rustsim-transit 0.0.1

Public-transit primitives for rustsim: stops, routes, schedules, boarding/alighting queues, dwell times, headway control
Documentation
//! Transit routes: an ordered sequence of stops plus a schedule.

use crate::schedule::Schedule;
use crate::stop::StopId;

/// Stable identifier for a [`Route`].
pub type RouteId = u64;

/// An ordered list of stops served by one transit line.
#[derive(Debug, Clone)]
pub struct Route {
    /// Unique identifier.
    pub id: RouteId,
    /// Line name (e.g. "N17", "Central").
    pub name: String,
    /// Ordered stop IDs.
    pub stops: Vec<StopId>,
    /// Typical inter-stop travel times (s). Length == `stops.len() - 1`.
    pub inter_stop_times: Vec<f64>,
    /// Departure pattern from the first stop.
    pub schedule: Schedule,
}

impl Route {
    /// Construct a route.
    pub fn new(
        id: RouteId,
        name: impl Into<String>,
        stops: Vec<StopId>,
        inter_stop_times: Vec<f64>,
        schedule: Schedule,
    ) -> Self {
        assert!(stops.len() >= 2, "route needs at least two stops");
        assert_eq!(
            inter_stop_times.len(),
            stops.len() - 1,
            "inter_stop_times length must be stops.len() - 1"
        );
        Self {
            id,
            name: name.into(),
            stops,
            inter_stop_times,
            schedule,
        }
    }

    /// Total scheduled runtime from first to last stop, ignoring dwell.
    pub fn scheduled_runtime(&self) -> f64 {
        self.inter_stop_times.iter().sum()
    }

    /// Zero-based index of `stop_id` in this route, or `None` if the route
    /// does not serve it.
    pub fn index_of(&self, stop_id: StopId) -> Option<usize> {
        self.stops.iter().position(|s| *s == stop_id)
    }
}

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

    #[test]
    fn route_runtime_sums_inter_stop() {
        let r = Route::new(
            42,
            "N17",
            vec![1, 2, 3, 4],
            vec![60.0, 90.0, 75.0],
            Schedule::fixed_headway(600.0),
        );
        assert_eq!(r.scheduled_runtime(), 225.0);
        assert_eq!(r.index_of(3), Some(2));
    }

    #[test]
    #[should_panic]
    fn inter_stop_length_mismatch_panics() {
        Route::new(
            1,
            "bad",
            vec![1, 2, 3],
            vec![60.0], // only one, needs two
            Schedule::fixed_headway(600.0),
        );
    }
}