diff --git a/__tests__/Foo/Foo__Test.re b/__tests__/Foo/Foo__Test.re index b35d7d1c..c613d9db 100644 --- a/__tests__/Foo/Foo__Test.re +++ b/__tests__/Foo/Foo__Test.re @@ -79,7 +79,10 @@ describe("Shape", () => { make({xs: [|1., 4., 8.|], ys: [|0.1, 5., 1.0|]}, `Stepwise); continuous |> toLinear |> getShape; }, - {xs: [|1.0, 4.0, 4.0, 8.0, 8.0|], ys: [|0.1, 0.1, 5.0, 5.0, 1.0|]}, + { + xs: [|1.00007, 4.0, 4.00007, 8.0, 8.00007|], + ys: [|0.1, 0.1, 5.0, 5.0, 1.0|], + }, ); makeTest( "integralXToY", @@ -251,13 +254,20 @@ describe("Shape", () => { T.Integral.get(~cache=None, mixed), Distributions.Continuous.make( { - xs: [|1., 3., 4., 4., 7., 8., 8., 14.|], - ys: [|0.15, 0.0, 0.15, 0.4, 0.13986013986013987, 0.4, 0.5, 0.5|], + xs: [|1.00007, 3., 4., 4.00007, 7., 8., 8.00007, 14.|], + ys: [| + 0.15, + 0.15, + 0.18496503496503497, + 0.4349674825174825, + 0.5398601398601399, + 0.5913086913086913, + 0.6913122927072927, + 1.0, + |], }, `Linear, ), ); - // makeTest("integralXToY", T.Integral.xToY(~cache=None, 6.0, mixed), 0.9); - // makeTest("integralSum", T.Integral.sum(~cache=None, mixed), 1.0); }); }); \ No newline at end of file diff --git a/showcase/entries/Continuous.re b/showcase/entries/Continuous.re index 7357d5b6..585d258c 100644 --- a/showcase/entries/Continuous.re +++ b/showcase/entries/Continuous.re @@ -12,7 +12,11 @@ let distributions = () =>

{"Basic Mixed Distribution" |> ReasonReact.string}

- {timeDist |> E.O.React.fmapOrNull(distPlus => )} + {timeDist + |> E.O.fmap( + Distributions.DistPlus.T.scaleToIntegralSum(~intendedSum=1.0), + ) + |> E.O.React.fmapOrNull(distPlus => )}

{"Simple Continuous" |> ReasonReact.string}

; diff --git a/src/components/charts/DistPlusPlot.re b/src/components/charts/DistPlusPlot.re index ce5dbd48..d6f9d820 100644 --- a/src/components/charts/DistPlusPlot.re +++ b/src/components/charts/DistPlusPlot.re @@ -60,12 +60,6 @@ let make = (~distPlus: DistTypes.distPlus) => { () => { {setX(_ => r)}} />}, [|distPlus|], ); - Js.log4( - "distPlus", - x, - distPlus, - distPlus |> Distributions.DistPlus.T.xToY(x), - );
chart chart2 @@ -93,14 +87,14 @@ let make = (~distPlus: DistTypes.distPlus) => { {distPlus |> Distributions.DistPlus.T.xToY(x) |> DistTypes.MixedPoint.toDiscreteValue - |> E.Float.with2DigitsPrecision + |> Js.Float.toPrecisionWithPrecision(_, ~digits=7) |> ReasonReact.string} {distPlus |> Distributions.DistPlus.T.xToY(x) |> DistTypes.MixedPoint.toContinuousValue - |> E.Float.with2DigitsPrecision + |> Js.Float.toPrecisionWithPrecision(_, ~digits=7) |> ReasonReact.string} diff --git a/src/distributions/Distributions.re b/src/distributions/Distributions.re index 1a5a0fb0..ab9d6c6e 100644 --- a/src/distributions/Distributions.re +++ b/src/distributions/Distributions.re @@ -101,7 +101,7 @@ module Continuous = { switch (interpolation) { | `Stepwise => xyShape - |> XYShape.XtoY.stepwise(f) + |> XYShape.XtoY.stepwiseIncremental(f) |> E.O.default(0.0) |> DistTypes.MixedPoint.makeContinuous | `Linear => @@ -109,6 +109,14 @@ module Continuous = { |> XYShape.XtoY.linear(f) |> DistTypes.MixedPoint.makeContinuous }; + + // let combineWithFn = (t1: t, t2: t, fn: (float, float) => float) => { + // switch(t1, t2){ + // | ({interpolation: `Stepwise}, {interpolation: `Stepwise}) => 3.0 + // | ({interpolation: `Linear}, {interpolation: `Linear}) => 3.0 + // } + // }; + let integral = (~cache, t) => cache |> E.O.default( @@ -148,7 +156,7 @@ module Discrete = { let toScaledDiscrete = t => Some(t); let xToY = (f, t) => { - XYShape.XtoY.ifAtX(f, t) + XYShape.XtoY.stepwiseIfAtX(f, t) |> E.O.default(0.0) |> DistTypes.MixedPoint.makeDiscrete; }; @@ -254,9 +262,11 @@ module Mixed = { ~scale=discreteProbabilityMassFraction, ); Continuous.make( - XYShape.combine( + XYShape.Combine.combineLinear( Continuous.getShape(cont), Continuous.getShape(dist), + (a, b) => + a +. b ), `Linear, ); diff --git a/src/distributions/XYShape.re b/src/distributions/XYShape.re index 26fd0f05..0eb6fcfa 100644 --- a/src/distributions/XYShape.re +++ b/src/distributions/XYShape.re @@ -5,15 +5,21 @@ type t = xyShape; let toJs = (t: t) => { {"xs": t.xs, "ys": t.ys}; }; -let minX = (t: t) => t.xs |> E.A.first; -let maxX = (t: t) => t.xs |> E.A.last; -let first = (t: t) => - switch (t.xs |> E.A.first, t.ys |> E.A.first) { +let xs = (t: t) => t.xs; +let minX = (t: t) => t |> xs |> E.A.first; +let maxX = (t: t) => t |> xs |> 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 = (t: t) => - switch (t.xs |> E.A.last, t.ys |> E.A.last) { +let last = ({xs, ys}: t) => + switch (xs |> E.A.last, ys |> E.A.last) { | (Some(x), Some(y)) => Some((x, y)) | _ => None }; @@ -21,7 +27,7 @@ let last = (t: t) => 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 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) => { @@ -38,20 +44,64 @@ let firstPairAtOrBeforeValue = (xValue, t: t) => { }; module XtoY = { - let ifAtX = (f, t: t) => - getBy(t, ((x, _)) => x == f) |> E.O.fmap(((_, y)) => y); - - let stepwise = (f, t: t) => + let stepwiseIncremental = (f, t: t) => firstPairAtOrBeforeValue(f, t) |> E.O.fmap(((_, y)) => y); + let stepwiseIfAtX = (f, t: t) => + getBy(t, ((x, _)) => 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 |> CdfLibrary.Distribution.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}; +module Combine = { + let combineLinear = (t1: t, t2: t, fn: (float, float) => float) => { + let allXs = Belt.Array.concat(xs(t1), xs(t2)); + allXs |> Array.sort(compare); + let allYs = + allXs + |> E.A.fmap(x => { + let y1 = XtoY.linear(x, t1); + let y2 = XtoY.linear(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.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.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); @@ -155,12 +205,19 @@ module Range = { let derivative = mapYsBasedOnRanges(delta_y_over_delta_x); - let stepsToContinuous = t => - Belt.Array.zip(t.xs, t.ys) - |> E.A.toRanges - |> E.R.toOption - |> E.O.fmap(r => r |> Belt.Array.map(_, rangePointAssumingSteps)) - |> E.O.fmap(Belt.Array.unzip) - |> E.O.fmap(fromArray) - |> E.O.fmap(intersperce(t)); + // TODO: It would be nicer if this the diff didn't change the first element, and also maybe if there were a more elegant way of doing this. + let stepsToContinuous = t => { + let diff = xTotalRange(t) |> E.O.fmap(r => r *. 0.00001); + switch (diff, E.A.toRanges(Belt.Array.zip(t.xs, t.ys))) { + | (Some(diff), Ok(items)) => + Some( + items + |> Belt.Array.map(_, rangePointAssumingSteps) + |> Belt.Array.unzip + |> fromArray + |> intersperce(t |> xMap(e => e +. diff)), + ) + | _ => None + }; + }; }; \ No newline at end of file