diff --git a/packages/squiggle-lang/__tests__/Distributions/DistributionOperation__test.res b/packages/squiggle-lang/__tests__/Distributions/DistributionOperation__test.res index d0135cf1..c4410f68 100644 --- a/packages/squiggle-lang/__tests__/Distributions/DistributionOperation__test.res +++ b/packages/squiggle-lang/__tests__/Distributions/DistributionOperation__test.res @@ -1,8 +1,5 @@ open Jest open Expect -open FastCheck -// open Arbitrary -open Property.Sync let env: DistributionOperation.env = { sampleCount: 100, @@ -11,8 +8,6 @@ let env: DistributionOperation.env = { let mkNormal = (mean, stdev) => GenericDist_Types.Symbolic(#Normal({mean: mean, stdev: stdev})) let normalDist5: GenericDist_Types.genericDist = mkNormal(5.0, 2.0) -let normalDist10: GenericDist_Types.genericDist = mkNormal(10.0, 2.0) -let normalDist20: GenericDist_Types.genericDist = mkNormal(20.0, 2.0) let uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0})) let {toFloat, toDist, toString, toError} = module(DistributionOperation.Output) @@ -23,112 +18,6 @@ let outputMap = fmap(~env) let toExt: option<'a> => 'a = E.O.toExt( "Should be impossible to reach (This error is in test file)", ) -let unpackFloat = x => x -> toFloat -> toExt - -describe("normalize", () => { - test("has no impact on normal dist", () => { - let result = run(FromDist(ToDist(Normalize), normalDist5)) - expect(result)->toEqual(Dist(normalDist5)) - }) - - // Test is vapid while I figure out how to get jest to work with fast-check - // monitor situation here maybe https://github.com/TheSpyder/rescript-fast-check/issues/8 ? - test("all normals are already normalized", () => { - expect(assert_( - property2( - Arbitrary.double(()), - Arbitrary.double(()), - (mean, stdev) => { - // open! Expect.Operators - open GenericDist_Types.Operation - run(FromDist(ToDist(Normalize), mkNormal(mean, stdev))) == DistributionOperation.Dist(mkNormal(mean, stdev)) - } - ) - )) -> toEqual(()) - }) -}) - -describe("mean", () => { - test("of a normal distribution", () => { // should be property - run(FromDist(ToFloat(#Mean), normalDist5)) -> unpackFloat -> expect -> toBeCloseTo(5.0) - }) - - test("of an exponential distribution at a small rate", () => { // should be property - let rate = 1e-7 - let theMean = run(FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Exponential({rate: rate})))) - theMean -> unpackFloat -> expect -> toBeCloseTo(1.0 /. rate) // https://en.wikipedia.org/wiki/Exponential_distribution#Mean,_variance,_moments,_and_median - }) - - test("of an exponential distribution at a larger rate", () => { - let rate = 10.0 - let theMean = run(FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Exponential({rate: rate})))) - theMean -> unpackFloat -> expect -> toBeCloseTo(1.0 /. rate) // https://en.wikipedia.org/wiki/Exponential_distribution#Mean,_variance,_moments,_and_median - }) - -// test("of a cauchy distribution", () => { -// let result = run(FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Cauchy({local: 1.0, scale: 1.0})))) -// expect(result) -> toEqual(Error("Cauchy distributions may have no mean value.")) -// }) - - test("of a triangular distribution", () => { // should be property - let theMean = run(FromDist( - ToFloat(#Mean), - GenericDist_Types.Symbolic(#Triangular({low: - 5.0, medium: 1e-3, high: 10.0})) - )) - theMean -> unpackFloat -> expect -> toBeCloseTo((-5.0 +. 1e-3 +. 10.0) /. 3.0) // https://www.statology.org/triangular-distribution/ - }) - - test("of a beta distribution with alpha much smaller than beta", () => { // should be property - let theMean = run(FromDist( - ToFloat(#Mean), - GenericDist_Types.Symbolic(#Beta({alpha: 2e-4, beta: 64.0})) - )) - theMean -> unpackFloat -> expect -> toBeCloseTo(1.0 /. (1.0 +. (64.0 /. 2e-4))) // https://en.wikipedia.org/wiki/Beta_distribution#Mean - }) - - test("of a beta distribution with alpha much larger than beta", () => { // should be property - let theMean = run(FromDist( - ToFloat(#Mean), - GenericDist_Types.Symbolic(#Beta({alpha: 128.0, beta: 1.0})) - )) - theMean -> unpackFloat -> expect -> toBeCloseTo(1.0 /. (1.0 +. (1.0 /. 128.0))) // https://en.wikipedia.org/wiki/Beta_distribution#Mean - }) - - test("of a lognormal", () => { // should be property - let theMean = run(FromDist( - ToFloat(#Mean), - GenericDist_Types.Symbolic(#Lognormal({mu: 2.0, sigma: 4.0})) - )) - theMean -> unpackFloat -> expect -> toBeCloseTo(Js.Math.exp(2.0 +. 4.0 ** 2.0 /. 2.0 )) // https://brilliant.org/wiki/log-normal-distribution/ - }) - - test("of a uniform", () => { - let theMean = run(FromDist( - ToFloat(#Mean), - GenericDist_Types.Symbolic(#Uniform({low: 1e-5, high: 12.345})) - )) - theMean -> unpackFloat -> expect -> toBeCloseTo((1e-5 +. 12.345) /. 2.0) // https://en.wikipedia.org/wiki/Continuous_uniform_distribution#Moments - }) - - test("of a float", () => { - let theMean = run(FromDist( - ToFloat(#Mean), - GenericDist_Types.Symbolic(#Float(7.7)) - )) - theMean -> unpackFloat -> expect -> toBeCloseTo(7.7) - }) -}) - -describe("mixture", () => { - test("on two normal distributions", () => { - let result = - run(Mixture([(normalDist10, 0.5), (normalDist20, 0.5)])) - ->outputMap(FromDist(ToFloat(#Mean))) - ->toFloat - ->toExt - expect(result)->toBeCloseTo(15.28) - }) -}) describe("toPointSet", () => { test("on symbolic normal distribution", () => { diff --git a/packages/squiggle-lang/__tests__/Distributions/Mixture_test.res b/packages/squiggle-lang/__tests__/Distributions/Mixture_test.res new file mode 100644 index 00000000..c0cef5a6 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Distributions/Mixture_test.res @@ -0,0 +1,57 @@ +open Jest +open Expect + +let env: DistributionOperation.env = { + sampleCount: 1000, + xyPointLength: 100, +} + +let {toFloat, toDist, toString, toError} = module(DistributionOperation.Output) +let {run} = module(DistributionOperation) +let {fmap} = module(DistributionOperation.Output) +let run = run(~env) +let outputMap = fmap(~env) +let toExt: option<'a> => 'a = E.O.toExt( + "Should be impossible to reach (This error is in test file)", +) +let unpackFloat = x => x -> toFloat -> toExt + +let mkNormal = (mean, stdev) => GenericDist_Types.Symbolic(#Normal({mean: mean, stdev: stdev})) +let mkBeta = (alpha, beta) => GenericDist_Types.Symbolic(#Beta({alpha: alpha, beta: beta})) +let mkExponential = rate => GenericDist_Types.Symbolic(#Exponential({rate: rate})) + +describe("mixture", () => { + testAll("fair mean of two normal distributions", list{(0.0, 1e2), (-1e1, -1e-4), (-1e1, 1e2), (-1e1, 1e1)}, tup => { // should be property + let (mean1, mean2) = tup + let theMean = { + run(Mixture([(mkNormal(mean1, 9e-1), 0.5), (mkNormal(mean2, 9e-1), 0.5)])) + -> outputMap(FromDist(ToFloat(#Mean))) + } + theMean -> unpackFloat -> expect -> toBeSoCloseTo((mean1 +. mean2) /. 2.0, ~digits=-1) // the .56 is arbitrary? should be 15.0 with a looser tolerance? + }) + testAll( + "weighted mean of a beta and an exponential", + // This would not survive property testing, it was easy for me to find cases that NaN'd out. + list{((128.0, 1.0), 2.0), ((2e-1, 64.0), 16.0), ((1e0, 1e0), 64.0)}, + tup => { + let (betaParams, rate) = tup + let (alpha, beta) = betaParams + let theMean = { + run(Mixture( + [ + (mkBeta(alpha, beta), 0.25), + (mkExponential(rate), 0.75) + ] + )) -> outputMap(FromDist(ToFloat(#Mean))) + } + theMean + -> unpackFloat + -> expect + -> toBeSoCloseTo( + 0.25 *. 1.0 /. (1.0 +. beta /. alpha) +. 0.75 *. 1.0 /. rate, + ~digits=-1 + ) + } + ) +}) + diff --git a/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res b/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res new file mode 100644 index 00000000..8e48b3df --- /dev/null +++ b/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res @@ -0,0 +1,145 @@ +open Jest +open Expect + +let pdfImage = (thePdf, inps) => Js.Array.map(thePdf, inps) + +let env: DistributionOperation.env = { + sampleCount: 100, + xyPointLength: 100, +} + +let mkNormal = (mean, stdev) => GenericDist_Types.Symbolic(#Normal({mean: mean, stdev: stdev})) +let {toFloat, toDist, toString, toError, fmap} = module(DistributionOperation.Output) +let {run} = module(DistributionOperation) +let run = run(~env) +let outputMap = fmap(~env) +let toExtFloat: option => float = E.O.toExt( + "Should be impossible to reach (This error is in test file)", +) +let toExtDist: option => GenericDist_Types.genericDist = E.O.toExt( + "Should be impossible to reach (This error is in a test file)", +) +let unpackFloat = x => x -> toFloat -> toExtFloat +let unpackDist = y => y -> toDist -> toExtDist + +describe("normalize", () => { + testAll("has no impact on normal distributions", list{-1e8, -16.0, -1e-2, 0.0, 1e-4, 32.0, 1e16}, mean => { + let theNormal = mkNormal(mean, 2.0) + let theNormalized = run(FromDist(ToDist(Normalize), theNormal)) + theNormalized + -> unpackDist + -> expect + -> toEqual(theNormal) + }) +}) + +describe("mean", () => { + testAll("of normal distributions", list{-1e8, -16.0, -1e-2, 0.0, 1e-4, 32.0, 1e16}, mean => { + run(FromDist(ToFloat(#Mean), mkNormal(mean, 4.0))) + -> unpackFloat + -> expect + -> toBeCloseTo(mean) + }) + + testAll("of exponential distributions", list{1e-7, 2.0, 10.0, 100.0}, rate => { + let theMean = run(FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Exponential({rate: rate})))) + theMean -> unpackFloat -> expect -> toBeCloseTo(1.0 /. rate) // https://en.wikipedia.org/wiki/Exponential_distribution#Mean,_variance,_moments,_and_median + }) + +// test("of a cauchy distribution", () => { +// let result = run(FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Cauchy({local: 1.0, scale: 1.0})))) +// expect(result) -> toEqual(Error("Cauchy distributions may have no mean value.")) +// }) + + test("of a triangular distribution", () => { // should be property + let theMean = run(FromDist( + ToFloat(#Mean), + GenericDist_Types.Symbolic(#Triangular({low: - 5.0, medium: 1e-3, high: 10.0})) + )) + theMean + -> unpackFloat + -> expect + -> toBeCloseTo((-5.0 +. 1e-3 +. 10.0) /. 3.0) // https://www.statology.org/triangular-distribution/ + }) + + test("of a beta distribution with alpha much smaller than beta", () => { // should be property + let theMean = run(FromDist( + ToFloat(#Mean), + GenericDist_Types.Symbolic(#Beta({alpha: 2e-4, beta: 64.0})) + )) + theMean + -> unpackFloat + -> expect + -> toBeCloseTo(1.0 /. (1.0 +. (64.0 /. 2e-4))) // https://en.wikipedia.org/wiki/Beta_distribution#Mean + }) + + test("of a beta distribution with alpha much larger than beta", () => { // should be property + let theMean = run(FromDist( + ToFloat(#Mean), + GenericDist_Types.Symbolic(#Beta({alpha: 128.0, beta: 1.0})) + )) + theMean + -> unpackFloat + -> expect + -> toBeCloseTo(1.0 /. (1.0 +. (1.0 /. 128.0))) // https://en.wikipedia.org/wiki/Beta_distribution#Mean + }) + + test("of a lognormal", () => { // should be property + let theMean = run(FromDist( + ToFloat(#Mean), + GenericDist_Types.Symbolic(#Lognormal({mu: 2.0, sigma: 4.0})) + )) + theMean + -> unpackFloat + -> expect + -> toBeCloseTo(Js.Math.exp(2.0 +. 4.0 ** 2.0 /. 2.0 )) // https://brilliant.org/wiki/log-normal-distribution/ + }) + + test("of a uniform", () => { + let theMean = run(FromDist( + ToFloat(#Mean), + GenericDist_Types.Symbolic(#Uniform({low: 1e-5, high: 12.345})) + )) + theMean + -> unpackFloat + -> expect + -> toBeCloseTo((1e-5 +. 12.345) /. 2.0) // https://en.wikipedia.org/wiki/Continuous_uniform_distribution#Moments + }) + + test("of a float", () => { + let theMean = run(FromDist( + ToFloat(#Mean), + GenericDist_Types.Symbolic(#Float(7.7)) + )) + theMean -> unpackFloat -> expect -> toBeCloseTo(7.7) + }) +}) + +describe("Normal distribution with sparklines", () => { + + let parameterWiseAdditionHelper = (n1: SymbolicDistTypes.normal, n2: SymbolicDistTypes.normal) => { + let normalDistAtSumMeanConstr = SymbolicDist.Normal.add(n1, n2) + let normalDistAtSumMean: SymbolicDistTypes.normal = switch normalDistAtSumMeanConstr { + | #Normal(params) => params + } + x => SymbolicDist.Normal.pdf(x, normalDistAtSumMean) + } + + 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 pdfNormalDistAtMean5 = x => SymbolicDist.Normal.pdf(x, normalDistAtMean5) + let sparklineMean5 = pdfImage(pdfNormalDistAtMean5, range20Float) + test("mean=5", () => { + Sparklines.create(sparklineMean5, ()) + -> expect + -> toEqual(`▁▂▃▅███▅▃▂▁▁▁▁▁▁▁▁▁▁▁`) + }) + let sparklineMean15 = normalDistAtMean5 -> parameterWiseAdditionHelper(normalDistAtMean10) -> pdfImage(range20Float) + test("parameter-wise addition of two normal distributions", () => { + Sparklines.create(sparklineMean15, ()) + -> expect + -> toEqual(`▁▁▁▁▁▁▁▁▁▁▂▃▅▇███▇▅▃▂`) + }) +}) diff --git a/packages/squiggle-lang/__tests__/Symbolic_test.res b/packages/squiggle-lang/__tests__/Symbolic_test.res deleted file mode 100644 index 8ec3be22..00000000 --- a/packages/squiggle-lang/__tests__/Symbolic_test.res +++ /dev/null @@ -1,33 +0,0 @@ -open Jest -open Expect -open Js.Array -open SymbolicDist - -let makeTest = (~only=false, str, item1, item2) => - only - ? Only.test(str, () => expect(item1) -> toEqual(item2)) - : test(str, () => expect(item1) -> toEqual(item2)) - -let pdfImage = (thePdf, inps) => map(thePdf, inps) - -let parameterWiseAdditionHelper = (n1: SymbolicDistTypes.normal, n2: SymbolicDistTypes.normal) => { - let normalDistAtSumMeanConstr = Normal.add(n1, n2) - let normalDistAtSumMean: SymbolicDistTypes.normal = switch normalDistAtSumMeanConstr { - | #Normal(params) => params - } - x => Normal.pdf(x, normalDistAtSumMean) -} - -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 pdfNormalDistAtMean5 = x => Normal.pdf(x, normalDistAtMean5) - let sparklineMean5 = pdfImage(pdfNormalDistAtMean5, range20Float) - makeTest("mean=5", Sparklines.create(sparklineMean5, ()), `▁▂▃▅███▅▃▂▁▁▁▁▁▁▁▁▁▁▁`) - - let sparklineMean15 = normalDistAtMean5 -> parameterWiseAdditionHelper(normalDistAtMean10) -> pdfImage(range20Float) - makeTest("parameter-wise addition of two normal distributions", Sparklines.create(sparklineMean15, ()), `▁▁▁▁▁▁▁▁▁▁▂▃▅▇███▇▅▃▂`) -})