Skip to content

Commit

Permalink
Allow shortest path solving with no weights for faster performance wh…
Browse files Browse the repository at this point in the history
…en lengths are not important
  • Loading branch information
geographika committed Apr 23, 2024
1 parent c740e95 commit 032ff3a
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 31 deletions.
18 changes: 12 additions & 6 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,15 @@ def test_get_shortest_edge_mixed_keys():
assert res[0] == "A"


def test_get_shortest_edge_no_length():
edges = {
"A": {"EDGE_ID": "A", "LEN_": 100, "NODEID_FROM": 1, "NODEID_TO": 2},
"B": {"EDGE_ID": "B", "LEN_": 20, "NODEID_FROM": 2, "NODEID_TO": 3},
}
res = functions.get_shortest_edge(edges, length_field=None)
assert res[0] == "A"


def test_get_unique_ordered_list():
lst = [2, 2, 1, 1, 4, 4, 1, 2, 3]
res = functions.get_unique_ordered_list(lst)
Expand Down Expand Up @@ -470,7 +479,6 @@ def test_has_no_overlaps_loop_with_split():


def test_add_edge():

net = networkx.MultiGraph()
edge = Edge(0, 1, "A", {"EDGE_ID": 1, "OFFSET": 5, "LEN_": 10})
functions.add_edge(net, edge.start_node, edge.end_node, edge.key, edge.attributes)
Expand All @@ -495,7 +503,6 @@ def test_add_single_edge():


def test_remove_edge():

net = networkx.MultiGraph()
edge = Edge(0, 1, "A", {"EDGE_ID": 1, "OFFSET": 5, "LEN_": 10})
net.add_edge(edge.start_node, edge.end_node, edge.key, **edge.attributes)
Expand All @@ -505,7 +512,6 @@ def test_remove_edge():


def test_remove_edge_and_key():

net = loader.create_graph()
edge = Edge(0, 1, "A", {"EDGE_ID": 1, "OFFSET": 5, "LEN_": 10})
functions.add_edge(net, **edge._asdict())
Expand All @@ -517,7 +523,6 @@ def test_remove_edge_and_key():


def test_remove_edge_by_key():

net = loader.create_graph()
edge = Edge(0, 1, "A", {"EDGE_ID": 1, "OFFSET": 5, "LEN_": 10})
functions.add_edge(net, **edge._asdict())
Expand Down Expand Up @@ -561,6 +566,7 @@ def test_doctest():
# test_remove_edge()
# test_remove_edge_and_key()
# test_remove_edge_by_key()
test_add_edge_shorthand()
test_add_single_edge()
# test_add_edge_shorthand()
# test_add_single_edge()
test_get_shortest_edge_no_length()
print("Done!")
20 changes: 9 additions & 11 deletions tests/test_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,18 @@


def test_solve_all_simple_paths():

net = networks.simple_network()
simple_paths = routing.solve_all_simple_paths(net, 1, 5)
assert list(simple_paths) == [[1, 2, 3, 4, 5]]


def test_solve_all_simple_paths_no_node():

net = networks.simple_network()
with pytest.raises(NodeNotFound):
list(routing.solve_all_simple_paths(net, 1, 99))


def test_solve_all_simple_paths_cutoff():

net = networks.simple_network()
simple_paths = routing.solve_all_simple_paths(net, 1, 5, cutoff=3)
assert list(simple_paths) == []
Expand All @@ -36,15 +33,13 @@ def test_solve_all_simple_paths_cutoff():


def test_solve_all_shortest_paths():

net = networks.circle_network()
all_paths = routing.solve_all_shortest_paths(net, 1, 3)
# print(list(all_paths))
assert list(all_paths) == [[1, 2, 3], [1, 4, 3]]


def test_solve_all_shortest_paths_no_node():

net = networks.circle_network()
# it seems NetworkXNoPath is raised rather than NodeNotFound
# when using all_shortest_paths in networkx
Expand All @@ -54,31 +49,34 @@ def test_solve_all_shortest_paths_no_node():


def test_get_path_ends():

edges = [Edge(0, 1, 1, {}), Edge(1, 2, 1, {})]
ends = routing.get_path_ends(edges)
# print(ends)
assert ends == (0, 2)


def test_get_path_ends_single_edge():

edges = [Edge(0, 1, 1, {})]
ends = routing.get_path_ends(edges)
# print(ends)
assert ends == (0, 1)


def test_solve_shortest_path():

net = networks.simple_network()
edges = routing.solve_shortest_path(net, start_node=1, end_node=5)
edge_ids = [edge.key for edge in edges]
assert edge_ids == [1, 2, 3, 4]


def test_solve_shortest_path_directions():
def test_solve_shortest_path_no_weight():
net = networks.simple_network()
edges = routing.solve_shortest_path(net, start_node=1, end_node=5, weight=None)
edge_ids = [edge.key for edge in edges]
assert edge_ids == [1, 2, 3, 4]


def test_solve_shortest_path_directions():
net = networks.simple_network()
edges = routing.solve_shortest_path(net, start_node=1, end_node=5)

Expand All @@ -92,7 +90,6 @@ def test_solve_shortest_path_directions():


def test_solve_shortest_path_split_network():

net = networks.simple_network()

splitter.split_network_edge(net, 3, [2, 8])
Expand Down Expand Up @@ -122,12 +119,13 @@ def test_doctest():
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
# test_doctest()
test_solve_all_simple_paths_no_node()
# test_solve_all_simple_paths_no_node()
# test_solve_all_simple_paths_cutoff()
# test_solve_all_shortest_paths()
# test_solve_all_shortest_paths_no_node()
# test_get_path_ends()
# test_get_path_ends_single_edge()
# test_solve_shortest_path_directions()
# test_solve_shortest_path_split_network()
test_solve_shortest_path_no_weight()
print("Done!")
20 changes: 12 additions & 8 deletions wayfarer/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,8 @@ def get_all_paths_from_nodes(net, node_list, with_direction_flag=False):
return itertools.chain(*all_paths)


def get_path_length(path_edges):
return sum([edge.attributes[LENGTH_FIELD] for edge in path_edges])
def get_path_length(path_edges, length_field: str = LENGTH_FIELD):
return sum([edge.attributes[length_field] for edge in path_edges])


def get_edges_from_node_pair(
Expand Down Expand Up @@ -380,7 +380,7 @@ def get_edges_from_nodes(
net: networkx.MultiGraph | networkx.MultiDiGraph,
node_list: list[int | str],
with_direction_flag: bool = False,
length_field: str = LENGTH_FIELD,
length_field: str | None = LENGTH_FIELD,
return_unique: bool = True,
shortest_path_only: bool = True,
) -> list[Edge]:
Expand Down Expand Up @@ -427,7 +427,7 @@ def get_edges_from_nodes(
return edge_list


def get_shortest_edge(edges: dict, length_field=LENGTH_FIELD):
def get_shortest_edge(edges: dict, length_field: str | None = LENGTH_FIELD):
"""
From a dictionary of edges, get the shortest edge
by its length
Expand All @@ -438,10 +438,14 @@ def get_shortest_edge(edges: dict, length_field=LENGTH_FIELD):
if not edges:
raise ValueError("The edge list is empty")

min_key = min(
edges, key=lambda k: edges[k][length_field]
) # py3 can add default=None
return min_key, edges[min_key]
if length_field is None:
# simply return the first edge
return next(iter(edges)), edges[next(iter(edges))]
else:
min_key = min(
edges, key=lambda k: edges[k][length_field]
) # py3 can add default=None
return min_key, edges[min_key]


def add_direction_flag(start_node, end_node, attributes, **kwargs):
Expand Down
15 changes: 9 additions & 6 deletions wayfarer/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ def solve_shortest_path(
start_node: str | int,
end_node: str | int,
with_direction_flag: bool = True,
weight: str = LENGTH_FIELD,
weight: str | None = LENGTH_FIELD,
):
"""
Solve the shortest path between two nodes, returning a list of Edge objects
Solve the shortest path between two nodes, returning a list of Edge objects.
Set weight to the attribute name for deciding the shortest path, or to None
to ignore any weightings (faster).
"""
nodes = solve_shortest_path_from_nodes(net, [start_node, end_node], weight)
return functions.get_edges_from_nodes(
Expand All @@ -32,16 +34,16 @@ def solve_shortest_path(
def solve_shortest_path_from_nodes(
net: networkx.MultiGraph | networkx.MultiDiGraph,
node_list: list[int | str],
weight: str = LENGTH_FIELD,
weight: str | None = LENGTH_FIELD,
) -> list[int | str]:
"""
Return a list of nodes found by solving from each node in node_list to
the next
the next. Set weight to the attribute name for deciding the shortest path, or to None
to ignore any weightings (faster).
"""
nodes_in_path = []

for start_node, end_node in functions.pairwise(node_list):

log.debug("Solving from %s to %s", start_node, end_node)

if start_node == end_node:
Expand Down Expand Up @@ -361,8 +363,9 @@ def solve_all_shortest_paths(
>>> pths = solve_all_shortest_paths(net, 0, 2)
>>> print(list(pths))
[[0, 1, 2]]
"""
# noqa: E501
"""

all_shortest_paths = []

Expand Down

0 comments on commit 032ff3a

Please sign in to comment.