Skip to content

Commit 5e3622a

Browse files
committed
Add Evolve/Permutate distinction for MutationType examples
1 parent 41fab32 commit 5e3622a

6 files changed

Lines changed: 309 additions & 13 deletions

README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,9 @@ Run with `cargo run --example [EXAMPLE_BASENAME] --release`
117117
* Explore internal and external multithreading options
118118
* See [examples/explore_multithreading](../main/examples/explore_multithreading.rs)
119119
* Explore MutationType differences with visualization
120-
* See [examples/visualize_mutation_types](../main/examples/visualize_mutation_types.rs)
121-
* Generates visualization showing exploration patterns of different mutation strategies
120+
* See [examples/visualize_evolve_mutation_types](../main/examples/visualize_evolve_mutation_types.rs) for Evolve strategy
121+
* See [examples/visualize_permutate_mutation_types](../main/examples/visualize_permutate_mutation_types.rs) for Permutate strategy
122+
* Generates visualizations showing exploration patterns of different mutation strategies
122123
* Use superset StrategyBuilder for easier switching in implementation
123124
* See [examples/explore_strategies](../main/examples/explore_strategies.rs)
124125
* Use fitness LRU cache
@@ -146,7 +147,9 @@ several alternatives. These might converge faster, but are all more sensitive to
146147
local optima than Random. The visualization below shows how different mutation
147148
types explore a 2D search space when searching for a target point:
148149

149-
![Mutation Types Exploration Patterns](examples/visualize_mutation_types.png)
150+
### Evolve Strategy (and HillClimb)
151+
152+
![Evolve Mutation Types Patterns](examples/visualize_evolve_mutation_types.png)
150153

151154
The visualization demonstrates:
152155
- **Random**: Chaotic exploration, can jump anywhere in search space
@@ -156,7 +159,21 @@ The visualization demonstrates:
156159
- **StepScaled**: Grid-like exploration with progressively finer resolution
157160
- **Discrete**: ListGenotype behaviour, for categories in heterogeneous genotypes
158161

159-
Run the example with `cargo run --example visualize_mutation_types --release` to generate this visualization.
162+
Run the example with `cargo run --example visualize_evolve_mutation_types --release` to generate this visualization.
163+
164+
### Permutate Strategy
165+
166+
For exhaustive search in smaller spaces, the Permutate strategy can
167+
systematically explore continues genotypes (RangeGenotype and
168+
MultiRangeGenotype) using Step, StepScaled, and Discrete mutation types:
169+
170+
![Permutate Mutation Types Patterns](examples/visualize_permutate_mutation_types.png)
171+
172+
- **Step**: Systematically explores grid points at fixed intervals
173+
- **StepScaled**: Hierarchical search that refines around promising regions
174+
- **Discrete**: Exhaustive exploration of all value combinations
175+
176+
Run the example with `cargo run --example visualize_permutate_mutation_types --release` to generate this visualization.
160177

161178
## Performance considerations
162179

1.02 MB
Loading

examples/visualize_mutation_types.rs renamed to examples/visualize_evolve_mutation_types.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,19 @@ impl StrategyReporter for ExplorationReporter {
7171
.chromosomes
7272
.iter()
7373
.for_each(|chromosome| {
74-
let mut points = self.explored_points.lock().unwrap();
75-
points.push((
74+
let mut explored_points = self.explored_points.lock().unwrap();
75+
explored_points.push((
7676
chromosome.genes[0],
7777
chromosome.genes[1],
7878
state.current_generation(),
7979
));
8080
});
8181

8282
// Collect best point
83-
if let Some(best_genes) = state.best_genes() {
84-
let mut points = self.best_points.lock().unwrap();
85-
points.push((best_genes[0], best_genes[1], state.best_generation()));
83+
if state.best_generation() == state.current_generation() {
84+
let best_genes = state.best_genes().unwrap();
85+
let mut best_points = self.best_points.lock().unwrap();
86+
best_points.push((best_genes[0], best_genes[1], state.current_generation()));
8687
}
8788
}
8889

@@ -267,9 +268,9 @@ fn generate_plot(
267268
}
268269

269270
fn main() {
270-
println!("=== Visualizing Mutation Types in 2D Search Space ===\n");
271-
println!("Target point: {:?}", TARGET_POINT);
271+
println!("=== Visualizing Evolve Mutation Types in 2D Search Space ===\n");
272272
println!("Starting point: {:?}", SEED_POINT);
273+
println!("Target point: {:?}", TARGET_POINT);
273274
println!("Search space: [0.0, 100.0] x [0.0, 100.0]\n");
274275

275276
let mut reporters = Vec::new();
@@ -319,7 +320,7 @@ fn main() {
319320

320321
// Generate visualization
321322
println!("\nGenerating visualization...");
322-
if let Err(e) = generate_plot(reporters, "examples/visualize_mutation_types.png") {
323+
if let Err(e) = generate_plot(reporters, "examples/visualize_evolve_mutation_types.png") {
323324
eprintln!("Failed to generate plot: {}", e);
324325
}
325326

885 KB
Loading
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
use genetic_algorithm::strategy::permutate::prelude::*;
2+
use plotters::prelude::*;
3+
use std::sync::{Arc, Mutex};
4+
5+
const TARGET_POINT: [f32; 2] = [66.666, 77.777];
6+
7+
/// Fitness function targeting the point in 2D space
8+
#[derive(Clone, Debug)]
9+
struct TargetPointFitness {
10+
target: [f32; 2],
11+
precision: f32,
12+
}
13+
14+
impl TargetPointFitness {
15+
fn new(target: [f32; 2], precision: f32) -> Self {
16+
Self { target, precision }
17+
}
18+
}
19+
20+
impl Fitness for TargetPointFitness {
21+
type Genotype = RangeGenotype<f32>;
22+
23+
fn calculate_for_chromosome(
24+
&mut self,
25+
chromosome: &FitnessChromosome<Self>,
26+
_genotype: &FitnessGenotype<Self>,
27+
) -> Option<FitnessValue> {
28+
let dx = chromosome.genes[0] - self.target[0];
29+
let dy = chromosome.genes[1] - self.target[1];
30+
let score = (dx * dx + dy * dy).sqrt() / self.precision;
31+
Some(score as FitnessValue)
32+
}
33+
}
34+
35+
/// Custom reporter that collects all exploration points during permutation
36+
#[derive(Clone)]
37+
struct PermutationReporter {
38+
explored_points: Arc<Mutex<Vec<(f32, f32, usize)>>>, // (x, y, iteration)
39+
best_points: Arc<Mutex<Vec<(f32, f32, usize)>>>, // (x, y, iteration)
40+
}
41+
42+
impl PermutationReporter {
43+
fn new() -> Self {
44+
Self {
45+
explored_points: Arc::new(Mutex::new(Vec::new())),
46+
best_points: Arc::new(Mutex::new(Vec::new())),
47+
}
48+
}
49+
50+
fn get_explored_points(&self) -> Vec<(f32, f32, usize)> {
51+
self.explored_points.lock().unwrap().clone()
52+
}
53+
fn get_best_points(&self) -> Vec<(f32, f32, usize)> {
54+
self.best_points.lock().unwrap().clone()
55+
}
56+
}
57+
58+
impl StrategyReporter for PermutationReporter {
59+
type Genotype = RangeGenotype<f32>;
60+
61+
fn on_generation_complete<S: StrategyState<Self::Genotype>, C: StrategyConfig>(
62+
&mut self,
63+
_genotype: &Self::Genotype,
64+
state: &S,
65+
_config: &C,
66+
) {
67+
// For permutation, we track the current chromosome being evaluated
68+
if let Some(chromosome) = state.chromosome_as_ref() {
69+
let mut explored_points = self.explored_points.lock().unwrap();
70+
explored_points.push((
71+
chromosome.genes[0],
72+
chromosome.genes[1],
73+
state.current_generation(),
74+
));
75+
}
76+
77+
// Collect best point
78+
if state.best_generation() == state.current_generation() {
79+
let best_genes = state.best_genes().unwrap();
80+
let mut best_points = self.best_points.lock().unwrap();
81+
best_points.push((best_genes[0], best_genes[1], state.current_generation()));
82+
}
83+
}
84+
}
85+
86+
/// Run permutation with a specific mutation type and collect exploration points
87+
fn run_permutation(
88+
mutation_type: MutationType<f32>,
89+
mutation_type_name: String,
90+
) -> PermutationReporter {
91+
let fitness = TargetPointFitness::new(TARGET_POINT, 0.001);
92+
let reporter = PermutationReporter::new();
93+
94+
// Create genotype with 2 genes for 2D visualization
95+
// Using integer type for discrete steps
96+
let genotype = RangeGenotype::<f32>::builder()
97+
.with_genes_size(2)
98+
.with_allele_range(0.0..=100.0)
99+
.with_mutation_type(mutation_type)
100+
// No seed - let permutation explore the entire space
101+
.build()
102+
.unwrap();
103+
104+
// Build permutation strategy
105+
let permutate = Permutate::builder()
106+
.with_genotype(genotype)
107+
.with_fitness(fitness)
108+
.with_fitness_ordering(FitnessOrdering::Minimize)
109+
.with_reporter(reporter)
110+
.with_par_fitness(true) // maybe messes up graphs a bit
111+
.call()
112+
.unwrap();
113+
114+
println!(
115+
"Completed {} with {} exploration points, best_generation: {}, best_fitness_score: {:?}",
116+
mutation_type_name,
117+
permutate.reporter.get_explored_points().len(),
118+
permutate.best_generation(),
119+
permutate.best_fitness_score(),
120+
);
121+
122+
permutate.reporter
123+
}
124+
125+
/// Generate visualization plot showing exploration patterns
126+
fn generate_plot(
127+
reporters: Vec<(String, PermutationReporter)>,
128+
output_path: &str,
129+
) -> Result<(), Box<dyn std::error::Error>> {
130+
// Ensure the output path is created from the project root
131+
let output_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(output_path);
132+
let root = BitMapBackend::new(&output_path, (1800, 600)).into_drawing_area();
133+
root.fill(&WHITE)?;
134+
135+
let mut chart_builders = root.split_evenly((1, 3));
136+
137+
for ((name, reporter), chart_area) in reporters.iter().zip(chart_builders.iter_mut()) {
138+
let explored_points = reporter.get_explored_points();
139+
let best_points = reporter.get_best_points();
140+
141+
let mut chart = ChartBuilder::on(chart_area)
142+
.caption(name, ("sans-serif", 30))
143+
.margin(10)
144+
.x_label_area_size(30)
145+
.y_label_area_size(30)
146+
.build_cartesian_2d(0f32..100f32, 0f32..100f32)?;
147+
148+
chart.configure_mesh().draw()?;
149+
150+
// Draw target point
151+
chart
152+
.draw_series(PointSeries::of_element(
153+
[(TARGET_POINT[0], TARGET_POINT[1])],
154+
5,
155+
&RED,
156+
&|c, s, _st| {
157+
EmptyElement::at(c)
158+
+ Cross::new((0, 0), s * 2, ShapeStyle::from(&RED).stroke_width(2))
159+
},
160+
))?
161+
.label("Target")
162+
.legend(|(x, y)| Cross::new((x, y), 5 * 2, ShapeStyle::from(&RED).stroke_width(2)));
163+
164+
// Draw exploration points with color gradient based on generation
165+
if !explored_points.is_empty() {
166+
let max_gen = explored_points.iter().map(|(_, _, g)| *g).max().unwrap() as f32;
167+
168+
// Draw points
169+
for (x, y, gen) in &explored_points {
170+
let color_intensity = (*gen as f32 / max_gen * 200.0) as u8;
171+
let color = RGBColor(50, 50 + color_intensity, 255 - color_intensity);
172+
173+
chart.draw_series(PointSeries::of_element(
174+
[(*x, *y)],
175+
2,
176+
&color,
177+
&|c, s, st| Circle::new(c, s, st.filled()),
178+
))?;
179+
}
180+
}
181+
182+
// Draw best points with color gradient based on generation
183+
if !best_points.is_empty() {
184+
let max_gen = best_points.iter().map(|(_, _, g)| *g).max().unwrap() as f32;
185+
186+
// Draw lines connecting consecutive points
187+
for window in best_points.windows(2) {
188+
let (x1, y1, g1) = window[0];
189+
let (x2, y2, _) = window[1];
190+
191+
let color_intensity = (g1 as f32 / max_gen * 200.0) as u8;
192+
let color = RGBColor(50, 50 + color_intensity, 255 - color_intensity);
193+
194+
chart.draw_series(LineSeries::new(vec![(x1, y1), (x2, y2)], &color))?;
195+
}
196+
}
197+
198+
// Draw ending point
199+
if let Some(ending_point) = best_points.last() {
200+
chart
201+
.draw_series(PointSeries::of_element(
202+
[(ending_point.0, ending_point.1)],
203+
4,
204+
&RED,
205+
&|c, s, st| Circle::new(c, s, st.filled()),
206+
))?
207+
.label("End")
208+
.legend(|(x, y)| Circle::new((x, y), 3, RED.filled()));
209+
}
210+
211+
chart.configure_series_labels().draw()?;
212+
}
213+
214+
root.present()?;
215+
println!("Plot saved to {}", output_path.display());
216+
217+
Ok(())
218+
}
219+
220+
fn main() {
221+
println!("=== Visualizing Permutation Mutation Types in 2D Search Space ===\n");
222+
println!("Target point: {:?}", TARGET_POINT);
223+
println!("Search space: [0, 100] x [0, 100] (integer grid)\n");
224+
println!("Note: Permutation systematically explores the entire space as defined by the mutation type.\n");
225+
226+
let mut reporters = Vec::new();
227+
228+
// Step mutation - fixed step
229+
// With step=5, this will explore a 20x20 grid (every 5th point)
230+
println!("Running Step(5) mutation with permutation...");
231+
let reporter = run_permutation(MutationType::Step(5.0), "Step(5.0)".to_string());
232+
reporters.push(("Step(±5) Full Grid Exploration".to_string(), reporter));
233+
234+
// StepScaled mutation - halving each scale
235+
// This will first explore with large steps, then refine around the best
236+
println!("Running StepScaled mutation with permutation...");
237+
let steps = vec![50.0, 25.0, 12.5, 6.25, 3.125, 1.5625];
238+
let reporter = run_permutation(
239+
MutationType::StepScaled(steps.clone()),
240+
format!("StepScaled({:?})", steps),
241+
);
242+
reporters.push(("StepScaled Nested Grids".to_string(), reporter));
243+
244+
// Discrete mutation - like ListGenotype for categories
245+
// This explores a sampled subset of the space
246+
println!("Running Discrete mutation with permutation (limited range for feasibility)...");
247+
let reporter = run_permutation(MutationType::Discrete, "Discrete".to_string());
248+
reporters.push(("Discrete Full Exploration".to_string(), reporter));
249+
250+
// Generate visualization
251+
println!("\nGenerating visualization...");
252+
if let Err(e) = generate_plot(reporters, "examples/visualize_permutate_mutation_types.png") {
253+
eprintln!("Failed to generate plot: {}", e);
254+
}
255+
256+
println!("\n=== Analysis Summary ===");
257+
println!("- Step: Explores all points within step distance systematically");
258+
println!("- StepScaled: Progressively refines search with smaller steps");
259+
println!("- Discrete: Jumps to any discrete value in the range");
260+
println!("Color gradient: Blue (early) → Green (late) generations");
261+
}
262+

src/lib.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@
9898
//! * Explore internal and external multithreading options
9999
//! * See [examples/explore_multithreading](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/explore_multithreading.rs)
100100
//! * Explore [MutationType](crate::genotype::MutationType) differences with visualization
101-
//! * See [examples/visualize_mutation_types](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/visualize_mutation_types.rs)
101+
//! * See [examples/visualize_evolve_mutation_types](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/visualize_evolve_mutation_types.rs)
102+
//! * See [examples/visualize_permutate_mutation_types](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/visualize_permutate_mutation_types.rs)
103+
//! * Generates visualizations showing exploration patterns of different mutation strategies
102104
//! * Use superset StrategyBuilder for easier switching in implementation
103105
//! * See [examples/explore_strategies](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/explore_strategies.rs)
104106
//! * Use fitness LRU cache
@@ -127,6 +129,8 @@
127129
//! visualization example shows how different mutation types explore a 2D search space when searching
128130
//! for a target point:
129131
//!
132+
//! ### Evolve Strategy (and HillClimb)
133+
//!
130134
//! The visualization demonstrates:
131135
//! - **Random**: Chaotic exploration, can jump anywhere in search space
132136
//! - **Range**: Local search with fixed radius around current position
@@ -137,6 +141,18 @@
137141
//!
138142
//! Run the example with `cargo run --example visualize_mutation_types --release` to generate the visualization.
139143
//!
144+
//! ### Permutate Strategy (and HillClimb)
145+
//!
146+
//! For exhaustive search in smaller spaces, the Permutate strategy can
147+
//! systematically explore continues genotypes (RangeGenotype and
148+
//! MultiRangeGenotype) using Step, StepScaled, and Discrete mutation types:
149+
//!
150+
//! - **Step**: Systematically explores grid points at fixed intervals
151+
//! - **StepScaled**: Hierarchical search that refines around promising regions
152+
//! - **Discrete**: Exhaustive exploration of all value combinations
153+
//!
154+
//! Run the example with `cargo run --example visualize_permutate_mutation_types --release` to generate this visualization.
155+
//!
140156
//! ## Performance considerations
141157
//!
142158
//! For the [Evolve](strategy::evolve::Evolve) strategy:

0 commit comments

Comments
 (0)