Core¶
The System contract: the base class every block, plant and controller extends.
- class minilink.core.system.System(n=0)[source]¶
Static input-output shell: ports, parameters, metadata, and facades.
y = h(x, u, t; p)(via portcompute/h()).A static block is its output maps on its ports. Default
n = 0(no states). State dimensionnis the number of state slots this block contributes when stacked in a diagram (zero for static blocks). Continuous evolutiondx = f(x, u, t; p)is defined onDynamicSystemonly, and a discrete stepx_{k+1} = step(x, u, k; p)onStepSystem.A
Systemserves several roles at once:IO contract: output ports (and optional
h()) as functions of(x, u, t, params).Structural model description: dimensions, ports, state metadata, labels, units, bounds, and nominal values.
Model defaults and metadata: default parameters (
params), default initial condition (x0), and solver hints (solver_info).Visualization contract: forward-kinematic geometry for rendering and animation.
User shortcut façade:
compile(),compute_trajectory(),render(),animate()onSharedSystemFacades(continuous analysis andgame()onDynamicSystemFacades).Inspect:
__str__()is a short summary (name, class,n, ports with dimensions; a diagram adds its keys). A notebook last expression still draws the Graphviz block.Names:
nameis the display name, the class name unless the block sets its own;idis the key the block takes in a diagram built by+,>>or a loop (None: its name in snake case; in@the role namesplant,controllerandobserverwin).
Overridden
h()and portcomputefunctions are functions of(x, u, t, params)alone: no memory between calls, no cached side effects. This is the first invariant of CONSTITUTION.md, and it is what lets diagrams nest, solvers step, and JAX trace. Python cannot check it, so the burden sits with whoever writes the equation.- skin = None¶
a callable
(plant) -> dict[str, list[prim]]orNone.get_kinematic_geometrydelegates toskin;Nonemeans no skin (empty geometry). Set per instance/class to swap a look without touchingf/tf(skinis toget_kinematic_geometryasparamsis tof).- Type:
Opt-in swappable look
- is_random = False¶
Whether the block draws random numbers from a seed in its
params.
- property m¶
the sum of all input-port dimensions.
- Type:
Total input dimension
- property p¶
the dimension of the
"y"port, or 0.- Type:
Primary output dimension
- h(x, u, t=0, params=None)[source]¶
Output
y = h(x, u, t; p).Same purity contract as port
compute(convention only; not enforced).- Parameters:
x (array of shape (n,))
u (array of shape (m,))
t (float)
params (dict, optional)
- Returns:
y
- Return type:
array of shape (p,)
- compute_state(x, u, t=0, params=None)[source]¶
Output helper returning the state vector directly (
y = x).
- refresh()[source]¶
Recompute derived internals from current parameters/topology.
Base implementation is a no-op and can be overridden by subclasses.
- realize(key)[source]¶
The params of one realization of the block’s randomness under
key.A block with no randomness returns its
paramsunchanged. A random block returns a copy with a fresh seed derived fromkey, or withseed=None, the mean, whenkeyisNone. A diagram realizes every subsystem, each under its own stream named by its id, so the result nests likeparamsand can be assigned to it.
- add_input_port(id, *, dim=None, nominal_value=None, labels=None, units=None, lower_bound=None, upper_bound=None)[source]¶
Add a new input port to the system.
- Parameters:
id (str) – The programmatic identifier for the port.
dim (int, optional) – Dimension of the input port. If omitted, inferred from supplied metadata or defaults to 1.
nominal_value (np.ndarray, optional) – The nominal value of the signal, used as the constant fallback when the port is unconnected.
- add_output_port(id, *, dim=None, function=None, dependencies=(), nominal_value=None, labels=None, units=None, lower_bound=None, upper_bound=None)[source]¶
Add a new output port to the system.
- Parameters:
id (str) – The programmatic identifier for the port.
dim (int, optional) – Dimension of the output port. If omitted, inferred from supplied metadata or defaults to 1.
function (callable, optional) – The function
(x, u, t, params) -> valuecomputing the port’s signal.dependencies (sequence of str, or "all", optional) – The input-port ids this output directly feeds through from (default: no direct feedthrough).
- rename_ports(**names)[source]¶
Rename ports at creation:
Saturation(-1, 1).rename_ports(u="u_cmd", y="u").A port name is the block’s outside: wiring,
autowire, plot signals, drawings. The equations read the stackeduby position, so a rename changes nothing inside. The renames of one call apply at once (a chain such asu="u_cmd", y="u"is one step), the port order is kept, and the output dependency lists follow. Refused: a name that is not a port, a new name that is a port not renamed in the same call, two equal new names, and a diagram, whose edge is named withexpose. Rename a block before placing it in a diagram, whose wiring refers to the names.Returns the block, for chaining.
- get_u_from_input_ports()[source]¶
Concatenated default values of all input ports.
This is the disconnected / nominal fallback used for standalone systems and for the evaluator IVP tier.
- get_port_values_from_u(u, *port_ids)[source]¶
Extract named port signals from the concatenated input vector
u.No port ids: return a dict mapping every input-port id to its value.
One port id: return that port’s value.
Several port ids: return a tuple in the requested order.
- get_input_port_slice(port_id)[source]¶
Return the slice of one input port inside the flat input vector
u.
- get_all_input_labels_and_units()[source]¶
Flattened component labels and units across all input ports.
- class minilink.core.system.DynamicSystem(n, *, input_dim=None, output_dim=None, expose_state=False, y_dependencies=())[source]¶
A system with continuous evolution
dx = f(x, u, t; p).dx = f(x, u, t; p) y = h(x, u, t; p)
Leaf plants use
n > 0;DiagramSystemsubclasses this type withn = 0when the stacked subsystem states sum to zero (signal-flow diagrams still simulate viaSimulator).The constructor can create the standard ports in one line: an input
u, a primary outputywired toh(), and optionally an auxiliary state outputx.- 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,)
- h(x, u, t=0, params=None)[source]¶
Output
y = h(x, u, t; p).Default: the full state,
y = x, when theyport has the state dimension (p == n); zeros otherwise. Override for any other measurement.- Parameters:
x (array of shape (n,))
u (array of shape (m,))
t (float)
params (dict, optional)
- Returns:
y
- Return type:
array of shape (p,)
- class minilink.core.system.StepSystem(n, *, input_dim=None, output_dim=None, expose_state=False, y_dependencies=())[source]¶
A system with discrete states (
n >= 1)x_{k+1} = step(x, u, k; p) y_k = h(x, u, k; p)
The constructor can create the standard ports in one line: an input
u, a primary outputywired toh(), and optionally an auxiliary state outputx.
Block diagrams: systems built by connecting other systems.
- class minilink.core.diagram.DiagramSystem[source]¶
A system composed of subsystems connected through their named ports.
A diagram is a
DynamicSystem, so diagrams nest, simulate, and compile like any other continuous system. The diagram state stacks the subsystem states in insertion order,x = [x_1; x_2; …; x_k], dx_i = f_i(x_i, u_i, t; p_i),
where each local input
u_iis gathered from the output ports connected to subsystemi, from the diagram boundary inputs, or from the port’s constant nominal value when unconnected. Diagram-level params are nested by subsystem id:{"plant": {...}, "controller": {...}}.@names its blockscontrollerandplant;+and>>keep the blocks’ own names.Build diagrams with
add_subsystem()+connect()(canonical wiring for any topology) or with the shortcuts+,>>,@, andautowire()fromminilink.core.composition.This class implements the wiring contract and a simple recursive evaluation of
fused as the reference;compile()produces the fastExecutionPlan-based evaluator used by simulation and optimization.- add_subsystem(sys, sys_id)[source]¶
Add a continuous or static subsystem under a unique id.
A
StepSystemhas nofto stack, so it is refused here rather than left out of the state derivative.
- f(x, u, t=0, params=None)[source]¶
Stacked state derivative
dx = [f_1(x_1, u_1, t); f_2(x_2, u_2, t); ...].This is the interpreted reference implementation;
compile()produces the fast equivalent used by simulation and optimization.
- compile(backend='numpy', bind_params=False, verbose=False)[source]¶
Compile the diagram into a stateless evaluator for fast simulation.
Runs algebraic-loop detection internally; raises RuntimeError if a loop is found.
- Parameters:
backend (str) –
'numpy'(default) or'jax'.bind_params (bool, optional) – If
True, subsystemparamsare deep-copied into the plan at compile time (seeminilink.core.compile.compiler.compile_diagram()).verbose (bool) – If
True, print timed compilation steps.
- Return type:
- reconstruct_internal_signals(traj)[source]¶
Reconstruct all subsystem output-port trajectories for this diagram.
- Parameters:
traj (Trajectory) – State-input trajectory sampled on a time grid.
- Returns:
New trajectory enriched with one sampled signal per subsystem output port, keyed as
"sys_id:port_id".- Return type:
- trajectory_of(subsystem, traj=None)[source]¶
The trajectory of one block of this diagram, as that block saw it.
xis the block’s own state anduthe input it received from the diagram (connected signals, boundary inputs, or nominal values), on the time grid of the diagram trajectory. The input is recomputed from the stored states with the blocks’ currentparams, so change them only after scoring the trajectory they produced. Anything written for the block alone applies to it, such as the cost of the plant inside a closed loop:cost.total_cost(closed_loop.trajectory_of(plant)).- Parameters:
subsystem (System) – A block added to this diagram (for a block inside a nested diagram, call
trajectory_ofonce per level).traj (Trajectory, optional) – Trajectory of this diagram; defaults to the last one computed.
- Returns:
toftraj,xwith shape(subsystem.n, N)anduwith shape(subsystem.m, N).- Return type:
- class minilink.core.diagram.StepDiagramSystem[source]¶
A discrete-time diagram composed of
StepSystemand static blocks.The diagram state stacks subsystem states in insertion order,
x_{k+1} = step(x_k, u_k, k),
where each local input is gathered from connected output ports, boundary inputs, or port nominal values. The third gather slot is step index
k(int), not simulation time.compile()produces aStepEvaluatorfor fastcompute_rollout().- add_subsystem(sys, sys_id)[source]¶
Add a step or static subsystem under a unique id.
A
DynamicSystemwith states has nostepto stack, so it is refused here rather than left frozen bystep().
- step(x, u, k=0, params=None)[source]¶
Stacked discrete update
x_{k+1} = [step_1(...); step_2(...); ...].Interpreted reference;
compile()produces the fast equivalent.
- compile(backend='numpy', bind_params=False, verbose=False)[source]¶
Compile the step diagram into a stateless step evaluator.
- Parameters:
backend (str) –
'numpy'(default) or'jax'.bind_params (bool, optional) – If
True, subsystemparamsare deep-copied into the plan.verbose (bool) – If
True, print timed compilation steps.
- Return type:
- minilink.core.diagram.check_diagram_params(params, subsystem_ids)[source]¶
Check the nested diagram params contract.
paramsmust beNoneor a mapping keyed by subsystem id whose values are per-subsystem params dicts. Missing subsystem ids are allowed (those subsystems fall back to their liveself.params); unknown ids raise so sys-id typos fail loudly instead of silently using defaults.Called once at public entry points (
DiagramSystem.f,tf,get_dynamic_geometry, the evaluators’ parametric tier, theparamssetter), not inside the recursion.- Parameters:
params (None or Mapping) – Candidate diagram-level params, e.g.
{"plant": {"m": 1.0}}.subsystem_ids (container of str) – Valid subsystem ids (e.g.
diagram.subsystems).
Composition shortcuts: the +, >> and @ operators that build a diagram in one line.
- minilink.core.composition.add_systems(*systems)[source]¶
Return a new flat diagram containing
systemsas subsystems.- Parameters:
*systems (System or DiagramSystem) – Systems or diagrams to add, side by side. A diagram operand is laid out flat in the result, with its ids, wiring and boundary ports; it is not modified. The first operand’s name,
x0and camera settings are carried into the result when it is a diagram.- Returns:
A diagram containing all provided systems as direct subsystems. Existing diagram-internal connections are preserved, but no new cross-connections are inferred.
- Return type:
- minilink.core.composition.series(left, right, *rest)[source]¶
Connect systems in series: one output of each stage to one input of the next.
A >> Bwires one output of A to one input of B, the first rule that applies: A has one output and B one input; exactly one output of A and one input of B share a name; A hasyand B hasu; else it refuses. A diagram counts its edge (its boundary outputs and its free boundary inputs), a leaf its ports. Sizes are a check, never a choice. One guard: a source into a block that reads bothrandyis refused, since the name match would feed the measurement. The result keeps A’s free inputs as its inputs and B’sy(else its only output) as its outputy.- Parameters:
left (System or DiagramSystem) – The first system or diagram; a diagram is laid out flat in the result and left unchanged, its name,
x0and camera settings carried.right (System or DiagramSystem) – System or diagram to connect after
left.*rest (System or DiagramSystem) – Additional systems or diagrams to append to the series chain.
- Returns:
A new diagram with each stage connected output-to-input.
- Return type:
- minilink.core.composition.feedback(sys, filter=1, *, of=None, sign=-1.0, validate=True)[source]¶
Close
syson itself with anErrorblock:r -> e = r - y -> sys -> y, the textbookT = L / (1 + L H).Negative feedback (the default) inserts
Error, so the diagram shows ports+,-, ande. Positive feedback still uses a signedSum. The junction drives the command input ofsys(command_port():u, else its one free input) — a plant, a transfer function, a compensator, or a series diagram such asC >> G; a block that reads its own measurement (yorx) is closed on its plant with@instead. What comes back isof, elsey, else the only output.sysis not modified: the result is a new diagram sharing its blocks, with boundary inputrand that output as boundary outputy.@always means controller at plant; this is the one way to close an open loop on itself.- Parameters:
filter (1, scalar, array, or System) – The return path
H:1is a direct wire, a scalar or matrix inserts aGainblock, a system (a sensor, a low-pass) is inserted as a block; the block is namedfiltereither way.of (str or (str, int), optional) – Which output comes back: a boundary output port id, or
(port, index)for one component. Default: the whole output when its dimension matches the input; a scalar input against a vector output takes component 0 through a visibleDemux; any other mismatch is an error.sign (float) –
-1.0for negative feedback (default),+1.0for positive.validate (bool) – Run algebraic-loop detection before returning.
- Return type:
- minilink.core.composition.closed_loop(controller, plant, *, reference=True, disturbance=False, noise=False, filter=None, observer=None, validate=True)[source]¶
Build a controller/plant feedback diagram — the
controller @ plantoperator.One rule, read from the port names (
block_roles()): every controller input namedyorxis wired to the plant output of the same name; a controller with neither (an error-driven block: a compensator, a transfer function, a plant, a series diagram) gets anErrorblocke = r - yon its command entry, the junction offeedback(). The controller’s command (u, else its one output) drives the plant’s command input, and the plant’sy(else its one output) is the loop’s outputy.The port names are the whole declaration: there is no port-name keyword. A mechanical plant’s
yis its state[q; dq], read straight; a block with other names is renamed (rename_ports) or wired by hand. A controller that readsxneeds a plant that exposes it: a leaf built withexpose_state=True, or a box, whose own state is read.A box (a diagram) on either side is nested under its role name as one block; a leaf stays a leaf.
@needs a plant on the right:feedback()closes an open loop on itself (feedback(L),feedback(L, K)).Neither operand is modified: the result is a new diagram sharing their blocks; a plant box whose state is read is copied, with its blocks shared.
- Parameters:
controller (System) – Controller subsystem with reference, measurement, and control ports.
plant (System) – Plant subsystem with control input and measurement output ports.
reference (bool or System, optional) – The loop’s inputs, on the ports
r,dandn: the reference (default on), the load disturbance and the measurement noise (both off by default).Trueputs the port on the loop’s edge,Falseleaves it out (the reference then reads its nominal value), and a source block (Step(),WhiteNoise(...)) drives it inside the loop, namedreference,disturbanceornoise. A plant that declaresdornowns it: the input drives the plant’s own port. Otherwise aSumadds the disturbance to the plant’s command (disturbance_sum) and the noise to the measurement before it returns (noise_sum).disturbance (bool or System, optional) – The loop’s inputs, on the ports
r,dandn: the reference (default on), the load disturbance and the measurement noise (both off by default).Trueputs the port on the loop’s edge,Falseleaves it out (the reference then reads its nominal value), and a source block (Step(),WhiteNoise(...)) drives it inside the loop, namedreference,disturbanceornoise. A plant that declaresdornowns it: the input drives the plant’s own port. Otherwise aSumadds the disturbance to the plant’s command (disturbance_sum) and the noise to the measurement before it returns (noise_sum).noise (bool or System, optional) – The loop’s inputs, on the ports
r,dandn: the reference (default on), the load disturbance and the measurement noise (both off by default).Trueputs the port on the loop’s edge,Falseleaves it out (the reference then reads its nominal value), and a source block (Step(),WhiteNoise(...)) drives it inside the loop, namedreference,disturbanceornoise. A plant that declaresdornowns it: the input drives the plant’s own port. Otherwise aSumadds the disturbance to the plant’s command (disturbance_sum) and the noise to the measurement before it returns (noise_sum).filter (System, scalar or array, optional) – The return path: a sensor or filter block, or a gain (a
Gainblock), namedfilter, between the plant’syand what reads the measurement (the controller’sy, theErrorblock, or the observer). The plant’systays the loop’s output and the noisenenters before the filter. A controller that readsxwithout an observer is refused: a filter on the state is an observer.observer (System, optional) – The state estimator, named
observer, for a controller that readsx: the plant’syand the controller’sugo in, and itsx(the estimate) goes to the controller’sx, so the plant’s own state is never read. Akalmanorluenbergerobserver with a state-feedback gain is the LQG loop.validate (bool, optional) – If
True, run algebraic-loop detection before returning.
- Returns:
A diagram with the plant’s
yas output and the inputs left on its edge (r,d,n) as inputs.- Return type:
- minilink.core.composition.autowire(diagram, *, strict=False, validate=True)[source]¶
Connect unconnected inputs by name: each takes the one output with its name and size.
- Parameters:
diagram (DiagramSystem) – Diagram to modify in place.
strict (bool, optional) – If
True, raise when an input stays open (no match, or several). No new connections are applied before that error is raised. IfFalse, the open inputs are left unconnected and a warning lists them.validate (bool, optional) – If
True, run algebraic-loop detection after wiring.
- Returns:
The same diagram, for fluent use.
- Return type:
Notes
Names only, no guessing: an output may feed several inputs, existing connections are never overwritten, and a disturbance
dor a noisenleft open reads its nominal value without a word. Name a source’s signal after the input it drives (Step(port="r")) and a block’s ports at creation (rename_ports).
- minilink.core.composition.resolve_standard_feedback(controller, plant, *, controller_id='controller', plant_id='plant')[source]¶
Choose the feedback port names shared by
closed_loopand the sampled loop.The controller’s port names decide (
minilink.core.feedback.block_roles()): a block that readsycloses on the plant’sy, one that readsxon itsx, and one that reads both on the first the plant can serve. The command leaves onu(u_fffor a solver, else the one output) and enters the plant onu(else its one input). Dimensions are a check, never a choice: a mechanical plant’syis its state[q; dq].- Parameters:
controller_id (str)
plant_id (str)
- Return type:
- class minilink.core.composition.StandardFeedbackWiring(control_out, measurement_in, plant_in, plant_out)[source]¶
Resolved port names for controller/computer ↔ plant feedback.
- Parameters:
control_out (str)
measurement_in (str)
plant_in (str)
plant_out (str)
- minilink.core.composition.default_computer_boundary_ports(controller)[source]¶
Return
(measurement_in, control_out)for step-diagram boundary expose.- Return type:
tuple[str, str]
Trajectory: sampled state and input on a shared time grid.
- class minilink.core.trajectory.SampledSignals[source]¶
The named channels of a sampled record:
x,uand the extrasignals.- property n: int¶
State dimension.
- property m: int¶
Input dimension.
- property signal_names: tuple[str, ...]¶
All available sampled channel names.
- class minilink.core.trajectory.Trajectory(t, x, u, signals=<factory>)[source]¶
Sampled state-input trajectory on a shared time grid.
The canonical sampled record of simulation, planning, control, and rendering workflows.
- Parameters:
t (np.ndarray) – Time samples with shape
(N,).x (np.ndarray) – State samples with shape
(n, N).u (np.ndarray) – Input samples with shape
(m, N).signals (dict of str to np.ndarray, optional) – Additional sampled signals, each with shape
(dim, N).
Notes
The trajectory semantic core is
(t, x, u). Extra signals such as outputs, derivatives, or reconstructed internal ports can be attached throughwith_signal()orwith_signals().- property n_samples: int¶
Number of time samples.
- property t0: float¶
Initial time.
- property tf: float¶
Final time.
- property time_duration: float¶
Total covered time span
tf - t0.
- with_signal(name, values)[source]¶
Return a new trajectory with one additional sampled signal.
- Parameters:
name (str) – Signal name. Cannot be
"x"or"u".values (np.ndarray) – Signal samples with shape
(dim, N).
- Return type:
- with_signals(values)[source]¶
Return a new trajectory with multiple additional sampled signals.
- Parameters:
values (dict[str, ndarray])
- Return type:
- sample(name, t)[source]¶
Sample a signal by nearest time index.
- Parameters:
name (str)
t (float)
- Return type:
ndarray
- t2x(t)[source]¶
Return the state sample nearest to time
t.- Parameters:
t (float)
- Return type:
ndarray
- t2u(t)[source]¶
Return the input sample nearest to time
t.- Parameters:
t (float)
- Return type:
ndarray
- minilink.core.trajectory.interpolate_schedule(xp, t_samples, values, t)[source]¶
Value of a sampled schedule at time
t: linear between samples, held outside.t_samplesholdsNnondecreasing sample times andvaluesthe samples, shape(N, ...); the result has the shape of one sample. At a repeated time the later sample holds, and a single sample holds at everyt.xpis the array module of the caller (NumPy or JAX).
Allowable sets: x(t) in X(t), u(t) in U(x, t) and boundary sets x(tf) in Xf.
- class minilink.core.sets.Set[source]¶
Base class for deterministic vector-valued allowable sets.
A point is feasible when all components of
margin()are nonnegative. The set may depend on time and optional parameters, so the generic notation isz in Z(t).margin()is a native-array equation path (NumPy in, NumPy out; JAX in, JAX out);contains()is a NumPy boundary utility, andsample()follows the draw convention ofDistribution.- abstract margin(z, t=0.0, params=None)[source]¶
Return nonnegative feasibility margins for
z, shape(k,).
- contains(z, t=0.0, params=None)[source]¶
Return
Truewhenzbelongs to the set.- Parameters:
z (ndarray)
t (float)
- Return type:
bool
- sample(key=None, n=None, params=None)[source]¶
Draw one point
(dim,)ornpoints(n, dim)from the set when supported.keyis a NumPy generator, an integer seed,None(unseeded) or a JAX PRNG key (JAX arrays out, traceable): the draw convention ofDistribution. Search planners use this optional method on samplable sets such as boxes; other sets leave it unimplemented and are sampled by rejection from a box.
- class minilink.core.sets.InputSet[source]¶
Base class for admissible input sets
u in U(x, t).Input feasibility often depends on the current state, for example actuator envelopes or contact-dependent controls. The state argument is optional so constant input boxes stay simple.
margin()andcontains()follow the conventions ofSet.- abstract margin(u, x=None, t=0.0, params=None)[source]¶
Return nonnegative feasibility margins for
u, shape(k,).
- contains(u, x=None, t=0.0, params=None)[source]¶
Return
Truewhenuis admissible at(x, t).- Parameters:
u (ndarray)
x (ndarray | None)
t (float)
- Return type:
bool
- sample(key=None, n=None, x=None, t=0.0, params=None)[source]¶
Draw one input
(m,)orninputs(n, m)when supported (same convention asSet.sample()).
- class minilink.core.sets.BoxSet(lower, upper)[source]¶
Axis-aligned box
lower <= z <= upper.- Parameters:
lower (array_like) – Lower and upper bounds with shape
(n,).upper (array_like) – Lower and upper bounds with shape
(n,).
- property dim: int¶
Dimension of the boxed vector.
- classmethod from_system_state(sys)[source]¶
The state box of
sys: its state bounds (sys.state.box).- Return type:
- margin(z, t=0.0, params=None)[source]¶
Return the margins to the lower and upper faces, shape
(2n,).
- class minilink.core.sets.BoxInputSet(box)[source]¶
State-independent input box
lower <= u <= upper.- Parameters:
box (BoxSet)
- classmethod from_bounds(lower, upper)[source]¶
Create an input box from lower and upper arrays.
- Parameters:
lower (ndarray)
upper (ndarray)
- Return type:
- classmethod from_system_inputs(sys)[source]¶
The input box of
sys: its input ports’ bounds, stacked in port order.- Return type:
- margin(u, x=None, t=0.0, params=None)[source]¶
Return the margins to the input box faces, shape
(2m,).
- class minilink.core.sets.SingletonSet(point)[source]¶
Singleton boundary set
z == point.The margin uses a zero-tolerance absolute residual so exact equality can still be checked through
contains(). Optimizer equality constraints should useresidual()directly.- Parameters:
point (ndarray)
- property dim: int¶
Dimension of the singleton point.
- class minilink.core.sets.BallSet(center, radius)[source]¶
Euclidean ball
||z - center|| <= radius.- Parameters:
center (ndarray)
radius (float)
- property dim: int¶
Dimension of the ball center.
- class minilink.core.sets.CallableSet(margin_fn=None, contains_fn=None)[source]¶
Set backed by user callables.
- Parameters:
margin_fn (callable, optional) – Function
margin_fn(z, t, params) -> array.contains_fn (callable, optional) – Function
contains_fn(z, t, params) -> bool.
- class minilink.core.sets.IntersectionSet(sets)[source]¶
Intersection of several sets,
z in Z_1 and z in Z_2 and ....Built by the
&operator onSet;a & b & cis one flat intersection of three members.- Parameters:
sets (tuple)
- classmethod of(*sets)[source]¶
Build an intersection, flattening nested intersections into one member list.
- Parameters:
sets (Set)
- Return type:
- minilink.core.sets.is_finite_box(box)[source]¶
Truefor a box with finite bounds on every axis.- Return type:
bool
Cost functions: the running cost g(x, u, t) and the terminal cost h(x, t) of J.
- class minilink.core.costs.CostFunction[source]¶
Mother class for deterministic planning cost functions.
Subclasses define a running cost
g(x, u, t)and a terminal costh(x, t)of the objectiveJ = ∫ exp(-rho t) g dt + h(x(tf), tf). The horizon is the planning problem’s: a finitetfends the integral and chargeshthere, an infinite one never does. Leaving the allowed set is priced by the problem (infeasible_cost), not by the cost.gandhare native-array equation paths: NumPy in, NumPy out; JAX in, JAX out. Reporting helpers such astotal_cost()convert to Python floats at the boundary. A cost lives inminilink.core, not on aSystem, so one model serves many planning problems.Class attribute (override it in the body of a subclass):
discount_rate: continuous raterho >= 0;0is undiscounted. Planners convert it withdiscount_factor()(value iteration’salphaand reinforcement learning’sgammaare bothexp(-rho dt)). The library costs are frozen dataclasses, so assigning it on one of their instances raises.weight * costkeeps the cost’s rate anda + brequires equal rates. No constructor field orparamsentry sets the rate; a subclass does.
- abstract g(x, u, t=0.0, params=None)[source]¶
Return the native scalar running cost density
g(x, u, t).
- discount_factor(dt)[source]¶
Per-step factor
exp(-rho dt)for a planner with time stepdt.- Return type:
float
- evaluate_trajectory(traj, params=None)[source]¶
Return
trajwith sampled running and cumulative costs.The added signals are
"cost_rate"and"cost"with shape(1, N).cost_rateis the discounted integrandexp(-rho t) g(x, u, t); the cumulative integral uses the trapezoidal rule and excludes the terminal cost; callers can addh(x(tf), tf)when they need the full objective scalar.- Parameters:
traj (Trajectory)
- Return type:
- terminal_cost(traj, params=None)[source]¶
Return
hevaluated at the final state oftraj.- Parameters:
traj (Trajectory)
- Return type:
float
- total_cost(traj, params=None)[source]¶
Return the trapezoidal running cost plus terminal cost.
- Parameters:
traj (Trajectory)
- Return type:
float
- class minilink.core.costs.QuadraticCost(Q, R, S, xbar, ubar)[source]¶
Quadratic running and terminal cost.
The running cost is
(x - xbar).T @ Q @ (x - xbar) + (u - ubar).T @ R @ (u - ubar). The terminal cost is(x - xbar).T @ S @ (x - xbar).- Parameters:
Q (ndarray)
R (ndarray)
S (ndarray)
xbar (ndarray)
ubar (ndarray)
- classmethod from_system(sys, *, Q=None, R=None, S=None, xbar=None, ubar=None)[source]¶
Create a default quadratic cost from a Minilink system.
Parameters not provided default to identity running weights, zero terminal weight, and the system’s operating point as the target: its nominal state and nominal input.
- Parameters:
Q (ndarray | None)
R (ndarray | None)
S (ndarray | None)
xbar (ndarray | None)
ubar (ndarray | None)
- class minilink.core.costs.TimeCost(xbar, eps=0.001)[source]¶
Minimum-time cost
J = T.The running cost is
g = 1everywhere except withinepsof the targetxbar, where it is0(the absorbing goal); the terminal cost is0. Under dynamic programming the cost-to-go becomes the time-to-go and the optimal policy is bang-bang.- Parameters:
xbar (ndarray)
eps (float)
- class minilink.core.costs.SumCost(terms)[source]¶
Additive cost
J = sum_i J_iover several cost functions.Built by the
+operator onCostFunction; use it to add an obstacle or traversability term to a base objective. The terms share onediscount_rate; terms whose rates differ raiseValueError.- Parameters:
terms (tuple)
- property discount_rate: float¶
The terms’ common rate
rho(0for an empty sum).
- classmethod of(*costs)[source]¶
Build a sum cost, flattening nested sums into a single term list.
- Parameters:
costs (CostFunction)
- Return type:
- class minilink.core.costs.ScaledCost(cost, weight)[source]¶
Cost scaled by a weight,
J = weight * J0.Built by the
*operator onCostFunction, so a weighted sum reads asbase + weight * obstacle_cost. The weight leaves thediscount_rateofJ0unchanged.- Parameters:
cost (CostFunction)
weight (float)
- property discount_rate: float¶
The scaled cost’s rate
rho.
Who is who in a loop: the roles the port names and the reserved subsystem names declare.
- minilink.core.feedback.RESERVED_IDS = ('plant', 'controller', 'observer')¶
The three subsystem names that say what role a block plays in a diagram. One of each per diagram level; a second one is refused, never suffixed.
- class minilink.core.feedback.BlockRoles(measurements, reference, command, entry, stateful)[source]¶
What a block reads and commands, read from its port names alone.
A controller is an ordinary
System(aDynamicSystemwhen it has state): its roles change nothing in how it computes.@,feedback, the sampled loop,plot_control_lawand the evaluators read them; a block with other port names is wired by hand.- Parameters:
measurements (tuple)
reference (str | None)
command (str | None)
entry (str | None)
stateful (bool)
- measurements¶
The plant signals the block reads,
"y"and"x"among its inputs, in the order a loop tries them. Empty for an error-driven block or a source.- Type:
tuple of str
- reference¶
"r"when the block has a reference input.- Type:
str or None
- command¶
"u"when the block commands one, elseNone.- Type:
str or None
- entry¶
The input an
Errorblocke = r - ydrives when the block reads no measurement itself:"e", or the one input of a plain compensator; a disturbancedor a noiseninput is never the entry.- Type:
str or None
- stateful¶
Whether the block has internal state (
n > 0).- Type:
bool
- property measurement¶
The first measurement the block reads, or
None.
- property error_driven¶
Whether an
Errorblock drives the block’s entry.
- minilink.core.feedback.block_roles(block)[source]¶
What
blockreads and commands, from its port names (seeBlockRoles).- Return type:
- minilink.core.feedback.command_port(sys)[source]¶
The input a decision drives on
sys:u, else its one input that is notdorn.The design tools (LQR, pole placement, value iteration, trajectory optimization, MPC), the cost and the Gym bridge decide this port only; a disturbance
dand a measurement noisenare never decision variables, and a cascade’s outer loop drives the inner loop’s one free input.- Return type:
str
- minilink.core.feedback.require_command(block)[source]¶
The command output of
block(u, elseu_ff), orNoneto fall back on its default output.A block that reads a measurement (
yorx) and gives the statexback, with nouand noyto command through, is an observer: it is refused as a controller, with the recipe that makes it one. A compensator (no measurement) or a box that commands through an output namedy(a gain in series with a saturation) is left to its default output.- Return type:
str | None
- class minilink.core.feedback.Roles(plant, controller, observer)[source]¶
The plant, the controller and the observer of a diagram, or
Noneeach.- Parameters:
plant (object | None)
controller (object | None)
observer (object | None)
- minilink.core.feedback.roles(system)[source]¶
The plant, the controller and the observer of
system, from the reserved names.A leaf is its own plant. A diagram answers from its top-level keys
plant,controllerandobserver(the observer may also sit inside the controller); a sampled loop from itsplantandcomputersides. A missing name isNone: a tool that needs that block raises, and the plots and the camera keep their plain defaults.- Return type:
- minilink.core.feedback.check_reserved_id(sys_id, existing)[source]¶
Refuse a second block under a reserved name: one of each per diagram level.
- Return type:
None
- minilink.core.feedback.plot_space_of(block)[source]¶
The sweep space of
block’s law forplot_control_law, orNone.An explicit
plot_spaceattribute wins, the one hint the ports cannot give (Nonesays the block has no static law to draw, a solver call such as MPC); else the ports decide: a block that readsxsweeps the state, one that readsyand a reference of the same size the errore = r - ywithrpinned, one that readsyotherwise the measurement, and an error-driven block its entry port.
- class minilink.core.feedback.ErrorDriven[source]¶
Port-layout switch for laws written on the tracking error
e = r - y.ports="error"declares one inpute— the compensator form, whereblock @ plantinserts the Error block andblock >> plantis the loop gain.ports="reference"declaresrandy— the controller form, where@wires them to the plant. The law readse = self.error(u)either way, so it is written once.- add_error_ports(ports, dim, *, nominal_value=None)[source]¶
Declare the input ports of the chosen layout, and return the layout.
The constructor assigns the layout it returns to
self.port_layout.
- property error_dependencies¶
Input ports the command feeds through from.
Probability distributions: x ~ p with sample(key) on NumPy or JAX, mean() and an optional support.
- class minilink.core.distributions.Distribution[source]¶
Vector-valued distribution:
dim,mean(an array),sample(key, n=None), optionalsupport.A duck type, not a probability library: anything with
dim,sample(key)andmeanworks.keyis a NumPy generator, an integer seed, or a JAX PRNG key (JAX arrays out, traceable underjit/vmap/scan).
- class minilink.core.distributions.Gaussian(mean, std)[source]¶
Diagonal Gaussian
x ~ N(mean, diag(std**2)).- Parameters:
mean (ndarray)
- class minilink.core.distributions.Uniform(lower, upper)[source]¶
Uniform law on the box
lower <= x <= upper;supportis that box.
- class minilink.core.distributions.Particles(points)[source]¶
Empirical distribution: a uniform choice among fixed points
(N, dim).
- class minilink.core.distributions.Sampler(draw, mean, support=None)[source]¶
Distribution defined by a sampling function
draw(key) -> x.For task-specific starts (a random point along a track, a random pose in free space).
drawmust accept a JAX key and trace;meanis the representative point given explicitly.- Parameters:
mean (ndarray)
support (Set | None)
Workspace solids by their signed distance sdf(p): negative inside, zero on the surface.
- class minilink.core.geometry.Shape[source]¶
Base class for workspace solids defined by a signed-distance function.
A point
pis inside the solid whensdf()is negative. The solid may depend on time and optional parameters, so the generic signature issdf(p, t, params).- abstract property dim: int¶
Workspace dimension (2 or 3).
- abstract sdf(p, t=0.0, params=None)[source]¶
Return the signed distance from
pto the shape surface.
- class minilink.core.geometry.Sphere(center, radius)[source]¶
Ball
||p - center|| <= radius(a disc in 2-D, a sphere in 3-D).- Parameters:
center (array_like) – Center with shape
(dim,).radius (float) – Nonnegative radius.
- property dim: int¶
Workspace dimension (2 or 3).
- class minilink.core.geometry.Box(lower, upper)[source]¶
Axis-aligned box
lower <= p <= upper.- Parameters:
lower (array_like) – Opposite corners with shape
(dim,).upper (array_like) – Opposite corners with shape
(dim,).
- property dim: int¶
Workspace dimension (2 or 3).
- class minilink.core.geometry.Union(shapes)[source]¶
Union of several shapes, with
sdf = minover the members.This is the obstacle-field primitive: a point is occupied when it lies inside any member shape.
- Parameters:
shapes (tuple)
- property dim: int¶
Workspace dimension (2 or 3).
- class minilink.core.geometry.Inflated(base, radius)[source]¶
Shape grown outward by
radius(Minkowski sum with a ball).Subtracting the radius from a base signed distance gives the clearance for a disc-shaped robot, or a safety margin around a hard obstacle.
- Parameters:
base (Shape)
radius (float)
- property dim: int¶
Workspace dimension (2 or 3).