Merge pull request #22 from foretold-app/symbolic-parsing-attempt
Symbolic parsing with DistBuilder 3
This commit is contained in:
commit
f061e9fa01
|
@ -3,7 +3,8 @@
|
|||
"reason": {
|
||||
"react-jsx": 3
|
||||
},
|
||||
"sources": [{
|
||||
"sources": [
|
||||
{
|
||||
"dir": "src",
|
||||
"subdirs": true
|
||||
},
|
||||
|
@ -19,14 +20,17 @@
|
|||
}
|
||||
],
|
||||
"bsc-flags": ["-bs-super-errors", "-bs-no-version-header"],
|
||||
"package-specs": [{
|
||||
"package-specs": [
|
||||
{
|
||||
"module": "commonjs",
|
||||
"in-source": true
|
||||
}],
|
||||
}
|
||||
],
|
||||
"suffix": ".bs.js",
|
||||
"namespace": true,
|
||||
"bs-dependencies": [
|
||||
"@glennsl/bs-jest",
|
||||
"@glennsl/bs-json",
|
||||
"@foretold/components",
|
||||
"bs-ant-design-alt",
|
||||
"reason-react",
|
||||
|
@ -37,7 +41,5 @@
|
|||
"reschema"
|
||||
],
|
||||
"refmt": 3,
|
||||
"ppx-flags": [
|
||||
"lenses-ppx/ppx"
|
||||
]
|
||||
"ppx-flags": ["lenses-ppx/ppx"]
|
||||
}
|
|
@ -29,6 +29,7 @@
|
|||
"@foretold/components": "0.0.3",
|
||||
"@foretold/guesstimator": "1.0.10",
|
||||
"@glennsl/bs-jest": "^0.5.0",
|
||||
"@glennsl/bs-json": "^5.0.2",
|
||||
"antd": "3.17.0",
|
||||
"autoprefixer": "9.7.4",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
||||
|
|
|
@ -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(SymbolicDist.toShape(10000, r))
|
||||
| _ => None
|
||||
};
|
||||
|
||||
let str =
|
||||
switch (parsed1) {
|
||||
| Ok(r) => SymbolicDist.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: "mm(1 to 1000)"},
|
||||
(),
|
||||
);
|
||||
|
||||
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,14 +216,19 @@ 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 = {
|
||||
|
|
|
@ -25,7 +25,7 @@ class BaseDistributionBinned {
|
|||
this.max_bin_size = 0.005;
|
||||
this.min_bin_size = 0;
|
||||
this.increment = 0.0001;
|
||||
this.desired_delta = 0.0001;
|
||||
this.desired_delta = 0.001;
|
||||
this.start_bin_size = 0.0001;
|
||||
|
||||
[this.params, this.pdf_func, this.sample] = this.get_params_and_pdf_func(
|
||||
|
@ -44,6 +44,8 @@ class BaseDistributionBinned {
|
|||
throw new Error("NotImplementedError");
|
||||
}
|
||||
|
||||
|
||||
//Adaptive binning. Specify a desired change in density to get adjusted bin sizes.
|
||||
/**
|
||||
* @returns {(number[]|[*])[]}
|
||||
* @private
|
||||
|
|
|
@ -8,6 +8,7 @@ const math = _math.create(_math.all);
|
|||
const NUM_MC_SAMPLES = 3000;
|
||||
const OUTPUT_GRID_NUMEL = 3000;
|
||||
|
||||
|
||||
/**
|
||||
* The main algorithmic work is done by functions in this module.
|
||||
* It also contains the main function, taking the user's string
|
||||
|
@ -290,6 +291,7 @@ function pluck_from_array(array, idx) {
|
|||
* If distr_string requires MC, try all possible
|
||||
* choices for the deterministic distribution,
|
||||
* and pick the one with the least variance.
|
||||
* It's much better to sample from a normal than a lognormal.
|
||||
*
|
||||
* @param distr_string
|
||||
* @returns {(*|*[])[]|*[]}
|
||||
|
|
|
@ -200,13 +200,6 @@ module T = {
|
|||
| (true, true) => (-1)
|
||||
};
|
||||
|
||||
// todo: This is broken :(
|
||||
let combine = (t1: t, t2: t) => {
|
||||
let array = Belt.Array.concat(zip(t1), zip(t2));
|
||||
Array.fast_sort(comparePoints, array);
|
||||
array |> Belt.Array.unzip |> fromArray;
|
||||
};
|
||||
|
||||
// TODO: I'd bet this is pretty slow
|
||||
let intersperce = (t1: t, t2: t) => {
|
||||
let items: ref(array((float, float))) = ref([||]);
|
||||
|
|
57
src/symbolic/Jstat.re
Normal file
57
src/symbolic/Jstat.re
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Todo: Another way of doing this is with [@bs.scope "normal"], which may be more elegant
|
||||
type normal = {
|
||||
.
|
||||
[@bs.meth] "pdf": (float, float, float) => float,
|
||||
[@bs.meth] "cdf": (float, float, float) => float,
|
||||
[@bs.meth] "inv": (float, float, float) => float,
|
||||
[@bs.meth] "sample": (float, float) => float,
|
||||
};
|
||||
type lognormal = {
|
||||
.
|
||||
[@bs.meth] "pdf": (float, float, float) => float,
|
||||
[@bs.meth] "cdf": (float, float, float) => float,
|
||||
[@bs.meth] "inv": (float, float, float) => float,
|
||||
[@bs.meth] "sample": (float, float) => float,
|
||||
};
|
||||
type uniform = {
|
||||
.
|
||||
[@bs.meth] "pdf": (float, float, float) => float,
|
||||
[@bs.meth] "cdf": (float, float, float) => float,
|
||||
[@bs.meth] "inv": (float, float, float) => float,
|
||||
[@bs.meth] "sample": (float, float) => float,
|
||||
};
|
||||
type beta = {
|
||||
.
|
||||
[@bs.meth] "pdf": (float, float, float) => float,
|
||||
[@bs.meth] "cdf": (float, float, float) => float,
|
||||
[@bs.meth] "inv": (float, float, float) => float,
|
||||
[@bs.meth] "sample": (float, float) => float,
|
||||
};
|
||||
type exponential = {
|
||||
.
|
||||
[@bs.meth] "pdf": (float, float) => float,
|
||||
[@bs.meth] "cdf": (float, float) => float,
|
||||
[@bs.meth] "inv": (float, float) => float,
|
||||
[@bs.meth] "sample": float => float,
|
||||
};
|
||||
type cauchy = {
|
||||
.
|
||||
[@bs.meth] "pdf": (float, float, float) => float,
|
||||
[@bs.meth] "cdf": (float, float, float) => float,
|
||||
[@bs.meth] "inv": (float, float, float) => float,
|
||||
[@bs.meth] "sample": (float, float) => float,
|
||||
};
|
||||
type triangular = {
|
||||
.
|
||||
[@bs.meth] "pdf": (float, float, float, float) => float,
|
||||
[@bs.meth] "cdf": (float, float, float, float) => float,
|
||||
[@bs.meth] "inv": (float, float, float, float) => float,
|
||||
[@bs.meth] "sample": (float, float, float) => float,
|
||||
};
|
||||
[@bs.module "jStat"] external normal: normal = "normal";
|
||||
[@bs.module "jStat"] external lognormal: lognormal = "lognormal";
|
||||
[@bs.module "jStat"] external uniform: uniform = "uniform";
|
||||
[@bs.module "jStat"] external beta: beta = "beta";
|
||||
[@bs.module "jStat"] external exponential: exponential = "exponential";
|
||||
[@bs.module "jStat"] external cauchy: cauchy = "cauchy";
|
||||
[@bs.module "jStat"] external triangular: triangular = "triangular";
|
231
src/symbolic/MathJsParser.re
Normal file
231
src/symbolic/MathJsParser.re
Normal file
|
@ -0,0 +1,231 @@
|
|||
module MathJsonToMathJsAdt = {
|
||||
type arg =
|
||||
| Symbol(string)
|
||||
| Value(float)
|
||||
| Fn(fn)
|
||||
| Array(array(arg))
|
||||
| Object(Js.Dict.t(arg))
|
||||
and fn = {
|
||||
name: string,
|
||||
args: array(arg),
|
||||
};
|
||||
|
||||
let rec run = (j: Js.Json.t) =>
|
||||
Json.Decode.(
|
||||
switch (field("mathjs", string, j)) {
|
||||
| "FunctionNode" =>
|
||||
let args = j |> field("args", array(run));
|
||||
Some(
|
||||
Fn({
|
||||
name: j |> field("fn", field("name", string)),
|
||||
args: args |> E.A.O.concatSomes,
|
||||
}),
|
||||
);
|
||||
| "OperatorNode" =>
|
||||
let args = j |> field("args", array(run));
|
||||
Some(
|
||||
Fn({
|
||||
name: j |> field("fn", string),
|
||||
args: args |> E.A.O.concatSomes,
|
||||
}),
|
||||
);
|
||||
| "ConstantNode" =>
|
||||
optional(field("value", Json.Decode.float), j)
|
||||
|> E.O.fmap(r => Value(r))
|
||||
| "ObjectNode" =>
|
||||
let properties = j |> field("properties", dict(run));
|
||||
Js.Dict.entries(properties)
|
||||
|> E.A.fmap(((key, value)) => value |> E.O.fmap(v => (key, v)))
|
||||
|> E.A.O.concatSomes
|
||||
|> Js.Dict.fromArray
|
||||
|> (r => Some(Object(r)));
|
||||
| "ArrayNode" =>
|
||||
let items = field("items", array(run), j);
|
||||
Some(Array(items |> E.A.O.concatSomes));
|
||||
| "SymbolNode" => Some(Symbol(field("name", string, j)))
|
||||
| n =>
|
||||
Js.log3("Couldn't parse mathjs node", j, n);
|
||||
None;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
module MathAdtToDistDst = {
|
||||
open MathJsonToMathJsAdt;
|
||||
|
||||
module MathAdtCleaner = {
|
||||
let transformWithSymbol = (f: float, s: string) =>
|
||||
switch (s) {
|
||||
| "K"
|
||||
| "k" => f *. 1000.
|
||||
| "M"
|
||||
| "m" => f *. 1000000.
|
||||
| "B"
|
||||
| "b" => f *. 1000000000.
|
||||
| "T"
|
||||
| "t" => f *. 1000000000000.
|
||||
| _ => f
|
||||
};
|
||||
|
||||
let rec run =
|
||||
fun
|
||||
| Fn({name: "multiply", args: [|Value(f), Symbol(s)|]}) =>
|
||||
Value(transformWithSymbol(f, s))
|
||||
| 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)
|
||||
| Object(v) =>
|
||||
Object(
|
||||
v
|
||||
|> Js.Dict.entries
|
||||
|> E.A.fmap(((key, value)) => (key, run(value)))
|
||||
|> Js.Dict.fromArray,
|
||||
);
|
||||
};
|
||||
|
||||
let normal: array(arg) => result(SymbolicDist.bigDist, string) =
|
||||
fun
|
||||
| [|Value(mean), Value(stdev)|] =>
|
||||
Ok(`Simple(`Normal({mean, stdev})))
|
||||
| _ => Error("Wrong number of variables in normal distribution");
|
||||
|
||||
let lognormal: array(arg) => result(SymbolicDist.bigDist, string) =
|
||||
fun
|
||||
| [|Value(mu), Value(sigma)|] => Ok(`Simple(`Lognormal({mu, sigma})))
|
||||
| [|Object(o)|] => {
|
||||
let g = Js.Dict.get(o);
|
||||
switch (g("mean"), g("stdev"), g("mu"), g("sigma")) {
|
||||
| (Some(Value(mean)), Some(Value(stdev)), _, _) =>
|
||||
Ok(`Simple(SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev)))
|
||||
| (_, _, Some(Value(mu)), Some(Value(sigma))) =>
|
||||
Ok(`Simple(`Lognormal({mu, sigma})))
|
||||
| _ => Error("Lognormal distribution would need mean and stdev")
|
||||
};
|
||||
}
|
||||
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||
|
||||
let to_: array(arg) => result(SymbolicDist.bigDist, string) =
|
||||
fun
|
||||
| [|Value(low), Value(high)|] when low < high => {
|
||||
Ok(`Simple(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(SymbolicDist.bigDist, string) =
|
||||
fun
|
||||
| [|Value(low), Value(high)|] => Ok(`Simple(`Uniform({low, high})))
|
||||
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||
|
||||
let beta: array(arg) => result(SymbolicDist.bigDist, string) =
|
||||
fun
|
||||
| [|Value(alpha), Value(beta)|] => Ok(`Simple(`Beta({alpha, beta})))
|
||||
| _ => Error("Wrong number of variables in lognormal distribution");
|
||||
|
||||
let exponential: array(arg) => result(SymbolicDist.bigDist, string) =
|
||||
fun
|
||||
| [|Value(rate)|] => Ok(`Simple(`Exponential({rate: rate})))
|
||||
| _ => Error("Wrong number of variables in Exponential distribution");
|
||||
|
||||
let cauchy: array(arg) => result(SymbolicDist.bigDist, string) =
|
||||
fun
|
||||
| [|Value(local), Value(scale)|] =>
|
||||
Ok(`Simple(`Cauchy({local, scale})))
|
||||
| _ => Error("Wrong number of variables in cauchy distribution");
|
||||
|
||||
let triangular: array(arg) => result(SymbolicDist.bigDist, string) =
|
||||
fun
|
||||
| [|Value(low), Value(medium), Value(high)|] =>
|
||||
Ok(`Simple(`Triangular({low, medium, high})))
|
||||
| _ => Error("Wrong number of variables in triangle distribution");
|
||||
|
||||
let multiModal =
|
||||
(
|
||||
args: array(result(SymbolicDist.bigDist, string)),
|
||||
weights: array(float),
|
||||
) => {
|
||||
let dists =
|
||||
args
|
||||
|> E.A.fmap(
|
||||
fun
|
||||
| Ok(`Simple(n)) => Some(n)
|
||||
| _ => None,
|
||||
)
|
||||
|> E.A.O.concatSomes;
|
||||
switch (dists |> E.A.length) {
|
||||
| 0 => Error("Multimodals need at least one input")
|
||||
| _ =>
|
||||
dists
|
||||
|> E.A.fmapi((index, item) =>
|
||||
(item, weights |> E.A.get(_, index) |> E.O.default(1.0))
|
||||
)
|
||||
|> (r => Ok(`PointwiseCombination(r)))
|
||||
};
|
||||
};
|
||||
|
||||
let rec functionParser = (r): result(SymbolicDist.bigDist, string) =>
|
||||
r
|
||||
|> (
|
||||
fun
|
||||
| Fn({name: "normal", args}) => normal(args)
|
||||
| Fn({name: "lognormal", args}) => lognormal(args)
|
||||
| Fn({name: "uniform", args}) => uniform(args)
|
||||
| Fn({name: "beta", args}) => beta(args)
|
||||
| Fn({name: "to", args}) => to_(args)
|
||||
| Fn({name: "exponential", args}) => exponential(args)
|
||||
| Fn({name: "cauchy", args}) => cauchy(args)
|
||||
| Fn({name: "triangular", args}) => triangular(args)
|
||||
| Fn({name: "mm", args}) => {
|
||||
let dists = args |> E.A.fmap(functionParser);
|
||||
let weights =
|
||||
args
|
||||
|> E.A.last
|
||||
|> E.O.bind(
|
||||
_,
|
||||
fun
|
||||
| Array(values) => Some(values)
|
||||
| _ => None,
|
||||
)
|
||||
|> E.A.O.defaultEmpty
|
||||
|> E.A.fmap(
|
||||
fun
|
||||
| Value(r) => Some(r)
|
||||
| _ => None,
|
||||
)
|
||||
|> E.A.O.concatSomes;
|
||||
multiModal(dists, weights);
|
||||
}
|
||||
| Fn({name}) => Error(name ++ ": function not supported")
|
||||
| _ => Error("This type not currently supported")
|
||||
);
|
||||
|
||||
let topLevel = (r): result(SymbolicDist.bigDist, string) =>
|
||||
r
|
||||
|> (
|
||||
fun
|
||||
| Fn(_) => functionParser(r)
|
||||
| Value(_) => Error("Top level can't be value")
|
||||
| Array(_) => Error("Array not valid as top level")
|
||||
| Symbol(_) => Error("Symbol not valid as top level")
|
||||
| Object(_) => Error("Object not valid as top level")
|
||||
);
|
||||
|
||||
let run = (r): result(SymbolicDist.bigDist, string) =>
|
||||
r |> MathAdtCleaner.run |> topLevel;
|
||||
};
|
||||
|
||||
let fromString = str => {
|
||||
let mathJsToJson = Mathjs.parseMath(str);
|
||||
let mathJsParse =
|
||||
E.R.bind(mathJsToJson, r =>
|
||||
switch (MathJsonToMathJsAdt.run(r)) {
|
||||
| Some(r) => Ok(r)
|
||||
| None => Error("MathJsParse Error")
|
||||
}
|
||||
);
|
||||
let value = E.R.bind(mathJsParse, MathAdtToDistDst.run);
|
||||
Js.log4("fromString", mathJsToJson, mathJsParse, value);
|
||||
value;
|
||||
};
|
10
src/symbolic/Mathjs.re
Normal file
10
src/symbolic/Mathjs.re
Normal file
|
@ -0,0 +1,10 @@
|
|||
[@bs.module "./MathjsWrapper.js"]
|
||||
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)
|
||||
};
|
8
src/symbolic/MathjsWrapper.js
Normal file
8
src/symbolic/MathjsWrapper.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
const math = require("mathjs");
|
||||
|
||||
function parseMath(f){ return JSON.parse(JSON.stringify(math.parse(f))) };
|
||||
|
||||
module.exports = {
|
||||
parseMath,
|
||||
};
|
272
src/symbolic/SymbolicDist.re
Normal file
272
src/symbolic/SymbolicDist.re
Normal file
|
@ -0,0 +1,272 @@
|
|||
type normal = {
|
||||
mean: float,
|
||||
stdev: float,
|
||||
};
|
||||
|
||||
type lognormal = {
|
||||
mu: float,
|
||||
sigma: float,
|
||||
};
|
||||
|
||||
type uniform = {
|
||||
low: float,
|
||||
high: float,
|
||||
};
|
||||
|
||||
type beta = {
|
||||
alpha: float,
|
||||
beta: float,
|
||||
};
|
||||
|
||||
type exponential = {rate: float};
|
||||
|
||||
type cauchy = {
|
||||
local: float,
|
||||
scale: float,
|
||||
};
|
||||
|
||||
type triangular = {
|
||||
low: float,
|
||||
medium: float,
|
||||
high: float,
|
||||
};
|
||||
|
||||
type dist = [
|
||||
| `Normal(normal)
|
||||
| `Beta(beta)
|
||||
| `Lognormal(lognormal)
|
||||
| `Uniform(uniform)
|
||||
| `Exponential(exponential)
|
||||
| `Cauchy(cauchy)
|
||||
| `Triangular(triangular)
|
||||
];
|
||||
|
||||
type pointwiseAdd = array((dist, float));
|
||||
|
||||
type bigDist = [ | `Simple(dist) | `PointwiseCombination(pointwiseAdd)];
|
||||
|
||||
module Exponential = {
|
||||
type t = exponential;
|
||||
let pdf = (x, t: t) => Jstat.exponential##pdf(x, t.rate);
|
||||
let inv = (p, t: t) => Jstat.exponential##inv(p, t.rate);
|
||||
let sample = (t: t) => Jstat.exponential##sample(t.rate);
|
||||
let toString = ({rate}: t) => {j|Exponential($rate)|j};
|
||||
};
|
||||
|
||||
module Cauchy = {
|
||||
type t = cauchy;
|
||||
let pdf = (x, t: t) => Jstat.cauchy##pdf(x, t.local, t.scale);
|
||||
let inv = (p, t: t) => Jstat.cauchy##inv(p, t.local, t.scale);
|
||||
let sample = (t: t) => Jstat.cauchy##sample(t.local, t.scale);
|
||||
let toString = ({local, scale}: t) => {j|Cauchy($local, $scale)|j};
|
||||
};
|
||||
|
||||
module Triangular = {
|
||||
type t = triangular;
|
||||
let pdf = (x, t: t) => Jstat.triangular##pdf(x, t.low, t.high, t.medium);
|
||||
let inv = (p, t: t) => Jstat.triangular##inv(p, t.low, t.high, t.medium);
|
||||
let sample = (t: t) => Jstat.triangular##sample(t.low, t.high, t.medium);
|
||||
let toString = ({low, medium, high}: t) => {j|Triangular($low, $medium, $high)|j};
|
||||
};
|
||||
|
||||
module Normal = {
|
||||
type t = normal;
|
||||
let pdf = (x, t: t) => Jstat.normal##pdf(x, t.mean, t.stdev);
|
||||
let inv = (p, t: t) => Jstat.normal##inv(p, t.mean, t.stdev);
|
||||
let sample = (t: t) => Jstat.normal##sample(t.mean, t.stdev);
|
||||
let toString = ({mean, stdev}: t) => {j|Normal($mean,$stdev)|j};
|
||||
};
|
||||
|
||||
module Beta = {
|
||||
type t = beta;
|
||||
let pdf = (x, t: t) => Jstat.beta##pdf(x, t.alpha, t.beta);
|
||||
let inv = (p, t: t) => Jstat.beta##inv(p, t.alpha, t.beta);
|
||||
let sample = (t: t) => Jstat.beta##sample(t.alpha, t.beta);
|
||||
let toString = ({alpha, beta}: t) => {j|Beta($alpha,$beta)|j};
|
||||
};
|
||||
|
||||
module Lognormal = {
|
||||
type t = lognormal;
|
||||
let pdf = (x, t: t) => Jstat.lognormal##pdf(x, t.mu, t.sigma);
|
||||
let inv = (p, t: t) => Jstat.lognormal##inv(p, t.mu, t.sigma);
|
||||
let sample = (t: t) => Jstat.lognormal##sample(t.mu, t.sigma);
|
||||
let toString = ({mu, sigma}: t) => {j|Lognormal($mu,$sigma)|j};
|
||||
let from90PercentCI = (low, 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);
|
||||
`Lognormal({mu, sigma});
|
||||
};
|
||||
let fromMeanAndStdev = (mean, stdev) => {
|
||||
let variance = Js.Math.pow_float(~base=stdev, ~exp=2.0);
|
||||
let meanSquared = Js.Math.pow_float(~base=mean, ~exp=2.0);
|
||||
let mu =
|
||||
Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance /. meanSquared +. 1.0);
|
||||
let sigma =
|
||||
Js.Math.pow_float(
|
||||
~base=Js.Math.log(variance /. meanSquared +. 1.0),
|
||||
~exp=0.5,
|
||||
);
|
||||
`Lognormal({mu, sigma});
|
||||
};
|
||||
};
|
||||
|
||||
module Uniform = {
|
||||
type t = uniform;
|
||||
let pdf = (x, t: t) => Jstat.uniform##pdf(x, t.low, t.high);
|
||||
let inv = (p, t: t) => Jstat.uniform##inv(p, t.low, t.high);
|
||||
let sample = (t: t) => Jstat.uniform##sample(t.low, t.high);
|
||||
let toString = ({low, high}: t) => {j|Uniform($low,$high)|j};
|
||||
};
|
||||
|
||||
module GenericSimple = {
|
||||
let minCdfValue = 0.0001;
|
||||
let maxCdfValue = 0.9999;
|
||||
|
||||
let pdf = (x, dist) =>
|
||||
switch (dist) {
|
||||
| `Normal(n) => Normal.pdf(x, n)
|
||||
| `Triangular(n) => Triangular.pdf(x, n)
|
||||
| `Exponential(n) => Exponential.pdf(x, n)
|
||||
| `Cauchy(n) => Cauchy.pdf(x, n)
|
||||
| `Lognormal(n) => Lognormal.pdf(x, n)
|
||||
| `Uniform(n) => Uniform.pdf(x, n)
|
||||
| `Beta(n) => Beta.pdf(x, n)
|
||||
};
|
||||
|
||||
let inv = (x, dist) =>
|
||||
switch (dist) {
|
||||
| `Normal(n) => Normal.inv(x, n)
|
||||
| `Triangular(n) => Triangular.inv(x, n)
|
||||
| `Exponential(n) => Exponential.inv(x, n)
|
||||
| `Cauchy(n) => Cauchy.inv(x, n)
|
||||
| `Lognormal(n) => Lognormal.inv(x, n)
|
||||
| `Uniform(n) => Uniform.inv(x, n)
|
||||
| `Beta(n) => Beta.inv(x, n)
|
||||
};
|
||||
|
||||
let sample: dist => float =
|
||||
fun
|
||||
| `Normal(n) => Normal.sample(n)
|
||||
| `Triangular(n) => Triangular.sample(n)
|
||||
| `Exponential(n) => Exponential.sample(n)
|
||||
| `Cauchy(n) => Cauchy.sample(n)
|
||||
| `Lognormal(n) => Lognormal.sample(n)
|
||||
| `Uniform(n) => Uniform.sample(n)
|
||||
| `Beta(n) => Beta.sample(n);
|
||||
|
||||
let toString: dist => string =
|
||||
fun
|
||||
| `Triangular(n) => Triangular.toString(n)
|
||||
| `Exponential(n) => Exponential.toString(n)
|
||||
| `Cauchy(n) => Cauchy.toString(n)
|
||||
| `Normal(n) => Normal.toString(n)
|
||||
| `Lognormal(n) => Lognormal.toString(n)
|
||||
| `Uniform(n) => Uniform.toString(n)
|
||||
| `Beta(n) => Beta.toString(n);
|
||||
|
||||
let min: dist => float =
|
||||
fun
|
||||
| `Triangular({low}) => low
|
||||
| `Exponential(n) => Exponential.inv(minCdfValue, n)
|
||||
| `Cauchy(n) => Cauchy.inv(minCdfValue, n)
|
||||
| `Normal(n) => Normal.inv(minCdfValue, n)
|
||||
| `Lognormal(n) => Lognormal.inv(minCdfValue, n)
|
||||
| `Uniform({low}) => low
|
||||
| `Beta(n) => Beta.inv(minCdfValue, n);
|
||||
|
||||
let max: dist => float =
|
||||
fun
|
||||
| `Triangular(n) => n.high
|
||||
| `Exponential(n) => Exponential.inv(maxCdfValue, n)
|
||||
| `Cauchy(n) => Cauchy.inv(maxCdfValue, n)
|
||||
| `Normal(n) => Normal.inv(maxCdfValue, n)
|
||||
| `Lognormal(n) => Lognormal.inv(maxCdfValue, n)
|
||||
| `Beta(n) => Beta.inv(maxCdfValue, n)
|
||||
| `Uniform({high}) => high;
|
||||
|
||||
let interpolateXs =
|
||||
(~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => {
|
||||
switch (xSelection) {
|
||||
| `Linear => Functions.range(min(dist), max(dist), sampleCount)
|
||||
| `ByWeight =>
|
||||
Functions.range(minCdfValue, maxCdfValue, sampleCount)
|
||||
|> E.A.fmap(x => inv(x, dist))
|
||||
};
|
||||
};
|
||||
|
||||
let toShape =
|
||||
(~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => {
|
||||
let xs = interpolateXs(~xSelection, dist, sampleCount);
|
||||
let ys = xs |> E.A.fmap(r => pdf(r, dist));
|
||||
XYShape.T.fromArrays(xs, ys);
|
||||
};
|
||||
};
|
||||
|
||||
module PointwiseAddDistributionsWeighted = {
|
||||
type t = pointwiseAdd;
|
||||
|
||||
let normalizeWeights = (dists: t) => {
|
||||
let total = dists |> E.A.fmap(snd) |> Functions.sum;
|
||||
dists |> E.A.fmap(((a, b)) => (a, b /. total));
|
||||
};
|
||||
|
||||
let pdf = (dists: t, x: float) =>
|
||||
dists
|
||||
|> E.A.fmap(((e, w)) => GenericSimple.pdf(x, e) *. w)
|
||||
|> Functions.sum;
|
||||
|
||||
let min = (dists: t) =>
|
||||
dists |> E.A.fmap(d => d |> fst |> GenericSimple.min) |> Functions.min;
|
||||
|
||||
let max = (dists: t) =>
|
||||
dists |> E.A.fmap(d => d |> fst |> GenericSimple.max) |> Functions.max;
|
||||
|
||||
let toShape = (dists: t, sampleCount: int) => {
|
||||
let xs =
|
||||
dists
|
||||
|> E.A.fmap(r =>
|
||||
r
|
||||
|> fst
|
||||
|> GenericSimple.interpolateXs(
|
||||
~xSelection=`ByWeight,
|
||||
_,
|
||||
sampleCount / (dists |> E.A.length),
|
||||
)
|
||||
)
|
||||
|> E.A.concatMany;
|
||||
xs |> Array.fast_sort(compare);
|
||||
let ys = xs |> E.A.fmap(pdf(dists));
|
||||
XYShape.T.fromArrays(xs, ys);
|
||||
};
|
||||
|
||||
let toString = (dists: t) => {
|
||||
let distString =
|
||||
dists
|
||||
|> E.A.fmap(d => GenericSimple.toString(fst(d)))
|
||||
|> Js.Array.joinWith(",");
|
||||
let weights =
|
||||
dists
|
||||
|> E.A.fmap(d =>
|
||||
snd(d) |> Js.Float.toPrecisionWithPrecision(~digits=2)
|
||||
)
|
||||
|> Js.Array.joinWith(",");
|
||||
{j|multimodal($distString, [$weights])|j};
|
||||
};
|
||||
};
|
||||
|
||||
let toString = (r: bigDist) =>
|
||||
r
|
||||
|> (
|
||||
fun
|
||||
| `Simple(d) => GenericSimple.toString(d)
|
||||
| `PointwiseCombination(d) =>
|
||||
PointwiseAddDistributionsWeighted.toString(d)
|
||||
);
|
||||
|
||||
let toShape = n =>
|
||||
fun
|
||||
| `Simple(d) => GenericSimple.toShape(~xSelection=`ByWeight, d, n)
|
||||
| `PointwiseCombination(d) =>
|
||||
PointwiseAddDistributionsWeighted.toShape(d, n);
|
14
yarn.lock
14
yarn.lock
|
@ -1017,15 +1017,6 @@
|
|||
lodash "4.17.15"
|
||||
pdfast "0.2.0"
|
||||
|
||||
"@foretold/cdf@1.0.15":
|
||||
version "1.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@foretold/cdf/-/cdf-1.0.15.tgz#69ce4755158693e3d325e7be10d0aa9cdb465730"
|
||||
integrity sha512-I7GhFQd4HaFd+tGD1IJ0W8xvFp2YiJdcFiXSCq9vYQZWWy+Npi4QOYsMoDJyoUTvOlVba4ARa/pDKPD2hn+uuQ==
|
||||
dependencies:
|
||||
lodash "4.17.15"
|
||||
parcel "1.12.3"
|
||||
pdfast "0.2.0"
|
||||
|
||||
"@foretold/components@0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@foretold/components/-/components-0.0.3.tgz#a195912647499735f64cb2b74f722eee4b2da13f"
|
||||
|
@ -1081,6 +1072,11 @@
|
|||
dependencies:
|
||||
jest "^25.1.0"
|
||||
|
||||
"@glennsl/bs-json@^5.0.2":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@glennsl/bs-json/-/bs-json-5.0.2.tgz#cfb85d94d370ec6dc17849e0ddb1a51eee08cfcc"
|
||||
integrity sha512-vVlHJNrhmwvhyea14YiV4L5pDLjqw1edE3GzvMxlbPPQZVhzgO3sTWrUxCpQd2gV+CkMfk4FHBYunx9nWtBoDg==
|
||||
|
||||
"@iarna/toml@^2.2.0":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab"
|
||||
|
|
Loading…
Reference in New Issue
Block a user