| 3200 | def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, **kwds): |
| 3201 | r""" |
| 3202 | Returns a minimum edge multiway cut corresponding to the |
| 3203 | given set of vertices |
| 3204 | ( cf. http://www.d.kth.se/~viggo/wwwcompendium/node92.html ) |
| 3205 | represented by a list of edges. |
| 3206 | |
| 3207 | A multiway cut for a vertex set `S` in a graph or a digraph |
| 3208 | `G` if a set `C` of edges such that any two vertices `u,v` |
| 3209 | in `S` are disconnected when removing from `G` the edges from |
| 3210 | `C`. |
| 3211 | |
| 3212 | Such a cut is said to be minimum when its cardinality |
| 3213 | (or weight) is minimum. |
| 3214 | |
| 3215 | INPUT: |
| 3216 | |
| 3217 | - ``vertices`` (iterable)-- the set of vertices |
| 3218 | |
| 3219 | - ``value_only`` (boolean) |
| 3220 | |
| 3221 | - When set to ``True``, only the value of a minimum |
| 3222 | multiway cut is returned. |
| 3223 | |
| 3224 | - When set to ``False`` (default), the list of edges |
| 3225 | is returned |
| 3226 | |
| 3227 | - ``use_edge_labels`` (boolean) |
| 3228 | - When set to ``True``, computes a weighted minimum cut |
| 3229 | where each edge has a weight defined by its label. ( if |
| 3230 | an edge has no label, `1` is assumed ) |
| 3231 | |
| 3232 | - when set to ``False`` (default), each edge has weight `1`. |
| 3233 | |
| 3234 | - ``**kwds`` -- arguments to be passed down to the ``solve`` |
| 3235 | function of ``MixedIntegerLinearProgram``. See the documentation |
| 3236 | of ``MixedIntegerLinearProgram.solve`` for more information. |
| 3237 | |
| 3238 | EXAMPLES: |
| 3239 | |
| 3240 | Of course, a multiway cut between two vertices correspond |
| 3241 | to a minimum edge cut :: |
| 3242 | |
| 3243 | sage: g = graphs.PetersenGraph() |
| 3244 | sage: g.edge_cut(0,3) == g.multiway_cut([0,3], value_only = True) # optional - requires GLPK, CBC or CPLEX |
| 3245 | True |
| 3246 | |
| 3247 | As Petersen's graph is `3`-regular, a minimum multiway cut |
| 3248 | between three vertices contains at most `2\times 3` edges |
| 3249 | (which could correspond to the neighborhood of 2 |
| 3250 | vertices):: |
| 3251 | |
| 3252 | sage: g.multiway_cut([0,3,9], value_only = True) == 2*3 # optional - requires GLPK, CBC or CPLEX |
| 3253 | True |
| 3254 | |
| 3255 | In this case, though, the vertices are an independent set. |
| 3256 | If we pick instead vertices `0,9,` and `7`, we can save `4` |
| 3257 | edges in the multiway cut :: |
| 3258 | |
| 3259 | sage: g.multiway_cut([0,7,9], value_only = True) == 2*3 - 1 # optional - requires GLPK, CBC or CPLEX |
| 3260 | True |
| 3261 | |
| 3262 | This example, though, does not work in the directed case anymore, |
| 3263 | as it is not possible in Petersen's graph to mutualise edges :: |
| 3264 | |
| 3265 | sage: g = DiGraph(g) |
| 3266 | sage: g.multiway_cut([0,7,9], value_only = True) == 3*3 # optional - requires GLPK, CBC or CPLEX |
| 3267 | True |
| 3268 | |
| 3269 | Of course, a multiway cut between the whole vertex set |
| 3270 | contains all the edges of the graph:: |
| 3271 | |
| 3272 | sage: C = g.multiway_cut(g.vertices()) # optional - requires GLPK, CBC or CPLEX |
| 3273 | sage: set(C) == set(g.edges()) # optional - requires GLPK, CBC or CPLEX |
| 3274 | True |
| 3275 | """ |
| 3276 | from sage.numerical.mip import MixedIntegerLinearProgram |
| 3277 | from itertools import combinations, chain |
| 3278 | |
| 3279 | p = MixedIntegerLinearProgram(maximization = False) |
| 3280 | |
| 3281 | # height[c][v] represents the height of vertex v for commodity c |
| 3282 | height = p.new_variable(dim = 2) |
| 3283 | |
| 3284 | # cut[e] represents whether e is in the cut |
| 3285 | cut = p.new_variable() |
| 3286 | |
| 3287 | # Reorder |
| 3288 | R = lambda x,y : (x,y) if x<y else (y,x) |
| 3289 | |
| 3290 | # Weight function |
| 3291 | if use_edge_labels: |
| 3292 | w = lambda l : l if l is not None else 1 |
| 3293 | else: |
| 3294 | w = lambda l : 1 |
| 3295 | |
| 3296 | if self.is_directed(): |
| 3297 | |
| 3298 | p.set_objective( sum([ w(l) * cut[u,v] for u,v,l in self.edge_iterator() ]) ) |
| 3299 | |
| 3300 | for s,t in chain( combinations(vertices,2), map(lambda (x,y) : (y,x), combinations(vertices,2))) : |
| 3301 | # For each commodity, the source is at height 0 |
| 3302 | # and the destination is at height 1 |
| 3303 | p.add_constraint( height[(s,t)][s], min = 0, max = 0) |
| 3304 | p.add_constraint( height[(s,t)][t], min = 1, max = 1) |
| 3305 | |
| 3306 | # given a commodity (s,t), the height of two adjacent vertices u,v |
| 3307 | # can differ of at most the value of the edge between them |
| 3308 | for u,v in self.edges(labels = False): |
| 3309 | p.add_constraint( height[(s,t)][u] - height[(s,t)][v] - cut[u,v], max = 0) |
| 3310 | |
| 3311 | else: |
| 3312 | |
| 3313 | p.set_objective( sum([ w(l) * cut[R(u,v)] for u,v,l in self.edge_iterator() ]) ) |
| 3314 | |
| 3315 | for s,t in combinations(vertices,2): |
| 3316 | # For each commodity, the source is at height 0 |
| 3317 | # and the destination is at height 1 |
| 3318 | p.add_constraint( height[(s,t)][s], min = 0, max = 0) |
| 3319 | p.add_constraint( height[(s,t)][t], min = 1, max = 1) |
| 3320 | |
| 3321 | # given a commodity (s,t), the height of two adjacent vertices u,v |
| 3322 | # can differ of at most the value of the edge between them |
| 3323 | for u,v in self.edges(labels = False): |
| 3324 | p.add_constraint( height[(s,t)][u] - height[(s,t)][v] - cut[R(u,v)], max = 0) |
| 3325 | p.add_constraint( height[(s,t)][v] - height[(s,t)][u] - cut[R(u,v)], max = 0) |
| 3326 | |
| 3327 | |
| 3328 | p.set_binary(cut) |
| 3329 | if value_only: |
| 3330 | return p.solve(objective_only = True, **kwds) |
| 3331 | |
| 3332 | p.solve(**kwds) |
| 3333 | |
| 3334 | cut = p.get_values(cut) |
| 3335 | |
| 3336 | if self.is_directed(): |
| 3337 | return filter(lambda (u,v,l) : cut[u,v] > .5, self.edge_iterator()) |
| 3338 | |
| 3339 | else: |
| 3340 | return filter(lambda (u,v,l) : cut[R(u,v)] > .5, self.edge_iterator()) |
| 3341 | |