Skip to content

Commit

Permalink
Minor touchups to the beamsearch module (#7059)
Browse files Browse the repository at this point in the history
* MAINT: Rm unnecessary import.

* DOC: Add note about relative performance w/ bfs_edges.

* DOC: Simplify docstring example.

* MAINT: Minor test refactor/parametrization.

* DOC: Fixup note.

* Fix typo.
  • Loading branch information
rossbar committed Oct 31, 2023
1 parent 754bae3 commit 948fdcd
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 56 deletions.
41 changes: 12 additions & 29 deletions networkx/algorithms/traversal/beamsearch.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Basic algorithms for breadth-first searching the nodes of a graph."""
import networkx as nx

from .breadth_first_search import generic_bfs_edges

__all__ = ["bfs_beam_edges"]


Expand All @@ -16,6 +14,15 @@ def bfs_beam_edges(G, source, value, width=None):
heuristic. In general, a beam search with a small beam width might
not visit each node in the graph.
.. note::
With the default value of ``width=None`` or `width` greater than the
maximum degree of the graph, this function equates to a slower
version of `~networkx.algorithms.traversal.breadth_first_search.bfs_edges`.
All nodes will be visited, though the order of the reported edges may
vary. In such cases, `value` has no effect - consider using `bfs_edges`
directly instead.
Parameters
----------
G : NetworkX graph
Expand Down Expand Up @@ -50,28 +57,8 @@ def bfs_beam_edges(G, source, value, width=None):
>>> G = nx.karate_club_graph()
>>> centrality = nx.eigenvector_centrality(G)
>>> source = 0
>>> width = 5
>>> for u, v in nx.bfs_beam_edges(G, source, centrality.get, width):
... print((u, v))
...
(0, 2)
(0, 1)
(0, 8)
(0, 13)
(0, 3)
(2, 32)
(1, 30)
(8, 33)
(3, 7)
(32, 31)
(31, 28)
(31, 25)
(25, 23)
(25, 24)
(23, 29)
(23, 27)
(29, 26)
>>> list(nx.bfs_beam_edges(G, source=0, value=centrality.get, width=3))
[(0, 2), (0, 1), (0, 8), (2, 32), (1, 13), (8, 33)]
"""

if width is None:
Expand All @@ -85,10 +72,6 @@ def successors(v):
The "best" neighbors are chosen according to the `value`
function (higher is better). Only the `width` best neighbors of
`v` are returned.
The list returned by this function is in decreasing value as
measured by the `value` function.
"""
# TODO The Python documentation states that for small values, it
# is better to use `heapq.nlargest`. We should determine the
Expand All @@ -103,4 +86,4 @@ def successors(v):
# `bfs_edges(G, source)` but with a sorted enqueue step.
return iter(sorted(G.neighbors(v), key=value, reverse=True)[:width])

yield from generic_bfs_edges(G, source, successors)
yield from nx.generic_bfs_edges(G, source, successors)
46 changes: 19 additions & 27 deletions networkx/algorithms/traversal/tests/test_beamsearch.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
"""Unit tests for the beam search functions."""
import pytest

import networkx as nx


def identity(x):
return x


class TestBeamSearch:
"""Unit tests for the beam search function."""

def test_narrow(self):
"""Tests that a narrow beam width may cause an incomplete search."""
# In this search, we enqueue only the neighbor 3 at the first
# step, then only the neighbor 2 at the second step. Once at
# node 2, the search chooses node 3, since it has a higher value
# that node 1, but node 3 has already been visited, so the
# search terminates.
G = nx.cycle_graph(4)
edges = nx.bfs_beam_edges(G, 0, identity, width=1)
assert list(edges) == [(0, 3), (3, 2)]

def test_wide(self):
G = nx.cycle_graph(4)
edges = nx.bfs_beam_edges(G, 0, identity, width=2)
assert list(edges) == [(0, 3), (0, 1), (3, 2)]

def test_width_none(self):
G = nx.cycle_graph(4)
edges = nx.bfs_beam_edges(G, 0, identity, width=None)
assert list(edges) == [(0, 3), (0, 1), (3, 2)]
def test_narrow():
"""Tests that a narrow beam width may cause an incomplete search."""
# In this search, we enqueue only the neighbor 3 at the first
# step, then only the neighbor 2 at the second step. Once at
# node 2, the search chooses node 3, since it has a higher value
# than node 1, but node 3 has already been visited, so the
# search terminates.
G = nx.cycle_graph(4)
edges = nx.bfs_beam_edges(G, source=0, value=lambda n: n, width=1)
assert list(edges) == [(0, 3), (3, 2)]


@pytest.mark.parametrize("width", (2, None))
def test_wide(width):
"""All nodes are searched when `width` is None or >= max degree"""
G = nx.cycle_graph(4)
edges = nx.bfs_beam_edges(G, source=0, value=lambda n: n, width=width)
assert list(edges) == [(0, 3), (0, 1), (3, 2)]

0 comments on commit 948fdcd

Please sign in to comment.