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]]
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",
]

View File

@ -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<App> 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::<usize>().unwrap() - 1);

View File

@ -549,18 +549,22 @@ impl State<App> 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<App> 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,
);

View File

@ -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 {

View File

@ -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);

View File

@ -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" {

View File

@ -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"

View File

@ -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<Duration>,
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);
}