synthpop/
modifier.rs

1extern crate rand;
2
3use std::collections::BTreeSet;
4
5use rand::Rng;
6use rand_xorshift::XorShiftRng;
7use serde::{Deserialize, Serialize};
8
9use abstutil::Timer;
10use geom::{Duration, Time};
11use map_model::Map;
12
13use crate::{Scenario, TripMode};
14
15/// Transforms an existing Scenario before instantiating it.
16#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
17pub enum ScenarioModifier {
18    RepeatDays(usize),
19    RepeatDaysNoise {
20        days: usize,
21        departure_time_noise: Duration,
22    },
23    ChangeMode {
24        pct_ppl: usize,
25        departure_filter: (Time, Time),
26        from_modes: BTreeSet<TripMode>,
27        /// If `None`, then just cancel the trip.
28        to_mode: Option<TripMode>,
29    },
30    /// Scenario name
31    AddExtraTrips(String),
32}
33
34impl ScenarioModifier {
35    /// If this modifies scenario_name, then that means prebaked results don't match up and
36    /// shouldn't be used.
37    pub fn apply(&self, map: &Map, mut s: Scenario, rng: &mut XorShiftRng) -> Scenario {
38        match self {
39            ScenarioModifier::RepeatDays(n) => repeat_days(s, *n, None, rng),
40            ScenarioModifier::RepeatDaysNoise {
41                days,
42                departure_time_noise,
43            } => repeat_days(s, *days, Some(*departure_time_noise), rng),
44            ScenarioModifier::ChangeMode {
45                pct_ppl,
46                departure_filter,
47                from_modes,
48                to_mode,
49            } => {
50                for (idx, person) in s.people.iter_mut().enumerate() {
51                    // This is "stable" as percentage increases. If you modify 10% of people in one
52                    // run, then modify 11% in another, the modified people in the 11% run will be
53                    // a strict superset of the 10% run.
54                    if idx % 100 > *pct_ppl {
55                        continue;
56                    }
57                    let mut cancel_rest = false;
58                    for trip in &mut person.trips {
59                        if cancel_rest {
60                            trip.modified = true;
61                            trip.cancelled = true;
62                            continue;
63                        }
64
65                        if trip.depart < departure_filter.0 || trip.depart > departure_filter.1 {
66                            continue;
67                        }
68                        if !from_modes.contains(&trip.mode) {
69                            continue;
70                        }
71                        if let Some(to_mode) = *to_mode {
72                            trip.mode = to_mode;
73                            trip.modified = true;
74                        } else {
75                            trip.modified = true;
76                            trip.cancelled = true;
77                            // The next trip assumes we're at the destination of this cancelled
78                            // trip, and so on. Have to cancel the rest.
79                            cancel_rest = true;
80                        }
81                    }
82                }
83                s
84            }
85            // TODO This doesn't work on web!
86            ScenarioModifier::AddExtraTrips(name) => {
87                let other: Scenario = abstio::must_read_object(
88                    abstio::path_scenario(map.get_name(), name),
89                    &mut Timer::throwaway(),
90                );
91                for mut p in other.people {
92                    for trip in &mut p.trips {
93                        trip.modified = true;
94                    }
95                    s.people.push(p);
96                }
97                s
98            }
99        }
100    }
101
102    pub fn describe(&self) -> String {
103        match self {
104            ScenarioModifier::RepeatDays(n) => format!("repeat the entire day {} times", n),
105            ScenarioModifier::RepeatDaysNoise {
106                days,
107                departure_time_noise,
108            } => format!(
109                "repeat the entire day {} times with +/- {} noise on each departure",
110                days, departure_time_noise
111            ),
112            ScenarioModifier::ChangeMode {
113                pct_ppl,
114                to_mode,
115                departure_filter,
116                from_modes,
117            } => format!(
118                "change all trips for {}% of people of types {:?} leaving between {} and {} to \
119                 {:?}",
120                pct_ppl,
121                from_modes,
122                departure_filter.0.ampm_tostring(),
123                departure_filter.1.ampm_tostring(),
124                to_mode.map(|m| m.verb())
125            ),
126            ScenarioModifier::AddExtraTrips(name) => format!("Add extra trips from {}", name),
127        }
128    }
129}
130
131// Utter hack. Blindly repeats all trips taken by each person every day.
132//
133// What happens if the last place a person winds up in a day isn't the same as where their
134// first trip the next starts? Will crash as soon as the scenario is instantiated, through
135// check_schedule().
136//
137// The bigger problem is that any people that seem to require multiple cars... will wind up
138// needing LOTS of cars.
139fn repeat_days(
140    mut s: Scenario,
141    days: usize,
142    noise: Option<Duration>,
143    rng: &mut XorShiftRng,
144) -> Scenario {
145    s.scenario_name = format!("{} (repeated {} days)", s.scenario_name, days);
146    for person in &mut s.people {
147        let mut trips = Vec::new();
148        let mut offset = Duration::ZERO;
149        for _ in 0..days {
150            for trip in &person.trips {
151                let mut new = trip.clone();
152                new.depart += offset;
153                if let Some(noise_v) = noise {
154                    // + or - noise_v
155                    let noise_rnd = Duration::seconds(
156                        rng.gen_range((0.0)..=(2.0 * noise_v.inner_seconds() as f64)),
157                    ) - noise_v;
158                    new.depart = new.depart.clamped_sub(noise_rnd);
159                }
160                new.modified = true;
161                trips.push(new);
162            }
163            offset += Duration::hours(24);
164        }
165        person.trips = trips;
166    }
167    s
168}