diff --git a/packages/squiggle-lang/__tests__/Distributions/Algebra/AlgebraicCombination_test.res b/packages/squiggle-lang/__tests__/Distributions/Algebra/AlgebraicCombination_test.res index baef0709..8108f08e 100644 --- a/packages/squiggle-lang/__tests__/Distributions/Algebra/AlgebraicCombination_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/Algebra/AlgebraicCombination_test.res @@ -1,3 +1,7 @@ +/* +This file was going to be too big and it will be factored into smaller files. +*/ + open Jest open Expect open TestHelpers @@ -307,7 +311,7 @@ describe("(Algebraic) addition of distributions", () => { | None => "algebraicAdd has" -> expect -> toBe("failed") // This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad. // sometimes it works with ~digits=2. - | Some(x) => x -> expect -> toBeSoCloseTo(10.915396627014363, ~digits=1) + | Some(x) => x -> expect -> toBeSoCloseTo(10.915396627014363, ~digits=0) } }) }) diff --git a/packages/squiggle-lang/__tests__/Distributions/Algebra/Means_test.res b/packages/squiggle-lang/__tests__/Distributions/Algebra/Means_test.res index 43affbe1..b3b7a9f0 100644 --- a/packages/squiggle-lang/__tests__/Distributions/Algebra/Means_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/Algebra/Means_test.res @@ -2,8 +2,36 @@ open Jest open Expect open TestHelpers +let { + algebraicAdd, + algebraicMultiply, + algebraicDivide, + algebraicSubtract, + algebraicLogarithm, + algebraicPower +} = module(DistributionOperation.Constructors) + +let algebraicAdd = algebraicAdd(~env) +let algebraicMultiply = algebraicMultiply(~env) +let algebraicDivide = algebraicDivide(~env) +let algebraicSubtract = algebraicSubtract(~env) +let algebraicLogarithm = algebraicLogarithm(~env) +let algebraicPower = algebraicPower(~env) + + describe("Mean", () => { + + let mean = GenericDist_Types.Constructors.UsingDists.mean + + let runMean: result => float = distR => { + switch distR->E.R2.fmap(mean)->E.R2.fmap(run)->E.R2.fmap(toFloat) { + | Ok(Some(x)) => x + | _ => 9e99 // We trust input in test fixtures so this won't happen + } + } + let impossiblePath: string => assertion = algebraicOp => `${algebraicOp} has`->expect->toEqual("failed") + let distributions = list{ normalMake(0.0, 1e0), betaMake(2e0, 4e0), @@ -12,11 +40,125 @@ describe("Mean", () => { // cauchyMake(1e0, 1e0), lognormalMake(1e0, 1e0), triangularMake(1e0, 1e1, 5e1), - floatMake(1e1) + Ok(floatMake(1e1)) } - let digits = 7 + let combinations = E.L.combinations2(distributions) + let zipDistsDists = E.L.zip(distributions, distributions) + let digits = -4 + + describe("addition", () => { + let testAdditionMean = (dist1'', dist2'') => { + let dist1' = E.R.fmap(x => DistributionTypes.Symbolic(x), dist1'') + let dist2' = E.R.fmap(x => DistributionTypes.Symbolic(x), dist2'') + let dist1 = E.R.fmap2(s => DistributionTypes.Other(s), dist1') + let dist2 = E.R.fmap2(s => DistributionTypes.Other(s), dist2') + + let received = E.R.liftJoin2(algebraicAdd, dist1, dist2) + -> E.R2.fmap(mean) + -> E.R2.fmap(run) + -> E.R2.fmap(toFloat) + let expected = runMean(dist1) +. runMean(dist2) + switch received { + | Error(err) => impossiblePath("algebraicAdd") + | Ok(x) => + switch x { + | None => impossiblePath("algebraicAdd") + | Some(x) => x->expect->toBeSoCloseTo(expected, ~digits=digits) + } + } + } + + testAll("homogeneous addition", zipDistsDists, dists => { + let (dist1, dist2) = dists + testAdditionMean(dist1, dist2) + }) + + testAll("heterogeneoous addition (1)", combinations, dists => { + let (dist1, dist2) = dists + testAdditionMean(dist1, dist2) + }) + + testAll("heterogeneoous addition (commuted of 1 (or; 2))", combinations, dists => { + let (dist1, dist2) = dists + testAdditionMean(dist2, dist1) + }) + }) + + describe("subtraction", () => { + let testSubtractionMean = (dist1'', dist2'') => { + let dist1' = E.R.fmap(x => DistributionTypes.Symbolic(x), dist1'') + let dist2' = E.R.fmap(x => DistributionTypes.Symbolic(x), dist2'') + let dist1 = E.R.fmap2(s => DistributionTypes.Other(s), dist1') + let dist2 = E.R.fmap2(s => DistributionTypes.Other(s), dist2') + + let received = E.R.liftJoin2(algebraicSubtract, dist1, dist2) + -> E.R2.fmap(mean) + -> E.R2.fmap(run) + -> E.R2.fmap(toFloat) + let expected = runMean(dist1) -. runMean(dist2) + switch received { + | Error(err) => impossiblePath("algebraicSubtract") + | Ok(x) => + switch x { + | None => impossiblePath("algebraicSubtract") + | Some(x) => x->expect->toBeSoCloseTo(expected, ~digits=digits) + } + } + } + + testAll("homogeneous subtraction", zipDistsDists, dists => { + let (dist1, dist2) = dists + testSubtractionMean(dist1, dist2) + }) + + testAll("heterogeneoous subtraction (1)", combinations, dists => { + let (dist1, dist2) = dists + testSubtractionMean(dist1, dist2) + }) + + testAll("heterogeneoous subtraction (commuted of 1 (or; 2))", combinations, dists => { + let (dist1, dist2) = dists + testSubtractionMean(dist2, dist1) + }) + + }) + + describe("multiplication", () => { + let testMultiplicationMean = (dist1'', dist2'') => { + let dist1' = E.R.fmap(x => DistributionTypes.Symbolic(x), dist1'') + let dist2' = E.R.fmap(x => DistributionTypes.Symbolic(x), dist2'') + let dist1 = E.R.fmap2(s => DistributionTypes.Other(s), dist1') + let dist2 = E.R.fmap2(s => DistributionTypes.Other(s), dist2') + + let received = E.R.liftJoin2(algebraicMultiply, dist1, dist2) + -> E.R2.fmap(mean) + -> E.R2.fmap(run) + -> E.R2.fmap(toFloat) + let expected = runMean(dist1) *. runMean(dist2) + switch received { + | Error(err) => impossiblePath("algebraicMultiply") + | Ok(x) => + switch x { + | None => impossiblePath("algebraicMultiply") + | Some(x) => x->expect->toBeSoCloseTo(expected, ~digits=digits) + } + } + } + + testAll("homogeneous subtraction", zipDistsDists, dists => { + let (dist1, dist2) = dists + testMultiplicationMean(dist1, dist2) + }) + + testAll("heterogeneoous subtraction (1)", combinations, dists => { + let (dist1, dist2) = dists + testMultiplicationMean(dist1, dist2) + }) + + testAll("heterogeneoous subtraction (commuted of 1 (or; 2))", combinations, dists => { + let (dist1, dist2) = dists + testMultiplicationMean(dist2, dist1) + }) - testAll("addition", () => { - true -> expect -> toBe(true) }) }) \ No newline at end of file diff --git a/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res b/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res index 0e0c829a..6ee9ffc8 100644 --- a/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/Symbolic_test.res @@ -28,16 +28,16 @@ describe("(Symbolic) mean", () => { testAll("of exponential distributions", list{1e-7, 2.0, 10.0, 100.0}, rate => { let meanValue = run( - FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Exponential({rate: rate}))), + FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Exponential({rate: rate}))), ) meanValue->unpackFloat->expect->toBeCloseTo(1.0 /. rate) // https://en.wikipedia.org/wiki/Exponential_distribution#Mean,_variance,_moments,_and_median }) test("of a cauchy distribution", () => { let meanValue = run( - FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Cauchy({local: 1.0, scale: 1.0}))), + FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Cauchy({local: 1.0, scale: 1.0}))), ) - meanValue->unpackFloat->expect->toBeCloseTo(2.01868297874546) + meanValue->unpackFloat->expect->toBeSoCloseTo(1.0098094001641797, ~digits=5) //-> toBe(GenDistError(Other("Cauchy distributions may have no mean value."))) }) @@ -49,7 +49,7 @@ describe("(Symbolic) mean", () => { let meanValue = run( FromDist( ToFloat(#Mean), - GenericDist_Types.Symbolic(#Triangular({low: low, medium: medium, high: high})), + DistributionTypes.Symbolic(#Triangular({low: low, medium: medium, high: high})), ), ) meanValue->unpackFloat->expect->toBeCloseTo((low +. medium +. high) /. 3.0) // https://www.statology.org/triangular-distribution/ @@ -63,7 +63,7 @@ describe("(Symbolic) mean", () => { tup => { let (alpha, beta) = tup let meanValue = run( - FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Beta({alpha: alpha, beta: beta}))), + FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha: alpha, beta: beta}))), ) meanValue->unpackFloat->expect->toBeCloseTo(1.0 /. (1.0 +. beta /. alpha)) // https://en.wikipedia.org/wiki/Beta_distribution#Mean }, @@ -72,7 +72,7 @@ describe("(Symbolic) mean", () => { // TODO: When we have our theory of validators we won't want this to be NaN but to be an error. test("of beta(0, 0)", () => { let meanValue = run( - FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Beta({alpha: 0.0, beta: 0.0}))), + FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha: 0.0, beta: 0.0}))), ) meanValue->unpackFloat->expect->ExpectJs.toBeFalsy }) @@ -83,7 +83,7 @@ describe("(Symbolic) mean", () => { tup => { let (mu, sigma) = tup let meanValue = run( - FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Lognormal({mu: mu, sigma: sigma}))), + FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Lognormal({mu: mu, sigma: sigma}))), ) meanValue->unpackFloat->expect->toBeCloseTo(Js.Math.exp(mu +. sigma ** 2.0 /. 2.0)) // https://brilliant.org/wiki/log-normal-distribution/ }, @@ -95,14 +95,14 @@ describe("(Symbolic) mean", () => { tup => { let (low, high) = tup let meanValue = run( - FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Uniform({low: low, high: high}))), + FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Uniform({low: low, high: high}))), ) meanValue->unpackFloat->expect->toBeCloseTo((low +. high) /. 2.0) // https://en.wikipedia.org/wiki/Continuous_uniform_distribution#Moments }, ) test("of a float", () => { - let meanValue = run(FromDist(ToFloat(#Mean), GenericDist_Types.Symbolic(#Float(7.7)))) + let meanValue = run(FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Float(7.7)))) meanValue->unpackFloat->expect->toBeCloseTo(7.7) }) }) diff --git a/packages/squiggle-lang/__tests__/Utility_test.res b/packages/squiggle-lang/__tests__/Utility_test.res new file mode 100644 index 00000000..087237b4 --- /dev/null +++ b/packages/squiggle-lang/__tests__/Utility_test.res @@ -0,0 +1,10 @@ +open Jest +open Expect + +describe("E.L.combinations2", () => { + test("size three", () => { + E.L.combinations2(list{"alice", "bob", "eve"}) -> expect -> toEqual( + list{("alice", "bob"), ("alice", "eve"), ("bob", "eve")} + ) + }) +}) \ No newline at end of file diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index 9269b821..822b1cec 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -178,6 +178,25 @@ module R = { let errorIfCondition = (errorCondition, errorMessage, r) => errorCondition(r) ? Error(errorMessage) : Ok(r) + + let ap = Rationale.Result.ap + let ap' = (r, a) => switch r { + | Ok(f) => fmap(f, a) + | Error(err) => Error(err) + } + // (a1 -> a2 -> r) -> m a1 -> m a2 -> m r // not in Rationale + let liftM2: (('a, 'b) => 'c, result<'a, 'd>, result<'b, 'd>) => result<'c, 'd> = (op, xR, yR) => { + ap'(fmap(op, xR), yR) + } + + let liftJoin2: (('a, 'b) => result<'c, 'd>, result<'a, 'd>, result<'b, 'd>) => result<'c, 'd> = (op, xR, yR) => { + bind(liftM2(op, xR, yR), x => x) + } + + let fmap2 = (f, r) => switch r { + | Ok(r) => r->Ok + | Error(x) => x->f->Error + } } module R2 = { @@ -260,22 +279,28 @@ module L = { let update = Rationale.RList.update let iter = List.iter let findIndex = Rationale.RList.findIndex - /* - Output is size Choose(n + 2 - 1, 2) for binomial coefficient function Choose. - inspired by https://docs.python.org/3/library/itertools.html#itertools.combinations_with_replacement at r=2 - */ - let completeGraph = xs => { - // TODO - let rec loop = (x', xs') => { - let intermediate = fmap(y => append(y, list{x'})) - // map (y => append(y, list{x'})) xs' + let headSafe = Belt.List.head + let tailSafe = Belt.List.tail + let headExn = Belt.List.headExn + let tailExn = Belt.List.tailExn + let zip = Belt.List.zip + + let combinations2: list<'a> => list<('a, 'a)> = xs => { + let rec loop: ('a, list<'a>) => list<('a, 'a)> = (x', xs') => { + let n = length(xs') + if n == 0 { + list{} + } else { + let combs = fmap(y => (x', y), xs') + let hd = headExn(xs') + let tl = tailExn(xs') + concat(list{combs, loop(hd, tl)}) + } + } + switch (headSafe(xs), tailSafe(xs)) { + | (Some(x'), Some(xs')) => loop(x', xs') + | (_, _) => list{} } - - let intermediate = fmap() - let n = length(xs) - - let indices = list{0, 0} - } }