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("Shape", () => {
describe("Continuous", () => { describe("Continuous", () => {
open Distributions.Continuous; open Distributions.Continuous;
let continuous = make(shape, `Stepwise); let continuous = make(shape, `Linear);
makeTest("minX", T.minX(continuous), Some(1.0)); makeTest("minX", T.minX(continuous), Some(1.0));
makeTest("maxX", T.maxX(continuous), Some(8.0)); makeTest("maxX", T.maxX(continuous), Some(8.0));
makeTest( makeTest(
@ -19,32 +19,75 @@ describe("Shape", () => {
T.pointwiseFmap(r => r *. 2.0, continuous) |> getShape |> (r => r.ys), T.pointwiseFmap(r => r *. 2.0, continuous) |> getShape |> (r => r.ys),
[|16., 18.0, 4.0|], [|16., 18.0, 4.0|],
); );
makeTest( describe("xToY", () => {
"xToY at 4.0", describe("when Linear", () => {
T.xToY(4., continuous), makeTest(
{continuous: 9.0, discrete: 0.0}, "at 4.0",
); T.xToY(4., continuous),
makeTest( {continuous: 9.0, discrete: 0.0},
"xToY at 0.0", );
T.xToY(0., continuous), // Note: This below is weird to me, I'm not sure if it's what we want really.
{continuous: 8.0, discrete: 0.0}, makeTest(
); "at 0.0",
makeTest( T.xToY(0., continuous),
"xToY at 5.0", {continuous: 8.0, discrete: 0.0},
T.xToY(5., continuous), );
{continuous: 7.25, 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( makeTest(
"integral", "integral",
T.Integral.get(~cache=None, continuous) |> getShape, 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( makeTest(
"integralXToY", "integralXToY",
T.Integral.xToY(~cache=None, 2.0, continuous), 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", () => { describe("Discrete", () => {
@ -76,6 +119,23 @@ describe("Shape", () => {
T.xToY(5., discrete), T.xToY(5., discrete),
{discrete: 0.0, continuous: 0.0}, {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( makeTest(
"integral", "integral",
T.Integral.get(~cache=None, discrete), T.Integral.get(~cache=None, discrete),
@ -84,6 +144,11 @@ describe("Shape", () => {
`Stepwise, `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( makeTest(
"integralXToY", "integralXToY",
T.Integral.xToY(~cache=None, 6.0, discrete), 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)}} />}, () => {<IntegralChart distPlus onHover={r => {setX(_ => r)}} />},
[|distPlus|], [|distPlus|],
); );
// Js.log4(
// "distPlus",
// x,
// distPlus,
// distPlus |> Distributions.DistPlus.T.xToY(x),
// );
<div> <div>
chart chart
chart2 chart2
@ -67,6 +73,12 @@ let make = (~distPlus: DistTypes.distPlus) => {
<thead> <thead>
<tr> <tr>
<th className="px-4 py-2"> {"X Point" |> ReasonReact.string} </th> <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"> <th className="px-4 py-2">
{"Y Integral to Point" |> ReasonReact.string} {"Y Integral to Point" |> ReasonReact.string}
</th> </th>
@ -77,6 +89,20 @@ let make = (~distPlus: DistTypes.distPlus) => {
<th className="px-4 py-2 border "> <th className="px-4 py-2 border ">
{x |> E.Float.toString |> ReasonReact.string} {x |> E.Float.toString |> ReasonReact.string}
</th> </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 "> <th className="px-4 py-2 border ">
{distPlus {distPlus
|> Distributions.DistPlus.T.Integral.xToY(~cache=None, x) |> Distributions.DistPlus.T.Integral.xToY(~cache=None, x)

View File

@ -54,7 +54,7 @@ module Dist = (T: dist) => {
let sum = T.integralSum; 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 scaleToIntegralSum = (~intendedSum=1.0, t: t) => {
let scale = intendedSum /. Integral.sum(~cache=None, t); let scale = intendedSum /. Integral.sum(~cache=None, t);
scaleBy(~scale, t); scaleBy(~scale, t);
@ -90,8 +90,9 @@ module Continuous = {
type t = DistTypes.continuousShape; type t = DistTypes.continuousShape;
type integral = DistTypes.continuousShape; type integral = DistTypes.continuousShape;
let shapeFn = (fn, t: t) => t |> xyShape |> fn; 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: 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) => let integral = (~cache, t) =>
cache cache
|> E.O.default( |> E.O.default(
@ -103,16 +104,28 @@ module Continuous = {
); );
// This seems wrong, we really want the ending bit, I'd assume // This seems wrong, we really want the ending bit, I'd assume
let integralSum = (~cache, t) => let integralSum = (~cache, t) =>
t |> integral(~cache) |> xyShape |> XYShape.ySum; t
|> integral(~cache)
|> xyShape
|> XYShape.unsafeLast
|> (((_, y)) => y);
let minX = shapeFn(XYShape.minX); let minX = shapeFn(XYShape.minX);
let maxX = shapeFn(XYShape.maxX); let maxX = shapeFn(XYShape.maxX);
let pointwiseFmap = (fn, t: t) => let pointwiseFmap = (fn, t: t) =>
t |> xyShape |> XYShape.pointwiseMap(fn) |> fromShape; t |> xyShape |> XYShape.pointwiseMap(fn) |> fromShape;
let toShape = (t: t): DistTypes.shape => Continuous(t); 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, {interpolation, xyShape}: t) =>
let xToY = (f, t) => switch (interpolation) {
shapeFn(CdfLibrary.Distribution.findY(f), t) | `Stepwise =>
|> DistTypes.MixedPoint.makeContinuous; 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) => let integralXtoY = (~cache, f, t) =>
t |> integral(~cache) |> shapeFn(CdfLibrary.Distribution.findY(f)); t |> integral(~cache) |> shapeFn(CdfLibrary.Distribution.findY(f));
let toContinuous = t => Some(t); let toContinuous = t => Some(t);
@ -138,7 +151,6 @@ module Discrete = {
Continuous.make(XYShape.accumulateYs(t), `Stepwise); Continuous.make(XYShape.accumulateYs(t), `Stepwise);
}, },
); );
// todo: Fix this with last element
let integralSum = (~cache, t) => t |> XYShape.ySum; let integralSum = (~cache, t) => t |> XYShape.ySum;
let minX = XYShape.minX; let minX = XYShape.minX;
let maxX = XYShape.maxX; let maxX = XYShape.maxX;
@ -150,8 +162,7 @@ module Discrete = {
let toScaledDiscrete = t => Some(t); let toScaledDiscrete = t => Some(t);
let xToY = (f, t) => { let xToY = (f, t) => {
XYShape.getBy(t, ((x, _)) => x == f) XYShape.XtoY.ifAtX(f, t)
|> E.O.fmap(((_, y)) => y)
|> E.O.default(0.0) |> E.O.default(0.0)
|> DistTypes.MixedPoint.makeDiscrete; |> DistTypes.MixedPoint.makeDiscrete;
}; };

View File

@ -18,8 +18,36 @@ let last = (t: t) =>
| _ => None | _ => 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 zip = t => Belt.Array.zip(t.xs, t.ys);
let getBy = (t: t, fn) => t |> zip |> Belt.Array.getBy(_, fn); 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 pointwiseMap = (fn, t: t): t => {xs: t.xs, ys: t.ys |> E.A.fmap(fn)};
let fromArray = ((xs, ys)): t => {xs, ys}; let fromArray = ((xs, ys)): t => {xs, ys};
let fromArrays = (xs, ys): t => {xs, ys}; let fromArrays = (xs, ys): t => {xs, ys};
@ -109,21 +137,24 @@ module Range = {
(((lastX, lastY), (nextX, nextY)): zippedRange) => (((lastX, lastY), (nextX, nextY)): zippedRange) =>
(nextY -. lastY) /. (nextX -. lastX); (nextY -. lastY) /. (nextX -. lastX);
let inRanges = (mapper, reducer, t: t) => { let mapYsBasedOnRanges = (fn, t) =>
Belt.Array.zip(t.xs, t.ys) Belt.Array.zip(t.xs, t.ys)
|> E.A.toRanges |> E.A.toRanges
|> E.R.toOption |> 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 integrateWithTriangles = z => {
let rangeItems = mapYsBasedOnRanges(rangeAreaAssumingTriangles, z);
let integrateWithSteps = z => (
mapYsBasedOnRanges(rangeAreaAssumingSteps, z) |> E.O.fmap(accumulateYs); switch (rangeItems, z |> first) {
| (Some(r), Some((firstX, _))) =>
let integrateWithTriangles = z => Some(Belt.Array.concat([|(firstX, 0.0)|], r))
mapYsBasedOnRanges(rangeAreaAssumingTriangles, z) | _ => None
}
)
|> E.O.fmap(toT)
|> E.O.fmap(accumulateYs); |> E.O.fmap(accumulateYs);
};
let derivative = mapYsBasedOnRanges(delta_y_over_delta_x); let derivative = mapYsBasedOnRanges(delta_y_over_delta_x);