graphmind_optimization/algorithms/
motlbo.rs1use crate::common::{
2 MultiObjectiveIndividual, MultiObjectiveProblem, MultiObjectiveResult, SolverConfig,
3};
4use ndarray::Array1;
5use rand::prelude::*;
6
7pub struct MOTLBOSolver {
8 pub config: SolverConfig,
9}
10
11impl MOTLBOSolver {
12 pub fn new(config: SolverConfig) -> Self {
13 Self { config }
14 }
15
16 pub fn solve<P: MultiObjectiveProblem>(&self, problem: &P) -> MultiObjectiveResult {
17 let mut rng = thread_rng();
18 let dim = problem.dim();
19 let (lower, upper) = problem.bounds();
20 let pop_size = self.config.population_size;
21
22 let mut population: Vec<MultiObjectiveIndividual> = (0..pop_size)
24 .map(|_| {
25 let mut vars = Array1::zeros(dim);
26 for i in 0..dim {
27 vars[i] = rng.gen_range(lower[i]..upper[i]);
28 }
29 let fitness = problem.objectives(&vars);
30 MultiObjectiveIndividual::new(vars, fitness)
31 })
32 .collect();
33
34 self.evaluate_population(&mut population);
35
36 let mut history = Vec::with_capacity(self.config.max_iterations);
37
38 for _iter in 0..self.config.max_iterations {
39 let mut offspring = Vec::with_capacity(pop_size * 2);
40
41 let teacher_idx = self.select_teacher(&population);
43 let teacher_vars = population[teacher_idx].variables.clone();
44 let mean_vars = self.calculate_mean(&population, dim);
45
46 for ind in &population {
47 let mut local_rng = thread_rng();
48 let tf: f64 = local_rng.gen_range(1..3) as f64;
49 let mut new_vars = Array1::zeros(dim);
50 for j in 0..dim {
51 let r: f64 = local_rng.gen();
52 new_vars[j] = (ind.variables[j] + r * (teacher_vars[j] - tf * mean_vars[j]))
53 .clamp(lower[j], upper[j]);
54 }
55 offspring.push(MultiObjectiveIndividual::new(
56 new_vars.clone(),
57 problem.objectives(&new_vars),
58 ));
59 }
60
61 for i in 0..pop_size {
63 let mut local_rng = thread_rng();
64 let mut j;
65 loop {
66 j = local_rng.gen_range(0..pop_size);
67 if j != i {
68 break;
69 }
70 }
71
72 let mut new_vars = Array1::zeros(dim);
73 let dominates_i_j = self.dominates(&population[i].fitness, &population[j].fitness);
74 let dominates_j_i = self.dominates(&population[j].fitness, &population[i].fitness);
75
76 for k in 0..dim {
77 let r: f64 = local_rng.gen();
78 if dominates_i_j {
79 new_vars[k] = (population[i].variables[k]
80 + r * (population[i].variables[k] - population[j].variables[k]))
81 .clamp(lower[k], upper[k]);
82 } else if dominates_j_i {
83 new_vars[k] = (population[i].variables[k]
84 + r * (population[j].variables[k] - population[i].variables[k]))
85 .clamp(lower[k], upper[k]);
86 } else {
87 new_vars[k] = population[i].variables[k];
89 }
90 }
91 offspring.push(MultiObjectiveIndividual::new(
92 new_vars.clone(),
93 problem.objectives(&new_vars),
94 ));
95 }
96
97 let mut combined = population;
99 combined.extend(offspring);
100 self.evaluate_population(&mut combined);
101
102 combined.sort_by(|a, b| {
104 if a.rank != b.rank {
105 a.rank.cmp(&b.rank)
106 } else {
107 b.crowding_distance
108 .partial_cmp(&a.crowding_distance)
109 .unwrap()
110 }
111 });
112
113 combined.truncate(pop_size);
114 population = combined;
115
116 history.push(population[0].fitness[0]);
117 }
118
119 MultiObjectiveResult {
120 pareto_front: population.into_iter().filter(|ind| ind.rank == 0).collect(),
121 history,
122 }
123 }
124
125 fn select_teacher(&self, population: &[MultiObjectiveIndividual]) -> usize {
126 let first_rank: Vec<usize> = population
128 .iter()
129 .enumerate()
130 .filter(|(_, ind)| ind.rank == 0)
131 .map(|(i, _)| i)
132 .collect();
133
134 let mut rng = thread_rng();
135 *first_rank.choose(&mut rng).unwrap_or(&0)
136 }
137
138 fn calculate_mean(&self, population: &[MultiObjectiveIndividual], dim: usize) -> Array1<f64> {
139 let mut mean = Array1::zeros(dim);
140 for ind in population {
141 mean += &ind.variables;
142 }
143 mean / (population.len() as f64)
144 }
145
146 fn evaluate_population(&self, population: &mut [MultiObjectiveIndividual]) {
148 self.non_dominated_sort(population);
149 let mut rank = 0;
150 loop {
151 let indices: Vec<usize> = population
152 .iter()
153 .enumerate()
154 .filter(|(_, ind)| ind.rank == rank)
155 .map(|(i, _)| i)
156 .collect();
157 if indices.is_empty() {
158 break;
159 }
160 self.calculate_crowding_distance(population, &indices);
161 rank += 1;
162 }
163 }
164
165 fn non_dominated_sort(&self, population: &mut [MultiObjectiveIndividual]) {
166 let n = population.len();
167 let mut dominance_counts = vec![0; n];
168 let mut dominated_sets = vec![Vec::new(); n];
169 let mut fronts = vec![Vec::new()];
170
171 for i in 0..n {
172 for j in 0..n {
173 if i == j {
174 continue;
175 }
176 if self.dominates(&population[i].fitness, &population[j].fitness) {
177 dominated_sets[i].push(j);
178 } else if self.dominates(&population[j].fitness, &population[i].fitness) {
179 dominance_counts[i] += 1;
180 }
181 }
182 if dominance_counts[i] == 0 {
183 population[i].rank = 0;
184 fronts[0].push(i);
185 }
186 }
187
188 let mut i = 0;
189 while !fronts[i].is_empty() {
190 let mut next_front = Vec::new();
191 for &p in &fronts[i] {
192 for &q in &dominated_sets[p] {
193 dominance_counts[q] -= 1;
194 if dominance_counts[q] == 0 {
195 population[q].rank = i + 1;
196 next_front.push(q);
197 }
198 }
199 }
200 i += 1;
201 fronts.push(next_front);
202 }
203 }
204
205 fn dominates(&self, f1: &[f64], f2: &[f64]) -> bool {
206 let mut better = false;
207 for i in 0..f1.len() {
208 if f1[i] > f2[i] {
209 return false;
210 }
211 if f1[i] < f2[i] {
212 better = true;
213 }
214 }
215 better
216 }
217
218 fn calculate_crowding_distance(
219 &self,
220 population: &mut [MultiObjectiveIndividual],
221 indices: &[usize],
222 ) {
223 let num_objectives = population[0].fitness.len();
224 for &idx in indices {
225 population[idx].crowding_distance = 0.0;
226 }
227
228 for m in 0..num_objectives {
229 let mut sorted_indices = indices.to_vec();
230 sorted_indices.sort_by(|&a, &b| {
231 population[a].fitness[m]
232 .partial_cmp(&population[b].fitness[m])
233 .unwrap()
234 });
235
236 let min_val = population[*sorted_indices.first().unwrap()].fitness[m];
237 let max_val = population[*sorted_indices.last().unwrap()].fitness[m];
238 let range = max_val - min_val;
239
240 population[*sorted_indices.first().unwrap()].crowding_distance = f64::INFINITY;
241 population[*sorted_indices.last().unwrap()].crowding_distance = f64::INFINITY;
242
243 if range > 1e-9 {
244 for i in 1..(sorted_indices.len() - 1) {
245 let prev = population[sorted_indices[i - 1]].fitness[m];
246 let next = population[sorted_indices[i + 1]].fitness[m];
247 population[sorted_indices[i]].crowding_distance += (next - prev) / range;
248 }
249 }
250 }
251 }
252}