use std::time::Instant;
pub mod error;
pub mod initialisation;
pub mod nsga;
pub mod pso;
use crate::{
error::Result,
initialisation::Initialisation,
nsga::{nsga, PmParams, SbxParams},
pso::{pso, PsoParams},
};
#[cfg(feature = "parallel")]
use crate::nsga::nsga_par;
#[cfg(feature = "parallel")]
use crate::pso::pso_par;
#[derive(Debug, Clone)]
pub struct Variable(pub f64, pub f64);
#[derive(Debug)]
pub enum Optimiser {
Nsga {
pop_size: usize,
crossover: SbxParams,
mutation: PmParams,
seed: Option<u64>,
},
Pso {
n_particles: usize,
params: PsoParams,
constraint_handler: Option<ConstraintHandler>,
seed: Option<u64>,
},
}
impl Optimiser {
pub fn solve<F>(
&self,
func: &mut F,
vars: &[Variable],
max_iter: usize,
) -> Result<OptimiserResult>
where
F: FnMut(&[f64]) -> (Vec<f64>, Option<Vec<f64>>),
{
let now = Instant::now();
let mut result = match &self {
Optimiser::Nsga {
pop_size,
crossover,
mutation,
seed,
} => nsga(
func,
vars,
max_iter,
*pop_size,
crossover,
mutation,
Initialisation::LatinHypercube { centred: true },
*seed,
)?,
Optimiser::Pso {
n_particles,
params,
constraint_handler,
seed,
} => pso(
func,
vars,
max_iter,
*n_particles,
params,
Initialisation::LatinHypercube { centred: true },
*constraint_handler,
*seed,
)?,
};
result.execution_time = now.elapsed().as_secs_f64();
Ok(result)
}
#[cfg(feature = "parallel")]
pub fn solve_par<F>(
&self,
func: &F,
vars: &[Variable],
max_iter: usize,
) -> Result<OptimiserResult>
where
F: Fn(&[f64]) -> (Vec<f64>, Option<Vec<f64>>) + Sync + Send,
{
let now = Instant::now();
let mut result = match self {
Optimiser::Nsga {
pop_size,
crossover,
mutation,
seed,
} => nsga_par(
func,
vars,
max_iter,
*pop_size,
crossover,
mutation,
Initialisation::LatinHypercube { centred: true },
*seed,
)?,
Optimiser::Pso {
n_particles,
params,
constraint_handler,
seed,
} => pso_par(
func,
vars,
max_iter,
*n_particles,
params,
Initialisation::LatinHypercube { centred: true },
*constraint_handler,
*seed,
)?,
};
result.execution_time = now.elapsed().as_secs_f64();
Ok(result)
}
}
#[derive(Debug, Clone)]
pub struct OptimiserResult {
pub solutions: Vec<Solution>,
pub n_iterations: usize,
pub execution_time: f64,
}
impl OptimiserResult {
pub fn new(solutions: Vec<Solution>, n_iterations: usize) -> Self {
OptimiserResult {
solutions,
n_iterations,
execution_time: 0.0,
}
}
pub fn best_solution(&self) -> Option<&Solution> {
if self.solutions.is_empty() {
return None;
}
if self.solutions.len() == 1 {
return Some(&self.solutions[0]);
}
self.solutions.iter().min_by(|a, b| {
let f_a = a.f.first().unwrap_or(&f64::INFINITY);
let f_b = b.f.first().unwrap_or(&f64::INFINITY);
f_a.partial_cmp(f_b).unwrap_or(std::cmp::Ordering::Equal)
})
}
}
#[derive(Debug, Clone)]
pub struct Solution {
pub x: Vec<f64>,
pub f: Vec<f64>,
pub g: Option<Vec<f64>>,
}
impl Solution {
pub fn new(x: Vec<f64>, f: Vec<f64>, g: Option<Vec<f64>>) -> Self {
Solution { x, f, g }
}
}
#[derive(Debug, Clone, Copy)]
pub enum ConstraintHandler {
Penalty { multiplier: f64 },
}
impl ConstraintHandler {
fn calculate_penalty(&self, constraints: &Option<Vec<f64>>) -> f64 {
match self {
ConstraintHandler::Penalty { multiplier } => {
constraints
.as_deref() .unwrap_or(&[]) .iter()
.filter(|&&c| c > 0.0) .map(|&c| c * c) .sum::<f64>()
* multiplier
}
}
}
}
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use super::*;
fn quadratic_problem(x: &[f64]) -> (Vec<f64>, Option<Vec<f64>>) {
(vec![(x[0] - 5.0).powi(2)], None)
}
#[test]
fn test_optimiser_solve_nsga_dispatch() {
let optimiser = Optimiser::Nsga {
pop_size: 10,
crossover: SbxParams::default(),
mutation: PmParams::default(),
seed: Some(1),
};
let vars = vec![Variable(0.0, 10.0)];
let mut func = |x: &[f64]| quadratic_problem(x);
let result = optimiser.solve(&mut func, &vars, 10);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.n_iterations, 10);
assert_abs_diff_eq!(result.solutions[0].x[0], 4.999, epsilon = 1e-3);
}
#[test]
fn test_optimiser_solve_pso_dispatch() {
let optimiser = Optimiser::Pso {
n_particles: 10,
params: PsoParams::default(),
constraint_handler: None,
seed: Some(1),
};
let vars = vec![Variable(0.0, 10.0)];
let mut func = |x: &[f64]| quadratic_problem(x);
let result = optimiser.solve(&mut func, &vars, 10);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.n_iterations, 10);
assert_abs_diff_eq!(result.solutions[0].x[0], 4.991, epsilon = 1e-3);
}
}