scgraph 3.1.0__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {scgraph-3.1.0 → scgraph-3.2.0}/CMakeLists.txt +1 -0
  2. {scgraph-3.1.0 → scgraph-3.2.0}/PKG-INFO +6 -3
  3. {scgraph-3.1.0 → scgraph-3.2.0}/README.md +5 -2
  4. {scgraph-3.1.0 → scgraph-3.2.0}/build/cp314-cp314-linux_x86_64/CMakeFiles/CheckCXX/CMakeLists.txt +1 -1
  5. {scgraph-3.1.0 → scgraph-3.2.0}/pyproject.toml +1 -1
  6. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/__init__.py +5 -2
  7. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/cpp/bindings/graph_bindings.cpp +209 -6
  8. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/cpp/src/contraction_hierarchies.hpp +1 -1
  9. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/cpp/src/graph.cpp +90 -1
  10. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/cpp/src/graph.hpp +12 -2
  11. scgraph-3.2.0/scgraph/cpp/src/transit_node_routing.cpp +276 -0
  12. scgraph-3.2.0/scgraph/cpp/src/transit_node_routing.hpp +45 -0
  13. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/geograph.py +8 -26
  14. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/graph.py +158 -0
  15. scgraph-3.2.0/scgraph/transit_node_routing.py +394 -0
  16. {scgraph-3.1.0 → scgraph-3.2.0}/LICENSE +0 -0
  17. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/contraction_hierarchies.py +0 -0
  18. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/cpp/src/bmssp.hpp +0 -0
  19. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/cpp/src/contraction_hierarchies.cpp +0 -0
  20. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/cpp/src/graph_utils.cpp +0 -0
  21. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/cpp/src/graph_utils.hpp +0 -0
  22. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/graph_utils.py +0 -0
  23. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/grid.py +0 -0
  24. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/helpers/__init__.py +0 -0
  25. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/helpers/geojson.py +0 -0
  26. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/helpers/visvalingam.py +0 -0
  27. {scgraph-3.1.0 → scgraph-3.2.0}/scgraph/utils.py +0 -0
@@ -70,6 +70,7 @@ else()
70
70
  scgraph/cpp/src/graph.cpp
71
71
  scgraph/cpp/src/graph_utils.cpp
72
72
  scgraph/cpp/src/contraction_hierarchies.cpp
73
+ scgraph/cpp/src/transit_node_routing.cpp
73
74
  )
74
75
 
75
76
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scgraph
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Determine an approximate distance and route between two points on earth.
5
5
  Author-Email: Connor Makowski <conmak@mit.edu>
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ Description-Content-Type: text/markdown
19
19
  # SCGraph
20
20
  [![PyPI version](https://badge.fury.io/py/scgraph.svg)](https://badge.fury.io/py/scgraph)
21
21
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
22
- [![PyPI Downloads](https://pepy.tech/badge/scgraph)](https://pypi.org/project/scgraph/)
22
+ [![PyPI Downloads](https://static.pepy.tech/badge/scgraph)](https://pepy.tech/project/scgraph/)
23
23
 
24
24
  **A high-performance, lightweight Python library for shortest path routing on geographic and supply chain networks.**
25
25
 
@@ -191,21 +191,24 @@ All algorithms are available on `Graph` objects and accessible from `GeoGraph` v
191
191
  | `algorithm_fn` | Description | Time Complexity |
192
192
  |---|---|---|
193
193
  | `'dijkstra'` | Standard Dijkstra; general purpose, non-negative weights (default) | O((n+m) log n) |
194
+ | `'dijkstra_buckets'` | Dijkstra with buckets (Dial's algorithm); efficient for non-negative weights (ideally >= 1) | O(n+m+W) |
194
195
  | `'dijkstra_negative'` | Dijkstra with cycle detection; supports negative weights | O(n·m) |
195
196
  | `'a_star'` | A* with optional heuristic; faster than Dijkstra with a good heuristic | O((n+m) log n) |
196
197
  | `'bellman_ford'` | Bellman-Ford; supports negative weights, slower than Dijkstra | O(n·m) |
197
198
  | `'bmssp'` | [BMSSP Algorithm](https://arxiv.org/pdf/2504.17033) / [Implementation](https://github.com/connor-makowski/bmsspy) | O(m log^(2/3)(n)) |
198
199
  | `'cached_shortest_path'` | Caches shortest path tree from origin; near-instant repeated queries | O((n+m) log n) first, O(1) after |
199
200
  | `'contraction_hierarchy'` | Bidirectional Dijkstra on preprocessed CH graph; fast arbitrary queries | O(k log k) per query |
201
+ | `'tnr'` | [Transit Node Routing](https://en.wikipedia.org/wiki/Transit_node_routing); extremely fast for global queries | O(1) per query (global) |
200
202
 
201
203
  ## Performance Guide
202
204
 
203
205
  | Scenario | Recommended Approach |
204
206
  |---|---|
205
207
  | Single query | `dijkstra` (default) |
208
+ | Weights generally >= 1 | `dijkstra_buckets` |
206
209
  | Repeated queries from one origin | `cached_shortest_path` |
207
210
  | Large distance matrix (same graph) | `distance_matrix` method |
208
- | Many arbitrary queries on a fixed graph | `contraction_hierarchy` |
211
+ | Many arbitrary queries on a fixed graph | `contraction_hierarchy` or `tnr` |
209
212
  | Graph with negative weights | `dijkstra_negative` |
210
213
 
211
214
  ## Heuristic Functions (for A*)
@@ -1,7 +1,7 @@
1
1
  # SCGraph
2
2
  [![PyPI version](https://badge.fury.io/py/scgraph.svg)](https://badge.fury.io/py/scgraph)
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![PyPI Downloads](https://pepy.tech/badge/scgraph)](https://pypi.org/project/scgraph/)
4
+ [![PyPI Downloads](https://static.pepy.tech/badge/scgraph)](https://pepy.tech/project/scgraph/)
5
5
 
6
6
  **A high-performance, lightweight Python library for shortest path routing on geographic and supply chain networks.**
7
7
 
@@ -173,21 +173,24 @@ All algorithms are available on `Graph` objects and accessible from `GeoGraph` v
173
173
  | `algorithm_fn` | Description | Time Complexity |
174
174
  |---|---|---|
175
175
  | `'dijkstra'` | Standard Dijkstra; general purpose, non-negative weights (default) | O((n+m) log n) |
176
+ | `'dijkstra_buckets'` | Dijkstra with buckets (Dial's algorithm); efficient for non-negative weights (ideally >= 1) | O(n+m+W) |
176
177
  | `'dijkstra_negative'` | Dijkstra with cycle detection; supports negative weights | O(n·m) |
177
178
  | `'a_star'` | A* with optional heuristic; faster than Dijkstra with a good heuristic | O((n+m) log n) |
178
179
  | `'bellman_ford'` | Bellman-Ford; supports negative weights, slower than Dijkstra | O(n·m) |
179
180
  | `'bmssp'` | [BMSSP Algorithm](https://arxiv.org/pdf/2504.17033) / [Implementation](https://github.com/connor-makowski/bmsspy) | O(m log^(2/3)(n)) |
180
181
  | `'cached_shortest_path'` | Caches shortest path tree from origin; near-instant repeated queries | O((n+m) log n) first, O(1) after |
181
182
  | `'contraction_hierarchy'` | Bidirectional Dijkstra on preprocessed CH graph; fast arbitrary queries | O(k log k) per query |
183
+ | `'tnr'` | [Transit Node Routing](https://en.wikipedia.org/wiki/Transit_node_routing); extremely fast for global queries | O(1) per query (global) |
182
184
 
183
185
  ## Performance Guide
184
186
 
185
187
  | Scenario | Recommended Approach |
186
188
  |---|---|
187
189
  | Single query | `dijkstra` (default) |
190
+ | Weights generally >= 1 | `dijkstra_buckets` |
188
191
  | Repeated queries from one origin | `cached_shortest_path` |
189
192
  | Large distance matrix (same graph) | `distance_matrix` method |
190
- | Many arbitrary queries on a fixed graph | `contraction_hierarchy` |
193
+ | Many arbitrary queries on a fixed graph | `contraction_hierarchy` or `tnr` |
191
194
  | Graph with negative weights | `dijkstra_negative` |
192
195
 
193
196
  ## Heuristic Functions (for A*)
@@ -1,4 +1,4 @@
1
- cmake_minimum_required(VERSION 4.2.3)
1
+ cmake_minimum_required(VERSION 4.3.1)
2
2
  set(CMAKE_MODULE_PATH "")
3
3
 
4
4
  project(CheckCXX LANGUAGES CXX)
@@ -26,7 +26,7 @@ include = [
26
26
 
27
27
  [project]
28
28
  name = "scgraph"
29
- version = "3.1.0"
29
+ version = "3.2.0"
30
30
  description = "Determine an approximate distance and route between two points on earth."
31
31
  authors = [
32
32
  {name="Connor Makowski", email="conmak@mit.edu"}
@@ -2,7 +2,7 @@
2
2
  # SCGraph
3
3
  [![PyPI version](https://badge.fury.io/py/scgraph.svg)](https://badge.fury.io/py/scgraph)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![PyPI Downloads](https://pepy.tech/badge/scgraph)](https://pypi.org/project/scgraph/)
5
+ [![PyPI Downloads](https://static.pepy.tech/badge/scgraph)](https://pepy.tech/project/scgraph/)
6
6
 
7
7
  **A high-performance, lightweight Python library for shortest path routing on geographic and supply chain networks.**
8
8
 
@@ -174,21 +174,24 @@ All algorithms are available on `Graph` objects and accessible from `GeoGraph` v
174
174
  | `algorithm_fn` | Description | Time Complexity |
175
175
  |---|---|---|
176
176
  | `'dijkstra'` | Standard Dijkstra; general purpose, non-negative weights (default) | O((n+m) log n) |
177
+ | `'dijkstra_buckets'` | Dijkstra with buckets (Dial's algorithm); efficient for non-negative weights (ideally >= 1) | O(n+m+W) |
177
178
  | `'dijkstra_negative'` | Dijkstra with cycle detection; supports negative weights | O(n·m) |
178
179
  | `'a_star'` | A* with optional heuristic; faster than Dijkstra with a good heuristic | O((n+m) log n) |
179
180
  | `'bellman_ford'` | Bellman-Ford; supports negative weights, slower than Dijkstra | O(n·m) |
180
181
  | `'bmssp'` | [BMSSP Algorithm](https://arxiv.org/pdf/2504.17033) / [Implementation](https://github.com/connor-makowski/bmsspy) | O(m log^(2/3)(n)) |
181
182
  | `'cached_shortest_path'` | Caches shortest path tree from origin; near-instant repeated queries | O((n+m) log n) first, O(1) after |
182
183
  | `'contraction_hierarchy'` | Bidirectional Dijkstra on preprocessed CH graph; fast arbitrary queries | O(k log k) per query |
184
+ | `'tnr'` | [Transit Node Routing](https://en.wikipedia.org/wiki/Transit_node_routing); extremely fast for global queries | O(1) per query (global) |
183
185
 
184
186
  ## Performance Guide
185
187
 
186
188
  | Scenario | Recommended Approach |
187
189
  |---|---|
188
190
  | Single query | `dijkstra` (default) |
191
+ | Weights generally >= 1 | `dijkstra_buckets` |
189
192
  | Repeated queries from one origin | `cached_shortest_path` |
190
193
  | Large distance matrix (same graph) | `distance_matrix` method |
191
- | Many arbitrary queries on a fixed graph | `contraction_hierarchy` |
194
+ | Many arbitrary queries on a fixed graph | `contraction_hierarchy` or `tnr` |
192
195
  | Graph with negative weights | `dijkstra_negative` |
193
196
 
194
197
  ## Heuristic Functions (for A*)
@@ -12,6 +12,7 @@
12
12
  #include <nanobind/operators.h>
13
13
  #include "../src/graph.hpp"
14
14
  #include "../src/contraction_hierarchies.hpp"
15
+ #include "../src/transit_node_routing.hpp"
15
16
 
16
17
  namespace nb = nanobind;
17
18
  using namespace nb::literals;
@@ -143,7 +144,17 @@ NB_MODULE(cpp, m) {
143
144
  }, nb::arg("origin_id"), nb::arg("destination_id"),
144
145
  "Find shortest path using Dijkstra's algorithm")
145
146
 
147
+ .def("dijkstra_buckets", [](Graph& self,
148
+ const std::variant<int, std::set<int>>& origin_id,
149
+ int destination_id,
150
+ std::optional<double> max_edge_weight) -> nb::dict {
151
+ return graph_result_to_dict(self.dijkstra_buckets(origin_id, destination_id, max_edge_weight));
152
+ }, nb::arg("origin_id"), nb::arg("destination_id"),
153
+ nb::arg("max_edge_weight") = nb::none(),
154
+ "Find shortest path using Dijkstra with buckets")
155
+
146
156
  .def("dijkstra_negative", [](Graph& self,
157
+
147
158
  const std::variant<int, std::set<int>>& origin_id,
148
159
  int destination_id,
149
160
  std::optional<int> cycle_check_iterations) -> nb::dict {
@@ -193,12 +204,21 @@ NB_MODULE(cpp, m) {
193
204
  .def("create_contraction_hierarchy", &Graph::create_contraction_hierarchy,
194
205
  nb::arg("heuristic_fn") = nullptr,
195
206
  "Create a Contraction Hierarchies (CH) graph")
196
- .def("contraction_hierarchy", [](Graph& self, int origin_id, int destination_id,
197
- bool length_only) -> nb::dict {
207
+ .def("contraction_hierarchy", [](Graph& self, int origin_id, int destination_id) -> nb::dict {
198
208
  return graph_result_to_dict(self.contraction_hierarchy(origin_id, destination_id));
199
209
  }, nb::arg("origin_id"), nb::arg("destination_id"),
200
- nb::arg("length_only") = false,
201
- "Get shortest path using Contraction Hierarchies");
210
+ "Get shortest path using Contraction Hierarchies")
211
+
212
+ // Transit Node Routing
213
+ .def("create_tnr_hierarchy", &Graph::create_tnr_hierarchy,
214
+ nb::arg("num_transit_nodes") = 100, nb::arg("heuristic_fn") = nullptr,
215
+ "Create a Transit Node Routing (TNR) graph")
216
+ .def("set_tnr_graph", &Graph::set_tnr_graph, nb::arg("tnr_graph"),
217
+ "Set the TNRGraph object for the graph")
218
+ .def("tnr", [](Graph& self, int origin_id, int destination_id, bool length_only) -> nb::dict {
219
+ return graph_result_to_dict(self.tnr(origin_id, destination_id, length_only));
220
+ }, nb::arg("origin_id"), nb::arg("destination_id"), nb::arg("length_only") = false,
221
+ "Get shortest path using Transit Node Routing");
202
222
 
203
223
  // CHGraph class
204
224
  nb::class_<CHGraph>(m, "CHGraph")
@@ -317,5 +337,188 @@ NB_MODULE(cpp, m) {
317
337
  }
318
338
 
319
339
  return CHGraph(nodes_count, ranks, forward_graph, backward_graph, shortcuts, original_graph);
320
- }, nb::arg("filename"), "Load a CHGraph from a JSON file.");
321
- }
340
+ }, nb::arg("filename"), "Load a CHGraph from a JSON file.");
341
+
342
+ // TNRGraph class
343
+ nb::class_<TNRGraph, CHGraph>(m, "TNRGraph")
344
+ .def(nb::init<const std::vector<std::unordered_map<int, double>>&, int, std::function<double(CHGraph*, int)>>(),
345
+ nb::arg("graph"), nb::arg("num_transit_nodes") = 100, nb::arg("heuristic_fn") = nullptr,
346
+ "Initialize and preprocess a TNRGraph")
347
+ .def(nb::init<int, const std::vector<int>&,
348
+
349
+ const std::vector<std::unordered_map<int, double>>&,
350
+ const std::vector<std::unordered_map<int, double>>&,
351
+ const std::unordered_map<std::pair<int, int>, int, pair_hash>&,
352
+ const std::optional<std::vector<std::unordered_map<int, double>>>&,
353
+ const std::set<int>&,
354
+ const std::unordered_map<std::pair<int, int>, double, pair_hash>&,
355
+ const std::vector<std::unordered_map<int, double>>&,
356
+ const std::vector<std::unordered_map<int, double>>&>(),
357
+ nb::arg("nodes_count"), nb::arg("ranks"), nb::arg("forward_graph"),
358
+ nb::arg("backward_graph"), nb::arg("shortcuts"), nb::arg("original_graph"),
359
+ nb::arg("transit_nodes"), nb::arg("distance_table"),
360
+ nb::arg("forward_access_nodes"), nb::arg("backward_access_nodes"),
361
+ "Initialize a TNRGraph from pre-calculated data")
362
+ .def("search", [](TNRGraph& self, int origin_id, int destination_id, bool length_only) -> nb::dict {
363
+ return graph_result_to_dict(self.search(origin_id, destination_id, length_only));
364
+ }, nb::arg("origin_id"), nb::arg("destination_id"), nb::arg("length_only") = false,
365
+ "Perform a bidirectional search on the TNR")
366
+ .def("get_shortest_path", [](TNRGraph& self, int origin_id, int destination_id, bool length_only) -> nb::dict {
367
+ return graph_result_to_dict(self.search(origin_id, destination_id, length_only));
368
+ }, nb::arg("origin_id"), nb::arg("destination_id"), nb::arg("length_only") = false,
369
+ "Wrapper for search to match scgraph naming conventions")
370
+ .def_prop_ro("transit_nodes", &TNRGraph::get_transit_nodes)
371
+ .def_prop_ro("distance_table", [](const TNRGraph& self) {
372
+ nb::dict d;
373
+ for (const auto& [key, dist] : self.get_distance_table()) {
374
+ d[nb::cast(key)] = dist;
375
+ }
376
+ return d;
377
+ })
378
+ .def_prop_ro("forward_access_nodes", &TNRGraph::get_forward_access_nodes)
379
+ .def_prop_ro("backward_access_nodes", &TNRGraph::get_backward_access_nodes)
380
+ .def("save_as_tnrjson", [](const TNRGraph& self, const std::string& filename) {
381
+ if (filename.size() < 8 || filename.substr(filename.size() - 8) != ".tnrjson") {
382
+ throw std::invalid_argument("Filename must end with .tnrjson");
383
+ }
384
+
385
+ nb::dict d;
386
+ d["type"] = "TNRGraph";
387
+ d["nodes_count"] = self.get_nodes_count();
388
+
389
+ nb::list t_nodes;
390
+ for (int node : self.get_transit_nodes()) {
391
+ t_nodes.append(node);
392
+ }
393
+ d["transit_nodes"] = t_nodes;
394
+
395
+ nb::dict dist_table_str;
396
+ for (const auto& [key, dist] : self.get_distance_table()) {
397
+ std::string key_str = "(" + std::to_string(key.first) + "," + std::to_string(key.second) + ")";
398
+ dist_table_str[nb::cast(key_str)] = dist;
399
+ }
400
+ d["distance_table"] = dist_table_str;
401
+
402
+ auto convert_access_nodes = [](const std::vector<std::unordered_map<int, double>>& access_nodes) {
403
+ nb::list access_str;
404
+ for (const auto& node_map : access_nodes) {
405
+ nb::dict d_map;
406
+ for (const auto& [k, v] : node_map) {
407
+ d_map[nb::cast(std::to_string(k))] = v;
408
+ }
409
+ access_str.append(d_map);
410
+ }
411
+ return access_str;
412
+ };
413
+
414
+ d["forward_access_nodes"] = convert_access_nodes(self.get_forward_access_nodes());
415
+ d["backward_access_nodes"] = convert_access_nodes(self.get_backward_access_nodes());
416
+
417
+ nb::dict ch_data;
418
+ ch_data["ranks"] = self.get_ranks();
419
+ ch_data["forward_graph"] = self.get_forward_graph();
420
+ ch_data["backward_graph"] = self.get_backward_graph();
421
+
422
+ nb::dict shortcuts_str;
423
+ for (const auto& [key, via_node_id] : self.get_shortcuts()) {
424
+ std::string key_str = "(" + std::to_string(key.first) + ", " + std::to_string(key.second) + ")";
425
+ shortcuts_str[nb::cast(key_str)] = via_node_id;
426
+ }
427
+ ch_data["shortcuts"] = shortcuts_str;
428
+ ch_data["original_graph"] = self.get_original_graph();
429
+ ch_data["nodes_count"] = self.get_nodes_count();
430
+
431
+ d["ch_data"] = ch_data;
432
+
433
+ nb::module_ json = nb::module_::import_("json");
434
+ nb::module_ builtins = nb::module_::import_("builtins");
435
+ nb::object f = builtins.attr("open")(filename, "w");
436
+ json.attr("dump")(d, f);
437
+ f.attr("close")();
438
+ }, nb::arg("filename"), "Save the current TNRGraph as a JSON file.")
439
+ .def_static("load_from_tnrjson", [](const std::string& filename) {
440
+ if (filename.size() < 8 || filename.substr(filename.size() - 8) != ".tnrjson") {
441
+ throw std::invalid_argument("Filename must end with .tnrjson");
442
+ }
443
+
444
+ nb::module_ json = nb::module_::import_("json");
445
+ nb::module_ builtins = nb::module_::import_("builtins");
446
+ nb::object f = builtins.attr("open")(filename, "r");
447
+ nb::dict data = nb::cast<nb::dict>(json.attr("load")(f));
448
+ f.attr("close")();
449
+
450
+ if (!data.contains("type") || nb::cast<std::string>(data["type"]) != "TNRGraph") {
451
+ throw std::invalid_argument("JSON file is not a valid TNRGraph.");
452
+ }
453
+
454
+ int nodes_count = nb::cast<int>(data["nodes_count"]);
455
+
456
+ std::set<int> transit_nodes;
457
+ for (auto item : nb::cast<nb::list>(data["transit_nodes"])) {
458
+ transit_nodes.insert(nb::cast<int>(item));
459
+ }
460
+
461
+ std::unordered_map<std::pair<int, int>, double, pair_hash> distance_table;
462
+ for (auto [key, dist] : nb::cast<nb::dict>(data["distance_table"])) {
463
+ std::string key_str = nb::cast<std::string>(key);
464
+ size_t comma = key_str.find(',');
465
+ int t_f = std::stoi(key_str.substr(1, comma - 1));
466
+ int t_b = std::stoi(key_str.substr(comma + 1, key_str.size() - comma - 2));
467
+ distance_table[{t_f, t_b}] = nb::cast<double>(dist);
468
+ }
469
+
470
+ auto convert_access_nodes = [](nb::list access_str) {
471
+ std::vector<std::unordered_map<int, double>> access_nodes;
472
+ for (auto item : access_str) {
473
+ nb::dict d = nb::cast<nb::dict>(item);
474
+ std::unordered_map<int, double> node_map;
475
+ for (auto [k, v] : d) {
476
+ node_map[std::stoi(nb::cast<std::string>(k))] = nb::cast<double>(v);
477
+ }
478
+ access_nodes.push_back(node_map);
479
+ }
480
+ return access_nodes;
481
+ };
482
+
483
+ std::vector<std::unordered_map<int, double>> forward_access_nodes = convert_access_nodes(nb::cast<nb::list>(data["forward_access_nodes"]));
484
+ std::vector<std::unordered_map<int, double>> backward_access_nodes = convert_access_nodes(nb::cast<nb::list>(data["backward_access_nodes"]));
485
+
486
+ nb::dict ch_data = nb::cast<nb::dict>(data["ch_data"]);
487
+ std::vector<int> ranks = nb::cast<std::vector<int>>(ch_data["ranks"]);
488
+
489
+ auto convert_graph = [](nb::list raw_graph) {
490
+ std::vector<std::unordered_map<int, double>> graph;
491
+ for (auto item : raw_graph) {
492
+ nb::dict d = nb::cast<nb::dict>(item);
493
+ std::unordered_map<int, double> node_map;
494
+ for (auto [k, v] : d) {
495
+ node_map[std::stoi(nb::cast<std::string>(k))] = nb::cast<double>(v);
496
+ }
497
+ graph.push_back(node_map);
498
+ }
499
+ return graph;
500
+ };
501
+
502
+ std::vector<std::unordered_map<int, double>> forward_graph = convert_graph(nb::cast<nb::list>(ch_data["forward_graph"]));
503
+ std::vector<std::unordered_map<int, double>> backward_graph = convert_graph(nb::cast<nb::list>(ch_data["backward_graph"]));
504
+
505
+ nb::dict shortcuts_raw = nb::cast<nb::dict>(ch_data["shortcuts"]);
506
+ std::unordered_map<std::pair<int, int>, int, pair_hash> shortcuts;
507
+ for (auto [key, via_node_id] : shortcuts_raw) {
508
+ std::string key_str = nb::cast<std::string>(key);
509
+ size_t comma = key_str.find(',');
510
+ int shortcut_origin_id = std::stoi(key_str.substr(1, comma - 1));
511
+ size_t start_dest = comma + 1;
512
+ while (start_dest < key_str.size() && (key_str[start_dest] == ' ' || key_str[start_dest] == '\t')) start_dest++;
513
+ int shortcut_destination_id = std::stoi(key_str.substr(start_dest, key_str.size() - start_dest - 1));
514
+ shortcuts[{shortcut_origin_id, shortcut_destination_id}] = nb::cast<int>(via_node_id);
515
+ }
516
+
517
+ std::optional<std::vector<std::unordered_map<int, double>>> original_graph = std::nullopt;
518
+ if (ch_data.contains("original_graph") && !ch_data["original_graph"].is_none()) {
519
+ original_graph = convert_graph(nb::cast<nb::list>(ch_data["original_graph"]));
520
+ }
521
+
522
+ return TNRGraph(nodes_count, ranks, forward_graph, backward_graph, shortcuts, original_graph, transit_nodes, distance_table, forward_access_nodes, backward_access_nodes);
523
+ }, nb::arg("filename"), "Load a TNRGraph from a JSON file.");
524
+ }
@@ -8,7 +8,7 @@
8
8
  #include "graph_utils.hpp"
9
9
 
10
10
  class CHGraph {
11
- private:
11
+ protected:
12
12
  int nodes_count;
13
13
  std::vector<int> ranks;
14
14
  std::vector<std::unordered_map<int, double>> forward_graph;
@@ -17,10 +17,11 @@ Graph::Graph(const std::vector<std::unordered_map<int, double>>& graph_data, boo
17
17
  }
18
18
  }
19
19
 
20
- // Override reset_cache to also clear __ch_graph__
20
+ // Override reset_cache to also clear __ch_graph__ and __tnr_graph__
21
21
  void Graph::reset_cache() {
22
22
  GraphUtils::reset_cache();
23
23
  __ch_graph__ = nullptr;
24
+ __tnr_graph__ = nullptr;
24
25
  }
25
26
 
26
27
  // Tree algorithms
@@ -139,6 +140,78 @@ GraphResult Graph::dijkstra(const std::variant<int, std::set<int>>& origin_id, i
139
140
  };
140
141
  }
141
142
 
143
+ GraphResult Graph::dijkstra_buckets(const std::variant<int, std::set<int>>& origin_id, int destination_id,
144
+ std::optional<double> max_edge_weight) {
145
+ input_check(origin_id, destination_id);
146
+ auto origin_ids = get_origin_ids(origin_id);
147
+
148
+ double max_weight = 0.0;
149
+ if (max_edge_weight.has_value()) {
150
+ max_weight = max_edge_weight.value();
151
+ } else {
152
+ for (const auto& node_edges : graph) {
153
+ for (const auto& [connected_id, connected_distance] : node_edges) {
154
+ if (connected_distance > max_weight) {
155
+ max_weight = connected_distance;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ int num_buckets = static_cast<int>(std::ceil(max_weight)) + 1;
161
+
162
+ const size_t n = graph.size();
163
+ std::vector<double> distance_matrix(n, std::numeric_limits<double>::infinity());
164
+ std::vector<int> predecessor(n, -1);
165
+ std::vector<std::vector<int>> buckets(num_buckets);
166
+
167
+ for (int oid : origin_ids) {
168
+ distance_matrix[oid] = 0.0;
169
+ buckets[0].push_back(oid);
170
+ }
171
+
172
+ int current_dist = 0;
173
+ size_t nodes_in_buckets = origin_ids.size();
174
+
175
+ while (nodes_in_buckets > 0) {
176
+ int bucket_idx = current_dist % num_buckets;
177
+ while (buckets[bucket_idx].empty()) {
178
+ current_dist++;
179
+ bucket_idx = current_dist % num_buckets;
180
+ if (nodes_in_buckets == 0) break;
181
+ if (distance_matrix[destination_id] < static_cast<double>(current_dist)) break;
182
+ }
183
+
184
+ if (nodes_in_buckets == 0 || distance_matrix[destination_id] < static_cast<double>(current_dist)) break;
185
+
186
+ int current_id = buckets[bucket_idx].back();
187
+ buckets[bucket_idx].pop_back();
188
+ nodes_in_buckets--;
189
+
190
+ if (distance_matrix[current_id] < static_cast<double>(current_dist)) {
191
+ continue;
192
+ }
193
+
194
+ for (const auto& [connected_id, connected_distance] : graph[current_id]) {
195
+ double possible_distance = distance_matrix[current_id] + connected_distance;
196
+ if (possible_distance < distance_matrix[connected_id]) {
197
+ distance_matrix[connected_id] = possible_distance;
198
+ predecessor[connected_id] = current_id;
199
+ buckets[static_cast<int>(possible_distance) % num_buckets].push_back(connected_id);
200
+ nodes_in_buckets++;
201
+ }
202
+ }
203
+ }
204
+
205
+ if (distance_matrix[destination_id] == std::numeric_limits<double>::infinity()) {
206
+ throw std::runtime_error("The origin and destination nodes are not connected.");
207
+ }
208
+
209
+ return GraphResult{
210
+ reconstruct_path(destination_id, predecessor),
211
+ distance_matrix[destination_id]
212
+ };
213
+ }
214
+
142
215
  GraphResult Graph::dijkstra_negative(const std::variant<int, std::set<int>>& origin_id, int destination_id,
143
216
  std::optional<int> cycle_check_iterations) {
144
217
  input_check(origin_id, destination_id);
@@ -379,3 +452,19 @@ GraphResult Graph::contraction_hierarchy(int origin_id, int destination_id) {
379
452
  }
380
453
  return __ch_graph__->get_shortest_path(origin_id, destination_id);
381
454
  }
455
+
456
+ std::shared_ptr<TNRGraph> Graph::create_tnr_hierarchy(int num_transit_nodes, std::function<double(CHGraph*, int)> heuristic_fn) {
457
+ __tnr_graph__ = std::make_shared<TNRGraph>(get_graph(), num_transit_nodes, heuristic_fn);
458
+ return __tnr_graph__;
459
+ }
460
+
461
+ void Graph::set_tnr_graph(std::shared_ptr<TNRGraph> tnr_graph) {
462
+ __tnr_graph__ = tnr_graph;
463
+ }
464
+
465
+ GraphResult Graph::tnr(int origin_id, int destination_id, bool length_only) {
466
+ if (__tnr_graph__ == nullptr) {
467
+ throw std::runtime_error("TNRGraph has not been set. Use set_tnr_graph() first.");
468
+ }
469
+ return __tnr_graph__->search(origin_id, destination_id, length_only);
470
+ }
@@ -6,17 +6,19 @@
6
6
  #include <memory>
7
7
  #include "graph_utils.hpp"
8
8
 
9
- class CHGraph; // Forward declaration
9
+ #include "contraction_hierarchies.hpp"
10
+ #include "transit_node_routing.hpp"
10
11
 
11
12
  class Graph : public GraphUtils {
12
13
  private:
13
14
  std::shared_ptr<CHGraph> __ch_graph__ = nullptr;
15
+ std::shared_ptr<TNRGraph> __tnr_graph__ = nullptr;
14
16
 
15
17
  public:
16
18
  // Constructor
17
19
  explicit Graph(const std::vector<std::unordered_map<int, double>>& graph_data, bool validate = false);
18
20
 
19
- // Override reset_cache to also clear ch_graph
21
+ // Override reset_cache to also clear ch_graph and tnr_graph
20
22
  void reset_cache();
21
23
 
22
24
  // Tree algorithms
@@ -25,6 +27,8 @@ public:
25
27
 
26
28
  // Shortest path algorithms
27
29
  GraphResult dijkstra(const std::variant<int, std::set<int>>& origin_id, int destination_id);
30
+ GraphResult dijkstra_buckets(const std::variant<int, std::set<int>>& origin_id, int destination_id,
31
+ std::optional<double> max_edge_weight = std::nullopt);
28
32
  GraphResult dijkstra_negative(const std::variant<int, std::set<int>>& origin_id, int destination_id,
29
33
  std::optional<int> cycle_check_iterations = std::nullopt);
30
34
  GraphResult a_star(const std::variant<int, std::set<int>>& origin_id, int destination_id,
@@ -38,4 +42,10 @@ public:
38
42
  // Contraction Hierarchies
39
43
  std::shared_ptr<CHGraph> create_contraction_hierarchy(std::function<double(CHGraph*, int)> heuristic_fn = nullptr);
40
44
  GraphResult contraction_hierarchy(int origin_id, int destination_id);
45
+
46
+ // Transit Node Routing
47
+ std::shared_ptr<TNRGraph> create_tnr_hierarchy(int num_transit_nodes = 100, std::function<double(CHGraph*, int)> heuristic_fn = nullptr);
48
+ void set_tnr_graph(std::shared_ptr<TNRGraph> tnr_graph);
49
+ GraphResult tnr(int origin_id, int destination_id, bool length_only = false);
50
+
41
51
  };