simple-squiggle

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

simplify.js (38673B)


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