Skip to main content

des_sim/execution/strategy/continue_strategy/
limit_discard.rs

1//! The `limit_discard` module provides the `LimitDiscardStrategy`, an implementation
2//! of the `ContinueStrategy` trait that discards remaining tasks when a micro-step
3//! limit is reached.
4//!
5//! This strategy allows the simulation to continue without error, but with a controlled
6//! loss of precision or completeness within a tick, by purging pending events and
7//! sources once the micro-step count exceeds a defined threshold.
8
9use crate::execution::phase::UncheckedActiveExecutor;
10use crate::execution::strategy::{ContinueStrategy, ContinuousStrategyResult};
11use crate::modeling::model::Model;
12
13/// A strategy that discards remaining tasks when the micro-step limit is reached.
14///
15/// This strategy prioritizes the continuation of the simulation over absolute
16/// execution accuracy. When the micro-step count exceeds the specified
17/// `limit_micro_step_count`, the strategy purges all remaining micro-steps
18/// and pending events for the current tick without triggering an error,
19/// effectively forcing the simulation to proceed to the next tick.
20#[derive(Clone)]
21pub struct LimitDiscardStrategy {
22    limit_micro_step_count: u64,
23}
24
25impl<E, M: Model<E>, RunnerError> ContinueStrategy<E, M, RunnerError> for LimitDiscardStrategy {
26    type Err = RunnerError;
27
28    /// Checks the micro-step limit and purges remaining tasks if exceeded.
29    fn handle_micro_step_continue(
30        &mut self,
31        model: &M,
32        unchecked: UncheckedActiveExecutor<E, M>,
33    ) -> ContinuousStrategyResult<E, M, Self::Err> {
34        let current_micro_step = unchecked.current_micro_step();
35
36        // Continue without modification if within the threshold.
37        if current_micro_step.value() < self.limit_micro_step_count {
38            return Ok(unchecked.into_active_executor());
39        }
40
41        // Limit reached: Purge remaining tasks in the current execution context.
42        let mut next_active = unchecked.into_active_executor();
43        // Since it is still continuing, all remaining events to be processed on the current tick are discarded.
44        next_active.discard_remain_micro_step(model);
45        Ok(next_active)
46    }
47}
48
49impl LimitDiscardStrategy {
50    /// Creates a new `LimitDiscardStrategy`.
51    ///
52    /// # Arguments
53    /// * `limit_micro_step_count` - The micro-step threshold at which discarding begins.
54    pub fn new(limit_micro_step_count: u64) -> Self {
55        LimitDiscardStrategy {
56            limit_micro_step_count,
57        }
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::context::{ActiveExecutorContext, EventContext};
65    use crate::event_scheduler::EventScheduler;
66    use crate::modeling::event::Event;
67    use crate::modeling::hook::instance::HookDelegate;
68    use crate::primitive::time::{MicroStep, MicroStepStatus, TickStatus};
69    use crate::source_handler::SourceHandler;
70    use std::convert::Infallible;
71
72    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
73    enum TestEvent {}
74
75    struct TestModel;
76
77    impl Model<TestEvent> for TestModel {
78        fn handle_event(
79            &mut self,
80            _context: &mut EventContext<TestEvent, Self>,
81            _event: &Event<TestEvent>,
82        ) {
83        }
84    }
85
86    /// Helper to create a test `UncheckedActiveExecutor`.
87    fn create_unchecked_executor(
88        micro_step_val: u64,
89    ) -> UncheckedActiveExecutor<TestEvent, TestModel> {
90        let active_context = ActiveExecutorContext {
91            current_tick_status: TickStatus::initialize(),
92            next_micro_step_status: MicroStepStatus::new(MicroStep::new(micro_step_val + 1)),
93            hook_delegate: HookDelegate::new(),
94            source_handler: SourceHandler::new(),
95            event_scheduler: EventScheduler::new(),
96        };
97        UncheckedActiveExecutor::new(active_context, MicroStep::new(micro_step_val))
98    }
99
100    #[test]
101    fn test_limit_discard_strategy_under_limit() {
102        let model = TestModel;
103        let mut strategy = LimitDiscardStrategy::new(5);
104
105        // Within limit (4 < 5): continue without discarding.
106        let unchecked = create_unchecked_executor(4);
107        let result: ContinuousStrategyResult<TestEvent, TestModel, Infallible> =
108            strategy.handle_micro_step_continue(&model, unchecked);
109
110        // Since it is below the upper limit, discard does not run and is OK.
111        assert!(
112            result.is_ok(),
113            "Strategy failed to continue when under limit."
114        );
115    }
116
117    #[test]
118    fn test_limit_discard_strategy_reach_limit() {
119        let model = TestModel;
120        let mut strategy = LimitDiscardStrategy::new(5);
121
122        // Limit reached (5): should purge events and continue without error.
123        let unchecked = create_unchecked_executor(5);
124        let result: ContinuousStrategyResult<TestEvent, TestModel, Infallible> =
125            strategy.handle_micro_step_continue(&model, unchecked);
126
127        // Even if the upper limit is reached, it does not become Err,
128        // but is internally discarded and then becomes Ok.
129        assert!(
130            result.is_ok(),
131            "Strategy failed to continue after reaching limit."
132        );
133    }
134}