use std::collections::{BTreeMap, BTreeSet, HashSet, VecDeque};
use std::fmt;
use anyhow::Result;
use rand::seq::SliceRandom;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use serde::{Deserialize, Serialize};
use abstio::MapName;
use abstutil::{prettyprint_usize, Counter, Timer};
use geom::{Distance, Speed, Time};
use map_model::{BuildingID, Map, OffstreetParking, RoadID};
use crate::make::fork_rng;
use crate::{
OrigPersonID, ParkingSpot, Sim, StartTripArgs, TripEndpoint, TripInfo, TripMode, Vehicle,
VehicleSpec, VehicleType, BIKE_LENGTH, MAX_CAR_LENGTH, MIN_CAR_LENGTH,
};
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Scenario {
pub scenario_name: String,
pub map_name: MapName,
pub people: Vec<PersonSpec>,
pub only_seed_buses: Option<BTreeSet<String>>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct PersonSpec {
pub orig_id: Option<OrigPersonID>,
pub trips: Vec<IndividTrip>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct IndividTrip {
pub depart: Time,
pub origin: TripEndpoint,
pub destination: TripEndpoint,
pub mode: TripMode,
pub purpose: TripPurpose,
pub cancelled: bool,
pub modified: bool,
}
impl IndividTrip {
pub fn new(
depart: Time,
purpose: TripPurpose,
origin: TripEndpoint,
destination: TripEndpoint,
mode: TripMode,
) -> IndividTrip {
IndividTrip {
depart,
origin,
destination,
mode,
purpose,
cancelled: false,
modified: false,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum TripPurpose {
Home,
Work,
School,
Escort,
PersonalBusiness,
Shopping,
Meal,
Social,
Recreation,
Medical,
ParkAndRideTransfer,
}
impl fmt::Display for TripPurpose {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
TripPurpose::Home => "home",
TripPurpose::Work => "work",
TripPurpose::School => "school",
TripPurpose::Escort => "escort",
TripPurpose::PersonalBusiness => "personal business",
TripPurpose::Shopping => "shopping",
TripPurpose::Meal => "eating",
TripPurpose::Social => "social",
TripPurpose::Recreation => "recreation",
TripPurpose::Medical => "medical",
TripPurpose::ParkAndRideTransfer => "park-and-ride transfer",
}
)
}
}
impl Scenario {
pub fn instantiate(&self, sim: &mut Sim, map: &Map, rng: &mut XorShiftRng, timer: &mut Timer) {
self.instantiate_without_retries(sim, map, rng, true, timer);
}
pub fn instantiate_without_retries(
&self,
sim: &mut Sim,
map: &Map,
rng: &mut XorShiftRng,
retry_if_no_room: bool,
timer: &mut Timer,
) {
sim.set_run_name(self.scenario_name.clone());
timer.start(format!("Instantiating {}", self.scenario_name));
if let Some(ref routes) = self.only_seed_buses {
for route in map.all_bus_routes() {
if routes.contains(&route.full_name) {
sim.seed_bus_route(route);
}
}
} else {
for route in map.all_bus_routes() {
sim.seed_bus_route(route);
}
}
timer.start_iter("trips for People", self.people.len());
let mut parked_cars: Vec<(Vehicle, BuildingID)> = Vec::new();
let mut schedule_trips = Vec::new();
for p in &self.people {
timer.next();
if let Err(err) = p.check_schedule() {
panic!("{}", err);
}
let (vehicle_specs, cars_initially_parked_at, vehicle_foreach_trip) =
p.get_vehicles(rng);
let person = sim.new_person(p.orig_id, Scenario::rand_ped_speed(rng), vehicle_specs);
for (idx, b) in cars_initially_parked_at {
parked_cars.push((person.vehicles[idx].clone(), b));
}
for (trip, maybe_idx) in p.trips.iter().zip(vehicle_foreach_trip) {
schedule_trips.push((
person.id,
TripInfo {
departure: trip.depart,
mode: trip.mode,
start: trip.origin,
end: trip.destination,
purpose: trip.purpose,
modified: trip.modified,
cancellation_reason: if trip.cancelled {
Some("cancelled by ScenarioModifier".to_string())
} else {
None
},
},
StartTripArgs {
retry_if_no_room,
use_vehicle: maybe_idx.map(|idx| person.vehicles[idx].id),
},
));
}
}
parked_cars.shuffle(rng);
seed_parked_cars(parked_cars, sim, map, rng, timer);
sim.spawn_trips(schedule_trips, map, timer);
timer.stop(format!("Instantiating {}", self.scenario_name));
}
pub fn save(&self) {
abstio::write_binary(
abstio::path_scenario(&self.map_name, &self.scenario_name),
self,
);
}
pub fn empty(map: &Map, name: &str) -> Scenario {
Scenario {
scenario_name: name.to_string(),
map_name: map.get_name().clone(),
people: Vec::new(),
only_seed_buses: Some(BTreeSet::new()),
}
}
fn rand_car(rng: &mut XorShiftRng) -> VehicleSpec {
let length = Scenario::rand_dist(rng, MIN_CAR_LENGTH, MAX_CAR_LENGTH);
VehicleSpec {
vehicle_type: VehicleType::Car,
length,
max_speed: None,
}
}
fn rand_bike(rng: &mut XorShiftRng) -> VehicleSpec {
let max_speed = Some(Scenario::rand_speed(
rng,
Speed::miles_per_hour(8.0),
map_model::MAX_BIKE_SPEED,
));
VehicleSpec {
vehicle_type: VehicleType::Bike,
length: BIKE_LENGTH,
max_speed,
}
}
pub fn rand_dist(rng: &mut XorShiftRng, low: Distance, high: Distance) -> Distance {
assert!(high > low);
Distance::meters(rng.gen_range(low.inner_meters()..high.inner_meters()))
}
fn rand_speed(rng: &mut XorShiftRng, low: Speed, high: Speed) -> Speed {
assert!(high > low);
Speed::meters_per_second(
rng.gen_range(low.inner_meters_per_second()..high.inner_meters_per_second()),
)
}
pub fn rand_ped_speed(rng: &mut XorShiftRng) -> Speed {
Scenario::rand_speed(
rng,
Speed::miles_per_hour(2.0),
map_model::MAX_WALKING_SPEED,
)
}
pub fn count_parked_cars_per_bldg(&self) -> Counter<BuildingID> {
let mut per_bldg = Counter::new();
let mut rng = XorShiftRng::seed_from_u64(0);
for p in &self.people {
let (_, cars_initially_parked_at, _) = p.get_vehicles(&mut rng);
for (_, b) in cars_initially_parked_at {
per_bldg.inc(b);
}
}
per_bldg
}
pub fn remove_weird_schedules(mut self) -> Scenario {
let orig = self.people.len();
self.people.retain(|person| match person.check_schedule() {
Ok(()) => true,
Err(err) => {
println!("{}", err);
false
}
});
warn!(
"{} of {} people have nonsense schedules",
prettyprint_usize(orig - self.people.len()),
prettyprint_usize(orig)
);
self
}
}
fn seed_parked_cars(
parked_cars: Vec<(Vehicle, BuildingID)>,
sim: &mut Sim,
map: &Map,
base_rng: &mut XorShiftRng,
timer: &mut Timer,
) {
if sim.infinite_parking() {
let mut blackholed = 0;
timer.start_iter("seed parked cars in infinite mode", parked_cars.len());
for (vehicle, b) in parked_cars {
timer.next();
if let Some(spot) = sim.get_free_offstreet_spots(b).pop() {
sim.seed_parked_car(vehicle, spot);
} else {
blackholed += 1;
}
}
if blackholed > 0 {
warn!(
"{} parked cars weren't seeded, due to blackholed buildings",
prettyprint_usize(blackholed)
);
}
return;
}
let mut open_spots_per_road: BTreeMap<RoadID, Vec<(ParkingSpot, Option<BuildingID>)>> =
BTreeMap::new();
for spot in sim.get_all_parking_spots().1 {
let (r, restriction) = match spot {
ParkingSpot::Onstreet(l, _) => (map.get_l(l).parent, None),
ParkingSpot::Offstreet(b, _) => (
map.get_l(map.get_b(b).sidewalk()).parent,
match map.get_b(b).parking {
OffstreetParking::PublicGarage(_, _) => None,
OffstreetParking::Private(_, _) => Some(b),
},
),
ParkingSpot::Lot(pl, _) => (map.get_l(map.get_pl(pl).driving_pos.lane()).parent, None),
};
open_spots_per_road
.entry(r)
.or_insert_with(Vec::new)
.push((spot, restriction));
}
for r in map.all_roads() {
let mut tmp_rng = fork_rng(base_rng);
if let Some(ref mut spots) = open_spots_per_road.get_mut(&r.id) {
spots.shuffle(&mut tmp_rng);
}
}
timer.start_iter("seed parked cars", parked_cars.len());
let mut ok = true;
let total_cars = parked_cars.len();
let mut seeded = 0;
for (vehicle, b) in parked_cars {
timer.next();
if !ok {
continue;
}
if let Some(spot) = find_spot_near_building(b, &mut open_spots_per_road, map) {
seeded += 1;
sim.seed_parked_car(vehicle, spot);
} else {
warn!(
"Not enough room to seed parked cars. Only found spots for {} of {}",
prettyprint_usize(seeded),
prettyprint_usize(total_cars)
);
ok = false;
}
}
}
fn find_spot_near_building(
b: BuildingID,
open_spots_per_road: &mut BTreeMap<RoadID, Vec<(ParkingSpot, Option<BuildingID>)>>,
map: &Map,
) -> Option<ParkingSpot> {
let mut roads_queue: VecDeque<RoadID> = VecDeque::new();
let mut visited: HashSet<RoadID> = HashSet::new();
{
let start = map.building_to_road(b).id;
roads_queue.push_back(start);
visited.insert(start);
}
loop {
let r = roads_queue.pop_front()?;
if let Some(spots) = open_spots_per_road.get_mut(&r) {
if let Some(idx) = spots
.iter()
.position(|(_, restriction)| restriction == &Some(b))
{
return Some(spots.remove(idx).0);
}
if let Some(idx) = spots
.iter()
.position(|(_, restriction)| restriction.is_none())
{
return Some(spots.remove(idx).0);
}
}
for next_r in map.get_next_roads(r).into_iter() {
if !visited.contains(&next_r) {
roads_queue.push_back(next_r);
visited.insert(next_r);
}
}
}
}
impl PersonSpec {
fn check_schedule(&self) -> Result<()> {
for pair in self.trips.windows(2) {
if pair[0].depart >= pair[1].depart {
bail!(
"Person ({:?}) starts two trips in the wrong order: {} then {}",
self.orig_id,
pair[0].depart,
pair[1].depart
);
}
if pair[0].destination != pair[1].origin {
if matches!(pair[0].destination, TripEndpoint::Border(_))
&& matches!(pair[1].origin, TripEndpoint::Border(_))
{
continue;
}
bail!(
"Person ({:?}) warps from {:?} to {:?} during adjacent trips",
self.orig_id,
pair[0].destination,
pair[1].origin
);
}
}
for trip in &self.trips {
if trip.origin == trip.destination {
bail!(
"Person ({:?}) has a trip from/to the same place: {:?}",
self.orig_id,
trip.origin
);
}
}
Ok(())
}
fn get_vehicles(
&self,
rng: &mut XorShiftRng,
) -> (
Vec<VehicleSpec>,
Vec<(usize, BuildingID)>,
Vec<Option<usize>>,
) {
let mut vehicle_specs = Vec::new();
let mut cars_initially_parked_at = Vec::new();
let mut vehicle_foreach_trip = Vec::new();
let mut bike_idx = None;
let mut car_locations: Vec<(usize, Option<BuildingID>)> = Vec::new();
for trip in &self.trips {
let use_for_trip = match trip.mode {
TripMode::Walk | TripMode::Transit => None,
TripMode::Bike => {
if bike_idx.is_none() {
bike_idx = Some(vehicle_specs.len());
vehicle_specs.push(Scenario::rand_bike(rng));
}
bike_idx
}
TripMode::Drive => {
let need_parked_at = match trip.origin {
TripEndpoint::Bldg(b) => Some(b),
_ => None,
};
let idx = if let Some(idx) = car_locations
.iter()
.find(|(_, parked_at)| *parked_at == need_parked_at)
.map(|(idx, _)| *idx)
{
idx
} else {
let idx = vehicle_specs.len();
vehicle_specs.push(Scenario::rand_car(rng));
if let Some(b) = need_parked_at {
cars_initially_parked_at.push((idx, b));
}
idx
};
car_locations.retain(|(i, _)| idx != *i);
match trip.destination {
TripEndpoint::Bldg(b) => {
car_locations.push((idx, Some(b)));
}
TripEndpoint::Border(_) | TripEndpoint::SuddenlyAppear(_) => {
car_locations.push((idx, None));
}
}
Some(idx)
}
};
vehicle_foreach_trip.push(use_for_trip);
}
if false {
let mut n = vehicle_specs.len();
if bike_idx.is_some() {
n -= 1;
}
if n > 1 {
println!("Someone needs {} cars", n);
}
}
(
vehicle_specs,
cars_initially_parked_at,
vehicle_foreach_trip,
)
}
}