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

View File

@ -40,16 +40,16 @@ describe("XYShapes", () => {
Some(210.3721280423322), Some(210.3721280423322),
); );
}); });
describe("transverse", () => { // describe("transverse", () => {
makeTest( // makeTest(
"When very different", // "When very different",
XYShape.T.Transversal._transverse( // XYShape.Transversal._transverse(
(aCurrent, aLast) => aCurrent +. aLast, // (aCurrent, aLast) => aCurrent +. aLast,
[|1.0, 2.0, 3.0, 4.0|], // [|1.0, 2.0, 3.0, 4.0|],
), // ),
[|1.0, 3.0, 6.0, 10.0|], // [|1.0, 3.0, 6.0, 10.0|],
) // )
}); // });
describe("integrateWithTriangles", () => { describe("integrateWithTriangles", () => {
makeTest( makeTest(
"integrates correctly", "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 = { module type dist = {
type t; type t;
type integral; type integral;
let minX: t => option(float); let minX: t => option(float);
let maxX: 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 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);
@ -45,7 +29,7 @@ module Dist = (T: dist) => {
| (Some(min), Some(max)) => Some(max -. min) | (Some(min), Some(max)) => Some(max -. min)
| _ => None | _ => None
}; };
let pointwiseFmap = T.pointwiseFmap; let mapY = T.mapY;
let xToY = T.xToY; let xToY = T.xToY;
let truncate = T.truncate; let truncate = T.truncate;
let toShape = T.toShape; let toShape = T.toShape;
@ -56,8 +40,7 @@ module Dist = (T: dist) => {
let toScaledDiscrete = T.toScaledDiscrete; let toScaledDiscrete = T.toScaledDiscrete;
// TODO: Move this to each class, have use integral to produce integral in DistPlus class. // TODO: Move this to each class, have use integral to produce integral in DistPlus class.
let scaleBy = (~scale=1.0, t: t) => let scaleBy = (~scale=1.0, t: t) => t |> mapY((r: float) => r *. scale);
t |> pointwiseFmap((r: float) => r *. scale);
module Integral = { module Integral = {
type t = T.integral; type t = T.integral;
@ -87,7 +70,7 @@ module Continuous = {
interpolation, interpolation,
}; };
let lastY = (t: t) => let lastY = (t: t) =>
t |> xyShape |> XYShape.T.Pairs.unsafeLast |> (((_, y)) => y); t |> xyShape |> XYShape.Pairs.unsafeLast |> (((_, y)) => y);
let oShapeMap = let oShapeMap =
(fn, {xyShape, interpolation}: t): option(DistTypes.continuousShape) => (fn, {xyShape, interpolation}: t): option(DistTypes.continuousShape) =>
fn(xyShape) |> E.O.fmap(make(_, interpolation)); fn(xyShape) |> E.O.fmap(make(_, interpolation));
@ -110,19 +93,19 @@ module Continuous = {
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 toDiscreteProbabilityMass = _ => 0.0; let toDiscreteProbabilityMass = _ => 0.0;
let pointwiseFmap = (fn, t: t) => let mapY = (fn, t: t) =>
t |> xyShape |> XYShape.T.pointwiseMap(fn) |> fromShape; t |> xyShape |> XYShape.T.mapY(fn) |> fromShape;
let toShape = (t: t): DistTypes.shape => Continuous(t); let toShape = (t: t): DistTypes.shape => Continuous(t);
let xToY = (f, {interpolation, xyShape}: t) => let xToY = (f, {interpolation, xyShape}: t) =>
switch (interpolation) { switch (interpolation) {
| `Stepwise => | `Stepwise =>
xyShape xyShape
|> XYShape.T.XtoY.stepwiseIncremental(f) |> XYShape.XtoY.stepwiseIncremental(f)
|> E.O.default(0.0) |> E.O.default(0.0)
|> DistTypes.MixedPoint.makeContinuous |> DistTypes.MixedPoint.makeContinuous
| `Linear => | `Linear =>
xyShape xyShape
|> XYShape.T.XtoY.linear(f) |> XYShape.XtoY.linear(f)
|> DistTypes.MixedPoint.makeContinuous |> DistTypes.MixedPoint.makeContinuous
}; };
@ -146,16 +129,16 @@ module Continuous = {
let truncate = (~cache=None, i, t) => let truncate = (~cache=None, i, t) =>
t t
|> shapeMap( |> shapeMap(
XYShape.T.XsConversion.proportionByProbabilityMass( XYShape.XsConversion.proportionByProbabilityMass(
i, i,
integral(~cache, t).xyShape, integral(~cache, t).xyShape,
), ),
); );
let integralEndY = (~cache, t) => t |> integral(~cache) |> lastY; let integralEndY = (~cache, t) => t |> integral(~cache) |> lastY;
let integralXtoY = (~cache, f, t) => 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) => 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 toContinuous = t => Some(t);
let toDiscrete = _ => None; let toDiscrete = _ => None;
let toScaledContinuous = t => Some(t); let toScaledContinuous = t => Some(t);
@ -179,14 +162,14 @@ module Discrete = {
let integral = (~cache, t) => let integral = (~cache, t) =>
switch (cache) { switch (cache) {
| Some(c) => c | Some(c) => c
| None => Continuous.make(XYShape.T.accumulateYs(t), `Stepwise) | None => Continuous.make(XYShape.T.accumulateYs((+.), t), `Stepwise)
}; };
let integralEndY = (~cache, t) => let integralEndY = (~cache, t) =>
t |> integral(~cache) |> Continuous.lastY; t |> integral(~cache) |> Continuous.lastY;
let minX = XYShape.T.minX; let minX = XYShape.T.minX;
let maxX = XYShape.T.maxX; let maxX = XYShape.T.maxX;
let toDiscreteProbabilityMass = t => 1.0; let toDiscreteProbabilityMass = t => 1.0;
let pointwiseFmap = XYShape.T.pointwiseMap; let mapY = XYShape.T.mapY;
let toShape = (t: t): DistTypes.shape => Discrete(t); let toShape = (t: t): DistTypes.shape => Discrete(t);
let toContinuous = _ => None; let toContinuous = _ => None;
let toDiscrete = t => Some(t); let toDiscrete = t => Some(t);
@ -195,14 +178,14 @@ module Discrete = {
let truncate = (~cache=None, i, t: t): DistTypes.discreteShape => let truncate = (~cache=None, i, t: t): DistTypes.discreteShape =>
t t
|> XYShape.T.zip |> XYShape.T.zip
|> XYShape.T.Zipped.sortByY |> XYShape.Zipped.sortByY
|> Belt.Array.reverse |> Belt.Array.reverse
|> Belt.Array.slice(_, ~offset=0, ~len=i) |> Belt.Array.slice(_, ~offset=0, ~len=i)
|> XYShape.T.Zipped.sortByX |> XYShape.Zipped.sortByX
|> XYShape.T.fromZippedArray; |> XYShape.T.fromZippedArray;
let xToY = (f, t) => { let xToY = (f, t) => {
XYShape.T.XtoY.stepwiseIfAtX(f, t) XYShape.XtoY.stepwiseIfAtX(f, t)
|> E.O.default(0.0) |> E.O.default(0.0)
|> DistTypes.MixedPoint.makeDiscrete; |> DistTypes.MixedPoint.makeDiscrete;
}; };
@ -211,13 +194,13 @@ module Discrete = {
t t
|> integral(~cache) |> integral(~cache)
|> Continuous.getShape |> Continuous.getShape
|> XYShape.T.XtoY.linear(f); |> XYShape.XtoY.linear(f);
let integralYtoX = (~cache, f, t) => let integralYtoX = (~cache, f, t) =>
t t
|> integral(~cache) |> integral(~cache)
|> Continuous.getShape |> Continuous.getShape
|> XYShape.T.YtoX.linear(f); |> XYShape.YtoX.linear(f);
}); });
}; };
@ -348,9 +331,10 @@ module Mixed = {
let result = let result =
Continuous.make( Continuous.make(
XYShape.T.Combine.combineLinear( XYShape.Combine.combineLinear(
Continuous.getShape(cont), Continuous.getShape(dist), (a, b) => ~fn=(a, b) => a +. b,
a +. b Continuous.getShape(cont),
Continuous.getShape(dist),
), ),
`Linear, `Linear,
); );
@ -366,22 +350,22 @@ module Mixed = {
t t
|> integral(~cache) |> integral(~cache)
|> Continuous.getShape |> Continuous.getShape
|> XYShape.T.XtoY.linear(f); |> XYShape.XtoY.linear(f);
}; };
let integralYtoX = (~cache, f, t) => { let integralYtoX = (~cache, f, t) => {
t t
|> integral(~cache) |> integral(~cache)
|> Continuous.getShape |> 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. // 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 => { (fn, {discrete, continuous, discreteProbabilityMassFraction}: t): t => {
{ {
discrete: Discrete.T.pointwiseFmap(fn, discrete), discrete: Discrete.T.mapY(fn, discrete),
continuous: Continuous.T.pointwiseFmap(fn, continuous), continuous: Continuous.T.mapY(fn, continuous),
discreteProbabilityMassFraction, discreteProbabilityMassFraction,
}; };
}; };
@ -516,14 +500,10 @@ module Shape = {
}; };
let maxX = (t: t) => let maxX = (t: t) =>
mapToAll(t, (Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX)); mapToAll(t, (Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX));
let pointwiseFmap = (fn, t: t) => let mapY = (fn, t: t) =>
fmap( fmap(
t, t,
( (Mixed.T.mapY(fn), Discrete.T.mapY(fn), Continuous.T.mapY(fn)),
Mixed.T.pointwiseFmap(fn),
Discrete.T.pointwiseFmap(fn),
Continuous.T.pointwiseFmap(fn),
),
); );
}); });
}; };
@ -592,9 +572,7 @@ module DistPlus = {
|> toShape |> toShape
|> Shape.T.toScaledContinuous |> Shape.T.toScaledContinuous
|> E.O.fmap( |> E.O.fmap(
Continuous.T.pointwiseFmap( Continuous.T.mapY(domainIncludedProbabilityMassAdjustment(t)),
domainIncludedProbabilityMassAdjustment(t),
),
); );
}; };
@ -603,9 +581,7 @@ module DistPlus = {
|> toShape |> toShape
|> Shape.T.toScaledDiscrete |> Shape.T.toScaledDiscrete
|> E.O.fmap( |> E.O.fmap(
Discrete.T.pointwiseFmap( Discrete.T.mapY(domainIncludedProbabilityMassAdjustment(t)),
domainIncludedProbabilityMassAdjustment(t),
),
); );
}; };
@ -627,8 +603,8 @@ module DistPlus = {
let truncate = (~cache=None, i, t) => let truncate = (~cache=None, i, t) =>
updateShape(t |> toShape |> Shape.T.truncate(i), t); updateShape(t |> toShape |> Shape.T.truncate(i), t);
// todo: adjust for limit, maybe? // todo: adjust for limit, maybe?
let pointwiseFmap = (fn, {shape, _} as t: t): t => let mapY = (fn, {shape, _} as t: t): t =>
Shape.T.pointwiseFmap(fn, shape) |> updateShape(_, t); Shape.T.mapY(fn, shape) |> updateShape(_, t);
let integralEndY = (~cache as _, t: t) => let integralEndY = (~cache as _, t: t) =>
Shape.T.Integral.sum(~cache=Some(t.integralCache), toShape(t)); Shape.T.Integral.sum(~cache=Some(t.integralCache), toShape(t));

View File

@ -11,10 +11,6 @@ let interpolate =
module T = { module T = {
type t = xyShape; type t = xyShape;
type ts = array(xyShape); type ts = array(xyShape);
let toJs = (t: t) => {
{"xs": t.xs, "ys": t.ys};
};
let xs = (t: t) => t.xs; let xs = (t: t) => t.xs;
let ys = (t: t) => t.ys; let ys = (t: t) => t.ys;
let minX = (t: t) => t |> xs |> E.A.Sorted.min; 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 minY = (t: t) => t |> ys |> E.A.Sorted.min;
let maxY = (t: t) => t |> ys |> E.A.Sorted.max; let maxY = (t: t) => t |> ys |> E.A.Sorted.max;
let xTotalRange = (t: t) => t |> xs |> E.A.Sorted.range; 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 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 fromArray = ((xs, ys)): t => {xs, ys};
let fromArrays = (xs, ys): t => {xs, ys}; let fromArrays = (xs, ys): t => {xs, ys};
let fromZippedArray = (is: array((float, float))): t => let accumulateYs = (fn, p: t) => {
is |> Belt.Array.unzip |> fromArray; 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) => { let equallyDividedXs = (t: t, newLength) => {
E.A.Floats.range( E.A.Floats.range(
minX(t) |> E.O.toExt("Unsafe"), minX(t) |> E.O.toExt("Unsafe"),
@ -36,198 +35,195 @@ module T = {
newLength, newLength,
); );
}; };
let toJs = (t: t) => {
module Ts = { {"xs": t.xs, "ys": t.ys};
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);
};
}; };
};
module Pairs = { module Ts = {
let first = (t: t) => type t = T.ts;
switch (minX(t), minY(t)) { let minX = (t: t) =>
| (Some(x), Some(y)) => Some((x, y)) t
| _ => None |> 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) => previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped));
switch (maxX(t), maxY(t)) { };
| (Some(x), Some(y)) => Some((x, y)) };
| _ => None
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"); foundX;
let unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation"); };
};
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 stepwiseIfAtX = (f: float, t: T.t) => {
let zipped = zip(t); Pairs.getBy(t, ((x: float, _)) => {x == f}) |> E.O.fmap(Pairs.y);
let firstIndex = };
zipped |> Belt.Array.getIndexBy(_, ((x, _)) => x > xValue);
let previousIndex = let linear = (x: float, t: T.t): float => {
switch (firstIndex) { let firstHigherIndex =
| None => Some(Array.length(zipped) - 1) E.A.Sorted.binarySearchFirstElementGreaterIndex(T.xs(t), x);
| Some(0) => None let n =
| Some(n) => Some(n - 1) 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 equallyDivideXByMass = (newLength: int, integral: T.t) =>
let linear = (y: float, t: t): float => { E.A.Floats.range(0.0, 1.0, newLength)
let firstHigherIndex = |> E.A.fmap(YtoX.linear(_, integral));
E.A.Sorted.binarySearchFirstElementGreaterIndex(ys(t), y);
let foundX = let proportionEquallyOverX = (newLength: int, t: T.t): T.t => {
switch (firstHigherIndex) { T.equallyDividedXs(t, newLength) |> _replaceWithXs(_, t);
| `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;
};
}; };
module XtoY = { let proportionByProbabilityMass =
let stepwiseIncremental = (f, t: t) => (newLength: int, integral: T.t, t: T.t): T.t => {
Pairs.firstAtOrBeforeXValue(f, t) |> E.O.fmap(((_, y)) => y); equallyDivideXByMass(newLength, integral) |> _replaceWithXs(_, t);
};
};
let stepwiseIfAtX = (f: float, t: t) => { module Zipped = {
Pairs.getBy(t, ((x: float, _)) => {x == f}) type zipped = array((float, float));
|> E.O.fmap(((_, y)) => y); 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 => { module Combine = {
let firstHigherIndex = type xsSelection =
E.A.Sorted.binarySearchFirstElementGreaterIndex(xs(t), x); | ALL_XS
let n = | XS_EVENLY_DIVIDED(int);
switch (firstHigherIndex) {
| `overMax => maxY(t) |> E.O.default(0.0) type xToYSelection =
| `underMin => minY(t) |> E.O.default(0.0) | LINEAR
| `firstHigher(firstHigherIndex) => | STEPWISE_INCREMENTAL
let lowerOrEqualIndex = | STEPWISE_IF_AT_X;
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let (_xs, _ys) = (xs(t), ys(t)); let combine =
let needsInterpolation = _xs[lowerOrEqualIndex] != x; (
if (needsInterpolation) { ~xToYSelection: (float, T.t) => 'a,
interpolate( ~xsSelection=ALL_XS,
_xs[lowerOrEqualIndex], ~fn,
_xs[firstHigherIndex], t1: T.t,
_ys[lowerOrEqualIndex], t2: T.t,
_ys[firstHigherIndex], ) => {
x, let allXs =
); switch (xsSelection) {
} else { | ALL_XS => Ts.allXs([|t1, t2|])
_ys[lowerOrEqualIndex]; | XS_EVENLY_DIVIDED(sampleCount) =>
}; Ts.equallyDividedXs([|t1, t2|], sampleCount)
}; };
n;
}; let allYs =
allXs |> E.A.fmap(x => fn(xToYSelection(x, t1), xToYSelection(x, t2)));
T.fromArrays(allXs, allYs);
}; };
module XsConversion = { let combineLinear = combine(~xToYSelection=XtoY.linear);
let replaceWithXs = (newXs: array(float), t: t): t => { let combineStepwise = combine(~xToYSelection=XtoY.stepwiseIncremental);
let newYs = Belt.Array.map(newXs, f => XtoY.linear(f, t)); let combineIfAtX = combine(~xToYSelection=XtoY.stepwiseIfAtX);
{xs: newXs, ys: newYs};
};
let proportionEquallyOverX = (newLength: int, t: t): t => { // TODO: I'd bet this is pretty slow. Maybe it would be faster to intersperse Xs and Ys separately.
equallyDividedXs(t, newLength) |> replaceWithXs(_, t); let intersperse = (t1: T.t, t2: T.t) => {
}; E.A.intersperse(T.zip(t1), T.zip(t2)) |> T.fromZippedArray;
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);
};
}; };
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. // 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)) // ((lastX, lastY), (nextX, nextY))
type zippedRange = ((float, float), (float, float)); type zippedRange = ((float, float), (float, float));
let floatSum = Belt.Array.reduce(_, 0., (a, b) => a +. b);
let toT = T.fromZippedArray; let toT = T.fromZippedArray;
let nextX = ((_, (nextX, _)): zippedRange) => nextX; let nextX = ((_, (nextX, _)): zippedRange) => nextX;
let rangePointAssumingSteps = let rangePointAssumingSteps = (((_, lastY), (nextX, _)): zippedRange) => (
(((lastX, lastY), (nextX, nextY)): zippedRange) => (
nextX, nextX,
lastY, lastY,
); );
@ -270,14 +264,15 @@ module Range = {
let integrateWithTriangles = ({xs, ys}) => { let integrateWithTriangles = ({xs, ys}) => {
let length = E.A.length(xs); let length = E.A.length(xs);
let cumulativeY = Belt.Array.make(length, 0.0); 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) { for (x in 0 to E.A.length(xs) - 2) {
Belt.Array.set( let _ =
cumulativeY, Belt.Array.set(
x + 1, cumulativeY,
(xs[x + 1] -. xs[x]) *. ((ys[x] +. ys[x + 1]) /. 2.) +. cumulativeY[x], x + 1,
); (xs[x + 1] -. xs[x])
*. ((ys[x] +. ys[x + 1]) /. 2.)
+. cumulativeY[x],
);
(); ();
}; };
Some({xs, ys: cumulativeY}); Some({xs, ys: cumulativeY});
@ -295,7 +290,7 @@ module Range = {
items items
|> Belt.Array.map(_, rangePointAssumingSteps) |> Belt.Array.map(_, rangePointAssumingSteps)
|> T.fromZippedArray |> T.fromZippedArray
|> T.Combine.intersperse(t |> T.xMap(e => e +. diff)), |> Combine.intersperse(t |> T.mapX(e => e +. diff)),
) )
| _ => Some(t) | _ => Some(t)
}; };
@ -310,25 +305,21 @@ module Range = {
}; };
}; };
let combinePointwise = (fn, sampleCount, t1: xyShape, t2: xyShape) => { let pointLogScore = (prediction, answer) =>
let xs = T.Ts.equallyDividedXs([|t1, t2|], sampleCount); switch (answer) {
let ys = | 0. => 0.0
xs |> E.A.fmap(x => fn(T.XtoY.linear(x, t1), T.XtoY.linear(x, t2))); | answer => answer *. Js.Math.log2(Js.Math.abs_float(prediction /. answer))
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 logScorePoint = (sampleCount, t1, t2) => 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 |> Range.integrateWithTriangles
|> E.O.fmap(T.accumulateYs) |> E.O.fmap(T.accumulateYs((+.)))
|> E.O.bind(_, T.Pairs.last) |> E.O.bind(_, Pairs.last)
|> E.O.fmap(((_, y)) => y); |> E.O.fmap(Pairs.y);

View File

@ -76,6 +76,17 @@ module O = {
| None => Error(error) | 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 = { module React = {
let defaultNull = default(ReasonReact.null); let defaultNull = default(ReasonReact.null);
let fmapOrNull = fn => fmap(fn) ||> default(ReasonReact.null); let fmapOrNull = fn => fmap(fn) ||> default(ReasonReact.null);
@ -289,6 +300,26 @@ module A = {
items^; 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: Is -1 still the indicator that this is false (as is true with
// @todo: js findIndex)? Wasn't sure. // @todo: js findIndex)? Wasn't sure.
let findIndex = (e, i) => let findIndex = (e, i) =>
@ -356,6 +387,12 @@ module A = {
ts; ts;
}; };
let concatMany = (t1: array(array('a))) => {
let ts = Belt.Array.concatMany(t1);
ts |> Array.fast_sort(compare);
ts;
};
module Floats = { module Floats = {
let makeIncrementalUp = (a, b) => let makeIncrementalUp = (a, b) =>
Array.make(b - a + 1, a) Array.make(b - a + 1, a)