use pounce_common::types::{Index, Number};
use std::fmt::Write as _;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct SolSuffix {
pub name: String,
pub target: SolSuffixTarget,
pub values: SolSuffixValues,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolSuffixTarget {
Var = 0,
Con = 1,
Obj = 2,
Problem = 3,
}
#[derive(Debug, Clone)]
pub enum SolSuffixValues {
Int(Vec<Index>),
Real(Vec<Number>),
ProblemInt(Index),
ProblemReal(Number),
}
#[derive(Debug, Clone)]
pub struct SolutionFile<'a> {
pub message: &'a str,
pub x: &'a [Number],
pub lambda: &'a [Number],
pub solve_result_num: i32,
pub suffixes: &'a [SolSuffix],
}
pub fn format_sol(payload: &SolutionFile<'_>) -> String {
let mut out = String::new();
for line in payload.message.lines() {
let _ = writeln!(out, "{line}");
}
out.push('\n');
out.push_str("Options\n");
out.push_str("0\n");
let m = payload.lambda.len();
let n = payload.x.len();
let _ = writeln!(out, "{m}");
let _ = writeln!(out, "{m}");
let _ = writeln!(out, "{n}");
let _ = writeln!(out, "{n}");
for &v in payload.lambda {
let _ = writeln!(out, "{v:.17e}");
}
for &v in payload.x {
let _ = writeln!(out, "{v:.17e}");
}
let _ = writeln!(out, "objno 0 {}", payload.solve_result_num);
for s in payload.suffixes {
write_suffix(&mut out, s);
}
out
}
fn write_suffix(out: &mut String, s: &SolSuffix) {
let target_bits = s.target as u32 & 0x3;
match &s.values {
SolSuffixValues::Int(vs) => {
let entries: Vec<(usize, Index)> = vs
.iter()
.enumerate()
.filter(|(_, &v)| v != 0)
.map(|(i, &v)| (i, v))
.collect();
write_suffix_header(out, target_bits, entries.len(), &s.name);
for (i, v) in entries {
let _ = writeln!(out, "{i} {v}");
}
}
SolSuffixValues::Real(vs) => {
let entries: Vec<(usize, Number)> = vs
.iter()
.enumerate()
.filter(|(_, &v)| v != 0.0)
.map(|(i, &v)| (i, v))
.collect();
write_suffix_header(out, target_bits | 0x4, entries.len(), &s.name);
for (i, v) in entries {
let _ = writeln!(out, "{i} {v:.17e}");
}
}
SolSuffixValues::ProblemInt(v) => {
write_suffix_header(out, target_bits, 1, &s.name);
let _ = writeln!(out, "0 {v}");
}
SolSuffixValues::ProblemReal(v) => {
write_suffix_header(out, target_bits | 0x4, 1, &s.name);
let _ = writeln!(out, "0 {v:.17e}");
}
}
}
fn write_suffix_header(out: &mut String, kind: u32, nvalues: usize, name: &str) {
let namelen = name.len() + 1;
let _ = writeln!(out, "suffix {kind} {nvalues} {namelen} 0 0");
let _ = writeln!(out, "{name}");
}
pub fn write_sol_file(path: &Path, payload: &SolutionFile<'_>) -> std::io::Result<usize> {
let s = format_sol(payload);
std::fs::write(path, &s)?;
Ok(s.len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writes_basic_primal_dual_block() {
let payload = SolutionFile {
message: "POUNCE: SolveSucceeded",
x: &[1.0, 2.5, -0.5],
lambda: &[0.1, -0.2],
solve_result_num: 0,
suffixes: &[],
};
let s = format_sol(&payload);
assert!(s.starts_with("POUNCE: SolveSucceeded\n"));
assert!(s.contains("\nOptions\n0\n"));
assert!(s.contains("\n2\n2\n3\n3\n"), "counts missing:\n{s}");
assert!(
s.contains("1.00000000000000006e-1\n") || s.contains("1.0e-1\n"),
"lambda not present:\n{s}",
);
assert!(s.trim_end().ends_with("objno 0 0"));
}
#[test]
fn writes_real_var_suffix_sparse_trimming_zeros() {
let payload = SolutionFile {
message: "POUNCE-SENS",
x: &[0.0, 0.0],
lambda: &[],
solve_result_num: 0,
suffixes: &[SolSuffix {
name: "sens_sol_state_1".into(),
target: SolSuffixTarget::Var,
values: SolSuffixValues::Real(vec![0.0, 5.0, 0.0, 3.5]),
}],
};
let s = format_sol(&payload);
assert!(
s.contains("\nsuffix 4 2 17 0 0\nsens_sol_state_1\n"),
"missing suffix header:\n{s}",
);
assert!(s.contains("\n1 5.0"), "missing entry idx 1:\n{s}");
assert!(s.contains("\n3 3.5"), "missing entry idx 3:\n{s}");
assert!(!s.contains("\n0 0.0"), "zero entry was not trimmed:\n{s}",);
}
#[test]
fn writes_int_constraint_suffix() {
let payload = SolutionFile {
message: "msg",
x: &[],
lambda: &[],
solve_result_num: 0,
suffixes: &[SolSuffix {
name: "sens_init_constr".into(),
target: SolSuffixTarget::Con,
values: SolSuffixValues::Int(vec![0, 1, 2, 0]),
}],
};
let s = format_sol(&payload);
assert!(s.contains("\nsuffix 1 2 17 0 0\nsens_init_constr\n"), "{s}");
assert!(s.contains("\n1 1\n"));
assert!(s.contains("\n2 2\n"));
}
#[test]
fn writes_problem_real_suffix() {
let payload = SolutionFile {
message: "msg",
x: &[],
lambda: &[],
solve_result_num: 0,
suffixes: &[SolSuffix {
name: "wall_time".into(),
target: SolSuffixTarget::Problem,
values: SolSuffixValues::ProblemReal(0.0123),
}],
};
let s = format_sol(&payload);
assert!(s.contains("\nsuffix 7 1 10 0 0\nwall_time\n"), "{s}");
assert!(s.contains("0 1.23"));
}
#[test]
fn round_trip_through_nl_reader_suffix_parser() {
let payload = SolutionFile {
message: "m",
x: &[],
lambda: &[],
solve_result_num: 0,
suffixes: &[SolSuffix {
name: "foo".into(),
target: SolSuffixTarget::Var,
values: SolSuffixValues::Int(vec![1, 0, 3]),
}],
};
let s = format_sol(&payload);
assert!(s.contains("\nsuffix 0 2 4 0 0\nfoo\n"), "{s}");
}
}