simplifyConstant.js 16 KB

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