use crate::config::SimConfig;
use crate::entity::EntityId;
use crate::stop::StopId;
use rand::RngExt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TrafficPattern {
Uniform,
UpPeak,
DownPeak,
Lunchtime,
Mixed,
}
fn sample_indices(
pattern: TrafficPattern,
n: usize,
rng: &mut impl RngExt,
) -> Option<(usize, usize)> {
if n < 2 {
return None;
}
let lobby = 0;
let mid = n / 2;
match pattern {
TrafficPattern::Uniform => Some(uniform_pair_indices(n, rng)),
TrafficPattern::UpPeak => {
if rng.random_range(0.0..1.0) < 0.8 {
Some((lobby, rng.random_range(1..n)))
} else {
Some(uniform_pair_indices(n, rng))
}
}
TrafficPattern::DownPeak => {
if rng.random_range(0.0..1.0) < 0.8 {
Some((rng.random_range(1..n), lobby))
} else {
Some(uniform_pair_indices(n, rng))
}
}
TrafficPattern::Lunchtime => {
let r: f64 = rng.random_range(0.0..1.0);
let upper_start = n / 2 + 1;
if r < 0.4 && upper_start < n {
Some((rng.random_range(upper_start..n), mid))
} else if r < 0.8 && upper_start < n {
Some((mid, rng.random_range(upper_start..n)))
} else {
Some(uniform_pair_indices(n, rng))
}
}
TrafficPattern::Mixed => {
let r: f64 = rng.random_range(0.0..1.0);
if r < 0.3 {
Some((lobby, rng.random_range(1..n)))
} else if r < 0.6 {
Some((rng.random_range(1..n), lobby))
} else {
Some(uniform_pair_indices(n, rng))
}
}
}
}
fn uniform_pair_indices(n: usize, rng: &mut impl RngExt) -> (usize, usize) {
let o = rng.random_range(0..n);
let mut d = rng.random_range(0..n);
while d == o {
d = rng.random_range(0..n);
}
(o, d)
}
impl TrafficPattern {
pub fn sample(
&self,
stops: &[EntityId],
rng: &mut impl RngExt,
) -> Option<(EntityId, EntityId)> {
let (o, d) = sample_indices(*self, stops.len(), rng)?;
Some((stops[o], stops[d]))
}
pub fn sample_stop_ids(
&self,
stops: &[StopId],
rng: &mut impl RngExt,
) -> Option<(StopId, StopId)> {
let (o, d) = sample_indices(*self, stops.len(), rng)?;
Some((stops[o], stops[d]))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrafficSchedule {
segments: Vec<(std::ops::Range<u64>, TrafficPattern)>,
fallback: TrafficPattern,
}
impl TrafficSchedule {
#[must_use]
pub const fn new(segments: Vec<(std::ops::Range<u64>, TrafficPattern)>) -> Self {
Self {
segments,
fallback: TrafficPattern::Uniform,
}
}
#[must_use]
pub const fn with_fallback(mut self, pattern: TrafficPattern) -> Self {
self.fallback = pattern;
self
}
#[must_use]
pub fn pattern_at(&self, tick: u64) -> &TrafficPattern {
self.segments
.iter()
.find(|(range, _)| range.contains(&tick))
.map_or(&self.fallback, |(_, pattern)| pattern)
}
pub fn sample(
&self,
tick: u64,
stops: &[EntityId],
rng: &mut impl RngExt,
) -> Option<(EntityId, EntityId)> {
self.pattern_at(tick).sample(stops, rng)
}
pub fn sample_stop_ids(
&self,
tick: u64,
stops: &[StopId],
rng: &mut impl RngExt,
) -> Option<(StopId, StopId)> {
self.pattern_at(tick).sample_stop_ids(stops, rng)
}
#[must_use]
pub fn office_day(ticks_per_hour: u64) -> Self {
Self::new(vec![
(0..ticks_per_hour, TrafficPattern::UpPeak),
(ticks_per_hour..4 * ticks_per_hour, TrafficPattern::Uniform),
(
4 * ticks_per_hour..5 * ticks_per_hour,
TrafficPattern::Lunchtime,
),
(
5 * ticks_per_hour..8 * ticks_per_hour,
TrafficPattern::Uniform,
),
(
8 * ticks_per_hour..9 * ticks_per_hour,
TrafficPattern::DownPeak,
),
])
}
#[must_use]
pub const fn constant(pattern: TrafficPattern) -> Self {
Self {
segments: Vec::new(),
fallback: pattern,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SpawnRequest {
pub origin: StopId,
pub destination: StopId,
pub weight: f64,
}
pub trait TrafficSource {
fn generate(&mut self, tick: u64) -> Vec<SpawnRequest>;
}
pub struct PoissonSource {
stops: Vec<StopId>,
schedule: TrafficSchedule,
mean_interval: u32,
weight_range: (f64, f64),
rng: rand::rngs::StdRng,
next_arrival_tick: u64,
}
impl PoissonSource {
#[must_use]
pub fn new(
stops: Vec<StopId>,
schedule: TrafficSchedule,
mean_interval_ticks: u32,
weight_range: (f64, f64),
) -> Self {
let weight_range = if weight_range.0 > weight_range.1 {
(weight_range.1, weight_range.0)
} else {
weight_range
};
let mut rng = rand::make_rng::<rand::rngs::StdRng>();
let next = sample_next_arrival(0, mean_interval_ticks, &mut rng);
Self {
stops,
schedule,
mean_interval: mean_interval_ticks,
weight_range,
rng,
next_arrival_tick: next,
}
}
#[must_use]
pub fn from_config(config: &SimConfig) -> Self {
let mut stop_entries: Vec<_> = config.building.stops.iter().collect();
stop_entries.sort_by(|a, b| {
a.position
.partial_cmp(&b.position)
.unwrap_or(std::cmp::Ordering::Equal)
});
let stops: Vec<StopId> = stop_entries.iter().map(|s| s.id).collect();
let spawn = &config.passenger_spawning;
Self::new(
stops,
TrafficSchedule::constant(TrafficPattern::Uniform),
spawn.mean_interval_ticks,
spawn.weight_range,
)
}
#[must_use]
pub fn with_schedule(mut self, schedule: TrafficSchedule) -> Self {
self.schedule = schedule;
self
}
#[must_use]
pub fn with_mean_interval(mut self, ticks: u32) -> Self {
self.mean_interval = ticks;
self.next_arrival_tick =
sample_next_arrival(self.next_arrival_tick, self.mean_interval, &mut self.rng);
self
}
#[must_use]
pub const fn next_arrival_tick(&self) -> u64 {
self.next_arrival_tick
}
#[must_use]
pub fn with_rng(mut self, rng: rand::rngs::StdRng) -> Self {
self.rng = rng;
self.next_arrival_tick =
sample_next_arrival(self.next_arrival_tick, self.mean_interval, &mut self.rng);
self
}
#[must_use]
pub const fn with_weight_range(mut self, range: (f64, f64)) -> Self {
if range.0 > range.1 {
self.weight_range = (range.1, range.0);
} else {
self.weight_range = range;
}
self
}
}
impl TrafficSource for PoissonSource {
fn generate(&mut self, tick: u64) -> Vec<SpawnRequest> {
let mut requests = Vec::new();
while tick >= self.next_arrival_tick {
let arrival_tick = self.next_arrival_tick;
if let Some((origin, destination)) =
self.schedule
.sample_stop_ids(arrival_tick, &self.stops, &mut self.rng)
{
let weight = self
.rng
.random_range(self.weight_range.0..=self.weight_range.1);
requests.push(SpawnRequest {
origin,
destination,
weight,
});
}
self.next_arrival_tick =
sample_next_arrival(self.next_arrival_tick, self.mean_interval, &mut self.rng);
}
requests
}
}
impl std::fmt::Debug for PoissonSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PoissonSource")
.field("stops", &self.stops)
.field("schedule", &self.schedule)
.field("mean_interval", &self.mean_interval)
.field("weight_range", &self.weight_range)
.field("next_arrival_tick", &self.next_arrival_tick)
.finish_non_exhaustive()
}
}
fn sample_next_arrival(current: u64, mean_interval: u32, rng: &mut impl RngExt) -> u64 {
if mean_interval == 0 {
return current + 1;
}
let u: f64 = rng.random_range(0.0001..1.0);
let interval = -(f64::from(mean_interval)) * u.ln();
current + (interval as u64).max(1)
}