sefar/algos/eo/
ieo.rs

1//
2// Implementation of Improved Equilibrium Optimizer (EO)
3//
4
5extern crate rand;
6use rand::distributions::{Distribution, Uniform};
7//use rand::prelude::ThreadRng;
8use std::fmt::Display;
9use std::time::Instant;
10
11#[cfg(feature = "parallel")]
12use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
13
14use crate::common::*;
15use crate::core::eoa::{InitializationMode, EOA};
16use crate::core::genome::Genome;
17use crate::core::optimization_result::OptimizationResult;
18use crate::core::parameters::Parameters;
19use crate::core::problem::Problem;
20
21///
22/// Equilibrium Optimizer (EO)
23/// Reference:
24/// "Faramarzi, A., Heidarinejad, M., Stephens, B., & Mirjalili, S. (2020).
25/// Equilibrium optimizer: A novel optimization algorithm. Knowledge-Based Systems, 191, 105190."
26///
27#[derive(Debug)]
28pub struct IEO<'a, T: Problem> {
29    pub problem: &'a mut T,
30    pub params: &'a IEOparams<'a>,
31}
32
33impl<'a, T: Problem> IEO<'a, T> {
34    pub fn new(settings: &'a IEOparams, problem: &'a mut T) -> Self {
35        IEO {
36            problem,
37            params: settings,
38        }
39    }
40}
41
42impl<'a, T: Problem> EOA for IEO<'a, T> {
43    fn run(&mut self) -> OptimizationResult {
44        let chronos = Instant::now();
45
46        //check paramaters
47        //let params = self.params.clone();
48
49        match self.params.check() {
50            Err(error) => OptimizationResult::get_none(error),
51            Ok(()) => {
52                let dim = self.params.get_problem_dimension();
53                let particles_no = self.params.get_population_size();
54                let max_iter = self.params.get_max_iterations();
55
56                let ub = self.params.get_upper_bounds();
57
58                let lb = self.params.get_lower_bounds();
59
60                // a1=2;
61                // a2=1;
62                // GP=0.5;
63                let a1: f64 = self.params.a1;
64                let a2: f64 = self.params.a2;
65                let gp: f64 = self.params.gp;
66                let nu: f64 = f64::min(self.params.pool_size_rate, 1.0);
67                // Iter=0; V=1;
68                let mut iter = 0;
69                let v: f64 = 1.0;
70
71                // to store agents fitness values
72                let mut ceq1_fit: f64 = f64::MAX;
73                //let mut ceq1_index: usize = 0;
74
75                let mut ceq1: Genome = Genome::new(particles_no + 1, dim);
76
77                let mut fitness = vec![0.0f64; particles_no];
78                let mut fit_old = vec![0.0f64; particles_no];
79                let mut c_old = vec![vec![0.0f64; dim]; particles_no];
80
81                let mut lambda = vec![0.0f64; dim];
82                let mut r = vec![0.0f64; dim];
83                let mut r1 = vec![0.0f64; dim];
84                let mut r2 = vec![0.0f64; dim];
85                let mut ceq = vec![0.0f64; dim];
86                let mut f = vec![0.0f64; dim];
87                let mut _gcp: f64 = 0.0;
88                //------------------------------------------
89                //let between01 = Uniform::from(0.0..=1.0);
90                let mut rng = rand::thread_rng();
91                //------------------------------------------
92
93                let mut convergence_curve = vec![0.0f64; max_iter];
94                let mut _index: usize = 0;
95                let mut _g0: f64 = 0.0;
96                let mut _g: f64 = 0.0;
97
98                //C=initialization(Particles_no,dim,ub,lb);
99
100                let mut c: Vec<Genome> =
101                    self.initialize(self.params, InitializationMode::RealUniform);
102
103                // the main loop of EO
104                while iter < max_iter {
105                    // space bound
106                    for i in 0..c.len() {
107                        for j in 0..dim {
108                            if c[i].genes[j] < lb[j] {
109                                c[i].genes[j] = lb[j];
110                            }
111
112                            if c[i].genes[j] > ub[j] {
113                                c[i].genes[j] = ub[j];
114                            }
115                        }
116                    }
117
118                    // compute fitness for search agents
119                    // Sequential mode
120                    #[cfg(not(feature = "parallel"))]
121                    for i in 0..particles_no {
122                        fitness[i] = self.problem.objectivefunction(&mut c[i].genes);
123                        c[i].fitness = Some(fitness[i]);
124                        //fobj(&c[i]);
125                    }
126
127                    // Parallel mode
128                    //___________Parallel mode________________
129                    #[cfg(feature = "parallel")]
130                    {
131                        c.par_iter_mut().for_each(|g| {
132                            g.fitness = Some(self.problem.objectivefunction(&g.genes))
133                        });
134                        for i in 0..particles_no {
135                            match c[i].fitness {
136                                None => fitness[i] = f64::MAX,
137                                Some(fit) => fitness[i] = fit,
138                            };
139                        }
140                        //println!("EO: Parallel objective function evaluation was done.");
141                    }
142                    //________________________________________
143
144                    //-- Memory saving---
145
146                    if iter == 0 {
147                        copy_vector(&fitness, &mut fit_old);
148                        copy_matrix(&c, &mut c_old);
149                    }
150
151                    for i in 0..particles_no {
152                        if fit_old[i] < fitness[i] {
153                            fitness[i] = fit_old[i];
154                            copy_vector2genome(&c_old[i], &mut c[i]);
155                        }
156                    }
157
158                    copy_matrix(&c, &mut c_old);
159                    copy_vector(&fitness, &mut fit_old);
160
161                    //--------------------------- MODIFIED EO -------------------------------
162
163                    // compute the size of the equilibrium pool
164
165                    let jpool: usize =
166                        (nu * particles_no as f64 * (1.0 - (iter as f64 / max_iter as f64))).ceil()
167                            as usize;
168
169                    // create a matrix to store the pool elements
170                    let mut c_pool = vec![vec![0.0f64; dim]; jpool + 1];
171
172                    // interval to choose a random element from the equilibrium pool
173                    let interval = Uniform::from(0..jpool);
174
175                    // sort the candidate solutions to choose the equilibrium pool
176                    let mut ind: Vec<usize> = (0..particles_no).collect();
177                    ind.sort_by(|&a, &b| fitness[a].total_cmp(&fitness[b]));
178
179                    for i in 0..jpool {
180                        for j in 0..dim {
181                            c_pool[i][j] = c[ind[i]].genes[j];
182                        }
183                    }
184
185                    // Save the best solution and the best fitness :
186                    if ceq1_fit > fitness[ind[0]] {
187                        ceq1_fit = fitness[ind[0]];
188                        copy_vector2genome(&c[ind[0]].genes, &mut ceq1);
189                        ceq1.fitness = Some(ceq1_fit);
190                    }
191
192                    //println!("fitness = {:?} \n ind = {:?}", fitness, ind);
193                    // compute the average solution
194                    let mut sum_value: f64 = 0.0;
195                    for j in 0..dim {
196                        for i in 0..jpool {
197                            sum_value += c_pool[i][j];
198                        }
199                        c_pool[jpool][j] = sum_value / jpool as f64;
200                        sum_value = 0.0;
201                    }
202
203                    // comput t using Eq 09
204                    let tmpt = (iter / max_iter) as f64;
205                    let t: f64 = (1.0 - tmpt).powf(a2 * tmpt);
206
207                    for i in 0..particles_no {
208                        randomize(&mut lambda);
209
210                        randomize(&mut r); //  r=rand(1,dim);  r in Eq(11
211
212                        //-------------------------------------------------------
213                        // Ceq=C_pool(randi(size(C_pool,1)),:);
214                        // random selection of one candidate from the pool
215                        _index = interval.sample(&mut rng);
216                        copy_vector(&c_pool[_index], &mut ceq);
217                        //--------------------------------------------------------
218                        // compute F using Eq(11)
219                        for j in 0..dim {
220                            f[j] = a1
221                                * f64::signum(r[j] - 0.5)
222                                * (f64::exp(-1.0 * lambda[j] * t) - 1.0);
223                        }
224
225                        // r1 and r2 to use them in Eq(15)
226                        randomize(&mut r1);
227                        randomize(&mut r2);
228
229                        for j in 0..dim {
230                            // Eq. 15
231                            if r2[j] > gp {
232                                _gcp = 0.5 * r1[j];
233                            } else {
234                                _gcp = 0.0f64;
235                            }
236
237                            // Eq. 14
238                            _g0 = _gcp * (ceq[j] - lambda[j] * c[i].genes[j]);
239
240                            // Eq 13
241                            _g = _g0 * f[j];
242
243                            // Eq. 16
244                            c[i].genes[j] = ceq[j]
245                                + (c[i].genes[j] - ceq[j]) * f[j]
246                                + (_g / (lambda[j] * v)) * (1.0 - f[j]);
247                        }
248                    }
249
250                    // let duration = chronos.elapsed();
251                    // println!("seq--> End computation in : {:?}", duration);
252
253                    convergence_curve[iter] = ceq1_fit;
254                    iter += 1;
255
256                    #[cfg(feature = "report")]
257                    println!("Iter : {}, Best-fit : {}", iter, ceq1_fit);
258                }
259
260                //return results
261                let duration = chronos.elapsed();
262                let result = OptimizationResult {
263                    best_genome: Some(ceq1),
264                    best_fitness: Some(ceq1_fit),
265                    convergence_trend: Some(convergence_curve),
266                    computation_time: Some(duration),
267                    err_report: None,
268                };
269                return result;
270            }
271        }
272    }
273}
274/// Define parameters for Improved Equilibrium Optimizer
275#[derive(Debug, Clone)]
276pub struct IEOparams<'a> {
277    /// number of search agents (population size)
278    pub population_size: usize,
279
280    /// problem dimension (i.e., number of decision variables)
281    pub problem_dimension: usize,
282
283    /// maximum number of iterations
284    pub max_iterations: usize,
285
286    /// search space lower bounds
287    pub lower_bounds: &'a [f64],
288
289    /// search space upper bounds,
290    pub upper_bounds: &'a [f64],
291
292    /// EO parameter
293    pub a1: f64,
294    /// EO parameter
295    pub a2: f64,
296    /// EO parameter
297    pub gp: f64,
298    /// This parameter should be in [0, 1]. It controls the rate of equilibrium pool size reduction.
299    pub pool_size_rate: f64,
300}
301
302#[allow(dead_code)]
303impl<'a> IEOparams<'a> {
304    pub fn new(
305        p_size: usize,
306        dim: usize,
307        max_iter: usize,
308        lb: &'a [f64],
309        ub: &'a [f64],
310        a1: f64,
311        a2: f64,
312        gp: f64,
313        pool_size_rate: f64,
314    ) -> Result<IEOparams<'a>, String> {
315        let params = IEOparams {
316            population_size: p_size,
317            problem_dimension: dim,
318            max_iterations: max_iter,
319            lower_bounds: lb,
320            upper_bounds: ub,
321            a1,
322            a2,
323            gp,
324            pool_size_rate,
325        };
326
327        match params.check() {
328            Err(error) => Err(error),
329            Ok(()) => Ok(params),
330        }
331    }
332}
333
334impl<'a> Parameters for IEOparams<'a> {
335    fn get_population_size(&self) -> usize {
336        self.population_size
337    }
338
339    fn get_problem_dimension(&self) -> usize {
340        self.problem_dimension
341    }
342
343    fn get_max_iterations(&self) -> usize {
344        self.max_iterations
345    }
346
347    fn get_lower_bounds(&self) -> Vec<f64> {
348        self.lower_bounds.to_vec()
349    }
350
351    fn get_upper_bounds(&self) -> Vec<f64> {
352        self.upper_bounds.to_vec()
353    }
354}
355
356impl<'a> Default for IEOparams<'a> {
357    ///
358    /// Return default values of parameters, as following :
359    ///
360    /// ~~~
361    ///
362    ///  use sefar::algos::eo::ieo::*;
363    ///
364    ///  IEOparams{
365    ///     population_size : 10,
366    ///     problem_dimension : 3,
367    ///     max_iterations : 100,
368    ///     lower_bounds : &[100.0f64, 100.0, 100.0],
369    ///     upper_bounds : &[-100.0f64, -100.0, -100.0],
370    ///     a1 : 2.0f64,
371    ///     a2 : 1.0f64,
372    ///     gp : 0.5f64,
373    ///     pool_size_rate: 0.2,
374    /// };
375    /// ~~~
376    ///
377    fn default() -> Self {
378        IEOparams {
379            population_size: 10,
380            problem_dimension: 3,
381            max_iterations: 100,
382            lower_bounds: &[-100.0f64, -100.0, -100.0],
383            upper_bounds: &[100.0f64, 100.0, 100.0],
384            a1: 2.0f64,
385            a2: 1.0f64,
386            gp: 0.5f64,
387            pool_size_rate: 0.2,
388        }
389    }
390}
391
392impl<'a> Display for IEOparams<'a> {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        write!(
395            f,
396            "Pop.Size: {}, Problem dim.: {}, Max.Iter: {}, a1: {}, a2: {}, GP: {}, Pool Size Rate:{}, LB: {:?}, UB: {:?}",
397            self.population_size,
398            self.problem_dimension,
399            self.max_iterations,
400            self.a1,
401            self.a2,
402            self.gp,
403            self.pool_size_rate,
404            self.get_lower_bounds(),
405            self.get_upper_bounds()
406         )
407    }
408}
409
410#[cfg(test)]
411mod ieo_params_tests {
412    use super::*;
413
414    #[test]
415    fn test_ub_slice() {
416        let d: usize = 5;
417        let n: usize = 10;
418        let k: usize = 100;
419
420        let ub = vec![1.0f64, 2.0, 3.0, 4.0, 5.0];
421        let lb = ub.clone();
422
423        let params = IEOparams {
424            population_size: n,
425            max_iterations: k,
426            problem_dimension: d,
427            lower_bounds: lb.as_slice(),
428            upper_bounds: ub.as_slice(),
429            a1: 2.0f64,
430            a2: 1.0f64,
431            gp: 0.5f64,
432            pool_size_rate: 0.0625,
433        };
434
435        let sl_ub = vec![1.0f64, 2.0, 3.0, 4.0, 5.0];
436        let slice_ub = sl_ub.as_slice();
437
438        assert_eq!(params.upper_bounds, slice_ub);
439    }
440
441    #[test]
442    fn test_default_fn() {
443        let p = IEOparams::default();
444        assert_eq!(p.a1, 2.0f64);
445        assert_eq!(p.a2, 1.0f64);
446        assert_eq!(p.gp, 0.50f64);
447    }
448
449    #[test]
450    fn eoparams_unwrap_or_default_test_1() {
451        let _ub = vec![1.0f64, 2.0, 3.0, 4.0, 5.0];
452        let _lb = vec![-1.0f64, -2.0, -3.0, -4.0, -5.0];
453
454        let p = IEOparams::new(
455            10,
456            10,
457            100,
458            _lb.as_slice(),
459            _ub.as_slice(),
460            0.5,
461            0.5,
462            0.5,
463            0.2,
464        )
465        .unwrap_or_default();
466        assert_eq!(p.a1, 2.0f64);
467        assert_eq!(p.a2, 1.0f64);
468        assert_eq!(p.gp, 0.50f64);
469    }
470
471    #[test]
472    fn eoparams_unwrap_or_default_test_2() {
473        let _ub = vec![1.0f64, 2.0, 3.0, 4.0, 5.0];
474        let _lb = vec![-1.0f64, -2.0, -3.0, -4.0, -5.0];
475
476        let p = IEOparams::new(
477            10,
478            5,
479            100,
480            _lb.as_slice(),
481            _ub.as_slice(),
482            0.5,
483            0.5,
484            0.5,
485            0.2,
486        )
487        .unwrap_or_default();
488        assert_eq!(p.a1, 0.50f64);
489        assert_eq!(p.a2, 0.50f64);
490        assert_eq!(p.gp, 0.50f64);
491    }
492}