From bc39ce9c677798177b16b14946d7ad2598005e60 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Wed, 1 Apr 2020 14:52:13 +0100 Subject: [PATCH] Very simple functionality with multimodals --- src/components/DistBuilder3.re | 3 +- src/distribution/Distributions.re | 6 +++ src/distribution/XYShape.re | 6 +-- src/symbolic/Jstat.re | 30 ++++++++++++ src/symbolic/MathJsParser.re | 2 + src/symbolic/SymbolicDist.re | 79 +++++++++++++++++++++++++++---- 6 files changed, 109 insertions(+), 17 deletions(-) diff --git a/src/components/DistBuilder3.re b/src/components/DistBuilder3.re index 96fba988..790eddf4 100644 --- a/src/components/DistBuilder3.re +++ b/src/components/DistBuilder3.re @@ -52,8 +52,7 @@ module DemoDist = { |> E.O.fmap(shape => { let distPlus = Distributions.DistPlus.make( - ~shape= - Continuous(Distributions.Continuous.make(`Linear, shape)), + ~shape, ~domain=Complete, ~unit=UnspecifiedDistribution, ~guesstimatorString=None, diff --git a/src/distribution/Distributions.re b/src/distribution/Distributions.re index faa22476..4d03d46d 100644 --- a/src/distribution/Distributions.re +++ b/src/distribution/Distributions.re @@ -142,6 +142,12 @@ module Discrete = { t |> XYShape.T.zip |> XYShape.Zipped.sortByY; let sortedByX = (t: DistTypes.discreteShape) => t |> XYShape.T.zip |> XYShape.Zipped.sortByX; + let empty = XYShape.T.empty; + let combine = (fn, t1: DistTypes.discreteShape, t2: DistTypes.discreteShape): DistTypes.discreteShape => { + XYShape.Combine.combine(~xsSelection=ALL_XS, ~xToYSelection=XYShape.XtoY.stepwiseIfAtX, ~fn, t1, t2) + } + let _default0 = ((fn, a,b) => fn(E.O.default(0.0, a), E.O.default(0.0, b))); + let reduce = (fn, items) => items |> E.A.fold_left(combine(_default0((fn))), empty); module T = Dist({ type t = DistTypes.discreteShape; diff --git a/src/distribution/XYShape.re b/src/distribution/XYShape.re index 11f32373..7bdb9683 100644 --- a/src/distribution/XYShape.re +++ b/src/distribution/XYShape.re @@ -17,6 +17,7 @@ module T = { type ts = array(xyShape); let xs = (t: t) => t.xs; let ys = (t: t) => t.ys; + let empty = ({xs: [||], ys: [||]}); let minX = (t: t) => t |> xs |> E.A.Sorted.min |> extImp; let maxX = (t: t) => t |> xs |> E.A.Sorted.max |> extImp; let firstY = (t: t) => t |> ys |> E.A.first |> extImp; @@ -170,11 +171,6 @@ module Combine = { | ALL_XS | XS_EVENLY_DIVIDED(int); - type xToYSelection = - | LINEAR - | STEPWISE_INCREMENTAL - | STEPWISE_IF_AT_X; - let combine = ( ~xToYSelection: (float, T.t) => 'a, diff --git a/src/symbolic/Jstat.re b/src/symbolic/Jstat.re index ac5ecee7..4c361bd2 100644 --- a/src/symbolic/Jstat.re +++ b/src/symbolic/Jstat.re @@ -48,6 +48,32 @@ type triangular = { [@bs.meth] "inv": (float, float, float, float) => float, [@bs.meth] "sample": (float, float, float) => float, }; + +// Pareto doesn't have sample for some reason +type pareto = { + . + [@bs.meth] "pdf": (float, float, float) => float, + [@bs.meth] "cdf": (float, float, float) => float, + [@bs.meth] "inv": (float, float, float) => float, +}; +type poisson = { + . + [@bs.meth] "pdf": (float, float) => float, + [@bs.meth] "cdf": (float, float) => float, + [@bs.meth] "sample": float => float, +}; +type weibull = { + . + [@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 binomial = { + . + [@bs.meth] "pdf": (float, float, float) => float, + [@bs.meth] "cdf": (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"; @@ -55,6 +81,10 @@ type triangular = { [@bs.module "jstat"] external exponential: exponential = "exponential"; [@bs.module "jstat"] external cauchy: cauchy = "cauchy"; [@bs.module "jstat"] external triangular: triangular = "triangular"; +[@bs.module "jstat"] external poisson: poisson = "poisson"; +[@bs.module "jstat"] external pareto: pareto = "pareto"; +[@bs.module "jstat"] external weibull: weibull = "weibull"; +[@bs.module "jstat"] external binomial: binomial = "binomial"; [@bs.module "jstat"] external sum: array(float) => float = "sum"; [@bs.module "jstat"] external product: array(float) => float = "product"; diff --git a/src/symbolic/MathJsParser.re b/src/symbolic/MathJsParser.re index 2dd27540..1d83e6d3 100644 --- a/src/symbolic/MathJsParser.re +++ b/src/symbolic/MathJsParser.re @@ -177,6 +177,7 @@ module MathAdtToDistDst = { | Fn({name: "exponential", args}) => exponential(args) | Fn({name: "cauchy", args}) => cauchy(args) | Fn({name: "triangular", args}) => triangular(args) + | Value(f) => Ok(`Simple(`Float(f))) | Fn({name: "mm", args}) => { let dists = args |> E.A.fmap(functionParser); let weights = @@ -195,6 +196,7 @@ module MathAdtToDistDst = { | _ => None, ) |> E.A.O.concatSomes; + Js.log3("Making dists", dists, weights); multiModal(dists, weights); } | Fn({name}) => Error(name ++ ": function not supported") diff --git a/src/symbolic/SymbolicDist.re b/src/symbolic/SymbolicDist.re index 1fd6dc50..a4c33baf 100644 --- a/src/symbolic/SymbolicDist.re +++ b/src/symbolic/SymbolicDist.re @@ -31,6 +31,8 @@ type triangular = { high: float, }; +type contType = [ | `Continuous | `Discrete]; + type dist = [ | `Normal(normal) | `Beta(beta) @@ -39,6 +41,7 @@ type dist = [ | `Exponential(exponential) | `Cauchy(cauchy) | `Triangular(triangular) + | `Float(float) ]; type pointwiseAdd = array((dist, float)); @@ -51,6 +54,7 @@ module Exponential = { 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}; + let contType: contType = `Continuous; }; module Cauchy = { @@ -59,6 +63,7 @@ module Cauchy = { 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}; + let contType: contType = `Continuous; }; module Triangular = { @@ -67,6 +72,7 @@ module Triangular = { 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}; + let contType: contType = `Continuous; }; module Normal = { @@ -75,6 +81,7 @@ module Normal = { 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}; + let contType: contType = `Continuous; }; module Beta = { @@ -83,6 +90,7 @@ module 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}; + let contType: contType = `Continuous; }; module Lognormal = { @@ -91,6 +99,7 @@ module Lognormal = { 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 contType: contType = `Continuous; let from90PercentCI = (low, high) => { let logLow = Js.Math.log(low); let logHigh = Js.Math.log(high); @@ -118,6 +127,16 @@ module Uniform = { 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}; + let contType: contType = `Continuous; +}; + +module Float = { + type t = float; + let pdf = (x, t: t) => x == t ? 1.0 : 0.0; + let inv = (p, t: t) => p < t ? 0.0 : 1.0; + let sample = (t: t) => t; + let toString = Js.Float.toString; + let contType: contType = `Discrete; }; module GenericSimple = { @@ -133,6 +152,19 @@ module GenericSimple = { | `Lognormal(n) => Lognormal.pdf(x, n) | `Uniform(n) => Uniform.pdf(x, n) | `Beta(n) => Beta.pdf(x, n) + | `Float(n) => Float.pdf(x, n) + }; + + let contType = (dist:dist):contType => + switch (dist) { + | `Normal(_) => Normal.contType + | `Triangular(_) => Triangular.contType + | `Exponential(_) => Exponential.contType + | `Cauchy(_) => Cauchy.contType + | `Lognormal(_) => Lognormal.contType + | `Uniform(_) => Uniform.contType + | `Beta(_) => Beta.contType + | `Float(_) => Float.contType }; let inv = (x, dist) => @@ -144,6 +176,7 @@ module GenericSimple = { | `Lognormal(n) => Lognormal.inv(x, n) | `Uniform(n) => Uniform.inv(x, n) | `Beta(n) => Beta.inv(x, n) + | `Float(n) => Float.inv(x, n) }; let sample: dist => float = @@ -154,7 +187,8 @@ module GenericSimple = { | `Cauchy(n) => Cauchy.sample(n) | `Lognormal(n) => Lognormal.sample(n) | `Uniform(n) => Uniform.sample(n) - | `Beta(n) => Beta.sample(n); + | `Beta(n) => Beta.sample(n) + | `Float(n) => Float.sample(n); let toString: dist => string = fun @@ -164,7 +198,8 @@ module GenericSimple = { | `Normal(n) => Normal.toString(n) | `Lognormal(n) => Lognormal.toString(n) | `Uniform(n) => Uniform.toString(n) - | `Beta(n) => Beta.toString(n); + | `Beta(n) => Beta.toString(n) + | `Float(n) => Float.toString(n); let min: dist => float = fun @@ -174,7 +209,8 @@ module GenericSimple = { | `Normal(n) => Normal.inv(minCdfValue, n) | `Lognormal(n) => Lognormal.inv(minCdfValue, n) | `Uniform({low}) => low - | `Beta(n) => Beta.inv(minCdfValue, n); + | `Beta(n) => Beta.inv(minCdfValue, n) + | `Float(n) => n; let max: dist => float = fun @@ -184,7 +220,8 @@ module GenericSimple = { | `Normal(n) => Normal.inv(maxCdfValue, n) | `Lognormal(n) => Lognormal.inv(maxCdfValue, n) | `Beta(n) => Beta.inv(maxCdfValue, n) - | `Uniform({high}) => high; + | `Uniform({high}) => high + | `Float(n) => n; let interpolateXs = (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => { @@ -197,10 +234,13 @@ module GenericSimple = { }; let toShape = - (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => { + (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) + : DistTypes.shape => { let xs = interpolateXs(~xSelection, dist, sampleCount); let ys = xs |> E.A.fmap(r => pdf(r, dist)); - XYShape.T.fromArrays(xs, ys); + XYShape.T.fromArrays(xs, ys) + |> Distributions.Continuous.make(`Linear, _) + |> Distributions.Continuous.T.toShape; }; }; @@ -212,7 +252,7 @@ module PointwiseAddDistributionsWeighted = { dists |> E.A.fmap(((a, b)) => (a, b /. total)); }; - let pdf = (dists: t, x: float) => + let pdf = (x: float, dists: t) => dists |> E.A.fmap(((e, w)) => GenericSimple.pdf(x, e) *. w) |> E.A.Floats.sum; @@ -223,7 +263,17 @@ module PointwiseAddDistributionsWeighted = { let max = (dists: t) => dists |> E.A.fmap(d => d |> fst |> GenericSimple.max) |> E.A.max; - let toShape = (dists: t, sampleCount: int) => { + let discreteShape = (dists:t, sampleCount: int) => { + let discrete = dists |> E.A.fmap((((r,e)) => r |> fun + | `Float(r) => Some((r,e)) + | _ => None + )) |> E.A.O.concatSomes + |> E.A.fmap(((x, y)):DistTypes.xyShape => ({xs: [|x|], ys: [|y|]})) + |> Distributions.Discrete.reduce((+.)) + discrete + } + + let continuousShape = (dists:t, sampleCount: int) => { let xs = dists |> E.A.fmap(r => @@ -237,8 +287,17 @@ module PointwiseAddDistributionsWeighted = { ) |> E.A.concatMany; xs |> Array.fast_sort(compare); - let ys = xs |> E.A.fmap(pdf(dists)); - XYShape.T.fromArrays(xs, ys); + let ys = xs |> E.A.fmap(pdf(_, dists)); + XYShape.T.fromArrays(xs, ys) + |> Distributions.Continuous.make(`Linear, _) + } + + let toShape = (dists: t, sampleCount: int) => { + let normalized = normalizeWeights(dists); + let continuous = normalized |> E.A.filter(((r,_)) => GenericSimple.contType(r) == `Continuous) |> continuousShape(_, sampleCount); + let discrete = normalized |> E.A.filter(((r,_)) => GenericSimple.contType(r) == `Discrete) |> discreteShape(_, sampleCount); + let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete); + shape |> E.O.toExt("") }; let toString = (dists: t) => {