|
"""Functions for computing and verifying matchings in a graph.""" |
|
from collections import Counter |
|
from itertools import combinations, repeat |
|
|
|
import networkx as nx |
|
from networkx.utils import not_implemented_for |
|
|
|
__all__ = [ |
|
"is_matching", |
|
"is_maximal_matching", |
|
"is_perfect_matching", |
|
"max_weight_matching", |
|
"min_weight_matching", |
|
"maximal_matching", |
|
] |
|
|
|
|
|
@not_implemented_for("multigraph") |
|
@not_implemented_for("directed") |
|
@nx._dispatchable |
|
def maximal_matching(G): |
|
r"""Find a maximal matching in the graph. |
|
|
|
A matching is a subset of edges in which no node occurs more than once. |
|
A maximal matching cannot add more edges and still be a matching. |
|
|
|
Parameters |
|
---------- |
|
G : NetworkX graph |
|
Undirected graph |
|
|
|
Returns |
|
------- |
|
matching : set |
|
A maximal matching of the graph. |
|
|
|
Examples |
|
-------- |
|
>>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)]) |
|
>>> sorted(nx.maximal_matching(G)) |
|
[(1, 2), (3, 5)] |
|
|
|
Notes |
|
----- |
|
The algorithm greedily selects a maximal matching M of the graph G |
|
(i.e. no superset of M exists). It runs in $O(|E|)$ time. |
|
""" |
|
matching = set() |
|
nodes = set() |
|
for edge in G.edges(): |
|
|
|
|
|
u, v = edge |
|
if u not in nodes and v not in nodes and u != v: |
|
matching.add(edge) |
|
nodes.update(edge) |
|
return matching |
|
|
|
|
|
def matching_dict_to_set(matching): |
|
"""Converts matching dict format to matching set format |
|
|
|
Converts a dictionary representing a matching (as returned by |
|
:func:`max_weight_matching`) to a set representing a matching (as |
|
returned by :func:`maximal_matching`). |
|
|
|
In the definition of maximal matching adopted by NetworkX, |
|
self-loops are not allowed, so the provided dictionary is expected |
|
to never have any mapping from a key to itself. However, the |
|
dictionary is expected to have mirrored key/value pairs, for |
|
example, key ``u`` with value ``v`` and key ``v`` with value ``u``. |
|
|
|
""" |
|
edges = set() |
|
for edge in matching.items(): |
|
u, v = edge |
|
if (v, u) in edges or edge in edges: |
|
continue |
|
if u == v: |
|
raise nx.NetworkXError(f"Selfloops cannot appear in matchings {edge}") |
|
edges.add(edge) |
|
return edges |
|
|
|
|
|
@nx._dispatchable |
|
def is_matching(G, matching): |
|
"""Return True if ``matching`` is a valid matching of ``G`` |
|
|
|
A *matching* in a graph is a set of edges in which no two distinct |
|
edges share a common endpoint. Each node is incident to at most one |
|
edge in the matching. The edges are said to be independent. |
|
|
|
Parameters |
|
---------- |
|
G : NetworkX graph |
|
|
|
matching : dict or set |
|
A dictionary or set representing a matching. If a dictionary, it |
|
must have ``matching[u] == v`` and ``matching[v] == u`` for each |
|
edge ``(u, v)`` in the matching. If a set, it must have elements |
|
of the form ``(u, v)``, where ``(u, v)`` is an edge in the |
|
matching. |
|
|
|
Returns |
|
------- |
|
bool |
|
Whether the given set or dictionary represents a valid matching |
|
in the graph. |
|
|
|
Raises |
|
------ |
|
NetworkXError |
|
If the proposed matching has an edge to a node not in G. |
|
Or if the matching is not a collection of 2-tuple edges. |
|
|
|
Examples |
|
-------- |
|
>>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)]) |
|
>>> nx.is_maximal_matching(G, {1: 3, 2: 4}) # using dict to represent matching |
|
True |
|
|
|
>>> nx.is_matching(G, {(1, 3), (2, 4)}) # using set to represent matching |
|
True |
|
|
|
""" |
|
if isinstance(matching, dict): |
|
matching = matching_dict_to_set(matching) |
|
|
|
nodes = set() |
|
for edge in matching: |
|
if len(edge) != 2: |
|
raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}") |
|
u, v = edge |
|
if u not in G or v not in G: |
|
raise nx.NetworkXError(f"matching contains edge {edge} with node not in G") |
|
if u == v: |
|
return False |
|
if not G.has_edge(u, v): |
|
return False |
|
if u in nodes or v in nodes: |
|
return False |
|
nodes.update(edge) |
|
return True |
|
|
|
|
|
@nx._dispatchable |
|
def is_maximal_matching(G, matching): |
|
"""Return True if ``matching`` is a maximal matching of ``G`` |
|
|
|
A *maximal matching* in a graph is a matching in which adding any |
|
edge would cause the set to no longer be a valid matching. |
|
|
|
Parameters |
|
---------- |
|
G : NetworkX graph |
|
|
|
matching : dict or set |
|
A dictionary or set representing a matching. If a dictionary, it |
|
must have ``matching[u] == v`` and ``matching[v] == u`` for each |
|
edge ``(u, v)`` in the matching. If a set, it must have elements |
|
of the form ``(u, v)``, where ``(u, v)`` is an edge in the |
|
matching. |
|
|
|
Returns |
|
------- |
|
bool |
|
Whether the given set or dictionary represents a valid maximal |
|
matching in the graph. |
|
|
|
Examples |
|
-------- |
|
>>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (3, 5)]) |
|
>>> nx.is_maximal_matching(G, {(1, 2), (3, 4)}) |
|
True |
|
|
|
""" |
|
if isinstance(matching, dict): |
|
matching = matching_dict_to_set(matching) |
|
|
|
edges = set() |
|
nodes = set() |
|
for edge in matching: |
|
if len(edge) != 2: |
|
raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}") |
|
u, v = edge |
|
if u not in G or v not in G: |
|
raise nx.NetworkXError(f"matching contains edge {edge} with node not in G") |
|
if u == v: |
|
return False |
|
if not G.has_edge(u, v): |
|
return False |
|
if u in nodes or v in nodes: |
|
return False |
|
nodes.update(edge) |
|
edges.add(edge) |
|
edges.add((v, u)) |
|
|
|
|
|
|
|
for u, v in G.edges: |
|
if (u, v) not in edges: |
|
|
|
if u not in nodes and v not in nodes and u != v: |
|
return False |
|
return True |
|
|
|
|
|
@nx._dispatchable |
|
def is_perfect_matching(G, matching): |
|
"""Return True if ``matching`` is a perfect matching for ``G`` |
|
|
|
A *perfect matching* in a graph is a matching in which exactly one edge |
|
is incident upon each vertex. |
|
|
|
Parameters |
|
---------- |
|
G : NetworkX graph |
|
|
|
matching : dict or set |
|
A dictionary or set representing a matching. If a dictionary, it |
|
must have ``matching[u] == v`` and ``matching[v] == u`` for each |
|
edge ``(u, v)`` in the matching. If a set, it must have elements |
|
of the form ``(u, v)``, where ``(u, v)`` is an edge in the |
|
matching. |
|
|
|
Returns |
|
------- |
|
bool |
|
Whether the given set or dictionary represents a valid perfect |
|
matching in the graph. |
|
|
|
Examples |
|
-------- |
|
>>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5), (4, 6)]) |
|
>>> my_match = {1: 2, 3: 5, 4: 6} |
|
>>> nx.is_perfect_matching(G, my_match) |
|
True |
|
|
|
""" |
|
if isinstance(matching, dict): |
|
matching = matching_dict_to_set(matching) |
|
|
|
nodes = set() |
|
for edge in matching: |
|
if len(edge) != 2: |
|
raise nx.NetworkXError(f"matching has non-2-tuple edge {edge}") |
|
u, v = edge |
|
if u not in G or v not in G: |
|
raise nx.NetworkXError(f"matching contains edge {edge} with node not in G") |
|
if u == v: |
|
return False |
|
if not G.has_edge(u, v): |
|
return False |
|
if u in nodes or v in nodes: |
|
return False |
|
nodes.update(edge) |
|
return len(nodes) == len(G) |
|
|
|
|
|
@not_implemented_for("multigraph") |
|
@not_implemented_for("directed") |
|
@nx._dispatchable(edge_attrs="weight") |
|
def min_weight_matching(G, weight="weight"): |
|
"""Computing a minimum-weight maximal matching of G. |
|
|
|
Use the maximum-weight algorithm with edge weights subtracted |
|
from the maximum weight of all edges. |
|
|
|
A matching is a subset of edges in which no node occurs more than once. |
|
The weight of a matching is the sum of the weights of its edges. |
|
A maximal matching cannot add more edges and still be a matching. |
|
The cardinality of a matching is the number of matched edges. |
|
|
|
This method replaces the edge weights with 1 plus the maximum edge weight |
|
minus the original edge weight. |
|
|
|
new_weight = (max_weight + 1) - edge_weight |
|
|
|
then runs :func:`max_weight_matching` with the new weights. |
|
The max weight matching with these new weights corresponds |
|
to the min weight matching using the original weights. |
|
Adding 1 to the max edge weight keeps all edge weights positive |
|
and as integers if they started as integers. |
|
|
|
You might worry that adding 1 to each weight would make the algorithm |
|
favor matchings with more edges. But we use the parameter |
|
`maxcardinality=True` in `max_weight_matching` to ensure that the |
|
number of edges in the competing matchings are the same and thus |
|
the optimum does not change due to changes in the number of edges. |
|
|
|
Read the documentation of `max_weight_matching` for more information. |
|
|
|
Parameters |
|
---------- |
|
G : NetworkX graph |
|
Undirected graph |
|
|
|
weight: string, optional (default='weight') |
|
Edge data key corresponding to the edge weight. |
|
If key not found, uses 1 as weight. |
|
|
|
Returns |
|
------- |
|
matching : set |
|
A minimal weight matching of the graph. |
|
|
|
See Also |
|
-------- |
|
max_weight_matching |
|
""" |
|
if len(G.edges) == 0: |
|
return max_weight_matching(G, maxcardinality=True, weight=weight) |
|
G_edges = G.edges(data=weight, default=1) |
|
max_weight = 1 + max(w for _, _, w in G_edges) |
|
InvG = nx.Graph() |
|
edges = ((u, v, max_weight - w) for u, v, w in G_edges) |
|
InvG.add_weighted_edges_from(edges, weight=weight) |
|
return max_weight_matching(InvG, maxcardinality=True, weight=weight) |
|
|
|
|
|
@not_implemented_for("multigraph") |
|
@not_implemented_for("directed") |
|
@nx._dispatchable(edge_attrs="weight") |
|
def max_weight_matching(G, maxcardinality=False, weight="weight"): |
|
"""Compute a maximum-weighted matching of G. |
|
|
|
A matching is a subset of edges in which no node occurs more than once. |
|
The weight of a matching is the sum of the weights of its edges. |
|
A maximal matching cannot add more edges and still be a matching. |
|
The cardinality of a matching is the number of matched edges. |
|
|
|
Parameters |
|
---------- |
|
G : NetworkX graph |
|
Undirected graph |
|
|
|
maxcardinality: bool, optional (default=False) |
|
If maxcardinality is True, compute the maximum-cardinality matching |
|
with maximum weight among all maximum-cardinality matchings. |
|
|
|
weight: string, optional (default='weight') |
|
Edge data key corresponding to the edge weight. |
|
If key not found, uses 1 as weight. |
|
|
|
|
|
Returns |
|
------- |
|
matching : set |
|
A maximal matching of the graph. |
|
|
|
Examples |
|
-------- |
|
>>> G = nx.Graph() |
|
>>> edges = [(1, 2, 6), (1, 3, 2), (2, 3, 1), (2, 4, 7), (3, 5, 9), (4, 5, 3)] |
|
>>> G.add_weighted_edges_from(edges) |
|
>>> sorted(nx.max_weight_matching(G)) |
|
[(2, 4), (5, 3)] |
|
|
|
Notes |
|
----- |
|
If G has edges with weight attributes the edge data are used as |
|
weight values else the weights are assumed to be 1. |
|
|
|
This function takes time O(number_of_nodes ** 3). |
|
|
|
If all edge weights are integers, the algorithm uses only integer |
|
computations. If floating point weights are used, the algorithm |
|
could return a slightly suboptimal matching due to numeric |
|
precision errors. |
|
|
|
This method is based on the "blossom" method for finding augmenting |
|
paths and the "primal-dual" method for finding a matching of maximum |
|
weight, both methods invented by Jack Edmonds [1]_. |
|
|
|
Bipartite graphs can also be matched using the functions present in |
|
:mod:`networkx.algorithms.bipartite.matching`. |
|
|
|
References |
|
---------- |
|
.. [1] "Efficient Algorithms for Finding Maximum Matching in Graphs", |
|
Zvi Galil, ACM Computing Surveys, 1986. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NoNode: |
|
"""Dummy value which is different from any node.""" |
|
|
|
class Blossom: |
|
"""Representation of a non-trivial blossom or sub-blossom.""" |
|
|
|
__slots__ = ["childs", "edges", "mybestedges"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def leaves(self): |
|
stack = [*self.childs] |
|
while stack: |
|
t = stack.pop() |
|
if isinstance(t, Blossom): |
|
stack.extend(t.childs) |
|
else: |
|
yield t |
|
|
|
|
|
gnodes = list(G) |
|
if not gnodes: |
|
return set() |
|
|
|
|
|
maxweight = 0 |
|
allinteger = True |
|
for i, j, d in G.edges(data=True): |
|
wt = d.get(weight, 1) |
|
if i != j and wt > maxweight: |
|
maxweight = wt |
|
allinteger = allinteger and (str(type(wt)).split("'")[1] in ("int", "long")) |
|
|
|
|
|
|
|
|
|
mate = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
label = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
labeledge = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
inblossom = dict(zip(gnodes, gnodes)) |
|
|
|
|
|
|
|
|
|
blossomparent = dict(zip(gnodes, repeat(None))) |
|
|
|
|
|
|
|
blossombase = dict(zip(gnodes, gnodes)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bestedge = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
dualvar = dict(zip(gnodes, repeat(maxweight))) |
|
|
|
|
|
|
|
|
|
blossomdual = {} |
|
|
|
|
|
|
|
|
|
allowedge = {} |
|
|
|
|
|
queue = [] |
|
|
|
|
|
def slack(v, w): |
|
return dualvar[v] + dualvar[w] - 2 * G[v][w].get(weight, 1) |
|
|
|
|
|
|
|
def assignLabel(w, t, v): |
|
b = inblossom[w] |
|
assert label.get(w) is None and label.get(b) is None |
|
label[w] = label[b] = t |
|
if v is not None: |
|
labeledge[w] = labeledge[b] = (v, w) |
|
else: |
|
labeledge[w] = labeledge[b] = None |
|
bestedge[w] = bestedge[b] = None |
|
if t == 1: |
|
|
|
if isinstance(b, Blossom): |
|
queue.extend(b.leaves()) |
|
else: |
|
queue.append(b) |
|
elif t == 2: |
|
|
|
|
|
|
|
base = blossombase[b] |
|
assignLabel(mate[base], 1, base) |
|
|
|
|
|
|
|
|
|
def scanBlossom(v, w): |
|
|
|
path = [] |
|
base = NoNode |
|
while v is not NoNode: |
|
|
|
b = inblossom[v] |
|
if label[b] & 4: |
|
base = blossombase[b] |
|
break |
|
assert label[b] == 1 |
|
path.append(b) |
|
label[b] = 5 |
|
|
|
if labeledge[b] is None: |
|
|
|
assert blossombase[b] not in mate |
|
v = NoNode |
|
else: |
|
assert labeledge[b][0] == mate[blossombase[b]] |
|
v = labeledge[b][0] |
|
b = inblossom[v] |
|
assert label[b] == 2 |
|
|
|
v = labeledge[b][0] |
|
|
|
if w is not NoNode: |
|
v, w = w, v |
|
|
|
for b in path: |
|
label[b] = 1 |
|
|
|
return base |
|
|
|
|
|
|
|
|
|
def addBlossom(base, v, w): |
|
bb = inblossom[base] |
|
bv = inblossom[v] |
|
bw = inblossom[w] |
|
|
|
b = Blossom() |
|
blossombase[b] = base |
|
blossomparent[b] = None |
|
blossomparent[bb] = b |
|
|
|
b.childs = path = [] |
|
b.edges = edgs = [(v, w)] |
|
|
|
while bv != bb: |
|
|
|
blossomparent[bv] = b |
|
path.append(bv) |
|
edgs.append(labeledge[bv]) |
|
assert label[bv] == 2 or ( |
|
label[bv] == 1 and labeledge[bv][0] == mate[blossombase[bv]] |
|
) |
|
|
|
v = labeledge[bv][0] |
|
bv = inblossom[v] |
|
|
|
path.append(bb) |
|
path.reverse() |
|
edgs.reverse() |
|
|
|
while bw != bb: |
|
|
|
blossomparent[bw] = b |
|
path.append(bw) |
|
edgs.append((labeledge[bw][1], labeledge[bw][0])) |
|
assert label[bw] == 2 or ( |
|
label[bw] == 1 and labeledge[bw][0] == mate[blossombase[bw]] |
|
) |
|
|
|
w = labeledge[bw][0] |
|
bw = inblossom[w] |
|
|
|
assert label[bb] == 1 |
|
label[b] = 1 |
|
labeledge[b] = labeledge[bb] |
|
|
|
blossomdual[b] = 0 |
|
|
|
for v in b.leaves(): |
|
if label[inblossom[v]] == 2: |
|
|
|
|
|
queue.append(v) |
|
inblossom[v] = b |
|
|
|
bestedgeto = {} |
|
for bv in path: |
|
if isinstance(bv, Blossom): |
|
if bv.mybestedges is not None: |
|
|
|
nblist = bv.mybestedges |
|
|
|
bv.mybestedges = None |
|
else: |
|
|
|
|
|
nblist = [ |
|
(v, w) for v in bv.leaves() for w in G.neighbors(v) if v != w |
|
] |
|
else: |
|
nblist = [(bv, w) for w in G.neighbors(bv) if bv != w] |
|
for k in nblist: |
|
(i, j) = k |
|
if inblossom[j] == b: |
|
i, j = j, i |
|
bj = inblossom[j] |
|
if ( |
|
bj != b |
|
and label.get(bj) == 1 |
|
and ((bj not in bestedgeto) or slack(i, j) < slack(*bestedgeto[bj])) |
|
): |
|
bestedgeto[bj] = k |
|
|
|
bestedge[bv] = None |
|
b.mybestedges = list(bestedgeto.values()) |
|
|
|
mybestedge = None |
|
bestedge[b] = None |
|
for k in b.mybestedges: |
|
kslack = slack(*k) |
|
if mybestedge is None or kslack < mybestslack: |
|
mybestedge = k |
|
mybestslack = kslack |
|
bestedge[b] = mybestedge |
|
|
|
|
|
def expandBlossom(b, endstage): |
|
|
|
|
|
|
|
|
|
|
|
def _recurse(b, endstage): |
|
|
|
for s in b.childs: |
|
blossomparent[s] = None |
|
if isinstance(s, Blossom): |
|
if endstage and blossomdual[s] == 0: |
|
|
|
yield s |
|
else: |
|
for v in s.leaves(): |
|
inblossom[v] = s |
|
else: |
|
inblossom[s] = s |
|
|
|
|
|
if (not endstage) and label.get(b) == 2: |
|
|
|
|
|
|
|
|
|
|
|
entrychild = inblossom[labeledge[b][1]] |
|
|
|
j = b.childs.index(entrychild) |
|
if j & 1: |
|
|
|
j -= len(b.childs) |
|
jstep = 1 |
|
else: |
|
|
|
jstep = -1 |
|
|
|
v, w = labeledge[b] |
|
while j != 0: |
|
|
|
if jstep == 1: |
|
p, q = b.edges[j] |
|
else: |
|
q, p = b.edges[j - 1] |
|
label[w] = None |
|
label[q] = None |
|
assignLabel(w, 2, v) |
|
|
|
allowedge[(p, q)] = allowedge[(q, p)] = True |
|
j += jstep |
|
if jstep == 1: |
|
v, w = b.edges[j] |
|
else: |
|
w, v = b.edges[j - 1] |
|
|
|
allowedge[(v, w)] = allowedge[(w, v)] = True |
|
j += jstep |
|
|
|
|
|
bw = b.childs[j] |
|
label[w] = label[bw] = 2 |
|
labeledge[w] = labeledge[bw] = (v, w) |
|
bestedge[bw] = None |
|
|
|
j += jstep |
|
while b.childs[j] != entrychild: |
|
|
|
|
|
|
|
bv = b.childs[j] |
|
if label.get(bv) == 1: |
|
|
|
|
|
j += jstep |
|
continue |
|
if isinstance(bv, Blossom): |
|
for v in bv.leaves(): |
|
if label.get(v): |
|
break |
|
else: |
|
v = bv |
|
|
|
|
|
if label.get(v): |
|
assert label[v] == 2 |
|
assert inblossom[v] == bv |
|
label[v] = None |
|
label[mate[blossombase[bv]]] = None |
|
assignLabel(v, 2, labeledge[v][0]) |
|
j += jstep |
|
|
|
label.pop(b, None) |
|
labeledge.pop(b, None) |
|
bestedge.pop(b, None) |
|
del blossomparent[b] |
|
del blossombase[b] |
|
del blossomdual[b] |
|
|
|
|
|
|
|
|
|
|
|
|
|
stack = [_recurse(b, endstage)] |
|
while stack: |
|
top = stack[-1] |
|
for s in top: |
|
stack.append(_recurse(s, endstage)) |
|
break |
|
else: |
|
stack.pop() |
|
|
|
|
|
|
|
|
|
def augmentBlossom(b, v): |
|
|
|
|
|
|
|
|
|
|
|
def _recurse(b, v): |
|
|
|
|
|
t = v |
|
while blossomparent[t] != b: |
|
t = blossomparent[t] |
|
|
|
if isinstance(t, Blossom): |
|
yield (t, v) |
|
|
|
i = j = b.childs.index(t) |
|
if i & 1: |
|
|
|
j -= len(b.childs) |
|
jstep = 1 |
|
else: |
|
|
|
jstep = -1 |
|
|
|
while j != 0: |
|
|
|
j += jstep |
|
t = b.childs[j] |
|
if jstep == 1: |
|
w, x = b.edges[j] |
|
else: |
|
x, w = b.edges[j - 1] |
|
if isinstance(t, Blossom): |
|
yield (t, w) |
|
|
|
j += jstep |
|
t = b.childs[j] |
|
if isinstance(t, Blossom): |
|
yield (t, x) |
|
|
|
mate[w] = x |
|
mate[x] = w |
|
|
|
b.childs = b.childs[i:] + b.childs[:i] |
|
b.edges = b.edges[i:] + b.edges[:i] |
|
blossombase[b] = blossombase[b.childs[0]] |
|
assert blossombase[b] == v |
|
|
|
|
|
|
|
|
|
|
|
|
|
stack = [_recurse(b, v)] |
|
while stack: |
|
top = stack[-1] |
|
for args in top: |
|
stack.append(_recurse(*args)) |
|
break |
|
else: |
|
stack.pop() |
|
|
|
|
|
|
|
def augmentMatching(v, w): |
|
for s, j in ((v, w), (w, v)): |
|
|
|
|
|
|
|
while 1: |
|
bs = inblossom[s] |
|
assert label[bs] == 1 |
|
assert (labeledge[bs] is None and blossombase[bs] not in mate) or ( |
|
labeledge[bs][0] == mate[blossombase[bs]] |
|
) |
|
|
|
if isinstance(bs, Blossom): |
|
augmentBlossom(bs, s) |
|
|
|
mate[s] = j |
|
|
|
if labeledge[bs] is None: |
|
|
|
break |
|
t = labeledge[bs][0] |
|
bt = inblossom[t] |
|
assert label[bt] == 2 |
|
|
|
s, j = labeledge[bt] |
|
|
|
assert blossombase[bt] == t |
|
if isinstance(bt, Blossom): |
|
augmentBlossom(bt, j) |
|
|
|
mate[j] = s |
|
|
|
|
|
def verifyOptimum(): |
|
if maxcardinality: |
|
|
|
|
|
vdualoffset = max(0, -min(dualvar.values())) |
|
else: |
|
vdualoffset = 0 |
|
|
|
assert min(dualvar.values()) + vdualoffset >= 0 |
|
assert len(blossomdual) == 0 or min(blossomdual.values()) >= 0 |
|
|
|
|
|
for i, j, d in G.edges(data=True): |
|
wt = d.get(weight, 1) |
|
if i == j: |
|
continue |
|
s = dualvar[i] + dualvar[j] - 2 * wt |
|
iblossoms = [i] |
|
jblossoms = [j] |
|
while blossomparent[iblossoms[-1]] is not None: |
|
iblossoms.append(blossomparent[iblossoms[-1]]) |
|
while blossomparent[jblossoms[-1]] is not None: |
|
jblossoms.append(blossomparent[jblossoms[-1]]) |
|
iblossoms.reverse() |
|
jblossoms.reverse() |
|
for bi, bj in zip(iblossoms, jblossoms): |
|
if bi != bj: |
|
break |
|
s += 2 * blossomdual[bi] |
|
assert s >= 0 |
|
if mate.get(i) == j or mate.get(j) == i: |
|
assert mate[i] == j and mate[j] == i |
|
assert s == 0 |
|
|
|
for v in gnodes: |
|
assert (v in mate) or dualvar[v] + vdualoffset == 0 |
|
|
|
for b in blossomdual: |
|
if blossomdual[b] > 0: |
|
assert len(b.edges) % 2 == 1 |
|
for i, j in b.edges[1::2]: |
|
assert mate[i] == j and mate[j] == i |
|
|
|
|
|
|
|
while 1: |
|
|
|
|
|
|
|
|
|
|
|
label.clear() |
|
labeledge.clear() |
|
|
|
|
|
bestedge.clear() |
|
for b in blossomdual: |
|
b.mybestedges = None |
|
|
|
|
|
|
|
allowedge.clear() |
|
|
|
|
|
queue[:] = [] |
|
|
|
|
|
for v in gnodes: |
|
if (v not in mate) and label.get(inblossom[v]) is None: |
|
assignLabel(v, 1, None) |
|
|
|
|
|
augmented = 0 |
|
while 1: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while queue and not augmented: |
|
|
|
v = queue.pop() |
|
assert label[inblossom[v]] == 1 |
|
|
|
|
|
for w in G.neighbors(v): |
|
if w == v: |
|
continue |
|
|
|
bv = inblossom[v] |
|
bw = inblossom[w] |
|
if bv == bw: |
|
|
|
continue |
|
if (v, w) not in allowedge: |
|
kslack = slack(v, w) |
|
if kslack <= 0: |
|
|
|
allowedge[(v, w)] = allowedge[(w, v)] = True |
|
if (v, w) in allowedge: |
|
if label.get(bw) is None: |
|
|
|
|
|
assignLabel(w, 2, v) |
|
elif label.get(bw) == 1: |
|
|
|
|
|
|
|
base = scanBlossom(v, w) |
|
if base is not NoNode: |
|
|
|
|
|
addBlossom(base, v, w) |
|
else: |
|
|
|
|
|
augmentMatching(v, w) |
|
augmented = 1 |
|
break |
|
elif label.get(w) is None: |
|
|
|
|
|
|
|
|
|
assert label[bw] == 2 |
|
label[w] = 2 |
|
labeledge[w] = (v, w) |
|
elif label.get(bw) == 1: |
|
|
|
|
|
if bestedge.get(bv) is None or kslack < slack(*bestedge[bv]): |
|
bestedge[bv] = (v, w) |
|
elif label.get(w) is None: |
|
|
|
|
|
|
|
if bestedge.get(w) is None or kslack < slack(*bestedge[w]): |
|
bestedge[w] = (v, w) |
|
|
|
if augmented: |
|
break |
|
|
|
|
|
|
|
|
|
|
|
deltatype = -1 |
|
delta = deltaedge = deltablossom = None |
|
|
|
|
|
if not maxcardinality: |
|
deltatype = 1 |
|
delta = min(dualvar.values()) |
|
|
|
|
|
|
|
for v in G.nodes(): |
|
if label.get(inblossom[v]) is None and bestedge.get(v) is not None: |
|
d = slack(*bestedge[v]) |
|
if deltatype == -1 or d < delta: |
|
delta = d |
|
deltatype = 2 |
|
deltaedge = bestedge[v] |
|
|
|
|
|
|
|
for b in blossomparent: |
|
if ( |
|
blossomparent[b] is None |
|
and label.get(b) == 1 |
|
and bestedge.get(b) is not None |
|
): |
|
kslack = slack(*bestedge[b]) |
|
if allinteger: |
|
assert (kslack % 2) == 0 |
|
d = kslack // 2 |
|
else: |
|
d = kslack / 2.0 |
|
if deltatype == -1 or d < delta: |
|
delta = d |
|
deltatype = 3 |
|
deltaedge = bestedge[b] |
|
|
|
|
|
for b in blossomdual: |
|
if ( |
|
blossomparent[b] is None |
|
and label.get(b) == 2 |
|
and (deltatype == -1 or blossomdual[b] < delta) |
|
): |
|
delta = blossomdual[b] |
|
deltatype = 4 |
|
deltablossom = b |
|
|
|
if deltatype == -1: |
|
|
|
|
|
|
|
assert maxcardinality |
|
deltatype = 1 |
|
delta = max(0, min(dualvar.values())) |
|
|
|
|
|
for v in gnodes: |
|
if label.get(inblossom[v]) == 1: |
|
|
|
dualvar[v] -= delta |
|
elif label.get(inblossom[v]) == 2: |
|
|
|
dualvar[v] += delta |
|
for b in blossomdual: |
|
if blossomparent[b] is None: |
|
if label.get(b) == 1: |
|
|
|
blossomdual[b] += delta |
|
elif label.get(b) == 2: |
|
|
|
blossomdual[b] -= delta |
|
|
|
|
|
if deltatype == 1: |
|
|
|
break |
|
elif deltatype == 2: |
|
|
|
(v, w) = deltaedge |
|
assert label[inblossom[v]] == 1 |
|
allowedge[(v, w)] = allowedge[(w, v)] = True |
|
queue.append(v) |
|
elif deltatype == 3: |
|
|
|
(v, w) = deltaedge |
|
allowedge[(v, w)] = allowedge[(w, v)] = True |
|
assert label[inblossom[v]] == 1 |
|
queue.append(v) |
|
elif deltatype == 4: |
|
|
|
expandBlossom(deltablossom, False) |
|
|
|
|
|
|
|
|
|
for v in mate: |
|
assert mate[mate[v]] == v |
|
|
|
|
|
if not augmented: |
|
break |
|
|
|
|
|
for b in list(blossomdual.keys()): |
|
if b not in blossomdual: |
|
continue |
|
if blossomparent[b] is None and label.get(b) == 1 and blossomdual[b] == 0: |
|
expandBlossom(b, True) |
|
|
|
|
|
if allinteger: |
|
verifyOptimum() |
|
|
|
return matching_dict_to_set(mate) |
|
|