Skip to content

Commit

Permalink
sagemathgh-35903: Add parameter key to methods multiple_edges and edg…
Browse files Browse the repository at this point in the history
…e_boundary

    
Part of sagemath#35902.

### 📚 Description

We add parameter `key` to methods `multiple_edges` and `edge_boundary`.
This is useful when users manipulate graphs with edges of incomparable
types.

On the way, we avoid some calls to `multiple_edges` with `sort=True` in
method `is_tree`.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. It should be `[x]` not `[x
]`. -->

- [x] The title is concise, informative, and self-explanatory.
- [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 accordingly.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on
- sagemath#12345: short description why this is a dependency
- sagemath#34567: ...
-->

<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
    
URL: sagemath#35903
Reported by: David Coudert
Reviewer(s): Dima Pasechnik
  • Loading branch information
Release Manager committed Aug 10, 2023
2 parents beafba9 + ee2084f commit e5e889a
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 14 deletions.
76 changes: 68 additions & 8 deletions src/sage/graphs/generic_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -3515,7 +3515,7 @@ def allow_multiple_edges(self, new, check=True, keep_label='any'):

self._backend.multiple_edges(new)

def multiple_edges(self, to_undirected=False, labels=True, sort=False):
def multiple_edges(self, to_undirected=False, labels=True, sort=False, key=None):
"""
Return any multiple edges in the (di)graph.

Expand All @@ -3525,7 +3525,11 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False):

- ``labels`` -- boolean (default: ``True``); whether to include labels

- ``sort`` - boolean (default: ``False``); whether to sort the result
- ``sort`` -- boolean (default: ``False``); whether to sort the result

- ``key`` -- a function (default: ``None``); a function that takes an
edge as its one argument and returns a value that can be used for
comparisons in the sorting algorithm (we must have ``sort=True``)

EXAMPLES::

Expand Down Expand Up @@ -3574,7 +3578,36 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False):
[]
sage: G.multiple_edges(to_undirected=True, sort=True)
[(1, 2, 'h'), (2, 1, 'g')]

Using the ``key`` argument to order multiple edges of incomparable
types (see :trac:`35903`)::

sage: G = Graph([('A', 'B', 3), (1, 2, 1), ('A', 'B', 4), (1, 2, 2)], multiedges=True)
sage: G.multiple_edges(sort=True)
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for <: 'Integer Ring' and '<class 'str'>'
sage: G.multiple_edges(labels=False, sort=True, key=str)
[('A', 'B'), ('A', 'B'), (1, 2), (1, 2)]
sage: G.multiple_edges(sort=True, key=str)
[('A', 'B', 3), ('A', 'B', 4), (1, 2, 1), (1, 2, 2)]
sage: G.multiple_edges(labels=True, sort=True, key=lambda e:e[2])
[(1, 2, 1), (1, 2, 2), ('A', 'B', 3), ('A', 'B', 4)]
sage: G.multiple_edges(labels=False, sort=True, key=lambda e:e[2])
Traceback (most recent call last):
...
IndexError: tuple index out of range

TESTS::

sage: Graph().multiple_edges(sort=False, key=str)
Traceback (most recent call last):
...
ValueError: sort keyword is False, yet a key function is given
"""
if (not sort) and key:
raise ValueError('sort keyword is False, yet a key function is given')

multi_edges = []
seen = set()

Expand Down Expand Up @@ -3647,7 +3680,7 @@ def multiple_edges(self, to_undirected=False, labels=True, sort=False):
multi_edges.extend((u, v) for _ in L)

if sort:
multi_edges.sort()
return sorted(multi_edges, key=key)
return multi_edges

def name(self, new=None):
Expand Down Expand Up @@ -5092,7 +5125,7 @@ def cycle_basis(self, output='vertex'):
sage: G.cycle_basis() # needs networkx
[[0, 2], [2, 1, 0]]
sage: G.cycle_basis(output='edge') # needs networkx
[[(0, 2, 'a'), (2, 0, 'b')], [(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')]]
[[(0, 2, 'b'), (2, 0, 'a')], [(2, 1, 'd'), (1, 0, 'c'), (0, 2, 'a')]]
sage: H = Graph([(1, 2), (2, 3), (2, 3), (3, 4), (1, 4),
....: (1, 4), (4, 5), (5, 6), (4, 6), (6, 7)], multiedges=True)
sage: H.cycle_basis() # needs networkx
Expand Down Expand Up @@ -5134,10 +5167,9 @@ def cycle_basis(self, output='vertex'):
sage: G.cycle_basis() # needs networkx
[[2, 3], [4, 3, 2, 1], [4, 3, 2, 1]]
sage: G.cycle_basis(output='edge') # needs networkx
[[(2, 3, 'b'), (3, 2, 'c')],
[[(2, 3, 'c'), (3, 2, 'b')],
[(4, 3, 'd'), (3, 2, 'b'), (2, 1, 'a'), (1, 4, 'f')],
[(4, 3, 'e'), (3, 2, 'b'), (2, 1, 'a'), (1, 4, 'f')]]

"""
if output not in ['vertex', 'edge']:
raise ValueError('output must be either vertex or edge')
Expand Down Expand Up @@ -12568,7 +12600,7 @@ def edges(self, vertices=None, labels=True, sort=None, key=None,
return EdgesView(self, vertices=vertices, labels=labels, sort=sort, key=key,
ignore_direction=ignore_direction, sort_vertices=sort_vertices)

def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False, key=None):
r"""
Return a list of edges ``(u,v,l)`` with ``u`` in ``vertices1``
and ``v`` in ``vertices2``.
Expand All @@ -12586,6 +12618,10 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):

- ``sort`` -- boolean (default: ``False``); whether to sort the result

- ``key`` -- a function (default: ``None``); a function that takes an
edge as its one argument and returns a value that can be used for
comparisons in the sorting algorithm (we must have ``sort=True``)

EXAMPLES::

sage: K = graphs.CompleteBipartiteGraph(9, 3)
Expand All @@ -12610,6 +12646,23 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
sage: D.edge_boundary([0], labels=False, sort=True)
[(0, 1), (0, 2)]

Using the ``key`` argument to order multiple edges of incomparable
types (see :trac:`35903`)::

sage: G = Graph([(1, 'A', 4), (1, 2, 3)])
sage: G.edge_boundary([1], sort=True)
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for <: 'Integer Ring' and '<class 'str'>'
sage: G.edge_boundary([1], sort=True, key=str)
[('A', 1, 4), (1, 2, 3)]
sage: G.edge_boundary([1], sort=True, key=lambda e:e[2])
[(1, 2, 3), ('A', 1, 4)]
sage: G.edge_boundary([1], labels=False, sort=True, key=lambda e:e[2])
Traceback (most recent call last):
...
IndexError: tuple index out of range

TESTS::

sage: G = graphs.DiamondGraph()
Expand All @@ -12619,7 +12672,14 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
[]
sage: G.edge_boundary([2], [0])
[(0, 2, None)]
sage: G.edge_boundary([2], [0], sort=False, key=str)
Traceback (most recent call last):
...
ValueError: sort keyword is False, yet a key function is given
"""
if (not sort) and key:
raise ValueError('sort keyword is False, yet a key function is given')

vertices1 = set(v for v in vertices1 if v in self)
if self._directed:
if vertices2 is not None:
Expand All @@ -12639,7 +12699,7 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False):
output = [e for e in self.edges(vertices=vertices1, labels=labels, sort=False)
if e[1] not in vertices1 or e[0] not in vertices1]
if sort:
output.sort()
return sorted(output, key=key)
return output

def edge_iterator(self, vertices=None, labels=True, ignore_direction=False, sort_vertices=True):
Expand Down
28 changes: 22 additions & 6 deletions src/sage/graphs/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -1524,7 +1524,7 @@ def is_tree(self, certificate=False, output='vertex'):
sage: G.is_tree(certificate=True)
(False, [1, 2])
sage: G.is_tree(certificate=True, output='edge')
(False, [(1, 2, 'a'), (2, 1, 'b')])
(False, [(1, 2, 'b'), (2, 1, 'a')])
TESTS:
Expand Down Expand Up @@ -1552,6 +1552,16 @@ def is_tree(self, certificate=False, output='vertex'):
(False, [0])
sage: G.is_tree(certificate=True, output='edge')
(False, [(0, 0, None)])
Case of edges with incomparable types (see :trac:`35903`)::
sage: G = Graph(multiedges=True)
sage: G.add_cycle(['A', 1, 2, 3])
sage: G.add_cycle(['A', 1, 2, 3])
sage: G.is_tree(certificate=True, output='vertex')
(False, ['A', 1])
sage: G.is_tree(certificate=True, output='edge')
(False, [('A', 1, None), (1, 'A', None)])
"""
if output not in ['vertex', 'edge']:
raise ValueError('output must be either vertex or edge')
Expand All @@ -1569,12 +1579,18 @@ def is_tree(self, certificate=False, output='vertex'):
return False, L[:1]

if self.has_multiple_edges():
multiple_edges = self.multiple_edges(sort=False)
if output == 'vertex':
return (False, list(self.multiple_edges(sort=True)[0][:2]))
edge1, edge2 = self.multiple_edges(sort=True)[:2]
if edge1[0] != edge2[0]:
return (False, [edge1, edge2])
return (False, [edge1, (edge2[1], edge2[0], edge2[2])])
return (False, list(multiple_edges[0][:2]))
# Search for 2 edges between u and v.
# We do this way to handle the case of edges with incomparable
# types
u1, v1, w1 = multiple_edges[0]
for u2, v2, w2 in multiple_edges[1:]:
if u1 == u2 and v1 == v2:
return (False, [(u1, v1, w1), (v2, u2, w2)])
elif u1 == v2 and v1 == u2:
return (False, [(u1, v1, w1), (u2, v2, w2)])

if output == 'edge':
if self.allows_multiple_edges():
Expand Down

0 comments on commit e5e889a

Please sign in to comment.