diff --git a/packages/squiggle-lang/__tests__/Distributions/DistributionOperation_test.res b/packages/squiggle-lang/__tests__/Distributions/DistributionOperation_test.res index bfe630ae..cd22d512 100644 --- a/packages/squiggle-lang/__tests__/Distributions/DistributionOperation_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/DistributionOperation_test.res @@ -6,9 +6,19 @@ let env: DistributionOperation.env = { xyPointLength: 100, } +let { + normalDist5, + normalDist10, + normalDist20, + normalDist, + uniformDist, + betaDist, + lognormalDist, + cauchyDist, + triangularDist, + exponentialDist, +} = module(GenericDist_Fixtures) let mkNormal = (mean, stdev) => GenericDist_Types.Symbolic(#Normal({mean: mean, stdev: stdev})) -let normalDist5: GenericDist_Types.genericDist = mkNormal(5.0, 2.0) -let uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0})) let {toFloat, toDist, toString, toError} = module(DistributionOperation.Output) let {run} = module(DistributionOperation) @@ -19,6 +29,57 @@ let toExt: option<'a> => 'a = E.O.toExt( "Should be impossible to reach (This error is in test file)", ) +describe("sparkline", () => { + let runTest = ( + name: string, + dist: GenericDist_Types.genericDist, + expected: DistributionOperation.outputType, + ) => { + test(name, () => { + let result = DistributionOperation.run(~env, FromDist(ToString(ToSparkline(20)), dist)) + expect(result)->toEqual(expected) + }) + } + + runTest( + "normal", + normalDist, + String(`▁▁▁▁▁▂▄▆▇██▇▆▄▂▁▁▁▁▁`), + ) + + runTest( + "uniform", + uniformDist, + String(`████████████████████`), + ) + + runTest("beta", betaDist, String(`▁▄▇████▇▆▅▄▃▃▂▁▁▁▁▁▁`)) + + runTest( + "lognormal", + lognormalDist, + String(`▁█▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁`), + ) + + runTest( + "cauchy", + cauchyDist, + String(`▁▁▁▁▁▁▁▁▁██▁▁▁▁▁▁▁▁▁`), + ) + + runTest( + "triangular", + triangularDist, + String(`▁▁▂▃▄▅▆▇████▇▆▅▄▃▂▁▁`), + ) + + runTest( + "exponential", + exponentialDist, + String(`█▅▄▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁`), + ) +}) + describe("toPointSet", () => { test("on symbolic normal distribution", () => { let result = diff --git a/packages/squiggle-lang/__tests__/Distributions/GenericDist_Fixtures.res b/packages/squiggle-lang/__tests__/Distributions/GenericDist_Fixtures.res new file mode 100644 index 00000000..05f0981b --- /dev/null +++ b/packages/squiggle-lang/__tests__/Distributions/GenericDist_Fixtures.res @@ -0,0 +1,11 @@ +let normalDist5: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 5.0, stdev: 2.0})) +let normalDist10: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 10.0, stdev: 2.0})) +let normalDist20: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 20.0, stdev: 2.0})) +let normalDist: GenericDist_Types.genericDist = normalDist5 + +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 uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0})) diff --git a/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res b/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res index 59ab2a8b..9a37a63a 100644 --- a/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res @@ -134,21 +134,21 @@ describe("Normal distribution with sparklines", () => { let normalDistAtMean5: SymbolicDistTypes.normal = {mean: 5.0, stdev: 2.0} let normalDistAtMean10: SymbolicDistTypes.normal = {mean: 10.0, stdev: 2.0} - let range20Float = E.A.rangeFloat(0, 20) // [0.0,1.0,2.0,3.0,4.0,...19.0,] + let range20Float = E.A.Floats.range(0.0, 20.0, 20) // [0.0,1.0,2.0,3.0,4.0,...19.0,] test("mean=5 pdf", () => { let pdfNormalDistAtMean5 = x => SymbolicDist.Normal.pdf(x, normalDistAtMean5) let sparklineMean5 = fnImage(pdfNormalDistAtMean5, range20Float) Sparklines.create(sparklineMean5, ()) -> expect - -> toEqual(`▁▂▃▅███▅▃▂▁▁▁▁▁▁▁▁▁▁▁`) + -> toEqual(`▁▂▃▆██▇▅▂▁▁▁▁▁▁▁▁▁▁▁`) }) test("parameter-wise addition of two normal distributions", () => { let sparklineMean15 = normalDistAtMean5 -> parameterWiseAdditionPdf(normalDistAtMean10) -> fnImage(range20Float) Sparklines.create(sparklineMean15, ()) -> expect - -> toEqual(`▁▁▁▁▁▁▁▁▁▁▂▃▅▇███▇▅▃▂`) + -> toEqual(`▁▁▁▁▁▁▁▁▁▂▃▄▆███▇▅▄▂`) }) test("mean=10 cdf", () => { @@ -156,6 +156,6 @@ describe("Normal distribution with sparklines", () => { let sparklineMean10 = fnImage(cdfNormalDistAtMean10, range20Float) Sparklines.create(sparklineMean10, ()) -> expect - -> toEqual(`▁▁▁▁▁▁▁▁▂▃▅▆▇████████`) + -> toEqual(`▁▁▁▁▁▁▁▁▂▄▅▇████████`) }) }) diff --git a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res index 7029e2ff..65f689cc 100644 --- a/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res +++ b/packages/squiggle-lang/__tests__/Reducer/Reducer_TestHelpers.res @@ -9,6 +9,3 @@ let expectParseToBe = (expr: string, answer: string) => let expectEvalToBe = (expr: string, answer: string) => Reducer.eval(expr)->ExpressionValue.toStringResult->expect->toBe(answer) - -// Current configuration does not ignore this file so we have to have a test -test("test helpers", () => expect(1)->toBe(1)) diff --git a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res index 85d80919..096ac360 100644 --- a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res +++ b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res @@ -30,6 +30,9 @@ describe("eval on distribution functions", () => { testEval("mean(normal(5,2))", "Ok(5)") testEval("mean(lognormal(1,2))", "Ok(20.085536923187668)") }) + describe("toString", () => { + testEval("toString(normal(5,2))", "Ok('Normal(5,2)')") + }) describe("normalize", () => { testEval("normalize(normal(5,2))", "Ok(Normal(5,2))") }) diff --git a/packages/squiggle-lang/jest.config.js b/packages/squiggle-lang/jest.config.js index f539ef30..3347de7c 100644 --- a/packages/squiggle-lang/jest.config.js +++ b/packages/squiggle-lang/jest.config.js @@ -1,11 +1,13 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', + preset: "ts-jest", + testEnvironment: "node", setupFilesAfterEnv: [ - "/../../node_modules/bisect_ppx/src/runtime/js/jest.bs.js" + "/../../node_modules/bisect_ppx/src/runtime/js/jest.bs.js", ], testPathIgnorePatterns: [ - "__tests__/TestHelpers.bs.js" - ], + ".*Fixtures.bs.js", + "/node_modules/", + ".*Helpers.bs.js", + ], }; diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res index 87195ec5..14e7b6c4 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res @@ -113,7 +113,11 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => { GenericDist.toFloatOperation(dist, ~toPointSetFn, ~distToFloatOperation) ->E.R2.fmap(r => Float(r)) ->OutputLocal.fromResult - | ToString => dist->GenericDist.toString->String + | ToString(ToString) => dist->GenericDist.toString->String + | ToString(ToSparkline(buckets)) => + GenericDist.toSparkline(dist, ~sampleCount, ~buckets, ()) + ->E.R2.fmap(r => String(r)) + ->OutputLocal.fromResult | ToDist(Inspect) => { Js.log2("Console log requested: ", dist) Dist(dist) @@ -127,7 +131,7 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => { dist->GenericDist.sampleN(n)->E.R2.fmap(r => Dist(SampleSet(r)))->OutputLocal.fromResult | ToDist(ToPointSet) => dist - ->GenericDist.toPointSet(~xyPointLength, ~sampleCount) + ->GenericDist.toPointSet(~xyPointLength, ~sampleCount, ()) ->E.R2.fmap(r => Dist(PointSet(r))) ->OutputLocal.fromResult | ToDistCombination(Algebraic, _, #Float(_)) => GenDistError(NotYetImplemented) diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res index f71d9b93..58366a77 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res @@ -49,13 +49,19 @@ 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 => { +let toPointSet = ( + t, + ~xyPointLength, + ~sampleCount, + ~xSelection: GenericDist_Types.Operation.pointsetXSelection=#ByWeight, + unit, +): result => { switch (t: t) { | PointSet(pointSet) => Ok(pointSet) - | Symbolic(r) => Ok(SymbolicDist.T.toPointSetDist(xyPointLength, r)) + | Symbolic(r) => Ok(SymbolicDist.T.toPointSetDist(~xSelection, xyPointLength, r)) | SampleSet(r) => { let response = SampleSet.toPointSetDist( ~samples=r, @@ -75,6 +81,11 @@ let toPointSet = (~xyPointLength, ~sampleCount, t): result => + t + ->toPointSet(~xSelection=#Linear, ~xyPointLength=buckets, ~sampleCount, ()) + ->E.R.bind(r => r->PointSetDist.toSparkline->E.R2.errMap(r => Error(GenericDist_Types.Other(r)))) + 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..5fa24de9 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi @@ -20,17 +20,20 @@ let toFloatOperation: ( ) => result let toPointSet: ( + t, ~xyPointLength: int, ~sampleCount: int, - t, + ~xSelection: GenericDist_Types.Operation.pointsetXSelection=?, + unit, ) => result +let toSparkline: (t, ~sampleCount: int, ~buckets: int=?, unit) => result let truncate: ( t, ~toPointSetFn: toPointSetFn, ~leftCutoff: option=?, ~rightCutoff: option=?, - unit + unit, ) => result let algebraicCombination: ( @@ -59,4 +62,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/GenericDist/GenericDist_Types.res b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res index df7549bb..0eb64bd7 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res @@ -41,6 +41,8 @@ module Operation = { | #Sample ] + type pointsetXSelection = [#Linear | #ByWeight] + type toDist = | Normalize | ToPointSet @@ -50,11 +52,15 @@ module Operation = { type toFloatArray = Sample(int) + type toString = + | ToString + | ToSparkline(int) + type fromDist = | ToFloat(toFloat) | ToDist(toDist) | ToDistCombination(direction, arithmeticOperation, [#Dist(genericDist) | #Float(float)]) - | ToString + | ToString(toString) type singleParamaterFunction = | FromDist(fromDist) @@ -77,7 +83,8 @@ module Operation = { | ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})` | ToDist(Truncate(_, _)) => `truncate` | ToDist(Inspect) => `inspect` - | ToString => `toString` + | ToString(ToString) => `toString` + | ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})` | ToDistCombination(Algebraic, _, _) => `algebraic` | ToDistCombination(Pointwise, _, _) => `pointwise` } diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res index 834b244f..8224f4cb 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res @@ -191,7 +191,6 @@ let isFloat = (t: t) => let sampleNRendered = (n, dist) => { let integralCache = T.Integral.get(dist) let distWithUpdatedIntegralCache = T.updateIntegralCache(Some(integralCache), dist) - doN(n, () => sample(distWithUpdatedIntegralCache)) } @@ -203,3 +202,8 @@ let operate = (distToFloatOp: Operation.distToFloatOperation, s): float => | #Sample => sample(s) | #Mean => T.mean(s) } + +let toSparkline = (t: t) => + T.toContinuous(t) + ->E.O2.toResult("toContinous Error: Could not convert into continuous distribution") + ->E.R2.fmap(r => Continuous.getShape(r).ys->Sparklines.create()) \ No newline at end of file diff --git a/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res b/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res index 626b8099..f2c1e886 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res @@ -346,11 +346,11 @@ module T = { | _ => #NoSolution } - let toPointSetDist = (sampleCount, d: symbolicDist): PointSetTypes.pointSetDist => + let toPointSetDist = (~xSelection=#ByWeight, sampleCount, d: symbolicDist): PointSetTypes.pointSetDist => switch d { | #Float(v) => Discrete(Discrete.make(~integralSumCache=Some(1.0), {xs: [v], ys: [1.0]})) | _ => - let xs = interpolateXs(~xSelection=#ByWeight, d, sampleCount) + let xs = interpolateXs(~xSelection, d, sampleCount) let ys = xs |> E.A.fmap(x => pdf(x, d)) Continuous(Continuous.make(~integralSumCache=Some(1.0), {xs: xs, ys: ys})) } diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res index 84e0697f..89be2d46 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res @@ -45,6 +45,13 @@ module Helpers = { FromDist(GenericDist_Types.Operation.ToFloat(fnCall), dist)->runGenericOperation->Some } + let toStringFn = ( + fnCall: GenericDist_Types.Operation.toString, + dist: GenericDist_Types.genericDist, + ) => { + FromDist(GenericDist_Types.Operation.ToString(fnCall), dist)->runGenericOperation->Some + } + let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => { FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some } @@ -119,6 +126,9 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option< ->SymbolicConstructors.symbolicResultToOutput | ("sample", [EvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist) | ("mean", [EvDistribution(dist)]) => Helpers.toFloatFn(#Mean, dist) + | ("toString", [EvDistribution(dist)]) => Helpers.toStringFn(ToString, dist) + | ("toSparkline", [EvDistribution(dist)]) => Helpers.toStringFn(ToSparkline(20), dist) + | ("toSparkline", [EvDistribution(dist), EvNumber(n)]) => Helpers.toStringFn(ToSparkline(Belt.Float.toInt(n)), dist) | ("exp", [EvDistribution(a)]) => // https://mathjs.org/docs/reference/functions/exp.html Helpers.twoDiststoDistFn(Algebraic, "pow", GenericDist.fromFloat(Math.e), a)->Some diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index e36b6124..01dbde7d 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -101,6 +101,7 @@ module O2 = { let default = (a, b) => O.default(b, a) let toExn = (a, b) => O.toExn(b, a) let fmap = (a, b) => O.fmap(b, a) + let toResult = (a, b) => O.toResult(b, a) } /* Functions */ @@ -178,6 +179,13 @@ module R = { module R2 = { let fmap = (a,b) => R.fmap(b,a) + let bind = (a, b) => R.bind(b, a) + + //Converts result type to change error type only + let errMap = (a, map) => switch(a){ + | Ok(r) => Ok(r) + | Error(e) => map(e) + } } let safe_fn_of_string = (fn, s: string): option<'a> => @@ -289,8 +297,7 @@ module A = { )) |> Rationale.Result.return } - let rangeFloat = (~step=1, start, stop) => - Belt.Array.rangeBy(start, stop, ~step) |> fmap(Belt.Int.toFloat) + // This zips while taking the longest elements of each array. let zipMaxLength = (array1, array2) => { @@ -442,6 +449,12 @@ module A = { let mean = a => sum(a) /. (Array.length(a) |> float_of_int) let random = Js.Math.random_int + // Gives an array with all the differences between values + // diff([1,5,3,7]) = [4,-2,4] + let diff = (arr: array): array => + Belt.Array.zipBy(arr, Belt.Array.sliceToEnd(arr, 1), (left, right) => right -. left) + + exception RangeError(string) let range = (min: float, max: float, n: int): array => switch n { diff --git a/yarn.lock b/yarn.lock index 511b327f..ce5accca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8434,13 +8434,6 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -fast-check@^2.17.0: - version "2.24.0" - resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-2.24.0.tgz#39f85586862108a4de6394c5196ebcf8b76b6c8b" - integrity sha512-iNXbN90lbabaCUfnW5jyXYPwMJLFYl09eJDkXA9ZoidFlBK63gNRvcKxv+8D1OJ1kIYjwBef4bO/K3qesUeWLQ== - dependencies: - pure-rand "^5.0.1" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3, fast-deep-equal@~3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -13573,11 +13566,6 @@ pure-color@^1.2.0: resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= -pure-rand@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-5.0.1.tgz#97a287b4b4960b2a3448c0932bf28f2405cac51d" - integrity sha512-ksWccjmXOHU2gJBnH0cK1lSYdvSZ0zLoCMSz/nTGh6hDvCSgcRxDyIcOBD6KNxFz3xhMPm/T267Tbe2JRymKEQ== - q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -14766,13 +14754,6 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -rescript-fast-check@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/rescript-fast-check/-/rescript-fast-check-1.1.1.tgz#ef153cb01254b2f01a738faf85b73327d423d5e1" - integrity sha512-wxeW0TsL/prkRvEYGbhEiLaLKmYJaECyDcKEWh65hDqP2i76lARzVW3QmYujSYK4OnjAC70dln3X6UC/2m2Huw== - dependencies: - fast-check "^2.17.0" - rescript@^9.1.4: version "9.1.4" resolved "https://registry.npmjs.org/rescript/-/rescript-9.1.4.tgz"