outram-foam-basic-lib 0.1.6

OpenFOAM primitive functions translated to Rust -- an independent OUTRAM PARK fork, not the official OpenFOAM software
Documentation
// SPDX-License-Identifier: GPL-3.0-only
// Copyright (C) 2026 OUTRAM PARK contributors
// Derived from OpenFOAM (www.openfoam.com)
// Copyright (C) 2004-2023 OpenFOAM Foundation
// Copyright (C) 2016-2023 OpenCFD Ltd.
//
// This file is part of OUTRAM PARK.
//
// OUTRAM PARK is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at your
// option) any later version.
//
// OUTRAM PARK is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with OUTRAM PARK.  If not, see <https://www.gnu.org/licenses/>.

use petir::mathf::RealMath;
/// Inverse error function.
///
/// Returns `x` such that `erf(x) = y`.  Valid domain: `y ∈ (−1, 1)`.
/// Behaviour outside that domain is undefined.
///
/// Algorithm: Winitzki (2008) approximation with `a = 0.147`, which limits
/// the maximum relative error to O(10⁻⁴). Matches `Foam::Math::erfInv`.
///
/// Reference: S. Winitzki, "A handy approximation for the error function and
/// its inverse", preprint 2008.
pub fn erf_inv(y: f64) -> f64 {
    const A: f64 = 0.147;
    let pi = std::f64::consts::PI;

    let k = 2.0 / (A * pi) + 0.5 * (1.0 - y * y).r_ln();
    let h = (1.0 - y * y).r_ln() / A;

    let x = (-k + (k * k - h).sqrt()).sqrt();

    if y < 0.0 {
        -x
    } else {
        x
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // Reference values from scipy.special.erfinv
    #[test]
    fn known_values() {
        // erf_inv(0) = 0
        assert!(erf_inv(0.0).abs() < 1e-10);

        // erf_inv(erf(x)) ≈ x for several x (round-trip via libm erf)
        for &y_target in &[0.1_f64, 0.5, 0.8, 0.95, -0.3, -0.7] {
            // Get x from a trusted erf value:
            // We don't have erf available here without FFI, so we verify
            // self-consistency: erf_inv should be odd.
            let pos = erf_inv(y_target.abs());
            let neg = erf_inv(-y_target.abs());
            assert!(
                (pos + neg).abs() < 1e-14,
                "erf_inv not odd at |y|={}",
                y_target.abs()
            );
            assert!(pos > 0.0, "positive input gives positive output");
        }
    }

    #[test]
    fn monotone_and_positive() {
        let pts = [0.1, 0.3, 0.5, 0.7, 0.9, 0.99];
        let mut prev = erf_inv(pts[0]);
        for &y in &pts[1..] {
            let cur = erf_inv(y);
            assert!(cur > prev, "erf_inv not monotone: {} < {}", cur, prev);
            prev = cur;
        }
    }
}