use core::num::NonZeroUsize;
use rand::Rng;
use crate::gene::Gene;
use crate::neighbour::Neighbour;
use crate::operators::mutation::Mutation;
use super::mutations_by_rate;
#[derive(Copy, Clone, Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct Nudge {
pub maximum_distance: NonZeroUsize,
pub rate: f64,
}
impl<G, const GENES: usize, RNG> Mutation<[G; GENES], RNG> for Nudge
where
G: Gene + Neighbour,
RNG: Rng,
{
fn mutate(&self, genotype: &mut [G; GENES], rng: &mut RNG) {
assert!(
self.rate >= 0.0_f64,
"mutation rate has to be above or equal 0.0, but is {}",
self.rate
);
assert!(
self.rate <= 1.0_f64,
"mutation rate has to be below or equal 1.0, but is {}",
self.rate
);
#[allow(clippy::indexing_slicing)]
for _ in 0..mutations_by_rate(GENES, self.rate) {
let random_slot = rng.gen_range(0..GENES);
let selected_gene = &mut genotype[random_slot];
let possible_new_gene = if rng.gen() {
selected_gene.random_forward_neighbour(rng, self.maximum_distance)
} else {
selected_gene.random_backward_neighbour(rng, self.maximum_distance)
};
if let Some(new_gene) = possible_new_gene {
*selected_gene = new_gene;
}
}
}
}
impl<G, RNG> Mutation<Vec<G>, RNG> for Nudge
where
G: Gene + Neighbour,
RNG: Rng,
{
fn mutate(&self, genotype: &mut Vec<G>, rng: &mut RNG) {
assert!(
self.rate >= 0.0_f64,
"mutation rate has to be above or equal 0.0, but is {}",
self.rate
);
assert!(
self.rate <= 1.0_f64,
"mutation rate has to be below or equal 1.0, but is {}",
self.rate
);
#[allow(clippy::indexing_slicing)]
for _ in 0..mutations_by_rate(genotype.len(), self.rate) {
let random_slot = rng.gen_range(0..genotype.len());
let selected_gene = &mut genotype[random_slot];
let possible_new_gene = if rng.gen() {
selected_gene.random_forward_neighbour(rng, self.maximum_distance)
} else {
selected_gene.random_backward_neighbour(rng, self.maximum_distance)
};
if let Some(new_gene) = possible_new_gene {
*selected_gene = new_gene;
}
}
}
}
impl<RNG> Mutation<String, RNG> for Nudge
where
RNG: Rng,
{
fn mutate(&self, genotype: &mut String, rng: &mut RNG) {
assert!(
self.rate >= 0.0_f64,
"mutation rate has to be above or equal 0.0, but is {}",
self.rate
);
assert!(
self.rate <= 1.0_f64,
"mutation rate has to be below or equal 1.0, but is {}",
self.rate
);
let mut chars = genotype.chars().collect::<Vec<_>>();
#[allow(clippy::indexing_slicing)]
for _ in 0..mutations_by_rate(chars.len(), self.rate) {
let random_slot = rng.gen_range(0..chars.len());
let selected_gene = &mut chars[random_slot];
let possible_new_gene = if rng.gen() {
selected_gene.random_forward_neighbour(rng, self.maximum_distance)
} else {
selected_gene.random_backward_neighbour(rng, self.maximum_distance)
};
if let Some(new_gene) = possible_new_gene {
*selected_gene = new_gene;
}
}
*genotype = chars.iter().collect::<String>();
}
}
#[cfg(test)]
mod tests {
use rand::rngs::SmallRng;
use rand::SeedableRng;
use super::{Mutation, Nudge};
#[test]
fn array() {
let mutation = Nudge {
maximum_distance: 1.try_into().unwrap(),
rate: 0.4,
};
let mut rng = SmallRng::seed_from_u64(1);
let mut array = [0_u8, 1, 2, 3, 4];
mutation.mutate(&mut array, &mut rng);
assert_eq!(array, [0, 1, 3, 2, 4]);
}
#[test]
fn vector() {
let mutation = Nudge {
maximum_distance: 1.try_into().unwrap(),
rate: 0.2,
};
let mut rng = SmallRng::seed_from_u64(0);
let mut array = vec![0_u8, 1, 2, 3, 4];
mutation.mutate(&mut array, &mut rng);
assert_eq!(array, [0, 1, 1, 3, 4]);
}
#[test]
fn string() {
let mutation = Nudge {
maximum_distance: 1.try_into().unwrap(),
rate: 0.25,
};
let mut rng = SmallRng::seed_from_u64(4);
let mut string = String::from("test");
mutation.mutate(&mut string, &mut rng);
assert_eq!(string, "tfst");
}
}