From f7383bbc903e179909a39d1e55043422879860f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Wed, 5 Sep 2018 13:40:40 -0700 Subject: [PATCH] Add `weightsToEdgeEvaluator` (#785) This commit adds `weightsToEdgeEvaluator`, a function for converting weighted node types into an `EdgeEvaluator`. This replaces the `edgeWeights` module (which was untested, and an outmoded API). Test plan: The new `weightsToEdgeEvaluator` method is well-tested. Since `WeightConfig` is still not tested, I manually verified that it still works as anticipated. --- src/app/credExplorer/WeightConfig.js | 16 +--- src/app/credExplorer/edgeWeights.js | 47 --------- .../weights/weightsToEdgeEvaluator.js | 27 ++++++ .../weights/weightsToEdgeEvaluator.test.js | 96 +++++++++++++++++++ 4 files changed, 126 insertions(+), 60 deletions(-) delete mode 100644 src/app/credExplorer/edgeWeights.js create mode 100644 src/app/credExplorer/weights/weightsToEdgeEvaluator.js create mode 100644 src/app/credExplorer/weights/weightsToEdgeEvaluator.test.js diff --git a/src/app/credExplorer/WeightConfig.js b/src/app/credExplorer/WeightConfig.js index 4f2830b..977055a 100644 --- a/src/app/credExplorer/WeightConfig.js +++ b/src/app/credExplorer/WeightConfig.js @@ -3,7 +3,7 @@ import React from "react"; import {type EdgeEvaluator} from "../../core/attribution/pagerank"; -import {byEdgeType, byNodeType} from "./edgeWeights"; +import {weightsToEdgeEvaluator} from "./weights/weightsToEdgeEvaluator"; import type {StaticAdapterSet} from "../adapters/adapterSet"; import { type WeightedTypes, @@ -104,18 +104,8 @@ export class WeightConfig extends React.Component { edgeWeights = edgeWeights.concat(newEdgeWeights); } - const edgePrefixes = edgeWeights.map( - ({type, forwardWeight, backwardWeight}) => ({ - prefix: type.prefix, - forwardWeight, - backwardWeight, - }) - ); - const nodePrefixes = nodeWeights.map(({type, weight}) => ({ - prefix: type.prefix, - weight, - })); - const edgeEvaluator = byNodeType(byEdgeType(edgePrefixes), nodePrefixes); + const weights = {nodes: nodeWeights, edges: edgeWeights}; + const edgeEvaluator = weightsToEdgeEvaluator(weights); this.props.onChange(edgeEvaluator); } } diff --git a/src/app/credExplorer/edgeWeights.js b/src/app/credExplorer/edgeWeights.js deleted file mode 100644 index bc63519..0000000 --- a/src/app/credExplorer/edgeWeights.js +++ /dev/null @@ -1,47 +0,0 @@ -// @flow -import { - type Edge, - type EdgeAddressT, - type NodeAddressT, -} from "../../core/graph"; -import {NodeTrie, EdgeTrie} from "../../core/trie"; -import type {EdgeEvaluator} from "../../core/attribution/pagerank"; - -export function byEdgeType( - prefixes: $ReadOnlyArray<{| - +prefix: EdgeAddressT, - +forwardWeight: number, - +backwardWeight: number, - |}> -): EdgeEvaluator { - const trie = new EdgeTrie(); - for (const {prefix, forwardWeight, backwardWeight} of prefixes) { - trie.add(prefix, {toWeight: forwardWeight, froWeight: backwardWeight}); - } - return function evaluator(edge: Edge) { - return trie.getLast(edge.address); - }; -} - -export function byNodeType( - base: EdgeEvaluator, - prefixes: $ReadOnlyArray<{| - +prefix: NodeAddressT, - +weight: number, - |}> -): EdgeEvaluator { - const trie = new NodeTrie(); - for (const {weight, prefix} of prefixes) { - trie.add(prefix, weight); - } - return function evaluator(edge: Edge) { - const srcWeight = trie.getLast(edge.src); - const dstWeight = trie.getLast(edge.dst); - - const baseResult = base(edge); - return { - toWeight: dstWeight * baseResult.toWeight, - froWeight: srcWeight * baseResult.froWeight, - }; - }; -} diff --git a/src/app/credExplorer/weights/weightsToEdgeEvaluator.js b/src/app/credExplorer/weights/weightsToEdgeEvaluator.js new file mode 100644 index 0000000..fd373dc --- /dev/null +++ b/src/app/credExplorer/weights/weightsToEdgeEvaluator.js @@ -0,0 +1,27 @@ +// @flow + +import type {Edge} from "../../../core/graph"; +import type {WeightedTypes} from "./PluginWeightConfig"; +import type {EdgeEvaluator} from "../../../core/attribution/pagerank"; +import {NodeTrie, EdgeTrie} from "../../../core/trie"; + +export function weightsToEdgeEvaluator(weights: WeightedTypes): EdgeEvaluator { + const nodeTrie = new NodeTrie(); + for (const {type, weight} of weights.nodes) { + nodeTrie.add(type.prefix, weight); + } + const edgeTrie = new EdgeTrie(); + for (const {type, forwardWeight, backwardWeight} of weights.edges) { + edgeTrie.add(type.prefix, {forwardWeight, backwardWeight}); + } + + return function evaluator(edge: Edge) { + const srcWeight = nodeTrie.getLast(edge.src); + const dstWeight = nodeTrie.getLast(edge.dst); + const {forwardWeight, backwardWeight} = edgeTrie.getLast(edge.address); + return { + toWeight: dstWeight * forwardWeight, + froWeight: srcWeight * backwardWeight, + }; + }; +} diff --git a/src/app/credExplorer/weights/weightsToEdgeEvaluator.test.js b/src/app/credExplorer/weights/weightsToEdgeEvaluator.test.js new file mode 100644 index 0000000..2668eab --- /dev/null +++ b/src/app/credExplorer/weights/weightsToEdgeEvaluator.test.js @@ -0,0 +1,96 @@ +// @flow + +import * as NullUtil from "../../../util/null"; +import { + fallbackNodeType, + fallbackEdgeType, +} from "../../adapters/fallbackAdapter"; +import { + inserterNodeType, + machineNodeType, + assemblesEdgeType, + factorioEdges, +} from "../../adapters/demoAdapters"; +import {weightsToEdgeEvaluator} from "./weightsToEdgeEvaluator"; + +describe("app/credExplorer/weights/weightsToEdgeEvaluator", () => { + describe("weightsToEdgeEvaluator", () => { + type WeightArgs = {| + +assemblesForward?: number, + +assemblesBackward?: number, + +baseForward?: number, + +baseBackward?: number, + +inserter?: number, + +machine?: number, + +baseNode?: number, + |}; + function weights({ + assemblesForward, + assemblesBackward, + baseForward, + baseBackward, + inserter, + machine, + baseNode, + }: WeightArgs) { + const nodes = [ + {weight: NullUtil.orElse(inserter, 1), type: inserterNodeType}, + {weight: NullUtil.orElse(machine, 1), type: machineNodeType}, + {weight: NullUtil.orElse(baseNode, 1), type: fallbackNodeType}, + ]; + const edges = [ + { + forwardWeight: NullUtil.orElse(assemblesForward, 1), + backwardWeight: NullUtil.orElse(assemblesBackward, 1), + type: assemblesEdgeType, + }, + { + forwardWeight: NullUtil.orElse(baseForward, 1), + backwardWeight: NullUtil.orElse(baseBackward, 1), + type: fallbackEdgeType, + }, + ]; + return {nodes, edges}; + } + function exampleEdgeWeights(weightArgs: WeightArgs) { + const ws = weights(weightArgs); + const ee = weightsToEdgeEvaluator(ws); + // src is a machine, dst is an inserter, edge type is assembles + return ee(factorioEdges.assembles1); + } + it("toWeight is affected by the edge's forwardWeight", () => { + expect(exampleEdgeWeights({assemblesForward: 2}).toWeight).toEqual(2); + }); + it("froWeight is affected by the edge's backwardWeight", () => { + expect(exampleEdgeWeights({assemblesBackward: 3}).froWeight).toEqual(3); + }); + it("toWeight is affected by the dst's weight", () => { + expect(exampleEdgeWeights({inserter: 4}).toWeight).toEqual(4); + }); + it("froWeight is affected by the src's weight", () => { + expect(exampleEdgeWeights({machine: 5}).froWeight).toEqual(5); + }); + it("only the closest-matching node prefix is considered", () => { + expect(exampleEdgeWeights({baseNode: 6})).toEqual({ + toWeight: 1, + froWeight: 1, + }); + }); + it("only the closest-matching edge prefix is considered", () => { + expect(exampleEdgeWeights({baseBackward: 7})).toEqual({ + toWeight: 1, + froWeight: 1, + }); + }); + it("node and edge weights compose via multiplication", () => { + expect( + exampleEdgeWeights({ + inserter: 2, + machine: 3, + assemblesForward: 4, + assemblesBackward: 5, + }) + ).toEqual({toWeight: 8, froWeight: 15}); + }); + }); +});