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.entry(link.from_node.as_str()).or_default().push(id);
82 }
83 pq.init();
84 if verbose() {
85 pq.print();
86 }
87 while pq.len() > 0 {
88 if verbose() {
90 pq.print();
91 }
92 let entry_id = match pq.pop() {
93 Some(id) => id,
94 None => break,
95 };
96 let priority = pq.priority(entry_id);
97 if priority.is_infinite() && priority > 0.0 {
98 break;
99 }
100 let a = pq.link(entry_id);
101 let i = a.from_node.as_str();
102 let j = a.to_node.as_str();
103 let sum_uc = u.get(j).copied().unwrap_or(0.0) + a.travel_cost;
104
105 if verbose() {
107 println!("Process: $a = (i, j) = ({}, {})$, \\\\ ", i, j);
108 }
109 let f_i = f.get(i).copied().unwrap_or(0.0);
113 if f_i.is_infinite() {
114 continue;
115 }
116 let u_i = u.get(i).copied().unwrap_or(0.0);
117 if u_i <= sum_uc {
144 continue;
145 }
146 if verbose() {
147 println!(
148 "\\quad $u_i \\leq u_j + c_a : {} \\leq {}$ - FALSE \\\\ ",
149 u_i, sum_uc
150 );
151 }
152 if a.headway <= 0.0 {
153 u.insert(i.to_string(), sum_uc);
159 f.insert(i.to_string(), f64::INFINITY);
160 let indices = a_set_idx.entry(i).or_default();
161 for idx in indices.iter() {
162 overline_a[*idx] = None;
163 }
164 indices.clear();
165 overline_a.push(Some(a));
166 indices.push(overline_a.len() - 1);
167 if verbose() {
168 println!(
169 "\\quad no-wait link: $u_i = u_j + c_a = {}$, $f_i = \\infty$, basket replaced by $({}, {})$ \\\\ ",
170 sum_uc, i, j
171 );
172 }
173 } else {
174 let freq = 1.0 / a.headway;
175 if verbose() {
176 println!("\\quad $f_a = {}$ \\\\ ", freq);
177 println!("\\quad $u_j + c_a = {}$ \\\\ ", sum_uc);
178 println!("\\quad $u_i = {}$ \\\\ ", u_i);
179 }
180 let new_u = if f_i == 0.0 {
181 (ALPHA + freq * sum_uc) / freq
183 } else {
184 (f_i * u_i + freq * sum_uc) / (f_i + freq)
185 };
186 u.insert(i.to_string(), new_u);
187 f.insert(i.to_string(), f_i + freq);
188 overline_a.push(Some(a));
189 a_set_idx.entry(i).or_default().push(overline_a.len() - 1);
190 if verbose() {
191 println!(
192 "\\quad$u_i = \\frac{{f_i * u_i + f_a * (u_j + c_a)}}{{f_i + f_a}} = {}$, $f_i = {}$ \\\\ ",
193 new_u,
194 f_i + freq
195 );
196 println!(
197 "\\quad $\\overline{{A}} = \\overline{{A}} \\cup {{({}, {})}}$ \\\\ ",
198 i, j
199 );
200 }
201 }
202
203 if let Some(links_to_update) = links_by_to_node.get(i) {
204 for link in links_to_update {
205 if let Some(i_entries) = entries.get(link.from_node.as_str()) {
206 for &eid in i_entries {
207 let entry_link = pq.link(eid);
208 if entry_link.to_node == i && entry_link.from_node == link.from_node {
209 let new_priority = u.get(i).copied().unwrap_or(0.0) + link.travel_cost;
210 pq.update(eid, new_priority);
211 break;
212 }
213 }
214 }
215 }
216 }
217 if verbose() {
218 println!("Node labels: \\\\");
219 for s in all_stops {
220 println!(
221 "${} -> (u_i, f_i) = ({}, {})$ \\\\ ",
222 s,
223 u.get(s).copied().unwrap_or(0.0),
224 f.get(s).copied().unwrap_or(0.0)
225 );
226 }
227 }
228 }
229
230 let a_set: Vec<&'a Link> = overline_a.into_iter().flatten().collect();
233
234 Strategy {
235 labels: u,
236 freqs: f,
237 a_set,
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_hyper_paths() {
247 VERBOSE.store(true, Ordering::Relaxed);
248 let all_nodes: HashSet<String> = ["A", "X", "X2", "Y", "Y3", "B"]
249 .iter()
250 .map(|s| s.to_string())
251 .collect();
252 let all_links = vec![
253 Link::new("A", "B", "Line 1", 25.0, 6.0),
254 Link::new("A", "X2", "Line 2", 7.0, 6.0),
255 Link::new("X2", "X", "Line 2", 0.0, 0.0),
256 Link::new("X", "X2", "Line 2", 0.0, 6.0),
257 Link::new("X2", "Y", "Line 2", 6.0, 0.0),
258 Link::new("Y3", "Y", "Line 3", 0.0, 15.0),
259 Link::new("Y", "B", "Line 4", 10.0, 3.0),
260 Link::new("X", "Y3", "Line 3", 4.0, 15.0),
261 Link::new("Y", "Y3", "Line 3", 0.0, 15.0),
262 Link::new("Y3", "B", "Line 3", 4.0, 0.0),
263 ];
264 let destination_node = "B";
265 let ops = find_optimal_strategy(&all_links, &all_nodes, destination_node);
266
267 const EPS: f64 = 1e-9;
268
269 let expected_labels: HashMap<&str, f64> = HashMap::from([
272 ("A", 27.75),
273 ("X", 19.071428571428573),
274 ("X2", 17.5),
275 ("Y", 11.5),
276 ("Y3", 4.0),
277 ("B", 0.0),
278 ]);
279 let expected_freqs: HashMap<&str, f64> = HashMap::from([
281 ("A", 1.0 / 3.0),
282 ("X", 7.0 / 30.0),
283 ("X2", f64::INFINITY),
284 ("Y", 0.4),
285 ("Y3", f64::INFINITY),
286 ("B", 0.0),
287 ]);
288 let expected_a_set: Vec<&Link> = vec![
290 &all_links[9],
292 &all_links[8],
294 &all_links[7],
296 &all_links[6],
298 &all_links[4],
300 &all_links[3],
302 &all_links[1],
304 &all_links[0],
306 ];
307
308 assert_eq!(
309 ops.labels.len(),
310 expected_labels.len(),
311 "Incorrect number of labels"
312 );
313 assert_eq!(
314 ops.freqs.len(),
315 expected_freqs.len(),
316 "Incorrect number of frequencies"
317 );
318 assert_eq!(
319 ops.a_set.len(),
320 expected_a_set.len(),
321 "Incorrect number of links in attractive set"
322 );
323
324 for (k, v) in &ops.labels {
325 assert!(
326 expected_labels.contains_key(k.as_str()),
327 "Incorrect label key {} has met",
328 k
329 );
330 let want = expected_labels[k.as_str()];
331 assert!(
332 (v - want).abs() <= EPS,
333 "Incorrect label value for node {}: got {}, want {}",
334 k,
335 v,
336 want
337 );
338 }
339 for (k, v) in &ops.freqs {
340 assert!(
341 expected_freqs.contains_key(k.as_str()),
342 "Incorrect frequency key {} has met",
343 k
344 );
345 let want = expected_freqs[k.as_str()];
346 if want.is_infinite() {
347 assert!(
348 v.is_infinite() && *v > 0.0,
349 "Frequency for node {} must be +Inf, got {}",
350 k,
351 v
352 );
353 } else {
354 assert!(
355 (v - want).abs() <= EPS,
356 "Incorrect frequency value for node {}: got {}, want {}",
357 k,
358 v,
359 want
360 );
361 }
362 }
363 for (i, v) in ops.a_set.iter().enumerate() {
364 println!("{:?} {:?}", v, expected_a_set[i]);
365 assert!(
366 std::ptr::eq(*v, expected_a_set[i]),
367 "Incorrect link in attractive set at index {}",
368 i
369 );
370 }
371 }
372
373 #[test]
374 fn test_no_wait_replaces_basket() {
375 let all_nodes: HashSet<String> = ["I", "W", "D"].iter().map(|s| s.to_string()).collect();
379 let all_links = vec![
380 Link::new("I", "D", "Bus", 4.0, 6.0),
382 Link::new("I", "W", "Walk", 3.0, 0.0),
384 Link::new("W", "D", "Walk", 2.0, 0.0),
386 ];
387 let ops = find_optimal_strategy(&all_links, &all_nodes, "D");
388
389 assert!((ops.labels["I"] - 5.0).abs() <= 1e-12);
390 assert!((ops.labels["W"] - 2.0).abs() <= 1e-12);
391 assert!(ops.freqs["I"].is_infinite());
392 assert!(ops.freqs["W"].is_infinite());
393
394 assert_eq!(
396 ops.a_set.len(),
397 2,
398 "basket of I must hold only the no-wait link"
399 );
400 for link in &ops.a_set {
401 assert_eq!(
402 link.headway, 0.0,
403 "only no-wait links expected in the attractive set"
404 );
405 }
406 }
407}