use rand::{Rng, RngCore};
use serde::{Deserialize, Serialize};
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::core::{Individual, OError, Problem, VariableType, VariableValue};
pub trait Mutation {
fn mutate_offspring(
&self,
individual: &Individual,
rng: &mut dyn RngCore,
) -> Result<Individual, OError>;
}
#[cfg_attr(feature = "python", pyclass(get_all))]
#[derive(Serialize, Deserialize, Clone)]
pub struct PolynomialMutationArgs {
pub index_parameter: f64,
pub variable_probability: f64,
}
impl PolynomialMutationArgs {
pub fn default(problem: &Problem) -> Self {
let num_real_vars = problem
.variables()
.iter()
.filter(|(_, v)| v.is_real())
.count() as f64;
let variable_probability = 1.0 / num_real_vars;
Self {
index_parameter: 20.0,
variable_probability,
}
}
}
#[cfg(feature = "python")]
#[pymethods]
impl PolynomialMutationArgs {
#[new]
#[pyo3(signature = (variable_probability, index_parameter=None))]
fn new(variable_probability: f64, index_parameter: Option<f64>) -> Self {
PolynomialMutationArgs {
index_parameter: index_parameter.unwrap_or(20.0),
variable_probability,
}
}
pub fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"PolynomialMutationArgs(index_parameter={}, variable_probability={})",
self.index_parameter, self.variable_probability
))
}
fn __str__(&self) -> String {
self.__repr__().unwrap()
}
}
pub struct PolynomialMutation {
index_parameter: f64,
variable_probability: f64,
}
impl PolynomialMutation {
pub fn new(args: PolynomialMutationArgs) -> Result<Self, OError> {
if !(0.0..=1.0).contains(&args.variable_probability) {
return Err(OError::MutationOperator(
"PolynomialMutation".to_string(),
format!(
"The variable probability {} must be a number between 0 and 1",
args.variable_probability
),
));
}
Ok(Self {
index_parameter: args.index_parameter,
variable_probability: args.variable_probability,
})
}
fn mutate_variable(&self, y: f64, y_lower: f64, y_upper: f64, rng: &mut dyn RngCore) -> f64 {
let delta_y = y_upper - y_lower;
let prob = rng.random_range(0.0..=1.0);
let delta = if prob <= 0.5 {
let bl = (y - y_lower) / delta_y;
let b =
2.0 * prob + (1.0 - 2.0 * prob) * f64::powf(1.0 - bl, self.index_parameter + 1.0);
f64::powf(b, 1.0 / (self.index_parameter + 1.0)) - 1.0
} else {
let bu = (y_upper - y) / delta_y;
let b = 2.0 * (1.0 - prob)
+ 2.0 * (prob - 0.5) * f64::powf(1.0 - bu, self.index_parameter + 1.0);
1.0 - f64::powf(b, 1.0 / (self.index_parameter + 1.0))
};
let new_y = y + delta * delta_y;
f64::min(f64::max(new_y, y_lower), y_upper)
}
}
impl Mutation for PolynomialMutation {
fn mutate_offspring(
&self,
individual: &Individual,
rng: &mut dyn RngCore,
) -> Result<Individual, OError> {
let mut mutated_individual = individual.clone_variables();
let problem = individual.problem();
if !problem
.variables()
.iter()
.all(|(_, v)| v.is_real() | v.is_integer())
{
return Err(OError::CrossoverOperator(
"PolynomialMutation".to_string(),
"The PM operator only works with real or integer variables".to_string(),
));
}
for (var_name, var_type) in problem.variables() {
if rng.random_range(0.0..=1.0) <= self.variable_probability {
let y = individual.get_variable_value(&var_name)?;
if let (VariableValue::Real(y), VariableType::Real(vt)) = (y, &var_type) {
let (y_lower, y_upper) = vt.bounds();
let new_y = self.mutate_variable(*y, y_lower, y_upper, rng);
mutated_individual.update_variable(&var_name, VariableValue::Real(new_y))?;
} else if let (VariableValue::Integer(y), VariableType::Integer(vt)) = (y, var_type)
{
let (y_lower, y_upper) = vt.bounds();
let new_y =
self.mutate_variable(*y as f64, y_lower as f64, y_upper as f64, rng);
let mut new_y = new_y.trunc() as i64;
if rng.random_range(0.0..=1.0) < 0.5 {
new_y += 1;
new_y = new_y.min(y_upper);
}
mutated_individual.update_variable(&var_name, VariableValue::Integer(new_y))?;
}
}
}
Ok(mutated_individual)
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use crate::core::utils::{dummy_evaluator, get_rng};
use crate::core::{
BoundedNumber, Individual, Objective, ObjectiveDirection, Problem, VariableType,
VariableValue,
};
use crate::operators::{Mutation, PolynomialMutation, PolynomialMutationArgs};
#[test]
fn test_sbx_crossover() {
let objectives = vec![Objective::new("obj1", ObjectiveDirection::Minimise)];
let variables = vec![
VariableType::Real(BoundedNumber::new("var1", 0.0, 1000.0).unwrap()),
VariableType::Integer(BoundedNumber::new("var2", -10, 20).unwrap()),
];
let problem =
Arc::new(Problem::new(objectives, variables, None, dummy_evaluator()).unwrap());
let mut a = Individual::new(problem.clone());
a.update_variable("var1", VariableValue::Real(0.2)).unwrap();
a.update_variable("var2", VariableValue::Integer(0))
.unwrap();
let mut b = Individual::new(problem.clone());
b.update_variable("var1", VariableValue::Real(0.8)).unwrap();
b.update_variable("var2", VariableValue::Integer(3))
.unwrap();
let args = PolynomialMutationArgs {
index_parameter: 1.0,
variable_probability: 1.0,
};
let pm = PolynomialMutation::new(args).unwrap();
let mut rng = get_rng(Some(1));
let mutated_offspring = pm.mutate_offspring(&a, &mut rng).unwrap();
assert_ne!(
*mutated_offspring.get_variable_value("var1").unwrap(),
VariableValue::Real(0.2)
);
assert_ne!(
*mutated_offspring.get_variable_value("var2").unwrap(),
VariableValue::Integer(0)
);
}
}