-
Notifications
You must be signed in to change notification settings - Fork 89
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
refactor: move typetracer ufunc handling to backend [1 of 2] #2150
Changes from all commits
b3e541b
f52401e
b65aa4d
1958bc8
5be618c
03ef002
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
from awkward.typing import Callable, Final, Tuple, TypeAlias, TypeVar, Unpack | ||
|
||
np = NumpyMetadata.instance() | ||
numpy = ak._nplikes.Numpy.instance() | ||
|
||
|
||
T = TypeVar("T", covariant=True) | ||
|
@@ -20,7 +21,10 @@ | |
|
||
|
||
class Backend(Singleton, ABC): | ||
name: str | ||
@property | ||
@abstractmethod | ||
def name(self) -> str: | ||
raise ak._errors.wrap_error(NotImplementedError) | ||
|
||
@property | ||
@abstractmethod | ||
|
@@ -163,6 +167,34 @@ def __init__(self): | |
def __getitem__(self, index: KernelKeyType) -> TypeTracerKernel: | ||
return TypeTracerKernel(index) | ||
|
||
def _coerce_ufunc_argument(self, x): | ||
if isinstance(x, ak._typetracer.TypeTracerArray): | ||
return numpy.empty((0,) + x.shape[1:], dtype=x.dtype) | ||
# Convert scalars to 0-d arrays | ||
elif isinstance(x, ak._typetracer.UnknownScalar): | ||
return numpy.empty((0,), dtype=x.dtype) | ||
elif x is ak._typetracer.UnknownLength: | ||
return numpy.empty((0,), dtype=np.int64) | ||
elif isinstance(x, ak._typetracer.MaybeNone): | ||
return self._coerce_ufunc_argument(x.content) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jpivarski this is one part I'm hesitant on. We're throwing away type information here because this might not fail at runtime, i.e. if the value is not actually none. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do want this, should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And, what should we do about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our strategy is for typetracers to give code the benefit of the doubt and let it fail at runtime. The typetracer is not a type-check. So if a typetracer (including If a typetracer represents There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, that sounds like the best path to me, too. Let's go with the safe, minimal approach and see what happens. I haven't implemented |
||
else: | ||
return x | ||
|
||
def apply_ufunc(self, ufunc, method, args, kwargs): | ||
shape = None | ||
numpy_args = [] | ||
|
||
for x in args: | ||
if isinstance(x, ak._typetracer.TypeTracerArray): | ||
x.touch_data() | ||
shape = x.shape | ||
|
||
numpy_args.append(self._coerce_ufunc_argument(x)) | ||
|
||
assert shape is not None | ||
tmp = getattr(ufunc, method)(*numpy_args, **kwargs) | ||
return self._typetracer.empty((shape[0],) + tmp.shape[1:], dtype=tmp.dtype) | ||
|
||
|
||
def _backend_for_nplike(nplike: ak._nplikes.NumpyLike) -> Backend: | ||
# Currently there exists a one-to-one relationship between the nplike | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE | ||
|
||
import numpy as np | ||
import pytest # noqa: F401 | ||
|
||
import awkward as ak | ||
|
||
|
||
def test(): | ||
array = ak.Array([1, 2, 3, 4]) | ||
typetracer_array = ak.Array(array.layout.to_typetracer(forget_length=True)) | ||
typetracer_result = np.sqrt(typetracer_array) | ||
|
||
assert typetracer_result.type == ak.types.ArrayType( | ||
ak.types.NumpyType("float64"), ak._typetracer.UnknownLength | ||
) | ||
|
||
|
||
def test_add(): | ||
left = ak.Array([1, 2, 3, 4]) | ||
right = ak.Array([1, 2, 3, 4.0]) | ||
typetracer_left = ak.Array(left.layout.to_typetracer(forget_length=True)) | ||
typetracer_right = ak.Array(right.layout.to_typetracer(forget_length=True)) | ||
typetracer_result = np.add(typetracer_left, typetracer_right) | ||
|
||
assert typetracer_result.type == ak.types.ArrayType( | ||
ak.types.NumpyType("float64"), ak._typetracer.UnknownLength | ||
) | ||
|
||
|
||
def test_add_scalar(): | ||
array = ak.Array([1, 2, 3, 4]) | ||
typetracer_array = ak.Array(array.layout.to_typetracer(forget_length=True)) | ||
other = ak.min(typetracer_array, mask_identity=False, initial=10) | ||
assert isinstance(other, ak._typetracer.UnknownScalar) | ||
|
||
typetracer_result = np.add(typetracer_array, other) | ||
assert typetracer_result.type == ak.types.ArrayType( | ||
ak.types.NumpyType("int64"), ak._typetracer.UnknownLength | ||
) | ||
|
||
|
||
def test_add_none_scalar(): | ||
array = ak.Array([1, 2, 3, 4]) | ||
typetracer_array = ak.Array(array.layout.to_typetracer(forget_length=True)) | ||
other = ak.min(typetracer_array, mask_identity=True, initial=10) | ||
assert isinstance(other, ak._typetracer.MaybeNone) | ||
assert isinstance(other.content, ak._typetracer.UnknownScalar) | ||
|
||
typetracer_result = np.add(typetracer_array, other) | ||
assert typetracer_result.type == ak.types.ArrayType( | ||
ak.types.NumpyType("int64"), ak._typetracer.UnknownLength | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misc cleanup.