Add `identity/contractIdentities` (#1601)
This commit adds a `contractIdentities` method to the Identity plugin, which allows contracting a WeightedGraph using the provided identities. The method does not attempt to contract weights together, although as a safety check it will error if weights have been explicitly provided for any of the contracted nodes. This PR replaces #1591; see that pull for some context on why this method is defined on the identity plugin rather than as part of the WeightedGraph module. Test plan: For ease of testing, `contractIdentities` is a thin wrapper around `nodeContractions` (which is already tested) and a new private `_contractWeightedGraph` method (for which tests have been added). Since `contractIdentities` is a trivial oneline composition, it does not need any additional explicit testing. `yarn test` passes.
This commit is contained in:
parent
50a6a5f6a7
commit
02b072699e
|
@ -0,0 +1,67 @@
|
|||
// @flow
|
||||
|
||||
import * as Weights from "../../core/weights";
|
||||
import {type WeightedGraph as WeightedGraphT} from "../../core/weightedGraph";
|
||||
import {type NodeContraction, NodeAddress} from "../../core/graph";
|
||||
import {nodeContractions} from "./nodeContractions";
|
||||
import {type Identity} from "./identity";
|
||||
|
||||
/**
|
||||
* Applies nodeContractions to a WeightedGraph.
|
||||
*
|
||||
* This functionality is defined as part of the identity plugin rather than as
|
||||
* a feature of WeightedGraph because it doesn't attempt to contract weights
|
||||
* (it's not clear how to do this in a general principled way). Within the
|
||||
* identity plugin, we do not expect user identities to have weights
|
||||
* associated, so we can contract the graph without attempting to contract the
|
||||
* weights.
|
||||
*
|
||||
* As a safety measure, this method will error if any of the node addresses
|
||||
* being contracted has an explicitly set weight. It will not error if there
|
||||
* are matching type weights, so that it is still possible (e.g.) to apply
|
||||
* a weighting to an entire plugin.
|
||||
*
|
||||
* For more context on this decision, see discussion in #1591.
|
||||
*/
|
||||
export function _contractWeightedGraph(
|
||||
wg: WeightedGraphT,
|
||||
contractions: $ReadOnlyArray<NodeContraction>
|
||||
): WeightedGraphT {
|
||||
const {graph, weights} = wg;
|
||||
for (const {old} of contractions) {
|
||||
for (const address of old) {
|
||||
const weight = weights.nodeWeights.get(address) || 0;
|
||||
if (weight !== 0) {
|
||||
throw new Error(
|
||||
`Explicit weight ${weight} on contracted node ${NodeAddress.toString(
|
||||
address
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
graph: graph.contractNodes(contractions),
|
||||
weights: Weights.copy(weights),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a WeightedGraph and identity information, produce a contracted
|
||||
* WeightedGraph where all of an identitiy's aliases have been contracted into
|
||||
* a unified identity.
|
||||
*
|
||||
* An error will be thrown if any of the aliases had an explicitly set weight,
|
||||
* since we don't currently support weight contraction.
|
||||
*
|
||||
* Note: This function has no unit tests, because it is a trivial composition
|
||||
* of two tested functions. Thus, flow typechecking in sufficient. If adding
|
||||
* any complexity to this function, please also add tests.
|
||||
*/
|
||||
export function contractIdentities(
|
||||
wg: WeightedGraphT,
|
||||
identities: $ReadOnlyArray<Identity>,
|
||||
discourseUrl: string | null
|
||||
): WeightedGraphT {
|
||||
return _contractWeightedGraph(wg, nodeContractions(identities, discourseUrl));
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// @flow
|
||||
|
||||
import {node} from "../../core/graphTestUtil";
|
||||
import {Graph, NodeAddress} from "../../core/graph";
|
||||
import * as Weights from "../../core/weights";
|
||||
import {_contractWeightedGraph} from "./contractIdentities";
|
||||
|
||||
describe("plugins/identity/contractIdentities", () => {
|
||||
const a = node("a");
|
||||
const b = node("b");
|
||||
const c = node("c");
|
||||
const graph = () => new Graph().addNode(a).addNode(b);
|
||||
const contractions = () => [{old: [a.address, b.address], replacement: c}];
|
||||
const weights = () => {
|
||||
const w = Weights.empty();
|
||||
w.nodeWeights.set(NodeAddress.empty, 3);
|
||||
return w;
|
||||
};
|
||||
describe("_contractWeightedGraph", () => {
|
||||
it("contracts the graph", () => {
|
||||
const wg = {graph: graph(), weights: weights()};
|
||||
const contracted = _contractWeightedGraph(wg, contractions());
|
||||
const expected = graph().contractNodes(contractions());
|
||||
expect(expected.equals(contracted.graph)).toBe(true);
|
||||
});
|
||||
it("returns a copy of the weights", () => {
|
||||
const ws = weights();
|
||||
const wg = {graph: graph(), weights: ws};
|
||||
const contracted = _contractWeightedGraph(wg, contractions());
|
||||
expect(contracted.weights).not.toBe(ws);
|
||||
expect(ws).toEqual(contracted.weights);
|
||||
// check they can be modified independently
|
||||
ws.nodeWeights.set(a.address, 5);
|
||||
expect(ws).not.toEqual(contracted.weights);
|
||||
});
|
||||
it("throws an error if a contracted node has an explicit weight", () => {
|
||||
const ws = weights();
|
||||
ws.nodeWeights.set(a.address, 5);
|
||||
const wg = {graph: graph(), weights: ws};
|
||||
expect(() => _contractWeightedGraph(wg, contractions())).toThrow(
|
||||
"Explicit weight 5 on contracted node"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue