Skip to content

Support multiple edges in the MILP spanning tree packing, and fix k=1 on digraphs - #42633

Open
ProfTR55 wants to merge 3 commits into
sagemath:developfrom
ProfTR55:gsoc-milp-multiedge
Open

Support multiple edges in the MILP spanning tree packing, and fix k=1 on digraphs#42633
ProfTR55 wants to merge 3 commits into
sagemath:developfrom
ProfTR55:gsoc-milp-multiedge

Conversation

@ProfTR55

@ProfTR55 ProfTR55 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📚 Description

Follow-up to #42570, which added multigraph support to the Gabow backend.
This does the same for the MILP formulation of edge_disjoint_spanning_trees,
as discussed with @dcoudert, and fixes a related bug found on the way.

Multiple edges in the MILP formulation. The formulation indexed its
variables by the pair (u, v), so parallel edges collapsed into a single
variable. Every edge now gets a distinct identifier and the variables are
indexed by the resulting arcs, an undirected edge with identifier i yielding
the arcs (u, v, i) and (v, u, i). Loops are discarded, as they cannot
belong to a spanning tree.

The subtour elimination constraints need the variable of a pair (u, v). A
tree uses at most one of the parallel arcs from u to v, so the sum of their
variables carries exactly that meaning and is used instead, in both the
Miller-Tucker-Zemlin and the Desrosiers-Langevin constraints. The input check
now rejects loops and multiple edges only for the Roskind-Tarjan algorithm.

Bug fix: k=1 on a digraph. edge_disjoint_spanning_trees short-circuits
k=1 to min_spanning_tree, which ignores the orientation of the edges. On a
digraph it therefore returned a tree that need not be an arborescence rooted at
the requested root, unlike the MILP formulation used for larger k. On
digraphs.Complete(4) the returned graph was an in-arborescence, the root
having in-degree 3. The shortcut is now used for undirected graphs only.

Validation. Beyond the doctests: complete digraphs K_2 to K_5
(regression), doubled digraphs and doubled complete graphs with known packing
sizes, random multigraphs where the MILP and the Gabow backend both return a
valid packing of size ec, undirected multigraphs, and loops being ignored.
Edge-disjointness is checked with multiplicity, that is every (u, v) is used
by at most as many trees as it has parallel copies.

📝 Checklist

  • The title is concise, informative, and self-explanatory.
  • The description explains in detail what this PR is about.
  • I have linked a relevant issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation and checked the documentation preview.

edge_disjoint_spanning_trees short-circuits k=1 to min_spanning_tree, which
ignores the orientation of the edges. On a digraph it therefore returned a
tree that need not be an arborescence rooted at the requested root, unlike
the MILP formulation used for larger k: on the complete digraph of order 4
the returned graph was an in-arborescence, with the root having in-degree 3.

Keep the shortcut for undirected graphs only; on a digraph the MILP
formulation already handles k=1 correctly.
The formulation indexed its variables by the pair (u, v), so parallel edges
collapsed into a single variable and could not be told apart. Give every edge
a distinct identifier and index the variables by the resulting arcs, an
undirected edge with identifier i yielding the arcs (u, v, i) and (v, u, i).
Loops are discarded, as they cannot belong to a spanning tree.

The subtour elimination constraints need the variable of a pair (u, v). A
tree uses at most one of the parallel arcs from u to v, so the sum of their
variables has the same meaning and is used instead, both in the
Miller-Tucker-Zemlin and in the Desrosiers-Langevin constraints.

The input check is relaxed accordingly: it now rejects loops and multiple
edges only for the Roskind-Tarjan algorithm.
Mention in the description of the MILP algorithm that loops and multiple
edges are supported, and update the example accordingly.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Documentation preview for this PR (built with commit f2f85e3; changes) is ready! 🎉
This preview will update shortly after each push to this PR.

@dcoudert dcoudert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a few comments. Otherwise the formulation looks good to me.

root = next(G.vertex_iterator())

if k == 1:
if k == 1 and not G.is_directed():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When $G$ is directed, you can use our out-branchings iterator, so T = next(G.out_branchings(root).
By default the iterator searches for spanning out-branching from root, and so will raise an error if none can be found.
So call T = next(G.out_branchings(root, spanning=False) and then check if $T$ has the right number of edges.

Only available for directed graphs; digraphs with loops and
multiple edges are supported.

* ``None`` -- use ``'Roskind-Tarjan'`` for undirected graphs and

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be updated to make Gabow default for directed graphs.

self._scream_if_not_simple()
else:
# the Gabow and MILP backends support loops and multiple edges
self._scream_if_not_simple(allow_loops=True, allow_multiple_edges=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test is useless. It will always pass.

return [DiGraph(E) if G.is_directed() else Graph(E)]
return [Graph(E)]

D = G if G.is_directed() else DiGraph(G)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can avoid the creation of D. It is now only used for the BFS strengthening constraints and I think that we can use G directly.

@dcoudert dcoudert added c: graph theory gsoc: 2026 Tag for GSoC 2026 issues/PRs labels Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants