Repository navigation
Implemented np.allclose in numba/np/arraymath.py
#7940
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
Changes from 10 commits
e209bc1
d16cd63
eee11d0
2dc5bb7
ca98b63
6542301
f09d8ff
50367ef
0cc9234
9ee9842
4d90833
ead6817
9c1d44f
1b24788
a93b0ef
66969ce
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 | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -864,6 +864,100 @@ def flat_all(a): | |||||||||||||||||||||||||||||||
| return flat_all | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @register_jitable | ||||||||||||||||||||||||||||||||
| def _allclose_scalars(a_v, b_v, rtol=1e-05, atol=1e-08, equal_nan=False): | ||||||||||||||||||||||||||||||||
| a_v_isnan = np.isnan(a_v) | ||||||||||||||||||||||||||||||||
| b_v_isnan = np.isnan(b_v) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # only one of the values is NaN and the | ||||||||||||||||||||||||||||||||
| # other is not. | ||||||||||||||||||||||||||||||||
| if ( (not a_v_isnan and b_v_isnan) or | ||||||||||||||||||||||||||||||||
| (a_v_isnan and not b_v_isnan) ): | ||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # either both of the values are NaN | ||||||||||||||||||||||||||||||||
| # or both are numbers | ||||||||||||||||||||||||||||||||
| if a_v_isnan and b_v_isnan: | ||||||||||||||||||||||||||||||||
| if not equal_nan: | ||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| if np.isinf(a_v) or np.isinf(b_v): | ||||||||||||||||||||||||||||||||
| return a_v == b_v | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if np.abs(a_v - b_v) > atol + rtol * np.abs(b_v * 1.0): | ||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @overload(np.allclose) | ||||||||||||||||||||||||||||||||
| @overload_method(types.Array, "allclose") | ||||||||||||||||||||||||||||||||
| def np_allclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False): | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if not type_can_asarray(a): | ||||||||||||||||||||||||||||||||
| raise TypeError('The first argument "a" must be array-like') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if not type_can_asarray(b): | ||||||||||||||||||||||||||||||||
| raise TypeError('The first argument "b" must be array-like') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if not isinstance(rtol, types.Float): | ||||||||||||||||||||||||||||||||
| raise TypeError('The fourth argument "rtol" must be a ' | ||||||||||||||||||||||||||||||||
|
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.
Suggested change
|
||||||||||||||||||||||||||||||||
| 'floating point') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if not isinstance(atol, types.Float): | ||||||||||||||||||||||||||||||||
| raise TypingError('The fifth argument "atol" must be a ' | ||||||||||||||||||||||||||||||||
|
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.
Suggested change
|
||||||||||||||||||||||||||||||||
| 'floating point') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if not isinstance(equal_nan, types.Boolean): | ||||||||||||||||||||||||||||||||
| raise TypeError('The sixth argument "equal_nan" must be a ' | ||||||||||||||||||||||||||||||||
|
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.
Suggested change
|
||||||||||||||||||||||||||||||||
| 'boolean') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| is_a_scalar = isinstance(a, types.Number) | ||||||||||||||||||||||||||||||||
| is_b_scalar = isinstance(b, types.Number) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if is_a_scalar and is_b_scalar: | ||||||||||||||||||||||||||||||||
| def np_allclose_impl_scalar_scalar(a, b, rtol=1e-05, atol=1e-08, | ||||||||||||||||||||||||||||||||
| equal_nan=False): | ||||||||||||||||||||||||||||||||
| return _allclose_scalars(a, b, rtol=rtol, atol=atol, | ||||||||||||||||||||||||||||||||
| equal_nan=equal_nan) | ||||||||||||||||||||||||||||||||
| return np_allclose_impl_scalar_scalar | ||||||||||||||||||||||||||||||||
| elif is_a_scalar and not is_b_scalar: | ||||||||||||||||||||||||||||||||
| def np_allclose_impl_scalar_array(a, b, rtol=1e-05, atol=1e-08, | ||||||||||||||||||||||||||||||||
| equal_nan=False): | ||||||||||||||||||||||||||||||||
| b = np.asarray(b) | ||||||||||||||||||||||||||||||||
| for bv in np.nditer(b): | ||||||||||||||||||||||||||||||||
| if not _allclose_scalars(a, bv.item(), rtol=rtol, atol=atol, | ||||||||||||||||||||||||||||||||
| equal_nan=equal_nan): | ||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||
| return np_allclose_impl_scalar_array | ||||||||||||||||||||||||||||||||
|
Comment on lines
+924
to
+933
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. You can replace this block of code by:
Suggested change
Contributor
Author
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. I don't think this will give right results all the time, because the check depends on the ordering of Quoting from "Notes" section of https://numpy.org/doc/stable/reference/generated/numpy.allclose.html
Please let me know if should still apply the above code suggestion.
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. That's fine. I wasn't aware of this info. |
||||||||||||||||||||||||||||||||
| elif not is_a_scalar and is_b_scalar: | ||||||||||||||||||||||||||||||||
| def np_allclose_impl_array_scalar(a, b, rtol=1e-05, atol=1e-08, | ||||||||||||||||||||||||||||||||
| equal_nan=False): | ||||||||||||||||||||||||||||||||
| a = np.asarray(a) | ||||||||||||||||||||||||||||||||
| for av in np.nditer(a): | ||||||||||||||||||||||||||||||||
| if not _allclose_scalars(av.item(), b, rtol=rtol, atol=atol, | ||||||||||||||||||||||||||||||||
| equal_nan=equal_nan): | ||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||
| return np_allclose_impl_array_scalar | ||||||||||||||||||||||||||||||||
| elif not is_a_scalar and not is_b_scalar: | ||||||||||||||||||||||||||||||||
| def np_allclose_impl_array_array(a, b, rtol=1e-05, atol=1e-08, | ||||||||||||||||||||||||||||||||
| equal_nan=False): | ||||||||||||||||||||||||||||||||
| a = np.asarray(a) | ||||||||||||||||||||||||||||||||
| b = np.asarray(b) | ||||||||||||||||||||||||||||||||
| a_a, b_b = np.broadcast_arrays(a, b) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for av, bv in np.nditer((a_a, b_b)): | ||||||||||||||||||||||||||||||||
| if not _allclose_scalars(av.item(), bv.item(), rtol=rtol, | ||||||||||||||||||||||||||||||||
| atol=atol, equal_nan=equal_nan): | ||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return np_allclose_impl_array_array | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @overload(np.any) | ||||||||||||||||||||||||||||||||
| @overload_method(types.Array, "any") | ||||||||||||||||||||||||||||||||
| def np_any(a): | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -329,6 +329,10 @@ def np_trapz_x_dx(y, x, dx): | |||||
| return np.trapz(y, x, dx) | ||||||
|
|
||||||
|
|
||||||
| def np_allclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False): | ||||||
| return np.allclose(a, b, rtol, atol, equal_nan) | ||||||
|
|
||||||
|
|
||||||
| def np_average(a, axis=None, weights=None): | ||||||
| return np.average(a, axis=axis, weights=weights) | ||||||
|
|
||||||
|
|
@@ -3172,6 +3176,126 @@ def test_1D_weights_axis(data, axis, weights): | |||||
| #test with axis argument | ||||||
| test_1D_weights_axis(data, axis=1, weights=w) | ||||||
|
|
||||||
| def test_allclose(self): | ||||||
|
|
||||||
| pyfunc = np_allclose | ||||||
| cfunc = jit(nopython=True)(pyfunc) | ||||||
|
|
||||||
| min_int = np.iinfo(np.int_).min | ||||||
| a = np.array([min_int], dtype=np.int_) | ||||||
|
|
||||||
| simple_data = [ | ||||||
| (np.asarray([1e10, 1e-7]), np.asarray([1.00001e10, 1e-8])), | ||||||
| (np.asarray([1e10, 1e-8]), np.asarray([1.00001e10, 1e-9])), | ||||||
| (np.asarray([1e10, 1e-8]), np.asarray([1.0001e10, 1e-9])), | ||||||
| (np.asarray([1e10]), np.asarray([1.0001e10, 1e-9])), | ||||||
| (1.0, 1.0), | ||||||
| (np.array([np.inf, 1]), np.array([0, np.inf])), | ||||||
| (a, a) | ||||||
| ] | ||||||
|
|
||||||
| for a, b in simple_data: | ||||||
| py_result = pyfunc(a, b) | ||||||
| c_result = cfunc(a, b) | ||||||
| self.assertEqual(py_result, c_result) | ||||||
|
|
||||||
| a = np.asarray([1.0, np.nan]) | ||||||
| b = np.asarray([1.0, np.nan]) | ||||||
| self.assertFalse(cfunc(a, b)) | ||||||
| self.assertTrue(cfunc(a, b, equal_nan=True)) | ||||||
|
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. Compare the output of |
||||||
|
|
||||||
| b = np.asarray([np.nan, 1.0]) | ||||||
| self.assertFalse(cfunc(a, b)) | ||||||
|
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. Compare the output of |
||||||
|
|
||||||
| # NumPy test data | ||||||
|
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. Can you also include the link to NumPy repository pointing to the tests?
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. Maybe split the tests into different functions. This makes it easy to debug when there's a test failure. |
||||||
| arr = np.array([100.0, 1000.0]) | ||||||
| aran = np.arange(125).astype(dtype=np.float64).reshape((5, 5, 5)) | ||||||
| atol = 1e-8 | ||||||
| rtol = 1e-5 | ||||||
| numpy_data = [ | ||||||
| (np.asarray([1, 0]), np.asarray([1, 0])), | ||||||
| (np.asarray([atol]), np.asarray([0.0])), | ||||||
| (np.asarray([1.0]), np.asarray([1 + rtol + atol])), | ||||||
| (arr, arr + arr * rtol), | ||||||
| (arr, arr + arr * rtol + atol * 2), | ||||||
| (aran, aran + aran * rtol), | ||||||
| (np.inf, np.inf), | ||||||
| (np.inf, np.asarray([np.inf])) | ||||||
| ] | ||||||
|
|
||||||
| for (x, y) in numpy_data: | ||||||
| self.assertTrue(cfunc(x, y)) | ||||||
|
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. Instead of asserting to |
||||||
|
|
||||||
| numpy_data = [ | ||||||
| (np.asarray([np.inf, 0]), np.asarray([1.0, np.inf])), | ||||||
| (np.asarray([np.inf, 0]), np.asarray([1.0, 0])), | ||||||
| (np.asarray([np.inf, np.inf]), np.asarray([1.0, np.inf])), | ||||||
| (np.asarray([np.inf, np.inf]), np.asarray([1.0, 0.0])), | ||||||
| (np.asarray([-np.inf, 0.0]), np.asarray([np.inf, 0.0])), | ||||||
| (np.asarray([np.nan, 0.0]), np.asarray([np.nan, 0.0])), | ||||||
| (np.asarray([atol * 2]), np.asarray([0.0])), | ||||||
| (np.asarray([1.0]), np.asarray([1 + rtol + atol * 2])), | ||||||
| (aran, aran + aran * atol + atol * 2), | ||||||
| (np.array([np.inf, 1.0]), np.array([0.0, np.inf])) | ||||||
| ] | ||||||
|
|
||||||
| for (x, y) in numpy_data: | ||||||
| self.assertFalse(cfunc(x, y)) | ||||||
|
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. Same here. Compare the outputs of |
||||||
|
|
||||||
| class Foo(np.ndarray): | ||||||
| def __new__(cls, *args, **kwargs): | ||||||
| return np.array(*args, **kwargs).view(cls) | ||||||
|
|
||||||
| a = Foo([1]) | ||||||
| self.assertTrue(type(np.allclose(a, a)) is bool) | ||||||
|
|
||||||
| noise_levels = [1.0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 0.0] | ||||||
| zero_array = np.zeros((25, 4)) | ||||||
| a = np.random.ranf((25, 4)) | ||||||
| for noise in noise_levels: | ||||||
| for rtol in noise_levels: | ||||||
| for atol in noise_levels: | ||||||
| py_result = pyfunc(zero_array, noise, | ||||||
| atol=atol, rtol=rtol) | ||||||
| c_result = cfunc(zero_array, noise, | ||||||
| atol=atol, rtol=rtol) | ||||||
| self.assertEqual(py_result, c_result) | ||||||
|
|
||||||
| py_result = pyfunc(noise, zero_array, | ||||||
| atol=atol, rtol=rtol) | ||||||
| c_result = cfunc(noise, zero_array, | ||||||
| atol=atol, rtol=rtol) | ||||||
| self.assertEqual(py_result, c_result) | ||||||
|
|
||||||
| py_result = pyfunc(np.asarray([noise]), zero_array, | ||||||
| atol=atol, rtol=rtol) | ||||||
| c_result = cfunc(np.asarray([noise]), zero_array, | ||||||
| atol=atol, rtol=rtol) | ||||||
| self.assertEqual(py_result, c_result) | ||||||
|
|
||||||
| py_result = pyfunc(a, a + noise, atol=atol, rtol=rtol) | ||||||
| c_result = cfunc(a, a + noise, atol=atol, rtol=rtol) | ||||||
| self.assertEqual(py_result, c_result) | ||||||
|
|
||||||
| py_result = pyfunc(a + noise, a, atol=atol, rtol=rtol) | ||||||
| c_result = cfunc(a + noise, a, atol=atol, rtol=rtol) | ||||||
| self.assertEqual(py_result, c_result) | ||||||
|
|
||||||
| def test_allcase_exception(self): | ||||||
|
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.
Suggested change
|
||||||
| self.disable_leak_check() | ||||||
|
|
||||||
| pyfunc = np_allclose | ||||||
| cfunc = jit(nopython=True)(pyfunc) | ||||||
|
|
||||||
| a = np.asarray([1e10, 1e-9, np.nan]) | ||||||
| b = np.asarray([1.0001e10, 1e-9]) | ||||||
|
|
||||||
| with self.assertRaises(ValueError) as e: | ||||||
| cfunc(a, b) | ||||||
|
|
||||||
| self.assertIn(("shape mismatch: objects cannot be broadcast to " | ||||||
| "a single shape"), str(e.exception)) | ||||||
|
|
||||||
| def test_interp_basic(self): | ||||||
| pyfunc = interp | ||||||
| cfunc = jit(nopython=True)(pyfunc) | ||||||
|
|
||||||
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.
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.
I have addressed the review and the tests are passing. Please let me know if anything else is to be done here. Thanks.