Added DistBuilder3
This commit is contained in:
parent
f662ccd6c6
commit
64eef2b169
|
@ -1,21 +1,12 @@
|
|||
open Jest;
|
||||
open Expect;
|
||||
|
||||
let json = Mathjs.parseMath("mm(normal(5,2), normal(10))");
|
||||
|
||||
describe("Shape", () => {
|
||||
describe("Parser", () => {
|
||||
test("", () => {
|
||||
let parsed1 = MathJsParser.parseMathjs(json);
|
||||
let parsed2 =
|
||||
(
|
||||
switch (parsed1 |> E.O.fmap(MathJsParser.toValue)) {
|
||||
| Some(Ok(r)) => Some(r)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
|> E.O.fmap(Jstat.toString);
|
||||
Js.log2("YOYOYYO", parsed2);
|
||||
let parsed1 = MathJsParser.fromString("mm(normal(0,1), normal(10,1))");
|
||||
Js.log(parsed1 |> E.R.fmap(Jstat.toString));
|
||||
Js.log(parsed1 |> E.R.fmap(Jstat.toShape(20)));
|
||||
expect(1.0) |> toEqual(1.0);
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@ type route =
|
|||
| Model(string)
|
||||
| DistBuilder
|
||||
| DistBuilder2
|
||||
| DistBuilder3
|
||||
| Home
|
||||
| NotFound;
|
||||
|
||||
|
@ -10,6 +11,7 @@ let routeToPath = route =>
|
|||
| Model(modelId) => "/m/" ++ modelId
|
||||
| DistBuilder => "/dist-builder"
|
||||
| DistBuilder2 => "/dist-builder2"
|
||||
| DistBuilder3 => "/dist-builder3"
|
||||
| Home => "/"
|
||||
| _ => "/"
|
||||
};
|
||||
|
@ -81,6 +83,9 @@ module Menu = {
|
|||
<Item href={routeToPath(DistBuilder2)} key="dist-builder-2">
|
||||
{"Dist Builder 2" |> E.ste}
|
||||
</Item>
|
||||
<Item href={routeToPath(DistBuilder3)} key="dist-builder-3">
|
||||
{"Dist Builder 3" |> E.ste}
|
||||
</Item>
|
||||
</div>;
|
||||
};
|
||||
};
|
||||
|
@ -94,6 +99,7 @@ let make = () => {
|
|||
| ["m", modelId] => Model(modelId)
|
||||
| ["dist-builder"] => DistBuilder
|
||||
| ["dist-builder2"] => DistBuilder2
|
||||
| ["dist-builder3"] => DistBuilder3
|
||||
| [] => Home
|
||||
| _ => NotFound
|
||||
};
|
||||
|
@ -108,6 +114,7 @@ let make = () => {
|
|||
}
|
||||
| DistBuilder => <DistBuilder />
|
||||
| DistBuilder2 => <DistBuilder2 />
|
||||
| DistBuilder3 => <DistBuilder3 />
|
||||
| Home => <div> {"Welcome" |> E.ste} </div>
|
||||
| _ => <div> {"Page is not found" |> E.ste} </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);
|
||||
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.
|
||||
let minX =
|
||||
switch (
|
||||
distPlus |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.01),
|
||||
range,
|
||||
) {
|
||||
| (min, Some(range)) => Some(min -. range *. 0.001)
|
||||
| _ => None
|
||||
// // We subtract a bit from the range to make sure that it fits. Maybe this should be done in d3 instead.
|
||||
// let minX =
|
||||
// switch (
|
||||
// distPlus
|
||||
// |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.0001),
|
||||
// range,
|
||||
// ) {
|
||||
// | (min, Some(range)) => Some(min -. range *. 0.001)
|
||||
// | _ => None
|
||||
// };
|
||||
|
||||
let minX = {
|
||||
distPlus |> Distributions.DistPlus.T.Integral.yToX(~cache=None, 0.00001);
|
||||
};
|
||||
|
||||
let maxX = {
|
||||
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 toDiscreteProbabilityMass =
|
||||
distPlus |> Distributions.DistPlus.T.toDiscreteProbabilityMass;
|
||||
|
|
|
@ -102,15 +102,15 @@ module Mixed = {
|
|||
|
||||
let min = dist =>
|
||||
switch (dist) {
|
||||
| `Normal(n) => Normal.inv(0.01, n)
|
||||
| `Lognormal(n) => Lognormal.inv(0.01, n)
|
||||
| `Normal(n) => Normal.inv(0.0001, n)
|
||||
| `Lognormal(n) => Lognormal.inv(0.0001, n)
|
||||
| `Uniform({low}) => low
|
||||
};
|
||||
|
||||
let max = dist =>
|
||||
switch (dist) {
|
||||
| `Normal(n) => Normal.inv(0.99, n)
|
||||
| `Lognormal(n) => Lognormal.inv(0.99, n)
|
||||
| `Normal(n) => Normal.inv(0.9999, n)
|
||||
| `Lognormal(n) => Lognormal.inv(0.9999, n)
|
||||
| `Uniform({high}) => high
|
||||
};
|
||||
|
||||
|
@ -121,7 +121,7 @@ module Mixed = {
|
|||
switch (xSelection) {
|
||||
| `Linear => Functions.range(min(dist), max(dist), sampleCount)
|
||||
| `ByWeight =>
|
||||
Functions.range(0.001, 0.999, sampleCount)
|
||||
Functions.range(0.00001, 0.99999, sampleCount)
|
||||
|> E.A.fmap(x => inv(x, 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;
|
||||
|
||||
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 xs = Functions.range(min(dists), max(dists), sampleCount);
|
||||
|
@ -181,3 +181,9 @@ let toString = (r: bigDist) =>
|
|||
| `PointwiseCombination(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;
|
||||
|
||||
type arg =
|
||||
| Symbol(string)
|
||||
| Value(float)
|
||||
| Fn(fn)
|
||||
| Array(array(arg))
|
||||
and fn = {
|
||||
name: string,
|
||||
args: array(arg),
|
||||
};
|
||||
|
||||
let rec parseMathjs = (j: Js.Json.t) => {
|
||||
let rec parseMathjs = (j: Js.Json.t) =>
|
||||
Json.Decode.(
|
||||
switch (field("mathjs", string, j)) {
|
||||
| "FunctionNode" =>
|
||||
|
@ -19,19 +21,56 @@ let rec parseMathjs = (j: Js.Json.t) => {
|
|||
args: args |> E.A.O.concatSomes,
|
||||
}),
|
||||
);
|
||||
| "ConstantNode" => Some(Value(field("value", float, j)))
|
||||
| _ => None
|
||||
| "OperatorNode" =>
|
||||
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) =>
|
||||
r
|
||||
|> (
|
||||
// let logHigh = math.log(high);
|
||||
// let logLow = math.log(low);
|
||||
|
||||
// let mean = (math.mean(logHigh, logLow)).toFixed(3);
|
||||
// let stdev = ((logHigh-logLow) / (2*1.645)).toFixed(3);
|
||||
|
||||
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")
|
||||
);
|
||||
| _ => 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) =>
|
||||
r
|
||||
|
@ -39,14 +78,16 @@ let rec toValue = (r): result(bigDist, string) =>
|
|||
fun
|
||||
| Value(_) => Error("Top level can't be value")
|
||||
| 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}) => {
|
||||
let dists: array(dist) =
|
||||
args
|
||||
|> E.A.fmap(toValue)
|
||||
|> E.A.fmap(
|
||||
fun
|
||||
| Ok(`Dist(`Normal({mean, stdev}))) =>
|
||||
Some(`Normal({mean, stdev}))
|
||||
| Ok(`Dist(n)) => Some(n)
|
||||
| _ => None,
|
||||
)
|
||||
|> E.A.O.concatSomes;
|
||||
|
@ -55,4 +96,15 @@ let rec toValue = (r): result(bigDist, string) =>
|
|||
Ok(`PointwiseCombination(inputs));
|
||||
}
|
||||
| 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"]
|
||||
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