Skip to main content

rustsim_transit/
schedule.rs

1//! Departure schedules: fixed headway or explicit timetable.
2
3/// A single departure time (simulation seconds).
4pub type Departure = f64;
5
6/// Departure pattern for a route.
7#[derive(Debug, Clone)]
8pub enum Schedule {
9    /// Regular interval (seconds between successive departures).
10    FixedHeadway {
11        /// Time between consecutive departures.
12        headway: f64,
13        /// First departure time (default 0.0).
14        first: f64,
15    },
16    /// Explicit timetable.
17    Timetable(Vec<Departure>),
18}
19
20impl Schedule {
21    /// Fixed headway starting at `t = 0`.
22    pub fn fixed_headway(headway: f64) -> Self {
23        Self::FixedHeadway {
24            headway,
25            first: 0.0,
26        }
27    }
28
29    /// Fixed headway starting at `t = first`.
30    pub fn fixed_headway_starting(headway: f64, first: f64) -> Self {
31        Self::FixedHeadway { headway, first }
32    }
33
34    /// Timetable.
35    pub fn timetable(departures: Vec<Departure>) -> Self {
36        Self::Timetable(departures)
37    }
38
39    /// Returns the next scheduled departure time ≥ `now`.
40    pub fn next_departure(&self, now: f64) -> Option<Departure> {
41        match self {
42            Schedule::FixedHeadway { headway, first } => {
43                if *headway <= 0.0 {
44                    return None;
45                }
46                let elapsed = (now - first).max(0.0);
47                let k = (elapsed / headway).ceil();
48                Some(first + k * headway)
49            }
50            Schedule::Timetable(ts) => ts.iter().copied().find(|t| *t >= now),
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn fixed_headway_alignment() {
61        let s = Schedule::fixed_headway(300.0);
62        assert_eq!(s.next_departure(0.0), Some(0.0));
63        assert_eq!(s.next_departure(100.0), Some(300.0));
64        assert_eq!(s.next_departure(600.0), Some(600.0));
65    }
66
67    #[test]
68    fn timetable_picks_next_after_now() {
69        let s = Schedule::timetable(vec![100.0, 200.0, 350.0]);
70        assert_eq!(s.next_departure(50.0), Some(100.0));
71        assert_eq!(s.next_departure(150.0), Some(200.0));
72        assert_eq!(s.next_departure(400.0), None);
73    }
74}