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.
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/expansion/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAU,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1E,OAAO,KAAK,EACX,IAAI,EACJ,eAAe,EAGf,eAAe,EAEf,MAAM,SAAS,CAAC;AAqBjB;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,QAAQ,EAC1D,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,eAAe,CAoOjB"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/expansion/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAU,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1E,OAAO,KAAK,EACX,IAAI,EACJ,eAAe,EAGf,eAAe,EAEf,MAAM,SAAS,CAAC;AAqBjB;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,QAAQ,EAC1D,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,eAAe,CAwOjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"maze.d.ts","sourceRoot":"","sources":["../../src/expansion/maze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAU,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EACX,IAAI,EACJ,eAAe,EACf,eAAe,EAEf,MAAM,SAAS,CAAC;AASjB;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,QAAQ,EAC1D,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,eAAe,CAwEjB"}
1
+ {"version":3,"file":"maze.d.ts","sourceRoot":"","sources":["../../src/expansion/maze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAU,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EACX,IAAI,EACJ,eAAe,EACf,eAAe,EAEf,MAAM,SAAS,CAAC;AASjB;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,QAAQ,EAC1D,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,eAAe,CAsEjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"reach.d.ts","sourceRoot":"","sources":["../../src/expansion/reach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAU,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EACX,IAAI,EACJ,eAAe,EACf,eAAe,EAEf,MAAM,SAAS,CAAC;AAIjB;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,QAAQ,EAC3D,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,eAAe,CAiDjB"}
1
+ {"version":3,"file":"reach.d.ts","sourceRoot":"","sources":["../../src/expansion/reach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAU,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EACX,IAAI,EACJ,eAAe,EACf,eAAe,EAEf,MAAM,SAAS,CAAC;AAIjB;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,QAAQ,EAC3D,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,eAAe,CA2EjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"sage.d.ts","sourceRoot":"","sources":["../../src/expansion/sage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAU,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EACX,IAAI,EACJ,eAAe,EACf,eAAe,EAEf,MAAM,SAAS,CAAC;AAGjB;;;;;;;;;;;GAWG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,QAAQ,EAC1D,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,eAAe,CAsDjB"}
1
+ {"version":3,"file":"sage.d.ts","sourceRoot":"","sources":["../../src/expansion/sage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAU,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EACX,IAAI,EACJ,eAAe,EACf,eAAe,EAEf,MAAM,SAAS,CAAC;AAGjB;;;;;;;;;;;GAWG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,QAAQ,EAC1D,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5B,eAAe,CAiDjB"}
@@ -26,6 +26,8 @@ function base(graph, seeds, config) {
26
26
  const { maxNodes = 0, maxIterations = 0, maxPaths = 0, priority = degreePriority, debug = false } = config ?? {};
27
27
  if (seeds.length === 0) return emptyResult("base", startTime);
28
28
  const numFrontiers = seeds.length;
29
+ const allVisited = /* @__PURE__ */ new Set();
30
+ const combinedVisited = /* @__PURE__ */ new Map();
29
31
  const visitedByFrontier = [];
30
32
  const predecessors = [];
31
33
  const queues = [];
@@ -37,15 +39,16 @@ function base(graph, seeds, config) {
37
39
  if (seed === void 0) continue;
38
40
  const seedNode = seed.id;
39
41
  predecessors[i]?.set(seedNode, null);
40
- const seedPriority = priority(seedNode, createPriorityContext(graph, seedNode, i, visitedByFrontier, [], 0));
42
+ combinedVisited.set(seedNode, i);
43
+ allVisited.add(seedNode);
44
+ const seedPriority = priority(seedNode, createPriorityContext(graph, seedNode, i, combinedVisited, allVisited, [], 0));
41
45
  queues[i]?.push({
42
46
  nodeId: seedNode,
43
47
  frontierIndex: i,
44
48
  predecessor: null
45
49
  }, seedPriority);
46
50
  }
47
- const allVisited = /* @__PURE__ */ new Set();
48
- const sampledEdges = /* @__PURE__ */ new Set();
51
+ const sampledEdgeMap = /* @__PURE__ */ new Map();
49
52
  const discoveredPaths = [];
50
53
  let iterations = 0;
51
54
  let edgesTraversed = 0;
@@ -90,6 +93,7 @@ function base(graph, seeds, config) {
90
93
  const frontierVisited = visitedByFrontier[activeFrontier];
91
94
  if (frontierVisited === void 0 || frontierVisited.has(nodeId)) continue;
92
95
  frontierVisited.set(nodeId, activeFrontier);
96
+ combinedVisited.set(nodeId, activeFrontier);
93
97
  if (predecessor !== null) {
94
98
  const predMap = predecessors[activeFrontier];
95
99
  if (predMap !== void 0) predMap.set(nodeId, predecessor);
@@ -111,11 +115,16 @@ function base(graph, seeds, config) {
111
115
  const neighbours = graph.neighbours(nodeId);
112
116
  for (const neighbour of neighbours) {
113
117
  edgesTraversed++;
114
- const edgeKey = nodeId < neighbour ? `${nodeId}::${neighbour}` : `${neighbour}::${nodeId}`;
115
- sampledEdges.add(edgeKey);
118
+ const [s, t] = nodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];
119
+ let targets = sampledEdgeMap.get(s);
120
+ if (targets === void 0) {
121
+ targets = /* @__PURE__ */ new Set();
122
+ sampledEdgeMap.set(s, targets);
123
+ }
124
+ targets.add(t);
116
125
  const frontierVisited = visitedByFrontier[activeFrontier];
117
126
  if (frontierVisited === void 0 || frontierVisited.has(neighbour)) continue;
118
- const neighbourPriority = priority(neighbour, createPriorityContext(graph, neighbour, activeFrontier, visitedByFrontier, discoveredPaths, iterations + 1));
127
+ const neighbourPriority = priority(neighbour, createPriorityContext(graph, neighbour, activeFrontier, combinedVisited, allVisited, discoveredPaths, iterations + 1));
119
128
  queue.push({
120
129
  nodeId: neighbour,
121
130
  frontierIndex: activeFrontier,
@@ -127,14 +136,7 @@ function base(graph, seeds, config) {
127
136
  const endTime = performance.now();
128
137
  const visitedPerFrontier = visitedByFrontier.map((m) => new Set(m.keys()));
129
138
  const edgeTuples = /* @__PURE__ */ new Set();
130
- for (const edgeKey of sampledEdges) {
131
- const parts = edgeKey.split("::");
132
- if (parts.length === 2) {
133
- const source = parts[0];
134
- const target = parts[1];
135
- if (source !== void 0 && target !== void 0) edgeTuples.add([source, target]);
136
- }
137
- }
139
+ for (const [source, targets] of sampledEdgeMap) for (const target of targets) edgeTuples.add([source, target]);
138
140
  return {
139
141
  paths: discoveredPaths,
140
142
  sampledNodes: allVisited,
@@ -154,10 +156,7 @@ function base(graph, seeds, config) {
154
156
  /**
155
157
  * Create priority context for a node.
156
158
  */
157
- function createPriorityContext(graph, nodeId, frontierIndex, visitedByFrontier, discoveredPaths, iteration) {
158
- const combinedVisited = /* @__PURE__ */ new Map();
159
- for (const frontierMap of visitedByFrontier) for (const [id, idx] of frontierMap) combinedVisited.set(id, idx);
160
- const allVisited = new Set(combinedVisited.keys());
159
+ function createPriorityContext(graph, nodeId, frontierIndex, combinedVisited, allVisited, discoveredPaths, iteration) {
161
160
  return {
162
161
  graph,
163
162
  degree: graph.degree(nodeId),
@@ -341,10 +340,12 @@ function hae(graph, seeds, config) {
341
340
  * visited by OTHER frontiers (not the current frontier).
342
341
  */
343
342
  function pipePriority(nodeId, context) {
344
- const graph = context.graph;
345
- const neighbours = new Set(graph.neighbours(nodeId));
343
+ const neighbours = context.graph.neighbours(nodeId);
346
344
  let pathPotential = 0;
347
- for (const [visitedId, frontierIdx] of context.visitedByFrontier) if (frontierIdx !== context.frontierIndex && neighbours.has(visitedId)) pathPotential++;
345
+ for (const neighbour of neighbours) {
346
+ const visitedBy = context.visitedByFrontier.get(neighbour);
347
+ if (visitedBy !== void 0 && visitedBy !== context.frontierIndex) pathPotential++;
348
+ }
348
349
  return context.degree / (1 + pathPotential);
349
350
  }
350
351
  /**
@@ -387,10 +388,7 @@ function sage(graph, seeds, config) {
387
388
  */
388
389
  function sagePriority(nodeId, context) {
389
390
  const pathCount = context.discoveredPaths.length;
390
- if (pathCount > 0 && !inPhase2) {
391
- inPhase2 = true;
392
- for (const path of context.discoveredPaths) for (const node of path.nodes) salienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);
393
- }
391
+ if (pathCount > 0 && !inPhase2) inPhase2 = true;
394
392
  if (pathCount > lastPathCount) {
395
393
  for (let i = lastPathCount; i < pathCount; i++) {
396
394
  const path = context.discoveredPaths[i];
@@ -440,6 +438,23 @@ function jaccard(graph, source, target, config) {
440
438
  */
441
439
  function reach(graph, seeds, config) {
442
440
  let inPhase2 = false;
441
+ const jaccardCache = /* @__PURE__ */ new Map();
442
+ /**
443
+ * Compute Jaccard similarity with caching.
444
+ *
445
+ * Exploits symmetry of Jaccard (J(A,B) = J(B,A)) to reduce
446
+ * duplicate computations when the same pair appears in multiple
447
+ * discovered paths. Key format ensures consistent ordering.
448
+ */
449
+ function cachedJaccard(source, target) {
450
+ const key = source < target ? `${source}::${target}` : `${target}::${source}`;
451
+ let score = jaccardCache.get(key);
452
+ if (score === void 0) {
453
+ score = jaccard(graph, source, target);
454
+ jaccardCache.set(key, score);
455
+ }
456
+ return score;
457
+ }
443
458
  /**
444
459
  * REACH priority function with MI estimation.
445
460
  */
@@ -451,8 +466,8 @@ function reach(graph, seeds, config) {
451
466
  for (const path of context.discoveredPaths) {
452
467
  const fromNodeId = path.fromSeed.id;
453
468
  const toNodeId = path.toSeed.id;
454
- totalMI += jaccard(graph, nodeId, fromNodeId);
455
- totalMI += jaccard(graph, nodeId, toNodeId);
469
+ totalMI += cachedJaccard(nodeId, fromNodeId);
470
+ totalMI += cachedJaccard(nodeId, toNodeId);
456
471
  endpointCount += 2;
457
472
  }
458
473
  const miHat = endpointCount > 0 ? totalMI / endpointCount : 0;
@@ -500,9 +515,12 @@ function maze(graph, seeds, config) {
500
515
  }
501
516
  lastPathCount = pathCount;
502
517
  }
503
- const nodeNeighbours = new Set(graph.neighbours(nodeId));
518
+ const nodeNeighbours = graph.neighbours(nodeId);
504
519
  let pathPotential = 0;
505
- for (const [visitedId, frontierIdx] of context.visitedByFrontier) if (frontierIdx !== context.frontierIndex && nodeNeighbours.has(visitedId)) pathPotential++;
520
+ for (const neighbour of nodeNeighbours) {
521
+ const visitedBy = context.visitedByFrontier.get(neighbour);
522
+ if (visitedBy !== void 0 && visitedBy !== context.frontierIndex) pathPotential++;
523
+ }
506
524
  if (!inPhase2) return context.degree / (1 + pathPotential);
507
525
  const salience = salienceCounts.get(nodeId) ?? 0;
508
526
  return context.degree / (1 + pathPotential) * (1 / (1 + SALIENCE_WEIGHT * salience));