cycles.py 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234
  1. """
  2. ========================
  3. Cycle finding algorithms
  4. ========================
  5. """
  6. from collections import defaultdict
  7. from itertools import combinations, product
  8. from math import inf
  9. import networkx as nx
  10. from networkx.utils import not_implemented_for, pairwise
  11. __all__ = [
  12. "cycle_basis",
  13. "simple_cycles",
  14. "recursive_simple_cycles",
  15. "find_cycle",
  16. "minimum_cycle_basis",
  17. "chordless_cycles",
  18. "girth",
  19. ]
  20. @not_implemented_for("directed")
  21. @not_implemented_for("multigraph")
  22. @nx._dispatchable
  23. def cycle_basis(G, root=None):
  24. """Returns a list of cycles which form a basis for cycles of G.
  25. A basis for cycles of a network is a minimal collection of
  26. cycles such that any cycle in the network can be written
  27. as a sum of cycles in the basis. Here summation of cycles
  28. is defined as "exclusive or" of the edges. Cycle bases are
  29. useful, e.g. when deriving equations for electric circuits
  30. using Kirchhoff's Laws.
  31. Parameters
  32. ----------
  33. G : NetworkX Graph
  34. root : node, optional
  35. Specify starting node for basis.
  36. Returns
  37. -------
  38. A list of cycle lists. Each cycle list is a list of nodes
  39. which forms a cycle (loop) in G.
  40. Examples
  41. --------
  42. >>> G = nx.Graph()
  43. >>> nx.add_cycle(G, [0, 1, 2, 3])
  44. >>> nx.add_cycle(G, [0, 3, 4, 5])
  45. >>> nx.cycle_basis(G, 0)
  46. [[3, 4, 5, 0], [1, 2, 3, 0]]
  47. Notes
  48. -----
  49. This is adapted from algorithm CACM 491 [1]_.
  50. References
  51. ----------
  52. .. [1] Paton, K. An algorithm for finding a fundamental set of
  53. cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518.
  54. See Also
  55. --------
  56. simple_cycles
  57. minimum_cycle_basis
  58. """
  59. gnodes = dict.fromkeys(G) # set-like object that maintains node order
  60. cycles = []
  61. while gnodes: # loop over connected components
  62. if root is None:
  63. root = gnodes.popitem()[0]
  64. stack = [root]
  65. pred = {root: root}
  66. used = {root: set()}
  67. while stack: # walk the spanning tree finding cycles
  68. z = stack.pop() # use last-in so cycles easier to find
  69. zused = used[z]
  70. for nbr in G[z]:
  71. if nbr not in used: # new node
  72. pred[nbr] = z
  73. stack.append(nbr)
  74. used[nbr] = {z}
  75. elif nbr == z: # self loops
  76. cycles.append([z])
  77. elif nbr not in zused: # found a cycle
  78. pn = used[nbr]
  79. cycle = [nbr, z]
  80. p = pred[z]
  81. while p not in pn:
  82. cycle.append(p)
  83. p = pred[p]
  84. cycle.append(p)
  85. cycles.append(cycle)
  86. used[nbr].add(z)
  87. for node in pred:
  88. gnodes.pop(node, None)
  89. root = None
  90. return cycles
  91. @nx._dispatchable
  92. def simple_cycles(G, length_bound=None):
  93. """Find simple cycles (elementary circuits) of a graph.
  94. A "simple cycle", or "elementary circuit", is a closed path where
  95. no node appears twice. In a directed graph, two simple cycles are distinct
  96. if they are not cyclic permutations of each other. In an undirected graph,
  97. two simple cycles are distinct if they are not cyclic permutations of each
  98. other nor of the other's reversal.
  99. Optionally, the cycles are bounded in length. In the unbounded case, we use
  100. a nonrecursive, iterator/generator version of Johnson's algorithm [1]_. In
  101. the bounded case, we use a version of the algorithm of Gupta and
  102. Suzumura [2]_. There may be better algorithms for some cases [3]_ [4]_ [5]_.
  103. The algorithms of Johnson, and Gupta and Suzumura, are enhanced by some
  104. well-known preprocessing techniques. When `G` is directed, we restrict our
  105. attention to strongly connected components of `G`, generate all simple cycles
  106. containing a certain node, remove that node, and further decompose the
  107. remainder into strongly connected components. When `G` is undirected, we
  108. restrict our attention to biconnected components, generate all simple cycles
  109. containing a particular edge, remove that edge, and further decompose the
  110. remainder into biconnected components.
  111. Note that multigraphs are supported by this function -- and in undirected
  112. multigraphs, a pair of parallel edges is considered a cycle of length 2.
  113. Likewise, self-loops are considered to be cycles of length 1. We define
  114. cycles as sequences of nodes; so the presence of loops and parallel edges
  115. does not change the number of simple cycles in a graph.
  116. Parameters
  117. ----------
  118. G : NetworkX Graph
  119. A networkx graph. Undirected, directed, and multigraphs are all supported.
  120. length_bound : int or None, optional (default=None)
  121. If `length_bound` is an int, generate all simple cycles of `G` with length at
  122. most `length_bound`. Otherwise, generate all simple cycles of `G`.
  123. Yields
  124. ------
  125. list of nodes
  126. Each cycle is represented by a list of nodes along the cycle.
  127. Examples
  128. --------
  129. >>> G = nx.DiGraph([(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)])
  130. >>> sorted(nx.simple_cycles(G))
  131. [[0], [0, 1, 2], [0, 2], [1, 2], [2]]
  132. To filter the cycles so that they don't include certain nodes or edges,
  133. copy your graph and eliminate those nodes or edges before calling.
  134. For example, to exclude self-loops from the above example:
  135. >>> H = G.copy()
  136. >>> H.remove_edges_from(nx.selfloop_edges(G))
  137. >>> sorted(nx.simple_cycles(H))
  138. [[0, 1, 2], [0, 2], [1, 2]]
  139. Notes
  140. -----
  141. When `length_bound` is None, the time complexity is $O((n+e)(c+1))$ for $n$
  142. nodes, $e$ edges and $c$ simple circuits. Otherwise, when ``length_bound > 1``,
  143. the time complexity is $O((c+n)(k-1)d^k)$ where $d$ is the average degree of
  144. the nodes of `G` and $k$ = `length_bound`.
  145. Raises
  146. ------
  147. ValueError
  148. when ``length_bound < 0``.
  149. References
  150. ----------
  151. .. [1] Finding all the elementary circuits of a directed graph.
  152. D. B. Johnson, SIAM Journal on Computing 4, no. 1, 77-84, 1975.
  153. https://doi.org/10.1137/0204007
  154. .. [2] Finding All Bounded-Length Simple Cycles in a Directed Graph
  155. A. Gupta and T. Suzumura https://arxiv.org/abs/2105.10094
  156. .. [3] Enumerating the cycles of a digraph: a new preprocessing strategy.
  157. G. Loizou and P. Thanish, Information Sciences, v. 27, 163-182, 1982.
  158. .. [4] A search strategy for the elementary cycles of a directed graph.
  159. J.L. Szwarcfiter and P.E. Lauer, BIT NUMERICAL MATHEMATICS,
  160. v. 16, no. 2, 192-204, 1976.
  161. .. [5] Optimal Listing of Cycles and st-Paths in Undirected Graphs
  162. R. Ferreira and R. Grossi and A. Marino and N. Pisanti and R. Rizzi and
  163. G. Sacomoto https://arxiv.org/abs/1205.2766
  164. See Also
  165. --------
  166. cycle_basis
  167. chordless_cycles
  168. """
  169. if length_bound is not None:
  170. if length_bound == 0:
  171. return
  172. elif length_bound < 0:
  173. raise ValueError("length bound must be non-negative")
  174. directed = G.is_directed()
  175. yield from ([v] for v, Gv in G.adj.items() if v in Gv)
  176. if length_bound is not None and length_bound == 1:
  177. return
  178. if G.is_multigraph() and not directed:
  179. visited = set()
  180. for u, Gu in G.adj.items():
  181. multiplicity = ((v, len(Guv)) for v, Guv in Gu.items() if v in visited)
  182. yield from ([u, v] for v, m in multiplicity if m > 1)
  183. visited.add(u)
  184. # explicitly filter out loops; implicitly filter out parallel edges
  185. if directed:
  186. G = nx.DiGraph((u, v) for u, Gu in G.adj.items() for v in Gu if v != u)
  187. else:
  188. G = nx.Graph((u, v) for u, Gu in G.adj.items() for v in Gu if v != u)
  189. # this case is not strictly necessary but improves performance
  190. if length_bound is not None and length_bound == 2:
  191. if directed:
  192. visited = set()
  193. for u, Gu in G.adj.items():
  194. yield from (
  195. [v, u] for v in visited.intersection(Gu) if G.has_edge(v, u)
  196. )
  197. visited.add(u)
  198. return
  199. if directed:
  200. yield from _directed_cycle_search(G, length_bound)
  201. else:
  202. yield from _undirected_cycle_search(G, length_bound)
  203. def _directed_cycle_search(G, length_bound):
  204. """A dispatch function for `simple_cycles` for directed graphs.
  205. We generate all cycles of G through binary partition.
  206. 1. Pick a node v in G which belongs to at least one cycle
  207. a. Generate all cycles of G which contain the node v.
  208. b. Recursively generate all cycles of G \\ v.
  209. This is accomplished through the following:
  210. 1. Compute the strongly connected components SCC of G.
  211. 2. Select and remove a biconnected component C from BCC. Select a
  212. non-tree edge (u, v) of a depth-first search of G[C].
  213. 3. For each simple cycle P containing v in G[C], yield P.
  214. 4. Add the biconnected components of G[C \\ v] to BCC.
  215. If the parameter length_bound is not None, then step 3 will be limited to
  216. simple cycles of length at most length_bound.
  217. Parameters
  218. ----------
  219. G : NetworkX DiGraph
  220. A directed graph
  221. length_bound : int or None
  222. If length_bound is an int, generate all simple cycles of G with length at most length_bound.
  223. Otherwise, generate all simple cycles of G.
  224. Yields
  225. ------
  226. list of nodes
  227. Each cycle is represented by a list of nodes along the cycle.
  228. """
  229. scc = nx.strongly_connected_components
  230. components = [c for c in scc(G) if len(c) >= 2]
  231. while components:
  232. c = components.pop()
  233. Gc = G.subgraph(c)
  234. v = next(iter(c))
  235. if length_bound is None:
  236. yield from _johnson_cycle_search(Gc, [v])
  237. else:
  238. yield from _bounded_cycle_search(Gc, [v], length_bound)
  239. # delete v after searching G, to make sure we can find v
  240. G.remove_node(v)
  241. components.extend(c for c in scc(Gc) if len(c) >= 2)
  242. def _undirected_cycle_search(G, length_bound):
  243. """A dispatch function for `simple_cycles` for undirected graphs.
  244. We generate all cycles of G through binary partition.
  245. 1. Pick an edge (u, v) in G which belongs to at least one cycle
  246. a. Generate all cycles of G which contain the edge (u, v)
  247. b. Recursively generate all cycles of G \\ (u, v)
  248. This is accomplished through the following:
  249. 1. Compute the biconnected components BCC of G.
  250. 2. Select and remove a biconnected component C from BCC. Select a
  251. non-tree edge (u, v) of a depth-first search of G[C].
  252. 3. For each (v -> u) path P remaining in G[C] \\ (u, v), yield P.
  253. 4. Add the biconnected components of G[C] \\ (u, v) to BCC.
  254. If the parameter length_bound is not None, then step 3 will be limited to simple paths
  255. of length at most length_bound.
  256. Parameters
  257. ----------
  258. G : NetworkX Graph
  259. An undirected graph
  260. length_bound : int or None
  261. If length_bound is an int, generate all simple cycles of G with length at most length_bound.
  262. Otherwise, generate all simple cycles of G.
  263. Yields
  264. ------
  265. list of nodes
  266. Each cycle is represented by a list of nodes along the cycle.
  267. """
  268. bcc = nx.biconnected_components
  269. components = [c for c in bcc(G) if len(c) >= 3]
  270. while components:
  271. c = components.pop()
  272. Gc = G.subgraph(c)
  273. uv = list(next(iter(Gc.edges)))
  274. G.remove_edge(*uv)
  275. # delete (u, v) before searching G, to avoid fake 3-cycles [u, v, u]
  276. if length_bound is None:
  277. yield from _johnson_cycle_search(Gc, uv)
  278. else:
  279. yield from _bounded_cycle_search(Gc, uv, length_bound)
  280. components.extend(c for c in bcc(Gc) if len(c) >= 3)
  281. class _NeighborhoodCache(dict):
  282. """Very lightweight graph wrapper which caches neighborhoods as list.
  283. This dict subclass uses the __missing__ functionality to query graphs for
  284. their neighborhoods, and store the result as a list. This is used to avoid
  285. the performance penalty incurred by subgraph views.
  286. """
  287. def __init__(self, G):
  288. self.G = G
  289. def __missing__(self, v):
  290. Gv = self[v] = list(self.G[v])
  291. return Gv
  292. def _johnson_cycle_search(G, path):
  293. """The main loop of the cycle-enumeration algorithm of Johnson.
  294. Parameters
  295. ----------
  296. G : NetworkX Graph or DiGraph
  297. A graph
  298. path : list
  299. A cycle prefix. All cycles generated will begin with this prefix.
  300. Yields
  301. ------
  302. list of nodes
  303. Each cycle is represented by a list of nodes along the cycle.
  304. References
  305. ----------
  306. .. [1] Finding all the elementary circuits of a directed graph.
  307. D. B. Johnson, SIAM Journal on Computing 4, no. 1, 77-84, 1975.
  308. https://doi.org/10.1137/0204007
  309. """
  310. G = _NeighborhoodCache(G)
  311. blocked = set(path)
  312. B = defaultdict(set) # graph portions that yield no elementary circuit
  313. start = path[0]
  314. stack = [iter(G[path[-1]])]
  315. closed = [False]
  316. while stack:
  317. nbrs = stack[-1]
  318. for w in nbrs:
  319. if w == start:
  320. yield path[:]
  321. closed[-1] = True
  322. elif w not in blocked:
  323. path.append(w)
  324. closed.append(False)
  325. stack.append(iter(G[w]))
  326. blocked.add(w)
  327. break
  328. else: # no more nbrs
  329. stack.pop()
  330. v = path.pop()
  331. if closed.pop():
  332. if closed:
  333. closed[-1] = True
  334. unblock_stack = {v}
  335. while unblock_stack:
  336. u = unblock_stack.pop()
  337. if u in blocked:
  338. blocked.remove(u)
  339. unblock_stack.update(B[u])
  340. B[u].clear()
  341. else:
  342. for w in G[v]:
  343. B[w].add(v)
  344. def _bounded_cycle_search(G, path, length_bound):
  345. """The main loop of the cycle-enumeration algorithm of Gupta and Suzumura.
  346. Parameters
  347. ----------
  348. G : NetworkX Graph or DiGraph
  349. A graph
  350. path : list
  351. A cycle prefix. All cycles generated will begin with this prefix.
  352. length_bound: int
  353. A length bound. All cycles generated will have length at most length_bound.
  354. Yields
  355. ------
  356. list of nodes
  357. Each cycle is represented by a list of nodes along the cycle.
  358. References
  359. ----------
  360. .. [1] Finding All Bounded-Length Simple Cycles in a Directed Graph
  361. A. Gupta and T. Suzumura https://arxiv.org/abs/2105.10094
  362. """
  363. G = _NeighborhoodCache(G)
  364. lock = {v: 0 for v in path}
  365. B = defaultdict(set)
  366. start = path[0]
  367. stack = [iter(G[path[-1]])]
  368. blen = [length_bound]
  369. while stack:
  370. nbrs = stack[-1]
  371. for w in nbrs:
  372. if w == start:
  373. yield path[:]
  374. blen[-1] = 1
  375. elif len(path) < lock.get(w, length_bound):
  376. path.append(w)
  377. blen.append(length_bound)
  378. lock[w] = len(path)
  379. stack.append(iter(G[w]))
  380. break
  381. else:
  382. stack.pop()
  383. v = path.pop()
  384. bl = blen.pop()
  385. if blen:
  386. blen[-1] = min(blen[-1], bl)
  387. if bl < length_bound:
  388. relax_stack = [(bl, v)]
  389. while relax_stack:
  390. bl, u = relax_stack.pop()
  391. if lock.get(u, length_bound) < length_bound - bl + 1:
  392. lock[u] = length_bound - bl + 1
  393. relax_stack.extend((bl + 1, w) for w in B[u].difference(path))
  394. else:
  395. for w in G[v]:
  396. B[w].add(v)
  397. @nx._dispatchable
  398. def chordless_cycles(G, length_bound=None):
  399. """Find simple chordless cycles of a graph.
  400. A `simple cycle` is a closed path where no node appears twice. In a simple
  401. cycle, a `chord` is an additional edge between two nodes in the cycle. A
  402. `chordless cycle` is a simple cycle without chords. Said differently, a
  403. chordless cycle is a cycle C in a graph G where the number of edges in the
  404. induced graph G[C] is equal to the length of `C`.
  405. Note that some care must be taken in the case that G is not a simple graph
  406. nor a simple digraph. Some authors limit the definition of chordless cycles
  407. to have a prescribed minimum length; we do not.
  408. 1. We interpret self-loops to be chordless cycles, except in multigraphs
  409. with multiple loops in parallel. Likewise, in a chordless cycle of
  410. length greater than 1, there can be no nodes with self-loops.
  411. 2. We interpret directed two-cycles to be chordless cycles, except in
  412. multi-digraphs when any edge in a two-cycle has a parallel copy.
  413. 3. We interpret parallel pairs of undirected edges as two-cycles, except
  414. when a third (or more) parallel edge exists between the two nodes.
  415. 4. Generalizing the above, edges with parallel clones may not occur in
  416. chordless cycles.
  417. In a directed graph, two chordless cycles are distinct if they are not
  418. cyclic permutations of each other. In an undirected graph, two chordless
  419. cycles are distinct if they are not cyclic permutations of each other nor of
  420. the other's reversal.
  421. Optionally, the cycles are bounded in length.
  422. We use an algorithm strongly inspired by that of Dias et al [1]_. It has
  423. been modified in the following ways:
  424. 1. Recursion is avoided, per Python's limitations.
  425. 2. The labeling function is not necessary, because the starting paths
  426. are chosen (and deleted from the host graph) to prevent multiple
  427. occurrences of the same path.
  428. 3. The search is optionally bounded at a specified length.
  429. 4. Support for directed graphs is provided by extending cycles along
  430. forward edges, and blocking nodes along forward and reverse edges.
  431. 5. Support for multigraphs is provided by omitting digons from the set
  432. of forward edges.
  433. Parameters
  434. ----------
  435. G : NetworkX DiGraph
  436. A directed graph
  437. length_bound : int or None, optional (default=None)
  438. If length_bound is an int, generate all simple cycles of G with length at
  439. most length_bound. Otherwise, generate all simple cycles of G.
  440. Yields
  441. ------
  442. list of nodes
  443. Each cycle is represented by a list of nodes along the cycle.
  444. Examples
  445. --------
  446. >>> sorted(list(nx.chordless_cycles(nx.complete_graph(4))))
  447. [[1, 0, 2], [1, 0, 3], [2, 0, 3], [2, 1, 3]]
  448. Notes
  449. -----
  450. When length_bound is None, and the graph is simple, the time complexity is
  451. $O((n+e)(c+1))$ for $n$ nodes, $e$ edges and $c$ chordless cycles.
  452. Raises
  453. ------
  454. ValueError
  455. when length_bound < 0.
  456. References
  457. ----------
  458. .. [1] Efficient enumeration of chordless cycles
  459. E. Dias and D. Castonguay and H. Longo and W.A.R. Jradi
  460. https://arxiv.org/abs/1309.1051
  461. See Also
  462. --------
  463. simple_cycles
  464. """
  465. if length_bound is not None:
  466. if length_bound == 0:
  467. return
  468. elif length_bound < 0:
  469. raise ValueError("length bound must be non-negative")
  470. directed = G.is_directed()
  471. multigraph = G.is_multigraph()
  472. if multigraph:
  473. yield from ([v] for v, Gv in G.adj.items() if len(Gv.get(v, ())) == 1)
  474. else:
  475. yield from ([v] for v, Gv in G.adj.items() if v in Gv)
  476. if length_bound is not None and length_bound == 1:
  477. return
  478. # Nodes with loops cannot belong to longer cycles. Let's delete them here.
  479. # also, we implicitly reduce the multiplicity of edges down to 1 in the case
  480. # of multiedges.
  481. loops = set(nx.nodes_with_selfloops(G))
  482. edges = ((u, v) for u in G if u not in loops for v in G._adj[u] if v not in loops)
  483. if directed:
  484. F = nx.DiGraph(edges)
  485. B = F.to_undirected(as_view=False)
  486. else:
  487. F = nx.Graph(edges)
  488. B = None
  489. # If we're given a multigraph, we have a few cases to consider with parallel
  490. # edges.
  491. #
  492. # 1. If we have 2 or more edges in parallel between the nodes (u, v), we
  493. # must not construct longer cycles along (u, v).
  494. # 2. If G is not directed, then a pair of parallel edges between (u, v) is a
  495. # chordless cycle unless there exists a third (or more) parallel edge.
  496. # 3. If G is directed, then parallel edges do not form cycles, but do
  497. # preclude back-edges from forming cycles (handled in the next section),
  498. # Thus, if an edge (u, v) is duplicated and the reverse (v, u) is also
  499. # present, then we remove both from F.
  500. #
  501. # In directed graphs, we need to consider both directions that edges can
  502. # take, so iterate over all edges (u, v) and possibly (v, u). In undirected
  503. # graphs, we need to be a little careful to only consider every edge once,
  504. # so we use a "visited" set to emulate node-order comparisons.
  505. if multigraph:
  506. if not directed:
  507. B = F.copy()
  508. visited = set()
  509. for u, Gu in G.adj.items():
  510. if u in loops:
  511. continue
  512. if directed:
  513. multiplicity = ((v, len(Guv)) for v, Guv in Gu.items())
  514. for v, m in multiplicity:
  515. if m > 1:
  516. F.remove_edges_from(((u, v), (v, u)))
  517. else:
  518. multiplicity = ((v, len(Guv)) for v, Guv in Gu.items() if v in visited)
  519. for v, m in multiplicity:
  520. if m == 2:
  521. yield [u, v]
  522. if m > 1:
  523. F.remove_edge(u, v)
  524. visited.add(u)
  525. # If we're given a directed graphs, we need to think about digons. If we
  526. # have two edges (u, v) and (v, u), then that's a two-cycle. If either edge
  527. # was duplicated above, then we removed both from F. So, any digons we find
  528. # here are chordless. After finding digons, we remove their edges from F
  529. # to avoid traversing them in the search for chordless cycles.
  530. if directed:
  531. for u, Fu in F.adj.items():
  532. digons = [[u, v] for v in Fu if F.has_edge(v, u)]
  533. yield from digons
  534. F.remove_edges_from(digons)
  535. F.remove_edges_from(e[::-1] for e in digons)
  536. if length_bound is not None and length_bound == 2:
  537. return
  538. # Now, we prepare to search for cycles. We have removed all cycles of
  539. # lengths 1 and 2, so F is a simple graph or simple digraph. We repeatedly
  540. # separate digraphs into their strongly connected components, and undirected
  541. # graphs into their biconnected components. For each component, we pick a
  542. # node v, search for chordless cycles based at each "stem" (u, v, w), and
  543. # then remove v from that component before separating the graph again.
  544. if directed:
  545. separate = nx.strongly_connected_components
  546. # Directed stems look like (u -> v -> w), so we use the product of
  547. # predecessors of v with successors of v.
  548. def stems(C, v):
  549. for u, w in product(C.pred[v], C.succ[v]):
  550. if not G.has_edge(u, w): # omit stems with acyclic chords
  551. yield [u, v, w], F.has_edge(w, u)
  552. else:
  553. separate = nx.biconnected_components
  554. # Undirected stems look like (u ~ v ~ w), but we must not also search
  555. # (w ~ v ~ u), so we use combinations of v's neighbors of length 2.
  556. def stems(C, v):
  557. yield from (([u, v, w], F.has_edge(w, u)) for u, w in combinations(C[v], 2))
  558. components = [c for c in separate(F) if len(c) > 2]
  559. while components:
  560. c = components.pop()
  561. v = next(iter(c))
  562. Fc = F.subgraph(c)
  563. Fcc = Bcc = None
  564. for S, is_triangle in stems(Fc, v):
  565. if is_triangle:
  566. yield S
  567. else:
  568. if Fcc is None:
  569. Fcc = _NeighborhoodCache(Fc)
  570. Bcc = Fcc if B is None else _NeighborhoodCache(B.subgraph(c))
  571. yield from _chordless_cycle_search(Fcc, Bcc, S, length_bound)
  572. components.extend(c for c in separate(F.subgraph(c - {v})) if len(c) > 2)
  573. def _chordless_cycle_search(F, B, path, length_bound):
  574. """The main loop for chordless cycle enumeration.
  575. This algorithm is strongly inspired by that of Dias et al [1]_. It has been
  576. modified in the following ways:
  577. 1. Recursion is avoided, per Python's limitations
  578. 2. The labeling function is not necessary, because the starting paths
  579. are chosen (and deleted from the host graph) to prevent multiple
  580. occurrences of the same path
  581. 3. The search is optionally bounded at a specified length
  582. 4. Support for directed graphs is provided by extending cycles along
  583. forward edges, and blocking nodes along forward and reverse edges
  584. 5. Support for multigraphs is provided by omitting digons from the set
  585. of forward edges
  586. Parameters
  587. ----------
  588. F : _NeighborhoodCache
  589. A graph of forward edges to follow in constructing cycles
  590. B : _NeighborhoodCache
  591. A graph of blocking edges to prevent the production of chordless cycles
  592. path : list
  593. A cycle prefix. All cycles generated will begin with this prefix.
  594. length_bound : int
  595. A length bound. All cycles generated will have length at most length_bound.
  596. Yields
  597. ------
  598. list of nodes
  599. Each cycle is represented by a list of nodes along the cycle.
  600. References
  601. ----------
  602. .. [1] Efficient enumeration of chordless cycles
  603. E. Dias and D. Castonguay and H. Longo and W.A.R. Jradi
  604. https://arxiv.org/abs/1309.1051
  605. """
  606. blocked = defaultdict(int)
  607. target = path[0]
  608. blocked[path[1]] = 1
  609. for w in path[1:]:
  610. for v in B[w]:
  611. blocked[v] += 1
  612. stack = [iter(F[path[2]])]
  613. while stack:
  614. nbrs = stack[-1]
  615. for w in nbrs:
  616. if blocked[w] == 1 and (length_bound is None or len(path) < length_bound):
  617. Fw = F[w]
  618. if target in Fw:
  619. yield path + [w]
  620. else:
  621. Bw = B[w]
  622. if target in Bw:
  623. continue
  624. for v in Bw:
  625. blocked[v] += 1
  626. path.append(w)
  627. stack.append(iter(Fw))
  628. break
  629. else:
  630. stack.pop()
  631. for v in B[path.pop()]:
  632. blocked[v] -= 1
  633. @not_implemented_for("undirected")
  634. @nx._dispatchable(mutates_input=True)
  635. def recursive_simple_cycles(G):
  636. """Find simple cycles (elementary circuits) of a directed graph.
  637. A `simple cycle`, or `elementary circuit`, is a closed path where
  638. no node appears twice. Two elementary circuits are distinct if they
  639. are not cyclic permutations of each other.
  640. This version uses a recursive algorithm to build a list of cycles.
  641. You should probably use the iterator version called simple_cycles().
  642. Warning: This recursive version uses lots of RAM!
  643. It appears in NetworkX for pedagogical value.
  644. Parameters
  645. ----------
  646. G : NetworkX DiGraph
  647. A directed graph
  648. Returns
  649. -------
  650. A list of cycles, where each cycle is represented by a list of nodes
  651. along the cycle.
  652. Example:
  653. >>> edges = [(0, 0), (0, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]
  654. >>> G = nx.DiGraph(edges)
  655. >>> nx.recursive_simple_cycles(G)
  656. [[0], [2], [0, 1, 2], [0, 2], [1, 2]]
  657. Notes
  658. -----
  659. The implementation follows pp. 79-80 in [1]_.
  660. The time complexity is $O((n+e)(c+1))$ for $n$ nodes, $e$ edges and $c$
  661. elementary circuits.
  662. References
  663. ----------
  664. .. [1] Finding all the elementary circuits of a directed graph.
  665. D. B. Johnson, SIAM Journal on Computing 4, no. 1, 77-84, 1975.
  666. https://doi.org/10.1137/0204007
  667. See Also
  668. --------
  669. simple_cycles, cycle_basis
  670. """
  671. # Jon Olav Vik, 2010-08-09
  672. def _unblock(thisnode):
  673. """Recursively unblock and remove nodes from B[thisnode]."""
  674. if blocked[thisnode]:
  675. blocked[thisnode] = False
  676. while B[thisnode]:
  677. _unblock(B[thisnode].pop())
  678. def circuit(thisnode, startnode, component):
  679. closed = False # set to True if elementary path is closed
  680. path.append(thisnode)
  681. blocked[thisnode] = True
  682. for nextnode in component[thisnode]: # direct successors of thisnode
  683. if nextnode == startnode:
  684. result.append(path[:])
  685. closed = True
  686. elif not blocked[nextnode]:
  687. if circuit(nextnode, startnode, component):
  688. closed = True
  689. if closed:
  690. _unblock(thisnode)
  691. else:
  692. for nextnode in component[thisnode]:
  693. if thisnode not in B[nextnode]: # TODO: use set for speedup?
  694. B[nextnode].append(thisnode)
  695. path.pop() # remove thisnode from path
  696. return closed
  697. path = [] # stack of nodes in current path
  698. blocked = defaultdict(bool) # vertex: blocked from search?
  699. B = defaultdict(list) # graph portions that yield no elementary circuit
  700. result = [] # list to accumulate the circuits found
  701. # Johnson's algorithm exclude self cycle edges like (v, v)
  702. # To be backward compatible, we record those cycles in advance
  703. # and then remove from subG
  704. for v in G:
  705. if G.has_edge(v, v):
  706. result.append([v])
  707. G.remove_edge(v, v)
  708. # Johnson's algorithm requires some ordering of the nodes.
  709. # They might not be sortable so we assign an arbitrary ordering.
  710. ordering = dict(zip(G, range(len(G))))
  711. for s in ordering:
  712. # Build the subgraph induced by s and following nodes in the ordering
  713. subgraph = G.subgraph(node for node in G if ordering[node] >= ordering[s])
  714. # Find the strongly connected component in the subgraph
  715. # that contains the least node according to the ordering
  716. strongcomp = nx.strongly_connected_components(subgraph)
  717. mincomp = min(strongcomp, key=lambda ns: min(ordering[n] for n in ns))
  718. component = G.subgraph(mincomp)
  719. if len(component) > 1:
  720. # smallest node in the component according to the ordering
  721. startnode = min(component, key=ordering.__getitem__)
  722. for node in component:
  723. blocked[node] = False
  724. B[node][:] = []
  725. dummy = circuit(startnode, startnode, component)
  726. return result
  727. @nx._dispatchable
  728. def find_cycle(G, source=None, orientation=None):
  729. """Returns a cycle found via depth-first traversal.
  730. The cycle is a list of edges indicating the cyclic path.
  731. Orientation of directed edges is controlled by `orientation`.
  732. Parameters
  733. ----------
  734. G : graph
  735. A directed/undirected graph/multigraph.
  736. source : node, list of nodes
  737. The node from which the traversal begins. If None, then a source
  738. is chosen arbitrarily and repeatedly until all edges from each node in
  739. the graph are searched.
  740. orientation : None | 'original' | 'reverse' | 'ignore' (default: None)
  741. For directed graphs and directed multigraphs, edge traversals need not
  742. respect the original orientation of the edges.
  743. When set to 'reverse' every edge is traversed in the reverse direction.
  744. When set to 'ignore', every edge is treated as undirected.
  745. When set to 'original', every edge is treated as directed.
  746. In all three cases, the yielded edge tuples add a last entry to
  747. indicate the direction in which that edge was traversed.
  748. If orientation is None, the yielded edge has no direction indicated.
  749. The direction is respected, but not reported.
  750. Returns
  751. -------
  752. edges : directed edges
  753. A list of directed edges indicating the path taken for the loop.
  754. If no cycle is found, then an exception is raised.
  755. For graphs, an edge is of the form `(u, v)` where `u` and `v`
  756. are the tail and head of the edge as determined by the traversal.
  757. For multigraphs, an edge is of the form `(u, v, key)`, where `key` is
  758. the key of the edge. When the graph is directed, then `u` and `v`
  759. are always in the order of the actual directed edge.
  760. If orientation is not None then the edge tuple is extended to include
  761. the direction of traversal ('forward' or 'reverse') on that edge.
  762. Raises
  763. ------
  764. NetworkXNoCycle
  765. If no cycle was found.
  766. Examples
  767. --------
  768. In this example, we construct a DAG and find, in the first call, that there
  769. are no directed cycles, and so an exception is raised. In the second call,
  770. we ignore edge orientations and find that there is an undirected cycle.
  771. Note that the second call finds a directed cycle while effectively
  772. traversing an undirected graph, and so, we found an "undirected cycle".
  773. This means that this DAG structure does not form a directed tree (which
  774. is also known as a polytree).
  775. >>> G = nx.DiGraph([(0, 1), (0, 2), (1, 2)])
  776. >>> nx.find_cycle(G, orientation="original")
  777. Traceback (most recent call last):
  778. ...
  779. networkx.exception.NetworkXNoCycle: No cycle found.
  780. >>> list(nx.find_cycle(G, orientation="ignore"))
  781. [(0, 1, 'forward'), (1, 2, 'forward'), (0, 2, 'reverse')]
  782. See Also
  783. --------
  784. simple_cycles
  785. """
  786. if not G.is_directed() or orientation in (None, "original"):
  787. def tailhead(edge):
  788. return edge[:2]
  789. elif orientation == "reverse":
  790. def tailhead(edge):
  791. return edge[1], edge[0]
  792. elif orientation == "ignore":
  793. def tailhead(edge):
  794. if edge[-1] == "reverse":
  795. return edge[1], edge[0]
  796. return edge[:2]
  797. explored = set()
  798. cycle = []
  799. final_node = None
  800. for start_node in G.nbunch_iter(source):
  801. if start_node in explored:
  802. # No loop is possible.
  803. continue
  804. edges = []
  805. # All nodes seen in this iteration of edge_dfs
  806. seen = {start_node}
  807. # Nodes in active path.
  808. active_nodes = {start_node}
  809. previous_head = None
  810. for edge in nx.edge_dfs(G, start_node, orientation):
  811. # Determine if this edge is a continuation of the active path.
  812. tail, head = tailhead(edge)
  813. if head in explored:
  814. # Then we've already explored it. No loop is possible.
  815. continue
  816. if previous_head is not None and tail != previous_head:
  817. # This edge results from backtracking.
  818. # Pop until we get a node whose head equals the current tail.
  819. # So for example, we might have:
  820. # (0, 1), (1, 2), (2, 3), (1, 4)
  821. # which must become:
  822. # (0, 1), (1, 4)
  823. while True:
  824. try:
  825. popped_edge = edges.pop()
  826. except IndexError:
  827. edges = []
  828. active_nodes = {tail}
  829. break
  830. else:
  831. popped_head = tailhead(popped_edge)[1]
  832. active_nodes.remove(popped_head)
  833. if edges:
  834. last_head = tailhead(edges[-1])[1]
  835. if tail == last_head:
  836. break
  837. edges.append(edge)
  838. if head in active_nodes:
  839. # We have a loop!
  840. cycle.extend(edges)
  841. final_node = head
  842. break
  843. else:
  844. seen.add(head)
  845. active_nodes.add(head)
  846. previous_head = head
  847. if cycle:
  848. break
  849. else:
  850. explored.update(seen)
  851. else:
  852. assert len(cycle) == 0
  853. raise nx.exception.NetworkXNoCycle("No cycle found.")
  854. # We now have a list of edges which ends on a cycle.
  855. # So we need to remove from the beginning edges that are not relevant.
  856. for i, edge in enumerate(cycle):
  857. tail, head = tailhead(edge)
  858. if tail == final_node:
  859. break
  860. return cycle[i:]
  861. @not_implemented_for("directed")
  862. @not_implemented_for("multigraph")
  863. @nx._dispatchable(edge_attrs="weight")
  864. def minimum_cycle_basis(G, weight=None):
  865. """Returns a minimum weight cycle basis for G
  866. Minimum weight means a cycle basis for which the total weight
  867. (length for unweighted graphs) of all the cycles is minimum.
  868. Parameters
  869. ----------
  870. G : NetworkX Graph
  871. weight: string
  872. name of the edge attribute to use for edge weights
  873. Returns
  874. -------
  875. A list of cycle lists. Each cycle list is a list of nodes
  876. which forms a cycle (loop) in G. Note that the nodes are not
  877. necessarily returned in a order by which they appear in the cycle
  878. Examples
  879. --------
  880. >>> G = nx.Graph()
  881. >>> nx.add_cycle(G, [0, 1, 2, 3])
  882. >>> nx.add_cycle(G, [0, 3, 4, 5])
  883. >>> nx.minimum_cycle_basis(G)
  884. [[5, 4, 3, 0], [3, 2, 1, 0]]
  885. References:
  886. [1] Kavitha, Telikepalli, et al. "An O(m^2n) Algorithm for
  887. Minimum Cycle Basis of Graphs."
  888. http://link.springer.com/article/10.1007/s00453-007-9064-z
  889. [2] de Pina, J. 1995. Applications of shortest path methods.
  890. Ph.D. thesis, University of Amsterdam, Netherlands
  891. See Also
  892. --------
  893. simple_cycles, cycle_basis
  894. """
  895. # We first split the graph in connected subgraphs
  896. return sum(
  897. (_min_cycle_basis(G.subgraph(c), weight) for c in nx.connected_components(G)),
  898. [],
  899. )
  900. def _min_cycle_basis(G, weight):
  901. cb = []
  902. # We extract the edges not in a spanning tree. We do not really need a
  903. # *minimum* spanning tree. That is why we call the next function with
  904. # weight=None. Depending on implementation, it may be faster as well
  905. tree_edges = list(nx.minimum_spanning_edges(G, weight=None, data=False))
  906. chords = G.edges - tree_edges - {(v, u) for u, v in tree_edges}
  907. # We maintain a set of vectors orthogonal to sofar found cycles
  908. set_orth = [{edge} for edge in chords]
  909. while set_orth:
  910. base = set_orth.pop()
  911. # kth cycle is "parallel" to kth vector in set_orth
  912. cycle_edges = _min_cycle(G, base, weight)
  913. cb.append([v for u, v in cycle_edges])
  914. # now update set_orth so that k+1,k+2... th elements are
  915. # orthogonal to the newly found cycle, as per [p. 336, 1]
  916. set_orth = [
  917. (
  918. {e for e in orth if e not in base if e[::-1] not in base}
  919. | {e for e in base if e not in orth if e[::-1] not in orth}
  920. )
  921. if sum((e in orth or e[::-1] in orth) for e in cycle_edges) % 2
  922. else orth
  923. for orth in set_orth
  924. ]
  925. return cb
  926. def _min_cycle(G, orth, weight):
  927. """
  928. Computes the minimum weight cycle in G,
  929. orthogonal to the vector orth as per [p. 338, 1]
  930. Use (u, 1) to indicate the lifted copy of u (denoted u' in paper).
  931. """
  932. Gi = nx.Graph()
  933. # Add 2 copies of each edge in G to Gi.
  934. # If edge is in orth, add cross edge; otherwise in-plane edge
  935. for u, v, wt in G.edges(data=weight, default=1):
  936. if (u, v) in orth or (v, u) in orth:
  937. Gi.add_edges_from([(u, (v, 1)), ((u, 1), v)], Gi_weight=wt)
  938. else:
  939. Gi.add_edges_from([(u, v), ((u, 1), (v, 1))], Gi_weight=wt)
  940. # find the shortest length in Gi between n and (n, 1) for each n
  941. # Note: Use "Gi_weight" for name of weight attribute
  942. spl = nx.shortest_path_length
  943. lift = {n: spl(Gi, source=n, target=(n, 1), weight="Gi_weight") for n in G}
  944. # Now compute that short path in Gi, which translates to a cycle in G
  945. start = min(lift, key=lift.get)
  946. end = (start, 1)
  947. min_path_i = nx.shortest_path(Gi, source=start, target=end, weight="Gi_weight")
  948. # Now we obtain the actual path, re-map nodes in Gi to those in G
  949. min_path = [n if n in G else n[0] for n in min_path_i]
  950. # Now remove the edges that occur two times
  951. # two passes: flag which edges get kept, then build it
  952. edgelist = list(pairwise(min_path))
  953. edgeset = set()
  954. for e in edgelist:
  955. if e in edgeset:
  956. edgeset.remove(e)
  957. elif e[::-1] in edgeset:
  958. edgeset.remove(e[::-1])
  959. else:
  960. edgeset.add(e)
  961. min_edgelist = []
  962. for e in edgelist:
  963. if e in edgeset:
  964. min_edgelist.append(e)
  965. edgeset.remove(e)
  966. elif e[::-1] in edgeset:
  967. min_edgelist.append(e[::-1])
  968. edgeset.remove(e[::-1])
  969. return min_edgelist
  970. @not_implemented_for("directed")
  971. @not_implemented_for("multigraph")
  972. @nx._dispatchable
  973. def girth(G):
  974. """Returns the girth of the graph.
  975. The girth of a graph is the length of its shortest cycle, or infinity if
  976. the graph is acyclic. The algorithm follows the description given on the
  977. Wikipedia page [1]_, and runs in time O(mn) on a graph with m edges and n
  978. nodes.
  979. Parameters
  980. ----------
  981. G : NetworkX Graph
  982. Returns
  983. -------
  984. int or math.inf
  985. Examples
  986. --------
  987. All examples below (except P_5) can easily be checked using Wikipedia,
  988. which has a page for each of these famous graphs.
  989. >>> nx.girth(nx.chvatal_graph())
  990. 4
  991. >>> nx.girth(nx.tutte_graph())
  992. 4
  993. >>> nx.girth(nx.petersen_graph())
  994. 5
  995. >>> nx.girth(nx.heawood_graph())
  996. 6
  997. >>> nx.girth(nx.pappus_graph())
  998. 6
  999. >>> nx.girth(nx.path_graph(5))
  1000. inf
  1001. References
  1002. ----------
  1003. .. [1] `Wikipedia: Girth <https://en.wikipedia.org/wiki/Girth_(graph_theory)>`_
  1004. """
  1005. girth = depth_limit = inf
  1006. tree_edge = nx.algorithms.traversal.breadth_first_search.TREE_EDGE
  1007. level_edge = nx.algorithms.traversal.breadth_first_search.LEVEL_EDGE
  1008. for n in G:
  1009. # run a BFS from source n, keeping track of distances; since we want
  1010. # the shortest cycle, no need to explore beyond the current minimum length
  1011. depth = {n: 0}
  1012. for u, v, label in nx.bfs_labeled_edges(G, n):
  1013. du = depth[u]
  1014. if du > depth_limit:
  1015. break
  1016. if label is tree_edge:
  1017. depth[v] = du + 1
  1018. else:
  1019. # if (u, v) is a level edge, the length is du + du + 1 (odd)
  1020. # otherwise, it's a forward edge; length is du + (du + 1) + 1 (even)
  1021. delta = label is level_edge
  1022. length = du + du + 2 - delta
  1023. if length < girth:
  1024. girth = length
  1025. depth_limit = du - delta
  1026. return girth