@graphty/algorithms 2.0.2 → 2.0.4

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.
Files changed (48) hide show
  1. package/README.md +42 -43
  2. package/dist/algorithms.js +85 -47
  3. package/dist/algorithms.js.map +1 -1
  4. package/dist/algorithms.standalone.js +86 -48
  5. package/dist/algorithms.standalone.js.map +1 -1
  6. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +9 -3
  7. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -1
  8. package/dist/src/algorithms/centrality/delta-pagerank-simple.js +29 -31
  9. package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -1
  10. package/dist/src/algorithms/centrality/eigenvector.d.ts +6 -3
  11. package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
  12. package/dist/src/algorithms/centrality/eigenvector.js +14 -5
  13. package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
  14. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  15. package/dist/src/algorithms/centrality/pagerank.js +7 -3
  16. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  17. package/dist/src/core/graph.d.ts.map +1 -1
  18. package/dist/src/core/graph.js +7 -3
  19. package/dist/src/core/graph.js.map +1 -1
  20. package/dist/src/errors.d.ts +22 -0
  21. package/dist/src/errors.d.ts.map +1 -1
  22. package/dist/src/errors.js +24 -0
  23. package/dist/src/errors.js.map +1 -1
  24. package/dist/src/index.d.ts +1 -1
  25. package/dist/src/index.d.ts.map +1 -1
  26. package/dist/src/index.js +1 -1
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/indexed/dijkstra.d.ts +2 -0
  29. package/dist/src/indexed/dijkstra.d.ts.map +1 -1
  30. package/dist/src/indexed/dijkstra.js +32 -13
  31. package/dist/src/indexed/dijkstra.js.map +1 -1
  32. package/dist/src/link-prediction/adamic-adar.js +1 -1
  33. package/dist/src/link-prediction/adamic-adar.js.map +1 -1
  34. package/dist/src/link-prediction/common-neighbors.js +1 -1
  35. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  36. package/dist/src/types/index.d.ts +4 -0
  37. package/dist/src/types/index.d.ts.map +1 -1
  38. package/package.json +3 -3
  39. package/src/algorithms/centrality/delta-pagerank-simple.ts +44 -33
  40. package/src/algorithms/centrality/eigenvector.ts +14 -5
  41. package/src/algorithms/centrality/pagerank.ts +9 -3
  42. package/src/core/graph.ts +8 -3
  43. package/src/errors.ts +29 -0
  44. package/src/index.ts +1 -1
  45. package/src/indexed/dijkstra.ts +33 -13
  46. package/src/link-prediction/adamic-adar.ts +1 -1
  47. package/src/link-prediction/common-neighbors.ts +1 -1
  48. package/src/types/index.ts +4 -0
@@ -84,7 +84,8 @@ class Graph {
84
84
  if (!this.config.allowSelfLoops && source === target) {
85
85
  throw new Error("Self-loops are not allowed in this graph");
86
86
  }
87
- if (!this.config.allowParallelEdges && this.hasEdge(source, target)) {
87
+ const replacing = this.hasEdge(source, target);
88
+ if (!this.config.allowParallelEdges && replacing) {
88
89
  throw new Error("Parallel edges are not allowed in this graph");
89
90
  }
90
91
  const edge = { source, target, weight, data };
@@ -106,7 +107,9 @@ class Graph {
106
107
  }
107
108
  }
108
109
  }
109
- this.edgeCount++;
110
+ if (!replacing) {
111
+ this.edgeCount++;
112
+ }
110
113
  this.mutations++;
111
114
  }
112
115
  /**
@@ -351,6 +354,23 @@ class ConvergenceError extends Error {
351
354
  this.name = "ConvergenceError";
352
355
  }
353
356
  }
357
+ class PathWalkError extends Error {
358
+ /**
359
+ * Builds the error and its message.
360
+ * @param source - The search's source node index
361
+ * @param target - The node index the walk started from
362
+ * @param reason - `"gap"` or `"cycle"`, as above
363
+ */
364
+ constructor(source, target, reason) {
365
+ super(
366
+ `cannot walk the shortest path from node ${String(source)} to node ${String(target)}: the predecessor array has a ${reason}`
367
+ );
368
+ this.source = source;
369
+ this.target = target;
370
+ this.reason = reason;
371
+ this.name = "PathWalkError";
372
+ }
373
+ }
354
374
  var lib = {};
355
375
  var SparseTypedFastBitSet = {};
356
376
  var utils = {};
@@ -5252,7 +5272,7 @@ class PriorityDeltaPageRank {
5252
5272
  }
5253
5273
  }
5254
5274
  function eigenvectorCentrality(graph, options = {}) {
5255
- const { maxIterations = 100, tolerance = 1e-6, normalized = true, startVector } = options;
5275
+ const { maxIterations = 100, tolerance = 1e-6, normalized = true, startVector, mode = "in" } = options;
5256
5276
  const nodeIds = Array.from(graph.nodes(), (node) => node.id);
5257
5277
  const keys = nodeIds.map((id) => id.toString());
5258
5278
  const n = keys.length;
@@ -5261,7 +5281,13 @@ function eigenvectorCentrality(graph, options = {}) {
5261
5281
  return centrality;
5262
5282
  }
5263
5283
  const index2 = new Map(keys.map((key, i) => [key, i]));
5264
- const adjacency = nodeIds.map((id) => Array.from(graph.neighbors(id), (m) => index2.get(m.toString()) ?? 0));
5284
+ const feeders = (id) => {
5285
+ if (!graph.isDirected || mode === "out") {
5286
+ return graph.neighbors(id);
5287
+ }
5288
+ return mode === "in" ? graph.inNeighbors(id) : /* @__PURE__ */ new Set([...graph.inNeighbors(id), ...graph.neighbors(id)]);
5289
+ };
5290
+ const adjacency = nodeIds.map((id) => Array.from(feeders(id), (m) => index2.get(m.toString()) ?? 0));
5265
5291
  if (isNilpotent(adjacency)) {
5266
5292
  for (const key of keys) {
5267
5293
  centrality[key] = 0;
@@ -5549,6 +5575,8 @@ class SimpleDeltaPageRank {
5549
5575
  */
5550
5576
  constructor(graph) {
5551
5577
  this.previousScores = null;
5578
+ this.iterations = 0;
5579
+ this.converged = false;
5552
5580
  if (!graph.isDirected) {
5553
5581
  throw new Error("PageRank requires a directed graph");
5554
5582
  }
@@ -5561,20 +5589,32 @@ class SimpleDeltaPageRank {
5561
5589
  * @param options.dampingFactor - Probability of following a link (default: 0.85)
5562
5590
  * @param options.tolerance - Convergence tolerance threshold (default: 1e-6)
5563
5591
  * @param options.maxIterations - Maximum number of iterations (default: 100)
5592
+ * @param options.initialRanks - Starting scores (missing nodes start at 1/n; normalized to sum 1)
5564
5593
  * @param options.personalization - Personalization vector for Personalized PageRank
5565
5594
  * @param options.weight - Edge attribute name for weighted PageRank
5566
- * @returns Map of node IDs to their PageRank scores
5595
+ * @returns Map of node IDs to their PageRank scores; `iterations` and `converged` describe the run
5567
5596
  */
5568
5597
  compute(options = {}) {
5569
- const { dampingFactor = 0.85, tolerance = 1e-6, maxIterations = 100, personalization, weight } = options;
5598
+ const {
5599
+ dampingFactor = 0.85,
5600
+ tolerance = 1e-6,
5601
+ maxIterations = 100,
5602
+ initialRanks,
5603
+ personalization,
5604
+ weight
5605
+ } = options;
5606
+ this.iterations = 0;
5607
+ this.converged = false;
5570
5608
  if (this.nodeCount === 0) {
5609
+ this.converged = true;
5571
5610
  return /* @__PURE__ */ new Map();
5572
5611
  }
5573
5612
  const scores = /* @__PURE__ */ new Map();
5574
5613
  const newScores = /* @__PURE__ */ new Map();
5575
5614
  for (const node of this.graph.nodes()) {
5576
- scores.set(node.id, 1 / this.nodeCount);
5615
+ scores.set(node.id, initialRanks?.get(node.id) ?? 1 / this.nodeCount);
5577
5616
  }
5617
+ this.normalizeMap(scores);
5578
5618
  const outWeights = /* @__PURE__ */ new Map();
5579
5619
  const danglingNodes = /* @__PURE__ */ new Set();
5580
5620
  for (const node of this.graph.nodes()) {
@@ -5582,8 +5622,7 @@ class SimpleDeltaPageRank {
5582
5622
  let outDegree = 0;
5583
5623
  for (const neighbor of Array.from(this.graph.neighbors(node.id))) {
5584
5624
  outDegree++;
5585
- const edge = this.graph.getEdge(node.id, neighbor);
5586
- totalWeight += edge?.weight ?? 1;
5625
+ totalWeight += weight ? this.graph.getEdge(node.id, neighbor)?.weight ?? 1 : 1;
5587
5626
  }
5588
5627
  outWeights.set(node.id, totalWeight);
5589
5628
  if (outDegree === 0) {
@@ -5595,7 +5634,6 @@ class SimpleDeltaPageRank {
5595
5634
  personalVector = new Map(personalization);
5596
5635
  this.normalizeMap(personalVector);
5597
5636
  }
5598
- let activeNodes = new Set(scores.keys());
5599
5637
  let iteration = 0;
5600
5638
  for (iteration = 0; iteration < maxIterations; iteration++) {
5601
5639
  for (const nodeId of scores.keys()) {
@@ -5621,7 +5659,7 @@ class SimpleDeltaPageRank {
5621
5659
  }
5622
5660
  }
5623
5661
  }
5624
- for (const nodeId of activeNodes) {
5662
+ for (const nodeId of scores.keys()) {
5625
5663
  const currentRank = scores.get(nodeId) ?? 0;
5626
5664
  const nodeOutWeight = outWeights.get(nodeId) ?? 0;
5627
5665
  if (nodeOutWeight > 0) {
@@ -5637,33 +5675,21 @@ class SimpleDeltaPageRank {
5637
5675
  }
5638
5676
  }
5639
5677
  }
5640
- const nextActive = /* @__PURE__ */ new Set();
5641
5678
  let maxDiff = 0;
5642
5679
  for (const nodeId of scores.keys()) {
5643
- const oldRank = scores.get(nodeId) ?? 0;
5644
- const newRank = newScores.get(nodeId) ?? 0;
5645
- const diff = Math.abs(newRank - oldRank);
5646
- maxDiff = Math.max(maxDiff, diff);
5647
- if (diff > tolerance / 10) {
5648
- nextActive.add(nodeId);
5649
- for (const neighbor of Array.from(this.graph.neighbors(nodeId))) {
5650
- nextActive.add(neighbor);
5651
- }
5652
- for (const neighbor of Array.from(this.graph.inNeighbors(nodeId))) {
5653
- nextActive.add(neighbor);
5654
- }
5655
- }
5680
+ maxDiff = Math.max(maxDiff, Math.abs((newScores.get(nodeId) ?? 0) - (scores.get(nodeId) ?? 0)));
5656
5681
  }
5657
5682
  scores.clear();
5658
5683
  for (const [k, v] of newScores) {
5659
5684
  scores.set(k, v);
5660
5685
  }
5661
5686
  newScores.clear();
5662
- activeNodes = nextActive;
5663
5687
  if (maxDiff < tolerance) {
5688
+ this.converged = true;
5664
5689
  break;
5665
5690
  }
5666
5691
  }
5692
+ this.iterations = this.converged ? iteration + 1 : iteration;
5667
5693
  this.previousScores = new Map(scores);
5668
5694
  return scores;
5669
5695
  }
@@ -5684,6 +5710,8 @@ class SimpleDeltaPageRank {
5684
5710
  return this.compute(options);
5685
5711
  }
5686
5712
  const { dampingFactor = 0.85, tolerance = 1e-6, maxIterations = 100, personalization, weight } = options;
5713
+ this.iterations = 0;
5714
+ this.converged = false;
5687
5715
  const scores = new Map(this.previousScores);
5688
5716
  const newScores = /* @__PURE__ */ new Map();
5689
5717
  for (const node of this.graph.nodes()) {
@@ -5708,8 +5736,7 @@ class SimpleDeltaPageRank {
5708
5736
  let outDegree = 0;
5709
5737
  for (const neighbor of Array.from(this.graph.neighbors(node.id))) {
5710
5738
  outDegree++;
5711
- const edge = this.graph.getEdge(node.id, neighbor);
5712
- totalWeight += edge?.weight ?? 1;
5739
+ totalWeight += weight ? this.graph.getEdge(node.id, neighbor)?.weight ?? 1 : 1;
5713
5740
  }
5714
5741
  outWeights.set(node.id, totalWeight);
5715
5742
  if (outDegree === 0) {
@@ -5786,9 +5813,11 @@ class SimpleDeltaPageRank {
5786
5813
  newScores.clear();
5787
5814
  activeNodes = nextActive;
5788
5815
  if (maxDiff < tolerance) {
5816
+ this.converged = true;
5789
5817
  break;
5790
5818
  }
5791
5819
  }
5820
+ this.iterations = this.converged ? iteration + 1 : iteration;
5792
5821
  this.previousScores = new Map(scores);
5793
5822
  return scores;
5794
5823
  }
@@ -5831,6 +5860,9 @@ function pageRank$1(graph, options = {}) {
5831
5860
  tolerance,
5832
5861
  maxIterations
5833
5862
  };
5863
+ if (initialRanks) {
5864
+ deltaOptions.initialRanks = initialRanks;
5865
+ }
5834
5866
  if (personalization) {
5835
5867
  deltaOptions.personalization = personalization;
5836
5868
  }
@@ -5845,9 +5877,8 @@ function pageRank$1(graph, options = {}) {
5845
5877
  }
5846
5878
  return {
5847
5879
  ranks: result2,
5848
- iterations: maxIterations,
5849
- // For now, assume we used all iterations
5850
- converged: true
5880
+ iterations: deltaPageRank.iterations,
5881
+ converged: deltaPageRank.converged
5851
5882
  };
5852
5883
  }
5853
5884
  let ranks = /* @__PURE__ */ new Map();
@@ -5949,7 +5980,8 @@ function pageRank$1(graph, options = {}) {
5949
5980
  }
5950
5981
  return {
5951
5982
  ranks: result,
5952
- iterations: iteration + 1,
5983
+ // The loop breaks before incrementing on convergence, and leaves at maxIterations otherwise.
5984
+ iterations: converged ? iteration + 1 : iteration,
5953
5985
  converged
5954
5986
  };
5955
5987
  }
@@ -10121,7 +10153,7 @@ function adamicAdarPrediction(graph, options = {}) {
10121
10153
  for (let j = i + 1; j < nodes.length; j++) {
10122
10154
  const source = nodes[i];
10123
10155
  const target = nodes[j];
10124
- if (!source || !target) {
10156
+ if (source === void 0 || target === void 0) {
10125
10157
  continue;
10126
10158
  }
10127
10159
  if (!includeExisting && graph.hasEdge(source, target)) {
@@ -10299,7 +10331,7 @@ function commonNeighborsPrediction(graph, options = {}) {
10299
10331
  for (let j = i + 1; j < nodes.length; j++) {
10300
10332
  const source = nodes[i];
10301
10333
  const target = nodes[j];
10302
- if (!source || !target) {
10334
+ if (source === void 0 || target === void 0) {
10303
10335
  continue;
10304
10336
  }
10305
10337
  if (!includeExisting && graph.hasEdge(source, target)) {
@@ -18444,7 +18476,7 @@ function checkUniqueColumn(table, column) {
18444
18476
  throw err;
18445
18477
  }
18446
18478
  }
18447
- const WIRE_PRODUCER = `@graphty/graph-format@${"b339f0a4f690"}`;
18479
+ const WIRE_PRODUCER = `@graphty/graph-format@${"4edcfed629f1"}`;
18448
18480
  const SCALAR_VIEWS = /* @__PURE__ */ new Set(["totalWeight", "symmetric"]);
18449
18481
  const encoder = new TextEncoder();
18450
18482
  function assertLittleEndianHost(littleEndian = IS_LITTLE_ENDIAN) {
@@ -24202,6 +24234,22 @@ class IndexedMinHeap {
24202
24234
  this.slot[node] = at;
24203
24235
  }
24204
24236
  }
24237
+ function walkBack(g, predArc, source, target) {
24238
+ const arcs = [];
24239
+ let node = target;
24240
+ while (node !== source) {
24241
+ if (arcs.length >= g.nodeCount - 1) {
24242
+ throw new PathWalkError(source, target, "cycle");
24243
+ }
24244
+ const arc = predArc[node];
24245
+ if (!(arc < g.arcCount)) {
24246
+ throw new PathWalkError(source, target, "gap");
24247
+ }
24248
+ arcs.push(arc);
24249
+ node = arcSourceIn(g.rowPtr, arc);
24250
+ }
24251
+ return arcs;
24252
+ }
24205
24253
  function walkPredArcs(g, predArc, source, target) {
24206
24254
  if (target === source) {
24207
24255
  return Uint32Array.of(source);
@@ -24209,12 +24257,7 @@ function walkPredArcs(g, predArc, source, target) {
24209
24257
  if (predArc[target] === INVALID_INDEX) {
24210
24258
  return new Uint32Array(0);
24211
24259
  }
24212
- const reversed = [target];
24213
- let node = target;
24214
- while (node !== source) {
24215
- node = arcSourceIn(g.rowPtr, predArc[node]);
24216
- reversed.push(node);
24217
- }
24260
+ const reversed = [target, ...walkBack(g, predArc, source, target).map((arc) => arcSourceIn(g.rowPtr, arc))];
24218
24261
  const out = new Uint32Array(reversed.length);
24219
24262
  for (let i = 0; i < reversed.length; i++) {
24220
24263
  out[i] = reversed[reversed.length - 1 - i];
@@ -24225,13 +24268,7 @@ function walkPredEdges(g, predArc, source, target) {
24225
24268
  if (target === source || predArc[target] === INVALID_INDEX) {
24226
24269
  return new Uint32Array(0);
24227
24270
  }
24228
- const reversed = [];
24229
- let node = target;
24230
- while (node !== source) {
24231
- const arc = predArc[node];
24232
- reversed.push(g.arcToEdge[arc]);
24233
- node = arcSourceIn(g.rowPtr, arc);
24234
- }
24271
+ const reversed = walkBack(g, predArc, source, target).map((arc) => g.arcToEdge[arc]);
24235
24272
  const out = new Uint32Array(reversed.length);
24236
24273
  for (let i = 0; i < reversed.length; i++) {
24237
24274
  out[i] = reversed[reversed.length - 1 - i];
@@ -24409,6 +24446,7 @@ export {
24409
24446
  DirectionOptimizedBFS,
24410
24447
  Graph,
24411
24448
  GraphBitSet,
24449
+ PathWalkError,
24412
24450
  PriorityDeltaPageRank,
24413
24451
  PriorityQueue,
24414
24452
  UnionFind,