Trapz dx doesn't trigger array_function mechanism
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 32.8k
- Forks
- 12.8k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 197
Description
It seems that when using numpy's trapz numpy.trapzwith the __array_function__ mechanism, only y and x are caught, but not dx, with numpy's signature numpy.trapz(y, x=None, dx=1.0, axis=-1).
Reproducing code example:
Here is a sample code that creates a numerical-labelled object, basically a value (scalar or array) and a label as a string.
A wrapped version of numpy trapz is created and registered, so that anytime numpy recieves a NumericalLabelled in a trapz call, it relies on the wrapped version. But it seems that the wrapped version is never called when only dx is a NumericalLabelled. I would expect it is called as soon as any of the input is of the type NumericalLabelled.
import numpy as np
HANDLED_FUNCTIONS = {}
class NumericalLabeled():
def __init__(self, value, label=""):
self.value = value
self.label = label
def __repr__(self):
return "NumericalLabelled<"+str(self.value) + "," + self.label+">"
def __array_function__(self, func, types, args, kwargs):
print("Got into array function")
if func not in HANDLED_FUNCTIONS:
return NotImplemented
return HANDLED_FUNCTIONS[func](*args, **kwargs)
def make_numericallabelled(x, label=""):
"""
Helper function to cast anything into a NumericalLabelled object.
"""
if isinstance(x, NumericalLabeled):
return x
else:
return NumericalLabeled(x, label=label)
# Numpy functions
# Override functions - used with __array_function__
def implements(np_function):
def decorator(func):
HANDLED_FUNCTIONS[np_function] = func
return func
return decorator
@implements(np.trapz)
def np_trapz(q, x=None, dx=1, **kwargs):
"""
Numpy's trapz wrapper for NumericalLabelled.
"""
# first convert q into a NumericalLabelled to use `q.value`
q = make_numericallabelled(q)
if x is None:
# using dx.value and dx.label
dx = make_numericallabelled(dx, label="dx")
return NumericalLabeled(np.trapz(q.value, dx=dx.value, x=None, **kwargs),
q.label + dx.label,
)
else:
# using x/value and x.label
x = make_numericallabelled(x, label="x")
return NumericalLabeled(np.trapz(q.value, x=x.value, **kwargs),
q.label + x.label,
)
def main():
# create a scalar to use as dx
half = NumericalLabeled(0.5, "half")
# create an array to use as x
x = NumericalLabeled(np.arange(5), "x")
# then
# this works
print(np.trapz(NumericalLabeled(np.arange(5), "a")))
# this also works
print(np.trapz(np.arange(5), x=x))
# but not this
print(np.trapz(np.arange(5), dx=half))
# TypeError: unsupported operand type(s) for *: 'NumericalLabeled' and 'int'
main()
Error message:
Got into array function
NumericalLabelled<8.0,adx>
Got into array function
NumericalLabelled<8.0,x>
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-30-3401bee8c135> in <module>
68 np.trapz(np.arange(5), dx=half)
69 # TypeError: unsupported operand type(s) for *: 'NumericalLabeled' and 'int'
---> 70 main()
<ipython-input-30-3401bee8c135> in main()
66 np.trapz(np.arange(5), x=x)
67 # but not this
---> 68 np.trapz(np.arange(5), dx=half)
69 # TypeError: unsupported operand type(s) for *: 'NumericalLabeled' and 'int'
70 main()
<__array_function__ internals> in trapz(*args, **kwargs)
/opt/anaconda3/lib/python3.8/site-packages/numpy/lib/function_base.py in trapz(y, x, dx, axis)
4161 slice2[axis] = slice(None, -1)
4162 try:
-> 4163 ret = (d * (y[tuple(slice1)] + y[tuple(slice2)]) / 2.0).sum(axis)
4164 except ValueError:
4165 # Operations didn't work, cast to ndarray
TypeError: unsupported operand type(s) for *: 'NumericalLabeled' and 'int'
NumPy/Python version information:
1.20.0 3.8.5 (default, Sep 4 2020, 02:22:02)
[Clang 10.0.0 ]
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Run the reproducing example with numpy.trapz and a NumericalLabeled object passed as dx. Then inspect numpy/lib/function_base.py at the trapz entry point and the array_function dispatch behavior; done means dx triggers the registered wrapper like y and x and the example no longer raises the TypeError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- data
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100