From e7c2a7db01a0d4105e0838222785066e0e748b74 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Fri, 1 Apr 2022 15:41:11 -0400 Subject: [PATCH] Light cleanup of ReducerInterface_GenericDistribution --- .../ReducerInterface_Distribution_test.res | 4 +- .../src/rescript/GenericDist/GenericDist.res | 2 +- .../GenericDist/GenericDist_Types.res | 4 +- .../ReducerInterface_GenericDistribution.res | 188 +++++++++--------- .../typeSystem/HardcodedFunctions.res | 2 +- .../AlgebraicShapeCombination.res | 4 +- .../rescript/symbolicDist/SymbolicDist.res | 40 ++-- .../src/rescript/utility/Operation.res | 16 +- 8 files changed, 138 insertions(+), 122 deletions(-) diff --git a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res index 8fd6f9da..ada50f3f 100644 --- a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res +++ b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res @@ -6,6 +6,9 @@ let makeTest = (str, result) => test(str, () => expectEvalToBe(str, result)) describe("eval", () => { Only.describe("expressions", () => { makeTest("normal(5,2)", "Ok(Normal(5,2))") + makeTest("5 to 2", "Error(TODO: Low value must be less than high value.)") + makeTest("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.278507821238345))") + makeTest("to(-2,2)", "Ok(Normal(0,1.215913388057542))") makeTest("lognormal(5,2)", "Ok(Lognormal(5,2))") makeTest("mean(normal(5,2))", "Ok(5)") makeTest("mean(lognormal(1,2))", "Ok(20.085536923187668)") @@ -21,6 +24,5 @@ describe("eval", () => { makeTest("3+normal(5,2)", "Ok(Point Set Distribution)") makeTest("add(3, 3)", "Ok(6)") makeTest("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)") - makeTest("mean(add(3, normal(5,2)))", "Ok(8.004619792609384)") }) }) diff --git a/packages/squiggle-lang/src/rescript/GenericDist/GenericDist.res b/packages/squiggle-lang/src/rescript/GenericDist/GenericDist.res index bb2f8d71..f71d9b93 100644 --- a/packages/squiggle-lang/src/rescript/GenericDist/GenericDist.res +++ b/packages/squiggle-lang/src/rescript/GenericDist/GenericDist.res @@ -228,7 +228,7 @@ let pointwiseCombinationFloat = ( ): result => { let m = switch arithmeticOperation { | #Add | #Subtract => Error(GenericDist_Types.DistributionVerticalShiftIsInvalid) - | (#Multiply | #Divide | #Exponentiate | #Log) as arithmeticOperation => + | (#Multiply | #Divide | #Exponentiate | #Logarithm) as arithmeticOperation => toPointSetFn(t)->E.R2.fmap(t => { //TODO: Move to PointSet codebase let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary) diff --git a/packages/squiggle-lang/src/rescript/GenericDist/GenericDist_Types.res b/packages/squiggle-lang/src/rescript/GenericDist/GenericDist_Types.res index 98c0da25..43ce5d74 100644 --- a/packages/squiggle-lang/src/rescript/GenericDist/GenericDist_Types.res +++ b/packages/squiggle-lang/src/rescript/GenericDist/GenericDist_Types.res @@ -20,7 +20,7 @@ module Operation = { | #Subtract | #Divide | #Exponentiate - | #Log + | #Logarithm ] let arithmeticToFn = (arithmetic: arithmeticOperation) => @@ -30,7 +30,7 @@ module Operation = { | #Subtract => \"-." | #Exponentiate => \"**" | #Divide => \"/." - | #Log => (a, b) => log(a) /. log(b) + | #Logarithm => (a, b) => log(a) /. log(b) } type toFloat = [ diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res index 0bc7e23f..9970c340 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res @@ -1,80 +1,67 @@ module ExpressionValue = ReducerInterface_ExpressionValue type expressionValue = ReducerInterface_ExpressionValue.expressionValue -let env: GenericDist_GenericOperation.env = { - sampleCount: 1000, - xyPointLength: 1000, -} +let runGenericOperation = GenericDist_GenericOperation.run( + ~env={ + sampleCount: 1000, + xyPointLength: 1000, + }, +) -let runGenericOperation = GenericDist_GenericOperation.run(~env) +module Helpers = { + let arithmeticMap = r => + switch r { + | "add" => #Add + | "dotAdd" => #Add + | "subtract" => #Subtract + | "dotSubtract" => #Subtract + | "divide" => #Divide + | "logarithm" => #Logarithm + | "dotDivide" => #Divide + | "exponentiate" => #Exponentiate + | "dotExponentiate" => #Exponentiate + | "multiply" => #Multiply + | "dotMultiply" => #Multiply + | "dotLogarithm" => #Logarithm + | _ => #Multiply + } -let arithmeticMap = r => - switch r { - | "add" => #Add - | "dotAdd" => #Add - | "subtract" => #Subtract - | "dotSubtract" => #Subtract - | "divide" => #Divide - | "logarithm" => #Divide - | "dotDivide" => #Divide - | "exponentiate" => #Exponentiate - | "dotExponentiate" => #Exponentiate - | "multiply" => #Multiply - | "dotMultiply" => #Multiply - | "dotLogarithm" => #Divide - | _ => #Multiply + let catchAndConvertTwoArgsToDists = (args: array): option<( + GenericDist_Types.genericDist, + GenericDist_Types.genericDist, + )> => { + switch args { + | [EvDistribution(a), EvDistribution(b)] => Some((a, b)) + | [EvNumber(a), EvDistribution(b)] => Some((GenericDist.fromFloat(a), b)) + | [EvDistribution(a), EvNumber(b)] => Some((a, GenericDist.fromFloat(b))) + | _ => None + } } -let catchAndConvertTwoArgsToDists = (args: array): option<( - GenericDist_Types.genericDist, - GenericDist_Types.genericDist, -)> => { - switch args { - | [EvDistribution(a), EvDistribution(b)] => Some((a, b)) - | [EvNumber(a), EvDistribution(b)] => Some((GenericDist.fromFloat(a), b)) - | [EvDistribution(a), EvNumber(b)] => Some((a, GenericDist.fromFloat(b))) - | _ => None + let toFloatFn = ( + fnCall: GenericDist_Types.Operation.toFloat, + dist: GenericDist_Types.genericDist, + ) => { + FromDist(GenericDist_Types.Operation.ToFloat(fnCall), dist)->runGenericOperation->Some + } + + let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => { + FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some + } + + let twoDiststoDistFn = (direction, arithmetic, dist1, dist2) => { + FromDist( + GenericDist_Types.Operation.ToDistCombination( + direction, + arithmeticMap(arithmetic), + #Dist(dist2), + ), + dist1, + )->runGenericOperation } } -let toFloatFn = ( - fnCall: GenericDist_Types.Operation.toFloat, - dist: GenericDist_Types.genericDist, -) => { - FromDist(GenericDist_Types.Operation.ToFloat(fnCall), dist)->runGenericOperation->Some -} - -let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => { - FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some -} - -let twoDiststoDistFn = (direction, arithmetic, dist1, dist2) => { - FromDist( - GenericDist_Types.Operation.ToDistCombination( - direction, - arithmeticMap(arithmetic), - #Dist(dist2), - ), - dist1, - )->runGenericOperation -} - -let genericOutputToReducerValue = (o: GenericDist_GenericOperation.outputType): result< - expressionValue, - Reducer_ErrorValue.errorValue, -> => - switch o { - | Dist(d) => Ok(ReducerInterface_ExpressionValue.EvDistribution(d)) - | Float(d) => Ok(EvNumber(d)) - | String(d) => Ok(EvString(d)) - | GenDistError(NotYetImplemented) => Error(RETodo("Function not yet implemented")) - | GenDistError(Unreachable) => Error(RETodo("Unreachable")) - | GenDistError(DistributionVerticalShiftIsInvalid) => - Error(RETodo("Distribution Vertical Shift is Invalid")) - | GenDistError(Other(s)) => Error(RETodo(s)) - } - -module SymbolicConstructor = { +module SymbolicConstructors = { let oneFloat = name => switch name { | "exponential" => Ok(SymbolicDist.Exponential.make) @@ -87,6 +74,7 @@ module SymbolicConstructor = { | "uniform" => Ok(SymbolicDist.Uniform.make) | "beta" => Ok(SymbolicDist.Beta.make) | "lognormal" => Ok(SymbolicDist.Lognormal.make) + | "to" => Ok(SymbolicDist.From90thPercentile.make) | _ => Error("impossible path") } @@ -111,47 +99,65 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option< let (fnName, args) = call switch (fnName, args) { | ("exponential" as fnName, [EvNumber(f1)]) => - SymbolicConstructor.oneFloat(fnName) + SymbolicConstructors.oneFloat(fnName) ->E.R.bind(r => r(f1)) - ->SymbolicConstructor.symbolicResultToOutput - | (("normal" | "uniform" | "beta" | "lognormal") as fnName, [EvNumber(f1), EvNumber(f2)]) => - SymbolicConstructor.twoFloat(fnName) + ->SymbolicConstructors.symbolicResultToOutput + | ( + ("normal" | "uniform" | "beta" | "lognormal" | "to") as fnName, + [EvNumber(f1), EvNumber(f2)], + ) => + SymbolicConstructors.twoFloat(fnName) ->E.R.bind(r => r(f1, f2)) - ->SymbolicConstructor.symbolicResultToOutput + ->SymbolicConstructors.symbolicResultToOutput | ("triangular" as fnName, [EvNumber(f1), EvNumber(f2), EvNumber(f3)]) => - SymbolicConstructor.threeFloat(fnName) + SymbolicConstructors.threeFloat(fnName) ->E.R.bind(r => r(f1, f2, f3)) - ->SymbolicConstructor.symbolicResultToOutput - | ("sample", [EvDistribution(dist)]) => toFloatFn(#Sample, dist) - | ("mean", [EvDistribution(dist)]) => toFloatFn(#Mean, dist) - | ("normalize", [EvDistribution(dist)]) => toDistFn(Normalize, dist) - | ("toPointSet", [EvDistribution(dist)]) => toDistFn(ToPointSet, dist) - | ("cdf", [EvDistribution(dist), EvNumber(float)]) => toFloatFn(#Cdf(float), dist) - | ("pdf", [EvDistribution(dist), EvNumber(float)]) => toFloatFn(#Pdf(float), dist) - | ("inv", [EvDistribution(dist), EvNumber(float)]) => toFloatFn(#Inv(float), dist) + ->SymbolicConstructors.symbolicResultToOutput + | ("sample", [EvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist) + | ("mean", [EvDistribution(dist)]) => Helpers.toFloatFn(#Mean, dist) + | ("normalize", [EvDistribution(dist)]) => Helpers.toDistFn(Normalize, dist) + | ("toPointSet", [EvDistribution(dist)]) => Helpers.toDistFn(ToPointSet, dist) + | ("cdf", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Cdf(float), dist) + | ("pdf", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Pdf(float), dist) + | ("inv", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Inv(float), dist) | ("toSampleSet", [EvDistribution(dist), EvNumber(float)]) => - toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist) + Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist) | ("truncateLeft", [EvDistribution(dist), EvNumber(float)]) => - toDistFn(Truncate(Some(float), None), dist) + Helpers.toDistFn(Truncate(Some(float), None), dist) | ("truncateRight", [EvDistribution(dist), EvNumber(float)]) => - toDistFn(Truncate(None, Some(float)), dist) + Helpers.toDistFn(Truncate(None, Some(float)), dist) | ("truncate", [EvDistribution(dist), EvNumber(float1), EvNumber(float2)]) => - toDistFn(Truncate(Some(float1), Some(float2)), dist) - | (("add" | "multiply" | "subtract" | "divide" | "exponentiate") as arithmetic, [a, b] as args) => - catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) => - twoDiststoDistFn(Algebraic, arithmetic, fst, snd) + Helpers.toDistFn(Truncate(Some(float1), Some(float2)), dist) + | (("add" | "multiply" | "subtract" | "divide" | "exponentiate" | "log") as arithmetic, [a, b] as args) => + Helpers.catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) => + Helpers.twoDiststoDistFn(Algebraic, arithmetic, fst, snd) ) | ( - ("dotAdd" | "dotMultiply" | "dotSubtract" | "dotDivide" | "dotExponentiate") as arithmetic, + ("dotAdd" | "dotMultiply" | "dotSubtract" | "dotDivide" | "dotExponentiate" | "dotLogarithm") as arithmetic, [a, b] as args, ) => - catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) => - twoDiststoDistFn(Pointwise, arithmetic, fst, snd) + Helpers.catchAndConvertTwoArgsToDists(args)->E.O2.fmap(((fst, snd)) => + Helpers.twoDiststoDistFn(Pointwise, arithmetic, fst, snd) ) | _ => None } } +let genericOutputToReducerValue = (o: GenericDist_GenericOperation.outputType): result< + expressionValue, + Reducer_ErrorValue.errorValue, +> => + switch o { + | Dist(d) => Ok(ReducerInterface_ExpressionValue.EvDistribution(d)) + | Float(d) => Ok(EvNumber(d)) + | String(d) => Ok(EvString(d)) + | GenDistError(NotYetImplemented) => Error(RETodo("Function not yet implemented")) + | GenDistError(Unreachable) => Error(RETodo("Unreachable")) + | GenDistError(DistributionVerticalShiftIsInvalid) => + Error(RETodo("Distribution Vertical Shift is Invalid")) + | GenDistError(Other(s)) => Error(RETodo(s)) + } + let dispatch = call => { dispatchToGenericOutput(call)->E.O2.fmap(genericOutputToReducerValue) } diff --git a/packages/squiggle-lang/src/rescript/interpreter/typeSystem/HardcodedFunctions.res b/packages/squiggle-lang/src/rescript/interpreter/typeSystem/HardcodedFunctions.res index f6741bab..cf8fe470 100644 --- a/packages/squiggle-lang/src/rescript/interpreter/typeSystem/HardcodedFunctions.res +++ b/packages/squiggle-lang/src/rescript/interpreter/typeSystem/HardcodedFunctions.res @@ -229,6 +229,6 @@ let all = [ ), makeRenderedDistFloat("scaleExp", (dist, float) => verticalScaling(#Exponentiate, dist, float)), makeRenderedDistFloat("scaleMultiply", (dist, float) => verticalScaling(#Multiply, dist, float)), - makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Log, dist, float)), + makeRenderedDistFloat("scaleLog", (dist, float) => verticalScaling(#Logarithm, dist, float)), Multimodal._function, ] diff --git a/packages/squiggle-lang/src/rescript/pointSetDist/AlgebraicShapeCombination.res b/packages/squiggle-lang/src/rescript/pointSetDist/AlgebraicShapeCombination.res index 3c298b18..9cfdc66d 100644 --- a/packages/squiggle-lang/src/rescript/pointSetDist/AlgebraicShapeCombination.res +++ b/packages/squiggle-lang/src/rescript/pointSetDist/AlgebraicShapeCombination.res @@ -115,7 +115,7 @@ let combineShapesContinuousContinuous = ( | #Multiply => (m1, m2) => m1 *. m2 | #Divide => (m1, mInv2) => m1 *. mInv2 | #Exponentiate => (m1, mInv2) => m1 ** mInv2 - | #Log => (m1, m2) => log(m1) /. log(m2) + | #Logarithm => (m1, m2) => log(m1) /. log(m2) } // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2) // TODO: I don't know what the variances are for exponentatiation @@ -233,7 +233,7 @@ let combineShapesContinuousDiscrete = ( } | #Multiply | #Exponentiate - | #Log + | #Logarithm | #Divide => for j in 0 to t2n - 1 { // creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes. diff --git a/packages/squiggle-lang/src/rescript/symbolicDist/SymbolicDist.res b/packages/squiggle-lang/src/rescript/symbolicDist/SymbolicDist.res index ffeec12f..cd4132b3 100644 --- a/packages/squiggle-lang/src/rescript/symbolicDist/SymbolicDist.res +++ b/packages/squiggle-lang/src/rescript/symbolicDist/SymbolicDist.res @@ -2,7 +2,7 @@ open SymbolicDistTypes module Normal = { type t = normal - let make = (mean: float, stdev: float): result => + let make = (mean: float, stdev: float): result => stdev > 0.0 ? Ok(#Normal({mean: mean, stdev: stdev})) : Error("Standard deviation of normal distribution must be larger than 0") @@ -48,11 +48,13 @@ module Normal = { module Exponential = { type t = exponential - let make = (rate: float): result => + let make = (rate: float): result => rate > 0.0 - ? Ok(#Exponential({ - rate: rate, - })) + ? Ok( + #Exponential({ + rate: rate, + }), + ) : Error("Exponential distributions mean 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) @@ -89,7 +91,7 @@ module Triangular = { module Beta = { type t = beta - let make = (alpha, beta) => + let make = (alpha, beta) => alpha > 0.0 && beta > 0.0 ? Ok(#Beta({alpha: alpha, beta: beta})) : Error("Beta distribution parameters must be positive") @@ -103,10 +105,10 @@ module Beta = { module Lognormal = { type t = lognormal - let make = (mu, sigma) => - sigma > 0.0 - ? Ok(#Lognormal({mu: mu, sigma: sigma})) - : Error("Lognormal standard deviation must be larger than 0") + let make = (mu, sigma) => + sigma > 0.0 + ? Ok(#Lognormal({mu: mu, sigma: sigma})) + : Error("Lognormal standard deviation must be larger than 0") let pdf = (x, t: t) => Jstat.Lognormal.pdf(x, t.mu, t.sigma) let cdf = (x, t: t) => Jstat.Lognormal.cdf(x, t.mu, t.sigma) let inv = (p, t: t) => Jstat.Lognormal.inv(p, t.mu, t.sigma) @@ -127,8 +129,7 @@ module Lognormal = { let mu = Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0) let sigma = Js.Math.pow_float(~base=Js.Math.log(variance /. meanSquared +. 1.0), ~exp=0.5) Ok(#Lognormal({mu: mu, sigma: sigma})) - } - else { + } else { Error("Lognormal standard deviation must be larger than 0") } } @@ -154,9 +155,7 @@ module Lognormal = { module Uniform = { type t = uniform let make = (low, high) => - high > low - ? Ok(#Uniform({low: low, high: high})) - : Error("High must be larger than low") + high > low ? Ok(#Uniform({low: low, high: high})) : Error("High must be larger than low") let pdf = (x, t: t) => Jstat.Uniform.pdf(x, t.low, t.high) let cdf = (x, t: t) => Jstat.Uniform.cdf(x, t.low, t.high) @@ -165,7 +164,7 @@ module Uniform = { let mean = (t: t) => Ok(Jstat.Uniform.mean(t.low, t.high)) let toString = ({low, high}: t) => j`Uniform($low,$high)` let truncate = (low, high, t: t): t => { -//todo: add check + //todo: add check let newLow = max(E.O.default(neg_infinity, low), t.low) let newHigh = min(E.O.default(infinity, high), t.high) {low: newLow, high: newHigh} @@ -183,6 +182,15 @@ module Float = { let toString = Js.Float.toString } +module From90thPercentile = { + let make = (low, high) => + switch (low, high) { + | (low, high) if low <= 0.0 && low < high => Ok(Normal.from90PercentCI(low, high)) + | (low, high) if low < high => Ok(Lognormal.from90PercentCI(low, high)) + | (_, _) => Error("Low value must be less than high value.") + } +} + module T = { let minCdfValue = 0.0001 let maxCdfValue = 0.9999 diff --git a/packages/squiggle-lang/src/rescript/utility/Operation.res b/packages/squiggle-lang/src/rescript/utility/Operation.res index 540bd08c..55e0b42f 100644 --- a/packages/squiggle-lang/src/rescript/utility/Operation.res +++ b/packages/squiggle-lang/src/rescript/utility/Operation.res @@ -7,11 +7,11 @@ type algebraicOperation = [ | #Subtract | #Divide | #Exponentiate - | #Log + | #Logarithm ] @genType type pointwiseOperation = [#Add | #Multiply | #Exponentiate] -type scaleOperation = [#Multiply | #Exponentiate | #Log | #Divide] +type scaleOperation = [#Multiply | #Exponentiate | #Logarithm | #Divide] type distToFloatOperation = [ | #Pdf(float) | #Cdf(float) @@ -29,7 +29,7 @@ module Algebraic = { | #Multiply => \"*." | #Exponentiate => \"**" | #Divide => \"/." - | #Log => (a, b) => log(a) /. log(b) + | #Logarithm => (a, b) => log(a) /. log(b) } let applyFn = (t, f1, f2) => @@ -45,7 +45,7 @@ module Algebraic = { | #Multiply => "*" | #Exponentiate => "**" | #Divide => "/" - | #Log => "log" + | #Logarithm => "log" } let format = (a, b, c) => b ++ (" " ++ (toString(a) ++ (" " ++ c))) @@ -84,7 +84,7 @@ module Scale = { | #Multiply => \"*." | #Divide => \"/." | #Exponentiate => \"**" - | #Log => (a, b) => log(a) /. log(b) + | #Logarithm => (a, b) => log(a) /. log(b) } let format = (operation: t, value, scaleBy) => @@ -92,7 +92,7 @@ module Scale = { | #Multiply => j`verticalMultiply($value, $scaleBy) ` | #Divide => j`verticalDivide($value, $scaleBy) ` | #Exponentiate => j`verticalExponentiate($value, $scaleBy) ` - | #Log => j`verticalLog($value, $scaleBy) ` + | #Logarithm => j`verticalLog($value, $scaleBy) ` } let toIntegralSumCacheFn = x => @@ -100,7 +100,7 @@ module Scale = { | #Multiply => (a, b) => Some(a *. b) | #Divide => (a, b) => Some(a /. b) | #Exponentiate => (_, _) => None - | #Log => (_, _) => None + | #Logarithm => (_, _) => None } let toIntegralCacheFn = x => @@ -108,7 +108,7 @@ module Scale = { | #Multiply => (_, _) => None // TODO: this could probably just be multiplied out (using Continuous.scaleBy) | #Divide => (_, _) => None | #Exponentiate => (_, _) => None - | #Log => (_, _) => None + | #Logarithm => (_, _) => None } }