diff --git a/src/v3/core/attribution/__snapshots__/pagerank.test.js.snap b/src/v3/core/attribution/__snapshots__/pagerank.test.js.snap new file mode 100644 index 0000000..c99e003 --- /dev/null +++ b/src/v3/core/attribution/__snapshots__/pagerank.test.js.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`core/attribution/pagerank respects explicit arguments 1`] = ` +Array [ + Object { + "parts": Array [ + "dst", + ], + "probability": 0.25, + }, + Object { + "parts": Array [ + "isolated", + ], + "probability": 0.25, + }, + Object { + "parts": Array [ + "loop", + ], + "probability": 0.25, + }, + Object { + "parts": Array [ + "src", + ], + "probability": 0.25, + }, +] +`; + +exports[`core/attribution/pagerank snapshots as expected on the advanced graph 1`] = ` +Array [ + Object { + "parts": Array [ + "dst", + ], + "probability": 0.4999999999687968, + }, + Object { + "parts": Array [ + "isolated", + ], + "probability": 0.25, + }, + Object { + "parts": Array [ + "loop", + ], + "probability": 0.25, + }, + Object { + "parts": Array [ + "src", + ], + "probability": 3.120317183596679e-11, + }, +] +`; diff --git a/src/v3/core/attribution/pagerank.js b/src/v3/core/attribution/pagerank.js new file mode 100644 index 0000000..c258696 --- /dev/null +++ b/src/v3/core/attribution/pagerank.js @@ -0,0 +1,49 @@ +// @flow + +import {type Edge, Graph} from "../graph"; +import { + type PagerankResult, + distributionToPagerankResult, + graphToOrderedSparseMarkovChain, + type EdgeWeight, +} from "./graphToMarkovChain"; + +import {findStationaryDistribution} from "./markovChain"; + +export type PagerankOptions = {| + +selfLoopWeight?: number, + +verbose?: boolean, + +convergenceThreshold?: number, + +maxIterations?: number, +|}; + +function defaultOptions(): PagerankOptions { + return { + verbose: false, + selfLoopWeight: 1e-3, + convergenceThreshold: 1e-7, + maxIterations: 255, + }; +} + +export function pagerank( + graph: Graph, + edgeWeight: (Edge) => EdgeWeight, + options?: PagerankOptions +): PagerankResult { + const fullOptions = { + ...defaultOptions(), + ...(options || {}), + }; + const osmc = graphToOrderedSparseMarkovChain( + graph, + edgeWeight, + fullOptions.selfLoopWeight + ); + const distribution = findStationaryDistribution(osmc.chain, { + verbose: fullOptions.verbose, + convergenceThreshold: fullOptions.convergenceThreshold, + maxIterations: fullOptions.maxIterations, + }); + return distributionToPagerankResult(osmc.nodeOrder, distribution); +} diff --git a/src/v3/core/attribution/pagerank.test.js b/src/v3/core/attribution/pagerank.test.js new file mode 100644 index 0000000..bf90efb --- /dev/null +++ b/src/v3/core/attribution/pagerank.test.js @@ -0,0 +1,32 @@ +// @flow + +import {pagerank} from "./pagerank"; +import {NodeAddress} from "../graph"; +import {advancedGraph} from "../graphTestUtil"; + +function snapshotPagerankResult(result) { + const partsToProbability = []; + const sortedKeys = Array.from(result.keys()).sort(); + for (const key of sortedKeys) { + const probability = result.get(key); + const parts = NodeAddress.toParts((key: any)); + partsToProbability.push({parts, probability}); + } + expect(partsToProbability).toMatchSnapshot(); +} + +describe("core/attribution/pagerank", () => { + function edgeWeight(_unused_edge) { + return {toWeight: 1, froWeight: 0}; + } + it("snapshots as expected on the advanced graph", () => { + const pagerankResult = pagerank(advancedGraph().graph1(), edgeWeight); + snapshotPagerankResult(pagerankResult); + }); + it("respects explicit arguments", () => { + const pagerankResult = pagerank(advancedGraph().graph1(), edgeWeight, { + maxIterations: 0, + }); + snapshotPagerankResult(pagerankResult); + }); +});