Skip to main content

genalg/
lib.rs

1//! # GenAlg
2//!
3//! A flexible, high-performance genetic algorithm library written in Rust.
4//!
5//! ## Overview
6//!
7//! GenAlg is a modern, thread-safe genetic algorithm framework designed for flexibility,
8//! performance, and ease of use. It provides a robust foundation for implementing
9//! evolutionary algorithms to solve optimization problems across various domains.
10//!
11//! ## Key Features
12//!
13//! - **Thread-safe**: Designed for parallel processing with `Send` and `Sync` traits
14//! - **High Performance**: Optimized for speed with thread-local random number generation
15//! - **Flexible**: Adaptable to a wide range of optimization problems
16//! - **Extensible**: Easy to implement custom phenotypes, fitness functions, and breeding strategies
17//! - **Parallel Processing**: Automatic parallelization for large populations using Rayon
18//! - **Local Search**: Integrated local search algorithms to refine solutions
19//! - **Constraint Handling**: Support for combinatorial optimization with constraint management
20//! - **Fitness Caching**: Efficient caching for expensive fitness evaluations
21//!
22//! ## Core Components
23//!
24//! ### Phenotype
25//!
26//! The [`Phenotype`] trait defines the interface for types that represent individuals
27//! in an evolutionary algorithm. It provides methods for crossover and mutation.
28//!
29//! ```rust
30//! use genalg::phenotype::Phenotype;
31//! use genalg::rng::RandomNumberGenerator;
32//!
33//! #[derive(Clone, Debug)]
34//! struct MyPhenotype {
35//!     value: f64,
36//! }
37//!
38//! impl Phenotype for MyPhenotype {
39//!     fn crossover(&mut self, other: &Self) {
40//!         // Combine genetic material with another individual
41//!         self.value = (self.value + other.value) / 2.0;
42//!     }
43//!
44//!     fn mutate(&mut self, rng: &mut RandomNumberGenerator) {
45//!         // Introduce random changes
46//!         let values = rng.fetch_uniform(-0.1, 0.1, 1);
47//!         let delta = values.front().unwrap();
48//!         self.value += *delta as f64;
49//!     }
50//!     
51//!     // Optional: Override for better performance in parallel contexts
52//!     fn mutate_thread_local(&mut self) {
53//!         // Custom implementation using thread-local RNG
54//!         use genalg::rng::ThreadLocalRng;
55//!         let delta = ThreadLocalRng::gen_range(-0.1..0.1);
56//!         self.value += delta;
57//!     }
58//! }
59//! ```
60//!
61//! ### Challenge
62//!
63//! The [`Challenge`] trait defines how to evaluate the fitness of phenotypes:
64//!
65//! ```rust
66//! use genalg::evolution::Challenge;
67//! use genalg::phenotype::Phenotype;
68//!
69//! #[derive(Clone, Debug)]
70//! struct MyPhenotype {
71//!     value: f64,
72//! }
73//!
74//! // Implementation of Phenotype trait omitted for brevity
75//! # impl Phenotype for MyPhenotype {
76//! #     fn crossover(&mut self, other: &Self) {}
77//! #     fn mutate(&mut self, rng: &mut genalg::rng::RandomNumberGenerator) {}
78//! # }
79//!
80//! #[derive(Clone)]
81//! struct MyChallenge {
82//!     target: f64,
83//! }
84//!
85//! impl Challenge<MyPhenotype> for MyChallenge {
86//!     fn score(&self, phenotype: &MyPhenotype) -> f64 {
87//!         // Calculate and return fitness score (higher is better)
88//!         1.0 / (phenotype.value - self.target).abs().max(0.001)
89//!     }
90//! }
91//! ```
92//!
93//! ### Breeding Strategies
94//!
95//! GenAlg provides several built-in breeding strategies:
96//!
97//! 1. [`OrdinaryStrategy`]: A basic breeding strategy where the first parent is considered
98//!    the winner of the previous generation.
99//!
100//! 2. [`BoundedBreedStrategy`]: Similar to `OrdinaryStrategy` but imposes bounds on
101//!    phenotypes during evolution using the [`Magnitude`] trait.
102//!
103//! 3. [`CombinatorialBreedStrategy`]: Specialized for combinatorial optimization problems,
104//!    supporting constraint handling and penalty-based fitness adjustment.
105//!
106//! ### Selection Strategies
107//!
108//! GenAlg provides several built-in selection strategies for choosing parents based on their fitness:
109//!
110//! 1. [`ElitistSelection`]: Selects the best individuals based on fitness scores.
111//!
112//! 2. [`TournamentSelection`]: Selects individuals through tournament selection.
113//!
114//! 3. [`RouletteWheelSelection`]: Selects individuals with probability proportional to fitness.
115//!
116//! 4. [`RankBasedSelection`]: Selects individuals based on their rank in the population.
117//!
118//! ### Local Search Algorithms
119//!
120//! GenAlg integrates local search algorithms to refine solutions during the evolutionary process:
121//!
122//! 1. [`HillClimbing`]: A simple hill climbing algorithm that iteratively moves to better neighboring solutions.
123//!
124//! 2. [`SimulatedAnnealing`]: A probabilistic algorithm that allows moves to worse solutions with decreasing probability.
125//!
126//! 3. [`TabuSearch`]: A metaheuristic that maintains a list of recently visited solutions to avoid cycling.
127//!
128//! 4. [`HybridLocalSearch`]: Combines multiple local search algorithms for more effective refinement.
129//!
130//! Local search can be applied to selected individuals using various strategies:
131//!
132//! 1. [`AllIndividualsStrategy`]: Applies local search to all individuals in the population.
133//!
134//! 2. [`TopNStrategy`]: Applies local search to the top N individuals based on fitness.
135//!
136//! 3. [`TopPercentStrategy`]: Applies local search to a percentage of the top individuals.
137//!
138//! 4. [`ProbabilisticStrategy`]: Applies local search to individuals with a certain probability.
139//!
140//! ### Constraint Handling
141//!
142//! For combinatorial optimization problems, GenAlg provides constraint handling capabilities:
143//!
144//! 1. [`Constraint`]: Trait for defining constraints on phenotypes.
145//!
146//! 2. [`ConstraintManager`]: Manages multiple constraints and provides methods for checking and repairing solutions.
147//!
148//! 3. [`PenaltyAdjustedChallenge`]: Wraps a challenge to adjust fitness scores based on constraint violations.
149//!
150//! 4. Built-in constraints for combinatorial problems like [`UniqueElementsConstraint`], [`CompleteAssignmentConstraint`], etc.
151//!
152//! ### Evolution Launcher
153//!
154//! The [`EvolutionLauncher`] manages the evolution process using a specified breeding
155//! strategy, selection strategy, local search manager, and challenge. It now requires four parameters:
156//! a breeding strategy, a selection strategy, an optional local search manager, and a challenge.
157//!
158//! ```rust
159//! use genalg::{
160//!     evolution::{Challenge, EvolutionLauncher, EvolutionOptions, LogLevel},
161//!     phenotype::Phenotype,
162//!     rng::RandomNumberGenerator,
163//!     breeding::OrdinaryStrategy,
164//!     selection::ElitistSelection,
165//!     local_search::{HillClimbing, AllIndividualsStrategy},
166//! };
167//!
168//! #[derive(Clone, Debug)]
169//! struct MyPhenotype {
170//!     value: f64,
171//! }
172//!
173//! // Implementation of Phenotype trait omitted for brevity
174//! # impl Phenotype for MyPhenotype {
175//! #     fn crossover(&mut self, other: &Self) {}
176//! #     fn mutate(&mut self, rng: &mut RandomNumberGenerator) {}
177//! # }
178//!
179//! #[derive(Clone)]
180//! struct MyChallenge {
181//!     target: f64,
182//! }
183//!
184//! impl Challenge<MyPhenotype> for MyChallenge {
185//!     fn score(&self, phenotype: &MyPhenotype) -> f64 {
186//!         // Calculate and return fitness score (higher is better)
187//!         1.0 / (phenotype.value - self.target).abs().max(0.001)
188//!     }
189//! }
190//!
191//! // Create components for evolution
192//! let breed_strategy = OrdinaryStrategy::default();
193//! let selection_strategy = ElitistSelection::default();
194//! let challenge = MyChallenge { target: 42.0 };
195//! let options = EvolutionOptions::default();
196//! let starting_value = MyPhenotype { value: 0.0 };
197//!
198//! // Create launcher with breeding, selection, and challenge
199//! // Note: The third parameter (local_search_manager) can be None if local search is not needed
200//! let launcher: EvolutionLauncher<
201//!     MyPhenotype,
202//!     OrdinaryStrategy,
203//!     ElitistSelection,
204//!     HillClimbing,
205//!     MyChallenge,
206//!     AllIndividualsStrategy
207//! > = EvolutionLauncher::new(
208//!     breed_strategy,
209//!     selection_strategy,
210//!     None, // No local search
211//!     challenge
212//! );
213//!
214//! // Configure and run the evolution
215//! let result = launcher
216//!     .configure(options, starting_value)
217//!     .with_seed(42)  // Optional: Set a specific seed
218//!     .run();
219//! ```
220//!
221//! If you want to use local search, you can create a local search manager and pass it to the launcher:
222//!
223//! ```rust
224//! use genalg::{
225//!     evolution::{Challenge, EvolutionLauncher, EvolutionOptions, LogLevel},
226//!     phenotype::Phenotype,
227//!     rng::RandomNumberGenerator,
228//!     breeding::OrdinaryStrategy,
229//!     selection::ElitistSelection,
230//!     local_search::{HillClimbing, AllIndividualsStrategy, LocalSearchManager},
231//! };
232//!
233//! // Define a simple phenotype
234//! #[derive(Clone, Debug)]
235//! struct MyPhenotype {
236//!     value: f64,
237//! }
238//!
239//! impl Phenotype for MyPhenotype {
240//!     fn crossover(&mut self, other: &Self) {
241//!         self.value = (self.value + other.value) / 2.0;
242//!     }
243//!
244//!     fn mutate(&mut self, rng: &mut RandomNumberGenerator) {
245//!         let values = rng.fetch_uniform(-0.1, 0.1, 1);
246//!         let delta = values.front().unwrap();
247//!         self.value += *delta as f64;
248//!     }
249//! }
250//!
251//! // Define a challenge
252//! #[derive(Clone)]
253//! struct MyChallenge {
254//!     target: f64,
255//! }
256//!
257//! impl Challenge<MyPhenotype> for MyChallenge {
258//!     fn score(&self, phenotype: &MyPhenotype) -> f64 {
259//!         1.0 / (phenotype.value - self.target).abs().max(0.001)
260//!     }
261//! }
262//!
263//! // Create components for evolution
264//! let breed_strategy = OrdinaryStrategy::default();
265//! let selection_strategy = ElitistSelection::default();
266//! let challenge = MyChallenge { target: 42.0 };
267//! let options = EvolutionOptions::default();
268//! let starting_value = MyPhenotype { value: 0.0 };
269//!
270//! // Create a local search manager
271//! let hill_climbing = HillClimbing::new(10, 10).unwrap();
272//! let application_strategy = AllIndividualsStrategy::new();
273//! let local_search_manager = Some(
274//!     LocalSearchManager::new(hill_climbing, application_strategy)
275//! );
276//!
277//! // Create launcher with breeding, selection, local search, and challenge
278//! let launcher: EvolutionLauncher<
279//!     MyPhenotype,
280//!     OrdinaryStrategy,
281//!     ElitistSelection,
282//!     HillClimbing,
283//!     MyChallenge,
284//!     AllIndividualsStrategy
285//! > = EvolutionLauncher::new(
286//!     breed_strategy,
287//!     selection_strategy,
288//!     local_search_manager,
289//!     challenge
290//! );
291//!
292//! // Configure and run the evolution with local search
293//! let result = launcher
294//!     .configure(options, starting_value)
295//!     .with_seed(42)
296//!     .with_local_search()  // Enable local search
297//!     .run();
298//! ```
299//!
300//! You can also use the builder pattern to create an `EvolutionLauncher`:
301//!
302//! ```rust
303//! # use genalg::{
304//! #     evolution::{Challenge, EvolutionLauncher, EvolutionOptions, LogLevel},
305//! #     phenotype::Phenotype,
306//! #     rng::RandomNumberGenerator,
307//! #     breeding::OrdinaryStrategy,
308//! #     selection::ElitistSelection,
309//! #     local_search::{HillClimbing, AllIndividualsStrategy},
310//! #     error::Result,
311//! # };
312//! #
313//! # #[derive(Clone, Debug)]
314//! # struct MyPhenotype {
315//! #     value: f64,
316//! # }
317//! #
318//! # impl Phenotype for MyPhenotype {
319//! #     fn crossover(&mut self, other: &Self) {}
320//! #     fn mutate(&mut self, rng: &mut RandomNumberGenerator) {}
321//! # }
322//! #
323//! # #[derive(Clone)]
324//! # struct MyChallenge {
325//! #     target: f64,
326//! # }
327//! #
328//! # impl Challenge<MyPhenotype> for MyChallenge {
329//! #     fn score(&self, phenotype: &MyPhenotype) -> f64 {
330//! #         1.0 / (phenotype.value - self.target).abs().max(0.001)
331//! #     }
332//! # }
333//!
334//! fn create_launcher() -> Result<EvolutionLauncher<
335//!     MyPhenotype,
336//!     OrdinaryStrategy,
337//!     ElitistSelection,
338//!     HillClimbing,
339//!     MyChallenge,
340//!     AllIndividualsStrategy
341//! >> {
342//!     let breed_strategy = OrdinaryStrategy::default();
343//!     let selection_strategy = ElitistSelection::default();
344//!     let challenge = MyChallenge { target: 42.0 };
345//!     
346//!     // Create a local search strategy and application strategy
347//!     let hill_climbing = HillClimbing::new(10, 10)?;
348//!     let application_strategy = AllIndividualsStrategy::new();
349//!     
350//!     // Use the builder pattern
351//!     EvolutionLauncher::builder()
352//!         .with_breed_strategy(breed_strategy)
353//!         .with_selection_strategy(selection_strategy)
354//!         .with_local_search_manager(hill_climbing, application_strategy)
355//!         .with_challenge(challenge)
356//!         .build()
357//! }
358//! ```
359//!
360//! ### Fitness Caching
361//!
362//! For expensive fitness evaluations, GenAlg provides caching mechanisms:
363//!
364//! ```rust
365//! use genalg::{
366//!     caching::{CacheKey, CachedChallenge},
367//!     evolution::Challenge,
368//!     phenotype::Phenotype,
369//! };
370//!
371//! #[derive(Clone, Debug)]
372//! struct MyPhenotype {
373//!     value: f64,
374//! }
375//!
376//! // Implementation of Phenotype trait omitted for brevity
377//! # impl Phenotype for MyPhenotype {
378//! #     fn crossover(&mut self, other: &Self) {}
379//! #     fn mutate(&mut self, rng: &mut genalg::rng::RandomNumberGenerator) {}
380//! # }
381//!
382//! // Implement CacheKey to enable caching
383//! impl CacheKey for MyPhenotype {
384//!     // Use i32 as the key type since it implements Hash and Eq
385//!     type Key = i32;
386//!     
387//!     fn cache_key(&self) -> Self::Key {
388//!         // Convert the f64 value to an i32 for caching
389//!         // In a real implementation, you might want to use a more sophisticated
390//!         // conversion that preserves more precision
391//!         self.value as i32
392//!     }
393//! }
394//!
395//! #[derive(Clone)]
396//! struct MyChallenge {
397//!     target: f64,
398//! }
399//!
400//! impl Challenge<MyPhenotype> for MyChallenge {
401//!     fn score(&self, phenotype: &MyPhenotype) -> f64 {
402//!         // Expensive calculation
403//!         1.0 / (phenotype.value - self.target).abs().max(0.001)
404//!     }
405//! }
406//!
407//! // Create a cached version of the challenge
408//! let challenge = MyChallenge { target: 42.0 };
409//! let cached_challenge = CachedChallenge::new(challenge);
410//! ```
411//!
412//! ### Thread-Local Random Number Generation
413//!
414//! For optimal performance in parallel contexts, GenAlg provides thread-local random
415//! number generation through the [`ThreadLocalRng`] struct:
416//!
417//! ```rust
418//! use genalg::rng::ThreadLocalRng;
419//!
420//! // Generate a random number in a range
421//! let value = ThreadLocalRng::gen_range(0.0..1.0);
422//!
423//! // Generate multiple random numbers
424//! let numbers = ThreadLocalRng::fetch_uniform(0.0, 1.0, 5);
425//! ```
426//!
427//! ## Parallel Processing
428//!
429//! GenAlg automatically uses parallel processing for fitness evaluation and breeding
430//! when the population size exceeds the parallel threshold. Configure this in your
431//! [`EvolutionOptions`]:
432//!
433//! ```rust
434//! use genalg::evolution::options::{EvolutionOptions, LogLevel};
435//!
436//! // Using the builder pattern
437//! let options = EvolutionOptions::builder()
438//!     .num_generations(100)
439//!     .log_level(LogLevel::Info)
440//!     .population_size(10)
441//!     .num_offspring(50)
442//!     .parallel_threshold(500) // Use parallel processing when population >= 500
443//!     .build();
444//!
445//! // Or using setter methods
446//! let mut options = EvolutionOptions::default();
447//! options.set_parallel_threshold(500);
448//! ```
449//!
450//! ### Performance Characteristics
451//!
452//! GenAlg is optimized for parallel processing with larger populations:
453//!
454//! - **Parallel operations** show significant performance improvements for larger populations
455//! - **Thread-local RNG** eliminates mutex contention in parallel contexts
456//! - **Automatic parallelization** occurs when population size exceeds the parallel threshold
457//! - **Optimal threshold** depends on your specific hardware and problem complexity
458//!
459//! For best performance:
460//! - Use `mutate_thread_local()` in your phenotype implementations
461//! - Set an appropriate parallel threshold based on your hardware
462//! - Consider using the `OrdinaryStrategy` for very large populations
463//! - Use fitness caching for expensive evaluations
464//!
465//! ## Error Handling
466//!
467//! GenAlg provides a comprehensive error handling system through the [`error`] module:
468//!
469//! ```rust
470//! use genalg::error::{GeneticError, Result, ResultExt, OptionExt};
471//!
472//! fn my_function() -> Result<()> {
473//!     // Return specific errors
474//!     if false {
475//!         return Err(GeneticError::Configuration("Invalid parameter".to_string()));
476//!     }
477//!     
478//!     // Convert Option to Result with custom error
479//!     let candidates = vec![1, 2, 3];
480//!     let best = candidates.iter().max().ok_or_else_genetic(||
481//!         GeneticError::EmptyPopulation
482//!     )?;
483//!     
484//!     Ok(())
485//! }
486//! ```
487//!
488//! ## Modules
489//!
490//! - [`error`]: Error types and utilities
491//! - [`evolution`]: Evolution process management
492//! - [`phenotype`]: Phenotype trait definition
493//! - [`rng`]: Random number generation utilities
494//! - [`strategy`]: Breeding strategy implementations
495//! - [`selection`]: Selection strategy implementations
496//! - [`local_search`]: Local search algorithms and application strategies
497//! - [`constraints`]: Constraint handling for combinatorial optimization
498//! - [`caching`]: Fitness caching for expensive evaluations
499//!
500//! [`Phenotype`]: phenotype::Phenotype
501//! [`Challenge`]: evolution::Challenge
502//! [`OrdinaryStrategy`]: breeding::ordinary::OrdinaryStrategy
503//! [`BoundedBreedStrategy`]: breeding::bounded::BoundedBreedStrategy
504//! [`CombinatorialBreedStrategy`]: breeding::combinatorial::CombinatorialBreedStrategy
505//! [`Magnitude`]: breeding::bounded::Magnitude
506//! [`EvolutionLauncher`]: evolution::EvolutionLauncher
507//! [`EvolutionOptions`]: evolution::options::EvolutionOptions
508//! [`ThreadLocalRng`]: rng::ThreadLocalRng
509//! [`ElitistSelection`]: selection::ElitistSelection
510//! [`TournamentSelection`]: selection::TournamentSelection
511//! [`RouletteWheelSelection`]: selection::RouletteWheelSelection
512//! [`RankBasedSelection`]: selection::RankBasedSelection
513//! [`HillClimbing`]: local_search::HillClimbing
514//! [`SimulatedAnnealing`]: local_search::SimulatedAnnealing
515//! [`TabuSearch`]: local_search::TabuSearch
516//! [`HybridLocalSearch`]: local_search::HybridLocalSearch
517//! [`AllIndividualsStrategy`]: local_search::application::AllIndividualsStrategy
518//! [`TopNStrategy`]: local_search::application::TopNStrategy
519//! [`TopPercentStrategy`]: local_search::application::TopPercentStrategy
520//! [`ProbabilisticStrategy`]: local_search::application::ProbabilisticStrategy
521//! [`Constraint`]: constraints::Constraint
522//! [`ConstraintManager`]: constraints::ConstraintManager
523//! [`PenaltyAdjustedChallenge`]: constraints::PenaltyAdjustedChallenge
524//! [`UniqueElementsConstraint`]: constraints::combinatorial::UniqueElementsConstraint
525//! [`CompleteAssignmentConstraint`]: constraints::combinatorial::CompleteAssignmentConstraint
526
527pub mod breeding;
528pub mod caching;
529pub mod constraints;
530pub mod error;
531pub mod evolution;
532pub mod local_search;
533pub mod phenotype;
534pub mod rng;
535pub mod selection;
536
537// Re-export commonly used types for convenience
538pub use breeding::combinatorial::{CombinatorialBreedConfig, CombinatorialBreedStrategy};
539pub use breeding::{
540    bounded::{BoundedBreedConfig, BoundedBreedStrategy, Magnitude},
541    ordinary::OrdinaryStrategy,
542    BreedStrategy,
543};
544pub use caching::{CacheKey, CachedChallenge, ThreadLocalCachedChallenge};
545pub use constraints::{Constraint, ConstraintManager, ConstraintViolation};
546pub use error::{GeneticError, OptionExt, Result, ResultExt};
547pub use evolution::{Challenge, EvolutionLauncher, EvolutionOptions, EvolutionResult, LogLevel};
548pub use local_search::{HillClimbing, LocalSearch, SimulatedAnnealing};
549pub use phenotype::Phenotype;
550/// A marker trait for phenotypes that can be serialized and deserialized.
551///
552/// This trait is automatically implemented for any type that implements both
553/// `Phenotype` and the relevant serde traits (`Serialize` and `DeserializeOwned`).
554/// It's only available when the `serde` feature is enabled.
555///
556/// # Example
557///
558/// ```rust
559/// # #[cfg(feature = "serde")]
560/// # {
561/// use genalg::phenotype::{Phenotype, SerializablePhenotype};
562/// use genalg::rng::RandomNumberGenerator;
563/// use serde::{Serialize, Deserialize};
564///
565/// #[derive(Clone, Debug, Serialize, Deserialize)]
566/// struct MyPhenotype {
567///     value: f64,
568/// }
569///
570/// impl Phenotype for MyPhenotype {
571///     fn crossover(&mut self, other: &Self) {
572///         self.value = (self.value + other.value) / 2.0;
573///     }
574///     
575///     fn mutate(&mut self, rng: &mut RandomNumberGenerator) {
576///         let values = rng.fetch_uniform(-0.1, 0.1, 1);
577///         let delta = values.front().unwrap();
578///         self.value += *delta as f64;
579///     }
580/// }
581///
582/// // SerializablePhenotype is automatically implemented
583/// // No manual implementation needed
584/// # }
585/// ```
586#[cfg(feature = "serde")]
587pub use phenotype::SerializablePhenotype;
588pub use rng::ThreadLocalRng;
589pub use selection::{
590    ElitistSelection, RankBasedSelection, RouletteWheelSelection, SelectionStrategy,
591    TournamentSelection,
592};