Skip to content

Commit 6537374

Browse files
committed
add method orient to Graph
1 parent b0f09d9 commit 6537374

File tree

2 files changed

+174
-2
lines changed

2 files changed

+174
-2
lines changed

src/sage/graphs/graph.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9596,7 +9596,10 @@ def bipartite_double(self, extended=False):
95969596
from sage.graphs.tutte_polynomial import tutte_polynomial
95979597
from sage.graphs.lovasz_theta import lovasz_theta
95989598
from sage.graphs.partial_cube import is_partial_cube
9599-
from sage.graphs.orientations import strong_orientations_iterator, random_orientation, acyclic_orientations
9599+
from sage.graphs.orientations import orient
9600+
from sage.graphs.orientations import strong_orientations_iterator
9601+
from sage.graphs.orientations import random_orientation
9602+
from sage.graphs.orientations import acyclic_orientations
96009603
from sage.graphs.connectivity import bridges, cleave, spqr_tree
96019604
from sage.graphs.connectivity import is_triconnected
96029605
from sage.graphs.comparability import is_comparability
@@ -9646,6 +9649,7 @@ def bipartite_double(self, extended=False):
96469649
"is_permutation" : "Graph properties",
96479650
"tutte_polynomial" : "Algorithmically hard stuff",
96489651
"lovasz_theta" : "Leftovers",
9652+
"orient" : "Connectivity, orientations, trees",
96499653
"strong_orientations_iterator" : "Connectivity, orientations, trees",
96509654
"random_orientation" : "Connectivity, orientations, trees",
96519655
"acyclic_orientations" : "Connectivity, orientations, trees",

src/sage/graphs/orientations.py

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
:widths: 30, 70
1313
:delim: |
1414
15+
:meth:`orient` | Return an oriented version of `G` according the input function `f`.
16+
:meth:`acyclic_orientations` | Return an iterator over all acyclic orientations of an undirected graph `G`.
1517
:meth:`strong_orientations_iterator` | Return an iterator over all strong orientations of a graph `G`
1618
:meth:`random_orientation` | Return a random orientation of a graph `G`
1719
@@ -28,7 +30,7 @@
2830
# ****************************************************************************
2931
# Copyright (C) 2017 Kolja Knauer <[email protected]>
3032
# 2017 Petru Valicov <[email protected]>
31-
# 2017-2023 David Coudert <[email protected]>
33+
# 2017-2024 David Coudert <[email protected]>
3234
#
3335
# This program is free software: you can redistribute it and/or modify
3436
# it under the terms of the GNU General Public License as published by
@@ -41,6 +43,172 @@
4143
from sage.graphs.digraph import DiGraph
4244

4345

46+
def orient(G, f, weighted=None, data_structure=None, sparse=None,
47+
immutable=None, hash_labels=None):
48+
r"""
49+
Return an oriented version of `G` according the input function `f`.
50+
51+
INPUT:
52+
53+
- ``f`` -- a function that inputs an edge and outputs an orientation of this
54+
edge
55+
56+
- ``weighted`` -- boolean (default: ``None``); weightedness for the oriented
57+
digraph. By default (``None``), the graph and its orientation will behave
58+
the same.
59+
60+
- ``sparse`` -- boolean (default: ``None``); ``sparse=True`` is an alias for
61+
``data_structure="sparse"``, and ``sparse=False`` is an alias for
62+
``data_structure="dense"``. Only used when ``data_structure=None``.
63+
64+
- ``data_structure`` -- string (default: ``None``); one of ``'sparse'``,
65+
``'static_sparse'``, or ``'dense'``. See the documentation of
66+
:class:`DiGraph`.
67+
68+
- ``immutable`` -- boolean (default: ``None``); whether to create a
69+
mutable/immutable digraph. Only used when ``data_structure=None``.
70+
71+
* ``immutable=None`` (default) means that the graph and its orientation
72+
will behave the same way.
73+
74+
* ``immutable=True`` is a shortcut for ``data_structure='static_sparse'``
75+
76+
* ``immutable=False`` means that the created digraph is mutable. When used
77+
to orient an immutable graph, the data structure used is ``'sparse'``
78+
unless anything else is specified.
79+
80+
- ``hash_labels`` -- boolean (default: ``None``); whether to include edge
81+
labels during hashing of the oriented digraph. This parameter defaults to
82+
``True`` if the graph is weighted. This parameter is ignored when
83+
parameter ``immutable`` is not ``True``. Beware that trying to hash
84+
unhashable labels will raise an error.
85+
86+
OUTPUT: a :class:`DiGraph` object
87+
88+
.. NOTE::
89+
90+
This method behaves similarly to method
91+
:meth:`~sage.graphs.generic_graph.GenericGraph.copy`. That is, the
92+
returned digraph uses the same data structure by default, unless the
93+
user asks to use another data structure, and the attributes of the input
94+
graph are copied.
95+
96+
EXAMPLES::
97+
98+
sage: G = graphs.CycleGraph(4); G
99+
Cycle graph: Graph on 4 vertices
100+
sage: D = G.orient(lambda e:e if e[0] < e[1] else (e[1], e[0], e[2])); D
101+
Orientation of Cycle graph: Digraph on 4 vertices
102+
sage: sorted(D.edges(labels=False))
103+
[(0, 1), (0, 3), (1, 2), (2, 3)]
104+
105+
TESTS:
106+
107+
We make sure that one can get an immutable orientation by providing the
108+
``data_structure`` optional argument::
109+
110+
sage: def foo(e):
111+
....: return e if e[0] < e[1] else (e[1], e[0], e[2])
112+
sage: G = graphs.CycleGraph(4)
113+
sage: D = G.orient(foo, data_structure='static_sparse')
114+
sage: D.is_immutable()
115+
True
116+
sage: D = G.orient(foo, immutable=True)
117+
sage: D.is_immutable()
118+
True
119+
120+
Bad input::
121+
122+
sage: G.orient(foo, data_structure='sparse', sparse=False)
123+
Traceback (most recent call last):
124+
...
125+
ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
126+
sage: G.orient(foo, data_structure='sparse', immutable=True)
127+
Traceback (most recent call last):
128+
...
129+
ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
130+
sage: G.orient(foo, immutable=True, sparse=False)
131+
Traceback (most recent call last):
132+
...
133+
ValueError: there is no dense immutable backend at the moment
134+
135+
Which backend? ::
136+
137+
sage: G.orient(foo, data_structure='sparse')._backend
138+
<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
139+
sage: G.orient(foo, data_structure='dense')._backend
140+
<sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
141+
sage: G.orient(foo, data_structure='static_sparse')._backend
142+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
143+
sage: G.orient(foo, immutable=True)._backend
144+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
145+
sage: G.orient(foo, immutable=True, sparse=True)._backend
146+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
147+
sage: G.orient(foo, immutable=False, sparse=True)._backend
148+
<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
149+
sage: G.orient(foo, immutable=False, sparse=False)._backend
150+
<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
151+
"""
152+
# Which data structure should be used ?
153+
if data_structure is not None:
154+
# data_structure is already defined so there is nothing left to do
155+
# here. Did the user try to define too much ?
156+
if immutable is not None or sparse is not None:
157+
raise ValueError("you cannot define 'immutable' or 'sparse' "
158+
"when 'data_structure' has a value")
159+
# At this point, data_structure is None.
160+
elif immutable is True:
161+
data_structure = 'static_sparse'
162+
if sparse is False:
163+
raise ValueError("there is no dense immutable backend at the moment")
164+
elif immutable is False:
165+
# If the user requests a mutable digraph and input is immutable, we
166+
# choose the 'sparse' cgraph backend. Unless the user explicitly
167+
# asked for something different.
168+
if G.is_immutable():
169+
data_structure = 'dense' if sparse is False else 'sparse'
170+
elif sparse is True:
171+
data_structure = "sparse"
172+
elif sparse is False:
173+
data_structure = "dense"
174+
175+
if data_structure is None:
176+
from sage.graphs.base.dense_graph import DenseGraphBackend
177+
if isinstance(G._backend, DenseGraphBackend):
178+
data_structure = "dense"
179+
else:
180+
data_structure = "sparse"
181+
182+
if weighted is None:
183+
weighted = G.weighted()
184+
185+
edges = (f(e) for e in G.edge_iterator())
186+
D = DiGraph([G, edges], format='vertices_and_edges',
187+
data_structure=data_structure,
188+
loops=G.allows_loops(),
189+
multiedges=G.allows_multiple_edges(),
190+
name=f"Orientation of {G.name()}",
191+
pos=copy(G._pos), weighted=weighted,
192+
hash_labels=hash_labels)
193+
194+
attributes_to_copy = ('_assoc', '_embedding')
195+
for attr in attributes_to_copy:
196+
if hasattr(G, attr):
197+
copy_attr = {}
198+
old_attr = getattr(G, attr)
199+
if isinstance(old_attr, dict):
200+
for v, value in old_attr.items():
201+
try:
202+
copy_attr[v] = value.copy()
203+
except AttributeError:
204+
copy_attr[v] = copy(value)
205+
setattr(D, attr, copy_attr)
206+
else:
207+
setattr(D, attr, copy(old_attr))
208+
209+
return D
210+
211+
44212
def acyclic_orientations(G):
45213
r"""
46214
Return an iterator over all acyclic orientations of an undirected graph `G`.

0 commit comments

Comments
 (0)