Skip to main content

graphmind_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 {
25            pop_size * dim / 2
26        } else {
27            self.limit
28        };
29
30        // 1. Initialize Food Sources (Population)
31        let mut foods: Vec<Individual> = (0..pop_size)
32            .map(|_| {
33                let mut vars = Array1::zeros(dim);
34                for i in 0..dim {
35                    vars[i] = rng.gen_range(lower[i]..upper[i]);
36                }
37                let fitness = problem.fitness(&vars);
38                Individual::new(vars, fitness)
39            })
40            .collect();
41
42        let mut trial_counters = vec![0; pop_size];
43
44        let mut best_idx = 0;
45        for i in 1..pop_size {
46            if foods[i].fitness < foods[best_idx].fitness {
47                best_idx = i;
48            }
49        }
50        let mut best_vars = foods[best_idx].variables.clone();
51        let mut best_fitness = foods[best_idx].fitness;
52
53        let mut history = Vec::with_capacity(self.config.max_iterations);
54
55        for _iter in 0..self.config.max_iterations {
56            history.push(best_fitness);
57
58            // 2. Employed Bees Phase
59            for i in 0..pop_size {
60                let mut new_vars = foods[i].variables.clone();
61                let j = rng.gen_range(0..dim);
62                let mut k;
63                loop {
64                    k = rng.gen_range(0..pop_size);
65                    if k != i {
66                        break;
67                    }
68                }
69
70                let phi: f64 = rng.gen_range(-1.0..1.0);
71                new_vars[j] = (foods[i].variables[j]
72                    + phi * (foods[i].variables[j] - foods[k].variables[j]))
73                    .clamp(lower[j], upper[j]);
74
75                let new_fitness = problem.fitness(&new_vars);
76                if new_fitness < foods[i].fitness {
77                    foods[i] = Individual::new(new_vars, new_fitness);
78                    trial_counters[i] = 0;
79                } else {
80                    trial_counters[i] += 1;
81                }
82            }
83
84            // 3. Onlooker Bees Phase
85            // Calculate probabilities based on fitness
86            // Fitness in ABC is often 1/(1+f) if f>=0 else 1+|f|
87            let mut probs = vec![0.0; pop_size];
88            let mut total_f = 0.0;
89            for i in 0..pop_size {
90                let f_val = if foods[i].fitness >= 0.0 {
91                    1.0 / (1.0 + foods[i].fitness)
92                } else {
93                    1.0 + foods[i].fitness.abs()
94                };
95                probs[i] = f_val;
96                total_f += f_val;
97            }
98            for prob in probs.iter_mut() {
99                *prob /= total_f;
100            }
101
102            let mut m = 0;
103            let mut t = 0;
104            while m < pop_size {
105                if rng.gen::<f64>() < probs[t] {
106                    m += 1;
107                    let i = t;
108                    let mut new_vars = foods[i].variables.clone();
109                    let j = rng.gen_range(0..dim);
110                    let mut k;
111                    loop {
112                        k = rng.gen_range(0..pop_size);
113                        if k != i {
114                            break;
115                        }
116                    }
117
118                    let phi: f64 = rng.gen_range(-1.0..1.0);
119                    new_vars[j] = (foods[i].variables[j]
120                        + phi * (foods[i].variables[j] - foods[k].variables[j]))
121                        .clamp(lower[j], upper[j]);
122
123                    let new_fitness = problem.fitness(&new_vars);
124                    if new_fitness < foods[i].fitness {
125                        foods[i] = Individual::new(new_vars, new_fitness);
126                        trial_counters[i] = 0;
127                    } else {
128                        trial_counters[i] += 1;
129                    }
130                }
131                t = (t + 1) % pop_size;
132            }
133
134            // 4. Scout Bees Phase
135            for i in 0..pop_size {
136                if trial_counters[i] > limit {
137                    // Reset
138                    let mut vars = Array1::zeros(dim);
139                    for j in 0..dim {
140                        vars[j] = rng.gen_range(lower[j]..upper[j]);
141                    }
142                    let fitness = problem.fitness(&vars);
143                    foods[i] = Individual::new(vars, fitness);
144                    trial_counters[i] = 0;
145                }
146            }
147
148            // Update best
149            for food in foods.iter() {
150                if food.fitness < best_fitness {
151                    best_fitness = food.fitness;
152                    best_vars = food.variables.clone();
153                }
154            }
155        }
156
157        OptimizationResult {
158            best_variables: best_vars,
159            best_fitness,
160            history,
161        }
162    }
163}