mod bitstring;
mod generic;
mod realvalued;
use std::error::Error;
use std::fmt::Display;
use std::marker::PhantomData;
use super::individual::{BitStringIndividual, IndividualTrait, RealValueIndividual};
use super::operators::replacement::ReplacementOperator;
use super::operators::selection::SelectionOperator;
use super::population::PopulationGenerator;
use super::{CrossoverOperator, GAConfig, GAParams, MutationOperator, Probe};
use crate::ga::operators::fitness::Fitness;
pub use bitstring::BitStringBuilder;
pub use generic::GenericBuilder;
pub use realvalued::RealValuedBuilder;
type FitnessFn<S, R> = fn(&S) -> R;
#[derive(Debug, Clone)]
enum ConfigError {
MissingParam(String),
MissingOperator(String),
MissingPopulationFactory,
NoProbe,
NoParams,
}
impl Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MissingParam(param) => write!(f, "Unspecified parameter: {param}"),
Self::MissingOperator(op) => write!(f, "Unspecified operator: {op}"),
Self::MissingPopulationFactory => write!(f, "Unspecified population factory"),
Self::NoProbe => write!(f, "Unspecified probe"),
Self::NoParams => write!(f, "No parameters were specified"),
}
}
}
impl Error for ConfigError {}
#[derive(Debug, Clone)]
pub(self) struct GAParamsOpt {
pub selection_rate: Option<f64>,
pub mutation_rate: Option<f64>,
pub population_size: Option<usize>,
pub generation_limit: Option<usize>,
pub max_duration: Option<std::time::Duration>,
}
impl GAParamsOpt {
pub fn new() -> Self {
Self {
selection_rate: None,
mutation_rate: None,
population_size: None,
generation_limit: None,
max_duration: None,
}
}
pub fn fill_from(&mut self, other: &GAParams) {
self.selection_rate.get_or_insert(other.selection_rate);
self.mutation_rate.get_or_insert(other.mutation_rate);
self.population_size.get_or_insert(other.population_size);
self.generation_limit.get_or_insert(other.generation_limit);
self.max_duration.get_or_insert(other.max_duration);
}
}
impl TryFrom<GAParamsOpt> for GAParams {
type Error = ConfigError;
fn try_from(params_opt: GAParamsOpt) -> Result<Self, Self::Error> {
let Some(selection_rate) = params_opt.selection_rate else {
return Err(ConfigError::MissingParam("Unspecified selection rate".to_owned()));
};
let Some(mutation_rate) = params_opt.mutation_rate else {
return Err(ConfigError::MissingParam("Unspecified mutation rate".to_owned()));
};
let Some(population_size) = params_opt.population_size else {
return Err(ConfigError::MissingParam("Unspecified population size".to_owned()));
};
let Some(generation_limit) = params_opt.generation_limit else {
return Err(ConfigError::MissingParam("Unspecified generation_limit".to_owned()));
};
let Some(max_duration) = params_opt.max_duration else {
return Err(ConfigError::MissingParam("Unspecified max duration".to_owned()));
};
Ok(GAParams {
selection_rate,
mutation_rate,
population_size,
generation_limit,
max_duration,
})
}
}
pub(self) struct GAConfigOpt<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
where
IndividualT: IndividualTrait,
MutOpT: MutationOperator<IndividualT>,
CrossOpT: CrossoverOperator<IndividualT>,
SelOpT: SelectionOperator<IndividualT>,
ReplOpT: ReplacementOperator<IndividualT>,
PopGenT: PopulationGenerator<IndividualT>,
FitnessT: Fitness<IndividualT>,
ProbeT: Probe<IndividualT>,
{
pub params: GAParamsOpt,
pub fitness_fn: Option<FitnessT>,
pub mutation_operator: Option<MutOpT>,
pub crossover_operator: Option<CrossOpT>,
pub selection_operator: Option<SelOpT>,
pub replacement_operator: Option<ReplOpT>,
pub population_factory: Option<PopGenT>,
pub probe: Option<ProbeT>,
_phantom: PhantomData<IndividualT>,
}
impl<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
GAConfigOpt<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
where
IndividualT: IndividualTrait,
MutOpT: MutationOperator<IndividualT>,
CrossOpT: CrossoverOperator<IndividualT>,
SelOpT: SelectionOperator<IndividualT>,
ReplOpT: ReplacementOperator<IndividualT>,
PopGenT: PopulationGenerator<IndividualT>,
FitnessT: Fitness<IndividualT>,
ProbeT: Probe<IndividualT>,
{
pub fn new() -> Self {
Self {
params: GAParamsOpt::new(),
fitness_fn: None,
mutation_operator: None,
crossover_operator: None,
selection_operator: None,
replacement_operator: None,
population_factory: None,
probe: None,
_phantom: Default::default(),
}
}
}
impl<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
TryFrom<GAConfigOpt<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>>
for GAConfig<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
where
IndividualT: IndividualTrait,
MutOpT: MutationOperator<IndividualT>,
CrossOpT: CrossoverOperator<IndividualT>,
SelOpT: SelectionOperator<IndividualT>,
ReplOpT: ReplacementOperator<IndividualT>,
PopGenT: PopulationGenerator<IndividualT>,
FitnessT: Fitness<IndividualT>,
ProbeT: Probe<IndividualT>,
{
type Error = ConfigError;
fn try_from(
config_opt: GAConfigOpt<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>,
) -> Result<Self, Self::Error> {
let params = GAParams::try_from(config_opt.params)?;
let Some(fitness_fn) = config_opt.fitness_fn else {
return Err(ConfigError::MissingOperator("No fitness function specified".to_owned()));
};
let Some(mutation_operator) = config_opt.mutation_operator else {
return Err(ConfigError::MissingOperator("No mutation operator specified".to_owned()));
};
let Some(crossover_operator) = config_opt.crossover_operator else {
return Err(ConfigError::MissingOperator("No crossover operator specified".to_owned()));
};
let Some(selection_operator) = config_opt.selection_operator else {
return Err(ConfigError::MissingOperator("No selection operator specified".to_owned()));
};
let Some(replacement_operator) = config_opt.replacement_operator else {
return Err(ConfigError::MissingOperator("No replacement operator specified".to_owned()));
};
let Some(population_factory) = config_opt.population_factory else {
return Err(ConfigError::MissingPopulationFactory);
};
let Some(probe) = config_opt.probe else {
return Err(ConfigError::NoProbe);
};
Ok(GAConfig {
params,
fitness_fn,
mutation_operator,
crossover_operator,
selection_operator,
replacement_operator,
population_factory,
probe,
_phantom: PhantomData,
})
}
}
pub struct Builder;
impl Builder {
#[allow(clippy::new_ret_no_self)]
pub fn new<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>(
) -> GenericBuilder<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
where
IndividualT: IndividualTrait,
MutOpT: MutationOperator<IndividualT>,
CrossOpT: CrossoverOperator<IndividualT>,
SelOpT: SelectionOperator<IndividualT>,
ReplOpT: ReplacementOperator<IndividualT>,
PopGenT: PopulationGenerator<IndividualT>,
FitnessT: Fitness<IndividualT>,
ProbeT: Probe<IndividualT>,
{
GenericBuilder::<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>::new()
}
pub fn with_rvc<F: Fitness<RealValueIndividual>>() -> RealValuedBuilder<F> {
RealValuedBuilder::new()
}
pub fn with_bsc<F: Fitness<BitStringIndividual>>() -> BitStringBuilder<F> {
BitStringBuilder::new()
}
}
pub(crate) trait DefaultParams {
const DEFAULT_PARAMS: GAParams = GAParams {
selection_rate: 1.0,
mutation_rate: 0.05,
population_size: 100,
generation_limit: usize::MAX,
max_duration: std::time::Duration::MAX,
};
}
#[cfg(test)]
mod test {
use super::GAParamsOpt;
use crate::ga::{builder::ConfigError, GAParams};
fn convert_gaparamsopt_to_ga_params(params_opt: GAParamsOpt) -> Result<GAParams, ConfigError> {
params_opt.try_into()
}
#[test]
fn new_param_opt_is_empty() {
let params = GAParamsOpt::new();
assert!(params.selection_rate.is_none());
assert!(params.mutation_rate.is_none());
assert!(params.population_size.is_none());
assert!(params.generation_limit.is_none());
assert!(params.max_duration.is_none());
}
#[test]
fn param_opt_fills_correctly() {
let mut params_opt = GAParamsOpt::new();
params_opt.selection_rate = Some(0.5);
params_opt.generation_limit = Some(100);
let params = GAParams {
selection_rate: 1.0,
mutation_rate: 1.0,
population_size: 100,
generation_limit: 200,
max_duration: std::time::Duration::from_secs(1),
};
params_opt.fill_from(¶ms);
assert!(params_opt.selection_rate.is_some() && params_opt.selection_rate.unwrap() == 0.5);
assert!(params_opt.mutation_rate.is_some() && params_opt.mutation_rate.unwrap() == 1.0);
assert!(params_opt.population_size.is_some() && params_opt.population_size.unwrap() == 100);
assert!(params_opt.generation_limit.is_some() && params_opt.generation_limit.unwrap() == 100);
assert!(
params_opt.max_duration.is_some()
&& params_opt.max_duration.unwrap() == std::time::Duration::from_secs(1)
);
}
#[test]
fn conversion_works_as_expected() {
let mut params_opt = GAParamsOpt::new();
params_opt.selection_rate = Some(1.0);
assert!(convert_gaparamsopt_to_ga_params(params_opt.clone()).is_err());
params_opt.mutation_rate = Some(0.0);
assert!(convert_gaparamsopt_to_ga_params(params_opt.clone()).is_err());
params_opt.population_size = Some(200);
assert!(convert_gaparamsopt_to_ga_params(params_opt.clone()).is_err());
params_opt.generation_limit = Some(200);
assert!(convert_gaparamsopt_to_ga_params(params_opt.clone()).is_err());
params_opt.max_duration = Some(std::time::Duration::from_micros(10));
assert!(convert_gaparamsopt_to_ga_params(params_opt).is_ok());
}
}