Merge branch 'develop' into feat/migrate-to-webpack-5

This commit is contained in:
Jos de Jong 2022-02-28 11:57:36 +01:00 committed by GitHub
commit 211aceecb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 445 additions and 256 deletions

View File

@ -10,6 +10,8 @@
errors for larger values, see #2414. Thanks @gwhitney.
- Fix #2385: function `rotate` missing in TypeScript definitions.
Thanks @DIVYA-19.
- Fix #2450: Add BigNumber to parameter type in `math.unit` and add TypeScript
types for `Unit.simplify` and `Unit.units` (#2353). Thanks @joshhansen.
# 2022-02-02, version 10.1.1

View File

@ -76,6 +76,9 @@ export const createHelpClass = /* #__PURE__ */ factory(name, dependencies, ({ pa
}
desc += '\n'
}
if (doc.mayThrow && doc.mayThrow.length) {
desc += 'Throws: ' + doc.mayThrow.join(', ') + '\n\n'
}
if (doc.seealso && doc.seealso.length) {
desc += 'See also: ' + doc.seealso.join(', ') + '\n'
}

View File

@ -178,6 +178,8 @@ import { sluDocs } from './function/algebra/slu.js'
import { leafCountDocs } from './function/algebra/leafCount.js'
import { rationalizeDocs } from './function/algebra/rationalize.js'
import { simplifyDocs } from './function/algebra/simplify.js'
import { simplifyCoreDocs } from './function/algebra/simplifyCore.js'
import { resolveDocs } from './function/algebra/resolve.js'
import { lupDocs } from './function/algebra/lup.js'
import { lsolveDocs } from './function/algebra/lsolve.js'
import { lsolveAllDocs } from './function/algebra/lsolveAll.js'
@ -329,6 +331,8 @@ export const embeddedDocs = {
lusolve: lusolveDocs,
leafCount: leafCountDocs,
simplify: simplifyDocs,
resolve: resolveDocs,
simplifyCore: simplifyCoreDocs,
rationalize: rationalizeDocs,
slu: sluDocs,
usolve: usolveDocs,

View File

@ -0,0 +1,20 @@
export const resolveDocs = {
name: 'resolve',
category: 'Algebra',
syntax: [
'resolve(node, scope)'
],
description: 'Recursively substitute variables in an expression tree.',
examples: [
'resolve(parse("1 + x"), { x: 7 })',
'resolve(parse("size(text)"), { text: "Hello World" })',
'resolve(parse("x + y"), { x: parse("3z") })',
'resolve(parse("3x"), { x: parse("y+z"), z: parse("w^y") })'
],
seealso: [
'simplify', 'evaluate'
],
mayThrow: [
'ReferenceError'
]
}

View File

@ -14,6 +14,6 @@ export const simplifyDocs = {
'simplified.evaluate({x: 2})'
],
seealso: [
'derivative', 'parse', 'evaluate'
'simplifyCore', 'derivative', 'evaluate', 'parse', 'rationalize', 'resolve'
]
}

View File

@ -0,0 +1,15 @@
export const simplifyCoreDocs = {
name: 'simplifyCore',
category: 'Algebra',
syntax: [
'simplifyCore(node)'
],
description: 'Perform simple one-pass simplifications on an expression tree.',
examples: [
'simplifyCore(parse("0*x"))',
'simplifyCore(parse("(x+0)*2"))'
],
seealso: [
'simplify', 'evaluate'
]
}

View File

@ -243,6 +243,8 @@ export { createCatalan } from './function/combinatorics/catalan.js'
export { createComposition } from './function/combinatorics/composition.js'
export { createLeafCount } from './function/algebra/leafCount.js'
export { createSimplify } from './function/algebra/simplify.js'
export { createSimplifyCore } from './function/algebra/simplifyCore.js'
export { createResolve } from './function/algebra/resolve.js'
export { createDerivative } from './function/algebra/derivative.js'
export { createRationalize } from './function/algebra/rationalize.js'
export { createReviver } from './json/reviver.js'

View File

@ -89,7 +89,9 @@ export { createHelp } from './expression/function/help.js'
export { createChain } from './type/chain/function/chain.js'
// algebra
export { createResolve } from './function/algebra/resolve.js'
export { createSimplify } from './function/algebra/simplify.js'
export { createSimplifyCore } from './function/algebra/simplifyCore.js'
export { createDerivative } from './function/algebra/derivative.js'
export { createRationalize } from './function/algebra/rationalize.js'

View File

@ -1,7 +1,6 @@
import { isInteger } from '../../utils/number.js'
import { factory } from '../../utils/factory.js'
import { createSimplifyConstant } from './simplify/simplifyConstant.js'
import { createSimplifyCore } from './simplify/simplifyCore.js'
const name = 'rationalize'
const dependencies = [
@ -15,6 +14,7 @@ const dependencies = [
'divide',
'pow',
'parse',
'simplifyCore',
'simplify',
'?bignumber',
'?fraction',
@ -42,6 +42,7 @@ export const createRationalize = /* #__PURE__ */ factory(name, dependencies, ({
divide,
pow,
parse,
simplifyCore,
simplify,
fraction,
bignumber,
@ -73,24 +74,6 @@ export const createRationalize = /* #__PURE__ */ factory(name, dependencies, ({
OperatorNode,
SymbolNode
})
const simplifyCore = createSimplifyCore({
equal,
isZero,
add,
subtract,
multiply,
divide,
pow,
AccessorNode,
ArrayNode,
ConstantNode,
FunctionNode,
IndexNode,
ObjectNode,
OperatorNode,
ParenthesisNode,
SymbolNode
})
/**
* Transform a rationalizable expression in a rational fraction.

View File

@ -0,0 +1,94 @@
import { createMap, isMap } from '../../utils/map.js'
import { isFunctionNode, isNode, isOperatorNode, isParenthesisNode, isSymbolNode } from '../../utils/is.js'
import { factory } from '../../utils/factory.js'
const name = 'resolve'
const dependencies = [
'parse',
'ConstantNode',
'FunctionNode',
'OperatorNode',
'ParenthesisNode'
]
export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({
parse,
ConstantNode,
FunctionNode,
OperatorNode,
ParenthesisNode
}) => {
/**
* resolve(expr, scope) replaces variable nodes with their scoped values
*
* Syntax:
*
* resolve(expr, scope)
*
* Examples:
*
* math.resolve('x + y', {x:1, y:2}) // Node {1 + 2}
* math.resolve(math.parse('x+y'), {x:1, y:2}) // Node {1 + 2}
* math.simplify('x+y', {x:2, y:'x+x'}).toString() // "6"
*
* See also:
*
* simplify, evaluate
*
* @param {Node} node
* The expression tree to be simplified
* @param {Object} scope
* Scope specifying variables to be resolved
* @return {Node} Returns `node` with variables recursively substituted.
* @throws {ReferenceError}
* If there is a cyclic dependency among the variables in `scope`,
* resolution is impossible and a ReferenceError is thrown.
*/
function resolve (node, scope, within = new Set()) { // note `within`:
// `within` is not documented, since it is for internal cycle
// detection only
if (!scope) {
return node
}
if (!isMap(scope)) {
scope = createMap(scope)
}
if (isSymbolNode(node)) {
if (within.has(node.name)) {
const variables = Array.from(within).join(', ')
throw new ReferenceError(
`recursive loop of variable definitions among {${variables}}`
)
}
const value = scope.get(node.name)
if (isNode(value)) {
const nextWithin = new Set(within)
nextWithin.add(node.name)
return resolve(value, scope, nextWithin)
} else if (typeof value === 'number') {
return parse(String(value))
} else if (value !== undefined) {
return new ConstantNode(value)
} else {
return node
}
} else if (isOperatorNode(node)) {
const args = node.args.map(function (arg) {
return resolve(arg, scope, within)
})
return new OperatorNode(node.op, node.fn, args, node.implicit)
} else if (isParenthesisNode(node)) {
return new ParenthesisNode(resolve(node.content, scope, within))
} else if (isFunctionNode(node)) {
const args = node.args.map(function (arg) {
return resolve(arg, scope, within)
})
return new FunctionNode(node.name, args)
}
// Otherwise just recursively resolve any children (might also work
// for some of the above special cases)
return node.map(child => resolve(child, scope, within))
}
return resolve
})

View File

@ -1,9 +1,7 @@
import { isConstantNode, isParenthesisNode } from '../../utils/is.js'
import { factory } from '../../utils/factory.js'
import { createUtil } from './simplify/util.js'
import { createSimplifyCore } from './simplify/simplifyCore.js'
import { createSimplifyConstant } from './simplify/simplifyConstant.js'
import { createResolve } from './simplify/resolve.js'
import { hasOwnProperty } from '../../utils/object.js'
import { createEmptyMap, createMap } from '../../utils/map.js'
@ -19,6 +17,8 @@ const dependencies = [
'pow',
'isZero',
'equal',
'resolve',
'simplifyCore',
'?fraction',
'?bignumber',
'mathWithTransform',
@ -46,6 +46,8 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
pow,
isZero,
equal,
resolve,
simplifyCore,
fraction,
bignumber,
mathWithTransform,
@ -77,30 +79,6 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
OperatorNode,
SymbolNode
})
const simplifyCore = createSimplifyCore({
equal,
isZero,
add,
subtract,
multiply,
divide,
pow,
AccessorNode,
ArrayNode,
ConstantNode,
FunctionNode,
IndexNode,
ObjectNode,
OperatorNode,
ParenthesisNode,
SymbolNode
})
const resolve = createResolve({
parse,
FunctionNode,
OperatorNode,
ParenthesisNode
})
const { hasProperty, isCommutative, isAssociative, mergeContext, flatten, unflattenr, unflattenl, createMakeNodeFunction, defaultContext, realContext, positiveContext } =
createUtil({ FunctionNode, OperatorNode, SymbolNode })
@ -200,7 +178,7 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
*
* See also:
*
* derivative, parse, evaluate, rationalize
* simplifyCore, derivative, evaluate, parse, rationalize, resolve
*
* @param {Node | string} expr
* The expression to be simplified
@ -298,8 +276,6 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
return res
}
})
simplify.simplifyCore = simplifyCore
simplify.resolve = resolve
simplify.defaultContext = defaultContext
simplify.realContext = realContext
simplify.positiveContext = positiveContext
@ -374,7 +350,7 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
assuming: { multiply: { commutative: false }, subtract: { total: true } }
},
{ l: '-(n1/n2)', r: '-n1/n2' },
{ l: '-v', r: 'v * (-1)' },
{ l: '-v', r: 'v * (-1)' }, // finish making non-constant terms positive
{ l: '(n1 + n2)*(-1)', r: 'n1*(-1) + n2*(-1)', repeat: true }, // expand negations to achieve as much sign cancellation as possible
{ l: 'n/n1^n2', r: 'n*n1^-n2' }, // temporarily replace 'divide' so we can further flatten the 'multiply' operator
{ l: 'n/n1', r: 'n*n1^-1' },
@ -387,15 +363,26 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
assuming: { multiply: { commutative: false } }
},
simplifyConstant,
// expand nested exponentiation
{
s: '(n ^ n1) ^ n2 -> n ^ (n1 * n2)',
assuming: { divide: { total: true } } // 1/(1/n) = n needs 1/n to exist
},
// collect like factors
// collect like factors; into a sum, only do this for nonconstants
{ l: ' v * ( v * n1 + n2)', r: 'v^2 * n1 + v * n2' },
{
s: ' v * (v^n4 * n1 + n2) -> v^(1+n4) * n1 + v * n2',
assuming: { divide: { total: true } } // v*1/v = v^(1+-1) needs 1/v
},
{
s: 'v^n3 * ( v * n1 + n2) -> v^(n3+1) * n1 + v^n3 * n2',
assuming: { divide: { total: true } }
},
{
s: 'v^n3 * (v^n4 * n1 + n2) -> v^(n3+n4) * n1 + v^n3 * n2',
assuming: { divide: { total: true } }
},
{ l: 'n*n', r: 'n^2' },
{
s: 'n * n^n1 -> n^(n1+1)',
@ -406,6 +393,12 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
assuming: { divide: { total: true } } // ditto for n^2*1/n^2
},
// Unfortunately, to deal with more complicated cancellations, it
// becomes necessary to simplify constants twice per pass. It's not
// terribly expensive compared to matching rules, so this should not
// pose a performance problem.
simplifyConstant, // First: before collecting like terms
// collect like terms
{
s: 'n+n -> 2*n',
@ -414,6 +407,8 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
{ l: 'n+-n', r: '0' },
{ l: 'v*n + v', r: 'v*(n+1)' }, // NOTE: leftmost position is special:
{ l: 'n3*n1 + n3*n2', r: 'n3*(n1+n2)' }, // All sub-monomials tried there.
{ l: 'n3^(-n4)*n1 + n3 * n2', r: 'n3^(-n4)*(n1 + n3^(n4+1) *n2)' },
{ l: 'n3^(-n4)*n1 + n3^n5 * n2', r: 'n3^(-n4)*(n1 + n3^(n4+n5)*n2)' },
{
s: 'n*v + v -> (n+1)*v', // noncommutative additional cases
assuming: { multiply: { commutative: false } }
@ -422,12 +417,22 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
s: 'n1*n3 + n2*n3 -> (n1+n2)*n3',
assuming: { multiply: { commutative: false } }
},
{
s: 'n1*n3^(-n4) + n2 * n3 -> (n1 + n2*n3^(n4 + 1))*n3^(-n4)',
assuming: { multiply: { commutative: false } }
},
{
s: 'n1*n3^(-n4) + n2 * n3^n5 -> (n1 + n2*n3^(n4 + n5))*n3^(-n4)',
assuming: { multiply: { commutative: false } }
},
{ l: 'n*c + c', r: '(n+1)*c' },
{
s: 'c*n + c -> c*(n+1)',
assuming: { multiply: { commutative: false } }
},
simplifyConstant, // Second: before returning expressions to "standard form"
// make factors positive (and undo 'make non-constant terms positive')
{
s: '(-n)*n1 -> -(n*n1)',
@ -462,10 +467,10 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
assuming: { multiply: { commutative: true } } // o.w. / not conventional
},
{
s: 'n1^-1 -> 1/n1',
s: 'n^-1 -> 1/n',
assuming: { multiply: { commutative: true } } // o.w. / not conventional
},
{ l: 'n^1', r: 'n' }, // can be produced by power cancellation
{
s: 'n*(n1/n2) -> (n*n1)/n2', // '*' before '/'
assuming: { multiply: { associative: true } }

View File

@ -1,67 +0,0 @@
import { createMap, isMap } from '../../../utils/map.js'
import { isFunctionNode, isNode, isOperatorNode, isParenthesisNode, isSymbolNode } from '../../../utils/is.js'
import { factory } from '../../../utils/factory.js'
const name = 'resolve'
const dependencies = [
'parse',
'FunctionNode',
'OperatorNode',
'ParenthesisNode'
]
export const createResolve = /* #__PURE__ */ factory(name, dependencies, ({
parse,
FunctionNode,
OperatorNode,
ParenthesisNode
}) => {
/**
* resolve(expr, scope) replaces variable nodes with their scoped values
*
* Syntax:
*
* simplify.resolve(expr, scope)
*
* Examples:
*
* math.simplify.resolve('x + y', {x:1, y:2}) // Node {1 + 2}
* math.simplify.resolve(math.parse('x+y'), {x:1, y:2}) // Node {1 + 2}
* math.simplify('x+y', {x:2, y:'x+x'}).toString() // "6"
*
* @param {Node} node
* The expression tree to be simplified
* @param {Object} scope with variables to be resolved
*/
function resolve (node, scope) {
if (!scope) {
return node
}
if (!isMap(scope)) {
scope = createMap(scope)
}
if (isSymbolNode(node)) {
const value = scope.get(node.name)
if (isNode(value)) {
return resolve(value, scope)
} else if (typeof value === 'number') {
return parse(String(value))
}
} else if (isOperatorNode(node)) {
const args = node.args.map(function (arg) {
return resolve(arg, scope)
})
return new OperatorNode(node.op, node.fn, args, node.implicit)
} else if (isParenthesisNode(node)) {
return new ParenthesisNode(resolve(node.content, scope))
} else if (isFunctionNode(node)) {
const args = node.args.map(function (arg) {
return resolve(arg, scope)
})
return new FunctionNode(node.name, args)
}
return node
}
return resolve
})

View File

@ -1,6 +1,6 @@
import { isAccessorNode, isArrayNode, isConstantNode, isFunctionNode, isIndexNode, isObjectNode, isOperatorNode } from '../../../utils/is.js'
import { createUtil } from './util.js'
import { factory } from '../../../utils/factory.js'
import { isAccessorNode, isArrayNode, isConstantNode, isFunctionNode, isIndexNode, isObjectNode, isOperatorNode } from '../../utils/is.js'
import { createUtil } from './simplify/util.js'
import { factory } from '../../utils/factory.js'
const name = 'simplifyCore'
const dependencies = [
@ -53,22 +53,23 @@ export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({
*
* Syntax:
*
* simplify.simplifyCore(expr)
* simplifyCore(expr)
*
* Examples:
*
* const f = math.parse('2 * 1 * x ^ (2 - 1)')
* math.simplify.simpifyCore(f) // Node {2 * x}
* math.simplify('2 * 1 * x ^ (2 - 1)', [math.simplify.simpifyCore]) // Node {2 * x}
* math.simpifyCore(f) // Node {2 * x}
* math.simplify('2 * 1 * x ^ (2 - 1)', [math.simplifyCore]) // Node {2 * x}
*
* See also:
*
* derivative
* simplify, resolve, derivative
*
* @param {Node} node
* The expression to be simplified
* @param {Object} options
* Simplification options, as per simplify()
* @return {Node} Returns expression with basic simplifications applied
*/
function simplifyCore (node, options) {
const context = options ? options.context : undefined

View File

@ -0,0 +1,64 @@
// test resolve
import assert from 'assert'
import math from '../../../../src/defaultInstance.js'
import { simplifyAndCompare } from './simplify.test.js'
describe('resolve', function () {
it('should substitute scoped constants', function () {
const sumxy = math.parse('x+y')
const collapsingScope = { x: math.parse('y'), y: math.parse('z') }
assert.strictEqual(
math.resolve(sumxy, { x: 1 }).toString(),
'1 + y'
) // direct
assert.strictEqual(
math.resolve(sumxy, collapsingScope).toString(),
'z + z'
)
assert.strictEqual(
math.resolve(
math.parse('[x,y,1,w]'), collapsingScope).toString(),
'[z, z, 1, w]'
)
simplifyAndCompare('x+y', 'x+y', {}) // operator
simplifyAndCompare('x+y', 'y+1', { x: 1 })
simplifyAndCompare('x+y', 'y+1', { x: math.parse('1') })
simplifyAndCompare('x+y', '3', { x: 1, y: 2 })
simplifyAndCompare('x+x+x', '3*x')
simplifyAndCompare('y', 'x+1', { y: math.parse('1+x') })
simplifyAndCompare('y', '3', { x: 2, y: math.parse('1+x') })
simplifyAndCompare('x+y', '3*x', { y: math.parse('x+x') })
simplifyAndCompare('x+y', '6', { x: 2, y: math.parse('x+x') })
simplifyAndCompare('x+(y+2-1-1)', '6', { x: 2, y: math.parse('x+x') }) // parentheses
simplifyAndCompare('log(x+y)', String(Math.log(6)), { x: 2, y: math.parse('x+x') }) // function
simplifyAndCompare('combinations( ceil(abs(sin(x)) * (y+3)), abs(x) )',
'combinations(ceil(0.9092974268256817 * (y + 3) ), 2)', { x: -2 })
simplifyAndCompare('size(text)[1]', '11', { text: 'hello world' })
})
it('should substitute scoped constants from Map like scopes', function () {
assert.strictEqual(
math.resolve(math.parse('x+y'), new Map([['x', 1]])).toString(), '1 + y'
) // direct
simplifyAndCompare('x+y', 'x+y', new Map()) // operator
simplifyAndCompare('x+y', 'y+1', new Map([['x', 1]]))
simplifyAndCompare('x+y', 'y+1', new Map([['x', math.parse('1')]]))
})
it('should throw an error in case of reference loop', function () {
const sumxy = math.parse('x+y')
assert.throws(
() => math.resolve(sumxy, { x: math.parse('x') }),
/ReferenceError.*\{x\}/)
assert.throws(
() => math.resolve(sumxy, {
y: math.parse('3z'),
z: math.parse('1-x'),
x: math.parse('cos(y)')
}),
/ReferenceError.*\{x, y, z\}/)
})
})

View File

@ -3,44 +3,44 @@ import assert from 'assert'
import math from '../../../../src/defaultInstance.js'
describe('simplify', function () {
const expLibrary = []
function simplifyAndCompare (left, right, rules, scope, opt, stringOpt) {
expLibrary.push(left)
let simpLeft
try {
if (Array.isArray(rules)) {
if (opt) {
simpLeft = math.simplify(left, rules, scope, opt)
} else if (scope) {
simpLeft = math.simplify(left, rules, scope)
} else {
simpLeft = math.simplify(left, rules)
}
const expLibrary = []
export function simplifyAndCompare (left, right, rules, scope, opt, stringOpt) {
expLibrary.push(left)
let simpLeft
try {
if (Array.isArray(rules)) {
if (opt) {
simpLeft = math.simplify(left, rules, scope, opt)
} else if (scope) {
simpLeft = math.simplify(left, rules, scope)
} else {
if (opt) stringOpt = opt
if (scope) opt = scope
if (rules) scope = rules
if (opt) {
simpLeft = math.simplify(left, scope, opt)
} else if (scope) {
simpLeft = math.simplify(left, scope)
} else {
simpLeft = math.simplify(left)
}
simpLeft = math.simplify(left, rules)
}
} catch (err) {
if (err instanceof Error) {
console.log(err.stack)
} else {
if (opt) stringOpt = opt
if (scope) opt = scope
if (rules) scope = rules
if (opt) {
simpLeft = math.simplify(left, scope, opt)
} else if (scope) {
simpLeft = math.simplify(left, scope)
} else {
console.log(new Error(err))
simpLeft = math.simplify(left)
}
throw err
}
assert.strictEqual(
simpLeft.toString(stringOpt), math.parse(right).toString(stringOpt))
} catch (err) {
if (err instanceof Error) {
console.log(err.stack)
} else {
console.log(new Error(err))
}
throw err
}
assert.strictEqual(
simpLeft.toString(stringOpt), math.parse(right).toString(stringOpt))
}
describe('simplify', function () {
function simplifyAndCompareEval (left, right, scope) {
expLibrary.push(left)
scope = scope || {}
@ -101,65 +101,13 @@ describe('simplify', function () {
const f = new math.FunctionAssignmentNode('sigma', ['x'], math.parse('1 / (1 + exp(-x))'))
assert.strictEqual(f.toString(), 'sigma(x) = 1 / (1 + exp(-x))')
assert.strictEqual(f.evaluate()(5), 0.9933071490757153)
const fsimplified = math.simplify.simplifyCore(f)
const fsimplified = math.simplifyCore(f)
assert.strictEqual(fsimplified.toString(), 'sigma(x) = 1 / (1 + exp(-x))')
assert.strictEqual(fsimplified.evaluate()(5), 0.9933071490757153)
})
const testSimplifyCore = function (expr, expected, opts = {}) {
const actual = math.simplify.simplifyCore(math.parse(expr)).toString(opts)
assert.strictEqual(actual, expected)
}
it('simplifyCore should handle different node types', function () {
testSimplifyCore('5*x*3', '15 * x')
testSimplifyCore('5*x*3*x', '15 * x * x')
testSimplifyCore('x-0', 'x')
testSimplifyCore('0-x', '-x')
testSimplifyCore('0-3', '-3')
testSimplifyCore('x+0', 'x')
testSimplifyCore('0+x', 'x')
testSimplifyCore('0*x', '0')
testSimplifyCore('x*0', '0')
testSimplifyCore('x*1', 'x')
testSimplifyCore('1*x', 'x')
testSimplifyCore('-(x)', '-x')
testSimplifyCore('0/x', '0')
testSimplifyCore('(1*x + y*0)*1+0', 'x')
testSimplifyCore('sin(x+0)*1', 'sin(x)')
testSimplifyCore('((x+0)*1)', 'x')
testSimplifyCore('sin((x-0)*1+y*0)', 'sin(x)')
testSimplifyCore('[x+0,1*y,z*0]', '[x, y, 0]')
testSimplifyCore('(a+b+0)[n*0+1,-(n)]', '(a + b)[1, -n]')
testSimplifyCore('{a:x*1, b:y-0}', '{"a": x, "b": y}')
})
it('simplifyCore strips ParenthesisNodes (implicit in tree)', function () {
testSimplifyCore('((x)*(y))', 'x * y')
testSimplifyCore('((x)*(y))^1', 'x * y')
testSimplifyCore('x*(y+z)', 'x * (y + z)')
testSimplifyCore('x+(y+z)+w', 'x + y + z + w')
// But it doesn't actually change the association internally:
testSimplifyCore('x+ y+z +w', '((x + y) + z) + w', { parenthesis: 'all' })
testSimplifyCore('x+(y+z)+w', '(x + (y + z)) + w', { parenthesis: 'all' })
})
it('simplifyCore folds constants', function () {
testSimplifyCore('1+2', '3')
testSimplifyCore('2*3', '6')
testSimplifyCore('2-3', '-1')
testSimplifyCore('3/2', '1.5')
testSimplifyCore('3^2', '9')
})
it('should simplifyCore convert +unaryMinus to subtract', function () {
simplifyAndCompareEval('--2', '2')
const result = math.simplify('x + y + a', [math.simplify.simplifyCore], { a: -1 }).toString()
assert.strictEqual(result, 'x + y - 1')
})
it('should simplify convert minus and unary minus', function () {
simplifyAndCompareEval('--2', '2')
// see https://github.com/josdejong/mathjs/issues/1013
assert.strictEqual(math.simplify('0 - -1', {}).toString(), '1')
assert.strictEqual(math.simplify('0 - -x', {}).toString(), 'x')
@ -171,10 +119,10 @@ describe('simplify', function () {
})
it('should simplify inside arrays and indexing', function () {
simplifyAndCompare('[3x+0]', '[3x]') // simplifyCore inside array
simplifyAndCompare('[3x+0]', '[3x]')
simplifyAndCompare('[3x+5x]', '[8*x]')
simplifyAndCompare('[2*3,6+2]', '[6,8]')
simplifyAndCompare('[x^0,y*0,z*1,w-0][2+n*1]', '[1,0,z,w][n+2]') // simplifyCore in index
simplifyAndCompare('[x^0,y*0,z*1,w-0][2+n*1]', '[1,0,z,w][n+2]')
simplifyAndCompare('[x,y-2y,z,w+w][(3-2)*n+2]', '[x,-y,z,2*w][n+2]')
})
@ -212,7 +160,7 @@ describe('simplify', function () {
const f = new math.FunctionNode(new math.SymbolNode('doubleIt'), [new math.SymbolNode('value')])
assert.strictEqual(f.toString(), 'doubleIt(value)')
assert.strictEqual(f.evaluate({ doubleIt: doubleIt, value: 4 }), 8)
const fsimplified = math.simplify.simplifyCore(f)
const fsimplified = math.simplifyCore(f)
assert.strictEqual(fsimplified.toString(), 'doubleIt(value)')
assert.strictEqual(fsimplified.evaluate({ doubleIt: doubleIt, value: 4 }), 8)
})
@ -222,7 +170,7 @@ describe('simplify', function () {
const f = new math.FunctionNode(s, [new math.SymbolNode('x')])
assert.strictEqual(f.toString(), '(sigma(x) = 1 / (1 + exp(-x)))(x)')
assert.strictEqual(f.evaluate({ x: 5 }), 0.9933071490757153)
const fsimplified = math.simplify.simplifyCore(f)
const fsimplified = math.simplifyCore(f)
assert.strictEqual(fsimplified.toString(), '(sigma(x) = 1 / (1 + exp(-x)))(x)')
assert.strictEqual(fsimplified.evaluate({ x: 5 }), 0.9933071490757153)
})
@ -232,11 +180,11 @@ describe('simplify', function () {
simplifyAndCompare('2 - 3', '-1')
simplifyAndCompare('2 - -3', '5')
let e = math.parse('2 - -3')
e = math.simplify.simplifyCore(e)
e = math.simplifyCore(e)
assert.strictEqual(e.toString(), '5') // simplifyCore
simplifyAndCompare('x - -x', '2*x')
e = math.parse('x - -x')
e = math.simplify.simplifyCore(e)
e = math.simplifyCore(e)
assert.strictEqual(e.toString(), 'x + x') // not a core simplification since + is cheaper than *
})
@ -301,6 +249,13 @@ describe('simplify', function () {
simplifyAndCompare('x - (y - (y - x))', '0')
simplifyAndCompare('5 + (5 * x) - (3 * x) + 2', '2*x+7')
simplifyAndCompare('x^2*y^2 - (x*y)^2', '0')
simplifyAndCompare('(x*z^2 + y*z)/z^4', '(y + z*x)/z^3') // #1423
simplifyAndCompare('(x^2*y + z*y)/y^4', '(x^2 + z)/y^3')
simplifyAndCompare('6x/3x', '2') // Additional cases from PR review
simplifyAndCompare('-28y/-4y', '7')
simplifyAndCompare('-28*(z/-4z)', '7')
simplifyAndCompare('(x^2 + 2x)*x', '2*x^2 + x^3')
simplifyAndCompare('x + y/z', 'x + y/z') // avoid overzealous '(x+y*z)/z'
})
it('should collect separated like factors', function () {
@ -415,38 +370,6 @@ describe('simplify', function () {
simplifyAndCompare('x-(y-y+x)', '0', {}, optsNAANCM)
})
it('resolve() should substitute scoped constants', function () {
assert.strictEqual(
math.simplify.resolve(math.parse('x+y'), { x: 1 }).toString(),
'1 + y'
) // direct
simplifyAndCompare('x+y', 'x+y', {}) // operator
simplifyAndCompare('x+y', 'y+1', { x: 1 })
simplifyAndCompare('x+y', 'y+1', { x: math.parse('1') })
simplifyAndCompare('x+y', '3', { x: 1, y: 2 })
simplifyAndCompare('x+x+x', '3*x')
simplifyAndCompare('y', 'x+1', { y: math.parse('1+x') })
simplifyAndCompare('y', '3', { x: 2, y: math.parse('1+x') })
simplifyAndCompare('x+y', '3*x', { y: math.parse('x+x') })
simplifyAndCompare('x+y', '6', { x: 2, y: math.parse('x+x') })
simplifyAndCompare('x+(y+2-1-1)', '6', { x: 2, y: math.parse('x+x') }) // parentheses
simplifyAndCompare('log(x+y)', String(Math.log(6)), { x: 2, y: math.parse('x+x') }) // function
simplifyAndCompare('combinations( ceil(abs(sin(x)) * (y+3)), abs(x) )',
'combinations(ceil(0.9092974268256817 * (y + 3) ), 2)', { x: -2 })
// TODO(deal with accessor nodes) simplifyAndCompare('size(text)[1]', '11', {text: "hello world"})
})
it('resolve() should substitute scoped constants from Map like scopes', function () {
assert.strictEqual(
math.simplify.resolve(math.parse('x+y'), new Map([['x', 1]])).toString(),
'1 + y'
) // direct
simplifyAndCompare('x+y', 'x+y', new Map()) // operator
simplifyAndCompare('x+y', 'y+1', new Map([['x', 1]]))
simplifyAndCompare('x+y', 'y+1', new Map([['x', math.parse('1')]]))
})
it('should keep implicit multiplication implicit', function () {
const f = math.parse('2x')
assert.strictEqual(f.toString({ implicit: 'hide' }), '2 x')

View File

@ -0,0 +1,60 @@
// test simplifyCore
import assert from 'assert'
import math from '../../../../src/defaultInstance.js'
describe('simplifyCore', function () {
const testSimplifyCore = function (expr, expected, opts = {}) {
const actual = math.simplifyCore(math.parse(expr)).toString(opts)
assert.strictEqual(actual, expected)
}
it('should handle different node types', function () {
testSimplifyCore('5*x*3', '15 * x')
testSimplifyCore('5*x*3*x', '15 * x * x')
testSimplifyCore('x-0', 'x')
testSimplifyCore('0-x', '-x')
testSimplifyCore('0-3', '-3')
testSimplifyCore('x+0', 'x')
testSimplifyCore('0+x', 'x')
testSimplifyCore('0*x', '0')
testSimplifyCore('x*0', '0')
testSimplifyCore('x*1', 'x')
testSimplifyCore('1*x', 'x')
testSimplifyCore('-(x)', '-x')
testSimplifyCore('0/x', '0')
testSimplifyCore('(1*x + y*0)*1+0', 'x')
testSimplifyCore('sin(x+0)*1', 'sin(x)')
testSimplifyCore('((x+0)*1)', 'x')
testSimplifyCore('sin((x-0)*1+y*0)', 'sin(x)')
testSimplifyCore('[x+0,1*y,z*0]', '[x, y, 0]')
testSimplifyCore('(a+b+0)[n*0+1,-(n)]', '(a + b)[1, -n]')
testSimplifyCore('{a:x*1, b:y-0}', '{"a": x, "b": y}')
})
it('strips ParenthesisNodes (implicit in tree)', function () {
testSimplifyCore('((x)*(y))', 'x * y')
testSimplifyCore('((x)*(y))^1', 'x * y')
testSimplifyCore('x*(y+z)', 'x * (y + z)')
testSimplifyCore('x+(y+z)+w', 'x + y + z + w')
// But it doesn't actually change the association internally:
testSimplifyCore('x+ y+z +w', '((x + y) + z) + w', { parenthesis: 'all' })
testSimplifyCore('x+(y+z)+w', '(x + (y + z)) + w', { parenthesis: 'all' })
})
it('folds constants', function () {
testSimplifyCore('1+2', '3')
testSimplifyCore('2*3', '6')
testSimplifyCore('2-3', '-1')
testSimplifyCore('3/2', '1.5')
testSimplifyCore('3^2', '9')
})
it('should convert +unaryMinus to subtract', function () {
const result = math.simplify(
'x + y + a', [math.simplifyCore], { a: -1 }
).toString()
assert.strictEqual(result, 'x + y - 1')
})
})

View File

@ -282,6 +282,34 @@ function generateDoc (name, code) {
return count > 0
}
function parseThrows () {
let count = 0
let match
do {
match = /\s*@throws\s*\{(.*)}\s*(.*)?$/.exec(line)
if (match) {
next()
count++
const annotation = {
description: (match[2] || '').trim(),
type: (match[1] || '').trim()
}
doc.mayThrow.push(annotation)
// multi line description (must be non-empty and not start with @param or @return)
while (exists() && !empty() && !/^\s*@/.test(line)) {
const lineTrim = line.trim()
const separator = (lineTrim[0] === '-' ? '</br>' : ' ')
annotation.description += separator + lineTrim
next()
}
}
} while (match)
return count > 0
}
function parseReturns () {
const match = /\s*@returns?\s*\{(.*)}\s*(.*)?$/.exec(line)
if (match) {
@ -312,7 +340,8 @@ function generateDoc (name, code) {
examples: [],
seeAlso: [],
parameters: [],
returns: null
returns: null,
mayThrow: []
}
next()
@ -327,7 +356,8 @@ function generateDoc (name, code) {
parseExamples() ||
parseSeeAlso() ||
parseParameters() ||
parseReturns()
parseReturns() ||
parseThrows()
if (!handled) {
// skip this line, no one knows what to do with it
@ -384,6 +414,15 @@ function validateDoc (doc) {
}
}
if (doc.mayThrow && doc.mayThrow.length) {
doc.mayThrow.forEach(function (err, index) {
if (!err.type) {
issues.push(
'function "' + doc.name + '": error type missing for throw ' + index)
}
})
}
if (doc.returns) {
if (!doc.returns.description || !doc.returns.description.trim()) {
issues.push('function "' + doc.name + '": description missing of returns')
@ -453,6 +492,16 @@ function generateMarkdown (doc, functions) {
'\n\n\n'
}
if (doc.mayThrow) {
text += '### Throws\n\n' +
'Type | Description\n' +
'---- | -----------\n' +
doc.mayThrow.map(function (t) {
return (t.type || '') + ' | ' + t.description
}).join('\n') +
'\n\n'
}
if (doc.examples && doc.examples.length) {
text += '## Examples\n\n' +
'```js\n' +

33
types/index.d.ts vendored
View File

@ -575,7 +575,7 @@ declare namespace math {
* @param unit The unit to be created
* @returns The created unit
*/
unit(value: number | MathArray | Matrix, unit: string): Unit;
unit(value: number | MathArray | Matrix | BigNumber, unit: string): Unit;
/*************************************************************************
* Expression functions
@ -1796,7 +1796,7 @@ declare namespace math {
* @param n A real or complex number
* @returns The gamma of n
*/
gamma(n: number | MathArray | Matrix): number | MathArray | Matrix;
gamma<T extends number | BigNumber | Complex | MathArray | Matrix>(n: T): NoLiteralType<T>;
/**
* Calculate the Kullback-Leibler (KL) divergence between two
@ -3134,6 +3134,28 @@ declare namespace math {
fixPrefix?: boolean;
}
interface UnitComponent {
power: number;
prefix: string;
unit: {
name: string;
base: {
dimensions: number[];
key: string;
};
prefixes: Record<string, UnitPrefix>;
value: number;
offset: number;
dimensions: number[];
};
}
interface UnitPrefix {
name: string;
value: number;
scientific: boolean;
}
interface Unit {
valueOf(): string;
clone(): Unit;
@ -3152,7 +3174,14 @@ declare namespace math {
toJSON(): MathJSON;
formatUnits(): string;
format(options: FormatOptions): string;
simplify(): Unit;
splitUnit(parts: ReadonlyArray<string | Unit>): Unit[];
units: UnitComponent[];
dimensions: number[];
value: number;
fixPrefix: boolean;
skipAutomaticSimplification: true;
}
interface CreateUnitOptions {