mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 12:12:00 +03:00
prototyping an example of finding overlapping time indoors by "infected"
agents
This commit is contained in:
parent
2cce8b5446
commit
7f35d06c96
@ -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
|
||||
|
@ -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![
|
||||
|
@ -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) => {
|
||||
|
@ -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
105
sim/src/pandemic.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user