use rand::prelude::*;
use rand::rngs::StdRng;
use crate::{
error::Result, initialisation::Initialisation, ConstraintHandler, OptimiserResult, Solution,
Variable,
};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
#[derive(Debug, Copy, Clone)]
pub struct PsoParams {
pub inertia: f64,
pub cognitive_coeff: f64,
pub social_coeff: f64,
}
impl Default for PsoParams {
fn default() -> Self {
PsoParams {
inertia: 0.5,
cognitive_coeff: 1.5,
social_coeff: 1.5,
}
}
}
#[derive(Clone, Debug)]
struct Particle {
position: Vec<f64>,
velocity: Vec<f64>,
best_position: Vec<f64>,
best_fitness: f64,
}
pub fn pso<F>(
func: &mut F,
vars: &[Variable],
max_iter: usize,
n_particles: usize,
params: &PsoParams,
initialisation: Initialisation,
constraint_handler: Option<ConstraintHandler>,
seed: Option<u64>,
) -> Result<OptimiserResult>
where
F: FnMut(&[f64]) -> (Vec<f64>, Option<Vec<f64>>),
{
let num_dimensions = vars.len();
let mut rng = seed.map_or_else(StdRng::from_entropy, StdRng::seed_from_u64);
let initial_positions = initialisation.generate_samples(n_particles, vars, &mut rng);
let mut swarm: Vec<Particle> = initial_positions
.into_iter()
.map(|position| {
let velocity = vec![0.0; num_dimensions];
let (objectives, constraints) = func(&position);
let fitness = match constraint_handler {
Some(constraint_handler) => {
objectives.iter().sum::<f64>()
+ constraint_handler.calculate_penalty(&constraints)
}
None => objectives.iter().sum::<f64>(),
};
Particle {
position: position.clone(),
velocity,
best_position: position,
best_fitness: fitness,
}
})
.collect();
let (global_best_position, global_best_fitness) = swarm
.iter()
.min_by(|a, b| a.best_fitness.partial_cmp(&b.best_fitness).unwrap())
.map(|p| (p.best_position.clone(), p.best_fitness))
.unwrap_or((vec![], f64::INFINITY));
let mut best_known_position = global_best_position;
let mut best_known_fitness = global_best_fitness;
for _ in 0..max_iter {
for particle in &mut swarm {
let (objectives, constraints) = func(&particle.position);
let fitness = match constraint_handler {
Some(constraint_handler) => {
objectives.iter().sum::<f64>()
+ constraint_handler.calculate_penalty(&constraints)
}
None => objectives.iter().sum::<f64>(),
};
if fitness < particle.best_fitness {
particle.best_fitness = fitness;
particle.best_position = particle.position.clone();
}
if fitness < best_known_fitness {
best_known_fitness = fitness;
best_known_position = particle.position.clone();
}
}
for particle in &mut swarm {
for i in 0..num_dimensions {
let r1: f64 = rng.gen();
let r2: f64 = rng.gen();
let cognitive_component = params.cognitive_coeff
* r1
* (particle.best_position[i] - particle.position[i]);
let social_component =
params.social_coeff * r2 * (best_known_position[i] - particle.position[i]);
particle.velocity[i] = (params.inertia * particle.velocity[i])
+ cognitive_component
+ social_component;
particle.position[i] += particle.velocity[i];
if particle.position[i] < vars[i].0 {
particle.position[i] = vars[i].0;
particle.velocity[i] = 0.0; } else if particle.position[i] > vars[i].1 {
particle.position[i] = vars[i].1;
particle.velocity[i] = 0.0; }
}
}
}
let (best_objectives, best_constraints) = func(&best_known_position);
Ok(OptimiserResult::new(
vec![Solution::new(
best_known_position,
best_objectives,
best_constraints,
)],
max_iter, ))
}
#[cfg(feature = "parallel")]
pub fn pso_par<F>(
func: &F, vars: &[Variable],
n_particles: usize,
max_iter: usize,
params: &PsoParams,
initialisation: Initialisation,
constraint_handler: Option<ConstraintHandler>,
seed: Option<u64>,
) -> Result<OptimiserResult>
where
F: Fn(&[f64]) -> (Vec<f64>, Option<Vec<f64>>) + Sync + Send,
{
let num_dimensions = vars.len();
let mut rng = seed.map_or_else(StdRng::from_entropy, StdRng::seed_from_u64);
let initial_positions = initialisation.generate_samples(n_particles, vars, &mut rng);
let mut swarm: Vec<Particle> = initial_positions
.into_par_iter() .map(|position| {
let velocity = vec![0.0; num_dimensions];
let (objectives, constraints) = func(&position);
let fitness = match constraint_handler {
Some(constraint_handler) => {
objectives.iter().sum::<f64>()
+ constraint_handler.calculate_penalty(&constraints)
}
None => objectives.iter().sum::<f64>(),
};
Particle {
position: position.clone(),
velocity,
best_position: position,
best_fitness: fitness,
}
})
.collect();
let (mut best_known_position, mut best_known_fitness) = swarm
.iter()
.min_by(|a, b| a.best_fitness.partial_cmp(&b.best_fitness).unwrap())
.map(|p| (p.best_position.clone(), p.best_fitness))
.unwrap_or((vec![], f64::INFINITY));
for _ in 0..max_iter {
let fitness_values: Vec<f64> = swarm
.par_iter()
.map(|p| {
let (objectives, constraints) = func(&p.position);
match constraint_handler {
Some(constraint_handler) => {
objectives.iter().sum::<f64>()
+ constraint_handler.calculate_penalty(&constraints)
}
None => objectives.iter().sum::<f64>(),
}
})
.collect();
for (i, particle) in swarm.iter_mut().enumerate() {
let fitness = fitness_values[i];
if fitness < particle.best_fitness {
particle.best_fitness = fitness;
particle.best_position = particle.position.clone();
}
if fitness < best_known_fitness {
best_known_fitness = fitness;
best_known_position = particle.position.clone();
}
}
let r1_values: Vec<f64> = (0..n_particles * num_dimensions)
.map(|_| rng.gen())
.collect();
let r2_values: Vec<f64> = (0..n_particles * num_dimensions)
.map(|_| rng.gen())
.collect();
swarm
.par_iter_mut()
.enumerate()
.for_each(|(p_idx, particle)| {
for i in 0..num_dimensions {
let r1 = r1_values[p_idx * num_dimensions + i];
let r2 = r2_values[p_idx * num_dimensions + i];
let cognitive = params.cognitive_coeff
* r1
* (particle.best_position[i] - particle.position[i]);
let social =
params.social_coeff * r2 * (best_known_position[i] - particle.position[i]);
particle.velocity[i] =
(params.inertia * particle.velocity[i]) + cognitive + social;
particle.position[i] += particle.velocity[i];
if particle.position[i] < vars[i].0 {
particle.position[i] = vars[i].0;
particle.velocity[i] = 0.0;
} else if particle.position[i] > vars[i].1 {
particle.position[i] = vars[i].1;
particle.velocity[i] = 0.0;
}
}
});
}
let (best_objectives, best_constraints) = func(&best_known_position);
Ok(OptimiserResult::new(
vec![Solution::new(
best_known_position,
best_objectives,
best_constraints,
)],
max_iter, ))
}