aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/euler.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/algorithms/euler.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/euler.py')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/euler.py470
1 files changed, 470 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/euler.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/euler.py
new file mode 100644
index 00000000..2c308e38
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/euler.py
@@ -0,0 +1,470 @@
+"""
+Eulerian circuits and graphs.
+"""
+
+from itertools import combinations
+
+import networkx as nx
+
+from ..utils import arbitrary_element, not_implemented_for
+
+__all__ = [
+ "is_eulerian",
+ "eulerian_circuit",
+ "eulerize",
+ "is_semieulerian",
+ "has_eulerian_path",
+ "eulerian_path",
+]
+
+
+@nx._dispatchable
+def is_eulerian(G):
+ """Returns True if and only if `G` is Eulerian.
+
+ A graph is *Eulerian* if it has an Eulerian circuit. An *Eulerian
+ circuit* is a closed walk that includes each edge of a graph exactly
+ once.
+
+ Graphs with isolated vertices (i.e. vertices with zero degree) are not
+ considered to have Eulerian circuits. Therefore, if the graph is not
+ connected (or not strongly connected, for directed graphs), this function
+ returns False.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+ A graph, either directed or undirected.
+
+ Examples
+ --------
+ >>> nx.is_eulerian(nx.DiGraph({0: [3], 1: [2], 2: [3], 3: [0, 1]}))
+ True
+ >>> nx.is_eulerian(nx.complete_graph(5))
+ True
+ >>> nx.is_eulerian(nx.petersen_graph())
+ False
+
+ If you prefer to allow graphs with isolated vertices to have Eulerian circuits,
+ you can first remove such vertices and then call `is_eulerian` as below example shows.
+
+ >>> G = nx.Graph([(0, 1), (1, 2), (0, 2)])
+ >>> G.add_node(3)
+ >>> nx.is_eulerian(G)
+ False
+
+ >>> G.remove_nodes_from(list(nx.isolates(G)))
+ >>> nx.is_eulerian(G)
+ True
+
+
+ """
+ if G.is_directed():
+ # Every node must have equal in degree and out degree and the
+ # graph must be strongly connected
+ return all(
+ G.in_degree(n) == G.out_degree(n) for n in G
+ ) and nx.is_strongly_connected(G)
+ # An undirected Eulerian graph has no vertices of odd degree and
+ # must be connected.
+ return all(d % 2 == 0 for v, d in G.degree()) and nx.is_connected(G)
+
+
+@nx._dispatchable
+def is_semieulerian(G):
+ """Return True iff `G` is semi-Eulerian.
+
+ G is semi-Eulerian if it has an Eulerian path but no Eulerian circuit.
+
+ See Also
+ --------
+ has_eulerian_path
+ is_eulerian
+ """
+ return has_eulerian_path(G) and not is_eulerian(G)
+
+
+def _find_path_start(G):
+ """Return a suitable starting vertex for an Eulerian path.
+
+ If no path exists, return None.
+ """
+ if not has_eulerian_path(G):
+ return None
+
+ if is_eulerian(G):
+ return arbitrary_element(G)
+
+ if G.is_directed():
+ v1, v2 = (v for v in G if G.in_degree(v) != G.out_degree(v))
+ # Determines which is the 'start' node (as opposed to the 'end')
+ if G.out_degree(v1) > G.in_degree(v1):
+ return v1
+ else:
+ return v2
+
+ else:
+ # In an undirected graph randomly choose one of the possibilities
+ start = [v for v in G if G.degree(v) % 2 != 0][0]
+ return start
+
+
+def _simplegraph_eulerian_circuit(G, source):
+ if G.is_directed():
+ degree = G.out_degree
+ edges = G.out_edges
+ else:
+ degree = G.degree
+ edges = G.edges
+ vertex_stack = [source]
+ last_vertex = None
+ while vertex_stack:
+ current_vertex = vertex_stack[-1]
+ if degree(current_vertex) == 0:
+ if last_vertex is not None:
+ yield (last_vertex, current_vertex)
+ last_vertex = current_vertex
+ vertex_stack.pop()
+ else:
+ _, next_vertex = arbitrary_element(edges(current_vertex))
+ vertex_stack.append(next_vertex)
+ G.remove_edge(current_vertex, next_vertex)
+
+
+def _multigraph_eulerian_circuit(G, source):
+ if G.is_directed():
+ degree = G.out_degree
+ edges = G.out_edges
+ else:
+ degree = G.degree
+ edges = G.edges
+ vertex_stack = [(source, None)]
+ last_vertex = None
+ last_key = None
+ while vertex_stack:
+ current_vertex, current_key = vertex_stack[-1]
+ if degree(current_vertex) == 0:
+ if last_vertex is not None:
+ yield (last_vertex, current_vertex, last_key)
+ last_vertex, last_key = current_vertex, current_key
+ vertex_stack.pop()
+ else:
+ triple = arbitrary_element(edges(current_vertex, keys=True))
+ _, next_vertex, next_key = triple
+ vertex_stack.append((next_vertex, next_key))
+ G.remove_edge(current_vertex, next_vertex, next_key)
+
+
+@nx._dispatchable
+def eulerian_circuit(G, source=None, keys=False):
+ """Returns an iterator over the edges of an Eulerian circuit in `G`.
+
+ An *Eulerian circuit* is a closed walk that includes each edge of a
+ graph exactly once.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+ A graph, either directed or undirected.
+
+ source : node, optional
+ Starting node for circuit.
+
+ keys : bool
+ If False, edges generated by this function will be of the form
+ ``(u, v)``. Otherwise, edges will be of the form ``(u, v, k)``.
+ This option is ignored unless `G` is a multigraph.
+
+ Returns
+ -------
+ edges : iterator
+ An iterator over edges in the Eulerian circuit.
+
+ Raises
+ ------
+ NetworkXError
+ If the graph is not Eulerian.
+
+ See Also
+ --------
+ is_eulerian
+
+ Notes
+ -----
+ This is a linear time implementation of an algorithm adapted from [1]_.
+
+ For general information about Euler tours, see [2]_.
+
+ References
+ ----------
+ .. [1] J. Edmonds, E. L. Johnson.
+ Matching, Euler tours and the Chinese postman.
+ Mathematical programming, Volume 5, Issue 1 (1973), 111-114.
+ .. [2] https://en.wikipedia.org/wiki/Eulerian_path
+
+ Examples
+ --------
+ To get an Eulerian circuit in an undirected graph::
+
+ >>> G = nx.complete_graph(3)
+ >>> list(nx.eulerian_circuit(G))
+ [(0, 2), (2, 1), (1, 0)]
+ >>> list(nx.eulerian_circuit(G, source=1))
+ [(1, 2), (2, 0), (0, 1)]
+
+ To get the sequence of vertices in an Eulerian circuit::
+
+ >>> [u for u, v in nx.eulerian_circuit(G)]
+ [0, 2, 1]
+
+ """
+ if not is_eulerian(G):
+ raise nx.NetworkXError("G is not Eulerian.")
+ if G.is_directed():
+ G = G.reverse()
+ else:
+ G = G.copy()
+ if source is None:
+ source = arbitrary_element(G)
+ if G.is_multigraph():
+ for u, v, k in _multigraph_eulerian_circuit(G, source):
+ if keys:
+ yield u, v, k
+ else:
+ yield u, v
+ else:
+ yield from _simplegraph_eulerian_circuit(G, source)
+
+
+@nx._dispatchable
+def has_eulerian_path(G, source=None):
+ """Return True iff `G` has an Eulerian path.
+
+ An Eulerian path is a path in a graph which uses each edge of a graph
+ exactly once. If `source` is specified, then this function checks
+ whether an Eulerian path that starts at node `source` exists.
+
+ A directed graph has an Eulerian path iff:
+ - at most one vertex has out_degree - in_degree = 1,
+ - at most one vertex has in_degree - out_degree = 1,
+ - every other vertex has equal in_degree and out_degree,
+ - and all of its vertices belong to a single connected
+ component of the underlying undirected graph.
+
+ If `source` is not None, an Eulerian path starting at `source` exists if no
+ other node has out_degree - in_degree = 1. This is equivalent to either
+ there exists an Eulerian circuit or `source` has out_degree - in_degree = 1
+ and the conditions above hold.
+
+ An undirected graph has an Eulerian path iff:
+ - exactly zero or two vertices have odd degree,
+ - and all of its vertices belong to a single connected component.
+
+ If `source` is not None, an Eulerian path starting at `source` exists if
+ either there exists an Eulerian circuit or `source` has an odd degree and the
+ conditions above hold.
+
+ Graphs with isolated vertices (i.e. vertices with zero degree) are not considered
+ to have an Eulerian path. Therefore, if the graph is not connected (or not strongly
+ connected, for directed graphs), this function returns False.
+
+ Parameters
+ ----------
+ G : NetworkX Graph
+ The graph to find an euler path in.
+
+ source : node, optional
+ Starting node for path.
+
+ Returns
+ -------
+ Bool : True if G has an Eulerian path.
+
+ Examples
+ --------
+ If you prefer to allow graphs with isolated vertices to have Eulerian path,
+ you can first remove such vertices and then call `has_eulerian_path` as below example shows.
+
+ >>> G = nx.Graph([(0, 1), (1, 2), (0, 2)])
+ >>> G.add_node(3)
+ >>> nx.has_eulerian_path(G)
+ False
+
+ >>> G.remove_nodes_from(list(nx.isolates(G)))
+ >>> nx.has_eulerian_path(G)
+ True
+
+ See Also
+ --------
+ is_eulerian
+ eulerian_path
+ """
+ if nx.is_eulerian(G):
+ return True
+
+ if G.is_directed():
+ ins = G.in_degree
+ outs = G.out_degree
+ # Since we know it is not eulerian, outs - ins must be 1 for source
+ if source is not None and outs[source] - ins[source] != 1:
+ return False
+
+ unbalanced_ins = 0
+ unbalanced_outs = 0
+ for v in G:
+ if ins[v] - outs[v] == 1:
+ unbalanced_ins += 1
+ elif outs[v] - ins[v] == 1:
+ unbalanced_outs += 1
+ elif ins[v] != outs[v]:
+ return False
+
+ return (
+ unbalanced_ins <= 1 and unbalanced_outs <= 1 and nx.is_weakly_connected(G)
+ )
+ else:
+ # We know it is not eulerian, so degree of source must be odd.
+ if source is not None and G.degree[source] % 2 != 1:
+ return False
+
+ # Sum is 2 since we know it is not eulerian (which implies sum is 0)
+ return sum(d % 2 == 1 for v, d in G.degree()) == 2 and nx.is_connected(G)
+
+
+@nx._dispatchable
+def eulerian_path(G, source=None, keys=False):
+ """Return an iterator over the edges of an Eulerian path in `G`.
+
+ Parameters
+ ----------
+ G : NetworkX Graph
+ The graph in which to look for an eulerian path.
+ source : node or None (default: None)
+ The node at which to start the search. None means search over all
+ starting nodes.
+ keys : Bool (default: False)
+ Indicates whether to yield edge 3-tuples (u, v, edge_key).
+ The default yields edge 2-tuples
+
+ Yields
+ ------
+ Edge tuples along the eulerian path.
+
+ Warning: If `source` provided is not the start node of an Euler path
+ will raise error even if an Euler Path exists.
+ """
+ if not has_eulerian_path(G, source):
+ raise nx.NetworkXError("Graph has no Eulerian paths.")
+ if G.is_directed():
+ G = G.reverse()
+ if source is None or nx.is_eulerian(G) is False:
+ source = _find_path_start(G)
+ if G.is_multigraph():
+ for u, v, k in _multigraph_eulerian_circuit(G, source):
+ if keys:
+ yield u, v, k
+ else:
+ yield u, v
+ else:
+ yield from _simplegraph_eulerian_circuit(G, source)
+ else:
+ G = G.copy()
+ if source is None:
+ source = _find_path_start(G)
+ if G.is_multigraph():
+ if keys:
+ yield from reversed(
+ [(v, u, k) for u, v, k in _multigraph_eulerian_circuit(G, source)]
+ )
+ else:
+ yield from reversed(
+ [(v, u) for u, v, k in _multigraph_eulerian_circuit(G, source)]
+ )
+ else:
+ yield from reversed(
+ [(v, u) for u, v in _simplegraph_eulerian_circuit(G, source)]
+ )
+
+
+@not_implemented_for("directed")
+@nx._dispatchable(returns_graph=True)
+def eulerize(G):
+ """Transforms a graph into an Eulerian graph.
+
+ If `G` is Eulerian the result is `G` as a MultiGraph, otherwise the result is a smallest
+ (in terms of the number of edges) multigraph whose underlying simple graph is `G`.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+ An undirected graph
+
+ Returns
+ -------
+ G : NetworkX multigraph
+
+ Raises
+ ------
+ NetworkXError
+ If the graph is not connected.
+
+ See Also
+ --------
+ is_eulerian
+ eulerian_circuit
+
+ References
+ ----------
+ .. [1] J. Edmonds, E. L. Johnson.
+ Matching, Euler tours and the Chinese postman.
+ Mathematical programming, Volume 5, Issue 1 (1973), 111-114.
+ .. [2] https://en.wikipedia.org/wiki/Eulerian_path
+ .. [3] http://web.math.princeton.edu/math_alive/5/Notes1.pdf
+
+ Examples
+ --------
+ >>> G = nx.complete_graph(10)
+ >>> H = nx.eulerize(G)
+ >>> nx.is_eulerian(H)
+ True
+
+ """
+ if G.order() == 0:
+ raise nx.NetworkXPointlessConcept("Cannot Eulerize null graph")
+ if not nx.is_connected(G):
+ raise nx.NetworkXError("G is not connected")
+ odd_degree_nodes = [n for n, d in G.degree() if d % 2 == 1]
+ G = nx.MultiGraph(G)
+ if len(odd_degree_nodes) == 0:
+ return G
+
+ # get all shortest paths between vertices of odd degree
+ odd_deg_pairs_paths = [
+ (m, {n: nx.shortest_path(G, source=m, target=n)})
+ for m, n in combinations(odd_degree_nodes, 2)
+ ]
+
+ # use the number of vertices in a graph + 1 as an upper bound on
+ # the maximum length of a path in G
+ upper_bound_on_max_path_length = len(G) + 1
+
+ # use "len(G) + 1 - len(P)",
+ # where P is a shortest path between vertices n and m,
+ # as edge-weights in a new graph
+ # store the paths in the graph for easy indexing later
+ Gp = nx.Graph()
+ for n, Ps in odd_deg_pairs_paths:
+ for m, P in Ps.items():
+ if n != m:
+ Gp.add_edge(
+ m, n, weight=upper_bound_on_max_path_length - len(P), path=P
+ )
+
+ # find the minimum weight matching of edges in the weighted graph
+ best_matching = nx.Graph(list(nx.max_weight_matching(Gp)))
+
+ # duplicate each edge along each path in the set of paths in Gp
+ for m, n in best_matching.edges():
+ path = Gp[m][n]["path"]
+ G.add_edges_from(nx.utils.pairwise(path))
+ return G