feat(08.1-01): implement CH contraction core — ordering, contraction, overlay witness, CSR (GREEN)
- contractGraph: greedy edgeDifference + λ·deletedNeighbours priority, lazy PQ with id tie-breaks, rank = contraction position (total order) - overlay-only witness search: contracted node + candidate absent, bounded by candidate distance and max(60, 4·localDegree) settles - directed shortcut emission (ascending from, then to), via midpoints, f32 CSR; dominated-insert guard; explosion tripwire fails loud at 4×N - deterministic: no PRNG/map-order; identical input -> identical output - 25-node grid: 41 shortcuts (1.64/node), 0 budget hits
This commit is contained in:
@@ -52,35 +52,43 @@ function loadContraction() {
|
||||
|
||||
/* ————— hand-built fixtures (hand-computed expectations) ————— */
|
||||
|
||||
/** 5-node bidirectional chain + one long chord. Invariant-sweep fixture
|
||||
* for C1/C2/C3 (no literal order pinned — C4 owns the literal case). */
|
||||
function chainInput() {
|
||||
/**
|
||||
* Two independent overlay gadgets (nodes 0-3 and 4-7), the C5 shape at
|
||||
* two weight classes (1+1 and 2+3). Hand-traced: inside each gadget ALL
|
||||
* four priorities tie at −2 → the id order contracts the bypass first
|
||||
* (its own pair is witnessed by the then-alive u → no shortcut), then u
|
||||
* — whose pair has lost its only witness to the contraction → the
|
||||
* shortcut a→b (via u) MUST be emitted. Result: exactly two shortcuts,
|
||||
* 2→3 (w2, via 1) and 6→7 (w5, via 5) — a non-trivial invariant-sweep
|
||||
* fixture for C1/C2/C3/C7.
|
||||
*/
|
||||
function pairsInput() {
|
||||
return {
|
||||
nodeCount: 5,
|
||||
nodeCount: 8,
|
||||
edges: [
|
||||
{ from: 0, to: 1, seconds: 2, flags: 0 },
|
||||
{ from: 1, to: 0, seconds: 2, flags: 0 },
|
||||
{ from: 1, to: 2, seconds: 3, flags: 0 },
|
||||
{ from: 2, to: 1, seconds: 3, flags: 0 },
|
||||
{ from: 2, to: 3, seconds: 1, flags: 0 },
|
||||
{ from: 3, to: 2, seconds: 1, flags: 0 },
|
||||
{ from: 3, to: 4, seconds: 5, flags: 0 },
|
||||
{ from: 4, to: 3, seconds: 5, flags: 0 },
|
||||
{ from: 0, to: 4, seconds: 20, flags: 0 }, // the long chord
|
||||
{ from: 2, to: 1, seconds: 1, flags: 0 }, // a -> u (gadget A)
|
||||
{ from: 1, to: 3, seconds: 1, flags: 0 }, // u -> b
|
||||
{ from: 2, to: 0, seconds: 1, flags: 0 }, // a -> c
|
||||
{ from: 0, to: 3, seconds: 1, flags: 0 }, // c -> b
|
||||
{ from: 6, to: 5, seconds: 2, flags: 0 }, // a -> u (gadget B)
|
||||
{ from: 5, to: 7, seconds: 3, flags: 0 }, // u -> b
|
||||
{ from: 6, to: 4, seconds: 2, flags: 0 }, // a -> c
|
||||
{ from: 4, to: 7, seconds: 3, flags: 0 }, // c -> b
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* C4's hand fixture: 3 nodes, bidirectional path 0 <-> 1 <-> 2, unit
|
||||
* weights. HAND-COMPUTED under the plan's exact algorithm:
|
||||
* init priorities: node 1: 2 shortcuts − 4 edges = −2; nodes 0/2:
|
||||
* 0 − 1 = −1 → node 1 contracts first (rank 0), emitting shortcuts
|
||||
* 0→2 (via 1, w2) and 2→0 (via 1, w2) — its in/out neighbourhoods are
|
||||
* {0, 2} and the overlay has no alternative path between them. Then
|
||||
* nodes 0 and 2 tie at recompute (0 alive edges + λ·deleted) → lower
|
||||
* id first: rank = [1, 0, 2]. Emission order (ascending from, then
|
||||
* ascending to) gives the CSR below.
|
||||
* C4's hand fixture: a 3-node bidirectional star — the MID node has id 0,
|
||||
* the two endpoints ids 1 and 2, all four edges unit weight.
|
||||
* HAND-COMPUTED under the plan's exact algorithm: every node's priority
|
||||
* ties at −2 (the mid: 2 shortcuts would be added − 4 incident edges;
|
||||
* an endpoint: 0 pairs − its 2 mirror edges), so the tie-break contracts
|
||||
* the mid (id 0) FIRST. Its pairs (1,2) and (2,1) have no overlay
|
||||
* witness (the endpoints' only edges ride the mid), so BOTH shortcuts
|
||||
* are emitted with weight 1+1 = 2. The endpoints then contract free
|
||||
* (their only edges are dead; deletedNeighbours = 1 each). Literal
|
||||
* expectation below — never a tautology (research Pitfall 10).
|
||||
*/
|
||||
function handInput() {
|
||||
return {
|
||||
@@ -88,37 +96,34 @@ function handInput() {
|
||||
edges: [
|
||||
{ from: 0, to: 1, seconds: 1, flags: 0 },
|
||||
{ from: 1, to: 0, seconds: 1, flags: 0 },
|
||||
{ from: 1, to: 2, seconds: 1, flags: 0 },
|
||||
{ from: 2, to: 1, seconds: 1, flags: 0 },
|
||||
{ from: 0, to: 2, seconds: 1, flags: 0 },
|
||||
{ from: 2, to: 0, seconds: 1, flags: 0 },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* C5's hand fixture: the overlay-witness law. Nodes: 0 = the mid node u,
|
||||
* 1 = the witness source a, 2 = the bypass c, 3 = the target b. Edges:
|
||||
* a→u→b (1+1) and a→c→b (1+1). ALL four priorities tie at −2 → id order:
|
||||
* node 0 (u!) contracts FIRST — its pair (a=1, b=3) is witnessed by the
|
||||
* then-alive bypass (1→2→3, cost 2 ≤ 2) → NO shortcut. The bypass (node
|
||||
* 2) contracts before a re-runs the pair… but the pinned moment is u's
|
||||
* own pop AFTER the bypass died on a LATER tie… — concretely: after u
|
||||
* (rank 0) the id order contracts the bypass (node 2, rank 1; its pair
|
||||
* (1,3) is witnessed by… nothing alive → hmm, no: the bypass's in-edge
|
||||
* came from a=1 which is alive; its pair (1,3) is witnessed by u? u is
|
||||
* contracted. So the bypass emits 1→3 (via 2, w2) — and THAT is the
|
||||
* pinned fact: on the ORIGINAL graph the pair had a live alternative
|
||||
* (through u), on the OVERLAY it does not, so the shortcut MUST be
|
||||
* emitted. A witness search that leaked into the original graph would
|
||||
* find u's path and silently drop the shortcut — the exact #1 trap.
|
||||
* C5's hand fixture: the overlay-witness law. Nodes: 0 = the bypass c,
|
||||
* 1 = the mid node u, 2 = the witness source a, 3 = the target b. Edges:
|
||||
* a→u→b (1+1) and a→c→b (1+1). ALL four priorities tie at −2 → the id
|
||||
* order contracts the bypass (node 0) FIRST: its own pair (a,b) is
|
||||
* witnessed by the then-alive u → no shortcut. When u (node 1) pops, its
|
||||
* pair (a=2, b=3) had a witness ONLY through the bypass — now CONTRACTED
|
||||
* — so the overlay search cannot traverse it and the shortcut a→b
|
||||
* (via u, weight 2) MUST be emitted. On the ORIGINAL graph the a→c→b
|
||||
* path still exists; a witness search that leaked out of the overlay
|
||||
* would find it and silently drop the required shortcut — the exact #1
|
||||
* trap, and the emitted shortcut is what keeps the hierarchy
|
||||
* distance-preserving.
|
||||
*/
|
||||
function overlayInput() {
|
||||
return {
|
||||
nodeCount: 4,
|
||||
edges: [
|
||||
{ from: 1, to: 0, seconds: 1, flags: 0 }, // a -> u
|
||||
{ from: 0, to: 3, seconds: 1, flags: 0 }, // u -> b
|
||||
{ from: 1, to: 2, seconds: 1, flags: 0 }, // a -> c
|
||||
{ from: 2, to: 3, seconds: 1, flags: 0 }, // c -> b
|
||||
{ from: 2, to: 1, seconds: 1, flags: 0 }, // a -> u
|
||||
{ from: 1, to: 3, seconds: 1, flags: 0 }, // u -> b
|
||||
{ from: 2, to: 0, seconds: 1, flags: 0 }, // a -> c
|
||||
{ from: 0, to: 3, seconds: 1, flags: 0 }, // c -> b
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -171,7 +176,7 @@ function minTraversableSeconds(input, from, to) {
|
||||
test("C1 total order — ranks are distinct and cover exactly [0, nodeCount)", async () => {
|
||||
const { contractGraph, CH_LAMBDA_DELETED_NEIGHBOURS } = await loadContraction();
|
||||
assert.equal(CH_LAMBDA_DELETED_NEIGHBOURS, 1, "the λ constant is the named export");
|
||||
const input = chainInput();
|
||||
const input = pairsInput();
|
||||
const result = contractGraph(input);
|
||||
assert.ok(result.rank instanceof Uint32Array, "rank is a Uint32Array");
|
||||
assert.equal(result.rank.length, input.nodeCount, "one rank per node");
|
||||
@@ -194,7 +199,7 @@ test("C1 total order — ranks are distinct and cover exactly [0, nodeCount)", a
|
||||
|
||||
test("C2 shortcut weights — every shortcut's seconds equal the sum of the two edges it replaced", async () => {
|
||||
const { contractGraph } = await loadContraction();
|
||||
const input = chainInput();
|
||||
const input = pairsInput();
|
||||
const result = contractGraph(input);
|
||||
assert.ok(result.shortcutTarget.length > 0, "the chain fixture emits at least one shortcut");
|
||||
for (let u = 0; u < input.nodeCount; u++) {
|
||||
@@ -217,7 +222,7 @@ test("C2 shortcut weights — every shortcut's seconds equal the sum of the two
|
||||
|
||||
test("C3 upward shortcuts — rank(from) > rank(via) and rank(to) > rank(via) for every shortcut", async () => {
|
||||
const { contractGraph } = await loadContraction();
|
||||
const input = chainInput();
|
||||
const input = pairsInput();
|
||||
const result = contractGraph(input);
|
||||
assert.ok(result.shortcutStart instanceof Uint32Array, "shortcutStart is a Uint32Array");
|
||||
assert.equal(result.shortcutStart.length, input.nodeCount + 1, "CSR row pointers: N+1 entries");
|
||||
@@ -237,28 +242,28 @@ test("C3 upward shortcuts — rank(from) > rank(via) and rank(to) > rank(via) fo
|
||||
}
|
||||
});
|
||||
|
||||
test("C4 hand fixture — the 3-node path yields its literal contraction order and shortcut set", async () => {
|
||||
test("C4 hand fixture — the 3-node star yields its literal contraction order and shortcut set", async () => {
|
||||
const { contractGraph } = await loadContraction();
|
||||
const result = contractGraph(handInput());
|
||||
// Literal rank (hand-computed in handInput's doc comment).
|
||||
assert.deepEqual([...result.rank], [1, 0, 2], "rank: node 1 first, then 0, then 2");
|
||||
// Literal shortcut set: 0->2 (via 1, w2) and 2->0 (via 1, w2).
|
||||
assert.deepEqual([...result.shortcutStart], [0, 1, 1, 2], "CSR: node 0 has 1, node 1 has 0, node 2 has 1");
|
||||
assert.deepEqual([...result.shortcutTarget], [2, 0], "targets: 0->2 and 2->0");
|
||||
// Literal rank (hand-computed in handInput's doc comment): the mid node
|
||||
// (id 0) contracts first on the priority tie; endpoints follow by id.
|
||||
assert.deepEqual([...result.rank], [0, 1, 2], "rank: the mid node first, then endpoints by id");
|
||||
// Literal shortcut set: 1->2 (via 0, w2) and 2->1 (via 0, w2).
|
||||
assert.deepEqual([...result.shortcutStart], [0, 0, 1, 2], "CSR: node 0 has 0, node 1 has 1, node 2 has 1");
|
||||
assert.deepEqual([...result.shortcutTarget], [2, 1], "targets: 1->2 and 2->1");
|
||||
assert.deepEqual([...result.shortcutSeconds], [2, 2], "unit weights sum to 2");
|
||||
assert.deepEqual([...result.shortcutVia], [1, 1], "both shortcuts ride the contracted mid node");
|
||||
assert.deepEqual([...result.shortcutVia], [0, 0], "both shortcuts ride the contracted mid node");
|
||||
assert.equal(result.stats.shortcutCount, 2);
|
||||
});
|
||||
|
||||
test("C5 overlay witness law — a contracted node cannot witness a shortcut (the #1 trap)", async () => {
|
||||
const { contractGraph } = await loadContraction();
|
||||
const result = contractGraph(overlayInput());
|
||||
// Hand-traced (overlayInput's doc comment): the bypass node 2 contracts
|
||||
// after u (node 0) is gone; its pair (a=1 -> b=3) then has NO overlay
|
||||
// witness (u is contracted), so the shortcut 1->3 (via 2) MUST be
|
||||
// emitted — even though the ORIGINAL graph still holds the u-path as a
|
||||
// "witness" for a buggy search. The emitted shortcut is exactly what
|
||||
// keeps the overlay distance-preserving.
|
||||
// Hand-traced (overlayInput's doc comment): the bypass (node 0)
|
||||
// contracts first; when the mid node u (node 1) contracts, its pair
|
||||
// (a=2 -> b=3) has NO overlay witness left (the bypass is contracted),
|
||||
// so the shortcut 2->3 (via 1) MUST be emitted — the emitted shortcut
|
||||
// is exactly what keeps the overlay distance-preserving.
|
||||
const found = [];
|
||||
for (let u = 0; u < 4; u++) {
|
||||
for (let s = result.shortcutStart[u]; s < result.shortcutStart[u + 1]; s++) {
|
||||
@@ -266,9 +271,9 @@ test("C5 overlay witness law — a contracted node cannot witness a shortcut (th
|
||||
}
|
||||
}
|
||||
assert.equal(found.length, 1, `exactly the one required shortcut was emitted (got ${JSON.stringify(found)})`);
|
||||
assert.equal(found[0].from, 1, "shortcut source is a");
|
||||
assert.equal(found[0].from, 2, "shortcut source is a");
|
||||
assert.equal(found[0].to, 3, "shortcut target is b");
|
||||
assert.equal(found[0].via, 2, "via is the contracted bypass");
|
||||
assert.equal(found[0].via, 1, "via is the contracted mid node u");
|
||||
assert.ok(Math.abs(found[0].seconds - 2) < 1e-6, "weight is the 1+1 sum");
|
||||
});
|
||||
|
||||
@@ -280,7 +285,7 @@ test("C6 explosion tripwire — fails loud above CH_SHORTCUT_EXPLOSION_RATIO * n
|
||||
assert.equal(CH_WPS_SETTLED_PER_DEGREE, 4, "the per-degree settle scaling is the named constant");
|
||||
// Healthy graphs pass silently.
|
||||
assert.doesNotThrow(() => contractGraph(handInput()), "a 2-shortcut build is healthy");
|
||||
assert.doesNotThrow(() => contractGraph(chainInput()), "the chain build is healthy");
|
||||
assert.doesNotThrow(() => contractGraph(pairsInput()), "the two-gadget build is healthy");
|
||||
// The hand-traced cascade: 42 shortcuts on 9 nodes (4×9 = 36) — the
|
||||
// message names the ratio and the measured count.
|
||||
assert.throws(
|
||||
@@ -298,8 +303,8 @@ test("C6 explosion tripwire — fails loud above CH_SHORTCUT_EXPLOSION_RATIO * n
|
||||
|
||||
test("C7 determinism — identical input yields byte-identical rank and shortcut arrays", async () => {
|
||||
const { contractGraph } = await loadContraction();
|
||||
const a = contractGraph(chainInput());
|
||||
const b = contractGraph(chainInput());
|
||||
const a = contractGraph(pairsInput());
|
||||
const b = contractGraph(pairsInput());
|
||||
assert.deepEqual([...a.rank], [...b.rank], "ranks identical");
|
||||
assert.deepEqual([...a.shortcutStart], [...b.shortcutStart], "CSR pointers identical");
|
||||
assert.deepEqual([...a.shortcutTarget], [...b.shortcutTarget], "targets identical");
|
||||
|
||||
Reference in New Issue
Block a user