Merge pull request #4 from foretold-app/feature/1083

Feature/1083
This commit is contained in:
Ozzie Gooen 2020-02-26 09:13:38 +00:00 committed by GitHub
commit 3f901a1102
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 579 additions and 180 deletions

179
__tests__/CDF__Test.re Normal file
View File

@ -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.)
});
});
});

View File

@ -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.)|])
});
});

View File

@ -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 */

View File

@ -1,174 +1,170 @@
const { const {
Cdf, Cdf,
Pdf, Pdf,
ContinuousDistribution, ContinuousDistribution,
ContinuousDistributionCombination, ContinuousDistributionCombination,
scoringFunctions, scoringFunctions,
} = require("@foretold/cdf/lib"); } = require("@foretold/cdf/lib");
const _ = require("lodash"); 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 };
}
/** /**
* *
* @param xs * @param xs
* @param ys * @param ys
* @returns {{ys: *, xs: *}} * @returns {{ys: *, xs: *}}
*/ */
function pdfToCdf({ xs, ys }) { function cdfToPdf({ xs, ys }) {
let cdf = new Pdf(xs, ys); let cdf = new Cdf(xs, ys);
let pdf = cdf.toCdf(); let pdf = cdf.toPdf();
return { xs: pdf.xs, ys: pdf.ys }; 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 x * @param xs
* @param xs * @param ys
* @param ys * @returns {{ys: *, xs: *}}
* @returns {number[]} */
*/ function pdfToCdf({ xs, ys }) {
function convertToNewLength(n, { xs, ys }) { let cdf = new Pdf(xs, ys);
let dist = new ContinuousDistribution(xs, ys); let pdf = cdf.toCdf();
return dist.convertToNewLength(n); return { xs: pdf.xs, ys: pdf.ys };
} }
/** /**
* *
* @param y * @param sampleCount
* @param xs * @param vars
* @param ys * @returns {{ys: *, xs: *}}
* @returns {number} */
*/ function mean(sampleCount, vars) {
function findX(y, { xs, ys }) { let cdfs = vars.map(r => new Cdf(r.xs, r.ys));
let cdf = new Cdf(xs, ys); let comb = new ContinuousDistributionCombination(cdfs);
return cdf.findX(y); let newCdf = comb.combineYsWithMean(sampleCount);
}
return { xs: newCdf.xs, ys: newCdf.ys };
/** }
*
* @param xs /**
* @param ys *
* @returns {number[]} * @param sampleCount
*/ * @param predictionCdf
function integral({ xs, ys }) { * @param resolutionCdf
if (_.includes(ys, NaN)){ */
return NaN; function scoreNonMarketCdfCdf(sampleCount, predictionCdf, resolutionCdf, resolutionUniformAdditionWeight = 0) {
} let toCdf = (r) => (new Cdf(r.xs, r.ys));
else if (_.includes(ys, Infinity) && _.includes(ys, -Infinity)){ let prediction = toCdf(predictionCdf);
return NaN; if (_.isFinite(resolutionUniformAdditionWeight)) {
} prediction = prediction.combineWithUniformOfCdf(
else if (_.includes(ys, Infinity)){ {
return Infinity; cdf: toCdf(resolutionCdf),
} uniformWeight: resolutionUniformAdditionWeight,
else if (_.includes(ys, -Infinity)){ sampleCount
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 = { return scoringFunctions.distributionInputDistributionOutputMarketless({
cdfToPdf, predictionCdf: prediction,
pdfToCdf, resultCdf: toCdf(resolutionCdf),
findY, sampleCount,
findX, });
convertToNewLength, }
mean,
scoreNonMarketCdfCdf, /**
differentialEntropy, *
integral, * @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,
};

107
src/utility/lib/CDF.re Normal file
View File

@ -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;
}
});
};
};

View File

@ -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;