It compiles!

This commit is contained in:
Sebastian Kosch 2020-06-26 21:29:21 -07:00
parent bd528571af
commit dc1ec1bb86
9 changed files with 751 additions and 468 deletions

View File

@ -24,7 +24,7 @@ let makeTestCloseEquality = (~only=false, str, item1, item2, ~digits) =>
describe("Shape", () => { describe("Shape", () => {
describe("Continuous", () => { describe("Continuous", () => {
open Distributions.Continuous; open Distributions.Continuous;
let continuous = make(`Linear, shape); let continuous = make(`Linear, shape, None);
makeTest("minX", T.minX(continuous), 1.0); makeTest("minX", T.minX(continuous), 1.0);
makeTest("maxX", T.maxX(continuous), 8.0); makeTest("maxX", T.maxX(continuous), 8.0);
makeTest( makeTest(
@ -57,7 +57,7 @@ describe("Shape", () => {
); );
}); });
describe("when Stepwise", () => { describe("when Stepwise", () => {
let continuous = make(`Stepwise, shape); let continuous = make(`Stepwise, shape, None);
makeTest( makeTest(
"at 4.0", "at 4.0",
T.xToY(4., continuous), T.xToY(4., continuous),
@ -89,7 +89,7 @@ describe("Shape", () => {
"toLinear", "toLinear",
{ {
let continuous = let continuous =
make(`Stepwise, {xs: [|1., 4., 8.|], ys: [|0.1, 5., 1.0|]}); make(`Stepwise, {xs: [|1., 4., 8.|], ys: [|0.1, 5., 1.0|]}, None);
continuous |> toLinear |> E.O.fmap(getShape); continuous |> toLinear |> E.O.fmap(getShape);
}, },
Some({ Some({
@ -100,7 +100,7 @@ describe("Shape", () => {
makeTest( makeTest(
"toLinear", "toLinear",
{ {
let continuous = make(`Stepwise, {xs: [|0.0|], ys: [|0.3|]}); let continuous = make(`Stepwise, {xs: [|0.0|], ys: [|0.3|]}, None);
continuous |> toLinear |> E.O.fmap(getShape); continuous |> toLinear |> E.O.fmap(getShape);
}, },
Some({xs: [|0.0|], ys: [|0.3|]}), Some({xs: [|0.0|], ys: [|0.3|]}),
@ -123,7 +123,7 @@ describe("Shape", () => {
makeTest( makeTest(
"integralEndY", "integralEndY",
continuous continuous
|> T.scaleToIntegralSum(~intendedSum=1.0) |> T.normalize //scaleToIntegralSum(~intendedSum=1.0)
|> T.Integral.sum(~cache=None), |> T.Integral.sum(~cache=None),
1.0, 1.0,
); );
@ -135,12 +135,12 @@ describe("Shape", () => {
xs: [|1., 4., 8.|], xs: [|1., 4., 8.|],
ys: [|0.3, 0.5, 0.2|], ys: [|0.3, 0.5, 0.2|],
}; };
let discrete = shape; let discrete = make(shape, None);
makeTest("minX", T.minX(discrete), 1.0); makeTest("minX", T.minX(discrete), 1.0);
makeTest("maxX", T.maxX(discrete), 8.0); makeTest("maxX", T.maxX(discrete), 8.0);
makeTest( makeTest(
"mapY", "mapY",
T.mapY(r => r *. 2.0, discrete) |> (r => r.ys), T.mapY(r => r *. 2.0, discrete) |> (r => getShape(r).ys),
[|0.6, 1.0, 0.4|], [|0.6, 1.0, 0.4|],
); );
makeTest( makeTest(
@ -160,19 +160,22 @@ describe("Shape", () => {
); );
makeTest( makeTest(
"scaleBy", "scaleBy",
T.scaleBy(~scale=4.0, discrete), scaleBy(~scale=4.0, discrete),
{xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]}, make({xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]}, None),
); );
makeTest( makeTest(
"scaleToIntegralSum", "normalize, then scale by 4.0",
T.scaleToIntegralSum(~intendedSum=4.0, discrete), discrete
{xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]}, |> T.normalize
|> scaleBy(~scale=4.0),
make({xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]}, None),
); );
makeTest( makeTest(
"scaleToIntegralSum: back and forth", "scaleToIntegralSum: back and forth",
discrete discrete
|> T.scaleToIntegralSum(~intendedSum=4.0) |> T.normalize
|> T.scaleToIntegralSum(~intendedSum=1.0), |> scaleBy(~scale=4.0)
|> T.normalize,
discrete, discrete,
); );
makeTest( makeTest(
@ -181,12 +184,13 @@ describe("Shape", () => {
Distributions.Continuous.make( Distributions.Continuous.make(
`Stepwise, `Stepwise,
{xs: [|1., 4., 8.|], ys: [|0.3, 0.8, 1.0|]}, {xs: [|1., 4., 8.|], ys: [|0.3, 0.8, 1.0|]},
None
), ),
); );
makeTest( makeTest(
"integral with 1 element", "integral with 1 element",
T.Integral.get(~cache=None, {xs: [|0.0|], ys: [|1.0|]}), T.Integral.get(~cache=None, Distributions.Discrete.make({xs: [|0.0|], ys: [|1.0|]}, None)),
Distributions.Continuous.make(`Stepwise, {xs: [|0.0|], ys: [|1.0|]}), Distributions.Continuous.make(`Stepwise, {xs: [|0.0|], ys: [|1.0|]}, None),
); );
makeTest( makeTest(
"integralXToY", "integralXToY",
@ -205,27 +209,22 @@ describe("Shape", () => {
describe("Mixed", () => { describe("Mixed", () => {
open Distributions.Mixed; open Distributions.Mixed;
let discrete: DistTypes.xyShape = { let discreteShape: DistTypes.xyShape = {
xs: [|1., 4., 8.|], xs: [|1., 4., 8.|],
ys: [|0.3, 0.5, 0.2|], ys: [|0.3, 0.5, 0.2|],
}; };
let discrete = Distributions.Discrete.make(discreteShape, None);
let continuous = let continuous =
Distributions.Continuous.make( Distributions.Continuous.make(
`Linear, `Linear,
{xs: [|3., 7., 14.|], ys: [|0.058, 0.082, 0.124|]}, {xs: [|3., 7., 14.|], ys: [|0.058, 0.082, 0.124|]},
None
) )
|> Distributions.Continuous.T.scaleToIntegralSum(~intendedSum=1.0); |> Distributions.Continuous.T.normalize; //scaleToIntegralSum(~intendedSum=1.0);
let mixed = let mixed = Distributions.Mixed.make(
MixedShapeBuilder.build(
~continuous, ~continuous,
~discrete, ~discrete,
~assumptions={ );
continuous: ADDS_TO_CORRECT_PROBABILITY,
discrete: ADDS_TO_CORRECT_PROBABILITY,
discreteProbabilityMass: Some(0.5),
},
)
|> E.O.toExn("");
makeTest("minX", T.minX(mixed), 1.0); makeTest("minX", T.minX(mixed), 1.0);
makeTest("maxX", T.maxX(mixed), 14.0); makeTest("maxX", T.maxX(mixed), 14.0);
makeTest( makeTest(
@ -243,9 +242,9 @@ describe("Shape", () => {
0.24775224775224775, 0.24775224775224775,
|], |],
}, },
None
), ),
~discrete={xs: [|1., 4., 8.|], ys: [|0.6, 1.0, 0.4|]}, ~discrete=Distributions.Discrete.make({xs: [|1., 4., 8.|], ys: [|0.6, 1.0, 0.4|]}, None)
~discreteProbabilityMassFraction=0.5,
), ),
); );
makeTest( makeTest(
@ -266,7 +265,7 @@ describe("Shape", () => {
makeTest("integralEndY", T.Integral.sum(~cache=None, mixed), 1.0); makeTest("integralEndY", T.Integral.sum(~cache=None, mixed), 1.0);
makeTest( makeTest(
"scaleBy", "scaleBy",
T.scaleBy(~scale=2.0, mixed), Distributions.Mixed.scaleBy(~scale=2.0, mixed),
Distributions.Mixed.make( Distributions.Mixed.make(
~continuous= ~continuous=
Distributions.Continuous.make( Distributions.Continuous.make(
@ -279,9 +278,9 @@ describe("Shape", () => {
0.24775224775224775, 0.24775224775224775,
|], |],
}, },
None
), ),
~discrete={xs: [|1., 4., 8.|], ys: [|0.6, 1.0, 0.4|]}, ~discrete=Distributions.Discrete.make({xs: [|1., 4., 8.|], ys: [|0.6, 1.0, 0.4|]}, None),
~discreteProbabilityMassFraction=0.5,
), ),
); );
makeTest( makeTest(
@ -302,34 +301,31 @@ describe("Shape", () => {
0.6913122927072927, 0.6913122927072927,
1.0, 1.0,
|], |],
}, },
None,
), ),
); );
}); });
describe("Distplus", () => { describe("Distplus", () => {
open Distributions.DistPlus; open Distributions.DistPlus;
let discrete: DistTypes.xyShape = { let discreteShape: DistTypes.xyShape = {
xs: [|1., 4., 8.|], xs: [|1., 4., 8.|],
ys: [|0.3, 0.5, 0.2|], ys: [|0.3, 0.5, 0.2|],
}; };
let discrete = Distributions.Discrete.make(discreteShape, None);
let continuous = let continuous =
Distributions.Continuous.make( Distributions.Continuous.make(
`Linear, `Linear,
{xs: [|3., 7., 14.|], ys: [|0.058, 0.082, 0.124|]}, {xs: [|3., 7., 14.|], ys: [|0.058, 0.082, 0.124|]},
None
) )
|> Distributions.Continuous.T.scaleToIntegralSum(~intendedSum=1.0); |> Distributions.Continuous.T.normalize; //scaleToIntegralSum(~intendedSum=1.0);
let mixed = let mixed =
MixedShapeBuilder.build( Distributions.Mixed.make(
~continuous, ~continuous,
~discrete, ~discrete,
~assumptions={ );
continuous: ADDS_TO_CORRECT_PROBABILITY,
discrete: ADDS_TO_CORRECT_PROBABILITY,
discreteProbabilityMass: Some(0.5),
},
)
|> E.O.toExn("");
let distPlus = let distPlus =
Distributions.DistPlus.make( Distributions.DistPlus.make(
~shape=Mixed(mixed), ~shape=Mixed(mixed),
@ -374,6 +370,7 @@ describe("Shape", () => {
1.0, 1.0,
|], |],
}, },
None,
), ),
), ),
); );
@ -386,9 +383,9 @@ describe("Shape", () => {
let numSamples = 10000; let numSamples = 10000;
open Distributions.Shape; open Distributions.Shape;
let normal: SymbolicDist.dist = `Normal({mean, stdev}); let normal: SymbolicDist.dist = `Normal({mean, stdev});
let normalShape = TreeNode.toShape(numSamples, normal); let normalShape = TreeNode.toShape(numSamples, `DistData(`Symbolic(normal)));
let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev); let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev);
let lognormalShape = TreeNode.toShape(numSamples, lognormal); let lognormalShape = TreeNode.toShape(numSamples, `DistData(`Symbolic(lognormal)));
makeTestCloseEquality( makeTestCloseEquality(
"Mean of a normal", "Mean of a normal",

View File

@ -17,7 +17,7 @@ module FormConfig = [%lenses
// //
sampleCount: string, sampleCount: string,
outputXYPoints: string, outputXYPoints: string,
truncateTo: string, downsampleTo: string,
kernelWidth: string, kernelWidth: string,
} }
]; ];
@ -25,7 +25,7 @@ module FormConfig = [%lenses
type options = { type options = {
sampleCount: int, sampleCount: int,
outputXYPoints: int, outputXYPoints: int,
truncateTo: option(int), downsampleTo: option(int),
kernelWidth: option(float), kernelWidth: option(float),
}; };
@ -115,7 +115,7 @@ type inputs = {
samplingInputs: RenderTypes.ShapeRenderer.Sampling.inputs, samplingInputs: RenderTypes.ShapeRenderer.Sampling.inputs,
guesstimatorString: string, guesstimatorString: string,
length: int, length: int,
shouldTruncateSampledDistribution: int, shouldDownsampleSampledDistribution: int,
}; };
module DemoDist = { module DemoDist = {
@ -141,8 +141,8 @@ module DemoDist = {
kernelWidth: options.kernelWidth, kernelWidth: options.kernelWidth,
}, },
~distPlusIngredients, ~distPlusIngredients,
~shouldTruncate=options.truncateTo |> E.O.isSome, ~shouldDownsample=options.downsampleTo |> E.O.isSome,
~recommendedLength=options.truncateTo |> E.O.default(10000), ~recommendedLength=options.downsampleTo |> E.O.default(10000),
(), (),
); );
let response = DistPlusRenderer.run(inputs); let response = DistPlusRenderer.run(inputs);
@ -182,7 +182,7 @@ let make = () => {
unit: "days", unit: "days",
sampleCount: "30000", sampleCount: "30000",
outputXYPoints: "10000", outputXYPoints: "10000",
truncateTo: "1000", downsampleTo: "1000",
kernelWidth: "5", kernelWidth: "5",
}, },
(), (),
@ -210,7 +210,7 @@ let make = () => {
let sampleCount = reform.state.values.sampleCount |> Js.Float.fromString; let sampleCount = reform.state.values.sampleCount |> Js.Float.fromString;
let outputXYPoints = let outputXYPoints =
reform.state.values.outputXYPoints |> Js.Float.fromString; reform.state.values.outputXYPoints |> Js.Float.fromString;
let truncateTo = reform.state.values.truncateTo |> Js.Float.fromString; let downsampleTo = reform.state.values.downsampleTo |> Js.Float.fromString;
let kernelWidth = reform.state.values.kernelWidth |> Js.Float.fromString; let kernelWidth = reform.state.values.kernelWidth |> Js.Float.fromString;
let domain = let domain =
@ -252,20 +252,20 @@ let make = () => {
}; };
let options = let options =
switch (sampleCount, outputXYPoints, truncateTo) { switch (sampleCount, outputXYPoints, downsampleTo) {
| (_, _, _) | (_, _, _)
when when
!Js.Float.isNaN(sampleCount) !Js.Float.isNaN(sampleCount)
&& !Js.Float.isNaN(outputXYPoints) && !Js.Float.isNaN(outputXYPoints)
&& !Js.Float.isNaN(truncateTo) && !Js.Float.isNaN(downsampleTo)
&& sampleCount > 10. && sampleCount > 10.
&& outputXYPoints > 10. => && outputXYPoints > 10. =>
Some({ Some({
sampleCount: sampleCount |> int_of_float, sampleCount: sampleCount |> int_of_float,
outputXYPoints: outputXYPoints |> int_of_float, outputXYPoints: outputXYPoints |> int_of_float,
truncateTo: downsampleTo:
int_of_float(truncateTo) > 0 int_of_float(downsampleTo) > 0
? Some(int_of_float(truncateTo)) : None, ? Some(int_of_float(downsampleTo)) : None,
kernelWidth: kernelWidth == 0.0 ? None : Some(kernelWidth), kernelWidth: kernelWidth == 0.0 ? None : Some(kernelWidth),
}) })
| _ => None | _ => None
@ -287,7 +287,7 @@ let make = () => {
reform.state.values.unit, reform.state.values.unit,
reform.state.values.sampleCount, reform.state.values.sampleCount,
reform.state.values.outputXYPoints, reform.state.values.outputXYPoints,
reform.state.values.truncateTo, reform.state.values.downsampleTo,
reform.state.values.kernelWidth, reform.state.values.kernelWidth,
reloader |> string_of_int, reloader |> string_of_int,
|], |],
@ -481,7 +481,7 @@ let make = () => {
/> />
</Col> </Col>
<Col span=4> <Col span=4>
<FieldFloat field=FormConfig.TruncateTo label="Truncate To" /> <FieldFloat field=FormConfig.DownsampleTo label="Downsample To" />
</Col> </Col>
<Col span=4> <Col span=4>
<FieldFloat field=FormConfig.KernelWidth label="Kernel Width" /> <FieldFloat field=FormConfig.KernelWidth label="Kernel Width" />

View File

@ -43,7 +43,7 @@ module DemoDist = {
let str = let str =
switch (parsed1) { switch (parsed1) {
| Ok(r) => SymbolicDist.toString(r) | Ok(r) => TreeNode.toString(r)
| Error(e) => e | Error(e) => e
}; };
@ -58,7 +58,7 @@ module DemoDist = {
~guesstimatorString=None, ~guesstimatorString=None,
(), (),
) )
|> Distributions.DistPlus.T.scaleToIntegralSum(~intendedSum=1.0); |> Distributions.DistPlus.T.normalize;
<DistPlusPlot distPlus />; <DistPlusPlot distPlus />;
}) })
|> E.O.default(ReasonReact.null); |> E.O.default(ReasonReact.null);

View File

@ -3,7 +3,8 @@ module type dist = {
type integral; type integral;
let minX: t => float; let minX: t => float;
let maxX: t => float; let maxX: t => float;
let mapY: (~knownIntegralSumFn: float => option(float)=?, float => float, t) => t; let mapY:
(~knownIntegralSumFn: float => option(float)=?, float => float, t) => t;
let xToY: (float, t) => DistTypes.mixedPoint; let xToY: (float, t) => DistTypes.mixedPoint;
let toShape: t => DistTypes.shape; let toShape: t => DistTypes.shape;
let toContinuous: t => option(DistTypes.continuousShape); let toContinuous: t => option(DistTypes.continuousShape);
@ -13,6 +14,7 @@ module type dist = {
let normalizedToDiscrete: t => option(DistTypes.discreteShape); let normalizedToDiscrete: t => option(DistTypes.discreteShape);
let toDiscreteProbabilityMassFraction: t => float; let toDiscreteProbabilityMassFraction: t => float;
let downsample: (~cache: option(integral)=?, int, t) => t; let downsample: (~cache: option(integral)=?, int, t) => t;
let truncate: (option(float), option(float), t) => t;
let integral: (~cache: option(integral), t) => integral; let integral: (~cache: option(integral), t) => integral;
let integralEndY: (~cache: option(integral), t) => float; let integralEndY: (~cache: option(integral), t) => float;
@ -38,6 +40,7 @@ module Dist = (T: dist) => {
let toContinuous = T.toContinuous; let toContinuous = T.toContinuous;
let toDiscrete = T.toDiscrete; let toDiscrete = T.toDiscrete;
let normalize = T.normalize; let normalize = T.normalize;
let truncate = T.truncate;
let normalizedToContinuous = T.normalizedToContinuous; let normalizedToContinuous = T.normalizedToContinuous;
let normalizedToDiscrete = T.normalizedToDiscrete; let normalizedToDiscrete = T.normalizedToDiscrete;
let mean = T.mean; let mean = T.mean;
@ -52,7 +55,22 @@ module Dist = (T: dist) => {
}; };
}; };
module Continuous { 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 = {
type t = DistTypes.continuousShape; type t = DistTypes.continuousShape;
let getShape = (t: t) => t.xyShape; let getShape = (t: t) => t.xyShape;
let interpolation = (t: t) => t.interpolation; let interpolation = (t: t) => t.interpolation;
@ -78,17 +96,21 @@ module Continuous {
knownIntegralSum: Some(0.0), knownIntegralSum: Some(0.0),
}; };
let combine = let combine =
(fn, t1: DistTypes.continuousShape, t2: DistTypes.continuousShape) (
~knownIntegralSumsFn,
fn,
t1: DistTypes.continuousShape,
t2: DistTypes.continuousShape,
)
: DistTypes.continuousShape => { : DistTypes.continuousShape => {
// If we're adding the distributions, and we know the total of each, then we // 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. // can just sum them up. Otherwise, all bets are off.
let combinedIntegralSum = let combinedIntegralSum =
switch (fn, t1.knownIntegralSum, t2.knownIntegralSum) { Common.combineIntegralSums(
| (_, None, _) knownIntegralSumsFn,
| (_, _, None) => None t1.knownIntegralSum,
| ((+.), Some(s1), Some(s2)) => Some(s1 +. s2) t2.knownIntegralSum,
}; );
make( make(
`Linear, `Linear,
@ -102,7 +124,6 @@ module Continuous {
combinedIntegralSum, combinedIntegralSum,
); );
}; };
let reduce = (fn, items) => items |> E.A.fold_left(combine(fn), empty);
let toLinear = (t: t): option(t) => { let toLinear = (t: t): option(t) => {
switch (t) { switch (t) {
@ -114,7 +135,19 @@ module Continuous {
}; };
}; };
let shapeFn = (fn, t: t) => t |> getShape |> fn; let shapeFn = (fn, t: t) => t |> getShape |> fn;
let updateKnownIntegralSum = (knownIntegralSum, t: t): t => ({...t, knownIntegralSum}); 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);
// Contracts every point in the continuous xyShape into a single dirac-Delta-like point, // 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. // using the centerpoints between adjacent xs and the area under each trapezoid.
@ -128,11 +161,18 @@ module Continuous {
Belt.Array.set( Belt.Array.set(
pointMassesY, pointMassesY,
x, x,
(xs[x + 1] -. xs[x]) *. ((ys[x] +. ys[x + 1]) /. 2.)); // = dx * (1/2) * (avgY) (xs[x + 1] -. xs[x]) *. ((ys[x] +. ys[x + 1]) /. 2.),
); // = dx * (1/2) * (avgY)
(); ();
}; };
{xyShape: {xs: xs, ys: pointMassesY}, knownIntegralSum: t.knownIntegralSum}; {
xyShape: {
xs,
ys: pointMassesY,
},
knownIntegralSum: t.knownIntegralSum,
};
}; };
/* Performs a discrete convolution between two continuous distributions A and B. /* Performs a discrete convolution between two continuous distributions A and B.
@ -153,18 +193,25 @@ module Continuous {
let t1n = t1s |> XYShape.T.length; let t1n = t1s |> XYShape.T.length;
let t2n = t2s |> XYShape.T.length; let t2n = t2s |> XYShape.T.length;
let outXYShapes: array(array((float, float))) = Belt.Array.makeUninitializedUnsafe(t1n); let outXYShapes: array(array((float, float))) =
Belt.Array.makeUninitializedUnsafe(t1n);
for (i in 0 to t1n - 1) { for (i in 0 to t1n - 1) {
// create a new distribution // create a new distribution
let dxyShape: array((float, float)) = Belt.Array.makeUninitializedUnsafe(t2n); let dxyShape: array((float, float)) =
Belt.Array.makeUninitializedUnsafe(t2n);
for (j in 0 to t2n - 1) { for (j in 0 to t2n - 1) {
let _ = Belt.Array.set(dxyShape, j, (fn(t1s.xs[i], t2s.xs[j]), t1s.ys[i] *. t2s.ys[j])); let _ =
Belt.Array.set(
dxyShape,
j,
(fn(t1s.xs[i], t2s.xs[j]), t1s.ys[i] *. t2s.ys[j]),
);
(); ();
} };
let _ = Belt.Array.set(outXYShapes, i, dxyShape); let _ = Belt.Array.set(outXYShapes, i, dxyShape);
(); ();
} };
let combinedIntegralSum = let combinedIntegralSum =
switch (t1.knownIntegralSum, t2.knownIntegralSum) { switch (t1.knownIntegralSum, t2.knownIntegralSum) {
@ -175,9 +222,9 @@ module Continuous {
outXYShapes outXYShapes
|> E.A.fmap(s => { |> E.A.fmap(s => {
let xyShape = XYShape.T.fromZippedArray(s); let xyShape = XYShape.T.fromZippedArray(s);
make(`Linear, xyShape, None); make(`Linear, xyShape, None);
}) })
|> reduce((+.)) |> reduce((+.))
|> updateKnownIntegralSum(combinedIntegralSum); |> updateKnownIntegralSum(combinedIntegralSum);
}; };
@ -185,35 +232,22 @@ module Continuous {
let convolve = (fn, t1: t, t2: t) => let convolve = (fn, t1: t, t2: t) =>
convolveWithDiscrete(fn, t1, toDiscretePointMasses(t2)); convolveWithDiscrete(fn, t1, toDiscretePointMasses(t2));
let mapY = (~knownIntegralSumFn=(previousKnownIntegralSum => None), fn, t: t) => { let mapY = (~knownIntegralSumFn=previousKnownIntegralSum => None, fn, t: t) => {
let u = E.O.bind(_, knownIntegralSumFn); let u = E.O.bind(_, knownIntegralSumFn);
let yMapFn = shapeMap(XYShape.T.mapY(fn)); let yMapFn = shapeMap(XYShape.T.mapY(fn));
t |> yMapFn |> updateKnownIntegralSum(u(t.knownIntegralSum)); t |> yMapFn |> updateKnownIntegralSum(u(t.knownIntegralSum));
}; };
let scaleBy = (~scale=1.0, ~knownIntegralSum=None, t: t): t => let scaleBy = (~scale=1.0, t: t): t => {
t |> mapY((r: float) => r *. scale) |> updateKnownIntegralSum(knownIntegralSum); t
|> mapY((r: float) => r *. scale)
let truncate = (leftCutoff: option(float), rightCutoff: option(float), t: t) => { |> updateKnownIntegralSum(
let truncatedZippedPairs = E.O.bind(t.knownIntegralSum, v => Some(scale *. v)),
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);
}; };
module T = module T =
Dist({ Dist({
type t = DistTypes.continuousShape; type t = DistTypes.continuousShape;
@ -236,12 +270,31 @@ module Continuous {
|> DistTypes.MixedPoint.makeContinuous; |> DistTypes.MixedPoint.makeContinuous;
}; };
// let combineWithFn = (t1: t, t2: t, fn: (float, float) => float) => { let truncate =
// switch(t1, t2){ (leftCutoff: option(float), rightCutoff: option(float), t: t) => {
// | ({interpolation: `Stepwise}, {interpolation: `Stepwise}) => 3.0 let truncatedZippedPairs =
// | ({interpolation: `Linear}, {interpolation: `Linear}) => 3.0 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);
};
// TODO: This should work with stepwise plots. // TODO: This should work with stepwise plots.
let integral = (~cache, t) => let integral = (~cache, t) =>
@ -272,9 +325,9 @@ module Continuous {
let toDiscrete = _ => None; let toDiscrete = _ => None;
let normalize = (t: t): t => { let normalize = (t: t): t => {
let continuousIntegralSum = integralEndY(~cache=None, t); t
|> scaleBy(~scale=1. /. integralEndY(~cache=None, t))
scaleBy(~scale=(1. /. continuousIntegralSum), ~knownIntegralSum=Some(1.0), t); |> updateKnownIntegralSum(Some(1.0));
}; };
let normalizedToContinuous = t => Some(t); // TODO: this should be normalized let normalizedToContinuous = t => Some(t); // TODO: this should be normalized
@ -316,40 +369,41 @@ module Discrete = {
let lastY = (t: t) => t |> getShape |> XYShape.T.lastY; let lastY = (t: t) => t |> getShape |> XYShape.T.lastY;
let combineIntegralSums = (combineFn: ((float, float) => option(float)), t1KnownIntegralSum: option(float), t2KnownIntegralSum: option(float)) => { let combine =
switch (t1KnownIntegralSum, t2KnownIntegralSum) { (
| (None, _) ~knownIntegralSumsFn,
| (_, None) => None fn,
| (Some(s1), Some(s2)) => combineFn(s1, s2) t1: DistTypes.discreteShape,
}; t2: DistTypes.discreteShape,
}; )
let combine = (combineIntegralSumsFn, fn, t1: DistTypes.discreteShape, t2: DistTypes.discreteShape)
: DistTypes.discreteShape => { : DistTypes.discreteShape => {
let combinedIntegralSum =
let combinedIntegralSum = combineIntegralSums(combineIntegralSumsFn, t1.knownIntegralSum, t2.knownIntegralSum); Common.combineIntegralSums(
knownIntegralSumsFn,
t1.knownIntegralSum,
t2.knownIntegralSum,
);
make( make(
XYShape.Combine.combine( XYShape.Combine.combine(
~xsSelection=ALL_XS, ~xsSelection=ALL_XS,
~xToYSelection=XYShape.XtoY.stepwiseIfAtX, ~xToYSelection=XYShape.XtoY.stepwiseIfAtX,
~fn, // stepwiseIfAtX returns option(float), so this fn needs to handle None, which is what the _default0 wrapper is for ~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
t1.xyShape, t1.xyShape,
t2.xyShape, t2.xyShape,
), ),
combinedIntegralSum, combinedIntegralSum,
); );
}; };
let _default0 = (fn, a, b) =>
fn(E.O.default(0.0, a), E.O.default(0.0, b));
let reduce = (fn, items) =>
items |> E.A.fold_left(combine((_, _) => None, _default0(fn)), empty);
// a special version of reduce that adds the results (which should be the most common case by far),
// and conveniently also adds the knownIntegralSums.
let reduceAdd = (fn, items) =>
items |> E.A.fold_left(combine((s1, s2) => Some(s1 +. s2), _default0((+.))), empty);
let updateKnownIntegralSum = (knownIntegralSum, t: t): t => ({...t, knownIntegralSum}); 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,
};
let convolve = (fn, t1: t, t2: t) => { let convolve = (fn, t1: t, t2: t) => {
let t1s = t1 |> getShape; let t1s = t1 |> getShape;
@ -357,7 +411,12 @@ module Discrete = {
let t1n = t1s |> XYShape.T.length; let t1n = t1s |> XYShape.T.length;
let t2n = t2s |> XYShape.T.length; let t2n = t2s |> XYShape.T.length;
let combinedIntegralSum = combineIntegralSums((s1, s2) => Some(s1 *. s2), t1.knownIntegralSum, t2.knownIntegralSum); let combinedIntegralSum =
Common.combineIntegralSums(
(s1, s2) => Some(s1 *. s2),
t1.knownIntegralSum,
t2.knownIntegralSum,
);
let xToYMap = E.FloatFloatMap.empty(); let xToYMap = E.FloatFloatMap.empty();
@ -368,8 +427,8 @@ module Discrete = {
let my = t1s.ys[i] *. t2s.ys[j]; let my = t1s.ys[i] *. t2s.ys[j];
let _ = Belt.MutableMap.set(xToYMap, x, cv +. my); let _ = Belt.MutableMap.set(xToYMap, x, cv +. my);
(); ();
} };
} };
let rxys = xToYMap |> E.FloatFloatMap.toArray |> XYShape.Zipped.sortByX; let rxys = xToYMap |> E.FloatFloatMap.toArray |> XYShape.Zipped.sortByX;
@ -378,25 +437,19 @@ module Discrete = {
make(convolvedShape, combinedIntegralSum); make(convolvedShape, combinedIntegralSum);
}; };
let mapY = (~knownIntegralSumFn=(previousKnownIntegralSum => None), fn, t: t) => { let mapY = (~knownIntegralSumFn=previousKnownIntegralSum => None, fn, t: t) => {
let u = E.O.bind(_, knownIntegralSumFn); let u = E.O.bind(_, knownIntegralSumFn);
let yMapFn = shapeMap(XYShape.T.mapY(fn)); let yMapFn = shapeMap(XYShape.T.mapY(fn));
t |> yMapFn |> updateKnownIntegralSum(u(t.knownIntegralSum)); t |> yMapFn |> updateKnownIntegralSum(u(t.knownIntegralSum));
}; };
let scaleBy = (~scale=1.0, ~knownIntegralSum=None, t: t): t => let scaleBy = (~scale=1.0, t: t): t => {
t |> mapY((r: float) => r *. scale) |> updateKnownIntegralSum(knownIntegralSum); t
|> mapY((r: float) => r *. scale)
let truncate = (leftCutoff: option(float), rightCutoff: option(float), t: t) => { |> updateKnownIntegralSum(
let truncatedShape = E.O.bind(t.knownIntegralSum, v => Some(scale *. v)),
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);
}; };
module T = module T =
@ -414,7 +467,8 @@ module Discrete = {
) )
}; };
let integralEndY = (~cache, t: t) => let integralEndY = (~cache, t: t) =>
t.knownIntegralSum |> E.O.default(t |> integral(~cache) |> Continuous.lastY); t.knownIntegralSum
|> E.O.default(t |> integral(~cache) |> Continuous.lastY);
let minX = shapeFn(XYShape.T.minX); let minX = shapeFn(XYShape.T.minX);
let maxX = shapeFn(XYShape.T.maxX); let maxX = shapeFn(XYShape.T.maxX);
let toDiscreteProbabilityMassFraction = _ => 1.0; let toDiscreteProbabilityMassFraction = _ => 1.0;
@ -424,9 +478,9 @@ module Discrete = {
let toDiscrete = t => Some(t); let toDiscrete = t => Some(t);
let normalize = (t: t): t => { let normalize = (t: t): t => {
let discreteIntegralSum = integralEndY(~cache=None, t); t
|> scaleBy(~scale=1. /. integralEndY(~cache=None, t))
scaleBy(~scale=(1. /. discreteIntegralSum), ~knownIntegralSum=Some(1.0), t); |> updateKnownIntegralSum(Some(1.0));
}; };
let normalizedToContinuous = _ => None; let normalizedToContinuous = _ => None;
@ -448,6 +502,21 @@ module Discrete = {
make(clippedShape, None); // if someone needs the sum, they'll have to recompute it make(clippedShape, None); // if someone needs the sum, they'll have to recompute it
}; };
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);
};
let xToY = (f, t) => let xToY = (f, t) =>
t t
|> getShape |> getShape
@ -477,53 +546,43 @@ module Discrete = {
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares); XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares);
}; };
}); });
}; };
// TODO: I think this shouldn't assume continuous/discrete are normalized to 1.0, and thus should not need the discreteProbabilityMassFraction being separate.
module Mixed = { module Mixed = {
type t = DistTypes.mixedShape; type t = DistTypes.mixedShape;
let make = (~continuous, ~discrete): t => { let make = (~continuous, ~discrete): t => {continuous, discrete};
continuous,
discrete,
};
let totalLength = (t: t): int => { let totalLength = (t: t): int => {
let continuousLength = t.continuous |> Continuous.getShape |> XYShape.T.length; let continuousLength =
t.continuous |> Continuous.getShape |> XYShape.T.length;
let discreteLength = t.discrete |> Discrete.getShape |> XYShape.T.length; let discreteLength = t.discrete |> Discrete.getShape |> XYShape.T.length;
continuousLength + discreteLength; continuousLength + discreteLength;
}; };
// TODO: Put into scaling module let scaleBy = (~scale=1.0, {discrete, continuous}: t): t => {
//let normalizeMixedPoint = (t, f) => f *. discreteProbabilityMassFraction;*/ let scaledDiscrete = Discrete.scaleBy(~scale, discrete);
let scaledContinuous = Continuous.scaleBy(~scale, continuous);
make(~discrete=scaledDiscrete, ~continuous=scaledContinuous);
};
//TODO: Warning: This currently computes the integral, which is expensive. let toContinuous = ({continuous}: t) => Some(continuous);
/*let scaleContinuousFn = let toDiscrete = ({discrete}: t) => Some(discrete);
({discreteProbabilityMassFraction}: DistTypes.mixedShape, f) =>
f *. (1.0 -. discreteProbabilityMassFraction); */
//TODO: Warning: This currently computes the integral, which is expensive. let combine = (~knownIntegralSumsFn, fn, t1: t, t2: t) => {
let reducedDiscrete =
[|t1, t2|]
|> E.A.fmap(toDiscrete)
|> E.A.O.concatSomes
|> Discrete.reduce(~knownIntegralSumsFn, fn);
// Normalizes to 1.0. let reducedContinuous =
/*let scaleContinuous = ({discreteProbabilityMassFraction}: t, continuous) => [|t1, t2|]
// get only the continuous, and scale it to the respective |> E.A.fmap(toContinuous)
continuous |> E.A.O.concatSomes
|> Continuous.T.scaleToIntegralSum( |> Continuous.reduce(~knownIntegralSumsFn, fn);
~intendedSum=1.0 -. discreteProbabilityMassFraction,
);
let scaleDiscrete = ({discreteProbabilityMassFraction}: t, disrete) => make(~discrete=reducedDiscrete, ~continuous=reducedContinuous);
disrete
|> Discrete.T.scaleToIntegralSum(
~intendedSum=discreteProbabilityMassFraction,
);*/
let truncate = (leftCutoff: option(float), rightCutoff: option(float), {discrete, continuous}: t) => {
let truncatedDiscrete = Discrete.truncate(leftCutoff, rightCutoff, discrete);
let truncatedContinuous = Continuous.truncate(leftCutoff, rightCutoff, continuous);
make(~discrete=truncatedDiscrete, ~continuous=truncatedContinuous);
}; };
module T = module T =
@ -536,19 +595,40 @@ module Mixed = {
let maxX = ({continuous, discrete}: t) => let maxX = ({continuous, discrete}: t) =>
max(Continuous.T.maxX(continuous), Discrete.T.maxX(discrete)); max(Continuous.T.maxX(continuous), Discrete.T.maxX(discrete));
let toShape = (t: t): DistTypes.shape => Mixed(t); let toShape = (t: t): DistTypes.shape => Mixed(t);
let toContinuous = ({continuous}: t) => Some(continuous);
let toDiscrete = ({discrete}: t) => Some(discrete); 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 normalize = (t: t): t => {
let continuousIntegralSum = Continuous.T.Integral.sum(~cache=None, t.continuous); let continuousIntegralSum =
let discreteIntegralSum = Discrete.T.Integral.sum(~cache=None, t.discrete); Continuous.T.Integral.sum(~cache=None, t.continuous);
let discreteIntegralSum =
Discrete.T.Integral.sum(~cache=None, t.discrete);
let totalIntegralSum = continuousIntegralSum +. discreteIntegralSum; let totalIntegralSum = continuousIntegralSum +. discreteIntegralSum;
let newContinuousSum = continuousIntegralSum /. totalIntegralSum; let newContinuousSum = continuousIntegralSum /. totalIntegralSum;
let newDiscreteSum = discreteIntegralSum /. totalIntegralSum; let newDiscreteSum = discreteIntegralSum /. totalIntegralSum;
let normalizedContinuous = Continuous.scaleBy(~scale=(1. /. newContinuousSum), ~knownIntegralSum=Some(newContinuousSum), t.continuous); let normalizedContinuous =
let normalizedDiscrete = Discrete.scaleBy(~scale=(1. /. newDiscreteSum), ~knownIntegralSum=Some(newDiscreteSum), t.discrete); 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); make(~continuous=normalizedContinuous, ~discrete=normalizedDiscrete);
}; };
@ -563,8 +643,10 @@ module Mixed = {
}; };
let toDiscreteProbabilityMassFraction = ({discrete, continuous}: t) => { let toDiscreteProbabilityMassFraction = ({discrete, continuous}: t) => {
let discreteIntegralSum = Discrete.T.Integral.sum(~cache=None, discrete); let discreteIntegralSum =
let continuousIntegralSum = Continuous.T.Integral.sum(~cache=None, continuous); Discrete.T.Integral.sum(~cache=None, discrete);
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, continuous);
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum; let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
discreteIntegralSum /. totalIntegralSum; discreteIntegralSum /. totalIntegralSum;
@ -575,20 +657,25 @@ module Mixed = {
// The easiest way to do this is to simply go by the previous probability masses. // 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 // The cache really isn't helpful here, because we would need two separate caches
let discreteIntegralSum = Discrete.T.Integral.sum(~cache=None, discrete); let discreteIntegralSum =
let continuousIntegralSum = Continuous.T.Integral.sum(~cache=None, continuous); Discrete.T.Integral.sum(~cache=None, discrete);
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, continuous);
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum; let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
let downsampledDiscrete = let downsampledDiscrete =
Discrete.T.downsample( Discrete.T.downsample(
int_of_float(float_of_int(count) *. (discreteIntegralSum /. totalIntegralSum)), int_of_float(
float_of_int(count) *. (discreteIntegralSum /. totalIntegralSum),
),
discrete, discrete,
); );
let downsampledContinuous = let downsampledContinuous =
Continuous.T.downsample( Continuous.T.downsample(
int_of_float( int_of_float(
float_of_int(count) *. (continuousIntegralSum /. totalIntegralSum), float_of_int(count)
*. (continuousIntegralSum /. totalIntegralSum),
), ),
continuous, continuous,
); );
@ -596,23 +683,20 @@ module Mixed = {
{discrete: downsampledDiscrete, continuous: downsampledContinuous}; {discrete: downsampledDiscrete, continuous: downsampledContinuous};
}; };
let normalizedToContinuous = (t: t) => let normalizedToContinuous = (t: t) => Some(normalize(t).continuous);
Some(normalize(t).continuous);
let normalizedToDiscrete = ({discrete} as t: t) => let normalizedToDiscrete = ({discrete} as t: t) =>
Some(normalize(t).discrete); Some(normalize(t).discrete);
let integral = let integral = (~cache, {continuous, discrete}: t) => {
(
~cache,
{continuous, discrete}: t,
) => {
switch (cache) { switch (cache) {
| Some(cache) => cache | Some(cache) => cache
| None => { | None =>
// note: if the underlying shapes aren't normalized, then these integrals won't be either! // 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 continuousIntegral =
let discreteIntegral = Discrete.T.Integral.get(~cache=None, discrete); Continuous.T.Integral.get(~cache=None, continuous);
let discreteIntegral =
Discrete.T.Integral.get(~cache=None, discrete);
Continuous.make( Continuous.make(
`Linear, `Linear,
@ -623,7 +707,6 @@ module Mixed = {
), ),
None, None,
); );
}
}; };
}; };
@ -648,14 +731,26 @@ module Mixed = {
// This pipes all ys (continuous and discrete) through fn. // 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 mapY is a linear operation, we might be able to update the knownIntegralSums as well;
// if not, they'll be set to None. // if not, they'll be set to None.
let mapY = (~knownIntegralSumFn=(previousIntegralSum => None), fn, {discrete, continuous}: t): t => { let mapY =
(
~knownIntegralSumFn=previousIntegralSum => None,
fn,
{discrete, continuous}: t,
)
: t => {
let u = E.O.bind(_, knownIntegralSumFn); let u = E.O.bind(_, knownIntegralSumFn);
let yMappedDiscrete = let yMappedDiscrete =
discrete |> Discrete.T.mapY(fn) |> Discrete.updateKnownIntegralSum(u(discrete.knownIntegralSum)); discrete
|> Discrete.T.mapY(fn)
|> Discrete.updateKnownIntegralSum(u(discrete.knownIntegralSum));
let yMappedContinuous = let yMappedContinuous =
continuous |> Continuous.T.mapY(fn) |> Continuous.updateKnownIntegralSum(u(continuous.knownIntegralSum)); continuous
|> Continuous.T.mapY(fn)
|> Continuous.updateKnownIntegralSum(
u(continuous.knownIntegralSum),
);
{ {
discrete: yMappedDiscrete, discrete: yMappedDiscrete,
@ -668,34 +763,55 @@ module Mixed = {
let continuousMean = Continuous.T.mean(continuous); let continuousMean = Continuous.T.mean(continuous);
// the combined mean is the weighted sum of the two: // the combined mean is the weighted sum of the two:
let discreteIntegralSum = Discrete.T.Integral.sum(~cache=None, discrete); let discreteIntegralSum =
let continuousIntegralSum = Continuous.T.Integral.sum(~cache=None, continuous); Discrete.T.Integral.sum(~cache=None, discrete);
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, continuous);
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum; let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
(discreteMean *. discreteIntegralSum +. continuousMean *. continuousIntegralSum) /. totalIntegralSum; (
discreteMean
*. discreteIntegralSum
+. continuousMean
*. continuousIntegralSum
)
/. totalIntegralSum;
}; };
let variance = ({discrete, continuous} as t: t): float => { let variance = ({discrete, continuous} as t: t): float => {
// the combined mean is the weighted sum of the two: // the combined mean is the weighted sum of the two:
let discreteIntegralSum = Discrete.T.Integral.sum(~cache=None, discrete); let discreteIntegralSum =
let continuousIntegralSum = Continuous.T.Integral.sum(~cache=None, continuous); Discrete.T.Integral.sum(~cache=None, discrete);
let continuousIntegralSum =
Continuous.T.Integral.sum(~cache=None, continuous);
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum; let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
let getMeanOfSquares = ({discrete, continuous} as t: t) => { let getMeanOfSquares = ({discrete, continuous} as t: t) => {
let discreteMean = discrete |> Discrete.shapeMap(XYShape.Analysis.squareXYShape) |> Discrete.T.mean; let discreteMean =
let continuousMean = continuous |> XYShape.Analysis.getMeanOfSquaresContinuousShape; discrete
(discreteMean *. discreteIntegralSum +. continuousMean *. continuousIntegralSum) /. totalIntegralSum |> Discrete.shapeMap(XYShape.Analysis.squareXYShape)
|> Discrete.T.mean;
let continuousMean =
continuous |> XYShape.Analysis.getMeanOfSquaresContinuousShape;
(
discreteMean
*. discreteIntegralSum
+. continuousMean
*. continuousIntegralSum
)
/. totalIntegralSum;
}; };
switch (discreteIntegralSum /. totalIntegralSum) { switch (discreteIntegralSum /. totalIntegralSum) {
| 1.0 => Discrete.T.variance(discrete) | 1.0 => Discrete.T.variance(discrete)
| 0.0 => Continuous.T.variance(continuous) | 0.0 => Continuous.T.variance(continuous)
| _ => XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares) | _ =>
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares)
}; };
}; };
}); });
let convolve = (fn: ((float, float) => float), t1: t, t2: t): t => { let convolve = (fn: (float, float) => float, t1: t, t2: t): t => {
// Discrete convolution can cause a huge increase in the number of samples, // Discrete convolution can cause a huge increase in the number of samples,
// so we'll first downsample. // so we'll first downsample.
@ -713,16 +829,21 @@ module Mixed = {
// continuous (*) continuous => continuous, but also // continuous (*) continuous => continuous, but also
// discrete (*) continuous => continuous (and vice versa). We have to take care of all combos and then combine them: // discrete (*) continuous => continuous (and vice versa). We have to take care of all combos and then combine them:
let ccConvResult = Continuous.convolve(fn, t1d.continuous, t2d.continuous); let ccConvResult =
let dcConvResult = Continuous.convolveWithDiscrete(fn, t2d.continuous, t1d.discrete); Continuous.convolve(fn, t1d.continuous, t2d.continuous);
let cdConvResult = Continuous.convolveWithDiscrete(fn, t1d.continuous, t2d.discrete); let dcConvResult =
let continuousConvResult = Continuous.reduce((+.), [|ccConvResult, dcConvResult, cdConvResult|]); Continuous.convolveWithDiscrete(fn, t2d.continuous, t1d.discrete);
let cdConvResult =
Continuous.convolveWithDiscrete(fn, t1d.continuous, t2d.discrete);
let continuousConvResult =
Continuous.reduce((+.), [|ccConvResult, dcConvResult, cdConvResult|]);
// ... finally, discrete (*) discrete => discrete, obviously: // ... finally, discrete (*) discrete => discrete, obviously:
let discreteConvResult = Discrete.convolve(fn, t1d.discrete, t2d.discrete); let discreteConvResult =
Discrete.convolve(fn, t1d.discrete, t2d.discrete);
{discrete: discreteConvResult, continuous: continuousConvResult}; {discrete: discreteConvResult, continuous: continuousConvResult};
} };
}; };
module Shape = { module Shape = {
@ -741,43 +862,31 @@ module Shape = {
| Continuous(m) => Continuous(fn3(m)) | Continuous(m) => Continuous(fn3(m))
}; };
let toMixed = mapToAll(( let toMixed =
m => m, mapToAll((
d => Mixed.make(~discrete=d, ~continuous=Continuous.empty), m => m,
c => Mixed.make(~discrete=Discrete.empty, ~continuous=c), d => Mixed.make(~discrete=d, ~continuous=Continuous.empty),
)); c => Mixed.make(~discrete=Discrete.empty, ~continuous=c),
));
let convolve = (fn, t1: t, t2: t): t => { let convolve = (fn, t1: t, t2: t): t => {
Mixed(Mixed.convolve(fn, toMixed(t1), toMixed(t2))); Mixed(Mixed.convolve(fn, toMixed(t1), toMixed(t2)));
}; };
let downsample = (~cache=None, i, t) => let combine = (~knownIntegralSumsFn=(_, _) => None, fn, t1: t, t2: t) =>
fmap(( switch ((t1, t2)) {
Mixed.T.downsample(i), | (Continuous(m1), Continuous(m2)) => DistTypes.Continuous(Continuous.combine(~knownIntegralSumsFn, fn, m1, m2))
Discrete.T.downsample(i), | (Discrete(m1), Discrete(m2)) => DistTypes.Discrete(Discrete.combine(~knownIntegralSumsFn, fn, m1, m2))
Continuous.T.downsample(i), | (m1, m2) => {
), t); DistTypes.Mixed(Mixed.combine(~knownIntegralSumsFn, fn, toMixed(m1), toMixed(m2)))
}
let normalize = };
fmap((
Mixed.T.normalize,
Discrete.T.normalize,
Continuous.T.normalize,
));
let truncate (leftCutoff, rightCutoff, t): t =
fmap((
Mixed.truncate(leftCutoff, rightCutoff),
Discrete.truncate(leftCutoff, rightCutoff),
Continuous.truncate(leftCutoff, rightCutoff),
), t);
module T = module T =
Dist({ Dist({
type t = DistTypes.shape; type t = DistTypes.shape;
type integral = DistTypes.continuousShape; type integral = DistTypes.continuousShape;
let xToY = (f: float) => let xToY = (f: float) =>
mapToAll(( mapToAll((
Mixed.T.xToY(f), Mixed.T.xToY(f),
@ -789,9 +898,31 @@ module Shape = {
let toContinuous = t => None; let toContinuous = t => None;
let toDiscrete = t => None; let toDiscrete = t => None;
let downsample = (~cache=None, i, t) => t;
let toDiscreteProbabilityMassFraction = t => 0.0;
let normalize = t => t; 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));
let toContinuous = let toContinuous =
mapToAll(( mapToAll((
Mixed.T.toContinuous, Mixed.T.toContinuous,
@ -853,7 +984,7 @@ module Shape = {
)); ));
}; };
let maxX = mapToAll((Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX)); let maxX = mapToAll((Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX));
let mapY = (~knownIntegralSumFn=(previousIntegralSum => None), fn) => let mapY = (~knownIntegralSumFn=previousIntegralSum => None, fn) =>
fmap(( fmap((
Mixed.T.mapY(~knownIntegralSumFn, fn), Mixed.T.mapY(~knownIntegralSumFn, fn),
Discrete.T.mapY(~knownIntegralSumFn, fn), Discrete.T.mapY(~knownIntegralSumFn, fn),
@ -935,14 +1066,18 @@ module DistPlus = {
let toDiscrete = shapeFn(Shape.T.toDiscrete); let toDiscrete = shapeFn(Shape.T.toDiscrete);
let normalize = (t: t): t => { let normalize = (t: t): t => {
let normalizedShape = let normalizedShape = t |> toShape |> Shape.T.normalize;
t |> toShape |> Shape.T.normalize;
t |> updateShape(normalizedShape);
t |> updateShape(normalizedShape);
// TODO: also adjust for domainIncludedProbabilityMass here. // TODO: also adjust for domainIncludedProbabilityMass here.
}; };
let truncate = (leftCutoff, rightCutoff, t: t): t => {
let truncatedShape = t |> toShape |> Shape.T.truncate(leftCutoff, rightCutoff);
t |> updateShape(truncatedShape);
};
// TODO: replace this with // TODO: replace this with
let normalizedToContinuous = (t: t) => { let normalizedToContinuous = (t: t) => {
t t
@ -980,7 +1115,13 @@ module DistPlus = {
let downsample = (~cache=None, i, t): t => let downsample = (~cache=None, i, t): t =>
updateShape(t |> toShape |> Shape.T.downsample(i), t); updateShape(t |> toShape |> Shape.T.downsample(i), t);
// todo: adjust for limit, maybe? // todo: adjust for limit, maybe?
let mapY = (~knownIntegralSumFn=(previousIntegralSum => None), fn, {shape, _} as t: t): t => let mapY =
(
~knownIntegralSumFn=previousIntegralSum => None,
fn,
{shape, _} as t: t,
)
: t =>
Shape.T.mapY(~knownIntegralSumFn, fn, shape) |> updateShape(_, t); Shape.T.mapY(~knownIntegralSumFn, fn, shape) |> updateShape(_, t);
let integralEndY = (~cache as _, t: t) => let integralEndY = (~cache as _, t: t) =>

View File

@ -1,13 +1,13 @@
let truncateIfShould = let downsampleIfShould =
( (
{recommendedLength, shouldTruncate}: RenderTypes.DistPlusRenderer.inputs, {recommendedLength, shouldDownsample}: RenderTypes.DistPlusRenderer.inputs,
outputs: RenderTypes.ShapeRenderer.Combined.outputs, outputs: RenderTypes.ShapeRenderer.Combined.outputs,
dist, dist,
) => { ) => {
let willTruncate = let willDownsample =
shouldTruncate shouldDownsample
&& RenderTypes.ShapeRenderer.Combined.methodUsed(outputs) == `Sampling; && RenderTypes.ShapeRenderer.Combined.methodUsed(outputs) == `Sampling;
willTruncate ? dist |> Distributions.DistPlus.T.truncate(recommendedLength) : dist; willDownsample ? dist |> Distributions.DistPlus.T.downsample(recommendedLength) : dist;
}; };
let run = let run =
@ -21,7 +21,7 @@ let run =
~guesstimatorString=Some(inputs.distPlusIngredients.guesstimatorString), ~guesstimatorString=Some(inputs.distPlusIngredients.guesstimatorString),
(), (),
) )
|> Distributions.DistPlus.T.scaleToIntegralSum(~intendedSum=1.0); |> Distributions.DistPlus.T.normalize;
let outputs = let outputs =
ShapeRenderer.run({ ShapeRenderer.run({
samplingInputs: inputs.samplingInputs, samplingInputs: inputs.samplingInputs,
@ -32,6 +32,6 @@ let run =
}); });
let shape = outputs |> RenderTypes.ShapeRenderer.Combined.getShape; let shape = outputs |> RenderTypes.ShapeRenderer.Combined.getShape;
let dist = let dist =
shape |> E.O.fmap(toDist) |> E.O.fmap(truncateIfShould(inputs, outputs)); shape |> E.O.fmap(toDist) |> E.O.fmap(downsampleIfShould(inputs, outputs));
RenderTypes.DistPlusRenderer.Outputs.make(outputs, dist); RenderTypes.DistPlusRenderer.Outputs.make(outputs, dist);
}; };

View File

@ -75,7 +75,7 @@ module ShapeRenderer = {
module DistPlusRenderer = { module DistPlusRenderer = {
let defaultRecommendedLength = 10000; let defaultRecommendedLength = 10000;
let defaultShouldTruncate = true; let defaultShouldDownsample = true;
type ingredients = { type ingredients = {
guesstimatorString: string, guesstimatorString: string,
domain: DistTypes.domain, domain: DistTypes.domain,
@ -85,7 +85,7 @@ module DistPlusRenderer = {
distPlusIngredients: ingredients, distPlusIngredients: ingredients,
samplingInputs: ShapeRenderer.Sampling.inputs, samplingInputs: ShapeRenderer.Sampling.inputs,
recommendedLength: int, recommendedLength: int,
shouldTruncate: bool, shouldDownsample: bool,
}; };
module Ingredients = { module Ingredients = {
let make = let make =
@ -105,7 +105,7 @@ module DistPlusRenderer = {
( (
~samplingInputs=ShapeRenderer.Sampling.Inputs.empty, ~samplingInputs=ShapeRenderer.Sampling.Inputs.empty,
~recommendedLength=defaultRecommendedLength, ~recommendedLength=defaultRecommendedLength,
~shouldTruncate=defaultShouldTruncate, ~shouldDownsample=defaultShouldDownsample,
~distPlusIngredients, ~distPlusIngredients,
(), (),
) )
@ -113,7 +113,7 @@ module DistPlusRenderer = {
distPlusIngredients, distPlusIngredients,
samplingInputs, samplingInputs,
recommendedLength, recommendedLength,
shouldTruncate, shouldDownsample,
}; };
type outputs = { type outputs = {
shapeRenderOutputs: ShapeRenderer.Combined.outputs, shapeRenderOutputs: ShapeRenderer.Combined.outputs,

View File

@ -154,16 +154,17 @@ module MathAdtToDistDst = {
weights: option(array(float)), weights: option(array(float)),
) => { ) => {
let weights = weights |> E.O.default([||]); let weights = weights |> E.O.default([||]);
let dists =
/*let dists: =
args args
|> E.A.fmap( |> E.A.fmap(
fun fun
| Ok(a) => a | Ok(a) => a
| Error(e) => Error(e) | Error(e) => Error(e)
); );*/
let firstWithError = dists |> Belt.Array.getBy(_, Belt.Result.isError); let firstWithError = args |> Belt.Array.getBy(_, Belt.Result.isError);
let withoutErrors = dists |> E.A.fmap(E.R.toOption) |> E.A.O.concatSomes; let withoutErrors = args |> E.A.fmap(E.R.toOption) |> E.A.O.concatSomes;
switch (firstWithError) { switch (firstWithError) {
| Some(Error(e)) => Error(e) | Some(Error(e)) => Error(e)
@ -174,16 +175,16 @@ module MathAdtToDistDst = {
|> E.A.fmapi((index, t) => { |> E.A.fmapi((index, t) => {
let w = weights |> E.A.get(_, index) |> E.O.default(1.0); let w = weights |> E.A.get(_, index) |> E.O.default(1.0);
`Operation(`ScaleBy(`Multiply, t, `DistData(`Symbolic(`Float(w))))) `Operation(`ScaleOperation(`Multiply, t, `DistData(`Symbolic(`Float(w)))))
}); });
let pointwiseSum = components let pointwiseSum = components
|> Js.Array.sliceFrom(1) |> Js.Array.sliceFrom(1)
|> E.A.fold_left((acc, x) => { |> E.A.fold_left((acc, x) => {
`PointwiseSum(acc, x) `Operation(`PointwiseOperation(`Add, acc, x))
}, E.A.unsafe_get(components, 0)) }, E.A.unsafe_get(components, 0))
Ok(`Normalize(pointwiseSum)) Ok(`Operation(`Normalize(pointwiseSum)))
} }
}; };
}; };
@ -254,21 +255,21 @@ module MathAdtToDistDst = {
args args
|> E.A.fmap(functionParser) |> E.A.fmap(functionParser)
|> (fun |> (fun
| [|Ok(l), Ok(r)|] => Ok(`Combination(l, r, `AddOperation)) | [|Ok(l), Ok(r)|] => Ok(`Operation(`StandardOperation(`Add, l, r)))
| _ => Error("Addition needs two operands")) | _ => Error("Addition needs two operands"))
} }
| Fn({name: "subtract", args}) => { | Fn({name: "subtract", args}) => {
args args
|> E.A.fmap(functionParser) |> E.A.fmap(functionParser)
|> (fun |> (fun
| [|Ok(l), Ok(r)|] => Ok(`Combination(l, r, `SubtractOperation)) | [|Ok(l), Ok(r)|] => Ok(`Operation(`StandardOperation(`Subtract, l, r)))
| _ => Error("Subtraction needs two operands")) | _ => Error("Subtraction needs two operands"))
} }
| Fn({name: "multiply", args}) => { | Fn({name: "multiply", args}) => {
args args
|> E.A.fmap(functionParser) |> E.A.fmap(functionParser)
|> (fun |> (fun
| [|Ok(l), Ok(r)|] => Ok(`Combination(l, r, `MultiplyOperation)) | [|Ok(l), Ok(r)|] => Ok(`Operation(`StandardOperation(`Multiply, l, r)))
| _ => Error("Multiplication needs two operands")) | _ => Error("Multiplication needs two operands"))
} }
| Fn({name: "divide", args}) => { | Fn({name: "divide", args}) => {
@ -276,28 +277,37 @@ module MathAdtToDistDst = {
|> E.A.fmap(functionParser) |> E.A.fmap(functionParser)
|> (fun |> (fun
| [|Ok(l), Ok(`DistData(`Symbolic(`Float(0.0))))|] => Error("Division by zero") | [|Ok(l), Ok(`DistData(`Symbolic(`Float(0.0))))|] => Error("Division by zero")
| [|Ok(l), Ok(r)|] => Ok(`Combination(l, r, `DivideOperation)) | [|Ok(l), Ok(r)|] => Ok(`Operation(`StandardOperation(`Divide, l, r)))
| _ => Error("Division needs two operands")) | _ => Error("Division needs two operands"))
} }
| Fn({name: "pow", args}) => { | Fn({name: "pow", args}) => {
args args
|> E.A.fmap(functionParser) |> E.A.fmap(functionParser)
|> (fun |> (fun
| [|Ok(l), Ok(r)|] => Ok(`Combination(l, r, `ExponentiateOperation)) | [|Ok(l), Ok(r)|] => Ok(`Operation(`StandardOperation(`Exponentiate, l, r)))
| _ => Error("Division needs two operands")
| _ => Error("Exponentiations needs two operands")) | _ => Error("Exponentiations needs two operands"))
} }
| Fn({name: "leftTruncate", args}) => { | Fn({name: "leftTruncate", args}) => {
args args
|> E.A.fmap(functionParser) |> E.A.fmap(functionParser)
|> (fun |> (fun
| [|Ok(l), Ok(`DistData(`Symbolic(`Float(r))))|] => Ok(`LeftTruncate(l, r)) | [|Ok(d), Ok(`DistData(`Symbolic(`Float(lc))))|] => Ok(`Operation(`Truncate(Some(lc), None, d)))
| _ => Error("leftTruncate needs two arguments: the expression and the cutoff")) | _ => Error("leftTruncate needs two arguments: the expression and the cutoff"))
} }
| Fn({name: "rightTruncate", args}) => { | Fn({name: "rightTruncate", args}) => {
args args
|> E.A.fmap(functionParser) |> E.A.fmap(functionParser)
|> (fun |> (fun
| [|Ok(l), Ok(`DistData(`Symbolic(`Float(r))))|] => Ok(`RightTruncate(l, r)) | [|Ok(d), Ok(`DistData(`Symbolic(`Float(rc))))|] => Ok(`Operation(`Truncate(None, Some(rc), d)))
| _ => Error("rightTruncate needs two arguments: the expression and the cutoff"))
}
| Fn({name: "truncate", args}) => {
args
|> E.A.fmap(functionParser)
|> (fun
| [|Ok(d), Ok(`DistData(`Symbolic(`Float(lc)))), Ok(`DistData(`Symbolic(`Float(rc))))|] => Ok(`Operation(`Truncate(Some(lc), Some(rc), d)))
// TODO: allow on-the-fly evaluations of FloatFromDists to be used as cutoff arguments here.
| _ => Error("rightTruncate needs two arguments: the expression and the cutoff")) | _ => Error("rightTruncate needs two arguments: the expression and the cutoff"))
} }
| Fn({name}) => Error(name ++ ": function not supported") | Fn({name}) => Error(name ++ ": function not supported")

View File

@ -1,69 +1,60 @@
/* This module represents a tree node. */ /* This module represents a tree node. */
/* TreeNodes are either Data (i.e. symbolic or rendered distributions) or Operations. */ type distData = [
type treeNode = [
| `DistData(distData)
| `Operation(operation)
] and distData = [
| `Symbolic(SymbolicDist.dist) | `Symbolic(SymbolicDist.dist)
| `RenderedShape(DistTypes.shape) | `RenderedShape(DistTypes.shape)
] and operation = [ ];
// binary operations
| `StandardOperation(standardOperation, treeNode, treeNode) type standardOperation = [
| `PointwiseOperation(pointwiseOperation, treeNode, treeNode)
| `ScaleOperation(scaleOperation, treeNode, scaleBy)
// unary operations
| `Render(treeNode) // always evaluates to `DistData(`RenderedShape(...))
| `Truncate(leftCutoff, rightCutoff, treeNode)
| `Normalize(treeNode)
// direct evaluations of dists (e.g. cdf, sample)
| `FloatFromDist(distToFloatOperation, treeNode)
] and standardOperation = [
| `Add | `Add
| `Multiply | `Multiply
| `Subtract | `Subtract
| `Divide | `Divide
| `Exponentiate | `Exponentiate
] and pointwiseOperation = [ ];
| `Add type pointwiseOperation = [ | `Add | `Multiply];
| `Multiply type scaleOperation = [ | `Multiply | `Exponentiate | `Log];
] and scaleOperation = [ type distToFloatOperation = [ | `Pdf(float) | `Inv(float) | `Mean | `Sample];
| `Multiply
| `Log /* TreeNodes are either Data (i.e. symbolic or rendered distributions) or Operations. */
type treeNode = [
| `DistData(distData) // a leaf node that describes a distribution
| `Operation(operation) // an operation on two child nodes
] ]
and scaleBy = treeNode and leftCutoff = option(float) and rightCutoff = option(float) and operation = [
and distToFloatOperation = [ | // binary operations
| `Pdf(float) `StandardOperation(
| `Cdf(float) standardOperation,
| `Inv(float) treeNode,
| `Sample treeNode,
)
// unary operations
| `PointwiseOperation(pointwiseOperation, treeNode, treeNode) // always evaluates to `DistData(`RenderedShape(...))
| `ScaleOperation(scaleOperation, treeNode, treeNode) // always evaluates to `DistData(`RenderedShape(...))
| `Render(treeNode) // always evaluates to `DistData(`RenderedShape(...))
| `Truncate // always evaluates to `DistData(`RenderedShape(...))
(
option(float),
option(float),
treeNode,
) // leftCutoff and rightCutoff
| `Normalize // always evaluates to `DistData(`RenderedShape(...))
// leftCutoff and rightCutoff
(
treeNode,
)
| `FloatFromDist // always evaluates to `DistData(`RenderedShape(...))
// leftCutoff and rightCutoff
(
distToFloatOperation,
treeNode,
)
]; ];
module TreeNode = { module TreeNode = {
type t = treeNode; type t = treeNode;
type simplifier = treeNode => result(treeNode, string); type simplifier = treeNode => result(treeNode, string);
type renderParams = {
operationToDistData: (int, operation) => result(t, string),
sampleCount: int,
}
let rec renderToShape = (renderParams, t: t): result(DistTypes.shape, string) => {
switch (t) {
| `DistData(`RenderedShape(s)) => Ok(s) // already a rendered shape, we're done here
| `DistData(`Symbolic(d)) =>
switch (d) {
| `Float(v) =>
Ok(Discrete(Distributions.Discrete.make({xs: [|v|], ys: [|1.0|]}, Some(1.0))));
| _ =>
let xs = SymbolicDist.GenericDistFunctions.interpolateXs(~xSelection=`ByWeight, d, renderParams.sampleCount);
let ys = xs |> E.A.fmap(x => SymbolicDist.GenericDistFunctions.pdf(x, d));
Ok(Continuous(Distributions.Continuous.make(`Linear, {xs, ys}, Some(1.0))));
}
| `Operation(op) => E.R.bind(renderParams.operationToDistData(renderParams.sampleCount, op), renderToShape(renderParams))
};
};
/* The following modules encapsulate everything we can do with /* The following modules encapsulate everything we can do with
* different kinds of operations. */ * different kinds of operations. */
@ -154,207 +145,328 @@ module TreeNode = {
}; };
}; };
let evaluateNumerically = (standardOp, renderParams, t1, t2) => { let evaluateNumerically = (standardOp, operationToDistData, t1, t2) => {
let func = funcFromOp(standardOp); let func = funcFromOp(standardOp);
// TODO: downsample the two shapes // force rendering into shapes
let renderedShape1 = t1 |> renderToShape(renderParams); let renderedShape1 = operationToDistData(`Render(t1));
let renderedShape2 = t2 |> renderToShape(renderParams); let renderedShape2 = operationToDistData(`Render(t2));
// This will most likely require a mixed switch (renderedShape1, renderedShape2) {
| (
switch ((renderedShape1, renderedShape2)) { Ok(`DistData(`RenderedShape(s1))),
| (Error(e1), _) => Error(e1) Ok(`DistData(`RenderedShape(s2))),
| (_, Error(e2)) => Error(e2) ) =>
| (Ok(s1), Ok(s2)) => Ok(`DistData(`RenderedShape(Distributions.Shape.convolve(func, s1, s2)))) Ok(
`DistData(
`RenderedShape(Distributions.Shape.convolve(func, s1, s2)),
),
)
| (Error(e1), _) => Error(e1)
| (_, Error(e2)) => Error(e2)
| _ => Error("Could not render shapes.")
}; };
}; };
let evaluateToDistData = let evaluateToDistData =
(standardOp: standardOperation, renderParams, t1: t, t2: t): result(treeNode, string) => (standardOp: standardOperation, operationToDistData, t1: t, t2: t)
: result(treeNode, string) =>
standardOp standardOp
|> Simplify.attempt(_, t1, t2) |> Simplify.attempt(_, t1, t2)
|> E.R.bind( |> E.R.bind(
_, _,
fun fun
| `DistData(d) => Ok(`DistData(d)) // the analytical simplifaction worked, nice! | `DistData(d) => Ok(`DistData(d)) // the analytical simplifaction worked, nice!
| `Operation(_) => // if not, run the convolution | `Operation(_) =>
evaluateNumerically(standardOp, renderParams, t1, t2), // if not, run the convolution
evaluateNumerically(standardOp, operationToDistData, t1, t2),
); );
}; };
module ScaleOperation = { module ScaleOperation = {
let rec mean = (renderParams, t: t): result(float, string) => {
switch (t) {
| `DistData(`RenderedShape(s)) => Ok(Distributions.Shape.T.mean(s))
| `DistData(`Symbolic(s)) => SymbolicDist.GenericDistFunctions.mean(s)
// evaluating the operation returns result(treeNode(distData)). We then want to make sure
| `Operation(op) => E.R.bind(renderParams.operationToDistData(renderParams.sampleCount, op), mean(renderParams))
}
};
let fnFromOp = let fnFromOp =
fun fun
| `Multiply => (*.) | `Multiply => ( *. )
| `Log => ((a, b) => ( log(a) /. log(b) )); | `Exponentiate => ( ** )
| `Log => ((a, b) => log(a) /. log(b));
let knownIntegralSumFnFromOp = let knownIntegralSumFnFromOp =
fun fun
| `Multiply => (a, b) => Some(a *. b) | `Multiply => ((a, b) => Some(a *. b))
| `Exponentiate => ((_, _) => None)
| `Log => ((_, _) => None); | `Log => ((_, _) => None);
let evaluateToDistData = (scaleOp, renderParams, t, scaleBy) => { let evaluateToDistData = (scaleOp, operationToDistData, t, scaleBy) => {
// scaleBy has to be a single float, otherwise we'll return an error.
let fn = fnFromOp(scaleOp); let fn = fnFromOp(scaleOp);
let knownIntegralSumFn = knownIntegralSumFnFromOp(scaleOp); let knownIntegralSumFn = knownIntegralSumFnFromOp(scaleOp);
let renderedShape = t |> renderToShape(renderParams);
let scaleByMeanValue = mean(renderParams, scaleBy);
switch ((renderedShape, scaleByMeanValue)) { let renderedShape = operationToDistData(`Render(t));
switch (renderedShape, scaleBy) {
| (Error(e1), _) => Error(e1) | (Error(e1), _) => Error(e1)
| (_, Error(e2)) => Error(e2) | (
| (Ok(rs), Ok(sm)) => Ok(`DistData(`RenderedShape(rs))),
Ok(`DistData(`RenderedShape(Distributions.Shape.T.mapY(~knownIntegralSumFn=knownIntegralSumFn(sm), fn(sm), rs)))) `DistData(`Symbolic(`Float(sm))),
} ) =>
Ok(
`DistData(
`RenderedShape(
Distributions.Shape.T.mapY(
~knownIntegralSumFn=knownIntegralSumFn(sm),
fn(sm),
rs,
),
),
),
)
| (_, _) => Error("Can only scale by float values.")
};
}; };
}; };
module PointwiseOperation = { module PointwiseOperation = {
let funcFromOp: (pointwiseOperation => ((float, float) => float)) = let pointwiseAdd = (operationToDistData, t1, t2) => {
fun let renderedShape1 = operationToDistData(`Render(t1));
| `Add => (+.) let renderedShape2 = operationToDistData(`Render(t2));
| `Multiply => ( *. );
let evaluateToDistData = (pointwiseOp, renderParams, t1, t2) => { switch ((renderedShape1, renderedShape2)) {
let func = funcFromOp(pointwiseOp); | (Error(e1), _) => Error(e1)
let renderedShape1 = t1 |> renderToShape(renderParams); | (_, Error(e2)) => Error(e2)
let renderedShape2 = t2 |> renderToShape(renderParams); | (Ok(`DistData(`RenderedShape(rs1))), Ok(`DistData(`RenderedShape(rs2)))) => Ok(`DistData(`RenderedShape(Distributions.Shape.combine(~knownIntegralSumsFn=(a, b) => Some(a +. b), (+.), rs1, rs2))))
| _ => Error("Could not perform pointwise addition.")
};
};
// TODO: figure out integral, diff between pointwiseAdd and pointwiseProduct and other stuff let pointwiseMultiply = (operationToDistData, t1, t2) => {
// Distributions.Shape.reduce(func, renderedShape1, renderedShape2); // TODO: construct a function that we can easily sample from, to construct
// a RenderedShape. Use the xMin and xMax of the rendered shapes to tell the sampling function where to look.
Error("Pointwise multiplication not yet supported.");
};
Error("Pointwise operations currently not supported.") let evaluateToDistData = (pointwiseOp, operationToDistData, t1, t2) => {
switch (pointwiseOp) {
| `Add => pointwiseAdd(operationToDistData, t1, t2)
| `Multiply => pointwiseMultiply(operationToDistData, t1, t2)
}
}; };
}; };
module Truncate = { module Truncate = {
module Simplify = { module Simplify = {
let tryTruncatingNothing: simplifier = fun let tryTruncatingNothing: simplifier =
| `Operation(`Truncate(None, None, `DistData(d))) => Ok(`DistData(d)) fun
| t => Ok(t); | `Operation(`Truncate(None, None, `DistData(d))) =>
Ok(`DistData(d))
| t => Ok(t);
let tryTruncatingUniform: simplifier = fun let tryTruncatingUniform: simplifier =
| `Operation(`Truncate(lc, rc, `DistData(`Symbolic(`Uniform(u))))) => { fun
// just create a new Uniform distribution | `Operation(`Truncate(lc, rc, `DistData(`Symbolic(`Uniform(u))))) => {
let newLow = max(E.O.default(neg_infinity, lc), u.low); // just create a new Uniform distribution
let newHigh = min(E.O.default(infinity, rc), u.high); let newLow = max(E.O.default(neg_infinity, lc), u.low);
Ok(`DistData(`Symbolic(`Uniform({low: newLow, high: newHigh})))); let newHigh = min(E.O.default(infinity, rc), u.high);
} Ok(
| t => Ok(t); `DistData(`Symbolic(`Uniform({low: newLow, high: newHigh}))),
);
}
| t => Ok(t);
let attempt = (leftCutoff, rightCutoff, t): result(treeNode, string) => { let attempt = (leftCutoff, rightCutoff, t): result(treeNode, string) => {
let originalTreeNode = `Operation(`Truncate(leftCutoff, rightCutoff, t)); let originalTreeNode =
`Operation(`Truncate((leftCutoff, rightCutoff, t)));
originalTreeNode originalTreeNode
|> tryTruncatingNothing |> tryTruncatingNothing
|> E.R.bind(_, tryTruncatingUniform); |> E.R.bind(_, tryTruncatingUniform);
}; };
}; };
let evaluateNumerically = (leftCutoff, rightCutoff, renderParams, t) => { let evaluateNumerically =
(leftCutoff, rightCutoff, operationToDistData, t) => {
// TODO: use named args in renderToShape; if we're lucky we can at least get the tail // TODO: use named args in renderToShape; if we're lucky we can at least get the tail
// of a distribution we otherwise wouldn't get at all // of a distribution we otherwise wouldn't get at all
let renderedShape = t |> renderToShape(renderParams); let renderedShape = operationToDistData(`Render(t));
E.R.bind(renderedShape, rs => { switch (renderedShape) {
let truncatedShape = rs |> Distributions.Shape.truncate(leftCutoff, rightCutoff); | Ok(`DistData(`RenderedShape(rs))) =>
let truncatedShape =
rs |> Distributions.Shape.T.truncate(leftCutoff, rightCutoff);
Ok(`DistData(`RenderedShape(rs))); Ok(`DistData(`RenderedShape(rs)));
}); | Error(e1) => Error(e1)
| _ => Error("Could not truncate distribution.")
};
}; };
let evaluateToDistData = (leftCutoff: option(float), rightCutoff: option(float), renderParams, t: treeNode): result(treeNode, string) => { let evaluateToDistData =
(
leftCutoff: option(float),
rightCutoff: option(float),
operationToDistData,
t: treeNode,
)
: result(treeNode, string) => {
t t
|> Simplify.attempt(leftCutoff, rightCutoff) |> Simplify.attempt(leftCutoff, rightCutoff)
|> E.R.bind( |> E.R.bind(
_, _,
fun fun
| `DistData(d) => Ok(`DistData(d)) // the analytical simplifaction worked, nice! | `DistData(d) => Ok(`DistData(d)) // the analytical simplifaction worked, nice!
| `Operation(_) => evaluateNumerically(leftCutoff, rightCutoff, renderParams, t), | `Operation(_) =>
evaluateNumerically(
leftCutoff,
rightCutoff,
operationToDistData,
t,
),
); // if not, run the convolution ); // if not, run the convolution
}; };
}; };
module Normalize = { module Normalize = {
let rec evaluateToDistData = (renderParams, t: treeNode): result(treeNode, string) => { let rec evaluateToDistData =
(operationToDistData, t: treeNode): result(treeNode, string) => {
switch (t) { switch (t) {
| `DistData(`Symbolic(_)) => Ok(t) | `DistData(`Symbolic(_)) => Ok(t)
| `DistData(`RenderedShape(s)) => { | `DistData(`RenderedShape(s)) =>
let normalized = Distributions.Shape.normalize(s); let normalized = Distributions.Shape.T.normalize(s);
Ok(`DistData(`RenderedShape(normalized))); Ok(`DistData(`RenderedShape(normalized)));
} | `Operation(op) =>
| `Operation(op) => E.R.bind(renderParams.operationToDistData(renderParams.sampleCount, op), evaluateToDistData(renderParams)) E.R.bind(
} operationToDistData(op),
} evaluateToDistData(operationToDistData),
)
};
};
}; };
module FloatFromDist = { module FloatFromDist = {
let evaluateFromSymbolic = (distToFloatOp: distToFloatOperation, s) => { let evaluateFromSymbolic = (distToFloatOp: distToFloatOperation, s) => {
let value = switch (distToFloatOp) { let value =
| `Pdf(f) => SymbolicDist.GenericDistFunctions.pdf(f, s) switch (distToFloatOp) {
| `Cdf(f) => 0.0 | `Pdf(f) => Ok(SymbolicDist.GenericDistFunctions.pdf(f, s))
| `Inv(f) => SymbolicDist.GenericDistFunctions.inv(f, s) | `Inv(f) => Ok(SymbolicDist.GenericDistFunctions.inv(f, s))
| `Sample => SymbolicDist.GenericDistFunctions.sample(s) | `Sample => Ok(SymbolicDist.GenericDistFunctions.sample(s))
} | `Mean => SymbolicDist.GenericDistFunctions.mean(s)
Ok(`DistData(`Symbolic(`Float(value)))); };
E.R.bind(value, v => Ok(`DistData(`Symbolic(`Float(v)))));
}; };
let evaluateFromRenderedShape = (distToFloatOp: distToFloatOperation, rs: DistTypes.shape): result(treeNode, string) => { let evaluateFromRenderedShape =
// evaluate the pdf, cdf, get sample, etc. from the renderedShape rs (distToFloatOp: distToFloatOperation, rs: DistTypes.shape)
// Should be a float like Ok(`DistData(`Symbolic(Float(0.0)))); : result(treeNode, string) => {
Error("Float from dist is not yet implemented."); Ok(`DistData(`Symbolic(`Float(Distributions.Shape.T.mean(rs)))));
}; };
let rec evaluateToDistData = (distToFloatOp: distToFloatOperation, renderParams, t: treeNode): result(treeNode, string) => { let rec evaluateToDistData =
(
distToFloatOp: distToFloatOperation,
operationToDistData,
t: treeNode,
)
: result(treeNode, string) => {
switch (t) { switch (t) {
| `DistData(`Symbolic(s)) => evaluateFromSymbolic(distToFloatOp, s) // we want to evaluate the distToFloatOp on the symbolic dist | `DistData(`Symbolic(s)) => evaluateFromSymbolic(distToFloatOp, s) // we want to evaluate the distToFloatOp on the symbolic dist
| `DistData(`RenderedShape(rs)) => evaluateFromRenderedShape(distToFloatOp, rs) | `DistData(`RenderedShape(rs)) =>
| `Operation(op) => E.R.bind(renderParams.operationToDistData(renderParams.sampleCount, op), evaluateToDistData(distToFloatOp, renderParams)) evaluateFromRenderedShape(distToFloatOp, rs)
} | `Operation(op) =>
} E.R.bind(
operationToDistData(op),
evaluateToDistData(distToFloatOp, operationToDistData),
)
};
};
}; };
module Render = { module Render = {
let evaluateToRenderedShape = (renderParams, t: treeNode): result(t, string) => { let rec evaluateToRenderedShape =
E.R.bind(renderToShape(renderParams, t), rs => Ok(`DistData(`RenderedShape(rs)))); (operationToDistData: operation => result(t, string), sampleCount: int, t: treeNode)
} : result(t, string) => {
switch (t) {
| `DistData(`RenderedShape(s)) => Ok(`DistData(`RenderedShape(s))) // already a rendered shape, we're done here
| `DistData(`Symbolic(d)) =>
switch (d) {
| `Float(v) =>
Ok(
`DistData(
`RenderedShape(
Discrete(
Distributions.Discrete.make(
{xs: [|v|], ys: [|1.0|]},
Some(1.0),
),
),
),
),
)
| _ =>
let xs =
SymbolicDist.GenericDistFunctions.interpolateXs(
~xSelection=`ByWeight,
d,
sampleCount,
);
let ys =
xs |> E.A.fmap(x => SymbolicDist.GenericDistFunctions.pdf(x, d));
Ok(
`DistData(
`RenderedShape(
Continuous(
Distributions.Continuous.make(
`Linear,
{xs, ys},
Some(1.0),
),
),
),
),
);
}
| `Operation(op) =>
E.R.bind(
operationToDistData(op),
evaluateToRenderedShape(operationToDistData, sampleCount),
)
};
};
}; };
let rec operationToDistData = let rec operationToDistData =
(sampleCount: int, op: operation): result(t, string) => { (sampleCount: int, op: operation): result(t, string) => {
// the functions that convert the Operation nodes to DistData nodes need to // the functions that convert the Operation nodes to DistData nodes need to
// have a way to call this function on their children, if their children are themselves Operation nodes. // have a way to call this function on their children, if their children are themselves Operation nodes.
let renderParams: renderParams = {
operationToDistData: operationToDistData,
sampleCount: sampleCount,
};
switch (op) { switch (op) {
| `StandardOperation(standardOp, t1, t2) => | `StandardOperation(standardOp, t1, t2) =>
StandardOperation.evaluateToDistData( StandardOperation.evaluateToDistData(
standardOp, renderParams, t1, t2 // we want to give it the option to render or simply leave it as is standardOp,
operationToDistData(sampleCount),
t1,
t2 // we want to give it the option to render or simply leave it as is
) )
| `PointwiseOperation(pointwiseOp, t1, t2) => | `PointwiseOperation(pointwiseOp, t1, t2) =>
PointwiseOperation.evaluateToDistData( PointwiseOperation.evaluateToDistData(
pointwiseOp, pointwiseOp,
renderParams, operationToDistData(sampleCount),
t1, t1,
t2, t2,
) )
| `ScaleOperation(scaleOp, t, scaleBy) => | `ScaleOperation(scaleOp, t, scaleBy) =>
ScaleOperation.evaluateToDistData(scaleOp, renderParams, t, scaleBy) ScaleOperation.evaluateToDistData(
| `Truncate(leftCutoff, rightCutoff, t) => Truncate.evaluateToDistData(leftCutoff, rightCutoff, renderParams, t) scaleOp,
| `FloatFromDist(distToFloatOp, t) => FloatFromDist.evaluateToDistData(distToFloatOp, renderParams, t) operationToDistData(sampleCount),
| `Normalize(t) => Normalize.evaluateToDistData(renderParams, t) t,
| `Render(t) => Render.evaluateToRenderedShape(renderParams, t) scaleBy,
)
| `Truncate(leftCutoff, rightCutoff, t) =>
Truncate.evaluateToDistData(
leftCutoff,
rightCutoff,
operationToDistData(sampleCount),
t,
)
| `FloatFromDist(distToFloatOp, t) =>
FloatFromDist.evaluateToDistData(distToFloatOp, operationToDistData(sampleCount), t)
| `Normalize(t) => Normalize.evaluateToDistData(operationToDistData(sampleCount), t)
| `Render(t) =>
Render.evaluateToRenderedShape(operationToDistData(sampleCount), sampleCount, t)
}; };
}; };
@ -372,7 +484,8 @@ module TreeNode = {
}; };
let rec toString = (t: t): string => { let rec toString = (t: t): string => {
let stringFromStandardOperation = fun let stringFromStandardOperation =
fun
| `Add => " + " | `Add => " + "
| `Subtract => " - " | `Subtract => " - "
| `Multiply => " * " | `Multiply => " * "
@ -384,31 +497,53 @@ module TreeNode = {
| `Add => " .+ " | `Add => " .+ "
| `Multiply => " .* "; | `Multiply => " .* ";
let stringFromFloatFromDistOperation =
fun
| `Pdf(f) => "pdf(x=$f, "
| `Inv(f) => "inv(c=$f, "
| `Sample => "sample("
| `Mean => "mean(";
switch (t) { switch (t) {
| `DistData(`Symbolic(d)) => SymbolicDist.GenericDistFunctions.toString(d) | `DistData(`Symbolic(d)) =>
SymbolicDist.GenericDistFunctions.toString(d)
| `DistData(`RenderedShape(s)) => "[shape]" | `DistData(`RenderedShape(s)) => "[shape]"
| `Operation(`StandardOperation(op, t1, t2)) => toString(t1) ++ stringFromStandardOperation(op) ++ toString(t2) | `Operation(`StandardOperation(op, t1, t2)) =>
| `Operation(`PointwiseOperation(op, t1, t2)) => toString(t1) ++ stringFromPointwiseOperation(op) ++ toString(t2) toString(t1) ++ stringFromStandardOperation(op) ++ toString(t2)
| `Operation(`ScaleOperation(_scaleOp, t, scaleBy)) => toString(t) ++ " @ " ++ toString(scaleBy) | `Operation(`PointwiseOperation(op, t1, t2)) =>
toString(t1) ++ stringFromPointwiseOperation(op) ++ toString(t2)
| `Operation(`ScaleOperation(_scaleOp, t, scaleBy)) =>
toString(t) ++ " @ " ++ toString(scaleBy)
| `Operation(`Normalize(t)) => "normalize(" ++ toString(t) ++ ")" | `Operation(`Normalize(t)) => "normalize(" ++ toString(t) ++ ")"
| `Operation(`Truncate(lc, rc, t)) => "truncate(" ++ toString(t) ++ ", " ++ E.O.dimap(string_of_float, () => "-inf", lc) ++ ", " ++ E.O.dimap(string_of_float, () => "inf", rc) ++ ")" | `Operation(`FloatFromDist(floatFromDistOp, t)) => stringFromFloatFromDistOperation(floatFromDistOp) ++ toString(t) ++ ")"
| `Operation(`Truncate(lc, rc, t)) =>
"truncate("
++ toString(t)
++ ", "
++ E.O.dimap(Js.Float.toString, () => "-inf", lc)
++ ", "
++ E.O.dimap(Js.Float.toString, () => "inf", rc)
++ ")"
| `Operation(`Render(t)) => toString(t) | `Operation(`Render(t)) => toString(t)
} };
}; };
}; };
let toShape = (sampleCount: int, treeNode: treeNode) => { let toShape = (sampleCount: int, treeNode: treeNode) => {
let renderResult = TreeNode.toDistData(`Operation(`Render(treeNode)), sampleCount); let renderResult =
TreeNode.toDistData(`Operation(`Render(treeNode)), sampleCount);
switch (renderResult) { switch (renderResult) {
| Ok(`DistData(`RenderedShape(rs))) => { | Ok(`DistData(`RenderedShape(rs))) =>
let continuous = Distributions.Shape.T.toContinuous(rs); let continuous = Distributions.Shape.T.toContinuous(rs);
let discrete = Distributions.Shape.T.toDiscrete(rs); let discrete = Distributions.Shape.T.toDiscrete(rs);
let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete); let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete);
shape |> E.O.toExt(""); shape |> E.O.toExt("");
}
| Ok(_) => E.O.toExn("Rendering failed.", None) | Ok(_) => E.O.toExn("Rendering failed.", None)
| Error(message) => E.O.toExn("No shape found!", None) | Error(message) => E.O.toExn("No shape found, error: " ++ message, None)
} };
}; };
let toString = (treeNode: treeNode) =>
TreeNode.toString(treeNode);

View File

@ -22,7 +22,7 @@ let propValue = (t: Prop.Value.t) => {
RenderTypes.DistPlusRenderer.make( RenderTypes.DistPlusRenderer.make(
~distPlusIngredients=r, ~distPlusIngredients=r,
~recommendedLength=10000, ~recommendedLength=10000,
~shouldTruncate=true, ~shouldDownsample=true,
(), (),
) )
|> DistPlusRenderer.run |> DistPlusRenderer.run