use std::collections::{BTreeMap, BTreeSet, HashSet, VecDeque};
use std::fmt;
use rand::seq::SliceRandom;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use serde::{Deserialize, Serialize};
use abstutil::{prettyprint_usize, Counter, MapName, Timer};
use geom::{Distance, Duration, LonLat, Speed, Time};
use map_model::{
BuildingID, BusRouteID, BusStopID, DirectedRoadID, Map, OffstreetParking, PathConstraints,
Position, RoadID,
};
use crate::make::fork_rng;
use crate::{
CarID, DrivingGoal, OrigPersonID, ParkingSpot, PersonID, SidewalkPOI, SidewalkSpot, Sim,
TripEndpoint, TripMode, TripSpec, Vehicle, VehicleSpec, VehicleType, BIKE_LENGTH,
MAX_CAR_LENGTH, MIN_CAR_LENGTH, SPAWN_DIST,
};
#[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 id: PersonID,
pub orig_id: Option<OrigPersonID>,
pub trips: Vec<IndividTrip>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct IndividTrip {
pub depart: Time,
pub trip: SpawnTrip,
pub purpose: TripPurpose,
pub cancelled: bool,
pub modified: bool,
}
impl IndividTrip {
pub fn new(depart: Time, purpose: TripPurpose, trip: SpawnTrip) -> IndividTrip {
IndividTrip {
depart,
trip,
purpose,
cancelled: false,
modified: false,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SpawnTrip {
VehicleAppearing {
start: Position,
goal: DrivingGoal,
is_bike: bool,
},
FromBorder {
dr: DirectedRoadID,
goal: DrivingGoal,
is_bike: bool,
origin: Option<OffMapLocation>,
},
UsingParkedCar(BuildingID, DrivingGoal),
UsingBike(BuildingID, DrivingGoal),
JustWalking(SidewalkSpot, SidewalkSpot),
UsingTransit(
SidewalkSpot,
SidewalkSpot,
BusRouteID,
BusStopID,
Option<BusStopID>,
),
Remote {
from: OffMapLocation,
to: OffMapLocation,
trip_time: Duration,
mode: TripMode,
},
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct OffMapLocation {
pub parcel_id: usize,
pub gps: LonLat,
}
#[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) {
sim.set_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 spawner = sim.make_spawner();
let mut parked_cars: Vec<(Vehicle, BuildingID)> = Vec::new();
for p in &self.people {
timer.next();
if let Err(err) = p.check_schedule(map) {
panic!("{}", err);
}
let (vehicle_specs, cars_initially_parked_at, vehicle_foreach_trip) =
p.get_vehicles(rng);
sim.new_person(
p.id,
p.orig_id,
Scenario::rand_ped_speed(rng),
vehicle_specs,
);
let person = sim.get_person(p.id);
for (idx, b) in cars_initially_parked_at {
parked_cars.push((person.vehicles[idx].clone(), b));
}
for (t, maybe_idx) in p.trips.iter().zip(vehicle_foreach_trip) {
let mut tmp_rng = fork_rng(rng);
let spec = t.trip.clone().to_trip_spec(
maybe_idx.map(|idx| person.vehicles[idx].id),
&mut tmp_rng,
map,
);
spawner.schedule_trip(
person,
t.depart,
spec,
t.trip.start(map),
t.purpose,
t.cancelled,
t.modified,
map,
);
}
}
parked_cars.shuffle(rng);
seed_parked_cars(parked_cars, sim, map, rng, timer);
sim.flush_spawner(spawner, map, timer);
timer.stop(format!("Instantiating {}", self.scenario_name));
}
pub fn save(&self) {
abstutil::write_binary(
abstutil::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()),
}
}
pub 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,
}
}
pub fn rand_bike(rng: &mut XorShiftRng) -> VehicleSpec {
let max_speed = Some(Scenario::rand_speed(
rng,
Speed::miles_per_hour(8.0),
Scenario::max_bike_speed(),
));
VehicleSpec {
vehicle_type: VehicleType::Bike,
length: BIKE_LENGTH,
max_speed,
}
}
pub fn max_bike_speed() -> Speed {
Speed::miles_per_hour(10.0)
}
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), Speed::miles_per_hour(3.0))
}
pub fn max_ped_speed() -> Speed {
Speed::miles_per_hour(3.0)
}
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, map: &Map) -> Scenario {
let orig = self.people.len();
self.people
.retain(|person| match person.check_schedule(map) {
Ok(()) => true,
Err(err) => {
println!("{}", err);
false
}
});
println!(
"{} of {} people have nonsense schedules",
prettyprint_usize(orig - self.people.len()),
prettyprint_usize(orig)
);
for (idx, person) in self.people.iter_mut().enumerate() {
person.id = PersonID(idx);
}
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;
for (vehicle, b) in parked_cars {
if let Some(spot) = sim.get_free_offstreet_spots(b).pop() {
sim.seed_parked_car(vehicle, spot);
} else {
blackholed += 1;
}
}
if blackholed > 0 {
timer.warn(format!(
"{} 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 {
timer.warn(format!(
"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 SpawnTrip {
fn to_trip_spec(
self,
use_vehicle: Option<CarID>,
rng: &mut XorShiftRng,
map: &Map,
) -> TripSpec {
match self {
SpawnTrip::VehicleAppearing { start, goal, .. } => TripSpec::VehicleAppearing {
start_pos: start,
goal,
use_vehicle: use_vehicle.unwrap(),
retry_if_no_room: true,
origin: None,
},
SpawnTrip::FromBorder {
dr,
goal,
is_bike,
origin,
} => {
let constraints = if is_bike {
PathConstraints::Bike
} else {
PathConstraints::Car
};
if let Some(l) = dr.lanes(constraints, map).choose(rng) {
TripSpec::VehicleAppearing {
start_pos: Position::new(*l, SPAWN_DIST),
goal,
use_vehicle: use_vehicle.unwrap(),
retry_if_no_room: true,
origin,
}
} else {
TripSpec::NoRoomToSpawn {
i: dr.src_i(map),
goal,
use_vehicle: use_vehicle.unwrap(),
origin,
error: format!("{} has no lanes to spawn a {:?}", dr.id, constraints),
}
}
}
SpawnTrip::UsingParkedCar(start_bldg, goal) => TripSpec::UsingParkedCar {
start_bldg,
goal,
car: use_vehicle.unwrap(),
},
SpawnTrip::UsingBike(start, goal) => TripSpec::UsingBike {
bike: use_vehicle.unwrap(),
start,
goal,
},
SpawnTrip::JustWalking(start, goal) => TripSpec::JustWalking { start, goal },
SpawnTrip::UsingTransit(start, goal, route, stop1, maybe_stop2) => {
TripSpec::UsingTransit {
start,
goal,
route,
stop1,
maybe_stop2,
}
}
SpawnTrip::Remote {
from,
to,
trip_time,
mode,
} => TripSpec::Remote {
from,
to,
trip_time,
mode,
},
}
}
pub fn mode(&self) -> TripMode {
match self {
SpawnTrip::VehicleAppearing { is_bike, .. } => {
if *is_bike {
TripMode::Bike
} else {
TripMode::Drive
}
}
SpawnTrip::FromBorder { is_bike, .. } => {
if *is_bike {
TripMode::Bike
} else {
TripMode::Drive
}
}
SpawnTrip::UsingParkedCar(_, _) => TripMode::Drive,
SpawnTrip::UsingBike(_, _) => TripMode::Bike,
SpawnTrip::JustWalking(_, _) => TripMode::Walk,
SpawnTrip::UsingTransit(_, _, _, _, _) => TripMode::Transit,
SpawnTrip::Remote { .. } => TripMode::Drive,
}
}
pub fn start(&self, map: &Map) -> TripEndpoint {
match self {
SpawnTrip::VehicleAppearing { ref start, .. } => {
TripEndpoint::Border(map.get_l(start.lane()).src_i, None)
}
SpawnTrip::FromBorder { dr, ref origin, .. } => {
TripEndpoint::Border(dr.src_i(map), origin.clone())
}
SpawnTrip::UsingParkedCar(b, _) => TripEndpoint::Bldg(*b),
SpawnTrip::UsingBike(b, _) => TripEndpoint::Bldg(*b),
SpawnTrip::JustWalking(ref spot, _) | SpawnTrip::UsingTransit(ref spot, _, _, _, _) => {
match spot.connection {
SidewalkPOI::Building(b) => TripEndpoint::Bldg(b),
SidewalkPOI::Border(i, ref loc) => TripEndpoint::Border(i, loc.clone()),
SidewalkPOI::SuddenlyAppear => {
TripEndpoint::Border(map.get_l(spot.sidewalk_pos.lane()).src_i, None)
}
_ => unreachable!(),
}
}
SpawnTrip::Remote { ref from, .. } => {
TripEndpoint::Border(map.all_outgoing_borders()[0].id, Some(from.clone()))
}
}
}
pub fn end(&self, map: &Map) -> TripEndpoint {
match self {
SpawnTrip::VehicleAppearing { ref goal, .. }
| SpawnTrip::FromBorder { ref goal, .. }
| SpawnTrip::UsingParkedCar(_, ref goal)
| SpawnTrip::UsingBike(_, ref goal) => match goal {
DrivingGoal::ParkNear(b) => TripEndpoint::Bldg(*b),
DrivingGoal::Border(i, _, ref loc) => TripEndpoint::Border(*i, loc.clone()),
},
SpawnTrip::JustWalking(_, ref spot) | SpawnTrip::UsingTransit(_, ref spot, _, _, _) => {
match spot.connection {
SidewalkPOI::Building(b) => TripEndpoint::Bldg(b),
SidewalkPOI::Border(i, ref loc) => TripEndpoint::Border(i, loc.clone()),
_ => unreachable!(),
}
}
SpawnTrip::Remote { ref to, .. } => {
TripEndpoint::Border(map.all_incoming_borders()[0].id, Some(to.clone()))
}
}
}
pub fn new(
from: TripEndpoint,
to: TripEndpoint,
mode: TripMode,
map: &Map,
) -> Option<SpawnTrip> {
Some(match mode {
TripMode::Drive => match from {
TripEndpoint::Bldg(b) => {
SpawnTrip::UsingParkedCar(b, to.driving_goal(PathConstraints::Car, map)?)
}
TripEndpoint::Border(i, ref origin) => SpawnTrip::FromBorder {
dr: map.get_i(i).some_outgoing_road(map)?,
goal: to.driving_goal(PathConstraints::Car, map)?,
is_bike: false,
origin: origin.clone(),
},
},
TripMode::Bike => match from {
TripEndpoint::Bldg(b) => {
SpawnTrip::UsingBike(b, to.driving_goal(PathConstraints::Bike, map)?)
}
TripEndpoint::Border(i, ref origin) => SpawnTrip::FromBorder {
dr: map.get_i(i).some_outgoing_road(map)?,
goal: to.driving_goal(PathConstraints::Bike, map)?,
is_bike: true,
origin: origin.clone(),
},
},
TripMode::Walk => {
SpawnTrip::JustWalking(from.start_sidewalk_spot(map)?, to.end_sidewalk_spot(map)?)
}
TripMode::Transit => {
let start = from.start_sidewalk_spot(map)?;
let goal = to.end_sidewalk_spot(map)?;
if let Some((stop1, maybe_stop2, route)) =
map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos)
{
SpawnTrip::UsingTransit(start, goal, route, stop1, maybe_stop2)
} else {
SpawnTrip::JustWalking(start, goal)
}
}
})
}
}
impl PersonSpec {
fn check_schedule(&self, map: &Map) -> Result<(), String> {
for pair in self.trips.iter().zip(self.trips.iter().skip(1)) {
if pair.0.depart >= pair.1.depart {
return Err(format!(
"{} {:?} starts two trips in the wrong order: {} then {}",
self.id, self.orig_id, pair.0.depart, pair.1.depart
));
}
let end_bldg = match pair.0.trip.end(map) {
TripEndpoint::Bldg(b) => Some(b),
TripEndpoint::Border(_, _) => None,
};
let start_bldg = match pair.1.trip.start(map) {
TripEndpoint::Bldg(b) => Some(b),
TripEndpoint::Border(_, _) => None,
};
if end_bldg != start_bldg {
return Err(format!(
"At {}, {} {:?} warps between some trips, from {:?} to {:?}",
pair.1.depart, self.id, self.orig_id, end_bldg, start_bldg
));
}
if let (SpawnTrip::Remote { ref to, .. }, SpawnTrip::Remote { ref from, .. }) =
(&pair.0.trip, &pair.1.trip)
{
if to != from {
return Err(format!(
"At {}, {} {:?} warps between some trips, from {:?} to {:?}",
pair.1.depart, self.id, self.orig_id, to, from
));
}
}
}
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.trip {
SpawnTrip::VehicleAppearing {
is_bike, ref goal, ..
}
| SpawnTrip::FromBorder {
is_bike, ref goal, ..
} => {
if is_bike {
if bike_idx.is_none() {
bike_idx = Some(vehicle_specs.len());
vehicle_specs.push(Scenario::rand_bike(rng));
}
bike_idx
} else {
let idx = if let Some(idx) = car_locations
.iter()
.find(|(_, parked_at)| parked_at.is_none())
.map(|(idx, _)| *idx)
{
idx
} else {
let idx = vehicle_specs.len();
vehicle_specs.push(Scenario::rand_car(rng));
idx
};
car_locations.retain(|(i, _)| idx != *i);
match goal {
DrivingGoal::ParkNear(b) => {
car_locations.push((idx, Some(*b)));
}
DrivingGoal::Border(_, _, _) => {
car_locations.push((idx, None));
}
}
Some(idx)
}
}
SpawnTrip::UsingParkedCar(b, ref goal) => {
let idx = if let Some(idx) = car_locations
.iter()
.find(|(_, parked_at)| *parked_at == Some(b))
.map(|(idx, _)| *idx)
{
idx
} else {
let idx = vehicle_specs.len();
vehicle_specs.push(Scenario::rand_car(rng));
cars_initially_parked_at.push((idx, b));
idx
};
car_locations.retain(|(i, _)| idx != *i);
match goal {
DrivingGoal::ParkNear(b) => {
car_locations.push((idx, Some(*b)));
}
DrivingGoal::Border(_, _, _) => {
car_locations.push((idx, None));
}
}
Some(idx)
}
SpawnTrip::UsingBike(_, _) => {
if bike_idx.is_none() {
bike_idx = Some(vehicle_specs.len());
vehicle_specs.push(Scenario::rand_bike(rng));
}
bike_idx
}
SpawnTrip::JustWalking(_, _) | SpawnTrip::UsingTransit(_, _, _, _, _) => None,
SpawnTrip::Remote { .. } => None,
};
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!("{} needs {} cars", self.id, n);
}
}
(
vehicle_specs,
cars_initially_parked_at,
vehicle_foreach_trip,
)
}
}