diff --git a/src/components/charts/GenericDistributionChart.re b/src/components/charts/GenericDistributionChart.re index 42bf3200..3141cff9 100644 --- a/src/components/charts/GenericDistributionChart.re +++ b/src/components/charts/GenericDistributionChart.re @@ -1,12 +1,12 @@ module Shapee = { [@react.component] - let make = (~shape: DistributionTypes.pointsType, ~timeScale, ~onHover) => { - let discrete = Shape.PointsType.scaledDiscreteComponent(shape); - let continuous = Shape.PointsType.scaledContinuousComponent(shape); + let make = (~shape: DistributionTypes.shape, ~timeScale, ~onHover) => { + let discrete = Shape.T.scaledDiscreteComponent(shape); + let continuous = Shape.T.scaledContinuousComponent(shape);
{ genericDistribution - |> DistributionTypes.shape + |> DistributionTypes.shapee |> E.O.React.fmapOrNull(shape => { setX(_ => r)} /> }) diff --git a/src/core/DistFunctor.re b/src/core/DistFunctor.re new file mode 100644 index 00000000..6db55b90 --- /dev/null +++ b/src/core/DistFunctor.re @@ -0,0 +1,218 @@ +let min = (f1: option(float), f2: option(float)) => + switch (f1, f2) { + | (Some(f1), Some(f2)) => Some(f1 < f2 ? f1 : f2) + | (Some(f1), None) => Some(f1) + | (None, Some(f2)) => Some(f2) + | (None, None) => None + }; + +let max = (f1: option(float), f2: option(float)) => + switch (f1, f2) { + | (Some(f1), Some(f2)) => Some(f1 > f2 ? f1 : f2) + | (Some(f1), None) => Some(f1) + | (None, Some(f2)) => Some(f2) + | (None, None) => None + }; + +type yPoint = + | Mixed({ + continuous: float, + discrete: float, + }) + | Continuous(float) + | Discrete(float); + +module type dist = { + type t; + type integral; + let minX: t => option(float); + let maxX: t => option(float); + let pointwiseFmap: (float => float, t) => t; + let xToY: (float, t) => yPoint; + let xToIntegralY: (float, t) => float; + let shape: t => DistributionTypes.shape; + let integral: t => integral; + let integralSum: t => float; +}; + +module Dist = (T: dist) => { + type t = T.t; + type integral = T.integral; + let minX = T.minX; + let maxX = T.maxX; + let pointwiseFmap = T.pointwiseFmap; + let xToIntegralY = T.xToIntegralY; + let xToY = T.xToY; + let shape = T.shape; + let integral = T.integral; + let integralSum = T.integralSum; +}; + +module Continuous = + Dist({ + type t = DistributionTypes.continuousShape; + type integral = DistributionTypes.continuousShape; + let integral = t => + t |> Shape.XYShape.Range.integrateWithTriangles |> E.O.toExt(""); + let integralSum = t => t |> integral |> Shape.XYShape.ySum; + let minX = Shape.XYShape.minX; + let maxX = Shape.XYShape.maxX; + let pointwiseFmap = Shape.XYShape.pointwiseMap; + let shape = (t: t): DistributionTypes.shape => Continuous(t); + let xToY = (f, t) => + CdfLibrary.Distribution.findY(f, t) |> (e => Continuous(e)); + let xToIntegralY = (f, t) => + t |> integral |> CdfLibrary.Distribution.findY(f); + }); + +module Discrete = + Dist({ + type t = DistributionTypes.discreteShape; + type integral = DistributionTypes.continuousShape; + let integral = t => t |> Shape.Discrete.integrate; + let integralSum = t => t |> Shape.XYShape.ySum; + let minX = Shape.XYShape.minX; + let maxX = Shape.XYShape.maxX; + let pointwiseFmap = Shape.XYShape.pointwiseMap; + let shape = (t: t): DistributionTypes.shape => Discrete(t); + let xToY = (f, t) => + CdfLibrary.Distribution.findY(f, t) |> (e => Discrete(e)); + let xToIntegralY = (f, t) => + t |> Shape.XYShape.accumulateYs |> CdfLibrary.Distribution.findY(f); + }); + +module Mixed = + Dist({ + type t = DistributionTypes.mixedShape; + type integral = DistributionTypes.continuousShape; + let minX = ({continuous, discrete}: t) => + min(Continuous.minX(continuous), Discrete.minX(discrete)); + let maxX = ({continuous, discrete}: t) => + max(Continuous.maxX(continuous), Discrete.maxX(discrete)); + let shape = (t: t): DistributionTypes.shape => Mixed(t); + let xToY = + (f, {discrete, continuous, discreteProbabilityMassFraction}: t) => + Mixed({ + continuous: + CdfLibrary.Distribution.findY(f, continuous) + |> (e => e *. (1. -. discreteProbabilityMassFraction)), + discrete: + Shape.Discrete.findY(f, discrete) + |> (e => e *. discreteProbabilityMassFraction), + }); + + let scaledContinuousComponent = + ({continuous, discreteProbabilityMassFraction}: t) + : option(DistributionTypes.continuousShape) => { + Shape.Continuous.scalePdf( + ~scaleTo=1.0 -. discreteProbabilityMassFraction, + continuous, + ); + }; + + let scaledDiscreteComponent = + ({discrete, discreteProbabilityMassFraction}: t) + : DistributionTypes.continuousShape => + Discrete.pointwiseFmap( + f => f *. discreteProbabilityMassFraction, + discrete, + ); + + // TODO: Add these two directly, once interpolation is added. + let integral = t => { + // let cont = scaledContinuousComponent(t); + // let discrete = scaledDiscreteComponent(t); + scaledContinuousComponent(t) |> E.O.toExt(""); + }; + + let integralSum = + ({discrete, continuous, discreteProbabilityMassFraction}: t) => { + Discrete.integralSum(discrete) + *. discreteProbabilityMassFraction + +. Continuous.integralSum(continuous) + *. (1.0 -. discreteProbabilityMassFraction); + }; + + let xToIntegralY = + (f, {discrete, continuous, discreteProbabilityMassFraction}: t) => { + let cont = Continuous.xToIntegralY(f, continuous); + let discrete = Discrete.xToIntegralY(f, discrete); + discrete + *. discreteProbabilityMassFraction + +. cont + *. (1.0 -. discreteProbabilityMassFraction); + }; + + let pointwiseFmap = + (fn, {discrete, continuous, discreteProbabilityMassFraction}: t): t => { + { + discrete: Shape.XYShape.pointwiseMap(fn, discrete), + continuous: Shape.XYShape.pointwiseMap(fn, continuous), + discreteProbabilityMassFraction, + }; + }; + }); + +module Shape = + Dist({ + type t = DistributionTypes.shape; + type integral = DistributionTypes.continuousShape; + let xToY = (f, t) => + Shape.T.mapToAll( + t, + (Mixed.xToY(f), Discrete.xToY(f), Continuous.xToY(f)), + ); + let shape = (t: t) => t; + let minX = (t: t) => + Shape.T.mapToAll(t, (Mixed.minX, Discrete.minX, Continuous.minX)); + let integral = (t: t) => + Shape.T.mapToAll( + t, + (Mixed.integral, Discrete.integral, Continuous.integral), + ); + let integralSum = (t: t) => + Shape.T.mapToAll( + t, + (Mixed.integralSum, Discrete.integralSum, Continuous.integralSum), + ); + let xToIntegralY = (f, t) => { + Shape.T.mapToAll( + t, + ( + Mixed.xToIntegralY(f), + Discrete.xToIntegralY(f), + Continuous.xToIntegralY(f), + ), + ); + }; + let maxX = (t: t) => + Shape.T.mapToAll(t, (Mixed.minX, Discrete.minX, Continuous.minX)); + let pointwiseFmap = (fn, t: t) => + Shape.T.fmap( + t, + ( + Mixed.pointwiseFmap(fn), + Discrete.pointwiseFmap(fn), + Continuous.pointwiseFmap(fn), + ), + ); + }); + +module WithMetadata = + Dist({ + type t = DistributionTypes.complexPower; + type integral = DistributionTypes.complexPower; + let shape = ({shape, _}: t) => shape; + let xToY = (f, t: t) => t |> shape |> Shape.xToY(f); + let minX = (t: t) => t |> shape |> Shape.minX; + let maxX = (t: t) => t |> shape |> Shape.maxX; + let fromShape = (shape, t): t => DistributionTypes.update(~shape, t); + let pointwiseFmap = (fn, {shape, _} as t: t): t => + fromShape(Shape.pointwiseFmap(fn, shape), t); + + let integral = (t: t) => fromShape(Continuous(t.integralCache), t); + let integralSum = (t: t) => t |> shape |> Shape.integralSum; + let xToIntegralY = (f, t) => { + 3.0; + }; + }); \ No newline at end of file diff --git a/src/core/DistributionTypes.re b/src/core/DistributionTypes.re index 677ec416..069a063f 100644 --- a/src/core/DistributionTypes.re +++ b/src/core/DistributionTypes.re @@ -23,14 +23,26 @@ type mixedShape = { discreteProbabilityMassFraction: float, }; -type pointsType = - | Mixed(mixedShape) - | Discrete(discreteShape) - | Continuous(continuousShape); +type shapeMonad('a, 'b, 'c) = + | Mixed('a) + | Discrete('b) + | Continuous('c); + +type shape = shapeMonad(mixedShape, discreteShape, continuousShape); + +module ShapeMonad = { + let fmap = + (t: shapeMonad('a, 'b, 'c), (fn1, fn2, fn3)): shapeMonad('d, 'e, 'f) => + switch (t) { + | Mixed(m) => Mixed(fn1(m)) + | Discrete(m) => Discrete(fn2(m)) + | Continuous(m) => Continuous(fn3(m)) + }; +}; type generationSource = | GuesstimatorString(string) - | Shape(pointsType); + | Shape(shape); type distributionUnit = | UnspecifiedDistribution @@ -48,12 +60,31 @@ type genericDistribution = { unit: distributionUnit, }; -let shape = ({generationSource}: genericDistribution) => +let shapee = ({generationSource}: genericDistribution) => switch (generationSource) { | GuesstimatorString(_) => None | Shape(pointsType) => Some(pointsType) }; +type pdfCdfCombo = { + pdf: shape, + cdf: continuousShape, +}; + +type complexPower = { + shape, + integralCache: continuousShape, + domain: domainLimit, + unit: distributionUnit, +}; + +let update = (~shape=?, ~integralCache=?, ~domain=?, ~unit=?, t: complexPower) => { + shape: E.O.default(t.shape, shape), + integralCache: E.O.default(t.integralCache, integralCache), + domain: E.O.default(t.domain, domain), + unit: E.O.default(t.unit, unit), +}; + module DistributionUnit = { let toJson = (distributionUnit: distributionUnit) => switch (distributionUnit) { diff --git a/src/core/GenericDistribution.re b/src/core/GenericDistribution.re index f9a147fd..cc5aa2a9 100644 --- a/src/core/GenericDistribution.re +++ b/src/core/GenericDistribution.re @@ -43,36 +43,22 @@ let make = unit, }; +//TODO: The fact that it is a CDF is really something you find later, this can't be chosen until GuesstimatorToString happens. let renderIfNeeded = (~sampleCount=1000, t: genericDistribution): option(genericDistribution) => { switch (t.generationSource) { | GuesstimatorString(s) => - let shape = Guesstimator.stringToMixedShape(~string=s, ~sampleCount, ()); - let newShape = - switch (shape) { - | Some({ - continuous: {xs: [||], ys: [||]}, - discrete: {xs: [||], ys: [||]}, - }) => - None - | Some({continuous, discrete: {xs: [||], ys: [||]}}) => - Some(Continuous(continuous)) - | Some({continuous: {xs: [||], ys: [||]}, discrete}) => - Some(Discrete(discrete)) - | Some(shape) => Some(Mixed(shape)) - | _ => None - }; - - newShape - |> E.O.fmap((shape: DistributionTypes.pointsType) => + Guesstimator.stringToMixedShape(~string=s, ~sampleCount, ()) + |> E.O.bind(_, Shape.Mixed.clean) + |> E.O.fmap((shape: DistributionTypes.shape) => make( ~generationSource=Shape(shape), - ~probabilityType=Cdf, + ~probabilityType=Pdf, ~domain=t.domain, ~unit=t.unit, (), ) - ); + ) | Shape(_) => Some(t) }; }; @@ -80,7 +66,7 @@ let renderIfNeeded = let normalize = (t: genericDistribution): option(genericDistribution) => { switch (t.generationSource) { | Shape(shape) => - Shape.PointsType.normalizePdf(shape) + Shape.T.Pdf.normalize(shape) |> E.O.fmap(shape => {...t, generationSource: Shape(shape)}) | GuesstimatorString(_) => Some(t) }; @@ -91,7 +77,7 @@ let yIntegral = (t: DistributionTypes.genericDistribution, x) => { let normalize = n => n *. Domain.normalizeProbabilityMass(t.domain); switch (t) { | {generationSource: Shape(shape)} => - Shape.PointsType.yIntegral(shape, x) + Shape.T.yIntegral(shape, x) |> E.O.fmap(addInitialMass) |> E.O.fmap(normalize) | _ => None diff --git a/src/core/Shape.re b/src/core/Shape.re index f54ade9e..c4c9c738 100644 --- a/src/core/Shape.re +++ b/src/core/Shape.re @@ -25,6 +25,8 @@ module XYShape = { let fmap = (t: t, y): t => {xs: t.xs, ys: t.ys |> E.A.fmap(y)}; + let pointwiseMap = (fn, t: t): t => {xs: t.xs, ys: t.ys |> E.A.fmap(fn)}; + let scaleCdfTo = (~scaleTo=1., t: t) => switch (_lastElement(t.ys)) { | Some(n) => @@ -135,6 +137,10 @@ module Discrete = { let toJs = XYShape.toJs; let ySum = XYShape.ySum; let zip = t => Belt.Array.zip(t.xs, t.ys); + let pointwiseMap = (t: discreteShape, fn): discreteShape => { + xs: t.xs, + ys: t.ys |> E.A.fmap(fn), + }; let scaleYToTotal = (totalDesired, t: t): t => { let difference = totalDesired /. ySum(t); @@ -214,13 +220,17 @@ module Mixed = { type yPdfPoint = { continuous: option(float), discrete: option(float), - discreteProbabilityMassFraction: float, }; let findY = (t: DistributionTypes.mixedShape, x: float): yPdfPoint => { - continuous: Continuous.findY(x, t.continuous) |> E.O.some, - discrete: Discrete.findY(x, t.discrete) |> E.O.some, - discreteProbabilityMassFraction: t.discreteProbabilityMassFraction, + continuous: + Continuous.findY(x, t.continuous) + |> (e => e *. (1. -. t.discreteProbabilityMassFraction)) + |> E.O.some, + discrete: + Discrete.findY(x, t.discrete) + |> (e => e *. t.discreteProbabilityMassFraction) + |> E.O.some, }; let findYIntegral = @@ -232,10 +242,36 @@ module Mixed = { | _ => None }; }; + + let clean = (t: DistributionTypes.mixedShape) => + switch (t) { + | {continuous: {xs: [||], ys: [||]}, discrete: {xs: [||], ys: [||]}} => + None + | {discrete: {xs: [|_|], ys: [|_|]}} => None + | {continuous, discrete: {xs: [||], ys: [||]}} => + Some(Continuous(continuous)) + | {continuous: {xs: [||], ys: [||]}, discrete} => + Some(Discrete(discrete)) + | shape => Some(Mixed(shape)) + }; }; -module PointsType = { - type t = DistributionTypes.pointsType; +module T = { + type t = DistributionTypes.shape; + + let mapToAll = (t: t, (fn1, fn2, fn3)) => + switch (t) { + | Mixed(m) => fn1(m) + | Discrete(m) => fn2(m) + | Continuous(m) => fn3(m) + }; + + let fmap = (t: t, (fn1, fn2, fn3)) => + switch (t) { + | Mixed(m) => Mixed(fn1(m)) + | Discrete(m) => Discrete(fn2(m)) + | Continuous(m) => Continuous(fn3(m)) + }; let y = (t: t, x: float) => switch (t) { @@ -268,22 +304,6 @@ module PointsType = { | Continuous(continuousShape) => Continuous.maxX(continuousShape) }; - // TODO: This is wrong. The discrete component should be made continuous when integrating. - let pdfToCdf = (t: t) => - switch (t) { - | Mixed({continuous, discrete, discreteProbabilityMassFraction}) => - Some( - Mixed({ - continuous: Continuous.toCdf(continuous) |> E.O.toExt(""), - discrete: discrete |> Discrete.integrate, - discreteProbabilityMassFraction, - }), - ) - | Discrete(discrete) => Some(Continuous(discrete |> Discrete.integrate)) - | Continuous(continuous) => - Continuous.toCdf(continuous) |> E.O.fmap(e => Continuous(e)) - }; - let discreteComponent = (t: t) => switch (t) { | Mixed({discrete}) => Some(discrete) @@ -319,37 +339,123 @@ module PointsType = { }; }; - let normalizeCdf = (t: DistributionTypes.pointsType) => { + let pointwiseFmap = (fn, t: t): shape => switch (t) { - | Mixed({continuous, discrete, discreteProbabilityMassFraction}) => + | Mixed({discrete, continuous, discreteProbabilityMassFraction}) => Mixed({ - continuous: continuous |> Continuous.normalizeCdf, - discrete: discrete |> Discrete.scaleYToTotal(1.0), + continuous: XYShape.pointwiseMap(fn, continuous), + discrete: XYShape.pointwiseMap(fn, discrete), discreteProbabilityMassFraction, }) - | Discrete(d) => Discrete(d |> Discrete.scaleYToTotal(1.0)) - | Continuous(continuousShape) => - Continuous(Continuous.normalizeCdf(continuousShape)) + | Discrete(x) => Discrete(XYShape.pointwiseMap(fn, x)) + | Continuous(x) => Continuous(XYShape.pointwiseMap(fn, x)) + }; + + module Cdf = { + let normalizeCdf = (t: DistributionTypes.shape) => { + switch (t) { + | Mixed({continuous, discrete, discreteProbabilityMassFraction}) => + Mixed({ + continuous: continuous |> Continuous.normalizeCdf, + discrete: discrete |> Discrete.scaleYToTotal(1.0), + discreteProbabilityMassFraction, + }) + | Discrete(d) => Discrete(d |> Discrete.scaleYToTotal(1.0)) + | Continuous(continuousShape) => + Continuous(Continuous.normalizeCdf(continuousShape)) + }; }; }; - let normalizePdf = (t: DistributionTypes.pointsType) => { - switch (t) { - | Mixed({continuous, discrete, discreteProbabilityMassFraction}) => - continuous - |> Continuous.scalePdf(~scaleTo=1.0) - |> E.O.fmap(r => - Mixed({ - continuous: r, - discrete: discrete |> Discrete.scaleYToTotal(1.0), - discreteProbabilityMassFraction, - }) - ) - | Discrete(d) => Some(Discrete(d |> Discrete.scaleYToTotal(1.0))) - | Continuous(continuousShape) => - continuousShape - |> Continuous.scalePdf(~scaleTo=1.0) - |> E.O.fmap(r => Continuous(r)) + module Pdf = { + // TODO: This is wrong. The discrete component should be made continuous when integrating. + let toCdf = (t: t) => + switch (t) { + | Mixed({continuous, discrete, discreteProbabilityMassFraction}) => + Some( + Mixed({ + continuous: Continuous.toCdf(continuous) |> E.O.toExt(""), + discrete: discrete |> Discrete.integrate, + discreteProbabilityMassFraction, + }), + ) + | Discrete(discrete) => + Some(Continuous(discrete |> Discrete.integrate)) + | Continuous(continuous) => + Continuous.toCdf(continuous) |> E.O.fmap(e => Continuous(e)) + }; + + let normalize = (t: DistributionTypes.shape): option(shape) => { + switch (t) { + | Mixed({continuous, discrete, discreteProbabilityMassFraction}) => + continuous + |> Continuous.scalePdf(~scaleTo=1.0) + |> E.O.fmap(r => + Mixed({ + continuous: r, + discrete: discrete |> Discrete.scaleYToTotal(1.0), + discreteProbabilityMassFraction, + }) + ) + | Discrete(d) => Some(Discrete(d |> Discrete.scaleYToTotal(1.0))) + | Continuous(continuousShape) => + continuousShape + |> Continuous.scalePdf(~scaleTo=1.0) + |> E.O.fmap(r => Continuous(r)) + }; }; }; + + let toPdfCdfCombo = (t: t): option(pdfCdfCombo) => + switch (t) { + | Mixed({continuous, discrete, discreteProbabilityMassFraction} as tt) => + Some({ + cdf: continuous |> Continuous.toCdf |> E.O.toExt(""), + pdf: Mixed(tt), + }) + | Discrete(d) => Some({cdf: d |> Discrete.integrate, pdf: Discrete(d)}) + | Continuous(c) => + Some({ + cdf: c |> Continuous.toCdf |> E.O.toExt(""), + pdf: Continuous(c), + }) + }; +}; + +module PdfCdfShape = { + type t = pdfCdfCombo; + let pdf = (t: t) => + switch (t.pdf) { + | Mixed(pdf) => Mixed(pdf) + | Discrete(pdf) => Discrete(pdf) + | Continuous(pdf) => Continuous(pdf) + }; + let cdf = (t: t) => t.cdf; +}; + +type distributionUnit = + | UnspecifiedDistribution + | TimeDistribution(TimeTypes.timeVector); + +type withLimitedDomain = { + domain, + dist: pdfCdfCombo, +}; + +module WithLimitedDomain = { + type t = withLimitedDomain; + let dist = (t: t) => t.dist; + let pdf = (t: t) => PdfCdfShape.pdf(t.dist); + let cdf = (t: t) => PdfCdfShape.cdf(t.dist); + // TODO: This is bad, obviously needs to be fixed. + let distScaleFactor = (t: t) => 3.0; + let scaledPdfShape = (scaleFactor, t: t) => + t |> pdf |> T.pointwiseFmap(r => r *. scaleFactor); + let scaledCdfShape = (scaleFactor, t: t) => + t |> cdf |> XYShape.pointwiseMap(r => r *. scaleFactor); +}; + +type withTimeVector = { + timeVector: TimeTypes.timeVector, + dist: withLimitedDomain, }; \ No newline at end of file diff --git a/src/core/Try24.re b/src/core/Try24.re new file mode 100644 index 00000000..4f2da095 --- /dev/null +++ b/src/core/Try24.re @@ -0,0 +1,5 @@ +open DistributionTypes; + +module Shappe = { + type t = shape; +}; \ No newline at end of file diff --git a/src/utility/Guesstimator.re b/src/utility/Guesstimator.re index ae99c34e..7407c36f 100644 --- a/src/utility/Guesstimator.re +++ b/src/utility/Guesstimator.re @@ -25,6 +25,7 @@ module Internals = { [@bs.module "./GuesstimatorLibrary.js"] external toCombinedFormat: (string, int) => combined = "run"; + // todo: Format to correct mass, also normalize the pdf. let toMixedShape = (r: combined): option(DistributionTypes.mixedShape) => { let assumptions: MixedShapeBuilder.assumptions = { continuous: ADDS_TO_1,