Conversation
numpy/core/tests/test_umath.py
Outdated
| #real part less than zero imag part greater than zero | ||
| assert_complex_equal(np.power(zero,-1+1j),cnan) | ||
| #real part less than zero imag part less than zero | ||
| assert_complex_equal(np.power(zero,-2-3j),cnan) |
There was a problem hiding this comment.
Can you split these out and use pytest.warns()? That way it is explicitly about which case gives warnings and which does not. (and the test will notify it if it does not work properly due to a change or weird platform)
|
@eric-wieser @seberg I have made the changes. Please review my pull request. |
|
@eric-wieser @seberg friendly reminder. I don't mean to bother you but you haven't reviewed my pull request since the last changes. Please take a look. |
|
A couple of notes. Sorry, I have to dig into some things or I won't make progress there. I thin kthis goes in the right direction, but I wonder if we can't simplify the code quite a bit.
|
|
@seberg I have checked cpow in C99 and C11 and both are present and working fine. Also could you please explain your third point as I am not able to understand it? |
|
Cool, thanks, sorry very briefly. This code block optimizes integer powers: numpy/numpy/core/src/npymath/npy_math_complex.c.src Lines 470 to 506 in a157224 And the last part optimizes all between -100 and 100, although I can't say I understand the algorithm, so it probably makes sense :). |
|
@seberg friendly reminder. I have made the changes please take a look. |
|
close/reopen |
|
Can you update the PR description to reference the bit of the IEEE standard that says this is correct behavior? |
|
@eric-wieser I have made the changes. Please take a look. |
|
@seberg friendly reminder. Please review my pull request. |
|
@seberg I apologize for tagging you again and again but you haven't reviewed my pull request. I just wanted to remind you in case it slipped. |
|
@seberg friendly reminder. Please review my Pull request. |
seberg
left a comment
There was a problem hiding this comment.
Sorry, I am still confused about the warning flags a bit. And now also about the +0 and -0 dependency, which the (deleted) NB: comment allures too.
| return npy_cpack@c@(1.,0.); | ||
| } | ||
| /*Here it is already tested that for a^b, b is not zero. | ||
| So we are checking behaviour of a.*/ |
There was a problem hiding this comment.
I don't think thois comment is necessary? It might make sense in the "generating an invalid" branch below.
There was a problem hiding this comment.
@seberg Could you please explain what do you mean by generating an invalid as I am not able to understand it
There was a problem hiding this comment.
I mean moving it down to the branch where you return NaN manually. Maybe its a bit of a code layout thing in the diff, but I think that branch is the place where you may want to point out that 0^0 is already handled.
There was a problem hiding this comment.
All right got it. Thanks
| //Generating an invalid | ||
| volatile @type@ tmp=NPY_INFINITY@C@; | ||
| tmp-=NPY_INFINITY@C@; | ||
| ar=tmp; |
There was a problem hiding this comment.
Please fix the spaces here again, no operators without spaces around them.
There was a problem hiding this comment.
@seberg Here is what I found.
The first section discusses how the complex exponentiation is done.
Second section deals with the sign.
Complex Exponentiation
If z and w are two complex numbers then
z^w = e^{w*(ln|z|+i*arg(z} *e^{i*w*(2*n*pi)}
which means that z^w is multivalued. We call the value at n=0 the principle value.
Now, here I will discuss the case of 0^{non-zero complex number} which is related to our issue.
0^w = e^{w * (ln(0) + i*arg(0,0))}*e^{ i*w*(2*n *pi)}
Lets call this equation 1.
Now arg is calculated with the help of atan2. Mathematically undefined but is realized in computer languages.
As we are considering C language, for unsigned 0, we get the output as 0. For signed numbers, we get the
output 0, pi, -0, -pi. This is demonstrated with the following C program.
#include<stdio.h>
#include<math.h>
int main(){
double a=atan2(0,0);
double b=atan2(+0.0,+0.0);
double c=atan2(+0.0,-0.0);
double d=atan2(-0.0,+0.0);
double e=atan2(-0.0,-0.0);
printf("%lf\n%lf\n%lf\n%lf\n%lf\n",a,b,c,d,e);
return 0;
}Output
0.000000
0.000000
3.141593
-0.000000
-3.141593
Therefore e^{i*arg(0,0)} will be +1 or -1 depending upon values of ar and ai (0 but still signed).
Now, let's spilt equation 1 into three parts.
Part 1: e^{ w*ln|0| } (ln|0| = -inf)
Part 2: e^{ i*arg(0,0) }= ±1
Part 3: e^{ i*w*(2n*pi)}
For complex number w , we get 4 cases
br = 0, bi != 0
br != 0, bi = 0
br = 0, bi != 0
br != 0, bi != 0
The case for br=0 and bi=0 leads to 0^0 which is defined to be 1 by IEEE.
For case br != 0, bi = 0, we have
if br > 0 we have
Part 1= e^{ br* -inf }= 0
Part2 = ±1 (actual sign depending on signs of ar and ai)
Part3 = dont care
Result = ±0
if br < 0 we have
Part 1= e^{ br*-inf }= inf
Part 2= ±1
Part 3= oscillates between positive and negative value
Therefore this leads to indeterminate form of inf*0 and
the result is undefined . (This might also expalin why C
generates (inf,nan) as its output when encountered with
such values. In our case we generate (nan,nan).
For br = 0, bi != 0
if bi > 0
Part 1: e^{ i*bi*-inf}
which can be written as
cos(bi*-inf)+isin(bi*-inf)
which is undefined.
Part 2: don't care
Part 3: don't care
Result: undefined
if bi < 0
Same as above because this time we get
+inf instead of minus inf.
For br !=0 and bi != 0
Part 1: e^{ w * -inf}
=e^{br*-inf} * e^{i*bi*-inf} (w=br + i*bi)
Therefore if br > 0 this goes down to 0
if not, the result oscillates and is undifined.
Part 2 and 3 are dont care.
All the cases are summarized in the table below
| bi < 0 | bi = 0 | bi > 0 | |
|---|---|---|---|
| br < 0 | undef | undef | undef |
| br = 0 | undef | 1 | undef |
| br > 0 | 0 | 0 | 0 |
Value of 0 ^ {complex}
Note: 0s are signed.
Combining with the complex exponentiation table
| exp = 0 | exp = non-zero | |
|---|---|---|
| mantissa = 0 | 1 | Table above |
| mantissa = non-zero | 1 | Already handled |
As we learned that complex exponentiation is multi-valued and the actual
signs depend on many factors such as signs of ar and ai and whether the API
is selecting principle value or not. Also 0+0i, 0-0i,-0+0i and -0-0i correspond
to the same point on the complex plane. So as long the result is 0 it mathematically
does not affect the result
I am ready to go with whatever you suggest. If you suggest going with signs (the resulting 0 contains sign opposite of bi as the C program below shows.)
I will make changes in the code accordingly. If you say its fine just make the code according to numpy C style I will do that.
#include<stdio.h>
#include<math.h>
#include<complex.h>
int main(){
complex mantissa[]={ 0.0+0.0*I, 0.0-0.0*I, -0.0+0.0*I, -0.0-0.0*I};
complex exponents[]={1.0+1.0*I, 1.0-1.0*I, -1.0+1.0*I, -1.0-1.0*I};
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
complex c=cpow(mantissa[i],exponents[j]);
printf("%lf + %lf *I\n",creal(c),cimag(c));
}
}
return 0;
}Output:
0.000000 + -0.000000 *I
0.000000 + 0.000000 *I
inf + -nan *I
inf + -nan *I
0.000000 + -0.000000 *I
0.000000 + 0.000000 *I
inf + -nan *I
inf + -nan *I
0.000000 + -0.000000 *I
0.000000 + 0.000000 *I
inf + -nan *I
inf + -nan *I
0.000000 + -0.000000 *I
0.000000 + 0.000000 *I
inf + -nan *I
inf + -nan *I
References:
Complex Eponentiation
atan2
There was a problem hiding this comment.
Sorry for taking a while to respond. That anlysis sounds great, and atan2 is a seems like a great "basis" to base for a decision, I think, thanks! (I have not followed every step, I admit)
My feeling is that we should try to preserve the signs for the 0 return. And the fact that GCC gives a floating point warnings seems like a small issue in glibc? After all, atan2 makes a point of getting this right as well. Also the old code-comment to me reads like: Let's return NaN, because the sign means that returning 0 would not be quite right.
So my personal judgment would be: return the correct sign here, in the special case! And IIRC the rest already looked good. I double checked the C99 standard, but could not find anything relevant (the cpow documentation is pretty much a stub).
I think we should open an issue on gcc/glibc about the warning flags being set for those signed 0 returns. In the best case scenario we learn something new. In the other best case scenario glibc stops giving spurious warnings ;).
There was a problem hiding this comment.
Ah GLIBC issue of course, I have opened a bug report, hopefully in the right place here: https://sourceware.org/bugzilla/show_bug.cgi?id=27875
|
Otherwise, the main thing is that it would be nice if you can clean up the code to adher to NumPy C-style. |
|
@seberg Friendly reminder. I have made the changes. Please review my Pull request. |
seberg
left a comment
There was a problem hiding this comment.
Just some nits and the tightening the tests. Otherwise, I think this is good to go in. Thanks!
seberg
left a comment
There was a problem hiding this comment.
Thanks, looks good to me now. I am considering if we should add a release note, but in general, I think it can go in as-is.
|
@seberg I think we should add a release note (in the changes section or improvements section whichever you suggest). Please guide me on how to add a release note and I will add it. Many thanks. 😃 |
|
You have to add a brief |
|
I had meant to just fix up the release notes myself a bit (pushed a change, feel free to overwrite). But now I noticed that we are not handling |
|
@seberg Thanks for enhancing the release note 😄 . I also thought of adding this note but then thought this is documentation related so I didn't add added it in the release note. |
Co-authored-by: Ivan Gonzalez <scratchmex@gmail.com>
Co-authored-by: Ivan Gonzalez <scratchmex@gmail.com>
Co-authored-by: Matti Picus <matti.picus@gmail.com> Co-authored-by: Ivan Gonzalez <scratchmex@gmail.com>
f067731 to
2805164
Compare
Hi @charris, sorry for the delay. I've committed the suggestions and rebased with the latest main now. |
|
@seberg Are you good with this? |
|
Sorry, I am burned on this. I would like someone else to make up their mind carefully and then, yes I am happy. But I am not Kahan and all standards I am aware of bail on this beyond saying |
Sorry :) I just wanted to get it off the 1.24 checklist, and away it goes. |
|
I punted on all the places this says the value is defined by IEEE 754 since we have not seen such a definition, and think we should put this in since it replaces an unjustified NAN result. |
Co-authored-by: Matti Picus <matti.picus@gmail.com>
Co-authored-by: Matti Picus <matti.picus@gmail.com>
Co-authored-by: Matti Picus <matti.picus@gmail.com>
|
@mattip gentle ping. I have updated the PR according to changes suggested. Please take a look. |
|
Thanks @prithvitewatia |
The IEEE standard 754 2008 is used in the development of floating-point libraries.
The 0^0 case is discussed in Wikipedia.
For the case of 0^non-zero, the result is the same as that of C99.
Side note: C99 for negative real in complex powers generates inf + nan but we are generating nan + nan as
it is consistent with our previous warning.
Resolves #18378.