869 lines
26 KiB
JavaScript
869 lines
26 KiB
JavaScript
import { isInteger } from '../../utils/number.js';
|
||
import { factory } from '../../utils/factory.js';
|
||
import { createSimplifyConstant } from './simplify/simplifyConstant.js';
|
||
var name = 'rationalize';
|
||
var dependencies = ['config', 'typed', 'equal', 'isZero', 'add', 'subtract', 'multiply', 'divide', 'pow', 'parse', 'simplifyCore', 'simplify', '?bignumber', '?fraction', 'mathWithTransform', 'matrix', 'AccessorNode', 'ArrayNode', 'ConstantNode', 'FunctionNode', 'IndexNode', 'ObjectNode', 'OperatorNode', 'SymbolNode', 'ParenthesisNode'];
|
||
export var createRationalize = /* #__PURE__ */factory(name, dependencies, _ref => {
|
||
var {
|
||
config,
|
||
typed,
|
||
equal,
|
||
isZero,
|
||
add,
|
||
subtract,
|
||
multiply,
|
||
divide,
|
||
pow,
|
||
parse,
|
||
simplifyCore,
|
||
simplify,
|
||
fraction,
|
||
bignumber,
|
||
mathWithTransform,
|
||
matrix,
|
||
AccessorNode,
|
||
ArrayNode,
|
||
ConstantNode,
|
||
FunctionNode,
|
||
IndexNode,
|
||
ObjectNode,
|
||
OperatorNode,
|
||
SymbolNode,
|
||
ParenthesisNode
|
||
} = _ref;
|
||
var simplifyConstant = createSimplifyConstant({
|
||
typed,
|
||
config,
|
||
mathWithTransform,
|
||
matrix,
|
||
fraction,
|
||
bignumber,
|
||
AccessorNode,
|
||
ArrayNode,
|
||
ConstantNode,
|
||
FunctionNode,
|
||
IndexNode,
|
||
ObjectNode,
|
||
OperatorNode,
|
||
SymbolNode
|
||
});
|
||
/**
|
||
* Transform a rationalizable expression in a rational fraction.
|
||
* If rational fraction is one variable polynomial then converts
|
||
* the numerator and denominator in canonical form, with decreasing
|
||
* exponents, returning the coefficients of numerator.
|
||
*
|
||
* Syntax:
|
||
*
|
||
* rationalize(expr)
|
||
* rationalize(expr, detailed)
|
||
* rationalize(expr, scope)
|
||
* rationalize(expr, scope, detailed)
|
||
*
|
||
* Examples:
|
||
*
|
||
* math.rationalize('sin(x)+y')
|
||
* // Error: There is an unsolved function call
|
||
* math.rationalize('2x/y - y/(x+1)')
|
||
* // (2*x^2-y^2+2*x)/(x*y+y)
|
||
* math.rationalize('(2x+1)^6')
|
||
* // 64*x^6+192*x^5+240*x^4+160*x^3+60*x^2+12*x+1
|
||
* math.rationalize('2x/( (2x-1) / (3x+2) ) - 5x/ ( (3x+4) / (2x^2-5) ) + 3')
|
||
* // -20*x^4+28*x^3+104*x^2+6*x-12)/(6*x^2+5*x-4)
|
||
* math.rationalize('x/(1-x)/(x-2)/(x-3)/(x-4) + 2x/ ( (1-2x)/(2-3x) )/ ((3-4x)/(4-5x) )') =
|
||
* // (-30*x^7+344*x^6-1506*x^5+3200*x^4-3472*x^3+1846*x^2-381*x)/
|
||
* // (-8*x^6+90*x^5-383*x^4+780*x^3-797*x^2+390*x-72)
|
||
*
|
||
* math.rationalize('x+x+x+y',{y:1}) // 3*x+1
|
||
* math.rationalize('x+x+x+y',{}) // 3*x+y
|
||
*
|
||
* const ret = math.rationalize('x+x+x+y',{},true)
|
||
* // ret.expression=3*x+y, ret.variables = ["x","y"]
|
||
* const ret = math.rationalize('-2+5x^2',{},true)
|
||
* // ret.expression=5*x^2-2, ret.variables = ["x"], ret.coefficients=[-2,0,5]
|
||
*
|
||
* See also:
|
||
*
|
||
* simplify
|
||
*
|
||
* @param {Node|string} expr The expression to check if is a polynomial expression
|
||
* @param {Object|boolean} optional scope of expression or true for already evaluated rational expression at input
|
||
* @param {Boolean} detailed optional True if return an object, false if return expression node (default)
|
||
*
|
||
* @return {Object | Node} The rational polynomial of `expr` or an object
|
||
* `{expression, numerator, denominator, variables, coefficients}`, where
|
||
* `expression` is a `Node` with the node simplified expression,
|
||
* `numerator` is a `Node` with the simplified numerator of expression,
|
||
* `denominator` is a `Node` or `boolean` with the simplified denominator or `false` (if there is no denominator),
|
||
* `variables` is an array with variable names,
|
||
* and `coefficients` is an array with coefficients of numerator sorted by increased exponent
|
||
* {Expression Node} node simplified expression
|
||
*
|
||
*/
|
||
|
||
return typed(name, {
|
||
string: function string(expr) {
|
||
return this(parse(expr), {}, false);
|
||
},
|
||
'string, boolean': function stringBoolean(expr, detailed) {
|
||
return this(parse(expr), {}, detailed);
|
||
},
|
||
'string, Object': function stringObject(expr, scope) {
|
||
return this(parse(expr), scope, false);
|
||
},
|
||
'string, Object, boolean': function stringObjectBoolean(expr, scope, detailed) {
|
||
return this(parse(expr), scope, detailed);
|
||
},
|
||
Node: function Node(expr) {
|
||
return this(expr, {}, false);
|
||
},
|
||
'Node, boolean': function NodeBoolean(expr, detailed) {
|
||
return this(expr, {}, detailed);
|
||
},
|
||
'Node, Object': function NodeObject(expr, scope) {
|
||
return this(expr, scope, false);
|
||
},
|
||
'Node, Object, boolean': function NodeObjectBoolean(expr, scope, detailed) {
|
||
var setRules = rulesRationalize(); // Rules for change polynomial in near canonical form
|
||
|
||
var polyRet = polynomial(expr, scope, true, setRules.firstRules); // Check if expression is a rationalizable polynomial
|
||
|
||
var nVars = polyRet.variables.length;
|
||
var noExactFractions = {
|
||
exactFractions: false
|
||
};
|
||
var withExactFractions = {
|
||
exactFractions: true
|
||
};
|
||
expr = polyRet.expression;
|
||
|
||
if (nVars >= 1) {
|
||
// If expression in not a constant
|
||
expr = expandPower(expr); // First expand power of polynomials (cannot be made from rules!)
|
||
|
||
var sBefore; // Previous expression
|
||
|
||
var rules;
|
||
var eDistrDiv = true;
|
||
var redoInic = false; // Apply the initial rules, including succ div rules:
|
||
|
||
expr = simplify(expr, setRules.firstRules, {}, noExactFractions);
|
||
var s;
|
||
|
||
while (true) {
|
||
// Alternate applying successive division rules and distr.div.rules
|
||
// until there are no more changes:
|
||
rules = eDistrDiv ? setRules.distrDivRules : setRules.sucDivRules;
|
||
expr = simplify(expr, rules, {}, withExactFractions);
|
||
eDistrDiv = !eDistrDiv; // Swap between Distr.Div and Succ. Div. Rules
|
||
|
||
s = expr.toString();
|
||
|
||
if (s === sBefore) {
|
||
break; // No changes : end of the loop
|
||
}
|
||
|
||
redoInic = true;
|
||
sBefore = s;
|
||
}
|
||
|
||
if (redoInic) {
|
||
// Apply first rules again without succ div rules (if there are changes)
|
||
expr = simplify(expr, setRules.firstRulesAgain, {}, noExactFractions);
|
||
} // Apply final rules:
|
||
|
||
|
||
expr = simplify(expr, setRules.finalRules, {}, noExactFractions);
|
||
} // NVars >= 1
|
||
|
||
|
||
var coefficients = [];
|
||
var retRationalize = {};
|
||
|
||
if (expr.type === 'OperatorNode' && expr.isBinary() && expr.op === '/') {
|
||
// Separate numerator from denominator
|
||
if (nVars === 1) {
|
||
expr.args[0] = polyToCanonical(expr.args[0], coefficients);
|
||
expr.args[1] = polyToCanonical(expr.args[1]);
|
||
}
|
||
|
||
if (detailed) {
|
||
retRationalize.numerator = expr.args[0];
|
||
retRationalize.denominator = expr.args[1];
|
||
}
|
||
} else {
|
||
if (nVars === 1) {
|
||
expr = polyToCanonical(expr, coefficients);
|
||
}
|
||
|
||
if (detailed) {
|
||
retRationalize.numerator = expr;
|
||
retRationalize.denominator = null;
|
||
}
|
||
} // nVars
|
||
|
||
|
||
if (!detailed) return expr;
|
||
retRationalize.coefficients = coefficients;
|
||
retRationalize.variables = polyRet.variables;
|
||
retRationalize.expression = expr;
|
||
return retRationalize;
|
||
} // ^^^^^^^ end of rationalize ^^^^^^^^
|
||
|
||
}); // end of typed rationalize
|
||
|
||
/**
|
||
* Function to simplify an expression using an optional scope and
|
||
* return it if the expression is a polynomial expression, i.e.
|
||
* an expression with one or more variables and the operators
|
||
* +, -, *, and ^, where the exponent can only be a positive integer.
|
||
*
|
||
* Syntax:
|
||
*
|
||
* polynomial(expr,scope,extended, rules)
|
||
*
|
||
* @param {Node | string} expr The expression to simplify and check if is polynomial expression
|
||
* @param {object} scope Optional scope for expression simplification
|
||
* @param {boolean} extended Optional. Default is false. When true allows divide operator.
|
||
* @param {array} rules Optional. Default is no rule.
|
||
*
|
||
*
|
||
* @return {Object}
|
||
* {Object} node: node simplified expression
|
||
* {Array} variables: variable names
|
||
*/
|
||
|
||
function polynomial(expr, scope, extended, rules) {
|
||
var variables = [];
|
||
var node = simplify(expr, rules, scope, {
|
||
exactFractions: false
|
||
}); // Resolves any variables and functions with all defined parameters
|
||
|
||
extended = !!extended;
|
||
var oper = '+-*' + (extended ? '/' : '');
|
||
recPoly(node);
|
||
var retFunc = {};
|
||
retFunc.expression = node;
|
||
retFunc.variables = variables;
|
||
return retFunc; // -------------------------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Function to simplify an expression using an optional scope and
|
||
* return it if the expression is a polynomial expression, i.e.
|
||
* an expression with one or more variables and the operators
|
||
* +, -, *, and ^, where the exponent can only be a positive integer.
|
||
*
|
||
* Syntax:
|
||
*
|
||
* recPoly(node)
|
||
*
|
||
*
|
||
* @param {Node} node The current sub tree expression in recursion
|
||
*
|
||
* @return nothing, throw an exception if error
|
||
*/
|
||
|
||
function recPoly(node) {
|
||
var tp = node.type; // node type
|
||
|
||
if (tp === 'FunctionNode') {
|
||
// No function call in polynomial expression
|
||
throw new Error('There is an unsolved function call');
|
||
} else if (tp === 'OperatorNode') {
|
||
if (node.op === '^') {
|
||
// TODO: handle negative exponents like in '1/x^(-2)'
|
||
if (node.args[1].type !== 'ConstantNode' || !isInteger(parseFloat(node.args[1].value))) {
|
||
throw new Error('There is a non-integer exponent');
|
||
} else {
|
||
recPoly(node.args[0]);
|
||
}
|
||
} else {
|
||
if (oper.indexOf(node.op) === -1) {
|
||
throw new Error('Operator ' + node.op + ' invalid in polynomial expression');
|
||
}
|
||
|
||
for (var i = 0; i < node.args.length; i++) {
|
||
recPoly(node.args[i]);
|
||
}
|
||
} // type of operator
|
||
|
||
} else if (tp === 'SymbolNode') {
|
||
var _name = node.name; // variable name
|
||
|
||
var pos = variables.indexOf(_name);
|
||
|
||
if (pos === -1) {
|
||
// new variable in expression
|
||
variables.push(_name);
|
||
}
|
||
} else if (tp === 'ParenthesisNode') {
|
||
recPoly(node.content);
|
||
} else if (tp !== 'ConstantNode') {
|
||
throw new Error('type ' + tp + ' is not allowed in polynomial expression');
|
||
}
|
||
} // end of recPoly
|
||
|
||
} // end of polynomial
|
||
// ---------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Return a rule set to rationalize an polynomial expression in rationalize
|
||
*
|
||
* Syntax:
|
||
*
|
||
* rulesRationalize()
|
||
*
|
||
* @return {array} rule set to rationalize an polynomial expression
|
||
*/
|
||
|
||
|
||
function rulesRationalize() {
|
||
var oldRules = [simplifyCore, // sCore
|
||
{
|
||
l: 'n+n',
|
||
r: '2*n'
|
||
}, {
|
||
l: 'n+-n',
|
||
r: '0'
|
||
}, simplifyConstant, // sConstant
|
||
{
|
||
l: 'n*(n1^-1)',
|
||
r: 'n/n1'
|
||
}, {
|
||
l: 'n*n1^-n2',
|
||
r: 'n/n1^n2'
|
||
}, {
|
||
l: 'n1^-1',
|
||
r: '1/n1'
|
||
}, {
|
||
l: 'n*(n1/n2)',
|
||
r: '(n*n1)/n2'
|
||
}, {
|
||
l: '1*n',
|
||
r: 'n'
|
||
}];
|
||
var rulesFirst = [{
|
||
l: '(-n1)/(-n2)',
|
||
r: 'n1/n2'
|
||
}, // Unary division
|
||
{
|
||
l: '(-n1)*(-n2)',
|
||
r: 'n1*n2'
|
||
}, // Unary multiplication
|
||
{
|
||
l: 'n1--n2',
|
||
r: 'n1+n2'
|
||
}, // '--' elimination
|
||
{
|
||
l: 'n1-n2',
|
||
r: 'n1+(-n2)'
|
||
}, // Subtraction turn into add with un<75>ry minus
|
||
{
|
||
l: '(n1+n2)*n3',
|
||
r: '(n1*n3 + n2*n3)'
|
||
}, // Distributive 1
|
||
{
|
||
l: 'n1*(n2+n3)',
|
||
r: '(n1*n2+n1*n3)'
|
||
}, // Distributive 2
|
||
{
|
||
l: 'c1*n + c2*n',
|
||
r: '(c1+c2)*n'
|
||
}, // Joining constants
|
||
{
|
||
l: 'c1*n + n',
|
||
r: '(c1+1)*n'
|
||
}, // Joining constants
|
||
{
|
||
l: 'c1*n - c2*n',
|
||
r: '(c1-c2)*n'
|
||
}, // Joining constants
|
||
{
|
||
l: 'c1*n - n',
|
||
r: '(c1-1)*n'
|
||
}, // Joining constants
|
||
{
|
||
l: 'v/c',
|
||
r: '(1/c)*v'
|
||
}, // variable/constant (new!)
|
||
{
|
||
l: 'v/-c',
|
||
r: '-(1/c)*v'
|
||
}, // variable/constant (new!)
|
||
{
|
||
l: '-v*-c',
|
||
r: 'c*v'
|
||
}, // Inversion constant and variable 1
|
||
{
|
||
l: '-v*c',
|
||
r: '-c*v'
|
||
}, // Inversion constant and variable 2
|
||
{
|
||
l: 'v*-c',
|
||
r: '-c*v'
|
||
}, // Inversion constant and variable 3
|
||
{
|
||
l: 'v*c',
|
||
r: 'c*v'
|
||
}, // Inversion constant and variable 4
|
||
{
|
||
l: '-(-n1*n2)',
|
||
r: '(n1*n2)'
|
||
}, // Unary propagation
|
||
{
|
||
l: '-(n1*n2)',
|
||
r: '(-n1*n2)'
|
||
}, // Unary propagation
|
||
{
|
||
l: '-(-n1+n2)',
|
||
r: '(n1-n2)'
|
||
}, // Unary propagation
|
||
{
|
||
l: '-(n1+n2)',
|
||
r: '(-n1-n2)'
|
||
}, // Unary propagation
|
||
{
|
||
l: '(n1^n2)^n3',
|
||
r: '(n1^(n2*n3))'
|
||
}, // Power to Power
|
||
{
|
||
l: '-(-n1/n2)',
|
||
r: '(n1/n2)'
|
||
}, // Division and Unary
|
||
{
|
||
l: '-(n1/n2)',
|
||
r: '(-n1/n2)'
|
||
}]; // Divisao and Unary
|
||
|
||
var rulesDistrDiv = [{
|
||
l: '(n1/n2 + n3/n4)',
|
||
r: '((n1*n4 + n3*n2)/(n2*n4))'
|
||
}, // Sum of fractions
|
||
{
|
||
l: '(n1/n2 + n3)',
|
||
r: '((n1 + n3*n2)/n2)'
|
||
}, // Sum fraction with number 1
|
||
{
|
||
l: '(n1 + n2/n3)',
|
||
r: '((n1*n3 + n2)/n3)'
|
||
}]; // Sum fraction with number 1
|
||
|
||
var rulesSucDiv = [{
|
||
l: '(n1/(n2/n3))',
|
||
r: '((n1*n3)/n2)'
|
||
}, // Division simplification
|
||
{
|
||
l: '(n1/n2/n3)',
|
||
r: '(n1/(n2*n3))'
|
||
}];
|
||
var setRules = {}; // rules set in 4 steps.
|
||
// All rules => infinite loop
|
||
// setRules.allRules =oldRules.concat(rulesFirst,rulesDistrDiv,rulesSucDiv)
|
||
|
||
setRules.firstRules = oldRules.concat(rulesFirst, rulesSucDiv); // First rule set
|
||
|
||
setRules.distrDivRules = rulesDistrDiv; // Just distr. div. rules
|
||
|
||
setRules.sucDivRules = rulesSucDiv; // Jus succ. div. rules
|
||
|
||
setRules.firstRulesAgain = oldRules.concat(rulesFirst); // Last rules set without succ. div.
|
||
// Division simplification
|
||
// Second rule set.
|
||
// There is no aggregate expression with parentesis, but the only variable can be scattered.
|
||
|
||
setRules.finalRules = [simplifyCore, // simplify.rules[0]
|
||
{
|
||
l: 'n*-n',
|
||
r: '-n^2'
|
||
}, // Joining multiply with power 1
|
||
{
|
||
l: 'n*n',
|
||
r: 'n^2'
|
||
}, // Joining multiply with power 2
|
||
simplifyConstant, // simplify.rules[14] old 3rd index in oldRules
|
||
{
|
||
l: 'n*-n^n1',
|
||
r: '-n^(n1+1)'
|
||
}, // Joining multiply with power 3
|
||
{
|
||
l: 'n*n^n1',
|
||
r: 'n^(n1+1)'
|
||
}, // Joining multiply with power 4
|
||
{
|
||
l: 'n^n1*-n^n2',
|
||
r: '-n^(n1+n2)'
|
||
}, // Joining multiply with power 5
|
||
{
|
||
l: 'n^n1*n^n2',
|
||
r: 'n^(n1+n2)'
|
||
}, // Joining multiply with power 6
|
||
{
|
||
l: 'n^n1*-n',
|
||
r: '-n^(n1+1)'
|
||
}, // Joining multiply with power 7
|
||
{
|
||
l: 'n^n1*n',
|
||
r: 'n^(n1+1)'
|
||
}, // Joining multiply with power 8
|
||
{
|
||
l: 'n^n1/-n',
|
||
r: '-n^(n1-1)'
|
||
}, // Joining multiply with power 8
|
||
{
|
||
l: 'n^n1/n',
|
||
r: 'n^(n1-1)'
|
||
}, // Joining division with power 1
|
||
{
|
||
l: 'n/-n^n1',
|
||
r: '-n^(1-n1)'
|
||
}, // Joining division with power 2
|
||
{
|
||
l: 'n/n^n1',
|
||
r: 'n^(1-n1)'
|
||
}, // Joining division with power 3
|
||
{
|
||
l: 'n^n1/-n^n2',
|
||
r: 'n^(n1-n2)'
|
||
}, // Joining division with power 4
|
||
{
|
||
l: 'n^n1/n^n2',
|
||
r: 'n^(n1-n2)'
|
||
}, // Joining division with power 5
|
||
{
|
||
l: 'n1+(-n2*n3)',
|
||
r: 'n1-n2*n3'
|
||
}, // Solving useless parenthesis 1
|
||
{
|
||
l: 'v*(-c)',
|
||
r: '-c*v'
|
||
}, // Solving useless unary 2
|
||
{
|
||
l: 'n1+-n2',
|
||
r: 'n1-n2'
|
||
}, // Solving +- together (new!)
|
||
{
|
||
l: 'v*c',
|
||
r: 'c*v'
|
||
}, // inversion constant with variable
|
||
{
|
||
l: '(n1^n2)^n3',
|
||
r: '(n1^(n2*n3))'
|
||
} // Power to Power
|
||
];
|
||
return setRules;
|
||
} // End rulesRationalize
|
||
// ---------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Expand recursively a tree node for handling with expressions with exponents
|
||
* (it's not for constants, symbols or functions with exponents)
|
||
* PS: The other parameters are internal for recursion
|
||
*
|
||
* Syntax:
|
||
*
|
||
* expandPower(node)
|
||
*
|
||
* @param {Node} node Current expression node
|
||
* @param {node} parent Parent current node inside the recursion
|
||
* @param (int} Parent number of chid inside the rercursion
|
||
*
|
||
* @return {node} node expression with all powers expanded.
|
||
*/
|
||
|
||
|
||
function expandPower(node, parent, indParent) {
|
||
var tp = node.type;
|
||
var internal = arguments.length > 1; // TRUE in internal calls
|
||
|
||
if (tp === 'OperatorNode' && node.isBinary()) {
|
||
var does = false;
|
||
var val;
|
||
|
||
if (node.op === '^') {
|
||
// First operator: Parenthesis or UnaryMinus
|
||
if ((node.args[0].type === 'ParenthesisNode' || node.args[0].type === 'OperatorNode') && node.args[1].type === 'ConstantNode') {
|
||
// Second operator: Constant
|
||
val = parseFloat(node.args[1].value);
|
||
does = val >= 2 && isInteger(val);
|
||
}
|
||
}
|
||
|
||
if (does) {
|
||
// Exponent >= 2
|
||
// Before:
|
||
// operator A --> Subtree
|
||
// parent pow
|
||
// constant
|
||
//
|
||
if (val > 2) {
|
||
// Exponent > 2,
|
||
// AFTER: (exponent > 2)
|
||
// operator A --> Subtree
|
||
// parent *
|
||
// deep clone (operator A --> Subtree
|
||
// pow
|
||
// constant - 1
|
||
//
|
||
var nEsqTopo = node.args[0];
|
||
var nDirTopo = new OperatorNode('^', 'pow', [node.args[0].cloneDeep(), new ConstantNode(val - 1)]);
|
||
node = new OperatorNode('*', 'multiply', [nEsqTopo, nDirTopo]);
|
||
} else {
|
||
// Expo = 2 - no power
|
||
// AFTER: (exponent = 2)
|
||
// operator A --> Subtree
|
||
// parent oper
|
||
// deep clone (operator A --> Subtree)
|
||
//
|
||
node = new OperatorNode('*', 'multiply', [node.args[0], node.args[0].cloneDeep()]);
|
||
}
|
||
|
||
if (internal) {
|
||
// Change parent references in internal recursive calls
|
||
if (indParent === 'content') {
|
||
parent.content = node;
|
||
} else {
|
||
parent.args[indParent] = node;
|
||
}
|
||
}
|
||
} // does
|
||
|
||
} // binary OperatorNode
|
||
|
||
|
||
if (tp === 'ParenthesisNode') {
|
||
// Recursion
|
||
expandPower(node.content, node, 'content');
|
||
} else if (tp !== 'ConstantNode' && tp !== 'SymbolNode') {
|
||
for (var i = 0; i < node.args.length; i++) {
|
||
expandPower(node.args[i], node, i);
|
||
}
|
||
}
|
||
|
||
if (!internal) {
|
||
// return the root node
|
||
return node;
|
||
}
|
||
} // End expandPower
|
||
// ---------------------------------------------------------------------------------------
|
||
|
||
/**
|
||
* Auxilary function for rationalize
|
||
* Convert near canonical polynomial in one variable in a canonical polynomial
|
||
* with one term for each exponent in decreasing order
|
||
*
|
||
* Syntax:
|
||
*
|
||
* polyToCanonical(node [, coefficients])
|
||
*
|
||
* @param {Node | string} expr The near canonical polynomial expression to convert in a a canonical polynomial expression
|
||
*
|
||
* The string or tree expression needs to be at below syntax, with free spaces:
|
||
* ( (^(-)? | [+-]? )cte (*)? var (^expo)? | cte )+
|
||
* Where 'var' is one variable with any valid name
|
||
* 'cte' are real numeric constants with any value. It can be omitted if equal than 1
|
||
* 'expo' are integers greater than 0. It can be omitted if equal than 1.
|
||
*
|
||
* @param {array} coefficients Optional returns coefficients sorted by increased exponent
|
||
*
|
||
*
|
||
* @return {node} new node tree with one variable polynomial or string error.
|
||
*/
|
||
|
||
|
||
function polyToCanonical(node, coefficients) {
|
||
if (coefficients === undefined) {
|
||
coefficients = [];
|
||
} // coefficients.
|
||
|
||
|
||
coefficients[0] = 0; // index is the exponent
|
||
|
||
var o = {};
|
||
o.cte = 1;
|
||
o.oper = '+'; // fire: mark with * or ^ when finds * or ^ down tree, reset to "" with + and -.
|
||
// It is used to deduce the exponent: 1 for *, 0 for "".
|
||
|
||
o.fire = '';
|
||
var maxExpo = 0; // maximum exponent
|
||
|
||
var varname = ''; // variable name
|
||
|
||
recurPol(node, null, o);
|
||
maxExpo = coefficients.length - 1;
|
||
var first = true;
|
||
var no;
|
||
|
||
for (var i = maxExpo; i >= 0; i--) {
|
||
if (coefficients[i] === 0) continue;
|
||
var n1 = new ConstantNode(first ? coefficients[i] : Math.abs(coefficients[i]));
|
||
var op = coefficients[i] < 0 ? '-' : '+';
|
||
|
||
if (i > 0) {
|
||
// Is not a constant without variable
|
||
var n2 = new SymbolNode(varname);
|
||
|
||
if (i > 1) {
|
||
var n3 = new ConstantNode(i);
|
||
n2 = new OperatorNode('^', 'pow', [n2, n3]);
|
||
}
|
||
|
||
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]);
|
||
}
|
||
}
|
||
|
||
if (first) {
|
||
no = n1;
|
||
} else if (op === '+') {
|
||
no = new OperatorNode('+', 'add', [no, n1]);
|
||
} else {
|
||
no = new OperatorNode('-', 'subtract', [no, n1]);
|
||
}
|
||
|
||
first = false;
|
||
} // for
|
||
|
||
|
||
if (first) {
|
||
return new ConstantNode(0);
|
||
} else {
|
||
return no;
|
||
}
|
||
/**
|
||
* Recursive auxilary function inside polyToCanonical for
|
||
* converting expression in canonical form
|
||
*
|
||
* Syntax:
|
||
*
|
||
* recurPol(node, noPai, obj)
|
||
*
|
||
* @param {Node} node The current subpolynomial expression
|
||
* @param {Node | Null} noPai The current parent node
|
||
* @param {object} obj Object with many internal flags
|
||
*
|
||
* @return {} No return. If error, throws an exception
|
||
*/
|
||
|
||
|
||
function recurPol(node, noPai, o) {
|
||
var tp = node.type;
|
||
|
||
if (tp === 'FunctionNode') {
|
||
// ***** FunctionName *****
|
||
// No function call in polynomial expression
|
||
throw new Error('There is an unsolved function call');
|
||
} else if (tp === 'OperatorNode') {
|
||
// ***** OperatorName *****
|
||
if ('+-*^'.indexOf(node.op) === -1) throw new Error('Operator ' + node.op + ' invalid');
|
||
|
||
if (noPai !== null) {
|
||
// -(unary),^ : children of *,+,-
|
||
if ((node.fn === 'unaryMinus' || node.fn === 'pow') && noPai.fn !== 'add' && noPai.fn !== 'subtract' && noPai.fn !== 'multiply') {
|
||
throw new Error('Invalid ' + node.op + ' placing');
|
||
} // -,+,* : children of +,-
|
||
|
||
|
||
if ((node.fn === 'subtract' || node.fn === 'add' || node.fn === 'multiply') && noPai.fn !== 'add' && noPai.fn !== 'subtract') {
|
||
throw new Error('Invalid ' + node.op + ' placing');
|
||
} // -,+ : first child
|
||
|
||
|
||
if ((node.fn === 'subtract' || node.fn === 'add' || node.fn === 'unaryMinus') && o.noFil !== 0) {
|
||
throw new Error('Invalid ' + node.op + ' placing');
|
||
}
|
||
} // Has parent
|
||
// Firers: ^,* Old: ^,&,-(unary): firers
|
||
|
||
|
||
if (node.op === '^' || node.op === '*') {
|
||
o.fire = node.op;
|
||
}
|
||
|
||
for (var _i = 0; _i < node.args.length; _i++) {
|
||
// +,-: reset fire
|
||
if (node.fn === 'unaryMinus') o.oper = '-';
|
||
|
||
if (node.op === '+' || node.fn === 'subtract') {
|
||
o.fire = '';
|
||
o.cte = 1; // default if there is no constant
|
||
|
||
o.oper = _i === 0 ? '+' : node.op;
|
||
}
|
||
|
||
o.noFil = _i; // number of son
|
||
|
||
recurPol(node.args[_i], node, o);
|
||
} // for in children
|
||
|
||
} else if (tp === 'SymbolNode') {
|
||
// ***** SymbolName *****
|
||
if (node.name !== varname && varname !== '') {
|
||
throw new Error('There is more than one variable');
|
||
}
|
||
|
||
varname = node.name;
|
||
|
||
if (noPai === null) {
|
||
coefficients[1] = 1;
|
||
return;
|
||
} // ^: Symbol is First child
|
||
|
||
|
||
if (noPai.op === '^' && o.noFil !== 0) {
|
||
throw new Error('In power the variable should be the first parameter');
|
||
} // *: Symbol is Second child
|
||
|
||
|
||
if (noPai.op === '*' && o.noFil !== 1) {
|
||
throw new Error('In multiply the variable should be the second parameter');
|
||
} // Symbol: firers '',* => it means there is no exponent above, so it's 1 (cte * var)
|
||
|
||
|
||
if (o.fire === '' || o.fire === '*') {
|
||
if (maxExpo < 1) coefficients[1] = 0;
|
||
coefficients[1] += o.cte * (o.oper === '+' ? 1 : -1);
|
||
maxExpo = Math.max(1, maxExpo);
|
||
}
|
||
} else if (tp === 'ConstantNode') {
|
||
var valor = parseFloat(node.value);
|
||
|
||
if (noPai === null) {
|
||
coefficients[0] = valor;
|
||
return;
|
||
}
|
||
|
||
if (noPai.op === '^') {
|
||
// cte: second child of power
|
||
if (o.noFil !== 1) throw new Error('Constant cannot be powered');
|
||
|
||
if (!isInteger(valor) || valor <= 0) {
|
||
throw new Error('Non-integer exponent is not allowed');
|
||
}
|
||
|
||
for (var _i2 = maxExpo + 1; _i2 < valor; _i2++) {
|
||
coefficients[_i2] = 0;
|
||
}
|
||
|
||
if (valor > maxExpo) coefficients[valor] = 0;
|
||
coefficients[valor] += o.cte * (o.oper === '+' ? 1 : -1);
|
||
maxExpo = Math.max(valor, maxExpo);
|
||
return;
|
||
}
|
||
|
||
o.cte = valor; // Cte: firer '' => There is no exponent and no multiplication, so the exponent is 0.
|
||
|
||
if (o.fire === '') {
|
||
coefficients[0] += o.cte * (o.oper === '+' ? 1 : -1);
|
||
}
|
||
} else {
|
||
throw new Error('Type ' + tp + ' is not allowed');
|
||
}
|
||
} // End of recurPol
|
||
|
||
} // End of polyToCanonical
|
||
|
||
}); |