squiggle/src/distPlus/distribution/Mixed.re
2020-07-08 17:30:36 +01:00

308 lines
10 KiB
ReasonML

open Distributions;
type t = DistTypes.mixedShape;
let make = (~continuous, ~discrete): t => {continuous, discrete};
let totalLength = (t: t): int => {
let continuousLength =
t.continuous |> Continuous.getShape |> XYShape.T.length;
let discreteLength = t.discrete |> Discrete.getShape |> XYShape.T.length;
continuousLength + discreteLength;
};
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);
};
let toContinuous = ({continuous}: t) => Some(continuous);
let toDiscrete = ({discrete}: t) => Some(discrete);
let combinePointwise = (~knownIntegralSumsFn, fn, t1: t, t2: t) => {
let reducedDiscrete =
[|t1, t2|]
|> E.A.fmap(toDiscrete)
|> E.A.O.concatSomes
|> Discrete.reduce(~knownIntegralSumsFn, fn);
let reducedContinuous =
[|t1, t2|]
|> E.A.fmap(toContinuous)
|> E.A.O.concatSomes
|> Continuous.reduce(~knownIntegralSumsFn, fn);
make(~discrete=reducedDiscrete, ~continuous=reducedContinuous);
};
module T =
Dist({
type t = DistTypes.mixedShape;
type integral = DistTypes.continuousShape;
let minX = ({continuous, discrete}: t) => {
min(Continuous.T.minX(continuous), Discrete.T.minX(discrete));
};
let maxX = ({continuous, discrete}: t) =>
max(Continuous.T.maxX(continuous), Discrete.T.maxX(discrete));
let toShape = (t: t): DistTypes.shape => Mixed(t);
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);
};
let normalize = (t: t): t => {
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, t.continuous);
let discreteIntegralSum =
Discrete.T.Integral.sum(~cache=None, t.discrete);
let totalIntegralSum = continuousIntegralSum +. discreteIntegralSum;
let newContinuousSum = continuousIntegralSum /. totalIntegralSum;
let newDiscreteSum = discreteIntegralSum /. totalIntegralSum;
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));
make(~continuous=normalizedContinuous, ~discrete=normalizedDiscrete);
};
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) => {
let discreteIntegralSum =
Discrete.T.Integral.sum(~cache=None, discrete);
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, continuous);
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
let discreteIntegralSum =
Discrete.T.Integral.sum(~cache=None, discrete);
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, continuous);
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
// TODO: figure out what to do when the totalIntegralSum is zero.
let downsampledDiscrete =
Discrete.T.downsample(
int_of_float(
float_of_int(count) *. (discreteIntegralSum /. totalIntegralSum),
),
discrete,
);
let downsampledContinuous =
Continuous.T.downsample(
int_of_float(
float_of_int(count) *. (continuousIntegralSum /. totalIntegralSum),
),
continuous,
);
{discrete: downsampledDiscrete, continuous: downsampledContinuous};
};
let normalizedToContinuous = (t: t) => Some(normalize(t).continuous);
let normalizedToDiscrete = ({discrete} as t: t) =>
Some(normalize(t).discrete);
let integral = (~cache, {continuous, discrete}: t) => {
switch (cache) {
| Some(cache) => cache
| None =>
// note: if the underlying shapes aren't normalized, then these integrals won't be either!
let continuousIntegral =
Continuous.T.Integral.get(~cache=None, continuous);
let discreteIntegral = Discrete.T.Integral.get(~cache=None, discrete);
Continuous.make(
`Linear,
XYShape.PointwiseCombination.combineLinear(
~fn=(+.),
Continuous.getShape(continuousIntegral),
Continuous.getShape(discreteIntegral),
),
None,
);
};
};
let integralEndY = (~cache, t: t) => {
integral(~cache, t) |> Continuous.lastY;
};
let integralXtoY = (~cache, f, t) => {
t |> integral(~cache) |> Continuous.getShape |> XYShape.XtoY.linear(f);
};
let integralYtoX = (~cache, f, t) => {
t |> integral(~cache) |> Continuous.getShape |> XYShape.YtoX.linear(f);
};
// 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.
let mapY =
(
~knownIntegralSumFn=previousIntegralSum => None,
fn,
{discrete, continuous}: t,
)
: t => {
let u = E.O.bind(_, knownIntegralSumFn);
let yMappedDiscrete =
discrete
|> Discrete.T.mapY(fn)
|> Discrete.updateKnownIntegralSum(u(discrete.knownIntegralSum));
let yMappedContinuous =
continuous
|> Continuous.T.mapY(fn)
|> Continuous.updateKnownIntegralSum(u(continuous.knownIntegralSum));
{
discrete: yMappedDiscrete,
continuous: Continuous.T.mapY(fn, continuous),
};
};
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:
let discreteIntegralSum =
Discrete.T.Integral.sum(~cache=None, discrete);
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, continuous);
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
(
discreteMean
*. discreteIntegralSum
+. continuousMean
*. continuousIntegralSum
)
/. totalIntegralSum;
};
let variance = ({discrete, continuous} as t: t): float => {
// the combined mean is the weighted sum of the two:
let discreteIntegralSum =
Discrete.T.Integral.sum(~cache=None, discrete);
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, continuous);
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
let getMeanOfSquares = ({discrete, continuous}: t) => {
let discreteMean =
discrete
|> Discrete.shapeMap(XYShape.Analysis.squareXYShape)
|> Discrete.T.mean;
let continuousMean =
continuous |> XYShape.Analysis.getMeanOfSquaresContinuousShape;
(
discreteMean
*. discreteIntegralSum
+. continuousMean
*. continuousIntegralSum
)
/. totalIntegralSum;
};
switch (discreteIntegralSum /. totalIntegralSum) {
| 1.0 => Discrete.T.variance(discrete)
| 0.0 => Continuous.T.variance(continuous)
| _ =>
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares)
};
};
});
let combineAlgebraically =
(~downsample=false, op: ExpressionTypes.algebraicOperation, t1: t, t2: t)
: t => {
// 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)));
sqtl > 10. && downsample ? T.downsample(int_of_float(sqtl), t) : t;
};
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:
let ccConvResult =
Continuous.combineAlgebraically(
~downsample=false,
op,
t1d.continuous,
t2d.continuous,
);
let dcConvResult =
Continuous.combineAlgebraicallyWithDiscrete(
~downsample=false,
op,
t2d.continuous,
t1d.discrete,
);
let cdConvResult =
Continuous.combineAlgebraicallyWithDiscrete(
~downsample=false,
op,
t1d.continuous,
t2d.discrete,
);
let continuousConvResult =
Continuous.reduce((+.), [|ccConvResult, dcConvResult, cdConvResult|]);
// ... finally, discrete (*) discrete => discrete, obviously:
let discreteConvResult =
Discrete.combineAlgebraically(op, t1d.discrete, t2d.discrete);
{discrete: discreteConvResult, continuous: continuousConvResult};
};