yt-project / unyt

Handle, manipulate, and convert data with units in Python
https://unyt.readthedocs.io
BSD 3-Clause "New" or "Revised" License
364 stars 48 forks source link

Support np.vectorize and np.frompyfunc #141

Open ngoldbaum opened 4 years ago

ngoldbaum commented 4 years ago

Description

Right now using a function wrapped with np.vectorize or np.frompyfunc raises an error.

This was originally reported against yt as https://github.com/yt-project/yt/issues/2465.

What I Did

Running this script:

import unyt as u
import numpy as np

def myfunc(a, b):
    return a + b

vmyfunc = np.vectorize(myfunc)

print(vmyfunc([1, 2, 3]*u.g, [1, 2, 3]*u.kg))

Produces the following traceback:

Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(vmyfunc([1, 2, 3]*u.g, [1, 2, 3]*u.kg))
  File "/home/goldbaum/.pyenv/versions/3.7.3/lib/python3.7/site-packages/numpy/lib/function_base.py", line 2091, in __call__
    return self._vectorize_call(func=func, args=vargs)
  File "/home/goldbaum/.pyenv/versions/3.7.3/lib/python3.7/site-packages/numpy/lib/function_base.py", line 2167, in _vectorize_call
    outputs = ufunc(*inputs)
  File "/home/goldbaum/Documents/unyt/unyt/array.py", line 1692, in __array_ufunc__
    unit_operator = self._ufunc_registry[ufunc]
KeyError: <ufunc '? (vectorized)'>

Similarly:

import unyt as u
import numpy as np

def myfunc(a, b):
    return a + b

vmyfunc = np.frompyfunc(myfunc, 2, 1)

print(vmyfunc([1, 2, 3]*u.g, [1, 2, 3]*u.kg))
Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(vmyfunc([1, 2, 3]*u.g, [1, 2, 3]*u.kg))
  File "/home/goldbaum/Documents/unyt/unyt/array.py", line 1692, in __array_ufunc__
    unit_operator = self._ufunc_registry[ufunc]
KeyError: <ufunc '? (vectorized)'>

For what it's worth, astropy produces exactly the same error:

Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(vmyfunc([1, 2, 3]*u.g, [1, 2, 3]*u.kg))
  File "/home/goldbaum/.pyenv/versions/3.7.3/lib/python3.7/site-packages/numpy/lib/function_base.py", line 2091, in __call__
    return self._vectorize_call(func=func, args=vargs)
  File "/home/goldbaum/.pyenv/versions/3.7.3/lib/python3.7/site-packages/numpy/lib/function_base.py", line 2167, in _vectorize_call
    outputs = ufunc(*inputs)
  File "/home/goldbaum/.pyenv/versions/3.7.3/lib/python3.7/site-packages/astropy/units/quantity.py", line 446, in __array_ufunc__
    converters, unit = converters_and_unit(function, method, *inputs)
  File "/home/goldbaum/.pyenv/versions/3.7.3/lib/python3.7/site-packages/astropy/units/quantity_helper/converters.py", line 157, in converters_and_unit
    ufunc_helper = UFUNC_HELPERS[function]
  File "/home/goldbaum/.pyenv/versions/3.7.3/lib/python3.7/site-packages/astropy/units/quantity_helper/converters.py", line 89, in __missing__
    .format(ufunc.__name__))
TypeError: unknown ufunc ? (vectorized).  If you believe this ufunc should be supported, please raise an issue on https://github.com/astropy/astropy
ngoldbaum commented 4 years ago

I think the most straightforward way to support this would be to cast the arguments to ndarray and just ignore the units. That's more useful than completely erroring out. We could probably also print a warning saying that the units are being ignored.

I don't think we can support ufuncs generated via frompyfunc without degrading to ignoring the units because we'd have no ability to introspect and determine what the units should be.