Problem API
For certain optimization problems, it is useful to have a more structured way to define a problem. For instance, it may be useful to hold stateful information within a fitness function, store data in a more unified way, or evaluate a Genotype<C> directly without decoding. The problem interface provides a way to do just that. Under the hood of the GeneticEngine, the builder constructs a problem object that holds the codec and fitness function. Because of this, when using the problem API, we don't need a codec or a fitness function - the problem will take care of that for us.
See the image evolution example for a more detailed example of using the problem API.
The Problem interface is not available in python because it isn't needed.
// Define a problem struct that holds stateful information
struct MyFloatProblem {
num_genes: usize,
value_range: Range<f32>,
}
impl Problem<FloatChromosome<f32>, Vec<f32>> for MyFloatProblem {
fn encode(&self) -> Genotype<FloatChromosome<f32>> {
Genotype::from(FloatChromosome::from((
self.num_genes,
self.value_range.clone(),
)))
}
fn decode(&self, genotype: &Genotype<FloatChromosome<f32>>) -> Vec<f32> {
genotype
.iter()
.flat_map(|chromosome| chromosome.iter())
.map(|gene| *gene.allele())
.collect()
}
fn eval(&self, genotype: &Genotype<FloatChromosome<f32>>) -> Result<Score, RadiateError> {
// Evaluate the genotype directly without decoding
Ok(my_fitness_fn(genotype))
}
}
// `Problem<C, T>` requires `Send + Sync`; this struct satisfies them automatically.
// You'd only write a manual `unsafe impl` if it held non-thread-safe state.
// Create an engine with the problem
let mut engine = GeneticEngine::builder()
.problem(MyFloatProblem {
num_genes: 10,
value_range: 0.0..1.0,
})
.build();
// Run the engine
let result = engine.run(|epoch| epoch.index() >= 100);