Skip to content

Commit d88402a

Browse files
committed
Add new multiple extension hooks
1 parent cd00f7b commit d88402a

15 files changed

Lines changed: 498 additions & 51 deletions

benches/extension.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,21 @@ pub fn criterion_benchmark(c: &mut Criterion) {
5555
];
5656
for mut extension in extensions {
5757
group.throughput(Throughput::Elements(population_size as u64));
58-
let (genotype, state) = setup(*genes_size, population_size, &mut rng);
58+
let (mut genotype, state) = setup(*genes_size, population_size, &mut rng);
5959
group.bench_with_input(
6060
BenchmarkId::new(format!("{:?}", extension), genes_size),
6161
genes_size,
6262
|b, &_genes_size| {
6363
b.iter_batched(
6464
|| state.clone(),
6565
|mut data| {
66-
extension.call(&genotype, &mut data, &config, &mut reporter, &mut rng)
66+
extension.after_selection_complete(
67+
&mut genotype,
68+
&mut data,
69+
&config,
70+
&mut reporter,
71+
&mut rng,
72+
)
6773
},
6874
BatchSize::SmallInput,
6975
)
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use genetic_algorithm::fitness::placeholders::CountTrue;
2+
use genetic_algorithm::strategy::evolve::prelude::*;
3+
use rand::Rng;
4+
5+
/// Custom extension that demonstrates using multiple extension points.
6+
/// All extension hooks are optional as shown in default after_mutation_complete noop
7+
#[derive(Clone, Debug)]
8+
pub struct MultiPointExtension {
9+
pub selection_threshold: usize,
10+
}
11+
12+
impl Extension for MultiPointExtension {
13+
type Genotype = BinaryGenotype;
14+
15+
// After selection: Mass extinction if diversity is too low
16+
fn after_selection_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
17+
&mut self,
18+
genotype: &mut Self::Genotype,
19+
state: &mut EvolveState<Self::Genotype>,
20+
config: &EvolveConfig,
21+
reporter: &mut SR,
22+
_rng: &mut R,
23+
) {
24+
if let Some(cardinality) = state.population_cardinality() {
25+
if cardinality <= self.selection_threshold {
26+
println!(
27+
"After selection: Low diversity detected ({}), applying mass extinction",
28+
cardinality
29+
);
30+
31+
reporter.on_extension_event(
32+
ExtensionEvent("MassExtinctionAfterSelection".to_string()),
33+
genotype,
34+
state,
35+
config,
36+
);
37+
38+
// Keep only best 20% of population
39+
let keep_size = (state.population.size() as f32 * 0.2).ceil() as usize;
40+
let mut elite = self.extract_elite_chromosomes(genotype, state, config, keep_size);
41+
state.population.truncate(2);
42+
state.population.chromosomes.append(&mut elite);
43+
}
44+
}
45+
}
46+
47+
// After crossover: Log statistics
48+
fn after_crossover_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
49+
&mut self,
50+
_genotype: &mut Self::Genotype,
51+
state: &mut EvolveState<Self::Genotype>,
52+
_config: &EvolveConfig,
53+
_reporter: &mut SR,
54+
_rng: &mut R,
55+
) {
56+
let avg_age = state
57+
.population
58+
.chromosomes
59+
.iter()
60+
.map(|c| c.age())
61+
.sum::<usize>() as f64
62+
/ state.population.size() as f64;
63+
println!(
64+
"After crossover: Population size: {}, Avg age: {:.2}",
65+
state.population.size(),
66+
avg_age
67+
);
68+
}
69+
70+
// After mutation: Default Noop
71+
72+
// After fitness: Remove duplicates if too many
73+
fn after_fitness_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
74+
&mut self,
75+
genotype: &mut Self::Genotype,
76+
state: &mut EvolveState<Self::Genotype>,
77+
config: &EvolveConfig,
78+
reporter: &mut SR,
79+
_rng: &mut R,
80+
) {
81+
if genotype.genes_hashing() {
82+
let unique_count = state.population.unique_chromosome_indices().len();
83+
let total_count = state.population.size();
84+
let duplicate_ratio = 1.0 - (unique_count as f64 / total_count as f64);
85+
86+
if duplicate_ratio > 0.5 {
87+
println!(
88+
"After fitness: High duplication ratio ({:.2}%), removing duplicates",
89+
duplicate_ratio * 100.0
90+
);
91+
92+
reporter.on_extension_event(
93+
ExtensionEvent("RemoveDuplicates".to_string()),
94+
genotype,
95+
state,
96+
config,
97+
);
98+
99+
let mut unique = self.extract_unique_chromosomes(genotype, state, config);
100+
let remaining = 2usize.saturating_sub(unique.len());
101+
state.population.truncate(remaining);
102+
state.population.chromosomes.append(&mut unique);
103+
}
104+
}
105+
}
106+
107+
// After generation: Summary statistics
108+
fn after_generation_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
109+
&mut self,
110+
_genotype: &mut Self::Genotype,
111+
state: &mut EvolveState<Self::Genotype>,
112+
_config: &EvolveConfig,
113+
_reporter: &mut SR,
114+
_rng: &mut R,
115+
) {
116+
if state.current_generation() % 100 == 0 {
117+
println!(
118+
"Generation {}: Best fitness: {:?}, Cardinality: {:?}",
119+
state.current_generation(),
120+
state.best_fitness_score(),
121+
state.population_cardinality()
122+
);
123+
}
124+
}
125+
}
126+
127+
fn main() {
128+
println!("Starting evolution with multiple extension points...\n");
129+
130+
let genotype = BinaryGenotype::builder()
131+
.with_genes_size(50)
132+
.with_genes_hashing(true) // Required for deduplication
133+
.with_chromosome_recycling(true)
134+
.build()
135+
.unwrap();
136+
137+
let evolve = Evolve::builder()
138+
.with_genotype(genotype)
139+
.with_select(SelectElite::new(0.9, 0.02))
140+
.with_crossover(CrossoverUniform::new(0.8, 0.8))
141+
.with_mutate(MutateSingleGene::new(0.1))
142+
.with_fitness(CountTrue)
143+
.with_fitness_ordering(FitnessOrdering::Maximize)
144+
.with_extension(MultiPointExtension {
145+
selection_threshold: 10,
146+
})
147+
.with_target_population_size(50)
148+
.with_target_fitness_score(50) // All true
149+
.with_max_stale_generations(200)
150+
.with_max_generations(10_000)
151+
.with_rng_seed_from_u64(42) // Deterministic for demo
152+
.call()
153+
.unwrap();
154+
155+
let (best_genes, best_fitness) = evolve.best_genes_and_fitness_score().unwrap();
156+
let true_count = best_genes.iter().filter(|&&g| g).count();
157+
158+
println!("\n=== Evolution complete ===");
159+
println!("Best fitness: {}", best_fitness);
160+
println!("True genes: {}/{}", true_count, best_genes.len());
161+
println!("Total generations: {}", evolve.state.current_generation);
162+
println!("Best found at generation: {}", evolve.state.best_generation);
163+
}

examples/evolve_range_integer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ impl Fitness for DistanceTo {
1616
chromosome
1717
.genes
1818
.iter()
19-
.map(|v| if *v > self.0 { v - self.0 } else { self.0 - v } as FitnessValue)
19+
.map(|v| v.abs_diff(self.0) as FitnessValue)
2020
.sum(),
2121
)
2222
}

src/extension.rs

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,20 @@ pub type ExtensionAllele<E> = <<E as Extension>::Genotype as Genotype>::Allele;
3636
///
3737
/// For the user API, the Extension Trait has an associated Genotype. This way the user can
3838
/// implement a specialized Extension alterative with access to the user's Genotype specific
39-
/// methods at hand.
39+
/// methods at hand. The extension gives full control to the user, so all inputs are mutable.
40+
///
41+
/// # Extension Points
42+
///
43+
/// Extensions can run at multiple points in the evolution flow, always AFTER the corresponding
44+
/// reporter hooks (if any):
45+
/// * `after_selection_complete`
46+
/// * `after_crossover_complete`
47+
/// * `after_mutation_complete`
48+
/// * `after_fitness_complete`
49+
/// * `after_generation_complete`
50+
///
51+
/// For backward compatibility, the `call` method delegates to `after_selection_complete` by default.
52+
/// Extensions can override specific methods for the points they need.
4053
///
4154
/// # Example
4255
/// ```rust
@@ -53,9 +66,10 @@ pub type ExtensionAllele<E> = <<E as Extension>::Genotype as Genotype>::Allele;
5366
/// impl Extension for CustomExtension {
5467
/// type Genotype = MultiRangeGenotype<f32>;
5568
///
56-
/// fn call<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
69+
/// // Override specific extension points as needed
70+
/// fn after_selection_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
5771
/// &mut self,
58-
/// genotype: &Self::Genotype,
72+
/// genotype: &mut Self::Genotype,
5973
/// state: &mut EvolveState<Self::Genotype>,
6074
/// config: &EvolveConfig,
6175
/// reporter: &mut SR,
@@ -90,14 +104,73 @@ pub type ExtensionAllele<E> = <<E as Extension>::Genotype as Genotype>::Allele;
90104
pub trait Extension: Clone + Send + Sync + std::fmt::Debug {
91105
type Genotype: EvolveGenotype;
92106

107+
/// Legacy method for backward compatibility. Delegates to `after_selection_complete`
108+
#[deprecated(since = "0.26.0", note = "use `after_selection_complete` instead")]
93109
fn call<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
94110
&mut self,
95-
genotype: &Self::Genotype,
111+
genotype: &mut Self::Genotype,
96112
state: &mut EvolveState<Self::Genotype>,
97113
config: &EvolveConfig,
98114
reporter: &mut SR,
99115
rng: &mut R,
100-
);
116+
) {
117+
self.after_selection_complete(genotype, state, config, reporter, rng);
118+
}
119+
120+
fn after_selection_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
121+
&mut self,
122+
_genotype: &mut Self::Genotype,
123+
_state: &mut EvolveState<Self::Genotype>,
124+
_config: &EvolveConfig,
125+
_reporter: &mut SR,
126+
_rng: &mut R,
127+
) {
128+
// Default no-op implementation
129+
}
130+
131+
fn after_crossover_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
132+
&mut self,
133+
_genotype: &mut Self::Genotype,
134+
_state: &mut EvolveState<Self::Genotype>,
135+
_config: &EvolveConfig,
136+
_reporter: &mut SR,
137+
_rng: &mut R,
138+
) {
139+
// Default no-op implementation
140+
}
141+
142+
fn after_mutation_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
143+
&mut self,
144+
_genotype: &mut Self::Genotype,
145+
_state: &mut EvolveState<Self::Genotype>,
146+
_config: &EvolveConfig,
147+
_reporter: &mut SR,
148+
_rng: &mut R,
149+
) {
150+
// Default no-op implementation
151+
}
152+
153+
fn after_fitness_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
154+
&mut self,
155+
_genotype: &mut Self::Genotype,
156+
_state: &mut EvolveState<Self::Genotype>,
157+
_config: &EvolveConfig,
158+
_reporter: &mut SR,
159+
_rng: &mut R,
160+
) {
161+
// Default no-op implementation
162+
}
163+
164+
fn after_generation_complete<R: Rng, SR: StrategyReporter<Genotype = Self::Genotype>>(
165+
&mut self,
166+
_genotype: &mut Self::Genotype,
167+
_state: &mut EvolveState<Self::Genotype>,
168+
_config: &EvolveConfig,
169+
_reporter: &mut SR,
170+
_rng: &mut R,
171+
) {
172+
// Default no-op implementation
173+
}
101174

102175
fn extract_elite_chromosomes(
103176
&self,

src/extension/mass_deduplication.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ pub struct MassDeduplication<G: EvolveGenotype> {
2121
impl<G: EvolveGenotype> Extension for MassDeduplication<G> {
2222
type Genotype = G;
2323

24-
fn call<R: Rng, SR: StrategyReporter<Genotype = G>>(
24+
fn after_selection_complete<R: Rng, SR: StrategyReporter<Genotype = G>>(
2525
&mut self,
26-
genotype: &G,
26+
genotype: &mut G,
2727
state: &mut EvolveState<G>,
2828
config: &EvolveConfig,
2929
reporter: &mut SR,

src/extension/mass_degeneration.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ pub struct MassDegeneration<G: EvolveGenotype> {
2525
impl<G: EvolveGenotype> Extension for MassDegeneration<G> {
2626
type Genotype = G;
2727

28-
fn call<R: Rng, SR: StrategyReporter<Genotype = G>>(
28+
fn after_selection_complete<R: Rng, SR: StrategyReporter<Genotype = G>>(
2929
&mut self,
30-
genotype: &G,
30+
genotype: &mut G,
3131
state: &mut EvolveState<G>,
3232
config: &EvolveConfig,
3333
reporter: &mut SR,

src/extension/mass_extinction.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ pub struct MassExtinction<G: EvolveGenotype> {
2424
impl<G: EvolveGenotype> Extension for MassExtinction<G> {
2525
type Genotype = G;
2626

27-
fn call<R: Rng, SR: StrategyReporter<Genotype = G>>(
27+
fn after_selection_complete<R: Rng, SR: StrategyReporter<Genotype = G>>(
2828
&mut self,
29-
genotype: &G,
29+
genotype: &mut G,
3030
state: &mut EvolveState<G>,
3131
config: &EvolveConfig,
3232
reporter: &mut SR,

src/extension/mass_genesis.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ pub struct MassGenesis<G: EvolveGenotype> {
2222
impl<G: EvolveGenotype> Extension for MassGenesis<G> {
2323
type Genotype = G;
2424

25-
fn call<R: Rng, SR: StrategyReporter<Genotype = G>>(
25+
fn after_selection_complete<R: Rng, SR: StrategyReporter<Genotype = G>>(
2626
&mut self,
27-
genotype: &G,
27+
genotype: &mut G,
2828
state: &mut EvolveState<G>,
2929
config: &EvolveConfig,
3030
reporter: &mut SR,

src/extension/noop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl<G: EvolveGenotype> Extension for Noop<G> {
1414

1515
fn call<R: Rng, SR: StrategyReporter<Genotype = G>>(
1616
&mut self,
17-
_genotype: &G,
17+
_genotype: &mut G,
1818
_state: &mut EvolveState<G>,
1919
_config: &EvolveConfig,
2020
_reporter: &mut SR,

0 commit comments

Comments
 (0)