use std::fmt::Debug;
use crate::error::{SanosError, SanosResult};
#[derive(Debug, Clone, Copy)]
pub struct SanityCase {
pub maturity: f64,
pub a: f64,
pub b: f64,
}
#[derive(Debug, Clone)]
pub struct SanityReport {
pub case: SanityCase,
pub value: f64,
pub issues: Vec<&'static str>,
}
pub trait YModel: Send + Sync + Debug {
fn call(&self, maturity: f64, a: f64, b: f64) -> SanosResult<f64>;
fn linear_call(&self, maturity: f64, a: f64, b: f64) -> SanosResult<f64> {
if !maturity.is_finite() {
return Err(SanosError::NonFinite {
field: "maturity",
value: maturity,
});
}
if maturity <= 0.0 {
return Err(SanosError::InvalidBound {
field: "maturity",
value: maturity,
min: f64::MIN_POSITIVE,
max: f64::INFINITY,
});
}
if !a.is_finite() {
return Err(SanosError::NonFinite {
field: "a",
value: a,
});
}
if !b.is_finite() {
return Err(SanosError::NonFinite {
field: "b",
value: b,
});
}
if a <= 0.0 {
return Err(SanosError::InvalidBound {
field: "a",
value: a,
min: f64::MIN_POSITIVE,
max: f64::INFINITY,
});
}
if b < 0.0 {
return Err(SanosError::InvalidBound {
field: "b",
value: b,
min: 0.0,
max: f64::INFINITY,
});
}
Ok((a - b).max(0.0))
}
fn sanity_check(&self, cases: &[SanityCase], tol: f64) -> SanosResult<Vec<SanityReport>> {
if !tol.is_finite() || tol < 0.0 {
return Err(SanosError::InvalidBound {
field: "tol",
value: tol,
min: 0.0,
max: f64::INFINITY,
});
}
let mut reports = Vec::with_capacity(cases.len());
for &case in cases {
let kernel_value = self.call(case.maturity, case.a, case.b)?;
let mut issues: Vec<&'static str> = Vec::new();
if !kernel_value.is_finite() {
issues.push("non_finite_value");
}
if kernel_value < -tol {
issues.push("below_zero");
}
if kernel_value > case.a + tol {
issues.push("above_a");
}
if case.b == 0.0 && (kernel_value - case.a).abs() > tol.max(tol * case.a.abs().max(1.0))
{
issues.push("unit_mean_violation");
}
reports.push(SanityReport {
case,
value: kernel_value,
issues,
});
}
Ok(reports)
}
}