2020-02-21 23:42:15 +00:00
|
|
|
module type dist = {
|
|
|
|
type t;
|
2020-03-18 20:50:01 +00:00
|
|
|
type integral;
|
2020-03-28 21:29:02 +00:00
|
|
|
let minX: t => float;
|
|
|
|
let maxX: t => float;
|
2020-06-27 04:29:21 +00:00
|
|
|
let mapY:
|
|
|
|
(~knownIntegralSumFn: float => option(float)=?, float => float, t) => t;
|
2020-02-23 13:27:52 +00:00
|
|
|
let xToY: (float, t) => DistTypes.mixedPoint;
|
|
|
|
let toShape: t => DistTypes.shape;
|
|
|
|
let toContinuous: t => option(DistTypes.continuousShape);
|
|
|
|
let toDiscrete: t => option(DistTypes.discreteShape);
|
2020-06-26 06:38:14 +00:00
|
|
|
let normalize: t => t;
|
|
|
|
let normalizedToContinuous: t => option(DistTypes.continuousShape);
|
|
|
|
let normalizedToDiscrete: t => option(DistTypes.discreteShape);
|
|
|
|
let toDiscreteProbabilityMassFraction: t => float;
|
|
|
|
let downsample: (~cache: option(integral)=?, int, t) => t;
|
2020-06-27 04:29:21 +00:00
|
|
|
let truncate: (option(float), option(float), t) => t;
|
2020-02-22 10:17:51 +00:00
|
|
|
|
2020-02-22 10:10:10 +00:00
|
|
|
let integral: (~cache: option(integral), t) => integral;
|
2020-02-24 21:01:29 +00:00
|
|
|
let integralEndY: (~cache: option(integral), t) => float;
|
2020-02-22 10:17:51 +00:00
|
|
|
let integralXtoY: (~cache: option(integral), float, t) => float;
|
2020-03-14 18:33:39 +00:00
|
|
|
let integralYtoX: (~cache: option(integral), float, t) => float;
|
2020-04-18 21:20:59 +00:00
|
|
|
|
2020-04-19 20:04:50 +00:00
|
|
|
let mean: t => float;
|
|
|
|
let variance: t => float;
|
2020-02-21 23:42:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
module Dist = (T: dist) => {
|
|
|
|
type t = T.t;
|
|
|
|
type integral = T.integral;
|
|
|
|
let minX = T.minX;
|
|
|
|
let maxX = T.maxX;
|
2020-04-11 13:22:13 +00:00
|
|
|
let integral = T.integral;
|
2020-03-28 21:29:02 +00:00
|
|
|
let xTotalRange = (t: t) => maxX(t) -. minX(t);
|
2020-03-28 14:17:47 +00:00
|
|
|
let mapY = T.mapY;
|
2020-02-21 23:42:15 +00:00
|
|
|
let xToY = T.xToY;
|
2020-06-26 06:38:14 +00:00
|
|
|
let downsample = T.downsample;
|
2020-02-22 16:24:54 +00:00
|
|
|
let toShape = T.toShape;
|
2020-06-26 06:38:14 +00:00
|
|
|
let toDiscreteProbabilityMassFraction = T.toDiscreteProbabilityMassFraction;
|
2020-02-22 16:24:54 +00:00
|
|
|
let toContinuous = T.toContinuous;
|
|
|
|
let toDiscrete = T.toDiscrete;
|
2020-06-26 06:38:14 +00:00
|
|
|
let normalize = T.normalize;
|
2020-06-27 04:29:21 +00:00
|
|
|
let truncate = T.truncate;
|
2020-06-26 06:38:14 +00:00
|
|
|
let normalizedToContinuous = T.normalizedToContinuous;
|
|
|
|
let normalizedToDiscrete = T.normalizedToDiscrete;
|
2020-04-19 20:04:50 +00:00
|
|
|
let mean = T.mean;
|
|
|
|
let variance = T.variance;
|
2020-03-18 21:46:43 +00:00
|
|
|
|
2020-02-22 10:17:51 +00:00
|
|
|
module Integral = {
|
|
|
|
type t = T.integral;
|
|
|
|
let get = T.integral;
|
|
|
|
let xToY = T.integralXtoY;
|
2020-03-14 18:33:39 +00:00
|
|
|
let yToX = T.integralYtoX;
|
2020-02-24 21:01:29 +00:00
|
|
|
let sum = T.integralEndY;
|
2020-02-22 10:17:51 +00:00
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
module Common = {
|
|
|
|
let combineIntegralSums =
|
|
|
|
(
|
|
|
|
combineFn: (float, float) => option(float),
|
|
|
|
t1KnownIntegralSum: option(float),
|
|
|
|
t2KnownIntegralSum: option(float),
|
|
|
|
) => {
|
|
|
|
switch (t1KnownIntegralSum, t2KnownIntegralSum) {
|
|
|
|
| (None, _)
|
|
|
|
| (_, None) => None
|
|
|
|
| (Some(s1), Some(s2)) => combineFn(s1, s2)
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
module Continuous = {
|
2020-02-23 13:27:52 +00:00
|
|
|
type t = DistTypes.continuousShape;
|
2020-02-22 16:24:54 +00:00
|
|
|
let getShape = (t: t) => t.xyShape;
|
|
|
|
let interpolation = (t: t) => t.interpolation;
|
2020-06-26 06:38:14 +00:00
|
|
|
let make = (interpolation, xyShape, knownIntegralSum): t => {
|
|
|
|
xyShape,
|
|
|
|
interpolation,
|
|
|
|
knownIntegralSum,
|
|
|
|
};
|
|
|
|
let shapeMap = (fn, {xyShape, interpolation, knownIntegralSum}: t): t => {
|
2020-02-22 16:24:54 +00:00
|
|
|
xyShape: fn(xyShape),
|
|
|
|
interpolation,
|
2020-06-26 06:38:14 +00:00
|
|
|
knownIntegralSum,
|
2020-02-22 16:24:54 +00:00
|
|
|
};
|
2020-03-28 21:29:02 +00:00
|
|
|
let lastY = (t: t) => t |> getShape |> XYShape.T.lastY;
|
2020-02-22 16:24:54 +00:00
|
|
|
let oShapeMap =
|
2020-06-26 06:38:14 +00:00
|
|
|
(fn, {xyShape, interpolation, knownIntegralSum}: t)
|
|
|
|
: option(DistTypes.continuousShape) =>
|
|
|
|
fn(xyShape) |> E.O.fmap(make(interpolation, _, knownIntegralSum));
|
|
|
|
|
|
|
|
let empty: DistTypes.continuousShape = {
|
|
|
|
xyShape: XYShape.T.empty,
|
|
|
|
interpolation: `Linear,
|
|
|
|
knownIntegralSum: Some(0.0),
|
|
|
|
};
|
2020-06-13 06:30:51 +00:00
|
|
|
let combine =
|
2020-06-27 04:29:21 +00:00
|
|
|
(
|
|
|
|
~knownIntegralSumsFn,
|
|
|
|
fn,
|
|
|
|
t1: DistTypes.continuousShape,
|
|
|
|
t2: DistTypes.continuousShape,
|
|
|
|
)
|
2020-06-13 06:30:51 +00:00
|
|
|
: DistTypes.continuousShape => {
|
2020-06-26 06:38:14 +00:00
|
|
|
// If we're adding the distributions, and we know the total of each, then we
|
|
|
|
// can just sum them up. Otherwise, all bets are off.
|
|
|
|
let combinedIntegralSum =
|
2020-06-27 04:29:21 +00:00
|
|
|
Common.combineIntegralSums(
|
|
|
|
knownIntegralSumsFn,
|
|
|
|
t1.knownIntegralSum,
|
|
|
|
t2.knownIntegralSum,
|
|
|
|
);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
make(
|
|
|
|
`Linear,
|
|
|
|
XYShape.Combine.combine(
|
|
|
|
~xsSelection=ALL_XS,
|
|
|
|
~xToYSelection=XYShape.XtoY.linear,
|
|
|
|
~fn,
|
|
|
|
t1.xyShape,
|
|
|
|
t2.xyShape,
|
|
|
|
),
|
|
|
|
combinedIntegralSum,
|
|
|
|
);
|
2020-06-13 06:30:51 +00:00
|
|
|
};
|
|
|
|
|
2020-02-24 21:01:29 +00:00
|
|
|
let toLinear = (t: t): option(t) => {
|
2020-02-22 23:55:43 +00:00
|
|
|
switch (t) {
|
2020-06-26 06:38:14 +00:00
|
|
|
| {interpolation: `Stepwise, xyShape, knownIntegralSum} =>
|
|
|
|
xyShape
|
|
|
|
|> XYShape.Range.stepsToContinuous
|
|
|
|
|> E.O.fmap(make(`Linear, _, knownIntegralSum))
|
|
|
|
| {interpolation: `Linear} => Some(t)
|
2020-02-22 23:55:43 +00:00
|
|
|
};
|
2020-02-24 21:01:29 +00:00
|
|
|
};
|
2020-03-28 21:29:02 +00:00
|
|
|
let shapeFn = (fn, t: t) => t |> getShape |> fn;
|
2020-06-27 04:29:21 +00:00
|
|
|
let updateKnownIntegralSum = (knownIntegralSum, t: t): t => {
|
|
|
|
...t,
|
|
|
|
knownIntegralSum,
|
|
|
|
};
|
|
|
|
|
|
|
|
let reduce =
|
|
|
|
(
|
|
|
|
~knownIntegralSumsFn: (float, float) => option(float)=(_, _) => None,
|
|
|
|
fn,
|
|
|
|
continuousShapes,
|
|
|
|
) =>
|
|
|
|
continuousShapes
|
|
|
|
|> E.A.fold_left(combine(~knownIntegralSumsFn, fn), empty);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
// Contracts every point in the continuous xyShape into a single dirac-Delta-like point,
|
|
|
|
// using the centerpoints between adjacent xs and the area under each trapezoid.
|
|
|
|
// This is essentially like integrateWithTriangles, without the accumulation.
|
|
|
|
let toDiscretePointMasses = (t: t): DistTypes.discreteShape => {
|
|
|
|
let tl = t |> getShape |> XYShape.T.length;
|
2020-06-27 05:37:24 +00:00
|
|
|
let pointMassesX: array(float) = Belt.Array.make(tl - 1, 0.0);
|
2020-06-26 06:38:14 +00:00
|
|
|
let pointMassesY: array(float) = Belt.Array.make(tl - 1, 0.0);
|
|
|
|
let {xs, ys}: XYShape.T.t = t |> getShape;
|
|
|
|
for (x in 0 to E.A.length(xs) - 2) {
|
|
|
|
let _ =
|
|
|
|
Belt.Array.set(
|
|
|
|
pointMassesY,
|
|
|
|
x,
|
2020-06-27 04:29:21 +00:00
|
|
|
(xs[x + 1] -. xs[x]) *. ((ys[x] +. ys[x + 1]) /. 2.),
|
|
|
|
); // = dx * (1/2) * (avgY)
|
2020-06-27 05:37:24 +00:00
|
|
|
let _ =
|
|
|
|
Belt.Array.set(
|
|
|
|
pointMassesX,
|
|
|
|
x,
|
|
|
|
(xs[x] +. xs[x + 1]) /. 2.,
|
|
|
|
); // midpoints
|
2020-06-26 06:38:14 +00:00
|
|
|
();
|
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
{
|
|
|
|
xyShape: {
|
2020-06-27 05:37:24 +00:00
|
|
|
xs: pointMassesX,
|
2020-06-27 04:29:21 +00:00
|
|
|
ys: pointMassesY,
|
|
|
|
},
|
|
|
|
knownIntegralSum: t.knownIntegralSum,
|
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let mapY = (~knownIntegralSumFn=previousKnownIntegralSum => None, fn, t: t) => {
|
2020-06-26 06:38:14 +00:00
|
|
|
let u = E.O.bind(_, knownIntegralSumFn);
|
|
|
|
let yMapFn = shapeMap(XYShape.T.mapY(fn));
|
|
|
|
|
|
|
|
t |> yMapFn |> updateKnownIntegralSum(u(t.knownIntegralSum));
|
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let scaleBy = (~scale=1.0, t: t): t => {
|
|
|
|
t
|
|
|
|
|> mapY((r: float) => r *. scale)
|
|
|
|
|> updateKnownIntegralSum(
|
|
|
|
E.O.bind(t.knownIntegralSum, v => Some(scale *. v)),
|
|
|
|
);
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
2020-02-24 21:01:29 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
module T =
|
|
|
|
Dist({
|
2020-02-23 13:27:52 +00:00
|
|
|
type t = DistTypes.continuousShape;
|
|
|
|
type integral = DistTypes.continuousShape;
|
2020-03-28 21:46:17 +00:00
|
|
|
let minX = shapeFn(XYShape.T.minX);
|
|
|
|
let maxX = shapeFn(XYShape.T.maxX);
|
2020-06-26 06:38:14 +00:00
|
|
|
let mapY = mapY;
|
|
|
|
let toDiscreteProbabilityMassFraction = _ => 0.0;
|
2020-02-23 13:27:52 +00:00
|
|
|
let toShape = (t: t): DistTypes.shape => Continuous(t);
|
2020-03-28 21:46:17 +00:00
|
|
|
let xToY = (f, {interpolation, xyShape}: t) => {
|
|
|
|
(
|
|
|
|
switch (interpolation) {
|
|
|
|
| `Stepwise =>
|
|
|
|
xyShape
|
|
|
|
|> XYShape.XtoY.stepwiseIncremental(f)
|
|
|
|
|> E.O.default(0.0)
|
|
|
|
| `Linear => xyShape |> XYShape.XtoY.linear(f)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|> DistTypes.MixedPoint.makeContinuous;
|
|
|
|
};
|
2020-04-18 21:27:24 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let truncate =
|
|
|
|
(leftCutoff: option(float), rightCutoff: option(float), t: t) => {
|
|
|
|
let truncatedZippedPairs =
|
|
|
|
t
|
|
|
|
|> getShape
|
|
|
|
|> XYShape.T.zip
|
|
|
|
|> XYShape.Zipped.filterByX(x =>
|
|
|
|
x >= E.O.default(neg_infinity, leftCutoff)
|
|
|
|
|| x <= E.O.default(infinity, rightCutoff)
|
|
|
|
);
|
|
|
|
|
|
|
|
let eps = (t |> getShape |> XYShape.T.xTotalRange) *. 0.0001;
|
|
|
|
|
|
|
|
let leftNewPoint =
|
|
|
|
leftCutoff |> E.O.dimap(lc => [|(lc -. eps, 0.)|], _ => [||]);
|
|
|
|
let rightNewPoint =
|
|
|
|
rightCutoff |> E.O.dimap(rc => [|(rc +. eps, 0.)|], _ => [||]);
|
|
|
|
|
|
|
|
let truncatedZippedPairsWithNewPoints =
|
|
|
|
E.A.concatMany([|leftNewPoint, truncatedZippedPairs, rightNewPoint|]);
|
|
|
|
let truncatedShape =
|
|
|
|
XYShape.T.fromZippedArray(truncatedZippedPairsWithNewPoints);
|
|
|
|
|
|
|
|
make(`Linear, truncatedShape, None);
|
|
|
|
};
|
2020-02-24 16:39:55 +00:00
|
|
|
|
2020-03-28 21:46:17 +00:00
|
|
|
// TODO: This should work with stepwise plots.
|
2020-02-24 11:11:03 +00:00
|
|
|
let integral = (~cache, t) =>
|
2020-02-25 12:28:26 +00:00
|
|
|
switch (cache) {
|
|
|
|
| Some(cache) => cache
|
|
|
|
| None =>
|
|
|
|
t
|
2020-03-28 21:29:02 +00:00
|
|
|
|> getShape
|
2020-02-25 12:28:26 +00:00
|
|
|
|> XYShape.Range.integrateWithTriangles
|
|
|
|
|> E.O.toExt("This should not have happened")
|
2020-06-26 06:38:14 +00:00
|
|
|
|> make(`Linear, _, None)
|
2020-02-25 12:28:26 +00:00
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
let downsample = (~cache=None, length, t): t =>
|
2020-03-18 20:50:01 +00:00
|
|
|
t
|
|
|
|
|> shapeMap(
|
2020-03-28 14:17:47 +00:00
|
|
|
XYShape.XsConversion.proportionByProbabilityMass(
|
2020-03-28 21:46:17 +00:00
|
|
|
length,
|
2020-03-18 20:50:01 +00:00
|
|
|
integral(~cache, t).xyShape,
|
|
|
|
),
|
|
|
|
);
|
2020-06-26 06:38:14 +00:00
|
|
|
let integralEndY = (~cache, t: t) =>
|
|
|
|
t.knownIntegralSum |> E.O.default(t |> integral(~cache) |> lastY);
|
|
|
|
let integralXtoY = (~cache, f, t: t) =>
|
2020-03-28 14:17:47 +00:00
|
|
|
t |> integral(~cache) |> shapeFn(XYShape.XtoY.linear(f));
|
2020-06-26 06:38:14 +00:00
|
|
|
let integralYtoX = (~cache, f, t: t) =>
|
2020-03-28 14:17:47 +00:00
|
|
|
t |> integral(~cache) |> shapeFn(XYShape.YtoX.linear(f));
|
2020-02-22 16:24:54 +00:00
|
|
|
let toContinuous = t => Some(t);
|
|
|
|
let toDiscrete = _ => None;
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let normalize = (t: t): t => {
|
2020-06-27 04:29:21 +00:00
|
|
|
t
|
|
|
|
|> scaleBy(~scale=1. /. integralEndY(~cache=None, t))
|
|
|
|
|> updateKnownIntegralSum(Some(1.0));
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let normalizedToContinuous = t => Some(t); // TODO: this should be normalized
|
|
|
|
let normalizedToDiscrete = _ => None;
|
2020-04-18 21:20:59 +00:00
|
|
|
|
2020-04-19 20:04:50 +00:00
|
|
|
let mean = (t: t) => {
|
2020-04-18 21:27:24 +00:00
|
|
|
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 2.0 /. 2.0;
|
|
|
|
let indefiniteIntegralLinear = (p, a, b) =>
|
|
|
|
a *. p ** 2.0 /. 2.0 +. b *. p ** 3.0 /. 3.0;
|
|
|
|
XYShape.Analysis.integrateContinuousShape(
|
|
|
|
~indefiniteIntegralStepwise,
|
|
|
|
~indefiniteIntegralLinear,
|
|
|
|
t,
|
|
|
|
);
|
|
|
|
};
|
2020-04-19 20:04:50 +00:00
|
|
|
let variance = (t: t): float =>
|
2020-04-18 21:27:24 +00:00
|
|
|
XYShape.Analysis.getVarianceDangerously(
|
|
|
|
t,
|
2020-04-19 20:04:50 +00:00
|
|
|
mean,
|
2020-04-18 21:27:24 +00:00
|
|
|
XYShape.Analysis.getMeanOfSquaresContinuousShape,
|
|
|
|
);
|
2020-02-22 16:24:54 +00:00
|
|
|
});
|
2020-06-27 05:37:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
/* Performs a discrete convolution between two continuous distributions A and B.
|
|
|
|
* It is an extremely good idea to downsample the distributions beforehand,
|
|
|
|
* because the number of samples in the convolution can be up to length(A) * length(B).
|
|
|
|
*
|
|
|
|
* Conventional convolution uses fn = (+.), but we also allow other operations to combine the xs.
|
|
|
|
*
|
|
|
|
* In practice, the convolution works by multiplying the ys for each possible combo of points of
|
|
|
|
* the two shapes. This creates a new shape for each point of A. These new shapes are then combined
|
|
|
|
* linearly. This may not always be the most efficient way, but it is probably the most robust for now.
|
|
|
|
*
|
|
|
|
* In the future, it may be possible to use a non-uniform fast Fourier transform instead (although only for addition).
|
|
|
|
*/
|
|
|
|
let convolveWithDiscrete = (~downsample=false, fn, t1: t, t2: DistTypes.discreteShape) => {
|
|
|
|
let t1s = t1 |> getShape;
|
|
|
|
let t2s = t2.xyShape; // would like to use Discrete.getShape here, but current file structure doesn't allow for that
|
|
|
|
let t1n = t1s |> XYShape.T.length;
|
|
|
|
let t2n = t2s |> XYShape.T.length;
|
|
|
|
|
|
|
|
let outXYShapes: array(array((float, float))) =
|
2020-06-27 06:16:37 +00:00
|
|
|
Belt.Array.makeUninitializedUnsafe(t2n);
|
2020-06-27 05:37:24 +00:00
|
|
|
|
2020-06-27 06:16:37 +00:00
|
|
|
for (j in 0 to t2n - 1) { // for each one of the discrete points
|
|
|
|
// create a new distribution, as long as the original continuous one
|
|
|
|
let dxyShape: array((float, float)) = Belt.Array.makeUninitializedUnsafe(t1n);
|
|
|
|
for (i in 0 to t1n - 1) {
|
2020-06-27 05:37:24 +00:00
|
|
|
let _ =
|
|
|
|
Belt.Array.set(
|
|
|
|
dxyShape,
|
2020-06-27 06:16:37 +00:00
|
|
|
i,
|
2020-06-27 05:37:24 +00:00
|
|
|
(fn(t1s.xs[i], t2s.xs[j]), t1s.ys[i] *. t2s.ys[j]),
|
|
|
|
);
|
|
|
|
();
|
|
|
|
};
|
|
|
|
|
2020-06-27 06:16:37 +00:00
|
|
|
let _ = Belt.Array.set(outXYShapes, j, dxyShape);
|
2020-06-27 05:37:24 +00:00
|
|
|
();
|
|
|
|
};
|
|
|
|
|
|
|
|
let combinedIntegralSum = Common.combineIntegralSums((a, b) => Some(a *. b), t1.knownIntegralSum, t2.knownIntegralSum);
|
|
|
|
|
|
|
|
outXYShapes
|
|
|
|
|> E.A.fmap(s => {
|
|
|
|
let xyShape = XYShape.T.fromZippedArray(s);
|
|
|
|
make(`Linear, xyShape, None);
|
|
|
|
})
|
|
|
|
|> reduce((+.))
|
|
|
|
|> updateKnownIntegralSum(combinedIntegralSum);
|
|
|
|
};
|
|
|
|
|
2020-06-28 06:50:53 +00:00
|
|
|
/* This function takes a continuous distribution and efficiently approximates it as
|
|
|
|
point masses that have variances associated with them.
|
|
|
|
We estimate the means and variances from overlapping triangular distributions which we imagine are making up the
|
|
|
|
XYShape.
|
|
|
|
We can then use the algebra of random variables to "convolve" the point masses and their variances,
|
|
|
|
and finally reconstruct a new distribution from them, e.g. using a Fast Gauss Transform or Raykar et al. (2007). */
|
|
|
|
type pointMassesWithMoments = {
|
|
|
|
n: int,
|
|
|
|
masses: array(float),
|
|
|
|
means: array(float),
|
|
|
|
variances: array(float)
|
|
|
|
};
|
|
|
|
let toDiscretePointMassesFromTriangulars = (~inverse=False, t: t): pointMassesWithMoments => {
|
|
|
|
// TODO: what if there is only one point in the distribution?
|
|
|
|
let s = t |> getShape;
|
|
|
|
let n = s |> XYShape.T.length;
|
|
|
|
// first, double up the leftmost and rightmost points:
|
|
|
|
let {xs, ys}: XYShape.T.t = s;
|
|
|
|
let _ = Js.Array.unshift(xs[0], xs);
|
|
|
|
let _ = Js.Array.unshift(ys[0], ys);
|
|
|
|
let _ = Js.Array.push(xs[n - 1], xs);
|
|
|
|
let _ = Js.Array.push(ys[n - 1], ys);
|
|
|
|
let n = E.A.length(xs);
|
|
|
|
// squares and neighbourly products of the xs
|
|
|
|
let xsSq: array(float) = Belt.Array.makeUninitializedUnsafe(n);
|
|
|
|
let xsProdN1: array(float) = Belt.Array.makeUninitializedUnsafe(n - 1);
|
|
|
|
let xsProdN2: array(float) = Belt.Array.makeUninitializedUnsafe(n - 2);
|
|
|
|
for (i in 0 to n - 1) {
|
|
|
|
let _ = Belt.Array.set(xsSq, i, xs[i] *. xs[i]); ();
|
|
|
|
};
|
|
|
|
for (i in 0 to n - 2) {
|
|
|
|
let _ = Belt.Array.set(xsProdN1, i, xs[i] *. xs[i + 1]); ();
|
|
|
|
};
|
|
|
|
for (i in 0 to n - 3) {
|
|
|
|
let _ = Belt.Array.set(xsProdN2, i, xs[i] *. xs[i + 2]); ();
|
|
|
|
};
|
|
|
|
// means and variances
|
|
|
|
let masses: array(float) = Belt.Array.makeUninitializedUnsafe(n);
|
|
|
|
let means: array(float) = Belt.Array.makeUninitializedUnsafe(n);
|
|
|
|
let variances: array(float) = Belt.Array.makeUninitializedUnsafe(n);
|
|
|
|
|
|
|
|
if (inverse) {
|
|
|
|
for (i in 1 to n - 2) {
|
|
|
|
let _ = Belt.Array.set(masses, i - 1, (xs[i + 1] -. xs[i - 1]) *. ys[i] /. 2.);
|
|
|
|
|
|
|
|
// this only works when the whole triange is either on the left or on the right of zero
|
|
|
|
let a = xs[i - 1];
|
|
|
|
let c = xs[i];
|
|
|
|
let b = xs[i + 1];
|
|
|
|
|
|
|
|
// These are the moments of the reciprocal of a triangular distribution, as symbolically integrated by Mathematica.
|
|
|
|
// They're probably pretty close to invMean ~ 1/mean = 3/(a+b+c) and invVar. But I haven't worked out
|
|
|
|
// the worst case error, so for now let's use these monster equations
|
|
|
|
let inverseMean = 2. *. ((a *. log(a/.c) /. (a-.c)) +. ((b *. log(c/.b))/.(b-.c))) /. (a -. b);
|
|
|
|
let inverseVar = 2. *. ((log(c/.a) /. (a-.c)) +. ((b *. log(b/.c))/.(b-.c))) /. (a -. b) - inverseMean ** 2.;
|
|
|
|
|
|
|
|
let _ = Belt.Array.set(means, i - 1, inverseMean);
|
|
|
|
|
|
|
|
let _ = Belt.Array.set(variances, i - 1, inverseVar);
|
|
|
|
();
|
|
|
|
};
|
|
|
|
|
|
|
|
{n, masses, means, variances};
|
|
|
|
} else {
|
|
|
|
for (i in 1 to n - 2) {
|
|
|
|
let _ = Belt.Array.set(masses, i - 1, (xs[i + 1] -. xs[i - 1]) *. ys[i] /. 2.);
|
|
|
|
let _ = Belt.Array.set(means, i - 1, (xs[i - 1] +. xs[i] +. xs[i + 1]) /. 3.);
|
|
|
|
|
|
|
|
let _ = Belt.Array.set(variances, i - 1,
|
|
|
|
(xsSq[i-1] +. xsSq[i] +. xsSq[i+1] -. xsProdN1[i-1] -. xsProdN1[i] -. xsProdN2[i-1]) /. 18.);
|
|
|
|
();
|
|
|
|
};
|
|
|
|
{n, masses, means, variances};
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2020-06-27 05:37:24 +00:00
|
|
|
let convolve = (~downsample=false, fn, t1: t, t2: t) => {
|
|
|
|
let downsampleIfTooLarge = (t: t) => {
|
|
|
|
let sqtl = sqrt(float_of_int(t |> getShape |> XYShape.T.length));
|
2020-06-27 06:16:37 +00:00
|
|
|
sqtl > 10. && downsample && false ? T.downsample(int_of_float(sqtl), t) : t;
|
2020-06-27 05:37:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let t1d = downsampleIfTooLarge(t1);
|
|
|
|
let t2d = downsampleIfTooLarge(t2);
|
|
|
|
|
2020-06-28 06:50:53 +00:00
|
|
|
// if we add the two distributions, we should probably use normal filters.
|
|
|
|
// if we multiply the two distributions, we should probably use lognormal filters.
|
|
|
|
let t1m = toDiscretePointMassesFromTriangulars(t1);
|
|
|
|
let t2m = toDiscretePointMassesFromTriangulars(t2);
|
|
|
|
|
|
|
|
let convolveMeansFn = (TreeNode.standardOp) => fun
|
|
|
|
| `Add => (m1, m2) => m1 +. m2
|
|
|
|
| `Subtract => (m1, m2) => m1 -. m2
|
|
|
|
| `Multiply => (m1, m2) => m1 *. m2
|
|
|
|
| `Divide => (m1, mInv2) => m1 *. mInv2; // note: here, mInv2 = mean(1 / t2)
|
|
|
|
|
|
|
|
// converts the variances and means of the two inputs into the variance of the output
|
|
|
|
let convolveVariancesFn = (TreeNode.standardOp) => fun
|
|
|
|
| `Add => (v1, v2, m1, m2) => v1 +. v2
|
|
|
|
| `Subtract => (v1, v2, m1, m2) => v1 +. v2
|
|
|
|
| `Multiply => (v1, v2, m1, m2) => (v1 *. v2) +. (v1 *. m1**2.) +. (v2 *. m1**2.)
|
|
|
|
| `Divide => (v1, vInv2, m1, mInv2) => (v1 *. vInv2) +. (v1 *. mInv2**2.) +. (vInv2 *. m1**2.);
|
|
|
|
|
|
|
|
let masses: array(float) = Belt.Array.makeUninitializedUnsafe(t1m.n * t2m.n);
|
|
|
|
let means: array(float) = Belt.Array.makeUninitializedUnsafe(t1m.n * t2m.n);
|
|
|
|
let variances: array(float) = Belt.Array.makeUninitializedUnsafe(t1m.n * t2m.n);
|
|
|
|
// then convolve the two sets of pointMassesWithMoments
|
|
|
|
for (i in 0 to t1m.n - 1) {
|
|
|
|
for (j in 0 to t2m.n - 1) {
|
|
|
|
let k = i * t2m.n + j;
|
|
|
|
let _ = Belt.Array.set(masses, k, t1m.masses[i] *. t2m.masses[j]);
|
|
|
|
let _ = Belt.Array.set(means, k, convolveMeansFn(t1m.means[i], t2m.means[j]));
|
|
|
|
let _ = Belt.Array.set(variances, k, convolveMeansFn(t1m.variances[i], t2m.variances[j], t1m.means[i], t2m.means[j]));
|
|
|
|
};
|
|
|
|
};
|
2020-06-27 06:48:54 +00:00
|
|
|
|
2020-06-28 06:50:53 +00:00
|
|
|
// now, run a Fast Gauss transform to estimate the new distribution:
|
2020-06-27 06:48:54 +00:00
|
|
|
|
2020-06-27 05:37:24 +00:00
|
|
|
};
|
2020-02-22 16:24:54 +00:00
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
module Discrete = {
|
2020-06-26 06:38:14 +00:00
|
|
|
type t = DistTypes.discreteShape;
|
|
|
|
|
|
|
|
let make = (xyShape, knownIntegralSum): t => {xyShape, knownIntegralSum};
|
|
|
|
let shapeMap = (fn, {xyShape, knownIntegralSum}: t): t => {
|
|
|
|
xyShape: fn(xyShape),
|
|
|
|
knownIntegralSum,
|
|
|
|
};
|
|
|
|
let getShape = (t: t) => t.xyShape;
|
|
|
|
let oShapeMap = (fn, {xyShape, knownIntegralSum}: t): option(t) =>
|
|
|
|
fn(xyShape) |> E.O.fmap(make(_, knownIntegralSum));
|
|
|
|
|
|
|
|
let empty: t = {xyShape: XYShape.T.empty, knownIntegralSum: Some(0.0)};
|
|
|
|
let shapeFn = (fn, t: t) => t |> getShape |> fn;
|
|
|
|
|
|
|
|
let lastY = (t: t) => t |> getShape |> XYShape.T.lastY;
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let combine =
|
|
|
|
(
|
|
|
|
~knownIntegralSumsFn,
|
|
|
|
fn,
|
|
|
|
t1: DistTypes.discreteShape,
|
|
|
|
t2: DistTypes.discreteShape,
|
|
|
|
)
|
2020-04-18 21:20:59 +00:00
|
|
|
: DistTypes.discreteShape => {
|
2020-06-27 04:29:21 +00:00
|
|
|
let combinedIntegralSum =
|
|
|
|
Common.combineIntegralSums(
|
|
|
|
knownIntegralSumsFn,
|
|
|
|
t1.knownIntegralSum,
|
|
|
|
t2.knownIntegralSum,
|
|
|
|
);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
make(
|
|
|
|
XYShape.Combine.combine(
|
|
|
|
~xsSelection=ALL_XS,
|
|
|
|
~xToYSelection=XYShape.XtoY.stepwiseIfAtX,
|
2020-06-27 04:29:21 +00:00
|
|
|
~fn=((a, b) => fn(E.O.default(0.0, a), E.O.default(0.0, b))), // stepwiseIfAtX returns option(float), so this fn needs to handle None
|
2020-06-26 06:38:14 +00:00
|
|
|
t1.xyShape,
|
|
|
|
t2.xyShape,
|
|
|
|
),
|
|
|
|
combinedIntegralSum,
|
2020-04-18 21:20:59 +00:00
|
|
|
);
|
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let reduce = (~knownIntegralSumsFn=(_, _) => None, fn, discreteShapes): DistTypes.discreteShape =>
|
|
|
|
discreteShapes
|
|
|
|
|> E.A.fold_left(combine(~knownIntegralSumsFn, fn), empty);
|
|
|
|
|
|
|
|
let updateKnownIntegralSum = (knownIntegralSum, t: t): t => {
|
|
|
|
...t,
|
|
|
|
knownIntegralSum,
|
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let convolve = (fn, t1: t, t2: t) => {
|
|
|
|
let t1s = t1 |> getShape;
|
|
|
|
let t2s = t2 |> getShape;
|
|
|
|
let t1n = t1s |> XYShape.T.length;
|
|
|
|
let t2n = t2s |> XYShape.T.length;
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let combinedIntegralSum =
|
|
|
|
Common.combineIntegralSums(
|
|
|
|
(s1, s2) => Some(s1 *. s2),
|
|
|
|
t1.knownIntegralSum,
|
|
|
|
t2.knownIntegralSum,
|
|
|
|
);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let xToYMap = E.FloatFloatMap.empty();
|
|
|
|
|
|
|
|
for (i in 0 to t1n - 1) {
|
|
|
|
for (j in 0 to t2n - 1) {
|
|
|
|
let x = fn(t1s.xs[i], t2s.xs[j]);
|
|
|
|
let cv = xToYMap |> E.FloatFloatMap.get(x) |> E.O.default(0.);
|
|
|
|
let my = t1s.ys[i] *. t2s.ys[j];
|
|
|
|
let _ = Belt.MutableMap.set(xToYMap, x, cv +. my);
|
|
|
|
();
|
2020-06-27 04:29:21 +00:00
|
|
|
};
|
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let rxys = xToYMap |> E.FloatFloatMap.toArray |> XYShape.Zipped.sortByX;
|
|
|
|
|
|
|
|
let convolvedShape = XYShape.T.fromZippedArray(rxys);
|
|
|
|
|
|
|
|
make(convolvedShape, combinedIntegralSum);
|
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let mapY = (~knownIntegralSumFn=previousKnownIntegralSum => None, fn, t: t) => {
|
2020-06-26 06:38:14 +00:00
|
|
|
let u = E.O.bind(_, knownIntegralSumFn);
|
|
|
|
let yMapFn = shapeMap(XYShape.T.mapY(fn));
|
|
|
|
|
|
|
|
t |> yMapFn |> updateKnownIntegralSum(u(t.knownIntegralSum));
|
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let scaleBy = (~scale=1.0, t: t): t => {
|
|
|
|
t
|
|
|
|
|> mapY((r: float) => r *. scale)
|
|
|
|
|> updateKnownIntegralSum(
|
|
|
|
E.O.bind(t.knownIntegralSum, v => Some(scale *. v)),
|
|
|
|
);
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
2020-04-18 21:20:59 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
module T =
|
|
|
|
Dist({
|
2020-02-23 13:27:52 +00:00
|
|
|
type t = DistTypes.discreteShape;
|
|
|
|
type integral = DistTypes.continuousShape;
|
2020-02-22 16:24:54 +00:00
|
|
|
let integral = (~cache, t) =>
|
2020-02-25 12:28:26 +00:00
|
|
|
switch (cache) {
|
|
|
|
| Some(c) => c
|
2020-06-26 06:38:14 +00:00
|
|
|
| None =>
|
|
|
|
Continuous.make(
|
|
|
|
`Stepwise,
|
|
|
|
XYShape.T.accumulateYs((+.), getShape(t)),
|
|
|
|
None,
|
|
|
|
)
|
2020-02-25 12:28:26 +00:00
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
let integralEndY = (~cache, t: t) =>
|
2020-06-27 04:29:21 +00:00
|
|
|
t.knownIntegralSum
|
|
|
|
|> E.O.default(t |> integral(~cache) |> Continuous.lastY);
|
2020-06-26 06:38:14 +00:00
|
|
|
let minX = shapeFn(XYShape.T.minX);
|
|
|
|
let maxX = shapeFn(XYShape.T.maxX);
|
|
|
|
let toDiscreteProbabilityMassFraction = _ => 1.0;
|
|
|
|
let mapY = mapY;
|
2020-02-23 13:27:52 +00:00
|
|
|
let toShape = (t: t): DistTypes.shape => Discrete(t);
|
2020-02-22 16:24:54 +00:00
|
|
|
let toContinuous = _ => None;
|
|
|
|
let toDiscrete = t => Some(t);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let normalize = (t: t): t => {
|
2020-06-27 04:29:21 +00:00
|
|
|
t
|
|
|
|
|> scaleBy(~scale=1. /. integralEndY(~cache=None, t))
|
|
|
|
|> updateKnownIntegralSum(Some(1.0));
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let normalizedToContinuous = _ => None;
|
|
|
|
let normalizedToDiscrete = t => Some(t); // TODO: this should be normalized!
|
|
|
|
|
|
|
|
let downsample = (~cache=None, i, t: t): t => {
|
|
|
|
// It's not clear how to downsample a set of discrete points in a meaningful way.
|
|
|
|
// The best we can do is to clip off the smallest values.
|
2020-06-27 05:37:24 +00:00
|
|
|
let currentLength = t |> getShape |> XYShape.T.length;
|
|
|
|
|
|
|
|
if (i < currentLength) {
|
|
|
|
let clippedShape =
|
|
|
|
t
|
|
|
|
|> getShape
|
|
|
|
|> XYShape.T.zip
|
|
|
|
|> XYShape.Zipped.sortByY
|
|
|
|
|> Belt.Array.reverse
|
|
|
|
|> Belt.Array.slice(_, ~offset=0, ~len=i)
|
|
|
|
|> XYShape.Zipped.sortByX
|
|
|
|
|> XYShape.T.fromZippedArray;
|
|
|
|
|
|
|
|
make(clippedShape, None); // if someone needs the sum, they'll have to recompute it
|
|
|
|
} else {
|
|
|
|
t;
|
|
|
|
}
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let truncate =
|
|
|
|
(leftCutoff: option(float), rightCutoff: option(float), t: t): t => {
|
|
|
|
let truncatedShape =
|
|
|
|
t
|
|
|
|
|> getShape
|
|
|
|
|> XYShape.T.zip
|
|
|
|
|> XYShape.Zipped.filterByX(x =>
|
|
|
|
x >= E.O.default(neg_infinity, leftCutoff)
|
|
|
|
|| x <= E.O.default(infinity, rightCutoff)
|
|
|
|
)
|
|
|
|
|> XYShape.T.fromZippedArray;
|
|
|
|
|
|
|
|
make(truncatedShape, None);
|
|
|
|
};
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let xToY = (f, t) =>
|
2020-03-15 00:30:18 +00:00
|
|
|
t
|
2020-06-26 06:38:14 +00:00
|
|
|
|> getShape
|
|
|
|
|> XYShape.XtoY.stepwiseIfAtX(f)
|
2020-02-23 19:40:38 +00:00
|
|
|
|> E.O.default(0.0)
|
2020-02-23 13:27:52 +00:00
|
|
|
|> DistTypes.MixedPoint.makeDiscrete;
|
2020-03-14 18:33:39 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
let integralXtoY = (~cache, f, t) =>
|
2020-03-26 23:18:19 +00:00
|
|
|
t
|
|
|
|
|> integral(~cache)
|
|
|
|
|> Continuous.getShape
|
2020-03-28 14:17:47 +00:00
|
|
|
|> XYShape.XtoY.linear(f);
|
2020-03-14 18:33:39 +00:00
|
|
|
|
|
|
|
let integralYtoX = (~cache, f, t) =>
|
2020-03-26 23:18:19 +00:00
|
|
|
t
|
|
|
|
|> integral(~cache)
|
|
|
|
|> Continuous.getShape
|
2020-03-28 14:17:47 +00:00
|
|
|
|> XYShape.YtoX.linear(f);
|
2020-04-18 21:20:59 +00:00
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let mean = (t: t): float => {
|
|
|
|
let s = getShape(t);
|
|
|
|
E.A.reducei(s.xs, 0.0, (acc, x, i) => acc +. x *. s.ys[i]);
|
|
|
|
};
|
2020-04-19 20:04:50 +00:00
|
|
|
let variance = (t: t): float => {
|
2020-04-18 21:27:24 +00:00
|
|
|
let getMeanOfSquares = t =>
|
2020-06-26 06:38:14 +00:00
|
|
|
t |> shapeMap(XYShape.Analysis.squareXYShape) |> mean;
|
2020-04-19 20:04:50 +00:00
|
|
|
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares);
|
2020-04-18 21:20:59 +00:00
|
|
|
};
|
2020-02-22 16:24:54 +00:00
|
|
|
});
|
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
module Mixed = {
|
2020-02-23 18:34:34 +00:00
|
|
|
type t = DistTypes.mixedShape;
|
2020-06-27 04:29:21 +00:00
|
|
|
let make = (~continuous, ~discrete): t => {continuous, discrete};
|
2020-02-22 19:21:04 +00:00
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let totalLength = (t: t): int => {
|
2020-06-27 04:29:21 +00:00
|
|
|
let continuousLength =
|
|
|
|
t.continuous |> Continuous.getShape |> XYShape.T.length;
|
2020-06-26 06:38:14 +00:00
|
|
|
let discreteLength = t.discrete |> Discrete.getShape |> XYShape.T.length;
|
|
|
|
|
|
|
|
continuousLength + discreteLength;
|
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let scaleBy = (~scale=1.0, {discrete, continuous}: t): t => {
|
|
|
|
let scaledDiscrete = Discrete.scaleBy(~scale, discrete);
|
|
|
|
let scaledContinuous = Continuous.scaleBy(~scale, continuous);
|
|
|
|
make(~discrete=scaledDiscrete, ~continuous=scaledContinuous);
|
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let toContinuous = ({continuous}: t) => Some(continuous);
|
|
|
|
let toDiscrete = ({discrete}: t) => Some(discrete);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let combine = (~knownIntegralSumsFn, fn, t1: t, t2: t) => {
|
|
|
|
let reducedDiscrete =
|
|
|
|
[|t1, t2|]
|
|
|
|
|> E.A.fmap(toDiscrete)
|
|
|
|
|> E.A.O.concatSomes
|
|
|
|
|> Discrete.reduce(~knownIntegralSumsFn, fn);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let reducedContinuous =
|
|
|
|
[|t1, t2|]
|
|
|
|
|> E.A.fmap(toContinuous)
|
|
|
|
|> E.A.O.concatSomes
|
|
|
|
|> Continuous.reduce(~knownIntegralSumsFn, fn);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
make(~discrete=reducedDiscrete, ~continuous=reducedContinuous);
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
2020-02-23 18:34:34 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
module T =
|
|
|
|
Dist({
|
2020-02-23 13:27:52 +00:00
|
|
|
type t = DistTypes.mixedShape;
|
|
|
|
type integral = DistTypes.continuousShape;
|
2020-02-25 19:55:01 +00:00
|
|
|
let minX = ({continuous, discrete}: t) => {
|
2020-02-22 16:24:54 +00:00
|
|
|
min(Continuous.T.minX(continuous), Discrete.T.minX(discrete));
|
2020-02-25 19:55:01 +00:00
|
|
|
};
|
2020-02-22 16:24:54 +00:00
|
|
|
let maxX = ({continuous, discrete}: t) =>
|
|
|
|
max(Continuous.T.maxX(continuous), Discrete.T.maxX(discrete));
|
2020-02-23 13:27:52 +00:00
|
|
|
let toShape = (t: t): DistTypes.shape => Mixed(t);
|
2020-06-27 04:29:21 +00:00
|
|
|
|
|
|
|
let toContinuous = toContinuous;
|
|
|
|
let toDiscrete = toDiscrete;
|
|
|
|
|
|
|
|
let truncate =
|
|
|
|
(
|
|
|
|
leftCutoff: option(float),
|
|
|
|
rightCutoff: option(float),
|
|
|
|
{discrete, continuous}: t,
|
|
|
|
) => {
|
|
|
|
let truncatedContinuous = Continuous.T.truncate(leftCutoff, rightCutoff, continuous);
|
|
|
|
let truncatedDiscrete = Discrete.T.truncate(leftCutoff, rightCutoff, discrete);
|
|
|
|
|
|
|
|
make(~discrete=truncatedDiscrete, ~continuous=truncatedContinuous);
|
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let normalize = (t: t): t => {
|
2020-06-27 04:29:21 +00:00
|
|
|
let continuousIntegralSum =
|
|
|
|
Continuous.T.Integral.sum(~cache=None, t.continuous);
|
|
|
|
let discreteIntegralSum =
|
|
|
|
Discrete.T.Integral.sum(~cache=None, t.discrete);
|
2020-06-26 06:38:14 +00:00
|
|
|
let totalIntegralSum = continuousIntegralSum +. discreteIntegralSum;
|
|
|
|
|
|
|
|
let newContinuousSum = continuousIntegralSum /. totalIntegralSum;
|
|
|
|
let newDiscreteSum = discreteIntegralSum /. totalIntegralSum;
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let normalizedContinuous =
|
|
|
|
t.continuous
|
|
|
|
|> Continuous.scaleBy(~scale=1. /. newContinuousSum)
|
|
|
|
|> Continuous.updateKnownIntegralSum(Some(newContinuousSum));
|
|
|
|
let normalizedDiscrete =
|
|
|
|
t.discrete
|
|
|
|
|> Discrete.scaleBy(~scale=1. /. newDiscreteSum)
|
|
|
|
|> Discrete.updateKnownIntegralSum(Some(newDiscreteSum));
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
make(~continuous=normalizedContinuous, ~discrete=normalizedDiscrete);
|
2020-02-22 16:24:54 +00:00
|
|
|
};
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let xToY = (x, t: t) => {
|
|
|
|
// This evaluates the mixedShape at x, interpolating if necessary.
|
|
|
|
// Note that we normalize entire mixedShape first.
|
|
|
|
let {continuous, discrete}: t = normalize(t);
|
|
|
|
let c = Continuous.T.xToY(x, continuous);
|
|
|
|
let d = Discrete.T.xToY(x, discrete);
|
|
|
|
DistTypes.MixedPoint.add(c, d); // "add" here just combines the two values into a single MixedPoint.
|
|
|
|
};
|
|
|
|
|
|
|
|
let toDiscreteProbabilityMassFraction = ({discrete, continuous}: t) => {
|
2020-06-27 04:29:21 +00:00
|
|
|
let discreteIntegralSum =
|
|
|
|
Discrete.T.Integral.sum(~cache=None, discrete);
|
|
|
|
let continuousIntegralSum =
|
|
|
|
Continuous.T.Integral.sum(~cache=None, continuous);
|
2020-06-26 06:38:14 +00:00
|
|
|
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
|
|
|
|
|
|
|
|
discreteIntegralSum /. totalIntegralSum;
|
|
|
|
};
|
|
|
|
|
|
|
|
let downsample = (~cache=None, count, {discrete, continuous}: t): t => {
|
|
|
|
// We will need to distribute the new xs fairly between the discrete and continuous shapes.
|
|
|
|
// The easiest way to do this is to simply go by the previous probability masses.
|
|
|
|
|
|
|
|
// The cache really isn't helpful here, because we would need two separate caches
|
2020-06-27 04:29:21 +00:00
|
|
|
let discreteIntegralSum =
|
|
|
|
Discrete.T.Integral.sum(~cache=None, discrete);
|
|
|
|
let continuousIntegralSum =
|
|
|
|
Continuous.T.Integral.sum(~cache=None, continuous);
|
2020-06-26 06:38:14 +00:00
|
|
|
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
|
|
|
|
|
2020-06-27 05:37:24 +00:00
|
|
|
// TODO: figure out what to do when the totalIntegralSum is zero.
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let downsampledDiscrete =
|
|
|
|
Discrete.T.downsample(
|
2020-06-27 04:29:21 +00:00
|
|
|
int_of_float(
|
|
|
|
float_of_int(count) *. (discreteIntegralSum /. totalIntegralSum),
|
|
|
|
),
|
2020-06-26 06:38:14 +00:00
|
|
|
discrete,
|
|
|
|
);
|
|
|
|
|
|
|
|
let downsampledContinuous =
|
|
|
|
Continuous.T.downsample(
|
|
|
|
int_of_float(
|
2020-06-27 04:29:21 +00:00
|
|
|
float_of_int(count)
|
|
|
|
*. (continuousIntegralSum /. totalIntegralSum),
|
2020-03-15 00:30:18 +00:00
|
|
|
),
|
2020-06-26 06:38:14 +00:00
|
|
|
continuous,
|
|
|
|
);
|
|
|
|
|
|
|
|
{discrete: downsampledDiscrete, continuous: downsampledContinuous};
|
2020-03-15 00:30:18 +00:00
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let normalizedToContinuous = (t: t) => Some(normalize(t).continuous);
|
2020-02-22 23:55:43 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let normalizedToDiscrete = ({discrete} as t: t) =>
|
|
|
|
Some(normalize(t).discrete);
|
2020-02-21 23:42:15 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let integral = (~cache, {continuous, discrete}: t) => {
|
2020-02-25 12:28:26 +00:00
|
|
|
switch (cache) {
|
|
|
|
| Some(cache) => cache
|
2020-06-27 04:29:21 +00:00
|
|
|
| None =>
|
2020-06-26 06:38:14 +00:00
|
|
|
// note: if the underlying shapes aren't normalized, then these integrals won't be either!
|
2020-06-27 04:29:21 +00:00
|
|
|
let continuousIntegral =
|
|
|
|
Continuous.T.Integral.get(~cache=None, continuous);
|
|
|
|
let discreteIntegral =
|
|
|
|
Discrete.T.Integral.get(~cache=None, discrete);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
Continuous.make(
|
|
|
|
`Linear,
|
|
|
|
XYShape.Combine.combineLinear(
|
|
|
|
~fn=(+.),
|
|
|
|
Continuous.getShape(continuousIntegral),
|
|
|
|
Continuous.getShape(discreteIntegral),
|
|
|
|
),
|
|
|
|
None,
|
|
|
|
);
|
2020-02-25 12:28:26 +00:00
|
|
|
};
|
2020-02-22 10:10:10 +00:00
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
|
2020-02-24 21:01:29 +00:00
|
|
|
let integralEndY = (~cache, t: t) => {
|
|
|
|
integral(~cache, t) |> Continuous.lastY;
|
2020-02-22 10:10:10 +00:00
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
|
2020-03-14 18:33:39 +00:00
|
|
|
let integralXtoY = (~cache, f, t) => {
|
2020-03-26 23:18:19 +00:00
|
|
|
t
|
|
|
|
|> integral(~cache)
|
|
|
|
|> Continuous.getShape
|
2020-03-28 14:17:47 +00:00
|
|
|
|> XYShape.XtoY.linear(f);
|
2020-03-14 18:33:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let integralYtoX = (~cache, f, t) => {
|
2020-03-26 23:18:19 +00:00
|
|
|
t
|
|
|
|
|> integral(~cache)
|
|
|
|
|> Continuous.getShape
|
2020-03-28 14:17:47 +00:00
|
|
|
|> XYShape.YtoX.linear(f);
|
2020-02-22 16:24:54 +00:00
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
// This pipes all ys (continuous and discrete) through fn.
|
|
|
|
// If mapY is a linear operation, we might be able to update the knownIntegralSums as well;
|
|
|
|
// if not, they'll be set to None.
|
2020-06-27 04:29:21 +00:00
|
|
|
let mapY =
|
|
|
|
(
|
|
|
|
~knownIntegralSumFn=previousIntegralSum => None,
|
|
|
|
fn,
|
|
|
|
{discrete, continuous}: t,
|
|
|
|
)
|
|
|
|
: t => {
|
2020-06-26 06:38:14 +00:00
|
|
|
let u = E.O.bind(_, knownIntegralSumFn);
|
|
|
|
|
|
|
|
let yMappedDiscrete =
|
2020-06-27 04:29:21 +00:00
|
|
|
discrete
|
|
|
|
|> Discrete.T.mapY(fn)
|
|
|
|
|> Discrete.updateKnownIntegralSum(u(discrete.knownIntegralSum));
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let yMappedContinuous =
|
2020-06-27 04:29:21 +00:00
|
|
|
continuous
|
|
|
|
|> Continuous.T.mapY(fn)
|
|
|
|
|> Continuous.updateKnownIntegralSum(
|
|
|
|
u(continuous.knownIntegralSum),
|
|
|
|
);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
{
|
2020-06-26 06:38:14 +00:00
|
|
|
discrete: yMappedDiscrete,
|
2020-03-28 14:17:47 +00:00
|
|
|
continuous: Continuous.T.mapY(fn, continuous),
|
2020-02-22 16:24:54 +00:00
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
};
|
2020-04-18 21:20:59 +00:00
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let mean = ({discrete, continuous}: t): float => {
|
|
|
|
let discreteMean = Discrete.T.mean(discrete);
|
|
|
|
let continuousMean = Continuous.T.mean(continuous);
|
|
|
|
|
|
|
|
// the combined mean is the weighted sum of the two:
|
2020-06-27 04:29:21 +00:00
|
|
|
let discreteIntegralSum =
|
|
|
|
Discrete.T.Integral.sum(~cache=None, discrete);
|
|
|
|
let continuousIntegralSum =
|
|
|
|
Continuous.T.Integral.sum(~cache=None, continuous);
|
2020-06-26 06:38:14 +00:00
|
|
|
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
(
|
|
|
|
discreteMean
|
|
|
|
*. discreteIntegralSum
|
|
|
|
+. continuousMean
|
|
|
|
*. continuousIntegralSum
|
|
|
|
)
|
|
|
|
/. totalIntegralSum;
|
2020-04-18 21:20:59 +00:00
|
|
|
};
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let variance = ({discrete, continuous} as t: t): float => {
|
|
|
|
// the combined mean is the weighted sum of the two:
|
2020-06-27 04:29:21 +00:00
|
|
|
let discreteIntegralSum =
|
|
|
|
Discrete.T.Integral.sum(~cache=None, discrete);
|
|
|
|
let continuousIntegralSum =
|
|
|
|
Continuous.T.Integral.sum(~cache=None, continuous);
|
2020-06-26 06:38:14 +00:00
|
|
|
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
|
|
|
|
|
|
|
|
let getMeanOfSquares = ({discrete, continuous} as t: t) => {
|
2020-06-27 04:29:21 +00:00
|
|
|
let discreteMean =
|
|
|
|
discrete
|
|
|
|
|> Discrete.shapeMap(XYShape.Analysis.squareXYShape)
|
|
|
|
|> Discrete.T.mean;
|
|
|
|
let continuousMean =
|
|
|
|
continuous |> XYShape.Analysis.getMeanOfSquaresContinuousShape;
|
|
|
|
(
|
|
|
|
discreteMean
|
|
|
|
*. discreteIntegralSum
|
|
|
|
+. continuousMean
|
|
|
|
*. continuousIntegralSum
|
|
|
|
)
|
|
|
|
/. totalIntegralSum;
|
2020-04-18 21:20:59 +00:00
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
switch (discreteIntegralSum /. totalIntegralSum) {
|
|
|
|
| 1.0 => Discrete.T.variance(discrete)
|
|
|
|
| 0.0 => Continuous.T.variance(continuous)
|
2020-06-27 04:29:21 +00:00
|
|
|
| _ =>
|
|
|
|
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares)
|
2020-04-18 21:20:59 +00:00
|
|
|
};
|
|
|
|
};
|
2020-02-22 16:24:54 +00:00
|
|
|
});
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-06-27 05:37:24 +00:00
|
|
|
let convolve = (~downsample=false, fn: (float, float) => float, t1: t, t2: t): t => {
|
2020-06-26 06:38:14 +00:00
|
|
|
// Discrete convolution can cause a huge increase in the number of samples,
|
|
|
|
// so we'll first downsample.
|
|
|
|
|
|
|
|
// An alternative (to be explored in the future) may be to first perform the full convolution and then to downsample the result;
|
|
|
|
// to use non-uniform fast Fourier transforms (for addition only), add web workers or gpu.js, etc. ...
|
|
|
|
|
|
|
|
let downsampleIfTooLarge = (t: t) => {
|
|
|
|
let sqtl = sqrt(float_of_int(totalLength(t)));
|
2020-06-27 05:37:24 +00:00
|
|
|
sqtl > 10. && downsample ? T.downsample(int_of_float(sqtl), t) : t;
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let t1d = downsampleIfTooLarge(t1);
|
|
|
|
let t2d = downsampleIfTooLarge(t2);
|
|
|
|
|
|
|
|
// continuous (*) continuous => continuous, but also
|
|
|
|
// discrete (*) continuous => continuous (and vice versa). We have to take care of all combos and then combine them:
|
2020-06-27 04:29:21 +00:00
|
|
|
let ccConvResult =
|
2020-06-27 05:37:24 +00:00
|
|
|
Continuous.convolve(~downsample=false, fn, t1d.continuous, t2d.continuous);
|
2020-06-27 04:29:21 +00:00
|
|
|
let dcConvResult =
|
2020-06-27 05:37:24 +00:00
|
|
|
Continuous.convolveWithDiscrete(~downsample=false, fn, t2d.continuous, t1d.discrete);
|
2020-06-27 04:29:21 +00:00
|
|
|
let cdConvResult =
|
2020-06-27 05:37:24 +00:00
|
|
|
Continuous.convolveWithDiscrete(~downsample=false, fn, t1d.continuous, t2d.discrete);
|
2020-06-27 04:29:21 +00:00
|
|
|
let continuousConvResult =
|
|
|
|
Continuous.reduce((+.), [|ccConvResult, dcConvResult, cdConvResult|]);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
// ... finally, discrete (*) discrete => discrete, obviously:
|
2020-06-27 04:29:21 +00:00
|
|
|
let discreteConvResult =
|
|
|
|
Discrete.convolve(fn, t1d.discrete, t2d.discrete);
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
{discrete: discreteConvResult, continuous: continuousConvResult};
|
2020-06-27 04:29:21 +00:00
|
|
|
};
|
2020-02-22 16:24:54 +00:00
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
module Shape = {
|
2020-06-26 06:38:14 +00:00
|
|
|
type t = DistTypes.shape;
|
|
|
|
let mapToAll = ((fn1, fn2, fn3), t: t) =>
|
|
|
|
switch (t) {
|
|
|
|
| Mixed(m) => fn1(m)
|
|
|
|
| Discrete(m) => fn2(m)
|
|
|
|
| Continuous(m) => fn3(m)
|
|
|
|
};
|
|
|
|
|
|
|
|
let fmap = ((fn1, fn2, fn3), t: t): t =>
|
|
|
|
switch (t) {
|
|
|
|
| Mixed(m) => Mixed(fn1(m))
|
|
|
|
| Discrete(m) => Discrete(fn2(m))
|
|
|
|
| Continuous(m) => Continuous(fn3(m))
|
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let toMixed =
|
|
|
|
mapToAll((
|
|
|
|
m => m,
|
|
|
|
d => Mixed.make(~discrete=d, ~continuous=Continuous.empty),
|
|
|
|
c => Mixed.make(~discrete=Discrete.empty, ~continuous=c),
|
|
|
|
));
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let convolve = (fn, t1: t, t2: t): t => {
|
2020-06-27 05:37:24 +00:00
|
|
|
switch ((t1, t2)) {
|
|
|
|
| (Continuous(m1), Continuous(m2)) => DistTypes.Continuous(Continuous.convolve(~downsample=true, fn, m1, m2))
|
|
|
|
| (Discrete(m1), Discrete(m2)) => DistTypes.Discrete(Discrete.convolve(fn, m1, m2))
|
|
|
|
| (m1, m2) => {
|
|
|
|
DistTypes.Mixed(Mixed.convolve(~downsample=true, fn, toMixed(m1), toMixed(m2)))
|
|
|
|
}
|
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let combine = (~knownIntegralSumsFn=(_, _) => None, fn, t1: t, t2: t) =>
|
|
|
|
switch ((t1, t2)) {
|
|
|
|
| (Continuous(m1), Continuous(m2)) => DistTypes.Continuous(Continuous.combine(~knownIntegralSumsFn, fn, m1, m2))
|
|
|
|
| (Discrete(m1), Discrete(m2)) => DistTypes.Discrete(Discrete.combine(~knownIntegralSumsFn, fn, m1, m2))
|
|
|
|
| (m1, m2) => {
|
|
|
|
DistTypes.Mixed(Mixed.combine(~knownIntegralSumsFn, fn, toMixed(m1), toMixed(m2)))
|
|
|
|
}
|
|
|
|
};
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
module T =
|
|
|
|
Dist({
|
2020-02-23 13:27:52 +00:00
|
|
|
type t = DistTypes.shape;
|
|
|
|
type integral = DistTypes.continuousShape;
|
2020-02-22 12:51:25 +00:00
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let xToY = (f: float) =>
|
2020-03-28 22:51:53 +00:00
|
|
|
mapToAll((
|
|
|
|
Mixed.T.xToY(f),
|
|
|
|
Discrete.T.xToY(f),
|
|
|
|
Continuous.T.xToY(f),
|
|
|
|
));
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
let toShape = (t: t) => t;
|
2020-06-26 06:38:14 +00:00
|
|
|
|
|
|
|
let toContinuous = t => None;
|
|
|
|
let toDiscrete = t => None;
|
2020-06-27 04:29:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
let downsample = (~cache=None, i, t) =>
|
|
|
|
fmap(
|
|
|
|
(
|
|
|
|
Mixed.T.downsample(i),
|
|
|
|
Discrete.T.downsample(i),
|
|
|
|
Continuous.T.downsample(i),
|
|
|
|
),
|
|
|
|
t,
|
|
|
|
);
|
|
|
|
|
|
|
|
let truncate = (leftCutoff, rightCutoff, t): t =>
|
|
|
|
fmap(
|
|
|
|
(
|
|
|
|
Mixed.T.truncate(leftCutoff, rightCutoff),
|
|
|
|
Discrete.T.truncate(leftCutoff, rightCutoff),
|
|
|
|
Continuous.T.truncate(leftCutoff, rightCutoff),
|
|
|
|
),
|
|
|
|
t,
|
|
|
|
);
|
|
|
|
|
|
|
|
let toDiscreteProbabilityMassFraction = t => 0.0;
|
|
|
|
let normalize =
|
|
|
|
fmap((Mixed.T.normalize, Discrete.T.normalize, Continuous.T.normalize));
|
2020-03-28 22:51:53 +00:00
|
|
|
let toContinuous =
|
|
|
|
mapToAll((
|
|
|
|
Mixed.T.toContinuous,
|
|
|
|
Discrete.T.toContinuous,
|
|
|
|
Continuous.T.toContinuous,
|
|
|
|
));
|
|
|
|
let toDiscrete =
|
|
|
|
mapToAll((
|
|
|
|
Mixed.T.toDiscrete,
|
|
|
|
Discrete.T.toDiscrete,
|
|
|
|
Continuous.T.toDiscrete,
|
|
|
|
));
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let toDiscreteProbabilityMassFraction =
|
2020-03-28 22:51:53 +00:00
|
|
|
mapToAll((
|
2020-06-26 06:38:14 +00:00
|
|
|
Mixed.T.toDiscreteProbabilityMassFraction,
|
|
|
|
Discrete.T.toDiscreteProbabilityMassFraction,
|
|
|
|
Continuous.T.toDiscreteProbabilityMassFraction,
|
2020-03-28 22:51:53 +00:00
|
|
|
));
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let normalizedToDiscrete =
|
2020-03-28 22:51:53 +00:00
|
|
|
mapToAll((
|
2020-06-26 06:38:14 +00:00
|
|
|
Mixed.T.normalizedToDiscrete,
|
|
|
|
Discrete.T.normalizedToDiscrete,
|
|
|
|
Continuous.T.normalizedToDiscrete,
|
2020-03-28 22:51:53 +00:00
|
|
|
));
|
2020-06-26 06:38:14 +00:00
|
|
|
let normalizedToContinuous =
|
2020-03-28 22:51:53 +00:00
|
|
|
mapToAll((
|
2020-06-26 06:38:14 +00:00
|
|
|
Mixed.T.normalizedToContinuous,
|
|
|
|
Discrete.T.normalizedToContinuous,
|
|
|
|
Continuous.T.normalizedToContinuous,
|
2020-03-28 22:51:53 +00:00
|
|
|
));
|
|
|
|
let minX = mapToAll((Mixed.T.minX, Discrete.T.minX, Continuous.T.minX));
|
|
|
|
let integral = (~cache) => {
|
|
|
|
mapToAll((
|
|
|
|
Mixed.T.Integral.get(~cache),
|
|
|
|
Discrete.T.Integral.get(~cache),
|
|
|
|
Continuous.T.Integral.get(~cache),
|
|
|
|
));
|
2020-02-22 23:55:43 +00:00
|
|
|
};
|
2020-03-28 22:51:53 +00:00
|
|
|
let integralEndY = (~cache) =>
|
|
|
|
mapToAll((
|
|
|
|
Mixed.T.Integral.sum(~cache),
|
|
|
|
Discrete.T.Integral.sum(~cache),
|
|
|
|
Continuous.T.Integral.sum(~cache),
|
|
|
|
));
|
|
|
|
let integralXtoY = (~cache, f) => {
|
|
|
|
mapToAll((
|
|
|
|
Mixed.T.Integral.xToY(~cache, f),
|
|
|
|
Discrete.T.Integral.xToY(~cache, f),
|
|
|
|
Continuous.T.Integral.xToY(~cache, f),
|
|
|
|
));
|
2020-02-22 16:24:54 +00:00
|
|
|
};
|
2020-03-28 22:51:53 +00:00
|
|
|
let integralYtoX = (~cache, f) => {
|
|
|
|
mapToAll((
|
|
|
|
Mixed.T.Integral.yToX(~cache, f),
|
|
|
|
Discrete.T.Integral.yToX(~cache, f),
|
|
|
|
Continuous.T.Integral.yToX(~cache, f),
|
|
|
|
));
|
2020-03-14 18:33:39 +00:00
|
|
|
};
|
2020-03-28 22:51:53 +00:00
|
|
|
let maxX = mapToAll((Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX));
|
2020-06-27 04:29:21 +00:00
|
|
|
let mapY = (~knownIntegralSumFn=previousIntegralSum => None, fn) =>
|
2020-03-28 22:51:53 +00:00
|
|
|
fmap((
|
2020-06-26 06:38:14 +00:00
|
|
|
Mixed.T.mapY(~knownIntegralSumFn, fn),
|
|
|
|
Discrete.T.mapY(~knownIntegralSumFn, fn),
|
|
|
|
Continuous.T.mapY(~knownIntegralSumFn, fn),
|
2020-03-28 22:51:53 +00:00
|
|
|
));
|
2020-04-18 21:20:59 +00:00
|
|
|
|
2020-04-19 20:04:50 +00:00
|
|
|
let mean = (t: t): float =>
|
2020-04-18 21:27:24 +00:00
|
|
|
switch (t) {
|
2020-04-19 20:04:50 +00:00
|
|
|
| Mixed(m) => Mixed.T.mean(m)
|
|
|
|
| Discrete(m) => Discrete.T.mean(m)
|
|
|
|
| Continuous(m) => Continuous.T.mean(m)
|
2020-04-18 21:27:24 +00:00
|
|
|
};
|
|
|
|
|
2020-04-19 20:04:50 +00:00
|
|
|
let variance = (t: t): float =>
|
2020-04-18 21:27:24 +00:00
|
|
|
switch (t) {
|
2020-04-19 20:04:50 +00:00
|
|
|
| Mixed(m) => Mixed.T.variance(m)
|
|
|
|
| Discrete(m) => Discrete.T.variance(m)
|
|
|
|
| Continuous(m) => Continuous.T.variance(m)
|
2020-04-18 21:27:24 +00:00
|
|
|
};
|
2020-02-22 16:24:54 +00:00
|
|
|
});
|
|
|
|
};
|
2020-02-21 23:42:15 +00:00
|
|
|
|
2020-02-23 12:49:33 +00:00
|
|
|
module DistPlus = {
|
2020-02-23 13:27:52 +00:00
|
|
|
open DistTypes;
|
2020-02-23 18:34:34 +00:00
|
|
|
|
|
|
|
type t = DistTypes.distPlus;
|
|
|
|
|
2020-02-24 21:01:29 +00:00
|
|
|
let shapeIntegral = shape => Shape.T.Integral.get(~cache=None, shape);
|
2020-02-22 20:36:22 +00:00
|
|
|
let make =
|
|
|
|
(
|
|
|
|
~shape,
|
|
|
|
~guesstimatorString,
|
|
|
|
~domain=Complete,
|
|
|
|
~unit=UnspecifiedDistribution,
|
|
|
|
(),
|
|
|
|
)
|
2020-02-23 18:34:34 +00:00
|
|
|
: t => {
|
2020-02-24 21:01:29 +00:00
|
|
|
let integral = shapeIntegral(shape);
|
2020-02-22 20:36:22 +00:00
|
|
|
{shape, domain, integralCache: integral, unit, guesstimatorString};
|
|
|
|
};
|
2020-02-23 18:34:34 +00:00
|
|
|
|
2020-02-22 20:36:22 +00:00
|
|
|
let update =
|
|
|
|
(
|
|
|
|
~shape=?,
|
|
|
|
~integralCache=?,
|
|
|
|
~domain=?,
|
|
|
|
~unit=?,
|
|
|
|
~guesstimatorString=?,
|
2020-02-23 18:34:34 +00:00
|
|
|
t: t,
|
2020-02-22 20:36:22 +00:00
|
|
|
) => {
|
|
|
|
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),
|
|
|
|
guesstimatorString: E.O.default(t.guesstimatorString, guesstimatorString),
|
|
|
|
};
|
|
|
|
|
2020-02-24 21:01:29 +00:00
|
|
|
let updateShape = (shape, t) => {
|
|
|
|
let integralCache = shapeIntegral(shape);
|
|
|
|
update(~shape, ~integralCache, t);
|
|
|
|
};
|
|
|
|
|
2020-02-23 18:34:34 +00:00
|
|
|
let domainIncludedProbabilityMass = (t: t) =>
|
|
|
|
Domain.includedProbabilityMass(t.domain);
|
|
|
|
|
|
|
|
let domainIncludedProbabilityMassAdjustment = (t: t, f) =>
|
|
|
|
f *. Domain.includedProbabilityMass(t.domain);
|
|
|
|
|
|
|
|
let toShape = ({shape, _}: t) => shape;
|
|
|
|
|
|
|
|
let shapeFn = (fn, {shape}: t) => fn(shape);
|
|
|
|
|
2020-02-22 16:24:54 +00:00
|
|
|
module T =
|
|
|
|
Dist({
|
2020-02-23 13:27:52 +00:00
|
|
|
type t = DistTypes.distPlus;
|
|
|
|
type integral = DistTypes.distPlus;
|
2020-02-23 18:34:34 +00:00
|
|
|
let toShape = toShape;
|
2020-02-22 19:21:04 +00:00
|
|
|
let toContinuous = shapeFn(Shape.T.toContinuous);
|
|
|
|
let toDiscrete = shapeFn(Shape.T.toDiscrete);
|
2020-02-23 12:40:18 +00:00
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let normalize = (t: t): t => {
|
2020-06-27 04:29:21 +00:00
|
|
|
let normalizedShape = t |> toShape |> Shape.T.normalize;
|
2020-06-26 06:38:14 +00:00
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
t |> updateShape(normalizedShape);
|
2020-06-26 06:38:14 +00:00
|
|
|
// TODO: also adjust for domainIncludedProbabilityMass here.
|
|
|
|
};
|
|
|
|
|
2020-06-27 04:29:21 +00:00
|
|
|
let truncate = (leftCutoff, rightCutoff, t: t): t => {
|
|
|
|
let truncatedShape = t |> toShape |> Shape.T.truncate(leftCutoff, rightCutoff);
|
|
|
|
|
|
|
|
t |> updateShape(truncatedShape);
|
|
|
|
};
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
// TODO: replace this with
|
|
|
|
let normalizedToContinuous = (t: t) => {
|
2020-02-23 12:40:18 +00:00
|
|
|
t
|
|
|
|
|> toShape
|
2020-06-26 06:38:14 +00:00
|
|
|
|> Shape.T.normalizedToContinuous
|
2020-02-23 12:40:18 +00:00
|
|
|
|> E.O.fmap(
|
2020-03-28 14:17:47 +00:00
|
|
|
Continuous.T.mapY(domainIncludedProbabilityMassAdjustment(t)),
|
2020-02-23 12:40:18 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let normalizedToDiscrete = (t: t) => {
|
2020-02-23 12:40:18 +00:00
|
|
|
t
|
|
|
|
|> toShape
|
2020-06-26 06:38:14 +00:00
|
|
|
|> Shape.T.normalizedToDiscrete
|
2020-02-23 12:40:18 +00:00
|
|
|
|> E.O.fmap(
|
2020-03-28 14:17:47 +00:00
|
|
|
Discrete.T.mapY(domainIncludedProbabilityMassAdjustment(t)),
|
2020-02-23 12:40:18 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
let xToY = (f, t: t) =>
|
|
|
|
t
|
|
|
|
|> toShape
|
|
|
|
|> Shape.T.xToY(f)
|
|
|
|
|> MixedPoint.fmap(domainIncludedProbabilityMassAdjustment(t));
|
|
|
|
|
2020-02-22 19:21:04 +00:00
|
|
|
let minX = shapeFn(Shape.T.minX);
|
|
|
|
let maxX = shapeFn(Shape.T.maxX);
|
2020-06-26 06:38:14 +00:00
|
|
|
let toDiscreteProbabilityMassFraction =
|
|
|
|
shapeFn(Shape.T.toDiscreteProbabilityMassFraction);
|
2020-02-23 12:40:18 +00:00
|
|
|
|
2020-03-18 21:46:43 +00:00
|
|
|
// This bit is kind of awkward, could probably use rethinking.
|
2020-02-24 22:04:39 +00:00
|
|
|
let integral = (~cache, t: t) =>
|
2020-02-24 21:01:29 +00:00
|
|
|
updateShape(Continuous(t.integralCache), t);
|
|
|
|
|
2020-06-26 06:38:14 +00:00
|
|
|
let downsample = (~cache=None, i, t): t =>
|
|
|
|
updateShape(t |> toShape |> Shape.T.downsample(i), t);
|
2020-02-24 21:01:29 +00:00
|
|
|
// todo: adjust for limit, maybe?
|
2020-06-27 04:29:21 +00:00
|
|
|
let mapY =
|
|
|
|
(
|
|
|
|
~knownIntegralSumFn=previousIntegralSum => None,
|
|
|
|
fn,
|
|
|
|
{shape, _} as t: t,
|
|
|
|
)
|
|
|
|
: t =>
|
2020-06-26 06:38:14 +00:00
|
|
|
Shape.T.mapY(~knownIntegralSumFn, fn, shape) |> updateShape(_, t);
|
2020-02-23 12:40:18 +00:00
|
|
|
|
2020-02-24 21:01:29 +00:00
|
|
|
let integralEndY = (~cache as _, t: t) =>
|
2020-02-22 19:21:04 +00:00
|
|
|
Shape.T.Integral.sum(~cache=Some(t.integralCache), toShape(t));
|
2020-02-23 12:40:18 +00:00
|
|
|
|
2020-02-23 18:34:34 +00:00
|
|
|
// TODO: Fix this below, obviously. Adjust for limits
|
2020-02-22 19:21:04 +00:00
|
|
|
let integralXtoY = (~cache as _, f, t: t) => {
|
2020-02-25 19:27:30 +00:00
|
|
|
Shape.T.Integral.xToY(~cache=Some(t.integralCache), f, toShape(t))
|
|
|
|
|> domainIncludedProbabilityMassAdjustment(t);
|
2020-02-22 16:24:54 +00:00
|
|
|
};
|
2020-03-14 18:33:39 +00:00
|
|
|
|
|
|
|
// TODO: This part is broken when there is a limit, if this is supposed to be taken into account.
|
|
|
|
let integralYtoX = (~cache as _, f, t: t) => {
|
|
|
|
Shape.T.Integral.yToX(~cache=Some(t.integralCache), f, toShape(t));
|
|
|
|
};
|
2020-04-19 20:04:50 +00:00
|
|
|
let mean = (t: t) => Shape.T.mean(t.shape);
|
|
|
|
let variance = (t: t) => Shape.T.variance(t.shape);
|
2020-02-22 16:24:54 +00:00
|
|
|
});
|
2020-02-23 18:34:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
module DistPlusTime = {
|
|
|
|
open DistTypes;
|
|
|
|
|
|
|
|
type t = DistTypes.distPlus;
|
|
|
|
|
|
|
|
let unitToJson = ({unit}: t) => unit |> DistTypes.DistributionUnit.toJson;
|
|
|
|
|
|
|
|
let timeVector = ({unit}: t) =>
|
|
|
|
switch (unit) {
|
|
|
|
| TimeDistribution(timeVector) => Some(timeVector)
|
|
|
|
| UnspecifiedDistribution => None
|
|
|
|
};
|
|
|
|
|
|
|
|
let timeInVectorToX = (f: TimeTypes.timeInVector, t: t) => {
|
|
|
|
let timeVector = t |> timeVector;
|
|
|
|
timeVector |> E.O.fmap(TimeTypes.RelativeTimePoint.toXValue(_, f));
|
|
|
|
};
|
|
|
|
|
|
|
|
let xToY = (f: TimeTypes.timeInVector, t: t) => {
|
|
|
|
timeInVectorToX(f, t) |> E.O.fmap(DistPlus.T.xToY(_, t));
|
|
|
|
};
|
|
|
|
|
|
|
|
module Integral = {
|
|
|
|
include DistPlus.T.Integral;
|
2020-02-25 19:27:30 +00:00
|
|
|
let xToY = (f: TimeTypes.timeInVector, t: t) => {
|
2020-02-23 18:34:34 +00:00
|
|
|
timeInVectorToX(f, t)
|
|
|
|
|> E.O.fmap(x => DistPlus.T.Integral.xToY(~cache=None, x, t));
|
|
|
|
};
|
|
|
|
};
|
2020-06-13 06:30:51 +00:00
|
|
|
};
|