Adjoint Methods
The adjoint method computes the gradient of an objective function with respect to parameters in backward integrations, regardless of the number of parameters. This makes it dramatically more efficient than forward sensitivity when there are many parameters (as in machine learning or large-scale parameter estimation).
The Problem
Section titled “The Problem”Given a parameterized ODE:
and an objective:
compute the gradient .
Forward vs Adjoint
Section titled “Forward vs Adjoint”Forward sensitivity propagates derivatives forward alongside the state:
This requires solving additional equations — expensive when is large.
Adjoint method introduces the costate and integrates backward:
with terminal condition .
The gradient is then:
This requires only one backward integration regardless of .
use numra::ocp::adjoint_gradient;
// Model: dx/dt = -p[0]*x + p[1]*sin(t)let gradient_result = adjoint_gradient( // model: f(t, x, dxdt, params) |t, x, dxdt, p| { dxdt[0] = -p[0] * x[0] + p[1] * t.sin(); }, 1, // n_states 2, // n_params 0.0, // t0 10.0, // tf &[1.0], // x0 &[0.5, 1.0], // params // terminal cost: phi(x(tf)) |x_tf| x_tf[0] * x_tf[0], // running cost (optional): L(t, x, p) Some(|_t: f64, _x: &[f64], _p: &[f64]| 0.0),).unwrap();
println!("Objective: {:.6}", gradient_result.objective);println!("dJ/dp = {:?}", gradient_result.gradient);Cost Comparison
Section titled “Cost Comparison”| Method | Cost | Best when |
|---|---|---|
| Forward sensitivity | ||
| Adjoint | backward | |
| Finite differences | forward solves | Quick & dirty |
For a 3-state system with 100 parameters:
- Forward: 300 additional ODEs
- Adjoint: 3 backward ODEs + gradient quadrature
- Finite differences: 100 full ODE solves
AdjointResult
Section titled “AdjointResult”| Field | Description |
|---|---|
gradient | vector |
objective | Scalar objective |
costate | Costate trajectory |
costate_time | Time points for costate |
Practical Notes
Section titled “Practical Notes”- Jacobians and are computed via finite differences internally.
- The forward state trajectory must be stored or recomputed during the backward pass. Numra stores the trajectory from the forward integration.
- For problems with discontinuous dynamics, the adjoint equations need jump conditions at discontinuity points.