rustsim-transit 0.0.1

Public-transit primitives for rustsim: stops, routes, schedules, boarding/alighting queues, dwell times, headway control
Documentation
//! Boarding queues and FIFO service discipline at transit stops.

use std::collections::VecDeque;

use crate::policy::{BoardingPolicy, FifoBoardingPolicy};
use crate::stop::StopId;
use crate::vehicle::TransitVehicle;
use crate::PassengerId;

/// A waiting passenger with an intended alighting stop.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Waiter {
    /// Passenger identifier.
    pub passenger: PassengerId,
    /// Stop at which this passenger intends to alight.
    pub destination: StopId,
    /// Simulation time at which the passenger arrived at the stop.
    pub arrived_at: f64,
}

/// FIFO boarding queue for a single stop.
#[derive(Debug, Clone, Default)]
pub struct Boarding {
    /// Queued passengers.
    queue: VecDeque<Waiter>,
}

/// Outcome of a single boarding attempt.
#[derive(Debug, Clone, PartialEq)]
pub struct BoardingResult {
    /// Number of passengers that boarded this vehicle at this stop.
    pub boarded: u32,
    /// Number of passengers that stayed at the stop (because the vehicle
    /// was full or served an incompatible destination).
    pub passed_over: u32,
}

impl Boarding {
    /// New empty queue.
    pub fn new() -> Self {
        Self::default()
    }

    /// Enqueue a waiter.
    pub fn enqueue(&mut self, waiter: Waiter) {
        self.queue.push_back(waiter);
    }

    /// Number of waiters.
    pub fn len(&self) -> usize {
        self.queue.len()
    }

    /// True if no passengers are waiting.
    pub fn is_empty(&self) -> bool {
        self.queue.is_empty()
    }

    /// Board a vehicle from the front of the queue. Only waiters whose
    /// `destination` is in `served_stops` board; others are left at the
    /// front of the queue (they will wait for a different service).
    ///
    /// Returns a summary of how many boarded vs passed over.
    pub fn board_vehicle(
        &mut self,
        vehicle: &mut TransitVehicle,
        served_stops: &[StopId],
        passenger_destinations: &mut Vec<StopId>,
    ) -> BoardingResult {
        self.board_vehicle_with_policy(
            vehicle,
            served_stops,
            passenger_destinations,
            &FifoBoardingPolicy::default(),
        )
    }

    /// Board a vehicle using an explicit boarding policy.
    pub fn board_vehicle_with_policy<P: BoardingPolicy>(
        &mut self,
        vehicle: &mut TransitVehicle,
        served_stops: &[StopId],
        passenger_destinations: &mut Vec<StopId>,
        policy: &P,
    ) -> BoardingResult {
        let mut boarded: u32 = 0;
        let mut passed: u32 = 0;

        // Walk the queue once. Boardable waiters are popped; non-serviced
        // waiters are rotated to the back so the next vehicle can pick them
        // up. The rotation is bounded by the initial queue size.
        let initial = self.queue.len();
        for _ in 0..initial {
            if !vehicle.has_room() {
                break;
            }
            if policy
                .max_boardings()
                .is_some_and(|max_boardings| boarded >= max_boardings)
            {
                break;
            }
            let Some(w) = self.queue.pop_front() else {
                break;
            };
            if policy.may_board(&w, vehicle, served_stops) {
                vehicle.passengers.push(w.passenger);
                passenger_destinations.push(w.destination);
                boarded += 1;
            } else {
                self.queue.push_back(w);
                passed += 1;
            }
        }

        BoardingResult {
            boarded,
            passed_over: passed,
        }
    }
}

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

    #[test]
    fn boarding_matches_served_destinations() {
        let mut q = Boarding::new();
        q.enqueue(Waiter {
            passenger: 10,
            destination: 3,
            arrived_at: 0.0,
        });
        q.enqueue(Waiter {
            passenger: 11,
            destination: 99, // unserved
            arrived_at: 0.0,
        });
        q.enqueue(Waiter {
            passenger: 12,
            destination: 4,
            arrived_at: 0.0,
        });

        let mut v = TransitVehicle::idle(1, 1, 10);
        let mut dests = Vec::new();
        let r = q.board_vehicle(&mut v, &[3, 4, 5], &mut dests);

        assert_eq!(r.boarded, 2);
        assert_eq!(r.passed_over, 1);
        assert_eq!(v.passengers, vec![10, 12]);
        assert_eq!(dests, vec![3, 4]);
        assert_eq!(q.len(), 1);
    }

    #[test]
    fn full_vehicle_stops_boarding() {
        let mut q = Boarding::new();
        for i in 0..5 {
            q.enqueue(Waiter {
                passenger: i,
                destination: 3,
                arrived_at: 0.0,
            });
        }
        let mut v = TransitVehicle::idle(1, 1, 2);
        let mut dests = Vec::new();
        let r = q.board_vehicle(&mut v, &[3], &mut dests);
        assert_eq!(r.boarded, 2);
        assert_eq!(q.len(), 3);
    }
}