Skip to content

Commit 22bb23b

Browse files
♻️ refactor(blossom): Extract verification code.
1 parent 1ece25e commit 22bb23b

File tree

7 files changed

+265
-179
lines changed

7 files changed

+265
-179
lines changed

src/core/blossom.js renamed to src/core/blossom/blossom.js

Lines changed: 43 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import assert from 'assert';
2+
import min from './min';
3+
import rotate from './rotate';
4+
import verifyOptimum from './verifyOptimum';
5+
import checkDelta2 from './checkDelta2';
6+
import checkDelta3 from './checkDelta3';
27

38
// Adapted from http://jorisvr.nl/maximummatching.html
49
// All credit for the implementation goes to Joris van Rantwijk [http://jorisvr.nl].
@@ -18,12 +23,6 @@ import assert from 'assert';
1823
// A C program for maximum weight matching by Ed Rothberg was used extensively
1924
// to validate this new code.
2025

21-
const min = (a, i, j) => {
22-
let o = a[i];
23-
for (++i; i < j; ++i) if (a[i] < o) o = a[i];
24-
return o;
25-
};
26-
2726
export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
2827
// Check delta2/delta3 computation after every substage;
2928
// only works on integer weights, slows down the algorithm to O(n^4).
@@ -32,7 +31,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
3231
// Check optimality of solution before returning; only works on integer weights.
3332
if (CHECK_OPTIMUM === undefined) CHECK_OPTIMUM = true;
3433

35-
const maxWeightMatching = function (edges, maxcardinality = false) {
34+
const maxWeightMatching = function (edges, maxCardinality = false) {
3635
let i;
3736
let j;
3837
let k;
@@ -43,7 +42,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
4342
/**
4443
*
4544
* Compute a maximum-weighted matching in the general undirected
46-
* weighted graph given by "edges". If "maxcardinality" is true,
45+
* weighted graph given by "edges". If "maxCardinality" is true,
4746
* only maximum-cardinality matchings are considered as solutions.
4847
*
4948
* Edges is a sequence of tuples (i, j, wt) describing an undirected
@@ -638,13 +637,6 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
638637
unusedblossoms.push(b);
639638
};
640639

641-
const rotate = function (a, n) {
642-
const head = a.splice(0, n);
643-
for (let i = 0; i < n; ++i) {
644-
a.push(head[i]);
645-
}
646-
};
647-
648640
// Swap matched/unmatched edges over an alternating path through blossom b
649641
// between vertex v and the base vertex. Keep blossom bookkeeping consistent.
650642
const augmentBlossom = function (b, v) {
@@ -772,165 +764,6 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
772764
});
773765
};
774766

775-
// Verify that the optimum solution has been reached.
776-
const verifyOptimum = function () {
777-
let i;
778-
let j;
779-
let wt;
780-
let v;
781-
let b;
782-
let p;
783-
let k;
784-
let s;
785-
let vdualoffset;
786-
let iblossoms;
787-
let jblossoms;
788-
if (maxcardinality) {
789-
// Vertices may have negative dual;
790-
// find a constant non-negative number to add to all vertex duals.
791-
vdualoffset = Math.max(0, -min(dualvar, 0, nvertex));
792-
} else vdualoffset = 0;
793-
// 0. all dual variables are non-negative
794-
assert(min(dualvar, 0, nvertex) + vdualoffset >= 0);
795-
assert(min(dualvar, nvertex, 2 * nvertex) >= 0);
796-
// 0. all edges have non-negative slack and
797-
// 1. all matched edges have zero slack;
798-
for (k = 0; k < nedge; ++k) {
799-
i = edges[k][0];
800-
j = edges[k][1];
801-
wt = edges[k][2];
802-
803-
s = dualvar[i] + dualvar[j] - 2 * wt;
804-
iblossoms = [i];
805-
jblossoms = [j];
806-
while (blossomparent[iblossoms[iblossoms.length - 1]] !== -1)
807-
iblossoms.push(blossomparent[iblossoms[iblossoms.length - 1]]);
808-
while (blossomparent[jblossoms[jblossoms.length - 1]] !== -1)
809-
jblossoms.push(blossomparent[jblossoms[jblossoms.length - 1]]);
810-
iblossoms.reverse();
811-
jblossoms.reverse();
812-
const length = Math.min(iblossoms.length, jblossoms.length);
813-
for (let x = 0; x < length; ++x) {
814-
const bi = iblossoms[x];
815-
const bj = jblossoms[x];
816-
if (bi !== bj) break;
817-
s += 2 * dualvar[bi];
818-
}
819-
820-
assert(s >= 0);
821-
if (Math.floor(mate[i] / 2) === k || Math.floor(mate[j] / 2) === k) {
822-
assert(
823-
Math.floor(mate[i] / 2) === k && Math.floor(mate[j] / 2) === k
824-
);
825-
assert(s === 0);
826-
}
827-
}
828-
829-
// 2. all single vertices have zero dual value;
830-
for (v = 0; v < nvertex; ++v)
831-
assert(mate[v] >= 0 || dualvar[v] + vdualoffset === 0);
832-
// 3. all blossoms with positive dual value are full.
833-
for (b = nvertex; b < 2 * nvertex; ++b) {
834-
if (blossombase[b] >= 0 && dualvar[b] > 0) {
835-
assert(blossomendps[b].length % 2 === 1);
836-
for (i = 1; i < blossomendps[b].length; i += 2) {
837-
p = blossomendps[b][i];
838-
assert((mate[endpoint[p]] === p) ^ 1);
839-
assert(mate[endpoint[p ^ 1]] === p);
840-
}
841-
}
842-
}
843-
// Ok.
844-
};
845-
846-
// Check optimized delta2 against a trivial computation.
847-
const checkDelta2 = function () {
848-
for (let v = 0; v < nvertex; ++v) {
849-
if (label[inblossom[v]] === 0) {
850-
let bd = null;
851-
let bk = -1;
852-
for (let i = 0; i < neighbend[v].length; ++i) {
853-
const p = neighbend[v][i];
854-
const k = Math.floor(p / 2);
855-
const w = endpoint[p];
856-
if (label[inblossom[w]] === 1) {
857-
const d = slack(k);
858-
if (bk === -1 || d < bd) {
859-
bk = k;
860-
bd = d;
861-
}
862-
}
863-
}
864-
865-
if (
866-
(bestedge[v] !== -1 || bk !== -1) &&
867-
(bestedge[v] === -1 || bd !== slack(bestedge[v]))
868-
) {
869-
console.debug(
870-
'v=' +
871-
v +
872-
' bk=' +
873-
bk +
874-
' bd=' +
875-
bd +
876-
' bestedge=' +
877-
bestedge[v] +
878-
' slack=' +
879-
slack(bestedge[v])
880-
);
881-
}
882-
883-
assert(
884-
(bk === -1 && bestedge[v] === -1) ||
885-
(bestedge[v] !== -1 && bd === slack(bestedge[v]))
886-
);
887-
}
888-
}
889-
};
890-
891-
// Check optimized delta3 against a trivial computation.
892-
const checkDelta3 = function () {
893-
let bk = -1;
894-
let bd = null;
895-
let tbk = -1;
896-
let tbd = null;
897-
for (let b = 0; b < 2 * nvertex; ++b) {
898-
if (blossomparent[b] === -1 && label[b] === 1) {
899-
blossomLeaves(b, function (v) {
900-
for (let x = 0; x < neighbend[v].length; ++x) {
901-
const p = neighbend[v][x];
902-
const k = Math.floor(p / 2);
903-
const w = endpoint[p];
904-
if (inblossom[w] !== b && label[inblossom[w]] === 1) {
905-
const d = slack(k);
906-
if (bk === -1 || d < bd) {
907-
bk = k;
908-
bd = d;
909-
}
910-
}
911-
}
912-
});
913-
914-
if (bestedge[b] !== -1) {
915-
const i = edges[bestedge[b]][0];
916-
const j = edges[bestedge[b]][1];
917-
918-
assert(inblossom[i] === b || inblossom[j] === b);
919-
assert(inblossom[i] !== b || inblossom[j] !== b);
920-
assert(label[inblossom[i]] === 1 && label[inblossom[j]] === 1);
921-
if (tbk === -1 || slack(bestedge[b]) < tbd) {
922-
tbk = bestedge[b];
923-
tbd = slack(bestedge[b]);
924-
}
925-
}
926-
}
927-
}
928-
929-
if (bd !== tbd)
930-
console.debug('bk=' + bk + ' tbk=' + tbk + ' bd=' + bd + ' tbd=' + tbd);
931-
assert(bd === tbd);
932-
};
933-
934767
let b;
935768
let d;
936769
let t;
@@ -1073,12 +906,31 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
1073906

1074907
// Verify data structures for delta2/delta3 computation.
1075908
if (CHECK_DELTA) {
1076-
checkDelta2();
1077-
checkDelta3();
909+
checkDelta2({
910+
nvertex,
911+
neighbend,
912+
label,
913+
endpoint,
914+
bestedge,
915+
slack,
916+
inblossom
917+
});
918+
checkDelta3({
919+
nvertex,
920+
edges,
921+
blossomparent,
922+
blossomLeaves,
923+
neighbend,
924+
label,
925+
endpoint,
926+
bestedge,
927+
slack,
928+
inblossom
929+
});
1078930
}
1079931

1080932
// Compute delta1: the minumum value of any vertex dual.
1081-
if (!maxcardinality) {
933+
if (!maxCardinality) {
1082934
deltatype = 1;
1083935
delta = min(dualvar, 0, nvertex);
1084936
}
@@ -1128,7 +980,7 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
1128980
// No further improvement possible; max-cardinality optimum
1129981
// reached. Do a final delta update to make the optimum
1130982
// verifyable.
1131-
assert(maxcardinality);
983+
assert(maxCardinality);
1132984
deltatype = 1;
1133985
delta = Math.max(0, min(dualvar, 0, nvertex));
1134986
}
@@ -1206,7 +1058,19 @@ export default function blossom(CHECK_OPTIMUM, CHECK_DELTA) {
12061058
}
12071059

12081060
// Verify that we reached the optimum solution.
1209-
if (CHECK_OPTIMUM) verifyOptimum();
1061+
if (CHECK_OPTIMUM)
1062+
verifyOptimum({
1063+
nvertex,
1064+
edges,
1065+
maxCardinality,
1066+
nedge,
1067+
blossomparent,
1068+
mate,
1069+
endpoint,
1070+
dualvar,
1071+
blossombase,
1072+
blossomendps
1073+
});
12101074

12111075
// Transform mate[] such that mate[v] is the vertex to which v is paired.
12121076
for (v = 0; v < nvertex; ++v) {

src/core/blossom/checkDelta2.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import assert from 'assert';
2+
3+
// Check optimized delta2 against a trivial computation.
4+
const checkDelta2 = ({
5+
nvertex,
6+
neighbend,
7+
label,
8+
endpoint,
9+
bestedge,
10+
slack,
11+
inblossom
12+
}) => {
13+
for (let v = 0; v < nvertex; ++v) {
14+
if (label[inblossom[v]] === 0) {
15+
let bd = null;
16+
let bk = -1;
17+
for (let i = 0; i < neighbend[v].length; ++i) {
18+
const p = neighbend[v][i];
19+
const k = Math.floor(p / 2);
20+
const w = endpoint[p];
21+
if (label[inblossom[w]] === 1) {
22+
const d = slack(k);
23+
if (bk === -1 || d < bd) {
24+
bk = k;
25+
bd = d;
26+
}
27+
}
28+
}
29+
30+
if (
31+
(bestedge[v] !== -1 || bk !== -1) &&
32+
(bestedge[v] === -1 || bd !== slack(bestedge[v]))
33+
) {
34+
console.debug(
35+
'v=' +
36+
v +
37+
' bk=' +
38+
bk +
39+
' bd=' +
40+
bd +
41+
' bestedge=' +
42+
bestedge[v] +
43+
' slack=' +
44+
slack(bestedge[v])
45+
);
46+
}
47+
48+
assert(
49+
(bk === -1 && bestedge[v] === -1) ||
50+
(bestedge[v] !== -1 && bd === slack(bestedge[v]))
51+
);
52+
}
53+
}
54+
};
55+
56+
export default checkDelta2;

0 commit comments

Comments
 (0)