1use std::collections::{HashMap, HashSet};
2
3use crate::hyperpath::{verbose, Strategy};
4use crate::transit_network::Link;
5
6pub struct Volumes {
8 pub links: HashMap<String, HashMap<String, f64>>,
10 pub nodes: HashMap<String, f64>,
12}
13
14pub fn assign_demand<'a>(
15 all_links: &'a [Link],
16 all_stops: &HashSet<String>,
17 optimal_strategy: &Strategy<'a>,
18 trips: &HashMap<String, HashMap<String, f64>>,
19 destination: &str,
20) -> Volumes {
21 let sorted = &optimal_strategy.a_set;
31
32 let mut node_volumes: HashMap<String, f64> = HashMap::with_capacity(all_stops.len());
33 for i in all_stops {
34 node_volumes.insert(i.clone(), 0.0);
35 }
36 for (origin, dests) in trips {
37 if let Some(&trips_num) = dests.get(destination) {
38 node_volumes.insert(origin.clone(), trips_num);
39 *node_volumes.entry(destination.to_string()).or_insert(0.0) += trips_num;
40 }
41 }
42 *node_volumes.entry(destination.to_string()).or_insert(0.0) *= -1.0;
44
45 let mut v: HashMap<String, HashMap<String, f64>> = HashMap::new();
46 for a in all_links {
47 v.entry(a.from_node.clone())
48 .or_default()
49 .insert(a.to_node.clone(), 0.0);
50 }
51
52 for a in sorted.iter().rev() {
53 let f_i = optimal_strategy.freqs.get(&a.from_node).copied().unwrap_or(0.0);
54 let node_volume = node_volumes.get(&a.from_node).copied().unwrap_or(0.0);
55 let va = if f_i.is_infinite() {
56 node_volume
60 } else {
61 let freq = 1.0 / a.headway;
63 (freq / f_i) * node_volume
64 };
65 if verbose() {
66 let to_volume = node_volumes.get(&a.to_node).copied().unwrap_or(0.0);
67 println!(
68 "Assigning demand for link: ({}, {}) \\\\ ",
69 a.from_node, a.to_node
70 );
71 println!(
72 "\\quad $v_{{({}, {})}} = {}$, $V_{{{}}} = {} + {}$ \\\\ ",
73 a.from_node, a.to_node, va, a.to_node, to_volume, va
74 );
75 }
76 v.entry(a.from_node.clone())
77 .or_default()
78 .insert(a.to_node.clone(), va);
79 *node_volumes.entry(a.to_node.clone()).or_insert(0.0) += va;
80 }
81 if verbose() {
82 println!("Final node volumes: \\\\");
83 for (k, volume) in &node_volumes {
84 println!("\\quad $V_{{{}}} = {}$ \\\\ ", k, volume);
85 }
86 }
87
88 Volumes {
89 links: v,
90 nodes: node_volumes,
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_zero_cost_chain_loading() {
100 use crate::hyperpath::find_optimal_strategy;
106
107 let all_nodes: HashSet<String> = ["S1", "B0", "B1", "S2", "S3"]
108 .iter()
109 .map(|s| s.to_string())
110 .collect();
111 let all_links = vec![
112 Link::new("S1", "B0", "Bus", 0.0, 5.0),
114 Link::new("B0", "B1", "Bus", 10.0, 0.0),
116 Link::new("B1", "S2", "Bus", 0.0, 0.0),
118 Link::new("S2", "S3", "Walk", 4.0, 0.0),
120 ];
121 let ops = find_optimal_strategy(&all_links, &all_nodes, "S3");
122 assert!((ops.labels["S1"] - 19.0).abs() <= 1e-12);
124
125 let trips: HashMap<String, HashMap<String, f64>> = HashMap::from([(
126 "S1".to_string(),
127 HashMap::from([("S3".to_string(), 100.0)]),
128 )]);
129 let volumes = assign_demand(&all_links, &all_nodes, &ops, &trips, "S3");
130 assert!((volumes.links["S1"]["B0"] - 100.0).abs() <= 1e-12);
131 assert!((volumes.links["B0"]["B1"] - 100.0).abs() <= 1e-12);
132 assert!((volumes.links["B1"]["S2"] - 100.0).abs() <= 1e-12);
133 assert!((volumes.links["S2"]["S3"] - 100.0).abs() <= 1e-12);
134 }
135
136 #[test]
137 fn test_assign_demand() {
138 let all_nodes: HashSet<String> = ["A", "X", "X2", "Y", "Y3", "B"]
139 .iter()
140 .map(|s| s.to_string())
141 .collect();
142 let all_links = vec![
143 Link::new("A", "B", "Line 1", 25.0, 6.0),
144 Link::new("A", "X2", "Line 2", 7.0, 6.0),
145 Link::new("X2", "X", "Line 2", 0.0, 0.0),
146 Link::new("X", "X2", "Line 2", 0.0, 6.0),
147 Link::new("X2", "Y", "Line 2", 6.0, 0.0),
148 Link::new("Y3", "Y", "Line 3", 0.0, 15.0),
149 Link::new("Y", "B", "Line 4", 10.0, 3.0),
150 Link::new("X", "Y3", "Line 3", 4.0, 15.0),
151 Link::new("Y", "Y3", "Line 3", 0.0, 15.0),
152 Link::new("Y3", "B", "Line 3", 4.0, 0.0),
153 ];
154 let destination_node = "B";
155 let od_matrix: HashMap<String, HashMap<String, f64>> = HashMap::from([(
156 "A".to_string(),
157 HashMap::from([("B".to_string(), 1.0)]),
158 )]);
159 let optimal_strategy = Strategy {
160 labels: HashMap::from([
161 ("A".to_string(), 27.75),
162 ("X".to_string(), 19.071428571428573),
163 ("X2".to_string(), 17.5),
164 ("Y".to_string(), 11.5),
165 ("Y3".to_string(), 4.0),
166 ("B".to_string(), 0.0),
167 ]),
168 freqs: HashMap::from([
169 ("A".to_string(), 1.0 / 3.0),
170 ("X".to_string(), 7.0 / 30.0),
171 ("X2".to_string(), f64::INFINITY),
172 ("Y".to_string(), 0.4),
173 ("Y3".to_string(), f64::INFINITY),
174 ("B".to_string(), 0.0),
175 ]),
176 a_set: vec![
177 &all_links[9],
179 &all_links[8],
181 &all_links[7],
183 &all_links[6],
185 &all_links[4],
187 &all_links[3],
189 &all_links[1],
191 &all_links[0],
193 ],
194 };
195 let volumes = assign_demand(
196 &all_links,
197 &all_nodes,
198 &optimal_strategy,
199 &od_matrix,
200 destination_node,
201 );
202
203 let correct_links: HashMap<&str, HashMap<&str, f64>> = HashMap::from([
204 ("A", HashMap::from([("B", 0.5), ("X2", 0.5)])),
205 ("X2", HashMap::from([("X", 0.0), ("Y", 0.5)])),
206 ("X", HashMap::from([("X2", 0.0), ("Y3", 0.0)])),
207 (
208 "Y",
209 HashMap::from([("Y3", 1.0 / 12.0), ("B", 5.0 / 12.0)]),
210 ),
211 ("Y3", HashMap::from([("Y", 0.0), ("B", 1.0 / 12.0)])),
212 ]);
213 let correct_nodes: HashMap<&str, f64> = HashMap::from([
214 ("A", 1.0),
215 ("X2", 0.5),
216 ("X", 0.0),
217 ("Y3", 1.0 / 12.0),
218 ("Y", 0.5),
219 ("B", 0.0),
220 ]);
221
222 assert_eq!(
223 volumes.links.len(),
224 correct_links.len(),
225 "Incorrect number of links in volumes data"
226 );
227 assert_eq!(
228 volumes.nodes.len(),
229 correct_nodes.len(),
230 "Incorrect number of nodes in volumes data"
231 );
232
233 const EPS: f64 = 1e-9;
234 for (from_node, to_map) in &volumes.links {
235 assert!(
236 correct_links.contains_key(from_node.as_str()),
237 "No 'FromNode' in correct volumes data"
238 );
239 for (to_node, volume) in to_map {
240 assert!(
241 correct_links[from_node.as_str()].contains_key(to_node.as_str()),
242 "No 'ToNode' in correct volumes data"
243 );
244 let want = correct_links[from_node.as_str()][to_node.as_str()];
245 assert!(
246 (volume - want).abs() <= EPS,
247 "Incorrect volume in link ({}, {}): got {}, want {}",
248 from_node,
249 to_node,
250 volume,
251 want
252 );
253 }
254 }
255 for (node, node_volume) in &volumes.nodes {
256 assert!(
257 correct_nodes.contains_key(node.as_str()),
258 "No node in correct volumes data"
259 );
260 let want = correct_nodes[node.as_str()];
261 assert!(
262 (node_volume - want).abs() <= EPS,
263 "Incorrect volume in node {}: got {}, want {}",
264 node,
265 node_volume,
266 want
267 );
268 }
269 }
270}