From 53f4e565291223935dc3b6fef479be69aeca7e95 Mon Sep 17 00:00:00 2001 From: Sam Nolan Date: Fri, 8 Apr 2022 10:17:01 +1000 Subject: [PATCH] Implement generic sparklines with tests --- .../GenericDist/GenericDistSparkline_Test.res | 35 +++++++++++++++++++ .../Distributions/GenericDist/GenericDist.res | 5 ++- .../GenericDist/GenericDist.resi | 8 ++++- .../PointSetDist/PointSetDist.res | 16 +++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 packages/squiggle-lang/__tests__/GenericDist/GenericDistSparkline_Test.res diff --git a/packages/squiggle-lang/__tests__/GenericDist/GenericDistSparkline_Test.res b/packages/squiggle-lang/__tests__/GenericDist/GenericDistSparkline_Test.res new file mode 100644 index 00000000..8c29f7e4 --- /dev/null +++ b/packages/squiggle-lang/__tests__/GenericDist/GenericDistSparkline_Test.res @@ -0,0 +1,35 @@ +open Jest +open Expect + +let env: DistributionOperation.env = { + sampleCount: 100, + xyPointLength: 100, +} + +let normalDist: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 5.0, stdev: 2.0})) +let uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0})) +let betaDist: GenericDist_Types.genericDist = Symbolic(#Beta({alpha: 2.0, beta: 5.0})) +let lognormalDist: GenericDist_Types.genericDist = Symbolic(#Lognormal({mu: 0.0, sigma: 1.0})) +let cauchyDist: GenericDist_Types.genericDist = Symbolic(#Cauchy({local: 1.0, scale: 1.0})) +let triangularDist: GenericDist_Types.genericDist = Symbolic(#Triangular({low: 1.0, medium: 2.0, high: 3.0})) +let exponentialDist: GenericDist_Types.genericDist = Symbolic(#Exponential({rate: 2.0})) + +let runTest = (name: string, dist : GenericDist_Types.genericDist, expected: string) => { + test(name, () => { + let result = GenericDist.toSparkline(~xyPointLength=100, ~sampleCount=100, ~buckets=20, dist) + switch result { + | Ok(sparkline) => expect(sparkline)->toEqual(expected) + | Error(err) => expect("Error")->toEqual(expected) + } + }) +} + +describe("sparkline of generic distribution", () => { + runTest("normal", normalDist, `▁▁▁▁▂▃▄▆▇██▇▆▄▃▂▁▁▁`) + runTest("uniform", uniformDist, `████████████████████`) + runTest("beta", uniformDist, `████████████████████`) + runTest("lognormal", lognormalDist, `█▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁`) + runTest("cauchy", cauchyDist, `▁▁▁▁▁▁▁▁▁██▁▁▁▁▁▁▁▁▁`) + runTest("triangular", triangularDist, `▁▂▃▄▄▅▆▇████▇▆▅▄▄▃▂▁`) + runTest("exponential", exponentialDist, `█▆▄▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁`) +}) diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res index f71d9b93..ab2dd63f 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res @@ -49,7 +49,7 @@ let toFloatOperation = ( } } -//Todo: If it's a pointSet, but the xyPointLenght is different from what it has, it should change. +//Todo: If it's a pointSet, but the xyPointLength is different from what it has, it should change. // This is tricky because the case of discrete distributions. // Also, change the outputXYPoints/pointSetDistLength details let toPointSet = (~xyPointLength, ~sampleCount, t): result => { @@ -75,6 +75,9 @@ let toPointSet = (~xyPointLength, ~sampleCount, t): result => + toPointSet(~xyPointLength, ~sampleCount, t) -> E.R2.fmap(PointSetDist.toSparkline(buckets)) + module Truncate = { let trySymbolicSimplification = (leftCutoff, rightCutoff, t: t): option => switch (leftCutoff, rightCutoff, t) { diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi index 46db83a7..dcb929ca 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi @@ -24,6 +24,12 @@ let toPointSet: ( ~sampleCount: int, t, ) => result +let toSparkline: ( + ~xyPointLength: int, + ~sampleCount: int, + ~buckets: int=?, + t, +) => result let truncate: ( t, @@ -59,4 +65,4 @@ let mixture: ( array<(t, float)>, ~scaleMultiplyFn: scaleMultiplyFn, ~pointwiseAddFn: pointwiseAddFn, -) => result \ No newline at end of file +) => result diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res index 834b244f..7cacd634 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res @@ -168,6 +168,22 @@ let pdf = (f: float, t: t) => { let inv = T.Integral.yToX let cdf = T.Integral.xToY +let diff = (arr: array): array => + Belt.Array.zipBy(arr, Belt.Array.sliceToEnd(arr, 1), (left, right) => right -. left) + +let rec rangeByFloat = (start : float, end: float, step: float) => + start > end ? + [] + : Belt.Array.concat([start], rangeByFloat(start +. step, end, step)) + +@genType +let toSparkline = (buckets: int, t: t ): string => { + let size : float = T.maxX(t) -. T.minX(t) + let stepSize = size /. Belt.Int.toFloat(buckets) + let cdf = rangeByFloat(T.minX(t), T.maxX(t), stepSize) -> Belt.Array.map(val => cdf(val,t)) + Sparklines.create(diff(cdf), ()) +} + let doN = (n, fn) => { let items = Belt.Array.make(n, 0.0) for x in 0 to n - 1 {