1use anyhow::Result;
7use rand::seq::SliceRandom;
8use rand::Rng;
9use rand_xorshift::XorShiftRng;
10
11use abstutil::{prettyprint_usize, Timer};
12use geom::{Distance, Duration, Time};
13use map_model::{BuildingID, BuildingType, Map, PathConstraints, PathRequest};
14
15use crate::{IndividTrip, PersonSpec, Scenario, TripEndpoint, TripMode, TripPurpose};
16
17use crate::make::{fork_rng, ScenarioGenerator};
18
19impl ScenarioGenerator {
20 pub fn proletariat_robot(map: &Map, rng: &mut XorShiftRng, timer: &mut Timer) -> Scenario {
22 let mut residents: Vec<BuildingID> = Vec::new();
23 let mut workers: Vec<BuildingID> = Vec::new();
24
25 let mut num_bldg_residential = 0;
26 let mut num_bldg_commercial = 0;
27 let mut num_bldg_mixed_residential_commercial = 0;
28 for b in map.all_buildings() {
29 match b.bldg_type {
30 BuildingType::Residential { num_residents, .. } => {
31 for _ in 0..num_residents {
32 residents.push(b.id);
33 }
34 num_bldg_residential += 1;
35 }
36 BuildingType::ResidentialCommercial(resident_cap, worker_cap) => {
37 for _ in 0..resident_cap {
38 residents.push(b.id);
39 }
40 for _ in 0..worker_cap {
41 workers.push(b.id);
42 }
43 num_bldg_mixed_residential_commercial += 1;
44 }
45 BuildingType::Commercial(worker_cap) => {
46 for _ in 0..worker_cap {
47 workers.push(b.id);
48 }
49 num_bldg_commercial += 1;
50 }
51 BuildingType::Empty => {}
52 }
53 }
54
55 residents.shuffle(rng);
56 workers.shuffle(rng);
57
58 let mut s = Scenario::empty(map, "random people going to and from work");
59 s.only_seed_buses = None;
61
62 let residents_cap = residents.len();
63 let workers_cap = workers.len();
64
65 let trip_saturation = 1.2;
70 let num_trips = (trip_saturation * (residents_cap + workers_cap) as f64) as usize;
71
72 let lower_bound_prob = 0.05;
74 let upper_bound_prob = 0.90;
75 let prob_local_resident = if workers_cap == 0 {
76 lower_bound_prob
77 } else {
78 f64::min(
79 upper_bound_prob,
80 f64::max(lower_bound_prob, residents_cap as f64 / num_trips as f64),
81 )
82 };
83 let prob_local_worker = f64::min(
84 upper_bound_prob,
85 f64::max(lower_bound_prob, workers_cap as f64 / num_trips as f64),
86 );
87
88 debug!(
89 "BUILDINGS - workplaces: {}, residences: {}, mixed: {}",
90 prettyprint_usize(num_bldg_commercial),
91 prettyprint_usize(num_bldg_residential),
92 prettyprint_usize(num_bldg_mixed_residential_commercial)
93 );
94 debug!(
95 "CAPACITY - workers_cap: {}, residents_cap: {}, prob_local_worker: {:.1}%, \
96 prob_local_resident: {:.1}%",
97 prettyprint_usize(workers_cap),
98 prettyprint_usize(residents_cap),
99 prob_local_worker * 100.,
100 prob_local_resident * 100.
101 );
102
103 let mut num_trips_local = 0;
104 let mut num_trips_commuting_in = 0;
105 let mut num_trips_commuting_out = 0;
106 let mut num_trips_passthru = 0;
107 timer.start("create people");
108
109 let commuter_borders: Vec<TripEndpoint> = map
116 .all_outgoing_borders()
117 .into_iter()
118 .filter(|b| b.is_incoming_border())
119 .map(|b| TripEndpoint::Border(b.id))
120 .collect();
121 let person_params = (0..num_trips)
122 .filter_map(|_| {
123 let (is_local_resident, is_local_worker) = (
124 rng.gen_bool(prob_local_resident),
125 rng.gen_bool(prob_local_worker),
126 );
127 let home = if is_local_resident {
128 if let Some(residence) = residents.pop() {
129 TripEndpoint::Building(residence)
130 } else {
131 *commuter_borders.choose(rng)?
132 }
133 } else {
134 *commuter_borders.choose(rng)?
135 };
136
137 let work = if is_local_worker {
138 if let Some(workplace) = workers.pop() {
139 TripEndpoint::Building(workplace)
140 } else {
141 *commuter_borders.choose(rng)?
142 }
143 } else {
144 *commuter_borders.choose(rng)?
145 };
146
147 match (&home, &work) {
148 (TripEndpoint::Building(_), TripEndpoint::Building(_)) => {
149 num_trips_local += 1;
150 }
151 (TripEndpoint::Building(_), TripEndpoint::Border(_)) => {
152 num_trips_commuting_out += 1;
153 }
154 (TripEndpoint::Border(_), TripEndpoint::Building(_)) => {
155 num_trips_commuting_in += 1;
156 }
157 (TripEndpoint::Border(_), TripEndpoint::Border(_)) => {
158 num_trips_passthru += 1;
159 }
160 (TripEndpoint::SuddenlyAppear(_), _) => unreachable!(),
161 (_, TripEndpoint::SuddenlyAppear(_)) => unreachable!(),
162 };
163
164 Some((home, work, fork_rng(rng)))
165 })
166 .collect();
167
168 s.people.extend(
169 timer
170 .parallelize(
171 "create people: making PersonSpec from endpoints",
172 person_params,
173 |(home, work, mut rng)| match create_prole(home, work, map, &mut rng) {
174 Ok(person) => Some(person),
175 Err(e) => {
176 trace!("Unable to create person. error: {}", e);
177 None
178 }
179 },
180 )
181 .into_iter()
182 .flatten(),
183 );
184
185 timer.stop("create people");
186
187 info!(
188 "TRIPS - total: {}, local: {}, commuting_in: {}, commuting_out: {}, passthru: {}, \
189 errored: {}, leftover_resident_capacity: {}, leftover_worker_capacity: {}",
190 prettyprint_usize(num_trips),
191 prettyprint_usize(num_trips_local),
192 prettyprint_usize(num_trips_commuting_in),
193 prettyprint_usize(num_trips_commuting_out),
194 prettyprint_usize(num_trips_passthru),
195 prettyprint_usize(num_trips - s.people.len()),
196 prettyprint_usize(residents.len()),
197 prettyprint_usize(workers.len()),
198 );
199 s
200 }
201}
202
203fn create_prole(
204 home: TripEndpoint,
205 work: TripEndpoint,
206 map: &Map,
207 rng: &mut XorShiftRng,
208) -> Result<PersonSpec> {
209 if home == work {
210 bail!("TODO: handle working and living in the same building");
213 }
214
215 let mode = match (&home, &work) {
216 (TripEndpoint::Building(home_bldg), TripEndpoint::Building(work_bldg)) => {
218 let dist = if let Some(path) = PathRequest::between_buildings(
221 map,
222 *home_bldg,
223 *work_bldg,
224 PathConstraints::Pedestrian,
225 )
226 .and_then(|req| map.pathfind(req).ok())
227 {
228 path.total_length()
229 } else {
230 bail!("no path found");
231 };
232
233 select_trip_mode(dist, rng)
237 }
238 _ => TripMode::Drive,
240 };
241
242 let mut depart_am = rand_time(
246 rng,
247 Time::START_OF_DAY + Duration::hours(7),
248 Time::START_OF_DAY + Duration::hours(10),
249 );
250 let mut depart_pm = rand_time(
251 rng,
252 Time::START_OF_DAY + Duration::hours(17),
253 Time::START_OF_DAY + Duration::hours(19),
254 );
255
256 if rng.gen_bool(0.1) {
257 depart_am = rand_time(
259 rng,
260 Time::START_OF_DAY + Duration::hours(0),
261 Time::START_OF_DAY + Duration::hours(12),
262 );
263 depart_pm = rand_time(
264 rng,
265 Time::START_OF_DAY + Duration::hours(12),
266 Time::START_OF_DAY + Duration::hours(24),
267 );
268 }
269
270 Ok(PersonSpec {
271 orig_id: None,
272 trips: vec![
273 IndividTrip::new(depart_am, TripPurpose::Work, home, work, mode),
274 IndividTrip::new(depart_pm, TripPurpose::Home, work, home, mode),
275 ],
276 })
277}
278
279fn select_trip_mode(distance: Distance, rng: &mut XorShiftRng) -> TripMode {
280 if distance < Distance::miles(0.5) {
292 return TripMode::Walk;
293 }
294
295 if distance < Distance::miles(3.0) {
297 if rng.gen_bool(0.15) {
298 return TripMode::Bike;
299 }
300 if rng.gen_bool(0.05) {
301 return TripMode::Walk;
302 }
303 }
304
305 if rng.gen_bool(0.005) {
307 return TripMode::Bike;
308 }
309 if rng.gen_bool(0.3) {
311 return TripMode::Transit;
312 }
313
314 TripMode::Drive
316}
317
318fn rand_time(rng: &mut XorShiftRng, low: Time, high: Time) -> Time {
319 assert!(high > low);
320 Time::START_OF_DAY + Duration::seconds(rng.gen_range(low.inner_seconds()..high.inner_seconds()))
321}