Scenario should just have specific trips. split a different structure for generating Scenarios. has the happy side effect of removing lots of weird old spawning code, duplicated checks

This commit is contained in:
Dustin Carlino 2020-04-01 13:50:36 -07:00
parent 9138097e9e
commit 70875d104d
13 changed files with 694 additions and 798 deletions

View File

@ -5,6 +5,7 @@ use serde_derive::{Deserialize, Serialize};
// Need to explain this trick -- basically keeps consistency between two different simulations when
// each one might make slightly different sequences of calls to the RNG.
// TODO Was once used for parked car seeding, but we're not doing things that way anymore...
pub fn fork_rng(base_rng: &mut XorShiftRng) -> XorShiftRng {
XorShiftRng::from_seed([base_rng.next_u32() as u8; 16])
}

View File

@ -203,10 +203,11 @@ e761e1d1f6c9b8befe53296e0411cef2 data/system/assets/minimap/zoom_out_fully.svg
9caf97939884d4859accca34265f45fb data/system/assets/minimap/up.svg
a5e849fa8883569519976ebfef3ae269 data/system/night_colors.json
1909af5ebfefe7ad4102335c9e789d24 data/system/override_colors.json
e07df86cef2e721115583d61d1fb68a6 data/system/fonts/Roboto-Bold.ttf
d02d0d103f7b00672a5f1145c5169d8c data/system/fonts/Overpass-Bold.ttf
0807de33685581f680c95f790cbe3236 data/system/fonts/Overpass-Regular.ttf
2a13391023ce8787887331530cac35a7 data/system/fonts/BungeeInline-Regular.ttf
17a1468e62195d0688a6f3bd12da2e92 data/system/fonts/Overpass-SemiBold.ttf
259d4afad7edca07e727ef80f5bbce07 data/system/fonts/Bungee-Regular.ttf
11eabca2251325cfc5589c9c6fb57b46 data/system/fonts/Roboto-Regular.ttf
b38a9172d4a3c5521c1bde089b87a4b5 data/system/maps/huge_seattle.bin
06e7705be021d6b236b4c90e9a8f4d35 data/system/maps/ballard.bin
8f0915d1398fef700122bb9a0d0c8eda data/system/maps/downtown.bin
@ -217,16 +218,16 @@ cb824a0243359190ca6f766329e074ef data/system/maps/intl_district.bin
cc45f42cb24cad1cfdbf5ed7a0cb86d4 data/system/synthetic_maps/signal_double.json
8b949cc34d9a27ace0bd8ecde55a9520 data/system/synthetic_maps/signal_single.json
1cd7be125e1d992613ed3a41e8b25b6a data/system/synthetic_maps/signal_fan_in.json
1b62cbc57987972b34ee40cc001acd57 data/system/scenarios/ballard/weekday.bin
ee9e8849cd6e2a10cbabc88d1d521258 data/system/scenarios/intl_district/weekday.bin
330dd07ea6b81fbf17d0f18fa4014572 data/system/scenarios/23rd/weekday.bin
af8cefc0c99972082ab3ce3363412d47 data/system/scenarios/downtown/weekday.bin
e780764fe58e760ca76f017c1fd87244 data/system/scenarios/huge_seattle/weekday.bin
cd85737ea5e775f7c67b1d82c08b64fe data/system/scenarios/caphill/weekday.bin
b6367e75ae8bb7e824beb57a845c15e0 data/system/scenarios/montlake/weekday.bin
6493fe47693a9785d3928a3212a982e5 data/system/scenarios/ballard/weekday.bin
122d6b5d57bba81b5e1a188bff6c14f0 data/system/scenarios/intl_district/weekday.bin
49d8c7d7f6bcd90847b4dc3d4d66693e data/system/scenarios/23rd/weekday.bin
dcf786ec9ad3284e6f70179e19795720 data/system/scenarios/downtown/weekday.bin
370928cacb5d28ce7adb845982dd58b3 data/system/scenarios/huge_seattle/weekday.bin
7292bd648e628e38b381e27dec613893 data/system/scenarios/caphill/weekday.bin
c92d3580f01b37aad20dfede7270039d data/system/scenarios/montlake/weekday.bin
b284a19e12b63bb660cc36bc86344bcc data/system/prebaked_results/23rd/weekday.bin
1adfeaed9d4095b3048999ec745de780 data/system/prebaked_results/signal_single/tutorial lvl1.bin
1c5a9a6f69b5f8b7ad9615af9c0ecc8c data/system/prebaked_results/signal_single/tutorial lvl2.bin
1e26ae48f3b5f31f55ab34b7a1d8c162 data/system/prebaked_results/montlake/car vs bike contention.bin
f044fb981b9cf2842804fbeb7eb3713b data/system/prebaked_results/signal_single/tutorial lvl1.bin
dc7d38456d49e3a03bb5d5ea4b9d54ce data/system/prebaked_results/signal_single/tutorial lvl2.bin
3567a2f9e8faa46ef54e205216122491 data/system/prebaked_results/montlake/car vs bike contention.bin
d59fc84ef83a2773c3c48c7f8f01a5a4 data/system/prebaked_results/montlake/weekday.bin
f15bffa5c937d56515dc2a59770f7510 data/system/prebaked_results/montlake/car vs bus contention.bin
dc258540146adf0a8114f72e720e4d29 data/system/prebaked_results/montlake/car vs bus contention.bin

View File

@ -285,7 +285,13 @@ pub fn prebake_all() {
let mut done_scenarios = HashSet::new();
for challenge in list {
if let Some(scenario) = challenge.gameplay.scenario(&map, None, &mut timer) {
// Bit of an abuse of this, but just need to fix the rng seed.
if let Some(scenario) = challenge.gameplay.scenario(
&map,
None,
SimFlags::for_test("prebaked").make_rng(),
&mut timer,
) {
if done_scenarios.contains(&scenario.scenario_name) {
continue;
}
@ -296,7 +302,12 @@ pub fn prebake_all() {
}
// TODO A weird hack to glue up tutorial scenarios.
if map.get_name() == "montlake" {
for scenario in TutorialState::scenarios_to_prebake(&map) {
for generator in TutorialState::scenarios_to_prebake() {
let scenario = generator.generate(
&map,
&mut SimFlags::for_test("prebaked").make_rng(),
&mut timer,
);
prebake(&map, scenario, &mut timer);
}
}

View File

@ -20,7 +20,7 @@ pub struct ScenarioManager {
tool_panel: WrappedComposite,
scenario: Scenario,
// The (person, trip) usizes are indices into scenario.population.people[x].trips[y]
// The (person, trip) usizes are indices into scenario.people[x].trips[y]
trips_from_bldg: MultiMap<BuildingID, (usize, usize)>,
trips_to_bldg: MultiMap<BuildingID, (usize, usize)>,
trips_from_border: MultiMap<IntersectionID, (usize, usize)>,
@ -37,7 +37,7 @@ impl ScenarioManager {
let mut trips_from_border = MultiMap::new();
let mut trips_to_border = MultiMap::new();
let mut num_trips = 0;
for (idx1, person) in scenario.population.people.iter().enumerate() {
for (idx1, person) in scenario.people.iter().enumerate() {
for (idx2, trip) in person.trips.iter().enumerate() {
num_trips += 1;
let idx = (idx1, idx2);
@ -96,7 +96,7 @@ impl ScenarioManager {
],
);
let mut total_cars_needed = 0;
for (b, count) in &scenario.population.individ_parked_cars {
for (b, count) in &scenario.parked_cars_per_bldg {
total_cars_needed += count;
let color = if *count == 0 {
continue;
@ -119,10 +119,7 @@ impl ScenarioManager {
format!("Scenario {}", scenario.scenario_name),
vec![
format!("{} total trips", prettyprint_usize(num_trips),),
format!(
"{} people",
prettyprint_usize(scenario.population.people.len())
),
format!("{} people", prettyprint_usize(scenario.people.len())),
format!("seed {} parked cars", prettyprint_usize(total_cars_needed)),
format!(
"{} parking spots",
@ -224,7 +221,7 @@ fn make_trip_picker(
WizardState::new(Box::new(move |wiz, ctx, app| {
let mut people = BTreeSet::new();
for (idx1, _) in &indices {
people.insert(scenario.population.people[*idx1].id);
people.insert(scenario.people[*idx1].id);
}
let warp_to = wiz
@ -236,7 +233,7 @@ fn make_trip_picker(
indices
.iter()
.map(|(idx1, idx2)| {
let person = &scenario.population.people[*idx1];
let person = &scenario.people[*idx1];
let trip = &person.trips[*idx2];
Choice::new(
describe(person, trip, home),
@ -394,7 +391,7 @@ fn show_demand(
let mut from_ids = Counter::new();
for (idx1, idx2) in from {
from_ids.inc(other_endpt(
&scenario.population.people[*idx1].trips[*idx2],
&scenario.people[*idx1].trips[*idx2],
home,
&app.primary.map,
));
@ -402,7 +399,7 @@ fn show_demand(
let mut to_ids = Counter::new();
for (idx1, idx2) in to {
to_ids.inc(other_endpt(
&scenario.population.people[*idx1].trips[*idx2],
&scenario.people[*idx1].trips[*idx2],
home,
&app.primary.map,
));
@ -462,7 +459,6 @@ impl DotMap {
fn new(ctx: &mut EventCtx, app: &App, scenario: &Scenario) -> DotMap {
let map = &app.primary.map;
let lines = scenario
.population
.people
.iter()
.flat_map(|p| {

View File

@ -7,7 +7,7 @@ use crate::sandbox::{SandboxControls, ScoreCard};
use ezgui::{EventCtx, GfxCtx};
use geom::{Duration, Statistic, Time};
use map_model::{IntersectionID, Map};
use sim::{BorderSpawnOverTime, OriginDestination, Scenario};
use sim::{BorderSpawnOverTime, OriginDestination, ScenarioGenerator};
const GOAL: Duration = Duration::const_seconds(30.0);
@ -146,7 +146,7 @@ fn final_score(app: &App) -> (String, bool) {
// UI.
// Motivate a separate left turn phase for north/south, but not left/right
pub fn tutorial_scenario_lvl1(map: &Map) -> Scenario {
pub fn tutorial_scenario_lvl1(map: &Map) -> ScenarioGenerator {
// TODO In lieu of the deleted labels
let north = IntersectionID(2);
let south = IntersectionID(3);
@ -154,7 +154,7 @@ pub fn tutorial_scenario_lvl1(map: &Map) -> Scenario {
let left = IntersectionID(1);
let right = IntersectionID(0);
let mut s = Scenario::empty(map, "tutorial lvl1");
let mut s = ScenarioGenerator::empty("tutorial lvl1");
// What's the essence of what I've specified below? Don't care about the time distribution,
// exact number of agents, different modes. It's just an OD matrix with relative weights.
@ -195,7 +195,7 @@ pub fn tutorial_scenario_lvl1(map: &Map) -> Scenario {
}
// Motivate a pedestrian scramble cycle
pub fn tutorial_scenario_lvl2(map: &Map) -> Scenario {
pub fn tutorial_scenario_lvl2(map: &Map) -> ScenarioGenerator {
let north = IntersectionID(3);
let south = IntersectionID(3);
let left = IntersectionID(1);
@ -216,21 +216,21 @@ pub fn tutorial_scenario_lvl2(map: &Map) -> Scenario {
s
}
fn heavy(s: &mut Scenario, map: &Map, from: IntersectionID, to: IntersectionID) {
fn heavy(s: &mut ScenarioGenerator, map: &Map, from: IntersectionID, to: IntersectionID) {
spawn(s, map, from, to, 100, 0);
}
fn heavy_peds(s: &mut Scenario, map: &Map, from: IntersectionID, to: IntersectionID) {
fn heavy_peds(s: &mut ScenarioGenerator, map: &Map, from: IntersectionID, to: IntersectionID) {
spawn(s, map, from, to, 0, 100);
}
fn medium(s: &mut Scenario, map: &Map, from: IntersectionID, to: IntersectionID) {
fn medium(s: &mut ScenarioGenerator, map: &Map, from: IntersectionID, to: IntersectionID) {
spawn(s, map, from, to, 100, 0);
}
fn light(s: &mut Scenario, map: &Map, from: IntersectionID, to: IntersectionID) {
fn light(s: &mut ScenarioGenerator, map: &Map, from: IntersectionID, to: IntersectionID) {
spawn(s, map, from, to, 100, 0);
}
fn spawn(
s: &mut Scenario,
s: &mut ScenarioGenerator,
map: &Map,
from: IntersectionID,
to: IntersectionID,

View File

@ -26,7 +26,8 @@ use ezgui::{
};
use geom::{Duration, Polygon};
use map_model::{EditCmd, EditIntersection, Map, MapEdits};
use sim::{Analytics, Scenario, TripMode};
use rand_xorshift::XorShiftRng;
use sim::{Analytics, Scenario, ScenarioGenerator, TripMode};
#[derive(PartialEq, Clone)]
pub enum GameplayMode {
@ -107,6 +108,7 @@ impl GameplayMode {
&self,
map: &Map,
num_agents: Option<usize>,
mut rng: XorShiftRng,
timer: &mut Timer,
) -> Option<Scenario> {
let name = match self {
@ -115,13 +117,14 @@ impl GameplayMode {
}
GameplayMode::PlayScenario(_, ref scenario) => scenario.to_string(),
GameplayMode::FixTrafficSignalsTutorial(stage) => {
if *stage == 0 {
return Some(fix_traffic_signals::tutorial_scenario_lvl1(map));
let generator = if *stage == 0 {
fix_traffic_signals::tutorial_scenario_lvl1(map)
} else if *stage == 1 {
return Some(fix_traffic_signals::tutorial_scenario_lvl2(map));
fix_traffic_signals::tutorial_scenario_lvl2(map)
} else {
unreachable!()
}
};
return Some(generator.generate(map, &mut rng, timer));
}
// TODO Some of these WILL have scenarios!
GameplayMode::Tutorial(_) => {
@ -130,17 +133,26 @@ impl GameplayMode {
_ => "weekday".to_string(),
};
Some(if name == "random" {
if let Some(n) = num_agents {
Scenario::scaled_run(map, n)
(if let Some(n) = num_agents {
ScenarioGenerator::scaled_run(n)
} else {
Scenario::small_run(map)
}
ScenarioGenerator::small_run(map)
})
.generate(map, &mut rng, &mut Timer::new("generate scenario"))
} else if name == "just buses" {
let mut s = Scenario::empty(map, "just buses");
s.only_seed_buses = None;
s
} else {
abstutil::read_binary(abstutil::path_scenario(map.get_name(), &name), timer)
let path = abstutil::path_scenario(map.get_name(), &name);
match abstutil::maybe_read_binary(path.clone(), timer) {
Ok(s) => s,
Err(err) => {
println!("\n\n{} is missing or corrupt. Check https://github.com/dabreegster/abstreet/blob/master/docs/dev.md and file an issue if you have trouble.", path);
println!("\n{}", err);
std::process::exit(1);
}
}
})
}
@ -188,6 +200,7 @@ impl GameplayMode {
if let Some(scenario) = self.scenario(
&app.primary.map,
app.primary.current_flags.num_agents,
app.primary.current_flags.sim_flags.make_rng(),
timer,
) {
scenario.instantiate(

View File

@ -19,8 +19,8 @@ use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use sim::{
BorderSpawnOverTime, DrivingGoal, OriginDestination, Scenario, SidewalkSpot, Sim, TripSpawner,
TripSpec,
BorderSpawnOverTime, DrivingGoal, OriginDestination, Scenario, ScenarioGenerator, SidewalkSpot,
Sim, TripSpawner, TripSpec,
};
const SMALL_DT: Duration = Duration::const_seconds(0.1);
@ -505,7 +505,7 @@ impl State for SpawnManyAgents {
}
fn create_swarm(app: &mut App, from: LaneID, to: LaneID, count: usize, duration: Duration) {
let mut scenario = Scenario::empty(&app.primary.map, "swarm");
let mut scenario = ScenarioGenerator::empty("swarm");
scenario.border_spawn_over_time.push(BorderSpawnOverTime {
num_peds: 0,
num_cars: count,
@ -526,12 +526,14 @@ fn create_swarm(app: &mut App, from: LaneID, to: LaneID, count: usize, duration:
percent_use_transit: 0.0,
});
let mut rng = app.primary.current_flags.sim_flags.make_rng();
scenario.instantiate(
&mut app.primary.sim,
&app.primary.map,
&mut rng,
&mut Timer::throwaway(),
);
scenario
.generate(&app.primary.map, &mut rng, &mut Timer::throwaway())
.instantiate(
&mut app.primary.sim,
&app.primary.map,
&mut rng,
&mut Timer::throwaway(),
);
}
fn make_top_bar(ctx: &mut EventCtx, title: &str, howto: &str) -> Composite {

View File

@ -16,10 +16,11 @@ use ezgui::{
VerticalAlignment, Widget,
};
use geom::{Distance, Duration, PolyLine, Polygon, Pt2D, Statistic, Time};
use map_model::{BuildingID, IntersectionID, IntersectionType, LaneType, Map, RoadID};
use map_model::{BuildingID, IntersectionID, IntersectionType, LaneType, RoadID};
use maplit::btreeset;
use sim::{
AgentID, Analytics, BorderSpawnOverTime, CarID, OriginDestination, Scenario, VehicleType,
AgentID, Analytics, BorderSpawnOverTime, CarID, OriginDestination, ScenarioGenerator,
VehicleType,
};
pub struct Tutorial {
@ -548,18 +549,29 @@ impl Stage {
fn spawn_randomly(self) -> Stage {
self.spawn(Box::new(|app| {
Scenario::small_run(&app.primary.map).instantiate(
&mut app.primary.sim,
&app.primary.map,
&mut app.primary.current_flags.sim_flags.make_rng(),
&mut Timer::throwaway(),
)
ScenarioGenerator::small_run(&app.primary.map)
.generate(
&app.primary.map,
&mut app.primary.current_flags.sim_flags.make_rng(),
&mut Timer::throwaway(),
)
.instantiate(
&mut app.primary.sim,
&app.primary.map,
&mut app.primary.current_flags.sim_flags.make_rng(),
&mut Timer::throwaway(),
)
}))
}
fn spawn_scenario(self, scenario: Scenario) -> Stage {
fn spawn_scenario(self, generator: ScenarioGenerator) -> Stage {
self.spawn(Box::new(move |app| {
let mut timer = Timer::new("spawn scenario with prebaked results");
let scenario = generator.generate(
&app.primary.map,
&mut app.primary.current_flags.sim_flags.make_rng(),
&mut timer,
);
scenario.instantiate(
&mut app.primary.sim,
&app.primary.map,
@ -603,8 +615,8 @@ pub struct TutorialState {
score_delivered: bool,
}
fn make_bike_lane_scenario(map: &Map) -> Scenario {
let mut s = Scenario::empty(map, "car vs bike contention");
fn make_bike_lane_scenario() -> ScenarioGenerator {
let mut s = ScenarioGenerator::empty("car vs bike contention");
s.border_spawn_over_time.push(BorderSpawnOverTime {
num_peds: 0,
num_cars: 10,
@ -618,8 +630,8 @@ fn make_bike_lane_scenario(map: &Map) -> Scenario {
s
}
fn make_bus_lane_scenario(map: &Map) -> Scenario {
let mut s = Scenario::empty(map, "car vs bus contention");
fn make_bus_lane_scenario() -> ScenarioGenerator {
let mut s = ScenarioGenerator::empty("car vs bus contention");
s.only_seed_buses = Some(btreeset! {"43".to_string(), "48".to_string()});
for src in vec![
RoadID(61).backwards(),
@ -1134,7 +1146,7 @@ impl TutorialState {
),
);
let bike_lane_scenario = make_bike_lane_scenario(&app.primary.map);
let bike_lane_scenario = make_bike_lane_scenario();
state.stages.push(
Stage::new(Task::WatchBikes)
@ -1201,7 +1213,7 @@ impl TutorialState {
);
if false {
let bus_lane_scenario = make_bus_lane_scenario(&app.primary.map);
let bus_lane_scenario = make_bus_lane_scenario();
// TODO There's no clear measurement for how well the buses are doing.
// TODO Probably want a steady stream of the cars appearing
@ -1260,8 +1272,8 @@ impl TutorialState {
}
// TODO Weird hack to prebake.
pub fn scenarios_to_prebake(map: &Map) -> Vec<Scenario> {
vec![make_bike_lane_scenario(map), make_bus_lane_scenario(map)]
pub fn scenarios_to_prebake() -> Vec<ScenarioGenerator> {
vec![make_bike_lane_scenario(), make_bus_lane_scenario()]
}
}

View File

@ -4,8 +4,7 @@ use abstutil::{prettyprint_usize, MultiMap, Timer};
use geom::{Distance, Duration, LonLat, Polygon, Pt2D, Time};
use map_model::{BuildingID, IntersectionID, Map, PathConstraints, Position};
use sim::{
DrivingGoal, IndividTrip, PersonID, PersonSpec, Population, Scenario, SidewalkSpot, SpawnTrip,
TripSpec,
DrivingGoal, IndividTrip, PersonID, PersonSpec, Scenario, SidewalkSpot, SpawnTrip, TripSpec,
};
use std::collections::{BTreeMap, HashMap};
@ -286,7 +285,7 @@ pub fn trips_to_scenario(map: &Map, timer: &mut Timer) -> Scenario {
let (trips, _) = clip_trips(map, timer);
let orig_trips = trips.len();
let individ_parked_cars = count_cars(&trips, map);
let parked_cars_per_bldg = count_cars(&trips, map);
let mut individ_trips: Vec<Option<IndividTrip>> = Vec::new();
// person -> (trip seq, index into individ_trips)
@ -311,12 +310,9 @@ pub fn trips_to_scenario(map: &Map, timer: &mut Timer) -> Scenario {
prettyprint_usize(trips_per_person.len())
));
let mut population = Population {
people: Vec::new(),
individ_parked_cars,
};
let mut people = Vec::new();
for (_, seq_trips) in trips_per_person.consume() {
let id = PersonID(population.people.len());
let id = PersonID(people.len());
let mut trips = Vec::new();
for (_, idx) in seq_trips {
// TODO Track when there are gaps in the sequence, to explain the person warping.
@ -325,12 +321,7 @@ pub fn trips_to_scenario(map: &Map, timer: &mut Timer) -> Scenario {
// Actually, the sequence in the Soundcast dataset crosses midnight. Don't do that; sort by
// departure time starting with midnight.
trips.sort_by_key(|t| t.depart);
population.people.push(PersonSpec {
id,
// TODO Do we have to scrape a new input file for this? :(
home: None,
trips,
});
people.push(PersonSpec { id, trips });
}
for maybe_t in individ_trips {
if maybe_t.is_some() {
@ -341,11 +332,9 @@ pub fn trips_to_scenario(map: &Map, timer: &mut Timer) -> Scenario {
Scenario {
scenario_name: "weekday".to_string(),
map_name: map.get_name().to_string(),
people,
parked_cars_per_bldg,
only_seed_buses: None,
seed_parked_cars: Vec::new(),
spawn_over_time: Vec::new(),
border_spawn_over_time: Vec::new(),
population,
}
}
@ -353,10 +342,10 @@ fn count_cars(trips: &Vec<Trip>, map: &Map) -> BTreeMap<BuildingID, usize> {
// How many parked cars do we need to spawn near each building?
// TODO This assumes trips are instantaneous. At runtime, somebody might try to use a parked
// car from a building, but one hasn't been delivered yet.
let mut individ_parked_cars = BTreeMap::new();
let mut parked_cars_per_bldg = BTreeMap::new();
let mut avail_per_bldg = BTreeMap::new();
for b in map.all_buildings() {
individ_parked_cars.insert(b.id, 0);
parked_cars_per_bldg.insert(b.id, 0);
avail_per_bldg.insert(b.id, 0);
}
for trip in trips {
@ -367,12 +356,12 @@ fn count_cars(trips: &Vec<Trip>, map: &Map) -> BTreeMap<BuildingID, usize> {
if avail_per_bldg[&b] > 0 {
*avail_per_bldg.get_mut(&b).unwrap() -= 1;
} else {
*individ_parked_cars.get_mut(&b).unwrap() += 1;
*parked_cars_per_bldg.get_mut(&b).unwrap() += 1;
}
}
if let TripEndpt::Building(b) = trip.to {
*avail_per_bldg.get_mut(&b).unwrap() += 1;
}
}
individ_parked_cars
parked_cars_per_bldg
}

View File

@ -13,8 +13,8 @@ mod trips;
pub use self::analytics::{Analytics, TripPhase};
pub use self::events::{Event, TripPhaseType};
pub use self::make::{
ABTest, BorderSpawnOverTime, IndividTrip, OriginDestination, PersonSpec, Population, Scenario,
SeedParkedCars, SimFlags, SpawnOverTime, SpawnTrip, TripSpawner, TripSpec,
ABTest, BorderSpawnOverTime, IndividTrip, OriginDestination, PersonSpec, Scenario,
ScenarioGenerator, SeedParkedCars, SimFlags, SpawnOverTime, SpawnTrip, TripSpawner, TripSpec,
};
pub(crate) use self::mechanics::{
DrivingSimState, IntersectionSimState, ParkingSimState, WalkingSimState,

523
sim/src/make/generator.rs Normal file
View File

@ -0,0 +1,523 @@
use crate::{
DrivingGoal, IndividTrip, PersonID, PersonSpec, Scenario, SidewalkSpot, SpawnTrip, BIKE_LENGTH,
MAX_CAR_LENGTH,
};
use abstutil::{Timer, WeightedUsizeChoice};
use geom::{Duration, Time};
use map_model::{
BuildingID, DirectedRoadID, FullNeighborhoodInfo, LaneID, Map, PathConstraints, Position,
};
use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeSet, HashMap};
// A way to generate Scenarios
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ScenarioGenerator {
pub scenario_name: String,
pub only_seed_buses: Option<BTreeSet<String>>,
pub seed_parked_cars: Vec<SeedParkedCars>,
pub spawn_over_time: Vec<SpawnOverTime>,
pub border_spawn_over_time: Vec<BorderSpawnOverTime>,
}
// SpawnOverTime and BorderSpawnOverTime should be kept separate. Agents in SpawnOverTime pick
// their mode (use a car, walk, bus) based on the situation. When spawning directly a border,
// agents have to start as a car or pedestrian already.
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct SpawnOverTime {
pub num_agents: usize,
// TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html
pub start_time: Time,
pub stop_time: Time,
pub start_from_neighborhood: String,
pub goal: OriginDestination,
pub percent_driving: f64,
pub percent_biking: f64,
pub percent_use_transit: f64,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct BorderSpawnOverTime {
pub num_peds: usize,
pub num_cars: usize,
pub num_bikes: usize,
pub percent_use_transit: f64,
// TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html
pub start_time: Time,
pub stop_time: Time,
pub start_from_border: DirectedRoadID,
pub goal: OriginDestination,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct SeedParkedCars {
pub neighborhood: String,
pub cars_per_building: WeightedUsizeChoice,
}
impl ScenarioGenerator {
// TODO may need to fork the RNG a bit more
pub fn generate(&self, map: &Map, rng: &mut XorShiftRng, timer: &mut Timer) -> Scenario {
let mut scenario = Scenario::empty(map, &self.scenario_name);
scenario.only_seed_buses = self.only_seed_buses.clone();
timer.start(format!("Generating scenario {}", self.scenario_name));
timer.start("load full neighborhood info");
let neighborhoods = FullNeighborhoodInfo::load_all(map);
timer.stop("load full neighborhood info");
for s in &self.seed_parked_cars {
if !neighborhoods.contains_key(&s.neighborhood) {
panic!("Neighborhood {} isn't defined", s.neighborhood);
}
for b in &neighborhoods[&s.neighborhood].buildings {
scenario
.parked_cars_per_bldg
.insert(*b, s.cars_per_building.sample(rng));
}
}
for s in &self.spawn_over_time {
if !neighborhoods.contains_key(&s.start_from_neighborhood) {
panic!("Neighborhood {} isn't defined", s.start_from_neighborhood);
}
timer.start_iter("SpawnOverTime each agent", s.num_agents);
for _ in 0..s.num_agents {
timer.next();
s.spawn_agent(rng, &mut scenario, &neighborhoods, map, timer);
}
}
timer.start_iter("BorderSpawnOverTime", self.border_spawn_over_time.len());
for s in &self.border_spawn_over_time {
timer.next();
s.spawn_peds(rng, &mut scenario, &neighborhoods, map, timer);
s.spawn_cars(rng, &mut scenario, &neighborhoods, map, timer);
s.spawn_bikes(rng, &mut scenario, &neighborhoods, map, timer);
}
timer.stop(format!("Generating scenario {}", self.scenario_name));
scenario
}
pub fn small_run(map: &Map) -> ScenarioGenerator {
let mut s = ScenarioGenerator {
scenario_name: "small_run".to_string(),
only_seed_buses: None,
seed_parked_cars: vec![SeedParkedCars {
neighborhood: "_everywhere_".to_string(),
cars_per_building: WeightedUsizeChoice {
weights: vec![5, 5],
},
}],
spawn_over_time: vec![SpawnOverTime {
num_agents: 100,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::seconds(5.0),
start_from_neighborhood: "_everywhere_".to_string(),
goal: OriginDestination::Neighborhood("_everywhere_".to_string()),
percent_driving: 0.5,
percent_biking: 0.5,
percent_use_transit: 0.5,
}],
// If there are no sidewalks/driving lanes at a border, scenario instantiation will
// just warn and skip them.
border_spawn_over_time: map
.all_incoming_borders()
.into_iter()
.map(|i| BorderSpawnOverTime {
num_peds: 10,
num_cars: 10,
num_bikes: 10,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::seconds(5.0),
start_from_border: i.some_outgoing_road(map),
goal: OriginDestination::Neighborhood("_everywhere_".to_string()),
percent_use_transit: 0.5,
})
.collect(),
};
for i in map.all_outgoing_borders() {
s.spawn_over_time.push(SpawnOverTime {
num_agents: 10,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::seconds(5.0),
start_from_neighborhood: "_everywhere_".to_string(),
goal: OriginDestination::EndOfRoad(i.some_incoming_road(map)),
percent_driving: 0.5,
percent_biking: 0.5,
percent_use_transit: 0.5,
});
}
s
}
pub fn empty(name: &str) -> ScenarioGenerator {
ScenarioGenerator {
scenario_name: name.to_string(),
only_seed_buses: Some(BTreeSet::new()),
seed_parked_cars: Vec::new(),
spawn_over_time: Vec::new(),
border_spawn_over_time: Vec::new(),
}
}
// No border agents here, because making the count work is hard.
pub fn scaled_run(num_agents: usize) -> ScenarioGenerator {
ScenarioGenerator {
scenario_name: "scaled_run".to_string(),
only_seed_buses: Some(BTreeSet::new()),
seed_parked_cars: vec![SeedParkedCars {
neighborhood: "_everywhere_".to_string(),
cars_per_building: WeightedUsizeChoice {
weights: vec![5, 5],
},
}],
spawn_over_time: vec![SpawnOverTime {
num_agents: num_agents,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::seconds(5.0),
start_from_neighborhood: "_everywhere_".to_string(),
goal: OriginDestination::Neighborhood("_everywhere_".to_string()),
percent_driving: 0.5,
percent_biking: 0.5,
percent_use_transit: 0.5,
}],
border_spawn_over_time: Vec::new(),
}
}
}
impl SpawnOverTime {
fn spawn_agent(
&self,
rng: &mut XorShiftRng,
scenario: &mut Scenario,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
timer: &mut Timer,
) {
let depart = rand_time(rng, self.start_time, self.stop_time);
// Note that it's fine for agents to start/end at the same building. Later we might
// want a better assignment of people per household, or workers per office building.
let from_bldg = *neighborhoods[&self.start_from_neighborhood]
.buildings
.choose(rng)
.unwrap();
let id = PersonID(scenario.people.len());
if rng.gen_bool(self.percent_driving) {
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Car, map, &neighborhoods, rng, timer)
{
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::MaybeUsingParkedCar(from_bldg, goal),
}],
});
return;
}
}
let start_spot = SidewalkSpot::building(from_bldg, map);
if rng.gen_bool(self.percent_biking) {
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Bike, map, &neighborhoods, rng, timer)
{
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::UsingBike(start_spot, goal),
}],
});
return;
}
}
if let Some(goal) = self.goal.pick_walking_goal(map, &neighborhoods, rng, timer) {
if start_spot == goal {
timer.warn("Skipping walking trip between same two buildings".to_string());
return;
}
if rng.gen_bool(self.percent_use_transit) {
// TODO This throws away some work. It also sequentially does expensive
// work right here.
if let Some((stop1, stop2, route)) =
map.should_use_transit(start_spot.sidewalk_pos, goal.sidewalk_pos)
{
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::UsingTransit(start_spot, goal, route, stop1, stop2),
}],
});
return;
}
}
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::JustWalking(start_spot, goal),
}],
});
return;
}
timer.warn(format!("Couldn't fulfill {:?} at all", self));
}
}
impl BorderSpawnOverTime {
fn spawn_peds(
&self,
rng: &mut XorShiftRng,
scenario: &mut Scenario,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
timer: &mut Timer,
) {
if self.num_peds == 0 {
return;
}
let start = if let Some(s) =
SidewalkSpot::start_at_border(self.start_from_border.src_i(map), map)
{
s
} else {
timer.warn(format!(
"Can't start_at_border for {} without sidewalk",
self.start_from_border
));
return;
};
for _ in 0..self.num_peds {
let depart = rand_time(rng, self.start_time, self.stop_time);
let id = PersonID(scenario.people.len());
if let Some(goal) = self.goal.pick_walking_goal(map, &neighborhoods, rng, timer) {
if rng.gen_bool(self.percent_use_transit) {
// TODO This throws away some work. It also sequentially does expensive
// work right here.
if let Some((stop1, stop2, route)) =
map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos)
{
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::UsingTransit(
start.clone(),
goal,
route,
stop1,
stop2,
),
}],
});
continue;
}
}
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::JustWalking(start.clone(), goal),
}],
});
}
}
}
fn spawn_cars(
&self,
rng: &mut XorShiftRng,
scenario: &mut Scenario,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
timer: &mut Timer,
) {
if self.num_cars == 0 {
return;
}
let lanes = pick_starting_lanes(
self.start_from_border.lanes(PathConstraints::Car, map),
false,
map,
);
if lanes.is_empty() {
timer.warn(format!(
"Can't start {} cars at border for {}",
self.num_cars, self.start_from_border
));
return;
};
for _ in 0..self.num_cars {
let depart = rand_time(rng, self.start_time, self.stop_time);
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Car, map, &neighborhoods, rng, timer)
{
let id = PersonID(scenario.people.len());
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::CarAppearing {
// Safe because pick_starting_lanes checks for this
start: Position::new(*lanes.choose(rng).unwrap(), MAX_CAR_LENGTH),
goal,
is_bike: false,
},
}],
});
}
}
}
fn spawn_bikes(
&self,
rng: &mut XorShiftRng,
scenario: &mut Scenario,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
timer: &mut Timer,
) {
if self.num_bikes == 0 {
return;
}
let lanes = pick_starting_lanes(
self.start_from_border.lanes(PathConstraints::Bike, map),
true,
map,
);
if lanes.is_empty() {
timer.warn(format!(
"Can't start {} bikes at border for {}",
self.num_bikes, self.start_from_border
));
return;
};
for _ in 0..self.num_bikes {
let depart = rand_time(rng, self.start_time, self.stop_time);
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Bike, map, &neighborhoods, rng, timer)
{
let id = PersonID(scenario.people.len());
scenario.people.push(PersonSpec {
id,
trips: vec![IndividTrip {
depart,
trip: SpawnTrip::CarAppearing {
start: Position::new(*lanes.choose(rng).unwrap(), BIKE_LENGTH),
goal,
is_bike: true,
},
}],
});
}
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum OriginDestination {
Neighborhood(String),
EndOfRoad(DirectedRoadID),
GotoBldg(BuildingID),
}
impl OriginDestination {
fn pick_driving_goal(
&self,
constraints: PathConstraints,
map: &Map,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
rng: &mut XorShiftRng,
timer: &mut Timer,
) -> Option<DrivingGoal> {
match self {
OriginDestination::Neighborhood(ref n) => Some(DrivingGoal::ParkNear(
*neighborhoods[n].buildings.choose(rng).unwrap(),
)),
OriginDestination::GotoBldg(b) => Some(DrivingGoal::ParkNear(*b)),
OriginDestination::EndOfRoad(dr) => {
let goal = DrivingGoal::end_at_border(*dr, constraints, map);
if goal.is_none() {
timer.warn(format!(
"Can't spawn a {:?} ending at border {}; no appropriate lanes there",
constraints, dr
));
}
goal
}
}
}
fn pick_walking_goal(
&self,
map: &Map,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
rng: &mut XorShiftRng,
timer: &mut Timer,
) -> Option<SidewalkSpot> {
match self {
OriginDestination::Neighborhood(ref n) => Some(SidewalkSpot::building(
*neighborhoods[n].buildings.choose(rng).unwrap(),
map,
)),
OriginDestination::EndOfRoad(dr) => {
let goal = SidewalkSpot::end_at_border(dr.dst_i(map), map);
if goal.is_none() {
timer.warn(format!("Can't end_at_border for {} without a sidewalk", dr));
}
goal
}
OriginDestination::GotoBldg(b) => Some(SidewalkSpot::building(*b, map)),
}
}
}
fn rand_time(rng: &mut XorShiftRng, low: Time, high: Time) -> Time {
assert!(high > low);
Time::START_OF_DAY + Duration::seconds(rng.gen_range(low.inner_seconds(), high.inner_seconds()))
}
fn pick_starting_lanes(mut lanes: Vec<LaneID>, is_bike: bool, map: &Map) -> Vec<LaneID> {
let min_len = if is_bike { BIKE_LENGTH } else { MAX_CAR_LENGTH };
lanes.retain(|l| map.get_l(*l).length() > min_len);
if is_bike {
// If there's a choice between bike lanes and otherwise, always use the bike lanes.
let bike_lanes = lanes
.iter()
.filter(|l| map.get_l(**l).is_biking())
.cloned()
.collect::<Vec<LaneID>>();
if !bike_lanes.is_empty() {
lanes = bike_lanes;
}
}
lanes
}

View File

@ -1,12 +1,13 @@
mod a_b_test;
mod generator;
mod load;
mod scenario;
mod spawner;
pub use self::a_b_test::ABTest;
pub use self::load::SimFlags;
pub use self::scenario::{
BorderSpawnOverTime, IndividTrip, OriginDestination, PersonSpec, Population, Scenario,
SeedParkedCars, SpawnOverTime, SpawnTrip,
pub use self::generator::{
BorderSpawnOverTime, OriginDestination, ScenarioGenerator, SeedParkedCars, SpawnOverTime,
};
pub use self::load::SimFlags;
pub use self::scenario::{IndividTrip, PersonSpec, Scenario, SpawnTrip};
pub use self::spawner::{TripSpawner, TripSpec};

View File

@ -1,68 +1,53 @@
use crate::make::TripSpawner;
use crate::{
CarID, DrivingGoal, ParkingSpot, PersonID, SidewalkSpot, Sim, TripSpec, VehicleSpec,
VehicleType, BIKE_LENGTH, MAX_CAR_LENGTH, MIN_CAR_LENGTH,
};
use abstutil::{fork_rng, Timer, WeightedUsizeChoice};
use geom::{Distance, Duration, Speed, Time};
use map_model::{
BuildingID, BusRouteID, BusStopID, DirectedRoadID, FullNeighborhoodInfo, LaneID, Map,
PathConstraints, Position, RoadID,
DrivingGoal, ParkingSpot, PersonID, SidewalkSpot, Sim, TripSpec, VehicleSpec, VehicleType,
BIKE_LENGTH, MAX_CAR_LENGTH, MIN_CAR_LENGTH,
};
use abstutil::Timer;
use geom::{Distance, Speed, Time};
use map_model::{BuildingID, BusRouteID, BusStopID, Map, Position, RoadID};
use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::collections::{BTreeMap, BTreeSet, HashSet, VecDeque};
// How to start a simulation.
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Scenario {
pub scenario_name: String,
pub map_name: String,
// Higher-level ways of specifying stuff
pub people: Vec<PersonSpec>,
pub parked_cars_per_bldg: BTreeMap<BuildingID, usize>,
// None means seed all buses. Otherwise the route name must be present here.
pub only_seed_buses: Option<BTreeSet<String>>,
pub seed_parked_cars: Vec<SeedParkedCars>,
pub spawn_over_time: Vec<SpawnOverTime>,
pub border_spawn_over_time: Vec<BorderSpawnOverTime>,
// Much more detailed
pub population: Population,
}
// SpawnOverTime and BorderSpawnOverTime should be kept separate. Agents in SpawnOverTime pick
// their mode (use a car, walk, bus) based on the situation. When spawning directly a border,
// agents have to start as a car or pedestrian already.
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct SpawnOverTime {
pub num_agents: usize,
// TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html
pub start_time: Time,
pub stop_time: Time,
pub start_from_neighborhood: String,
pub goal: OriginDestination,
pub percent_biking: f64,
pub percent_use_transit: f64,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct BorderSpawnOverTime {
pub num_peds: usize,
pub num_cars: usize,
pub num_bikes: usize,
pub percent_use_transit: f64,
// TODO use https://docs.rs/rand/0.5.5/rand/distributions/struct.Normal.html
pub start_time: Time,
pub stop_time: Time,
pub start_from_border: DirectedRoadID,
pub goal: OriginDestination,
pub struct PersonSpec {
pub id: PersonID,
pub trips: Vec<IndividTrip>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct SeedParkedCars {
pub neighborhood: String,
pub cars_per_building: WeightedUsizeChoice,
pub struct IndividTrip {
pub depart: Time,
pub trip: SpawnTrip,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum SpawnTrip {
CarAppearing {
// TODO Replace start with building|border
start: Position,
goal: DrivingGoal,
// For bikes starting at a border, use CarAppearing. UsingBike implies a walk->bike trip.
is_bike: bool,
},
MaybeUsingParkedCar(BuildingID, DrivingGoal),
UsingBike(SidewalkSpot, DrivingGoal),
JustWalking(SidewalkSpot, SidewalkSpot),
UsingTransit(SidewalkSpot, SidewalkSpot, BusRouteID, BusStopID, BusStopID),
}
impl Scenario {
@ -85,70 +70,19 @@ impl Scenario {
}
}
timer.start("load full neighborhood info");
let neighborhoods = FullNeighborhoodInfo::load_all(map);
timer.stop("load full neighborhood info");
for s in &self.seed_parked_cars {
if !neighborhoods.contains_key(&s.neighborhood) {
panic!("Neighborhood {} isn't defined", s.neighborhood);
}
seed_parked_cars(
sim,
&s.cars_per_building,
&neighborhoods[&s.neighborhood].buildings,
&neighborhoods[&s.neighborhood].roads,
rng,
map,
timer,
);
}
let mut spawner = sim.make_spawner();
// Don't let two pedestrians starting from one building use the same car.
let mut reserved_cars: HashSet<CarID> = HashSet::new();
for s in &self.spawn_over_time {
if !neighborhoods.contains_key(&s.start_from_neighborhood) {
panic!("Neighborhood {} isn't defined", s.start_from_neighborhood);
}
timer.start_iter("SpawnOverTime each agent", s.num_agents);
for _ in 0..s.num_agents {
timer.next();
s.spawn_agent(
rng,
sim,
&mut spawner,
&mut reserved_cars,
&neighborhoods,
map,
timer,
);
}
}
timer.start_iter("BorderSpawnOverTime", self.border_spawn_over_time.len());
for s in &self.border_spawn_over_time {
timer.next();
s.spawn_peds(rng, &mut spawner, &neighborhoods, map, sim, timer);
s.spawn_cars(rng, &mut spawner, &neighborhoods, map, sim, timer);
s.spawn_bikes(rng, &mut spawner, &neighborhoods, map, sim, timer);
}
let mut individ_parked_cars: Vec<(BuildingID, usize)> = Vec::new();
for (b, cnt) in &self.population.individ_parked_cars {
let mut parked_cars_per_bldg: Vec<(BuildingID, usize)> = Vec::new();
for (b, cnt) in &self.parked_cars_per_bldg {
if *cnt != 0 {
individ_parked_cars.push((*b, *cnt));
parked_cars_per_bldg.push((*b, *cnt));
}
}
individ_parked_cars.shuffle(rng);
seed_individ_parked_cars(individ_parked_cars, sim, map, rng, timer);
parked_cars_per_bldg.shuffle(rng);
seed_parked_cars(parked_cars_per_bldg, sim, map, rng, timer);
timer.start_iter("trips for People", self.population.people.len());
for p in &self.population.people {
timer.start_iter("trips for People", self.people.len());
for p in &self.people {
timer.next();
// TODO Or spawner?
sim.new_person(p.id);
@ -169,102 +103,13 @@ impl Scenario {
);
}
pub fn small_run(map: &Map) -> Scenario {
let mut s = Scenario {
scenario_name: "small_run".to_string(),
only_seed_buses: None,
map_name: map.get_name().to_string(),
seed_parked_cars: vec![SeedParkedCars {
neighborhood: "_everywhere_".to_string(),
cars_per_building: WeightedUsizeChoice {
weights: vec![5, 5],
},
}],
spawn_over_time: vec![SpawnOverTime {
num_agents: 100,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::seconds(5.0),
start_from_neighborhood: "_everywhere_".to_string(),
goal: OriginDestination::Neighborhood("_everywhere_".to_string()),
percent_biking: 0.5,
percent_use_transit: 0.5,
}],
// If there are no sidewalks/driving lanes at a border, scenario instantiation will
// just warn and skip them.
border_spawn_over_time: map
.all_incoming_borders()
.into_iter()
.map(|i| BorderSpawnOverTime {
num_peds: 10,
num_cars: 10,
num_bikes: 10,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::seconds(5.0),
start_from_border: i.some_outgoing_road(map),
goal: OriginDestination::Neighborhood("_everywhere_".to_string()),
percent_use_transit: 0.5,
})
.collect(),
population: Population {
people: Vec::new(),
individ_parked_cars: BTreeMap::new(),
},
};
for i in map.all_outgoing_borders() {
s.spawn_over_time.push(SpawnOverTime {
num_agents: 10,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::seconds(5.0),
start_from_neighborhood: "_everywhere_".to_string(),
goal: OriginDestination::EndOfRoad(i.some_incoming_road(map)),
percent_biking: 0.5,
percent_use_transit: 0.5,
});
}
s
}
pub fn empty(map: &Map, name: &str) -> Scenario {
Scenario {
scenario_name: name.to_string(),
map_name: map.get_name().to_string(),
people: Vec::new(),
parked_cars_per_bldg: BTreeMap::new(),
only_seed_buses: Some(BTreeSet::new()),
seed_parked_cars: Vec::new(),
spawn_over_time: Vec::new(),
border_spawn_over_time: Vec::new(),
population: Population {
people: Vec::new(),
individ_parked_cars: BTreeMap::new(),
},
}
}
// No border agents here, because making the count work is hard.
pub fn scaled_run(map: &Map, num_agents: usize) -> Scenario {
Scenario {
scenario_name: "scaled_run".to_string(),
map_name: map.get_name().to_string(),
only_seed_buses: Some(BTreeSet::new()),
seed_parked_cars: vec![SeedParkedCars {
neighborhood: "_everywhere_".to_string(),
cars_per_building: WeightedUsizeChoice {
weights: vec![5, 5],
},
}],
spawn_over_time: vec![SpawnOverTime {
num_agents: num_agents,
start_time: Time::START_OF_DAY,
stop_time: Time::START_OF_DAY + Duration::seconds(5.0),
start_from_neighborhood: "_everywhere_".to_string(),
goal: OriginDestination::Neighborhood("_everywhere_".to_string()),
percent_biking: 0.5,
percent_use_transit: 0.5,
}],
border_spawn_over_time: Vec::new(),
population: Population {
people: Vec::new(),
individ_parked_cars: BTreeMap::new(),
},
}
}
@ -313,439 +158,8 @@ impl Scenario {
}
}
impl SpawnOverTime {
fn spawn_agent(
&self,
rng: &mut XorShiftRng,
sim: &mut Sim,
spawner: &mut TripSpawner,
reserved_cars: &mut HashSet<CarID>,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
timer: &mut Timer,
) {
let spawn_time = rand_time(rng, self.start_time, self.stop_time);
// Note that it's fine for agents to start/end at the same building. Later we might
// want a better assignment of people per household, or workers per office building.
let from_bldg = *neighborhoods[&self.start_from_neighborhood]
.buildings
.choose(rng)
.unwrap();
// What mode?
if let Some(parked_car) = sim
.get_parked_cars_by_owner(from_bldg)
.into_iter()
.find(|p| !reserved_cars.contains(&p.vehicle.id))
{
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Car, map, &neighborhoods, rng, timer)
{
reserved_cars.insert(parked_car.vehicle.id);
let spot = parked_car.spot;
spawner.schedule_trip(
sim.random_person(),
spawn_time,
TripSpec::UsingParkedCar {
start: SidewalkSpot::building(from_bldg, map),
spot,
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
map,
sim,
);
return;
}
}
if rng.gen_bool(self.percent_biking) {
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Bike, map, &neighborhoods, rng, timer)
{
let start_at = map.get_b(from_bldg).sidewalk();
// TODO Just start biking on the other side of the street if the sidewalk
// is on a one-way. Or at least warn.
if map
.get_parent(start_at)
.sidewalk_to_bike(start_at)
.is_some()
{
let ok = if let DrivingGoal::ParkNear(to_bldg) = goal {
let end_at = map.get_b(to_bldg).sidewalk();
map.get_parent(end_at).sidewalk_to_bike(end_at).is_some()
&& start_at != end_at
} else {
true
};
if ok {
spawner.schedule_trip(
sim.random_person(),
spawn_time,
TripSpec::UsingBike {
start: SidewalkSpot::building(from_bldg, map),
vehicle: Scenario::rand_bike(rng),
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
map,
sim,
);
return;
}
}
}
}
if let Some(goal) = self.goal.pick_walking_goal(map, &neighborhoods, rng, timer) {
let start_spot = SidewalkSpot::building(from_bldg, map);
if start_spot == goal {
timer.warn("Skipping walking trip between same two buildings".to_string());
return;
}
if rng.gen_bool(self.percent_use_transit) {
// TODO This throws away some work. It also sequentially does expensive
// work right here.
if let Some((stop1, stop2, route)) =
map.should_use_transit(start_spot.sidewalk_pos, goal.sidewalk_pos)
{
spawner.schedule_trip(
sim.random_person(),
spawn_time,
TripSpec::UsingTransit {
start: start_spot,
route,
stop1,
stop2,
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
map,
sim,
);
return;
}
}
spawner.schedule_trip(
sim.random_person(),
spawn_time,
TripSpec::JustWalking {
start: start_spot,
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
map,
sim,
);
return;
}
timer.warn(format!("Couldn't fulfill {:?} at all", self));
}
}
impl BorderSpawnOverTime {
fn spawn_peds(
&self,
rng: &mut XorShiftRng,
spawner: &mut TripSpawner,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
sim: &mut Sim,
timer: &mut Timer,
) {
if self.num_peds == 0 {
return;
}
let start = if let Some(s) =
SidewalkSpot::start_at_border(self.start_from_border.src_i(map), map)
{
s
} else {
timer.warn(format!(
"Can't start_at_border for {} without sidewalk",
self.start_from_border
));
return;
};
for _ in 0..self.num_peds {
let spawn_time = rand_time(rng, self.start_time, self.stop_time);
if let Some(goal) = self.goal.pick_walking_goal(map, &neighborhoods, rng, timer) {
if rng.gen_bool(self.percent_use_transit) {
// TODO This throws away some work. It also sequentially does expensive
// work right here.
if let Some((stop1, stop2, route)) =
map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos)
{
spawner.schedule_trip(
sim.random_person(),
spawn_time,
TripSpec::UsingTransit {
start: start.clone(),
route,
stop1,
stop2,
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
map,
sim,
);
continue;
}
}
spawner.schedule_trip(
sim.random_person(),
spawn_time,
TripSpec::JustWalking {
start: start.clone(),
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
map,
sim,
);
}
}
}
fn spawn_cars(
&self,
rng: &mut XorShiftRng,
spawner: &mut TripSpawner,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
sim: &mut Sim,
timer: &mut Timer,
) {
if self.num_cars == 0 {
return;
}
let lanes = pick_starting_lanes(
self.start_from_border.lanes(PathConstraints::Car, map),
false,
map,
);
if lanes.is_empty() {
timer.warn(format!(
"Can't start {} cars at border for {}",
self.num_cars, self.start_from_border
));
return;
};
for _ in 0..self.num_cars {
let spawn_time = rand_time(rng, self.start_time, self.stop_time);
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Car, map, &neighborhoods, rng, timer)
{
let vehicle = Scenario::rand_car(rng);
spawner.schedule_trip(
sim.random_person(),
spawn_time,
TripSpec::CarAppearing {
start_pos: Position::new(*lanes.choose(rng).unwrap(), vehicle.length),
vehicle_spec: vehicle,
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
map,
sim,
);
}
}
}
fn spawn_bikes(
&self,
rng: &mut XorShiftRng,
spawner: &mut TripSpawner,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
map: &Map,
sim: &mut Sim,
timer: &mut Timer,
) {
if self.num_bikes == 0 {
return;
}
let lanes = pick_starting_lanes(
self.start_from_border.lanes(PathConstraints::Bike, map),
true,
map,
);
if lanes.is_empty() {
timer.warn(format!(
"Can't start {} bikes at border for {}",
self.num_bikes, self.start_from_border
));
return;
};
for _ in 0..self.num_bikes {
let spawn_time = rand_time(rng, self.start_time, self.stop_time);
if let Some(goal) =
self.goal
.pick_driving_goal(PathConstraints::Bike, map, &neighborhoods, rng, timer)
{
let bike = Scenario::rand_bike(rng);
spawner.schedule_trip(
sim.random_person(),
spawn_time,
TripSpec::CarAppearing {
start_pos: Position::new(*lanes.choose(rng).unwrap(), bike.length),
vehicle_spec: bike,
goal,
ped_speed: Scenario::rand_ped_speed(rng),
},
map,
sim,
);
}
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum OriginDestination {
Neighborhood(String),
EndOfRoad(DirectedRoadID),
GotoBldg(BuildingID),
}
impl OriginDestination {
fn pick_driving_goal(
&self,
constraints: PathConstraints,
map: &Map,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
rng: &mut XorShiftRng,
timer: &mut Timer,
) -> Option<DrivingGoal> {
match self {
OriginDestination::Neighborhood(ref n) => Some(DrivingGoal::ParkNear(
*neighborhoods[n].buildings.choose(rng).unwrap(),
)),
OriginDestination::GotoBldg(b) => Some(DrivingGoal::ParkNear(*b)),
OriginDestination::EndOfRoad(dr) => {
let goal = DrivingGoal::end_at_border(*dr, constraints, map);
if goal.is_none() {
timer.warn(format!(
"Can't spawn a {:?} ending at border {}; no appropriate lanes there",
constraints, dr
));
}
goal
}
}
}
fn pick_walking_goal(
&self,
map: &Map,
neighborhoods: &HashMap<String, FullNeighborhoodInfo>,
rng: &mut XorShiftRng,
timer: &mut Timer,
) -> Option<SidewalkSpot> {
match self {
OriginDestination::Neighborhood(ref n) => Some(SidewalkSpot::building(
*neighborhoods[n].buildings.choose(rng).unwrap(),
map,
)),
OriginDestination::EndOfRoad(dr) => {
let goal = SidewalkSpot::end_at_border(dr.dst_i(map), map);
if goal.is_none() {
timer.warn(format!("Can't end_at_border for {} without a sidewalk", dr));
}
goal
}
OriginDestination::GotoBldg(b) => Some(SidewalkSpot::building(*b, map)),
}
}
}
fn seed_parked_cars(
sim: &mut Sim,
cars_per_building: &WeightedUsizeChoice,
owner_buildings: &Vec<BuildingID>,
neighborhoods_roads: &BTreeSet<RoadID>,
base_rng: &mut XorShiftRng,
map: &Map,
timer: &mut Timer,
) {
// Track the available parking spots per road, only for the roads in the appropriate
// neighborhood.
let mut total_spots = 0;
let mut open_spots_per_road: BTreeMap<RoadID, Vec<ParkingSpot>> = BTreeMap::new();
for id in neighborhoods_roads {
let r = map.get_r(*id);
let mut spots: Vec<ParkingSpot> = Vec::new();
for (lane, _) in r
.children_forwards
.iter()
.chain(r.children_backwards.iter())
{
spots.extend(sim.get_free_spots(*lane));
}
total_spots += spots.len();
spots.shuffle(&mut fork_rng(base_rng));
open_spots_per_road.insert(r.id, spots);
}
let mut new_cars = 0;
let mut ok = true;
timer.start_iter("seed parked cars for buildings", owner_buildings.len());
for b in owner_buildings {
timer.next();
if !ok {
continue;
}
for _ in 0..cars_per_building.sample(base_rng) {
let mut forked_rng = fork_rng(base_rng);
if let Some(spot) = find_spot_near_building(
*b,
&mut open_spots_per_road,
neighborhoods_roads,
map,
timer,
) {
sim.seed_parked_car(Scenario::rand_car(&mut forked_rng), spot, Some(*b));
new_cars += 1;
} else {
// TODO This should be more critical, but neighborhoods can currently contain a
// building, but not even its road, so this is inevitable.
timer.warn(format!(
"No room to seed parked cars. {} total spots, {:?} of {} buildings requested, \
{} new cars so far. Searched from {}",
total_spots,
cars_per_building,
owner_buildings.len(),
new_cars,
b
));
ok = false;
break;
}
}
}
timer.note(format!(
"Seeded {} of {} parking spots with cars, leaving {} buildings without cars",
new_cars,
total_spots,
owner_buildings.len() - new_cars
));
}
fn seed_individ_parked_cars(
individ_parked_cars: Vec<(BuildingID, usize)>,
parked_cars_per_bldg: Vec<(BuildingID, usize)>,
sim: &mut Sim,
map: &Map,
base_rng: &mut XorShiftRng,
@ -765,27 +179,20 @@ fn seed_individ_parked_cars(
for spots in open_spots_per_road.values_mut() {
spots.shuffle(base_rng);
}
let all_roads = map
.all_roads()
.iter()
.map(|r| r.id)
.collect::<BTreeSet<_>>();
timer.start_iter("seed individual parked cars", individ_parked_cars.len());
timer.start_iter("seed parked cars", parked_cars_per_bldg.len());
let mut ok = true;
for (b, cnt) in individ_parked_cars {
for (b, cnt) in parked_cars_per_bldg {
timer.next();
if !ok {
continue;
}
for _ in 0..cnt {
// TODO Fork?
if let Some(spot) =
find_spot_near_building(b, &mut open_spots_per_road, &all_roads, map, timer)
{
if let Some(spot) = find_spot_near_building(b, &mut open_spots_per_road, map, timer) {
sim.seed_parked_car(Scenario::rand_car(base_rng), spot, Some(b));
} else {
timer.warn("Not enough room to seed individual parked cars.".to_string());
timer.warn("Not enough room to seed parked cars.".to_string());
ok = false;
break;
}
@ -799,7 +206,6 @@ fn seed_individ_parked_cars(
fn find_spot_near_building(
b: BuildingID,
open_spots_per_road: &mut BTreeMap<RoadID, Vec<ParkingSpot>>,
neighborhoods_roads: &BTreeSet<RoadID>,
map: &Map,
timer: &mut Timer,
) -> Option<ParkingSpot> {
@ -829,8 +235,7 @@ fn find_spot_near_building(
}
for next_r in map.get_next_roads(r).into_iter() {
// Don't floodfill out of the neighborhood
if !visited.contains(&next_r) && neighborhoods_roads.contains(&next_r) {
if !visited.contains(&next_r) {
roads_queue.push_back(next_r);
visited.insert(next_r);
}
@ -838,32 +243,6 @@ fn find_spot_near_building(
}
}
fn rand_time(rng: &mut XorShiftRng, low: Time, high: Time) -> Time {
assert!(high > low);
Time::START_OF_DAY + Duration::seconds(rng.gen_range(low.inner_seconds(), high.inner_seconds()))
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct IndividTrip {
pub depart: Time,
pub trip: SpawnTrip,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum SpawnTrip {
CarAppearing {
// TODO Replace start with building|border
start: Position,
goal: DrivingGoal,
// For bikes starting at a border, use CarAppearing. UsingBike implies a walk->bike trip.
is_bike: bool,
},
MaybeUsingParkedCar(BuildingID, DrivingGoal),
UsingBike(SidewalkSpot, DrivingGoal),
JustWalking(SidewalkSpot, SidewalkSpot),
UsingTransit(SidewalkSpot, SidewalkSpot, BusRouteID, BusStopID, BusStopID),
}
impl SpawnTrip {
pub fn to_trip_spec(self, rng: &mut XorShiftRng) -> TripSpec {
match self {
@ -909,35 +288,3 @@ impl SpawnTrip {
}
}
}
fn pick_starting_lanes(mut lanes: Vec<LaneID>, is_bike: bool, map: &Map) -> Vec<LaneID> {
let min_len = if is_bike { BIKE_LENGTH } else { MAX_CAR_LENGTH };
lanes.retain(|l| map.get_l(*l).length() > min_len);
if is_bike {
// If there's a choice between bike lanes and otherwise, always use the bike lanes.
let bike_lanes = lanes
.iter()
.filter(|l| map.get_l(**l).is_biking())
.cloned()
.collect::<Vec<LaneID>>();
if !bike_lanes.is_empty() {
lanes = bike_lanes;
}
}
lanes
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Population {
pub people: Vec<PersonSpec>,
pub individ_parked_cars: BTreeMap<BuildingID, usize>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct PersonSpec {
pub id: PersonID,
pub home: Option<BuildingID>,
pub trips: Vec<IndividTrip>,
}