Remove Leaf and Operation wrapper types

This commit is contained in:
Sebastian Kosch 2020-07-03 14:55:27 -07:00
parent a649a6bca2
commit ca9f725ae7
8 changed files with 193 additions and 229 deletions

View File

@ -383,9 +383,9 @@ describe("Shape", () => {
let numSamples = 10000;
open Distributions.Shape;
let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
let normalShape = ExpressionTree.toShape(numSamples, `Leaf(`SymbolicDist(normal)));
let normalShape = ExpressionTree.toShape(numSamples, `SymbolicDist(normal));
let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev);
let lognormalShape = ExpressionTree.toShape(numSamples, `Leaf(`SymbolicDist(lognormal)));
let lognormalShape = ExpressionTree.toShape(numSamples, `SymbolicDist(lognormal));
makeTestCloseEquality(
"Mean of a normal",

View File

@ -389,7 +389,7 @@ module Draw = {
let numSamples = 3000;
let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
let normalShape = ExpressionTree.toShape(numSamples, `Leaf(`SymbolicDist(normal)));
let normalShape = ExpressionTree.toShape(numSamples, `SymbolicDist(normal));
let xyShape: Types.xyShape =
switch (normalShape) {
| Mixed(_) => {xs: [||], ys: [||]}

View File

@ -2,10 +2,11 @@ open ExpressionTypes.ExpressionTree;
let toShape = (sampleCount: int, node: node) => {
let renderResult =
ExpressionTreeEvaluator.toLeaf(`Operation(`Render(node)), sampleCount);
`Render(`Normalize(node))
|> ExpressionTreeEvaluator.toLeaf({sampleCount: sampleCount});
switch (renderResult) {
| Ok(`Leaf(`RenderedDist(rs))) =>
| Ok(`RenderedDist(rs)) =>
let continuous = Distributions.Shape.T.toContinuous(rs);
let discrete = Distributions.Shape.T.toDiscrete(rs);
let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete);
@ -17,6 +18,6 @@ let toShape = (sampleCount: int, node: node) => {
let rec toString =
fun
| `Leaf(`SymbolicDist(d)) => SymbolicDist.T.toString(d)
| `Leaf(`RenderedDist(_)) => "[shape]"
| `Operation(op) => Operation.T.toString(toString, op);
| `SymbolicDist(d) => SymbolicDist.T.toString(d)
| `RenderedDist(_) => "[shape]"
| op => Operation.T.toString(toString, op);

View File

@ -1,91 +1,83 @@
/* This module represents a tree node. */
open ExpressionTypes;
open ExpressionTypes.ExpressionTree;
type t = node;
type tResult = node => result(node, string);
type renderParams = {
sampleCount: int,
};
/* Given two random variables A and B, this returns the distribution
of a new variable that is the result of the operation on A and B.
For instance, normal(0, 1) + normal(1, 1) -> normal(1, 2).
In general, this is implemented via convolution. */
module AlgebraicCombination = {
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.attemptAnalyticalOperation(d1, d2, operation)) {
| `AnalyticalSolution(symbolicDist) =>
Ok(`Leaf(`SymbolicDist(symbolicDist)))
let tryAnalyticalSimplification = (operation, t1: t, t2: t) =>
switch (operation, t1, t2) {
| (operation,
`SymbolicDist(d1),
`SymbolicDist(d2),
) =>
switch (SymbolicDist.T.tryAnalyticalSimplification(d1, d2, operation)) {
| `AnalyticalSolution(symbolicDist) => Ok(`SymbolicDist(symbolicDist))
| `Error(er) => Error(er)
| `NoSolution => Ok(t)
| `NoSolution => Ok(`AlgebraicCombination(operation, t1, t2))
}
| t => Ok(t);
| _ => Ok(`AlgebraicCombination(operation, t1, t2))
};
// todo: I don't like the name evaluateNumerically that much, if this renders and does it algebraically. It's tricky.
let evaluateNumerically = (algebraicOp, operationToLeaf, t1, t2) => {
// force rendering into shapes
let renderShape = r => operationToLeaf(`Render(r));
let combineAsShapes = (toLeaf, renderParams, algebraicOp, t1, t2) => {
let renderShape = r => toLeaf(renderParams, `Render(r));
switch (renderShape(t1), renderShape(t2)) {
| (Ok(`Leaf(`RenderedDist(s1))), Ok(`Leaf(`RenderedDist(s2)))) =>
| (Ok(`RenderedDist(s1)), Ok(`RenderedDist(s2))) =>
Ok(
`Leaf(
`RenderedDist(
Distributions.Shape.combineAlgebraically(algebraicOp, s1, s2),
),
),
)
| (Error(e1), _) => Error(e1)
| (_, Error(e2)) => Error(e2)
| _ => Error("Could not render shapes.")
| _ => Error("Algebraic combination: rendering failed.")
};
};
let toLeaf =
let operationToLeaf =
(
operationToLeaf,
toLeaf,
renderParams: renderParams,
algebraicOp: ExpressionTypes.algebraicOperation,
t1: t,
t2: t,
)
: result(node, string) =>
toTreeNode(algebraicOp, t1, t2)
|> tryAnalyticalSolution
algebraicOp
|> tryAnalyticalSimplification(_, t1, t2)
|> E.R.bind(
_,
fun
| `Leaf(d) => Ok(`Leaf(d)) // the analytical simplifaction worked, nice!
| `Operation(_) =>
// if not, run the convolution
evaluateNumerically(algebraicOp, operationToLeaf, t1, t2),
| `SymbolicDist(d) as t => Ok(t)
| _ => combineAsShapes(toLeaf, renderParams, algebraicOp, t1, t2)
);
};
module VerticalScaling = {
let toLeaf = (operationToLeaf, scaleOp, t, scaleBy) => {
let operationToLeaf = (toLeaf, renderParams, scaleOp, t, scaleBy) => {
// scaleBy has to be a single float, otherwise we'll return an error.
let fn = Operation.Scale.toFn(scaleOp);
let knownIntegralSumFn = Operation.Scale.toKnownIntegralSumFn(scaleOp);
let renderedShape = operationToLeaf(`Render(t));
let renderedShape = toLeaf(renderParams, `Render(t));
switch (renderedShape, scaleBy) {
| (Ok(`Leaf(`RenderedDist(rs))), `Leaf(`SymbolicDist(`Float(sm)))) =>
| (Ok(`RenderedDist(rs)), `SymbolicDist(`Float(sm))) =>
Ok(
`Leaf(
`RenderedDist(
Distributions.Shape.T.mapY(
~knownIntegralSumFn=knownIntegralSumFn(sm),
fn(sm),
rs,
),
),
),
)
| (Error(e1), _) => Error(e1)
@ -95,31 +87,27 @@ module VerticalScaling = {
};
module PointwiseCombination = {
let pointwiseAdd = (operationToLeaf, t1, t2) => {
let renderedShape1 = operationToLeaf(`Render(t1));
let renderedShape2 = operationToLeaf(`Render(t2));
switch (renderedShape1, renderedShape2) {
| (Ok(`Leaf(`RenderedDist(rs1))), Ok(`Leaf(`RenderedDist(rs2)))) =>
let pointwiseAdd = (toLeaf, renderParams, t1, t2) => {
let renderShape = r => toLeaf(renderParams, `Render(r));
switch (renderShape(t1), renderShape(t2)) {
| (Ok(`RenderedDist(rs1)), Ok(`RenderedDist(rs2))) =>
Ok(
`Leaf(
`RenderedDist(
Distributions.Shape.combinePointwise(
~knownIntegralSumsFn=(a, b) => Some(a +. b),
(+.),
rs1,
rs2,
),
`RenderedDist(
Distributions.Shape.combinePointwise(
~knownIntegralSumsFn=(a, b) => Some(a +. b),
(+.),
rs1,
rs2,
),
),
)
| (Error(e1), _) => Error(e1)
| (_, Error(e2)) => Error(e2)
| _ => Error("Could not perform pointwise addition.")
| _ => Error("Pointwise combination: rendering failed.")
};
};
let pointwiseMultiply = (operationToLeaf, t1, t2) => {
let pointwiseMultiply = (toLeaf, renderParams, t1, t2) => {
// TODO: construct a function that we can easily sample from, to construct
// a RenderedDist. Use the xMin and xMax of the rendered shapes to tell the sampling function where to look.
Error(
@ -127,84 +115,72 @@ module PointwiseCombination = {
);
};
let toLeaf = (operationToLeaf, pointwiseOp, t1, t2) => {
let operationToLeaf = (toLeaf, renderParams, pointwiseOp, t1, t2) => {
switch (pointwiseOp) {
| `Add => pointwiseAdd(operationToLeaf, t1, t2)
| `Multiply => pointwiseMultiply(operationToLeaf, t1, t2)
| `Add => pointwiseAdd(toLeaf, renderParams, t1, t2)
| `Multiply => pointwiseMultiply(toLeaf, renderParams, t1, t2)
};
};
};
module Truncate = {
module Simplify = {
let tryTruncatingNothing: tResult =
fun
| `Operation(`Truncate(None, None, `Leaf(d))) => Ok(`Leaf(d))
| t => Ok(t);
let tryTruncatingUniform: tResult =
fun
| `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(`Leaf(`SymbolicDist(`Uniform({low: newLow, high: newHigh}))));
}
| t => Ok(t);
let attempt = (leftCutoff, rightCutoff, t): result(node, string) => {
let originalTreeNode =
`Operation(`Truncate((leftCutoff, rightCutoff, t)));
originalTreeNode
|> tryTruncatingNothing
|> E.R.bind(_, tryTruncatingUniform);
let trySimplification = (leftCutoff, rightCutoff, t) => {
switch (leftCutoff, rightCutoff, t) {
| (None, None, t) => Ok(t)
| (lc, rc, `SymbolicDist(`Uniform(u))) => {
// just create a new Uniform distribution
let nu: SymbolicTypes.uniform = u;
let newLow = max(E.O.default(neg_infinity, lc), nu.low);
let newHigh = min(E.O.default(infinity, rc), nu.high);
Ok(`SymbolicDist(`Uniform({low: newLow, high: newHigh})));
}
| (_, _, t) => Ok(t)
};
};
let evaluateNumerically = (leftCutoff, rightCutoff, operationToLeaf, t) => {
let truncateAsShape = (toLeaf, renderParams, leftCutoff, rightCutoff, 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 = operationToLeaf(`Render(t));
let renderedShape = toLeaf(renderParams, `Render(t));
switch (renderedShape) {
| Ok(`Leaf(`RenderedDist(rs))) =>
| Ok(`RenderedDist(rs)) => {
let truncatedShape =
rs |> Distributions.Shape.T.truncate(leftCutoff, rightCutoff);
Ok(`Leaf(`RenderedDist(rs)));
Ok(`RenderedDist(rs));
}
| Error(e1) => Error(e1)
| _ => Error("Could not truncate distribution.")
};
};
let toLeaf =
(
operationToLeaf,
leftCutoff: option(float),
rightCutoff: option(float),
t: node,
)
: result(node, string) => {
let operationToLeaf =
(
toLeaf,
renderParams,
leftCutoff: option(float),
rightCutoff: option(float),
t: node,
)
: result(node, string) => {
t
|> Simplify.attempt(leftCutoff, rightCutoff)
|> trySimplification(leftCutoff, rightCutoff)
|> E.R.bind(
_,
fun
| `Leaf(d) => Ok(`Leaf(d)) // the analytical simplifaction worked, nice!
| `Operation(_) =>
evaluateNumerically(leftCutoff, rightCutoff, operationToLeaf, t),
); // if not, run the convolution
| `SymbolicDist(d) as t => Ok(t)
| _ => truncateAsShape(toLeaf, renderParams, leftCutoff, rightCutoff, t),
);
};
};
module Normalize = {
let rec toLeaf = (operationToLeaf, t: node): result(node, string) => {
let rec operationToLeaf = (toLeaf, renderParams, t: node): result(node, string) => {
switch (t) {
| `Leaf(`RenderedDist(s)) =>
Ok(`Leaf(`RenderedDist(Distributions.Shape.T.normalize(s))))
| `Leaf(`SymbolicDist(_)) => Ok(t)
| `Operation(op) =>
operationToLeaf(op) |> E.R.bind(_, toLeaf(operationToLeaf))
| `RenderedDist(s) =>
Ok(`RenderedDist(Distributions.Shape.T.normalize(s)))
| `SymbolicDist(_) => Ok(t)
| _ => t |> toLeaf(renderParams) |> E.R.bind(_, operationToLeaf(toLeaf, renderParams))
};
};
};
@ -212,83 +188,79 @@ module Normalize = {
module FloatFromDist = {
let symbolicToLeaf = (distToFloatOp: distToFloatOperation, s) => {
SymbolicDist.T.operate(distToFloatOp, s)
|> E.R.bind(_, v => Ok(`Leaf(`SymbolicDist(`Float(v)))));
|> E.R.bind(_, v => Ok(`SymbolicDist(`Float(v))));
};
let renderedToLeaf =
(distToFloatOp: distToFloatOperation, rs: DistTypes.shape)
: result(node, string) => {
Distributions.Shape.operate(distToFloatOp, rs)
|> (v => Ok(`Leaf(`SymbolicDist(`Float(v)))));
|> (v => Ok(`SymbolicDist(`Float(v))));
};
let rec toLeaf =
(operationToLeaf, distToFloatOp: distToFloatOperation, t: node)
let rec operationToLeaf =
(toLeaf, renderParams, distToFloatOp: distToFloatOperation, t: node)
: result(node, string) => {
switch (t) {
| `Leaf(`SymbolicDist(s)) => symbolicToLeaf(distToFloatOp, s) // we want to evaluate the distToFloatOp on the symbolic dist
| `Leaf(`RenderedDist(rs)) => renderedToLeaf(distToFloatOp, rs)
| `Operation(op) =>
E.R.bind(operationToLeaf(op), toLeaf(operationToLeaf, distToFloatOp))
| `SymbolicDist(s) => symbolicToLeaf(distToFloatOp, s)
| `RenderedDist(rs) => renderedToLeaf(distToFloatOp, rs)
| _ => t |> toLeaf(renderParams) |> E.R.bind(_, operationToLeaf(toLeaf, renderParams, distToFloatOp))
};
};
};
module Render = {
let rec toLeaf =
let rec operationToLeaf =
(
operationToLeaf: operation => result(t, string),
sampleCount: int,
toLeaf,
renderParams,
t: node,
)
: result(t, string) => {
switch (t) {
| `Leaf(`SymbolicDist(d)) =>
Ok(`Leaf(`RenderedDist(SymbolicDist.T.toShape(sampleCount, d))))
| `Leaf(`RenderedDist(_)) as t => Ok(t) // already a rendered shape, we're done here
| `Operation(op) =>
E.R.bind(operationToLeaf(op), toLeaf(operationToLeaf, sampleCount))
| `SymbolicDist(d) =>
Ok(`RenderedDist(SymbolicDist.T.toShape(renderParams.sampleCount, d)))
| `RenderedDist(_) as t => Ok(t) // already a rendered shape, we're done here
| _ => t |> toLeaf(renderParams) |> E.R.bind(_, operationToLeaf(toLeaf, renderParams))
};
};
};
let rec operationToLeaf =
(sampleCount: int, op: operation): result(t, string) => {
// 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.toLeaf(
operationToLeaf(sampleCount),
algebraicOp,
t1,
t2 // we want to give it the option to render or simply leave it as is
)
| `PointwiseCombination(pointwiseOp, t1, t2) =>
PointwiseCombination.toLeaf(
operationToLeaf(sampleCount),
pointwiseOp,
t1,
t2,
)
| `VerticalScaling(scaleOp, t, scaleBy) =>
VerticalScaling.toLeaf(operationToLeaf(sampleCount), scaleOp, t, scaleBy)
| `Truncate(leftCutoff, rightCutoff, t) =>
Truncate.toLeaf(operationToLeaf(sampleCount), leftCutoff, rightCutoff, t)
| `FloatFromDist(distToFloatOp, t) =>
FloatFromDist.toLeaf(operationToLeaf(sampleCount), distToFloatOp, t)
| `Normalize(t) => Normalize.toLeaf(operationToLeaf(sampleCount), t)
| `Render(t) => Render.toLeaf(operationToLeaf(sampleCount), sampleCount, t)
};
};
/* 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 RenderedDist.
This function is used mainly to turn a parse tree into a single RenderedDist
that can then be displayed to the user. */
let toLeaf = (node: t, sampleCount: int): result(t, string) => {
let rec toLeaf = (renderParams, node: t): result(t, string) => {
switch (node) {
| `Leaf(d) => Ok(`Leaf(d))
| `Operation(op) => operationToLeaf(sampleCount, op)
// Leaf nodes just stay leaf nodes
| `SymbolicDist(_)
| `RenderedDist(_) => Ok(node)
// Operations need to be turned into leaves
| `AlgebraicCombination(algebraicOp, t1, t2) =>
AlgebraicCombination.operationToLeaf(
toLeaf,
renderParams,
algebraicOp,
t1,
t2
)
| `PointwiseCombination(pointwiseOp, t1, t2) =>
PointwiseCombination.operationToLeaf(
toLeaf,
renderParams,
pointwiseOp,
t1,
t2,
)
| `VerticalScaling(scaleOp, t, scaleBy) =>
VerticalScaling.operationToLeaf(
toLeaf, renderParams, scaleOp, t, scaleBy
)
| `Truncate(leftCutoff, rightCutoff, t) =>
Truncate.operationToLeaf(toLeaf, renderParams, leftCutoff, rightCutoff, t)
| `FloatFromDist(distToFloatOp, t) =>
FloatFromDist.operationToLeaf(toLeaf, renderParams, distToFloatOp, t)
| `Normalize(t) => Normalize.operationToLeaf(toLeaf, renderParams, t)
| `Render(t) => Render.operationToLeaf(toLeaf, renderParams, t)
};
};

View File

@ -3,22 +3,18 @@ type pointwiseOperation = [ | `Add | `Multiply];
type scaleOperation = [ | `Multiply | `Exponentiate | `Log];
type distToFloatOperation = [ | `Pdf(float) | `Inv(float) | `Mean | `Sample];
type abstractOperation('a) = [
| `AlgebraicCombination(algebraicOperation, 'a, 'a)
| `PointwiseCombination(pointwiseOperation, 'a, 'a)
| `VerticalScaling(scaleOperation, 'a, 'a)
| `Render('a)
| `Truncate(option(float), option(float), 'a)
| `Normalize('a)
| `FloatFromDist(distToFloatOperation, 'a)
];
module ExpressionTree = {
type leaf = [
type node = [
// leaf nodes:
| `SymbolicDist(SymbolicTypes.symbolicDist)
| `RenderedDist(DistTypes.shape)
// operations:
| `AlgebraicCombination(algebraicOperation, node, node)
| `PointwiseCombination(pointwiseOperation, node, node)
| `VerticalScaling(scaleOperation, node, node)
| `Render(node)
| `Truncate(option(float), option(float), node)
| `Normalize(node)
| `FloatFromDist(distToFloatOperation, node)
];
type node = [ | `Leaf(leaf) | `Operation(operation)]
and operation = abstractOperation(node);
};

View File

@ -86,29 +86,29 @@ module MathAdtToDistDst = {
);
};
let normal: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
let normal:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(mean), Value(stdev)|] =>
Ok(`Leaf(`SymbolicDist(`Normal({mean, stdev}))))
Ok(`SymbolicDist(`Normal({mean, stdev})))
| _ => Error("Wrong number of variables in normal distribution");
let lognormal: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
let lognormal:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(mu), Value(sigma)|] =>
Ok(`Leaf(`SymbolicDist(`Lognormal({mu, sigma}))))
Ok(`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(
`Leaf(
`SymbolicDist(
SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev),
),
`SymbolicDist(
SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev),
),
)
| (_, _, Some(Value(mu)), Some(Value(sigma))) =>
Ok(`Leaf(`SymbolicDist(`Lognormal({mu, sigma}))))
Ok(`SymbolicDist(`Lognormal({mu, sigma})))
| _ => Error("Lognormal distribution would need mean and stdev")
};
}
@ -117,51 +117,48 @@ module MathAdtToDistDst = {
let to_: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(low), Value(high)|] when low <= 0.0 && low < high => {
Ok(
`Leaf(
`SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high)),
),
);
Ok(`SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high)));
}
| [|Value(low), Value(high)|] when low < high => {
Ok(
`Leaf(
`SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high)),
),
`SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high)),
);
}
| [|Value(_), Value(_)|] =>
Error("Low value must be less than high value.")
| _ => Error("Wrong number of variables in lognormal distribution");
let uniform: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
let uniform:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(low), Value(high)|] =>
Ok(`Leaf(`SymbolicDist(`Uniform({low, high}))))
Ok(`SymbolicDist(`Uniform({low, high})))
| _ => Error("Wrong number of variables in lognormal distribution");
let beta: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(alpha), Value(beta)|] =>
Ok(`Leaf(`SymbolicDist(`Beta({alpha, beta}))))
Ok(`SymbolicDist(`Beta({alpha, beta})))
| _ => Error("Wrong number of variables in lognormal distribution");
let exponential: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
let exponential:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(rate)|] =>
Ok(`Leaf(`SymbolicDist(`Exponential({rate: rate}))))
| [|Value(rate)|] => Ok(`SymbolicDist(`Exponential({rate: rate})))
| _ => Error("Wrong number of variables in Exponential distribution");
let cauchy: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
let cauchy:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(local), Value(scale)|] =>
Ok(`Leaf(`SymbolicDist(`Cauchy({local, scale}))))
Ok(`SymbolicDist(`Cauchy({local, scale})))
| _ => Error("Wrong number of variables in cauchy distribution");
let triangular: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
let triangular:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(low), Value(medium), Value(high)|] =>
Ok(`Leaf(`SymbolicDist(`Triangular({low, medium, high}))))
Ok(`SymbolicDist(`Triangular({low, medium, high})))
| _ => Error("Wrong number of variables in triangle distribution");
let multiModal =
@ -192,30 +189,24 @@ module MathAdtToDistDst = {
|> E.A.fmapi((index, t) => {
let w = weights |> E.A.get(_, index) |> E.O.default(1.0);
`Operation(
`VerticalScaling((
`Multiply,
t,
`Leaf(`SymbolicDist(`Float(w))),
)),
);
`VerticalScaling((`Multiply, t, `SymbolicDist(`Float(w))));
});
let pointwiseSum =
components
|> Js.Array.sliceFrom(1)
|> E.A.fold_left(
(acc, x) => {
`Operation(`PointwiseCombination((`Add, acc, x)))
},
(acc, x) => {`PointwiseCombination((`Add, acc, x))},
E.A.unsafe_get(components, 0),
);
Ok(`Operation(`Normalize(pointwiseSum)));
Ok(`Normalize(pointwiseSum));
};
};
let arrayParser = (args: array(arg)): result(ExpressionTypes.ExpressionTree.node, string) => {
let arrayParser =
(args: array(arg))
: result(ExpressionTypes.ExpressionTree.node, string) => {
let samples =
args
|> E.A.fmap(
@ -235,15 +226,18 @@ module MathAdtToDistDst = {
SymbolicDist.ContinuousShape.make(_pdf, cdf);
});
switch (shape) {
| Some(s) => Ok(`Leaf(`SymbolicDist(`ContinuousShape(s))))
| Some(s) => Ok(`SymbolicDist(`ContinuousShape(s)))
| None => Error("Rendering did not work")
};
};
let operationParser =
(name: string, args: array(result(ExpressionTypes.ExpressionTree.node, string))) => {
let toOkAlgebraic = r => Ok(`Operation(`AlgebraicCombination(r)));
let toOkTrunctate = r => Ok(`Operation(`Truncate(r)));
(
name: string,
args: array(result(ExpressionTypes.ExpressionTree.node, string)),
) => {
let toOkAlgebraic = r => Ok(`AlgebraicCombination(r));
let toOkTrunctate = r => Ok(`Truncate(r));
switch (name, args) {
| ("add", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Add, l, r))
| ("add", _) => Error("Addition needs two operands")
@ -254,11 +248,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(`Leaf(`SymbolicDist(`Float(lc))))|]) =>
| ("leftTruncate", [|Ok(d), Ok(`SymbolicDist(`Float(lc)))|]) =>
toOkTrunctate((Some(lc), None, d))
| ("leftTruncate", _) =>
Error("leftTruncate needs two arguments: the expression and the cutoff")
| ("rightTruncate", [|Ok(d), Ok(`Leaf(`SymbolicDist(`Float(rc))))|]) =>
| ("rightTruncate", [|Ok(d), Ok(`SymbolicDist(`Float(rc)))|]) =>
toOkTrunctate((None, Some(rc), d))
| ("rightTruncate", _) =>
Error(
@ -268,8 +262,8 @@ module MathAdtToDistDst = {
"truncate",
[|
Ok(d),
Ok(`Leaf(`SymbolicDist(`Float(lc)))),
Ok(`Leaf(`SymbolicDist(`Float(rc)))),
Ok(`SymbolicDist(`Float(lc))),
Ok(`SymbolicDist(`Float(rc))),
|],
) =>
toOkTrunctate((Some(lc), Some(rc), d))
@ -333,7 +327,7 @@ module MathAdtToDistDst = {
let rec nodeParser =
fun
| Value(f) => Ok(`Leaf(`SymbolicDist(`Float(f))))
| Value(f) => Ok(`SymbolicDist(`Float(f)))
| Fn({name, args}) => functionParser(nodeParser, name, args)
| _ => {
Error("This type not currently supported");

View File

@ -89,5 +89,6 @@ module T = {
| `FloatFromDist(floatFromDistOp, t) =>
DistToFloat.format(floatFromDistOp, nodeToString(t))
| `Truncate(lc, rc, t) => truncateToString(lc, rc, nodeToString(t))
| `Render(t) => nodeToString(t);
| `Render(t) => nodeToString(t)
| _ => ""; // SymbolicDist and RenderedDist are handled in ExpressionTree.toString.
};

View File

@ -269,23 +269,23 @@ module T = {
};
};
/* This returns an optional that wraps a result. If the optional is None,
there is no valid analytic solution. If it Some, it
/* Calling e.g. "Normal.operate" 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.
like in the case of a divide by 0.
*/
type analyticalSolutionAttempt = [
type analyticalSimplificationResult = [
| `AnalyticalSolution(SymbolicTypes.symbolicDist)
| `Error(string)
| `NoSolution
];
let attemptAnalyticalOperation =
let tryAnalyticalSimplification =
(
d1: symbolicDist,
d2: symbolicDist,
op: ExpressionTypes.algebraicOperation,
)
: analyticalSolutionAttempt =>
: analyticalSimplificationResult =>
switch (d1, d2) {
| (`Float(v1), `Float(v2)) =>
switch (Operation.Algebraic.applyFn(op, v1, v2)) {