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