Added DistBuilder3

This commit is contained in:
Ozzie Gooen 2020-03-24 00:04:48 +00:00
parent f662ccd6c6
commit 64eef2b169
7 changed files with 233 additions and 44 deletions

View File

@ -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);
}) })
}) })

View File

@ -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>;
}; };

View 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>;
};

View File

@ -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;

View File

@ -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);

View File

@ -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")
}
);

View File

@ -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)
};