use crate::boarding::{Boarding, BoardingResult, Waiter};
use crate::dwell::{self, DwellParams, DwellTime};
use crate::route::Route;
use crate::stop::{Stop, StopId};
use crate::vehicle::TransitVehicle;
pub trait StopQueuePolicy {
fn can_enqueue(&self, stop: &Stop, queue: &Boarding, waiter: &Waiter) -> bool;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CapacityStopQueuePolicy;
impl StopQueuePolicy for CapacityStopQueuePolicy {
fn can_enqueue(&self, stop: &Stop, queue: &Boarding, _waiter: &Waiter) -> bool {
queue.len() < stop.capacity as usize
}
}
pub trait BoardingPolicy {
fn max_boardings(&self) -> Option<u32> {
None
}
fn may_board(&self, waiter: &Waiter, vehicle: &TransitVehicle, served_stops: &[StopId])
-> bool;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct FifoBoardingPolicy {
pub max_boardings: Option<u32>,
}
impl FifoBoardingPolicy {
pub fn unlimited() -> Self {
Self {
max_boardings: None,
}
}
pub fn capped(max_boardings: u32) -> Self {
Self {
max_boardings: Some(max_boardings),
}
}
}
impl BoardingPolicy for FifoBoardingPolicy {
fn max_boardings(&self) -> Option<u32> {
self.max_boardings
}
fn may_board(
&self,
waiter: &Waiter,
_vehicle: &TransitVehicle,
served_stops: &[StopId],
) -> bool {
served_stops.contains(&waiter.destination)
}
}
#[derive(Debug, Clone, Copy)]
pub struct DispatchContext<'a> {
pub route: &'a Route,
pub now_s: f64,
pub active_vehicles: usize,
pub max_active_vehicles: Option<usize>,
}
impl<'a> DispatchContext<'a> {
pub fn new(route: &'a Route, now_s: f64) -> Self {
Self {
route,
now_s,
active_vehicles: 0,
max_active_vehicles: None,
}
}
pub fn with_active_vehicles(
mut self,
active_vehicles: usize,
max_active_vehicles: Option<usize>,
) -> Self {
self.active_vehicles = active_vehicles;
self.max_active_vehicles = max_active_vehicles;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DispatchDecision {
Dispatch {
departure_s: f64,
},
Wait {
next_departure_s: Option<f64>,
wait_s: Option<f64>,
},
}
pub trait DispatchPolicy {
fn decide(&self, context: DispatchContext<'_>) -> DispatchDecision;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ScheduledDispatchPolicy;
impl DispatchPolicy for ScheduledDispatchPolicy {
fn decide(&self, context: DispatchContext<'_>) -> DispatchDecision {
if context
.max_active_vehicles
.is_some_and(|cap| context.active_vehicles >= cap)
{
return DispatchDecision::Wait {
next_departure_s: context.route.schedule.next_departure(context.now_s),
wait_s: None,
};
}
match context.route.schedule.next_departure(context.now_s) {
Some(departure_s) if departure_s <= context.now_s => {
DispatchDecision::Dispatch { departure_s }
}
Some(departure_s) => DispatchDecision::Wait {
next_departure_s: Some(departure_s),
wait_s: Some((departure_s - context.now_s).max(0.0)),
},
None => DispatchDecision::Wait {
next_departure_s: None,
wait_s: None,
},
}
}
}
pub trait DwellPolicy {
fn dwell_time(&self, alighters: u32, boarders: u32) -> DwellTime;
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct LinearDwellPolicy {
pub params: DwellParams,
}
impl LinearDwellPolicy {
pub fn new(params: DwellParams) -> Self {
Self { params }
}
}
impl DwellPolicy for LinearDwellPolicy {
fn dwell_time(&self, alighters: u32, boarders: u32) -> DwellTime {
dwell::compute(alighters, boarders, &self.params)
}
}
pub fn board_with_policy<P: BoardingPolicy>(
queue: &mut Boarding,
vehicle: &mut TransitVehicle,
served_stops: &[StopId],
passenger_destinations: &mut Vec<StopId>,
policy: &P,
) -> BoardingResult {
queue.board_vehicle_with_policy(vehicle, served_stops, passenger_destinations, policy)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Boarding, Route, Schedule, TransitVehicle, Waiter};
#[test]
fn capacity_stop_queue_policy_rejects_full_stop() {
let stop = Stop::at_ground(1, "A", 0.0, 0.0, 1);
let mut queue = Boarding::new();
let first = Waiter {
passenger: 1,
destination: 2,
arrived_at: 0.0,
};
let second = Waiter {
passenger: 2,
destination: 2,
arrived_at: 1.0,
};
assert!(CapacityStopQueuePolicy.can_enqueue(&stop, &queue, &first));
queue.enqueue(first);
assert!(!CapacityStopQueuePolicy.can_enqueue(&stop, &queue, &second));
}
#[test]
fn capped_fifo_boarding_limits_one_stop_event() {
let mut queue = Boarding::new();
for passenger in 1..=3 {
queue.enqueue(Waiter {
passenger,
destination: 9,
arrived_at: passenger as f64,
});
}
let mut vehicle = TransitVehicle::idle(10, 5, 10);
let mut destinations = Vec::new();
let result = queue.board_vehicle_with_policy(
&mut vehicle,
&[9],
&mut destinations,
&FifoBoardingPolicy::capped(2),
);
assert_eq!(result.boarded, 2);
assert_eq!(vehicle.passengers, vec![1, 2]);
assert_eq!(destinations, vec![9, 9]);
assert_eq!(queue.len(), 1);
}
#[test]
fn scheduled_dispatch_waits_dispatches_and_respects_active_cap() {
let route = Route::new(
1,
"Blue",
vec![1, 2],
vec![60.0],
Schedule::fixed_headway(300.0),
);
let policy = ScheduledDispatchPolicy;
assert_eq!(
policy.decide(DispatchContext::new(&route, 100.0)),
DispatchDecision::Wait {
next_departure_s: Some(300.0),
wait_s: Some(200.0)
}
);
assert_eq!(
policy.decide(DispatchContext::new(&route, 300.0)),
DispatchDecision::Dispatch { departure_s: 300.0 }
);
assert_eq!(
policy.decide(DispatchContext::new(&route, 300.0).with_active_vehicles(2, Some(2))),
DispatchDecision::Wait {
next_departure_s: Some(300.0),
wait_s: None
}
);
}
#[test]
fn linear_dwell_policy_matches_dwell_formula() {
let dwell = LinearDwellPolicy::default().dwell_time(2, 3);
assert!((dwell.total - (8.0 + 2.0 * 1.8 + 3.0 * 2.6)).abs() < 1e-9);
}
}