Skip to main content

graphmind_optimization/algorithms/
bat.rs

1use crate::common::{Individual, OptimizationResult, Problem, SolverConfig};
2use ndarray::Array1;
3use rand::prelude::*;
4
5pub struct BatSolver {
6    pub config: SolverConfig,
7    pub f_min: f64,
8    pub f_max: f64,
9    pub alpha: f64, // Constant for loudness update (0.9)
10    pub gamma: f64, // Constant for emission rate update (0.9)
11}
12
13impl BatSolver {
14    pub fn new(config: SolverConfig) -> Self {
15        Self {
16            config,
17            f_min: 0.0,
18            f_max: 2.0,
19            alpha: 0.9,
20            gamma: 0.9,
21        }
22    }
23
24    pub fn solve<P: Problem>(&self, problem: &P) -> OptimizationResult {
25        let mut rng = thread_rng();
26        let dim = problem.dim();
27        let (lower, upper) = problem.bounds();
28
29        // 1. Initialize Bats
30        let mut population: Vec<Individual> = (0..self.config.population_size)
31            .map(|_| {
32                let mut vars = Array1::zeros(dim);
33                for i in 0..dim {
34                    vars[i] = rng.gen_range(lower[i]..upper[i]);
35                }
36                let fitness = problem.fitness(&vars);
37                Individual::new(vars, fitness)
38            })
39            .collect();
40
41        let mut velocities: Vec<Array1<f64>> = (0..self.config.population_size)
42            .map(|_| Array1::zeros(dim))
43            .collect();
44
45        let mut frequencies = vec![0.0; self.config.population_size];
46        let mut loudnesses = vec![1.0; self.config.population_size];
47        let mut emission_rates = vec![0.5; self.config.population_size];
48        let r0 = 0.5; // initial emission rate
49
50        // Initial best
51        let mut best_idx = 0;
52        for i in 1..population.len() {
53            if population[i].fitness < population[best_idx].fitness {
54                best_idx = i;
55            }
56        }
57        let mut best_vars = population[best_idx].variables.clone();
58        let mut best_fitness = population[best_idx].fitness;
59
60        let mut history = Vec::with_capacity(self.config.max_iterations);
61
62        for iter in 0..self.config.max_iterations {
63            history.push(best_fitness);
64
65            for i in 0..self.config.population_size {
66                // Update frequency
67                let beta: f64 = rng.gen();
68                frequencies[i] = self.f_min + (self.f_max - self.f_min) * beta;
69
70                // Update velocity and position
71                for j in 0..dim {
72                    velocities[i][j] +=
73                        (population[i].variables[j] - best_vars[j]) * frequencies[i];
74                    population[i].variables[j] =
75                        (population[i].variables[j] + velocities[i][j]).clamp(lower[j], upper[j]);
76                }
77
78                // Local search around best
79                if rng.gen::<f64>() > emission_rates[i] {
80                    let mut temp_vars = best_vars.clone();
81                    let avg_loudness: f64 =
82                        loudnesses.iter().sum::<f64>() / (self.config.population_size as f64);
83                    for j in 0..dim {
84                        let epsilon = (rng.gen::<f64>() - 0.5) * 2.0;
85                        temp_vars[j] =
86                            (temp_vars[j] + epsilon * avg_loudness).clamp(lower[j], upper[j]);
87                    }
88
89                    let temp_fitness = problem.fitness(&temp_vars);
90
91                    // Accept new solution
92                    if temp_fitness < population[i].fitness && rng.gen::<f64>() < loudnesses[i] {
93                        population[i].variables = temp_vars;
94                        population[i].fitness = temp_fitness;
95
96                        // Update loudness and emission rate
97                        loudnesses[i] *= self.alpha;
98                        emission_rates[i] = r0 * (1.0 - (-self.gamma * (iter as f64)).exp());
99                    }
100                } else {
101                    population[i].fitness = problem.fitness(&population[i].variables);
102                }
103
104                // Update global best
105                if population[i].fitness < best_fitness {
106                    best_vars = population[i].variables.clone();
107                    best_fitness = population[i].fitness;
108                }
109            }
110        }
111
112        OptimizationResult {
113            best_variables: best_vars,
114            best_fitness,
115            history,
116        }
117    }
118}