squiggle/src/distributions/Distributions.re

540 lines
16 KiB
ReasonML
Raw Normal View History

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
};
module type dist = {
type t;
let minX: t => option(float);
let maxX: t => option(float);
let pointwiseFmap: (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);
let toScaledContinuous: t => option(DistTypes.continuousShape);
let toScaledDiscrete: t => option(DistTypes.discreteShape);
2020-02-22 10:17:51 +00:00
type integral;
2020-02-22 10:10:10 +00:00
let integral: (~cache: option(integral), t) => integral;
let integralSum: (~cache: option(integral), t) => float;
2020-02-22 10:17:51 +00:00
let integralXtoY: (~cache: option(integral), float, 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 xToY = T.xToY;
2020-02-22 16:24:54 +00:00
let toShape = T.toShape;
let toContinuous = T.toContinuous;
let toDiscrete = T.toDiscrete;
2020-02-22 21:46:49 +00:00
let toScaledContinuous = T.toScaledContinuous;
let toScaledDiscrete = T.toScaledDiscrete;
2020-02-22 16:24:54 +00:00
let scaleBy = (~scale=1.0, t: t) =>
t |> pointwiseFmap((r: float) => r *. scale);
2020-02-22 10:17:51 +00:00
module Integral = {
type t = T.integral;
let get = T.integral;
let xToY = T.integralXtoY;
let sum = T.integralSum;
};
// This is suboptimal because it could get the cache but doesn't here.
let scaleToIntegralSum = (~intendedSum=1.0, t: t) => {
let scale = intendedSum /. Integral.sum(~cache=None, t);
scaleBy(~scale, t);
};
};
2020-02-22 16:24:54 +00:00
module Continuous = {
2020-02-23 13:27:52 +00:00
type t = DistTypes.continuousShape;
2020-02-22 16:24:54 +00:00
let xyShape = (t: t) => t.xyShape;
let getShape = (t: t) => t.xyShape;
let interpolation = (t: t) => t.interpolation;
let make = (xyShape, interpolation): t => {xyShape, interpolation};
let fromShape = xyShape => make(xyShape, `Linear);
let shapeMap = (fn, {xyShape, interpolation}: t): t => {
xyShape: fn(xyShape),
interpolation,
};
2020-02-24 11:11:03 +00:00
let lastY = (t: t) =>
t |> xyShape |> XYShape.unsafeLast |> (((_, y)) => y);
2020-02-22 16:24:54 +00:00
let oShapeMap =
2020-02-23 13:27:52 +00:00
(fn, {xyShape, interpolation}: t): option(DistTypes.continuousShape) =>
2020-02-23 18:34:34 +00:00
fn(xyShape) |> E.O.fmap(make(_, interpolation));
let toLinear = (t: t): t =>
switch (t) {
| {interpolation: `Stepwise, xyShape} => {
interpolation: `Linear,
xyShape: xyShape |> XYShape.Range.stepsToContinuous |> E.O.toExt(""),
}
| {interpolation: `Linear, _} => t
};
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-02-22 16:24:54 +00:00
let shapeFn = (fn, t: t) => t |> xyShape |> fn;
let minX = shapeFn(XYShape.minX);
let maxX = shapeFn(XYShape.maxX);
let pointwiseFmap = (fn, t: t) =>
t |> xyShape |> XYShape.pointwiseMap(fn) |> fromShape;
2020-02-23 13:27:52 +00:00
let toShape = (t: t): DistTypes.shape => Continuous(t);
let xToY = (f, {interpolation, xyShape}: t) =>
switch (interpolation) {
| `Stepwise =>
xyShape
|> XYShape.XtoY.stepwise(f)
|> E.O.default(0.0)
|> DistTypes.MixedPoint.makeContinuous
| `Linear =>
xyShape
|> XYShape.XtoY.linear(f)
|> DistTypes.MixedPoint.makeContinuous
};
2020-02-24 11:11:03 +00:00
let integral = (~cache, t) =>
cache
|> E.O.default(
t
|> xyShape
|> XYShape.Range.integrateWithTriangles
|> E.O.toExt("This should not have happened")
|> fromShape,
);
let integralSum = (~cache, t) => t |> integral(~cache) |> lastY;
2020-02-22 16:24:54 +00:00
let integralXtoY = (~cache, f, t) =>
t |> integral(~cache) |> shapeFn(CdfLibrary.Distribution.findY(f));
let toContinuous = t => Some(t);
let toDiscrete = _ => None;
2020-02-22 21:46:49 +00:00
let toScaledContinuous = t => Some(t);
let toScaledDiscrete = _ => None;
2020-02-22 16:24:54 +00:00
});
};
2020-02-22 16:24:54 +00:00
module Discrete = {
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) =>
cache
2020-02-24 11:11:03 +00:00
|> E.O.default(Continuous.make(XYShape.accumulateYs(t), `Stepwise));
let integralSum = (~cache, t) =>
t |> integral(~cache) |> Continuous.lastY;
2020-02-22 16:24:54 +00:00
let minX = XYShape.minX;
let maxX = XYShape.maxX;
let pointwiseFmap = XYShape.pointwiseMap;
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-02-23 18:34:34 +00:00
let toScaledContinuous = _ => None;
2020-02-22 21:46:49 +00:00
let toScaledDiscrete = t => Some(t);
2020-02-23 19:40:38 +00:00
let xToY = (f, t) => {
XYShape.XtoY.ifAtX(f, t)
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-02-23 19:40:38 +00:00
};
2020-02-23 18:34:34 +00:00
// todo: This should use cache and/or same code as above. FindingY is more complex, should use interpolationType.
2020-02-22 16:24:54 +00:00
let integralXtoY = (~cache, f, t) =>
2020-02-23 19:40:38 +00:00
t
|> integral(~cache)
|> Continuous.getShape
|> CdfLibrary.Distribution.findY(f);
2020-02-22 16:24:54 +00:00
});
};
2020-02-22 16:24:54 +00:00
module Mixed = {
2020-02-23 18:34:34 +00:00
type t = DistTypes.mixedShape;
let make =
(~continuous, ~discrete, ~discreteProbabilityMassFraction)
2020-02-23 13:27:52 +00:00
: DistTypes.mixedShape => {
continuous,
discrete,
discreteProbabilityMassFraction,
};
2020-02-23 13:27:52 +00:00
let clean = (t: DistTypes.mixedShape): option(DistTypes.shape) => {
switch (t) {
| {
continuous: {xyShape: {xs: [||], ys: [||]}},
discrete: {xs: [||], ys: [||]},
} =>
None
2020-02-23 18:34:34 +00:00
| {continuous, discrete: {xs: [|_|], ys: [|_|]}} =>
Some(Continuous(continuous))
| {continuous, discrete: {xs: [||], ys: [||]}} =>
Some(Continuous(continuous))
| {continuous: {xyShape: {xs: [||], ys: [||]}}, discrete} =>
Some(Discrete(discrete))
| shape => Some(Mixed(shape))
};
};
2020-02-23 18:34:34 +00:00
// todo: Put into scaling module
2020-02-23 12:40:18 +00:00
let scaleDiscreteFn =
2020-02-23 13:27:52 +00:00
({discreteProbabilityMassFraction}: DistTypes.mixedShape, f) =>
2020-02-22 21:46:49 +00:00
f *. discreteProbabilityMassFraction;
2020-02-23 12:40:18 +00:00
let scaleContinuousFn =
2020-02-23 13:27:52 +00:00
({discreteProbabilityMassFraction}: DistTypes.mixedShape, f) =>
2020-02-22 21:46:49 +00:00
f *. (1.0 -. discreteProbabilityMassFraction);
2020-02-23 18:34:34 +00:00
let scaleContinuous = ({discreteProbabilityMassFraction}: t, continuous) =>
continuous
|> Continuous.T.scaleBy(~scale=1.0 -. discreteProbabilityMassFraction);
let scaleDiscrete = ({discreteProbabilityMassFraction}: t, disrete) =>
disrete |> Discrete.T.scaleBy(~scale=discreteProbabilityMassFraction);
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-22 16:24:54 +00:00
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));
2020-02-23 13:27:52 +00:00
let toShape = (t: t): DistTypes.shape => Mixed(t);
2020-02-22 16:24:54 +00:00
let toContinuous = ({continuous}: t) => Some(continuous);
let toDiscrete = ({discrete}: t) => Some(discrete);
2020-02-23 12:40:18 +00:00
let xToY = (f, {discrete, continuous} as t: t) => {
2020-02-22 16:24:54 +00:00
let c =
continuous
|> Continuous.T.xToY(f)
2020-02-23 13:27:52 +00:00
|> DistTypes.MixedPoint.fmap(scaleContinuousFn(t));
2020-02-22 16:24:54 +00:00
let d =
discrete
|> Discrete.T.xToY(f)
2020-02-23 13:27:52 +00:00
|> DistTypes.MixedPoint.fmap(scaleDiscreteFn(t));
DistTypes.MixedPoint.add(c, d);
2020-02-22 16:24:54 +00:00
};
let toScaledContinuous = ({continuous} as t: t) =>
Some(scaleContinuous(t, continuous));
let toScaledDiscrete = ({discrete} as t: t) =>
Some(scaleDiscrete(t, discrete));
2020-02-22 16:24:54 +00:00
// TODO: Add these two directly, once interpolation is added.
let integral =
(
~cache,
{continuous, discrete, discreteProbabilityMassFraction} as t: t,
) => {
cache
|> E.O.default(
{
let cont =
continuous
|> Continuous.T.Integral.get(~cache=None)
|> scaleContinuous(t);
let dist =
discrete
|> Discrete.T.Integral.get(~cache=None)
2020-02-23 18:34:34 +00:00
|> Continuous.toLinear
|> Continuous.T.scaleBy(
~scale=discreteProbabilityMassFraction,
);
2020-02-23 18:34:34 +00:00
Continuous.make(
XYShape.combine(
Continuous.getShape(cont),
Continuous.getShape(dist),
),
`Linear,
);
},
);
2020-02-22 10:10:10 +00:00
};
2020-02-23 12:40:18 +00:00
// todo: Get last element of actual sum.
let integralSum = (~cache, {discrete, continuous} as t: t) => {
2020-02-22 16:24:54 +00:00
switch (cache) {
| Some(cache) => 3.0
| None =>
2020-02-23 12:40:18 +00:00
scaleDiscreteFn(t, Discrete.T.Integral.sum(~cache=None, discrete))
+. scaleContinuousFn(
t,
Continuous.T.Integral.sum(~cache=None, continuous),
)
2020-02-22 16:24:54 +00:00
};
2020-02-22 10:10:10 +00:00
};
2020-02-23 12:40:18 +00:00
let integralXtoY = (~cache, f, {discrete, continuous} as t: t) => {
2020-02-22 16:24:54 +00:00
let cont = Continuous.T.Integral.xToY(~cache, f, continuous);
let discrete = Discrete.T.Integral.xToY(~cache, f, discrete);
2020-02-23 12:40:18 +00:00
scaleDiscreteFn(t, discrete) +. scaleContinuousFn(t, cont);
2020-02-22 16:24:54 +00:00
};
2020-02-23 18:34:34 +00:00
// TODO: This functionality is kinda weird, because it seems to assume the cdf adds to 1.0 elsewhere, which wouldn't happen here.
2020-02-22 16:24:54 +00:00
let pointwiseFmap =
(fn, {discrete, continuous, discreteProbabilityMassFraction}: t): t => {
{
discrete: Discrete.T.pointwiseFmap(fn, discrete),
continuous: Continuous.T.pointwiseFmap(fn, continuous),
discreteProbabilityMassFraction,
};
};
2020-02-22 16:24:54 +00:00
});
};
2020-02-22 16:24:54 +00:00
module Shape = {
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-02-23 18:34:34 +00:00
// todo: change order of arguments so t goes last.
// todo: Think of other name here?
2020-02-22 16:24:54 +00:00
let mapToAll = (t: t, (fn1, fn2, fn3)) =>
switch (t) {
| Mixed(m) => fn1(m)
| Discrete(m) => fn2(m)
| Continuous(m) => fn3(m)
};
2020-02-22 12:51:25 +00:00
2020-02-22 16:24:54 +00:00
let fmap = (t: t, (fn1, fn2, fn3)): t =>
switch (t) {
| Mixed(m) => Mixed(fn1(m))
| Discrete(m) => Discrete(fn2(m))
| Continuous(m) => Continuous(fn3(m))
};
2020-02-22 12:51:25 +00:00
2020-02-22 16:24:54 +00:00
let xToY = (f, t) =>
mapToAll(
t,
(Mixed.T.xToY(f), Discrete.T.xToY(f), Continuous.T.xToY(f)),
);
let toShape = (t: t) => t;
let toContinuous = (t: t) =>
mapToAll(
t,
(
Mixed.T.toContinuous,
Discrete.T.toContinuous,
Continuous.T.toContinuous,
),
);
let toDiscrete = (t: t) =>
mapToAll(
t,
(
Mixed.T.toDiscrete,
Discrete.T.toDiscrete,
Continuous.T.toDiscrete,
),
);
2020-02-22 21:46:49 +00:00
let toScaledDiscrete = (t: t) =>
mapToAll(
t,
(
Mixed.T.toScaledDiscrete,
Discrete.T.toScaledDiscrete,
Continuous.T.toScaledDiscrete,
),
);
let toScaledContinuous = (t: t) =>
mapToAll(
t,
(
Mixed.T.toScaledContinuous,
Discrete.T.toScaledContinuous,
Continuous.T.toScaledContinuous,
),
);
2020-02-22 16:24:54 +00:00
let minX = (t: t) =>
mapToAll(t, (Mixed.T.minX, Discrete.T.minX, Continuous.T.minX));
let integral = (~cache, t: t) => {
2020-02-22 16:24:54 +00:00
mapToAll(
t,
(
Mixed.T.Integral.get(~cache),
Discrete.T.Integral.get(~cache),
Continuous.T.Integral.get(~cache),
),
);
};
2020-02-22 16:24:54 +00:00
let integralSum = (~cache, t: t) =>
mapToAll(
t,
(
Mixed.T.Integral.sum(~cache),
Discrete.T.Integral.sum(~cache),
Continuous.T.Integral.sum(~cache),
),
);
let integralXtoY = (~cache, f, t) => {
mapToAll(
t,
(
Mixed.T.Integral.xToY(~cache, f),
Discrete.T.Integral.xToY(~cache, f),
Continuous.T.Integral.xToY(~cache, f),
),
);
};
let maxX = (t: t) =>
mapToAll(t, (Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX));
2020-02-22 16:24:54 +00:00
let pointwiseFmap = (fn, t: t) =>
fmap(
t,
(
Mixed.T.pointwiseFmap(fn),
Discrete.T.pointwiseFmap(fn),
Continuous.T.pointwiseFmap(fn),
),
);
});
};
module DistPlus = {
2020-02-23 13:27:52 +00:00
open DistTypes;
2020-02-23 18:34:34 +00:00
type t = DistTypes.distPlus;
let make =
(
~shape,
~guesstimatorString,
~domain=Complete,
~unit=UnspecifiedDistribution,
(),
)
2020-02-23 18:34:34 +00:00
: t => {
let integral = Shape.T.Integral.get(~cache=None, shape);
{shape, domain, integralCache: integral, unit, guesstimatorString};
};
2020-02-23 18:34:34 +00:00
let update =
(
~shape=?,
~integralCache=?,
~domain=?,
~unit=?,
~guesstimatorString=?,
2020-02-23 18:34:34 +00:00
t: t,
) => {
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-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;
let toContinuous = shapeFn(Shape.T.toContinuous);
let toDiscrete = shapeFn(Shape.T.toDiscrete);
2020-02-22 21:46:49 +00:00
// todo: Adjust for total mass.
2020-02-23 12:40:18 +00:00
let toScaledContinuous = (t: t) => {
t
|> toShape
|> Shape.T.toScaledContinuous
|> E.O.fmap(
2020-02-23 18:34:34 +00:00
Continuous.T.pointwiseFmap(
domainIncludedProbabilityMassAdjustment(t),
2020-02-23 12:40:18 +00:00
),
);
};
let toScaledDiscrete = (t: t) => {
t
|> toShape
|> Shape.T.toScaledDiscrete
|> E.O.fmap(
Discrete.T.pointwiseFmap(
domainIncludedProbabilityMassAdjustment(t),
),
);
};
let xToY = (f, t: t) =>
t
|> toShape
|> Shape.T.xToY(f)
|> MixedPoint.fmap(domainIncludedProbabilityMassAdjustment(t));
let minX = shapeFn(Shape.T.minX);
let maxX = shapeFn(Shape.T.maxX);
2020-02-23 12:40:18 +00:00
let fromShape = (t, shape): t => update(~shape, t);
// todo: adjust for limit, maybe?
2020-02-22 16:24:54 +00:00
let pointwiseFmap = (fn, {shape, _} as t: t): t =>
2020-02-23 12:40:18 +00:00
Shape.T.pointwiseFmap(fn, shape) |> fromShape(t);
// This bit is kind of akward, could probably use rethinking.
2020-02-22 16:24:54 +00:00
let integral = (~cache as _, t: t) =>
2020-02-23 12:40:18 +00:00
fromShape(t, Continuous(t.integralCache));
2020-02-22 16:24:54 +00:00
let integralSum = (~cache as _, t: t) =>
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
let integralXtoY = (~cache as _, f, t: t) => {
Shape.T.Integral.xToY(~cache=Some(t.integralCache), f, toShape(t));
2020-02-22 16:24:54 +00:00
};
});
2020-02-23 18:34:34 +00:00
};
module DistPlusTime = {
open DistTypes;
open DistPlus;
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;
let xToY = (~cache as _, f: TimeTypes.timeInVector, t: t) => {
timeInVectorToX(f, t)
|> E.O.fmap(x => DistPlus.T.Integral.xToY(~cache=None, x, t));
};
};
2020-02-22 16:24:54 +00:00
};