Skip to content

Commit 76c8c62

Browse files
authored
Improve standard simplify rule matches in non-commutative contexts (#2841)
* Improve standard simplify rule matches in non-commutative contexts Addresses the rule application limitation aspect as highlighted in issue #2825; such that a broader set of successful standard replacement rules are applied to multi-arg/associative expressions in non-commutative contexts. * Remove 'clone()' operations on expanded simplify rules since original rule nodes (including expanded variations) are essentially readonly objects, cloning of expanded rule LHS' is unnecessary during canonicalization * Hoist non-commutative context expanded rule app. in simplify (applyRule) * Add two simplify non-commutative ctx. test cases
1 parent f99020e commit 76c8c62

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

src/function/algebra/simplify.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,15 +447,34 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
447447
}
448448

449449
if (isAssociative(newRule.l, context)) {
450+
const nonCommutative = !isCommutative(newRule.l, context)
451+
let leftExpandsym
452+
// Gen. the LHS placeholder used in this NC-context specific expansion rules
453+
if (nonCommutative) leftExpandsym = _getExpandPlaceholderSymbol()
454+
450455
const makeNode = createMakeNodeFunction(newRule.l)
451456
const expandsym = _getExpandPlaceholderSymbol()
452457
newRule.expanded = {}
453-
newRule.expanded.l = makeNode([newRule.l.clone(), expandsym])
458+
newRule.expanded.l = makeNode([newRule.l, expandsym])
454459
// Push the expandsym into the deepest possible branch.
455460
// This helps to match the newRule against nodes returned from getSplits() later on.
456461
flatten(newRule.expanded.l, context)
457462
unflattenr(newRule.expanded.l, context)
458463
newRule.expanded.r = makeNode([newRule.r, expandsym])
464+
465+
// In and for a non-commutative context, attempting with yet additional expansion rules makes
466+
// way for more matches cases of multi-arg expressions; such that associative rules (such as
467+
// 'n*n -> n^2') can be applied to exprs. such as 'a * b * b' and 'a * b * b * a'.
468+
if (nonCommutative) {
469+
// 'Non-commutative' 1: LHS (placeholder) only
470+
newRule.expandedNC1 = {}
471+
newRule.expandedNC1.l = makeNode([leftExpandsym, newRule.l])
472+
newRule.expandedNC1.r = makeNode([leftExpandsym, newRule.r])
473+
// 'Non-commutative' 2: farmost LHS and RHS placeholders
474+
newRule.expandedNC2 = {}
475+
newRule.expandedNC2.l = makeNode([leftExpandsym, newRule.expanded.l])
476+
newRule.expandedNC2.r = makeNode([leftExpandsym, newRule.expanded.r])
477+
}
459478
}
460479

461480
return newRule
@@ -657,6 +676,15 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
657676
repl = rule.expanded.r
658677
matches = _ruleMatch(rule.expanded.l, res, mergedContext)[0]
659678
}
679+
// Additional, non-commutative context expansion-rules
680+
if (!matches && rule.expandedNC1) {
681+
repl = rule.expandedNC1.r
682+
matches = _ruleMatch(rule.expandedNC1.l, res, mergedContext)[0]
683+
if (!matches) { // Existence of NC1 implies NC2
684+
repl = rule.expandedNC2.r
685+
matches = _ruleMatch(rule.expandedNC2.l, res, mergedContext)[0]
686+
}
687+
}
660688

661689
if (matches) {
662690
// const before = res.toString({parenthesis: 'all'})
@@ -880,8 +908,8 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
880908
}
881909
res = mergeChildMatches(childMatches)
882910
} else if (node.args.length >= 2 && rule.args.length === 2) { // node is flattened, rule is not
883-
// Associative operators/functions can be split in different ways so we check if the rule matches each
884-
// them and return their union.
911+
// Associative operators/functions can be split in different ways so we check if the rule
912+
// matches for each of them and return their union.
885913
const splits = getSplits(node, context)
886914
let splitMatches = []
887915
for (let i = 0; i < splits.length; i++) {

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,13 +440,35 @@ describe('simplify', function () {
440440

441441
it('should respect context changes to operator properties', function () {
442442
const optsNCM = { context: { multiply: { commutative: false } } }
443+
const optsNCA = { context: { add: { commutative: false } } }
444+
443445
simplifyAndCompare('x*y+y*x', 'x*y+y*x', {}, optsNCM)
444446
simplifyAndCompare('x*y-y*x', 'x*y-y*x', {}, optsNCM)
445447
simplifyAndCompare('x*5', 'x*5', {}, optsNCM)
446448
simplifyAndCompare('x*y*x^(-1)', 'x*y*x^(-1)', {}, optsNCM)
447449
simplifyAndCompare('x*y/x', 'x*y*x^(-1)', {}, optsNCM)
448450
simplifyAndCompare('x*y*(1/x)', 'x*y*x^(-1)', {}, optsNCM)
449451

452+
// Rules apply to *segments* of operands in NC multi-arg. exprs.
453+
// ('n*n->n^2')
454+
simplifyAndCompare('n*n*3', 'n^2*3', {}, optsNCM)
455+
simplifyAndCompare('3*n*n', '3*n^2', {}, optsNCM)
456+
simplifyAndCompare('3*n*n*3', '3*n^2*3', {}, optsNCM)
457+
simplifyAndCompare('3*n*n*n*3', '3*n^2*n*3', {}, optsNCM)
458+
simplifyAndCompare('3*3*n*n*n*3', '9*n^2*n*3', {}, optsNCM)
459+
simplifyAndCompare('(w*z)*n*n*3', 'w*z*n^2*3', {}, optsNCM)
460+
simplifyAndCompare('2*n*n*3*n*n*4', '2*n^2*3*n^2*4', {}, optsNCM) // 'double wedged', +applied >1x
461+
// ('v*(v*n1+n2) -> v^2*n1+v*n2')
462+
simplifyAndCompare('w*x*(x*y+z)', 'w*(x^2*y+x*z)', {}, optsNCM)
463+
simplifyAndCompare('w*x*(x*y+z)*w', 'w*(x^2*y+x*z)*w', {}, optsNCM)
464+
// 'n+n -> 2*n'
465+
simplifyAndCompare('x+x+3', '2*x+3', {}, optsNCA)
466+
simplifyAndCompare('3+x+x', '3+2*x', {}, optsNCA)
467+
simplifyAndCompare('4+x+x+4', '4+2*x+4', {}, optsNCA)
468+
simplifyAndCompare('4+x+x+5+x+x+6', '4+2*x+5+2*x+6', {}, optsNCA) // 'double wedged', +applied >1x
469+
// 'n+n -> 2*n' & 'n3*n1 + n3*n2 -> n3*(n1+n2)'
470+
simplifyAndCompare('5+x+x+x+x+5', '5+4*x+5', {}, optsNCA)
471+
450472
const optsNAA = { context: { add: { associative: false } } }
451473
simplifyAndCompare(
452474
'x + (-x+y)', 'x + (y-x)', {}, optsNAA, { parenthesis: 'all' })

0 commit comments

Comments
 (0)