use std::{fmt::Debug, marker::PhantomData};
use rayon::prelude::*;
use tracing::{debug, info};
use crate::{
breeding::BreedStrategy,
error::{GeneticError, Result},
evolution::options::EvolutionOptions,
evolution::options::LogLevel,
phenotype::Phenotype,
rng::RandomNumberGenerator,
};
pub trait Magnitude<Pheno: Phenotype> {
fn magnitude(&self) -> f64;
fn min_magnitude(&self) -> f64;
fn max_magnitude(&self) -> f64;
fn is_within_bounds(&self) -> bool {
let mag = self.magnitude();
mag >= self.min_magnitude() && mag <= self.max_magnitude() && mag.is_finite()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct BoundedBreedConfig {
pub max_development_attempts: usize,
}
impl Default for BoundedBreedConfig {
fn default() -> Self {
Self {
max_development_attempts: 1000,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct BoundedBreedConfigBuilder {
max_development_attempts: Option<usize>,
}
impl BoundedBreedConfigBuilder {
pub fn max_development_attempts(mut self, value: usize) -> Self {
self.max_development_attempts = Some(value);
self
}
pub fn build(self) -> BoundedBreedConfig {
BoundedBreedConfig {
max_development_attempts: self.max_development_attempts.unwrap_or(1000),
}
}
}
impl BoundedBreedConfig {
pub fn builder() -> BoundedBreedConfigBuilder {
BoundedBreedConfigBuilder::default()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct BoundedBreedStrategy<Pheno>
where
Pheno: Phenotype + Magnitude<Pheno>,
{
_marker: PhantomData<Pheno>,
config: BoundedBreedConfig,
}
impl<Pheno> Default for BoundedBreedStrategy<Pheno>
where
Pheno: Phenotype + Magnitude<Pheno>,
{
fn default() -> Self {
Self {
_marker: PhantomData,
config: BoundedBreedConfig::default(),
}
}
}
impl<Pheno> BoundedBreedStrategy<Pheno>
where
Pheno: Phenotype + Magnitude<Pheno>,
{
pub fn new(max_development_attempts: usize) -> Self {
Self {
_marker: PhantomData,
config: BoundedBreedConfig {
max_development_attempts,
},
}
}
pub fn with_config(config: BoundedBreedConfig) -> Self {
Self {
_marker: PhantomData,
config,
}
}
#[deprecated(
since = "0.1.0",
note = "Set parallel_threshold in EvolutionOptions instead"
)]
pub fn new_with_params(max_development_attempts: usize, _parallel_threshold: usize) -> Self {
Self::new(max_development_attempts)
}
}
impl<Pheno> BreedStrategy<Pheno> for BoundedBreedStrategy<Pheno>
where
Pheno: Phenotype + Magnitude<Pheno> + Send + Sync,
{
fn breed(
&self,
parents: &[Pheno],
evol_options: &EvolutionOptions,
rng: &mut RandomNumberGenerator,
) -> Result<Vec<Pheno>> {
if parents.is_empty() {
return Err(GeneticError::EmptyPopulation);
}
let winner_previous_generation = parents[0].clone();
let mut children_to_develop: Vec<(Pheno, bool)> = Vec::new();
children_to_develop.push((winner_previous_generation.clone(), false));
for parent in parents.iter().skip(1) {
let mut child = winner_previous_generation.clone();
child.crossover(parent);
children_to_develop.push((child, true));
}
for _ in parents.len()..evol_options.get_num_offspring() {
children_to_develop.push((winner_previous_generation.clone(), true));
}
let parallel_threshold = evol_options.get_parallel_threshold();
let log_level = evol_options.get_log_level();
if children_to_develop.len() >= parallel_threshold {
children_to_develop
.into_par_iter()
.map(|(pheno, initial_mutate)| {
self.develop_thread_local(pheno, initial_mutate, log_level)
})
.collect()
} else {
let mut developed_children = Vec::with_capacity(children_to_develop.len());
for (pheno, initial_mutate) in children_to_develop {
developed_children.push(self.develop(pheno, rng, initial_mutate, log_level)?);
}
Ok(developed_children)
}
}
}
impl<Pheno> BoundedBreedStrategy<Pheno>
where
Pheno: Phenotype + Magnitude<Pheno>,
{
fn develop(
&self,
pheno: Pheno,
rng: &mut RandomNumberGenerator,
initial_mutate: bool,
log_level: &LogLevel,
) -> Result<Pheno> {
let mut phenotype = pheno;
if initial_mutate {
phenotype.mutate(rng);
}
if phenotype.is_within_bounds() {
return Ok(phenotype);
}
let mag = phenotype.magnitude();
if !mag.is_finite() {
return Err(GeneticError::InvalidNumericValue(format!(
"Phenotype magnitude is not a finite number: {}",
mag
)));
}
for attempt in 1..=self.config.max_development_attempts {
phenotype.mutate(rng);
if phenotype.is_within_bounds() {
return Ok(phenotype);
}
let mag = phenotype.magnitude();
if !mag.is_finite() {
return Err(GeneticError::InvalidNumericValue(format!(
"Phenotype magnitude became non-finite during development: {}",
mag
)));
}
if attempt % (self.config.max_development_attempts / 10) == 0 {
match log_level {
LogLevel::Debug => {
debug!(
attempt,
max_attempts = self.config.max_development_attempts,
magnitude = phenotype.magnitude(),
min_bound = phenotype.min_magnitude(),
max_bound = phenotype.max_magnitude(),
"Development attempt progress"
);
}
LogLevel::Info => {
if attempt == self.config.max_development_attempts / 2 {
info!(
progress = "50%",
magnitude = phenotype.magnitude(),
min_bound = phenotype.min_magnitude(),
max_bound = phenotype.max_magnitude(),
"Development halfway complete"
);
}
}
LogLevel::None => {}
}
}
}
Err(GeneticError::MaxAttemptsReached(format!(
"Failed to develop phenotype within bounds after {} attempts. Current magnitude: {}, min: {}, max: {}",
self.config.max_development_attempts,
phenotype.magnitude(),
phenotype.min_magnitude(),
phenotype.max_magnitude()
)))
}
fn develop_thread_local(
&self,
pheno: Pheno,
initial_mutate: bool,
log_level: &LogLevel,
) -> Result<Pheno> {
let mut phenotype = pheno;
if initial_mutate {
phenotype.mutate_thread_local();
}
if phenotype.is_within_bounds() {
return Ok(phenotype);
}
let mag = phenotype.magnitude();
if !mag.is_finite() {
return Err(GeneticError::InvalidNumericValue(format!(
"Phenotype magnitude is not a finite number: {}",
mag
)));
}
for attempt in 1..=self.config.max_development_attempts {
phenotype.mutate_thread_local();
if phenotype.is_within_bounds() {
return Ok(phenotype);
}
let mag = phenotype.magnitude();
if !mag.is_finite() {
return Err(GeneticError::InvalidNumericValue(format!(
"Phenotype magnitude became non-finite during development: {}",
mag
)));
}
if attempt % (self.config.max_development_attempts / 10) == 0 {
match log_level {
LogLevel::Debug => {
debug!(
attempt,
max_attempts = self.config.max_development_attempts,
magnitude = phenotype.magnitude(),
min_bound = phenotype.min_magnitude(),
max_bound = phenotype.max_magnitude(),
"Development attempt progress"
);
}
LogLevel::Info => {
if attempt == self.config.max_development_attempts / 2 {
info!(
progress = "50%",
magnitude = phenotype.magnitude(),
min_bound = phenotype.min_magnitude(),
max_bound = phenotype.max_magnitude(),
"Development halfway complete"
);
}
}
LogLevel::None => {}
}
}
}
Err(GeneticError::MaxAttemptsReached(format!(
"Failed to develop phenotype within bounds after {} attempts. Current magnitude: {}, min: {}, max: {}",
self.config.max_development_attempts,
phenotype.magnitude(),
phenotype.min_magnitude(),
phenotype.max_magnitude()
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
evolution::options::{EvolutionOptions, LogLevel},
phenotype::Phenotype,
rng::RandomNumberGenerator,
};
#[derive(Clone, Debug)]
struct TestPhenotype {
value: f64,
min_bound: f64,
max_bound: f64,
}
impl TestPhenotype {
fn new(value: f64, min_bound: f64, max_bound: f64) -> Self {
Self {
value,
min_bound,
max_bound,
}
}
}
impl Phenotype for TestPhenotype {
fn crossover(&mut self, other: &Self) {
self.value = (self.value + other.value) / 2.0;
}
fn mutate(&mut self, rng: &mut RandomNumberGenerator) {
let delta = *rng.fetch_uniform(-0.1, 0.1, 1).front().unwrap() as f64;
self.value += delta;
}
}
impl Magnitude<TestPhenotype> for TestPhenotype {
fn magnitude(&self) -> f64 {
self.value.abs()
}
fn min_magnitude(&self) -> f64 {
self.min_bound
}
fn max_magnitude(&self) -> f64 {
self.max_bound
}
}
#[test]
fn test_develop_within_bounds() {
let mut rng = RandomNumberGenerator::new();
let strategy = BoundedBreedStrategy::default();
let pheno = TestPhenotype::new(5.0, 4.0, 6.0);
let result = strategy.develop(pheno, &mut rng, false, &LogLevel::None);
assert!(result.is_ok());
let developed = result.unwrap();
assert!(developed.magnitude() >= developed.min_magnitude());
assert!(developed.magnitude() <= developed.max_magnitude());
}
#[test]
fn test_develop_outside_bounds() {
let mut rng = RandomNumberGenerator::new();
let strategy = BoundedBreedStrategy::new(10);
let pheno = TestPhenotype::new(100.0, 0.0, 0.1);
let result = strategy.develop(pheno, &mut rng, false, &LogLevel::None);
assert!(result.is_err());
match result {
Err(GeneticError::MaxAttemptsReached(_)) => (),
_ => panic!("Expected MaxAttemptsReached error"),
}
}
#[test]
fn test_breed_empty_parents() {
let mut rng = RandomNumberGenerator::new();
let evol_options = EvolutionOptions::default();
let strategy = BoundedBreedStrategy::<TestPhenotype>::default();
let parents = Vec::<TestPhenotype>::new();
let result = strategy.breed(&parents, &evol_options, &mut rng);
assert!(result.is_err());
match result {
Err(GeneticError::EmptyPopulation) => (),
_ => panic!("Expected EmptyPopulation error"),
}
}
}