diff --git a/__tests__/Distributions__Test.re b/__tests__/Distributions__Test.re index c02430fe..4e16bb80 100644 --- a/__tests__/Distributions__Test.re +++ b/__tests__/Distributions__Test.re @@ -382,10 +382,10 @@ describe("Shape", () => { let variance = stdev ** 2.0; let numSamples = 10000; open Distributions.Shape; - let normal: SymbolicDist.dist = `Normal({mean, stdev}); - let normalShape = TreeNode.toShape(numSamples, `DistData(`Symbolic(normal))); + let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev}); + let normalShape = TreeNode.toShape(numSamples, `Leaf(`SymbolicDist(normal))); let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev); - let lognormalShape = TreeNode.toShape(numSamples, `DistData(`Symbolic(lognormal))); + let lognormalShape = TreeNode.toShape(numSamples, `Leaf(`SymbolicDist(lognormal))); makeTestCloseEquality( "Mean of a normal", diff --git a/src/components/Drawer.re b/src/components/Drawer.re index 8dc0c7db..f39cd3ad 100644 --- a/src/components/Drawer.re +++ b/src/components/Drawer.re @@ -388,8 +388,8 @@ module Draw = { let stdev = 15.0; let numSamples = 3000; - let normal: SymbolicDist.dist = `Normal({mean, stdev}); - let normalShape = TreeNode.toShape(numSamples, `DistData(`Symbolic(normal))); + let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev}); + let normalShape = TreeNode.toShape(numSamples, `Leaf(`SymbolicDist(normal))); let xyShape: Types.xyShape = switch (normalShape) { | Mixed(_) => {xs: [||], ys: [||]} @@ -398,9 +398,9 @@ module Draw = { }; /* // To use a lognormal instead: - let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev); + let lognormal = SymbolicTypes.Lognormal.fromMeanAndStdev(mean, stdev); let lognormalShape = - SymbolicDist.GenericSimple.toShape(lognormal, numSamples); + SymbolicTypes.GenericSimple.toShape(lognormal, numSamples); let lognormalXYShape: Types.xyShape = switch (lognormalShape) { | Mixed(_) => {xs: [||], ys: [||]} diff --git a/src/distPlus/symbolic/MathJsParser.re b/src/distPlus/symbolic/MathJsParser.re index b83713a2..3874fda4 100644 --- a/src/distPlus/symbolic/MathJsParser.re +++ b/src/distPlus/symbolic/MathJsParser.re @@ -89,26 +89,26 @@ module MathAdtToDistDst = { let normal: array(arg) => result(TreeNode.treeNode, string) = fun | [|Value(mean), Value(stdev)|] => - Ok(`DistData(`Symbolic(`Normal({mean, stdev})))) + Ok(`Leaf(`SymbolicDist(`Normal({mean, stdev})))) | _ => Error("Wrong number of variables in normal distribution"); let lognormal: array(arg) => result(TreeNode.treeNode, string) = fun | [|Value(mu), Value(sigma)|] => - Ok(`DistData(`Symbolic(`Lognormal({mu, sigma})))) + Ok(`Leaf(`SymbolicDist(`Lognormal({mu, sigma})))) | [|Object(o)|] => { let g = Js.Dict.get(o); switch (g("mean"), g("stdev"), g("mu"), g("sigma")) { | (Some(Value(mean)), Some(Value(stdev)), _, _) => Ok( - `DistData( - `Symbolic( + `Leaf( + `SymbolicDist( SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev), ), ), ) | (_, _, Some(Value(mu)), Some(Value(sigma))) => - Ok(`DistData(`Symbolic(`Lognormal({mu, sigma})))) + Ok(`Leaf(`SymbolicDist(`Lognormal({mu, sigma})))) | _ => Error("Lognormal distribution would need mean and stdev") }; } @@ -118,15 +118,15 @@ module MathAdtToDistDst = { fun | [|Value(low), Value(high)|] when low <= 0.0 && low < high => { Ok( - `DistData( - `Symbolic(SymbolicDist.Normal.from90PercentCI(low, high)), + `Leaf( + `SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high)), ), ); } | [|Value(low), Value(high)|] when low < high => { Ok( - `DistData( - `Symbolic(SymbolicDist.Lognormal.from90PercentCI(low, high)), + `Leaf( + `SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high)), ), ); } @@ -137,31 +137,31 @@ module MathAdtToDistDst = { let uniform: array(arg) => result(TreeNode.treeNode, string) = fun | [|Value(low), Value(high)|] => - Ok(`DistData(`Symbolic(`Uniform({low, high})))) + Ok(`Leaf(`SymbolicDist(`Uniform({low, high})))) | _ => Error("Wrong number of variables in lognormal distribution"); let beta: array(arg) => result(TreeNode.treeNode, string) = fun | [|Value(alpha), Value(beta)|] => - Ok(`DistData(`Symbolic(`Beta({alpha, beta})))) + Ok(`Leaf(`SymbolicDist(`Beta({alpha, beta})))) | _ => Error("Wrong number of variables in lognormal distribution"); let exponential: array(arg) => result(TreeNode.treeNode, string) = fun | [|Value(rate)|] => - Ok(`DistData(`Symbolic(`Exponential({rate: rate})))) + Ok(`Leaf(`SymbolicDist(`Exponential({rate: rate})))) | _ => Error("Wrong number of variables in Exponential distribution"); let cauchy: array(arg) => result(TreeNode.treeNode, string) = fun | [|Value(local), Value(scale)|] => - Ok(`DistData(`Symbolic(`Cauchy({local, scale})))) + Ok(`Leaf(`SymbolicDist(`Cauchy({local, scale})))) | _ => Error("Wrong number of variables in cauchy distribution"); let triangular: array(arg) => result(TreeNode.treeNode, string) = fun | [|Value(low), Value(medium), Value(high)|] => - Ok(`DistData(`Symbolic(`Triangular({low, medium, high})))) + Ok(`Leaf(`SymbolicDist(`Triangular({low, medium, high})))) | _ => Error("Wrong number of variables in triangle distribution"); let multiModal = @@ -196,7 +196,7 @@ module MathAdtToDistDst = { `VerticalScaling(( `Multiply, t, - `DistData(`Symbolic(`Float(w))), + `Leaf(`SymbolicDist(`Float(w))), )), ); }); @@ -235,7 +235,7 @@ module MathAdtToDistDst = { SymbolicDist.ContinuousShape.make(_pdf, cdf); }); switch (shape) { - | Some(s) => Ok(`DistData(`Symbolic(`ContinuousShape(s)))) + | Some(s) => Ok(`Leaf(`SymbolicDist(`ContinuousShape(s)))) | None => Error("Rendering did not work") }; }; @@ -254,11 +254,11 @@ module MathAdtToDistDst = { | ("divide", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Divide, l, r)) | ("divide", _) => Error("Division needs two operands") | ("pow", _) => Error("Exponentiation is not yet supported.") - | ("leftTruncate", [|Ok(d), Ok(`DistData(`Symbolic(`Float(lc))))|]) => + | ("leftTruncate", [|Ok(d), Ok(`Leaf(`SymbolicDist(`Float(lc))))|]) => toOkTrunctate((Some(lc), None, d)) | ("leftTruncate", _) => Error("leftTruncate needs two arguments: the expression and the cutoff") - | ("rightTruncate", [|Ok(d), Ok(`DistData(`Symbolic(`Float(rc))))|]) => + | ("rightTruncate", [|Ok(d), Ok(`Leaf(`SymbolicDist(`Float(rc))))|]) => toOkTrunctate((None, Some(rc), d)) | ("rightTruncate", _) => Error( @@ -268,8 +268,8 @@ module MathAdtToDistDst = { "truncate", [| Ok(d), - Ok(`DistData(`Symbolic(`Float(lc)))), - Ok(`DistData(`Symbolic(`Float(rc)))), + Ok(`Leaf(`SymbolicDist(`Float(lc)))), + Ok(`Leaf(`SymbolicDist(`Float(rc)))), |], ) => toOkTrunctate((Some(lc), Some(rc), d)) @@ -333,7 +333,7 @@ module MathAdtToDistDst = { let rec nodeParser = fun - | Value(f) => Ok(`DistData(`Symbolic(`Float(f)))) + | Value(f) => Ok(`Leaf(`SymbolicDist(`Float(f)))) | Fn({name, args}) => functionParser(nodeParser, name, args) | _ => { Error("This type not currently supported"); diff --git a/src/distPlus/symbolic/SymbolicDist.re b/src/distPlus/symbolic/SymbolicDist.re index 28130a98..58402b38 100644 --- a/src/distPlus/symbolic/SymbolicDist.re +++ b/src/distPlus/symbolic/SymbolicDist.re @@ -1,52 +1,4 @@ -type normal = { - mean: float, - stdev: float, -}; - -type lognormal = { - mu: float, - sigma: float, -}; - -type uniform = { - low: float, - high: float, -}; - -type beta = { - alpha: float, - beta: float, -}; - -type exponential = {rate: float}; - -type cauchy = { - local: float, - scale: float, -}; - -type triangular = { - low: float, - medium: float, - high: float, -}; - -type continuousShape = { - pdf: DistTypes.continuousShape, - cdf: DistTypes.continuousShape, -}; - -type dist = [ - | `Normal(normal) - | `Beta(beta) - | `Lognormal(lognormal) - | `Uniform(uniform) - | `Exponential(exponential) - | `Cauchy(cauchy) - | `Triangular(triangular) - | `ContinuousShape(continuousShape) - | `Float(float) // Dirac delta at x. Practically useful only in the context of multimodals. -]; +open SymbolicTypes; module ContinuousShape = { type t = continuousShape; @@ -124,11 +76,12 @@ module Normal = { `Normal({mean, stdev}); }; - let operate = (operation: SymbolicTypes.Algebraic.t, n1: t, n2: t) => switch(operation){ + let operate = (operation: SymbolicTypes.Algebraic.t, n1: t, n2: t) => + switch (operation) { | `Add => Some(add(n1, n2)) | `Subtract => Some(subtract(n1, n2)) | _ => None - } + }; }; module Beta = { @@ -177,11 +130,12 @@ module Lognormal = { let sigma = l1.sigma +. l2.sigma; `Lognormal({mu, sigma}); }; - let operate = (operation: SymbolicTypes.Algebraic.t, n1: t, n2: t) => switch(operation){ + let operate = (operation: SymbolicTypes.Algebraic.t, n1: t, n2: t) => + switch (operation) { | `Multiply => Some(multiply(n1, n2)) | `Divide => Some(divide(n1, n2)) | _ => None - } + }; }; module Uniform = { @@ -202,7 +156,7 @@ module Float = { let toString = Js.Float.toString; }; -module GenericDistFunctions = { +module T = { let minCdfValue = 0.0001; let maxCdfValue = 0.9999; @@ -232,7 +186,7 @@ module GenericDistFunctions = { | `ContinuousShape(n) => ContinuousShape.inv(x, n) }; - let sample: dist => float = + let sample: symbolicDist => float = fun | `Normal(n) => Normal.sample(n) | `Triangular(n) => Triangular.sample(n) @@ -244,7 +198,7 @@ module GenericDistFunctions = { | `Float(n) => Float.sample(n) | `ContinuousShape(n) => ContinuousShape.sample(n); - let toString: dist => string = + let toString: symbolicDist => string = fun | `Triangular(n) => Triangular.toString(n) | `Exponential(n) => Exponential.toString(n) @@ -256,7 +210,7 @@ module GenericDistFunctions = { | `Float(n) => Float.toString(n) | `ContinuousShape(n) => ContinuousShape.toString(n); - let min: dist => float = + let min: symbolicDist => float = fun | `Triangular({low}) => low | `Exponential(n) => Exponential.inv(minCdfValue, n) @@ -268,7 +222,7 @@ module GenericDistFunctions = { | `ContinuousShape(n) => ContinuousShape.inv(minCdfValue, n) | `Float(n) => n; - let max: dist => float = + let max: symbolicDist => float = fun | `Triangular(n) => n.high | `Exponential(n) => Exponential.inv(maxCdfValue, n) @@ -280,7 +234,7 @@ module GenericDistFunctions = { | `Uniform({high}) => high | `Float(n) => n; - let mean: dist => result(float, string) = + let mean: symbolicDist => result(float, string) = fun | `Triangular(n) => Triangular.mean(n) | `Exponential(n) => Exponential.mean(n) @@ -293,7 +247,7 @@ module GenericDistFunctions = { | `Float(n) => Float.mean(n); let interpolateXs = - (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, n) => { + (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: symbolicDist, n) => { switch (xSelection, dist) { | (`Linear, _) => E.A.Floats.range(min(dist), max(dist), n) /* | (`ByWeight, `Uniform(n)) => @@ -306,4 +260,36 @@ module GenericDistFunctions = { ys |> E.A.fmap(y => inv(y, dist)); }; }; + + /* This returns an optional that wraps a result. If the optional is None, + there is no valid analytic solution. If it Some, it + can still return an error if there is a serious problem, + like in the casea of a divide by 0. + */ + type analyticalSolutionAttempt = [ + | `AnalyticalSolution(SymbolicTypes.symbolicDist) + | `Error(string) + | `NoSolution + ]; + let attemptAlgebraicOperation = + ( + d1: symbolicDist, + d2: symbolicDist, + op: SymbolicTypes.algebraicOperation, + ) + : analyticalSolutionAttempt => + switch (d1, d2) { + | (`Float(v1), `Float(v2)) => + switch (SymbolicTypes.Algebraic.applyFn(op, v1, v2)) { + | Ok(r) => `AnalyticalSolution(`Float(r)) + | Error(n) => `Error(n) + } + | (`Normal(v1), `Normal(v2)) => + Normal.operate(op, v1, v2) + |> E.O.dimap(r => `AnalyticalSolution(r), () => `NoSolution) + | (`Lognormal(v1), `Lognormal(v2)) => + Lognormal.operate(op, v1, v2) + |> E.O.dimap(r => `AnalyticalSolution(r), () => `NoSolution) + | _ => `NoSolution + }; }; diff --git a/src/distPlus/symbolic/SymbolicTypes.re b/src/distPlus/symbolic/SymbolicTypes.re index 07ae5703..ccc2ecba 100644 --- a/src/distPlus/symbolic/SymbolicTypes.re +++ b/src/distPlus/symbolic/SymbolicTypes.re @@ -1,7 +1,57 @@ +type normal = { + mean: float, + stdev: float, +}; + +type lognormal = { + mu: float, + sigma: float, +}; + +type uniform = { + low: float, + high: float, +}; + +type beta = { + alpha: float, + beta: float, +}; + +type exponential = {rate: float}; + +type cauchy = { + local: float, + scale: float, +}; + +type triangular = { + low: float, + medium: float, + high: float, +}; + +type continuousShape = { + pdf: DistTypes.continuousShape, + cdf: DistTypes.continuousShape, +}; + +type symbolicDist = [ + | `Normal(normal) + | `Beta(beta) + | `Lognormal(lognormal) + | `Uniform(uniform) + | `Exponential(exponential) + | `Cauchy(cauchy) + | `Triangular(triangular) + | `ContinuousShape(continuousShape) + | `Float(float) // Dirac delta at x. Practically useful only in the context of multimodals. +]; + +type algebraicOperation = [ | `Add | `Multiply | `Subtract | `Divide]; type pointwiseOperation = [ | `Add | `Multiply]; type scaleOperation = [ | `Multiply | `Exponentiate | `Log]; type distToFloatOperation = [ | `Pdf(float) | `Inv(float) | `Mean | `Sample]; -type algebraicOperation = [ | `Add | `Multiply | `Subtract | `Divide]; module Algebraic = { type t = algebraicOperation; diff --git a/src/distPlus/symbolic/TreeNode.re b/src/distPlus/symbolic/TreeNode.re index 92836c0d..de68e433 100644 --- a/src/distPlus/symbolic/TreeNode.re +++ b/src/distPlus/symbolic/TreeNode.re @@ -1,13 +1,12 @@ /* This module represents a tree node. */ open SymbolicTypes; -// todo: Symbolic already has an arbitrary continuousShape option. It seems messy to have both. -type distData = [ - | `Symbolic(SymbolicDist.dist) - | `RenderedShape(DistTypes.shape) +type leaf = [ + | `SymbolicDist(SymbolicTypes.symbolicDist) + | `RenderedDist(DistTypes.shape) ]; /* TreeNodes are either Data (i.e. symbolic or rendered distributions) or Operations. Operations always refer to two child nodes.*/ -type treeNode = [ | `DistData(distData) | `Operation(operation)] +type treeNode = [ | `Leaf(leaf) | `Operation(operation)] and operation = [ | `AlgebraicCombination(algebraicOperation, treeNode, treeNode) | `PointwiseCombination(pointwiseOperation, treeNode, treeNode) @@ -48,9 +47,8 @@ module TreeNode = { let rec toString = fun - | `DistData(`Symbolic(d)) => - SymbolicDist.GenericDistFunctions.toString(d) - | `DistData(`RenderedShape(_)) => "[shape]" + | `Leaf(`SymbolicDist(d)) => SymbolicDist.T.toString(d) + | `Leaf(`RenderedDist(_)) => "[shape]" | `Operation(op) => Operation.toString(toString, op); /* The following modules encapsulate everything we can do with @@ -61,73 +59,34 @@ module TreeNode = { For instance, normal(0, 1) + normal(1, 1) -> normal(1, 2). In general, this is implemented via convolution. */ module AlgebraicCombination = { - let simplify = (algebraicOp, t1: t, t2: t): result(treeNode, string) => { - let tryCombiningFloats: tResult = - fun - | `Operation( - `AlgebraicCombination( - algebraicOp, - `DistData(`Symbolic(`Float(v1))), - `DistData(`Symbolic(`Float(v2))), - ), - ) => - SymbolicTypes.Algebraic.applyFn(algebraicOp, v1, v2) - |> E.R.fmap(r => `DistData(`Symbolic(`Float(r)))) - | t => Ok(t); - - let optionToSymbolicResult = (t, o) => - o - |> E.O.dimap(r => `DistData(`Symbolic(r)), () => t) - |> (r => Ok(r)); - - let tryCombiningNormals: tResult = - fun - | `Operation( - `AlgebraicCombination( - operation, - `DistData(`Symbolic(`Normal(n1))), - `DistData(`Symbolic(`Normal(n2))), - ), - ) as t => - SymbolicDist.Normal.operate(operation, n1, n2) - |> optionToSymbolicResult(t) - | t => Ok(t); - - let tryCombiningLognormals: tResult = - fun - | `Operation( - `AlgebraicCombination( - operation, - `DistData(`Symbolic(`Lognormal(n1))), - `DistData(`Symbolic(`Lognormal(n2))), - ), - ) as t => - SymbolicDist.Lognormal.operate(operation, n1, n2) - |> optionToSymbolicResult(t) - | t => Ok(t); - - let originalTreeNode = - `Operation(`AlgebraicCombination((algebraicOp, t1, t2))); - - // Feedback: I like this pattern, kudos - originalTreeNode - |> tryCombiningFloats - |> E.R.bind(_, tryCombiningNormals) - |> E.R.bind(_, tryCombiningLognormals); - }; + let toTreeNode = (op, t1, t2) => + `Operation(`AlgebraicCombination((op, t1, t2))); + let tryAnalyticalSolution = + fun + | `Operation( + `AlgebraicCombination( + operation, + `Leaf(`SymbolicDist(d1)), + `Leaf(`SymbolicDist(d2)), + ), + ) as t => + switch (SymbolicDist.T.attemptAlgebraicOperation(d1, d2, operation)) { + | `AnalyticalSolution(symbolicDist) => + Ok(`Leaf(`SymbolicDist(symbolicDist))) + | `Error(er) => Error(er) + | `NoSolution => Ok(t) + } + | t => Ok(t); // todo: I don't like the name evaluateNumerically that much, if this renders and does it algebraically. It's tricky. - let evaluateNumerically = (algebraicOp, operationToDistData, t1, t2) => { + let evaluateNumerically = (algebraicOp, operationToLeaf, t1, t2) => { // force rendering into shapes - let renderShape = r => operationToDistData(`Render(r)); + let renderShape = r => operationToLeaf(`Render(r)); switch (renderShape(t1), renderShape(t2)) { - | ( - Ok(`DistData(`RenderedShape(s1))), - Ok(`DistData(`RenderedShape(s2))), - ) => + | (Ok(`Leaf(`RenderedDist(s1))), Ok(`Leaf(`RenderedDist(s2)))) => Ok( - `DistData( - `RenderedShape( + `Leaf( + `RenderedDist( Distributions.Shape.combineAlgebraically(algebraicOp, s1, s2), ), ), @@ -138,42 +97,40 @@ module TreeNode = { }; }; - let evaluateToDistData = + let evaluateToLeaf = ( algebraicOp: SymbolicTypes.algebraicOperation, - operationToDistData, + operationToLeaf, t1: t, t2: t, ) : result(treeNode, string) => algebraicOp - |> simplify(_, t1, t2) + |> toTreeNode(_, t1, t2) + |> tryAnalyticalSolution |> E.R.bind( _, fun - | `DistData(d) => Ok(`DistData(d)) // the analytical simplifaction worked, nice! + | `Leaf(d) => Ok(`Leaf(d)) // the analytical simplifaction worked, nice! | `Operation(_) => // if not, run the convolution - evaluateNumerically(algebraicOp, operationToDistData, t1, t2), + evaluateNumerically(algebraicOp, operationToLeaf, t1, t2), ); }; module VerticalScaling = { - let evaluateToDistData = (scaleOp, operationToDistData, t, scaleBy) => { + let evaluateToLeaf = (scaleOp, operationToLeaf, t, scaleBy) => { // scaleBy has to be a single float, otherwise we'll return an error. let fn = SymbolicTypes.Scale.toFn(scaleOp); let knownIntegralSumFn = SymbolicTypes.Scale.toKnownIntegralSumFn(scaleOp); - let renderedShape = operationToDistData(`Render(t)); + let renderedShape = operationToLeaf(`Render(t)); switch (renderedShape, scaleBy) { - | ( - Ok(`DistData(`RenderedShape(rs))), - `DistData(`Symbolic(`Float(sm))), - ) => + | (Ok(`Leaf(`RenderedDist(rs))), `Leaf(`SymbolicDist(`Float(sm)))) => Ok( - `DistData( - `RenderedShape( + `Leaf( + `RenderedDist( Distributions.Shape.T.mapY( ~knownIntegralSumFn=knownIntegralSumFn(sm), fn(sm), @@ -189,18 +146,15 @@ module TreeNode = { }; module PointwiseCombination = { - let pointwiseAdd = (operationToDistData, t1, t2) => { - let renderedShape1 = operationToDistData(`Render(t1)); - let renderedShape2 = operationToDistData(`Render(t2)); + let pointwiseAdd = (operationToLeaf, t1, t2) => { + let renderedShape1 = operationToLeaf(`Render(t1)); + let renderedShape2 = operationToLeaf(`Render(t2)); switch (renderedShape1, renderedShape2) { - | ( - Ok(`DistData(`RenderedShape(rs1))), - Ok(`DistData(`RenderedShape(rs2))), - ) => + | (Ok(`Leaf(`RenderedDist(rs1))), Ok(`Leaf(`RenderedDist(rs2)))) => Ok( - `DistData( - `RenderedShape( + `Leaf( + `RenderedDist( Distributions.Shape.combinePointwise( ~knownIntegralSumsFn=(a, b) => Some(a +. b), (+.), @@ -216,18 +170,18 @@ module TreeNode = { }; }; - let pointwiseMultiply = (operationToDistData, t1, t2) => { + let pointwiseMultiply = (operationToLeaf, t1, t2) => { // TODO: construct a function that we can easily sample from, to construct - // a RenderedShape. Use the xMin and xMax of the rendered shapes to tell the sampling function where to look. + // a RenderedDist. Use the xMin and xMax of the rendered shapes to tell the sampling function where to look. Error( "Pointwise multiplication not yet supported.", ); }; - let evaluateToDistData = (pointwiseOp, operationToDistData, t1, t2) => { + let evaluateToLeaf = (pointwiseOp, operationToLeaf, t1, t2) => { switch (pointwiseOp) { - | `Add => pointwiseAdd(operationToDistData, t1, t2) - | `Multiply => pointwiseMultiply(operationToDistData, t1, t2) + | `Add => pointwiseAdd(operationToLeaf, t1, t2) + | `Multiply => pointwiseMultiply(operationToLeaf, t1, t2) }; }; }; @@ -236,18 +190,17 @@ module TreeNode = { module Simplify = { let tryTruncatingNothing: tResult = fun - | `Operation(`Truncate(None, None, `DistData(d))) => - Ok(`DistData(d)) + | `Operation(`Truncate(None, None, `Leaf(d))) => Ok(`Leaf(d)) | t => Ok(t); let tryTruncatingUniform: tResult = fun - | `Operation(`Truncate(lc, rc, `DistData(`Symbolic(`Uniform(u))))) => { + | `Operation(`Truncate(lc, rc, `Leaf(`SymbolicDist(`Uniform(u))))) => { // just create a new Uniform distribution let newLow = max(E.O.default(neg_infinity, lc), u.low); let newHigh = min(E.O.default(infinity, rc), u.high); Ok( - `DistData(`Symbolic(`Uniform({low: newLow, high: newHigh}))), + `Leaf(`SymbolicDist(`Uniform({low: newLow, high: newHigh}))), ); } | t => Ok(t); @@ -262,27 +215,26 @@ module TreeNode = { }; }; - let evaluateNumerically = - (leftCutoff, rightCutoff, operationToDistData, t) => { + let evaluateNumerically = (leftCutoff, rightCutoff, operationToLeaf, t) => { // TODO: use named args in renderToShape; if we're lucky we can at least get the tail // of a distribution we otherwise wouldn't get at all - let renderedShape = operationToDistData(`Render(t)); + let renderedShape = operationToLeaf(`Render(t)); switch (renderedShape) { - | Ok(`DistData(`RenderedShape(rs))) => + | Ok(`Leaf(`RenderedDist(rs))) => let truncatedShape = rs |> Distributions.Shape.T.truncate(leftCutoff, rightCutoff); - Ok(`DistData(`RenderedShape(rs))); + Ok(`Leaf(`RenderedDist(rs))); | Error(e1) => Error(e1) | _ => Error("Could not truncate distribution.") }; }; - let evaluateToDistData = + let evaluateToLeaf = ( leftCutoff: option(float), rightCutoff: option(float), - operationToDistData, + operationToLeaf, t: treeNode, ) : result(treeNode, string) => { @@ -291,31 +243,23 @@ module TreeNode = { |> E.R.bind( _, fun - | `DistData(d) => Ok(`DistData(d)) // the analytical simplifaction worked, nice! + | `Leaf(d) => Ok(`Leaf(d)) // the analytical simplifaction worked, nice! | `Operation(_) => - evaluateNumerically( - leftCutoff, - rightCutoff, - operationToDistData, - t, - ), + evaluateNumerically(leftCutoff, rightCutoff, operationToLeaf, t), ); // if not, run the convolution }; }; module Normalize = { - let rec evaluateToDistData = - (operationToDistData, t: treeNode): result(treeNode, string) => { + let rec evaluateToLeaf = + (operationToLeaf, t: treeNode): result(treeNode, string) => { switch (t) { - | `DistData(`Symbolic(_)) => Ok(t) - | `DistData(`RenderedShape(s)) => + | `Leaf(`SymbolicDist(_)) => Ok(t) + | `Leaf(`RenderedDist(s)) => let normalized = Distributions.Shape.T.normalize(s); - Ok(`DistData(`RenderedShape(normalized))); + Ok(`Leaf(`RenderedDist(normalized))); | `Operation(op) => - E.R.bind( - operationToDistData(op), - evaluateToDistData(operationToDistData), - ) + E.R.bind(operationToLeaf(op), evaluateToLeaf(operationToLeaf)) }; }; }; @@ -324,14 +268,14 @@ module TreeNode = { let evaluateFromSymbolic = (distToFloatOp: distToFloatOperation, s) => { let value = switch (distToFloatOp) { - | `Pdf(f) => Ok(SymbolicDist.GenericDistFunctions.pdf(f, s)) - | `Inv(f) => Ok(SymbolicDist.GenericDistFunctions.inv(f, s)) - | `Sample => Ok(SymbolicDist.GenericDistFunctions.sample(s)) - | `Mean => SymbolicDist.GenericDistFunctions.mean(s) + | `Pdf(f) => Ok(SymbolicDist.T.pdf(f, s)) + | `Inv(f) => Ok(SymbolicDist.T.inv(f, s)) + | `Sample => Ok(SymbolicDist.T.sample(s)) + | `Mean => SymbolicDist.T.mean(s) }; - E.R.bind(value, v => Ok(`DistData(`Symbolic(`Float(v))))); + E.R.bind(value, v => Ok(`Leaf(`SymbolicDist(`Float(v))))); }; - let evaluateFromRenderedShape = + let evaluateFromRenderedDist = (distToFloatOp: distToFloatOperation, rs: DistTypes.shape) : result(treeNode, string) => { let value = @@ -341,45 +285,45 @@ module TreeNode = { | `Sample => Ok(Distributions.Shape.sample(rs)) | `Mean => Ok(Distributions.Shape.T.mean(rs)) }; - E.R.bind(value, v => Ok(`DistData(`Symbolic(`Float(v))))); + E.R.bind(value, v => Ok(`Leaf(`SymbolicDist(`Float(v))))); }; - let rec evaluateToDistData = + let rec evaluateToLeaf = ( distToFloatOp: distToFloatOperation, - operationToDistData, + operationToLeaf, t: treeNode, ) : result(treeNode, string) => { switch (t) { - | `DistData(`Symbolic(s)) => evaluateFromSymbolic(distToFloatOp, s) // we want to evaluate the distToFloatOp on the symbolic dist - | `DistData(`RenderedShape(rs)) => - evaluateFromRenderedShape(distToFloatOp, rs) + | `Leaf(`SymbolicDist(s)) => evaluateFromSymbolic(distToFloatOp, s) // we want to evaluate the distToFloatOp on the symbolic dist + | `Leaf(`RenderedDist(rs)) => + evaluateFromRenderedDist(distToFloatOp, rs) | `Operation(op) => E.R.bind( - operationToDistData(op), - evaluateToDistData(distToFloatOp, operationToDistData), + operationToLeaf(op), + evaluateToLeaf(distToFloatOp, operationToLeaf), ) }; }; }; module Render = { - let rec evaluateToRenderedShape = + let rec evaluateToRenderedDist = ( - operationToDistData: operation => result(t, string), + operationToLeaf: operation => result(t, string), sampleCount: int, t: treeNode, ) : result(t, string) => { switch (t) { - | `DistData(`RenderedShape(s)) => Ok(`DistData(`RenderedShape(s))) // already a rendered shape, we're done here - | `DistData(`Symbolic(d)) => + | `Leaf(`RenderedDist(s)) => Ok(`Leaf(`RenderedDist(s))) // already a rendered shape, we're done here + | `Leaf(`SymbolicDist(d)) => // todo: move to dist switch (d) { | `Float(v) => Ok( - `DistData( - `RenderedShape( + `Leaf( + `RenderedDist( Discrete( Distributions.Discrete.make( {xs: [|v|], ys: [|1.0|]}, @@ -391,16 +335,15 @@ module TreeNode = { ) | _ => let xs = - SymbolicDist.GenericDistFunctions.interpolateXs( + SymbolicDist.T.interpolateXs( ~xSelection=`ByWeight, d, sampleCount, ); - let ys = - xs |> E.A.fmap(x => SymbolicDist.GenericDistFunctions.pdf(x, d)); + let ys = xs |> E.A.fmap(x => SymbolicDist.T.pdf(x, d)); Ok( - `DistData( - `RenderedShape( + `Leaf( + `RenderedDist( Continuous( Distributions.Continuous.make( `Linear, @@ -414,57 +357,57 @@ module TreeNode = { } | `Operation(op) => E.R.bind( - operationToDistData(op), - evaluateToRenderedShape(operationToDistData, sampleCount), + operationToLeaf(op), + evaluateToRenderedDist(operationToLeaf, sampleCount), ) }; }; }; - let rec operationToDistData = + let rec operationToLeaf = (sampleCount: int, op: operation): result(t, string) => { - // the functions that convert the Operation nodes to DistData nodes need to + // the functions that convert the Operation nodes to Leaf nodes need to // have a way to call this function on their children, if their children are themselves Operation nodes. switch (op) { | `AlgebraicCombination(algebraicOp, t1, t2) => - AlgebraicCombination.evaluateToDistData( + AlgebraicCombination.evaluateToLeaf( algebraicOp, - operationToDistData(sampleCount), + operationToLeaf(sampleCount), t1, t2 // we want to give it the option to render or simply leave it as is ) | `PointwiseCombination(pointwiseOp, t1, t2) => - PointwiseCombination.evaluateToDistData( + PointwiseCombination.evaluateToLeaf( pointwiseOp, - operationToDistData(sampleCount), + operationToLeaf(sampleCount), t1, t2, ) | `VerticalScaling(scaleOp, t, scaleBy) => - VerticalScaling.evaluateToDistData( + VerticalScaling.evaluateToLeaf( scaleOp, - operationToDistData(sampleCount), + operationToLeaf(sampleCount), t, scaleBy, ) | `Truncate(leftCutoff, rightCutoff, t) => - Truncate.evaluateToDistData( + Truncate.evaluateToLeaf( leftCutoff, rightCutoff, - operationToDistData(sampleCount), + operationToLeaf(sampleCount), t, ) | `FloatFromDist(distToFloatOp, t) => - FloatFromDist.evaluateToDistData( + FloatFromDist.evaluateToLeaf( distToFloatOp, - operationToDistData(sampleCount), + operationToLeaf(sampleCount), t, ) | `Normalize(t) => - Normalize.evaluateToDistData(operationToDistData(sampleCount), t) + Normalize.evaluateToLeaf(operationToLeaf(sampleCount), t) | `Render(t) => - Render.evaluateToRenderedShape( - operationToDistData(sampleCount), + Render.evaluateToRenderedDist( + operationToLeaf(sampleCount), sampleCount, t, ) @@ -474,23 +417,23 @@ module TreeNode = { /* This function recursively goes through the nodes of the parse tree, replacing each Operation node and its subtree with a Data node. Whenever possible, the replacement produces a new Symbolic Data node, - but most often it will produce a RenderedShape. - This function is used mainly to turn a parse tree into a single RenderedShape + but most often it will produce a RenderedDist. + This function is used mainly to turn a parse tree into a single RenderedDist that can then be displayed to the user. */ - let toDistData = (treeNode: t, sampleCount: int): result(t, string) => { + let toLeaf = (treeNode: t, sampleCount: int): result(t, string) => { switch (treeNode) { - | `DistData(d) => Ok(`DistData(d)) - | `Operation(op) => operationToDistData(sampleCount, op) + | `Leaf(d) => Ok(`Leaf(d)) + | `Operation(op) => operationToLeaf(sampleCount, op) }; }; }; let toShape = (sampleCount: int, treeNode: treeNode) => { let renderResult = - TreeNode.toDistData(`Operation(`Render(treeNode)), sampleCount); + TreeNode.toLeaf(`Operation(`Render(treeNode)), sampleCount); switch (renderResult) { - | Ok(`DistData(`RenderedShape(rs))) => + | Ok(`Leaf(`RenderedDist(rs))) => let continuous = Distributions.Shape.T.toContinuous(rs); let discrete = Distributions.Shape.T.toDiscrete(rs); let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete);