1use crate::ipopt_cq::IpoptCqHandle;
17use crate::ipopt_data::IpoptDataHandle;
18use crate::output::r#trait::IterationOutput;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum PrintInfoString {
22 Yes,
23 No,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum InfPrTag {
28 Internal,
29 Original,
30}
31
32pub struct OrigIterationOutput {
33 pub print_info_string: PrintInfoString,
34 pub inf_pr_output: InfPrTag,
35 pub print_frequency_iter: i32,
36 pub print_frequency_time: f64,
37 last_header_iter: i32,
40}
41
42impl Default for OrigIterationOutput {
43 fn default() -> Self {
44 Self {
45 print_info_string: PrintInfoString::No,
46 inf_pr_output: InfPrTag::Original,
47 print_frequency_iter: 1,
48 print_frequency_time: 0.0,
49 last_header_iter: -1,
50 }
51 }
52}
53
54impl OrigIterationOutput {
55 pub fn new() -> Self {
56 Self::default()
57 }
58
59 pub const HEADER: &'static str =
63 "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n";
64}
65
66impl IterationOutput for OrigIterationOutput {
67 fn write_output(&mut self) {
68 self.last_header_iter = 0;
71 }
72
73 fn format_row(&mut self, data: &IpoptDataHandle, cq: &IpoptCqHandle) -> String {
77 let d = data.borrow();
78 let c = cq.borrow();
79
80 let iter = d.iter_count;
81 let unscaled_f = c.unscaled_curr_f();
82 let inf_pr = match self.inf_pr_output {
83 InfPrTag::Internal => c.curr_primal_infeasibility_max(),
84 InfPrTag::Original => c.curr_unscaled_nlp_constraint_violation_max(),
85 };
86 let inf_du = c.curr_dual_infeasibility_max();
87 let mu = d.curr_mu;
88 let lg_mu = mu.log10();
89
90 let dnrm = match &d.delta {
92 Some(delta) => delta.x.amax().max(delta.s.amax()),
93 None => 0.0,
94 };
95
96 let regu_x = d.info_regu_x;
97 let regu_str: String = if regu_x == 0.0 {
98 " -".to_string()
99 } else {
100 format!("{:6.1}", regu_x.log10())
101 };
102
103 let alpha_dual = d.info_alpha_dual;
104 let alpha_primal = d.info_alpha_primal;
105 let alpha_char = d.info_alpha_primal_char;
106 let ls_count = d.info_ls_count;
107
108 let mut row = format!(
109 "{:>4} {:>14} {:>8} {:>8} {:6.1} {:>8} {:>6} {:>8} {:>8}{}{:>3}",
110 iter,
111 format_e(unscaled_f, 7),
112 format_e(inf_pr, 2),
113 format_e(inf_du, 2),
114 lg_mu,
115 format_e(dnrm, 2),
116 regu_str,
117 format_e(alpha_dual, 2),
118 format_e(alpha_primal, 2),
119 alpha_char,
120 ls_count,
121 );
122 if self.print_info_string == PrintInfoString::Yes && !d.info_string.is_empty() {
129 row.push(' ');
130 row.push_str(&d.info_string);
131 }
132 row
133 }
134}
135
136pub(crate) fn format_e(x: f64, precision: usize) -> String {
147 if !x.is_finite() {
148 return format!("{}", x);
149 }
150 let s = format!("{:.*e}", precision, x);
151 let (mantissa, exp) = match s.split_once('e') {
152 Some(pair) => pair,
153 None => return s,
154 };
155 let (sign, digits) = match exp.strip_prefix('-') {
156 Some(rest) => ('-', rest),
157 None => ('+', exp),
158 };
159 if digits.len() == 1 {
160 format!("{}e{}0{}", mantissa, sign, digits)
161 } else {
162 format!("{}e{}{}", mantissa, sign, digits)
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn header_layout_right_aligns_each_label() {
172 assert_eq!(OrigIterationOutput::HEADER.len(), 83); let h = OrigIterationOutput::HEADER.trim_end_matches('\n');
178 assert!(h.ends_with("ls"), "h = {h:?}");
179 assert_eq!(&h[10..19], "objective");
180 assert_eq!(&h[22..28], "inf_pr");
181 assert_eq!(&h[61..69], "alpha_du");
182 assert_eq!(&h[70..78], "alpha_pr");
183 }
184
185 #[test]
186 fn format_e_pads_short_exponents() {
187 assert_eq!(format_e(0.0, 2), "0.00e+00");
188 assert_eq!(format_e(1.0, 2), "1.00e+00");
189 assert_eq!(format_e(0.178, 2), "1.78e-01");
190 assert_eq!(format_e(8.83e-13, 2), "8.83e-13");
191 assert_eq!(format_e(7.74, 2), "7.74e+00");
192 assert_eq!(format_e(1.0e10, 2), "1.00e+10");
194 }
195
196 #[test]
197 fn format_e_passes_through_non_finite() {
198 assert_eq!(format_e(f64::NAN, 2), "NaN");
199 assert_eq!(format_e(f64::INFINITY, 2), "inf");
200 }
201}