simplifyConstant.js 16 KB

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