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}