Comparing version 1.0.4 to 1.1.0
@@ -17,1 +17,2 @@ /* | ||
module.exports = nerdamer; | ||
@@ -9,3 +9,3 @@ { | ||
"license": "MIT", | ||
"version": "1.0.4", | ||
"version": "1.1.0", | ||
"homepage": "http://nerdamer.com/", | ||
@@ -12,0 +12,0 @@ "directory": { |
@@ -0,1 +1,3 @@ | ||
[![Build Status](https://travis-ci.org/jiggzson/nerdamer.svg?branch=master)](https://travis-ci.org/jiggzson/nerdamer) | ||
Nerdamer | ||
@@ -2,0 +4,0 @@ ======== |
214
Solve.js
@@ -34,2 +34,4 @@ /* | ||
FN = core.groups.FN, | ||
Settings = core.Settings, | ||
range = core.Utils.range, | ||
isArray = core.Utils.isArray; | ||
@@ -56,3 +58,13 @@ | ||
core.Settings.NON_LINEAR_START = 0.01; | ||
//When points are generated as starting points for Newton's method, they are sliced into small | ||
//slices to make sure that we have convergence on the right point. This defines the | ||
//size of the slice | ||
core.Settings.NEWTON_SLICES = 200; | ||
//The epsilon used in Newton's iteration | ||
core.Settings.NEWTON_EPSILON = Number.EPSILON*2; | ||
//The distance in which two solutions are deemed the same | ||
core.Settings.SOLUTION_PROXIMITY = 1e-14; | ||
//Indicate wheter to filter the solutions are not | ||
core.Settings.FILTER_SOLUTIONS = true; | ||
core.Symbol.prototype.hasTrig = function () { | ||
@@ -95,3 +107,5 @@ return this.containsFunction(['cos', 'sin', 'tan', 'cot', 'csc', 'sec']); | ||
var eqn = this.removeDenom(); | ||
return _.expand(_.subtract(eqn.LHS, eqn.RHS));; | ||
var _t = _.subtract(eqn.LHS, eqn.RHS); | ||
var retval = _.expand(_t); | ||
return retval; | ||
}, | ||
@@ -169,3 +183,22 @@ removeDenom: function () { | ||
core.Expression.prototype.solveFor = function (x) { | ||
return solve(core.Utils.isSymbol(this.symbol) ? this.symbol : this.symbol.toLHS(), x).map(function (x) { | ||
var symbol; | ||
if(this.symbol instanceof Equation) { | ||
//exit right away if we already have the answer | ||
//check the LHS | ||
if(this.symbol.LHS.isConstant() && this.symbol.RHS.equals(x)) | ||
return new core.Expression(this.symbol.LHS); | ||
//check the RHS | ||
if(this.symbol.RHS.isConstant() && this.symbol.LHS.equals(x)) | ||
return new core.Expression(this.symbol.RHS); | ||
//otherwise just bring it to LHS | ||
symbol = this.symbol.toLHS(); | ||
} | ||
else { | ||
symbol = this.symbol; | ||
} | ||
return solve(symbol, x).map(function (x) { | ||
return new core.Expression(x); | ||
@@ -546,4 +579,4 @@ }); | ||
quad: function (c, b, a) { | ||
var bsqmin4ac = _.subtract(_.pow(b.clone(), Symbol(2)), _.multiply(_.multiply(a.clone(), c.clone()), Symbol(4)))/*b^2 - 4ac*/; | ||
var det = _.pow(bsqmin4ac, Symbol(0.5)); | ||
var discriminant = _.subtract(_.pow(b.clone(), Symbol(2)), _.multiply(_.multiply(a.clone(), c.clone()), Symbol(4)))/*b^2 - 4ac*/; | ||
var det = _.pow(discriminant, Symbol(0.5)); | ||
var retval = [ | ||
@@ -693,11 +726,14 @@ _.parse(_.divide(_.add(b.clone().negate(), det.clone()), _.multiply(new Symbol(2), a.clone()))), | ||
* @param {Number} step | ||
* @param {Array} points | ||
* @returns {Array} | ||
*/ | ||
getPoints: function (symbol, step) { | ||
getPoints: function (symbol, step, points) { | ||
step = step || 0.01; | ||
points = points || []; | ||
var f = build(symbol); | ||
var start = Math.round(f(0)), | ||
var x0 = 0; | ||
var start = Math.round(x0), | ||
last = f(start), | ||
last_sign = last / Math.abs(last), | ||
points = [], | ||
rside = core.Settings.ROOTS_PER_SIDE, // the max number of roots on right side | ||
@@ -711,32 +747,34 @@ lside = rside * 2 + 1; // the max number of roots on left side | ||
symbol.each(function (x) { | ||
if (x.containsFunction('log')) | ||
if (x.containsFunction(core.Settings.LOG)) | ||
points.push(0.1); | ||
}); | ||
// Possible issue #1. If the step size exceeds the zeros then they'll be missed. Consider the case | ||
// where the function dips to negative and then back the positive with a step size of 0.1. The function | ||
// will miss the zeros because it will jump right over it. Think of a case where this can happen. | ||
for (var i = start; (i) < core.Settings.SOLVE_RADIUS; i++) { | ||
var val = f(i * step), | ||
sign = val / Math.abs(val); | ||
if (isNaN(val) || !isFinite(val) || points.length > rside) { | ||
break; | ||
var left = range(-core.Settings.SOLVE_RADIUS, start, step), | ||
right = range(start, core.Settings.SOLVE_RADIUS, step); | ||
var test_side = function(side, num_roots) { | ||
var xi, val, sign; | ||
var hits = []; | ||
for(var i=0, l=side.length; i<l; i++) { | ||
xi = side[i]; //the point being evaluated | ||
val = f(xi); | ||
sign = val / Math.abs(val); | ||
//Don't add non-numeric values | ||
if (isNaN(val) || !isFinite(val) || hits.length > num_roots) { | ||
continue; | ||
} | ||
//compare the signs. The have to be different if they cross a zero | ||
if (sign !== last_sign) { | ||
hits.push(xi); //take note of the possible zero location | ||
} | ||
last_sign = sign; | ||
} | ||
//compare the signs. The have to be different if they cross a zero | ||
if (sign !== last_sign) { | ||
points.push((i - 1) / 2); //take note of the possible zero location | ||
} | ||
last_sign = sign; | ||
} | ||
//check the other side | ||
for (var i = start - 1; i > -core.Settings.SOLVE_RADIUS; i--) { | ||
var val = f(i), | ||
sign = val / Math.abs(val); | ||
if (isNaN(val) || !isFinite(val) || points.length > lside) | ||
break; | ||
//compare the signs. The have to be different if they cross a zero | ||
if (sign !== last_sign) | ||
points.push((i - 1) / 2); //take note of the possible zero location | ||
last_sign = sign; | ||
} | ||
points = points.concat(hits); | ||
}; | ||
test_side(left, lside); | ||
test_side(right, rside); | ||
return points; | ||
@@ -756,2 +794,3 @@ }, | ||
} | ||
iter++; | ||
@@ -765,5 +804,7 @@ if (iter > maxiter) | ||
} | ||
while (e > Number.EPSILON) | ||
return x; | ||
while (e > Settings.NEWTON_EPSILON) | ||
//check if the number is indeed zero. 1e-13 seems to give the most accurate results | ||
if(Math.abs(f(x)) <= 1e-13) | ||
return x; | ||
}, | ||
@@ -895,3 +936,2 @@ rewrite: function (rhs, lhs, for_variable) { | ||
var solve = function (eqns, solve_for, solutions) { | ||
//make preparations if it's an Equation | ||
@@ -982,2 +1022,4 @@ if (eqns instanceof Equation) { | ||
numvars = vars.length;//how many variables are we dealing with | ||
//if we're dealing with a single variable then we first check if it's a | ||
@@ -1186,10 +1228,16 @@ //polynomial (including rationals).If it is then we use the Jenkins-Traubb algorithm. | ||
var points3 = __.getPoints(eq, 0.01); | ||
var points = core.Utils.arrayUnique(points1.concat(points2).concat(points3)), | ||
l = points.length; | ||
var points = core.Utils.arrayUnique(points1.concat(points2).concat(points3)).sort(function(a, b) { return a-b}); | ||
//generate slices | ||
//points = core.Utils.arrayAddSlices(points, Settings.NEWTON_SLICES); | ||
//compile the function and the derivative of the function | ||
var f = build(eq.clone()); | ||
var d = _C.diff(eq.clone()); | ||
var fp = build(d); | ||
for (var i = 0; i < l; i++) { | ||
for (var i = 0; i < points.length; i++) { | ||
var point = points[i]; | ||
add_to_result(__.Newton(point, f, fp), has_trig); | ||
@@ -1200,3 +1248,3 @@ } | ||
catch(e) { | ||
; | ||
console.log(e); | ||
} | ||
@@ -1210,37 +1258,47 @@ } | ||
try { | ||
var coeffs = core.Utils.getCoeffs(eq, solve_for); | ||
var factored = core.Algebra.Factor.factor(eq.clone()); | ||
if(factored.group === CB) { | ||
factored.each(function(x) { | ||
add_to_result(solve(x, solve_for)); | ||
}); | ||
} | ||
else { | ||
var coeffs = core.Utils.getCoeffs(eq, solve_for); | ||
var l = coeffs.length, | ||
deg = l - 1; //the degree of the polynomial | ||
//get the denominator and make sure it doesn't have x | ||
var l = coeffs.length, | ||
deg = l - 1; //the degree of the polynomial | ||
//get the denominator and make sure it doesn't have x | ||
//handle the problem based on the degree | ||
switch (deg) { | ||
case 0: | ||
var separated = separate(eq); | ||
var lhs = separated[0], | ||
rhs = separated[1]; | ||
if (lhs.group === core.groups.EX) { | ||
add_to_result(_.parse(core.Utils.format(core.Settings.LOG+'(({0})/({2}))/'+core.Settings.LOG+'({1})', rhs, lhs.value, lhs.multiplier))); | ||
} | ||
break; | ||
case 1: | ||
//nothing to do but to return the quotient of the constant and the LT | ||
//e.g. 2*x-1 | ||
add_to_result(_.divide(coeffs[0], coeffs[1].negate())); | ||
break; | ||
case 2: | ||
add_to_result(__.quad.apply(undefined, coeffs)); | ||
break; | ||
case 3: | ||
add_to_result(__.cubic.apply(undefined, coeffs)); | ||
break; | ||
case 4: | ||
add_to_result(__.quartic.apply(undefined, coeffs)); | ||
break; | ||
default: | ||
add_to_result(__.csolve(eq, solve_for)); | ||
if (solutions.length === 0) | ||
add_to_result(__.divideAndConquer(eq, solve_for)); | ||
} | ||
} | ||
//handle the problem based on the degree | ||
switch (deg) { | ||
case 0: | ||
var separated = separate(eq); | ||
var lhs = separated[0], | ||
rhs = separated[1]; | ||
if (lhs.group === core.groups.EX) { | ||
add_to_result(_.parse(core.Utils.format('log(({0})/({2}))/log({1})', rhs, lhs.value, lhs.multiplier))); | ||
} | ||
break; | ||
case 1: | ||
//nothing to do but to return the quotient of the constant and the LT | ||
//e.g. 2*x-1 | ||
add_to_result(_.divide(coeffs[0], coeffs[1].negate())); | ||
break; | ||
case 2: | ||
add_to_result(__.quad.apply(undefined, coeffs)); | ||
break; | ||
case 3: | ||
add_to_result(__.cubic.apply(undefined, coeffs)); | ||
break; | ||
case 4: | ||
add_to_result(__.quartic.apply(undefined, coeffs)); | ||
break; | ||
default: | ||
add_to_result(__.csolve(eq, solve_for)); | ||
if (solutions.length === 0) | ||
add_to_result(__.divideAndConquer(eq, solve_for)); | ||
} | ||
} | ||
@@ -1272,3 +1330,3 @@ catch (e) { /*something went wrong. EXITING*/ | ||
} | ||
else if(lhs.fname === 'log') { | ||
else if(lhs.fname === core.Settings.LOG) { | ||
//ax+b comes back as [a, x, ax, b]; | ||
@@ -1328,3 +1386,3 @@ var parts = explode(lhs.args[0], solve_for); | ||
} | ||
return solutions; | ||
@@ -1376,2 +1434,2 @@ }; | ||
nerdamer.api(); | ||
})(); | ||
})(); |
@@ -396,3 +396,3 @@ /* global expect */ | ||
// then | ||
expect(result).toBe('(1+2*x)^2*(1/4)'); | ||
expect(result).toBe('(1/4)*(1+2*x)^2'); | ||
}); | ||
@@ -418,6 +418,6 @@ | ||
expected: '(1+x)^2' | ||
}, | ||
}, | ||
{ | ||
given: 'factor(x^2-y^2)', | ||
expected: '(-y+x)*(x+y)' | ||
expected: '-(-x+y)*(x+y)' | ||
}, | ||
@@ -462,7 +462,15 @@ { | ||
given: 'factor(sqrt(4*x^2*y+4*x^2))', | ||
expected: '(2)*(abs(x))*(sqrt(1+y))' | ||
expected: '2*abs(x)*sqrt(1+y)' | ||
}, | ||
{ | ||
given: 'factor(x^3-1/2x^2-13/2x-3)', | ||
expected: '(-3+x)*(1+2*x)*(1/2)*(2+x)' | ||
expected: '(1/2)*(-3+x)*(1+2*x)*(2+x)' | ||
}, | ||
{ | ||
given: 'factor(x^16-1)', | ||
expected: '(-1+x)*(1+x)*(1+x^2)*(1+x^4)*(1+x^8)' | ||
}, | ||
{ | ||
given: 'factor(-1866240-311040*x^2-3265920*x+1120*x^8+150080*x^6+17610*x^7+2026080*x^4+2509920*x^3+30*x^9+738360*x^5)', | ||
expected: '10*(-1+x)*(1+x)*(3*x+4)*(6+x)^6' | ||
} | ||
@@ -480,2 +488,7 @@ ]; | ||
it('should not have any regression to factor', function() { | ||
//this test will absolutely break as factor improves enough to factor this expression. For now it just serves as a safeguard | ||
expect(nerdamer('factor(x^a+2x^(a-1)+1x^(a-2))').toString()).toEqual('2*x^(-1+a)+x^(-2+a)+x^a'); | ||
}); | ||
it('should correctly determine the polynomial degree', function () { | ||
@@ -702,3 +715,3 @@ // given | ||
given: 'simplify(cos(x)^2+sin(x)^2+cos(x)-tan(x)-1+sin(x^2)^2+cos(x^2)^2)', | ||
expected: '-(-1-cos(x)+tan(x))' | ||
expected: '-tan(x)+1+cos(x)' | ||
}, | ||
@@ -733,2 +746,6 @@ { | ||
expected: '(29/53)*i+22/53' | ||
}, | ||
{ | ||
given: 'simplify(((17/2)*(-5*K+32)^(-1)*K^2+(5/2)*K-125*(-5*K+32)^(-1)*K-16+400*(-5*K+32)^(-1))*(-17*(-5*K+32)^(-1)*K+80*(-5*K+32)^(-1))^(-1))', | ||
expected: '-(-112-4*K^2+35*K)*(-80+17*K)^(-1)' | ||
} | ||
@@ -735,0 +752,0 @@ ]; |
@@ -54,2 +54,3 @@ /* global expect */ | ||
it('should respect modulus with percentages', function(){expect(parse('8000%%8')).toEqual(0);}); | ||
it('should correctly handle modulus left assoc', function(){expect(parse('3*3%9')).toEqual(0);}); | ||
}); | ||
@@ -125,2 +126,8 @@ | ||
describe('Setting vector values', function() { | ||
it('should set values of vectors with the assign operator', function() { | ||
expect(parse('[1,2][1]:x').toString()).toEqual('[1,x]'); | ||
}); | ||
}); | ||
describe('Substitutions', function(){ | ||
@@ -127,0 +134,0 @@ it('should substitute x', function() {expect(parse('x+1', {x: 4})).toEqual(5);}); |
@@ -53,3 +53,3 @@ /* global expect */ | ||
TeX: '\\frac{3 \\cdot x^{\\frac{2}{3}}}{4}', | ||
decimalTeX: '0.75 \\cdot x^{0.666666666666666666666666666666666666667}' | ||
decimalTeX: '0.75 \\cdot x^{0.6666666666666666666666666666666666666666666666666666666666666666666666666666667}' | ||
}, { | ||
@@ -56,0 +56,0 @@ given: '4*cos(x)', |
@@ -78,2 +78,3 @@ /* global expect */ | ||
{ | ||
//NOTE: this test has duplicates | ||
given: 'solve(sqrt(x^3)+sqrt(x^2)-sqrt(x)=0,x)', | ||
@@ -92,3 +93,3 @@ expected: '[0,78202389238903801/240831735646702201]' | ||
given: 'solve(x=2/(3-x),x)', | ||
expected: '[1,2]' | ||
expected: '[2,1]' | ||
}, | ||
@@ -114,2 +115,4 @@ { | ||
}, | ||
//The tests below were disabled. Too verbose. | ||
/* | ||
{ | ||
@@ -122,2 +125,3 @@ given: 'solve(cos(x), x)', | ||
}, | ||
{ | ||
@@ -131,2 +135,3 @@ given: 'solve(cos(x)*x+1-cos(x), x)', | ||
}, | ||
*/ | ||
{ | ||
@@ -147,2 +152,4 @@ given: 'solve(a*x^3+b*x+c, x)', | ||
}, | ||
//The tests below are incorrect and have no solutions | ||
/* | ||
{ | ||
@@ -156,2 +163,3 @@ given: 'solve(log(x,2)+log(x,3)=log(x,5), x)', | ||
}, | ||
*/ | ||
{ | ||
@@ -187,4 +195,5 @@ given: 'solve((1/2)*sqrt(-4*x+4*y)-2+y, y)', | ||
//NOTE: 4503599627370497/4503599627370496 result can be safely removed since it has rounding errors | ||
//NOTE: this test has duplicate solutions. The last two are duplicates of the first but have rounding errors | ||
given: 'solve(sqrt(x)-2x+x^2,x)', | ||
expected: '[(1/2)*(-sqrt(5)+3),0,1,4503599627370497/4503599627370496]' | ||
expected: '[(1/2)*(-sqrt(5)+3),0,1,832040/2178309]' | ||
}, | ||
@@ -269,5 +278,14 @@ { | ||
it('parse equations correctly', function () { | ||
it('should parse equations correctly', function () { | ||
expect(nerdamer("-(a+1)=(a+3)^2").toString()).toEqual('-1-a=(3+a)^2'); | ||
}); | ||
//NOTE: contains duplicates | ||
it('should solve functions with factorials', function() { | ||
expect(nerdamer('solve(x!-x^2,x)').text()).toEqual('[-2.2003917826105948,-4.010232827899529,-2.938361683501947,1,1.0000000000000009,1.0000000000000007,3.5623822853908957,3.5623822853908966,0.9999999999999998,1.0000000000000002]'); | ||
}); | ||
xit('should solve factors', function() { | ||
expect(nerdamer('solve((x-1)*(-a*c-a*x+c*x+x^2),x)').text()).toEqual('[1,-c,a]'); | ||
}); | ||
}); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1248402
24584
399