mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-28 20:20:35 +00:00
pagerankGraph: add edge filter (#1105)
Part of ongoing work for #1020. Test plan: Added tests that mirror the edge filtering tests in `graph.test` to check that `graph` and `pagerankGraph` return the same edges with the given `EdgesOptions` parameter. Also added a sanity check that a `weight` prop is returned from the iterator along with the edge. Given the dependence on a helper function to test the edge iterator's equality between graphs, I would suggest reviewers give particular attention to that function: `expectConsistentEdges()`
This commit is contained in:
parent
656a2d1543
commit
b16c374a2b
@ -6,6 +6,7 @@ import {toCompat, fromCompat, type Compatible} from "../util/compat";
|
|||||||
import {
|
import {
|
||||||
Graph,
|
Graph,
|
||||||
type Edge,
|
type Edge,
|
||||||
|
type EdgesOptions,
|
||||||
type NodeAddressT,
|
type NodeAddressT,
|
||||||
type EdgeAddressT,
|
type EdgeAddressT,
|
||||||
type GraphJSON,
|
type GraphJSON,
|
||||||
@ -237,15 +238,18 @@ export class PagerankGraph {
|
|||||||
/**
|
/**
|
||||||
* Provides edge and weight for every edge in the underlying graph.
|
* Provides edge and weight for every edge in the underlying graph.
|
||||||
*
|
*
|
||||||
* TODO(#1020): Allow optional filtering, as in Graph.edges.
|
* Optionally, provide an EdgesOptions parameter to return an
|
||||||
|
* iterator containing edges matching the EdgesOptions prefix
|
||||||
|
* filter parameters. See Graph.edges for details.
|
||||||
*/
|
*/
|
||||||
edges(): Iterator<WeightedEdge> {
|
edges(options?: EdgesOptions): Iterator<WeightedEdge> {
|
||||||
this._verifyGraphNotModified();
|
this._verifyGraphNotModified();
|
||||||
return this._edgesIterator();
|
const iterator = this._graph.edges(options);
|
||||||
|
return this._edgesIterator(iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
*_edgesIterator(): Iterator<WeightedEdge> {
|
*_edgesIterator(iterator: Iterator<Edge>): Iterator<WeightedEdge> {
|
||||||
for (const edge of this._graph.edges()) {
|
for (const edge of iterator) {
|
||||||
const weight = NullUtil.get(this._edgeWeights.get(edge.address));
|
const weight = NullUtil.get(this._edgeWeights.get(edge.address));
|
||||||
yield {edge, weight};
|
yield {edge, weight};
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
EdgeAddress,
|
EdgeAddress,
|
||||||
type NodeAddressT,
|
type NodeAddressT,
|
||||||
type Edge,
|
type Edge,
|
||||||
|
type EdgesOptions,
|
||||||
} from "./graph";
|
} from "./graph";
|
||||||
import {PagerankGraph} from "./pagerankGraph";
|
import {PagerankGraph} from "./pagerankGraph";
|
||||||
import {advancedGraph} from "./graphTestUtil";
|
import {advancedGraph} from "./graphTestUtil";
|
||||||
@ -230,6 +231,123 @@ describe("core/pagerankGraph", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("edge filtering", () => {
|
||||||
|
const src1 = NodeAddress.fromParts(["src", "1"]);
|
||||||
|
const src2 = NodeAddress.fromParts(["src", "2"]);
|
||||||
|
const dst1 = NodeAddress.fromParts(["dst", "1"]);
|
||||||
|
const dst2 = NodeAddress.fromParts(["dst", "2"]);
|
||||||
|
const e11 = {
|
||||||
|
src: src1,
|
||||||
|
dst: dst1,
|
||||||
|
address: EdgeAddress.fromParts(["e", "1", "1"]),
|
||||||
|
};
|
||||||
|
const e12 = {
|
||||||
|
src: src1,
|
||||||
|
dst: dst2,
|
||||||
|
address: EdgeAddress.fromParts(["e", "1", "2"]),
|
||||||
|
};
|
||||||
|
const e21 = {
|
||||||
|
src: src2,
|
||||||
|
dst: dst1,
|
||||||
|
address: EdgeAddress.fromParts(["e", "2", "1"]),
|
||||||
|
};
|
||||||
|
const e22 = {
|
||||||
|
src: src2,
|
||||||
|
dst: dst2,
|
||||||
|
address: EdgeAddress.fromParts(["e", "2", "2"]),
|
||||||
|
};
|
||||||
|
const graph = () => {
|
||||||
|
const g = new Graph();
|
||||||
|
[src1, src2, dst1, dst2].forEach((n) => g.addNode(n));
|
||||||
|
[e11, e12, e21, e22].forEach((e) => g.addEdge(e));
|
||||||
|
return g;
|
||||||
|
};
|
||||||
|
const pagerankGraph = () => new PagerankGraph(graph(), defaultEvaluator);
|
||||||
|
|
||||||
|
function expectConsistentEdges(options: EdgesOptions | void) {
|
||||||
|
const pagerankGraphEdges = Array.from(pagerankGraph().edges(options));
|
||||||
|
pagerankGraphEdges.forEach((e) => {
|
||||||
|
expect(e.weight.froWeight).toBe(0);
|
||||||
|
expect(e.weight.toWeight).toBe(1);
|
||||||
|
});
|
||||||
|
const graphEdges = Array.from(graph().edges(options));
|
||||||
|
expect(pagerankGraphEdges.map((e) => e.edge)).toEqual(graphEdges);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("edge filter matches graph edge filter", () => {
|
||||||
|
it("finds all edges when no options are specified", () => {
|
||||||
|
expectConsistentEdges(undefined);
|
||||||
|
});
|
||||||
|
it("finds all edges when all-inclusive filters are specified", () => {
|
||||||
|
expectConsistentEdges({
|
||||||
|
addressPrefix: EdgeAddress.fromParts(["e"]),
|
||||||
|
srcPrefix: NodeAddress.fromParts(["src"]),
|
||||||
|
dstPrefix: NodeAddress.fromParts(["dst"]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("finds edges by address prefix", () => {
|
||||||
|
expectConsistentEdges({
|
||||||
|
addressPrefix: EdgeAddress.fromParts(["e", "1"]),
|
||||||
|
srcPrefix: NodeAddress.empty,
|
||||||
|
dstPrefix: NodeAddress.empty,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("finds edges by src prefix", () => {
|
||||||
|
expectConsistentEdges({
|
||||||
|
addressPrefix: EdgeAddress.empty,
|
||||||
|
srcPrefix: NodeAddress.fromParts(["src", "1"]),
|
||||||
|
dstPrefix: NodeAddress.empty,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("finds edges by dst prefix", () => {
|
||||||
|
expectConsistentEdges({
|
||||||
|
addressPrefix: EdgeAddress.empty,
|
||||||
|
srcPrefix: NodeAddress.empty,
|
||||||
|
dstPrefix: NodeAddress.fromParts(["dst", "1"]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("yields nothing for disjoint filters", () => {
|
||||||
|
expectConsistentEdges({
|
||||||
|
addressPrefix: EdgeAddress.fromParts(["e", "1"]),
|
||||||
|
srcPrefix: NodeAddress.fromParts(["src", "2"]),
|
||||||
|
dstPrefix: NodeAddress.empty,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("yields appropriate filter intersection", () => {
|
||||||
|
expectConsistentEdges({
|
||||||
|
addressPrefix: EdgeAddress.empty,
|
||||||
|
srcPrefix: NodeAddress.fromParts(["src", "1"]),
|
||||||
|
dstPrefix: NodeAddress.fromParts(["dst", "2"]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("edge filter options", () => {
|
||||||
|
it("requires `addressPrefix` to be present in provided options", () => {
|
||||||
|
expect(() => {
|
||||||
|
pagerankGraph()
|
||||||
|
// $ExpectFlowError
|
||||||
|
.edges({srcPrefix: src1, dstPrefix: dst1});
|
||||||
|
}).toThrow("Invalid address prefix: undefined");
|
||||||
|
});
|
||||||
|
it("requires `srcPrefix` to be present in provided options", () => {
|
||||||
|
expect(() => {
|
||||||
|
pagerankGraph()
|
||||||
|
// $ExpectFlowError
|
||||||
|
.edges({addressPrefix: e11, dstPrefix: dst1});
|
||||||
|
}).toThrow("Invalid src prefix: undefined");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires `dstPrefix` to be present in provided options", () => {
|
||||||
|
expect(() => {
|
||||||
|
pagerankGraph()
|
||||||
|
// $ExpectFlowError
|
||||||
|
.edges({addressPrefix: e11, srcPrefix: dst1});
|
||||||
|
}).toThrow("Invalid dst prefix: undefined");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("runPagerank", () => {
|
describe("runPagerank", () => {
|
||||||
// The mathematical semantics of PageRank are thoroughly tested
|
// The mathematical semantics of PageRank are thoroughly tested
|
||||||
// in the markovChain module. The goal for these tests is just
|
// in the markovChain module. The goal for these tests is just
|
||||||
|
Loading…
x
Reference in New Issue
Block a user