Merge pull request #68 from foretold-app/variable-test

Adds simple variables and functions
This commit is contained in:
Ozzie Gooen 2020-07-31 19:09:12 +01:00 committed by GitHub
commit 65f1485a55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 808 additions and 654 deletions

View File

@ -14,12 +14,12 @@ describe("Lodash", () => {
describe("Lodash", () => {
makeTest(
"split",
Samples.T.splitContinuousAndDiscrete([|1.432, 1.33455, 2.0|]),
SamplesToShape.Internals.T.splitContinuousAndDiscrete([|1.432, 1.33455, 2.0|]),
([|1.432, 1.33455, 2.0|], E.FloatFloatMap.empty()),
);
makeTest(
"split",
Samples.T.splitContinuousAndDiscrete([|
SamplesToShape.Internals.T.splitContinuousAndDiscrete([|
1.432,
1.33455,
2.0,
@ -39,12 +39,12 @@ describe("Lodash", () => {
};
let (_, discrete) =
Samples.T.splitContinuousAndDiscrete(makeDuplicatedArray(10));
SamplesToShape.Internals.T.splitContinuousAndDiscrete(makeDuplicatedArray(10));
let toArr = discrete |> E.FloatFloatMap.toArray;
makeTest("splitMedium", toArr |> Belt.Array.length, 10);
let (c, discrete) =
Samples.T.splitContinuousAndDiscrete(makeDuplicatedArray(500));
SamplesToShape.Internals.T.splitContinuousAndDiscrete(makeDuplicatedArray(500));
let toArr = discrete |> E.FloatFloatMap.toArray;
makeTest("splitMedium", toArr |> Belt.Array.length, 500);
})

View File

@ -6,18 +6,18 @@
// uniform(0,1) > 0.3 ? lognormal(6.652, -0.41): 0
let timeDist ={
let ingredients = RenderTypes.DistPlusRenderer.Ingredients.make(
let ingredients = DistPlusRenderer.Inputs.Ingredients.make(
~guesstimatorString="(floor(10 to 15))",
~domain=RightLimited({xPoint: 50.0, excludingProbabilityMass: 0.3}),
~unit=
DistTypes.TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
());
let inputs = RenderTypes.DistPlusRenderer.make(~distPlusIngredients=ingredients,())
let inputs = DistPlusRenderer.Inputs.make(~distPlusIngredients=ingredients,())
inputs |> DistPlusRenderer.run
}
let setup = dist =>
RenderTypes.DistPlusRenderer.make(~distPlusIngredients=dist,())
DistPlusRenderer.Inputs.make(~distPlusIngredients=dist,())
|> DistPlusRenderer.run
|> E.R.fmap(distPlus => <DistPlusPlot distPlus />)
|> E.R.toOption
@ -28,7 +28,7 @@ let simpleExample = (name, guesstimatorString) =>
<h3 className="text-gray-600 text-lg font-bold">
{name |> ReasonReact.string}
</h3>
{setup(RenderTypes.DistPlusRenderer.Ingredients.make(~guesstimatorString, ()))}
{setup(DistPlusRenderer.Inputs.Ingredients.make(~guesstimatorString, ()))}
</>;
let timeExample = (name, guesstimatorString) =>
@ -37,7 +37,7 @@ let timeExample = (name, guesstimatorString) =>
{name |> ReasonReact.string}
</h3>
{setup(
RenderTypes.DistPlusRenderer.Ingredients.make(
DistPlusRenderer.Inputs.Ingredients.make(
~guesstimatorString,
~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
(),

View File

@ -1,5 +1,5 @@
let setup = dist =>
RenderTypes.DistPlusRenderer.make(~distPlusIngredients=dist, ())
DistPlusRenderer.Inputs.make(~distPlusIngredients=dist, ())
|> DistPlusRenderer.run
|> E.R.fmap(distPlus => <DistPlusPlot distPlus />)
|> E.R.toOption
@ -10,7 +10,7 @@ let simpleExample = (guesstimatorString, ~problem="", ()) =>
<p> {guesstimatorString |> ReasonReact.string} </p>
<p> {problem |> (e => "problem: " ++ e) |> ReasonReact.string} </p>
{setup(
RenderTypes.DistPlusRenderer.Ingredients.make(~guesstimatorString, ()),
DistPlusRenderer.Inputs.Ingredients.make(~guesstimatorString, ()),
)}
</>;

View File

@ -33,6 +33,23 @@ module Form = ReForm.Make(FormConfig);
let schema = Form.Validation.Schema([||]);
module FieldText = {
[@react.component]
let make = (~field, ~label) => {
<Form.Field
field
render={({handleChange, error, value, validate}) =>
<Antd.Form.Item label={label |> R.ste}>
<Antd.Input.TextArea
value
onChange={BsReform.Helpers.handleChange(handleChange)}
onBlur={_ => validate()}
/>
</Antd.Form.Item>
}
/>;
};
};
module FieldString = {
[@react.component]
let make = (~field, ~label) => {
@ -111,13 +128,6 @@ module Styles = {
]);
};
type inputs = {
samplingInputs: RenderTypes.ShapeRenderer.Sampling.inputs,
guesstimatorString: string,
length: int,
shouldDownsampleSampledDistribution: int,
};
module DemoDist = {
[@react.component]
let make = (~guesstimatorString, ~domain, ~unit, ~options) => {
@ -127,30 +137,34 @@ module DemoDist = {
{switch (domain, unit, options) {
| (Some(domain), Some(unit), Some(options)) =>
let distPlusIngredients =
RenderTypes.DistPlusRenderer.Ingredients.make(
DistPlusRenderer.Inputs.Ingredients.make(
~guesstimatorString,
~domain,
~unit,
(),
);
let inputs =
RenderTypes.DistPlusRenderer.make(
let inputs1 =
DistPlusRenderer.Inputs.make(
~samplingInputs={
sampleCount: Some(options.sampleCount),
outputXYPoints: Some(options.outputXYPoints),
kernelWidth: options.kernelWidth,
shapeLength: Some(options.downsampleTo |> E.O.default(1000))
},
~distPlusIngredients,
~shouldDownsample=options.downsampleTo |> E.O.isSome,
~recommendedLength=options.downsampleTo |> E.O.default(1000),
~environment=
[|("p", `SymbolicDist(`Float(1.0)))|]
->Belt.Map.String.fromArray,
(),
);
let response = DistPlusRenderer.run(inputs);
switch (response) {
| Ok(distPlus) =>
let normalizedDistPlus = DistPlus.T.normalize(distPlus);
<DistPlusPlot distPlus=normalizedDistPlus />;
| Error(r) => r |> R.ste
let response1 = DistPlusRenderer.run(inputs1);
switch (response1) {
| (Ok(distPlus1)) =>
<>
<DistPlusPlot distPlus={DistPlus.T.normalize(distPlus1)} />
</>
| (Error(r)) => r |> R.ste
};
| _ =>
"Nothing to show. Try to change the distribution description."
@ -314,7 +328,7 @@ let make = () => {
<Antd.Form onSubmit>
<Row _type=`flex className=Styles.rows>
<Col span=24>
<FieldString
<FieldText
field=FormConfig.GuesstimatorString
label="Guesstimator String"
/>

View File

@ -392,8 +392,8 @@ module Draw = {
let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
let normalShape =
ExpressionTree.toShape(
numSamples,
{sampleCount: 10000, outputXYPoints: 10000, kernelWidth: None},
{sampleCount: 10000, outputXYPoints: 10000, kernelWidth: None, shapeLength:numSamples},
ExpressionTypes.ExpressionTree.Environment.empty,
`SymbolicDist(normal),
) |> E.R.toExn;
let xyShape: Types.xyShape =

View File

@ -106,7 +106,6 @@ let init = {
showParams: false,
showPercentiles: true,
distributions: [
{yLog: false, xLog: false, isCumulative: false, height: 4},
{yLog: false, xLog: false, isCumulative: true, height: 1},
{yLog: false, xLog: false, isCumulative: false, height: 1},
],
};

View File

@ -27,15 +27,15 @@ let toDiscretePointMassesFromTriangulars =
let xsProdN1: array(float) = Belt.Array.makeUninitializedUnsafe(n - 1);
let xsProdN2: array(float) = Belt.Array.makeUninitializedUnsafe(n - 2);
for (i in 0 to n - 1) {
Belt.Array.set(xsSq, i, xs[i] *. xs[i]) |> ignore;
Belt.Array.set(xsSq, i, xs[i] *. xs[i]) |> ignore;
();
};
for (i in 0 to n - 2) {
Belt.Array.set(xsProdN1, i, xs[i] *. xs[i + 1]) |> ignore;
Belt.Array.set(xsProdN1, i, xs[i] *. xs[i + 1]) |> ignore;
();
};
for (i in 0 to n - 3) {
Belt.Array.set(xsProdN2, i, xs[i] *. xs[i + 2]) |> ignore;
Belt.Array.set(xsProdN2, i, xs[i] *. xs[i + 2]) |> ignore;
();
};
// means and variances
@ -45,11 +45,8 @@ let toDiscretePointMassesFromTriangulars =
if (inverse) {
for (i in 1 to n - 2) {
Belt.Array.set(
masses,
i - 1,
(xs[i + 1] -. xs[i - 1]) *. ys[i] /. 2.,
) |> ignore;
Belt.Array.set(masses, i - 1, (xs[i + 1] -. xs[i - 1]) *. ys[i] /. 2.)
|> ignore;
// this only works when the whole triange is either on the left or on the right of zero
let a = xs[i - 1];
@ -70,9 +67,9 @@ let toDiscretePointMassesFromTriangulars =
-. inverseMean
** 2.;
Belt.Array.set(means, i - 1, inverseMean) |> ignore;
Belt.Array.set(means, i - 1, inverseMean) |> ignore;
Belt.Array.set(variances, i - 1, inverseVar) |> ignore;
Belt.Array.set(variances, i - 1, inverseVar) |> ignore;
();
};
@ -80,14 +77,12 @@ let toDiscretePointMassesFromTriangulars =
} else {
for (i in 1 to n - 2) {
// area of triangle = width * height / 2
Belt.Array.set(
masses,
i - 1,
(xs[i + 1] -. xs[i - 1]) *. ys[i] /. 2.,
) |> ignore;
Belt.Array.set(masses, i - 1, (xs[i + 1] -. xs[i - 1]) *. ys[i] /. 2.)
|> ignore;
// means of triangle = (a + b + c) / 3
Belt.Array.set(means, i - 1, (xs[i - 1] +. xs[i] +. xs[i + 1]) /. 3.) |> ignore;
Belt.Array.set(means, i - 1, (xs[i - 1] +. xs[i] +. xs[i + 1]) /. 3.)
|> ignore;
// variance of triangle = (a^2 + b^2 + c^2 - ab - ac - bc) / 18
Belt.Array.set(
@ -102,7 +97,8 @@ let toDiscretePointMassesFromTriangulars =
-. xsProdN2[i - 1]
)
/. 18.,
) |> ignore;
)
|> ignore;
();
};
{n: n - 2, masses, means, variances};
@ -134,8 +130,10 @@ let combineShapesContinuousContinuous =
| `Subtract => ((m1, m2) => m1 -. m2)
| `Multiply => ((m1, m2) => m1 *. m2)
| `Divide => ((m1, mInv2) => m1 *. mInv2)
| `Exponentiate => ((m1, mInv2) => m1 ** mInv2)
}; // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
// TODO: I don't know what the variances are for exponentatiation
// converts the variances and means of the two inputs into the variance of the output
let combineVariancesFn =
switch (op) {
@ -144,6 +142,8 @@ let combineShapesContinuousContinuous =
| `Multiply => (
(v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
)
| `Exponentiate =>
((v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.);
| `Divide => (
(v1, vInv2, m1, mInv2) =>
v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2.
@ -225,9 +225,12 @@ let toDiscretePointMassesFromDiscrete =
};
let combineShapesContinuousDiscrete =
(op: ExpressionTypes.algebraicOperation, continuousShape: DistTypes.xyShape, discreteShape: DistTypes.xyShape)
(
op: ExpressionTypes.algebraicOperation,
continuousShape: DistTypes.xyShape,
discreteShape: DistTypes.xyShape,
)
: DistTypes.xyShape => {
let t1n = continuousShape |> XYShape.T.length;
let t2n = discreteShape |> XYShape.T.length;
@ -248,15 +251,19 @@ let combineShapesContinuousDiscrete =
Belt.Array.set(
dxyShape,
i,
(fn(continuousShape.xs[i], discreteShape.xs[j]),
continuousShape.ys[i] *. discreteShape.ys[j]),
) |> ignore;
(
fn(continuousShape.xs[i], discreteShape.xs[j]),
continuousShape.ys[i] *. discreteShape.ys[j],
),
)
|> ignore;
();
};
Belt.Array.set(outXYShapes, j, dxyShape) |> ignore;
();
}
| `Multiply
| `Exponentiate
| `Divide =>
for (j in 0 to t2n - 1) {
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
@ -266,8 +273,12 @@ let combineShapesContinuousDiscrete =
Belt.Array.set(
dxyShape,
i,
(fn(continuousShape.xs[i], discreteShape.xs[j]), continuousShape.ys[i] *. discreteShape.ys[j] /. discreteShape.xs[j]),
) |> ignore;
(
fn(continuousShape.xs[i], discreteShape.xs[j]),
{continuousShape.ys[i] *. discreteShape.ys[j] /. discreteShape.xs[j]}
),
)
|> ignore;
();
};
Belt.Array.set(outXYShapes, j, dxyShape) |> ignore;
@ -278,7 +289,10 @@ let combineShapesContinuousDiscrete =
outXYShapes
|> E.A.fmap(XYShape.T.fromZippedArray)
|> E.A.fold_left(
XYShape.PointwiseCombination.combine((+.),
XYShape.XtoY.continuousInterpolator(`Linear, `UseZero)),
XYShape.T.empty);
XYShape.PointwiseCombination.combine(
(+.),
XYShape.XtoY.continuousInterpolator(`Linear, `UseZero),
),
XYShape.T.empty,
);
};

View File

@ -37,6 +37,7 @@ let empty: DistTypes.continuousShape = {
let stepwiseToLinear = (t: t): t =>
make(~integralSumCache=t.integralSumCache, ~integralCache=t.integralCache, XYShape.Range.stepwiseToLinear(t.xyShape));
// Note: This results in a distribution with as many points as the sum of those in t1 and t2.
let combinePointwise =
(
~integralSumCachesFn=(_, _) => None,

View File

@ -216,6 +216,11 @@ let sample = (t: t): float => {
bar;
};
let isFloat = (t:t) => switch(t){
| Discrete({xyShape: {xs: [|_|], ys: [|1.0|]}}) => true
| _ => false
}
let sampleNRendered = (n, dist) => {
let integralCache = T.Integral.get(dist);
let distWithUpdatedIntegralCache = T.updateIntegralCache(Some(integralCache), dist);

View File

@ -1,13 +1,17 @@
open ExpressionTypes.ExpressionTree;
let toShape = (intendedShapeLength: int, samplingInputs, node: node) => {
let toLeaf = (samplingInputs, environment, node: node) => {
node
|> ExpressionTreeEvaluator.toLeaf({
samplingInputs,
environment,
evaluateNode: ExpressionTreeEvaluator.toLeaf,
});
};
let toShape = (samplingInputs, environment, node: node) => {
let renderResult =
`Render(`Normalize(node))
|> ExpressionTreeEvaluator.toLeaf({
samplingInputs,
intendedShapeLength,
evaluateNode: ExpressionTreeEvaluator.toLeaf,
});
`Render(`Normalize(node)) |> toLeaf(samplingInputs, environment);
switch (renderResult) {
| Ok(`RenderedDist(shape)) => Ok(shape)

View File

@ -151,7 +151,7 @@ module PointwiseCombination = {
};
};
let pointwiseMultiply = (evaluationParams: evaluationParams, t1: t, t2: t) => {
let pointwiseCombine = (fn, evaluationParams: evaluationParams, t1: t, t2: t) => {
// 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.
// TODO: This should work for symbolic distributions too!
@ -176,7 +176,8 @@ module PointwiseCombination = {
) => {
switch (pointwiseOp) {
| `Add => pointwiseAdd(evaluationParams, t1, t2)
| `Multiply => pointwiseMultiply(evaluationParams, t1, t2)
| `Multiply => pointwiseCombine(( *. ),evaluationParams, t1, t2)
| `Exponentiate => pointwiseCombine(( *. ),evaluationParams, t1, t2)
};
};
};
@ -259,14 +260,27 @@ module FloatFromDist = {
};
};
// TODO: This forces things to be floats
let callableFunction = (evaluationParams, name, args) => {
let b =
args
|> E.A.fmap(a =>
Render.render(evaluationParams, a)
|> E.R.bind(_, Render.toFloat)
)
|> E.A.R.firstErrorOrOpen;
b |> E.R.bind(_, Functions.fnn(evaluationParams, name));
};
module Render = {
let rec operationToLeaf =
(evaluationParams: evaluationParams, t: node): result(t, string) => {
switch (t) {
| `Function(_) => Error("Cannot render a function")
| `SymbolicDist(d) =>
Ok(
`RenderedDist(
SymbolicDist.T.toShape(evaluationParams.intendedShapeLength, d),
SymbolicDist.T.toShape(evaluationParams.samplingInputs.shapeLength, d),
),
)
| `RenderedDist(_) as t => Ok(t) // already a rendered shape, we're done here
@ -275,6 +289,13 @@ module Render = {
};
};
let run = (node, fnNode) => {
switch (fnNode) {
| `Function(r) => Ok(r(node))
| _ => Error("Not a function")
};
};
/* 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,
@ -314,5 +335,9 @@ let toLeaf =
FloatFromDist.operationToLeaf(evaluationParams, distToFloatOp, t)
| `Normalize(t) => Normalize.operationToLeaf(evaluationParams, t)
| `Render(t) => Render.operationToLeaf(evaluationParams, t)
| `Function(t) => Ok(`Function(t))
| `Symbol(r) => ExpressionTypes.ExpressionTree.Environment.get(evaluationParams.environment, r) |> E.O.toResult("Undeclared variable " ++ r)
| `FunctionCall(name, args) =>
callableFunction(evaluationParams, name, args)
};
};

View File

@ -1,5 +1,5 @@
type algebraicOperation = [ | `Add | `Multiply | `Subtract | `Divide];
type pointwiseOperation = [ | `Add | `Multiply];
type algebraicOperation = [ | `Add | `Multiply | `Subtract | `Divide | `Exponentiate];
type pointwiseOperation = [ | `Add | `Multiply | `Exponentiate];
type scaleOperation = [ | `Multiply | `Exponentiate | `Log];
type distToFloatOperation = [
| `Pdf(float)
@ -20,17 +20,37 @@ module ExpressionTree = {
| `Truncate(option(float), option(float), node)
| `Normalize(node)
| `FloatFromDist(distToFloatOperation, node)
| `Function(array(string), node)
| `FunctionCall(string, array(node))
| `Symbol(string)
];
type samplingInputs = {
sampleCount: int,
outputXYPoints: int,
kernelWidth: option(float),
shapeLength: int
};
type environment = Belt.Map.String.t(node);
module Environment = {
type t = environment
module MS = Belt.Map.String;
let fromArray = MS.fromArray
let empty:t = [||]->fromArray;
let mergeKeepSecond = (a:t,b:t) => MS.merge(a,b, (_,a,b) =>switch(a,b){
| (_, Some(b)) => Some(b)
| (Some(a), _) => Some(a)
| _ => None
})
let update = (t,str, fn) => MS.update(t, str, fn)
let get = (t:t,str) => MS.get(t, str)
}
type evaluationParams = {
samplingInputs,
intendedShapeLength: int,
environment,
evaluateNode: (evaluationParams, node) => Belt.Result.t(node, string),
};
@ -71,8 +91,20 @@ module ExpressionTree = {
| `RenderedDist(r) => Some(r)
| _ => None
};
};
let _toFloat = (t: DistTypes.shape) =>
switch (t) {
| Discrete({xyShape: {xs: [|x|], ys: [|1.0|]}}) =>
Some(`SymbolicDist(`Float(x)))
| _ => None
};
let toFloat = (item: node): result(node, string) =>
item
|> getShape
|> E.O.bind(_, _toFloat)
|> E.O.toResult("Not valid shape");
};
};
type simplificationResult = [
@ -80,3 +112,8 @@ type simplificationResult = [
| `Error(string)
| `NoSolution
];
module Program = {
type statement = [ | `Assignment(string, ExpressionTree.node) | `Expression(ExpressionTree.node)];
type program = array(statement);
}

View File

@ -0,0 +1,122 @@
type node = ExpressionTypes.ExpressionTree.node;
let toOkSym = r => Ok(`SymbolicDist(r));
let twoFloats = (fn, n1: node, n2: node): result(node, string) =>
switch (n1, n2) {
| (`SymbolicDist(`Float(a)), `SymbolicDist(`Float(b))) => fn(a, b)
| _ => Error("Variables have wrong type")
};
let threeFloats = (fn, n1: node, n2: node, n3: node): result(node, string) =>
switch (n1, n2, n3) {
| (
`SymbolicDist(`Float(a)),
`SymbolicDist(`Float(b)),
`SymbolicDist(`Float(c)),
) =>
fn(a, b, c)
| _ => Error("Variables have wrong type")
};
let twoFloatsToOkSym = fn => twoFloats((f1, f2) => fn(f1, f2) |> toOkSym);
let threeFloats = fn => threeFloats((f1, f2, f3) => fn(f1, f2, f3));
let apply2 = (fn, args): result(node, string) =>
switch (args) {
| [|a, b|] => fn(a, b)
| _ => Error("Needs 2 args")
};
let apply3 = (fn, args: array(node)): result(node, string) =>
switch (args) {
| [|a, b, c|] => fn(a, b, c)
| _ => Error("Needs 3 args")
};
let to_: array(node) => result(node, string) =
fun
| [|`SymbolicDist(`Float(low)), `SymbolicDist(`Float(high))|]
when low <= 0.0 && low < high => {
Ok(`SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high)));
}
| [|`SymbolicDist(`Float(low)), `SymbolicDist(`Float(high))|]
when low < high => {
Ok(`SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high)));
}
| [|`SymbolicDist(`Float(_)), `SymbolicDist(_)|] =>
Error("Low value must be less than high value.")
| _ => Error("Requires 2 variables");
let processCustomFn =
(
evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
args: array(node),
argNames: array(string),
fnResult: node,
) =>
if (E.A.length(args) == E.A.length(argNames)) {
let newEnvironment =
Belt.Array.zip(argNames, args)
|> ExpressionTypes.ExpressionTree.Environment.fromArray;
let newEvaluationParams: ExpressionTypes.ExpressionTree.evaluationParams = {
samplingInputs: evaluationParams.samplingInputs,
environment:
ExpressionTypes.ExpressionTree.Environment.mergeKeepSecond(
evaluationParams.environment,
newEnvironment,
),
evaluateNode: evaluationParams.evaluateNode,
};
evaluationParams.evaluateNode(newEvaluationParams, fnResult);
} else {
Error("Failure");
};
let fnn =
(
evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
name,
args: array(node),
) =>
switch (
name,
ExpressionTypes.ExpressionTree.Environment.get(
evaluationParams.environment,
name,
),
) {
| (_, Some(`Function(argNames, tt))) =>
processCustomFn(evaluationParams, args, argNames, tt)
| ("normal", _) =>
apply2(twoFloatsToOkSym(SymbolicDist.Normal.make), args)
| ("uniform", _) =>
apply2(twoFloatsToOkSym(SymbolicDist.Uniform.make), args)
| ("beta", _) => apply2(twoFloatsToOkSym(SymbolicDist.Beta.make), args)
| ("cauchy", _) =>
apply2(twoFloatsToOkSym(SymbolicDist.Cauchy.make), args)
| ("lognormal", _) =>
apply2(twoFloatsToOkSym(SymbolicDist.Lognormal.make), args)
| ("lognormalFromMeanAndStdDev", _) =>
apply2(twoFloatsToOkSym(SymbolicDist.Lognormal.fromMeanAndStdev), args)
| ("exponential", _) =>
switch (args) {
| [|`SymbolicDist(`Float(a))|] =>
Ok(`SymbolicDist(SymbolicDist.Exponential.make(a)))
| _ => Error("Needs 3 valid arguments")
}
| ("triangular", _) =>
switch (args) {
| [|
`SymbolicDist(`Float(a)),
`SymbolicDist(`Float(b)),
`SymbolicDist(`Float(c)),
|] =>
SymbolicDist.Triangular.make(a, b, c)
|> E.R.fmap(r => `SymbolicDist(r))
| _ => Error("Needs 3 valid arguments")
}
| ("to", _) => to_(args)
| _ => Error("Function not found")
};

View File

@ -4,10 +4,18 @@ module MathJsonToMathJsAdt = {
| Value(float)
| Fn(fn)
| Array(array(arg))
| Blocks(array(arg))
| Object(Js.Dict.t(arg))
| Assignment(arg, arg)
| FunctionAssignment(fnAssignment)
and fn = {
name: string,
args: array(arg),
}
and fnAssignment = {
name: string,
args: array(string),
expression: arg,
};
let rec run = (j: Js.Json.t) =>
@ -40,6 +48,25 @@ module MathJsonToMathJsAdt = {
let items = field("items", array(run), j);
Some(Array(items |> E.A.O.concatSomes));
| "SymbolNode" => Some(Symbol(field("name", string, j)))
| "AssignmentNode" =>
let object_ = j |> field("object", run);
let value_ = j |> field("value", run);
switch (object_, value_) {
| (Some(o), Some(v)) => Some(Assignment(o, v))
| _ => None
};
| "BlockNode" =>
let block = r => r |> field("node", run);
let args = j |> field("blocks", array(block)) |> E.A.O.concatSomes;
Some(Blocks(args));
| "FunctionAssignmentNode" =>
let name = j |> field("name", string);
let args = j |> field("params", array(field("name", string)));
let expression = j |> field("expr", run);
expression
|> E.O.fmap(expression =>
FunctionAssignment({name, args, expression})
);
| n =>
Js.log3("Couldn't parse mathjs node", j, n);
None;
@ -50,29 +77,37 @@ module MathJsonToMathJsAdt = {
module MathAdtToDistDst = {
open MathJsonToMathJsAdt;
let handleSymbol = sym => {
Ok(`Symbol(sym));
};
module MathAdtCleaner = {
let transformWithSymbol = (f: float, s: string) =>
switch (s) {
| "K"
| "k" => f *. 1000.
| "k" => Some(f *. 1000.)
| "M"
| "m" => f *. 1000000.
| "m" => Some(f *. 1000000.)
| "B"
| "b" => f *. 1000000000.
| "b" => Some(f *. 1000000000.)
| "T"
| "t" => f *. 1000000000000.
| _ => f
| "t" => Some(f *. 1000000000000.)
| _ => None
};
let rec run =
fun
| Fn({name: "multiply", args: [|Value(f), Symbol(s)|]}) =>
Value(transformWithSymbol(f, s))
| Fn({name: "multiply", args: [|Value(f), Symbol(s)|]}) as doNothing =>
transformWithSymbol(f, s)
|> E.O.fmap(r => Value(r))
|> E.O.default(doNothing)
| Fn({name: "unaryMinus", args: [|Value(f)|]}) => Value((-1.0) *. f)
| Fn({name, args}) => Fn({name, args: args |> E.A.fmap(run)})
| Array(args) => Array(args |> E.A.fmap(run))
| Symbol(s) => Symbol(s)
| Value(v) => Value(v)
| Blocks(args) => Blocks(args |> E.A.fmap(run))
| Assignment(a, b) => Assignment(a, run(b))
| FunctionAssignment(a) => FunctionAssignment(a)
| Object(v) =>
Object(
v
@ -82,83 +117,31 @@ module MathAdtToDistDst = {
);
};
let normal:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(mean), Value(stdev)|] =>
Ok(`SymbolicDist(`Normal({mean, stdev})))
| _ => Error("Wrong number of variables in normal distribution");
let lognormal:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(mu), Value(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(
`SymbolicDist(
SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev),
),
)
| (_, _, Some(Value(mu)), Some(Value(sigma))) =>
Ok(`SymbolicDist(`Lognormal({mu, sigma})))
| _ => Error("Lognormal distribution would need mean and stdev")
};
}
| _ => Error("Wrong number of variables in lognormal distribution");
let to_: array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(low), Value(high)|] when low <= 0.0 && low < high => {
Ok(`SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high)));
}
| [|Value(low), Value(high)|] when low < high => {
let lognormal = (args, parseArgs, nodeParser) =>
switch (args) {
| [|Object(o)|] =>
let g = s =>
Js.Dict.get(o, s)
|> E.O.toResult("Variable was empty")
|> E.R.bind(_, nodeParser);
switch (g("mean"), g("stdev"), g("mu"), g("sigma")) {
| (Ok(mean), Ok(stdev), _, _) =>
Ok(
`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) =
fun
| [|Value(low), Value(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(`SymbolicDist(`Beta({alpha, beta})))
| _ => Error("Wrong number of variables in lognormal distribution");
let exponential:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(rate)|] => Ok(`SymbolicDist(`Exponential({rate: rate})))
| _ => Error("Wrong number of variables in Exponential distribution");
let cauchy:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(local), Value(scale)|] =>
Ok(`SymbolicDist(`Cauchy({local, scale})))
| _ => Error("Wrong number of variables in cauchy distribution");
let triangular:
array(arg) => result(ExpressionTypes.ExpressionTree.node, string) =
fun
| [|Value(low), Value(medium), Value(high)|]
when low < medium && medium < high =>
Ok(`SymbolicDist(`Triangular({low, medium, high})))
| [|Value(_), Value(_), Value(_)|] =>
Error("Triangular values must be increasing order")
| _ => Error("Wrong number of variables in triangle distribution");
`FunctionCall(("lognormalFromMeanAndStdDev", [|mean, stdev|])),
)
| (_, _, Ok(mu), Ok(sigma)) =>
Ok(`FunctionCall(("lognormal", [|mu, sigma|])))
| _ =>
Error(
"Lognormal distribution needs either mean and stdev or mu and sigma",
)
};
| _ =>
parseArgs()
|> E.R.fmap((args: array(ExpressionTypes.ExpressionTree.node)) =>
`FunctionCall(("lognormal", args))
)
};
let multiModal =
(
@ -166,15 +149,6 @@ module MathAdtToDistDst = {
weights: option(array(float)),
) => {
let weights = weights |> E.O.default([||]);
/*let dists: =
args
|> E.A.fmap(
fun
| Ok(a) => a
| Error(e) => Error(e)
);*/
let firstWithError = args |> Belt.Array.getBy(_, Belt.Result.isError);
let withoutErrors = args |> E.A.fmap(E.R.toOption) |> E.A.O.concatSomes;
@ -203,101 +177,75 @@ module MathAdtToDistDst = {
};
};
// let arrayParser =
// (args: array(arg))
// : result(ExpressionTypes.ExpressionTree.node, string) => {
// let samples =
// args
// |> E.A.fmap(
// fun
// | Value(n) => Some(n)
// | _ => None,
// )
// |> E.A.O.concatSomes;
// let outputs = Samples.T.fromSamples(samples);
// let pdf =
// outputs.shape |> E.O.bind(_, Shape.T.toContinuous);
// let shape =
// pdf
// |> E.O.fmap(pdf => {
// let _pdf = Continuous.T.normalize(pdf);
// let cdf = Continuous.T.integral(~cache=None, _pdf);
// SymbolicDist.ContinuousShape.make(_pdf, cdf);
// });
// switch (shape) {
// | Some(s) => Ok(`SymbolicDist(`ContinuousShape(s)))
// | None => Error("Rendering did not work")
// };
// };
// Error("Dotwise exponentiation needs two operands")
let operationParser =
(
name: string,
args: array(result(ExpressionTypes.ExpressionTree.node, string)),
) => {
args: result(array(ExpressionTypes.ExpressionTree.node), string),
):result(ExpressionTypes.ExpressionTree.node,string) => {
let toOkAlgebraic = r => Ok(`AlgebraicCombination(r));
let toOkPointwise = r => Ok(`PointwiseCombination(r));
let toOkTruncate = r => Ok(`Truncate(r));
let toOkFloatFromDist = r => Ok(`FloatFromDist(r));
switch (name, args) {
| ("add", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Add, l, r))
| ("add", _) => Error("Addition needs two operands")
| ("subtract", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Subtract, l, r))
| ("subtract", _) => Error("Subtraction needs two operands")
| ("multiply", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Multiply, l, r))
| ("multiply", _) => Error("Multiplication needs two operands")
| ("dotMultiply", [|Ok(l), Ok(r)|]) => toOkPointwise((`Multiply, l, r))
| ("dotMultiply", _) =>
Error("Dotwise multiplication needs two operands")
| ("rightLogShift", [|Ok(l), Ok(r)|]) => toOkPointwise((`Add, l, r))
| ("rightLogShift", _) => Error("Dotwise addition needs two operands")
| ("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(`SymbolicDist(`Float(lc)))|]) =>
toOkTruncate((Some(lc), None, d))
| ("leftTruncate", _) =>
Error("leftTruncate needs two arguments: the expression and the cutoff")
| ("rightTruncate", [|Ok(d), Ok(`SymbolicDist(`Float(rc)))|]) =>
toOkTruncate((None, Some(rc), d))
| ("rightTruncate", _) =>
Error(
"rightTruncate needs two arguments: the expression and the cutoff",
)
| (
"truncate",
[|
Ok(d),
Ok(`SymbolicDist(`Float(lc))),
Ok(`SymbolicDist(`Float(rc))),
|],
) =>
toOkTruncate((Some(lc), Some(rc), d))
| ("truncate", _) =>
Error("truncate needs three arguments: the expression and both cutoffs")
| ("pdf", [|Ok(d), Ok(`SymbolicDist(`Float(v)))|]) =>
toOkFloatFromDist((`Pdf(v), d))
| ("cdf", [|Ok(d), Ok(`SymbolicDist(`Float(v)))|]) =>
toOkFloatFromDist((`Cdf(v), d))
| ("inv", [|Ok(d), Ok(`SymbolicDist(`Float(v)))|]) =>
toOkFloatFromDist((`Inv(v), d))
| ("mean", [|Ok(d)|]) => toOkFloatFromDist((`Mean, d))
| ("sample", [|Ok(d)|]) => toOkFloatFromDist((`Sample, d))
| _ => Error("This type not currently supported")
};
args
|> E.R.bind(_, args => {
switch (name, args) {
| ("add", [|l, r|]) => toOkAlgebraic((`Add, l, r))
| ("add", _) => Error("Addition needs two operands")
| ("subtract", [|l, r|]) => toOkAlgebraic((`Subtract, l, r))
| ("subtract", _) => Error("Subtraction needs two operands")
| ("multiply", [|l, r|]) => toOkAlgebraic((`Multiply, l, r))
| ("multiply", _) => Error("Multiplication needs two operands")
| ("pow", [|l,r|]) => toOkAlgebraic((`Exponentiate, l, r))
| ("pow", _) => Error("Exponentiation needs two operands")
| ("dotMultiply", [|l, r|]) => toOkPointwise((`Multiply, l, r))
| ("dotMultiply", _) =>
Error("Dotwise multiplication needs two operands")
| ("rightLogShift", [|l, r|]) => toOkPointwise((`Add, l, r))
| ("rightLogShift", _) =>
Error("Dotwise addition needs two operands")
| ("divide", [|l, r|]) => toOkAlgebraic((`Divide, l, r))
| ("divide", _) => Error("Division needs two operands")
| ("leftTruncate", [|d, `SymbolicDist(`Float(lc))|]) =>
toOkTruncate((Some(lc), None, d))
| ("leftTruncate", _) =>
Error(
"leftTruncate needs two arguments: the expression and the cutoff",
)
| ("rightTruncate", [|d, `SymbolicDist(`Float(rc))|]) =>
toOkTruncate((None, Some(rc), d))
| ("rightTruncate", _) =>
Error(
"rightTruncate needs two arguments: the expression and the cutoff",
)
| (
"truncate",
[|d, `SymbolicDist(`Float(lc)), `SymbolicDist(`Float(rc))|],
) =>
toOkTruncate((Some(lc), Some(rc), d))
| ("truncate", _) =>
Error(
"truncate needs three arguments: the expression and both cutoffs",
)
| ("pdf", [|d, `SymbolicDist(`Float(v))|]) =>
toOkFloatFromDist((`Pdf(v), d))
| ("cdf", [|d, `SymbolicDist(`Float(v))|]) =>
toOkFloatFromDist((`Cdf(v), d))
| ("inv", [|d, `SymbolicDist(`Float(v))|]) =>
toOkFloatFromDist((`Inv(v), d))
| ("mean", [|d|]) => toOkFloatFromDist((`Mean, d))
| ("sample", [|d|]) => toOkFloatFromDist((`Sample, d))
| _ => Error("This type not currently supported")
}
});
};
let functionParser = (nodeParser, name, args) => {
let parseArgs = () => args |> E.A.fmap(nodeParser);
let parseArray = ags =>
ags |> E.A.fmap(nodeParser) |> E.A.R.firstErrorOrOpen;
let parseArgs = () => parseArray(args);
switch (name) {
| "normal" => normal(args)
| "lognormal" => lognormal(args)
| "uniform" => uniform(args)
| "beta" => beta(args)
| "to" => to_(args)
| "exponential" => exponential(args)
| "cauchy" => cauchy(args)
| "triangular" => triangular(args)
| "lognormal" => lognormal(args, parseArgs, nodeParser)
| "mm" =>
let weights =
args
@ -338,27 +286,55 @@ module MathAdtToDistDst = {
| "sample"
| "cdf"
| "pdf" => operationParser(name, parseArgs())
| n => Error(n ++ "(...) is not currently supported")
| name =>
parseArgs()
|> E.R.fmap((args: array(ExpressionTypes.ExpressionTree.node)) =>
`FunctionCall((name, args))
)
};
};
let rec nodeParser =
let rec nodeParser:
MathJsonToMathJsAdt.arg =>
result(ExpressionTypes.ExpressionTree.node, string) =
fun
| Value(f) => Ok(`SymbolicDist(`Float(f)))
| Symbol(sym) => Ok(`Symbol(sym))
| Fn({name, args}) => functionParser(nodeParser, name, args)
| _ => {
Error("This type not currently supported");
Error("This type not currently supported")
};
let topLevel =
fun
| Value(_) as r => nodeParser(r)
| Fn(_) as r => nodeParser(r)
// | FunctionAssignment({name, args, expression}) => {
// let evaluatedExpression = run(expression);
// `Function(_ => Ok(evaluatedExpression));
// }
let rec topLevel = (r): result(ExpressionTypes.Program.program, string) =>
switch (r) {
| FunctionAssignment({name, args, expression}) =>
switch (nodeParser(expression)) {
| Ok(r) => Ok([|`Assignment((name, `Function((args, r))))|])
| Error(r) => Error(r)
}
| Value(_) as r => nodeParser(r) |> E.R.fmap(r => [|`Expression(r)|])
| Fn(_) as r => nodeParser(r) |> E.R.fmap(r => [|`Expression(r)|])
| Array(_) => Error("Array not valid as top level")
| Symbol(_) => Error("Symbol not valid as top level")
| Object(_) => Error("Object not valid as top level");
| Symbol(s) => handleSymbol(s) |> E.R.fmap(r => [|`Expression(r)|])
| Object(_) => Error("Object not valid as top level")
| Assignment(name, value) =>
switch (name) {
| Symbol(symbol) =>
nodeParser(value) |> E.R.fmap(r => [|`Assignment((symbol, r))|])
| _ => Error("Symbol not a string")
}
| Blocks(blocks) =>
blocks
|> E.A.fmap(b => topLevel(b))
|> E.A.R.firstErrorOrOpen
|> E.R.fmap(E.A.concatMany)
};
let run = (r): result(ExpressionTypes.ExpressionTree.node, string) =>
let run = (r): result(ExpressionTypes.Program.program, string) =>
r |> MathAdtCleaner.run |> topLevel;
};
@ -369,7 +345,7 @@ module MathAdtToDistDst = {
*/
let pointwiseToRightLogShift = Js.String.replaceByRe([%re "/\.\+/g"], ">>>");
let fromString = str => {
let fromString2 = str => {
/* We feed the user-typed string into Mathjs.parseMath,
which returns a JSON with (hopefully) a single-element array.
This array element is the top-level node of a nested-object tree
@ -390,3 +366,7 @@ let fromString = str => {
let value = E.R.bind(mathJsParse, MathAdtToDistDst.run);
value;
};
let fromString = str => {
fromString2(str);
};

View File

@ -7,6 +7,7 @@ module Algebraic = {
| `Add => (+.)
| `Subtract => (-.)
| `Multiply => ( *. )
| `Exponentiate => ( ** )
| `Divide => (/.);
let applyFn = (t, f1, f2) => {
@ -21,6 +22,7 @@ module Algebraic = {
| `Add => "+"
| `Subtract => "-"
| `Multiply => "*"
| `Exponentiate => ( "**" )
| `Divide => "/";
let format = (a, b, c) => b ++ " " ++ toString(a) ++ " " ++ c;

View File

@ -0,0 +1,5 @@
type t = ExpressionTypes.Program.program;
let last = (r:t) => E.A.last(r) |> E.O.toResult("No rendered lines");
// let run = (p:program) => p |> E.A.last |> E.O.fmap(r =>
// )

View File

@ -60,17 +60,11 @@ let combineShapesUsingSampling =
let shape =
samples
|> E.O.fmap(
Samples.T.fromSamples(
~samplingInputs={
sampleCount:
Some(evaluationParams.samplingInputs.sampleCount),
outputXYPoints:
Some(evaluationParams.samplingInputs.outputXYPoints),
kernelWidth: evaluationParams.samplingInputs.kernelWidth,
},
SamplesToShape.fromSamples(
~samplingInputs=evaluationParams.samplingInputs,
),
)
|> E.O.bind(_, (r) => r.shape)
|> E.O.bind(_, r => r.shape)
|> E.O.toResult("No response");
shape |> E.R.fmap(r => `Normalize(`RenderedDist(r)));
},

View File

@ -1,23 +1,147 @@
let run = (inputs: RenderTypes.DistPlusRenderer.inputs) => {
let toDist = shape =>
// TODO: This setup is more confusing than it should be, there's more work to do in cleanup here.
module Inputs = {
module SamplingInputs = {
type t = {
sampleCount: option(int),
outputXYPoints: option(int),
kernelWidth: option(float),
shapeLength: option(int),
};
};
let defaultRecommendedLength = 10000;
let defaultShouldDownsample = true;
type ingredients = {
guesstimatorString: string,
domain: DistTypes.domain,
unit: DistTypes.distributionUnit,
};
module Ingredients = {
type t = ingredients;
let make =
(
~guesstimatorString,
~domain=DistTypes.Complete,
~unit=DistTypes.UnspecifiedDistribution,
(),
)
: t => {
guesstimatorString,
domain,
unit,
};
};
type inputs = {
distPlusIngredients: ingredients,
samplingInputs: SamplingInputs.t,
environment: ExpressionTypes.ExpressionTree.environment
};
let empty: SamplingInputs.t = {
sampleCount: None,
outputXYPoints: None,
kernelWidth: None,
shapeLength: None,
};
let make =
(
~samplingInputs=empty,
~distPlusIngredients,
~environment=ExpressionTypes.ExpressionTree.Environment.empty,
(),
)
: inputs => {
distPlusIngredients,
samplingInputs,
environment,
};
};
module Internals = {
type inputs = {
samplingInputs: Inputs.SamplingInputs.t,
guesstimatorString: string,
environment: ExpressionTypes.ExpressionTree.environment,
};
let addVariable =
(
{samplingInputs, guesstimatorString, environment}: inputs,
str,
node,
)
: inputs => {
samplingInputs,
guesstimatorString,
environment:
ExpressionTypes.ExpressionTree.Environment.update(environment, str, _ => Some(node))
};
let distPlusRenderInputsToInputs = (inputs: Inputs.inputs): inputs => {
{
samplingInputs: inputs.samplingInputs,
guesstimatorString: inputs.distPlusIngredients.guesstimatorString,
environment: inputs.environment,
};
};
type outputs = {
graph: ExpressionTypes.ExpressionTree.node,
shape: DistTypes.shape,
};
let makeOutputs = (graph, shape): outputs => {graph, shape};
let runNode = (inputs, node) => {
ExpressionTree.toShape(
{
sampleCount: inputs.samplingInputs.sampleCount |> E.O.default(10000),
outputXYPoints:
inputs.samplingInputs.outputXYPoints |> E.O.default(10000),
kernelWidth: inputs.samplingInputs.kernelWidth,
shapeLength: inputs.samplingInputs.shapeLength |> E.O.default(10000),
},
inputs.environment,
node,
);
};
let runProgram = (inputs: inputs, p: ExpressionTypes.Program.program) => {
let ins = ref(inputs);
p
|> E.A.fmap(statement =>
switch (statement) {
| `Assignment(name, node) =>
ins := addVariable(ins^, name, node);
None;
| `Expression(node) => Some(runNode(ins^, node))
}
)
|> E.A.O.concatSomes
|> E.A.R.firstErrorOrOpen;
};
let inputsToShape = (inputs: inputs) => {
MathJsParser.fromString(inputs.guesstimatorString)
|> E.R.bind(_, g => runProgram(inputs, g))
|> E.R.bind(_, r => E.A.last(r) |> E.O.toResult("No rendered lines") |> E.R.fmap(Shape.T.normalize));
};
let outputToDistPlus = (inputs: Inputs.inputs, shape: DistTypes.shape) => {
DistPlus.make(
~shape,
~domain=inputs.distPlusIngredients.domain,
~unit=inputs.distPlusIngredients.unit,
~guesstimatorString=Some(inputs.distPlusIngredients.guesstimatorString),
(),
)
|> DistPlus.T.normalize;
let output =
ShapeRenderer.run({
samplingInputs: inputs.samplingInputs,
guesstimatorString: inputs.distPlusIngredients.guesstimatorString,
symbolicInputs: {
length: inputs.recommendedLength,
},
});
output
|> E.R.fmap((o: RenderTypes.ShapeRenderer.Symbolic.outputs) =>
toDist(o.shape)
);
);
};
};
let run = (inputs: Inputs.inputs) => {
inputs
|> Internals.distPlusRenderInputsToInputs
|> Internals.inputsToShape
|> E.R.fmap(Internals.outputToDistPlus(inputs));
};

View File

View File

@ -1,127 +0,0 @@
module ShapeRenderer = {
module Sampling = {
type inputs = {
sampleCount: option(int),
outputXYPoints: option(int),
kernelWidth: option(float),
};
type samplingStats = {
sampleCount: int,
outputXYPoints: int,
bandwidthXSuggested: float,
bandwidthUnitSuggested: float,
bandwidthXImplemented: float,
bandwidthUnitImplemented: float,
};
type outputs = {
continuousParseParams: option(samplingStats),
shape: option(DistTypes.shape),
};
module Inputs = {
let defaultSampleCount = 5000;
let defaultOutputXYPoints = 10000;
let empty = {
sampleCount: None,
outputXYPoints: None,
kernelWidth: None,
};
type fInputs = {
sampleCount: int,
outputXYPoints: int,
kernelWidth: option(float),
};
let toF = (i: inputs): fInputs => {
sampleCount: i.sampleCount |> E.O.default(defaultSampleCount),
outputXYPoints:
i.outputXYPoints |> E.O.default(defaultOutputXYPoints),
kernelWidth: i.kernelWidth,
};
};
};
module Symbolic = {
type inputs = {length: int};
type outputs = {
graph: ExpressionTypes.ExpressionTree.node,
shape: DistTypes.shape,
};
let make = (graph, shape) => {graph, shape};
};
module Combined = {
type inputs = {
samplingInputs: Sampling.inputs,
symbolicInputs: Symbolic.inputs,
guesstimatorString: string,
};
type outputs = {
symbolic: option(Belt.Result.t(Symbolic.outputs, string)),
sampling: option(Sampling.outputs),
};
let methodUsed = ({symbolic, sampling}:outputs) => switch(symbolic, sampling){
| (Some(Ok(_)), _) => `Symbolic
| (_, Some({shape: Some(_)})) => `Sampling
| _ => `None
}
let getShape = (r: outputs) =>
switch (r.symbolic, r.sampling) {
| (Some(Ok({shape})), _) => Some(shape)
| (_, Some({shape})) => shape
| _ => None
};
};
};
module DistPlusRenderer = {
let defaultRecommendedLength = 10000;
let defaultShouldDownsample = true;
type ingredients = {
guesstimatorString: string,
domain: DistTypes.domain,
unit: DistTypes.distributionUnit,
};
type inputs = {
distPlusIngredients: ingredients,
samplingInputs: ShapeRenderer.Sampling.inputs,
recommendedLength: int,
shouldDownsample: bool,
};
module Ingredients = {
let make =
(
~guesstimatorString,
~domain=DistTypes.Complete,
~unit=DistTypes.UnspecifiedDistribution,
(),
)
: ingredients => {
guesstimatorString,
domain,
unit,
};
};
let make =
(
~samplingInputs=ShapeRenderer.Sampling.Inputs.empty,
~recommendedLength=defaultRecommendedLength,
~shouldDownsample=defaultShouldDownsample,
~distPlusIngredients,
(),
)
: inputs => {
distPlusIngredients,
samplingInputs,
recommendedLength,
shouldDownsample,
};
type outputs = {
shapeRenderOutputs: ShapeRenderer.Combined.outputs,
distPlus: option(DistTypes.distPlus)
}
module Outputs = {
let distplus = (t:outputs) => t.distPlus
let shapeRenderOutputs = (t:outputs) => t.shapeRenderOutputs
let make = (shapeRenderOutputs, distPlus) => {shapeRenderOutputs, distPlus};
}
};

View File

@ -1,39 +0,0 @@
// This transforms an array intersperced with spaces or newlines with a normally formatted one.
// "3 4 5 3 2 1 " -> "[3,4,5,3,2,1]""
let formatMessyArray = str => {
let split = Js.String.splitByRe([%re "/\\n|\\r|\\s/"], str);
if (E.A.length(split) > 20) {
let inner = split |> Js.Array.joinWith(",");
{j|[$inner]|j};
} else {
str;
};
};
let formatString = str => {
str |> formatMessyArray;
};
let runSymbolic = (inputs: RenderTypes.ShapeRenderer.Combined.inputs) => {
let str = formatString(inputs.guesstimatorString);
let graph = MathJsParser.fromString(str);
graph
|> E.R.bind(_, g =>
ExpressionTree.toShape(
inputs.symbolicInputs.length,
{
sampleCount:
inputs.samplingInputs.sampleCount |> E.O.default(10000),
outputXYPoints:
inputs.samplingInputs.outputXYPoints |> E.O.default(10000),
kernelWidth: inputs.samplingInputs.kernelWidth,
},
g,
)
|> E.R.fmap(RenderTypes.ShapeRenderer.Symbolic.make(g))
);
};
let run = (inputs: RenderTypes.ShapeRenderer.Combined.inputs) => {
runSymbolic(inputs);
};

View File

@ -1,185 +0,0 @@
module Types = {
let defaultSampleCount = 5000;
let defaultOutputXYPoints = 10000;
type inputs = {
sampleCount: option(int),
outputXYPoints: option(int),
kernelWidth: option(float),
};
type samplingStats = {
sampleCount: int,
outputXYPoints: int,
bandwidthXSuggested: float,
bandwidthUnitSuggested: float,
bandwidthXImplemented: float,
bandwidthUnitImplemented: float,
};
type outputs = {
continuousParseParams: option(samplingStats),
shape: option(DistTypes.shape),
};
let empty = {sampleCount: None, outputXYPoints: None, kernelWidth: None};
type fInputs = {
sampleCount: int,
outputXYPoints: int,
kernelWidth: option(float),
};
let toF = (i: inputs): fInputs => {
sampleCount: i.sampleCount |> E.O.default(defaultSampleCount),
outputXYPoints: i.outputXYPoints |> E.O.default(defaultOutputXYPoints),
kernelWidth: i.kernelWidth,
};
};
module JS = {
[@bs.deriving abstract]
type distJs = {
xs: array(float),
ys: array(float),
};
let jsToDist = (d: distJs): DistTypes.xyShape => {
xs: xsGet(d),
ys: ysGet(d),
};
[@bs.module "./KdeLibrary.js"]
external samplesToContinuousPdf: (array(float), int, int) => distJs =
"samplesToContinuousPdf";
};
module KDE = {
let normalSampling = (samples, outputXYPoints, kernelWidth) => {
samples
|> JS.samplesToContinuousPdf(_, outputXYPoints, kernelWidth)
|> JS.jsToDist;
};
};
module T = {
type t = array(float);
let splitContinuousAndDiscrete = (sortedArray: t) => {
let continuous = [||];
let discrete = E.FloatFloatMap.empty();
Belt.Array.forEachWithIndex(
sortedArray,
(index, element) => {
let maxIndex = (sortedArray |> Array.length) - 1;
let possiblySimilarElements =
(
switch (index) {
| 0 => [|index + 1|]
| n when n == maxIndex => [|index - 1|]
| _ => [|index - 1, index + 1|]
}
)
|> Belt.Array.map(_, r => sortedArray[r]);
let hasSimilarElement =
Belt.Array.some(possiblySimilarElements, r => r == element);
hasSimilarElement
? E.FloatFloatMap.increment(element, discrete)
: {
let _ = Js.Array.push(element, continuous);
();
};
();
},
);
(continuous, discrete);
};
let xWidthToUnitWidth = (samples, outputXYPoints, xWidth) => {
let xyPointRange = E.A.Sorted.range(samples) |> E.O.default(0.0);
let xyPointWidth = xyPointRange /. float_of_int(outputXYPoints);
xWidth /. xyPointWidth;
};
let formatUnitWidth = w => Jstat.max([|w, 1.0|]) |> int_of_float;
let suggestedUnitWidth = (samples, outputXYPoints) => {
let suggestedXWidth = Bandwidth.nrd0(samples);
xWidthToUnitWidth(samples, outputXYPoints, suggestedXWidth);
};
let kde = (~samples, ~outputXYPoints, width) => {
KDE.normalSampling(samples, outputXYPoints, width);
};
let toShape =
(
~samples: t,
~samplingInputs: Types.fInputs,
(),
) => {
Array.fast_sort(compare, samples);
let (continuousPart, discretePart) = E.A.Sorted.Floats.split(samples);
let length = samples |> E.A.length |> float_of_int;
let discrete: DistTypes.discreteShape =
discretePart
|> E.FloatFloatMap.fmap(r => r /. length)
|> E.FloatFloatMap.toArray
|> XYShape.T.fromZippedArray
|> Discrete.make;
let pdf =
continuousPart |> E.A.length > 5
? {
let _suggestedXWidth = Bandwidth.nrd0(continuousPart);
// todo: This does some recalculating from the last step.
let _suggestedUnitWidth =
suggestedUnitWidth(continuousPart, samplingInputs.outputXYPoints);
let usedWidth =
samplingInputs.kernelWidth |> E.O.default(_suggestedXWidth);
let usedUnitWidth =
xWidthToUnitWidth(
samples,
samplingInputs.outputXYPoints,
usedWidth,
);
let foo: Types.samplingStats = {
sampleCount: samplingInputs.sampleCount,
outputXYPoints: samplingInputs.outputXYPoints,
bandwidthXSuggested: _suggestedXWidth,
bandwidthUnitSuggested: _suggestedUnitWidth,
bandwidthXImplemented: usedWidth,
bandwidthUnitImplemented: usedUnitWidth,
};
continuousPart
|> kde(
~samples=_,
~outputXYPoints=samplingInputs.outputXYPoints,
formatUnitWidth(usedUnitWidth),
)
|> Continuous.make
|> (r => Some((r, foo)));
}
: None;
let shape =
MixedShapeBuilder.buildSimple(
~continuous=pdf |> E.O.fmap(fst),
~discrete=Some(discrete),
);
let samplesParse: Types.outputs = {
continuousParseParams: pdf |> E.O.fmap(snd),
shape,
};
samplesParse;
};
let fromSamples =
(
~samplingInputs=Types.empty,
samples,
) => {
let samplingInputs =
Types.toF(samplingInputs);
toShape(~samples, ~samplingInputs, ());
};
};

View File

@ -0,0 +1,164 @@
module Internals = {
module Types = {
type samplingStats = {
sampleCount: int,
outputXYPoints: int,
bandwidthXSuggested: float,
bandwidthUnitSuggested: float,
bandwidthXImplemented: float,
bandwidthUnitImplemented: float,
};
type outputs = {
continuousParseParams: option(samplingStats),
shape: option(DistTypes.shape),
};
};
module JS = {
[@bs.deriving abstract]
type distJs = {
xs: array(float),
ys: array(float),
};
let jsToDist = (d: distJs): DistTypes.xyShape => {
xs: xsGet(d),
ys: ysGet(d),
};
[@bs.module "./KdeLibrary.js"]
external samplesToContinuousPdf: (array(float), int, int) => distJs =
"samplesToContinuousPdf";
};
module KDE = {
let normalSampling = (samples, outputXYPoints, kernelWidth) => {
samples
|> JS.samplesToContinuousPdf(_, outputXYPoints, kernelWidth)
|> JS.jsToDist;
};
};
module T = {
type t = array(float);
let splitContinuousAndDiscrete = (sortedArray: t) => {
let continuous = [||];
let discrete = E.FloatFloatMap.empty();
Belt.Array.forEachWithIndex(
sortedArray,
(index, element) => {
let maxIndex = (sortedArray |> Array.length) - 1;
let possiblySimilarElements =
(
switch (index) {
| 0 => [|index + 1|]
| n when n == maxIndex => [|index - 1|]
| _ => [|index - 1, index + 1|]
}
)
|> Belt.Array.map(_, r => sortedArray[r]);
let hasSimilarElement =
Belt.Array.some(possiblySimilarElements, r => r == element);
hasSimilarElement
? E.FloatFloatMap.increment(element, discrete)
: {
let _ = Js.Array.push(element, continuous);
();
};
();
},
);
(continuous, discrete);
};
let xWidthToUnitWidth = (samples, outputXYPoints, xWidth) => {
let xyPointRange = E.A.Sorted.range(samples) |> E.O.default(0.0);
let xyPointWidth = xyPointRange /. float_of_int(outputXYPoints);
xWidth /. xyPointWidth;
};
let formatUnitWidth = w => Jstat.max([|w, 1.0|]) |> int_of_float;
let suggestedUnitWidth = (samples, outputXYPoints) => {
let suggestedXWidth = Bandwidth.nrd0(samples);
xWidthToUnitWidth(samples, outputXYPoints, suggestedXWidth);
};
let kde = (~samples, ~outputXYPoints, width) => {
KDE.normalSampling(samples, outputXYPoints, width);
};
};
};
let toShape =
(
~samples: Internals.T.t,
~samplingInputs: ExpressionTypes.ExpressionTree.samplingInputs,
(),
) => {
Array.fast_sort(compare, samples);
let (continuousPart, discretePart) = E.A.Sorted.Floats.split(samples);
let length = samples |> E.A.length |> float_of_int;
let discrete: DistTypes.discreteShape =
discretePart
|> E.FloatFloatMap.fmap(r => r /. length)
|> E.FloatFloatMap.toArray
|> XYShape.T.fromZippedArray
|> Discrete.make;
let pdf =
continuousPart |> E.A.length > 5
? {
let _suggestedXWidth = Bandwidth.nrd0(continuousPart);
// todo: This does some recalculating from the last step.
let _suggestedUnitWidth =
Internals.T.suggestedUnitWidth(
continuousPart,
samplingInputs.outputXYPoints,
);
let usedWidth =
samplingInputs.kernelWidth |> E.O.default(_suggestedXWidth);
let usedUnitWidth =
Internals.T.xWidthToUnitWidth(
samples,
samplingInputs.outputXYPoints,
usedWidth,
);
let samplingStats: Internals.Types.samplingStats = {
sampleCount: samplingInputs.sampleCount,
outputXYPoints: samplingInputs.outputXYPoints,
bandwidthXSuggested: _suggestedXWidth,
bandwidthUnitSuggested: _suggestedUnitWidth,
bandwidthXImplemented: usedWidth,
bandwidthUnitImplemented: usedUnitWidth,
};
continuousPart
|> Internals.T.kde(
~samples=_,
~outputXYPoints=samplingInputs.outputXYPoints,
Internals.T.formatUnitWidth(usedUnitWidth),
)
|> Continuous.make
|> (r => Some((r, samplingStats)));
}
: None;
let shape =
MixedShapeBuilder.buildSimple(
~continuous=pdf |> E.O.fmap(fst),
~discrete=Some(discrete),
);
let samplesParse: Internals.Types.outputs = {
continuousParseParams: pdf |> E.O.fmap(snd),
shape,
};
samplesParse;
};
let fromSamples = (~samplingInputs, samples) => {
toShape(~samples, ~samplingInputs, ());
};

View File

@ -2,6 +2,12 @@ open SymbolicTypes;
module Exponential = {
type t = exponential;
let make = (rate:float): symbolicDist =>
`Exponential(
{
rate:rate
},
);
let pdf = (x, t: t) => Jstat.exponential##pdf(x, t.rate);
let cdf = (x, t: t) => Jstat.exponential##cdf(x, t.rate);
let inv = (p, t: t) => Jstat.exponential##inv(p, t.rate);
@ -12,6 +18,7 @@ module Exponential = {
module Cauchy = {
type t = cauchy;
let make = (local, scale): symbolicDist => `Cauchy({local, scale});
let pdf = (x, t: t) => Jstat.cauchy##pdf(x, t.local, t.scale);
let cdf = (x, t: t) => Jstat.cauchy##cdf(x, t.local, t.scale);
let inv = (p, t: t) => Jstat.cauchy##inv(p, t.local, t.scale);
@ -22,6 +29,10 @@ module Cauchy = {
module Triangular = {
type t = triangular;
let make = (low, medium, high): result(symbolicDist, string) =>
low < medium && medium < high
? Ok(`Triangular({low, medium, high}))
: Error("Triangular values must be increasing order");
let pdf = (x, t: t) => Jstat.triangular##pdf(x, t.low, t.high, t.medium);
let cdf = (x, t: t) => Jstat.triangular##cdf(x, t.low, t.high, t.medium);
let inv = (p, t: t) => Jstat.triangular##inv(p, t.low, t.high, t.medium);
@ -32,6 +43,7 @@ module Triangular = {
module Normal = {
type t = normal;
let make = (mean, stdev): symbolicDist => `Normal({mean, stdev});
let pdf = (x, t: t) => Jstat.normal##pdf(x, t.mean, t.stdev);
let cdf = (x, t: t) => Jstat.normal##cdf(x, t.mean, t.stdev);
@ -75,6 +87,7 @@ module Normal = {
module Beta = {
type t = beta;
let make = (alpha, beta) => `Beta({alpha, beta});
let pdf = (x, t: t) => Jstat.beta##pdf(x, t.alpha, t.beta);
let cdf = (x, t: t) => Jstat.beta##cdf(x, t.alpha, t.beta);
let inv = (p, t: t) => Jstat.beta##inv(p, t.alpha, t.beta);
@ -85,6 +98,7 @@ module Beta = {
module Lognormal = {
type t = lognormal;
let make = (mu, sigma) => `Lognormal({mu, sigma});
let pdf = (x, t: t) => Jstat.lognormal##pdf(x, t.mu, t.sigma);
let cdf = (x, t: t) => Jstat.lognormal##cdf(x, t.mu, t.sigma);
let inv = (p, t: t) => Jstat.lognormal##inv(p, t.mu, t.sigma);
@ -131,6 +145,7 @@ module Lognormal = {
module Uniform = {
type t = uniform;
let make = (low, high) => `Uniform({low, high});
let pdf = (x, t: t) => Jstat.uniform##pdf(x, t.low, t.high);
let cdf = (x, t: t) => Jstat.uniform##cdf(x, t.low, t.high);
let inv = (p, t: t) => Jstat.uniform##inv(p, t.low, t.high);
@ -146,6 +161,7 @@ module Uniform = {
module Float = {
type t = float;
let make = t => `Float(t);
let pdf = (x, t: t) => x == t ? 1.0 : 0.0;
let cdf = (x, t: t) => x >= t ? 1.0 : 0.0;
let inv = (p, t: t) => p < t ? 0.0 : 1.0;
@ -317,13 +333,14 @@ module T = {
switch (d) {
| `Float(v) =>
Discrete(
Discrete.make(~integralSumCache=Some(1.0), {xs: [|v|], ys: [|1.0|]}),
Discrete.make(
~integralSumCache=Some(1.0),
{xs: [|v|], ys: [|1.0|]},
),
)
| _ =>
let xs = interpolateXs(~xSelection=`ByWeight, d, sampleCount);
let ys = xs |> E.A.fmap(x => pdf(x, d));
Continuous(
Continuous.make(~integralSumCache=Some(1.0), {xs, ys}),
);
Continuous(Continuous.make(~integralSumCache=Some(1.0), {xs, ys}));
};
};

View File

@ -17,12 +17,10 @@ let propValue = (t: Prop.Value.t) => {
switch (t) {
| SelectSingle(r) => r |> ReasonReact.string
| ConditionalArray(r) => "Array" |> ReasonReact.string
| DistPlusIngredients((r: RenderTypes.DistPlusRenderer.ingredients)) =>
| DistPlusIngredients((r: DistPlusRenderer.Inputs.ingredients)) =>
let newDistribution =
RenderTypes.DistPlusRenderer.make(
DistPlusRenderer.Inputs.make(
~distPlusIngredients=r,
~recommendedLength=10000,
~shouldDownsample=true,
(),
)
|> DistPlusRenderer.run;

View File

@ -9,7 +9,7 @@ module Value = {
| DateTime(MomentRe.Moment.t)
| FloatPoint(float)
| Probability(float)
| DistPlusIngredients(RenderTypes.DistPlusRenderer.ingredients)
| DistPlusIngredients(DistPlusRenderer.Inputs.ingredients)
| ConditionalArray(array(conditional))
| FloatCdf(string);
@ -85,7 +85,7 @@ module ValueCluster = {
[ | `combination(range(MomentRe.Moment.t)) | `item(string)],
)
| Probability([ | `item(string)])
| DistPlusIngredients([ | `item(RenderTypes.DistPlusRenderer.ingredients)])
| DistPlusIngredients([ | `item(DistPlusRenderer.Inputs.ingredients)])
| ConditionalArray([ | `item(array(conditional))])
| FloatCdf([ | `item(string)]);
};

View File

@ -110,7 +110,7 @@ module Model = {
// TODO: Fixe number that integral is calculated for
let getGlobalCatastropheChance = dateTime => {
GlobalCatastrophe.makeI(MomentRe.momentNow())
|> RenderTypes.DistPlusRenderer.make(~distPlusIngredients=_, ())
|> DistPlusRenderer.Inputs.make(~distPlusIngredients=_, ())
|> DistPlusRenderer.run
|> E.R.bind(_, r =>
r
@ -157,7 +157,7 @@ module Model = {
};
let distPlusIngredients =
RenderTypes.DistPlusRenderer.Ingredients.make(
DistPlusRenderer.Inputs.Ingredients.make(
~guesstimatorString=str,
~domain=Complete,
~unit=UnspecifiedDistribution,
@ -167,7 +167,7 @@ module Model = {
| CHANCE_OF_EXISTENCE =>
Prop.Value.DistPlusIngredients(
RenderTypes.DistPlusRenderer.Ingredients.make(
DistPlusRenderer.Inputs.Ingredients.make(
~guesstimatorString=
GuesstimatorDist.min(
GlobalCatastrophe.guesstimatorString,

View File

@ -1,7 +1,7 @@
let guesstimatorString = "uniform(1, 100)";
let makeI = (currentDateTime: MomentRe.Moment.t) => {
RenderTypes.DistPlusRenderer.Ingredients.make(
DistPlusRenderer.Inputs.Ingredients.make(
~guesstimatorString,
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
~domain=RightLimited({xPoint: 300.0, excludingProbabilityMass: 0.3}),

View File

@ -2,7 +2,7 @@ let guesstimatorString = age =>
GuesstimatorDist.normal(72.0 -. age, 5.0 -. age *. 0.01);
let makeI = (age: float) => {
RenderTypes.DistPlusRenderer.Ingredients.make(
DistPlusRenderer.Inputs.Ingredients.make(
~guesstimatorString=guesstimatorString(age),
~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
~domain=RightLimited({xPoint: 300.0, excludingProbabilityMass: 0.3}),