Controllers¶
shinro.controllers
Control algorithms for state regulation and trajectory tracking.
Provides discrete-time controllers that compute control actions from state feedback. All controllers implement the Controller ABC.
Available controllers: LQR — Linear Quadratic Regulator (DARE-based optimal gain) PIDController — Proportional-Integral-Derivative with anti-windup MPC_LTI — Linear Time-Invariant MPC with OSQP QP solver MPC_LTI_DeltaU — MPC with Δu (control rate) regularization MPPIController — Model Predictive Path Integral (sampling-based) SlidingModeController — Sliding Mode Control (robust nonlinear)
LQR¶
Bases: Controller
Linear Quadratic Regulator for discrete-time systems.
Computes the optimal state-feedback control law \(u = -K (x - x_t)\) that minimizes:
The gain K is computed once via the Discrete Algebraic Riccati Equation (DARE) and applied online as a single matrix-vector multiply.
The DARE solve uses scipy (numpy-only) since there is no equivalent in
PyTorch. The conversion is handled transparently via bk.to_numpy /
bk.from_numpy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state_cost_matrix
|
Q — penalizes state deviation (n_x, n_x). |
required | |
control_cost_matrix
|
R — penalizes control effort (n_u, n_u). |
required | |
dynamics_state_matrix
|
A — discrete-time state transition (n_x, n_x). |
required | |
dynamics_control_matrix
|
B — control input matrix (n_x, n_u). |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/controllers/lqr.py
gain_calculation
¶
Solve DARE and compute the optimal LQR gain matrix K.
Solves \(P = A^T P A - A^T P B (R + B^T P B)^{-1} B^T P A + Q\)
via scipy.linalg.solve_discrete_are, then computes:
The gain K is stored as self.K and used in compute().
Source code in src/shinro/controllers/lqr.py
compute
¶
Compute the optimal control input \(u = -K (x - x_t)\).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
current_state
|
Current state vector (n_x,). |
required | |
target_state
|
Any | None
|
Desired state vector (n_x,). Defaults to zeros. |
None
|
Returns:
| Type | Description |
|---|---|
|
Control input vector (n_u,). |
Source code in src/shinro/controllers/lqr.py
reset
¶
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create an LQR controller from a TOML config dict or LQRConfig.
Config fields
state_cost: Diagonal Q weights (n_x,) or full Q matrix (n_x, n_x). control_cost: Diagonal R weights (n_u,) or full R matrix (n_u, n_u). dt: Time step — used to set B = dt * I unless B_dynamics is given. A_dynamics: Optional full A matrix (n_x, n_x). Defaults to I. B_dynamics: Optional full B matrix (n_x, n_u). Defaults to dt * I.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict or LQRConfig. |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
LQR instance. |
Source code in src/shinro/controllers/lqr.py
PIDController¶
Bases: Controller
Proportional-Integral-Derivative controller with anti-windup.
Computes control effort as:
Features: - Independent gains per channel (Kp, Ki, Kd as vectors). - Output clamping with integral anti-windup back-calculation. - Derivative on error (standard form).
When output is clamped, the integral term is back-calculated on saturated channels only to prevent integral windup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kp
|
Proportional gain vector (n,). |
required | |
ki
|
Integral gain vector (n,). |
required | |
kd
|
Derivative gain vector (n,). |
required | |
dt
|
float
|
Time step in seconds. |
required |
output_limits
|
tuple | None
|
Optional (min_limits, max_limits) for output clamping. Each is an array of shape (n,). |
None
|
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/controllers/pid.py
compute
¶
Compute the PID control effort.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
current_state
|
Measured current state (n,). |
required | |
target_state
|
Desired target state (n,). |
required |
Returns:
| Type | Description |
|---|---|
|
Control effort vector (n,). |
Source code in src/shinro/controllers/pid.py
reset
¶
Reset the controller's internal state (integral and previous error).
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create a PID controller from a TOML config dict or PIDConfig.
Config fields
kp: List of proportional gains (n,). Defaults to [1.0].
ki: List of integral gains (n,). Defaults to zeros.
kd: List of derivative gains (n,). Defaults to zeros.
dt: Time step. Required at runtime; injected from the plant in
scenario builds.
output_limits: Optional dict with min and max lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict or PIDConfig. |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
PIDController instance. |
Source code in src/shinro/controllers/pid.py
MPC_LTI_DeltaU¶
Bases: MPC_LTI
MPC with \(\Delta u\) (control rate) regularization.
Augments the state to \([x; u_{\text{prev}}]\) so the control variable becomes \(\Delta u = u_k - u_{k-1}\). The cost penalizes \(\Delta u^T S \Delta u\), smoothing chatter.
The augmented dynamics are:
where \(z = [x; u_{\text{prev}}]\).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
delta_u_penalty
|
S — cost matrix for \(\Delta u\) (n_u, n_u). |
required | |
**kwargs
|
Passed to MPC_LTI.init. |
{}
|
Source code in src/shinro/controllers/mpc_lti.py
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create an MPC_DeltaU controller from a TOML config dict or MPCDeltaUConfig.
Config fields
delta_u_penalty: Diagonal S weights (n_u,) or full S matrix (n_u, n_u).
horizon: Prediction horizon.
state_cost: Diagonal Q weights (n_x,) or full Q matrix (n_x, n_x).
control_cost: Diagonal R weights (n_u,) or full R matrix (n_u, n_u).
dt: Time step.
A_dynamics: Optional full A matrix (n_x, n_x). Defaults to I.
B_dynamics: Optional full B matrix (n_x, n_u). Defaults to dt * I.
constraints: Optional dict with upper and lower bound lists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict or MPCDeltaUConfig. |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
MPC_LTI_DeltaU instance. |
Source code in src/shinro/controllers/mpc_lti.py
compute
¶
Solve MPC with \(\Delta u\) regularization.
Regulates the tracking error e = current_state - target_state to
zero, augmenting the state with the previous control input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
current_state
|
Original (non-augmented) state vector (n_x,). |
required | |
target_state
|
Any | None
|
Optional reference state (n_x,) to track. |
None
|
u_prev
|
Any | None
|
Previous control input (n_u,). Defaults to zeros. |
None
|
Returns:
| Type | Description |
|---|---|
|
Optimal control action (n_u,). |
Source code in src/shinro/controllers/mpc_lti.py
MPPIController¶
Bases: Controller
Model Predictive Path Integral controller.
Each compute() call samples \(N\) Gaussian control perturbations,
rolls out the dynamics over a horizon of \(K\) steps, weights the
perturbations by the softmax of their total cost, and advances the
receding-horizon nominal control sequence by one step. The returned action
is the first element of the updated nominal sequence, clipped to the
configured control bounds.
The dynamics and cost callables receive batched arrays of shape
(N, D_x) / (N, D_u) and may be injected directly or produced by
attach_plant from a plant's model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dynamics_fn
|
Any | None
|
Callable |
None
|
cost_fn
|
Any | None
|
Callable |
None
|
num_samples
|
int
|
Number of sampled perturbations N. |
100
|
temperature
|
float
|
Softmax temperature \(\lambda\) (> 0). |
1.0
|
dt
|
float
|
Time step passed to the dynamics. |
0.01
|
horizon
|
int
|
Prediction horizon K. |
10
|
noise_sigma
|
Standard deviation of the control perturbation per
input channel |
None
|
|
u_min
|
Optional lower control bound |
None
|
|
u_max
|
Optional upper control bound |
None
|
|
seed
|
int | None
|
Optional RNG seed for reproducible sampling. |
None
|
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. The rollout loop runs on this backend, so a TorchBackend executes batched tensor ops over the sample dimension. |
None
|
Source code in src/shinro/controllers/mppi.py
attach_plant
¶
Wire a plant into the controller via a batched dynamics adapter.
Builds a BatchedDynamicsAdapter from the plant and sets
dynamics_fn / cost_fn from it. The cost uses the quadratic
stage cost \(x^T Q x + u^T R u\), optionally tracking a reference
passed to compute. Q and R may be diagonal (D,) or
full (D, D) matrices; they default to identity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plant
|
A |
required | |
Q
|
Any | None
|
State cost matrix (D_x, D_x) or diagonal (D_x,). |
None
|
R
|
Any | None
|
Control cost matrix (D_u, D_u) or diagonal (D_u,). |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the plant's control dimension disagrees with the
controller's |
Source code in src/shinro/controllers/mppi.py
compute
¶
Compute the MPPI control action for a given initial state.
Samples \(N\) Gaussian perturbation sequences, rolls out the dynamics over the horizon, computes the softmax-weighted update, and returns the first action of the updated nominal sequence (clipped to bounds if configured).
The rollout loop runs on self.bk, so with a TorchBackend
the batched dynamics/cost operations execute as torch tensor ops. The
Gaussian sampling and softmax weighting run in numpy and are bridged
to the backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
current_state
|
Initial state vector (D_x,). Accepts the backend's native array type (numpy or torch). |
required | |
target_state
|
Any | None
|
Optional reference state (D_x,) to track. When given,
the cost penalizes deviation |
None
|
Returns:
| Type | Description |
|---|---|
|
First control action (D_u,) in the backend's native type. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If |
Source code in src/shinro/controllers/mppi.py
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 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 | |
reset
¶
Reset the controller to its initial state.
Zeros the nominal control sequence and clears the last-sample bookkeeping attributes.
Source code in src/shinro/controllers/mppi.py
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create an MPPI controller from a TOML config dict or MPPIConfig.
Config fields
num_samples: Number of sampled perturbations N. temperature: Softmax temperature. horizon: Prediction horizon K. noise_sigma: Per-channel perturbation std dev (D_u,). dt: Time step. Required at runtime; injected from the plant in scenario builds. u_min: Optional lower bound list (D_u,). u_max: Optional upper bound list (D_u,). seed: Optional RNG seed. state_cost: Optional diagonal Q weights (D_x,) or full Q matrix. control_cost: Optional diagonal R weights (D_u,) or full R matrix.
The dynamics_fn and cost_fn callables cannot be serialized to
TOML. They are created as None and must be injected after
construction, either by setting the attributes directly or by calling
attach_plant(plant):
.. code-block:: python
ctrl = MPPIController.from_config(config)
ctrl.dynamics_fn = my_dynamics
ctrl.cost_fn = my_cost
# or:
ctrl.attach_plant(plant)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict or MPPIConfig. |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
MPPIController instance. |
Source code in src/shinro/controllers/mppi.py
LeRobotDiffusionAdapter¶
Bases: Controller
Wrap a LeRobot diffusion policy as a Controller.
The policy is loaded from a Hugging Face checkpoint or local path.
The compute() method converts the plant state into LeRobot's
observation dict format, runs the policy, and returns the action.
Accepts both numpy arrays and torch tensors for state and camera frames, so it works with any ArrayBackend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
policy
|
Loaded LeRobot policy instance. |
required | |
use_camera
|
bool
|
Whether to expect camera observations. |
False
|
device
|
str
|
Torch device for policy inference. |
'cpu'
|
Source code in src/shinro/controllers/lerobot_adapter.py
update_camera
¶
Feed a camera frame for the next policy step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Camera image as numpy array (HWC) or torch tensor (CHW or HWC). |
required |
compute
¶
Run the LeRobot policy on the current state.
Builds a LeRobot observation dict from the state vector, runs the policy, and returns the action as a numpy array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Plant state vector (n_x,). Can be numpy or torch tensor. |
required | |
target
|
Ignored for learned policies — they generate actions from observation alone. |
None
|
Returns:
| Type | Description |
|---|---|
|
Action vector (n_u,) as numpy array. |
Source code in src/shinro/controllers/lerobot_adapter.py
reset
¶
from_config
classmethod
¶
Load a LeRobot policy from a config dict.
Config fields
policy_type: Type of policy ("diffusion", "act", "pi0", etc.).
checkpoint: Hugging Face repo ID or local path.
use_camera: Whether to expect camera observations (default: false).
device: Torch device (default: "cpu").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict. |
required |
Returns:
| Type | Description |
|---|---|
|
LeRobotDiffusionAdapter instance. |
Source code in src/shinro/controllers/lerobot_adapter.py
OnnxRLAdapter¶
Bases: Controller
Wrap an ONNX-exported RL policy as a Controller.
The policy is loaded from a local .onnx file via onnxruntime.
compute() encodes the plant state (normalization / clipping / index
selection), runs the model, post-processes the raw output into a control
action, and returns it as a numpy array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Loaded |
required | |
obs_encoder
|
_ObsEncoder
|
Encoder mapping plant state to the ONNX feed dict. |
required |
output_name
|
str
|
ONNX output tensor name. |
required |
action_space
|
str
|
|
'continuous'
|
deterministic
|
bool
|
For discrete/stochastic policies, return the greedy
argmax / mean instead of sampling (default: |
True
|
action_scale
|
float | ndarray
|
Post-policy per-action scaling (tanh-squash support). |
1.0
|
action_bias
|
float | ndarray
|
Post-policy per-action bias. |
0.0
|
action_clip
|
tuple[float, float] | None
|
Optional |
None
|
seed
|
int
|
RNG seed for sampling action spaces. |
0
|
backend
|
ArrayBackend | None
|
Array backend for state input and action output. ONNX inference itself always runs on numpy arrays, but the adapter converts the backend-native state to numpy at the boundary and the resulting action back to the backend's native type. |
None
|
Source code in src/shinro/controllers/onnx_rl_adapter.py
compute
¶
Run the ONNX policy on the current state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
Plant state vector in the configured backend's native type (numpy array or torch tensor). |
required | |
target
|
Ignored for learned policies — they generate actions from observation alone. |
None
|
Returns:
| Type | Description |
|---|---|
|
Action vector (n_u,) in the backend-native type. |
Source code in src/shinro/controllers/onnx_rl_adapter.py
reset
¶
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create an OnnxRLAdapter from a TOML config dict.
Config fields
model_path: Path to the .onnx model file (required).
action_space: "continuous", "discrete", or "stochastic"
(default: "continuous").
deterministic: Whether to return argmax/mean instead of sampling
(default: true).
action_scale: Post-policy action scale (default: 1.0).
action_bias: Post-policy action bias (default: 0.0).
action_clip_low / action_clip_high: Clip the final action
(default: no clipping).
seed: RNG seed for stochastic sampling (default: 0).
[observation] subtable fields:
input_name: ONNX input tensor name (defaults to the model's first
input).
state_keys: Integer indices into the plant state to use as
observations (default: [0, 1, ..., n-1]).
normalize: Apply mean/std normalization (default: false).
obs_mean / obs_std: Arrays for normalization.
clip: [low, high] observation clipping (default: none).
add_batch_dim: Prepend a batch axis (default: true).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict. |
required | |
backend
|
ArrayBackend | None
|
Array backend for state input and action output. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
OnnxRLAdapter instance. |
Source code in src/shinro/controllers/onnx_rl_adapter.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 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 | |
SlidingModeController¶
Bases: Controller
Sliding Mode Controller for nonlinear systems.
Implements the equivalent control approach with a switching term and
optional boundary-layer smoothing. The sliding surface coefficients
c must form a Hurwitz polynomial.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
c
|
Sliding surface coefficients (n,). The polynomial
|
required | |
k1
|
float
|
Discontinuous (switching) gain — drives the state to the surface. |
required |
phi
|
float
|
Boundary layer thickness for chattering suppression. If 0, uses sign (pure switching). |
0.0
|
k2
|
float
|
Linear (proportional) gain on the sliding variable. |
0.0
|
smoother
|
str
|
Boundary-layer smoothing function. One of |
'sat'
|
alpha
|
float
|
Fractional power exponent for the switching term \(|s|^\alpha\). 0 gives sign-only; 1 gives linear. |
0.0
|
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Source code in src/shinro/controllers/smc.py
compute
¶
Compute the sliding mode control action.
Evaluates \(u = (c^T g)^{-1} ( -c^T f - k_1 |s|^\alpha \, \text{smooth}(s) - k_2 s )\).
For scalar input (c^T g is scalar), uses direct division. For
vector input, solves the least-squares problem.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Current state vector (n,). |
required | |
f_x
|
Drift dynamics \(f(x)\) evaluated at x (n,). |
required | |
g_x
|
Control matrix \(g(x)\) evaluated at x (n, n_u). |
required |
Returns:
| Type | Description |
|---|---|
|
Control input vector (n_u,). |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If \(c^T g(x)\) is near-zero for scalar input. |
Source code in src/shinro/controllers/smc.py
reset
¶
from_config
classmethod
¶
from_config(config, backend: ArrayBackend | None = None)
Create an SMC controller from a TOML config dict or SMCConfig.
Config fields
c: List of sliding surface coefficients (n,).
k1: Discontinuous (switching) gain.
phi: Boundary layer thickness (default 0.0).
k2: Linear gain on sliding variable (default 0.0).
smoother: Smoothing function — "sat", "tanh", or
"sigmoid" (default "sat").
alpha: Fractional power exponent (default 0.0).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
TOML config dict or SMCConfig. |
required | |
backend
|
ArrayBackend | None
|
Array backend. Defaults to NumpyBackend. |
None
|
Returns:
| Type | Description |
|---|---|
|
SlidingModeController instance. |