Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quantile-refactor #3003

Merged
merged 31 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
104310f
Included math to syntax when missing
dvd101x Apr 24, 2023
431ee46
Included solveODE
May 26, 2023
1475b41
renamed initialStep as firstStep
May 29, 2023
b4cac47
Included tests for solveODE
May 29, 2023
ca78866
Test the full state instead of the final state
May 29, 2023
bc0dd3f
Fixed issue with tolerance
May 29, 2023
ffc5a2f
Merge branch 'develop' into master
dvd101x May 29, 2023
35a875e
Added unit signature for y0
dvd101x May 30, 2023
9eb77ac
Included units test also for y0
dvd101x Jun 2, 2023
727c34e
Included embedded docs and more tests
dvd101x Jul 1, 2023
9873270
Included error for tspan
dvd101x Jul 2, 2023
8071e91
It works with bignumbers
dvd101x Jul 2, 2023
b364fc5
reduced calling bignumber
dvd101x Jul 2, 2023
d2a9cb8
extended the search for bignumbers
dvd101x Jul 2, 2023
6f56580
The jsdocs is less ambiguous
dvd101x Jul 5, 2023
9448e88
included tests for step options
dvd101x Jul 6, 2023
a827c7b
Allowed for 0 minStep
dvd101x Jul 6, 2023
61f063f
Optimization to avoid checking the sign every step
dvd101x Jul 10, 2023
9d84f1b
Merge branch 'develop' into master
josdejong Jul 12, 2023
1552ddb
Merge branch 'develop' into master
josdejong Jul 12, 2023
41bd5c8
refactor
dvd101x Jul 29, 2023
034a894
Merge branch 'josdejong:develop' into quantile-refactor
dvd101x Jul 29, 2023
c9517b0
Typo
dvd101x Jul 29, 2023
8e4d899
removed unnecesary error
dvd101x Jul 29, 2023
6134f18
Fixes conflict with develop
dvd101x Aug 24, 2023
f015ab4
Merge branch 'develop' into quantile-refactor
dvd101x Aug 24, 2023
8b4d23b
Merge logic on _quantileSeqProbNumber
dvd101x Sep 16, 2023
119d353
Reduced _quantileSeqProbCollection
dvd101x Sep 16, 2023
8c2d4d4
Merged logic of _quantileSeq
dvd101x Sep 16, 2023
5d9f685
Fixed issue with transform and browser
dvd101x Sep 17, 2023
a9bd7ea
Merge branch 'develop' into quantile-refactor
josdejong Sep 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/expression/transform/quantileSeq.transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createQuantileSeq } from '../../function/statistics/quantileSeq.js'
import { lastDimToZeroBase } from './utils/lastDimToZeroBase.js'

const name = 'quantileSeq'
const dependencies = ['typed', 'add', 'multiply', 'partitionSelect', 'compare', 'isInteger']
const dependencies = ['typed', 'bignumber', 'add', 'subtract', 'divide', 'multiply', 'partitionSelect', 'compare', 'isInteger', 'smaller', 'smallerEq', 'larger']

/**
* Attach a transform function to math.quantileSeq
Expand All @@ -12,12 +12,18 @@ const dependencies = ['typed', 'add', 'multiply', 'partitionSelect', 'compare',
* This transform changed the `dim` parameter of function std
* from one-based to zero based
*/
export const createQuantileSeqTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, multiply, partitionSelect, compare, isInteger }) => {
const quantileSeq = createQuantileSeq({ typed, add, multiply, partitionSelect, compare, isInteger })
export const createQuantileSeqTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, bignumber, add, subtract, divide, multiply, partitionSelect, compare, isInteger, smaller, smallerEq, larger }) => {
const quantileSeq = createQuantileSeq({ typed, bignumber, add, subtract, divide, multiply, partitionSelect, compare, isInteger, smaller, smallerEq, larger })

return typed('quantileSeq', {
'Array|Matrix, number|BigNumber|Array, number': (arr, prob, dim) => quantileSeq(arr, prob, dimToZeroBase(dim)),
'Array|Matrix, number|BigNumber|Array, boolean, number': (arr, prob, sorted, dim) => quantileSeq(arr, prob, sorted, dimToZeroBase(dim))
'Array | Matrix, number | BigNumber': quantileSeq,
'Array | Matrix, number | BigNumber, number': (arr, prob, dim) => quantileSeq(arr, prob, dimToZeroBase(dim)),
'Array | Matrix, number | BigNumber, boolean': quantileSeq,
'Array | Matrix, number | BigNumber, boolean, number': (arr, prob, sorted, dim) => quantileSeq(arr, prob, sorted, dimToZeroBase(dim)),
'Array | Matrix, Array | Matrix': quantileSeq,
'Array | Matrix, Array | Matrix, number': (data, prob, dim) => quantileSeq(data, prob, dimToZeroBase(dim)),
'Array | Matrix, Array | Matrix, boolean': quantileSeq,
'Array | Matrix, Array | Matrix, boolean, number': (data, prob, sorted, dim) => quantileSeq(data, prob, sorted, dimToZeroBase(dim))
})

function dimToZeroBase (dim) {
Expand Down
255 changes: 77 additions & 178 deletions src/function/statistics/quantileSeq.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { isBigNumber, isCollection, isNumber } from '../../utils/is.js'
import { isNumber } from '../../utils/is.js'
import { flatten } from '../../utils/array.js'
import { factory } from '../../utils/factory.js'
import { createApply } from '../matrix/apply.js'

const name = 'quantileSeq'
const dependencies = ['typed', 'add', 'multiply', 'partitionSelect', 'compare', 'isInteger']
const dependencies = ['typed', '?bignumber', 'add', 'subtract', 'divide', 'multiply', 'partitionSelect', 'compare', 'isInteger', 'smaller', 'smallerEq', 'larger']

export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, multiply, partitionSelect, compare, isInteger }) => {
export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({ typed, bignumber, add, subtract, divide, multiply, partitionSelect, compare, isInteger, smaller, smallerEq, larger }) => {
/**
* Compute the prob order quantile of a matrix or a list with values.
* The sequence is sorted and the middle value is returned.
Expand Down Expand Up @@ -43,133 +43,77 @@ export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({
*/

const apply = createApply({ typed, isInteger })
/**
* Check if array value types are valid, throw error otherwise.
* @param {number | BigNumber | Unit} x
* @param {number | BigNumber | Unit} x
* @private
*/
const validate = typed({
'number | BigNumber | Unit': function (x) {
return x
}
})

return typed(name, {
'Array|Matrix, number|BigNumber|Array': (data, prob) => quantileSeq(data, prob, false),
'Array|Matrix, number|BigNumber|Array, boolean': quantileSeq,
'Array|Matrix, number|BigNumber|Array, number': (data, prob, dim) => _quantileSeqDim(data, prob, false, dim),
'Array|Matrix, number|BigNumber|Array, boolean, number': (data, prob, sorted, dim) => _quantileSeqDim(data, prob, sorted, dim)
'Array | Matrix, number | BigNumber': (data, p) => _quantileSeqProbNumber(data, p, false),
'Array | Matrix, number | BigNumber, number': (data, prob, dim) => _quantileSeqDim(data, prob, false, dim, _quantileSeqProbNumber),
'Array | Matrix, number | BigNumber, boolean': _quantileSeqProbNumber,
'Array | Matrix, number | BigNumber, boolean, number': (data, prob, sorted, dim) => _quantileSeqDim(data, prob, sorted, dim, _quantileSeqProbNumber),
'Array | Matrix, Array | Matrix': (data, p) => _quantileSeqProbCollection(data, p, false),
'Array | Matrix, Array | Matrix, number': (data, prob, dim) => _quantileSeqDim(data, prob, false, dim, _quantileSeqProbCollection),
'Array | Matrix, Array | Matrix, boolean': _quantileSeqProbCollection,
'Array | Matrix, Array | Matrix, boolean, number': (data, prob, sorted, dim) => _quantileSeqDim(data, prob, sorted, dim, _quantileSeqProbCollection)
})

function _quantileSeqDim (data, prob, sorted, dim) {
// return [1.3, 1.2]
return apply(data, dim, x => quantileSeq(x, prob, sorted))
function _quantileSeqDim (data, prob, sorted, dim, fn) {
return apply(data, dim, x => fn(x, prob, sorted))
}

function quantileSeq (data, probOrN, sorted) {
let probArr, dataArr, one

if (arguments.length < 2 || arguments.length > 3) {
throw new SyntaxError('Function quantileSeq requires two or three parameters')
function _quantileSeqProbNumber (data, probOrN, sorted) {
let probArr
const dataArr = data.valueOf()
if (smaller(probOrN, 0)) {
throw new Error('N/prob must be non-negative')
}
if (smallerEq(probOrN, 1)) {
// quantileSeq([a, b, c, d, ...], prob[,sorted])
return isNumber(probOrN)
? _quantileSeq(dataArr, probOrN, sorted)
: bignumber(_quantileSeq(dataArr, probOrN, sorted))
}
if (larger(probOrN, 1)) {
// quantileSeq([a, b, c, d, ...], N[,sorted])
if (!isInteger(probOrN)) {
throw new Error('N must be a positive integer')
}

if (isCollection(data)) {
sorted = sorted || false
if (typeof sorted === 'boolean') {
dataArr = data.valueOf()
if (isNumber(probOrN)) {
if (probOrN < 0) {
throw new Error('N/prob must be non-negative')
}

if (probOrN <= 1) {
// quantileSeq([a, b, c, d, ...], prob[,sorted])
return _quantileSeq(dataArr, probOrN, sorted)
}

if (probOrN > 1) {
// quantileSeq([a, b, c, d, ...], N[,sorted])
if (!isInteger(probOrN)) {
throw new Error('N must be a positive integer')
}

const nPlusOne = probOrN + 1
probArr = new Array(probOrN)
for (let i = 0; i < probOrN;) {
probArr[i] = _quantileSeq(dataArr, (++i) / nPlusOne, sorted)
}
return probArr
}
}

if (isBigNumber(probOrN)) {
const BigNumber = probOrN.constructor

if (probOrN.isNegative()) {
throw new Error('N/prob must be non-negative')
}

one = new BigNumber(1)

if (probOrN.lte(one)) {
// quantileSeq([a, b, c, d, ...], prob[,sorted])
return new BigNumber(_quantileSeq(dataArr, probOrN, sorted))
}

if (probOrN.gt(one)) {
// quantileSeq([a, b, c, d, ...], N[,sorted])
if (!probOrN.isInteger()) {
throw new Error('N must be a positive integer')
}

// largest possible Array length is 2^32-1
// 2^32 < 10^15, thus safe conversion guaranteed
const intN = probOrN.toNumber()
if (intN > 4294967295) {
throw new Error('N must be less than or equal to 2^32-1, as that is the maximum length of an Array')
}

const nPlusOne = new BigNumber(intN + 1)
probArr = new Array(intN)
for (let i = 0; i < intN;) {
probArr[i] = new BigNumber(_quantileSeq(dataArr, new BigNumber(++i).div(nPlusOne), sorted))
}
return probArr
}
}

if (isCollection(probOrN)) {
// quantileSeq([a, b, c, d, ...], [prob1, prob2, ...][,sorted])
const probOrNArr = probOrN.valueOf()
probArr = new Array(probOrNArr.length)
for (let i = 0; i < probArr.length; ++i) {
const currProb = probOrNArr[i]
if (isNumber(currProb)) {
if (currProb < 0 || currProb > 1) {
throw new Error('Probability must be between 0 and 1, inclusive')
}
} else if (isBigNumber(currProb)) {
one = new currProb.constructor(1)
if (currProb.isNegative() || currProb.gt(one)) {
throw new Error('Probability must be between 0 and 1, inclusive')
}
} else {
throw new TypeError('Unexpected type of argument in function quantileSeq') // FIXME: becomes redundant when converted to typed-function
}
// largest possible Array length is 2^32-1
// 2^32 < 10^15, thus safe conversion guaranteed
if (larger(probOrN, 4294967295)) {
throw new Error('N must be less than or equal to 2^32-1, as that is the maximum length of an Array')
}

probArr[i] = _quantileSeq(dataArr, currProb, sorted)
}
return probArr
}
const nPlusOne = add(probOrN, 1)
probArr = []

throw new TypeError('Unexpected type of argument in function quantileSeq') // FIXME: becomes redundant when converted to typed-function
for (let i = 0; smaller(i, probOrN); i++) {
const prob = divide(i + 1, nPlusOne)
probArr.push(_quantileSeq(dataArr, prob, sorted))
}

throw new TypeError('Unexpected type of argument in function quantileSeq') // FIXME: becomes redundant when converted to typed-function
return isNumber(probOrN) ? probArr : bignumber(probArr)
}
}

throw new TypeError('Unexpected type of argument in function quantileSeq') // FIXME: becomes redundant when converted to typed-function
/**
* Calculate the prob order quantile of an n-dimensional array.
*
* @param {Array, Matrix} array
* @param {Array, Matrix} prob
* @param {Boolean} sorted
* @return {Number, BigNumber, Unit} prob order quantile
* @private
*/

function _quantileSeqProbCollection (data, probOrN, sorted) {
const dataArr = data.valueOf()
// quantileSeq([a, b, c, d, ...], [prob1, prob2, ...][,sorted])
const probOrNArr = probOrN.valueOf()
const probArr = []
for (let i = 0; i < probOrNArr.length; ++i) {
probArr.push(_quantileSeq(dataArr, probOrNArr[i], sorted))
}
return probArr
}

/**
Expand All @@ -188,80 +132,35 @@ export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({
throw new Error('Cannot calculate quantile of an empty sequence')
}

if (isNumber(prob)) {
const index = prob * (len - 1)
const fracPart = index % 1
if (fracPart === 0) {
const value = sorted ? flat[index] : partitionSelect(flat, index)

validate(value)

return value
}

const integerPart = Math.floor(index)

let left
let right
if (sorted) {
left = flat[integerPart]
right = flat[integerPart + 1]
} else {
right = partitionSelect(flat, integerPart + 1)

// max of partition is kth largest
left = flat[integerPart]
for (let i = 0; i < integerPart; ++i) {
if (compare(flat[i], left) > 0) {
left = flat[i]
}
}
}

validate(left)
validate(right)

// Q(prob) = (1-f)*A[floor(index)] + f*A[floor(index)+1]
return add(multiply(left, 1 - fracPart), multiply(right, fracPart))
}

// If prob is a BigNumber
let index = prob.times(len - 1)
if (index.isInteger()) {
index = index.toNumber()
const value = sorted ? flat[index] : partitionSelect(flat, index)

validate(value)

return value
const index = isNumber(prob) ? prob * (len - 1) : prob.times(len - 1)
const integerPart = isNumber(prob) ? Math.floor(index) : index.floor().toNumber()
const fracPart = isNumber(prob) ? index % 1 : index.minus(integerPart)

if (isInteger(index)) {
return sorted
? flat[index]
: partitionSelect(
flat,
isNumber(prob) ? index : index.valueOf()
)
}

const integerPart = index.floor()
const fracPart = index.minus(integerPart)
const integerPartNumber = integerPart.toNumber()

let left
let right
if (sorted) {
left = flat[integerPartNumber]
right = flat[integerPartNumber + 1]
left = flat[integerPart]
right = flat[integerPart + 1]
} else {
right = partitionSelect(flat, integerPartNumber + 1)
right = partitionSelect(flat, integerPart + 1)

// max of partition is kth largest
left = flat[integerPartNumber]
for (let i = 0; i < integerPartNumber; ++i) {
left = flat[integerPart]
for (let i = 0; i < integerPart; ++i) {
if (compare(flat[i], left) > 0) {
left = flat[i]
}
}
}

validate(left)
validate(right)

// Q(prob) = (1-f)*A[floor(index)] + f*A[floor(index)+1]
const one = new fracPart.constructor(1)
return add(multiply(left, one.minus(fracPart)), multiply(right, fracPart))
return add(multiply(left, subtract(1, fracPart)), multiply(right, fracPart))
}
})