use scirs2_core::ndarray::{array, Array1, ArrayView1};
use scirs2_integrate::ode::{solve_ivp, ODEMethod, ODEOptions};
#[allow(dead_code)]
fn exponential_growth(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let n = y[0]; let r = 0.5;
let dn_dt = r * n;
array![dn_dt]
}
#[allow(dead_code)]
fn logistic_growth(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let n = y[0]; let r = 1.0; let k = 100.0;
let dn_dt = r * n * (1.0 - n / k);
array![dn_dt]
}
#[allow(dead_code)]
fn lotka_volterra(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let n = y[0]; let p = y[1];
let a = 1.0; let b = 0.1; let c = 0.075; let d = 1.5;
let dn_dt = a * n - b * n * p;
let dp_dt = c * b * n * p - d * p;
array![dn_dt, dp_dt]
}
#[allow(dead_code)]
fn competitive_lotka_volterra(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let n1 = y[0]; let n2 = y[1];
let r1 = 1.0; let r2 = 0.8; let k1 = 100.0; let k2 = 80.0; let alpha12 = 1.2; let alpha21 = 0.9;
let dn1_dt = r1 * n1 * (1.0 - (n1 + alpha12 * n2) / k1);
let dn2_dt = r2 * n2 * (1.0 - (n2 + alpha21 * n1) / k2);
array![dn1_dt, dn2_dt]
}
#[allow(dead_code)]
fn sir_model(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let s = y[0]; let i = y[1]; let r = y[2];
let n = s + i + r; let beta = 0.3; let gamma = 0.1;
let ds_dt = -beta * s * i / n;
let di_dt = beta * s * i / n - gamma * i;
let dr_dt = gamma * i;
array![ds_dt, di_dt, dr_dt]
}
#[allow(dead_code)]
fn seir_model(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let s = y[0]; let e = y[1]; let i = y[2]; let r = y[3];
let n = s + e + i + r; let beta = 0.4; let sigma = 0.2; let gamma = 0.1;
let ds_dt = -beta * s * i / n;
let de_dt = beta * s * i / n - sigma * e;
let di_dt = sigma * e - gamma * i;
let dr_dt = gamma * i;
array![ds_dt, de_dt, di_dt, dr_dt]
}
#[allow(dead_code)]
fn sirs_model(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let s = y[0]; let i = y[1]; let r = y[2];
let n = s + i + r; let beta = 0.3; let gamma = 0.1; let omega = 0.05;
let ds_dt = -beta * s * i / n + omega * r;
let di_dt = beta * s * i / n - gamma * i;
let dr_dt = gamma * i - omega * r;
array![ds_dt, di_dt, dr_dt]
}
#[allow(dead_code)]
fn sis_model(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let s = y[0]; let i = y[1];
let n = s + i; let beta = 0.5; let gamma = 0.2;
let ds_dt = -beta * s * i / n + gamma * i;
let di_dt = beta * s * i / n - gamma * i;
array![ds_dt, di_dt]
}
#[allow(dead_code)]
fn metapopulation_sir(t: f64, y: ArrayView1<f64>) -> Array1<f64> {
let s1 = y[0];
let i1 = y[1];
let r1 = y[2]; let s2 = y[3];
let i2 = y[4];
let r2 = y[5];
let n1 = s1 + i1 + r1; let n2 = s2 + i2 + r2;
let beta1 = 0.3;
let beta2 = 0.25; let gamma = 0.1; let m = 0.01;
let ds1_dt_local = -beta1 * s1 * i1 / n1;
let di1_dt_local = beta1 * s1 * i1 / n1 - gamma * i1;
let dr1_dt_local = gamma * i1;
let ds2_dt_local = -beta2 * s2 * i2 / n2;
let di2_dt_local = beta2 * s2 * i2 / n2 - gamma * i2;
let dr2_dt_local = gamma * i2;
let ds1_dt = ds1_dt_local + m * (s2 - s1);
let di1_dt = di1_dt_local + m * (i2 - i1);
let dr1_dt = dr1_dt_local + m * (r2 - r1);
let ds2_dt = ds2_dt_local + m * (s1 - s2);
let di2_dt = di2_dt_local + m * (i1 - i2);
let dr2_dt = dr2_dt_local + m * (r1 - r2);
array![ds1_dt, di1_dt, dr1_dt, ds2_dt, di2_dt, dr2_dt]
}
#[allow(dead_code)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Population Dynamics and Epidemiological Models\n");
println!("1. Exponential Population Growth");
let t_span = [0.0, 5.0];
let y0 = array![10.0];
let result = solve_ivp(exponential_growth, t_span, y0.clone(), None)?;
println!(" Initial population: {:.1}", y0[0]);
println!(
" Final population: {:.1}",
result.y.last().expect("Operation failed")[0]
);
println!(
" Theoretical final: {:.1}",
y0[0] * (0.5 * t_span[1]).exp()
);
println!();
println!("2. Logistic Population Growth");
let result = solve_ivp(logistic_growth, [0.0, 10.0], array![5.0], None)?;
println!(" Initial population: {:.1}", 5.0);
println!(
" Final population: {:.1}",
result.y.last().expect("Operation failed")[0]
);
println!(" Carrying capacity: {:.1}", 100.0);
println!(
" Approached carrying capacity: {:.1}%",
result.y.last().expect("Operation failed")[0] / 100.0 * 100.0
);
println!();
println!("3. Lotka-Volterra Predator-Prey Model");
let options = ODEOptions {
method: ODEMethod::RK45,
rtol: 1e-8,
atol: 1e-10,
..Default::default()
};
let result = solve_ivp(
lotka_volterra,
[0.0, 15.0],
array![10.0, 5.0],
Some(options.clone()),
)?;
println!(
" Initial populations: Prey={:.1}, Predator={:.1}",
10.0, 5.0
);
println!(
" Final populations: Prey={:.1}, Predator={:.1}",
result.y.last().expect("Operation failed")[0],
result.y.last().expect("Operation failed")[1]
);
println!(" Steps taken: {} (oscillatory dynamics)", result.t.len());
println!();
println!("4. Competitive Lotka-Volterra (Two Species)");
let result = solve_ivp(
competitive_lotka_volterra,
[0.0, 20.0],
array![30.0, 20.0],
None,
)?;
println!(
" Initial populations: Species1={:.1}, Species2={:.1}",
30.0, 20.0
);
println!(
" Final populations: Species1={:.1}, Species2={:.1}",
result.y.last().expect("Operation failed")[0],
result.y.last().expect("Operation failed")[1]
);
let final_ratio = result.y.last().expect("Operation failed")[0]
/ result.y.last().expect("Operation failed")[1];
if final_ratio > 2.0 {
println!(" Outcome: Species 1 dominates");
} else if final_ratio < 0.5 {
println!(" Outcome: Species 2 dominates");
} else {
println!(" Outcome: Coexistence");
}
println!();
println!("5. SIR Epidemic Model");
let sir_initial = array![990.0, 10.0, 0.0]; let result = solve_ivp(sir_model, [0.0, 50.0], sir_initial.clone(), None)?;
println!(
" Initial: S={:.0}, I={:.0}, R={:.0}",
sir_initial[0], sir_initial[1], sir_initial[2]
);
let final_state = result.y.last().expect("Operation failed");
println!(
" Final: S={:.0}, I={:.0}, R={:.0}",
final_state[0], final_state[1], final_state[2]
);
let r0 = 0.3 / 0.1;
println!(" Basic reproduction number R₀: {r0:.1}");
println!(
" Attack rate: {:.1}%",
(sir_initial[0] - final_state[0]) / sir_initial[0] * 100.0
);
println!();
println!("6. SEIR Epidemic Model (with incubation)");
let seir_initial = array![990.0, 0.0, 10.0, 0.0]; let result = solve_ivp(
seir_model,
[0.0, 60.0],
seir_initial.clone(),
Some(options.clone()),
)?;
println!(
" Initial: S={:.0}, E={:.0}, I={:.0}, R={:.0}",
seir_initial[0], seir_initial[1], seir_initial[2], seir_initial[3]
);
let final_state = result.y.last().expect("Operation failed");
println!(
" Final: S={:.0}, E={:.0}, I={:.0}, R={:.0}",
final_state[0], final_state[1], final_state[2], final_state[3]
);
println!(
" Peak exposed: {:.0}",
result
.y
.iter()
.map(|state| state[1])
.fold(0.0_f64, f64::max)
);
println!();
println!("7. SIRS Model (Immunity Loss)");
let result = solve_ivp(sirs_model, [0.0, 100.0], array![990.0, 10.0, 0.0], None)?;
let final_state = result.y.last().expect("Operation failed");
println!(" Initial: S={:.0}, I={:.0}, R={:.0}", 990.0, 10.0, 0.0);
println!(
" Final: S={:.0}, I={:.0}, R={:.0}",
final_state[0], final_state[1], final_state[2]
);
println!(" Endemic equilibrium reached (cycling infections)");
println!();
println!("8. SIS Model (No Immunity)");
let result = solve_ivp(sis_model, [0.0, 30.0], array![950.0, 50.0], None)?;
let final_state = result.y.last().expect("Operation failed");
println!(" Initial: S={:.0}, I={:.0}", 950.0, 50.0);
println!(" Final: S={:.0}, I={:.0}", final_state[0], final_state[1]);
let endemic_level = final_state[1] / (final_state[0] + final_state[1]) * 100.0;
println!(" Endemic infection level: {endemic_level:.1}%");
println!();
println!("9. Metapopulation SIR (Two Connected Cities)");
let meta_initial = array![500.0, 10.0, 0.0, 480.0, 5.0, 15.0]; let result = solve_ivp(
metapopulation_sir,
[0.0, 40.0],
meta_initial.clone(),
Some(options),
)?;
println!(
" City 1 Initial: S={:.0}, I={:.0}, R={:.0}",
meta_initial[0], meta_initial[1], meta_initial[2]
);
println!(
" City 2 Initial: S={:.0}, I={:.0}, R={:.0}",
meta_initial[3], meta_initial[4], meta_initial[5]
);
let final_state = result.y.last().expect("Operation failed");
println!(
" City 1 Final: S={:.0}, I={:.0}, R={:.0}",
final_state[0], final_state[1], final_state[2]
);
println!(
" City 2 Final: S={:.0}, I={:.0}, R={:.0}",
final_state[3], final_state[4], final_state[5]
);
println!(" Migration facilitates disease spread between cities");
println!();
println!("All population dynamics examples completed successfully!");
println!("\nModel Analysis Summary:");
println!("- Exponential growth shows unlimited population increase");
println!("- Logistic growth approaches carrying capacity asymptotically");
println!("- Predator-prey systems exhibit oscillatory dynamics");
println!("- Competitive species may coexist or lead to exclusion");
println!("- Epidemic models show threshold behavior based on R₀");
println!("- SEIR models capture incubation period effects");
println!("- SIRS/SIS models show endemic equilibria with reinfection");
println!("- Metapopulation models demonstrate spatial disease spread");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_logistic_growth_carrying_capacity() {
let t_span = [0.0, 20.0]; let y0 = array![10.0];
let result = solve_ivp(logistic_growth, t_span, y0, None).expect("Operation failed");
let final_pop = result.y.last().expect("Operation failed")[0];
let carrying_capacity = 100.0;
assert!((final_pop - carrying_capacity).abs() / carrying_capacity < 0.05);
}
#[test]
fn test_sir_population_conservation() {
let t_span = [0.0, 30.0];
let y0 = array![1000.0, 1.0, 0.0];
let result = solve_ivp(sir_model, t_span, y0.clone(), None).expect("Operation failed");
let initial_total = y0[0] + y0[1] + y0[2];
let final_state = result.y.last().expect("Operation failed");
let final_total = final_state[0] + final_state[1] + final_state[2];
assert_abs_diff_eq!(initial_total, final_total, epsilon = 1e-6);
}
#[test]
fn test_seir_population_conservation() {
let t_span = [0.0, 50.0];
let y0 = array![1000.0, 0.0, 1.0, 0.0];
let options = ODEOptions {
rtol: 1e-8,
atol: 1e-10,
..Default::default()
};
let result =
solve_ivp(seir_model, t_span, y0.clone(), Some(options)).expect("Operation failed");
let initial_total = y0.sum();
let final_state = result.y.last().expect("Operation failed");
let final_total = final_state[0] + final_state[1] + final_state[2] + final_state[3];
assert_abs_diff_eq!(initial_total, final_total, epsilon = 1e-6);
}
#[test]
fn test_lotka_volterra_conservation() {
let t_span = [0.0, 10.0];
let y0 = array![10.0, 5.0];
let options = ODEOptions {
rtol: 1e-10,
atol: 1e-12,
..Default::default()
};
let result =
solve_ivp(lotka_volterra, t_span, y0.clone(), Some(options)).expect("Operation failed");
let _a = 1.0;
let _b = 0.1;
let _c = 0.075;
let _d = 1.5;
let mut min_prey = f64::INFINITY;
let mut max_prey = 0.0;
let mut min_pred = f64::INFINITY;
let mut max_pred = 0.0;
for state in result.y.iter() {
assert!(state[0] > 0.0, "Prey population became non-positive");
assert!(state[1] > 0.0, "Predator population became non-positive");
min_prey = f64::min(min_prey, state[0]);
max_prey = f64::max(max_prey, state[0]);
min_pred = f64::min(min_pred, state[1]);
max_pred = f64::max(max_pred, state[1]);
}
assert!(min_prey > 0.01);
assert!(max_prey < 1000.0);
assert!(min_pred > 0.01);
assert!(max_pred < 500.0);
}
#[test]
fn test_epidemic_basic_reproduction_number() {
let t_span = [0.0, 20.0];
let y0 = array![999.0, 1.0, 0.0];
let result = solve_ivp(sir_model, t_span, y0.clone(), None).expect("Operation failed");
let peak_infected = result
.y
.iter()
.map(|state| state[1])
.fold(0.0_f64, f64::max);
assert!(peak_infected > 10.0 * y0[1]);
let final_susceptible = result.y.last().expect("Operation failed")[0];
assert!(final_susceptible < y0[0]);
}
}