Continuous integration should include first item

This commit is contained in:
Ozzie Gooen 2020-02-23 20:50:27 +00:00
parent 8d7a6f7f6c
commit 24dc4e657e
4 changed files with 172 additions and 39 deletions

View File

@ -11,7 +11,7 @@ let makeTest = (str, item1, item2) =>
describe("Shape", () => {
describe("Continuous", () => {
open Distributions.Continuous;
let continuous = make(shape, `Stepwise);
let continuous = make(shape, `Linear);
makeTest("minX", T.minX(continuous), Some(1.0));
makeTest("maxX", T.maxX(continuous), Some(8.0));
makeTest(
@ -19,32 +19,75 @@ describe("Shape", () => {
T.pointwiseFmap(r => r *. 2.0, continuous) |> getShape |> (r => r.ys),
[|16., 18.0, 4.0|],
);
makeTest(
"xToY at 4.0",
T.xToY(4., continuous),
{continuous: 9.0, discrete: 0.0},
);
makeTest(
"xToY at 0.0",
T.xToY(0., continuous),
{continuous: 8.0, discrete: 0.0},
);
makeTest(
"xToY at 5.0",
T.xToY(5., continuous),
{continuous: 7.25, discrete: 0.0},
);
describe("xToY", () => {
describe("when Linear", () => {
makeTest(
"at 4.0",
T.xToY(4., continuous),
{continuous: 9.0, discrete: 0.0},
);
// Note: This below is weird to me, I'm not sure if it's what we want really.
makeTest(
"at 0.0",
T.xToY(0., continuous),
{continuous: 8.0, discrete: 0.0},
);
makeTest(
"at 5.0",
T.xToY(5., continuous),
{continuous: 7.25, discrete: 0.0},
);
makeTest(
"at 10.0",
T.xToY(10., continuous),
{continuous: 2.0, discrete: 0.0},
);
});
describe("when Stepwise", () => {
let continuous = make(shape, `Stepwise);
makeTest(
"at 4.0",
T.xToY(4., continuous),
{continuous: 9.0, discrete: 0.0},
);
makeTest(
"at 0.0",
T.xToY(0., continuous),
{continuous: 0.0, discrete: 0.0},
);
makeTest(
"at 5.0",
T.xToY(5., continuous),
{continuous: 9.0, discrete: 0.0},
);
makeTest(
"at 10.0",
T.xToY(10., continuous),
{continuous: 2.0, discrete: 0.0},
);
});
});
makeTest(
"integral",
T.Integral.get(~cache=None, continuous) |> getShape,
{xs: [|4.0, 8.0|], ys: [|25.5, 47.5|]},
{xs: [|1.0, 4.0, 8.0|], ys: [|0.0, 25.5, 47.5|]},
);
makeTest(
"integralXToY",
T.Integral.xToY(~cache=None, 0.0, continuous),
0.0,
);
makeTest(
"integralXToY",
T.Integral.xToY(~cache=None, 2.0, continuous),
25.5,
8.5,
);
makeTest("integralSum", T.Integral.sum(~cache=None, continuous), 73.0);
makeTest(
"integralXToY",
T.Integral.xToY(~cache=None, 100.0, continuous),
47.5,
);
makeTest("integralSum", T.Integral.sum(~cache=None, continuous), 47.5);
});
describe("Discrete", () => {
@ -76,6 +119,23 @@ describe("Shape", () => {
T.xToY(5., discrete),
{discrete: 0.0, continuous: 0.0},
);
makeTest(
"scaleBy",
T.scaleBy(~scale=4.0, discrete),
{xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]},
);
makeTest(
"scaleToIntegralSum",
T.scaleToIntegralSum(~intendedSum=4.0, discrete),
{xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]},
);
makeTest(
"scaleToIntegralSum: back and forth",
discrete
|> T.scaleToIntegralSum(~intendedSum=4.0)
|> T.scaleToIntegralSum(~intendedSum=1.0),
discrete,
);
makeTest(
"integral",
T.Integral.get(~cache=None, discrete),
@ -84,6 +144,11 @@ describe("Shape", () => {
`Stepwise,
),
);
makeTest(
"integral with 1 element",
T.Integral.get(~cache=None, {xs: [|0.0|], ys: [|1.0|]}),
Distributions.Continuous.make({xs: [|0.0|], ys: [|1.0|]}, `Stepwise),
);
makeTest(
"integralXToY",
T.Integral.xToY(~cache=None, 6.0, discrete),

View File

@ -60,6 +60,12 @@ let make = (~distPlus: DistTypes.distPlus) => {
() => {<IntegralChart distPlus onHover={r => {setX(_ => r)}} />},
[|distPlus|],
);
// Js.log4(
// "distPlus",
// x,
// distPlus,
// distPlus |> Distributions.DistPlus.T.xToY(x),
// );
<div>
chart
chart2
@ -67,6 +73,12 @@ let make = (~distPlus: DistTypes.distPlus) => {
<thead>
<tr>
<th className="px-4 py-2"> {"X Point" |> ReasonReact.string} </th>
<th className="px-4 py-2">
{"Discrete Value" |> ReasonReact.string}
</th>
<th className="px-4 py-2">
{"Continuous Value" |> ReasonReact.string}
</th>
<th className="px-4 py-2">
{"Y Integral to Point" |> ReasonReact.string}
</th>
@ -77,6 +89,20 @@ let make = (~distPlus: DistTypes.distPlus) => {
<th className="px-4 py-2 border ">
{x |> E.Float.toString |> ReasonReact.string}
</th>
<th className="px-4 py-2 border ">
{distPlus
|> Distributions.DistPlus.T.xToY(x)
|> DistTypes.MixedPoint.toDiscreteValue
|> E.Float.with2DigitsPrecision
|> ReasonReact.string}
</th>
<th className="px-4 py-2 border ">
{distPlus
|> Distributions.DistPlus.T.xToY(x)
|> DistTypes.MixedPoint.toContinuousValue
|> E.Float.with2DigitsPrecision
|> ReasonReact.string}
</th>
<th className="px-4 py-2 border ">
{distPlus
|> Distributions.DistPlus.T.Integral.xToY(~cache=None, x)

View File

@ -54,7 +54,7 @@ module Dist = (T: dist) => {
let sum = T.integralSum;
};
// This is suboptimal because it could get the cache but doesn't here.
// This is suboptimal because it could get the cache but doesn't here.
let scaleToIntegralSum = (~intendedSum=1.0, t: t) => {
let scale = intendedSum /. Integral.sum(~cache=None, t);
scaleBy(~scale, t);
@ -90,8 +90,9 @@ module Continuous = {
type t = DistTypes.continuousShape;
type integral = DistTypes.continuousShape;
let shapeFn = (fn, t: t) => t |> xyShape |> fn;
// TODO: Obviously fix this, it's terrible. Use interpolation method here.
// TODO: Obviously fix this, it's terrible. Use interpolation param to do appropriate interpolation.
// TODO: Steps could be 1 value, interpolation needs at least 2.
// TODO: integrateWithTriangles should return (x0, 0.0) as the first item.
let integral = (~cache, t) =>
cache
|> E.O.default(
@ -103,16 +104,28 @@ module Continuous = {
);
// This seems wrong, we really want the ending bit, I'd assume
let integralSum = (~cache, t) =>
t |> integral(~cache) |> xyShape |> XYShape.ySum;
t
|> integral(~cache)
|> xyShape
|> XYShape.unsafeLast
|> (((_, y)) => y);
let minX = shapeFn(XYShape.minX);
let maxX = shapeFn(XYShape.maxX);
let pointwiseFmap = (fn, t: t) =>
t |> xyShape |> XYShape.pointwiseMap(fn) |> fromShape;
let toShape = (t: t): DistTypes.shape => Continuous(t);
// TODO: When Roman's PR comes in, fix this bit. This depends on interpolation, obviously.
let xToY = (f, t) =>
shapeFn(CdfLibrary.Distribution.findY(f), t)
|> DistTypes.MixedPoint.makeContinuous;
let xToY = (f, {interpolation, xyShape}: t) =>
switch (interpolation) {
| `Stepwise =>
xyShape
|> XYShape.XtoY.stepwise(f)
|> E.O.default(0.0)
|> DistTypes.MixedPoint.makeContinuous
| `Linear =>
xyShape
|> XYShape.XtoY.linear(f)
|> DistTypes.MixedPoint.makeContinuous
};
let integralXtoY = (~cache, f, t) =>
t |> integral(~cache) |> shapeFn(CdfLibrary.Distribution.findY(f));
let toContinuous = t => Some(t);
@ -138,7 +151,6 @@ module Discrete = {
Continuous.make(XYShape.accumulateYs(t), `Stepwise);
},
);
// todo: Fix this with last element
let integralSum = (~cache, t) => t |> XYShape.ySum;
let minX = XYShape.minX;
let maxX = XYShape.maxX;
@ -150,8 +162,7 @@ module Discrete = {
let toScaledDiscrete = t => Some(t);
let xToY = (f, t) => {
XYShape.getBy(t, ((x, _)) => x == f)
|> E.O.fmap(((_, y)) => y)
XYShape.XtoY.ifAtX(f, t)
|> E.O.default(0.0)
|> DistTypes.MixedPoint.makeDiscrete;
};

View File

@ -18,8 +18,36 @@ let last = (t: t) =>
| _ => None
};
let unsafeFirst = (t: t) => first(t) |> E.O.toExn("Unsafe operation");
let unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation");
let zip = t => Belt.Array.zip(t.xs, t.ys);
let getBy = (t: t, fn) => t |> zip |> Belt.Array.getBy(_, fn);
let firstPairAtOrBeforeValue = (xValue, t: t) => {
let zipped = zip(t);
let firstIndex =
zipped |> Belt.Array.getIndexBy(_, ((x, y)) => x > xValue);
let previousIndex =
switch (firstIndex) {
| None => Some(Array.length(zipped) - 1)
| Some(0) => None
| Some(n) => Some(n - 1)
};
previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped));
};
module XtoY = {
let ifAtX = (f, t: t) =>
getBy(t, ((x, _)) => x == f) |> E.O.fmap(((_, y)) => y);
let stepwise = (f, t: t) =>
firstPairAtOrBeforeValue(f, t) |> E.O.fmap(((_, y)) => y);
// TODO: When Roman's PR comes in, fix this bit. This depends on interpolation, obviously.
let linear = (f, t: t) => t |> CdfLibrary.Distribution.findY(f);
};
let pointwiseMap = (fn, t: t): t => {xs: t.xs, ys: t.ys |> E.A.fmap(fn)};
let fromArray = ((xs, ys)): t => {xs, ys};
let fromArrays = (xs, ys): t => {xs, ys};
@ -109,21 +137,24 @@ module Range = {
(((lastX, lastY), (nextX, nextY)): zippedRange) =>
(nextY -. lastY) /. (nextX -. lastX);
let inRanges = (mapper, reducer, t: t) => {
let mapYsBasedOnRanges = (fn, t) =>
Belt.Array.zip(t.xs, t.ys)
|> E.A.toRanges
|> E.R.toOption
|> E.O.fmap(r => r |> Belt.Array.map(_, mapper) |> reducer);
};
|> E.O.fmap(r => r |> Belt.Array.map(_, r => (nextX(r), fn(r))));
let mapYsBasedOnRanges = fn => inRanges(r => (nextX(r), fn(r)), toT);
let integrateWithSteps = z =>
mapYsBasedOnRanges(rangeAreaAssumingSteps, z) |> E.O.fmap(accumulateYs);
let integrateWithTriangles = z =>
mapYsBasedOnRanges(rangeAreaAssumingTriangles, z)
let integrateWithTriangles = z => {
let rangeItems = mapYsBasedOnRanges(rangeAreaAssumingTriangles, z);
(
switch (rangeItems, z |> first) {
| (Some(r), Some((firstX, _))) =>
Some(Belt.Array.concat([|(firstX, 0.0)|], r))
| _ => None
}
)
|> E.O.fmap(toT)
|> E.O.fmap(accumulateYs);
};
let derivative = mapYsBasedOnRanges(delta_y_over_delta_x);