Permalink
Browse files

Bugfixes in rationalize, extended simplify with new options see #1173

Fix of bug fixes in `rationalize.js`, also changing  `simplify.js` and `simplifyConstant.js` and more  2 bugs in `simplify.js` and `simplifyconstant.js` in order to be possible passing in `Travis` test. 

**Bugs in `simplifyConstant.js` and   `simplify.js`**

1. `simplifyConstant.js` - I've changed `new ConstantNode(stringNumber, 'number')` to `new ConstantNode(number)`

1.  `simplify.js` - Due to problems with a  number node with  string type,  I've added `!isNaN(node.value)))` in number type test condition 

**Bugs in `rationalize.js`**

3.   I've fixed  negative power exponents and decimals coefficients troubles. The decimals coefficients problem has led to the need to add a new feature in `simplify.js ` and `simplifyConstant.js` (next topic)

**New feature in `simplify.js` and `simplifyConstant.js`**

4.  New rule type (string), whose valid values are in `listCommStrings` new variable. The only string rule accepted so far is to turn off exact fraction conversion in `simplifyConstant.js`
  • Loading branch information...
paulobuchsbaum authored and josdejong committed Aug 3, 2018
1 parent 8a3abd5 commit 443d42a7fcea1fb09828e5cf703aa28e2b043005
@@ -8,4 +8,5 @@ node_modules
*.log
dist
lib
.nyc_output
.nyc_output
*.bak
@@ -93,27 +93,37 @@ function factory (type, config, load, typed) {
},
'Node, Object, boolean': function (expr, scope, detailed) {
const polyRet = polynomial(expr, scope, true) // Check if expression is a rationalizable polynomial
const setRules = rulesRationalize() // Rules for change polynomial in near canonical form
const polyRet = polynomial(expr, scope, true, setRules.firstRules) // Check if expression is a rationalizable polynomial
const nVars = polyRet.variables.length
expr = polyRet.expression
if (nVars >= 1) { // If expression in not a constant
const setRules = rulesRationalize() // Rules for change polynomial in near canonical form
expr = expandPower(expr) // First expand power of polynomials (cannot be made from rules!)
let sBefore // Previous expression
let rules
let eDistrDiv = true
let redoInic = false
expr = simplify(expr, setRules.firstRules, {}, {exactFractions: false}) // Apply the initial rules, including succ div rules
let s
while (true) { // Apply alternately successive division rules and distr.div.rules
expr = simplify(expr, setRules.firstRules) // Apply the initial rules, including succ div rules
expr = simplify(expr, setRules.distrDivRules) // and distr.div.rules until no more changes
rules = eDistrDiv ? setRules.distrDivRules : setRules.sucDivRules
expr = simplify(expr, rules) // until no more changes
eDistrDiv = !eDistrDiv // Swap between Distr.Div and Succ. Div. Rules
const s = expr.toString()
if (s === sBefore) break // No changes : end of the loop
s = expr.toString()
if (s === sBefore) {
break // No changes : end of the loop
}
redoInic = true
sBefore = s
}
expr = simplify(expr, setRules.firstRulesAgain)
expr = simplify(expr, setRules.finalRules) // Apply final rules
if (redoInic) { // Apply first rules again without succ div rules (if there are changes)
expr = simplify(expr, setRules.firstRulesAgain, {}, {exactFractions: false})
}
expr = simplify(expr, setRules.finalRules, {}, {exactFractions: false}) // Apply final rules
} // NVars >= 1
const coefficients = []
@@ -155,20 +165,21 @@ function factory (type, config, load, typed) {
*
* Syntax:
*
* polynomial(expr,scope,extended)
* 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) {
function polynomial (expr, scope, extended, rules) {
const variables = []
const node = simplify(expr, scope) // Resolves any variables and functions with all defined parameters
const node = simplify(expr, rules, scope, {exactFractions: false}) // Resolves any variables and functions with all defined parameters
extended = !!extended
const oper = '+-*' + (extended ? '/' : '')
@@ -201,14 +212,11 @@ function factory (type, config, load, typed) {
// No function call in polynomial expression
throw new Error('There is an unsolved function call')
} else if (tp === 'OperatorNode') {
if (node.op === '^' && node.isBinary()) {
if (node.args[1].op === '-' && node.args[1].isUnary()) {
if (node.args[1].args[0].type !== 'ConstantNode' || !number.isInteger(parseFloat(node.args[1].args[0].value))) {
throw new Error('There is a non-integer exponent')
} else {
recPoly(node.args[0])
}
} else if (node.args[1].type !== 'ConstantNode' || !number.isInteger(parseFloat(node.args[1].value))) {
if (node.op === '^') {
if (node.args[1].fn === 'unaryMinus') {
node = node.args[0]
}
if (node.args[1].type !== 'ConstantNode' || !number.isInteger(parseFloat(node.args[1].value))) {
throw new Error('There is a non-integer exponent')
} else {
recPoly(node.args[0])
@@ -254,7 +262,6 @@ function factory (type, config, load, typed) {
{l: 'n*(n1^-1)', r: 'n/n1'},
{l: 'n*n1^-n2', r: 'n/n1^n2'},
{l: 'n1^-1', r: '1/n1'},
{l: 'n1^-n2', r: '1/n1^n2'},
{l: 'n*(n1/n2)', r: '(n*n1)/n2'},
{l: '1*n', r: 'n'}]
@@ -266,6 +273,11 @@ function factory (type, config, load, typed) {
{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
@@ -301,7 +313,7 @@ function factory (type, config, load, typed) {
// Second rule set.
// There is no aggregate expression with parentesis, but the only variable can be scattered.
setRules.finalRules = [ simplifyCore, // simplify.rules[0]
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
@@ -81,47 +81,61 @@ function factory (type, config, load, typed, math) {
*/
const simplify = typed('simplify', {
'string': function (expr) {
return simplify(parse(expr), simplify.rules, {})
return simplify(parse(expr), simplify.rules, {}, {})
},
'string, Object': function (expr, scope) {
return simplify(parse(expr), simplify.rules, scope)
return simplify(parse(expr), simplify.rules, scope, {})
},
'string, Object, Object': function (expr, scope, options) {
return simplify(parse(expr), simplify.rules, scope, options)
},
'string, Array': function (expr, rules) {
return simplify(parse(expr), rules, {})
return simplify(parse(expr), rules, {}, {})
},
'string, Array, Object': function (expr, rules, scope) {
return simplify(parse(expr), rules, scope)
return simplify(parse(expr), rules, scope, {})
},
'string, Array, Object, Object': function (expr, rules, scope, options) {
return simplify(parse(expr), rules, scope, options)
},
'Node, Object': function (expr, scope) {
return simplify(expr, simplify.rules, scope)
return simplify(expr, simplify.rules, scope, {})
},
'Node, Object, Object': function (expr, scope, options) {
return simplify(expr, simplify.rules, scope, options)
},
'Node': function (expr) {
return simplify(expr, simplify.rules, {})
return simplify(expr, simplify.rules, {}, {})
},
'Node, Array': function (expr, rules) {
return simplify(expr, rules, {})
return simplify(expr, rules, {}, {})
},
'Node, Array, Object': function (expr, rules, scope) {
rules = _buildRules(rules)
return simplify(expr, rules, scope, {})
},
'Node, Array, Object, Object': function (expr, rules, scope, options) {
rules = _buildRules(rules)
let res = resolve(expr, scope)
res = removeParens(res)
let visited = {}
let str = res.toString({parenthesis: 'all'})
while (!visited[str]) {
visited[str] = true
_lastsym = 0 // counter for placeholder symbols
for (let i = 0; i < rules.length; i++) {
if (typeof rules[i] === 'function') {
res = rules[i](res)
res = rules[i](res, options)
} else {
flatten(res)
res = applyRule(res, rules[i])
@@ -130,7 +144,6 @@ function factory (type, config, load, typed, math) {
}
str = res.toString({parenthesis: 'all'})
}
return res
}
})
@@ -261,10 +274,11 @@ function factory (type, config, load, typed, math) {
switch (ruleType) {
case 'string':
const lr = rule.split('->')
if (lr.length !== 2) {
if (lr.length === 2) {
rule = {l: lr[0], r: lr[1]}
} else {
throw SyntaxError('Could not parse rule: ' + rule)
}
rule = {l: lr[0], r: lr[1]}
/* falls through */
case 'object':
newRule = {
@@ -11,7 +11,9 @@ function factory (type, config, load, typed, math) {
const OperatorNode = math.expression.node.OperatorNode
const FunctionNode = math.expression.node.FunctionNode
function simplifyConstant (expr) {
let optionsGlobal // Global options for "simplifyConstant"
function simplifyConstant (expr, options) {
optionsGlobal = (options === undefined ? {} : options)
const res = foldFraction(expr)
return type.isNode(res) ? res : _toNode(res)
}
@@ -41,9 +43,9 @@ function factory (type, config, load, typed, math) {
},
'BigNumber': function (n) {
if (n < 0) {
return unaryMinusNode(new ConstantNode(n.negated().toString(), 'number'))
return unaryMinusNode(new ConstantNode(-n))
}
return new ConstantNode(n.toString(), 'number')
return new ConstantNode(n) // old parameters: (n.toString(), 'number')
},
'Complex': function (s) {
throw new Error('Cannot convert Complex number to Node')
@@ -52,7 +54,9 @@ function factory (type, config, load, typed, math) {
// convert a number to a fraction only if it can be expressed exactly
function _exactFraction (n) {
if (isFinite(n)) {
// 'optionGlobal' is declared in simplifyConstant's factory function
const exactFraction = (optionsGlobal.exactFractions !== false)
if (exactFraction && isFinite(n)) {
const f = math.fraction(n)
if (f.valueOf() === n) {
return f
@@ -143,7 +147,7 @@ function factory (type, config, load, typed, math) {
case 'SymbolNode':
return node
case 'ConstantNode':
if (typeof node.value === 'number') {
if (typeof node.value === 'number' || !isNaN(node.value)) {
return _toNumber(node.value)
}
return node
@@ -98,6 +98,12 @@ describe('rationalize', function () {
assert.equal(stri(math.rationalize('x*5')), '5*x')
})
it('aditional simple expressions', function () {
assert.equal(stri(math.rationalize('1/(0.1x+1)+1')), '(0.1*x+2)/(0.1*x+1)')
assert.equal(stri(math.rationalize('1/x^2+1')), '(x^2+1)/x^2')
assert.equal(stri(math.rationalize('1/(x/10+1)+1')), '(0.1*x+2)/(0.1*x+1)')
})
it('processing 2 variable expressions', function () {
assert.equal(stri(math.rationalize('x+y')), 'x+y')
assert.equal(stri(math.rationalize('x^2 + 2*x*y + 3')), 'x^2+2*x*y+3')
@@ -126,12 +132,6 @@ describe('rationalize', function () {
'(-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)')
})
it('should handle non integer powers correctly', function () {
assert.equal(stri(math.rationalize('1/x^2+1')), '(x^2+1)/x^2')
assert.equal(stri(math.rationalize('1/(0.1x+1)+1')), '(10*x+200)/(10*x+100)')
assert.equal(stri(math.rationalize('1/(0.125x+1)+1')), '(8*x+128)/(8*x+64)')
})
it('testing scope', function () {
assert.equal(stri(math.rationalize('x+x+x+y', {y: 1})), '3*x+1')
assert.equal(stri(math.rationalize('x+x+x+y', {})), '3*x+y')
@@ -3,12 +3,26 @@ const assert = require('assert')
const math = require('../../../src/main')
describe('simplify', function () {
function simplifyAndCompare (left, right, scope) {
function simplifyAndCompare (left, right, rules, scope, opt) {
try {
if (scope) {
assert.equal(math.simplify(left, scope).toString(), math.parse(right).toString())
if (Array.isArray(rules)) {
if (opt) {
assert.equal(math.simplify(left, rules, scope, opt).toString(), math.parse(right).toString())
} else if (scope) {
assert.equal(math.simplify(left, rules, scope).toString(), math.parse(right).toString())
} else {
assert.equal(math.simplify(left, rules).toString(), math.parse(right).toString())
}
} else {
assert.equal(math.simplify(left).toString(), math.parse(right).toString())
if (scope) opt = scope
if (rules) scope = rules
if (opt) {
assert.equal(math.simplify(left, scope, opt).toString(), math.parse(right).toString())
} else if (scope) {
assert.equal(math.simplify(left, scope).toString(), math.parse(right).toString())
} else {
assert.equal(math.simplify(left).toString(), math.parse(right).toString())
}
}
} catch (err) {
if (err instanceof Error) {
@@ -277,6 +291,15 @@ describe('simplify', function () {
simplifyAndCompare('x-0', 'x')
})
it('new options parameters', function () {
simplifyAndCompare('0.1*x', 'x/10')
simplifyAndCompare('0.1*x', 'x/10', math.simplify.rules, {}, {exactFractions: true})
simplifyAndCompare('0.1*x', '0.1*x', math.simplify.rules, {}, {exactFractions: false})
simplifyAndCompare('y+0.1*x', 'x/10+1', {y: 1})
simplifyAndCompare('y+0.1*x', 'x/10+1', {y: 1}, {exactFractions: true})
simplifyAndCompare('y+0.1*x', '0.1*x+1', {y: 1}, {exactFractions: false})
})
it('resolve() should substitute scoped constants', function () {
assert.equal(
math.simplify.resolve(math.parse('x+y'), {x: 1}).toString(),

0 comments on commit 443d42a

Please sign in to comment.