sciforge 0.0.3

A comprehensive scientific computing library in pure Rust with zero dependencies
Documentation
pub fn signal_detection_d_prime(hit_rate: f64, false_alarm_rate: f64) -> f64 {
    fn inv_normal(p: f64) -> f64 {
        let p_clamped = p.clamp(1e-10, 1.0 - 1e-10);
        let a = [
            -3.969683028665376e1,
            2.209460984245205e2,
            -2.759285104469687e2,
            1.38357751867269e2,
            -3.066479806614716e1,
            2.506628277459239e0,
        ];
        let b = [
            -5.447609879822406e1,
            1.615858368580409e2,
            -1.556989798598866e2,
            6.680131188771972e1,
            -1.328068155288572e1,
        ];
        let c = [
            -7.784894002430293e-3,
            -3.223964580411365e-1,
            -2.400758277161838e0,
            -2.549732539343734e0,
            4.374664141464968e0,
            2.938163982698783e0,
        ];
        let d = [
            7.784695709041462e-3,
            3.224671290700398e-1,
            2.445134137142996e0,
            3.754408661907416e0,
        ];
        let p_low = 0.02425;
        let p_high = 1.0 - p_low;
        if p_clamped < p_low {
            let q = (-2.0 * p_clamped.ln()).sqrt();
            (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
                / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0)
        } else if p_clamped <= p_high {
            let q = p_clamped - 0.5;
            let r = q * q;
            (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q
                / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1.0)
        } else {
            let q = (-2.0 * (1.0 - p_clamped).ln()).sqrt();
            -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
                / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0)
        }
    }
    inv_normal(hit_rate) - inv_normal(false_alarm_rate)
}

pub fn honest_signal_handicap(quality: f64, cost_per_signal: f64, benefit_per_signal: f64) -> f64 {
    if cost_per_signal <= 0.0 {
        return 0.0;
    }
    quality * benefit_per_signal / cost_per_signal
}

pub fn alarm_call_kin_selection(
    relatedness: f64,
    benefit_to_kin: f64,
    cost_to_caller: f64,
) -> bool {
    relatedness * benefit_to_kin > cost_to_caller
}

pub fn mate_choice_threshold(
    quality_assessed: f64,
    search_cost: f64,
    encounters: usize,
    threshold: f64,
) -> bool {
    quality_assessed >= threshold - search_cost * encounters as f64
}

pub fn ritualized_contest(size_a: f64, size_b: f64, motivation_a: f64, motivation_b: f64) -> f64 {
    let resource_holding_a = size_a * motivation_a;
    let resource_holding_b = size_b * motivation_b;
    resource_holding_a / (resource_holding_a + resource_holding_b).max(1e-30)
}

pub fn hawk_dove_contest(v: f64, c: f64, p_hawk: f64) -> (f64, f64) {
    let hawk_payoff = p_hawk * (v - c) / 2.0 + (1.0 - p_hawk) * v;
    let dove_payoff = p_hawk * 0.0 + (1.0 - p_hawk) * v / 2.0;
    (hawk_payoff, dove_payoff)
}

pub fn producer_scrounger_frequency(
    producer_payoff: f64,
    scrounger_payoff: f64,
    p_producer: f64,
    selection_strength: f64,
) -> f64 {
    let fitness_p = (selection_strength * producer_payoff).exp();
    let fitness_s = (selection_strength * scrounger_payoff).exp();
    p_producer * fitness_p / (p_producer * fitness_p + (1.0 - p_producer) * fitness_s)
}

pub fn territory_size_optimal(energy_gain_rate: f64, defense_cost_per_area: f64) -> f64 {
    (energy_gain_rate / defense_cost_per_area.max(1e-30)).sqrt()
}

pub fn dominance_index(wins: f64, total_interactions: f64) -> f64 {
    wins / total_interactions.max(1e-30)
}