Skip to content

Commit

Permalink
sagemathgh-38778: add method orient to Graph
Browse files Browse the repository at this point in the history
    
As suggested by @maxale in
sagemath#38758 (comment),
we add a method to apply an orientation function to a graph and obtain
the corresponding directed graph.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation and checked the documentation
preview.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - sagemath#12345: short description why this is a dependency -->
<!-- - sagemath#34567: ... -->
    
URL: sagemath#38778
Reported by: David Coudert
Reviewer(s): Travis Scrimshaw
  • Loading branch information
Release Manager committed Oct 11, 2024
2 parents f69dd69 + bb3749d commit e61d20c
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 2 deletions.
6 changes: 5 additions & 1 deletion src/sage/graphs/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -9690,7 +9690,10 @@ def bipartite_double(self, extended=False):
from sage.graphs.tutte_polynomial import tutte_polynomial
from sage.graphs.lovasz_theta import lovasz_theta
from sage.graphs.partial_cube import is_partial_cube
from sage.graphs.orientations import strong_orientations_iterator, random_orientation, acyclic_orientations
from sage.graphs.orientations import orient
from sage.graphs.orientations import strong_orientations_iterator
from sage.graphs.orientations import random_orientation
from sage.graphs.orientations import acyclic_orientations
from sage.graphs.connectivity import bridges, cleave, spqr_tree
from sage.graphs.connectivity import is_triconnected
from sage.graphs.comparability import is_comparability
Expand Down Expand Up @@ -9740,6 +9743,7 @@ def bipartite_double(self, extended=False):
"is_permutation" : "Graph properties",
"tutte_polynomial" : "Algorithmically hard stuff",
"lovasz_theta" : "Leftovers",
"orient" : "Connectivity, orientations, trees",
"strong_orientations_iterator" : "Connectivity, orientations, trees",
"random_orientation" : "Connectivity, orientations, trees",
"acyclic_orientations" : "Connectivity, orientations, trees",
Expand Down
181 changes: 180 additions & 1 deletion src/sage/graphs/orientations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
:widths: 30, 70
:delim: |
:meth:`orient` | Return an oriented version of `G` according the input function `f`.
:meth:`acyclic_orientations` | Return an iterator over all acyclic orientations of an undirected graph `G`.
:meth:`strong_orientations_iterator` | Return an iterator over all strong orientations of a graph `G`
:meth:`random_orientation` | Return a random orientation of a graph `G`
Expand All @@ -28,7 +30,7 @@
# ****************************************************************************
# Copyright (C) 2017 Kolja Knauer <kolja.knauer@gmail.com>
# 2017 Petru Valicov <petru.valicov@lirmm.fr>
# 2017-2023 David Coudert <david.coudert@inria.fr>
# 2017-2024 David Coudert <david.coudert@inria.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -41,6 +43,183 @@
from sage.graphs.digraph import DiGraph


def orient(G, f, weighted=None, data_structure=None, sparse=None,
immutable=None, hash_labels=None):
r"""
Return an oriented version of `G` according the input function `f`.
INPUT:
- ``G`` -- an undirected graph
- ``f`` -- a function that inputs an edge and outputs an orientation of this
edge
- ``weighted`` -- 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 for
``data_structure="sparse"``, and ``sparse=False`` is an alias for
``data_structure="dense"``. Only used when ``data_structure=None``.
- ``data_structure`` -- string (default: ``None``); one of ``'sparse'``,
``'static_sparse'``, or ``'dense'``. See the documentation of
:class:`DiGraph`.
- ``immutable`` -- boolean (default: ``None``); whether to create a
mutable/immutable digraph. Only used when ``data_structure=None``.
* ``immutable=None`` (default) means that the graph and its orientation
will behave the same way.
* ``immutable=True`` is a shortcut for ``data_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 to
``True`` if the graph is weighted. This parameter is ignored when
parameter ``immutable`` is not ``True``. Beware that trying to hash
unhashable labels will raise an error.
OUTPUT: a :class:`DiGraph` object
.. NOTE::
This method behaves similarly to method
:meth:`~sage.graphs.generic_graph.GenericGraph.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)]
TESTS:
We make sure that one can get an immutable orientation by providing the
``data_structure`` optional argument::
sage: def foo(e):
....: return e if e[0] < e[1] else (e[1], e[0], e[2])
sage: G = graphs.CycleGraph(4)
sage: D = G.orient(foo, data_structure='static_sparse')
sage: D.is_immutable()
True
sage: D = G.orient(foo, immutable=True)
sage: D.is_immutable()
True
Bad input::
sage: G.orient(foo, data_structure='sparse', sparse=False)
Traceback (most recent call last):
...
ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
sage: G.orient(foo, data_structure='sparse', immutable=True)
Traceback (most recent call last):
...
ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
sage: G.orient(foo, immutable=True, sparse=False)
Traceback (most recent call last):
...
ValueError: there is no dense immutable backend at the moment
Which backend? ::
sage: G.orient(foo, data_structure='sparse')._backend
<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
sage: G.orient(foo, data_structure='dense')._backend
<sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
sage: G.orient(foo, data_structure='static_sparse')._backend
<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
sage: G.orient(foo, immutable=True)._backend
<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
sage: G.orient(foo, immutable=True, sparse=True)._backend
<sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
sage: G.orient(foo, immutable=False, sparse=True)._backend
<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
sage: G.orient(foo, immutable=False, sparse=False)._backend
<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
sage: G.orient(foo, data_structure=None, immutable=None, sparse=True)._backend
<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
sage: G.orient(foo, data_structure=None, immutable=None, sparse=False)._backend
<sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
sage: G.orient(foo, data_structure=None, immutable=None, sparse=None)._backend
<sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
sage: H = Graph(data_structure='dense')
sage: H.orient(foo, data_structure=None, immutable=None, sparse=None)._backend
<sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
"""
# Which data structure should be used ?
if data_structure is not None:
# data_structure is already defined so there is nothing left to do
# here. Did the user try to define too much ?
if immutable is not None or sparse is not None:
raise ValueError("you cannot define 'immutable' or 'sparse' "
"when 'data_structure' has a value")
# At this point, data_structure is None.
elif immutable is True:
data_structure = 'static_sparse'
if sparse is False:
raise ValueError("there is no dense immutable backend at the moment")
elif immutable is False:
# If the user requests a mutable digraph and input is immutable, we
# choose the 'sparse' cgraph backend. Unless the user explicitly
# asked for something different.
if G.is_immutable():
data_structure = 'dense' if sparse is False else 'sparse'
elif sparse is True:
data_structure = "sparse"
elif sparse is False:
data_structure = "dense"

if data_structure is None:
from sage.graphs.base.dense_graph import DenseGraphBackend
if isinstance(G._backend, DenseGraphBackend):
data_structure = "dense"
else:
data_structure = "sparse"

if weighted is None:
weighted = G.weighted()

edges = (f(e) for e in G.edge_iterator())
D = DiGraph([G, edges], format='vertices_and_edges',
data_structure=data_structure,
loops=G.allows_loops(),
multiedges=G.allows_multiple_edges(),
name=f"Orientation of {G.name()}",
pos=copy(G._pos), weighted=weighted,
hash_labels=hash_labels)

attributes_to_copy = ('_assoc', '_embedding')
for attr in attributes_to_copy:
if hasattr(G, attr):
copy_attr = {}
old_attr = getattr(G, attr)
if isinstance(old_attr, dict):
for v, value in old_attr.items():
try:
copy_attr[v] = value.copy()
except AttributeError:
copy_attr[v] = copy(value)
setattr(D, attr, copy_attr)
else:
setattr(D, attr, copy(old_attr))

return D


def acyclic_orientations(G):
r"""
Return an iterator over all acyclic orientations of an undirected graph `G`.
Expand Down

0 comments on commit e61d20c

Please sign in to comment.