use crate::ipopt_cq::IpoptCqHandle;
use crate::ipopt_data::IpoptDataHandle;
use crate::output::r#trait::IterationOutput;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrintInfoString {
Yes,
No,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InfPrTag {
Internal,
Original,
}
pub struct OrigIterationOutput {
pub print_info_string: PrintInfoString,
pub inf_pr_output: InfPrTag,
pub print_frequency_iter: i32,
pub print_frequency_time: f64,
last_header_iter: i32,
}
impl Default for OrigIterationOutput {
fn default() -> Self {
Self {
print_info_string: PrintInfoString::No,
inf_pr_output: InfPrTag::Original,
print_frequency_iter: 1,
print_frequency_time: 0.0,
last_header_iter: -1,
}
}
}
impl OrigIterationOutput {
pub fn new() -> Self {
Self::default()
}
pub const HEADER: &'static str =
"iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n";
}
impl IterationOutput for OrigIterationOutput {
fn write_output(&mut self) {
self.last_header_iter = 0;
}
fn format_row(&mut self, data: &IpoptDataHandle, cq: &IpoptCqHandle) -> String {
let d = data.borrow();
let c = cq.borrow();
let iter = d.iter_count;
let unscaled_f = c.unscaled_curr_f();
let inf_pr = match self.inf_pr_output {
InfPrTag::Internal => c.curr_primal_infeasibility_max(),
InfPrTag::Original => c.curr_primal_infeasibility_max(),
};
let inf_du = c.curr_dual_infeasibility_max();
let mu = d.curr_mu;
let lg_mu = mu.log10();
let dnrm = match &d.delta {
Some(delta) => delta.x.amax().max(delta.s.amax()),
None => 0.0,
};
let regu_x = d.info_regu_x;
let regu_str: String = if regu_x == 0.0 {
" -".to_string()
} else {
format!("{:6.1}", regu_x.log10())
};
let alpha_dual = d.info_alpha_dual;
let alpha_primal = d.info_alpha_primal;
let alpha_char = d.info_alpha_primal_char;
let ls_count = d.info_ls_count;
let mut row = format!(
"{:>4} {:>14} {:>8} {:>8} {:6.1} {:>8} {:>6} {:>8} {:>8}{}{:>3}",
iter,
format_e(unscaled_f, 7),
format_e(inf_pr, 2),
format_e(inf_du, 2),
lg_mu,
format_e(dnrm, 2),
regu_str,
format_e(alpha_dual, 2),
format_e(alpha_primal, 2),
alpha_char,
ls_count,
);
if self.print_info_string == PrintInfoString::Yes && !d.info_string.is_empty() {
row.push(' ');
row.push_str(&d.info_string);
}
row
}
}
pub(crate) fn format_e(x: f64, precision: usize) -> String {
if !x.is_finite() {
return format!("{}", x);
}
let s = format!("{:.*e}", precision, x);
let (mantissa, exp) = match s.split_once('e') {
Some(pair) => pair,
None => return s,
};
let (sign, digits) = match exp.strip_prefix('-') {
Some(rest) => ('-', rest),
None => ('+', exp),
};
if digits.len() == 1 {
format!("{}e{}0{}", mantissa, sign, digits)
} else {
format!("{}e{}{}", mantissa, sign, digits)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_layout_right_aligns_each_label() {
assert_eq!(OrigIterationOutput::HEADER.len(), 83); let h = OrigIterationOutput::HEADER.trim_end_matches('\n');
assert!(h.ends_with("ls"), "h = {h:?}");
assert_eq!(&h[10..19], "objective");
assert_eq!(&h[22..28], "inf_pr");
assert_eq!(&h[61..69], "alpha_du");
assert_eq!(&h[70..78], "alpha_pr");
}
#[test]
fn format_e_pads_short_exponents() {
assert_eq!(format_e(0.0, 2), "0.00e+00");
assert_eq!(format_e(1.0, 2), "1.00e+00");
assert_eq!(format_e(0.178, 2), "1.78e-01");
assert_eq!(format_e(8.83e-13, 2), "8.83e-13");
assert_eq!(format_e(7.74, 2), "7.74e+00");
assert_eq!(format_e(1.0e10, 2), "1.00e+10");
}
#[test]
fn format_e_passes_through_non_finite() {
assert_eq!(format_e(f64::NAN, 2), "NaN");
assert_eq!(format_e(f64::INFINITY, 2), "inf");
}
}