Skip to content

Conversation

@amcandio
Copy link
Contributor

@amcandio amcandio commented Jul 30, 2025

Summary

This is a follow up from discussion on #703. It adds documentation notes to the shortest_paths.rst file explaining how to perform multi-target shortest path queries in NetworkX using a sentinel node trick. While NetworkX does not currently provide a built-in method for computing the shortest path from a source to the nearest of several targets, the documented technique enables users to efficiently perform such queries using existing shortest path algorithms.

Motivation

Multi-target queries—such as finding the nearest facility, emergency center, or pickup point—are common in real-world applications. This note provides guidance on how to implement these queries in NetworkX using an elegant and general approach.

Rendering

image image image

Validations

This test-case is passing and I can send in a separate PR to ease reviewing:

    def test_sentinel_trick_all_algorithms(self):

        def reconstruct_path(pred, source, target):
            path = [target]
            while path[-1] != source:
                path.append(pred[path[-1]])
            return list(reversed(path))

        # Build the test graph inline
        G = nx.Graph()
        G.add_edge("A", "B", weight=1)
        G.add_edge("B", "C", weight=1)
        G.add_edge("C", "D", weight=1)
        G.add_edge("D", "E", weight=1)

        source = "A"
        targets = {"C", "D", "E"}
        expected_target = "C"  # A-B-C is closest

        sentinel = "_sentinel_"
        G.add_node(sentinel)
        for t in targets:
            G.add_edge(t, sentinel, weight=0)

        # shortest_path: Dijkstra (default)
        path = nx.shortest_path(G, source=source, target=sentinel, weight="weight")
        assert path[-2] == expected_target

        # shortest_path: Bellman-Ford
        path = nx.shortest_path(G, source=source, target=sentinel, weight="weight", method="bellman-ford")
        assert path[-2] == expected_target

        # shortest_path: Unweighted (BFS)
        path = nx.shortest_path(G, source=source, target=sentinel, weight=None)
        assert path[-2] == expected_target

        # bidirectional_dijkstra
        _, path = nx.bidirectional_dijkstra(G, source, sentinel, weight="weight")
        assert path[-2] == expected_target

        # goldberg_radzik
        pred, _ = nx.goldberg_radzik(G, source, weight="weight")
        path = reconstruct_path(pred, source, sentinel)
        assert path[-2] == expected_target

        # astar_path with zero heuristic
        path = nx.astar_path(G, source, sentinel, heuristic=lambda u, v: 0, weight="weight")
        assert path[-2] == expected_target

        # johnson (all-pairs shortest paths)
        paths = nx.johnson(G, weight="weight")
        assert paths[source][sentinel][-2] == expected_target

        # floyd_warshall_predecessor_and_distance
        pred, _ = nx.floyd_warshall_predecessor_and_distance(G, weight="weight")
        path = reconstruct_path(pred[source], source, sentinel)
        assert path[-2] == expected_target

Copy link
Member

@dschult dschult left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really nice @amcandio Thanks very much!!

I don't see any typos. I guess we should use double-ticks for code symbols. And we should wrap lines to about 80 chars so the .rst file is easily readable without formatting.

I really like the simple description and the choice of example. :)

@amcandio
Copy link
Contributor Author

Thanks @dschult! I replaced the code symbols by math symbols, reduced the line-lenght (except for the numerated lists, where it breaks) and also added some details on how to recover the true distance.

Copy link
Contributor

@rossbar rossbar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an excellent addition to the docs @amcandio , thanks very much! There may be ways we can consider to increase visibility, e.g. adding references to this section to See Also portions of path-finding docstrings (at least those with target kwargs) but that can be addressed as needed/desired separately!

@rossbar rossbar merged commit 96c2e50 into networkx:main Jul 30, 2025
45 checks passed
@amcandio amcandio deleted the multitarget_shortest_path branch July 30, 2025 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants