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.
168pub 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
278 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.
3105 fn next ( & mut self ) -> Self :: Epoch ;
4106}
5107
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
6124pub 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.
7161 fn run < F > ( & mut self , limit : F ) -> E :: Epoch
8162 where
9163 F : Fn ( & E :: Epoch ) -> bool ;
10164}
11165
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
12177impl < E > EngineExt < E > for E
13178where
14179 E : Engine ,
@@ -26,3 +191,110 @@ where
26191 }
27192 }
28193}
194+
195+ #[ cfg( test) ]
196+ mod 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+ }
0 commit comments