Merge branch 'develop' into reducer-dev
This commit is contained in:
		
						commit
						1fa1867470
					
				|  | @ -1,15 +1,64 @@ | ||||||
| /* | /* | ||||||
| This is the most basic file in our invariants family of tests.  | 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://develop--squiggle-documentation.netlify.app/docs/internal/invariants/ | ||||||
|  | 
 | ||||||
|  | Note: epsilon of 1e3 means the invariants are, in general, not being satisfied.  | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| open Jest | open Jest | ||||||
| open Expect | open Expect | ||||||
| open TestHelpers | open TestHelpers | ||||||
| 
 | 
 | ||||||
|  | module Internals = { | ||||||
|  |   let epsilon = 5e1 | ||||||
|  | 
 | ||||||
|  |   let mean = GenericDist_Types.Constructors.UsingDists.mean | ||||||
|  | 
 | ||||||
|  |   let expectImpossiblePath: string => assertion = algebraicOp => | ||||||
|  |     `${algebraicOp} has`->expect->toEqual("failed") | ||||||
|  | 
 | ||||||
|  |   let distributions = list{ | ||||||
|  |     normalMake(4e0, 1e0), | ||||||
|  |     betaMake(2e0, 4e0), | ||||||
|  |     exponentialMake(1.234e0), | ||||||
|  |     uniformMake(7e0, 1e1), | ||||||
|  |     // cauchyMake(1e0, 1e0), | ||||||
|  |     lognormalMake(2e0, 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<DistributionTypes.genericDist, DistributionTypes.error>, | ||||||
|  |     description: string, | ||||||
|  |     floatOp: (float, float) => float, | ||||||
|  |     dist1': SymbolicDistTypes.symbolicDist, | ||||||
|  |     dist2': SymbolicDistTypes.symbolicDist, | ||||||
|  |     ~epsilon: float, | ||||||
|  |   ) => { | ||||||
|  |     let dist1 = dist1'->DistributionTypes.Symbolic | ||||||
|  |     let dist2 = dist2'->DistributionTypes.Symbolic | ||||||
|  |     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) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| let { | let { | ||||||
|   algebraicAdd, |   algebraicAdd, | ||||||
|   algebraicMultiply, |   algebraicMultiply, | ||||||
|  | @ -26,115 +75,82 @@ let algebraicSubtract = algebraicSubtract(~env) | ||||||
| let algebraicLogarithm = algebraicLogarithm(~env) | let algebraicLogarithm = algebraicLogarithm(~env) | ||||||
| let algebraicPower = algebraicPower(~env) | let algebraicPower = algebraicPower(~env) | ||||||
| 
 | 
 | ||||||
| describe("Mean", () => { | let {testOperationMean, distributions, pairsOfDifferentDistributions, epsilon} = module(Internals) | ||||||
|   let digits = -4 |  | ||||||
| 
 | 
 | ||||||
|   let mean = GenericDist_Types.Constructors.UsingDists.mean | describe("Means are invariant", () => { | ||||||
|  |   describe("for addition", () => { | ||||||
|  |     let testAdditionMean = testOperationMean(algebraicAdd, "algebraicAdd", \"+.", ~epsilon) | ||||||
| 
 | 
 | ||||||
|   let runMean: result<DistributionTypes.genericDist, DistributionTypes.error> => float = distR => { |     testAll("with two of the same distribution", distributions, dist => { | ||||||
|     distR |       E.R.liftM2(testAdditionMean, dist, dist)->E.R.toExn | ||||||
|     ->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<DistributionTypes.genericDist, DistributionTypes.error>,  |  | ||||||
|       description: string,  |  | ||||||
|       floatOp: (float, float) => float,  |  | ||||||
|       dist1': result<SymbolicDistTypes.symbolicDist, string>,  |  | ||||||
|       dist2': result<SymbolicDistTypes.symbolicDist, string> |  | ||||||
|     ) => { |  | ||||||
|     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) |  | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     testAll("heterogeneous addition (1)", combinations, dists => { |     testAll("with two different distributions", pairsOfDifferentDistributions, dists => { | ||||||
|       let (dist1, dist2) = 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( | ||||||
|       let (dist1, dist2) = dists |       "with two different distributions in swapped order", | ||||||
|       testAdditionMean(dist2, dist1) |       pairsOfDifferentDistributions, | ||||||
|     }) |       dists => { | ||||||
|  |         let (dist1, dist2) = dists | ||||||
|  |         E.R.liftM2(testAdditionMean, dist2, dist1)->E.R.toExn | ||||||
|  |       }, | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("subtraction", () => { |   describe("for subtraction", () => { | ||||||
|     let testSubtractionMean = testOperationMean(algebraicSubtract, "algebraicSubtract", \"-.") |     let testSubtractionMean = testOperationMean( | ||||||
|  |       algebraicSubtract, | ||||||
|  |       "algebraicSubtract", | ||||||
|  |       \"-.", | ||||||
|  |       ~epsilon, | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|     testAll("homogeneous subtraction", zipDistsDists, dists => { |     testAll("with two of the same distribution", distributions, dist => { | ||||||
|       let (dist1, dist2) = dists |       E.R.liftM2(testSubtractionMean, dist, dist)->E.R.toExn | ||||||
|       testSubtractionMean(dist1, dist2) |  | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     testAll("heterogeneous subtraction (1)", combinations, dists => { |     testAll("with two different distributions", pairsOfDifferentDistributions, dists => { | ||||||
|       let (dist1, dist2) = 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( | ||||||
|       let (dist1, dist2) = dists |       "with two different distributions in swapped order", | ||||||
|       testSubtractionMean(dist2, dist1) |       pairsOfDifferentDistributions, | ||||||
|     }) |       dists => { | ||||||
|  |         let (dist1, dist2) = dists | ||||||
|  |         E.R.liftM2(testSubtractionMean, dist2, dist1)->E.R.toExn | ||||||
|  |       }, | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   describe("multiplication", () => { |   describe("for multiplication", () => { | ||||||
|     let testMultiplicationMean = testOperationMean(algebraicMultiply, "algebraicMultiply", \"*.") |     let testMultiplicationMean = testOperationMean( | ||||||
|  |       algebraicMultiply, | ||||||
|  |       "algebraicMultiply", | ||||||
|  |       \"*.", | ||||||
|  |       ~epsilon, | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
|     testAll("homogeneous subtraction", zipDistsDists, dists => { |     testAll("with two of the same distribution", distributions, dist => { | ||||||
|       let (dist1, dist2) = dists |       E.R.liftM2(testMultiplicationMean, dist, dist)->E.R.toExn | ||||||
|       testMultiplicationMean(dist1, dist2) |  | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     testAll("heterogeneoous subtraction (1)", combinations, dists => { |     testAll("with two different distributions", pairsOfDifferentDistributions, dists => { | ||||||
|       let (dist1, dist2) = 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( | ||||||
|       let (dist1, dist2) = dists |       "with two different distributions in swapped order", | ||||||
|       testMultiplicationMean(dist2, dist1) |       pairsOfDifferentDistributions, | ||||||
|     }) |       dists => { | ||||||
|  |         let (dist1, dist2) = dists | ||||||
|  |         E.R.liftM2(testMultiplicationMean, dist2, dist1)->E.R.toExn | ||||||
|  |       }, | ||||||
|  |     ) | ||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,25 @@ | ||||||
| open Jest | open Jest | ||||||
| open Expect | open Expect | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  | This encodes the expression for percent error | ||||||
|  | The test says "the percent error of received against expected is bounded by epsilon" | ||||||
|  | 
 | ||||||
|  | However, the semantics are degraded by catching some numerical instability:  | ||||||
|  | when expected is too small, the return of this function might blow up to infinity.  | ||||||
|  | So we capture that by taking the max of abs(expected) against a 1.  | ||||||
|  | 
 | ||||||
|  | A sanity check of this function would be welcome, in general it is a better way of approaching  | ||||||
|  | squiggle-lang tests than toBeSoCloseTo.  | ||||||
|  | */ | ||||||
|  | let expectErrorToBeBounded = (received, expected, ~epsilon) => { | ||||||
|  |   let distance = Js.Math.abs_float(received -. expected) | ||||||
|  |   let expectedAbs = Js.Math.abs_float(expected) | ||||||
|  |   let normalizingDenom = Js.Math.max_float(expectedAbs, 1e0) | ||||||
|  |   let error = distance /. normalizingDenom | ||||||
|  |   error->expect->toBeLessThan(epsilon) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| let makeTest = (~only=false, str, item1, item2) => | let makeTest = (~only=false, str, item1, item2) => | ||||||
|   only |   only | ||||||
|     ? Only.test(str, () => expect(item1)->toEqual(item2)) |     ? Only.test(str, () => expect(item1)->toEqual(item2)) | ||||||
|  |  | ||||||
|  | @ -117,7 +117,8 @@ module Helpers = { | ||||||
|         | Error(err) => GenDistError(ArgumentError(err)) |         | Error(err) => GenDistError(ArgumentError(err)) | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     | Some(EvDistribution(b)) => switch parseDistributionArray(args) { |     | Some(EvDistribution(b)) => | ||||||
|  |       switch parseDistributionArray(args) { | ||||||
|       | Ok(distributions) => mixtureWithDefaultWeights(distributions) |       | Ok(distributions) => mixtureWithDefaultWeights(distributions) | ||||||
|       | Error(err) => GenDistError(ArgumentError(err)) |       | Error(err) => GenDistError(ArgumentError(err)) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  | @ -215,8 +215,8 @@ module R2 = { | ||||||
|     | Ok(r) => Ok(r) |     | Ok(r) => Ok(r) | ||||||
|     | Error(e) => map(e) |     | Error(e) => map(e) | ||||||
|     } |     } | ||||||
|    | 
 | ||||||
|   let fmap2 = (xR, f) =>  |   let fmap2 = (xR, f) => | ||||||
|     switch xR { |     switch xR { | ||||||
|     | Ok(x) => x->Ok |     | Ok(x) => x->Ok | ||||||
|     | Error(x) => x->f->Error |     | Error(x) => x->f->Error | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user