1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Sensitivity analysis for POUNCE — port of upstream Ipopt's `contrib/sIPOPT/`.
//!
//! # Status
//!
//! Phases A–C complete. Wired today:
//!
//! * [`schur_data::IndexSchurData`] + [`p_calculator::IndexPCalculator`]:
//! row-selector representation of the perturbation matrix `B`.
//! * [`backsolver::DenseLuBacksolver`] + [`PdSensBacksolver`]: backsolves
//! against the converged KKT factor (test / live IPM, respectively).
//! * [`schur_driver::DenseGenSchurDriver`]: dense Schur-complement
//! factor `S = -B K⁻¹ Bᵀ` with parallel right-hand-side solves.
//! * [`step_calc::StdStepCalc`] + [`sens_app::SensApplication`]:
//! high-level `parametric_step(Δp, dx)` and
//! [`reduced_hessian::compute_reduced_hessian`] entry points.
//! * [`SensSolve`] / [`SensResult`]: one-call builder (covers the
//! `on_converged` plumbing typically required to wire the above into
//! an `IpoptApplication`).
//!
//! Verified against upstream sIPOPT 3.14.19's `parametric_cpp` golden
//! output to 1e-8 (see `tests/parametric_cpp.rs`); the standalone
//! `pounce_sens` AMPL driver in `pounce-cli` matches `sensitivity_amplsolver`'s
//! `_sens_sol` output on representative .nl problems.
//!
//! **Phase D progress** (per [pounce#7](https://github.com/jkitchin/pounce/issues/7)):
//!
//! * **Fixed-variable lifting** ✔ — `pounce_sens` handles `n_x != n_full`
//! via the `IpoptNlp::full_x_to_var_x` / `var_x_to_full_x` /
//! `full_g_to_c_block` / `full_g_to_d_block` trait methods (which
//! delegate to `BoundClassification.x_not_fixed_map` / `full_to_c` /
//! `full_to_d`).
//! * **Reduced-Hessian eigendecomposition** ✔ — pure-Rust cyclic Jacobi
//! in [`pounce_linalg::symmetric_eigen`] (shared with the convex QP
//! sensitivity path); surfaced via
//! [`SensApplication::compute_reduced_hessian_eigen`],
//! [`SensSolve::with_reduced_hessian_eigen`], the `pounce_sens
//! --rh-eigendecomp` flag, and the Python `solve_with_sens(rh_eigendecomp=True)`
//! kwarg. Over **parameter-pin rows** the decomposed matrix is `−H_R`,
//! so its ascending spectrum runs stiffest-first (gh#937); see
//! [`Solver::compute_reduced_hessian`].
//! * **`sens_boundcheck` bound refinement** ✔ —
//! [`boundcheck::refine_step_onto_bounds`] repairs the active set the
//! step implies, both halves of upstream's fix-relax: a coordinate
//! the step carries past a bound is pinned AT that bound, and a bound
//! multiplier the step drives negative is set to zero so the variable
//! can leave. Either way the system is re-solved, so the other
//! coordinates move with it. Surfaced via
//! [`SensSolve::with_boundcheck`], `pounce_sens --sens-boundcheck`,
//! the Python `solve_with_sens(sens_boundcheck=True)` kwarg, and
//! `estimate(mode="fix_relax")` in pyomo-pounce, all four running the
//! same refinement.
//!
//! # Algorithmic reference
//!
//! Pirnay, H., López-Negrete, R., and Biegler, L.T. (2012).
//! *Optimal sensitivity based on IPOPT.*
//! Mathematical Programming Computation, **4**(4), 307–331.
//! [DOI: 10.1007/s12532-012-0043-2](https://doi.org/10.1007/s12532-012-0043-2).
//!
//! Verified 2026-05-14 via Crossref: title, authors (Hans Pirnay; Rodrigo
//! López-Negrete; Lorenz T. Biegler), MPC volume 4 issue 4 pp 307–331.
//!
//! # Upstream source mirror
//!
//! Port targets `ref/Ipopt/contrib/sIPOPT/src/` in this repo
//! (EPL-2.0, © Hans Pirnay 2009–2011 per the file headers). Each
//! public item in this crate documents the upstream symbol it mirrors
//! with file path and (where stable) line numbers.
/// The engine-agnostic core, for anything not surfaced below.
///
/// The half of this crate that does not know which solver produced the KKT
/// system — the `SensBacksolver` contract, `boundcheck`'s fix-relax / path /
/// directional machinery, and the Schur-complement stack — lives in
/// `pounce-sens-core` so the convex arm can reach it without pulling in the
/// NLP engine.
pub use pounce_sens_core;
// Re-exporting the *modules*, not merely their items, is what keeps this
// crate's published API unchanged across that move: a `pub use` of a module
// creates a valid path at that name, so
// `pounce_sensitivity::boundcheck::refine_step_onto_bounds` still resolves for
// `pounce-cli` and for this crate's own tests, and the internal
// `crate::backsolver::SensBacksolver` spellings in `solver.rs`, `activity.rs`
// and `corrector.rs` needed no edit at all.
pub use PathOperator;
pub use ;
pub use PdSensBacksolver;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use compute_reduced_hessian;
pub use ;
pub use ;
pub use ;
pub use ;
// Hoisted to pounce-linalg so the convex QP sensitivity path can share it;
// re-exported here to preserve `pounce_sensitivity::symmetric_eigen`.
pub use symmetric_eigen;
pub use ;
/// Run a sensitivity-producing solve in the original TNLP coordinate system.
///
/// Presolve can reduce or reorder the KKT system, while sIPOPT pin indices and
/// reduced-Hessian coordinates are defined against the submitted TNLP. Keep
/// the public `presolve` option intact for callers, but bypass its generic
/// wrapper for this solve.
pub