Merge pull request #22 from foretold-app/symbolic-parsing-attempt

Symbolic parsing with DistBuilder 3
This commit is contained in:
Ozzie Gooen 2020-03-26 16:06:27 +00:00 committed by GitHub
commit f061e9fa01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 737 additions and 37 deletions

View File

@ -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"]
}

View File

@ -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",

View File

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

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

View File

@ -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 = {

View File

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

View File

@ -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 {(*|*[])[]|*[]}

View File

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

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

View File

@ -0,0 +1,8 @@
const math = require("mathjs");
function parseMath(f){ return JSON.parse(JSON.stringify(math.parse(f))) };
module.exports = {
parseMath,
};

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

View File

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