simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

simplify.js (37181B)


      1 import { isConstantNode, isParenthesisNode } from '../../utils/is.js';
      2 import { factory } from '../../utils/factory.js';
      3 import { createUtil } from './simplify/util.js';
      4 import { createSimplifyConstant } from './simplify/simplifyConstant.js';
      5 import { hasOwnProperty } from '../../utils/object.js';
      6 import { createEmptyMap, createMap } from '../../utils/map.js';
      7 var name = 'simplify';
      8 var dependencies = ['config', 'typed', 'parse', 'add', 'subtract', 'multiply', 'divide', 'pow', 'isZero', 'equal', 'resolve', 'simplifyCore', '?fraction', '?bignumber', 'mathWithTransform', 'matrix', 'AccessorNode', 'ArrayNode', 'ConstantNode', 'FunctionNode', 'IndexNode', 'ObjectNode', 'OperatorNode', 'ParenthesisNode', 'SymbolNode'];
      9 export var createSimplify = /* #__PURE__ */factory(name, dependencies, _ref => {
     10   var {
     11     config,
     12     typed,
     13     parse,
     14     add,
     15     subtract,
     16     multiply,
     17     divide,
     18     pow,
     19     isZero,
     20     equal,
     21     resolve,
     22     simplifyCore,
     23     fraction,
     24     bignumber,
     25     mathWithTransform,
     26     matrix,
     27     AccessorNode,
     28     ArrayNode,
     29     ConstantNode,
     30     FunctionNode,
     31     IndexNode,
     32     ObjectNode,
     33     OperatorNode,
     34     ParenthesisNode,
     35     SymbolNode
     36   } = _ref;
     37   var simplifyConstant = createSimplifyConstant({
     38     typed,
     39     config,
     40     mathWithTransform,
     41     matrix,
     42     fraction,
     43     bignumber,
     44     AccessorNode,
     45     ArrayNode,
     46     ConstantNode,
     47     FunctionNode,
     48     IndexNode,
     49     ObjectNode,
     50     OperatorNode,
     51     SymbolNode
     52   });
     53   var {
     54     hasProperty,
     55     isCommutative,
     56     isAssociative,
     57     mergeContext,
     58     flatten,
     59     unflattenr,
     60     unflattenl,
     61     createMakeNodeFunction,
     62     defaultContext,
     63     realContext,
     64     positiveContext
     65   } = createUtil({
     66     FunctionNode,
     67     OperatorNode,
     68     SymbolNode
     69   });
     70   /**
     71    * Simplify an expression tree.
     72    *
     73    * A list of rules are applied to an expression, repeating over the list until
     74    * no further changes are made.
     75    * It's possible to pass a custom set of rules to the function as second
     76    * argument. A rule can be specified as an object, string, or function:
     77    *
     78    *     const rules = [
     79    *       { l: 'n1*n3 + n2*n3', r: '(n1+n2)*n3' },
     80    *       'n1*n3 + n2*n3 -> (n1+n2)*n3',
     81    *       function (node) {
     82    *         // ... return a new node or return the node unchanged
     83    *         return node
     84    *       }
     85    *     ]
     86    *
     87    * String and object rules consist of a left and right pattern. The left is
     88    * used to match against the expression and the right determines what matches
     89    * are replaced with. The main difference between a pattern and a normal
     90    * expression is that variables starting with the following characters are
     91    * interpreted as wildcards:
     92    *
     93    * - 'n' - matches any Node
     94    * - 'c' - matches any ConstantNode
     95    * - 'v' - matches any Node that is not a ConstantNode
     96    *
     97    * The default list of rules is exposed on the function as `simplify.rules`
     98    * and can be used as a basis to built a set of custom rules.
     99    *
    100    * To specify a rule as a string, separate the left and right pattern by '->'
    101    * When specifying a rule as an object, the following keys are meaningful:
    102    * - l - the left pattern
    103    * - r - the right pattern
    104    * - s - in lieu of l and r, the string form that is broken at -> to give them
    105    * - repeat - whether to repeat this rule until the expression stabilizes
    106    * - assuming - gives a context object, as in the 'context' option to
    107    *     simplify. Every property in the context object must match the current
    108    *     context in order, or else the rule will not be applied.
    109    * - imposeContext - gives a context object, as in the 'context' option to
    110    *     simplify. Any settings specified will override the incoming context
    111    *     for all matches of this rule.
    112    *
    113    * For more details on the theory, see:
    114    *
    115    * - [Strategies for simplifying math expressions (Stackoverflow)](https://stackoverflow.com/questions/7540227/strategies-for-simplifying-math-expressions)
    116    * - [Symbolic computation - Simplification (Wikipedia)](https://en.wikipedia.org/wiki/Symbolic_computation#Simplification)
    117    *
    118    *  An optional `options` argument can be passed as last argument of `simplify`.
    119    *  Currently available options (defaults in parentheses):
    120    *  - `consoleDebug` (false): whether to write the expression being simplified
    121    *    and any changes to it, along with the rule responsible, to console
    122    *  - `context` (simplify.defaultContext): an object giving properties of
    123    *    each operator, which determine what simplifications are allowed. The
    124    *    currently meaningful properties are commutative, associative,
    125    *    total (whether the operation is defined for all arguments), and
    126    *    trivial (whether the operation applied to a single argument leaves
    127    *    that argument unchanged). The default context is very permissive and
    128    *    allows almost all simplifications. Only properties differing from
    129    *    the default need to be specified; the default context is used as a
    130    *    fallback. Additional contexts `simplify.realContext` and
    131    *    `simplify.positiveContext` are supplied to cause simplify to perform
    132    *    just simplifications guaranteed to preserve all values of the expression
    133    *    assuming all variables and subexpressions are real numbers or
    134    *    positive real numbers, respectively. (Note that these are in some cases
    135    *    more restrictive than the default context; for example, the default
    136    *    context will allow `x/x` to simplify to 1, whereas
    137    *    `simplify.realContext` will not, as `0/0` is not equal to 1.)
    138    *  - `exactFractions` (true): whether to try to convert all constants to
    139    *    exact rational numbers.
    140    *  - `fractionsLimit` (10000): when `exactFractions` is true, constants will
    141    *    be expressed as fractions only when both numerator and denominator
    142    *    are smaller than `fractionsLimit`.
    143    *
    144    * Syntax:
    145    *
    146    *     simplify(expr)
    147    *     simplify(expr, rules)
    148    *     simplify(expr, rules)
    149    *     simplify(expr, rules, scope)
    150    *     simplify(expr, rules, scope, options)
    151    *     simplify(expr, scope)
    152    *     simplify(expr, scope, options)
    153    *
    154    * Examples:
    155    *
    156    *     math.simplify('2 * 1 * x ^ (2 - 1)')      // Node "2 * x"
    157    *     math.simplify('2 * 3 * x', {x: 4})        // Node "24"
    158    *     const f = math.parse('2 * 1 * x ^ (2 - 1)')
    159    *     math.simplify(f)                          // Node "2 * x"
    160    *     math.simplify('0.4 * x', {}, {exactFractions: true})  // Node "x * 2 / 5"
    161    *     math.simplify('0.4 * x', {}, {exactFractions: false}) // Node "0.4 * x"
    162    *
    163    * See also:
    164    *
    165    *     simplifyCore, derivative, evaluate, parse, rationalize, resolve
    166    *
    167    * @param {Node | string} expr
    168    *            The expression to be simplified
    169    * @param {Array<{l:string, r: string} | string | function>} [rules]
    170    *            Optional list with custom rules
    171    * @return {Node} Returns the simplified form of `expr`
    172    */
    173 
    174   var simplify = typed('simplify', {
    175     string: function string(expr) {
    176       return this(parse(expr), this.rules, createEmptyMap(), {});
    177     },
    178     'string, Map | Object': function stringMapObject(expr, scope) {
    179       return this(parse(expr), this.rules, scope, {});
    180     },
    181     'string, Map | Object, Object': function stringMapObjectObject(expr, scope, options) {
    182       return this(parse(expr), this.rules, scope, options);
    183     },
    184     'string, Array': function stringArray(expr, rules) {
    185       return this(parse(expr), rules, createEmptyMap(), {});
    186     },
    187     'string, Array, Map | Object': function stringArrayMapObject(expr, rules, scope) {
    188       return this(parse(expr), rules, scope, {});
    189     },
    190     'string, Array, Map | Object, Object': function stringArrayMapObjectObject(expr, rules, scope, options) {
    191       return this(parse(expr), rules, scope, options);
    192     },
    193     'Node, Map | Object': function NodeMapObject(expr, scope) {
    194       return this(expr, this.rules, scope, {});
    195     },
    196     'Node, Map | Object, Object': function NodeMapObjectObject(expr, scope, options) {
    197       return this(expr, this.rules, scope, options);
    198     },
    199     Node: function Node(expr) {
    200       return this(expr, this.rules, createEmptyMap(), {});
    201     },
    202     'Node, Array': function NodeArray(expr, rules) {
    203       return this(expr, rules, createEmptyMap(), {});
    204     },
    205     'Node, Array, Map | Object': function NodeArrayMapObject(expr, rules, scope) {
    206       return this(expr, rules, scope, {});
    207     },
    208     'Node, Array, Object, Object': function NodeArrayObjectObject(expr, rules, scope, options) {
    209       return this(expr, rules, createMap(scope), options);
    210     },
    211     'Node, Array, Map, Object': function NodeArrayMapObject(expr, rules, scope, options) {
    212       var debug = options.consoleDebug;
    213       rules = _buildRules(rules, options.context);
    214       var res = resolve(expr, scope);
    215       res = removeParens(res);
    216       var visited = {};
    217       var str = res.toString({
    218         parenthesis: 'all'
    219       });
    220 
    221       while (!visited[str]) {
    222         visited[str] = true;
    223         _lastsym = 0; // counter for placeholder symbols
    224 
    225         var laststr = str;
    226         if (debug) console.log('Working on: ', str);
    227 
    228         for (var i = 0; i < rules.length; i++) {
    229           var rulestr = '';
    230 
    231           if (typeof rules[i] === 'function') {
    232             res = rules[i](res, options);
    233             if (debug) rulestr = rules[i].name;
    234           } else {
    235             flatten(res, options.context);
    236             res = applyRule(res, rules[i], options.context);
    237 
    238             if (debug) {
    239               rulestr = "".concat(rules[i].l.toString(), " -> ").concat(rules[i].r.toString());
    240             }
    241           }
    242 
    243           if (debug) {
    244             var newstr = res.toString({
    245               parenthesis: 'all'
    246             });
    247 
    248             if (newstr !== laststr) {
    249               console.log('Applying', rulestr, 'produced', newstr);
    250               laststr = newstr;
    251             }
    252           }
    253           /* Use left-heavy binary tree internally,
    254            * since custom rule functions may expect it
    255            */
    256 
    257 
    258           unflattenl(res, options.context);
    259         }
    260 
    261         str = res.toString({
    262           parenthesis: 'all'
    263         });
    264       }
    265 
    266       return res;
    267     }
    268   });
    269   simplify.defaultContext = defaultContext;
    270   simplify.realContext = realContext;
    271   simplify.positiveContext = positiveContext;
    272 
    273   function removeParens(node) {
    274     return node.transform(function (node, path, parent) {
    275       return isParenthesisNode(node) ? removeParens(node.content) : node;
    276     });
    277   } // All constants that are allowed in rules
    278 
    279 
    280   var SUPPORTED_CONSTANTS = {
    281     true: true,
    282     false: true,
    283     e: true,
    284     i: true,
    285     Infinity: true,
    286     LN2: true,
    287     LN10: true,
    288     LOG2E: true,
    289     LOG10E: true,
    290     NaN: true,
    291     phi: true,
    292     pi: true,
    293     SQRT1_2: true,
    294     SQRT2: true,
    295     tau: true // null: false,
    296     // undefined: false,
    297     // version: false,
    298 
    299   }; // Array of strings, used to build the ruleSet.
    300   // Each l (left side) and r (right side) are parsed by
    301   // the expression parser into a node tree.
    302   // Left hand sides are matched to subtrees within the
    303   // expression to be parsed and replaced with the right
    304   // hand side.
    305   // TODO: Add support for constraints on constants (either in the form of a '=' expression or a callback [callback allows things like comparing symbols alphabetically])
    306   // To evaluate lhs constants for rhs constants, use: { l: 'c1+c2', r: 'c3', evaluate: 'c3 = c1 + c2' }. Multiple assignments are separated by ';' in block format.
    307   // It is possible to get into an infinite loop with conflicting rules
    308 
    309   simplify.rules = [simplifyCore, // { l: 'n+0', r: 'n' },     // simplifyCore
    310   // { l: 'n^0', r: '1' },     // simplifyCore
    311   // { l: '0*n', r: '0' },     // simplifyCore
    312   // { l: 'n/n', r: '1'},      // simplifyCore
    313   // { l: 'n^1', r: 'n' },     // simplifyCore
    314   // { l: '+n1', r:'n1' },     // simplifyCore
    315   // { l: 'n--n1', r:'n+n1' }, // simplifyCore
    316   {
    317     l: 'log(e)',
    318     r: '1'
    319   }, // temporary rules
    320   // Note initially we tend constants to the right because like-term
    321   // collection prefers the left, and we would rather collect nonconstants
    322   {
    323     s: 'n-n1 -> n+-n1',
    324     // temporarily replace 'subtract' so we can further flatten the 'add' operator
    325     assuming: {
    326       subtract: {
    327         total: true
    328       }
    329     }
    330   }, {
    331     s: 'n-n -> 0',
    332     // partial alternative when we can't always subtract
    333     assuming: {
    334       subtract: {
    335         total: false
    336       }
    337     }
    338   }, {
    339     s: '-(c*v) -> v * (-c)',
    340     // make non-constant terms positive
    341     assuming: {
    342       multiply: {
    343         commutative: true
    344       },
    345       subtract: {
    346         total: true
    347       }
    348     }
    349   }, {
    350     s: '-(c*v) -> (-c) * v',
    351     // non-commutative version, part 1
    352     assuming: {
    353       multiply: {
    354         commutative: false
    355       },
    356       subtract: {
    357         total: true
    358       }
    359     }
    360   }, {
    361     s: '-(v*c) -> v * (-c)',
    362     // non-commutative version, part 2
    363     assuming: {
    364       multiply: {
    365         commutative: false
    366       },
    367       subtract: {
    368         total: true
    369       }
    370     }
    371   }, {
    372     l: '-(n1/n2)',
    373     r: '-n1/n2'
    374   }, {
    375     l: '-v',
    376     r: 'v * (-1)'
    377   }, // finish making non-constant terms positive
    378   {
    379     l: '(n1 + n2)*(-1)',
    380     r: 'n1*(-1) + n2*(-1)',
    381     repeat: true
    382   }, // expand negations to achieve as much sign cancellation as possible
    383   {
    384     l: 'n/n1^n2',
    385     r: 'n*n1^-n2'
    386   }, // temporarily replace 'divide' so we can further flatten the 'multiply' operator
    387   {
    388     l: 'n/n1',
    389     r: 'n*n1^-1'
    390   }, {
    391     s: '(n1*n2)^n3 -> n1^n3 * n2^n3',
    392     assuming: {
    393       multiply: {
    394         commutative: true
    395       }
    396     }
    397   }, {
    398     s: '(n1*n2)^(-1) -> n2^(-1) * n1^(-1)',
    399     assuming: {
    400       multiply: {
    401         commutative: false
    402       }
    403     }
    404   }, // expand nested exponentiation
    405   {
    406     s: '(n ^ n1) ^ n2 -> n ^ (n1 * n2)',
    407     assuming: {
    408       divide: {
    409         total: true
    410       }
    411     } // 1/(1/n) = n needs 1/n to exist
    412 
    413   }, // collect like factors; into a sum, only do this for nonconstants
    414   {
    415     l: ' v   * ( v   * n1 + n2)',
    416     r: 'v^2       * n1 +  v   * n2'
    417   }, {
    418     s: ' v   * (v^n4 * n1 + n2)   ->  v^(1+n4)  * n1 +  v   * n2',
    419     assuming: {
    420       divide: {
    421         total: true
    422       }
    423     } // v*1/v = v^(1+-1) needs 1/v
    424 
    425   }, {
    426     s: 'v^n3 * ( v   * n1 + n2)   ->  v^(n3+1)  * n1 + v^n3 * n2',
    427     assuming: {
    428       divide: {
    429         total: true
    430       }
    431     }
    432   }, {
    433     s: 'v^n3 * (v^n4 * n1 + n2)   ->  v^(n3+n4) * n1 + v^n3 * n2',
    434     assuming: {
    435       divide: {
    436         total: true
    437       }
    438     }
    439   }, {
    440     l: 'n*n',
    441     r: 'n^2'
    442   }, {
    443     s: 'n * n^n1 -> n^(n1+1)',
    444     assuming: {
    445       divide: {
    446         total: true
    447       }
    448     } // n*1/n = n^(-1+1) needs 1/n
    449 
    450   }, {
    451     s: 'n^n1 * n^n2 -> n^(n1+n2)',
    452     assuming: {
    453       divide: {
    454         total: true
    455       }
    456     } // ditto for n^2*1/n^2
    457 
    458   }, // Unfortunately, to deal with more complicated cancellations, it
    459   // becomes necessary to simplify constants twice per pass. It's not
    460   // terribly expensive compared to matching rules, so this should not
    461   // pose a performance problem.
    462   simplifyConstant, // First: before collecting like terms
    463   // collect like terms
    464   {
    465     s: 'n+n -> 2*n',
    466     assuming: {
    467       add: {
    468         total: true
    469       }
    470     } // 2 = 1 + 1 needs to exist
    471 
    472   }, {
    473     l: 'n+-n',
    474     r: '0'
    475   }, {
    476     l: 'v*n + v',
    477     r: 'v*(n+1)'
    478   }, // NOTE: leftmost position is special:
    479   {
    480     l: 'n3*n1 + n3*n2',
    481     r: 'n3*(n1+n2)'
    482   }, // All sub-monomials tried there.
    483   {
    484     l: 'n3^(-n4)*n1 +   n3  * n2',
    485     r: 'n3^(-n4)*(n1 + n3^(n4+1) *n2)'
    486   }, {
    487     l: 'n3^(-n4)*n1 + n3^n5 * n2',
    488     r: 'n3^(-n4)*(n1 + n3^(n4+n5)*n2)'
    489   }, {
    490     s: 'n*v + v -> (n+1)*v',
    491     // noncommutative additional cases
    492     assuming: {
    493       multiply: {
    494         commutative: false
    495       }
    496     }
    497   }, {
    498     s: 'n1*n3 + n2*n3 -> (n1+n2)*n3',
    499     assuming: {
    500       multiply: {
    501         commutative: false
    502       }
    503     }
    504   }, {
    505     s: 'n1*n3^(-n4) + n2 * n3    -> (n1 + n2*n3^(n4 +  1))*n3^(-n4)',
    506     assuming: {
    507       multiply: {
    508         commutative: false
    509       }
    510     }
    511   }, {
    512     s: 'n1*n3^(-n4) + n2 * n3^n5 -> (n1 + n2*n3^(n4 + n5))*n3^(-n4)',
    513     assuming: {
    514       multiply: {
    515         commutative: false
    516       }
    517     }
    518   }, {
    519     l: 'n*c + c',
    520     r: '(n+1)*c'
    521   }, {
    522     s: 'c*n + c -> c*(n+1)',
    523     assuming: {
    524       multiply: {
    525         commutative: false
    526       }
    527     }
    528   }, simplifyConstant, // Second: before returning expressions to "standard form"
    529   // make factors positive (and undo 'make non-constant terms positive')
    530   {
    531     s: '(-n)*n1 -> -(n*n1)',
    532     assuming: {
    533       subtract: {
    534         total: true
    535       }
    536     }
    537   }, {
    538     s: 'n1*(-n) -> -(n1*n)',
    539     // in case * non-commutative
    540     assuming: {
    541       subtract: {
    542         total: true
    543       },
    544       multiply: {
    545         commutative: false
    546       }
    547     }
    548   }, // final ordering of constants
    549   {
    550     s: 'c+v -> v+c',
    551     assuming: {
    552       add: {
    553         commutative: true
    554       }
    555     },
    556     imposeContext: {
    557       add: {
    558         commutative: false
    559       }
    560     }
    561   }, {
    562     s: 'v*c -> c*v',
    563     assuming: {
    564       multiply: {
    565         commutative: true
    566       }
    567     },
    568     imposeContext: {
    569       multiply: {
    570         commutative: false
    571       }
    572     }
    573   }, // undo temporary rules
    574   // { l: '(-1) * n', r: '-n' }, // #811 added test which proved this is redundant
    575   {
    576     l: 'n+-n1',
    577     r: 'n-n1'
    578   }, // undo replace 'subtract'
    579   {
    580     s: 'n*(n1^-1) -> n/n1',
    581     // undo replace 'divide'; for * commutative
    582     assuming: {
    583       multiply: {
    584         commutative: true
    585       }
    586     } // o.w. / not conventional
    587 
    588   }, {
    589     s: 'n*n1^-n2 -> n/n1^n2',
    590     assuming: {
    591       multiply: {
    592         commutative: true
    593       }
    594     } // o.w. / not conventional
    595 
    596   }, {
    597     s: 'n^-1 -> 1/n',
    598     assuming: {
    599       multiply: {
    600         commutative: true
    601       }
    602     } // o.w. / not conventional
    603 
    604   }, {
    605     l: 'n^1',
    606     r: 'n'
    607   }, // can be produced by power cancellation
    608   {
    609     s: 'n*(n1/n2) -> (n*n1)/n2',
    610     // '*' before '/'
    611     assuming: {
    612       multiply: {
    613         associative: true
    614       }
    615     }
    616   }, {
    617     s: 'n-(n1+n2) -> n-n1-n2',
    618     // '-' before '+'
    619     assuming: {
    620       addition: {
    621         associative: true,
    622         commutative: true
    623       }
    624     }
    625   }, // { l: '(n1/n2)/n3', r: 'n1/(n2*n3)' },
    626   // { l: '(n*n1)/(n*n2)', r: 'n1/n2' },
    627   // simplifyConstant can leave an extra factor of 1, which can always
    628   // be eliminated, since the identity always commutes
    629   {
    630     l: '1*n',
    631     r: 'n',
    632     imposeContext: {
    633       multiply: {
    634         commutative: true
    635       }
    636     }
    637   }, {
    638     s: 'n1/(n2/n3) -> (n1*n3)/n2',
    639     assuming: {
    640       multiply: {
    641         associative: true
    642       }
    643     }
    644   }, {
    645     l: 'n1/(-n2)',
    646     r: '-n1/n2'
    647   }];
    648   /**
    649    * Takes any rule object as allowed by the specification in simplify
    650    * and puts it in a standard form used by applyRule
    651    */
    652 
    653   function _canonicalizeRule(ruleObject, context) {
    654     var newRule = {};
    655 
    656     if (ruleObject.s) {
    657       var lr = ruleObject.s.split('->');
    658 
    659       if (lr.length === 2) {
    660         newRule.l = lr[0];
    661         newRule.r = lr[1];
    662       } else {
    663         throw SyntaxError('Could not parse rule: ' + ruleObject.s);
    664       }
    665     } else {
    666       newRule.l = ruleObject.l;
    667       newRule.r = ruleObject.r;
    668     }
    669 
    670     newRule.l = removeParens(parse(newRule.l));
    671     newRule.r = removeParens(parse(newRule.r));
    672 
    673     for (var prop of ['imposeContext', 'repeat', 'assuming']) {
    674       if (prop in ruleObject) {
    675         newRule[prop] = ruleObject[prop];
    676       }
    677     }
    678 
    679     if (ruleObject.evaluate) {
    680       newRule.evaluate = parse(ruleObject.evaluate);
    681     }
    682 
    683     if (isAssociative(newRule.l, context)) {
    684       var makeNode = createMakeNodeFunction(newRule.l);
    685 
    686       var expandsym = _getExpandPlaceholderSymbol();
    687 
    688       newRule.expanded = {};
    689       newRule.expanded.l = makeNode([newRule.l.clone(), expandsym]); // Push the expandsym into the deepest possible branch.
    690       // This helps to match the newRule against nodes returned from getSplits() later on.
    691 
    692       flatten(newRule.expanded.l, context);
    693       unflattenr(newRule.expanded.l, context);
    694       newRule.expanded.r = makeNode([newRule.r, expandsym]);
    695     }
    696 
    697     return newRule;
    698   }
    699   /**
    700    * Parse the string array of rules into nodes
    701    *
    702    * Example syntax for rules:
    703    *
    704    * Position constants to the left in a product:
    705    * { l: 'n1 * c1', r: 'c1 * n1' }
    706    * n1 is any Node, and c1 is a ConstantNode.
    707    *
    708    * Apply difference of squares formula:
    709    * { l: '(n1 - n2) * (n1 + n2)', r: 'n1^2 - n2^2' }
    710    * n1, n2 mean any Node.
    711    *
    712    * Short hand notation:
    713    * 'n1 * c1 -> c1 * n1'
    714    */
    715 
    716 
    717   function _buildRules(rules, context) {
    718     // Array of rules to be used to simplify expressions
    719     var ruleSet = [];
    720 
    721     for (var i = 0; i < rules.length; i++) {
    722       var rule = rules[i];
    723       var newRule = void 0;
    724       var ruleType = typeof rule;
    725 
    726       switch (ruleType) {
    727         case 'string':
    728           rule = {
    729             s: rule
    730           };
    731 
    732         /* falls through */
    733 
    734         case 'object':
    735           newRule = _canonicalizeRule(rule, context);
    736           break;
    737 
    738         case 'function':
    739           newRule = rule;
    740           break;
    741 
    742         default:
    743           throw TypeError('Unsupported type of rule: ' + ruleType);
    744       } // console.log('Adding rule: ' + rules[i])
    745       // console.log(newRule)
    746 
    747 
    748       ruleSet.push(newRule);
    749     }
    750 
    751     return ruleSet;
    752   }
    753 
    754   var _lastsym = 0;
    755 
    756   function _getExpandPlaceholderSymbol() {
    757     return new SymbolNode('_p' + _lastsym++);
    758   }
    759 
    760   function mapRule(nodes, rule, context) {
    761     var resNodes = nodes;
    762 
    763     if (nodes) {
    764       for (var i = 0; i < nodes.length; ++i) {
    765         var newNode = applyRule(nodes[i], rule, context);
    766 
    767         if (newNode !== nodes[i]) {
    768           if (resNodes === nodes) {
    769             resNodes = nodes.slice();
    770           }
    771 
    772           resNodes[i] = newNode;
    773         }
    774       }
    775     }
    776 
    777     return resNodes;
    778   }
    779   /**
    780    * Returns a simplfied form of node, or the original node if no simplification was possible.
    781    *
    782    * @param  {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node
    783    * @param  {Object | Function} rule
    784    * @param  {Object} context -- information about assumed properties of operators
    785    * @return {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} The simplified form of `expr`, or the original node if no simplification was possible.
    786    */
    787 
    788 
    789   function applyRule(node, rule, context) {
    790     //    console.log('Entering applyRule("', rule.l.toString({parenthesis:'all'}), '->', rule.r.toString({parenthesis:'all'}), '",', node.toString({parenthesis:'all'}),')')
    791     // check that the assumptions for this rule are satisfied by the current
    792     // context:
    793     if (rule.assuming) {
    794       for (var symbol in rule.assuming) {
    795         for (var property in rule.assuming[symbol]) {
    796           if (hasProperty(symbol, property, context) !== rule.assuming[symbol][property]) {
    797             return node;
    798           }
    799         }
    800       }
    801     }
    802 
    803     var mergedContext = mergeContext(rule.imposeContext, context); // Do not clone node unless we find a match
    804 
    805     var res = node; // First replace our child nodes with their simplified versions
    806     // If a child could not be simplified, applying the rule to it
    807     // will have no effect since the node is returned unchanged
    808 
    809     if (res instanceof OperatorNode || res instanceof FunctionNode) {
    810       var newArgs = mapRule(res.args, rule, context);
    811 
    812       if (newArgs !== res.args) {
    813         res = res.clone();
    814         res.args = newArgs;
    815       }
    816     } else if (res instanceof ParenthesisNode) {
    817       if (res.content) {
    818         var newContent = applyRule(res.content, rule, context);
    819 
    820         if (newContent !== res.content) {
    821           res = new ParenthesisNode(newContent);
    822         }
    823       }
    824     } else if (res instanceof ArrayNode) {
    825       var newItems = mapRule(res.items, rule, context);
    826 
    827       if (newItems !== res.items) {
    828         res = new ArrayNode(newItems);
    829       }
    830     } else if (res instanceof AccessorNode) {
    831       var newObj = res.object;
    832 
    833       if (res.object) {
    834         newObj = applyRule(res.object, rule, context);
    835       }
    836 
    837       var newIndex = res.index;
    838 
    839       if (res.index) {
    840         newIndex = applyRule(res.index, rule, context);
    841       }
    842 
    843       if (newObj !== res.object || newIndex !== res.index) {
    844         res = new AccessorNode(newObj, newIndex);
    845       }
    846     } else if (res instanceof IndexNode) {
    847       var newDims = mapRule(res.dimensions, rule, context);
    848 
    849       if (newDims !== res.dimensions) {
    850         res = new IndexNode(newDims);
    851       }
    852     } else if (res instanceof ObjectNode) {
    853       var changed = false;
    854       var newProps = {};
    855 
    856       for (var prop in res.properties) {
    857         newProps[prop] = applyRule(res.properties[prop], rule, context);
    858 
    859         if (newProps[prop] !== res.properties[prop]) {
    860           changed = true;
    861         }
    862       }
    863 
    864       if (changed) {
    865         res = new ObjectNode(newProps);
    866       }
    867     } // Try to match a rule against this node
    868 
    869 
    870     var repl = rule.r;
    871 
    872     var matches = _ruleMatch(rule.l, res, mergedContext)[0]; // If the rule is associative operator, we can try matching it while allowing additional terms.
    873     // This allows us to match rules like 'n+n' to the expression '(1+x)+x' or even 'x+1+x' if the operator is commutative.
    874 
    875 
    876     if (!matches && rule.expanded) {
    877       repl = rule.expanded.r;
    878       matches = _ruleMatch(rule.expanded.l, res, mergedContext)[0];
    879     }
    880 
    881     if (matches) {
    882       // const before = res.toString({parenthesis: 'all'})
    883       // Create a new node by cloning the rhs of the matched rule
    884       // we keep any implicit multiplication state if relevant
    885       var implicit = res.implicit;
    886       res = repl.clone();
    887 
    888       if (implicit && 'implicit' in repl) {
    889         res.implicit = true;
    890       } // Replace placeholders with their respective nodes without traversing deeper into the replaced nodes
    891 
    892 
    893       res = res.transform(function (node) {
    894         if (node.isSymbolNode && hasOwnProperty(matches.placeholders, node.name)) {
    895           return matches.placeholders[node.name].clone();
    896         } else {
    897           return node;
    898         }
    899       }); // const after = res.toString({parenthesis: 'all'})
    900       // console.log('Simplified ' + before + ' to ' + after)
    901     }
    902 
    903     if (rule.repeat && res !== node) {
    904       res = applyRule(res, rule, context);
    905     }
    906 
    907     return res;
    908   }
    909   /**
    910    * Get (binary) combinations of a flattened binary node
    911    * e.g. +(node1, node2, node3) -> [
    912    *        +(node1,  +(node2, node3)),
    913    *        +(node2,  +(node1, node3)),
    914    *        +(node3,  +(node1, node2))]
    915    *
    916    */
    917 
    918 
    919   function getSplits(node, context) {
    920     var res = [];
    921     var right, rightArgs;
    922     var makeNode = createMakeNodeFunction(node);
    923 
    924     if (isCommutative(node, context)) {
    925       for (var i = 0; i < node.args.length; i++) {
    926         rightArgs = node.args.slice(0);
    927         rightArgs.splice(i, 1);
    928         right = rightArgs.length === 1 ? rightArgs[0] : makeNode(rightArgs);
    929         res.push(makeNode([node.args[i], right]));
    930       }
    931     } else {
    932       // Keep order, but try all parenthesizations
    933       for (var _i = 1; _i < node.args.length; _i++) {
    934         var left = node.args[0];
    935 
    936         if (_i > 1) {
    937           left = makeNode(node.args.slice(0, _i));
    938         }
    939 
    940         rightArgs = node.args.slice(_i);
    941         right = rightArgs.length === 1 ? rightArgs[0] : makeNode(rightArgs);
    942         res.push(makeNode([left, right]));
    943       }
    944     }
    945 
    946     return res;
    947   }
    948   /**
    949    * Returns the set union of two match-placeholders or null if there is a conflict.
    950    */
    951 
    952 
    953   function mergeMatch(match1, match2) {
    954     var res = {
    955       placeholders: {}
    956     }; // Some matches may not have placeholders; this is OK
    957 
    958     if (!match1.placeholders && !match2.placeholders) {
    959       return res;
    960     } else if (!match1.placeholders) {
    961       return match2;
    962     } else if (!match2.placeholders) {
    963       return match1;
    964     } // Placeholders with the same key must match exactly
    965 
    966 
    967     for (var key in match1.placeholders) {
    968       if (hasOwnProperty(match1.placeholders, key)) {
    969         res.placeholders[key] = match1.placeholders[key];
    970 
    971         if (hasOwnProperty(match2.placeholders, key)) {
    972           if (!_exactMatch(match1.placeholders[key], match2.placeholders[key])) {
    973             return null;
    974           }
    975         }
    976       }
    977     }
    978 
    979     for (var _key in match2.placeholders) {
    980       if (hasOwnProperty(match2.placeholders, _key)) {
    981         res.placeholders[_key] = match2.placeholders[_key];
    982       }
    983     }
    984 
    985     return res;
    986   }
    987   /**
    988    * Combine two lists of matches by applying mergeMatch to the cartesian product of two lists of matches.
    989    * Each list represents matches found in one child of a node.
    990    */
    991 
    992 
    993   function combineChildMatches(list1, list2) {
    994     var res = [];
    995 
    996     if (list1.length === 0 || list2.length === 0) {
    997       return res;
    998     }
    999 
   1000     var merged;
   1001 
   1002     for (var i1 = 0; i1 < list1.length; i1++) {
   1003       for (var i2 = 0; i2 < list2.length; i2++) {
   1004         merged = mergeMatch(list1[i1], list2[i2]);
   1005 
   1006         if (merged) {
   1007           res.push(merged);
   1008         }
   1009       }
   1010     }
   1011 
   1012     return res;
   1013   }
   1014   /**
   1015    * Combine multiple lists of matches by applying mergeMatch to the cartesian product of two lists of matches.
   1016    * Each list represents matches found in one child of a node.
   1017    * Returns a list of unique matches.
   1018    */
   1019 
   1020 
   1021   function mergeChildMatches(childMatches) {
   1022     if (childMatches.length === 0) {
   1023       return childMatches;
   1024     }
   1025 
   1026     var sets = childMatches.reduce(combineChildMatches);
   1027     var uniqueSets = [];
   1028     var unique = {};
   1029 
   1030     for (var i = 0; i < sets.length; i++) {
   1031       var s = JSON.stringify(sets[i]);
   1032 
   1033       if (!unique[s]) {
   1034         unique[s] = true;
   1035         uniqueSets.push(sets[i]);
   1036       }
   1037     }
   1038 
   1039     return uniqueSets;
   1040   }
   1041   /**
   1042    * Determines whether node matches rule.
   1043    *
   1044    * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} rule
   1045    * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} node
   1046    * @param {Object} context -- provides assumed properties of operators
   1047    * @param {Boolean} isSplit -- whether we are in process of splitting an
   1048    *                    n-ary operator node into possible binary combinations.
   1049    *                    Defaults to false.
   1050    * @return {Object} Information about the match, if it exists.
   1051    */
   1052 
   1053 
   1054   function _ruleMatch(rule, node, context, isSplit) {
   1055     //    console.log('Entering _ruleMatch(' + JSON.stringify(rule) + ', ' + JSON.stringify(node) + ')')
   1056     //    console.log('rule = ' + rule)
   1057     //    console.log('node = ' + node)
   1058     //    console.log('Entering _ruleMatch(', rule.toString({parenthesis:'all'}), ', ', node.toString({parenthesis:'all'}), ', ', context, ')')
   1059     var res = [{
   1060       placeholders: {}
   1061     }];
   1062 
   1063     if (rule instanceof OperatorNode && node instanceof OperatorNode || rule instanceof FunctionNode && node instanceof FunctionNode) {
   1064       // If the rule is an OperatorNode or a FunctionNode, then node must match exactly
   1065       if (rule instanceof OperatorNode) {
   1066         if (rule.op !== node.op || rule.fn !== node.fn) {
   1067           return [];
   1068         }
   1069       } else if (rule instanceof FunctionNode) {
   1070         if (rule.name !== node.name) {
   1071           return [];
   1072         }
   1073       } // rule and node match. Search the children of rule and node.
   1074 
   1075 
   1076       if (node.args.length === 1 && rule.args.length === 1 || !isAssociative(node, context) && node.args.length === rule.args.length || isSplit) {
   1077         // Expect non-associative operators to match exactly,
   1078         // except in any order if operator is commutative
   1079         var childMatches = [];
   1080 
   1081         for (var i = 0; i < rule.args.length; i++) {
   1082           var childMatch = _ruleMatch(rule.args[i], node.args[i], context);
   1083 
   1084           if (childMatch.length === 0) {
   1085             // Child did not match, so stop searching immediately
   1086             break;
   1087           } // The child matched, so add the information returned from the child to our result
   1088 
   1089 
   1090           childMatches.push(childMatch);
   1091         }
   1092 
   1093         if (childMatches.length !== rule.args.length) {
   1094           if (!isCommutative(node, context) || // exact match in order needed
   1095           rule.args.length === 1) {
   1096             // nothing to commute
   1097             return [];
   1098           }
   1099 
   1100           if (rule.args.length > 2) {
   1101             /* Need to generate all permutations and try them.
   1102              * It's a bit complicated, and unlikely to come up since there
   1103              * are very few ternary or higher operators. So punt for now.
   1104              */
   1105             throw new Error('permuting >2 commutative non-associative rule arguments not yet implemented');
   1106           }
   1107           /* Exactly two arguments, try them reversed */
   1108 
   1109 
   1110           var leftMatch = _ruleMatch(rule.args[0], node.args[1], context);
   1111 
   1112           if (leftMatch.length === 0) {
   1113             return [];
   1114           }
   1115 
   1116           var rightMatch = _ruleMatch(rule.args[1], node.args[0], context);
   1117 
   1118           if (rightMatch.length === 0) {
   1119             return [];
   1120           }
   1121 
   1122           childMatches = [leftMatch, rightMatch];
   1123         }
   1124 
   1125         res = mergeChildMatches(childMatches);
   1126       } else if (node.args.length >= 2 && rule.args.length === 2) {
   1127         // node is flattened, rule is not
   1128         // Associative operators/functions can be split in different ways so we check if the rule matches each
   1129         // them and return their union.
   1130         var splits = getSplits(node, context);
   1131         var splitMatches = [];
   1132 
   1133         for (var _i2 = 0; _i2 < splits.length; _i2++) {
   1134           var matchSet = _ruleMatch(rule, splits[_i2], context, true); // recursing at the same tree depth here
   1135 
   1136 
   1137           splitMatches = splitMatches.concat(matchSet);
   1138         }
   1139 
   1140         return splitMatches;
   1141       } else if (rule.args.length > 2) {
   1142         throw Error('Unexpected non-binary associative function: ' + rule.toString());
   1143       } else {
   1144         // Incorrect number of arguments in rule and node, so no match
   1145         return [];
   1146       }
   1147     } else if (rule instanceof SymbolNode) {
   1148       // If the rule is a SymbolNode, then it carries a special meaning
   1149       // according to the first character of the symbol node name.
   1150       // c.* matches a ConstantNode
   1151       // n.* matches any node
   1152       if (rule.name.length === 0) {
   1153         throw new Error('Symbol in rule has 0 length...!?');
   1154       }
   1155 
   1156       if (SUPPORTED_CONSTANTS[rule.name]) {
   1157         // built-in constant must match exactly
   1158         if (rule.name !== node.name) {
   1159           return [];
   1160         }
   1161       } else if (rule.name[0] === 'n' || rule.name.substring(0, 2) === '_p') {
   1162         // rule matches _anything_, so assign this node to the rule.name placeholder
   1163         // Assign node to the rule.name placeholder.
   1164         // Our parent will check for matches among placeholders.
   1165         res[0].placeholders[rule.name] = node;
   1166       } else if (rule.name[0] === 'v') {
   1167         // rule matches any variable thing (not a ConstantNode)
   1168         if (!isConstantNode(node)) {
   1169           res[0].placeholders[rule.name] = node;
   1170         } else {
   1171           // Mis-match: rule was expecting something other than a ConstantNode
   1172           return [];
   1173         }
   1174       } else if (rule.name[0] === 'c') {
   1175         // rule matches any ConstantNode
   1176         if (node instanceof ConstantNode) {
   1177           res[0].placeholders[rule.name] = node;
   1178         } else {
   1179           // Mis-match: rule was expecting a ConstantNode
   1180           return [];
   1181         }
   1182       } else {
   1183         throw new Error('Invalid symbol in rule: ' + rule.name);
   1184       }
   1185     } else if (rule instanceof ConstantNode) {
   1186       // Literal constant must match exactly
   1187       if (!equal(rule.value, node.value)) {
   1188         return [];
   1189       }
   1190     } else {
   1191       // Some other node was encountered which we aren't prepared for, so no match
   1192       return [];
   1193     } // It's a match!
   1194     // console.log('_ruleMatch(' + rule.toString() + ', ' + node.toString() + ') found a match')
   1195 
   1196 
   1197     return res;
   1198   }
   1199   /**
   1200    * Determines whether p and q (and all their children nodes) are identical.
   1201    *
   1202    * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} p
   1203    * @param {ConstantNode | SymbolNode | ParenthesisNode | FunctionNode | OperatorNode} q
   1204    * @return {Object} Information about the match, if it exists.
   1205    */
   1206 
   1207 
   1208   function _exactMatch(p, q) {
   1209     if (p instanceof ConstantNode && q instanceof ConstantNode) {
   1210       if (!equal(p.value, q.value)) {
   1211         return false;
   1212       }
   1213     } else if (p instanceof SymbolNode && q instanceof SymbolNode) {
   1214       if (p.name !== q.name) {
   1215         return false;
   1216       }
   1217     } else if (p instanceof OperatorNode && q instanceof OperatorNode || p instanceof FunctionNode && q instanceof FunctionNode) {
   1218       if (p instanceof OperatorNode) {
   1219         if (p.op !== q.op || p.fn !== q.fn) {
   1220           return false;
   1221         }
   1222       } else if (p instanceof FunctionNode) {
   1223         if (p.name !== q.name) {
   1224           return false;
   1225         }
   1226       }
   1227 
   1228       if (p.args.length !== q.args.length) {
   1229         return false;
   1230       }
   1231 
   1232       for (var i = 0; i < p.args.length; i++) {
   1233         if (!_exactMatch(p.args[i], q.args[i])) {
   1234           return false;
   1235         }
   1236       }
   1237     } else {
   1238       return false;
   1239     }
   1240 
   1241     return true;
   1242   }
   1243 
   1244   return simplify;
   1245 });