| """Functions for computing and verifying matchings in a graph.""" |
|
|
| 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"): |
| """Compute a minimum-weight maximum-cardinality matching of `G`. |
| |
| The minimum-weight maximum-cardinality matching is the matching |
| that has the minimum weight among all maximum-cardinality matchings. |
| |
| 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. |
| |
| 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) |
|
|