Home Manual Reference Source

src/core/blossom/blossom.js

import assert from 'assert';
import min from './min.js';
import rotate from './rotate.js';
import verifyOptimum from './verifyOptimum.js';
import checkDelta2 from './checkDelta2.js';
import checkDelta3 from './checkDelta3.js';
import statistics from './statistics.js';
import endpoints from './endpoints.js';
import neighbours from './neighbours.js';
import blossomLeaves from './blossomLeaves.js';
import blossomEdges from './blossomEdges.js';

// Adapted from http://jorisvr.nl/maximummatching.html
// All credit for the implementation goes to Joris van Rantwijk [http://jorisvr.nl].

// ** Original introduction below **

// Weighted maximum matching in general graphs.

// The algorithm is taken from "Efficient Algorithms for Finding Maximum
// Matching in Graphs" by Zvi Galil, ACM Computing Surveys, 1986.
// It is based on the "blossom" method for finding augmenting paths and
// the "primal-dual" method for finding a matching of maximum weight, both
// due to Jack Edmonds.
// Some ideas came from "Implementation of algorithms for maximum matching
// on non-bipartite graphs" by H.J. Gabow, Standford Ph.D. thesis, 1973.

// A C program for maximum weight matching by Ed Rothberg was used extensively
// to validate this new code.

export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
	// Check delta2/delta3 computation after every substage;
	// only works on integer weights, slows down the algorithm to O(n^4).
	if (CHECK_DELTA === undefined) CHECK_DELTA = false;

	// Check optimality of solution before returning; only works on integer weights.
	if (CHECK_OPTIMUM === undefined) CHECK_OPTIMUM = true;

	/**
	 * Compute a maximum-weighted matching in the general undirected
	 * weighted graph given by "edges". If "maxCardinality" is true,
	 * only maximum-cardinality matchings are considered as solutions.
	 *
	 * Edges is a sequence of tuples (i, j, wt) describing an undirected
	 * edge between vertex i and vertex j with weight wt.  There is at most
	 * one edge between any two vertices; no vertex has an edge to itthis.
	 * Vertices are identified by consecutive, non-negative integers.
	 *
	 * Return a list "mate", such that mate[i] === j if vertex i is
	 * matched to vertex j, and mate[i] === -1 if vertex i is not matched.
	 *
	 * This function takes time O(n^3)
	 *
	 * @param {Array} edges
	 * @param {Boolean} maxCardinality
	 * @return {Array}
	 */

	const maxWeightMatching = (edges, maxCardinality = false) => {
		// Vertices are numbered 0 .. (nvertex-1).
		// Non-trivial blossoms are numbered nvertex .. (2*nvertex-1)
		//
		// Edges are numbered 0 .. (nedge-1).
		// Edge endpoints are numbered 0 .. (2*nedge-1), such that endpoints
		// (2*k) and (2*k+1) both belong to edge k.
		//
		// Many terms used in the comments (sub-blossom, T-vertex) come from
		// the paper by Galil; read the paper before reading this code.

		// Deal swiftly with empty graphs.
		if (edges.length === 0) return [];

		// Count vertices + find the maximum edge weight.
		const [nvertex, nedge, maxweight] = statistics(edges);

		// If p is an edge endpoint,
		// endpoint[p] is the vertex to which endpoint p is attached.
		// Not modified by the algorithm.
		const endpoint = endpoints(nedge, edges);

		// If v is a vertex,
		// neighbend[v] is the list of remote endpoints of the edges attached to v.
		// Not modified by the algorithm.
		const neighbend = neighbours(nvertex, nedge, edges);

		// If v is a vertex,
		// mate[v] is the remote endpoint of its matched edge, or -1 if it is single
		// (i.e. endpoint[mate[v]] is v's partner vertex).
		// Initially all vertices are single; updated during augmentation.
		const mate = new Array(nvertex).fill(-1);

		// If b is a top-level blossom,
		// label[b] is 0 if b is unlabeled (free);
		//             1 if b is an S-vertex/blossom;
		//             2 if b is a T-vertex/blossom.
		// The label of a vertex is found by looking at the label of its
		// top-level containing blossom.
		// If v is a vertex inside a T-blossom,
		// label[v] is 2 iff v is reachable from an S-vertex outside the blossom.
		// Labels are assigned during a stage and reset after each augmentation.
		const label = new Array(2 * nvertex).fill(0);

		// If b is a labeled top-level blossom,
		// labelend[b] is the remote endpoint of the edge through which b obtained
		// its label, or -1 if b's base vertex is single.
		// If v is a vertex inside a T-blossom and label[v] === 2,
		// labelend[v] is the remote endpoint of the edge through which v is
		// reachable from outside the blossom.
		const labelend = new Array(2 * nvertex).fill(-1);

		// If v is a vertex,
		// inblossom[v] is the top-level blossom to which v belongs.
		// If v is a top-level vertex, v is itthis a blossom (a trivial blossom)
		// and inblossom[v] === v.
		// Initially all vertices are top-level trivial blossoms.
		const inblossom = new Array(nvertex);
		for (let i = 0; i < nvertex; ++i) inblossom[i] = i;

		// If b is a sub-blossom,
		// blossomparent[b] is its immediate parent (sub-)blossom.
		// If b is a top-level blossom, blossomparent[b] is -1.
		const blossomparent = new Array(2 * nvertex).fill(-1);

		// If b is a non-trivial (sub-)blossom,
		// blossomchilds[b] is an ordered list of its sub-blossoms, starting with
		// the base and going round the blossom.
		const blossomchilds = new Array(2 * nvertex).fill(null);

		// If b is a (sub-)blossom,
		// blossombase[b] is its base VERTEX (i.e. recursive sub-blossom).
		const blossombase = new Array(2 * nvertex);
		for (let i = 0; i < nvertex; ++i) blossombase[i] = i;
		blossombase.fill(-1, nvertex, 2 * nvertex);

		// If b is a non-trivial (sub-)blossom,
		// blossomendps[b] is a list of endpoints on its connecting edges,
		// such that blossomendps[b][i] is the local endpoint of blossomchilds[b][i]
		// on the edge that connects it to blossomchilds[b][wrap(i+1)].
		const blossomendps = new Array(2 * nvertex).fill(null);

		// If v is a free vertex (or an unreached vertex inside a T-blossom),
		// bestedge[v] is the edge to an S-vertex with least slack,
		// or -1 if there is no such edge.
		// If b is a (possibly trivial) top-level S-blossom,
		// bestedge[b] is the least-slack edge to a different S-blossom,
		// or -1 if there is no such edge.
		// This is used for efficient computation of delta2 and delta3.
		const bestedge = new Array(2 * nvertex).fill(-1);

		// If b is a non-trivial top-level S-blossom,
		// blossombestedges[b] is a list of least-slack edges to neighbouring
		// S-blossoms, or null if no such list has been computed yet.
		// This is used for efficient computation of delta3.
		const blossombestedges = new Array(2 * nvertex).fill(null);

		// List of currently unused blossom numbers.
		const unusedblossoms = new Array(nvertex);
		for (let i = 0; i < nvertex; ++i) unusedblossoms[i] = nvertex + i;

		// If v is a vertex,
		// dualvar[v] = 2 * u(v) where u(v) is the v's variable in the dual
		// optimization problem (multiplication by two ensures integer values
		// throughout the algorithm if all edge weights are integers).
		// If b is a non-trivial blossom,
		// dualvar[b] = z(b) where z(b) is b's variable in the dual optimization
		// problem.
		const dualvar = new Array(2 * nvertex);
		dualvar.fill(maxweight, 0, nvertex);
		dualvar.fill(0, nvertex, 2 * nvertex);

		// If allowedge[k] is true, edge k has zero slack in the optimization
		// problem; if allowedge[k] is false, the edge's slack may or may not
		// be zero.
		const allowedge = new Array(nedge).fill(false);

		// Queue of newly discovered S-vertices.
		let queue = [];

		// Return 2 * slack of edge k (does not work inside blossoms).
		const slack = (k) => {
			const [i, j, wt] = edges[k];
			return dualvar[i] + dualvar[j] - 2 * wt;
		};

		// Assign label t to the top-level blossom containing vertex w
		// and record the fact that w was reached through the edge with
		// remote endpoint p.
		const assignLabel = (w, t, p) => {
			console.debug('DEBUG: assignLabel(' + w + ',' + t + ',' + p + ')');
			const b = inblossom[w];
			assert(label[w] === 0 && label[b] === 0);
			assert(t === 1 || t === 2);
			label[w] = t;
			label[b] = t;
			labelend[w] = p;
			labelend[b] = p;
			bestedge[w] = -1;
			bestedge[b] = -1;
			if (t === 1) {
				// B became an S-vertex/blossom; add it(s vertices) to the queue.
				for (const v of blossomLeaves(nvertex, blossomchilds, b)) {
					queue.push(v);
				}

				console.debug('DEBUG: PUSH ' + queue);
			} else {
				// B became a T-vertex/blossom; assign label S to its mate.
				// (If b is a non-trivial blossom, its base is the only vertex
				// with an external mate.)
				const base = blossombase[b];
				assert(mate[base] >= 0);
				assignLabel(endpoint[mate[base]], 1, mate[base] ^ 1);
			}
		};

		// Trace back from vertices v and w to discover either a new blossom
		// or an augmenting path. Return the base vertex of the new blossom or -1.
		const scanBlossom = (v, w) => {
			console.debug('DEBUG: scanBlossom(' + v + ',' + w + ')');
			// Trace back from v and w, placing breadcrumbs as we go.
			const path = [];
			let base = -1;
			while (v !== -1 || w !== -1) {
				// Look for a breadcrumb in v's blossom or put a new breadcrumb.
				let b = inblossom[v];
				if (label[b] & 4) {
					base = blossombase[b];
					break;
				}

				assert(label[b] === 1);
				path.push(b);
				label[b] = 5;
				// Trace one step back.
				assert(labelend[b] === mate[blossombase[b]]);
				if (labelend[b] === -1) {
					// The base of blossom b is single; stop tracing this path.
					v = -1;
				} else {
					v = endpoint[labelend[b]];
					b = inblossom[v];
					assert(label[b] === 2);
					// B is a T-blossom; trace one more step back.
					assert(labelend[b] >= 0);
					v = endpoint[labelend[b]];
				}

				// Swap v and w so that we alternate between both paths.
				if (w !== -1) {
					const temporary_ = v;
					v = w;
					w = temporary_;
				}
			}

			// Remove breadcrumbs.
			for (const b of path) label[b] = 1;

			// Return base vertex, if we found one.
			return base;
		};

		// Construct a new blossom with given base, containing edge k which
		// connects a pair of S vertices. Label the new blossom as S; set its dual
		// variable to zero; relabel its T-vertices to S and add them to the queue.
		const addBlossom = (base, k) => {
			let v = edges[k][0];
			let w = edges[k][1];
			const bb = inblossom[base];
			let bv = inblossom[v];
			let bw = inblossom[w];
			// Create blossom.
			const b = unusedblossoms.pop();
			console.debug(
				'DEBUG: addBlossom(' +
					base +
					',' +
					k +
					') (v=' +
					v +
					' w=' +
					w +
					') -> ' +
					b,
			);
			blossombase[b] = base;
			blossomparent[b] = -1;
			blossomparent[bb] = b;
			// Make list of sub-blossoms and their interconnecting edge endpoints.
			const path = [];
			blossomchilds[b] = path;
			const endps = [];
			blossomendps[b] = endps;
			// Trace back from v to base.
			while (bv !== bb) {
				// Add bv to the new blossom.
				blossomparent[bv] = b;
				path.push(bv);
				endps.push(labelend[bv]);
				assert(
					label[bv] === 2 ||
						(label[bv] === 1 && labelend[bv] === mate[blossombase[bv]]),
				);
				// Trace one step back.
				assert(labelend[bv] >= 0);
				v = endpoint[labelend[bv]];
				bv = inblossom[v];
			}

			// Reverse lists, add endpoint that connects the pair of S vertices.
			path.push(bb);
			path.reverse();
			endps.reverse();
			endps.push(2 * k);
			// Trace back from w to base.
			while (bw !== bb) {
				// Add bw to the new blossom.
				blossomparent[bw] = b;
				path.push(bw);
				endps.push(labelend[bw] ^ 1);
				assert(
					label[bw] === 2 ||
						(label[bw] === 1 && labelend[bw] === mate[blossombase[bw]]),
				);
				// Trace one step back.
				assert(labelend[bw] >= 0);
				w = endpoint[labelend[bw]];
				bw = inblossom[w];
			}

			// Set label to S.
			assert(label[bb] === 1);
			label[b] = 1;
			labelend[b] = labelend[bb];
			// Set dual variable to zero.
			dualvar[b] = 0;
			// Relabel vertices.
			for (const v of blossomLeaves(nvertex, blossomchilds, b)) {
				if (label[inblossom[v]] === 2) {
					// This T-vertex now turns into an S-vertex because it becomes
					// part of an S-blossom; add it to the queue.
					queue.push(v);
				}

				inblossom[v] = b;
			}

			// Compute blossombestedges[b].

			const bestedgeto = new Array(2 * nvertex).fill(-1);

			const length_ = path.length;
			for (let z = 0; z < length_; ++z) {
				const bv = path[z];
				// Walk this subblossom's least-slack edges.
				let nblist = blossombestedges[bv];
				if (nblist === null) {
					// This subblossom does not have a list of least-slack edges;
					// get the information from the vertices.
					nblist = blossomEdges(nvertex, blossomchilds, neighbend, bv);
				}

				for (const k of nblist) {
					const [i, j] = edges[k];
					const bj = inblossom[j] === b ? inblossom[i] : inblossom[j];

					if (
						bj !== b &&
						label[bj] === 1 &&
						(bestedgeto[bj] === -1 || slack(k) < slack(bestedgeto[bj]))
					) {
						bestedgeto[bj] = k;
					}
				}

				// Forget about least-slack edges of the subblossom.
				blossombestedges[bv] = null;
				bestedge[bv] = -1;
			}

			blossombestedges[b] = [];
			const length_2 = bestedgeto.length;
			for (let i = 0; i < length_2; ++i) {
				k = bestedgeto[i];
				if (k !== -1) blossombestedges[b].push(k);
			}

			// Select bestedge[b].

			const length_3 = blossombestedges[b].length;
			if (length_3 > 0) {
				bestedge[b] = blossombestedges[b][0];
				for (let i = 1; i < length_3; ++i) {
					k = blossombestedges[b][i];
					if (slack(k) < slack(bestedge[b])) {
						bestedge[b] = k;
					}
				}
			} else bestedge[b] = -1;

			console.debug('DEBUG: blossomchilds[' + b + ']=' + blossomchilds[b]);
		};

		// Expand the given top-level blossom.
		const expandBlossom = (b, endstage) => {
			console.debug(
				'DEBUG: expandBlossom(' + b + ',' + endstage + ') ' + blossomchilds[b],
			);
			// Convert sub-blossoms into top-level blossoms.
			for (let i = 0; i < blossomchilds[b].length; ++i) {
				const s = blossomchilds[b][i];

				blossomparent[s] = -1;
				if (s < nvertex) inblossom[s] = s;
				else if (endstage && dualvar[s] === 0) {
					// Recursively expand this sub-blossom.
					expandBlossom(s, endstage);
				} else {
					for (const v of blossomLeaves(nvertex, blossomchilds, s)) {
						inblossom[v] = s;
					}
				}
			}

			// If we expand a T-blossom during a stage, its sub-blossoms must be
			// relabeled.
			if (!endstage && label[b] === 2) {
				// Start at the sub-blossom through which the expanding
				// blossom obtained its label, and relabel sub-blossoms untili
				// we reach the base.
				// Figure out through which sub-blossom the expanding blossom
				// obtained its label initially.
				assert(labelend[b] >= 0);
				const entrychild = inblossom[endpoint[labelend[b] ^ 1]];
				// Decide in which direction we will go round the blossom.
				let j = blossomchilds[b].indexOf(entrychild);
				let jstep;
				let endptrick;
				let stop;
				let base;
				if (j & 1) {
					// Start index is odd; go forward.
					jstep = 1;
					endptrick = 0;
					stop = blossomchilds[b].length;
					base = 0;
				} else {
					// Start index is even; go backward.
					jstep = -1;
					endptrick = 1;
					stop = 0;
					base = blossomchilds[b].length;
				}

				// Move along the blossom until we get to the base.
				let p = labelend[b];
				while (j !== stop) {
					// Relabel the T-sub-blossom.
					label[endpoint[p ^ 1]] = 0;
					label[endpoint[blossomendps[b][j - endptrick] ^ endptrick ^ 1]] = 0;
					assignLabel(endpoint[p ^ 1], 2, p);
					// Step to the next S-sub-blossom and note its forward endpoint.
					allowedge[Math.floor(blossomendps[b][j - endptrick] / 2)] = true;
					j += jstep;
					p = blossomendps[b][j - endptrick] ^ endptrick;
					// Step to the next T-sub-blossom.
					allowedge[Math.floor(p / 2)] = true;
					j += jstep;
				}

				// Relabel the base T-sub-blossom WITHOUT stepping through to
				// its mate (so don't call assignLabel).
				let bv = blossomchilds[b][0];
				label[endpoint[p ^ 1]] = 2;
				label[bv] = 2;
				labelend[endpoint[p ^ 1]] = p;
				labelend[bv] = p;
				bestedge[bv] = -1;
				// Continue along the blossom until we get back to entrychild.
				j = base + jstep;
				while (blossomchilds[b][j] !== entrychild) {
					// Examine the vertices of the sub-blossom to see whether
					// it is reachable from a neighbouring S-vertex outside the
					// expanding blossom.
					bv = blossomchilds[b][j];
					if (label[bv] === 1) {
						// This sub-blossom just got label S through one of its
						// neighbours; leave it.
						j += jstep;
						continue;
					}

					for (const v of blossomLeaves(nvertex, blossomchilds, bv)) {
						if (label[v] === 0) continue;
						// If the sub-blossom contains a reachable vertex, assign
						// label T to the sub-blossom.
						assert(label[v] === 2);
						assert(inblossom[v] === bv);
						label[v] = 0;
						label[endpoint[mate[blossombase[bv]]]] = 0;
						assignLabel(v, 2, labelend[v]);
						break;
					}

					j += jstep;
				}
			}

			// Recycle the blossom number.
			label[b] = -1;
			labelend[b] = -1;
			blossomchilds[b] = null;
			blossomendps[b] = null;
			blossombase[b] = -1;
			blossombestedges[b] = null;
			bestedge[b] = -1;
			unusedblossoms.push(b);
		};

		// Swap matched/unmatched edges over an alternating path through blossom b
		// between vertex v and the base vertex. Keep blossom bookkeeping consistent.
		const augmentBlossom = (b, v) => {
			console.debug('DEBUG: augmentBlossom(' + b + ',' + v + ')');
			// Bubble up through the blossom tree from vertex v to an immediate
			// sub-blossom of b.
			let j;
			let jstep;
			let endptrick;
			let stop;
			let p;
			let t = v;
			while (blossomparent[t] !== b) t = blossomparent[t];
			// Recursively deal with the first sub-blossom.
			if (t >= nvertex) augmentBlossom(t, v);
			// Decide in which direction we will go round the blossom.
			j = blossomchilds[b].indexOf(t);
			const i = j;
			const length_ = blossomchilds[b].length;
			if (i & 1) {
				// Start index is odd; go forward.
				jstep = 1;
				endptrick = 0;
				stop = length_;
			} else {
				// Start index is even; go backward.
				jstep = -1;
				endptrick = 1;
				stop = 0;
			}

			// Move along the blossom until we get to the base.
			while (j !== stop) {
				// Step to the next sub-blossom and augment it recursively.
				j += jstep;
				t = blossomchilds[b][j];
				p = blossomendps[b][j - endptrick] ^ endptrick;
				if (t >= nvertex) augmentBlossom(t, endpoint[p]);
				// Step to the next sub-blossom and augment it recursively.
				j += jstep;
				t = blossomchilds[b][Math.abs(j % length_)];
				if (t >= nvertex) augmentBlossom(t, endpoint[p ^ 1]);
				// Match the edge connecting those sub-blossoms.
				mate[endpoint[p]] = p ^ 1;
				mate[endpoint[p ^ 1]] = p;
				console.debug(
					'DEBUG: PAIR ' +
						endpoint[p] +
						' ' +
						endpoint[p ^ 1] +
						' (k=' +
						Math.floor(p / 2) +
						')',
				);
			}

			// Rotate the list of sub-blossoms to put the new base at the front.
			rotate(blossomchilds[b], i);
			rotate(blossomendps[b], i);
			blossombase[b] = blossombase[blossomchilds[b][0]];
			assert(blossombase[b] === v);
		};

		// Swap matched/unmatched edges over an alternating path between two
		// single vertices. The augmenting path runs through edge k, which
		// connects a pair of S vertices.
		const augmentMatching = (k) => {
			const v = edges[k][0];
			const w = edges[k][1];

			console.debug(
				'DEBUG: augmentMatching(' + k + ') (v=' + v + ' w=' + w + ')',
			);
			console.debug('DEBUG: PAIR ' + v + ' ' + w + ' (k=' + k + ')');

			matchVerticesAndFix(v, 2 * k + 1);
			matchVerticesAndFix(w, 2 * k);
		};

		const matchVerticesAndFix = (s, p) => {
			// Match vertex s to remote endpoint p. Then trace back from s
			// until we find a single vertex, swapping matched and unmatched
			// edges as we go.
			// eslint-disable-next-line no-constant-condition
			while (true) {
				const bs = inblossom[s];
				assert(label[bs] === 1);
				assert(labelend[bs] === mate[blossombase[bs]]);
				// Augment through the S-blossom from s to base.
				if (bs >= nvertex) augmentBlossom(bs, s);
				// Update mate[s]
				mate[s] = p;
				// Trace one step back.
				if (labelend[bs] === -1) {
					// Reached single vertex; stop.
					break;
				}

				const t = endpoint[labelend[bs]];
				const bt = inblossom[t];
				assert(label[bt] === 2);
				// Trace one step back.
				assert(labelend[bt] >= 0);
				s = endpoint[labelend[bt]];
				const j = endpoint[labelend[bt] ^ 1];
				// Augment through the T-blossom from j to base.
				assert(blossombase[bt] === t);
				if (bt >= nvertex) augmentBlossom(bt, j);
				// Update mate[j]
				mate[j] = labelend[bt];
				// Keep the opposite endpoint;
				// it will be assigned to mate[s] in the next step.
				p = labelend[bt] ^ 1;
				console.debug(
					'DEBUG: PAIR ' + s + ' ' + t + ' (k=' + Math.floor(p / 2) + ')',
				);
			}
		};

		let d;
		let kslack;
		let base;
		let deltatype;
		let delta;
		let deltaedge;
		let deltablossom;

		// Main loop: continue until no further improvement is possible.
		for (let t = 0; t < nvertex; ++t) {
			// Each iteration of this loop is a "stage".
			// A stage finds an augmenting path and uses that to improve
			// the matching.
			console.debug('DEBUG: STAGE ' + t);

			// Remove labels from top-level blossoms/vertices.
			label.fill(0);

			// Forget all about least-slack edges.
			bestedge.fill(-1);
			blossombestedges.fill(null, nvertex, 2 * nvertex);

			// Loss of labeling means that we can not be sure that currently
			// allowable edges remain allowable througout this stage.
			allowedge.fill(false);

			// Make queue empty.
			queue = [];

			// Label single blossoms/vertices with S and put them in the queue.
			for (let v = 0; v < nvertex; ++v) {
				if (mate[v] === -1 && label[inblossom[v]] === 0) assignLabel(v, 1, -1);
			}

			// Loop until we succeed in augmenting the matching.
			let augmented = false;
			// eslint-disable-next-line no-constant-condition
			while (true) {
				// Each iteration of this loop is a "substage".
				// A substage tries to find an augmenting path;
				// if found, the path is used to improve the matching and
				// the stage ends. If there is no augmenting path, the
				// primal-dual method is used to pump some slack out of
				// the dual variables.
				console.debug('DEBUG: SUBSTAGE');

				// Continue labeling until all vertices which are reachable
				// through an alternating path have got a label.
				while (queue.length > 0 && !augmented) {
					// Take an S vertex from the queue.
					const v = queue.pop();
					console.debug('DEBUG: POP v=' + v);
					assert(label[inblossom[v]] === 1);

					// Scan its neighbours:
					const length = neighbend[v].length;
					for (let i = 0; i < length; ++i) {
						const p = neighbend[v][i];
						const k = Math.floor(p / 2);
						const w = endpoint[p];
						// W is a neighbour to v
						if (inblossom[v] === inblossom[w]) {
							// This edge is internal to a blossom; ignore it
							continue;
						}

						if (!allowedge[k]) {
							kslack = slack(k);
							if (kslack <= 0) {
								// Edge k has zero slack => it is allowable
								allowedge[k] = true;
							}
						}

						if (allowedge[k]) {
							if (label[inblossom[w]] === 0) {
								// (C1) w is a free vertex;
								// label w with T and label its mate with S (R12).
								assignLabel(w, 2, p ^ 1);
							} else if (label[inblossom[w]] === 1) {
								// (C2) w is an S-vertex (not in the same blossom);
								// follow back-links to discover either an
								// augmenting path or a new blossom.
								base = scanBlossom(v, w);
								if (base >= 0) {
									// Found a new blossom; add it to the blossom
									// bookkeeping and turn it into an S-blossom.
									addBlossom(base, k);
								} else {
									// Found an augmenting path; augment the
									// matching and end this stage.
									augmentMatching(k);
									augmented = true;
									break;
								}
							} else if (label[w] === 0) {
								// W is inside a T-blossom, but w itthis has not
								// yet been reached from outside the blossom;
								// mark it as reached (we need this to relabel
								// during T-blossom expansion).
								assert(label[inblossom[w]] === 2);
								label[w] = 2;
								labelend[w] = p ^ 1;
							}
						} else if (label[inblossom[w]] === 1) {
							// Keep track of the least-slack non-allowable edge to
							// a different S-blossom.
							const b = inblossom[v];
							if (bestedge[b] === -1 || kslack < slack(bestedge[b]))
								bestedge[b] = k;
						} else if (
							label[w] === 0 && // W is a free vertex (or an unreached vertex inside
							// a T-blossom) but we can not reach it yet;
							// keep track of the least-slack edge that reaches w.
							(bestedge[w] === -1 || kslack < slack(bestedge[w]))
						)
							bestedge[w] = k;
					}
				}

				if (augmented) break;

				// There is no augmenting path under these constraints;
				// compute delta and reduce slack in the optimization problem.
				// (Note that our vertex dual variables, edge slacks and delta's
				// are pre-multiplied by two.)
				deltatype = -1;
				delta = null;
				deltaedge = null;
				deltablossom = null;

				// Verify data structures for delta2/delta3 computation.
				if (CHECK_DELTA) {
					checkDelta2({
						nvertex,
						neighbend,
						label,
						endpoint,
						bestedge,
						slack,
						inblossom,
					});
					checkDelta3({
						nvertex,
						edges,
						blossomparent,
						blossomchilds,
						neighbend,
						label,
						endpoint,
						bestedge,
						slack,
						inblossom,
					});
				}

				// Compute delta1: the minumum value of any vertex dual.
				if (!maxCardinality) {
					deltatype = 1;
					delta = min(dualvar, 0, nvertex);
				}

				// Compute delta2: the minimum slack on any edge between
				// an S-vertex and a free vertex.
				for (let v = 0; v < nvertex; ++v) {
					if (label[inblossom[v]] === 0 && bestedge[v] !== -1) {
						d = slack(bestedge[v]);
						if (deltatype === -1 || d < delta) {
							delta = d;
							deltatype = 2;
							deltaedge = bestedge[v];
						}
					}
				}

				// Compute delta3: half the minimum slack on any edge between
				// a pair of S-blossoms.
				for (let b = 0; b < 2 * nvertex; ++b) {
					if (blossomparent[b] === -1 && label[b] === 1 && bestedge[b] !== -1) {
						kslack = slack(bestedge[b]);
						d = kslack / 2;
						if (deltatype === -1 || d < delta) {
							delta = d;
							deltatype = 3;
							deltaedge = bestedge[b];
						}
					}
				}

				// Compute delta4: minimum z variable of any T-blossom.
				for (let b = nvertex; b < 2 * nvertex; ++b) {
					if (
						blossombase[b] >= 0 &&
						blossomparent[b] === -1 &&
						label[b] === 2 &&
						(deltatype === -1 || dualvar[b] < delta)
					) {
						delta = dualvar[b];
						deltatype = 4;
						deltablossom = b;
					}
				}

				if (deltatype === -1) {
					// No further improvement possible; max-cardinality optimum
					// reached. Do a final delta update to make the optimum
					// verifyable.
					assert(maxCardinality);
					deltatype = 1;
					delta = Math.max(0, min(dualvar, 0, nvertex));
				}

				// Update dual variables according to delta.
				for (let v = 0; v < nvertex; ++v) {
					if (label[inblossom[v]] === 1) {
						// S-vertex: 2*u = 2*u - 2*delta
						dualvar[v] -= delta;
					} else if (label[inblossom[v]] === 2) {
						// T-vertex: 2*u = 2*u + 2*delta
						dualvar[v] += delta;
					}
				}

				for (let b = nvertex; b < 2 * nvertex; ++b) {
					if (blossombase[b] >= 0 && blossomparent[b] === -1) {
						if (label[b] === 1) {
							// Top-level S-blossom: z = z + 2*delta
							dualvar[b] += delta;
						} else if (label[b] === 2) {
							// Top-level T-blossom: z = z - 2*delta
							dualvar[b] -= delta;
						}
					}
				}

				// Take action at the point where minimum delta occurred.
				console.debug('DEBUG: delta' + deltatype + '=' + delta);
				assert(
					deltatype === 1 ||
						deltatype === 2 ||
						deltatype === 3 ||
						deltatype === 4,
				);
				if (deltatype === 1) {
					// No further improvement possible; optimum reached.
					break;
				} else if (deltatype === 2) {
					// Use the least-slack edge to continue the search.
					allowedge[deltaedge] = true;
					let i = edges[deltaedge][0];
					if (label[inblossom[i]] === 0) i = edges[deltaedge][1];
					assert(label[inblossom[i]] === 1);
					queue.push(i);
				} else if (deltatype === 3) {
					// Use the least-slack edge to continue the search.
					allowedge[deltaedge] = true;
					const i = edges[deltaedge][0];
					assert(label[inblossom[i]] === 1);
					queue.push(i);
				} else {
					// Expand the least-z blossom.
					expandBlossom(deltablossom, false);
				}
			}

			// End of a this substage.

			// Stop when no more augmenting path can be found.
			if (!augmented) break;

			// End of a stage; expand all S-blossoms which have dualvar = 0.
			for (let b = nvertex; b < 2 * nvertex; ++b) {
				if (
					blossomparent[b] === -1 &&
					blossombase[b] >= 0 &&
					label[b] === 1 &&
					dualvar[b] === 0
				) {
					expandBlossom(b, true);
				}
			}
		}

		// Verify that we reached the optimum solution.
		if (CHECK_OPTIMUM)
			verifyOptimum({
				nvertex,
				edges,
				maxCardinality,
				nedge,
				blossomparent,
				mate,
				endpoint,
				dualvar,
				blossombase,
				blossomendps,
			});

		// Transform mate[] such that mate[v] is the vertex to which v is paired.
		for (let v = 0; v < nvertex; ++v) {
			if (mate[v] >= 0) {
				mate[v] = endpoint[mate[v]];
			}
		}

		for (let v = 0; v < nvertex; ++v) {
			assert(mate[v] === -1 || mate[mate[v]] === v);
		}

		return mate;
	};

	return maxWeightMatching;
}