Skip to main content

des_sim/execution/
runner.rs

1//! The `runner` module defines the `Runner` trait, which specifies how a simulation
2//! should be executed.
3//!
4//! Different implementations of this trait provide various strategies for advancing
5//! the simulation, such as running for a fixed number of ticks, until idle,
6//! or with a delay for visualization. It also includes functionality for
7//! parallel batch execution.
8
9pub mod instance;
10
11use crate::context::ExecutorStatus;
12use crate::execution::SimulationResult;
13use crate::execution::engine::Engine;
14use crate::execution::strategy::ContinueStrategy;
15use crate::modeling::model::Model;
16use crate::primitive::time::{TickStatus, TimeTick};
17
18/// A trait defining the execution policy for simulation engines.
19///
20/// The `Runner` orchestrates the interaction between a `Model` and an `Engine`.
21/// It provides a standardized interface for different execution strategies—such
22/// as synchronous execution, playback, or parallel batch processing—allowing
23/// the user to control how the simulation proceeds over time.
24pub trait Runner<E, M: Model<E>, CS: ContinueStrategy<E, M, Self::Err>> {
25    /// Implementation-specific error type that may occur during simulation.
26    type Err: std::fmt::Debug;
27
28    /// The core, low-level method for driving the simulation.
29    ///
30    /// # Arguments
31    /// * `engine` - The engine managing the event scheduler and context.
32    /// * `model` - The initial model state.
33    /// * `should_stop` - A closure invoked before each tick to evaluate termination
34    ///   conditions. As an `FnMut`, it can track internal state like retry counts
35    ///   or cumulative errors to trigger dynamic stops.
36    fn run<F>(
37        &mut self,
38        engine: Engine<E, M>,
39        model: M,
40        should_stop: F,
41    ) -> SimulationResult<M, CS::Err>
42    where
43        F: FnMut(&M, ExecutorStatus, TickStatus) -> bool;
44
45    /// Advances the simulation while inserting a specified delay between ticks.
46    ///
47    /// Ideal for GUI/CUI visualizations or debugging where the simulation
48    /// progress needs to be observable by a human.
49    fn run_playback<F>(
50        &mut self,
51        engine: Engine<E, M>,
52        model: M,
53        duration: std::time::Duration,
54        mut should_stop: F,
55    ) -> SimulationResult<M, CS::Err>
56    where
57        F: FnMut(&M, ExecutorStatus, TickStatus) -> bool,
58    {
59        self.run(engine, model, |model, exec_status, tick_status| {
60            if should_stop(model, exec_status, tick_status) {
61                return true;
62            }
63            std::thread::sleep(duration);
64            false
65        })
66    }
67
68    /// Advances the simulation until a specific tick count is reached.
69    ///
70    /// If the `Runner` implementation optimizes by skipping time periods, this
71    /// method ensures a safe exit at the first appropriate processing step
72    /// exceeding the specified threshold.
73    fn run_do_ticks(
74        &mut self,
75        engine: Engine<E, M>,
76        model: M,
77        tick_count: TimeTick,
78        include_zero_tick: bool,
79    ) -> SimulationResult<M, CS::Err> {
80        self.run(
81            engine,
82            model,
83            |_, _, next_handle_tick_status: TickStatus| {
84                next_handle_tick_status.is_done_ticks(include_zero_tick, tick_count)
85            },
86        )
87    }
88
89    /// Automatically advances the simulation until the event queue is exhausted (idle).
90    fn run_until_idle(&mut self, engine: Engine<E, M>, model: M) -> SimulationResult<M, CS::Err> {
91        self.run(engine, model, |_, executor_status: ExecutorStatus, _| {
92            executor_status == ExecutorStatus::NoMoreEvent
93        })
94    }
95
96    /// Advances the simulation until the model's internal state satisfies a condition.
97    ///
98    /// Allows for domain-specific termination criteria, such as reaching a production
99    /// target or a specific KPI threshold.
100    fn run_until_model_condition<F>(
101        &mut self,
102        engine: Engine<E, M>,
103        model: M,
104        mut should_stop_model_condition: F,
105    ) -> SimulationResult<M, CS::Err>
106    where
107        F: FnMut(&M) -> bool,
108    {
109        self.run(engine, model, |model: &M, _, _| {
110            should_stop_model_condition(model)
111        })
112    }
113
114    /// Executes multiple simulation trials in parallel across multiple CPU cores.
115    ///
116    /// Useful for Monte Carlo simulations or parametric studies. Each thread
117    /// constructs its own independent instance using the provided builders.
118    fn run_batch_parallel<MF, EF, F>(
119        &self,
120        count: usize,
121        engine_builder: EF,
122        model_builder: MF,
123        should_stop: F,
124    ) -> Vec<SimulationResult<M, CS::Err>>
125    where
126        Self: Clone + Sync,
127        // Restricted to immutable Fn instead of FnMut so that it can be safely called many times
128        // at the same time from parallel threads
129        EF: Fn(usize) -> Engine<E, M> + Sync,
130        M: Send,
131        // Restricted to immutable Fn instead of FnMut so that it can be safely called many times
132        // at the same time from parallel threads
133        MF: Fn(usize) -> M + Sync,
134        F: FnMut(&M, ExecutorStatus, TickStatus) -> bool + Clone + Sync,
135        CS::Err: Send,
136    {
137        use rayon::prelude::*;
138
139        (0..count)
140            .into_par_iter()
141            .map(|index| {
142                let mut local_runner = self.clone();
143                let local_engine = engine_builder(index);
144                let local_model = model_builder(index);
145
146                local_runner.run(local_engine, local_model, should_stop.clone())
147            })
148            .collect()
149    }
150}