use radiate_error::Result;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum EngineState {
PreStart,
Running,
Paused,
Stopped,
}
pub trait Engine {
type Epoch;
type Ctx;
fn context(&self) -> &Self::Ctx;
fn epoch(&self) -> Self::Epoch;
fn step(&mut self) -> Result<()>;
fn start(&mut self) {}
fn stop(&mut self) {}
fn state(&self) -> EngineState;
}
pub trait EngineStream: Engine {
type View<'a>
where
Self: 'a;
fn run<F>(self, limit: F) -> Result<Self::Epoch>
where
F: Fn(Self::View<'_>) -> bool + 'static;
}
pub trait EngineExt<E: Engine> {
#[deprecated(
since = "1.3.1",
note = "Use the `EngineStream` trait impl instead, which provides a more flexible and \
efficient way to run engines with custom termination conditions. Instead of an `E::Epoch` being \
given to the fn, a `GenerationView<'a, C, T>` is provided instead which is much more efficient."
)]
fn run<F>(&mut self, limit: F) -> E::Epoch
where
F: Fn(&E::Epoch) -> bool;
}
impl<E> EngineExt<E> for E
where
E: Engine,
{
fn run<F>(&mut self, limit: F) -> E::Epoch
where
F: Fn(&E::Epoch) -> bool,
{
loop {
match self.step().map(|_| self.epoch()) {
Ok(epoch) => {
if limit(&epoch) {
return epoch;
}
}
Err(e) => {
panic!("{e}");
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockEpoch {
generation: usize,
fitness: f32,
}
#[derive(Default)]
struct MockEngine {
generation: usize,
}
impl Engine for MockEngine {
type Epoch = MockEpoch;
type Ctx = ();
fn context(&self) -> &Self::Ctx {
&()
}
fn epoch(&self) -> Self::Epoch {
MockEpoch {
generation: self.generation,
fitness: 1.0 / (self.generation as f32),
}
}
fn step(&mut self) -> Result<()> {
self.generation += 1;
Ok(())
}
fn state(&self) -> EngineState {
EngineState::Running
}
}
impl EngineStream for MockEngine {
type View<'a>
= &'a MockEpoch
where
Self: 'a;
fn run<F>(mut self, limit: F) -> Result<Self::Epoch>
where
F: Fn(Self::View<'_>) -> bool + 'static,
{
loop {
match self.step().map(|_| self.epoch()) {
Ok(epoch) => {
if limit(&epoch) {
return Ok(epoch);
}
}
Err(e) => {
return Err(e);
}
}
}
}
}
#[test]
fn test_engine_next() {
let mut engine = MockEngine::default();
let epoch1 = engine.step().map(|_| engine.epoch()).unwrap();
assert_eq!(epoch1.generation, 1);
assert_eq!(epoch1.fitness, 1.0);
let epoch2 = engine.step().map(|_| engine.epoch()).unwrap();
assert_eq!(epoch2.generation, 2);
assert_eq!(epoch2.fitness, 0.5);
}
#[test]
fn test_engine_ext_run_generation_limit() {
let engine = MockEngine::default();
let final_epoch = engine.run(|epoch| epoch.generation >= 3).unwrap();
assert_eq!(final_epoch.generation, 3);
assert_eq!(final_epoch.fitness, 1.0 / 3.0);
}
#[test]
fn test_engine_ext_run_fitness_limit() {
let engine = MockEngine::default();
let final_epoch = engine.run(|epoch| epoch.fitness < 0.3).unwrap();
assert_eq!(final_epoch.generation, 4);
assert_eq!(final_epoch.fitness, 0.25);
}
#[test]
fn test_engine_ext_run_complex_condition() {
let engine = MockEngine::default();
let final_epoch = engine
.run(|epoch| epoch.generation >= 5 || epoch.fitness < 0.2)
.unwrap();
assert_eq!(final_epoch.generation, 5);
assert_eq!(final_epoch.fitness, 0.2);
}
#[test]
fn test_engine_ext_run_immediate_termination() {
let engine = MockEngine::default();
let final_epoch = engine.run(|_| true).unwrap();
assert_eq!(final_epoch.generation, 1);
assert_eq!(final_epoch.fitness, 1.0);
}
#[test]
fn test_engine_ext_run_zero_generations() {
let engine = MockEngine::default();
let final_epoch = engine.run(|epoch| epoch.generation > 0).unwrap();
assert_eq!(final_epoch.generation, 1);
}
}