@graphty/algorithms 2.0.3 → 2.0.5
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/README.md +2 -2
- package/dist/algorithms.js +85 -47
- package/dist/algorithms.js.map +1 -1
- package/dist/algorithms.standalone.js +97 -49
- package/dist/algorithms.standalone.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +9 -3
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +29 -31
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +6 -3
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +14 -5
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +7 -3
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +7 -3
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/errors.d.ts +22 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +24 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexed/dijkstra.d.ts +2 -0
- package/dist/src/indexed/dijkstra.d.ts.map +1 -1
- package/dist/src/indexed/dijkstra.js +32 -13
- package/dist/src/indexed/dijkstra.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.js +1 -1
- package/dist/src/link-prediction/adamic-adar.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +1 -1
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/types/index.d.ts +4 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/algorithms/centrality/delta-pagerank-simple.ts +44 -33
- package/src/algorithms/centrality/eigenvector.ts +14 -5
- package/src/algorithms/centrality/pagerank.ts +9 -3
- package/src/core/graph.ts +8 -3
- package/src/errors.ts +29 -0
- package/src/index.ts +1 -1
- package/src/indexed/dijkstra.ts +33 -13
- package/src/link-prediction/adamic-adar.ts +1 -1
- package/src/link-prediction/common-neighbors.ts +1 -1
- package/src/types/index.ts +4 -0
package/README.md
CHANGED
|
@@ -110,8 +110,8 @@ class Graph {
|
|
|
110
110
|
```typescript
|
|
111
111
|
interface GraphConfig {
|
|
112
112
|
directed: boolean; // Default: false
|
|
113
|
-
allowSelfLoops: boolean; // Default:
|
|
114
|
-
allowParallelEdges: boolean; // Default: false
|
|
113
|
+
allowSelfLoops: boolean; // Default: true
|
|
114
|
+
allowParallelEdges: boolean; // Default: false. When true, re-adding a pair replaces its edge (one edge per pair)
|
|
115
115
|
}
|
|
116
116
|
```
|
|
117
117
|
|
package/dist/algorithms.js
CHANGED
|
@@ -85,7 +85,8 @@ class Graph {
|
|
|
85
85
|
if (!this.config.allowSelfLoops && source === target) {
|
|
86
86
|
throw new Error("Self-loops are not allowed in this graph");
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
const replacing = this.hasEdge(source, target);
|
|
89
|
+
if (!this.config.allowParallelEdges && replacing) {
|
|
89
90
|
throw new Error("Parallel edges are not allowed in this graph");
|
|
90
91
|
}
|
|
91
92
|
const edge = { source, target, weight, data };
|
|
@@ -107,7 +108,9 @@ class Graph {
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
|
-
|
|
111
|
+
if (!replacing) {
|
|
112
|
+
this.edgeCount++;
|
|
113
|
+
}
|
|
111
114
|
this.mutations++;
|
|
112
115
|
}
|
|
113
116
|
/**
|
|
@@ -352,6 +355,23 @@ class ConvergenceError extends Error {
|
|
|
352
355
|
this.name = "ConvergenceError";
|
|
353
356
|
}
|
|
354
357
|
}
|
|
358
|
+
class PathWalkError extends Error {
|
|
359
|
+
/**
|
|
360
|
+
* Builds the error and its message.
|
|
361
|
+
* @param source - The search's source node index
|
|
362
|
+
* @param target - The node index the walk started from
|
|
363
|
+
* @param reason - `"gap"` or `"cycle"`, as above
|
|
364
|
+
*/
|
|
365
|
+
constructor(source, target, reason) {
|
|
366
|
+
super(
|
|
367
|
+
`cannot walk the shortest path from node ${String(source)} to node ${String(target)}: the predecessor array has a ${reason}`
|
|
368
|
+
);
|
|
369
|
+
this.source = source;
|
|
370
|
+
this.target = target;
|
|
371
|
+
this.reason = reason;
|
|
372
|
+
this.name = "PathWalkError";
|
|
373
|
+
}
|
|
374
|
+
}
|
|
355
375
|
var lib = {};
|
|
356
376
|
var SparseTypedFastBitSet = {};
|
|
357
377
|
var utils = {};
|
|
@@ -5253,7 +5273,7 @@ class PriorityDeltaPageRank {
|
|
|
5253
5273
|
}
|
|
5254
5274
|
}
|
|
5255
5275
|
function eigenvectorCentrality(graph, options = {}) {
|
|
5256
|
-
const { maxIterations = 100, tolerance = 1e-6, normalized = true, startVector } = options;
|
|
5276
|
+
const { maxIterations = 100, tolerance = 1e-6, normalized = true, startVector, mode = "in" } = options;
|
|
5257
5277
|
const nodeIds = Array.from(graph.nodes(), (node) => node.id);
|
|
5258
5278
|
const keys = nodeIds.map((id) => id.toString());
|
|
5259
5279
|
const n = keys.length;
|
|
@@ -5262,7 +5282,13 @@ function eigenvectorCentrality(graph, options = {}) {
|
|
|
5262
5282
|
return centrality;
|
|
5263
5283
|
}
|
|
5264
5284
|
const index2 = new Map(keys.map((key, i) => [key, i]));
|
|
5265
|
-
const
|
|
5285
|
+
const feeders = (id) => {
|
|
5286
|
+
if (!graph.isDirected || mode === "out") {
|
|
5287
|
+
return graph.neighbors(id);
|
|
5288
|
+
}
|
|
5289
|
+
return mode === "in" ? graph.inNeighbors(id) : /* @__PURE__ */ new Set([...graph.inNeighbors(id), ...graph.neighbors(id)]);
|
|
5290
|
+
};
|
|
5291
|
+
const adjacency = nodeIds.map((id) => Array.from(feeders(id), (m) => index2.get(m.toString()) ?? 0));
|
|
5266
5292
|
if (isNilpotent(adjacency)) {
|
|
5267
5293
|
for (const key of keys) {
|
|
5268
5294
|
centrality[key] = 0;
|
|
@@ -5550,6 +5576,8 @@ class SimpleDeltaPageRank {
|
|
|
5550
5576
|
*/
|
|
5551
5577
|
constructor(graph) {
|
|
5552
5578
|
this.previousScores = null;
|
|
5579
|
+
this.iterations = 0;
|
|
5580
|
+
this.converged = false;
|
|
5553
5581
|
if (!graph.isDirected) {
|
|
5554
5582
|
throw new Error("PageRank requires a directed graph");
|
|
5555
5583
|
}
|
|
@@ -5562,20 +5590,32 @@ class SimpleDeltaPageRank {
|
|
|
5562
5590
|
* @param options.dampingFactor - Probability of following a link (default: 0.85)
|
|
5563
5591
|
* @param options.tolerance - Convergence tolerance threshold (default: 1e-6)
|
|
5564
5592
|
* @param options.maxIterations - Maximum number of iterations (default: 100)
|
|
5593
|
+
* @param options.initialRanks - Starting scores (missing nodes start at 1/n; normalized to sum 1)
|
|
5565
5594
|
* @param options.personalization - Personalization vector for Personalized PageRank
|
|
5566
5595
|
* @param options.weight - Edge attribute name for weighted PageRank
|
|
5567
|
-
* @returns Map of node IDs to their PageRank scores
|
|
5596
|
+
* @returns Map of node IDs to their PageRank scores; `iterations` and `converged` describe the run
|
|
5568
5597
|
*/
|
|
5569
5598
|
compute(options = {}) {
|
|
5570
|
-
const {
|
|
5599
|
+
const {
|
|
5600
|
+
dampingFactor = 0.85,
|
|
5601
|
+
tolerance = 1e-6,
|
|
5602
|
+
maxIterations = 100,
|
|
5603
|
+
initialRanks,
|
|
5604
|
+
personalization,
|
|
5605
|
+
weight
|
|
5606
|
+
} = options;
|
|
5607
|
+
this.iterations = 0;
|
|
5608
|
+
this.converged = false;
|
|
5571
5609
|
if (this.nodeCount === 0) {
|
|
5610
|
+
this.converged = true;
|
|
5572
5611
|
return /* @__PURE__ */ new Map();
|
|
5573
5612
|
}
|
|
5574
5613
|
const scores = /* @__PURE__ */ new Map();
|
|
5575
5614
|
const newScores = /* @__PURE__ */ new Map();
|
|
5576
5615
|
for (const node of this.graph.nodes()) {
|
|
5577
|
-
scores.set(node.id, 1 / this.nodeCount);
|
|
5616
|
+
scores.set(node.id, initialRanks?.get(node.id) ?? 1 / this.nodeCount);
|
|
5578
5617
|
}
|
|
5618
|
+
this.normalizeMap(scores);
|
|
5579
5619
|
const outWeights = /* @__PURE__ */ new Map();
|
|
5580
5620
|
const danglingNodes = /* @__PURE__ */ new Set();
|
|
5581
5621
|
for (const node of this.graph.nodes()) {
|
|
@@ -5583,8 +5623,7 @@ class SimpleDeltaPageRank {
|
|
|
5583
5623
|
let outDegree = 0;
|
|
5584
5624
|
for (const neighbor of Array.from(this.graph.neighbors(node.id))) {
|
|
5585
5625
|
outDegree++;
|
|
5586
|
-
|
|
5587
|
-
totalWeight += edge?.weight ?? 1;
|
|
5626
|
+
totalWeight += weight ? this.graph.getEdge(node.id, neighbor)?.weight ?? 1 : 1;
|
|
5588
5627
|
}
|
|
5589
5628
|
outWeights.set(node.id, totalWeight);
|
|
5590
5629
|
if (outDegree === 0) {
|
|
@@ -5596,7 +5635,6 @@ class SimpleDeltaPageRank {
|
|
|
5596
5635
|
personalVector = new Map(personalization);
|
|
5597
5636
|
this.normalizeMap(personalVector);
|
|
5598
5637
|
}
|
|
5599
|
-
let activeNodes = new Set(scores.keys());
|
|
5600
5638
|
let iteration = 0;
|
|
5601
5639
|
for (iteration = 0; iteration < maxIterations; iteration++) {
|
|
5602
5640
|
for (const nodeId of scores.keys()) {
|
|
@@ -5622,7 +5660,7 @@ class SimpleDeltaPageRank {
|
|
|
5622
5660
|
}
|
|
5623
5661
|
}
|
|
5624
5662
|
}
|
|
5625
|
-
for (const nodeId of
|
|
5663
|
+
for (const nodeId of scores.keys()) {
|
|
5626
5664
|
const currentRank = scores.get(nodeId) ?? 0;
|
|
5627
5665
|
const nodeOutWeight = outWeights.get(nodeId) ?? 0;
|
|
5628
5666
|
if (nodeOutWeight > 0) {
|
|
@@ -5638,33 +5676,21 @@ class SimpleDeltaPageRank {
|
|
|
5638
5676
|
}
|
|
5639
5677
|
}
|
|
5640
5678
|
}
|
|
5641
|
-
const nextActive = /* @__PURE__ */ new Set();
|
|
5642
5679
|
let maxDiff = 0;
|
|
5643
5680
|
for (const nodeId of scores.keys()) {
|
|
5644
|
-
|
|
5645
|
-
const newRank = newScores.get(nodeId) ?? 0;
|
|
5646
|
-
const diff = Math.abs(newRank - oldRank);
|
|
5647
|
-
maxDiff = Math.max(maxDiff, diff);
|
|
5648
|
-
if (diff > tolerance / 10) {
|
|
5649
|
-
nextActive.add(nodeId);
|
|
5650
|
-
for (const neighbor of Array.from(this.graph.neighbors(nodeId))) {
|
|
5651
|
-
nextActive.add(neighbor);
|
|
5652
|
-
}
|
|
5653
|
-
for (const neighbor of Array.from(this.graph.inNeighbors(nodeId))) {
|
|
5654
|
-
nextActive.add(neighbor);
|
|
5655
|
-
}
|
|
5656
|
-
}
|
|
5681
|
+
maxDiff = Math.max(maxDiff, Math.abs((newScores.get(nodeId) ?? 0) - (scores.get(nodeId) ?? 0)));
|
|
5657
5682
|
}
|
|
5658
5683
|
scores.clear();
|
|
5659
5684
|
for (const [k, v] of newScores) {
|
|
5660
5685
|
scores.set(k, v);
|
|
5661
5686
|
}
|
|
5662
5687
|
newScores.clear();
|
|
5663
|
-
activeNodes = nextActive;
|
|
5664
5688
|
if (maxDiff < tolerance) {
|
|
5689
|
+
this.converged = true;
|
|
5665
5690
|
break;
|
|
5666
5691
|
}
|
|
5667
5692
|
}
|
|
5693
|
+
this.iterations = this.converged ? iteration + 1 : iteration;
|
|
5668
5694
|
this.previousScores = new Map(scores);
|
|
5669
5695
|
return scores;
|
|
5670
5696
|
}
|
|
@@ -5685,6 +5711,8 @@ class SimpleDeltaPageRank {
|
|
|
5685
5711
|
return this.compute(options);
|
|
5686
5712
|
}
|
|
5687
5713
|
const { dampingFactor = 0.85, tolerance = 1e-6, maxIterations = 100, personalization, weight } = options;
|
|
5714
|
+
this.iterations = 0;
|
|
5715
|
+
this.converged = false;
|
|
5688
5716
|
const scores = new Map(this.previousScores);
|
|
5689
5717
|
const newScores = /* @__PURE__ */ new Map();
|
|
5690
5718
|
for (const node of this.graph.nodes()) {
|
|
@@ -5709,8 +5737,7 @@ class SimpleDeltaPageRank {
|
|
|
5709
5737
|
let outDegree = 0;
|
|
5710
5738
|
for (const neighbor of Array.from(this.graph.neighbors(node.id))) {
|
|
5711
5739
|
outDegree++;
|
|
5712
|
-
|
|
5713
|
-
totalWeight += edge?.weight ?? 1;
|
|
5740
|
+
totalWeight += weight ? this.graph.getEdge(node.id, neighbor)?.weight ?? 1 : 1;
|
|
5714
5741
|
}
|
|
5715
5742
|
outWeights.set(node.id, totalWeight);
|
|
5716
5743
|
if (outDegree === 0) {
|
|
@@ -5787,9 +5814,11 @@ class SimpleDeltaPageRank {
|
|
|
5787
5814
|
newScores.clear();
|
|
5788
5815
|
activeNodes = nextActive;
|
|
5789
5816
|
if (maxDiff < tolerance) {
|
|
5817
|
+
this.converged = true;
|
|
5790
5818
|
break;
|
|
5791
5819
|
}
|
|
5792
5820
|
}
|
|
5821
|
+
this.iterations = this.converged ? iteration + 1 : iteration;
|
|
5793
5822
|
this.previousScores = new Map(scores);
|
|
5794
5823
|
return scores;
|
|
5795
5824
|
}
|
|
@@ -5832,6 +5861,9 @@ function pageRank$1(graph, options = {}) {
|
|
|
5832
5861
|
tolerance,
|
|
5833
5862
|
maxIterations
|
|
5834
5863
|
};
|
|
5864
|
+
if (initialRanks) {
|
|
5865
|
+
deltaOptions.initialRanks = initialRanks;
|
|
5866
|
+
}
|
|
5835
5867
|
if (personalization) {
|
|
5836
5868
|
deltaOptions.personalization = personalization;
|
|
5837
5869
|
}
|
|
@@ -5846,9 +5878,8 @@ function pageRank$1(graph, options = {}) {
|
|
|
5846
5878
|
}
|
|
5847
5879
|
return {
|
|
5848
5880
|
ranks: result2,
|
|
5849
|
-
iterations:
|
|
5850
|
-
|
|
5851
|
-
converged: true
|
|
5881
|
+
iterations: deltaPageRank.iterations,
|
|
5882
|
+
converged: deltaPageRank.converged
|
|
5852
5883
|
};
|
|
5853
5884
|
}
|
|
5854
5885
|
let ranks = /* @__PURE__ */ new Map();
|
|
@@ -5950,7 +5981,8 @@ function pageRank$1(graph, options = {}) {
|
|
|
5950
5981
|
}
|
|
5951
5982
|
return {
|
|
5952
5983
|
ranks: result,
|
|
5953
|
-
|
|
5984
|
+
// The loop breaks before incrementing on convergence, and leaves at maxIterations otherwise.
|
|
5985
|
+
iterations: converged ? iteration + 1 : iteration,
|
|
5954
5986
|
converged
|
|
5955
5987
|
};
|
|
5956
5988
|
}
|
|
@@ -10122,7 +10154,7 @@ function adamicAdarPrediction(graph, options = {}) {
|
|
|
10122
10154
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
10123
10155
|
const source = nodes[i];
|
|
10124
10156
|
const target = nodes[j];
|
|
10125
|
-
if (
|
|
10157
|
+
if (source === void 0 || target === void 0) {
|
|
10126
10158
|
continue;
|
|
10127
10159
|
}
|
|
10128
10160
|
if (!includeExisting && graph.hasEdge(source, target)) {
|
|
@@ -10300,7 +10332,7 @@ function commonNeighborsPrediction(graph, options = {}) {
|
|
|
10300
10332
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
10301
10333
|
const source = nodes[i];
|
|
10302
10334
|
const target = nodes[j];
|
|
10303
|
-
if (
|
|
10335
|
+
if (source === void 0 || target === void 0) {
|
|
10304
10336
|
continue;
|
|
10305
10337
|
}
|
|
10306
10338
|
if (!includeExisting && graph.hasEdge(source, target)) {
|
|
@@ -11604,6 +11636,22 @@ class IndexedMinHeap {
|
|
|
11604
11636
|
this.slot[node] = at;
|
|
11605
11637
|
}
|
|
11606
11638
|
}
|
|
11639
|
+
function walkBack(g, predArc, source, target) {
|
|
11640
|
+
const arcs = [];
|
|
11641
|
+
let node = target;
|
|
11642
|
+
while (node !== source) {
|
|
11643
|
+
if (arcs.length >= g.nodeCount - 1) {
|
|
11644
|
+
throw new PathWalkError(source, target, "cycle");
|
|
11645
|
+
}
|
|
11646
|
+
const arc = predArc[node];
|
|
11647
|
+
if (!(arc < g.arcCount)) {
|
|
11648
|
+
throw new PathWalkError(source, target, "gap");
|
|
11649
|
+
}
|
|
11650
|
+
arcs.push(arc);
|
|
11651
|
+
node = arcSourceIn(g.rowPtr, arc);
|
|
11652
|
+
}
|
|
11653
|
+
return arcs;
|
|
11654
|
+
}
|
|
11607
11655
|
function walkPredArcs(g, predArc, source, target) {
|
|
11608
11656
|
if (target === source) {
|
|
11609
11657
|
return Uint32Array.of(source);
|
|
@@ -11611,12 +11659,7 @@ function walkPredArcs(g, predArc, source, target) {
|
|
|
11611
11659
|
if (predArc[target] === INVALID_INDEX) {
|
|
11612
11660
|
return new Uint32Array(0);
|
|
11613
11661
|
}
|
|
11614
|
-
const reversed = [target];
|
|
11615
|
-
let node = target;
|
|
11616
|
-
while (node !== source) {
|
|
11617
|
-
node = arcSourceIn(g.rowPtr, predArc[node]);
|
|
11618
|
-
reversed.push(node);
|
|
11619
|
-
}
|
|
11662
|
+
const reversed = [target, ...walkBack(g, predArc, source, target).map((arc) => arcSourceIn(g.rowPtr, arc))];
|
|
11620
11663
|
const out = new Uint32Array(reversed.length);
|
|
11621
11664
|
for (let i = 0; i < reversed.length; i++) {
|
|
11622
11665
|
out[i] = reversed[reversed.length - 1 - i];
|
|
@@ -11627,13 +11670,7 @@ function walkPredEdges(g, predArc, source, target) {
|
|
|
11627
11670
|
if (target === source || predArc[target] === INVALID_INDEX) {
|
|
11628
11671
|
return new Uint32Array(0);
|
|
11629
11672
|
}
|
|
11630
|
-
const reversed = [];
|
|
11631
|
-
let node = target;
|
|
11632
|
-
while (node !== source) {
|
|
11633
|
-
const arc = predArc[node];
|
|
11634
|
-
reversed.push(g.arcToEdge[arc]);
|
|
11635
|
-
node = arcSourceIn(g.rowPtr, arc);
|
|
11636
|
-
}
|
|
11673
|
+
const reversed = walkBack(g, predArc, source, target).map((arc) => g.arcToEdge[arc]);
|
|
11637
11674
|
const out = new Uint32Array(reversed.length);
|
|
11638
11675
|
for (let i = 0; i < reversed.length; i++) {
|
|
11639
11676
|
out[i] = reversed[reversed.length - 1 - i];
|
|
@@ -11811,6 +11848,7 @@ export {
|
|
|
11811
11848
|
DirectionOptimizedBFS,
|
|
11812
11849
|
Graph,
|
|
11813
11850
|
GraphBitSet,
|
|
11851
|
+
PathWalkError,
|
|
11814
11852
|
PriorityDeltaPageRank,
|
|
11815
11853
|
PriorityQueue,
|
|
11816
11854
|
UnionFind,
|