use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
use pounce_cli::solve_report::SolveReport;
fn pounce_exe() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_pounce"))
}
fn fixture(name: &str) -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push("tests");
p.push("fixtures");
p.push(name);
p
}
fn tmp_path(suffix: &str) -> PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let mut p = std::env::temp_dir();
p.push(format!(
"pounce_falseinfeas_{}_{}_{suffix}",
std::process::id(),
n
));
p
}
fn solve(fixture_name: &str, extra: &[&str]) -> SolveReport {
let json_path = tmp_path(&format!("{fixture_name}.json"));
let sol_path = tmp_path(&format!("{fixture_name}.sol"));
let mut cmd = Command::new(pounce_exe());
cmd.arg(fixture(fixture_name))
.arg(&sol_path)
.arg("--json-output")
.arg(&json_path);
for o in extra {
cmd.arg(o);
}
let _ = cmd.status().expect("spawn pounce");
let text = std::fs::read_to_string(&json_path).expect("read json report");
let _ = std::fs::remove_file(&json_path);
let _ = std::fs::remove_file(&sol_path);
serde_json::from_str(&text).expect("deserialize SolveReport")
}
const HS13_IPOPT: f64 = 0.984_928_715_337_977_43;
#[test]
fn hs13_from_remote_start_is_not_reported_infeasible() {
let report = solve("hs13_bigstart.nl", &[]);
let code = report.solution.solve_result_num;
assert!(
!(200..300).contains(&code),
"HS13 reported INFEASIBLE (solve_result_num={code}, status={:?}) — it is \
feasible, with f* = 1 and Ipopt reaching {HS13_IPOPT} from this same \
start. The returned point had constraint violation 0.51 and was not a \
stationary point of the infeasibility (‖∇θ‖ = 1.4, no active bound)",
report.solution.status,
);
}
#[test]
fn hs13_from_remote_start_reaches_the_optimum() {
let report = solve("hs13_bigstart.nl", &[]);
let obj = report.solution.objective;
let rel = (obj - HS13_IPOPT).abs() / HS13_IPOPT.abs();
assert!(
rel < 1e-6,
"HS13 from the remote start: got {obj}, expected Ipopt's {HS13_IPOPT} \
(rel err {rel:.3e})",
);
}
#[test]
fn hs13_returned_point_is_feasible_in_user_units() {
let report = solve("hs13_bigstart.nl", &[]);
let x = &report.solution.x;
assert_eq!(x.len(), 2, "HS13 has two variables");
let (x1, x2) = (x[0], x[1]);
let c = (1.0 - x1).powi(3) - x2;
let violation = (-c).max(0.0);
assert!(
violation < 1e-4,
"returned point x = ({x1}, {x2}) violates (1-x1)^3 - x2 >= 0 by \
{violation} — pre-fix POUNCE stopped at (1.5698, 0.31744), a violation \
of 0.51, and called it infeasible",
);
assert!(x1 >= -1e-8 && x2 >= -1e-8, "bounds violated: ({x1}, {x2})");
}
#[test]
fn hs13_neither_scaling_path_reports_infeasible() {
const HS13_STAR: f64 = 1.0;
for opts in [vec![], vec!["nlp_scaling_method=none"]] {
let r = solve("hs13_bigstart.nl", &opts);
let code = r.solution.solve_result_num;
assert!(
!(200..300).contains(&code),
"HS13 reported INFEASIBLE with opts {opts:?} \
(solve_result_num={code}, status={:?})",
r.solution.status,
);
let obj = r.solution.objective;
assert!(
(obj - HS13_STAR).abs() < 0.05,
"HS13 with opts {opts:?}: objective {obj} is not near the published \
f* = {HS13_STAR}",
);
}
}
#[test]
fn feasible_models_with_a_feasible_start_are_not_reported_infeasible() {
for model in ["feasible_x0_extreme_row.nl", "feasible_x0_wide_scale.nl"] {
let report = solve(
model,
&["solver_selection=nlp", "presolve=no", "print_level=0"],
);
let code = report.solution.solve_result_num;
assert!(
!(200..300).contains(&code),
"{model} is feasible — its own starting point satisfies every \
constraint, and POUNCE's convex QP route solves it to optimality — \
but the NLP path reported the AMPL infeasible band \
(solve_result_num={code}, status={:?})",
report.solution.status,
);
}
}
#[test]
fn the_convex_route_still_solves_both_shapes() {
for model in ["feasible_x0_extreme_row.nl", "feasible_x0_wide_scale.nl"] {
let report = solve(model, &["print_level=0"]);
let code = report.solution.solve_result_num;
assert_eq!(
code, 0,
"{model} is a convex QP the auto-route solves; got \
solve_result_num={code}, status={:?}",
report.solution.status,
);
}
}
const CRESC4_STAR: f64 = 0.871_897_6;
#[test]
fn cresc4_is_not_reported_infeasible() {
let report = solve("cresc4.nl", &[]);
let code = report.solution.solve_result_num;
assert!(
!(200..300).contains(&code),
"cresc4 reported INFEASIBLE (solve_result_num={code}, status={:?}) — it \
is feasible, with f* = {CRESC4_STAR} reached by LOQO, SNOPT and Ipopt \
(71 iterations). Pre-fix POUNCE stopped at a point with constraint \
violation 0.51 and objective ~2e-09, and the MC64 rung of the retry \
ladder re-traced the same trajectory and 'corroborated' it",
report.solution.status,
);
let obj = report.solution.objective;
let rel = (obj - CRESC4_STAR).abs() / CRESC4_STAR;
assert!(
rel < 1e-6,
"cresc4: got objective {obj}, expected the known optimum {CRESC4_STAR} \
(rel err {rel:.3e})",
);
}
#[test]
fn cresc4_needs_the_barrier_rung_not_the_scaling_rung() {
let scaling_only = solve("cresc4.nl", &["infeasibility_mu_strategy_retry=no"]);
assert!(
(200..300).contains(&scaling_only.solution.solve_result_num),
"cresc4 with only the MC64 rung is expected to still report infeasible \
(that is gh #524); it returned status={:?}. If this now passes, the \
underlying monotone-mu failure may have been fixed properly — good, but \
re-derive whether the barrier rung is still carrying this case",
scaling_only.solution.status,
);
for opts in [
vec!["mu_strategy=adaptive"],
vec!["nlp_scaling_method=none"],
] {
let r = solve("cresc4.nl", &opts);
assert!(
!(200..300).contains(&r.solution.solve_result_num),
"cresc4 with {opts:?} should solve; got status={:?}",
r.solution.status,
);
}
}
#[test]
fn ladder_rungs_are_skipped_when_the_baseline_already_has_them() {
let report = solve(
"infeasible_equalities.nl",
&["mu_strategy=adaptive", "feral_scaling=mc64"],
);
assert!(
(200..300).contains(&report.solution.solve_result_num),
"genuinely infeasible problem must still report infeasible with both \
ladder knobs already set at baseline; got status={:?}",
report.solution.status,
);
}
#[test]
fn genuinely_infeasible_problem_is_still_detected() {
let report = solve("infeasible_equalities.nl", &[]);
let code = report.solution.solve_result_num;
assert!(
(200..300).contains(&code),
"genuinely infeasible problem was NOT detected (solve_result_num={code}, \
status={:?}). The descent probe must not suppress correct verdicts — \
before this fix POUNCE reported local infeasibility here in ~25 \
iterations",
report.solution.status,
);
}