genetic_algorithm/strategy/evolve/
reporter.rs

1use crate::extension::ExtensionEvent;
2use crate::genotype::EvolveGenotype;
3use crate::mutate::MutateEvent;
4use crate::strategy::{StrategyConfig, StrategyReporter, StrategyState, STRATEGY_ACTIONS};
5use std::fmt::Arguments;
6use std::io::Write;
7use std::marker::PhantomData;
8
9/// A Simple Evolve Reporter generic over Genotype.
10/// A report is triggered every period generations
11///
12/// Example output:
13///
14/// ```"not rust",ignore
15/// enter - evolve, iteration: 0
16/// new best - generation: 0,  fitness_score: Some(-3112), scale_index: None, genes: None
17/// new best - generation: 6,  fitness_score: Some(-2012), scale_index: None, genes: None
18/// new best - generation: 38, fitness_score: Some(-1440), scale_index: None, genes: None
19/// new best - generation: 47, fitness_score: Some(-1439), scale_index: None, genes: None
20/// periodic - current_generation: 50, stale_generations: 2, best_generation: 47, scale_index: None, population_cardinality: Some(6), current_population_size: 800, #extension_events: 0
21/// new best - generation: 51, fitness_score: Some(-1437), scale_index: None, genes: None
22/// new best - generation: 60, fitness_score: Some(-1435), scale_index: None, genes: None
23/// new best - generation: 77, fitness_score: Some(-1120), scale_index: None, genes: None
24/// new best - generation: 99, fitness_score: Some(-639),  scale_index: None, genes: None
25/// periodic - current_generation: 100, stale_generations: 0, best_generation: 99, scale_index: None, population_cardinality: Some(11), current_population_size: 800, #extension_events: 1
26/// new best - generation: 146, fitness_score: Some(-125), scale_index: None, genes: None
27/// periodic - current_generation: 150, stale_generations: 3,   best_generation: 146, scale_index: None, population_cardinality: Some(59),  current_population_size: 800, #extension_events: 1
28/// periodic - current_generation: 200, stale_generations: 53,  best_generation: 146, scale_index: None, population_cardinality: Some(592), current_population_size: 800, #extension_events: 3
29/// periodic - current_generation: 250, stale_generations: 103, best_generation: 146, scale_index: None, population_cardinality: Some(4),   current_population_size: 800, #extension_events: 2
30/// periodic - current_generation: 300, stale_generations: 153, best_generation: 146, scale_index: None, population_cardinality: Some(335), current_population_size: 800, #extension_events: 3
31/// periodic - current_generation: 350, stale_generations: 203, best_generation: 146, scale_index: None, population_cardinality: Some(1),   current_population_size: 800, #extension_events: 2
32/// new best - generation: 379, fitness_score: Some(66), scale_index: None, genes: None
33/// periodic - current_generation: 400, stale_generations: 20,  best_generation: 379, scale_index: None, population_cardinality: Some(570), current_population_size: 800, #extension_events: 3
34/// periodic - current_generation: 450, stale_generations: 70,  best_generation: 379, scale_index: None, population_cardinality: Some(5),   current_population_size: 800, #extension_events: 2
35/// periodic - current_generation: 500, stale_generations: 120, best_generation: 379, scale_index: None, population_cardinality: Some(368), current_population_size: 800, #extension_events: 3
36/// periodic - current_generation: 550, stale_generations: 170, best_generation: 379, scale_index: None, population_cardinality: Some(692), current_population_size: 800, #extension_events: 3
37/// periodic - current_generation: 600, stale_generations: 220, best_generation: 379, scale_index: None, population_cardinality: Some(75),  current_population_size: 800, #extension_events: 2
38/// exit - evolve, iteration: 0
39///   SetupAndCleanup: 141.833µs
40///   Extension: 2.139ms
41///   Select: 11.807ms
42///   Crossover: 16.921ms
43///   Mutate: 4.337ms
44///   Fitness: 231.512ms
45///   UpdateBestChromosome: 1.497ms
46///   Other: 6.050ms
47///   Total: 274.404ms (84% fitness)
48/// ```
49///
50#[derive(Clone)]
51pub struct Simple<G: EvolveGenotype> {
52    pub buffer: Option<Vec<u8>>,
53    pub period: usize,
54    pub show_genes: bool,
55    pub show_equal_fitness: bool,
56    pub show_mutate_event: bool,
57    pub show_extension_event: bool,
58    number_of_mutate_events: usize,
59    number_of_extension_events: usize,
60    _phantom: PhantomData<G>,
61}
62impl<G: EvolveGenotype> Default for Simple<G> {
63    fn default() -> Self {
64        Self {
65            buffer: None,
66            period: 1,
67            show_genes: false,
68            show_equal_fitness: false,
69            show_mutate_event: false,
70            show_extension_event: false,
71            number_of_mutate_events: 0,
72            number_of_extension_events: 0,
73            _phantom: PhantomData,
74        }
75    }
76}
77impl<G: EvolveGenotype> Simple<G> {
78    pub fn new(period: usize) -> Self {
79        Self {
80            period,
81            ..Default::default()
82        }
83    }
84    pub fn new_with_buffer(period: usize) -> Self {
85        Self {
86            buffer: Some(Vec::new()),
87            period,
88            ..Default::default()
89        }
90    }
91    pub fn new_with_flags(
92        period: usize,
93        buffered: bool,
94        show_genes: bool,
95        show_equal_fitness: bool,
96        show_mutate_event: bool,
97        show_extension_event: bool,
98    ) -> Self {
99        Self {
100            buffer: if buffered { Some(Vec::new()) } else { None },
101            period,
102            show_genes,
103            show_equal_fitness,
104            show_mutate_event,
105            show_extension_event,
106            ..Default::default()
107        }
108    }
109    fn writeln(&mut self, args: Arguments<'_>) {
110        if let Some(buffer) = self.buffer.as_mut() {
111            buffer.write_fmt(args).unwrap_or(());
112            writeln!(buffer).unwrap_or(())
113        } else {
114            std::io::stdout().write_fmt(args).unwrap_or(());
115            println!()
116        }
117    }
118}
119impl<G: EvolveGenotype> StrategyReporter for Simple<G> {
120    type Genotype = G;
121
122    fn flush(&mut self, output: &mut Vec<u8>) {
123        if let Some(buffer) = self.buffer.as_mut() {
124            output.append(buffer);
125        }
126    }
127    fn on_enter<S: StrategyState<Self::Genotype>, C: StrategyConfig>(
128        &mut self,
129        genotype: &Self::Genotype,
130        state: &S,
131        config: &C,
132    ) {
133        let number_of_seed_genes = genotype.seed_genes_list().len();
134        if number_of_seed_genes > 0 {
135            self.writeln(format_args!(
136                "enter - {}, iteration: {}, number of seed genes: {}",
137                config.variant(),
138                state.current_iteration(),
139                number_of_seed_genes
140            ));
141        } else {
142            self.writeln(format_args!(
143                "enter - {}, iteration: {}",
144                config.variant(),
145                state.current_iteration()
146            ));
147        }
148    }
149    fn on_exit<S: StrategyState<Self::Genotype>, C: StrategyConfig>(
150        &mut self,
151        _genotype: &Self::Genotype,
152        state: &S,
153        config: &C,
154    ) {
155        let fitness_report = if let Some((hits, misses, ratio)) =
156            config.fitness_cache().map(|c| c.hit_miss_stats())
157        {
158            format!(
159                "({:.0}% fitness, cache hits/misses/ratio: {}/{}/{:.2})",
160                state.fitness_duration_rate() * 100.0,
161                hits,
162                misses,
163                ratio
164            )
165        } else {
166            format!("({:.0}% fitness)", state.fitness_duration_rate() * 100.0)
167        };
168
169        self.writeln(format_args!(
170            "exit - {}, iteration: {}",
171            config.variant(),
172            state.current_iteration()
173        ));
174        STRATEGY_ACTIONS.iter().for_each(|action| {
175            if let Some(duration) = state.durations().get(action) {
176                self.writeln(format_args!("  {:?}: {:.3?}", action, duration));
177            }
178        });
179        self.writeln(format_args!(
180            "  Total: {:.3?} {}",
181            &state.total_duration(),
182            fitness_report
183        ));
184    }
185
186    /// Is triggered after selection
187    fn on_new_generation<S: StrategyState<Self::Genotype>, C: StrategyConfig>(
188        &mut self,
189        _genotype: &Self::Genotype,
190        state: &S,
191        config: &C,
192    ) {
193        if state.current_generation() % self.period == 0 {
194            let number_of_extension_events = self.number_of_extension_events;
195            let fitness_cache_hit_miss_ratio = config.fitness_cache().map(|c| c.hit_miss_stats().2);
196
197            self.writeln(format_args!(
198                "periodic - current_generation: {}, stale_generations: {}, best_generation: {}, scale_index: {:?}, population_cardinality: {:?}, current_population_size: {}, fitness_cache_hit_miss_ratio: {:.2?}, #extension_events: {}",
199                state.current_generation(),
200                state.stale_generations(),
201                state.best_generation(),
202                state.current_scale_index(),
203                state.population_cardinality(),
204                state.population_as_ref().size(),
205                fitness_cache_hit_miss_ratio,
206                number_of_extension_events,
207            ));
208            self.number_of_mutate_events = 0;
209            self.number_of_extension_events = 0;
210        }
211    }
212
213    fn on_new_best_chromosome<S: StrategyState<Self::Genotype>, C: StrategyConfig>(
214        &mut self,
215        genotype: &Self::Genotype,
216        state: &S,
217        _config: &C,
218    ) {
219        self.writeln(format_args!(
220            "new best - generation: {}, fitness_score: {:?}, scale_index: {:?}, genes: {:?}",
221            state.current_generation(),
222            state.best_fitness_score(),
223            state.current_scale_index(),
224            if self.show_genes {
225                Some(genotype.best_genes())
226            } else {
227                None
228            },
229        ));
230    }
231
232    fn on_new_best_chromosome_equal_fitness<S: StrategyState<Self::Genotype>, C: StrategyConfig>(
233        &mut self,
234        genotype: &Self::Genotype,
235        state: &S,
236        _config: &C,
237    ) {
238        if self.show_equal_fitness {
239            self.writeln(format_args!(
240                "equal best - generation: {}, fitness_score: {:?}, scale_index: {:?}, genes: {:?}",
241                state.current_generation(),
242                state.best_fitness_score(),
243                state.current_scale_index(),
244                if self.show_genes {
245                    Some(genotype.best_genes())
246                } else {
247                    None
248                },
249            ));
250        }
251    }
252
253    fn on_extension_event<S: StrategyState<Self::Genotype>, C: StrategyConfig>(
254        &mut self,
255        event: ExtensionEvent,
256        _genotype: &Self::Genotype,
257        state: &S,
258        _config: &C,
259    ) {
260        self.number_of_extension_events += 1;
261        if self.show_extension_event {
262            match event {
263                ExtensionEvent::MassDeduplication(message) => self.writeln(format_args!(
264                    "extension event - mass deduplication - generation {} - {}",
265                    state.current_generation(),
266                    message
267                )),
268                ExtensionEvent::MassDegeneration(message) => self.writeln(format_args!(
269                    "extension event - mass degeneration - generation {} - {}",
270                    state.current_generation(),
271                    message
272                )),
273                ExtensionEvent::MassExtinction(message) => self.writeln(format_args!(
274                    "extension event - mass extinction - generation {} - {}",
275                    state.current_generation(),
276                    message
277                )),
278                ExtensionEvent::MassGenesis(message) => self.writeln(format_args!(
279                    "extension event - mass genesis - generation {} - {}",
280                    state.current_generation(),
281                    message
282                )),
283            }
284        }
285    }
286
287    fn on_mutate_event<S: StrategyState<Self::Genotype>, C: StrategyConfig>(
288        &mut self,
289        event: MutateEvent,
290        _genotype: &Self::Genotype,
291        state: &S,
292        _config: &C,
293    ) {
294        self.number_of_mutate_events += 1;
295        if self.show_mutate_event {
296            match event {
297                MutateEvent::ChangeMutationProbability(message) => self.writeln(format_args!(
298                    "mutate event - change mutation probability - generation {} - {}",
299                    state.current_generation(),
300                    message
301                )),
302            }
303        }
304    }
305}