JAX-Native Workflows¶
These examples show differentiable controller design, batched linearization, compiled simulation, and nonlinear estimation workflows.
Runnable scripts:
examples/differentiable_lqr.pyexamples/linearize_lqr_simulate.pyexamples/continuous_lqr.pyexamples/lqr_optimal_execution.pyexamples/continuous_nonlinear_estimation.pyexamples/structured_nonlinear_estimation.py
Related API pages:
1. Tune LQR Weights With Gradients¶
dare and lqr can sit inside an objective and be differentiated with respect
to controller weights.
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import contrax as cx
A = jnp.array([[1.0, 0.05], [0.0, 1.0]])
B = jnp.array([[0.0], [0.05]])
sys = cx.dss(A, B, jnp.eye(2), jnp.zeros((2, 1)), dt=0.05)
x0 = jnp.array([1.0, 0.0])
def objective(q_diag, log_r):
Q = jnp.diag(q_diag)
R = jnp.exp(log_r)[None, None]
K = cx.lqr(sys, Q, R).K
trajectory = cx.simulate(sys, x0, lambda t, x: -K @ x, num_steps=80)
return jnp.sum(trajectory.xs**2)
objective_and_grad = jax.jit(jax.value_and_grad(objective, argnums=(0, 1)))
cost, (dq, dlog_r) = objective_and_grad(jnp.ones(2), jnp.array(0.0))
The objective differentiates through controller design and simulation as one JAX computation.
Related pages: - Tune LQR with gradients - Differentiable LQR
2. Turn Nonlinear Dynamics Into State-Space Models¶
linearize (also available as linearize_ss) lets you move from nonlinear
plant code to local state-space models with one JAX-compatible call.
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import contrax as cx
def pendulum(t, x, u):
theta, theta_dot = x
torque = u[0]
return jnp.array([theta_dot, -jnp.sin(theta) + torque])
def sensor(x, u):
return x
x_eq = jnp.array([0.1, 0.0])
u_eq = jnp.array([jnp.sin(0.1)])
sys_c = cx.linearize(pendulum, x_eq, u_eq, output=sensor)
sys_d = cx.c2d(sys_c, dt=0.05)
That makes it easy to keep your plant model nonlinear and only linearize where you actually need a controller or estimator.
Related pages: - Systems API - Discretization and linearization
3. Design Controllers Over Batches Of Operating Points¶
Because the workflow is JAX-native, you can vmap over operating points or
design conditions.
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import contrax as cx
def pendulum(t, x, u):
return jnp.array([x[1], -jnp.sin(x[0]) + u[0]])
def sensor(x, u):
return x
def design(x_eq, u_eq):
sys_c = cx.linearize(pendulum, x_eq, u_eq, output=sensor)
sys_d = cx.c2d(sys_c, dt=0.05)
return cx.lqr(sys_d, jnp.eye(2), jnp.ones((1, 1))).K
x_eqs = jnp.array([[0.0, 0.0], [0.1, 0.0], [-0.1, 0.0]])
u_eqs = jnp.zeros((3, 1))
batched_design = jax.jit(jax.vmap(design))
Ks = batched_design(x_eqs, u_eqs)
Use the batched design for gain scheduling, operating-point sweeps, or controller redesign over a grid.
Related pages: - Batch controller design - Linearize, LQR, simulate
4. JIT The Full Closed-Loop Path¶
Controller design and simulation can stay in one compiled pipeline.
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import contrax as cx
A = jnp.array([[1.0, 0.05], [0.0, 1.0]])
B = jnp.array([[0.0], [0.05]])
sys = cx.dss(A, B, jnp.eye(2), jnp.zeros((2, 1)), dt=0.05)
@jax.jit
def run(q_scale):
Q = jnp.diag(jnp.array([10.0, 1.0]) * q_scale)
R = jnp.array([[1.0]])
K = cx.lqr(sys, Q, R).K
return cx.simulate(sys, jnp.array([1.0, 0.0]), lambda t, x: -K @ x, num_steps=60)
ts, xs, ys = run(jnp.array(1.0))
That keeps redesign and simulation in the same JAX program instead of pushing part of the workflow into a separate control toolbox.
5. Estimate Continuous Nonlinear Systems On A Discrete Grid¶
sample_system() integrates a continuous plant over each sample interval and
returns a discrete transition map. Use foh_inputs() to provide
first-order-hold endpoint pairs.
Runnable scripts:
- examples/continuous_nonlinear_estimation.py
- examples/structured_nonlinear_estimation.py
Related pages: - Continuous nonlinear estimation - Structured nonlinear estimation - Estimation API - Simulation API
6. Estimation And Optimization Live In The Same JAX World¶
Batch filters use scans, one-step helpers fit runtime loops, and MHE objective construction provides a differentiable cost. These workflows share model and array conventions with LTI design.
Related pages: - Kalman filtering - Handle missing measurements - Build an MHE objective - Estimation pipelines
7. Cast Quadratic Optimal Execution As LQR¶
Quadratic execution models map naturally to standard control objects. This example represents remaining inventory as the state and signed inventory change as the control.
Treat remaining inventory as the state and signed inventory change as the control:
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import contrax as cx
A = jnp.array([[1.0]])
B = jnp.array([[1.0]])
sys = cx.dss(A, B, jnp.array([[1.0]]), jnp.zeros((1, 1)), dt=1.0)
Q = jnp.array([[2.5]]) # inventory risk
R = jnp.array([[0.4]]) # temporary impact / trading cost
K = cx.lqr(sys, Q, R).K
Then the usual LQR feedback law gives a liquidation schedule. Because the
whole path stays inside JAX, you can also differentiate through the Riccati
solve to tune the execution urgency or vmap the design across many assets.
Runnable script:
- examples/lqr_optimal_execution.py
Related page: - LQR optimal execution