├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── changelog ├── meta └── test_maps │ ├── bremen_dist.gr │ ├── bremen_time.gr │ ├── graph_23rd.gr │ ├── graph_ballard.gr │ └── south_seattle_car.gr ├── rustfmt.toml └── src ├── constants.rs ├── dijkstra.rs ├── fast_graph.rs ├── fast_graph32.rs ├── fast_graph_builder.rs ├── floyd_warshall.rs ├── heap_item.rs ├── input_graph.rs ├── lib.rs ├── node_contractor.rs ├── path_calculator.rs ├── preparation_graph.rs ├── shortest_path.rs ├── valid_flags.rs └── witness_search.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Check formatting 18 | run: cargo fmt -- --check 19 | - name: Run tests 20 | run: cargo test --release -- --nocapture 21 | - name: Run performance tests 22 | run: export RUST_TEST_THREADS=1; cargo test --release -- --ignored --nocapture 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .DS_Store 5 | 6 | # jetbrains 7 | .idea 8 | *.iml 9 | 10 | example.fp 11 | local/ 12 | local 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fast_paths" 3 | version = "1.0.0" 4 | authors = ["easbar "] 5 | edition = "2018" 6 | description = "Fast shortest path calculations on directed graphs made possible by pre-processing the graph using Contraction Hierarchies" 7 | repository = "https://github.com/easbar/fast_paths" 8 | categories = ["algorithms", "data-structures", "science"] 9 | license = "MIT/Apache-2.0" 10 | exclude = [ 11 | ".github/*", 12 | ".gitignore", 13 | "meta/*" 14 | ] 15 | 16 | [badges] 17 | travis-ci = { repository = "easbar/fast_paths", branch = "master" } 18 | 19 | [dependencies] 20 | serde = { version = "1.0", features =["derive"] } 21 | log = "0.4" 22 | priority-queue = "2.0.2" 23 | 24 | [dev-dependencies] 25 | bincode = "1.3.3" 26 | rand = "0.6" 27 | stopwatch = "0.0.7" 28 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 easbar 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fast Paths 2 | 3 | The most famous algorithms used to calculate shortest paths are probably Dijkstra's algorithm and A*. However, shortest path calculation can be done much faster by preprocessing the graph. 4 | 5 | *Fast Paths* uses *Contraction Hierarchies*, one of the best known speed-up techniques for shortest path calculation. It is especially suited to calculate shortest paths in road networks, but can be used for any directed graph with positive, non-zero edge weights. 6 | 7 | ### Installation 8 | 9 | In `Cargo.toml` 10 | 11 | ```toml 12 | [dependencies] 13 | fast_paths = "1.0.0" 14 | 15 | ``` 16 | ### Basic usage 17 | 18 | ```rust 19 | // begin with an empty graph 20 | let mut input_graph = InputGraph::new(); 21 | 22 | // add an edge between nodes with ID 0 and 6, the weight of the edge is 12. 23 | // Note that the node IDs should be consecutive, if your graph has N nodes use 0...N-1 as node IDs, 24 | // otherwise performance will degrade. 25 | input_graph.add_edge(0, 6, 12); 26 | // ... add many more edges here 27 | 28 | // freeze the graph before using it (you cannot add more edges afterwards, unless you call thaw() first) 29 | input_graph.freeze(); 30 | 31 | // prepare the graph for fast shortest path calculations. note that you have to do this again if you want to change the 32 | // graph topology or any of the edge weights 33 | let fast_graph = fast_paths::prepare(&input_graph); 34 | 35 | // calculate the shortest path between nodes with ID 8 and 6 36 | let shortest_path = fast_paths::calc_path(&fast_graph, 8, 6); 37 | 38 | match shortest_path { 39 | Some(p) => { 40 | // the weight of the shortest path 41 | let weight = p.get_weight(); 42 | 43 | // all nodes of the shortest path (including source and target) 44 | let nodes = p.get_nodes(); 45 | }, 46 | None => { 47 | // no path has been found (nodes are not connected in this graph) 48 | } 49 | } 50 | 51 | 52 | ``` 53 | 54 | ### Batch-wise shortest path calculation 55 | 56 | For batch-wise calculation of shortest paths the method described above is inefficient. You should keep the `PathCalculator` object to execute multiple queries instead: 57 | 58 | ```rust 59 | // ... see above 60 | // create a path calculator (note: not thread-safe, use a separate object per thread) 61 | let mut path_calculator = fast_paths::create_calculator(&fast_graph); 62 | let shortest_path = path_calculator.calc_path(&fast_graph, 8, 6); 63 | ``` 64 | 65 | ### Calculating paths between multiple sources and targets 66 | 67 | We can also efficiently calculate the shortest path when we want to consider multiple sources or targets: 68 | 69 | ```rust 70 | // ... see above 71 | // we want to either start at node 2 or 3 both of which carry a different initial weight 72 | let sources = vec![(3, 5), (2, 7)]; 73 | // ... and go to either node 6 or 8 which also both carry a cost upon arrival 74 | let targets = vec![(6, 2), (8, 10)]; 75 | // calculate the path with minimum cost that connects any of the sources with any of the targets while taking into 76 | // account the initial weights of each source and node 77 | let shortest_path = path_calculator.calc_path_multiple_sources_and_targets(&fast_graph, sources, targets); 78 | ``` 79 | 80 | ### Serializing the prepared graph 81 | 82 | `FastGraph` implements standard [Serde](https://serde.rs/) serialization. 83 | 84 | To be able to use the graph in a 32bit WebAssembly environment, it needs to be transformed to a 32bit representation when preparing it on a 64bit system. This can be achieved with the following two methods, but it will only work for graphs that do not exceed the 32bit limit, i.e. the number of nodes and edges and all weights must be below 2^32. 85 | 86 | ```rust 87 | use fast_paths::{deserialize_32, serialize_32, FastGraph}; 88 | 89 | #[derive(Serialize, Deserialize)] 90 | struct YourData { 91 | #[serde(serialize_with = "serialize_32", deserialize_with = "deserialize_32")] 92 | graph: FastGraph, 93 | // the rest of your struct 94 | } 95 | ``` 96 | 97 | ### Preparing the graph after changes 98 | 99 | The graph preparation can be done much faster using a fixed node ordering, which is just a permutation of node ids. This can be done like this: 100 | 101 | ```rust 102 | let fast_graph = fast_paths::prepare(&input_graph); 103 | let node_ordering = fast_graph.get_node_ordering(); 104 | 105 | let another_fast_graph = fast_paths::prepare_with_order(&another_input_graph, &node_ordering); 106 | ``` 107 | 108 | For this to work `another_input_graph` must have the same number of nodes as `input_graph`, otherwise `prepare_with_order` will return an error. Also performance will only be acceptable if `input_graph` and `another_input_graph` are similar to each other, say you only changed a few edge weights. 109 | 110 | ### Benchmarks 111 | 112 | *FastPaths* was run on a single core on a consumer-grade laptop using the road networks provided for the [DIMACS implementation challenge graphs](http://www.diag.uniroma1.it/~challenge9/download.shtml). The following graphs were used for the benchmark: 113 | 114 | |area|number of nodes|number of edges| 115 | |-|-|-| 116 | |New York|264.347|730.100| 117 | |California&Nevada|1.890.816|4.630.444| 118 | |USA|23.947.348|57.708.624| 119 | 120 | |graph|metric|preparation time|average query time|out edges|in edges| 121 | |-|-|-|-|-|-| 122 | |NY city|distance|9 s|55 μs|747.555|747.559| 123 | |CAL&NV|distance|36 s|103 μs|4.147.109|4.147.183| 124 | |USA|distance|10.6 min|630 μs|52.617.216|52.617.642| 125 | |NY city|time|6 s|26 μs|706.053|706.084| 126 | |CAL&NV|time|24 s|60 μs|3.975.276|3.975.627| 127 | |USA|time|5.5 min|305 μs|49.277.058|49.283.162| 128 | 129 | The shortest path calculation time was averaged over 100k random routing queries. The benchmarks were run on a Macbook Pro M1 Max using Rust 1.74.1. 130 | The code for running these benchmarks can be found on the `benchmarks` branch. 131 | 132 | There are also some benchmarks using smaller maps included in the test suite. You can run them like this: 133 | ```shell 134 | export RUST_TEST_THREADS=1; cargo test --release -- --ignored --nocapture 135 | ``` 136 | 137 | ### Graph limitations 138 | 139 | - loop-edges (from node A to node A) will be ignored, because since we are only considering positive non-zero edge-weights they cannot be part of a shortest path 140 | - in case the graph has duplicate edges (multiple edges from node A to node B) only the edge with the lowest weight will be considered 141 | 142 | ### Special Thanks 143 | 144 | Thanks to [Dustin Carlino](http://github.com/dabreegster) from [A/B Street](http://github.com/dabreegster/abstreet)! 145 | 146 | 147 | #### License 148 | 149 | This project is licensed under either of 150 | 151 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 152 | http://www.apache.org/licenses/LICENSE-2.0) 153 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 154 | http://opensource.org/licenses/MIT) 155 | 156 | at your option. 157 | 158 | #### Contribution 159 | 160 | Unless you explicitly state otherwise, any contribution intentionally submitted 161 | for inclusion in fast_paths by you, as defined in the Apache-2.0 license, shall be 162 | dual licensed as above, without any additional terms or conditions. 163 | -------------------------------------------------------------------------------- /changelog: -------------------------------------------------------------------------------- 1 | 1.0.0 [May 4th 2024] 2 | smaller package size (excluded test maps) 3 | breaking: add max_settled_nodes parameters to Params, important performance tuning for graphs with large-weight edges, #37 4 | add InputGraph::to_file, #35 5 | faster fast_graph building, 82e9a2ef417af6de1b2cb41bcee41b6302db1b4a 6 | allow passing &[T] instead of &Vec in a few places, #32 7 | add calc_path_multiple_sources_and_targets, #30 8 | 0.2.0 [April 25th 2021] 9 | add clone and copy derives, #26 10 | faster fast_graph building 11 | faster queries (stall on demand optimization), #18 12 | fix serious performance regression for Rust >=1.45, #21 13 | remove rand and bincode dependencies, #17 14 | allow storing the graph for 32bit systems on a 64bit system, #14 15 | WebAssembly compatibility, #11 16 | deterministic fast_graph building, #10 17 | license change to dual MIT/Apache 2.0, #4 18 | 0.1.1 [June 17th 2019] 19 | adds travis and meta information to Cargo.toml 20 | 0.1.0 [June 17th 2019] 21 | initial version -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easbar/fast_paths/cf922636fbafe2e3aa23795e6c5ccb05f8758e66/rustfmt.toml -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | pub type NodeId = usize; 21 | pub type EdgeId = usize; 22 | pub type Weight = usize; 23 | 24 | pub const INVALID_NODE: NodeId = std::usize::MAX; 25 | pub const INVALID_EDGE: EdgeId = std::usize::MAX; 26 | pub const WEIGHT_MAX: Weight = std::usize::MAX; 27 | pub const WEIGHT_ZERO: Weight = 0; 28 | -------------------------------------------------------------------------------- /src/dijkstra.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::collections::BinaryHeap; 21 | 22 | use crate::constants::Weight; 23 | use crate::constants::{NodeId, INVALID_NODE, WEIGHT_MAX}; 24 | use crate::heap_item::HeapItem; 25 | use crate::preparation_graph::PreparationGraph; 26 | use crate::shortest_path::ShortestPath; 27 | use crate::valid_flags::ValidFlags; 28 | 29 | pub struct Dijkstra { 30 | num_nodes: usize, 31 | data: Vec, 32 | valid_flags: ValidFlags, 33 | heap: BinaryHeap, 34 | } 35 | 36 | /// Dijkstra's algorithm using pre-allocated memory for the shortest path tree. Currently only used 37 | /// to test the correctness of the path_calculator implementation. Providing a flexible Dijkstra 38 | /// implementation that works for arbitrary weight functions and that runs on the fast_graph 39 | /// datastructure might be useful, but this was not the intention here. 40 | impl Dijkstra { 41 | pub fn new(num_nodes: usize) -> Self { 42 | let heap = BinaryHeap::new(); 43 | Dijkstra { 44 | num_nodes, 45 | data: (0..num_nodes).map(|_i| Data::new()).collect(), 46 | valid_flags: ValidFlags::new(num_nodes), 47 | heap, 48 | } 49 | } 50 | 51 | #[allow(dead_code)] 52 | pub fn calc_path( 53 | &mut self, 54 | graph: &PreparationGraph, 55 | start: NodeId, 56 | end: NodeId, 57 | ) -> Option { 58 | self.init(start); 59 | self.do_calc_path(graph, start, end); 60 | self.build_path(start, end) 61 | } 62 | 63 | fn init(&mut self, start: NodeId) { 64 | self.heap.clear(); 65 | self.valid_flags.invalidate_all(); 66 | self.update_node(start, 0, INVALID_NODE); 67 | self.heap.push(HeapItem::new(0, start)); 68 | } 69 | 70 | fn do_calc_path(&mut self, graph: &PreparationGraph, start: NodeId, end: NodeId) { 71 | assert_eq!( 72 | graph.get_num_nodes(), 73 | self.num_nodes, 74 | "given graph has invalid node count" 75 | ); 76 | if start == end { 77 | return; 78 | } 79 | if self.is_settled(end) { 80 | return; 81 | } 82 | while !self.heap.is_empty() { 83 | let curr = self.heap.pop().unwrap(); 84 | if self.is_settled(curr.node_id) { 85 | // todo: since we are not using a special decrease key operation yet we need to 86 | // filter out duplicate heap items here 87 | continue; 88 | } 89 | for i in 0..graph.out_edges[curr.node_id].len() { 90 | let adj = graph.out_edges[curr.node_id][i].adj_node; 91 | let edge_weight = graph.out_edges[curr.node_id][i].weight; 92 | let weight = curr.weight + edge_weight; 93 | if weight < self.get_weight(adj) { 94 | self.update_node(adj, weight, curr.node_id); 95 | self.heap.push(HeapItem::new(weight, adj)); 96 | } 97 | } 98 | self.data[curr.node_id].settled = true; 99 | if curr.node_id == end { 100 | break; 101 | } 102 | } 103 | } 104 | 105 | fn build_path(&mut self, start: NodeId, end: NodeId) -> Option { 106 | if start == end { 107 | return Some(ShortestPath::singular(start)); 108 | } 109 | if !self.valid_flags.is_valid(end) || !self.data[end].settled { 110 | return None; 111 | } 112 | let mut path = Vec::new(); 113 | let mut node = end; 114 | while self.data[node].parent != INVALID_NODE { 115 | path.push(node); 116 | node = self.data[node].parent; 117 | } 118 | path.push(start); 119 | path = path.iter().rev().cloned().collect(); 120 | Some(ShortestPath::new(start, end, self.data[end].weight, path)) 121 | } 122 | 123 | fn update_node(&mut self, node: NodeId, weight: Weight, parent: NodeId) { 124 | self.valid_flags.set_valid(node); 125 | self.data[node].settled = false; 126 | self.data[node].weight = weight; 127 | self.data[node].parent = parent; 128 | } 129 | 130 | fn is_settled(&self, node: NodeId) -> bool { 131 | self.valid_flags.is_valid(node) && self.data[node].settled 132 | } 133 | 134 | fn get_weight(&self, node: NodeId) -> Weight { 135 | if self.valid_flags.is_valid(node) { 136 | self.data[node].weight 137 | } else { 138 | WEIGHT_MAX 139 | } 140 | } 141 | } 142 | 143 | struct Data { 144 | settled: bool, 145 | weight: Weight, 146 | parent: NodeId, 147 | } 148 | 149 | impl Data { 150 | fn new() -> Self { 151 | // todo: initializing with these values is not strictly necessary 152 | Data { 153 | settled: false, 154 | weight: WEIGHT_MAX, 155 | parent: INVALID_NODE, 156 | } 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use super::*; 163 | 164 | #[test] 165 | fn simple_path() { 166 | // 7 -> 8 -> 9 167 | // | | 168 | // 0 -> 5 -> 6 - | 169 | // | | \ | 170 | // 1 -> 2 -> 3 -> 4 171 | let mut g = PreparationGraph::new(10); 172 | g.add_edge(0, 1, 1); 173 | g.add_edge(1, 2, 1); 174 | g.add_edge(2, 3, 1); 175 | g.add_edge(3, 4, 20); 176 | g.add_edge(0, 5, 5); 177 | g.add_edge(5, 6, 1); 178 | g.add_edge(6, 4, 20); 179 | g.add_edge(6, 3, 20); 180 | g.add_edge(5, 7, 5); 181 | g.add_edge(7, 8, 1); 182 | g.add_edge(8, 9, 1); 183 | g.add_edge(9, 4, 1); 184 | let mut d = Dijkstra::new(g.get_num_nodes()); 185 | assert_no_path(&mut d, &g, 4, 0); 186 | assert_path(&mut d, &g, 4, 4, 0, vec![4]); 187 | assert_path(&mut d, &g, 6, 3, 20, vec![6, 3]); 188 | assert_path(&mut d, &g, 1, 4, 22, vec![1, 2, 3, 4]); 189 | assert_path(&mut d, &g, 0, 4, 13, vec![0, 5, 7, 8, 9, 4]); 190 | } 191 | 192 | #[test] 193 | fn go_around() { 194 | // 0 -> 1 195 | // | | 196 | // 2 -> 3 197 | let mut g = PreparationGraph::new(4); 198 | g.add_edge(0, 1, 10); 199 | g.add_edge(0, 2, 1); 200 | g.add_edge(2, 3, 1); 201 | g.add_edge(3, 1, 1); 202 | let mut d = Dijkstra::new(g.get_num_nodes()); 203 | assert_path(&mut d, &g, 0, 1, 3, vec![0, 2, 3, 1]); 204 | } 205 | 206 | #[test] 207 | fn more() { 208 | // 0 -> 1 -> 2 209 | // \ 210 | // 3 -> 4 211 | // / \ 212 | // 7 <-6 |-> 5 213 | // \ 214 | // 8 -> 9 -> 10 215 | let mut g = PreparationGraph::new(11); 216 | g.add_edge(0, 1, 1); 217 | g.add_edge(1, 2, 1); 218 | g.add_edge(1, 3, 1); 219 | g.add_edge(3, 4, 1); 220 | g.add_edge(3, 6, 1); 221 | g.add_edge(6, 7, 1); 222 | g.add_edge(3, 5, 1); 223 | g.add_edge(3, 8, 1); 224 | g.add_edge(8, 9, 1); 225 | g.add_edge(9, 10, 1); 226 | let mut d = Dijkstra::new(g.get_num_nodes()); 227 | assert_path(&mut d, &g, 0, 1, 1, vec![0, 1]); 228 | assert_path(&mut d, &g, 0, 2, 2, vec![0, 1, 2]); 229 | assert_path(&mut d, &g, 0, 4, 3, vec![0, 1, 3, 4]); 230 | assert_path(&mut d, &g, 0, 3, 2, vec![0, 1, 3]); 231 | assert_path(&mut d, &g, 0, 7, 4, vec![0, 1, 3, 6, 7]); 232 | assert_path(&mut d, &g, 0, 5, 3, vec![0, 1, 3, 5]); 233 | assert_path(&mut d, &g, 0, 10, 5, vec![0, 1, 3, 8, 9, 10]); 234 | assert_path(&mut d, &g, 3, 10, 3, vec![3, 8, 9, 10]); 235 | } 236 | 237 | fn assert_no_path( 238 | dijkstra: &mut Dijkstra, 239 | graph: &PreparationGraph, 240 | source: NodeId, 241 | target: NodeId, 242 | ) { 243 | assert_eq!(dijkstra.calc_path(&graph, source, target), None); 244 | } 245 | 246 | fn assert_path( 247 | dijkstra: &mut Dijkstra, 248 | graph: &PreparationGraph, 249 | source: NodeId, 250 | target: NodeId, 251 | weight: Weight, 252 | nodes: Vec, 253 | ) { 254 | let dijkstra_path = dijkstra.calc_path(&graph, source, target); 255 | assert_eq!( 256 | dijkstra_path, 257 | Some(ShortestPath::new(source, target, weight, nodes.clone())) 258 | ); 259 | // ShortestPath PartialEq does not consider nodes! 260 | assert_eq!(nodes, dijkstra_path.unwrap().get_nodes().clone()); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/fast_graph.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use serde::Deserialize; 21 | use serde::Serialize; 22 | 23 | use crate::constants::Weight; 24 | use crate::constants::{EdgeId, NodeId, INVALID_EDGE}; 25 | 26 | #[derive(Serialize, Deserialize, Debug, Clone)] 27 | pub struct FastGraph { 28 | num_nodes: usize, 29 | pub(crate) ranks: Vec, 30 | pub(crate) edges_fwd: Vec, 31 | pub(crate) first_edge_ids_fwd: Vec, 32 | 33 | pub(crate) edges_bwd: Vec, 34 | pub(crate) first_edge_ids_bwd: Vec, 35 | } 36 | 37 | impl FastGraph { 38 | pub fn new(num_nodes: usize) -> Self { 39 | FastGraph { 40 | ranks: vec![0; num_nodes], 41 | num_nodes, 42 | edges_fwd: vec![], 43 | first_edge_ids_fwd: vec![0; num_nodes + 1], 44 | edges_bwd: vec![], 45 | first_edge_ids_bwd: vec![0; num_nodes + 1], 46 | } 47 | } 48 | 49 | pub fn get_node_ordering(&self) -> Vec { 50 | let mut ordering = vec![0; self.ranks.len()]; 51 | for i in 0..self.ranks.len() { 52 | ordering[self.ranks[i]] = i; 53 | } 54 | ordering 55 | } 56 | 57 | pub fn get_num_nodes(&self) -> usize { 58 | self.num_nodes 59 | } 60 | 61 | pub fn get_num_out_edges(&self) -> usize { 62 | self.edges_fwd.len() 63 | } 64 | 65 | pub fn get_num_in_edges(&self) -> usize { 66 | self.edges_bwd.len() 67 | } 68 | 69 | pub fn begin_in_edges(&self, node: NodeId) -> usize { 70 | self.first_edge_ids_bwd[self.ranks[node]] 71 | } 72 | 73 | pub fn end_in_edges(&self, node: NodeId) -> usize { 74 | self.first_edge_ids_bwd[self.ranks[node] + 1] 75 | } 76 | 77 | pub fn begin_out_edges(&self, node: NodeId) -> usize { 78 | self.first_edge_ids_fwd[self.ranks[node]] 79 | } 80 | 81 | pub fn end_out_edges(&self, node: NodeId) -> usize { 82 | self.first_edge_ids_fwd[self.ranks[node] + 1] 83 | } 84 | } 85 | 86 | #[derive(Serialize, Deserialize, Debug, Clone, Copy)] 87 | pub struct FastGraphEdge { 88 | // todo: the base_node is 'redundant' for the routing query so to say, but makes the implementation easier for now 89 | // and can still be removed at a later time, we definitely need this information on original 90 | // edges for shortcut unpacking. a possible hack is storing it in the (for non-shortcuts) 91 | // unused replaced_in/out_edge field. 92 | pub base_node: NodeId, 93 | pub adj_node: NodeId, 94 | pub weight: Weight, 95 | pub replaced_in_edge: EdgeId, 96 | pub replaced_out_edge: EdgeId, 97 | } 98 | 99 | impl FastGraphEdge { 100 | pub fn new( 101 | base_node: NodeId, 102 | adj_node: NodeId, 103 | weight: Weight, 104 | replaced_edge1: EdgeId, 105 | replaced_edge2: EdgeId, 106 | ) -> Self { 107 | FastGraphEdge { 108 | base_node, 109 | adj_node, 110 | weight, 111 | replaced_in_edge: replaced_edge1, 112 | replaced_out_edge: replaced_edge2, 113 | } 114 | } 115 | 116 | pub fn is_shortcut(&self) -> bool { 117 | assert!( 118 | (self.replaced_in_edge == INVALID_EDGE && self.replaced_out_edge == INVALID_EDGE) 119 | || (self.replaced_in_edge != INVALID_EDGE 120 | && self.replaced_out_edge != INVALID_EDGE) 121 | ); 122 | self.replaced_in_edge != INVALID_EDGE 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/fast_graph32.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::convert::TryFrom; 21 | 22 | use serde::Deserialize; 23 | use serde::Serialize; 24 | 25 | use crate::fast_graph::FastGraphEdge; 26 | use crate::FastGraph; 27 | 28 | /// Special graph data-structure that is identical to `FastGraph` except that it uses u32 integers 29 | /// instead of usize integers. This is used to store a `FastGraph` in a 32bit representation on disk 30 | /// when using a 64bit system. 31 | #[derive(Serialize, Deserialize, Debug, Clone)] 32 | pub struct FastGraph32 { 33 | num_nodes: u32, 34 | pub ranks: Vec, 35 | pub edges_fwd: Vec, 36 | pub first_edge_ids_fwd: Vec, 37 | 38 | pub edges_bwd: Vec, 39 | pub first_edge_ids_bwd: Vec, 40 | } 41 | 42 | impl FastGraph32 { 43 | /// Creates a 32bit Graph from a given `FastGraph`. All (potentially 64bit) `usize` integers are 44 | /// simply converted to u32 and if a value exceeds the 32bit limit an error is thrown. The only 45 | /// exception is `std::u32::MAX`, which is converted to `std::usize::MAX`. 46 | pub fn new(fast_graph: &FastGraph) -> Self { 47 | FastGraph32 { 48 | num_nodes: usize_to_u32(fast_graph.get_num_nodes()), 49 | ranks: usize_to_u32_vec(&fast_graph.ranks), 50 | edges_fwd: usize_to_u32_edges(&fast_graph.edges_fwd), 51 | first_edge_ids_fwd: usize_to_u32_vec(&fast_graph.first_edge_ids_fwd), 52 | edges_bwd: usize_to_u32_edges(&fast_graph.edges_bwd), 53 | first_edge_ids_bwd: usize_to_u32_vec(&fast_graph.first_edge_ids_bwd), 54 | } 55 | } 56 | 57 | /// Converts a 32bit Graph to an actual `FastGraph` using `usize` such that it can be used with 58 | /// FastPaths crate. Any integers that equal `std::u32::MAX` are mapped to `std::usize::MAX`. 59 | pub fn convert_to_usize(self) -> FastGraph { 60 | let mut g = FastGraph::new(self.num_nodes as usize); 61 | g.ranks = u32_to_usize_vec(&self.ranks); 62 | g.edges_fwd = u32_to_usize_edges(&self.edges_fwd); 63 | g.first_edge_ids_fwd = u32_to_usize_vec(&self.first_edge_ids_fwd); 64 | g.edges_bwd = u32_to_usize_edges(&self.edges_bwd); 65 | g.first_edge_ids_bwd = u32_to_usize_vec(&self.first_edge_ids_bwd); 66 | g 67 | } 68 | } 69 | 70 | /// 32bit equivalent to `FastGraphEdge`, see `FastGraph32` docs. 71 | #[derive(Serialize, Deserialize, Debug, Clone, Copy)] 72 | pub struct FastGraphEdge32 { 73 | pub base_node: u32, 74 | pub adj_node: u32, 75 | pub weight: u32, 76 | pub replaced_in_edge: u32, 77 | pub replaced_out_edge: u32, 78 | } 79 | 80 | fn usize_to_u32(int: usize) -> u32 { 81 | if int == std::usize::MAX { 82 | usize_to_u32(std::u32::MAX as usize) 83 | } else if let Ok(x) = u32::try_from(int) { 84 | x 85 | } else { 86 | panic!("Could not convert {} to a 32-bit integer", int); 87 | } 88 | } 89 | 90 | fn usize_to_u32_vec(vec: &[usize]) -> Vec { 91 | vec.iter().map(|i| usize_to_u32(*i)).collect() 92 | } 93 | 94 | fn usize_to_u32_edges(vec: &[FastGraphEdge]) -> Vec { 95 | vec.iter().map(|e| usize_to_u32_edge(e)).collect() 96 | } 97 | 98 | fn usize_to_u32_edge(edge: &FastGraphEdge) -> FastGraphEdge32 { 99 | FastGraphEdge32 { 100 | base_node: usize_to_u32(edge.base_node), 101 | adj_node: usize_to_u32(edge.adj_node), 102 | weight: usize_to_u32(edge.weight), 103 | replaced_in_edge: usize_to_u32(edge.replaced_in_edge), 104 | replaced_out_edge: usize_to_u32(edge.replaced_out_edge), 105 | } 106 | } 107 | 108 | fn u32_to_usize(int: u32) -> usize { 109 | if int == std::u32::MAX { 110 | std::usize::MAX 111 | } else { 112 | int as usize 113 | } 114 | } 115 | 116 | fn u32_to_usize_vec(vec: &[u32]) -> Vec { 117 | vec.iter().map(|i| u32_to_usize(*i)).collect() 118 | } 119 | 120 | fn u32_to_usize_edges(vec: &[FastGraphEdge32]) -> Vec { 121 | vec.iter().map(|e| u32_to_usize_edge(e)).collect() 122 | } 123 | 124 | fn u32_to_usize_edge(edge: &FastGraphEdge32) -> FastGraphEdge { 125 | FastGraphEdge { 126 | base_node: u32_to_usize(edge.base_node), 127 | adj_node: u32_to_usize(edge.adj_node), 128 | weight: u32_to_usize(edge.weight), 129 | replaced_in_edge: u32_to_usize(edge.replaced_in_edge), 130 | replaced_out_edge: u32_to_usize(edge.replaced_out_edge), 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use crate::fast_graph::FastGraph; 137 | use crate::fast_graph::FastGraphEdge; 138 | 139 | use super::*; 140 | 141 | #[test] 142 | fn create() { 143 | let num_nodes = 5; 144 | let ranks = vec![286, 45, 480_001, std::usize::MAX, 4468]; 145 | let edges_fwd = vec![ 146 | FastGraphEdge::new(std::usize::MAX, 598, 48, std::usize::MAX, std::usize::MAX), 147 | FastGraphEdge::new( 148 | std::usize::MAX, 149 | std::usize::MAX, 150 | std::usize::MAX, 151 | 4, 152 | std::usize::MAX, 153 | ), 154 | ]; 155 | let edges_bwd = vec![FastGraphEdge::new(0, 1, 3, 4, std::usize::MAX)]; 156 | let first_edge_ids_fwd = vec![1, std::usize::MAX, std::usize::MAX]; 157 | let first_edge_ids_bwd = vec![1, std::usize::MAX, 5, std::usize::MAX, 9, 10]; 158 | 159 | let mut g = FastGraph::new(num_nodes); 160 | g.ranks = ranks; 161 | g.edges_fwd = edges_fwd; 162 | g.first_edge_ids_fwd = first_edge_ids_fwd; 163 | g.edges_bwd = edges_bwd; 164 | g.first_edge_ids_bwd = first_edge_ids_bwd; 165 | 166 | let g32 = FastGraph32::new(&g); 167 | assert_eq!(g32.num_nodes, 5); 168 | 169 | assert_eq!(g32.ranks.len(), 5); 170 | assert_eq!(g32.ranks[0], 286); 171 | assert_eq!(g32.ranks[2], 480_001); 172 | assert_eq!(g32.ranks[3], std::u32::MAX); 173 | 174 | assert_eq!(g32.edges_fwd.len(), 2); 175 | assert_eq!(g32.edges_fwd[0].base_node, std::u32::MAX); 176 | assert_eq!(g32.edges_fwd[0].adj_node, 598); 177 | assert_eq!(g32.edges_fwd[0].weight, 48); 178 | assert_eq!(g32.edges_fwd[0].replaced_in_edge, std::u32::MAX); 179 | assert_eq!(g32.edges_fwd[0].replaced_out_edge, std::u32::MAX); 180 | 181 | assert_eq!(g32.edges_fwd[1].base_node, std::u32::MAX); 182 | assert_eq!(g32.edges_fwd[1].adj_node, std::u32::MAX); 183 | assert_eq!(g32.edges_fwd[1].weight, std::u32::MAX); 184 | assert_eq!(g32.edges_fwd[1].replaced_in_edge, 4); 185 | assert_eq!(g32.edges_fwd[1].replaced_out_edge, std::u32::MAX); 186 | 187 | assert_eq!(g32.edges_bwd.len(), 1); 188 | assert_eq!(g32.edges_bwd[0].weight, 3); 189 | assert_eq!(g32.edges_bwd[0].replaced_out_edge, std::u32::MAX); 190 | 191 | assert_eq!(g32.first_edge_ids_fwd.len(), 3); 192 | assert_eq!(g32.first_edge_ids_fwd[1], std::u32::MAX); 193 | assert_eq!(g32.first_edge_ids_bwd.len(), 6); 194 | assert_eq!(g32.first_edge_ids_bwd[3], std::u32::MAX); 195 | assert_eq!(g32.first_edge_ids_bwd[4], 9); 196 | 197 | // briefly check back-conversion 198 | let g_from32 = g32.convert_to_usize(); 199 | assert_eq!(g_from32.get_num_nodes(), 5); 200 | assert_eq!( 201 | g_from32.ranks, 202 | vec![286, 45, 480_001, std::usize::MAX, 4468] 203 | ); 204 | assert_eq!(g_from32.first_edge_ids_fwd[2], std::usize::MAX); 205 | assert_eq!(g_from32.first_edge_ids_bwd[0], 1); 206 | assert_eq!(g_from32.first_edge_ids_bwd[1], std::usize::MAX); 207 | assert_eq!(g_from32.edges_fwd[0].base_node, std::usize::MAX); 208 | assert_eq!(g_from32.edges_fwd[0].adj_node, 598); 209 | assert_eq!(g_from32.edges_fwd[0].weight, 48); 210 | assert_eq!(g_from32.edges_bwd[0].replaced_in_edge, 4); 211 | } 212 | 213 | #[test] 214 | #[should_panic] 215 | fn create_fails_with_too_large_numbers() { 216 | let num_nodes = 5; 217 | let mut g = FastGraph::new(num_nodes); 218 | g.ranks = vec![5_000_000_000]; 219 | FastGraph32::new(&g); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/fast_graph_builder.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::cmp::{max, Reverse}; 21 | use std::collections::BTreeSet; 22 | 23 | use priority_queue::PriorityQueue; 24 | 25 | use crate::constants::Weight; 26 | use crate::constants::{EdgeId, NodeId, INVALID_EDGE, INVALID_NODE}; 27 | use crate::fast_graph::FastGraphEdge; 28 | 29 | use super::fast_graph::FastGraph; 30 | use super::input_graph::InputGraph; 31 | use super::preparation_graph::PreparationGraph; 32 | use crate::node_contractor; 33 | use crate::witness_search::WitnessSearch; 34 | 35 | pub struct FastGraphBuilder { 36 | fast_graph: FastGraph, 37 | num_nodes: usize, 38 | } 39 | 40 | impl FastGraphBuilder { 41 | fn new(input_graph: &InputGraph) -> Self { 42 | FastGraphBuilder { 43 | fast_graph: FastGraph::new(input_graph.get_num_nodes()), 44 | num_nodes: input_graph.get_num_nodes(), 45 | } 46 | } 47 | 48 | pub fn build(input_graph: &InputGraph) -> FastGraph { 49 | FastGraphBuilder::build_with_params(input_graph, &Params::default()) 50 | } 51 | 52 | pub fn build_with_params(input_graph: &InputGraph, params: &Params) -> FastGraph { 53 | let mut builder = FastGraphBuilder::new(input_graph); 54 | builder.run_contraction(input_graph, params); 55 | builder.fast_graph 56 | } 57 | 58 | pub fn build_with_order( 59 | input_graph: &InputGraph, 60 | order: &[NodeId], 61 | ) -> Result { 62 | FastGraphBuilder::build_with_order_with_params( 63 | input_graph, 64 | order, 65 | &ParamsWithOrder::default(), 66 | ) 67 | } 68 | 69 | pub fn build_with_order_with_params( 70 | input_graph: &InputGraph, 71 | order: &[NodeId], 72 | params: &ParamsWithOrder, 73 | ) -> Result { 74 | if input_graph.get_num_nodes() != order.len() { 75 | return Err(String::from( 76 | "The given order must have as many nodes as the input graph", 77 | )); 78 | } 79 | let mut builder = FastGraphBuilder::new(input_graph); 80 | builder.run_contraction_with_order(input_graph, order, params); 81 | Ok(builder.fast_graph) 82 | } 83 | 84 | fn run_contraction(&mut self, input_graph: &InputGraph, params: &Params) { 85 | let mut preparation_graph = PreparationGraph::from_input_graph(input_graph); 86 | let mut witness_search = WitnessSearch::new(self.num_nodes); 87 | let mut levels = vec![0; self.num_nodes]; 88 | let mut queue = PriorityQueue::new(); 89 | for node in 0..self.num_nodes { 90 | let priority = node_contractor::calc_relevance( 91 | &mut preparation_graph, 92 | params, 93 | &mut witness_search, 94 | node, 95 | 0, 96 | params.max_settled_nodes_initial_relevance, 97 | ) as Weight; 98 | queue.push(node, Reverse(priority)); 99 | } 100 | let mut rank = 0; 101 | while !queue.is_empty() { 102 | // This normally yields the greatest priority, but since we use Reverse, it's the 103 | // least. 104 | let node = queue.pop().unwrap().0; 105 | let mut neighbors = BTreeSet::new(); 106 | for out_edge in &preparation_graph.out_edges[node] { 107 | neighbors.insert(out_edge.adj_node); 108 | self.fast_graph.edges_fwd.push(FastGraphEdge::new( 109 | node, 110 | out_edge.adj_node, 111 | out_edge.weight, 112 | out_edge.center_node, 113 | INVALID_EDGE, 114 | )); 115 | } 116 | self.fast_graph.first_edge_ids_fwd[rank + 1] = self.fast_graph.get_num_out_edges(); 117 | 118 | for in_edge in &preparation_graph.in_edges[node] { 119 | neighbors.insert(in_edge.adj_node); 120 | self.fast_graph.edges_bwd.push(FastGraphEdge::new( 121 | node, 122 | in_edge.adj_node, 123 | in_edge.weight, 124 | in_edge.center_node, 125 | INVALID_EDGE, 126 | )); 127 | } 128 | self.fast_graph.first_edge_ids_bwd[rank + 1] = self.fast_graph.get_num_in_edges(); 129 | 130 | self.fast_graph.ranks[node] = rank; 131 | node_contractor::contract_node( 132 | &mut preparation_graph, 133 | &mut witness_search, 134 | node, 135 | params.max_settled_nodes_contraction, 136 | ); 137 | for neighbor in neighbors { 138 | levels[neighbor] = max(levels[neighbor], levels[node] + 1); 139 | let priority = node_contractor::calc_relevance( 140 | &mut preparation_graph, 141 | params, 142 | &mut witness_search, 143 | neighbor, 144 | levels[neighbor], 145 | params.max_settled_nodes_neighbor_relevance, 146 | ) as Weight; 147 | queue.change_priority(&neighbor, Reverse(priority)); 148 | } 149 | debug!( 150 | "contracted node {} / {}, num edges fwd: {}, num edges bwd: {}", 151 | rank + 1, 152 | self.num_nodes, 153 | self.fast_graph.get_num_out_edges(), 154 | self.fast_graph.get_num_in_edges() 155 | ); 156 | rank += 1; 157 | } 158 | self.finish_contraction(); 159 | } 160 | 161 | fn run_contraction_with_order( 162 | &mut self, 163 | input_graph: &InputGraph, 164 | order: &[NodeId], 165 | params: &ParamsWithOrder, 166 | ) { 167 | let mut preparation_graph = PreparationGraph::from_input_graph(input_graph); 168 | let mut witness_search = WitnessSearch::new(self.num_nodes); 169 | for (rank, node) in order.iter().cloned().enumerate() { 170 | if node >= self.num_nodes { 171 | panic!("Order contains invalid node id: {}", node); 172 | } 173 | for out_edge in &preparation_graph.out_edges[node] { 174 | self.fast_graph.edges_fwd.push(FastGraphEdge::new( 175 | node, 176 | out_edge.adj_node, 177 | out_edge.weight, 178 | out_edge.center_node, 179 | INVALID_EDGE, 180 | )); 181 | } 182 | self.fast_graph.first_edge_ids_fwd[rank + 1] = self.fast_graph.get_num_out_edges(); 183 | 184 | for in_edge in &preparation_graph.in_edges[node] { 185 | self.fast_graph.edges_bwd.push(FastGraphEdge::new( 186 | node, 187 | in_edge.adj_node, 188 | in_edge.weight, 189 | in_edge.center_node, 190 | INVALID_EDGE, 191 | )); 192 | } 193 | self.fast_graph.first_edge_ids_bwd[rank + 1] = self.fast_graph.get_num_in_edges(); 194 | 195 | self.fast_graph.ranks[node] = rank; 196 | node_contractor::contract_node( 197 | &mut preparation_graph, 198 | &mut witness_search, 199 | node, 200 | params.max_settled_nodes_contraction_with_order, 201 | ); 202 | debug!( 203 | "contracted node {} / {}, num edges fwd: {}, num edges bwd: {}", 204 | rank + 1, 205 | self.num_nodes, 206 | self.fast_graph.get_num_out_edges(), 207 | self.fast_graph.get_num_in_edges() 208 | ); 209 | } 210 | self.finish_contraction(); 211 | } 212 | 213 | fn finish_contraction(&mut self) { 214 | for i in 0..self.num_nodes { 215 | for edge_id in self.fast_graph.begin_out_edges(i)..self.fast_graph.end_out_edges(i) { 216 | // we temporarily stored the center node in the replaced_in_edge field. now we 217 | // set the actual replaced edges 218 | let c = self.fast_graph.edges_fwd[edge_id].replaced_in_edge; 219 | if c == INVALID_NODE { 220 | self.fast_graph.edges_fwd[edge_id].replaced_in_edge = INVALID_EDGE; 221 | debug_assert_eq!( 222 | INVALID_EDGE, 223 | self.fast_graph.edges_fwd[edge_id].replaced_out_edge 224 | ); 225 | } else { 226 | self.fast_graph.edges_fwd[edge_id].replaced_in_edge = self.get_in_edge_id(c, i); 227 | self.fast_graph.edges_fwd[edge_id].replaced_out_edge = 228 | self.get_out_edge_id(c, self.fast_graph.edges_fwd[edge_id].adj_node); 229 | } 230 | } 231 | } 232 | 233 | for i in 0..self.num_nodes { 234 | for edge_id in self.fast_graph.begin_in_edges(i)..self.fast_graph.end_in_edges(i) { 235 | let c = self.fast_graph.edges_bwd[edge_id].replaced_in_edge; 236 | if c == INVALID_NODE { 237 | self.fast_graph.edges_bwd[edge_id].replaced_in_edge = INVALID_EDGE; 238 | debug_assert_eq!( 239 | INVALID_EDGE, 240 | self.fast_graph.edges_bwd[edge_id].replaced_out_edge 241 | ); 242 | } else { 243 | self.fast_graph.edges_bwd[edge_id].replaced_in_edge = 244 | self.get_in_edge_id(c, self.fast_graph.edges_bwd[edge_id].adj_node); 245 | self.fast_graph.edges_bwd[edge_id].replaced_out_edge = 246 | self.get_out_edge_id(c, i); 247 | } 248 | } 249 | } 250 | } 251 | 252 | fn get_out_edge_id(&self, node: NodeId, adj_node: NodeId) -> EdgeId { 253 | for edge_id in self.fast_graph.begin_out_edges(node)..self.fast_graph.end_out_edges(node) { 254 | if self.fast_graph.edges_fwd[edge_id].adj_node == adj_node { 255 | return edge_id; 256 | } 257 | } 258 | panic!("could not find out-edge id") 259 | } 260 | 261 | fn get_in_edge_id(&self, node: NodeId, adj_node: NodeId) -> EdgeId { 262 | for edge_id in self.fast_graph.begin_in_edges(node)..self.fast_graph.end_in_edges(node) { 263 | if self.fast_graph.edges_bwd[edge_id].adj_node == adj_node { 264 | return edge_id; 265 | } 266 | } 267 | panic!("could not find in-edge id") 268 | } 269 | } 270 | 271 | pub struct Params { 272 | /// Smaller values typically yield less shortcuts and a faster preparation time. The relation to 273 | /// query speeds is less clear. For large values that yield a much higher number of shortcuts 274 | /// queries become slow, but otherwise the query speed might only change by a small amount 275 | /// when you change this parameter. The optimal value can only be found heuristically for your 276 | /// specific graph and can even depend on the other parameters below. We recommend trying 277 | /// different values between 0 and 1. 278 | pub hierarchy_depth_factor: f32, 279 | /// This parameter is simply set to 1.0, because only it's relative size compared to 280 | /// hierarchy_depth_factor plays a role. 281 | pub edge_quotient_factor: f32, 282 | /// The maximum number of settled nodes per witness search performed when priorities are 283 | /// calculated for all nodes initially. Since this does not take much time you should probably 284 | /// keep the default. 285 | pub max_settled_nodes_initial_relevance: usize, 286 | /// The maximum number of settled nodes per witness search performed when updating priorities 287 | /// of neighbor nodes after a node was contracted. The preparation time can strongly depend on 288 | /// this value and even setting it to a very small value like 0 or 1 might be feasible. 289 | /// Higher values (like ~500+) should yield less shortcuts and faster query times at the cost of 290 | /// a longer preparation time. Lower values (like ~0-100) should yield a faster preparation at 291 | /// the cost of slower query times and more shortcuts. To know for sure you should still run 292 | /// your own experiments for your specific graph. 293 | pub max_settled_nodes_neighbor_relevance: usize, 294 | /// The maximum number of settled nodes per witness search when contracting a node. Higher values 295 | /// like ~500+ mean less shortcuts (fast graph edges), slower preparation and faster queries. 296 | /// Lower values mean more shortcuts, slower queries and faster preparation. 297 | pub max_settled_nodes_contraction: usize, 298 | } 299 | 300 | impl Params { 301 | pub fn new( 302 | ratio: f32, 303 | max_settled_nodes_initial_relevance: usize, 304 | max_settled_nodes_neighbor_relevance: usize, 305 | max_settled_nodes_contraction: usize, 306 | ) -> Self { 307 | Params { 308 | hierarchy_depth_factor: ratio, 309 | edge_quotient_factor: 1.0, 310 | max_settled_nodes_initial_relevance, 311 | max_settled_nodes_neighbor_relevance, 312 | max_settled_nodes_contraction, 313 | } 314 | } 315 | 316 | pub fn default() -> Self { 317 | Params::new(0.1, 500, 100, 500) 318 | } 319 | } 320 | 321 | pub struct ParamsWithOrder { 322 | /// The maximum number of settled nodes per witness search when contracting a node. Smaller 323 | /// values mean slower queries, more shortcuts, but a faster preparation. Note that the 324 | /// performance can also strongly depend on the relation between this parameter and 325 | /// Params::max_settled_nodes_contraction that was used to build the FastGraph and obtain the 326 | /// node ordering initially. In most cases you should use the same value for these two parameters. 327 | pub max_settled_nodes_contraction_with_order: usize, 328 | } 329 | 330 | impl ParamsWithOrder { 331 | pub fn new(max_settled_nodes_contraction_with_order: usize) -> Self { 332 | ParamsWithOrder { 333 | max_settled_nodes_contraction_with_order, 334 | } 335 | } 336 | 337 | pub fn default() -> Self { 338 | ParamsWithOrder::new(100) 339 | } 340 | } 341 | 342 | #[cfg(test)] 343 | mod tests { 344 | use crate::shortest_path::ShortestPath; 345 | 346 | use super::*; 347 | // todo: maybe move these tests and the ones in lib.rs into the 'tests' folder as integration tests 348 | // see rust docs 349 | use crate::{ 350 | calc_path, create_calculator, prepare, prepare_with_order, PathCalculator, WEIGHT_MAX, 351 | }; 352 | 353 | #[test] 354 | fn calc_path_linear_bwd_only() { 355 | // 2->0->1 356 | let mut g = InputGraph::new(); 357 | g.add_edge(2, 0, 9); 358 | g.add_edge(0, 1, 49); 359 | g.freeze(); 360 | let fast_graph = prepare_with_order(&g, &vec![0, 1, 2]).unwrap(); 361 | assert_path(&fast_graph, 2, 1, 58, vec![2, 0, 1]); 362 | } 363 | 364 | #[test] 365 | fn calc_path_linear_fwd_only() { 366 | // 1->0->2 367 | let mut g = InputGraph::new(); 368 | g.add_edge(1, 0, 9); 369 | g.add_edge(0, 2, 49); 370 | g.freeze(); 371 | let fast_graph = prepare_with_order(&g, &vec![0, 1, 2]).unwrap(); 372 | assert_path(&fast_graph, 1, 2, 58, vec![1, 0, 2]); 373 | } 374 | 375 | #[test] 376 | fn calc_path_simple() { 377 | // --->------4 378 | // / | 379 | // 0 - 1 - 2 - 3 380 | let mut g = InputGraph::new(); 381 | g.add_edge_bidir(0, 1, 5); 382 | g.add_edge_bidir(1, 2, 3); 383 | g.add_edge_bidir(2, 3, 2); 384 | g.add_edge_bidir(3, 4, 6); 385 | g.add_edge(0, 4, 2); 386 | g.freeze(); 387 | 388 | let fast_graph = prepare_with_order(&g, &vec![0, 1, 2, 3, 4]).unwrap(); 389 | assert_path(&fast_graph, 0, 4, 2, vec![0, 4]); 390 | assert_path(&fast_graph, 4, 0, 16, vec![4, 3, 2, 1, 0]); 391 | assert_path(&fast_graph, 1, 4, 7, vec![1, 0, 4]); 392 | assert_path(&fast_graph, 2, 4, 8, vec![2, 3, 4]); 393 | } 394 | 395 | #[test] 396 | fn calc_path_another() { 397 | // 4 398 | // | \ 399 | // 0 -> 2 400 | // | | 401 | // 3 - 1 402 | let mut g = InputGraph::new(); 403 | g.add_edge(0, 2, 1); 404 | g.add_edge(0, 4, 9); 405 | g.add_edge(1, 3, 3); 406 | g.add_edge(2, 1, 8); 407 | g.add_edge(3, 0, 4); 408 | g.add_edge(3, 1, 8); 409 | g.add_edge(4, 2, 4); 410 | g.freeze(); 411 | 412 | let fast_graph = prepare_with_order(&g, &vec![0, 1, 2, 3, 4]).unwrap(); 413 | assert_path(&fast_graph, 4, 3, 15, vec![4, 2, 1, 3]); 414 | } 415 | 416 | fn assert_path( 417 | fast_graph: &FastGraph, 418 | source: NodeId, 419 | target: NodeId, 420 | weight: Weight, 421 | nodes: Vec, 422 | ) { 423 | let fast_path = calc_path(fast_graph, source, target); 424 | assert_eq!( 425 | fast_path, 426 | Some(ShortestPath::new(source, target, weight, nodes.clone())) 427 | ); 428 | // ShortestPath PartialEq does not consider nodes! 429 | assert_eq!(nodes, fast_path.unwrap().get_nodes().clone(),); 430 | } 431 | 432 | #[test] 433 | fn multiple_sources() { 434 | // 0 -> 1 -> 2 <- 5 435 | // 3 -> 4 ->/ 436 | let mut input_graph = InputGraph::new(); 437 | input_graph.add_edge(0, 1, 3); 438 | input_graph.add_edge(1, 2, 4); 439 | input_graph.add_edge(3, 4, 2); 440 | input_graph.add_edge(4, 2, 3); 441 | input_graph.add_edge(5, 2, 2); 442 | input_graph.freeze(); 443 | let fast_graph = prepare(&input_graph); 444 | let mut path_calculator = create_calculator(&fast_graph); 445 | // two different options for source, without initial weight 446 | assert_path_multiple_sources_and_targets( 447 | &mut path_calculator, 448 | &fast_graph, 449 | vec![(0, 0), (3, 0)], 450 | vec![(2, 0)], 451 | vec![3, 4, 2], 452 | 5, 453 | ); 454 | // two different options for source, with initial weights, 0->1->2's weight is higher, 455 | // but since the initial weight is smaller it is the shortest path 456 | assert_path_multiple_sources_and_targets( 457 | &mut path_calculator, 458 | &fast_graph, 459 | vec![(0, 1), (3, 4)], 460 | vec![(2, 0)], 461 | vec![0, 1, 2], 462 | 8, 463 | ); 464 | // one option appearing twice with different initial weights, the smaller one should be taken 465 | assert_path_multiple_sources_and_targets( 466 | &mut path_calculator, 467 | &fast_graph, 468 | vec![(0, 5), (0, 3)], 469 | vec![(2, 0)], 470 | vec![0, 1, 2], 471 | 10, 472 | ); 473 | // ... now put the smaller weight first 474 | assert_path_multiple_sources_and_targets( 475 | &mut path_calculator, 476 | &fast_graph, 477 | vec![(5, 10), (5, 20)], 478 | vec![(2, 0)], 479 | vec![5, 2], 480 | 12, 481 | ); 482 | // start options equal the target 483 | assert_path_multiple_sources_and_targets( 484 | &mut path_calculator, 485 | &fast_graph, 486 | vec![(1, 10), (1, 1)], 487 | vec![(1, 0)], 488 | vec![1], 489 | 1, 490 | ); 491 | // one of the start options equals the target, but still the shortest path is another one 492 | assert_path_multiple_sources_and_targets( 493 | &mut path_calculator, 494 | &fast_graph, 495 | vec![(2, 10), (0, 0)], 496 | vec![(2, 0)], 497 | vec![0, 1, 2], 498 | 7, 499 | ); 500 | // start options with max weight cannot yield a shortest path 501 | assert_path_multiple_sources_and_targets_not_found( 502 | &mut path_calculator, 503 | &fast_graph, 504 | vec![(1, WEIGHT_MAX)], 505 | vec![(1, 0)], 506 | ); 507 | // .. or at least they are ignored in case there are other ones 508 | assert_path_multiple_sources_and_targets( 509 | &mut path_calculator, 510 | &fast_graph, 511 | vec![(1, WEIGHT_MAX), (0, 3)], 512 | vec![(1, 0)], 513 | vec![0, 1], 514 | 6, 515 | ); 516 | assert_path_multiple_sources_and_targets( 517 | &mut path_calculator, 518 | &fast_graph, 519 | vec![(1, WEIGHT_MAX), (3, 3)], 520 | vec![(2, 0)], 521 | vec![3, 4, 2], 522 | 8, 523 | ); 524 | } 525 | 526 | #[test] 527 | fn multiple_targets() { 528 | // 0 <- 1 <- 2 529 | // 3 <- 4 <-/ 530 | let mut input_graph = InputGraph::new(); 531 | input_graph.add_edge(1, 0, 3); 532 | input_graph.add_edge(2, 1, 4); 533 | input_graph.add_edge(4, 3, 2); 534 | input_graph.add_edge(2, 4, 3); 535 | input_graph.freeze(); 536 | let fast_graph = prepare(&input_graph); 537 | let mut path_calculator = create_calculator(&fast_graph); 538 | // two different options for target, without initial weight 539 | assert_path_multiple_sources_and_targets( 540 | &mut path_calculator, 541 | &fast_graph, 542 | vec![(2, 0)], 543 | vec![(0, 0), (3, 0)], 544 | vec![2, 4, 3], 545 | 5, 546 | ); 547 | // two different options for target, with initial weight 548 | assert_path_multiple_sources_and_targets( 549 | &mut path_calculator, 550 | &fast_graph, 551 | vec![(2, 0)], 552 | vec![(0, 0), (3, 1)], 553 | vec![2, 4, 3], 554 | 6, 555 | ); 556 | assert_path_multiple_sources_and_targets( 557 | &mut path_calculator, 558 | &fast_graph, 559 | vec![(2, 0)], 560 | vec![(0, 0), (3, 3)], 561 | vec![2, 1, 0], 562 | 7, 563 | ); 564 | // start==end 565 | assert_path_multiple_sources_and_targets( 566 | &mut path_calculator, 567 | &fast_graph, 568 | vec![(4, 0)], 569 | vec![(4, 3), (4, 1)], 570 | vec![4], 571 | 1, 572 | ) 573 | } 574 | 575 | #[test] 576 | fn multiple_sources_and_targets() { 577 | // 0 -- 1 -- 2 -- 3 -- 4 578 | // 5 -- 6 --/ \-- 7 -- 8 579 | let mut input_graph = InputGraph::new(); 580 | input_graph.add_edge_bidir(0, 1, 1); 581 | input_graph.add_edge_bidir(1, 2, 2); 582 | input_graph.add_edge_bidir(2, 3, 3); 583 | input_graph.add_edge_bidir(3, 4, 4); 584 | input_graph.add_edge_bidir(5, 6, 5); 585 | input_graph.add_edge_bidir(6, 2, 6); 586 | input_graph.add_edge_bidir(2, 7, 7); 587 | input_graph.add_edge_bidir(7, 8, 8); 588 | input_graph.freeze(); 589 | let fast_graph = prepare(&input_graph); 590 | let mut path_calculator = create_calculator(&fast_graph); 591 | assert_path_multiple_sources_and_targets( 592 | &mut path_calculator, 593 | &fast_graph, 594 | vec![(1, 7), (6, 2), (5, 6)], 595 | vec![(3, 1), (4, 9), (5, 7)], 596 | vec![6, 2, 3], 597 | 12, 598 | ); 599 | assert_path_multiple_sources_and_targets( 600 | &mut path_calculator, 601 | &fast_graph, 602 | vec![(1, 7), (6, 2)], 603 | vec![(1, 9), (6, 3)], 604 | vec![6], 605 | 5, 606 | ); 607 | } 608 | 609 | fn assert_path_multiple_sources_and_targets( 610 | path_calculator: &mut PathCalculator, 611 | fast_graph: &FastGraph, 612 | sources: Vec<(NodeId, Weight)>, 613 | targets: Vec<(NodeId, Weight)>, 614 | expected_nodes: Vec, 615 | expected_weight: Weight, 616 | ) { 617 | let fast_path = 618 | path_calculator.calc_path_multiple_sources_and_targets(fast_graph, sources, targets); 619 | assert!(fast_path.is_some()); 620 | let p = fast_path.unwrap(); 621 | assert_eq!(expected_nodes, p.get_nodes().clone(), "unexpected nodes"); 622 | assert_eq!(expected_weight, p.get_weight(), "unexpected weight"); 623 | } 624 | 625 | fn assert_path_multiple_sources_and_targets_not_found( 626 | path_calculator: &mut PathCalculator, 627 | fast_graph: &FastGraph, 628 | sources: Vec<(NodeId, Weight)>, 629 | targets: Vec<(NodeId, Weight)>, 630 | ) { 631 | let fast_path = 632 | path_calculator.calc_path_multiple_sources_and_targets(&fast_graph, sources, targets); 633 | assert!(fast_path.is_none(), "there should be no path"); 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /src/floyd_warshall.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::cmp; 21 | 22 | use crate::constants::{NodeId, Weight, WEIGHT_MAX}; 23 | use crate::input_graph::InputGraph; 24 | 25 | pub struct FloydWarshall { 26 | num_nodes: usize, 27 | matrix: Vec, 28 | } 29 | 30 | impl FloydWarshall { 31 | pub fn new(num_nodes: usize) -> Self { 32 | // todo: move num_nodes initialization into prepare and prevent calling calc_path before 33 | // prepare 34 | FloydWarshall { 35 | num_nodes, 36 | matrix: vec![WEIGHT_MAX; num_nodes * num_nodes], 37 | } 38 | } 39 | 40 | pub fn prepare(&mut self, input_graph: &InputGraph) { 41 | assert_eq!( 42 | input_graph.get_num_nodes(), 43 | self.num_nodes, 44 | "input graph has invalid number of nodes" 45 | ); 46 | let n = self.num_nodes; 47 | for e in input_graph.get_edges() { 48 | self.matrix[e.from * n + e.to] = e.weight; 49 | } 50 | for k in 0..n { 51 | for i in 0..n { 52 | for j in 0..n { 53 | if i == j { 54 | self.matrix[i * n + j] = 0; 55 | } 56 | let weight_ik = self.matrix[i * n + k]; 57 | let weight_kj = self.matrix[k * n + j]; 58 | if weight_ik == WEIGHT_MAX || weight_kj == WEIGHT_MAX { 59 | continue; 60 | } 61 | let idx = i * n + j; 62 | self.matrix[idx] = cmp::min(self.matrix[idx], weight_ik + weight_kj) 63 | } 64 | } 65 | } 66 | } 67 | 68 | pub fn calc_weight(&self, source: NodeId, target: NodeId) -> Weight { 69 | return self.matrix[source * self.num_nodes + target]; 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | #[test] 78 | fn calc_weights() { 79 | // 0 -> 1 -- 3 80 | // | | 81 | // 4 -> 5 -> 6 82 | // | | 83 | // 7 -> 8 84 | let mut g = InputGraph::new(); 85 | g.add_edge(0, 1, 6); 86 | g.add_edge(0, 4, 1); 87 | g.add_edge(4, 5, 1); 88 | g.add_edge(5, 7, 1); 89 | g.add_edge(7, 8, 1); 90 | g.add_edge(8, 6, 1); 91 | g.add_edge(6, 3, 1); 92 | g.add_edge(3, 1, 1); 93 | g.add_edge(1, 3, 1); 94 | g.add_edge(5, 6, 4); 95 | g.freeze(); 96 | let mut fw = FloydWarshall::new(g.get_num_nodes()); 97 | fw.prepare(&g); 98 | assert_eq!(fw.calc_weight(0, 3), 6); 99 | assert_eq!(fw.calc_weight(5, 3), 4); 100 | assert_eq!(fw.calc_weight(1, 1), 0); 101 | assert_eq!(fw.calc_weight(5, 5), 0); 102 | assert_eq!(fw.calc_weight(6, 5), WEIGHT_MAX); 103 | assert_eq!(fw.calc_weight(8, 0), WEIGHT_MAX); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/heap_item.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::cmp::Ordering; 21 | 22 | use crate::constants::NodeId; 23 | use crate::constants::Weight; 24 | 25 | #[derive(Eq, Copy, Clone, Debug)] 26 | pub struct HeapItem { 27 | pub weight: Weight, 28 | pub node_id: NodeId, 29 | } 30 | 31 | impl HeapItem { 32 | pub fn new(weight: Weight, node_id: NodeId) -> HeapItem { 33 | HeapItem { weight, node_id } 34 | } 35 | } 36 | 37 | impl PartialOrd for HeapItem { 38 | fn partial_cmp(&self, other: &HeapItem) -> Option { 39 | Some(self.cmp(other)) 40 | } 41 | } 42 | 43 | impl Ord for HeapItem { 44 | fn cmp(&self, other: &HeapItem) -> Ordering { 45 | self.weight.cmp(&other.weight).reverse() 46 | } 47 | } 48 | 49 | impl PartialEq for HeapItem { 50 | fn eq(&self, other: &HeapItem) -> bool { 51 | self.weight == other.weight 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/input_graph.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::cmp; 21 | use std::fmt; 22 | use std::fs::File; 23 | use std::io::{BufRead, BufReader, BufWriter, Write}; 24 | 25 | #[cfg(test)] 26 | use rand::rngs::StdRng; 27 | #[cfg(test)] 28 | use rand::Rng; 29 | 30 | use serde::{Deserialize, Serialize}; 31 | 32 | use crate::constants::NodeId; 33 | use crate::constants::Weight; 34 | 35 | #[derive(Serialize, Deserialize, Clone)] 36 | pub struct InputGraph { 37 | edges: Vec, 38 | num_nodes: usize, 39 | frozen: bool, 40 | } 41 | 42 | impl InputGraph { 43 | pub fn new() -> Self { 44 | InputGraph { 45 | edges: Vec::new(), 46 | num_nodes: 0, 47 | frozen: false, 48 | } 49 | } 50 | 51 | /// Builds a random input graph, mostly used for testing purposes 52 | #[cfg(test)] 53 | pub fn random(rng: &mut StdRng, num_nodes: usize, mean_degree: f32) -> Self { 54 | InputGraph::build_random_graph(rng, num_nodes, mean_degree) 55 | } 56 | 57 | /// Reads an input graph from a text file, using the following format: 58 | /// a 59 | /// where , and must be >= 0. 60 | /// All other lines are ignored. 61 | /// This function is compatible to DIMACS files, but consider using InputGraph::from_dimacs 62 | /// for these instead. 63 | /// Mostly used for performance testing. 64 | pub fn from_file(filename: &str) -> Self { 65 | InputGraph::read_from_file(filename) 66 | } 67 | 68 | /// Writes the input graph to a text file, using the following format: 69 | /// a 70 | /// Mostly used for performance testing. 71 | pub fn to_file(&self, filename: &str) -> Result<(), std::io::Error> { 72 | let mut f = BufWriter::new(File::create(filename)?); 73 | for edge in self.get_edges() { 74 | writeln!(f, "a {} {} {}", edge.from, edge.to, edge.weight)?; 75 | } 76 | Ok(()) 77 | } 78 | 79 | /// Reads an input graph from a text file, using the DIMACS format: 80 | /// http://users.diag.uniroma1.it/challenge9/format.shtml#graph 81 | /// 82 | /// * empty lines and lines starting with 'c' are ignored: 83 | /// c 84 | /// * the 'problem line' states the number of nodes and edges of the graph: 85 | /// it must be written before any arc line 86 | /// p 87 | /// * there is one line per (directed) edge: 88 | /// a 89 | /// where and must be >= 1 and must be >= 0 90 | /// Note that here, in contrast to InputGraph::from_file, the node IDs are 1-based, not 91 | /// 0-based. They will be converted to 0-based IDs internally. 92 | /// 93 | /// Mostly used for performance testing. 94 | pub fn from_dimacs_file(filename: &str) -> Self { 95 | InputGraph::read_from_dimacs(filename) 96 | } 97 | 98 | /// Writes the input graph to a text file, using the DIMACS format: 99 | /// p sp 100 | /// a 101 | /// Note that and are 1-based, so they are incremented by one compared to the 102 | /// node IDs used by internally. 103 | /// Mostly used for performance testing. 104 | pub fn to_dimacs_file(&self, filename: &str) -> Result<(), std::io::Error> { 105 | let mut f = BufWriter::new(File::create(filename)?); 106 | writeln!(f, "p sp {} {}", self.get_num_nodes(), self.get_num_edges())?; 107 | for edge in self.get_edges() { 108 | writeln!(f, "a {} {} {}", edge.from + 1, edge.to + 1, edge.weight)?; 109 | } 110 | Ok(()) 111 | } 112 | 113 | pub fn add_edge(&mut self, from: NodeId, to: NodeId, weight: Weight) -> usize { 114 | self.do_add_edge(from, to, weight, false) 115 | } 116 | 117 | pub fn add_edge_bidir(&mut self, from: NodeId, to: NodeId, weight: Weight) -> usize { 118 | self.do_add_edge(from, to, weight, true) 119 | } 120 | 121 | pub fn get_edges(&self) -> &Vec { 122 | self.check_frozen(); 123 | &self.edges 124 | } 125 | 126 | pub fn get_num_nodes(&self) -> usize { 127 | self.check_frozen(); 128 | self.num_nodes 129 | } 130 | 131 | pub fn get_num_edges(&self) -> usize { 132 | self.check_frozen(); 133 | self.edges.len() 134 | } 135 | 136 | pub fn freeze(&mut self) { 137 | if self.frozen { 138 | panic!("Input graph is already frozen"); 139 | } 140 | self.sort(); 141 | self.remove_duplicate_edges(); 142 | self.frozen = true; 143 | } 144 | 145 | pub fn thaw(&mut self) { 146 | self.frozen = false; 147 | } 148 | 149 | fn sort(&mut self) { 150 | self.edges.sort_unstable_by(|a, b| { 151 | a.from 152 | .cmp(&b.from) 153 | .then(a.to.cmp(&b.to)) 154 | .then(a.weight.cmp(&&b.weight)) 155 | }); 156 | } 157 | 158 | fn remove_duplicate_edges(&mut self) { 159 | // we go through (already sorted!) list of edges and remove duplicates 160 | let len_before = self.edges.len(); 161 | self.edges.dedup_by(|a, b| a.from == b.from && a.to == b.to); 162 | if len_before != self.edges.len() { 163 | warn!( 164 | "There were {} duplicate edges, only the ones with lowest weight were kept", 165 | len_before - self.edges.len() 166 | ); 167 | } 168 | } 169 | 170 | pub fn unit_test_output_string(&self) -> String { 171 | return self 172 | .edges 173 | .iter() 174 | .map(|e| e.unit_test_output_string()) 175 | .collect::>() 176 | .join("\n") 177 | + "\n"; 178 | } 179 | 180 | fn check_frozen(&self) { 181 | if !self.frozen { 182 | panic!("You need to call freeze() before using the input graph") 183 | } 184 | } 185 | 186 | fn do_add_edge(&mut self, from: NodeId, to: NodeId, weight: Weight, bidir: bool) -> usize { 187 | if self.frozen { 188 | panic!("Graph is frozen already, for further changes first use thaw()"); 189 | } 190 | if from == to { 191 | warn!( 192 | "Loop edges are not allowed. Skipped edge! from: {}, to: {}, weight: {}", 193 | from, to, weight 194 | ); 195 | return 0; 196 | } 197 | if weight < 1 { 198 | warn!( 199 | "Zero weight edges are not allowed. Skipped edge! from: {}, to: {}, weight: {}", 200 | from, to, weight 201 | ); 202 | return 0; 203 | } 204 | self.num_nodes = cmp::max(self.num_nodes, cmp::max(from, to) + 1); 205 | self.edges.push(Edge::new(from, to, weight)); 206 | if bidir { 207 | self.edges.push(Edge::new(to, from, weight)); 208 | } 209 | if bidir { 210 | 2 211 | } else { 212 | 1 213 | } 214 | } 215 | 216 | #[cfg(test)] 217 | fn build_random_graph(rng: &mut StdRng, num_nodes: usize, mean_degree: f32) -> InputGraph { 218 | let num_edges = (mean_degree * num_nodes as f32) as usize; 219 | let mut result = InputGraph::new(); 220 | let mut edge_count = 0; 221 | loop { 222 | let head = rng.gen_range(0, num_nodes); 223 | let tail = rng.gen_range(0, num_nodes); 224 | // limit max weight, but otherwise allow duplicates, loops etc. to make sure clean-up 225 | // inside InputGraph works correctly 226 | let weight = rng.gen_range(1, 100); 227 | edge_count += result.add_edge(tail, head, weight); 228 | if edge_count == num_edges { 229 | break; 230 | } 231 | } 232 | result.freeze(); 233 | result 234 | } 235 | 236 | fn read_from_file(filename: &str) -> Self { 237 | let file = File::open(filename).unwrap(); 238 | let reader = BufReader::new(file); 239 | let mut g = InputGraph::new(); 240 | for (index, line) in reader.lines().enumerate() { 241 | let s: String = line.unwrap(); 242 | if s.starts_with("a ") { 243 | let (from, to, weight) = InputGraph::read_arc_line(index, &s); 244 | g.add_edge(from, to, weight); 245 | } else { 246 | continue; 247 | } 248 | } 249 | g.freeze(); 250 | g 251 | } 252 | 253 | fn read_from_dimacs(filename: &str) -> Self { 254 | let file = File::open(filename).unwrap(); 255 | let reader = BufReader::new(file); 256 | let mut g = InputGraph::new(); 257 | let mut nodes = 0; 258 | let mut edges = 0; 259 | let mut curr_edges = 0; 260 | let mut found_problem_line = false; 261 | for (index, line) in reader.lines().enumerate() { 262 | let s: String = line.unwrap(); 263 | if s.is_empty() || s.starts_with("c") { 264 | continue; 265 | } else if s.starts_with("p sp ") { 266 | if found_problem_line { 267 | panic!( 268 | "There should be only one problem line, but found: {} | {}", 269 | index + 1, 270 | s 271 | ); 272 | } 273 | let mut split = s[5..].split_whitespace(); 274 | nodes = split.next().unwrap().parse::().unwrap(); 275 | edges = split.next().unwrap().parse::().unwrap(); 276 | assert!(split.next().is_none(), "Invalid problem line: {}", s); 277 | found_problem_line = true; 278 | } else if s.starts_with("a ") { 279 | assert!( 280 | found_problem_line, 281 | "The problem line must be written before the arc lines" 282 | ); 283 | let (from, to, weight) = InputGraph::read_arc_line(index, &s); 284 | assert!( 285 | from <= nodes && to <= nodes, 286 | "Invalid nodes in line: {} | {}", 287 | index + 1, 288 | s 289 | ); 290 | assert!( 291 | curr_edges < edges, 292 | "Too many arc lines: {}, expected: {}", 293 | curr_edges + 1, 294 | edges 295 | ); 296 | 297 | assert!( 298 | from > 0 && to > 0, 299 | "Invalid arc line: {} | {}", 300 | index + 1, 301 | s 302 | ); 303 | // we convert 1-based node IDs from DIMACS to 0-based node IDs 304 | g.add_edge(from - 1, to - 1, weight); 305 | curr_edges += 1; 306 | } else { 307 | panic!( 308 | "Invalid line: {} {}\nAll non-empty lines must start with 'c', 'p' or 'a'", 309 | index, s 310 | ); 311 | } 312 | } 313 | assert_eq!( 314 | curr_edges, edges, 315 | "Not enough arc lines: {}, expected: {}", 316 | curr_edges, edges 317 | ); 318 | g.freeze(); 319 | g 320 | } 321 | 322 | fn read_arc_line(index: usize, line: &String) -> (usize, usize, usize) { 323 | let mut split = line[2..].split_whitespace(); 324 | let from = split.next().unwrap().parse::().unwrap(); 325 | let to = split.next().unwrap().parse::().unwrap(); 326 | let weight = split.next().unwrap().parse::().unwrap(); 327 | assert!( 328 | split.next().is_none(), 329 | "Invalid arc line: {} | {}", 330 | index + 1, 331 | line 332 | ); 333 | (from, to, weight) 334 | } 335 | } 336 | 337 | impl fmt::Debug for InputGraph { 338 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 339 | write!(f, "{}", self.unit_test_output_string()) 340 | } 341 | } 342 | 343 | impl Default for InputGraph { 344 | fn default() -> Self { 345 | Self::new() 346 | } 347 | } 348 | 349 | #[derive(Serialize, Deserialize, Debug, Copy, Clone)] 350 | pub struct Edge { 351 | pub from: NodeId, 352 | pub to: NodeId, 353 | pub weight: Weight, 354 | } 355 | 356 | impl Edge { 357 | pub fn new(from: NodeId, to: NodeId, weight: Weight) -> Edge { 358 | Edge { from, to, weight } 359 | } 360 | 361 | pub fn unit_test_output_string(&self) -> String { 362 | return format!("g.add_edge({}, {}, {});", self.from, self.to, self.weight); 363 | } 364 | } 365 | 366 | #[cfg(test)] 367 | mod tests { 368 | use super::*; 369 | 370 | #[test] 371 | #[should_panic] 372 | fn panic_if_not_frozen_get_edges() { 373 | let mut g = InputGraph::new(); 374 | g.add_edge(0, 1, 3); 375 | g.get_edges(); 376 | } 377 | 378 | #[test] 379 | #[should_panic] 380 | fn panic_if_not_frozen_get_num_edges() { 381 | let mut g = InputGraph::new(); 382 | g.add_edge(0, 1, 3); 383 | g.get_num_edges(); 384 | } 385 | 386 | #[test] 387 | #[should_panic] 388 | fn panic_if_not_frozen_get_num_nodes() { 389 | let mut g = InputGraph::new(); 390 | g.add_edge(0, 1, 3); 391 | g.get_num_nodes(); 392 | } 393 | 394 | #[test] 395 | #[should_panic] 396 | fn panic_if_frozen_add_edge() { 397 | let mut g = InputGraph::new(); 398 | g.add_edge(0, 1, 3); 399 | g.freeze(); 400 | g.add_edge(2, 5, 4); 401 | } 402 | 403 | #[test] 404 | fn freeze_and_thaw() { 405 | let mut g = InputGraph::new(); 406 | g.add_edge(0, 5, 10); 407 | g.add_edge(0, 5, 5); 408 | g.freeze(); 409 | assert_eq!(1, g.get_num_edges()); 410 | g.thaw(); 411 | g.add_edge(0, 5, 1); 412 | g.freeze(); 413 | assert_eq!(1, g.get_num_edges()); 414 | assert_eq!(1, g.get_edges()[0].weight); 415 | } 416 | 417 | #[test] 418 | fn num_nodes() { 419 | let mut g = InputGraph::new(); 420 | g.add_edge(7, 1, 2); 421 | g.add_edge(5, 6, 4); 422 | g.add_edge(11, 8, 3); 423 | g.freeze(); 424 | assert_eq!(12, g.get_num_nodes()); 425 | } 426 | 427 | #[test] 428 | fn skips_loops() { 429 | let mut g = InputGraph::new(); 430 | g.add_edge(0, 1, 3); 431 | g.add_edge(4, 4, 2); 432 | g.add_edge(2, 5, 4); 433 | g.freeze(); 434 | assert_eq!(2, g.get_num_edges()); 435 | } 436 | 437 | #[test] 438 | fn skips_zero_weight_edges() { 439 | let mut g = InputGraph::new(); 440 | g.add_edge(0, 1, 5); 441 | g.add_edge(1, 2, 0); 442 | g.add_edge(2, 3, 3); 443 | g.freeze(); 444 | assert_eq!(2, g.get_num_edges()); 445 | } 446 | 447 | #[test] 448 | fn skips_duplicate_edges() { 449 | let mut g = InputGraph::new(); 450 | g.add_edge(0, 1, 7); 451 | g.add_edge(2, 3, 5); 452 | g.add_edge(0, 2, 3); 453 | g.add_edge(0, 1, 2); 454 | g.add_edge(4, 6, 9); 455 | g.add_edge(0, 1, 4); 456 | g.freeze(); 457 | assert_eq!(4, g.get_num_edges()); 458 | // edges should be sorted and duplicates should be removed keeping only the ones with 459 | // lowest weight 460 | let weights = g 461 | .get_edges() 462 | .iter() 463 | .map(|e| e.weight) 464 | .collect::>(); 465 | assert_eq!(vec![2, 3, 5, 9], weights); 466 | } 467 | 468 | #[test] 469 | fn skips_duplicate_edges_more() { 470 | let mut g = InputGraph::new(); 471 | g.add_edge(1, 3, 43); 472 | g.add_edge(3, 2, 90); 473 | g.add_edge(3, 2, 88); 474 | g.add_edge(2, 3, 87); 475 | g.add_edge(3, 0, 75); 476 | g.add_edge(0, 2, 45); 477 | g.add_edge(1, 3, 71); 478 | g.add_edge(4, 3, 5); 479 | g.add_edge(1, 3, 91); 480 | g.freeze(); 481 | assert_eq!(6, g.get_num_edges()); 482 | let weights = g 483 | .get_edges() 484 | .iter() 485 | .map(|e| e.weight) 486 | .collect::>(); 487 | assert_eq!(vec![45, 43, 87, 75, 88, 5], weights); 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | #[macro_use] 21 | extern crate log; 22 | 23 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 24 | 25 | pub use crate::constants::*; 26 | pub use crate::fast_graph::FastGraph; 27 | pub use crate::fast_graph32::FastGraph32; 28 | pub use crate::fast_graph_builder::FastGraphBuilder; 29 | pub use crate::fast_graph_builder::Params; 30 | pub use crate::fast_graph_builder::ParamsWithOrder; 31 | pub use crate::input_graph::Edge; 32 | pub use crate::input_graph::InputGraph; 33 | pub use crate::path_calculator::PathCalculator; 34 | pub use crate::shortest_path::ShortestPath; 35 | 36 | mod constants; 37 | #[cfg(test)] 38 | mod dijkstra; 39 | mod fast_graph; 40 | mod fast_graph32; 41 | mod fast_graph_builder; 42 | #[cfg(test)] 43 | mod floyd_warshall; 44 | mod heap_item; 45 | mod input_graph; 46 | mod node_contractor; 47 | mod path_calculator; 48 | mod preparation_graph; 49 | mod shortest_path; 50 | mod valid_flags; 51 | mod witness_search; 52 | 53 | /// Prepares the given `InputGraph` for fast shortest path calculations. 54 | pub fn prepare(input_graph: &InputGraph) -> FastGraph { 55 | FastGraphBuilder::build(input_graph) 56 | } 57 | 58 | /// Like `prepare()`, but allows specifying some parameters used for the graph preparation. 59 | pub fn prepare_with_params(input_graph: &InputGraph, params: &Params) -> FastGraph { 60 | FastGraphBuilder::build_with_params(input_graph, params) 61 | } 62 | 63 | /// Prepares the given input graph using a fixed node ordering, which can be any permutation 64 | /// of the node ids. This can be used to speed up the graph preparation if you have done 65 | /// it for a similar graph with an equal number of nodes. For example if you have changed some 66 | /// of the edge weights only. 67 | pub fn prepare_with_order(input_graph: &InputGraph, order: &[NodeId]) -> Result { 68 | FastGraphBuilder::build_with_order(input_graph, order) 69 | } 70 | 71 | /// Like `prepare_with_order()`, but allows specifying some parameters used for the graph preparation 72 | pub fn prepare_with_order_with_params( 73 | input_graph: &InputGraph, 74 | order: &[NodeId], 75 | params: &ParamsWithOrder, 76 | ) -> Result { 77 | FastGraphBuilder::build_with_order_with_params(input_graph, order, params) 78 | } 79 | 80 | /// Calculates the shortest path from `source` to `target`. 81 | pub fn calc_path(fast_graph: &FastGraph, source: NodeId, target: NodeId) -> Option { 82 | let mut calc = PathCalculator::new(fast_graph.get_num_nodes()); 83 | calc.calc_path(fast_graph, source, target) 84 | } 85 | 86 | /// Calculates the shortest path from any of the `sources` to any of the `targets`. 87 | /// 88 | /// The path returned will be the one with minimum weight among all possible paths between the sources 89 | /// and targets. The sources and targets can also be assigned an initial weight. In this case the 90 | /// path returned will be the one that minimizes start_weight + path-weight + target_weight. The 91 | /// weight of the path also includes start_weight and target_weight. 92 | pub fn calc_path_multiple_sources_and_targets( 93 | fast_graph: &FastGraph, 94 | sources: Vec<(NodeId, Weight)>, 95 | target: Vec<(NodeId, Weight)>, 96 | ) -> Option { 97 | let mut calc = PathCalculator::new(fast_graph.get_num_nodes()); 98 | calc.calc_path_multiple_sources_and_targets(fast_graph, sources, target) 99 | } 100 | 101 | /// Creates a `PathCalculator` that can be used to run many shortest path calculations in a row. 102 | /// This is the preferred way to calculate shortest paths in case you are calculating more than 103 | /// one path. Use one `PathCalculator` for each thread. 104 | pub fn create_calculator(fast_graph: &FastGraph) -> PathCalculator { 105 | PathCalculator::new(fast_graph.get_num_nodes()) 106 | } 107 | 108 | /// Returns the node ordering of a prepared graph. This can be used to run the preparation with 109 | /// `prepare_with_order()`. 110 | pub fn get_node_ordering(fast_graph: &FastGraph) -> Vec { 111 | fast_graph.get_node_ordering() 112 | } 113 | 114 | /// When serializing a `FastGraph` in a larger struct, use `#[serde(serialize_with = 115 | /// "fast_paths::serialize_32`)]` to transform the graph to a 32-bit representation. This will use 116 | /// 50% more RAM than serializing without transformation, but the resulting size will be 50% less. 117 | /// It will panic if the graph has more than 2^32 nodes or edges or values for weight. 118 | pub fn serialize_32(fg: &FastGraph, s: S) -> Result { 119 | FastGraph32::new(fg).serialize(s) 120 | } 121 | 122 | /// When deserializing a `FastGraph` in a larger struct, use `#[serde(deserialize_with = 123 | /// "fast_paths::deserialize_32`)]` to transform the graph from a 32-bit representation to the 124 | /// current platform's supported size. This is necessary when serializing on a 64-bit system and 125 | /// deserializing on a 32-bit system, such as WASM. 126 | pub fn deserialize_32<'de, D: Deserializer<'de>>(d: D) -> Result { 127 | let fg32 = ::deserialize(d)?; 128 | Ok(fg32.convert_to_usize()) 129 | } 130 | 131 | #[cfg(test)] 132 | mod tests { 133 | use std::error::Error; 134 | use std::fs::{remove_file, File}; 135 | use std::time::SystemTime; 136 | 137 | use rand::rngs::StdRng; 138 | use rand::Rng; 139 | use stopwatch::Stopwatch; 140 | 141 | use crate::constants::NodeId; 142 | use crate::dijkstra::Dijkstra; 143 | use crate::fast_graph::FastGraph; 144 | use crate::floyd_warshall::FloydWarshall; 145 | use crate::path_calculator::PathCalculator; 146 | use crate::preparation_graph::PreparationGraph; 147 | 148 | use super::*; 149 | use crate::fast_graph_builder::ParamsWithOrder; 150 | 151 | #[test] 152 | fn routing_on_random_graph() { 153 | const REPEATS: usize = 100; 154 | for _i in 0..REPEATS { 155 | run_test_on_random_graph(); 156 | } 157 | } 158 | 159 | fn run_test_on_random_graph() { 160 | const NUM_NODES: usize = 50; 161 | const NUM_QUERIES: usize = 1_000; 162 | const MEAN_DEGREE: f32 = 2.0; 163 | 164 | let mut rng = create_rng(); 165 | let input_graph = InputGraph::random(&mut rng, NUM_NODES, MEAN_DEGREE); 166 | debug!("random graph: \n {:?}", input_graph); 167 | let fast_graph = prepare(&input_graph); 168 | let mut path_calculator = create_calculator(&fast_graph); 169 | 170 | let dijkstra_graph = PreparationGraph::from_input_graph(&input_graph); 171 | let mut dijkstra = Dijkstra::new(input_graph.get_num_nodes()); 172 | 173 | let mut fw = FloydWarshall::new(input_graph.get_num_nodes()); 174 | fw.prepare(&input_graph); 175 | 176 | let mut num_different_paths = 0; 177 | for _i in 0..NUM_QUERIES { 178 | let source = rng.gen_range(0, input_graph.get_num_nodes()); 179 | let target = rng.gen_range(0, input_graph.get_num_nodes()); 180 | let path_fast = path_calculator 181 | .calc_path(&fast_graph, source, target) 182 | .unwrap_or(ShortestPath::none(source, target)); 183 | let path_dijkstra = dijkstra 184 | .calc_path(&dijkstra_graph, source, target) 185 | .unwrap_or(ShortestPath::none(source, target)); 186 | let weight_fast = path_fast.get_weight(); 187 | let weight_dijkstra = path_dijkstra.get_weight(); 188 | let weight_fw = fw.calc_weight(source, target); 189 | assert_eq!( 190 | weight_fw, weight_fast, 191 | "\nNo agreement for routing query from: {} to: {}\nFloyd-Warshall: {}\nCH: {}\ 192 | \n Failing graph:\n{:?}", 193 | source, target, weight_fw, weight_fast, input_graph 194 | ); 195 | assert_eq!( 196 | path_dijkstra, path_fast, 197 | "\nNo agreement for routing query from: {} to: {}\nDijkstra: {}\nCH: {}\ 198 | \n Failing graph:\n{:?}", 199 | source, target, weight_dijkstra, weight_fast, input_graph 200 | ); 201 | if path_dijkstra.get_nodes() != path_fast.get_nodes() { 202 | num_different_paths += 1; 203 | } 204 | } 205 | if num_different_paths as f32 > 0.1 * NUM_QUERIES as f32 { 206 | panic!( 207 | "too many different paths: {}, out of {}, a few different paths can be expected \ 208 | because of unambiguous shortest paths, but if there are too many something is \ 209 | wrong", 210 | num_different_paths, NUM_QUERIES 211 | ); 212 | } 213 | } 214 | 215 | #[test] 216 | fn routing_with_multiple_sources_and_targets_on_random_graph() { 217 | const REPEATS: usize = 20; 218 | for _ in 0..REPEATS { 219 | const NUM_NODES: usize = 50; 220 | const NUM_QUERIES: usize = 1_000; 221 | const MEAN_DEGREE: f32 = 2.0; 222 | const NUM_SOURCES: usize = 3; 223 | const NUM_TARGETS: usize = 3; 224 | 225 | let mut rng = create_rng(); 226 | let input_graph = InputGraph::random(&mut rng, NUM_NODES, MEAN_DEGREE); 227 | debug!("random graph: \n {:?}", input_graph); 228 | let fast_graph = prepare(&input_graph); 229 | let mut path_calculator = create_calculator(&fast_graph); 230 | 231 | let dijkstra_graph = PreparationGraph::from_input_graph(&input_graph); 232 | let mut dijkstra = Dijkstra::new(input_graph.get_num_nodes()); 233 | 234 | let mut num_different_paths = 0; 235 | let mut num_paths_not_found = 0; 236 | for _ in 0..NUM_QUERIES { 237 | // This may pick duplicate source nodes, and even duplicate source nodes with 238 | // different weights; anyway that shouldn't break anything. 239 | let sources = 240 | gen_weighted_nodes(&mut rng, input_graph.get_num_nodes(), NUM_SOURCES); 241 | let targets = 242 | gen_weighted_nodes(&mut rng, input_graph.get_num_nodes(), NUM_TARGETS); 243 | let fast_path = path_calculator.calc_path_multiple_sources_and_targets( 244 | &fast_graph, 245 | sources.clone(), 246 | targets.clone(), 247 | ); 248 | let mut dijkstra_paths: Vec<(Option, Weight, Weight)> = vec![]; 249 | for (source, source_weight) in &sources { 250 | for (target, target_weight) in &targets { 251 | dijkstra_paths.push(( 252 | dijkstra.calc_path(&dijkstra_graph, *source, *target), 253 | *source_weight, 254 | *target_weight, 255 | )) 256 | } 257 | } 258 | 259 | let found_dijkstras: Vec<(ShortestPath, Weight, Weight)> = dijkstra_paths 260 | .into_iter() 261 | .filter(|(p, source_weight, target_weight)| { 262 | p.is_some() && *source_weight < WEIGHT_MAX && *target_weight < WEIGHT_MAX 263 | }) 264 | .map(|(p, source_weight, target_weight)| { 265 | (p.unwrap(), source_weight, target_weight) 266 | }) 267 | .collect(); 268 | 269 | // We have to make sure fast_path is as short as the shortest of all dijkstra_paths 270 | let shortest_dijkstra_weight = found_dijkstras 271 | .iter() 272 | .map(|(p, source_weight, target_weight)| { 273 | p.get_weight() + source_weight + target_weight 274 | }) 275 | .min(); 276 | 277 | if shortest_dijkstra_weight.is_none() { 278 | assert!(fast_path.is_none()); 279 | num_paths_not_found += 1; 280 | continue; 281 | } 282 | 283 | assert!(fast_path.is_some()); 284 | let f = fast_path.unwrap(); 285 | let w = shortest_dijkstra_weight.unwrap(); 286 | assert_eq!(w, f.get_weight(), 287 | "\nfast_path's weight {} does not match the weight of the shortest Dijkstra path {}.\ 288 | \nsources: {:?}\ 289 | \ntargets: {:?}\ 290 | \nFailing graph:\n{:?}", 291 | f.get_weight(), w, sources, targets, input_graph); 292 | 293 | // There can be multiple options with the same weight. fast_path has to match 294 | // at least one of them 295 | let matching_dijkstras: Vec<(ShortestPath, Weight, Weight)> = found_dijkstras 296 | .into_iter() 297 | .filter(|(p, source_weight, target_weight)| { 298 | p.get_weight() + source_weight + target_weight == f.get_weight() 299 | && p.get_source() == f.get_source() 300 | && p.get_target() == f.get_target() 301 | }) 302 | .collect(); 303 | 304 | assert!( 305 | matching_dijkstras.len() > 0, 306 | "There has to be at least one Dijkstra path with source,target and weight equal to fast_path" 307 | ); 308 | 309 | // one of the matching Dijkstra's should have the same nodes as fast_path, but in 310 | // some rare cases this might not be true, because there are multiple shortest paths. 311 | if !matching_dijkstras 312 | .into_iter() 313 | .any(|(p, _, _)| p.get_nodes() == f.get_nodes()) 314 | { 315 | num_different_paths += 1; 316 | } 317 | } 318 | if num_paths_not_found as f32 > 0.5 * NUM_QUERIES as f32 { 319 | panic!( 320 | "too many paths not found: {}, out of {}", 321 | num_paths_not_found, NUM_QUERIES 322 | ) 323 | } 324 | if num_different_paths as f32 > 0.1 * NUM_QUERIES as f32 { 325 | panic!( 326 | "too many different paths: {}, out of {}, a few different paths can be expected \ 327 | because of unambiguous shortest paths, but if there are too many something is \ 328 | wrong", 329 | num_different_paths, NUM_QUERIES 330 | ); 331 | } 332 | } 333 | } 334 | 335 | fn gen_weighted_nodes(rng: &mut StdRng, max_node: usize, num: usize) -> Vec<(NodeId, Weight)> { 336 | (0..num) 337 | .map(|_| { 338 | ( 339 | rng.gen_range(0, max_node), 340 | // sometimes use max weight 341 | if rng.gen_range(0, 100) < 3 { 342 | WEIGHT_MAX 343 | } else { 344 | rng.gen_range(0, 100) 345 | }, 346 | ) 347 | }) 348 | .collect() 349 | } 350 | 351 | #[test] 352 | fn save_to_and_load_from_disk() { 353 | let mut g = InputGraph::new(); 354 | g.add_edge(0, 5, 6); 355 | g.add_edge(5, 2, 1); 356 | g.add_edge(2, 3, 4); 357 | g.freeze(); 358 | let fast_graph = prepare(&g); 359 | save_to_disk(&fast_graph, "example.fp").expect("writing to disk failed"); 360 | let loaded = load_from_disk("example.fp").unwrap(); 361 | remove_file("example.fp").expect("deleting file failed"); 362 | assert_eq!(fast_graph.get_num_nodes(), loaded.get_num_nodes()); 363 | assert_eq!(fast_graph.get_num_in_edges(), loaded.get_num_in_edges()); 364 | assert_eq!(fast_graph.get_num_out_edges(), loaded.get_num_out_edges()); 365 | } 366 | 367 | #[test] 368 | fn save_to_and_load_from_disk_32() { 369 | let mut g = InputGraph::new(); 370 | g.add_edge(0, 5, 6); 371 | g.add_edge(5, 2, 1); 372 | g.add_edge(2, 3, 4); 373 | g.freeze(); 374 | let fast_graph = prepare(&g); 375 | save_to_disk32(&fast_graph, "example32.fp").expect("writing to disk failed"); 376 | let loaded = load_from_disk32("example32.fp").unwrap(); 377 | remove_file("example32.fp").expect("deleting file failed"); 378 | assert_eq!(fast_graph.get_num_nodes(), loaded.get_num_nodes()); 379 | assert_eq!(fast_graph.get_num_in_edges(), loaded.get_num_in_edges()); 380 | assert_eq!(fast_graph.get_num_out_edges(), loaded.get_num_out_edges()); 381 | } 382 | 383 | #[test] 384 | fn deterministic_result() { 385 | const NUM_NODES: usize = 50; 386 | const MEAN_DEGREE: f32 = 2.0; 387 | 388 | // Repeat a few times to reduce test flakiness. 389 | for _ in 0..10 { 390 | let mut rng = create_rng(); 391 | let input_graph = InputGraph::random(&mut rng, NUM_NODES, MEAN_DEGREE); 392 | let serialized1 = bincode::serialize(&prepare(&input_graph)).unwrap(); 393 | let serialized2 = bincode::serialize(&prepare(&input_graph)).unwrap(); 394 | if serialized1 != serialized2 { 395 | panic!("Preparing and serializing the same graph twice produced different results"); 396 | } 397 | } 398 | } 399 | 400 | #[ignore] 401 | #[test] 402 | fn run_performance_test_dist() { 403 | println!("Running performance test for Bremen dist"); 404 | // road network extracted from OSM data from Bremen, Germany using the road distance as weight 405 | // prep: 190ms, query: 16μs, out: 68494, in: 68426 406 | // todo: try to tune parameters 407 | run_performance_test( 408 | &InputGraph::from_file("meta/test_maps/bremen_dist.gr"), 409 | &Params::new(0.1, 500, 2, 50), 410 | 845493338, 411 | 30265, 412 | ) 413 | } 414 | 415 | #[ignore] 416 | #[test] 417 | fn run_performance_test_time() { 418 | println!("Running performance test for Bremen time"); 419 | // road network extracted from OSM data from Bremen, Germany using the travel time as weight 420 | // prep: 256ms, query: 11μs, out: 64825, in: 65027 421 | // todo: try to tune parameters 422 | run_performance_test( 423 | &InputGraph::from_file("meta/test_maps/bremen_time.gr"), 424 | &Params::new(0.1, 100, 2, 100), 425 | 88104267255, 426 | 30265, 427 | ); 428 | } 429 | 430 | #[ignore] 431 | #[test] 432 | fn run_performance_test_ballard() { 433 | println!("Running performance test for ballard"); 434 | // prep: 1150ms, query: 53μs, out: 43849, in: 43700 435 | // todo: try to tune parameters 436 | run_performance_test( 437 | &InputGraph::from_file("meta/test_maps/graph_ballard.gr"), 438 | &Params::new(0.1, 100, 3, 100), 439 | 28409159409, 440 | 14992, 441 | ); 442 | } 443 | 444 | #[ignore] 445 | #[test] 446 | fn run_performance_test_23rd() { 447 | println!("Running performance test for 23rd"); 448 | // prep: 170ms, query: 23μs, out: 11478, in: 11236 449 | // todo: try to tune parameters 450 | run_performance_test( 451 | &InputGraph::from_file("meta/test_maps/graph_23rd.gr"), 452 | &Params::new(0.1, 100, 3, 100), 453 | 19438403873, 454 | 20421, 455 | ); 456 | } 457 | 458 | #[ignore] 459 | #[test] 460 | fn run_performance_test_south_seattle_car() { 461 | println!("Running performance test for South Seattle car"); 462 | // prep: 877ms, query: 26μs, out: 68777, in: 68161 463 | // todo: try to tune parameters 464 | run_performance_test( 465 | &InputGraph::from_file("meta/test_maps/south_seattle_car.gr"), 466 | &Params::new(0.1, 100, 10, 100), 467 | 77479396, 468 | 30805, 469 | ); 470 | } 471 | 472 | #[ignore] 473 | #[test] 474 | fn run_performance_test_bremen_dist_fixed_ordering() { 475 | println!("Running performance test for Bremen dist (fixed node ordering)"); 476 | // prep: 340ms, prep_order: 64ms, query: 16μs, out: 66646, in: 66725 477 | // todo: try to tune parameters 478 | run_performance_test_fixed_ordering( 479 | &InputGraph::from_file("meta/test_maps/bremen_dist.gr"), 480 | &Params::default(), 481 | &ParamsWithOrder::default(), 482 | 845493338, 483 | 30265, 484 | ); 485 | } 486 | 487 | #[ignore] 488 | #[test] 489 | fn run_performance_test_south_seattle_fixed_ordering() { 490 | println!("Running performance test for South Seattle car (fixed node ordering)"); 491 | // prep: 811ms, prep order: 138ms, query: 27μs, out: 68777, in: 68161 492 | // todo: try to tune parameters 493 | run_performance_test_fixed_ordering( 494 | &InputGraph::from_file("meta/test_maps/south_seattle_car.gr"), 495 | &Params::new(0.1, 100, 10, 100), 496 | &ParamsWithOrder::new(100), 497 | 77479396, 498 | 30805, 499 | ); 500 | } 501 | 502 | fn run_performance_test( 503 | input_graph: &InputGraph, 504 | params: &Params, 505 | expected_checksum: usize, 506 | expected_num_not_found: usize, 507 | ) { 508 | let mut fast_graph = FastGraph::new(1); 509 | prepare_algo( 510 | &mut |input_graph| fast_graph = prepare_with_params(input_graph, params), 511 | &input_graph, 512 | ); 513 | print_fast_graph_stats(&fast_graph); 514 | let mut path_calculator = PathCalculator::new(fast_graph.get_num_nodes()); 515 | do_run_performance_test( 516 | &mut |s, t| path_calculator.calc_path(&fast_graph, s, t), 517 | input_graph.get_num_nodes(), 518 | expected_checksum, 519 | expected_num_not_found, 520 | ); 521 | } 522 | 523 | fn run_performance_test_fixed_ordering( 524 | input_graph: &InputGraph, 525 | params: &Params, 526 | params_with_order: &ParamsWithOrder, 527 | expected_checksum: usize, 528 | expected_num_not_found: usize, 529 | ) { 530 | let mut time = Stopwatch::start_new(); 531 | let mut fast_graph = prepare_with_params(input_graph, params); 532 | time.stop(); 533 | println!( 534 | "preparation time (heuristic order). {} ms", 535 | time.elapsed_ms() 536 | ); 537 | let order = get_node_ordering(&fast_graph); 538 | prepare_algo( 539 | &mut |input_graph| { 540 | fast_graph = 541 | prepare_with_order_with_params(input_graph, &order, params_with_order).unwrap() 542 | }, 543 | &input_graph, 544 | ); 545 | print_fast_graph_stats(&fast_graph); 546 | let mut path_calculator = PathCalculator::new(fast_graph.get_num_nodes()); 547 | do_run_performance_test( 548 | &mut |s, t| path_calculator.calc_path(&fast_graph, s, t), 549 | input_graph.get_num_nodes(), 550 | expected_checksum, 551 | expected_num_not_found, 552 | ); 553 | } 554 | 555 | fn print_fast_graph_stats(fast_graph: &FastGraph) { 556 | println!( 557 | "number of nodes (fast graph) ...... {}", 558 | fast_graph.get_num_nodes() 559 | ); 560 | println!( 561 | "number of out-edges (fast graph) .. {}", 562 | fast_graph.get_num_out_edges() 563 | ); 564 | println!( 565 | "number of in-edges (fast graph) .. {}", 566 | fast_graph.get_num_in_edges() 567 | ); 568 | } 569 | 570 | pub fn prepare_algo(preparation: &mut F, input_graph: &InputGraph) 571 | where 572 | F: FnMut(&InputGraph), 573 | { 574 | let mut time = Stopwatch::new(); 575 | time.start(); 576 | preparation(&input_graph); 577 | time.stop(); 578 | println!( 579 | "number of nodes (input graph) ..... {}", 580 | input_graph.get_num_nodes() 581 | ); 582 | println!( 583 | "number of edges (input graph) ..... {}", 584 | input_graph.get_num_edges() 585 | ); 586 | println!( 587 | "preparation time .................. {} ms", 588 | time.elapsed_ms() 589 | ); 590 | } 591 | 592 | fn do_run_performance_test( 593 | calc_path: &mut F, 594 | num_nodes: usize, 595 | expected_checksum: usize, 596 | expected_num_not_found: usize, 597 | ) where 598 | F: FnMut(NodeId, NodeId) -> Option, 599 | { 600 | let num_queries = 100_000; 601 | let seed = 123; 602 | let mut rng = create_rng_with_seed(seed); 603 | let mut checksum = 0; 604 | let mut num_not_found = 0; 605 | let mut time = Stopwatch::new(); 606 | for _i in 0..num_queries { 607 | let source = rng.gen_range(0, num_nodes); 608 | let target = rng.gen_range(0, num_nodes); 609 | time.start(); 610 | let path = calc_path(source, target); 611 | time.stop(); 612 | match path { 613 | Some(path) => checksum += path.get_weight(), 614 | None => num_not_found += 1, 615 | } 616 | } 617 | println!( 618 | "total query time .................. {} ms", 619 | time.elapsed_ms() 620 | ); 621 | println!( 622 | "query time on average ............. {} μs", 623 | time.elapsed().as_micros() / (num_queries as u128) 624 | ); 625 | assert_eq!(expected_checksum, checksum, "invalid checksum"); 626 | assert_eq!( 627 | expected_num_not_found, num_not_found, 628 | "invalid number of paths not found" 629 | ); 630 | } 631 | 632 | fn create_rng() -> StdRng { 633 | let seed = create_seed(); 634 | create_rng_with_seed(seed) 635 | } 636 | 637 | fn create_rng_with_seed(seed: u64) -> StdRng { 638 | debug!("creating random number generator with seed: {}", seed); 639 | rand::SeedableRng::seed_from_u64(seed) 640 | } 641 | 642 | fn create_seed() -> u64 { 643 | SystemTime::now() 644 | .duration_since(SystemTime::UNIX_EPOCH) 645 | .unwrap() 646 | .as_nanos() as u64 647 | } 648 | 649 | /// Saves the given prepared graph to disk 650 | fn save_to_disk(fast_graph: &FastGraph, file_name: &str) -> Result<(), Box> { 651 | let file = File::create(file_name)?; 652 | Ok(bincode::serialize_into(file, fast_graph)?) 653 | } 654 | 655 | /// Restores a prepared graph from disk 656 | fn load_from_disk(file_name: &str) -> Result> { 657 | let file = File::open(file_name)?; 658 | Ok(bincode::deserialize_from(file)?) 659 | } 660 | 661 | /// Saves the given prepared graph to disk thereby enforcing a 32bit representation no matter whether 662 | /// the system in use uses 32 or 64bit. This is useful when creating the graph on a 64bit system and 663 | /// afterwards loading it on a 32bit system. 664 | /// Note: Using this method requires an extra +50% of RAM while storing the graph (even though 665 | /// the graph will use 50% *less* disk space when it has been saved. 666 | fn save_to_disk32(fast_graph: &FastGraph, file_name: &str) -> Result<(), Box> { 667 | let fast_graph32 = &FastGraph32::new(fast_graph); 668 | let file = File::create(file_name)?; 669 | Ok(bincode::serialize_into(file, fast_graph32)?) 670 | } 671 | 672 | /// Loads a graph from disk that was saved in 32bit representation, i.e. using save_to_disk32. The 673 | /// graph will use usize to store integers, so most commonly either 32 or 64bits per integer 674 | /// depending on the system in use. 675 | /// Note: Using this method requires an extra +50% RAM while loading the graph. 676 | fn load_from_disk32(file_name: &str) -> Result> { 677 | let file = File::open(file_name)?; 678 | let r: Result> = Ok(bincode::deserialize_from(file)?); 679 | r.map(|g| g.convert_to_usize()) 680 | } 681 | } 682 | -------------------------------------------------------------------------------- /src/node_contractor.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use crate::constants::NodeId; 21 | use crate::constants::Weight; 22 | use crate::fast_graph_builder::Params; 23 | use crate::preparation_graph::PreparationGraph; 24 | use crate::witness_search::WitnessSearch; 25 | 26 | /// removes all edges incident to `node` from the graph and adds shortcuts between all neighbors 27 | /// of `node` such that all shortest paths are preserved 28 | pub fn contract_node( 29 | graph: &mut PreparationGraph, 30 | witness_search: &mut WitnessSearch, 31 | node: NodeId, 32 | max_settled_nodes: usize, 33 | ) { 34 | handle_shortcuts(graph, witness_search, node, add_shortcut, max_settled_nodes); 35 | graph.disconnect(node); 36 | } 37 | 38 | pub fn calc_relevance( 39 | graph: &mut PreparationGraph, 40 | params: &Params, 41 | witness_search: &mut WitnessSearch, 42 | node: NodeId, 43 | level: NodeId, 44 | max_settled_nodes: usize, 45 | ) -> f32 { 46 | let mut num_shortcuts = 0; 47 | handle_shortcuts( 48 | graph, 49 | witness_search, 50 | node, 51 | |_graph, _shortcut| { 52 | num_shortcuts += 1; 53 | }, 54 | max_settled_nodes, 55 | ); 56 | let num_edges = graph.get_out_edges(node).len() + graph.get_in_edges(node).len(); 57 | let relevance = (params.hierarchy_depth_factor * level as f32) 58 | + (params.edge_quotient_factor * num_shortcuts as f32 + 1.0) / (num_edges as f32 + 1.0); 59 | relevance * 1000.0 60 | } 61 | 62 | pub fn handle_shortcuts( 63 | graph: &mut PreparationGraph, 64 | witness_search: &mut WitnessSearch, 65 | node: NodeId, 66 | mut handle_shortcut: F, 67 | max_settled_nodes: usize, 68 | ) where 69 | F: FnMut(&mut PreparationGraph, Shortcut), 70 | { 71 | for i in 0..graph.in_edges[node].len() { 72 | let in_node = graph.in_edges[node][i].adj_node; 73 | witness_search.init(in_node, node); 74 | for j in 0..graph.out_edges[node].len() { 75 | let weight = graph.in_edges[node][i].weight + graph.out_edges[node][j].weight; 76 | let out_node = graph.out_edges[node][j].adj_node; 77 | // no need to find the actual weight of a witness path as long as we can be sure 78 | // that there is some witness with weight smaller or equal to the removed direct 79 | // path 80 | let max_witness_weight = 81 | witness_search.find_max_weight(graph, out_node, weight, max_settled_nodes); 82 | if max_witness_weight <= weight { 83 | continue; 84 | } 85 | handle_shortcut(graph, Shortcut::new(in_node, out_node, node, weight)) 86 | } 87 | } 88 | } 89 | 90 | fn add_shortcut(graph: &mut PreparationGraph, shortcut: Shortcut) { 91 | graph.add_or_reduce_edge( 92 | shortcut.from, 93 | shortcut.to, 94 | shortcut.weight, 95 | shortcut.center_node, 96 | ); 97 | } 98 | 99 | #[derive(Eq, PartialEq, Debug, Copy, Clone)] 100 | pub struct Shortcut { 101 | from: NodeId, 102 | to: NodeId, 103 | center_node: NodeId, 104 | weight: Weight, 105 | } 106 | 107 | impl Shortcut { 108 | pub fn new(from: NodeId, to: NodeId, center_node: NodeId, weight: Weight) -> Self { 109 | Shortcut { 110 | from, 111 | to, 112 | center_node, 113 | weight, 114 | } 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod tests { 120 | use super::*; 121 | use crate::node_contractor; 122 | use crate::witness_search::WitnessSearch; 123 | 124 | #[test] 125 | fn calc_shortcuts_no_witness() { 126 | // 0 -> 2 -> 3 127 | // 1 ->/ \-> 4 128 | let mut g = PreparationGraph::new(5); 129 | g.add_edge(0, 2, 1); 130 | g.add_edge(1, 2, 2); 131 | g.add_edge(2, 3, 3); 132 | g.add_edge(2, 4, 1); 133 | let shortcuts = calc_shortcuts(&mut g, 2); 134 | let expected_shortcuts = vec![ 135 | Shortcut::new(0, 3, 2, 4), 136 | Shortcut::new(0, 4, 2, 2), 137 | Shortcut::new(1, 3, 2, 5), 138 | Shortcut::new(1, 4, 2, 3), 139 | ]; 140 | assert_eq!(expected_shortcuts, shortcuts); 141 | } 142 | 143 | #[test] 144 | fn calc_shortcuts_witness() { 145 | // 0 -> 1 -> 2 146 | // \-> 3 ->/ 147 | let mut g = PreparationGraph::new(4); 148 | g.add_edge(0, 1, 1); 149 | g.add_edge(1, 2, 1); 150 | g.add_edge(0, 3, 1); 151 | g.add_edge(3, 2, 1); 152 | let shortcuts = calc_shortcuts(&mut g, 1); 153 | assert_eq!(0, shortcuts.len()); 154 | } 155 | 156 | #[test] 157 | fn calc_shortcuts_witness_via_center() { 158 | // 0 -> 1 -> 2 159 | // | / 160 | // 3 - 161 | let mut g = PreparationGraph::new(4); 162 | g.add_edge(0, 1, 10); 163 | g.add_edge(1, 2, 1); 164 | g.add_edge(0, 3, 1); 165 | g.add_edge(3, 1, 1); 166 | let _shortcuts = calc_shortcuts(&mut g, 1); 167 | // performance: there is no need for a shortcut 0->1->2, because there is already the 168 | // (required) shortcut 3->1->2 169 | let _expected_shortcuts = vec![Shortcut::new(3, 2, 1, 2)]; 170 | // todo: handle this case for better performance (less shortcuts) 171 | // assert_eq!(expected_shortcuts, handler.shortcuts); 172 | } 173 | 174 | #[test] 175 | fn contract_node() { 176 | // 0 -> 1 -> 2 177 | // | / \ | 178 | // 3 --->--- 4 179 | let mut g = PreparationGraph::new(5); 180 | g.add_edge(0, 1, 1); 181 | g.add_edge(1, 2, 1); 182 | g.add_edge(0, 3, 1); 183 | g.add_edge(3, 1, 5); 184 | g.add_edge(1, 4, 4); 185 | g.add_edge(3, 4, 3); 186 | g.add_edge(4, 2, 1); 187 | let mut witness_search = WitnessSearch::new(g.get_num_nodes()); 188 | node_contractor::contract_node(&mut g, &mut witness_search, 1, usize::MAX); 189 | // there should be a shortcut 0->2, but no shortcuts 0->4, 3->2 190 | // node 1 should be properly disconnected 191 | assert_eq!(0, g.get_out_edges(1).len()); 192 | assert_eq!(0, g.get_in_edges(1).len()); 193 | assert_eq!(2, g.get_out_edges(0).len()); 194 | assert_eq!(2, g.get_in_edges(2).len()); 195 | } 196 | 197 | #[test] 198 | fn calc_priority() { 199 | // 3 200 | // | 201 | // 0 -> 1 -> 2 -> 5 202 | // | 203 | // 4 204 | let mut g = PreparationGraph::new(6); 205 | g.add_edge(0, 1, 1); 206 | g.add_edge(1, 2, 1); 207 | g.add_edge(2, 5, 1); 208 | g.add_edge(3, 1, 1); 209 | g.add_edge(1, 4, 1); 210 | let mut witness_search = WitnessSearch::new(g.get_num_nodes()); 211 | let priorities = vec![ 212 | calc_relevance( 213 | &mut g, 214 | &Params::default(), 215 | &mut witness_search, 216 | 0, 217 | 0, 218 | usize::MAX, 219 | ), 220 | calc_relevance( 221 | &mut g, 222 | &Params::default(), 223 | &mut witness_search, 224 | 1, 225 | 0, 226 | usize::MAX, 227 | ), 228 | calc_relevance( 229 | &mut g, 230 | &Params::default(), 231 | &mut witness_search, 232 | 2, 233 | 0, 234 | usize::MAX, 235 | ), 236 | calc_relevance( 237 | &mut g, 238 | &Params::default(), 239 | &mut witness_search, 240 | 3, 241 | 0, 242 | usize::MAX, 243 | ), 244 | calc_relevance( 245 | &mut g, 246 | &Params::default(), 247 | &mut witness_search, 248 | 4, 249 | 0, 250 | usize::MAX, 251 | ), 252 | calc_relevance( 253 | &mut g, 254 | &Params::default(), 255 | &mut witness_search, 256 | 5, 257 | 0, 258 | usize::MAX, 259 | ), 260 | ]; 261 | println!("{:?}", priorities); 262 | } 263 | 264 | fn calc_shortcuts(g: &mut PreparationGraph, node: NodeId) -> Vec { 265 | let mut witness_search = WitnessSearch::new(g.get_num_nodes()); 266 | let mut shortcuts = vec![]; 267 | handle_shortcuts( 268 | g, 269 | &mut witness_search, 270 | node, 271 | |_g, shortcut| shortcuts.push(shortcut), 272 | usize::MAX, 273 | ); 274 | shortcuts 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/path_calculator.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::collections::BinaryHeap; 21 | 22 | use crate::constants::Weight; 23 | use crate::constants::INVALID_EDGE; 24 | use crate::constants::INVALID_NODE; 25 | use crate::constants::WEIGHT_MAX; 26 | use crate::constants::{EdgeId, NodeId}; 27 | use crate::fast_graph::FastGraph; 28 | use crate::heap_item::HeapItem; 29 | use crate::shortest_path::ShortestPath; 30 | use crate::valid_flags::ValidFlags; 31 | 32 | pub struct PathCalculator { 33 | num_nodes: usize, 34 | data_fwd: Vec, 35 | data_bwd: Vec, 36 | valid_flags_fwd: ValidFlags, 37 | valid_flags_bwd: ValidFlags, 38 | heap_fwd: BinaryHeap, 39 | heap_bwd: BinaryHeap, 40 | } 41 | 42 | impl PathCalculator { 43 | pub fn new(num_nodes: usize) -> Self { 44 | PathCalculator { 45 | num_nodes, 46 | data_fwd: (0..num_nodes).map(|_i| Data::new()).collect(), 47 | data_bwd: (0..num_nodes).map(|_i| Data::new()).collect(), 48 | valid_flags_fwd: ValidFlags::new(num_nodes), 49 | valid_flags_bwd: ValidFlags::new(num_nodes), 50 | heap_fwd: BinaryHeap::new(), 51 | heap_bwd: BinaryHeap::new(), 52 | } 53 | } 54 | 55 | pub fn calc_path( 56 | &mut self, 57 | graph: &FastGraph, 58 | start: NodeId, 59 | end: NodeId, 60 | ) -> Option { 61 | self.calc_path_multiple_sources_and_targets(graph, vec![(start, 0)], vec![(end, 0)]) 62 | } 63 | 64 | pub fn calc_path_multiple_sources_and_targets( 65 | &mut self, 66 | graph: &FastGraph, 67 | starts: Vec<(NodeId, Weight)>, 68 | ends: Vec<(NodeId, Weight)>, 69 | ) -> Option { 70 | assert_eq!( 71 | graph.get_num_nodes(), 72 | self.num_nodes, 73 | "given graph has invalid node count" 74 | ); 75 | assert!(!starts.is_empty(), "there has to be at least one start"); 76 | assert!(!ends.is_empty(), "there has to be at least one end"); 77 | for (start_node, _) in &starts { 78 | assert!(*start_node < self.num_nodes, "invalid start node"); 79 | } 80 | for (end_node, _) in &ends { 81 | assert!(*end_node < self.num_nodes, "invalid end node"); 82 | } 83 | self.heap_fwd.clear(); 84 | self.heap_bwd.clear(); 85 | self.valid_flags_fwd.invalidate_all(); 86 | self.valid_flags_bwd.invalidate_all(); 87 | 88 | let mut best_weight = WEIGHT_MAX; 89 | let mut meeting_node = INVALID_NODE; 90 | 91 | for (start_node, start_weight) in &starts { 92 | for (end_node, end_weight) in &ends { 93 | if *start_node == *end_node 94 | && *start_weight < WEIGHT_MAX 95 | && *end_weight < WEIGHT_MAX 96 | && *start_weight + *end_weight < best_weight 97 | { 98 | best_weight = *start_weight + *end_weight; 99 | meeting_node = *end_node; 100 | } 101 | } 102 | } 103 | 104 | for (node, weight) in starts { 105 | if weight < self.get_weight_fwd(node) { 106 | // this is a bit of a hack, we store the start node as parent even though it is not 107 | // the parent. this way we can easily obtain the target node when we unpack the path 108 | // later 109 | self.update_node_fwd(node, weight, node, INVALID_EDGE); 110 | self.heap_fwd.push(HeapItem::new(weight, node)); 111 | } 112 | } 113 | for (node, weight) in ends { 114 | if weight < self.get_weight_bwd(node) { 115 | // ... same here 116 | self.update_node_bwd(node, weight, node, INVALID_EDGE); 117 | self.heap_bwd.push(HeapItem::new(weight, node)); 118 | } 119 | } 120 | 121 | loop { 122 | if self.heap_fwd.is_empty() && self.heap_bwd.is_empty() { 123 | break; 124 | } 125 | loop { 126 | if self.heap_fwd.is_empty() { 127 | break; 128 | } 129 | let curr = self.heap_fwd.pop().unwrap(); 130 | if self.is_settled_fwd(curr.node_id) { 131 | continue; 132 | } 133 | if curr.weight > best_weight { 134 | break; 135 | } 136 | // stall on demand optimization 137 | if self.is_stallable_fwd(graph, curr) { 138 | continue; 139 | } 140 | let begin = graph.begin_out_edges(curr.node_id); 141 | let end = graph.end_out_edges(curr.node_id); 142 | for edge_id in begin..end { 143 | let adj = graph.edges_fwd[edge_id].adj_node; 144 | let edge_weight = graph.edges_fwd[edge_id].weight; 145 | let weight = curr.weight + edge_weight; 146 | if weight < self.get_weight_fwd(adj) { 147 | self.update_node_fwd(adj, weight, curr.node_id, edge_id); 148 | self.heap_fwd.push(HeapItem::new(weight, adj)); 149 | } 150 | } 151 | self.data_fwd[curr.node_id].settled = true; 152 | if self.valid_flags_bwd.is_valid(curr.node_id) 153 | && curr.weight + self.get_weight_bwd(curr.node_id) < best_weight 154 | { 155 | best_weight = curr.weight + self.get_weight_bwd(curr.node_id); 156 | meeting_node = curr.node_id; 157 | } 158 | break; 159 | } 160 | 161 | loop { 162 | if self.heap_bwd.is_empty() { 163 | break; 164 | } 165 | let curr = self.heap_bwd.pop().unwrap(); 166 | if self.is_settled_bwd(curr.node_id) { 167 | continue; 168 | } 169 | if curr.weight > best_weight { 170 | break; 171 | } 172 | // stall on demand optimization 173 | if self.is_stallable_bwd(graph, curr) { 174 | continue; 175 | } 176 | let begin = graph.begin_in_edges(curr.node_id); 177 | let end = graph.end_in_edges(curr.node_id); 178 | for edge_id in begin..end { 179 | let adj = graph.edges_bwd[edge_id].adj_node; 180 | let edge_weight = graph.edges_bwd[edge_id].weight; 181 | let weight = curr.weight + edge_weight; 182 | if weight < self.get_weight_bwd(adj) { 183 | self.update_node_bwd(adj, weight, curr.node_id, edge_id); 184 | self.heap_bwd.push(HeapItem::new(weight, adj)); 185 | } 186 | } 187 | self.data_bwd[curr.node_id].settled = true; 188 | if self.valid_flags_fwd.is_valid(curr.node_id) 189 | && curr.weight + self.get_weight_fwd(curr.node_id) < best_weight 190 | { 191 | best_weight = curr.weight + self.get_weight_fwd(curr.node_id); 192 | meeting_node = curr.node_id; 193 | } 194 | break; 195 | } 196 | } 197 | 198 | if meeting_node == INVALID_NODE { 199 | None 200 | } else { 201 | assert!(best_weight < WEIGHT_MAX); 202 | let nodes = self.extract_nodes(graph, meeting_node); 203 | assert!(!nodes.is_empty()); 204 | Some(ShortestPath::new( 205 | nodes[0], 206 | nodes[nodes.len() - 1], 207 | best_weight, 208 | nodes, 209 | )) 210 | } 211 | } 212 | 213 | fn is_stallable_fwd(&self, graph: &FastGraph, curr: HeapItem) -> bool { 214 | let begin = graph.begin_in_edges(curr.node_id); 215 | let end = graph.end_in_edges(curr.node_id); 216 | for edge_id in begin..end { 217 | let adj = graph.edges_bwd[edge_id].adj_node; 218 | let adj_weight = self.get_weight_fwd(adj); 219 | if adj_weight == WEIGHT_MAX { 220 | continue; 221 | } 222 | let edge_weight = graph.edges_bwd[edge_id].weight; 223 | if adj_weight + edge_weight < curr.weight { 224 | return true; 225 | } 226 | } 227 | false 228 | } 229 | 230 | fn is_stallable_bwd(&self, graph: &FastGraph, curr: HeapItem) -> bool { 231 | let begin = graph.begin_out_edges(curr.node_id); 232 | let end = graph.end_out_edges(curr.node_id); 233 | for edge_id in begin..end { 234 | let adj = graph.edges_fwd[edge_id].adj_node; 235 | let adj_weight = self.get_weight_bwd(adj); 236 | if adj_weight == WEIGHT_MAX { 237 | continue; 238 | } 239 | let edge_weight = graph.edges_fwd[edge_id].weight; 240 | if adj_weight + edge_weight < curr.weight { 241 | return true; 242 | } 243 | } 244 | false 245 | } 246 | 247 | fn extract_nodes(&self, graph: &FastGraph, meeting_node: NodeId) -> Vec { 248 | assert_ne!(meeting_node, INVALID_NODE); 249 | assert!(self.valid_flags_fwd.is_valid(meeting_node)); 250 | assert!(self.valid_flags_bwd.is_valid(meeting_node)); 251 | let mut result = Vec::new(); 252 | let mut node = meeting_node; 253 | while self.data_fwd[node].inc_edge != INVALID_EDGE { 254 | PathCalculator::unpack_fwd(graph, &mut result, self.data_fwd[node].inc_edge, true); 255 | node = self.data_fwd[node].parent; 256 | } 257 | result.reverse(); 258 | node = meeting_node; 259 | while self.data_bwd[node].inc_edge != INVALID_EDGE { 260 | PathCalculator::unpack_bwd(graph, &mut result, self.data_bwd[node].inc_edge, false); 261 | node = self.data_bwd[node].parent; 262 | } 263 | // we stored the target node as 'parent' of the root of the shortest tree, so we can use it 264 | // here 265 | result.push(node); 266 | result 267 | } 268 | 269 | fn unpack_fwd(graph: &FastGraph, nodes: &mut Vec, edge_id: EdgeId, reverse: bool) { 270 | if !graph.edges_fwd[edge_id].is_shortcut() { 271 | nodes.push(graph.edges_fwd[edge_id].base_node); 272 | return; 273 | } 274 | if reverse { 275 | PathCalculator::unpack_fwd( 276 | graph, 277 | nodes, 278 | graph.edges_fwd[edge_id].replaced_out_edge, 279 | reverse, 280 | ); 281 | PathCalculator::unpack_bwd( 282 | graph, 283 | nodes, 284 | graph.edges_fwd[edge_id].replaced_in_edge, 285 | reverse, 286 | ); 287 | } else { 288 | PathCalculator::unpack_bwd( 289 | graph, 290 | nodes, 291 | graph.edges_fwd[edge_id].replaced_in_edge, 292 | reverse, 293 | ); 294 | PathCalculator::unpack_fwd( 295 | graph, 296 | nodes, 297 | graph.edges_fwd[edge_id].replaced_out_edge, 298 | reverse, 299 | ); 300 | } 301 | } 302 | 303 | fn unpack_bwd(graph: &FastGraph, nodes: &mut Vec, edge_id: EdgeId, reverse: bool) { 304 | if !graph.edges_bwd[edge_id].is_shortcut() { 305 | nodes.push(graph.edges_bwd[edge_id].adj_node); 306 | return; 307 | } 308 | if reverse { 309 | PathCalculator::unpack_fwd( 310 | graph, 311 | nodes, 312 | graph.edges_bwd[edge_id].replaced_out_edge, 313 | reverse, 314 | ); 315 | PathCalculator::unpack_bwd( 316 | graph, 317 | nodes, 318 | graph.edges_bwd[edge_id].replaced_in_edge, 319 | reverse, 320 | ); 321 | } else { 322 | PathCalculator::unpack_bwd( 323 | graph, 324 | nodes, 325 | graph.edges_bwd[edge_id].replaced_in_edge, 326 | reverse, 327 | ); 328 | PathCalculator::unpack_fwd( 329 | graph, 330 | nodes, 331 | graph.edges_bwd[edge_id].replaced_out_edge, 332 | reverse, 333 | ); 334 | } 335 | } 336 | 337 | fn update_node_fwd(&mut self, node: NodeId, weight: Weight, parent: NodeId, inc_edge: EdgeId) { 338 | self.valid_flags_fwd.set_valid(node); 339 | self.data_fwd[node].settled = false; 340 | self.data_fwd[node].weight = weight; 341 | self.data_fwd[node].parent = parent; 342 | self.data_fwd[node].inc_edge = inc_edge; 343 | } 344 | 345 | fn update_node_bwd(&mut self, node: NodeId, weight: Weight, parent: NodeId, inc_edge: EdgeId) { 346 | self.valid_flags_bwd.set_valid(node); 347 | self.data_bwd[node].settled = false; 348 | self.data_bwd[node].weight = weight; 349 | self.data_bwd[node].parent = parent; 350 | self.data_bwd[node].inc_edge = inc_edge; 351 | } 352 | 353 | fn is_settled_fwd(&self, node: NodeId) -> bool { 354 | self.valid_flags_fwd.is_valid(node) && self.data_fwd[node].settled 355 | } 356 | 357 | fn is_settled_bwd(&self, node: NodeId) -> bool { 358 | self.valid_flags_bwd.is_valid(node) && self.data_bwd[node].settled 359 | } 360 | 361 | fn get_weight_fwd(&self, node: NodeId) -> Weight { 362 | if self.valid_flags_fwd.is_valid(node) { 363 | self.data_fwd[node].weight 364 | } else { 365 | WEIGHT_MAX 366 | } 367 | } 368 | 369 | fn get_weight_bwd(&self, node: NodeId) -> Weight { 370 | if self.valid_flags_bwd.is_valid(node) { 371 | self.data_bwd[node].weight 372 | } else { 373 | WEIGHT_MAX 374 | } 375 | } 376 | } 377 | 378 | struct Data { 379 | settled: bool, 380 | weight: Weight, 381 | parent: NodeId, 382 | inc_edge: usize, 383 | } 384 | 385 | impl Data { 386 | fn new() -> Self { 387 | Data { 388 | settled: false, 389 | weight: WEIGHT_MAX, 390 | parent: INVALID_NODE, 391 | inc_edge: INVALID_EDGE, 392 | } 393 | } 394 | } 395 | 396 | #[cfg(test)] 397 | mod tests { 398 | use crate::fast_graph::FastGraphEdge; 399 | 400 | use super::*; 401 | 402 | #[test] 403 | fn unpack_fwd_single() { 404 | // 0 -> 1 405 | let mut g = FastGraph::new(2); 406 | g.edges_fwd 407 | .push(FastGraphEdge::new(0, 1, 3, INVALID_EDGE, INVALID_EDGE)); 408 | let mut nodes = vec![]; 409 | PathCalculator::unpack_fwd(&g, &mut nodes, 0, false); 410 | assert_eq!(nodes, vec![0]); 411 | } 412 | 413 | #[test] 414 | fn unpack_fwd_simple() { 415 | // 0 -> 1 -> 2 416 | let mut g = FastGraph::new(3); 417 | g.edges_fwd 418 | .push(FastGraphEdge::new(0, 1, 2, INVALID_EDGE, INVALID_EDGE)); 419 | g.edges_fwd.push(FastGraphEdge::new(0, 2, 5, 0, 0)); 420 | g.edges_bwd 421 | .push(FastGraphEdge::new(2, 1, 3, INVALID_EDGE, INVALID_EDGE)); 422 | g.first_edge_ids_fwd = vec![0, 2, 0, 0]; 423 | let mut nodes = vec![]; 424 | PathCalculator::unpack_fwd(&g, &mut nodes, 1, false); 425 | assert_eq!(nodes, vec![1, 0]); 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /src/preparation_graph.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use crate::constants::Weight; 21 | use crate::constants::{NodeId, INVALID_NODE}; 22 | use crate::input_graph::InputGraph; 23 | 24 | pub struct PreparationGraph { 25 | pub out_edges: Vec>, 26 | pub in_edges: Vec>, 27 | num_nodes: usize, 28 | } 29 | 30 | impl PreparationGraph { 31 | pub fn new(num_nodes: usize) -> Self { 32 | let out_edges: Vec> = (0..num_nodes).map(|_| Vec::with_capacity(3)).collect(); 33 | let in_edges = out_edges.clone(); 34 | PreparationGraph { 35 | out_edges, 36 | in_edges, 37 | num_nodes, 38 | } 39 | } 40 | 41 | pub fn from_input_graph(input_graph: &InputGraph) -> Self { 42 | let mut graph = PreparationGraph::new(input_graph.get_num_nodes()); 43 | for e in input_graph.get_edges() { 44 | graph.add_edge(e.from, e.to, e.weight); 45 | } 46 | graph 47 | } 48 | 49 | pub fn add_edge(&mut self, from: NodeId, to: NodeId, weight: Weight) { 50 | self.add_edge_or_shortcut(from, to, weight, INVALID_NODE); 51 | } 52 | 53 | pub fn add_edge_or_shortcut( 54 | &mut self, 55 | from: NodeId, 56 | to: NodeId, 57 | weight: Weight, 58 | center_node: NodeId, 59 | ) { 60 | self.assert_valid_node_id(to); 61 | self.out_edges[from].push(Arc::new(to, weight, center_node)); 62 | self.in_edges[to].push(Arc::new(from, weight, center_node)); 63 | } 64 | 65 | pub fn add_or_reduce_edge( 66 | &mut self, 67 | from: NodeId, 68 | to: NodeId, 69 | weight: Weight, 70 | center_node: NodeId, 71 | ) { 72 | if self.reduce_edge(from, to, weight, center_node) { 73 | return; 74 | } 75 | self.add_edge_or_shortcut(from, to, weight, center_node); 76 | } 77 | 78 | fn reduce_edge( 79 | &mut self, 80 | from: NodeId, 81 | to: NodeId, 82 | weight: Weight, 83 | center_node: NodeId, 84 | ) -> bool { 85 | for out_edge in &mut self.out_edges[from] { 86 | if out_edge.adj_node == to { 87 | if out_edge.weight <= weight { 88 | return true; 89 | } 90 | for in_edge in &mut self.in_edges[to] { 91 | if in_edge.adj_node == from { 92 | out_edge.weight = weight; 93 | in_edge.weight = weight; 94 | out_edge.center_node = center_node; 95 | in_edge.center_node = center_node; 96 | } 97 | } 98 | return true; 99 | } 100 | } 101 | false 102 | } 103 | 104 | pub fn get_num_nodes(&self) -> usize { 105 | self.num_nodes 106 | } 107 | 108 | pub fn disconnect(&mut self, node: NodeId) { 109 | for i in 0..self.out_edges[node].len() { 110 | let adj = self.out_edges[node][i].adj_node; 111 | self.remove_in_edge(adj, node); 112 | } 113 | for i in 0..self.in_edges[node].len() { 114 | let adj = self.in_edges[node][i].adj_node; 115 | self.remove_out_edge(adj, node); 116 | } 117 | self.in_edges[node].clear(); 118 | self.out_edges[node].clear(); 119 | } 120 | 121 | pub fn remove_out_edge(&mut self, node: NodeId, adj: NodeId) { 122 | PreparationGraph::remove_edge_with_adj_node(&mut self.out_edges[node], adj); 123 | } 124 | 125 | pub fn remove_in_edge(&mut self, node: NodeId, adj: NodeId) { 126 | PreparationGraph::remove_edge_with_adj_node(&mut self.in_edges[node], adj); 127 | } 128 | 129 | pub fn remove_edge_with_adj_node(edges: &mut Vec, adj: NodeId) { 130 | let len_before = edges.len(); 131 | edges.retain(|e| e.adj_node != adj); 132 | assert_eq!( 133 | edges.len(), 134 | len_before - 1, 135 | "should have removed exactly one edge" 136 | ); 137 | } 138 | 139 | pub fn get_out_edges(&self, node: NodeId) -> &Vec { 140 | &self.out_edges[node] 141 | } 142 | 143 | pub fn get_in_edges(&self, node: NodeId) -> &Vec { 144 | &self.in_edges[node] 145 | } 146 | 147 | fn assert_valid_node_id(&self, node: NodeId) { 148 | if node >= self.num_nodes { 149 | panic!( 150 | "invalid node id {}, must be in [0, {})", 151 | node, self.num_nodes 152 | ); 153 | } 154 | } 155 | } 156 | 157 | #[derive(Clone)] 158 | pub struct Arc { 159 | pub adj_node: NodeId, 160 | pub weight: Weight, 161 | pub center_node: NodeId, 162 | } 163 | 164 | impl Arc { 165 | pub fn new(adj_node: NodeId, weight: Weight, center_node: NodeId) -> Self { 166 | Arc { 167 | adj_node, 168 | weight, 169 | center_node, 170 | } 171 | } 172 | } 173 | 174 | #[cfg(test)] 175 | mod tests { 176 | use super::*; 177 | 178 | #[test] 179 | fn add_and_remove_edges() { 180 | let mut g = PreparationGraph::new(4); 181 | g.add_edge(0, 1, 1); 182 | g.add_edge(0, 2, 1); 183 | g.add_edge(0, 3, 1); 184 | g.add_edge(2, 3, 1); 185 | assert_eq!(adj_nodes(g.get_out_edges(0)), vec![1, 2, 3]); 186 | assert_eq!(adj_nodes(g.get_in_edges(3)), vec![0, 2]); 187 | 188 | g.remove_out_edge(0, 2); 189 | assert_eq!(adj_nodes(g.get_out_edges(0)), vec![1, 3]); 190 | assert_eq!(adj_nodes(g.get_in_edges(3)), vec![0, 2]); 191 | 192 | g.remove_in_edge(3, 0); 193 | assert_eq!(adj_nodes(g.get_out_edges(0)), vec![1, 3]); 194 | assert_eq!(adj_nodes(g.get_in_edges(3)), vec![2]); 195 | } 196 | 197 | #[test] 198 | fn add_or_remove_edge() { 199 | // 0 -> 1 200 | let mut g = PreparationGraph::new(3); 201 | g.add_edge(0, 1, 10); 202 | g.add_or_reduce_edge(0, 1, 6, INVALID_NODE); 203 | assert_eq!(1, g.get_out_edges(0).len()); 204 | assert_eq!(6, g.get_out_edges(0)[0].weight); 205 | assert_eq!(1, g.get_in_edges(1).len()); 206 | assert_eq!(6, g.get_in_edges(1)[0].weight); 207 | } 208 | 209 | #[test] 210 | fn disconnect() { 211 | // 0 <-> 1 <-> 2 212 | let mut g = PreparationGraph::new(4); 213 | g.add_edge(1, 0, 1); 214 | g.add_edge(1, 2, 1); 215 | g.add_edge(0, 1, 1); 216 | g.add_edge(2, 1, 1); 217 | assert_eq!(vec![0, 2], adj_nodes(g.get_out_edges(1))); 218 | assert_eq!(vec![0, 2], adj_nodes(g.get_in_edges(1))); 219 | g.disconnect(1); 220 | assert_eq!(0, adj_nodes(g.get_out_edges(0)).len()); 221 | assert_eq!(0, adj_nodes(g.get_out_edges(1)).len()); 222 | assert_eq!(0, adj_nodes(g.get_out_edges(2)).len()); 223 | assert_eq!(0, adj_nodes(g.get_in_edges(0)).len()); 224 | assert_eq!(0, adj_nodes(g.get_in_edges(1)).len()); 225 | assert_eq!(0, adj_nodes(g.get_in_edges(2)).len()); 226 | } 227 | 228 | fn adj_nodes(edges: &Vec) -> Vec { 229 | edges.iter().map(|e| e.adj_node).collect::>() 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/shortest_path.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use crate::constants::NodeId; 21 | use crate::constants::Weight; 22 | use crate::constants::WEIGHT_MAX; 23 | use crate::constants::WEIGHT_ZERO; 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct ShortestPath { 27 | source: NodeId, 28 | target: NodeId, 29 | weight: Weight, 30 | nodes: Vec, 31 | } 32 | 33 | impl PartialEq for ShortestPath { 34 | fn eq(&self, other: &ShortestPath) -> bool { 35 | self.source == other.source && self.target == other.target && self.weight == other.weight 36 | // do not insist on equal nodes arrays, because there can be unambiguous shortest paths 37 | } 38 | } 39 | 40 | impl ShortestPath { 41 | pub fn new(source: NodeId, target: NodeId, weight: Weight, nodes: Vec) -> Self { 42 | ShortestPath { 43 | source, 44 | target, 45 | weight, 46 | nodes, 47 | } 48 | } 49 | 50 | pub fn singular(node: NodeId) -> Self { 51 | ShortestPath { 52 | source: node, 53 | target: node, 54 | weight: WEIGHT_ZERO, 55 | nodes: vec![node], 56 | } 57 | } 58 | 59 | pub fn none(source: NodeId, target: NodeId) -> Self { 60 | ShortestPath { 61 | source, 62 | target, 63 | weight: WEIGHT_MAX, 64 | nodes: vec![], 65 | } 66 | } 67 | 68 | pub fn get_source(&self) -> NodeId { 69 | self.source 70 | } 71 | 72 | pub fn get_target(&self) -> NodeId { 73 | self.target 74 | } 75 | 76 | pub fn get_weight(&self) -> Weight { 77 | self.weight 78 | } 79 | 80 | pub fn get_nodes(&self) -> &Vec { 81 | &self.nodes 82 | } 83 | 84 | pub fn is_found(&self) -> bool { 85 | self.weight != WEIGHT_MAX 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/valid_flags.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::u32::MAX; 21 | 22 | use crate::constants::NodeId; 23 | 24 | /// Maintains a collection of N boolean flags that can efficiently be reset by incrementing a 25 | /// single integer 26 | pub struct ValidFlags { 27 | valid_flags: Vec, 28 | valid_flag: u32, 29 | } 30 | 31 | impl ValidFlags { 32 | pub fn new(num_nodes: usize) -> Self { 33 | ValidFlags { 34 | valid_flags: vec![0; num_nodes], 35 | valid_flag: 1, 36 | } 37 | } 38 | 39 | pub fn is_valid(&self, node: NodeId) -> bool { 40 | self.valid_flags[node] == self.valid_flag 41 | } 42 | 43 | pub fn set_valid(&mut self, node: NodeId) { 44 | self.valid_flags[node] = self.valid_flag; 45 | } 46 | 47 | pub fn invalidate_all(&mut self) { 48 | if self.valid_flag == MAX { 49 | self.valid_flags = vec![0; self.valid_flags.len()]; 50 | self.valid_flag = 1; 51 | } else { 52 | self.valid_flag += 1; 53 | } 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use crate::valid_flags::ValidFlags; 60 | 61 | #[test] 62 | fn set_valid_and_invalidate() { 63 | let mut flags = ValidFlags::new(5); 64 | assert!(!flags.is_valid(3)); 65 | flags.set_valid(3); 66 | assert!(flags.is_valid(3)); 67 | flags.invalidate_all(); 68 | assert!(!flags.is_valid(3)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/witness_search.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | use std::collections::BinaryHeap; 21 | 22 | use crate::constants::Weight; 23 | use crate::constants::{NodeId, INVALID_NODE, WEIGHT_MAX, WEIGHT_ZERO}; 24 | use crate::heap_item::HeapItem; 25 | use crate::preparation_graph::PreparationGraph; 26 | use crate::valid_flags::ValidFlags; 27 | 28 | pub struct WitnessSearch { 29 | num_nodes: usize, 30 | data: Vec, 31 | valid_flags: ValidFlags, 32 | heap: BinaryHeap, 33 | start_node: NodeId, 34 | avoid_node: NodeId, 35 | settled_nodes: usize, 36 | } 37 | 38 | impl WitnessSearch { 39 | pub fn new(num_nodes: usize) -> Self { 40 | let heap = BinaryHeap::new(); 41 | WitnessSearch { 42 | num_nodes, 43 | data: (0..num_nodes).map(|_i| Data::new()).collect(), 44 | valid_flags: ValidFlags::new(num_nodes), 45 | heap, 46 | start_node: INVALID_NODE, 47 | avoid_node: INVALID_NODE, 48 | settled_nodes: 0, 49 | } 50 | } 51 | 52 | /// Initializes the witness search for a given start and avoid node. Calling this method 53 | /// resets/clears previously calculated data. 54 | pub fn init(&mut self, start: NodeId, avoid_node: NodeId) { 55 | assert_ne!(start, INVALID_NODE, "the start node must be valid"); 56 | assert_ne!( 57 | start, avoid_node, 58 | "path calculation must not start with avoided node" 59 | ); 60 | self.start_node = start; 61 | self.avoid_node = avoid_node; 62 | 63 | self.heap.clear(); 64 | self.valid_flags.invalidate_all(); 65 | self.update_node(start, 0); 66 | self.heap.push(HeapItem::new(0, start)); 67 | self.settled_nodes = 0; 68 | } 69 | 70 | /// Returns an upper bound for the shortest path weight between the start node and a given target 71 | /// node. 72 | /// Calling this method runs Dijkstra's algorithm for the given start_node. The avoid_node will 73 | /// never be visited. There are multiple criteria that make the search stop: 74 | /// 1) the target is settled. the returned weight will be the actual shortest path weight. 75 | /// 2) the next node to be settled exceeds the given weight_limit. the returned weight will 76 | /// be the best known upper bound for the real shortest path weight at this point. it will 77 | /// always be larger than weight_limit in this case. 78 | /// 3) the tentative weight of the target is found to be equal or smaller than weight_limit. 79 | /// this way the search can be stopped without finding the actual shortest path as soon as 80 | /// any path with weight <= weight_limit has been found. 81 | /// 4) settled_nodes_limit nodes have been settled. the returned weight will be the best known 82 | /// upper bound for the real shortest path weight at this point. 83 | /// The shortest path tree established during the search will be re-used until the init 84 | /// function is called again. 85 | pub fn find_max_weight( 86 | &mut self, 87 | graph: &PreparationGraph, 88 | target: NodeId, 89 | weight_limit: Weight, 90 | settled_nodes_limit: usize, 91 | ) -> Weight { 92 | assert_eq!( 93 | graph.get_num_nodes(), 94 | self.num_nodes, 95 | "given graph has invalid node count" 96 | ); 97 | assert_ne!( 98 | self.start_node, INVALID_NODE, 99 | "the start node must be valid, call init() before find_max_weight()" 100 | ); 101 | assert!( 102 | self.start_node != self.avoid_node && target != self.avoid_node, 103 | "path calculation must not start or end with avoided node" 104 | ); 105 | if target == self.start_node { 106 | return WEIGHT_ZERO; 107 | } 108 | if self.valid_flags.is_valid(target) 109 | && (self.data[target].settled || self.data[target].weight <= weight_limit) 110 | { 111 | return self.data[target].weight; 112 | } 113 | while !self.heap.is_empty() { 114 | if self.settled_nodes >= settled_nodes_limit { 115 | break; 116 | } 117 | let curr = *self.heap.peek().unwrap(); 118 | if curr.weight > weight_limit { 119 | break; 120 | } 121 | self.heap.pop(); 122 | if self.is_settled(curr.node_id) { 123 | // todo: since we are not using a special decrease key operation yet we need to 124 | // filter out duplicate heap items here 125 | continue; 126 | } 127 | let mut found_target = false; 128 | for i in 0..graph.out_edges[curr.node_id].len() { 129 | let adj = graph.out_edges[curr.node_id][i].adj_node; 130 | if adj == self.avoid_node { 131 | continue; 132 | } 133 | let edge_weight = graph.out_edges[curr.node_id][i].weight; 134 | let weight = curr.weight + edge_weight; 135 | if weight < self.get_current_weight(adj) { 136 | self.update_node(adj, weight); 137 | self.heap.push(HeapItem::new(weight, adj)); 138 | if adj == target && weight <= weight_limit { 139 | found_target = true; 140 | } 141 | } 142 | } 143 | self.data[curr.node_id].settled = true; 144 | self.settled_nodes += 1; 145 | if found_target || curr.node_id == target { 146 | break; 147 | } 148 | } 149 | self.get_current_weight(target) 150 | } 151 | 152 | fn update_node(&mut self, node: NodeId, weight: Weight) { 153 | self.valid_flags.set_valid(node); 154 | self.data[node].settled = false; 155 | self.data[node].weight = weight; 156 | } 157 | 158 | fn is_settled(&self, node: NodeId) -> bool { 159 | self.valid_flags.is_valid(node) && self.data[node].settled 160 | } 161 | 162 | fn get_current_weight(&self, node: NodeId) -> Weight { 163 | if self.valid_flags.is_valid(node) { 164 | self.data[node].weight 165 | } else { 166 | WEIGHT_MAX 167 | } 168 | } 169 | } 170 | 171 | struct Data { 172 | settled: bool, 173 | weight: Weight, 174 | } 175 | 176 | impl Data { 177 | fn new() -> Self { 178 | // todo: initializing with these values is not strictly necessary 179 | Data { 180 | settled: false, 181 | weight: WEIGHT_MAX, 182 | } 183 | } 184 | } 185 | 186 | #[cfg(test)] 187 | mod tests { 188 | use super::*; 189 | 190 | #[test] 191 | fn avoid_node() { 192 | // 0 -> 1 -> 2 193 | // | | 194 | // 3 -> 4 -> 5 195 | let mut g = PreparationGraph::new(6); 196 | g.add_edge(0, 1, 1); 197 | g.add_edge(1, 2, 1); 198 | g.add_edge(0, 3, 10); 199 | g.add_edge(3, 4, 1); 200 | g.add_edge(4, 5, 1); 201 | g.add_edge(5, 2, 1); 202 | let mut ws = WitnessSearch::new(g.get_num_nodes()); 203 | ws.init(0, INVALID_NODE); 204 | assert_eq!(2, ws.find_max_weight(&g, 2, 2, 100)); 205 | assert_eq!(2, ws.find_max_weight(&g, 2, 2, 100)); 206 | assert_eq!(2, ws.settled_nodes); 207 | ws.init(0, 1); 208 | assert_eq!(13, ws.find_max_weight(&g, 2, 13, 100)); 209 | assert_eq!(4, ws.settled_nodes); 210 | // calling init again also resets settled nodes 211 | ws.init(4, 3); 212 | assert_eq!(2, ws.find_max_weight(&g, 2, 5, 100)); 213 | assert_eq!(2, ws.settled_nodes); 214 | } 215 | 216 | #[test] 217 | fn limit_weight() { 218 | // 0 -> 1 -> 2 -> 3 -> 4 219 | let mut g = PreparationGraph::new(5); 220 | for i in 0..4 { 221 | g.add_edge(i, i + 1, 1); 222 | } 223 | let mut ws = WitnessSearch::new(g.get_num_nodes()); 224 | ws.init(0, INVALID_NODE); 225 | assert_eq!(3, ws.find_max_weight(&g, 3, 3, 100)); 226 | assert_eq!(3, ws.settled_nodes); 227 | // reset and reduce weight limit to 2. node 3 will not be settled, but we still get 3 as 228 | // upper bound for the weight of node 3 229 | ws.init(0, INVALID_NODE); 230 | assert_eq!(3, ws.find_max_weight(&g, 3, 2, 100)); 231 | assert_eq!(3, ws.settled_nodes); 232 | // .. but not for node 4 233 | assert_eq!(WEIGHT_MAX, ws.find_max_weight(&g, 4, 2, 100)); 234 | // if the weight has already be calculated no further search is required 235 | assert_eq!(2, ws.find_max_weight(&g, 2, 2, 100)); 236 | assert_eq!(2, ws.find_max_weight(&g, 2, 2, 100)); 237 | assert_eq!(2, ws.find_max_weight(&g, 2, 2, 100)); 238 | // ... even when the weight limit is smaller than previously 239 | assert_eq!(2, ws.find_max_weight(&g, 2, 1, 100)); 240 | assert_eq!(3, ws.settled_nodes); 241 | // we can extend the current search space 242 | assert_eq!(4, ws.find_max_weight(&g, 4, 3, 100)); 243 | assert_eq!(4, ws.find_max_weight(&g, 4, 4, 100)); 244 | assert_eq!(4, ws.find_max_weight(&g, 4, 5, 100)); 245 | assert_eq!(4, ws.settled_nodes); 246 | } 247 | 248 | #[test] 249 | fn stop_early() { 250 | // 0 -> 1 -> 2 -> 3 251 | // \----------->/ 252 | let mut g = PreparationGraph::new(4); 253 | g.add_edge(0, 1, 1); 254 | g.add_edge(1, 2, 1); 255 | g.add_edge(2, 3, 1); 256 | g.add_edge(0, 3, 4); 257 | let mut ws = WitnessSearch::new(g.get_num_nodes()); 258 | ws.init(0, INVALID_NODE); 259 | // the shortest path weight is 3, but since we set the limit to 10 the alternative path 260 | // 0->3 with weight 4 that we find earlier is 'good enough' and find_max_weight returns 261 | // early 262 | assert_eq!(4, ws.find_max_weight(&g, 3, 10, 100)); 263 | assert_eq!(1, ws.settled_nodes); 264 | // calling the same again still does not trigger an expansion of the search tree 265 | assert_eq!(4, ws.find_max_weight(&g, 3, 10, 100)); 266 | assert_eq!(1, ws.settled_nodes); 267 | // this is still true when we reduce the weight limit to the weight of the sub-optimal path 268 | assert_eq!(4, ws.find_max_weight(&g, 3, 4, 100)); 269 | assert_eq!(4, ws.find_max_weight(&g, 3, 4, 100)); 270 | assert_eq!(1, ws.settled_nodes); 271 | // when we further reduce the weight limit the search needs to be more accurate 272 | assert_eq!(3, ws.find_max_weight(&g, 3, 3, 100)); 273 | // ... even though settling node 3 is still not necessary 274 | assert_eq!(3, ws.settled_nodes); 275 | // ... and repeating the search yields the same result 276 | assert_eq!(3, ws.find_max_weight(&g, 3, 3, 100)); 277 | assert_eq!(3, ws.settled_nodes); 278 | 279 | // we can also limit the number of settled nodes 280 | ws.init(0, INVALID_NODE); 281 | // ... here the weight limit is so large that the alternative path is returned anyway 282 | assert_eq!(4, ws.find_max_weight(&g, 3, 100, 2)); 283 | assert_eq!(1, ws.settled_nodes); 284 | // ... here the weight limit does not allow the suboptimal weight to be returned, but once 285 | // the settled_nodes_limit is exceeded it is returned anyway 286 | assert_eq!(4, ws.find_max_weight(&g, 3, 3, 2)); 287 | assert_eq!(2, ws.settled_nodes); 288 | } 289 | 290 | #[test] 291 | fn large_edge_weight() { 292 | // 100 <- 99 <- ... <- 3 -> 2 -> 1 293 | // \-> 0 ->/ 294 | let mut g = PreparationGraph::new(101); 295 | g.add_edge(3, 2, 100); 296 | g.add_edge(2, 1, 100); 297 | g.add_edge(3, 0, 50); 298 | g.add_edge(0, 1, 50); 299 | for i in 3..100 { 300 | g.add_edge(i, i + 1, 1); 301 | } 302 | let mut ws = WitnessSearch::new(g.get_num_nodes()); 303 | // going from 3 to 1 while skipping 2 requires travelling along the large weight edge 3->0 304 | // this means many low weight edges will be visited first which slows down the search 305 | ws.init(3, 2); 306 | assert_eq!(100, ws.find_max_weight(&g, 1, 200, usize::MAX)); 307 | assert_eq!(51, ws.settled_nodes); 308 | // we can improve this by limiting the number of settled nodes. however, in this case 309 | // we won't find the witness and risk inserting an unnecessary shortcut. this might or might 310 | // not be ok depending on the situation. 311 | ws.init(3, 2); 312 | assert_eq!(WEIGHT_MAX, ws.find_max_weight(&g, 1, 200, 5)); 313 | assert_eq!(5, ws.settled_nodes); 314 | } 315 | 316 | #[test] 317 | fn large_edge_weight_target_touched() { 318 | // 100 <- 99 <- ... <- 3 -> 2 -> 1 319 | // \-> 0 ->/ 320 | let mut g = PreparationGraph::new(101); 321 | g.add_edge(3, 2, 100); 322 | g.add_edge(2, 1, 100); 323 | g.add_edge(3, 0, 1); 324 | g.add_edge(0, 1, 99); 325 | for i in 3..100 { 326 | g.add_edge(i, i + 1, 1); 327 | } 328 | let mut ws = WitnessSearch::new(g.get_num_nodes()); 329 | // going from 3 to 1 while skipping 2 requires travelling the large weight edge 0->1 so 330 | // before 1 is settled many other nodes will be settled. however, find_max_weight already 331 | // returns even before node 1 is settled because the tentative weight is small enough 332 | ws.init(3, 2); 333 | assert_eq!(100, ws.find_max_weight(&g, 1, 200, usize::MAX)); 334 | // .. just two settled nodes. this is quite important for preparation speed when there are 335 | // large weight edges 336 | assert_eq!(2, ws.settled_nodes); 337 | } 338 | } 339 | --------------------------------------------------------------------------------