use std::collections::{HashMap, HashSet};
use rand::Rng;
use crate::error::{OperatorError, OperatorResult};
use crate::genome::bit_string::BitString;
use crate::genome::bounds::MultiBounds;
use crate::genome::permutation::Permutation;
use crate::genome::real_vector::RealVector;
use crate::genome::traits::{
BinaryGenome, EvolutionaryGenome, PermutationGenome, RealValuedGenome,
};
use crate::operators::traits::{BoundedCrossoverOperator, CrossoverOperator};
#[derive(Clone, Debug)]
pub struct SbxCrossover {
pub eta: f64,
pub crossover_probability: f64,
pub exchange_probability: f64,
}
impl SbxCrossover {
pub fn new(eta: f64) -> Self {
assert!(eta >= 0.0, "Distribution index must be non-negative");
Self {
eta,
crossover_probability: 0.9,
exchange_probability: 0.5,
}
}
pub fn with_probability(mut self, probability: f64) -> Self {
assert!(
(0.0..=1.0).contains(&probability),
"Probability must be in [0, 1]"
);
self.crossover_probability = probability;
self
}
pub fn with_exchange_probability(mut self, probability: f64) -> Self {
assert!(
(0.0..=1.0).contains(&probability),
"Probability must be in [0, 1]"
);
self.exchange_probability = probability;
self
}
fn spread_factor(&self, u: f64) -> f64 {
if u <= 0.5 {
(2.0 * u).powf(1.0 / (self.eta + 1.0))
} else {
(1.0 / (2.0 * (1.0 - u))).powf(1.0 / (self.eta + 1.0))
}
}
fn sbx_bounded_pair(&self, y1: f64, y2: f64, yl: f64, yu: f64, u: f64) -> (f64, f64) {
let dy = y2 - y1;
let exp = self.eta + 1.0;
let power = 1.0 / exp;
let beta_l = 1.0 + 2.0 * (y1 - yl) / dy;
let alpha_l = 2.0 - beta_l.powf(-exp);
let betaq_l = if u <= 1.0 / alpha_l {
(u * alpha_l).powf(power)
} else {
(1.0 / (2.0 - u * alpha_l)).powf(power)
};
let c_low = 0.5 * ((y1 + y2) - betaq_l * dy);
let beta_u = 1.0 + 2.0 * (yu - y2) / dy;
let alpha_u = 2.0 - beta_u.powf(-exp);
let betaq_u = if u <= 1.0 / alpha_u {
(u * alpha_u).powf(power)
} else {
(1.0 / (2.0 - u * alpha_u)).powf(power)
};
let c_high = 0.5 * ((y1 + y2) + betaq_u * dy);
(c_low.clamp(yl, yu), c_high.clamp(yl, yu))
}
fn apply_sbx<R: Rng>(
&self,
parent1: &[f64],
parent2: &[f64],
bounds: Option<&MultiBounds>,
rng: &mut R,
) -> (Vec<f64>, Vec<f64>) {
let mut child1: Vec<f64> = parent1.to_vec();
let mut child2: Vec<f64> = parent2.to_vec();
for i in 0..parent1.len() {
if rng.gen::<f64>() < self.exchange_probability {
let x1 = parent1[i];
let x2 = parent2[i];
if (x1 - x2).abs() > 1e-14 {
let u = rng.gen::<f64>();
let (c_low, c_high) = match bounds.and_then(|b| b.get(i)) {
Some(bound) => {
let (y1, y2) = if x1 <= x2 { (x1, x2) } else { (x2, x1) };
self.sbx_bounded_pair(y1, y2, bound.min, bound.max, u)
}
None => {
let beta = self.spread_factor(u);
let y1 = 0.5 * ((1.0 + beta) * x1 + (1.0 - beta) * x2);
let y2 = 0.5 * ((1.0 - beta) * x1 + (1.0 + beta) * x2);
(y1, y2)
}
};
if rng.gen::<bool>() {
child1[i] = c_low;
child2[i] = c_high;
} else {
child1[i] = c_high;
child2[i] = c_low;
}
}
}
}
(child1, child2)
}
}
impl CrossoverOperator<RealVector> for SbxCrossover {
fn crossover<R: Rng>(
&self,
parent1: &RealVector,
parent2: &RealVector,
rng: &mut R,
) -> OperatorResult<(RealVector, RealVector)> {
if parent1.dimension() != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
let (child1_genes, child2_genes) =
self.apply_sbx(parent1.genes(), parent2.genes(), None, rng);
let child1 = RealVector::from_genes(child1_genes).unwrap();
let child2 = RealVector::from_genes(child2_genes).unwrap();
OperatorResult::Success((child1, child2))
}
fn crossover_probability(&self) -> f64 {
self.crossover_probability
}
}
impl BoundedCrossoverOperator<RealVector> for SbxCrossover {
fn crossover_bounded<R: Rng>(
&self,
parent1: &RealVector,
parent2: &RealVector,
bounds: &MultiBounds,
rng: &mut R,
) -> OperatorResult<(RealVector, RealVector)> {
if parent1.dimension() != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
let (child1_genes, child2_genes) =
self.apply_sbx(parent1.genes(), parent2.genes(), Some(bounds), rng);
let child1 = RealVector::from_genes(child1_genes).unwrap();
let child2 = RealVector::from_genes(child2_genes).unwrap();
OperatorResult::Success((child1, child2))
}
}
#[derive(Clone, Debug)]
pub struct BlxAlphaCrossover {
pub alpha: f64,
}
impl BlxAlphaCrossover {
pub fn new(alpha: f64) -> Self {
assert!(alpha >= 0.0, "Alpha must be non-negative");
Self { alpha }
}
pub fn default_alpha() -> Self {
Self::new(0.5)
}
}
impl CrossoverOperator<RealVector> for BlxAlphaCrossover {
fn crossover<R: Rng>(
&self,
parent1: &RealVector,
parent2: &RealVector,
rng: &mut R,
) -> OperatorResult<(RealVector, RealVector)> {
if parent1.dimension() != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
let mut child1_genes = Vec::with_capacity(parent1.dimension());
let mut child2_genes = Vec::with_capacity(parent2.dimension());
for i in 0..parent1.dimension() {
let x1 = parent1[i];
let x2 = parent2[i];
let min_val = x1.min(x2);
let max_val = x1.max(x2);
let range = max_val - min_val;
let low = min_val - self.alpha * range;
let high = max_val + self.alpha * range;
child1_genes.push(rng.gen_range(low..=high));
child2_genes.push(rng.gen_range(low..=high));
}
let child1 = RealVector::from_genes(child1_genes).unwrap();
let child2 = RealVector::from_genes(child2_genes).unwrap();
OperatorResult::Success((child1, child2))
}
}
#[derive(Clone, Debug)]
pub struct UniformCrossover {
pub bias: f64,
}
impl UniformCrossover {
pub fn new() -> Self {
Self { bias: 0.5 }
}
pub fn with_bias(bias: f64) -> Self {
assert!((0.0..=1.0).contains(&bias), "Bias must be in [0, 1]");
Self { bias }
}
}
impl Default for UniformCrossover {
fn default() -> Self {
Self::new()
}
}
impl CrossoverOperator<BitString> for UniformCrossover {
fn crossover<R: Rng>(
&self,
parent1: &BitString,
parent2: &BitString,
rng: &mut R,
) -> OperatorResult<(BitString, BitString)> {
if parent1.dimension() != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
let mut child1_bits = Vec::with_capacity(parent1.dimension());
let mut child2_bits = Vec::with_capacity(parent2.dimension());
for i in 0..parent1.dimension() {
if rng.gen::<f64>() < self.bias {
child1_bits.push(parent1[i]);
child2_bits.push(parent2[i]);
} else {
child1_bits.push(parent2[i]);
child2_bits.push(parent1[i]);
}
}
let child1 = BitString::from_bits(child1_bits).unwrap();
let child2 = BitString::from_bits(child2_bits).unwrap();
OperatorResult::Success((child1, child2))
}
}
#[derive(Clone, Debug, Default)]
pub struct OnePointCrossover;
impl OnePointCrossover {
pub fn new() -> Self {
Self
}
}
impl CrossoverOperator<BitString> for OnePointCrossover {
fn crossover<R: Rng>(
&self,
parent1: &BitString,
parent2: &BitString,
rng: &mut R,
) -> OperatorResult<(BitString, BitString)> {
if parent1.dimension() != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
let n = parent1.dimension();
if n == 0 {
return OperatorResult::Success((parent1.clone(), parent2.clone()));
}
let crossover_point = rng.gen_range(0..n);
let mut child1_bits = Vec::with_capacity(n);
let mut child2_bits = Vec::with_capacity(n);
for i in 0..n {
if i < crossover_point {
child1_bits.push(parent1[i]);
child2_bits.push(parent2[i]);
} else {
child1_bits.push(parent2[i]);
child2_bits.push(parent1[i]);
}
}
let child1 = BitString::from_bits(child1_bits).unwrap();
let child2 = BitString::from_bits(child2_bits).unwrap();
OperatorResult::Success((child1, child2))
}
}
#[derive(Clone, Debug, Default)]
pub struct TwoPointCrossover;
impl TwoPointCrossover {
pub fn new() -> Self {
Self
}
}
impl CrossoverOperator<BitString> for TwoPointCrossover {
fn crossover<R: Rng>(
&self,
parent1: &BitString,
parent2: &BitString,
rng: &mut R,
) -> OperatorResult<(BitString, BitString)> {
if parent1.dimension() != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
let n = parent1.dimension();
if n < 2 {
return OperatorResult::Success((parent1.clone(), parent2.clone()));
}
let mut point1 = rng.gen_range(0..n);
let mut point2 = rng.gen_range(0..n);
if point1 > point2 {
std::mem::swap(&mut point1, &mut point2);
}
let mut child1_bits = Vec::with_capacity(n);
let mut child2_bits = Vec::with_capacity(n);
for i in 0..n {
if i < point1 || i >= point2 {
child1_bits.push(parent1[i]);
child2_bits.push(parent2[i]);
} else {
child1_bits.push(parent2[i]);
child2_bits.push(parent1[i]);
}
}
let child1 = BitString::from_bits(child1_bits).unwrap();
let child2 = BitString::from_bits(child2_bits).unwrap();
OperatorResult::Success((child1, child2))
}
}
#[derive(Clone, Debug)]
pub struct ArithmeticCrossover {
pub weight: f64,
}
impl ArithmeticCrossover {
pub fn new(weight: f64) -> Self {
assert!((0.0..=1.0).contains(&weight), "Weight must be in [0, 1]");
Self { weight }
}
pub fn uniform() -> Self {
Self::new(0.5)
}
}
impl CrossoverOperator<RealVector> for ArithmeticCrossover {
fn crossover<R: Rng>(
&self,
parent1: &RealVector,
parent2: &RealVector,
_rng: &mut R,
) -> OperatorResult<(RealVector, RealVector)> {
if parent1.dimension() != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
let w = self.weight;
let mut child1_genes = Vec::with_capacity(parent1.dimension());
let mut child2_genes = Vec::with_capacity(parent2.dimension());
for i in 0..parent1.dimension() {
child1_genes.push(w * parent1[i] + (1.0 - w) * parent2[i]);
child2_genes.push((1.0 - w) * parent1[i] + w * parent2[i]);
}
let child1 = RealVector::from_genes(child1_genes).unwrap();
let child2 = RealVector::from_genes(child2_genes).unwrap();
OperatorResult::Success((child1, child2))
}
}
#[derive(Clone, Debug, Default)]
pub struct PmxCrossover;
impl PmxCrossover {
pub fn new() -> Self {
Self
}
}
impl CrossoverOperator<Permutation> for PmxCrossover {
fn crossover<R: Rng>(
&self,
parent1: &Permutation,
parent2: &Permutation,
rng: &mut R,
) -> OperatorResult<(Permutation, Permutation)> {
let n = parent1.dimension();
if n != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
if n < 2 {
return OperatorResult::Success((parent1.clone(), parent2.clone()));
}
let mut start = rng.gen_range(0..n);
let mut end = rng.gen_range(0..n);
if start > end {
std::mem::swap(&mut start, &mut end);
}
let p1 = parent1.permutation();
let p2 = parent2.permutation();
let mut child1 = vec![usize::MAX; n];
let mut child2 = vec![usize::MAX; n];
for i in start..=end {
child1[i] = p2[i];
child2[i] = p1[i];
}
let mut map1: HashMap<usize, usize> = HashMap::new();
let mut map2: HashMap<usize, usize> = HashMap::new();
for i in start..=end {
map1.insert(p2[i], p1[i]);
map2.insert(p1[i], p2[i]);
}
for i in (0..start).chain((end + 1)..n) {
let mut val1 = p1[i];
while child1[start..=end].contains(&val1) {
val1 = *map1.get(&val1).unwrap_or(&val1);
}
child1[i] = val1;
let mut val2 = p2[i];
while child2[start..=end].contains(&val2) {
val2 = *map2.get(&val2).unwrap_or(&val2);
}
child2[i] = val2;
}
let c1 = match Permutation::try_new(child1) {
Ok(p) => p,
Err(e) => {
return OperatorResult::Failed(OperatorError::CrossoverFailed(format!(
"PMX produced invalid child1: {}",
e
)))
}
};
let c2 = match Permutation::try_new(child2) {
Ok(p) => p,
Err(e) => {
return OperatorResult::Failed(OperatorError::CrossoverFailed(format!(
"PMX produced invalid child2: {}",
e
)))
}
};
OperatorResult::Success((c1, c2))
}
}
#[derive(Clone, Debug, Default)]
pub struct OxCrossover;
impl OxCrossover {
pub fn new() -> Self {
Self
}
}
impl CrossoverOperator<Permutation> for OxCrossover {
fn crossover<R: Rng>(
&self,
parent1: &Permutation,
parent2: &Permutation,
rng: &mut R,
) -> OperatorResult<(Permutation, Permutation)> {
let n = parent1.dimension();
if n != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
if n < 2 {
return OperatorResult::Success((parent1.clone(), parent2.clone()));
}
let mut start = rng.gen_range(0..n);
let mut end = rng.gen_range(0..n);
if start > end {
std::mem::swap(&mut start, &mut end);
}
let child1 = Self::ox_single(parent1, parent2, start, end);
let child2 = Self::ox_single(parent2, parent1, start, end);
let c1 = match Permutation::try_new(child1) {
Ok(p) => p,
Err(e) => {
return OperatorResult::Failed(OperatorError::CrossoverFailed(format!(
"OX produced invalid child1: {}",
e
)))
}
};
let c2 = match Permutation::try_new(child2) {
Ok(p) => p,
Err(e) => {
return OperatorResult::Failed(OperatorError::CrossoverFailed(format!(
"OX produced invalid child2: {}",
e
)))
}
};
OperatorResult::Success((c1, c2))
}
}
impl OxCrossover {
fn ox_single(
parent1: &Permutation,
parent2: &Permutation,
start: usize,
end: usize,
) -> Vec<usize> {
let n = parent1.dimension();
let p1 = parent1.permutation();
let p2 = parent2.permutation();
let mut child = vec![usize::MAX; n];
let segment: HashSet<usize> = p1[start..=end].iter().copied().collect();
for i in start..=end {
child[i] = p1[i];
}
let mut pos = (end + 1) % n;
let mut p2_idx = (end + 1) % n;
while pos != start {
while segment.contains(&p2[p2_idx]) {
p2_idx = (p2_idx + 1) % n;
}
child[pos] = p2[p2_idx];
pos = (pos + 1) % n;
p2_idx = (p2_idx + 1) % n;
}
child
}
}
#[derive(Clone, Debug, Default)]
pub struct CxCrossover;
impl CxCrossover {
pub fn new() -> Self {
Self
}
}
impl CrossoverOperator<Permutation> for CxCrossover {
fn crossover<R: Rng>(
&self,
parent1: &Permutation,
parent2: &Permutation,
_rng: &mut R,
) -> OperatorResult<(Permutation, Permutation)> {
let n = parent1.dimension();
if n != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
if n == 0 {
return OperatorResult::Success((parent1.clone(), parent2.clone()));
}
let p1 = parent1.permutation();
let p2 = parent2.permutation();
let mut pos_in_p1: HashMap<usize, usize> = HashMap::new();
for (i, &val) in p1.iter().enumerate() {
pos_in_p1.insert(val, i);
}
let mut child1 = vec![usize::MAX; n];
let mut child2 = vec![usize::MAX; n];
let mut visited = vec![false; n];
let mut use_p1 = true;
for start in 0..n {
if visited[start] {
continue;
}
let mut cycle_positions = Vec::new();
let mut pos = start;
loop {
cycle_positions.push(pos);
visited[pos] = true;
let val_in_p2 = p2[pos];
pos = *pos_in_p1.get(&val_in_p2).unwrap();
if pos == start {
break;
}
}
for &cycle_pos in &cycle_positions {
if use_p1 {
child1[cycle_pos] = p1[cycle_pos];
child2[cycle_pos] = p2[cycle_pos];
} else {
child1[cycle_pos] = p2[cycle_pos];
child2[cycle_pos] = p1[cycle_pos];
}
}
use_p1 = !use_p1; }
let c1 = match Permutation::try_new(child1) {
Ok(p) => p,
Err(e) => {
return OperatorResult::Failed(OperatorError::CrossoverFailed(format!(
"CX produced invalid child1: {}",
e
)))
}
};
let c2 = match Permutation::try_new(child2) {
Ok(p) => p,
Err(e) => {
return OperatorResult::Failed(OperatorError::CrossoverFailed(format!(
"CX produced invalid child2: {}",
e
)))
}
};
OperatorResult::Success((c1, c2))
}
}
#[derive(Clone, Debug, Default)]
pub struct EdgeRecombinationCrossover;
impl EdgeRecombinationCrossover {
pub fn new() -> Self {
Self
}
fn build_edge_table(
parent1: &Permutation,
parent2: &Permutation,
) -> HashMap<usize, HashSet<usize>> {
let n = parent1.dimension();
let p1 = parent1.permutation();
let p2 = parent2.permutation();
let mut edges: HashMap<usize, HashSet<usize>> = HashMap::new();
for i in 0..n {
edges.insert(i, HashSet::new());
}
for i in 0..n {
let curr = p1[i];
let prev = p1[(i + n - 1) % n];
let next = p1[(i + 1) % n];
edges.get_mut(&curr).unwrap().insert(prev);
edges.get_mut(&curr).unwrap().insert(next);
}
for i in 0..n {
let curr = p2[i];
let prev = p2[(i + n - 1) % n];
let next = p2[(i + 1) % n];
edges.get_mut(&curr).unwrap().insert(prev);
edges.get_mut(&curr).unwrap().insert(next);
}
edges
}
}
impl CrossoverOperator<Permutation> for EdgeRecombinationCrossover {
fn crossover<R: Rng>(
&self,
parent1: &Permutation,
parent2: &Permutation,
rng: &mut R,
) -> OperatorResult<(Permutation, Permutation)> {
let n = parent1.dimension();
if n != parent2.dimension() {
return OperatorResult::Failed(OperatorError::CrossoverFailed(
"Parent dimensions do not match".to_string(),
));
}
if n < 2 {
return OperatorResult::Success((parent1.clone(), parent2.clone()));
}
let mut edges = Self::build_edge_table(parent1, parent2);
let mut child = Vec::with_capacity(n);
let mut remaining: HashSet<usize> = (0..n).collect();
let mut current = parent1.permutation()[0];
child.push(current);
remaining.remove(¤t);
for edge_set in edges.values_mut() {
edge_set.remove(¤t);
}
while child.len() < n {
let neighbors = edges.get(¤t).cloned().unwrap_or_default();
let next = if !neighbors.is_empty() {
let filtered: Vec<usize> = neighbors
.iter()
.filter(|x| remaining.contains(x))
.copied()
.collect();
if filtered.is_empty() {
let remaining_vec: Vec<usize> = remaining.iter().copied().collect();
remaining_vec[rng.gen_range(0..remaining_vec.len())]
} else {
*filtered
.iter()
.min_by_key(|&&x| edges.get(&x).map(|s| s.len()).unwrap_or(0))
.unwrap()
}
} else {
let remaining_vec: Vec<usize> = remaining.iter().copied().collect();
remaining_vec[rng.gen_range(0..remaining_vec.len())]
};
child.push(next);
remaining.remove(&next);
current = next;
for edge_set in edges.values_mut() {
edge_set.remove(¤t);
}
}
let mut edges2 = Self::build_edge_table(parent1, parent2);
let mut child2 = Vec::with_capacity(n);
let mut remaining2: HashSet<usize> = (0..n).collect();
let mut current2 = parent2.permutation()[0];
child2.push(current2);
remaining2.remove(¤t2);
for edge_set in edges2.values_mut() {
edge_set.remove(¤t2);
}
while child2.len() < n {
let neighbors = edges2.get(¤t2).cloned().unwrap_or_default();
let next2 = if !neighbors.is_empty() {
let filtered: Vec<usize> = neighbors
.iter()
.filter(|x| remaining2.contains(x))
.copied()
.collect();
if filtered.is_empty() {
let remaining_vec: Vec<usize> = remaining2.iter().copied().collect();
remaining_vec[rng.gen_range(0..remaining_vec.len())]
} else {
*filtered
.iter()
.min_by_key(|&&x| edges2.get(&x).map(|s| s.len()).unwrap_or(0))
.unwrap()
}
} else {
let remaining_vec: Vec<usize> = remaining2.iter().copied().collect();
remaining_vec[rng.gen_range(0..remaining_vec.len())]
};
child2.push(next2);
remaining2.remove(&next2);
current2 = next2;
for edge_set in edges2.values_mut() {
edge_set.remove(¤t2);
}
}
let c1 = match Permutation::try_new(child) {
Ok(p) => p,
Err(e) => {
return OperatorResult::Failed(OperatorError::CrossoverFailed(format!(
"ERX produced invalid child1: {}",
e
)))
}
};
let c2 = match Permutation::try_new(child2) {
Ok(p) => p,
Err(e) => {
return OperatorResult::Failed(OperatorError::CrossoverFailed(format!(
"ERX produced invalid child2: {}",
e
)))
}
};
OperatorResult::Success((c1, c2))
}
}
use crate::genome::tree::{Function, Terminal, TreeGenome, TreeNode};
#[derive(Clone, Debug)]
pub struct SubtreeCrossover {
pub max_depth: Option<usize>,
pub function_probability: f64,
}
impl Default for SubtreeCrossover {
fn default() -> Self {
Self {
max_depth: Some(17), function_probability: 0.9, }
}
}
impl SubtreeCrossover {
pub fn new() -> Self {
Self::default()
}
pub fn with_max_depth(mut self, max_depth: usize) -> Self {
self.max_depth = Some(max_depth);
self
}
pub fn without_depth_limit(mut self) -> Self {
self.max_depth = None;
self
}
pub fn with_function_probability(mut self, prob: f64) -> Self {
self.function_probability = prob.clamp(0.0, 1.0);
self
}
fn select_crossover_point<T: Terminal, F: Function, R: Rng>(
&self,
tree: &TreeNode<T, F>,
rng: &mut R,
) -> Vec<usize> {
let select_function = rng.gen::<f64>() < self.function_probability;
let positions = if select_function {
let func_pos = tree.function_positions();
if func_pos.is_empty() {
tree.positions() } else {
func_pos
}
} else {
let term_pos = tree.terminal_positions();
if term_pos.is_empty() {
tree.positions()
} else {
term_pos
}
};
if positions.is_empty() {
vec![] } else {
positions[rng.gen_range(0..positions.len())].clone()
}
}
}
impl<T: Terminal, F: Function> CrossoverOperator<TreeGenome<T, F>> for SubtreeCrossover {
fn crossover<R: Rng>(
&self,
parent1: &TreeGenome<T, F>,
parent2: &TreeGenome<T, F>,
rng: &mut R,
) -> OperatorResult<(TreeGenome<T, F>, TreeGenome<T, F>)> {
let point1 = self.select_crossover_point(&parent1.root, rng);
let point2 = self.select_crossover_point(&parent2.root, rng);
let subtree1 = parent1
.root
.get_subtree(&point1)
.cloned()
.unwrap_or_else(|| parent1.root.clone());
let subtree2 = parent2
.root
.get_subtree(&point2)
.cloned()
.unwrap_or_else(|| parent2.root.clone());
let mut child1_root = parent1.root.clone();
let mut child2_root = parent2.root.clone();
if point1.is_empty() {
child1_root = subtree2.clone();
} else {
child1_root.replace_subtree(&point1, subtree2.clone());
}
if point2.is_empty() {
child2_root = subtree1.clone();
} else {
child2_root.replace_subtree(&point2, subtree1);
}
if let Some(max_depth) = self.max_depth {
if child1_root.depth() > max_depth {
return OperatorResult::Success((parent1.clone(), parent2.clone()));
}
if child2_root.depth() > max_depth {
return OperatorResult::Success((parent1.clone(), parent2.clone()));
}
}
let child1 = TreeGenome::new(child1_root, parent1.max_depth);
let child2 = TreeGenome::new(child2_root, parent2.max_depth);
OperatorResult::Success((child1, child2))
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_sbx_creates_valid_offspring() {
let mut rng = rand::thread_rng();
let parent1 = RealVector::new(vec![0.0, 0.0, 0.0]);
let parent2 = RealVector::new(vec![1.0, 1.0, 1.0]);
let sbx = SbxCrossover::new(20.0);
let result = sbx.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
assert_eq!(child1.dimension(), 3);
assert_eq!(child2.dimension(), 3);
}
#[test]
fn test_sbx_with_bounds() {
let mut rng = rand::thread_rng();
let parent1 = RealVector::new(vec![-0.3, -0.2]);
let parent2 = RealVector::new(vec![0.3, 0.4]);
let bounds = MultiBounds::symmetric(0.5, 2);
let sbx = SbxCrossover::new(2.0).with_exchange_probability(1.0);
for _ in 0..100 {
let result = sbx.crossover_bounded(&parent1, &parent2, &bounds, &mut rng);
let (child1, child2) = result.genome().unwrap();
for gene in child1.genes() {
assert!(
*gene >= -0.5 && *gene <= 0.5,
"gene {} out of bounds [-0.5, 0.5]",
gene
);
}
for gene in child2.genes() {
assert!(
*gene >= -0.5 && *gene <= 0.5,
"gene {} out of bounds [-0.5, 0.5]",
gene
);
}
}
}
#[test]
fn test_sbx_default_probabilities_are_distinct() {
let sbx = SbxCrossover::new(20.0);
assert_eq!(sbx.crossover_probability, 0.9);
assert_eq!(sbx.exchange_probability, 0.5);
let sbx = SbxCrossover::new(20.0).with_probability(0.7);
assert_eq!(
CrossoverOperator::<RealVector>::crossover_probability(&sbx),
0.7
);
assert_eq!(sbx.exchange_probability, 0.5);
let sbx = SbxCrossover::new(20.0).with_exchange_probability(0.3);
assert_eq!(sbx.exchange_probability, 0.3);
assert_eq!(
CrossoverOperator::<RealVector>::crossover_probability(&sbx),
0.9
);
}
#[test]
fn test_sbx_bounded_no_atom_at_bounds_mean_preserving() {
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
let bounds = MultiBounds::symmetric(1.0, 1); let parent1 = RealVector::new(vec![-0.6]);
let parent2 = RealVector::new(vec![0.6]);
let parent_mid = 0.0;
let sbx = SbxCrossover::new(2.0).with_exchange_probability(1.0);
let n = 20_000;
let mut atom_count = 0usize;
let mut sum_children = 0.0;
let mut child_samples = 0usize;
let eps = 1e-9;
for _ in 0..n {
let result = sbx.crossover_bounded(&parent1, &parent2, &bounds, &mut rng);
let (c1, c2) = result.genome().unwrap();
for &g in c1.genes().iter().chain(c2.genes()) {
assert!(
(-1.0..=1.0).contains(&g),
"child gene {g} escaped bounds [-1, 1]"
);
if (g - 1.0).abs() < eps || (g + 1.0).abs() < eps {
atom_count += 1;
}
sum_children += g;
child_samples += 1;
}
}
let atom_frac = atom_count as f64 / child_samples as f64;
assert!(
atom_frac < 0.01,
"too many children pinned to the bounds: {atom_frac} (expected ~0)"
);
let mean_children = sum_children / child_samples as f64;
assert!(
(mean_children - parent_mid).abs() < 0.02,
"bounded SBX is not mean-preserving: mean {mean_children} vs parent midpoint {parent_mid}"
);
}
#[test]
fn test_sbx_spread_factor() {
let sbx = SbxCrossover::new(20.0);
let beta = sbx.spread_factor(0.5);
assert_relative_eq!(beta, 1.0, epsilon = 1e-10);
let beta_low = sbx.spread_factor(0.25);
let beta_high = sbx.spread_factor(0.75);
assert_relative_eq!(beta_low, 1.0 / beta_high, epsilon = 1e-10);
}
#[test]
fn test_sbx_identical_parents() {
let mut rng = rand::thread_rng();
let parent = RealVector::new(vec![1.0, 2.0, 3.0]);
let sbx = SbxCrossover::new(20.0);
let result = sbx.crossover(&parent, &parent, &mut rng);
let (child1, child2) = result.genome().unwrap();
assert_eq!(child1.genes(), parent.genes());
assert_eq!(child2.genes(), parent.genes());
}
#[test]
fn test_sbx_dimension_mismatch() {
let mut rng = rand::thread_rng();
let parent1 = RealVector::new(vec![1.0, 2.0]);
let parent2 = RealVector::new(vec![1.0, 2.0, 3.0]);
let sbx = SbxCrossover::new(20.0);
let result = sbx.crossover(&parent1, &parent2, &mut rng);
assert!(!result.is_ok());
}
#[test]
fn test_blx_alpha_creates_valid_offspring() {
let mut rng = rand::thread_rng();
let parent1 = RealVector::new(vec![0.0, 0.0]);
let parent2 = RealVector::new(vec![1.0, 1.0]);
let blx = BlxAlphaCrossover::new(0.5);
let result = blx.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
assert_eq!(child1.dimension(), 2);
assert_eq!(child2.dimension(), 2);
}
#[test]
fn test_blx_alpha_range() {
let mut rng = rand::thread_rng();
let parent1 = RealVector::new(vec![0.0]);
let parent2 = RealVector::new(vec![1.0]);
let blx = BlxAlphaCrossover::new(0.0);
for _ in 0..100 {
let result = blx.crossover(&parent1, &parent2, &mut rng);
let (child1, child2) = result.genome().unwrap();
assert!(child1[0] >= 0.0 && child1[0] <= 1.0);
assert!(child2[0] >= 0.0 && child2[0] <= 1.0);
}
}
#[test]
fn test_uniform_crossover_creates_valid_offspring() {
let mut rng = rand::thread_rng();
let parent1 = BitString::new(vec![true, true, true, true]);
let parent2 = BitString::new(vec![false, false, false, false]);
let ux = UniformCrossover::new();
let result = ux.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
assert_eq!(child1.len(), 4);
assert_eq!(child2.len(), 4);
}
#[test]
fn test_uniform_crossover_complementary() {
let mut rng = rand::thread_rng();
let parent1 = BitString::new(vec![true, true, true, true]);
let parent2 = BitString::new(vec![false, false, false, false]);
let ux = UniformCrossover::new();
let result = ux.crossover(&parent1, &parent2, &mut rng);
let (child1, child2) = result.genome().unwrap();
for i in 0..4 {
assert_ne!(child1[i], child2[i]);
}
}
#[test]
fn test_one_point_crossover() {
let mut rng = rand::thread_rng();
let parent1 = BitString::ones(10);
let parent2 = BitString::zeros(10);
let opx = OnePointCrossover::new();
let result = opx.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
for i in 0..10 {
assert_ne!(child1[i], child2[i]);
}
}
#[test]
fn test_two_point_crossover() {
let mut rng = rand::thread_rng();
let parent1 = BitString::ones(10);
let parent2 = BitString::zeros(10);
let tpx = TwoPointCrossover::new();
let result = tpx.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
assert_eq!(child1.len(), 10);
assert_eq!(child2.len(), 10);
}
#[test]
fn test_arithmetic_crossover() {
let mut rng = rand::thread_rng();
let parent1 = RealVector::new(vec![0.0, 0.0]);
let parent2 = RealVector::new(vec![1.0, 1.0]);
let ax = ArithmeticCrossover::new(0.5);
let result = ax.crossover(&parent1, &parent2, &mut rng);
let (child1, child2) = result.genome().unwrap();
for gene in child1.genes() {
assert_relative_eq!(*gene, 0.5);
}
for gene in child2.genes() {
assert_relative_eq!(*gene, 0.5);
}
}
#[test]
fn test_arithmetic_crossover_weighted() {
let mut rng = rand::thread_rng();
let parent1 = RealVector::new(vec![0.0]);
let parent2 = RealVector::new(vec![1.0]);
let ax = ArithmeticCrossover::new(0.75);
let result = ax.crossover(&parent1, &parent2, &mut rng);
let (child1, child2) = result.genome().unwrap();
assert_relative_eq!(child1[0], 0.25);
assert_relative_eq!(child2[0], 0.75);
}
#[test]
fn test_pmx_creates_valid_permutations() {
let mut rng = rand::thread_rng();
let parent1 = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
let parent2 = Permutation::new(vec![7, 6, 5, 4, 3, 2, 1, 0]);
let pmx = PmxCrossover::new();
for _ in 0..100 {
let result = pmx.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
assert!(child1.is_valid_permutation());
assert!(child2.is_valid_permutation());
assert_eq!(child1.dimension(), 8);
assert_eq!(child2.dimension(), 8);
}
}
#[test]
fn test_pmx_identical_parents() {
let mut rng = rand::thread_rng();
let parent = Permutation::new(vec![0, 1, 2, 3, 4]);
let pmx = PmxCrossover::new();
let result = pmx.crossover(&parent, &parent, &mut rng);
let (child1, child2) = result.genome().unwrap();
assert_eq!(child1.as_slice(), parent.as_slice());
assert_eq!(child2.as_slice(), parent.as_slice());
}
#[test]
fn test_pmx_dimension_mismatch() {
let mut rng = rand::thread_rng();
let parent1 = Permutation::new(vec![0, 1, 2, 3]);
let parent2 = Permutation::new(vec![0, 1, 2, 3, 4]);
let pmx = PmxCrossover::new();
let result = pmx.crossover(&parent1, &parent2, &mut rng);
assert!(!result.is_ok());
}
#[test]
fn test_ox_creates_valid_permutations() {
let mut rng = rand::thread_rng();
let parent1 = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
let parent2 = Permutation::new(vec![7, 6, 5, 4, 3, 2, 1, 0]);
let ox = OxCrossover::new();
for _ in 0..100 {
let result = ox.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
assert!(child1.is_valid_permutation());
assert!(child2.is_valid_permutation());
assert_eq!(child1.dimension(), 8);
assert_eq!(child2.dimension(), 8);
}
}
#[test]
fn test_ox_preserves_segment() {
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let parent1 = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
let parent2 = Permutation::new(vec![7, 6, 5, 4, 3, 2, 1, 0]);
let ox = OxCrossover::new();
let result = ox.crossover(&parent1, &parent2, &mut rng);
let (child1, child2) = result.genome().unwrap();
assert!(child1.is_valid_permutation());
assert!(child2.is_valid_permutation());
}
#[test]
fn test_cx_creates_valid_permutations() {
let mut rng = rand::thread_rng();
let parent1 = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
let parent2 = Permutation::new(vec![1, 2, 3, 4, 5, 6, 7, 0]);
let cx = CxCrossover::new();
for _ in 0..100 {
let result = cx.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
assert!(child1.is_valid_permutation());
assert!(child2.is_valid_permutation());
assert_eq!(child1.dimension(), 8);
assert_eq!(child2.dimension(), 8);
}
}
#[test]
fn test_cx_preserves_positions() {
let mut rng = rand::thread_rng();
let parent1 = Permutation::new(vec![0, 1, 2, 3, 4]);
let parent2 = Permutation::new(vec![4, 3, 2, 1, 0]);
let cx = CxCrossover::new();
let result = cx.crossover(&parent1, &parent2, &mut rng);
let (child1, child2) = result.genome().unwrap();
for i in 0..5 {
let c1_val = child1[i];
let c2_val = child2[i];
assert!(c1_val == parent1[i] || c1_val == parent2[i]);
assert!(c2_val == parent1[i] || c2_val == parent2[i]);
}
}
#[test]
fn test_cx_identical_parents() {
let mut rng = rand::thread_rng();
let parent = Permutation::new(vec![0, 1, 2, 3, 4]);
let cx = CxCrossover::new();
let result = cx.crossover(&parent, &parent, &mut rng);
let (child1, child2) = result.genome().unwrap();
assert_eq!(child1.as_slice(), parent.as_slice());
assert_eq!(child2.as_slice(), parent.as_slice());
}
#[test]
fn test_erx_creates_valid_permutations() {
let mut rng = rand::thread_rng();
let parent1 = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
let parent2 = Permutation::new(vec![7, 6, 5, 4, 3, 2, 1, 0]);
let erx = EdgeRecombinationCrossover::new();
for _ in 0..50 {
let result = erx.crossover(&parent1, &parent2, &mut rng);
assert!(result.is_ok());
let (child1, child2) = result.genome().unwrap();
assert!(child1.is_valid_permutation());
assert!(child2.is_valid_permutation());
assert_eq!(child1.dimension(), 8);
assert_eq!(child2.dimension(), 8);
}
}
#[test]
fn test_erx_preserves_some_edges() {
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let parent1 = Permutation::new(vec![0, 1, 2, 3, 4]);
let parent2 = Permutation::new(vec![0, 1, 4, 3, 2]);
let erx = EdgeRecombinationCrossover::new();
let result = erx.crossover(&parent1, &parent2, &mut rng);
let (child1, _child2) = result.genome().unwrap();
assert!(child1.is_valid_permutation());
}
}