mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 20:29:04 +03:00
forking RNGs to keep SeedParking consistent
This commit is contained in:
parent
21dcb14123
commit
77786c9483
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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> {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user