Thanks to visit codestin.com
Credit goes to github.com

Skip to content

BUG: np.expm1 returns unexpected values in special cases with complex numbers #21746

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

Open
steff456 opened this issue Jun 13, 2022 · 4 comments
Open
Labels

Comments

@steff456
Copy link
Contributor

Describe the issue:

Writing the spec for complex number support in the Array API we found that NumPy currently fails in 4 special cases. The behavior is inconsistent with np.exp(z)-1 and C99 as shown in the code example.

The full list of special cases and specification is detailed in data-apis/array-api#452

cc @kgryte

Reproduce the code example:

import numpy as np

def is_equal(x, y):
    """Test whether two complex numbers are equal with special consideration for NaNs.

    Parameters
    ----------
    x : complex
        First input number.
    y : complex
        Second input number.

    Returns
    -------
    bool
        Boolean indicating whether two complex numbers are equal.

    Examples
    --------
    >>> import numpy as np
    >>> is_equal(complex(np.nan, np.nan), complex(np.nan, np.nan))
    True
    """
    re1 = y.real
    im1 = y.imag

    re2 = x.real
    im2 = x.imag
    if re1 == re1:
        if im1 == im1:
            # Second value has non-NaN real and imaginary components, so test for component equality:
            return (re1 == re2) and (im1 == im2)
        
        if im2 == im2:
            # Second value has a NaN imaginary component, but first value does not:
            return False
        
        # Both values have NaN imaginary components:
        return True

    if im1 == im1:
        # Second value has a NaN real component, but a non-NaN imaginary component...
        if re2 == re2:
            # First value has a non-NaN real component:
            return False
        
        # Both values have NaN real components, so test for imaginary component equality:
        return (im1 == im2)

    if re2 == re2 or im2 == im2:
        # Second value has real and imaginary components which are NaN, but first value does not:
        return False

    # Both values have real and imaginary components which are NaN:
    return True

def compare(v, e):
    actual = np.expm1(v) 
    print('Value: {value}'.format(value=str(v)))
    print('Actual: {actual}'.format(actual=str(actual)))
    print('Expected: {expected}'.format(expected=str(e)))
    print('Equal: {is_equal}'.format(is_equal=str(is_equal(actual, e))))
    print('\n')

# Case 1
v = complex(np.inf, 0.0)
e = complex(np.inf, 0.0)
compare(v, e) # np.exmp1 returns (inf+nanj), vs np.exp(complex(np.inf, 0.0))-1.0 == (inf+0j)

# Case 2
v = complex(-np.inf, np.inf)
e = complex(-1.0, 0.0)
compare(v, e) # np.exmp1 returns (nan+nanj), vs np.exp(complex(-np.inf, np.inf))-1.0 == (-1+0j)

# Case 3
v = complex(-np.inf, np.nan)
e = complex(-1.0, 0.0)
compare(v, e) # np.exmp1 returns (nan+nanj), vs np.exp(complex(-np.inf, np.nan))-1.0 == (-1+0j)

# Case 4
v = complex(np.nan, 0.0)
e = complex(np.nan, 0.0)
compare(v, e) # np.exmp1 returns (nan+nanj), vs np.exp(complex(np.nan, 0.0))-1.0 == (nan+0j)

Error message:

Value: (inf+0j)
Actual: (inf+nanj)
Expected: (inf+0j)
Equal: False

Value: (-inf+infj)
Actual: (nan+nanj)
Expected: (-1+0j)
Equal: False

Value: (-inf+nanj)
Actual: (nan+nanj)
Expected: (-1+0j)
Equal: False

Value: (nan+0j)
Actual: (nan+nanj)
Expected: (nan+0j)
Equal: False

NumPy/Python version information:

v1.22.4

@seberg
Copy link
Member

seberg commented Jun 16, 2022

Do you know if there is a reference implementation for a complex expm1 that would not suffer from these things? I am surprised C99 bothers to define the 3rd and 4th case for complex exp, as I thought all complex NaNs and Infs are usually considered equivalent. But so be it.

However, I did a brief search right now, and did not yet find a complex expm1 implementation, does one of the other libs pass these tests and include one that we can "steal"?

EDIT: I suppose our implementation may be good, and just needs a few special cases. But it would still be nice to find a reference implementation for comparison.

@kgryte
Copy link

kgryte commented Jun 20, 2022

@steff456 I updated data-apis/array-api#452 and, in the process, found 2 additional special cases in which NumPy's behavior for certain special cases deviates from the naive np.exp(x)-1.0. We should update the OP accordingly.

@seberg I think the main concern is that special case behavior for np.expm1(z) and np.exp(z) - 1.0 should, more or less, be the same. As NumPy's np.exp(z) currently satisfies expected complex number handling (see data-apis/array-api#451), I think the course of action would be to make np.exp and np.expm1 consistent wrt particular combinations of np.inf and np.nan, which is what we've tried to do at the standards level. In which case, I think you are right in that NumPy's implementation likely just needs a few additional special cases for complex number input.

@steff456
Copy link
Contributor Author

After digging a little bit and trying these cases in multiple OS with different architectures I found that this corner cases are present in macOS Intel, macOS ARM and Windows x64, but the corner cases are working as expected in Linux Intel and Linux ARM so probably the issue is related with the compiler of C99.

Hope this info is useful!

@seberg
Copy link
Member

seberg commented Jul 11, 2022

Thanks @steff456. As far as I understand for the API test-suite the idea is currently to try to inform about failing corner cases but not fail on it.
For NumPy, I would not spend any specific effort on it (i.e. if someone has a use case and fixes it, sure we can probably merge.).

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

No branches or pull requests

3 participants