Skip to content

Commit

Permalink
fix(simplify): Improve collection of nonconstant like terms (#2384)
Browse files Browse the repository at this point in the history
The key is that the rule-matching engine is optimized for finding matches
  on the lefts of terms, but the central term-collection rule
  `n1*n3 + n2*n3 -> (n1+n2)*n3` was written with the key common term rightmost.
  Reversing this rule to `n3*n1 + n3*n2 -> n3*(n1+n2)` therefore does most
  of the work of improving like-term collection. It also better corresponds
  to typical mathematical presentation: common terms tend to be pulled out
  to the left in common practice.

  Floating constants to the right of a product initially (before they are
  moved back to the left for human-preferred output) and ensuring that
  negations are subsumed into constants whenever possible did the rest.

  Also, rule context was not being propagated into the simplification
  engine; this commit corrects that.

  Resolves #1179.
  Resolves #1290.

Co-authored-by: Jos de Jong <wjosdejong@gmail.com>
  • Loading branch information
gwhitney and josdejong authored Jan 15, 2022
1 parent 7beac55 commit 84c3abd
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 10 deletions.
24 changes: 15 additions & 9 deletions src/function/algebra/simplify.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,20 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
{ l: 'log(e)', r: '1' },

// temporary rules
// Note initially we tend constants to the right because like-term
// collection prefers the left, and we would rather collect nonconstants
{ l: 'n-n1', r: 'n+-n1' }, // temporarily replace 'subtract' so we can further flatten the 'add' operator
{ l: '-(c*v)', r: '(-c) * v' }, // make non-constant terms positive
{ l: '-v', r: '(-1) * v' },
{ l: '-(c*v)', r: 'v * (-c)' }, // make non-constant terms positive
{ l: '-v', r: 'v * (-1)' },
{ 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' },

// remove parenthesis in the case of negating a quantity
{ l: 'n1 + (n2 + n3)*(-1)', r: 'n1 + n2*(-1) + n3*(-1)' },
// subsume resulting -1 into constants where possible
{ l: '(-1) * c', r: '-c' },
{ l: '(-1) * (-c)', r: 'c' },

// expand nested exponentiation
{ l: '(n ^ n1) ^ n2', r: 'n ^ (n1 * n2)' },

Expand All @@ -302,17 +310,15 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
// collect like terms
{ l: 'n+n', r: '2*n' },
{ l: 'n+-n', r: '0' },
{ l: 'n1*n2 + n2', r: '(n1+1)*n2' },
{ l: 'n1*n3 + n2*n3', r: '(n1+n2)*n3' },

// remove parenthesis in the case of negating a quantitiy
{ l: 'n1 + -1 * (n2 + n3)', r: 'n1 + -1 * n2 + -1 * n3' },
{ 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: 'n*c + c', r: '(n+1)*c' },

simplifyConstant,

{ l: '(-n)*n1', r: '-(n*n1)' }, // make factors positive (and undo 'make non-constant terms positive')

// ordering of constants
// final ordering of constants
{ l: 'c+v', r: 'v+c', context: { add: { commutative: false } } },
{ l: 'v*c', r: 'c*v', context: { multiply: { commutative: false } } },

Expand Down Expand Up @@ -376,7 +382,7 @@ export const createSimplify = /* #__PURE__ */ factory(name, dependencies, (
r: removeParens(parse(rule.r))
}
if (rule.context) {
newRule.evaluate = rule.context
newRule.context = rule.context
}
if (rule.evaluate) {
newRule.evaluate = parse(rule.evaluate)
Expand Down
8 changes: 7 additions & 1 deletion test/unit-tests/function/algebra/simplify.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,12 @@ describe('simplify', function () {
simplifyAndCompare('x+x', '2*x')
simplifyAndCompare('2x+x', '3*x')
simplifyAndCompare('2(x+1)+(x+1)', '3*(x + 1)')
simplifyAndCompare('y*x^2+2*x^2', '(y+2)*x^2')
simplifyAndCompare('2(x+1)+x+1', '3*(x + 1)')
simplifyAndCompare('y*x^2+2*x^2', 'x^2*(y+2)')
simplifyAndCompare('x*y + y*x', '2*x*y')
simplifyAndCompare('x*y - y*x', '0')
simplifyAndCompare('x^2*y^3*z - y*z*y*x^2*y', '0')
simplifyAndCompare('x^2*y^3*z - y*z*x^2*y', 'x^2*z*(y^3-y^2)')
})

it('should collect separated like terms', function () {
Expand All @@ -246,6 +251,7 @@ describe('simplify', function () {
simplifyAndCompare('10 - (x - 2)', '12 - x')
simplifyAndCompare('x - (y + x)', '-y')
simplifyAndCompare('x - (y - (y - x))', '0')
simplifyAndCompare('5 + (5 * x) - (3 * x) + 2', '2*x+7')
})

it('should collect separated like factors', function () {
Expand Down

0 comments on commit 84c3abd

Please sign in to comment.