diff --git a/Cargo.lock b/Cargo.lock index fafebdc215..7b684efaf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3167,14 +3167,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] @@ -3206,15 +3205,6 @@ dependencies = [ "rand", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "rand_xorshift" version = "0.3.0" @@ -3923,6 +3913,8 @@ dependencies = [ "geom", "log", "map_model", + "rand", + "rand_xorshift", "serde", ] diff --git a/apps/game/src/sandbox/gameplay/play_scenario.rs b/apps/game/src/sandbox/gameplay/play_scenario.rs index 2cdc4c0881..3f392c4714 100644 --- a/apps/game/src/sandbox/gameplay/play_scenario.rs +++ b/apps/game/src/sandbox/gameplay/play_scenario.rs @@ -259,12 +259,19 @@ impl EditScenarioModifiers { .build_def(ctx), ); rows.push(Widget::row(vec![ - Spinner::widget(ctx, "repeat_days", (2, 14), 2_usize, 1), + Spinner::widget(ctx, "repeat_days", (2, 14), 2, 1), ctx.style() .btn_outline .text("Repeat schedule multiple days") .build_def(ctx), ])); + rows.push(Widget::row(vec![ + Spinner::widget(ctx, "repeat_days_noise", (2, 14), 2_usize, 1), + ctx.style() + .btn_outline + .text("Repeat schedule multiple days with +/- 10 minutes of noise") + .build_def(ctx), + ])); rows.push(Widget::horiz_separator(ctx, 1.0)); rows.push( Widget::row(vec![ @@ -361,6 +368,17 @@ impl State for EditScenarioModifiers { self.modifiers.clone(), )); } + "Repeat schedule multiple days with +/- 10 minutes of noise" => { + self.modifiers.push(ScenarioModifier::RepeatDaysNoise { + days: self.panel.spinner("repeat_days_noise"), + departure_time_noise: Duration::minutes(10), + }); + return Transition::Replace(EditScenarioModifiers::new_state( + ctx, + self.scenario_name.clone(), + self.modifiers.clone(), + )); + } x => { if let Some(x) = x.strip_prefix("delete modifier ") { self.modifiers.remove(x.parse::().unwrap() - 1); diff --git a/apps/game/src/sandbox/mod.rs b/apps/game/src/sandbox/mod.rs index 410b5cb8c4..9a51550cb7 100644 --- a/apps/game/src/sandbox/mod.rs +++ b/apps/game/src/sandbox/mod.rs @@ -549,18 +549,22 @@ impl State for SandboxLoader { ctx.loading_screen("instantiate scenario", |_, timer| { app.primary.scenario = Some(scenario.clone()); + // Use the same RNG as we apply scenario modifiers and instantiate the + // scenario. One unexpected effect will be that parked car seeding (during + // scenario instantiation) may spuriously change if a scenario modifier + // uses the RNG. This is at least consistent with the tests, headless mode, + // and instantiating a scenario from CLI flags. + let mut rng = app.primary.current_flags.sim_flags.make_rng(); + if let GameplayMode::PlayScenario(_, _, ref modifiers) = self.mode { for m in modifiers { - scenario = m.apply(&app.primary.map, scenario); + scenario = m.apply(&app.primary.map, scenario, &mut rng); } } - app.primary.sim.instantiate( - &scenario, - &app.primary.map, - &mut app.primary.current_flags.sim_flags.make_rng(), - timer, - ); + app.primary + .sim + .instantiate(&scenario, &app.primary.map, &mut rng, timer); app.primary .sim .tiny_step(&app.primary.map, &mut app.primary.sim_cb); @@ -572,6 +576,8 @@ impl State for SandboxLoader { secondary.sim.instantiate( &scenario, &secondary.map, + // Start fresh here. This will match up with the primary sim, + // unless modifiers used the RNG &mut secondary.current_flags.sim_flags.make_rng(), timer, ); diff --git a/cli/src/augment_scenario.rs b/cli/src/augment_scenario.rs index 7760f64b66..c40806c4b5 100644 --- a/cli/src/augment_scenario.rs +++ b/cli/src/augment_scenario.rs @@ -29,7 +29,7 @@ pub fn run( } for m in modifiers { - scenario = m.apply(&map, scenario); + scenario = m.apply(&map, scenario, &mut rng); } if should_delete_cancelled_trips { diff --git a/headless/src/main.rs b/headless/src/main.rs index 5bbd62854b..3f95e47c6c 100644 --- a/headless/src/main.rs +++ b/headless/src/main.rs @@ -510,11 +510,11 @@ impl LoadSim { map.recalculate_pathfinding_after_edits(timer); } + let mut rng = XorShiftRng::seed_from_u64(self.rng_seed); for m in &self.modifiers { - scenario = m.apply(&map, scenario); + scenario = m.apply(&map, scenario, &mut rng); } - let mut rng = XorShiftRng::seed_from_u64(self.rng_seed); let mut sim = Sim::new(&map, self.opts.clone()); sim.instantiate(&scenario, &map, &mut rng, timer); diff --git a/sim/src/make/load.rs b/sim/src/make/load.rs index 930e439c51..75a5ef1d75 100644 --- a/sim/src/make/load.rs +++ b/sim/src/make/load.rs @@ -108,7 +108,7 @@ impl SimFlags { let map = Map::load_synchronously(scenario.map_name.path(), timer); for m in &self.scenario_modifiers { - scenario = m.apply(&map, scenario); + scenario = m.apply(&map, scenario, &mut rng); } if opts.run_name == "unnamed" { diff --git a/synthpop/Cargo.toml b/synthpop/Cargo.toml index ea956b2db9..58107acb21 100644 --- a/synthpop/Cargo.toml +++ b/synthpop/Cargo.toml @@ -10,4 +10,6 @@ anyhow = "1.0.38" geom = { path = "../geom" } log = "0.4.14" map_model = { path = "../map_model" } +rand = "0.8.5" +rand_xorshift = "0.3.0" serde = "1.0.123" diff --git a/synthpop/src/modifier.rs b/synthpop/src/modifier.rs index a41efd27c8..6d1a7ea181 100644 --- a/synthpop/src/modifier.rs +++ b/synthpop/src/modifier.rs @@ -1,5 +1,9 @@ +extern crate rand; + use std::collections::BTreeSet; +use rand::Rng; +use rand_xorshift::XorShiftRng; use serde::{Deserialize, Serialize}; use abstutil::Timer; @@ -12,6 +16,10 @@ use crate::{Scenario, TripMode}; #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)] pub enum ScenarioModifier { RepeatDays(usize), + RepeatDaysNoise { + days: usize, + departure_time_noise: Duration, + }, ChangeMode { pct_ppl: usize, departure_filter: (Time, Time), @@ -26,9 +34,13 @@ pub enum ScenarioModifier { impl ScenarioModifier { /// If this modifies scenario_name, then that means prebaked results don't match up and /// shouldn't be used. - pub fn apply(&self, map: &Map, mut s: Scenario) -> Scenario { + pub fn apply(&self, map: &Map, mut s: Scenario, rng: &mut XorShiftRng) -> Scenario { match self { - ScenarioModifier::RepeatDays(n) => repeat_days(s, *n), + ScenarioModifier::RepeatDays(n) => repeat_days(s, *n, None, rng), + ScenarioModifier::RepeatDaysNoise { + days, + departure_time_noise, + } => repeat_days(s, *days, Some(*departure_time_noise), rng), ScenarioModifier::ChangeMode { pct_ppl, departure_filter, @@ -90,6 +102,13 @@ impl ScenarioModifier { pub fn describe(&self) -> String { match self { ScenarioModifier::RepeatDays(n) => format!("repeat the entire day {} times", n), + ScenarioModifier::RepeatDaysNoise { + days, + departure_time_noise, + } => format!( + "repeat the entire day {} times with +/- {} noise on each departure", + days, departure_time_noise + ), ScenarioModifier::ChangeMode { pct_ppl, to_mode, @@ -117,7 +136,12 @@ impl ScenarioModifier { // // The bigger problem is that any people that seem to require multiple cars... will wind up // needing LOTS of cars. -fn repeat_days(mut s: Scenario, days: usize) -> Scenario { +fn repeat_days( + mut s: Scenario, + days: usize, + noise: Option, + rng: &mut XorShiftRng, +) -> Scenario { s.scenario_name = format!("{} (repeated {} days)", s.scenario_name, days); for person in &mut s.people { let mut trips = Vec::new(); @@ -126,6 +150,13 @@ fn repeat_days(mut s: Scenario, days: usize) -> Scenario { for trip in &person.trips { let mut new = trip.clone(); new.depart += offset; + if let Some(noise_v) = noise { + // + or - noise_v + let noise_rnd = Duration::seconds( + rng.gen_range((0.0)..=(2.0 * noise_v.inner_seconds() as f64)), + ) - noise_v; + new.depart = new.depart.clamped_sub(noise_rnd); + } new.modified = true; trips.push(new); }