Improve weight config (#644)
This modifies WeightConfig to properly use the fallback node type, as created in #640 and merged in #642. As an additional change, it now displays type names, rather than the address parts. For example, the issue type is now displayed as `Issue`, not `["sourcecred", "github", "issue"]`. The WeightConfig is an untested mess, and I will likely re-write it entirely. (See a bevy of WeightConfig related issues: #604, #595, #588). So, not too much effort was invested in keeping high code quality in this commit. Test plan: The weight config has no tests, so I manually tested: - weights persist after page reload - node weights influence cred attribution - edge weights influence cred attribution - edge directionality influences cred attribution - weights have reasonably pretty description messages
This commit is contained in:
parent
05c9f81cc0
commit
86ce26acb8
|
@ -19,7 +19,7 @@ import {
|
|||
export default class AppPage extends React.Component<{||}> {
|
||||
static _LOCAL_STORE = new CheckedLocalStore(
|
||||
new BrowserLocalStore({
|
||||
version: "1",
|
||||
version: "2",
|
||||
keyPrefix: "cred-explorer",
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,53 +1,42 @@
|
|||
// @flow
|
||||
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
type EdgeAddressT,
|
||||
type NodeAddressT,
|
||||
EdgeAddress,
|
||||
NodeAddress,
|
||||
} from "../../core/graph";
|
||||
import sortBy from "lodash.sortby";
|
||||
|
||||
import type {LocalStore} from "../localStore";
|
||||
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
|
||||
import {byEdgeType, byNodeType} from "./edgeWeights";
|
||||
import * as MapUtil from "../../util/map";
|
||||
import * as NullUtil from "../../util/null";
|
||||
import {defaultStaticAdapters} from "../adapters/defaultPlugins";
|
||||
import type {NodeType, EdgeType} from "../adapters/pluginAdapter";
|
||||
|
||||
type Props = {|
|
||||
+localStore: LocalStore,
|
||||
+onChange: (EdgeEvaluator) => void,
|
||||
|};
|
||||
|
||||
type EdgeWeights = Map<EdgeAddressT, UserEdgeWeight>;
|
||||
type UserEdgeWeight = {|+logWeight: number, +directionality: number|};
|
||||
|
||||
type WeightedEdgeType = {|
|
||||
+type: EdgeType,
|
||||
+logWeight: number,
|
||||
+directionality: number,
|
||||
|};
|
||||
type EdgeWeights = WeightedEdgeType[];
|
||||
const EDGE_WEIGHTS_KEY = "edgeWeights";
|
||||
const defaultEdgeWeights = (): EdgeWeights => {
|
||||
const result = new Map();
|
||||
for (const {prefix} of defaultStaticAdapters().edgeTypes()) {
|
||||
if (prefix === EdgeAddress.empty) {
|
||||
// We haven't decided how to deal with the FallbackAdapter's fallback type.
|
||||
continue;
|
||||
}
|
||||
result.set(prefix, {logWeight: 0, directionality: 0.5});
|
||||
const result = [];
|
||||
for (const type of defaultStaticAdapters().edgeTypes()) {
|
||||
result.push({type, logWeight: 0, directionality: 0.5});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
type NodeWeights = Map<NodeAddressT, UserNodeWeight>;
|
||||
type UserNodeWeight = number /* in log space */;
|
||||
type NodeWeights = WeightedNodeType[];
|
||||
type WeightedNodeType = {|+type: NodeType, +logWeight: number|};
|
||||
const NODE_WEIGHTS_KEY = "nodeWeights";
|
||||
const defaultNodeWeights = (): NodeWeights => {
|
||||
const result = new Map();
|
||||
for (const {prefix, defaultWeight} of defaultStaticAdapters().nodeTypes()) {
|
||||
if (prefix === NodeAddress.empty) {
|
||||
// We haven't decided how to deal with the FallbackAdapter's fallback type.
|
||||
continue;
|
||||
}
|
||||
result.set(prefix, Math.log2(defaultWeight));
|
||||
const result = [];
|
||||
for (const type of defaultStaticAdapters().nodeTypes()) {
|
||||
result.push({type, logWeight: type.defaultWeight});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
@ -74,11 +63,11 @@ export class WeightConfig extends React.Component<Props, State> {
|
|||
(state) => {
|
||||
return {
|
||||
edgeWeights: NullUtil.orElse(
|
||||
NullUtil.map(localStore.get(EDGE_WEIGHTS_KEY), MapUtil.fromObject),
|
||||
localStore.get(EDGE_WEIGHTS_KEY),
|
||||
state.edgeWeights
|
||||
),
|
||||
nodeWeights: NullUtil.orElse(
|
||||
NullUtil.map(localStore.get(NODE_WEIGHTS_KEY), MapUtil.fromObject),
|
||||
localStore.get(NODE_WEIGHTS_KEY),
|
||||
state.nodeWeights
|
||||
),
|
||||
};
|
||||
|
@ -127,21 +116,19 @@ export class WeightConfig extends React.Component<Props, State> {
|
|||
fire() {
|
||||
const {localStore} = this.props;
|
||||
const {edgeWeights, nodeWeights} = this.state;
|
||||
localStore.set(EDGE_WEIGHTS_KEY, MapUtil.toObject(edgeWeights));
|
||||
localStore.set(NODE_WEIGHTS_KEY, MapUtil.toObject(nodeWeights));
|
||||
const edgePrefixes = Array.from(edgeWeights.entries()).map(
|
||||
([prefix, {logWeight, directionality}]) => ({
|
||||
prefix,
|
||||
localStore.set(EDGE_WEIGHTS_KEY, edgeWeights);
|
||||
localStore.set(NODE_WEIGHTS_KEY, nodeWeights);
|
||||
const edgePrefixes = edgeWeights.map(
|
||||
({type, logWeight, directionality}) => ({
|
||||
prefix: type.prefix,
|
||||
weight: 2 ** logWeight,
|
||||
directionality,
|
||||
})
|
||||
);
|
||||
const nodePrefixes = Array.from(nodeWeights.entries()).map(
|
||||
([prefix, logWeight]) => ({
|
||||
prefix,
|
||||
weight: 2 ** logWeight,
|
||||
})
|
||||
);
|
||||
const nodePrefixes = nodeWeights.map(({type, logWeight}) => ({
|
||||
prefix: type.prefix,
|
||||
weight: 2 ** logWeight,
|
||||
}));
|
||||
const edgeEvaluator = byNodeType(byEdgeType(edgePrefixes), nodePrefixes);
|
||||
this.props.onChange(edgeEvaluator);
|
||||
}
|
||||
|
@ -152,49 +139,55 @@ class EdgeConfig extends React.Component<{
|
|||
onChange: (EdgeWeights) => void,
|
||||
}> {
|
||||
weightControls() {
|
||||
return Array.from(this.props.edgeWeights.entries()).map(([key, datum]) => (
|
||||
<label style={{display: "block"}} key={key}>
|
||||
const sortedWeights = sortBy(
|
||||
this.props.edgeWeights,
|
||||
({type}) => type.prefix
|
||||
);
|
||||
return sortedWeights.map(({type, directionality, logWeight}) => (
|
||||
<label style={{display: "block"}} key={type.prefix}>
|
||||
<input
|
||||
type="range"
|
||||
min={-10}
|
||||
max={10}
|
||||
step={0.1}
|
||||
value={datum.logWeight}
|
||||
value={logWeight}
|
||||
onChange={(e) => {
|
||||
const value: number = e.target.valueAsNumber;
|
||||
const edgeWeights = MapUtil.copy(this.props.edgeWeights).set(key, {
|
||||
...datum,
|
||||
logWeight: value,
|
||||
});
|
||||
const edgeWeights = this.props.edgeWeights.filter(
|
||||
(x) => x.type.prefix !== type.prefix
|
||||
);
|
||||
edgeWeights.push({type, logWeight: value, directionality});
|
||||
this.props.onChange(edgeWeights);
|
||||
}}
|
||||
/>{" "}
|
||||
{formatNumber(datum.logWeight)}{" "}
|
||||
{JSON.stringify(EdgeAddress.toParts(key))}
|
||||
{formatNumber(logWeight)} {`${type.forwardName}/${type.backwardName}`}
|
||||
</label>
|
||||
));
|
||||
}
|
||||
|
||||
directionControls() {
|
||||
return Array.from(this.props.edgeWeights.entries()).map(([key, datum]) => (
|
||||
<label style={{display: "block"}} key={key}>
|
||||
const sortedWeights = sortBy(
|
||||
this.props.edgeWeights,
|
||||
({type}) => type.prefix
|
||||
);
|
||||
return sortedWeights.map(({type, directionality, logWeight}) => (
|
||||
<label style={{display: "block"}} key={type.prefix}>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={datum.directionality}
|
||||
value={directionality}
|
||||
onChange={(e) => {
|
||||
const value: number = e.target.valueAsNumber;
|
||||
const edgeWeights = MapUtil.copy(this.props.edgeWeights).set(key, {
|
||||
...datum,
|
||||
directionality: value,
|
||||
});
|
||||
const edgeWeights = this.props.edgeWeights.filter(
|
||||
(x) => x.type.prefix !== type.prefix
|
||||
);
|
||||
edgeWeights.push({type, directionality: value, logWeight});
|
||||
this.props.onChange(edgeWeights);
|
||||
}}
|
||||
/>{" "}
|
||||
{datum.directionality.toFixed(2)}{" "}
|
||||
{JSON.stringify(EdgeAddress.toParts(key))}
|
||||
{directionality.toFixed(2)} {type.forwardName}
|
||||
</label>
|
||||
));
|
||||
}
|
||||
|
@ -215,29 +208,31 @@ class NodeConfig extends React.Component<{
|
|||
onChange: (NodeWeights) => void,
|
||||
}> {
|
||||
render() {
|
||||
const controls = Array.from(this.props.nodeWeights.entries()).map(
|
||||
([key, currentValue]) => (
|
||||
<label style={{display: "block"}} key={key}>
|
||||
<input
|
||||
type="range"
|
||||
min={-10}
|
||||
max={10}
|
||||
step={0.1}
|
||||
value={currentValue}
|
||||
onChange={(e) => {
|
||||
const value: number = e.target.valueAsNumber;
|
||||
const nodeWeights = MapUtil.copy(this.props.nodeWeights).set(
|
||||
key,
|
||||
value
|
||||
);
|
||||
this.props.onChange(nodeWeights);
|
||||
}}
|
||||
/>{" "}
|
||||
{formatNumber(currentValue)}{" "}
|
||||
{JSON.stringify(NodeAddress.toParts(key))}
|
||||
</label>
|
||||
)
|
||||
const sortedWeights = sortBy(
|
||||
this.props.nodeWeights,
|
||||
({type}) => type.prefix
|
||||
);
|
||||
|
||||
const controls = sortedWeights.map(({type, logWeight}) => (
|
||||
<label style={{display: "block"}} key={type.prefix}>
|
||||
<input
|
||||
type="range"
|
||||
min={-10}
|
||||
max={10}
|
||||
step={0.1}
|
||||
value={logWeight}
|
||||
onChange={(e) => {
|
||||
const value: number = e.target.valueAsNumber;
|
||||
const nodeWeights = this.props.nodeWeights.filter(
|
||||
(x) => x.type.prefix !== type.prefix
|
||||
);
|
||||
nodeWeights.push({type, logWeight: value});
|
||||
this.props.onChange(nodeWeights);
|
||||
}}
|
||||
/>{" "}
|
||||
{formatNumber(logWeight)} {type.name}
|
||||
</label>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
<h2>Node weights (in log space)</h2>
|
||||
|
|
|
@ -3,13 +3,10 @@ import {
|
|||
type Edge,
|
||||
type EdgeAddressT,
|
||||
type NodeAddressT,
|
||||
EdgeAddress,
|
||||
NodeAddress,
|
||||
} from "../../core/graph";
|
||||
import {NodeTrie, EdgeTrie} from "../../core/trie";
|
||||
import type {EdgeEvaluator} from "../../core/attribution/pagerank";
|
||||
|
||||
import * as NullUtil from "../../util/null";
|
||||
|
||||
export function byEdgeType(
|
||||
prefixes: $ReadOnlyArray<{|
|
||||
+prefix: EdgeAddressT,
|
||||
|
@ -17,10 +14,15 @@ export function byEdgeType(
|
|||
+directionality: number,
|
||||
|}>
|
||||
): EdgeEvaluator {
|
||||
const trie = new EdgeTrie();
|
||||
for (const weightedPrefix of prefixes) {
|
||||
trie.add(weightedPrefix.prefix, weightedPrefix);
|
||||
}
|
||||
return function evaluator(edge: Edge) {
|
||||
const {weight, directionality} = NullUtil.get(
|
||||
prefixes.find(({prefix}) => EdgeAddress.hasPrefix(edge.address, prefix))
|
||||
);
|
||||
const matchingPrefixes = trie.get(edge.address);
|
||||
const {weight, directionality} = matchingPrefixes[
|
||||
matchingPrefixes.length - 1
|
||||
];
|
||||
return {
|
||||
toWeight: directionality * weight,
|
||||
froWeight: (1 - directionality) * weight,
|
||||
|
@ -35,16 +37,16 @@ export function byNodeType(
|
|||
+weight: number,
|
||||
|}>
|
||||
): EdgeEvaluator {
|
||||
const trie = new NodeTrie();
|
||||
for (const weightedPrefix of prefixes) {
|
||||
trie.add(weightedPrefix.prefix, weightedPrefix);
|
||||
}
|
||||
return function evaluator(edge: Edge) {
|
||||
const srcDatum = prefixes.find(({prefix}) =>
|
||||
NodeAddress.hasPrefix(edge.src, prefix)
|
||||
);
|
||||
const dstDatum = prefixes.find(({prefix}) =>
|
||||
NodeAddress.hasPrefix(edge.dst, prefix)
|
||||
);
|
||||
if (srcDatum == null || dstDatum == null) {
|
||||
throw new Error(EdgeAddress.toString(edge.address));
|
||||
}
|
||||
const srcPrefixes = trie.get(edge.src);
|
||||
const srcDatum = srcPrefixes[srcPrefixes.length - 1];
|
||||
const dstPrefixes = trie.get(edge.dst);
|
||||
const dstDatum = dstPrefixes[dstPrefixes.length - 1];
|
||||
|
||||
const baseResult = base(edge);
|
||||
return {
|
||||
toWeight: dstDatum.weight * baseResult.toWeight,
|
||||
|
|
Loading…
Reference in New Issue