pub struct SolveStatistics {Show 40 fields
pub iteration_count: i32,
pub total_cpu_time_secs: f64,
pub total_sys_time_secs: f64,
pub total_wallclock_time_secs: f64,
pub num_obj_evals: i32,
pub num_constr_evals: i32,
pub num_obj_grad_evals: i32,
pub num_constr_jac_evals: i32,
pub num_hess_evals: i32,
pub final_objective: f64,
pub final_scaled_objective: f64,
pub final_dual_inf: f64,
pub final_constr_viol: f64,
pub final_compl: f64,
pub final_kkt_error: f64,
pub final_declared_constr_viol: f64,
pub final_declared_box_viol: f64,
pub final_unscaled_dual_inf: f64,
pub final_unscaled_constr_viol: f64,
pub final_unscaled_compl: f64,
pub final_unscaled_kkt_error: f64,
pub final_kkt_error_above_noise: f64,
pub final_mu: f64,
pub fd_hessian_pattern_used: i32,
pub fd_hessian_nnz: i32,
pub fd_hessian_n: i32,
pub fd_hessian_groups: i32,
pub fd_hessian_rho_max: i32,
pub fd_hessian_coloring_fell_back: bool,
pub fd_hessian_objective_clique_widened: bool,
pub restoration_calls: i32,
pub restoration_inner_iters: i32,
pub restoration_outer_iters: i32,
pub restoration_wall_secs: f64,
pub quality_escalations: i32,
pub dual_divergence_signature: bool,
pub dual_divergence_retry_promoted: bool,
pub sqp_qp_solves: i32,
pub sqp_qp_working_set_changes: i32,
pub iterations: Vec<IterRecord>,
}Fields§
§iteration_count: i32§total_cpu_time_secs: f64§total_sys_time_secs: f64§total_wallclock_time_secs: f64§num_obj_evals: i32§num_constr_evals: i32§num_obj_grad_evals: i32§num_constr_jac_evals: i32§num_hess_evals: i32§final_objective: f64§final_scaled_objective: f64§final_dual_inf: f64§final_constr_viol: f64§final_compl: f64§final_kkt_error: f64§final_declared_constr_viol: f64Primal violation measured against the model as declared, before
the bound_relax_factor widening the convex arm applies
(qp_extract::BoundRelax, gh #744/#745).
final_constr_viol measures the model the solver was HANDED, whose
inequality rows and variable box are widened by
min(factor, cap)·|b|. That is the right model for the convergence
test, and it is what every acceptance gate reads — but it is not how
far the returned point sits outside the model the caller wrote. On
netlib afiro the point is 4.99e-06 outside a declared row
b = 500 (exactly 1e-8·500) while final_constr_viol reads
8.68e-13; on 25fv47 it is 1.97e-05 against 2.19e-11.
Reported so a caller can tell the two apart rather than reading the
widened number as its own model’s feasibility. NaN when the solve
applied no widening (the two coincide) or on paths that do not
compute it.
final_declared_box_viol: f64How far the returned point sits outside the declared variable box
— the box the caller wrote, before the bound_relax_factor widening.
This is Ipopt’s Variable bound violation, and it is the box half of
Self::final_declared_constr_viol reported on its own, because once
the two are maxed together a box violation cannot be told from a row
violation.
Variable bounds carry no scaling — POUNCE scales the objective and the constraint rows only — so there is no scaled/unscaled pair here; the one number is right in both columns.
NaN on a path that does not compute it.
final_unscaled_dual_inf: f64§final_unscaled_constr_viol: f64§final_unscaled_compl: f64§final_unscaled_kkt_error: f64§final_kkt_error_above_noise: f64final_kkt_error with each constraint row’s residual counted only
where it rises above what that row can represent in floating point —
the aggregate the strict convergence gate actually tests (gh #528).
Equal to final_kkt_error on every problem whose data is O(1), and
smaller only where a row is at its own resolution limit. Reported so a
summary that ends EXIT: Optimal Solution Found beside an error above
tol accounts for the gap rather than merely presenting it.
final_mu: f64Final barrier parameter μ at termination (the IPM’s curr_mu
after the last iterate). Lets a caller thread the converged
barrier into a warm-started re-solve’s mu_init /
warm_start_target_mu for predictor–corrector path following
(pounce#86). 0.0 on the barrier-free SQP path, where μ has
no meaning.
fd_hessian_pattern_used: i32Number of times IpoptAlgorithm::invoke_restoration was
entered during this solve.
Finite-difference Hessian census, when
hessian_approximation=finite-difference actually built a pattern.
All zero / -1 on every other Hessian mode, which is how a caller
tells “the mode did not run” from “it ran with an empty pattern”.
fd_hessian_pattern_used is the source the run ended up with,
not the one requested: 0 declared, 1 jacobian, -1 not run.
declared silently falls back to jacobian when the TNLP declares
no Hessian structure, and that fallback is the difference between
17 probe groups and 341 on benchmarks/large_scale laptime, so
reporting the request would hide the number a reader is here for.
fd_hessian_nnz: i32Hessian nonzeros in the pattern that was coloured (lower triangle).
fd_hessian_n: i32Columns the colouring ran over, i.e. the problem’s variable count.
Present so the report is self-contained: groups / n is the
compression, the fraction of a dense finite-difference scheme’s
probes this pattern costs, and without n a reader cannot form it.
fd_hessian_groups: i32Probe groups per Hessian — the count of extra gradient/Jacobian evaluations each rebuild costs.
fd_hessian_rho_max: i32Widest row of the pattern; the quantity that decides whether the colouring can stay narrow under mesh refinement.
fd_hessian_coloring_fell_back: boolWhether a requested star colouring failed validation and CPR was substituted.
fd_hessian_objective_clique_widened: boolWhether the objective clique fell back to a conservative structural
set because the model stated no objective linearity. This is the
field that explains a surprising fd_hessian_groups: the clique is
then N, or all n, and the probe count reflects that rather than
the objective’s true support.
restoration_calls: i32§restoration_inner_iters: i32Cumulative inner-IPM iterations across every restoration call —
the number of r-suffix rows a print_level=5 log would show.
Each call contributes its sub-solve’s own length: the inner
counter is seeded from the outer’s at entry (upstream
IpRestoMinC_1Nrm.cpp:181), so the length is the terminating value
minus the outer count at entry. Before gh #819 this summed the
terminating values themselves — absolute positions in a shared
numbering, not lengths — and recorded 0 for any call that failed,
which is the case a reader is looking at this field to understand.
restoration_outer_iters: i32Number of outer iterations consumed by restoration: one per call,
so this always equals restoration_calls.
It is not the count of r-suffix rows — that is
restoration_inner_iters, and reading this field as those rows is
what the doc comment here said until gh #819. Restoration in POUNCE
is a nested solve entered from a single outer iteration, not a mode
the outer loop runs in, so there is no third number here to report.
restoration_wall_secs: f64Cumulative wall-clock seconds spent inside perform_restoration
across all restoration calls. Useful for “what fraction of the
solve was restoration?” without running with high print_level.
quality_escalations: i32Successful linear-solver quality escalations over the whole solve
— the main loop’s and every restoration sub-solve’s — i.e. the
count of q flags in the info-string column (gh#857).
An escalation is not an error and not, on its own, a problem: it is how the IPM answers a factorization that will not deliver. But with the FERAL backend it reroutes the rest of the solve, because that backend’s ladder changes which pivots are taken and never steps back down, so a run that ends badly having escalated is a different animal from one that ends badly without. Before this counter the two were indistinguishable in a report, which is why gh#857’s regression had to be found by instrumenting a build.
0 on the SQP and convex paths, which never escalate, and on any
run whose backend declines to (increase_quality returning
false is not counted — this counts escalations that happened,
not escalations that were asked for).
On a laddered run this is the promoted solve’s count, not the
base solve’s — the same rule iteration_count follows, and the
same trap. It is a sharp edge here because feral_increase_quality_retry
promotes a re-solve that by construction escalated zero times, so
a run whose base solve escalated twenty-five times reports 0
once the recovery lands. That is not a lost number: the ladder
block records the base verdict alongside it, and
feral_increase_quality_retry=no reproduces the base solve
outright. The rung’s own gate reads the base statistics inside the
driver, before any promotion, so the gate is unaffected.
dual_divergence_signature: boolgh#884. The solve observed the biactive dual-divergence signature:
at one and the same iterate, a converged primal
(inf_pr <= dual_divergence_retry_primal_tol), a scale-relative
step at or below dual_divergence_retry_step_tol, and an
unscaled dual infeasibility at or above
dual_divergence_retry_du_floor.
Reported whether or not a retry ran or promoted, so a caller can tell “the multipliers ran away on a settled iterate” from an exit that merely ran out of iterations.
Unlike quality_escalations and iteration_count, which on a
promoted run describe the promoted attempt alone, this accumulates
across every attempt of one solve. That is deliberate: the reason
a second solve happened at all is a fact about the solve, and a
promoted run that reported false here would say the retry’s
answer came from nowhere.
dual_divergence_retry_promoted: boolgh#884. A dual-divergence retry ran and replaced the base
attempt’s answer. false both when no retry ran and when one ran
and lost — in the latter case the returned point, status and
residuals are the base attempt’s.
sqp_qp_solves: i32Number of QP subproblems solved during this solve.
sqp_qp_working_set_changes: i32Active-set changes (adds + drops) summed over those QP subproblems. This is the measurement a working-set warm start is judged on: the outer iteration count can be identical between a cold and a warm solve while this differs by an order of magnitude, and on a QP-shaped NLP (one outer iteration by construction) it is the only thing that moves at all.
iterations: Vec<IterRecord>Per-iteration trajectory. Empty when the consumer doesn’t ask
for it (iter_history_enabled = false on the application or
the binary’s --json-detail summary mode). Populated in order
by [IpoptAlgorithm::iterate] when enabled.
Implementations§
Source§impl SolveStatistics
impl SolveStatistics
pub fn new() -> SolveStatistics
Trait Implementations§
Source§impl Clone for SolveStatistics
impl Clone for SolveStatistics
Source§fn clone(&self) -> SolveStatistics
fn clone(&self) -> SolveStatistics
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for SolveStatistics
impl Debug for SolveStatistics
Source§impl Default for SolveStatistics
The eight residual fields default to NaN, not zero.
impl Default for SolveStatistics
The eight residual fields default to NaN, not zero.
They are populated by the convergence check at the end of a solve. A solve
that never gets that far – rejected during setup (Not_Enough_Degrees_Of_Freedom,
Invalid_Problem_Definition), aborted, or caught by the batch panic
handler – leaves them untouched, and a default of 0.0 there reads as
“converged perfectly” rather than “never computed”.
That is not hypothetical. pounce.minimize upgrades a non-success status
to success=True when the final KKT error is within the acceptable
tolerance, which is right for a solve that stalled near a good point. With
a zero default it also fired for problems the solver had refused: an
over-determined NLP returned Not_Enough_Degrees_Of_Freedom together with
success=True and an x outside its own variable bounds. NaN makes the
existing is_finite guard on that path do what its comment already claims.
Consequences worth knowing:
- NaN compares false against everything, so any
residual <= toltest now fails closed for an uncomputed value. That is the intent. serde_jsonrenders non-finite floats asnull, so these fields appear asnullrather than0.0in a solve report for an aborted solve. Seedocs/src/schema/solve-report-v1.md.
The two objective fields are in the set for the same reason, though the
stakes are lower: nothing decides anything from them, they are only
reported (console summary, studio markdown, the JSON report). But 0.0 is
a perfectly ordinary objective value, so a reader cannot tell a solve that
legitimately reached zero from one that never evaluated anything. One rule
– uncomputed is NaN – is easier to reason about than “residuals are NaN,
objectives are zero, and you have to remember which is which”. Note they
are seeded best-effort from the current iterate whenever one exists, so
they are only NaN when the solve died before producing any point at all.
final_mu is deliberately not in this set: 0.0 is its documented value
on the barrier-free SQP path, where mu has no meaning.
Source§fn default() -> SolveStatistics
fn default() -> SolveStatistics
Source§impl<'de> Deserialize<'de> for SolveStatistics
impl<'de> Deserialize<'de> for SolveStatistics
Source§fn deserialize<__D>(
__deserializer: __D,
) -> Result<SolveStatistics, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(
__deserializer: __D,
) -> Result<SolveStatistics, <__D as Deserializer<'de>>::Error>where
__D: Deserializer<'de>,
Source§impl Serialize for SolveStatistics
impl Serialize for SolveStatistics
Source§fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
fn serialize<__S>(
&self,
__serializer: __S,
) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where
__S: Serializer,
Auto Trait Implementations§
impl Freeze for SolveStatistics
impl RefUnwindSafe for SolveStatistics
impl Send for SolveStatistics
impl Sync for SolveStatistics
impl Unpin for SolveStatistics
impl UnsafeUnpin for SolveStatistics
impl UnwindSafe for SolveStatistics
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
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
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