Skip to main content

graphmind_optimization/algorithms/
hs.rs

1use crate::common::{Individual, OptimizationResult, Problem, SolverConfig};
2use ndarray::Array1;
3use rand::prelude::*;
4
5pub struct HSSolver {
6    pub config: SolverConfig,
7    pub hmcr: f64, // Harmony Memory Consideration Rate (0.7-0.95)
8    pub par: f64,  // Pitch Adjustment Rate (0.1-0.5)
9    pub bw: f64,   // Bandwidth (step size)
10}
11
12impl HSSolver {
13    pub fn new(config: SolverConfig) -> Self {
14        Self {
15            config,
16            hmcr: 0.9,
17            par: 0.3,
18            bw: 0.01,
19        }
20    }
21
22    pub fn solve<P: Problem>(&self, problem: &P) -> OptimizationResult {
23        let mut rng = thread_rng();
24        let dim = problem.dim();
25        let (lower, upper) = problem.bounds();
26
27        // 1. Initialize Harmony Memory (HM)
28        let mut hm: Vec<Individual> = (0..self.config.population_size)
29            .map(|_| {
30                let mut vars = Array1::zeros(dim);
31                for i in 0..dim {
32                    vars[i] = rng.gen_range(lower[i]..upper[i]);
33                }
34                let fitness = problem.fitness(&vars);
35                Individual::new(vars, fitness)
36            })
37            .collect();
38
39        let mut history = Vec::with_capacity(self.config.max_iterations);
40
41        for _iter in 0..self.config.max_iterations {
42            // Find current best for history
43            let mut best_idx = 0;
44            let mut worst_idx = 0;
45            for i in 1..hm.len() {
46                if hm[i].fitness < hm[best_idx].fitness {
47                    best_idx = i;
48                }
49                if hm[i].fitness > hm[worst_idx].fitness {
50                    worst_idx = i;
51                }
52            }
53            history.push(hm[best_idx].fitness);
54
55            // 2. Improvise a New Harmony
56            let mut new_vars = Array1::zeros(dim);
57            for j in 0..dim {
58                if rng.gen::<f64>() < self.hmcr {
59                    // Memory Consideration: pick from HM
60                    let r_idx = rng.gen_range(0..self.config.population_size);
61                    new_vars[j] = hm[r_idx].variables[j];
62
63                    // Pitch Adjustment
64                    if rng.gen::<f64>() < self.par {
65                        let range = upper[j] - lower[j];
66                        let delta = (rng.gen::<f64>() - 0.5) * 2.0 * self.bw * range;
67                        new_vars[j] = (new_vars[j] + delta).clamp(lower[j], upper[j]);
68                    }
69                } else {
70                    // Random selection
71                    new_vars[j] = rng.gen_range(lower[j]..upper[j]);
72                }
73            }
74
75            let new_fitness = problem.fitness(&new_vars);
76
77            // 3. Update Harmony Memory
78            if new_fitness < hm[worst_idx].fitness {
79                hm[worst_idx] = Individual::new(new_vars, new_fitness);
80            }
81        }
82
83        // Final sort to find best
84        hm.sort_by(|a, b| a.fitness.partial_cmp(&b.fitness).unwrap());
85        let best = &hm[0];
86
87        OptimizationResult {
88            best_variables: best.variables.clone(),
89            best_fitness: best.fitness,
90            history,
91        }
92    }
93}