1 | import { isInteger } from '../../utils/number'
|
2 | import { factory } from '../../utils/factory'
|
3 | import { createSimplifyConstant } from './simplify/simplifyConstant'
|
4 | import { createSimplifyCore } from './simplify/simplifyCore'
|
5 |
|
6 | const name = 'rationalize'
|
7 | const dependencies = [
|
8 | 'config',
|
9 | 'typed',
|
10 | 'equal',
|
11 | 'isZero',
|
12 | 'add',
|
13 | 'subtract',
|
14 | 'multiply',
|
15 | 'divide',
|
16 | 'pow',
|
17 | 'parse',
|
18 | 'simplify',
|
19 | '?bignumber',
|
20 | '?fraction',
|
21 | 'mathWithTransform',
|
22 | 'ConstantNode',
|
23 | 'OperatorNode',
|
24 | 'FunctionNode',
|
25 | 'SymbolNode',
|
26 | 'ParenthesisNode'
|
27 | ]
|
28 |
|
29 | export const createRationalize = /* #__PURE__ */ factory(name, dependencies, ({
|
30 | config,
|
31 | typed,
|
32 | equal,
|
33 | isZero,
|
34 | add,
|
35 | subtract,
|
36 | multiply,
|
37 | divide,
|
38 | pow,
|
39 | parse,
|
40 | simplify,
|
41 | fraction,
|
42 | bignumber,
|
43 | mathWithTransform,
|
44 | ConstantNode,
|
45 | OperatorNode,
|
46 | FunctionNode,
|
47 | SymbolNode,
|
48 | ParenthesisNode
|
49 | }) => {
|
50 | const simplifyConstant = createSimplifyConstant({
|
51 | typed,
|
52 | config,
|
53 | mathWithTransform,
|
54 | fraction,
|
55 | bignumber,
|
56 | ConstantNode,
|
57 | OperatorNode,
|
58 | FunctionNode,
|
59 | SymbolNode
|
60 | })
|
61 | const simplifyCore = createSimplifyCore({
|
62 | equal,
|
63 | isZero,
|
64 | add,
|
65 | subtract,
|
66 | multiply,
|
67 | divide,
|
68 | pow,
|
69 | ConstantNode,
|
70 | OperatorNode,
|
71 | FunctionNode,
|
72 | ParenthesisNode
|
73 | })
|
74 |
|
75 | /**
|
76 | * Transform a rationalizable expression in a rational fraction.
|
77 | * If rational fraction is one variable polynomial then converts
|
78 | * the numerator and denominator in canonical form, with decreasing
|
79 | * exponents, returning the coefficients of numerator.
|
80 | *
|
81 | * Syntax:
|
82 | *
|
83 | * rationalize(expr)
|
84 | * rationalize(expr, detailed)
|
85 | * rationalize(expr, scope)
|
86 | * rationalize(expr, scope, detailed)
|
87 | *
|
88 | * Examples:
|
89 | *
|
90 | * math.rationalize('sin(x)+y')
|
91 | * // Error: There is an unsolved function call
|
92 | * math.rationalize('2x/y - y/(x+1)')
|
93 | * // (2*x^2-y^2+2*x)/(x*y+y)
|
94 | * math.rationalize('(2x+1)^6')
|
95 | * // 64*x^6+192*x^5+240*x^4+160*x^3+60*x^2+12*x+1
|
96 | * math.rationalize('2x/( (2x-1) / (3x+2) ) - 5x/ ( (3x+4) / (2x^2-5) ) + 3')
|
97 | * // -20*x^4+28*x^3+104*x^2+6*x-12)/(6*x^2+5*x-4)
|
98 | * math.rationalize('x/(1-x)/(x-2)/(x-3)/(x-4) + 2x/ ( (1-2x)/(2-3x) )/ ((3-4x)/(4-5x) )') =
|
99 | * // (-30*x^7+344*x^6-1506*x^5+3200*x^4-3472*x^3+1846*x^2-381*x)/
|
100 | * // (-8*x^6+90*x^5-383*x^4+780*x^3-797*x^2+390*x-72)
|
101 | *
|
102 | * math.rationalize('x+x+x+y',{y:1}) // 3*x+1
|
103 | * math.rationalize('x+x+x+y',{}) // 3*x+y
|
104 | *
|
105 | * const ret = math.rationalize('x+x+x+y',{},true)
|
106 | * // ret.expression=3*x+y, ret.variables = ["x","y"]
|
107 | * const ret = math.rationalize('-2+5x^2',{},true)
|
108 | * // ret.expression=5*x^2-2, ret.variables = ["x"], ret.coefficients=[-2,0,5]
|
109 | *
|
110 | * See also:
|
111 | *
|
112 | * simplify
|
113 | *
|
114 | * @param {Node|string} expr The expression to check if is a polynomial expression
|
115 | * @param {Object|boolean} optional scope of expression or true for already evaluated rational expression at input
|
116 | * @param {Boolean} detailed optional True if return an object, false if return expression node (default)
|
117 | *
|
118 | * @return {Object | Node} The rational polynomial of `expr` or na object
|
119 | * {Object}
|
120 | * {Expression Node} expression: node simplified expression
|
121 | * {Expression Node} numerator: simplified numerator of expression
|
122 | * {Expression Node | boolean} denominator: simplified denominator or false (if there is no denominator)
|
123 | * {Array} variables: variable names
|
124 | * {Array} coefficients: coefficients of numerator sorted by increased exponent
|
125 | * {Expression Node} node simplified expression
|
126 | *
|
127 | */
|
128 | const rationalize = typed(name, {
|
129 | string: function (expr) {
|
130 | return rationalize(parse(expr), {}, false)
|
131 | },
|
132 |
|
133 | 'string, boolean': function (expr, detailed) {
|
134 | return rationalize(parse(expr), {}, detailed)
|
135 | },
|
136 |
|
137 | 'string, Object': function (expr, scope) {
|
138 | return rationalize(parse(expr), scope, false)
|
139 | },
|
140 |
|
141 | 'string, Object, boolean': function (expr, scope, detailed) {
|
142 | return rationalize(parse(expr), scope, detailed)
|
143 | },
|
144 |
|
145 | Node: function (expr) {
|
146 | return rationalize(expr, {}, false)
|
147 | },
|
148 |
|
149 | 'Node, boolean': function (expr, detailed) {
|
150 | return rationalize(expr, {}, detailed)
|
151 | },
|
152 |
|
153 | 'Node, Object': function (expr, scope) {
|
154 | return rationalize(expr, scope, false)
|
155 | },
|
156 |
|
157 | 'Node, Object, boolean': function (expr, scope, detailed) {
|
158 | const setRules = rulesRationalize() // Rules for change polynomial in near canonical form
|
159 | const polyRet = polynomial(expr, scope, true, setRules.firstRules) // Check if expression is a rationalizable polynomial
|
160 | const nVars = polyRet.variables.length
|
161 | expr = polyRet.expression
|
162 |
|
163 | if (nVars >= 1) { // If expression in not a constant
|
164 | expr = expandPower(expr) // First expand power of polynomials (cannot be made from rules!)
|
165 | let sBefore // Previous expression
|
166 | let rules
|
167 | let eDistrDiv = true
|
168 | let redoInic = false
|
169 | expr = simplify(expr, setRules.firstRules, {}, { exactFractions: false }) // Apply the initial rules, including succ div rules
|
170 | let s
|
171 | while (true) { // Apply alternately successive division rules and distr.div.rules
|
172 | rules = eDistrDiv ? setRules.distrDivRules : setRules.sucDivRules
|
173 | expr = simplify(expr, rules) // until no more changes
|
174 | eDistrDiv = !eDistrDiv // Swap between Distr.Div and Succ. Div. Rules
|
175 |
|
176 | s = expr.toString()
|
177 | if (s === sBefore) {
|
178 | break // No changes : end of the loop
|
179 | }
|
180 |
|
181 | redoInic = true
|
182 | sBefore = s
|
183 | }
|
184 |
|
185 | if (redoInic) { // Apply first rules again without succ div rules (if there are changes)
|
186 | expr = simplify(expr, setRules.firstRulesAgain, {}, { exactFractions: false })
|
187 | }
|
188 | expr = simplify(expr, setRules.finalRules, {}, { exactFractions: false }) // Apply final rules
|
189 | } // NVars >= 1
|
190 |
|
191 | const coefficients = []
|
192 | const retRationalize = {}
|
193 |
|
194 | if (expr.type === 'OperatorNode' && expr.isBinary() && expr.op === '/') { // Separate numerator from denominator
|
195 | if (nVars === 1) {
|
196 | expr.args[0] = polyToCanonical(expr.args[0], coefficients)
|
197 | expr.args[1] = polyToCanonical(expr.args[1])
|
198 | }
|
199 | if (detailed) {
|
200 | retRationalize.numerator = expr.args[0]
|
201 | retRationalize.denominator = expr.args[1]
|
202 | }
|
203 | } else {
|
204 | if (nVars === 1) {
|
205 | expr = polyToCanonical(expr, coefficients)
|
206 | }
|
207 | if (detailed) {
|
208 | retRationalize.numerator = expr
|
209 | retRationalize.denominator = null
|
210 | }
|
211 | }
|
212 | // nVars
|
213 |
|
214 | if (!detailed) return expr
|
215 | retRationalize.coefficients = coefficients
|
216 | retRationalize.variables = polyRet.variables
|
217 | retRationalize.expression = expr
|
218 | return retRationalize
|
219 | } // ^^^^^^^ end of rationalize ^^^^^^^^
|
220 | }) // end of typed rationalize
|
221 |
|
222 | /**
|
223 | * Function to simplify an expression using an optional scope and
|
224 | * return it if the expression is a polynomial expression, i.e.
|
225 | * an expression with one or more variables and the operators
|
226 | * +, -, *, and ^, where the exponent can only be a positive integer.
|
227 | *
|
228 | * Syntax:
|
229 | *
|
230 | * polynomial(expr,scope,extended, rules)
|
231 | *
|
232 | * @param {Node | string} expr The expression to simplify and check if is polynomial expression
|
233 | * @param {object} scope Optional scope for expression simplification
|
234 | * @param {boolean} extended Optional. Default is false. When true allows divide operator.
|
235 | * @param {array} rules Optional. Default is no rule.
|
236 | *
|
237 | *
|
238 | * @return {Object}
|
239 | * {Object} node: node simplified expression
|
240 | * {Array} variables: variable names
|
241 | */
|
242 | function polynomial (expr, scope, extended, rules) {
|
243 | const variables = []
|
244 | const node = simplify(expr, rules, scope, { exactFractions: false }) // Resolves any variables and functions with all defined parameters
|
245 | extended = !!extended
|
246 |
|
247 | const oper = '+-*' + (extended ? '/' : '')
|
248 | recPoly(node)
|
249 | const retFunc = {}
|
250 | retFunc.expression = node
|
251 | retFunc.variables = variables
|
252 | return retFunc
|
253 |
|
254 | // -------------------------------------------------------------------------------------------------------
|
255 |
|
256 | /**
|
257 | * Function to simplify an expression using an optional scope and
|
258 | * return it if the expression is a polynomial expression, i.e.
|
259 | * an expression with one or more variables and the operators
|
260 | * +, -, *, and ^, where the exponent can only be a positive integer.
|
261 | *
|
262 | * Syntax:
|
263 | *
|
264 | * recPoly(node)
|
265 | *
|
266 | *
|
267 | * @param {Node} node The current sub tree expression in recursion
|
268 | *
|
269 | * @return nothing, throw an exception if error
|
270 | */
|
271 | function recPoly (node) {
|
272 | const tp = node.type // node type
|
273 | if (tp === 'FunctionNode') {
|
274 | // No function call in polynomial expression
|
275 | throw new Error('There is an unsolved function call')
|
276 | } else if (tp === 'OperatorNode') {
|
277 | if (node.op === '^') {
|
278 | if (node.args[1].fn === 'unaryMinus') {
|
279 | node = node.args[0]
|
280 | }
|
281 | if (node.args[1].type !== 'ConstantNode' || !isInteger(parseFloat(node.args[1].value))) {
|
282 | throw new Error('There is a non-integer exponent')
|
283 | } else {
|
284 | recPoly(node.args[0])
|
285 | }
|
286 | } else {
|
287 | if (oper.indexOf(node.op) === -1) {
|
288 | throw new Error('Operator ' + node.op + ' invalid in polynomial expression')
|
289 | }
|
290 | for (let i = 0; i < node.args.length; i++) {
|
291 | recPoly(node.args[i])
|
292 | }
|
293 | } // type of operator
|
294 | } else if (tp === 'SymbolNode') {
|
295 | const name = node.name // variable name
|
296 | const pos = variables.indexOf(name)
|
297 | if (pos === -1) {
|
298 | // new variable in expression
|
299 | variables.push(name)
|
300 | }
|
301 | } else if (tp === 'ParenthesisNode') {
|
302 | recPoly(node.content)
|
303 | } else if (tp !== 'ConstantNode') {
|
304 | throw new Error('type ' + tp + ' is not allowed in polynomial expression')
|
305 | }
|
306 | } // end of recPoly
|
307 | } // end of polynomial
|
308 |
|
309 | // ---------------------------------------------------------------------------------------
|
310 | /**
|
311 | * Return a rule set to rationalize an polynomial expression in rationalize
|
312 | *
|
313 | * Syntax:
|
314 | *
|
315 | * rulesRationalize()
|
316 | *
|
317 | * @return {array} rule set to rationalize an polynomial expression
|
318 | */
|
319 | function rulesRationalize () {
|
320 | const oldRules = [simplifyCore, // sCore
|
321 | { l: 'n+n', r: '2*n' },
|
322 | { l: 'n+-n', r: '0' },
|
323 | simplifyConstant, // sConstant
|
324 | { l: 'n*(n1^-1)', r: 'n/n1' },
|
325 | { l: 'n*n1^-n2', r: 'n/n1^n2' },
|
326 | { l: 'n1^-1', r: '1/n1' },
|
327 | { l: 'n*(n1/n2)', r: '(n*n1)/n2' },
|
328 | { l: '1*n', r: 'n' }]
|
329 |
|
330 | const rulesFirst = [
|
331 | { l: '(-n1)/(-n2)', r: 'n1/n2' }, // Unary division
|
332 | { l: '(-n1)*(-n2)', r: 'n1*n2' }, // Unary multiplication
|
333 | { l: 'n1--n2', r: 'n1+n2' }, // '--' elimination
|
334 | { l: 'n1-n2', r: 'n1+(-n2)' }, // Subtraction turn into add with un�ry minus
|
335 | { l: '(n1+n2)*n3', r: '(n1*n3 + n2*n3)' }, // Distributive 1
|
336 | { l: 'n1*(n2+n3)', r: '(n1*n2+n1*n3)' }, // Distributive 2
|
337 | { l: 'c1*n + c2*n', r: '(c1+c2)*n' }, // Joining constants
|
338 | { l: 'c1*n + n', r: '(c1+1)*n' }, // Joining constants
|
339 | { l: 'c1*n - c2*n', r: '(c1-c2)*n' }, // Joining constants
|
340 | { l: 'c1*n - n', r: '(c1-1)*n' }, // Joining constants
|
341 | { l: 'v/c', r: '(1/c)*v' }, // variable/constant (new!)
|
342 | { l: 'v/-c', r: '-(1/c)*v' }, // variable/constant (new!)
|
343 | { l: '-v*-c', r: 'c*v' }, // Inversion constant and variable 1
|
344 | { l: '-v*c', r: '-c*v' }, // Inversion constant and variable 2
|
345 | { l: 'v*-c', r: '-c*v' }, // Inversion constant and variable 3
|
346 | { l: 'v*c', r: 'c*v' }, // Inversion constant and variable 4
|
347 | { l: '-(-n1*n2)', r: '(n1*n2)' }, // Unary propagation
|
348 | { l: '-(n1*n2)', r: '(-n1*n2)' }, // Unary propagation
|
349 | { l: '-(-n1+n2)', r: '(n1-n2)' }, // Unary propagation
|
350 | { l: '-(n1+n2)', r: '(-n1-n2)' }, // Unary propagation
|
351 | { l: '(n1^n2)^n3', r: '(n1^(n2*n3))' }, // Power to Power
|
352 | { l: '-(-n1/n2)', r: '(n1/n2)' }, // Division and Unary
|
353 | { l: '-(n1/n2)', r: '(-n1/n2)' }] // Divisao and Unary
|
354 |
|
355 | const rulesDistrDiv = [
|
356 | { l: '(n1/n2 + n3/n4)', r: '((n1*n4 + n3*n2)/(n2*n4))' }, // Sum of fractions
|
357 | { l: '(n1/n2 + n3)', r: '((n1 + n3*n2)/n2)' }, // Sum fraction with number 1
|
358 | { l: '(n1 + n2/n3)', r: '((n1*n3 + n2)/n3)' }] // Sum fraction with number 1
|
359 |
|
360 | const rulesSucDiv = [
|
361 | { l: '(n1/(n2/n3))', r: '((n1*n3)/n2)' }, // Division simplification
|
362 | { l: '(n1/n2/n3)', r: '(n1/(n2*n3))' }]
|
363 |
|
364 | const setRules = {} // rules set in 4 steps.
|
365 |
|
366 | // All rules => infinite loop
|
367 | // setRules.allRules =oldRules.concat(rulesFirst,rulesDistrDiv,rulesSucDiv)
|
368 |
|
369 | setRules.firstRules = oldRules.concat(rulesFirst, rulesSucDiv) // First rule set
|
370 | setRules.distrDivRules = rulesDistrDiv // Just distr. div. rules
|
371 | setRules.sucDivRules = rulesSucDiv // Jus succ. div. rules
|
372 | setRules.firstRulesAgain = oldRules.concat(rulesFirst) // Last rules set without succ. div.
|
373 |
|
374 | // Division simplification
|
375 |
|
376 | // Second rule set.
|
377 | // There is no aggregate expression with parentesis, but the only variable can be scattered.
|
378 | setRules.finalRules = [simplifyCore, // simplify.rules[0]
|
379 | { l: 'n*-n', r: '-n^2' }, // Joining multiply with power 1
|
380 | { l: 'n*n', r: 'n^2' }, // Joining multiply with power 2
|
381 | simplifyConstant, // simplify.rules[14] old 3rd index in oldRules
|
382 | { l: 'n*-n^n1', r: '-n^(n1+1)' }, // Joining multiply with power 3
|
383 | { l: 'n*n^n1', r: 'n^(n1+1)' }, // Joining multiply with power 4
|
384 | { l: 'n^n1*-n^n2', r: '-n^(n1+n2)' }, // Joining multiply with power 5
|
385 | { l: 'n^n1*n^n2', r: 'n^(n1+n2)' }, // Joining multiply with power 6
|
386 | { l: 'n^n1*-n', r: '-n^(n1+1)' }, // Joining multiply with power 7
|
387 | { l: 'n^n1*n', r: 'n^(n1+1)' }, // Joining multiply with power 8
|
388 | { l: 'n^n1/-n', r: '-n^(n1-1)' }, // Joining multiply with power 8
|
389 | { l: 'n^n1/n', r: 'n^(n1-1)' }, // Joining division with power 1
|
390 | { l: 'n/-n^n1', r: '-n^(1-n1)' }, // Joining division with power 2
|
391 | { l: 'n/n^n1', r: 'n^(1-n1)' }, // Joining division with power 3
|
392 | { l: 'n^n1/-n^n2', r: 'n^(n1-n2)' }, // Joining division with power 4
|
393 | { l: 'n^n1/n^n2', r: 'n^(n1-n2)' }, // Joining division with power 5
|
394 | { l: 'n1+(-n2*n3)', r: 'n1-n2*n3' }, // Solving useless parenthesis 1
|
395 | { l: 'v*(-c)', r: '-c*v' }, // Solving useless unary 2
|
396 | { l: 'n1+-n2', r: 'n1-n2' }, // Solving +- together (new!)
|
397 | { l: 'v*c', r: 'c*v' }, // inversion constant with variable
|
398 | { l: '(n1^n2)^n3', r: '(n1^(n2*n3))' } // Power to Power
|
399 |
|
400 | ]
|
401 | return setRules
|
402 | } // End rulesRationalize
|
403 |
|
404 | // ---------------------------------------------------------------------------------------
|
405 | /**
|
406 | * Expand recursively a tree node for handling with expressions with exponents
|
407 | * (it's not for constants, symbols or functions with exponents)
|
408 | * PS: The other parameters are internal for recursion
|
409 | *
|
410 | * Syntax:
|
411 | *
|
412 | * expandPower(node)
|
413 | *
|
414 | * @param {Node} node Current expression node
|
415 | * @param {node} parent Parent current node inside the recursion
|
416 | * @param (int} Parent number of chid inside the rercursion
|
417 | *
|
418 | * @return {node} node expression with all powers expanded.
|
419 | */
|
420 | function expandPower (node, parent, indParent) {
|
421 | const tp = node.type
|
422 | const internal = (arguments.length > 1) // TRUE in internal calls
|
423 |
|
424 | if (tp === 'OperatorNode' && node.isBinary()) {
|
425 | let does = false
|
426 | let val
|
427 | if (node.op === '^') { // First operator: Parenthesis or UnaryMinus
|
428 | if ((node.args[0].type === 'ParenthesisNode' ||
|
429 | node.args[0].type === 'OperatorNode') &&
|
430 | (node.args[1].type === 'ConstantNode')) { // Second operator: Constant
|
431 | val = parseFloat(node.args[1].value)
|
432 | does = (val >= 2 && isInteger(val))
|
433 | }
|
434 | }
|
435 |
|
436 | if (does) { // Exponent >= 2
|
437 | // Before:
|
438 | // operator A --> Subtree
|
439 | // parent pow
|
440 | // constant
|
441 | //
|
442 | if (val > 2) { // Exponent > 2,
|
443 | // AFTER: (exponent > 2)
|
444 | // operator A --> Subtree
|
445 | // parent *
|
446 | // deep clone (operator A --> Subtree
|
447 | // pow
|
448 | // constant - 1
|
449 | //
|
450 | const nEsqTopo = node.args[0]
|
451 | const nDirTopo = new OperatorNode('^', 'pow', [node.args[0].cloneDeep(), new ConstantNode(val - 1)])
|
452 | node = new OperatorNode('*', 'multiply', [nEsqTopo, nDirTopo])
|
453 | } else { // Expo = 2 - no power
|
454 | // AFTER: (exponent = 2)
|
455 | // operator A --> Subtree
|
456 | // parent oper
|
457 | // deep clone (operator A --> Subtree)
|
458 | //
|
459 | node = new OperatorNode('*', 'multiply', [node.args[0], node.args[0].cloneDeep()])
|
460 | }
|
461 |
|
462 | if (internal) {
|
463 | // Change parent references in internal recursive calls
|
464 | if (indParent === 'content') { parent.content = node } else { parent.args[indParent] = node }
|
465 | }
|
466 | } // does
|
467 | } // binary OperatorNode
|
468 |
|
469 | if (tp === 'ParenthesisNode') {
|
470 | // Recursion
|
471 | expandPower(node.content, node, 'content')
|
472 | } else if (tp !== 'ConstantNode' && tp !== 'SymbolNode') {
|
473 | for (let i = 0; i < node.args.length; i++) {
|
474 | expandPower(node.args[i], node, i)
|
475 | }
|
476 | }
|
477 |
|
478 | if (!internal) {
|
479 | // return the root node
|
480 | return node
|
481 | }
|
482 | } // End expandPower
|
483 |
|
484 | // ---------------------------------------------------------------------------------------
|
485 | /**
|
486 | * Auxilary function for rationalize
|
487 | * Convert near canonical polynomial in one variable in a canonical polynomial
|
488 | * with one term for each exponent in decreasing order
|
489 | *
|
490 | * Syntax:
|
491 | *
|
492 | * polyToCanonical(node [, coefficients])
|
493 | *
|
494 | * @param {Node | string} expr The near canonical polynomial expression to convert in a a canonical polynomial expression
|
495 | *
|
496 | * The string or tree expression needs to be at below syntax, with free spaces:
|
497 | * ( (^(-)? | [+-]? )cte (*)? var (^expo)? | cte )+
|
498 | * Where 'var' is one variable with any valid name
|
499 | * 'cte' are real numeric constants with any value. It can be omitted if equal than 1
|
500 | * 'expo' are integers greater than 0. It can be omitted if equal than 1.
|
501 | *
|
502 | * @param {array} coefficients Optional returns coefficients sorted by increased exponent
|
503 | *
|
504 | *
|
505 | * @return {node} new node tree with one variable polynomial or string error.
|
506 | */
|
507 | function polyToCanonical (node, coefficients) {
|
508 | if (coefficients === undefined) { coefficients = [] } // coefficients.
|
509 |
|
510 | coefficients[0] = 0 // index is the exponent
|
511 | const o = {}
|
512 | o.cte = 1
|
513 | o.oper = '+'
|
514 |
|
515 | // fire: mark with * or ^ when finds * or ^ down tree, reset to "" with + and -.
|
516 | // It is used to deduce the exponent: 1 for *, 0 for "".
|
517 | o.fire = ''
|
518 |
|
519 | let maxExpo = 0 // maximum exponent
|
520 | let varname = '' // variable name
|
521 |
|
522 | recurPol(node, null, o)
|
523 | maxExpo = coefficients.length - 1
|
524 | let first = true
|
525 | let no
|
526 |
|
527 | for (let i = maxExpo; i >= 0; i--) {
|
528 | if (coefficients[i] === 0) continue
|
529 | let n1 = new ConstantNode(
|
530 | first ? coefficients[i] : Math.abs(coefficients[i]))
|
531 | const op = coefficients[i] < 0 ? '-' : '+'
|
532 |
|
533 | if (i > 0) { // Is not a constant without variable
|
534 | let n2 = new SymbolNode(varname)
|
535 | if (i > 1) {
|
536 | const n3 = new ConstantNode(i)
|
537 | n2 = new OperatorNode('^', 'pow', [n2, n3])
|
538 | }
|
539 | if (coefficients[i] === -1 && first) { n1 = new OperatorNode('-', 'unaryMinus', [n2]) } else if (Math.abs(coefficients[i]) === 1) { n1 = n2 } else { n1 = new OperatorNode('*', 'multiply', [n1, n2]) }
|
540 | }
|
541 |
|
542 | if (first) { no = n1 } else if (op === '+') { no = new OperatorNode('+', 'add', [no, n1]) } else { no = new OperatorNode('-', 'subtract', [no, n1]) }
|
543 |
|
544 | first = false
|
545 | } // for
|
546 |
|
547 | if (first) { return new ConstantNode(0) } else { return no }
|
548 |
|
549 | /**
|
550 | * Recursive auxilary function inside polyToCanonical for
|
551 | * converting expression in canonical form
|
552 | *
|
553 | * Syntax:
|
554 | *
|
555 | * recurPol(node, noPai, obj)
|
556 | *
|
557 | * @param {Node} node The current subpolynomial expression
|
558 | * @param {Node | Null} noPai The current parent node
|
559 | * @param {object} obj Object with many internal flags
|
560 | *
|
561 | * @return {} No return. If error, throws an exception
|
562 | */
|
563 | function recurPol (node, noPai, o) {
|
564 | const tp = node.type
|
565 | if (tp === 'FunctionNode') {
|
566 | // ***** FunctionName *****
|
567 | // No function call in polynomial expression
|
568 | throw new Error('There is an unsolved function call')
|
569 | } else if (tp === 'OperatorNode') {
|
570 | // ***** OperatorName *****
|
571 | if ('+-*^'.indexOf(node.op) === -1) throw new Error('Operator ' + node.op + ' invalid')
|
572 |
|
573 | if (noPai !== null) {
|
574 | // -(unary),^ : children of *,+,-
|
575 | if ((node.fn === 'unaryMinus' || node.fn === 'pow') && noPai.fn !== 'add' &&
|
576 | noPai.fn !== 'subtract' && noPai.fn !== 'multiply') { throw new Error('Invalid ' + node.op + ' placing') }
|
577 |
|
578 | // -,+,* : children of +,-
|
579 | if ((node.fn === 'subtract' || node.fn === 'add' || node.fn === 'multiply') &&
|
580 | noPai.fn !== 'add' && noPai.fn !== 'subtract') { throw new Error('Invalid ' + node.op + ' placing') }
|
581 |
|
582 | // -,+ : first child
|
583 | if ((node.fn === 'subtract' || node.fn === 'add' ||
|
584 | node.fn === 'unaryMinus') && o.noFil !== 0) { throw new Error('Invalid ' + node.op + ' placing') }
|
585 | } // Has parent
|
586 |
|
587 | // Firers: ^,* Old: ^,&,-(unary): firers
|
588 | if (node.op === '^' || node.op === '*') {
|
589 | o.fire = node.op
|
590 | }
|
591 |
|
592 | for (let i = 0; i < node.args.length; i++) {
|
593 | // +,-: reset fire
|
594 | if (node.fn === 'unaryMinus') o.oper = '-'
|
595 | if (node.op === '+' || node.fn === 'subtract') {
|
596 | o.fire = ''
|
597 | o.cte = 1 // default if there is no constant
|
598 | o.oper = (i === 0 ? '+' : node.op)
|
599 | }
|
600 | o.noFil = i // number of son
|
601 | recurPol(node.args[i], node, o)
|
602 | } // for in children
|
603 | } else if (tp === 'SymbolNode') { // ***** SymbolName *****
|
604 | if (node.name !== varname && varname !== '') { throw new Error('There is more than one variable') }
|
605 | varname = node.name
|
606 | if (noPai === null) {
|
607 | coefficients[1] = 1
|
608 | return
|
609 | }
|
610 |
|
611 | // ^: Symbol is First child
|
612 | if (noPai.op === '^' && o.noFil !== 0) { throw new Error('In power the variable should be the first parameter') }
|
613 |
|
614 | // *: Symbol is Second child
|
615 | if (noPai.op === '*' && o.noFil !== 1) { throw new Error('In multiply the variable should be the second parameter') }
|
616 |
|
617 | // Symbol: firers '',* => it means there is no exponent above, so it's 1 (cte * var)
|
618 | if (o.fire === '' || o.fire === '*') {
|
619 | if (maxExpo < 1) coefficients[1] = 0
|
620 | coefficients[1] += o.cte * (o.oper === '+' ? 1 : -1)
|
621 | maxExpo = Math.max(1, maxExpo)
|
622 | }
|
623 | } else if (tp === 'ConstantNode') {
|
624 | const valor = parseFloat(node.value)
|
625 | if (noPai === null) {
|
626 | coefficients[0] = valor
|
627 | return
|
628 | }
|
629 | if (noPai.op === '^') {
|
630 | // cte: second child of power
|
631 | if (o.noFil !== 1) throw new Error('Constant cannot be powered')
|
632 |
|
633 | if (!isInteger(valor) || valor <= 0) { throw new Error('Non-integer exponent is not allowed') }
|
634 |
|
635 | for (let i = maxExpo + 1; i < valor; i++) coefficients[i] = 0
|
636 | if (valor > maxExpo) coefficients[valor] = 0
|
637 | coefficients[valor] += o.cte * (o.oper === '+' ? 1 : -1)
|
638 | maxExpo = Math.max(valor, maxExpo)
|
639 | return
|
640 | }
|
641 | o.cte = valor
|
642 |
|
643 | // Cte: firer '' => There is no exponent and no multiplication, so the exponent is 0.
|
644 | if (o.fire === '') { coefficients[0] += o.cte * (o.oper === '+' ? 1 : -1) }
|
645 | } else { throw new Error('Type ' + tp + ' is not allowed') }
|
646 | } // End of recurPol
|
647 | } // End of polyToCanonical
|
648 |
|
649 | return rationalize
|
650 | })
|