1use std::cell::RefCell;
20use std::rc::Rc;
21
22use pounce_algorithm::application::IpoptApplication;
23use pounce_common::types::{Index, Number};
24use pounce_nlp::return_codes::ApplicationReturnStatus;
25use pounce_nlp::tnlp::{
26 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest, StartingPoint,
27 TNLP,
28};
29use pounce_sensitivity::Solver;
30
31struct PinnedQuadratic {
39 p0: Number,
40 p1: Number,
41}
42
43impl TNLP for PinnedQuadratic {
44 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
45 Some(NlpInfo {
46 n: 2,
47 m: 2,
48 nnz_jac_g: 2,
49 nnz_h_lag: 3,
50 index_style: IndexStyle::C,
51 })
52 }
53
54 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
55 for k in 0..2 {
56 b.x_l[k] = -1.0e19;
57 b.x_u[k] = 1.0e19;
58 }
59 b.g_l[0] = self.p0;
60 b.g_u[0] = self.p0;
61 b.g_l[1] = self.p1;
62 b.g_u[1] = self.p1;
63 true
64 }
65
66 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
67 sp.x[0] = self.p0;
68 sp.x[1] = self.p1;
69 true
70 }
71
72 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
73 Some(x[0] * x[0] + x[1] * x[1] + x[0] * x[1])
74 }
75
76 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
77 g[0] = 2.0 * x[0] + x[1];
78 g[1] = 2.0 * x[1] + x[0];
79 true
80 }
81
82 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
83 g[0] = x[0];
84 g[1] = x[1];
85 true
86 }
87
88 fn eval_jac_g(
89 &mut self,
90 _x: Option<&[Number]>,
91 _new_x: bool,
92 mode: SparsityRequest<'_>,
93 ) -> bool {
94 match mode {
95 SparsityRequest::Structure { irow, jcol } => {
96 irow.copy_from_slice(&[0 as Index, 1 as Index]);
97 jcol.copy_from_slice(&[0 as Index, 1 as Index]);
98 }
99 SparsityRequest::Values { values } => values.copy_from_slice(&[1.0, 1.0]),
100 }
101 true
102 }
103
104 fn eval_h(
105 &mut self,
106 _x: Option<&[Number]>,
107 _new_x: bool,
108 obj_factor: Number,
109 _lambda: Option<&[Number]>,
110 _new_lambda: bool,
111 mode: SparsityRequest<'_>,
112 ) -> bool {
113 match mode {
114 SparsityRequest::Structure { irow, jcol } => {
115 irow.copy_from_slice(&[0 as Index, 1 as Index, 1 as Index]);
117 jcol.copy_from_slice(&[0 as Index, 0 as Index, 1 as Index]);
118 }
119 SparsityRequest::Values { values } => {
120 values.copy_from_slice(&[2.0 * obj_factor, obj_factor, 2.0 * obj_factor]);
121 }
122 }
123 true
124 }
125
126 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
127}
128
129const H: [Number; 4] = [2.0, 1.0, 1.0, 2.0];
131
132fn main() {
133 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(PinnedQuadratic { p0: 1.0, p1: 2.0 }));
134 let mut app = IpoptApplication::new();
135 app.options_mut()
136 .set_integer_value("print_level", 0, true, false)
137 .unwrap();
138 app.options_mut()
139 .set_string_value("sb", "yes", true, false)
140 .unwrap();
141 app.initialize().unwrap();
142
143 let mut solver = Solver::new(app, tnlp);
144 let status = solver.solve();
145 assert!(
146 matches!(
147 status,
148 ApplicationReturnStatus::SolveSucceeded
149 | ApplicationReturnStatus::SolvedToAcceptableLevel
150 ),
151 "solve failed: {status:?}"
152 );
153
154 let (hr, vals, vecs) = solver
155 .compute_reduced_hessian_eigen(&[0, 1], 1.0)
156 .expect("reduced Hessian");
157
158 println!("model: min x0² + x1² + x0·x1 s.t. x0 = 1, x1 = 2");
159 println!("H = [[2, 1], [1, 2]] (eigenvalues 1 and 3)");
160 println!();
161 println!("compute_reduced_hessian(pins=[0, 1]):");
162 for i in 0..2 {
163 println!(" [{:>9.6}, {:>9.6}]", hr[i], hr[i + 2]);
164 }
165 println!(
166 " eigenvalues (ascending) = [{:>9.6}, {:>9.6}]",
167 vals[0], vals[1]
168 );
169 for j in 0..2 {
170 println!(
171 " eigenvector[{j}] = [{:>9.6}, {:>9.6}]",
172 vecs[2 * j],
173 vecs[2 * j + 1]
174 );
175 }
176 println!();
177
178 let h_inv: [Number; 4] = [2.0 / 3.0, -1.0 / 3.0, -1.0 / 3.0, 2.0 / 3.0];
181 let candidates: [(&str, [Number; 4]); 3] = [
182 ("+H_R", H),
183 ("-H_R", [-H[0], -H[1], -H[2], -H[3]]),
184 ("H_R⁻¹", h_inv),
185 ];
186 for (name, want) in candidates {
187 let err = (0..4)
188 .map(|k| (hr[k] - want[k]).abs())
189 .fold(0.0 as Number, Number::max);
190 println!(
191 " vs {name:<6} max|Δ| = {err:.3e} {}",
192 if err < 1e-7 { "← MATCH" } else { "" }
193 );
194 }
195 println!();
196 println!(
197 "So the ascending spectrum runs STIFFEST first: {:.6} is the curvature-3",
198 vals[0]
199 );
200 println!(
201 "mode and {:.6} the curvature-1 (soft) one — the reverse of the",
202 vals[1]
203 );
204 println!("order a caller reading `+H_R` would assume.");
205}