diff --git a/packages/squiggle-lang/README.md b/packages/squiggle-lang/README.md index a7d0d5d0..87bc76fe 100644 --- a/packages/squiggle-lang/README.md +++ b/packages/squiggle-lang/README.md @@ -13,6 +13,9 @@ Other: yarn start # listens to files and recompiles at every mutation yarn test yarn test:watch # keeps an active session and runs all tests at every mutation + +# where o := open in osx and o := xdg-open in linux, +yarn coverage; o _coverage/index.html # produces coverage report and opens it in browser ``` # TODO: clean up this README.md diff --git a/packages/squiggle-lang/__tests__/Distributions/DistributionOperation__Test.res b/packages/squiggle-lang/__tests__/Distributions/DistributionOperation__Test.res index dc456865..d0135cf1 100644 --- a/packages/squiggle-lang/__tests__/Distributions/DistributionOperation__Test.res +++ b/packages/squiggle-lang/__tests__/Distributions/DistributionOperation__Test.res @@ -1,14 +1,18 @@ open Jest open Expect +open FastCheck +// open Arbitrary +open Property.Sync let env: DistributionOperation.env = { sampleCount: 100, xyPointLength: 100, } -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 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) @@ -19,18 +23,99 @@ 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("for a normal distribution", () => { - let result = DistributionOperation.run(~env, FromDist(ToFloat(#Mean), normalDist5)) - expect(result)->toEqual(Float(5.0)) + 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) }) }) diff --git a/packages/squiggle-lang/bsconfig.json b/packages/squiggle-lang/bsconfig.json index 4f38e124..9bd15e11 100644 --- a/packages/squiggle-lang/bsconfig.json +++ b/packages/squiggle-lang/bsconfig.json @@ -30,6 +30,9 @@ "rationale", "bisect_ppx" ], + "bs-dev-dependencies": [ + "rescript-fast-check" + ], "gentypeconfig": { "language": "typescript", "module": "commonjs", diff --git a/packages/squiggle-lang/package.json b/packages/squiggle-lang/package.json index 03983cd2..92554dd9 100644 --- a/packages/squiggle-lang/package.json +++ b/packages/squiggle-lang/package.json @@ -35,6 +35,7 @@ "docsify": "^4.12.2", "gentype": "^4.3.0", "jest": "^27.5.1", + "rescript-fast-check": "^1.1.1", "moduleserve": "0.9.1", "ts-jest": "^27.1.4", "ts-loader": "^9.2.8", diff --git a/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res b/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res index cd4132b3..626b8099 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res @@ -55,7 +55,7 @@ module Exponential = { rate: rate, }), ) - : Error("Exponential distributions mean must be larger than 0") + : Error("Exponential distributions rate must be larger than 0.") let pdf = (x, t: t) => Jstat.Exponential.pdf(x, t.rate) let cdf = (x, t: t) => Jstat.Exponential.cdf(x, t.rate) let inv = (p, t: t) => Jstat.Exponential.inv(p, t.rate) @@ -71,7 +71,7 @@ module Cauchy = { let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale) let inv = (p, t: t) => Jstat.Cauchy.inv(p, t.local, t.scale) let sample = (t: t) => Jstat.Cauchy.sample(t.local, t.scale) - let mean = (_: t) => Error("Cauchy distributions have no mean value.") + let mean = (_: t) => Error("Cauchy distributions may have no mean value.") let toString = ({local, scale}: t) => j`Cauchy($local, $scale)` } @@ -80,8 +80,8 @@ module Triangular = { let make = (low, medium, high): result => low < medium && medium < high ? Ok(#Triangular({low: low, medium: medium, high: high})) - : Error("Triangular values must be increasing order") - let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium) + : Error("Triangular values must be increasing order.") + let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium) // not obvious in jstat docs that high comes before medium? let cdf = (x, t: t) => Jstat.Triangular.cdf(x, t.low, t.high, t.medium) let inv = (p, t: t) => Jstat.Triangular.inv(p, t.low, t.high, t.medium) let sample = (t: t) => Jstat.Triangular.sample(t.low, t.high, t.medium) diff --git a/yarn.lock b/yarn.lock index 6d0a7619..8a0e9676 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8392,6 +8392,13 @@ 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" @@ -13524,6 +13531,11 @@ 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" @@ -14712,6 +14724,13 @@ 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"