simplify.js 40 KB

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