radiate_core/
engine.rs

1//! # Engine Traits
2//!
3//! This module provides the core engine abstraction for genetic algorithms and evolutionary
4//! computation. The [Engine] trait defines the basic interface for evolutionary engines,
5//! while `EngineExt` provides convenient extension methods for running engines with
6//! custom termination conditions.
7//!
8//! The engine system is designed to be flexible and extensible, allowing different
9//! evolutionary algorithms to implement their own epoch types and progression logic
10//! while providing a common interface for execution control.
11
12use radiate_error::Result;
13
14/// A trait representing an evolutionary computation engine.
15//
16/// The [Engine] trait defines the fundamental interface for evolutionary algorithms.
17/// Implementors define how the algorithm progresses from one generation/epoch to the
18/// next, encapsulating the core evolutionary logic.
19///
20/// It is intentially esentially an iterator.
21///
22/// # Generic Parameters
23///
24/// - `Epoch`: The type representing a single step or generation in the evolutionary process
25///
26/// # Examples
27///
28/// ```rust
29/// use radiate_core::engine::{Engine, EngineExt};
30/// use radiate_error::RadiateError;
31///
32/// #[derive(Default)]
33/// struct MyEngine {
34///     generation: usize,
35///     population: Vec<i32>,
36/// }
37///
38/// #[derive(Debug, Clone)]
39/// struct MyEpoch {
40///     generation: usize,
41///     population_size: usize,
42/// }
43///
44/// impl Engine for MyEngine {
45///     type Epoch = MyEpoch;
46///     
47///     fn next(&mut self) -> Result<Self::Epoch, RadiateError> {
48///         // Perform one generation of evolution
49///         // ... evolve population ...
50///         self.generation += 1;
51///         
52///         Ok(MyEpoch {
53///             generation: self.generation,
54///             population_size: self.population.len()
55///         })
56///     }
57/// }
58///
59/// // Use the engine with a termination condition
60/// let mut engine = MyEngine::default();
61/// let final_epoch = engine.run(|epoch| epoch.generation >= 10);
62/// println!("Final generation: {}", final_epoch.generation);
63/// ```
64///
65/// # Design Philosophy
66///
67/// The [Engine] trait is intentionally minimal, focusing on the core concept of
68/// progression through evolutionary time. This allows for maximum flexibility in
69/// implementing different evolutionary algorithms while maintaining a small, consistent
70/// interface for execution control.
71pub trait Engine {
72    /// The type representing a single epoch or generation in the evolutionary process.
73    ///
74    /// The epoch type should contain all relevant information about the current
75    /// state of the evolutionary algorithm, such as:
76    /// - Generation number
77    /// - Population statistics
78    /// - Best fitness values
79    /// - Convergence metrics
80    /// - Any other state information needed for monitoring or decision making
81    type Epoch;
82
83    /// Advances the engine to the next epoch or generation.
84    ///
85    /// This method encapsulates one complete iteration of the evolutionary algorithm.
86    /// It should perform all necessary operations to progress the population from
87    /// the current state to the next generation, including:
88    /// - Fitness evaluation
89    /// - Selection
90    /// - Reproduction (crossover and mutation)
91    /// - Population replacement
92    /// - Any other evolutionary operators
93    ///
94    /// # Returns
95    ///
96    /// An instance of `Self::Epoch` representing the new state after the evolution step
97    ///
98    /// # Side Effects
99    ///
100    /// This method is mutable for allowance of modification of the internal state of the engine,
101    /// advancing the evolutionary process. The engine should maintain its state between calls
102    /// to allow for continuous evolution over multiple generations.
103    ///
104    /// # Performance
105    ///
106    /// This method is called repeatedly during execution, so it should be
107    /// optimized for performance.
108    fn next(&mut self) -> Result<Self::Epoch>;
109}
110
111/// Extension trait providing convenient methods for running engines with custom logic.
112///
113/// `EngineExt` provides additional functionality for engines without requiring
114/// changes to the core [Engine] trait. This follows the Rust pattern of using
115/// extension traits to add functionality to existing types.
116///
117/// # Generic Parameters
118///
119/// - `E`: The engine type that this extension applies to
120///
121/// # Design Benefits
122///
123/// - **Separation of Concerns**: Core engine logic is separate from execution control
124/// - **Flexibility**: Different termination conditions can be easily implemented
125/// - **Reusability**: The same engine can be run with different stopping criteria
126/// - **Testability**: Termination logic can be tested independently of engine logic
127pub trait EngineExt<E: Engine> {
128    /// Runs the engine until the specified termination condition is met.
129    ///
130    /// This method continuously calls `engine.next()` until the provided closure
131    /// returns `true`, indicating that the termination condition has been satisfied.
132    /// The final epoch is returned, allowing you to inspect the final state of
133    /// the evolutionary process.
134    ///
135    /// # Arguments
136    ///
137    /// * `limit` - A closure that takes the current epoch and returns `true` when
138    ///             the engine should stop, `false` to continue
139    ///
140    /// # Returns
141    ///
142    /// The epoch that satisfied the termination condition
143    ///
144    /// # Termination Conditions
145    ///
146    /// Common termination conditions include:
147    /// - **Generation Limit**: Stop after a fixed number of generations
148    /// - **Fitness Threshold**: Stop when best fitness reaches a target value
149    /// - **Convergence**: Stop when population diversity or fitness improvement is minimal
150    /// - **Time Limit**: Stop after a certain amount of computation time
151    /// - **Solution Quality**: Stop when a satisfactory solution is found
152    ///
153    /// # Performance Considerations
154    ///
155    /// - The termination condition is checked after every epoch, so keep it lightweight
156    /// - Avoid expensive computations in the termination closure
157    /// - Consider using early termination for conditions that can be checked incrementally
158    ///
159    /// # Infinite Loops
160    ///
161    /// Be careful to ensure that your termination condition will eventually be met,
162    /// especially when using complex logic. An infinite loop will cause the program
163    /// to hang indefinitely.
164    fn run<F>(&mut self, limit: F) -> E::Epoch
165    where
166        F: Fn(&E::Epoch) -> bool;
167}
168
169/// Blanket implementation of [EngineExt] for all types that implement [Engine].
170///
171/// This implementation provides the `run` method to any type that implements
172/// the [Engine] trait, without requiring manual implementation.
173///
174/// # Implementation Details
175///
176/// The `run` method implements a simple loop that:
177/// 1. Calls `self.next()` to advance the engine
178/// 2. Checks the termination condition using the provided closure
179/// 3. Breaks and returns the final epoch when the condition is met
180impl<E> EngineExt<E> for E
181where
182    E: Engine,
183{
184    fn run<F>(&mut self, limit: F) -> E::Epoch
185    where
186        F: Fn(&E::Epoch) -> bool,
187    {
188        loop {
189            match self.next() {
190                Ok(epoch) => {
191                    if limit(&epoch) {
192                        return epoch;
193                    }
194                }
195                Err(e) => {
196                    panic!("{e}");
197                }
198            }
199        }
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    struct MockEpoch {
208        generation: usize,
209        fitness: f32,
210    }
211
212    #[derive(Default)]
213    struct MockEngine {
214        generation: usize,
215    }
216
217    impl Engine for MockEngine {
218        type Epoch = MockEpoch;
219
220        fn next(&mut self) -> Result<Self::Epoch> {
221            self.generation += 1;
222            Ok(MockEpoch {
223                generation: self.generation,
224                fitness: 1.0 / (self.generation as f32),
225            })
226        }
227    }
228
229    #[test]
230    fn test_engine_next() {
231        let mut engine = MockEngine::default();
232
233        let epoch1 = engine.next().unwrap();
234        assert_eq!(epoch1.generation, 1);
235        assert_eq!(epoch1.fitness, 1.0);
236
237        let epoch2 = engine.next().unwrap();
238        assert_eq!(epoch2.generation, 2);
239        assert_eq!(epoch2.fitness, 0.5);
240    }
241
242    #[test]
243    fn test_engine_ext_run_generation_limit() {
244        let mut engine = MockEngine::default();
245
246        let final_epoch = engine.run(|epoch| epoch.generation >= 3);
247
248        assert_eq!(final_epoch.generation, 3);
249        assert_eq!(final_epoch.fitness, 1.0 / 3.0);
250    }
251
252    #[test]
253    fn test_engine_ext_run_fitness_limit() {
254        let mut engine = MockEngine::default();
255
256        let final_epoch = engine.run(|epoch| epoch.fitness < 0.3);
257
258        // Should stop when fitness drops below 0.3
259        // 1/4 = 0.25, so it should stop at generation 4
260        assert_eq!(final_epoch.generation, 4);
261        assert_eq!(final_epoch.fitness, 0.25);
262    }
263
264    #[test]
265    fn test_engine_ext_run_complex_condition() {
266        let mut engine = MockEngine::default();
267
268        let final_epoch = engine.run(|epoch| epoch.generation >= 5 || epoch.fitness < 0.2);
269
270        // Should stop at generation 5 due to generation limit
271        // (fitness at gen 5 is 0.2, which doesn't meet the fitness condition)
272        assert_eq!(final_epoch.generation, 5);
273        assert_eq!(final_epoch.fitness, 0.2);
274    }
275
276    #[test]
277    fn test_engine_ext_run_immediate_termination() {
278        let mut engine = MockEngine::default();
279
280        let final_epoch = engine.run(|_| true);
281
282        // Should stop immediately after first epoch
283        assert_eq!(final_epoch.generation, 1);
284        assert_eq!(final_epoch.fitness, 1.0);
285    }
286
287    #[test]
288    fn test_engine_ext_run_zero_generations() {
289        let mut engine = MockEngine::default();
290
291        let final_epoch = engine.run(|epoch| epoch.generation > 0);
292
293        // Should run at least one generation
294        assert_eq!(final_epoch.generation, 1);
295    }
296
297    #[test]
298    fn test_engine_ext_method_chaining() {
299        let mut engine = MockEngine::default();
300
301        // Test that we can call run multiple times on the same engine
302        let epoch1 = engine.run(|epoch| epoch.generation >= 2);
303        assert_eq!(epoch1.generation, 2);
304
305        let epoch2 = engine.run(|epoch| epoch.generation >= 4);
306        assert_eq!(epoch2.generation, 4);
307    }
308}