beamsearch.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. """Basic algorithms for breadth-first searching the nodes of a graph."""
  2. from .breadth_first_search import generic_bfs_edges
  3. __all__ = ["bfs_beam_edges"]
  4. def bfs_beam_edges(G, source, value, width=None):
  5. """Iterates over edges in a beam search.
  6. The beam search is a generalized breadth-first search in which only
  7. the "best" *w* neighbors of the current node are enqueued, where *w*
  8. is the beam width and "best" is an application-specific
  9. heuristic. In general, a beam search with a small beam width might
  10. not visit each node in the graph.
  11. Parameters
  12. ----------
  13. G : NetworkX graph
  14. source : node
  15. Starting node for the breadth-first search; this function
  16. iterates over only those edges in the component reachable from
  17. this node.
  18. value : function
  19. A function that takes a node of the graph as input and returns a
  20. real number indicating how "good" it is. A higher value means it
  21. is more likely to be visited sooner during the search. When
  22. visiting a new node, only the `width` neighbors with the highest
  23. `value` are enqueued (in decreasing order of `value`).
  24. width : int (default = None)
  25. The beam width for the search. This is the number of neighbors
  26. (ordered by `value`) to enqueue when visiting each new node.
  27. Yields
  28. ------
  29. edge
  30. Edges in the beam search starting from `source`, given as a pair
  31. of nodes.
  32. Examples
  33. --------
  34. To give nodes with, for example, a higher centrality precedence
  35. during the search, set the `value` function to return the centrality
  36. value of the node:
  37. >>> G = nx.karate_club_graph()
  38. >>> centrality = nx.eigenvector_centrality(G)
  39. >>> source = 0
  40. >>> width = 5
  41. >>> for u, v in nx.bfs_beam_edges(G, source, centrality.get, width):
  42. ... print((u, v))
  43. ...
  44. (0, 2)
  45. (0, 1)
  46. (0, 8)
  47. (0, 13)
  48. (0, 3)
  49. (2, 32)
  50. (1, 30)
  51. (8, 33)
  52. (3, 7)
  53. (32, 31)
  54. (31, 28)
  55. (31, 25)
  56. (25, 23)
  57. (25, 24)
  58. (23, 29)
  59. (23, 27)
  60. (29, 26)
  61. """
  62. if width is None:
  63. width = len(G)
  64. def successors(v):
  65. """Returns a list of the best neighbors of a node.
  66. `v` is a node in the graph `G`.
  67. The "best" neighbors are chosen according to the `value`
  68. function (higher is better). Only the `width` best neighbors of
  69. `v` are returned.
  70. The list returned by this function is in decreasing value as
  71. measured by the `value` function.
  72. """
  73. # TODO The Python documentation states that for small values, it
  74. # is better to use `heapq.nlargest`. We should determine the
  75. # threshold at which its better to use `heapq.nlargest()`
  76. # instead of `sorted()[:]` and apply that optimization here.
  77. #
  78. # If `width` is greater than the number of neighbors of `v`, all
  79. # neighbors are returned by the semantics of slicing in
  80. # Python. This occurs in the special case that the user did not
  81. # specify a `width`: in this case all neighbors are always
  82. # returned, so this is just a (slower) implementation of
  83. # `bfs_edges(G, source)` but with a sorted enqueue step.
  84. return iter(sorted(G.neighbors(v), key=value, reverse=True)[:width])
  85. yield from generic_bfs_edges(G, source, successors)