Skip to content

CCDData raises KeyError when passed into numpy functions on numpy 1.24 #15397

Closed

Description

@NanoExplorer

Description

After installing astropy earlier today, I ran into an issue that I hadn't seen before. When passing a CCDData instance into np.min(), I receive a vague KeyError.

Expected behavior

In numpy 1.23/astropy 5.1, np.min(CCDData) would return the minimum value of the CCDData array.

Is this expected behavior?

Even if np.min(CCDData) should not be allowed due to its handling of units, the exception produced does not indicate that.

How to Reproduce

  1. mamba install astropy numpy
import numpy as np
from astropy import nddata
a=nddata.CCDData([1,2,3],unit="adu")
np.min(a)

expected / old result:
1 (notably, units were not preserved)

new result:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/miniforge3/envs/mamba_test/lib/python3.11/site-packages/astropy/nddata/mixins/ndarithmetic.py:243, in NDArithmeticMixin._arithmetic(self, operation, operand, propagate_uncertainties, handle_mask, handle_meta, uncertainty_correlation, compare_wcs, operation_ignores_mask, axis, **kwds)
    242 try:
--> 243     kwds2[splitted[0]][splitted[1]] = kwds[i]
    244 except KeyError:

KeyError: 'out'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
Cell In[10], line 1
----> 1 np.min(a)

File <__array_function__ internals>:200, in amin(*args, **kwargs)

File ~/miniforge3/envs/mamba_test/lib/python3.11/site-packages/numpy/core/fromnumeric.py:2946, in amin(a, axis, out, keepdims, initial, where)
   2829 @array_function_dispatch(_amin_dispatcher)
   2830 def amin(a, axis=None, out=None, keepdims=np._NoValue, initial=np._NoValue,
   2831          where=np._NoValue):
   2832     """
   2833     Return the minimum of an array or minimum along an axis.
   2834 
   (...)
   2944     6
   2945     """
-> 2946     return _wrapreduction(a, np.minimum, 'min', axis, None, out,
   2947                           keepdims=keepdims, initial=initial, where=where)

File ~/miniforge3/envs/mamba_test/lib/python3.11/site-packages/numpy/core/fromnumeric.py:84, in _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs)
     82             return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
     83         else:
---> 84             return reduction(axis=axis, out=out, **passkwargs)
     86 return ufunc.reduce(obj, axis, dtype, out, **passkwargs)

File ~/miniforge3/envs/mamba_test/lib/python3.11/site-packages/astropy/nddata/mixins/ndarithmetic.py:644, in NDArithmeticMixin.min(self, **kwargs)
    640 @sharedmethod
    641 def min(self, **kwargs):
    642     # use the provided propagate_uncertainties if available, otherwise default is False:
    643     propagate_uncertainties = kwargs.pop("propagate_uncertainties", None)
--> 644     return self._prepare_then_do_arithmetic(
    645         np.min, propagate_uncertainties=propagate_uncertainties, **kwargs
    646     )

File ~/miniforge3/envs/mamba_test/lib/python3.11/site-packages/astropy/nddata/mixins/ndarithmetic.py:746, in NDArithmeticMixin._prepare_then_do_arithmetic(self_or_cls, operation, operand, operand2, **kwargs)
    737     result, init_kwds = cls._arithmetic(
    738         operand,
    739         operation,
    740         operand2,
    741         **kwargs,
    742     )
    743 else:
    744     # otherwise call the _arithmetic method on self for a collapse operation:
    745     # for collapse operations, use astropy.utils.masked rather than handle_mask
--> 746     result, init_kwds = self_or_cls._arithmetic(
    747         operation,
    748         operand2,
    749         **kwargs,
    750     )
    752 # Return a new class based on the result
    753 return cls(result, **init_kwds)

File ~/miniforge3/envs/mamba_test/lib/python3.11/site-packages/astropy/nddata/mixins/ndarithmetic.py:245, in NDArithmeticMixin._arithmetic(self, operation, operand, propagate_uncertainties, handle_mask, handle_meta, uncertainty_correlation, compare_wcs, operation_ignores_mask, axis, **kwds)
    243         kwds2[splitted[0]][splitted[1]] = kwds[i]
    244     except KeyError:
--> 245         raise KeyError(f"Unknown prefix {splitted[0]} for parameter {i}")
    247 kwargs = {}
    249 # First check that the WCS allows the arithmetic operation

KeyError: 'Unknown prefix out for parameter out'

Versions

Linux-4.19.294-1-MANJARO-x86_64-with-glibc2.38
Python 3.11.5 | packaged by conda-forge | (main, Aug 27 2023, 03:34:09) [GCC 12.3.0]
astropy 5.3.3
Numpy 1.24.4
pyerfa 2.0.0.3
Scipy 1.11.2
Matplotlib 3.8.0

Activity

  1. github-actions commented on Sep 27, 2023

    @github-actions
    Contributor

    Welcome to Astropy 👋 and thank you for your first issue!

    A project member will respond to you as soon as possible; in the meantime, please double-check the guidelines for submitting issues and make sure you've provided the requested details.

    GitHub issues in the Astropy repository are used to track bug reports and feature requests; If your issue poses a question about how to use Astropy, please instead raise your question in the Astropy Discourse user forum and close this issue.

    If you feel that this issue has not been responded to in a timely manner, please send a message directly to the development mailing list. If the issue is urgent or sensitive in nature (e.g., a security vulnerability) please send an e-mail directly to the private e-mail [email protected].

  2. pllim commented on Sep 27, 2023

    @pllim
    Member

    That is because np.min has "out" now and that signature is somehow passed into kwds here and crashing:

    for i in kwds:
    splitted = i.split("_", 1)
    try:
    kwds2[splitted[0]][splitted[1]] = kwds[i]
    except KeyError:
    raise KeyError(f"Unknown prefix {splitted[0]} for parameter {i}")

    But then again, I am not sure if this is a supported usage. Our CI does not run this (hence we did not catch this incompatibility).

    Why not use it like this?

    >>> a.min()
    CCDData(1, unit='adu')
  3. pllim commented on Sep 27, 2023

    @pllim
    Member

    @mwcraig , is np.min(CCDData) supported?

  4. NanoExplorer commented on Sep 27, 2023

    @NanoExplorer
    Author

    I think it makes sense to use a.min() when possible. Right now it's another library that's using np.min(a). I can send them a pull request to switch.

    If it isn't supported, would it be possible to make that clear from the exception?

  5. pllim commented on Sep 27, 2023

    @pllim
    Member

    would it be possible to make that clear from the exception?

    I'll have to defer to @mwcraig or @bmorris3 on this one.

    Though when they lost unit using np.min(a) to begin with, it would have been a strong hint that the usage is wonky?

  6. bmorris3 commented on Sep 27, 2023

    @bmorris3
    Contributor

    @mwcraig , is np.min(CCDData) supported?

    The numpy functions should be supported by NDData (or at least they were?), because many of numpy functions like np.min(a) check for a built-in method with the same name on the the input object , like a.min(). Here's where that happens in the numpy source. So numpy will use the built-in implementation on NDData when NDData are passed and that method is available.

    The traceback in this issue arises from an unexpected call signature, which is still surprising to me. @pllim – did I understand your comment (#15397 (comment)) correctly that the numpy.min call signature changed?

  7. pllim commented on Sep 27, 2023

    @pllim
    Member

    It did change but maybe not "out". I vaguely remember discussion about "where" but even that was a while ago.

    https://numpy.org/devdocs/reference/generated/numpy.min.html

  8. mhvk commented on Sep 27, 2023

    @mhvk
    Contributor

    The difference indeed seems to be that np.min passes on out=None. I think it can be solved fairly easily by adding out=None to the _arithmetic method (and raising if out is not None; this is done in Time too.).

    Note that in principle people can also pass keepdims, so perhaps that needs some code too...

  9. pllim commented on Sep 27, 2023

    @pllim
    Member

    Still, this usage was never tested in the CI. Hmm...

  10. pllim commented on Sep 27, 2023

    @pllim
    Member

    Also, even if you add out and keepdims to the mixin, there is still a matter of losing unit.

  11. mwcraig commented on Sep 28, 2023

    @mwcraig
    Member

    @NanoExplorer -- thanks for the detailed report!

    I don't recall ever intending that np.min woudn't work, though at the time the code was written using .min() was the main way we expected folks to do it.

    With the new xarray standard we are going to need to find a way to support functions like np.min as well as we have methods in the past.

    It seems like the short-term fix should be to implement what @mhvk suggested -- I can aim to do that over the weekend, but if you want to give it a shot, @NanoExplorer, let me know.

    Not sure how to handle the units....

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions