Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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