Skip to main content

Solver

Struct Solver 

Source
pub struct Solver { /* private fields */ }
Expand description

Session-style solver: holds an IpoptApplication, its TNLP, and the converged factor between calls.

Implementations§

Source§

impl Solver

Source

pub fn new(app: IpoptApplication, tnlp: Rc<RefCell<dyn TNLP>>) -> Self

Build a new session. The app should already have its options configured and initialize() called.

Examples found in repository?
examples/sensitivity_session.rs (line 166)
160fn main() {
161    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
162        eta1: 5.0,
163        eta2: 1.0,
164    }));
165
166    let mut solver = Solver::new(make_app(), tnlp);
167    let status = solver.solve();
168    println!("solve status: {status:?}");
169    assert!(solver.converged().is_some(), "solver did not converge");
170
171    let pins = vec![2 as Index, 3];
172
173    // Two cheap parametric steps against the same factor.
174    for deltas in &[vec![-0.5, 0.0], vec![0.0, 0.2]] {
175        let dx = solver
176            .parametric_step(&pins, deltas)
177            .expect("parametric_step ok");
178        println!("parametric_step(deltas={deltas:?}) -> dx = {dx:?}");
179    }
180
181    // Reduced Hessian over the same pinned-row set.
182    let hr = solver
183        .compute_reduced_hessian(&pins, 1.0)
184        .expect("reduced Hessian ok");
185    println!("reduced Hessian (2x2, column-major) = {hr:?}");
186
187    // Raw back-solve against a zero RHS — must come back zero.
188    let dim = solver.kkt_dim().expect("kkt_dim available");
189    let rhs = vec![0.0; dim];
190    let mut lhs = vec![1.0; dim];
191    solver.kkt_solve(&rhs, &mut lhs).expect("kkt_solve ok");
192    let max_abs = lhs.iter().fold(0.0_f64, |a, b| a.max(b.abs()));
193    println!("kkt_solve(0) max |lhs| = {max_abs:e}");
194    assert!(max_abs < 1e-10);
195}
More examples
Hide additional examples
examples/parametric_mpc.rs (line 178)
171fn main() {
172    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
173        eta1: 5.0,
174        eta2: 1.0,
175    }));
176
177    // One full IPM solve at the nominal parameter.
178    let mut solver = Solver::new(make_app(), tnlp);
179    let t0 = Instant::now();
180    let status = solver.solve();
181    let solve_dt = t0.elapsed();
182    println!(
183        "IPM solve at nominal (eta1=5.0, eta2=1.0): status={status:?}, {:.3} ms",
184        solve_dt.as_secs_f64() * 1e3
185    );
186    assert!(solver.converged().is_some());
187
188    // 10 parametric steps as eta2 sweeps from 1.0 to 1.5. Each one is
189    // a back-solve against the held factor — no IPM iteration.
190    let pins = vec![2 as Index, 3];
191    let mut total_step_dt = std::time::Duration::ZERO;
192    let mut steps = Vec::new();
193    for k in 1..=10 {
194        let d_eta2 = 0.05 * k as f64;
195        let t = Instant::now();
196        let dx = solver
197            .parametric_step(&pins, &[0.0, d_eta2])
198            .expect("parametric_step ok");
199        total_step_dt += t.elapsed();
200        steps.push((d_eta2, dx));
201    }
202
203    println!("\nparametric steps against the held factor:");
204    for (d_eta2, dx) in &steps {
205        println!(
206            "  Δeta2 = {d_eta2:+.2}  -> Δx_primal = [{:+.5}, {:+.5}, {:+.5}]",
207            dx[0], dx[1], dx[2]
208        );
209    }
210    let avg_step_us = total_step_dt.as_secs_f64() * 1e6 / steps.len() as f64;
211    println!(
212        "\n10 parametric steps: total {:.3} ms, mean {avg_step_us:.1} µs/step",
213        total_step_dt.as_secs_f64() * 1e3
214    );
215    println!(
216        "\nRatio: each parametric step is roughly {:.0}x cheaper than the IPM solve.",
217        solve_dt.as_secs_f64() / (total_step_dt.as_secs_f64() / steps.len() as f64),
218    );
219}
examples/sensitivity_factor_reuse_bench.rs (line 167)
157fn main() {
158    let deltas: Vec<Vec<Number>> = (1..=N).map(|k| vec![0.0, 0.01 * k as Number]).collect();
159    let pins = vec![2 as Index, 3];
160
161    // (1) Held-factor path: 1 solve + N parametric steps.
162    let t = Instant::now();
163    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
164        eta1: 5.0,
165        eta2: 1.0,
166    }));
167    let mut solver = Solver::new(make_app(), tnlp);
168    solver.solve();
169    let solve_dt = t.elapsed();
170    let mut held_steps = Vec::with_capacity(N);
171    let t = Instant::now();
172    for d in &deltas {
173        let dx = solver
174            .parametric_step(&pins, d)
175            .expect("parametric_step ok");
176        held_steps.push(dx);
177    }
178    let held_step_dt = t.elapsed();
179    let held_total = solve_dt + held_step_dt;
180
181    // (2) Cold path: N fresh SensSolve runs (one full IPM each).
182    let mut cold_steps = Vec::with_capacity(N);
183    let t = Instant::now();
184    for d in &deltas {
185        let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
186            eta1: 5.0,
187            eta2: 1.0,
188        }));
189        let mut app = make_app();
190        let r = SensSolve::new(pins.clone())
191            .with_deltas(d.clone())
192            .run(&mut app, tnlp);
193        cold_steps.push(r.dx.expect("dx"));
194    }
195    let cold_total = t.elapsed();
196
197    // Cross-check: held vs cold dx should agree.
198    let mut max_err = 0.0_f64;
199    for (h, c) in held_steps.iter().zip(cold_steps.iter()) {
200        for (a, b) in h.iter().zip(c.iter()) {
201            max_err = max_err.max((a - b).abs());
202        }
203    }
204
205    println!("Workload: 1 IPM solve + {N} parametric steps (Δeta2 sweep).");
206    println!();
207    println!(
208        "Held-factor path : solve {:.2} ms + {N} steps {:.2} ms = {:.2} ms total",
209        solve_dt.as_secs_f64() * 1e3,
210        held_step_dt.as_secs_f64() * 1e3,
211        held_total.as_secs_f64() * 1e3,
212    );
213    println!(
214        "Cold-restart path:                          {N} fresh solves = {:.2} ms total",
215        cold_total.as_secs_f64() * 1e3,
216    );
217    println!();
218    println!(
219        "Speedup of held-factor over cold-restart: {:.1}x",
220        cold_total.as_secs_f64() / held_total.as_secs_f64()
221    );
222    println!("Numerical agreement (held vs cold dx): max |err| = {max_err:.2e}");
223}
Source

pub fn app(&self) -> &IpoptApplication

Borrow the underlying IpoptApplication (e.g. to read its options table after a solve). Mutation between solve calls is supported via Self::app_mut.

Source

pub fn app_mut(&mut self) -> &mut IpoptApplication

Mutable borrow of the underlying IpoptApplication. Useful for reconfiguring options before a follow-up solve(). Note that changing options that affect the KKT linear system between calls will invalidate the cached factor; the next solve() rebuilds it.

Source

pub fn solve(&mut self) -> ApplicationReturnStatus

Run the IPM to convergence. On a successful solve the ConvergedState (including the KKT backsolver) is stashed inside the Solver and accessible via Self::converged.

Each call to solve() overwrites the previous converged state; the previously held factor is dropped.

Examples found in repository?
examples/sensitivity_session.rs (line 167)
160fn main() {
161    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
162        eta1: 5.0,
163        eta2: 1.0,
164    }));
165
166    let mut solver = Solver::new(make_app(), tnlp);
167    let status = solver.solve();
168    println!("solve status: {status:?}");
169    assert!(solver.converged().is_some(), "solver did not converge");
170
171    let pins = vec![2 as Index, 3];
172
173    // Two cheap parametric steps against the same factor.
174    for deltas in &[vec![-0.5, 0.0], vec![0.0, 0.2]] {
175        let dx = solver
176            .parametric_step(&pins, deltas)
177            .expect("parametric_step ok");
178        println!("parametric_step(deltas={deltas:?}) -> dx = {dx:?}");
179    }
180
181    // Reduced Hessian over the same pinned-row set.
182    let hr = solver
183        .compute_reduced_hessian(&pins, 1.0)
184        .expect("reduced Hessian ok");
185    println!("reduced Hessian (2x2, column-major) = {hr:?}");
186
187    // Raw back-solve against a zero RHS — must come back zero.
188    let dim = solver.kkt_dim().expect("kkt_dim available");
189    let rhs = vec![0.0; dim];
190    let mut lhs = vec![1.0; dim];
191    solver.kkt_solve(&rhs, &mut lhs).expect("kkt_solve ok");
192    let max_abs = lhs.iter().fold(0.0_f64, |a, b| a.max(b.abs()));
193    println!("kkt_solve(0) max |lhs| = {max_abs:e}");
194    assert!(max_abs < 1e-10);
195}
More examples
Hide additional examples
examples/parametric_mpc.rs (line 180)
171fn main() {
172    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
173        eta1: 5.0,
174        eta2: 1.0,
175    }));
176
177    // One full IPM solve at the nominal parameter.
178    let mut solver = Solver::new(make_app(), tnlp);
179    let t0 = Instant::now();
180    let status = solver.solve();
181    let solve_dt = t0.elapsed();
182    println!(
183        "IPM solve at nominal (eta1=5.0, eta2=1.0): status={status:?}, {:.3} ms",
184        solve_dt.as_secs_f64() * 1e3
185    );
186    assert!(solver.converged().is_some());
187
188    // 10 parametric steps as eta2 sweeps from 1.0 to 1.5. Each one is
189    // a back-solve against the held factor — no IPM iteration.
190    let pins = vec![2 as Index, 3];
191    let mut total_step_dt = std::time::Duration::ZERO;
192    let mut steps = Vec::new();
193    for k in 1..=10 {
194        let d_eta2 = 0.05 * k as f64;
195        let t = Instant::now();
196        let dx = solver
197            .parametric_step(&pins, &[0.0, d_eta2])
198            .expect("parametric_step ok");
199        total_step_dt += t.elapsed();
200        steps.push((d_eta2, dx));
201    }
202
203    println!("\nparametric steps against the held factor:");
204    for (d_eta2, dx) in &steps {
205        println!(
206            "  Δeta2 = {d_eta2:+.2}  -> Δx_primal = [{:+.5}, {:+.5}, {:+.5}]",
207            dx[0], dx[1], dx[2]
208        );
209    }
210    let avg_step_us = total_step_dt.as_secs_f64() * 1e6 / steps.len() as f64;
211    println!(
212        "\n10 parametric steps: total {:.3} ms, mean {avg_step_us:.1} µs/step",
213        total_step_dt.as_secs_f64() * 1e3
214    );
215    println!(
216        "\nRatio: each parametric step is roughly {:.0}x cheaper than the IPM solve.",
217        solve_dt.as_secs_f64() / (total_step_dt.as_secs_f64() / steps.len() as f64),
218    );
219}
examples/sensitivity_factor_reuse_bench.rs (line 168)
157fn main() {
158    let deltas: Vec<Vec<Number>> = (1..=N).map(|k| vec![0.0, 0.01 * k as Number]).collect();
159    let pins = vec![2 as Index, 3];
160
161    // (1) Held-factor path: 1 solve + N parametric steps.
162    let t = Instant::now();
163    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
164        eta1: 5.0,
165        eta2: 1.0,
166    }));
167    let mut solver = Solver::new(make_app(), tnlp);
168    solver.solve();
169    let solve_dt = t.elapsed();
170    let mut held_steps = Vec::with_capacity(N);
171    let t = Instant::now();
172    for d in &deltas {
173        let dx = solver
174            .parametric_step(&pins, d)
175            .expect("parametric_step ok");
176        held_steps.push(dx);
177    }
178    let held_step_dt = t.elapsed();
179    let held_total = solve_dt + held_step_dt;
180
181    // (2) Cold path: N fresh SensSolve runs (one full IPM each).
182    let mut cold_steps = Vec::with_capacity(N);
183    let t = Instant::now();
184    for d in &deltas {
185        let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
186            eta1: 5.0,
187            eta2: 1.0,
188        }));
189        let mut app = make_app();
190        let r = SensSolve::new(pins.clone())
191            .with_deltas(d.clone())
192            .run(&mut app, tnlp);
193        cold_steps.push(r.dx.expect("dx"));
194    }
195    let cold_total = t.elapsed();
196
197    // Cross-check: held vs cold dx should agree.
198    let mut max_err = 0.0_f64;
199    for (h, c) in held_steps.iter().zip(cold_steps.iter()) {
200        for (a, b) in h.iter().zip(c.iter()) {
201            max_err = max_err.max((a - b).abs());
202        }
203    }
204
205    println!("Workload: 1 IPM solve + {N} parametric steps (Δeta2 sweep).");
206    println!();
207    println!(
208        "Held-factor path : solve {:.2} ms + {N} steps {:.2} ms = {:.2} ms total",
209        solve_dt.as_secs_f64() * 1e3,
210        held_step_dt.as_secs_f64() * 1e3,
211        held_total.as_secs_f64() * 1e3,
212    );
213    println!(
214        "Cold-restart path:                          {N} fresh solves = {:.2} ms total",
215        cold_total.as_secs_f64() * 1e3,
216    );
217    println!();
218    println!(
219        "Speedup of held-factor over cold-restart: {:.1}x",
220        cold_total.as_secs_f64() / held_total.as_secs_f64()
221    );
222    println!("Numerical agreement (held vs cold dx): max |err| = {max_err:.2e}");
223}
Source

pub fn converged(&self) -> Option<Ref<'_, ConvergedState>>

Borrow the converged state, if a successful solve has been run. Returns None if no solve has run or if the most recent solve failed before reaching convergence.

Examples found in repository?
examples/sensitivity_session.rs (line 169)
160fn main() {
161    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
162        eta1: 5.0,
163        eta2: 1.0,
164    }));
165
166    let mut solver = Solver::new(make_app(), tnlp);
167    let status = solver.solve();
168    println!("solve status: {status:?}");
169    assert!(solver.converged().is_some(), "solver did not converge");
170
171    let pins = vec![2 as Index, 3];
172
173    // Two cheap parametric steps against the same factor.
174    for deltas in &[vec![-0.5, 0.0], vec![0.0, 0.2]] {
175        let dx = solver
176            .parametric_step(&pins, deltas)
177            .expect("parametric_step ok");
178        println!("parametric_step(deltas={deltas:?}) -> dx = {dx:?}");
179    }
180
181    // Reduced Hessian over the same pinned-row set.
182    let hr = solver
183        .compute_reduced_hessian(&pins, 1.0)
184        .expect("reduced Hessian ok");
185    println!("reduced Hessian (2x2, column-major) = {hr:?}");
186
187    // Raw back-solve against a zero RHS — must come back zero.
188    let dim = solver.kkt_dim().expect("kkt_dim available");
189    let rhs = vec![0.0; dim];
190    let mut lhs = vec![1.0; dim];
191    solver.kkt_solve(&rhs, &mut lhs).expect("kkt_solve ok");
192    let max_abs = lhs.iter().fold(0.0_f64, |a, b| a.max(b.abs()));
193    println!("kkt_solve(0) max |lhs| = {max_abs:e}");
194    assert!(max_abs < 1e-10);
195}
More examples
Hide additional examples
examples/parametric_mpc.rs (line 186)
171fn main() {
172    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
173        eta1: 5.0,
174        eta2: 1.0,
175    }));
176
177    // One full IPM solve at the nominal parameter.
178    let mut solver = Solver::new(make_app(), tnlp);
179    let t0 = Instant::now();
180    let status = solver.solve();
181    let solve_dt = t0.elapsed();
182    println!(
183        "IPM solve at nominal (eta1=5.0, eta2=1.0): status={status:?}, {:.3} ms",
184        solve_dt.as_secs_f64() * 1e3
185    );
186    assert!(solver.converged().is_some());
187
188    // 10 parametric steps as eta2 sweeps from 1.0 to 1.5. Each one is
189    // a back-solve against the held factor — no IPM iteration.
190    let pins = vec![2 as Index, 3];
191    let mut total_step_dt = std::time::Duration::ZERO;
192    let mut steps = Vec::new();
193    for k in 1..=10 {
194        let d_eta2 = 0.05 * k as f64;
195        let t = Instant::now();
196        let dx = solver
197            .parametric_step(&pins, &[0.0, d_eta2])
198            .expect("parametric_step ok");
199        total_step_dt += t.elapsed();
200        steps.push((d_eta2, dx));
201    }
202
203    println!("\nparametric steps against the held factor:");
204    for (d_eta2, dx) in &steps {
205        println!(
206            "  Δeta2 = {d_eta2:+.2}  -> Δx_primal = [{:+.5}, {:+.5}, {:+.5}]",
207            dx[0], dx[1], dx[2]
208        );
209    }
210    let avg_step_us = total_step_dt.as_secs_f64() * 1e6 / steps.len() as f64;
211    println!(
212        "\n10 parametric steps: total {:.3} ms, mean {avg_step_us:.1} µs/step",
213        total_step_dt.as_secs_f64() * 1e3
214    );
215    println!(
216        "\nRatio: each parametric step is roughly {:.0}x cheaper than the IPM solve.",
217        solve_dt.as_secs_f64() / (total_step_dt.as_secs_f64() / steps.len() as f64),
218    );
219}
Source

pub fn kkt_dim(&self) -> Option<usize>

Total dimension of the compound KKT vector (sum of block_dims). Returns None if no converged factor is held.

Examples found in repository?
examples/sensitivity_session.rs (line 188)
160fn main() {
161    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
162        eta1: 5.0,
163        eta2: 1.0,
164    }));
165
166    let mut solver = Solver::new(make_app(), tnlp);
167    let status = solver.solve();
168    println!("solve status: {status:?}");
169    assert!(solver.converged().is_some(), "solver did not converge");
170
171    let pins = vec![2 as Index, 3];
172
173    // Two cheap parametric steps against the same factor.
174    for deltas in &[vec![-0.5, 0.0], vec![0.0, 0.2]] {
175        let dx = solver
176            .parametric_step(&pins, deltas)
177            .expect("parametric_step ok");
178        println!("parametric_step(deltas={deltas:?}) -> dx = {dx:?}");
179    }
180
181    // Reduced Hessian over the same pinned-row set.
182    let hr = solver
183        .compute_reduced_hessian(&pins, 1.0)
184        .expect("reduced Hessian ok");
185    println!("reduced Hessian (2x2, column-major) = {hr:?}");
186
187    // Raw back-solve against a zero RHS — must come back zero.
188    let dim = solver.kkt_dim().expect("kkt_dim available");
189    let rhs = vec![0.0; dim];
190    let mut lhs = vec![1.0; dim];
191    solver.kkt_solve(&rhs, &mut lhs).expect("kkt_solve ok");
192    let max_abs = lhs.iter().fold(0.0_f64, |a, b| a.max(b.abs()));
193    println!("kkt_solve(0) max |lhs| = {max_abs:e}");
194    assert!(max_abs < 1e-10);
195}
Source

pub fn block_dims(&self) -> Option<[usize; 8]>

Block dimensions of the compound KKT vector in (x, s, y_c, y_d, z_l, z_u, v_l, v_u) order. Returns None if no converged factor is held.

Source

pub fn kkt_solve( &self, rhs: &[Number], lhs: &mut [Number], ) -> Result<(), SolverError>

Solve K · lhs = rhs against the converged KKT factor. Both slices must have length kkt_dim(); the layout is the flat x || s || y_c || y_d || z_l || z_u || v_l || v_u packing.

Examples found in repository?
examples/sensitivity_session.rs (line 191)
160fn main() {
161    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
162        eta1: 5.0,
163        eta2: 1.0,
164    }));
165
166    let mut solver = Solver::new(make_app(), tnlp);
167    let status = solver.solve();
168    println!("solve status: {status:?}");
169    assert!(solver.converged().is_some(), "solver did not converge");
170
171    let pins = vec![2 as Index, 3];
172
173    // Two cheap parametric steps against the same factor.
174    for deltas in &[vec![-0.5, 0.0], vec![0.0, 0.2]] {
175        let dx = solver
176            .parametric_step(&pins, deltas)
177            .expect("parametric_step ok");
178        println!("parametric_step(deltas={deltas:?}) -> dx = {dx:?}");
179    }
180
181    // Reduced Hessian over the same pinned-row set.
182    let hr = solver
183        .compute_reduced_hessian(&pins, 1.0)
184        .expect("reduced Hessian ok");
185    println!("reduced Hessian (2x2, column-major) = {hr:?}");
186
187    // Raw back-solve against a zero RHS — must come back zero.
188    let dim = solver.kkt_dim().expect("kkt_dim available");
189    let rhs = vec![0.0; dim];
190    let mut lhs = vec![1.0; dim];
191    solver.kkt_solve(&rhs, &mut lhs).expect("kkt_solve ok");
192    let max_abs = lhs.iter().fold(0.0_f64, |a, b| a.max(b.abs()));
193    println!("kkt_solve(0) max |lhs| = {max_abs:e}");
194    assert!(max_abs < 1e-10);
195}
Source

pub fn kkt_solve_many( &self, rhs_flat: &[Number], lhs_flat: &mut [Number], n_rhs: usize, ) -> Result<(), SolverError>

Batched-RHS back-solve. rhs_flat and lhs_flat are row-major (n_rhs, kkt_dim) buffers; each row is solved against the same converged factor. Equivalent in result to looping Self::kkt_solve but reuses one IteratesVector for the RHS and one for the result across all n_rhs calls — see crate::algorithm_backsolver::PdSensBacksolver::solve_many.

Source

pub fn parametric_step( &self, pin_constraint_indices: &[Index], deltas: &[Number], ) -> Result<Vec<Number>, SolverError>

First-order parametric step Δx ≈ ∂x*/∂p · Δp for a set of pinned equality constraints. pin_constraint_indices are 0-based indices into the user’s g(x); deltas is the perturbation Δp (same length).

Returns the n_x-long primal step. For the full KKT-space step, use Self::kkt_solve directly.

Examples found in repository?
examples/sensitivity_session.rs (line 176)
160fn main() {
161    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
162        eta1: 5.0,
163        eta2: 1.0,
164    }));
165
166    let mut solver = Solver::new(make_app(), tnlp);
167    let status = solver.solve();
168    println!("solve status: {status:?}");
169    assert!(solver.converged().is_some(), "solver did not converge");
170
171    let pins = vec![2 as Index, 3];
172
173    // Two cheap parametric steps against the same factor.
174    for deltas in &[vec![-0.5, 0.0], vec![0.0, 0.2]] {
175        let dx = solver
176            .parametric_step(&pins, deltas)
177            .expect("parametric_step ok");
178        println!("parametric_step(deltas={deltas:?}) -> dx = {dx:?}");
179    }
180
181    // Reduced Hessian over the same pinned-row set.
182    let hr = solver
183        .compute_reduced_hessian(&pins, 1.0)
184        .expect("reduced Hessian ok");
185    println!("reduced Hessian (2x2, column-major) = {hr:?}");
186
187    // Raw back-solve against a zero RHS — must come back zero.
188    let dim = solver.kkt_dim().expect("kkt_dim available");
189    let rhs = vec![0.0; dim];
190    let mut lhs = vec![1.0; dim];
191    solver.kkt_solve(&rhs, &mut lhs).expect("kkt_solve ok");
192    let max_abs = lhs.iter().fold(0.0_f64, |a, b| a.max(b.abs()));
193    println!("kkt_solve(0) max |lhs| = {max_abs:e}");
194    assert!(max_abs < 1e-10);
195}
More examples
Hide additional examples
examples/parametric_mpc.rs (line 197)
171fn main() {
172    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
173        eta1: 5.0,
174        eta2: 1.0,
175    }));
176
177    // One full IPM solve at the nominal parameter.
178    let mut solver = Solver::new(make_app(), tnlp);
179    let t0 = Instant::now();
180    let status = solver.solve();
181    let solve_dt = t0.elapsed();
182    println!(
183        "IPM solve at nominal (eta1=5.0, eta2=1.0): status={status:?}, {:.3} ms",
184        solve_dt.as_secs_f64() * 1e3
185    );
186    assert!(solver.converged().is_some());
187
188    // 10 parametric steps as eta2 sweeps from 1.0 to 1.5. Each one is
189    // a back-solve against the held factor — no IPM iteration.
190    let pins = vec![2 as Index, 3];
191    let mut total_step_dt = std::time::Duration::ZERO;
192    let mut steps = Vec::new();
193    for k in 1..=10 {
194        let d_eta2 = 0.05 * k as f64;
195        let t = Instant::now();
196        let dx = solver
197            .parametric_step(&pins, &[0.0, d_eta2])
198            .expect("parametric_step ok");
199        total_step_dt += t.elapsed();
200        steps.push((d_eta2, dx));
201    }
202
203    println!("\nparametric steps against the held factor:");
204    for (d_eta2, dx) in &steps {
205        println!(
206            "  Δeta2 = {d_eta2:+.2}  -> Δx_primal = [{:+.5}, {:+.5}, {:+.5}]",
207            dx[0], dx[1], dx[2]
208        );
209    }
210    let avg_step_us = total_step_dt.as_secs_f64() * 1e6 / steps.len() as f64;
211    println!(
212        "\n10 parametric steps: total {:.3} ms, mean {avg_step_us:.1} µs/step",
213        total_step_dt.as_secs_f64() * 1e3
214    );
215    println!(
216        "\nRatio: each parametric step is roughly {:.0}x cheaper than the IPM solve.",
217        solve_dt.as_secs_f64() / (total_step_dt.as_secs_f64() / steps.len() as f64),
218    );
219}
examples/sensitivity_factor_reuse_bench.rs (line 174)
157fn main() {
158    let deltas: Vec<Vec<Number>> = (1..=N).map(|k| vec![0.0, 0.01 * k as Number]).collect();
159    let pins = vec![2 as Index, 3];
160
161    // (1) Held-factor path: 1 solve + N parametric steps.
162    let t = Instant::now();
163    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
164        eta1: 5.0,
165        eta2: 1.0,
166    }));
167    let mut solver = Solver::new(make_app(), tnlp);
168    solver.solve();
169    let solve_dt = t.elapsed();
170    let mut held_steps = Vec::with_capacity(N);
171    let t = Instant::now();
172    for d in &deltas {
173        let dx = solver
174            .parametric_step(&pins, d)
175            .expect("parametric_step ok");
176        held_steps.push(dx);
177    }
178    let held_step_dt = t.elapsed();
179    let held_total = solve_dt + held_step_dt;
180
181    // (2) Cold path: N fresh SensSolve runs (one full IPM each).
182    let mut cold_steps = Vec::with_capacity(N);
183    let t = Instant::now();
184    for d in &deltas {
185        let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
186            eta1: 5.0,
187            eta2: 1.0,
188        }));
189        let mut app = make_app();
190        let r = SensSolve::new(pins.clone())
191            .with_deltas(d.clone())
192            .run(&mut app, tnlp);
193        cold_steps.push(r.dx.expect("dx"));
194    }
195    let cold_total = t.elapsed();
196
197    // Cross-check: held vs cold dx should agree.
198    let mut max_err = 0.0_f64;
199    for (h, c) in held_steps.iter().zip(cold_steps.iter()) {
200        for (a, b) in h.iter().zip(c.iter()) {
201            max_err = max_err.max((a - b).abs());
202        }
203    }
204
205    println!("Workload: 1 IPM solve + {N} parametric steps (Δeta2 sweep).");
206    println!();
207    println!(
208        "Held-factor path : solve {:.2} ms + {N} steps {:.2} ms = {:.2} ms total",
209        solve_dt.as_secs_f64() * 1e3,
210        held_step_dt.as_secs_f64() * 1e3,
211        held_total.as_secs_f64() * 1e3,
212    );
213    println!(
214        "Cold-restart path:                          {N} fresh solves = {:.2} ms total",
215        cold_total.as_secs_f64() * 1e3,
216    );
217    println!();
218    println!(
219        "Speedup of held-factor over cold-restart: {:.1}x",
220        cold_total.as_secs_f64() / held_total.as_secs_f64()
221    );
222    println!("Numerical agreement (held vs cold dx): max |err| = {max_err:.2e}");
223}
Source

pub fn compute_reduced_hessian( &self, pin_constraint_indices: &[Index], obj_scal: Number, ) -> Result<Vec<Number>, SolverError>

Reduced Hessian H_R = obj_scal · B K⁻¹ Bᵀ over the pinned equality-constraint rows, where B selects the pin_constraint_indices rows of the y_c block. Returns the -long column-major dense matrix (n = pin_constraint_indices.len()).

Equivalent to crate::SensSolve::with_reduced_hessian but usable post-hoc on a held Solver.

Examples found in repository?
examples/sensitivity_session.rs (line 183)
160fn main() {
161    let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(ParametricTNLP {
162        eta1: 5.0,
163        eta2: 1.0,
164    }));
165
166    let mut solver = Solver::new(make_app(), tnlp);
167    let status = solver.solve();
168    println!("solve status: {status:?}");
169    assert!(solver.converged().is_some(), "solver did not converge");
170
171    let pins = vec![2 as Index, 3];
172
173    // Two cheap parametric steps against the same factor.
174    for deltas in &[vec![-0.5, 0.0], vec![0.0, 0.2]] {
175        let dx = solver
176            .parametric_step(&pins, deltas)
177            .expect("parametric_step ok");
178        println!("parametric_step(deltas={deltas:?}) -> dx = {dx:?}");
179    }
180
181    // Reduced Hessian over the same pinned-row set.
182    let hr = solver
183        .compute_reduced_hessian(&pins, 1.0)
184        .expect("reduced Hessian ok");
185    println!("reduced Hessian (2x2, column-major) = {hr:?}");
186
187    // Raw back-solve against a zero RHS — must come back zero.
188    let dim = solver.kkt_dim().expect("kkt_dim available");
189    let rhs = vec![0.0; dim];
190    let mut lhs = vec![1.0; dim];
191    solver.kkt_solve(&rhs, &mut lhs).expect("kkt_solve ok");
192    let max_abs = lhs.iter().fold(0.0_f64, |a, b| a.max(b.abs()));
193    println!("kkt_solve(0) max |lhs| = {max_abs:e}");
194    assert!(max_abs < 1e-10);
195}

Auto Trait Implementations§

§

impl !Freeze for Solver

§

impl !RefUnwindSafe for Solver

§

impl !Send for Solver

§

impl !Sync for Solver

§

impl Unpin for Solver

§

impl UnsafeUnpin for Solver

§

impl !UnwindSafe for Solver

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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