use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Relatedness;
impl Relatedness {
pub const CLONE: f32 = 1.0;
pub const PARENT_OFFSPRING: f32 = 0.5;
pub const FULL_SIBLING: f32 = 0.5;
pub const HALF_SIBLING: f32 = 0.25;
pub const GRANDPARENT: f32 = 0.25;
pub const FIRST_COUSIN: f32 = 0.125;
pub const UNRELATED: f32 = 0.0;
pub const HAPLODIPLOID_SISTER: f32 = 0.75;
}
#[must_use]
pub fn hamiltons_rule(relatedness: f32, benefit: f32, cost: f32) -> bool {
relatedness * benefit > cost
}
#[must_use]
pub fn inclusive_fitness(
direct_fitness: f32,
indirect_fitness: f32,
average_relatedness: f32,
) -> f32 {
direct_fitness + indirect_fitness * average_relatedness.clamp(0.0, 1.0)
}
#[must_use]
pub fn should_alarm_call(
relatedness: f32,
group_size: u32,
survival_reduction: f32,
predator_lethality: f32,
) -> bool {
let cost = survival_reduction.clamp(0.0, 1.0) * predator_lethality.clamp(0.0, 1.0);
let benefit_per_member = survival_reduction * 0.8; let total_benefit = benefit_per_member * group_size as f32;
hamiltons_rule(relatedness, total_benefit, cost)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hamiltons_rule_sibling_altruism() {
assert!(hamiltons_rule(0.5, 10.0, 3.0));
}
#[test]
fn hamiltons_rule_rejects_costly() {
assert!(!hamiltons_rule(0.5, 10.0, 6.0));
}
#[test]
fn hamiltons_rule_unrelated_never() {
assert!(!hamiltons_rule(0.0, 100.0, 0.01));
}
#[test]
fn inclusive_fitness_combines() {
let total = inclusive_fitness(5.0, 8.0, 0.5);
assert!((total - 9.0).abs() < f32::EPSILON);
}
#[test]
fn inclusive_fitness_zero_relatedness() {
let total = inclusive_fitness(5.0, 100.0, 0.0);
assert!((total - 5.0).abs() < f32::EPSILON);
}
#[test]
fn alarm_call_related_group() {
assert!(should_alarm_call(0.5, 5, 0.1, 0.5));
}
#[test]
fn alarm_call_unrelated_no() {
assert!(!should_alarm_call(0.0, 5, 0.3, 0.8));
}
#[test]
fn haplodiploid_super_sisters() {
assert!(hamiltons_rule(Relatedness::HAPLODIPLOID_SISTER, 4.0, 2.5));
assert!(!hamiltons_rule(Relatedness::FULL_SIBLING, 4.0, 2.5));
}
#[test]
fn serde_roundtrip_relatedness() {
let r = Relatedness;
let json = serde_json::to_string(&r).unwrap();
let _r2: Relatedness = serde_json::from_str(&json).unwrap();
}
}