graphwise 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/async/index.cjs +98 -1
- package/dist/async/index.cjs.map +1 -0
- package/dist/async/index.d.ts +1 -0
- package/dist/async/index.d.ts.map +1 -1
- package/dist/async/index.js +96 -2
- package/dist/async/index.js.map +1 -0
- package/dist/async/ops.d.ts +2 -0
- package/dist/async/ops.d.ts.map +1 -1
- package/dist/async/protocol.d.ts +14 -0
- package/dist/async/protocol.d.ts.map +1 -1
- package/dist/async/runner-batched.d.ts +21 -0
- package/dist/async/runner-batched.d.ts.map +1 -0
- package/dist/async/runner-batched.unit.test.d.ts +2 -0
- package/dist/async/runner-batched.unit.test.d.ts.map +1 -0
- package/dist/async/runners.d.ts.map +1 -1
- package/dist/expansion/base-core.d.ts.map +1 -1
- package/dist/expansion/fuse.d.ts +24 -1
- package/dist/expansion/fuse.d.ts.map +1 -1
- package/dist/expansion/index.cjs +9 -1
- package/dist/expansion/index.js +2 -2
- package/dist/expansion/lace.d.ts +23 -2
- package/dist/expansion/lace.d.ts.map +1 -1
- package/dist/expansion/priority-helpers.d.ts +20 -1
- package/dist/expansion/priority-helpers.d.ts.map +1 -1
- package/dist/expansion/sift.d.ts +24 -1
- package/dist/expansion/sift.d.ts.map +1 -1
- package/dist/expansion/types.d.ts +30 -0
- package/dist/expansion/types.d.ts.map +1 -1
- package/dist/{expansion-DaTroIyv.cjs → expansion--UuRowv-.cjs} +267 -4
- package/dist/expansion--UuRowv-.cjs.map +1 -0
- package/dist/{expansion-ClDhlMK8.js → expansion-CZLNK6Pr.js} +220 -5
- package/dist/expansion-CZLNK6Pr.js.map +1 -0
- package/dist/gpu/csr-graph.d.ts +68 -0
- package/dist/gpu/csr-graph.d.ts.map +1 -0
- package/dist/gpu/csr-graph.unit.test.d.ts +2 -0
- package/dist/gpu/csr-graph.unit.test.d.ts.map +1 -0
- package/dist/gpu/index.cjs +220 -15
- package/dist/gpu/index.cjs.map +1 -0
- package/dist/gpu/index.d.ts +1 -0
- package/dist/gpu/index.d.ts.map +1 -1
- package/dist/gpu/index.js +204 -2
- package/dist/gpu/index.js.map +1 -0
- package/dist/gpu/kernels/adamic-adar/kernel.d.ts +39 -0
- package/dist/gpu/kernels/adamic-adar/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/intersection/kernel.d.ts +50 -0
- package/dist/gpu/kernels/intersection/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/intersection/logic.d.ts +87 -0
- package/dist/gpu/kernels/intersection/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/intersection/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/intersection/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/kmeans/index.d.ts +6 -0
- package/dist/gpu/kernels/kmeans/index.d.ts.map +1 -0
- package/dist/gpu/kernels/kmeans/kernel.d.ts +34 -0
- package/dist/gpu/kernels/kmeans/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/kmeans/logic.d.ts +111 -0
- package/dist/gpu/kernels/kmeans/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/kmeans/logic.unit.test.d.ts +5 -0
- package/dist/gpu/kernels/kmeans/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/operations.d.ts +52 -0
- package/dist/gpu/operations.d.ts.map +1 -1
- package/dist/index/index.cjs +42 -19
- package/dist/index/index.js +11 -9
- package/dist/{jaccard-Bys9_dGW.cjs → jaccard-Bdw4B0i4.cjs} +1 -1
- package/dist/{jaccard-Bys9_dGW.cjs.map → jaccard-Bdw4B0i4.cjs.map} +1 -1
- package/dist/{jaccard-3rCdilwm.js → jaccard-BwC_NuQu.js} +1 -1
- package/dist/{jaccard-3rCdilwm.js.map → jaccard-BwC_NuQu.js.map} +1 -1
- package/dist/kernel-2oH4Cn32.cjs +1001 -0
- package/dist/kernel-2oH4Cn32.cjs.map +1 -0
- package/dist/kernel-6deK9fh1.js +724 -0
- package/dist/kernel-6deK9fh1.js.map +1 -0
- package/dist/kernel-CXeGBH3s.cjs +467 -0
- package/dist/kernel-CXeGBH3s.cjs.map +1 -0
- package/dist/kernel-CigCjrts.js +467 -0
- package/dist/kernel-CigCjrts.js.map +1 -0
- package/dist/kernel-CvnRsF7E.js +1001 -0
- package/dist/kernel-CvnRsF7E.js.map +1 -0
- package/dist/kernel-DukrXtVb.cjs +724 -0
- package/dist/kernel-DukrXtVb.cjs.map +1 -0
- package/dist/{kmeans-B8x9D1kt.cjs → kmeans-CZ7tJFYw.cjs} +1 -1
- package/dist/{kmeans-B8x9D1kt.cjs.map → kmeans-CZ7tJFYw.cjs.map} +1 -1
- package/dist/{kmeans-DKkL9rAN.js → kmeans-DLrlrp6i.js} +1 -1
- package/dist/{kmeans-DKkL9rAN.js.map → kmeans-DLrlrp6i.js.map} +1 -1
- package/dist/logic-Dbyfb_-7.cjs +289 -0
- package/dist/logic-Dbyfb_-7.cjs.map +1 -0
- package/dist/logic-DyBzRg1A.js +242 -0
- package/dist/logic-DyBzRg1A.js.map +1 -0
- package/dist/operations-D-RB67WP.cjs +2269 -0
- package/dist/operations-D-RB67WP.cjs.map +1 -0
- package/dist/operations-D9otVlIH.js +2198 -0
- package/dist/operations-D9otVlIH.js.map +1 -0
- package/dist/{ops-upIi6JIi.js → ops-D5xZr4fV.js} +60 -2
- package/dist/ops-D5xZr4fV.js.map +1 -0
- package/dist/{ops-djAsQQSh.cjs → ops-paa1Nvlf.cjs} +71 -1
- package/dist/ops-paa1Nvlf.cjs.map +1 -0
- package/dist/ranking/baselines/communicability.d.ts +12 -0
- package/dist/ranking/baselines/communicability.d.ts.map +1 -1
- package/dist/ranking/baselines/katz.d.ts +12 -0
- package/dist/ranking/baselines/katz.d.ts.map +1 -1
- package/dist/ranking/baselines/pagerank.d.ts +15 -0
- package/dist/ranking/baselines/pagerank.d.ts.map +1 -1
- package/dist/ranking/baselines/types.d.ts +3 -0
- package/dist/ranking/baselines/types.d.ts.map +1 -1
- package/dist/ranking/index.cjs +5 -2
- package/dist/ranking/index.js +3 -3
- package/dist/ranking/mi/index.cjs +1 -1
- package/dist/ranking/mi/index.js +1 -1
- package/dist/ranking/parse-gpu.d.ts +31 -0
- package/dist/ranking/parse-gpu.d.ts.map +1 -0
- package/dist/ranking/parse-gpu.unit.test.d.ts +5 -0
- package/dist/ranking/parse-gpu.unit.test.d.ts.map +1 -0
- package/dist/ranking/parse.d.ts.map +1 -1
- package/dist/{ranking-3ez5m67U.js → ranking-DOKDBcIR.js} +237 -11
- package/dist/ranking-DOKDBcIR.js.map +1 -0
- package/dist/{ranking-DVvajgUZ.cjs → ranking-pe5UaxKg.cjs} +254 -10
- package/dist/ranking-pe5UaxKg.cjs.map +1 -0
- package/dist/schemas/graph.d.ts +1 -1
- package/dist/seeds/crest.d.ts +48 -0
- package/dist/seeds/crest.d.ts.map +1 -0
- package/dist/seeds/crest.unit.test.d.ts +2 -0
- package/dist/seeds/crest.unit.test.d.ts.map +1 -0
- package/dist/seeds/crisp.d.ts +57 -0
- package/dist/seeds/crisp.d.ts.map +1 -0
- package/dist/seeds/crisp.unit.test.d.ts +2 -0
- package/dist/seeds/crisp.unit.test.d.ts.map +1 -0
- package/dist/seeds/grasp-gpu.d.ts +40 -0
- package/dist/seeds/grasp-gpu.d.ts.map +1 -0
- package/dist/seeds/index.cjs +715 -5
- package/dist/seeds/index.cjs.map +1 -1
- package/dist/seeds/index.d.ts +4 -0
- package/dist/seeds/index.d.ts.map +1 -1
- package/dist/seeds/index.js +712 -6
- package/dist/seeds/index.js.map +1 -1
- package/dist/seeds/spine.d.ts +50 -0
- package/dist/seeds/spine.d.ts.map +1 -0
- package/dist/seeds/spine.unit.test.d.ts +2 -0
- package/dist/seeds/spine.unit.test.d.ts.map +1 -0
- package/dist/seeds/stride.d.ts +55 -0
- package/dist/seeds/stride.d.ts.map +1 -0
- package/dist/seeds/stride.unit.test.d.ts +2 -0
- package/dist/seeds/stride.unit.test.d.ts.map +1 -0
- package/dist/{gpu-CHiCN0wa.js → typegpu-Dq5FfUB8.cjs} +16 -2041
- package/dist/typegpu-Dq5FfUB8.cjs.map +1 -0
- package/dist/{gpu-Y6owRVMi.cjs → typegpu-DwnJf28i.js} +2 -2127
- package/dist/typegpu-DwnJf28i.js.map +1 -0
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/dist/expansion-ClDhlMK8.js.map +0 -1
- package/dist/expansion-DaTroIyv.cjs.map +0 -1
- package/dist/gpu-CHiCN0wa.js.map +0 -1
- package/dist/gpu-Y6owRVMi.cjs.map +0 -1
- package/dist/ops-djAsQQSh.cjs.map +0 -1
- package/dist/ops-upIi6JIi.js.map +0 -1
- package/dist/ranking-3ez5m67U.js.map +0 -1
- package/dist/ranking-DVvajgUZ.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel-DukrXtVb.cjs","names":[],"sources":["../src/gpu/kernels/intersection/kernel.ts"],"sourcesContent":["/**\n * TypeGPU compute kernel for batch neighbourhood intersection.\n *\n * Computes intersection size and neighbourhood sizes for multiple node pairs.\n * One thread per pair: iterates the smaller neighbourhood and binary-searches the other.\n *\n * @module gpu/kernels/intersection/kernel\n */\n\nimport tgpu, { d, type StorageFlag, type TgpuBuffer } from \"typegpu\";\nimport type { TypedBufferGroup } from \"../../csr\";\nimport type { GraphwiseGPURoot } from \"../../root\";\n\n/**\n * Bind group layout for Intersection kernel.\n */\nconst IntersectionLayout = tgpu.bindGroupLayout({\n\trowOffsets: { storage: d.arrayOf(d.u32) },\n\tcolIndices: { storage: d.arrayOf(d.u32) },\n\tpairsU: { storage: d.arrayOf(d.u32) },\n\tpairsV: { storage: d.arrayOf(d.u32) },\n\tintersections: { storage: d.arrayOf(d.u32), access: \"mutable\" },\n\tsizeUs: { storage: d.arrayOf(d.u32), access: \"mutable\" },\n\tsizeVs: { storage: d.arrayOf(d.u32), access: \"mutable\" },\n\tpairCount: { uniform: d.u32 },\n});\n\n/**\n * Intersection compute pipeline: one thread per pair.\n *\n * For each pair (u, v):\n * - Count intersection by iterating smaller neighbourhood\n * - Binary search in larger neighbourhood\n * - Write intersection, sizeU, sizeV to output buffers\n */\nconst intersectionPipeline = (pairIdx: number): void => {\n\t\"use gpu\";\n\tconst u = IntersectionLayout.$.pairsU[pairIdx] ?? 0;\n\tconst v = IntersectionLayout.$.pairsV[pairIdx] ?? 0;\n\n\tconst uStart = IntersectionLayout.$.rowOffsets[u] ?? 0;\n\tconst uEnd = IntersectionLayout.$.rowOffsets[u + 1] ?? 0;\n\tconst vStart = IntersectionLayout.$.rowOffsets[v] ?? 0;\n\tconst vEnd = IntersectionLayout.$.rowOffsets[v + 1] ?? 0;\n\n\tconst degU = uEnd - uStart;\n\tconst degV = vEnd - vStart;\n\n\t// Write sizes first\n\tIntersectionLayout.$.sizeUs[pairIdx] = degU;\n\tIntersectionLayout.$.sizeVs[pairIdx] = degV;\n\n\t// Empty neighbourhoods → intersection = 0\n\tif (degU === 0 || degV === 0) {\n\t\tIntersectionLayout.$.intersections[pairIdx] = 0;\n\t\treturn;\n\t}\n\n\tlet intersection = 0;\n\n\tif (degU <= degV) {\n\t\t// Iterate u's neighbours, search in v's\n\t\tfor (let i = uStart; i < uEnd; i = i + 1) {\n\t\t\tconst neighbour = IntersectionLayout.$.colIndices[i] ?? 0;\n\t\t\t// Binary search in v's neighbourhood\n\t\t\tlet lo = vStart;\n\t\t\tlet hi = vEnd;\n\t\t\twhile (lo < hi) {\n\t\t\t\tconst mid = lo + (hi - lo) / 2;\n\t\t\t\tconst midVal = IntersectionLayout.$.colIndices[mid] ?? 0;\n\t\t\t\tif (midVal === neighbour) {\n\t\t\t\t\tintersection = intersection + 1;\n\t\t\t\t\tlo = hi; // break\n\t\t\t\t} else if (midVal < neighbour) {\n\t\t\t\t\tlo = mid + 1;\n\t\t\t\t} else {\n\t\t\t\t\thi = mid;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Iterate v's neighbours, search in u's\n\t\tfor (let i = vStart; i < vEnd; i = i + 1) {\n\t\t\tconst neighbour = IntersectionLayout.$.colIndices[i] ?? 0;\n\t\t\t// Binary search in u's neighbourhood\n\t\t\tlet lo = uStart;\n\t\t\tlet hi = uEnd;\n\t\t\twhile (lo < hi) {\n\t\t\t\tconst mid = lo + (hi - lo) / 2;\n\t\t\t\tconst midVal = IntersectionLayout.$.colIndices[mid] ?? 0;\n\t\t\t\tif (midVal === neighbour) {\n\t\t\t\t\tintersection = intersection + 1;\n\t\t\t\t\tlo = hi; // break\n\t\t\t\t} else if (midVal < neighbour) {\n\t\t\t\t\tlo = mid + 1;\n\t\t\t\t} else {\n\t\t\t\t\thi = mid;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tIntersectionLayout.$.intersections[pairIdx] = intersection;\n};\n\n/**\n * Dispatch batch intersection on GPU.\n *\n * @param root - TypeGPU root instance\n * @param csrBuffers - CSR matrix as typed buffers\n * @param pairsU - First node of each pair (u32 array)\n * @param pairsV - Second node of each pair (u32 array)\n * @param intersections - Output intersections (u32 array, mutable)\n * @param sizeUs - Output sizeU per pair (u32 array, mutable)\n * @param sizeVs - Output sizeV per pair (u32 array, mutable)\n * @param pairCount - Number of pairs to compute\n */\nexport function dispatchIntersection(\n\troot: GraphwiseGPURoot,\n\tcsrBuffers: TypedBufferGroup,\n\tpairsU: TgpuBuffer<ReturnType<typeof d.arrayOf<typeof d.u32>>> & StorageFlag,\n\tpairsV: TgpuBuffer<ReturnType<typeof d.arrayOf<typeof d.u32>>> & StorageFlag,\n\tintersections: TgpuBuffer<ReturnType<typeof d.arrayOf<typeof d.u32>>> &\n\t\tStorageFlag,\n\tsizeUs: TgpuBuffer<ReturnType<typeof d.arrayOf<typeof d.u32>>> & StorageFlag,\n\tsizeVs: TgpuBuffer<ReturnType<typeof d.arrayOf<typeof d.u32>>> & StorageFlag,\n\tpairCount: number,\n): void {\n\tconst pipeline = root.createGuardedComputePipeline(intersectionPipeline);\n\n\tconst pairCountBuffer = root.createBuffer(d.u32, pairCount).$usage(\"uniform\");\n\n\tconst bindGroup = root.createBindGroup(IntersectionLayout, {\n\t\trowOffsets: csrBuffers.rowOffsets,\n\t\tcolIndices: csrBuffers.colIndices,\n\t\tpairsU,\n\t\tpairsV,\n\t\tintersections,\n\t\tsizeUs,\n\t\tsizeVs,\n\t\tpairCount: pairCountBuffer,\n\t});\n\n\tpipeline.with(bindGroup).dispatchThreads(pairCount);\n}\n\nexport { IntersectionLayout };\n"],"mappings":";;;;;;;;;;;;;AAgBA,IAAM,sBAAA,WAAA,0BAAA,MAAA,IAAqB,gBAAA,YAAK,gBAAgB;CAC/C,YAAY,EAAE,SAAS,gBAAA,aAAE,QAAQ,gBAAA,aAAE,IAAI,EAAE;CACzC,YAAY,EAAE,SAAS,gBAAA,aAAE,QAAQ,gBAAA,aAAE,IAAI,EAAE;CACzC,QAAQ,EAAE,SAAS,gBAAA,aAAE,QAAQ,gBAAA,aAAE,IAAI,EAAE;CACrC,QAAQ,EAAE,SAAS,gBAAA,aAAE,QAAQ,gBAAA,aAAE,IAAI,EAAE;CACrC,eAAe;EAAE,SAAS,gBAAA,aAAE,QAAQ,gBAAA,aAAE,IAAI;EAAE,QAAQ;EAAW;CAC/D,QAAQ;EAAE,SAAS,gBAAA,aAAE,QAAQ,gBAAA,aAAE,IAAI;EAAE,QAAQ;EAAW;CACxD,QAAQ;EAAE,SAAS,gBAAA,aAAE,QAAQ,gBAAA,aAAE,IAAI;EAAE,QAAQ;EAAW;CACxD,WAAW,EAAE,SAAS,gBAAA,aAAE,KAAA;CACxB,CAAA,EAAA,qBAAA;;;;;;;;;AAUD,IAAM,yBAAA,OAAA,WAAA,qCAAA,IAAA,SAAA,EAAA,IAAA,EAAA,MAAwB,YAA0B;AACvD;CACA,MAAM,IAAI,mBAAmB,EAAE,OAAO,YAAY;CAClD,MAAM,IAAI,mBAAmB,EAAE,OAAO,YAAY;CAElD,MAAM,SAAS,mBAAmB,EAAE,WAAW,MAAM;CACrD,MAAM,OAAO,mBAAmB,EAAE,WAAW,aAAA,GAAA,EAAI,KAAM;CACvD,MAAM,SAAS,mBAAmB,EAAE,WAAW,MAAM;CACrD,MAAM,OAAO,mBAAmB,EAAE,WAAW,aAAA,GAAA,EAAI,KAAM;CAEvD,MAAM,OAAO,aAAA,MAAA,OAAO;CACpB,MAAM,OAAO,aAAA,MAAA,OAAO;AAGpB,oBAAmB,EAAE,OAAO,WAAW;AACvC,oBAAmB,EAAE,OAAO,WAAW;AAGvC,KAAI,SAAS,KAAK,SAAS,GAAG;AAC7B,qBAAmB,EAAE,cAAc,WAAW;AAC9C;;CAGD,IAAI,eAAe;AAEnB,KAAI,QAAQ,KAEX,MAAK,IAAI,IAAI,QAAQ,IAAI,MAAM,IAAI,aAAA,GAAA,EAAI,EAAG;EACzC,MAAM,YAAY,mBAAmB,EAAE,WAAW,MAAM;EAExD,IAAI,KAAK;EACT,IAAI,KAAK;AACT,SAAO,KAAK,IAAI;GACf,MAAM,MAAM,aAAA,IAAA,aAAA,aAAA,IAAA,GAAA,EAAA,EAAA,CAAiB;GAC7B,MAAM,SAAS,mBAAmB,EAAE,WAAW,QAAQ;AACvD,OAAI,WAAW,WAAW;AACzB,mBAAe,aAAA,cAAA,EAAe;AAC9B,SAAK;cACK,SAAS,UACnB,MAAK,aAAA,KAAA,EAAM;OAEX,MAAK;;;KAMR,MAAK,IAAI,IAAI,QAAQ,IAAI,MAAM,IAAI,aAAA,GAAA,EAAI,EAAG;EACzC,MAAM,YAAY,mBAAmB,EAAE,WAAW,MAAM;EAExD,IAAI,KAAK;EACT,IAAI,KAAK;AACT,SAAO,KAAK,IAAI;GACf,MAAM,MAAM,aAAA,IAAA,aAAA,aAAA,IAAA,GAAA,EAAA,EAAA,CAAiB;GAC7B,MAAM,SAAS,mBAAmB,EAAE,WAAW,QAAQ;AACvD,OAAI,WAAW,WAAW;AACzB,mBAAe,aAAA,cAAA,EAAe;AAC9B,SAAK;cACK,SAAS,UACnB,MAAK,aAAA,KAAA,EAAM;OAEX,MAAK;;;AAMT,oBAAmB,EAAE,cAAc,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAe/C,SAAgB,qBACf,MACA,YACA,QACA,QACA,eAEA,QACA,QACA,WACO;CACP,MAAM,YAAA,WAAA,0BAAA,MAAA,IAAW,KAAK,6BAA6B,qBAAA,EAAA,WAAA;CAEnD,MAAM,mBAAA,WAAA,0BAAA,MAAA,IAAkB,KAAK,aAAa,gBAAA,aAAE,KAAK,UAAU,CAAC,OAAO,UAAA,EAAA,kBAAA;CAEnE,MAAM,YAAY,KAAK,gBAAgB,oBAAoB;EAC1D,YAAY,WAAW;EACvB,YAAY,WAAW;EACvB;EACA;EACA;EACA;EACA;EACA,WAAW;EACX,CAAC;AAEF,UAAS,KAAK,UAAU,CAAC,gBAAgB,UAAU"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kmeans-B8x9D1kt.cjs","names":[],"sources":["../src/utils/kmeans.ts"],"sourcesContent":["/**\n * Minimal K-means clustering implementation for GRASP seed selection.\n *\n * This is a lightweight implementation specifically designed for 3D feature vectors\n * used in structural seed selection. For general-purpose clustering, consider\n * using a dedicated library.\n *\n * @packageDocumentation\n */\n\n/**\n * A 3D feature vector representing node structural properties.\n */\nexport interface FeatureVector3D {\n\t/** First dimension (e.g., log-degree) */\n\treadonly f1: number;\n\t/** Second dimension (e.g., clustering coefficient) */\n\treadonly f2: number;\n\t/** Third dimension (e.g., approximate PageRank) */\n\treadonly f3: number;\n}\n\n/**\n * A labelled feature vector with associated node ID.\n */\nexport interface LabelledFeature extends FeatureVector3D {\n\t/** Node identifier */\n\treadonly nodeId: string;\n}\n\n/**\n * Result of K-means clustering.\n */\nexport interface KMeansResult {\n\t/** Cluster centroids */\n\treadonly centroids: readonly FeatureVector3D[];\n\t/** Cluster assignments: nodeId -> cluster index */\n\treadonly assignments: ReadonlyMap<string, number>;\n\t/** Number of clusters */\n\treadonly k: number;\n}\n\n/**\n * Options for K-means clustering.\n */\nexport interface KMeansOptions {\n\t/** Number of clusters */\n\treadonly k: number;\n\t/** Maximum iterations (default: 100) */\n\treadonly maxIterations?: number;\n\t/** Convergence threshold (default: 1e-6) */\n\treadonly tolerance?: number;\n\t/** Random seed for reproducibility */\n\treadonly seed?: number;\n}\n\n/** Small epsilon to prevent division by zero */\nconst EPSILON = 1e-10;\n\n/**\n * Simple seeded pseudo-random number generator using mulberry32.\n */\nfunction createRNG(seed: number): () => number {\n\tlet state = seed >>> 0;\n\treturn (): number => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = Math.imul(state ^ (state >>> 15), state | 1);\n\t\tt = (t ^ (t >>> 7)) * (t | 0x61c88647);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * Compute Euclidean distance between two 3D feature vectors.\n */\nfunction euclideanDistance(a: FeatureVector3D, b: FeatureVector3D): number {\n\tconst d1 = a.f1 - b.f1;\n\tconst d2 = a.f2 - b.f2;\n\tconst d3 = a.f3 - b.f3;\n\treturn Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);\n}\n\n/**\n * Compute the mean of a set of feature vectors.\n * @internal - Used for testing\n */\nexport function _computeMean(\n\tvectors: readonly FeatureVector3D[],\n): FeatureVector3D {\n\tif (vectors.length === 0) {\n\t\treturn { f1: 0, f2: 0, f3: 0 };\n\t}\n\tlet sum1 = 0;\n\tlet sum2 = 0;\n\tlet sum3 = 0;\n\tfor (const v of vectors) {\n\t\tsum1 += v.f1;\n\t\tsum2 += v.f2;\n\t\tsum3 += v.f3;\n\t}\n\tconst n = vectors.length;\n\treturn { f1: sum1 / n, f2: sum2 / n, f3: sum3 / n };\n}\n\n/**\n * Z-score normalise features (zero mean, unit variance).\n */\nexport { normaliseFeatures as zScoreNormalise };\n\nexport function normaliseFeatures(\n\tfeatures: readonly LabelledFeature[],\n): LabelledFeature[] {\n\tif (features.length === 0) {\n\t\treturn [];\n\t}\n\n\t// Compute means\n\tlet sum1 = 0;\n\tlet sum2 = 0;\n\tlet sum3 = 0;\n\tfor (const f of features) {\n\t\tsum1 += f.f1;\n\t\tsum2 += f.f2;\n\t\tsum3 += f.f3;\n\t}\n\tconst n = features.length;\n\tconst mean1 = sum1 / n;\n\tconst mean2 = sum2 / n;\n\tconst mean3 = sum3 / n;\n\n\t// Compute standard deviations\n\tlet var1 = 0;\n\tlet var2 = 0;\n\tlet var3 = 0;\n\tfor (const f of features) {\n\t\tvar1 += (f.f1 - mean1) ** 2;\n\t\tvar2 += (f.f2 - mean2) ** 2;\n\t\tvar3 += (f.f3 - mean3) ** 2;\n\t}\n\tconst std1 = Math.sqrt(var1 / n + EPSILON);\n\tconst std2 = Math.sqrt(var2 / n + EPSILON);\n\tconst std3 = Math.sqrt(var3 / n + EPSILON);\n\n\t// Normalise\n\treturn features.map(\n\t\t(f): LabelledFeature => ({\n\t\t\tnodeId: f.nodeId,\n\t\t\tf1: (f.f1 - mean1) / std1,\n\t\t\tf2: (f.f2 - mean2) / std2,\n\t\t\tf3: (f.f3 - mean3) / std3,\n\t\t}),\n\t);\n}\n\n/**\n * Mini-batch K-means clustering for 3D feature vectors.\n *\n * Uses Mini-batch K-means for efficiency with large datasets.\n * This is specifically designed for the GRASP seed selection algorithm.\n *\n * @param features - Array of labelled feature vectors\n * @param options - Clustering options\n * @returns Clustering result with centroids and assignments\n */\nexport function miniBatchKMeans(\n\tfeatures: readonly LabelledFeature[],\n\toptions: KMeansOptions,\n): KMeansResult {\n\tconst { k, maxIterations = 100, tolerance = 1e-6, seed = 42 } = options;\n\n\tif (features.length === 0) {\n\t\treturn {\n\t\t\tcentroids: [],\n\t\t\tassignments: new Map(),\n\t\t\tk,\n\t\t};\n\t}\n\n\tconst rng = createRNG(seed);\n\tconst n = features.length;\n\tconst effectiveK = Math.min(k, n);\n\n\t// Initialise centroids using k-means++ seeding\n\tconst centroids: FeatureVector3D[] = initialiseCentroidsKMeansPP(\n\t\tfeatures,\n\t\teffectiveK,\n\t\trng,\n\t);\n\n\t// Assignments map\n\tconst assignments = new Map<string, number>();\n\n\t// Mini-batch size (10% of data or at least 10)\n\tconst batchSize = Math.max(10, Math.floor(n / 10));\n\n\tfor (let iter = 0; iter < maxIterations; iter++) {\n\t\t// Sample mini-batch\n\t\tconst batchIndices = new Set<number>();\n\t\twhile (batchIndices.size < Math.min(batchSize, n)) {\n\t\t\tbatchIndices.add(Math.floor(rng() * n));\n\t\t}\n\n\t\t// Assign batch points to nearest centroid\n\t\tconst batchPoints: { feature: LabelledFeature; cluster: number }[] = [];\n\t\tfor (const idx of batchIndices) {\n\t\t\tconst feature = features[idx];\n\t\t\tif (feature === undefined) continue;\n\t\t\tlet minDist = Infinity;\n\t\t\tlet bestCluster = 0;\n\t\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\t\tconst centroid = centroids[c];\n\t\t\t\tif (centroid === undefined) continue;\n\t\t\t\tconst dist = euclideanDistance(feature, centroid);\n\t\t\t\tif (dist < minDist) {\n\t\t\t\t\tminDist = dist;\n\t\t\t\t\tbestCluster = c;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbatchPoints.push({ feature, cluster: bestCluster });\n\t\t}\n\n\t\t// Update centroids based on batch\n\t\tconst oldCentroids = centroids.map((c) => ({ ...c }));\n\n\t\t// Compute per-cluster counts and sums from batch\n\t\tconst clusterCounts = Array.from({ length: centroids.length }, () => 0);\n\t\tconst clusterSums: [number, number, number][] = Array.from(\n\t\t\t{ length: centroids.length },\n\t\t\t(): [number, number, number] => [0, 0, 0],\n\t\t);\n\n\t\tfor (const { feature, cluster } of batchPoints) {\n\t\t\tconst currentCount = clusterCounts[cluster];\n\t\t\tif (currentCount !== undefined) {\n\t\t\t\tclusterCounts[cluster] = currentCount + 1;\n\t\t\t}\n\t\t\tconst sum = clusterSums[cluster];\n\t\t\tif (sum !== undefined) {\n\t\t\t\tsum[0] += feature.f1;\n\t\t\t\tsum[1] += feature.f2;\n\t\t\t\tsum[2] += feature.f3;\n\t\t\t}\n\t\t}\n\n\t\t// Update centroids\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst count = clusterCounts[c] ?? 0;\n\t\t\tif (count > 0) {\n\t\t\t\tconst sum = clusterSums[c];\n\t\t\t\tif (sum !== undefined) {\n\t\t\t\t\tcentroids[c] = {\n\t\t\t\t\t\tf1: sum[0] / count,\n\t\t\t\t\t\tf2: sum[1] / count,\n\t\t\t\t\t\tf3: sum[2] / count,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check convergence\n\t\tlet maxShift = 0;\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst newCentroid = centroids[c];\n\t\t\tconst oldCentroid = oldCentroids[c];\n\t\t\tif (newCentroid !== undefined && oldCentroid !== undefined) {\n\t\t\t\tconst shift = euclideanDistance(newCentroid, oldCentroid);\n\t\t\t\tmaxShift = Math.max(maxShift, shift);\n\t\t\t}\n\t\t}\n\n\t\tif (maxShift < tolerance) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Final assignment of all points\n\tfor (const feature of features) {\n\t\tlet minDist = Infinity;\n\t\tlet bestCluster = 0;\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst centroid = centroids[c];\n\t\t\tif (centroid === undefined) continue;\n\t\t\tconst dist = euclideanDistance(feature, centroid);\n\t\t\tif (dist < minDist) {\n\t\t\t\tminDist = dist;\n\t\t\t\tbestCluster = c;\n\t\t\t}\n\t\t}\n\t\tassignments.set(feature.nodeId, bestCluster);\n\t}\n\n\treturn {\n\t\tcentroids,\n\t\tassignments,\n\t\tk: effectiveK,\n\t};\n}\n\n/**\n * K-means++ initialisation for better centroid seeding.\n */\nfunction initialiseCentroidsKMeansPP(\n\tfeatures: readonly LabelledFeature[],\n\tk: number,\n\trng: () => number,\n): FeatureVector3D[] {\n\tconst centroids: FeatureVector3D[] = [];\n\tconst n = features.length;\n\n\t// Choose first centroid randomly\n\tconst firstIdx = Math.floor(rng() * n);\n\tconst firstFeature = features[firstIdx];\n\tif (firstFeature === undefined) {\n\t\treturn [{ f1: 0, f2: 0, f3: 0 }];\n\t}\n\tcentroids.push({\n\t\tf1: firstFeature.f1,\n\t\tf2: firstFeature.f2,\n\t\tf3: firstFeature.f3,\n\t});\n\n\t// Choose remaining centroids with probability proportional to squared distance\n\tconst distances = Array.from({ length: n }, () => Infinity);\n\n\tfor (let c = 1; c < k; c++) {\n\t\t// Update distances to nearest centroid\n\t\tlet totalDistSq = 0;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst feature = features[i];\n\t\t\tif (feature === undefined) continue;\n\t\t\tconst lastCentroid = centroids[c - 1];\n\t\t\tif (lastCentroid === undefined) continue;\n\t\t\tconst dist = euclideanDistance(feature, lastCentroid);\n\t\t\tconst currentMin = distances[i];\n\t\t\tif (currentMin !== undefined && dist < currentMin) {\n\t\t\t\tdistances[i] = dist;\n\t\t\t}\n\t\t\tconst d = distances[i];\n\t\t\tif (d !== undefined) {\n\t\t\t\ttotalDistSq += d * d;\n\t\t\t}\n\t\t}\n\n\t\t// Choose next centroid with probability proportional to squared distance\n\t\tconst threshold = rng() * totalDistSq;\n\t\tlet cumulative = 0;\n\t\tlet nextIdx = 0;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst d = distances[i];\n\t\t\tif (d !== undefined) {\n\t\t\t\tcumulative += d * d;\n\t\t\t\tif (cumulative >= threshold) {\n\t\t\t\t\tnextIdx = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst nextFeature = features[nextIdx];\n\t\tif (nextFeature !== undefined) {\n\t\t\tcentroids.push({\n\t\t\t\tf1: nextFeature.f1,\n\t\t\t\tf2: nextFeature.f2,\n\t\t\t\tf3: nextFeature.f3,\n\t\t\t});\n\t\t} else {\n\t\t\t// Fallback: use zero centroid\n\t\t\tcentroids.push({ f1: 0, f2: 0, f3: 0 });\n\t\t}\n\t}\n\n\treturn centroids;\n}\n"],"mappings":";;AAyDA,IAAM,UAAU;;;;AAKhB,SAAS,UAAU,MAA4B;CAC9C,IAAI,QAAQ,SAAS;AACrB,cAAqB;AACpB,UAAS,QAAQ,eAAgB;EACjC,IAAI,IAAI,KAAK,KAAK,QAAS,UAAU,IAAK,QAAQ,EAAE;AACpD,OAAK,IAAK,MAAM,MAAO,IAAI;AAC3B,WAAS,IAAK,MAAM,QAAS,KAAK;;;;;;AAOpC,SAAS,kBAAkB,GAAoB,GAA4B;CAC1E,MAAM,KAAK,EAAE,KAAK,EAAE;CACpB,MAAM,KAAK,EAAE,KAAK,EAAE;CACpB,MAAM,KAAK,EAAE,KAAK,EAAE;AACpB,QAAO,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;;;;;;AAO9C,SAAgB,aACf,SACkB;AAClB,KAAI,QAAQ,WAAW,EACtB,QAAO;EAAE,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG;CAE/B,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;AACX,MAAK,MAAM,KAAK,SAAS;AACxB,UAAQ,EAAE;AACV,UAAQ,EAAE;AACV,UAAQ,EAAE;;CAEX,MAAM,IAAI,QAAQ;AAClB,QAAO;EAAE,IAAI,OAAO;EAAG,IAAI,OAAO;EAAG,IAAI,OAAO;EAAG;;AAQpD,SAAgB,kBACf,UACoB;AACpB,KAAI,SAAS,WAAW,EACvB,QAAO,EAAE;CAIV,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;AACX,MAAK,MAAM,KAAK,UAAU;AACzB,UAAQ,EAAE;AACV,UAAQ,EAAE;AACV,UAAQ,EAAE;;CAEX,MAAM,IAAI,SAAS;CACnB,MAAM,QAAQ,OAAO;CACrB,MAAM,QAAQ,OAAO;CACrB,MAAM,QAAQ,OAAO;CAGrB,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;AACX,MAAK,MAAM,KAAK,UAAU;AACzB,WAAS,EAAE,KAAK,UAAU;AAC1B,WAAS,EAAE,KAAK,UAAU;AAC1B,WAAS,EAAE,KAAK,UAAU;;CAE3B,MAAM,OAAO,KAAK,KAAK,OAAO,IAAI,QAAQ;CAC1C,MAAM,OAAO,KAAK,KAAK,OAAO,IAAI,QAAQ;CAC1C,MAAM,OAAO,KAAK,KAAK,OAAO,IAAI,QAAQ;AAG1C,QAAO,SAAS,KACd,OAAwB;EACxB,QAAQ,EAAE;EACV,KAAK,EAAE,KAAK,SAAS;EACrB,KAAK,EAAE,KAAK,SAAS;EACrB,KAAK,EAAE,KAAK,SAAS;EACrB,EACD;;;;;;;;;;;;AAaF,SAAgB,gBACf,UACA,SACe;CACf,MAAM,EAAE,GAAG,gBAAgB,KAAK,YAAY,MAAM,OAAO,OAAO;AAEhE,KAAI,SAAS,WAAW,EACvB,QAAO;EACN,WAAW,EAAE;EACb,6BAAa,IAAI,KAAK;EACtB;EACA;CAGF,MAAM,MAAM,UAAU,KAAK;CAC3B,MAAM,IAAI,SAAS;CACnB,MAAM,aAAa,KAAK,IAAI,GAAG,EAAE;CAGjC,MAAM,YAA+B,4BACpC,UACA,YACA,IACA;CAGD,MAAM,8BAAc,IAAI,KAAqB;CAG7C,MAAM,YAAY,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC;AAElD,MAAK,IAAI,OAAO,GAAG,OAAO,eAAe,QAAQ;EAEhD,MAAM,+BAAe,IAAI,KAAa;AACtC,SAAO,aAAa,OAAO,KAAK,IAAI,WAAW,EAAE,CAChD,cAAa,IAAI,KAAK,MAAM,KAAK,GAAG,EAAE,CAAC;EAIxC,MAAM,cAA+D,EAAE;AACvE,OAAK,MAAM,OAAO,cAAc;GAC/B,MAAM,UAAU,SAAS;AACzB,OAAI,YAAY,KAAA,EAAW;GAC3B,IAAI,UAAU;GACd,IAAI,cAAc;AAClB,QAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;IAC1C,MAAM,WAAW,UAAU;AAC3B,QAAI,aAAa,KAAA,EAAW;IAC5B,MAAM,OAAO,kBAAkB,SAAS,SAAS;AACjD,QAAI,OAAO,SAAS;AACnB,eAAU;AACV,mBAAc;;;AAGhB,eAAY,KAAK;IAAE;IAAS,SAAS;IAAa,CAAC;;EAIpD,MAAM,eAAe,UAAU,KAAK,OAAO,EAAE,GAAG,GAAG,EAAE;EAGrD,MAAM,gBAAgB,MAAM,KAAK,EAAE,QAAQ,UAAU,QAAQ,QAAQ,EAAE;EACvE,MAAM,cAA0C,MAAM,KACrD,EAAE,QAAQ,UAAU,QAAQ,QACI;GAAC;GAAG;GAAG;GAAE,CACzC;AAED,OAAK,MAAM,EAAE,SAAS,aAAa,aAAa;GAC/C,MAAM,eAAe,cAAc;AACnC,OAAI,iBAAiB,KAAA,EACpB,eAAc,WAAW,eAAe;GAEzC,MAAM,MAAM,YAAY;AACxB,OAAI,QAAQ,KAAA,GAAW;AACtB,QAAI,MAAM,QAAQ;AAClB,QAAI,MAAM,QAAQ;AAClB,QAAI,MAAM,QAAQ;;;AAKpB,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;GAC1C,MAAM,QAAQ,cAAc,MAAM;AAClC,OAAI,QAAQ,GAAG;IACd,MAAM,MAAM,YAAY;AACxB,QAAI,QAAQ,KAAA,EACX,WAAU,KAAK;KACd,IAAI,IAAI,KAAK;KACb,IAAI,IAAI,KAAK;KACb,IAAI,IAAI,KAAK;KACb;;;EAMJ,IAAI,WAAW;AACf,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;GAC1C,MAAM,cAAc,UAAU;GAC9B,MAAM,cAAc,aAAa;AACjC,OAAI,gBAAgB,KAAA,KAAa,gBAAgB,KAAA,GAAW;IAC3D,MAAM,QAAQ,kBAAkB,aAAa,YAAY;AACzD,eAAW,KAAK,IAAI,UAAU,MAAM;;;AAItC,MAAI,WAAW,UACd;;AAKF,MAAK,MAAM,WAAW,UAAU;EAC/B,IAAI,UAAU;EACd,IAAI,cAAc;AAClB,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;GAC1C,MAAM,WAAW,UAAU;AAC3B,OAAI,aAAa,KAAA,EAAW;GAC5B,MAAM,OAAO,kBAAkB,SAAS,SAAS;AACjD,OAAI,OAAO,SAAS;AACnB,cAAU;AACV,kBAAc;;;AAGhB,cAAY,IAAI,QAAQ,QAAQ,YAAY;;AAG7C,QAAO;EACN;EACA;EACA,GAAG;EACH;;;;;AAMF,SAAS,4BACR,UACA,GACA,KACoB;CACpB,MAAM,YAA+B,EAAE;CACvC,MAAM,IAAI,SAAS;CAInB,MAAM,eAAe,SADJ,KAAK,MAAM,KAAK,GAAG,EAAE;AAEtC,KAAI,iBAAiB,KAAA,EACpB,QAAO,CAAC;EAAE,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG,CAAC;AAEjC,WAAU,KAAK;EACd,IAAI,aAAa;EACjB,IAAI,aAAa;EACjB,IAAI,aAAa;EACjB,CAAC;CAGF,MAAM,YAAY,MAAM,KAAK,EAAE,QAAQ,GAAG,QAAQ,SAAS;AAE3D,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAE3B,IAAI,cAAc;AAClB,OAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;GAC3B,MAAM,UAAU,SAAS;AACzB,OAAI,YAAY,KAAA,EAAW;GAC3B,MAAM,eAAe,UAAU,IAAI;AACnC,OAAI,iBAAiB,KAAA,EAAW;GAChC,MAAM,OAAO,kBAAkB,SAAS,aAAa;GACrD,MAAM,aAAa,UAAU;AAC7B,OAAI,eAAe,KAAA,KAAa,OAAO,WACtC,WAAU,KAAK;GAEhB,MAAM,IAAI,UAAU;AACpB,OAAI,MAAM,KAAA,EACT,gBAAe,IAAI;;EAKrB,MAAM,YAAY,KAAK,GAAG;EAC1B,IAAI,aAAa;EACjB,IAAI,UAAU;AACd,OAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;GAC3B,MAAM,IAAI,UAAU;AACpB,OAAI,MAAM,KAAA,GAAW;AACpB,kBAAc,IAAI;AAClB,QAAI,cAAc,WAAW;AAC5B,eAAU;AACV;;;;EAKH,MAAM,cAAc,SAAS;AAC7B,MAAI,gBAAgB,KAAA,EACnB,WAAU,KAAK;GACd,IAAI,YAAY;GAChB,IAAI,YAAY;GAChB,IAAI,YAAY;GAChB,CAAC;MAGF,WAAU,KAAK;GAAE,IAAI;GAAG,IAAI;GAAG,IAAI;GAAG,CAAC;;AAIzC,QAAO"}
|
|
1
|
+
{"version":3,"file":"kmeans-CZ7tJFYw.cjs","names":[],"sources":["../src/utils/kmeans.ts"],"sourcesContent":["/**\n * Minimal K-means clustering implementation for GRASP seed selection.\n *\n * This is a lightweight implementation specifically designed for 3D feature vectors\n * used in structural seed selection. For general-purpose clustering, consider\n * using a dedicated library.\n *\n * @packageDocumentation\n */\n\n/**\n * A 3D feature vector representing node structural properties.\n */\nexport interface FeatureVector3D {\n\t/** First dimension (e.g., log-degree) */\n\treadonly f1: number;\n\t/** Second dimension (e.g., clustering coefficient) */\n\treadonly f2: number;\n\t/** Third dimension (e.g., approximate PageRank) */\n\treadonly f3: number;\n}\n\n/**\n * A labelled feature vector with associated node ID.\n */\nexport interface LabelledFeature extends FeatureVector3D {\n\t/** Node identifier */\n\treadonly nodeId: string;\n}\n\n/**\n * Result of K-means clustering.\n */\nexport interface KMeansResult {\n\t/** Cluster centroids */\n\treadonly centroids: readonly FeatureVector3D[];\n\t/** Cluster assignments: nodeId -> cluster index */\n\treadonly assignments: ReadonlyMap<string, number>;\n\t/** Number of clusters */\n\treadonly k: number;\n}\n\n/**\n * Options for K-means clustering.\n */\nexport interface KMeansOptions {\n\t/** Number of clusters */\n\treadonly k: number;\n\t/** Maximum iterations (default: 100) */\n\treadonly maxIterations?: number;\n\t/** Convergence threshold (default: 1e-6) */\n\treadonly tolerance?: number;\n\t/** Random seed for reproducibility */\n\treadonly seed?: number;\n}\n\n/** Small epsilon to prevent division by zero */\nconst EPSILON = 1e-10;\n\n/**\n * Simple seeded pseudo-random number generator using mulberry32.\n */\nfunction createRNG(seed: number): () => number {\n\tlet state = seed >>> 0;\n\treturn (): number => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = Math.imul(state ^ (state >>> 15), state | 1);\n\t\tt = (t ^ (t >>> 7)) * (t | 0x61c88647);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * Compute Euclidean distance between two 3D feature vectors.\n */\nfunction euclideanDistance(a: FeatureVector3D, b: FeatureVector3D): number {\n\tconst d1 = a.f1 - b.f1;\n\tconst d2 = a.f2 - b.f2;\n\tconst d3 = a.f3 - b.f3;\n\treturn Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);\n}\n\n/**\n * Compute the mean of a set of feature vectors.\n * @internal - Used for testing\n */\nexport function _computeMean(\n\tvectors: readonly FeatureVector3D[],\n): FeatureVector3D {\n\tif (vectors.length === 0) {\n\t\treturn { f1: 0, f2: 0, f3: 0 };\n\t}\n\tlet sum1 = 0;\n\tlet sum2 = 0;\n\tlet sum3 = 0;\n\tfor (const v of vectors) {\n\t\tsum1 += v.f1;\n\t\tsum2 += v.f2;\n\t\tsum3 += v.f3;\n\t}\n\tconst n = vectors.length;\n\treturn { f1: sum1 / n, f2: sum2 / n, f3: sum3 / n };\n}\n\n/**\n * Z-score normalise features (zero mean, unit variance).\n */\nexport { normaliseFeatures as zScoreNormalise };\n\nexport function normaliseFeatures(\n\tfeatures: readonly LabelledFeature[],\n): LabelledFeature[] {\n\tif (features.length === 0) {\n\t\treturn [];\n\t}\n\n\t// Compute means\n\tlet sum1 = 0;\n\tlet sum2 = 0;\n\tlet sum3 = 0;\n\tfor (const f of features) {\n\t\tsum1 += f.f1;\n\t\tsum2 += f.f2;\n\t\tsum3 += f.f3;\n\t}\n\tconst n = features.length;\n\tconst mean1 = sum1 / n;\n\tconst mean2 = sum2 / n;\n\tconst mean3 = sum3 / n;\n\n\t// Compute standard deviations\n\tlet var1 = 0;\n\tlet var2 = 0;\n\tlet var3 = 0;\n\tfor (const f of features) {\n\t\tvar1 += (f.f1 - mean1) ** 2;\n\t\tvar2 += (f.f2 - mean2) ** 2;\n\t\tvar3 += (f.f3 - mean3) ** 2;\n\t}\n\tconst std1 = Math.sqrt(var1 / n + EPSILON);\n\tconst std2 = Math.sqrt(var2 / n + EPSILON);\n\tconst std3 = Math.sqrt(var3 / n + EPSILON);\n\n\t// Normalise\n\treturn features.map(\n\t\t(f): LabelledFeature => ({\n\t\t\tnodeId: f.nodeId,\n\t\t\tf1: (f.f1 - mean1) / std1,\n\t\t\tf2: (f.f2 - mean2) / std2,\n\t\t\tf3: (f.f3 - mean3) / std3,\n\t\t}),\n\t);\n}\n\n/**\n * Mini-batch K-means clustering for 3D feature vectors.\n *\n * Uses Mini-batch K-means for efficiency with large datasets.\n * This is specifically designed for the GRASP seed selection algorithm.\n *\n * @param features - Array of labelled feature vectors\n * @param options - Clustering options\n * @returns Clustering result with centroids and assignments\n */\nexport function miniBatchKMeans(\n\tfeatures: readonly LabelledFeature[],\n\toptions: KMeansOptions,\n): KMeansResult {\n\tconst { k, maxIterations = 100, tolerance = 1e-6, seed = 42 } = options;\n\n\tif (features.length === 0) {\n\t\treturn {\n\t\t\tcentroids: [],\n\t\t\tassignments: new Map(),\n\t\t\tk,\n\t\t};\n\t}\n\n\tconst rng = createRNG(seed);\n\tconst n = features.length;\n\tconst effectiveK = Math.min(k, n);\n\n\t// Initialise centroids using k-means++ seeding\n\tconst centroids: FeatureVector3D[] = initialiseCentroidsKMeansPP(\n\t\tfeatures,\n\t\teffectiveK,\n\t\trng,\n\t);\n\n\t// Assignments map\n\tconst assignments = new Map<string, number>();\n\n\t// Mini-batch size (10% of data or at least 10)\n\tconst batchSize = Math.max(10, Math.floor(n / 10));\n\n\tfor (let iter = 0; iter < maxIterations; iter++) {\n\t\t// Sample mini-batch\n\t\tconst batchIndices = new Set<number>();\n\t\twhile (batchIndices.size < Math.min(batchSize, n)) {\n\t\t\tbatchIndices.add(Math.floor(rng() * n));\n\t\t}\n\n\t\t// Assign batch points to nearest centroid\n\t\tconst batchPoints: { feature: LabelledFeature; cluster: number }[] = [];\n\t\tfor (const idx of batchIndices) {\n\t\t\tconst feature = features[idx];\n\t\t\tif (feature === undefined) continue;\n\t\t\tlet minDist = Infinity;\n\t\t\tlet bestCluster = 0;\n\t\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\t\tconst centroid = centroids[c];\n\t\t\t\tif (centroid === undefined) continue;\n\t\t\t\tconst dist = euclideanDistance(feature, centroid);\n\t\t\t\tif (dist < minDist) {\n\t\t\t\t\tminDist = dist;\n\t\t\t\t\tbestCluster = c;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbatchPoints.push({ feature, cluster: bestCluster });\n\t\t}\n\n\t\t// Update centroids based on batch\n\t\tconst oldCentroids = centroids.map((c) => ({ ...c }));\n\n\t\t// Compute per-cluster counts and sums from batch\n\t\tconst clusterCounts = Array.from({ length: centroids.length }, () => 0);\n\t\tconst clusterSums: [number, number, number][] = Array.from(\n\t\t\t{ length: centroids.length },\n\t\t\t(): [number, number, number] => [0, 0, 0],\n\t\t);\n\n\t\tfor (const { feature, cluster } of batchPoints) {\n\t\t\tconst currentCount = clusterCounts[cluster];\n\t\t\tif (currentCount !== undefined) {\n\t\t\t\tclusterCounts[cluster] = currentCount + 1;\n\t\t\t}\n\t\t\tconst sum = clusterSums[cluster];\n\t\t\tif (sum !== undefined) {\n\t\t\t\tsum[0] += feature.f1;\n\t\t\t\tsum[1] += feature.f2;\n\t\t\t\tsum[2] += feature.f3;\n\t\t\t}\n\t\t}\n\n\t\t// Update centroids\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst count = clusterCounts[c] ?? 0;\n\t\t\tif (count > 0) {\n\t\t\t\tconst sum = clusterSums[c];\n\t\t\t\tif (sum !== undefined) {\n\t\t\t\t\tcentroids[c] = {\n\t\t\t\t\t\tf1: sum[0] / count,\n\t\t\t\t\t\tf2: sum[1] / count,\n\t\t\t\t\t\tf3: sum[2] / count,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check convergence\n\t\tlet maxShift = 0;\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst newCentroid = centroids[c];\n\t\t\tconst oldCentroid = oldCentroids[c];\n\t\t\tif (newCentroid !== undefined && oldCentroid !== undefined) {\n\t\t\t\tconst shift = euclideanDistance(newCentroid, oldCentroid);\n\t\t\t\tmaxShift = Math.max(maxShift, shift);\n\t\t\t}\n\t\t}\n\n\t\tif (maxShift < tolerance) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Final assignment of all points\n\tfor (const feature of features) {\n\t\tlet minDist = Infinity;\n\t\tlet bestCluster = 0;\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst centroid = centroids[c];\n\t\t\tif (centroid === undefined) continue;\n\t\t\tconst dist = euclideanDistance(feature, centroid);\n\t\t\tif (dist < minDist) {\n\t\t\t\tminDist = dist;\n\t\t\t\tbestCluster = c;\n\t\t\t}\n\t\t}\n\t\tassignments.set(feature.nodeId, bestCluster);\n\t}\n\n\treturn {\n\t\tcentroids,\n\t\tassignments,\n\t\tk: effectiveK,\n\t};\n}\n\n/**\n * K-means++ initialisation for better centroid seeding.\n */\nfunction initialiseCentroidsKMeansPP(\n\tfeatures: readonly LabelledFeature[],\n\tk: number,\n\trng: () => number,\n): FeatureVector3D[] {\n\tconst centroids: FeatureVector3D[] = [];\n\tconst n = features.length;\n\n\t// Choose first centroid randomly\n\tconst firstIdx = Math.floor(rng() * n);\n\tconst firstFeature = features[firstIdx];\n\tif (firstFeature === undefined) {\n\t\treturn [{ f1: 0, f2: 0, f3: 0 }];\n\t}\n\tcentroids.push({\n\t\tf1: firstFeature.f1,\n\t\tf2: firstFeature.f2,\n\t\tf3: firstFeature.f3,\n\t});\n\n\t// Choose remaining centroids with probability proportional to squared distance\n\tconst distances = Array.from({ length: n }, () => Infinity);\n\n\tfor (let c = 1; c < k; c++) {\n\t\t// Update distances to nearest centroid\n\t\tlet totalDistSq = 0;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst feature = features[i];\n\t\t\tif (feature === undefined) continue;\n\t\t\tconst lastCentroid = centroids[c - 1];\n\t\t\tif (lastCentroid === undefined) continue;\n\t\t\tconst dist = euclideanDistance(feature, lastCentroid);\n\t\t\tconst currentMin = distances[i];\n\t\t\tif (currentMin !== undefined && dist < currentMin) {\n\t\t\t\tdistances[i] = dist;\n\t\t\t}\n\t\t\tconst d = distances[i];\n\t\t\tif (d !== undefined) {\n\t\t\t\ttotalDistSq += d * d;\n\t\t\t}\n\t\t}\n\n\t\t// Choose next centroid with probability proportional to squared distance\n\t\tconst threshold = rng() * totalDistSq;\n\t\tlet cumulative = 0;\n\t\tlet nextIdx = 0;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst d = distances[i];\n\t\t\tif (d !== undefined) {\n\t\t\t\tcumulative += d * d;\n\t\t\t\tif (cumulative >= threshold) {\n\t\t\t\t\tnextIdx = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst nextFeature = features[nextIdx];\n\t\tif (nextFeature !== undefined) {\n\t\t\tcentroids.push({\n\t\t\t\tf1: nextFeature.f1,\n\t\t\t\tf2: nextFeature.f2,\n\t\t\t\tf3: nextFeature.f3,\n\t\t\t});\n\t\t} else {\n\t\t\t// Fallback: use zero centroid\n\t\t\tcentroids.push({ f1: 0, f2: 0, f3: 0 });\n\t\t}\n\t}\n\n\treturn centroids;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kmeans-DKkL9rAN.js","names":[],"sources":["../src/utils/kmeans.ts"],"sourcesContent":["/**\n * Minimal K-means clustering implementation for GRASP seed selection.\n *\n * This is a lightweight implementation specifically designed for 3D feature vectors\n * used in structural seed selection. For general-purpose clustering, consider\n * using a dedicated library.\n *\n * @packageDocumentation\n */\n\n/**\n * A 3D feature vector representing node structural properties.\n */\nexport interface FeatureVector3D {\n\t/** First dimension (e.g., log-degree) */\n\treadonly f1: number;\n\t/** Second dimension (e.g., clustering coefficient) */\n\treadonly f2: number;\n\t/** Third dimension (e.g., approximate PageRank) */\n\treadonly f3: number;\n}\n\n/**\n * A labelled feature vector with associated node ID.\n */\nexport interface LabelledFeature extends FeatureVector3D {\n\t/** Node identifier */\n\treadonly nodeId: string;\n}\n\n/**\n * Result of K-means clustering.\n */\nexport interface KMeansResult {\n\t/** Cluster centroids */\n\treadonly centroids: readonly FeatureVector3D[];\n\t/** Cluster assignments: nodeId -> cluster index */\n\treadonly assignments: ReadonlyMap<string, number>;\n\t/** Number of clusters */\n\treadonly k: number;\n}\n\n/**\n * Options for K-means clustering.\n */\nexport interface KMeansOptions {\n\t/** Number of clusters */\n\treadonly k: number;\n\t/** Maximum iterations (default: 100) */\n\treadonly maxIterations?: number;\n\t/** Convergence threshold (default: 1e-6) */\n\treadonly tolerance?: number;\n\t/** Random seed for reproducibility */\n\treadonly seed?: number;\n}\n\n/** Small epsilon to prevent division by zero */\nconst EPSILON = 1e-10;\n\n/**\n * Simple seeded pseudo-random number generator using mulberry32.\n */\nfunction createRNG(seed: number): () => number {\n\tlet state = seed >>> 0;\n\treturn (): number => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = Math.imul(state ^ (state >>> 15), state | 1);\n\t\tt = (t ^ (t >>> 7)) * (t | 0x61c88647);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * Compute Euclidean distance between two 3D feature vectors.\n */\nfunction euclideanDistance(a: FeatureVector3D, b: FeatureVector3D): number {\n\tconst d1 = a.f1 - b.f1;\n\tconst d2 = a.f2 - b.f2;\n\tconst d3 = a.f3 - b.f3;\n\treturn Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);\n}\n\n/**\n * Compute the mean of a set of feature vectors.\n * @internal - Used for testing\n */\nexport function _computeMean(\n\tvectors: readonly FeatureVector3D[],\n): FeatureVector3D {\n\tif (vectors.length === 0) {\n\t\treturn { f1: 0, f2: 0, f3: 0 };\n\t}\n\tlet sum1 = 0;\n\tlet sum2 = 0;\n\tlet sum3 = 0;\n\tfor (const v of vectors) {\n\t\tsum1 += v.f1;\n\t\tsum2 += v.f2;\n\t\tsum3 += v.f3;\n\t}\n\tconst n = vectors.length;\n\treturn { f1: sum1 / n, f2: sum2 / n, f3: sum3 / n };\n}\n\n/**\n * Z-score normalise features (zero mean, unit variance).\n */\nexport { normaliseFeatures as zScoreNormalise };\n\nexport function normaliseFeatures(\n\tfeatures: readonly LabelledFeature[],\n): LabelledFeature[] {\n\tif (features.length === 0) {\n\t\treturn [];\n\t}\n\n\t// Compute means\n\tlet sum1 = 0;\n\tlet sum2 = 0;\n\tlet sum3 = 0;\n\tfor (const f of features) {\n\t\tsum1 += f.f1;\n\t\tsum2 += f.f2;\n\t\tsum3 += f.f3;\n\t}\n\tconst n = features.length;\n\tconst mean1 = sum1 / n;\n\tconst mean2 = sum2 / n;\n\tconst mean3 = sum3 / n;\n\n\t// Compute standard deviations\n\tlet var1 = 0;\n\tlet var2 = 0;\n\tlet var3 = 0;\n\tfor (const f of features) {\n\t\tvar1 += (f.f1 - mean1) ** 2;\n\t\tvar2 += (f.f2 - mean2) ** 2;\n\t\tvar3 += (f.f3 - mean3) ** 2;\n\t}\n\tconst std1 = Math.sqrt(var1 / n + EPSILON);\n\tconst std2 = Math.sqrt(var2 / n + EPSILON);\n\tconst std3 = Math.sqrt(var3 / n + EPSILON);\n\n\t// Normalise\n\treturn features.map(\n\t\t(f): LabelledFeature => ({\n\t\t\tnodeId: f.nodeId,\n\t\t\tf1: (f.f1 - mean1) / std1,\n\t\t\tf2: (f.f2 - mean2) / std2,\n\t\t\tf3: (f.f3 - mean3) / std3,\n\t\t}),\n\t);\n}\n\n/**\n * Mini-batch K-means clustering for 3D feature vectors.\n *\n * Uses Mini-batch K-means for efficiency with large datasets.\n * This is specifically designed for the GRASP seed selection algorithm.\n *\n * @param features - Array of labelled feature vectors\n * @param options - Clustering options\n * @returns Clustering result with centroids and assignments\n */\nexport function miniBatchKMeans(\n\tfeatures: readonly LabelledFeature[],\n\toptions: KMeansOptions,\n): KMeansResult {\n\tconst { k, maxIterations = 100, tolerance = 1e-6, seed = 42 } = options;\n\n\tif (features.length === 0) {\n\t\treturn {\n\t\t\tcentroids: [],\n\t\t\tassignments: new Map(),\n\t\t\tk,\n\t\t};\n\t}\n\n\tconst rng = createRNG(seed);\n\tconst n = features.length;\n\tconst effectiveK = Math.min(k, n);\n\n\t// Initialise centroids using k-means++ seeding\n\tconst centroids: FeatureVector3D[] = initialiseCentroidsKMeansPP(\n\t\tfeatures,\n\t\teffectiveK,\n\t\trng,\n\t);\n\n\t// Assignments map\n\tconst assignments = new Map<string, number>();\n\n\t// Mini-batch size (10% of data or at least 10)\n\tconst batchSize = Math.max(10, Math.floor(n / 10));\n\n\tfor (let iter = 0; iter < maxIterations; iter++) {\n\t\t// Sample mini-batch\n\t\tconst batchIndices = new Set<number>();\n\t\twhile (batchIndices.size < Math.min(batchSize, n)) {\n\t\t\tbatchIndices.add(Math.floor(rng() * n));\n\t\t}\n\n\t\t// Assign batch points to nearest centroid\n\t\tconst batchPoints: { feature: LabelledFeature; cluster: number }[] = [];\n\t\tfor (const idx of batchIndices) {\n\t\t\tconst feature = features[idx];\n\t\t\tif (feature === undefined) continue;\n\t\t\tlet minDist = Infinity;\n\t\t\tlet bestCluster = 0;\n\t\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\t\tconst centroid = centroids[c];\n\t\t\t\tif (centroid === undefined) continue;\n\t\t\t\tconst dist = euclideanDistance(feature, centroid);\n\t\t\t\tif (dist < minDist) {\n\t\t\t\t\tminDist = dist;\n\t\t\t\t\tbestCluster = c;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbatchPoints.push({ feature, cluster: bestCluster });\n\t\t}\n\n\t\t// Update centroids based on batch\n\t\tconst oldCentroids = centroids.map((c) => ({ ...c }));\n\n\t\t// Compute per-cluster counts and sums from batch\n\t\tconst clusterCounts = Array.from({ length: centroids.length }, () => 0);\n\t\tconst clusterSums: [number, number, number][] = Array.from(\n\t\t\t{ length: centroids.length },\n\t\t\t(): [number, number, number] => [0, 0, 0],\n\t\t);\n\n\t\tfor (const { feature, cluster } of batchPoints) {\n\t\t\tconst currentCount = clusterCounts[cluster];\n\t\t\tif (currentCount !== undefined) {\n\t\t\t\tclusterCounts[cluster] = currentCount + 1;\n\t\t\t}\n\t\t\tconst sum = clusterSums[cluster];\n\t\t\tif (sum !== undefined) {\n\t\t\t\tsum[0] += feature.f1;\n\t\t\t\tsum[1] += feature.f2;\n\t\t\t\tsum[2] += feature.f3;\n\t\t\t}\n\t\t}\n\n\t\t// Update centroids\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst count = clusterCounts[c] ?? 0;\n\t\t\tif (count > 0) {\n\t\t\t\tconst sum = clusterSums[c];\n\t\t\t\tif (sum !== undefined) {\n\t\t\t\t\tcentroids[c] = {\n\t\t\t\t\t\tf1: sum[0] / count,\n\t\t\t\t\t\tf2: sum[1] / count,\n\t\t\t\t\t\tf3: sum[2] / count,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check convergence\n\t\tlet maxShift = 0;\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst newCentroid = centroids[c];\n\t\t\tconst oldCentroid = oldCentroids[c];\n\t\t\tif (newCentroid !== undefined && oldCentroid !== undefined) {\n\t\t\t\tconst shift = euclideanDistance(newCentroid, oldCentroid);\n\t\t\t\tmaxShift = Math.max(maxShift, shift);\n\t\t\t}\n\t\t}\n\n\t\tif (maxShift < tolerance) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Final assignment of all points\n\tfor (const feature of features) {\n\t\tlet minDist = Infinity;\n\t\tlet bestCluster = 0;\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst centroid = centroids[c];\n\t\t\tif (centroid === undefined) continue;\n\t\t\tconst dist = euclideanDistance(feature, centroid);\n\t\t\tif (dist < minDist) {\n\t\t\t\tminDist = dist;\n\t\t\t\tbestCluster = c;\n\t\t\t}\n\t\t}\n\t\tassignments.set(feature.nodeId, bestCluster);\n\t}\n\n\treturn {\n\t\tcentroids,\n\t\tassignments,\n\t\tk: effectiveK,\n\t};\n}\n\n/**\n * K-means++ initialisation for better centroid seeding.\n */\nfunction initialiseCentroidsKMeansPP(\n\tfeatures: readonly LabelledFeature[],\n\tk: number,\n\trng: () => number,\n): FeatureVector3D[] {\n\tconst centroids: FeatureVector3D[] = [];\n\tconst n = features.length;\n\n\t// Choose first centroid randomly\n\tconst firstIdx = Math.floor(rng() * n);\n\tconst firstFeature = features[firstIdx];\n\tif (firstFeature === undefined) {\n\t\treturn [{ f1: 0, f2: 0, f3: 0 }];\n\t}\n\tcentroids.push({\n\t\tf1: firstFeature.f1,\n\t\tf2: firstFeature.f2,\n\t\tf3: firstFeature.f3,\n\t});\n\n\t// Choose remaining centroids with probability proportional to squared distance\n\tconst distances = Array.from({ length: n }, () => Infinity);\n\n\tfor (let c = 1; c < k; c++) {\n\t\t// Update distances to nearest centroid\n\t\tlet totalDistSq = 0;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst feature = features[i];\n\t\t\tif (feature === undefined) continue;\n\t\t\tconst lastCentroid = centroids[c - 1];\n\t\t\tif (lastCentroid === undefined) continue;\n\t\t\tconst dist = euclideanDistance(feature, lastCentroid);\n\t\t\tconst currentMin = distances[i];\n\t\t\tif (currentMin !== undefined && dist < currentMin) {\n\t\t\t\tdistances[i] = dist;\n\t\t\t}\n\t\t\tconst d = distances[i];\n\t\t\tif (d !== undefined) {\n\t\t\t\ttotalDistSq += d * d;\n\t\t\t}\n\t\t}\n\n\t\t// Choose next centroid with probability proportional to squared distance\n\t\tconst threshold = rng() * totalDistSq;\n\t\tlet cumulative = 0;\n\t\tlet nextIdx = 0;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst d = distances[i];\n\t\t\tif (d !== undefined) {\n\t\t\t\tcumulative += d * d;\n\t\t\t\tif (cumulative >= threshold) {\n\t\t\t\t\tnextIdx = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst nextFeature = features[nextIdx];\n\t\tif (nextFeature !== undefined) {\n\t\t\tcentroids.push({\n\t\t\t\tf1: nextFeature.f1,\n\t\t\t\tf2: nextFeature.f2,\n\t\t\t\tf3: nextFeature.f3,\n\t\t\t});\n\t\t} else {\n\t\t\t// Fallback: use zero centroid\n\t\t\tcentroids.push({ f1: 0, f2: 0, f3: 0 });\n\t\t}\n\t}\n\n\treturn centroids;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"kmeans-DLrlrp6i.js","names":[],"sources":["../src/utils/kmeans.ts"],"sourcesContent":["/**\n * Minimal K-means clustering implementation for GRASP seed selection.\n *\n * This is a lightweight implementation specifically designed for 3D feature vectors\n * used in structural seed selection. For general-purpose clustering, consider\n * using a dedicated library.\n *\n * @packageDocumentation\n */\n\n/**\n * A 3D feature vector representing node structural properties.\n */\nexport interface FeatureVector3D {\n\t/** First dimension (e.g., log-degree) */\n\treadonly f1: number;\n\t/** Second dimension (e.g., clustering coefficient) */\n\treadonly f2: number;\n\t/** Third dimension (e.g., approximate PageRank) */\n\treadonly f3: number;\n}\n\n/**\n * A labelled feature vector with associated node ID.\n */\nexport interface LabelledFeature extends FeatureVector3D {\n\t/** Node identifier */\n\treadonly nodeId: string;\n}\n\n/**\n * Result of K-means clustering.\n */\nexport interface KMeansResult {\n\t/** Cluster centroids */\n\treadonly centroids: readonly FeatureVector3D[];\n\t/** Cluster assignments: nodeId -> cluster index */\n\treadonly assignments: ReadonlyMap<string, number>;\n\t/** Number of clusters */\n\treadonly k: number;\n}\n\n/**\n * Options for K-means clustering.\n */\nexport interface KMeansOptions {\n\t/** Number of clusters */\n\treadonly k: number;\n\t/** Maximum iterations (default: 100) */\n\treadonly maxIterations?: number;\n\t/** Convergence threshold (default: 1e-6) */\n\treadonly tolerance?: number;\n\t/** Random seed for reproducibility */\n\treadonly seed?: number;\n}\n\n/** Small epsilon to prevent division by zero */\nconst EPSILON = 1e-10;\n\n/**\n * Simple seeded pseudo-random number generator using mulberry32.\n */\nfunction createRNG(seed: number): () => number {\n\tlet state = seed >>> 0;\n\treturn (): number => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = Math.imul(state ^ (state >>> 15), state | 1);\n\t\tt = (t ^ (t >>> 7)) * (t | 0x61c88647);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * Compute Euclidean distance between two 3D feature vectors.\n */\nfunction euclideanDistance(a: FeatureVector3D, b: FeatureVector3D): number {\n\tconst d1 = a.f1 - b.f1;\n\tconst d2 = a.f2 - b.f2;\n\tconst d3 = a.f3 - b.f3;\n\treturn Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);\n}\n\n/**\n * Compute the mean of a set of feature vectors.\n * @internal - Used for testing\n */\nexport function _computeMean(\n\tvectors: readonly FeatureVector3D[],\n): FeatureVector3D {\n\tif (vectors.length === 0) {\n\t\treturn { f1: 0, f2: 0, f3: 0 };\n\t}\n\tlet sum1 = 0;\n\tlet sum2 = 0;\n\tlet sum3 = 0;\n\tfor (const v of vectors) {\n\t\tsum1 += v.f1;\n\t\tsum2 += v.f2;\n\t\tsum3 += v.f3;\n\t}\n\tconst n = vectors.length;\n\treturn { f1: sum1 / n, f2: sum2 / n, f3: sum3 / n };\n}\n\n/**\n * Z-score normalise features (zero mean, unit variance).\n */\nexport { normaliseFeatures as zScoreNormalise };\n\nexport function normaliseFeatures(\n\tfeatures: readonly LabelledFeature[],\n): LabelledFeature[] {\n\tif (features.length === 0) {\n\t\treturn [];\n\t}\n\n\t// Compute means\n\tlet sum1 = 0;\n\tlet sum2 = 0;\n\tlet sum3 = 0;\n\tfor (const f of features) {\n\t\tsum1 += f.f1;\n\t\tsum2 += f.f2;\n\t\tsum3 += f.f3;\n\t}\n\tconst n = features.length;\n\tconst mean1 = sum1 / n;\n\tconst mean2 = sum2 / n;\n\tconst mean3 = sum3 / n;\n\n\t// Compute standard deviations\n\tlet var1 = 0;\n\tlet var2 = 0;\n\tlet var3 = 0;\n\tfor (const f of features) {\n\t\tvar1 += (f.f1 - mean1) ** 2;\n\t\tvar2 += (f.f2 - mean2) ** 2;\n\t\tvar3 += (f.f3 - mean3) ** 2;\n\t}\n\tconst std1 = Math.sqrt(var1 / n + EPSILON);\n\tconst std2 = Math.sqrt(var2 / n + EPSILON);\n\tconst std3 = Math.sqrt(var3 / n + EPSILON);\n\n\t// Normalise\n\treturn features.map(\n\t\t(f): LabelledFeature => ({\n\t\t\tnodeId: f.nodeId,\n\t\t\tf1: (f.f1 - mean1) / std1,\n\t\t\tf2: (f.f2 - mean2) / std2,\n\t\t\tf3: (f.f3 - mean3) / std3,\n\t\t}),\n\t);\n}\n\n/**\n * Mini-batch K-means clustering for 3D feature vectors.\n *\n * Uses Mini-batch K-means for efficiency with large datasets.\n * This is specifically designed for the GRASP seed selection algorithm.\n *\n * @param features - Array of labelled feature vectors\n * @param options - Clustering options\n * @returns Clustering result with centroids and assignments\n */\nexport function miniBatchKMeans(\n\tfeatures: readonly LabelledFeature[],\n\toptions: KMeansOptions,\n): KMeansResult {\n\tconst { k, maxIterations = 100, tolerance = 1e-6, seed = 42 } = options;\n\n\tif (features.length === 0) {\n\t\treturn {\n\t\t\tcentroids: [],\n\t\t\tassignments: new Map(),\n\t\t\tk,\n\t\t};\n\t}\n\n\tconst rng = createRNG(seed);\n\tconst n = features.length;\n\tconst effectiveK = Math.min(k, n);\n\n\t// Initialise centroids using k-means++ seeding\n\tconst centroids: FeatureVector3D[] = initialiseCentroidsKMeansPP(\n\t\tfeatures,\n\t\teffectiveK,\n\t\trng,\n\t);\n\n\t// Assignments map\n\tconst assignments = new Map<string, number>();\n\n\t// Mini-batch size (10% of data or at least 10)\n\tconst batchSize = Math.max(10, Math.floor(n / 10));\n\n\tfor (let iter = 0; iter < maxIterations; iter++) {\n\t\t// Sample mini-batch\n\t\tconst batchIndices = new Set<number>();\n\t\twhile (batchIndices.size < Math.min(batchSize, n)) {\n\t\t\tbatchIndices.add(Math.floor(rng() * n));\n\t\t}\n\n\t\t// Assign batch points to nearest centroid\n\t\tconst batchPoints: { feature: LabelledFeature; cluster: number }[] = [];\n\t\tfor (const idx of batchIndices) {\n\t\t\tconst feature = features[idx];\n\t\t\tif (feature === undefined) continue;\n\t\t\tlet minDist = Infinity;\n\t\t\tlet bestCluster = 0;\n\t\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\t\tconst centroid = centroids[c];\n\t\t\t\tif (centroid === undefined) continue;\n\t\t\t\tconst dist = euclideanDistance(feature, centroid);\n\t\t\t\tif (dist < minDist) {\n\t\t\t\t\tminDist = dist;\n\t\t\t\t\tbestCluster = c;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbatchPoints.push({ feature, cluster: bestCluster });\n\t\t}\n\n\t\t// Update centroids based on batch\n\t\tconst oldCentroids = centroids.map((c) => ({ ...c }));\n\n\t\t// Compute per-cluster counts and sums from batch\n\t\tconst clusterCounts = Array.from({ length: centroids.length }, () => 0);\n\t\tconst clusterSums: [number, number, number][] = Array.from(\n\t\t\t{ length: centroids.length },\n\t\t\t(): [number, number, number] => [0, 0, 0],\n\t\t);\n\n\t\tfor (const { feature, cluster } of batchPoints) {\n\t\t\tconst currentCount = clusterCounts[cluster];\n\t\t\tif (currentCount !== undefined) {\n\t\t\t\tclusterCounts[cluster] = currentCount + 1;\n\t\t\t}\n\t\t\tconst sum = clusterSums[cluster];\n\t\t\tif (sum !== undefined) {\n\t\t\t\tsum[0] += feature.f1;\n\t\t\t\tsum[1] += feature.f2;\n\t\t\t\tsum[2] += feature.f3;\n\t\t\t}\n\t\t}\n\n\t\t// Update centroids\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst count = clusterCounts[c] ?? 0;\n\t\t\tif (count > 0) {\n\t\t\t\tconst sum = clusterSums[c];\n\t\t\t\tif (sum !== undefined) {\n\t\t\t\t\tcentroids[c] = {\n\t\t\t\t\t\tf1: sum[0] / count,\n\t\t\t\t\t\tf2: sum[1] / count,\n\t\t\t\t\t\tf3: sum[2] / count,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check convergence\n\t\tlet maxShift = 0;\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst newCentroid = centroids[c];\n\t\t\tconst oldCentroid = oldCentroids[c];\n\t\t\tif (newCentroid !== undefined && oldCentroid !== undefined) {\n\t\t\t\tconst shift = euclideanDistance(newCentroid, oldCentroid);\n\t\t\t\tmaxShift = Math.max(maxShift, shift);\n\t\t\t}\n\t\t}\n\n\t\tif (maxShift < tolerance) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Final assignment of all points\n\tfor (const feature of features) {\n\t\tlet minDist = Infinity;\n\t\tlet bestCluster = 0;\n\t\tfor (let c = 0; c < centroids.length; c++) {\n\t\t\tconst centroid = centroids[c];\n\t\t\tif (centroid === undefined) continue;\n\t\t\tconst dist = euclideanDistance(feature, centroid);\n\t\t\tif (dist < minDist) {\n\t\t\t\tminDist = dist;\n\t\t\t\tbestCluster = c;\n\t\t\t}\n\t\t}\n\t\tassignments.set(feature.nodeId, bestCluster);\n\t}\n\n\treturn {\n\t\tcentroids,\n\t\tassignments,\n\t\tk: effectiveK,\n\t};\n}\n\n/**\n * K-means++ initialisation for better centroid seeding.\n */\nfunction initialiseCentroidsKMeansPP(\n\tfeatures: readonly LabelledFeature[],\n\tk: number,\n\trng: () => number,\n): FeatureVector3D[] {\n\tconst centroids: FeatureVector3D[] = [];\n\tconst n = features.length;\n\n\t// Choose first centroid randomly\n\tconst firstIdx = Math.floor(rng() * n);\n\tconst firstFeature = features[firstIdx];\n\tif (firstFeature === undefined) {\n\t\treturn [{ f1: 0, f2: 0, f3: 0 }];\n\t}\n\tcentroids.push({\n\t\tf1: firstFeature.f1,\n\t\tf2: firstFeature.f2,\n\t\tf3: firstFeature.f3,\n\t});\n\n\t// Choose remaining centroids with probability proportional to squared distance\n\tconst distances = Array.from({ length: n }, () => Infinity);\n\n\tfor (let c = 1; c < k; c++) {\n\t\t// Update distances to nearest centroid\n\t\tlet totalDistSq = 0;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst feature = features[i];\n\t\t\tif (feature === undefined) continue;\n\t\t\tconst lastCentroid = centroids[c - 1];\n\t\t\tif (lastCentroid === undefined) continue;\n\t\t\tconst dist = euclideanDistance(feature, lastCentroid);\n\t\t\tconst currentMin = distances[i];\n\t\t\tif (currentMin !== undefined && dist < currentMin) {\n\t\t\t\tdistances[i] = dist;\n\t\t\t}\n\t\t\tconst d = distances[i];\n\t\t\tif (d !== undefined) {\n\t\t\t\ttotalDistSq += d * d;\n\t\t\t}\n\t\t}\n\n\t\t// Choose next centroid with probability proportional to squared distance\n\t\tconst threshold = rng() * totalDistSq;\n\t\tlet cumulative = 0;\n\t\tlet nextIdx = 0;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst d = distances[i];\n\t\t\tif (d !== undefined) {\n\t\t\t\tcumulative += d * d;\n\t\t\t\tif (cumulative >= threshold) {\n\t\t\t\t\tnextIdx = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst nextFeature = features[nextIdx];\n\t\tif (nextFeature !== undefined) {\n\t\t\tcentroids.push({\n\t\t\t\tf1: nextFeature.f1,\n\t\t\t\tf2: nextFeature.f2,\n\t\t\t\tf3: nextFeature.f3,\n\t\t\t});\n\t\t} else {\n\t\t\t// Fallback: use zero centroid\n\t\t\tcentroids.push({ f1: 0, f2: 0, f3: 0 });\n\t\t}\n\t}\n\n\treturn centroids;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const require_typegpu = require("./typegpu-Dq5FfUB8.cjs");
|
|
2
|
+
//#region src/gpu/csr.ts
|
|
3
|
+
/**
|
|
4
|
+
* Compressed Sparse Row (CSR) matrix representation for GPU computation.
|
|
5
|
+
*
|
|
6
|
+
* CSR format is memory-efficient for sparse graphs and maps well to
|
|
7
|
+
* GPU parallel operations. The format stores adjacency information
|
|
8
|
+
* in three arrays: row offsets, column indices, and optional values.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Convert a ReadableGraph to CSR format.
|
|
12
|
+
*
|
|
13
|
+
* For undirected graphs, each edge is stored twice (once in each direction).
|
|
14
|
+
* For directed graphs, edges are stored in the out-direction by default.
|
|
15
|
+
*
|
|
16
|
+
* @param graph - The graph to convert
|
|
17
|
+
* @param direction - Edge direction to include (default: 'out' for directed, 'both' for undirected)
|
|
18
|
+
* @returns CSR representation with index mapping
|
|
19
|
+
*/
|
|
20
|
+
function graphToCSR(graph, direction = graph.directed ? "out" : "both") {
|
|
21
|
+
const nodeToIndex = /* @__PURE__ */ new Map();
|
|
22
|
+
const indexToNode = [];
|
|
23
|
+
for (const nodeId of graph.nodeIds()) {
|
|
24
|
+
const index = indexToNode.length;
|
|
25
|
+
nodeToIndex.set(nodeId, index);
|
|
26
|
+
indexToNode.push(nodeId);
|
|
27
|
+
}
|
|
28
|
+
const nodeCount = indexToNode.length;
|
|
29
|
+
const degrees = new Uint32Array(nodeCount);
|
|
30
|
+
for (const nodeId of graph.nodeIds()) {
|
|
31
|
+
const srcIndex = nodeToIndex.get(nodeId);
|
|
32
|
+
if (srcIndex === void 0) continue;
|
|
33
|
+
degrees[srcIndex] = graph.degree(nodeId, direction);
|
|
34
|
+
}
|
|
35
|
+
let totalEdges = 0;
|
|
36
|
+
for (let i = 0; i < nodeCount; i++) totalEdges += degrees[i] ?? 0;
|
|
37
|
+
const rowOffsets = new Uint32Array(nodeCount + 1);
|
|
38
|
+
for (let i = 0; i < nodeCount; i++) rowOffsets[i + 1] = (rowOffsets[i] ?? 0) + (degrees[i] ?? 0);
|
|
39
|
+
const colIndices = new Uint32Array(totalEdges);
|
|
40
|
+
const values = new Float32Array(totalEdges);
|
|
41
|
+
for (const nodeId of graph.nodeIds()) {
|
|
42
|
+
const srcIndex = nodeToIndex.get(nodeId);
|
|
43
|
+
if (srcIndex === void 0) continue;
|
|
44
|
+
const baseOffset = rowOffsets[srcIndex] ?? 0;
|
|
45
|
+
let localOffset = 0;
|
|
46
|
+
for (const neighbourId of graph.neighbours(nodeId, direction)) {
|
|
47
|
+
const dstIndex = nodeToIndex.get(neighbourId);
|
|
48
|
+
if (dstIndex === void 0) continue;
|
|
49
|
+
const edgeIdx = baseOffset + localOffset;
|
|
50
|
+
colIndices[edgeIdx] = dstIndex;
|
|
51
|
+
values[edgeIdx] = graph.getEdge(nodeId, neighbourId)?.weight ?? 1;
|
|
52
|
+
localOffset++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
csr: {
|
|
57
|
+
rowOffsets,
|
|
58
|
+
colIndices,
|
|
59
|
+
values,
|
|
60
|
+
nodeCount,
|
|
61
|
+
edgeCount: graph.directed ? graph.edgeCount : graph.edgeCount * 2
|
|
62
|
+
},
|
|
63
|
+
indexMap: {
|
|
64
|
+
nodeToIndex,
|
|
65
|
+
indexToNode
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create TypeGPU typed buffers from a CSR matrix.
|
|
71
|
+
*
|
|
72
|
+
* Uses TypeGPU's typed buffer API for type-safe GPU computation.
|
|
73
|
+
* Buffers are created with storage usage for compute operations.
|
|
74
|
+
*
|
|
75
|
+
* @param root - TypeGPU root instance
|
|
76
|
+
* @param csr - CSR matrix to upload
|
|
77
|
+
* @returns Typed buffer group
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* import { initGPU, csrToTypedBuffers, graphToCSR } from "graphwise/gpu";
|
|
82
|
+
*
|
|
83
|
+
* const root = await initGPU();
|
|
84
|
+
* const { csr } = graphToCSR(graph);
|
|
85
|
+
* const buffers = csrToTypedBuffers(root, csr);
|
|
86
|
+
*
|
|
87
|
+
* // Read data back to CPU
|
|
88
|
+
* const rowOffsets = await buffers.rowOffsets.read();
|
|
89
|
+
* console.log(rowOffsets); // Uint32Array
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
function csrToTypedBuffers(root, csr) {
|
|
93
|
+
const rowOffsetsArray = Array.from(csr.rowOffsets);
|
|
94
|
+
const colIndicesArray = Array.from(csr.colIndices);
|
|
95
|
+
const rowOffsetsBuffer = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(root.createBuffer(require_typegpu.data_exports.arrayOf(require_typegpu.data_exports.u32, csr.rowOffsets.length), rowOffsetsArray).$usage("storage"), "rowOffsetsBuffer");
|
|
96
|
+
const colIndicesBuffer = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(root.createBuffer(require_typegpu.data_exports.arrayOf(require_typegpu.data_exports.u32, csr.colIndices.length), colIndicesArray).$usage("storage"), "colIndicesBuffer");
|
|
97
|
+
if (csr.values !== void 0) {
|
|
98
|
+
const valuesArray = Array.from(csr.values);
|
|
99
|
+
return {
|
|
100
|
+
rowOffsets: rowOffsetsBuffer,
|
|
101
|
+
colIndices: colIndicesBuffer,
|
|
102
|
+
values: (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(root.createBuffer(require_typegpu.data_exports.arrayOf(require_typegpu.data_exports.f32, csr.values.length), valuesArray).$usage("storage"), "valuesBuffer"),
|
|
103
|
+
nodeCount: csr.nodeCount,
|
|
104
|
+
edgeCount: csr.edgeCount
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
rowOffsets: rowOffsetsBuffer,
|
|
109
|
+
colIndices: colIndicesBuffer,
|
|
110
|
+
nodeCount: csr.nodeCount,
|
|
111
|
+
edgeCount: csr.edgeCount
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/gpu/kernels/intersection/logic.ts
|
|
116
|
+
/**
|
|
117
|
+
* Binary search in a sorted subarray of colIndices.
|
|
118
|
+
*
|
|
119
|
+
* @param colIndices - CSR column indices array (must be sorted per row)
|
|
120
|
+
* @param start - Start index (inclusive)
|
|
121
|
+
* @param end - End index (exclusive)
|
|
122
|
+
* @param target - Value to search for
|
|
123
|
+
* @returns true if target is found between colIndices[start] and colIndices[end-1]
|
|
124
|
+
*/
|
|
125
|
+
function binarySearch(colIndices, start, end, target) {
|
|
126
|
+
let lo = start;
|
|
127
|
+
let hi = end;
|
|
128
|
+
while (lo < hi) {
|
|
129
|
+
const mid = lo + Math.floor((hi - lo) / 2);
|
|
130
|
+
const midVal = colIndices[mid] ?? 0;
|
|
131
|
+
if (midVal === target) return true;
|
|
132
|
+
else if (midVal < target) lo = mid + 1;
|
|
133
|
+
else hi = mid;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Compute intersection stats for a single pair of nodes in CSR format.
|
|
139
|
+
*
|
|
140
|
+
* Neighbours must be sorted (CSR guarantees this from graphToCSR).
|
|
141
|
+
*
|
|
142
|
+
* @param rowOffsets - CSR row offset array
|
|
143
|
+
* @param colIndices - CSR column indices array
|
|
144
|
+
* @param u - First node index
|
|
145
|
+
* @param v - Second node index
|
|
146
|
+
* @returns Intersection result with sizes
|
|
147
|
+
*/
|
|
148
|
+
function intersectionPair(rowOffsets, colIndices, u, v) {
|
|
149
|
+
const uStart = rowOffsets[u] ?? 0;
|
|
150
|
+
const uEnd = rowOffsets[u + 1] ?? 0;
|
|
151
|
+
const vStart = rowOffsets[v] ?? 0;
|
|
152
|
+
const vEnd = rowOffsets[v + 1] ?? 0;
|
|
153
|
+
const sizeU = uEnd - uStart;
|
|
154
|
+
const sizeV = vEnd - vStart;
|
|
155
|
+
if (sizeU === 0 || sizeV === 0) return {
|
|
156
|
+
intersection: 0,
|
|
157
|
+
sizeU,
|
|
158
|
+
sizeV
|
|
159
|
+
};
|
|
160
|
+
let intersection = 0;
|
|
161
|
+
if (sizeU <= sizeV) {
|
|
162
|
+
for (let i = uStart; i < uEnd; i++) if (binarySearch(colIndices, vStart, vEnd, colIndices[i] ?? 0)) intersection++;
|
|
163
|
+
} else for (let i = vStart; i < vEnd; i++) if (binarySearch(colIndices, uStart, uEnd, colIndices[i] ?? 0)) intersection++;
|
|
164
|
+
return {
|
|
165
|
+
intersection,
|
|
166
|
+
sizeU,
|
|
167
|
+
sizeV
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Batch intersection stats for multiple node pairs.
|
|
172
|
+
*
|
|
173
|
+
* @param rowOffsets - CSR row offset array
|
|
174
|
+
* @param colIndices - CSR column indices array
|
|
175
|
+
* @param pairs - Array of [u, v] node index pairs
|
|
176
|
+
* @returns Object with parallel arrays: intersections, sizeUs, sizeVs
|
|
177
|
+
*/
|
|
178
|
+
function intersectionBatch(rowOffsets, colIndices, pairs) {
|
|
179
|
+
const n = pairs.length;
|
|
180
|
+
const intersections = new Uint32Array(n);
|
|
181
|
+
const sizeUs = new Uint32Array(n);
|
|
182
|
+
const sizeVs = new Uint32Array(n);
|
|
183
|
+
for (let i = 0; i < n; i++) {
|
|
184
|
+
const pair = pairs[i];
|
|
185
|
+
if (pair !== void 0) {
|
|
186
|
+
const [u, v] = pair;
|
|
187
|
+
const result = intersectionPair(rowOffsets, colIndices, u, v);
|
|
188
|
+
intersections[i] = result.intersection;
|
|
189
|
+
sizeUs[i] = result.sizeU;
|
|
190
|
+
sizeVs[i] = result.sizeV;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
intersections,
|
|
195
|
+
sizeUs,
|
|
196
|
+
sizeVs
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Compute Jaccard from intersection stats.
|
|
201
|
+
* J = intersection / (sizeU + sizeV - intersection)
|
|
202
|
+
*/
|
|
203
|
+
function jaccardFromIntersection(result) {
|
|
204
|
+
const union = result.sizeU + result.sizeV - result.intersection;
|
|
205
|
+
return union === 0 ? 0 : result.intersection / union;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Compute Cosine similarity from intersection stats.
|
|
209
|
+
* Cosine = intersection / sqrt(sizeU * sizeV)
|
|
210
|
+
*/
|
|
211
|
+
function cosineFromIntersection(result) {
|
|
212
|
+
const denominator = Math.sqrt(result.sizeU * result.sizeV);
|
|
213
|
+
return denominator === 0 ? 0 : result.intersection / denominator;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Compute Sorensen-Dice from intersection stats.
|
|
217
|
+
* SD = 2 * intersection / (sizeU + sizeV)
|
|
218
|
+
*/
|
|
219
|
+
function sorensenDiceFromIntersection(result) {
|
|
220
|
+
const denominator = result.sizeU + result.sizeV;
|
|
221
|
+
return denominator === 0 ? 0 : 2 * result.intersection / denominator;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Compute Overlap coefficient from intersection stats.
|
|
225
|
+
* Overlap = intersection / min(sizeU, sizeV)
|
|
226
|
+
*/
|
|
227
|
+
function overlapFromIntersection(result) {
|
|
228
|
+
const minSize = Math.min(result.sizeU, result.sizeV);
|
|
229
|
+
return minSize === 0 ? 0 : result.intersection / minSize;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Compute Hub Promoted from intersection stats.
|
|
233
|
+
* HP = intersection / min(sizeU, sizeV)
|
|
234
|
+
* Note: Same formula as Overlap coefficient
|
|
235
|
+
*/
|
|
236
|
+
function hubPromotedFromIntersection(result) {
|
|
237
|
+
return overlapFromIntersection(result);
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
Object.defineProperty(exports, "cosineFromIntersection", {
|
|
241
|
+
enumerable: true,
|
|
242
|
+
get: function() {
|
|
243
|
+
return cosineFromIntersection;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
Object.defineProperty(exports, "csrToTypedBuffers", {
|
|
247
|
+
enumerable: true,
|
|
248
|
+
get: function() {
|
|
249
|
+
return csrToTypedBuffers;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
Object.defineProperty(exports, "graphToCSR", {
|
|
253
|
+
enumerable: true,
|
|
254
|
+
get: function() {
|
|
255
|
+
return graphToCSR;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
Object.defineProperty(exports, "hubPromotedFromIntersection", {
|
|
259
|
+
enumerable: true,
|
|
260
|
+
get: function() {
|
|
261
|
+
return hubPromotedFromIntersection;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
Object.defineProperty(exports, "intersectionBatch", {
|
|
265
|
+
enumerable: true,
|
|
266
|
+
get: function() {
|
|
267
|
+
return intersectionBatch;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
Object.defineProperty(exports, "jaccardFromIntersection", {
|
|
271
|
+
enumerable: true,
|
|
272
|
+
get: function() {
|
|
273
|
+
return jaccardFromIntersection;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
Object.defineProperty(exports, "overlapFromIntersection", {
|
|
277
|
+
enumerable: true,
|
|
278
|
+
get: function() {
|
|
279
|
+
return overlapFromIntersection;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
Object.defineProperty(exports, "sorensenDiceFromIntersection", {
|
|
283
|
+
enumerable: true,
|
|
284
|
+
get: function() {
|
|
285
|
+
return sorensenDiceFromIntersection;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
//# sourceMappingURL=logic-Dbyfb_-7.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logic-Dbyfb_-7.cjs","names":[],"sources":["../src/gpu/csr.ts","../src/gpu/kernels/intersection/logic.ts"],"sourcesContent":["/**\n * Compressed Sparse Row (CSR) matrix representation for GPU computation.\n *\n * CSR format is memory-efficient for sparse graphs and maps well to\n * GPU parallel operations. The format stores adjacency information\n * in three arrays: row offsets, column indices, and optional values.\n */\n\nimport { d, type StorageFlag, type TgpuBuffer } from \"typegpu\";\nimport type { NodeId, Direction, NodeData, EdgeData } from \"../graph/types\";\nimport type { ReadableGraph } from \"../graph\";\nimport type { GraphwiseGPURoot } from \"./root\";\n\n/**\n * CSR matrix representation of a graph adjacency structure.\n *\n * The rowOffsets array has length nodeCount + 1, where rowOffsets[i]\n * gives the start index in colIndices for node i's neighbours.\n * The neighbours of node i are colIndices[rowOffsets[i] : rowOffsets[i+1]].\n */\nexport interface CSRMatrix {\n\t/** Row offsets array (length: nodeCount + 1) */\n\treadonly rowOffsets: Uint32Array;\n\t/** Column indices array (length: edgeCount for directed, 2*edgeCount for undirected) */\n\treadonly colIndices: Uint32Array;\n\t/** Optional edge weights aligned with colIndices */\n\treadonly values?: Float32Array;\n\t/** Number of nodes in the graph */\n\treadonly nodeCount: number;\n\t/** Number of directed edges (undirected edges counted once) */\n\treadonly edgeCount: number;\n}\n\n/**\n * Mapping from node IDs to CSR indices.\n *\n * Required because CSR uses dense integer indices while graphs\n * may use arbitrary string identifiers.\n */\nexport interface CSRIndexMap {\n\t/** Map from NodeId to CSR row index */\n\treadonly nodeToIndex: ReadonlyMap<NodeId, number>;\n\t/** Map from CSR row index to NodeId */\n\treadonly indexToNode: readonly NodeId[];\n}\n\n/**\n * Combined CSR matrix with index mapping.\n */\nexport interface CSRGraph {\n\treadonly csr: CSRMatrix;\n\treadonly indexMap: CSRIndexMap;\n}\n\n/**\n * Group of TypeGPU typed buffers holding a CSR matrix.\n *\n * Uses TypeGPU's typed buffer API for type-safe GPU computation.\n * Buffers are created with storage usage for compute operations.\n */\nexport interface TypedBufferGroup {\n\t/** Buffer containing rowOffsets data (u32 array) */\n\treadonly rowOffsets: TgpuBuffer<ReturnType<typeof d.arrayOf<typeof d.u32>>> &\n\t\tStorageFlag;\n\t/** Buffer containing colIndices data (u32 array) */\n\treadonly colIndices: TgpuBuffer<ReturnType<typeof d.arrayOf<typeof d.u32>>> &\n\t\tStorageFlag;\n\t/** Buffer containing values data (f32 array, optional) */\n\treadonly values?: TgpuBuffer<ReturnType<typeof d.arrayOf<typeof d.f32>>> &\n\t\tStorageFlag;\n\t/** Number of nodes */\n\treadonly nodeCount: number;\n\t/** Number of edges */\n\treadonly edgeCount: number;\n}\n\n/**\n * Convert a ReadableGraph to CSR format.\n *\n * For undirected graphs, each edge is stored twice (once in each direction).\n * For directed graphs, edges are stored in the out-direction by default.\n *\n * @param graph - The graph to convert\n * @param direction - Edge direction to include (default: 'out' for directed, 'both' for undirected)\n * @returns CSR representation with index mapping\n */\nexport function graphToCSR<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tdirection: Direction = graph.directed ? \"out\" : \"both\",\n): CSRGraph {\n\t// Build node index mapping\n\tconst nodeToIndex = new Map<NodeId, number>();\n\tconst indexToNode: NodeId[] = [];\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tconst index = indexToNode.length;\n\t\tnodeToIndex.set(nodeId, index);\n\t\tindexToNode.push(nodeId);\n\t}\n\n\tconst nodeCount = indexToNode.length;\n\n\t// Count edges per node to build row offsets\n\tconst degrees = new Uint32Array(nodeCount);\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tconst srcIndex = nodeToIndex.get(nodeId);\n\t\tif (srcIndex === undefined) continue;\n\t\tdegrees[srcIndex] = graph.degree(nodeId, direction);\n\t}\n\n\t// Calculate total edge count\n\tlet totalEdges = 0;\n\tfor (let i = 0; i < nodeCount; i++) {\n\t\ttotalEdges += degrees[i] ?? 0;\n\t}\n\n\t// Build rowOffsets array\n\tconst rowOffsets = new Uint32Array(nodeCount + 1);\n\tfor (let i = 0; i < nodeCount; i++) {\n\t\trowOffsets[i + 1] = (rowOffsets[i] ?? 0) + (degrees[i] ?? 0);\n\t}\n\n\t// Build colIndices and values arrays\n\tconst colIndices = new Uint32Array(totalEdges);\n\tconst values = new Float32Array(totalEdges);\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tconst srcIndex = nodeToIndex.get(nodeId);\n\t\tif (srcIndex === undefined) continue;\n\n\t\tconst baseOffset = rowOffsets[srcIndex] ?? 0;\n\t\tlet localOffset = 0;\n\n\t\tfor (const neighbourId of graph.neighbours(nodeId, direction)) {\n\t\t\tconst dstIndex = nodeToIndex.get(neighbourId);\n\t\t\tif (dstIndex === undefined) continue;\n\n\t\t\tconst edgeIdx = baseOffset + localOffset;\n\t\t\tcolIndices[edgeIdx] = dstIndex;\n\n\t\t\t// Get edge weight if available\n\t\t\tconst edge = graph.getEdge(nodeId, neighbourId);\n\t\t\tvalues[edgeIdx] = edge?.weight ?? 1.0;\n\n\t\t\tlocalOffset++;\n\t\t}\n\t}\n\n\tconst csr: CSRMatrix = {\n\t\trowOffsets,\n\t\tcolIndices,\n\t\tvalues,\n\t\tnodeCount,\n\t\tedgeCount: graph.directed ? graph.edgeCount : graph.edgeCount * 2,\n\t};\n\n\tconst indexMap: CSRIndexMap = {\n\t\tnodeToIndex,\n\t\tindexToNode,\n\t};\n\n\treturn { csr, indexMap };\n}\n\n/**\n * Create TypeGPU typed buffers from a CSR matrix.\n *\n * Uses TypeGPU's typed buffer API for type-safe GPU computation.\n * Buffers are created with storage usage for compute operations.\n *\n * @param root - TypeGPU root instance\n * @param csr - CSR matrix to upload\n * @returns Typed buffer group\n *\n * @example\n * ```typescript\n * import { initGPU, csrToTypedBuffers, graphToCSR } from \"graphwise/gpu\";\n *\n * const root = await initGPU();\n * const { csr } = graphToCSR(graph);\n * const buffers = csrToTypedBuffers(root, csr);\n *\n * // Read data back to CPU\n * const rowOffsets = await buffers.rowOffsets.read();\n * console.log(rowOffsets); // Uint32Array\n * ```\n */\nexport function csrToTypedBuffers(\n\troot: GraphwiseGPURoot,\n\tcsr: CSRMatrix,\n): TypedBufferGroup {\n\t// Convert typed arrays to regular arrays for TypeGPU\n\tconst rowOffsetsArray = Array.from(csr.rowOffsets);\n\tconst colIndicesArray = Array.from(csr.colIndices);\n\n\t// Row offsets buffer (u32 array)\n\tconst rowOffsetsBuffer = root\n\t\t.createBuffer(d.arrayOf(d.u32, csr.rowOffsets.length), rowOffsetsArray)\n\t\t.$usage(\"storage\");\n\n\t// Column indices buffer (u32 array)\n\tconst colIndicesBuffer = root\n\t\t.createBuffer(d.arrayOf(d.u32, csr.colIndices.length), colIndicesArray)\n\t\t.$usage(\"storage\");\n\n\t// Values buffer (f32 array, optional)\n\tif (csr.values !== undefined) {\n\t\tconst valuesArray = Array.from(csr.values);\n\t\tconst valuesBuffer = root\n\t\t\t.createBuffer(d.arrayOf(d.f32, csr.values.length), valuesArray)\n\t\t\t.$usage(\"storage\");\n\n\t\treturn {\n\t\t\trowOffsets: rowOffsetsBuffer,\n\t\t\tcolIndices: colIndicesBuffer,\n\t\t\tvalues: valuesBuffer,\n\t\t\tnodeCount: csr.nodeCount,\n\t\t\tedgeCount: csr.edgeCount,\n\t\t};\n\t}\n\n\treturn {\n\t\trowOffsets: rowOffsetsBuffer,\n\t\tcolIndices: colIndicesBuffer,\n\t\tnodeCount: csr.nodeCount,\n\t\tedgeCount: csr.edgeCount,\n\t};\n}\n","/**\n * Pure CPU implementation of batch neighbourhood intersection.\n *\n * Computes intersection size and neighbourhood sizes for multiple node pairs.\n * This is the foundation for ALL Jaccard-family MI variants:\n * J(A,B) = |A ∩ B| / |A ∪ B| = intersection / (sizeA + sizeB - intersection)\n * Cosine(A,B) = |A ∩ B| / sqrt(|A| * |B|)\n * Sorensen-Dice(A,B) = 2|A ∩ B| / (|A| + |B|)\n * Overlap(A,B) = |A ∩ B| / min(|A|, |B|)\n *\n * Uses binary search optimisation: iterate smaller neighbourhood, search in larger.\n *\n * @module gpu/kernels/intersection/logic\n */\n\n/**\n * Result of computing intersection for a single pair.\n */\nexport interface IntersectionResult {\n\t/** Size of the intersection |N(u) ∩ N(v)| */\n\treadonly intersection: number;\n\t/** Size of first neighbourhood |N(u)| */\n\treadonly sizeU: number;\n\t/** Size of second neighbourhood |N(v)| */\n\treadonly sizeV: number;\n}\n\n/**\n * Binary search in a sorted subarray of colIndices.\n *\n * @param colIndices - CSR column indices array (must be sorted per row)\n * @param start - Start index (inclusive)\n * @param end - End index (exclusive)\n * @param target - Value to search for\n * @returns true if target is found between colIndices[start] and colIndices[end-1]\n */\nexport function binarySearch(\n\tcolIndices: Uint32Array,\n\tstart: number,\n\tend: number,\n\ttarget: number,\n): boolean {\n\tlet lo = start;\n\tlet hi = end;\n\n\twhile (lo < hi) {\n\t\tconst mid = lo + Math.floor((hi - lo) / 2);\n\t\tconst midVal = colIndices[mid] ?? 0;\n\n\t\tif (midVal === target) {\n\t\t\treturn true;\n\t\t} else if (midVal < target) {\n\t\t\tlo = mid + 1;\n\t\t} else {\n\t\t\thi = mid;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Compute intersection stats for a single pair of nodes in CSR format.\n *\n * Neighbours must be sorted (CSR guarantees this from graphToCSR).\n *\n * @param rowOffsets - CSR row offset array\n * @param colIndices - CSR column indices array\n * @param u - First node index\n * @param v - Second node index\n * @returns Intersection result with sizes\n */\nexport function intersectionPair(\n\trowOffsets: Uint32Array,\n\tcolIndices: Uint32Array,\n\tu: number,\n\tv: number,\n): IntersectionResult {\n\tconst uStart = rowOffsets[u] ?? 0;\n\tconst uEnd = rowOffsets[u + 1] ?? 0;\n\tconst vStart = rowOffsets[v] ?? 0;\n\tconst vEnd = rowOffsets[v + 1] ?? 0;\n\n\tconst sizeU = uEnd - uStart;\n\tconst sizeV = vEnd - vStart;\n\n\t// Empty neighbourhoods → intersection = 0\n\tif (sizeU === 0 || sizeV === 0) {\n\t\treturn { intersection: 0, sizeU, sizeV };\n\t}\n\n\t// Count intersection by iterating smaller neighbourhood, binary searching in larger\n\tlet intersection = 0;\n\n\tif (sizeU <= sizeV) {\n\t\t// Iterate u's neighbours, search in v's\n\t\tfor (let i = uStart; i < uEnd; i++) {\n\t\t\tconst neighbour = colIndices[i] ?? 0;\n\t\t\tif (binarySearch(colIndices, vStart, vEnd, neighbour)) {\n\t\t\t\tintersection++;\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Iterate v's neighbours, search in u's\n\t\tfor (let i = vStart; i < vEnd; i++) {\n\t\t\tconst neighbour = colIndices[i] ?? 0;\n\t\t\tif (binarySearch(colIndices, uStart, uEnd, neighbour)) {\n\t\t\t\tintersection++;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { intersection, sizeU, sizeV };\n}\n\n/**\n * Batch intersection stats for multiple node pairs.\n *\n * @param rowOffsets - CSR row offset array\n * @param colIndices - CSR column indices array\n * @param pairs - Array of [u, v] node index pairs\n * @returns Object with parallel arrays: intersections, sizeUs, sizeVs\n */\nexport function intersectionBatch(\n\trowOffsets: Uint32Array,\n\tcolIndices: Uint32Array,\n\tpairs: readonly (readonly [number, number])[],\n): {\n\tintersections: Uint32Array;\n\tsizeUs: Uint32Array;\n\tsizeVs: Uint32Array;\n} {\n\tconst n = pairs.length;\n\tconst intersections = new Uint32Array(n);\n\tconst sizeUs = new Uint32Array(n);\n\tconst sizeVs = new Uint32Array(n);\n\n\tfor (let i = 0; i < n; i++) {\n\t\tconst pair = pairs[i];\n\t\tif (pair !== undefined) {\n\t\t\tconst [u, v] = pair;\n\t\t\tconst result = intersectionPair(rowOffsets, colIndices, u, v);\n\t\t\tintersections[i] = result.intersection;\n\t\t\tsizeUs[i] = result.sizeU;\n\t\t\tsizeVs[i] = result.sizeV;\n\t\t}\n\t}\n\n\treturn { intersections, sizeUs, sizeVs };\n}\n\n/**\n * Compute Jaccard from intersection stats.\n * J = intersection / (sizeU + sizeV - intersection)\n */\nexport function jaccardFromIntersection(result: IntersectionResult): number {\n\tconst union = result.sizeU + result.sizeV - result.intersection;\n\treturn union === 0 ? 0 : result.intersection / union;\n}\n\n/**\n * Compute Cosine similarity from intersection stats.\n * Cosine = intersection / sqrt(sizeU * sizeV)\n */\nexport function cosineFromIntersection(result: IntersectionResult): number {\n\tconst denominator = Math.sqrt(result.sizeU * result.sizeV);\n\treturn denominator === 0 ? 0 : result.intersection / denominator;\n}\n\n/**\n * Compute Sorensen-Dice from intersection stats.\n * SD = 2 * intersection / (sizeU + sizeV)\n */\nexport function sorensenDiceFromIntersection(\n\tresult: IntersectionResult,\n): number {\n\tconst denominator = result.sizeU + result.sizeV;\n\treturn denominator === 0 ? 0 : (2 * result.intersection) / denominator;\n}\n\n/**\n * Compute Overlap coefficient from intersection stats.\n * Overlap = intersection / min(sizeU, sizeV)\n */\nexport function overlapFromIntersection(result: IntersectionResult): number {\n\tconst minSize = Math.min(result.sizeU, result.sizeV);\n\treturn minSize === 0 ? 0 : result.intersection / minSize;\n}\n\n/**\n * Compute Hub Promoted from intersection stats.\n * HP = intersection / min(sizeU, sizeV)\n * Note: Same formula as Overlap coefficient\n */\nexport function hubPromotedFromIntersection(\n\tresult: IntersectionResult,\n): number {\n\treturn overlapFromIntersection(result);\n}\n"],"mappings":"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"}
|