1use std::collections::{HashMap, HashSet};
5use std::sync::atomic::{AtomicBool, Ordering};
6
7use crate::hyperpath_queue::PriorityQueue;
8use crate::transit_network::Link;
9
10pub struct Strategy<'a> {
12 pub labels: HashMap<String, f64>,
14 pub freqs: HashMap<String, f64>,
17 pub a_set: Vec<&'a Link>,
19}
20
21pub(crate) const ALPHA: f64 = 1.0;
26
27pub static VERBOSE: AtomicBool = AtomicBool::new(false);
28
29pub(crate) fn verbose() -> bool {
30 VERBOSE.load(Ordering::Relaxed)
31}
32
33pub fn find_optimal_strategy<'a>(
34 all_links: &'a [Link],
35 all_stops: &HashSet<String>,
36 destination: &str,
37) -> Strategy<'a> {
38 if verbose() {
40 println!("1.1 Initialization \\\\");
41 }
42 let mut u: HashMap<String, f64> = HashMap::with_capacity(all_stops.len());
43 let mut f: HashMap<String, f64> = HashMap::with_capacity(all_stops.len());
44 for stop in all_stops {
45 if verbose() {
46 println!("$f_{{{}}} = 0$ \\\\ ", stop);
47 }
48 f.insert(stop.clone(), 0.0);
49 if stop == destination {
50 if verbose() {
51 println!("$u_{{{}}} = 0$ \\\\ ", destination);
52 }
53 u.insert(stop.clone(), 0.0);
54 continue;
55 }
56 if verbose() {
57 println!("$u_{{{}}} = Infinity$ \\\\ ", stop);
58 }
59 u.insert(stop.clone(), f64::INFINITY);
60 }
61
62 let mut overline_a: Vec<Option<&'a Link>> = Vec::with_capacity(all_links.len() / 2);
63 let mut a_set_idx: HashMap<&'a str, Vec<usize>> = HashMap::new();
67
68 let mut links_by_to_node: HashMap<&'a str, Vec<&'a Link>> = HashMap::new();
69 for link in all_links {
70 links_by_to_node
71 .entry(link.to_node.as_str())
72 .or_default()
73 .push(link);
74 }
75
76 let mut entries: HashMap<&'a str, Vec<usize>> = HashMap::with_capacity(all_links.len());
77 let mut pq = PriorityQueue::with_capacity(all_links.len());
78 for link in all_links {
79 let priority = u.get(&link.to_node).copied().unwrap_or(0.0) + link.travel_cost;
80 let id = pq.push(link, priority);
81 entries
82 .entry(link.from_node.as_str())
83 .or_default()
84 .push(id);
85 }
86 pq.init();
87 if verbose() {
88 pq.print();
89 }
90 while pq.len() > 0 {
91 if verbose() {
93 pq.print();
94 }
95 let entry_id = match pq.pop() {
96 Some(id) => id,
97 None => break,
98 };
99 let priority = pq.priority(entry_id);
100 if priority.is_infinite() && priority > 0.0 {
101 break;
102 }
103 let a = pq.link(entry_id);
104 let i = a.from_node.as_str();
105 let j = a.to_node.as_str();
106 let sum_uc = u.get(j).copied().unwrap_or(0.0) + a.travel_cost;
107
108 if verbose() {
110 println!("Process: $a = (i, j) = ({}, {})$, \\\\ ", i, j);
111 }
112 let f_i = f.get(i).copied().unwrap_or(0.0);
116 if f_i.is_infinite() {
117 continue;
118 }
119 let u_i = u.get(i).copied().unwrap_or(0.0);
120 if u_i < sum_uc {
121 continue;
122 }
123 if verbose() {
124 println!(
125 "\\quad $u_i < u_j + c_a : {} < {}$ - FALSE \\\\ ",
126 u_i, sum_uc
127 );
128 }
129 if a.headway <= 0.0 {
130 u.insert(i.to_string(), sum_uc);
136 f.insert(i.to_string(), f64::INFINITY);
137 let indices = a_set_idx.entry(i).or_default();
138 for idx in indices.iter() {
139 overline_a[*idx] = None;
140 }
141 indices.clear();
142 overline_a.push(Some(a));
143 indices.push(overline_a.len() - 1);
144 if verbose() {
145 println!(
146 "\\quad no-wait link: $u_i = u_j + c_a = {}$, $f_i = \\infty$, basket replaced by $({}, {})$ \\\\ ",
147 sum_uc, i, j
148 );
149 }
150 } else {
151 let freq = 1.0 / a.headway;
152 if verbose() {
153 println!("\\quad $f_a = {}$ \\\\ ", freq);
154 println!("\\quad $u_j + c_a = {}$ \\\\ ", sum_uc);
155 println!("\\quad $u_i = {}$ \\\\ ", u_i);
156 }
157 let new_u = if f_i == 0.0 {
158 (ALPHA + freq * sum_uc) / freq
160 } else {
161 (f_i * u_i + freq * sum_uc) / (f_i + freq)
162 };
163 u.insert(i.to_string(), new_u);
164 f.insert(i.to_string(), f_i + freq);
165 overline_a.push(Some(a));
166 a_set_idx.entry(i).or_default().push(overline_a.len() - 1);
167 if verbose() {
168 println!(
169 "\\quad$u_i = \\frac{{f_i * u_i + f_a * (u_j + c_a)}}{{f_i + f_a}} = {}$, $f_i = {}$ \\\\ ",
170 new_u,
171 f_i + freq
172 );
173 println!(
174 "\\quad $\\overline{{A}} = \\overline{{A}} \\cup {{({}, {})}}$ \\\\ ",
175 i, j
176 );
177 }
178 }
179
180 if let Some(links_to_update) = links_by_to_node.get(i) {
181 for link in links_to_update {
182 if let Some(i_entries) = entries.get(link.from_node.as_str()) {
183 for &eid in i_entries {
184 let entry_link = pq.link(eid);
185 if entry_link.to_node == i && entry_link.from_node == link.from_node {
186 let new_priority =
187 u.get(i).copied().unwrap_or(0.0) + link.travel_cost;
188 pq.update(eid, new_priority);
189 break;
190 }
191 }
192 }
193 }
194 }
195 if verbose() {
196 println!("Node labels: \\\\");
197 for s in all_stops {
198 println!(
199 "${} -> (u_i, f_i) = ({}, {})$ \\\\ ",
200 s,
201 u.get(s).copied().unwrap_or(0.0),
202 f.get(s).copied().unwrap_or(0.0)
203 );
204 }
205 }
206 }
207
208 let a_set: Vec<&'a Link> = overline_a.into_iter().flatten().collect();
211
212 Strategy {
213 labels: u,
214 freqs: f,
215 a_set,
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_hyper_paths() {
225 VERBOSE.store(true, Ordering::Relaxed);
226 let all_nodes: HashSet<String> = ["A", "X", "X2", "Y", "Y3", "B"]
227 .iter()
228 .map(|s| s.to_string())
229 .collect();
230 let all_links = vec![
231 Link::new("A", "B", "Line 1", 25.0, 6.0),
232 Link::new("A", "X2", "Line 2", 7.0, 6.0),
233 Link::new("X2", "X", "Line 2", 0.0, 0.0),
234 Link::new("X", "X2", "Line 2", 0.0, 6.0),
235 Link::new("X2", "Y", "Line 2", 6.0, 0.0),
236 Link::new("Y3", "Y", "Line 3", 0.0, 15.0),
237 Link::new("Y", "B", "Line 4", 10.0, 3.0),
238 Link::new("X", "Y3", "Line 3", 4.0, 15.0),
239 Link::new("Y", "Y3", "Line 3", 0.0, 15.0),
240 Link::new("Y3", "B", "Line 3", 4.0, 0.0),
241 ];
242 let destination_node = "B";
243 let ops = find_optimal_strategy(&all_links, &all_nodes, destination_node);
244
245 const EPS: f64 = 1e-9;
246
247 let expected_labels: HashMap<&str, f64> = HashMap::from([
250 ("A", 27.75),
251 ("X", 19.071428571428573),
252 ("X2", 17.5),
253 ("Y", 11.5),
254 ("Y3", 4.0),
255 ("B", 0.0),
256 ]);
257 let expected_freqs: HashMap<&str, f64> = HashMap::from([
259 ("A", 1.0 / 3.0),
260 ("X", 7.0 / 30.0),
261 ("X2", f64::INFINITY),
262 ("Y", 0.4),
263 ("Y3", f64::INFINITY),
264 ("B", 0.0),
265 ]);
266 let expected_a_set: Vec<&Link> = vec![
268 &all_links[9],
270 &all_links[8],
272 &all_links[7],
274 &all_links[6],
276 &all_links[4],
278 &all_links[3],
280 &all_links[1],
282 &all_links[0],
284 ];
285
286 assert_eq!(
287 ops.labels.len(),
288 expected_labels.len(),
289 "Incorrect number of labels"
290 );
291 assert_eq!(
292 ops.freqs.len(),
293 expected_freqs.len(),
294 "Incorrect number of frequencies"
295 );
296 assert_eq!(
297 ops.a_set.len(),
298 expected_a_set.len(),
299 "Incorrect number of links in attractive set"
300 );
301
302 for (k, v) in &ops.labels {
303 assert!(
304 expected_labels.contains_key(k.as_str()),
305 "Incorrect label key {} has met",
306 k
307 );
308 let want = expected_labels[k.as_str()];
309 assert!(
310 (v - want).abs() <= EPS,
311 "Incorrect label value for node {}: got {}, want {}",
312 k,
313 v,
314 want
315 );
316 }
317 for (k, v) in &ops.freqs {
318 assert!(
319 expected_freqs.contains_key(k.as_str()),
320 "Incorrect frequency key {} has met",
321 k
322 );
323 let want = expected_freqs[k.as_str()];
324 if want.is_infinite() {
325 assert!(
326 v.is_infinite() && *v > 0.0,
327 "Frequency for node {} must be +Inf, got {}",
328 k,
329 v
330 );
331 } else {
332 assert!(
333 (v - want).abs() <= EPS,
334 "Incorrect frequency value for node {}: got {}, want {}",
335 k,
336 v,
337 want
338 );
339 }
340 }
341 for (i, v) in ops.a_set.iter().enumerate() {
342 println!("{:?} {:?}", v, expected_a_set[i]);
343 assert!(
344 std::ptr::eq(*v, expected_a_set[i]),
345 "Incorrect link in attractive set at index {}",
346 i
347 );
348 }
349 }
350
351 #[test]
352 fn test_no_wait_replaces_basket() {
353 let all_nodes: HashSet<String> =
357 ["I", "W", "D"].iter().map(|s| s.to_string()).collect();
358 let all_links = vec![
359 Link::new("I", "D", "Bus", 4.0, 6.0),
361 Link::new("I", "W", "Walk", 3.0, 0.0),
363 Link::new("W", "D", "Walk", 2.0, 0.0),
365 ];
366 let ops = find_optimal_strategy(&all_links, &all_nodes, "D");
367
368 assert!((ops.labels["I"] - 5.0).abs() <= 1e-12);
369 assert!((ops.labels["W"] - 2.0).abs() <= 1e-12);
370 assert!(ops.freqs["I"].is_infinite());
371 assert!(ops.freqs["W"].is_infinite());
372
373 assert_eq!(
375 ops.a_set.len(),
376 2,
377 "basket of I must hold only the no-wait link"
378 );
379 for link in &ops.a_set {
380 assert_eq!(
381 link.headway, 0.0,
382 "only no-wait links expected in the attractive set"
383 );
384 }
385 }
386}