Skip to content

Commit

Permalink
chore: Prevent confusion with standard matrix functions. (#2465)
Browse files Browse the repository at this point in the history
* chore: Prevent consfusion with standard matrix functions.

  Prior to this commit, many functions operated elementwise on matrices
  even though in standard mathematical usage they have a different
  meaning on square matrices. Since the elementwise operation is easily
  recoverable using `math.map`, this commit removes the elementwise
  operation on arrays and matrices from these functions.
  Affected functions include all trigonometric functions, exp, log, gamma,
  square, sqrt, cube, and cbrt.
  Resolves #2440.

* chore(typescript): Revise usages in light of changes

  sqrt() is now correctly typed as `number | Complex` and so must
  be explicitly cast to number when called on a positive and used
  where a Complex is disallowed; sqrt() no longer applies to matrices
  at all.

* feat: Provide better error messages for v10 -> v11 transition

  Uses new `typed.onMismatch` handler so that matrix calls that used to
  work will suggest a replacement.
  • Loading branch information
gwhitney committed May 30, 2022
1 parent 433f576 commit ef4c494
Show file tree
Hide file tree
Showing 76 changed files with 441 additions and 535 deletions.
20 changes: 20 additions & 0 deletions src/core/function/typed.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
isBlockNode,
isBoolean,
isChain,
isCollection,
isComplex,
isConditionalNode,
isConstantNode,
Expand Down Expand Up @@ -332,6 +333,25 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi
}
]

// Provide a suggestion on how to call a function elementwise
// This was added primarily as guidance for the v10 -> v11 transition,
// and could potentially be removed in the future if it no longer seems
// to be helpful.
typed.onMismatch = (name, args, signatures) => {
const usualError = typed.createError(name, args, signatures)
if (['wrongType', 'mismatch'].includes(usualError.data.category) &&
args.length === 1 && isCollection(args[0]) &&
// check if the function can be unary:
signatures.some(sig => !sig.params.includes(','))) {
const err = new TypeError(
`Function '${name}' doesn't apply to matrices. To call it ` +
`elementwise on a matrix 'M', try 'map(M, ${name})'.`)
err.data = usualError.data
throw err
}
throw usualError
}

return typed
})

Expand Down
6 changes: 3 additions & 3 deletions src/expression/transform/std.transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { errorTransform } from './utils/errorTransform.js'
import { lastDimToZeroBase } from './utils/lastDimToZeroBase.js'

const name = 'std'
const dependencies = ['typed', 'sqrt', 'variance']
const dependencies = ['typed', 'map', 'sqrt', 'variance']

/**
* Attach a transform function to math.std
Expand All @@ -13,8 +13,8 @@ const dependencies = ['typed', 'sqrt', 'variance']
* This transform changed the `dim` parameter of function std
* from one-based to zero based
*/
export const createStdTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, sqrt, variance }) => {
const std = createStd({ typed, sqrt, variance })
export const createStdTransform = /* #__PURE__ */ factory(name, dependencies, ({ typed, map, sqrt, variance }) => {
const std = createStd({ typed, map, sqrt, variance })

return typed('std', {
'...any': function (args) {
Expand Down
18 changes: 7 additions & 11 deletions src/function/arithmetic/cbrt.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { factory } from '../../utils/factory.js'
import { isBigNumber, isComplex, isFraction } from '../../utils/is.js'
import { deepMap } from '../../utils/collection.js'
import { cbrtNumber } from '../../plain/number/index.js'

const name = 'cbrt'
Expand All @@ -19,7 +18,9 @@ export const createCbrt = /* #__PURE__ */ factory(name, dependencies, ({ config,
/**
* Calculate the cubic root of a value.
*
* For matrices, the function is evaluated element wise.
* To avoid confusion with the matrix cube root, this function does not
* apply to matrices. For a matrix, to take the cube root elementwise,
* see the examples.
*
* Syntax:
*
Expand All @@ -32,7 +33,7 @@ export const createCbrt = /* #__PURE__ */ factory(name, dependencies, ({ config,
* math.cube(3) // returns 27
* math.cbrt(-64) // returns -4
* math.cbrt(math.unit('27 m^3')) // returns Unit 3 m
* math.cbrt([27, 64, 125]) // returns [3, 4, 5]
* math.map([27, 64, 125], x => math.cbrt(x)) // returns [3, 4, 5]
*
* const x = math.complex('8i')
* math.cbrt(x) // returns Complex 1.7320508075689 + i
Expand All @@ -46,13 +47,13 @@ export const createCbrt = /* #__PURE__ */ factory(name, dependencies, ({ config,
*
* square, sqrt, cube
*
* @param {number | BigNumber | Complex | Unit | Array | Matrix} x
* @param {number | BigNumber | Complex | Unit} x
* Value for which to calculate the cubic root.
* @param {boolean} [allRoots] Optional, false by default. Only applicable
* when `x` is a number or complex number. If true, all complex
* roots are returned, if false (default) the principal root is
* returned.
* @return {number | BigNumber | Complex | Unit | Array | Matrix}
* @return {number | BigNumber | Complex | Unit}
* Returns the cubic root of `x`
*/
return typed(name, {
Expand All @@ -68,12 +69,7 @@ export const createCbrt = /* #__PURE__ */ factory(name, dependencies, ({ config,
return x.cbrt()
},

Unit: _cbrtUnit,

'Array | Matrix': function (x) {
// deep map collection, skip zeros since cbrt(0) = 0
return deepMap(x, this, true)
}
Unit: _cbrtUnit
})

/**
Expand Down
15 changes: 5 additions & 10 deletions src/function/arithmetic/cube.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { factory } from '../../utils/factory.js'
import { deepMap } from '../../utils/collection.js'
import { cubeNumber } from '../../plain/number/index.js'

const name = 'cube'
Expand All @@ -8,7 +7,8 @@ const dependencies = ['typed']
export const createCube = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
/**
* Compute the cube of a value, `x * x * x`.
* For matrices, the function is evaluated element wise.
* To avoid confusion with `pow(M,3)`, this function does not apply to matrices.
* If you wish to cube every entry of a matrix, see the examples.
*
* Syntax:
*
Expand All @@ -21,14 +21,14 @@ export const createCube = /* #__PURE__ */ factory(name, dependencies, ({ typed }
* math.cube(4) // returns number 64
* 4 * 4 * 4 // returns number 64
*
* math.cube([1, 2, 3, 4]) // returns Array [1, 8, 27, 64]
* math.map([1, 2, 3, 4], math.cube) // returns Array [1, 8, 27, 64]
*
* See also:
*
* multiply, square, pow, cbrt
*
* @param {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} x Number for which to calculate the cube
* @return {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} Cube of x
* @param {number | BigNumber | Fraction | Complex | Unit} x Number for which to calculate the cube
* @return {number | BigNumber | Fraction | Complex | Unit} Cube of x
*/
return typed(name, {
number: cubeNumber,
Expand All @@ -45,11 +45,6 @@ export const createCube = /* #__PURE__ */ factory(name, dependencies, ({ typed }
return x.pow(3) // Is faster than mul()mul()mul()
},

'Array | Matrix': function (x) {
// deep map collection, skip zeros since cube(0) = 0
return deepMap(x, this, true)
},

Unit: function (x) {
return x.pow(3)
}
Expand Down
20 changes: 8 additions & 12 deletions src/function/arithmetic/exp.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { factory } from '../../utils/factory.js'
import { deepMap } from '../../utils/collection.js'
import { expNumber } from '../../plain/number/index.js'

const name = 'exp'
const dependencies = ['typed']

export const createExp = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
/**
* Calculate the exponent of a value.
* For matrices, the function is evaluated element wise.
* Calculate the exponential of a value.
* For matrices, if you want the matrix exponential of square matrix, use
* the `expm` function; if you want to take the exponential of each element,
* see the examples.
*
* Syntax:
*
Expand All @@ -20,7 +21,7 @@ export const createExp = /* #__PURE__ */ factory(name, dependencies, ({ typed })
* math.pow(math.e, 2) // returns number 7.3890560989306495
* math.log(math.exp(2)) // returns number 2
*
* math.exp([1, 2, 3])
* math.map([1, 2, 3], math.exp)
* // returns Array [
* // 2.718281828459045,
* // 7.3890560989306495,
Expand All @@ -29,10 +30,10 @@ export const createExp = /* #__PURE__ */ factory(name, dependencies, ({ typed })
*
* See also:
*
* expm1, log, pow
* expm1, expm, log, pow
*
* @param {number | BigNumber | Complex | Array | Matrix} x A number or matrix to exponentiate
* @return {number | BigNumber | Complex | Array | Matrix} Exponent of `x`
* @param {number | BigNumber | Complex} x A number to exponentiate
* @return {number | BigNumber | Complex} Exponential of `x`
*/
return typed(name, {
number: expNumber,
Expand All @@ -43,11 +44,6 @@ export const createExp = /* #__PURE__ */ factory(name, dependencies, ({ typed })

BigNumber: function (x) {
return x.exp()
},

'Array | Matrix': function (x) {
// TODO: exp(sparse) should return a dense matrix since exp(0)==1
return deepMap(x, this)
}
})
})
20 changes: 10 additions & 10 deletions src/function/arithmetic/expm1.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { factory } from '../../utils/factory.js'
import { deepMap } from '../../utils/collection.js'
import { expm1Number } from '../../plain/number/index.js'

const name = 'expm1'
Expand All @@ -8,7 +7,10 @@ const dependencies = ['typed', 'Complex']
export const createExpm1 = /* #__PURE__ */ factory(name, dependencies, ({ typed, Complex }) => {
/**
* Calculate the value of subtracting 1 from the exponential value.
* For matrices, the function is evaluated element wise.
* This function is more accurate than `math.exp(x)-1` when `x` is near 0
* To avoid ambiguity with the matrix exponential `expm`, this function
* does not operate on matrices; if you wish to apply it elementwise, see
* the examples.
*
* Syntax:
*
Expand All @@ -18,9 +20,11 @@ export const createExpm1 = /* #__PURE__ */ factory(name, dependencies, ({ typed,
*
* math.expm1(2) // returns number 6.38905609893065
* math.pow(math.e, 2) - 1 // returns number 6.3890560989306495
* math.expm1(1e-8) // returns number 1.0000000050000001e-8
* math.exp(1e-8) - 1 // returns number 9.9999999392253e-9
* math.log(math.expm1(2) + 1) // returns number 2
*
* math.expm1([1, 2, 3])
* math.map([1, 2, 3], math.expm1)
* // returns Array [
* // 1.718281828459045,
* // 6.3890560989306495,
Expand All @@ -29,10 +33,10 @@ export const createExpm1 = /* #__PURE__ */ factory(name, dependencies, ({ typed,
*
* See also:
*
* exp, log, pow
* exp, expm, log, pow
*
* @param {number | BigNumber | Complex | Array | Matrix} x A number or matrix to apply expm1
* @return {number | BigNumber | Complex | Array | Matrix} Exponent of `x`
* @param {number | BigNumber | Complex} x A number or matrix to apply expm1
* @return {number | BigNumber | Complex} Exponential of `x`, minus one
*/
return typed(name, {
number: expm1Number,
Expand All @@ -47,10 +51,6 @@ export const createExpm1 = /* #__PURE__ */ factory(name, dependencies, ({ typed,

BigNumber: function (x) {
return x.exp().minus(1)
},

'Array | Matrix': function (x) {
return deepMap(x, this)
}
})
})
12 changes: 4 additions & 8 deletions src/function/arithmetic/log.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { factory } from '../../utils/factory.js'
import { deepMap } from '../../utils/collection.js'
import { logNumber } from '../../plain/number/index.js'

const name = 'log'
Expand All @@ -9,7 +8,8 @@ export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
/**
* Calculate the logarithm of a value.
*
* For matrices, the function is evaluated element wise.
* To avoid confusion with the matrix logarithm, this function does not
* apply to matrices.
*
* Syntax:
*
Expand All @@ -32,12 +32,12 @@ export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
*
* exp, log2, log10, log1p
*
* @param {number | BigNumber | Complex | Array | Matrix} x
* @param {number | BigNumber | Complex} x
* Value for which to calculate the logarithm.
* @param {number | BigNumber | Complex} [base=e]
* Optional base for the logarithm. If not provided, the natural
* logarithm of `x` is calculated.
* @return {number | BigNumber | Complex | Array | Matrix}
* @return {number | BigNumber | Complex}
* Returns the logarithm of `x`
*/
return typed(name, {
Expand All @@ -63,10 +63,6 @@ export const createLog = /* #__PURE__ */ factory(name, dependencies, ({ typed, c
}
},

'Array | Matrix': function (x) {
return deepMap(x, this)
},

'any, any': function (x, base) {
// calculate logarithm for a specified base, log(x, base)
return divideScalar(this(x), this(base))
Expand Down
14 changes: 5 additions & 9 deletions src/function/arithmetic/sqrt.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { factory } from '../../utils/factory.js'
import { deepMap } from '../../utils/collection.js'

const name = 'sqrt'
const dependencies = ['config', 'typed', 'Complex']
Expand All @@ -8,7 +7,9 @@ export const createSqrt = /* #__PURE__ */ factory(name, dependencies, ({ config,
/**
* Calculate the square root of a value.
*
* For matrices, the function is evaluated element wise.
* For matrices, if you want the matrix square root of a square matrix,
* use the `sqrtm` function. If you wish to apply `sqrt` elementwise to
* a matrix M, use `math.map(M, math.sqrt)`.
*
* Syntax:
*
Expand All @@ -24,9 +25,9 @@ export const createSqrt = /* #__PURE__ */ factory(name, dependencies, ({ config,
*
* square, multiply, cube, cbrt, sqrtm
*
* @param {number | BigNumber | Complex | Array | Matrix | Unit} x
* @param {number | BigNumber | Complex | Unit} x
* Value for which to calculate the square root.
* @return {number | BigNumber | Complex | Array | Matrix | Unit}
* @return {number | BigNumber | Complex | Unit}
* Returns the square root of `x`
*/
return typed('sqrt', {
Expand All @@ -45,11 +46,6 @@ export const createSqrt = /* #__PURE__ */ factory(name, dependencies, ({ config,
}
},

'Array | Matrix': function (x) {
// deep map collection, skip zeros since sqrt(0) = 0
return deepMap(x, this, true)
},

Unit: function (x) {
// Someday will work for complex units when they are implemented
return x.pow(0.5)
Expand Down
16 changes: 6 additions & 10 deletions src/function/arithmetic/square.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { factory } from '../../utils/factory.js'
import { deepMap } from '../../utils/collection.js'
import { squareNumber } from '../../plain/number/index.js'

const name = 'square'
Expand All @@ -8,7 +7,9 @@ const dependencies = ['typed']
export const createSquare = /* #__PURE__ */ factory(name, dependencies, ({ typed }) => {
/**
* Compute the square of a value, `x * x`.
* For matrices, the function is evaluated element wise.
* To avoid confusion with multiplying a square matrix by itself,
* this function does not apply to matrices. If you wish to square
* every element of a matrix, see the examples.
*
* Syntax:
*
Expand All @@ -21,15 +22,15 @@ export const createSquare = /* #__PURE__ */ factory(name, dependencies, ({ typed
* math.pow(3, 2) // returns number 9
* math.multiply(3, 3) // returns number 9
*
* math.square([1, 2, 3, 4]) // returns Array [1, 4, 9, 16]
* math.map([1, 2, 3, 4], math.square) // returns Array [1, 4, 9, 16]
*
* See also:
*
* multiply, cube, sqrt, pow
*
* @param {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} x
* @param {number | BigNumber | Fraction | Complex | Unit} x
* Number for which to calculate the square
* @return {number | BigNumber | Fraction | Complex | Array | Matrix | Unit}
* @return {number | BigNumber | Fraction | Complex | Unit}
* Squared value
*/
return typed(name, {
Expand All @@ -47,11 +48,6 @@ export const createSquare = /* #__PURE__ */ factory(name, dependencies, ({ typed
return x.mul(x)
},

'Array | Matrix': function (x) {
// deep map collection, skip zeros since square(0) = 0
return deepMap(x, this, true)
},

Unit: function (x) {
return x.pow(2)
}
Expand Down
Loading

0 comments on commit ef4c494

Please sign in to comment.