Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What should 0**I be? #16478

Open
oscarbenjamin opened this issue Mar 28, 2019 · 6 comments
Open

What should 0**I be? #16478

oscarbenjamin opened this issue Mar 28, 2019 · 6 comments

Comments

@oscarbenjamin
Copy link
Contributor

I get inconsistent results with 0**I:

In [1]: r = Pow(0, I, evaluate=False)                                                                                                          

In [2]: r                                                                                                                                      
Out[2]:0 

In [3]: r.doit()                                                                                                                               
Out[3]: nan

In [4]: r.evalf()                                                                                                                              
Out[4]: 0

In [5]: abs(r)                                                                                                                                 
Out[5]: 1

In [6]: r.is_zero                                                                                                                              
Out[6]: False

One way to think about this is the limit of x**I as x -> 0 which (for positive x) is the limit of exp(I*log(x)) = cos(log(x)) + I*sin(log(x). In that case we have abs(r)=1 and r.is_zero is False which makes sense. Also r.doit should give nan since the phase is unknowable. Then we should say that r.evalf() is incorrect and the other results are fine.

I'm sure there are other ways to think about this that lead to different conclusions though.

Taking a slightly different example:

In [8]: r = Pow(0, 1+I, evaluate=False)                                                                                                        

In [9]: r                                                                                                                                      
Out[9]: 
 1 +0     

In [10]: r.doit()                                                                                                                              
Out[10]: nan

In [11]: r.evalf()                                                                                                                             
Out[11]: 0

In [12]: abs(r)                                                                                                                                
Out[12]: 0

In [13]: r.is_zero   # None

Here we could see this as the limit of exp((1+I)*log(x)) = x*(cos(log(x)+I*sin(log(x)) as x->0 which would be 0. Then evalf and abs are correct but is_zero and doit are wrong.

In [14]: r = Pow(0, -1+I, evaluate=False)                                                                                                      

In [15]: r                                                                                                                                     
Out[15]: 
 -1 +0      

In [16]: r.doit()                                                                                                                              
Out[16]: nan

In [17]: r.evalf()                                                                                                                             
Out[17]: 0

In [18]: abs(r)                                                                                                                                
Out[18]: nan

In [19]: r.is_zero # None

Following the same reasoning as above this is the limit of (1/x)*(cos(log(x))+I*sin(log(x))) which should be zoo. Then doit should give zoo, abs should give oo, is_zero should be True, evalf should give ...?

@jksuom
Copy link
Member

jksuom commented Mar 29, 2019

I think that the results should be nan. That is often the best value for a limit that does not exist.

@oscarbenjamin
Copy link
Contributor Author

I think that the abs of these limits can be well defined:

In [16]: abs(x**I).limit(x, 0)                                                                                                                 
Out[16]: 1

In [17]: abs(x**(1+I)).limit(x, 0)                                                                                                             
Out[17]: 0

In [18]: abs(x**(-1+I)).limit(x, 0)                                                                                                            
Out[18]:

That would suggest:

0**I --> nan
0**(1+I) --> 0
0**(-1+I) --> zoo

@jksuom
Copy link
Member

jksuom commented Mar 29, 2019

I think that one should consider all values of a multi-valued function like x**(1 + I). They are
a*exp((1 + I)*2*pi*I*n) for an integer n. There are always arbitrarily large and small values among these. Hence I think that nan is the only proper value even if the principal branch had a definite limit.

@oscarbenjamin
Copy link
Contributor Author

I had considered that and it does make sense. Following that reasoning we should have:

0**I
doit --> nan (currently correct)
evalf --> nan (currently 0)
abs --> 1 (I think this is still correct...)
is_zero --> False (currently correct)

0**(1+I)
doit --> nan (currently correct)
evalf --> nan (currently 0)
abs --> nan (currently 0)
is_zero --> None (currently correct)

0**(-1+I)
doit --> nan (currently correct)
evalf --> nan (currently 0)
abs --> nan (currently correct)
iz_zero --> None (currently correct)

So I still think that abs(0**I) should be 1 (as it currently is) but open to suggestions.

Otherwise evalf is wrong in all these cases and should give nan.

abs(0**(1+I)) is incorrect and should be nan rather than 0.

Do you agree?

@jksuom
Copy link
Member

jksuom commented Mar 30, 2019

Do you agree?

I do. Explicit limits can be used to obtain better results but I think that nan is often the safest choice for direct substitution.

@oscarbenjamin
Copy link
Contributor Author

I have more examples.

(-oo)**0

In [69]: p = Pow(-oo, 0, evaluate=False)                                                                                                       

In [70]: p                                                                                                                                     
Out[70]: 
  0
-∞ 

In [71]: p.doit()                                                                                                                              
Out[71]: 1

In [72]: p.evalf()                                                                                                                             
Out[72]: 1.00000000000000

In [73]: p.is_zero                                                                                                                             
Out[73]: False

I can give different limits for (-oo)**0 that give different answers though:

In [99]: ((-x)**(1/x)).limit(x, oo)                                                                                                            
Out[99]: 1

In [100]: ((-exp(x))**(1/x)).limit(x, oo)                                                                                                      
Out[100]: ℯ

In [101]: ((-exp(x**2))**(1/x)).limit(x, oo)                                                                                                   
Out[101]: ∞

In [102]: ((-exp(x**2))**(-1/x)).limit(x, oo)                                                                                                  
Out[102]: 0

I'm not sure if those limits are correct but I think that (-oo)**0 should be nan for both doit and evalf. is_zero should give None.

0**0

In [74]: p = Pow(0, 0, evaluate=False)                                                                                                         

In [75]: p                                                                                                                                     
Out[75]: 
 0
0 

In [76]: p.doit()                                                                                                                              
Out[76]: 1

In [77]: p.evalf()                                                                                                                             
Out[77]: 1.00000000000000

In [78]: p.is_zero                                                                                                                             
Out[78]: False

This has been discussed in #5359 and it was decided that it should be like this for consistency with Python:

In [79]: 0**0                                                                                                                                  
Out[79]: 1

That's awkward though since I don't see any consistent way to justify that while fixing everything else.

zoo**0

In [80]: p = Pow(zoo, 0, evaluate=False)                                                                                                       

In [81]: p                                                                                                                                     
Out[81]: 
   0
zoo 

In [82]: p.doit()                                                                                                                              
Out[82]: 1

In [83]: p.evalf()                                                                                                                             
Out[83]: 1.00000000000000

In [84]: p.is_zero                                                                                                                             
Out[84]: False

1**oo

In [85]: p = Pow(1, oo, evaluate=False)                                                                                                        

In [86]: p                                                                                                                                     
Out[86]:1 

In [87]: p.doit()                                                                                                                              
Out[87]: nan

In [88]: p.evalf()                                                                                                                             
Out[88]: nan

In [89]: p.is_zero                                                                                                                             
Out[89]: False

This gives nan but I think is_zero should be None since

In [105]: ((1-1/sqrt(x))**x).limit(x, oo)                                                                                                      
Out[105]: 0

(-1)**oo

In [90]: p = Pow(-1, oo, evaluate=False)                                                                                                       

In [91]: p                                                                                                                                     
Out[91]: 
    ∞
(-1) 

In [92]: p.doit()                                                                                                                              
Out[92]: nan

In [93]: p.evalf()                                                                                                                             
Out[93]: nan

In [94]: p.is_zero                                                                                                                             
Out[94]: False

Again is_zero should be None since

In [106]: ((-1+1/sqrt(x))**x).limit(x, oo)                                                                                                     
Out[106]: 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants