More XYShape cleanup

This commit is contained in:
Ozzie Gooen 2020-03-28 14:17:47 +00:00
parent 91734fdd32
commit 29d8e40693
5 changed files with 290 additions and 286 deletions

View File

@ -19,8 +19,8 @@ describe("Shape", () => {
makeTest("minX", T.minX(continuous), Some(1.0));
makeTest("maxX", T.maxX(continuous), Some(8.0));
makeTest(
"pointwiseFmap",
T.pointwiseFmap(r => r *. 2.0, continuous) |> getShape |> (r => r.ys),
"mapY",
T.mapY(r => r *. 2.0, continuous) |> getShape |> (r => r.ys),
[|16., 18.0, 4.0|],
);
describe("xToY", () => {
@ -130,8 +130,8 @@ describe("Shape", () => {
makeTest("minX", T.minX(discrete), Some(1.0));
makeTest("maxX", T.maxX(discrete), Some(8.0));
makeTest(
"pointwiseFmap",
T.pointwiseFmap(r => r *. 2.0, discrete) |> (r => r.ys),
"mapY",
T.mapY(r => r *. 2.0, discrete) |> (r => r.ys),
[|0.6, 1.0, 0.4|],
);
makeTest(
@ -213,8 +213,8 @@ describe("Shape", () => {
makeTest("minX", T.minX(mixed), Some(1.0));
makeTest("maxX", T.maxX(mixed), Some(14.0));
makeTest(
"pointwiseFmap",
T.pointwiseFmap(r => r *. 2.0, mixed),
"mapY",
T.mapY(r => r *. 2.0, mixed),
Distributions.Mixed.make(
~continuous=
Distributions.Continuous.make(

View File

@ -40,16 +40,16 @@ describe("XYShapes", () => {
Some(210.3721280423322),
);
});
describe("transverse", () => {
makeTest(
"When very different",
XYShape.T.Transversal._transverse(
(aCurrent, aLast) => aCurrent +. aLast,
[|1.0, 2.0, 3.0, 4.0|],
),
[|1.0, 3.0, 6.0, 10.0|],
)
});
// describe("transverse", () => {
// makeTest(
// "When very different",
// XYShape.Transversal._transverse(
// (aCurrent, aLast) => aCurrent +. aLast,
// [|1.0, 2.0, 3.0, 4.0|],
// ),
// [|1.0, 3.0, 6.0, 10.0|],
// )
// });
describe("integrateWithTriangles", () => {
makeTest(
"integrates correctly",

View File

@ -1,25 +1,9 @@
let min = (f1: option(float), f2: option(float)) =>
switch (f1, f2) {
| (Some(f1), Some(f2)) => Some(f1 < f2 ? f1 : f2)
| (Some(f1), None) => Some(f1)
| (None, Some(f2)) => Some(f2)
| (None, None) => None
};
let max = (f1: option(float), f2: option(float)) =>
switch (f1, f2) {
| (Some(f1), Some(f2)) => Some(f1 > f2 ? f1 : f2)
| (Some(f1), None) => Some(f1)
| (None, Some(f2)) => Some(f2)
| (None, None) => None
};
module type dist = {
type t;
type integral;
let minX: t => option(float);
let maxX: t => option(float);
let pointwiseFmap: (float => float, t) => t;
let mapY: (float => float, t) => t;
let xToY: (float, t) => DistTypes.mixedPoint;
let toShape: t => DistTypes.shape;
let toContinuous: t => option(DistTypes.continuousShape);
@ -45,7 +29,7 @@ module Dist = (T: dist) => {
| (Some(min), Some(max)) => Some(max -. min)
| _ => None
};
let pointwiseFmap = T.pointwiseFmap;
let mapY = T.mapY;
let xToY = T.xToY;
let truncate = T.truncate;
let toShape = T.toShape;
@ -56,8 +40,7 @@ module Dist = (T: dist) => {
let toScaledDiscrete = T.toScaledDiscrete;
// TODO: Move this to each class, have use integral to produce integral in DistPlus class.
let scaleBy = (~scale=1.0, t: t) =>
t |> pointwiseFmap((r: float) => r *. scale);
let scaleBy = (~scale=1.0, t: t) => t |> mapY((r: float) => r *. scale);
module Integral = {
type t = T.integral;
@ -87,7 +70,7 @@ module Continuous = {
interpolation,
};
let lastY = (t: t) =>
t |> xyShape |> XYShape.T.Pairs.unsafeLast |> (((_, y)) => y);
t |> xyShape |> XYShape.Pairs.unsafeLast |> (((_, y)) => y);
let oShapeMap =
(fn, {xyShape, interpolation}: t): option(DistTypes.continuousShape) =>
fn(xyShape) |> E.O.fmap(make(_, interpolation));
@ -110,19 +93,19 @@ module Continuous = {
let minX = shapeFn(XYShape.T.minX);
let maxX = shapeFn(XYShape.T.maxX);
let toDiscreteProbabilityMass = _ => 0.0;
let pointwiseFmap = (fn, t: t) =>
t |> xyShape |> XYShape.T.pointwiseMap(fn) |> fromShape;
let mapY = (fn, t: t) =>
t |> xyShape |> XYShape.T.mapY(fn) |> fromShape;
let toShape = (t: t): DistTypes.shape => Continuous(t);
let xToY = (f, {interpolation, xyShape}: t) =>
switch (interpolation) {
| `Stepwise =>
xyShape
|> XYShape.T.XtoY.stepwiseIncremental(f)
|> XYShape.XtoY.stepwiseIncremental(f)
|> E.O.default(0.0)
|> DistTypes.MixedPoint.makeContinuous
| `Linear =>
xyShape
|> XYShape.T.XtoY.linear(f)
|> XYShape.XtoY.linear(f)
|> DistTypes.MixedPoint.makeContinuous
};
@ -146,16 +129,16 @@ module Continuous = {
let truncate = (~cache=None, i, t) =>
t
|> shapeMap(
XYShape.T.XsConversion.proportionByProbabilityMass(
XYShape.XsConversion.proportionByProbabilityMass(
i,
integral(~cache, t).xyShape,
),
);
let integralEndY = (~cache, t) => t |> integral(~cache) |> lastY;
let integralXtoY = (~cache, f, t) =>
t |> integral(~cache) |> shapeFn(XYShape.T.XtoY.linear(f));
t |> integral(~cache) |> shapeFn(XYShape.XtoY.linear(f));
let integralYtoX = (~cache, f, t) =>
t |> integral(~cache) |> shapeFn(XYShape.T.YtoX.linear(f));
t |> integral(~cache) |> shapeFn(XYShape.YtoX.linear(f));
let toContinuous = t => Some(t);
let toDiscrete = _ => None;
let toScaledContinuous = t => Some(t);
@ -179,14 +162,14 @@ module Discrete = {
let integral = (~cache, t) =>
switch (cache) {
| Some(c) => c
| None => Continuous.make(XYShape.T.accumulateYs(t), `Stepwise)
| None => Continuous.make(XYShape.T.accumulateYs((+.), t), `Stepwise)
};
let integralEndY = (~cache, t) =>
t |> integral(~cache) |> Continuous.lastY;
let minX = XYShape.T.minX;
let maxX = XYShape.T.maxX;
let toDiscreteProbabilityMass = t => 1.0;
let pointwiseFmap = XYShape.T.pointwiseMap;
let mapY = XYShape.T.mapY;
let toShape = (t: t): DistTypes.shape => Discrete(t);
let toContinuous = _ => None;
let toDiscrete = t => Some(t);
@ -195,14 +178,14 @@ module Discrete = {
let truncate = (~cache=None, i, t: t): DistTypes.discreteShape =>
t
|> XYShape.T.zip
|> XYShape.T.Zipped.sortByY
|> XYShape.Zipped.sortByY
|> Belt.Array.reverse
|> Belt.Array.slice(_, ~offset=0, ~len=i)
|> XYShape.T.Zipped.sortByX
|> XYShape.Zipped.sortByX
|> XYShape.T.fromZippedArray;
let xToY = (f, t) => {
XYShape.T.XtoY.stepwiseIfAtX(f, t)
XYShape.XtoY.stepwiseIfAtX(f, t)
|> E.O.default(0.0)
|> DistTypes.MixedPoint.makeDiscrete;
};
@ -211,13 +194,13 @@ module Discrete = {
t
|> integral(~cache)
|> Continuous.getShape
|> XYShape.T.XtoY.linear(f);
|> XYShape.XtoY.linear(f);
let integralYtoX = (~cache, f, t) =>
t
|> integral(~cache)
|> Continuous.getShape
|> XYShape.T.YtoX.linear(f);
|> XYShape.YtoX.linear(f);
});
};
@ -348,9 +331,10 @@ module Mixed = {
let result =
Continuous.make(
XYShape.T.Combine.combineLinear(
Continuous.getShape(cont), Continuous.getShape(dist), (a, b) =>
a +. b
XYShape.Combine.combineLinear(
~fn=(a, b) => a +. b,
Continuous.getShape(cont),
Continuous.getShape(dist),
),
`Linear,
);
@ -366,22 +350,22 @@ module Mixed = {
t
|> integral(~cache)
|> Continuous.getShape
|> XYShape.T.XtoY.linear(f);
|> XYShape.XtoY.linear(f);
};
let integralYtoX = (~cache, f, t) => {
t
|> integral(~cache)
|> Continuous.getShape
|> XYShape.T.YtoX.linear(f);
|> XYShape.YtoX.linear(f);
};
// TODO: This functionality is kinda weird, because it seems to assume the cdf adds to 1.0 elsewhere, which wouldn't happen here.
let pointwiseFmap =
let mapY =
(fn, {discrete, continuous, discreteProbabilityMassFraction}: t): t => {
{
discrete: Discrete.T.pointwiseFmap(fn, discrete),
continuous: Continuous.T.pointwiseFmap(fn, continuous),
discrete: Discrete.T.mapY(fn, discrete),
continuous: Continuous.T.mapY(fn, continuous),
discreteProbabilityMassFraction,
};
};
@ -516,14 +500,10 @@ module Shape = {
};
let maxX = (t: t) =>
mapToAll(t, (Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX));
let pointwiseFmap = (fn, t: t) =>
let mapY = (fn, t: t) =>
fmap(
t,
(
Mixed.T.pointwiseFmap(fn),
Discrete.T.pointwiseFmap(fn),
Continuous.T.pointwiseFmap(fn),
),
(Mixed.T.mapY(fn), Discrete.T.mapY(fn), Continuous.T.mapY(fn)),
);
});
};
@ -592,9 +572,7 @@ module DistPlus = {
|> toShape
|> Shape.T.toScaledContinuous
|> E.O.fmap(
Continuous.T.pointwiseFmap(
domainIncludedProbabilityMassAdjustment(t),
),
Continuous.T.mapY(domainIncludedProbabilityMassAdjustment(t)),
);
};
@ -603,9 +581,7 @@ module DistPlus = {
|> toShape
|> Shape.T.toScaledDiscrete
|> E.O.fmap(
Discrete.T.pointwiseFmap(
domainIncludedProbabilityMassAdjustment(t),
),
Discrete.T.mapY(domainIncludedProbabilityMassAdjustment(t)),
);
};
@ -627,8 +603,8 @@ module DistPlus = {
let truncate = (~cache=None, i, t) =>
updateShape(t |> toShape |> Shape.T.truncate(i), t);
// todo: adjust for limit, maybe?
let pointwiseFmap = (fn, {shape, _} as t: t): t =>
Shape.T.pointwiseFmap(fn, shape) |> updateShape(_, t);
let mapY = (fn, {shape, _} as t: t): t =>
Shape.T.mapY(fn, shape) |> updateShape(_, t);
let integralEndY = (~cache as _, t: t) =>
Shape.T.Integral.sum(~cache=Some(t.integralCache), toShape(t));

View File

@ -11,10 +11,6 @@ let interpolate =
module T = {
type t = xyShape;
type ts = array(xyShape);
let toJs = (t: t) => {
{"xs": t.xs, "ys": t.ys};
};
let xs = (t: t) => t.xs;
let ys = (t: t) => t.ys;
let minX = (t: t) => t |> xs |> E.A.Sorted.min;
@ -22,13 +18,16 @@ module T = {
let minY = (t: t) => t |> ys |> E.A.Sorted.min;
let maxY = (t: t) => t |> ys |> E.A.Sorted.max;
let xTotalRange = (t: t) => t |> xs |> E.A.Sorted.range;
let mapX = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys};
let mapY = (fn, t: t): t => {xs: t.xs, ys: E.A.fmap(fn, t.ys)};
let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys);
let pointwiseMap = (fn, t: t): t => {xs: t.xs, ys: t.ys |> E.A.fmap(fn)};
let xMap = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys};
let fromArray = ((xs, ys)): t => {xs, ys};
let fromArrays = (xs, ys): t => {xs, ys};
let fromZippedArray = (is: array((float, float))): t =>
is |> Belt.Array.unzip |> fromArray;
let accumulateYs = (fn, p: t) => {
fromArray((p.xs, E.A.accumulate(fn, p.ys)));
};
let fromZippedArray = (pairs: array((float, float))): t =>
pairs |> Belt.Array.unzip |> fromArray;
let equallyDividedXs = (t: t, newLength) => {
E.A.Floats.range(
minX(t) |> E.O.toExt("Unsafe"),
@ -36,198 +35,195 @@ module T = {
newLength,
);
};
module Ts = {
type t = ts;
let minX = (t: t) =>
t
|> E.A.fmap(minX)
|> E.A.O.concatSomes
|> E.A.min
|> E.O.toExt("Unsafe");
let maxX = (t: t) =>
t
|> E.A.fmap(maxX)
|> E.A.O.concatSomes
|> E.A.max
|> E.O.toExt("Unsafe");
let equallyDividedXs = (t: t, newLength) => {
E.A.Floats.range(minX(t), maxX(t), newLength);
};
let toJs = (t: t) => {
{"xs": t.xs, "ys": t.ys};
};
};
module Pairs = {
let first = (t: t) =>
switch (minX(t), minY(t)) {
| (Some(x), Some(y)) => Some((x, y))
| _ => None
module Ts = {
type t = T.ts;
let minX = (t: t) =>
t
|> E.A.fmap(T.minX)
|> E.A.O.concatSomes
|> E.A.min
|> E.O.toExt("Unsafe");
let maxX = (t: t) =>
t
|> E.A.fmap(T.maxX)
|> E.A.O.concatSomes
|> E.A.max
|> E.O.toExt("Unsafe");
let equallyDividedXs = (t: t, newLength) => {
E.A.Floats.range(minX(t), maxX(t), newLength);
};
let allXs = (t: t) => t |> E.A.fmap(T.xs) |> E.A.Sorted.concatMany;
};
module Pairs = {
let x = fst;
let y = snd;
let first = (t: T.t) =>
switch (T.minX(t), T.minY(t)) {
| (Some(x), Some(y)) => Some((x, y))
| _ => None
};
let last = (t: T.t) =>
switch (T.maxX(t), T.maxY(t)) {
| (Some(x), Some(y)) => Some((x, y))
| _ => None
};
let unsafeFirst = (t: T.t) => first(t) |> E.O.toExn("Unsafe operation");
let unsafeLast = (t: T.t) => last(t) |> E.O.toExn("Unsafe operation");
let getBy = (t: T.t, fn) => t |> T.zip |> E.A.getBy(_, fn);
let firstAtOrBeforeXValue = (xValue, t: T.t) => {
let zipped = T.zip(t);
let firstIndex =
zipped |> Belt.Array.getIndexBy(_, ((x, _)) => x > xValue);
let previousIndex =
switch (firstIndex) {
| None => Some(Array.length(zipped) - 1)
| Some(0) => None
| Some(n) => Some(n - 1)
};
let last = (t: t) =>
switch (maxX(t), maxY(t)) {
| (Some(x), Some(y)) => Some((x, y))
| _ => None
previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped));
};
};
module YtoX = {
let linear = (y: float, t: T.t): float => {
let firstHigherIndex =
E.A.Sorted.binarySearchFirstElementGreaterIndex(T.ys(t), y);
let foundX =
switch (firstHigherIndex) {
| `overMax => T.maxX(t) |> E.O.default(0.0)
| `underMin => T.minX(t) |> E.O.default(0.0)
| `firstHigher(firstHigherIndex) =>
let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let (_xs, _ys) = (T.xs(t), T.ys(t));
let needsInterpolation = _ys[lowerOrEqualIndex] != y;
if (needsInterpolation) {
interpolate(
_ys[lowerOrEqualIndex],
_ys[firstHigherIndex],
_xs[lowerOrEqualIndex],
_xs[firstHigherIndex],
y,
);
} else {
_xs[lowerOrEqualIndex];
};
};
let unsafeFirst = (t: t) => first(t) |> E.O.toExn("Unsafe operation");
let unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation");
foundX;
};
};
let getBy = (t: t, fn) => t |> zip |> Belt.Array.getBy(_, fn);
module XtoY = {
let stepwiseIncremental = (f, t: T.t) =>
Pairs.firstAtOrBeforeXValue(f, t) |> E.O.fmap(Pairs.y);
let firstAtOrBeforeXValue = (xValue, t: t) => {
let zipped = zip(t);
let firstIndex =
zipped |> Belt.Array.getIndexBy(_, ((x, _)) => x > xValue);
let previousIndex =
switch (firstIndex) {
| None => Some(Array.length(zipped) - 1)
| Some(0) => None
| Some(n) => Some(n - 1)
let stepwiseIfAtX = (f: float, t: T.t) => {
Pairs.getBy(t, ((x: float, _)) => {x == f}) |> E.O.fmap(Pairs.y);
};
let linear = (x: float, t: T.t): float => {
let firstHigherIndex =
E.A.Sorted.binarySearchFirstElementGreaterIndex(T.xs(t), x);
let n =
switch (firstHigherIndex) {
| `overMax => T.maxY(t) |> E.O.default(0.0)
| `underMin => T.minY(t) |> E.O.default(0.0)
| `firstHigher(firstHigherIndex) =>
let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let (_xs, _ys) = (T.xs(t), T.ys(t));
let needsInterpolation = _xs[lowerOrEqualIndex] != x;
if (needsInterpolation) {
interpolate(
_xs[lowerOrEqualIndex],
_xs[firstHigherIndex],
_ys[lowerOrEqualIndex],
_ys[firstHigherIndex],
x,
);
} else {
_ys[lowerOrEqualIndex];
};
previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped));
};
};
n;
};
};
module XsConversion = {
let _replaceWithXs = (newXs: array(float), t: T.t): T.t => {
let newYs = Belt.Array.map(newXs, XtoY.linear(_, t));
{xs: newXs, ys: newYs};
};
module YtoX = {
let linear = (y: float, t: t): float => {
let firstHigherIndex =
E.A.Sorted.binarySearchFirstElementGreaterIndex(ys(t), y);
let foundX =
switch (firstHigherIndex) {
| `overMax => maxX(t) |> E.O.default(0.0)
| `underMin => minX(t) |> E.O.default(0.0)
| `firstHigher(firstHigherIndex) =>
let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let (_xs, _ys) = (xs(t), ys(t));
let needsInterpolation = _ys[lowerOrEqualIndex] != y;
if (needsInterpolation) {
interpolate(
_ys[lowerOrEqualIndex],
_ys[firstHigherIndex],
_xs[lowerOrEqualIndex],
_xs[firstHigherIndex],
y,
);
} else {
_xs[lowerOrEqualIndex];
};
};
foundX;
};
let equallyDivideXByMass = (newLength: int, integral: T.t) =>
E.A.Floats.range(0.0, 1.0, newLength)
|> E.A.fmap(YtoX.linear(_, integral));
let proportionEquallyOverX = (newLength: int, t: T.t): T.t => {
T.equallyDividedXs(t, newLength) |> _replaceWithXs(_, t);
};
module XtoY = {
let stepwiseIncremental = (f, t: t) =>
Pairs.firstAtOrBeforeXValue(f, t) |> E.O.fmap(((_, y)) => y);
let proportionByProbabilityMass =
(newLength: int, integral: T.t, t: T.t): T.t => {
equallyDivideXByMass(newLength, integral) |> _replaceWithXs(_, t);
};
};
let stepwiseIfAtX = (f: float, t: t) => {
Pairs.getBy(t, ((x: float, _)) => {x == f})
|> E.O.fmap(((_, y)) => y);
};
module Zipped = {
type zipped = array((float, float));
let sortByY = (t: zipped) =>
t |> E.A.stableSortBy(_, ((_, y1), (_, y2)) => y1 > y2 ? 1 : 0);
let sortByX = (t: zipped) =>
t |> E.A.stableSortBy(_, ((x1, _), (x2, _)) => x1 > x2 ? 1 : 0);
};
let linear = (x: float, t: t): float => {
let firstHigherIndex =
E.A.Sorted.binarySearchFirstElementGreaterIndex(xs(t), x);
let n =
switch (firstHigherIndex) {
| `overMax => maxY(t) |> E.O.default(0.0)
| `underMin => minY(t) |> E.O.default(0.0)
| `firstHigher(firstHigherIndex) =>
let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let (_xs, _ys) = (xs(t), ys(t));
let needsInterpolation = _xs[lowerOrEqualIndex] != x;
if (needsInterpolation) {
interpolate(
_xs[lowerOrEqualIndex],
_xs[firstHigherIndex],
_ys[lowerOrEqualIndex],
_ys[firstHigherIndex],
x,
);
} else {
_ys[lowerOrEqualIndex];
};
};
n;
};
module Combine = {
type xsSelection =
| ALL_XS
| XS_EVENLY_DIVIDED(int);
type xToYSelection =
| LINEAR
| STEPWISE_INCREMENTAL
| STEPWISE_IF_AT_X;
let combine =
(
~xToYSelection: (float, T.t) => 'a,
~xsSelection=ALL_XS,
~fn,
t1: T.t,
t2: T.t,
) => {
let allXs =
switch (xsSelection) {
| ALL_XS => Ts.allXs([|t1, t2|])
| XS_EVENLY_DIVIDED(sampleCount) =>
Ts.equallyDividedXs([|t1, t2|], sampleCount)
};
let allYs =
allXs |> E.A.fmap(x => fn(xToYSelection(x, t1), xToYSelection(x, t2)));
T.fromArrays(allXs, allYs);
};
module XsConversion = {
let replaceWithXs = (newXs: array(float), t: t): t => {
let newYs = Belt.Array.map(newXs, f => XtoY.linear(f, t));
{xs: newXs, ys: newYs};
};
let combineLinear = combine(~xToYSelection=XtoY.linear);
let combineStepwise = combine(~xToYSelection=XtoY.stepwiseIncremental);
let combineIfAtX = combine(~xToYSelection=XtoY.stepwiseIfAtX);
let proportionEquallyOverX = (newLength: int, t: t): t => {
equallyDividedXs(t, newLength) |> replaceWithXs(_, t);
};
let proportionByProbabilityMass = (newLength: int, integral: t, t: t): t => {
E.A.Floats.range(0.0, 1.0, newLength)
|> E.A.fmap(YtoX.linear(_, integral))
|> replaceWithXs(_, t);
};
// 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) => {
E.A.intersperse(T.zip(t1), T.zip(t2)) |> T.fromZippedArray;
};
module Zipped = {
type zipped = array((float, float));
let sortByY = (t: zipped) =>
t |> E.A.stableSortBy(_, ((_, y1), (_, y2)) => y1 > y2 ? 1 : 0);
let sortByX = (t: zipped) =>
t |> E.A.stableSortBy(_, ((x1, _), (x2, _)) => x1 > x2 ? 1 : 0);
};
module Combine = {
let _allXs = (t1: t, t2: t) => E.A.Sorted.concat(xs(t1), xs(t2));
let _combineAbstract = (comboAlg, t1: t, t2: t, fn) => {
let allXs = _allXs(t1, t2);
let allYs =
allXs
|> E.A.fmap(x => {
let y1 = comboAlg(x, t1);
let y2 = comboAlg(x, t2);
fn(y1, y2);
});
fromArrays(allXs, allYs);
};
let combineLinear = _combineAbstract(XtoY.linear);
let combineStepwise = _combineAbstract(XtoY.stepwiseIncremental);
let combineIfAtX = _combineAbstract(XtoY.stepwiseIfAtX);
// TODO: I'd bet this is pretty slow
let intersperse = (t1: t, t2: t) => {
E.A.intersperse(zip(t1), zip(t2)) |> fromZippedArray;
};
};
module Transversal = {
let _transverse = (fn, items) => {
let length = items |> E.A.length;
let empty = Belt.Array.make(length, items |> E.A.unsafe_get(_, 0));
Belt.Array.forEachWithIndex(
items,
(index, element) => {
let item =
switch (index) {
| 0 => element
| index => fn(element, E.A.unsafe_get(empty, index - 1))
};
let _ = Belt.Array.set(empty, index, item);
();
},
);
empty;
};
let _transverseShape = (fn, p: t) => {
fromArray((p.xs, _transverse(fn, p.ys)));
};
};
let accumulateYs =
Transversal._transverseShape((aCurrent, aLast) => aCurrent +. aLast);
};
// I'm really not sure this part is actually what we want at this point.
@ -235,12 +231,10 @@ module Range = {
// ((lastX, lastY), (nextX, nextY))
type zippedRange = ((float, float), (float, float));
let floatSum = Belt.Array.reduce(_, 0., (a, b) => a +. b);
let toT = T.fromZippedArray;
let nextX = ((_, (nextX, _)): zippedRange) => nextX;
let rangePointAssumingSteps =
(((lastX, lastY), (nextX, nextY)): zippedRange) => (
let rangePointAssumingSteps = (((_, lastY), (nextX, _)): zippedRange) => (
nextX,
lastY,
);
@ -270,14 +264,15 @@ module Range = {
let integrateWithTriangles = ({xs, ys}) => {
let length = E.A.length(xs);
let cumulativeY = Belt.Array.make(length, 0.0);
//TODO: I don't think this next line is needed, but definitely check.
let _ = Belt.Array.set(cumulativeY, 0, 0.0);
for (x in 0 to E.A.length(xs) - 2) {
Belt.Array.set(
cumulativeY,
x + 1,
(xs[x + 1] -. xs[x]) *. ((ys[x] +. ys[x + 1]) /. 2.) +. cumulativeY[x],
);
let _ =
Belt.Array.set(
cumulativeY,
x + 1,
(xs[x + 1] -. xs[x])
*. ((ys[x] +. ys[x + 1]) /. 2.)
+. cumulativeY[x],
);
();
};
Some({xs, ys: cumulativeY});
@ -295,7 +290,7 @@ module Range = {
items
|> Belt.Array.map(_, rangePointAssumingSteps)
|> T.fromZippedArray
|> T.Combine.intersperse(t |> T.xMap(e => e +. diff)),
|> Combine.intersperse(t |> T.mapX(e => e +. diff)),
)
| _ => Some(t)
};
@ -310,25 +305,21 @@ module Range = {
};
};
let combinePointwise = (fn, sampleCount, t1: xyShape, t2: xyShape) => {
let xs = T.Ts.equallyDividedXs([|t1, t2|], sampleCount);
let ys =
xs |> E.A.fmap(x => fn(T.XtoY.linear(x, t1), T.XtoY.linear(x, t2)));
T.fromArrays(xs, ys);
};
let logScoreDist =
combinePointwise((prediction, answer) =>
switch (answer) {
| 0. => 0.0
| answer =>
answer *. Js.Math.log2(Js.Math.abs_float(prediction /. answer))
}
);
let pointLogScore = (prediction, answer) =>
switch (answer) {
| 0. => 0.0
| answer => answer *. Js.Math.log2(Js.Math.abs_float(prediction /. answer))
};
let logScorePoint = (sampleCount, t1, t2) =>
logScoreDist(sampleCount, t1, t2)
Combine.combine(
~xsSelection=XS_EVENLY_DIVIDED(sampleCount),
~xToYSelection=XtoY.linear,
~fn=pointLogScore,
t1,
t2,
)
|> Range.integrateWithTriangles
|> E.O.fmap(T.accumulateYs)
|> E.O.bind(_, T.Pairs.last)
|> E.O.fmap(((_, y)) => y);
|> E.O.fmap(T.accumulateYs((+.)))
|> E.O.bind(_, Pairs.last)
|> E.O.fmap(Pairs.y);

View File

@ -76,6 +76,17 @@ module O = {
| None => Error(error)
};
let compare = (compare, f1: option(float), f2: option(float)) =>
switch (f1, f2) {
| (Some(f1), Some(f2)) => Some(compare(f1, f2) ? f1 : f2)
| (Some(f1), None) => Some(f1)
| (None, Some(f2)) => Some(f2)
| (None, None) => None
};
let min = compare((<));
let max = compare((>));
module React = {
let defaultNull = default(ReasonReact.null);
let fmapOrNull = fn => fmap(fn) ||> default(ReasonReact.null);
@ -289,6 +300,26 @@ module A = {
items^;
};
// This is like map, but
//accumulate((a,b) => a + b, [1,2,3]) => [1, 3, 5]
let accumulate = (fn: ('a, 'a) => 'a, items: array('a)) => {
let length = items |> length;
let empty = Belt.Array.make(length, items |> unsafe_get(_, 0));
Belt.Array.forEachWithIndex(
items,
(index, element) => {
let item =
switch (index) {
| 0 => element
| index => fn(element, unsafe_get(empty, index - 1))
};
let _ = Belt.Array.set(empty, index, item);
();
},
);
empty;
};
// @todo: Is -1 still the indicator that this is false (as is true with
// @todo: js findIndex)? Wasn't sure.
let findIndex = (e, i) =>
@ -356,6 +387,12 @@ module A = {
ts;
};
let concatMany = (t1: array(array('a))) => {
let ts = Belt.Array.concatMany(t1);
ts |> Array.fast_sort(compare);
ts;
};
module Floats = {
let makeIncrementalUp = (a, b) =>
Array.make(b - a + 1, a)