chc.support¶
Stability tier: evolving.
Support / pessimism: keep offline-trained control inside the region the data justifies.
SupportModel scores how far a state-action pair (x, u) sits from the offline data cloud
(squared Mahalanobis distance D); pessimistic_control penalises leaving that support, so the
controller does not exploit the model where it was never trained. This is the offline-safety layer:
the objective is J_task + λ_unc·Σ U + λ_supp·Σ D, where the calibrated
predictive-uncertainty term U comes from chc.uncertainty (deep ensemble / conformal) and
this module supplies D and the controller that combines them through the PenaltyModel.
PenaltyModel
¶
Bases: Protocol
Anything that scores a trajectory penalty penalty_trajectory(xs, us) -> scalar.
Both SupportModel (density distance D) and the chc.uncertainty scorers (calibrated
predictive uncertainty U) satisfy it, so they are interchangeable penalty channels.
SupportModel
¶
Bases: Module
Gaussian support of the offline (x, u) cloud; scores squared Mahalanobis distance.
fit(xs, us, ridge=0.001)
classmethod
¶
The log's mean and the inverse of its covariance, each variance raised by ridge of
itself.
Raising each variance by a share of itself shrinks the correlations and counts the distance
in each coordinate's own standard deviations: a point k of them off the log, along a
coordinate the log moved apart from the others, scores about k**2 / (1 + ridge) in any
units and from any origin, and coordinates the log moved in step keep a finite distance
across their line. A ridge added in the caller's units outweighed a coordinate logged in
small ones: at 1e-3 of its units, a point 4 standard deviations off scored 0.016 where it
scored 16.0.
Raises:
| Type | Description |
|---|---|
ValueError
|
on a coordinate the log never moved, or moved by rounding alone (a spread about its mean of at most 64 eps of its root mean square, eps its precision's), whose distance is unbounded in any units: a finite stand-in would be set by the units it was logged in, or by its rounding. And on a coordinate whose spread the precision cannot hold in the caller's units, where the inverse of its square overflows or underflows the precision's dtype and every point would read as off the support, or on it. |
penalty_trajectory(xs, us)
¶
Total off-support penalty over the visited (x, u) pairs (xs: (H,n), us: (H,m)).
pessimistic_solve(model, x0, us0, dt, cost, support, lam_supp, u_lo, u_hi, steps=10000, lr0=1.0, tol=1e-14, uncertainty=None, lam_unc=0.0, *, constraints=())
¶
pessimistic_control, returning why it stopped as well as where.
The stationarity residual is of the augmented objective, not the task cost: the penalties
are what the descent actually minimised, so a residual measured on the task alone would be
non-zero at the very point the solver was right to stop. With constraints it projects onto
the box and the rows together, as chc.control.projected_gradient_solve does. Under the
confounding radius with no constraints it is ||u - prox(u - grad f)||, f the
augmented cost less the radius' norm and prox the norm's proximal map with the box: zero
exactly where the unsmoothed problem is stationary, where the smoothed norm's gradient reads
zero at an action on the kink whatever the cost's gradient there. The residual is in the
caller's units, so it is zero at a KKT point in any units but its size is not.
pessimistic_control(model, x0, us0, dt, cost, support, lam_supp, u_lo, u_hi, steps=10000, lr0=1.0, tol=1e-14, uncertainty=None, lam_unc=0.0, *, constraints=())
¶
Projected-gradient OC with an offline-safety penalty λ_supp·Σ D + λ_unc·Σ U.
support supplies the density-distance term D; the optional uncertainty scorer gives
the calibrated predictive-uncertainty term U (a chc.uncertainty ensemble/conformal).
Uses autodiff for the augmented-objective gradient (validated equal to the discrete adjoint in
01 §4.1). Returns the optimised controls and the task-cost history (penalties excluded,
so runs at different weights are comparable).
u_lo / u_hi take the same scalar, per-lever or full-schedule forms, and
constraints the same linear rows, that chc.control.projected_gradient_control
accepts.
The descent is the planner's, so a problem is planned the same in any units of each lever and
of the cost and penalties together. It runs in v_kj = sigma_kj u_kj: sigma_kj^2 starts
as the cost's Gauss-Newton curvature along lever j at step k alone, over the augmented
cost at the guess clipped to the box, and after each accepted step it is raised to the secant
of the augmented cost's curvature along that action over the step: a penalty's curvature is
measured where the plan is, not where the guess was.
The confounding radius (chc.uncertainty.ConfoundingRobustPenalty) has a kink at every
u_t = 0, where a plan holds the actions it does not use. Its norm is not differentiated:
the descent minimises it unsmoothed, read in the cost's precision, and takes it by its proximal
map with the box at each step, a forward-backward step whose line search asks a third of the
fall the smooth part's gradient and the norm predict together. A plan holds an unused action at
exactly zero, and from a zero guess the search finds a step wherever the proximal map moves it
and the step lowers the cost by more than tol asks; in float32 the descent no longer stops
at the kink's rounding (docs/adr/0078-the-radius-norm-is-taken-by-its-proximal-map.md).
Under constraints the rows' projection is not the norm's proximal map, and the descent
takes the smoothed norm's gradient as before: a zero guess on the kink may take no step there.
lr0is the first step the line search tries, inv: 1 is the Newton step along each action alone. It is halved until the augmented cost falls by a third of what the gradient predicts for the step; where no halving does, the longest step that lowers it is taken.tolis relative: a step counts where it lowers the augmented cost by more thantoltimes its value at the clipped guess, and from a guess where it is zero any decrease counts.
Both used to be absolute, in the caller's units: lr0 a step of 0.2 action units per unit of
gradient and tol a fall of 1e-9 in the cost's own units, so with a lever in units a
thousand times its own the descent ran out of steps, and in a million times took none
(docs/adr/0073-the-penalised-descent-reads-its-penalty-as-it-goes.md). A fall eps in
the cost's own units is tol = eps / |F|, with F the augmented cost of the guess clipped
to the box.