Skip to main content

akar_function/scalar/
utils.rs

1//! Shared utility functions used across scalar function categories.
2
3use super::{REGEX_CACHE, RNG_STATE};
4
5/// Get a compiled regex from the cache, or compile and cache it.
6pub(crate) fn get_cached_regex(pattern: &str) -> Result<regex::Regex, String> {
7    let mut cache = REGEX_CACHE.lock().map_err(|e| format!("Regex cache lock error: {e}"))?;
8    if let Some(re) = cache.get(pattern) {
9        return Ok(re.clone());
10    }
11    let re = regex::Regex::new(pattern).map_err(|e| format!("Regex error: {e}"))?;
12    cache.insert(pattern.to_string(), re.clone());
13    Ok(re)
14}
15
16/// Get next random f64 in [0, 1) from the thread-local LCG.
17pub(crate) fn rng_next() -> f64 {
18    RNG_STATE.with(|state| {
19        let old = state.get();
20        let new = old.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
21        state.set(new);
22        (new >> 11) as f64 / (1u64 << 53) as f64
23    })
24}
25
26/// Log-Gamma function using Lanczos approximation.
27pub(crate) fn log_gamma(x: f64) -> f64 {
28    if x < 0.5 {
29        let pi = std::f64::consts::PI;
30        let reflection = pi / (pi * x).sin();
31        reflection.abs().ln() - log_gamma(1.0 - x)
32    } else {
33        let xm1 = x - 1.0;
34        let g = 7.0;
35        let c = [
36            0.999_999_999_999_809_9,
37            676.5203681218851,
38            -1259.1392167224028,
39            771.323_428_777_653_1,
40            -176.615_029_162_140_6,
41            12.507343278686905,
42            -0.13857109526572012,
43            9.984_369_578_019_572e-6,
44            1.5056327351493116e-7,
45        ];
46        let t = xm1 + g + 0.5;
47        let mut s = c[0];
48        for (i, &ci) in c[1..].iter().enumerate() {
49            s += ci / (xm1 + (i as f64) + 1.0);
50        }
51        let sqrt_2pi = (2.0 * std::f64::consts::PI).sqrt();
52        (sqrt_2pi * s).ln() + (xm1 + 0.5) * t.ln() - t
53    }
54}
55
56/// Lanczos approximation for Gamma(x) — computed via exp(log_gamma(x)).
57pub(crate) fn gamma_func(x: f64) -> f64 {
58    log_gamma(x).exp()
59}
60
61/// Set the thread-local RNG seed.
62pub fn set_rng_seed(seed: u64) {
63    RNG_STATE.with(|state| state.set(seed));
64}