doxa-maps/internal/check-mst-clustering.mjs
Doxa Maps 3cd8a8cd30
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions
Update from source c2af6b16
2026-09-26 07:01:59 +00:00

49 lines
2.6 KiB
JavaScript

#!/usr/bin/env node
/**
* check-mst-clustering — the "MST" clustering mode draws a true minimum spanning tree, fast enough to
* run on the main thread. Headless.
*
* WHY (2026-09-26): MST built and sorted every pair of pins (~2.2 million edges for the 2,107-pin roster),
* ~12 s on the main thread, twice over with a stats pass. Choosing "MST" froze the page, and the browser
* gate check:clustering-modes read the map before any line existed ("MST drew no cluster lines").
* MSTClusteringUtil now runs Prim in O(n²).
*
* ASSERTS (1) on sizes 2-400, capped and uncapped, the util's tree has the SAME edge count and total
* length as a reference all-pairs Kruskal written here; (2) 2,107 pins in under 3 s.
* EXIT 0 pass · 1 fail
*/
import MST from '../library/utils/MSTClusteringUtil.js'
import { euclideanDistance } from '../library/utils/ClusterHelpers.js'
const kruskal = (pts, cap) => {
const e = []
for (let i = 0; i < pts.length; i++) for (let j = i + 1; j < pts.length; j++) {
const d = euclideanDistance(pts[i].latitude, pts[i].longitude, pts[j].latitude, pts[j].longitude)
if (d <= cap) e.push([d, i, j])
}
e.sort((a, b) => a[0] - b[0])
const p = pts.map((_, i) => i), f = (x) => (p[x] === x ? x : (p[x] = f(p[x])))
let n = 0, len = 0
for (const [d, i, j] of e) { const a = f(i), b = f(j); if (a !== b) { p[a] = b; n++; len += d } }
return { n, len }
}
const pts = (n, k = 1) => Array.from({ length: n }, (_, i) => ({ latitude: Math.sin(i * 1.7 * k) * 60, longitude: Math.cos(i * 1.3 * k) * 170 }))
const quiet = console.warn; console.warn = () => {}
let bad = 0
for (const [n, cap] of [[2, Infinity], [3, Infinity], [50, Infinity], [400, Infinity], [400, 12], [400, 4]]) {
const P = pts(n), ref = kruskal(P, cap)
const L = new MST({ maxConnectionDistance: cap }).generateMSTLines(P)
const len = L.reduce((a, f) => a + f.properties.distance, 0)
const ok = L.length === ref.n && Math.abs(len - ref.len) < 1e-6
if (!ok) bad++
console.log(` ${ok ? 'ok ' : 'FAIL'} ${String(n).padStart(4)} pins, cap ${cap}: ${L.length} edges, length ${len.toFixed(4)} (reference ${ref.n}, ${ref.len.toFixed(4)})`)
}
console.warn = quiet
const t = Date.now()
const L = new MST({}).generateAllMSTLines({ A: pts(1500, 1.1), B: pts(607, 0.9) })
const ms = Date.now() - t
const fast = ms < 3000 && L.length === 2105
if (!fast) bad++
console.log(` ${fast ? 'ok ' : 'FAIL'} 2,107 pins in two groups: ${L.length} lines in ${ms} ms (budget 3000 ms)`)
if (bad) { console.log(`MST-CLUSTERING: FAIL — ${bad} case(s).`); process.exit(1) }
console.log('MST-CLUSTERING: PASS — a true minimum spanning tree/forest, and fast enough for the main thread.')