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}