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