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
impl Solver
Sourcepub fn new(app: IpoptApplication, tnlp: Rc<RefCell<dyn TNLP>>) -> Self
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?
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
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}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}Sourcepub fn app(&self) -> &IpoptApplication
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.
Sourcepub fn app_mut(&mut self) -> &mut IpoptApplication
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.
Sourcepub fn solve(&mut self) -> ApplicationReturnStatus
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?
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
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}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}Sourcepub fn converged(&self) -> Option<Ref<'_, ConvergedState>>
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?
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
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}Sourcepub fn kkt_dim(&self) -> Option<usize>
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?
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}Sourcepub fn block_dims(&self) -> Option<[usize; 8]>
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.
Sourcepub fn kkt_solve(
&self,
rhs: &[Number],
lhs: &mut [Number],
) -> Result<(), SolverError>
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?
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}Sourcepub fn parametric_step(
&self,
pin_constraint_indices: &[Index],
deltas: &[Number],
) -> Result<Vec<Number>, SolverError>
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?
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
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}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}Sourcepub fn compute_reduced_hessian(
&self,
pin_constraint_indices: &[Index],
obj_scal: Number,
) -> Result<Vec<Number>, SolverError>
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
n²-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?
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> 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> 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