Starting to move functionality into functor

This commit is contained in:
Ozzie Gooen 2020-02-21 23:42:15 +00:00
parent 7bc62d3934
commit 745cf01317
7 changed files with 427 additions and 80 deletions

View File

@ -1,12 +1,12 @@
module Shapee = { module Shapee = {
[@react.component] [@react.component]
let make = (~shape: DistributionTypes.pointsType, ~timeScale, ~onHover) => { let make = (~shape: DistributionTypes.shape, ~timeScale, ~onHover) => {
let discrete = Shape.PointsType.scaledDiscreteComponent(shape); let discrete = Shape.T.scaledDiscreteComponent(shape);
let continuous = Shape.PointsType.scaledContinuousComponent(shape); let continuous = Shape.T.scaledContinuousComponent(shape);
<div> <div>
<CdfChart__Plain <CdfChart__Plain
minX={Shape.PointsType.minX(shape)} minX={Shape.T.minX(shape)}
maxX={Shape.PointsType.maxX(shape)} maxX={Shape.T.maxX(shape)}
?discrete ?discrete
?continuous ?continuous
color={`hex("333")} color={`hex("333")}
@ -28,7 +28,7 @@ module GenericDist = {
React.useMemo1( React.useMemo1(
() => { () => {
genericDistribution genericDistribution
|> DistributionTypes.shape |> DistributionTypes.shapee
|> E.O.React.fmapOrNull(shape => { |> E.O.React.fmapOrNull(shape => {
<Shapee shape timeScale onHover={r => setX(_ => r)} /> <Shapee shape timeScale onHover={r => setX(_ => r)} />
}) })

218
src/core/DistFunctor.re Normal file
View File

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

View File

@ -23,14 +23,26 @@ type mixedShape = {
discreteProbabilityMassFraction: float, discreteProbabilityMassFraction: float,
}; };
type pointsType = type shapeMonad('a, 'b, 'c) =
| Mixed(mixedShape) | Mixed('a)
| Discrete(discreteShape) | Discrete('b)
| Continuous(continuousShape); | 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 = type generationSource =
| GuesstimatorString(string) | GuesstimatorString(string)
| Shape(pointsType); | Shape(shape);
type distributionUnit = type distributionUnit =
| UnspecifiedDistribution | UnspecifiedDistribution
@ -48,12 +60,31 @@ type genericDistribution = {
unit: distributionUnit, unit: distributionUnit,
}; };
let shape = ({generationSource}: genericDistribution) => let shapee = ({generationSource}: genericDistribution) =>
switch (generationSource) { switch (generationSource) {
| GuesstimatorString(_) => None | GuesstimatorString(_) => None
| Shape(pointsType) => Some(pointsType) | 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 = { module DistributionUnit = {
let toJson = (distributionUnit: distributionUnit) => let toJson = (distributionUnit: distributionUnit) =>
switch (distributionUnit) { switch (distributionUnit) {

View File

@ -43,36 +43,22 @@ let make =
unit, 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 = let renderIfNeeded =
(~sampleCount=1000, t: genericDistribution): option(genericDistribution) => { (~sampleCount=1000, t: genericDistribution): option(genericDistribution) => {
switch (t.generationSource) { switch (t.generationSource) {
| GuesstimatorString(s) => | GuesstimatorString(s) =>
let shape = Guesstimator.stringToMixedShape(~string=s, ~sampleCount, ()); Guesstimator.stringToMixedShape(~string=s, ~sampleCount, ())
let newShape = |> E.O.bind(_, Shape.Mixed.clean)
switch (shape) { |> E.O.fmap((shape: DistributionTypes.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) =>
make( make(
~generationSource=Shape(shape), ~generationSource=Shape(shape),
~probabilityType=Cdf, ~probabilityType=Pdf,
~domain=t.domain, ~domain=t.domain,
~unit=t.unit, ~unit=t.unit,
(), (),
) )
); )
| Shape(_) => Some(t) | Shape(_) => Some(t)
}; };
}; };
@ -80,7 +66,7 @@ let renderIfNeeded =
let normalize = (t: genericDistribution): option(genericDistribution) => { let normalize = (t: genericDistribution): option(genericDistribution) => {
switch (t.generationSource) { switch (t.generationSource) {
| Shape(shape) => | Shape(shape) =>
Shape.PointsType.normalizePdf(shape) Shape.T.Pdf.normalize(shape)
|> E.O.fmap(shape => {...t, generationSource: Shape(shape)}) |> E.O.fmap(shape => {...t, generationSource: Shape(shape)})
| GuesstimatorString(_) => Some(t) | GuesstimatorString(_) => Some(t)
}; };
@ -91,7 +77,7 @@ let yIntegral = (t: DistributionTypes.genericDistribution, x) => {
let normalize = n => n *. Domain.normalizeProbabilityMass(t.domain); let normalize = n => n *. Domain.normalizeProbabilityMass(t.domain);
switch (t) { switch (t) {
| {generationSource: Shape(shape)} => | {generationSource: Shape(shape)} =>
Shape.PointsType.yIntegral(shape, x) Shape.T.yIntegral(shape, x)
|> E.O.fmap(addInitialMass) |> E.O.fmap(addInitialMass)
|> E.O.fmap(normalize) |> E.O.fmap(normalize)
| _ => None | _ => None

View File

@ -25,6 +25,8 @@ module XYShape = {
let fmap = (t: t, y): t => {xs: t.xs, ys: t.ys |> E.A.fmap(y)}; 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) => let scaleCdfTo = (~scaleTo=1., t: t) =>
switch (_lastElement(t.ys)) { switch (_lastElement(t.ys)) {
| Some(n) => | Some(n) =>
@ -135,6 +137,10 @@ module Discrete = {
let toJs = XYShape.toJs; let toJs = XYShape.toJs;
let ySum = XYShape.ySum; let ySum = XYShape.ySum;
let zip = t => Belt.Array.zip(t.xs, t.ys); 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 scaleYToTotal = (totalDesired, t: t): t => {
let difference = totalDesired /. ySum(t); let difference = totalDesired /. ySum(t);
@ -214,13 +220,17 @@ module Mixed = {
type yPdfPoint = { type yPdfPoint = {
continuous: option(float), continuous: option(float),
discrete: option(float), discrete: option(float),
discreteProbabilityMassFraction: float,
}; };
let findY = (t: DistributionTypes.mixedShape, x: float): yPdfPoint => { let findY = (t: DistributionTypes.mixedShape, x: float): yPdfPoint => {
continuous: Continuous.findY(x, t.continuous) |> E.O.some, continuous:
discrete: Discrete.findY(x, t.discrete) |> E.O.some, Continuous.findY(x, t.continuous)
discreteProbabilityMassFraction: t.discreteProbabilityMassFraction, |> (e => e *. (1. -. t.discreteProbabilityMassFraction))
|> E.O.some,
discrete:
Discrete.findY(x, t.discrete)
|> (e => e *. t.discreteProbabilityMassFraction)
|> E.O.some,
}; };
let findYIntegral = let findYIntegral =
@ -232,10 +242,36 @@ module Mixed = {
| _ => None | _ => 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 = { module T = {
type t = DistributionTypes.pointsType; 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) => let y = (t: t, x: float) =>
switch (t) { switch (t) {
@ -268,22 +304,6 @@ module PointsType = {
| Continuous(continuousShape) => Continuous.maxX(continuousShape) | 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) => let discreteComponent = (t: t) =>
switch (t) { switch (t) {
| Mixed({discrete}) => Some(discrete) | Mixed({discrete}) => Some(discrete)
@ -319,7 +339,20 @@ module PointsType = {
}; };
}; };
let normalizeCdf = (t: DistributionTypes.pointsType) => { let pointwiseFmap = (fn, t: t): shape =>
switch (t) {
| Mixed({discrete, continuous, discreteProbabilityMassFraction}) =>
Mixed({
continuous: XYShape.pointwiseMap(fn, continuous),
discrete: XYShape.pointwiseMap(fn, discrete),
discreteProbabilityMassFraction,
})
| Discrete(x) => Discrete(XYShape.pointwiseMap(fn, x))
| Continuous(x) => Continuous(XYShape.pointwiseMap(fn, x))
};
module Cdf = {
let normalizeCdf = (t: DistributionTypes.shape) => {
switch (t) { switch (t) {
| Mixed({continuous, discrete, discreteProbabilityMassFraction}) => | Mixed({continuous, discrete, discreteProbabilityMassFraction}) =>
Mixed({ Mixed({
@ -332,8 +365,27 @@ module PointsType = {
Continuous(Continuous.normalizeCdf(continuousShape)) Continuous(Continuous.normalizeCdf(continuousShape))
}; };
}; };
};
let normalizePdf = (t: DistributionTypes.pointsType) => { 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) { switch (t) {
| Mixed({continuous, discrete, discreteProbabilityMassFraction}) => | Mixed({continuous, discrete, discreteProbabilityMassFraction}) =>
continuous continuous
@ -353,3 +405,57 @@ module PointsType = {
}; };
}; };
}; };
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,
};

5
src/core/Try24.re Normal file
View File

@ -0,0 +1,5 @@
open DistributionTypes;
module Shappe = {
type t = shape;
};

View File

@ -25,6 +25,7 @@ module Internals = {
[@bs.module "./GuesstimatorLibrary.js"] [@bs.module "./GuesstimatorLibrary.js"]
external toCombinedFormat: (string, int) => combined = "run"; external toCombinedFormat: (string, int) => combined = "run";
// todo: Format to correct mass, also normalize the pdf.
let toMixedShape = (r: combined): option(DistributionTypes.mixedShape) => { let toMixedShape = (r: combined): option(DistributionTypes.mixedShape) => {
let assumptions: MixedShapeBuilder.assumptions = { let assumptions: MixedShapeBuilder.assumptions = {
continuous: ADDS_TO_1, continuous: ADDS_TO_1,