rustsim-transit 0.0.1

Public-transit primitives for rustsim: stops, routes, schedules, boarding/alighting queues, dwell times, headway control
Documentation
//! Departure schedules: fixed headway or explicit timetable.

/// A single departure time (simulation seconds).
pub type Departure = f64;

/// Departure pattern for a route.
#[derive(Debug, Clone)]
pub enum Schedule {
    /// Regular interval (seconds between successive departures).
    FixedHeadway {
        /// Time between consecutive departures.
        headway: f64,
        /// First departure time (default 0.0).
        first: f64,
    },
    /// Explicit timetable.
    Timetable(Vec<Departure>),
}

impl Schedule {
    /// Fixed headway starting at `t = 0`.
    pub fn fixed_headway(headway: f64) -> Self {
        Self::FixedHeadway {
            headway,
            first: 0.0,
        }
    }

    /// Fixed headway starting at `t = first`.
    pub fn fixed_headway_starting(headway: f64, first: f64) -> Self {
        Self::FixedHeadway { headway, first }
    }

    /// Timetable.
    pub fn timetable(departures: Vec<Departure>) -> Self {
        Self::Timetable(departures)
    }

    /// Returns the next scheduled departure time ≥ `now`.
    pub fn next_departure(&self, now: f64) -> Option<Departure> {
        match self {
            Schedule::FixedHeadway { headway, first } => {
                if *headway <= 0.0 {
                    return None;
                }
                let elapsed = (now - first).max(0.0);
                let k = (elapsed / headway).ceil();
                Some(first + k * headway)
            }
            Schedule::Timetable(ts) => ts.iter().copied().find(|t| *t >= now),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fixed_headway_alignment() {
        let s = Schedule::fixed_headway(300.0);
        assert_eq!(s.next_departure(0.0), Some(0.0));
        assert_eq!(s.next_departure(100.0), Some(300.0));
        assert_eq!(s.next_departure(600.0), Some(600.0));
    }

    #[test]
    fn timetable_picks_next_after_now() {
        let s = Schedule::timetable(vec![100.0, 200.0, 350.0]);
        assert_eq!(s.next_departure(50.0), Some(100.0));
        assert_eq!(s.next_departure(150.0), Some(200.0));
        assert_eq!(s.next_departure(400.0), None);
    }
}