2020-07-08 16:00:13 +00:00
|
|
|
open Distributions;
|
|
|
|
|
|
|
|
type t = DistTypes.continuousShape;
|
|
|
|
let getShape = (t: t) => t.xyShape;
|
|
|
|
let interpolation = (t: t) => t.interpolation;
|
2020-07-17 01:14:42 +00:00
|
|
|
let make = (interpolation, xyShape, integralSumCache, integralCache): t => {
|
2020-07-08 16:00:13 +00:00
|
|
|
xyShape,
|
|
|
|
interpolation,
|
2020-07-17 01:14:42 +00:00
|
|
|
integralSumCache,
|
|
|
|
integralCache,
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
2020-07-17 01:14:42 +00:00
|
|
|
let shapeMap = (fn, {xyShape, interpolation, integralSumCache, integralCache}: t): t => {
|
2020-07-08 16:00:13 +00:00
|
|
|
xyShape: fn(xyShape),
|
|
|
|
interpolation,
|
2020-07-17 01:14:42 +00:00
|
|
|
integralSumCache,
|
|
|
|
integralCache,
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
let lastY = (t: t) => t |> getShape |> XYShape.T.lastY;
|
|
|
|
let oShapeMap =
|
2020-07-17 01:14:42 +00:00
|
|
|
(fn, {xyShape, interpolation, integralSumCache, integralCache}: t)
|
2020-07-08 16:00:13 +00:00
|
|
|
: option(DistTypes.continuousShape) =>
|
2020-07-17 01:14:42 +00:00
|
|
|
fn(xyShape) |> E.O.fmap(make(interpolation, _, integralSumCache, integralCache));
|
2020-07-08 16:00:13 +00:00
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
let emptyIntegral: DistTypes.continuousShape = {
|
|
|
|
xyShape: {xs: [|neg_infinity|], ys: [|0.0|]},
|
|
|
|
interpolation: `Linear,
|
|
|
|
integralSumCache: Some(0.0),
|
|
|
|
integralCache: None,
|
|
|
|
};
|
2020-07-08 16:00:13 +00:00
|
|
|
let empty: DistTypes.continuousShape = {
|
|
|
|
xyShape: XYShape.T.empty,
|
|
|
|
interpolation: `Linear,
|
2020-07-17 01:14:42 +00:00
|
|
|
integralSumCache: Some(0.0),
|
|
|
|
integralCache: Some(emptyIntegral),
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
2020-07-17 01:14:42 +00:00
|
|
|
|
|
|
|
let stepwiseToLinear = (t: t): t =>
|
|
|
|
make(`Linear, XYShape.Range.stepwiseToLinear(t.xyShape), t.integralSumCache, t.integralCache);
|
|
|
|
|
2020-07-08 16:00:13 +00:00
|
|
|
let combinePointwise =
|
|
|
|
(
|
2020-07-17 01:14:42 +00:00
|
|
|
~integralSumCachesFn=(_, _) => None,
|
|
|
|
~integralCachesFn: (t, t) => option(t) =(_, _) => None,
|
|
|
|
~extrapolation=`UseZero,
|
2020-07-08 16:00:13 +00:00
|
|
|
fn: (float, float) => float,
|
|
|
|
t1: DistTypes.continuousShape,
|
|
|
|
t2: DistTypes.continuousShape,
|
|
|
|
)
|
|
|
|
: DistTypes.continuousShape => {
|
|
|
|
// 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 =
|
|
|
|
Common.combineIntegralSums(
|
2020-07-17 01:14:42 +00:00
|
|
|
integralSumCachesFn,
|
|
|
|
t1.integralSumCache,
|
|
|
|
t2.integralSumCache,
|
2020-07-08 16:00:13 +00:00
|
|
|
);
|
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
// TODO: does it ever make sense to pointwise combine the integrals here?
|
|
|
|
// It could be done for pointwise additions, but is that ever needed?
|
|
|
|
|
|
|
|
// If combining stepwise and linear, we must convert the stepwise to linear first,
|
|
|
|
// i.e. add a point at the bottom of each step
|
|
|
|
let (t1, t2) = switch (t1.interpolation, t2.interpolation) {
|
|
|
|
| (`Linear, `Linear) => (t1, t2);
|
|
|
|
| (`Stepwise, `Stepwise) => (t1, t2);
|
|
|
|
| (`Linear, `Stepwise) => (t1, stepwiseToLinear(t2));
|
|
|
|
| (`Stepwise, `Linear) => (stepwiseToLinear(t1), t2);
|
|
|
|
};
|
|
|
|
|
|
|
|
let interpolator = XYShape.XtoY.continuousInterpolator(t1.interpolation, extrapolation);
|
|
|
|
|
2020-07-08 16:00:13 +00:00
|
|
|
make(
|
|
|
|
`Linear,
|
2020-07-13 23:32:45 +00:00
|
|
|
XYShape.PointwiseCombination.combine(
|
|
|
|
(+.),
|
2020-07-17 01:14:42 +00:00
|
|
|
interpolator,
|
|
|
|
interpolator,
|
2020-07-08 16:00:13 +00:00
|
|
|
t1.xyShape,
|
|
|
|
t2.xyShape,
|
|
|
|
),
|
|
|
|
combinedIntegralSum,
|
2020-07-17 01:14:42 +00:00
|
|
|
None,
|
2020-07-08 16:00:13 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
let toLinear = (t: t): option(t) => {
|
|
|
|
switch (t) {
|
2020-07-17 01:14:42 +00:00
|
|
|
| {interpolation: `Stepwise, xyShape, integralSumCache, integralCache} =>
|
2020-07-08 16:00:13 +00:00
|
|
|
xyShape
|
|
|
|
|> XYShape.Range.stepsToContinuous
|
2020-07-17 01:14:42 +00:00
|
|
|
|> E.O.fmap(make(`Linear, _, integralSumCache, integralCache))
|
2020-07-08 16:00:13 +00:00
|
|
|
| {interpolation: `Linear} => Some(t)
|
|
|
|
};
|
|
|
|
};
|
|
|
|
let shapeFn = (fn, t: t) => t |> getShape |> fn;
|
2020-07-17 01:14:42 +00:00
|
|
|
|
|
|
|
let updateIntegralSumCache = (integralSumCache, t: t): t => {
|
|
|
|
...t,
|
|
|
|
integralSumCache,
|
|
|
|
};
|
|
|
|
|
|
|
|
let updateIntegralCache = (integralCache, t: t): t => {
|
2020-07-08 16:00:13 +00:00
|
|
|
...t,
|
2020-07-17 01:14:42 +00:00
|
|
|
integralCache,
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let reduce =
|
|
|
|
(
|
2020-07-17 01:14:42 +00:00
|
|
|
~integralSumCachesFn: (float, float) => option(float)=(_, _) => None,
|
|
|
|
~integralCachesFn: (t, t) => option(t)=(_, _) => None,
|
2020-07-08 16:00:13 +00:00
|
|
|
fn,
|
|
|
|
continuousShapes,
|
|
|
|
) =>
|
|
|
|
continuousShapes
|
2020-07-17 01:14:42 +00:00
|
|
|
|> E.A.fold_left(combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn), empty);
|
2020-07-08 16:00:13 +00:00
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
let mapY = (~integralSumCacheFn=_ => None,
|
|
|
|
~integralCacheFn=_ => None,
|
|
|
|
fn, t: t) => {
|
2020-07-08 16:00:13 +00:00
|
|
|
let yMapFn = shapeMap(XYShape.T.mapY(fn));
|
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
t
|
|
|
|
|> yMapFn
|
|
|
|
|> updateIntegralSumCache(E.O.bind(t.integralSumCache, integralSumCacheFn))
|
|
|
|
|> updateIntegralCache(E.O.bind(t.integralCache, integralCacheFn));
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
let rec scaleBy = (~scale=1.0, t: t): t => {
|
|
|
|
let scaledIntegralSumCache = E.O.bind(t.integralSumCache, v => Some(scale *. v));
|
|
|
|
let scaledIntegralCache = E.O.bind(t.integralCache, v => Some(scaleBy(~scale, v)));
|
|
|
|
|
2020-07-08 16:00:13 +00:00
|
|
|
t
|
|
|
|
|> mapY((r: float) => r *. scale)
|
2020-07-17 01:14:42 +00:00
|
|
|
|> updateIntegralSumCache(scaledIntegralSumCache)
|
|
|
|
|> updateIntegralCache(scaledIntegralCache)
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
module T =
|
|
|
|
Dist({
|
|
|
|
type t = DistTypes.continuousShape;
|
|
|
|
type integral = DistTypes.continuousShape;
|
|
|
|
let minX = shapeFn(XYShape.T.minX);
|
|
|
|
let maxX = shapeFn(XYShape.T.maxX);
|
|
|
|
let mapY = mapY;
|
2020-07-17 01:45:40 +00:00
|
|
|
let updateIntegralCache = updateIntegralCache;
|
2020-07-08 16:00:13 +00:00
|
|
|
let toDiscreteProbabilityMassFraction = _ => 0.0;
|
|
|
|
let toShape = (t: t): DistTypes.shape => Continuous(t);
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
let truncate =
|
|
|
|
(leftCutoff: option(float), rightCutoff: option(float), t: t) => {
|
|
|
|
let lc = E.O.default(neg_infinity, leftCutoff);
|
|
|
|
let rc = E.O.default(infinity, rightCutoff);
|
|
|
|
let truncatedZippedPairs =
|
|
|
|
t
|
|
|
|
|> getShape
|
|
|
|
|> XYShape.T.zip
|
|
|
|
|> XYShape.Zipped.filterByX(x => x >= lc && x <= rc);
|
|
|
|
|
|
|
|
let leftNewPoint =
|
2020-07-17 03:50:12 +00:00
|
|
|
leftCutoff |> E.O.dimap(lc => [|(lc -. epsilon_float, 0.)|], _ => [||]);
|
2020-07-08 16:00:13 +00:00
|
|
|
let rightNewPoint =
|
2020-07-17 03:50:12 +00:00
|
|
|
rightCutoff |> E.O.dimap(rc => [|(rc +. epsilon_float, 0.)|], _ => [||]);
|
2020-07-08 16:00:13 +00:00
|
|
|
|
|
|
|
let truncatedZippedPairsWithNewPoints =
|
|
|
|
E.A.concatMany([|leftNewPoint, truncatedZippedPairs, rightNewPoint|]);
|
|
|
|
let truncatedShape =
|
|
|
|
XYShape.T.fromZippedArray(truncatedZippedPairsWithNewPoints);
|
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
make(`Linear, truncatedShape, None, None);
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: This should work with stepwise plots.
|
2020-07-17 01:14:42 +00:00
|
|
|
let integral = (t) => {
|
|
|
|
if (t |> getShape |> XYShape.T.isEmpty) {
|
|
|
|
make(`Linear, {xs: [|neg_infinity|], ys: [|0.0|]}, None, None);
|
|
|
|
} else {
|
|
|
|
switch (t.integralCache) {
|
2020-07-08 16:00:13 +00:00
|
|
|
| Some(cache) => cache
|
|
|
|
| None =>
|
|
|
|
t
|
|
|
|
|> getShape
|
|
|
|
|> XYShape.Range.integrateWithTriangles
|
|
|
|
|> E.O.toExt("This should not have happened")
|
2020-07-17 01:14:42 +00:00
|
|
|
|> make(`Linear, _, None, None)
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
2020-07-17 01:14:42 +00:00
|
|
|
};
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
let downsample = (length, t): t =>
|
2020-07-08 16:00:13 +00:00
|
|
|
t
|
|
|
|
|> shapeMap(
|
|
|
|
XYShape.XsConversion.proportionByProbabilityMass(
|
|
|
|
length,
|
2020-07-17 01:14:42 +00:00
|
|
|
integral(t).xyShape,
|
2020-07-08 16:00:13 +00:00
|
|
|
),
|
|
|
|
);
|
2020-07-17 01:14:42 +00:00
|
|
|
let integralEndY = (t: t) =>
|
|
|
|
t.integralSumCache |> E.O.default(t |> integral |> lastY);
|
|
|
|
let integralXtoY = (f, t: t) =>
|
|
|
|
t |> integral |> shapeFn(XYShape.XtoY.linear(f));
|
|
|
|
let integralYtoX = (f, t: t) =>
|
|
|
|
t |> integral |> shapeFn(XYShape.YtoX.linear(f));
|
2020-07-08 16:00:13 +00:00
|
|
|
let toContinuous = t => Some(t);
|
|
|
|
let toDiscrete = _ => None;
|
|
|
|
|
|
|
|
let normalize = (t: t): t => {
|
|
|
|
t
|
2020-07-17 01:14:42 +00:00
|
|
|
|> updateIntegralCache(Some(integral(t)))
|
|
|
|
|> scaleBy(~scale=1. /. integralEndY(t))
|
|
|
|
|> updateIntegralSumCache(Some(1.0));
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let mean = (t: t) => {
|
|
|
|
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,
|
|
|
|
);
|
|
|
|
};
|
|
|
|
let variance = (t: t): float =>
|
|
|
|
XYShape.Analysis.getVarianceDangerously(
|
|
|
|
t,
|
|
|
|
mean,
|
|
|
|
XYShape.Analysis.getMeanOfSquaresContinuousShape,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
/* This simply creates multiple copies of the continuous distribution, scaled and shifted according to
|
|
|
|
each discrete data point, and then adds them all together. */
|
|
|
|
let combineAlgebraicallyWithDiscrete =
|
|
|
|
(
|
|
|
|
op: ExpressionTypes.algebraicOperation,
|
|
|
|
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
|
2020-07-12 22:53:53 +00:00
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
if (XYShape.T.isEmpty(t1s) || XYShape.T.isEmpty(t2s)) {
|
|
|
|
empty;
|
|
|
|
} else {
|
|
|
|
let shapeArray = AlgebraicShapeCombination.combineShapesContinuousDiscrete(op, t1s, t2s);
|
|
|
|
|
|
|
|
let t1Interpolator = XYShape.XtoY.continuousInterpolator(t1.interpolation, `UseZero);
|
|
|
|
let t2Interpolator = XYShape.XtoY.discreteInterpolator;
|
2020-07-08 16:00:13 +00:00
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
let combinedShape =
|
|
|
|
shapeArray
|
|
|
|
|> E.A.fold_left(
|
|
|
|
XYShape.PointwiseCombination.combine((+.),
|
|
|
|
t1Interpolator,
|
|
|
|
t2Interpolator),
|
|
|
|
XYShape.T.empty);
|
2020-07-08 16:00:13 +00:00
|
|
|
|
2020-07-12 22:53:53 +00:00
|
|
|
let combinedIntegralSum =
|
|
|
|
Common.combineIntegralSums(
|
|
|
|
(a, b) => Some(a *. b),
|
2020-07-17 01:14:42 +00:00
|
|
|
t1.integralSumCache,
|
|
|
|
t2.integralSumCache,
|
2020-07-12 22:53:53 +00:00
|
|
|
);
|
2020-07-08 16:00:13 +00:00
|
|
|
|
2020-07-17 01:14:42 +00:00
|
|
|
// TODO: It could make sense to automatically transform the integrals here (shift or scale)
|
|
|
|
make(t1.interpolation, combinedShape, combinedIntegralSum, None)
|
|
|
|
};
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let combineAlgebraically =
|
2020-07-09 10:46:28 +00:00
|
|
|
(op: ExpressionTypes.algebraicOperation, t1: t, t2: t) => {
|
2020-07-08 16:00:13 +00:00
|
|
|
let s1 = t1 |> getShape;
|
|
|
|
let s2 = t2 |> getShape;
|
|
|
|
let t1n = s1 |> XYShape.T.length;
|
|
|
|
let t2n = s2 |> XYShape.T.length;
|
|
|
|
if (t1n == 0 || t2n == 0) {
|
|
|
|
empty;
|
|
|
|
} else {
|
|
|
|
let combinedShape =
|
|
|
|
AlgebraicShapeCombination.combineShapesContinuousContinuous(op, s1, s2);
|
|
|
|
let combinedIntegralSum =
|
|
|
|
Common.combineIntegralSums(
|
|
|
|
(a, b) => Some(a *. b),
|
2020-07-17 01:14:42 +00:00
|
|
|
t1.integralSumCache,
|
|
|
|
t2.integralSumCache,
|
2020-07-08 16:00:13 +00:00
|
|
|
);
|
|
|
|
// return a new Continuous distribution
|
2020-07-17 01:14:42 +00:00
|
|
|
make(`Linear, combinedShape, combinedIntegralSum, None);
|
2020-07-08 16:00:13 +00:00
|
|
|
};
|
|
|
|
};
|