use super::crossover::GpCrossover;
use super::mutation::GpMutation;
use crate::configuration::{LimitConfiguration, SelectionConfiguration};
use crate::error::GaError;
use crate::operations::{Selection, Survivor};
#[derive(Debug, Clone)]
pub struct GpConfiguration {
pub(crate) population_size: usize,
pub(crate) max_generations: usize,
pub(crate) init_max_depth: usize,
pub(crate) max_depth: usize,
pub(crate) max_node_count: usize,
pub(crate) crossover: GpCrossover,
pub(crate) mutations: Vec<(GpMutation, f64)>,
pub(crate) selection: SelectionConfiguration,
pub(crate) survivor: Survivor,
pub(crate) is_maximization: bool,
pub(crate) max_stagnation: Option<usize>,
pub(crate) fitness_target: Option<f64>,
}
impl Default for GpConfiguration {
fn default() -> Self {
Self::new()
}
}
impl GpConfiguration {
pub fn new() -> Self {
GpConfiguration {
population_size: 100,
max_generations: 50,
init_max_depth: 4,
max_depth: 8,
max_node_count: 200,
crossover: GpCrossover::SubtreeCrossover,
mutations: vec![(
GpMutation::SubtreeMutation {
mutation_max_depth: 4,
},
0.1,
)],
selection: SelectionConfiguration {
number_of_couples: 0, method: Selection::Tournament,
boltzmann_temperature: 1.0,
niche_radius: 0.1,
epsilon: 0.0,
},
survivor: Survivor::Fitness,
is_maximization: false,
max_stagnation: None,
fitness_target: None,
}
}
pub fn population_size(&self) -> usize {
self.population_size
}
pub fn max_generations(&self) -> usize {
self.max_generations
}
pub fn init_max_depth(&self) -> usize {
self.init_max_depth
}
pub fn max_depth(&self) -> usize {
self.max_depth
}
pub fn max_node_count(&self) -> usize {
self.max_node_count
}
pub fn is_maximization(&self) -> bool {
self.is_maximization
}
pub fn with_population_size(mut self, size: usize) -> Self {
self.population_size = size;
self
}
pub fn with_max_generations(mut self, max: usize) -> Self {
self.max_generations = max;
self
}
pub fn with_init_max_depth(mut self, depth: usize) -> Self {
self.init_max_depth = depth;
self
}
pub fn with_max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
pub fn with_max_node_count(mut self, count: usize) -> Self {
self.max_node_count = count;
self
}
pub fn with_crossover(mut self, crossover: GpCrossover) -> Self {
self.crossover = crossover;
self
}
pub fn with_mutations(mut self, mutations: Vec<(GpMutation, f64)>) -> Self {
self.mutations = mutations;
self
}
pub fn with_selection_config(mut self, selection: SelectionConfiguration) -> Self {
self.selection = selection;
self
}
pub fn with_survivor_config(mut self, survivor: Survivor) -> Self {
self.survivor = survivor;
self
}
pub fn with_is_maximization(mut self, is_maximization: bool) -> Self {
self.is_maximization = is_maximization;
self
}
pub fn with_max_stagnation(mut self, max_stagnation: Option<usize>) -> Self {
self.max_stagnation = max_stagnation;
self
}
pub fn with_fitness_target(mut self, target: Option<f64>) -> Self {
self.fitness_target = target;
self
}
pub(crate) fn limit_configuration(&self) -> LimitConfiguration {
use crate::chromosomes::ChromosomeLength;
use crate::configuration::ProblemSolving;
LimitConfiguration {
problem_solving: if self.is_maximization {
ProblemSolving::Maximization
} else {
ProblemSolving::Minimization
},
max_generations: self.max_generations,
fitness_target: self.fitness_target,
population_size: self.population_size,
chromosome_length: ChromosomeLength::Fixed(1),
}
}
pub(crate) fn effective_selection_config(&self) -> SelectionConfiguration {
let mut cfg = self.selection;
if cfg.number_of_couples == 0 {
cfg.number_of_couples = (self.population_size / 2).max(1);
}
cfg
}
pub fn build(&self) -> Result<(), GaError> {
if self.max_depth == 0 {
return Err(GaError::ConfigurationError(
"max_depth must be greater than 0".to_string(),
));
}
if self.max_depth > 1_000 {
return Err(GaError::ConfigurationError(format!(
"max_depth {} exceeds the hard cap of 1000; set a smaller limit to prevent memory exhaustion",
self.max_depth
)));
}
if self.max_node_count < self.max_depth {
return Err(GaError::ConfigurationError(format!(
"max_node_count ({}) must be >= max_depth ({}) — a right-spine chain of depth D requires D nodes",
self.max_node_count, self.max_depth
)));
}
if self.max_node_count > 100_000 {
return Err(GaError::ConfigurationError(format!(
"max_node_count {} exceeds the hard cap of 100000; set a smaller limit to prevent memory exhaustion",
self.max_node_count
)));
}
if self.init_max_depth == 0 {
return Err(GaError::ConfigurationError(
"init_max_depth must be greater than 0".to_string(),
));
}
if self.init_max_depth > self.max_depth {
return Err(GaError::ConfigurationError(format!(
"init_max_depth ({}) must be <= max_depth ({})",
self.init_max_depth, self.max_depth
)));
}
if self.population_size == 0 {
return Err(GaError::ConfigurationError(
"population_size must be greater than 0".to_string(),
));
}
if self.max_generations == 0 {
return Err(GaError::ConfigurationError(
"max_generations must be greater than 0".to_string(),
));
}
if self.mutations.is_empty() {
return Err(GaError::ConfigurationError(
"mutations list must not be empty — provide at least one (GpMutation, probability) pair".to_string(),
));
}
for (i, (_, prob)) in self.mutations.iter().enumerate() {
if !prob.is_finite() || *prob < 0.0 || *prob > 1.0 {
return Err(GaError::ConfigurationError(format!(
"mutations[{}]: probability {} is not in [0.0, 1.0]",
i, prob
)));
}
}
Ok(())
}
}