Add the `attribution/pagerank` module (#455)

This module exposes a method, `pagerank`, which is a convenient entry
point for taking a `Graph` and returning a `PagerankResult`. This
obviates the need for `src/v1/app/credExplorer/basicPagerank.js`.

Test plan: Unit tests included.
This commit is contained in:
Dandelion Mané 2018-06-29 14:24:28 -07:00 committed by GitHub
parent 5c93085430
commit fe64377194
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 0 deletions

View File

@ -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,
},
]
`;

View File

@ -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);
}

View File

@ -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);
});
});