@@ -337,13 +332,13 @@ export class ContributionRow extends React.PureComponent<
>
{expanded ? "\u2212" : "+"}
-
+
- {contributionPercent}% |
+ {connectionPercent}% |
{scoreDisplay(sourceScore)} |
{expanded && (
- ,
|}> {
render() {
- const {contribution, adapters} = this.props;
+ const {connection, adapters} = this.props;
function Badge({children}) {
return (
// The outer acts as a strut to ensure that the badge
@@ -378,30 +373,30 @@ export class ContributionView extends React.PureComponent<{|
);
}
- const {contributor} = contribution;
- switch (contributor.type) {
+ const {adjacency} = connection;
+ switch (adjacency.type) {
case "SYNTHETIC_LOOP":
return synthetic loop;
case "IN_EDGE":
return (
- {edgeVerb(contributor.edge.address, "BACKWARD", adapters)}
+ {edgeVerb(adjacency.edge.address, "BACKWARD", adapters)}
{" "}
- {nodeDescription(contributor.edge.src, adapters)}
+ {nodeDescription(adjacency.edge.src, adapters)}
);
case "OUT_EDGE":
return (
- {edgeVerb(contributor.edge.address, "FORWARD", adapters)}
+ {edgeVerb(adjacency.edge.address, "FORWARD", adapters)}
{" "}
- {nodeDescription(contributor.edge.dst, adapters)}
+ {nodeDescription(adjacency.edge.dst, adapters)}
);
default:
- throw new Error((contributor.type: empty));
+ throw new Error((adjacency.type: empty));
}
}
}
diff --git a/src/app/credExplorer/PagerankTable.test.js b/src/app/credExplorer/PagerankTable.test.js
index a27cbf2..beeec09 100644
--- a/src/app/credExplorer/PagerankTable.test.js
+++ b/src/app/credExplorer/PagerankTable.test.js
@@ -8,13 +8,13 @@ import {
PagerankTable,
NodeRowList,
NodeRow,
- ContributionRowList,
- ContributionRow,
- ContributionView,
+ ConnectionRowList,
+ ConnectionRow,
+ ConnectionView,
} from "./PagerankTable";
import {pagerank} from "../../core/attribution/pagerank";
import sortBy from "lodash.sortby";
-import {type Contribution} from "../../core/attribution/graphToMarkovChain";
+import {type Connection} from "../../core/attribution/graphToMarkovChain";
import * as NullUtil from "../../util/null";
import {
@@ -26,7 +26,7 @@ import {
require("../testUtil").configureEnzyme();
-const COLUMNS = () => ["Description", "Contribution", "Score"];
+const COLUMNS = () => ["Description", "Connection", "Score"];
async function example() {
const graph = new Graph();
@@ -337,14 +337,14 @@ describe("app/credExplorer/PagerankTable", () => {
.text()
).toEqual(expectedDescription);
});
- it("renders an empty contribution column", async () => {
+ it("renders an empty connection column", async () => {
const {element} = await setup();
- const contributionColumn = COLUMNS().indexOf("Contribution");
- expect(contributionColumn).not.toEqual(-1);
+ const connectionColumn = COLUMNS().indexOf("Connection");
+ expect(connectionColumn).not.toEqual(-1);
expect(
element
.find("td")
- .at(contributionColumn)
+ .at(connectionColumn)
.text()
).toEqual("—");
});
@@ -352,18 +352,18 @@ describe("app/credExplorer/PagerankTable", () => {
const {element, sharedProps, node} = await setup();
const {score: rawScore} = NullUtil.get(sharedProps.pnd.get(node));
const expectedScore = (-Math.log(rawScore)).toFixed(2);
- const contributionColumn = COLUMNS().indexOf("Score");
- expect(contributionColumn).not.toEqual(-1);
+ const connectionColumn = COLUMNS().indexOf("Score");
+ expect(connectionColumn).not.toEqual(-1);
expect(
element
.find("td")
- .at(contributionColumn)
+ .at(connectionColumn)
.text()
).toEqual(expectedScore);
});
it("does not render children by default", async () => {
const {element} = await setup();
- expect(element.find("ContributionRowList")).toHaveLength(0);
+ expect(element.find("ConnectionRowList")).toHaveLength(0);
});
it('has a working "expand" button', async () => {
const {element, sharedProps, node} = await setup();
@@ -371,7 +371,7 @@ describe("app/credExplorer/PagerankTable", () => {
element.find("button").simulate("click");
expect(element.find("button").text()).toEqual("\u2212");
- const crl = element.find("ContributionRowList");
+ const crl = element.find("ConnectionRowList");
expect(crl).toHaveLength(1);
expect(crl.prop("sharedProps")).toEqual(sharedProps);
expect(crl.prop("depth")).toBe(1);
@@ -379,18 +379,18 @@ describe("app/credExplorer/PagerankTable", () => {
element.find("button").simulate("click");
expect(element.find("button").text()).toEqual("+");
- expect(element.find("ContributionRowList")).toHaveLength(0);
+ expect(element.find("ConnectionRowList")).toHaveLength(0);
});
});
- describe("ContributionRowList", () => {
+ describe("ConnectionRowList", () => {
async function setup(maxEntriesPerList: number = 100000) {
const {adapters, pnd, nodes} = await example();
const depth = 2;
const node = nodes.bar1;
const sharedProps = {adapters, pnd, maxEntriesPerList};
const component = (
- {
const element = shallow(component);
return {element, depth, node, sharedProps};
}
- it("creates `ContributionRow`s with the right props", async () => {
+ it("creates `ConnectionRow`s with the right props", async () => {
const {element, depth, node, sharedProps} = await setup();
- const contributions = NullUtil.get(sharedProps.pnd.get(node))
- .scoredContributions;
- const rows = element.find("ContributionRow");
- expect(rows).toHaveLength(contributions.length);
+ const connections = NullUtil.get(sharedProps.pnd.get(node))
+ .scoredConnections;
+ const rows = element.find("ConnectionRow");
+ expect(rows).toHaveLength(connections.length);
const rowPropses = rows.map((row) => row.props());
// Order should be the same as the order in the decomposition.
expect(rowPropses).toEqual(
- contributions.map((sc) => ({
+ connections.map((sc) => ({
depth,
sharedProps,
target: node,
- scoredContribution: sc,
+ scoredConnection: sc,
}))
);
});
it("limits the number of rows by `maxEntriesPerList`", async () => {
const maxEntriesPerList = 1;
const {element, node, sharedProps} = await setup(maxEntriesPerList);
- const contributions = NullUtil.get(sharedProps.pnd.get(node))
- .scoredContributions;
- expect(contributions.length).toBeGreaterThan(maxEntriesPerList);
- const rows = element.find("ContributionRow");
+ const connections = NullUtil.get(sharedProps.pnd.get(node))
+ .scoredConnections;
+ expect(connections.length).toBeGreaterThan(maxEntriesPerList);
+ const rows = element.find("ConnectionRow");
expect(rows).toHaveLength(maxEntriesPerList);
- const rowContributions = rows.map((row) =>
- row.prop("scoredContribution")
- );
+ const rowConnections = rows.map((row) => row.prop("scoredConnection"));
// Should have selected the right nodes.
- expect(rowContributions).toEqual(
- contributions.slice(0, maxEntriesPerList)
- );
+ expect(rowConnections).toEqual(connections.slice(0, maxEntriesPerList));
});
});
- describe("ContributionRow", () => {
+ describe("ConnectionRow", () => {
async function setup() {
const {pnd, adapters, nodes} = await example();
const sharedProps = {adapters, pnd, maxEntriesPerList: 123};
const target = nodes.bar1;
- const {scoredContributions} = NullUtil.get(pnd.get(target));
- const alphaContributions = scoredContributions.filter(
+ const {scoredConnections} = NullUtil.get(pnd.get(target));
+ const alphaConnections = scoredConnections.filter(
(sc) => sc.source === nodes.fooAlpha
);
- expect(alphaContributions).toHaveLength(1);
- const contribution = alphaContributions[0];
- const {source} = contribution;
+ expect(alphaConnections).toHaveLength(1);
+ const connection = alphaConnections[0];
+ const {source} = connection;
const depth = 2;
const component = (
-
);
const element = shallow(component);
- return {element, depth, target, source, contribution, sharedProps};
+ return {element, depth, target, source, connection, sharedProps};
}
it("renders the right number of columns", async () => {
expect((await setup()).element.find("td")).toHaveLength(COLUMNS().length);
@@ -469,25 +465,25 @@ describe("app/credExplorer/PagerankTable", () => {
}).toMatchSnapshot();
});
it("renders the source view", async () => {
- const {element, sharedProps, contribution} = await setup();
+ const {element, sharedProps, connection} = await setup();
const descriptionColumn = COLUMNS().indexOf("Description");
expect(descriptionColumn).not.toEqual(-1);
const view = element
.find("td")
.at(descriptionColumn)
- .find("ContributionView");
+ .find("ConnectionView");
expect(view).toHaveLength(1);
expect(view.props()).toEqual({
adapters: sharedProps.adapters,
- contribution: contribution.contribution,
+ connection: connection.connection,
});
});
- it("renders the contribution percentage", async () => {
- const {element, contribution, sharedProps, target} = await setup();
- const contributionColumn = COLUMNS().indexOf("Contribution");
- expect(contributionColumn).not.toEqual(-1);
+ it("renders the connection percentage", async () => {
+ const {element, connection, sharedProps, target} = await setup();
+ const connectionColumn = COLUMNS().indexOf("Connection");
+ expect(connectionColumn).not.toEqual(-1);
const proportion =
- contribution.contributionScore /
+ connection.connectionScore /
NullUtil.get(sharedProps.pnd.get(target)).score;
expect(proportion).toBeGreaterThan(0.0);
expect(proportion).toBeLessThan(1.0);
@@ -495,25 +491,25 @@ describe("app/credExplorer/PagerankTable", () => {
expect(
element
.find("td")
- .at(contributionColumn)
+ .at(connectionColumn)
.text()
).toEqual(expectedText);
});
it("renders a score column with the source's log-score", async () => {
- const {element, contribution} = await setup();
- const expectedScore = (-Math.log(contribution.sourceScore)).toFixed(2);
- const contributionColumn = COLUMNS().indexOf("Score");
- expect(contributionColumn).not.toEqual(-1);
+ const {element, connection} = await setup();
+ const expectedScore = (-Math.log(connection.sourceScore)).toFixed(2);
+ const connectionColumn = COLUMNS().indexOf("Score");
+ expect(connectionColumn).not.toEqual(-1);
expect(
element
.find("td")
- .at(contributionColumn)
+ .at(connectionColumn)
.text()
).toEqual(expectedScore);
});
it("does not render children by default", async () => {
const {element} = await setup();
- expect(element.find("ContributionRowList")).toHaveLength(0);
+ expect(element.find("ConnectionRowList")).toHaveLength(0);
});
it('has a working "expand" button', async () => {
const {element, depth, sharedProps, source} = await setup();
@@ -521,7 +517,7 @@ describe("app/credExplorer/PagerankTable", () => {
element.find("button").simulate("click");
expect(element.find("button").text()).toEqual("\u2212");
- const crl = element.find("ContributionRowList");
+ const crl = element.find("ConnectionRowList");
expect(crl).toHaveLength(1);
expect(crl).not.toHaveLength(0);
expect(crl.prop("sharedProps")).toEqual(sharedProps);
@@ -530,75 +526,75 @@ describe("app/credExplorer/PagerankTable", () => {
element.find("button").simulate("click");
expect(element.find("button").text()).toEqual("+");
- expect(element.find("ContributionRowList")).toHaveLength(0);
+ expect(element.find("ConnectionRowList")).toHaveLength(0);
});
});
- describe("ContributionView", () => {
+ describe("ConnectionView", () => {
async function setup() {
const {pnd, adapters, nodes} = await example();
- const {scoredContributions} = NullUtil.get(pnd.get(nodes.bar1));
- const contributions = scoredContributions.map((sc) => sc.contribution);
- function contributionByType(t) {
+ const {scoredConnections} = NullUtil.get(pnd.get(nodes.bar1));
+ const connections = scoredConnections.map((sc) => sc.connection);
+ function connectionByType(t) {
return NullUtil.get(
- contributions.filter((c) => c.contributor.type === t)[0],
- `Couldn't find contribution for type ${t}`
+ connections.filter((c) => c.adjacency.type === t)[0],
+ `Couldn't find connection for type ${t}`
);
}
- const inContribution = contributionByType("IN_EDGE");
- const outContribution = contributionByType("OUT_EDGE");
- const syntheticContribution = contributionByType("SYNTHETIC_LOOP");
- function cvForContribution(contribution: Contribution) {
+ const inConnection = connectionByType("IN_EDGE");
+ const outConnection = connectionByType("OUT_EDGE");
+ const syntheticConnection = connectionByType("SYNTHETIC_LOOP");
+ function cvForConnection(connection: Connection) {
return shallow(
-
+
);
}
return {
adapters,
- contributions,
+ connections,
pnd,
- cvForContribution,
- inContribution,
- outContribution,
- syntheticContribution,
+ cvForConnection,
+ inConnection,
+ outConnection,
+ syntheticConnection,
};
}
it("always renders exactly one `Badge`", async () => {
const {
- cvForContribution,
- inContribution,
- outContribution,
- syntheticContribution,
+ cvForConnection,
+ inConnection,
+ outConnection,
+ syntheticConnection,
} = await setup();
- for (const contribution of [
- syntheticContribution,
- inContribution,
- outContribution,
+ for (const connection of [
+ syntheticConnection,
+ inConnection,
+ outConnection,
]) {
- expect(cvForContribution(contribution).find("Badge")).toHaveLength(1);
+ expect(cvForConnection(connection).find("Badge")).toHaveLength(1);
}
});
- it("for inward contributions, renders a `Badge` and description", async () => {
- const {cvForContribution, inContribution} = await setup();
- const view = cvForContribution(inContribution);
+ it("for inward connections, renders a `Badge` and description", async () => {
+ const {cvForConnection, inConnection} = await setup();
+ const view = cvForConnection(inConnection);
const outerSpan = view.find("span").first();
const badge = outerSpan.find("Badge");
const description = outerSpan.children().find("span");
expect(badge.children().text()).toEqual("is barred by");
expect(description.text()).toEqual('bar: NodeAddress["bar","a","1"]');
});
- it("for outward contributions, renders a `Badge` and description", async () => {
- const {cvForContribution, outContribution} = await setup();
- const view = cvForContribution(outContribution);
+ it("for outward connections, renders a `Badge` and description", async () => {
+ const {cvForConnection, outConnection} = await setup();
+ const view = cvForConnection(outConnection);
const outerSpan = view.find("span").first();
const badge = outerSpan.find("Badge");
const description = outerSpan.children().find("span");
expect(badge.children().text()).toEqual("bars");
expect(description.text()).toEqual("xox node!");
});
- it("for synthetic contributions, renders only a `Badge`", async () => {
- const {cvForContribution, syntheticContribution} = await setup();
- const view = cvForContribution(syntheticContribution);
+ it("for synthetic connections, renders only a `Badge`", async () => {
+ const {cvForConnection, syntheticConnection} = await setup();
+ const view = cvForConnection(syntheticConnection);
expect(view.find("span")).toHaveLength(0);
expect(
view
diff --git a/src/app/credExplorer/__snapshots__/PagerankTable.test.js.snap b/src/app/credExplorer/__snapshots__/PagerankTable.test.js.snap
index 5f59191..38bcd59 100644
--- a/src/app/credExplorer/__snapshots__/PagerankTable.test.js.snap
+++ b/src/app/credExplorer/__snapshots__/PagerankTable.test.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`app/credExplorer/PagerankTable ContributionRow has proper depth-based styling 1`] = `
+exports[`app/credExplorer/PagerankTable ConnectionRow has proper depth-based styling 1`] = `
Object {
"buttonStyle": Object {
"marginLeft": 30,
diff --git a/src/core/attribution/__snapshots__/pagerankNodeDecomposition.test.js.snap b/src/core/attribution/__snapshots__/pagerankNodeDecomposition.test.js.snap
index 8e1b848..be85fb5 100644
--- a/src/core/attribution/__snapshots__/pagerankNodeDecomposition.test.js.snap
+++ b/src/core/attribution/__snapshots__/pagerankNodeDecomposition.test.js.snap
@@ -1,13 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`core/attribution/contributions decompose has the expected output on a simple asymmetric chain 1`] = `
+exports[`core/attribution/connections decompose has the expected output on a simple asymmetric chain 1`] = `
Map {
"NodeAddress[\\"n1\\"]" => Object {
"score": 0.19117656878499834,
- "scoredContributions": Array [
+ "scoredConnections": Array [
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"edge": Object {
"address": "EdgeAddress[\\"e3\\"]",
"dst": "NodeAddress[\\"sink\\"]",
@@ -17,13 +17,13 @@ Map {
},
"weight": 0.1875,
},
- "contributionScore": 0.1102941261444197,
+ "connectionScore": 0.1102941261444197,
"source": "NodeAddress[\\"sink\\"]",
"sourceScore": 0.5882353394369051,
},
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"edge": Object {
"address": "EdgeAddress[\\"e1\\"]",
"dst": "NodeAddress[\\"n2\\"]",
@@ -33,18 +33,18 @@ Map {
},
"weight": 0.3,
},
- "contributionScore": 0.066176427533429,
+ "connectionScore": 0.066176427533429,
"source": "NodeAddress[\\"n2\\"]",
"sourceScore": 0.22058809177809668,
},
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"type": "SYNTHETIC_LOOP",
},
"weight": 0.07692307692307693,
},
- "contributionScore": 0.014705889906538334,
+ "connectionScore": 0.014705889906538334,
"source": "NodeAddress[\\"n1\\"]",
"sourceScore": 0.19117656878499834,
},
@@ -52,10 +52,10 @@ Map {
},
"NodeAddress[\\"n2\\"]" => Object {
"score": 0.22058809177809668,
- "scoredContributions": Array [
+ "scoredConnections": Array [
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"edge": Object {
"address": "EdgeAddress[\\"e2\\"]",
"dst": "NodeAddress[\\"sink\\"]",
@@ -65,13 +65,13 @@ Map {
},
"weight": 0.1875,
},
- "contributionScore": 0.1102941261444197,
+ "connectionScore": 0.1102941261444197,
"source": "NodeAddress[\\"sink\\"]",
"sourceScore": 0.5882353394369051,
},
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"edge": Object {
"address": "EdgeAddress[\\"e1\\"]",
"dst": "NodeAddress[\\"n2\\"]",
@@ -81,18 +81,18 @@ Map {
},
"weight": 0.46153846153846156,
},
- "contributionScore": 0.08823533943923001,
+ "connectionScore": 0.08823533943923001,
"source": "NodeAddress[\\"n1\\"]",
"sourceScore": 0.19117656878499834,
},
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"type": "SYNTHETIC_LOOP",
},
"weight": 0.1,
},
- "contributionScore": 0.02205880917780967,
+ "connectionScore": 0.02205880917780967,
"source": "NodeAddress[\\"n2\\"]",
"sourceScore": 0.22058809177809668,
},
@@ -100,10 +100,10 @@ Map {
},
"NodeAddress[\\"sink\\"]" => Object {
"score": 0.5882353394369051,
- "scoredContributions": Array [
+ "scoredConnections": Array [
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"edge": Object {
"address": "EdgeAddress[\\"e4\\"]",
"dst": "NodeAddress[\\"sink\\"]",
@@ -113,13 +113,13 @@ Map {
},
"weight": 0.375,
},
- "contributionScore": 0.2205882522888394,
+ "connectionScore": 0.2205882522888394,
"source": "NodeAddress[\\"sink\\"]",
"sourceScore": 0.5882353394369051,
},
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"edge": Object {
"address": "EdgeAddress[\\"e2\\"]",
"dst": "NodeAddress[\\"sink\\"]",
@@ -129,13 +129,13 @@ Map {
},
"weight": 0.6,
},
- "contributionScore": 0.132352855066858,
+ "connectionScore": 0.132352855066858,
"source": "NodeAddress[\\"n2\\"]",
"sourceScore": 0.22058809177809668,
},
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"edge": Object {
"address": "EdgeAddress[\\"e4\\"]",
"dst": "NodeAddress[\\"sink\\"]",
@@ -145,13 +145,13 @@ Map {
},
"weight": 0.1875,
},
- "contributionScore": 0.1102941261444197,
+ "connectionScore": 0.1102941261444197,
"source": "NodeAddress[\\"sink\\"]",
"sourceScore": 0.5882353394369051,
},
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"edge": Object {
"address": "EdgeAddress[\\"e3\\"]",
"dst": "NodeAddress[\\"sink\\"]",
@@ -161,18 +161,18 @@ Map {
},
"weight": 0.46153846153846156,
},
- "contributionScore": 0.08823533943923001,
+ "connectionScore": 0.08823533943923001,
"source": "NodeAddress[\\"n1\\"]",
"sourceScore": 0.19117656878499834,
},
Object {
- "contribution": Object {
- "contributor": Object {
+ "connection": Object {
+ "adjacency": Object {
"type": "SYNTHETIC_LOOP",
},
"weight": 0.0625,
},
- "contributionScore": 0.03676470871480657,
+ "connectionScore": 0.03676470871480657,
"source": "NodeAddress[\\"sink\\"]",
"sourceScore": 0.5882353394369051,
},
diff --git a/src/core/attribution/graphToMarkovChain.js b/src/core/attribution/graphToMarkovChain.js
index 8756ec2..3788adc 100644
--- a/src/core/attribution/graphToMarkovChain.js
+++ b/src/core/attribution/graphToMarkovChain.js
@@ -6,40 +6,34 @@ import * as MapUtil from "../../util/map";
import * as NullUtil from "../../util/null";
export type Probability = number;
-export type Contributor =
+export type Adjacency =
| {|+type: "SYNTHETIC_LOOP"|}
| {|+type: "IN_EDGE", +edge: Edge|}
| {|+type: "OUT_EDGE", +edge: Edge|};
-export type Contribution = {|
- +contributor: Contributor,
+export type Connection = {|
+ +adjacency: Adjacency,
// This `weight` is a conditional probability: given that you're at
- // the source of this contribution's contributor, what's the
- // probability that you travel along this contribution to the target?
+ // the source of this connection's adjacency, what's the
+ // probability that you travel along this connection to the target?
+weight: Probability,
|};
-export function contributorSource(
- target: NodeAddressT,
- contributor: Contributor
-) {
- switch (contributor.type) {
+export function adjacencySource(target: NodeAddressT, adjacency: Adjacency) {
+ switch (adjacency.type) {
case "SYNTHETIC_LOOP":
return target;
case "IN_EDGE":
- return contributor.edge.src;
+ return adjacency.edge.src;
case "OUT_EDGE":
- return contributor.edge.dst;
+ return adjacency.edge.dst;
default:
- throw new Error((contributor.type: empty));
+ throw new Error((adjacency.type: empty));
}
}
export type NodeDistribution = Map;
-export type NodeToContributions = Map<
- NodeAddressT,
- $ReadOnlyArray
->;
+export type NodeToConnections = Map>;
type NodeAddressMarkovChain = Map<
NodeAddressT,
@@ -56,11 +50,11 @@ export type EdgeWeight = {|
+froWeight: number, // weight from dst to src
|};
-export function createContributions(
+export function createConnections(
graph: Graph,
edgeWeight: (Edge) => EdgeWeight,
syntheticLoopWeight: number
-): NodeToContributions {
+): NodeToConnections {
const result = new Map();
const totalOutWeight: Map = new Map();
for (const node of graph.nodes()) {
@@ -68,23 +62,20 @@ export function createContributions(
totalOutWeight.set(node, 0);
}
- function processContribution(
- target: NodeAddressT,
- contribution: Contribution
- ) {
- const contributions = NullUtil.get(result.get(target));
- (((contributions: $ReadOnlyArray): any): Contribution[]).push(
- contribution
+ function processConnection(target: NodeAddressT, connection: Connection) {
+ const connections = NullUtil.get(result.get(target));
+ (((connections: $ReadOnlyArray): any): Connection[]).push(
+ connection
);
- const source = contributorSource(target, contribution.contributor);
+ const source = adjacencySource(target, connection.adjacency);
const priorOutWeight = NullUtil.get(totalOutWeight.get(source));
- totalOutWeight.set(source, priorOutWeight + contribution.weight);
+ totalOutWeight.set(source, priorOutWeight + connection.weight);
}
// Add self-loops.
for (const node of graph.nodes()) {
- processContribution(node, {
- contributor: {type: "SYNTHETIC_LOOP"},
+ processConnection(node, {
+ adjacency: {type: "SYNTHETIC_LOOP"},
weight: syntheticLoopWeight,
});
}
@@ -93,25 +84,25 @@ export function createContributions(
for (const edge of graph.edges()) {
const {toWeight, froWeight} = edgeWeight(edge);
const {src, dst} = edge;
- processContribution(dst, {
- contributor: {type: "IN_EDGE", edge},
+ processConnection(dst, {
+ adjacency: {type: "IN_EDGE", edge},
weight: toWeight,
});
- processContribution(src, {
- contributor: {type: "OUT_EDGE", edge},
+ processConnection(src, {
+ adjacency: {type: "OUT_EDGE", edge},
weight: froWeight,
});
}
// Normalize in-weights.
- for (const [target, contributions] of result.entries()) {
- for (const contribution of contributions) {
- const source = contributorSource(target, contribution.contributor);
+ for (const [target, connections] of result.entries()) {
+ for (const connection of connections) {
+ const source = adjacencySource(target, connection.adjacency);
const normalization = NullUtil.get(totalOutWeight.get(source));
- const newWeight: typeof contribution.weight =
- contribution.weight / normalization;
+ const newWeight: typeof connection.weight =
+ connection.weight / normalization;
// (any-cast because property is not writable)
- (contribution: any).weight = newWeight;
+ (connection: any).weight = newWeight;
}
}
@@ -119,15 +110,15 @@ export function createContributions(
}
function createNodeAddressMarkovChain(
- ntc: NodeToContributions
+ ntc: NodeToConnections
): NodeAddressMarkovChain {
- return MapUtil.mapValues(ntc, (target, contributions) => {
+ return MapUtil.mapValues(ntc, (target, connections) => {
const inNeighbors = new Map();
- for (const contribution of contributions) {
- const source = contributorSource(target, contribution.contributor);
+ for (const connection of connections) {
+ const source = adjacencySource(target, connection.adjacency);
inNeighbors.set(
source,
- contribution.weight + NullUtil.orElse(inNeighbors.get(source), 0)
+ connection.weight + NullUtil.orElse(inNeighbors.get(source), 0)
);
}
return inNeighbors;
@@ -163,9 +154,9 @@ function nodeAddressMarkovChainToOrderedSparseMarkovChain(
}
export function createOrderedSparseMarkovChain(
- contributions: NodeToContributions
+ connections: NodeToConnections
): OrderedSparseMarkovChain {
- const chain = createNodeAddressMarkovChain(contributions);
+ const chain = createNodeAddressMarkovChain(connections);
return nodeAddressMarkovChainToOrderedSparseMarkovChain(chain);
}
diff --git a/src/core/attribution/graphToMarkovChain.test.js b/src/core/attribution/graphToMarkovChain.test.js
index 59f122f..d5ffc03 100644
--- a/src/core/attribution/graphToMarkovChain.test.js
+++ b/src/core/attribution/graphToMarkovChain.test.js
@@ -5,7 +5,7 @@ import sortBy from "lodash.sortby";
import {EdgeAddress, Graph, NodeAddress} from "../graph";
import {
distributionToNodeDistribution,
- createContributions,
+ createConnections,
createOrderedSparseMarkovChain,
normalize,
normalizeNeighbors,
@@ -81,9 +81,9 @@ describe("core/attribution/graphToMarkovChain", () => {
expect(actual).toEqual(expected);
});
- describe("createContributions", () => {
+ describe("createConnections", () => {
// The tests for `createOrderedSparseMarkovChain` also must invoke
- // `createContributions`, so we add only light testing separately.
+ // `createConnections`, so we add only light testing separately.
it("works on a simple asymmetric chain", () => {
const n1 = NodeAddress.fromParts(["n1"]);
const n2 = NodeAddress.fromParts(["n2"]);
@@ -101,28 +101,28 @@ describe("core/attribution/graphToMarkovChain", () => {
.addEdge(e3)
.addEdge(e4);
const edgeWeight = () => ({toWeight: 6.0, froWeight: 3.0});
- const actual = createContributions(g, edgeWeight, 1.0);
+ const actual = createConnections(g, edgeWeight, 1.0);
// Total out-weights (for normalization factors):
// - for `n1`: 2 out, 0 in, 1 synthetic: 12 + 0 + 1 = 13
// - for `n2`: 1 out, 1 in, 1 synthetic: 6 + 3 + 1 = 10
// - for `n3`: 1 out, 3 in, 1 synthetic: 6 + 9 + 1 = 16
const expected = new Map()
.set(n1, [
- {contributor: {type: "SYNTHETIC_LOOP"}, weight: 1 / 13},
- {contributor: {type: "OUT_EDGE", edge: e1}, weight: 3 / 10},
- {contributor: {type: "OUT_EDGE", edge: e3}, weight: 3 / 16},
+ {adjacency: {type: "SYNTHETIC_LOOP"}, weight: 1 / 13},
+ {adjacency: {type: "OUT_EDGE", edge: e1}, weight: 3 / 10},
+ {adjacency: {type: "OUT_EDGE", edge: e3}, weight: 3 / 16},
])
.set(n2, [
- {contributor: {type: "SYNTHETIC_LOOP"}, weight: 1 / 10},
- {contributor: {type: "IN_EDGE", edge: e1}, weight: 6 / 13},
- {contributor: {type: "OUT_EDGE", edge: e2}, weight: 3 / 16},
+ {adjacency: {type: "SYNTHETIC_LOOP"}, weight: 1 / 10},
+ {adjacency: {type: "IN_EDGE", edge: e1}, weight: 6 / 13},
+ {adjacency: {type: "OUT_EDGE", edge: e2}, weight: 3 / 16},
])
.set(n3, [
- {contributor: {type: "SYNTHETIC_LOOP"}, weight: 1 / 16},
- {contributor: {type: "IN_EDGE", edge: e2}, weight: 6 / 10},
- {contributor: {type: "IN_EDGE", edge: e3}, weight: 6 / 13},
- {contributor: {type: "IN_EDGE", edge: e4}, weight: 6 / 16},
- {contributor: {type: "OUT_EDGE", edge: e4}, weight: 3 / 16},
+ {adjacency: {type: "SYNTHETIC_LOOP"}, weight: 1 / 16},
+ {adjacency: {type: "IN_EDGE", edge: e2}, weight: 6 / 10},
+ {adjacency: {type: "IN_EDGE", edge: e3}, weight: 6 / 13},
+ {adjacency: {type: "IN_EDGE", edge: e4}, weight: 6 / 16},
+ {adjacency: {type: "OUT_EDGE", edge: e4}, weight: 3 / 16},
]);
const canonicalize = (map) =>
MapUtil.mapValues(map, (_, v) => sortBy(v, (x) => JSON.stringify(x)));
@@ -138,7 +138,7 @@ describe("core/attribution/graphToMarkovChain", () => {
throw new Error("Don't even look at me");
};
const osmc = createOrderedSparseMarkovChain(
- createContributions(g, edgeWeight, 1e-3)
+ createConnections(g, edgeWeight, 1e-3)
);
const expected = {
nodeOrder: [n],
@@ -167,7 +167,7 @@ describe("core/attribution/graphToMarkovChain", () => {
.addEdge(e4);
const edgeWeight = () => ({toWeight: 1, froWeight: 0});
const osmc = createOrderedSparseMarkovChain(
- createContributions(g, edgeWeight, 0.0)
+ createConnections(g, edgeWeight, 0.0)
);
const expected = {
nodeOrder: [n1, n2, n3],
@@ -205,7 +205,7 @@ describe("core/attribution/graphToMarkovChain", () => {
.addEdge(e3);
const edgeWeight = () => ({toWeight: 1, froWeight: 1});
const osmc = createOrderedSparseMarkovChain(
- createContributions(g, edgeWeight, 0.0)
+ createConnections(g, edgeWeight, 0.0)
);
const expected = {
nodeOrder: [n1, n2, n3],
@@ -237,7 +237,7 @@ describe("core/attribution/graphToMarkovChain", () => {
return {toWeight: 4 - epsilon / 2, froWeight: 1 - epsilon / 2};
}
const osmc = createOrderedSparseMarkovChain(
- createContributions(g, edgeWeight, epsilon)
+ createConnections(g, edgeWeight, epsilon)
);
// Edges from `src`:
// - to `src` with weight `epsilon`
diff --git a/src/core/attribution/pagerank.js b/src/core/attribution/pagerank.js
index 3cdd797..278584e 100644
--- a/src/core/attribution/pagerank.js
+++ b/src/core/attribution/pagerank.js
@@ -3,7 +3,7 @@
import {type Edge, Graph} from "../graph";
import {
distributionToNodeDistribution,
- createContributions,
+ createConnections,
createOrderedSparseMarkovChain,
type EdgeWeight,
} from "./graphToMarkovChain";
@@ -44,12 +44,12 @@ export async function pagerank(
...defaultOptions(),
...(options || {}),
};
- const contributions = createContributions(
+ const connections = createConnections(
graph,
edgeWeight,
fullOptions.selfLoopWeight
);
- const osmc = createOrderedSparseMarkovChain(contributions);
+ const osmc = createOrderedSparseMarkovChain(connections);
const distribution = await findStationaryDistribution(osmc.chain, {
verbose: fullOptions.verbose,
convergenceThreshold: fullOptions.convergenceThreshold,
@@ -57,5 +57,5 @@ export async function pagerank(
yieldAfterMs: 30,
});
const pi = distributionToNodeDistribution(osmc.nodeOrder, distribution);
- return decompose(pi, contributions);
+ return decompose(pi, connections);
}
diff --git a/src/core/attribution/pagerankNodeDecomposition.js b/src/core/attribution/pagerankNodeDecomposition.js
index d37883f..03f67b1 100644
--- a/src/core/attribution/pagerankNodeDecomposition.js
+++ b/src/core/attribution/pagerankNodeDecomposition.js
@@ -4,50 +4,50 @@ import sortBy from "lodash.sortby";
import type {NodeAddressT} from "../graph";
import {
- type Contribution,
- type NodeToContributions,
- contributorSource,
+ type Connection,
+ type NodeToConnections,
+ adjacencySource,
} from "./graphToMarkovChain";
import type {NodeDistribution} from "./pagerank";
import * as MapUtil from "../../util/map";
import * as NullUtil from "../../util/null";
-export type ScoredContribution = {|
- +contribution: Contribution,
+export type ScoredConnection = {|
+ +connection: Connection,
+source: NodeAddressT,
+sourceScore: number,
- +contributionScore: number,
+ +connectionScore: number,
|};
export type PagerankNodeDecomposition = Map<
NodeAddressT,
{|
+score: number,
- // Contributions are sorted by `contributorScore` descending,
+ // Connections are sorted by `adjacencyScore` descending,
// breaking ties in a deterministic (but unspecified) order.
- +scoredContributions: $ReadOnlyArray,
+ +scoredConnections: $ReadOnlyArray,
|}
>;
export function decompose(
pr: NodeDistribution,
- contributions: NodeToContributions
+ connections: NodeToConnections
): PagerankNodeDecomposition {
- return MapUtil.mapValues(contributions, (target, contributions) => {
+ return MapUtil.mapValues(connections, (target, connections) => {
const score = NullUtil.get(pr.get(target));
- const scoredContributions = sortBy(
- contributions.map(
- (contribution): ScoredContribution => {
- const source = contributorSource(target, contribution.contributor);
+ const scoredConnections = sortBy(
+ connections.map(
+ (connection): ScoredConnection => {
+ const source = adjacencySource(target, connection.adjacency);
const sourceScore = NullUtil.get(pr.get(source));
- const contributionScore = contribution.weight * sourceScore;
- return {contribution, source, sourceScore, contributionScore};
+ const connectionScore = connection.weight * sourceScore;
+ return {connection, source, sourceScore, connectionScore};
}
),
- (x) => -x.contributionScore,
+ (x) => -x.connectionScore,
// The following should be called rarely and on small objects.
- (x) => JSON.stringify(x.contribution.contributor)
+ (x) => JSON.stringify(x.connection.adjacency)
);
- return {score, scoredContributions};
+ return {score, scoredConnections};
});
}
diff --git a/src/core/attribution/pagerankNodeDecomposition.test.js b/src/core/attribution/pagerankNodeDecomposition.test.js
index 71e7bb2..cab0d1a 100644
--- a/src/core/attribution/pagerankNodeDecomposition.test.js
+++ b/src/core/attribution/pagerankNodeDecomposition.test.js
@@ -3,7 +3,7 @@
import {EdgeAddress, Graph, NodeAddress, edgeToStrings} from "../graph";
import {
distributionToNodeDistribution,
- createContributions,
+ createConnections,
createOrderedSparseMarkovChain,
} from "./graphToMarkovChain";
import {findStationaryDistribution} from "./markovChain";
@@ -17,57 +17,57 @@ import {advancedGraph} from "../graphTestUtil";
* addresses and edges to strings to avoid NUL characters.
*/
function formatDecomposition(d) {
- return MapUtil.mapEntries(d, (key, {score, scoredContributions}) => [
+ return MapUtil.mapEntries(d, (key, {score, scoredConnections}) => [
NodeAddress.toString(key),
{
score,
- scoredContributions: scoredContributions.map(
- ({contribution, source, sourceScore, contributionScore}) => ({
- contribution: {
- contributor: formatContributor(contribution.contributor),
- weight: contribution.weight,
+ scoredConnections: scoredConnections.map(
+ ({connection, source, sourceScore, connectionScore}) => ({
+ connection: {
+ adjacency: formatAdjacency(connection.adjacency),
+ weight: connection.weight,
},
source: NodeAddress.toString(source),
sourceScore,
- contributionScore,
+ connectionScore,
})
),
},
]);
- function formatContributor(contributor) {
- switch (contributor.type) {
+ function formatAdjacency(adjacency) {
+ switch (adjacency.type) {
case "SYNTHETIC_LOOP":
return {type: "SYNTHETIC_LOOP"};
case "IN_EDGE":
- return {type: "IN_EDGE", edge: edgeToStrings(contributor.edge)};
+ return {type: "IN_EDGE", edge: edgeToStrings(adjacency.edge)};
case "OUT_EDGE":
- return {type: "OUT_EDGE", edge: edgeToStrings(contributor.edge)};
+ return {type: "OUT_EDGE", edge: edgeToStrings(adjacency.edge)};
default:
- throw new Error((contributor.type: empty));
+ throw new Error((adjacency.type: empty));
}
}
}
/**
* Perform basic sanity checks on a decomposition. This ensures that
- * every node's score is the sum of its contributions' scores, that the
+ * every node's score is the sum of its connections' scores, that the
* scores of the decomposition sum to 1, and that each node's
- * contributions are listed in non-increasing order of score.
+ * connections are listed in non-increasing order of score.
*/
function validateDecomposition(decomposition) {
const epsilon = 1e-6;
// Check that each node's score is the sum of its subscores.
- for (const [key, {score, scoredContributions}] of decomposition.entries()) {
- const totalSubscore = scoredContributions
- .map((sc) => sc.contributionScore)
+ for (const [key, {score, scoredConnections}] of decomposition.entries()) {
+ const totalSubscore = scoredConnections
+ .map((sc) => sc.connectionScore)
.reduce((a, b) => a + b, 0);
const delta = totalSubscore - score;
if (Math.abs(delta) > epsilon) {
const message = [
`for node ${NodeAddress.toString(key)}: `,
`expected total score (${score}) to equal `,
- `sum of contribution scores (${totalSubscore}) `,
+ `sum of connection scores (${totalSubscore}) `,
`within ${epsilon}, but the difference is ${delta}`,
].join("");
throw new Error(message);
@@ -89,18 +89,18 @@ function validateDecomposition(decomposition) {
}
}
- // Check that each node's contributions are in score-descending order.
- for (const {scoredContributions} of decomposition.values()) {
- scoredContributions.forEach((current, index) => {
+ // Check that each node's connections are in score-descending order.
+ for (const {scoredConnections} of decomposition.values()) {
+ scoredConnections.forEach((current, index) => {
if (index === 0) {
return;
}
- const previous = scoredContributions[index - 1];
- if (current.contributionScore > previous.contributionScore) {
+ const previous = scoredConnections[index - 1];
+ if (current.connectionScore > previous.connectionScore) {
const message = [
- `expected contribution score to be non-increasing, but `,
- `element at index ${index} has score ${current.contributionScore}, `,
- `higher than that of its predecessor (${previous.contributionScore})`,
+ `expected connection score to be non-increasing, but `,
+ `element at index ${index} has score ${current.connectionScore}, `,
+ `higher than that of its predecessor (${previous.connectionScore})`,
].join("");
throw new Error(message);
}
@@ -108,7 +108,7 @@ function validateDecomposition(decomposition) {
}
}
-describe("core/attribution/contributions", () => {
+describe("core/attribution/connections", () => {
describe("decompose", () => {
it("has the expected output on a simple asymmetric chain", async () => {
const n1 = NodeAddress.fromParts(["n1"]);
@@ -127,8 +127,8 @@ describe("core/attribution/contributions", () => {
.addEdge(e3)
.addEdge(e4);
const edgeWeight = () => ({toWeight: 6.0, froWeight: 3.0});
- const contributions = createContributions(g, edgeWeight, 1.0);
- const osmc = createOrderedSparseMarkovChain(contributions);
+ const connections = createConnections(g, edgeWeight, 1.0);
+ const osmc = createOrderedSparseMarkovChain(connections);
const pi = await findStationaryDistribution(osmc.chain, {
verbose: false,
convergenceThreshold: 1e-6,
@@ -136,7 +136,7 @@ describe("core/attribution/contributions", () => {
yieldAfterMs: 1,
});
const pr = distributionToNodeDistribution(osmc.nodeOrder, pi);
- const result = decompose(pr, contributions);
+ const result = decompose(pr, connections);
expect(formatDecomposition(result)).toMatchSnapshot();
validateDecomposition(result);
});
@@ -144,8 +144,8 @@ describe("core/attribution/contributions", () => {
it("is valid on the example graph", async () => {
const g = advancedGraph().graph1();
const edgeWeight = () => ({toWeight: 6.0, froWeight: 3.0});
- const contributions = createContributions(g, edgeWeight, 1.0);
- const osmc = createOrderedSparseMarkovChain(contributions);
+ const connections = createConnections(g, edgeWeight, 1.0);
+ const osmc = createOrderedSparseMarkovChain(connections);
const pi = await findStationaryDistribution(osmc.chain, {
verbose: false,
convergenceThreshold: 1e-6,
@@ -153,7 +153,7 @@ describe("core/attribution/contributions", () => {
yieldAfterMs: 1,
});
const pr = distributionToNodeDistribution(osmc.nodeOrder, pi);
- const result = decompose(pr, contributions);
+ const result = decompose(pr, connections);
validateDecomposition(result);
});
});