1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use serde::Serialize;

use abstio::MapName;
use abstutil::{prettyprint_usize, Timer};
use geom::{Duration, Time};
use map_model::Map;
use sim::{AlertHandler, Scenario, ScenarioGenerator, Sim, SimFlags, SimOptions};

use crate::sandbox::TutorialState;

/// Simulate a curated list of scenarios to completion, and save the analytics as "prebaked
/// results," to later compare simulation metrics against the baseline without map edits.
pub fn prebake_all() {
    let mut timer = Timer::new("prebake all challenge results");

    {
        let map =
            map_model::Map::load_synchronously(MapName::seattle("montlake").path(), &mut timer);
        for generator in TutorialState::scenarios_to_prebake(&map) {
            let scenario = generator.generate(
                &map,
                &mut SimFlags::for_test("prebaked").make_rng(),
                &mut timer,
            );
            // Don't record a summary for this
            prebake(&map, scenario, &mut timer);
        }
    }

    let mut summaries = Vec::new();
    for name in vec![
        MapName::seattle("arboretum"),
        MapName::seattle("montlake"),
        //MapName::seattle("lakeslice"),
        //MapName::seattle("phinney"),
        //MapName::seattle("qa"),
        //MapName::seattle("wallingford"),
    ] {
        let map = map_model::Map::load_synchronously(name.path(), &mut timer);
        let scenario: Scenario =
            abstio::read_binary(abstio::path_scenario(map.get_name(), "weekday"), &mut timer);
        summaries.push(prebake(&map, scenario, &mut timer));
    }

    let pbury_map = map_model::Map::load_synchronously(
        MapName::new("gb", "poundbury", "center").path(),
        &mut timer,
    );
    for scenario_name in ["base", "go_active", "base_with_bg", "go_active_with_bg"] {
        let scenario: Scenario = abstio::read_binary(
            abstio::path_scenario(pbury_map.get_name(), scenario_name),
            &mut timer,
        );
        summaries.push(prebake(&pbury_map, scenario, &mut timer));
    }

    {
        let tehran_map = map_model::Map::load_synchronously(
            MapName::new("ir", "tehran", "parliament").path(),
            &mut timer,
        );
        let scenario = ScenarioGenerator::proletariat_robot(
            &tehran_map,
            &mut SimFlags::for_test("prebaked").make_rng(),
            &mut timer,
        );
        summaries.push(prebake(&tehran_map, scenario, &mut timer));
    }

    // Assume this is being run from the 'game' directory. This other tests directory is the most
    // appropriate place to keep this.
    abstio::write_json(
        "../tests/goldenfiles/prebaked_summaries.json".to_string(),
        &summaries,
    );
}

fn prebake(map: &Map, scenario: Scenario, timer: &mut Timer) -> PrebakeSummary {
    timer.start(format!(
        "prebake for {} / {}",
        scenario.map_name.describe(),
        scenario.scenario_name
    ));

    let mut opts = SimOptions::new("prebaked");
    opts.alerts = AlertHandler::Silence;
    let mut sim = Sim::new(map, opts);
    // Bit of an abuse of this, but just need to fix the rng seed.
    let mut rng = SimFlags::for_test("prebaked").make_rng();
    scenario.instantiate(&mut sim, map, &mut rng, timer);

    // Run until a few hours after the end of the day. Some trips start close to midnight, and we
    // want prebaked data for them too.
    sim.timed_step(
        map,
        sim.get_end_of_day() - Time::START_OF_DAY + Duration::hours(3),
        &mut None,
        timer,
    );
    abstio::write_binary(
        abstio::path_prebaked_results(&scenario.map_name, &scenario.scenario_name),
        sim.get_analytics(),
    );
    // TODO Remove the num_agents check once transit isn't broken. In Green Lake, 3 poor people are
    // waiting at a bus stop that'll never be served...
    if !sim.is_done() && sim.num_agents().sum() > 10 {
        panic!(
            "It's {} and there are still {} agents left in {}. Gridlock likely...",
            sim.time(),
            prettyprint_usize(sim.num_agents().sum()),
            scenario.map_name.describe()
        );
    }
    timer.stop(format!(
        "prebake for {} / {}",
        scenario.map_name.describe(),
        scenario.scenario_name
    ));

    let mut finished_trips = 0;
    let mut cancelled_trips = 0;
    // Use f64 seconds, since a serialized Duration has a low cap.
    let mut total_trip_duration_seconds = 0.0;
    for (_, _, _, maybe_duration) in &sim.get_analytics().finished_trips {
        if let Some(dt) = maybe_duration {
            finished_trips += 1;
            total_trip_duration_seconds += dt.inner_seconds();
        } else {
            cancelled_trips += 1;
        }
    }
    PrebakeSummary {
        map: scenario.map_name.describe(),
        scenario: scenario.scenario_name,
        finished_trips,
        cancelled_trips,
        total_trip_duration_seconds,
    }
}

#[derive(Serialize)]
struct PrebakeSummary {
    map: String,
    scenario: String,
    finished_trips: usize,
    cancelled_trips: usize,
    total_trip_duration_seconds: f64,
}