use std::collections::BTreeMap;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use serde::{Deserialize, Serialize};
use geom::{Duration, Time};
use map_model::{BuildingID, BusStopID};
use crate::pandemic::{AnyTime, State};
use crate::{CarID, Event, Person, PersonID, Scheduler, TripPhaseType};
#[derive(Clone)]
pub struct PandemicModel {
pop: BTreeMap<PersonID, State>,
bldgs: SharedSpace<BuildingID>,
bus_stops: SharedSpace<BusStopID>,
buses: SharedSpace<CarID>,
person_to_bus: BTreeMap<PersonID, CarID>,
rng: XorShiftRng,
initialized: bool,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug)]
pub enum Cmd {
BecomeHospitalized(PersonID),
BecomeQuarantined(PersonID),
}
impl PandemicModel {
pub fn new(rng: XorShiftRng) -> PandemicModel {
PandemicModel {
pop: BTreeMap::new(),
bldgs: SharedSpace::new(),
bus_stops: SharedSpace::new(),
buses: SharedSpace::new(),
person_to_bus: BTreeMap::new(),
rng,
initialized: false,
}
}
pub(crate) fn initialize(&mut self, population: &[Person], _scheduler: &mut Scheduler) {
assert!(!self.initialized);
self.initialized = true;
for p in population {
let state = State::new(0.5, 0.5);
let state = if self.rng.gen_bool(State::ini_exposed_ratio()) {
let next_state = state
.start(
AnyTime::from(Time::START_OF_DAY),
Duration::seconds(std::f64::MAX),
&mut self.rng,
)
.unwrap();
if self.rng.gen_bool(State::ini_infectious_ratio()) {
next_state
.next_default(AnyTime::from(Time::START_OF_DAY), &mut self.rng)
.unwrap()
} else {
next_state
}
} else {
state
};
self.pop.insert(p.id, state);
}
}
pub fn count_sane(&self) -> usize {
self.pop
.iter()
.filter(|(_, state)| matches!(state, State::Sane(_)))
.count()
}
pub fn count_exposed(&self) -> usize {
self.pop
.iter()
.filter(|(_, state)| matches!(state, State::Exposed(_)))
.count()
}
pub fn count_infected(&self) -> usize {
self.pop
.iter()
.filter(|(_, state)| matches!(state, State::Infectious(_) | State::Hospitalized(_)))
.count()
}
pub fn count_recovered(&self) -> usize {
self.pop
.iter()
.filter(|(_, state)| matches!(state, State::Recovered(_)))
.count()
}
pub fn count_dead(&self) -> usize {
self.pop
.iter()
.filter(|(_, state)| matches!(state, State::Dead(_)))
.count()
}
pub fn count_total(&self) -> usize {
self.count_sane()
+ self.count_exposed()
+ self.count_infected()
+ self.count_recovered()
+ self.count_dead()
}
pub(crate) fn handle_event(&mut self, now: Time, ev: &Event, scheduler: &mut Scheduler) {
assert!(self.initialized);
match ev {
Event::PersonEntersBuilding(person, bldg) => {
self.bldgs.person_enters_space(now, *person, *bldg);
}
Event::PersonLeavesBuilding(person, bldg) => {
if let Some(others) = self.bldgs.person_leaves_space(now, *person, *bldg) {
self.transmission(now, *person, others, scheduler);
} else {
panic!("{} left {}, but they weren't inside", person, bldg);
}
}
Event::TripPhaseStarting(_, p, _, tpt) => {
let person = *p;
match tpt {
TripPhaseType::WaitingForBus(_, stop) => {
self.bus_stops.person_enters_space(now, person, *stop);
}
TripPhaseType::RidingBus(_, stop, bus) => {
let others = self
.bus_stops
.person_leaves_space(now, person, *stop)
.unwrap();
self.transmission(now, person, others, scheduler);
self.buses.person_enters_space(now, person, *bus);
self.person_to_bus.insert(person, *bus);
}
TripPhaseType::Walking => {
if let Some(car) = self.person_to_bus.remove(&person) {
let others = self.buses.person_leaves_space(now, person, car).unwrap();
self.transmission(now, person, others, scheduler);
}
}
_ => {
self.transition(now, person, scheduler);
}
}
}
_ => {}
}
}
pub(crate) fn handle_cmd(&mut self, _now: Time, cmd: Cmd, _scheduler: &mut Scheduler) {
assert!(self.initialized);
match cmd {
Cmd::BecomeHospitalized(_person) => {
}
Cmd::BecomeQuarantined(_person) => {
}
}
}
pub fn get_time(&self, person: PersonID) -> Option<Time> {
match self.pop.get(&person) {
Some(state) => state.get_time(),
None => unreachable!(),
}
}
pub fn is_sane(&self, person: PersonID) -> bool {
match self.pop.get(&person) {
Some(state) => state.is_sane(),
None => unreachable!(),
}
}
pub fn is_infectious(&self, person: PersonID) -> bool {
match self.pop.get(&person) {
Some(state) => state.is_infectious(),
None => unreachable!(),
}
}
pub fn is_exposed(&self, person: PersonID) -> bool {
match self.pop.get(&person) {
Some(state) => state.is_exposed(),
None => unreachable!(),
}
}
pub fn is_recovered(&self, person: PersonID) -> bool {
match self.pop.get(&person) {
Some(state) => state.is_recovered(),
None => unreachable!(),
}
}
pub fn is_dead(&self, person: PersonID) -> bool {
match self.pop.get(&person) {
Some(state) => state.is_dead(),
None => unreachable!(),
}
}
fn infectious_contact(&self, person: PersonID, other: PersonID) -> Option<PersonID> {
if self.is_sane(person) && self.is_infectious(other) {
return Some(person);
} else if self.is_infectious(person) && self.is_sane(other) {
return Some(other);
}
None
}
fn transmission(
&mut self,
now: Time,
person: PersonID,
other_occupants: Vec<(PersonID, Duration)>,
scheduler: &mut Scheduler,
) {
for (other, overlap) in other_occupants {
if let Some(pid) = self.infectious_contact(person, other) {
self.become_exposed(now, overlap, pid, scheduler);
}
}
}
fn transition(&mut self, now: Time, person: PersonID, _scheduler: &mut Scheduler) {
let state = self.pop.remove(&person).unwrap();
let state = state.next(AnyTime::from(now), &mut self.rng).unwrap();
self.pop.insert(person, state);
}
fn become_exposed(
&mut self,
now: Time,
overlap: Duration,
person: PersonID,
_scheduler: &mut Scheduler,
) {
#![allow(clippy::float_cmp)]
let state = self.pop.remove(&person).unwrap();
assert_eq!(
state.get_event_time().unwrap().inner_seconds(),
std::f64::INFINITY
);
let state = state
.start(AnyTime::from(now), overlap, &mut self.rng)
.unwrap();
self.pop.insert(person, state);
}
}
#[derive(Clone)]
struct SharedSpace<T: Ord> {
occupants: BTreeMap<T, Vec<(PersonID, Time)>>,
}
impl<T: Ord> SharedSpace<T> {
fn new() -> SharedSpace<T> {
SharedSpace {
occupants: BTreeMap::new(),
}
}
fn person_enters_space(&mut self, now: Time, person: PersonID, space: T) {
self.occupants
.entry(space)
.or_insert_with(Vec::new)
.push((person, now));
}
fn person_leaves_space(
&mut self,
now: Time,
person: PersonID,
space: T,
) -> Option<Vec<(PersonID, Duration)>> {
let mut inside_since: Option<Time> = None;
let occupants = self.occupants.entry(space).or_insert_with(Vec::new);
occupants.retain(|(p, t)| {
if *p == person {
inside_since = Some(*t);
false
} else {
true
}
});
let inside_since = inside_since?;
Some(
occupants
.iter()
.map(|(p, t)| (*p, now - (*t).max(inside_since)))
.collect(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn time(x: usize) -> Time {
Time::START_OF_DAY + Duration::hours(x)
}
#[test]
fn test_overlap() {
let mut space = SharedSpace::new();
let mut now = time(0);
let bldg1 = BuildingID(1);
let bldg2 = BuildingID(2);
let person1 = PersonID(1);
let person2 = PersonID(2);
let person3 = PersonID(3);
space.person_enters_space(now, person1, bldg1);
now = time(1);
assert_eq!(
space.person_leaves_space(now, person1, bldg1),
Some(Vec::new())
);
now = time(2);
space.person_enters_space(now, person1, bldg2);
space.person_enters_space(now, person2, bldg2);
now = time(3);
assert_eq!(
space.person_leaves_space(now, person1, bldg2),
Some(vec![(person2, Duration::hours(1))])
);
assert_eq!(space.person_leaves_space(now, person3, bldg2), None);
now = time(5);
space.person_enters_space(now, person1, bldg1);
now = time(6);
space.person_enters_space(now, person2, bldg1);
now = time(7);
space.person_enters_space(now, person3, bldg1);
now = time(10);
assert_eq!(
space.person_leaves_space(now, person1, bldg1),
Some(vec![
(person2, Duration::hours(4)),
(person3, Duration::hours(3))
])
);
now = time(12);
assert_eq!(
space.person_leaves_space(now, person2, bldg1),
Some(vec![(person3, Duration::hours(5))])
);
}
}