forking RNGs to keep SeedParking consistent

This commit is contained in:
Dustin Carlino 2018-10-16 10:29:17 -07:00
parent 21dcb14123
commit 77786c9483
6 changed files with 123 additions and 112 deletions

View File

@ -108,12 +108,39 @@ more things invariant.
Alright, the deviations are still starting too early!
- Swapping shouldn't show different parked cars when the parking lane is present in both sims... or should it?
- If 50% of spots in a neighborhood need to be initially populated, then more or less options for those DOES affect it.
- the problem seems like "seed 50% of parked car spots in this area" is too vague. but theres also a desire that scenarios remain human-readable, high-level.
- Peds are picking very different parked cars in the neighborhood to go drive, causing big deviations early on.
Can we avoid storing trip/parked car mappings in the scenario?
- if peds pick the parked car closest to their start building... maybe
Maybe it's time to rethink how parked cars are matched up to trips...
### Idea: forking RNGs
50% of spots filled isn't really accurate -- we're saying for every spot, flip
a coin with some weight. The flips are independent. We can regain an amount of
determinism by forking RNGs -- for every single lane in the map, use the main
RNG to generate a new RNG. Use that new RNG only if it really is a parking
lane.
Cool, much closer to working! Spots are consistently filled out or not. Car
colors are different, because car IDs are different. Making CarIDs consistent
would require changing them to have a stable form -- namely, their original
parking spot (lane ID and an offset). We _could_ do that...
But also wait, ped IDs are a bit different in some cases, and a trip is missing
entirely... huh?
Ah, we call gen_range on different inputs. Not sure why that throws off IDs
though... Can we fork RNG for that too?
Problems:
- CarIDs are different, could make them be original parking spot
- Missing trip ID, different ped IDs
- gen_range on different inputs
Alright, now how do peds picking a car work?
## Parked cars and ownership

View File

@ -93,6 +93,7 @@ impl SimController {
let mut the_secondary = secondary.take();
the_secondary.as_mut().map(|s| mem::swap(primary, s));
*secondary = the_secondary;
// TODO Any time the screen changes, need to recalculate mouseover state
}
} else {
// Interactively spawning stuff would ruin an A/B test, don't allow it

View File

@ -1,7 +1,6 @@
use abstutil;
use control::ControlMap;
use flame;
use geom::Polygon;
use map_model::{BuildingID, BusRoute, BusStopID, LaneID, Map};
use rand::Rng;
use std::collections::VecDeque;
@ -144,7 +143,7 @@ impl Sim {
// Spawning helpers
impl Sim {
pub fn small_spawn(&mut self, map: &Map) {
self.seed_parked_cars(None, 0.5);
self.seed_parked_cars(map.all_lanes().iter().map(|l| l.id).collect(), 0.5);
self.seed_walking_trips(&map, 100);
self.seed_driving_trips(&map, 100);
@ -163,14 +162,14 @@ impl Sim {
);*/ }
pub fn big_spawn(&mut self, map: &Map) {
self.seed_parked_cars(None, 0.95);
self.seed_parked_cars(map.all_lanes().iter().map(|l| l.id).collect(), 0.95);
self.seed_walking_trips(&map, 1000);
self.seed_driving_trips(&map, 1000);
}
pub fn seed_parked_cars(&mut self, in_poly: Option<&Polygon>, percent: f64) {
pub fn seed_parked_cars(&mut self, in_lanes: Vec<LaneID>, percent: f64) {
self.spawner
.seed_parked_cars(percent, in_poly, &mut self.parking_state, &mut self.rng);
.seed_parked_cars(percent, in_lanes, &mut self.parking_state, &mut self.rng);
}
pub fn seed_bus_route(&mut self, route: &BusRoute, map: &Map) -> Vec<CarID> {

View File

@ -47,19 +47,12 @@ impl ParkingSimState {
.collect()
}
pub fn get_all_free_spots(&self, in_poly: Option<&Polygon>) -> Vec<ParkingSpot> {
pub fn get_free_spots(&self, lane: LaneID) -> Vec<ParkingSpot> {
let l = &self.lanes[lane.0];
let mut spots: Vec<ParkingSpot> = Vec::new();
for l in &self.lanes {
for (idx, maybe_occupant) in l.occupants.iter().enumerate() {
if maybe_occupant.is_none() {
// Just match based on the front of the spot
if in_poly
.map(|p| p.contains_pt(l.spots[idx].pos))
.unwrap_or(true)
{
spots.push(ParkingSpot::new(l.id, idx));
}
}
for (idx, maybe_occupant) in l.occupants.iter().enumerate() {
if maybe_occupant.is_none() {
spots.push(ParkingSpot::new(l.id, idx));
}
}
spots

View File

@ -1,6 +1,6 @@
use abstutil;
use geom::{Polygon, Pt2D};
use map_model::{BuildingID, Map};
use map_model::{BuildingID, LaneID, Map};
use rand::Rng;
use std::collections::HashMap;
use {ParkedCar, Sim, Tick};
@ -26,16 +26,6 @@ pub struct SpawnOverTime {
pub go_to_neighborhood: String,
}
// One SpawnOverTime produces many of these. This intermediate structure is just here to make some
// RNG calls at the beginning of scenario instantiation that are independent of map edits.
struct Spawn {
spawn_time: Tick,
from_bldg: BuildingID,
to_bldg: BuildingID,
drive: bool,
start_from_neighborhood: String,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct SeedParkedCars {
pub neighborhood: String,
@ -64,6 +54,19 @@ impl Neighborhood {
results
}
// TODO This should use quadtrees and/or not just match the first point of each lane.
fn find_matching_lanes(&self, map: &Map) -> Vec<LaneID> {
let poly = Polygon::new(&self.points);
let mut results: Vec<LaneID> = Vec::new();
for l in map.all_lanes() {
if poly.contains_pt(l.first_pt()) {
results.push(l.id);
}
}
results
}
pub fn save(&self) {
abstutil::save_object("neighborhoods", &self.map_name, &self.name, self);
}
@ -91,8 +94,22 @@ impl Scenario {
.insert(name.to_string(), neighborhood.find_matching_buildings(map));
}
// spawn_list doesn't depend on MapEdits, so the RNG isn't sensitive yet.
let mut spawn_list: Vec<Spawn> = Vec::new();
for s in &self.seed_parked_cars {
sim.seed_parked_cars(
neighborhoods[&s.neighborhood].find_matching_lanes(map),
s.percent_to_fill,
);
}
let mut parked_cars_per_neighborhood: HashMap<String, Vec<ParkedCar>> = HashMap::new();
for (name, neighborhood) in &neighborhoods {
parked_cars_per_neighborhood.insert(
name.to_string(),
sim.parking_state
.get_all_parked_cars(Some(&Polygon::new(&neighborhood.points))),
);
}
for s in &self.spawn_over_time {
for _ in 0..s.num_agents {
// TODO normal distribution, not uniform
@ -107,73 +124,43 @@ impl Scenario {
.rng
.choose(&bldgs_per_neighborhood[&s.go_to_neighborhood])
.unwrap();
let drive = sim.rng.gen_bool(s.percent_drive);
spawn_list.push(Spawn {
spawn_time,
from_bldg,
to_bldg,
drive,
start_from_neighborhood: s.start_from_neighborhood.clone(),
});
}
}
if sim.rng.gen_bool(s.percent_drive) {
if parked_cars_per_neighborhood[&s.start_from_neighborhood].is_empty() {
panic!(
"{} has no parked cars; can't instantiate {}",
s.start_from_neighborhood, self.scenario_name
);
}
// TODO Probably prefer parked cars close to from_bldg, unless the particular
// area is tight on parking. :)
let idx = sim.rng.gen_range(
0,
parked_cars_per_neighborhood[&s.start_from_neighborhood].len(),
);
let parked_car = parked_cars_per_neighborhood
.get_mut(&s.start_from_neighborhood)
.unwrap()
.remove(idx);
// Now do stuff that's sensitive to small map edits. The RNG can get thrown off in an A/B
// test wildly here by adding/removing a parking lane, for example.
for s in &self.seed_parked_cars {
sim.seed_parked_cars(
Some(&Polygon::new(&neighborhoods[&s.neighborhood].points)),
s.percent_to_fill,
);
}
let mut parked_cars_per_neighborhood: HashMap<String, Vec<ParkedCar>> = HashMap::new();
for (name, neighborhood) in &neighborhoods {
parked_cars_per_neighborhood.insert(
name.to_string(),
sim.parking_state
.get_all_parked_cars(Some(&Polygon::new(&neighborhood.points))),
);
}
// Now execute the spawn list
for s in spawn_list.into_iter() {
if s.drive {
if parked_cars_per_neighborhood[&s.start_from_neighborhood].is_empty() {
panic!(
"{} has no parked cars; can't instantiate {}",
s.start_from_neighborhood, self.scenario_name
sim.spawner.start_trip_using_parked_car(
spawn_time,
map,
parked_car,
&sim.parking_state,
from_bldg,
to_bldg,
&mut sim.trips_state,
);
} else {
sim.spawner.start_trip_just_walking(
spawn_time,
map,
from_bldg,
to_bldg,
&mut sim.trips_state,
);
}
// TODO Probably prefer parked cars close to from_bldg, unless the particular
// area is tight on parking. :)
let idx = sim.rng.gen_range(
0,
parked_cars_per_neighborhood[&s.start_from_neighborhood].len(),
);
let parked_car = parked_cars_per_neighborhood
.get_mut(&s.start_from_neighborhood)
.unwrap()
.remove(idx);
sim.spawner.start_trip_using_parked_car(
s.spawn_time,
map,
parked_car,
&sim.parking_state,
s.from_bldg,
s.to_bldg,
&mut sim.trips_state,
);
} else {
sim.spawner.start_trip_just_walking(
s.spawn_time,
map,
s.from_bldg,
s.to_bldg,
&mut sim.trips_state,
);
}
}
}

View File

@ -1,10 +1,9 @@
use abstutil::elapsed_seconds;
use driving::DrivingSimState;
use geom::Polygon;
use kinematics::Vehicle;
use map_model::{BuildingID, BusRoute, BusStopID, LaneID, Map, Pathfinder};
use parking::ParkingSimState;
use rand::Rng;
use rand::{Rng, SeedableRng, XorShiftRng};
use router::Router;
use std::collections::VecDeque;
use std::time::Instant;
@ -203,30 +202,35 @@ impl Spawner {
pub fn seed_parked_cars<R: Rng + ?Sized>(
&mut self,
percent_capacity_to_fill: f64,
in_poly: Option<&Polygon>,
in_lanes: Vec<LaneID>,
parking_sim: &mut ParkingSimState,
rng: &mut R,
base_rng: &mut R,
) {
assert!(percent_capacity_to_fill >= 0.0 && percent_capacity_to_fill <= 1.0);
let mut total_capacity = 0;
let mut new_cars = 0;
for spot in parking_sim.get_all_free_spots(in_poly) {
total_capacity += 1;
if rng.gen_bool(percent_capacity_to_fill) {
new_cars += 1;
let car = CarID(self.car_id_counter);
// TODO since spawning applies during the next step, lots of stuff breaks without
// this :(
parking_sim.add_parked_car(ParkedCar::new(
car,
spot,
Vehicle::generate_typical_car(car, rng),
));
self.car_id_counter += 1;
// Fork a new RNG for each candidate lane. This keeps things more deterministic, invariant
// of lane edits.
for l in in_lanes.into_iter() {
let mut rng = XorShiftRng::from_seed([base_rng.next_u32() as u8; 16]);
for spot in parking_sim.get_free_spots(l) {
total_capacity += 1;
if rng.gen_bool(percent_capacity_to_fill) {
new_cars += 1;
let car = CarID(self.car_id_counter);
// TODO since spawning applies during the next step, lots of stuff breaks without
// this :(
parking_sim.add_parked_car(ParkedCar::new(
car,
spot,
Vehicle::generate_typical_car(car, &mut rng),
));
self.car_id_counter += 1;
}
}
}
info!(
"Seeded {} of {} parking spots with cars",
new_cars, total_capacity