Skip to content

Commit

Permalink
Feature to repeat days with noise on departure (#926)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
IliasN and dabreegster committed Jun 14, 2022
1 parent 34487a3 commit d3333d8
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 27 deletions.
16 changes: 4 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion apps/game/src/sandbox/gameplay/play_scenario.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![
Expand Down Expand Up @@ -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);
Expand Down
20 changes: 13 additions & 7 deletions apps/game/src/sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
);
Expand Down
2 changes: 1 addition & 1 deletion cli/src/augment_scenario.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions headless/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion sim/src/make/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
2 changes: 2 additions & 0 deletions synthpop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
37 changes: 34 additions & 3 deletions synthpop/src/modifier.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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),
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
Expand Down

0 comments on commit d3333d8

Please sign in to comment.