scgraph 3.4.1__tar.gz → 3.5.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.
- {scgraph-3.4.1 → scgraph-3.5.0}/CMakeLists.txt +1 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/PKG-INFO +83 -7
- {scgraph-3.4.1 → scgraph-3.5.0}/README.md +82 -6
- {scgraph-3.4.1 → scgraph-3.5.0}/pyproject.toml +1 -1
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/__init__.py +87 -7
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/contraction_hierarchies.py +297 -118
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/cpp/bindings/graph_bindings.cpp +117 -12
- scgraph-3.5.0/scgraph/cpp/src/contraction_hierarchies.cpp +494 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/cpp/src/contraction_hierarchies.hpp +24 -6
- scgraph-3.5.0/scgraph/cpp/src/graph.cpp +778 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/cpp/src/graph.hpp +14 -6
- scgraph-3.5.0/scgraph/cpp/src/graph_reducer.cpp +402 -0
- scgraph-3.5.0/scgraph/cpp/src/graph_reducer.hpp +54 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/cpp/src/graph_utils.cpp +17 -3
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/cpp/src/graph_utils.hpp +3 -1
- scgraph-3.5.0/scgraph/cpp/src/transit_node_routing.cpp +486 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/cpp/src/transit_node_routing.hpp +11 -2
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/geograph.py +189 -7
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/graph.py +277 -60
- scgraph-3.5.0/scgraph/graph_reducer.py +531 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/graph_utils.py +98 -52
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/transit_node_routing.py +188 -80
- scgraph-3.4.1/scgraph/cpp/src/contraction_hierarchies.cpp +0 -316
- scgraph-3.4.1/scgraph/cpp/src/graph.cpp +0 -472
- scgraph-3.4.1/scgraph/cpp/src/transit_node_routing.cpp +0 -290
- {scgraph-3.4.1 → scgraph-3.5.0}/LICENSE +0 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/cpp/src/bmssp.hpp +0 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/grid.py +0 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/helpers/__init__.py +0 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/helpers/geojson.py +0 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/helpers/visvalingam.py +0 -0
- {scgraph-3.4.1 → scgraph-3.5.0}/scgraph/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scgraph
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.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
|
|
@@ -35,7 +35,7 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
|
|
36
36
|

|
|
37
37
|
|
|
38
|
-
SCGraph provides fast, flexible shortest path routing for road, rail, maritime, and custom networks. It ships with prebuilt geographic networks, supports arbitrary graph construction and OSMNx integration, and scales from simple point-to-point queries to massive distance matrices. It also supports optional C++ acceleration and Contraction Hierarchy preprocessing for large-scale applications.
|
|
38
|
+
SCGraph provides fast, flexible shortest path routing for road, rail, maritime, and custom networks. It ships with prebuilt geographic networks, supports arbitrary graph construction and OSMNx integration, and scales from simple point-to-point queries to massive distance matrices. It also supports graph reduction, lazy loading, optional C++ acceleration, and Contraction Hierarchy preprocessing for large-scale applications.
|
|
39
39
|
|
|
40
40
|
- **Docs**: https://connor-makowski.github.io/scgraph/scgraph.html
|
|
41
41
|
- **Paper**: https://ssrn.com/abstract=5388845
|
|
@@ -201,6 +201,7 @@ All algorithms are available on `Graph` objects and accessible from `GeoGraph` v
|
|
|
201
201
|
| `algorithm_fn` | Description | Time Complexity |
|
|
202
202
|
|---|---|---|
|
|
203
203
|
| `'dijkstra'` | Standard Dijkstra; general purpose, non-negative weights (default) | O((n+m) log n) |
|
|
204
|
+
| `'bidirectional_dijkstra'` | Bidirectional Dijkstra; simultaneous search from origin and destination | O((n+m) log n) |
|
|
204
205
|
| `'dijkstra_buckets'` | Dijkstra with buckets (Dial's algorithm); efficient for non-negative weights (ideally >= 1) | O(n+m+W) |
|
|
205
206
|
| `'dijkstra_negative'` | Dijkstra with cycle detection; supports negative weights | O(n·m) |
|
|
206
207
|
| `'a_star'` | A* with optional heuristic; faster than Dijkstra with a good heuristic | O((n+m) log n) |
|
|
@@ -214,12 +215,14 @@ All algorithms are available on `Graph` objects and accessible from `GeoGraph` v
|
|
|
214
215
|
|
|
215
216
|
| Scenario | Recommended Approach |
|
|
216
217
|
|---|---|
|
|
217
|
-
| Single query | `dijkstra` (default) |
|
|
218
|
+
| Single query | `dijkstra` (default) or `bidirectional_dijkstra` |
|
|
219
|
+
| Sparse networks (road/rail/maritime) | Reduce with `geograph.reduce()` for 2–4x faster queries |
|
|
218
220
|
| Weights generally >= 1 | `dijkstra_buckets` |
|
|
219
221
|
| Repeated queries from one origin | `cached_shortest_path` |
|
|
220
222
|
| Large distance matrix (same graph) | `distance_matrix` method |
|
|
221
223
|
| Many arbitrary queries on a fixed graph | `contraction_hierarchy` or `tnr` |
|
|
222
224
|
| Graph with negative weights | `dijkstra_negative` |
|
|
225
|
+
| Fast app startup / multi-network loading | `GeoGraph.load_geograph(..., lazy=True)` |
|
|
223
226
|
|
|
224
227
|
## Heuristic Functions (for A*)
|
|
225
228
|
|
|
@@ -297,14 +300,14 @@ from scgraph import GeoGraph
|
|
|
297
300
|
|
|
298
301
|
marnet_geograph = GeoGraph.load_geograph("marnet")
|
|
299
302
|
|
|
300
|
-
# First call: computes and caches the shortest path tree (~same cost as dijkstra)
|
|
303
|
+
# First call: computes and caches the shortest path tree (~same cost as dijkstra without an early termination)
|
|
301
304
|
output1 = marnet_geograph.get_shortest_path(
|
|
302
305
|
origin_node={"latitude": 31.23, "longitude": 121.47}, # Shanghai
|
|
303
306
|
destination_node={"latitude": 32.08, "longitude": -81.09}, # Savannah, GA
|
|
304
307
|
algorithm_fn='cached_shortest_path',
|
|
305
308
|
)
|
|
306
309
|
|
|
307
|
-
# Subsequent calls to the same origin are near-instant
|
|
310
|
+
# Subsequent calls to the same origin are near-instant (O(1) lookup)
|
|
308
311
|
output2 = marnet_geograph.get_shortest_path(
|
|
309
312
|
origin_node={"latitude": 31.23, "longitude": 121.47}, # Shanghai (same)
|
|
310
313
|
destination_node={"latitude": 51.50, "longitude": -0.13}, # London
|
|
@@ -339,6 +342,53 @@ matrix = us_freeway_geograph.distance_matrix(cities, output_units='km')
|
|
|
339
342
|
|
|
340
343
|
For large matrices, throughput can approach 500 nanoseconds per distance query.
|
|
341
344
|
|
|
345
|
+
## Graph Reduction
|
|
346
|
+
|
|
347
|
+
Sparse networks (highways, rail lines, shipping corridors) often contain long chains of intermediate nodes with only two connections (degree 2). Graph reduction compresses these chains into single composite edges while preserving exact distances and full path reconstruction:
|
|
348
|
+
|
|
349
|
+
```py
|
|
350
|
+
from scgraph import GeoGraph
|
|
351
|
+
|
|
352
|
+
us_freeway = GeoGraph.load_geograph("us_freeway")
|
|
353
|
+
|
|
354
|
+
# Reduce the graph by bypassing pass-through chains (e.g. simplifies ~88% of nodes on us_freeway)
|
|
355
|
+
us_freeway.reduce(iterations=1)
|
|
356
|
+
|
|
357
|
+
# All standard algorithms (dijkstra, bidirectional_dijkstra, a_star, etc.)
|
|
358
|
+
# automatically execute on the reduced graph and expand paths back to original coordinates:
|
|
359
|
+
output = us_freeway.get_shortest_path(
|
|
360
|
+
origin_node={"latitude": 34.05, "longitude": -118.24}, # Los Angeles
|
|
361
|
+
destination_node={"latitude": 40.71, "longitude": -74.01}, # New York City
|
|
362
|
+
algorithm_fn='bidirectional_dijkstra',
|
|
363
|
+
)
|
|
364
|
+
print(output['length'])
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Key features of graph reduction:
|
|
368
|
+
- **Zero inaccuracy**: Paths and lengths are guaranteed to be mathematically identical to the unreduced graph.
|
|
369
|
+
- **Same-chain routing**: If origin and destination fall on the same contracted chain, the original graph is used with the standard dijkstra algorithm.
|
|
370
|
+
- **Iterative reduction**: Pass `iterations=-1` to reduce iteratively until full convergence (simplifying sub-chains created after initial contraction).
|
|
371
|
+
|
|
372
|
+
## Lazy Loading
|
|
373
|
+
|
|
374
|
+
To avoid upfront downloading, parsing, or memory overhead when defining multiple networks or optimizing startup latency, use `lazy=True`:
|
|
375
|
+
|
|
376
|
+
```py
|
|
377
|
+
from scgraph import GeoGraph
|
|
378
|
+
|
|
379
|
+
# Returns a LazyGeoGraph proxy immediately without downloading or parsing
|
|
380
|
+
world_net = GeoGraph.load_geograph("world_highways_and_marnet", lazy=True)
|
|
381
|
+
|
|
382
|
+
# The network is downloaded and instantiated only when first queried:
|
|
383
|
+
output = world_net.get_shortest_path(
|
|
384
|
+
origin_node={"latitude": 31.23, "longitude": 121.47},
|
|
385
|
+
destination_node={"latitude": 32.08, "longitude": -81.09},
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Or explicitly load the graph at any time:
|
|
389
|
+
world_net.load()
|
|
390
|
+
```
|
|
391
|
+
|
|
342
392
|
## Node Addition Options
|
|
343
393
|
|
|
344
394
|
Control how origin/destination are connected to the graph:
|
|
@@ -376,7 +426,7 @@ output = marnet_geograph.get_shortest_path(
|
|
|
376
426
|
|
|
377
427
|
## Built-in Geographs: Cache Management
|
|
378
428
|
|
|
379
|
-
Built-in geographs are downloaded from GitHub on first use and stored in a local cache. Subsequent loads
|
|
429
|
+
Built-in geographs are downloaded from GitHub on first use and stored in a local cache. Subsequent loads only require reading from the local cache and do not require network access.
|
|
380
430
|
|
|
381
431
|
```py
|
|
382
432
|
from scgraph import GeoGraph
|
|
@@ -411,6 +461,16 @@ The cache location defaults to the platform-appropriate directory:
|
|
|
411
461
|
| macOS | `~/Library/Caches/scgraph/` |
|
|
412
462
|
| Windows | `%LOCALAPPDATA%\scgraph\` |
|
|
413
463
|
|
|
464
|
+
### `load_geograph` Parameters
|
|
465
|
+
|
|
466
|
+
| Parameter | Default | Description |
|
|
467
|
+
|---|---|---|
|
|
468
|
+
| `name` | required | Name of the built-in geograph (e.g. `'marnet'`, `'us_freeway'`) |
|
|
469
|
+
| `cache_dir` | `None` | Custom directory for downloaded files; defaults to the OS cache dir above |
|
|
470
|
+
| `geograph_url` | `'https://raw.githubusercontent.com/...'` | Base URL for fetching geographs |
|
|
471
|
+
| `reduce_iterations` | `0` | Number of reduction iterations to perform upon loading (`0` = none, `1` = single pass, `-1` = full convergence) |
|
|
472
|
+
| `lazy` | `False` | Defer loading/fetching until the geograph is first accessed |
|
|
473
|
+
|
|
414
474
|
## Loading from OSMNx
|
|
415
475
|
|
|
416
476
|
SCGraph integrates directly with [OSMNx](https://osmnx.readthedocs.io/) — load any OpenStreetMap network and convert it to a `GeoGraph` in two lines:
|
|
@@ -445,6 +505,8 @@ print(output['length'])
|
|
|
445
505
|
| `off_graph_travel_speed` | `None` | Speed (km/h) for off-graph connections; used to convert time-based weights to distances |
|
|
446
506
|
| `load_intermediate_nodes` | `True` | Load intermediate shape points for accurate path visualization |
|
|
447
507
|
| `silent` | `False` | Suppress progress output |
|
|
508
|
+
| `reduce_iterations` | `0` | Number of reduction iterations to perform upon loading (`0` = none, `1` = single pass, `-1` = full convergence) |
|
|
509
|
+
| `lazy` | `False` | Defer loading until the geograph is first accessed |
|
|
448
510
|
|
|
449
511
|
## Building from OSM Data (Without OSMNx)
|
|
450
512
|
|
|
@@ -547,6 +609,20 @@ graph.validate()
|
|
|
547
609
|
|
|
548
610
|
output = graph.dijkstra(origin_id=0, destination_id=5)
|
|
549
611
|
print(output) #=> {'path': [0, 2, 1, 3, 5], 'length': 10}
|
|
612
|
+
|
|
613
|
+
# Reduce the graph (contracts pass-through nodes):
|
|
614
|
+
graph.reduce(iterations=1)
|
|
615
|
+
|
|
616
|
+
# Custom algorithm decorator with automated reduced graph support:
|
|
617
|
+
@Graph.algorithm
|
|
618
|
+
def custom_solver(self, origin_id: int, destination_id: int) -> dict:
|
|
619
|
+
# Standard single-sided algorithm implementation using self.graph
|
|
620
|
+
...
|
|
621
|
+
|
|
622
|
+
@Graph.algorithm(bidirectional=True)
|
|
623
|
+
def custom_bidirectional_solver(self, origin_id: int, destination_id: int) -> dict:
|
|
624
|
+
# Bidirectional algorithm using self.graph and self.inverse_graph
|
|
625
|
+
...
|
|
550
626
|
```
|
|
551
627
|
|
|
552
628
|
## Custom GeoGraph
|
|
@@ -755,7 +831,7 @@ uv sync --extra dev
|
|
|
755
831
|
| `uv run pytest test/NN_*.py` | Run a specific test file |
|
|
756
832
|
| `uv run nox` | Run tests (C++ then no-C++) across Python 3.11–3.14 |
|
|
757
833
|
| `uv run nox -s tests-3.14` | Run both build variants on a single Python version |
|
|
758
|
-
| `uv run utils/
|
|
834
|
+
| `uv run utils/bench.py` | Run benchmarks (<30s), output `benchmark.md` |
|
|
759
835
|
| `uv run utils/prettify.py` | Format with autoflake + black |
|
|
760
836
|
|
|
761
837
|
For full developer documentation see [DEVELOPMENT.md](DEVELOPMENT.md).
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
SCGraph provides fast, flexible shortest path routing for road, rail, maritime, and custom networks. It ships with prebuilt geographic networks, supports arbitrary graph construction and OSMNx integration, and scales from simple point-to-point queries to massive distance matrices. It also supports optional C++ acceleration and Contraction Hierarchy preprocessing for large-scale applications.
|
|
10
|
+
SCGraph provides fast, flexible shortest path routing for road, rail, maritime, and custom networks. It ships with prebuilt geographic networks, supports arbitrary graph construction and OSMNx integration, and scales from simple point-to-point queries to massive distance matrices. It also supports graph reduction, lazy loading, optional C++ acceleration, and Contraction Hierarchy preprocessing for large-scale applications.
|
|
11
11
|
|
|
12
12
|
- **Docs**: https://connor-makowski.github.io/scgraph/scgraph.html
|
|
13
13
|
- **Paper**: https://ssrn.com/abstract=5388845
|
|
@@ -173,6 +173,7 @@ 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
|
+
| `'bidirectional_dijkstra'` | Bidirectional Dijkstra; simultaneous search from origin and destination | O((n+m) log n) |
|
|
176
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) |
|
|
@@ -186,12 +187,14 @@ All algorithms are available on `Graph` objects and accessible from `GeoGraph` v
|
|
|
186
187
|
|
|
187
188
|
| Scenario | Recommended Approach |
|
|
188
189
|
|---|---|
|
|
189
|
-
| Single query | `dijkstra` (default) |
|
|
190
|
+
| Single query | `dijkstra` (default) or `bidirectional_dijkstra` |
|
|
191
|
+
| Sparse networks (road/rail/maritime) | Reduce with `geograph.reduce()` for 2–4x faster queries |
|
|
190
192
|
| Weights generally >= 1 | `dijkstra_buckets` |
|
|
191
193
|
| Repeated queries from one origin | `cached_shortest_path` |
|
|
192
194
|
| Large distance matrix (same graph) | `distance_matrix` method |
|
|
193
195
|
| Many arbitrary queries on a fixed graph | `contraction_hierarchy` or `tnr` |
|
|
194
196
|
| Graph with negative weights | `dijkstra_negative` |
|
|
197
|
+
| Fast app startup / multi-network loading | `GeoGraph.load_geograph(..., lazy=True)` |
|
|
195
198
|
|
|
196
199
|
## Heuristic Functions (for A*)
|
|
197
200
|
|
|
@@ -269,14 +272,14 @@ from scgraph import GeoGraph
|
|
|
269
272
|
|
|
270
273
|
marnet_geograph = GeoGraph.load_geograph("marnet")
|
|
271
274
|
|
|
272
|
-
# First call: computes and caches the shortest path tree (~same cost as dijkstra)
|
|
275
|
+
# First call: computes and caches the shortest path tree (~same cost as dijkstra without an early termination)
|
|
273
276
|
output1 = marnet_geograph.get_shortest_path(
|
|
274
277
|
origin_node={"latitude": 31.23, "longitude": 121.47}, # Shanghai
|
|
275
278
|
destination_node={"latitude": 32.08, "longitude": -81.09}, # Savannah, GA
|
|
276
279
|
algorithm_fn='cached_shortest_path',
|
|
277
280
|
)
|
|
278
281
|
|
|
279
|
-
# Subsequent calls to the same origin are near-instant
|
|
282
|
+
# Subsequent calls to the same origin are near-instant (O(1) lookup)
|
|
280
283
|
output2 = marnet_geograph.get_shortest_path(
|
|
281
284
|
origin_node={"latitude": 31.23, "longitude": 121.47}, # Shanghai (same)
|
|
282
285
|
destination_node={"latitude": 51.50, "longitude": -0.13}, # London
|
|
@@ -311,6 +314,53 @@ matrix = us_freeway_geograph.distance_matrix(cities, output_units='km')
|
|
|
311
314
|
|
|
312
315
|
For large matrices, throughput can approach 500 nanoseconds per distance query.
|
|
313
316
|
|
|
317
|
+
## Graph Reduction
|
|
318
|
+
|
|
319
|
+
Sparse networks (highways, rail lines, shipping corridors) often contain long chains of intermediate nodes with only two connections (degree 2). Graph reduction compresses these chains into single composite edges while preserving exact distances and full path reconstruction:
|
|
320
|
+
|
|
321
|
+
```py
|
|
322
|
+
from scgraph import GeoGraph
|
|
323
|
+
|
|
324
|
+
us_freeway = GeoGraph.load_geograph("us_freeway")
|
|
325
|
+
|
|
326
|
+
# Reduce the graph by bypassing pass-through chains (e.g. simplifies ~88% of nodes on us_freeway)
|
|
327
|
+
us_freeway.reduce(iterations=1)
|
|
328
|
+
|
|
329
|
+
# All standard algorithms (dijkstra, bidirectional_dijkstra, a_star, etc.)
|
|
330
|
+
# automatically execute on the reduced graph and expand paths back to original coordinates:
|
|
331
|
+
output = us_freeway.get_shortest_path(
|
|
332
|
+
origin_node={"latitude": 34.05, "longitude": -118.24}, # Los Angeles
|
|
333
|
+
destination_node={"latitude": 40.71, "longitude": -74.01}, # New York City
|
|
334
|
+
algorithm_fn='bidirectional_dijkstra',
|
|
335
|
+
)
|
|
336
|
+
print(output['length'])
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Key features of graph reduction:
|
|
340
|
+
- **Zero inaccuracy**: Paths and lengths are guaranteed to be mathematically identical to the unreduced graph.
|
|
341
|
+
- **Same-chain routing**: If origin and destination fall on the same contracted chain, the original graph is used with the standard dijkstra algorithm.
|
|
342
|
+
- **Iterative reduction**: Pass `iterations=-1` to reduce iteratively until full convergence (simplifying sub-chains created after initial contraction).
|
|
343
|
+
|
|
344
|
+
## Lazy Loading
|
|
345
|
+
|
|
346
|
+
To avoid upfront downloading, parsing, or memory overhead when defining multiple networks or optimizing startup latency, use `lazy=True`:
|
|
347
|
+
|
|
348
|
+
```py
|
|
349
|
+
from scgraph import GeoGraph
|
|
350
|
+
|
|
351
|
+
# Returns a LazyGeoGraph proxy immediately without downloading or parsing
|
|
352
|
+
world_net = GeoGraph.load_geograph("world_highways_and_marnet", lazy=True)
|
|
353
|
+
|
|
354
|
+
# The network is downloaded and instantiated only when first queried:
|
|
355
|
+
output = world_net.get_shortest_path(
|
|
356
|
+
origin_node={"latitude": 31.23, "longitude": 121.47},
|
|
357
|
+
destination_node={"latitude": 32.08, "longitude": -81.09},
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Or explicitly load the graph at any time:
|
|
361
|
+
world_net.load()
|
|
362
|
+
```
|
|
363
|
+
|
|
314
364
|
## Node Addition Options
|
|
315
365
|
|
|
316
366
|
Control how origin/destination are connected to the graph:
|
|
@@ -348,7 +398,7 @@ output = marnet_geograph.get_shortest_path(
|
|
|
348
398
|
|
|
349
399
|
## Built-in Geographs: Cache Management
|
|
350
400
|
|
|
351
|
-
Built-in geographs are downloaded from GitHub on first use and stored in a local cache. Subsequent loads
|
|
401
|
+
Built-in geographs are downloaded from GitHub on first use and stored in a local cache. Subsequent loads only require reading from the local cache and do not require network access.
|
|
352
402
|
|
|
353
403
|
```py
|
|
354
404
|
from scgraph import GeoGraph
|
|
@@ -383,6 +433,16 @@ The cache location defaults to the platform-appropriate directory:
|
|
|
383
433
|
| macOS | `~/Library/Caches/scgraph/` |
|
|
384
434
|
| Windows | `%LOCALAPPDATA%\scgraph\` |
|
|
385
435
|
|
|
436
|
+
### `load_geograph` Parameters
|
|
437
|
+
|
|
438
|
+
| Parameter | Default | Description |
|
|
439
|
+
|---|---|---|
|
|
440
|
+
| `name` | required | Name of the built-in geograph (e.g. `'marnet'`, `'us_freeway'`) |
|
|
441
|
+
| `cache_dir` | `None` | Custom directory for downloaded files; defaults to the OS cache dir above |
|
|
442
|
+
| `geograph_url` | `'https://raw.githubusercontent.com/...'` | Base URL for fetching geographs |
|
|
443
|
+
| `reduce_iterations` | `0` | Number of reduction iterations to perform upon loading (`0` = none, `1` = single pass, `-1` = full convergence) |
|
|
444
|
+
| `lazy` | `False` | Defer loading/fetching until the geograph is first accessed |
|
|
445
|
+
|
|
386
446
|
## Loading from OSMNx
|
|
387
447
|
|
|
388
448
|
SCGraph integrates directly with [OSMNx](https://osmnx.readthedocs.io/) — load any OpenStreetMap network and convert it to a `GeoGraph` in two lines:
|
|
@@ -417,6 +477,8 @@ print(output['length'])
|
|
|
417
477
|
| `off_graph_travel_speed` | `None` | Speed (km/h) for off-graph connections; used to convert time-based weights to distances |
|
|
418
478
|
| `load_intermediate_nodes` | `True` | Load intermediate shape points for accurate path visualization |
|
|
419
479
|
| `silent` | `False` | Suppress progress output |
|
|
480
|
+
| `reduce_iterations` | `0` | Number of reduction iterations to perform upon loading (`0` = none, `1` = single pass, `-1` = full convergence) |
|
|
481
|
+
| `lazy` | `False` | Defer loading until the geograph is first accessed |
|
|
420
482
|
|
|
421
483
|
## Building from OSM Data (Without OSMNx)
|
|
422
484
|
|
|
@@ -519,6 +581,20 @@ graph.validate()
|
|
|
519
581
|
|
|
520
582
|
output = graph.dijkstra(origin_id=0, destination_id=5)
|
|
521
583
|
print(output) #=> {'path': [0, 2, 1, 3, 5], 'length': 10}
|
|
584
|
+
|
|
585
|
+
# Reduce the graph (contracts pass-through nodes):
|
|
586
|
+
graph.reduce(iterations=1)
|
|
587
|
+
|
|
588
|
+
# Custom algorithm decorator with automated reduced graph support:
|
|
589
|
+
@Graph.algorithm
|
|
590
|
+
def custom_solver(self, origin_id: int, destination_id: int) -> dict:
|
|
591
|
+
# Standard single-sided algorithm implementation using self.graph
|
|
592
|
+
...
|
|
593
|
+
|
|
594
|
+
@Graph.algorithm(bidirectional=True)
|
|
595
|
+
def custom_bidirectional_solver(self, origin_id: int, destination_id: int) -> dict:
|
|
596
|
+
# Bidirectional algorithm using self.graph and self.inverse_graph
|
|
597
|
+
...
|
|
522
598
|
```
|
|
523
599
|
|
|
524
600
|
## Custom GeoGraph
|
|
@@ -727,7 +803,7 @@ uv sync --extra dev
|
|
|
727
803
|
| `uv run pytest test/NN_*.py` | Run a specific test file |
|
|
728
804
|
| `uv run nox` | Run tests (C++ then no-C++) across Python 3.11–3.14 |
|
|
729
805
|
| `uv run nox -s tests-3.14` | Run both build variants on a single Python version |
|
|
730
|
-
| `uv run utils/
|
|
806
|
+
| `uv run utils/bench.py` | Run benchmarks (<30s), output `benchmark.md` |
|
|
731
807
|
| `uv run utils/prettify.py` | Format with autoflake + black |
|
|
732
808
|
|
|
733
809
|
For full developer documentation see [DEVELOPMENT.md](DEVELOPMENT.md).
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
SCGraph provides fast, flexible shortest path routing for road, rail, maritime, and custom networks. It ships with prebuilt geographic networks, supports arbitrary graph construction and OSMNx integration, and scales from simple point-to-point queries to massive distance matrices. It also supports optional C++ acceleration and Contraction Hierarchy preprocessing for large-scale applications.
|
|
11
|
+
SCGraph provides fast, flexible shortest path routing for road, rail, maritime, and custom networks. It ships with prebuilt geographic networks, supports arbitrary graph construction and OSMNx integration, and scales from simple point-to-point queries to massive distance matrices. It also supports graph reduction, lazy loading, optional C++ acceleration, and Contraction Hierarchy preprocessing for large-scale applications.
|
|
12
12
|
|
|
13
13
|
- **Docs**: https://connor-makowski.github.io/scgraph/scgraph.html
|
|
14
14
|
- **Paper**: https://ssrn.com/abstract=5388845
|
|
@@ -174,6 +174,7 @@ 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
|
+
| `'bidirectional_dijkstra'` | Bidirectional Dijkstra; simultaneous search from origin and destination | O((n+m) log n) |
|
|
177
178
|
| `'dijkstra_buckets'` | Dijkstra with buckets (Dial's algorithm); efficient for non-negative weights (ideally >= 1) | O(n+m+W) |
|
|
178
179
|
| `'dijkstra_negative'` | Dijkstra with cycle detection; supports negative weights | O(n·m) |
|
|
179
180
|
| `'a_star'` | A* with optional heuristic; faster than Dijkstra with a good heuristic | O((n+m) log n) |
|
|
@@ -187,12 +188,14 @@ All algorithms are available on `Graph` objects and accessible from `GeoGraph` v
|
|
|
187
188
|
|
|
188
189
|
| Scenario | Recommended Approach |
|
|
189
190
|
|---|---|
|
|
190
|
-
| Single query | `dijkstra` (default) |
|
|
191
|
+
| Single query | `dijkstra` (default) or `bidirectional_dijkstra` |
|
|
192
|
+
| Sparse networks (road/rail/maritime) | Reduce with `geograph.reduce()` for 2–4x faster queries |
|
|
191
193
|
| Weights generally >= 1 | `dijkstra_buckets` |
|
|
192
194
|
| Repeated queries from one origin | `cached_shortest_path` |
|
|
193
195
|
| Large distance matrix (same graph) | `distance_matrix` method |
|
|
194
196
|
| Many arbitrary queries on a fixed graph | `contraction_hierarchy` or `tnr` |
|
|
195
197
|
| Graph with negative weights | `dijkstra_negative` |
|
|
198
|
+
| Fast app startup / multi-network loading | `GeoGraph.load_geograph(..., lazy=True)` |
|
|
196
199
|
|
|
197
200
|
## Heuristic Functions (for A*)
|
|
198
201
|
|
|
@@ -270,14 +273,14 @@ from scgraph import GeoGraph
|
|
|
270
273
|
|
|
271
274
|
marnet_geograph = GeoGraph.load_geograph("marnet")
|
|
272
275
|
|
|
273
|
-
# First call: computes and caches the shortest path tree (~same cost as dijkstra)
|
|
276
|
+
# First call: computes and caches the shortest path tree (~same cost as dijkstra without an early termination)
|
|
274
277
|
output1 = marnet_geograph.get_shortest_path(
|
|
275
278
|
origin_node={"latitude": 31.23, "longitude": 121.47}, # Shanghai
|
|
276
279
|
destination_node={"latitude": 32.08, "longitude": -81.09}, # Savannah, GA
|
|
277
280
|
algorithm_fn='cached_shortest_path',
|
|
278
281
|
)
|
|
279
282
|
|
|
280
|
-
# Subsequent calls to the same origin are near-instant
|
|
283
|
+
# Subsequent calls to the same origin are near-instant (O(1) lookup)
|
|
281
284
|
output2 = marnet_geograph.get_shortest_path(
|
|
282
285
|
origin_node={"latitude": 31.23, "longitude": 121.47}, # Shanghai (same)
|
|
283
286
|
destination_node={"latitude": 51.50, "longitude": -0.13}, # London
|
|
@@ -312,6 +315,53 @@ matrix = us_freeway_geograph.distance_matrix(cities, output_units='km')
|
|
|
312
315
|
|
|
313
316
|
For large matrices, throughput can approach 500 nanoseconds per distance query.
|
|
314
317
|
|
|
318
|
+
## Graph Reduction
|
|
319
|
+
|
|
320
|
+
Sparse networks (highways, rail lines, shipping corridors) often contain long chains of intermediate nodes with only two connections (degree 2). Graph reduction compresses these chains into single composite edges while preserving exact distances and full path reconstruction:
|
|
321
|
+
|
|
322
|
+
```py
|
|
323
|
+
from scgraph import GeoGraph
|
|
324
|
+
|
|
325
|
+
us_freeway = GeoGraph.load_geograph("us_freeway")
|
|
326
|
+
|
|
327
|
+
# Reduce the graph by bypassing pass-through chains (e.g. simplifies ~88% of nodes on us_freeway)
|
|
328
|
+
us_freeway.reduce(iterations=1)
|
|
329
|
+
|
|
330
|
+
# All standard algorithms (dijkstra, bidirectional_dijkstra, a_star, etc.)
|
|
331
|
+
# automatically execute on the reduced graph and expand paths back to original coordinates:
|
|
332
|
+
output = us_freeway.get_shortest_path(
|
|
333
|
+
origin_node={"latitude": 34.05, "longitude": -118.24}, # Los Angeles
|
|
334
|
+
destination_node={"latitude": 40.71, "longitude": -74.01}, # New York City
|
|
335
|
+
algorithm_fn='bidirectional_dijkstra',
|
|
336
|
+
)
|
|
337
|
+
print(output['length'])
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Key features of graph reduction:
|
|
341
|
+
- **Zero inaccuracy**: Paths and lengths are guaranteed to be mathematically identical to the unreduced graph.
|
|
342
|
+
- **Same-chain routing**: If origin and destination fall on the same contracted chain, the original graph is used with the standard dijkstra algorithm.
|
|
343
|
+
- **Iterative reduction**: Pass `iterations=-1` to reduce iteratively until full convergence (simplifying sub-chains created after initial contraction).
|
|
344
|
+
|
|
345
|
+
## Lazy Loading
|
|
346
|
+
|
|
347
|
+
To avoid upfront downloading, parsing, or memory overhead when defining multiple networks or optimizing startup latency, use `lazy=True`:
|
|
348
|
+
|
|
349
|
+
```py
|
|
350
|
+
from scgraph import GeoGraph
|
|
351
|
+
|
|
352
|
+
# Returns a LazyGeoGraph proxy immediately without downloading or parsing
|
|
353
|
+
world_net = GeoGraph.load_geograph("world_highways_and_marnet", lazy=True)
|
|
354
|
+
|
|
355
|
+
# The network is downloaded and instantiated only when first queried:
|
|
356
|
+
output = world_net.get_shortest_path(
|
|
357
|
+
origin_node={"latitude": 31.23, "longitude": 121.47},
|
|
358
|
+
destination_node={"latitude": 32.08, "longitude": -81.09},
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Or explicitly load the graph at any time:
|
|
362
|
+
world_net.load()
|
|
363
|
+
```
|
|
364
|
+
|
|
315
365
|
## Node Addition Options
|
|
316
366
|
|
|
317
367
|
Control how origin/destination are connected to the graph:
|
|
@@ -349,7 +399,7 @@ output = marnet_geograph.get_shortest_path(
|
|
|
349
399
|
|
|
350
400
|
## Built-in Geographs: Cache Management
|
|
351
401
|
|
|
352
|
-
Built-in geographs are downloaded from GitHub on first use and stored in a local cache. Subsequent loads
|
|
402
|
+
Built-in geographs are downloaded from GitHub on first use and stored in a local cache. Subsequent loads only require reading from the local cache and do not require network access.
|
|
353
403
|
|
|
354
404
|
```py
|
|
355
405
|
from scgraph import GeoGraph
|
|
@@ -384,6 +434,16 @@ The cache location defaults to the platform-appropriate directory:
|
|
|
384
434
|
| macOS | `~/Library/Caches/scgraph/` |
|
|
385
435
|
| Windows | `%LOCALAPPDATA%\\scgraph\\` |
|
|
386
436
|
|
|
437
|
+
### `load_geograph` Parameters
|
|
438
|
+
|
|
439
|
+
| Parameter | Default | Description |
|
|
440
|
+
|---|---|---|
|
|
441
|
+
| `name` | required | Name of the built-in geograph (e.g. `'marnet'`, `'us_freeway'`) |
|
|
442
|
+
| `cache_dir` | `None` | Custom directory for downloaded files; defaults to the OS cache dir above |
|
|
443
|
+
| `geograph_url` | `'https://raw.githubusercontent.com/...'` | Base URL for fetching geographs |
|
|
444
|
+
| `reduce_iterations` | `0` | Number of reduction iterations to perform upon loading (`0` = none, `1` = single pass, `-1` = full convergence) |
|
|
445
|
+
| `lazy` | `False` | Defer loading/fetching until the geograph is first accessed |
|
|
446
|
+
|
|
387
447
|
## Loading from OSMNx
|
|
388
448
|
|
|
389
449
|
SCGraph integrates directly with [OSMNx](https://osmnx.readthedocs.io/) — load any OpenStreetMap network and convert it to a `GeoGraph` in two lines:
|
|
@@ -418,6 +478,8 @@ print(output['length'])
|
|
|
418
478
|
| `off_graph_travel_speed` | `None` | Speed (km/h) for off-graph connections; used to convert time-based weights to distances |
|
|
419
479
|
| `load_intermediate_nodes` | `True` | Load intermediate shape points for accurate path visualization |
|
|
420
480
|
| `silent` | `False` | Suppress progress output |
|
|
481
|
+
| `reduce_iterations` | `0` | Number of reduction iterations to perform upon loading (`0` = none, `1` = single pass, `-1` = full convergence) |
|
|
482
|
+
| `lazy` | `False` | Defer loading until the geograph is first accessed |
|
|
421
483
|
|
|
422
484
|
## Building from OSM Data (Without OSMNx)
|
|
423
485
|
|
|
@@ -520,6 +582,20 @@ graph.validate()
|
|
|
520
582
|
|
|
521
583
|
output = graph.dijkstra(origin_id=0, destination_id=5)
|
|
522
584
|
print(output) #=> {'path': [0, 2, 1, 3, 5], 'length': 10}
|
|
585
|
+
|
|
586
|
+
# Reduce the graph (contracts pass-through nodes):
|
|
587
|
+
graph.reduce(iterations=1)
|
|
588
|
+
|
|
589
|
+
# Custom algorithm decorator with automated reduced graph support:
|
|
590
|
+
@Graph.algorithm
|
|
591
|
+
def custom_solver(self, origin_id: int, destination_id: int) -> dict:
|
|
592
|
+
# Standard single-sided algorithm implementation using self.graph
|
|
593
|
+
...
|
|
594
|
+
|
|
595
|
+
@Graph.algorithm(bidirectional=True)
|
|
596
|
+
def custom_bidirectional_solver(self, origin_id: int, destination_id: int) -> dict:
|
|
597
|
+
# Bidirectional algorithm using self.graph and self.inverse_graph
|
|
598
|
+
...
|
|
523
599
|
```
|
|
524
600
|
|
|
525
601
|
## Custom GeoGraph
|
|
@@ -728,7 +804,7 @@ uv sync --extra dev
|
|
|
728
804
|
| `uv run pytest test/NN_*.py` | Run a specific test file |
|
|
729
805
|
| `uv run nox` | Run tests (C++ then no-C++) across Python 3.11–3.14 |
|
|
730
806
|
| `uv run nox -s tests-3.14` | Run both build variants on a single Python version |
|
|
731
|
-
| `uv run utils/
|
|
807
|
+
| `uv run utils/bench.py` | Run benchmarks (<30s), output `benchmark.md` |
|
|
732
808
|
| `uv run utils/prettify.py` | Format with autoflake + black |
|
|
733
809
|
|
|
734
810
|
For full developer documentation see [DEVELOPMENT.md](DEVELOPMENT.md).
|
|
@@ -741,12 +817,16 @@ Originally inspired by [searoute](https://github.com/genthalili/searoute-py), in
|
|
|
741
817
|
|
|
742
818
|
"""
|
|
743
819
|
|
|
820
|
+
from scgraph.graph_reducer import algorithm
|
|
821
|
+
|
|
744
822
|
try:
|
|
745
823
|
from scgraph.cpp import Graph, CHGraph
|
|
824
|
+
# Assign the python algorithm function to the cpp graph class
|
|
825
|
+
Graph.algorithm = staticmethod(algorithm)
|
|
746
826
|
except ImportError:
|
|
747
827
|
from scgraph.graph import Graph
|
|
748
828
|
from scgraph.contraction_hierarchies import CHGraph
|
|
749
|
-
|
|
829
|
+
|
|
750
830
|
from scgraph.geograph import GeoGraph
|
|
751
831
|
from scgraph.grid import GridGraph
|
|
752
832
|
|