Skip to content

Commit

Permalink
feat: #3041, #3340 rename apply to mapSlices (#3357)
Browse files Browse the repository at this point in the history
* chore: Rename `apply` to `mapSlices`

  This renaming conforms with the Julia name for the function formerly
  called `apply`, and allows it to be called from the expression parser.
  The previous name `apply` is kept as an alias for `mapSlices`, for
  backward compatibility. This commit implements an `alias` metadata
  property for function factories to facilitate the `apply` alias for
  `mapSlices`.

  As a separate bonus, this PR corrects several typos in function docs
  and removes now-passing doc tests from the list of "known failing" doc
  tests to get down to 45 known failures and 136 total issues in doc tests.
  (Most of the excess of 136 as compared to 45 are just due to roundoff
  error/slight inaccuracy of what the documentation claims the result will
  be and the actual result returned by mathjs. When the 45 are eliminated,
  a reasonable numeric tolerance can be decided on for doc testing and
  then the doc tests can be made binding rather than advisory.

* refactor: changes per PR review

---------

Co-authored-by: Jos de Jong <[email protected]>
  • Loading branch information
gwhitney and josdejong authored Jan 30, 2025
1 parent 1a85b87 commit 127ea62
Show file tree
Hide file tree
Showing 29 changed files with 180 additions and 112 deletions.
5 changes: 5 additions & 0 deletions docs/core/extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ where:
created again when there is a change in the configuration. This is for
example used for the constants like `pi`, which is different depending
on the configsetting `number` which can be numbers or BigNumbers.
- `formerly: string`. If present, the created function will also be
accessible on the instance under the name given by the value of
`formerly` as a (deprecated) synonym for the specified `name`. This
facility should only be used when a function is renamed, to allow
temporary use of the previous name, for backward compatibility.

Here an example of a factory function which depends on `multiply`:

Expand Down
19 changes: 15 additions & 4 deletions src/core/function/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,27 +294,38 @@ export function importFactory (typed, load, math, importedFactories) {
}
}

const former = factory.meta?.formerly ?? ''
const needsTransform = isTransformFunctionFactory(factory) ||
factoryAllowedInExpressions(factory)
const withTransform = math.expression.mathWithTransform

// TODO: add unit test with non-lazy factory
if (!factory.meta || factory.meta.lazy !== false) {
lazy(namespace, name, resolver)
if (former) lazy(namespace, former, resolver)

// FIXME: remove the `if (existing &&` condition again. Can we make sure subset is loaded before subset.transform? (Name collision, and no dependencies between the two)
if (existing && existingTransform) {
_deleteTransform(name)
if (former) _deleteTransform(former)
} else {
if (isTransformFunctionFactory(factory) || factoryAllowedInExpressions(factory)) {
lazy(math.expression.mathWithTransform, name, () => namespace[name])
if (needsTransform) {
lazy(withTransform, name, () => namespace[name])
if (former) lazy(withTransform, former, () => namespace[name])
}
}
} else {
namespace[name] = resolver()
if (former) namespace[former] = namespace[name]

// FIXME: remove the `if (existing &&` condition again. Can we make sure subset is loaded before subset.transform? (Name collision, and no dependencies between the two)
if (existing && existingTransform) {
_deleteTransform(name)
if (former) _deleteTransform(former)
} else {
if (isTransformFunctionFactory(factory) || factoryAllowedInExpressions(factory)) {
lazy(math.expression.mathWithTransform, name, () => namespace[name])
if (needsTransform) {
lazy(withTransform, name, () => namespace[name])
if (former) lazy(withTransform, former, () => namespace[name])
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/expression/embeddedDocs/embeddedDocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import { andDocs } from './function/logical/and.js'
import { notDocs } from './function/logical/not.js'
import { orDocs } from './function/logical/or.js'
import { xorDocs } from './function/logical/xor.js'
import { mapSlicesDocs } from './function/matrix/mapSlices.js'
import { columnDocs } from './function/matrix/column.js'
import { concatDocs } from './function/matrix/concat.js'
import { countDocs } from './function/matrix/count.js'
Expand Down Expand Up @@ -440,6 +441,7 @@ export const embeddedDocs = {
xor: xorDocs,

// functions - matrix
mapSlices: mapSlicesDocs,
concat: concatDocs,
count: countDocs,
cross: crossDocs,
Expand Down
14 changes: 14 additions & 0 deletions src/expression/embeddedDocs/function/matrix/mapSlices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const mapSlicesDocs = {
name: 'mapSlices',
category: 'Matrix',
syntax: ['mapSlices(A, dim, callback)'],
description:
'Generate a matrix one dimension less than A by applying callback to ' +
'each slice of A along dimension dim.',
examples: [
'A = [[1, 2], [3, 4]]',
'mapSlices(A, 1, sum)', // returns [4, 6]
'mapSlices(A, 2, product)' // returns [2, 12]
],
seealso: ['map', 'forEach']
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { errorTransform } from './utils/errorTransform.js'
import { factory } from '../../utils/factory.js'
import { createApply } from '../../function/matrix/apply.js'
import { createMapSlices } from '../../function/matrix/mapSlices.js'
import { isBigNumber, isNumber } from '../../utils/is.js'

const name = 'apply'
const name = 'mapSlices'
const dependencies = ['typed', 'isInteger']

/**
* Attach a transform function to math.apply
* Attach a transform function to math.mapSlices
* Adds a property transform containing the transform function.
*
* This transform changed the last `dim` parameter of function apply
* This transform changed the last `dim` parameter of function mapSlices
* from one-based to zero based
*/
export const createApplyTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, isInteger }) => {
const apply = createApply({ typed, isInteger })
export const createMapSlicesTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, isInteger }) => {
const mapSlices = createMapSlices({ typed, isInteger })

// @see: comment of concat itself
return typed('apply', {
return typed('mapSlices', {
'...any': function (args) {
// change dim from one-based to zero-based
const dim = args[1]
Expand All @@ -29,10 +29,10 @@ export const createApplyTransform = /* #__PURE__ */ factory(name, dependencies,
}

try {
return apply.apply(null, args)
return mapSlices.apply(null, args)
} catch (err) {
throw errorTransform(err)
}
}
})
}, { isTransformFunction: true })
}, { isTransformFunction: true, ...createMapSlices.meta })
6 changes: 3 additions & 3 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', 'bignumber', 'add', 'subtract', 'divide', 'multiply', 'partitionSelect', 'compare', 'isInteger', 'smaller', 'smallerEq', 'larger']
const dependencies = ['typed', 'bignumber', 'add', 'subtract', 'divide', 'multiply', 'partitionSelect', 'compare', 'isInteger', 'smaller', 'smallerEq', 'larger', 'mapSlices']

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

return typed('quantileSeq', {
'Array | Matrix, number | BigNumber': quantileSeq,
Expand Down
6 changes: 3 additions & 3 deletions src/expression/transform/variance.transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createVariance } from '../../function/statistics/variance.js'
import { lastDimToZeroBase } from './utils/lastDimToZeroBase.js'

const name = 'variance'
const dependencies = ['typed', 'add', 'subtract', 'multiply', 'divide', 'apply', 'isNaN']
const dependencies = ['typed', 'add', 'subtract', 'multiply', 'divide', 'mapSlices', 'isNaN']

/**
* Attach a transform function to math.var
Expand All @@ -13,8 +13,8 @@ const dependencies = ['typed', 'add', 'subtract', 'multiply', 'divide', 'apply',
* This transform changed the `dim` parameter of function var
* from one-based to zero based
*/
export const createVarianceTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, subtract, multiply, divide, apply, isNaN }) => {
const variance = createVariance({ typed, add, subtract, multiply, divide, apply, isNaN })
export const createVarianceTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, subtract, multiply, divide, mapSlices, isNaN }) => {
const variance = createVariance({ typed, add, subtract, multiply, divide, mapSlices, isNaN })

return typed(name, {
'...any': function (args) {
Expand Down
4 changes: 2 additions & 2 deletions src/factoriesAny.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export { createSplitUnit } from './type/unit/function/splitUnit.js'
export { createUnaryMinus } from './function/arithmetic/unaryMinus.js'
export { createUnaryPlus } from './function/arithmetic/unaryPlus.js'
export { createAbs } from './function/arithmetic/abs.js'
export { createApply } from './function/matrix/apply.js'
export { createMapSlices } from './function/matrix/mapSlices.js'
export { createAddScalar } from './function/arithmetic/addScalar.js'
export { createSubtractScalar } from './function/arithmetic/subtractScalar.js'
export { createCbrt } from './function/arithmetic/cbrt.js'
Expand Down Expand Up @@ -340,7 +340,7 @@ export {
createWeakMixingAngle,
createWienDisplacement
} from './type/unit/physicalConstants.js'
export { createApplyTransform } from './expression/transform/apply.transform.js'
export { createMapSlicesTransform } from './expression/transform/mapSlices.transform.js'
export { createColumnTransform } from './expression/transform/column.transform.js'
export { createFilterTransform } from './expression/transform/filter.transform.js'
export { createForEachTransform } from './expression/transform/forEach.transform.js'
Expand Down
4 changes: 2 additions & 2 deletions src/factoriesNumber.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export const createOr = /* #__PURE__ */ createNumberFactory('or', orNumber)
export const createXor = /* #__PURE__ */ createNumberFactory('xor', xorNumber)

// matrix
export { createApply } from './function/matrix/apply.js'
export { createMapSlices } from './function/matrix/mapSlices.js'
export { createFilter } from './function/matrix/filter.js'
export { createForEach } from './function/matrix/forEach.js'
export { createMap } from './function/matrix/map.js'
Expand Down Expand Up @@ -299,7 +299,7 @@ export const createTan = /* #__PURE__ */ createNumberFactory('tan', tanNumber)
export const createTanh = /* #__PURE__ */ createNumberFactory('tanh', tanhNumber)

// transforms
export { createApplyTransform } from './expression/transform/apply.transform.js'
export { createMapSlicesTransform } from './expression/transform/mapSlices.transform.js'
export { createFilterTransform } from './expression/transform/filter.transform.js'
export { createForEachTransform } from './expression/transform/forEach.transform.js'
export { createMapTransform } from './expression/transform/map.transform.js'
Expand Down
2 changes: 1 addition & 1 deletion src/function/matrix/ctranspose.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const createCtranspose = /* #__PURE__ */ factory(name, dependencies, ({ t
* Examples:
*
* const A = [[1, 2, 3], [4, 5, math.complex(6,7)]]
* math.ctranspose(A) // returns [[1, 4], [2, 5], [3, {re:6,im:7}]]
* math.ctranspose(A) // returns [[1, 4], [2, 5], [3, {re:6,im:-7}]]
*
* See also:
*
Expand Down
29 changes: 17 additions & 12 deletions src/function/matrix/apply.js → src/function/matrix/mapSlices.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import { arraySize } from '../../utils/array.js'
import { isMatrix } from '../../utils/is.js'
import { IndexError } from '../../error/IndexError.js'

const name = 'apply'
const name = 'mapSlices'
const dependencies = ['typed', 'isInteger']

export const createApply = /* #__PURE__ */ factory(name, dependencies, ({ typed, isInteger }) => {
export const createMapSlices = /* #__PURE__ */ factory(name, dependencies, ({ typed, isInteger }) => {
/**
* Apply a function that maps an array to a scalar
* along a given axis of a matrix or array.
* Returns a new matrix or array with one less dimension than the input.
*
* Syntax:
*
* math.apply(A, dim, callback)
* math.mapSlices(A, dim, callback)
*
* Where:
*
Expand All @@ -25,19 +25,24 @@ export const createApply = /* #__PURE__ */ factory(name, dependencies, ({ typed,
* const A = [[1, 2], [3, 4]]
* const sum = math.sum
*
* math.apply(A, 0, sum) // returns [4, 6]
* math.apply(A, 1, sum) // returns [3, 7]
* math.mapSlices(A, 0, sum) // returns [4, 6]
* math.mapSlices(A, 1, sum) // returns [3, 7]
*
* See also:
*
* map, filter, forEach
*
* Note:
*
* `mapSlices()` is also currently available via its deprecated
* synonym `apply()`.
*
* @param {Array | Matrix} array The input Matrix
* @param {number} dim The dimension along which the callback is applied
* @param {Function} callback The callback function that is applied. This Function
* should take an array or 1-d matrix as an input and
* return a number.
* @return {Array | Matrix} res The residual matrix with the function applied over some dimension.
* @return {Array | Matrix} res The residual matrix with the function mapped on the slices over some dimension.
*/
return typed(name, {
'Array | Matrix, number | BigNumber, function': function (mat, dim, callback) {
Expand All @@ -51,13 +56,13 @@ export const createApply = /* #__PURE__ */ factory(name, dependencies, ({ typed,
}

if (isMatrix(mat)) {
return mat.create(_apply(mat.valueOf(), dim, callback), mat.datatype())
return mat.create(_mapSlices(mat.valueOf(), dim, callback), mat.datatype())
} else {
return _apply(mat, dim, callback)
return _mapSlices(mat, dim, callback)
}
}
})
})
}, { formerly: 'apply' })

/**
* Recursively reduce a matrix
Expand All @@ -67,7 +72,7 @@ export const createApply = /* #__PURE__ */ factory(name, dependencies, ({ typed,
* @returns {Array} ret
* @private
*/
function _apply (mat, dim, callback) {
function _mapSlices (mat, dim, callback) {
let i, ret, tran

if (dim <= 0) {
Expand All @@ -77,14 +82,14 @@ function _apply (mat, dim, callback) {
tran = _switch(mat)
ret = []
for (i = 0; i < tran.length; i++) {
ret[i] = _apply(tran[i], dim - 1, callback)
ret[i] = _mapSlices(tran[i], dim - 1, callback)
}
return ret
}
} else {
ret = []
for (i = 0; i < mat.length; i++) {
ret[i] = _apply(mat[i], dim - 1, callback)
ret[i] = _mapSlices(mat[i], dim - 1, callback)
}
return ret
}
Expand Down
9 changes: 3 additions & 6 deletions src/function/statistics/quantileSeq.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
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', '?bignumber', 'add', 'subtract', 'divide', 'multiply', 'partitionSelect', 'compare', 'isInteger', 'smaller', 'smallerEq', 'larger']

export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({ typed, bignumber, add, subtract, divide, multiply, partitionSelect, compare, isInteger, smaller, smallerEq, larger }) => {
const apply = createApply({ typed, isInteger })
const dependencies = ['typed', '?bignumber', 'add', 'subtract', 'divide', 'multiply', 'partitionSelect', 'compare', 'isInteger', 'smaller', 'smallerEq', 'larger', 'mapSlices']

export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({ typed, bignumber, add, subtract, divide, multiply, partitionSelect, compare, isInteger, smaller, smallerEq, larger, mapSlices }) => {
/**
* 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 @@ -55,7 +52,7 @@ export const createQuantileSeq = /* #__PURE__ */ factory(name, dependencies, ({
})

function _quantileSeqDim (data, prob, sorted, dim, fn) {
return apply(data, dim, x => fn(x, prob, sorted))
return mapSlices(data, dim, x => fn(x, prob, sorted))
}

function _quantileSeqProbNumber (data, probOrN, sorted) {
Expand Down
6 changes: 3 additions & 3 deletions src/function/statistics/variance.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { improveErrorMessage } from './utils/improveErrorMessage.js'
const DEFAULT_NORMALIZATION = 'unbiased'

const name = 'variance'
const dependencies = ['typed', 'add', 'subtract', 'multiply', 'divide', 'apply', 'isNaN']
const dependencies = ['typed', 'add', 'subtract', 'multiply', 'divide', 'mapSlices', 'isNaN']

export const createVariance = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, subtract, multiply, divide, apply, isNaN }) => {
export const createVariance = /* #__PURE__ */ factory(name, dependencies, ({ typed, add, subtract, multiply, divide, mapSlices, isNaN }) => {
/**
* Compute the variance of a matrix or a list with values.
* In case of a multidimensional array or matrix, the variance over all
Expand Down Expand Up @@ -152,7 +152,7 @@ export const createVariance = /* #__PURE__ */ factory(name, dependencies, ({ typ
if (array.length === 0) {
throw new SyntaxError('Function variance requires one or more parameters (0 provided)')
}
return apply(array, dim, (x) => _var(x, normalization))
return mapSlices(array, dim, (x) => _var(x, normalization))
} catch (err) {
throw improveErrorMessage(err, 'variance')
}
Expand Down
2 changes: 1 addition & 1 deletion src/function/string/hex.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const dependencies = ['typed', 'format']
*
* Examples:
*
* math.hex(240) // returns "0xF0"
* math.hex(240) // returns "0xf0"
*
* See also:
*
Expand Down
2 changes: 1 addition & 1 deletion src/function/trigonometry/acoth.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const createAcoth = /* #__PURE__ */ factory(name, dependencies, ({ typed,
*
* Examples:
*
* math.acoth(0.5) // returns 0.8047189562170503
* math.acoth(0.5) // returns 0.5493061443340548 - 1.5707963267948966i
*
* See also:
*
Expand Down
2 changes: 1 addition & 1 deletion src/function/trigonometry/acsc.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const createAcsc = /* #__PURE__ */ factory(name, dependencies, ({ typed,
*
* math.acsc(2) // returns 0.5235987755982989
* math.acsc(0.5) // returns Complex 1.5707963267948966 -1.3169578969248166i
* math.acsc(math.csc(1.5)) // returns number ~1.5
* math.acsc(math.csc(1.5)) // returns number 1.5
*
* See also:
*
Expand Down
2 changes: 1 addition & 1 deletion src/function/trigonometry/asin.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const createAsin = /* #__PURE__ */ factory(name, dependencies, ({ typed,
* Examples:
*
* math.asin(0.5) // returns number 0.5235987755982989
* math.asin(math.sin(1.5)) // returns number ~1.5
* math.asin(math.sin(1.5)) // returns number 1.5
*
* math.asin(2) // returns Complex 1.5707963267948966 -1.3169578969248166i
*
Expand Down
Loading

0 comments on commit 127ea62

Please sign in to comment.