@@ -20516,7 +20516,7 @@ def disjunctive_product(self, other, immutable=None):
2051620516 return GT([vertices, edges], format='vertices_and_edges',
2051720517 loops=loops, immutable=immutable)
2051820518
20519- def transitive_closure(self, loops=True ):
20519+ def transitive_closure(self, loops=None, immutable=None ):
2052020520 r"""
2052120521 Return the transitive closure of the (di)graph.
2052220522
@@ -20528,11 +20528,18 @@ def transitive_closure(self, loops=True):
2052820528 acyclic graph is a directed acyclic graph representing the full partial
2052920529 order.
2053020530
20531- .. NOTE: :
20531+ INPUT :
2053220532
20533- If the (di)graph allows loops, its transitive closure will by
20534- default have one loop edge per vertex. This can be prevented by
20535- disallowing loops in the (di)graph (``self.allow_loops(False)``).
20533+ - ``loops`` -- boolean (default: ``None``); whether to allow loops in
20534+ the returned (di)graph. By default (``None``), if the (di)graph allows
20535+ loops, its transitive closure will have one loop edge per vertex. This
20536+ can be prevented by disallowing loops in the (di)graph
20537+ (``self.allow_loops(False)``).
20538+
20539+ - ``immutable`` -- boolean (default: ``None``); whether to create a
20540+ mutable/immutable transitive closure. ``immutable=None`` (default)
20541+ means that the (di)graph and its transitive closure will behave the
20542+ same way.
2053620543
2053720544 EXAMPLES::
2053820545
@@ -20560,21 +20567,49 @@ def transitive_closure(self, loops=True):
2056020567 sage: G.transitive_closure().loop_edges(labels=False)
2056120568 [(0, 0), (1, 1), (2, 2)]
2056220569
20563- ::
20570+ Check the behavior of parameter ``loops`` ::
2056420571
2056520572 sage: G = graphs.CycleGraph(3)
2056620573 sage: G.transitive_closure().loop_edges(labels=False)
2056720574 []
20575+ sage: G.transitive_closure(loops=True).loop_edges(labels=False)
20576+ [(0, 0), (1, 1), (2, 2)]
2056820577 sage: G.allow_loops(True)
2056920578 sage: G.transitive_closure().loop_edges(labels=False)
2057020579 [(0, 0), (1, 1), (2, 2)]
20580+ sage: G.transitive_closure(loops=False).loop_edges(labels=False)
20581+ []
20582+
20583+ Check the behavior of parameter ``immutable``::
20584+
20585+ sage: G = Graph([(0, 1)])
20586+ sage: G.transitive_closure().is_immutable()
20587+ False
20588+ sage: G.transitive_closure(immutable=True).is_immutable()
20589+ True
20590+ sage: G = Graph([(0, 1)], immutable=True)
20591+ sage: G.transitive_closure().is_immutable()
20592+ True
20593+ sage: G.transitive_closure(immutable=False).is_immutable()
20594+ False
2057120595 """
20572- G = copy(self)
20573- G.name('Transitive closure of ' + self.name())
20574- G.add_edges(((u, v) for u in G for v in G.breadth_first_search(u)), loops=None)
20575- return G
20596+ name = f"Transitive closure of {self.name()}"
20597+ if immutable is None:
20598+ immutable = self.is_immutable()
20599+ if loops is None:
20600+ loops = self.allows_loops()
20601+ if loops:
20602+ edges = ((u, v) for u in self for v in self.depth_first_search(u))
20603+ else:
20604+ edges = ((u, v) for u in self for v in self.depth_first_search(u) if u != v)
20605+ if self.is_directed():
20606+ from sage.graphs.digraph import DiGraph as GT
20607+ else:
20608+ from sage.graphs.graph import Graph as GT
20609+ return GT([self, edges], format='vertices_and_edges', loops=loops,
20610+ immutable=immutable, name=name)
2057620611
20577- def transitive_reduction(self):
20612+ def transitive_reduction(self, immutable=None ):
2057820613 r"""
2057920614 Return a transitive reduction of a graph.
2058020615
@@ -20587,6 +20622,13 @@ def transitive_reduction(self):
2058720622 A transitive reduction of a complete graph is a tree. A transitive
2058820623 reduction of a tree is itself.
2058920624
20625+ INPUT:
20626+
20627+ - ``immutable`` -- boolean (default: ``None``); whether to create a
20628+ mutable/immutable transitive closure. ``immutable=None`` (default)
20629+ means that the (di)graph and its transitive closure will behave the
20630+ same way.
20631+
2059020632 EXAMPLES::
2059120633
2059220634 sage: g = graphs.PathGraph(4)
@@ -20595,38 +20637,72 @@ def transitive_reduction(self):
2059520637 sage: g = graphs.CompleteGraph(5)
2059620638 sage: h = g.transitive_reduction(); h.size()
2059720639 4
20640+ sage: (2*g).transitive_reduction().size()
20641+ 8
2059820642 sage: g = DiGraph({0: [1, 2], 1: [2, 3, 4, 5], 2: [4, 5]})
2059920643 sage: g.transitive_reduction().size()
2060020644 5
20645+ sage: (2*g).transitive_reduction().size()
20646+ 10
20647+
20648+ TESTS:
20649+
20650+ Check the behavior of parameter ``immutable``::
20651+
20652+ sage: G = Graph([(0, 1)])
20653+ sage: G.transitive_reduction().is_immutable()
20654+ False
20655+ sage: G.transitive_reduction(immutable=True).is_immutable()
20656+ True
20657+ sage: G = Graph([(0, 1)], immutable=True)
20658+ sage: G.transitive_reduction().is_immutable()
20659+ True
20660+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)])
20661+ sage: G.transitive_reduction().is_immutable()
20662+ False
20663+ sage: G.transitive_reduction(immutable=True).is_immutable()
20664+ True
20665+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20666+ sage: G.transitive_reduction().is_immutable()
20667+ True
2060120668 """
20669+ if immutable is None:
20670+ immutable = self.is_immutable()
20671+
2060220672 if self.is_directed():
2060320673 if self.is_directed_acyclic():
2060420674 from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
20605- return transitive_reduction_acyclic(self)
20675+ return transitive_reduction_acyclic(self, immutable=immutable )
2060620676
20607- G = copy(self )
20677+ G = self. copy(immutable=False )
2060820678 G.allow_multiple_edges(False)
2060920679 n = G.order()
20610- for e in G.edges(sort=False):
20680+ for e in list( G.edges(sort=False) ):
2061120681 # Try deleting the edge, see if we still have a path between
2061220682 # the vertices.
2061320683 G.delete_edge(e)
2061420684 if G.distance(e[0], e[1]) > n:
2061520685 # oops, we shouldn't have deleted it
2061620686 G.add_edge(e)
20687+ if immutable:
20688+ return G.copy(immutable=True)
2061720689 return G
2061820690
2061920691 # The transitive reduction of each connected component of an
2062020692 # undirected graph is a spanning tree
20621- from sage.graphs.graph import Graph
2062220693 if self.is_connected():
20623- return Graph(self.min_spanning_tree(weight_function=lambda e: 1))
20624- G = Graph(list(self))
20625- for cc in self.connected_components(sort=False):
20626- if len(cc) > 1:
20627- edges = self.subgraph(cc).min_spanning_tree(weight_function=lambda e: 1)
20628- G.add_edges(edges)
20629- return G
20694+ CC = [self]
20695+ else:
20696+ CC = (self.subgraph(c)
20697+ for c in self.connected_components(sort=False) if len(c) > 1)
20698+
20699+ def edges():
20700+ for g in CC:
20701+ yield from g.min_spanning_tree(weight_function=lambda e: 1)
20702+
20703+ from sage.graphs.graph import Graph
20704+ return Graph([self, edges()], format='vertices_and_edges',
20705+ immutable=immutable)
2063020706
2063120707 def is_transitively_reduced(self):
2063220708 r"""
@@ -20648,13 +20724,27 @@ def is_transitively_reduced(self):
2064820724 sage: d = DiGraph({0: [1, 2], 1: [2], 2: []})
2064920725 sage: d.is_transitively_reduced()
2065020726 False
20727+
20728+ TESTS:
20729+
20730+ Check the behavior of the method for immutable (di)graphs::
20731+
20732+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20733+ sage: G.is_transitively_reduced()
20734+ True
20735+ sage: G = DiGraph(graphs.CompleteGraph(4), immutable=True)
20736+ sage: G.is_transitively_reduced()
20737+ False
20738+ sage: G = Graph([(0, 1), (2, 3)], immutable=True)
20739+ sage: G.is_transitively_reduced()
20740+ True
2065120741 """
2065220742 if self.is_directed():
2065320743 if self.is_directed_acyclic():
2065420744 return self == self.transitive_reduction()
2065520745
2065620746 from sage.rings.infinity import Infinity
20657- G = copy(self )
20747+ G = self. copy(immutable=False )
2065820748 for e in self.edge_iterator():
2065920749 G.delete_edge(e)
2066020750 if G.distance(e[0], e[1]) == Infinity:
0 commit comments