use rand_chacha::ChaChaRng;
use super::strategy::{RestartControl, Strategy};
use super::InvalidRestartStrategyOptionsError;
use crate::{CMAESOptions, TerminationData, TerminationReason, CMAES};
const DEFAULT_INCREASE_FACTOR: usize = 2;
const MAX_RUNS: usize = 10;
#[derive(Clone, Debug)]
pub struct IPOP {
runs: usize,
current_multiplier: usize,
increase_factor: usize,
}
impl IPOP {
pub fn new(increase_factor: usize) -> Result<Self, InvalidRestartStrategyOptionsError> {
if increase_factor == 0 {
Err(InvalidRestartStrategyOptionsError::PopulationSize)
} else {
Ok(Self {
runs: 0,
current_multiplier: 1,
increase_factor,
})
}
}
fn get_initial_step_size(&self, search_range_size: f64) -> f64 {
search_range_size / 2.0
}
}
impl Default for IPOP {
fn default() -> Self {
Self::new(DEFAULT_INCREASE_FACTOR).unwrap()
}
}
impl Strategy for IPOP {
fn has_zero_max_runs(&self) -> bool {
false
}
fn next_run<F, R: FnOnce(&mut CMAES<F>) -> TerminationData>(
&mut self,
mut options: CMAESOptions,
search_range_size: f64,
objective_function: F,
run: R,
_: &mut ChaChaRng,
) -> (CMAES<F>, Vec<TerminationReason>, RestartControl) {
options.initial_step_size = self.get_initial_step_size(search_range_size);
options.population_size *= self.current_multiplier;
let mut cmaes_state = options.build(objective_function).unwrap();
let results = run(&mut cmaes_state);
self.runs += 1;
self.current_multiplier *= self.increase_factor;
let control = if self.runs >= MAX_RUNS {
RestartControl::MaxRunsReached
} else {
RestartControl::Continue
};
(cmaes_state, results.reasons, control)
}
fn get_algorithm_name(&self) -> &'static str {
"IPOP-aCMA-ES"
}
fn get_parameters_as_strings(&self) -> Vec<(String, String)> {
[(
"increase_factor".to_string(),
format!("{}", self.increase_factor),
)]
.into()
}
}
#[cfg(test)]
mod tests {
use nalgebra::DVector;
use rand::SeedableRng;
use super::*;
#[test]
fn test_new() {
assert!(IPOP::new(1).is_ok());
assert!(matches!(
IPOP::new(0),
Err(InvalidRestartStrategyOptionsError::PopulationSize)
));
}
#[test]
fn test_get_initial_step_size() {
let ipop = IPOP::default();
assert_eq!(50.0, ipop.get_initial_step_size(100.0));
}
#[test]
fn test_ipop() {
let mut ipop = IPOP::default();
let function = |x: &DVector<f64>| x.magnitude();
for i in 0..10 {
let (_, _, control) = ipop.next_run(
CMAESOptions::new(vec![1.0; 2], 0.5),
1.0,
function,
|state| state.run(),
&mut ChaChaRng::seed_from_u64(rand::random()),
);
assert_eq!(i + 1 == 10, control.should_terminate());
}
assert_eq!(10, ipop.runs);
assert_eq!(2usize.pow(10), ipop.current_multiplier);
assert_eq!(2, ipop.increase_factor);
}
}