@graphty/algorithms 2.0.3 → 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.
- package/README.md +2 -2
- package/dist/algorithms.js +85 -47
- package/dist/algorithms.js.map +1 -1
- package/dist/algorithms.standalone.js +86 -48
- 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 +1 -1
- 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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
5849
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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@${"
|
|
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,
|