sciforge 0.0.3

A comprehensive scientific computing library in pure Rust with zero dependencies
Documentation
use sciforge::hub::prelude::*;
use sciforge::hub::tools::validation::report_to_tsv;
use std::fs;
use std::path::Path;

fn run(domain: DomainType, function: &str, params: Vec<(&str, ParameterValue)>) -> RunOutput {
    let mut exp = Experiment::new(domain, function);
    for (key, value) in params {
        exp = exp.param(key, value);
    }
    ExperimentRunner::new()
        .run(&exp)
        .unwrap_or_else(|_| panic!("dispatch '{function}' failed"))
}

fn scalar(output: RunOutput) -> f64 {
    match output {
        RunOutput::Scalar(value) => value,
        other => panic!("expected scalar output, got {other:?}"),
    }
}

fn validation_cases() -> Vec<ValidationCase> {
    vec![
        ValidationCase::new(
            "physics_carnot_efficiency",
            DomainType::Physics,
            "carnot_efficiency",
            vec![("t_hot", 600.0), ("t_cold", 300.0)],
            0.5,
            1e-12,
            "Carnot theorem",
        ),
        ValidationCase::new(
            "chemistry_strong_acid_ph",
            DomainType::Chemistry,
            "ph_strong_acid",
            vec![("concentration", 0.01)],
            2.0,
            1e-12,
            "pH = -log10(c)",
        ),
        ValidationCase::new(
            "biology_michaelis_menten",
            DomainType::Biology,
            "michaelis_menten",
            vec![("s", 10.0), ("vmax", 100.0), ("km", 5.0)],
            100.0 * 10.0 / 15.0,
            1e-12,
            "Michaelis-Menten equation",
        ),
        ValidationCase::new(
            "astronomy_escape_velocity_earth",
            DomainType::Astronomy,
            "escape_velocity",
            vec![("mu", 3.986e14), ("r", 6.371e6)],
            11_186.0,
            2e-2,
            "Earth escape velocity reference",
        ),
        ValidationCase::new(
            "geology_half_life_from_lambda",
            DomainType::Geology,
            "half_life",
            vec![("lambda", std::f64::consts::LN_2 / 5730.0)],
            5730.0,
            1e-12,
            "Half-life definition",
        ),
        ValidationCase::new(
            "meteorology_relative_humidity_ratio",
            DomainType::Meteorology,
            "relative_humidity",
            vec![("e", 10.0), ("es", 20.0)],
            50.0,
            1e-12,
            "Relative humidity percentage definition",
        ),
        ValidationCase::new(
            "maths_uniform_cdf_midpoint",
            DomainType::Maths,
            "prob_uniform_cdf",
            vec![("x", 0.5), ("a", 0.0), ("b", 1.0)],
            0.5,
            1e-12,
            "Uniform CDF on [0,1]",
        ),
    ]
}

fn ensure_validation_artifacts(report: &ValidationReport) {
    let dir = Path::new("output/tests/validation");
    fs::create_dir_all(dir).unwrap();
    fs::write(dir.join("scientific_validation.md"), report.to_markdown()).unwrap();
    fs::write(dir.join("scientific_validation.csv"), report.to_csv()).unwrap();
    fs::write(dir.join("scientific_validation.tsv"), report_to_tsv(report)).unwrap();
    assert!(dir.join("scientific_validation.md").exists());
    assert!(dir.join("scientific_validation.csv").exists());
    assert!(dir.join("scientific_validation.tsv").exists());
}

#[test]
fn scientific_reference_validation_passes() {
    let cases = validation_cases();
    let report = run_validation(&cases);
    assert!(
        report.all_passed(),
        "reference validation failures: {:?}",
        report.failures()
    );
    assert_eq!(report.total(), cases.len());
    ensure_validation_artifacts(&report);
}

#[test]
fn constants_are_consistent_with_public_domain_exports() {
    const { assert!(constants::C == 299_792_458.0) };
    const { assert!(constants::G > 0.0) };
    const { assert!(constants::N_A > 6.0e23 - 1.0e20) };
    const { assert!((constants::ATM_TO_PASCAL - 101_325.0).abs() < 1e-12) };
    const { assert!(constants::EARTH_RADIUS > 6.0e6) };
    const { assert!(constants::COMPTON_WAVELENGTH > 0.0) };
}

#[test]
fn systematic_edge_cases_remain_finite() {
    let checks = vec![
        (
            DomainType::Physics,
            "time_dilation",
            vec![
                ("proper_time", ParameterValue::Scalar(0.0)),
                ("v", ParameterValue::Scalar(0.0)),
            ],
        ),
        (
            DomainType::Chemistry,
            "ideal_gas_pressure",
            vec![
                ("n", ParameterValue::Scalar(1.0)),
                ("t", ParameterValue::Scalar(273.15)),
                ("v", ParameterValue::Scalar(22.4)),
            ],
        ),
        (
            DomainType::Biology,
            "michaelis_menten",
            vec![
                ("s", ParameterValue::Scalar(0.0)),
                ("vmax", ParameterValue::Scalar(100.0)),
                ("km", ParameterValue::Scalar(5.0)),
            ],
        ),
        (
            DomainType::Astronomy,
            "kepler_period",
            vec![
                ("a", ParameterValue::Scalar(7.0e6)),
                ("mu", ParameterValue::Scalar(3.986e14)),
            ],
        ),
        (
            DomainType::Geology,
            "radioactive_decay",
            vec![
                ("n0", ParameterValue::Scalar(1.0)),
                ("lambda", ParameterValue::Scalar(1e-6)),
                ("t", ParameterValue::Scalar(0.0)),
            ],
        ),
        (
            DomainType::Meteorology,
            "barometric_formula",
            vec![
                ("p0", ParameterValue::Scalar(101_325.0)),
                ("m", ParameterValue::Scalar(0.028_964_4)),
                ("g", ParameterValue::Scalar(9.806_65)),
                ("h", ParameterValue::Scalar(0.0)),
                ("t", ParameterValue::Scalar(288.15)),
            ],
        ),
        (
            DomainType::Maths,
            "prob_normal_pdf",
            vec![
                ("x", ParameterValue::Scalar(0.0)),
                ("mu", ParameterValue::Scalar(0.0)),
                ("sigma", ParameterValue::Scalar(1.0)),
            ],
        ),
    ];

    for (domain, function, params) in checks {
        let value = scalar(run(domain, function, params));
        assert!(
            value.is_finite(),
            "{function} should stay finite on edge-case input"
        );
    }
}