diff --git a/__tests__/Jstat__test.re b/__tests__/Jstat__test.re deleted file mode 100644 index 3d48ef35..00000000 --- a/__tests__/Jstat__test.re +++ /dev/null @@ -1,11 +0,0 @@ -open Jest; -open Expect; - -describe("Shape", () => { - describe("Continuous", () => { - test("", () => { - Js.log(Jstat.Jstat.normal); - expect(Jstat.Jstat.normal##pdf(3.0, 3.0, 3.0)) |> toEqual(1.0); - }) - }) -}); \ No newline at end of file diff --git a/__tests__/Parser__test.re b/__tests__/Parser__test.re deleted file mode 100644 index 935ca668..00000000 --- a/__tests__/Parser__test.re +++ /dev/null @@ -1,13 +0,0 @@ -open Jest; -open Expect; - -describe("Shape", () => { - describe("Parser", () => { - test("", () => { - 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); - }) - }) -}); \ No newline at end of file diff --git a/src/components/DistBuilder3.re b/src/components/DistBuilder3.re index a3f36c1e..0bc0f16c 100644 --- a/src/components/DistBuilder3.re +++ b/src/components/DistBuilder3.re @@ -37,13 +37,13 @@ module DemoDist = { let parsed1 = MathJsParser.fromString(guesstimatorString); let shape = switch (parsed1) { - | Ok(r) => Some(Jstat.toShape(10000, r)) + | Ok(r) => Some(SymbolicDist.toShape(10000, r)) | _ => None }; let str = switch (parsed1) { - | Ok(r) => Jstat.toString(r) + | Ok(r) => SymbolicDist.toString(r) | Error(e) => e }; diff --git a/src/symbolic/Jstat.re b/src/symbolic/Jstat.re index 3a415ae5..98511270 100644 --- a/src/symbolic/Jstat.re +++ b/src/symbolic/Jstat.re @@ -1,189 +1,33 @@ // Todo: Another way of doing this is with [@bs.scope "normal"], which may be more elegant -module Jstat = { - 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, - }; - [@bs.module "jStat"] external normal: normal = "normal"; - [@bs.module "jStat"] external lognormal: lognormal = "lognormal"; - [@bs.module "jStat"] external uniform: uniform = "uniform"; -}; - type normal = { - mean: float, - stdev: float, + . + [@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 = { - mu: float, - sigma: float, + . + [@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 = { - low: float, - high: float, + . + [@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, }; - -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}; +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, }; - -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}; -}; - -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}; -}; - -type dist = [ - | `Normal(normal) - | `Lognormal(lognormal) - | `Uniform(uniform) -]; - -module Mixed = { - let pdf = (x, dist) => - switch (dist) { - | `Normal(n) => Normal.pdf(x, n) - | `Lognormal(n) => Lognormal.pdf(x, n) - | `Uniform(n) => Uniform.pdf(x, n) - }; - - let inv = (x, dist) => - switch (dist) { - | `Normal(n) => Normal.inv(x, n) - | `Lognormal(n) => Lognormal.inv(x, n) - | `Uniform(n) => Uniform.inv(x, n) - }; - - let sample = dist => - switch (dist) { - | `Normal(n) => Normal.sample(n) - | `Lognormal(n) => Lognormal.sample(n) - | `Uniform(n) => Uniform.sample(n) - }; - - let toString = dist => - switch (dist) { - | `Normal(n) => Normal.toString(n) - | `Lognormal(n) => Lognormal.toString(n) - | `Uniform(n) => Uniform.toString(n) - }; - - let min = dist => - switch (dist) { - | `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.9999, n) - | `Lognormal(n) => Lognormal.inv(0.9999, n) - | `Uniform({high}) => high - }; - - // will space linear - let toShape = - (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => { - let xs = - switch (xSelection) { - | `Linear => Functions.range(min(dist), max(dist), sampleCount) - | `ByWeight => - Functions.range(0.00001, 0.99999, sampleCount) - |> E.A.fmap(x => inv(x, dist)) - }; - let ys = xs |> E.A.fmap(r => pdf(r, dist)); - XYShape.T.fromArrays(xs, ys); - }; -}; - -// module PointwiseCombination = { -// type math = Multiply | Add | Exponent | Power; -// let fn = fun -// | Multiply => 3.0 -// | Add => 4.0 -// } - -module PointwiseAddDistributionsWeighted = { - type t = array((dist, float)); - - 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)) => Mixed.pdf(x, e) *. w) |> Functions.sum; - - let min = (dists: t) => - dists |> E.A.fmap(d => d |> fst |> Mixed.min) |> Functions.min; - - let max = (dists: t) => - 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); - let ys = xs |> E.A.fmap(pdf(dists)); - XYShape.T.fromArrays(xs, ys); - }; - - let toString = (dists: t) => { - let distString = - dists - |> E.A.fmap(d => Mixed.toString(fst(d))) - |> Js.Array.joinWith(","); - {j|pointwideAdded($distString)|j}; - }; -}; - -type bigDist = [ - | `Dist(dist) - | `PointwiseCombination(PointwiseAddDistributionsWeighted.t) -]; - -let toString = (r: bigDist) => - r - |> ( - fun - | `Dist(d) => Mixed.toString(d) - | `PointwiseCombination(d) => - PointwiseAddDistributionsWeighted.toString(d) - ); - -let toShape = n => - fun - | `Dist(d) => Mixed.toShape(~xSelection=`ByWeight, d, n) - | `PointwiseCombination(d) => - PointwiseAddDistributionsWeighted.toShape(d, n); \ No newline at end of file +[@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"; \ No newline at end of file diff --git a/src/symbolic/MathJsParser.re b/src/symbolic/MathJsParser.re index 2081db79..964032fc 100644 --- a/src/symbolic/MathJsParser.re +++ b/src/symbolic/MathJsParser.re @@ -1,110 +1,187 @@ -open Jstat; +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), + }; -type arg = - | Symbol(string) - | Value(float) - | Fn(fn) - | Array(array(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; + } + ); }; -let rec parseMathjs = (j: Js.Json.t) => - Json.Decode.( - switch (field("mathjs", string, j)) { - | "FunctionNode" => - let args = j |> field("args", array(parseMathjs)); - Some( - Fn({ - name: j |> field("fn", field("name", string)), - args: args |> E.A.O.concatSomes, - }), - ); - | "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; - } - ); +module MathAdtToDistDst = { + open MathJsonToMathJsAdt; -// let logHigh = math.log(high); -// let logLow = math.log(low); + 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 mean = (math.mean(logHigh, logLow)).toFixed(3); -// let stdev = ((logHigh-logLow) / (2*1.645)).toFixed(3); + 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(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) => - r - |> ( + let normal: array(arg) => result(SymbolicDist.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(n)) => Some(n) - | _ => None, - ) - |> E.A.O.concatSomes; + | [|Value(mean), Value(stdev)|] => + Ok(`Simple(`Normal({mean, stdev}))) + | _ => Error("Wrong number of variables in normal distribution"); - let inputs = dists |> E.A.fmap(r => (r, 1.0)); - Ok(`PointwiseCombination(inputs)); + 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") + }; } - | Fn({name}) => Error(name ++ ": name not found") - | Array(_) => Error("Array not valid as top level") - | Symbol(_) => Error("Symbol not valid as top level") - ); + | _ => Error("Wrong number of variables in lognormal distribution"); -let fromString = str => - Mathjs.parseMath(str) - |> E.R.bind(_, r => - switch (parseMathjs(r)) { - | Some(r) => toValue(r) - | None => Error("Second parse failed") - } - ); \ No newline at end of file + let to_: array(arg) => result(SymbolicDist.bigDist, string) = + fun + | [|Value(low), Value(high)|] => { + Ok(`Simple(SymbolicDist.Lognormal.from90PercentCI(low, high))); + } + | _ => 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 multiModal = (args: array(result(SymbolicDist.bigDist, string))) => { + 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.fmap(r => (r, 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: "mm", args}) => { + let dists = args |> E.A.fmap(functionParser); + multiModal(dists); + } + | Fn({name}) => Error(name ++ ": name not found") + | _ => 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; +}; \ No newline at end of file diff --git a/src/symbolic/SymbolicDist.re b/src/symbolic/SymbolicDist.re new file mode 100644 index 00000000..ccd78b6e --- /dev/null +++ b/src/symbolic/SymbolicDist.re @@ -0,0 +1,196 @@ +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 dist = [ + | `Normal(normal) + | `Beta(beta) + | `Lognormal(lognormal) + | `Uniform(uniform) +]; + +type pointwiseAdd = array((dist, float)); + +type bigDist = [ | `Simple(dist) | `PointwiseCombination(pointwiseAdd)]; + +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) + | `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) + | `Lognormal(n) => Lognormal.inv(x, n) + | `Uniform(n) => Uniform.inv(x, n) + | `Beta(n) => Beta.inv(x, n) + }; + + let sample = dist => + switch (dist) { + | `Normal(n) => Normal.sample(n) + | `Lognormal(n) => Lognormal.sample(n) + | `Uniform(n) => Uniform.sample(n) + | `Beta(n) => Beta.sample(n) + }; + + let toString = dist => + switch (dist) { + | `Normal(n) => Normal.toString(n) + | `Lognormal(n) => Lognormal.toString(n) + | `Uniform(n) => Uniform.toString(n) + | `Beta(n) => Beta.toString(n) + }; + + let min = dist => + switch (dist) { + | `Normal(n) => Normal.inv(minCdfValue, n) + | `Lognormal(n) => Lognormal.inv(minCdfValue, n) + | `Uniform({low}) => low + | `Beta(n) => Beta.inv(minCdfValue, n) + }; + + let max = dist => + switch (dist) { + | `Normal(n) => Normal.inv(maxCdfValue, n) + | `Lognormal(n) => Lognormal.inv(maxCdfValue, n) + | `Beta(n) => Beta.inv(maxCdfValue, n) + | `Uniform({high}) => high + }; + + let toShape = + (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => { + let xs = + switch (xSelection) { + | `Linear => Functions.range(min(dist), max(dist), sampleCount) + | `ByWeight => + Functions.range(minCdfValue, maxCdfValue, sampleCount) + |> E.A.fmap(x => inv(x, dist)) + }; + 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 = Functions.range(min(dists), max(dists), sampleCount); + 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(","); + {j|multimodal($distString)|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); \ No newline at end of file