| 3356 | def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, **kwds): |
| 3357 | r""" |
| 3358 | Returns a minimum edge multiway cut corresponding to the |
| 3359 | given set of vertices |
| 3360 | ( cf. http://www.d.kth.se/~viggo/wwwcompendium/node92.html ) |
| 3361 | represented by a list of edges. |
| 3362 | |
| 3363 | A multiway cut for a vertex set `S` in a graph or a digraph |
| 3364 | `G` if a set `C` of edges such that any two vertices `u,v` |
| 3365 | in `S` are disconnected when removing from `G` the edges from |
| 3366 | `C`. |
| 3367 | |
| 3368 | Such a cut is said to be minimum when its cardinality |
| 3369 | (or weight) is minimum. |
| 3370 | |
| 3371 | INPUT: |
| 3372 | |
| 3373 | - ``vertices`` (iterable)-- the set of vertices |
| 3374 | |
| 3375 | - ``value_only`` (boolean) |
| 3376 | |
| 3377 | - When set to ``True``, only the value of a minimum |
| 3378 | multiway cut is returned. |
| 3379 | |
| 3380 | - When set to ``False`` (default), the list of edges |
| 3381 | is returned |
| 3382 | |
| 3383 | - ``use_edge_labels`` (boolean) |
| 3384 | - When set to ``True``, computes a weighted minimum cut |
| 3385 | where each edge has a weight defined by its label. ( if |
| 3386 | an edge has no label, `1` is assumed ) |
| 3387 | |
| 3388 | - when set to ``False`` (default), each edge has weight `1`. |
| 3389 | |
| 3390 | - ``**kwds`` -- arguments to be passed down to the ``solve`` |
| 3391 | function of ``MixedIntegerLinearProgram``. See the documentation |
| 3392 | of ``MixedIntegerLinearProgram.solve`` for more information. |
| 3393 | |
| 3394 | EXAMPLES: |
| 3395 | |
| 3396 | Of course, a multiway cut between two vertices correspond |
| 3397 | to a minimum edge cut :: |
| 3398 | |
| 3399 | sage: g = graphs.PetersenGraph() |
| 3400 | sage: g.edge_cut(0,3) == g.multiway_cut([0,3], value_only = True) # optional - requires GLPK, CBC or CPLEX |
| 3401 | True |
| 3402 | |
| 3403 | As Petersen's graph is `3`-regular, a minimum multiway cut |
| 3404 | between three vertices contains at most `2\times 3` edges |
| 3405 | (which could correspond to the neighborhood of 2 |
| 3406 | vertices):: |
| 3407 | |
| 3408 | sage: g.multiway_cut([0,3,9], value_only = True) == 2*3 # optional - requires GLPK, CBC or CPLEX |
| 3409 | True |
| 3410 | |
| 3411 | In this case, though, the vertices are an independent set. |
| 3412 | If we pick instead vertices `0,9,` and `7`, we can save `4` |
| 3413 | edges in the multiway cut :: |
| 3414 | |
| 3415 | sage: g.multiway_cut([0,7,9], value_only = True) == 2*3 - 1 # optional - requires GLPK, CBC or CPLEX |
| 3416 | True |
| 3417 | |
| 3418 | This example, though, does not work in the directed case anymore, |
| 3419 | as it is not possible in Petersen's graph to mutualise edges :: |
| 3420 | |
| 3421 | sage: g = DiGraph(g) |
| 3422 | sage: g.multiway_cut([0,7,9], value_only = True) == 3*3 # optional - requires GLPK, CBC or CPLEX |
| 3423 | True |
| 3424 | |
| 3425 | Of course, a multiway cut between the whole vertex set |
| 3426 | contains all the edges of the graph:: |
| 3427 | |
| 3428 | sage: C = g.multiway_cut(g.vertices()) # optional - requires GLPK, CBC or CPLEX |
| 3429 | sage: set(C) == set(g.edges()) # optional - requires GLPK, CBC or CPLEX |
| 3430 | True |
| 3431 | """ |
| 3432 | from sage.numerical.mip import MixedIntegerLinearProgram |
| 3433 | from itertools import combinations, chain |
| 3434 | |
| 3435 | p = MixedIntegerLinearProgram(maximization = False) |
| 3436 | |
| 3437 | # height[c][v] represents the height of vertex v for commodity c |
| 3438 | height = p.new_variable(dim = 2) |
| 3439 | |
| 3440 | # cut[e] represents whether e is in the cut |
| 3441 | cut = p.new_variable() |
| 3442 | |
| 3443 | # Reorder |
| 3444 | R = lambda x,y : (x,y) if x<y else (y,x) |
| 3445 | |
| 3446 | # Weight function |
| 3447 | if use_edge_labels: |
| 3448 | w = lambda l : l if l is not None else 1 |
| 3449 | else: |
| 3450 | w = lambda l : 1 |
| 3451 | |
| 3452 | if self.is_directed(): |
| 3453 | |
| 3454 | p.set_objective( sum([ w(l) * cut[u,v] for u,v,l in self.edge_iterator() ]) ) |
| 3455 | |
| 3456 | for s,t in chain( combinations(vertices,2), map(lambda (x,y) : (y,x), combinations(vertices,2))) : |
| 3457 | # For each commodity, the source is at height 0 |
| 3458 | # and the destination is at height 1 |
| 3459 | p.add_constraint( height[(s,t)][s], min = 0, max = 0) |
| 3460 | p.add_constraint( height[(s,t)][t], min = 1, max = 1) |
| 3461 | |
| 3462 | # given a commodity (s,t), the height of two adjacent vertices u,v |
| 3463 | # can differ of at most the value of the edge between them |
| 3464 | for u,v in self.edges(labels = False): |
| 3465 | p.add_constraint( height[(s,t)][u] - height[(s,t)][v] - cut[u,v], max = 0) |
| 3466 | |
| 3467 | else: |
| 3468 | |
| 3469 | p.set_objective( sum([ w(l) * cut[R(u,v)] for u,v,l in self.edge_iterator() ]) ) |
| 3470 | |
| 3471 | for s,t in combinations(vertices,2): |
| 3472 | # For each commodity, the source is at height 0 |
| 3473 | # and the destination is at height 1 |
| 3474 | p.add_constraint( height[(s,t)][s], min = 0, max = 0) |
| 3475 | p.add_constraint( height[(s,t)][t], min = 1, max = 1) |
| 3476 | |
| 3477 | # given a commodity (s,t), the height of two adjacent vertices u,v |
| 3478 | # can differ of at most the value of the edge between them |
| 3479 | for u,v in self.edges(labels = False): |
| 3480 | p.add_constraint( height[(s,t)][u] - height[(s,t)][v] - cut[R(u,v)], max = 0) |
| 3481 | p.add_constraint( height[(s,t)][v] - height[(s,t)][u] - cut[R(u,v)], max = 0) |
| 3482 | |
| 3483 | |
| 3484 | p.set_binary(cut) |
| 3485 | if value_only: |
| 3486 | return p.solve(objective_only = True, **kwds) |
| 3487 | |
| 3488 | p.solve(**kwds) |
| 3489 | |
| 3490 | cut = p.get_values(cut) |
| 3491 | |
| 3492 | if self.is_directed(): |
| 3493 | return filter(lambda (u,v,l) : cut[u,v] > .5, self.edge_iterator()) |
| 3494 | |
| 3495 | else: |
| 3496 | return filter(lambda (u,v,l) : cut[R(u,v)] > .5, self.edge_iterator()) |
| 3497 | |