use crate::error::{PhopError, Result};
use oxieml::numeric_verified::{find_root_verified, RootOpts};
use oxieml::{
EmlTree, EvalCtx, IntegrateResult, IntervalLO, LimitPoint, LimitResult, RootCertificate,
};
#[derive(Debug, Clone)]
pub struct Analysis {
pub latex: String,
pub derivative: String,
pub antiderivative: Option<String>,
pub maclaurin: Option<String>,
pub limit_pos_inf: Option<f64>,
}
#[must_use]
pub fn analyze(tree: &EmlTree, wrt: usize, series_order: usize) -> Analysis {
let f = tree.lower().simplify();
let derivative = f.grad(wrt).simplify().to_latex();
let antiderivative = match f.integrate(wrt) {
IntegrateResult::Closed(g) => Some(g.simplify().to_latex()),
IntegrateResult::Unsupported => None,
};
let maclaurin = f
.maclaurin(wrt, series_order)
.ok()
.map(|s| s.simplify().to_latex());
let limit_pos_inf = match f.limit(wrt, LimitPoint::PosInf) {
LimitResult::Finite(v) => Some(v),
_ => None,
};
Analysis {
latex: f.to_latex(),
derivative,
antiderivative,
maclaurin,
limit_pos_inf,
}
}
pub fn certified_root(
tree: &EmlTree,
wrt: usize,
others: &[f64],
lo: f64,
hi: f64,
) -> Result<RootCertificate> {
let expr = tree.lower();
let ctx = EvalCtx::new(others);
find_root_verified(&expr, wrt, &ctx, lo, hi, &RootOpts::default())
.map_err(|e| PhopError::Symbolic(format!("certified root finding failed: {e:?}")))
}
#[must_use]
pub fn certified_range(tree: &EmlTree, domain: &[(f64, f64)]) -> (f64, f64) {
let ivs: Vec<IntervalLO> = domain
.iter()
.map(|&(lo, hi)| IntervalLO::new(lo, hi))
.collect();
let r = tree.lower().eval_interval(&ivs);
(r.lo, r.hi)
}
#[cfg(test)]
mod tests {
use super::*;
use oxieml::RootStatus;
#[test]
fn analyze_exp_derivative_and_integral() {
let tree = EmlTree::eml(&EmlTree::var(0), &EmlTree::one());
let f = tree.lower();
let d = f.grad(0);
assert!(
(d.eval(&[0.7]) - 0.7_f64.exp()).abs() < 1e-6,
"d/dx mismatch: {}",
d.eval(&[0.7])
);
let integ_ok = match f.integrate(0) {
IntegrateResult::Closed(g) => (g.grad(0).eval(&[0.4]) - 0.4_f64.exp()).abs() < 1e-5,
IntegrateResult::Unsupported => false,
};
assert!(integ_ok, "expected a closed antiderivative for exp");
let a = analyze(&tree, 0, 4);
assert!(!a.latex.is_empty() && !a.derivative.is_empty());
}
#[test]
fn certified_range_encloses_exp() {
let tree = EmlTree::eml(&EmlTree::var(0), &EmlTree::one());
let (lo, hi) = certified_range(&tree, &[(0.0, 1.0)]);
assert!(lo <= 1.0 + 1e-9, "lower bound {lo} should be ≤ 1");
assert!(
hi >= std::f64::consts::E - 1e-9,
"upper bound {hi} should be ≥ e"
);
}
#[test]
fn certified_root_brackets_known_zero() {
let tree = EmlTree::eml(&EmlTree::var(0), &EmlTree::const_val(std::f64::consts::E));
let cert = certified_root(&tree, 0, &[], -1.0, 1.0).expect("verifier ran");
assert!(
matches!(cert.status, RootStatus::UniqueExists),
"expected a certified unique root, got {:?}",
cert.status
);
assert!(
cert.enclosure.contains(0.0),
"enclosure must bracket the root x0 = 0"
);
}
}