@@ -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