sim/
analytics.rs

1use std::collections::{BTreeMap, BTreeSet, VecDeque};
2use std::fmt::Write;
3
4use serde::{Deserialize, Serialize};
5
6use abstutil::Counter;
7use geom::{Duration, Pt2D, Time};
8use map_model::{
9    CompressedMovementID, IntersectionID, LaneID, Map, MovementID, ParkingLotID, Path, PathRequest,
10    RoadID, TransitRouteID, TransitStopID, Traversable, TurnID,
11};
12use synthpop::TripMode;
13
14use crate::{AgentID, AgentType, AlertLocation, CarID, Event, ParkingSpot, TripID, TripPhaseType};
15
16/// As a simulation runs, different pieces emit Events. The Analytics object listens to these,
17/// organizing and storing some information from them. The UI queries Analytics to draw time-series
18/// and display statistics.
19///
20/// For all maps whose weekday scenario fully runs, the game's release includes some "prebaked
21/// results." These are just serialized Analytics after running the simulation on a map without any
22/// edits for the full day. This is the basis of A/B testing -- the player can edit the map, start
23/// running the simulation, and compare the live Analytics to the prebaked baseline Analytics.
24#[derive(Clone, Serialize, Deserialize)]
25pub struct Analytics {
26    pub road_thruput: TimeSeriesCount<RoadID>,
27    pub intersection_thruput: TimeSeriesCount<IntersectionID>,
28    // TODO For traffic signals, intersection_thruput could theoretically use this. But that
29    // requires occasionally expensive or complicated summing or merging over all directions of an
30    // intersection. So for now, eat the file size cost.
31    pub traffic_signal_thruput: TimeSeriesCount<CompressedMovementID>,
32
33    /// Most fields in Analytics are cumulative over time, but this is just for the current moment
34    /// in time.
35    pub demand: BTreeMap<MovementID, usize>,
36
37    // TODO Reconsider this one
38    pub bus_arrivals: Vec<(Time, CarID, TransitRouteID, TransitStopID)>,
39    /// For each passenger boarding, how long did they wait at the stop?
40    pub passengers_boarding: BTreeMap<TransitStopID, Vec<(Time, TransitRouteID, Duration)>>,
41    pub passengers_alighting: BTreeMap<TransitStopID, Vec<(Time, TransitRouteID)>>,
42
43    pub started_trips: BTreeMap<TripID, Time>,
44    /// Finish time, ID, mode, trip duration if successful (or None if cancelled)
45    pub finished_trips: Vec<(Time, TripID, TripMode, Option<Duration>)>,
46
47    /// Record different problems that each trip encounters.
48    pub problems_per_trip: BTreeMap<TripID, Vec<(Time, Problem)>>,
49
50    // TODO This subsumes finished_trips
51    pub trip_log: Vec<(Time, TripID, Option<PathRequest>, TripPhaseType)>,
52
53    // TODO Transit riders aren't represented here yet, just the vehicle they're riding.
54    /// Only for traffic signals. The u8 is the movement index from a CompressedMovementID.
55    pub intersection_delays: BTreeMap<IntersectionID, Vec<(u8, Time, Duration, AgentType)>>,
56
57    /// Per parking lane or lot, when does a spot become filled (true) or free (false)
58    pub parking_lane_changes: BTreeMap<LaneID, Vec<(Time, bool)>>,
59    pub parking_lot_changes: BTreeMap<ParkingLotID, Vec<(Time, bool)>>,
60
61    pub(crate) alerts: Vec<(Time, AlertLocation, String)>,
62
63    /// For benchmarking, we may want to disable collecting data.
64    record_anything: bool,
65}
66
67#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
68pub enum Problem {
69    /// A vehicle waited >30s, or a pedestrian waited >15s.
70    IntersectionDelay(IntersectionID, Duration),
71    /// A cyclist crossed an intersection with >4 connecting roads.
72    ComplexIntersectionCrossing(IntersectionID),
73    /// A pedestrian crossed an intersection with an Arterial street
74    ArterialIntersectionCrossing(TurnID),
75    /// Another vehicle wanted to over-take this cyclist somewhere on this lane or turn.
76    OvertakeDesired(Traversable),
77    /// Too many people are crossing the same sidewalk or crosswalk at the same time.
78    PedestrianOvercrowding(Traversable),
79}
80
81impl Problem {
82    /// Returns the rough location where the problem occurred -- just at the granularity of an
83    /// entire lane, turn, or intersection.
84    pub fn point(&self, map: &Map) -> Pt2D {
85        match self {
86            Problem::IntersectionDelay(i, _) | Problem::ComplexIntersectionCrossing(i) => {
87                map.get_i(*i).polygon.center()
88            }
89            Problem::OvertakeDesired(on) | Problem::PedestrianOvercrowding(on) => {
90                on.get_polyline(map).middle()
91            }
92            Problem::ArterialIntersectionCrossing(t) => map.get_t(*t).geom.middle(),
93        }
94    }
95}
96
97#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
98pub enum ProblemType {
99    IntersectionDelay,
100    ComplexIntersectionCrossing,
101    OvertakeDesired,
102    ArterialIntersectionCrossing,
103    PedestrianOvercrowding,
104}
105
106impl From<&Problem> for ProblemType {
107    fn from(problem: &Problem) -> Self {
108        match problem {
109            Problem::IntersectionDelay(_, _) => Self::IntersectionDelay,
110            Problem::ComplexIntersectionCrossing(_) => Self::ComplexIntersectionCrossing,
111            Problem::OvertakeDesired(_) => Self::OvertakeDesired,
112            Problem::ArterialIntersectionCrossing(_) => Self::ArterialIntersectionCrossing,
113            Problem::PedestrianOvercrowding(_) => Self::PedestrianOvercrowding,
114        }
115    }
116}
117
118impl ProblemType {
119    pub fn count(self, problems: &[(Time, Problem)]) -> usize {
120        let mut cnt = 0;
121        for (_, problem) in problems {
122            if self == ProblemType::from(problem) {
123                cnt += 1;
124            }
125        }
126        cnt
127    }
128
129    pub fn all() -> Vec<ProblemType> {
130        vec![
131            ProblemType::IntersectionDelay,
132            ProblemType::ComplexIntersectionCrossing,
133            ProblemType::OvertakeDesired,
134            ProblemType::ArterialIntersectionCrossing,
135            ProblemType::PedestrianOvercrowding,
136        ]
137    }
138
139    pub fn name(self) -> &'static str {
140        match self {
141            ProblemType::IntersectionDelay => "delays",
142            ProblemType::ComplexIntersectionCrossing => {
143                "where cyclists cross complex intersections"
144            }
145            ProblemType::OvertakeDesired => "where cars want to overtake cyclists",
146            ProblemType::ArterialIntersectionCrossing => {
147                "where pedestrians cross arterial intersections"
148            }
149            ProblemType::PedestrianOvercrowding => "where pedestrians are over-crowded",
150        }
151    }
152}
153
154impl Analytics {
155    pub fn new(record_anything: bool) -> Analytics {
156        Analytics {
157            road_thruput: TimeSeriesCount::new(),
158            intersection_thruput: TimeSeriesCount::new(),
159            traffic_signal_thruput: TimeSeriesCount::new(),
160            demand: BTreeMap::new(),
161            bus_arrivals: Vec::new(),
162            passengers_boarding: BTreeMap::new(),
163            passengers_alighting: BTreeMap::new(),
164            started_trips: BTreeMap::new(),
165            finished_trips: Vec::new(),
166            problems_per_trip: BTreeMap::new(),
167            trip_log: Vec::new(),
168            intersection_delays: BTreeMap::new(),
169            parking_lane_changes: BTreeMap::new(),
170            parking_lot_changes: BTreeMap::new(),
171            alerts: Vec::new(),
172            record_anything,
173        }
174    }
175
176    pub fn event(&mut self, ev: Event, time: Time, map: &Map) {
177        if !self.record_anything {
178            return;
179        }
180
181        // Throughput
182        if let Event::AgentEntersTraversable(a, _, to, passengers) = ev {
183            match to {
184                Traversable::Lane(l) => {
185                    self.road_thruput.record(time, l.road, a.to_type(), 1);
186                    if let Some(n) = passengers {
187                        self.road_thruput
188                            .record(time, l.road, AgentType::TransitRider, n);
189                    }
190                }
191                Traversable::Turn(t) => {
192                    self.intersection_thruput
193                        .record(time, t.parent, a.to_type(), 1);
194                    if let Some(n) = passengers {
195                        self.intersection_thruput.record(
196                            time,
197                            t.parent,
198                            AgentType::TransitRider,
199                            n,
200                        );
201                    }
202
203                    if let Some((id, compressed)) = map.get_movement_for_traffic_signal(t) {
204                        *self.demand.entry(id).or_insert(0) -= 1;
205                        self.traffic_signal_thruput
206                            .record(time, compressed, a.to_type(), 1);
207                        if let Some(n) = passengers {
208                            self.traffic_signal_thruput.record(
209                                time,
210                                compressed,
211                                AgentType::TransitRider,
212                                n,
213                            );
214                        }
215                    }
216                }
217            };
218        }
219        match ev {
220            Event::PersonLeavesMap(_, Some(a), i) => {
221                // Ignore cancelled trips
222                self.intersection_thruput.record(time, i, a.to_type(), 1);
223            }
224            Event::PersonEntersMap(_, a, i) => {
225                self.intersection_thruput.record(time, i, a.to_type(), 1);
226            }
227            _ => {}
228        }
229
230        // Bus arrivals
231        if let Event::BusArrivedAtStop(bus, route, stop) = ev {
232            self.bus_arrivals.push((time, bus, route, stop));
233        }
234
235        // Passengers boarding/alighting
236        if let Event::PassengerBoardsTransit(_, _, route, stop, waiting) = ev {
237            self.passengers_boarding
238                .entry(stop)
239                .or_insert_with(Vec::new)
240                .push((time, route, waiting));
241        }
242        if let Event::PassengerAlightsTransit(_, _, route, stop) = ev {
243            self.passengers_alighting
244                .entry(stop)
245                .or_insert_with(Vec::new)
246                .push((time, route));
247        }
248
249        // Started trips
250        if let Event::TripPhaseStarting(id, _, _, _) = ev {
251            self.started_trips.entry(id).or_insert(time);
252        }
253
254        // Finished trips
255        if let Event::TripFinished {
256            trip,
257            mode,
258            total_time,
259            ..
260        } = ev
261        {
262            self.finished_trips
263                .push((time, trip, mode, Some(total_time)));
264        } else if let Event::TripCancelled(id, mode) = ev {
265            self.started_trips.entry(id).or_insert(time);
266            self.finished_trips.push((time, id, mode, None));
267        }
268
269        // Intersection delay
270        if let Event::IntersectionDelayMeasured(trip_id, turn_id, agent, delay) = ev {
271            let threshold = match agent {
272                AgentID::Car(_) => Duration::seconds(30.0),
273                AgentID::Pedestrian(_) => Duration::seconds(15.0),
274                // Don't record for riders
275                AgentID::BusPassenger(_, _) => Duration::hours(24),
276            };
277            if delay > threshold {
278                self.problems_per_trip
279                    .entry(trip_id)
280                    .or_insert_with(Vec::new)
281                    .push((time, Problem::IntersectionDelay(turn_id.parent, delay)));
282            }
283
284            // Save memory and space by only storing these measurements at traffic signals, for
285            // turns that actually conflict (so no SharedSidewalkCorners).
286            if let Some((_, compressed)) = map.get_movement_for_traffic_signal(turn_id) {
287                self.intersection_delays
288                    .entry(turn_id.parent)
289                    .or_insert_with(Vec::new)
290                    .push((compressed.idx, time, delay, agent.to_type()));
291            }
292        }
293
294        // Parking spot changes
295        if let Event::CarReachedParkingSpot(_, spot) = ev {
296            if let ParkingSpot::Onstreet(l, _) = spot {
297                self.parking_lane_changes
298                    .entry(l)
299                    .or_insert_with(Vec::new)
300                    .push((time, true));
301            } else if let ParkingSpot::Lot(pl, _) = spot {
302                self.parking_lot_changes
303                    .entry(pl)
304                    .or_insert_with(Vec::new)
305                    .push((time, true));
306            }
307        }
308        if let Event::CarLeftParkingSpot(_, spot) = ev {
309            if let ParkingSpot::Onstreet(l, _) = spot {
310                self.parking_lane_changes
311                    .entry(l)
312                    .or_insert_with(Vec::new)
313                    .push((time, false));
314            } else if let ParkingSpot::Lot(pl, _) = spot {
315                self.parking_lot_changes
316                    .entry(pl)
317                    .or_insert_with(Vec::new)
318                    .push((time, false));
319            }
320        }
321
322        // Safety metrics
323        if let Event::AgentEntersTraversable(a, Some(trip), Traversable::Turn(t), _) = ev {
324            if a.to_type() == AgentType::Bike && map.get_i(t.parent).roads.len() > 4 {
325                // Defining a "large intersection" is tricky. If a road is split into two one-ways,
326                // should we count it as two roads? If we haven't consolidated some crazy
327                // intersection, we won't see it.
328                self.problems_per_trip
329                    .entry(trip)
330                    .or_insert_with(Vec::new)
331                    .push((time, Problem::ComplexIntersectionCrossing(t.parent)));
332            }
333        }
334
335        if let Event::AgentEntersTraversable(a, Some(trip), Traversable::Turn(t), _) = ev {
336            let turn = map.get_t(t);
337            if a.to_type() == AgentType::Pedestrian && turn.is_crossing_arterial_intersection(map) {
338                self.problems_per_trip
339                    .entry(trip)
340                    .or_insert_with(Vec::new)
341                    .push((time, Problem::ArterialIntersectionCrossing(turn.id)));
342            }
343        }
344
345        // TODO Kinda hacky, but these all consume the event, so kinda bundle em.
346        match ev {
347            Event::TripPhaseStarting(id, _, maybe_req, phase_type) => {
348                self.trip_log.push((time, id, maybe_req, phase_type));
349            }
350            Event::TripCancelled(id, _) => {
351                self.trip_log
352                    .push((time, id, None, TripPhaseType::Cancelled));
353            }
354            Event::TripFinished { trip, .. } => {
355                self.trip_log
356                    .push((time, trip, None, TripPhaseType::Finished));
357            }
358            Event::PathAmended(path) => {
359                self.record_demand(&path, map);
360            }
361            Event::Alert(loc, msg) => {
362                self.alerts.push((time, loc, msg));
363            }
364            Event::ProblemEncountered(trip, problem) => {
365                self.problems_per_trip
366                    .entry(trip)
367                    .or_insert_with(Vec::new)
368                    .push((time, problem));
369            }
370            _ => {}
371        }
372    }
373
374    pub fn record_demand(&mut self, path: &Path, map: &Map) {
375        for step in path.get_steps() {
376            if let Traversable::Turn(t) = step.as_traversable() {
377                if let Some((id, _)) = map.get_movement_for_traffic_signal(t) {
378                    *self.demand.entry(id).or_insert(0) += 1;
379                }
380            }
381        }
382    }
383
384    // TODO If these ever need to be speeded up, just cache the histogram and index in the events
385    // list.
386
387    /// Ignores the current time. Returns None for cancelled trips.
388    pub fn finished_trip_time(&self, trip: TripID) -> Option<Duration> {
389        // TODO This is so inefficient!
390        for (_, id, _, maybe_dt) in &self.finished_trips {
391            if *id == trip {
392                return *maybe_dt;
393            }
394        }
395        None
396    }
397
398    /// Returns pairs of trip times for finished trips in both worlds. (ID, before, after, mode)
399    pub fn both_finished_trips(
400        &self,
401        now: Time,
402        before: &Analytics,
403    ) -> Vec<(TripID, Duration, Duration, TripMode)> {
404        let mut a = BTreeMap::new();
405        for (t, id, _, maybe_dt) in &self.finished_trips {
406            if *t > now {
407                break;
408            }
409            if let Some(dt) = maybe_dt {
410                a.insert(*id, *dt);
411            }
412        }
413
414        let mut results = Vec::new();
415        for (t, id, mode, maybe_dt) in &before.finished_trips {
416            if *t > now {
417                break;
418            }
419            if let Some(dt) = maybe_dt {
420                if let Some(dt1) = a.remove(id) {
421                    results.push((*id, *dt, dt1, *mode));
422                }
423            }
424        }
425        results
426    }
427
428    /// If calling on prebaked Analytics, be careful to pass in an unedited map, to match how the
429    /// simulation was originally run. Otherwise the paths may be nonsense.
430    pub fn get_trip_phases(&self, trip: TripID, map: &Map) -> Vec<TripPhase> {
431        let mut phases: Vec<TripPhase> = Vec::new();
432        for (t, id, maybe_req, phase_type) in &self.trip_log {
433            if *id != trip {
434                continue;
435            }
436            if let Some(ref mut last) = phases.last_mut() {
437                last.end_time = Some(*t);
438            }
439            if *phase_type == TripPhaseType::Finished || *phase_type == TripPhaseType::Cancelled {
440                break;
441            }
442            phases.push(TripPhase {
443                start_time: *t,
444                end_time: None,
445                path: maybe_req.clone().and_then(|req| map.pathfind(req).ok()),
446                has_path_req: maybe_req.is_some(),
447                phase_type: *phase_type,
448            })
449        }
450        phases
451    }
452
453    pub fn get_all_trip_phases(&self) -> BTreeMap<TripID, Vec<TripPhase>> {
454        let mut trips = BTreeMap::new();
455        for (t, id, maybe_req, phase_type) in &self.trip_log {
456            let phases: &mut Vec<TripPhase> = trips.entry(*id).or_insert_with(Vec::new);
457            if let Some(ref mut last) = phases.last_mut() {
458                last.end_time = Some(*t);
459            }
460            if *phase_type == TripPhaseType::Finished {
461                continue;
462            }
463            // Remove cancelled trips
464            if *phase_type == TripPhaseType::Cancelled {
465                trips.remove(id);
466                continue;
467            }
468            phases.push(TripPhase {
469                start_time: *t,
470                end_time: None,
471                // Don't compute any paths
472                path: None,
473                has_path_req: maybe_req.is_some(),
474                phase_type: *phase_type,
475            })
476        }
477        trips
478    }
479
480    pub fn active_agents(&self, now: Time) -> Vec<(Time, usize)> {
481        let mut starts_stops: Vec<(Time, bool)> = Vec::new();
482        for t in self.started_trips.values() {
483            if *t <= now {
484                starts_stops.push((*t, false));
485            }
486        }
487        for (t, _, _, _) in &self.finished_trips {
488            if *t > now {
489                break;
490            }
491            starts_stops.push((*t, true));
492        }
493        // Make sure the start events get sorted before the stops.
494        starts_stops.sort();
495
496        let mut pts = Vec::new();
497        let mut cnt = 0;
498        let mut last_t = Time::START_OF_DAY;
499        for (t, ended) in starts_stops {
500            if t != last_t {
501                // Step functions. Don't interpolate.
502                pts.push((last_t, cnt));
503            }
504            last_t = t;
505            if ended {
506                // release mode disables this check, so...
507                if cnt == 0 {
508                    panic!("active_agents at {} has more ended trips than started", t);
509                }
510                cnt -= 1;
511            } else {
512                cnt += 1;
513            }
514        }
515        pts.push((last_t, cnt));
516        if last_t != now {
517            pts.push((now, cnt));
518        }
519        pts
520    }
521
522    /// Returns the free spots over time
523    pub fn parking_lane_availability(
524        &self,
525        now: Time,
526        l: LaneID,
527        capacity: usize,
528    ) -> Vec<(Time, usize)> {
529        if let Some(changes) = self.parking_lane_changes.get(&l) {
530            Analytics::parking_spot_availability(now, changes, capacity)
531        } else {
532            vec![(Time::START_OF_DAY, capacity), (now, capacity)]
533        }
534    }
535    pub fn parking_lot_availability(
536        &self,
537        now: Time,
538        pl: ParkingLotID,
539        capacity: usize,
540    ) -> Vec<(Time, usize)> {
541        if let Some(changes) = self.parking_lot_changes.get(&pl) {
542            Analytics::parking_spot_availability(now, changes, capacity)
543        } else {
544            vec![(Time::START_OF_DAY, capacity), (now, capacity)]
545        }
546    }
547
548    fn parking_spot_availability(
549        now: Time,
550        changes: &[(Time, bool)],
551        capacity: usize,
552    ) -> Vec<(Time, usize)> {
553        let mut pts = Vec::new();
554        let mut cnt = capacity;
555        let mut last_t = Time::START_OF_DAY;
556
557        for (t, filled) in changes {
558            if *t > now {
559                break;
560            }
561            if *t != last_t {
562                // Step functions. Don't interpolate.
563                pts.push((last_t, cnt));
564            }
565            last_t = *t;
566            if *filled {
567                if cnt == 0 {
568                    panic!("parking_spot_availability at {} went below 0", t);
569                }
570                cnt -= 1;
571            } else {
572                cnt += 1;
573            }
574        }
575        pts.push((last_t, cnt));
576        if last_t != now {
577            pts.push((now, cnt));
578        }
579        pts
580    }
581
582    pub fn problems_per_intersection(
583        &self,
584        now: Time,
585        id: IntersectionID,
586    ) -> Vec<(ProblemType, Vec<(Time, usize)>)> {
587        let window_size = Duration::minutes(15);
588
589        let mut raw_per_type: BTreeMap<ProblemType, Vec<Time>> = BTreeMap::new();
590        for problem_type in ProblemType::all() {
591            raw_per_type.insert(problem_type, Vec::new());
592        }
593
594        for (_, problems) in &self.problems_per_trip {
595            for (time, problem) in problems {
596                if *time > now {
597                    break;
598                }
599                let i = match problem {
600                    Problem::IntersectionDelay(i, _) | Problem::ComplexIntersectionCrossing(i) => {
601                        *i
602                    }
603                    Problem::OvertakeDesired(on) | Problem::PedestrianOvercrowding(on) => {
604                        match on {
605                            Traversable::Turn(t) => t.parent,
606                            _ => {
607                                continue;
608                            }
609                        }
610                    }
611                    Problem::ArterialIntersectionCrossing(t) => t.parent,
612                };
613                if id == i {
614                    raw_per_type
615                        .get_mut(&ProblemType::from(problem))
616                        .unwrap()
617                        .push(*time);
618                }
619            }
620        }
621
622        let mut result = Vec::new();
623        for (problem_type, mut raw) in raw_per_type {
624            raw.sort();
625            let mut pts = vec![(Time::START_OF_DAY, 0)];
626            let mut window = SlidingWindow::new(window_size);
627            for t in raw {
628                let count = window.add(t);
629                pts.push((t, count));
630            }
631            window.close_off_pts(&mut pts, now);
632            result.push((problem_type, pts));
633        }
634        result
635    }
636
637    pub fn problems_per_lane(
638        &self,
639        now: Time,
640        id: LaneID,
641    ) -> Vec<(ProblemType, Vec<(Time, usize)>)> {
642        let window_size = Duration::minutes(15);
643
644        let mut raw_per_type: BTreeMap<ProblemType, Vec<Time>> = BTreeMap::new();
645        for problem_type in ProblemType::all() {
646            raw_per_type.insert(problem_type, Vec::new());
647        }
648
649        for (_, problems) in &self.problems_per_trip {
650            for (time, problem) in problems {
651                if *time > now {
652                    break;
653                }
654                let l = match problem {
655                    Problem::OvertakeDesired(on) | Problem::PedestrianOvercrowding(on) => {
656                        match on {
657                            Traversable::Lane(l) => *l,
658                            _ => {
659                                continue;
660                            }
661                        }
662                    }
663                    _ => {
664                        continue;
665                    }
666                };
667                if id == l {
668                    raw_per_type
669                        .get_mut(&ProblemType::from(problem))
670                        .unwrap()
671                        .push(*time);
672                }
673            }
674        }
675
676        let mut result = Vec::new();
677        for (problem_type, mut raw) in raw_per_type {
678            raw.sort();
679            let mut pts = vec![(Time::START_OF_DAY, 0)];
680            let mut window = SlidingWindow::new(window_size);
681            for t in raw {
682                let count = window.add(t);
683                pts.push((t, count));
684            }
685            window.close_off_pts(&mut pts, now);
686            result.push((problem_type, pts));
687        }
688        result
689    }
690}
691
692impl Default for Analytics {
693    fn default() -> Analytics {
694        Analytics::new(false)
695    }
696}
697
698#[derive(Debug)]
699pub struct TripPhase {
700    pub start_time: Time,
701    pub end_time: Option<Time>,
702    pub path: Option<Path>,
703    pub has_path_req: bool,
704    pub phase_type: TripPhaseType,
705}
706
707/// See https://github.com/a-b-street/abstreet/issues/85
708#[derive(Clone, Serialize, Deserialize)]
709pub struct TimeSeriesCount<X: Ord + Clone> {
710    /// (Road or intersection, type, hour block) -> count for that hour
711    pub counts: BTreeMap<(X, AgentType, usize), usize>,
712
713    /// Very expensive to store, so it's optional. But useful to flag on to experiment with
714    /// representations better than the hour count above.
715    pub raw: Vec<(Time, AgentType, X)>,
716}
717
718impl<X: Ord + Clone> TimeSeriesCount<X> {
719    fn new() -> TimeSeriesCount<X> {
720        TimeSeriesCount {
721            counts: BTreeMap::new(),
722            raw: Vec::new(),
723        }
724    }
725
726    fn record(&mut self, time: Time, id: X, agent_type: AgentType, count: usize) {
727        // TODO Manually change flag
728        if false {
729            // TODO Woo, handling transit passengers is even more expensive in this already
730            // expensive representation...
731            for _ in 0..count {
732                self.raw.push((time, agent_type, id.clone()));
733            }
734        }
735
736        *self
737            .counts
738            .entry((id, agent_type, time.get_hours()))
739            .or_insert(0) += count;
740    }
741
742    pub fn total_for(&self, id: X) -> usize {
743        self.total_for_with_agent_types(id, AgentType::all().into_iter().collect())
744    }
745
746    pub fn total_for_with_agent_types(&self, id: X, agent_types: BTreeSet<AgentType>) -> usize {
747        let mut cnt = 0;
748        for agent_type in agent_types {
749            // TODO Hmm
750            for hour in 0..24 {
751                cnt += self
752                    .counts
753                    .get(&(id.clone(), agent_type, hour))
754                    .cloned()
755                    .unwrap_or(0);
756            }
757        }
758        cnt
759    }
760
761    pub fn total_for_by_time(&self, id: X, now: Time) -> usize {
762        let mut cnt = 0;
763        for agent_type in AgentType::all() {
764            for hour in 0..=now.get_hours() {
765                cnt += self
766                    .counts
767                    .get(&(id.clone(), agent_type, hour))
768                    .cloned()
769                    .unwrap_or(0);
770            }
771        }
772        cnt
773    }
774
775    pub fn all_total_counts(&self, agent_types: &BTreeSet<AgentType>) -> Counter<X> {
776        let mut cnt = Counter::new();
777        for ((id, agent_type, _), value) in &self.counts {
778            if agent_types.contains(agent_type) {
779                cnt.add(id.clone(), *value);
780            }
781        }
782        cnt
783    }
784
785    pub fn count_per_hour(&self, id: X, time: Time) -> Vec<(AgentType, Vec<(Time, usize)>)> {
786        let hour = time.get_hours();
787        let mut results = Vec::new();
788        for agent_type in AgentType::all() {
789            let mut pts = Vec::new();
790            for hour in 0..=hour {
791                let cnt = self
792                    .counts
793                    .get(&(id.clone(), agent_type, hour))
794                    .cloned()
795                    .unwrap_or(0);
796                pts.push((Time::START_OF_DAY + Duration::hours(hour), cnt));
797                pts.push((Time::START_OF_DAY + Duration::hours(hour + 1), cnt));
798            }
799            pts.pop();
800            results.push((agent_type, pts));
801        }
802        results
803    }
804
805    pub fn raw_throughput(&self, now: Time, id: X) -> Vec<(AgentType, Vec<(Time, usize)>)> {
806        let window_size = Duration::hours(1);
807        let mut pts_per_type: BTreeMap<AgentType, Vec<(Time, usize)>> = BTreeMap::new();
808        let mut windows_per_type: BTreeMap<AgentType, SlidingWindow> = BTreeMap::new();
809        for agent_type in AgentType::all() {
810            pts_per_type.insert(agent_type, vec![(Time::START_OF_DAY, 0)]);
811            windows_per_type.insert(agent_type, SlidingWindow::new(window_size));
812        }
813
814        for (t, agent_type, x) in &self.raw {
815            if *x != id {
816                continue;
817            }
818            if *t > now {
819                break;
820            }
821
822            let count = windows_per_type.get_mut(agent_type).unwrap().add(*t);
823            pts_per_type.get_mut(agent_type).unwrap().push((*t, count));
824        }
825
826        for (agent_type, pts) in pts_per_type.iter_mut() {
827            let mut window = windows_per_type.remove(agent_type).unwrap();
828
829            window.close_off_pts(pts, now);
830        }
831
832        pts_per_type.into_iter().collect()
833    }
834
835    /// Returns the contents of a CSV file
836    pub fn export_csv<F: Fn(&X) -> usize>(&self, extract_id: F) -> String {
837        let mut out = String::new();
838        writeln!(out, "id,agent_type,hour,count").unwrap();
839        for ((id, agent_type, hour), count) in &self.counts {
840            writeln!(
841                out,
842                "{},{:?},{},{}",
843                extract_id(id),
844                agent_type,
845                hour,
846                count
847            )
848            .unwrap();
849        }
850        out
851    }
852}
853
854/// A sliding window, used to count something over time
855pub struct SlidingWindow {
856    times: VecDeque<Time>,
857    window_size: Duration,
858}
859
860impl SlidingWindow {
861    pub fn new(window_size: Duration) -> SlidingWindow {
862        SlidingWindow {
863            times: VecDeque::new(),
864            window_size,
865        }
866    }
867
868    /// Returns the count at time
869    pub fn add(&mut self, time: Time) -> usize {
870        self.times.push_back(time);
871        self.count(time)
872    }
873
874    /// Grab the count at this time, but don't add a new time
875    pub fn count(&mut self, end: Time) -> usize {
876        while !self.times.is_empty() && end - *self.times.front().unwrap() > self.window_size {
877            self.times.pop_front();
878        }
879        self.times.len()
880    }
881
882    /// Ensure the points cover up to `end_time`. The last event may occur before then, and it's
883    /// necessary to draw more points to show the count drop off.
884    pub fn close_off_pts(&mut self, pts: &mut Vec<(Time, usize)>, end_time: Time) {
885        // Add a drop-off after window_size (+ a little epsilon!)
886        let t = (pts.last().unwrap().0 + self.window_size + Duration::seconds(0.1)).min(end_time);
887        if pts.last().unwrap().0 != t {
888            pts.push((t, self.count(t)));
889        }
890
891        if pts.last().unwrap().0 != end_time {
892            pts.push((end_time, self.count(end_time)));
893        }
894    }
895}