mathjs icon indicating copy to clipboard operation
mathjs copied to clipboard

Extend simplify with rules for `log(e)=1`, `sin(pi)=0`, etc

Open josdejong opened this issue 7 years ago β€’ 11 comments

New issue originating from #925

josdejong avatar Aug 19 '17 12:08 josdejong

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 avatar Dec 28 '19 16:12 Veeloxfire

@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)?

josdejong avatar Dec 31 '19 11:12 josdejong

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.

gwhitney avatar Jan 16 '22 03:01 gwhitney

Oh, another specific one I wanted to ask about was phi^2 -> phi+1 (as this is the defining identity of phi) ?

gwhitney avatar Jan 16 '22 04:01 gwhitney

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? 😁

josdejong avatar Jan 23 '22 09:01 josdejong

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.

gwhitney avatar Jan 27 '22 21:01 gwhitney

Yes, it is a nice one πŸ‘

josdejong avatar Jan 30 '22 09:01 josdejong

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.

meramos avatar Mar 29 '22 04:03 meramos

Thanks for sharing your use case MarΓ­a, that helps πŸ‘

josdejong avatar Mar 29 '22 06:03 josdejong

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.

gwhitney avatar Mar 29 '22 14:03 gwhitney

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.

josdejong avatar Mar 30 '22 13:03 josdejong