From 24fe66c9d3d9b18c648cd95c711c597b5999fc41 Mon Sep 17 00:00:00 2001 From: Quinn Dougherty Date: Wed, 13 Apr 2022 19:17:49 -0400 Subject: [PATCH] 6/6 tasks done --- .../Distributions/Invariants/Means_test.res | 162 +++++++++--------- .../squiggle-lang/__tests__/TestHelpers.res | 10 ++ 2 files changed, 89 insertions(+), 83 deletions(-) diff --git a/packages/squiggle-lang/__tests__/Distributions/Invariants/Means_test.res b/packages/squiggle-lang/__tests__/Distributions/Invariants/Means_test.res index 51f9d1c1..01f52b7c 100644 --- a/packages/squiggle-lang/__tests__/Distributions/Invariants/Means_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/Invariants/Means_test.res @@ -1,15 +1,65 @@ /* This is the most basic file in our invariants family of tests. -See document in https://github.com/quantified-uncertainty/squiggle/pull/238 for details +Validate that the addition of means equals the mean of the addition, similar for subtraction and multiplication. -Note: digits parameter should be higher than -4. +Details in https://deploy-preview-251--squiggle-documentation.netlify.app/docs/internal/invariants/ + +Note: epsilon of 1e3 means the invariants are, in general, not being satisfied. */ open Jest open Expect open TestHelpers +module Internals = { + let epsilon = 1e3 + + let mean = GenericDist_Types.Constructors.UsingDists.mean + + let expectImpossiblePath: string => assertion = algebraicOp => + `${algebraicOp} has`->expect->toEqual("failed") + + let distributions = list{ + normalMake(0.0, 1e0), + betaMake(2e0, 4e0), + exponentialMake(1.234e0), + uniformMake(7e0, 1e1), + // cauchyMake(1e0, 1e0), + lognormalMake(1e0, 1e0), + triangularMake(1e0, 1e1, 5e1), + Ok(floatMake(1e1)), + } + let pairsOfDifferentDistributions = E.L.combinations2(distributions) + + let runMean: DistributionTypes.genericDist => float = dist => { + dist->mean->run->toFloat->E.O2.toExn("Shouldn't see this because we trust testcase input") + } + + let testOperationMean = ( + distOp: (DistributionTypes.genericDist, DistributionTypes.genericDist) => result, + description: string, + floatOp: (float, float) => float, + dist1': SymbolicDistTypes.symbolicDist, + dist2': SymbolicDistTypes.symbolicDist, + ~epsilon: float + ) => { + let dist1 = dist1'->DistributionTypes.Symbolic // ->DistributionTypes.Other + let dist2 = dist2'->DistributionTypes.Symbolic // ->DistributionTypes.Other + let received = + distOp(dist1, dist2) + ->E.R2.fmap(mean) + ->E.R2.fmap(run) + ->E.R2.fmap(toFloat) + ->E.R.toExn + let expected = floatOp(runMean(dist1), runMean(dist2)) + switch received { + | None => expectImpossiblePath(description) + | Some(x) => expectErrorToBeBounded(x, expected, ~epsilon=epsilon) + } + } +} + let { algebraicAdd, algebraicMultiply, @@ -26,115 +76,61 @@ let algebraicSubtract = algebraicSubtract(~env) let algebraicLogarithm = algebraicLogarithm(~env) let algebraicPower = algebraicPower(~env) -describe("Mean", () => { - let digits = -4 +let {testOperationMean, distributions, pairsOfDifferentDistributions, epsilon} = module(Internals) - let mean = GenericDist_Types.Constructors.UsingDists.mean +describe("Means invariant", () => { - let runMean: result => float = distR => { - distR - ->E.R2.fmap(mean) - ->E.R2.fmap(run) - ->E.R2.fmap(toFloat) - ->E.R.toExn - ->E.O2.toExn("Shouldn't see this because we trust testcase input") - } - - let impossiblePath: string => assertion = algebraicOp => - `${algebraicOp} has`->expect->toEqual("failed") - - let distributions = list{ - normalMake(0.0, 1e0), - betaMake(2e0, 4e0), - exponentialMake(1.234e0), - uniformMake(7e0, 1e1), - // cauchyMake(1e0, 1e0), - lognormalMake(1e0, 1e0), - triangularMake(1e0, 1e1, 5e1), - Ok(floatMake(1e1)), - } - let combinations = E.L.combinations2(distributions) - let zipDistsDists = E.L.zip(distributions, distributions) - - let testOperationMean = ( - distOp: (DistributionTypes.genericDist, DistributionTypes.genericDist) => result, - description: string, - floatOp: (float, float) => float, - dist1': result, - dist2': result - ) => { - let dist1 = dist1'->E.R2.fmap(x=>DistributionTypes.Symbolic(x))->E.R2.fmap2(s=>DistributionTypes.Other(s)) - let dist2 = dist2'->E.R2.fmap(x=>DistributionTypes.Symbolic(x))->E.R2.fmap2(s=>DistributionTypes.Other(s)) - let received = - E.R.liftJoin2(distOp, dist1, dist2) - ->E.R2.fmap(mean) - ->E.R2.fmap(run) - ->E.R2.fmap(toFloat) - let expected = floatOp(runMean(dist1), runMean(dist2)) - switch received { - | Error(err) => impossiblePath(description) - | Ok(x) => - switch x { - | None => impossiblePath(description) - | Some(x) => x->expect->toBeSoCloseTo(expected, ~digits) - } - } - } - - describe("addition", () => { - let testAdditionMean = testOperationMean(algebraicAdd, "algebraicAdd", \"+.") - - testAll("homogeneous addition", zipDistsDists, dists => { - let (dist1, dist2) = dists - testAdditionMean(dist1, dist2) + describe("for addition", () => { + let testAdditionMean = testOperationMean(algebraicAdd, "algebraicAdd", \"+.", ~epsilon=epsilon) + + testAll("of two of the same distribution", distributions, dist => { + E.R.liftM2(testAdditionMean, dist, dist) -> E.R.toExn }) - testAll("heterogeneous addition (1)", combinations, dists => { + testAll("of two different distributions", pairsOfDifferentDistributions, dists => { let (dist1, dist2) = dists - testAdditionMean(dist1, dist2) + E.R.liftM2(testAdditionMean, dist1, dist2) -> E.R.toExn }) - testAll("heterogeneous addition (commuted of 1 (or; 2))", combinations, dists => { + testAll("of two difference distributions", pairsOfDifferentDistributions, dists => { let (dist1, dist2) = dists - testAdditionMean(dist2, dist1) + E.R.liftM2(testAdditionMean, dist2, dist1) -> E.R.toExn }) }) - describe("subtraction", () => { - let testSubtractionMean = testOperationMean(algebraicSubtract, "algebraicSubtract", \"-.") + describe("for subtraction", () => { + let testSubtractionMean = testOperationMean(algebraicSubtract, "algebraicSubtract", \"-.", ~epsilon=epsilon) - testAll("homogeneous subtraction", zipDistsDists, dists => { - let (dist1, dist2) = dists - testSubtractionMean(dist1, dist2) + testAll("of two of the same distribution", distributions, dist => { + E.R.liftM2(testSubtractionMean, dist, dist) -> E.R.toExn }) - testAll("heterogeneous subtraction (1)", combinations, dists => { + testAll("of two different distributions", pairsOfDifferentDistributions, dists => { let (dist1, dist2) = dists - testSubtractionMean(dist1, dist2) + E.R.liftM2(testSubtractionMean, dist1, dist2) -> E.R.toExn }) - testAll("heterogeneous subtraction (commuted of 1 (or; 2))", combinations, dists => { + testAll("of two different distributions", pairsOfDifferentDistributions, dists => { let (dist1, dist2) = dists - testSubtractionMean(dist2, dist1) + E.R.liftM2(testSubtractionMean, dist2, dist1) -> E.R.toExn }) }) - describe("multiplication", () => { - let testMultiplicationMean = testOperationMean(algebraicMultiply, "algebraicMultiply", \"*.") + describe("for multiplication", () => { + let testMultiplicationMean = testOperationMean(algebraicMultiply, "algebraicMultiply", \"*.", ~epsilon=epsilon) - testAll("homogeneous subtraction", zipDistsDists, dists => { - let (dist1, dist2) = dists - testMultiplicationMean(dist1, dist2) + testAll("of two of the same distribution", distributions, dist => { + E.R.liftM2(testMultiplicationMean, dist, dist) -> E.R.toExn }) - testAll("heterogeneoous subtraction (1)", combinations, dists => { + testAll("of two different distributions", pairsOfDifferentDistributions, dists => { let (dist1, dist2) = dists - testMultiplicationMean(dist1, dist2) + E.R.liftM2(testMultiplicationMean, dist1, dist2) -> E.R.toExn }) - testAll("heterogeneoous subtraction (commuted of 1 (or; 2))", combinations, dists => { + testAll("of two different distributions", pairsOfDifferentDistributions, dists => { let (dist1, dist2) = dists - testMultiplicationMean(dist2, dist1) + E.R.liftM2(testMultiplicationMean, dist2, dist1) -> E.R.toExn }) }) }) diff --git a/packages/squiggle-lang/__tests__/TestHelpers.res b/packages/squiggle-lang/__tests__/TestHelpers.res index 4967f75c..bff21805 100644 --- a/packages/squiggle-lang/__tests__/TestHelpers.res +++ b/packages/squiggle-lang/__tests__/TestHelpers.res @@ -1,6 +1,16 @@ open Jest open Expect +let expectErrorToBeBounded = (received, expected, ~epsilon) => { + let distance = Js.Math.abs_float(received -. expected) + let error = if expected < epsilon ** 2.5 { + distance /. epsilon + } else { + distance /. Js.Math.abs_float(expected) + } + error -> expect -> toBeLessThan(epsilon) +} + let makeTest = (~only=false, str, item1, item2) => only ? Only.test(str, () => expect(item1)->toEqual(item2))