#![forbid(unsafe_code)]
use std::collections::BTreeMap;
use std::fs;
use std::path::PathBuf;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use fsci_conformance::{ArmCounts, CompareLedger};
use fsci_opt::cobyla;
use serde::Serialize;
const PACKET_ID: &str = "FSCI-P2C-007";
const TOL: f64 = 5.0e-2;
#[derive(Debug, Clone, Serialize)]
struct CaseDiff {
case_id: String,
abs_diff: f64,
pass: bool,
}
#[derive(Debug, Clone, Serialize)]
struct DiffLog {
test_id: String,
category: String,
case_count: usize,
compared: BTreeMap<String, ArmCounts>,
max_abs_diff: f64,
pass: bool,
timestamp_ms: u128,
duration_ns: u128,
cases: Vec<CaseDiff>,
}
fn output_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("fixtures/artifacts/{PACKET_ID}/diff"))
}
fn ensure_output_dir() {
fs::create_dir_all(output_dir()).expect("create cobyla diff dir");
}
fn timestamp_ms() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_millis())
}
fn emit_log(log: &DiffLog) {
ensure_output_dir();
let path = output_dir().join(format!("{}.json", log.test_id));
let json = serde_json::to_string_pretty(log).expect("serialize log");
fs::write(path, json).expect("write log");
}
#[test]
fn diff_opt_cobyla() {
let start = Instant::now();
let mut diffs: Vec<CaseDiff> = Vec::new();
let mut max_overall = 0.0_f64;
let mut ledger = CompareLedger::new("diff_opt_cobyla", &["cobyla"]);
let f1 = |x: &[f64]| x[0] * x[0] + x[1] * x[1];
let c1: [fn(&[f64]) -> f64; 1] = [|x: &[f64]| x[0] + x[1] - 1.0];
let res1 = cobyla(f1, &[0.0_f64, 0.0], &c1, 500, 0.5).ok();
if let Some((f_star1, f_at1)) =
ledger.pair("cobyla", "cobyla_linear", Some(0.5), res1.map(|r| f1(&r.x)))
{
let abs_d1 = (f_at1 - f_star1).abs();
max_overall = max_overall.max(abs_d1);
ledger.compared("cobyla", "cobyla_linear", abs_d1 <= TOL);
diffs.push(CaseDiff {
case_id: "cobyla_linear".into(),
abs_diff: abs_d1,
pass: abs_d1 <= TOL,
});
}
let f2 = |x: &[f64]| (x[0] - 2.0).powi(2) + (x[1] - 1.0).powi(2);
let c2: [fn(&[f64]) -> f64; 2] = [|x: &[f64]| x[0], |x: &[f64]| x[1]];
let res2 = cobyla(f2, &[1.0_f64, 0.5], &c2, 500, 0.5).ok();
if let Some((f_star2, f_at2)) = ledger.pair(
"cobyla",
"cobyla_two_pos",
Some(0.0),
res2.map(|r| f2(&r.x)),
) {
let f_at2 = f_at2 - f_star2;
max_overall = max_overall.max(f_at2);
ledger.compared("cobyla", "cobyla_two_pos", f_at2 <= TOL);
diffs.push(CaseDiff {
case_id: "cobyla_two_pos".into(),
abs_diff: f_at2,
pass: f_at2 <= TOL,
});
}
let all_pass = diffs.iter().all(|d| d.pass);
let log = DiffLog {
test_id: "diff_opt_cobyla".into(),
category: "fsci_opt::cobyla property test".into(),
case_count: diffs.len(),
compared: ledger.counts().clone(),
max_abs_diff: max_overall,
pass: all_pass,
timestamp_ms: timestamp_ms(),
duration_ns: start.elapsed().as_nanos(),
cases: diffs.clone(),
};
emit_log(&log);
for d in &diffs {
if !d.pass {
eprintln!("cobyla mismatch: {} abs_diff={}", d.case_id, d.abs_diff);
}
}
assert!(
all_pass,
"cobyla conformance failed: {} cases, max_diff={}",
diffs.len(),
max_overall
);
ledger.finish(2);
}