diff --git a/abstutil/Cargo.toml b/abstutil/Cargo.toml index 9102f30ef7..ce74fd608a 100644 --- a/abstutil/Cargo.toml +++ b/abstutil/Cargo.toml @@ -6,7 +6,9 @@ authors = ["Dustin Carlino "] [dependencies] log = "0.4.5" multimap = "0.4.0" +rand = { version = "0.5.1", features = ["serde1"] } serde = "1.0" serde_cbor = "0.8.2" +serde_derive = "1.0" serde_json = "1.0" yansi = "0.4.0" diff --git a/abstutil/src/clone.rs b/abstutil/src/clone.rs index 42367b909d..aa8c31359d 100644 --- a/abstutil/src/clone.rs +++ b/abstutil/src/clone.rs @@ -1,4 +1,5 @@ use std::any::Any; +use WeightedUsizeChoice; // Trick to make a cloneable Any from // https://stackoverflow.com/questions/30353462/how-to-clone-a-struct-storing-a-boxed-trait-object/30353928#30353928. @@ -34,3 +35,4 @@ impl Cloneable for usize {} impl Cloneable for f64 {} impl Cloneable for String {} impl Cloneable for (String, Box) {} +impl Cloneable for WeightedUsizeChoice {} diff --git a/abstutil/src/lib.rs b/abstutil/src/lib.rs index 8cbcb86efd..d5c45c50e6 100644 --- a/abstutil/src/lib.rs +++ b/abstutil/src/lib.rs @@ -1,7 +1,10 @@ extern crate log; extern crate multimap; +extern crate rand; extern crate serde; extern crate serde_cbor; +#[macro_use] +extern crate serde_derive; extern crate serde_json; extern crate yansi; @@ -11,6 +14,7 @@ mod collections; mod error; mod io; mod logs; +mod random; mod time; pub use abst_multimap::MultiMap; @@ -23,6 +27,7 @@ pub use io::{ write_json, FileWithProgress, }; pub use logs::{format_log_record, LogAdapter}; +pub use random::{fork_rng, WeightedUsizeChoice}; pub use time::{elapsed_seconds, Timer}; const PROGRESS_FREQUENCY_SECONDS: f64 = 0.2; diff --git a/abstutil/src/random.rs b/abstutil/src/random.rs new file mode 100644 index 0000000000..b03f308930 --- /dev/null +++ b/abstutil/src/random.rs @@ -0,0 +1,41 @@ +use rand::distributions::{Distribution, Weighted, WeightedChoice}; +use rand::{RngCore, SeedableRng, XorShiftRng}; + +// Need to explain this trick -- basically keeps consistency between two different simulations when +// each one might make slightly different sequences of calls to the RNG. +pub fn fork_rng(base_rng: &mut XorShiftRng) -> XorShiftRng { + XorShiftRng::from_seed([base_rng.next_u32() as u8; 16]) +} + +// Represents the probability of sampling 0, 1, 2, 3... The sum can be anything. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct WeightedUsizeChoice { + pub weights: Vec, +} + +impl WeightedUsizeChoice { + pub fn parse(string: &str) -> Option { + let parts: Vec<&str> = string.split(",").collect(); + if parts.is_empty() { + return None; + } + let mut weights: Vec = Vec::new(); + for x in parts.into_iter() { + let x = x.parse::().ok()?; + weights.push(x); + } + Some(WeightedUsizeChoice { weights }) + } + + pub fn sample(&self, rng: &mut XorShiftRng) -> u32 { + let mut items: Vec> = self + .weights + .iter() + .enumerate() + .map(|(idx, pr)| Weighted { + weight: *pr, + item: idx as u32, + }).collect(); + WeightedChoice::new(&mut items).sample(rng) + } +} diff --git a/editor/src/plugins/mod.rs b/editor/src/plugins/mod.rs index 1a246c334d..273717b3a9 100644 --- a/editor/src/plugins/mod.rs +++ b/editor/src/plugins/mod.rs @@ -28,14 +28,12 @@ pub mod turn_cycler; pub mod warp; use abstutil; +use abstutil::WeightedUsizeChoice; use downcast::Any; use ezgui::{Color, GfxCtx, WrappedWizard}; use map_model::{IntersectionID, Map}; use objects::{Ctx, ID}; -use sim::{ - ABTest, Neighborhood, NeighborhoodBuilder, OriginDestination, Scenario, Tick, - WeightedUsizeChoice, -}; +use sim::{ABTest, Neighborhood, NeighborhoodBuilder, OriginDestination, Scenario, Tick}; use ui::PluginCtx; pub trait Plugin: Any { diff --git a/sim/src/helpers.rs b/sim/src/helpers.rs index 3e7a08d23d..98b04e9c17 100644 --- a/sim/src/helpers.rs +++ b/sim/src/helpers.rs @@ -1,3 +1,4 @@ +use abstutil::WeightedUsizeChoice; use control::ControlMap; use driving::DrivingGoal; use map_model::{BuildingID, BusRoute, BusStopID, LaneID, Map, RoadID}; @@ -5,7 +6,7 @@ use std::collections::{BTreeSet, VecDeque}; use walking::SidewalkSpot; use { BorderSpawnOverTime, CarID, Event, OriginDestination, PedestrianID, RouteID, Scenario, - SeedParkedCars, Sim, SpawnOverTime, Tick, WeightedUsizeChoice, + SeedParkedCars, Sim, SpawnOverTime, Tick, }; // Helpers to run the sim diff --git a/sim/src/lib.rs b/sim/src/lib.rs index d75ae4f41b..95e67620d5 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -58,7 +58,6 @@ pub use make::{ OriginDestination, Scenario, SeedParkedCars, SimFlags, SpawnOverTime, }; use map_model::{BuildingID, LaneID, Trace, TurnID}; -use rand::{RngCore, SeedableRng, XorShiftRng}; pub use sim::{Benchmark, Sim, Summary}; pub use stats::SimStats; use std::fmt; @@ -335,42 +334,3 @@ impl Cloneable for Scenario {} impl Cloneable for Tick {} impl Cloneable for MapEdits {} impl Cloneable for ABTest {} -impl Cloneable for WeightedUsizeChoice {} - -fn fork_rng(base_rng: &mut XorShiftRng) -> XorShiftRng { - XorShiftRng::from_seed([base_rng.next_u32() as u8; 16]) -} - -// Represents the probability of sampling 0, 1, 2, 3... The sum can be anything. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WeightedUsizeChoice { - weights: Vec, -} - -impl WeightedUsizeChoice { - pub fn parse(string: &str) -> Option { - let parts: Vec<&str> = string.split(",").collect(); - if parts.is_empty() { - return None; - } - let mut weights: Vec = Vec::new(); - for x in parts.into_iter() { - let x = x.parse::().ok()?; - weights.push(x); - } - Some(WeightedUsizeChoice { weights }) - } -} - -fn weighted_sample(choices: &WeightedUsizeChoice, rng: &mut XorShiftRng) -> u32 { - use rand::distributions::{Distribution, Weighted, WeightedChoice}; - let mut items: Vec> = choices - .weights - .iter() - .enumerate() - .map(|(idx, pr)| Weighted { - weight: *pr, - item: idx as u32, - }).collect(); - WeightedChoice::new(&mut items).sample(rng) -} diff --git a/sim/src/make/scenario.rs b/sim/src/make/scenario.rs index 2087164f62..5680ca6899 100644 --- a/sim/src/make/scenario.rs +++ b/sim/src/make/scenario.rs @@ -1,11 +1,12 @@ use abstutil; +use abstutil::WeightedUsizeChoice; use driving::DrivingGoal; use map_model::{BuildingID, IntersectionID, LaneType, Map, RoadID}; use rand::Rng; use rand::XorShiftRng; use std::collections::{BTreeSet, HashMap, HashSet}; use walking::SidewalkSpot; -use {CarID, Neighborhood, Sim, Tick, WeightedUsizeChoice}; +use {CarID, Neighborhood, Sim, Tick}; #[derive(Clone, Serialize, Deserialize, Debug)] pub struct Scenario { diff --git a/sim/src/spawn.rs b/sim/src/spawn.rs index ec5573417b..65d124d3e3 100644 --- a/sim/src/spawn.rs +++ b/sim/src/spawn.rs @@ -1,4 +1,4 @@ -use abstutil::elapsed_seconds; +use abstutil::{elapsed_seconds, fork_rng, WeightedUsizeChoice}; use dimensioned::si; use driving::{CreateCar, DrivingGoal, DrivingSimState}; use kinematics::Vehicle; @@ -12,8 +12,7 @@ use transit::TransitSimState; use trips::{TripLeg, TripManager}; use walking::{SidewalkSpot, WalkingSimState}; use { - fork_rng, weighted_sample, AgentID, CarID, Distance, Event, ParkedCar, ParkingSpot, - PedestrianID, RouteID, Tick, TripID, WeightedUsizeChoice, + AgentID, CarID, Distance, Event, ParkedCar, ParkingSpot, PedestrianID, RouteID, Tick, TripID, }; #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] @@ -329,7 +328,7 @@ impl Spawner { let mut new_cars = 0; for b in owner_buildings { - for _ in 0..weighted_sample(&cars_per_building, base_rng) { + for _ in 0..cars_per_building.sample(base_rng) { if let Some(spot) = find_spot_near_building(*b, &mut open_spots_per_road, neighborhoods_roads, map) {