Skip to content

BUG: Fix np.testing utils failing for masked scalar vs. scalar (#29317) #29318

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

Merged
merged 4 commits into from
Jul 13, 2025

Conversation

danra
Copy link
Contributor

@danra danra commented Jul 4, 2025

Resolves #29317

Fixes np.testing.assert_XXX utilities failing when comparing a masked scalar against another scalar, with a "nan location mismatch" error. The consistent behavior is to ignore any masked elements, in this case the only one, and therefore succeed.

See also #11121

@ngoldbaum
Copy link
Member

What is currently broken? What does this PR change?

I think the reason this PR hasn't been reviewed yet is because it has a blank description and does nontrivial things that aren't obvious just from the PR title.

@danra
Copy link
Contributor Author

danra commented Jul 7, 2025

What is currently broken?

Described in the linked bug: np.testing.assert_XXX utilities fail when comparing a masked scalar against another scalar.

it has a blank description

The title says that it fixes the bug though... I moved a later comment "Resolves #29317" into the description, if that's helpful.

@ngoldbaum
Copy link
Member

Described in the linked bug: np.testing.assert_XXX utilities fail when comparing a masked scalar against another scalar.

I don't think the np.testing functions are designed to work with masked arrays at all, see e.g.: #14624.

I'm not sure it makes sense to do one-off fixes like this, especially this one that does a cast based on the result of an isinstance call, which feels pretty brittle to me.

Much better IMO to add e.g. a new np.ma.testing module that intentionally accepts masked arrays and handles all these cases, just like we have for all the other masked operations.

Sadly masked arrays are somewhat of an unloved feature no one as far as I'm aware it working on any systematic fixes for them like this.

@danra
Copy link
Contributor Author

danra commented Jul 7, 2025

Thanks for the reference to #14624.

Personally, I agree it makes more sense to have a separate np.ma.testing module. However, it looks like there have been numerous commits to the current testing module to support masked arrays; since those had been accepted and merged, I took it as a given that that support does need to be kept in case I make any changes. See for example #11121. But, if there's consensus on that direction, then I'd be happy to make such a split to simplify np.testing even though that's more work than I'd planned :)

I've made these one-off changes not for their own sake, but as incremental fixes towards #28827. I'd described that in some more detail under the Context section of #29317:

Ran into this while working on #28827
- while amending assert_array_compare's nan/inf handling to be able to completely remove the additional inf handling in assert_array_almost_equal
- which is needed to avoid assert_array_almost_equal.compare potentially producing an output shape different from its input
- which in turn would avoid additional logic to produce the correct indices of the max abs/rel errors in the original array.

@ngoldbaum
Copy link
Member

ping @mhvk since you merged a fix a while back along these lines - what do you think?

# support them if possible.
if np.bool(x_id == y_id).all() != True:
result = x_id == y_id
if isinstance(result, bool):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels pretty brittle to me. Is there another way to do this without isinstance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other ways, but have you seen the rationale in the commit message for d1963d9 ?

There are a few ways to resolve this; I went with testing the
comparison result with isinstance(bool) to check if a conversion to
array is necessary, which is the same approach already taken in
assert_array_compare after evaluating comparison(x, y).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference here is that logic has been there a long time, so people have written code to handle it. In this case, you're introducing a possibility for NumPy to suddently generate AttributeErrors in downstream tests when NumPy didn't do that before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a more robust approach

The two added test cases fail with:

E       AssertionError:
E       Arrays are not equal
E
E       nan location mismatch:
E        ACTUAL: MaskedArray(3.)
E        DESIRED: array(3.)

and

E       AssertionError:
E       Arrays are not equal
E
E       nan location mismatch:
E        ACTUAL: MaskedArray(3.)
E        DESIRED: array(nan)
@danra danra force-pushed the compare_masked_scalar branch from 11cff5c to b8d4da1 Compare July 10, 2025 03:12
@danra danra requested a review from ngoldbaum July 10, 2025 03:14
danra added 3 commits July 9, 2025 20:15
…#29317)

TestArrayEqual.test_masked_scalar now passes.

This case regressed since 7315145 (merged in numpy#12119) due to:
- `<masked scalar> == <scalar>` returning np.ma.masked (not a 0-dim
   masked bool array), followed by
- `np.bool(np.ma.masked)` unintentionally converting it to np._False

Note on the modified comment: Confusingly, "isinstance(..., bool)
checks" in the previous wording actually incorrectly referred to the
ones towards the end of the function, which are not actually related
to __eq__'s behavior but to the possibility of `func` returning a bool.
- Use same language as elsewhere below to explain `!= True` used to
  handle np.ma.masked
- Clarify committed to support standard MaskedArrays
- Restore note lost in 7315145 comment changes about how the
  np.bool casts towards the end of the function handle np.ma.masked,
  and expand further.
@danra danra force-pushed the compare_masked_scalar branch from b8d4da1 to faa1ed8 Compare July 10, 2025 03:17
Copy link
Member

@ngoldbaum ngoldbaum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Since this is load-bearing code in testing utilities that are used across the ecosystem I'd appreciate it if someone else could also look this over and think about possible issues before merging.

Copy link
Contributor

@tylerjereddy tylerjereddy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd appreciate it if someone else could also look this over and think about possible issues before merging.

I didn't really think, but I did build this NumPy feature branch from source locally, then built SciPy main from scratch locally against it, and the full testsuite was fine on ARM Mac. I guess astropy may be a nice one to check as well, I believe you already pinged Marten.

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix looks good - I like the extra care (and it reminds me inventing all that trickery in the first place...).

Just some small comments on the added tests.

Aside: in principle, one could use pytest.mark.parametrize for those, but I think this is fine (and probably faster).

@mhvk
Copy link
Contributor

mhvk commented Jul 12, 2025

p.s. I checked this branch on astropy -- no problems. Also, pretty confident this change is all OK.

Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, since the two comments I had made in my previous review were based on a misunderstanding of what the tests were trying to do. Thanks, @danra!

@ngoldbaum
Copy link
Member

Thanks for working on this @danra and for being patient with our caution around changing this code. I hope you continue on your efforts to improve the testing utilities.

@ngoldbaum ngoldbaum merged commit 08916dc into numpy:main Jul 13, 2025
76 checks passed
@danra danra deleted the compare_masked_scalar branch July 13, 2025 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: np.testing utilities fail for masked scalar vs. scalar
4 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy