Adds required files
This commit is contained in:
parent
7bc62d3934
commit
8aafb2bb6e
204
src/utility/lib/continuousDistribution.js
Normal file
204
src/utility/lib/continuousDistribution.js
Normal file
|
@ -0,0 +1,204 @@
|
|||
const _ = require('lodash');
|
||||
const { interpolate, range, min, max } = require('./functions');
|
||||
|
||||
class ContinuousDistribution {
|
||||
/**
|
||||
* @param {number[]} xs
|
||||
* @param {number[]} ys
|
||||
*/
|
||||
constructor(xs, ys) {
|
||||
if (!_.isArray(xs)) {
|
||||
throw new Error('XS should be an array.');
|
||||
}
|
||||
if (!_.isArray(ys)) {
|
||||
throw new Error('YS should be an array.');
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[]} xs
|
||||
* @param {number[]} ys
|
||||
* @return {boolean}
|
||||
*/
|
||||
validateSize(xs, ys) {
|
||||
return xs.length === ys.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param xs
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateHasLength(xs) {
|
||||
return xs.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
minX() {
|
||||
return this.xs[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
maxX() {
|
||||
return this.xs[this.xs.length - 1]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[]} xs
|
||||
* @return {ContinuousDistribution}
|
||||
*/
|
||||
convertWithAlternativeXs(xs) {
|
||||
const ys = xs.map(x => this.findY(x));
|
||||
return new ContinuousDistribution(xs, ys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} newLength
|
||||
* @return {ContinuousDistribution}
|
||||
*/
|
||||
convertToNewLength(newLength) {
|
||||
const _range = range(min(this.xs), max(this.xs), newLength);
|
||||
return this.convertWithAlternativeXs(_range);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
sampleSingle() {
|
||||
const y = Math.random();
|
||||
return this.findX(y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
};
|
111
src/utility/lib/continuousDistribution.spec.js
Normal file
111
src/utility/lib/continuousDistribution.spec.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
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);
|
||||
});
|
||||
})
|
||||
});
|
136
src/utility/lib/functions.js
Normal file
136
src/utility/lib/functions.js
Normal file
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[]} arr
|
||||
* @return {number}
|
||||
*/
|
||||
function sum(arr) {
|
||||
return arr.reduce((acc, val) => acc + val, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[]} arr
|
||||
* @return {number}
|
||||
*/
|
||||
function mean(arr) {
|
||||
return sum(arr) / arr.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @return {number}
|
||||
*/
|
||||
function random(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
};
|
55
src/utility/lib/functions.spec.js
Normal file
55
src/utility/lib/functions.spec.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
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]);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user