Skip to main content

samyama_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] += (population[i].variables[j] - best_vars[j]) * frequencies[i];
73                    population[i].variables[j] = (population[i].variables[j] + velocities[i][j]).clamp(lower[j], upper[j]);
74                }
75
76                // Local search around best
77                if rng.gen::<f64>() > emission_rates[i] {
78                    let mut temp_vars = best_vars.clone();
79                    let avg_loudness: f64 = loudnesses.iter().sum::<f64>() / (self.config.population_size as f64);
80                    for j in 0..dim {
81                        let epsilon = (rng.gen::<f64>() - 0.5) * 2.0;
82                        temp_vars[j] = (temp_vars[j] + epsilon * avg_loudness).clamp(lower[j], upper[j]);
83                    }
84                    
85                    let temp_fitness = problem.fitness(&temp_vars);
86                    
87                    // Accept new solution
88                    if temp_fitness < population[i].fitness && rng.gen::<f64>() < loudnesses[i] {
89                        population[i].variables = temp_vars;
90                        population[i].fitness = temp_fitness;
91                        
92                        // Update loudness and emission rate
93                        loudnesses[i] *= self.alpha;
94                        emission_rates[i] = r0 * (1.0 - (-self.gamma * (iter as f64)).exp());
95                    }
96                } else {
97                    population[i].fitness = problem.fitness(&population[i].variables);
98                }
99
100                // Update global best
101                if population[i].fitness < best_fitness {
102                    best_vars = population[i].variables.clone();
103                    best_fitness = population[i].fitness;
104                }
105            }
106        }
107
108        OptimizationResult {
109            best_variables: best_vars,
110            best_fitness,
111            history,
112        }
113    }
114}