switch sim analytics to use Time

This commit is contained in:
Dustin Carlino 2019-11-27 11:06:57 -08:00
parent 3b6cc91440
commit e109c6b5a4
11 changed files with 114 additions and 60 deletions

View File

@ -4,7 +4,7 @@ use crate::helpers::ID;
use crate::ui::UI;
use abstutil::prettyprint_usize;
use ezgui::{hotkey, Color, EventCtx, GfxCtx, Key, Line, ModalMenu, Text};
use geom::Duration;
use geom::Time;
use map_model::PathConstraints;
use sim::CarID;
use std::collections::BTreeMap;
@ -253,7 +253,7 @@ fn info_for(id: ID, ui: &UI, ctx: &EventCtx) -> Text {
let passengers = &sim.get_analytics().total_bus_passengers;
for r in map.get_routes_serving_stop(id) {
txt.add_appended(vec![Line("- Route "), Line(&r.name).fg(name_color)]);
let arrivals: Vec<(Duration, CarID)> = all_arrivals
let arrivals: Vec<(Time, CarID)> = all_arrivals
.iter()
.filter(|(_, _, route, stop)| r.id == *route && id == *stop)
.map(|(t, car, _, _)| (*t, *car))
@ -261,7 +261,7 @@ fn info_for(id: ID, ui: &UI, ctx: &EventCtx) -> Text {
if let Some((t, car)) = arrivals.last() {
txt.add(Line(format!(
" Last bus arrived {} ago ({})",
(sim.time() - *t).minimal_tostring(),
(sim.time().tmp_as_time() - *t).minimal_tostring(),
car
)));
} else {

View File

@ -2,7 +2,7 @@ use crate::common::ColorLegend;
use ezgui::{
Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, MultiText, ScreenPt, ScreenRectangle, Text,
};
use geom::{Bounds, Circle, Distance, Duration, FindClosest, PolyLine, Polygon, Pt2D};
use geom::{Bounds, Circle, Distance, Duration, FindClosest, PolyLine, Polygon, Pt2D, Time};
pub struct Plot<T> {
draw: Drawable,
@ -11,7 +11,7 @@ pub struct Plot<T> {
rect: ScreenRectangle,
// The geometry here is in screen-space.
max_x: Duration,
max_x: Time,
max_y: Box<dyn Yvalue<T>>,
closest: FindClosest<String>,
}
@ -35,7 +35,7 @@ impl<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
),
);
// Assume min_x is Duration::ZERO and min_y is 0
// Assume min_x is Time::START_OF_DAY and min_y is 0
let max_x = series
.iter()
.map(|s| {
@ -43,10 +43,10 @@ impl<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
.iter()
.map(|(t, _)| *t)
.max()
.unwrap_or(Duration::ZERO)
.unwrap_or(Time::START_OF_DAY)
})
.max()
.unwrap_or(Duration::ZERO);
.unwrap_or(Time::START_OF_DAY);
let max_y = series
.iter()
.map(|s| {
@ -62,7 +62,7 @@ impl<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
let num_x_labels = 5;
for i in 0..num_x_labels {
let percent_x = (i as f64) / ((num_x_labels - 1) as f64);
let t = percent_x * max_x;
let t = max_x.percent_of(percent_x);
labels.add(
Text::from(Line(t.to_string())),
ScreenPt::new(x1 + percent_x * (x2 - x1), y2),
@ -86,12 +86,12 @@ impl<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
let mut closest =
FindClosest::new(&Bounds::from(&vec![Pt2D::new(x1, y1), Pt2D::new(x2, y2)]));
for s in series {
if max_x == Duration::ZERO {
if max_x == Time::START_OF_DAY {
continue;
}
let mut pts = Vec::new();
for (t, y) in s.pts {
let percent_x = t / max_x;
let percent_x = t.to_percent(max_x);
let percent_y = y.to_percent(max_y);
pts.push(Pt2D::new(
x1 + (x2 - x1) * percent_x,
@ -131,7 +131,9 @@ impl<T: 'static + Ord + PartialEq + Copy + core::fmt::Debug + Yvalue<T>> Plot<T>
let radius = Distance::meters(5.0);
let mut txt = Text::new();
for (label, pt, _) in self.closest.all_close_pts(cursor.to_pt(), radius) {
let t = (pt.x() - self.rect.x1) / self.rect.width() * self.max_x;
let t = self
.max_x
.percent_of((pt.x() - self.rect.x1) / self.rect.width());
let y_percent = 1.0 - (pt.y() - self.rect.y1) / self.rect.height();
// TODO Draw this info in the ColorLegend
@ -178,7 +180,7 @@ impl Yvalue<usize> for usize {
}
impl Yvalue<Duration> for Duration {
fn from_percent(&self, percent: f64) -> Duration {
percent * *self
*self * percent
}
fn to_percent(self, max: Duration) -> f64 {
if max == Duration::ZERO {
@ -196,5 +198,5 @@ pub struct Series<T> {
pub label: String,
pub color: Color,
// X-axis is time. Assume this is sorted by X.
pub pts: Vec<(Duration, T)>,
pub pts: Vec<(Time, T)>,
}

View File

@ -69,9 +69,10 @@ fn gridlock_panel(ui: &UI) -> Text {
.primary
.sim
.get_analytics()
.all_finished_trips(ui.primary.sim.time());
let (baseline_all, _, baseline_per_mode) =
ui.prebaked.all_finished_trips(ui.primary.sim.time());
.all_finished_trips(ui.primary.sim.time().tmp_as_time());
let (baseline_all, _, baseline_per_mode) = ui
.prebaked
.all_finished_trips(ui.primary.sim.time().tmp_as_time());
let mut txt = Text::new();
txt.add_appended(vec![

View File

@ -54,7 +54,7 @@ impl GameplayState for FasterTrips {
}
pub fn faster_trips_panel(mode: TripMode, ui: &UI) -> Text {
let time = ui.primary.sim.time();
let time = ui.primary.sim.time().tmp_as_time();
let now = ui.primary.sim.get_analytics().finished_trips(time, mode);
let baseline = ui.prebaked.finished_trips(time, mode);

View File

@ -133,8 +133,10 @@ fn bus_route_panel(id: BusRouteID, ui: &UI, stat: Statistic) -> Text {
.primary
.sim
.get_analytics()
.bus_arrivals(ui.primary.sim.time(), id);
let baseline = ui.prebaked.bus_arrivals(ui.primary.sim.time(), id);
.bus_arrivals(ui.primary.sim.time().tmp_as_time(), id);
let baseline = ui
.prebaked
.bus_arrivals(ui.primary.sim.time().tmp_as_time(), id);
let route = ui.primary.map.get_br(id);
let mut txt = Text::new();
@ -167,7 +169,7 @@ fn bus_delays(id: BusRouteID, ui: &UI, ctx: &mut EventCtx) -> Plot<Duration> {
.primary
.sim
.get_analytics()
.bus_arrivals_over_time(ui.primary.sim.time(), id);
.bus_arrivals_over_time(ui.primary.sim.time().tmp_as_time(), id);
let mut series = Vec::new();
for idx1 in 0..route.stops.len() {

View File

@ -9,7 +9,7 @@ use crate::sandbox::SandboxMode;
use crate::ui::{ShowEverything, UI};
use abstutil::{prettyprint_usize, Counter};
use ezgui::{Choice, Color, EventCtx, GfxCtx, Key, Line, MenuUnderButton, Text};
use geom::Duration;
use geom::{Duration, Time};
use map_model::{IntersectionID, LaneID, PathConstraints, PathStep, RoadID};
use sim::{ParkingSpot, TripMode};
use std::collections::{BTreeMap, HashSet};
@ -326,7 +326,7 @@ impl Overlays {
ui.primary
.sim
.get_analytics()
.throughput_road(ui.primary.sim.time(), r, bucket)
.throughput_road(ui.primary.sim.time().tmp_as_time(), r, bucket)
.into_iter()
.map(|(m, pts)| Series {
label: m.to_string(),
@ -360,7 +360,7 @@ impl Overlays {
ui.primary
.sim
.get_analytics()
.throughput_intersection(ui.primary.sim.time(), i, bucket)
.throughput_intersection(ui.primary.sim.time().tmp_as_time(), i, bucket)
.into_iter()
.map(|(m, pts)| Series {
label: m.to_string(),
@ -459,13 +459,13 @@ impl Overlays {
let mut times = Vec::new();
for i in 0..num_x_pts {
let percent_x = (i as f64) / ((num_x_pts - 1) as f64);
let t = ui.primary.sim.time() * percent_x;
let t = ui.primary.sim.time().tmp_as_time().percent_of(percent_x);
times.push(t);
}
// Gather the data
let mut counts = Counter::new();
let mut pts_per_mode: BTreeMap<Option<TripMode>, Vec<(Duration, usize)>> =
let mut pts_per_mode: BTreeMap<Option<TripMode>, Vec<(Time, usize)>> =
lines.iter().map(|(_, _, m)| (*m, Vec::new())).collect();
for (t, _, m, _) in &ui.primary.sim.get_analytics().finished_trips {
counts.inc(*m);
@ -484,7 +484,7 @@ impl Overlays {
pts_per_mode
.get_mut(mode)
.unwrap()
.push((ui.primary.sim.time(), counts.get(*mode)));
.push((ui.primary.sim.time().tmp_as_time(), counts.get(*mode)));
}
let plot = Plot::new(

View File

@ -30,9 +30,10 @@ impl Scoreboard {
.primary
.sim
.get_analytics()
.all_finished_trips(ui.primary.sim.time());
let (baseline_all, baseline_aborted, baseline_per_mode) =
ui.prebaked.all_finished_trips(ui.primary.sim.time());
.all_finished_trips(ui.primary.sim.time().tmp_as_time());
let (baseline_all, baseline_aborted, baseline_per_mode) = ui
.prebaked
.all_finished_trips(ui.primary.sim.time().tmp_as_time());
// TODO Include unfinished count
let mut txt = Text::new();

View File

@ -1,4 +1,4 @@
use crate::{trim_f64, Distance, Speed};
use crate::{trim_f64, Distance, Speed, Time};
use histogram::Histogram;
use serde_derive::{Deserialize, Serialize};
use std::{cmp, f64, ops};
@ -203,6 +203,15 @@ impl Duration {
true
}
}
pub fn since_midnight(self) -> Time {
Time::START_OF_DAY + self
}
// TODO During transition only
pub fn tmp_as_time(self) -> Time {
Time::seconds_since_midnight(self.inner_seconds())
}
}
impl std::fmt::Display for Duration {

View File

@ -1,6 +1,6 @@
use crate::trim_f64;
use crate::{trim_f64, Duration};
use serde_derive::{Deserialize, Serialize};
use std::cmp;
use std::{cmp, ops};
// In seconds since midnight. Can't be negative.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
@ -116,6 +116,33 @@ impl Time {
))),
}
}
// TODO Why isn't this free given Ord?
pub fn min(self, other: Time) -> Time {
if self <= other {
self
} else {
other
}
}
pub fn max(self, other: Time) -> Time {
if self >= other {
self
} else {
other
}
}
// TODO These are a little weird, so don't operator overload yet
pub fn percent_of(self, p: f64) -> Time {
assert!(p >= 0.0 && p <= 1.0);
Time::seconds_since_midnight(self.0 * p)
}
pub fn to_percent(self, other: Time) -> f64 {
self.0 / other.0
}
}
impl std::fmt::Display for Time {
@ -128,3 +155,19 @@ impl std::fmt::Display for Time {
)
}
}
impl ops::Add<Duration> for Time {
type Output = Time;
fn add(self, other: Duration) -> Time {
Time::seconds_since_midnight(self.0 + other.inner_seconds())
}
}
impl ops::Sub for Time {
type Output = Duration;
fn sub(self, other: Time) -> Duration {
Duration::seconds(self.0 - other.0)
}
}

View File

@ -1,7 +1,7 @@
use crate::{AgentID, CarID, Event, TripID, TripMode, VehicleType};
use abstutil::Counter;
use derivative::Derivative;
use geom::{Duration, DurationHistogram};
use geom::{Duration, DurationHistogram, Time};
use map_model::{BusRouteID, BusStopID, IntersectionID, Map, RoadID, Traversable};
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, VecDeque};
@ -13,12 +13,12 @@ pub struct Analytics {
pub thruput_stats: ThruputStats,
#[serde(skip_serializing, skip_deserializing)]
pub(crate) test_expectations: VecDeque<Event>,
pub bus_arrivals: Vec<(Duration, CarID, BusRouteID, BusStopID)>,
pub bus_arrivals: Vec<(Time, CarID, BusRouteID, BusStopID)>,
#[serde(skip_serializing, skip_deserializing)]
pub total_bus_passengers: Counter<BusRouteID>,
// TODO Hack: No TripMode means aborted
// Finish time, ID, mode (or None as aborted), trip duration
pub finished_trips: Vec<(Duration, TripID, Option<TripMode>, Duration)>,
pub finished_trips: Vec<(Time, TripID, Option<TripMode>, Duration)>,
}
#[derive(Serialize, Deserialize, Derivative)]
@ -28,8 +28,8 @@ pub struct ThruputStats {
#[serde(skip_serializing, skip_deserializing)]
pub count_per_intersection: Counter<IntersectionID>,
raw_per_road: Vec<(Duration, TripMode, RoadID)>,
raw_per_intersection: Vec<(Duration, TripMode, IntersectionID)>,
raw_per_road: Vec<(Time, TripMode, RoadID)>,
raw_per_intersection: Vec<(Time, TripMode, IntersectionID)>,
}
impl Analytics {
@ -48,7 +48,7 @@ impl Analytics {
}
}
pub fn event(&mut self, ev: Event, time: Duration, map: &Map) {
pub fn event(&mut self, ev: Event, time: Time, map: &Map) {
// TODO Plumb a flag
let raw_thruput = true;
@ -109,7 +109,7 @@ impl Analytics {
// TODO If these ever need to be speeded up, just cache the histogram and index in the events
// list.
pub fn finished_trips(&self, now: Duration, mode: TripMode) -> DurationHistogram {
pub fn finished_trips(&self, now: Time, mode: TripMode) -> DurationHistogram {
let mut distrib = DurationHistogram::new();
for (t, _, m, dt) in &self.finished_trips {
if *t > now {
@ -125,7 +125,7 @@ impl Analytics {
// Returns (all trips except aborted, number of aborted trips, trips by mode)
pub fn all_finished_trips(
&self,
now: Duration,
now: Time,
) -> (
DurationHistogram,
usize,
@ -151,12 +151,8 @@ impl Analytics {
(all, num_aborted, per_mode)
}
pub fn bus_arrivals(
&self,
now: Duration,
r: BusRouteID,
) -> BTreeMap<BusStopID, DurationHistogram> {
let mut per_bus: BTreeMap<CarID, Vec<(Duration, BusStopID)>> = BTreeMap::new();
pub fn bus_arrivals(&self, now: Time, r: BusRouteID) -> BTreeMap<BusStopID, DurationHistogram> {
let mut per_bus: BTreeMap<CarID, Vec<(Time, BusStopID)>> = BTreeMap::new();
for (t, car, route, stop) in &self.bus_arrivals {
if *t > now {
break;
@ -184,10 +180,10 @@ impl Analytics {
// For each stop, a list of (time, delay)
pub fn bus_arrivals_over_time(
&self,
now: Duration,
now: Time,
r: BusRouteID,
) -> BTreeMap<BusStopID, Vec<(Duration, Duration)>> {
let mut per_bus: BTreeMap<CarID, Vec<(Duration, BusStopID)>> = BTreeMap::new();
) -> BTreeMap<BusStopID, Vec<(Time, Duration)>> {
let mut per_bus: BTreeMap<CarID, Vec<(Time, BusStopID)>> = BTreeMap::new();
for (t, car, route, stop) in &self.bus_arrivals {
if *t > now {
break;
@ -199,7 +195,7 @@ impl Analytics {
.push((*t, *stop));
}
}
let mut delays_to_stop: BTreeMap<BusStopID, Vec<(Duration, Duration)>> = BTreeMap::new();
let mut delays_to_stop: BTreeMap<BusStopID, Vec<(Time, Duration)>> = BTreeMap::new();
for events in per_bus.values() {
for pair in events.windows(2) {
delays_to_stop
@ -214,14 +210,14 @@ impl Analytics {
// Slightly misleading -- TripMode::Transit means buses, not pedestrians taking transit
pub fn throughput_road(
&self,
now: Duration,
now: Time,
road: RoadID,
bucket: Duration,
) -> BTreeMap<TripMode, Vec<(Duration, usize)>> {
let mut max_this_bucket = now.min(bucket);
) -> BTreeMap<TripMode, Vec<(Time, usize)>> {
let mut max_this_bucket = now.min(bucket.since_midnight());
let mut per_mode = TripMode::all()
.into_iter()
.map(|m| (m, vec![(Duration::ZERO, 0), (max_this_bucket, 0)]))
.map(|m| (m, vec![(Time::START_OF_DAY, 0), (max_this_bucket, 0)]))
.collect::<BTreeMap<_, _>>();
for (t, m, r) in &self.thruput_stats.raw_per_road {
if *r != road {
@ -244,15 +240,15 @@ impl Analytics {
// TODO Refactor!
pub fn throughput_intersection(
&self,
now: Duration,
now: Time,
intersection: IntersectionID,
bucket: Duration,
) -> BTreeMap<TripMode, Vec<(Duration, usize)>> {
) -> BTreeMap<TripMode, Vec<(Time, usize)>> {
let mut per_mode = TripMode::all()
.into_iter()
.map(|m| (m, vec![(Duration::ZERO, 0)]))
.map(|m| (m, vec![(Time::START_OF_DAY, 0)]))
.collect::<BTreeMap<_, _>>();
let mut max_this_bucket = bucket;
let mut max_this_bucket = bucket.since_midnight();
for (t, m, i) in &self.thruput_stats.raw_per_intersection {
if *i != intersection {
continue;

View File

@ -544,7 +544,7 @@ impl Sim {
events.extend(self.driving.collect_events());
events.extend(self.walking.collect_events());
for ev in events {
self.analytics.event(ev, self.time, map);
self.analytics.event(ev, self.time.tmp_as_time(), map);
}
}
if let Some(t) = savestate_at {