Added DistBuilder3
This commit is contained in:
parent
f662ccd6c6
commit
64eef2b169
|
@ -1,21 +1,12 @@
|
||||||
open Jest;
|
open Jest;
|
||||||
open Expect;
|
open Expect;
|
||||||
|
|
||||||
let json = Mathjs.parseMath("mm(normal(5,2), normal(10))");
|
|
||||||
|
|
||||||
describe("Shape", () => {
|
describe("Shape", () => {
|
||||||
describe("Parser", () => {
|
describe("Parser", () => {
|
||||||
test("", () => {
|
test("", () => {
|
||||||
let parsed1 = MathJsParser.parseMathjs(json);
|
let parsed1 = MathJsParser.fromString("mm(normal(0,1), normal(10,1))");
|
||||||
let parsed2 =
|
Js.log(parsed1 |> E.R.fmap(Jstat.toString));
|
||||||
(
|
Js.log(parsed1 |> E.R.fmap(Jstat.toShape(20)));
|
||||||
switch (parsed1 |> E.O.fmap(MathJsParser.toValue)) {
|
|
||||||
| Some(Ok(r)) => Some(r)
|
|
||||||
| _ => None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|> E.O.fmap(Jstat.toString);
|
|
||||||
Js.log2("YOYOYYO", parsed2);
|
|
||||||
expect(1.0) |> toEqual(1.0);
|
expect(1.0) |> toEqual(1.0);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@ type route =
|
||||||
| Model(string)
|
| Model(string)
|
||||||
| DistBuilder
|
| DistBuilder
|
||||||
| DistBuilder2
|
| DistBuilder2
|
||||||
|
| DistBuilder3
|
||||||
| Home
|
| Home
|
||||||
| NotFound;
|
| NotFound;
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ let routeToPath = route =>
|
||||||
| Model(modelId) => "/m/" ++ modelId
|
| Model(modelId) => "/m/" ++ modelId
|
||||||
| DistBuilder => "/dist-builder"
|
| DistBuilder => "/dist-builder"
|
||||||
| DistBuilder2 => "/dist-builder2"
|
| DistBuilder2 => "/dist-builder2"
|
||||||
|
| DistBuilder3 => "/dist-builder3"
|
||||||
| Home => "/"
|
| Home => "/"
|
||||||
| _ => "/"
|
| _ => "/"
|
||||||
};
|
};
|
||||||
|
@ -81,6 +83,9 @@ module Menu = {
|
||||||
<Item href={routeToPath(DistBuilder2)} key="dist-builder-2">
|
<Item href={routeToPath(DistBuilder2)} key="dist-builder-2">
|
||||||
{"Dist Builder 2" |> E.ste}
|
{"Dist Builder 2" |> E.ste}
|
||||||
</Item>
|
</Item>
|
||||||
|
<Item href={routeToPath(DistBuilder3)} key="dist-builder-3">
|
||||||
|
{"Dist Builder 3" |> E.ste}
|
||||||
|
</Item>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -94,6 +99,7 @@ let make = () => {
|
||||||
| ["m", modelId] => Model(modelId)
|
| ["m", modelId] => Model(modelId)
|
||||||
| ["dist-builder"] => DistBuilder
|
| ["dist-builder"] => DistBuilder
|
||||||
| ["dist-builder2"] => DistBuilder2
|
| ["dist-builder2"] => DistBuilder2
|
||||||
|
| ["dist-builder3"] => DistBuilder3
|
||||||
| [] => Home
|
| [] => Home
|
||||||
| _ => NotFound
|
| _ => NotFound
|
||||||
};
|
};
|
||||||
|
@ -108,8 +114,9 @@ let make = () => {
|
||||||
}
|
}
|
||||||
| DistBuilder => <DistBuilder />
|
| DistBuilder => <DistBuilder />
|
||||||
| DistBuilder2 => <DistBuilder2 />
|
| DistBuilder2 => <DistBuilder2 />
|
||||||
|
| DistBuilder3 => <DistBuilder3 />
|
||||||
| Home => <div> {"Welcome" |> E.ste} </div>
|
| Home => <div> {"Welcome" |> E.ste} </div>
|
||||||
| _ => <div> {"Page is not found" |> E.ste} </div>
|
| _ => <div> {"Page is not found" |> E.ste} </div>
|
||||||
}}
|
}}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
114
src/components/DistBuilder3.re
Normal file
114
src/components/DistBuilder3.re
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
open BsReform;
|
||||||
|
open Antd.Grid;
|
||||||
|
|
||||||
|
module FormConfig = [%lenses type state = {guesstimatorString: string}];
|
||||||
|
|
||||||
|
module Form = ReForm.Make(FormConfig);
|
||||||
|
|
||||||
|
let schema = Form.Validation.Schema([||]);
|
||||||
|
|
||||||
|
module FieldString = {
|
||||||
|
[@react.component]
|
||||||
|
let make = (~field, ~label) => {
|
||||||
|
<Form.Field
|
||||||
|
field
|
||||||
|
render={({handleChange, error, value, validate}) =>
|
||||||
|
<Antd.Form.Item label={label |> E.ste}>
|
||||||
|
<Antd.Input
|
||||||
|
value
|
||||||
|
onChange={BsReform.Helpers.handleChange(handleChange)}
|
||||||
|
onBlur={_ => validate()}
|
||||||
|
/>
|
||||||
|
</Antd.Form.Item>
|
||||||
|
}
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Styles = {
|
||||||
|
open Css;
|
||||||
|
let dist = style([padding(em(1.))]);
|
||||||
|
let spacer = style([marginTop(em(1.))]);
|
||||||
|
};
|
||||||
|
|
||||||
|
module DemoDist = {
|
||||||
|
[@react.component]
|
||||||
|
let make = (~guesstimatorString: string) => {
|
||||||
|
let parsed1 = MathJsParser.fromString(guesstimatorString);
|
||||||
|
let shape =
|
||||||
|
switch (parsed1) {
|
||||||
|
| Ok(r) => Some(Jstat.toShape(10000, r))
|
||||||
|
| _ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let str =
|
||||||
|
switch (parsed1) {
|
||||||
|
| Ok(r) => Jstat.toString(r)
|
||||||
|
| Error(e) => e
|
||||||
|
};
|
||||||
|
|
||||||
|
let inside =
|
||||||
|
shape
|
||||||
|
|> E.O.fmap(shape => {
|
||||||
|
let distPlus =
|
||||||
|
Distributions.DistPlus.make(
|
||||||
|
~shape=Continuous(Distributions.Continuous.fromShape(shape)),
|
||||||
|
~domain=Complete,
|
||||||
|
~unit=UnspecifiedDistribution,
|
||||||
|
~guesstimatorString=None,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
|> Distributions.DistPlus.T.scaleToIntegralSum(~intendedSum=1.0);
|
||||||
|
<DistPlusPlot distPlus />;
|
||||||
|
})
|
||||||
|
|> E.O.default(ReasonReact.null);
|
||||||
|
<Antd.Card title={"Distribution" |> E.ste}>
|
||||||
|
<div className=Styles.spacer />
|
||||||
|
inside
|
||||||
|
{str |> ReasonReact.string}
|
||||||
|
</Antd.Card>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make = () => {
|
||||||
|
let reform =
|
||||||
|
Form.use(
|
||||||
|
~validationStrategy=OnDemand,
|
||||||
|
~schema,
|
||||||
|
~onSubmit=({state}) => {None},
|
||||||
|
~initialState={guesstimatorString: "lognormal(6.1, 3)"},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let demoDist =
|
||||||
|
React.useMemo1(
|
||||||
|
() => {
|
||||||
|
<DemoDist
|
||||||
|
guesstimatorString={reform.state.values.guesstimatorString}
|
||||||
|
/>
|
||||||
|
},
|
||||||
|
[|reform.state.values.guesstimatorString|],
|
||||||
|
);
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className=Styles.spacer />
|
||||||
|
demoDist
|
||||||
|
<div className=Styles.spacer />
|
||||||
|
<Antd.Card title={"Distribution Form" |> E.ste}>
|
||||||
|
<Form.Provider value=reform>
|
||||||
|
<Antd.Form>
|
||||||
|
<Row _type=`flex>
|
||||||
|
<Col span=12>
|
||||||
|
<FieldString
|
||||||
|
field=FormConfig.GuesstimatorString
|
||||||
|
label="Guesstimator String"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Antd.Form>
|
||||||
|
</Form.Provider>
|
||||||
|
</Antd.Card>
|
||||||
|
<div className=Styles.spacer />
|
||||||
|
</div>;
|
||||||
|
};
|
|
@ -216,20 +216,31 @@ module DistPlusChart = {
|
||||||
|> E.O.fmap(Distributions.Continuous.getShape);
|
|> E.O.fmap(Distributions.Continuous.getShape);
|
||||||
let range = T.xTotalRange(distPlus);
|
let range = T.xTotalRange(distPlus);
|
||||||
|
|
||||||
// We subtract a bit from the range to make sure that it fits. Maybe this should be done in d3 instead.
|
// // We subtract a bit from the range to make sure that it fits. Maybe this should be done in d3 instead.
|
||||||
let minX =
|
// let minX =
|
||||||
switch (
|
// switch (
|
||||||
distPlus |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.01),
|
// distPlus
|
||||||
range,
|
// |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.0001),
|
||||||
) {
|
// range,
|
||||||
| (min, Some(range)) => Some(min -. range *. 0.001)
|
// ) {
|
||||||
| _ => None
|
// | (min, Some(range)) => Some(min -. range *. 0.001)
|
||||||
};
|
// | _ => None
|
||||||
|
// };
|
||||||
|
|
||||||
|
let minX = {
|
||||||
|
distPlus |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.00001);
|
||||||
|
};
|
||||||
|
|
||||||
let maxX = {
|
let maxX = {
|
||||||
distPlus |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.99);
|
distPlus |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.99);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Js.log3(
|
||||||
|
distPlus |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.0001),
|
||||||
|
minX,
|
||||||
|
distPlus |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.98),
|
||||||
|
);
|
||||||
|
|
||||||
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson;
|
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson;
|
||||||
let toDiscreteProbabilityMass =
|
let toDiscreteProbabilityMass =
|
||||||
distPlus |> Distributions.DistPlus.T.toDiscreteProbabilityMass;
|
distPlus |> Distributions.DistPlus.T.toDiscreteProbabilityMass;
|
||||||
|
|
|
@ -102,15 +102,15 @@ module Mixed = {
|
||||||
|
|
||||||
let min = dist =>
|
let min = dist =>
|
||||||
switch (dist) {
|
switch (dist) {
|
||||||
| `Normal(n) => Normal.inv(0.01, n)
|
| `Normal(n) => Normal.inv(0.0001, n)
|
||||||
| `Lognormal(n) => Lognormal.inv(0.01, n)
|
| `Lognormal(n) => Lognormal.inv(0.0001, n)
|
||||||
| `Uniform({low}) => low
|
| `Uniform({low}) => low
|
||||||
};
|
};
|
||||||
|
|
||||||
let max = dist =>
|
let max = dist =>
|
||||||
switch (dist) {
|
switch (dist) {
|
||||||
| `Normal(n) => Normal.inv(0.99, n)
|
| `Normal(n) => Normal.inv(0.9999, n)
|
||||||
| `Lognormal(n) => Lognormal.inv(0.99, n)
|
| `Lognormal(n) => Lognormal.inv(0.9999, n)
|
||||||
| `Uniform({high}) => high
|
| `Uniform({high}) => high
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ module Mixed = {
|
||||||
switch (xSelection) {
|
switch (xSelection) {
|
||||||
| `Linear => Functions.range(min(dist), max(dist), sampleCount)
|
| `Linear => Functions.range(min(dist), max(dist), sampleCount)
|
||||||
| `ByWeight =>
|
| `ByWeight =>
|
||||||
Functions.range(0.001, 0.999, sampleCount)
|
Functions.range(0.00001, 0.99999, sampleCount)
|
||||||
|> E.A.fmap(x => inv(x, dist))
|
|> E.A.fmap(x => inv(x, dist))
|
||||||
};
|
};
|
||||||
let ys = xs |> E.A.fmap(r => pdf(r, dist));
|
let ys = xs |> E.A.fmap(r => pdf(r, dist));
|
||||||
|
@ -151,7 +151,7 @@ module PointwiseAddDistributionsWeighted = {
|
||||||
dists |> E.A.fmap(d => d |> fst |> Mixed.min) |> Functions.min;
|
dists |> E.A.fmap(d => d |> fst |> Mixed.min) |> Functions.min;
|
||||||
|
|
||||||
let max = (dists: t) =>
|
let max = (dists: t) =>
|
||||||
dists |> E.A.fmap(d => d |> fst |> Mixed.min) |> Functions.min;
|
dists |> E.A.fmap(d => d |> fst |> Mixed.max) |> Functions.max;
|
||||||
|
|
||||||
let toShape = (dists: t, sampleCount: int) => {
|
let toShape = (dists: t, sampleCount: int) => {
|
||||||
let xs = Functions.range(min(dists), max(dists), sampleCount);
|
let xs = Functions.range(min(dists), max(dists), sampleCount);
|
||||||
|
@ -180,4 +180,10 @@ let toString = (r: bigDist) =>
|
||||||
| `Dist(d) => Mixed.toString(d)
|
| `Dist(d) => Mixed.toString(d)
|
||||||
| `PointwiseCombination(d) =>
|
| `PointwiseCombination(d) =>
|
||||||
PointwiseAddDistributionsWeighted.toString(d)
|
PointwiseAddDistributionsWeighted.toString(d)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let toShape = n =>
|
||||||
|
fun
|
||||||
|
| `Dist(d) => Mixed.toShape(~xSelection=`ByWeight, d, n)
|
||||||
|
| `PointwiseCombination(d) =>
|
||||||
|
PointwiseAddDistributionsWeighted.toShape(d, n);
|
|
@ -1,14 +1,16 @@
|
||||||
open Jstat;
|
open Jstat;
|
||||||
|
|
||||||
type arg =
|
type arg =
|
||||||
|
| Symbol(string)
|
||||||
| Value(float)
|
| Value(float)
|
||||||
| Fn(fn)
|
| Fn(fn)
|
||||||
|
| Array(array(arg))
|
||||||
and fn = {
|
and fn = {
|
||||||
name: string,
|
name: string,
|
||||||
args: array(arg),
|
args: array(arg),
|
||||||
};
|
};
|
||||||
|
|
||||||
let rec parseMathjs = (j: Js.Json.t) => {
|
let rec parseMathjs = (j: Js.Json.t) =>
|
||||||
Json.Decode.(
|
Json.Decode.(
|
||||||
switch (field("mathjs", string, j)) {
|
switch (field("mathjs", string, j)) {
|
||||||
| "FunctionNode" =>
|
| "FunctionNode" =>
|
||||||
|
@ -19,19 +21,56 @@ let rec parseMathjs = (j: Js.Json.t) => {
|
||||||
args: args |> E.A.O.concatSomes,
|
args: args |> E.A.O.concatSomes,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
| "ConstantNode" => Some(Value(field("value", float, j)))
|
| "OperatorNode" =>
|
||||||
| _ => None
|
let args = j |> field("args", array(parseMathjs));
|
||||||
|
Some(
|
||||||
|
Fn({
|
||||||
|
name: j |> field("fn", string),
|
||||||
|
args: args |> E.A.O.concatSomes,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
| "ConstantNode" => Some(Value(field("value", Json.Decode.float, j)))
|
||||||
|
| "ArrayNode" =>
|
||||||
|
let items = field("items", array(parseMathjs), j);
|
||||||
|
Some(Array(items |> E.A.O.concatSomes));
|
||||||
|
| "SymbolNode" => Some(Symbol(field("name", string, j)))
|
||||||
|
| n =>
|
||||||
|
Js.log2("Couldn't parse mathjs node", j);
|
||||||
|
None;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
let normal = (r): result(bigDist, string) =>
|
// let logHigh = math.log(high);
|
||||||
r
|
// let logLow = math.log(low);
|
||||||
|> (
|
|
||||||
fun
|
// let mean = (math.mean(logHigh, logLow)).toFixed(3);
|
||||||
| [|Value(mean), Value(stdev)|] => Ok(`Dist(`Normal({mean, stdev})))
|
// let stdev = ((logHigh-logLow) / (2*1.645)).toFixed(3);
|
||||||
| _ => Error("Wrong number of variables in normal distribution")
|
|
||||||
);
|
let normal: array(arg) => result(bigDist, string) =
|
||||||
|
fun
|
||||||
|
| [|Value(mean), Value(stdev)|] => Ok(`Dist(`Normal({mean, stdev})))
|
||||||
|
| _ => Error("Wrong number of variables in normal distribution");
|
||||||
|
|
||||||
|
let lognormal: array(arg) => result(bigDist, string) =
|
||||||
|
fun
|
||||||
|
| [|Value(mu), Value(sigma)|] => Ok(`Dist(`Lognormal({mu, sigma})))
|
||||||
|
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||||
|
|
||||||
|
let to_: array(arg) => result(bigDist, string) =
|
||||||
|
fun
|
||||||
|
| [|Value(low), Value(high)|] => {
|
||||||
|
let logLow = Js.Math.log(low);
|
||||||
|
let logHigh = Js.Math.log(high);
|
||||||
|
let mu = Functions.mean([|logLow, logHigh|]);
|
||||||
|
let sigma = (logHigh -. logLow) /. (2.0 *. 1.645);
|
||||||
|
Ok(`Dist(`Lognormal({mu, sigma})));
|
||||||
|
}
|
||||||
|
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||||
|
|
||||||
|
let uniform: array(arg) => result(bigDist, string) =
|
||||||
|
fun
|
||||||
|
| [|Value(low), Value(high)|] => Ok(`Dist(`Uniform({low, high})))
|
||||||
|
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||||
|
|
||||||
let rec toValue = (r): result(bigDist, string) =>
|
let rec toValue = (r): result(bigDist, string) =>
|
||||||
r
|
r
|
||||||
|
@ -39,14 +78,16 @@ let rec toValue = (r): result(bigDist, string) =>
|
||||||
fun
|
fun
|
||||||
| Value(_) => Error("Top level can't be value")
|
| Value(_) => Error("Top level can't be value")
|
||||||
| Fn({name: "normal", args}) => normal(args)
|
| Fn({name: "normal", args}) => normal(args)
|
||||||
|
| Fn({name: "lognormal", args}) => lognormal(args)
|
||||||
|
| Fn({name: "uniform", args}) => uniform(args)
|
||||||
|
| Fn({name: "to", args}) => to_(args)
|
||||||
| Fn({name: "mm", args}) => {
|
| Fn({name: "mm", args}) => {
|
||||||
let dists: array(dist) =
|
let dists: array(dist) =
|
||||||
args
|
args
|
||||||
|> E.A.fmap(toValue)
|
|> E.A.fmap(toValue)
|
||||||
|> E.A.fmap(
|
|> E.A.fmap(
|
||||||
fun
|
fun
|
||||||
| Ok(`Dist(`Normal({mean, stdev}))) =>
|
| Ok(`Dist(n)) => Some(n)
|
||||||
Some(`Normal({mean, stdev}))
|
|
||||||
| _ => None,
|
| _ => None,
|
||||||
)
|
)
|
||||||
|> E.A.O.concatSomes;
|
|> E.A.O.concatSomes;
|
||||||
|
@ -55,4 +96,15 @@ let rec toValue = (r): result(bigDist, string) =>
|
||||||
Ok(`PointwiseCombination(inputs));
|
Ok(`PointwiseCombination(inputs));
|
||||||
}
|
}
|
||||||
| Fn({name}) => Error(name ++ ": name not found")
|
| Fn({name}) => Error(name ++ ": name not found")
|
||||||
);
|
| Array(_) => Error("Array not valid as top level")
|
||||||
|
| Symbol(_) => Error("Symbol not valid as top level")
|
||||||
|
);
|
||||||
|
|
||||||
|
let fromString = str =>
|
||||||
|
Mathjs.parseMath(str)
|
||||||
|
|> E.R.bind(_, r =>
|
||||||
|
switch (parseMathjs(r)) {
|
||||||
|
| Some(r) => toValue(r)
|
||||||
|
| None => Error("Second parse failed")
|
||||||
|
}
|
||||||
|
);
|
|
@ -1,2 +1,10 @@
|
||||||
[@bs.module "./MathjsWrapper.js"]
|
[@bs.module "./MathjsWrapper.js"]
|
||||||
external parseMath: string => Js.Json.t = "parseMath";
|
external parseMathExt: string => Js.Json.t = "parseMath";
|
||||||
|
|
||||||
|
let parseMath = (str: string): result(Js.Json.t, string) =>
|
||||||
|
switch (parseMathExt(str)) {
|
||||||
|
| exception (Js.Exn.Error(err)) =>
|
||||||
|
Error(Js.Exn.message(err) |> E.O.default("MathJS Parse Error"))
|
||||||
|
| exception _ => Error("MathJS Parse Error")
|
||||||
|
| j => Ok(j)
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user