Feature to repeat days with noise on departure (#926)

* Added repeat days with noise function & enum

* Added widgets and controls

* Modified description

* Refactor to have an Option<usize> in signature instead of a whole new similar function

* Added noise parameter + xor random shift + clamped substraction

* Added ref to the XorShiftRng to ScenarioModifiers::apply calls

* Added noise to pushed ScenarioModifier

* Finish up the PR:

- consistently reuse the RNG in the UI like tests/headless
- slight style tweaks

Co-authored-by: Dustin Carlino <dabreegster@gmail.com>
This commit is contained in:
Ilias 2022-06-14 19:23:15 +02:00 committed by GitHub
parent 34487a3322
commit d3333d813d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 76 additions and 27 deletions

16
Cargo.lock generated
View File

@ -3167,14 +3167,13 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.4" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha",
"rand_core", "rand_core",
"rand_hc",
] ]
[[package]] [[package]]
@ -3206,15 +3205,6 @@ dependencies = [
"rand", "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]] [[package]]
name = "rand_xorshift" name = "rand_xorshift"
version = "0.3.0" version = "0.3.0"
@ -3923,6 +3913,8 @@ dependencies = [
"geom", "geom",
"log", "log",
"map_model", "map_model",
"rand",
"rand_xorshift",
"serde", "serde",
] ]

View File

@ -259,12 +259,19 @@ impl EditScenarioModifiers {
.build_def(ctx), .build_def(ctx),
); );
rows.push(Widget::row(vec![ 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() ctx.style()
.btn_outline .btn_outline
.text("Repeat schedule multiple days") .text("Repeat schedule multiple days")
.build_def(ctx), .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::horiz_separator(ctx, 1.0));
rows.push( rows.push(
Widget::row(vec![ Widget::row(vec![
@ -361,6 +368,17 @@ impl State<App> for EditScenarioModifiers {
self.modifiers.clone(), 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 => { x => {
if let Some(x) = x.strip_prefix("delete modifier ") { if let Some(x) = x.strip_prefix("delete modifier ") {
self.modifiers.remove(x.parse::<usize>().unwrap() - 1); self.modifiers.remove(x.parse::<usize>().unwrap() - 1);

View File

@ -549,18 +549,22 @@ impl State<App> for SandboxLoader {
ctx.loading_screen("instantiate scenario", |_, timer| { ctx.loading_screen("instantiate scenario", |_, timer| {
app.primary.scenario = Some(scenario.clone()); 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 { if let GameplayMode::PlayScenario(_, _, ref modifiers) = self.mode {
for m in modifiers { for m in modifiers {
scenario = m.apply(&app.primary.map, scenario); scenario = m.apply(&app.primary.map, scenario, &mut rng);
} }
} }
app.primary.sim.instantiate( app.primary
&scenario, .sim
&app.primary.map, .instantiate(&scenario, &app.primary.map, &mut rng, timer);
&mut app.primary.current_flags.sim_flags.make_rng(),
timer,
);
app.primary app.primary
.sim .sim
.tiny_step(&app.primary.map, &mut app.primary.sim_cb); .tiny_step(&app.primary.map, &mut app.primary.sim_cb);
@ -572,6 +576,8 @@ impl State<App> for SandboxLoader {
secondary.sim.instantiate( secondary.sim.instantiate(
&scenario, &scenario,
&secondary.map, &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(), &mut secondary.current_flags.sim_flags.make_rng(),
timer, timer,
); );

View File

@ -29,7 +29,7 @@ pub fn run(
} }
for m in modifiers { for m in modifiers {
scenario = m.apply(&map, scenario); scenario = m.apply(&map, scenario, &mut rng);
} }
if should_delete_cancelled_trips { if should_delete_cancelled_trips {

View File

@ -510,11 +510,11 @@ impl LoadSim {
map.recalculate_pathfinding_after_edits(timer); map.recalculate_pathfinding_after_edits(timer);
} }
let mut rng = XorShiftRng::seed_from_u64(self.rng_seed);
for m in &self.modifiers { 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()); let mut sim = Sim::new(&map, self.opts.clone());
sim.instantiate(&scenario, &map, &mut rng, timer); sim.instantiate(&scenario, &map, &mut rng, timer);

View File

@ -108,7 +108,7 @@ impl SimFlags {
let map = Map::load_synchronously(scenario.map_name.path(), timer); let map = Map::load_synchronously(scenario.map_name.path(), timer);
for m in &self.scenario_modifiers { for m in &self.scenario_modifiers {
scenario = m.apply(&map, scenario); scenario = m.apply(&map, scenario, &mut rng);
} }
if opts.run_name == "unnamed" { if opts.run_name == "unnamed" {

View File

@ -10,4 +10,6 @@ anyhow = "1.0.38"
geom = { path = "../geom" } geom = { path = "../geom" }
log = "0.4.14" log = "0.4.14"
map_model = { path = "../map_model" } map_model = { path = "../map_model" }
rand = "0.8.5"
rand_xorshift = "0.3.0"
serde = "1.0.123" serde = "1.0.123"

View File

@ -1,5 +1,9 @@
extern crate rand;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use abstutil::Timer; use abstutil::Timer;
@ -12,6 +16,10 @@ use crate::{Scenario, TripMode};
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub enum ScenarioModifier { pub enum ScenarioModifier {
RepeatDays(usize), RepeatDays(usize),
RepeatDaysNoise {
days: usize,
departure_time_noise: Duration,
},
ChangeMode { ChangeMode {
pct_ppl: usize, pct_ppl: usize,
departure_filter: (Time, Time), departure_filter: (Time, Time),
@ -26,9 +34,13 @@ pub enum ScenarioModifier {
impl ScenarioModifier { impl ScenarioModifier {
/// If this modifies scenario_name, then that means prebaked results don't match up and /// If this modifies scenario_name, then that means prebaked results don't match up and
/// shouldn't be used. /// 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 { 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 { ScenarioModifier::ChangeMode {
pct_ppl, pct_ppl,
departure_filter, departure_filter,
@ -90,6 +102,13 @@ impl ScenarioModifier {
pub fn describe(&self) -> String { pub fn describe(&self) -> String {
match self { match self {
ScenarioModifier::RepeatDays(n) => format!("repeat the entire day {} times", n), 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 { ScenarioModifier::ChangeMode {
pct_ppl, pct_ppl,
to_mode, to_mode,
@ -117,7 +136,12 @@ impl ScenarioModifier {
// //
// The bigger problem is that any people that seem to require multiple cars... will wind up // The bigger problem is that any people that seem to require multiple cars... will wind up
// needing LOTS of cars. // needing LOTS of cars.
fn repeat_days(mut s: Scenario, days: usize) -> Scenario { fn repeat_days(
mut s: Scenario,
days: usize,
noise: Option<Duration>,
rng: &mut XorShiftRng,
) -> Scenario {
s.scenario_name = format!("{} (repeated {} days)", s.scenario_name, days); s.scenario_name = format!("{} (repeated {} days)", s.scenario_name, days);
for person in &mut s.people { for person in &mut s.people {
let mut trips = Vec::new(); let mut trips = Vec::new();
@ -126,6 +150,13 @@ fn repeat_days(mut s: Scenario, days: usize) -> Scenario {
for trip in &person.trips { for trip in &person.trips {
let mut new = trip.clone(); let mut new = trip.clone();
new.depart += offset; 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; new.modified = true;
trips.push(new); trips.push(new);
} }