sciforge 0.0.3

A comprehensive scientific computing library in pure Rust with zero dependencies
Documentation
pub fn optimal_diet_value(energy_gain: f64, handling_time: f64, encounter_rate: f64) -> f64 {
    let profitability = energy_gain / handling_time;
    profitability * encounter_rate
}

pub fn marginal_value_theorem(
    travel_time: f64,
    gain_curve: impl Fn(f64) -> f64,
    max_t: f64,
) -> f64 {
    let steps = 1000;
    let dt = max_t / steps as f64;
    let mut best_rate = f64::NEG_INFINITY;
    let mut best_t = 0.0;
    for i in 1..=steps {
        let t = i as f64 * dt;
        let rate = gain_curve(t) / (travel_time + t);
        if rate > best_rate {
            best_rate = rate;
            best_t = t;
        }
    }
    best_t
}

pub fn ideal_free_distribution(resource: &[f64], total_individuals: f64) -> Vec<f64> {
    let total_resource: f64 = resource.iter().sum();
    if total_resource <= 0.0 {
        return vec![0.0; resource.len()];
    }
    resource
        .iter()
        .map(|&r| total_individuals * r / total_resource)
        .collect()
}

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

pub fn ess_hawk_frequency(v: f64, c: f64) -> f64 {
    if c <= 0.0 {
        return 1.0;
    }
    (v / c).min(1.0)
}

pub fn tit_for_tat_payoff(r: f64, s: f64, t: f64, p: f64, opponent_cooperates: bool) -> f64 {
    if opponent_cooperates {
        r
    } else {
        s + (t - s) * (1.0 - p / p.max(1e-30))
    }
}

pub fn prey_choice_ranking(prey_types: &[(f64, f64)]) -> Vec<(usize, f64)> {
    let mut ranked: Vec<(usize, f64)> = prey_types
        .iter()
        .enumerate()
        .map(|(i, &(energy, handling))| (i, energy / handling))
        .collect();
    ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
    ranked
}

pub fn risk_sensitive_foraging(mean_gain: f64, variance: f64, risk_aversion: f64) -> f64 {
    mean_gain - risk_aversion * variance
}

pub fn central_place_foraging(
    distance: f64,
    load: f64,
    travel_cost_per_unit: f64,
    gain_per_load: f64,
) -> f64 {
    let travel_cost = 2.0 * distance * travel_cost_per_unit;
    let net = gain_per_load * load - travel_cost;
    net / (2.0 * distance + load)
}

pub fn producer_scrounger_game(p_freq: f64, finder_advantage: f64, group_size: f64) -> (f64, f64) {
    let producer_payoff = finder_advantage / (1.0 + (1.0 - p_freq) * (group_size - 1.0));
    let scrounger_payoff = (1.0 - finder_advantage) * p_freq * group_size / group_size;
    (producer_payoff, scrounger_payoff)
}

pub fn giving_up_density(metabolic_cost: f64, predation_cost: f64, missed_opportunity: f64) -> f64 {
    metabolic_cost + predation_cost + missed_opportunity
}

pub fn patch_residence_time(gain_rate: f64, travel_time: f64, depletion_rate: f64) -> f64 {
    ((gain_rate * travel_time) / depletion_rate).sqrt()
}

pub fn functional_response_type_ii(prey_density: f64, attack_rate: f64, handling_time: f64) -> f64 {
    attack_rate * prey_density / (1.0 + attack_rate * handling_time * prey_density)
}

pub fn functional_response_type_iii(
    prey_density: f64,
    attack_max: f64,
    half_sat: f64,
    handling_time: f64,
) -> f64 {
    let a = attack_max * prey_density / (half_sat + prey_density);
    a * prey_density / (1.0 + a * handling_time * prey_density)
}

pub fn starvation_risk(reserves: f64, daily_cost: f64, variance: f64) -> f64 {
    (-2.0 * reserves * daily_cost / variance.max(1e-30)).exp()
}

pub fn cache_pilferage_rate(competitors: f64, detection_prob: f64, cache_density: f64) -> f64 {
    1.0 - (1.0 - detection_prob * cache_density).powf(competitors)
}

pub fn optimal_load_size(
    distance: f64,
    max_load: f64,
    loading_rate: f64,
    travel_speed: f64,
) -> f64 {
    let travel_time = distance / travel_speed;
    (loading_rate * travel_time).min(max_load)
}

pub fn diet_breadth_threshold(energy: &[f64], handling: &[f64], encounter: &[f64]) -> usize {
    let mut indices: Vec<usize> = (0..energy.len()).collect();
    indices.sort_by(|&a, &b| {
        let pa = energy[a] / handling[a];
        let pb = energy[b] / handling[b];
        pb.partial_cmp(&pa).unwrap_or(std::cmp::Ordering::Equal)
    });
    let mut cum_rate_num = 0.0;
    let mut cum_rate_den = 1.0;
    for (k, &idx) in indices.iter().enumerate() {
        cum_rate_num += encounter[idx] * energy[idx];
        cum_rate_den += encounter[idx] * handling[idx];
        let avg_rate = cum_rate_num / cum_rate_den;
        let next_profit = if k + 1 < indices.len() {
            let ni = indices[k + 1];
            energy[ni] / handling[ni]
        } else {
            0.0
        };
        if avg_rate >= next_profit {
            return k + 1;
        }
    }
    indices.len()
}