diff --git a/__tests__/CDF__Test.re b/__tests__/CDF__Test.re new file mode 100644 index 00000000..68d0bd37 --- /dev/null +++ b/__tests__/CDF__Test.re @@ -0,0 +1,179 @@ +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 new file mode 100644 index 00000000..5dda5e3d --- /dev/null +++ b/__tests__/Functions__Test.re @@ -0,0 +1,92 @@ +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/src/Index.bs.js b/src/Index.bs.js deleted file mode 100644 index d28cff64..00000000 --- a/src/Index.bs.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -var React = require("react"); -var ReactDOMRe = require("reason-react/src/ReactDOMRe.js"); -var App$ProbExample = require("./App.bs.js"); - -((import('./styles/index.css'))); - -ReactDOMRe.renderToElementWithId(React.createElement(App$ProbExample.make, { }), "app"); - -/* Not a pure module */ diff --git a/src/utility/CdfLibrary.js b/src/utility/CdfLibrary.js index ffe63322..2e23da7e 100644 --- a/src/utility/CdfLibrary.js +++ b/src/utility/CdfLibrary.js @@ -1,174 +1,170 @@ const { - Cdf, - Pdf, - ContinuousDistribution, - ContinuousDistributionCombination, - scoringFunctions, - } = require("@foretold/cdf/lib"); - const _ = require("lodash"); - - /** - * - * @param xs - * @param ys - * @returns {{ys: *, xs: *}} - */ - function cdfToPdf({ xs, ys }) { - let cdf = new Cdf(xs, ys); - let pdf = cdf.toPdf(); - return { xs: pdf.xs, ys: pdf.ys }; - } + Cdf, + Pdf, + ContinuousDistribution, + ContinuousDistributionCombination, + scoringFunctions, +} = require("@foretold/cdf/lib"); +const _ = require("lodash"); - /** - * - * @param xs - * @param ys - * @returns {{ys: *, xs: *}} - */ - function pdfToCdf({ xs, ys }) { - let cdf = new Pdf(xs, ys); - let pdf = cdf.toCdf(); - return { xs: pdf.xs, ys: pdf.ys }; - } - - /** - * - * @param sampleCount - * @param vars - * @returns {{ys: *, xs: *}} - */ - function mean(sampleCount, vars) { - let cdfs = vars.map(r => new Cdf(r.xs, r.ys)); - let comb = new ContinuousDistributionCombination(cdfs); - let newCdf = comb.combineYsWithMean(sampleCount); - - return { xs: newCdf.xs, ys: newCdf.ys }; - } - - /** - * - * @param sampleCount - * @param predictionCdf - * @param resolutionCdf - */ - function scoreNonMarketCdfCdf(sampleCount, predictionCdf, resolutionCdf, resolutionUniformAdditionWeight=0) { - let toCdf = (r) => (new Cdf(r.xs, r.ys)); - let prediction = toCdf(predictionCdf); - if (_.isFinite(resolutionUniformAdditionWeight)){ - prediction = prediction.combineWithUniformOfCdf( - { - cdf: toCdf(resolutionCdf), - uniformWeight: resolutionUniformAdditionWeight, - sampleCount - } - ); - } - - return scoringFunctions.distributionInputDistributionOutputMarketless({ - predictionCdf: prediction, - resultCdf: toCdf(resolutionCdf), - sampleCount, - }); - } - - /** - * - * @param sampleCount - * @param cdf - */ - function differentialEntropy(sampleCount, cdf) { - let toCdf = (r) => (new Cdf(r.xs, r.ys)); - - return scoringFunctions.differentialEntropy({ - cdf: toCdf(cdf), - sampleCount: sampleCount - }); - } - - /** - * - * @param x - * @param xs - * @param ys - * @returns {number} - */ - function findY(x, { xs, ys }) { - let cdf = new Cdf(xs, ys); - return cdf.findY(x); - } +/** + * + * @param xs + * @param ys + * @returns {{ys: *, xs: *}} + */ +function cdfToPdf({ xs, ys }) { + let cdf = new Cdf(xs, ys); + let pdf = cdf.toPdf(); + return { xs: pdf.xs, ys: pdf.ys }; +} - /** - * - * @param x - * @param xs - * @param ys - * @returns {number[]} - */ - function convertToNewLength(n, { xs, ys }) { - let dist = new ContinuousDistribution(xs, ys); - return dist.convertToNewLength(n); - } - - /** - * - * @param y - * @param xs - * @param ys - * @returns {number} - */ - function findX(y, { xs, ys }) { - let cdf = new Cdf(xs, ys); - return cdf.findX(y); - } - - /** - * - * @param xs - * @param ys - * @returns {number[]} - */ - function integral({ xs, ys }) { - if (_.includes(ys, NaN)){ - return NaN; - } - else if (_.includes(ys, Infinity) && _.includes(ys, -Infinity)){ - return NaN; - } - else if (_.includes(ys, Infinity)){ - return Infinity; - } - else if (_.includes(ys, -Infinity)){ - return -Infinity; - } - - let integral = 0; - for (let i = 1; i < ys.length; i++) { - let thisY = ys[i]; - let lastY = ys[i - 1]; - let thisX = xs[i]; - let lastX = xs[i - 1]; - - if ( - _.isFinite(thisY) && _.isFinite(lastY) && - _.isFinite(thisX) && _.isFinite(lastX) - ) { - let sectionInterval = ((thisY + lastY) / 2) * (thisX - lastX); - integral = integral + sectionInterval; +/** + * + * @param xs + * @param ys + * @returns {{ys: *, xs: *}} + */ +function pdfToCdf({ xs, ys }) { + let cdf = new Pdf(xs, ys); + let pdf = cdf.toCdf(); + return { xs: pdf.xs, ys: pdf.ys }; +} + +/** + * + * @param sampleCount + * @param vars + * @returns {{ys: *, xs: *}} + */ +function mean(sampleCount, vars) { + let cdfs = vars.map(r => new Cdf(r.xs, r.ys)); + let comb = new ContinuousDistributionCombination(cdfs); + let newCdf = comb.combineYsWithMean(sampleCount); + + return { xs: newCdf.xs, ys: newCdf.ys }; +} + +/** + * + * @param sampleCount + * @param predictionCdf + * @param resolutionCdf + */ +function scoreNonMarketCdfCdf(sampleCount, predictionCdf, resolutionCdf, resolutionUniformAdditionWeight = 0) { + let toCdf = (r) => (new Cdf(r.xs, r.ys)); + let prediction = toCdf(predictionCdf); + if (_.isFinite(resolutionUniformAdditionWeight)) { + prediction = prediction.combineWithUniformOfCdf( + { + cdf: toCdf(resolutionCdf), + uniformWeight: resolutionUniformAdditionWeight, + sampleCount } - - } - return integral; + ); } - - module.exports = { - cdfToPdf, - pdfToCdf, - findY, - findX, - convertToNewLength, - mean, - scoreNonMarketCdfCdf, - differentialEntropy, - integral, - }; - \ No newline at end of file + + return scoringFunctions.distributionInputDistributionOutputMarketless({ + predictionCdf: prediction, + resultCdf: toCdf(resolutionCdf), + sampleCount, + }); +} + +/** + * + * @param sampleCount + * @param cdf + */ +function differentialEntropy(sampleCount, cdf) { + let toCdf = (r) => (new Cdf(r.xs, r.ys)); + + return scoringFunctions.differentialEntropy({ + cdf: toCdf(cdf), + sampleCount: sampleCount + }); +} + +/** + * + * @param x + * @param xs + * @param ys + * @returns {number} + */ +function findY(x, { xs, ys }) { + let cdf = new Cdf(xs, ys); + return cdf.findY(x); +} + +/** + * + * @param x + * @param xs + * @param ys + * @returns {number[]} + */ +function convertToNewLength(n, { xs, ys }) { + let dist = new ContinuousDistribution(xs, ys); + return dist.convertToNewLength(n); +} + +/** + * + * @param y + * @param xs + * @param ys + * @returns {number} + */ +function findX(y, { xs, ys }) { + let cdf = new Cdf(xs, ys); + return cdf.findX(y); +} + +/** + * + * @param xs + * @param ys + * @returns {number[]} + */ +function integral({ xs, ys }) { + if (_.includes(ys, NaN)) { + return NaN; + } else if (_.includes(ys, Infinity) && _.includes(ys, -Infinity)) { + return NaN; + } else if (_.includes(ys, Infinity)) { + return Infinity; + } else if (_.includes(ys, -Infinity)) { + return -Infinity; + } + + let integral = 0; + for (let i = 1; i < ys.length; i++) { + let thisY = ys[i]; + let lastY = ys[i - 1]; + let thisX = xs[i]; + let lastX = xs[i - 1]; + + if ( + _.isFinite(thisY) && _.isFinite(lastY) && + _.isFinite(thisX) && _.isFinite(lastX) + ) { + let sectionInterval = ((thisY + lastY) / 2) * (thisX - lastX); + integral = integral + sectionInterval; + } + + } + return integral; +} + +module.exports = { + cdfToPdf, + pdfToCdf, + findY, + findX, + convertToNewLength, + mean, + scoreNonMarketCdfCdf, + differentialEntropy, + integral, +}; diff --git a/src/utility/lib/CDF.re b/src/utility/lib/CDF.re new file mode 100644 index 00000000..cbfefa6c --- /dev/null +++ b/src/utility/lib/CDF.re @@ -0,0 +1,107 @@ +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 = Belt.Array.getIndexBy(xs, e => e >= x); + switch (firstHigherIndex) { + | None => maxY() + | Some(0) => minY() + | Some(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 = Belt.Array.getIndexBy(ys, e => e >= y); + switch (firstHigherIndex) { + | None => maxX() + | Some(0) => minX() + | Some(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; + } + }); + }; +}; diff --git a/src/utility/lib/Functions.re b/src/utility/lib/Functions.re new file mode 100644 index 00000000..3124c873 --- /dev/null +++ b/src/utility/lib/Functions.re @@ -0,0 +1,36 @@ +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;