diff --git a/__tests__/Foo/Foo__Test.re b/__tests__/Foo/Foo__Test.re index 5011704c..54581f47 100644 --- a/__tests__/Foo/Foo__Test.re +++ b/__tests__/Foo/Foo__Test.re @@ -11,7 +11,7 @@ let makeTest = (str, item1, item2) => describe("Shape", () => { describe("Continuous", () => { open Distributions.Continuous; - let continuous = make(shape, `Stepwise); + let continuous = make(shape, `Linear); makeTest("minX", T.minX(continuous), Some(1.0)); makeTest("maxX", T.maxX(continuous), Some(8.0)); makeTest( @@ -19,32 +19,75 @@ describe("Shape", () => { T.pointwiseFmap(r => r *. 2.0, continuous) |> getShape |> (r => r.ys), [|16., 18.0, 4.0|], ); - makeTest( - "xToY at 4.0", - T.xToY(4., continuous), - {continuous: 9.0, discrete: 0.0}, - ); - makeTest( - "xToY at 0.0", - T.xToY(0., continuous), - {continuous: 8.0, discrete: 0.0}, - ); - makeTest( - "xToY at 5.0", - T.xToY(5., continuous), - {continuous: 7.25, discrete: 0.0}, - ); + describe("xToY", () => { + describe("when Linear", () => { + makeTest( + "at 4.0", + T.xToY(4., continuous), + {continuous: 9.0, discrete: 0.0}, + ); + // Note: This below is weird to me, I'm not sure if it's what we want really. + makeTest( + "at 0.0", + T.xToY(0., continuous), + {continuous: 8.0, discrete: 0.0}, + ); + makeTest( + "at 5.0", + T.xToY(5., continuous), + {continuous: 7.25, discrete: 0.0}, + ); + makeTest( + "at 10.0", + T.xToY(10., continuous), + {continuous: 2.0, discrete: 0.0}, + ); + }); + describe("when Stepwise", () => { + let continuous = make(shape, `Stepwise); + makeTest( + "at 4.0", + T.xToY(4., continuous), + {continuous: 9.0, discrete: 0.0}, + ); + makeTest( + "at 0.0", + T.xToY(0., continuous), + {continuous: 0.0, discrete: 0.0}, + ); + makeTest( + "at 5.0", + T.xToY(5., continuous), + {continuous: 9.0, discrete: 0.0}, + ); + makeTest( + "at 10.0", + T.xToY(10., continuous), + {continuous: 2.0, discrete: 0.0}, + ); + }); + }); makeTest( "integral", T.Integral.get(~cache=None, continuous) |> getShape, - {xs: [|4.0, 8.0|], ys: [|25.5, 47.5|]}, + {xs: [|1.0, 4.0, 8.0|], ys: [|0.0, 25.5, 47.5|]}, + ); + makeTest( + "integralXToY", + T.Integral.xToY(~cache=None, 0.0, continuous), + 0.0, ); makeTest( "integralXToY", T.Integral.xToY(~cache=None, 2.0, continuous), - 25.5, + 8.5, ); - makeTest("integralSum", T.Integral.sum(~cache=None, continuous), 73.0); + makeTest( + "integralXToY", + T.Integral.xToY(~cache=None, 100.0, continuous), + 47.5, + ); + makeTest("integralSum", T.Integral.sum(~cache=None, continuous), 47.5); }); describe("Discrete", () => { @@ -76,6 +119,23 @@ describe("Shape", () => { T.xToY(5., discrete), {discrete: 0.0, continuous: 0.0}, ); + makeTest( + "scaleBy", + T.scaleBy(~scale=4.0, discrete), + {xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]}, + ); + makeTest( + "scaleToIntegralSum", + T.scaleToIntegralSum(~intendedSum=4.0, discrete), + {xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]}, + ); + makeTest( + "scaleToIntegralSum: back and forth", + discrete + |> T.scaleToIntegralSum(~intendedSum=4.0) + |> T.scaleToIntegralSum(~intendedSum=1.0), + discrete, + ); makeTest( "integral", T.Integral.get(~cache=None, discrete), @@ -84,6 +144,11 @@ describe("Shape", () => { `Stepwise, ), ); + makeTest( + "integral with 1 element", + T.Integral.get(~cache=None, {xs: [|0.0|], ys: [|1.0|]}), + Distributions.Continuous.make({xs: [|0.0|], ys: [|1.0|]}, `Stepwise), + ); makeTest( "integralXToY", T.Integral.xToY(~cache=None, 6.0, discrete), diff --git a/src/components/charts/DistPlusPlot.re b/src/components/charts/DistPlusPlot.re index ff30d06e..36ea2267 100644 --- a/src/components/charts/DistPlusPlot.re +++ b/src/components/charts/DistPlusPlot.re @@ -60,6 +60,12 @@ let make = (~distPlus: DistTypes.distPlus) => { () => { {setX(_ => r)}} />}, [|distPlus|], ); + // Js.log4( + // "distPlus", + // x, + // distPlus, + // distPlus |> Distributions.DistPlus.T.xToY(x), + // );
chart chart2 @@ -67,6 +73,12 @@ let make = (~distPlus: DistTypes.distPlus) => { {"X Point" |> ReasonReact.string} + + {"Discrete Value" |> ReasonReact.string} + + + {"Continuous Value" |> ReasonReact.string} + {"Y Integral to Point" |> ReasonReact.string} @@ -77,6 +89,20 @@ let make = (~distPlus: DistTypes.distPlus) => { {x |> E.Float.toString |> ReasonReact.string} + + {distPlus + |> Distributions.DistPlus.T.xToY(x) + |> DistTypes.MixedPoint.toDiscreteValue + |> E.Float.with2DigitsPrecision + |> ReasonReact.string} + + + {distPlus + |> Distributions.DistPlus.T.xToY(x) + |> DistTypes.MixedPoint.toContinuousValue + |> E.Float.with2DigitsPrecision + |> ReasonReact.string} + {distPlus |> Distributions.DistPlus.T.Integral.xToY(~cache=None, x) diff --git a/src/distributions/Distributions.re b/src/distributions/Distributions.re index 486fe0c1..9c636ac6 100644 --- a/src/distributions/Distributions.re +++ b/src/distributions/Distributions.re @@ -54,7 +54,7 @@ module Dist = (T: dist) => { let sum = T.integralSum; }; - // This is suboptimal because it could get the cache but doesn't here. + // This is suboptimal because it could get the cache but doesn't here. let scaleToIntegralSum = (~intendedSum=1.0, t: t) => { let scale = intendedSum /. Integral.sum(~cache=None, t); scaleBy(~scale, t); @@ -90,8 +90,9 @@ module Continuous = { type t = DistTypes.continuousShape; type integral = DistTypes.continuousShape; let shapeFn = (fn, t: t) => t |> xyShape |> fn; - // TODO: Obviously fix this, it's terrible. Use interpolation method here. + // TODO: Obviously fix this, it's terrible. Use interpolation param to do appropriate interpolation. // TODO: Steps could be 1 value, interpolation needs at least 2. + // TODO: integrateWithTriangles should return (x0, 0.0) as the first item. let integral = (~cache, t) => cache |> E.O.default( @@ -103,16 +104,28 @@ module Continuous = { ); // This seems wrong, we really want the ending bit, I'd assume let integralSum = (~cache, t) => - t |> integral(~cache) |> xyShape |> XYShape.ySum; + t + |> integral(~cache) + |> xyShape + |> XYShape.unsafeLast + |> (((_, y)) => y); let minX = shapeFn(XYShape.minX); let maxX = shapeFn(XYShape.maxX); let pointwiseFmap = (fn, t: t) => t |> xyShape |> XYShape.pointwiseMap(fn) |> fromShape; let toShape = (t: t): DistTypes.shape => Continuous(t); - // TODO: When Roman's PR comes in, fix this bit. This depends on interpolation, obviously. - let xToY = (f, t) => - shapeFn(CdfLibrary.Distribution.findY(f), t) - |> DistTypes.MixedPoint.makeContinuous; + let xToY = (f, {interpolation, xyShape}: t) => + switch (interpolation) { + | `Stepwise => + xyShape + |> XYShape.XtoY.stepwise(f) + |> E.O.default(0.0) + |> DistTypes.MixedPoint.makeContinuous + | `Linear => + xyShape + |> XYShape.XtoY.linear(f) + |> DistTypes.MixedPoint.makeContinuous + }; let integralXtoY = (~cache, f, t) => t |> integral(~cache) |> shapeFn(CdfLibrary.Distribution.findY(f)); let toContinuous = t => Some(t); @@ -138,7 +151,6 @@ module Discrete = { Continuous.make(XYShape.accumulateYs(t), `Stepwise); }, ); - // todo: Fix this with last element let integralSum = (~cache, t) => t |> XYShape.ySum; let minX = XYShape.minX; let maxX = XYShape.maxX; @@ -150,8 +162,7 @@ module Discrete = { let toScaledDiscrete = t => Some(t); let xToY = (f, t) => { - XYShape.getBy(t, ((x, _)) => x == f) - |> E.O.fmap(((_, y)) => y) + XYShape.XtoY.ifAtX(f, t) |> E.O.default(0.0) |> DistTypes.MixedPoint.makeDiscrete; }; diff --git a/src/distributions/XYShape.re b/src/distributions/XYShape.re index 65cb59bd..f212d26b 100644 --- a/src/distributions/XYShape.re +++ b/src/distributions/XYShape.re @@ -18,8 +18,36 @@ let last = (t: t) => | _ => None }; +let unsafeFirst = (t: t) => first(t) |> E.O.toExn("Unsafe operation"); +let unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation"); + let zip = t => Belt.Array.zip(t.xs, t.ys); let getBy = (t: t, fn) => t |> zip |> Belt.Array.getBy(_, fn); + +let firstPairAtOrBeforeValue = (xValue, t: t) => { + let zipped = zip(t); + let firstIndex = + zipped |> Belt.Array.getIndexBy(_, ((x, y)) => x > xValue); + let previousIndex = + switch (firstIndex) { + | None => Some(Array.length(zipped) - 1) + | Some(0) => None + | Some(n) => Some(n - 1) + }; + previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped)); +}; + +module XtoY = { + let ifAtX = (f, t: t) => + getBy(t, ((x, _)) => x == f) |> E.O.fmap(((_, y)) => y); + + let stepwise = (f, t: t) => + firstPairAtOrBeforeValue(f, t) |> E.O.fmap(((_, y)) => y); + + // TODO: When Roman's PR comes in, fix this bit. This depends on interpolation, obviously. + let linear = (f, t: t) => t |> CdfLibrary.Distribution.findY(f); +}; + let pointwiseMap = (fn, t: t): t => {xs: t.xs, ys: t.ys |> E.A.fmap(fn)}; let fromArray = ((xs, ys)): t => {xs, ys}; let fromArrays = (xs, ys): t => {xs, ys}; @@ -109,21 +137,24 @@ module Range = { (((lastX, lastY), (nextX, nextY)): zippedRange) => (nextY -. lastY) /. (nextX -. lastX); - let inRanges = (mapper, reducer, t: t) => { + let mapYsBasedOnRanges = (fn, t) => Belt.Array.zip(t.xs, t.ys) |> E.A.toRanges |> E.R.toOption - |> E.O.fmap(r => r |> Belt.Array.map(_, mapper) |> reducer); - }; + |> E.O.fmap(r => r |> Belt.Array.map(_, r => (nextX(r), fn(r)))); - let mapYsBasedOnRanges = fn => inRanges(r => (nextX(r), fn(r)), toT); - - let integrateWithSteps = z => - mapYsBasedOnRanges(rangeAreaAssumingSteps, z) |> E.O.fmap(accumulateYs); - - let integrateWithTriangles = z => - mapYsBasedOnRanges(rangeAreaAssumingTriangles, z) + let integrateWithTriangles = z => { + let rangeItems = mapYsBasedOnRanges(rangeAreaAssumingTriangles, z); + ( + switch (rangeItems, z |> first) { + | (Some(r), Some((firstX, _))) => + Some(Belt.Array.concat([|(firstX, 0.0)|], r)) + | _ => None + } + ) + |> E.O.fmap(toT) |> E.O.fmap(accumulateYs); + }; let derivative = mapYsBasedOnRanges(delta_y_over_delta_x);