Set up new expressionTree directory
This commit is contained in:
parent
19e9eaee83
commit
41eca03618
|
@ -383,9 +383,9 @@ describe("Shape", () => {
|
||||||
let numSamples = 10000;
|
let numSamples = 10000;
|
||||||
open Distributions.Shape;
|
open Distributions.Shape;
|
||||||
let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
|
let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
|
||||||
let normalShape = TreeNode.toShape(numSamples, `Leaf(`SymbolicDist(normal)));
|
let normalShape = ExpressionTree.toShape(numSamples, `Leaf(`SymbolicDist(normal)));
|
||||||
let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev);
|
let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev);
|
||||||
let lognormalShape = TreeNode.toShape(numSamples, `Leaf(`SymbolicDist(lognormal)));
|
let lognormalShape = ExpressionTree.toShape(numSamples, `Leaf(`SymbolicDist(lognormal)));
|
||||||
|
|
||||||
makeTestCloseEquality(
|
makeTestCloseEquality(
|
||||||
"Mean of a normal",
|
"Mean of a normal",
|
||||||
|
|
|
@ -37,13 +37,13 @@ module DemoDist = {
|
||||||
let parsed1 = MathJsParser.fromString(guesstimatorString);
|
let parsed1 = MathJsParser.fromString(guesstimatorString);
|
||||||
let shape =
|
let shape =
|
||||||
switch (parsed1) {
|
switch (parsed1) {
|
||||||
| Ok(r) => Some(TreeNode.toShape(10000, r))
|
| Ok(r) => Some(ExpressionTree.toShape(10000, r))
|
||||||
| _ => None
|
| _ => None
|
||||||
};
|
};
|
||||||
|
|
||||||
let str =
|
let str =
|
||||||
switch (parsed1) {
|
switch (parsed1) {
|
||||||
| Ok(r) => TreeNode.toString(r)
|
| Ok(r) => ExpressionTree.toString(r)
|
||||||
| Error(e) => e
|
| Error(e) => e
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -389,7 +389,7 @@ module Draw = {
|
||||||
let numSamples = 3000;
|
let numSamples = 3000;
|
||||||
|
|
||||||
let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
|
let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
|
||||||
let normalShape = TreeNode.toShape(numSamples, `Leaf(`SymbolicDist(normal)));
|
let normalShape = ExpressionTree.toShape(numSamples, `Leaf(`SymbolicDist(normal)));
|
||||||
let xyShape: Types.xyShape =
|
let xyShape: Types.xyShape =
|
||||||
switch (normalShape) {
|
switch (normalShape) {
|
||||||
| Mixed(_) => {xs: [||], ys: [||]}
|
| Mixed(_) => {xs: [||], ys: [||]}
|
||||||
|
|
|
@ -110,7 +110,7 @@ let toDiscretePointMassesFromTriangulars =
|
||||||
};
|
};
|
||||||
|
|
||||||
let combineShapesContinuousContinuous =
|
let combineShapesContinuousContinuous =
|
||||||
(op: SymbolicTypes.algebraicOperation, s1: DistTypes.xyShape, s2: DistTypes.xyShape)
|
(op: ExpressionTypes.algebraicOperation, s1: DistTypes.xyShape, s2: DistTypes.xyShape)
|
||||||
: DistTypes.xyShape => {
|
: DistTypes.xyShape => {
|
||||||
let t1n = s1 |> XYShape.T.length;
|
let t1n = s1 |> XYShape.T.length;
|
||||||
let t2n = s2 |> XYShape.T.length;
|
let t2n = s2 |> XYShape.T.length;
|
||||||
|
|
|
@ -282,7 +282,7 @@ module Continuous = {
|
||||||
let combineAlgebraicallyWithDiscrete =
|
let combineAlgebraicallyWithDiscrete =
|
||||||
(
|
(
|
||||||
~downsample=false,
|
~downsample=false,
|
||||||
op: SymbolicTypes.algebraicOperation,
|
op: ExpressionTypes.algebraicOperation,
|
||||||
t1: t,
|
t1: t,
|
||||||
t2: DistTypes.discreteShape,
|
t2: DistTypes.discreteShape,
|
||||||
) => {
|
) => {
|
||||||
|
@ -291,7 +291,7 @@ module Continuous = {
|
||||||
let t1n = t1s |> XYShape.T.length;
|
let t1n = t1s |> XYShape.T.length;
|
||||||
let t2n = t2s |> XYShape.T.length;
|
let t2n = t2s |> XYShape.T.length;
|
||||||
|
|
||||||
let fn = SymbolicTypes.Algebraic.toFn(op);
|
let fn = Operation.Algebraic.toFn(op);
|
||||||
|
|
||||||
let outXYShapes: array(array((float, float))) =
|
let outXYShapes: array(array((float, float))) =
|
||||||
Belt.Array.makeUninitializedUnsafe(t2n);
|
Belt.Array.makeUninitializedUnsafe(t2n);
|
||||||
|
@ -333,7 +333,7 @@ module Continuous = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let combineAlgebraically =
|
let combineAlgebraically =
|
||||||
(~downsample=false, op: SymbolicTypes.algebraicOperation, t1: t, t2: t) => {
|
(~downsample=false, op: ExpressionTypes.algebraicOperation, t1: t, t2: t) => {
|
||||||
let s1 = t1 |> getShape;
|
let s1 = t1 |> getShape;
|
||||||
let s2 = t2 |> getShape;
|
let s2 = t2 |> getShape;
|
||||||
let t1n = s1 |> XYShape.T.length;
|
let t1n = s1 |> XYShape.T.length;
|
||||||
|
@ -413,7 +413,7 @@ module Discrete = {
|
||||||
/* This multiples all of the data points together and creates a new discrete distribution from the results.
|
/* This multiples all of the data points together and creates a new discrete distribution from the results.
|
||||||
Data points at the same xs get added together. It may be a good idea to downsample t1 and t2 before and/or the result after. */
|
Data points at the same xs get added together. It may be a good idea to downsample t1 and t2 before and/or the result after. */
|
||||||
let combineAlgebraically =
|
let combineAlgebraically =
|
||||||
(op: SymbolicTypes.algebraicOperation, t1: t, t2: t) => {
|
(op: ExpressionTypes.algebraicOperation, t1: t, t2: t) => {
|
||||||
let t1s = t1 |> getShape;
|
let t1s = t1 |> getShape;
|
||||||
let t2s = t2 |> getShape;
|
let t2s = t2 |> getShape;
|
||||||
let t1n = t1s |> XYShape.T.length;
|
let t1n = t1s |> XYShape.T.length;
|
||||||
|
@ -426,7 +426,7 @@ module Discrete = {
|
||||||
t2.knownIntegralSum,
|
t2.knownIntegralSum,
|
||||||
);
|
);
|
||||||
|
|
||||||
let fn = SymbolicTypes.Algebraic.toFn(op);
|
let fn = Operation.Algebraic.toFn(op);
|
||||||
let xToYMap = E.FloatFloatMap.empty();
|
let xToYMap = E.FloatFloatMap.empty();
|
||||||
|
|
||||||
for (i in 0 to t1n - 1) {
|
for (i in 0 to t1n - 1) {
|
||||||
|
@ -840,7 +840,7 @@ module Mixed = {
|
||||||
});
|
});
|
||||||
|
|
||||||
let combineAlgebraically =
|
let combineAlgebraically =
|
||||||
(~downsample=false, op: SymbolicTypes.algebraicOperation, t1: t, t2: t)
|
(~downsample=false, op: ExpressionTypes.algebraicOperation, t1: t, t2: t)
|
||||||
: t => {
|
: t => {
|
||||||
// Discrete convolution can cause a huge increase in the number of samples,
|
// Discrete convolution can cause a huge increase in the number of samples,
|
||||||
// so we'll first downsample.
|
// so we'll first downsample.
|
||||||
|
@ -914,7 +914,7 @@ module Shape = {
|
||||||
));
|
));
|
||||||
|
|
||||||
let combineAlgebraically =
|
let combineAlgebraically =
|
||||||
(op: SymbolicTypes.algebraicOperation, t1: t, t2: t): t => {
|
(op: ExpressionTypes.algebraicOperation, t1: t, t2: t): t => {
|
||||||
switch (t1, t2) {
|
switch (t1, t2) {
|
||||||
| (Continuous(m1), Continuous(m2)) =>
|
| (Continuous(m1), Continuous(m2)) =>
|
||||||
DistTypes.Continuous(
|
DistTypes.Continuous(
|
||||||
|
@ -1096,7 +1096,7 @@ module Shape = {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let operate = (distToFloatOp: SymbolicTypes.distToFloatOperation, s) =>
|
let operate = (distToFloatOp: ExpressionTypes.distToFloatOperation, s) =>
|
||||||
switch (distToFloatOp) {
|
switch (distToFloatOp) {
|
||||||
| `Pdf(f) => pdf(f, s)
|
| `Pdf(f) => pdf(f, s)
|
||||||
| `Inv(f) => inv(f, s)
|
| `Inv(f) => inv(f, s)
|
||||||
|
|
22
src/distPlus/expressionTree/ExpressionTree.re
Normal file
22
src/distPlus/expressionTree/ExpressionTree.re
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
open ExpressionTypes.ExpressionTree;
|
||||||
|
|
||||||
|
let toShape = (sampleCount: int, node: node) => {
|
||||||
|
let renderResult =
|
||||||
|
ExpressionTreeEvaluator.toLeaf(`Operation(`Render(node)), sampleCount);
|
||||||
|
|
||||||
|
switch (renderResult) {
|
||||||
|
| Ok(`Leaf(`RenderedDist(rs))) =>
|
||||||
|
let continuous = Distributions.Shape.T.toContinuous(rs);
|
||||||
|
let discrete = Distributions.Shape.T.toDiscrete(rs);
|
||||||
|
let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete);
|
||||||
|
shape |> E.O.toExt("Could not build final shape.");
|
||||||
|
| Ok(_) => E.O.toExn("Rendering failed.", None)
|
||||||
|
| Error(message) => E.O.toExn("No shape found, error: " ++ message, None)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let rec toString =
|
||||||
|
fun
|
||||||
|
| `Leaf(`SymbolicDist(d)) => SymbolicDist.T.toString(d)
|
||||||
|
| `Leaf(`RenderedDist(_)) => "[shape]"
|
||||||
|
| `Operation(op) => Operation.T.toString(toString, op);
|
294
src/distPlus/expressionTree/ExpressionTreeEvaluator.re
Normal file
294
src/distPlus/expressionTree/ExpressionTreeEvaluator.re
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
/* This module represents a tree node. */
|
||||||
|
open ExpressionTypes;
|
||||||
|
open ExpressionTypes.ExpressionTree;
|
||||||
|
|
||||||
|
type t = node;
|
||||||
|
type tResult = node => result(node, string);
|
||||||
|
|
||||||
|
/* 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)))
|
||||||
|
| `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, operationToLeaf, t1, t2) => {
|
||||||
|
// force rendering into shapes
|
||||||
|
let renderShape = r => operationToLeaf(`Render(r));
|
||||||
|
switch (renderShape(t1), renderShape(t2)) {
|
||||||
|
| (Ok(`Leaf(`RenderedDist(s1))), Ok(`Leaf(`RenderedDist(s2)))) =>
|
||||||
|
Ok(
|
||||||
|
`Leaf(
|
||||||
|
`RenderedDist(
|
||||||
|
Distributions.Shape.combineAlgebraically(algebraicOp, s1, s2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
| (Error(e1), _) => Error(e1)
|
||||||
|
| (_, Error(e2)) => Error(e2)
|
||||||
|
| _ => Error("Could not render shapes.")
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let toLeaf =
|
||||||
|
(
|
||||||
|
operationToLeaf,
|
||||||
|
algebraicOp: ExpressionTypes.algebraicOperation,
|
||||||
|
t1: t,
|
||||||
|
t2: t,
|
||||||
|
)
|
||||||
|
: result(node, string) =>
|
||||||
|
toTreeNode(algebraicOp, t1, t2)
|
||||||
|
|> tryAnalyticalSolution
|
||||||
|
|> 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),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module VerticalScaling = {
|
||||||
|
let toLeaf = (operationToLeaf, 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));
|
||||||
|
|
||||||
|
switch (renderedShape, scaleBy) {
|
||||||
|
| (Ok(`Leaf(`RenderedDist(rs))), `Leaf(`SymbolicDist(`Float(sm)))) =>
|
||||||
|
Ok(
|
||||||
|
`Leaf(
|
||||||
|
`RenderedDist(
|
||||||
|
Distributions.Shape.T.mapY(
|
||||||
|
~knownIntegralSumFn=knownIntegralSumFn(sm),
|
||||||
|
fn(sm),
|
||||||
|
rs,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
| (Error(e1), _) => Error(e1)
|
||||||
|
| (_, _) => Error("Can only scale by float values.")
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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)))) =>
|
||||||
|
Ok(
|
||||||
|
`Leaf(
|
||||||
|
`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.")
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let pointwiseMultiply = (operationToLeaf, 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(
|
||||||
|
"Pointwise multiplication not yet supported.",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let toLeaf = (operationToLeaf, pointwiseOp, t1, t2) => {
|
||||||
|
switch (pointwiseOp) {
|
||||||
|
| `Add => pointwiseAdd(operationToLeaf, t1, t2)
|
||||||
|
| `Multiply => pointwiseMultiply(operationToLeaf, 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 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 = operationToLeaf(`Render(t));
|
||||||
|
|
||||||
|
switch (renderedShape) {
|
||||||
|
| Ok(`Leaf(`RenderedDist(rs))) =>
|
||||||
|
let truncatedShape =
|
||||||
|
rs |> Distributions.Shape.T.truncate(leftCutoff, rightCutoff);
|
||||||
|
Ok(`Leaf(`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) => {
|
||||||
|
t
|
||||||
|
|> Simplify.attempt(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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Normalize = {
|
||||||
|
let rec toLeaf = (operationToLeaf, 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))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module FloatFromDist = {
|
||||||
|
let symbolicToLeaf = (distToFloatOp: distToFloatOperation, s) => {
|
||||||
|
SymbolicDist.T.operate(distToFloatOp, s)
|
||||||
|
|> E.R.bind(_, v => Ok(`Leaf(`SymbolicDist(`Float(v)))));
|
||||||
|
};
|
||||||
|
let renderedToLeaf =
|
||||||
|
(distToFloatOp: distToFloatOperation, rs: DistTypes.shape)
|
||||||
|
: result(node, string) => {
|
||||||
|
Distributions.Shape.operate(distToFloatOp, rs)
|
||||||
|
|> (v => Ok(`Leaf(`SymbolicDist(`Float(v)))));
|
||||||
|
};
|
||||||
|
let rec toLeaf =
|
||||||
|
(operationToLeaf, 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))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Render = {
|
||||||
|
let rec toLeaf =
|
||||||
|
(
|
||||||
|
operationToLeaf: operation => result(t, string),
|
||||||
|
sampleCount: int,
|
||||||
|
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))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
switch (node) {
|
||||||
|
| `Leaf(d) => Ok(`Leaf(d))
|
||||||
|
| `Operation(op) => operationToLeaf(sampleCount, op)
|
||||||
|
};
|
||||||
|
};
|
24
src/distPlus/expressionTree/ExpressionTypes.re
Normal file
24
src/distPlus/expressionTree/ExpressionTypes.re
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
type algebraicOperation = [ | `Add | `Multiply | `Subtract | `Divide];
|
||||||
|
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 = [
|
||||||
|
| `SymbolicDist(SymbolicTypes.symbolicDist)
|
||||||
|
| `RenderedDist(DistTypes.shape)
|
||||||
|
];
|
||||||
|
|
||||||
|
type node = [ | `Leaf(leaf) | `Operation(operation)]
|
||||||
|
and operation = abstractOperation(node);
|
||||||
|
};
|
|
@ -86,13 +86,13 @@ module MathAdtToDistDst = {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let normal: array(arg) => result(TreeNode.treeNode, string) =
|
let normal: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
|
||||||
fun
|
fun
|
||||||
| [|Value(mean), Value(stdev)|] =>
|
| [|Value(mean), Value(stdev)|] =>
|
||||||
Ok(`Leaf(`SymbolicDist(`Normal({mean, stdev}))))
|
Ok(`Leaf(`SymbolicDist(`Normal({mean, stdev}))))
|
||||||
| _ => Error("Wrong number of variables in normal distribution");
|
| _ => Error("Wrong number of variables in normal distribution");
|
||||||
|
|
||||||
let lognormal: array(arg) => result(TreeNode.treeNode, string) =
|
let lognormal: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
|
||||||
fun
|
fun
|
||||||
| [|Value(mu), Value(sigma)|] =>
|
| [|Value(mu), Value(sigma)|] =>
|
||||||
Ok(`Leaf(`SymbolicDist(`Lognormal({mu, sigma}))))
|
Ok(`Leaf(`SymbolicDist(`Lognormal({mu, sigma}))))
|
||||||
|
@ -114,7 +114,7 @@ module MathAdtToDistDst = {
|
||||||
}
|
}
|
||||||
| _ => Error("Wrong number of variables in lognormal distribution");
|
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||||
|
|
||||||
let to_: array(arg) => result(TreeNode.treeNode, string) =
|
let to_: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
|
||||||
fun
|
fun
|
||||||
| [|Value(low), Value(high)|] when low <= 0.0 && low < high => {
|
| [|Value(low), Value(high)|] when low <= 0.0 && low < high => {
|
||||||
Ok(
|
Ok(
|
||||||
|
@ -134,31 +134,31 @@ module MathAdtToDistDst = {
|
||||||
Error("Low value must be less than high value.")
|
Error("Low value must be less than high value.")
|
||||||
| _ => Error("Wrong number of variables in lognormal distribution");
|
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||||
|
|
||||||
let uniform: array(arg) => result(TreeNode.treeNode, string) =
|
let uniform: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
|
||||||
fun
|
fun
|
||||||
| [|Value(low), Value(high)|] =>
|
| [|Value(low), Value(high)|] =>
|
||||||
Ok(`Leaf(`SymbolicDist(`Uniform({low, high}))))
|
Ok(`Leaf(`SymbolicDist(`Uniform({low, high}))))
|
||||||
| _ => Error("Wrong number of variables in lognormal distribution");
|
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||||
|
|
||||||
let beta: array(arg) => result(TreeNode.treeNode, string) =
|
let beta: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
|
||||||
fun
|
fun
|
||||||
| [|Value(alpha), Value(beta)|] =>
|
| [|Value(alpha), Value(beta)|] =>
|
||||||
Ok(`Leaf(`SymbolicDist(`Beta({alpha, beta}))))
|
Ok(`Leaf(`SymbolicDist(`Beta({alpha, beta}))))
|
||||||
| _ => Error("Wrong number of variables in lognormal distribution");
|
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||||
|
|
||||||
let exponential: array(arg) => result(TreeNode.treeNode, string) =
|
let exponential: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
|
||||||
fun
|
fun
|
||||||
| [|Value(rate)|] =>
|
| [|Value(rate)|] =>
|
||||||
Ok(`Leaf(`SymbolicDist(`Exponential({rate: rate}))))
|
Ok(`Leaf(`SymbolicDist(`Exponential({rate: rate}))))
|
||||||
| _ => Error("Wrong number of variables in Exponential distribution");
|
| _ => Error("Wrong number of variables in Exponential distribution");
|
||||||
|
|
||||||
let cauchy: array(arg) => result(TreeNode.treeNode, string) =
|
let cauchy: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
|
||||||
fun
|
fun
|
||||||
| [|Value(local), Value(scale)|] =>
|
| [|Value(local), Value(scale)|] =>
|
||||||
Ok(`Leaf(`SymbolicDist(`Cauchy({local, scale}))))
|
Ok(`Leaf(`SymbolicDist(`Cauchy({local, scale}))))
|
||||||
| _ => Error("Wrong number of variables in cauchy distribution");
|
| _ => Error("Wrong number of variables in cauchy distribution");
|
||||||
|
|
||||||
let triangular: array(arg) => result(TreeNode.treeNode, string) =
|
let triangular: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
|
||||||
fun
|
fun
|
||||||
| [|Value(low), Value(medium), Value(high)|] =>
|
| [|Value(low), Value(medium), Value(high)|] =>
|
||||||
Ok(`Leaf(`SymbolicDist(`Triangular({low, medium, high}))))
|
Ok(`Leaf(`SymbolicDist(`Triangular({low, medium, high}))))
|
||||||
|
@ -166,7 +166,7 @@ module MathAdtToDistDst = {
|
||||||
|
|
||||||
let multiModal =
|
let multiModal =
|
||||||
(
|
(
|
||||||
args: array(result(TreeNode.treeNode, string)),
|
args: array(result(ExpressionTypes.ExpressionTree.node, string)),
|
||||||
weights: option(array(float)),
|
weights: option(array(float)),
|
||||||
) => {
|
) => {
|
||||||
let weights = weights |> E.O.default([||]);
|
let weights = weights |> E.O.default([||]);
|
||||||
|
@ -215,7 +215,7 @@ module MathAdtToDistDst = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
let arrayParser = (args: array(arg)): result(TreeNode.treeNode, string) => {
|
let arrayParser = (args: array(arg)): result(ExpressionTypes.ExpressionTree.node, string) => {
|
||||||
let samples =
|
let samples =
|
||||||
args
|
args
|
||||||
|> E.A.fmap(
|
|> E.A.fmap(
|
||||||
|
@ -241,7 +241,7 @@ module MathAdtToDistDst = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let operationParser =
|
let operationParser =
|
||||||
(name: string, args: array(result(TreeNode.treeNode, string))) => {
|
(name: string, args: array(result(ExpressionTypes.ExpressionTree.node, string))) => {
|
||||||
let toOkAlgebraic = r => Ok(`Operation(`AlgebraicCombination(r)));
|
let toOkAlgebraic = r => Ok(`Operation(`AlgebraicCombination(r)));
|
||||||
let toOkTrunctate = r => Ok(`Operation(`Truncate(r)));
|
let toOkTrunctate = r => Ok(`Operation(`Truncate(r)));
|
||||||
switch (name, args) {
|
switch (name, args) {
|
||||||
|
@ -347,7 +347,7 @@ module MathAdtToDistDst = {
|
||||||
| Symbol(_) => Error("Symbol not valid as top level")
|
| Symbol(_) => Error("Symbol not valid as top level")
|
||||||
| Object(_) => Error("Object not valid as top level");
|
| Object(_) => Error("Object not valid as top level");
|
||||||
|
|
||||||
let run = (r): result(TreeNode.treeNode, string) =>
|
let run = (r): result(ExpressionTypes.ExpressionTree.node, string) =>
|
||||||
r |> MathAdtCleaner.run |> topLevel;
|
r |> MathAdtCleaner.run |> topLevel;
|
||||||
};
|
};
|
||||||
|
|
93
src/distPlus/expressionTree/Operation.re
Normal file
93
src/distPlus/expressionTree/Operation.re
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
open ExpressionTypes;
|
||||||
|
|
||||||
|
module Algebraic = {
|
||||||
|
type t = algebraicOperation;
|
||||||
|
let toFn: (t, float, float) => float =
|
||||||
|
fun
|
||||||
|
| `Add => (+.)
|
||||||
|
| `Subtract => (-.)
|
||||||
|
| `Multiply => ( *. )
|
||||||
|
| `Divide => (/.);
|
||||||
|
|
||||||
|
let applyFn = (t, f1, f2) => {
|
||||||
|
switch (t, f1, f2) {
|
||||||
|
| (`Divide, _, 0.) => Error("Cannot divide $v1 by zero.")
|
||||||
|
| _ => Ok(toFn(t, f1, f2))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let toString =
|
||||||
|
fun
|
||||||
|
| `Add => "+"
|
||||||
|
| `Subtract => "-"
|
||||||
|
| `Multiply => "*"
|
||||||
|
| `Divide => "/";
|
||||||
|
|
||||||
|
let format = (a, b, c) => b ++ " " ++ toString(a) ++ " " ++ c;
|
||||||
|
};
|
||||||
|
|
||||||
|
module Pointwise = {
|
||||||
|
type t = pointwiseOperation;
|
||||||
|
let toString =
|
||||||
|
fun
|
||||||
|
| `Add => "+"
|
||||||
|
| `Multiply => "*";
|
||||||
|
|
||||||
|
let format = (a, b, c) => b ++ " " ++ toString(a) ++ " " ++ c;
|
||||||
|
};
|
||||||
|
|
||||||
|
module DistToFloat = {
|
||||||
|
type t = distToFloatOperation;
|
||||||
|
|
||||||
|
let format = (operation, value) =>
|
||||||
|
switch (operation) {
|
||||||
|
| `Pdf(f) => {j|pdf(x=$f,$value)|j}
|
||||||
|
| `Inv(f) => {j|inv(x=$f,$value)|j}
|
||||||
|
| `Sample => "sample($value)"
|
||||||
|
| `Mean => "mean($value)"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Scale = {
|
||||||
|
type t = scaleOperation;
|
||||||
|
let toFn =
|
||||||
|
fun
|
||||||
|
| `Multiply => ( *. )
|
||||||
|
| `Exponentiate => ( ** )
|
||||||
|
| `Log => ((a, b) => log(a) /. log(b));
|
||||||
|
|
||||||
|
let format = (operation: t, value, scaleBy) =>
|
||||||
|
switch (operation) {
|
||||||
|
| `Multiply => {j|scaleMultiply($value, $scaleBy) |j}
|
||||||
|
| `Exponentiate => {j|ScaleExponentiate($value, $scaleBy) |j}
|
||||||
|
| `Log => {j|ScaleLog($value, $scaleBy) |j}
|
||||||
|
};
|
||||||
|
|
||||||
|
let toKnownIntegralSumFn =
|
||||||
|
fun
|
||||||
|
| `Multiply => ((a, b) => Some(a *. b))
|
||||||
|
| `Exponentiate => ((_, _) => None)
|
||||||
|
| `Log => ((_, _) => None);
|
||||||
|
};
|
||||||
|
|
||||||
|
module T = {
|
||||||
|
let truncateToString =
|
||||||
|
(left: option(float), right: option(float), nodeToString) => {
|
||||||
|
let left = left |> E.O.dimap(Js.Float.toString, () => "-inf");
|
||||||
|
let right = right |> E.O.dimap(Js.Float.toString, () => "inf");
|
||||||
|
{j|truncate($nodeToString, $left, $right)|j};
|
||||||
|
};
|
||||||
|
let toString = nodeToString =>
|
||||||
|
fun
|
||||||
|
| `AlgebraicCombination(op, t1, t2) =>
|
||||||
|
Algebraic.format(op, nodeToString(t1), nodeToString(t2))
|
||||||
|
| `PointwiseCombination(op, t1, t2) =>
|
||||||
|
Pointwise.format(op, nodeToString(t1), nodeToString(t2))
|
||||||
|
| `VerticalScaling(scaleOp, t, scaleBy) =>
|
||||||
|
Scale.format(scaleOp, nodeToString(t), nodeToString(scaleBy))
|
||||||
|
| `Normalize(t) => "normalize(" ++ nodeToString(t) ++ ")"
|
||||||
|
| `FloatFromDist(floatFromDistOp, t) =>
|
||||||
|
DistToFloat.format(floatFromDistOp, nodeToString(t))
|
||||||
|
| `Truncate(lc, rc, t) => truncateToString(lc, rc, nodeToString(t))
|
||||||
|
| `Render(t) => nodeToString(t);
|
||||||
|
};
|
|
@ -43,7 +43,7 @@ module ShapeRenderer = {
|
||||||
module Symbolic = {
|
module Symbolic = {
|
||||||
type inputs = {length: int};
|
type inputs = {length: int};
|
||||||
type outputs = {
|
type outputs = {
|
||||||
graph: TreeNode.treeNode,
|
graph: ExpressionTypes.ExpressionTree.node,
|
||||||
shape: DistTypes.shape,
|
shape: DistTypes.shape,
|
||||||
};
|
};
|
||||||
let make = (graph, shape) => {graph, shape};
|
let make = (graph, shape) => {graph, shape};
|
||||||
|
|
|
@ -21,7 +21,7 @@ let runSymbolic = (guesstimatorString, length) => {
|
||||||
|> E.R.fmap(g =>
|
|> E.R.fmap(g =>
|
||||||
RenderTypes.ShapeRenderer.Symbolic.make(
|
RenderTypes.ShapeRenderer.Symbolic.make(
|
||||||
g,
|
g,
|
||||||
TreeNode.toShape(length, g),
|
ExpressionTree.toShape(length, g),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
const math = require("mathjs");
|
|
||||||
|
|
||||||
function parseMath(f){ return JSON.parse(JSON.stringify(math.parse(f))) };
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
parseMath,
|
|
||||||
};
|
|
|
@ -76,7 +76,7 @@ module Normal = {
|
||||||
`Normal({mean, stdev});
|
`Normal({mean, stdev});
|
||||||
};
|
};
|
||||||
|
|
||||||
let operate = (operation: SymbolicTypes.Algebraic.t, n1: t, n2: t) =>
|
let operate = (operation: Operation.Algebraic.t, n1: t, n2: t) =>
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
| `Add => Some(add(n1, n2))
|
| `Add => Some(add(n1, n2))
|
||||||
| `Subtract => Some(subtract(n1, n2))
|
| `Subtract => Some(subtract(n1, n2))
|
||||||
|
@ -130,7 +130,7 @@ module Lognormal = {
|
||||||
let sigma = l1.sigma +. l2.sigma;
|
let sigma = l1.sigma +. l2.sigma;
|
||||||
`Lognormal({mu, sigma});
|
`Lognormal({mu, sigma});
|
||||||
};
|
};
|
||||||
let operate = (operation: SymbolicTypes.Algebraic.t, n1: t, n2: t) =>
|
let operate = (operation: Operation.Algebraic.t, n1: t, n2: t) =>
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
| `Multiply => Some(multiply(n1, n2))
|
| `Multiply => Some(multiply(n1, n2))
|
||||||
| `Divide => Some(divide(n1, n2))
|
| `Divide => Some(divide(n1, n2))
|
||||||
|
@ -246,7 +246,7 @@ module T = {
|
||||||
| `Uniform(n) => Uniform.mean(n)
|
| `Uniform(n) => Uniform.mean(n)
|
||||||
| `Float(n) => Float.mean(n);
|
| `Float(n) => Float.mean(n);
|
||||||
|
|
||||||
let operate = (distToFloatOp: distToFloatOperation, s) =>
|
let operate = (distToFloatOp: ExpressionTypes.distToFloatOperation, s) =>
|
||||||
switch (distToFloatOp) {
|
switch (distToFloatOp) {
|
||||||
| `Pdf(f) => Ok(pdf(f, s))
|
| `Pdf(f) => Ok(pdf(f, s))
|
||||||
| `Inv(f) => Ok(inv(f, s))
|
| `Inv(f) => Ok(inv(f, s))
|
||||||
|
@ -283,12 +283,12 @@ module T = {
|
||||||
(
|
(
|
||||||
d1: symbolicDist,
|
d1: symbolicDist,
|
||||||
d2: symbolicDist,
|
d2: symbolicDist,
|
||||||
op: SymbolicTypes.algebraicOperation,
|
op: ExpressionTypes.algebraicOperation,
|
||||||
)
|
)
|
||||||
: analyticalSolutionAttempt =>
|
: analyticalSolutionAttempt =>
|
||||||
switch (d1, d2) {
|
switch (d1, d2) {
|
||||||
| (`Float(v1), `Float(v2)) =>
|
| (`Float(v1), `Float(v2)) =>
|
||||||
switch (SymbolicTypes.Algebraic.applyFn(op, v1, v2)) {
|
switch (Operation.Algebraic.applyFn(op, v1, v2)) {
|
||||||
| Ok(r) => `AnalyticalSolution(`Float(r))
|
| Ok(r) => `AnalyticalSolution(`Float(r))
|
||||||
| Error(n) => `Error(n)
|
| Error(n) => `Error(n)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,79 +47,3 @@ type symbolicDist = [
|
||||||
| `ContinuousShape(continuousShape)
|
| `ContinuousShape(continuousShape)
|
||||||
| `Float(float) // Dirac delta at x. Practically useful only in the context of multimodals.
|
| `Float(float) // Dirac delta at x. Practically useful only in the context of multimodals.
|
||||||
];
|
];
|
||||||
|
|
||||||
// todo: These operations are really applicable for all dists
|
|
||||||
type algebraicOperation = [ | `Add | `Multiply | `Subtract | `Divide];
|
|
||||||
type pointwiseOperation = [ | `Add | `Multiply];
|
|
||||||
type scaleOperation = [ | `Multiply | `Exponentiate | `Log];
|
|
||||||
type distToFloatOperation = [ | `Pdf(float) | `Inv(float) | `Mean | `Sample];
|
|
||||||
|
|
||||||
module Algebraic = {
|
|
||||||
type t = algebraicOperation;
|
|
||||||
let toFn: (t, float, float) => float =
|
|
||||||
fun
|
|
||||||
| `Add => (+.)
|
|
||||||
| `Subtract => (-.)
|
|
||||||
| `Multiply => ( *. )
|
|
||||||
| `Divide => (/.);
|
|
||||||
|
|
||||||
let applyFn = (t, f1, f2) => {
|
|
||||||
switch (t, f1, f2) {
|
|
||||||
| (`Divide, _, 0.) => Error("Cannot divide $v1 by zero.")
|
|
||||||
| _ => Ok(toFn(t, f1, f2))
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let toString =
|
|
||||||
fun
|
|
||||||
| `Add => "+"
|
|
||||||
| `Subtract => "-"
|
|
||||||
| `Multiply => "*"
|
|
||||||
| `Divide => "/";
|
|
||||||
|
|
||||||
let format = (a, b, c) => b ++ " " ++ toString(a) ++ " " ++ c;
|
|
||||||
};
|
|
||||||
|
|
||||||
module Pointwise = {
|
|
||||||
type t = pointwiseOperation;
|
|
||||||
let toString =
|
|
||||||
fun
|
|
||||||
| `Add => "+"
|
|
||||||
| `Multiply => "*";
|
|
||||||
|
|
||||||
let format = (a, b, c) => b ++ " " ++ toString(a) ++ " " ++ c;
|
|
||||||
};
|
|
||||||
|
|
||||||
module DistToFloat = {
|
|
||||||
type t = distToFloatOperation;
|
|
||||||
|
|
||||||
let format = (operation, value) =>
|
|
||||||
switch (operation) {
|
|
||||||
| `Pdf(f) => {j|pdf(x=$f,$value)|j}
|
|
||||||
| `Inv(f) => {j|inv(x=$f,$value)|j}
|
|
||||||
| `Sample => "sample($value)"
|
|
||||||
| `Mean => "mean($value)"
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module Scale = {
|
|
||||||
type t = scaleOperation;
|
|
||||||
let toFn =
|
|
||||||
fun
|
|
||||||
| `Multiply => ( *. )
|
|
||||||
| `Exponentiate => ( ** )
|
|
||||||
| `Log => ((a, b) => log(a) /. log(b));
|
|
||||||
|
|
||||||
let format = (operation:t, value, scaleBy) =>
|
|
||||||
switch (operation) {
|
|
||||||
| `Multiply => {j|scaleMultiply($value, $scaleBy) |j}
|
|
||||||
| `Exponentiate => {j|ScaleExponentiate($value, $scaleBy) |j}
|
|
||||||
| `Log => {j|ScaleLog($value, $scaleBy) |j}
|
|
||||||
};
|
|
||||||
|
|
||||||
let toKnownIntegralSumFn =
|
|
||||||
fun
|
|
||||||
| `Multiply => ((a, b) => Some(a *. b))
|
|
||||||
| `Exponentiate => ((_, _) => None)
|
|
||||||
| `Log => ((_, _) => None);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,387 +0,0 @@
|
||||||
/* This module represents a tree node. */
|
|
||||||
open SymbolicTypes;
|
|
||||||
|
|
||||||
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 = [ | `Leaf(leaf) | `Operation(operation)]
|
|
||||||
and operation = [
|
|
||||||
| `AlgebraicCombination(algebraicOperation, treeNode, treeNode)
|
|
||||||
| `PointwiseCombination(pointwiseOperation, treeNode, treeNode)
|
|
||||||
| `VerticalScaling(scaleOperation, treeNode, treeNode)
|
|
||||||
| `Render(treeNode)
|
|
||||||
| `Truncate(option(float), option(float), treeNode)
|
|
||||||
| `Normalize(treeNode)
|
|
||||||
| `FloatFromDist(distToFloatOperation, treeNode)
|
|
||||||
];
|
|
||||||
|
|
||||||
module Operation = {
|
|
||||||
type t = operation;
|
|
||||||
let truncateToString =
|
|
||||||
(left: option(float), right: option(float), nodeToString) => {
|
|
||||||
let left = left |> E.O.dimap(Js.Float.toString, () => "-inf");
|
|
||||||
let right = right |> E.O.dimap(Js.Float.toString, () => "inf");
|
|
||||||
{j|truncate($nodeToString, $left, $right)|j};
|
|
||||||
};
|
|
||||||
|
|
||||||
let toString = nodeToString =>
|
|
||||||
fun
|
|
||||||
| `AlgebraicCombination(op, t1, t2) =>
|
|
||||||
SymbolicTypes.Algebraic.format(op, nodeToString(t1), nodeToString(t2))
|
|
||||||
| `PointwiseCombination(op, t1, t2) =>
|
|
||||||
SymbolicTypes.Pointwise.format(op, nodeToString(t1), nodeToString(t2))
|
|
||||||
| `VerticalScaling(scaleOp, t, scaleBy) =>
|
|
||||||
SymbolicTypes.Scale.format(
|
|
||||||
scaleOp,
|
|
||||||
nodeToString(t),
|
|
||||||
nodeToString(scaleBy),
|
|
||||||
)
|
|
||||||
| `Normalize(t) => "normalize(" ++ nodeToString(t) ++ ")"
|
|
||||||
| `FloatFromDist(floatFromDistOp, t) =>
|
|
||||||
SymbolicTypes.DistToFloat.format(floatFromDistOp, nodeToString(t))
|
|
||||||
| `Truncate(lc, rc, t) => truncateToString(lc, rc, nodeToString(t))
|
|
||||||
| `Render(t) => nodeToString(t);
|
|
||||||
};
|
|
||||||
|
|
||||||
module TreeNode = {
|
|
||||||
type t = treeNode;
|
|
||||||
type tResult = treeNode => result(treeNode, string);
|
|
||||||
|
|
||||||
let rec toString =
|
|
||||||
fun
|
|
||||||
| `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
|
|
||||||
* different kinds of operations. */
|
|
||||||
|
|
||||||
/* 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)))
|
|
||||||
| `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, operationToLeaf, t1, t2) => {
|
|
||||||
// force rendering into shapes
|
|
||||||
let renderShape = r => operationToLeaf(`Render(r));
|
|
||||||
switch (renderShape(t1), renderShape(t2)) {
|
|
||||||
| (Ok(`Leaf(`RenderedDist(s1))), Ok(`Leaf(`RenderedDist(s2)))) =>
|
|
||||||
Ok(
|
|
||||||
`Leaf(
|
|
||||||
`RenderedDist(
|
|
||||||
Distributions.Shape.combineAlgebraically(algebraicOp, s1, s2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
| (Error(e1), _) => Error(e1)
|
|
||||||
| (_, Error(e2)) => Error(e2)
|
|
||||||
| _ => Error("Could not render shapes.")
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let toLeaf =
|
|
||||||
(
|
|
||||||
operationToLeaf,
|
|
||||||
algebraicOp: SymbolicTypes.algebraicOperation,
|
|
||||||
t1: t,
|
|
||||||
t2: t,
|
|
||||||
)
|
|
||||||
: result(treeNode, string) =>
|
|
||||||
toTreeNode(algebraicOp, t1, t2)
|
|
||||||
|> tryAnalyticalSolution
|
|
||||||
|> 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),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
module VerticalScaling = {
|
|
||||||
let toLeaf = (operationToLeaf, scaleOp, 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 = operationToLeaf(`Render(t));
|
|
||||||
|
|
||||||
switch (renderedShape, scaleBy) {
|
|
||||||
| (Ok(`Leaf(`RenderedDist(rs))), `Leaf(`SymbolicDist(`Float(sm)))) =>
|
|
||||||
Ok(
|
|
||||||
`Leaf(
|
|
||||||
`RenderedDist(
|
|
||||||
Distributions.Shape.T.mapY(
|
|
||||||
~knownIntegralSumFn=knownIntegralSumFn(sm),
|
|
||||||
fn(sm),
|
|
||||||
rs,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
| (Error(e1), _) => Error(e1)
|
|
||||||
| (_, _) => Error("Can only scale by float values.")
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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)))) =>
|
|
||||||
Ok(
|
|
||||||
`Leaf(
|
|
||||||
`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.")
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let pointwiseMultiply = (operationToLeaf, 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(
|
|
||||||
"Pointwise multiplication not yet supported.",
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let toLeaf = (operationToLeaf, pointwiseOp, t1, t2) => {
|
|
||||||
switch (pointwiseOp) {
|
|
||||||
| `Add => pointwiseAdd(operationToLeaf, t1, t2)
|
|
||||||
| `Multiply => pointwiseMultiply(operationToLeaf, 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(treeNode, string) => {
|
|
||||||
let originalTreeNode =
|
|
||||||
`Operation(`Truncate((leftCutoff, rightCutoff, t)));
|
|
||||||
|
|
||||||
originalTreeNode
|
|
||||||
|> tryTruncatingNothing
|
|
||||||
|> E.R.bind(_, tryTruncatingUniform);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = operationToLeaf(`Render(t));
|
|
||||||
|
|
||||||
switch (renderedShape) {
|
|
||||||
| Ok(`Leaf(`RenderedDist(rs))) =>
|
|
||||||
let truncatedShape =
|
|
||||||
rs |> Distributions.Shape.T.truncate(leftCutoff, rightCutoff);
|
|
||||||
Ok(`Leaf(`RenderedDist(rs)));
|
|
||||||
| Error(e1) => Error(e1)
|
|
||||||
| _ => Error("Could not truncate distribution.")
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let toLeaf =
|
|
||||||
(
|
|
||||||
operationToLeaf,
|
|
||||||
leftCutoff: option(float),
|
|
||||||
rightCutoff: option(float),
|
|
||||||
t: treeNode,
|
|
||||||
)
|
|
||||||
: result(treeNode, string) => {
|
|
||||||
t
|
|
||||||
|> Simplify.attempt(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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module Normalize = {
|
|
||||||
let rec toLeaf = (operationToLeaf, t: treeNode): result(treeNode, 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))
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module FloatFromDist = {
|
|
||||||
let symbolicToLeaf = (distToFloatOp: distToFloatOperation, s) => {
|
|
||||||
SymbolicDist.T.operate(distToFloatOp, s)
|
|
||||||
|> E.R.bind(_, v => Ok(`Leaf(`SymbolicDist(`Float(v)))));
|
|
||||||
};
|
|
||||||
let renderedToLeaf =
|
|
||||||
(distToFloatOp: distToFloatOperation, rs: DistTypes.shape)
|
|
||||||
: result(treeNode, string) => {
|
|
||||||
Distributions.Shape.operate(distToFloatOp, rs)
|
|
||||||
|> (v => Ok(`Leaf(`SymbolicDist(`Float(v)))));
|
|
||||||
};
|
|
||||||
let rec toLeaf =
|
|
||||||
(
|
|
||||||
operationToLeaf,
|
|
||||||
distToFloatOp: distToFloatOperation,
|
|
||||||
t: treeNode,
|
|
||||||
)
|
|
||||||
: result(treeNode, 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),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module Render = {
|
|
||||||
let rec toLeaf =
|
|
||||||
(
|
|
||||||
operationToLeaf: operation => result(t, string),
|
|
||||||
sampleCount: int,
|
|
||||||
t: treeNode,
|
|
||||||
)
|
|
||||||
: 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))
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = (treeNode: t, sampleCount: int): result(t, string) => {
|
|
||||||
switch (treeNode) {
|
|
||||||
| `Leaf(d) => Ok(`Leaf(d))
|
|
||||||
| `Operation(op) => operationToLeaf(sampleCount, op)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let toShape = (sampleCount: int, treeNode: treeNode) => {
|
|
||||||
let renderResult =
|
|
||||||
TreeNode.toLeaf(`Operation(`Render(treeNode)), sampleCount);
|
|
||||||
|
|
||||||
switch (renderResult) {
|
|
||||||
| Ok(`Leaf(`RenderedDist(rs))) =>
|
|
||||||
let continuous = Distributions.Shape.T.toContinuous(rs);
|
|
||||||
let discrete = Distributions.Shape.T.toDiscrete(rs);
|
|
||||||
let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete);
|
|
||||||
shape |> E.O.toExt("Could not build final shape.");
|
|
||||||
| Ok(_) => E.O.toExn("Rendering failed.", None)
|
|
||||||
| Error(message) => E.O.toExn("No shape found, error: " ++ message, None)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let toString = (treeNode: treeNode) => TreeNode.toString(treeNode);
|
|
Loading…
Reference in New Issue
Block a user