graphwise 1.8.0 → 1.9.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/adjacency-map-BtKzcuJq.js +229 -0
- package/dist/adjacency-map-BtKzcuJq.js.map +1 -0
- package/dist/adjacency-map-JqBnMNkF.cjs +234 -0
- package/dist/adjacency-map-JqBnMNkF.cjs.map +1 -0
- package/dist/async/index.cjs +15 -242
- package/dist/async/index.js +2 -229
- package/dist/expansion/index.cjs +43 -0
- package/dist/expansion/index.js +2 -0
- package/dist/expansion-ClDhlMK8.js +1704 -0
- package/dist/expansion-ClDhlMK8.js.map +1 -0
- package/dist/expansion-DaTroIyv.cjs +1949 -0
- package/dist/expansion-DaTroIyv.cjs.map +1 -0
- package/dist/extraction/index.cjs +630 -0
- package/dist/extraction/index.cjs.map +1 -0
- package/dist/extraction/index.js +621 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/gpu/csr.d.ts +29 -30
- package/dist/gpu/csr.d.ts.map +1 -1
- package/dist/gpu/dispatch.d.ts +31 -0
- package/dist/gpu/dispatch.d.ts.map +1 -0
- package/dist/gpu/dispatch.unit.test.d.ts +5 -0
- package/dist/gpu/dispatch.unit.test.d.ts.map +1 -0
- package/dist/gpu/index.cjs +15 -410
- package/dist/gpu/index.d.ts +3 -1
- package/dist/gpu/index.d.ts.map +1 -1
- package/dist/gpu/index.js +2 -400
- package/dist/gpu/kernels/bfs/kernel.d.ts +59 -0
- package/dist/gpu/kernels/bfs/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/bfs/logic.d.ts +47 -0
- package/dist/gpu/kernels/bfs/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/bfs/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/bfs/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/degree-histogram/kernel.d.ts +32 -0
- package/dist/gpu/kernels/degree-histogram/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/degree-histogram/logic.d.ts +45 -0
- package/dist/gpu/kernels/degree-histogram/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/degree-histogram/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/degree-histogram/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/jaccard/kernel.d.ts +40 -0
- package/dist/gpu/kernels/jaccard/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/jaccard/logic.d.ts +43 -0
- package/dist/gpu/kernels/jaccard/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/jaccard/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/jaccard/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/pagerank/kernel.d.ts +44 -0
- package/dist/gpu/kernels/pagerank/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/pagerank/logic.d.ts +50 -0
- package/dist/gpu/kernels/pagerank/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/pagerank/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/pagerank/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/kernels/spmv/kernel.d.ts +43 -0
- package/dist/gpu/kernels/spmv/kernel.d.ts.map +1 -0
- package/dist/gpu/kernels/spmv/logic.d.ts +31 -0
- package/dist/gpu/kernels/spmv/logic.d.ts.map +1 -0
- package/dist/gpu/kernels/spmv/logic.unit.test.d.ts +2 -0
- package/dist/gpu/kernels/spmv/logic.unit.test.d.ts.map +1 -0
- package/dist/gpu/operations.d.ts +76 -0
- package/dist/gpu/operations.d.ts.map +1 -0
- package/dist/gpu/operations.unit.test.d.ts +5 -0
- package/dist/gpu/operations.unit.test.d.ts.map +1 -0
- package/dist/gpu/root.d.ts +53 -0
- package/dist/gpu/root.d.ts.map +1 -0
- package/dist/gpu/root.unit.test.d.ts +2 -0
- package/dist/gpu/root.unit.test.d.ts.map +1 -0
- package/dist/gpu/types.d.ts +3 -8
- package/dist/gpu/types.d.ts.map +1 -1
- package/dist/gpu-CHiCN0wa.js +16945 -0
- package/dist/gpu-CHiCN0wa.js.map +1 -0
- package/dist/gpu-Y6owRVMi.cjs +17028 -0
- package/dist/gpu-Y6owRVMi.cjs.map +1 -0
- package/dist/graph/index.cjs +2 -229
- package/dist/graph/index.js +1 -228
- package/dist/index/index.cjs +141 -4040
- package/dist/index/index.js +15 -3917
- package/dist/jaccard-3rCdilwm.js +39 -0
- package/dist/jaccard-3rCdilwm.js.map +1 -0
- package/dist/jaccard-Bys9_dGW.cjs +50 -0
- package/dist/jaccard-Bys9_dGW.cjs.map +1 -0
- package/dist/{kmeans-BIgSyGKu.cjs → kmeans-B8x9D1kt.cjs} +1 -1
- package/dist/{kmeans-BIgSyGKu.cjs.map → kmeans-B8x9D1kt.cjs.map} +1 -1
- package/dist/{kmeans-87ExSUNZ.js → kmeans-DKkL9rAN.js} +1 -1
- package/dist/{kmeans-87ExSUNZ.js.map → kmeans-DKkL9rAN.js.map} +1 -1
- package/dist/ops-djAsQQSh.cjs +277 -0
- package/dist/ops-djAsQQSh.cjs.map +1 -0
- package/dist/ops-upIi6JIi.js +212 -0
- package/dist/ops-upIi6JIi.js.map +1 -0
- package/dist/priority-queue-BIiD1L0k.cjs +148 -0
- package/dist/priority-queue-BIiD1L0k.cjs.map +1 -0
- package/dist/priority-queue-CFDd5cBg.js +143 -0
- package/dist/priority-queue-CFDd5cBg.js.map +1 -0
- package/dist/ranking/index.cjs +43 -0
- package/dist/ranking/index.js +4 -0
- package/dist/ranking/mi/index.cjs +581 -0
- package/dist/ranking/mi/index.cjs.map +1 -0
- package/dist/ranking/mi/index.js +555 -0
- package/dist/ranking/mi/index.js.map +1 -0
- package/dist/ranking-3ez5m67U.js +1016 -0
- package/dist/ranking-3ez5m67U.js.map +1 -0
- package/dist/ranking-DVvajgUZ.cjs +1093 -0
- package/dist/ranking-DVvajgUZ.cjs.map +1 -0
- package/dist/seeds/index.cjs +1 -1
- package/dist/seeds/index.js +1 -1
- package/dist/structures/index.cjs +2 -143
- package/dist/structures/index.js +1 -142
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils-BodeE2Mo.js +22 -0
- package/dist/utils-BodeE2Mo.js.map +1 -0
- package/dist/utils-CDtCcsyF.cjs +33 -0
- package/dist/utils-CDtCcsyF.cjs.map +1 -0
- package/package.json +3 -1
- package/dist/async/index.cjs.map +0 -1
- package/dist/async/index.js.map +0 -1
- package/dist/gpu/context.d.ts +0 -118
- package/dist/gpu/context.d.ts.map +0 -1
- package/dist/gpu/context.unit.test.d.ts +0 -2
- package/dist/gpu/context.unit.test.d.ts.map +0 -1
- package/dist/gpu/index.cjs.map +0 -1
- package/dist/gpu/index.js.map +0 -1
- package/dist/graph/index.cjs.map +0 -1
- package/dist/graph/index.js.map +0 -1
- package/dist/index/index.cjs.map +0 -1
- package/dist/index/index.js.map +0 -1
- package/dist/structures/index.cjs.map +0 -1
- package/dist/structures/index.js.map +0 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { t as collectAsyncIterable } from "./utils-BodeE2Mo.js";
|
|
2
|
+
import { computeJaccard } from "./utils/index.js";
|
|
3
|
+
//#region src/ranking/mi/jaccard.ts
|
|
4
|
+
/**
|
|
5
|
+
* Compute Jaccard similarity between neighbourhoods of two nodes.
|
|
6
|
+
*
|
|
7
|
+
* @param graph - Source graph
|
|
8
|
+
* @param source - Source node ID
|
|
9
|
+
* @param target - Target node ID
|
|
10
|
+
* @param config - Optional configuration
|
|
11
|
+
* @returns Jaccard coefficient in [0, 1]
|
|
12
|
+
*/
|
|
13
|
+
function jaccard(graph, source, target, config) {
|
|
14
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
15
|
+
const { jaccard: jaccardScore, sourceNeighbours, targetNeighbours } = computeJaccard(graph, source, target);
|
|
16
|
+
if (sourceNeighbours.size === 0 && targetNeighbours.size === 0) return 0;
|
|
17
|
+
return Math.max(epsilon, jaccardScore);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Async variant of Jaccard similarity for use with async graph data sources.
|
|
21
|
+
*
|
|
22
|
+
* Fetches both neighbourhoods concurrently, then applies the same formula.
|
|
23
|
+
*/
|
|
24
|
+
async function jaccardAsync(graph, source, target, config) {
|
|
25
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
26
|
+
const [sourceNeighboursArr, targetNeighboursArr] = await Promise.all([collectAsyncIterable(graph.neighbours(source)), collectAsyncIterable(graph.neighbours(target))]);
|
|
27
|
+
const srcSet = new Set(sourceNeighboursArr.filter((n) => n !== target));
|
|
28
|
+
const tgtSet = new Set(targetNeighboursArr.filter((n) => n !== source));
|
|
29
|
+
if (srcSet.size === 0 && tgtSet.size === 0) return 0;
|
|
30
|
+
let intersection = 0;
|
|
31
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
32
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
33
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
34
|
+
return Math.max(epsilon, jaccardScore);
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { jaccardAsync as n, jaccard as t };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=jaccard-3rCdilwm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jaccard-3rCdilwm.js","names":[],"sources":["../src/ranking/mi/jaccard.ts"],"sourcesContent":["/**\n * Jaccard similarity coefficient for edge salience.\n *\n * Measures overlap between neighbourhoods of connected nodes:\n * MI(u,v) = |N(u) ∩ N(v)| / |N(u) ∪ N(v)|\n *\n * Range: [0, 1]\n * - 0: No shared neighbours (low salience)\n * - 1: Identical neighbourhoods (high salience)\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Jaccard similarity between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Jaccard coefficient in [0, 1]\n */\nexport function jaccard<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst {\n\t\tjaccard: jaccardScore,\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t} = computeJaccard(graph, source, target);\n\n\t// Return 0 only when the union is empty (no neighbours on either side)\n\tif (sourceNeighbours.size === 0 && targetNeighbours.size === 0) {\n\t\treturn 0;\n\t}\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, jaccardScore);\n}\n\n/**\n * Async variant of Jaccard similarity for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then applies the same formula.\n */\nexport async function jaccardAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceNeighboursArr, targetNeighboursArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceNeighboursArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetNeighboursArr.filter((n) => n !== source));\n\n\t// Return 0 only when the union is empty (no neighbours on either side)\n\tif (srcSet.size === 0 && tgtSet.size === 0) {\n\t\treturn 0;\n\t}\n\n\t// Compute intersection and union sizes\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\treturn Math.max(epsilon, jaccardScore);\n}\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAgB,QACf,OACA,QACA,QACA,QACS;CACT,MAAM,EAAE,UAAU,UAAU,UAAU,EAAE;CAExC,MAAM,EACL,SAAS,cACT,kBACA,qBACG,eAAe,OAAO,QAAQ,OAAO;AAGzC,KAAI,iBAAiB,SAAS,KAAK,iBAAiB,SAAS,EAC5D,QAAO;AAIR,QAAO,KAAK,IAAI,SAAS,aAAa;;;;;;;AAQvC,eAAsB,aACrB,OACA,QACA,QACA,QACkB;CAClB,MAAM,EAAE,UAAU,UAAU,UAAU,EAAE;CAGxC,MAAM,CAAC,qBAAqB,uBAAuB,MAAM,QAAQ,IAAI,CACpE,qBAAqB,MAAM,WAAW,OAAO,CAAC,EAC9C,qBAAqB,MAAM,WAAW,OAAO,CAAC,CAC9C,CAAC;CAEF,MAAM,SAAS,IAAI,IAAI,oBAAoB,QAAQ,MAAM,MAAM,OAAO,CAAC;CACvE,MAAM,SAAS,IAAI,IAAI,oBAAoB,QAAQ,MAAM,MAAM,OAAO,CAAC;AAGvE,KAAI,OAAO,SAAS,KAAK,OAAO,SAAS,EACxC,QAAO;CAIR,IAAI,eAAe;AACnB,MAAK,MAAM,KAAK,OACf,KAAI,OAAO,IAAI,EAAE,CAAE;CAEpB,MAAM,QAAQ,OAAO,OAAO,OAAO,OAAO;CAE1C,MAAM,eAAe,QAAQ,IAAI,eAAe,QAAQ;AAExD,QAAO,KAAK,IAAI,SAAS,aAAa"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const require_utils = require("./utils-CDtCcsyF.cjs");
|
|
2
|
+
const require_utils$1 = require("./utils/index.cjs");
|
|
3
|
+
//#region src/ranking/mi/jaccard.ts
|
|
4
|
+
/**
|
|
5
|
+
* Compute Jaccard similarity between neighbourhoods of two nodes.
|
|
6
|
+
*
|
|
7
|
+
* @param graph - Source graph
|
|
8
|
+
* @param source - Source node ID
|
|
9
|
+
* @param target - Target node ID
|
|
10
|
+
* @param config - Optional configuration
|
|
11
|
+
* @returns Jaccard coefficient in [0, 1]
|
|
12
|
+
*/
|
|
13
|
+
function jaccard(graph, source, target, config) {
|
|
14
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
15
|
+
const { jaccard: jaccardScore, sourceNeighbours, targetNeighbours } = require_utils$1.computeJaccard(graph, source, target);
|
|
16
|
+
if (sourceNeighbours.size === 0 && targetNeighbours.size === 0) return 0;
|
|
17
|
+
return Math.max(epsilon, jaccardScore);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Async variant of Jaccard similarity for use with async graph data sources.
|
|
21
|
+
*
|
|
22
|
+
* Fetches both neighbourhoods concurrently, then applies the same formula.
|
|
23
|
+
*/
|
|
24
|
+
async function jaccardAsync(graph, source, target, config) {
|
|
25
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
26
|
+
const [sourceNeighboursArr, targetNeighboursArr] = await Promise.all([require_utils.collectAsyncIterable(graph.neighbours(source)), require_utils.collectAsyncIterable(graph.neighbours(target))]);
|
|
27
|
+
const srcSet = new Set(sourceNeighboursArr.filter((n) => n !== target));
|
|
28
|
+
const tgtSet = new Set(targetNeighboursArr.filter((n) => n !== source));
|
|
29
|
+
if (srcSet.size === 0 && tgtSet.size === 0) return 0;
|
|
30
|
+
let intersection = 0;
|
|
31
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
32
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
33
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
34
|
+
return Math.max(epsilon, jaccardScore);
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
Object.defineProperty(exports, "jaccard", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
get: function() {
|
|
40
|
+
return jaccard;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
Object.defineProperty(exports, "jaccardAsync", {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
get: function() {
|
|
46
|
+
return jaccardAsync;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=jaccard-Bys9_dGW.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jaccard-Bys9_dGW.cjs","names":[],"sources":["../src/ranking/mi/jaccard.ts"],"sourcesContent":["/**\n * Jaccard similarity coefficient for edge salience.\n *\n * Measures overlap between neighbourhoods of connected nodes:\n * MI(u,v) = |N(u) ∩ N(v)| / |N(u) ∪ N(v)|\n *\n * Range: [0, 1]\n * - 0: No shared neighbours (low salience)\n * - 1: Identical neighbourhoods (high salience)\n */\n\nimport type { NodeId, NodeData, EdgeData, ReadableGraph } from \"../../graph\";\nimport type { AsyncReadableGraph } from \"../../graph/async-interfaces\";\nimport { computeJaccard } from \"../../utils\";\nimport { collectAsyncIterable } from \"../../async/utils\";\nimport type { MIConfig } from \"./types\";\n\n/**\n * Compute Jaccard similarity between neighbourhoods of two nodes.\n *\n * @param graph - Source graph\n * @param source - Source node ID\n * @param target - Target node ID\n * @param config - Optional configuration\n * @returns Jaccard coefficient in [0, 1]\n */\nexport function jaccard<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): number {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\tconst {\n\t\tjaccard: jaccardScore,\n\t\tsourceNeighbours,\n\t\ttargetNeighbours,\n\t} = computeJaccard(graph, source, target);\n\n\t// Return 0 only when the union is empty (no neighbours on either side)\n\tif (sourceNeighbours.size === 0 && targetNeighbours.size === 0) {\n\t\treturn 0;\n\t}\n\n\t// Apply epsilon floor for numerical stability\n\treturn Math.max(epsilon, jaccardScore);\n}\n\n/**\n * Async variant of Jaccard similarity for use with async graph data sources.\n *\n * Fetches both neighbourhoods concurrently, then applies the same formula.\n */\nexport async function jaccardAsync<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\tsource: NodeId,\n\ttarget: NodeId,\n\tconfig?: MIConfig,\n): Promise<number> {\n\tconst { epsilon = 1e-10 } = config ?? {};\n\n\t// Fetch both neighbourhoods in parallel\n\tconst [sourceNeighboursArr, targetNeighboursArr] = await Promise.all([\n\t\tcollectAsyncIterable(graph.neighbours(source)),\n\t\tcollectAsyncIterable(graph.neighbours(target)),\n\t]);\n\n\tconst srcSet = new Set(sourceNeighboursArr.filter((n) => n !== target));\n\tconst tgtSet = new Set(targetNeighboursArr.filter((n) => n !== source));\n\n\t// Return 0 only when the union is empty (no neighbours on either side)\n\tif (srcSet.size === 0 && tgtSet.size === 0) {\n\t\treturn 0;\n\t}\n\n\t// Compute intersection and union sizes\n\tlet intersection = 0;\n\tfor (const n of srcSet) {\n\t\tif (tgtSet.has(n)) intersection++;\n\t}\n\tconst union = srcSet.size + tgtSet.size - intersection;\n\n\tconst jaccardScore = union > 0 ? intersection / union : 0;\n\n\treturn Math.max(epsilon, jaccardScore);\n}\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAgB,QACf,OACA,QACA,QACA,QACS;CACT,MAAM,EAAE,UAAU,UAAU,UAAU,EAAE;CAExC,MAAM,EACL,SAAS,cACT,kBACA,qBACG,gBAAA,eAAe,OAAO,QAAQ,OAAO;AAGzC,KAAI,iBAAiB,SAAS,KAAK,iBAAiB,SAAS,EAC5D,QAAO;AAIR,QAAO,KAAK,IAAI,SAAS,aAAa;;;;;;;AAQvC,eAAsB,aACrB,OACA,QACA,QACA,QACkB;CAClB,MAAM,EAAE,UAAU,UAAU,UAAU,EAAE;CAGxC,MAAM,CAAC,qBAAqB,uBAAuB,MAAM,QAAQ,IAAI,CACpE,cAAA,qBAAqB,MAAM,WAAW,OAAO,CAAC,EAC9C,cAAA,qBAAqB,MAAM,WAAW,OAAO,CAAC,CAC9C,CAAC;CAEF,MAAM,SAAS,IAAI,IAAI,oBAAoB,QAAQ,MAAM,MAAM,OAAO,CAAC;CACvE,MAAM,SAAS,IAAI,IAAI,oBAAoB,QAAQ,MAAM,MAAM,OAAO,CAAC;AAGvE,KAAI,OAAO,SAAS,KAAK,OAAO,SAAS,EACxC,QAAO;CAIR,IAAI,eAAe;AACnB,MAAK,MAAM,KAAK,OACf,KAAI,OAAO,IAAI,EAAE,CAAE;CAEpB,MAAM,QAAQ,OAAO,OAAO,OAAO,OAAO;CAE1C,MAAM,eAAe,QAAQ,IAAI,eAAe,QAAQ;AAExD,QAAO,KAAK,IAAI,SAAS,aAAa"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kmeans-BIgSyGKu.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
|
+
{"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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kmeans-87ExSUNZ.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":";;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-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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"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
const require_utils = require("./utils-CDtCcsyF.cjs");
|
|
2
|
+
//#region src/async/runners.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a single GraphOp against a synchronous ReadableGraph.
|
|
5
|
+
*
|
|
6
|
+
* Returns a tagged GraphOpResponse so the receiving generator can narrow
|
|
7
|
+
* the result type without type assertions.
|
|
8
|
+
*
|
|
9
|
+
* @param graph - The synchronous graph to query
|
|
10
|
+
* @param op - The operation to resolve
|
|
11
|
+
* @returns The tagged response
|
|
12
|
+
*/
|
|
13
|
+
function resolveSyncOp(graph, op) {
|
|
14
|
+
switch (op.tag) {
|
|
15
|
+
case "neighbours": return {
|
|
16
|
+
tag: "neighbours",
|
|
17
|
+
value: Array.from(graph.neighbours(op.id, op.direction))
|
|
18
|
+
};
|
|
19
|
+
case "degree": return {
|
|
20
|
+
tag: "degree",
|
|
21
|
+
value: graph.degree(op.id, op.direction)
|
|
22
|
+
};
|
|
23
|
+
case "getNode": return {
|
|
24
|
+
tag: "getNode",
|
|
25
|
+
value: graph.getNode(op.id)
|
|
26
|
+
};
|
|
27
|
+
case "getEdge": return {
|
|
28
|
+
tag: "getEdge",
|
|
29
|
+
value: graph.getEdge(op.source, op.target)
|
|
30
|
+
};
|
|
31
|
+
case "hasNode": return {
|
|
32
|
+
tag: "hasNode",
|
|
33
|
+
value: graph.hasNode(op.id)
|
|
34
|
+
};
|
|
35
|
+
case "yield": return { tag: "yield" };
|
|
36
|
+
case "progress": return { tag: "progress" };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Drive a generator to completion using a synchronous graph.
|
|
41
|
+
*
|
|
42
|
+
* The generator yields GraphOp requests; each is resolved immediately
|
|
43
|
+
* against the graph and the tagged response is fed back via gen.next().
|
|
44
|
+
*
|
|
45
|
+
* @param gen - The generator to drive
|
|
46
|
+
* @param graph - The graph to resolve ops against
|
|
47
|
+
* @returns The generator's return value
|
|
48
|
+
*/
|
|
49
|
+
function runSync(gen, graph) {
|
|
50
|
+
let step = gen.next();
|
|
51
|
+
while (step.done !== true) {
|
|
52
|
+
const response = resolveSyncOp(graph, step.value);
|
|
53
|
+
step = gen.next(response);
|
|
54
|
+
}
|
|
55
|
+
return step.value;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve a single GraphOp against an async ReadableGraph.
|
|
59
|
+
*
|
|
60
|
+
* AsyncIterables (neighbours) are collected into readonly arrays so the
|
|
61
|
+
* generator receives the same value type as in sync mode. Returns a tagged
|
|
62
|
+
* GraphOpResponse for type-safe narrowing without assertions.
|
|
63
|
+
*
|
|
64
|
+
* @param graph - The async graph to query
|
|
65
|
+
* @param op - The operation to resolve
|
|
66
|
+
* @returns A promise resolving to the tagged response
|
|
67
|
+
*/
|
|
68
|
+
async function resolveAsyncOp(graph, op) {
|
|
69
|
+
switch (op.tag) {
|
|
70
|
+
case "neighbours": return {
|
|
71
|
+
tag: "neighbours",
|
|
72
|
+
value: await require_utils.collectAsyncIterable(graph.neighbours(op.id, op.direction))
|
|
73
|
+
};
|
|
74
|
+
case "degree": return {
|
|
75
|
+
tag: "degree",
|
|
76
|
+
value: await graph.degree(op.id, op.direction)
|
|
77
|
+
};
|
|
78
|
+
case "getNode": return {
|
|
79
|
+
tag: "getNode",
|
|
80
|
+
value: await graph.getNode(op.id)
|
|
81
|
+
};
|
|
82
|
+
case "getEdge": return {
|
|
83
|
+
tag: "getEdge",
|
|
84
|
+
value: await graph.getEdge(op.source, op.target)
|
|
85
|
+
};
|
|
86
|
+
case "hasNode": return {
|
|
87
|
+
tag: "hasNode",
|
|
88
|
+
value: await graph.hasNode(op.id)
|
|
89
|
+
};
|
|
90
|
+
case "yield": return { tag: "yield" };
|
|
91
|
+
case "progress": return { tag: "progress" };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Drive a generator to completion using an async graph.
|
|
96
|
+
*
|
|
97
|
+
* Extends sync semantics with:
|
|
98
|
+
* - Cancellation via AbortSignal (throws DOMException "AbortError")
|
|
99
|
+
* - Cooperative yielding at `yield` ops (calls yieldStrategy)
|
|
100
|
+
* - Progress callbacks at `progress` ops (may be async for backpressure)
|
|
101
|
+
* - Error propagation: graph errors are forwarded via gen.throw(); if the
|
|
102
|
+
* generator does not handle them, they propagate to the caller
|
|
103
|
+
*
|
|
104
|
+
* @param gen - The generator to drive
|
|
105
|
+
* @param graph - The async graph to resolve ops against
|
|
106
|
+
* @param options - Runner configuration
|
|
107
|
+
* @returns A promise resolving to the generator's return value
|
|
108
|
+
*/
|
|
109
|
+
async function runAsync(gen, graph, options) {
|
|
110
|
+
const signal = options?.signal;
|
|
111
|
+
const onProgress = options?.onProgress;
|
|
112
|
+
const yieldStrategy = options?.yieldStrategy ?? require_utils.defaultYieldStrategy;
|
|
113
|
+
let step = gen.next();
|
|
114
|
+
while (step.done !== true) {
|
|
115
|
+
if (signal?.aborted === true) {
|
|
116
|
+
const abortError = new DOMException("Aborted", "AbortError");
|
|
117
|
+
try {
|
|
118
|
+
gen.throw(abortError);
|
|
119
|
+
} catch {
|
|
120
|
+
throw abortError;
|
|
121
|
+
}
|
|
122
|
+
throw abortError;
|
|
123
|
+
}
|
|
124
|
+
const op = step.value;
|
|
125
|
+
if (op.tag === "yield") {
|
|
126
|
+
await yieldStrategy();
|
|
127
|
+
step = gen.next({ tag: "yield" });
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (op.tag === "progress") {
|
|
131
|
+
if (onProgress !== void 0) {
|
|
132
|
+
const maybePromise = onProgress(op.stats);
|
|
133
|
+
if (maybePromise instanceof Promise) await maybePromise;
|
|
134
|
+
}
|
|
135
|
+
step = gen.next({ tag: "progress" });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
let response;
|
|
139
|
+
try {
|
|
140
|
+
response = await resolveAsyncOp(graph, op);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
step = gen.throw(error);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
step = gen.next(response);
|
|
146
|
+
}
|
|
147
|
+
return step.value;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/async/ops.ts
|
|
151
|
+
function* opNeighbours(id, direction) {
|
|
152
|
+
const response = yield direction !== void 0 ? {
|
|
153
|
+
tag: "neighbours",
|
|
154
|
+
id,
|
|
155
|
+
direction
|
|
156
|
+
} : {
|
|
157
|
+
tag: "neighbours",
|
|
158
|
+
id
|
|
159
|
+
};
|
|
160
|
+
if (response.tag !== "neighbours") throw new TypeError(`Expected neighbours response, got ${response.tag}`);
|
|
161
|
+
return response.value;
|
|
162
|
+
}
|
|
163
|
+
function* opDegree(id, direction) {
|
|
164
|
+
const response = yield direction !== void 0 ? {
|
|
165
|
+
tag: "degree",
|
|
166
|
+
id,
|
|
167
|
+
direction
|
|
168
|
+
} : {
|
|
169
|
+
tag: "degree",
|
|
170
|
+
id
|
|
171
|
+
};
|
|
172
|
+
if (response.tag !== "degree") throw new TypeError(`Expected degree response, got ${response.tag}`);
|
|
173
|
+
return response.value;
|
|
174
|
+
}
|
|
175
|
+
function* opGetNode(id) {
|
|
176
|
+
const response = yield {
|
|
177
|
+
tag: "getNode",
|
|
178
|
+
id
|
|
179
|
+
};
|
|
180
|
+
if (response.tag !== "getNode") throw new TypeError(`Expected getNode response, got ${response.tag}`);
|
|
181
|
+
return response.value;
|
|
182
|
+
}
|
|
183
|
+
function* opGetEdge(source, target) {
|
|
184
|
+
const response = yield {
|
|
185
|
+
tag: "getEdge",
|
|
186
|
+
source,
|
|
187
|
+
target
|
|
188
|
+
};
|
|
189
|
+
if (response.tag !== "getEdge") throw new TypeError(`Expected getEdge response, got ${response.tag}`);
|
|
190
|
+
return response.value;
|
|
191
|
+
}
|
|
192
|
+
function* opHasNode(id) {
|
|
193
|
+
const response = yield {
|
|
194
|
+
tag: "hasNode",
|
|
195
|
+
id
|
|
196
|
+
};
|
|
197
|
+
if (response.tag !== "hasNode") throw new TypeError(`Expected hasNode response, got ${response.tag}`);
|
|
198
|
+
return response.value;
|
|
199
|
+
}
|
|
200
|
+
function* opYield() {
|
|
201
|
+
yield { tag: "yield" };
|
|
202
|
+
}
|
|
203
|
+
function* opProgress(stats) {
|
|
204
|
+
yield {
|
|
205
|
+
tag: "progress",
|
|
206
|
+
stats
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
Object.defineProperty(exports, "opDegree", {
|
|
211
|
+
enumerable: true,
|
|
212
|
+
get: function() {
|
|
213
|
+
return opDegree;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
Object.defineProperty(exports, "opGetEdge", {
|
|
217
|
+
enumerable: true,
|
|
218
|
+
get: function() {
|
|
219
|
+
return opGetEdge;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
Object.defineProperty(exports, "opGetNode", {
|
|
223
|
+
enumerable: true,
|
|
224
|
+
get: function() {
|
|
225
|
+
return opGetNode;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
Object.defineProperty(exports, "opHasNode", {
|
|
229
|
+
enumerable: true,
|
|
230
|
+
get: function() {
|
|
231
|
+
return opHasNode;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
Object.defineProperty(exports, "opNeighbours", {
|
|
235
|
+
enumerable: true,
|
|
236
|
+
get: function() {
|
|
237
|
+
return opNeighbours;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
Object.defineProperty(exports, "opProgress", {
|
|
241
|
+
enumerable: true,
|
|
242
|
+
get: function() {
|
|
243
|
+
return opProgress;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
Object.defineProperty(exports, "opYield", {
|
|
247
|
+
enumerable: true,
|
|
248
|
+
get: function() {
|
|
249
|
+
return opYield;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
Object.defineProperty(exports, "resolveAsyncOp", {
|
|
253
|
+
enumerable: true,
|
|
254
|
+
get: function() {
|
|
255
|
+
return resolveAsyncOp;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
Object.defineProperty(exports, "resolveSyncOp", {
|
|
259
|
+
enumerable: true,
|
|
260
|
+
get: function() {
|
|
261
|
+
return resolveSyncOp;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
Object.defineProperty(exports, "runAsync", {
|
|
265
|
+
enumerable: true,
|
|
266
|
+
get: function() {
|
|
267
|
+
return runAsync;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
Object.defineProperty(exports, "runSync", {
|
|
271
|
+
enumerable: true,
|
|
272
|
+
get: function() {
|
|
273
|
+
return runSync;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
//# sourceMappingURL=ops-djAsQQSh.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ops-djAsQQSh.cjs","names":[],"sources":["../src/async/runners.ts","../src/async/ops.ts"],"sourcesContent":["/**\n * Sync and async runners for generator-based graph algorithms.\n *\n * The runner drives a generator that yields GraphOp objects, resolves each op\n * against the graph, and feeds the result back via gen.next(response). This\n * allows algorithm logic to be written once as a generator and executed\n * synchronously or asynchronously depending on the graph backing.\n *\n * @module async/runners\n */\n\nimport type { NodeData, EdgeData, ReadableGraph } from \"../graph\";\nimport type { AsyncReadableGraph } from \"../graph/async-interfaces\";\nimport type { GraphOp, GraphOpResponse } from \"./protocol\";\nimport type { AsyncRunnerOptions } from \"./types\";\nimport { collectAsyncIterable, defaultYieldStrategy } from \"./utils\";\n\n// ---------------------------------------------------------------------------\n// Sync runner\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a single GraphOp against a synchronous ReadableGraph.\n *\n * Returns a tagged GraphOpResponse so the receiving generator can narrow\n * the result type without type assertions.\n *\n * @param graph - The synchronous graph to query\n * @param op - The operation to resolve\n * @returns The tagged response\n */\nexport function resolveSyncOp<N extends NodeData, E extends EdgeData>(\n\tgraph: ReadableGraph<N, E>,\n\top: GraphOp,\n): GraphOpResponse<N, E> {\n\tswitch (op.tag) {\n\t\tcase \"neighbours\":\n\t\t\treturn {\n\t\t\t\ttag: \"neighbours\",\n\t\t\t\tvalue: Array.from(graph.neighbours(op.id, op.direction)),\n\t\t\t};\n\t\tcase \"degree\":\n\t\t\treturn { tag: \"degree\", value: graph.degree(op.id, op.direction) };\n\t\tcase \"getNode\":\n\t\t\treturn { tag: \"getNode\", value: graph.getNode(op.id) };\n\t\tcase \"getEdge\":\n\t\t\treturn { tag: \"getEdge\", value: graph.getEdge(op.source, op.target) };\n\t\tcase \"hasNode\":\n\t\t\treturn { tag: \"hasNode\", value: graph.hasNode(op.id) };\n\t\tcase \"yield\":\n\t\t\treturn { tag: \"yield\" };\n\t\tcase \"progress\":\n\t\t\treturn { tag: \"progress\" };\n\t}\n}\n\n/**\n * Drive a generator to completion using a synchronous graph.\n *\n * The generator yields GraphOp requests; each is resolved immediately\n * against the graph and the tagged response is fed back via gen.next().\n *\n * @param gen - The generator to drive\n * @param graph - The graph to resolve ops against\n * @returns The generator's return value\n */\nexport function runSync<N extends NodeData, E extends EdgeData, R>(\n\tgen: Generator<GraphOp, R, GraphOpResponse<N, E>>,\n\tgraph: ReadableGraph<N, E>,\n): R {\n\tlet step = gen.next();\n\twhile (step.done !== true) {\n\t\tconst response = resolveSyncOp(graph, step.value);\n\t\tstep = gen.next(response);\n\t}\n\treturn step.value;\n}\n\n// ---------------------------------------------------------------------------\n// Async runner\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a single GraphOp against an async ReadableGraph.\n *\n * AsyncIterables (neighbours) are collected into readonly arrays so the\n * generator receives the same value type as in sync mode. Returns a tagged\n * GraphOpResponse for type-safe narrowing without assertions.\n *\n * @param graph - The async graph to query\n * @param op - The operation to resolve\n * @returns A promise resolving to the tagged response\n */\nexport async function resolveAsyncOp<N extends NodeData, E extends EdgeData>(\n\tgraph: AsyncReadableGraph<N, E>,\n\top: GraphOp,\n): Promise<GraphOpResponse<N, E>> {\n\tswitch (op.tag) {\n\t\tcase \"neighbours\":\n\t\t\treturn {\n\t\t\t\ttag: \"neighbours\",\n\t\t\t\tvalue: await collectAsyncIterable(\n\t\t\t\t\tgraph.neighbours(op.id, op.direction),\n\t\t\t\t),\n\t\t\t};\n\t\tcase \"degree\":\n\t\t\treturn { tag: \"degree\", value: await graph.degree(op.id, op.direction) };\n\t\tcase \"getNode\":\n\t\t\treturn { tag: \"getNode\", value: await graph.getNode(op.id) };\n\t\tcase \"getEdge\":\n\t\t\treturn {\n\t\t\t\ttag: \"getEdge\",\n\t\t\t\tvalue: await graph.getEdge(op.source, op.target),\n\t\t\t};\n\t\tcase \"hasNode\":\n\t\t\treturn { tag: \"hasNode\", value: await graph.hasNode(op.id) };\n\t\tcase \"yield\":\n\t\t\treturn { tag: \"yield\" };\n\t\tcase \"progress\":\n\t\t\treturn { tag: \"progress\" };\n\t}\n}\n\n/**\n * Drive a generator to completion using an async graph.\n *\n * Extends sync semantics with:\n * - Cancellation via AbortSignal (throws DOMException \"AbortError\")\n * - Cooperative yielding at `yield` ops (calls yieldStrategy)\n * - Progress callbacks at `progress` ops (may be async for backpressure)\n * - Error propagation: graph errors are forwarded via gen.throw(); if the\n * generator does not handle them, they propagate to the caller\n *\n * @param gen - The generator to drive\n * @param graph - The async graph to resolve ops against\n * @param options - Runner configuration\n * @returns A promise resolving to the generator's return value\n */\nexport async function runAsync<N extends NodeData, E extends EdgeData, R>(\n\tgen: Generator<GraphOp, R, GraphOpResponse<N, E>>,\n\tgraph: AsyncReadableGraph<N, E>,\n\toptions?: AsyncRunnerOptions,\n): Promise<R> {\n\tconst signal = options?.signal;\n\tconst onProgress = options?.onProgress;\n\tconst yieldStrategy = options?.yieldStrategy ?? defaultYieldStrategy;\n\n\tlet step = gen.next();\n\n\twhile (step.done !== true) {\n\t\t// Check for cancellation before processing each op. Throw the error\n\t\t// into the generator so that any finally blocks in the algorithm run\n\t\t// before the error propagates to the caller.\n\t\tif (signal?.aborted === true) {\n\t\t\tconst abortError = new DOMException(\"Aborted\", \"AbortError\");\n\t\t\ttry {\n\t\t\t\tgen.throw(abortError);\n\t\t\t} catch {\n\t\t\t\t// Generator did not handle the error — propagate it\n\t\t\t\tthrow abortError;\n\t\t\t}\n\t\t\t// Generator handled the error but we still honour cancellation\n\t\t\tthrow abortError;\n\t\t}\n\n\t\tconst op = step.value;\n\n\t\t// Handle cooperative yield ops without hitting the graph\n\t\tif (op.tag === \"yield\") {\n\t\t\tawait yieldStrategy();\n\t\t\tstep = gen.next({ tag: \"yield\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Handle progress ops: call the callback (awaiting if async)\n\t\tif (op.tag === \"progress\") {\n\t\t\tif (onProgress !== undefined) {\n\t\t\t\tconst maybePromise = onProgress(op.stats);\n\t\t\t\tif (maybePromise instanceof Promise) {\n\t\t\t\t\tawait maybePromise;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstep = gen.next({ tag: \"progress\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Resolve graph ops, forwarding any errors into the generator\n\t\tlet response: GraphOpResponse<N, E>;\n\t\ttry {\n\t\t\tresponse = await resolveAsyncOp(graph, op);\n\t\t} catch (error) {\n\t\t\t// Forward the error into the generator; if unhandled, it propagates\n\t\t\tstep = gen.throw(error);\n\t\t\tcontinue;\n\t\t}\n\n\t\tstep = gen.next(response);\n\t}\n\n\treturn step.value;\n}\n","/**\n * Type-safe yield helpers for graph operations.\n *\n * Each function is a sub-generator that yields one GraphOp and returns\n * the correctly-typed result. Narrowing is done via the tagged discriminated\n * union in GraphOpResponse — no type assertions needed.\n *\n * Use with `yield*` inside algorithm generators.\n *\n * @module async/ops\n */\n\nimport type { NodeId, NodeData, EdgeData, Direction } from \"../graph\";\nimport type { GraphOp, GraphOpResponse, ProgressStats } from \"./protocol\";\n\nexport function* opNeighbours<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tid: NodeId,\n\tdirection?: Direction,\n): Generator<GraphOp, readonly NodeId[], GraphOpResponse<N, E>> {\n\tconst op: GraphOp =\n\t\tdirection !== undefined\n\t\t\t? { tag: \"neighbours\", id, direction }\n\t\t\t: { tag: \"neighbours\", id };\n\tconst response: GraphOpResponse<N, E> = yield op;\n\tif (response.tag !== \"neighbours\") {\n\t\tthrow new TypeError(`Expected neighbours response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opDegree<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tid: NodeId,\n\tdirection?: Direction,\n): Generator<GraphOp, number, GraphOpResponse<N, E>> {\n\tconst op: GraphOp =\n\t\tdirection !== undefined\n\t\t\t? { tag: \"degree\", id, direction }\n\t\t\t: { tag: \"degree\", id };\n\tconst response: GraphOpResponse<N, E> = yield op;\n\tif (response.tag !== \"degree\") {\n\t\tthrow new TypeError(`Expected degree response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opGetNode<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(id: NodeId): Generator<GraphOp, N | undefined, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield { tag: \"getNode\", id };\n\tif (response.tag !== \"getNode\") {\n\t\tthrow new TypeError(`Expected getNode response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opGetEdge<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(\n\tsource: NodeId,\n\ttarget: NodeId,\n): Generator<GraphOp, E | undefined, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield {\n\t\ttag: \"getEdge\",\n\t\tsource,\n\t\ttarget,\n\t};\n\tif (response.tag !== \"getEdge\") {\n\t\tthrow new TypeError(`Expected getEdge response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opHasNode<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(id: NodeId): Generator<GraphOp, boolean, GraphOpResponse<N, E>> {\n\tconst response: GraphOpResponse<N, E> = yield { tag: \"hasNode\", id };\n\tif (response.tag !== \"hasNode\") {\n\t\tthrow new TypeError(`Expected hasNode response, got ${response.tag}`);\n\t}\n\treturn response.value;\n}\n\nexport function* opYield<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(): Generator<GraphOp, void, GraphOpResponse<N, E>> {\n\tyield { tag: \"yield\" };\n}\n\nexport function* opProgress<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n>(stats: ProgressStats): Generator<GraphOp, void, GraphOpResponse<N, E>> {\n\tyield { tag: \"progress\", stats };\n}\n"],"mappings":"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"}
|