Skip to content

Commit e8d4f79

Browse files
committed
fix; handle multiple consecutive operations in simplifyCore()
Also adds support for logical operators. Resolves #2484.
1 parent 02fd9d1 commit e8d4f79

3 files changed

Lines changed: 76 additions & 29 deletions

File tree

src/function/algebra/simplifyCore.js

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,24 @@ export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({
5252
createUtil({ FunctionNode, OperatorNode, SymbolNode })
5353
/**
5454
* simplifyCore() performs single pass simplification suitable for
55-
* applications requiring ultimate performance. In contrast, simplify()
56-
* extends simplifyCore() with additional passes to provide deeper
57-
* simplification.
55+
* applications requiring ultimate performance. To roughly summarize,
56+
* it handles cases along the lines of simplifyConstant() but where
57+
* knowledge of a single argument is sufficient to determine the value.
58+
* In contrast, simplify() extends simplifyCore() with additional passes
59+
* to provide deeper simplification (such as gathering like terms).
5860
*
5961
* Specifically, simplifyCore:
6062
*
6163
* * Converts all function calls with operator equivalents to their
6264
* operator forms.
6365
* * Removes operators or function calls that are guaranteed to have no
6466
* effect (such as unary '+').
65-
* * Removes double unary '-' and '~'
67+
* * Removes double unary '-', '~', and 'not'
6668
* * Eliminates addition/subtraction of 0 and multiplication/division/powers
6769
* by 1 or 0.
6870
* * Converts addition of a negation into subtraction.
71+
* * Eliminates logical operations with constant true or false leading
72+
* arguments.
6973
* * Puts constants on the left of a product, if multiplication is
7074
* considered commutative by the options (which is the default)
7175
*
@@ -146,26 +150,33 @@ export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({
146150
if (isOperatorNode(node) && node.isUnary()) {
147151
const a0 = simplifyCore(node.args[0], options)
148152

149-
if (node.op === '-') { // unary minus
150-
if (isOperatorNode(a0)) {
151-
if (a0.isUnary() && a0.op === '-') {
152-
return a0.args[0]
153-
} else if (a0.isBinary() && a0.fn === 'subtract') {
154-
return new OperatorNode('-', 'subtract', [a0.args[1], a0.args[0]])
155-
}
153+
if (node.op === '~') { // bitwise not
154+
if (isOperatorNode(a0) && a0.isUnary() && a0.op === '~') {
155+
return a0.args[0]
156156
}
157-
return new OperatorNode(node.op, node.fn, [a0])
158157
}
159-
if (node.op === '~') { // bitwise not
158+
if (node.op === 'not') { // logical not
159+
if (isOperatorNode(a0) && a0.isUnary() && a0.op === 'not') {
160+
return a0.args[0]
161+
}
162+
}
163+
let finish = true
164+
if (node.op === '-') { // unary minus
160165
if (isOperatorNode(a0)) {
161-
if (a0.isUnary() && a0.op === '~') {
166+
if (a0.isBinary() && a0.fn === 'subtract') {
167+
node = new OperatorNode('-', 'subtract', [a0.args[1], a0.args[0]])
168+
finish = false // continue to process the new binary node
169+
}
170+
if (a0.isUnary() && a0.op === '-') {
162171
return a0.args[0]
163172
}
164173
}
165174
}
166-
} else if (isOperatorNode(node) && node.isBinary()) {
175+
if (finish) return new OperatorNode(node.op, node.fn, [a0])
176+
}
177+
if (isOperatorNode(node) && node.isBinary()) {
167178
const a0 = simplifyCore(node.args[0], options)
168-
const a1 = simplifyCore(node.args[1], options)
179+
let a1 = simplifyCore(node.args[1], options)
169180

170181
if (node.op === '+') {
171182
if (isConstantNode(a0) && isZero(a0.value)) {
@@ -175,22 +186,24 @@ export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({
175186
return a0
176187
}
177188
if (isOperatorNode(a1) && a1.isUnary() && a1.op === '-') {
178-
return new OperatorNode('-', 'subtract', [a0, a1.args[0]])
189+
a1 = a1.args[0]
190+
node = new OperatorNode('-', 'subtract', [a0, a1])
191+
}
192+
}
193+
if (node.op === '-') {
194+
if (isOperatorNode(a1) && a1.isUnary() && a1.op === '-') {
195+
return simplifyCore(
196+
new OperatorNode('+', 'add', [a0, a1.args[0]]), options)
179197
}
180-
return new OperatorNode(node.op, node.fn, a1 ? [a0, a1] : [a0])
181-
} else if (node.op === '-') {
182198
if (isConstantNode(a0) && isZero(a0.value)) {
183-
return new OperatorNode('-', 'unaryMinus', [a1])
199+
return simplifyCore(new OperatorNode('-', 'unaryMinus', [a1]))
184200
}
185201
if (isConstantNode(a1) && isZero(a1.value)) {
186202
return a0
187203
}
188-
if (isOperatorNode(a1) && a1.isUnary() && a1.op === '-') {
189-
return simplifyCore(
190-
new OperatorNode('+', 'add', [a0, a1.args[0]]), options)
191-
}
192204
return new OperatorNode(node.op, node.fn, [a0, a1])
193-
} else if (node.op === '*') {
205+
}
206+
if (node.op === '*') {
194207
if (isConstantNode(a0)) {
195208
if (isZero(a0.value)) {
196209
return node0
@@ -209,15 +222,17 @@ export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({
209222
}
210223
}
211224
return new OperatorNode(node.op, node.fn, [a0, a1], node.implicit)
212-
} else if (node.op === '/') {
225+
}
226+
if (node.op === '/') {
213227
if (isConstantNode(a0) && isZero(a0.value)) {
214228
return node0
215229
}
216230
if (isConstantNode(a1) && equal(a1.value, 1)) {
217231
return a0
218232
}
219233
return new OperatorNode(node.op, node.fn, [a0, a1])
220-
} else if (node.op === '^') {
234+
}
235+
if (node.op === '^') {
221236
if (isConstantNode(a1)) {
222237
if (isZero(a1.value)) {
223238
return node1
@@ -226,8 +241,27 @@ export const createSimplifyCore = /* #__PURE__ */ factory(name, dependencies, ({
226241
}
227242
}
228243
}
244+
if (node.op === 'and') {
245+
if (isConstantNode(a0)) {
246+
if (a0.value) {
247+
return a1
248+
} else {
249+
return a0
250+
}
251+
}
252+
}
253+
if (node.op === 'or') {
254+
if (isConstantNode(a0)) {
255+
if (a0.value) {
256+
return a0
257+
} else {
258+
return a1
259+
}
260+
}
261+
}
229262
return new OperatorNode(node.op, node.fn, [a0, a1])
230-
} else if (isOperatorNode(node)) {
263+
}
264+
if (isOperatorNode(node)) {
231265
return new OperatorNode(node.op, node.fn,
232266
node.args.map(a => simplifyCore(a, options)))
233267
}

test/unit-tests/function/algebra/simplify.test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,6 @@ describe('simplify', function () {
516516
const expr = math.parse(textExpr)
517517
const realex = math.simplify(expr, {}, realContext)
518518
const posex = math.simplify(expr, {}, positiveContext)
519-
console.log('Trying', textExpr)
520519
assertAlike(realex.evaluate(zeroes), expr.evaluate(zeroes))
521520
assertAlike(realex.evaluate(negones), expr.evaluate(negones))
522521
assertAlike(realex.evaluate(ones), expr.evaluate(ones))

test/unit-tests/function/algebra/simplifyCore.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ describe('simplifyCore', function () {
2626
testSimplifyCore('1*x', 'x')
2727
testSimplifyCore('-(x)', '-x')
2828
testSimplifyCore('0/x', '0')
29+
testSimplifyCore('~~(a | b)', 'a | b')
30+
testSimplifyCore('not (not (p and q))', 'p and q')
31+
testSimplifyCore('1 and done', 'done')
32+
testSimplifyCore('false and you(know, it)', 'false')
33+
testSimplifyCore('false or bust', 'bust')
34+
testSimplifyCore('6 or dozen/2', '6')
2935
testSimplifyCore('(1*x + y*0)*1+0', 'x')
3036
testSimplifyCore('sin(x+0)*1', 'sin(x)')
3137
testSimplifyCore('((x+0)*1)', 'x')
@@ -54,6 +60,7 @@ describe('simplifyCore', function () {
5460
})
5561

5662
it('should convert +unaryMinus to subtract', function () {
63+
testSimplifyCore('x + -1', 'x - 1')
5764
const result = math.simplify(
5865
'x + y + a', [math.simplifyCore], { a: -1 }
5966
).toString()
@@ -77,4 +84,11 @@ describe('simplifyCore', function () {
7784
testSimplifyCore('and(multiply(1, x), true)', 'x and true')
7885
testSimplifyCore('add(x, 0 ,y)', 'x + y')
7986
})
87+
88+
it('can perform sequential distinct core simplifications', () => {
89+
testSimplifyCore('0 - -x', 'x')
90+
testSimplifyCore('0 - (x - y)', 'y - x')
91+
testSimplifyCore('a + -0', 'a')
92+
testSimplifyCore('-(-x - y)', 'y + x')
93+
})
8094
})

0 commit comments

Comments
 (0)