Skip to content

Core ABCs

shinro.components


ConfigDriven

Mixin for components constructed from a TOML config.

Subclasses declare a frozen Config dataclass whose fields ARE the config schema. parse_config strict-parses a raw TOML dict into it: unknown keys, a wrong type value, and missing required fields are loud errors naming the component, so authoring typos surface at parse time instead of mid-simulation.

Config class-attribute

Config: Any = None

The component's strict config dataclass. Subclasses must override.

parse_config classmethod

parse_config(config: Any) -> Any

Strict-parse a TOML config dict into cls.Config.

An already-built Config instance is passed through unchanged.

Parameters:

Name Type Description Default
config Any

Raw TOML dict (with optional type key) or a Config instance.

required

Returns:

Type Description
Any

A cls.Config instance.

Raises:

Type Description
NotImplementedError

If the subclass has not declared Config.

ValueError

On unknown keys, a mismatched type key, or missing required fields.

Source code in src/shinro/components.py
@classmethod
def parse_config(cls, config: Any) -> Any:
    """Strict-parse a TOML config dict into ``cls.Config``.

    An already-built Config instance is passed through unchanged.

    Args:
        config: Raw TOML dict (with optional ``type`` key) or a ``Config``
            instance.

    Returns:
        A ``cls.Config`` instance.

    Raises:
        NotImplementedError: If the subclass has not declared ``Config``.
        ValueError: On unknown keys, a mismatched ``type`` key, or missing
            required fields.
    """
    spec = cls.Config
    if spec is None:
        raise NotImplementedError(f"{cls.__name__} must define a Config dataclass")
    from shinro.utils.config_spec import strict_from_dict

    if isinstance(config, dict):
        return strict_from_dict(spec, config, getattr(cls, "_registry_name", cls.__name__))
    return config

load_config classmethod

load_config(config, plant=None, derive_model: bool = False)

Strict-parse a TOML config into cls.Config, then inject plant-derived values.

With plant, dt is filled from the plant when the config omits it and a declared dt that disagrees with the plant's is a loud error (the plant is the source of truth). With derive_model, A_dynamics/B_dynamics are additionally derived from the plant when the config declares neither. See shinro.utils.linearization.inject_plant_derived.

Parameters:

Name Type Description Default
config

Raw TOML dict (with optional type key) or a Config instance.

required
plant

Optional plant for derived-value injection.

None
derive_model bool

Also derive A_dynamics/B_dynamics from the plant when both are omitted.

False

Returns:

Type Description

A cls.Config instance.

Raises:

Type Description
ValueError

On strict-parse failure or a dt disagreement.

Source code in src/shinro/components.py
@classmethod
def load_config(cls, config, plant=None, derive_model: bool = False):
    """Strict-parse a TOML config into ``cls.Config``, then inject plant-derived values.

    With ``plant``, ``dt`` is filled from the plant when the config omits
    it and a declared ``dt`` that disagrees with the plant's is a loud
    error (the plant is the source of truth). With ``derive_model``,
    ``A_dynamics``/``B_dynamics`` are additionally derived from the plant
    when the config declares neither. See
    :func:`shinro.utils.linearization.inject_plant_derived`.

    Args:
        config: Raw TOML dict (with optional ``type`` key) or a ``Config``
            instance.
        plant: Optional plant for derived-value injection.
        derive_model: Also derive ``A_dynamics``/``B_dynamics`` from the
            plant when both are omitted.

    Returns:
        A ``cls.Config`` instance.

    Raises:
        ValueError: On strict-parse failure or a ``dt`` disagreement.
    """
    if isinstance(config, str):
        import tomllib

        from shinro.utils.config_resolver import resolve_config_path

        with open(resolve_config_path(config), "rb") as f:
            config = tomllib.load(f)
    cfg = cls.parse_config(config)
    if plant is not None:
        from shinro.utils.linearization import inject_plant_derived

        cfg = inject_plant_derived(cfg, plant, with_model=derive_model)
    return cfg

PhysicsEngine

Bases: ABC

Abstract base class for physics engines (MuJoCo, PyBullet, Drake, etc.).

Provides name-based access to joints, bodies, and actuators. Plants use this interface instead of importing a physics engine directly.

dt abstractmethod property

dt: float

Simulation timestep.

nv abstractmethod property

nv: int

Number of velocity DOFs (for Jacobian column count).

joint_names abstractmethod property

joint_names: list[str]

List of all joint names in the model.

actuator_names abstractmethod property

actuator_names: list[str]

List of all actuator names in the model.

body_names abstractmethod property

body_names: list[str]

List of all body names in the model.

has_free_joint property

has_free_joint: bool

Whether the model's first joint is a free (floating) joint.

backend property

backend: ArrayBackend

Array backend for this engine. Subclasses may override.

get_joint_qpos abstractmethod

get_joint_qpos(name: str) -> float

Get a single joint position by name.

Source code in src/shinro/components.py
@abstractmethod
def get_joint_qpos(self, name: str) -> float:
    """Get a single joint position by name."""
    pass

set_joint_qpos abstractmethod

set_joint_qpos(name: str, value: float)

Set a single joint position by name.

Source code in src/shinro/components.py
@abstractmethod
def set_joint_qpos(self, name: str, value: float):
    """Set a single joint position by name."""
    pass

get_joint_vel abstractmethod

get_joint_vel(name: str) -> float

Get a single joint velocity by name.

Source code in src/shinro/components.py
@abstractmethod
def get_joint_vel(self, name: str) -> float:
    """Get a single joint velocity by name."""
    pass

set_joint_ctrl abstractmethod

set_joint_ctrl(name: str, value: float)

Set a single actuator control signal by name.

Source code in src/shinro/components.py
@abstractmethod
def set_joint_ctrl(self, name: str, value: float):
    """Set a single actuator control signal by name."""
    pass

get_joint_limits abstractmethod

get_joint_limits(name: str) -> tuple[float, float]

Get [min, max] limits for a joint by name.

Source code in src/shinro/components.py
@abstractmethod
def get_joint_limits(self, name: str) -> tuple[float, float]:
    """Get [min, max] limits for a joint by name."""
    pass

get_body_xpos abstractmethod

get_body_xpos(name: str) -> ndarray

Get 3D position of a body by name.

Source code in src/shinro/components.py
@abstractmethod
def get_body_xpos(self, name: str) -> np.ndarray:
    """Get 3D position of a body by name."""
    pass

get_body_xquat abstractmethod

get_body_xquat(name: str) -> ndarray

Get 3D orientation of a body by name as a quaternion (w, x, y, z).

Source code in src/shinro/components.py
@abstractmethod
def get_body_xquat(self, name: str) -> np.ndarray:
    """Get 3D orientation of a body by name as a quaternion (w, x, y, z)."""
    pass

get_body_id abstractmethod

get_body_id(name: str) -> int

Get the internal body ID for a named body. Returns -1 if not found.

Source code in src/shinro/components.py
@abstractmethod
def get_body_id(self, name: str) -> int:
    """Get the internal body ID for a named body. Returns -1 if not found."""
    pass

compute_jacobian abstractmethod

compute_jacobian(body_name: str) -> tuple[ndarray, ndarray]

Return (jacp, jacr) — 3×nv position and orientation Jacobians for a body.

Source code in src/shinro/components.py
@abstractmethod
def compute_jacobian(self, body_name: str) -> tuple[np.ndarray, np.ndarray]:
    """Return (jacp, jacr) — 3×nv position and orientation Jacobians for a body."""
    pass

compute_jacobian_for_joints abstractmethod

compute_jacobian_for_joints(body_name: str, joint_names: list[str]) -> ndarray

Return the 6×k Jacobian (position + orientation) for a body, extracting only the columns corresponding to the named joints. Returns shape (6, k).

Source code in src/shinro/components.py
@abstractmethod
def compute_jacobian_for_joints(self, body_name: str, joint_names: list[str]) -> np.ndarray:
    """Return the 6×k Jacobian (position + orientation) for a body, extracting only
    the columns corresponding to the named joints. Returns shape (6, k)."""
    pass

forward abstractmethod

forward()

Run forward kinematics (mj_forward equivalent).

Source code in src/shinro/components.py
@abstractmethod
def forward(self):
    """Run forward kinematics (mj_forward equivalent)."""
    pass

step abstractmethod

step()

Advance physics by one timestep.

Source code in src/shinro/components.py
@abstractmethod
def step(self):
    """Advance physics by one timestep."""
    pass

reset abstractmethod

reset(qpos: ndarray | None = None)

Reset simulation state.

Source code in src/shinro/components.py
@abstractmethod
def reset(self, qpos: np.ndarray | None = None):
    """Reset simulation state."""
    pass

get_sensor_data abstractmethod

get_sensor_data() -> dict

Return a dict of all sensor data (qpos, qvel, ctrl, time, etc.).

Source code in src/shinro/components.py
@abstractmethod
def get_sensor_data(self) -> dict:
    """Return a dict of all sensor data (qpos, qvel, ctrl, time, etc.)."""
    pass

pin_free_base

pin_free_base(x: float, y: float) -> None

Pin a floating planar base to (x, y) with upright orientation.

For engines whose world contains a free joint driven by a self-integrating plant (see Plant.post_engine_step): writes the plant's planar pose into the base, pins its orientation upright, and zeroes the floating-base velocities. Default: no-op — engines without a floating base (or with a contact-physics-owned base) need not override.

Source code in src/shinro/components.py
def pin_free_base(self, x: float, y: float) -> None:
    """Pin a floating planar base to (x, y) with upright orientation.

    For engines whose world contains a free joint driven by a
    self-integrating plant (see :meth:`Plant.post_engine_step`): writes the
    plant's planar pose into the base, pins its orientation upright, and
    zeroes the floating-base velocities. Default: no-op — engines without a
    floating base (or with a contact-physics-owned base) need not override.
    """
    return None

Controller

Bases: ConfigDriven, ABC

Abstract base class for all controllers.

A controller computes control actions (e.g., torques, voltages) from reference signals and/or state feedback. Subclasses implement specific control laws (PID, MPC, LQR, etc.).

Usage

controller = MyController(...) action = controller.compute(reference=ref, state=x) controller.reset()

compute abstractmethod

compute(*args: Any, **kwargs: Any) -> Any

Compute the control action.

Parameters:

Name Type Description Default
*args Any

Positional arguments for computation.

()
**kwargs Any

Keyword arguments for computation.

{}

Returns:

Type Description
Any

The computed control action.

Source code in src/shinro/components.py
@abstractmethod
def compute(self,*args:Any,**kwargs:Any)-> Any:
    """
    Compute the control action.

    Args:
        *args: Positional arguments for computation.
        **kwargs: Keyword arguments for computation.

    Returns:
        The computed control action.
    """
    pass

reset

reset(*args: Any, **kwargs: Any) -> Any

Reset the controller to its initial state.

Parameters:

Name Type Description Default
*args Any

Positional arguments for reset.

()
**kwargs Any

Keyword arguments for reset.

{}

Returns:

Type Description
Any

None.

Source code in src/shinro/components.py
def reset(self,*args:Any,**kwargs:Any)-> Any:
    """
    Reset the controller to its initial state.

    Args:
        *args: Positional arguments for reset.
        **kwargs: Keyword arguments for reset.

    Returns:
        None.
    """
    pass

Plant

Bases: ConfigDriven, ABC

Abstract base class for a system plant.

A plant represents the system to be controlled (e.g., a robot, motor, or dynamical system). It provides the current state, a model for prediction/optimization, and a step method for simulation.

Usage

plant = MyPlant(...) state = plant.get_state() model = plant.get_model() next_state = plant.step(control_input)

input_dim class-attribute instance-attribute

input_dim: int | None = None

Control input dimension.

Plants that support linearization via linearize_plant should set this in __init__ so get_model() can default u0 correctly. None means the plant has not declared its input dimension; callers of linearize_plant must then pass u0 explicitly.

get_state abstractmethod

get_state(*args: Any, **kwargs: Any) -> Any

Get the current state of the plant.

Returns:

Type Description
Any

The current state (e.g., joint positions, velocities).

Source code in src/shinro/components.py
@abstractmethod
def get_state(self, *args:Any, **kwargs:Any)->Any:
    """
    Get the current state of the plant.

    Returns:
        The current state (e.g., joint positions, velocities).
    """
    pass

get_model abstractmethod

get_model(*args: Any, **kwargs: Any) -> Any

Get the mathematical model of the plant.

For linear plants, returns (A, B) state-space matrices. For nonlinear plants, accepts optional x0 and u0 keyword arguments to linearize the dynamics around an operating point.

Returns:

Type Description
Any

The plant model (e.g., a dynamics function, matrices, or

Any

a callable used by the controller for prediction).

Source code in src/shinro/components.py
@abstractmethod
def get_model(self, *args:Any, **kwargs:Any)->Any:
    """
    Get the mathematical model of the plant.

    For linear plants, returns (A, B) state-space matrices.
    For nonlinear plants, accepts optional ``x0`` and ``u0`` keyword
    arguments to linearize the dynamics around an operating point.

    Returns:
        The plant model (e.g., a dynamics function, matrices, or
        a callable used by the controller for prediction).
    """
    pass

step abstractmethod

step(*args: Any, **kwargs: Any) -> Any

Perform a single time step simulation or execution of the plant.

Parameters:

Name Type Description Default
*args Any

Positional arguments (e.g., control input).

()
**kwargs Any

Keyword arguments.

{}

Returns:

Type Description
Any

The next state or result of the step.

Source code in src/shinro/components.py
@abstractmethod
def step(self, *args:Any, **kwargs:Any)->Any:
    """
    Perform a single time step simulation or execution of the plant.

    Args:
        *args: Positional arguments (e.g., control input).
        **kwargs: Keyword arguments.

    Returns:
        The next state or result of the step.
    """
    pass

physics_engine abstractmethod

physics_engine(engine: PhysicsEngine | None, *args: Any, **kwargs: Any) -> Any

Attach a physics engine to the plant.

Parameters:

Name Type Description Default
engine PhysicsEngine | None

A PhysicsEngine instance, or None to detach.

required
Source code in src/shinro/components.py
@abstractmethod
def physics_engine(self, engine: PhysicsEngine | None, *args: Any, **kwargs: Any)-> Any:
    """
    Attach a physics engine to the plant.

    Args:
        engine: A PhysicsEngine instance, or None to detach.
    """
    pass

reset_state

reset_state() -> None

Zero the plant's analytic state at a run boundary.

Concrete default preserves historical behavior (RobotSim.reset zeroed self.state whenever it was a numpy array); plants with custom reset semantics (e.g. state derived from the engine) override this. Called by the simulation factory's reset for every plant.

Source code in src/shinro/components.py
def reset_state(self) -> None:
    """Zero the plant's analytic state at a run boundary.

    Concrete default preserves historical behavior (``RobotSim.reset`` zeroed
    ``self.state`` whenever it was a numpy array); plants with custom reset
    semantics (e.g. state derived from the engine) override this. Called by
    the simulation factory's ``reset`` for every plant.
    """
    state = getattr(self, "state", None)
    if isinstance(state, np.ndarray):
        self.state = np.zeros_like(state)

post_engine_step

post_engine_step(engine: PhysicsEngine) -> None

Reconcile plant state with the engine after engine.step().

Called once per plant by the simulation factory after the shared engine advances the world. Plants that self-integrate their dynamics (e.g. a wheeled base with analytical kinematics under a sim-backed scenario) override this to write their state back into the engine — syncing a floating base, depositing visual joint deltas, etc. Plants whose dynamics are fully owned by the engine need not override.

Parameters:

Name Type Description Default
engine PhysicsEngine

The physics engine this plant is attached to.

required
Source code in src/shinro/components.py
def post_engine_step(self, engine: "PhysicsEngine") -> None:
    """Reconcile plant state with the engine after ``engine.step()``.

    Called once per plant by the simulation factory after the shared
    engine advances the world. Plants that self-integrate their dynamics
    (e.g. a wheeled base with analytical kinematics under a sim-backed
    scenario) override this to write their state back into the engine —
    syncing a floating base, depositing visual joint deltas, etc. Plants
    whose dynamics are fully owned by the engine need not override.

    Args:
        engine: The physics engine this plant is attached to.
    """
    return None

dynamics

dynamics(state: Any, control: Any) -> Any

Continuous-time dynamics \(\dot{x} = f(x, u)\).

Override in nonlinear plants to expose the dynamics function for linearization. Returns None by default (linear plants need not override this).

Parameters:

Name Type Description Default
state Any

Current state vector (n_x,).

required
control Any

Control input vector (n_u,).

required

Returns:

Type Description
Any

Time derivative of the state (n_x,), or None if not implemented.

Source code in src/shinro/components.py
def dynamics(self, state: Any, control: Any) -> Any:
    """Continuous-time dynamics :math:`\\dot{x} = f(x, u)`.

    Override in nonlinear plants to expose the dynamics function for
    linearization. Returns None by default (linear plants need not
    override this).

    Args:
        state: Current state vector (n_x,).
        control: Control input vector (n_u,).

    Returns:
        Time derivative of the state (n_x,), or None if not implemented.
    """
    return None

StateEstimator

Bases: ConfigDriven, ABC

Abstract base class for state estimators.

A state estimator reconstructs the full system state from noisy measurements and known control inputs. Subclasses implement filters such as Kalman filters, observers, or complementary filters.

Usage

estimator = MyEstimator(...) state = estimator.estimate(measurement=y, control_input=u) estimator.reset()

estimate abstractmethod

estimate(measurement: Any, control_input: Any) -> Any

Estimate the current state from a measurement and control input.

Parameters:

Name Type Description Default
measurement Any

Sensor measurement (e.g., encoder reading, IMU).

required
control_input Any

Control input applied to the system.

required

Returns:

Type Description
Any

The estimated state.

Source code in src/shinro/components.py
@abstractmethod
def estimate(self,measurement:Any,control_input:Any)->Any:
    """
    Estimate the current state from a measurement and control input.

    Args:
        measurement: Sensor measurement (e.g., encoder reading, IMU).
        control_input: Control input applied to the system.

    Returns:
        The estimated state.
    """
    pass

reset

reset()

Reset the estimator to its initial condition.

Clears any internal buffer or covariance state.

Source code in src/shinro/components.py
def reset(self):
    """
    Reset the estimator to its initial condition.

    Clears any internal buffer or covariance state.
    """
    pass

TrajectoryGenerator

Bases: ConfigDriven, ABC

Abstract base class for trajectory generators.

A trajectory generator produces a reference path (position, velocity, acceleration) from a start to an end configuration. Subclasses implement splines, minimum-jerk, or motion-primitive generators.

Usage

generator = MyTrajectoryGenerator(...) trajectory = generator.generate(start=start_pos, end=end_pos) generator.reset()

generate abstractmethod

generate(start_position: Any, end_position: Any, duration: Any, *args: Any, **kwargs: Any) -> Any

Generate a trajectory from start to end position over a given duration.

Parameters:

Name Type Description Default
start_position Any

Initial configuration.

required
end_position Any

Final target configuration.

required
duration Any

Total time duration of the trajectory in seconds.

required

Returns:

Type Description
Any

The generated trajectory (e.g., a sequence of waypoints or

Any

a callable that returns setpoints at a given time).

Source code in src/shinro/components.py
@abstractmethod
def generate(self, start_position: Any, end_position: Any, duration: Any, *args: Any, **kwargs: Any) -> Any:
    """
    Generate a trajectory from start to end position over a given duration.

    Args:
        start_position: Initial configuration.
        end_position: Final target configuration.
        duration: Total time duration of the trajectory in seconds.

    Returns:
        The generated trajectory (e.g., a sequence of waypoints or
        a callable that returns setpoints at a given time).
    """
    pass

reset

reset()

Reset the trajectory generator to its initial state.

Clears any cached or ongoing trajectory data.

Source code in src/shinro/components.py
def reset(self):
    """
    Reset the trajectory generator to its initial state.

    Clears any cached or ongoing trajectory data.
    """
    pass