Fix discrete+continuous integration issue by cleaning up PointwiseCombination.combine
This commit is contained in:
parent
f8060458be
commit
49e379c8aa
|
@ -287,7 +287,7 @@ module IntegralChart = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let maxX = {
|
let maxX = {
|
||||||
distPlus |> DistPlus.T.Integral.yToX(~cache=None, 0.99);
|
distPlus |> DistPlus.T.Integral.yToX(~cache=None, 0.99999);
|
||||||
};
|
};
|
||||||
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson;
|
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson;
|
||||||
<DistributionPlot
|
<DistributionPlot
|
||||||
|
|
|
@ -304,5 +304,9 @@ let combineShapesContinuousDiscrete =
|
||||||
|
|
||||||
outXYShapes
|
outXYShapes
|
||||||
|> E.A.fmap(XYShape.T.fromZippedArray)
|
|> E.A.fmap(XYShape.T.fromZippedArray)
|
||||||
|> E.A.fold_left(XYShape.PointwiseCombination.combineLinear((+.)), XYShape.T.empty);
|
|> E.A.fold_left(
|
||||||
|
XYShape.PointwiseCombination.combine((+.),
|
||||||
|
XYShape.XtoY.linearBetweenPointsExtrapolateZero,
|
||||||
|
XYShape.XtoY.linearBetweenPointsExtrapolateZero),
|
||||||
|
XYShape.T.empty);
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,8 +43,10 @@ let combinePointwise =
|
||||||
|
|
||||||
make(
|
make(
|
||||||
`Linear,
|
`Linear,
|
||||||
XYShape.PointwiseCombination.combineLinear(
|
XYShape.PointwiseCombination.combine(
|
||||||
~fn=(+.),
|
(+.),
|
||||||
|
XYShape.XtoY.linearBetweenPointsExtrapolateZero,
|
||||||
|
XYShape.XtoY.linearBetweenPointsExtrapolateZero,
|
||||||
t1.xyShape,
|
t1.xyShape,
|
||||||
t2.xyShape,
|
t2.xyShape,
|
||||||
),
|
),
|
||||||
|
|
|
@ -33,9 +33,9 @@ let combinePointwise =
|
||||||
|
|
||||||
make(
|
make(
|
||||||
XYShape.PointwiseCombination.combine(
|
XYShape.PointwiseCombination.combine(
|
||||||
~xsSelection=ALL_XS,
|
(+.),
|
||||||
~xToYSelection=XYShape.XtoY.stepwiseIfAtX,
|
XYShape.XtoY.assumeZeroBetweenPoints,
|
||||||
~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
|
XYShape.XtoY.assumeZeroBetweenPoints,
|
||||||
t1.xyShape,
|
t1.xyShape,
|
||||||
t2.xyShape,
|
t2.xyShape,
|
||||||
),
|
),
|
||||||
|
@ -113,12 +113,18 @@ module T =
|
||||||
if (t |> getShape |> XYShape.T.length > 0) {
|
if (t |> getShape |> XYShape.T.length > 0) {
|
||||||
switch (cache) {
|
switch (cache) {
|
||||||
| Some(c) => c
|
| Some(c) => c
|
||||||
| None =>
|
| None => {
|
||||||
Continuous.make(
|
let ts = getShape(t);
|
||||||
`Stepwise,
|
// The first xy of this integral should always be the zero, to ensure nice plotting
|
||||||
XYShape.T.accumulateYs((+.), getShape(t)),
|
let firstX = ts |> XYShape.T.minX;
|
||||||
None,
|
let prependedZeroPoint: XYShape.T.t = {xs: [|firstX -. epsilon_float|], ys: [|0.|]};
|
||||||
)
|
let integralShape =
|
||||||
|
ts
|
||||||
|
|> XYShape.T.concat(prependedZeroPoint)
|
||||||
|
|> XYShape.T.accumulateYs((+.));
|
||||||
|
|
||||||
|
Continuous.make(`Stepwise, integralShape, None);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
Continuous.make(
|
Continuous.make(
|
||||||
|
|
|
@ -147,15 +147,17 @@ module 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 -- but that's the way it should be.
|
||||||
let continuousIntegral =
|
let continuousIntegral =
|
||||||
Continuous.T.Integral.get(~cache=None, continuous);
|
Continuous.T.Integral.get(~cache=None, continuous);
|
||||||
let discreteIntegral = Discrete.T.Integral.get(~cache=None, discrete);
|
let discreteIntegral = Discrete.T.Integral.get(~cache=None, discrete);
|
||||||
|
|
||||||
Continuous.make(
|
Continuous.make(
|
||||||
`Linear,
|
`Linear,
|
||||||
XYShape.PointwiseCombination.combineLinear(
|
XYShape.PointwiseCombination.combine(
|
||||||
~fn=(+.),
|
(+.),
|
||||||
|
XYShape.XtoY.linearBetweenPointsExtrapolateFlat,
|
||||||
|
XYShape.XtoY.stepwiseBetweenPointsExtrapolateFlat,
|
||||||
Continuous.getShape(continuousIntegral),
|
Continuous.getShape(continuousIntegral),
|
||||||
Continuous.getShape(discreteIntegral),
|
Continuous.getShape(discreteIntegral),
|
||||||
),
|
),
|
||||||
|
|
|
@ -32,6 +32,11 @@ module T = {
|
||||||
let accumulateYs = (fn, p: t) => {
|
let accumulateYs = (fn, p: t) => {
|
||||||
fromArray((p.xs, E.A.accumulate(fn, p.ys)));
|
fromArray((p.xs, E.A.accumulate(fn, p.ys)));
|
||||||
};
|
};
|
||||||
|
let concat = (t1: t, t2: t) => {
|
||||||
|
let cxs = Array.concat([t1.xs, t2.xs]);
|
||||||
|
let cys = Array.concat([t1.ys, t2.ys]);
|
||||||
|
{xs: cxs, ys: cys};
|
||||||
|
};
|
||||||
let fromZippedArray = (pairs: array((float, float))): t =>
|
let fromZippedArray = (pairs: array((float, float))): t =>
|
||||||
pairs |> Belt.Array.unzip |> fromArray;
|
pairs |> Belt.Array.unzip |> fromArray;
|
||||||
let equallyDividedXs = (t: t, newLength) => {
|
let equallyDividedXs = (t: t, newLength) => {
|
||||||
|
@ -137,6 +142,61 @@ module XtoY = {
|
||||||
};
|
};
|
||||||
n;
|
n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* The extrapolators below can be used e.g. with PointwiseCombination.combine */
|
||||||
|
let linearBetweenPointsExtrapolateFlat = (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
if (leftIndex < 0) {
|
||||||
|
t.ys[0];
|
||||||
|
} else if (leftIndex >= T.length(t) - 1) {
|
||||||
|
T.lastY(t);
|
||||||
|
} else {
|
||||||
|
let x1 = t.xs[leftIndex];
|
||||||
|
let x2 = t.xs[leftIndex + 1];
|
||||||
|
let y1 = t.ys[leftIndex];
|
||||||
|
let y2 = t.ys[leftIndex + 1];
|
||||||
|
let fraction = (x -. x1) /. (x2 -. x1);
|
||||||
|
y1 *. (1. -. fraction) +. y2 *. fraction;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let linearBetweenPointsExtrapolateZero = (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
if (leftIndex < 0) {
|
||||||
|
0.0
|
||||||
|
} else if (leftIndex >= T.length(t) - 1) {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
let x1 = t.xs[leftIndex];
|
||||||
|
let x2 = t.xs[leftIndex + 1];
|
||||||
|
let y1 = t.ys[leftIndex];
|
||||||
|
let y2 = t.ys[leftIndex + 1];
|
||||||
|
let fraction = (x -. x1) /. (x2 -. x1);
|
||||||
|
y1 *. (1. -. fraction) +. y2 *. fraction;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stepwiseBetweenPointsExtrapolateFlat = (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
if (leftIndex < 0) {
|
||||||
|
t.ys[0];
|
||||||
|
} else if (leftIndex >= T.length(t) - 1) {
|
||||||
|
T.lastY(t);
|
||||||
|
} else {
|
||||||
|
t.ys[leftIndex];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stepwiseBetweenPointsExtrapolateZero = (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
if (leftIndex < 0) {
|
||||||
|
0.0
|
||||||
|
} else if (leftIndex >= T.length(t) - 1) {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
t.ys[leftIndex];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let assumeZeroBetweenPoints = (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
0.0;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
module XsConversion = {
|
module XsConversion = {
|
||||||
|
@ -171,24 +231,22 @@ module Zipped = {
|
||||||
};
|
};
|
||||||
|
|
||||||
module PointwiseCombination = {
|
module PointwiseCombination = {
|
||||||
type xsSelection =
|
|
||||||
| ALL_XS
|
|
||||||
| XS_EVENLY_DIVIDED(int);
|
|
||||||
|
|
||||||
let combineLinear = [%raw {| // : (float => float => float, T.t, T.t) => T.t
|
// t1Interpolator and t2Interpolator are functions from XYShape.XtoY, e.g. linearBetweenPointsExtrapolateFlat.
|
||||||
|
let combine = [%raw {| // : (float => float => float, T.t, T.t, bool) => T.t
|
||||||
// This function combines two xyShapes by looping through both of them simultaneously.
|
// This function combines two xyShapes by looping through both of them simultaneously.
|
||||||
// It always moves on to the next smallest x, whether that's in the first or second input's xs,
|
// It always moves on to the next smallest x, whether that's in the first or second input's xs,
|
||||||
// and interpolates the value on the other side, thus accumulating xs and ys.
|
// and interpolates the value on the other side, thus accumulating xs and ys.
|
||||||
// In this implementation (unlike in XtoY.linear above), values outside of a shape's xs are considered 0.0 (instead of firstY or lastY).
|
|
||||||
// This is written in raw JS because this can still be a bottleneck, and using refs for the i and j indices is quite painful.
|
// This is written in raw JS because this can still be a bottleneck, and using refs for the i and j indices is quite painful.
|
||||||
|
|
||||||
function(fn, t1, t2) {
|
function(fn, t1Interpolator, t2Interpolator, t1, t2) {
|
||||||
let t1n = t1.xs.length;
|
let t1n = t1.xs.length;
|
||||||
let t2n = t2.xs.length;
|
let t2n = t2.xs.length;
|
||||||
let outX = [];
|
let outX = [];
|
||||||
let outY = [];
|
let outY = [];
|
||||||
let i = -1;
|
let i = -1;
|
||||||
let j = -1;
|
let j = -1;
|
||||||
|
|
||||||
while (i <= t1n - 1 && j <= t2n - 1) {
|
while (i <= t1n - 1 && j <= t2n - 1) {
|
||||||
let x, ya, yb;
|
let x, ya, yb;
|
||||||
if (j == t2n - 1 && i < t1n - 1 ||
|
if (j == t2n - 1 && i < t1n - 1 ||
|
||||||
|
@ -198,8 +256,7 @@ module PointwiseCombination = {
|
||||||
x = t1.xs[i];
|
x = t1.xs[i];
|
||||||
ya = t1.ys[i];
|
ya = t1.ys[i];
|
||||||
|
|
||||||
let bFraction = (x - (t2.xs[j] || x)) / ((t2.xs[j+1] || x) - (t2.xs[j] || 0));
|
yb = t2Interpolator(t2, j, x);
|
||||||
yb = (t2.ys[j] || 0) * (1-bFraction) + (t2.ys[j+1] || 0) * bFraction;
|
|
||||||
} else if (i == t1n - 1 && j < t2n - 1 ||
|
} else if (i == t1n - 1 && j < t2n - 1 ||
|
||||||
t1.xs[i+1] > t2.xs[j+1]) { // if b has to catch up to a, or if a is already done
|
t1.xs[i+1] > t2.xs[j+1]) { // if b has to catch up to a, or if a is already done
|
||||||
j++;
|
j++;
|
||||||
|
@ -207,8 +264,7 @@ module PointwiseCombination = {
|
||||||
x = t2.xs[j];
|
x = t2.xs[j];
|
||||||
yb = t2.ys[j];
|
yb = t2.ys[j];
|
||||||
|
|
||||||
let aFraction = (x - (t1.xs[i] || x)) / ((t1.xs[i+1] || x) - (t1.xs[i] || 0));
|
ya = t1Interpolator(t1, i, x);
|
||||||
ya = (t1.ys[i] || 0) * (1-aFraction) + (t1.ys[i+1] || 0) * aFraction;
|
|
||||||
} else if (i < t1n - 1 && j < t2n && t1.xs[i+1] === t2.xs[j+1]) { // if they happen to be equal, move both ahead
|
} else if (i < t1n - 1 && j < t2n && t1.xs[i+1] === t2.xs[j+1]) { // if they happen to be equal, move both ahead
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
|
@ -227,15 +283,16 @@ module PointwiseCombination = {
|
||||||
outX.push(x);
|
outX.push(x);
|
||||||
outY.push(fn(ya, yb));
|
outY.push(fn(ya, yb));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {xs: outX, ys: outY};
|
return {xs: outX, ys: outY};
|
||||||
}
|
}
|
||||||
|}];
|
|}];
|
||||||
|
|
||||||
let combine =
|
let combineEvenXs =
|
||||||
(
|
(
|
||||||
~xToYSelection: (float, T.t) => 'a,
|
|
||||||
~xsSelection=ALL_XS,
|
|
||||||
~fn,
|
~fn,
|
||||||
|
~xToYSelection,
|
||||||
|
sampleCount,
|
||||||
t1: T.t,
|
t1: T.t,
|
||||||
t2: T.t,
|
t2: T.t,
|
||||||
) => {
|
) => {
|
||||||
|
@ -245,24 +302,15 @@ module PointwiseCombination = {
|
||||||
| (0, _) => t2
|
| (0, _) => t2
|
||||||
| (_, 0) => t1
|
| (_, 0) => t1
|
||||||
| (_, _) => {
|
| (_, _) => {
|
||||||
let allXs =
|
let allXs = Ts.equallyDividedXs([|t1, t2|], sampleCount);
|
||||||
switch (xsSelection) {
|
|
||||||
| ALL_XS => Ts.allXs([|t1, t2|])
|
|
||||||
| XS_EVENLY_DIVIDED(sampleCount) =>
|
|
||||||
Ts.equallyDividedXs([|t1, t2|], sampleCount)
|
|
||||||
};
|
|
||||||
|
|
||||||
let allYs =
|
let allYs = allXs |> E.A.fmap(x => fn(xToYSelection(x, t1), xToYSelection(x, t2)));
|
||||||
allXs |> E.A.fmap(x => fn(xToYSelection(x, t1), xToYSelection(x, t2)));
|
|
||||||
|
|
||||||
T.fromArrays(allXs, allYs);
|
T.fromArrays(allXs, allYs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let combineStepwise = combine(~xToYSelection=XtoY.stepwiseIncremental);
|
|
||||||
let combineIfAtX = combine(~xToYSelection=XtoY.stepwiseIfAtX);
|
|
||||||
|
|
||||||
// TODO: I'd bet this is pretty slow. Maybe it would be faster to intersperse Xs and Ys separately.
|
// TODO: I'd bet this is pretty slow. Maybe it would be faster to intersperse Xs and Ys separately.
|
||||||
let intersperse = (t1: T.t, t2: T.t) => {
|
let intersperse = (t1: T.t, t2: T.t) => {
|
||||||
E.A.intersperse(T.zip(t1), T.zip(t2)) |> T.fromZippedArray;
|
E.A.intersperse(T.zip(t1), T.zip(t2)) |> T.fromZippedArray;
|
||||||
|
@ -355,10 +403,10 @@ let pointLogScore = (prediction, answer) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
let logScorePoint = (sampleCount, t1, t2) =>
|
let logScorePoint = (sampleCount, t1, t2) =>
|
||||||
PointwiseCombination.combine(
|
PointwiseCombination.combineEvenXs(
|
||||||
~xsSelection=XS_EVENLY_DIVIDED(sampleCount),
|
|
||||||
~xToYSelection=XtoY.linear,
|
|
||||||
~fn=pointLogScore,
|
~fn=pointLogScore,
|
||||||
|
~xToYSelection=XtoY.linear,
|
||||||
|
sampleCount,
|
||||||
t1,
|
t1,
|
||||||
t2,
|
t2,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user