Skip to main content

samyama_optimization/algorithms/
abc.rs

1use crate::common::{Individual, OptimizationResult, Problem, SolverConfig};
2use ndarray::Array1;
3use rand::prelude::*;
4
5pub struct ABCSolver {
6    pub config: SolverConfig,
7    pub limit: usize, // Abandonment limit for scout bees
8}
9
10impl ABCSolver {
11    pub fn new(config: SolverConfig) -> Self {
12        // Default limit is usually pop_size * dim / 2
13        Self { 
14            config,
15            limit: 100, // Placeholder, will be adjusted in solve
16        }
17    }
18
19    pub fn solve<P: Problem>(&self, problem: &P) -> OptimizationResult {
20        let mut rng = thread_rng();
21        let dim = problem.dim();
22        let (lower, upper) = problem.bounds();
23        let pop_size = self.config.population_size;
24        let limit = if self.limit == 100 { pop_size * dim / 2 } else { self.limit };
25
26        // 1. Initialize Food Sources (Population)
27        let mut foods: Vec<Individual> = (0..pop_size)
28            .map(|_| {
29                let mut vars = Array1::zeros(dim);
30                for i in 0..dim {
31                    vars[i] = rng.gen_range(lower[i]..upper[i]);
32                }
33                let fitness = problem.fitness(&vars);
34                Individual::new(vars, fitness)
35            })
36            .collect();
37
38        let mut trial_counters = vec![0; pop_size];
39        
40        let mut best_idx = 0;
41        for i in 1..pop_size {
42            if foods[i].fitness < foods[best_idx].fitness {
43                best_idx = i;
44            }
45        }
46        let mut best_vars = foods[best_idx].variables.clone();
47        let mut best_fitness = foods[best_idx].fitness;
48
49        let mut history = Vec::with_capacity(self.config.max_iterations);
50
51        for _iter in 0..self.config.max_iterations {
52            history.push(best_fitness);
53
54            // 2. Employed Bees Phase
55            for i in 0..pop_size {
56                let mut new_vars = foods[i].variables.clone();
57                let j = rng.gen_range(0..dim);
58                let mut k;
59                loop {
60                    k = rng.gen_range(0..pop_size);
61                    if k != i { break; }
62                }
63
64                let phi: f64 = rng.gen_range(-1.0..1.0);
65                new_vars[j] = (foods[i].variables[j] + phi * (foods[i].variables[j] - foods[k].variables[j])).clamp(lower[j], upper[j]);
66
67                let new_fitness = problem.fitness(&new_vars);
68                if new_fitness < foods[i].fitness {
69                    foods[i] = Individual::new(new_vars, new_fitness);
70                    trial_counters[i] = 0;
71                } else {
72                    trial_counters[i] += 1;
73                }
74            }
75
76            // 3. Onlooker Bees Phase
77            // Calculate probabilities based on fitness
78            // Fitness in ABC is often 1/(1+f) if f>=0 else 1+|f|
79            let mut probs = vec![0.0; pop_size];
80            let mut total_f = 0.0;
81            for i in 0..pop_size {
82                let f_val = if foods[i].fitness >= 0.0 {
83                    1.0 / (1.0 + foods[i].fitness)
84                } else {
85                    1.0 + foods[i].fitness.abs()
86                };
87                probs[i] = f_val;
88                total_f += f_val;
89            }
90            for i in 0..pop_size {
91                probs[i] /= total_f;
92            }
93
94            let mut m = 0;
95            let mut t = 0;
96            while m < pop_size {
97                if rng.gen::<f64>() < probs[t] {
98                    m += 1;
99                    let i = t;
100                    let mut new_vars = foods[i].variables.clone();
101                    let j = rng.gen_range(0..dim);
102                    let mut k;
103                    loop {
104                        k = rng.gen_range(0..pop_size);
105                        if k != i { break; }
106                    }
107
108                    let phi: f64 = rng.gen_range(-1.0..1.0);
109                    new_vars[j] = (foods[i].variables[j] + phi * (foods[i].variables[j] - foods[k].variables[j])).clamp(lower[j], upper[j]);
110
111                    let new_fitness = problem.fitness(&new_vars);
112                    if new_fitness < foods[i].fitness {
113                        foods[i] = Individual::new(new_vars, new_fitness);
114                        trial_counters[i] = 0;
115                    } else {
116                        trial_counters[i] += 1;
117                    }
118                }
119                t = (t + 1) % pop_size;
120            }
121
122            // 4. Scout Bees Phase
123            for i in 0..pop_size {
124                if trial_counters[i] > limit {
125                    // Reset
126                    let mut vars = Array1::zeros(dim);
127                    for j in 0..dim {
128                        vars[j] = rng.gen_range(lower[j]..upper[j]);
129                    }
130                    let fitness = problem.fitness(&vars);
131                    foods[i] = Individual::new(vars, fitness);
132                    trial_counters[i] = 0;
133                }
134            }
135
136            // Update best
137            for i in 0..pop_size {
138                if foods[i].fitness < best_fitness {
139                    best_fitness = foods[i].fitness;
140                    best_vars = foods[i].variables.clone();
141                }
142            }
143        }
144
145        OptimizationResult {
146            best_variables: best_vars,
147            best_fitness,
148            history,
149        }
150    }
151}