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 if s.growth_rate > 0.0 {
87 dpop[i] += s.growth_rate * s.population * (1.0 - s.population / s.carrying_capacity.max(1.0));
89 } else {
90 dpop[i] += s.growth_rate * s.population;
95 }
96 }
97
98 for inter in &self.interactions {
100 let pred_idx = self.species.iter().position(|s| s.id == inter.predator);
101 let prey_idx = self.species.iter().position(|s| s.id == inter.prey);
102 if let (Some(pi), Some(qi)) = (pred_idx, prey_idx) {
103 let pred_pop = self.species[pi].population;
104 let prey_pop = self.species[qi].population;
105 let alpha = inter.strength;
106
107 match inter.interaction_type {
108 InteractionType::Predation => {
109 dpop[pi] += alpha * pred_pop * prey_pop * 0.01; dpop[qi] -= alpha * pred_pop * prey_pop * 0.01; }
112 InteractionType::Competition => {
113 dpop[pi] -= alpha * pred_pop * prey_pop * 0.005;
114 dpop[qi] -= alpha * pred_pop * prey_pop * 0.005;
115 }
116 InteractionType::Mutualism => {
117 dpop[pi] += alpha * prey_pop * 0.001;
118 dpop[qi] += alpha * pred_pop * 0.001;
119 }
120 InteractionType::Parasitism => {
121 dpop[pi] += alpha * prey_pop * 0.002;
122 dpop[qi] -= alpha * pred_pop * 0.003;
123 }
124 InteractionType::Commensalism => {
125 dpop[pi] += alpha * prey_pop * 0.001;
126 }
127 }
128 }
129 }
130
131 for i in 0..n {
133 self.species[i].population = (self.species[i].population + dpop[i] * dt).max(0.0);
134 }
135
136 self.time += dt;
137
138 if self.history.is_empty() || self.time - self.history.last().unwrap().0 >= 1.0 {
140 let pops: Vec<f64> = self.species.iter().map(|s| s.population).collect();
141 self.history.push((self.time, pops));
142 }
143 }
144
145 pub fn simulate(&mut self, steps: usize, dt: f64) {
147 for _ in 0..steps {
148 self.step(dt);
149 }
150 }
151
152 pub fn lyapunov_exponent(&self, dt: f64, steps: usize) -> f64 {
154 let mut eco = self.clone();
155 let mut perturbed = self.clone();
156 if !perturbed.species.is_empty() {
158 perturbed.species[0].population *= 1.0001;
159 }
160
161 let mut sum_log = 0.0_f64;
162 let d0 = (eco.species[0].population - perturbed.species[0].population).abs().max(1e-15);
163
164 for i in 0..steps {
165 eco.step(dt);
166 perturbed.step(dt);
167
168 let d = (eco.species[0].population - perturbed.species[0].population).abs().max(1e-15);
169 sum_log += (d / d0).ln();
170
171 if !perturbed.species.is_empty() {
173 let scale = d0 / d;
174 for j in 0..perturbed.species.len() {
175 let diff = perturbed.species[j].population - eco.species[j].population;
176 perturbed.species[j].population = eco.species[j].population + diff * scale;
177 }
178 }
179 }
180
181 sum_log / (steps as f64 * dt)
182 }
183
184 pub fn total_biomass(&self) -> f64 {
186 self.species.iter().map(|s| s.population * s.traits.size).sum()
187 }
188
189 pub fn shannon_diversity(&self) -> f64 {
191 let total: f64 = self.species.iter().map(|s| s.population).sum();
192 if total < 1.0 { return 0.0; }
193 -self.species.iter()
194 .map(|s| {
195 let p = s.population / total;
196 if p > 0.0 { p * p.ln() } else { 0.0 }
197 })
198 .sum::<f64>()
199 }
200}
201
202pub fn lotka_volterra_example() -> Ecosystem {
204 let mut eco = Ecosystem::new();
205 eco.add_species(Species {
206 id: 0, name: "Rabbit".to_string(), population: 100.0,
207 carrying_capacity: 500.0, growth_rate: 0.5,
208 trophic_level: TrophicLevel::PrimaryConsumer,
209 traits: SpeciesTraits { size: 0.1, speed: 0.6, reproduction_rate: 0.8, lifespan: 5.0, aggression: 0.1 },
210 });
211 eco.add_species(Species {
212 id: 1, name: "Fox".to_string(), population: 20.0,
213 carrying_capacity: 100.0, growth_rate: -0.1,
214 trophic_level: TrophicLevel::SecondaryConsumer,
215 traits: SpeciesTraits { size: 0.3, speed: 0.8, reproduction_rate: 0.3, lifespan: 10.0, aggression: 0.7 },
216 });
217 eco.add_interaction(Interaction {
218 predator: 1, prey: 0,
219 interaction_type: InteractionType::Predation,
220 strength: 0.5,
221 });
222 eco
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_lotka_volterra() {
231 let mut eco = lotka_volterra_example();
232 eco.simulate(1000, 0.1);
233 assert!(eco.species[0].population > 0.0, "rabbits should survive");
235 assert!(eco.species[1].population > 0.0, "foxes should survive");
236 }
237
238 #[test]
239 fn test_shannon_diversity() {
240 let eco = lotka_volterra_example();
241 let h = eco.shannon_diversity();
242 assert!(h > 0.0, "diversity should be positive with 2 species");
243 }
244
245 #[test]
246 fn test_history_recording() {
247 let mut eco = lotka_volterra_example();
248 eco.simulate(100, 0.1);
249 assert!(!eco.history.is_empty());
250 }
251}