simple-squiggle

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

simplifyConstant.js (17268B)


      1 "use strict";
      2 
      3 var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
      4 
      5 Object.defineProperty(exports, "__esModule", {
      6   value: true
      7 });
      8 exports.createSimplifyConstant = 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("./util.js");
     17 
     18 var _noop = require("../../../utils/noop.js");
     19 
     20 function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
     21 
     22 function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
     23 
     24 function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
     25 
     26 var name = 'simplifyConstant';
     27 var dependencies = ['typed', 'config', 'mathWithTransform', 'matrix', '?fraction', '?bignumber', 'AccessorNode', 'ArrayNode', 'ConstantNode', 'FunctionNode', 'IndexNode', 'ObjectNode', 'OperatorNode', 'SymbolNode'];
     28 var createSimplifyConstant = /* #__PURE__ */(0, _factory.factory)(name, dependencies, function (_ref) {
     29   var typed = _ref.typed,
     30       config = _ref.config,
     31       mathWithTransform = _ref.mathWithTransform,
     32       matrix = _ref.matrix,
     33       fraction = _ref.fraction,
     34       bignumber = _ref.bignumber,
     35       AccessorNode = _ref.AccessorNode,
     36       ArrayNode = _ref.ArrayNode,
     37       ConstantNode = _ref.ConstantNode,
     38       FunctionNode = _ref.FunctionNode,
     39       IndexNode = _ref.IndexNode,
     40       ObjectNode = _ref.ObjectNode,
     41       OperatorNode = _ref.OperatorNode,
     42       SymbolNode = _ref.SymbolNode;
     43 
     44   var _createUtil = (0, _util.createUtil)({
     45     FunctionNode: FunctionNode,
     46     OperatorNode: OperatorNode,
     47     SymbolNode: SymbolNode
     48   }),
     49       isCommutative = _createUtil.isCommutative,
     50       isAssociative = _createUtil.isAssociative,
     51       allChildren = _createUtil.allChildren,
     52       createMakeNodeFunction = _createUtil.createMakeNodeFunction;
     53 
     54   function simplifyConstant(expr, options) {
     55     return _ensureNode(foldFraction(expr, options));
     56   }
     57 
     58   function _removeFractions(thing) {
     59     if ((0, _is.isFraction)(thing)) {
     60       return thing.valueOf();
     61     }
     62 
     63     if (thing instanceof Array) {
     64       return thing.map(_removeFractions);
     65     }
     66 
     67     if ((0, _is.isMatrix)(thing)) {
     68       return matrix(_removeFractions(thing.valueOf()));
     69     }
     70 
     71     return thing;
     72   }
     73 
     74   function _eval(fnname, args, options) {
     75     try {
     76       return mathWithTransform[fnname].apply(null, args);
     77     } catch (ignore) {
     78       // sometimes the implicit type conversion causes the evaluation to fail, so we'll try again after removing Fractions
     79       args = args.map(_removeFractions);
     80       return _toNumber(mathWithTransform[fnname].apply(null, args), options);
     81     }
     82   }
     83 
     84   var _toNode = typed({
     85     Fraction: _fractionToNode,
     86     number: function number(n) {
     87       if (n < 0) {
     88         return unaryMinusNode(new ConstantNode(-n));
     89       }
     90 
     91       return new ConstantNode(n);
     92     },
     93     BigNumber: function BigNumber(n) {
     94       if (n < 0) {
     95         return unaryMinusNode(new ConstantNode(-n));
     96       }
     97 
     98       return new ConstantNode(n); // old parameters: (n.toString(), 'number')
     99     },
    100     Complex: function Complex(s) {
    101       throw new Error('Cannot convert Complex number to Node');
    102     },
    103     string: function string(s) {
    104       return new ConstantNode(s);
    105     },
    106     Matrix: function Matrix(m) {
    107       return new ArrayNode(m.valueOf().map(function (e) {
    108         return _toNode(e);
    109       }));
    110     }
    111   });
    112 
    113   function _ensureNode(thing) {
    114     if ((0, _is.isNode)(thing)) {
    115       return thing;
    116     }
    117 
    118     return _toNode(thing);
    119   } // convert a number to a fraction only if it can be expressed exactly,
    120   // and when both numerator and denominator are small enough
    121 
    122 
    123   function _exactFraction(n, options) {
    124     var exactFractions = options && options.exactFractions !== false;
    125 
    126     if (exactFractions && isFinite(n) && fraction) {
    127       var f = fraction(n);
    128       var fractionsLimit = options && typeof options.fractionsLimit === 'number' ? options.fractionsLimit : Infinity; // no limit by default
    129 
    130       if (f.valueOf() === n && f.n < fractionsLimit && f.d < fractionsLimit) {
    131         return f;
    132       }
    133     }
    134 
    135     return n;
    136   } // Convert numbers to a preferred number type in preference order: Fraction, number, Complex
    137   // BigNumbers are left alone
    138 
    139 
    140   var _toNumber = typed({
    141     'string, Object': function stringObject(s, options) {
    142       if (config.number === 'BigNumber') {
    143         if (bignumber === undefined) {
    144           (0, _noop.noBignumber)();
    145         }
    146 
    147         return bignumber(s);
    148       } else if (config.number === 'Fraction') {
    149         if (fraction === undefined) {
    150           (0, _noop.noFraction)();
    151         }
    152 
    153         return fraction(s);
    154       } else {
    155         var n = parseFloat(s);
    156         return _exactFraction(n, options);
    157       }
    158     },
    159     'Fraction, Object': function FractionObject(s, options) {
    160       return s;
    161     },
    162     // we don't need options here
    163     'BigNumber, Object': function BigNumberObject(s, options) {
    164       return s;
    165     },
    166     // we don't need options here
    167     'number, Object': function numberObject(s, options) {
    168       return _exactFraction(s, options);
    169     },
    170     'Complex, Object': function ComplexObject(s, options) {
    171       if (s.im !== 0) {
    172         return s;
    173       }
    174 
    175       return _exactFraction(s.re, options);
    176     },
    177     'Matrix, Object': function MatrixObject(s, options) {
    178       return matrix(_exactFraction(s.valueOf()));
    179     },
    180     'Array, Object': function ArrayObject(s, options) {
    181       return s.map(_exactFraction);
    182     }
    183   });
    184 
    185   function unaryMinusNode(n) {
    186     return new OperatorNode('-', 'unaryMinus', [n]);
    187   }
    188 
    189   function _fractionToNode(f) {
    190     var n;
    191     var vn = f.s * f.n;
    192 
    193     if (vn < 0) {
    194       n = new OperatorNode('-', 'unaryMinus', [new ConstantNode(-vn)]);
    195     } else {
    196       n = new ConstantNode(vn);
    197     }
    198 
    199     if (f.d === 1) {
    200       return n;
    201     }
    202 
    203     return new OperatorNode('/', 'divide', [n, new ConstantNode(f.d)]);
    204   }
    205   /* Handles constant indexing of ArrayNodes, matrices, and ObjectNodes */
    206 
    207 
    208   function _foldAccessor(obj, index, options) {
    209     if (!(0, _is.isIndexNode)(index)) {
    210       // don't know what to do with that...
    211       return new AccessorNode(_ensureNode(obj), _ensureNode(index));
    212     }
    213 
    214     if ((0, _is.isArrayNode)(obj) || (0, _is.isMatrix)(obj)) {
    215       var remainingDims = Array.from(index.dimensions);
    216       /* We will resolve constant indices one at a time, looking
    217        * just in the first or second dimensions because (a) arrays
    218        * of more than two dimensions are likely rare, and (b) pulling
    219        * out the third or higher dimension would be pretty intricate.
    220        * The price is that we miss simplifying [..3d array][x,y,1]
    221        */
    222 
    223       while (remainingDims.length > 0) {
    224         if ((0, _is.isConstantNode)(remainingDims[0]) && typeof remainingDims[0].value !== 'string') {
    225           var first = _toNumber(remainingDims.shift().value, options);
    226 
    227           if ((0, _is.isArrayNode)(obj)) {
    228             obj = obj.items[first - 1];
    229           } else {
    230             // matrix
    231             obj = obj.valueOf()[first - 1];
    232 
    233             if (obj instanceof Array) {
    234               obj = matrix(obj);
    235             }
    236           }
    237         } else if (remainingDims.length > 1 && (0, _is.isConstantNode)(remainingDims[1]) && typeof remainingDims[1].value !== 'string') {
    238           var second = _toNumber(remainingDims[1].value, options);
    239 
    240           var tryItems = [];
    241           var fromItems = (0, _is.isArrayNode)(obj) ? obj.items : obj.valueOf();
    242 
    243           var _iterator = _createForOfIteratorHelper(fromItems),
    244               _step;
    245 
    246           try {
    247             for (_iterator.s(); !(_step = _iterator.n()).done;) {
    248               var item = _step.value;
    249 
    250               if ((0, _is.isArrayNode)(item)) {
    251                 tryItems.push(item.items[second - 1]);
    252               } else if ((0, _is.isMatrix)(obj)) {
    253                 tryItems.push(item[second - 1]);
    254               } else {
    255                 break;
    256               }
    257             }
    258           } catch (err) {
    259             _iterator.e(err);
    260           } finally {
    261             _iterator.f();
    262           }
    263 
    264           if (tryItems.length === fromItems.length) {
    265             if ((0, _is.isArrayNode)(obj)) {
    266               obj = new ArrayNode(tryItems);
    267             } else {
    268               // matrix
    269               obj = matrix(tryItems);
    270             }
    271 
    272             remainingDims.splice(1, 1);
    273           } else {
    274             // extracting slice along 2nd dimension failed, give up
    275             break;
    276           }
    277         } else {
    278           // neither 1st or 2nd dimension is constant, give up
    279           break;
    280         }
    281       }
    282 
    283       if (remainingDims.length === index.dimensions.length) {
    284         /* No successful constant indexing */
    285         return new AccessorNode(_ensureNode(obj), index);
    286       }
    287 
    288       if (remainingDims.length > 0) {
    289         /* Indexed some but not all dimensions */
    290         index = new IndexNode(remainingDims);
    291         return new AccessorNode(_ensureNode(obj), index);
    292       }
    293       /* All dimensions were constant, access completely resolved */
    294 
    295 
    296       return obj;
    297     }
    298 
    299     if ((0, _is.isObjectNode)(obj) && index.dimensions.length === 1 && (0, _is.isConstantNode)(index.dimensions[0])) {
    300       var key = index.dimensions[0].value;
    301 
    302       if (key in obj.properties) {
    303         return obj.properties[key];
    304       }
    305 
    306       return new ConstantNode(); // undefined
    307     }
    308     /* Don't know how to index this sort of obj, at least not with this index */
    309 
    310 
    311     return new AccessorNode(_ensureNode(obj), index);
    312   }
    313   /*
    314    * Create a binary tree from a list of Fractions and Nodes.
    315    * Tries to fold Fractions by evaluating them until the first Node in the list is hit, so
    316    * `args` should be sorted to have the Fractions at the start (if the operator is commutative).
    317    * @param args - list of Fractions and Nodes
    318    * @param fn - evaluator for the binary operation evaluator that accepts two Fractions
    319    * @param makeNode - creates a binary OperatorNode/FunctionNode from a list of child Nodes
    320    * if args.length is 1, returns args[0]
    321    * @return - Either a Node representing a binary expression or Fraction
    322    */
    323 
    324 
    325   function foldOp(fn, args, makeNode, options) {
    326     return args.reduce(function (a, b) {
    327       if (!(0, _is.isNode)(a) && !(0, _is.isNode)(b)) {
    328         try {
    329           return _eval(fn, [a, b], options);
    330         } catch (ignoreandcontinue) {}
    331 
    332         a = _toNode(a);
    333         b = _toNode(b);
    334       } else if (!(0, _is.isNode)(a)) {
    335         a = _toNode(a);
    336       } else if (!(0, _is.isNode)(b)) {
    337         b = _toNode(b);
    338       }
    339 
    340       return makeNode([a, b]);
    341     });
    342   } // destroys the original node and returns a folded one
    343 
    344 
    345   function foldFraction(node, options) {
    346     switch (node.type) {
    347       case 'SymbolNode':
    348         return node;
    349 
    350       case 'ConstantNode':
    351         switch ((0, _typeof2.default)(node.value)) {
    352           case 'number':
    353             return _toNumber(node.value, options);
    354 
    355           case 'string':
    356             return node.value;
    357 
    358           default:
    359             if (!isNaN(node.value)) return _toNumber(node.value, options);
    360         }
    361 
    362         return node;
    363 
    364       case 'FunctionNode':
    365         if (mathWithTransform[node.name] && mathWithTransform[node.name].rawArgs) {
    366           return node;
    367         }
    368 
    369         {
    370           // Process operators as OperatorNode
    371           var operatorFunctions = ['add', 'multiply'];
    372 
    373           if (operatorFunctions.indexOf(node.name) === -1) {
    374             var args = node.args.map(function (arg) {
    375               return foldFraction(arg, options);
    376             }); // If all args are numbers
    377 
    378             if (!args.some(_is.isNode)) {
    379               try {
    380                 return _eval(node.name, args, options);
    381               } catch (ignoreandcontinue) {}
    382             } // Size of a matrix does not depend on entries
    383 
    384 
    385             if (node.name === 'size' && args.length === 1 && (0, _is.isArrayNode)(args[0])) {
    386               var sz = [];
    387               var section = args[0];
    388 
    389               while ((0, _is.isArrayNode)(section)) {
    390                 sz.push(section.items.length);
    391                 section = section.items[0];
    392               }
    393 
    394               return matrix(sz);
    395             } // Convert all args to nodes and construct a symbolic function call
    396 
    397 
    398             return new FunctionNode(node.name, args.map(_ensureNode));
    399           } else {// treat as operator
    400           }
    401         }
    402 
    403       /* falls through */
    404 
    405       case 'OperatorNode':
    406         {
    407           var fn = node.fn.toString();
    408 
    409           var _args;
    410 
    411           var res;
    412           var makeNode = createMakeNodeFunction(node);
    413 
    414           if ((0, _is.isOperatorNode)(node) && node.isUnary()) {
    415             _args = [foldFraction(node.args[0], options)];
    416 
    417             if (!(0, _is.isNode)(_args[0])) {
    418               res = _eval(fn, _args, options);
    419             } else {
    420               res = makeNode(_args);
    421             }
    422           } else if (isAssociative(node, options.context)) {
    423             _args = allChildren(node, options.context);
    424             _args = _args.map(function (arg) {
    425               return foldFraction(arg, options);
    426             });
    427 
    428             if (isCommutative(fn, options.context)) {
    429               // commutative binary operator
    430               var consts = [];
    431               var vars = [];
    432 
    433               for (var i = 0; i < _args.length; i++) {
    434                 if (!(0, _is.isNode)(_args[i])) {
    435                   consts.push(_args[i]);
    436                 } else {
    437                   vars.push(_args[i]);
    438                 }
    439               }
    440 
    441               if (consts.length > 1) {
    442                 res = foldOp(fn, consts, makeNode, options);
    443                 vars.unshift(res);
    444                 res = foldOp(fn, vars, makeNode, options);
    445               } else {
    446                 // we won't change the children order since it's not neccessary
    447                 res = foldOp(fn, _args, makeNode, options);
    448               }
    449             } else {
    450               // non-commutative binary operator
    451               res = foldOp(fn, _args, makeNode, options);
    452             }
    453           } else {
    454             // non-associative binary operator
    455             _args = node.args.map(function (arg) {
    456               return foldFraction(arg, options);
    457             });
    458             res = foldOp(fn, _args, makeNode, options);
    459           }
    460 
    461           return res;
    462         }
    463 
    464       case 'ParenthesisNode':
    465         // remove the uneccessary parenthesis
    466         return foldFraction(node.content, options);
    467 
    468       case 'AccessorNode':
    469         return _foldAccessor(foldFraction(node.object, options), foldFraction(node.index, options), options);
    470 
    471       case 'ArrayNode':
    472         {
    473           var foldItems = node.items.map(function (item) {
    474             return foldFraction(item, options);
    475           });
    476 
    477           if (foldItems.some(_is.isNode)) {
    478             return new ArrayNode(foldItems.map(_ensureNode));
    479           }
    480           /* All literals -- return a Matrix so we can operate on it */
    481 
    482 
    483           return matrix(foldItems);
    484         }
    485 
    486       case 'IndexNode':
    487         {
    488           return new IndexNode(node.dimensions.map(function (n) {
    489             return simplifyConstant(n, options);
    490           }));
    491         }
    492 
    493       case 'ObjectNode':
    494         {
    495           var foldProps = {};
    496 
    497           for (var prop in node.properties) {
    498             foldProps[prop] = simplifyConstant(node.properties[prop], options);
    499           }
    500 
    501           return new ObjectNode(foldProps);
    502         }
    503 
    504       case 'AssignmentNode':
    505       /* falls through */
    506 
    507       case 'BlockNode':
    508       /* falls through */
    509 
    510       case 'FunctionAssignmentNode':
    511       /* falls through */
    512 
    513       case 'RangeNode':
    514       /* falls through */
    515 
    516       case 'ConditionalNode':
    517       /* falls through */
    518 
    519       default:
    520         throw new Error("Unimplemented node type in simplifyConstant: ".concat(node.type));
    521     }
    522   }
    523 
    524   return simplifyConstant;
    525 });
    526 exports.createSimplifyConstant = createSimplifyConstant;