mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
switch sim analytics to use Time
This commit is contained in:
parent
3b6cc91440
commit
e109c6b5a4
@ -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 {
|
||||
|
@ -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)>,
|
||||
}
|
||||
|
@ -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![
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user