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};