@@ -20094,7 +20094,7 @@ def disjunctive_product(self, other):
20094
20094
G.add_edge((u, v), (w, x))
20095
20095
return G
20096
20096
20097
- def transitive_closure(self, loops=True ):
20097
+ def transitive_closure(self, loops=None, immutable=None ):
20098
20098
r"""
20099
20099
Return the transitive closure of the (di)graph.
20100
20100
@@ -20106,11 +20106,18 @@ def transitive_closure(self, loops=True):
20106
20106
acyclic graph is a directed acyclic graph representing the full partial
20107
20107
order.
20108
20108
20109
- .. NOTE: :
20109
+ INPUT :
20110
20110
20111
- If the (di)graph allows loops, its transitive closure will by
20112
- default have one loop edge per vertex. This can be prevented by
20113
- disallowing loops in the (di)graph (``self.allow_loops(False)``).
20111
+ - ``loops`` -- boolean (default: ``None``); whether to allow loops in
20112
+ the returned (di)graph. By default (``None``), if the (di)graph allows
20113
+ loops, its transitive closure will have one loop edge per vertex. This
20114
+ can be prevented by disallowing loops in the (di)graph
20115
+ (``self.allow_loops(False)``).
20116
+
20117
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
20118
+ mutable/immutable transitive closure. ``immutable=None`` (default)
20119
+ means that the (di)graph and its transitive closure will behave the
20120
+ same way.
20114
20121
20115
20122
EXAMPLES::
20116
20123
@@ -20138,21 +20145,49 @@ def transitive_closure(self, loops=True):
20138
20145
sage: G.transitive_closure().loop_edges(labels=False)
20139
20146
[(0, 0), (1, 1), (2, 2)]
20140
20147
20141
- ::
20148
+ Check the behavior of parameter ``loops`` ::
20142
20149
20143
20150
sage: G = graphs.CycleGraph(3)
20144
20151
sage: G.transitive_closure().loop_edges(labels=False)
20145
20152
[]
20153
+ sage: G.transitive_closure(loops=True).loop_edges(labels=False)
20154
+ [(0, 0), (1, 1), (2, 2)]
20146
20155
sage: G.allow_loops(True)
20147
20156
sage: G.transitive_closure().loop_edges(labels=False)
20148
20157
[(0, 0), (1, 1), (2, 2)]
20158
+ sage: G.transitive_closure(loops=False).loop_edges(labels=False)
20159
+ []
20160
+
20161
+ Check the behavior of parameter `ìmmutable``::
20162
+
20163
+ sage: G = Graph([(0, 1)])
20164
+ sage: G.transitive_closure().is_immutable()
20165
+ False
20166
+ sage: G.transitive_closure(immutable=True).is_immutable()
20167
+ True
20168
+ sage: G = Graph([(0, 1)], immutable=True)
20169
+ sage: G.transitive_closure().is_immutable()
20170
+ True
20171
+ sage: G.transitive_closure(immutable=False).is_immutable()
20172
+ False
20149
20173
"""
20150
- G = copy(self)
20151
- G.name('Transitive closure of ' + self.name())
20152
- G.add_edges(((u, v) for u in G for v in G.breadth_first_search(u)), loops=None)
20153
- return G
20174
+ name = f"Transitive closure of {self.name()}"
20175
+ if immutable is None:
20176
+ immutable = self.is_immutable()
20177
+ if loops is None:
20178
+ loops = self.allows_loops()
20179
+ if loops:
20180
+ edges = ((u, v) for u in self for v in self.depth_first_search(u))
20181
+ else:
20182
+ edges = ((u, v) for u in self for v in self.depth_first_search(u) if u != v)
20183
+ if self.is_directed():
20184
+ from sage.graphs.digraph import DiGraph as GT
20185
+ else:
20186
+ from sage.graphs.graph import Graph as GT
20187
+ return GT([self, edges], format='vertices_and_edges', loops=loops,
20188
+ immutable=immutable, name=name)
20154
20189
20155
- def transitive_reduction(self):
20190
+ def transitive_reduction(self, immutable=None ):
20156
20191
r"""
20157
20192
Return a transitive reduction of a graph.
20158
20193
@@ -20165,6 +20200,13 @@ def transitive_reduction(self):
20165
20200
A transitive reduction of a complete graph is a tree. A transitive
20166
20201
reduction of a tree is itself.
20167
20202
20203
+ INPUT:
20204
+
20205
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
20206
+ mutable/immutable transitive closure. ``immutable=None`` (default)
20207
+ means that the (di)graph and its transitive closure will behave the
20208
+ same way.
20209
+
20168
20210
EXAMPLES::
20169
20211
20170
20212
sage: g = graphs.PathGraph(4)
@@ -20176,35 +20218,65 @@ def transitive_reduction(self):
20176
20218
sage: g = DiGraph({0: [1, 2], 1: [2, 3, 4, 5], 2: [4, 5]})
20177
20219
sage: g.transitive_reduction().size()
20178
20220
5
20221
+
20222
+ TESTS:
20223
+
20224
+ Check the behavior of parameter `ìmmutable``::
20225
+
20226
+ sage: G = Graph([(0, 1)])
20227
+ sage: G.transitive_reduction().is_immutable()
20228
+ False
20229
+ sage: G.transitive_reduction(immutable=True).is_immutable()
20230
+ True
20231
+ sage: G = Graph([(0, 1)], immutable=True)
20232
+ sage: G.transitive_reduction().is_immutable()
20233
+ True
20234
+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)])
20235
+ sage: G.transitive_reduction().is_immutable()
20236
+ False
20237
+ sage: G.transitive_reduction(immutable=True).is_immutable()
20238
+ True
20239
+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20240
+ sage: G.transitive_reduction().is_immutable()
20241
+ True
20179
20242
"""
20243
+ if immutable is None:
20244
+ immutable = self.is_immutable()
20245
+
20180
20246
if self.is_directed():
20181
20247
if self.is_directed_acyclic():
20182
20248
from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
20183
- return transitive_reduction_acyclic(self)
20249
+ return transitive_reduction_acyclic(self, immutable=immutable )
20184
20250
20185
- G = copy(self )
20251
+ G = self. copy(immutable=False )
20186
20252
G.allow_multiple_edges(False)
20187
20253
n = G.order()
20188
- for e in G.edges(sort=False):
20254
+ for e in list( G.edges(sort=False) ):
20189
20255
# Try deleting the edge, see if we still have a path between
20190
20256
# the vertices.
20191
20257
G.delete_edge(e)
20192
20258
if G.distance(e[0], e[1]) > n:
20193
20259
# oops, we shouldn't have deleted it
20194
20260
G.add_edge(e)
20261
+ if immutable:
20262
+ return G.copy(immutable=True)
20195
20263
return G
20196
20264
20197
20265
# The transitive reduction of each connected component of an
20198
20266
# undirected graph is a spanning tree
20199
- from sage.graphs.graph import Graph
20200
20267
if self.is_connected():
20201
- return Graph(self.min_spanning_tree(weight_function=lambda e: 1))
20202
- G = Graph(list(self))
20203
- for cc in self.connected_components(sort=False):
20204
- if len(cc) > 1:
20205
- edges = self.subgraph(cc).min_spanning_tree(weight_function=lambda e: 1)
20206
- G.add_edges(edges)
20207
- return G
20268
+ CC = [self]
20269
+ else:
20270
+ CC = (self.subgraph(c)
20271
+ for c in self.connected_components() if len(c) > 1)
20272
+
20273
+ def edges():
20274
+ for g in CC:
20275
+ yield from g.min_spanning_tree(weight_function=lambda e: 1)
20276
+
20277
+ from sage.graphs.graph import Graph
20278
+ return Graph([self, edges()], format='vertices_and_edges',
20279
+ immutable=immutable)
20208
20280
20209
20281
def is_transitively_reduced(self):
20210
20282
r"""
@@ -20226,13 +20298,27 @@ def is_transitively_reduced(self):
20226
20298
sage: d = DiGraph({0: [1, 2], 1: [2], 2: []})
20227
20299
sage: d.is_transitively_reduced()
20228
20300
False
20301
+
20302
+ TESTS:
20303
+
20304
+ Check the behavior of the method for immutable (di)graphs::
20305
+
20306
+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20307
+ sage: G.is_transitively_reduced()
20308
+ True
20309
+ sage: G = DiGraph(graphs.CompleteGraph(4), immutable=True)
20310
+ sage: G.is_transitively_reduced()
20311
+ False
20312
+ sage: G = Graph([(0, 1), (2, 3)], immutable=True)
20313
+ sage: G.is_transitively_reduced()
20314
+ True
20229
20315
"""
20230
20316
if self.is_directed():
20231
20317
if self.is_directed_acyclic():
20232
20318
return self == self.transitive_reduction()
20233
20319
20234
20320
from sage.rings.infinity import Infinity
20235
- G = copy(self )
20321
+ G = self. copy(immutable=False )
20236
20322
for e in self.edge_iterator():
20237
20323
G.delete_edge(e)
20238
20324
if G.distance(e[0], e[1]) == Infinity:
0 commit comments