display a plot of activity over the day. the windowing is incorrect! but

it's some kind of start
This commit is contained in:
Dustin Carlino 2020-02-20 15:11:50 -08:00
parent 3d972389e9
commit d29839fa27
4 changed files with 151 additions and 35 deletions

View File

@ -173,6 +173,31 @@ fn trips_summary_prebaked(ctx: &EventCtx, ui: &UI) -> ManagedWidget {
ctx,
)
.bg(colors::SECTION_BG),
ManagedWidget::draw_text(
ctx,
Text::from(Line("Active agents (20 minute buckets)").roboto_bold()),
),
Plot::new_usize(
vec![
Series {
label: "Baseline".to_string(),
color: Color::BLUE.alpha(0.5),
pts: ui
.prebaked()
.active_agents(Time::END_OF_DAY, Duration::minutes(20)),
},
Series {
label: "Current simulation".to_string(),
color: Color::RED,
pts: ui
.primary
.sim
.get_analytics()
.active_agents(ui.primary.sim.time(), Duration::minutes(20)),
},
],
ctx,
),
])
}
@ -222,6 +247,22 @@ fn trips_summary_not_prebaked(ctx: &EventCtx, ui: &UI) -> ManagedWidget {
ManagedWidget::col(vec![
ManagedWidget::draw_text(ctx, txt),
finished_trips_plot(ctx, ui).bg(colors::SECTION_BG),
ManagedWidget::draw_text(
ctx,
Text::from(Line("Active agents (20 minute buckets)").roboto_bold()),
),
Plot::new_usize(
vec![Series {
label: "Active agents".to_string(),
color: Color::RED,
pts: ui
.primary
.sim
.get_analytics()
.active_agents(ui.primary.sim.time(), Duration::minutes(20)),
}],
ctx,
),
])
}

View File

@ -131,8 +131,11 @@ impl Analytics {
// Finished trips
if let Event::TripFinished(id, mode, dt) = ev {
self.finished_trips.push((time, id, Some(mode), dt));
} else if let Event::TripAborted(id) = ev {
} else if let Event::TripAborted(id, mode) = ev {
self.finished_trips.push((time, id, None, Duration::ZERO));
if !self.started_trips.contains_key(&id) {
self.started_trips.insert(id, (time, mode));
}
}
// Intersection delays
@ -148,7 +151,7 @@ impl Analytics {
Event::TripPhaseStarting(id, _, maybe_req, metadata) => {
self.trip_log.push((time, id, maybe_req, metadata));
}
Event::TripAborted(id) => {
Event::TripAborted(id, _) => {
self.trip_log
.push((time, id, None, format!("trip aborted for some reason")));
}
@ -379,10 +382,8 @@ impl Analytics {
window_size: Duration,
data: &Vec<(Time, TripMode, X)>,
) -> BTreeMap<TripMode, Vec<(Time, usize)>> {
let mut pts_per_mode: BTreeMap<TripMode, Vec<(Time, usize)>> = BTreeMap::new();
let mut windows_per_mode: BTreeMap<TripMode, Window> = BTreeMap::new();
for mode in TripMode::all() {
pts_per_mode.insert(mode, vec![(Time::START_OF_DAY, 0)]);
windows_per_mode.insert(mode, Window::new(window_size));
}
@ -394,25 +395,13 @@ impl Analytics {
break;
}
let count = windows_per_mode.get_mut(m).unwrap().add(*t);
pts_per_mode.get_mut(m).unwrap().push((*t, count));
windows_per_mode.get_mut(m).unwrap().add(*t, 1);
}
for (m, pts) in pts_per_mode.iter_mut() {
let mut window = windows_per_mode.remove(m).unwrap();
// Add a drop-off after window_size (+ a little epsilon!)
let t = (pts.last().unwrap().0 + window_size + Duration::seconds(0.1)).min(now);
if pts.last().unwrap().0 != t {
pts.push((t, window.count(t)));
}
if pts.last().unwrap().0 != now {
pts.push((now, window.count(now)));
}
}
pts_per_mode
windows_per_mode
.into_iter()
.map(|(k, win)| (k, win.finalize(now)))
.collect()
}
pub fn get_trip_phases(&self, trip: TripID, map: &Map) -> Vec<TripPhase> {
@ -554,6 +543,31 @@ impl Analytics {
}
results
}
pub fn active_agents(&self, now: Time, window_size: Duration) -> Vec<(Time, usize)> {
// TODO This is not quite right. Need to figure out how Window can handle paired start/stop
// events.
let mut starts_stops: Vec<(Time, isize)> = Vec::new();
for (_, (t, _)) in &self.started_trips {
if *t <= now {
starts_stops.push((*t, 1));
}
}
for (t, _, _, _) in &self.finished_trips {
if *t > now {
break;
}
starts_stops.push((*t, -1));
}
starts_stops.sort();
let mut window = Window::new(window_size);
for (t, weight) in starts_stops {
window.add(t, weight);
}
window.finalize(now)
}
}
impl Default for Analytics {
@ -574,8 +588,11 @@ pub struct TripPhase {
}
struct Window {
times: VecDeque<Time>,
times: VecDeque<(Time, isize)>,
window_size: Duration,
output: Vec<(Time, usize)>,
last: Option<(Time, isize)>,
}
impl Window {
@ -583,20 +600,76 @@ impl Window {
Window {
times: VecDeque::new(),
window_size,
output: vec![(Time::START_OF_DAY, 0)],
last: None,
}
}
// Returns the count at time
fn add(&mut self, time: Time) -> usize {
self.times.push_back(time);
self.count(time)
fn add(&mut self, time: Time, weight: isize) {
self.times.push_back((time, weight));
let sum = self.sum(time);
if let Some((t, prev_sum)) = self.last {
if t == time {
// Overwrite
// TODO Does it make sense to add prev_sum here?
self.last = Some((time, sum));
} else {
/*println!("output so far: {:?}", self.output);
if let Some((tt, xx)) = self.output.last() {
println!(" that last one: {}, {}", tt, xx);
}
println!("last was previously ({}, {})", t, prev_sum);
println!("we're adding ({}, {})", time, weight);
*/
self.output.push((t, count(prev_sum)));
self.last = Some((time, sum));
}
} else {
self.last = Some((time, sum));
}
}
// Grab the count at this time, but don't add a new time
fn count(&mut self, end: Time) -> usize {
while !self.times.is_empty() && end - *self.times.front().unwrap() > self.window_size {
fn finalize(mut self, now: Time) -> Vec<(Time, usize)> {
if let Some((t, prev_sum)) = self.last.take() {
self.output.push((t, count(prev_sum)));
}
// Add a drop-off after window_size (+ a little epsilon!)
let t =
(self.output.last().unwrap().0 + self.window_size + Duration::seconds(0.1)).min(now);
if self.output.last().unwrap().0 != t {
let cnt = count(self.sum(t));
self.output.push((t, cnt));
}
if self.output.last().unwrap().0 != now {
let cnt = count(self.sum(now));
self.output.push((now, cnt));
}
self.output
}
// Grab the sum at this time, but don't add a new time
fn sum(&mut self, end: Time) -> isize {
while !self.times.is_empty() && end - self.times.front().unwrap().0 > self.window_size {
self.times.pop_front();
}
self.times.len()
let mut sum = 0;
for (_, weight) in &self.times {
sum += *weight;
}
sum
}
}
// TODO There's probably a cast that does this
fn count(i: isize) -> usize {
if i < 0 {
// TODO BUG ELSEWHERE!
return 0;
//panic!("Expected >= 0 count, but have {}", i);
}
i as usize
}

View File

@ -26,7 +26,7 @@ pub enum Event {
IntersectionDelayMeasured(IntersectionID, Duration),
TripFinished(TripID, TripMode, Duration),
TripAborted(TripID),
TripAborted(TripID, TripMode),
TripPhaseStarting(TripID, TripMode, Option<PathRequest>, String),
// Just use for parking replanning. Not happy about copying the full path in here, but the way

View File

@ -207,7 +207,7 @@ impl TripManager {
);
self.unfinished_trips -= 1;
trip.aborted = true;
self.events.push(Event::TripAborted(trip.id));
self.events.push(Event::TripAborted(trip.id, trip.mode));
return;
};
@ -266,7 +266,7 @@ impl TripManager {
);
self.unfinished_trips -= 1;
trip.aborted = true;
self.events.push(Event::TripAborted(trip.id));
self.events.push(Event::TripAborted(trip.id, trip.mode));
return;
};
@ -444,7 +444,8 @@ impl TripManager {
if !self.trips[id.0].is_bus_trip() {
self.unfinished_trips -= 1;
}
self.events.push(Event::TripAborted(id));
self.events
.push(Event::TripAborted(id, self.trips[id.0].mode));
}
pub fn abort_trip_impossible_parking(&mut self, car: CarID) {
@ -452,7 +453,8 @@ impl TripManager {
assert!(!self.trips[trip.0].is_bus_trip());
self.trips[trip.0].aborted = true;
self.unfinished_trips -= 1;
self.events.push(Event::TripAborted(trip));
self.events
.push(Event::TripAborted(trip, self.trips[trip.0].mode));
}
pub fn active_agents(&self) -> Vec<AgentID> {