mathjs
mathjs copied to clipboard
Extend simplify with rules for `log(e)=1`, `sin(pi)=0`, etc
New issue originating from #925
I thought I'd take a look at this just for something to do. I fixed it for sin, cos, tan and cot by adding if/switch statements for specific cases in the trig function js files e.g. in tan.js
number: Math.tan
goes to
number: function (x) {
// Simple exact values of tan(x) for better simplification
switch ((4 * x / Math.PI) % 4) {
// tan(0), tan(pi), tan(2pi), etc = 0
case 0:
return 0
// tan(pi/4), tan(5pi/4), tan(9pi/4), etc = 1
case 1:
return 1
// tan(pi/2), tan(3pi/2), tan(5pi/2), etc = NaN/+ and - Infinity
case 2:
return NaN
// tan(3pi/4), tan(7pi/4), tan(11pi/4), etc = -1
case 3:
return -1
// If not easy exact value then calculate normally
default:
return Math.tan(x)
}
},
idk if this is the sort of thing you were looking for because it does add in the NaN which isnt ideal and you could just remove that part and let it go to the default. I'm not really familiar with how mathjs is built and I noticed there are 2 versions of lots of the trig functions which seems redundant, sometimes if one is missing it references the other so surely it would be better to just have everything one way round rather than have duplicates.
Anyway I hope this is the sort of thing you were looking for, it does fix the simplification so sin(pi)=0 in every case I could find and the same for the other generic trig functions
I also added tests to check these cases as well and changed cot tests to actually test cot rather than 1/cot for normal numbers, I havent touched BigNumbers, Complex etc
@Veeloxfire thanks for looking into this! This is definitely the direction we should be thinking: replacing tan(pi/4)
and a multiple of that with 1
.
This issue is about algebraic simplification though, not numerical simplification. So we're looking to extend the rules that the simplify
function currently has. So for example:
console.log(math.simplify('log(e)').toString()) // already returns '1'
console.log(math.simplify('sin(pi)').toString()) // should return '0'
console.log(math.simplify('sin(pi / 2)').toString()) // should return '1'
// etc ...
I noticed there are 2 versions of lots of the trig functions
Can you give an example? It may be the difference between numeric only implementation and the implementation supporting multiple data types (number, BigNumber, Fraction, etc)?
In my efforts to enhance simplify() as much as is plausible, I thought I might attack this one. Some guidance would help: should this be construed to extend to identities among the built-in functions as well? In other words, should it include such items as log(exp(n)) -> n
and sqrt(n^2) -> abs(n)
? What about square(n) -> n^2
? etc. etc.
Oh, another specific one I wanted to ask about was phi^2 -> phi+1
(as this is the defining identity of phi) ?
Yes indeed, so we really rely on the meaning that we attach to named functions like log
, exp
, pi
, sin
, sqrt
, cube
, etc. Thinking about that, it will get important to clearly document these assumptions of simplify
(like for example log
may be interpreted as log with base e
like mathjs does, but maybe someone may expect that to be named ln
, and may assume log
means base 10
, anyway, clearly documenting that would be a good idea π
)
Oh, another specific one I wanted to ask about was phi^2 -> phi+1 (as this is the defining identity of phi) ?
Yes that is correct, phi
is defined as (1 + sqrt(5)) / 2
(see docs). However, when simplifying, I can imagine in some cases writing as phi^2
will result in a simpler expression, and in others phi+1
will simplify most? π
Ah, true; we can see how simplify fares with "bidirectional" rules that apply if they (ultimately) reduce the complexity, and if the combinatorial explosion is not too bad, phi^2 <-> phi+1
could be a good candidate for a bidirectional rule.
Yes, it is a nice one π
Request (https://github.com/josdejong/mathjs/issues/2502):
I want to simplify sqrt(x^2)
and sqrt(x)^2
. I expect math.simplify(sqrt(x^2))
and math.simplify(sqrt(x)^2)
to equal x
, but both stay the same after simplifying them. I'm using ^(1/2)
as a workaround for the time being.
Thanks for sharing your use case MarΓa, that helps π
So one question when we get around to this: should sqrt(expr) just simplify to (expr)^(1/2)? Or vice versa? Or simplify to expr^(1/2) at the beginning of simplification with that exponent marked in some way, and then if that exponent is around at the end of simplification convert it back to sqrt? Thoughts are welcome.
That is a good question. To me, sqrt(expr)
reads simpler, it has less parts. I can imagine though that for the simplification it works best to first normalize to exponential form (expr)^(1/2)
, after which it is probably easier to collect and simplify exponents, and only after that write it as sqrt
again. So then you would have the following steps: first a normalization step to optimize for simplification, then the actual simplification, and lastly a step to optimize for human reading.