Ticket #9350: trac_9350.patch

File trac_9350.patch, 23.1 KB (added by ncohen, 9 years ago)
  • sage/graphs/generic_graph.py

    # HG changeset patch
    # User Nathann Cohen <nathann.cohen@gmail.com>
    # Date 1277633734 -7200
    # Node ID 640363be14a5ca2ba45f2b7005adbe1e05c46a79
    # Parent  0b12ec9add58ab64d979b10238f140e1ee19c287
    trac 9350 : Implementation of the Ford-Fulkerson algorithm in Python, and updates to min_cut and gomory_hu to use it.
    
    diff -r 0b12ec9add58 -r 640363be14a5 sage/graphs/generic_graph.py
    a b  
    35623562        return classes
    35633563           
    35643564               
    3565     def edge_cut(self, s, t, value_only=True, use_edge_labels=False, vertices=False, solver=None, verbose=0):
     3565    def edge_cut(self, s, t, value_only=True, use_edge_labels=False, vertices=False, method="FF", solver=None, verbose=0):
    35663566        r"""
    35673567        Returns a minimum edge cut between vertices `s` and `t`
    35683568        represented by a list of edges.
     
    35893589          a weight defined by its label (if an edge has no label, `1`
    35903590          is assumed). Otherwise, each edge has weight `1`.
    35913591
    3592         - ``vertices`` -- boolean (default: ``False``). When set to ``True``,
    3593           also returns the two sets of vertices that are disconnected by
    3594           the cut. Implies ``value_only=False``.
     3592        - ``vertices`` -- boolean (default: ``False``). When set to
     3593          ``True``, returns a list of edges in the edge cut and the
     3594          two sets of vertices that are disconnected by the cut.
     3595
     3596          Note: ``vertices=True`` implies ``value_only=False``.
     3597
     3598        - ``method`` -- There are currently two different
     3599          implementations of this method :
     3600
     3601              * If ``method = "FF"`` (default), a Python
     3602                implementation of the Ford-Fulkerson algorithm is
     3603                used.
     3604
     3605              * If ``method = "LP"``, the flow problem is solved using
     3606                Linear Programming.
    35953607
    35963608        - ``solver`` -- (default: ``None``) Specify a Linear Program (LP)
    35973609          solver to be used. If set to ``None``, the default one is used. For
     
    36043616        - ``verbose`` -- integer (default: ``0``). Sets the level of
    36053617          verbosity. Set to 0 by default, which means quiet.
    36063618
     3619        .. NOTE::
     3620
     3621           The use of Linear Programming for non-integer problems may
     3622           possibly mean the presence of a (slight) numerical noise.
     3623
    36073624        OUTPUT:
    36083625
    36093626        Real number or tuple, depending on the given arguments
     
    36153632       
    36163633           sage: g = graphs.PappusGraph()
    36173634           sage: g.edge_cut(1, 2, value_only=True)
    3618            3.0
     3635           3
     3636
     3637        Or on Petersen's graph, with the corresponding bipartition of
     3638        the vertex set::
     3639
     3640           sage: g = graphs.PetersenGraph()
     3641           sage: g.edge_cut(0, 3, vertices=True)
     3642           [3, [(0, 1, None), (0, 4, None), (0, 5, None)], [[0], [1, 2, 3, 4, 5, 6, 7, 8, 9]]]
    36193643
    36203644        If the graph is a path with randomly weighted edges::
    36213645
     
    36263650        The edge cut between the two ends is the edge of minimum weight::
    36273651
    36283652           sage: minimum = min([l for u,v,l in g.edge_iterator()])
    3629            sage: abs(minimum - g.edge_cut(0, 14, use_edge_labels=True)) < 10**(-5)
     3653           sage: minimum == g.edge_cut(0, 14, use_edge_labels=True)
    36303654           True
    3631            sage: [value,[[u,v]]] = g.edge_cut(0, 14, use_edge_labels=True, value_only=False)
    3632            sage: g.edge_label(u, v) == minimum
     3655           sage: [value,[e]] = g.edge_cut(0, 14, use_edge_labels=True, value_only=False)
     3656           sage: g.edge_label(e[0],e[1]) == minimum
    36333657           True
    36343658
    36353659        The two sides of the edge cut are obviously shorter paths::
     
    36413665           True
    36423666           sage: len(set1) + len(set2) == g.order()
    36433667           True
    3644         """
     3668
     3669        TESTS:
     3670
     3671        If method is set to an exotic value::
     3672
     3673           sage: g = graphs.PetersenGraph()
     3674           sage: g.edge_cut(0,1, method="Divination")
     3675           Traceback (most recent call last):
     3676           ...
     3677           ValueError: The method argument has to be equal to either "FF" or "LP"
     3678
     3679        Same result for both methods::
     3680
     3681           sage: g = graphs.RandomGNP(20,.3)
     3682           sage: for u,v in g.edges(labels=False):
     3683           ...      g.set_edge_label(u,v,round(random(),5))
     3684           sage: g.edge_cut(0,1, method="FF") == g.edge_cut(0,1,method="LP")
     3685           True
     3686        """
     3687
     3688        if vertices:
     3689            value_only = False
     3690
     3691        if use_edge_labels:
     3692            weight = lambda x: x if (x!={} and x is not None) else 1
     3693        else:
     3694            weight = lambda x: 1
     3695
     3696        if method == "FF":
     3697            if value_only:
     3698                return self.flow(s,t,value_only=value_only,use_edge_labels=use_edge_labels, method=method)
     3699
     3700            flow_value, flow_graph = self.flow(s,t,value_only=value_only,use_edge_labels=use_edge_labels, method=method)
     3701            g = self.copy()
     3702            for u,v,l in flow_graph.edge_iterator():
     3703                if (not use_edge_labels or
     3704                    (weight(g.edge_label(u,v)) == weight(l))):
     3705                    g.delete_edge(u,v)
     3706
     3707            return_value = [flow_value]
     3708
     3709            reachable_from_s = list(g.breadth_first_search(s))
     3710
     3711            return_value.append(self.edge_boundary(reachable_from_s))
     3712
     3713            if vertices:
     3714                return_value.append([reachable_from_s,list(set(self.vertices())-set(reachable_from_s))])
     3715
     3716            return return_value
     3717
     3718        if method != "LP":
     3719            raise ValueError("The method argument has to be equal to either \"FF\" or \"LP\"")
     3720           
     3721
    36453722        from sage.numerical.mip import MixedIntegerLinearProgram, Sum
    36463723        g = self
    36473724        p = MixedIntegerLinearProgram(maximization=False)
    36483725        b = p.new_variable(dim=2)
    36493726        v = p.new_variable()
    36503727
    3651         if vertices:
    3652             value_only = False
    3653         if use_edge_labels:
    3654             from sage.rings.real_mpfr import RR
    3655             weight = lambda x: x if x in RR else 1
    3656         else:
    3657             weight = lambda x: 1
    3658 
    36593728        # Some vertices belong to part 1, others to part 0
    36603729        p.add_constraint(v[s], min=0, max=0)
    36613730        p.add_constraint(v[t], min=1, max=1)       
     
    44964565        except MIPSolverException:
    44974566            raise ValueError("The given graph is not hamiltonian")
    44984567
    4499     def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, vertex_bound=False, solver=None, verbose=0):
     4568    def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, vertex_bound=False, method = None, solver=None, verbose=0):
    45004569        r"""
    45014570        Returns a maximum flow in the graph from ``x`` to ``y``
    45024571        represented by an optimal valuation of the edges. For more
     
    45484617          - When set to ``True``, sets the maximum flow leaving
    45494618            a vertex different from `x` to `1` (useful for vertex
    45504619            connectivity parameters).
     4620
     4621        - ``method`` -- There are currently two different
     4622          implementations of this method :
     4623
     4624              * If ``method = "FF"``, a Python implementation of the
     4625                Ford-Fulkerson algorithm is used (only available when
     4626                ``vertex_bound = False``)
     4627
     4628              * If ``method = "LP"``, the flow problem is solved using
     4629                Linear Programming.
     4630
     4631              * If ``method = None`` (default), the Ford-Fulkerson
     4632                implementation is used iif ``vertex_bound = False``.
    45514633                             
    4552         - ``solver`` -- Specify a Linear Program solver to be used.
    4553           If set to ``None``, the default one is used.
    4554           function of ``MixedIntegerLinearProgram``. See the documentation  of ``MixedIntegerLinearProgram.solve``
    4555           for more information.
     4634        - ``solver`` -- Specify a Linear Program solver to be used.
     4635          If set to ``None``, the default one is used.  function of
     4636          ``MixedIntegerLinearProgram``. See the documentation of
     4637          ``MixedIntegerLinearProgram.solve`` for more information.
     4638
     4639          Only useful when LP is used to solve the flow problem.
    45564640
    45574641        - ``verbose`` (integer) -- sets the level of verbosity. Set to 0
    45584642          by default (quiet).
     4643
     4644          Only useful when LP is used to solve the flow problem.
     4645
     4646        .. NOTE::
     4647
     4648           Even though the two different implementations are meant to
     4649           return the same Flow values, they can not be expected to
     4650           return the same Flow graphs.
     4651
     4652           Besides, the use of Linear Programming may possibly mean a
     4653           (slight) numerical noise.
    45594654   
    45604655        EXAMPLES:
    45614656
     
    45644659       
    45654660           sage: g=graphs.PappusGraph()
    45664661           sage: g.flow(1,2)
    4567            3.0
     4662           3
    45684663
    45694664        ::
    45704665
    45714666           sage: b=digraphs.ButterflyGraph(2)
    45724667           sage: b.flow(('00',1),('00',2))
    4573            1.0
     4668           1
    45744669
    45754670        The flow method can be used to compute a matching in a bipartite graph
    45764671        by linking a source `s` to all the vertices of the first set and linking
     
    45834678            sage: g.add_edges([(4+i,'t') for i in range(4)])
    45844679            sage: [cardinal, flow_graph] = g.flow('s','t',integer=True,value_only=False)
    45854680            sage: flow_graph.delete_vertices(['s','t'])
    4586             sage: len(flow_graph.edges(labels=None))
     4681            sage: len(flow_graph.edges())
    45874682            4
    4588        
    4589         """
     4683
     4684        TESTS:
     4685
     4686        An exception if raised when forcing "FF" with ``vertex_bound = True``::
     4687
     4688            sage: g = graphs.PetersenGraph()
     4689            sage: g.flow(0,1,vertex_bound = True, method = "FF")
     4690            Traceback (most recent call last):
     4691            ...
     4692            ValueError: This method does not support both vertex_bound=True and method="FF".
     4693
     4694        Or if the method is different from the expected values::
     4695
     4696            sage: g.flow(0,1, method="Divination")
     4697            Traceback (most recent call last):
     4698            ...
     4699            ValueError: The method argument has to be equal to either "FF", "LP" or None       
     4700
     4701        The two methods are indeed returning the same results::
     4702
     4703           sage: g = graphs.RandomGNP(20,.3)
     4704           sage: for u,v in g.edges(labels=False):
     4705           ...      g.set_edge_label(u,v,round(random(),5))
     4706           sage: g.flow(0,1, method="FF") == g.flow(0,1,method="LP")
     4707           True
     4708        """
     4709       
     4710        if vertex_bound == True and method == "FF":
     4711            raise ValueError("This method does not support both vertex_bound=True and method=\"FF\".")
     4712
     4713        if (method == "FF" or
     4714            (method == None and vertex_bound == False)):
     4715            return self._ford_fulkerson(x,y, value_only=value_only, integer=integer, use_edge_labels=use_edge_labels)
     4716
     4717        if method != "LP" and method != None:
     4718            raise ValueError("The method argument has to be equal to either \"FF\", \"LP\" or None")
     4719
     4720
    45904721        from sage.numerical.mip import MixedIntegerLinearProgram, Sum
    45914722        g=self
    45924723        p=MixedIntegerLinearProgram(maximization=True)
     
    46554786            flow_graph = Graph(flow_graph)
    46564787 
    46574788        return [obj,flow_graph]
     4789
     4790    def _ford_fulkerson(self, s, t, use_edge_labels = False, integer = False, value_only = True):
     4791        r"""
     4792        Python implementation of the Ford-Fulkerson algorithm.
     4793
     4794        This method is a Python implementation of the Ford-Fulkerson
     4795        max-flow algorithm, which is (slightly) faster than the LP
     4796        implementation.
     4797
     4798        INPUT:
     4799   
     4800        - ``s`` -- Source vertex
     4801
     4802        - ``t`` -- Sink vertex
     4803
     4804        - ``value_only`` -- boolean (default: ``True``)
     4805
     4806          - When set to ``True``, only the value of a maximal
     4807            flow is returned.
     4808
     4809          - When set to ``False``, is returned a pair whose first element
     4810            is the value of the maximum flow, and whose second value is
     4811            a flow graph (a copy of the current graph, such that each edge
     4812            has the flow using it as a label, the edges without flow being
     4813            omitted).
     4814
     4815        - ``integer`` -- boolean (default: ``False``)
     4816
     4817          - When set to ``True``, computes an optimal solution under the
     4818            constraint that the flow going through an edge has to be an
     4819            integer.
     4820           
     4821        - ``use_edge_labels`` -- boolean (default: ``True``)
     4822
     4823          - When set to ``True``, computes a maximum flow
     4824            where each edge has a capacity defined by its label. (If
     4825            an edge has no label, `1` is assumed.)
     4826
     4827          - When set to ``False``, each edge has capacity `1`.
     4828
     4829        EXAMPLES:
     4830
     4831        Two basic applications of the flow method for the ``PappusGraph`` and the
     4832        ``ButterflyGraph`` with parameter `2` ::
     4833       
     4834           sage: g=graphs.PappusGraph()
     4835           sage: g._ford_fulkerson(1,2)
     4836           3
     4837
     4838        ::
     4839
     4840           sage: b=digraphs.ButterflyGraph(2)
     4841           sage: b._ford_fulkerson(('00',1),('00',2))
     4842           1
     4843
     4844        The flow method can be used to compute a matching in a bipartite graph
     4845        by linking a source `s` to all the vertices of the first set and linking
     4846        a sink `t` to all the vertices of the second set, then computing
     4847        a maximum `s-t` flow ::
     4848
     4849            sage: g = DiGraph()
     4850            sage: g.add_edges([('s',i) for i in range(4)])
     4851            sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)])
     4852            sage: g.add_edges([(4+i,'t') for i in range(4)])
     4853            sage: [cardinal, flow_graph] = g._ford_fulkerson('s','t',integer=True,value_only=False)
     4854            sage: flow_graph.delete_vertices(['s','t'])                                 
     4855            sage: len(flow_graph.edges(labels=None))                                   
     4856            4
     4857        """
     4858        from sage.graphs.digraph import DiGraph
     4859
     4860        # Whether we should consider the edges labeled
     4861        if use_edge_labels:
     4862            l_capacity=lambda x: 1 if (x is None or x == {}) else (floor(x) if integer else x)
     4863        else:
     4864            l_capacity=lambda x: 1
     4865
     4866        directed = self.is_directed()
     4867
     4868        # Associated to each edge (u,v) of the flow graph its capacity
     4869        capacity = {}
     4870        # Associates to each edge (u,v) of the graph the (directed)
     4871        # flow going through it
     4872        flow = {}
     4873
     4874        # Residual graph. Only contains edge on which some flow can be
     4875        # sent. This can happen both when the flow going through the
     4876        # current edge is strictly less than its capacity, or when
     4877        # there exists a back arc with non-null flow
     4878        residual = DiGraph()
     4879
     4880        # Initializing the variables
     4881        if directed:
     4882            for u,v,l in self.edge_iterator():
     4883                if l_capacity(l) > 0:
     4884                    capacity[(u,v)] = l_capacity(l) + capacity.get((u,v),0)
     4885                    capacity[(v,u)] = capacity.get((v,u),0)
     4886                    residual.add_edge(u,v)
     4887                    flow[(u,v)] = 0
     4888                    flow[(v,u)] = 0
     4889        else:
     4890            for u,v,l in self.edge_iterator():
     4891                if l_capacity(l) > 0:
     4892                    capacity[(u,v)] = l_capacity(l) + capacity.get((u,v),0)
     4893                    capacity[(v,u)] = l_capacity(l) + capacity.get((v,u),0)
     4894                    residual.add_edge(u,v)
     4895                    residual.add_edge(v,u)
     4896                    flow[(u,v)] = 0
     4897                    flow[(v,u)] = 0
     4898
     4899        # Reqrites a path as a list of edges :
     4900        # ex : [0,1,2,3,4,5] becomes [(0,1), (1,2), (2,3), (3,4), (4,5)]
     4901        path_to_edges = lambda P : zip(P[:-1],P[1:])
     4902
     4903        # Rewrites a path as a list of edges labeled with their
     4904        # available capacity
     4905        path_to_labelled_edges = lambda P : map(lambda (x,y) : (x,y,capacity[(x,y)]-flow[(x,y)] + flow[(y,x)]),path_to_edges(P))
     4906
     4907        # Total flow going from s to t
     4908        flow_intensity = 0
     4909
     4910        while True:
     4911
     4912            # If there is a shortest path from s to t
     4913            path = residual.shortest_path(s,t)
     4914            if not path:
     4915                break
     4916
     4917            # We are rewriting the shortest path as a sequence of
     4918            # edges whose labels are their available capacities
     4919            edges = path_to_labelled_edges(path)
     4920
     4921            # minimum capacity available on the whole path
     4922            epsilon = min(map( lambda x : x[2], edges))
     4923
     4924            flow_intensity = flow_intensity + epsilon
     4925           
     4926            # Updating variables
     4927            for uu,vv,ll in edges:
     4928
     4929                # The flow on the back arc
     4930                other = flow[(vv,uu)]
     4931                flow[(uu,vv)] = flow[(uu,vv)] + max(0,epsilon-other)
     4932                flow[(vv,uu)] = other - min(other, epsilon)
     4933
     4934                # If the current edge is fully used, we do not need it
     4935                # anymore in the residual graph
     4936                if capacity[(uu,vv)] - flow[(uu,vv)] + flow[(vv,uu)] == 0:
     4937                    residual.delete_edge(uu,vv)
     4938
     4939                # If the back arc does not exist, it now does as the
     4940                # edge (uu,vv) has a flow of at least epsilon>0
     4941                if not residual.has_edge(vv,uu):
     4942                    residual.add_edge(vv,uu,epsilon)
     4943
     4944        if value_only:
     4945            return flow_intensity
     4946
     4947        # Building and returning the flow graph
     4948        g = DiGraph()
     4949        g.add_edges([(x,y,l) for ((x,y),l) in flow.iteritems() if l > 0])
     4950        g.set_pos(self.get_pos())
     4951
     4952        return flow_intensity, g
    46584953 
    46594954    def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,vertex_bound=False, solver=None, verbose=0):
    46604955        r"""
     
    49555250        except ValueError:
    49565251            raise ValueError("The disjoint routed paths do not exist.")
    49575252
    4958     def edge_disjoint_paths(self, s, t):
     5253    def edge_disjoint_paths(self, s, t, method = "FF"):
    49595254        r"""
    49605255        Returns a list of edge-disjoint paths between two
    49615256        vertices as given by Menger's theorem.
     
    49685263
    49695264        This function returns a list of such paths.
    49705265
     5266        INPUT:
     5267
     5268        - ``method`` -- There are currently two different
     5269          implementations of this method :
     5270
     5271              * If ``method = "FF"`` (default), a Python implementation of the
     5272                Ford-Fulkerson algorithm is used.
     5273
     5274              * If ``method = "LP"``, the flow problem is solved using
     5275                Linear Programming.
     5276
    49715277        .. NOTE::
    49725278
    49735279            This function is topological : it does not take the eventual
     
    49825288            [[0, 2, 1], [0, 3, 1], [0, 4, 1]]
    49835289        """
    49845290
    4985         [obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False)
     5291        [obj, flow_graph] = self.flow(s,t,value_only=False, integer=True, use_edge_labels=False, method=method)
    49865292
    49875293        paths = []
    49885294
  • sage/graphs/generic_graph_pyx.pyx

    diff -r 0b12ec9add58 -r 640363be14a5 sage/graphs/generic_graph_pyx.pyx
    a b  
    455455    m = "".join(l)
    456456    return m[:n*n]
    457457
    458 
    459458# Exhaustive search in graphs
    460459
    461460cpdef list subgraph_search(G, H, bint induced=False):
  • sage/graphs/graph.py

    diff -r 0b12ec9add58 -r 640363be14a5 sage/graphs/graph.py
    a b  
    34563456
    34573457        return D[0] == "Prime" and len(D[1]) == self.order()
    34583458
    3459     def _gomory_hu_tree(self, vertices=None):
     3459    def _gomory_hu_tree(self, vertices=None, method="FF"):
    34603460        r"""
    34613461        Returns a Gomory-Hu tree associated to self.
    34623462
     
    34733473          fakes one introduced during the computations. This variable is
    34743474          useful for the algorithm and for recursion purposes.
    34753475
     3476        - ``method`` -- There are currently two different
     3477          implementations of this method :
     3478
     3479              * If ``method = "FF"`` (default), a Python
     3480                implementation of the Ford-Fulkerson algorithm is
     3481                used.
     3482
     3483              * If ``method = "LP"``, the flow problem is solved using
     3484                Linear Programming.
     3485
    34763486        EXAMPLE:
    34773487
    34783488        This function is actually tested in ``gomory_hu_tree()``, this
     
    34923502
    34933503        # Small case, not really a problem ;-)
    34943504        if self.order() == 1:
    3495             return copy(g)
     3505            return self.copy()
    34963506
    34973507        # This is a sign that this is the first call
    34983508        # to this recursive function
     
    35053515            if not self.is_connected():
    35063516                g = Graph()
    35073517                for cc in self.connected_components_subgraphs():
    3508                     g = g.union(cc._gomory_hu_tree())
     3518                    g = g.union(cc._gomory_hu_tree(method=method))
    35093519                g.set_pos(self.get_pos())
    35103520                return g
    35113521            # All the vertices is this graph are the "real ones"
     
    35243534        # flow is the connectivity between u and v
    35253535        # edges of a min cut
    35263536        # sets1, sets2 are the two sides of the edge cut
    3527         flow,edges,[set1,set2] = self.edge_cut(u, v, use_edge_labels=True, vertices=True)
     3537        flow,edges,[set1,set2] = self.edge_cut(u, v, use_edge_labels=True, vertices=True, method=method)
    35283538
    35293539        # One graph for each part of the previous one
    35303540        g1,g2 = self.subgraph(set1), self.subgraph(set2)
     
    35423552
    35433553        # We must preserve the labels. They sum.
    35443554
    3545         for x,y in edges:
     3555        for e in edges:
     3556            x,y = e[0],e[1]
    35463557            # Assumes x is in g1
    35473558            if x in g2:
    35483559                x,y = y,x
     
    35603571
    35613572        # Recursion for the two new graphs... The new "real" vertices are the intersection with
    35623573        # with the previous set of "real" vertices
    3563         g1_tree = g1._gomory_hu_tree(vertices=(vertices & Set(g1.vertices())))
    3564         g2_tree = g2._gomory_hu_tree(vertices=(vertices & Set(g2.vertices())))
     3574        g1_tree = g1._gomory_hu_tree(vertices=(vertices & Set(g1.vertices())), method=method)
     3575        g2_tree = g2._gomory_hu_tree(vertices=(vertices & Set(g2.vertices())), method=method)
    35653576
    35663577        # Union of the two partial trees ( it is disjoint, but
    35673578        # disjoint_union does not preserve the name of the vertices )
     
    35753586
    35763587        return g
    35773588
    3578     def gomory_hu_tree(self):
     3589    def gomory_hu_tree(self, method="FF"):
    35793590        r"""
    35803591        Returns a Gomory-Hu tree of self.
    35813592
     
    35893600        `Wikipedia article on Gomory-Hu tree <http://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree>`_.
    35903601        Note that, in general, a graph admits more than one Gomory-Hu tree.
    35913602
     3603        INPUT:
     3604
     3605        - ``method`` -- There are currently two different
     3606          implementations of this method :
     3607
     3608              * If ``method = "FF"`` (default), a Python
     3609                implementation of the Ford-Fulkerson algorithm is
     3610                used.
     3611
     3612              * If ``method = "LP"``, the flow problems are solved
     3613                using Linear Programming.
     3614
    35923615        OUTPUT:
    35933616
    35943617        graph with labeled edges
     
    36323655            sage: g.edge_connectivity() == min(t.edge_labels())
    36333656            True
    36343657        """
    3635         return self._gomory_hu_tree()
     3658        return self._gomory_hu_tree(method=method)
    36363659       
    36373660
    36383661    def two_factor_petersen(self):