Skip to content

Commit 7b982c7

Browse files
committed
Add ExtensionMassDeduplication
1 parent 9af7006 commit 7b982c7

File tree

11 files changed

+286
-19
lines changed

11 files changed

+286
-19
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ Find the flamegraph in: `./target/criterion/profile_evolve_binary/profile/flameg
194194
* Add simulated annealing strategy
195195
* Add Roulette selection with and without duplicates (with fitness ordering)
196196
* Add OrderOne crossover for UniqueGenotype?
197+
* Order Crossover (OX): Simple and works well for many permutation problems.
198+
* Partially Mapped Crossover (PMX): Preserves more of the parent's structure but is slightly more complex.
199+
* Cycle Crossover (CX): Ensures all genes come from one parent, useful for strict preservation of order.
200+
* Edge Crossover (EX): Preserves adjacency relationships, suitable for Traveling Salesman Problem or similar.
197201
* Add WholeArithmetic crossover for RangeGenotype?
198202
* Add CountTrueWithWork instead of CountTrueWithSleep for better benchmarks?
199203
* Explore more non-Vec genes: PackedSimd?

benches/extension.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
5050
let extensions: Vec<ExtensionWrapper> = vec![
5151
ExtensionMassGenesis::new(population_size).into(),
5252
ExtensionMassExtinction::new(population_size, 0.10, 0.02).into(),
53+
ExtensionMassDeduplication::new(population_size).into(),
5354
ExtensionMassDegeneration::new(population_size, 10, 0.02).into(),
5455
];
5556
for mut extension in extensions {

src/extension.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
//! selected for, killing of the offspring again. This reduces the efficiency, but also has the
55
//! risk of local optimum lock-in. To increase the variation in the population, an
66
//! [extension](crate::extension) mechanisms can optionally be used
7+
mod mass_deduplication;
78
mod mass_degeneration;
89
mod mass_extinction;
910
mod mass_genesis;
1011
mod noop;
1112
mod wrapper;
1213

14+
pub use self::mass_deduplication::MassDeduplication as ExtensionMassDeduplication;
1315
pub use self::mass_degeneration::MassDegeneration as ExtensionMassDegeneration;
1416
pub use self::mass_extinction::MassExtinction as ExtensionMassExtinction;
1517
pub use self::mass_genesis::MassGenesis as ExtensionMassGenesis;
@@ -53,6 +55,7 @@ pub trait Extension: Clone + Send + Sync + std::fmt::Debug {
5355

5456
#[derive(Clone, Debug)]
5557
pub enum ExtensionEvent {
58+
MassDeduplication(String),
5659
MassDegeneration(String),
5760
MassExtinction(String),
5861
MassGenesis(String),

src/extension/mass_deduplication.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use super::{Extension, ExtensionEvent};
2+
use crate::chromosome::{Chromosome, GenesHash};
3+
use crate::genotype::EvolveGenotype;
4+
use crate::strategy::evolve::{EvolveConfig, EvolveState};
5+
use crate::strategy::{StrategyAction, StrategyReporter, StrategyState};
6+
use rand::Rng;
7+
use std::collections::hash_map::Entry;
8+
use std::collections::HashMap;
9+
use std::time::Instant;
10+
11+
/// Simulates a cambrian explosion. The controlling metric is population cardinality in the
12+
/// population after selection. When this cardinality drops to the threshold, the population is
13+
/// reduced to only the unique individuals. Only works when genes_hash is stored on chromosome, as
14+
/// this is the uniqueness key, otherwise the extension is ignored.
15+
///
16+
/// Population will recover in the following generations
17+
#[derive(Debug, Clone)]
18+
pub struct MassDeduplication {
19+
pub cardinality_threshold: usize,
20+
}
21+
22+
impl Extension for MassDeduplication {
23+
fn call<G: EvolveGenotype, R: Rng, SR: StrategyReporter<Genotype = G>>(
24+
&mut self,
25+
genotype: &mut G,
26+
state: &mut EvolveState<G>,
27+
config: &EvolveConfig,
28+
reporter: &mut SR,
29+
_rng: &mut R,
30+
) {
31+
if genotype.genes_hashing() && state.population.size() >= config.target_population_size {
32+
let now = Instant::now();
33+
// ensures min 1
34+
if let Some(cardinality) = state.population_cardinality() {
35+
if cardinality <= self.cardinality_threshold {
36+
reporter.on_extension_event(
37+
ExtensionEvent::MassDeduplication("".to_string()),
38+
genotype,
39+
state,
40+
config,
41+
);
42+
43+
let mut selected_chromosomes: HashMap<GenesHash, G::Chromosome> =
44+
HashMap::new();
45+
state
46+
.population
47+
.chromosomes
48+
.drain(..)
49+
.for_each(|chromosome| {
50+
if let Some(genes_hash) = chromosome.genes_hash() {
51+
match selected_chromosomes.entry(genes_hash) {
52+
Entry::Occupied(_) => {
53+
genotype.chromosome_destructor(chromosome);
54+
}
55+
Entry::Vacant(entry) => {
56+
entry.insert(chromosome);
57+
}
58+
}
59+
} else {
60+
genotype.chromosome_destructor(chromosome);
61+
}
62+
});
63+
64+
state
65+
.population
66+
.chromosomes
67+
.extend(selected_chromosomes.into_values());
68+
69+
// ensures min 2
70+
if state.population.size() == 1 {
71+
genotype.chromosome_cloner_expand(&mut state.population.chromosomes, 1);
72+
}
73+
}
74+
}
75+
state.add_duration(StrategyAction::Extension, now.elapsed());
76+
}
77+
}
78+
}
79+
80+
impl MassDeduplication {
81+
pub fn new(cardinality_threshold: usize) -> Self {
82+
Self {
83+
cardinality_threshold,
84+
}
85+
}
86+
}

src/extension/mass_degeneration.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use rand::Rng;
66
use std::time::Instant;
77

88
/// Simulates a cambrian explosion. The controlling metric is population cardinality in the
9-
/// population after selection. When this cardinality drops to the threshold, the population
10-
/// (except for the two best chromosomes) is mutated the provided number of times, where the
11-
/// [Genotype](crate::genotype::Genotype) determines whether this is random, relative or scaled.
9+
/// population after selection. When this cardinality drops to the threshold, the population is
10+
/// mutated the provided number of times, where the [Genotype](crate::genotype::Genotype)
11+
/// determines whether this is random, relative or scaled.
1212
/// The elitism_rate ensures the passing of the best chromosomes before mutations are applied.
1313
///
1414
/// Duplicate mutations of the same gene are allowed. There is no change in population size.

src/extension/wrapper.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub use super::mass_deduplication::MassDeduplication as ExtensionMassDeduplication;
12
pub use super::mass_degeneration::MassDegeneration as ExtensionMassDegeneration;
23
pub use super::mass_extinction::MassExtinction as ExtensionMassExtinction;
34
pub use super::mass_genesis::MassGenesis as ExtensionMassGenesis;
@@ -11,10 +12,11 @@ use rand::Rng;
1112

1213
#[derive(Clone, Debug)]
1314
pub enum Wrapper {
14-
Noop(ExtensionNoop),
15+
MassDeduplication(ExtensionMassDeduplication),
16+
MassDegeneration(ExtensionMassDegeneration),
1517
MassExtinction(ExtensionMassExtinction),
1618
MassGenesis(ExtensionMassGenesis),
17-
MassDegeneration(ExtensionMassDegeneration),
19+
Noop(ExtensionNoop),
1820
}
1921

2022
impl Extension for Wrapper {
@@ -27,23 +29,31 @@ impl Extension for Wrapper {
2729
rng: &mut R,
2830
) {
2931
match self {
30-
Wrapper::Noop(extension) => extension.call(genotype, state, config, reporter, rng),
31-
Wrapper::MassExtinction(extension) => {
32+
Wrapper::MassDeduplication(extension) => {
3233
extension.call(genotype, state, config, reporter, rng)
3334
}
34-
Wrapper::MassGenesis(extension) => {
35+
Wrapper::MassDegeneration(extension) => {
3536
extension.call(genotype, state, config, reporter, rng)
3637
}
37-
Wrapper::MassDegeneration(extension) => {
38+
Wrapper::MassExtinction(extension) => {
3839
extension.call(genotype, state, config, reporter, rng)
3940
}
41+
Wrapper::MassGenesis(extension) => {
42+
extension.call(genotype, state, config, reporter, rng)
43+
}
44+
Wrapper::Noop(extension) => extension.call(genotype, state, config, reporter, rng),
4045
}
4146
}
4247
}
4348

44-
impl From<ExtensionNoop> for Wrapper {
45-
fn from(extension: ExtensionNoop) -> Self {
46-
Wrapper::Noop(extension)
49+
impl From<ExtensionMassDeduplication> for Wrapper {
50+
fn from(extension: ExtensionMassDeduplication) -> Self {
51+
Wrapper::MassDeduplication(extension)
52+
}
53+
}
54+
impl From<ExtensionMassDegeneration> for Wrapper {
55+
fn from(extension: ExtensionMassDegeneration) -> Self {
56+
Wrapper::MassDegeneration(extension)
4757
}
4858
}
4959
impl From<ExtensionMassExtinction> for Wrapper {
@@ -56,8 +66,8 @@ impl From<ExtensionMassGenesis> for Wrapper {
5666
Wrapper::MassGenesis(extension)
5767
}
5868
}
59-
impl From<ExtensionMassDegeneration> for Wrapper {
60-
fn from(extension: ExtensionMassDegeneration) -> Self {
61-
Wrapper::MassDegeneration(extension)
69+
impl From<ExtensionNoop> for Wrapper {
70+
fn from(extension: ExtensionNoop) -> Self {
71+
Wrapper::Noop(extension)
6272
}
6373
}

src/strategy/evolve/prelude.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ pub use crate::crossover::{
1111
};
1212
#[doc(no_inline)]
1313
pub use crate::extension::{
14-
ExtensionEvent, ExtensionMassDegeneration, ExtensionMassExtinction, ExtensionMassGenesis,
15-
ExtensionNoop, ExtensionWrapper,
14+
ExtensionEvent, ExtensionMassDeduplication, ExtensionMassDegeneration, ExtensionMassExtinction,
15+
ExtensionMassGenesis, ExtensionNoop, ExtensionWrapper,
1616
};
1717
#[doc(no_inline)]
1818
pub use crate::fitness::{

src/strategy/evolve/reporter.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,11 @@ impl<G: EvolveGenotype> StrategyReporter for Simple<G> {
260260
self.number_of_extension_events += 1;
261261
if self.show_extension_event {
262262
match event {
263+
ExtensionEvent::MassDeduplication(message) => self.writeln(format_args!(
264+
"extension event - mass deduplication - generation {} - {}",
265+
state.current_generation(),
266+
message
267+
)),
263268
ExtensionEvent::MassDegeneration(message) => self.writeln(format_args!(
264269
"extension event - mass degeneration - generation {} - {}",
265270
state.current_generation(),

src/strategy/prelude.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ pub use crate::crossover::{
1111
};
1212
#[doc(no_inline)]
1313
pub use crate::extension::{
14-
ExtensionEvent, ExtensionMassDegeneration, ExtensionMassExtinction, ExtensionMassGenesis,
15-
ExtensionNoop, ExtensionWrapper,
14+
ExtensionEvent, ExtensionMassDeduplication, ExtensionMassDegeneration, ExtensionMassExtinction,
15+
ExtensionMassGenesis, ExtensionNoop, ExtensionWrapper,
1616
};
1717
#[doc(no_inline)]
1818
pub use crate::fitness::{

0 commit comments

Comments
 (0)