#![forbid(unsafe_code)]
use std::collections::{BTreeMap, HashSet};
use std::fs;
use std::path::PathBuf;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use fsci_conformance::{ArmCounts, CompareLedger};
use fsci_interpolate::{polyroots, polyval_der, ratval};
use serde::Serialize;
const PACKET_ID: &str = "FSCI-P2C-007";
const ABS_TOL: f64 = 1.0e-9;
#[derive(Debug, Clone, Serialize)]
struct CaseDiff {
case_id: String,
pass: bool,
note: String,
}
#[derive(Debug, Clone, Serialize)]
struct DiffLog {
test_id: String,
category: String,
case_count: usize,
compared: BTreeMap<String, ArmCounts>,
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 poly_extras 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_interpolate_poly_extras() {
let start = Instant::now();
let mut diffs: Vec<CaseDiff> = Vec::new();
let mut ledger = CompareLedger::new(
"diff_interpolate_poly_extras",
&["polyval_der", "ratval", "polyroots"],
);
let mut check = |arm: &str, id: &str, ok: bool, note: String| {
ledger.compared(arm, id, ok);
diffs.push(CaseDiff {
case_id: id.into(),
pass: ok,
note,
});
};
{
let coeffs = vec![1.0_f64, -5.0, 6.0];
let derivs = polyval_der(&coeffs, 4.0, 2);
check(
"polyval_der",
"polyval_der_quadratic_at_x4_returns_3_values",
derivs.len() == 3
&& (derivs[0] - 2.0).abs() < ABS_TOL
&& (derivs[1] - 3.0).abs() < ABS_TOL
&& (derivs[2] - 2.0).abs() < ABS_TOL,
format!("derivs={derivs:?}"),
);
}
{
let coeffs = vec![7.0_f64];
let derivs = polyval_der(&coeffs, 3.0, 2);
check(
"polyval_der",
"polyval_der_constant_higher_order_zero",
derivs.len() == 3
&& (derivs[0] - 7.0).abs() < ABS_TOL
&& derivs[1].abs() < ABS_TOL
&& derivs[2].abs() < ABS_TOL,
format!("derivs={derivs:?}"),
);
}
{
let coeffs = vec![1.0_f64, 0.0, 0.0, 0.0];
let derivs = polyval_der(&coeffs, 2.0, 3);
check(
"polyval_der",
"polyval_der_cubic_x3_at_x2",
derivs.len() == 4
&& (derivs[0] - 8.0).abs() < ABS_TOL
&& (derivs[1] - 12.0).abs() < ABS_TOL
&& (derivs[2] - 12.0).abs() < ABS_TOL
&& (derivs[3] - 6.0).abs() < ABS_TOL,
format!("derivs={derivs:?}"),
);
}
{
let derivs = polyval_der(&[], 1.0, 2);
check(
"polyval_der",
"polyval_der_empty_returns_zeros",
derivs.len() == 3 && derivs.iter().all(|&v| v == 0.0),
format!("derivs={derivs:?}"),
);
}
{
let p = vec![1.0_f64, 2.0]; let q = vec![1.0_f64, 1.0]; check(
"ratval",
"ratval_simple_at_zero",
(ratval(&p, &q, 0.0) - 1.0).abs() < ABS_TOL,
String::new(),
);
check(
"ratval",
"ratval_simple_at_one",
(ratval(&p, &q, 1.0) - 1.5).abs() < ABS_TOL,
String::new(),
);
check(
"ratval",
"ratval_simple_at_two",
(ratval(&p, &q, 2.0) - 5.0 / 3.0).abs() < ABS_TOL,
String::new(),
);
}
{
let p = vec![1.0_f64, 0.0];
let q = vec![0.0_f64, 0.0]; let v = ratval(&p, &q, 1.0);
check(
"ratval",
"ratval_zero_denom_nan",
v.is_nan(),
format!("v={v}"),
);
}
{
let p = vec![10.0_f64, 0.0, 1.0]; let q = vec![5.0_f64];
check(
"ratval",
"ratval_constant_denom",
(ratval(&p, &q, 3.0) - 3.8).abs() < ABS_TOL,
String::new(),
);
}
{
let roots = polyroots(&[1.0_f64, -3.0]);
check(
"polyroots",
"polyroots_linear",
roots.len() == 1 && (roots[0] - 3.0).abs() < ABS_TOL,
format!("roots={roots:?}"),
);
}
{
let roots = polyroots(&[1.0_f64, -5.0, 6.0]);
let set: HashSet<i64> = roots.iter().map(|r| r.round() as i64).collect();
let expected: HashSet<i64> = [2_i64, 3].iter().copied().collect();
check(
"polyroots",
"polyroots_quadratic_distinct",
roots.len() == 2 && set == expected,
format!("roots={roots:?}"),
);
}
{
let roots = polyroots(&[1.0_f64, 0.0, 1.0]);
check(
"polyroots",
"polyroots_no_real_roots_empty",
roots.is_empty(),
format!("roots={roots:?}"),
);
}
{
let roots = polyroots(&[5.0_f64]);
check(
"polyroots",
"polyroots_constant_no_roots",
roots.is_empty(),
format!("roots={roots:?}"),
);
}
let all_pass = diffs.iter().all(|d| d.pass);
let log = DiffLog {
test_id: "diff_interpolate_poly_extras".into(),
category: "fsci_interpolate::{polyval_der, ratval, polyroots} coverage".into(),
case_count: diffs.len(),
compared: ledger.counts().clone(),
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!("poly_extras mismatch: {} — {}", d.case_id, d.note);
}
}
assert!(
all_pass,
"poly extras coverage failed: {} cases",
diffs.len()
);
ledger.finish(4);
}