use crate::mood::MoodVector;
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn sickness_mood(infected_fraction: f32) -> MoodVector {
let i = infected_fraction.clamp(0.0, 1.0);
MoodVector {
joy: (-i * 0.6).clamp(-1.0, 0.0),
arousal: (-i * 0.4).clamp(-1.0, 0.0),
dominance: (-i * 0.3).clamp(-1.0, 0.0),
trust: (-i * 0.5).clamp(-1.0, 0.0),
interest: (-i * 0.5).clamp(-1.0, 0.0),
frustration: (i * 0.5).clamp(0.0, 1.0),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn sickness_severity(exposed_fraction: f32, infected_fraction: f32) -> f32 {
let e = exposed_fraction.clamp(0.0, 1.0);
let i = infected_fraction.clamp(0.0, 1.0);
(e * 0.3 + i * 0.7).clamp(0.0, 1.0)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn recovery_mood_boost(recovered_fraction: f32) -> MoodVector {
let r = recovered_fraction.clamp(0.0, 1.0);
MoodVector {
joy: (r * 0.4).clamp(0.0, 1.0),
arousal: (r * 0.2).clamp(0.0, 1.0),
dominance: (r * 0.2).clamp(0.0, 1.0),
trust: (r * 0.3).clamp(0.0, 1.0),
interest: (r * 0.3).clamp(0.0, 1.0),
frustration: 0.0,
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn immune_energy_drain(infected_fraction: f32) -> f32 {
let i = infected_fraction.clamp(0.0, 1.0);
1.0 + i * 2.0
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
pub fn contagion_avoidance(beta: f64, gamma: f64) -> f32 {
let r0 = jivanu::epidemiology::r_naught(beta, gamma).unwrap_or(1.0);
let pressure = 1.0 - (-0.3 * (r0 - 1.0)).exp();
(pressure as f32).clamp(0.0, 1.0)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
pub fn herd_safety(vaccination_coverage: f64, r0: f64) -> f32 {
let threshold = jivanu::epidemiology::herd_immunity_threshold(r0).unwrap_or(1.0);
if threshold <= 0.0 {
return 0.3;
}
let ratio = (vaccination_coverage / threshold).clamp(0.0, 2.0);
((ratio - 0.5) * 0.4) as f32
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn metabolic_efficiency(growth_rate_fraction: f32) -> f32 {
let g = growth_rate_fraction.clamp(0.0, 1.0);
0.5 + g * 0.5
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
pub fn temperature_stress(temp_c: f64, t_min: f64, t_opt: f64, t_max: f64) -> f32 {
let growth_fraction =
jivanu::growth::cardinal_temperature(temp_c, t_min, t_opt, t_max).unwrap_or(0.0);
(1.0 - growth_fraction.clamp(0.0, 1.0)) as f32
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
pub fn drug_cognitive_effect(concentration: f64, e_max: f64, ec50: f64, hill_n: f64) -> f32 {
let effect = jivanu::metabolism::emax_model(concentration, e_max, ec50, hill_n).unwrap_or(0.0);
(effect as f32).clamp(0.0, 1.0)
}
#[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
#[must_use]
#[inline]
pub fn drug_sedation(concentration: f64, ec50: f64) -> f32 {
if ec50 <= 0.0 {
return 0.0;
}
let ratio = (concentration / ec50).clamp(0.0, 10.0);
let sedation = 1.0 - (-0.3 * (ratio - 0.5)).exp();
(sedation as f32).clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn healthy_no_sickness() {
let mood = sickness_mood(0.0);
assert!(mood.frustration < 0.01);
assert!(mood.joy.abs() < 0.01);
}
#[test]
fn sick_depressed_mood() {
let mood = sickness_mood(0.6);
assert!(mood.joy < 0.0);
assert!(mood.frustration > 0.2);
assert!(mood.trust < 0.0);
assert!(mood.interest < 0.0);
}
#[test]
fn severity_zero_when_healthy() {
assert!((sickness_severity(0.0, 0.0) - 0.0).abs() < 0.01);
}
#[test]
fn severity_infected_worse_than_exposed() {
let exposed_only = sickness_severity(0.5, 0.0);
let infected_only = sickness_severity(0.0, 0.5);
assert!(infected_only > exposed_only);
}
#[test]
fn recovery_boosts_joy() {
let early = recovery_mood_boost(0.1);
let late = recovery_mood_boost(0.9);
assert!(late.joy > early.joy);
assert!(late.trust > early.trust);
}
#[test]
fn immune_drain_at_rest() {
assert!((immune_energy_drain(0.0) - 1.0).abs() < 0.01);
}
#[test]
fn immune_drain_increases() {
assert!(immune_energy_drain(0.5) > 1.5);
assert!(immune_energy_drain(1.0) > 2.5);
}
#[test]
fn low_r0_low_avoidance() {
let pressure = contagion_avoidance(0.5, 0.3);
assert!(pressure < 0.3);
}
#[test]
fn high_r0_high_avoidance() {
let pressure = contagion_avoidance(5.0, 0.2);
assert!(pressure > 0.3);
}
#[test]
fn high_coverage_more_safety() {
let low = herd_safety(0.2, 3.0);
let high = herd_safety(0.8, 3.0);
assert!(high > low);
}
#[test]
fn optimal_growth_high_efficiency() {
assert!(metabolic_efficiency(1.0) > 0.9);
}
#[test]
fn stressed_growth_low_efficiency() {
assert!(metabolic_efficiency(0.0) < 0.6);
}
#[test]
fn optimal_temperature_low_stress() {
let stress = temperature_stress(37.0, 15.0, 37.0, 45.0);
assert!(stress < 0.1);
}
#[test]
fn extreme_temperature_high_stress() {
let cold = temperature_stress(18.0, 15.0, 37.0, 45.0);
assert!(cold > 0.5);
}
#[test]
fn sub_therapeutic_low_effect() {
let effect = drug_cognitive_effect(0.5, 1.0, 5.0, 1.0);
assert!(effect < 0.2);
}
#[test]
fn therapeutic_moderate_effect() {
let effect = drug_cognitive_effect(5.0, 1.0, 5.0, 1.0);
assert!(effect > 0.3 && effect < 0.7);
}
#[test]
fn low_concentration_low_sedation() {
assert!(drug_sedation(1.0, 5.0) < 0.3);
}
#[test]
fn high_concentration_high_sedation() {
assert!(drug_sedation(15.0, 5.0) > 0.5);
}
#[test]
fn zero_ec50_no_sedation() {
assert!((drug_sedation(5.0, 0.0) - 0.0).abs() < 0.01);
}
}