Diagnostics

SolverDiagnostics captures structured data about solver behavior. It is available at result.diagnostics; the compact summary below is printed to stderr when print_level >= 5. Structured JSON reports include the full diagnostics object, including preprocessing details.

Diagnostic block format

--- ripopt diagnostics ---
status: Optimal
iterations: 8
wall_time: 0.001s
final_mu: 4.14e-9
final_primal_inf: 5.55e-17
final_dual_inf: 1.04e-12
final_compl: 7.75e-9
restoration_count: 0
nlp_restoration_count: 0
mu_mode_switches: 2
filter_rejects: 0
watchdog_activations: 0
soc_corrections: 0
--- end diagnostics ---

Fields

FieldMeaning
statusFinal solve status
iterationsTotal IPM iterations
wall_time_secsTotal wall-clock time
final_muBarrier parameter at termination
final_primal_infConstraint violation at termination
final_dual_infDual infeasibility (stationarity error) at termination
final_complComplementarity error at termination
restoration_countGauss-Newton restoration entries
nlp_restoration_countFull NLP restoration entries (heavier)
mu_mode_switchesBarrier mode transitions (Free ↔ Fixed)
filter_rejectsLine search failures (backtracking exhausted)
watchdog_activationsWatchdog triggered by consecutive short steps
soc_correctionsSecond-order corrections accepted
fallback_usedWhich fallback succeeded, if any (lbfgs_hessian, augmented_lagrangian, sqp, slack)
preprocessingNested diagnostics for auxiliary presolve, auxiliary postsolve, and standard preprocessing

Preprocessing Diagnostics

diagnostics.preprocessing is always present in SolveResult and in CLI JSON reports written with ripopt problem.nl -o report.json. It has three nested objects:

ObjectMeaning
preprocessing.presolveAuxiliary equality-block reduction attempted before the main solve
preprocessing.postsolveAuxiliary equality-block reduction followed by recovery after a reduced solve
preprocessing.standardFixed-variable and redundant-constraint preprocessing

The auxiliary objects, presolve and postsolve, report both timing and structural data:

FieldMeaning
attempted, solved, failedWhether the phase ran, solved the problem, or rejected/fell back
skipped, skip_reasonWhether the phase returned to the normal solve path before auxiliary solves, and why; examples include no accepted candidates, no-op gates, and cost gates
total_time_secsTotal wall-clock time spent in that preprocessing phase
candidate_detection_time_secsEnd-to-end candidate search time
incidence_time_secsTime spent building equality incidence data
structural_analysis_time_secsTime spent on components, matching, Dulmage-Mendelsohn, and block-triangular analysis
candidate_filter_time_secsTime spent rejecting objective- or inequality-coupled variables
auxiliary_solve_time_secsPresolve time spent solving accepted auxiliary blocks
recovery_solve_time_secsPostsolve time spent recovering eliminated variables
reduction_build_time_secsTime spent wrapping the reduced problem
nested_preprocessing_time_secsTime spent running standard preprocessing on the auxiliary-reduced problem
reduced_solve_time_secsTime spent solving the reduced problem
unmap_time_secsTime spent mapping reduced solutions back to the original space
full_space_validation_time_secsTime spent recomputing objective, constraints, and residuals on the original problem
equality_rows, incident_variables, connected_componentsSize of the equality-incidence structure examined
candidates, btd_blocks, accepted_block_sizesAccepted auxiliary candidate and block structure
rejected_blocks, rejection_countsRejection totals grouped by reason
auxiliary_blocks_solved, auxiliary_iterations, auxiliary_*_evalsWork performed by internal auxiliary solves
original_*, reduced_*, removed_*, nested_*Original, reduced, removed, and nested standard-preprocessing dimensions

The standard object reports attempted, did_reduce, total_time_secs, construction_time_secs, reduced_solve_time_secs, unmap_time_secs, original/reduced dimensions, fixed_variables, and redundant_constraints. Use these fields to separate "preprocessing found no useful structure" from "preprocessing reduced the model but the overhead exceeded the iteration savings."

Interpreting the diagnostics

Healthy solve (HS071-like): 0 restorations, 0 filter rejects, 2–4 mu mode switches, final_mu near 1e-9, final_primal_inf and final_dual_inf both below tol.

Struggling solve: Many filter rejects, multiple restorations, final_mu stuck above 1e-4, or a fallback was used.

Pattern → cause → fix

PatternLikely causeOptions to try
filter_rejects > 5Line search fighting constraintsIncrease mu_init, reduce kappa
restoration_count > 3Repeated feasibility recoverySet enable_slack_fallback: true, increase mu_init
mu_mode_switches > 10Free/Fixed cyclingSet mu_strategy_adaptive: false
final_mu stuck > 1e-4Barrier parameter not decreasingIncrease max_iter, reduce mu_linear_decrease_factor
fallback_used: Some(...)Primary IPM failedCheck which fallback; consider changing Hessian strategy
soc_corrections > 0Nonlinear constraints causing step rejectionNormal; increase max_soc if filter rejects are also high
watchdog_activations > 0Tiny steps detectedTry hessian_approximation_lbfgs: true

Example: healthy solve (HS071)

#![allow(unused)]
fn main() {
let result = ripopt::solve(&Hs071, &SolverOptions::default());
let d = &result.diagnostics;
assert_eq!(d.filter_rejects, 0);
assert_eq!(d.restoration_count, 0);
assert!(d.final_mu < 1e-8);
// iterations ≈ 8
}

Example: struggling solve — reading and reacting

#![allow(unused)]
fn main() {
let r1 = ripopt::solve(&problem, &opts);

if r1.status != SolveStatus::Optimal {
    let d = &r1.diagnostics;
    let mut opts2 = opts.clone();

    if d.filter_rejects > 5 {
        opts2.mu_init = 1.0;
        opts2.kappa = 3.0;
    }
    if d.restoration_count > 3 {
        opts2.enable_slack_fallback = true;
    }
    if d.final_mu > 1e-4 {
        opts2.mu_strategy_adaptive = false;
    }
    if d.mu_mode_switches > 10 {
        opts2.hessian_approximation_lbfgs = true;
    }

    let r2 = ripopt::solve(&problem, &opts2);
}
}

Using diagnostics with Claude Code

Claude Code can read the --- ripopt diagnostics --- block from stderr and automatically adjust solver options:

claude -p "
  Run: cargo run --example debug_tp374 2>&1
  Parse the diagnostics block.
  If not Optimal:
    - High filter_rejects → increase mu_init, decrease kappa
    - High restoration_count → try enable_slack_fallback
    - mu stuck high → try mu_strategy_adaptive: false
    - Large multipliers → try hessian_approximation_lbfgs: true
  Adjust options, re-run, compare. Up to 3 attempts.
"

The Rust code is a pure reporter. All intelligence — pattern matching, strategy selection — lives in Claude's reasoning.