Systems¶
Systems is the public modeling namespace in Contrax. It brings together the objects and helpers you use to represent dynamics before you design a controller, run an estimator, or simulate a trajectory.
The namespace includes:
- continuous and discrete LTI models:
ContLTI,DiscLTI,ss(),dss() - nonlinear and port-Hamiltonian model objects:
NonlinearSystem,PHSSystem,nonlinear_system(),phs_system(),schedule_phs() - PHS structure helpers:
canonical_J()to define the symplectic structure map,phs_to_ss()to extract the local state-space linearization around an operating point - structured-system helpers:
partition_state(),block_observation(),block_matrix(),symmetrize_matrix(),project_psd(),phs_diagnostics() - discretization and local model construction:
c2d(),linearize(),linearize_ss() - simple state-space composition:
series(),parallel(), plus operator overloads onContLTIandDiscLTI
Minimal Example¶
Use the systems namespace when you want the model-building story directly:
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import contrax.systems as cxs
sys_c = cxs.ss(
jnp.array([[0.0, 1.0], [0.0, 0.0]]),
jnp.array([[0.0], [1.0]]),
jnp.eye(2),
jnp.zeros((2, 1)),
)
sys_d = cxs.c2d(sys_c, dt=0.05)
Canonical Model Forms¶
LTI And Nonlinear Systems¶
The LTI and generic nonlinear contracts are the broad modeling surface.
Port-Hamiltonian Systems¶
PHSSystem is the structured nonlinear family in
Contrax.
For scheduled coefficients, schedule_phs() binds an exogenous context into
that canonical model:
Conventions¶
A: state matrix with shape(n, n)B: input matrix with shape(n, m)C: output matrix with shape(p, n)D: feedthrough matrix with shape(p, m)DiscLTI.dt: scalar sample time stored as an array leaf
For nonlinear models, the reusable model-object contract is:
dynamics(t, x, u)for the transition or vector fieldoutput(t, x, u)for the output or measurement map
Contrax keeps continuous and discrete LTI systems on the same field names so code can move between them with minimal ceremony.
For PHSSystem, the base library contract is
intentionally narrower:
H(x)is the storage functionR(x)is the dissipation map when presentG(x)is the input map when presentJ(x)defaults to the canonical symplectic structure unless you override itschedule_phs()is the extension path when dissipation or input maps depend on observed scheduling contextschedule_phs()can also bind scheduled structure mapsJ(t, x, context)when the structure itself varies with observed context
Structured Helpers¶
Contrax now includes a small generic helper layer around structured nonlinear models so downstream repos do not need their own mini utility module just to express common PHS patterns.
Use:
partition_state(x, block_sizes)to split a structured state into consecutive blocksblock_observation(block_sizes, block_indices)to build partial-state output maps from those blocksblock_matrix(row_block_sizes, col_block_sizes, blocks)to assemble dense structured input, process, or observation maps from block entriessymmetrize_matrix(M)andproject_psd(M)when dissipation or covariance matrices pick up small numerical asymmetry or PSD driftphs_diagnostics(sys, x, u)for local skew-symmetry, dissipation, and instantaneous power-balance diagnostics
These are intentionally lightweight helpers, not a large structured-model framework. They are there to make the public PHS layer ergonomic and reusable.
Port-Hamiltonian Structure Helpers¶
canonical_J(n) returns the \(n \times n\) canonical symplectic structure
matrix for even \(n\):
Use it when building a PHSSystem whose
structure map is standard symplectic rather than a custom dissipative one.
phs_to_ss(sys, x_eq, u_eq) linearizes a PHS at an operating point and
returns a ContLTI. This is the standard bridge
from a structured nonlinear PHS model into the linear control design stack
(lqr, place, kalman_gain, etc.):
lti = cx.phs_to_ss(phs_sys, x_eq=jnp.zeros(4), u_eq=jnp.zeros(1))
design = cx.lqr(cx.c2d(lti, dt=0.01), Q, R)
Linearization And Discretization¶
linearize() and linearize_ss() are the main JAX-native bridge from
nonlinear plant code into the linear control stack.
Use:
linearize(f, x0, u0)when you only needAandBlinearize_ss(f, x0, u0, output=h)when you have plain dynamics and output callableslinearize_ss(sys, x0, u0)when you want a full localContLTIfrom a reusableNonlinearSystemorPHSSystem
The local linear model is the usual first-order expansion around an operating point:
with
Then use c2d() when the downstream workflow is discrete-time design or
simulation.
The c2d() methods are:
zoh: the stronger path, with a custom VJP around the matrix exponentialtustin: a convenience bilinear transform
ContLTI
with linearize_ss(), discretize it with c2d(),
then move into controller design and validation.
Interconnection¶
Simple state-space composition lives in the same public namespace because it is part of the model-building story.
Use:
series(sys2, sys1)orsys2 @ sys1for feedforward compositionparallel(sys1, sys2)orsys1 + sys2for summed-output compositionsys1 - sys2for parallel subtraction
Mixed continuous/discrete interconnections are intentionally unsupported, and
discrete interconnections require matching dt.
State ordering is explicit:
series(sys2, sys1)putssys1states first, thensys2parallel(sys1, sys2)putssys1states first, thensys2
Transform Behavior¶
The system containers themselves are pytrees, so they work naturally with
jit and vmap when shapes line up. That matters because the intended
Contrax workflows include vmapped linearization, batched controller design, and
compiled design-and-simulate objectives.
The important user-facing transform contracts here are:
linearize()andlinearize_ss()are designed to compose withjitandvmapDiscLTI.dtstays as an array leaf rather than static metadata, which makes batching over systems more natural- composition helpers are ordinary state-space algebra once family and shape checks pass
Numerical Notes¶
Contrax does not enable float64 globally on import. Precision-sensitive system
helpers such as c2d() require jax_enable_x64=True.
For nonlinear local models, remember that linearize() and linearize_ss()
produce local behavior around one operating point; they are not a guarantee of
global model fidelity.
The structured-system diagnostics are local algebraic checks. They are useful for sanity checking model definitions and recursive-estimation plumbing, but they are not a formal certificate of passivity or robustness.
Related Pages¶
- Simulation for
lsim(),simulate(), and response helpers - Control for LQR, Riccati solves, and state feedback
- Discretization and linearization
for the explanation page behind
c2d()andlinearize_ss() - Linearize, LQR, simulate for an end-to-end workflow
contrax.systems
¶
Public systems namespace.
This module gathers the system-model concepts that users typically look for first: LTI containers and constructors, nonlinear model containers, basic interconnection, and linearization helpers.
ContLTI
¶
Bases: Module
Continuous-time LTI system: ẋ = Ax + Bu, y = Cx + Du.
Source code in contrax/core.py
DiscLTI
¶
Bases: Module
Discrete-time LTI system: x_{k+1} = Ax_k + Bu_k, y_k = Cx_k + Du_k.
Source code in contrax/core.py
NonlinearSystem
¶
Bases: Module
General nonlinear system container.
The reusable model-object contract in Contrax is:
dynamics(t, x, u)for the state transition or vector fieldoutput(t, x, u)for the measured/output map
The constructor keeps the underlying field name observation so existing
pytrees stay stable, while the public constructor prefers the more
control-oriented output= spelling.
Source code in contrax/nonlinear.py
PHSSystem
¶
Bases: Module
Port-Hamiltonian system with user-supplied storage and structure maps.
Source code in contrax/phs.py
as_nonlinear_system() -> NonlinearSystem
¶
Expose the PHS model under the generic nonlinear-system contract.
Source code in contrax/phs.py
dynamics(t: Array | float, x: Array, u: Array) -> Array
¶
Evaluate the port-Hamiltonian vector field.
Source code in contrax/phs.py
block_matrix(row_block_sizes: Sequence[int], col_block_sizes: Sequence[int], blocks: dict[tuple[int, int], ArrayLike] | None = None, *, dtype: dtype | None = None) -> Array
¶
Assemble a dense matrix from block entries.
Source code in contrax/phs.py
block_observation(block_sizes: Sequence[int], block_indices: Sequence[int]) -> Callable[[Array | float, Array, Array], Array]
¶
Build an output map that concatenates selected state blocks.
Source code in contrax/phs.py
c2d(sys: ContLTI, dt: float, method: str = 'zoh') -> DiscLTI
¶
Discretize a continuous-time state-space system.
Converts a ContLTI into a DiscLTI using either zero-order hold or the bilinear Tustin transform.
The zoh path is the stronger current implementation and is differentiated
through a custom VJP on the matrix exponential. tustin is available as a
convenience discretization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
ContLTI
|
Continuous-time system. |
required |
dt
|
float
|
Sampling period. Shape: scalar. |
required |
method
|
str
|
Discretization method. Supported values are |
'zoh'
|
Returns:
| Type | Description |
|---|---|
DiscLTI
|
DiscLTI: A discrete-time realization with the same output matrices and the requested sampling period. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import jax.numpy as jnp
>>> import contrax as cx
>>> sys_c = cx.ss(
... jnp.array([[0.0, 1.0], [0.0, 0.0]]),
... jnp.array([[0.0], [1.0]]),
... jnp.eye(2),
... jnp.zeros((2, 1)),
... )
>>> sys_d = cx.c2d(sys_c, dt=0.05)
Source code in contrax/core.py
canonical_J(n_dofs: int, *, dtype: dtype | None = None) -> Array
¶
Construct the canonical skew-symmetric PHS structure matrix.
Source code in contrax/phs.py
dss(A: ArrayLike, B: ArrayLike, C: ArrayLike, D: ArrayLike, dt: ArrayLike) -> DiscLTI
¶
Construct a discrete-time state-space system.
Wraps discrete-time matrices in a
DiscLTI pytree with MATLAB-familiar
ss(A, B, C, D, dt) ergonomics.
This is a lightweight constructor rather than a solver. The sampling period is stored as a plain array leaf instead of static metadata, which keeps batching over systems more natural in JAX.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A
|
ArrayLike
|
State transition matrix. Shape: |
required |
B
|
ArrayLike
|
Input matrix. Shape: |
required |
C
|
ArrayLike
|
Output matrix. Shape: |
required |
D
|
ArrayLike
|
Feedthrough matrix. Shape: |
required |
dt
|
ArrayLike
|
Sampling period. Shape: scalar. |
required |
Returns:
| Type | Description |
|---|---|
DiscLTI
|
DiscLTI: A discrete-time system with
array-valued |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the matrix shapes are inconsistent. |
Examples:
>>> import jax.numpy as jnp
>>> import contrax as cx
>>> sys = cx.dss(
... jnp.array([[1.0, 0.1], [0.0, 1.0]]),
... jnp.array([[0.0], [0.1]]),
... jnp.eye(2),
... jnp.zeros((2, 1)),
... dt=0.1,
... )
Source code in contrax/core.py
linearize(model_or_f: DynamicsLike, x0: Array, u0: Array, *, t: ArrayLike = 0.0) -> tuple[Array, Array]
¶
Linearize a dynamics function around an operating point.
Computes the Jacobians A = df/dx and B = df/du at (x0, u0) using
forward-mode automatic differentiation.
This is one of the main JAX-native workflow bridges in Contrax. It works
cleanly with jit, vmap, and differentiation as long as f is written
as a pure JAX function without Python control flow over traced values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_or_f
|
DynamicsLike
|
A nonlinear system model or pure JAX dynamics function
mapping |
required |
x0
|
Array
|
State operating point. Shape: |
required |
u0
|
Array
|
Input operating point. Shape: |
required |
t
|
ArrayLike
|
Operating-point time for system-model linearization. |
0.0
|
Returns:
| Type | Description |
|---|---|
Array
|
tuple[Array, Array]: Jacobian pair |
Array
|
|
Examples:
>>> import jax.numpy as jnp
>>> import contrax as cx
>>> def dynamics(x, u):
... return jnp.array([x[1], -jnp.sin(x[0]) + u[0]])
>>> A, B = cx.linearize(dynamics, jnp.zeros(2), jnp.zeros(1))
Source code in contrax/core.py
linearize_ss(model_or_f: DynamicsLike, x0: Array, u0: Array, *, output: OutputFn | None = None, t: ArrayLike = 0.0) -> ContLTI
¶
Linearize dynamics and output maps into a continuous state-space model.
Computes A, B, C, and D Jacobians at (x0, u0) and returns them as
a ContLTI.
This is the main bridge from nonlinear plant code into the linear control
stack. Pair it with c2d() and lqr() when building local controllers
around an operating point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model_or_f
|
DynamicsLike
|
A nonlinear system model or pure JAX dynamics function
mapping |
required |
x0
|
Array
|
State operating point. Shape: |
required |
u0
|
Array
|
Input operating point. Shape: |
required |
output
|
OutputFn | None
|
Output function mapping |
None
|
t
|
ArrayLike
|
Operating-point time for system-model linearization. |
0.0
|
Returns:
| Type | Description |
|---|---|
ContLTI
|
ContLTI: A continuous-time state-space
model with linearized |
Examples:
>>> import jax.numpy as jnp
>>> import contrax as cx
>>> def dynamics(x, u):
... return jnp.array([x[1], -jnp.sin(x[0]) + u[0]])
>>> def output(x, u):
... return x
>>> sys_c = cx.linearize_ss(
... dynamics,
... jnp.zeros(2),
... jnp.zeros(1),
... output=output,
... )
Source code in contrax/core.py
nonlinear_system(dynamics: Callable, output: Callable | None = None, *, observation: Callable | None = None, dt: ArrayLike | None = None, state_dim: int | None = None, input_dim: int | None = None, output_dim: int | None = None) -> NonlinearSystem
¶
Construct a reusable nonlinear system model.
Contrax uses NonlinearSystem as the
shared contract for nonlinear simulation, linearization, and estimation.
The intended callable shape is dynamics(t, x, u) and, when present,
output(t, x, u).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dynamics
|
Callable
|
State transition or vector-field callable with signature
|
required |
output
|
Callable | None
|
Optional output/measurement map with signature |
None
|
observation
|
Callable | None
|
Deprecated synonym for |
None
|
dt
|
ArrayLike | None
|
Optional discrete sample time. Omit for continuous models. |
None
|
state_dim
|
int | None
|
Optional static state dimension metadata. |
None
|
input_dim
|
int | None
|
Optional static input dimension metadata. |
None
|
output_dim
|
int | None
|
Optional static output dimension metadata. |
None
|
Returns:
| Type | Description |
|---|---|
NonlinearSystem
|
NonlinearSystem: A reusable nonlinear system model. |
Source code in contrax/nonlinear.py
parallel(sys1: DiscLTI | ContLTI, sys2: DiscLTI | ContLTI, *, sign: float = 1.0) -> DiscLTI | ContLTI
¶
Connect two systems in parallel and sum their outputs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys1
|
DiscLTI | ContLTI
|
First system. |
required |
sys2
|
DiscLTI | ContLTI
|
Second system. |
required |
sign
|
float
|
Output sign applied to |
1.0
|
Returns:
| Type | Description |
|---|---|
DiscLTI | ContLTI
|
Source code in contrax/interconnect.py
partition_state(x: ArrayLike, block_sizes: Sequence[int]) -> tuple[Array, ...]
¶
Split a one-dimensional state vector into consecutive logical blocks.
Source code in contrax/phs.py
phs_diagnostics(sys: PHSSystem, x: ArrayLike, u: ArrayLike | None = None) -> PHSStructureDiagnostics
¶
Evaluate local structure and power-balance diagnostics for a PHS state.
Source code in contrax/phs.py
phs_system(H: Callable[[Array], Array], *, R: Callable[[Array], Array] | None = None, G: Callable[[Array], Array] | None = None, J: Callable[[Array], Array] | None = None, output: Callable | None = None, observation: Callable | None = None, dt: ArrayLike | None = None, state_dim: int | None = None, input_dim: int | None = None, output_dim: int | None = None) -> PHSSystem
¶
Construct a port-Hamiltonian system object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
H
|
Callable[[Array], Array]
|
Storage/Hamiltonian function. |
required |
R
|
Callable[[Array], Array] | None
|
Optional dissipation map. |
None
|
G
|
Callable[[Array], Array] | None
|
Optional input map. |
None
|
J
|
Callable[[Array], Array] | None
|
Optional structure map. |
None
|
output
|
Callable | None
|
Optional output/measurement map with signature |
None
|
observation
|
Callable | None
|
Deprecated synonym for |
None
|
dt
|
ArrayLike | None
|
Optional discrete sample time. |
None
|
state_dim
|
int | None
|
Optional static state dimension metadata. |
None
|
input_dim
|
int | None
|
Optional static input dimension metadata. |
None
|
output_dim
|
int | None
|
Optional static output dimension metadata. |
None
|
Returns:
| Type | Description |
|---|---|
PHSSystem
|
PHSSystem: A reusable port-Hamiltonian model. |
Source code in contrax/phs.py
phs_to_ss(sys: PHSSystem, x_eq: Array, u_eq: Array) -> ContLTI
¶
Linearize a port-Hamiltonian system to a continuous-time state-space model.
Computes the Jacobian linearization of the PHS dynamics and observation map
at the operating point (x_eq, u_eq). The result is the standard
ẋ ≈ A δx + B δu / y ≈ C δx + D δu form around that point.
For a linear PHS with quadratic Hamiltonian H(x) = ½ x^T M x, the
linearization is exact and gives A = (J - R) M, B = G.
This is a thin convenience wrapper around
linearize_ss(sys.as_nonlinear_system(), x_eq, u_eq, t=0.0). It adds
an explicit PHS-to-state-space path so that the intent is documented and
the connection between the PHS model and its LTI state-space representation
is clear.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
PHSSystem
|
Port-Hamiltonian system. |
required |
x_eq
|
Array
|
Equilibrium state. Shape: |
required |
u_eq
|
Array
|
Equilibrium input. Shape: |
required |
Returns:
| Type | Description |
|---|---|
ContLTI
|
ContLTI: Continuous-time LTI approximation |
ContLTI
|
at |
Examples:
>>> import jax.numpy as jnp
>>> import contrax as cx
>>> def H(x): return 0.5 * jnp.dot(x, x)
>>> phs = cx.phs_system(H, state_dim=2, input_dim=1)
>>> lti = cx.phs_to_ss(phs, jnp.zeros(2), jnp.zeros(1))
>>> isinstance(lti, cx.ContLTI)
True
Source code in contrax/phs.py
project_psd(M: ArrayLike, *, min_eigenvalue: float = 0.0) -> Array
¶
Project a square matrix onto the symmetric PSD cone.
Source code in contrax/phs.py
schedule_phs(sys: PHSSystem, context_fn: Callable[[Array | float], Any], *, J: Callable[[Array | float, Array, Any], Array] | None = None, R: Callable[[Array | float, Array, Any], Array] | None = None, G: Callable[[Array | float, Array, Any], Array] | None = None, observation: Callable[[Array | float, Array, Array, Any], Array] | None = None) -> NonlinearSystem
¶
Bind an exogenous schedule/context to a canonical PHS model.
The returned object is an ordinary NonlinearSystem, so it composes with the same simulation, estimation, and linearization surface as any other nonlinear system in Contrax.
context_fn(t) should be a pure JAX-friendly callable returning any pytree
of observed scheduling/context variables, such as a scale parameter
theta(t).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
PHSSystem
|
Canonical PHS model with state-only |
required |
context_fn
|
Callable[[Array | float], Any]
|
Callable mapping time to observed scheduling/context data. |
required |
J
|
Callable[[Array | float, Array, Any], Array] | None
|
Optional scheduled structure map |
None
|
R
|
Callable[[Array | float, Array, Any], Array] | None
|
Optional scheduled dissipation map |
None
|
G
|
Callable[[Array | float, Array, Any], Array] | None
|
Optional scheduled input map |
None
|
observation
|
Callable[[Array | float, Array, Array, Any], Array] | None
|
Optional scheduled observation map
|
None
|
Returns:
| Type | Description |
|---|---|
NonlinearSystem
|
NonlinearSystem: A scheduled nonlinear system with the context bound. |
Source code in contrax/phs.py
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | |
series(sys2: DiscLTI | ContLTI, sys1: DiscLTI | ContLTI) -> DiscLTI | ContLTI
¶
Connect two systems in series as sys2 @ sys1.
The output of sys1 feeds the input of sys2. State ordering follows the
MATLAB-style convention: sys1 states first, then sys2 states.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys2
|
DiscLTI | ContLTI
|
Downstream system. |
required |
sys1
|
DiscLTI | ContLTI
|
Upstream system. |
required |
Returns:
| Type | Description |
|---|---|
DiscLTI | ContLTI
|
Source code in contrax/interconnect.py
ss(A: ArrayLike, B: ArrayLike, C: ArrayLike, D: ArrayLike) -> ContLTI
¶
Construct a continuous-time state-space system.
Wraps continuous-time matrices in a
ContLTI pytree with MATLAB-familiar
ss(A, B, C, D) ergonomics.
This is a lightweight constructor rather than a solver. It is safe under
normal JAX transforms with fixed-shape array inputs because it only wraps
validated arrays into a data-only eqx.Module.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A
|
ArrayLike
|
State matrix. Shape: |
required |
B
|
ArrayLike
|
Input matrix. Shape: |
required |
C
|
ArrayLike
|
Output matrix. Shape: |
required |
D
|
ArrayLike
|
Feedthrough matrix. Shape: |
required |
Returns:
| Type | Description |
|---|---|
ContLTI
|
ContLTI: A continuous-time system with
array-valued |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the matrix shapes are inconsistent. |
Examples:
>>> import jax.numpy as jnp
>>> import contrax as cx
>>> sys = cx.ss(
... jnp.array([[0.0, 1.0], [0.0, 0.0]]),
... jnp.array([[0.0], [1.0]]),
... jnp.eye(2),
... jnp.zeros((2, 1)),
... )
Source code in contrax/core.py
symmetrize_matrix(M: ArrayLike) -> Array
¶
Return the symmetric part 0.5 * (M + M.T) of a square matrix.