| 3089 | |
| 3090 | def edge_disjoint_spanning_trees(self,k, root=None, **kwds): |
| 3091 | r""" |
| 3092 | Returns the desired number of edge-disjoint spanning |
| 3093 | trees/arborescences. |
| 3094 | |
| 3095 | INPUT: |
| 3096 | |
| 3097 | - ``k`` (integer) -- the required number of edge-disjoint |
| 3098 | spanning trees/arborescences |
| 3099 | |
| 3100 | - ``root`` (vertex) -- root of the disjoint arborescences |
| 3101 | when the graph is directed. |
| 3102 | If set to ``None``, the first vertex in the graph is picked. |
| 3103 | |
| 3104 | - ``**kwds`` -- arguments to be passed down to the ``solve`` |
| 3105 | function of ``MixedIntegerLinearProgram``. See the documentation |
| 3106 | of ``MixedIntegerLinearProgram.solve`` for more informations. |
| 3107 | |
| 3108 | ALGORITHM: |
| 3109 | |
| 3110 | Mixed Integer Linear Program. The formulation can be found |
| 3111 | in [JVNC]_. |
| 3112 | |
| 3113 | There are at least two possible rewritings of this method |
| 3114 | which do not use Linear Programming: |
| 3115 | |
| 3116 | * The algorithm presented in the paper entitled "A short |
| 3117 | proof of the tree-packing theorem", by Thomas Kaiser |
| 3118 | [KaisPacking]_. |
| 3119 | |
| 3120 | * The implementation of a Matroid class and of the Matroid |
| 3121 | Union Theorem (see section 42.3 of [SchrijverCombOpt]_), |
| 3122 | applied to the cycle Matroid (see chapter 51 of |
| 3123 | [SchrijverCombOpt]_). |
| 3124 | |
| 3125 | EXAMPLES: |
| 3126 | |
| 3127 | The Petersen Graph does have a spanning tree (it is connected):: |
| 3128 | |
| 3129 | sage: g = graphs.PetersenGraph() |
| 3130 | sage: [T] = g.edge_disjoint_spanning_trees(1) # optional - GLPK, CBC |
| 3131 | sage: T.is_tree() # optional - GLPK, CBC |
| 3132 | True |
| 3133 | |
| 3134 | Though, it does not have 2 edge-disjoint trees (as it has less |
| 3135 | than `2(|V|-1)` edges):: |
| 3136 | |
| 3137 | sage: g.edge_disjoint_spanning_trees(2) # optional - GLPK, CBC |
| 3138 | Traceback (most recent call last): |
| 3139 | ... |
| 3140 | ValueError: This graph does not contain the required number of trees/arborescences ! |
| 3141 | |
| 3142 | By Edmond's theorem, a graph which is `k`-connected always has `k` edge-disjoint |
| 3143 | arborescences, regardless of the root we pick:: |
| 3144 | |
| 3145 | sage: g = digraphs.RandomDirectedGNP(30,.3) |
| 3146 | sage: k = Integer(g.edge_connectivity()) # optional - GLPK, CBC |
| 3147 | sage: arborescences = g.edge_disjoint_spanning_trees(k) # optional - GLPK, CBC |
| 3148 | sage: all([a.is_directed_acyclic() for a in arborescences]) # optional - GLPK, CBC |
| 3149 | True |
| 3150 | sage: all([a.is_connected() for a in arborescences]) # optional - GLPK, CBC |
| 3151 | True |
| 3152 | |
| 3153 | In the undirected case, we can only ensure half of it:: |
| 3154 | |
| 3155 | sage: g = graphs.RandomGNP(30,.3) |
| 3156 | sage: k = floor(Integer(g.edge_connectivity())/2) # optional - GLPK, CBC |
| 3157 | sage: trees = g.edge_disjoint_spanning_trees(k) # optional - GLPK, CBC |
| 3158 | sage: all([t.is_tree() for t in trees]) # optional - GLPK, CBC |
| 3159 | True |
| 3160 | |
| 3161 | REFERENCES: |
| 3162 | |
| 3163 | .. [JVNC] David Joyner, Minh Van Nguyen, and Nathann Cohen, |
| 3164 | Algorithmic Graph Theory, |
| 3165 | http://code.google.com/p/graph-theory-algorithms-book/ |
| 3166 | |
| 3167 | .. [KaisPacking] Thomas Kaiser |
| 3168 | A short proof of the tree-packing theorem |
| 3169 | http://arxiv.org/abs/0911.2809 |
| 3170 | |
| 3171 | .. [SchrijverCombOpt] Alexander Schrijver |
| 3172 | Combinatorial optimization: polyhedra and efficiency |
| 3173 | 2003 |
| 3174 | """ |
| 3175 | |
| 3176 | from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException |
| 3177 | p = MixedIntegerLinearProgram() |
| 3178 | p.set_objective(None) |
| 3179 | |
| 3180 | # The colors we can use |
| 3181 | colors = range(0,k) |
| 3182 | |
| 3183 | # edges[j][e] is equal to one if and only if edge e belongs to color j |
| 3184 | edges = p.new_variable(dim=2) |
| 3185 | |
| 3186 | |
| 3187 | # r_edges is a relaxed variable grater than edges. It is used to |
| 3188 | # check the presence of cycles |
| 3189 | r_edges = p.new_variable(dim=2) |
| 3190 | |
| 3191 | epsilon = 1/(10*(Integer(self.order()))) |
| 3192 | |
| 3193 | |
| 3194 | if self.is_directed(): |
| 3195 | # Does nothing ot an edge.. Useful when out of "if self.directed" |
| 3196 | S = lambda (x,y) : (x,y) |
| 3197 | |
| 3198 | if root == None: |
| 3199 | root = self.vertex_iterator().next() |
| 3200 | |
| 3201 | |
| 3202 | # An edge belongs to at most arborescence |
| 3203 | for e in self.edges(labels=False): |
| 3204 | p.add_constraint(sum([edges[j][e] for j in colors]), max=1) |
| 3205 | |
| 3206 | |
| 3207 | for j in colors: |
| 3208 | # each color class has self.order()-1 edges |
| 3209 | p.add_constraint(sum([edges[j][e] for e in self.edges(labels=None)]), min=self.order()-1) |
| 3210 | |
| 3211 | # Each vertex different from the root has indegree equals to one |
| 3212 | for v in self.vertices(): |
| 3213 | if v is not root: |
| 3214 | p.add_constraint(sum([edges[j][e] for e in self.incoming_edges(v, labels=None)]), max=1, min=1) |
| 3215 | else: |
| 3216 | p.add_constraint(sum([edges[j][e] for e in self.incoming_edges(v, labels=None)]), max=0, min=0) |
| 3217 | |
| 3218 | # r_edges is larger than edges |
| 3219 | for u,v in self.edges(labels=None): |
| 3220 | if self.has_edge(v,u): |
| 3221 | if v<u: |
| 3222 | p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][(u,v)] - edges[j][(v,u)], min=0) |
| 3223 | else: |
| 3224 | p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][(u,v)], min=0) |
| 3225 | |
| 3226 | from sage.graphs.digraph import DiGraph |
| 3227 | D = DiGraph() |
| 3228 | D.add_vertices(self.vertices()) |
| 3229 | D.set_pos(self.get_pos()) |
| 3230 | classes = [D.copy() for j in colors] |
| 3231 | |
| 3232 | else: |
| 3233 | |
| 3234 | # Sort an edge |
| 3235 | S = lambda (x,y) : (x,y) if x<y else (y,x) |
| 3236 | |
| 3237 | # An edge belongs to at most one arborescence |
| 3238 | for e in self.edges(labels=False): |
| 3239 | p.add_constraint(sum([edges[j][S(e)] for j in colors]), max=1) |
| 3240 | |
| 3241 | |
| 3242 | for j in colors: |
| 3243 | # each color class has self.order()-1 edges |
| 3244 | p.add_constraint(sum([edges[j][S(e)] for e in self.edges(labels=None)]), min=self.order()-1) |
| 3245 | |
| 3246 | # Each vertex is in the tree |
| 3247 | for v in self.vertices(): |
| 3248 | p.add_constraint(sum([edges[j][S(e)] for e in self.edges_incident(v, labels=None)]), min=1) |
| 3249 | |
| 3250 | # r_edges is larger than edges |
| 3251 | for u,v in self.edges(labels=None): |
| 3252 | p.add_constraint(r_edges[j][(u,v)] + r_edges[j][(v, u)] - edges[j][S((u,v))], min=0) |
| 3253 | |
| 3254 | from sage.graphs.graph import Graph |
| 3255 | D = Graph() |
| 3256 | D.add_vertices(self.vertices()) |
| 3257 | D.set_pos(self.get_pos()) |
| 3258 | classes = [D.copy() for j in colors] |
| 3259 | |
| 3260 | # no cycles |
| 3261 | for j in colors: |
| 3262 | for v in self: |
| 3263 | p.add_constraint(sum(r_edges[j][(u,v)] for u in self.neighbors(v)), max=1-epsilon) |
| 3264 | |
| 3265 | p.set_binary(edges) |
| 3266 | |
| 3267 | try: |
| 3268 | p.solve(**kwds) |
| 3269 | |
| 3270 | except MIPSolverException: |
| 3271 | raise ValueError("This graph does not contain the required number of trees/arborescences !") |
| 3272 | |
| 3273 | edges = p.get_values(edges) |
| 3274 | |
| 3275 | for j,g in enumerate(classes): |
| 3276 | for e in self.edges(labels=False): |
| 3277 | if edges[j][S(e)] == 1: |
| 3278 | g.add_edge(e) |
| 3279 | |
| 3280 | return classes |
| 3281 | |