1pub mod population;
8pub mod food_web;
9pub mod migration;
10pub mod evolution;
11pub mod disease;
12
13use std::collections::HashMap;
14
15#[derive(Debug, Clone)]
17pub struct Species {
18 pub id: u32,
19 pub name: String,
20 pub population: f64,
21 pub carrying_capacity: f64,
22 pub growth_rate: f64,
23 pub trophic_level: TrophicLevel,
24 pub traits: SpeciesTraits,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum TrophicLevel { Producer, PrimaryConsumer, SecondaryConsumer, ApexPredator, Decomposer }
29
30#[derive(Debug, Clone, Copy)]
31pub struct SpeciesTraits {
32 pub size: f64,
33 pub speed: f64,
34 pub reproduction_rate: f64,
35 pub lifespan: f64,
36 pub aggression: f64,
37}
38
39#[derive(Debug, Clone)]
41pub struct Ecosystem {
42 pub species: Vec<Species>,
43 pub interactions: Vec<Interaction>,
44 pub time: f64,
45 pub history: Vec<(f64, Vec<f64>)>,
46}
47
48#[derive(Debug, Clone)]
50pub struct Interaction {
51 pub predator: u32,
52 pub prey: u32,
53 pub interaction_type: InteractionType,
54 pub strength: f64,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum InteractionType {
59 Predation, Competition, Mutualism, Parasitism, Commensalism, }
65
66impl Ecosystem {
67 pub fn new() -> Self {
68 Self { species: Vec::new(), interactions: Vec::new(), time: 0.0, history: Vec::new() }
69 }
70
71 pub fn add_species(&mut self, species: Species) {
72 self.species.push(species);
73 }
74
75 pub fn add_interaction(&mut self, interaction: Interaction) {
76 self.interactions.push(interaction);
77 }
78
79 pub fn step(&mut self, dt: f64) {
81 let n = self.species.len();
82 let mut dpop = vec![0.0f64; n];
83
84 for i in 0..n {
85 let s = &self.species[i];
86 dpop[i] += s.growth_rate * s.population * (1.0 - s.population / s.carrying_capacity.max(1.0));
88 }
89
90 for inter in &self.interactions {
92 let pred_idx = self.species.iter().position(|s| s.id == inter.predator);
93 let prey_idx = self.species.iter().position(|s| s.id == inter.prey);
94 if let (Some(pi), Some(qi)) = (pred_idx, prey_idx) {
95 let pred_pop = self.species[pi].population;
96 let prey_pop = self.species[qi].population;
97 let alpha = inter.strength;
98
99 match inter.interaction_type {
100 InteractionType::Predation => {
101 dpop[pi] += alpha * pred_pop * prey_pop * 0.01; dpop[qi] -= alpha * pred_pop * prey_pop * 0.01; }
104 InteractionType::Competition => {
105 dpop[pi] -= alpha * pred_pop * prey_pop * 0.005;
106 dpop[qi] -= alpha * pred_pop * prey_pop * 0.005;
107 }
108 InteractionType::Mutualism => {
109 dpop[pi] += alpha * prey_pop * 0.001;
110 dpop[qi] += alpha * pred_pop * 0.001;
111 }
112 InteractionType::Parasitism => {
113 dpop[pi] += alpha * prey_pop * 0.002;
114 dpop[qi] -= alpha * pred_pop * 0.003;
115 }
116 InteractionType::Commensalism => {
117 dpop[pi] += alpha * prey_pop * 0.001;
118 }
119 }
120 }
121 }
122
123 for i in 0..n {
125 self.species[i].population = (self.species[i].population + dpop[i] * dt).max(0.0);
126 }
127
128 self.time += dt;
129
130 if self.history.is_empty() || self.time - self.history.last().unwrap().0 >= 1.0 {
132 let pops: Vec<f64> = self.species.iter().map(|s| s.population).collect();
133 self.history.push((self.time, pops));
134 }
135 }
136
137 pub fn simulate(&mut self, steps: usize, dt: f64) {
139 for _ in 0..steps {
140 self.step(dt);
141 }
142 }
143
144 pub fn lyapunov_exponent(&self, dt: f64, steps: usize) -> f64 {
146 let mut eco = self.clone();
147 let mut perturbed = self.clone();
148 if !perturbed.species.is_empty() {
150 perturbed.species[0].population *= 1.0001;
151 }
152
153 let mut sum_log = 0.0_f64;
154 let d0 = (eco.species[0].population - perturbed.species[0].population).abs().max(1e-15);
155
156 for i in 0..steps {
157 eco.step(dt);
158 perturbed.step(dt);
159
160 let d = (eco.species[0].population - perturbed.species[0].population).abs().max(1e-15);
161 sum_log += (d / d0).ln();
162
163 if !perturbed.species.is_empty() {
165 let scale = d0 / d;
166 for j in 0..perturbed.species.len() {
167 let diff = perturbed.species[j].population - eco.species[j].population;
168 perturbed.species[j].population = eco.species[j].population + diff * scale;
169 }
170 }
171 }
172
173 sum_log / (steps as f64 * dt)
174 }
175
176 pub fn total_biomass(&self) -> f64 {
178 self.species.iter().map(|s| s.population * s.traits.size).sum()
179 }
180
181 pub fn shannon_diversity(&self) -> f64 {
183 let total: f64 = self.species.iter().map(|s| s.population).sum();
184 if total < 1.0 { return 0.0; }
185 -self.species.iter()
186 .map(|s| {
187 let p = s.population / total;
188 if p > 0.0 { p * p.ln() } else { 0.0 }
189 })
190 .sum::<f64>()
191 }
192}
193
194pub fn lotka_volterra_example() -> Ecosystem {
196 let mut eco = Ecosystem::new();
197 eco.add_species(Species {
198 id: 0, name: "Rabbit".to_string(), population: 100.0,
199 carrying_capacity: 500.0, growth_rate: 0.5,
200 trophic_level: TrophicLevel::PrimaryConsumer,
201 traits: SpeciesTraits { size: 0.1, speed: 0.6, reproduction_rate: 0.8, lifespan: 5.0, aggression: 0.1 },
202 });
203 eco.add_species(Species {
204 id: 1, name: "Fox".to_string(), population: 20.0,
205 carrying_capacity: 100.0, growth_rate: -0.1,
206 trophic_level: TrophicLevel::SecondaryConsumer,
207 traits: SpeciesTraits { size: 0.3, speed: 0.8, reproduction_rate: 0.3, lifespan: 10.0, aggression: 0.7 },
208 });
209 eco.add_interaction(Interaction {
210 predator: 1, prey: 0,
211 interaction_type: InteractionType::Predation,
212 strength: 0.5,
213 });
214 eco
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_lotka_volterra() {
223 let mut eco = lotka_volterra_example();
224 eco.simulate(1000, 0.1);
225 assert!(eco.species[0].population > 0.0, "rabbits should survive");
227 assert!(eco.species[1].population > 0.0, "foxes should survive");
228 }
229
230 #[test]
231 fn test_shannon_diversity() {
232 let eco = lotka_volterra_example();
233 let h = eco.shannon_diversity();
234 assert!(h > 0.0, "diversity should be positive with 2 species");
235 }
236
237 #[test]
238 fn test_history_recording() {
239 let mut eco = lotka_volterra_example();
240 eco.simulate(100, 0.1);
241 assert!(!eco.history.is_empty());
242 }
243}