Orientations¶
This module implements several methods to compute orientations of undirected graphs subject to specific constraints (e.g., acyclic, strongly connected, etc.). It also implements some iterators over all these orientations.
This module contains the following methods
Return an oriented version of \(G\) according the input function \(f\). |
|
Return an iterator over orientations of \(G\). |
|
Return an iterator over all acyclic orientations of an undirected graph \(G\). |
|
Return a strongly connected orientation of the graph \(G\). |
|
Return an iterator over all strong orientations of a graph \(G\) |
|
Return a random orientation of a graph \(G\) |
|
Return an orientation of \(G\) with the smallest possible maximum outdegree. |
|
Return an orientation of \(G\) such that every vertex \(v\) has out-degree less than \(b(v)\). |
|
|
Return a DiGraph which is an Eulerian orientation of the graph \(G\). |
Methods¶
- sage.graphs.orientations.acyclic_orientations(G)[source]¶
Return an iterator over all acyclic orientations of an undirected graph \(G\).
ALGORITHM:
The algorithm is based on [Sq1998]. It presents an efficient algorithm for listing the acyclic orientations of a graph. The algorithm is shown to require O(n) time per acyclic orientation generated, making it the most efficient known algorithm for generating acyclic orientations.
The function uses a recursive approach to generate acyclic orientations of the graph. It reorders the vertices and edges of the graph, creating a new graph with updated labels. Then, it iteratively generates acyclic orientations by considering subsets of edges and checking whether they form upsets in a corresponding poset.
INPUT:
G
– an undirected graph
OUTPUT: an iterator over all acyclic orientations of the input graph
Note
The function assumes that the input graph is undirected and the edges are unlabelled.
EXAMPLES:
To count the number of acyclic orientations for a graph:
sage: g = Graph([(0, 3), (0, 4), (3, 4), (1, 3), (1, 2), (2, 3), (2, 4)]) sage: it = g.acyclic_orientations() sage: len(list(it)) 54
>>> from sage.all import * >>> g = Graph([(Integer(0), Integer(3)), (Integer(0), Integer(4)), (Integer(3), Integer(4)), (Integer(1), Integer(3)), (Integer(1), Integer(2)), (Integer(2), Integer(3)), (Integer(2), Integer(4))]) >>> it = g.acyclic_orientations() >>> len(list(it)) 54
g = Graph([(0, 3), (0, 4), (3, 4), (1, 3), (1, 2), (2, 3), (2, 4)]) it = g.acyclic_orientations() len(list(it))
Test for arbitrary vertex labels:
sage: g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'), ....: ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')]) sage: it = g_str.acyclic_orientations() sage: len(list(it)) 42
>>> from sage.all import * >>> g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'), ... ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')]) >>> it = g_str.acyclic_orientations() >>> len(list(it)) 42
g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'), ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')]) it = g_str.acyclic_orientations() len(list(it))
Check that the method returns properly relabeled acyclic digraphs:
sage: g = Graph([(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]) sage: orientations = set([frozenset(d.edges(labels=false)) for d in g.acyclic_orientations()]) sage: len(orientations) 18 sage: all(d.is_directed_acyclic() for d in g.acyclic_orientations()) True
>>> from sage.all import * >>> g = Graph([(Integer(0), Integer(1)), (Integer(1), Integer(2)), (Integer(2), Integer(3)), (Integer(3), Integer(0)), (Integer(0), Integer(2))]) >>> orientations = set([frozenset(d.edges(labels=false)) for d in g.acyclic_orientations()]) >>> len(orientations) 18 >>> all(d.is_directed_acyclic() for d in g.acyclic_orientations()) True
g = Graph([(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]) orientations = set([frozenset(d.edges(labels=false)) for d in g.acyclic_orientations()]) len(orientations) all(d.is_directed_acyclic() for d in g.acyclic_orientations())
- sage.graphs.orientations.bounded_outdegree_orientation(G, bound, solver, verbose=None, integrality_tolerance=False)[source]¶
Return an orientation of \(G\) such that every vertex \(v\) has out-degree less than \(b(v)\).
INPUT:
G
– an undirected graphbound
– maximum bound on the out-degree. Can be of three different types :An integer \(k\). In this case, computes an orientation whose maximum out-degree is less than \(k\).
A dictionary associating to each vertex its associated maximum out-degree.
A function associating to each vertex its associated maximum out-degree.
solver
– string (default:None
); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set toNone
, the default one is used. For more information on MILP solvers and which default solver is used, see the methodsolve
of the classMixedIntegerLinearProgram
.verbose
– integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.integrality_tolerance
– float; parameter for use with MILP solvers over an inexact base ring; seeMixedIntegerLinearProgram.get_values()
.
OUTPUT:
A DiGraph representing the orientation if it exists. A
ValueError
exception is raised otherwise.ALGORITHM:
The problem is solved through a maximum flow :
Given a graph \(G\), we create a
DiGraph
\(D\) defined on \(E(G)\cup V(G)\cup \{s,t\}\). We then link \(s\) to all of \(V(G)\) (these edges having a capacity equal to the bound associated to each element of \(V(G)\)), and all the elements of \(E(G)\) to \(t\) . We then link each \(v \in V(G)\) to each of its incident edges in \(G\). A maximum integer flow of value \(|E(G)|\) corresponds to an admissible orientation of \(G\). Otherwise, none exists.EXAMPLES:
There is always an orientation of a graph \(G\) such that a vertex \(v\) has out-degree at most \(\lceil \frac {d(v)} 2 \rceil\):
sage: g = graphs.RandomGNP(40, .4) sage: b = lambda v: integer_ceil(g.degree(v)/2) sage: D = g.bounded_outdegree_orientation(b) sage: all(D.out_degree(v) <= b(v) for v in g) True
>>> from sage.all import * >>> g = graphs.RandomGNP(Integer(40), RealNumber('.4')) >>> b = lambda v: integer_ceil(g.degree(v)/Integer(2)) >>> D = g.bounded_outdegree_orientation(b) >>> all(D.out_degree(v) <= b(v) for v in g) True
g = graphs.RandomGNP(40, .4) b = lambda v: integer_ceil(g.degree(v)/2) D = g.bounded_outdegree_orientation(b) all(D.out_degree(v) <= b(v) for v in g)
Chvatal’s graph, being 4-regular, can be oriented in such a way that its maximum out-degree is 2:
sage: g = graphs.ChvatalGraph() sage: D = g.bounded_outdegree_orientation(2) sage: max(D.out_degree()) 2
>>> from sage.all import * >>> g = graphs.ChvatalGraph() >>> D = g.bounded_outdegree_orientation(Integer(2)) >>> max(D.out_degree()) 2
g = graphs.ChvatalGraph() D = g.bounded_outdegree_orientation(2) max(D.out_degree())
For any graph \(G\), it is possible to compute an orientation such that the maximum out-degree is at most the maximum average degree of \(G\) divided by 2. Anything less, though, is impossible.
sage: g = graphs.RandomGNP(40, .4) sage: mad = g.maximum_average_degree() # needs sage.numerical.mip
Hence this is possible
sage: d = g.bounded_outdegree_orientation(integer_ceil(mad/2)) # needs sage.numerical.mip
>>> from sage.all import * >>> d = g.bounded_outdegree_orientation(integer_ceil(mad/Integer(2))) # needs sage.numerical.mip
d = g.bounded_outdegree_orientation(integer_ceil(mad/2)) # needs sage.numerical.mip
While this is not:
sage: try: # needs sage.numerical.mip ....: g.bounded_outdegree_orientation(integer_ceil(mad/2-1)) ....: print("Error") ....: except ValueError: ....: pass
>>> from sage.all import * >>> try: # needs sage.numerical.mip ... g.bounded_outdegree_orientation(integer_ceil(mad/Integer(2)-Integer(1))) ... print("Error") ... except ValueError: ... pass
try: # needs sage.numerical.mip g.bounded_outdegree_orientation(integer_ceil(mad/2-1)) print("Error") except ValueError: pass
The bounds can be specified in different ways:
sage: g = graphs.PetersenGraph() sage: b = lambda v: integer_ceil(g.degree(v)/2) sage: D = g.bounded_outdegree_orientation(b) sage: b_dict = {u: b(u) for u in g} sage: D = g.bounded_outdegree_orientation(b_dict) sage: unique_bound = 2 sage: D = g.bounded_outdegree_orientation(unique_bound)
>>> from sage.all import * >>> g = graphs.PetersenGraph() >>> b = lambda v: integer_ceil(g.degree(v)/Integer(2)) >>> D = g.bounded_outdegree_orientation(b) >>> b_dict = {u: b(u) for u in g} >>> D = g.bounded_outdegree_orientation(b_dict) >>> unique_bound = Integer(2) >>> D = g.bounded_outdegree_orientation(unique_bound)
g = graphs.PetersenGraph() b = lambda v: integer_ceil(g.degree(v)/2) D = g.bounded_outdegree_orientation(b) b_dict = {u: b(u) for u in g} D = g.bounded_outdegree_orientation(b_dict) unique_bound = 2 D = g.bounded_outdegree_orientation(unique_bound)
- sage.graphs.orientations.eulerian_orientation(G)[source]¶
Return a DiGraph which is an Eulerian orientation of the graph \(G\).
An Eulerian graph being a graph such that any vertex has an even degree, an Eulerian orientation of a graph is an orientation of its edges such that each vertex \(v\) verifies \(d^+(v)=d^-(v)=d(v)/2\), where \(d^+\) and \(d^-\) respectively represent the out-degree and the in-degree of a vertex.
If the graph is not Eulerian, the orientation verifies for any vertex \(v\) that \(| d^+(v)-d^-(v) | \leq 1\).
INPUT:
G
– an undirected graph
ALGORITHM:
This algorithm is a random walk through the edges of the graph, which orients the edges according to the walk. When a vertex is reached which has no non-oriented edge (this vertex must have odd degree), the walk resumes at another vertex of odd degree, if any.
This algorithm has time complexity in \(O(n+m)\) for
SparseGraph
and \(O(n^2)\) forDenseGraph
, where \(m\) is the number of edges in the graph and \(n\) is the number of vertices in the graph.EXAMPLES:
The CubeGraph with parameter 4, which is regular of even degree, has an Eulerian orientation such that \(d^+ = d^-\):
sage: g = graphs.CubeGraph(4) sage: g.degree() [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4] sage: o = g.eulerian_orientation() sage: o.in_degree() [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] sage: o.out_degree() [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
>>> from sage.all import * >>> g = graphs.CubeGraph(Integer(4)) >>> g.degree() [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4] >>> o = g.eulerian_orientation() >>> o.in_degree() [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] >>> o.out_degree() [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
g = graphs.CubeGraph(4) g.degree() o = g.eulerian_orientation() o.in_degree() o.out_degree()
Secondly, the Petersen Graph, which is 3 regular has an orientation such that the difference between \(d^+\) and \(d^-\) is at most 1:
sage: g = graphs.PetersenGraph() sage: o = g.eulerian_orientation() sage: o.in_degree() [2, 2, 2, 2, 2, 1, 1, 1, 1, 1] sage: o.out_degree() [1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
>>> from sage.all import * >>> g = graphs.PetersenGraph() >>> o = g.eulerian_orientation() >>> o.in_degree() [2, 2, 2, 2, 2, 1, 1, 1, 1, 1] >>> o.out_degree() [1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
g = graphs.PetersenGraph() o = g.eulerian_orientation() o.in_degree() o.out_degree()
- sage.graphs.orientations.minimum_outdegree_orientation(G, use_edge_labels, solver=False, verbose=None, integrality_tolerance=0)[source]¶
Return an orientation of \(G\) with the smallest possible maximum outdegree.
Given a Graph \(G\), it is polynomial to compute an orientation \(D\) of the edges of \(G\) such that the maximum out-degree in \(D\) is minimized. This problem, though, is NP-complete in the weighted case [AMOZ2006].
INPUT:
G
– an undirected graphuse_edge_labels
– boolean (default:False
)When set to
True
, uses edge labels as weights to compute the orientation and assumes a weight of \(1\) when there is no value available for a given edge.When set to
False
(default), gives a weight of 1 to all the edges.
solver
– string (default:None
); specifies a Mixed Integer Linear Programming (MILP) solver to be used. If set toNone
, the default one is used. For more information on MILP solvers and which default solver is used, see the methodsolve
of the classMixedIntegerLinearProgram
.verbose
– integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.integrality_tolerance
– float; parameter for use with MILP solvers over an inexact base ring; seeMixedIntegerLinearProgram.get_values()
.
EXAMPLES:
Given a complete bipartite graph \(K_{n,m}\), the maximum out-degree of an optimal orientation is \(\left\lceil \frac {nm} {n+m}\right\rceil\):
sage: g = graphs.CompleteBipartiteGraph(3,4) sage: o = g.minimum_outdegree_orientation() # needs sage.numerical.mip sage: max(o.out_degree()) == integer_ceil((4*3)/(3+4)) # needs sage.numerical.mip True
>>> from sage.all import * >>> g = graphs.CompleteBipartiteGraph(Integer(3),Integer(4)) >>> o = g.minimum_outdegree_orientation() # needs sage.numerical.mip >>> max(o.out_degree()) == integer_ceil((Integer(4)*Integer(3))/(Integer(3)+Integer(4))) # needs sage.numerical.mip True
g = graphs.CompleteBipartiteGraph(3,4) o = g.minimum_outdegree_orientation() # needs sage.numerical.mip max(o.out_degree()) == integer_ceil((4*3)/(3+4)) # needs sage.numerical.mip
Show the influence of edge labels on the solution:
sage: # needs sage.numerical.mip sage: g = graphs.PetersenGraph() sage: o = g.minimum_outdegree_orientation(use_edge_labels=False) sage: max(o.out_degree()) 2 sage: _ = [g.set_edge_label(u, v, 1) for u, v in g.edge_iterator(labels=False)] sage: o = g.minimum_outdegree_orientation(use_edge_labels=True) sage: max(o.out_degree()) 2 sage: g.set_edge_label(0, 1, 100) sage: o = g.minimum_outdegree_orientation(use_edge_labels=True) sage: max(o.out_degree()) 3
>>> from sage.all import * >>> # needs sage.numerical.mip >>> g = graphs.PetersenGraph() >>> o = g.minimum_outdegree_orientation(use_edge_labels=False) >>> max(o.out_degree()) 2 >>> _ = [g.set_edge_label(u, v, Integer(1)) for u, v in g.edge_iterator(labels=False)] >>> o = g.minimum_outdegree_orientation(use_edge_labels=True) >>> max(o.out_degree()) 2 >>> g.set_edge_label(Integer(0), Integer(1), Integer(100)) >>> o = g.minimum_outdegree_orientation(use_edge_labels=True) >>> max(o.out_degree()) 3
# needs sage.numerical.mip g = graphs.PetersenGraph() o = g.minimum_outdegree_orientation(use_edge_labels=False) max(o.out_degree()) _ = [g.set_edge_label(u, v, 1) for u, v in g.edge_iterator(labels=False)] o = g.minimum_outdegree_orientation(use_edge_labels=True) max(o.out_degree()) g.set_edge_label(0, 1, 100) o = g.minimum_outdegree_orientation(use_edge_labels=True) max(o.out_degree())
- sage.graphs.orientations.orient(G, f, weighted=None, data_structure=None, sparse=None, immutable=None, hash_labels=None)[source]¶
Return an oriented version of \(G\) according the input function \(f\).
INPUT:
G
– an undirected graphf
– a function that inputs an edge and outputs an orientation of this edgeweighted
– boolean (default:None
); weightedness for the oriented digraph. By default (None
), the graph and its orientation will behave the same.sparse
– boolean (default:None
);sparse=True
is an alias fordata_structure="sparse"
, andsparse=False
is an alias fordata_structure="dense"
. Only used whendata_structure=None
.data_structure
– string (default:None
); one of'sparse'
,'static_sparse'
, or'dense'
. See the documentation ofDiGraph
.immutable
– boolean (default:None
); whether to create a mutable/immutable digraph. Only used whendata_structure=None
.immutable=None
(default) means that the graph and its orientation will behave the same way.immutable=True
is a shortcut fordata_structure='static_sparse'
immutable=False
means that the created digraph is mutable. When used to orient an immutable graph, the data structure used is'sparse'
unless anything else is specified.
hash_labels
– boolean (default:None
); whether to include edge labels during hashing of the oriented digraph. This parameter defaults toTrue
if the graph is weighted. This parameter is ignored when parameterimmutable
is notTrue
. Beware that trying to hash unhashable labels will raise an error.
OUTPUT: a
DiGraph
objectNote
This method behaves similarly to method
copy()
. That is, the returned digraph uses the same data structure by default, unless the user asks to use another data structure, and the attributes of the input graph are copied.EXAMPLES:
sage: G = graphs.CycleGraph(4); G Cycle graph: Graph on 4 vertices sage: D = G.orient(lambda e:e if e[0] < e[1] else (e[1], e[0], e[2])); D Orientation of Cycle graph: Digraph on 4 vertices sage: sorted(D.edges(labels=False)) [(0, 1), (0, 3), (1, 2), (2, 3)]
>>> from sage.all import * >>> G = graphs.CycleGraph(Integer(4)); G Cycle graph: Graph on 4 vertices >>> D = G.orient(lambda e:e if e[Integer(0)] < e[Integer(1)] else (e[Integer(1)], e[Integer(0)], e[Integer(2)])); D Orientation of Cycle graph: Digraph on 4 vertices >>> sorted(D.edges(labels=False)) [(0, 1), (0, 3), (1, 2), (2, 3)]
G = graphs.CycleGraph(4); G D = G.orient(lambda e:e if e[0] < e[1] else (e[1], e[0], e[2])); D sorted(D.edges(labels=False))
- sage.graphs.orientations.orientations(G, data_structure=None, sparse=None)[source]¶
Return an iterator over orientations of \(G\).
An orientation of an undirected graph is a directed graph such that every edge is assigned a direction. Hence there are \(2^s\) oriented digraphs for a simple graph with \(s\) edges.
INPUT:
G
– an undirected graphdata_structure
– one of'sparse'
,'static_sparse'
, or'dense'
; see the documentation ofGraph
orDiGraph
; default is the data structure of \(G\)sparse
– boolean (default:None
);sparse=True
is an alias fordata_structure="sparse"
, andsparse=False
is an alias fordata_structure="dense"
. By default (None
), guess the most suitable data structure.
Warning
This always considers multiple edges of graphs as distinguishable, and hence, may have repeated digraphs.
EXAMPLES:
sage: G = Graph([[1,2,3], [(1, 2, 'a'), (1, 3, 'b')]], format='vertices_and_edges') sage: it = G.orientations() sage: D = next(it) sage: D.edges(sort=True) [(1, 2, 'a'), (1, 3, 'b')] sage: D = next(it) sage: D.edges(sort=True) [(1, 2, 'a'), (3, 1, 'b')]
>>> from sage.all import * >>> G = Graph([[Integer(1),Integer(2),Integer(3)], [(Integer(1), Integer(2), 'a'), (Integer(1), Integer(3), 'b')]], format='vertices_and_edges') >>> it = G.orientations() >>> D = next(it) >>> D.edges(sort=True) [(1, 2, 'a'), (1, 3, 'b')] >>> D = next(it) >>> D.edges(sort=True) [(1, 2, 'a'), (3, 1, 'b')]
G = Graph([[1,2,3], [(1, 2, 'a'), (1, 3, 'b')]], format='vertices_and_edges') it = G.orientations() D = next(it) D.edges(sort=True) D = next(it) D.edges(sort=True)
- sage.graphs.orientations.random_orientation(G)[source]¶
Return a random orientation of a graph \(G\).
An orientation of an undirected graph is a directed graph such that every edge is assigned a direction. Hence there are \(2^m\) oriented digraphs for a simple graph with \(m\) edges.
INPUT:
G
– a Graph
EXAMPLES:
sage: from sage.graphs.orientations import random_orientation sage: G = graphs.PetersenGraph() sage: D = random_orientation(G) sage: D.order() == G.order(), D.size() == G.size() (True, True)
>>> from sage.all import * >>> from sage.graphs.orientations import random_orientation >>> G = graphs.PetersenGraph() >>> D = random_orientation(G) >>> D.order() == G.order(), D.size() == G.size() (True, True)
from sage.graphs.orientations import random_orientation G = graphs.PetersenGraph() D = random_orientation(G) D.order() == G.order(), D.size() == G.size()
- sage.graphs.orientations.strong_orientation(G)[source]¶
Return a strongly connected orientation of the graph \(G\).
An orientation of an undirected graph is a digraph obtained by giving an unique direction to each of its edges. An orientation is said to be strong if there is a directed path between each pair of vertices. See also the Wikipedia article Strongly_connected_component.
If the graph is 2-edge-connected, a strongly connected orientation can be found in linear time. If the given graph is not 2-connected, the orientation returned will ensure that each 2-connected component has a strongly connected orientation.
INPUT:
G
– an undirected graph
OUTPUT: a digraph representing an orientation of the current graph
Note
This method assumes that the input the graph is connected.
The time complexity is \(O(n+m)\) for
SparseGraph
and \(O(n^2)\) forDenseGraph
.
EXAMPLES:
For a 2-regular graph, a strong orientation gives to each vertex an out-degree equal to 1:
sage: g = graphs.CycleGraph(5) sage: g.strong_orientation().out_degree() [1, 1, 1, 1, 1]
>>> from sage.all import * >>> g = graphs.CycleGraph(Integer(5)) >>> g.strong_orientation().out_degree() [1, 1, 1, 1, 1]
g = graphs.CycleGraph(5) g.strong_orientation().out_degree()
The Petersen Graph is 2-edge connected. It then has a strongly connected orientation:
sage: g = graphs.PetersenGraph() sage: o = g.strong_orientation() sage: len(o.strongly_connected_components()) 1
>>> from sage.all import * >>> g = graphs.PetersenGraph() >>> o = g.strong_orientation() >>> len(o.strongly_connected_components()) 1
g = graphs.PetersenGraph() o = g.strong_orientation() len(o.strongly_connected_components())
The same goes for the CubeGraph in any dimension:
sage: all(len(graphs.CubeGraph(i).strong_orientation().strongly_connected_components()) == 1 ....: for i in range(2,6)) True
>>> from sage.all import * >>> all(len(graphs.CubeGraph(i).strong_orientation().strongly_connected_components()) == Integer(1) ... for i in range(Integer(2),Integer(6))) True
all(len(graphs.CubeGraph(i).strong_orientation().strongly_connected_components()) == 1 for i in range(2,6))
A multigraph also has a strong orientation:
sage: g = Graph([(0, 1), (0, 2), (1, 2)] * 2, multiedges=True) sage: g.strong_orientation() Multi-digraph on 3 vertices
>>> from sage.all import * >>> g = Graph([(Integer(0), Integer(1)), (Integer(0), Integer(2)), (Integer(1), Integer(2))] * Integer(2), multiedges=True) >>> g.strong_orientation() Multi-digraph on 3 vertices
g = Graph([(0, 1), (0, 2), (1, 2)] * 2, multiedges=True) g.strong_orientation()
- sage.graphs.orientations.strong_orientations_iterator(G)[source]¶
Return an iterator over all strong orientations of a graph \(G\).
A strong orientation of a graph is an orientation of its edges such that the obtained digraph is strongly connected (i.e. there exist a directed path between each pair of vertices). According to Robbins’ theorem (see the Wikipedia article Robbins_theorem), the graphs that have strong orientations are exactly the 2-edge-connected graphs (i.e., the bridgeless graphs).
ALGORITHM:
It is an adaptation of the algorithm published in [CGMRV16]. It runs in \(O(mn)\) amortized time, where \(m\) is the number of edges and \(n\) is the number of vertices. The amortized time can be improved to \(O(m)\) with a more involved method. In this function, first the graph is preprocessed and a spanning tree is generated. Then every orientation of the non-tree edges of the graph can be extended to at least one new strong orientation by orienting properly the edges of the spanning tree (this property is proved in [CGMRV16]). Therefore, this function generates all partial orientations of the non-tree edges and then launches a helper function corresponding to the generation algorithm described in [CGMRV16]. In order to avoid trivial symmetries, the orientation of an arbitrary edge is fixed before the start of the enumeration process.
INPUT:
G
– an undirected graph
OUTPUT: an iterator which will produce all strong orientations of this graph
Note
Works only for simple graphs (no multiple edges). To avoid symmetries an orientation of an arbitrary edge is fixed.
EXAMPLES:
A cycle has one possible (non-symmetric) strong orientation:
sage: g = graphs.CycleGraph(4) sage: it = g.strong_orientations_iterator() sage: len(list(it)) 1
>>> from sage.all import * >>> g = graphs.CycleGraph(Integer(4)) >>> it = g.strong_orientations_iterator() >>> len(list(it)) 1
g = graphs.CycleGraph(4) it = g.strong_orientations_iterator() len(list(it))
A tree cannot be strongly oriented:
sage: g = graphs.RandomTree(10) sage: len(list(g.strong_orientations_iterator())) 0
>>> from sage.all import * >>> g = graphs.RandomTree(Integer(10)) >>> len(list(g.strong_orientations_iterator())) 0
g = graphs.RandomTree(10) len(list(g.strong_orientations_iterator()))
Neither can be a disconnected graph:
sage: g = graphs.CompleteGraph(6) sage: g.add_vertex(7) sage: len(list(g.strong_orientations_iterator())) 0
>>> from sage.all import * >>> g = graphs.CompleteGraph(Integer(6)) >>> g.add_vertex(Integer(7)) >>> len(list(g.strong_orientations_iterator())) 0
g = graphs.CompleteGraph(6) g.add_vertex(7) len(list(g.strong_orientations_iterator()))