| | """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) |
| |
|