chc.adjoint¶
Stability tier: stable.
Control gradient dJ/du: a hand-written discrete adjoint + an adaptive diffrax continuous adjoint.
The discrete adjoint assembles the backward recursion explicitly from autodiff Jacobians -- the
object the gradient-check gate verifies against autodiff and finite differences.
control_gradient_diffrax is the modern sibling: each interval is integrated by an adaptive
(stiff-capable) solver, with the continuous BacksolveAdjoint for O(1)-memory gradients.
control_gradient_adjoint(dyn, x0, us, dt, cost)
¶
Return dJ/du of shape (H, m) via the discrete adjoint.
λ_H = ∇Φ(x_H); λ_k = ∇_x L_k + (∂F/∂x_k)ᵀ λ_{k+1};
∇_{u_k} J = ∇_u L_k + (∂F/∂u_k)ᵀ λ_{k+1}.
costate_norms(dyn, x0, us, dt, cost)
¶
||lambda_{k+1}|| for k = 0 .. H-1 -- the local Lipschitz constant of the cost-to-go.
The same backward recursion control_gradient_adjoint runs, returning the costates
themselves rather than the gradient they contract with. lambda_{k+1} is
dJ/dx_{k+1} along this trajectory, so ||lambda_{k+1}|| is exactly the first-order
sensitivity of the objective to a perturbation of the state at step k+1 -- which is what
turns a bound on a per-step transition error into a bound on the objective
(chc.uncertainty.ConfoundingRobustPenalty, Result 38 (b)).
Local by construction, and that is the honest reading: these are derivatives at one trajectory,
so the bound they license is a first-order one about a neighbourhood of it, not a global
Lipschitz constant of J.
perturbation_cost_weights(dyn, x0, us, dt, cost, radius)
¶
Weights w_k with sum_k w_k * radius * ||u_k|| >= |J(perturbed) - J(reference)|.
The object Result 38 (b) asks for. A support model licenses a bound on the field:
||f_true(x, u) - f_hat(x, u)|| <= radius * ||u||. This turns that into a bound on the
objective, at the actions it is handed, in three pieces:
- injection. One RK4 step maps a field offset
ginto a state offsetdt (I + dtJ/2 + (dtJ)^2/6 + (dtJ)^3/24) gwithJ = df/dx-- exact for an offset constant over the step, which an unmodelledDelta_B uat fixeduis. The spectral norm of that polynomial falls either side of 1 depending on the plant -- 0.99990 on the certificate's oscillator, so a baredthappens to be conservative there and need not be elsewhere -- which is reason enough to compute it rather than assume it. - first order.
||lambda_{k+1}||contracted with that injection (costate_norms). - second order. The first-order term is asymptotically tight, so it is not an upper
bound: the curvature term it drops is positive and O(radius^2). A deviation tube
rho_{k+1} = ||dF/dx|| rho_k + eps_kcarries the injections forward, and1/2 sum ||d^2 L/dx^2|| rho^2closes the Taylor expansion. Exact for a linear plant with a quadratic cost, whereJis exactly quadratic along the perturbation;O(radius^3)otherwise.
radius = 0 is meaningful and is used: it zeroes the tube, so the weights collapse to
exactly the first-order term, which is how the certificate reports both.
The curvature term does not factor per step, so it is spread across the same
radius * ||u_k|| denominator the first-order term uses -- the sum is the bound, and only
the sum is claimed. Weights are computed at one reference trajectory, so the guarantee is
local to it; chc.uncertainty.confounding_cost_bound_certificate measures where it holds.
total_cost_diffrax(dyn, x0, us, dt, cost, rtol=1e-06, atol=1e-09, adjoint=None)
¶
Discrete control cost J with each interval integrated by an adaptive diffrax solver.
Same objective and zero-order-hold control as chc.cost.total_cost, but each step
x_k -> x_{k+1} is an adaptive (stiff-capable) Tsit5 solve of dx/dt = dyn(t, x, u_k), not
the fixed-step RK4 of chc.integrate.rollout -- accuracy set by (rtol, atol), and the
continuous BacksolveAdjoint available (via control_gradient_diffrax).
control_gradient_diffrax(dyn, x0, us, dt, cost, rtol=1e-06, atol=1e-09, adjoint=None)
¶
dJ/du through the adaptive diffrax solve -- the continuous-adjoint sibling of the
discrete control_gradient_adjoint. Pass adjoint=diffrax.BacksolveAdjoint() for the
memory-light optimise-then-discretise continuous adjoint; the default backprops the solver.