pub struct PdFullSpaceSolver {
pub min_refinement_steps: Index,
pub max_refinement_steps: Index,
pub residual_ratio_max: Number,
pub residual_ratio_singular: Number,
pub residual_improvement_factor: Number,
pub neg_curv_test_tol: Number,
pub neg_curv_test_reg: bool,
/* private fields */
}Fields§
§min_refinement_steps: Index§max_refinement_steps: Index§residual_ratio_max: Number§residual_ratio_singular: Number§residual_improvement_factor: Number§neg_curv_test_tol: NumberNegative-curvature test tolerance (neg_curv_test_tol_, α_n in
Zavala & Chiang 2014). Zero — upstream’s RegisterOptions
default — keeps the inertia check and disables the heuristic.
Positive turns the inertia check off and instead accepts a
factorization whose inertia is wrong only when the computed
direction passes the curvature test in Self::solve_once
(IpPDFullSpaceSolver.cpp:592-634).
neg_curv_test_reg: boolneg_curv_test_reg_ — include the primal regularization
δ_x‖dx‖² + δ_s‖ds‖² in the curvature test. Upstream’s
RegisterOptions default is yes; no reproduces the original
Ipopt form that ignores it. Only read when
neg_curv_test_tol > 0.
Implementations§
Source§impl PdFullSpaceSolver
impl PdFullSpaceSolver
pub fn new( aug_solver: Box<dyn AugSystemSolver>, perturb: Rc<RefCell<PdPerturbationHandler>>, ) -> Self
pub fn aug_solver(&self) -> &dyn AugSystemSolver
pub fn aug_solver_mut(&mut self) -> &mut dyn AugSystemSolver
Sourcepub fn quality_escalations(&self) -> u64
pub fn quality_escalations(&self) -> u64
Successful quality escalations recorded so far — see
Self::quality_escalations
for what is and is not counted.
Sourcepub fn set_quality_escalation_counter(&mut self, counter: Rc<Cell<u64>>)
pub fn set_quality_escalation_counter(&mut self, counter: Rc<Cell<u64>>)
Share this solver’s escalation tally with counter, so a
restoration sub-solve’s escalations land in the same total as the
main loop’s. Any count already recorded here is folded in, which
makes the call order-independent; in practice it is made at build
time, before the first solve.
Sourcepub fn wrap_aug_solver<F>(&mut self, wrap: F)
pub fn wrap_aug_solver<F>(&mut self, wrap: F)
Replace the underlying AugSystemSolver by passing the
existing one through the supplied wrapper closure. Used by the
restoration phase to decorate the inner StdAugSystemSolver
with AugRestoSystemSolver (which performs the 8-block →
4-block Schur reduction before delegating).
Sourcepub fn negative_curvature_direction(
&mut self,
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
w_at_curr: Option<Rc<dyn SymMatrix>>,
) -> Option<NegativeCurvature>
pub fn negative_curvature_direction( &mut self, data: &IpoptDataHandle, cq: &IpoptCqHandle, nlp: &Rc<RefCell<dyn IpoptNlp>>, w_at_curr: Option<Rc<dyn SymMatrix>>, ) -> Option<NegativeCurvature>
Look for a direction of negative curvature in the null space of the constraint Jacobian at the current iterate (gh #797).
The filter line-search IPM certifies first-order stationarity. On a
nonconvex model that is not the same as a local minimum: a point where
the reduced Hessian on null(A) is negative definite is a constrained
maximum, and every first-order residual at it is zero, so the
convergence check has nothing to object to and the Newton step is
exactly zero — inertia correction included, since δ_x I is symmetric
and cannot break a symmetry the iterates already have. nonconvex_qp.nl
(min x₀x₁ s.t. x₀+x₁ = 2, 0 ≤ x ≤ 4) is the reported case: from the
symmetric bound-pushed start the first Newton step lands on (1,1),
f = 1, the maximum of the concave x₀(2-x₀) along the feasible
segment, and the solve reports Solve_Succeeded there.
This is the second-order information the step computation throws away. It runs only where a stationary point is about to be certified, and it answers two questions with the machinery already in place:
- Is the point second-order suspect? Factor the augmented system
unperturbed with the inertia check on. Correct inertia is exactly
the statement that
W + Σis positive definite onnull(A), so aSuccessatδ_x = 0ends the probe withNoneand costs one factorization. Only aWrongInertiacontinues. - Which way is down? Escalate
δ_xon the ladder below until the inertia is correct. Theδ_xthat first works is within a factor of [NEG_CURV_DELTA_FACTOR] of-λ_minof the reduced Hessian, so(W + Σ + δ_x I)⁻¹restricted tonull(A)has its largest amplification precisely along the eigenvector ofλ_min. A few inverse-iteration back-solves against that factor therefore converge to the most-negative-curvature direction, and each one is a back-solve against the cached factor rather than a refactorization.
The returned direction is never trusted on the strength of that
argument: dᵀ(W + Σ)d is measured for each candidate and the probe
returns None unless the best one is strictly negative. It also
satisfies J_c d_x = 0 and J_d d_x - d_s = 0 to the accuracy of the
factorization (both dual perturbations are held at zero), so stepping
along it does not move the linearised constraints.
Returns None — never an error — for every shape it cannot answer for:
a backend that reports no inertia, a non-dense iterate (the restoration
inner IPM’s CompoundVector), a singular or breaking-down
factorization, or a ladder that reaches [NEG_CURV_DELTA_MAX] without
fixing the inertia. Declining is always safe here: the caller’s
fallback is the pre-#797 behaviour of reporting the stationary point.
The augmented-system cache is invalidated on every exit, because the factor left behind describes a perturbed matrix that no ordinary solve asked for.
Sourcepub fn solve(
&mut self,
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
alpha: Number,
beta: Number,
rhs: &IteratesVector,
res: &mut IteratesVectorMut,
allow_inexact: bool,
improve_solution: bool,
) -> bool
pub fn solve( &mut self, data: &IpoptDataHandle, cq: &IpoptCqHandle, nlp: &Rc<RefCell<dyn IpoptNlp>>, alpha: Number, beta: Number, rhs: &IteratesVector, res: &mut IteratesVectorMut, allow_inexact: bool, improve_solution: bool, ) -> bool
Solve the full PD system. res = α · M⁻¹ · rhs + β · res_in,
matching IpPDFullSpaceSolver::Solve. Returns true on
success. The iterate fields used to assemble the system are
pulled from data (W, curr) and cq (jacobians, slacks,
sigmas).
Sourcepub fn solve_with_sigma(
&mut self,
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
alpha: Number,
beta: Number,
rhs: &IteratesVector,
res: &mut IteratesVectorMut,
allow_inexact: bool,
improve_solution: bool,
sigma_override: SigmaOverride,
) -> bool
pub fn solve_with_sigma( &mut self, data: &IpoptDataHandle, cq: &IpoptCqHandle, nlp: &Rc<RefCell<dyn IpoptNlp>>, alpha: Number, beta: Number, rhs: &IteratesVector, res: &mut IteratesVectorMut, allow_inexact: bool, improve_solution: bool, sigma_override: SigmaOverride, ) -> bool
Self::solve against the same system with the barrier
diagonals sigma_x / sigma_s replaced.
sigma is the barrier term the active bounds contribute to the
x (variable bounds) and s (inequality-row bounds) diagonals,
z / s per bound. Two callers want it substituted, for
different reasons:
- Release. Zeroing an entry takes that bound back out of the
active set, which is what a released bound means, so
factoring the result gives the released system directly. The
released system cannot be recovered from the converged factor:
reaching it by a rank-1 downdate through a Schur complement
asks for the difference of two quantities that agree to about
eps * sigma, and on a tightly converged boundsigmais large enough that the difference is noise – measured, the released answer degrades in proportion to how well the solve converged. Factoring is what buys those digits back, and it is still one factorization against the twenty to a hundred a re-solve would run. - Crossover (gh#654). A crossed-over iterate sits on the
declared bounds, so its live slacks read
bound_relax_factorrather than the barrier’smu/z, andsigmacomes out of the cache describing a looser pin than the point actually has. The sensitivity path substitutes the declared-frame diagonal here.
Only pounce-sensitivity calls this; the algorithm’s own step
computation goes through Self::solve and is unaffected, so no
solver trajectory moves. Both sigmas are among the thirteen
dependency tags, so passing a different vector misses the
factorization cache and re-factors, and the next ordinary solve
misses it back – correctness needs no extra bookkeeping here.
Sourcepub fn solve_many_cached<F1, F2>(
&mut self,
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
n_rhs: usize,
write_rhs: F1,
write_lhs: F2,
) -> Option<bool>
pub fn solve_many_cached<F1, F2>( &mut self, data: &IpoptDataHandle, cq: &IpoptCqHandle, nlp: &Rc<RefCell<dyn IpoptNlp>>, n_rhs: usize, write_rhs: F1, write_lhs: F2, ) -> Option<bool>
Batched back-substitution against the cached KKT factor for
n_rhs right-hand sides, sharing one
pounce_linsol::TSymLinearSolver::multi_solve call with
nrhs > 1. Each column k pulls its RHS through write_rhs(k, &mut iv) and emits its solution through write_lhs(k, &iv) —
closures over the caller’s flat / strided buffer keep the
rhs/sol IteratesVectorMut scratch out of the API surface.
Returns:
Some(true)— fast path executed against the cached factor.Some(false)— fast path was attempted but the linsol reported a back-solve failure.None— fast path not taken. Either the matrix tags differ from the last successfulSelf::solve(cache miss), the matrix has not been considered yet, or the underlyingAugSystemSolverdoes not implementAugSystemSolver::try_resolve_many_flat. The caller should fall back to loopingSelf::solve.
Used by pounce_sensitivity::PdSensBacksolver::solve_many for
the JaxProblem jacrev backward path, where every cotangent
re-solves against the same converged factor (pounce#77 follow-up).
Sourcepub fn solve_many_cached_flat(
&mut self,
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
nlp: &Rc<RefCell<dyn IpoptNlp>>,
n_rhs: usize,
rhs_flat: &[Number],
lhs_flat: &mut [Number],
block_dims: [usize; 8],
) -> Option<bool>
pub fn solve_many_cached_flat( &mut self, data: &IpoptDataHandle, cq: &IpoptCqHandle, nlp: &Rc<RefCell<dyn IpoptNlp>>, n_rhs: usize, rhs_flat: &[Number], lhs_flat: &mut [Number], block_dims: [usize; 8], ) -> Option<bool>
Flat-slice cached-factor multi-RHS path. Same cache-check
semantics as Self::solve_many_cached but operates on
row-major (n_rhs, total) flat buffers without going through
IteratesVectorMut or any dyn Vector / dyn Matrix dispatch
in the per-RHS inner loops — the eight source blocks
(slack_{x,s}_{l,u}, z_{l,u}, v_{l,u}) get downcast to
DenseVector once at the top, the four bound-expansion matrices
(px_l, px_u, pd_l, pd_u) get downcast to
ExpansionMatrix once, and Phase 1 / Phase 3 then run as raw
&[Number] / &mut [Number] arithmetic on the flat buffers.
total is the sum of the eight block_dims entries (in the
same (x, s, y_c, y_d, z_l, z_u, v_l, v_u) order that
IteratesVector uses); rhs_flat.len() == lhs_flat.len() == n_rhs * total.
Returns None (caller should fall back to
Self::solve_many_cached) when:
- the cache check fails (matrix tags differ),
- any block source vector is not a
DenseVectoror is homogeneous (uniform-scalar) on a non-empty block, - any bound-expansion matrix is not an
ExpansionMatrix, - the underlying
AugSystemSolverdoesn’t implementAugSystemSolver::try_resolve_many_flat.
Returns Some(true) on success, Some(false) on linsol back-
solve failure.
Used by pounce_sensitivity::PdSensBacksolver::solve_many as
the fastest tier of the JaxProblem jacrev backward path
(pounce#77 follow-up).
Trait Implementations§
Source§impl PdSystemSolver for PdFullSpaceSolver
impl PdSystemSolver for PdFullSpaceSolver
Source§fn solve_status(&self) -> ESymSolverStatus
fn solve_status(&self) -> ESymSolverStatus
super::pd_full_space_solver::PdFullSpaceSolver::solve will
implement it once IteratesVector is wired through.Auto Trait Implementations§
impl !RefUnwindSafe for PdFullSpaceSolver
impl !Send for PdFullSpaceSolver
impl !Sync for PdFullSpaceSolver
impl !UnwindSafe for PdFullSpaceSolver
impl Freeze for PdFullSpaceSolver
impl Unpin for PdFullSpaceSolver
impl UnsafeUnpin for PdFullSpaceSolver
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<T, U> Imply<T> for U
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more