parallelize the proletariat robot activity model for #154 [rebuild]

This commit is contained in:
Dustin Carlino 2020-08-16 17:33:12 -07:00
parent 7d460f27c8
commit e5f7136f27

View File

@ -2,9 +2,9 @@ use crate::{
IndividTrip, PersonID, PersonSpec, Scenario, ScenarioGenerator, SpawnTrip, TripEndpoint,
TripMode,
};
use abstutil::Timer;
use abstutil::{Parallelism, Timer};
use geom::{Distance, Duration, Time};
use map_model::{BuildingID, BuildingType, Map, PathConstraints, PathRequest};
use map_model::{BuildingID, BuildingType, Intersection, Map, PathConstraints, PathRequest};
use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
@ -12,19 +12,23 @@ use rand_xorshift::XorShiftRng;
impl ScenarioGenerator {
// Designed in https://github.com/dabreegster/abstreet/issues/154
pub fn proletariat_robot(map: &Map, rng: &mut XorShiftRng, timer: &mut Timer) -> Scenario {
let mut residences: Vec<(BuildingID, usize)> = Vec::new();
let mut residences: Vec<(BuildingID, XorShiftRng)> = Vec::new();
let mut workplaces: Vec<BuildingID> = Vec::new();
let mut total_ppl = 0;
let mut num_residences = 0;
for b in map.all_buildings() {
match b.bldg_type {
BuildingType::Residential(num_ppl) => {
residences.push((b.id, num_ppl));
total_ppl += num_ppl;
for _ in 0..num_ppl {
residences.push((b.id, abstutil::fork_rng(rng)));
}
num_residences += 1;
}
BuildingType::ResidentialCommercial(num_ppl) => {
residences.push((b.id, num_ppl));
total_ppl += num_ppl;
for _ in 0..num_ppl {
residences.push((b.id, abstutil::fork_rng(rng)));
}
workplaces.push(b.id);
num_residences += 1;
}
BuildingType::Commercial => {
workplaces.push(b.id);
@ -32,164 +36,190 @@ impl ScenarioGenerator {
BuildingType::Empty => {}
}
}
let num_residences = residences.len();
let mut s = Scenario::empty(map, "random people going to/from work");
// Include all buses/trains
s.only_seed_buses = None;
timer.start_iter("create people", total_ppl);
for (home, num_ppl) in residences {
for _ in 0..num_ppl {
timer.next();
// Make a person going from their home to a random workplace, then back again later.
let work = *workplaces.choose(rng).unwrap();
// Decide mode based on walking distance.
let dist = if let Some(path) = map.pathfind(PathRequest {
start: map.get_b(home).sidewalk_pos,
end: map.get_b(work).sidewalk_pos,
constraints: PathConstraints::Pedestrian,
}) {
path.total_length()
} else {
// Woops, the buildings aren't connected. Probably a bug in importing. Just skip
// this person.
continue;
};
if home == work {
// working and living in the same building
continue;
}
// TODO If home or work is in an access-restricted zone (like a living street),
// then probably don't drive there. Actually, it depends on the specific tagging;
// access=no in the US usually means a gated community.
let mode = select_trip_mode(dist, rng);
// TODO This will cause a single morning and afternoon rush. Outside of these times,
// it'll be really quiet. Probably want a normal distribution centered around these
// peak times, but with a long tail.
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) {
// hacky hack to get some background traffic
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, return_home) = match (
SpawnTrip::new(
TripEndpoint::Bldg(home),
TripEndpoint::Bldg(work),
mode,
map,
),
SpawnTrip::new(
TripEndpoint::Bldg(work),
TripEndpoint::Bldg(home),
mode,
map,
),
) {
(Some(t1), Some(t2)) => (t1, t2),
// Skip the person if either trip can't be created.
_ => continue,
};
s.people.push(PersonSpec {
id: PersonID(s.people.len()),
orig_id: None,
trips: vec![
IndividTrip::new(depart_am, goto_work),
IndividTrip::new(depart_pm, return_home),
],
});
}
for mut person in timer
.parallelize(
"create people",
Parallelism::Fastest,
residences,
|(home, mut forked_rng)| robot_person(home, &workplaces, map, &mut forked_rng),
)
.into_iter()
.flatten()
{
person.id = PersonID(s.people.len());
s.people.push(person);
}
// Create trips between map borders. For now, scale the number by the number of residences.
let incoming_connections = map.all_incoming_borders();
let outgoing_connections = map.all_outgoing_borders();
timer.start_iter("create border trips", num_residences);
for _ in 0..num_residences {
timer.next();
// TODO it would be nice to weigh border points by for example lane count
let random_incoming_border = incoming_connections.choose(rng).unwrap();
let random_outgoing_border = outgoing_connections.choose(rng).unwrap();
let b_random_incoming_border = incoming_connections.choose(rng).unwrap();
let b_random_outgoing_border = outgoing_connections.choose(rng).unwrap();
if random_incoming_border.id == random_outgoing_border.id
|| b_random_incoming_border.id == b_random_outgoing_border.id
{
continue;
}
// TODO calculate
let distance_on_map = Distance::meters(2000.0);
// TODO randomize
// having random trip distance happening offscreen will allow things
// like very short car trips, representing larger car trip happening mostly offscreen
let distance_outside_map = Distance::meters(rng.gen_range(0.0, 20_000.0));
let mode = select_trip_mode(distance_on_map + distance_outside_map, rng);
let (goto_work, return_home) = match (
SpawnTrip::new(
TripEndpoint::Border(random_incoming_border.id, None),
TripEndpoint::Border(random_outgoing_border.id, None),
mode,
map,
),
SpawnTrip::new(
TripEndpoint::Border(b_random_incoming_border.id, None),
TripEndpoint::Border(b_random_outgoing_border.id, None),
mode,
map,
),
) {
(Some(t1), Some(t2)) => (t1, t2),
// Skip the person if either trip can't be created.
_ => continue,
};
// TODO more reasonable time schedule, rush hour peak etc
let depart_am = rand_time(
rng,
Time::START_OF_DAY + Duration::hours(0),
Time::START_OF_DAY + Duration::hours(12),
);
let depart_pm = rand_time(
rng,
Time::START_OF_DAY + Duration::hours(12),
Time::START_OF_DAY + Duration::hours(24),
);
s.people.push(PersonSpec {
id: PersonID(s.people.len()),
orig_id: None,
trips: vec![
IndividTrip::new(depart_am, goto_work),
IndividTrip::new(depart_pm, return_home),
],
});
for mut person in timer
.parallelize(
"create border trips",
Parallelism::Fastest,
std::iter::repeat_with(|| abstutil::fork_rng(rng))
.take(num_residences)
.collect(),
|mut forked_rng| {
border_person(
&incoming_connections,
&outgoing_connections,
map,
&mut forked_rng,
)
},
)
.into_iter()
.flatten()
{
person.id = PersonID(s.people.len());
s.people.push(person);
}
s
}
}
// Make a person going from their home to a random workplace, then back again later.
fn robot_person(
home: BuildingID,
workplaces: &Vec<BuildingID>,
map: &Map,
rng: &mut XorShiftRng,
) -> Option<PersonSpec> {
let work = *workplaces.choose(rng).unwrap();
if home == work {
// working and living in the same building
return None;
}
// Decide mode based on walking distance. If the buildings aren't connected,
// probably a bug in importing; just skip this person.
let dist = map
.pathfind(PathRequest {
start: map.get_b(home).sidewalk_pos,
end: map.get_b(work).sidewalk_pos,
constraints: PathConstraints::Pedestrian,
})?
.total_length();
// TODO If home or work is in an access-restricted zone (like a living street),
// then probably don't drive there. Actually, it depends on the specific tagging;
// access=no in the US usually means a gated community.
let mode = select_trip_mode(dist, rng);
// TODO This will cause a single morning and afternoon rush. Outside of these times,
// it'll be really quiet. Probably want a normal distribution centered around these
// peak times, but with a long tail.
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) {
// hacky hack to get some background traffic
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),
);
}
// Skip the person if either trip can't be created.
let goto_work = SpawnTrip::new(
TripEndpoint::Bldg(home),
TripEndpoint::Bldg(work),
mode,
map,
)?;
let return_home = SpawnTrip::new(
TripEndpoint::Bldg(work),
TripEndpoint::Bldg(home),
mode,
map,
)?;
Some(PersonSpec {
// Fix this outside the parallelism
id: PersonID(0),
orig_id: None,
trips: vec![
IndividTrip::new(depart_am, goto_work),
IndividTrip::new(depart_pm, return_home),
],
})
}
fn border_person(
incoming_connections: &Vec<&Intersection>,
outgoing_connections: &Vec<&Intersection>,
map: &Map,
rng: &mut XorShiftRng,
) -> Option<PersonSpec> {
// TODO it would be nice to weigh border points by for example lane count
let random_incoming_border = incoming_connections.choose(rng).unwrap();
let random_outgoing_border = outgoing_connections.choose(rng).unwrap();
let b_random_incoming_border = incoming_connections.choose(rng).unwrap();
let b_random_outgoing_border = outgoing_connections.choose(rng).unwrap();
if random_incoming_border.id == random_outgoing_border.id
|| b_random_incoming_border.id == b_random_outgoing_border.id
{
return None;
}
// TODO calculate
let distance_on_map = Distance::meters(2000.0);
// TODO randomize
// having random trip distance happening offscreen will allow things
// like very short car trips, representing larger car trip happening mostly offscreen
let distance_outside_map = Distance::meters(rng.gen_range(0.0, 20_000.0));
let mode = select_trip_mode(distance_on_map + distance_outside_map, rng);
let goto_work = SpawnTrip::new(
TripEndpoint::Border(random_incoming_border.id, None),
TripEndpoint::Border(random_outgoing_border.id, None),
mode,
map,
)?;
let return_home = SpawnTrip::new(
TripEndpoint::Border(b_random_incoming_border.id, None),
TripEndpoint::Border(b_random_outgoing_border.id, None),
mode,
map,
)?;
// TODO more reasonable time schedule, rush hour peak etc
let depart_am = rand_time(
rng,
Time::START_OF_DAY + Duration::hours(0),
Time::START_OF_DAY + Duration::hours(12),
);
let depart_pm = rand_time(
rng,
Time::START_OF_DAY + Duration::hours(12),
Time::START_OF_DAY + Duration::hours(24),
);
Some(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 {
// TODO Make this probabilistic
// for example probability of walking currently has massive differences