Ticket #8640: trac_8640-bipartite.patch
File trac_8640-bipartite.patch, 39.7 KB (added by , 13 years ago) |
---|
-
doc/en/reference/graphs.rst
# HG changeset patch # User Minh Van Nguyen <nguyenminh2@gmail.com> # Date 1272661471 25200 # Node ID 19e6f9b02fe7093a3ca7899da71c801b13dd6650 # Parent 0c26d57cfc5ca87137186f07391b2bd02d60fb13 #8640: add BipartiteGraph to the reference manual diff --git a/doc/en/reference/graphs.rst b/doc/en/reference/graphs.rst
a b 4 4 .. toctree:: 5 5 :maxdepth: 2 6 6 7 sage/graphs/generic_graph 7 8 sage/graphs/graph 8 9 sage/graphs/digraph 9 sage/graphs/ generic_graph10 sage/graphs/bipartite_graph 10 11 11 12 sage/graphs/cliquer 12 13 sage/graphs/graph_coloring -
sage/graphs/bipartite_graph.py
diff --git a/sage/graphs/bipartite_graph.py b/sage/graphs/bipartite_graph.py
a b 4 4 This module implements bipartite graphs. 5 5 6 6 AUTHORS: 7 -- Robert L. Miller (2008-01-20): initial version8 -- Ryan W. Hinton (2010-03-04): overrides for adding and deleting vertices9 and edges10 7 11 TESTS: 8 - Robert L. Miller (2008-01-20): initial version 12 9 13 sage: B = graphs.CompleteBipartiteGraph(7,9) 10 - Ryan W. Hinton (2010-03-04): overrides for adding and deleting vertices 11 and edges 12 13 TESTS:: 14 15 sage: B = graphs.CompleteBipartiteGraph(7, 9) 14 16 sage: loads(dumps(B)) == B 15 17 True 16 18 17 19 :: 18 20 19 sage: B =BipartiteGraph(graphs.CycleGraph(4))21 sage: B = BipartiteGraph(graphs.CycleGraph(4)) 20 22 sage: B == B.copy() 21 23 True 22 24 sage: type(B.copy()) 23 25 <class 'sage.graphs.bipartite_graph.BipartiteGraph'> 24 25 26 """ 26 27 27 28 #***************************************************************************** … … 40 41 INPUT: 41 42 42 43 - ``data`` -- can be any of the following: 43 1. Empty or None (creates an empty graph). 44 2. An arbitrary graph (finds a bipartition). 45 3. A graph and a bipartition. 46 4. A reduced adjacency matrix. 47 5. A file in alist format. 48 6. From a NetworkX bipartite graph. 44 45 #. Empty or ``None`` (creates an empty graph). 46 #. An arbitrary graph (finds a bipartition). 47 #. A graph and a bipartition. 48 #. A reduced adjacency matrix. 49 #. A file in alist format. 50 #. From a NetworkX bipartite graph. 49 51 50 52 A reduced adjacency matrix contains only the non-redundant portion of the 51 53 full adjacency matrix for the bipartite graph. Specifically, for zero 52 matrices of the appropriate size, for the reduced adjacency matrix H, the53 full adjacency matrix is [[0, H'], [H, 0]].54 matrices of the appropriate size, for the reduced adjacency matrix ``H``, 55 the full adjacency matrix is ``[[0, H'], [H, 0]]``. 54 56 55 57 The alist file format is described at 56 58 http://www.inference.phy.cam.ac.uk/mackay/codes/alist.html 57 59 58 60 EXAMPLES: 59 60 1. No inputs or Nonefor the input creates an empty graph::61 62 1. No inputs or ``None`` for the input creates an empty graph:: 61 63 62 64 sage: B = BipartiteGraph() 63 65 sage: type(B) … … 69 71 70 72 2. From a graph: without any more information, finds a bipartition:: 71 73 72 sage: B = BipartiteGraph( graphs.CycleGraph(4))73 sage: B = BipartiteGraph( graphs.CycleGraph(5))74 sage: B = BipartiteGraph(graphs.CycleGraph(4)) 75 sage: B = BipartiteGraph(graphs.CycleGraph(5)) 74 76 Traceback (most recent call last): 75 77 ... 76 78 TypeError: Input graph is not bipartite! … … 91 93 set([4, 5, 6]) 92 94 93 95 3. From a graph and a partition. Note that if the input graph is not 94 bipartite, then Sage will raise an error. However, if one specifies check =95 False, the offending edges are simply deleted (along with those vertices96 not appearing in either list). We also lump creating one bipartite graph97 from another into this category::96 bipartite, then Sage will raise an error. However, if one specifies 97 ``check=False``, the offending edges are simply deleted (along with 98 those vertices not appearing in either list). We also lump creating 99 one bipartite graph from another into this category:: 98 100 99 101 sage: P = graphs.PetersenGraph() 100 102 sage: partition = [range(5), range(5,10)] … … 108 110 set([0, 1, 2, 3, 4]) 109 111 sage: B.show() 110 112 111 ::113 :: 112 114 113 115 sage: G = Graph({0:[5,6], 1:[4,5], 2:[4,6], 3:[4,5,6]}) 114 116 sage: B = BipartiteGraph(G) … … 121 123 sage: B3 == B2 122 124 True 123 125 124 ::126 :: 125 127 126 128 sage: G = Graph({0:[], 1:[], 2:[]}) 127 129 sage: part = (range(2), [2]) … … 132 134 133 135 4. From a reduced adjacency matrix:: 134 136 135 sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0), \136 137 sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0), 138 ... (0,1,0,1,0,1,0), (1,1,0,1,0,0,1)]) 137 139 sage: M 138 140 [1 1 1 0 0 0 0] 139 141 [1 0 0 1 1 0 0] … … 156 158 (5, 9, None), 157 159 (6, 10, None)] 158 160 159 ::161 :: 160 162 161 163 sage: M = Matrix([(1, 1, 2, 0, 0), (0, 2, 1, 1, 1), (0, 1, 2, 1, 1)]) 162 164 sage: B = BipartiteGraph(M, multiedges=True, sparse=True) … … 176 178 (4, 6, None), 177 179 (4, 7, None)] 178 180 179 ::181 :: 180 182 181 183 sage: F.<a> = GF(4) 182 184 sage: MS = MatrixSpace(F, 2, 3) … … 206 208 sage: G = graphs.OctahedralGraph() 207 209 sage: N = networkx.make_clique_bipartite(G.networkx_graph()) 208 210 sage: B = BipartiteGraph(N) 209 210 211 """ 211 212 212 213 def __init__(self, *args, **kwds): 213 214 """ 214 Create a bipartite graph. See documentation: BipartiteGraph? 215 216 EXAMPLE: 215 Create a bipartite graph. See documentation ``BipartiteGraph?`` for 216 detailed information. 217 218 EXAMPLE:: 219 217 220 sage: P = graphs.PetersenGraph() 218 221 sage: partition = [range(5), range(5,10)] 219 222 sage: B = BipartiteGraph(P, partition, check=False) 220 221 223 """ 222 224 if len(args) == 0: 223 225 Graph.__init__(self) 224 self.left = set(); self.right = set() 226 self.left = set() 227 self.right = set() 225 228 return 226 229 227 230 # need to turn off partition checking for Graph.__init__() adding 228 231 # vertices and edges; methods are restored ad the end of big "if" 229 232 # statement below 230 233 import types 231 self.add_vertex = types.MethodType(Graph.add_vertex, self, BipartiteGraph) 232 self.add_vertices = types.MethodType(Graph.add_vertices, self, BipartiteGraph) 234 self.add_vertex = types.MethodType(Graph.add_vertex, 235 self, 236 BipartiteGraph) 237 self.add_vertices = types.MethodType(Graph.add_vertices, 238 self, 239 BipartiteGraph) 233 240 self.add_edge = types.MethodType(Graph.add_edge, self, BipartiteGraph) 234 241 235 242 arg1 = args[0] … … 237 244 from sage.structure.element import is_Matrix 238 245 if isinstance(arg1, BipartiteGraph): 239 246 Graph.__init__(self, arg1, *args, **kwds) 240 self.left, self.right = set(arg1.left), set(arg1.right) 247 self.left = set(arg1.left) 248 self.right = set(arg1.right) 241 249 elif isinstance(arg1, str): 242 250 Graph.__init__(self, *args, **kwds) 243 251 # will call self.load_afile after restoring add_vertex() instance … … 246 254 self.right = set() 247 255 elif is_Matrix(arg1): 248 256 # sanity check for mutually exclusive keywords 249 if kwds.get('multiedges',False) and kwds.get('weighted',False): 250 raise TypeError, "Weighted multi-edge bipartite graphs from reduced adjacency matrix not supported." 257 if kwds.get("multiedges", False) and kwds.get("weighted", False): 258 raise TypeError( 259 "Weighted multi-edge bipartite graphs from reduced " + 260 "adjacency matrix not supported.") 251 261 Graph.__init__(self, *args, **kwds) 252 262 ncols = arg1.ncols() 253 263 nrows = arg1.nrows() 254 self.left, self.right = set(xrange(ncols)), set(xrange(ncols, nrows+ncols)) 255 if kwds.get('multiedges',False): 264 self.left = set(xrange(ncols)) 265 self.right = set(xrange(ncols, nrows + ncols)) 266 if kwds.get("multiedges", False): 256 267 for ii in range(ncols): 257 268 for jj in range(nrows): 258 269 if arg1[jj][ii] != 0: 259 self.add_edges([(ii, jj+ncols)] * arg1[jj][ii])260 elif kwds.get( 'weighted',False):270 self.add_edges([(ii, jj + ncols)] * arg1[jj][ii]) 271 elif kwds.get("weighted", False): 261 272 for ii in range(ncols): 262 273 for jj in range(nrows): 263 274 if arg1[jj][ii] != 0: 264 self.add_edge((ii, jj +ncols, arg1[jj][ii]))275 self.add_edge((ii, jj + ncols, arg1[jj][ii])) 265 276 else: 266 277 for ii in range(ncols): 267 278 for jj in range(nrows): 268 279 if arg1[jj][ii] != 0: 269 self.add_edge((ii, jj+ncols)) 270 elif isinstance(arg1, Graph) and \ 271 len(args) > 0 and isinstance(args[0], (list,tuple)) and \ 272 len(args[0]) == 2 and isinstance(args[0][0], (list,tuple)): 273 # Assume that args[0] is a bipartition 274 from copy import copy 275 left, right = args[0]; left = copy(left); right = copy(right) 276 verts = set(left)|set(right) 277 if set(arg1.vertices()) != verts: 278 arg1 = arg1.subgraph(list(verts)) 279 Graph.__init__(self, arg1, *(args[1:]), **kwds) 280 if not kwds.has_key('check') or kwds['check']: 281 while len(left) > 0: 282 a = left.pop(0) 283 if len( set( arg1.neighbors(a) ) & set(left) ) != 0: 284 raise TypeError("Input graph is not bipartite with " + \ 285 "respect to the given partition!") 286 while len(right) > 0: 287 a = right.pop(0) 288 if len( set( arg1.neighbors(a) ) & set(right) ) != 0: 289 raise TypeError("Input graph is not bipartite with " + \ 290 "respect to the given partition!") 291 else: 292 while len(left) > 0: 293 a = left.pop(0) 294 a_nbrs = set( arg1.neighbors(a) ) & set(left) 295 if len( a_nbrs ) != 0: 296 self.delete_edges([(a, b) for b in a_nbrs]) 297 while len(right) > 0: 298 a = right.pop(0) 299 a_nbrs = set( arg1.neighbors(a) ) & set(right) 300 if len( a_nbrs ) != 0: 301 self.delete_edges([(a, b) for b in a_nbrs]) 302 self.left, self.right = set(args[0][0]), set(args[0][1]) 280 self.add_edge((ii, jj + ncols)) 281 elif (isinstance(arg1, Graph) and 282 len(args) > 0 and isinstance(args[0], (list, tuple)) and 283 len(args[0]) == 2 and isinstance(args[0][0], (list, tuple))): 284 # Assume that args[0] is a bipartition 285 from copy import copy 286 left, right = args[0] 287 left = copy(left) 288 right = copy(right) 289 verts = set(left) | set(right) 290 if set(arg1.vertices()) != verts: 291 arg1 = arg1.subgraph(list(verts)) 292 Graph.__init__(self, arg1, *(args[1:]), **kwds) 293 if "check" not in kwds or kwds["check"]: 294 while len(left) > 0: 295 a = left.pop(0) 296 if len(set(arg1.neighbors(a)) & set(left)) != 0: 297 raise TypeError( 298 "Input graph is not bipartite with " + 299 "respect to the given partition!") 300 while len(right) > 0: 301 a = right.pop(0) 302 if len(set(arg1.neighbors(a)) & set(right)) != 0: 303 raise TypeError( 304 "Input graph is not bipartite with " + 305 "respect to the given partition!") 306 else: 307 while len(left) > 0: 308 a = left.pop(0) 309 a_nbrs = set(arg1.neighbors(a)) & set(left) 310 if len(a_nbrs) != 0: 311 self.delete_edges([(a, b) for b in a_nbrs]) 312 while len(right) > 0: 313 a = right.pop(0) 314 a_nbrs = set(arg1.neighbors(a)) & set(right) 315 if len(a_nbrs) != 0: 316 self.delete_edges([(a, b) for b in a_nbrs]) 317 self.left, self.right = set(args[0][0]), set(args[0][1]) 303 318 elif isinstance(arg1, Graph): 304 319 Graph.__init__(self, arg1, *args, **kwds) 305 320 try: … … 310 325 import networkx 311 326 Graph.__init__(self, arg1, *args, **kwds) 312 327 if isinstance(arg1, (networkx.MultiGraph, networkx.Graph)): 313 if hasattr(arg1, 'node_type'):328 if hasattr(arg1, "node_type"): 314 329 # Assume the graph is bipartite 315 330 self.left = set() 316 331 self.right = set() 317 332 for v in arg1.nodes_iter(): 318 if arg1.node_type[v] == 'Bottom':333 if arg1.node_type[v] == "Bottom": 319 334 self.left.add(v) 320 elif arg1.node_type[v] == 'Top':335 elif arg1.node_type[v] == "Top": 321 336 self.right.add(v) 322 337 else: 323 raise TypeError("NetworkX node_type defies bipartite assumption (is not 'Top' or 'Bottom')") 338 raise TypeError( 339 "NetworkX node_type defies bipartite " + 340 "assumption (is not 'Top' or 'Bottom')") 324 341 # make sure we found a bipartition 325 if not (hasattr(self, 'left') and hasattr(self, 'right')):342 if not (hasattr(self, "left") and hasattr(self, "right")): 326 343 try: 327 344 self.left, self.right = self.bipartite_sets() 328 345 except: 329 346 raise TypeError("Input graph is not bipartite!") 330 347 331 348 # restore vertex partition checking 332 self.add_vertex = types.MethodType(BipartiteGraph.add_vertex, self, BipartiteGraph) 333 self.add_vertices = types.MethodType(BipartiteGraph.add_vertices, self, BipartiteGraph) 334 self.add_edge = types.MethodType(BipartiteGraph.add_edge, self, BipartiteGraph) 349 self.add_vertex = types.MethodType(BipartiteGraph.add_vertex, 350 self, 351 BipartiteGraph) 352 self.add_vertices = types.MethodType(BipartiteGraph.add_vertices, 353 self, 354 BipartiteGraph) 355 self.add_edge = types.MethodType(BipartiteGraph.add_edge, 356 self, 357 BipartiteGraph) 335 358 336 359 # post-processing 337 360 if isinstance(arg1, str): … … 343 366 r""" 344 367 Returns a short string representation of self. 345 368 346 EXAMPLE: 369 EXAMPLE:: 370 347 371 sage: B = BipartiteGraph(graphs.CycleGraph(16)) 348 372 sage: B 349 373 Bipartite cycle graph: graph on 16 vertices 350 351 374 """ 352 375 s = Graph._repr_(self).lower() 353 if 'bipartite'in s:376 if "bipartite" in s: 354 377 return s.capitalize() 355 378 else: 356 return 'Bipartite ' + s 357 379 return "".join(["Bipartite ", s]) 358 380 359 381 def add_vertex(self, name=None, left=False, right=False): 360 382 """ 361 383 Creates an isolated vertex. If the vertex already exists, then 362 384 nothing is done. 363 385 364 386 INPUT: 365 366 - ``name`` - Name of the new vertex. If no name is specified, then the367 vertex will be represented by the least non-negative integer not368 already representing a vertex. Name must be an immutable object,369 and cannot be None.370 387 371 - ``left`` - if True, puts the new vertex in the left partition. 388 - ``name`` -- (default: ``None``) name of the new vertex. If no name 389 is specified, then the vertex will be represented by the least 390 non-negative integer not already representing a vertex. Name must 391 be an immutable object and cannot be ``None``. 372 392 373 - ``right`` - if True, puts the new vertex in the right partition. 393 - ``left`` -- (default: ``False``) if ``True``, puts the new vertex 394 in the left partition. 395 396 - ``right`` -- (default: ``False``) if ``True``, puts the new vertex 397 in the right partition. 374 398 375 399 Obviously, ``left`` and ``right`` are mutually exclusive. 376 400 377 401 As it is implemented now, if a graph `G` has a large number 378 of vertices with numeric labels, then G.add_vertex()could379 potentially be slow, if name is None.380 402 of vertices with numeric labels, then ``G.add_vertex()`` could 403 potentially be slow, if name is ``None``. 404 381 405 EXAMPLES:: 382 406 383 407 sage: G = BipartiteGraph() 384 408 sage: G.add_vertex(left=True) 385 409 sage: G.add_vertex(right=True) … … 390 414 sage: G.right 391 415 set([1]) 392 416 393 TEST ::417 TESTS: 394 418 395 419 Exactly one of ``left`` and ``right`` must be true:: 396 420 397 421 sage: G = BipartiteGraph() 398 422 sage: G.add_vertex() … … 404 428 ... 405 429 RuntimeError: Only one partition may be specified. 406 430 407 408 431 Adding the same vertex must specify the same partition:: 432 409 433 sage: bg = BipartiteGraph() 410 434 sage: bg.add_vertex(0, right=True) 411 435 sage: bg.add_vertex(0, right=True) … … 415 439 Traceback (most recent call last): 416 440 ... 417 441 RuntimeError: Cannot add duplicate vertex to other partition. 418 419 420 442 """ 421 443 # sanity check on partition specifiers 422 444 if left and right: 423 raise RuntimeError( 'Only one partition may be specified.')445 raise RuntimeError("Only one partition may be specified.") 424 446 if not (left or right): 425 raise RuntimeError( 'Partition must be specified (e.g. left=True).')447 raise RuntimeError("Partition must be specified (e.g. left=True).") 426 448 427 449 # do nothing if we already have this vertex (idempotent) 428 450 if (name is not None) and (name in self): 429 if ((name in self.left) and left) or ((name in self.right) and right): 451 if (((name in self.left) and left) or 452 ((name in self.right) and right)): 430 453 return 431 454 else: 432 raise RuntimeError('Cannot add duplicate vertex to other partition.') 455 raise RuntimeError( 456 "Cannot add duplicate vertex to other partition.") 433 457 434 458 # add the vertex 435 459 if name is not None: … … 450 474 451 475 return 452 476 453 454 477 def add_vertices(self, vertices, left=False, right=False): 455 478 """ 456 479 Add vertices to the bipartite graph from an iterable container of 457 480 vertices. Vertices that already exist in the graph will not be added 458 481 again. 459 482 460 483 INPUTS: 461 484 462 - ``vertices``- sequence of vertices to add.485 - ``vertices`` -- sequence of vertices to add. 463 486 464 - ``left`` - either True or sequence of same length as ``vertices``465 with True/Falseelements.487 - ``left`` -- (default: ``False``) either ``True`` or sequence of 488 same length as ``vertices`` with ``True``/``False`` elements. 466 489 467 - ``right`` - either True or sequence of the same length as468 ``vertices`` with True/Falseelements.490 - ``right`` -- (default: ``False``) either ``True`` or sequence of 491 the same length as ``vertices`` with ``True``/``False`` elements. 469 492 470 493 Only one of ``left`` and ``right`` keywords should be provided. See 471 494 the examples below. 472 495 473 496 EXAMPLES:: 474 497 475 498 sage: bg = BipartiteGraph() 476 499 sage: bg.add_vertices([0,1,2], left=True) 477 500 sage: bg.add_vertices([3,4,5], left=[True, False, True]) … … 481 504 set([0, 1, 2, 3, 5, 7]) 482 505 sage: bg.right 483 506 set([4, 6, 8, 9, 10, 11]) 484 485 507 486 508 TEST:: 487 509 … … 504 526 RuntimeError: Cannot add duplicate vertex to other partition. 505 527 sage: (bg.left, bg.right) 506 528 (set([0, 1, 2]), set([])) 507 508 529 """ 509 530 # sanity check on partition specifiers 510 531 if left and right: # also triggered if both lists are specified 511 raise RuntimeError( 'Only one partition may be specified.')532 raise RuntimeError("Only one partition may be specified.") 512 533 if not (left or right): 513 raise RuntimeError( 'Partition must be specified (e.g. left=True).')534 raise RuntimeError("Partition must be specified (e.g. left=True).") 514 535 515 536 # handle partitions 516 if left and (not hasattr(left, '__iter__')):537 if left and (not hasattr(left, "__iter__")): 517 538 new_left = set(vertices) 518 539 new_right = set() 519 elif right and (not hasattr(right, '__iter__')):540 elif right and (not hasattr(right, "__iter__")): 520 541 new_left = set() 521 542 new_right = set(vertices) 522 543 else: … … 525 546 left = map(lambda tf: not tf, right) 526 547 new_left = set() 527 548 new_right = set() 528 for tf, vv in zip(left, vertices):549 for tf, vv in zip(left, vertices): 529 550 if tf: 530 551 new_left.add(vv) 531 552 else: … … 533 554 534 555 # check that we're not trying to add vertices to the wrong sets 535 556 # or that a vertex is to be placed in both 536 if (new_left & self.right) or (new_right & self.left) or (new_right & new_left): 537 raise RuntimeError('Cannot add duplicate vertex to other partition.') 557 if ((new_left & self.right) or 558 (new_right & self.left) or 559 (new_right & new_left)): 560 raise RuntimeError( 561 "Cannot add duplicate vertex to other partition.") 538 562 539 563 # add vertices 540 564 Graph.add_vertices(self, vertices) … … 547 571 """ 548 572 Deletes vertex, removing all incident edges. Deleting a non-existent 549 573 vertex will raise an exception. 550 574 551 575 INPUT: 552 553 - ``in_order`` - (default ``False``) If ``True``, this deletes the `i`th vertex 554 in the sorted list of vertices, i.e. ``G.vertices()[i]`` 555 576 577 - ``vertex`` -- a vertex to delete. 578 579 - ``in_order`` -- (default ``False``) if ``True``, this deletes the 580 `i`-th vertex in the sorted list of vertices, 581 i.e. ``G.vertices()[i]``. 582 556 583 EXAMPLES:: 557 584 558 585 sage: B = BipartiteGraph(graphs.CycleGraph(4)) 559 586 sage: B 560 587 Bipartite cycle graph: graph on 4 vertices … … 606 633 try: 607 634 self.right.remove(vertex) 608 635 except: 609 raise RuntimeError("Vertex (%s) not found in partitions"%vertex) 636 raise RuntimeError( 637 "Vertex (%s) not found in partitions" % vertex) 610 638 611 639 def delete_vertices(self, vertices): 612 640 """ 613 641 Remove vertices from the bipartite graph taken from an iterable 614 642 sequence of vertices. Deleting a non-existent vertex will raise an 615 643 exception. 616 644 645 INPUT: 646 647 - ``vertices`` -- a sequence of vertices to remove. 648 617 649 EXAMPLES:: 618 650 619 651 sage: B = BipartiteGraph(graphs.CycleGraph(4)) 620 652 sage: B 621 653 Bipartite cycle graph: graph on 4 vertices … … 632 664 Traceback (most recent call last): 633 665 ... 634 666 RuntimeError: Vertex (0) not in the graph. 635 636 667 """ 637 668 # remove vertices from the graph 638 669 Graph.delete_vertices(self, vertices) 639 670 640 671 # now remove vertices from partition lists (exception already thrown 641 672 # for non-existant vertices) 642 673 for vertex in vertices: … … 646 677 try: 647 678 self.right.remove(vertex) 648 679 except: 649 raise RuntimeError("Vertex (%s) not found in partitions"%vertex) 680 raise RuntimeError( 681 "Vertex (%s) not found in partitions" % vertex) 650 682 651 683 def add_edge(self, u, v=None, label=None): 652 684 """ 653 Adds an edge from u and v.685 Adds an edge from ``u`` and ``v``. 654 686 655 INPUT: The following forms are all accepted: 656 657 - G.add_edge( 1, 2 ) 658 - G.add_edge( (1, 2) ) 659 - G.add_edges( [ (1, 2) ]) 660 - G.add_edge( 1, 2, 'label' ) 661 - G.add_edge( (1, 2, 'label') ) 662 - G.add_edges( [ (1, 2, 'label') ] ) 663 664 See Graph.add_edge for more detail. This method simply checks that the 665 edge endpoints are in different partitions. 687 INPUT: 688 689 - ``u`` -- the tail of an edge. 690 691 - ``v`` -- (default: ``None``) the head of an edge. If ``v=None``, then 692 attempt to add the edge ``(u, u)``. 693 694 - ``label`` -- (default: ``None``) the label of the edge ``(u, v)``. 695 696 The following forms are all accepted: 697 698 - ``G.add_edge(1, 2)`` 699 - ``G.add_edge((1, 2))`` 700 - ``G.add_edges([(1, 2)])`` 701 - ``G.add_edge(1, 2, 'label')`` 702 - ``G.add_edge((1, 2, 'label'))`` 703 - ``G.add_edges([(1, 2, 'label')])`` 704 705 See ``Graph.add_edge`` for more detail. This method simply checks 706 that the edge endpoints are in different partitions. 666 707 667 708 TEST:: 668 709 … … 673 714 Traceback (most recent call last): 674 715 ... 675 716 RuntimeError: Edge vertices must lie in different partitions. 676 677 717 """ 678 718 # logic for getting endpoints copied from generic_graph.py 679 719 if label is None: … … 688 728 u, v = u 689 729 690 730 # check for endpoints in different partitions 691 if self.left.issuperset((u,v)) or self.right.issuperset((u,v)): 692 raise RuntimeError('Edge vertices must lie in different partitions.') 731 if self.left.issuperset((u, v)) or self.right.issuperset((u, v)): 732 raise RuntimeError( 733 "Edge vertices must lie in different partitions.") 693 734 694 735 # add the edge 695 736 Graph.add_edge(self, u, v, label) … … 699 740 """ 700 741 Return an undirected Graph (without bipartite constraint) of the given 701 742 object. 702 743 703 744 EXAMPLES:: 704 745 705 746 sage: BipartiteGraph(graphs.CycleGraph(6)).to_undirected() 706 747 Cycle graph: Graph on 6 vertices 707 748 """ … … 711 752 r""" 712 753 Returns the underlying bipartition of the bipartite graph. 713 754 714 EXAMPLE: 715 sage: B = BipartiteGraph( graphs.CycleGraph(4) ) 755 EXAMPLE:: 756 757 sage: B = BipartiteGraph(graphs.CycleGraph(4)) 716 758 sage: B.bipartition() 717 759 (set([0, 2]), set([1, 3])) 718 719 760 """ 720 761 return (self.left, self.right) 721 762 722 763 def project_left(self): 723 764 r""" 724 Projects self onto left vertices: edges are 2-paths in the original. 765 Projects ``self`` onto left vertices. Edges are 2-paths in the 766 original. 725 767 726 EXAMPLE: 768 EXAMPLE:: 727 769 728 770 sage: B = BipartiteGraph(graphs.CycleGraph(20)) 729 771 sage: G = B.project_left() 730 772 sage: G.order(), G.size() 731 773 (10, 10) 732 733 774 """ 734 775 G = Graph() 735 776 G.add_vertices(self.left) 736 777 for v in G: 737 778 for u in self.neighbor_iterator(v): 738 G.add_edges([(v, w) for w in self.neighbor_iterator(u)])779 G.add_edges([(v, w) for w in self.neighbor_iterator(u)]) 739 780 return G 740 781 741 782 def project_right(self): 742 783 r""" 743 Projects self onto right vertices: edges are 2-paths in the original. 784 Projects ``self`` onto right vertices. Edges are 2-paths in the 785 original. 744 786 745 EXAMPLE: 787 EXAMPLE:: 746 788 747 789 sage: B = BipartiteGraph(graphs.CycleGraph(20)) 748 790 sage: G = B.project_right() 749 791 sage: G.order(), G.size() 750 792 (10, 10) 751 752 793 """ 753 794 G = Graph() 754 795 G.add_vertices(self.left) 755 796 for v in G: 756 797 for u in self.neighbor_iterator(v): 757 G.add_edges([(v, w) for w in self.neighbor_iterator(u)])798 G.add_edges([(v, w) for w in self.neighbor_iterator(u)]) 758 799 return G 759 800 760 801 def plot(self, *args, **kwds): 761 802 r""" 762 803 Overrides Graph's plot function, to illustrate the bipartite nature. 763 804 764 EXAMPLE: 805 EXAMPLE:: 765 806 766 807 sage: B = BipartiteGraph(graphs.CycleGraph(20)) 767 808 sage: B.plot() 768 769 809 """ 770 if 'pos' not in kwds.keys():771 kwds[ 'pos'] = None772 if kwds[ 'pos'] is None:810 if "pos" not in kwds: 811 kwds["pos"] = None 812 if kwds["pos"] is None: 773 813 pos = {} 774 814 left = list(self.left) 775 815 right = list(self.right) … … 781 821 pos[left[0]] = [-1, 0] 782 822 elif l_len > 1: 783 823 i = 0 784 d = 2. /(l_len-1)824 d = 2.0 / (l_len - 1) 785 825 for v in left: 786 pos[v] = [-1, 1 -i*d]826 pos[v] = [-1, 1 - i*d] 787 827 i += 1 788 828 if r_len == 1: 789 829 pos[right[0]] = [1, 0] 790 830 elif r_len > 1: 791 831 i = 0 792 d = 2. /(r_len-1)832 d = 2.0 / (r_len - 1) 793 833 for v in right: 794 pos[v] = [1, 1 -i*d]834 pos[v] = [1, 1 - i*d] 795 835 i += 1 796 kwds[ 'pos'] = pos836 kwds["pos"] = pos 797 837 return Graph.plot(self, *args, **kwds) 798 838 799 839 def load_afile(self, fname): … … 801 841 Loads into the current object the bipartite graph specified in the 802 842 given file name. This file should follow David MacKay's alist format, 803 843 see 804 844 http://www.inference.phy.cam.ac.uk/mackay/codes/data.html 805 845 for examples and definition of the format. 806 807 EXAMPLE: 846 847 EXAMPLE:: 848 808 849 sage: file_name = SAGE_TMP + 'deleteme.alist.txt' 809 850 sage: fi = open(file_name, 'w') 810 851 sage: fi.write("7 4 \n 3 4 \n 3 3 1 3 1 1 1 \n 3 3 3 4 \n\ … … 833 874 sage: B2 == B 834 875 True 835 876 """ 836 837 877 # open the file 838 878 try: 839 fi = open(fname, 'r')879 fi = open(fname, "r") 840 880 except IOError: 841 881 print("Unable to open file <<" + fname + ">>.") 842 882 return None … … 850 890 # sanity checks on header info 851 891 if len(col_degrees) != num_cols: 852 892 print("Invalid Alist format: ") 853 print("Number of column degree entries does not match number of columns.") 893 print("Number of column degree entries does not match number " + 894 "of columns.") 854 895 return None 855 896 if len(row_degrees) != num_rows: 856 897 print("Invalid Alist format: ") 857 print("Number of row degree entries does not match number of rows.") 898 print("Number of row degree entries does not match number " + 899 "of rows.") 858 900 return None 859 901 860 902 # clear out self 861 903 self.clear() 862 904 self.add_vertices(range(num_cols), left=True) 863 self.add_vertices(range(num_cols, num_cols +num_rows), right=True)905 self.add_vertices(range(num_cols, num_cols + num_rows), right=True) 864 906 865 907 # read adjacency information 866 908 for cidx in range(num_cols): … … 869 911 if ridx > 0: 870 912 self.add_edge(cidx, num_cols + ridx - 1) 871 913 872 #NOTE:: we could read in the row adjacency information as well to double-check.... 873 #NOTE:: we could check the actual node degrees against the reported node degrees.... 914 #NOTE:: we could read in the row adjacency information as well to 915 # double-check.... 916 #NOTE:: we could check the actual node degrees against the reported 917 # node degrees.... 874 918 875 # now we have all the edges in our graph, just fill in the bipartite partitioning 919 # now we have all the edges in our graph, just fill in the 920 # bipartite partitioning 876 921 self.left = set(xrange(num_cols)) 877 922 self.right = set(xrange(num_cols, num_cols + num_rows)) 878 923 … … 881 926 882 927 def save_afile(self, fname): 883 928 r""" 884 Save the graph to file in alist format. 929 Save the graph to file in alist format. 885 930 886 Saves this graph to file in David MacKay's alist format, see 887 931 Saves this graph to file in David MacKay's alist format, see 932 http://www.inference.phy.cam.ac.uk/mackay/codes/data.html 888 933 for examples and definition of the format. 889 890 EXAMPLE: 891 sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0), \ 892 (0,1,0,1,0,1,0), (1,1,0,1,0,0,1)]) 934 935 EXAMPLE:: 936 937 sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0), 938 ... (0,1,0,1,0,1,0), (1,1,0,1,0,0,1)]) 893 939 sage: M 894 940 [1 1 1 0 0 0 0] 895 941 [1 0 0 1 1 0 0] … … 902 948 sage: b == b2 903 949 True 904 950 905 TESTS: 951 TESTS:: 952 906 953 sage: file_name = SAGE_TMP + 'deleteme.alist.txt' 907 954 sage: for order in range(3, 13, 3): 908 955 ... num_chks = int(order / 3) … … 914 961 ... b = BipartiteGraph(g, partition, check=False) 915 962 ... b.save_afile(file_name) 916 963 ... b2 = BipartiteGraph(file_name) 917 ... if b != b2: 964 ... if b != b2: 918 965 ... print "Load/save failed for code with edges:" 919 966 ... print b.edges() 920 967 ... except: … … 922 969 ... print "with edges: " 923 970 ... g.edges() 924 971 ... raise 925 926 972 """ 927 928 973 # open the file 929 974 try: 930 fi = open(fname, 'w')975 fi = open(fname, "w") 931 976 except IOError: 932 977 print("Unable to open file <<" + fname + ">>.") 933 978 return … … 968 1013 Translate an edge to its reduced adjacency matrix position. 969 1014 970 1015 Returns (row index, column index) for the given pair of vertices. 971 972 EXAMPLE: 1016 1017 EXAMPLE:: 1018 973 1019 sage: P = graphs.PetersenGraph() 974 1020 sage: partition = [range(5), range(5,10)] 975 1021 sage: B = BipartiteGraph(P, partition, check=False) 976 1022 sage: B._BipartiteGraph__edge2idx(2,7,range(5),range(5,10)) 977 1023 (2, 2) 978 979 1024 """ 980 1025 try: 981 1026 if v1 in self.left: # note uses attribute for faster lookup … … 983 1028 else: 984 1029 return (right.index(v1), left.index(v2)) 985 1030 except ValueError: 986 raise ValueError("Tried to map invalid edge (%d,%d) to vertex indices" \ 987 % (v1, v2)) 1031 raise ValueError( 1032 "Tried to map invalid edge (%d,%d) to vertex indices" % 1033 (v1, v2)) 988 1034 989 1035 def reduced_adjacency_matrix(self, sparse=True): 990 1036 r""" 991 1037 Return the reduced adjacency matrix for the given graph. 992 1038 993 A reduced adjacency matrix contains only the non-redundant portion of the994 full adjacency matrix for the bipartite graph. Specifically, for zero995 matrices of the appropriate size, for the reduced adjacency matrix H, the996 full adjacency matrix is [[0, H'], [H, 0]].1039 A reduced adjacency matrix contains only the non-redundant portion of 1040 the full adjacency matrix for the bipartite graph. Specifically, for 1041 zero matrices of the appropriate size, for the reduced adjacency 1042 matrix ``H``, the full adjacency matrix is ``[[0, H'], [H, 0]]``. 997 1043 998 1044 This method supports the named argument 'sparse' which defaults to 999 True. When enabled, the returned matrix will be sparse.1045 ``True``. When enabled, the returned matrix will be sparse. 1000 1046 1001 1047 EXAMPLES: 1002 1048 1003 Bipartite graphs that are not weighted will return a matrix over ZZ. 1004 sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0), \ 1005 (0,1,0,1,0,1,0), (1,1,0,1,0,0,1)]) 1049 Bipartite graphs that are not weighted will return a matrix over ZZ:: 1050 1051 sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0), 1052 ... (0,1,0,1,0,1,0), (1,1,0,1,0,0,1)]) 1006 1053 sage: B = BipartiteGraph(M) 1007 1054 sage: N = B.reduced_adjacency_matrix() 1008 1055 sage: N … … 1015 1062 sage: N[0,0].parent() 1016 1063 Integer Ring 1017 1064 1018 Multi-edge graphs also return a matrix over ZZ. 1019 sage: M = Matrix([(1, 1, 2, 0, 0), (0, 2, 1, 1, 1), (0, 1, 2, 1, 1)]) 1065 Multi-edge graphs also return a matrix over ZZ:: 1066 1067 sage: M = Matrix([(1,1,2,0,0), (0,2,1,1,1), (0,1,2,1,1)]) 1020 1068 sage: B = BipartiteGraph(M, multiedges=True, sparse=True) 1021 1069 sage: N = B.reduced_adjacency_matrix() 1022 1070 sage: N == M … … 1025 1073 Integer Ring 1026 1074 1027 1075 Weighted graphs will return a matrix over the ring given by their 1028 (first) weights. 1076 (first) weights:: 1077 1029 1078 sage: F.<a> = GF(4) 1030 1079 sage: MS = MatrixSpace(F, 2, 3) 1031 1080 sage: M = MS.matrix([[0, 1, a+1], [a, 1, 1]]) … … 1036 1085 sage: N[0,0].parent() 1037 1086 Finite Field in a of size 2^2 1038 1087 1039 TESTS: 1088 TESTS:: 1089 1040 1090 sage: B = BipartiteGraph() 1041 1091 sage: B.reduced_adjacency_matrix() 1042 1092 [] … … 1049 1099 True 1050 1100 """ 1051 1101 if self.multiple_edges() and self.weighted(): 1052 raise NotImplementedError, "Don't know how to represent weights for a multigraph." 1102 raise NotImplementedError( 1103 "Don't know how to represent weights for a multigraph.") 1053 1104 if self.is_directed(): 1054 raise NotImplementedError, "Reduced adjacency matrix does not exist for directed graphs." 1105 raise NotImplementedError( 1106 "Reduced adjacency matrix does not exist for directed graphs.") 1055 1107 1056 1108 # create sorted lists of left and right edges 1057 1109 left = list(self.left) … … 1069 1121 # if we're normal or multi-edge, just create the matrix over ZZ 1070 1122 for (v1, v2, name) in self.edge_iterator(): 1071 1123 idx = self.__edge2idx(v1, v2, left, right) 1072 if D.has_key(idx):1124 if idx in D: 1073 1125 D[idx] = 1 + D[idx] 1074 1126 else: 1075 1127 D[idx] = 1