Skip to content

Commit

Permalink
splits dijkstra
Browse files Browse the repository at this point in the history
  • Loading branch information
songololo committed Feb 4, 2024
1 parent b22d07b commit 8899422
Show file tree
Hide file tree
Showing 18 changed files with 423 additions and 231 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish_package.yml
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"] # Add Python versions you want to test
python-version: ["3.10", "3.11", "3.12"]
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish_package_dev.yml
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"] # Add Python versions you want to test
python-version: ["3.10", "3.11", "3.12"]
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down
Binary file modified docs/public/images/graph_cleaning_1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_cleaning_1b.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_cleaning_2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_cleaning_3.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_cleaning_4.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_cleaning_5.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_cleaning_6.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/src/pages/intro.md
Expand Up @@ -20,7 +20,7 @@ The github repository is available at [github.com/benchmark-urbanism/cityseer-ap
pip install cityseer
```

Code tests are run against `python 3.10`, though the code base will generally be compatible with Python 3.8+.
Code tests are run against Python versions `3.10` - `3.12`.

## Notebooks

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "cityseer"
version = '4.10.3'
version = '4.11.0'
description = "Computational tools for network-based pedestrian-scale urban analysis"
readme = "README.md"
requires-python = ">=3.10, <3.13"
Expand Down
10 changes: 8 additions & 2 deletions pysrc/cityseer/rustalgos.pyi
Expand Up @@ -502,8 +502,14 @@ class NetworkStructure:
self, data_coord: Any, pred_map: list[int | None], last_nd_idx: int
) -> tuple[float, int | None, int | None]: ...
def assign_to_network(self, data_coord: Any, max_dist: float) -> tuple[int | None, int | None]: ...
def shortest_path_tree(
self, src_idx: int, max_dist: int, angular: bool | None = None, jitter_scale: float | None = None
def dijkstra_tree_shortest(
self, src_idx: int, max_dist: int, jitter_scale: float | None = None
) -> tuple[list[int], list[NodeVisit]]: ...
def dijkstra_tree_simplest(
self, src_idx: int, max_dist: int, jitter_scale: float | None = None
) -> tuple[list[int], list[NodeVisit]]: ...
def dijkstra_tree_segment(
self, src_idx: int, max_dist: int, jitter_scale: float | None = None
) -> tuple[list[int], list[int], list[NodeVisit], list[EdgeVisit]]: ...
def local_node_centrality_shortest(
self,
Expand Down
518 changes: 335 additions & 183 deletions src/centrality.rs

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions src/data.rs
Expand Up @@ -207,8 +207,11 @@ impl DataMap {
// this is for sifting out the closest entry point
let mut nearest_ids: HashMap<String, (String, f32)> = HashMap::new();
// shortest paths
let (_visited_nodes, _visited_edges, tree_map, _edge_map) = network_structure
.shortest_path_tree(netw_src_idx, max_dist, Some(angular), Some(jitter_scale));
let (_visited_nodes, tree_map) = if !angular {
network_structure.dijkstra_tree_shortest(netw_src_idx, max_dist, Some(jitter_scale))
} else {
network_structure.dijkstra_tree_simplest(netw_src_idx, max_dist, Some(jitter_scale))
};
// iterate data entries
for (data_key, data_val) in &self.entries {
let mut nearest_total_dist = f32::INFINITY;
Expand Down
11 changes: 4 additions & 7 deletions src/graph.rs
Expand Up @@ -60,6 +60,8 @@ pub struct NodeVisit {
#[pyo3(get)]
pub visited: bool,
#[pyo3(get)]
pub discovered: bool,
#[pyo3(get)]
pub pred: Option<usize>,
#[pyo3(get)]
pub short_dist: f32,
Expand All @@ -80,6 +82,7 @@ impl NodeVisit {
pub fn new() -> Self {
Self {
visited: false,
discovered: false,
pred: None,
short_dist: f32::INFINITY,
simpl_dist: f32::INFINITY,
Expand Down Expand Up @@ -692,12 +695,7 @@ mod tests {
120.0,
120.0,
);
let (visited_nodes, visited_edges, tree_map, edge_map) =
ns.shortest_path_tree(0, 5, None, None);
let (visited_nodes, visited_edges, tree_map, edge_map) =
ns.shortest_path_tree(0, 5, None, Some(1.0));
let (visited_nodes, visited_edges, tree_map, edge_map) =
ns.shortest_path_tree(0, 5, None, Some(10.0));
let (visited_nodes, tree_map) = ns.dijkstra_tree_shortest(0, 5, None);
// let close_result = ns.local_node_centrality_shortest(
// Some(vec![50]),
// None,
Expand All @@ -716,7 +714,6 @@ mod tests {
// None,
// None,
// );
let a = 1;
// assert_eq!(add(2, 2), 4);
}
}
36 changes: 13 additions & 23 deletions tests/rustalgos/test_centrality.py
Expand Up @@ -160,7 +160,7 @@ def find_path(start_idx, target_idx, tree_map):
return list(reversed(s_path))


def test_shortest_path_tree(primal_graph, dual_graph):
def test_shortest_path_trees(primal_graph, dual_graph):
nodes_gdf_p, edges_gdf_p, network_structure_p = io.network_structure_from_nx(primal_graph, 3395)
# prepare round-trip graph for checks
G_round_trip = io.nx_from_cityseer_geopandas(nodes_gdf_p, edges_gdf_p)
Expand All @@ -169,10 +169,9 @@ def test_shortest_path_tree(primal_graph, dual_graph):
for max_dist in [0, 500, 2000, 5000]:
for src_idx in range(len(primal_graph)):
# check shortest path maps
_visited_nodes, _visited_edges, tree_map, _edge_map = network_structure_p.shortest_path_tree(
_visited_nodes, tree_map = network_structure_p.dijkstra_tree_shortest(
src_idx,
max_dist,
angular=False,
)
# compare against networkx dijkstra
nx_dist, nx_path = nx.single_source_dijkstra(G_round_trip, str(src_idx), weight="length", cutoff=max_dist)
Expand All @@ -190,14 +189,13 @@ def test_shortest_path_tree(primal_graph, dual_graph):
if src_idx >= 49:
continue
# no jitter
_visited_nodes, _visited_edges, tree_map, _edge_map = network_structure_p.shortest_path_tree(
_visited_nodes, tree_map = network_structure_p.dijkstra_tree_shortest(
src_idx,
max_dist,
angular=False,
)
# with jitter
_visited_nodes_j, _visited_edges_j, tree_map_j, _edge_map_j = network_structure_j.shortest_path_tree(
src_idx, max_dist, angular=False, jitter_scale=jitter
_visited_nodes_j, tree_map_j = network_structure_j.dijkstra_tree_shortest(
src_idx, max_dist, jitter_scale=jitter
)
for to_idx in range(len(primal_graph)):
if to_idx >= 49:
Expand All @@ -211,9 +209,7 @@ def test_shortest_path_tree(primal_graph, dual_graph):
# test all shortest distance calculations against networkX
for src_idx in range(len(G_round_trip)):
shortest_dists = nx.shortest_path_length(G_round_trip, str(src_idx), weight="length")
_visted_nodes, _visited_edges, tree_map, _edge_map = network_structure_p.shortest_path_tree(
src_idx, 5000, jitter_scale=0.0, angular=False
)
_visted_nodes, tree_map = network_structure_p.dijkstra_tree_shortest(src_idx, 5000, jitter_scale=0.0)
for target_idx in range(len(G_round_trip)):
if str(target_idx) not in shortest_dists:
continue
Expand All @@ -234,26 +230,23 @@ def test_shortest_path_tree(primal_graph, dual_graph):
p_target_idx = nodes_gdf_p.index.tolist().index(p_target)
d_source_idx = nodes_gdf_d.index.tolist().index(d_source) # dual source index changes depending on direction
d_target_idx = nodes_gdf_d.index.tolist().index(d_target)
_visited_nodes_p, _visited_edges_p, tree_map_p, _edge_map_p = network_structure_p.shortest_path_tree(
_visited_nodes_p, tree_map_p = network_structure_p.dijkstra_tree_simplest(
p_source_idx,
5000,
angular=True,
)
_visited_nodes_d, _visited_edges_d, tree_map_d, _edge_map_d = network_structure_d.shortest_path_tree(
_visited_nodes_d, tree_map_d = network_structure_d.dijkstra_tree_simplest(
d_source_idx,
5000,
angular=True,
)
assert tree_map_p[p_target_idx].simpl_dist - tree_map_d[d_target_idx].simpl_dist < config.ATOL
# angular impedance should take a simpler but longer path - test basic case on dual
# source and target are the same for either
src_idx = nodes_gdf_d.index.tolist().index("11_6_k0")
target = nodes_gdf_d.index.tolist().index("39_40_k0")
# SIMPLEST PATH: get simplest path tree using angular impedance
_visited_nodes_d2, _visited_edges_d2, tree_map_d2, _edge_map_d2 = network_structure_d.shortest_path_tree(
_visited_nodes_d2, tree_map_d2 = network_structure_d.dijkstra_tree_simplest(
src_idx,
5000,
angular=True,
)
# find path
path = find_path(target, src_idx, tree_map_d2)
Expand All @@ -274,10 +267,9 @@ def test_shortest_path_tree(primal_graph, dual_graph):
# get shortest path tree using non angular impedance
# this should cut through central node
# would otherwise have used outside periphery route if using simplest path
_visited_nodes_d3, _visited_edges_d3, tree_map_d3, _edge_map_d3 = network_structure_d.shortest_path_tree(
_visited_nodes_d3, tree_map_d3 = network_structure_d.dijkstra_tree_shortest(
src_idx,
5000,
angular=False,
)
# find path
path = find_path(target, src_idx, tree_map_d3)
Expand All @@ -301,10 +293,9 @@ def test_shortest_path_tree(primal_graph, dual_graph):
# NO SIDESTEPS - explicit check that sidesteps are prevented
src_idx = nodes_gdf_d.index.tolist().index("10_43_k0")
target = nodes_gdf_d.index.tolist().index("10_5_k0")
_visited_nodes_d4, _visited_edges_d4, tree_map_d4, _edge_map_d4 = network_structure_d.shortest_path_tree(
_visited_nodes_d4, tree_map_d4 = network_structure_d.dijkstra_tree_simplest(
src_idx,
5000,
angular=True,
)
# find path
path = find_path(target, src_idx, tree_map_d4)
Expand All @@ -327,10 +318,9 @@ def test_shortest_path_tree(primal_graph, dual_graph):
edges_gdf_d.loc[idx].in_bearing,
edges_gdf_d.loc[idx].out_bearing,
)
_visited_nodes_d5, _visited_edges_d5, tree_map_d5, _edge_map_d5 = network_structure_d.shortest_path_tree(
_visited_nodes_d5, tree_map_d5 = network_structure_d.dijkstra_tree_shortest(
src_idx,
5000,
angular=False,
)
# find path
path = find_path(target, src_idx, tree_map_d5)
Expand Down Expand Up @@ -395,7 +385,7 @@ def test_local_node_centrality_shortest(primal_graph):
cyc: npt.NDArray[np.float32] = np.full((d_n, n_nodes), 0.0, dtype=np.float32)
for src_idx in range(n_nodes):
# get shortest path maps
visited_nodes, _visited_edges, tree_map, _edge_map = network_structure.shortest_path_tree(src_idx, 5000)
visited_nodes, tree_map = network_structure.dijkstra_tree_shortest(src_idx, 5000)
for to_idx in visited_nodes:
# skip self nodes
if to_idx == src_idx:
Expand Down
7 changes: 4 additions & 3 deletions tests/rustalgos/test_data.py
Expand Up @@ -36,9 +36,10 @@ def test_aggregate_to_src_idx(primal_graph):
)
# compare to manual checks on distances:
# get the network distances
_nodes, _edges, tree_map, _edge_map = network_structure.shortest_path_tree(
netw_src_idx, max_dist, angular
)
if angular is False:
_nodes, tree_map = network_structure.dijkstra_tree_shortest(netw_src_idx, max_dist)
else:
_nodes, tree_map = network_structure.dijkstra_tree_simplest(netw_src_idx, max_dist)
# verify distances vs. the max
for data_key, data_entry in data_map.entries.items():
# nearest
Expand Down
57 changes: 50 additions & 7 deletions tests/test_performance.py
Expand Up @@ -45,6 +45,23 @@ def test_local_centrality_time(primal_graph):
- Tests on using a List(Dict('x', 'y', etc.) structure proved almost four times slower, so sticking with arrays
- Experiments with golang proved too complex re: bindings...
- Ended up with rust
Rust
shortest_path_tree_wrapper: 0.28707868605852127 for 10000 iterations
node_cent_wrapper: 3.1882867829408497 for 10000 iterations
segment_cent_wrapper: 5.971783181885257 for 10000 iterations
Heap
shortest_path_tree_wrapper: 0.2747780899517238 for 10000 iterations
node_cent_wrapper: 3.095424270024523 for 10000 iterations
segment_cent_wrapper: 5.882331402972341 for 10000 iterations
Split functions
dijkstra_tree_shortest_wrapper: 0.11545719904825091 for 10000 iterations
dijkstra_tree_simplest_wrapper: 0.10305578005500138 for 10000 iterations
dijkstra_tree_segment_wrapper: 0.2831501439213753 for 10000 iterations
node_cent_wrapper: 3.0920922509394586 for 10000 iterations
segment_cent_wrapper: 5.607312946114689 for 10000 iterations
"""

if "GITHUB_ACTIONS" in os.environ:
Expand All @@ -55,21 +72,47 @@ def test_local_centrality_time(primal_graph):
# needs a large enough beta so that distance thresholds aren't encountered
distances, _betas = rustalgos.pair_distances_and_betas(distances=[5000])

def shortest_path_tree_wrapper():
network_structure.shortest_path_tree(
def dijkstra_tree_shortest_wrapper():
network_structure.dijkstra_tree_shortest(
src_idx=0,
max_dist=5000,
)

# prime the function
dijkstra_tree_shortest_wrapper()
iters = 10000
# time and report
func_time = timeit.timeit(dijkstra_tree_shortest_wrapper, number=iters)
print(f"dijkstra_tree_shortest_wrapper: {func_time} for {iters} iterations")
assert func_time < 1

def dijkstra_tree_simplest_wrapper():
network_structure.dijkstra_tree_simplest(
src_idx=0,
max_dist=5000,
)

# prime the function
dijkstra_tree_simplest_wrapper()
iters = 10000
# time and report
func_time = timeit.timeit(dijkstra_tree_simplest_wrapper, number=iters)
print(f"dijkstra_tree_simplest_wrapper: {func_time} for {iters} iterations")
assert func_time < 1

def dijkstra_tree_segment_wrapper():
network_structure.dijkstra_tree_segment(
src_idx=0,
max_dist=5000,
angular=False,
)

# prime the function
shortest_path_tree_wrapper()
dijkstra_tree_segment_wrapper()
iters = 10000
# time and report
func_time = timeit.timeit(shortest_path_tree_wrapper, number=iters)
print(f"shortest_path_tree_wrapper: {func_time} for {iters} iterations")
func_time = timeit.timeit(dijkstra_tree_segment_wrapper, number=iters)
print(f"dijkstra_tree_segment_wrapper: {func_time} for {iters} iterations")
assert func_time < 1
# shortest_path_tree_wrapper: 0.2906692470423877 for 10000 iterations

def node_cent_wrapper():
network_structure.local_node_centrality_shortest(
Expand Down

0 comments on commit 8899422

Please sign in to comment.