graphwise 1.4.0 → 1.4.1
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/expansion/base.d.ts.map +1 -1
- package/dist/expansion/maze.d.ts.map +1 -1
- package/dist/expansion/reach.d.ts.map +1 -1
- package/dist/expansion/sage.d.ts.map +1 -1
- package/dist/index/index.cjs +47 -29
- package/dist/index/index.cjs.map +1 -1
- package/dist/index/index.js +47 -29
- package/dist/index/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index/index.js
CHANGED
|
@@ -25,6 +25,8 @@ function base(graph, seeds, config) {
|
|
|
25
25
|
const { maxNodes = 0, maxIterations = 0, maxPaths = 0, priority = degreePriority, debug = false } = config ?? {};
|
|
26
26
|
if (seeds.length === 0) return emptyResult("base", startTime);
|
|
27
27
|
const numFrontiers = seeds.length;
|
|
28
|
+
const allVisited = /* @__PURE__ */ new Set();
|
|
29
|
+
const combinedVisited = /* @__PURE__ */ new Map();
|
|
28
30
|
const visitedByFrontier = [];
|
|
29
31
|
const predecessors = [];
|
|
30
32
|
const queues = [];
|
|
@@ -36,15 +38,16 @@ function base(graph, seeds, config) {
|
|
|
36
38
|
if (seed === void 0) continue;
|
|
37
39
|
const seedNode = seed.id;
|
|
38
40
|
predecessors[i]?.set(seedNode, null);
|
|
39
|
-
|
|
41
|
+
combinedVisited.set(seedNode, i);
|
|
42
|
+
allVisited.add(seedNode);
|
|
43
|
+
const seedPriority = priority(seedNode, createPriorityContext(graph, seedNode, i, combinedVisited, allVisited, [], 0));
|
|
40
44
|
queues[i]?.push({
|
|
41
45
|
nodeId: seedNode,
|
|
42
46
|
frontierIndex: i,
|
|
43
47
|
predecessor: null
|
|
44
48
|
}, seedPriority);
|
|
45
49
|
}
|
|
46
|
-
const
|
|
47
|
-
const sampledEdges = /* @__PURE__ */ new Set();
|
|
50
|
+
const sampledEdgeMap = /* @__PURE__ */ new Map();
|
|
48
51
|
const discoveredPaths = [];
|
|
49
52
|
let iterations = 0;
|
|
50
53
|
let edgesTraversed = 0;
|
|
@@ -89,6 +92,7 @@ function base(graph, seeds, config) {
|
|
|
89
92
|
const frontierVisited = visitedByFrontier[activeFrontier];
|
|
90
93
|
if (frontierVisited === void 0 || frontierVisited.has(nodeId)) continue;
|
|
91
94
|
frontierVisited.set(nodeId, activeFrontier);
|
|
95
|
+
combinedVisited.set(nodeId, activeFrontier);
|
|
92
96
|
if (predecessor !== null) {
|
|
93
97
|
const predMap = predecessors[activeFrontier];
|
|
94
98
|
if (predMap !== void 0) predMap.set(nodeId, predecessor);
|
|
@@ -110,11 +114,16 @@ function base(graph, seeds, config) {
|
|
|
110
114
|
const neighbours = graph.neighbours(nodeId);
|
|
111
115
|
for (const neighbour of neighbours) {
|
|
112
116
|
edgesTraversed++;
|
|
113
|
-
const
|
|
114
|
-
|
|
117
|
+
const [s, t] = nodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];
|
|
118
|
+
let targets = sampledEdgeMap.get(s);
|
|
119
|
+
if (targets === void 0) {
|
|
120
|
+
targets = /* @__PURE__ */ new Set();
|
|
121
|
+
sampledEdgeMap.set(s, targets);
|
|
122
|
+
}
|
|
123
|
+
targets.add(t);
|
|
115
124
|
const frontierVisited = visitedByFrontier[activeFrontier];
|
|
116
125
|
if (frontierVisited === void 0 || frontierVisited.has(neighbour)) continue;
|
|
117
|
-
const neighbourPriority = priority(neighbour, createPriorityContext(graph, neighbour, activeFrontier,
|
|
126
|
+
const neighbourPriority = priority(neighbour, createPriorityContext(graph, neighbour, activeFrontier, combinedVisited, allVisited, discoveredPaths, iterations + 1));
|
|
118
127
|
queue.push({
|
|
119
128
|
nodeId: neighbour,
|
|
120
129
|
frontierIndex: activeFrontier,
|
|
@@ -126,14 +135,7 @@ function base(graph, seeds, config) {
|
|
|
126
135
|
const endTime = performance.now();
|
|
127
136
|
const visitedPerFrontier = visitedByFrontier.map((m) => new Set(m.keys()));
|
|
128
137
|
const edgeTuples = /* @__PURE__ */ new Set();
|
|
129
|
-
for (const
|
|
130
|
-
const parts = edgeKey.split("::");
|
|
131
|
-
if (parts.length === 2) {
|
|
132
|
-
const source = parts[0];
|
|
133
|
-
const target = parts[1];
|
|
134
|
-
if (source !== void 0 && target !== void 0) edgeTuples.add([source, target]);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
138
|
+
for (const [source, targets] of sampledEdgeMap) for (const target of targets) edgeTuples.add([source, target]);
|
|
137
139
|
return {
|
|
138
140
|
paths: discoveredPaths,
|
|
139
141
|
sampledNodes: allVisited,
|
|
@@ -153,10 +155,7 @@ function base(graph, seeds, config) {
|
|
|
153
155
|
/**
|
|
154
156
|
* Create priority context for a node.
|
|
155
157
|
*/
|
|
156
|
-
function createPriorityContext(graph, nodeId, frontierIndex,
|
|
157
|
-
const combinedVisited = /* @__PURE__ */ new Map();
|
|
158
|
-
for (const frontierMap of visitedByFrontier) for (const [id, idx] of frontierMap) combinedVisited.set(id, idx);
|
|
159
|
-
const allVisited = new Set(combinedVisited.keys());
|
|
158
|
+
function createPriorityContext(graph, nodeId, frontierIndex, combinedVisited, allVisited, discoveredPaths, iteration) {
|
|
160
159
|
return {
|
|
161
160
|
graph,
|
|
162
161
|
degree: graph.degree(nodeId),
|
|
@@ -340,10 +339,12 @@ function hae(graph, seeds, config) {
|
|
|
340
339
|
* visited by OTHER frontiers (not the current frontier).
|
|
341
340
|
*/
|
|
342
341
|
function pipePriority(nodeId, context) {
|
|
343
|
-
const
|
|
344
|
-
const neighbours = new Set(graph.neighbours(nodeId));
|
|
342
|
+
const neighbours = context.graph.neighbours(nodeId);
|
|
345
343
|
let pathPotential = 0;
|
|
346
|
-
for (const
|
|
344
|
+
for (const neighbour of neighbours) {
|
|
345
|
+
const visitedBy = context.visitedByFrontier.get(neighbour);
|
|
346
|
+
if (visitedBy !== void 0 && visitedBy !== context.frontierIndex) pathPotential++;
|
|
347
|
+
}
|
|
347
348
|
return context.degree / (1 + pathPotential);
|
|
348
349
|
}
|
|
349
350
|
/**
|
|
@@ -386,10 +387,7 @@ function sage(graph, seeds, config) {
|
|
|
386
387
|
*/
|
|
387
388
|
function sagePriority(nodeId, context) {
|
|
388
389
|
const pathCount = context.discoveredPaths.length;
|
|
389
|
-
if (pathCount > 0 && !inPhase2)
|
|
390
|
-
inPhase2 = true;
|
|
391
|
-
for (const path of context.discoveredPaths) for (const node of path.nodes) salienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);
|
|
392
|
-
}
|
|
390
|
+
if (pathCount > 0 && !inPhase2) inPhase2 = true;
|
|
393
391
|
if (pathCount > lastPathCount) {
|
|
394
392
|
for (let i = lastPathCount; i < pathCount; i++) {
|
|
395
393
|
const path = context.discoveredPaths[i];
|
|
@@ -439,6 +437,23 @@ function jaccard(graph, source, target, config) {
|
|
|
439
437
|
*/
|
|
440
438
|
function reach(graph, seeds, config) {
|
|
441
439
|
let inPhase2 = false;
|
|
440
|
+
const jaccardCache = /* @__PURE__ */ new Map();
|
|
441
|
+
/**
|
|
442
|
+
* Compute Jaccard similarity with caching.
|
|
443
|
+
*
|
|
444
|
+
* Exploits symmetry of Jaccard (J(A,B) = J(B,A)) to reduce
|
|
445
|
+
* duplicate computations when the same pair appears in multiple
|
|
446
|
+
* discovered paths. Key format ensures consistent ordering.
|
|
447
|
+
*/
|
|
448
|
+
function cachedJaccard(source, target) {
|
|
449
|
+
const key = source < target ? `${source}::${target}` : `${target}::${source}`;
|
|
450
|
+
let score = jaccardCache.get(key);
|
|
451
|
+
if (score === void 0) {
|
|
452
|
+
score = jaccard(graph, source, target);
|
|
453
|
+
jaccardCache.set(key, score);
|
|
454
|
+
}
|
|
455
|
+
return score;
|
|
456
|
+
}
|
|
442
457
|
/**
|
|
443
458
|
* REACH priority function with MI estimation.
|
|
444
459
|
*/
|
|
@@ -450,8 +465,8 @@ function reach(graph, seeds, config) {
|
|
|
450
465
|
for (const path of context.discoveredPaths) {
|
|
451
466
|
const fromNodeId = path.fromSeed.id;
|
|
452
467
|
const toNodeId = path.toSeed.id;
|
|
453
|
-
totalMI +=
|
|
454
|
-
totalMI +=
|
|
468
|
+
totalMI += cachedJaccard(nodeId, fromNodeId);
|
|
469
|
+
totalMI += cachedJaccard(nodeId, toNodeId);
|
|
455
470
|
endpointCount += 2;
|
|
456
471
|
}
|
|
457
472
|
const miHat = endpointCount > 0 ? totalMI / endpointCount : 0;
|
|
@@ -499,9 +514,12 @@ function maze(graph, seeds, config) {
|
|
|
499
514
|
}
|
|
500
515
|
lastPathCount = pathCount;
|
|
501
516
|
}
|
|
502
|
-
const nodeNeighbours =
|
|
517
|
+
const nodeNeighbours = graph.neighbours(nodeId);
|
|
503
518
|
let pathPotential = 0;
|
|
504
|
-
for (const
|
|
519
|
+
for (const neighbour of nodeNeighbours) {
|
|
520
|
+
const visitedBy = context.visitedByFrontier.get(neighbour);
|
|
521
|
+
if (visitedBy !== void 0 && visitedBy !== context.frontierIndex) pathPotential++;
|
|
522
|
+
}
|
|
505
523
|
if (!inPhase2) return context.degree / (1 + pathPotential);
|
|
506
524
|
const salience = salienceCounts.get(nodeId) ?? 0;
|
|
507
525
|
return context.degree / (1 + pathPotential) * (1 / (1 + SALIENCE_WEIGHT * salience));
|