Merge pull request #68 from foretold-app/variable-test
Adds simple variables and functions
This commit is contained in:
commit
65f1485a55
|
@ -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);
|
||||
})
|
||||
|
|
|
@ -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}),
|
||||
(),
|
||||
|
|
|
@ -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, ()),
|
||||
)}
|
||||
</>;
|
||||
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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},
|
||||
],
|
||||
};
|
|
@ -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];
|
||||
|
@ -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,
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
open ExpressionTypes.ExpressionTree;
|
||||
|
||||
let toShape = (intendedShapeLength: int, samplingInputs, node: node) => {
|
||||
let renderResult =
|
||||
`Render(`Normalize(node))
|
||||
let toLeaf = (samplingInputs, environment, node: node) => {
|
||||
node
|
||||
|> ExpressionTreeEvaluator.toLeaf({
|
||||
samplingInputs,
|
||||
intendedShapeLength,
|
||||
environment,
|
||||
evaluateNode: ExpressionTreeEvaluator.toLeaf,
|
||||
});
|
||||
};
|
||||
|
||||
let toShape = (samplingInputs, environment, node: node) => {
|
||||
let renderResult =
|
||||
`Render(`Normalize(node)) |> toLeaf(samplingInputs, environment);
|
||||
|
||||
switch (renderResult) {
|
||||
| Ok(`RenderedDist(shape)) => Ok(shape)
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
122
src/distPlus/expressionTree/Functions.re
Normal file
122
src/distPlus/expressionTree/Functions.re
Normal 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")
|
||||
};
|
|
@ -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);
|
||||
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")) {
|
||||
| (Some(Value(mean)), Some(Value(stdev)), _, _) =>
|
||||
| (Ok(mean), Ok(stdev), _, _) =>
|
||||
Ok(
|
||||
`SymbolicDist(
|
||||
SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev),
|
||||
),
|
||||
`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))
|
||||
)
|
||||
| (_, _, 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 => {
|
||||
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");
|
||||
|
||||
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,62 +177,42 @@ 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));
|
||||
args
|
||||
|> E.R.bind(_, args => {
|
||||
switch (name, args) {
|
||||
| ("add", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Add, l, r))
|
||||
| ("add", [|l, r|]) => toOkAlgebraic((`Add, l, r))
|
||||
| ("add", _) => Error("Addition needs two operands")
|
||||
| ("subtract", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Subtract, l, r))
|
||||
| ("subtract", [|l, r|]) => toOkAlgebraic((`Subtract, l, r))
|
||||
| ("subtract", _) => Error("Subtraction needs two operands")
|
||||
| ("multiply", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Multiply, l, r))
|
||||
| ("multiply", [|l, r|]) => toOkAlgebraic((`Multiply, l, r))
|
||||
| ("multiply", _) => Error("Multiplication needs two operands")
|
||||
| ("dotMultiply", [|Ok(l), Ok(r)|]) => toOkPointwise((`Multiply, l, r))
|
||||
| ("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", [|Ok(l), Ok(r)|]) => toOkPointwise((`Add, l, r))
|
||||
| ("rightLogShift", _) => Error("Dotwise addition needs two operands")
|
||||
| ("divide", [|Ok(l), Ok(r)|]) => toOkAlgebraic((`Divide, l, r))
|
||||
| ("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")
|
||||
| ("pow", _) => Error("Exponentiation is not yet supported.")
|
||||
| ("leftTruncate", [|Ok(d), Ok(`SymbolicDist(`Float(lc)))|]) =>
|
||||
| ("leftTruncate", [|d, `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)))|]) =>
|
||||
Error(
|
||||
"leftTruncate needs two arguments: the expression and the cutoff",
|
||||
)
|
||||
| ("rightTruncate", [|d, `SymbolicDist(`Float(rc))|]) =>
|
||||
toOkTruncate((None, Some(rc), d))
|
||||
| ("rightTruncate", _) =>
|
||||
Error(
|
||||
|
@ -266,38 +220,32 @@ module MathAdtToDistDst = {
|
|||
)
|
||||
| (
|
||||
"truncate",
|
||||
[|
|
||||
Ok(d),
|
||||
Ok(`SymbolicDist(`Float(lc))),
|
||||
Ok(`SymbolicDist(`Float(rc))),
|
||||
|],
|
||||
[|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", [|Ok(d), Ok(`SymbolicDist(`Float(v)))|]) =>
|
||||
Error(
|
||||
"truncate needs three arguments: the expression and both cutoffs",
|
||||
)
|
||||
| ("pdf", [|d, `SymbolicDist(`Float(v))|]) =>
|
||||
toOkFloatFromDist((`Pdf(v), d))
|
||||
| ("cdf", [|Ok(d), Ok(`SymbolicDist(`Float(v)))|]) =>
|
||||
| ("cdf", [|d, `SymbolicDist(`Float(v))|]) =>
|
||||
toOkFloatFromDist((`Cdf(v), d))
|
||||
| ("inv", [|Ok(d), Ok(`SymbolicDist(`Float(v)))|]) =>
|
||||
| ("inv", [|d, `SymbolicDist(`Float(v))|]) =>
|
||||
toOkFloatFromDist((`Inv(v), d))
|
||||
| ("mean", [|Ok(d)|]) => toOkFloatFromDist((`Mean, d))
|
||||
| ("sample", [|Ok(d)|]) => toOkFloatFromDist((`Sample, 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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
5
src/distPlus/expressionTree/Program.re
Normal file
5
src/distPlus/expressionTree/Program.re
Normal 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 =>
|
||||
// )
|
|
@ -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)));
|
||||
},
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
|
0
src/distPlus/renderers/ProgramRunner.re
Normal file
0
src/distPlus/renderers/ProgramRunner.re
Normal 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};
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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, ());
|
||||
};
|
||||
};
|
164
src/distPlus/renderers/samplesRenderer/SamplesToShape.re
Normal file
164
src/distPlus/renderers/samplesRenderer/SamplesToShape.re
Normal 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, ());
|
||||
};
|
|
@ -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}));
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}),
|
||||
|
|
|
@ -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}),
|
||||
|
|
Loading…
Reference in New Issue
Block a user