diff --git a/src/analysis/timeline/timelineCred.js b/src/analysis/timeline/timelineCred.js index efac86f..e1991b4 100644 --- a/src/analysis/timeline/timelineCred.js +++ b/src/analysis/timeline/timelineCred.js @@ -209,12 +209,12 @@ export class TimelineCred { const addressToCred = new Map(); for (let i = 0; i < nodeOrder.length; i++) { const addr = nodeOrder[i]; - const addrCred = credScores.intervalCredScores.map((cred) => cred[i]); + const addrCred = credScores.map(({cred}) => cred[i]); addressToCred.set(addr, addrCred); } return new TimelineCred( weightedGraph, - credScores.intervals, + credScores.map((x) => x.interval), addressToCred, fullParams, plugins diff --git a/src/core/algorithm/distributionToCred.js b/src/core/algorithm/distributionToCred.js index dc384d1..5feec65 100644 --- a/src/core/algorithm/distributionToCred.js +++ b/src/core/algorithm/distributionToCred.js @@ -15,25 +15,17 @@ export opaque type NodeOrderedCredScores: Float64Array = Float64Array; /** * Represents cred scores over time. * - * It contains an array of intervals, which give timing information, and an - * array of CredTimeSlices, which are Float64Arrays. Each CredTimeSlice - * contains cred scores for an interval. The cred scores are included in - * node-address-sorted order, and as such the CredScores can only be - * interpreted in the context of an associated Graph. - * - * As invariants, it is guaranteed that: - * - intervals and intervalCredScores will always have the same length - * - all of the intervalCredScores will have a consistent implicit node ordering - * - * The type is marked opaque so that no-one else can construct instances that - * don't conform to these invariants. + * The TimelineCredScores consists of a time-ordered array of IntervalCreds. + * Each IntervalCred contains the interval information, as well as the raw + * cred score for every node in the graph. The cred is stored as a Float64Array, + * with scores corresponding to nodes by the node's index in the Graph's + * canonical address-sorted node ordering. */ -export opaque type TimelineCredScores: {| - +intervals: $ReadOnlyArray, - +intervalCredScores: $ReadOnlyArray, -|} = {| - +intervals: $ReadOnlyArray, - +intervalCredScores: $ReadOnlyArray, +export type TimelineCredScores = $ReadOnlyArray; + +export type IntervalCred = {| + +interval: Interval, + +cred: NodeOrderedCredScores, |}; /** @@ -59,9 +51,6 @@ export function distributionToCred( nodeOrder: $ReadOnlyArray, scoringNodePrefixes: $ReadOnlyArray ): TimelineCredScores { - if (ds.length === 0) { - return {intervals: [], intervalCredScores: []}; - } const scoringNodeIndices = []; for (let i = 0; i < nodeOrder.length; i++) { const addr = nodeOrder[i]; @@ -69,8 +58,7 @@ export function distributionToCred( scoringNodeIndices.push(i); } } - const intervals = ds.map((x) => x.interval); - const intervalCredScores = ds.map(({distribution, intervalWeight}) => { + return ds.map(({interval, distribution, intervalWeight}) => { const intervalTotalScore = sum( scoringNodeIndices.map((x) => distribution[x]) ); @@ -78,32 +66,33 @@ export function distributionToCred( const intervalNormalizer = intervalTotalScore === 0 ? 0 : intervalWeight / intervalTotalScore; const cred = distribution.map((x) => x * intervalNormalizer); - return cred; + return {interval, cred}; }); - return {intervalCredScores, intervals}; } -const COMPAT_INFO = {type: "sourcecred/timelineCredScores", version: "0.1.0"}; +const COMPAT_INFO = {type: "sourcecred/timelineCredScores", version: "0.2.0"}; -export type TimelineCredScoresJSON = Compatible<{| - +intervals: $ReadOnlyArray, - // TODO: Serializing floats as strings is space-inefficient. We can likely - // get space savings if we base64 encode a byte representation of the - // floats. - +intervalCredScores: $ReadOnlyArray<$ReadOnlyArray>, -|}>; +export type TimelineCredScoresJSON = Compatible< + $ReadOnlyArray<{| + +interval: Interval, + // TODO: Serializing floats as strings is space-inefficient. We can likely + // get space savings if we base64 encode a byte representation of the + // floats. + +cred: $ReadOnlyArray, + |}> +>; export function toJSON(s: TimelineCredScores): TimelineCredScoresJSON { - return toCompat(COMPAT_INFO, { - intervals: s.intervals, - intervalCredScores: s.intervalCredScores.map((x) => Array.from(x)), - }); + return toCompat( + COMPAT_INFO, + s.map(({interval, cred}) => ({interval, cred: Array.from(cred)})) + ); } export function fromJSON(j: TimelineCredScoresJSON): TimelineCredScores { - const {intervals, intervalCredScores} = fromCompat(COMPAT_INFO, j); - return { - intervals, - intervalCredScores: intervalCredScores.map((x) => new Float64Array(x)), - }; + const scoreArray = fromCompat(COMPAT_INFO, j); + return scoreArray.map(({cred, interval}) => ({ + cred: new Float64Array(cred), + interval, + })); } diff --git a/src/core/algorithm/distributionToCred.test.js b/src/core/algorithm/distributionToCred.test.js index 566362c..15d9244 100644 --- a/src/core/algorithm/distributionToCred.test.js +++ b/src/core/algorithm/distributionToCred.test.js @@ -21,16 +21,16 @@ describe("src/core/algorithm/distributionToCred", () => { ]; const nodeOrder = [na("foo"), na("bar")]; const actual = distributionToCred(ds, nodeOrder, [NodeAddress.empty]); - const expected = { - intervals: [ - {startTimeMs: 0, endTimeMs: 10}, - {startTimeMs: 10, endTimeMs: 20}, - ], - intervalCredScores: [ - new Float64Array([1, 1]), - new Float64Array([9, 1]), - ], - }; + const expected = [ + { + interval: {startTimeMs: 0, endTimeMs: 10}, + cred: new Float64Array([1, 1]), + }, + { + interval: {startTimeMs: 10, endTimeMs: 20}, + cred: new Float64Array([9, 1]), + }, + ]; expect(expected).toEqual(actual); }); it("correctly handles multiple scoring prefixes", () => { @@ -48,16 +48,16 @@ describe("src/core/algorithm/distributionToCred", () => { ]; const nodeOrder = [na("foo"), na("bar")]; const actual = distributionToCred(ds, nodeOrder, [na("foo"), na("bar")]); - const expected = { - intervals: [ - {startTimeMs: 0, endTimeMs: 10}, - {startTimeMs: 10, endTimeMs: 20}, - ], - intervalCredScores: [ - new Float64Array([1, 1]), - new Float64Array([9, 1]), - ], - }; + const expected = [ + { + interval: {startTimeMs: 0, endTimeMs: 10}, + cred: new Float64Array([1, 1]), + }, + { + interval: {startTimeMs: 10, endTimeMs: 20}, + cred: new Float64Array([9, 1]), + }, + ]; expect(expected).toEqual(actual); }); it("works in a case where some nodes are scoring", () => { @@ -75,16 +75,16 @@ describe("src/core/algorithm/distributionToCred", () => { ]; const nodeOrder = [na("foo"), na("bar")]; const actual = distributionToCred(ds, nodeOrder, [na("bar")]); - const expected = { - intervals: [ - {startTimeMs: 0, endTimeMs: 10}, - {startTimeMs: 10, endTimeMs: 20}, - ], - intervalCredScores: [ - new Float64Array([2, 2]), - new Float64Array([90, 10]), - ], - }; + const expected = [ + { + interval: {startTimeMs: 0, endTimeMs: 10}, + cred: new Float64Array([2, 2]), + }, + { + interval: {startTimeMs: 10, endTimeMs: 20}, + cred: new Float64Array([90, 10]), + }, + ]; expect(expected).toEqual(actual); }); it("handles the case where no nodes are scoring", () => { @@ -97,10 +97,12 @@ describe("src/core/algorithm/distributionToCred", () => { ]; const nodeOrder = [na("foo"), na("bar")]; const actual = distributionToCred(ds, nodeOrder, []); - const expected = { - intervals: [{startTimeMs: 0, endTimeMs: 10}], - intervalCredScores: [new Float64Array([0, 0])], - }; + const expected = [ + { + interval: {startTimeMs: 0, endTimeMs: 10}, + cred: new Float64Array([0, 0]), + }, + ]; expect(actual).toEqual(expected); }); @@ -114,18 +116,17 @@ describe("src/core/algorithm/distributionToCred", () => { ]; const nodeOrder = [na("foo"), na("bar")]; const actual = distributionToCred(ds, nodeOrder, [na("bar")]); - const expected = { - intervals: [{startTimeMs: 0, endTimeMs: 10}], - intervalCredScores: [new Float64Array([0, 0])], - }; + const expected = [ + { + interval: {startTimeMs: 0, endTimeMs: 10}, + cred: new Float64Array([0, 0]), + }, + ]; expect(actual).toEqual(expected); }); it("returns empty CredScores if no intervals are present", () => { - expect(distributionToCred([], [], [])).toEqual({ - intervals: [], - intervalCredScores: [], - }); + expect(distributionToCred([], [], [])).toEqual([]); }); }); describe("to/from JSON", () => { @@ -155,30 +156,30 @@ describe("src/core/algorithm/distributionToCred", () => { Array [ Object { "type": "sourcecred/timelineCredScores", - "version": "0.1.0", + "version": "0.2.0", }, - Object { - "intervalCredScores": Array [ - Array [ + Array [ + Object { + "cred": Array [ 2, 2, ], - Array [ - 90, - 10, - ], - ], - "intervals": Array [ - Object { + "interval": Object { "endTimeMs": 10, "startTimeMs": 0, }, - Object { + }, + Object { + "cred": Array [ + 90, + 10, + ], + "interval": Object { "endTimeMs": 20, "startTimeMs": 10, }, - ], - }, + }, + ], ] `); });