use crate::{
IndividTrip, PersonID, PersonSpec, Scenario, ScenarioGenerator, SpawnTrip, TripEndpoint,
TripMode,
};
use abstutil::{prettyprint_usize, Parallelism, Timer};
use geom::{Distance, Duration, Time};
use map_model::{BuildingID, BuildingType, Map, PathConstraints, PathRequest};
use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
impl ScenarioGenerator {
pub fn proletariat_robot(map: &Map, rng: &mut XorShiftRng, timer: &mut Timer) -> Scenario {
let mut residents: Vec<BuildingID> = Vec::new();
let mut workers: Vec<BuildingID> = Vec::new();
let mut num_bldg_residential = 0;
let mut num_bldg_commercial = 0;
let mut num_bldg_mixed_residential_commercial = 0;
for b in map.all_buildings() {
match b.bldg_type {
BuildingType::Residential(resident_cap) => {
for _ in 0..resident_cap {
residents.push(b.id);
}
num_bldg_residential += 1;
}
BuildingType::ResidentialCommercial(resident_cap, worker_cap) => {
for _ in 0..resident_cap {
residents.push(b.id);
}
for _ in 0..worker_cap {
workers.push(b.id);
}
num_bldg_mixed_residential_commercial += 1;
}
BuildingType::Commercial(worker_cap) => {
for _ in 0..worker_cap {
workers.push(b.id);
}
num_bldg_commercial += 1;
}
BuildingType::Empty => {}
}
}
residents.shuffle(rng);
workers.shuffle(rng);
let mut s = Scenario::empty(map, "random people going to/from work");
s.only_seed_buses = None;
let residents_cap = residents.len();
let workers_cap = workers.len();
let trip_saturation = 1.2;
let num_trips = (trip_saturation * (residents_cap + workers_cap) as f64) as usize;
let lower_bound_prob = 0.05;
let upper_bound_prob = 0.90;
let prob_local_resident = if workers_cap == 0 {
lower_bound_prob
} else {
f64::min(
upper_bound_prob,
f64::max(lower_bound_prob, residents_cap as f64 / num_trips as f64),
)
};
let prob_local_worker = f64::min(
upper_bound_prob,
f64::max(lower_bound_prob, workers_cap as f64 / num_trips as f64),
);
debug!(
"BUILDINGS - workplaces: {}, residences: {}, mixed: {}",
prettyprint_usize(num_bldg_commercial),
prettyprint_usize(num_bldg_residential),
prettyprint_usize(num_bldg_mixed_residential_commercial)
);
debug!(
"CAPACITY - workers_cap: {}, residents_cap: {}, prob_local_worker: {:.1}%, \
prob_local_resident: {:.1}%",
prettyprint_usize(workers_cap),
prettyprint_usize(residents_cap),
prob_local_worker * 100.,
prob_local_resident * 100.
);
let mut num_trips_local = 0;
let mut num_trips_commuting_in = 0;
let mut num_trips_commuting_out = 0;
let mut num_trips_passthru = 0;
timer.start("create people");
let commuter_borders: Vec<TripEndpoint> = map
.all_outgoing_borders()
.into_iter()
.filter(|b| b.is_incoming_border())
.map(|b| TripEndpoint::Border(b.id, None))
.collect();
assert!(commuter_borders.len() > 0);
let person_params = (0..num_trips)
.map(|_| {
let (is_local_resident, is_local_worker) = (
rng.gen_bool(prob_local_resident),
rng.gen_bool(prob_local_worker),
);
let home = if is_local_resident {
if let Some(residence) = residents.pop() {
TripEndpoint::Bldg(residence)
} else {
commuter_borders.choose(rng).unwrap().clone()
}
} else {
commuter_borders.choose(rng).unwrap().clone()
};
let work = if is_local_worker {
if let Some(workplace) = workers.pop() {
TripEndpoint::Bldg(workplace)
} else {
commuter_borders.choose(rng).unwrap().clone()
}
} else {
commuter_borders.choose(rng).unwrap().clone()
};
match (&home, &work) {
(TripEndpoint::Bldg(_), TripEndpoint::Bldg(_)) => {
num_trips_local += 1;
}
(TripEndpoint::Bldg(_), TripEndpoint::Border(_, _)) => {
num_trips_commuting_out += 1;
}
(TripEndpoint::Border(_, _), TripEndpoint::Bldg(_)) => {
num_trips_commuting_in += 1;
}
(TripEndpoint::Border(_, _), TripEndpoint::Border(_, _)) => {
num_trips_passthru += 1;
}
};
(home, work, abstutil::fork_rng(rng))
})
.collect();
timer
.parallelize(
"create people: making PersonSpec from endpoints",
Parallelism::Fastest,
person_params,
|(home, work, mut rng)| match create_prole(&home, &work, map, &mut rng) {
Ok(person) => Some(person),
Err(e) => {
trace!("Unable to create person. error: {}", e);
None
}
},
)
.into_iter()
.flatten()
.for_each(|mut person| {
person.id = PersonID(s.people.len());
s.people.push(person);
});
timer.stop("create people");
info!(
"TRIPS - total: {}, local: {}, commuting_in: {}, commuting_out: {}, passthru: {}, \
errored: {}, leftover_resident_capacity: {}, leftover_worker_capacity: {}",
prettyprint_usize(num_trips),
prettyprint_usize(num_trips_local),
prettyprint_usize(num_trips_commuting_in),
prettyprint_usize(num_trips_commuting_out),
prettyprint_usize(num_trips_passthru),
prettyprint_usize(num_trips - s.people.len()),
prettyprint_usize(residents.len()),
prettyprint_usize(workers.len()),
);
s
}
}
fn create_prole(
home: &TripEndpoint,
work: &TripEndpoint,
map: &Map,
rng: &mut XorShiftRng,
) -> Result<PersonSpec, Box<dyn std::error::Error>> {
if home == work {
return Err("TODO: handle working and living in the same building".into());
}
let mode = match (home, work) {
(TripEndpoint::Bldg(home_bldg), TripEndpoint::Bldg(work_bldg)) => {
let dist = if let Some(dist) = map
.pathfind(PathRequest {
start: map.get_b(*home_bldg).sidewalk_pos,
end: map.get_b(*work_bldg).sidewalk_pos,
constraints: PathConstraints::Pedestrian,
})
.map(|p| p.total_length())
{
dist
} else {
return Err("no path found".into());
};
select_trip_mode(dist, rng)
}
_ => TripMode::Drive,
};
let mut depart_am = rand_time(
rng,
Time::START_OF_DAY + Duration::hours(7),
Time::START_OF_DAY + Duration::hours(10),
);
let mut depart_pm = rand_time(
rng,
Time::START_OF_DAY + Duration::hours(17),
Time::START_OF_DAY + Duration::hours(19),
);
if rng.gen_bool(0.1) {
depart_am = rand_time(
rng,
Time::START_OF_DAY + Duration::hours(0),
Time::START_OF_DAY + Duration::hours(12),
);
depart_pm = rand_time(
rng,
Time::START_OF_DAY + Duration::hours(12),
Time::START_OF_DAY + Duration::hours(24),
);
}
let goto_work = SpawnTrip::new(home.clone(), work.clone(), mode, map)
.ok_or("unable to spawn 'goto work' trip")?;
let return_home = SpawnTrip::new(work.clone(), home.clone(), mode, map)
.ok_or("unable to spawn 'return home' trip")?;
Ok(PersonSpec {
id: PersonID(0),
orig_id: None,
trips: vec![
IndividTrip::new(depart_am, goto_work),
IndividTrip::new(depart_pm, return_home),
],
})
}
fn select_trip_mode(distance: Distance, rng: &mut XorShiftRng) -> TripMode {
if distance < Distance::miles(0.5) {
return TripMode::Walk;
}
if distance < Distance::miles(3.0) {
if rng.gen_bool(0.15) {
return TripMode::Bike;
}
if rng.gen_bool(0.05) {
return TripMode::Walk;
}
}
if rng.gen_bool(0.005) {
return TripMode::Bike;
}
if rng.gen_bool(0.3) {
return TripMode::Transit;
}
TripMode::Drive
}
fn rand_time(rng: &mut XorShiftRng, low: Time, high: Time) -> Time {
assert!(high > low);
Time::START_OF_DAY + Duration::seconds(rng.gen_range(low.inner_seconds(), high.inner_seconds()))
}