#![allow(clippy::disallowed_methods)]
use aprender::metaheuristics::{
Budget, DifferentialEvolution, PerturbativeMetaheuristic, SearchSpace,
};
#[derive(Debug, Clone, Copy)]
struct LotkaVolterraParams {
alpha: f64, beta: f64, delta: f64, gamma: f64, }
fn simulate_lotka_volterra(
params: &LotkaVolterraParams,
x0: f64, y0: f64, dt: f64, steps: usize, ) -> Vec<(f64, f64)> {
let mut trajectory = Vec::with_capacity(steps);
let mut x = x0;
let mut y = y0;
for _ in 0..steps {
trajectory.push((x, y));
let dx = params.alpha * x - params.beta * x * y;
let dy = params.delta * x * y - params.gamma * y;
x += dx * dt;
y += dy * dt;
x = x.max(0.0);
y = y.max(0.0);
}
trajectory
}
fn generate_observed_data() -> (Vec<(f64, f64)>, LotkaVolterraParams) {
let true_params = LotkaVolterraParams {
alpha: 1.1, beta: 0.4, delta: 0.1, gamma: 0.4, };
let data = simulate_lotka_volterra(&true_params, 10.0, 5.0, 0.1, 100);
(data, true_params)
}
fn main() {
println!("=== Predator-Prey Ecosystem Parameter Optimization ===\n");
let (observed, true_params) = generate_observed_data();
println!("True parameters (to be recovered):");
println!(" α (prey birth rate): {:.3}", true_params.alpha);
println!(" β (predation rate): {:.3}", true_params.beta);
println!(" δ (predator growth): {:.3}", true_params.delta);
println!(" γ (predator death rate): {:.3}", true_params.gamma);
println!();
let space = SearchSpace::Continuous {
dim: 4,
lower: vec![0.1, 0.01, 0.01, 0.1],
upper: vec![2.0, 1.0, 0.5, 1.0],
};
let objective = |params_vec: &[f64]| -> f64 {
let params = LotkaVolterraParams {
alpha: params_vec[0],
beta: params_vec[1],
delta: params_vec[2],
gamma: params_vec[3],
};
let simulated = simulate_lotka_volterra(¶ms, 10.0, 5.0, 0.1, 100);
let mse: f64 = observed
.iter()
.zip(simulated.iter())
.map(|((ox, oy), (sx, sy))| (ox - sx).powi(2) + (oy - sy).powi(2))
.sum::<f64>()
/ (observed.len() as f64);
mse
};
println!("Optimization objective: Minimize MSE between model and observed data");
println!("Search space: 4D continuous (α, β, δ, γ)\n");
println!("=== Method 1: Differential Evolution ===");
let mut de = DifferentialEvolution::default().with_seed(42);
let de_result = de.optimize(&objective, &space, Budget::Evaluations(5000));
println!("DE Result:");
println!(
" α = {:.4} (true: {:.4})",
de_result.solution[0], true_params.alpha
);
println!(
" β = {:.4} (true: {:.4})",
de_result.solution[1], true_params.beta
);
println!(
" δ = {:.4} (true: {:.4})",
de_result.solution[2], true_params.delta
);
println!(
" γ = {:.4} (true: {:.4})",
de_result.solution[3], true_params.gamma
);
println!(" MSE: {:.6}", de_result.objective_value);
println!();
println!("=== Method 2: Discretized Parameter Search (ACO-style) ===");
let alpha_values = [0.5, 0.8, 1.0, 1.1, 1.2, 1.5];
let beta_values = [0.2, 0.3, 0.4, 0.5, 0.6];
let delta_values = [0.05, 0.1, 0.15, 0.2];
let gamma_values = [0.2, 0.3, 0.4, 0.5, 0.6];
let mut best_mse = f64::INFINITY;
let mut best_discrete = (0.0, 0.0, 0.0, 0.0);
for &a in &alpha_values {
for &b in &beta_values {
for &d in &delta_values {
for &g in &gamma_values {
let mse = objective(&[a, b, d, g]);
if mse < best_mse {
best_mse = mse;
best_discrete = (a, b, d, g);
}
}
}
}
}
println!("Grid Search Result (discretized):");
println!(
" α = {:.4} (true: {:.4})",
best_discrete.0, true_params.alpha
);
println!(
" β = {:.4} (true: {:.4})",
best_discrete.1, true_params.beta
);
println!(
" δ = {:.4} (true: {:.4})",
best_discrete.2, true_params.delta
);
println!(
" γ = {:.4} (true: {:.4})",
best_discrete.3, true_params.gamma
);
println!(" MSE: {:.6}", best_mse);
println!();
println!("=== Summary ===\n");
println!(
"DE achieved {:.2}x better fit than grid search",
best_mse / de_result.objective_value.max(1e-10)
);
let de_error = ((de_result.solution[0] - true_params.alpha).powi(2)
+ (de_result.solution[1] - true_params.beta).powi(2)
+ (de_result.solution[2] - true_params.delta).powi(2)
+ (de_result.solution[3] - true_params.gamma).powi(2))
.sqrt();
let grid_error = ((best_discrete.0 - true_params.alpha).powi(2)
+ (best_discrete.1 - true_params.beta).powi(2)
+ (best_discrete.2 - true_params.delta).powi(2)
+ (best_discrete.3 - true_params.gamma).powi(2))
.sqrt();
println!("\nParameter Recovery Error (Euclidean distance from true):");
println!(" DE: {:.4}", de_error);
println!(" Grid: {:.4}", grid_error);
println!("\n=== Population Dynamics with Recovered Parameters ===");
let recovered_params = LotkaVolterraParams {
alpha: de_result.solution[0],
beta: de_result.solution[1],
delta: de_result.solution[2],
gamma: de_result.solution[3],
};
let recovered_sim = simulate_lotka_volterra(&recovered_params, 10.0, 5.0, 0.1, 100);
println!("\nTime Prey(Obs) Prey(Sim) Pred(Obs) Pred(Sim)");
println!("---- --------- --------- --------- ---------");
for i in (0..100).step_by(10) {
println!(
"{:4} {:7.2} {:7.2} {:7.2} {:7.2}",
i, observed[i].0, recovered_sim[i].0, observed[i].1, recovered_sim[i].1
);
}
}