Repository navigation
Fix false negative in minimum and maximum spanning arborescence (fixes #8964) - #8979
Roshanraj2580 wants to merge 2 commits into
Conversation
…orkx#8964) Signed-off-by: Roshanraj2580 <roshanraj2580@gmail.com>
42cb7ff to
260016b
Compare
dschult
left a comment
There was a problem hiding this comment.
Thanks for this!
I have a few questions below. I'd like to understand the formulas a little better.
| if not G.edges: | ||
| return maximum_branching(G, attr, default, preserve_attrs, partition) | ||
|
|
There was a problem hiding this comment.
I don't think I understand this. Is it checking for the case of no edges? If so, how can we be looking for a maximum branching?
There was a problem hiding this comment.
When G has no edges, min_weight is INF and max_weight is -INF, which causes the subsequent weight arithmetic (max_weight - min_weight) to produce NaN and crash.
For an edgeless graph, the only branching is the empty graph (0 edges), which is trivially both minimal and maximal. Calling maximum_branching(G, ...) was just a convenient way to get an empty graph of the appropriate type/nodes/attrs.
If preferred, I can change this to directly return G.copy() (or type(G)() with nodes added) to make it clearer that it's just returning an empty branching.
There was a problem hiding this comment.
I'd prefer returning G.copy(). Otherwise each time I read this part of the code I will need to go look up what maximum_branching does with an empty_graph. :}
Thanks!
| # over any non-spanning branching (<= n - 2 edges). The shift must exceed the maximum | ||
| # possible weight difference between a spanning and a non-spanning branching, | ||
| # bounded by (n - 1) * max(max_weight, 0) - (n - 2) * min(min_weight, 0). |
There was a problem hiding this comment.
Can you explain why this formula works?
Where do these values come from in terms of a spanning and a non-spanning branching?
There was a problem hiding this comment.
In minimal_branching, we transform weights as maximum_branching.
For any branching
A spanning arborescence maximum_branching, we need
Since
To bound the maximum possible gap
- In the worst case,
$w(T)$ could sum$n - 1$ edges of weight$\max(w_{\max}, 0)$ . -
$w(B)$ could sum at most$n - 2$ edges of weight$\min(w_{\min}, 0)$ (if negative).
So the gap is at most max_weight + 1 + (max_weight - min_weight) term keeps the legacy shift so transformed weights also stay positive).
Alternatively, an even simpler upper bound on
There was a problem hiding this comment.
Thanks for this description! Very helpful.
In the end, I think the simpler (though probably bigger than needed) upper bound is a better choice -- easier to read anyway... Is it also faster to compute? Would it be even better to use
2 * n * (max_weight - min_weight) + 1 (and do we need to max when negative and min when positive? We can be very generous with our upper bound estimate so long as it gets bigger... any way to help that simplify the expression and logic here? )
| shift = max( | ||
| 1 - min_weight + (max_weight - min_weight), | ||
| (n - 2) * max(max_weight, 0) - (n - 1) * min(min_weight, 0) + 1, | ||
| ) |
There was a problem hiding this comment.
Can you explain why this formula is correct?
There was a problem hiding this comment.
Same reasoning as in minimal_branching, but in reverse: here we shift weights maximum_branching.
For a spanning arborescence
Since
The worst-case advantage
-
$B$ has at most$n - 2$ edges, each at most$\max(w_{\max}, 0)$ . -
$T$ has$n - 1$ edges, each at least$\min(w_{\min}, 0)$ (which can be negative).
So
Setting maximum_branching never drops an edge needed to span the graph. The first term 1 - min_weight + (max_weight - min_weight) is the existing legacy shift to ensure positivity during Edmonds' contraction.
|
A cardinality-first alternative to the global shift is available here: woahwhattheheck/networkx@main...fix/arborescence-20261113 . It retains #8964 and Roshanraj2580's work here as the existing carrier. Spanning mode first maximizes edge count within Edmonds' algorithm, then optimizes weight. It avoids modifying/restoring the caller's weights. Finite built-in floats and integers use a common exact binary scale for contraction; selected output weights come from the originals. This covers the reported weight-31 tree, finite weights near 1e308 and cancellation near 1e16. Ordinary maximum branching still optimizes weight without a cardinality preference; iterator queue priorities and returned-weight summation retain their existing arithmetic. The complete branching module passed 45 native and 45 loopback cases with these changes, including the reported graph, input preservation and extreme-weight regressions. Formatting and whitespace checks passed. This is offered on the existing discussion rather than opening a duplicate PR. |
|
Thanks @woahwhattheheck, this cardinality-first formulation is much cleaner and avoids the weight mutation and precision edge cases with extreme floats entirely. I've pulled your commit into this PR branch ( |
Summary
Fixes #8964.
In
networkx.minimum_spanning_arborescence(via minimal_branching) andnetworkx.maximum_spanning_arborescence, edge weights were shifted by:C = 2 * max_weight - min_weight + 1C = max_weight - 2 * min_weight + 1Under this constant shift, maximum_branching optimizes the shifted weights$\sum_{e \in B} (C - w_e)$ or $\sum_{e \in B} (w_e + C)$ . However, if dropping an edge to yield a non-spanning branching saves more in original weight than $???$ , maximum_branching selects a non-spanning branching over the spanning arborescence. When this occurs,
is_arborescence(B)returnsFalse, causing NetworkX to falsely raise:NetworkXException: No minimum/maximum spanning arborescence in Geven when valid spanning arborescences exist.Solution
The weight shift must guarantee that any spanning arborescence ($???-1$ edges) strictly outscores any non-spanning branching ($\le n - 2$ edges).
We set:
We set:
Also handled empty edge graphs cleanly.
Testing
minimum_spanning_arborescence/maximum_spanning_arborescenceraise "No ... spanning arborescence in G" on graphs that have one (regression since 3.2) #8964 and verifying both minimum and maximum spanning arborescence algorithms.