From 6f8d06a6d687af57a54368dd3f728e7a2ced7cb2 Mon Sep 17 00:00:00 2001 From: Ozzie Gooen Date: Thu, 26 Mar 2020 23:18:19 +0000 Subject: [PATCH] First refactor to XYShape --- __tests__/CDF__Test.re | 179 ----------- __tests__/Functions__Test.re | 92 ------ __tests__/XYShape__Test.re | 2 +- src/Samples.re | 2 +- src/distributions/Distributions.re | 28 +- src/distributions/XYShape.re | 466 +++++++++++++---------------- src/symbolic/SymbolicDist.re | 14 +- src/utility/E.re | 120 ++++++-- src/utility/lib/CDF.re | 109 ------- src/utility/lib/Functions.re | 31 +- 10 files changed, 322 insertions(+), 721 deletions(-) delete mode 100644 __tests__/CDF__Test.re delete mode 100644 __tests__/Functions__Test.re delete mode 100644 src/utility/lib/CDF.re diff --git a/__tests__/CDF__Test.re b/__tests__/CDF__Test.re deleted file mode 100644 index 68d0bd37..00000000 --- a/__tests__/CDF__Test.re +++ /dev/null @@ -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.) - }); - }); -}); diff --git a/__tests__/Functions__Test.re b/__tests__/Functions__Test.re deleted file mode 100644 index 5dda5e3d..00000000 --- a/__tests__/Functions__Test.re +++ /dev/null @@ -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.)|]) - }); -}); diff --git a/__tests__/XYShape__Test.re b/__tests__/XYShape__Test.re index 44d0dcaa..4d3e9de3 100644 --- a/__tests__/XYShape__Test.re +++ b/__tests__/XYShape__Test.re @@ -43,7 +43,7 @@ describe("XYShapes", () => { describe("transverse", () => { makeTest( "When very different", - XYShape.T._transverse2( + XYShape.T.Transversal._transverse( (aCurrent, aLast) => aCurrent +. aLast, [|1.0, 2.0, 3.0, 4.0|], ), diff --git a/src/Samples.re b/src/Samples.re index ebd36722..354e0029 100644 --- a/src/Samples.re +++ b/src/Samples.re @@ -91,7 +91,7 @@ module T = { // todo: Figure out some way of doing this without having to integrate so many times. let toShape = (~samples: t, ~outputXYPoints=3000, ~kernelWidth=10, ()) => { 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 lengthFloat = float_of_int(length); let discrete: DistTypes.xyShape = diff --git a/src/distributions/Distributions.re b/src/distributions/Distributions.re index f78df201..bfe5fc28 100644 --- a/src/distributions/Distributions.re +++ b/src/distributions/Distributions.re @@ -87,7 +87,7 @@ module Continuous = { interpolation, }; let lastY = (t: t) => - t |> xyShape |> XYShape.T.unsafeLast |> (((_, y)) => y); + t |> xyShape |> XYShape.T.Pairs.unsafeLast |> (((_, y)) => y); let oShapeMap = (fn, {xyShape, interpolation}: t): option(DistTypes.continuousShape) => fn(xyShape) |> E.O.fmap(make(_, interpolation)); @@ -146,16 +146,16 @@ module Continuous = { let truncate = (~cache=None, i, t) => t |> shapeMap( - XYShape.T.convertToNewLengthByProbabilityMass( + XYShape.T.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.findY(f)); + t |> integral(~cache) |> shapeFn(XYShape.T.XtoY.linear(f)); 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 toDiscrete = _ => None; let toScaledContinuous = t => Some(t); @@ -208,10 +208,16 @@ module Discrete = { }; 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) => - 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) => { - t |> integral(~cache) |> Continuous.getShape |> XYShape.T.findY(f); + t + |> integral(~cache) + |> Continuous.getShape + |> XYShape.T.XtoY.linear(f); }; 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. diff --git a/src/distributions/XYShape.re b/src/distributions/XYShape.re index 7f7e073e..062be1f4 100644 --- a/src/distributions/XYShape.re +++ b/src/distributions/XYShape.re @@ -1,5 +1,13 @@ 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 = { type t = xyShape; type ts = array(xyShape); @@ -9,134 +17,157 @@ module T = { }; let xs = (t: t) => t.xs; let ys = (t: t) => t.ys; - let minX = (t: t) => t |> xs |> E.A.first; - let maxX = (t: t) => t |> xs |> E.A.last; - let minY = (t: t) => t |> ys |> E.A.first; - let maxY = (t: t) => t |> ys |> E.A.last; - let xTotalRange = (t: t) => - switch (minX(t), maxX(t)) { - | (Some(min), Some(max)) => Some(max -. min) - | _ => None - }; - let first = ({xs, ys}: t) => - switch (xs |> E.A.first, ys |> E.A.first) { - | (Some(x), Some(y)) => Some((x, y)) - | _ => None - }; - let last = ({xs, ys}: t) => - switch (xs |> E.A.last, ys |> E.A.last) { - | (Some(x), Some(y)) => Some((x, y)) - | _ => None - }; - - let unsafeFirst = (t: t) => first(t) |> E.O.toExn("Unsafe operation"); - let unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation"); - + let minX = (t: t) => t |> xs |> E.A.Sorted.min; + let maxX = (t: t) => t |> xs |> E.A.Sorted.max; + 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 zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys); - let getBy = (t: t, fn) => t |> zip |> Belt.Array.getBy(_, fn); - - let firstPairAtOrBeforeValue = (xValue, t: t) => { - let zipped = zip(t); - let firstIndex = - zipped |> Belt.Array.getIndexBy(_, ((x, y)) => x > xValue); - let previousIndex = - switch (firstIndex) { - | None => Some(Array.length(zipped) - 1) - | Some(0) => None - | Some(n) => Some(n - 1) - }; - previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped)); - }; - - 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 => { - 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 needsInterpolation = ys(t)[lowerOrEqualIndex] != y; - if (needsInterpolation) { - Functions.interpolate( - ys(t)[lowerOrEqualIndex], - ys(t)[firstHigherIndex], - xs(t)[lowerOrEqualIndex], - xs(t)[firstHigherIndex], - y, - ); - } else { - xs(t)[lowerOrEqualIndex]; - }; - }; - 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 = { - let stepwiseIncremental = (f, t: t) => - firstPairAtOrBeforeValue(f, t) |> E.O.fmap(((_, y)) => y); - - let stepwiseIfAtX = (f: float, t: t) => { - 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 = (f, t: t) => t |> findY(f); - }; - 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 equallyDividedXs = (t: t, newLength) => { + E.A.Floats.range( + minX(t) |> E.O.toExt("Unsafe"), + maxX(t) |> E.O.toExt("Unsafe"), + 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); + }; + }; + + 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 unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation"); + + let getBy = (t: t, fn) => t |> zip |> Belt.Array.getBy(_, fn); + + 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) + }; + previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped)); + }; + }; + + 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; + }; + }; + + module XtoY = { + let stepwiseIncremental = (f, t: t) => + Pairs.firstAtOrBeforeXValue(f, t) |> E.O.fmap(((_, y)) => y); + + let stepwiseIfAtX = (f: float, t: t) => { + Pairs.getBy(t, ((x: float, _)) => {x == f}) + |> E.O.fmap(((_, y)) => y); + }; + + 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 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 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 = { type zipped = array((float, float)); @@ -146,138 +177,57 @@ module T = { t |> E.A.stableSortBy(_, ((x1, _), (x2, _)) => x1 > x2 ? 1 : 0); }; - // TODO: Use faster sort at least, geese. module Combine = { - let combineLinear = (t1: t, t2: t, fn: (float, float) => float) => { - let allXs = Belt.Array.concat(xs(t1), xs(t2)); - allXs |> Array.fast_sort(compare); + 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 = XtoY.linear(x, t1); - let y2 = XtoY.linear(x, t2); + let y1 = comboAlg(x, t1); + let y2 = comboAlg(x, t2); fn(y1, y2); }); fromArrays(allXs, allYs); }; - let combineStepwise = - (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.stepwiseIncremental(x, t1); - let y2 = XtoY.stepwiseIncremental(x, t2); - fn(y1, y2); - }); - fromArrays(allXs, allYs); - }; + let combineLinear = _combineAbstract(XtoY.linear); + let combineStepwise = _combineAbstract(XtoY.stepwiseIncremental); + let combineIfAtX = _combineAbstract(XtoY.stepwiseIfAtX); - 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: I'd bet this is pretty slow + let intersperse = (t1: t, t2: t) => { + E.A.intersperse(zip(t1), zip(t2)) |> fromZippedArray; }; }; - // 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) + 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; }; - // TODO: I'd bet this is pretty slow - let intersperce = (t1: t, t2: t) => { - let items: ref(array((float, float))) = ref([||]); - 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 _transverseShape = (fn, p: t) => { + fromArray((p.xs, _transverse(fn, p.ys))); + }; }; - let yFold = (fn, t: t) => { - E.A.fold_left(fn, 0., t.ys); - }; - - 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 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 _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) => { - fromArray((p.xs, _transverse2(fn, p.ys))); - }; - - let filter = (fn, t: t) => - t |> zip |> E.A.filter(fn) |> Belt.Array.unzip |> fromArray; - - let accumulateYs = _transverseShape((aCurrent, aLast) => aCurrent +. aLast); - let subtractYs = _transverseShape((aCurrent, aLast) => aCurrent -. aLast); + let accumulateYs = + Transversal._transverseShape((aCurrent, aLast) => aCurrent +. aLast); }; // 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)); 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 rangePointAssumingSteps = @@ -320,6 +270,7 @@ 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( @@ -343,41 +294,24 @@ module Range = { Some( items |> Belt.Array.map(_, rangePointAssumingSteps) - |> Belt.Array.unzip - |> T.fromArray - |> T.intersperce(t |> T.xMap(e => e +. diff)), + |> T.fromZippedArray + |> T.Combine.intersperse(t |> T.xMap(e => e +. diff)), ) | _ => Some(t) }; - let bar = items |> E.O.fmap(T.zip) |> E.O.bind(_, E.A.get(_, 0)); - let items = - switch (items, bar) { - | (Some(items), Some((0.0, _))) => Some(items) - | (Some(items), Some((firstX, _))) => - let all = E.A.append([|(firstX, 0.0)|], items |> T.zip); - let foo = all |> Belt.Array.unzip |> T.fromArray; - Some(foo); - | _ => 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 first = items |> E.O.fmap(T.zip) |> E.O.bind(_, E.A.get(_, 0)); + switch (items, first) { + | (Some(items), Some((0.0, _))) => Some(items) + | (Some(items), Some((firstX, _))) => + let all = E.A.append([|(firstX, 0.0)|], items |> T.zip); + all |> T.fromZippedArray |> E.O.some; + | _ => None + }; }; }; 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 = xs |> E.A.fmap(x => fn(T.XtoY.linear(x, t1), T.XtoY.linear(x, t2))); T.fromArrays(xs, ys); @@ -396,5 +330,5 @@ let logScorePoint = (sampleCount, t1, t2) => logScoreDist(sampleCount, t1, t2) |> Range.integrateWithTriangles |> E.O.fmap(T.accumulateYs) - |> E.O.bind(_, T.last) + |> E.O.bind(_, T.Pairs.last) |> E.O.fmap(((_, y)) => y); \ No newline at end of file diff --git a/src/symbolic/SymbolicDist.re b/src/symbolic/SymbolicDist.re index 4bc3ab16..1fd6dc50 100644 --- a/src/symbolic/SymbolicDist.re +++ b/src/symbolic/SymbolicDist.re @@ -94,7 +94,7 @@ module Lognormal = { let from90PercentCI = (low, high) => { let logLow = Js.Math.log(low); 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); `Lognormal({mu, sigma}); }; @@ -189,9 +189,9 @@ module GenericSimple = { let interpolateXs = (~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => { switch (xSelection) { - | `Linear => Functions.range(min(dist), max(dist), sampleCount) + | `Linear => E.A.Floats.range(min(dist), max(dist), sampleCount) | `ByWeight => - Functions.range(minCdfValue, maxCdfValue, sampleCount) + E.A.Floats.range(minCdfValue, maxCdfValue, sampleCount) |> E.A.fmap(x => inv(x, dist)) }; }; @@ -208,20 +208,20 @@ module PointwiseAddDistributionsWeighted = { type t = pointwiseAdd; 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)); }; let pdf = (dists: t, x: float) => dists |> E.A.fmap(((e, w)) => GenericSimple.pdf(x, e) *. w) - |> Functions.sum; + |> E.A.Floats.sum; 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) => - 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 xs = diff --git a/src/utility/E.re b/src/utility/E.re index f47b00b8..5c00721c 100644 --- a/src/utility/E.re +++ b/src/utility/E.re @@ -249,6 +249,12 @@ module A = { let fold_right = Array.fold_right; let concatMany = Belt.Array.concatMany; 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 toRanges = (a: array('a)) => switch (a |> Belt.Array.length) { @@ -270,6 +276,19 @@ module A = { /* TODO: Is there a better way of doing this? */ 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: js findIndex)? Wasn't sure. let findIndex = (e, i) => @@ -314,6 +333,13 @@ module A = { }; 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 el = Belt.SortArray.binarySearchBy(ar, el, compare); let el = el < 0 ? el * (-1) - 1 : el; @@ -323,38 +349,76 @@ module A = { | 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 = { + 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 continuous = [||]; + let discrete = FloatFloatMap.empty(); + Belt.Array.forEachWithIndex( + sortedArray, + (index, element) => { + let maxIndex = (sortedArray |> Array.length) - 1; + let possiblySimilarElements = + ( + switch (index) { + | 0 => [|index + 1|] + | n when n == maxIndex => [|index - 1|] + | _ => [|index - 1, index + 1|] + } + ) + |> Belt.Array.map(_, r => sortedArray[r]); + let hasSimilarElement = + Belt.Array.some(possiblySimilarElements, r => r == element); + hasSimilarElement + ? FloatFloatMap.increment(element, discrete) + : { + let _ = Js.Array.push(element, continuous); + (); + }; + (); + }, + ); + + (continuous, discrete); + }; + }; }; module Floats = { - let split = (sortedArray: array(float)) => { - let continuous = [||]; - let discrete = FloatFloatMap.empty(); - Belt.Array.forEachWithIndex( - sortedArray, - (index, element) => { - let maxIndex = (sortedArray |> Array.length) - 1; - let possiblySimilarElements = - ( - switch (index) { - | 0 => [|index + 1|] - | n when n == maxIndex => [|index - 1|] - | _ => [|index - 1, index + 1|] - } - ) - |> Belt.Array.map(_, r => sortedArray[r]); - let hasSimilarElement = - Belt.Array.some(possiblySimilarElements, r => r == element); - hasSimilarElement - ? FloatFloatMap.increment(element, discrete) - : { - let _ = Js.Array.push(element, continuous); - (); - }; - (); - }, - ); + 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; - (continuous, discrete); + 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}); + }; }; }; }; diff --git a/src/utility/lib/CDF.re b/src/utility/lib/CDF.re deleted file mode 100644 index 900952e0..00000000 --- a/src/utility/lib/CDF.re +++ /dev/null @@ -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; - } - }); - }; -}; \ No newline at end of file diff --git a/src/utility/lib/Functions.re b/src/utility/lib/Functions.re index 3124c873..5c6317eb 100644 --- a/src/utility/lib/Functions.re +++ b/src/utility/lib/Functions.re @@ -1,36 +1,7 @@ -exception RangeWrong(string); - 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; -}; - -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; +}; \ No newline at end of file