Plants¶
shinro.plants
Robot plant models for simulation and control.
Provides concrete plant implementations that wrap robot kinematics and optionally attach a MuJoCo physics engine for mesh-accurate simulation.
Available plants: ArmRobot — 6-DOF serial-link arm with FK, Jacobian, IK HolonomicMobileRobot — N-wheel holonomic base with omni-wheel kinematics InvertedPendulum — 2D inverted pendulum with analytical dynamics CartPole — 4D cart-pole with coupled dynamics DoublePendulum — 4D planar double pendulum with analytical dynamics Quadrotor — 12D quadrotor with 4 rotor-speed inputs
ArmRobot¶
Bases: Plant
6-DOF robotic arm plant with forward kinematics, Jacobian, and IK.
Models a serial-link manipulator with configurable joint offsets and rotation axes. Supports two modes:
- Standalone — uses simplified FK/IK for quick testing.
- Physics engine — attaches a PhysicsEngine for mesh-accurate Jacobian IK via MuJoCo.
The arm operates in Cartesian space: step() takes a 6D velocity
twist \([dx, dy, dz, d\text{roll}, d\text{pitch}, d\text{yaw}]\),
integrates to a target pose, and uses inverse kinematics to compute
joint angles. The controller never touches joint space.
Forward kinematics uses homogeneous transforms (4x4) chained per joint. The Jacobian is computed via the geometric method (cross product of joint axes with position vectors). Inverse kinematics uses damped pseudoinverse with step clamping and joint limit enforcement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_dof
|
int
|
Number of degrees of freedom. |
required |
dt
|
float
|
Time step in seconds. |
required |
joint_limits
|
Array of shape (num_dof, 2) with [min, max] per joint. |
required | |
joint_offsets
|
Array of shape (num_dof, 3) with link offset vectors. |
required | |
rot_axes
|
list[str]
|
List of rotation axes per joint ( |
required |
joint_names
|
list[str] | None
|
Optional list of joint name strings. |
None
|
ee_body_name
|
str | None
|
Optional name of the end-effector body in the physics engine. |
None
|
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/plants/armrobot.py
physics_engine
¶
physics_engine(engine: PhysicsEngine | None)
Attach a physics engine.
When attached, the backend is inherited from the engine, the end-effector body name is auto-detected, and the initial state is read from the engine.
When detached, the standalone FK is used for state initialization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
PhysicsEngine | None
|
PhysicsEngine instance or None to detach. |
required |
Source code in src/shinro/plants/armrobot.py
get_model
¶
Get the discrete-time state-space model.
Returns:
| Type | Description |
|---|---|
|
Tuple of (A, B) where A = I_6 and B = dt * I_6. |
step
¶
Execute one control step.
When a physics engine is attached:
- Integrates the Cartesian velocity twist to get a target EE pose
(position + orientation, ZYX Euler rates — same math as standalone).
- Runs engine_ik() to compute joint angles.
- Sends joint angles to the engine via set_joint_ctrl.
When standalone:
- Integrates the state directly.
- Runs the standalone inverse_kinematics().
- Clips joint angles to limits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Control input (6,) — [dx, dy, dz, droll, dpitch, dyaw]. |
required |
Returns:
| Type | Description |
|---|---|
|
Joint angle vector (num_dof,). |
Source code in src/shinro/plants/armrobot.py
forward_kinematics
¶
Compute forward kinematics for a given joint configuration.
Chains the per-joint homogeneous transforms and returns the end-effector transform, joint positions, and joint axes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
joint_angles
|
Joint angle vector (num_dof,). |
required |
Returns:
| Type | Description |
|---|---|
|
Tuple of (T_ee, positions, axes): |
|
|
|
|
|
|
Source code in src/shinro/plants/armrobot.py
inverse_kinematics
¶
inverse_kinematics(target_pose, max_iters: int = 100, q_init=None, tol: float = 0.0001, max_step: float = 0.2)
Compute inverse kinematics via damped pseudoinverse.
Iteratively minimizes the pose error (position + orientation) using the geometric Jacobian. The orientation error is computed from the rotation matrix trace.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_pose
|
Target homogeneous transform (4, 4). |
required | |
max_iters
|
int
|
Maximum number of IK iterations. |
100
|
q_init
|
Initial joint angle guess. Defaults to last joint angles. |
None
|
|
tol
|
float
|
Convergence tolerance on position and orientation error. |
0.0001
|
max_step
|
float
|
Maximum joint angle change per iteration. |
0.2
|
Returns:
| Type | Description |
|---|---|
|
Joint angle vector (num_dof,) that achieves the target pose. |
Source code in src/shinro/plants/armrobot.py
engine_ik
¶
engine_ik(target_ee, target_euler=None, max_iters: int = 20, lam: float = 0.01, max_dq: float = 0.5)
Compute inverse kinematics using the physics engine's Jacobian.
Uses the engine's mesh-accurate Jacobian with damped least squares.
With target_euler given, solves the full 6D pose error (position +
orientation, world-frame log map); without it, solves position only
(the historical 3D path, byte-identical to previous behavior). Sets
joint positions in the engine and calls forward() each iteration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_ee
|
Target end-effector position (3,). |
required | |
target_euler
|
Optional target orientation as ZYX Euler [roll, pitch, yaw] (3,). When given, the 6D pose error is minimized. |
None
|
|
max_iters
|
int
|
Maximum IK iterations. |
20
|
lam
|
float
|
Damping factor for the least squares solve. |
0.01
|
max_dq
|
float
|
Maximum joint angle change per iteration. |
0.5
|
Returns:
| Type | Description |
|---|---|
|
Joint angle vector (num_dof,) that achieves the target pose. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If no physics engine is attached. |
Source code in src/shinro/plants/armrobot.py
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create an ArmRobot from a TOML config dict or ArmRobotConfig.
Config fields
joint_group: Name of the joint group in joint_groups.
num_dof: Number of degrees of freedom.
dt: Time step.
joint_offsets: List of link offset vectors.
rot_axes: List of rotation axes.
ee_body_name: Optional end-effector body name.
joint_groups + engine are runtime-injected by RobotSim. When
both are absent the arm is built in model-only standalone mode
(synthetic joint names, ±π limits) — enough for the compile pipeline to
derive n_x/n_u from a plant-authoritative [plant] section
without a live sim.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict (with runtime-injected |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
ArmRobot instance. |
Source code in src/shinro/plants/armrobot.py
HolonomicMobileRobot¶
Bases: Plant
Holonomic mobile robot with omni-wheel kinematics.
Models a robot with N wheels arranged symmetrically around a center. Maps world-frame velocity commands \([v_x, v_y, \omega]\) to individual wheel speeds via the kinematic matrix \(A_{\text{kin}}\).
The forward kinematics matrix \(A_{\text{kin}}\) maps body-frame velocities to wheel speeds:
where each row of \(A_{\text{kin}}\) is \([\sin(\theta_i), -\cos(\theta_i), -R]\) for wheel i at angle \(\theta_i\).
When a MuJoCo engine is attached, step() also stores wheel rotation
deltas for visual rolling in the physics simulation.
The kinematics matrix is built once in __init__ using numpy (scalar
trig from Python floats), then converted to the backend via
bk.from_numpy. All per-step operations use bk.xxx.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
num_wheels
|
int
|
Number of wheels (e.g., 3 for omni, 4 for mecanum). |
required |
radius_robots
|
float
|
Distance from robot center to each wheel (m). |
required |
gamma
|
float
|
Angle of the first wheel relative to the robot base (rad). |
required |
radius_wheels
|
float
|
Radius of each wheel (m). |
required |
dt
|
float
|
Simulation time step (s). |
required |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/plants/holonomicmobilerobot.py
physics_engine
¶
Attach a physics engine.
After attachment, step() uses the engine for visual wheel rolling.
The backend is inherited from the engine.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
PhysicsEngine instance or None to detach. |
required |
Source code in src/shinro/plants/holonomicmobilerobot.py
step
¶
Update robot state and compute wheel speeds.
Transforms the world-frame velocity command to body frame, computes wheel speeds via the kinematic matrix, and integrates the state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u_world
|
Desired velocity \([v_x, v_y, \omega]\) in world frame (3,). |
required |
Returns:
| Type | Description |
|---|---|
|
Wheel speed vector (n_wheels,). |
Source code in src/shinro/plants/holonomicmobilerobot.py
post_engine_step
¶
Reconcile the engine's free-joint base with the plant's integrated pose.
The plant self-integrates pose analytically; the MuJoCo base exists for
physics/rendering only. After engine.step() this writes (x, y) into
the free joint, pins the base quaternion upright and zeros the
floating-base velocities — the chassis follows the plant's kinematics,
not contact dynamics — then deposits the pending wheel-rotation deltas
onto the drive joints for visual rolling (consumed once per step).
No-op for engines without a free joint (e.g. a statically mounted fixture): plant state remains the source of truth either way.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
The physics engine stepping the world. |
required |
Source code in src/shinro/plants/holonomicmobilerobot.py
set_pose
¶
Set the robot's pose directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float
|
X position (m). |
required |
y
|
float
|
Y position (m). |
required |
theta
|
float
|
Orientation (rad). |
required |
get_state
¶
Return the current pose \([x, y, \theta]\).
Returns:
| Type | Description |
|---|---|
|
State vector (3,) — [x, y, theta]. |
get_model
¶
Get the discrete-time state-space model.
Returns:
| Type | Description |
|---|---|
|
Tuple of (A, B) where A = I_3 and B = dt * I_3. |
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create a HolonomicMobileRobot from a TOML config dict or HolonomicMobileRobotConfig.
Config fields
num_wheels: Number of wheels. radius_robots: Distance from center to each wheel (m). gamma: First wheel angle offset (rad). radius_wheels: Wheel radius (m). dt: Time step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict (may carry a runtime-injected |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
HolonomicMobileRobot instance. |
Source code in src/shinro/plants/holonomicmobilerobot.py
InvertedPendulum¶
Bases: Plant
2D inverted pendulum with standalone analytical dynamics and optional MuJoCo engine.
Models a simple pendulum with a point mass at the end of a massless rod, hinged at the origin. The state is the angle from upright \([\theta, \dot{\theta}]\) and the control is torque at the pivot \([\tau]\).
Supports two modes:
- Standalone — integrates the analytical dynamics using semi-implicit Euler (velocity updated before position).
- Physics engine — attaches a MuJoCo engine for mesh-accurate simulation.
The continuous-time dynamics are:
The linearized model is computed about an operating point supplied to
get_model, defaulting to the upright equilibrium
\((\theta=0, \dot{\theta}=0)\) where the continuous-time Jacobians
are:
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mass
|
float
|
Mass of the pendulum bob (kg). |
0.1
|
length
|
float
|
Length of the pendulum rod (m). |
0.5
|
damping
|
float
|
Linear damping coefficient at the pivot (Nms/rad). |
0.0
|
gravity
|
float
|
Gravitational acceleration (m/s^2). |
9.81
|
dt
|
float
|
Time step in seconds. |
0.01
|
state_bounds
|
tuple | None
|
Optional (min, max) bounds for state clipping. Each is an array of shape (2,). |
None
|
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/plants/inverted_pendulum.py
physics_engine
¶
physics_engine(engine: PhysicsEngine | None)
Attach or detach a physics engine.
When attached, the backend is inherited from the engine and the state is reset to zeros. When detached, the backend reverts to NumpyBackend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
PhysicsEngine | None
|
PhysicsEngine instance or None to detach. |
required |
Source code in src/shinro/plants/inverted_pendulum.py
get_state
¶
Get the current state \([\theta, \dot{\theta}]\).
When a physics engine is attached, reads joint position and velocity from the engine. Otherwise returns a copy of the internal state.
Returns:
| Type | Description |
|---|---|
|
State vector (2,) — [theta, theta_dot]. |
Source code in src/shinro/plants/inverted_pendulum.py
get_model
¶
Get the discrete-time state-space model around an operating point.
Linearizes the continuous-time dynamics \(f(x, u) = \dot{x}\)
around (x0, u0) using central finite differences via
shinro.utils.linearization.linearize_plant, then
Euler-discretizes at the plant's dt. When x0/u0 are
omitted, defaults to the upright equilibrium
\((\theta=0, \dot{\theta}=0)\) with zero control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x0
|
Operating point state (2,) — [theta, theta_dot]. Defaults to zeros (upright equilibrium). |
None
|
|
u0
|
Operating point control (1,) — [tau]. Defaults to zeros. |
None
|
|
eps
|
Step size for finite differences. |
1e-06
|
Returns:
| Type | Description |
|---|---|
|
Tuple of (A, B) where A = I + dt·∂f/∂x is (2, 2) and |
|
|
B = dt·∂f/∂u is (2, 1). |
Source code in src/shinro/plants/inverted_pendulum.py
dynamics
¶
Continuous-time dynamics \(\dot{x} = f(x, u)\).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State vector (2,) — [theta, theta_dot] — or a batch (N, 2). |
required | |
control
|
Control (1,), batch (N, 1), or scalar — [tau]. |
required | |
bk
|
Backend to evaluate with. Defaults to the plant's backend. |
None
|
Returns:
| Type | Description |
|---|---|
|
Time derivative with the rank of |
|
|
[theta_dot, theta_ddot]. |
Source code in src/shinro/plants/inverted_pendulum.py
measurement
¶
Bob position [l sin θ, l cos θ] relative to the pivot — a nonlinear sensor.
Measuring where the pendulum is rather than the angle directly: no
linear C can express this, so it is the model a config/scenario-driven
EKF/UKF gets from the plant. Follows the
shinro.components.Plant.measurement contract: bk-aware and
batch-capable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State vector (2,) — [theta, theta_dot] — or a batch (N, 2). |
required | |
bk
|
Backend to evaluate with. Defaults to the plant's backend. |
None
|
Returns:
| Type | Description |
|---|---|
|
Bob position |
Source code in src/shinro/plants/inverted_pendulum.py
step
¶
Execute one control step.
When a physics engine is attached, sets the torque actuator and advances the engine. Otherwise integrates the analytical dynamics using semi-implicit Euler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Control input (1,) or scalar — torque at pivot (Nm). |
required |
Returns:
| Type | Description |
|---|---|
|
New state vector (2,) — [theta, theta_dot]. |
Source code in src/shinro/plants/inverted_pendulum.py
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create an InvertedPendulum from a TOML config dict or InvertedPendulumConfig.
Config fields
mass: Pendulum bob mass (kg).
length: Pendulum rod length (m).
damping: Linear damping coefficient.
gravity: Gravitational acceleration (m/s^2).
dt: Time step.
state_bounds: Optional dict with min and max lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict (may carry a runtime-injected |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
InvertedPendulum instance. |
Source code in src/shinro/plants/inverted_pendulum.py
CartPole¶
Bases: Plant
4D cart-pole system with standalone analytical dynamics and optional MuJoCo engine.
Models a cart on a frictionless track with a pole hinged on top. The state is \([x, \dot{x}, \theta, \dot{\theta}]\) (cart position, cart velocity, pole angle from upright, pole angular velocity) and the control is horizontal force on the cart \([F]\).
Supports two modes:
- Standalone — integrates the coupled analytical dynamics using semi-implicit Euler (velocities updated before positions).
- Physics engine — attaches a MuJoCo engine for mesh-accurate simulation.
The equations of motion for the coupled system are:
The linearized model is computed about an operating point supplied to
get_model, defaulting to the upright equilibrium
\((x=0, \dot{x}=0, \theta=0, \dot{\theta}=0)\) where the
continuous-time Jacobians are:
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cart_mass
|
float
|
Mass of the cart (kg). |
0.5
|
pole_mass
|
float
|
Mass of the pole (kg). |
0.1
|
pole_length
|
float
|
Length of the pole (m). |
0.5
|
damping
|
float
|
Linear damping coefficient at the pole hinge (Nms/rad). |
0.0
|
gravity
|
float
|
Gravitational acceleration (m/s^2). |
9.81
|
dt
|
float
|
Time step in seconds. |
0.01
|
track_limits
|
tuple | None
|
Optional (min, max) bounds for cart position (m). |
None
|
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/plants/cartpole.py
physics_engine
¶
physics_engine(engine: PhysicsEngine | None)
Attach or detach a physics engine.
When attached, the backend is inherited from the engine and the state is reset to zeros. When detached, the backend reverts to NumpyBackend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
PhysicsEngine | None
|
PhysicsEngine instance or None to detach. |
required |
Source code in src/shinro/plants/cartpole.py
get_state
¶
Get the current state \([x, \dot{x}, \theta, \dot{\theta}]\).
When a physics engine is attached, reads joint positions and velocities from the engine. Otherwise returns a copy of the internal state.
Returns:
| Type | Description |
|---|---|
|
State vector (4,) — [x, x_dot, theta, theta_dot]. |
Source code in src/shinro/plants/cartpole.py
get_model
¶
Get the discrete-time state-space model around an operating point.
Linearizes the continuous-time dynamics \(f(x, u) = \dot{x}\)
around (x0, u0) using central finite differences via
shinro.utils.linearization.linearize_plant, then
Euler-discretizes at the plant's dt. When x0/u0 are
omitted, defaults to the upright equilibrium
\((x=0, \dot{x}=0, \theta=0, \dot{\theta}=0)\) with zero
control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x0
|
Operating point state (4,) — [x, x_dot, theta, theta_dot]. Defaults to zeros (upright equilibrium). |
None
|
|
u0
|
Operating point control (1,) — [F]. Defaults to zeros. |
None
|
|
eps
|
Step size for finite differences. |
1e-06
|
Returns:
| Type | Description |
|---|---|
|
Tuple of (A, B) where A = I + dt·∂f/∂x is (4, 4) and |
|
|
B = dt·∂f/∂u is (4, 1). |
Source code in src/shinro/plants/cartpole.py
dynamics
¶
Continuous-time dynamics \(\dot{x} = f(x, u)\).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State vector (4,) — [x, x_dot, theta, theta_dot] — or a batch (N, 4). |
required | |
control
|
Control (1,), batch (N, 1), or scalar — [F]. |
required | |
bk
|
Backend to evaluate with. Defaults to the plant's backend. |
None
|
Returns:
| Type | Description |
|---|---|
|
Time derivative with the rank of |
|
|
[x_dot, x_ddot, theta_dot, theta_ddot]. |
Source code in src/shinro/plants/cartpole.py
step
¶
Execute one control step.
When a physics engine is attached, sets the cart force actuator and advances the engine. Otherwise integrates the coupled analytical dynamics using semi-implicit Euler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Control input (1,) or scalar — horizontal force on cart (N). |
required |
Returns:
| Type | Description |
|---|---|
|
New state vector (4,) — [x, x_dot, theta, theta_dot]. |
Source code in src/shinro/plants/cartpole.py
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create a CartPole from a TOML config dict or CartPoleConfig.
Config fields
cart_mass: Mass of the cart (kg). pole_mass: Mass of the pole (kg). pole_length: Length of the pole (m). damping: Linear damping coefficient. gravity: Gravitational acceleration (m/s^2). dt: Time step. track_limits: Optional list of [min, max] for cart position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict (may carry a runtime-injected |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
CartPole instance. |
Source code in src/shinro/plants/cartpole.py
DoublePendulum¶
Bases: Plant
4D planar double pendulum with standalone analytical dynamics and optional MuJoCo engine.
Models two point masses at the ends of two massless rods, hinged in series. The state is \([\theta_1, \theta_2, \omega_1, \omega_2]\) (angle of each rod from the downward vertical and its angular velocity) and the control is joint torques \([\tau_1, \tau_2]\) applied at each hinge. The rest equilibrium \(\theta=0\) is stable (hanging down).
Supports two modes:
- Standalone — integrates the analytical dynamics using semi-implicit Euler (velocities updated before positions).
- Physics engine — attaches a MuJoCo engine for mesh-accurate
simulation (expects an MJCF model with
hinge_1/hinge_2joints andtorque_1/torque_2motor actuators).
The equations of motion are the standard double pendulum manipulator form:
where \(M\) is the mass matrix, \(C\) the Coriolis matrix, and \(G\) the gravity vector. The angular acceleration follows from \(\ddot{\theta} = M^{-1}(\tau - C\dot{\theta} - G)\).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mass_top
|
float
|
Mass of the top pendulum bob (kg). |
0.1
|
mass_bottom
|
float
|
Mass of the bottom pendulum bob (kg). |
0.1
|
length_top
|
float
|
Length of the top rod (m). |
0.5
|
length_bottom
|
float
|
Length of the bottom rod (m). |
0.5
|
dt
|
float
|
Time step in seconds. |
0.01
|
g
|
float
|
Gravitational acceleration (m/s^2). |
9.81
|
state_bounds
|
tuple | None
|
Optional (min, max) bounds for state clipping. Each is an array of shape (4,). |
None
|
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/plants/double_pendulum.py
physics_engine
¶
physics_engine(engine: PhysicsEngine | None)
Attach or detach a physics engine.
When attached, the backend is inherited from the engine and the state is reset to zeros. When detached, the backend reverts to NumpyBackend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
PhysicsEngine | None
|
PhysicsEngine instance or None to detach. |
required |
Source code in src/shinro/plants/double_pendulum.py
dynamics
¶
Continuous-time dynamics \(\dot{x} = f(x, u)\), batch-capable.
State ordering is \([\theta_1, \theta_2, \omega_1, \omega_2]\) and control is \([\tau_1, \tau_2]\). The angular acceleration solves the manipulator equation \(\ddot{\theta} = M^{-1}(\tau - C\dot{\theta} - G)\), with
\(C\dot{\theta} = [m_2 l_1 l_2 \sin\Delta\,\omega_2^2, -m_2 l_1 l_2 \sin\Delta\,\omega_1^2]\), and \(G = [(m_1 + m_2) g l_1 \sin\theta_1, m_2 g l_2 \sin\theta_2]\).
The 2x2 solve is written in closed form (Cramer's rule) rather than
bk.solve: on the batched path M would be rank-3 ((N, 2, 2)),
which the graph backend — strictly 2-D — does not represent. The
entries are therefore (N, 1) columns, and the determinant
\(m_2 l_1^2 l_2^2 (m_1 + m_2 \sin^2\Delta)\) is strictly
positive for positive masses, so the division is always safe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State (4,) — [theta_1, theta_2, omega_1, omega_2] — or a batch (N, 4). |
required | |
control
|
Control (2,), batch (N, 2), or scalar — [tau_1, tau_2]. |
required | |
bk
|
Backend to evaluate with. Defaults to the plant's backend. |
None
|
Returns:
| Type | Description |
|---|---|
|
Time derivative with the rank of |
|
|
[omega_1, omega_2, theta_1_ddot, theta_2_ddot]. |
Source code in src/shinro/plants/double_pendulum.py
get_model
¶
Get the discrete-time state-space model around an operating point.
Linearizes the continuous-time dynamics \(f(x, u) = \dot{x}\)
around (x0, u0) using central finite differences via
shinro.utils.linearization.linearize_plant, then
Euler-discretizes at the plant's dt. When x0/u0 are
omitted, defaults to the rest equilibrium
\((\theta=0, \dot{\theta}=0)\) with zero control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x0
|
Operating point state (4,) — [theta_1, theta_2, omega_1, omega_2]. Defaults to zeros. |
None
|
|
u0
|
Operating point control (2,) — [tau_1, tau_2]. Defaults to zeros. |
None
|
|
eps
|
Step size for finite differences. |
1e-06
|
Returns:
| Type | Description |
|---|---|
|
Tuple of (A, B) where A = I + dt·∂f/∂x is (4, 4) and |
|
|
B = dt·∂f/∂u is (4, 2). |
Source code in src/shinro/plants/double_pendulum.py
get_state
¶
Get the current state \([\theta_1, \theta_2, \omega_1, \omega_2]\).
When a physics engine is attached, reads joint positions and velocities from the engine. Otherwise returns a copy of the internal state.
Returns:
| Type | Description |
|---|---|
|
State vector (4,) — [theta_1, theta_2, omega_1, omega_2]. |
Source code in src/shinro/plants/double_pendulum.py
step
¶
Execute one control step.
When a physics engine is attached, sets the torque actuators and advances the engine. Otherwise integrates the analytical dynamics using semi-implicit Euler (velocities updated before positions).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Control input (2,) or scalar — [tau_1, tau_2] joint torques (Nm). |
required |
Returns:
| Type | Description |
|---|---|
|
New state vector (4,) — [theta_1, theta_2, omega_1, omega_2]. |
Source code in src/shinro/plants/double_pendulum.py
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create a DoublePendulum from a TOML config dict or DoublePendulumConfig.
Config fields
mass_top: Top bob mass (kg).
mass_bottom: Bottom bob mass (kg).
length_top: Top rod length (m).
length_bottom: Bottom rod length (m).
dt: Time step.
g: Gravitational acceleration (m/s^2).
state_bounds: Optional dict with min and max lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict (may carry a runtime-injected |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
DoublePendulum instance. |
Source code in src/shinro/plants/double_pendulum.py
Quadrotor¶
Bases: Plant
12-state quadrotor with four rotor-speed inputs.
The state is \([x, y, z, \phi, \theta, \psi, \dot{x}, \dot{y}, \dot{z}, p, q, r]\) — position, Euler angles (roll/pitch/yaw), world-frame linear velocity, and body-frame angular velocity. The control is the four rotor angular speeds \([\omega_1, \omega_2, \omega_3, \omega_4]\).
Rotor placement is a configurable mixer. Each rotor sits at body \((x_i, y_i)\) and has a spin sign \(\sigma_i\) (\(+1\) counter-clockwise seen from above, \(-1\) clockwise). Rotor \(i\) contributes thrust \(k\omega_i^2\) along body \(+z\), so its moment about the body axes is \(k\omega_i^2 (y_i, -x_i, 0)\), and its yaw reaction is \(-b\sigma_i\omega_i^2\) about \(z\):
The default mixer (no rotors config) is the + layout
\([(\ell,0), (0,\ell), (-\ell,0), (0,-\ell)]\) with spins
\([+,-,+,-]\), which gives the familiar
\(\tau_x = k\ell(\omega_2^2 - \omega_4^2)\),
\(\tau_y = k\ell(\omega_3^2 - \omega_1^2)\),
\(\tau_z = b(\omega_2^2 + \omega_4^2 - \omega_1^2 - \omega_3^2)\).
A different numbering/placement is just a different table; see
RotorConfig and samples/plants/quadrotor.toml.
The translational dynamics apply the body thrust along body \(+z\) through the rotation matrix \(R = R_z(\psi) R_y(\theta) R_x(\phi)\) and cancel gravity:
The rotational dynamics are Euler's rigid-body equations with the diagonal inertia \(I = \mathrm{diag}(I_{xx}, I_{yy}, I_{zz})\),
and the Euler angles evolve through the body-rate kinematics \(\dot{\boldsymbol{\eta}} = T(\phi, \theta)\,\boldsymbol{\omega}\).
Supports standalone analytical integration (explicit-Euler step) and
dynamics for finite-difference linearization and batched MPPI
rollouts. MuJoCo engine mode is not yet implemented: attaching an engine
only wires the backend, and step refuses to run while one is attached.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mass
|
float
|
Total mass (kg). |
0.5
|
radius
|
float
|
Rotor-to-center arm length \(\ell\) (m). |
0.1
|
inertia
|
list[float] | None
|
Body-frame principal moments |
None
|
dt
|
float
|
Integration step (s). |
0.01
|
g
|
float
|
Gravitational acceleration (m/s²). |
9.81
|
thrust_coeff
|
float
|
Rotor thrust coefficient \(k\) (N·s²/rad²). |
1.0
|
torque_coeff
|
float
|
Rotor drag/yaw coefficient \(b\) (N·m·s²/rad²). |
0.1
|
rotors
|
list[tuple[float, float, float]] | None
|
Optional mixer table of |
None
|
state_bounds
|
tuple | None
|
Optional |
None
|
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/plants/quadrotor.py
physics_engine
¶
physics_engine(engine: PhysicsEngine | None)
Attach or detach a physics engine.
Engine-backed simulation is not yet implemented for the quadrotor; this
only stores the engine and inherits its backend so a future MJCF mapping
can reuse the same wiring. step refuses to run while an engine is
attached, so the analytic model is never silently bypassed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
PhysicsEngine | None
|
PhysicsEngine instance or None to detach. |
required |
Source code in src/shinro/plants/quadrotor.py
dynamics
¶
Continuous-time dynamics \(\dot{x} = f(x, u)\), batch-capable.
State ordering is \([x, y, z, \phi, \theta, \psi, \dot{x}, \dot{y}, \dot{z}, p, q, r]\) and control is the four rotor speeds \([\omega_1, \omega_2, \omega_3, \omega_4]\). The returned derivative is \([\dot{x}, \dot{y}, \dot{z}, \dot{\phi}, \dot{\theta}, \dot{\psi}, \ddot{x}, \ddot{y}, \ddot{z}, \dot{p}, \dot{q}, \dot{r}]\).
The rotation and Euler-rate terms are expanded into their closed-form
components rather than applied as 3x3 matrix products: on the batched
path the matrices would be rank-3 (N, 3, 3), which the graph backend
(strictly 2-D) does not represent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State (12,) — [position, Euler angles, world velocity, body rates] — or a batch (N, 12). |
required | |
control
|
Control (4,), batch (N, 4), or scalar — rotor angular speeds. |
required | |
bk
|
Backend to evaluate with. Defaults to the plant's backend. |
None
|
Returns:
| Type | Description |
|---|---|
|
Time derivative with the rank of |
Source code in src/shinro/plants/quadrotor.py
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | |
get_state
¶
Get the current state.
Returns:
| Type | Description |
|---|---|
|
State vector (12,) — [x, y, z, roll, pitch, yaw, vx, vy, vz, |
|
|
p, q, r]. |
get_model
¶
Get the discrete-time state-space model around an operating point.
Linearizes the continuous-time dynamics \(f(x, u) = \dot{x}\)
around (x0, u0) using central finite differences via
shinro.utils.linearization.linearize_plant, then
Euler-discretizes at the plant's dt. When x0 is omitted it
defaults to the level, zero-velocity state; when u0 is omitted it
defaults to the rotor speed that balances gravity,
\(\omega_{\text{hover}} = \sqrt{mg/(4k)}\), so the model is
linearized about the true hover equilibrium (u0 = 0 would give
B = 0 because the rotor forces are quadratic in speed).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x0
|
Operating point state (12,). Defaults to zeros (level attitude, zero velocity). |
None
|
|
u0
|
Operating point control (4,) — rotor speeds. Defaults to the hover rotor speed \(\sqrt{mg/(4k)}\) on all four rotors. |
None
|
|
eps
|
Step size for finite differences. |
1e-06
|
Returns:
| Type | Description |
|---|---|
|
Tuple of (A, B) where A = I + dt·∂f/∂x is (12, 12) and |
|
|
B = dt·∂f/∂u is (12, 4). |
Source code in src/shinro/plants/quadrotor.py
operating_control
¶
Hover rotor speeds — the equilibrium the control matrix is evaluated at.
The rotors' thrust is quadratic in speed, so g(x, 0) = 0; the
tangent model (shinro.components.Plant.model_terms) must be
taken about the hover equilibrium
\(\omega_{\text{hover}} = \sqrt{mg/(4k)}\) rather than zero.
Returns:
| Type | Description |
|---|---|
|
Hover rotor speeds (4,). |
Source code in src/shinro/plants/quadrotor.py
step
¶
Execute one control step with explicit Euler integration.
Velocities (world linear + body angular) are updated first, then positions and Euler angles are advanced with the updated velocities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
u
|
Control input (4,) or scalar — rotor angular speeds. |
required |
Returns:
| Type | Description |
|---|---|
|
New state vector (12,). |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If a physics engine is attached (MuJoCo engine mode is not implemented yet). |
Source code in src/shinro/plants/quadrotor.py
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create a Quadrotor from a TOML config dict or QuadrotorConfig.
Config fields
mass: Total mass (kg).
radius: Rotor arm length (m) — used for the default + mixer.
inertia: Body-frame principal moments (kg·m²).
thrust_coeff: Rotor thrust coefficient.
torque_coeff: Rotor drag/yaw coefficient.
dt: Time step (s).
g: Gravitational acceleration (m/s²).
rotors: Optional [[rotors]] mixer table (x, y, spin
per entry); overrides the default layout.
state_bounds: Optional dict with min and max lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict (may carry a runtime-injected |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
Quadrotor instance. |