sim-lib-numbers-quad 0.1.0

SIM workspace package for sim lib numbers quad.
Documentation
use std::sync::Arc;

use sim_kernel::{Args, Cx, DefaultFactory, EagerPolicy, Error, Symbol, Value};
use sim_lib_numbers_func::Func;
use sim_lib_numbers_numeric::NumericNumbersLib;

use crate::QuadNumbersLib;

fn test_cx() -> Cx {
    let mut cx = Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory));
    cx.load_lib(&sim_lib_numbers_arith::NumbersArithmeticLib::new())
        .unwrap();
    cx.load_lib(&sim_lib_numbers_f64::F64NumbersLib::new())
        .unwrap();
    cx.load_lib(&NumericNumbersLib::new()).unwrap();
    cx.load_lib(&QuadNumbersLib::new()).unwrap();
    cx
}

fn f64_value(cx: &mut Cx, value: f64) -> Value {
    cx.factory()
        .number_literal(Symbol::qualified("numbers", "f64"), value.to_string())
        .unwrap()
}

fn value_to_f64(cx: &mut Cx, value: &Value) -> f64 {
    value.object().display(cx).unwrap().parse::<f64>().unwrap()
}

fn unary_native<F>(f: F) -> Func
where
    F: Fn(f64) -> f64 + Send + Sync + 'static,
{
    Func::native(
        vec![Symbol::new("x")],
        Arc::new(move |cx, args| {
            let [x] = args else {
                return Err(Error::Eval("expected one arg".to_owned()));
            };
            let x = value_to_f64(cx, x);
            Ok(f64_value(cx, f(x)))
        }),
    )
}

#[test]
fn differentiators_hit_expected_value() {
    let mut cx = test_cx();
    let func = cx
        .factory()
        .opaque(Arc::new(unary_native(f64::sin)))
        .unwrap();
    for method in [
        "forward",
        "backward",
        "central-3",
        "central-5",
        "richardson",
    ] {
        let method_value = cx.factory().symbol(Symbol::new(method)).unwrap();
        let h = f64_value(&mut cx, 1.0e-4);
        let options = cx
            .factory()
            .table(vec![
                (Symbol::new(":method"), method_value),
                (Symbol::new(":h"), h),
            ])
            .unwrap();
        let var = cx.factory().symbol(Symbol::new("x")).unwrap();
        let point = f64_value(&mut cx, 0.0);
        let out = cx
            .call_function(
                &Symbol::new("numeric-diff"),
                Args::new(vec![func.clone(), var, point, options]),
            )
            .unwrap();
        let value = value_to_f64(&mut cx, &out);
        let tol = if method == "forward" || method == "backward" {
            2.0e-4
        } else {
            1.0e-6
        };
        assert!((value - 1.0).abs() < tol, "{method} -> {value}");
    }
}

#[test]
fn quadratures_integrate_sine_to_two() {
    let mut cx = test_cx();
    let func = cx
        .factory()
        .opaque(Arc::new(unary_native(f64::sin)))
        .unwrap();
    for method in [
        "trapezoid",
        "simpson",
        "romberg",
        "gauss-legendre-8",
        "gauss-legendre-16",
        "gauss-legendre-32",
        "adaptive-gauss-kronrod",
    ] {
        let mut entries = vec![(
            Symbol::new(":method"),
            cx.factory().symbol(Symbol::new(method)).unwrap(),
        )];
        if method == "trapezoid" || method == "simpson" || method == "romberg" {
            entries.push((Symbol::new(":n"), f64_value(&mut cx, 128.0)));
        }
        if method == "adaptive-gauss-kronrod" || method == "romberg" {
            entries.push((Symbol::new(":tol"), f64_value(&mut cx, 1.0e-10)));
        }
        let options = cx.factory().table(entries).unwrap();
        let symbol = if method == "adaptive-gauss-kronrod" {
            Symbol::new("integrate-adapt")
        } else {
            Symbol::new("integrate")
        };
        let var = cx.factory().symbol(Symbol::new("x")).unwrap();
        let lo = f64_value(&mut cx, 0.0);
        let hi = f64_value(&mut cx, std::f64::consts::PI);
        let out = cx
            .call_function(&symbol, Args::new(vec![func.clone(), var, lo, hi, options]))
            .unwrap();
        let value = value_to_f64(&mut cx, &out);
        let tol = match method {
            "trapezoid" => 2.0e-4,
            "gauss-legendre-8" => 1.0e-7,
            _ => 1.0e-8,
        };
        assert!((value - 2.0).abs() < tol, "{method} -> {value}");
    }
}