Ticket #8330: trac_8330-bipartite-delete-vertex.patch

File trac_8330-bipartite-delete-vertex.patch, 13.6 KB (added by rhinton, 11 years ago)

REQUIRES #8421. Implements add/delete vertex/vertices and to_undirected to fix a failing doctest elsewhere.

  • sage/graphs/bipartite_graph.py

    # HG changeset patch
    # User Ryan Hinton on appserv <iobass@email.com>
    # Date 1266890415 18000
    # Node ID 52e2c5336c9db3d06ae88aab8014516bbf2c58ce
    # Parent  ee171a17915c57d25c2099a96bd36daa4dff7917
    update .left and .right sets when adding and deleting vertices
    
    diff -r ee171a17915c -r 52e2c5336c9d sage/graphs/bipartite_graph.py
    a b  
    221221            Graph.__init__(self)
    222222            self.left = set(); self.right = set()
    223223            return
     224
     225        # need to turn off partition checking for Graph.__init__() adding
     226        # vertices; methods are restored ad the end of big "if" statement below
     227        import types
     228        self.add_vertex = types.MethodType(Graph.add_vertex, self, BipartiteGraph)
     229        self.add_vertices = types.MethodType(Graph.add_vertices, self, BipartiteGraph)
     230
    224231        arg1 = args[0]
    225232        args = args[1:]
    226233        from sage.structure.element import is_Matrix
     
    229236            self.left, self.right = set(arg1.left), set(arg1.right)
    230237        elif isinstance(arg1, str):
    231238            Graph.__init__(self, *args, **kwds)
    232             self.load_afile(arg1)
     239            # will call self.load_afile after restoring add_vertex() instance
     240            # methods; initialize left and right attributes
     241            self.left = set()
     242            self.right = set()
    233243        elif is_Matrix(arg1):
    234244            # sanity check for mutually exclusive keywords
    235245            if kwds.get('multiedges',False) and kwds.get('weighted',False):
     
    287297                            self.delete_edges([(a, b) for b in a_nbrs])
    288298                self.left, self.right = set(args[0][0]), set(args[0][1])
    289299        elif isinstance(arg1, Graph):
     300            Graph.__init__(self, arg1, *args, **kwds)
    290301            try:
    291                 Graph.__init__(self, arg1, *args, **kwds)
    292302                self.left, self.right = self.bipartite_sets()
    293                 return
    294303            except:
    295304                raise TypeError("Input graph is not bipartite!")
    296305        else:
     
    315324                except:
    316325                    raise TypeError("Input graph is not bipartite!")
    317326
     327        # restore vertex partition checking
     328        self.add_vertex = types.MethodType(BipartiteGraph.add_vertex, self, BipartiteGraph)
     329        self.add_vertices = types.MethodType(BipartiteGraph.add_vertices, self, BipartiteGraph)
     330
     331        # post-processing
     332        if isinstance(arg1, str):
     333            self.load_afile(arg1)
     334
     335        return
     336
    318337    def __repr__(self):
    319338        r"""
    320339        Returns a short string representation of self.
     
    331350        else:
    332351            return 'Bipartite ' + s
    333352
     353
     354    def add_vertex(self, name=None, left=False, right=False):
     355        """
     356        Creates an isolated vertex. If the vertex already exists, then
     357        nothing is done.
     358       
     359        INPUT:
     360       
     361        - ``name`` - Name of the new vertex.  If no name is specified, then the
     362           vertex will be represented by the least non-negative integer not
     363           already representing a vertex.  Name must be an immutable object,
     364           and cannot be None.
     365
     366        - ``left`` - if True, puts the new vertex in the left partition.
     367
     368        - ``right`` - if True, puts the new vertex in the right partition.
     369
     370        Obviously, ``left`` and ``right`` are mutually exclusive.
     371       
     372        As it is implemented now, if a graph `G` has a large number
     373        of vertices with numeric labels, then G.add_vertex() could
     374        potentially be slow, if name is None.
     375       
     376        EXAMPLES::
     377       
     378            sage: G = BipartiteGraph()
     379            sage: G.add_vertex(left=True)
     380            sage: G.add_vertex(right=True)
     381            sage: G.vertices()
     382            [0, 1]
     383            sage: G.left
     384            set([0])
     385            sage: G.right
     386            set([1])
     387
     388        TEST::
     389
     390          Exactly one of ``left`` and ``right`` must be true::
     391
     392            sage: G = BipartiteGraph()
     393            sage: G.add_vertex()
     394            Traceback (most recent call last):
     395            ...
     396            RuntimeError: Partition must be specified (e.g. left=True).
     397            sage: G.add_vertex(left=True, right=True)
     398            Traceback (most recent call last):
     399            ...
     400            RuntimeError: Only one partition may be specified.
     401
     402          Adding the same vertex must specify the same partition::
     403         
     404            sage: bg = BipartiteGraph()
     405            sage: bg.add_vertex(0, right=True)
     406            sage: bg.add_vertex(0, right=True)
     407            sage: bg.vertices()
     408            [0]
     409            sage: bg.add_vertex(0, left=True)
     410            Traceback (most recent call last):
     411            ...
     412            RuntimeError: Cannot add duplicate vertex to other partition.
     413           
     414
     415        """
     416        # sanity check on partition specifiers
     417        if left and right:
     418            raise RuntimeError('Only one partition may be specified.')
     419        if not (left or right):
     420            raise RuntimeError('Partition must be specified (e.g. left=True).')
     421
     422        # do nothing if we already have this vertex (idempotent)
     423        if (name is not None) and (name in self):
     424            if ((name in self.left) and left) or ((name in self.right) and right):
     425                return
     426            else:
     427                raise RuntimeError('Cannot add duplicate vertex to other partition.')
     428
     429        # add the vertex
     430        if name is not None:
     431            # easy add
     432            Graph.add_vertex(self, name)
     433        else:
     434            # figure out what vertex name we end up with
     435            old_verts = set(self.vertices())
     436            Graph.add_vertex(self, name)
     437            new_verts = set(self.vertices())
     438            name = list(new_verts - old_verts)[0]
     439
     440        # add to proper partition
     441        if left:
     442            self.left.add(name)
     443        else:
     444            self.right.add(name)
     445
     446        return
     447
     448
     449    def add_vertices(self, vertices, left=False, right=False):
     450        """
     451        Add vertices to the bipartite graph from an iterable container of
     452        vertices.  Vertices that already exist in the graph will not be added
     453        again.
     454       
     455        INPUTS::
     456
     457          - ``vertices`` - sequence of vertices to add.
     458
     459          - ``left`` - either True or sequence of same length as ``vertices``
     460            with True/False elements.
     461
     462          - ``right`` - either True or sequence of the same length as
     463            ``vertices`` with True/False elements.
     464
     465        Only one of ``left`` and ``right`` keywords should be provided.  See
     466        the examples below.
     467
     468        EXAMPLES::
     469       
     470            sage: bg = BipartiteGraph()
     471            sage: bg.add_vertices([0,1,2], left=True)
     472            sage: bg.add_vertices([3,4,5], left=[True, False, True])
     473            sage: bg.add_vertices([6,7,8], right=[True, False, True])
     474            sage: bg.add_vertices([9,10,11], right=True)
     475            sage: bg.left
     476            set([0, 1, 2, 3, 5, 7])
     477            sage: bg.right
     478            set([4, 6, 8, 9, 10, 11])
     479           
     480
     481        TEST::
     482
     483            sage: bg = BipartiteGraph()
     484            sage: bg.add_vertices([0,1,2], left=True)
     485            sage: bg.add_vertices([0,1,2], left=[True,True,True])
     486            sage: bg.add_vertices([0,1,2], right=[False,False,False])
     487            sage: bg.add_vertices([0,1,2], right=[False,False,False])
     488            sage: bg.add_vertices([0,1,2])
     489            Traceback (most recent call last):
     490            ...
     491            RuntimeError: Partition must be specified (e.g. left=True).
     492            sage: bg.add_vertices([0,1,2], left=True, right=True)
     493            Traceback (most recent call last):
     494            ...
     495            RuntimeError: Only one partition may be specified.
     496            sage: bg.add_vertices([0,1,2], right=True)
     497            Traceback (most recent call last):
     498            ...
     499            RuntimeError: Cannot add duplicate vertex to other partition.
     500            sage: (bg.left, bg.right)
     501            (set([0, 1, 2]), set([]))
     502
     503        """
     504        # sanity check on partition specifiers
     505        if left and right:  # also triggered if both lists are specified
     506            raise RuntimeError('Only one partition may be specified.')
     507        if not (left or right):
     508            raise RuntimeError('Partition must be specified (e.g. left=True).')
     509
     510        # handle partitions
     511        if left and (not hasattr(left, '__iter__')):
     512            new_left = set(vertices)
     513            new_right = set()
     514        elif right and (not hasattr(right, '__iter__')):
     515            new_left = set()
     516            new_right = set(vertices)
     517        else:
     518            # simplify to always work with left
     519            if right:
     520                left = map(lambda tf: not tf, right)
     521            new_left = set()
     522            new_right = set()
     523            for tf,vv in zip(left, vertices):
     524                if tf:
     525                    new_left.add(vv)
     526                else:
     527                    new_right.add(vv)
     528
     529        # check that we're not trying to add vertices to the wrong sets
     530        if (new_left & self.right) or (new_right & self.left):
     531            raise RuntimeError('Cannot add duplicate vertex to other partition.')
     532
     533        # add vertices
     534        Graph.add_vertices(self, vertices)
     535        self.left.update(new_left)
     536        self.right.update(new_right)
     537
     538        return
     539
     540    def delete_vertex(self, vertex, in_order=False):
     541        """
     542        Deletes vertex, removing all incident edges. Deleting a non-existent
     543        vertex will raise an exception.
     544       
     545        INPUT:
     546       
     547       
     548        - ``in_order`` - (default ``False``) If ``True``, this deletes the ith vertex
     549           in the sorted list of vertices, i.e.  ``G.vertices()[i]``
     550       
     551       
     552        EXAMPLES::
     553       
     554            sage: B = BipartiteGraph(graphs.CycleGraph(4))
     555            sage: B
     556            Bipartite cycle graph: graph on 4 vertices
     557            sage: B.delete_vertex(0)
     558            sage: B
     559            Bipartite cycle graph: graph on 3 vertices
     560            sage: B.left
     561            set([2])
     562            sage: B.edges()
     563            [(1, 2, None), (2, 3, None)]
     564            sage: B.delete_vertex(3)
     565            sage: B.right
     566            set([1])
     567            sage: B.edges()
     568            [(1, 2, None)]
     569            sage: B.delete_vertex(0)
     570            Traceback (most recent call last):
     571            ...
     572            RuntimeError: Vertex (0) not in the graph.
     573
     574        ::
     575
     576            sage: g = Graph({'a':['b'], 'c':['b']})
     577            sage: bg = BipartiteGraph(g)  # finds bipartition
     578            sage: bg.vertices()
     579            ['a', 'b', 'c']
     580            sage: bg.delete_vertex('a')
     581            sage: bg.edges()
     582            [('b', 'c', None)]
     583            sage: bg.vertices()
     584            ['b', 'c']
     585            sage: bg2 = BipartiteGraph(g)
     586            sage: bg2.delete_vertex(0, in_order=True)
     587            sage: bg2 == bg
     588            True
     589        """
     590        # cache vertex lookup if requested
     591        if in_order:
     592            vertex = self.vertices()[vertex]
     593
     594        # delete from the graph
     595        Graph.delete_vertex(self, vertex)
     596
     597        # now remove from partition (exception already thrown for non-existant
     598        # vertex)
     599        try:
     600            self.left.remove(vertex)
     601        except:
     602            try:
     603                self.right.remove(vertex)
     604            except:
     605                raise RuntimeError("Vertex (%s) not found in partitions"%vertex)
     606
     607    def delete_vertices(self, vertices):
     608        """
     609        Remove vertices from the bipartite graph taken from an iterable
     610        sequence of vertices. Deleting a non-existent vertex will raise an
     611        exception.
     612       
     613        EXAMPLES::
     614       
     615            sage: B = BipartiteGraph(graphs.CycleGraph(4))
     616            sage: B
     617            Bipartite cycle graph: graph on 4 vertices
     618            sage: B.delete_vertices([0,3])
     619            sage: B
     620            Bipartite cycle graph: graph on 2 vertices
     621            sage: B.left
     622            set([2])
     623            sage: B.right
     624            set([1])
     625            sage: B.edges()
     626            [(1, 2, None)]
     627            sage: B.delete_vertices([0])
     628            Traceback (most recent call last):
     629            ...
     630            RuntimeError: Vertex (0) not in the graph.
     631
     632        """
     633        # remove vertices from the graph
     634        Graph.delete_vertices(self, vertices)
     635       
     636        # now remove vertices from partition lists (exception already thrown
     637        # for non-existant vertices)
     638        for vertex in vertices:
     639            try:
     640                self.left.remove(vertex)
     641            except:
     642                try:
     643                    self.right.remove(vertex)
     644                except:
     645                    raise RuntimeError("Vertex (%s) not found in partitions"%vertex)
     646
     647    def to_undirected(self):
     648        """
     649        Return an undirected Graph (without bipartite constraint) of the given
     650        object.
     651       
     652        EXAMPLES::
     653       
     654            sage: BipartiteGraph(graphs.CycleGraph(6)).to_undirected()
     655            Cycle graph: Graph on 6 vertices
     656        """
     657        return Graph(self)
     658
    334659    def bipartition(self):
    335660        r"""
    336661        Returns the underlying bipartition of the bipartite graph.
     
    483808
    484809        # clear out self
    485810        self.clear()
    486         self.add_vertices(range(num_cols + num_rows))
     811        self.add_vertices(range(num_cols), left=True)
     812        self.add_vertices(range(num_cols, num_cols+num_rows), right=True)
    487813
    488814        # read adjacency information
    489815        for cidx in range(num_cols):