use super::*;
use crate::operations::DifferentialParams;
pub(crate) struct ParentCrossoverParams<'a, U: LinearChromosome> {
pub(crate) age: usize,
pub(crate) f_max: f64,
pub(crate) f_avg: f64,
pub(crate) dynamic_mutation_prob: Option<f64>,
pub(crate) generation: usize,
pub(crate) best_fitness: f64,
pub(crate) is_maximization: bool,
pub(crate) fitness_fn: Option<Arc<FitnessFn<U::Gene>>>,
pub(crate) crossover_portfolio: Option<&'a Vec<Crossover>>,
pub(crate) mutation_portfolio: Option<&'a Vec<Mutation>>,
pub(crate) aos_crossover_state: Option<&'a Mutex<AosState>>,
pub(crate) aos_mutation_state: Option<&'a Mutex<AosState>>,
}
pub(crate) fn parent_crossover<U>(
parents: &[Vec<usize>],
chromosomes: &[U],
configuration: &GaConfiguration,
params: ParentCrossoverParams<'_, U>,
out: &mut Vec<U>,
) -> Result<(), GaError>
where
U: LinearChromosome
+ Send
+ Sync
+ 'static
+ Clone
+ mutation::ValueMutable
+ crate::traits::RealValuedMutation,
{
out.clear();
let ParentCrossoverParams {
age,
f_max,
f_avg,
dynamic_mutation_prob,
generation,
best_fitness,
is_maximization,
fitness_fn,
crossover_portfolio,
mutation_portfolio,
aos_crossover_state,
aos_mutation_state,
} = params;
let crossover_probability_config =
if let Some(p) = configuration.crossover_configuration.probability_max {
if !configuration.adaptive_ga {
Some(p)
} else {
None
}
} else {
Some(1.0)
};
let mutation_probability_config = if let Some(dp) = dynamic_mutation_prob {
Some(dp)
} else if let Some(p) = configuration.mutation_configuration.probability_max {
if !configuration.adaptive_ga {
Some(p)
} else {
None
}
} else {
Some(1.0)
};
let crossover_reward_acc: RewardAccumulator = if aos_crossover_state.is_some() {
Some(Arc::new(Mutex::new(Vec::new())))
} else {
None
};
let mutation_reward_acc: RewardAccumulator = if aos_mutation_state.is_some() {
Some(Arc::new(Mutex::new(Vec::new())))
} else {
None
};
let process_pair = |group: &Vec<usize>| -> Result<Vec<U>, GaError> {
let mut rng = crate::rng::make_rng();
if group.len() < 2 {
return Err(GaError::SelectionError(format!(
"Selection group has fewer than 2 parents (got {})",
group.len()
)));
}
let key = group[0];
let value = group[1];
let parent_1 = chromosomes.get(key).ok_or_else(|| {
GaError::SelectionError(format!(
"Selection returned out-of-bounds index {} (population size {})",
key,
chromosomes.len()
))
})?;
let parent_2 = chromosomes.get(value).ok_or_else(|| {
GaError::SelectionError(format!(
"Selection returned out-of-bounds index {} (population size {})",
value,
chromosomes.len()
))
})?;
let crossover_probability = rng.random_range(0.0..1.0);
let effective_crossover_prob = if let Some(p) = crossover_probability_config {
p
} else {
crossover::aga_probability(
parent_1,
parent_2,
f_max,
f_avg,
configuration
.crossover_configuration
.probability_max
.unwrap_or(1.0),
configuration
.crossover_configuration
.probability_min
.unwrap_or(0.0),
)
};
let mut mutation_probability = rng.random_range(0.0..1.0);
let effective_mutation_prob = if let Some(p) = mutation_probability_config {
p
} else {
mutation::aga_probability(
parent_1,
parent_2,
f_avg,
configuration
.mutation_configuration
.probability_max
.unwrap_or(1.0),
configuration
.mutation_configuration
.probability_min
.unwrap_or(0.0),
)
};
if crossover_probability > effective_crossover_prob {
return Ok(Vec::new());
}
let selected_crossover: Option<(usize, Crossover)> = if let (
Some(portfolio),
Some(aos_state),
) =
(crossover_portfolio, aos_crossover_state)
{
let mut state = aos_state
.lock()
.map_err(|_| GaError::InternalError("AOS state mutex poisoned".to_string()))?;
let op_idx = state.select_operator(&mut rng, generation);
Some((op_idx, portfolio[op_idx]))
} else {
None
};
let selected_mutation: Option<(usize, Mutation)> =
if let (Some(portfolio), Some(aos_state)) = (mutation_portfolio, aos_mutation_state) {
let mut state = aos_state
.lock()
.map_err(|_| GaError::InternalError("AOS state mutex poisoned".to_string()))?;
let op_idx = state.select_operator(&mut rng, generation);
Some((op_idx, portfolio[op_idx]))
} else {
None
};
let effective_method = selected_crossover
.map(|(_, op)| op)
.unwrap_or(configuration.crossover_configuration.method);
let mut children = if group.len() > 2 {
let mut parent_refs: Vec<&U> = Vec::with_capacity(group.len());
for &idx in group.iter() {
let p = chromosomes.get(idx).ok_or_else(|| {
GaError::SelectionError(format!(
"Selection returned out-of-bounds index {} (population size {})",
idx,
chromosomes.len()
))
})?;
parent_refs.push(p);
}
let mut cx_config = configuration.crossover_configuration;
cx_config.method = effective_method;
crossover::factory_multi_parent_dispatch(&parent_refs, cx_config)?
} else {
let mut cx_config = configuration.crossover_configuration;
cx_config.method = effective_method;
crossover::factory(parent_1, parent_2, cx_config)?
};
let mut child_1 = children
.pop()
.ok_or_else(|| GaError::CrossoverError("Crossover returned no children".to_string()))?;
let mut child_2 = children.pop().unwrap_or_else(|| parent_2.clone());
let selected_mutation_idx = selected_mutation.as_ref().map(|(idx, _)| *idx);
let mutation_method = selected_mutation
.map(|(_, op)| op)
.unwrap_or(configuration.mutation_configuration.method);
if mutation_probability <= effective_mutation_prob {
match &mutation_method {
Mutation::Differential(DifferentialParams { f }) => {
let f_val = f.unwrap_or(0.5);
crate::operations::mutation::differential::differential_mutation(
&mut child_1,
chromosomes,
key,
f_val,
)?;
}
Mutation::Insertion | Mutation::Deletion => {
mutation::factory_with_chromosome_length(
mutation_method,
&mut child_1,
Some(configuration.limit_configuration.chromosome_length),
)?;
}
_ => {
mutation_method.mutate(&mut child_1, &mutation_method)?;
}
}
}
mutation_probability = rng.random_range(0.0..1.0);
if mutation_probability <= effective_mutation_prob {
match &mutation_method {
Mutation::Differential(DifferentialParams { f }) => {
let f_val = f.unwrap_or(0.5);
crate::operations::mutation::differential::differential_mutation(
&mut child_2,
chromosomes,
value,
f_val,
)?;
}
Mutation::Insertion | Mutation::Deletion => {
mutation::factory_with_chromosome_length(
mutation_method,
&mut child_2,
Some(configuration.limit_configuration.chromosome_length),
)?;
}
_ => {
mutation_method.mutate(&mut child_2, &mutation_method)?;
}
}
}
if let Some(ref ff) = fitness_fn {
let ff1 = Arc::clone(ff);
child_1.set_fitness_fn(move |genes| ff1(genes));
let ff2 = Arc::clone(ff);
child_2.set_fitness_fn(move |genes| ff2(genes));
}
child_1.calculate_fitness();
child_2.calculate_fitness();
child_1.set_age(age);
child_2.set_age(age);
if let Some(ref acc) = crossover_reward_acc {
if let Some((c_op_idx, _)) = selected_crossover {
let (p, c) = if is_maximization {
(child_1.fitness(), parent_1.fitness())
} else {
(parent_1.fitness(), child_1.fitness())
};
let reward = crate::aos::compute_normalized_reward(p, c, best_fitness);
acc.lock()
.map_err(|_| {
GaError::InternalError("AOS reward accumulator poisoned".to_string())
})?
.push((c_op_idx, reward));
}
}
if let Some(ref acc) = mutation_reward_acc {
if let Some(m_op_idx) = selected_mutation_idx {
let (p, c) = if is_maximization {
(child_1.fitness(), parent_1.fitness())
} else {
(parent_1.fitness(), child_1.fitness())
};
let reward = crate::aos::compute_normalized_reward(p, c, best_fitness);
acc.lock()
.map_err(|_| {
GaError::InternalError("AOS reward accumulator poisoned".to_string())
})?
.push((m_op_idx, reward));
}
}
Ok(vec![child_1, child_2])
};
#[cfg(all(not(target_arch = "wasm32"), feature = "parallel"))]
let results: Vec<Result<Vec<U>, GaError>> = parents.par_iter().map(process_pair).collect();
#[cfg(any(target_arch = "wasm32", not(feature = "parallel")))]
let results: Vec<Result<Vec<U>, GaError>> = parents.iter().map(process_pair).collect();
for result in results {
out.extend(result?);
}
if let Some(acc) = crossover_reward_acc {
let rewards = acc
.lock()
.map_err(|_| GaError::InternalError("AOS reward accumulator poisoned".to_string()))?
.drain(..)
.collect::<Vec<_>>();
if !rewards.is_empty() {
if let Some(aos_state) = aos_crossover_state {
let mut state = aos_state
.lock()
.map_err(|_| GaError::InternalError("AOS state mutex poisoned".to_string()))?;
state.record_rewards(&rewards);
state.update();
}
}
}
if let Some(acc) = mutation_reward_acc {
let rewards = acc
.lock()
.map_err(|_| GaError::InternalError("AOS reward accumulator poisoned".to_string()))?
.drain(..)
.collect::<Vec<_>>();
if !rewards.is_empty() {
if let Some(aos_state) = aos_mutation_state {
let mut state = aos_state
.lock()
.map_err(|_| GaError::InternalError("AOS state mutex poisoned".to_string()))?;
state.record_rewards(&rewards);
state.update();
}
}
}
Ok(())
}
pub(crate) fn extract_elite<U: LinearChromosome>(
chromosomes: &[U],
count: usize,
problem_solving: ProblemSolving,
) -> Vec<usize> {
if count == 0 || chromosomes.is_empty() {
return Vec::new();
}
let k = count.min(chromosomes.len());
let mut indices: Vec<usize> = (0..chromosomes.len()).collect();
let cmp_fn = |a: &usize, b: &usize| {
let cmp = chromosomes[*a]
.fitness()
.partial_cmp(&chromosomes[*b].fitness())
.unwrap_or(std::cmp::Ordering::Equal);
match problem_solving {
ProblemSolving::Maximization => cmp.reverse(),
_ => cmp,
}
};
indices.select_nth_unstable_by(k - 1, cmp_fn);
indices.truncate(k);
indices
}
pub(crate) fn reinsert_elite<U: LinearChromosome>(
chromosomes: &mut [U],
elite: Vec<U>,
problem_solving: ProblemSolving,
) {
let k = elite.len().min(chromosomes.len());
if k == 0 {
return;
}
chromosomes.select_nth_unstable_by(k - 1, |a, b| {
let cmp = a
.fitness()
.partial_cmp(&b.fitness())
.unwrap_or(std::cmp::Ordering::Equal);
match problem_solving {
ProblemSolving::Maximization => cmp,
_ => cmp.reverse(),
}
});
for (i, elite_individual) in elite.into_iter().take(k).enumerate() {
chromosomes[i] = elite_individual;
}
}