optlib/particleswarm/velocitycalc/
mod.rs

1use rand::distributions::{Distribution, Uniform};
2use rand::rngs::ThreadRng;
3
4use num::{Float, Num, NumCast};
5
6use crate::particleswarm::{Particle, VelocityCalculator, Swarm};
7
8/// ClassicVelocityCalculator implements the equation from the article
9/// Kennedy, J.; Eberhart, R. (1995). "Particle Swarm Optimization".
10/// Proceedings of IEEE International Conference on Neural Networks IV, pp.1942-1948.
11/// v_i = v_i + phi_p * r_p * (p_i - x_i) + phi_g * r_g * (g_i - x_i)
12/// `v_i` - velocity projection for dimension i,
13/// `p_i` - personal best coordinate,
14/// `g_i` - global best coordinate,
15/// `x_i` - current coordinate,
16/// `phi_p`, `phi_g` - parameters,
17/// `r_p`, `r_g` - random values in (0, 1)
18pub struct ClassicVelocityCalculator<T> {
19    phi_personal: T,
20    phi_global: T,
21
22    random: ThreadRng,
23}
24
25impl<T> ClassicVelocityCalculator<T> {
26    pub fn new(phi_personal: T, phi_global: T) -> Self {
27        Self {
28            phi_personal,
29            phi_global,
30            random: rand::thread_rng(),
31        }
32    }
33}
34
35impl<T: NumCast + Num + Copy> VelocityCalculator<T> for ClassicVelocityCalculator<T> {
36    fn calc_new_velocity(&mut self, swarm: &Swarm<T>, particle: &Particle<T>) -> Vec<T> {
37        let dimension = particle.coordinates.len();
38        let global_best_particle = swarm.best_particle.as_ref().unwrap();
39        let global_best_solution = &global_best_particle.coordinates;
40
41        let between = Uniform::new_inclusive(0.0_f32, 1.0_f32);
42        let mut new_velocity = Vec::with_capacity(dimension);
43        for i in 0..dimension {
44            let r_personal = T::from(between.sample(&mut self.random)).unwrap();
45            let r_global = T::from(between.sample(&mut self.random)).unwrap();
46
47            let velocity_item = particle.velocity[i]
48                + self.phi_personal
49                    * r_personal
50                    * (particle.best_personal_coordinates[i] - particle.coordinates[i])
51                + self.phi_global * r_global * (global_best_solution[i] - particle.coordinates[i]);
52            new_velocity.push(velocity_item);
53        }
54
55        new_velocity
56    }
57}
58
59/// CanonicalVelocityCalculator implements the "canonical" equation:
60/// v_i = xi * (v_i + phi_p * r_p * (p_i - x_i) + phi_g * r_g * (g_i - x_i))
61/// `v_i` - velocity projection for dimension i,
62/// `p_i` - personal best coordinate,
63/// `g_i` - global best coordinate,
64/// `x_i` - current coordinate,
65/// `phi_p`, `phi_g` - parameters,
66/// `r_p`, `r_g` - random values in (0, 1),
67/// `xi` = 2 * alpha / (phi - 2),
68/// `phi` = phi_p + phi_g
69/// `alpha` in (0, 1),
70/// `phi` must be greater than 4
71pub struct CanonicalVelocityCalculator<T> {
72    phi_personal: T,
73    phi_global: T,
74    xi: T,
75
76    random: ThreadRng,
77}
78
79impl<T: Float> CanonicalVelocityCalculator<T> {
80    pub fn new(phi_personal: T, phi_global: T, alpha: T) -> Self {
81        assert!(phi_personal + phi_global > T::from(4.0).unwrap());
82        assert!(alpha > T::zero());
83        assert!(alpha < T::one());
84
85        let phi = phi_global + phi_personal;
86        let xi = T::from(2.0).unwrap() * alpha / (phi - T::from(2.0).unwrap());
87        Self {
88            phi_personal,
89            phi_global,
90            xi,
91            random: rand::thread_rng(),
92        }
93    }
94}
95
96impl<T: NumCast + Num + Copy> VelocityCalculator<T> for CanonicalVelocityCalculator<T> {
97    fn calc_new_velocity(&mut self, swarm: &Swarm<T>, particle: &Particle<T>) -> Vec<T> {
98        let dimension = particle.coordinates.len();
99        let global_best_particle = swarm.best_particle.as_ref().unwrap();
100        let global_best_solution = &global_best_particle.coordinates;
101
102        let between = Uniform::new_inclusive(0.0_f32, 1.0_f32);
103        let mut new_velocity = Vec::with_capacity(dimension);
104        for i in 0..dimension {
105            let r_personal = T::from(between.sample(&mut self.random)).unwrap();
106            let r_global = T::from(between.sample(&mut self.random)).unwrap();
107
108            let velocity_item = self.xi
109                * (particle.velocity[i]
110                    + self.phi_personal
111                        * r_personal
112                        * (particle.best_personal_coordinates[i] - particle.coordinates[i])
113                    + self.phi_global
114                        * r_global
115                        * (global_best_solution[i] - particle.coordinates[i]));
116            new_velocity.push(velocity_item);
117        }
118
119        new_velocity
120    }
121}
122
123/// Velocity update with negative reinforcement, global and current best and worst positions.
124/// v_i = xi * (v_i
125///     + phi_best_personal * rb_p * (p_best_i - x_i)
126///     + phi_best_current * rb_c * (c_best_i - x_i)
127///     + phi_best_global * rb_g * (g_best_i - x_i)
128///     - phi_worst_personal * rw_p * (p_worst_i - x_i)
129///     - phi_worst_current * rw_c * (c_worst_i - x_i)
130///     - phi_worst_global * rw_g * (g_worst_i - x_i))
131///
132/// `v_i` - velocity projection for dimension i,
133/// `p_best_i` - personal best coordinate,
134/// `c_best_i` - best coordinate for current swarm,
135/// `g_best_i` - global best coordinate,
136/// `p_worst_i` - personal worst coordinate,
137/// `c_worst_i` - worst coordinate for current swarm,
138/// `g_worst_i` - global worst coordinate,
139/// `xi` - parameter,
140/// `x_i` - current coordinate,
141/// `phi_best_personal`, `phi_best_current`, `phi_best_global`, `phi_worst_personal`, `phi_worst_current`, `phi_worst_global` - parameters,
142/// `rb_p`, `rb_c`, `rb_g`, `rw_p`, `rw_c`, `rw_g` - random values in (0, 1),
143pub struct NegativeReinforcement<T> {
144    phi_best_personal: T,
145    phi_best_current: T,
146    phi_best_global: T,
147
148    phi_worst_personal: T,
149    phi_worst_current: T,
150    phi_worst_global: T,
151
152    xi: T,
153
154    random: ThreadRng,
155}
156impl<T> NegativeReinforcement<T> {
157    pub fn new(
158        phi_best_personal: T,
159        phi_best_current: T,
160        phi_best_global: T,
161        phi_worst_personal: T,
162        phi_worst_current: T,
163        phi_worst_global: T,
164        xi: T,
165    ) -> Self {
166        Self {
167            phi_best_personal,
168            phi_best_current,
169            phi_best_global,
170            phi_worst_personal,
171            phi_worst_current,
172            phi_worst_global,
173            xi,
174            random: rand::thread_rng(),
175        }
176    }
177}
178
179impl<T: NumCast + Num + Copy> VelocityCalculator<T> for NegativeReinforcement<T> {
180    fn calc_new_velocity(&mut self, swarm: &Swarm<T>, particle: &Particle<T>) -> Vec<T> {
181        let dimension = particle.coordinates.len();
182
183        let global_best_particle = swarm.best_particle.as_ref().unwrap();
184        let global_best_solution = &global_best_particle.coordinates;
185
186        let current_best_particle = swarm.get_current_best_particle().unwrap();
187        let current_best_solution = current_best_particle.coordinates;
188
189        let global_worst_particle = swarm.worst_particle.as_ref().unwrap();
190        let global_worst_solution = &global_worst_particle.coordinates;
191
192        let current_worst_particle = swarm.get_current_worst_particle().unwrap();
193        let current_worst_solution = current_worst_particle.coordinates;
194
195        let between = Uniform::new_inclusive(0.0, 1.0);
196        let mut new_velocity = Vec::with_capacity(dimension);
197        for i in 0..dimension {
198            let r_best_global = T::from(between.sample(&mut self.random)).unwrap();
199            let r_best_current = T::from(between.sample(&mut self.random)).unwrap();
200            let r_best_personal = T::from(between.sample(&mut self.random)).unwrap();
201
202            let r_worst_global = T::from(between.sample(&mut self.random)).unwrap();
203            let r_worst_current = T::from(between.sample(&mut self.random)).unwrap();
204            let r_worst_personal = T::from(between.sample(&mut self.random)).unwrap();
205
206            let v_best_personal = self.phi_best_personal
207                * r_best_personal
208                * (particle.best_personal_coordinates[i] - particle.coordinates[i]);
209            let v_best_current = self.phi_best_current
210                * r_best_current
211                * (current_best_solution[i] - particle.coordinates[i]);
212            let v_best_global = self.phi_best_global
213                * r_best_global
214                * (global_best_solution[i] - particle.coordinates[i]);
215
216            let v_worst_personal = self.phi_worst_personal
217                * r_worst_personal
218                * (particle.worst_personal_coordinates[i] - particle.coordinates[i]);
219            let v_worst_current = self.phi_worst_current
220                * r_worst_current
221                * (current_worst_solution[i] - particle.coordinates[i]);
222            let v_worst_global = self.phi_worst_global
223                * r_worst_global
224                * (global_worst_solution[i] - particle.coordinates[i]);
225
226            let velocity_item = self.xi
227                * (particle.velocity[i] + v_best_personal + v_best_current + v_best_global
228                    - v_worst_personal
229                    - v_worst_current
230                    - v_worst_global);
231            new_velocity.push(velocity_item);
232        }
233
234        new_velocity
235    }
236}
237
238/// The trait to calculate the inertia coefficient (w) for InertiaVelocityCalculator
239pub trait Inertia<T> {
240    fn get(&mut self, iteration: usize) -> T;
241}
242
243
244/// The inertia coefficient (w) does not depend on the iteration number
245pub struct ConstInertia<T> {
246    w: T,
247}
248
249impl<T> ConstInertia<T> {
250    pub fn new(w: T) -> Self {
251        Self { w }
252    }
253}
254
255impl<T: Clone> Inertia<T> for ConstInertia<T> {
256    fn get(&mut self, _iteration: usize) -> T {
257        self.w.clone()
258    }
259}
260
261/// The inertia coefficient decreases linearly from w_max to w_min
262pub struct LinearInertia<T> {
263    w_min: T,
264    w_max: T,
265    t_max: usize,
266}
267
268impl<T: Float> LinearInertia<T> {
269    pub fn new(w_min: T, w_max: T, t_max: usize) -> Self {
270        Self {
271            w_min,
272            w_max,
273            t_max,
274        }
275    }
276}
277
278impl<T: Float> Inertia<T> for LinearInertia<T> {
279    fn get(&mut self, iteration: usize) -> T {
280        self.w_max
281            - (self.w_max - self.w_min) * T::from(iteration).unwrap() / T::from(self.t_max).unwrap()
282    }
283}
284
285/// InertiaVelocityCalculator implements the equation with itertia coefficient w(t)
286/// v_i = w(t) * v_i + phi_personal * r_p * (p_i - x_i) + phi_global * r_g * (g_i - x_i)
287/// `v_i` - velocity projection for dimension i,
288/// `p_i` - personal best coordinate,
289/// `g_i` - global best coordinate,
290/// `x_i` - current coordinate,
291/// `phi_personal`, `phi_global` - parameters,
292/// `r_p`, `r_g` - random values in (0, 1),
293/// `w(t)` calculate with the `Inertia` trait,
294/// `t` - iteration number,
295pub struct InertiaVelocityCalculator<'a, T> {
296    phi_personal: T,
297    phi_global: T,
298    inertia: Box<dyn Inertia<T> + 'a>,
299
300    random: ThreadRng,
301}
302
303impl<'a, T> InertiaVelocityCalculator<'a, T> {
304    pub fn new(phi_personal: T, phi_global: T, inertia: Box<dyn Inertia<T> + 'a>) -> Self {
305        Self {
306            phi_personal,
307            phi_global,
308            inertia,
309            random: rand::thread_rng(),
310        }
311    }
312}
313
314impl<'a, T: NumCast + Num + Copy> VelocityCalculator<T> for InertiaVelocityCalculator<'a, T> {
315    fn calc_new_velocity(&mut self, swarm: &Swarm<T>, particle: &Particle<T>) -> Vec<T> {
316        let dimension = particle.coordinates.len();
317        let global_best_particle = swarm.best_particle.as_ref().unwrap();
318        let global_best_solution = &global_best_particle.coordinates;
319        let inertia_ratio = self.inertia.get(swarm.iteration);
320
321        let between = Uniform::new_inclusive(0.0_f32, 1.0_f32);
322        let mut new_velocity = Vec::with_capacity(dimension);
323        for i in 0..dimension {
324            let r_personal = T::from(between.sample(&mut self.random)).unwrap();
325            let r_global = T::from(between.sample(&mut self.random)).unwrap();
326
327            let velocity_item = inertia_ratio * particle.velocity[i]
328                + self.phi_personal
329                    * r_personal
330                    * (particle.best_personal_coordinates[i] - particle.coordinates[i])
331                + self.phi_global * r_global * (global_best_solution[i] - particle.coordinates[i]);
332            new_velocity.push(velocity_item);
333        }
334
335        new_velocity
336    }
337}