mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
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:
parent
9138097e9e
commit
70875d104d
@ -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])
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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| {
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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()]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
523
sim/src/make/generator.rs
Normal 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
|
||||
}
|
@ -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};
|
||||
|
@ -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>,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user