Control¶
Virtual spring-damper laws on a measured [q; dq]: the rate-measured members of the classical family.
- class minilink.control.impedance.ImpedanceController(dof=1, *, tracking_ref=False, Kp=10.0, Kd=1.0, u_min=-inf, u_max=inf, y_labels=None, y_units=None, u_labels=None, u_units=None)[source]¶
Virtual spring-damper
u = Kp (q_d − q) + Kd (dq_d − dq)on a measured[q; dq].The measurement port
ycarries[q; dq](dim2·dof). The referencerisq_d(dimdof, regulation:dq_d = 0) or[q_d; dq_d](dim2·dof,tracking_ref=True). No state: the loop adds no pole.What is measured decides the derivative: this block reads the rate, so it carries no filter state, where
PDandPIDfilter the derivative of their one measurement.- Parameters:
dof (int) – Number of axes.
tracking_ref (bool) – If
True,ris[q_d; dq_d]; otherwiseq_dalone.Kp (float, vector or (dof, dof) array) – Stiffness and damping: a scalar or a per-axis vector is a diagonal matrix, a full matrix couples the axes. Stored in
self.paramsas(dof, dof)arrays, the shape a value assigned there later must have.Kd (float, vector or (dof, dof) array) – Stiffness and damping: a scalar or a per-axis vector is a diagonal matrix, a full matrix couples the axes. Stored in
self.paramsas(dof, dof)arrays, the shape a value assigned there later must have.u_min (float or vector) – Actuator limits per axis.
u_max (float or vector) – Actuator limits per axis.
y_labels (sequence of str, optional) – Display metadata for ports.
y_units (sequence of str, optional) – Display metadata for ports.
u_labels (sequence of str, optional) – Display metadata for ports.
u_units (sequence of str, optional) – Display metadata for ports.
- class minilink.control.impedance.ImpedanceIntegralController(dof=1, *, tracking_ref=False, Kp=10.0, Ki=1.0, Kd=1.0, u_min=-inf, u_max=inf, e_int_min=-inf, e_int_max=inf, y_labels=None, y_units=None, u_labels=None, u_units=None)[source]¶
Spring-damper with integral action,
u = Kp e + Ki ∫e + Kd ė, on a measured[q; dq].ImpedanceControllerplus one integral state per axis on the position errore = q_d − q, held while the command or the integral state sits on a limit (the anti-windup ofPID). Same ports and references.- Parameters:
dof (as
ImpedanceController.)tracking_ref (as
ImpedanceController.)Kp (as
ImpedanceController.)Kd (as
ImpedanceController.)u_min (as
ImpedanceController.)u_max (as
ImpedanceController.)Ki (float or vector) – Integral gain per axis, never a matrix: the anti-windup hold pairs a command with its own error.
e_int_min (float or vector) – End stops of the integral state per axis.
e_int_max (float or vector) – End stops of the integral state per axis.
y_labels (sequence of str, optional) – Display metadata for ports.
y_units (sequence of str, optional) – Display metadata for ports.
u_labels (sequence of str, optional) – Display metadata for ports.
u_units (sequence of str, optional) – Display metadata for ports.
- f(x, u, t=0, params=None)[source]¶
State derivative
dx = f(x, u, t; p).Subclasses should treat this as a function of
(x, u, t, params)only; the library does not verify purity.- Parameters:
x (array of shape (n,))
u (array of shape (m,))
t (float)
params (dict, optional) –
Nonemeans “useself.params”.
- Returns:
dx
- Return type:
array of shape (n,)
Full-state linear feedback, constant or scheduled gain.
- class minilink.control.state.StateFeedbackController(K, xbar=None, ubar=None)[source]¶
Full-state feedback
u = ubar - K (x - r)on a state reference.The reference
ris a state (dimensionn); left unconnected it holdsxbar, so the law regulates toxbar. This is the blockminilink.control.design.lqr()andminilink.control.design.place()return. A reference on the output isOutputReferenceController.Ports: the plant state
x(dimensionn) and the referencer.Khas shape(m, n);ubaris the feedforward command.This is state feedback on the full
xport. For output feedback on a measuredy(and@-operator wiring), useP.
- class minilink.control.state.OutputReferenceController(K, N, xbar=None, ubar=None)[source]¶
Full-state feedback
u = ubar - K (x - xbar) + N ron an output reference.The reference
ris a desired output (dimensionp, zero when unconnected), andNof shape(m, p)scales it to a command: the textbook’sN = -(C (A - B K)^-1 B)^-1givesy = rat steady state for the plant(A, B, C)linearized about(xbar, ubar). A reference on the state isStateFeedbackController.Ports: the plant state
x(dimensionn) and the referencer.Khas shape(m, n);ubaris the feedforward command.
- class minilink.control.state.TimeVaryingStateFeedbackController(t, K, xbar=None, ubar=None, K_after=None)[source]¶
Full-state feedback with a gain schedule
u = ubar - K(t) (x - r).tholdsNincreasing sample times andKthe matching gains, shape(N, m, n). Between samples the gain is interpolated linearly; before the first sampleK[0]applies, and past the last sample the block keepsK[-1]— or appliesK_afterwhen one is given (for a finite-horizon design, the stationary gain). Ports are those ofStateFeedbackController. This is the blockminilink.control.design.lqr_finite_horizon()returns.
- class minilink.control.state.TrajectoryFeedbackController(trajectory, K)[source]¶
Feedback around a reference trajectory
u = u_d(t) - K(t) (x - x_d(t)).trajectoryis the reference, aTrajectorywithNsamples ofx_dandu_d, andKholds the matching gains, shape(N, m, n). Reference and gains are interpolated linearly between samples; past the last sample the block holds the final point and gain, so it keeps regulating where the trajectory ends — usually an equilibrium. The measurement is the full state and the reference is the trajectory itself, so there is no reference port. This is the blockminilink.control.design.trajectory_lqr()returns.
- minilink.control.state.plot_gain_schedule(t, K, *, title='Gain schedule', ax=None, show=True)[source]¶
Draw every entry of a gain schedule
K(t), shape(N, m, n), against time.
Classical compensators: the decoupled P, PI, PD and PID laws on a tracking error.
- class minilink.control.siso.P(Kp=1.0, *, dof=1, ports='error', u_min=-inf, u_max=inf)[source]¶
Decoupled proportional compensator
u_i = Kp_i e_i, the static member of the family.ports="error"(default) is the compensator form: one inpute, soP(...) @ plantinserts the Error blocke = r - yandP(...) >> plantis the loop gain, with no pole added.ports="reference"is the controller form with inputsrandy, the one a hand-wired diagram and the sampled loop (P(...) % dt) read. A coupled static gain on an output error isGain(K) @ plant.- Parameters:
Kp (float or vector) – Per-axis gain (a scalar broadcasts to all axes).
dof (int) – Number of independent scalar loops (default 1).
ports ({"error", "reference"}) – Input layout, see above.
u_min (float or vector) – Actuator limits per axis, infinite by default.
u_max (float or vector) – Actuator limits per axis, infinite by default.
- class minilink.control.siso.PID(Kp=1.0, Ki=0.0, Kd=0.0, tau=0.1, *, dof=1, ports='error', y_filt0=0.0, u_min=-inf, u_max=inf, e_int_min=-inf, e_int_max=inf)[source]¶
Decoupled PID with filtered derivative and anti-windup.
ports="error"(default) is the compensator form: one inpute, soPID(...) @ plantinserts the Error blocke = r - yandPID(...) >> plantis the loop gain.ports="reference"is the controller form with inputsrandy. Per axis,u_i = Kp_i e_i + Ki_i e_int_i + Kd_i d_i,
where
d_iis the filtered derivative ofe_i(error form) or of-y_i(reference form: no derivative kick on reference steps); the integrator stops while the command saturates.One measurement per axis, so the derivative is filtered (a pole at
-1/tau); the rate-measured members of the same family are inminilink.control.impedance.The block carries the states its terms need —
2 dofhere. SettingKi=0does not remove the integrator: the gain is tunable, so the state dimension cannot depend on it, and the dead integrator then shows up as an unobservable pole at the origin in every linearization. UsePI,PDorPwhen a term is structurally absent, so poles and zeros match the hand calculation.- Parameters:
Kp (float or vector) – Per-axis gains (a scalar broadcasts to all axes).
Ki (float or vector) – Per-axis gains (a scalar broadcasts to all axes).
Kd (float or vector) – Per-axis gains (a scalar broadcasts to all axes).
tau (float or vector) – Derivative filter time constant per axis [s]; must be positive.
dof (int) – Number of independent scalar loops (default 1).
ports ({"error", "reference"}) – Input layout, see above.
y_filt0 (float or vector) – Initial filtered state per axis (the filtered
y, or-e).u_min (float or vector) – Saturation limits per axis, infinite by default.
u_max (float or vector) – Saturation limits per axis, infinite by default.
e_int_min (float or vector) – Saturation limits per axis, infinite by default.
e_int_max (float or vector) – Saturation limits per axis, infinite by default.
- has_integrator = True¶
Which terms this form carries — the state layout follows.
- f(x, u, t=0, params=None)[source]¶
State derivative
dx = f(x, u, t; p).Subclasses should treat this as a function of
(x, u, t, params)only; the library does not verify purity.- Parameters:
x (array of shape (n,))
u (array of shape (m,))
t (float)
params (dict, optional) –
Nonemeans “useself.params”.
- Returns:
dx
- Return type:
array of shape (n,)
- class minilink.control.siso.PI(Kp=1.0, Ki=0.0, *, dof=1, ports='error', u_min=-inf, u_max=inf, e_int_min=-inf, e_int_max=inf)[source]¶
Decoupled PI compensator
u_i = Kp_i e_i + Ki_i e_int_i.PIDwithout the derivative term and without its filter state, so a PI loop has exactly one pole per axis at the origin and no filter pole. Same ports and anti-windup asPID.- Parameters:
Kp (float)
Ki (float)
dof (int)
ports (str)
u_min (float)
u_max (float)
e_int_min (float)
e_int_max (float)
- class minilink.control.siso.PD(Kp=1.0, Kd=0.0, tau=0.1, *, dof=1, ports='error', y_filt0=0.0, u_min=-inf, u_max=inf)[source]¶
Decoupled PD compensator
u_i = Kp_i e_i + Kd_i d_i,dfiltered.PIDwithout the integrator, so a PD loop carries only the derivative-filter pole at-1/tauper axis and no pole at the origin. Without integral action the loop keeps a static error; that is the point of the comparison. An unfilteredKp + Kd sis improper and has no block: filter the derivative (this block, or its frequency-domain twinLead) or measure the rate (ImpedanceController). For the proportional law alone useP.- Parameters:
Kp (float)
Kd (float)
tau (float)
dof (int)
ports (str)
u_min (float)
u_max (float)
- has_integrator = False¶
Which terms this form carries — the state layout follows.
Manipulator controllers: joint and task impedance, task-space kinematics (Pyro robotcontrollers.py).
- class minilink.control.robotic.ModelJointImpedance(plant, *, tracking_ref=False, gravity_comp=False, gravity=None, Kp=None, Kd=None)[source]¶
Joint-space impedance on a mechanical plant with optional gravity feedforward.
Law:
τ = Kp e + Kd ė + g(q)whengravity_compis enabled.- Parameters:
plant (MechanicalSystem)
tracking_ref (bool)
gravity_comp (bool)
gravity (GravityHook | None)
- minilink.control.robotic.JointImpedance(plant_or_dof=1, *, dof=None, tracking_ref=False, gravity_comp=False, gravity=None, Kp=None, Kd=None, **impedance_kwargs)[source]¶
Joint-space impedance: generic (
dofonly) or plant-aware.JointImpedance(dof=n)returns a domain-neutralImpedanceController.JointImpedance(plant, gravity_comp=True)returnsModelJointImpedancewith optionalτ += g(q)(or a customgravity(q)hook).- Parameters:
tracking_ref (bool)
gravity_comp (bool)
gravity (Callable[[...], ndarray] | None)
- class minilink.control.robotic.TaskImpedance(plant, *, tracking_ref=False, gravity_comp=False, gravity=None, Kp=None, Kd=None, show_task_force=False, task_force_scale=0.01, task_target_radius=0.04)[source]¶
Task-space impedance using an internal kinematic model.
Joint measurements
y = [q; dq]are mapped to task space via the plant’sforward_kinematicsandJ(q); no task-space plant ports are required for control (p/pdotremain for visualization).τ = J(q)ᵀ (Kp e_p + Kd e_v) [+ g(q)]
Reference
rhas dimtask_dim(regulation) or2·task_dimfor stacked[p_d; pdot_d]tracking.Optional visualization (
show_task_force=True) draws:a ball at the desired task point
p_d(frametask_target);the commanded spring-damper task force
f_task = Kp e + Kd ėat the current end-effector (frametask_force) — not gravity feedforward and not the mapped joint torque.
In a diagram those frames namespace to
controller:task_target/controller:task_force.- Parameters:
plant (Manipulator)
tracking_ref (bool)
gravity_comp (bool)
gravity (GravityHook | None)
show_task_force (bool)
task_force_scale (float)
task_target_radius (float)
- task_quantities(u, params=None)[source]¶
Return
(q, p, p_d, J, f_task)for the current reference and measurement.
- class minilink.control.robotic.TaskKinematic(plant, *, Kp=None)[source]¶
Task-space kinematic controller for velocity-controlled manipulators.
Joint measurements
y = q; outputu = dqdrives aSpeedControlledManipulator.v_task = Kp (p_d − p) dq = J(q)⁺ v_task
Reference
ris the desired end-effector positionp_d(dimtask_dim).
- class minilink.control.robotic.TaskKinematicNullspace(plant, *, Kp=None, K_null=None)[source]¶
Kinematic controller with a secondary joint posture in the nullspace.
dq = J⁺ v_task + (I − J⁺ J) K_null (q_null − q)
Primary reference
risp_d; secondary referencer_nullisq_null. GainK_nulllives inparams.
Model-based joint control: computed torque and sliding mode on the plant’s own model.
- class minilink.control.modelbased.ComputedTorqueController(plant, *, tracking_ref=True, Kp=None, Kd=None)[source]¶
Computed torque with built-in outer PD on joint space (Pyro
nonlinear.py).Desired acceleration
qdd = Kp (q_d - q) + Kd (dq_d - dq)(regulation usesdq_d = 0), thenτ = inverse_dynamics(q, dq, qdd).yis the plant’s state[q; dq](ctl @ plant); referencerisq_d(dimdof) or stacked[q_d; dq_d]whentracking_ref=True.The embedded model (
inverse_dynamics) reads the referenced plant’s liveself.params; the controller’s own params carry gains only. See DESIGN §4 (Embedded-model params rule).- Parameters:
plant (MechanicalSystem)
tracking_ref (bool)
- class minilink.control.modelbased.SlidingModeController(plant, *, tracking_ref=True, lam=None, gain=None, nab=None)[source]¶
Joint-space sliding-mode law on a mechanical plant (Pyro
nonlinear.py).Inherits computed-torque port layout (
r,y = [q; dq]). Fixed-goal regulation usesdq_d = ddq_d = 0; stackedr = [q_d; dq_d]whentracking_ref=True.The law drives the tracking error onto the sliding surface
s = ė + λ ewith a computed-torque reaching term and a discontinuous gain (the equations are written inctl_regulation()andctl_tracking()).- Parameters:
plant (MechanicalSystem)
tracking_ref (bool)
Linear state-feedback design: a model or arrays in, a controller block out.
- minilink.control.design.lqr_gain(A, B, Q, R)[source]¶
Return the optimal feedback gain
Kof the lawu = -K xminimizing∫ xᵀQx + uᵀRu dt.
- minilink.control.design.place_gain(A, B, poles)[source]¶
Return the gain
Kof the lawu = -K xthat puts the eigenvalues ofA − B Katpoles.
- minilink.control.design.lqr(sys, Q, R, *, x_bar=None, u_bar=None, t=0.0, params=None, method='auto', eps=1e-06)[source]¶
Design an LQR on
sysand return aStateFeedbackController.sysis a linear model (anLTISystem, used as is with the operating point it carries) or any plant, linearized first about its command port at(x_bar, u_bar), the nominal state and inputs when omitted. The block implementsu = ubar - K (x - r)withrdefaulting toxbar, the operating point of the model; wire the plant state into itsxport. On arrays the gain islqr_gain(), and the modelLTISystem(A, B). Only the command port entersB(u, else the one input that is not a disturbancedor a noisen); every other input is held at its nominal value.- Parameters:
sys (System) – The linear model or the plant.
Q (array-like) – State and input weights of
∫ xᵀQx + uᵀRu dt.R (array-like) – State and input weights of
∫ xᵀQx + uᵀRu dt.x_bar (array-like, optional) – Operating point to linearize a plant about, the nominal by default; a linear model carries its own.
u_bar (array-like, optional) – Operating point to linearize a plant about, the nominal by default; a linear model carries its own.
t (optional) – Linearization time, parameter set, differentiation backend and central-difference step, as in
linearize().params (optional) – Linearization time, parameter set, differentiation backend and central-difference step, as in
linearize().method (optional) – Linearization time, parameter set, differentiation backend and central-difference step, as in
linearize().eps (optional) – Linearization time, parameter set, differentiation backend and central-difference step, as in
linearize().
- minilink.control.design.place(sys, poles, *, x_bar=None, u_bar=None, t=0.0, params=None, method='auto', eps=1e-06)[source]¶
Place the closed-loop poles of
sysand return aStateFeedbackController.sysis a linear model or any plant, linearized first about its command port at(x_bar, u_bar), as inlqr(), withpoles(nof them, complex ones in conjugate pairs) in place ofQ, R. The block implementsu = ubar - K (x - r)withrdefaulting toxbar. On arrays the gain isplace_gain().
- minilink.control.design.lqg(sys, Q, R, Q_n, R_n, *, x_bar=None, u_bar=None, t=0.0, params=None, method='auto', eps=1e-06)[source]¶
Design an LQG compensator on
sys, an LQR gain on a Kalman estimate, and return it as one controller box.sysis a linear model or any plant measured throughy, as inkalman().Q, Rare the LQR weights,Q_n, R_nthe process and measurement noise intensities ofkalman(). The box holds theStateObserverunderobserverand theStateFeedbackControllerundergain, the estimate wired into the gain and the command back into the observer, withr,y->uon its edge:box @ plantcloses the loop, and the closed-loop poles are the gain’s and the observer’s together (the separation principle).
- minilink.control.design.lqr_gain_schedule(A, B, Q, R, S_f, tf, *, n_steps=1001)[source]¶
Return
(t, K, S): the finite-horizon gainK(t)of the lawu = -K(t) xand the Riccati matrixS(t).Sis swept backward from the terminal weightS(t_f) = S_f, exactly on a uniform grid, through the Hamiltonian system of the co-state (riccati_step()): no integration error, no stiffness, andSstays symmetric.tholdsn_stepssamples from0totf;Khas shape(n_steps, m, n)andSshape(n_steps, n, n).
- minilink.control.design.lqr_finite_horizon(sys, Q, R, S_f, tf, *, n_steps=1001, after='hold', x_bar=None, u_bar=None, t=0.0, params=None, method='auto', eps=1e-06)[source]¶
Design a finite-horizon LQR on
sysand return aTimeVaryingStateFeedbackController.sysis a linear model or any plant, as inlqr(). The block implementsu = ubar - K(t) (x - r)withK(t)fromlqr_gain_schedule(), minimizing∫₀^tf xᵀQx + uᵀRu dt + x(tf)ᵀ S_f x(tf)for the linear model. Past the horizon the block keepsK(tf)(after="hold") or switches to the infinite-horizon gain oflqr_gain()(after="stationary").
- minilink.control.design.trajectory_lqr(sys, trajectory, Q, R, S_f=None, *, params=None, method='auto', eps=1e-06)[source]¶
Design an LQR along a reference trajectory and return a
TrajectoryFeedbackController.sysis linearized at every sample(x_d(t_k), u_d(t_k))oftrajectory, and the Riccati differential equation of the errorx - x_dis swept backward along the samples, the dynamics frozen on each interval and each interval exact, as inlqr_gain_schedule(). The gains give the lawu = u_d(t) - K(t) (x - x_d(t)).S_fdefaults to the algebraic Riccati solution at the final sample, so the gain held past the horizon is the stationary LQR of the end point.- Parameters:
sys (System) – Plant to stabilize.
trajectory (Trajectory) – Reference
(t, x_d, u_d),Nsamples at increasing times.Q (array-like) – LQR weights on the tracking error and on the input deviation.
R (array-like) – LQR weights on the tracking error and on the input deviation.
S_f (array-like, optional) – Terminal weight
S(t_f); default the stationary solution at the end.params (dict, optional) – Parameter dict forwarded to
sys.f.method ({"auto", "fd", "jax"}, optional) – Differentiation backend, see
jacobian().eps (float, optional) – Central-difference step.
Pure pursuit: steer along the arc through a look-ahead point of a waypoint path.
- minilink.control.geometric.MIN_REACH = 1e-06¶
[m] floor on the distance to the target, so the arc stays finite if the target ever lands on the axle itself (a path shorter than one lookahead, driven to its end).
- class minilink.control.geometric.PurePursuit(waypoints, wheelbase=0.34, lookahead=0.6, lookahead_gain=0.25, delta_max=0.52, rear_offset=0.0, closed=None, state_dim=9)[source]¶
Steer toward the point of the path one lookahead distance ahead.
The law is geometry only. Write the target in the body frame at the rear axle,
(x_t, y_t), at distanced; the circle through the axle and that point, tangent to the car, has curvature2 y_t / d^2, and the kinematic bicycle holds that curvature atdelta = arctan(wheelbase * curvature). No path derivative and no gain to tune apart from the lookahead — which grows with speed, so a fast car aims further ahead and stops weaving.Input port
y: the vehicle state in the minilink vehicle layout, i.e.[x, y, theta, vx, ...]. Output portu: the steer command [rad].The target is the first waypoint at least one lookahead ahead of the closest one, counted along the path. Counting along the path rather than by array index is what makes a closed circuit work: at the seam the waypoints ahead of the car are the ones at the start of the array, and an index comparison finds none of them and falls back to the last waypoint — which sits under the car, where the arc is undefined.
- Parameters:
waypoints (array of shape (N, 2)) – Path samples [m], in the order they are driven. A closed loop ends one spacing short of its first point (
circuit_waypoints()); it is not closed by repeating that point, which would leave a zero-length segment behind.wheelbase (float) – Distance between the axles [m].
lookahead (float) – Lookahead distance at standstill [m].
lookahead_gain (float) – Added lookahead per unit speed [s], so
L_d = lookahead + gain * |vx|.delta_max (float) – Steer command limit [rad].
rear_offset (float) – Distance from the point the state locates back to the rear axle [m], where the pursuit geometry is written: zero when the state already sits at the rear axle, and the centre-of-gravity-to-rear-axle distance for a model that carries its state at the centre of gravity.
closed (bool or None) – Whether the path loops.
Nonedecides from the waypoints themselves: the path is closed when the gap from its last point back to its first is no wider than the spacing between neighbours (is_closed()).state_dim (int) – Dimension of the state the block reads (the plant’s
yport).
- minilink.control.geometric.is_closed(waypoints)[source]¶
True when the polyline loops back on itself within one waypoint spacing.
A closed path is written without repeating its first point at the end — the repeat would leave a zero-length segment with no tangent — so the loop shows up as a last-to-first gap no wider than the other steps.
Neural state-feedback controllers: the law u = pi(x) that policy search trains.
- class minilink.control.neural.NeuralPolicyController(sys, *, features=None, hidden=(64, 64), activation='tanh', normalize=True, squash='clip', seed=0, name='Neural Policy Controller', show_setpoint=False, setpoint_plant=None, setpoint_radius=0.04, task_target=None, task_target_radius=0.04)[source]¶
State feedback
u = u_mid + u_half * clip(MLP(z(x)), -1, 1).The law reinforcement learning, or any policy search, trains: features of the state, a multilayer perceptron, and a map from the network’s normalized output to the input-port bounds. Its input port is named
x, soctl @ plantcloses the loop on the plant state,plot_control_lawdraws the law, and the weights are ordinaryparamsentries that a planner updates in place.- Parameters:
sys (System) – Plant whose state bounds and action-port bounds (
"u", or its single input port, e.g. the referencerof an inner loop) define the feature normalization and the action range.features (callable, optional) – Observation map
z = features(x)(must trace under JAX). Use it to make angles periodic or to give the policy task-relative sensing. Default: the state scaled by its bounds whennormalizeis true.hidden (tuple of int) – Hidden widths of the policy network.
activation ({"tanh", "relu"}) – Hidden nonlinearity.
normalize (bool) – Scale the default features by the state box (needs finite bounds).
squash ({"clip", "tanh"}) – How the network output is kept in
[-1, 1]: clipped (PPO family) or squashed bytanh(SAC family).seed (int) – Weight initialization seed.
show_setpoint (bool) – Draw a ball at the commanded set-point (default
False).setpoint_plant (System, optional) – Plant whose
forward_kinematicsmaps a joint action to a point. Default:sysitself, or the first manipulator leaf in a diagram.setpoint_radius (float) – Ball radius in metres (default
0.04).task_target (array, optional) – Fixed task point
p*. When set, a second ball is drawn there.task_target_radius (float) – Radius of the task ball (default
0.04).
Notes
The trainable weights are
params["mlp"]; the normalized-action range[-1, 1]maps onto theuport bounds, which is what makes exploration cover the whole input range from the first step.Optional visualization:
show_setpoint=Truedraws a ball at the commanded set-point (forward_kinematics(r)when the action is a joint reference, or the action itself when it is already a task point).task_targetdraws a second, fixed ball at the task point.- action(x, params=None)[source]¶
Plant input
u = u_mid + u_half * squash(a)withsquashclip or tanh.
- minilink.control.neural.angle_features(angles, scales=None)[source]¶
Feature map making the listed state angles periodic:
cos, sinper angle.anglesare state indices; every other component is passed through, multiplied byscales(a dict{index: gain}, e.g.{1: 0.1}to bring a rate to order one). Works on NumPy and traces under JAX.
Model predictive control as a System block: one trajectory-optimization solve per replan tick.
- minilink.control.mpc.controller.ModelPredictiveController(planner, *, dt_mpc, warm_start=True, verbose=False, t0=0.0, debug=False)[source]¶
Build the product MPC System family block.
- Parameters:
planner (TrajectoryOptimizationPlanner) – Trajopt planner (auto-compiles parametric NLP when needed).
dt_mpc (float) – Replan period (required for both warm-start modes).
warm_start (bool, optional) – If True, return a
StepSystemwith packedzonComputer.x. If False, algebraicSystem.verbose (bool) – Latch printouts, absolute-time epoch, and live debug figure auto-update.
t0 (float) – Latch printouts, absolute-time epoch, and live debug figure auto-update.
debug (bool) – Latch printouts, absolute-time epoch, and live debug figure auto-update.
- class minilink.control.mpc.controller.Command(solution, k, t_solve, u_ff, x_ff, z, success)[source]¶
One replan tick result for deploy or debugging.
solution.trajectory.tis plan-local time with zero at the solve instant. Absolute solve time ist_solve.- Parameters:
solution (PlanningSolution)
k (int)
t_solve (float)
u_ff (ndarray)
x_ff (ndarray)
z (ndarray)
success (bool)
- property solver¶
The optimizer’s record of this tick’s solve (
solution.solver).
- class minilink.control.mpc.controller.ModelPredictiveControllerMixin(planner, *, dt_mpc, t0=0.0, verbose=False, debug=False)[source]¶
Shared deploy / export / debug surface for MPC System family blocks.
A concrete block calls this constructor beside its System base’s, then declares its ports with
declare_mpc_ports().- Parameters:
planner (TrajectoryOptimizationPlanner)
dt_mpc (float)
t0 (float)
verbose (bool)
debug (bool)
- planner¶
The trajopt planner the block solves with.
- latch¶
The per-tick solve memo every port (and a stateful block’s step) shares.
- Type:
- dt_mpc¶
Replan period (warm-start shift and Computer schedule).
- Type:
float
- t0¶
Absolute-time epoch:
t_solve = t0 + k * dt_mpc.- Type:
float
- debug¶
Whether every command redraws the live debug figure.
- Type:
bool
- deploy_k¶
The deploy counter: the tick of the next
compute_command().- Type:
int
- last_command¶
Most recent
compute_command()result, if any.- Type:
Command or None
- debug_handles¶
The live debug figure’s handles, once
init_debug_figure()built it.- Type:
dict or None
- nominal_cache¶
The latched plan’s interpolator, once
generate_nominal_interpolator()built it.- Type:
NominalCache or None
- replan_divisor¶
Base ticks per replan tick (a dual-rate computer sets it).
- Type:
int
- Hooks a concrete block defines
- Type:
compute_u_ff,compute_x_ffand
- ``compute_z``, the port functions ``(x, u, t, params)``, and
- ``measurement(u)``, the measured state from the block input. Hooks the
- mixin provides, which a block may override
- Type:
z_warm_for_command(), the
- packed ``z`` that warm-starts the next deploy command (``None`` here);
- ``replan_k(k_tick)``, the replan index of a Computer base tick; and
- ``require_nominal_cache()``, the nominal cache, refused before it is built.
- A port looks its hook up on the block each time it is evaluated, so a
- subclass override is honoured.
- compute_command(y, *, params=None, k=None, t=None)[source]¶
Deploy replan tick: measure
y→ NLP →Command.Does not build the nominal interpolator; call
generate_nominal_interpolator()explicitly for dual-rate / broadcast.- Parameters:
y (array_like) – Measured plant state (diagram input
y).params (mapping, optional) – Forwarded to
solve_trajectory_from()via the tick latch (None/{}= x0-only; the keysceneraisesNotImplementedError).k (int, optional) – Tick index. Default: increment internal deploy counter.
t (float, optional) – Absolute solve time. Default:
t0 + k * dt_mpc.
- Return type:
- get_solver_record()[source]¶
The last NLP solve’s record for deploy / logging (ROS-agnostic).
Prefers
last_command; otherwise the planner’slast_solution(e.g. after a hybrid tick with no deploy call).Noneif nothing has been solved yet.
- reset()[source]¶
Clear deploy counter, last command, nominal cache, and tick latch.
- Return type:
None
- generate_nominal_interpolator(*, derivatives=True)[source]¶
Build the fast nominal cache from the last latched plan (opt-in).
Call after
compute_command()(or a hybrid replan tick) when using high-rateget_nominal_u()/ dual-rate broadcast. Not invoked bycompute_command()itself.- Parameters:
derivatives (bool, optional) – If True, attach FD knot rates for
get_nominal_*_dot.- Return type:
- get_nominal_u(t)[source]¶
Linear interp of latched plan
uat absolute timet.- Parameters:
t (float)
- Return type:
ndarray
- get_nominal_x(t)[source]¶
Linear interp of latched plan
xat absolute timet.- Parameters:
t (float)
- Return type:
ndarray
- get_nominal_u_dot(t)[source]¶
Interp of precomputed
urates (requiresderivatives=True).- Parameters:
t (float)
- Return type:
ndarray
- get_nominal_x_dot(t)[source]¶
Interp of precomputed
xrates (requiresderivatives=True).- Parameters:
t (float)
- Return type:
ndarray
- dual_rate_computer(dt_broadcast)[source]¶
Advanced multi-rate
Computer.Replan leaf at
dt_mpc; broadcast leaf atdt_broadcastapplyingget_nominal_u(). Requiresdt_mpc / dt_broadcastto be a positive integer. Does not change defaultexport_to_computer()/@.Leaves share this controller’s latch / nominal cache (no port edge between them). Deploy truth remains
compute_command()+generate_nominal_interpolator()+get_nominal_u().- Parameters:
dt_broadcast (float)
- Return type:
- init_debug_figure(sys, **kwargs)[source]¶
Create a matplotlib figure for live plan visualization.
Under a non-interactive backend (e.g.
Agg), still builds axes so tests can smoke the path;update_debug_figureredraws plan knots.
- update_debug_figure(cmd=None)[source]¶
Redraw debug axes from
cmdorlast_command.- Parameters:
cmd (Command | None)
- class minilink.control.mpc.controller.MPCStatelessController(planner, *, dt_mpc, verbose=False, t0=0.0, debug=False)[source]¶
Algebraic MPC feedforward block (
warm_start=Falseproduct sibling).Owns
dt_mpcfor Computer schedule /@. Outputsu_ff,x_ff, andzcome from one NLP per integer tickk(latch-memoized).- Parameters:
planner (TrajectoryOptimizationPlanner)
dt_mpc (float)
verbose (bool)
t0 (float)
debug (bool)
- class minilink.control.mpc.controller.MPCStatefulController(planner, *, dt_mpc, verbose=False, t0=0.0, debug=False)[source]¶
Warm-start MPC block with packed optimizer state on
Computer.x.warm_start=Trueproduct sibling. Statexis packedzfrom the previous tick. Ports andstep()share one NLP per tick via the latch.- Parameters:
planner (TrajectoryOptimizationPlanner)
dt_mpc (float)
verbose (bool)
t0 (float)
debug (bool)
- class minilink.control.mpc.controller.MPCTickSolve(plan, z, u_ff, x_ff, t_solve, k)[source]¶
One MPC fire: reconstructed plan and feedforward slices.
- Parameters:
plan (Trajectory)
z (ndarray)
u_ff (ndarray)
x_ff (ndarray)
t_solve (float)
k (int)
- class minilink.control.mpc.controller.MPCTickLatch(planner, *, verbose=False, dt_mpc=None, t0=0.0)[source]¶
Memoize one planner solve per integer replan tick
kand measurementy.Port
computepaths on MPC blocks callsolve_for_tick(); the first call at a new(k, y)runs the NLP viasolve_trajectory_from(), later calls with the samekand an equalyread the latch. Inside aComputertick every port sees the samey, so the block still solves once per tick.- Parameters:
planner (TrajectoryOptimizationPlanner)
verbose (bool)
dt_mpc (float | None)
t0 (float)
- property last_t_solve: float | None¶
Absolute solve time of the latched tick, if any.
- solve_for_tick(k, y, *, z_warm=None, dt_mpc=None, initial_guess=None, params=None)[source]¶
The tick’s solve: the latched one at the same
(k, y), else a new NLP.An explicit
initial_guessalways runs a new NLP from that seed, and latches its solve in place of the tick’s earlier one.- Return type:
- minilink.control.mpc.controller.export_mpc_to_computer(block, schedule=None, *, dt_mpc=None)[source]¶
Build a single-rate
Computerfrom an MPC block.scheduledefaults todt_mpc(the block’s own when not passed); a givenschedulemust tick atdt_mpc. Once the schedule is accepted, the export undoes the dual-rate hooks adual_rate_computercall left on the block; a rejected schedule leaves the block as it was.- Parameters:
schedule (StepSchedule | float | None)
dt_mpc (float | None)
- Return type:
- minilink.control.mpc.controller.export_mpc_dual_rate_computer(block, *, dt_broadcast)[source]¶
Multi-rate Computer: replan at
block.dt_mpc, broadcast atdt_broadcast.Boundary ports:
y→ replan,u_nomfrom broadcast (primary control).Coupling between
replanandbroadcastis a shared latch onblock(generate_nominal_interpolatorafter NLP, thenget_nominal_*) — not a diagram port. Graphviz correctly shows no edge.- Parameters:
dt_broadcast (float)
- Return type:
- class minilink.control.mpc.controller.MPCBroadcastController(mpc, *, dt_broadcast, t0=0.0)[source]¶
Sample latched nominals at Computer base ticks (no NLP).
Absolute time is
t0 + k * dt_broadcastwherekis the Computer tick passed as the porttargument.Holds a reference to the replan MPC (shared
NominalCache) — not a port input. See dual-rate packaging in DESIGN.md (MPC hybrid block).- Parameters:
dt_broadcast (float)
t0 (float)
- mpc¶
The replan block whose nominal cache the leaf samples.
- dt_broadcast¶
Broadcast period: one base tick of the Computer.
- Type:
float
- t0¶
Absolute-time epoch of base tick zero.
- Type:
float
- Hooks a broadcast leaf subclass overrides or calls
- Type:
compute_u_nomand
- ``compute_x_nom``, the port functions ``(x, u, t, params)``, and
- ``abs_t(k_tick)``, the absolute time of a base tick. A port looks its hook
- up on the leaf each time it is evaluated, so a subclass override is
- honoured.
- minilink.control.mpc.controller.validate_mpc_planner(planner)[source]¶
Validate a plan-producing planner and return
(n, m, n_z).The contract an MPC planner meets: a duck-typed
TrajectoryOptimizationPlanner(or compatible) with aproblem, atranscriptionandoptions, and the verbscompile_parametric_program,has_parametric_programandsolve_trajectory_from. A planner missing any of them is aTypeError.compile_backend='jax'auto-compiles a parametric NLP once (bind + solve each tick).compile_backend='numpy'or'direct'skips parametric compile; each tick rebuilds viasolve_trajectory_from().- Return type:
tuple[int, int, int]
Receding-horizon bookkeeping: the warm-start guesses and the nominal plan cache of MPC.
- minilink.control.mpc.utilities.mpc_warm_start_guess(z_prev, y, planner, *, dt_mpc, k=0)[source]¶
Build an
initial_guessTrajectory forsolve_trajectory_from().At tick
k == 0(or when shift fails), returns the default collocation guess. Otherwise unpacksz_prev, shifts on the transcription grid, and pins the first state node to the current measurementy.- Parameters:
planner (TrajectoryOptimizationPlanner)
dt_mpc (float)
k (int)
- Return type:
- minilink.control.mpc.utilities.warm_start_guess_from_prev_plan(prev_plan, x_meas, planner, *, dt_shift, horizon, t_anchor)[source]¶
Warm-start guess from a previous solved plan (manual MPC demo contract).
Mirrors a hand-loop
compute_command+ RK4 ZOH deploy pattern: shiftprev_planbydt_shift, pinx[:, 0] = x_meas, re-time witht_anchor(simulation time at the MPC fire). Without a previous plan of at least three samples, or when the shifted window is too short, returns the default collocation guess.- Parameters:
prev_plan (Trajectory | None)
planner (TrajectoryOptimizationPlanner)
dt_shift (float)
horizon (float)
t_anchor (float)
- Return type:
- minilink.control.mpc.utilities.mpc_default_computer_x0(planner)[source]¶
Packed default decision vector for
Computer.reset/x0_computer.- Parameters:
planner (TrajectoryOptimizationPlanner)
- Return type:
ndarray
- class minilink.control.mpc.utilities.NominalCache(t_solve, tau, x, u, x_dot=None, u_dot=None)[source]¶
Plan-local interpolant data built after an NLP replan.
- Parameters:
t_solve (float)
tau (ndarray)
x (ndarray)
u (ndarray)
x_dot (ndarray | None)
u_dot (ndarray | None)
- property tau_end: float¶
the last knot,
0for an empty plan.- Type:
The plan-local horizon
T
- minilink.control.mpc.utilities.build_nominal_cache(trajectory, t_solve, *, derivatives=True)[source]¶
Build a
NominalCachefrom a latched solution’s trajectory.- Parameters:
trajectory (Trajectory) – The latched solve’s schedule (plan-local
t).t_solve (float) – Absolute solve time (τ = 0).
derivatives (bool, optional) – If True, attach FD knot rates
x_dot/u_dot.
- Return type:
- minilink.control.mpc.utilities.eval_signal(cache, t, signal)[source]¶
Linear interp of rows of
signal(n, N) at absolutet.- Parameters:
cache (NominalCache)
t (float)
signal (ndarray)
- Return type:
ndarray
MPC visualization: the receding horizons of a hybrid rollout, as animation overlays.
- minilink.control.mpc.viz.mpc_animation_overlays(result, planner, *, scene=None, track=None, traj=None, trail=True, reference_pad=None, t0=0.0, dt_mpc=None, scene_color='tab:red', scene_opacity=0.45)[source]¶
Build
animate(overlays=[...])layers for a hybrid MPC rollout.Reconstructs horizon polylines from the computer tick history, optionally an executed-path trail, and optionally a straight reference line, obstacle scene skin, or track corridor.
- Parameters:
result (HybridSimResult) – Output of
compute_trajectory().planner – Trajopt planner (transcription and template problem).
scene (Scene, optional) – Collision scene drawn via
as_visualizer().track (Track, optional) – 2-D track corridor edges and centerline.
traj (Trajectory, optional) – Executed path for the trail. Defaults to
result.plant. Pass the leaf-car trajectory when the hybrid plant is a nested diagram, so the trail is pose(x, y)and not the first two diagram states.trail (bool, optional) – Draw the executed-path polyline.
Falsekeeps the receding horizon and any track/scene layers without the growing trail.reference_pad (float, optional) – When set, draw a dashed world
y=0reference spanning the executed path inxby this padding on each end.t0 (float, optional) – Simulation start time for horizon alignment.
dt_mpc (float, optional) – MPC sample period. Defaults to
result.hybrid.computer.schedule.dt_base.scene_color (str) – Passed to
as_visualizer().scene_opacity (float) – Passed to
as_visualizer().
- Returns:
Overlay drawables ready for
animate().- Return type:
list
- minilink.control.mpc.viz.mpc_plans_from_rollout(computer, transcription, problem, *, z_source='signals', signal_name='z', t0=0.0, dt_mpc)[source]¶
Build absolute-time MPC plans from per-tick packed decision vectors.
- Parameters:
computer (StepRollout) – Tick-indexed computer rollout from
HybridSimulator.transcription (DirectCollocationTranscription) – Collocation transcription used by the MPC block.
problem (PlanningProblem) – Template planning problem (system / cost layout).
z_source (str, optional) –
"signals"readscomputer.signals[signal_name];"x"readscomputer.x(warm-start step block state).signal_name (str, optional) – Signal key when
z_source="signals".t0 (float, optional) – Simulation start time.
dt_mpc (float) – MPC sample period (
Computer.schedule.dt_base).
- Returns:
(t_solve, plan)pairs suitable forHorizonPolyline.- Return type:
list of (float, Trajectory)