From 4fc7723e32d73ab98592d8569b4354d142280436 Mon Sep 17 00:00:00 2001 From: Roman Galochkin Date: Tue, 25 Feb 2020 13:10:32 +0300 Subject: [PATCH] Adds integral function --- __tests__/CDF__Test.re | 2 +- src/utility/lib/continuousDistribution.js | 214 ------------------ .../lib/continuousDistribution.spec.js | 111 --------- src/utility/lib/functions.js | 145 ------------ src/utility/lib/functions.spec.js | 55 ----- 5 files changed, 1 insertion(+), 526 deletions(-) delete mode 100644 src/utility/lib/continuousDistribution.js delete mode 100644 src/utility/lib/continuousDistribution.spec.js delete mode 100644 src/utility/lib/functions.js delete mode 100644 src/utility/lib/functions.spec.js diff --git a/__tests__/CDF__Test.re b/__tests__/CDF__Test.re index cfe74694..68d0bd37 100644 --- a/__tests__/CDF__Test.re +++ b/__tests__/CDF__Test.re @@ -144,7 +144,7 @@ describe("CDF", () => { }); }); - // @todo + // @todo: Should each test expect 70.? describe("sample", () => { open Functions; let xs = up(1, 9); diff --git a/src/utility/lib/continuousDistribution.js b/src/utility/lib/continuousDistribution.js deleted file mode 100644 index bd777908..00000000 --- a/src/utility/lib/continuousDistribution.js +++ /dev/null @@ -1,214 +0,0 @@ -const _ = require('lodash'); -const { interpolate, range, min, max } = require('./functions'); - -class ContinuousDistribution { - /** - * @Done - * @param {number[]} xs - * @param {number[]} ys - */ - constructor(xs, ys) { - if (!this.validateHasLength(xs)) { - throw new Error('You need at least one element.'); - } - if (!this.validateSize(xs, ys)) { - throw new Error('Arrays of "xs" and "ys" have different sizes.'); - } - - const sorted = this.order(xs, ys); - this.xs = sorted.xs; - this.ys = sorted.ys; - } - - /** - * @Done - * Order them to make sure that xs are increasing - * @param {number[]} xs - * @param {number[]} ys - * @return {{ys: number[], xs: number[]}} - */ - order(xs, ys) { - const xsYs = xs.map((v, i) => ({ ys: ys[i], xs: v })); - const sorted = xsYs.sort((a, b) => { - if (a.xs > b.xs) return 1; - if (a.xs < b.xs) return -1; - return 0; - }); - - const XS = sorted.map(v => v.xs); - const YS = sorted.map(v => v.ys); - - return { xs: XS, ys: YS }; - } - - /** - * @Done - * @param {number[]} xs - * @param {number[]} ys - * @return {boolean} - */ - validateSize(xs, ys) { - return xs.length === ys.length; - } - - /** - * @Done - * @param xs - * @returns {boolean} - */ - validateHasLength(xs) { - return xs.length > 0; - } - - /** - * @Done - * @returns {number} - */ - minX() { - return this.xs[0] - } - - /** - * @Done - * @returns {number} - */ - maxX() { - return this.xs[this.xs.length - 1] - } - - /** - * @Done - * If xs=[1,2,3], and ys=[5,6,7], - * then findY(1) = 5, findY(3) = 7, findY(1.5) = 5.5 - * @param {number} x - * @return {number} - */ - findY(x) { - let firstHigherIndex = this.xs.findIndex(X => X >= x); - if (firstHigherIndex < 0) return this.ys[this.ys.length - 1]; - if (firstHigherIndex === 0) return this.ys[0]; - - let lowerOrEqualIndex = firstHigherIndex - 1; - if (lowerOrEqualIndex < 0) lowerOrEqualIndex = 0; - - let needsInterpolation = this.xs[lowerOrEqualIndex] !== x; - if (needsInterpolation) { - return interpolate( - this.xs[lowerOrEqualIndex], - this.xs[firstHigherIndex], - this.ys[lowerOrEqualIndex], - this.ys[firstHigherIndex], - x - ); - } else { - return this.ys[lowerOrEqualIndex]; - } - } - - /** - * @Done - * If xs=[1,2,3], and ys=[5,6,7], - * then findX(5) = 1, findX(7) = 3, findY(5.5) = 1.5 - * This should do the same thing as `findY`, but for Y. - * @param {number} y - * @return {number} - */ - findX(y) { - let firstHigherIndex = this.ys.findIndex(Y => Y >= y); - if (firstHigherIndex < 0) return this.xs[this.xs.length - 1]; - if (firstHigherIndex === 0) return this.xs[0]; - - let lowerOrEqualIndex = firstHigherIndex - 1; - if (lowerOrEqualIndex < 0) lowerOrEqualIndex = 0; - - let needsInterpolation = this.ys[lowerOrEqualIndex] !== y; - if (needsInterpolation) { - return interpolate( - this.ys[lowerOrEqualIndex], - this.ys[firstHigherIndex], - this.xs[lowerOrEqualIndex], - this.xs[firstHigherIndex], - y - ); - } else { - return this.xs[lowerOrEqualIndex]; - } - } - - /** - * @Done - * @param {number[]} xs - * @return {ContinuousDistribution} - */ - convertWithAlternativeXs(xs) { - const ys = xs.map(x => this.findY(x)); - return new ContinuousDistribution(xs, ys); - } - - /** - * Done - * @param {number} newLength - * @return {ContinuousDistribution} - */ - convertToNewLength(newLength) { - const _range = range(min(this.xs), max(this.xs), newLength); - return this.convertWithAlternativeXs(_range); - } - - /** - * @Done - * @return {number} - */ - sampleSingle() { - const y = Math.random(); - return this.findX(y); - } - - /** - * @Done - * Poduce n samples, using ``sampleSingle`` for each. - * @param size - * @return {number[]} - */ - sample(size) { - return Array.from(Array(size), () => this.sampleSingle()); - } - - /** - * Finds the integral. Takes the average Y value between points, - * treating them like a triangle. - * @return {number[]} - */ - integral(params = { filterOutNaNs: false }) { - let integral = 0; - if (!params.filterOutNaNs && _.includes(this.ys, NaN)) { - return NaN; - } else if (_.includes(this.ys, Infinity) && _.includes(this.ys, -Infinity)) { - return NaN; - } else if (_.includes(this.ys, Infinity)) { - return Infinity; - } else if (_.includes(this.ys, -Infinity)) { - return -Infinity; - } - for (let i = 1; i < this.ys.length; i++) { - let thisY = this.ys[i]; - let lastY = this.ys[i - 1]; - let thisX = this.xs[i]; - let lastX = this.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 = { - ContinuousDistribution, -}; diff --git a/src/utility/lib/continuousDistribution.spec.js b/src/utility/lib/continuousDistribution.spec.js deleted file mode 100644 index 114caf58..00000000 --- a/src/utility/lib/continuousDistribution.spec.js +++ /dev/null @@ -1,111 +0,0 @@ -const { ContinuousDistribution } = require('./continuousDistribution'); -const { up, down } = require('./functions'); - -describe('ContinuousDistribution Class', () => { - it('constructor()', () => { - const xs = up(1, 9); - const ys = up(1, 8); - expect(() => { - new ContinuousDistribution(xs, ys); - }).toThrow(/^Arrays of "xs" and "ys" have different sizes.$/); - }); - it('order()', () => { - const xs = down(9, 1); - const ys = down(9, 1); - const cdf = new ContinuousDistribution(xs, ys); - expect(cdf.xs).toEqual(up(1, 9)); - expect(cdf.ys).toEqual(up(1, 9)); - }); - it('findY()', () => { - const xs = [1, 2, 3]; - const ys = [5, 6, 7]; - const cdf = new ContinuousDistribution(xs, ys); - expect(cdf.findY(1)).toEqual(5); - expect(cdf.findY(1.5)).toEqual(5.5); - expect(cdf.findY(3)).toEqual(7); - expect(cdf.findY(4)).toEqual(7); - expect(cdf.findY(15)).toEqual(7); - expect(cdf.findY(-1)).toEqual(5); - }); - it('findX()', () => { - const xs = [1, 2, 3]; - const ys = [5, 6, 7]; - const cdf = new ContinuousDistribution(xs, ys); - expect(cdf.findX(5)).toEqual(1); - expect(cdf.findX(7)).toEqual(3); - expect(cdf.findX(5.5)).toEqual(1.5); - expect(cdf.findX(8)).toEqual(3); - expect(cdf.findX(4)).toEqual(1); - }); - it('convertWithAlternativeXs() when "XS" within "xs"', () => { - const xs = up(1, 9); - const ys = up(20, 28); - const cdf = new ContinuousDistribution(xs, ys); - const XS = up(3, 7); - const CDF = cdf.convertWithAlternativeXs(XS); - expect(CDF.xs).toEqual([3, 4, 5, 6, 7]); - expect(CDF.ys).toEqual([22, 23, 24, 25, 26]); - }); - it('convertToNewLength()', () => { - const xs = up(1, 9); - const ys = up(50, 58); - const cdf = new ContinuousDistribution(xs, ys); - const CDF = cdf.convertToNewLength(3); - expect(CDF.xs).toEqual([1, 5, 9]); - expect(CDF.ys).toEqual([50, 54, 58]); - }); - it('sample()', () => { - const xs = up(1, 9); - const ys = up(70, 78); - const cdf = new ContinuousDistribution(xs, ys); - const XS = cdf.sample(3); - expect(Number.isInteger(XS[0])).toBe(true); - expect(Number.isInteger(XS[1])).toBe(true); - expect(Number.isInteger(XS[2])).toBe(true); - }); - - describe('integral()', () => { - it('with regular inputs', () => { - const xs = [0, 1, 2, 4]; - const ys = [0.0, 1.0, 2.0, 2.0]; - const cdf = new ContinuousDistribution(xs, ys); - const integral = cdf.integral(); - expect(integral).toEqual(6); - }); - it('with an infinity', () => { - const xs = [0, 1, 2, 4]; - const ys = [0.0, 1.0, Infinity, 2.0]; - const cdf = new ContinuousDistribution(xs, ys); - const integral = cdf.integral(); - expect(integral).toEqual(Infinity); - }); - it('with negative infinity', () => { - const xs = [0, 1, 2, 4]; - const ys = [0.0, 1.0, -Infinity, 2.0]; - const cdf = new ContinuousDistribution(xs, ys); - const integral = cdf.integral(); - expect(integral).toEqual(-Infinity); - }); - it('with both positive and negative infinities', () => { - const xs = [0, 1, 2, 4]; - const ys = [0.0, 1.0, -Infinity, Infinity]; - const cdf = new ContinuousDistribution(xs, ys); - const integral = cdf.integral(); - expect(integral).toEqual(NaN); - }); - it('with a NaN and filterOutNaNs set to false', () => { - const xs = [0, 1, 2, 4]; - const ys = [0.0, 1.0, 2.0, NaN]; - const cdf = new ContinuousDistribution(xs, ys); - const integral = cdf.integral({ filterOutNaNs: false }); - expect(integral).toEqual(NaN); - }); - it('with a NaN and filterOutNaNs set to true', () => { - const xs = [0, 1, 2, 4]; - const ys = [0.0, 1.0, 2.0, NaN]; - const cdf = new ContinuousDistribution(xs, ys); - const integral = cdf.integral({ filterOutNaNs: true }); - expect(integral).toEqual(2); - }); - }) -}); diff --git a/src/utility/lib/functions.js b/src/utility/lib/functions.js deleted file mode 100644 index c8c6fbfa..00000000 --- a/src/utility/lib/functions.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * @Done - * @param {number} xMin - * @param {number} xMax - * @param {number} yMin - * @param {number} yMax - * @param {number} xIntended - * @return {number} - */ -function interpolate(xMin, xMax, yMin, yMax, xIntended) { - const minProportion = (xMax - xIntended) / (xMax - xMin); - const maxProportion = (xIntended - xMin) / (xMax - xMin); - return (yMin * minProportion) + (yMax * maxProportion); -} - -/** - * @Done - * This should return an array of n evenly-spaced items - * between min and max, including min and max. - * range(1,5,3) = [1, 3, 5]; - * range(1,5,5) = [1, 2, 3, 4, 5]; - * @param {number} min - * @param {number} max - * @param {number} n - * @return {number[]} - */ -function range(min, max, n) { - if (n <= 0) throw new RangeError('n is less then zero'); - if (n === Infinity) throw new RangeError('n is Infinity'); - if (n === 0) return []; - if (n === 1) return [min]; - if (n === 2) return [min, max]; - if (min === max) return Array(n).fill(min); - n -= 1; - const diff = min - max; - const interval = Math.abs(diff / n); - - const result = []; - - let item = min; - do { - result.push(item); - item += interval; - } while (item <= max); - - // corrects results because of math errors - if ((n + 1) - result.length === 1) { - result.push(max); - } - - return result; -} - -/** - * @Done - * @param {number[]} arr - * @return {number} - */ -function sum(arr) { - return arr.reduce((acc, val) => acc + val, 0); -} - -/** - * @Done - * @param {number[]} arr - * @return {number} - */ -function mean(arr) { - return sum(arr) / arr.length; -} - -/** - * @Done - * @param {number[]} arr - * @return {number} - */ -function min(arr) { - let val = arr[0]; - for (let i = 1; i < arr.length; i++) { - if (arr[i] < val) { - val = arr[i]; - } - } - return val; -} - -/** - * @Done - * @param {number[]} arr - * @return {number} - */ -function max(arr) { - let val = arr[0]; - for (let i = 1; i < arr.length; i++) { - if (arr[i] > val) { - val = arr[i]; - } - } - return val; -} - -/** - * @Done - * @param {number} min - * @param {number} max - * @return {number} - */ -function random(min, max) { - return Math.floor(Math.random() * (max - min + 1) + min); -} - -/** - * @Done - * @param {number} from - * @param {number} to - * @return {number[]} - */ -function up(from, to) { - const arr = []; - for (let i = from; i <= to; i++) arr.push(i); - return arr; -} - -/** - * @Done - * @param {number} from - * @param {number} to - * @return {number[]} - */ -function down(from, to) { - const arr = []; - for (let i = from; i >= to; i--) arr.push(i); - return arr; -} - -module.exports = { - interpolate, - min, - max, - range, - mean, - random, - up, - down, -}; diff --git a/src/utility/lib/functions.spec.js b/src/utility/lib/functions.spec.js deleted file mode 100644 index 948f1f20..00000000 --- a/src/utility/lib/functions.spec.js +++ /dev/null @@ -1,55 +0,0 @@ -const { interpolate } = require('./functions'); -const { range } = require('./functions'); -const { mean } = require('./functions'); -const { min } = require('./functions'); -const { max } = require('./functions'); -const { random } = require('./functions'); -const { up, down } = require('./functions'); - -describe('Functions', () => { - it('interpolate()', () => { - expect(interpolate( - 10, 20, - 1, 2, - 15 - )).toBe(1.5); - }); - it('range()', () => { - expect(range(1, 5, 3)).toEqual([1, 3, 5]); - expect(range(1, 5, 5)).toEqual([1, 2, 3, 4, 5]); - expect(range(-10, 15, 2)).toEqual([-10, 15]); - expect(range(-10, 15, 3)).toEqual([-10, 2.5, 15]); - expect(range(-10.3, 17, 3)).toEqual([-10.3, 3.3499999999999996, 17]); - expect(range(-10.3, 17, 5)).toEqual([-10.3, -3.4750000000000005, 3.3499999999999996, 10.175, 17]); - expect(range(-10.3, 17.31, 3)).toEqual([-10.3, 3.504999999999999, 17.31]); - expect(range(1, 1, 3)).toEqual([1, 1, 1]); - }); - it('mean()', () => { - expect(mean([1, 2, 3])).toBe(2); - expect(mean([1, 2, 3, -2])).toBe(1); - expect(mean([1, 2, 3, -2, -10])).toBe(-1.2); - }); - it('min()', () => { - expect(min([1, 2, 3])).toBe(1); - expect(min([-1, -2, 0, 20])).toBe(-2); - expect(min([-1, -2, 0, 20, -2.2])).toBe(-2.2); - }); - it('max()', () => { - expect(max([1, 2, 3])).toBe(3); - expect(max([-1, -2, 0, 20])).toBe(20); - expect(max([-1, -2, 0, -2.2])).toBe(0); - }); - it('random()', () => { - const num = random(1, 5); - expect(num).toBeLessThanOrEqual(5); - expect(num).toBeGreaterThanOrEqual(1); - }); - it('up()', () => { - expect(up(1, 5)).toEqual([1, 2, 3, 4, 5]); - expect(up(-1, 5)).toEqual([-1, 0, 1, 2, 3, 4, 5]); - }); - it('down()', () => { - expect(down(5, 1)).toEqual([5, 4, 3, 2, 1]); - expect(down(5, -1)).toEqual([5, 4, 3, 2, 1, 0, -1]); - }); -});