Get an end-to-end sample experiment working through the API and Python client

This commit is contained in:
Dustin Carlino 2020-08-18 12:52:38 -07:00
parent 5c337e2e8e
commit 02569b1642
4 changed files with 87 additions and 27 deletions

1
Cargo.lock generated
View File

@ -1306,6 +1306,7 @@ dependencies = [
"hyper 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"map_model 0.1.0",
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
"sim 0.1.0",
"tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -10,6 +10,7 @@ geom = { path = "../geom" }
hyper = "0.13.7"
lazy_static = "1.4.0"
map_model = { path = "../map_model" }
serde = "1.0.110"
sim = { path = "../sim" }
tokio = { version = "0.2", features = ["full"] }
url = "2.1.1"

View File

@ -1,24 +1,58 @@
#!/usr/bin/python3
# This example will see how changing one traffic signal affects trip times.
# Before running this script, start the API server:
#
# > cargo run --release --bin headless -- --port=1234 data/system/scenarios/montlake/weekday.bin
import json
# You may need to install https://requests.readthedocs.io
import requests
api = 'http://localhost:1234'
print('Did you just start the simulation? Time is currently', requests.get(api + '/get-time').text)
print('Is intersection #42 a traffic signal?', requests.get(api + '/get-traffic-signal', params={'id': 42}).text)
def main():
api = 'http://localhost:1234'
# Get the current configuration of one traffic signal
ts = requests.get(api + '/get-traffic-signal', params={'id': 67}).json()
print('Offset of signal #67 is {} seconds'.format(ts['offset']))
print('Phases of signal #67:')
for phase in ts['phases']:
print('')
print(json.dumps(phase, indent=2))
# Make sure to start the simulation from the beginning
print('Did you just start the simulation? Time is currently', requests.get(api + '/sim/get-time').text)
print('Reset the simulation:', requests.get(api + '/sim/reset').text)
print()
# Double the duration of the first phase
print()
print()
ts['phases'][0]['phase_type']['Fixed'] *= 2
print('Update the signal config', requests.post(api + '/set-traffic-signal', json=ts).text)
# Run 12 hours to get a baseline
print('Simulating 12 hours before any edits')
print(requests.get(api + '/sim/goto-time', params={'t': '12:00:00'}).text)
baseline_trips = process_trips(requests.get(api + '/data/get-finished-trips').json()['trips'])
print('Baseline: {} finished trips, total of {} seconds'.format(len(baseline_trips), sum(baseline_trips.values())))
print()
# Modify one traffic signal, doubling the duration of its second phase
ts = requests.get(api + '/traffic-signals/get', params={'id': 67}).json()
ts['phases'][1]['phase_type']['Fixed'] *= 2
# Reset the simulation before applying the edit, since reset also clears edits.
print('Reset the simulation:', requests.get(api + '/sim/reset').text)
print('Update a traffic signal:', requests.post(api + '/traffic-signals/set', json=ts).text)
print()
# Repeat the experiment
print('Simulating 12 hours after the edits')
print(requests.get(api + '/sim/goto-time', params={'t': '12:00:00'}).text)
experimental_trips = process_trips(requests.get(api + '/data/get-finished-trips').json()['trips'])
print('Experiment: {} finished trips, total of {} seconds'.format(len(experimental_trips), sum(experimental_trips.values())))
print()
# Compare -- did this help or not?
print('{} more trips finished after the edits (higher is better)'.format(len(experimental_trips) - len(baseline_trips)))
print('Experiment was {} seconds faster, over all trips'.format(sum(baseline_trips.values()) - sum(experimental_trips.values())))
# Return a map from trip ID to the duration (in seconds) of the trip. Filter
# out aborted (failed) trips.
def process_trips(trips):
results = {}
for (_, trip, mode, duration) in trips:
if mode is not None:
results[trip] = duration
return results
if __name__ == '__main__':
main()

View File

@ -10,10 +10,11 @@
// ... huge JSON blob
use abstutil::{CmdArgs, Timer};
use geom::Time;
use geom::{Duration, Time};
use hyper::{Body, Request, Response, Server};
use map_model::{ControlTrafficSignal, IntersectionID, Map};
use sim::{AlertHandler, Sim, SimFlags, SimOptions};
use serde::Serialize;
use sim::{AlertHandler, Sim, SimFlags, SimOptions, TripID, TripMode};
use std::collections::HashMap;
use std::error::Error;
use std::sync::RwLock;
@ -21,6 +22,8 @@ use std::sync::RwLock;
lazy_static::lazy_static! {
static ref MAP: RwLock<Map> = RwLock::new(Map::blank());
static ref SIM: RwLock<Sim> = RwLock::new(Sim::new(&Map::blank(), SimOptions::new("tmp"), &mut Timer::throwaway()));
// TODO Readonly?
static ref FLAGS: RwLock<SimFlags> = RwLock::new(SimFlags::for_test("tmp"));
}
#[tokio::main]
@ -32,10 +35,10 @@ async fn main() {
// Less spam
sim_flags.opts.alerts = AlertHandler::Silence;
let mut timer = Timer::new("setup headless");
let (map, sim, _) = sim_flags.load(&mut timer);
let (map, sim, _) = sim_flags.load(&mut Timer::new("setup headless"));
*MAP.write().unwrap() = map;
*SIM.write().unwrap() = sim;
*FLAGS.write().unwrap() = sim_flags;
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
println!("Listening on http://{}", addr);
@ -81,19 +84,26 @@ fn handle_command(
map: &mut Map,
) -> Result<String, Box<dyn Error>> {
match path {
"/get-time" => Ok(sim.time().to_string()),
"/goto-time" => {
// Controlling the simulation
"/sim/reset" => {
let (new_map, new_sim, _) = FLAGS.read().unwrap().load(&mut Timer::new("reset sim"));
*map = new_map;
*sim = new_sim;
Ok(format!("sim reloaded"))
}
"/sim/get-time" => Ok(sim.time().to_string()),
"/sim/goto-time" => {
let t = Time::parse(&params["t"])?;
if t <= sim.time() {
Err(format!("{} is in the past", t).into())
Err(format!("{} is in the past. call /sim/reset first?", t).into())
} else {
let dt = t - sim.time();
sim.timed_step(map, dt, &mut None, &mut Timer::throwaway());
sim.timed_step(map, dt, &mut None, &mut Timer::new("goto-time"));
Ok(format!("it's now {}", t))
}
}
"/get-delays" => Ok(abstutil::to_json(&sim.get_analytics().intersection_delays)),
"/get-traffic-signal" => {
// Traffic signals
"/traffic-signals/get" => {
let i = IntersectionID(params["id"].parse::<usize>()?);
if let Some(ts) = map.maybe_get_traffic_signal(i) {
Ok(abstutil::to_json(ts))
@ -101,11 +111,25 @@ fn handle_command(
Err(format!("{} isn't a traffic signal", i).into())
}
}
"/set-traffic-signal" => {
"/traffic-signals/set" => {
let ts: ControlTrafficSignal = abstutil::from_json(body)?;
let id = ts.id;
map.incremental_edit_traffic_signal(ts);
Ok(format!("cool, got ts updates"))
Ok(format!("{} has been updated", id))
}
// Querying data
"/data/get-finished-trips" => Ok(abstutil::to_json(&FinishedTrips {
trips: sim.get_analytics().finished_trips.clone(),
})),
_ => Err("Unknown command".into()),
}
}
// TODO I think specifying the API with protobufs or similar will be a better idea.
#[derive(Serialize)]
struct FinishedTrips {
// TODO Hack: No TripMode means aborted
// Finish time, ID, mode (or None as aborted), trip duration
pub trips: Vec<(Time, TripID, Option<TripMode>, Duration)>,
}