Skip to main content

SolveStatistics

Struct SolveStatistics 

Source
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: f64

Primal 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: f64

How 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: f64

final_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: f64

Final 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: i32

Number 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: i32

Hessian nonzeros in the pattern that was coloured (lower triangle).

§fd_hessian_n: i32

Columns 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: i32

Probe groups per Hessian — the count of extra gradient/Jacobian evaluations each rebuild costs.

§fd_hessian_rho_max: i32

Widest row of the pattern; the quantity that decides whether the colouring can stay narrow under mesh refinement.

§fd_hessian_coloring_fell_back: bool

Whether a requested star colouring failed validation and CPR was substituted.

§fd_hessian_objective_clique_widened: bool

Whether 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: i32

Cumulative 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: i32

Number 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: f64

Cumulative 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: i32

Successful 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: bool

gh#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: bool

gh#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: i32

Number of QP subproblems solved during this solve.

§sqp_qp_working_set_changes: i32

Active-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§

Trait Implementations§

Source§

impl Clone for SolveStatistics

Source§

fn clone(&self) -> SolveStatistics

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SolveStatistics

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

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 <= tol test now fails closed for an uncomputed value. That is the intent.
  • serde_json renders non-finite floats as null, so these fields appear as null rather than 0.0 in a solve report for an aborted solve. See docs/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

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for SolveStatistics

Source§

fn deserialize<__D>( __deserializer: __D, ) -> Result<SolveStatistics, <__D as Deserializer<'de>>::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for SolveStatistics

Source§

fn serialize<__S>( &self, __serializer: __S, ) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> ByRef<T> for T

Source§

fn by_ref(&self) -> &T

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Imply<T> for U
where T: ?Sized, U: ?Sized,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more