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_primal_infeasibility_max(),
88 };
89 let inf_du = c.curr_dual_infeasibility_max();
90 let mu = d.curr_mu;
91 let lg_mu = mu.log10();
92
93 let dnrm = match &d.delta {
95 Some(delta) => delta.x.amax().max(delta.s.amax()),
96 None => 0.0,
97 };
98
99 let regu_x = d.info_regu_x;
100 let regu_str: String = if regu_x == 0.0 {
101 " -".to_string()
102 } else {
103 format!("{:6.1}", regu_x.log10())
104 };
105
106 let alpha_dual = d.info_alpha_dual;
107 let alpha_primal = d.info_alpha_primal;
108 let alpha_char = d.info_alpha_primal_char;
109 let ls_count = d.info_ls_count;
110
111 let mut row = format!(
112 "{:>4} {:>14} {:>8} {:>8} {:6.1} {:>8} {:>6} {:>8} {:>8}{}{:>3}",
113 iter,
114 format_e(unscaled_f, 7),
115 format_e(inf_pr, 2),
116 format_e(inf_du, 2),
117 lg_mu,
118 format_e(dnrm, 2),
119 regu_str,
120 format_e(alpha_dual, 2),
121 format_e(alpha_primal, 2),
122 alpha_char,
123 ls_count,
124 );
125 if self.print_info_string == PrintInfoString::Yes && !d.info_string.is_empty() {
132 row.push(' ');
133 row.push_str(&d.info_string);
134 }
135 row
136 }
137}
138
139pub(crate) fn format_e(x: f64, precision: usize) -> String {
150 if !x.is_finite() {
151 return format!("{}", x);
152 }
153 let s = format!("{:.*e}", precision, x);
154 let (mantissa, exp) = match s.split_once('e') {
155 Some(pair) => pair,
156 None => return s,
157 };
158 let (sign, digits) = match exp.strip_prefix('-') {
159 Some(rest) => ('-', rest),
160 None => ('+', exp),
161 };
162 if digits.len() == 1 {
163 format!("{}e{}0{}", mantissa, sign, digits)
164 } else {
165 format!("{}e{}{}", mantissa, sign, digits)
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn header_layout_right_aligns_each_label() {
175 assert_eq!(OrigIterationOutput::HEADER.len(), 83); let h = OrigIterationOutput::HEADER.trim_end_matches('\n');
181 assert!(h.ends_with("ls"), "h = {h:?}");
182 assert_eq!(&h[10..19], "objective");
183 assert_eq!(&h[22..28], "inf_pr");
184 assert_eq!(&h[61..69], "alpha_du");
185 assert_eq!(&h[70..78], "alpha_pr");
186 }
187
188 #[test]
189 fn format_e_pads_short_exponents() {
190 assert_eq!(format_e(0.0, 2), "0.00e+00");
191 assert_eq!(format_e(1.0, 2), "1.00e+00");
192 assert_eq!(format_e(0.178, 2), "1.78e-01");
193 assert_eq!(format_e(8.83e-13, 2), "8.83e-13");
194 assert_eq!(format_e(7.74, 2), "7.74e+00");
195 assert_eq!(format_e(1.0e10, 2), "1.00e+10");
197 }
198
199 #[test]
200 fn format_e_passes_through_non_finite() {
201 assert_eq!(format_e(f64::NAN, 2), "NaN");
202 assert_eq!(format_e(f64::INFINITY, 2), "inf");
203 }
204}