prototyping an example of finding overlapping time indoors by "infected"

agents
This commit is contained in:
Dustin Carlino 2020-03-30 22:12:48 -07:00
parent 2cce8b5446
commit 7f35d06c96
5 changed files with 153 additions and 9 deletions

View File

@ -146,6 +146,7 @@ fd9dfc89f3554bd490b633916298c385 data/system/assets/pregame/logo.svg
287a0629e1b11a2287e2fa1d902a6f3f data/system/assets/pregame/tutorial.svg
02dd549d1626c6ec941422f460696570 data/system/assets/pregame/sandbox.svg
254d439435ed1b7d01d7a2544fc06b09 data/system/assets/pregame/back.svg
d4615c4cb06658959bacb474293211f8 data/system/assets/pregame/start.svg
21145e91a6cb1c36261563e8b5b67dc1 data/system/assets/pregame/quit.svg
3a5a5796d5ff1d94dc57515f9d808c98 data/system/assets/pregame/challenges.svg
0fd2bedf0f16076c97738c80e64c7206 data/system/assets/speed/triangle.svg
@ -223,9 +224,9 @@ af8cefc0c99972082ab3ce3363412d47 data/system/scenarios/downtown/weekday.bin
e780764fe58e760ca76f017c1fd87244 data/system/scenarios/huge_seattle/weekday.bin
cd85737ea5e775f7c67b1d82c08b64fe data/system/scenarios/caphill/weekday.bin
b6367e75ae8bb7e824beb57a845c15e0 data/system/scenarios/montlake/weekday.bin
19df25a1ca21b8b9b0379723d858543d data/system/prebaked_results/23rd/weekday.bin
39dc5028982cc7ef40da44ec00ab1020 data/system/prebaked_results/signal_single/tutorial lvl1.bin
c4f5661e5c62c0c0ccccc7c2b9f2d365 data/system/prebaked_results/signal_single/tutorial lvl2.bin
b8137879ec00add4cd2a39e8c985420f data/system/prebaked_results/montlake/car vs bike contention.bin
7b21dd09012381f79c2de6b79650c7cd data/system/prebaked_results/montlake/weekday.bin
f275f3d5a926afc054f2b1a2b8d139b3 data/system/prebaked_results/montlake/car vs bus contention.bin
7051db6adccc82f47a1eca38bca758ee data/system/prebaked_results/23rd/weekday.bin
1adfeaed9d4095b3048999ec745de780 data/system/prebaked_results/signal_single/tutorial lvl1.bin
1c5a9a6f69b5f8b7ad9615af9c0ecc8c data/system/prebaked_results/signal_single/tutorial lvl2.bin
1e26ae48f3b5f31f55ab34b7a1d8c162 data/system/prebaked_results/montlake/car vs bike contention.bin
bce8e96ccc3ac8fdae1039ce2f3aa89a data/system/prebaked_results/montlake/weekday.bin
f15bffa5c937d56515dc2a59770f7510 data/system/prebaked_results/montlake/car vs bus contention.bin

View File

@ -14,7 +14,7 @@ use ezgui::{
};
use geom::{Circle, Distance, Duration, PolyLine, Pt2D, Time};
use map_model::{BusRouteID, IntersectionID};
use sim::{GetDrawAgents, ParkingSpot, PersonState};
use sim::{GetDrawAgents, PandemicModel, ParkingSpot, PersonState};
use std::collections::HashSet;
pub enum Overlays {
@ -886,6 +886,30 @@ fn population_controls(ctx: &mut EventCtx, app: &App, opts: Option<&HeatmapOptio
.centered(),
Widget::checkbox(ctx, "Show heatmap", None, opts.is_some()),
];
// TODO tmp place to put pandemic model
if app.opts.dev {
// TODO Why not app.primary.current_flags.sim_flags.make_rng()? Because that'll only be the
// same every time this code runs (frequently, as the simulation is run) if --rng_seed is
// specified in the flags. If you forget it, quite confusing to see the model jump around.
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
let model = PandemicModel::calculate(
app.primary.sim.get_analytics(),
app.primary.sim.time(),
&mut XorShiftRng::from_seed([42; 16]),
);
col.push(
format!(
"Pandemic model: {} infected ({:.1}%)",
prettyprint_usize(model.infected.len()),
(model.infected.len() as f64) / (total_ppl as f64) * 100.0
)
.draw_text(ctx),
);
}
if let Some(ref o) = opts {
// TODO Display the value...
col.push(Widget::row(vec![

View File

@ -1,9 +1,10 @@
use crate::{CarID, Event, TripID, TripMode, TripPhaseType};
use crate::{CarID, Event, PersonID, TripID, TripMode, TripPhaseType};
use abstutil::Counter;
use derivative::Derivative;
use geom::{Distance, Duration, DurationHistogram, PercentageHistogram, Time};
use map_model::{
BusRouteID, BusStopID, IntersectionID, Map, Path, PathRequest, RoadID, Traversable, TurnGroupID,
BuildingID, BusRouteID, BusStopID, IntersectionID, Map, Path, PathRequest, RoadID, Traversable,
TurnGroupID,
};
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, VecDeque};
@ -23,6 +24,8 @@ pub struct Analytics {
// TODO This subsumes finished_trips
pub trip_log: Vec<(Time, TripID, Option<PathRequest>, TripPhaseType)>,
pub intersection_delays: BTreeMap<IntersectionID, Vec<(Time, Duration)>>,
// True if a person left a building at some time, false if they entered
pub building_transitions: Vec<(Time, PersonID, BuildingID, bool)>,
// After we restore from a savestate, don't record anything. This is only going to make sense
// if savestates are only used for quickly previewing against prebaked results, where we have
@ -61,6 +64,7 @@ impl Analytics {
finished_trips: Vec::new(),
trip_log: Vec::new(),
intersection_delays: BTreeMap::new(),
building_transitions: Vec::new(),
record_anything: true,
}
}
@ -146,6 +150,14 @@ impl Analytics {
.push((time, delay));
}
// Building transitions
if let Event::PedEntersBuilding(_, p, b) = ev {
self.building_transitions.push((time, p, b, false));
}
if let Event::PedLeavesBuilding(_, p, b) = ev {
self.building_transitions.push((time, p, b, true));
}
// TODO Kinda hacky, but these all consume the event, so kinda bundle em.
match ev {
Event::TripPhaseStarting(id, _, maybe_req, phase_type) => {

View File

@ -2,6 +2,7 @@ mod analytics;
mod events;
mod make;
mod mechanics;
mod pandemic;
mod render;
mod router;
mod scheduler;
@ -35,6 +36,7 @@ use map_model::{
BuildingID, BusStopID, DirectedRoadID, IntersectionID, LaneID, Map, Path, PathConstraints,
PathRequest, Position,
};
pub use pandemic::PandemicModel;
use serde_derive::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;

105
sim/src/pandemic.rs Normal file
View File

@ -0,0 +1,105 @@
use crate::{Analytics, PersonID};
use geom::{Duration, Time};
use map_model::BuildingID;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use std::collections::{BTreeMap, BTreeSet};
pub struct PandemicModel {
pub infected: BTreeSet<PersonID>,
// Since when has a person been inside a building?
// TODO This is an awkward data structure; abstutil::MultiMap is also bad, because key removal
// would require knowing the time. Want something closer to
// https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Table.html.
bldg_occupants: BTreeMap<BuildingID, Vec<(PersonID, Time)>>,
}
impl PandemicModel {
// I think this general pattern makes the most sense. Unless we want to treat the pandemic
// model as a first-class part of the main traffic simulation, we don't really need to put the
// state in the rest of the sim crate. When the UI wants to do some reporting, we just read
// events and figure out the state of the pandemic model at some time.
//
// This recomputes everything every time the UI asks for it. That's fine for the scale of
// simulations now; everything else in Analytics works the same way. The faster streaming
// version is very straightforward -- cache this output and only process new events to update.
pub fn calculate(analytics: &Analytics, now: Time, rng: &mut XorShiftRng) -> PandemicModel {
let mut state = PandemicModel {
infected: BTreeSet::new(),
bldg_occupants: BTreeMap::new(),
};
// Kind of a messy way of figuring out where people are originally.
// TODO Before anyone leaves a building, there are 0 people as seen by this model. Very
// weird.
let mut seen_ppl = BTreeSet::new();
for (_, p, b, left) in &analytics.building_transitions {
if *left && !seen_ppl.contains(p) {
seen_ppl.insert(*p);
state
.bldg_occupants
.entry(*b)
.or_insert_with(Vec::new)
.push((*p, Time::START_OF_DAY));
// Make up some initial infection state
if rng.gen_bool(0.1) {
state.infected.insert(*p);
}
}
}
// Now track people's movements through buildings
for (time, person, bldg, left) in &analytics.building_transitions {
if *time > now {
break;
}
if *left {
// TODO Messy to mutate state inside a retain closure
let mut inside_since: Option<Time> = None;
state
.bldg_occupants
.entry(*bldg)
.or_insert_with(Vec::new)
.retain(|(p, t)| {
if *p == *person {
inside_since = Some(*t);
false
} else {
true
}
});
// TODO Same bug as above.
if inside_since.is_none() {
continue;
}
let inside_since = inside_since.unwrap();
// Was this person leaving infected while they were inside?
if !state.infected.contains(person) {
//let time_in_bldg = time - inside_since.unwrap();
let mut longest_overlap_with_infected = Duration::ZERO;
for (p, t) in &state.bldg_occupants[bldg] {
if !state.infected.contains(p) {
continue;
}
// How much time was p inside the building with person?
let dt = *time - (*t).max(inside_since);
longest_overlap_with_infected = longest_overlap_with_infected.max(dt);
}
if longest_overlap_with_infected > Duration::hours(1) && rng.gen_bool(0.1) {
state.infected.insert(*person);
}
}
} else {
state
.bldg_occupants
.entry(*bldg)
.or_insert_with(Vec::new)
.push((*person, *time));
}
}
state
}
}