use std::path::PathBuf;
use std::process::Command;
fn pounce_exe() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_pounce"))
}
struct Run {
stdout: String,
values: Vec<f64>,
}
fn run(tag: &str, opts: &[&str]) -> Run {
let dir = std::env::temp_dir().join(format!("pounce_lin_eq_reduction_{tag}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("scratch dir");
let mut fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
fixture.push("tests/fixtures/linear_eq_aggregation.nl");
let nl = dir.join("m.nl");
std::fs::copy(&fixture, &nl).expect("copy fixture");
let mut cmd = Command::new(pounce_exe());
cmd.current_dir(&dir).arg("m").arg("-AMPL");
for o in opts {
cmd.arg(o);
}
let out = cmd.output().expect("run pounce");
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
let sol = std::fs::read_to_string(dir.join("m.sol")).expect("read .sol");
let numeric: Vec<f64> = sol
.lines()
.take_while(|l| !l.starts_with("objno"))
.filter_map(|l| l.trim().parse::<f64>().ok())
.collect();
assert!(
numeric.len() >= 5,
"expected 2 duals + 3 primals in the .sol body, got {numeric:?}\n{sol}"
);
let values = numeric[numeric.len() - 5..].to_vec();
Run { stdout, values }
}
#[test]
fn the_reduction_reports_what_it_removed() {
let on = run("on", &["presolve=yes", "presolve_linear_eq_reduction=yes"]);
assert!(
on.stdout.contains("eliminated 1 columns"),
"no reduction summary on stdout:\n{}",
on.stdout
);
assert!(
on.stdout.contains("Optimal Solution Found"),
"{}",
on.stdout
);
}
#[test]
fn the_option_is_off_by_default() {
let on = run("default", &["presolve=yes"]);
assert!(
!on.stdout.contains("linear-equality reduction"),
"the reduction ran without being asked for:\n{}",
on.stdout
);
}
#[test]
fn the_sol_file_keeps_the_original_shape_and_values() {
let base = run("base", &[]);
let reduced = run(
"reduced",
&["presolve=yes", "presolve_linear_eq_reduction=yes"],
);
assert_eq!(
base.values.len(),
reduced.values.len(),
"the .sol body changed length: base={:?} reduced={:?}",
base.values,
reduced.values
);
for (k, (b, r)) in base.values.iter().zip(reduced.values.iter()).enumerate() {
assert!(
(b - r).abs() < 1e-6,
".sol entry {k} diverged: base={b} reduced={r}"
);
}
assert!(
reduced.values[0].abs() > 1e-6,
"the consumed row came back with a zero multiplier: {:?}",
reduced.values
);
let (x2, x1, x0) = (reduced.values[2], reduced.values[3], reduced.values[4]);
assert!(
(x0 - 2.0 * x1).abs() < 1e-9,
"the eliminated row is violated in the reported point: {x0} != 2 * {x1}"
);
assert!(
(x1 * x1 + x2 * x2 - 2.0).abs() < 1e-6,
"the surviving row is violated: x1={x1} x2={x2}"
);
}