Skip to main content

use_event_simulation/
lib.rs

1#![forbid(unsafe_code)]
2//! Simple event simulation helpers over finite event lists.
3//!
4//! Events are modeled as time and change pairs. The runtime sorts them by time
5//! and applies each change to a scalar state.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_event_simulation::{run_event_simulation, SimulationEvent};
11//!
12//! let states = run_event_simulation(
13//!     10.0,
14//!     &[
15//!         SimulationEvent::new(2.0, -1.0).unwrap(),
16//!         SimulationEvent::new(1.0, 3.0).unwrap(),
17//!     ],
18//! )
19//! .unwrap();
20//!
21//! assert_eq!(states[0].state, 10.0);
22//! assert_eq!(states[1].time, 1.0);
23//! assert_eq!(states[2].state, 12.0);
24//! ```
25
26#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct SimulationEvent {
28    pub time: f64,
29    pub change: f64,
30}
31
32impl SimulationEvent {
33    pub fn new(time: f64, change: f64) -> Option<Self> {
34        if !time.is_finite() || time < 0.0 || !change.is_finite() {
35            return None;
36        }
37
38        Some(Self { time, change })
39    }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
43pub struct EventState {
44    pub time: f64,
45    pub state: f64,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum EventSimulationError {
50    InvalidInitialState,
51    InvalidEvent,
52    NonFiniteState,
53}
54
55pub fn sorted_events(
56    events: &[SimulationEvent],
57) -> Result<Vec<SimulationEvent>, EventSimulationError> {
58    if events
59        .iter()
60        .any(|event| !event.time.is_finite() || event.time < 0.0 || !event.change.is_finite())
61    {
62        return Err(EventSimulationError::InvalidEvent);
63    }
64
65    let mut sorted = events.to_vec();
66    sorted.sort_by(|left, right| left.time.total_cmp(&right.time));
67    Ok(sorted)
68}
69
70pub fn run_event_simulation(
71    initial_state: f64,
72    events: &[SimulationEvent],
73) -> Result<Vec<EventState>, EventSimulationError> {
74    if !initial_state.is_finite() {
75        return Err(EventSimulationError::InvalidInitialState);
76    }
77
78    let events = sorted_events(events)?;
79    let mut states = Vec::with_capacity(events.len() + 1);
80    let mut current_state = initial_state;
81    states.push(EventState {
82        time: 0.0,
83        state: current_state,
84    });
85
86    for event in events {
87        current_state += event.change;
88        if !current_state.is_finite() {
89            return Err(EventSimulationError::NonFiniteState);
90        }
91
92        states.push(EventState {
93            time: event.time,
94            state: current_state,
95        });
96    }
97
98    Ok(states)
99}
100
101#[cfg(test)]
102mod tests {
103    use super::{EventSimulationError, SimulationEvent, run_event_simulation, sorted_events};
104
105    #[test]
106    fn sorts_events_by_time() {
107        let sorted = sorted_events(&[
108            SimulationEvent::new(3.0, 1.0).unwrap(),
109            SimulationEvent::new(1.0, 2.0).unwrap(),
110        ])
111        .unwrap();
112
113        assert_eq!(sorted[0].time, 1.0);
114        assert_eq!(sorted[1].time, 3.0);
115    }
116
117    #[test]
118    fn runs_event_simulation() {
119        let states = run_event_simulation(
120            10.0,
121            &[
122                SimulationEvent::new(2.0, -1.0).unwrap(),
123                SimulationEvent::new(1.0, 3.0).unwrap(),
124            ],
125        )
126        .unwrap();
127
128        assert_eq!(states.len(), 3);
129        assert_eq!(states[0].state, 10.0);
130        assert_eq!(states[1].time, 1.0);
131        assert_eq!(states[1].state, 13.0);
132        assert_eq!(states[2].state, 12.0);
133    }
134
135    #[test]
136    fn rejects_invalid_inputs() {
137        assert_eq!(SimulationEvent::new(-1.0, 1.0), None);
138        assert_eq!(
139            run_event_simulation(f64::NAN, &[]),
140            Err(EventSimulationError::InvalidInitialState)
141        );
142        assert_eq!(
143            sorted_events(&[SimulationEvent {
144                time: 1.0,
145                change: f64::NAN,
146            }]),
147            Err(EventSimulationError::InvalidEvent)
148        );
149    }
150}