-
Notifications
You must be signed in to change notification settings - Fork 29
Fix 566: coordinate arithmetic #608
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
base: master
Are you sure you want to change the base?
Changes from all commits
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 | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -430,10 +430,109 @@ def __str__(self): | |||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| __repr__ = __str__ | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| __array_priority__ = 1000.0 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __array__(self, dtype=None, copy=False): | ||||||||||||||||||||||||||||||||
| """Numpy method for getting array data with `np.array(coord)`.""" | ||||||||||||||||||||||||||||||||
| return self.data | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _get_coord_output(self, data, units=None): | ||||||||||||||||||||||||||||||||
| """Return output from operations as a coordinate when possible.""" | ||||||||||||||||||||||||||||||||
| if isinstance(data, BaseCoord): | ||||||||||||||||||||||||||||||||
| return data | ||||||||||||||||||||||||||||||||
| if hasattr(data, "magnitude") and hasattr(data, "units"): | ||||||||||||||||||||||||||||||||
| return get_coord(data=data.magnitude, units=data.units) | ||||||||||||||||||||||||||||||||
| return get_coord(data=data, units=units) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _binary_coord_op(self, operator, other, reversed=False): | ||||||||||||||||||||||||||||||||
| """Apply a binary operator and return a new coordinate.""" | ||||||||||||||||||||||||||||||||
| other_data = other.data if isinstance(other, BaseCoord) else other | ||||||||||||||||||||||||||||||||
| # Addition/subtraction treat scalars as values in current units. | ||||||||||||||||||||||||||||||||
| if hasattr(other_data, "units") and operator in (np.add, np.subtract): | ||||||||||||||||||||||||||||||||
| other_data = convert_units( | ||||||||||||||||||||||||||||||||
| other_data.magnitude, | ||||||||||||||||||||||||||||||||
| self.units, | ||||||||||||||||||||||||||||||||
| other_data.units, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| lhs, rhs = (other_data, self.data) if reversed else (self.data, other_data) | ||||||||||||||||||||||||||||||||
| out = operator(lhs, rhs) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+457
to
+458
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.
Binary ops compute using Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||
| units = ( | ||||||||||||||||||||||||||||||||
| self.units if not np.issubdtype(np.asarray(out).dtype, np.bool_) else None | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| return self._get_coord_output(out, units=units) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+447
to
+462
Contributor
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. Unit handling for When Consider adding a branch like: Proposed fix def _binary_coord_op(self, operator, other, reversed=False):
"""Apply a binary operator and return a new coordinate."""
other_data = other.data if isinstance(other, BaseCoord) else other
+ # Convert coord-to-coord units for add/sub
+ if (
+ isinstance(other, BaseCoord)
+ and other.units is not None
+ and self.units is not None
+ and operator in (np.add, np.subtract)
+ ):
+ other_data = convert_units(other_data, self.units, other.units)
# Addition/subtraction treat scalars as values in current units.
- if hasattr(other_data, "units") and operator in (np.add, np.subtract):
+ elif hasattr(other_data, "units") and operator in (np.add, np.subtract):🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __add__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.add, other) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __sub__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.subtract, other) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __mul__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.multiply, other) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __truediv__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.divide, other) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __floordiv__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.floor_divide, other) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __pow__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.power, other) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __mod__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.mod, other) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| __radd__ = __add__ | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __rsub__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.subtract, other, reversed=True) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| __rmul__ = __mul__ | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __rtruediv__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.divide, other, reversed=True) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __rfloordiv__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.floor_divide, other, reversed=True) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __rpow__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.power, other, reversed=True) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __rmod__(self, other): | ||||||||||||||||||||||||||||||||
| return self._binary_coord_op(np.mod, other, reversed=True) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): | ||||||||||||||||||||||||||||||||
| """Support numpy ufunc operations and return coordinate outputs.""" | ||||||||||||||||||||||||||||||||
| method_func = ufunc if method == "__call__" else getattr(ufunc, method) | ||||||||||||||||||||||||||||||||
| converted = [x.data if isinstance(x, BaseCoord) else x for x in inputs] | ||||||||||||||||||||||||||||||||
| out = method_func(*converted, **kwargs) | ||||||||||||||||||||||||||||||||
| units = ( | ||||||||||||||||||||||||||||||||
| self.units if not np.issubdtype(np.asarray(out).dtype, np.bool_) else None | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| return self._get_coord_output(out, units=units) | ||||||||||||||||||||||||||||||||
|
Comment on lines
504
to
512
Contributor
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. Fix line-length violation flagged by CI (line 503). Same 🔧 Proposed fix- units = self.units if not np.issubdtype(np.asarray(out).dtype, np.bool_) else None
+ is_bool = np.issubdtype(np.asarray(out).dtype, np.bool_)
+ units = self.units if not is_bool else None📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: LintCode[error] 503-503: E501 Line too long (90 > 88). 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __array_function__(self, func, types, args, kwargs): | ||||||||||||||||||||||||||||||||
| """Support NumPy array-function protocol for coordinates.""" | ||||||||||||||||||||||||||||||||
| if not any(issubclass(t, BaseCoord) for t in types): | ||||||||||||||||||||||||||||||||
| return NotImplemented | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _convert(obj): | ||||||||||||||||||||||||||||||||
| if isinstance(obj, BaseCoord): | ||||||||||||||||||||||||||||||||
| return obj.data | ||||||||||||||||||||||||||||||||
|
Comment on lines
+520
to
+521
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.
The array-function conversion replaces each Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||
| if isinstance(obj, tuple): | ||||||||||||||||||||||||||||||||
| return tuple(_convert(x) for x in obj) | ||||||||||||||||||||||||||||||||
| if isinstance(obj, list): | ||||||||||||||||||||||||||||||||
| return [_convert(x) for x in obj] | ||||||||||||||||||||||||||||||||
| if isinstance(obj, dict): | ||||||||||||||||||||||||||||||||
| return {k: _convert(v) for k, v in obj.items()} | ||||||||||||||||||||||||||||||||
| return obj | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| out = func(*_convert(args), **_convert(kwargs)) | ||||||||||||||||||||||||||||||||
| units = ( | ||||||||||||||||||||||||||||||||
| self.units if not np.issubdtype(np.asarray(out).dtype, np.bool_) else None | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| return self._get_coord_output(out, units=units) | ||||||||||||||||||||||||||||||||
|
Comment on lines
514
to
534
Contributor
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. 🧩 Analysis chain🏁 Script executed: # First, locate and read the file
git ls-files | grep -E "(coords\.py|core/coords\.py)"Repository: DASDAE/dascore Length of output: 170 🏁 Script executed: # Read the specific lines mentioned in the review
cat -n dascore/core/coords.py | sed -n '500,530p'Repository: DASDAE/dascore Length of output: 1568 🏁 Script executed: # Check the exact character count of line 523
sed -n '523p' dascore/core/coords.py | wc -cRepository: DASDAE/dascore Length of output: 60 🏁 Script executed: python3 << 'EOF'
import numpy as np
# Test case 1: np.asarray on tuple of arrays
print("Test 1: np.asarray on tuple of arrays")
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result_tuple = (arr1, arr2)
asarray_result = np.asarray(result_tuple)
print(f" Input: tuple of 2 arrays")
print(f" Result dtype: {asarray_result.dtype}")
print(f" Result shape: {asarray_result.shape}")
# Test case 2: np.asarray on scalar
print("\nTest 2: np.asarray on scalar")
scalar = 5
asarray_scalar = np.asarray(scalar)
print(f" Input: scalar 5")
print(f" Result dtype: {asarray_scalar.dtype}")
# Test case 3: np.issubdtype with object dtype
print("\nTest 3: np.issubdtype on object dtype")
obj_arr = np.asarray([(1, 2), (3, 4)])
print(f" Object array dtype: {obj_arr.dtype}")
is_bool = np.issubdtype(obj_arr.dtype, np.bool_)
print(f" np.issubdtype(object, np.bool_) = {is_bool}")
# Test case 4: Real numpy function returning tuple
print("\nTest 4: np.unique with return_index=True (returns tuple)")
data = np.array([1, 2, 1, 3, 2])
result = np.unique(data, return_index=True)
print(f" np.unique output type: {type(result)}")
print(f" np.unique output: {result}")
asarray_unique = np.asarray(result)
print(f" np.asarray(result).dtype: {asarray_unique.dtype}")
is_bool_unique = np.issubdtype(asarray_unique.dtype, np.bool_)
print(f" np.issubdtype on this object dtype: {is_bool_unique}")
# Test case 5: Boolean array as output
print("\nTest 5: np.isnan returns boolean array")
data = np.array([1.0, np.nan, 3.0])
result = np.isnan(data)
asarray_bool = np.asarray(result)
print(f" np.isnan output dtype: {asarray_bool.dtype}")
is_bool_result = np.issubdtype(asarray_bool.dtype, np.bool_)
print(f" np.issubdtype(bool_, np.bool_) = {is_bool_result}")
EOFRepository: DASDAE/dascore Length of output: 175 🏁 Script executed: # Find the _get_coord_output method to understand what it expects
rg -A 10 "def _get_coord_output" dascore/core/coords.pyRepository: DASDAE/dascore Length of output: 638 🏁 Script executed: pip install numpy -q && python3 << 'EOF'
import numpy as np
# Test case 1: np.asarray on tuple of arrays
print("Test 1: np.asarray on tuple of arrays")
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result_tuple = (arr1, arr2)
asarray_result = np.asarray(result_tuple)
print(f" Input: tuple of 2 arrays")
print(f" Result dtype: {asarray_result.dtype}")
print(f" Result shape: {asarray_result.shape}")
# Test case 2: np.asarray on scalar
print("\nTest 2: np.asarray on scalar")
scalar = 5
asarray_scalar = np.asarray(scalar)
print(f" Input: scalar 5")
print(f" Result dtype: {asarray_scalar.dtype}")
# Test case 3: np.issubdtype with object dtype
print("\nTest 3: np.issubdtype on object dtype")
obj_arr = np.asarray([(1, 2), (3, 4)])
print(f" Object array dtype: {obj_arr.dtype}")
is_bool = np.issubdtype(obj_arr.dtype, np.bool_)
print(f" np.issubdtype(object, np.bool_) = {is_bool}")
# Test case 4: Real numpy function returning tuple
print("\nTest 4: np.unique with return_index=True (returns tuple)")
data = np.array([1, 2, 1, 3, 2])
result = np.unique(data, return_index=True)
print(f" np.unique output type: {type(result)}")
print(f" np.unique output: {result}")
asarray_unique = np.asarray(result)
print(f" np.asarray(result).dtype: {asarray_unique.dtype}")
is_bool_unique = np.issubdtype(asarray_unique.dtype, np.bool_)
print(f" np.issubdtype on this object dtype: {is_bool_unique}")
# Test case 5: Boolean array as output
print("\nTest 5: np.isnan returns boolean array")
data = np.array([1.0, np.nan, 3.0])
result = np.isnan(data)
asarray_bool = np.asarray(result)
print(f" np.isnan output dtype: {asarray_bool.dtype}")
is_bool_result = np.issubdtype(asarray_bool.dtype, np.bool_)
print(f" np.issubdtype(bool_, np.bool_) = {is_bool_result}")
EOFRepository: DASDAE/dascore Length of output: 935 🏁 Script executed: rg -B 2 -A 20 "^def get_coord" dascore/core/coords.py | head -50Repository: DASDAE/dascore Length of output: 596 Fix line-length violation on line 523 (exceeds 88-character limit). Line 523 is 91 characters and must be shortened. The proposed refactoring improves both compliance and readability: 🔧 Proposed fix- units = self.units if not np.issubdtype(np.asarray(out).dtype, np.bool_) else None
+ is_bool = np.issubdtype(np.asarray(out).dtype, np.bool_)
+ units = self.units if not is_bool else None🧰 Tools🪛 GitHub Actions: LintCode[error] 523-523: E501 Line too long (90 > 88). 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @cached_method | ||||||||||||||||||||||||||||||||
| def min(self): | ||||||||||||||||||||||||||||||||
| """Return min value.""" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1895,6 +1895,89 @@ def test_not_implemented_in_baseclass(self, evenly_sampled_coord): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BaseCoord.change_length(coord, 10) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class TestCoordinateArithmetic: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Tests for coordinate arithmetic behavior (issue #566).""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "func, expected, other", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: coord + val, lambda vals, val: vals + val, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: coord - val, lambda vals, val: vals - val, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: coord * val, lambda vals, val: vals * val, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: coord / val, lambda vals, val: vals / val, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: coord // val, lambda vals, val: vals // val, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: coord**val, lambda vals, val: vals**val, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: coord % val, lambda vals, val: vals % val, 3), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: val + coord, lambda vals, val: val + vals, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: val - coord, lambda vals, val: val - vals, 10), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: val * coord, lambda vals, val: val * vals, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: val / coord, lambda vals, val: val / vals, 10), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: val // coord, lambda vals, val: val // vals, 10), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: val**coord, lambda vals, val: val**vals, 2), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (lambda coord, val: val % coord, lambda vals, val: val % vals, 10), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_all_dunder_binary_ops(self, func, expected, other): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Ensure all new arithmetic dunder paths return coordinates.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| coord = get_coord(data=[2, 4, 8], units="m") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out = func(coord, other) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert isinstance(out, BaseCoord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert out.units == coord.units | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| np.testing.assert_allclose(out.values, expected(coord.values, other)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_numpy_ufunc_call_returns_coord(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Ensure numpy ufunc __call__ dispatch returns coordinates.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| coord = get_coord(data=[1, 4, 9], units="m") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out = np.sqrt(coord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert isinstance(out, BaseCoord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert out.units == coord.units | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| np.testing.assert_allclose(out.values, np.array([1.0, 2.0, 3.0])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_numpy_ufunc_accumulate_returns_coord(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Ensure numpy ufunc method dispatch (e.g. accumulate) returns coordinates.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| coord = get_coord(data=[1, 2, 3], units="m") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out = np.add.accumulate(coord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert isinstance(out, BaseCoord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert out.units == coord.units | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| np.testing.assert_array_equal(out.values, np.array([1, 3, 6])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_numpy_array_function_returns_coord(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Ensure numpy array functions return coordinates where possible.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| coord = get_coord(data=[3, 4], units="m") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out = np.linalg.norm(coord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert isinstance(out, BaseCoord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert out.units == coord.units | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| np.testing.assert_allclose(out.values, np.array([5.0])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_tuple_list_dict_conversions(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Ensure tuple/list/dict conversion paths are exercised.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| coord1 = get_coord(data=[1, 2], units="m") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| coord2 = get_coord(data=[3, 4], units="m") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_tuple = np.concatenate((coord1, coord2)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert isinstance(out_tuple, BaseCoord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| np.testing.assert_array_equal(out_tuple.values, np.array([1, 2, 3, 4])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_list = np.concatenate([coord1, coord2]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert isinstance(out_list, BaseCoord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| np.testing.assert_array_equal(out_list.values, np.array([1, 2, 3, 4])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out_kwargs = np.mean(a=coord1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert isinstance(out_kwargs, BaseCoord) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| np.testing.assert_allclose(out_kwargs.values, np.array([1.5])) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1958
to
+1974
Contributor
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. Good coverage of conversion paths; missing blank lines before next class. The test name 🔧 Proposed fix out_kwargs = np.mean(a=coord1)
assert isinstance(out_kwargs, BaseCoord)
np.testing.assert_allclose(out_kwargs.values, np.array([1.5]))
+
+
class TestIssues:📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_array_function_returns_not_implemented_for_non_coord_types(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Ensure non-coordinate array_function dispatch returns NotImplemented.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| coord = get_coord(data=[1, 2, 3], units="m") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out = coord.__array_function__(np.mean, (np.ndarray,), (coord,), {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert out is NotImplemented | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class TestIssues: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Tests for special issues related to coords.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
The output helper always calls
get_coordfor non-BaseCoordvalues, so scalar-returning NumPy operations now produce invalid coordinates rather than numbers. In practice, reductions likenp.sum(coord)and scalar linear algebra calls such asnp.dot(coord, coord)are routed through this path and becomeCoordPartialobjects with NaN-filled data instead of the expected scalar value, which corrupts numerical results.Useful? React with 👍 / 👎.