use crate::common::{Individual, OptimizationResult, Problem, SolverConfig};
use ndarray::Array1;
use rand::prelude::*;
pub struct ABCSolver {
pub config: SolverConfig,
pub limit: usize, }
impl ABCSolver {
pub fn new(config: SolverConfig) -> Self {
Self {
config,
limit: 100, }
}
pub fn solve<P: Problem>(&self, problem: &P) -> OptimizationResult {
let mut rng = thread_rng();
let dim = problem.dim();
let (lower, upper) = problem.bounds();
let pop_size = self.config.population_size;
let limit = if self.limit == 100 { pop_size * dim / 2 } else { self.limit };
let mut foods: Vec<Individual> = (0..pop_size)
.map(|_| {
let mut vars = Array1::zeros(dim);
for i in 0..dim {
vars[i] = rng.gen_range(lower[i]..upper[i]);
}
let fitness = problem.fitness(&vars);
Individual::new(vars, fitness)
})
.collect();
let mut trial_counters = vec![0; pop_size];
let mut best_idx = 0;
for i in 1..pop_size {
if foods[i].fitness < foods[best_idx].fitness {
best_idx = i;
}
}
let mut best_vars = foods[best_idx].variables.clone();
let mut best_fitness = foods[best_idx].fitness;
let mut history = Vec::with_capacity(self.config.max_iterations);
for _iter in 0..self.config.max_iterations {
history.push(best_fitness);
for i in 0..pop_size {
let mut new_vars = foods[i].variables.clone();
let j = rng.gen_range(0..dim);
let mut k;
loop {
k = rng.gen_range(0..pop_size);
if k != i { break; }
}
let phi: f64 = rng.gen_range(-1.0..1.0);
new_vars[j] = (foods[i].variables[j] + phi * (foods[i].variables[j] - foods[k].variables[j])).clamp(lower[j], upper[j]);
let new_fitness = problem.fitness(&new_vars);
if new_fitness < foods[i].fitness {
foods[i] = Individual::new(new_vars, new_fitness);
trial_counters[i] = 0;
} else {
trial_counters[i] += 1;
}
}
let mut probs = vec![0.0; pop_size];
let mut total_f = 0.0;
for i in 0..pop_size {
let f_val = if foods[i].fitness >= 0.0 {
1.0 / (1.0 + foods[i].fitness)
} else {
1.0 + foods[i].fitness.abs()
};
probs[i] = f_val;
total_f += f_val;
}
for i in 0..pop_size {
probs[i] /= total_f;
}
let mut m = 0;
let mut t = 0;
while m < pop_size {
if rng.gen::<f64>() < probs[t] {
m += 1;
let i = t;
let mut new_vars = foods[i].variables.clone();
let j = rng.gen_range(0..dim);
let mut k;
loop {
k = rng.gen_range(0..pop_size);
if k != i { break; }
}
let phi: f64 = rng.gen_range(-1.0..1.0);
new_vars[j] = (foods[i].variables[j] + phi * (foods[i].variables[j] - foods[k].variables[j])).clamp(lower[j], upper[j]);
let new_fitness = problem.fitness(&new_vars);
if new_fitness < foods[i].fitness {
foods[i] = Individual::new(new_vars, new_fitness);
trial_counters[i] = 0;
} else {
trial_counters[i] += 1;
}
}
t = (t + 1) % pop_size;
}
for i in 0..pop_size {
if trial_counters[i] > limit {
let mut vars = Array1::zeros(dim);
for j in 0..dim {
vars[j] = rng.gen_range(lower[j]..upper[j]);
}
let fitness = problem.fitness(&vars);
foods[i] = Individual::new(vars, fitness);
trial_counters[i] = 0;
}
}
for i in 0..pop_size {
if foods[i].fitness < best_fitness {
best_fitness = foods[i].fitness;
best_vars = foods[i].variables.clone();
}
}
}
OptimizationResult {
best_variables: best_vars,
best_fitness,
history,
}
}
}