Skip to content

dev-notes: v0.10 roadmap for covariance and information - #262

Draft
devin-griff wants to merge 13 commits into
jkitchin:mainfrom
devin-griff:covariance-information-roadmap
Draft

dev-notes: v0.10 roadmap for covariance and information#262
devin-griff wants to merge 13 commits into
jkitchin:mainfrom
devin-griff:covariance-information-roadmap

Conversation

@devin-griff

@devin-griff devin-griff commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Rendered (formatted) view: https://github.com/devin-griff/pounce/blob/covariance-information-roadmap/dev-notes/covariance-information-roadmap.md

A dev-notes design proposal for the pyomo-pounce post-solve second-order surface, targeting v0.10. Companion to #255 (the active-set sensitivity roadmap): that note extends estimate(), this one extends covariance().

What it proposes

All additive to the v0.9 covariance() surface, nothing changes an existing signature:

  • information(), the un-inverted sibling of covariance(): the reduced Hessian formed directly from the held factor, avoiding the invert-then-reinvert round trip, in the same natural-units convention as the core solve_with_sens.
  • wrt= block selection on covariance() and information(): reduce onto any free-variable block post-solve off one retained factor, so a single solve serves multiple blocks. declare_fitted becomes the default block when wrt= is omitted, preserving v0.9 behavior.
  • retain_kkt(), a factor-retention switch for the declaration-free case.

Motivation

The moving-horizon-estimation information-form arrival cost needs the Lagrangian reduced Hessian marginalized onto the arrival state, plus the parameter covariance from the same solve. The note works through the marginal-versus-conditional semantics and the one-solve-two-blocks flow.

It also acknowledges that pounce already exposes the reduced Hessian in its core (solve_with_sens), convex-QP (QpSensitivity), and curve_fit interfaces; this brings that to the pyomo-pounce interface.

Docs-only (one dev note). Opening for design discussion before any implementation.

🤖 Generated with Claude Code

devin-griff and others added 10 commits July 22, 2026 05:05
Scope the post-solve second-order surface in pyomo-pounce: the new
information() accessor (un-inverted sibling of covariance()), wrt= block
selection on both, a sensitivity enable switch decoupled from
declare_fitted, and time-slice block declaration. Motivated by the MHE
information-form arrival cost. All additive to v0.9's covariance().
Item 4 was an ergonomic detail of the block specifier, not a co-equal
capability, and applies to both declare_fitted and wrt=. Move it into
item 2 as one line; the note is now three items.
…uth table

Name the switch retain_kkt() (the solve factors the KKT regardless; the
switch keeps it). Clarify it is the general factor-retention substrate,
not covariance-specific: gradient()/estimate() get it from their required
declare_sens_param and never call it. wrt= is not gated by retain_kkt();
it works whenever the factor is kept, so declare_fitted alone supports it.
retain_kkt() is only needed for the declaration-free, no-default flow.
Add the setup truth table for clarity.
…he core reduced-Hessian interfaces

pounce already exposes the reduced Hessian in the core (solve_with_sens,
natural units plus eigendecomposition), the convex-QP frontend
(QpSensitivity), and curve_fit; only the pyomo interface stops at
covariance(). Frame information()/wrt= as an interface-and-ergonomics
contribution layered on the core's existing reduced Hessian, not new
numerics, and note in Where-we-are that the missing accessor is a
pyomo-interface gap.
The double-inversion and two-blocks points now live in Benefit hypothesis,
so drop them here to avoid repeating. Keep the arrival-cost definition (the
unique content) and rename the section The MHE arrival cost.
…ram + wrt= works

retain_kkt() is only for the no-declaration case; declare_sens_param alone
supports covariance/information via wrt= off the same solve (no default
block, block conditional on the pinned param).
…aces

State of the art now surveys the field only (sIPOPT, k_aug, scipy
curve_fit) and observes that tools usually expose the covariance, not the
raw reduced Hessian. Pounce's own core/QP/curve_fit reduced-Hessian
exposure moves to a new 'Related reduced-Hessian work in other pounce
interfaces' section after Where-we-are, mirroring the sensitivity note.
Repoint the two cross-references.
…n not the covariance

sIPOPT and k_aug natively form the reduced Hessian (the information matrix); the parameter covariance is a downstream invert-and-scale step. parmest and scipy curve_fit are the covariance-first frontends. Reframes the paragraph around the reduced Hessian as the native engine output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
devin-griff added a commit to devin-griff/pounce that referenced this pull request Jul 25, 2026
The classifier citation pointed at an item that does not exist in PR jkitchin#262
as it stands, so whichever PR is read first, one of them cites nothing.
The rule is now stated in a sentence here with the covariance roadmap as
the reference for cutoffs and core work.

The survey table carried tilde and question-mark entries for WORHP Zen,
KNITRO, SNOPT and acados that I cannot stand behind from primary sources.
The two verifiable rows stay and the rest becomes prose that says the
picture is unconfirmed, which is what the section's argument actually
needs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous version delegated bound handling to covariance()'s
convention in one line. That convention classifies on slack alone and
embeds a pinned parameter as a zero row, which is correct for a variance
and wrong for an information matrix, where the same zeros claim the data
says nothing.

Adds two items at the front. Item 0 is an activity classifier keyed on
the ratio of barrier curvature to objective curvature, which separates
inactive, weakly active and strongly active at any mu where thresholds on
slack and multiplier cannot; it covers inequality rows on the same ratio
(jkitchin#362) and is Rust core work, since the bound multipliers,
mu and the Hessian diagonal do not reach the pyomo session. Item 1 is the
dispositions table, five statuses against what each accessor returns,
with the pinned entry a Schur complement rather than a zero row.

The former items 1 to 3 renumber to 2 to 4. The rest is broadly revised
for clarity, with a new Preconditions section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devin-griff

Copy link
Copy Markdown
Contributor Author

Two new items at the front. The previous version delegated bound handling to covariance()'s convention in a single line. That convention classifies on slack alone, and embeds a pinned parameter as a zero row, which is right for a variance and wrong for an information matrix, where the same zeros claim the data says nothing.

Item 0 is an activity classifier keyed on barrier curvature over objective curvature, written as pseudocode, and is Rust core work rather than pyomo surface. Item 1 is the dispositions table.

Items 1 to 3 are now 2 to 4, largely intact.

The rest has been broadly revised for clarity, with a new Preconditions section and constraint rows for #362.

224 to 354 lines.

A read-only pass over the solver internals found the converged state
already retains everything item 0 needs: the bound multipliers and
slacks, curr_mu, curr_sigma_x, and the exact Lagrangian Hessian. So H is
never recovered by subtracting Sigma from the barrier-augmented factor,
S forms densely from the exact Hessian on the small fitted block at full
precision, and the earlier least-accurate-object caveat is retired. The
unidentified floor is pinned to sqrt machine epsilon relative to the
block's largest curvature, the runtime contamination warning is scoped
to variables classified inactive, and the scope section states both
reasons a weakly active model's numbers move: membership, and the kept
value correcting from 2q to q.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devin-griff

Copy link
Copy Markdown
Contributor Author

Refreshed. A read-only pass over the solver internals ahead of implementing item 0 found the converged state already retains everything the classifier needs — bound multipliers, slacks, curr_mu, curr_sigma_x, and the exact Lagrangian Hessian — so H is never recovered by subtraction and the earlier claim that the pinned block loses precision as μ tightens is retired: S forms densely from the exact Hessian at full precision. The unidentified floor is pinned, and a review pass tightened items 0 and 1 (warning scope, row handling in the guard, notation stated forward).

Implementation of item 0 starts now against this text; the PR will reference back here, and anything implementation teaches lands as one more refresh.

Both accessors return full-block matrices in original coordinates,
reduced on the null space of the binding row normals within the
fitted block and singular by the number of binding rows; each
binding row is warned by name with its conditional-information
scalar. The bound-in-disguise case reduces to the table's
disposition, extending the jkitchin#357/jkitchin#362 agreement from item 0's
classification into item 1's returned matrices. Mechanism, item 2
cross-reference, compatibility note, and a validation bullet added
to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
devin-griff added a commit to devin-griff/pounce that referenced this pull request Jul 27, 2026
Item 0 of dev-notes/covariance-information-roadmap.md (jkitchin#262):
classify every bounded variable and finite-bounded inequality row of
the converged solve on the ratio r = Sigma/q of barrier curvature to
the model's own curvature. r is O(mu) inactive, O(1) weakly active,
O(1/mu) strongly active; edges at sqrt(mu) and 1/sqrt(mu) with a fixed
inner band [1e-1, 1e1], ambiguous in the gaps, and only the two clear
calls at mu > 1e-4. Curvature under sqrt(eps)*max(1, max|H_jj|)
reports unidentified with the sign of the raw value.

Rows classify through the curvature along the constraint normal,
which is the gap behind jkitchin#362: a bound moved onto a linear row
disappears from the bound-multiplier view but not from this one.

New pounce_sensitivity::activity module reading only state already
retained at convergence (sigma, the solver's slacks, the iterate's
bound multipliers, mu, and the exact Lagrangian Hessian);
Solver::classify_activity() enforces bound_relax_factor=0; Python
exposure returns a dict with per-entry off_central_path and
contaminated flags.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@devin-griff
devin-griff marked this pull request as draft July 27, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant