1use crate::counting_tnlp::CountingTnlp;
7use pounce_nlp::return_codes::ApplicationReturnStatus;
8use pounce_nlp::solve_statistics::SolveStatistics;
9use pounce_nlp::tnlp::{BoundsInfo, NlpInfo, SparsityRequest, TNLP};
10use std::cell::RefCell;
11use std::rc::Rc;
12
13const BOUND_INF: f64 = 1.0e19;
17
18#[derive(Debug, Clone, Copy)]
19pub struct ProblemStats {
20 pub n: i32,
21 pub m: i32,
22 pub nnz_jac_eq: i32,
23 pub nnz_jac_ineq: i32,
24 pub nnz_h_lag: i32,
25 pub var_lower_only: i32,
26 pub var_upper_only: i32,
27 pub var_both: i32,
28 pub var_free: i32,
29 pub n_eq: i32,
30 pub n_ineq: i32,
31 pub ineq_lower_only: i32,
32 pub ineq_upper_only: i32,
33 pub ineq_both: i32,
34}
35
36pub fn collect_stats(tnlp: &Rc<RefCell<dyn TNLP>>) -> Option<ProblemStats> {
40 let mut t = tnlp.borrow_mut();
41 let info: NlpInfo = t.get_nlp_info()?;
42 let n = info.n as usize;
43 let m = info.m as usize;
44 let mut x_l = vec![0.0; n];
45 let mut x_u = vec![0.0; n];
46 let mut g_l = vec![0.0; m];
47 let mut g_u = vec![0.0; m];
48 if !t.get_bounds_info(BoundsInfo {
49 x_l: &mut x_l,
50 x_u: &mut x_u,
51 g_l: &mut g_l,
52 g_u: &mut g_u,
53 }) {
54 return None;
55 }
56
57 let (mut var_lower_only, mut var_upper_only, mut var_both, mut var_free) = (0, 0, 0, 0);
59 for i in 0..n {
60 let has_l = x_l[i] > -BOUND_INF;
61 let has_u = x_u[i] < BOUND_INF;
62 match (has_l, has_u) {
63 (true, true) => var_both += 1,
64 (true, false) => var_lower_only += 1,
65 (false, true) => var_upper_only += 1,
66 (false, false) => var_free += 1,
67 }
68 }
69
70 let (mut n_eq, mut n_ineq) = (0, 0);
73 let (mut ineq_lower_only, mut ineq_upper_only, mut ineq_both) = (0, 0, 0);
74 let mut row_is_eq = vec![false; m];
75 for i in 0..m {
76 if (g_l[i] - g_u[i]).abs() < 1e-12 && g_l[i].abs() < BOUND_INF {
77 n_eq += 1;
78 row_is_eq[i] = true;
79 } else {
80 n_ineq += 1;
81 let has_l = g_l[i] > -BOUND_INF;
82 let has_u = g_u[i] < BOUND_INF;
83 match (has_l, has_u) {
84 (true, true) => ineq_both += 1,
85 (true, false) => ineq_lower_only += 1,
86 (false, true) => ineq_upper_only += 1,
87 (false, false) => {}
90 }
91 }
92 }
93
94 let nnz_total = info.nnz_jac_g as usize;
96 let (mut nnz_jac_eq, mut nnz_jac_ineq) = (0, 0);
97 if nnz_total > 0 && m > 0 {
98 let mut irow = vec![0_i32; nnz_total];
99 let mut jcol = vec![0_i32; nnz_total];
100 if t.eval_jac_g(
101 None,
102 true,
103 SparsityRequest::Structure {
104 irow: &mut irow,
105 jcol: &mut jcol,
106 },
107 ) {
108 let one_based = matches!(info.index_style, pounce_nlp::tnlp::IndexStyle::Fortran);
109 for &r in &irow {
110 let row = if one_based {
111 (r - 1) as usize
112 } else {
113 r as usize
114 };
115 if row < m && row_is_eq[row] {
116 nnz_jac_eq += 1;
117 } else {
118 nnz_jac_ineq += 1;
119 }
120 }
121 }
122 }
123
124 Some(ProblemStats {
125 n: info.n,
126 m: info.m,
127 nnz_jac_eq,
128 nnz_jac_ineq,
129 nnz_h_lag: info.nnz_h_lag,
130 var_lower_only,
131 var_upper_only,
132 var_both,
133 var_free,
134 n_eq,
135 n_ineq,
136 ineq_lower_only,
137 ineq_upper_only,
138 ineq_both,
139 })
140}
141
142pub fn print_banner(linear_solver: &str) {
143 println!("******************************************************************************");
144 println!("This program contains POUNCE, a Rust port of Ipopt for nonlinear optimization.");
145 println!(" Released under the Eclipse Public License (EPL) — drop-in compatible with Ipopt.");
146 println!(" For more information visit https://github.com/jkitchin/pounce");
147 println!("******************************************************************************");
148 println!();
149 println!(
150 "This is POUNCE version {}, running with linear solver {}.",
151 env!("CARGO_PKG_VERSION"),
152 linear_solver
153 );
154 println!();
155}
156
157pub fn print_problem_stats(s: &ProblemStats) {
158 println!(
159 "Number of nonzeros in equality constraint Jacobian...: {:>8}",
160 s.nnz_jac_eq
161 );
162 println!(
163 "Number of nonzeros in inequality constraint Jacobian.: {:>8}",
164 s.nnz_jac_ineq
165 );
166 println!(
167 "Number of nonzeros in Lagrangian Hessian.............: {:>8}",
168 s.nnz_h_lag
169 );
170 println!();
171 println!(
172 "Total number of variables............................: {:>8}",
173 s.n
174 );
175 println!(
176 " variables with only lower bounds: {:>8}",
177 s.var_lower_only
178 );
179 println!(
180 " variables with lower and upper bounds: {:>8}",
181 s.var_both
182 );
183 println!(
184 " variables with only upper bounds: {:>8}",
185 s.var_upper_only
186 );
187 println!(
188 "Total number of equality constraints.................: {:>8}",
189 s.n_eq
190 );
191 println!(
192 "Total number of inequality constraints...............: {:>8}",
193 s.n_ineq
194 );
195 println!(
196 " inequality constraints with only lower bounds: {:>8}",
197 s.ineq_lower_only
198 );
199 println!(
200 " inequality constraints with lower and upper bounds: {:>8}",
201 s.ineq_both
202 );
203 println!(
204 " inequality constraints with only upper bounds: {:>8}",
205 s.ineq_upper_only
206 );
207 println!();
208}
209
210pub fn print_summary(
211 status: ApplicationReturnStatus,
212 stats: &SolveStatistics,
213 counters: &CountingTnlp,
214) {
215 println!();
216 println!();
217 println!("Number of Iterations....: {}", stats.iteration_count);
218 println!();
219 println!(" (scaled) (unscaled)");
220 let row = |label: &str, scaled: f64, unscaled: f64| {
221 println!(
222 "{label}: {} {}",
223 fmt_ipopt(scaled),
224 fmt_ipopt(unscaled)
225 );
226 };
227 row(
228 "Objective...............",
229 stats.final_scaled_objective,
230 stats.final_objective,
231 );
232 row(
233 "Dual infeasibility......",
234 stats.final_dual_inf,
235 stats.final_dual_inf,
236 );
237 row(
238 "Constraint violation....",
239 stats.final_constr_viol,
240 stats.final_constr_viol,
241 );
242 row("Variable bound violation", 0.0, 0.0);
243 row(
244 "Complementarity.........",
245 stats.final_compl,
246 stats.final_compl,
247 );
248 row(
249 "Overall NLP error.......",
250 stats.final_kkt_error,
251 stats.final_kkt_error,
252 );
253 println!();
254 println!();
255 println!(
256 "Number of objective function evaluations = {}",
257 counters.n_obj.get()
258 );
259 println!(
260 "Number of objective gradient evaluations = {}",
261 counters.n_grad_f.get()
262 );
263 println!(
264 "Number of equality constraint evaluations = {}",
265 counters.n_g.get()
266 );
267 println!(
268 "Number of inequality constraint evaluations = {}",
269 counters.n_g.get()
270 );
271 println!(
272 "Number of equality constraint Jacobian evaluations = {}",
273 counters.n_jac_g.get()
274 );
275 println!(
276 "Number of inequality constraint Jacobian evaluations = {}",
277 counters.n_jac_g.get()
278 );
279 println!(
280 "Number of Lagrangian Hessian evaluations = {}",
281 counters.n_h.get()
282 );
283 println!(
284 "Total seconds in POUNCE = {:.3}",
285 stats.total_wallclock_time_secs
286 );
287 println!();
288 println!("EXIT: {}", status_message(status));
289 println!();
290 println!(
291 "POUNCE {}: {}",
292 env!("CARGO_PKG_VERSION"),
293 status_message(status)
294 );
295}
296
297pub fn fmt_ipopt(v: f64) -> String {
302 if v.is_nan() {
303 return "nan".to_string();
304 }
305 if v.is_infinite() {
306 return if v > 0.0 { "inf".into() } else { "-inf".into() };
307 }
308 let s = format!("{:.16e}", v);
309 let Some(e_pos) = s.rfind('e') else {
310 return s;
311 };
312 let (mantissa, exp_part) = s.split_at(e_pos);
313 let exp_str = &exp_part[1..];
314 let (sign, digits) = if let Some(rest) = exp_str.strip_prefix('-') {
315 ('-', rest)
316 } else if let Some(rest) = exp_str.strip_prefix('+') {
317 ('+', rest)
318 } else {
319 ('+', exp_str)
320 };
321 let padded = if digits.len() < 2 {
322 format!("0{digits}")
323 } else {
324 digits.to_string()
325 };
326 format!("{mantissa}e{sign}{padded}")
327}
328
329pub fn status_message(s: ApplicationReturnStatus) -> &'static str {
330 match s {
331 ApplicationReturnStatus::SolveSucceeded => "Optimal Solution Found.",
332 ApplicationReturnStatus::SolvedToAcceptableLevel => "Solved To Acceptable Level.",
333 ApplicationReturnStatus::InfeasibleProblemDetected => {
334 "Converged to a point of local infeasibility. Problem may be infeasible."
335 }
336 ApplicationReturnStatus::SearchDirectionBecomesTooSmall => {
337 "Search Direction is becoming Too Small."
338 }
339 ApplicationReturnStatus::DivergingIterates => {
340 "Iterates diverging; problem might be unbounded."
341 }
342 ApplicationReturnStatus::UserRequestedStop => "Stopping optimization at user request.",
343 ApplicationReturnStatus::FeasiblePointFound => "Feasible Point Found.",
344 ApplicationReturnStatus::MaximumIterationsExceeded => {
345 "Maximum Number of Iterations Exceeded."
346 }
347 ApplicationReturnStatus::RestorationFailed => "Restoration Failed!",
348 ApplicationReturnStatus::ErrorInStepComputation => "Error in step computation.",
349 ApplicationReturnStatus::MaximumCpuTimeExceeded => "Maximum CPU time exceeded.",
350 ApplicationReturnStatus::MaximumWallTimeExceeded => "Maximum wallclock time exceeded.",
351 ApplicationReturnStatus::NotEnoughDegreesOfFreedom => "Not Enough Degrees of Freedom.",
352 ApplicationReturnStatus::InvalidProblemDefinition => "Invalid Problem Definition.",
353 ApplicationReturnStatus::InvalidOption => "Invalid Option.",
354 ApplicationReturnStatus::InvalidNumberDetected => {
355 "Invalid number in NLP function or derivative detected."
356 }
357 ApplicationReturnStatus::UnrecoverableException => "Unrecoverable Exception.",
358 ApplicationReturnStatus::NonIpoptExceptionThrown => "Exception of type generic.",
359 ApplicationReturnStatus::InsufficientMemory => "Insufficient memory.",
360 ApplicationReturnStatus::InternalError => "INTERNAL ERROR: Unknown SolverReturn value.",
361 }
362}