First refactor to XYShape

This commit is contained in:
Ozzie Gooen 2020-03-26 23:18:19 +00:00
parent f061e9fa01
commit 6f8d06a6d6
10 changed files with 322 additions and 721 deletions

View File

@ -1,179 +0,0 @@
open Jest;
open Expect;
exception ShapeWrong(string);
describe("CDF", () => {
test("raise - w/o order", () => {
expect(() => {
module Cdf =
CDF.Make({
let shape: DistTypes.xyShape = {
xs: [|10., 4., 8.|],
ys: [|8., 9., 2.|],
};
});
();
})
|> toThrow
});
test("raise - with order", () => {
expect(() => {
module Cdf =
CDF.Make({
let shape: DistTypes.xyShape = {
xs: [|1., 4., 8.|],
ys: [|8., 9., 2.|],
};
});
();
})
|> not_
|> toThrow
});
test("order#1", () => {
let a = CDF.order({xs: [|1., 4., 8.|], ys: [|8., 9., 2.|]});
let b: DistTypes.xyShape = {xs: [|1., 4., 8.|], ys: [|8., 9., 2.|]};
expect(a) |> toEqual(b);
});
test("order#2", () => {
let a = CDF.order({xs: [|10., 5., 12.|], ys: [|8., 9., 2.|]});
let b: DistTypes.xyShape = {xs: [|5., 10., 12.|], ys: [|9., 8., 2.|]};
expect(a) |> toEqual(b);
});
describe("minX - maxX", () => {
module Dist =
CDF.Make({
let shape = CDF.order({xs: [|20., 4., 8.|], ys: [|8., 9., 2.|]});
});
test("minX", () => {
expect(Dist.minX()) |> toEqual(4.)
});
test("maxX", () => {
expect(Dist.maxX()) |> toEqual(20.)
});
});
describe("findY", () => {
module Dist =
CDF.Make({
let shape = CDF.order({xs: [|1., 2., 3.|], ys: [|5., 6., 7.|]});
});
test("#1", () => {
expect(Dist.findY(1.)) |> toEqual(5.)
});
test("#2", () => {
expect(Dist.findY(1.5)) |> toEqual(5.5)
});
test("#3", () => {
expect(Dist.findY(3.)) |> toEqual(7.)
});
test("#4", () => {
expect(Dist.findY(4.)) |> toEqual(7.)
});
test("#5", () => {
expect(Dist.findY(15.)) |> toEqual(7.)
});
test("#6", () => {
expect(Dist.findY(-1.)) |> toEqual(5.)
});
});
describe("findX", () => {
module Dist =
CDF.Make({
let shape = CDF.order({xs: [|1., 2., 3.|], ys: [|5., 6., 7.|]});
});
test("#1", () => {
expect(Dist.findX(5.)) |> toEqual(1.)
});
test("#2", () => {
expect(Dist.findX(7.)) |> toEqual(3.)
});
test("#3", () => {
expect(Dist.findX(5.5)) |> toEqual(1.5)
});
test("#4", () => {
expect(Dist.findX(8.)) |> toEqual(3.)
});
test("#5", () => {
expect(Dist.findX(4.)) |> toEqual(1.)
});
});
describe("convertWithAlternativeXs", () => {
open Functions;
let xs = up(1, 9);
let ys = up(20, 28);
module Dist =
CDF.Make({
let shape = CDF.order({xs, ys});
});
let xs2 = up(3, 7);
module Dist2 =
CDF.Make({
let shape = Dist.convertWithAlternativeXs(xs2);
});
test("#1", () => {
expect(Dist2.xs) |> toEqual([|3., 4., 5., 6., 7.|])
});
test("#2", () => {
expect(Dist2.ys) |> toEqual([|22., 23., 24., 25., 26.|])
});
});
describe("convertToNewLength", () => {
open Functions;
let xs = up(1, 9);
let ys = up(50, 58);
module Dist =
CDF.Make({
let shape = CDF.order({xs, ys});
});
module Dist2 =
CDF.Make({
let shape = Dist.convertToNewLength(3);
});
test("#1", () => {
expect(Dist2.xs) |> toEqual([|1., 5., 9.|])
});
test("#2", () => {
expect(Dist2.ys) |> toEqual([|50., 54., 58.|])
});
});
// @todo: Should each test expect 70.?
describe("sample", () => {
open Functions;
let xs = up(1, 9);
let ys = up(70, 78);
module Dist =
CDF.Make({
let shape = CDF.order({xs, ys});
});
let xs2 = Dist.sample(3);
test("#1", () => {
expect(xs2[0]) |> toBe(70.)
});
test("#2", () => {
expect(xs2[1]) |> toBe(70.)
});
test("#3", () => {
expect(xs2[2]) |> toBe(70.)
});
});
describe("integral", () => {
module Dist =
CDF.Make({
let shape =
CDF.order({xs: [|0., 1., 2., 4.|], ys: [|0.0, 1.0, 2.0, 2.0|]});
});
test("with regular inputs", () => {
expect(Dist.integral()) |> toBe(6.)
});
});
});

View File

@ -1,92 +0,0 @@
open Jest;
open Expect;
exception ShapeWrong(string);
describe("Functions", () => {
test("interpolate", () => {
let a = Functions.interpolate(10., 20., 1., 2., 15.);
let b = 1.5;
expect(a) |> toEqual(b);
});
test("range#1", () => {
expect(Functions.range(1., 5., 3)) |> toEqual([|1., 3., 5.|])
});
test("range#2", () => {
expect(Functions.range(1., 5., 5)) |> toEqual([|1., 2., 3., 4., 5.|])
});
test("range#3", () => {
expect(Functions.range(-10., 15., 2)) |> toEqual([|(-10.), 15.|])
});
test("range#4", () => {
expect(Functions.range(-10., 15., 3)) |> toEqual([|(-10.), 2.5, 15.|])
});
test("range#5", () => {
expect(Functions.range(-10.3, 17., 3))
|> toEqual([|(-10.3), 3.3499999999999996, 17.|])
});
test("range#6", () => {
expect(Functions.range(-10.3, 17., 5))
|> toEqual([|
(-10.3),
(-3.4750000000000005),
3.3499999999999996,
10.175,
17.0,
|])
});
test("range#7", () => {
expect(Functions.range(-10.3, 17.31, 3))
|> toEqual([|(-10.3), 3.504999999999999, 17.31|])
});
test("range#8", () => {
expect(Functions.range(1., 1., 3)) |> toEqual([|1., 1., 1.|])
});
test("mean#1", () => {
expect(Functions.mean([|1., 2., 3.|])) |> toEqual(2.)
});
test("mean#2", () => {
expect(Functions.mean([|1., 2., 3., (-2.)|])) |> toEqual(1.)
});
test("mean#3", () => {
expect(Functions.mean([|1., 2., 3., (-2.), (-10.)|])) |> toEqual(-1.2)
});
test("min#1", () => {
expect(Functions.min([|1., 2., 3.|])) |> toEqual(1.)
});
test("min#2", () => {
expect(Functions.min([|(-1.), (-2.), 0., 20.|])) |> toEqual(-2.)
});
test("min#3", () => {
expect(Functions.min([|(-1.), (-2.), 0., 20., (-2.2)|]))
|> toEqual(-2.2)
});
test("max#1", () => {
expect(Functions.max([|1., 2., 3.|])) |> toEqual(3.)
});
test("max#2", () => {
expect(Functions.max([|(-1.), (-2.), 0., 20.|])) |> toEqual(20.)
});
test("max#3", () => {
expect(Functions.max([|(-1.), (-2.), 0., (-2.2)|])) |> toEqual(0.)
});
test("random#1", () => {
expect(Functions.random(1, 5)) |> toBeLessThanOrEqual(5)
});
test("random#2", () => {
expect(Functions.random(1, 5)) |> toBeGreaterThanOrEqual(1)
});
test("up#1", () => {
expect(Functions.up(1, 5)) |> toEqual([|1., 2., 3., 4., 5.|])
});
test("up#2", () => {
expect(Functions.up(-1, 5))
|> toEqual([|(-1.), 0., 1., 2., 3., 4., 5.|])
});
test("down#1", () => {
expect(Functions.down(5, 1)) |> toEqual([|5., 4., 3., 2., 1.|])
});
test("down#2", () => {
expect(Functions.down(5, -1))
|> toEqual([|5., 4., 3., 2., 1., 0., (-1.)|])
});
});

View File

@ -43,7 +43,7 @@ describe("XYShapes", () => {
describe("transverse", () => { describe("transverse", () => {
makeTest( makeTest(
"When very different", "When very different",
XYShape.T._transverse2( XYShape.T.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|],
), ),

View File

@ -91,7 +91,7 @@ module T = {
// todo: Figure out some way of doing this without having to integrate so many times. // todo: Figure out some way of doing this without having to integrate so many times.
let toShape = (~samples: t, ~outputXYPoints=3000, ~kernelWidth=10, ()) => { let toShape = (~samples: t, ~outputXYPoints=3000, ~kernelWidth=10, ()) => {
Array.fast_sort(compare, samples); Array.fast_sort(compare, samples);
let (continuousPart, discretePart) = E.A.Floats.split(samples); let (continuousPart, discretePart) = E.A.Sorted.Floats.split(samples);
let length = samples |> E.A.length; let length = samples |> E.A.length;
let lengthFloat = float_of_int(length); let lengthFloat = float_of_int(length);
let discrete: DistTypes.xyShape = let discrete: DistTypes.xyShape =

View File

@ -87,7 +87,7 @@ module Continuous = {
interpolation, interpolation,
}; };
let lastY = (t: t) => let lastY = (t: t) =>
t |> xyShape |> XYShape.T.unsafeLast |> (((_, y)) => y); t |> xyShape |> XYShape.T.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));
@ -146,16 +146,16 @@ module Continuous = {
let truncate = (~cache=None, i, t) => let truncate = (~cache=None, i, t) =>
t t
|> shapeMap( |> shapeMap(
XYShape.T.convertToNewLengthByProbabilityMass( XYShape.T.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.findY(f)); t |> integral(~cache) |> shapeFn(XYShape.T.XtoY.linear(f));
let integralYtoX = (~cache, f, t) => let integralYtoX = (~cache, f, t) =>
t |> integral(~cache) |> shapeFn(XYShape.T.findX(f)); t |> integral(~cache) |> shapeFn(XYShape.T.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);
@ -208,10 +208,16 @@ module Discrete = {
}; };
let integralXtoY = (~cache, f, t) => let integralXtoY = (~cache, f, t) =>
t |> integral(~cache) |> Continuous.getShape |> XYShape.T.findY(f); t
|> integral(~cache)
|> Continuous.getShape
|> XYShape.T.XtoY.linear(f);
let integralYtoX = (~cache, f, t) => let integralYtoX = (~cache, f, t) =>
t |> integral(~cache) |> Continuous.getShape |> XYShape.T.findX(f); t
|> integral(~cache)
|> Continuous.getShape
|> XYShape.T.YtoX.linear(f);
}); });
}; };
@ -357,11 +363,17 @@ module Mixed = {
}; };
let integralXtoY = (~cache, f, t) => { let integralXtoY = (~cache, f, t) => {
t |> integral(~cache) |> Continuous.getShape |> XYShape.T.findY(f); t
|> integral(~cache)
|> Continuous.getShape
|> XYShape.T.XtoY.linear(f);
}; };
let integralYtoX = (~cache, f, t) => { let integralYtoX = (~cache, f, t) => {
t |> integral(~cache) |> Continuous.getShape |> XYShape.T.findX(f); t
|> integral(~cache)
|> Continuous.getShape
|> XYShape.T.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.

View File

@ -1,5 +1,13 @@
open DistTypes; open DistTypes;
let interpolate =
(xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float)
: float => {
let minProportion = (xMax -. xIntended) /. (xMax -. xMin);
let maxProportion = (xIntended -. xMin) /. (xMax -. xMin);
yMin *. minProportion +. yMax *. maxProportion;
};
module T = { module T = {
type t = xyShape; type t = xyShape;
type ts = array(xyShape); type ts = array(xyShape);
@ -9,36 +17,65 @@ module T = {
}; };
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.first; let minX = (t: t) => t |> xs |> E.A.Sorted.min;
let maxX = (t: t) => t |> xs |> E.A.last; let maxX = (t: t) => t |> xs |> E.A.Sorted.max;
let minY = (t: t) => t |> ys |> E.A.first; let minY = (t: t) => t |> ys |> E.A.Sorted.min;
let maxY = (t: t) => t |> ys |> E.A.last; let maxY = (t: t) => t |> ys |> E.A.Sorted.max;
let xTotalRange = (t: t) => let xTotalRange = (t: t) => t |> xs |> E.A.Sorted.range;
switch (minX(t), maxX(t)) { let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys);
| (Some(min), Some(max)) => Some(max -. min) let pointwiseMap = (fn, t: t): t => {xs: t.xs, ys: t.ys |> E.A.fmap(fn)};
| _ => None let xMap = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys};
}; let fromArray = ((xs, ys)): t => {xs, ys};
let first = ({xs, ys}: t) => let fromArrays = (xs, ys): t => {xs, ys};
switch (xs |> E.A.first, ys |> E.A.first) { let fromZippedArray = (is: array((float, float))): t =>
| (Some(x), Some(y)) => Some((x, y)) is |> Belt.Array.unzip |> fromArray;
| _ => None let equallyDividedXs = (t: t, newLength) => {
}; E.A.Floats.range(
let last = ({xs, ys}: t) => minX(t) |> E.O.toExt("Unsafe"),
switch (xs |> E.A.last, ys |> E.A.last) { maxX(t) |> E.O.toExt("Unsafe"),
| (Some(x), Some(y)) => Some((x, y)) newLength,
| _ => None );
}; };
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);
};
};
module Pairs = {
let first = (t: t) =>
switch (minX(t), minY(t)) {
| (Some(x), Some(y)) => Some((x, y))
| _ => None
};
let last = (t: t) =>
switch (maxX(t), maxY(t)) {
| (Some(x), Some(y)) => Some((x, y))
| _ => None
};
let unsafeFirst = (t: t) => first(t) |> E.O.toExn("Unsafe operation"); let unsafeFirst = (t: t) => first(t) |> E.O.toExn("Unsafe operation");
let unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation"); let unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation");
let zip = ({xs, ys}: t) => Belt.Array.zip(xs, 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 firstAtOrBeforeXValue = (xValue, t: t) => {
let zipped = zip(t); let zipped = zip(t);
let firstIndex = let firstIndex =
zipped |> Belt.Array.getIndexBy(_, ((x, y)) => x > xValue); zipped |> Belt.Array.getIndexBy(_, ((x, _)) => x > xValue);
let previousIndex = let previousIndex =
switch (firstIndex) { switch (firstIndex) {
| None => Some(Array.length(zipped) - 1) | None => Some(Array.length(zipped) - 1)
@ -47,34 +84,10 @@ module T = {
}; };
previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped)); previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped));
}; };
let findY = (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 needsInterpolation = xs(t)[lowerOrEqualIndex] != x;
if (needsInterpolation) {
Functions.interpolate(
xs(t)[lowerOrEqualIndex],
xs(t)[firstHigherIndex],
ys(t)[lowerOrEqualIndex],
ys(t)[firstHigherIndex],
x,
);
} else {
ys(t)[lowerOrEqualIndex];
};
};
n;
}; };
let findX = (y: float, t: t): float => { module YtoX = {
let linear = (y: float, t: t): float => {
let firstHigherIndex = let firstHigherIndex =
E.A.Sorted.binarySearchFirstElementGreaterIndex(ys(t), y); E.A.Sorted.binarySearchFirstElementGreaterIndex(ys(t), y);
let foundX = let foundX =
@ -84,59 +97,77 @@ module T = {
| `firstHigher(firstHigherIndex) => | `firstHigher(firstHigherIndex) =>
let lowerOrEqualIndex = let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1; firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let needsInterpolation = ys(t)[lowerOrEqualIndex] != y; let (_xs, _ys) = (xs(t), ys(t));
let needsInterpolation = _ys[lowerOrEqualIndex] != y;
if (needsInterpolation) { if (needsInterpolation) {
Functions.interpolate( interpolate(
ys(t)[lowerOrEqualIndex], _ys[lowerOrEqualIndex],
ys(t)[firstHigherIndex], _ys[firstHigherIndex],
xs(t)[lowerOrEqualIndex], _xs[lowerOrEqualIndex],
xs(t)[firstHigherIndex], _xs[firstHigherIndex],
y, y,
); );
} else { } else {
xs(t)[lowerOrEqualIndex]; _xs[lowerOrEqualIndex];
}; };
}; };
foundX; foundX;
}; };
let convertWithAlternativeXs = (newXs: array(float), t: t): t => {
let newYs = Belt.Array.map(newXs, f => findY(f, t));
{xs: newXs, ys: newYs};
};
let convertToNewLength = (newLength: int, t: t): DistTypes.xyShape => {
Functions.(
range(min(xs(t)), max(xs(t)), newLength)
|> convertWithAlternativeXs(_, t)
);
};
let convertToNewLengthByProbabilityMass =
(newLength: int, integral: t, t: t): DistTypes.xyShape => {
Functions.range(0.0, 1.0, newLength)
|> E.A.fmap(findX(_, integral))
|> convertWithAlternativeXs(_, t);
}; };
module XtoY = { module XtoY = {
let stepwiseIncremental = (f, t: t) => let stepwiseIncremental = (f, t: t) =>
firstPairAtOrBeforeValue(f, t) |> E.O.fmap(((_, y)) => y); Pairs.firstAtOrBeforeXValue(f, t) |> E.O.fmap(((_, y)) => y);
let stepwiseIfAtX = (f: float, t: t) => { let stepwiseIfAtX = (f: float, t: t) => {
getBy(t, ((x: float, _)) => {x == f}) |> E.O.fmap(((_, y)) => y); Pairs.getBy(t, ((x: float, _)) => {x == f})
|> E.O.fmap(((_, y)) => y);
}; };
// TODO: When Roman's PR comes in, fix this bit. This depends on interpolation, obviously. let linear = (x: float, t: t): float => {
let linear = (f, t: t) => t |> findY(f); 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;
};
}; };
let pointwiseMap = (fn, t: t): t => {xs: t.xs, ys: t.ys |> E.A.fmap(fn)}; module XsConversion = {
let xMap = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys}; let replaceWithXs = (newXs: array(float), t: t): t => {
let fromArray = ((xs, ys)): t => {xs, ys}; let newYs = Belt.Array.map(newXs, f => XtoY.linear(f, t));
let fromArrays = (xs, ys): t => {xs, ys}; {xs: newXs, ys: newYs};
let fromZippedArray = (is: array((float, float))): t => };
is |> Belt.Array.unzip |> fromArray;
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);
};
};
module Zipped = { module Zipped = {
type zipped = array((float, float)); type zipped = array((float, float));
@ -146,90 +177,33 @@ module T = {
t |> E.A.stableSortBy(_, ((x1, _), (x2, _)) => x1 > x2 ? 1 : 0); t |> E.A.stableSortBy(_, ((x1, _), (x2, _)) => x1 > x2 ? 1 : 0);
}; };
// TODO: Use faster sort at least, geese.
module Combine = { module Combine = {
let combineLinear = (t1: t, t2: t, fn: (float, float) => float) => { let _allXs = (t1: t, t2: t) => E.A.Sorted.concat(xs(t1), xs(t2));
let allXs = Belt.Array.concat(xs(t1), xs(t2));
allXs |> Array.fast_sort(compare); let _combineAbstract = (comboAlg, t1: t, t2: t, fn) => {
let allXs = _allXs(t1, t2);
let allYs = let allYs =
allXs allXs
|> E.A.fmap(x => { |> E.A.fmap(x => {
let y1 = XtoY.linear(x, t1); let y1 = comboAlg(x, t1);
let y2 = XtoY.linear(x, t2); let y2 = comboAlg(x, t2);
fn(y1, y2); fn(y1, y2);
}); });
fromArrays(allXs, allYs); fromArrays(allXs, allYs);
}; };
let combineStepwise = let combineLinear = _combineAbstract(XtoY.linear);
(t1: t, t2: t, fn: (option(float), option(float)) => float) => { let combineStepwise = _combineAbstract(XtoY.stepwiseIncremental);
let allXs = Belt.Array.concat(xs(t1), xs(t2)); let combineIfAtX = _combineAbstract(XtoY.stepwiseIfAtX);
allXs |> Array.fast_sort(compare);
let allYs =
allXs
|> E.A.fmap(x => {
let y1 = XtoY.stepwiseIncremental(x, t1);
let y2 = XtoY.stepwiseIncremental(x, t2);
fn(y1, y2);
});
fromArrays(allXs, allYs);
};
let combineIfAtX =
(t1: t, t2: t, fn: (option(float), option(float)) => float) => {
let allXs = Belt.Array.concat(xs(t1), xs(t2));
allXs |> Array.fast_sort(compare);
let allYs =
allXs
|> E.A.fmap(x => {
let y1 = XtoY.stepwiseIfAtX(x, t1);
let y2 = XtoY.stepwiseIfAtX(x, t2);
fn(y1, y2);
});
fromArrays(allXs, allYs);
};
};
// todo: maybe not needed?
// let comparePoint = (a: float, b: float) => a > b ? 1 : (-1);
let comparePoints = ((x1: float, y1: float), (x2: float, y2: float)) =>
switch (x1 == x2, y1 == y2) {
| (false, _) => compare(x1, x2)
| (true, false) => compare(y1, y2)
| (true, true) => (-1)
};
// TODO: I'd bet this is pretty slow // TODO: I'd bet this is pretty slow
let intersperce = (t1: t, t2: t) => { let intersperse = (t1: t, t2: t) => {
let items: ref(array((float, float))) = ref([||]); E.A.intersperse(zip(t1), zip(t2)) |> fromZippedArray;
let t1 = zip(t1); };
let t2 = zip(t2);
Belt.Array.forEachWithIndex(t1, (i, item) => {
switch (Belt.Array.get(t2, i)) {
| Some(r) => items := E.A.append(items^, [|item, r|])
| None => items := E.A.append(items^, [|item|])
}
});
items^ |> Belt.Array.unzip |> fromArray;
}; };
let yFold = (fn, t: t) => { module Transversal = {
E.A.fold_left(fn, 0., t.ys); let _transverse = (fn, items) => {
};
let ySum = yFold((a, b) => a +. b);
let _transverseSimple = fn =>
Belt.Array.reduce(_, [||], (items, y) =>
switch (E.A.last(items)) {
| Some(yLast) => Belt.Array.concat(items, [|fn(y, yLast)|])
| None => [|y|]
}
);
let _transverse2 = (fn, items) => {
let length = items |> E.A.length; let length = items |> E.A.length;
let empty = Belt.Array.make(length, items |> E.A.unsafe_get(_, 0)); let empty = Belt.Array.make(length, items |> E.A.unsafe_get(_, 0));
Belt.Array.forEachWithIndex( Belt.Array.forEachWithIndex(
@ -247,37 +221,13 @@ module T = {
empty; empty;
}; };
let _transverseB = (fn, items) => {
let (xs, ys) = items |> Belt.Array.unzip;
let newYs = _transverse2(fn, ys);
Belt.Array.zip(xs, newYs);
};
let _transverse = fn =>
Belt.Array.reduce(_, [||], (items, (x, y)) =>
switch (E.A.last(items)) {
| Some((_, yLast)) =>
Belt.Array.concat(items, [|(x, fn(y, yLast))|])
| None => [|(x, y)|]
}
);
let _transverseShape2 = (fn, p: t) => {
Belt.Array.zip(p.xs, p.ys)
|> _transverseB(fn)
|> Belt.Array.unzip
|> fromArray;
};
let _transverseShape = (fn, p: t) => { let _transverseShape = (fn, p: t) => {
fromArray((p.xs, _transverse2(fn, p.ys))); fromArray((p.xs, _transverse(fn, p.ys)));
};
}; };
let filter = (fn, t: t) => let accumulateYs =
t |> zip |> E.A.filter(fn) |> Belt.Array.unzip |> fromArray; Transversal._transverseShape((aCurrent, aLast) => aCurrent +. aLast);
let accumulateYs = _transverseShape((aCurrent, aLast) => aCurrent +. aLast);
let subtractYs = _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.
@ -286,7 +236,7 @@ module Range = {
type zippedRange = ((float, float), (float, float)); type zippedRange = ((float, float), (float, float));
let floatSum = Belt.Array.reduce(_, 0., (a, b) => a +. b); let floatSum = Belt.Array.reduce(_, 0., (a, b) => a +. b);
let toT = r => r |> Belt.Array.unzip |> T.fromArray; let toT = T.fromZippedArray;
let nextX = ((_, (nextX, _)): zippedRange) => nextX; let nextX = ((_, (nextX, _)): zippedRange) => nextX;
let rangePointAssumingSteps = let rangePointAssumingSteps =
@ -320,6 +270,7 @@ 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); 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( Belt.Array.set(
@ -343,41 +294,24 @@ module Range = {
Some( Some(
items items
|> Belt.Array.map(_, rangePointAssumingSteps) |> Belt.Array.map(_, rangePointAssumingSteps)
|> Belt.Array.unzip |> T.fromZippedArray
|> T.fromArray |> T.Combine.intersperse(t |> T.xMap(e => e +. diff)),
|> T.intersperce(t |> T.xMap(e => e +. diff)),
) )
| _ => Some(t) | _ => Some(t)
}; };
let bar = items |> E.O.fmap(T.zip) |> E.O.bind(_, E.A.get(_, 0)); let first = items |> E.O.fmap(T.zip) |> E.O.bind(_, E.A.get(_, 0));
let items = switch (items, first) {
switch (items, bar) {
| (Some(items), Some((0.0, _))) => Some(items) | (Some(items), Some((0.0, _))) => Some(items)
| (Some(items), Some((firstX, _))) => | (Some(items), Some((firstX, _))) =>
let all = E.A.append([|(firstX, 0.0)|], items |> T.zip); let all = E.A.append([|(firstX, 0.0)|], items |> T.zip);
let foo = all |> Belt.Array.unzip |> T.fromArray; all |> T.fromZippedArray |> E.O.some;
Some(foo);
| _ => None | _ => None
}; };
items;
};
};
module Ts = {
type t = T.ts;
let minX = (t: t) =>
t |> E.A.fmap(T.minX) |> E.A.O.concatSomes |> Functions.min;
let maxX = (t: t) =>
t |> E.A.fmap(T.maxX) |> E.A.O.concatSomes |> Functions.max;
// TODO/Warning: This will break if the shapes are empty.
let equallyDividedXs = (t: t, newLength) => {
Functions.range(minX(t), maxX(t), newLength);
}; };
}; };
let combinePointwise = (fn, sampleCount, t1: xyShape, t2: xyShape) => { let combinePointwise = (fn, sampleCount, t1: xyShape, t2: xyShape) => {
let xs = Ts.equallyDividedXs([|t1, t2|], sampleCount); let xs = T.Ts.equallyDividedXs([|t1, t2|], sampleCount);
let ys = let ys =
xs |> E.A.fmap(x => fn(T.XtoY.linear(x, t1), T.XtoY.linear(x, t2))); xs |> E.A.fmap(x => fn(T.XtoY.linear(x, t1), T.XtoY.linear(x, t2)));
T.fromArrays(xs, ys); T.fromArrays(xs, ys);
@ -396,5 +330,5 @@ let logScorePoint = (sampleCount, t1, t2) =>
logScoreDist(sampleCount, t1, t2) logScoreDist(sampleCount, t1, t2)
|> Range.integrateWithTriangles |> Range.integrateWithTriangles
|> E.O.fmap(T.accumulateYs) |> E.O.fmap(T.accumulateYs)
|> E.O.bind(_, T.last) |> E.O.bind(_, T.Pairs.last)
|> E.O.fmap(((_, y)) => y); |> E.O.fmap(((_, y)) => y);

View File

@ -94,7 +94,7 @@ module Lognormal = {
let from90PercentCI = (low, high) => { let from90PercentCI = (low, high) => {
let logLow = Js.Math.log(low); let logLow = Js.Math.log(low);
let logHigh = Js.Math.log(high); let logHigh = Js.Math.log(high);
let mu = Functions.mean([|logLow, logHigh|]); let mu = E.A.Floats.mean([|logLow, logHigh|]);
let sigma = (logHigh -. logLow) /. (2.0 *. 1.645); let sigma = (logHigh -. logLow) /. (2.0 *. 1.645);
`Lognormal({mu, sigma}); `Lognormal({mu, sigma});
}; };
@ -189,9 +189,9 @@ module GenericSimple = {
let interpolateXs = let interpolateXs =
(~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => { (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => {
switch (xSelection) { switch (xSelection) {
| `Linear => Functions.range(min(dist), max(dist), sampleCount) | `Linear => E.A.Floats.range(min(dist), max(dist), sampleCount)
| `ByWeight => | `ByWeight =>
Functions.range(minCdfValue, maxCdfValue, sampleCount) E.A.Floats.range(minCdfValue, maxCdfValue, sampleCount)
|> E.A.fmap(x => inv(x, dist)) |> E.A.fmap(x => inv(x, dist))
}; };
}; };
@ -208,20 +208,20 @@ module PointwiseAddDistributionsWeighted = {
type t = pointwiseAdd; type t = pointwiseAdd;
let normalizeWeights = (dists: t) => { let normalizeWeights = (dists: t) => {
let total = dists |> E.A.fmap(snd) |> Functions.sum; let total = dists |> E.A.fmap(snd) |> E.A.Floats.sum;
dists |> E.A.fmap(((a, b)) => (a, b /. total)); dists |> E.A.fmap(((a, b)) => (a, b /. total));
}; };
let pdf = (dists: t, x: float) => let pdf = (dists: t, x: float) =>
dists dists
|> E.A.fmap(((e, w)) => GenericSimple.pdf(x, e) *. w) |> E.A.fmap(((e, w)) => GenericSimple.pdf(x, e) *. w)
|> Functions.sum; |> E.A.Floats.sum;
let min = (dists: t) => let min = (dists: t) =>
dists |> E.A.fmap(d => d |> fst |> GenericSimple.min) |> Functions.min; dists |> E.A.fmap(d => d |> fst |> GenericSimple.min) |> E.A.min;
let max = (dists: t) => let max = (dists: t) =>
dists |> E.A.fmap(d => d |> fst |> GenericSimple.max) |> Functions.max; dists |> E.A.fmap(d => d |> fst |> GenericSimple.max) |> E.A.max;
let toShape = (dists: t, sampleCount: int) => { let toShape = (dists: t, sampleCount: int) => {
let xs = let xs =

View File

@ -249,6 +249,12 @@ module A = {
let fold_right = Array.fold_right; let fold_right = Array.fold_right;
let concatMany = Belt.Array.concatMany; let concatMany = Belt.Array.concatMany;
let keepMap = Belt.Array.keepMap; let keepMap = Belt.Array.keepMap;
let min = a =>
get(a, 0)
|> O.fmap(first => Belt.Array.reduce(a, first, (i, j) => i < j ? i : j));
let max = a =>
get(a, 0)
|> O.fmap(first => Belt.Array.reduce(a, first, (i, j) => i > j ? i : j));
let stableSortBy = Belt.SortArray.stableSortBy; let stableSortBy = Belt.SortArray.stableSortBy;
let toRanges = (a: array('a)) => let toRanges = (a: array('a)) =>
switch (a |> Belt.Array.length) { switch (a |> Belt.Array.length) {
@ -270,6 +276,19 @@ module A = {
/* TODO: Is there a better way of doing this? */ /* TODO: Is there a better way of doing this? */
let uniq = r => asList(L.uniq, r); let uniq = r => asList(L.uniq, r);
//intersperse([1,2,3], [10,11,12]) => [1,10,2,11,3,12]
let intersperse = (a: array('a), b: array('a)) => {
let items: ref(array('a)) = ref([||]);
Belt.Array.forEachWithIndex(a, (i, item) => {
switch (Belt.Array.get(b, i)) {
| Some(r) => items := append(items^, [|item, r|])
| None => items := append(items^, [|item|])
}
});
items^;
};
// @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) =>
@ -314,6 +333,13 @@ module A = {
}; };
module Sorted = { module Sorted = {
let min = first;
let max = last;
let range = (~min=min, ~max=max, a) =>
switch (min(a), max(a)) {
| (Some(min), Some(max)) => Some(max -. min)
| _ => None
};
let binarySearchFirstElementGreaterIndex = (ar: array('a), el: 'a) => { let binarySearchFirstElementGreaterIndex = (ar: array('a), el: 'a) => {
let el = Belt.SortArray.binarySearchBy(ar, el, compare); let el = Belt.SortArray.binarySearchBy(ar, el, compare);
let el = el < 0 ? el * (-1) - 1 : el; let el = el < 0 ? el * (-1) - 1 : el;
@ -323,9 +349,24 @@ module A = {
| e => `firstHigher(e) | e => `firstHigher(e)
}; };
}; };
let concat = (t1: array('a), t2: array('a)) => {
let ts = Belt.Array.concat(t1, t2);
ts |> Array.fast_sort(compare);
ts;
}; };
module Floats = { module Floats = {
let makeIncrementalUp = (a, b) =>
Array.make(b - a + 1, a)
|> Array.mapi((i, c) => c + i)
|> Belt.Array.map(_, float_of_int);
let makeIncrementalDown = (a, b) =>
Array.make(a - b + 1, a)
|> Array.mapi((i, c) => c - i)
|> Belt.Array.map(_, float_of_int);
let split = (sortedArray: array(float)) => { let split = (sortedArray: array(float)) => {
let continuous = [||]; let continuous = [||];
let discrete = FloatFloatMap.empty(); let discrete = FloatFloatMap.empty();
@ -357,6 +398,29 @@ module A = {
(continuous, discrete); (continuous, discrete);
}; };
}; };
};
module Floats = {
let sum = Belt.Array.reduce(_, 0., (i, j) => i +. j);
let mean = a => sum(a) /. (Array.length(a) |> float_of_int);
let random = Js.Math.random_int;
exception RangeError(string);
let range = (min: float, max: float, n: int): array(float) => {
switch (n) {
| 0 => [||]
| 1 => [|min|]
| 2 => [|min, max|]
| _ when min == max => Belt.Array.make(n, min)
| _ when n < 0 => raise(RangeError("n must be greater than 0"))
| _ when min > max =>
raise(RangeError("Min value is less then max value"))
| _ =>
let diff = (max -. min) /. Belt.Float.fromInt(n - 1);
Belt.Array.makeBy(n, i => {min +. Belt.Float.fromInt(i) *. diff});
};
};
};
}; };
module JsArray = { module JsArray = {

View File

@ -1,109 +0,0 @@
module type Config = {let shape: DistTypes.xyShape;};
exception ShapeWrong(string);
let order = (shape: DistTypes.xyShape): DistTypes.xyShape => {
let xy =
shape.xs
|> Array.mapi((i, x) => [x, shape.ys |> Array.get(_, i)])
|> Belt.SortArray.stableSortBy(_, ([a, _], [b, _]) => a > b ? 1 : (-1));
{
xs: xy |> Array.map(([x, _]) => x),
ys: xy |> Array.map(([_, y]) => y),
};
};
module Make = (Config: Config) => {
let xs = Config.shape.xs;
let ys = Config.shape.ys;
let get = Array.get;
let len = Array.length;
let validateHasLength = (): bool => len(xs) > 0;
let validateSize = (): bool => len(xs) == len(ys);
if (!validateHasLength()) {
raise(ShapeWrong("You need at least one element."));
};
if (!validateSize()) {
raise(ShapeWrong("Arrays of \"xs\" and \"ys\" have different sizes."));
};
if (!Belt.SortArray.isSorted(xs, (a, b) => a > b ? 1 : (-1))) {
raise(ShapeWrong("Arrays of \"xs\" and \"ys\" have different sizes."));
};
let minX = () => get(xs, 0);
let maxX = () => get(xs, len(xs) - 1);
let minY = () => get(ys, 0);
let maxY = () => get(ys, len(ys) - 1);
let findY = (x: float): float => {
let firstHigherIndex =
E.A.Sorted.binarySearchFirstElementGreaterIndex(xs, x);
switch (firstHigherIndex) {
| `overMax => maxY()
| `underMin => minY()
| `firstHigher(firstHigherIndex) =>
let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let needsInterpolation = get(xs, lowerOrEqualIndex) != x;
if (needsInterpolation) {
Functions.interpolate(
get(xs, lowerOrEqualIndex),
get(xs, firstHigherIndex),
get(ys, lowerOrEqualIndex),
get(ys, firstHigherIndex),
x,
);
} else {
ys[lowerOrEqualIndex];
};
};
};
let findX = (y: float): float => {
let firstHigherIndex =
E.A.Sorted.binarySearchFirstElementGreaterIndex(ys, y);
switch (firstHigherIndex) {
| `overMax => maxX()
| `underMin => minX()
| `firstHigher(firstHigherIndex) =>
let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let needsInterpolation = get(ys, lowerOrEqualIndex) != y;
if (needsInterpolation) {
Functions.interpolate(
get(ys, lowerOrEqualIndex),
get(ys, firstHigherIndex),
get(xs, lowerOrEqualIndex),
get(xs, firstHigherIndex),
y,
);
} else {
xs[lowerOrEqualIndex];
};
};
};
let convertWithAlternativeXs = (newXs: array(float)): DistTypes.xyShape => {
let newYs = Belt.Array.map(newXs, findY);
{xs: newXs, ys: newYs};
};
let convertToNewLength = (newLength: int): DistTypes.xyShape => {
Functions.(
range(min(xs), max(xs), newLength) |> convertWithAlternativeXs
);
};
let sampleSingle = (): float => Js.Math.random() |> findY;
let sample = (size: int): array(float) =>
Belt.Array.makeBy(size, i => sampleSingle());
let integral = () => {
Belt.Array.reduceWithIndex(ys, 0., (integral, y, i) => {
switch (i) {
| 0 => integral
| _ =>
let thisY = y;
let lastY = get(ys, i - 1);
let thisX = get(xs, i);
let lastX = get(xs, i - 1);
let sectionInterval = (thisY +. lastY) /. 2. *. (thisX -. lastX);
integral +. sectionInterval;
}
});
};
};

View File

@ -1,5 +1,3 @@
exception RangeWrong(string);
let interpolate = let interpolate =
(xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float) (xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float)
: float => { : float => {
@ -7,30 +5,3 @@ let interpolate =
let maxProportion = (xIntended -. xMin) /. (xMax -. xMin); let maxProportion = (xIntended -. xMin) /. (xMax -. xMin);
yMin *. minProportion +. yMax *. maxProportion; yMin *. minProportion +. yMax *. maxProportion;
}; };
let sum = Belt.Array.reduce(_, 0., (i, j) => i +. j);
let mean = a => sum(a) /. (Array.length(a) |> float_of_int);
let min = a => Belt.Array.reduce(a, a[0], (i, j) => i < j ? i : j);
let max = a => Belt.Array.reduce(a, a[0], (i, j) => i > j ? i : j);
let up = (a, b) =>
Array.make(b - a + 1, a)
|> Array.mapi((i, c) => c + i)
|> Belt.Array.map(_, float_of_int);
let down = (a, b) =>
Array.make(a - b + 1, a)
|> Array.mapi((i, c) => c - i)
|> Belt.Array.map(_, float_of_int);
let range = (min: float, max: float, n: int): array(float) => {
switch (n) {
| 0 => [||]
| 1 => [|min|]
| 2 => [|min, max|]
| _ when min == max => Belt.Array.make(n, min)
| _ when n < 0 => raise(RangeWrong("n is less then zero"))
| _ when min > max => raise(RangeWrong("Min values is less then max"))
| _ =>
let diff = (max -. min) /. Belt.Float.fromInt(n - 1);
Belt.Array.makeBy(n, i => {min +. Belt.Float.fromInt(i) *. diff});
};
};
let random = Js.Math.random_int;