From f89a3d54c64d48a75b7ae391508f4965210e77bf Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 4 Jan 2025 19:48:25 +0000 Subject: [PATCH 01/49] add pint's numpy tests --- tests/test_array.py | 1528 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1528 insertions(+) create mode 100644 tests/test_array.py diff --git a/tests/test_array.py b/tests/test_array.py new file mode 100644 index 0000000..4fac87d --- /dev/null +++ b/tests/test_array.py @@ -0,0 +1,1528 @@ +from __future__ import annotations + +import copy +import operator as op +import pickle +import warnings + +import pytest + +from pint import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning +# from pint.compat import np +from pint.testsuite import helpers +from pint.testsuite.test_umath import TestUFuncs + +import pint_array; +import numpy as np; +pnp = pint_array.pint_namespace(np); +from pint import UnitRegistry; +ureg = UnitRegistry() + +# @helpers.requires_numpy +class TestNumpyMethods: + @classmethod + def setup_class(cls): + from pint import _DEFAULT_REGISTRY + + cls.ureg = _DEFAULT_REGISTRY + cls.Q_ = cls.ureg.Quantity + + @classmethod + def teardown_class(cls): + cls.ureg = None + cls.Q_ = None + + @property + def q(self): + return [[1, 2], [3, 4]] * self.ureg.m + + @property + def q_scalar(self): + return np.array(5) * self.ureg.m + + @property + def q_nan(self): + return [[1, 2], [3, pnp.nan]] * self.ureg.m + + @property + def q_zero_or_nan(self): + return [[0, 0], [0, pnp.nan]] * self.ureg.m + + @property + def q_temperature(self): + return self.Q_([[1, 2], [3, 4]], self.ureg.degC) + + def assertNDArrayEqual(self, actual, desired): + # Assert that the given arrays are equal, and are not Quantities + pnp.testing.assert_array_equal(actual, desired) + assert not isinstance(actual, self.Q_) + assert not isinstance(desired, self.Q_) + + +class TestNumpyArrayCreation(TestNumpyMethods): + # https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html + + # @helpers.requires_array_function_protocol() + def test_ones_like(self): + self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) + + # @helpers.requires_array_function_protocol() + def test_zeros_like(self): + self.assertNDArrayEqual(pnp.zeros_like(self.q), np.array([[0, 0], [0, 0]])) + + # @helpers.requires_array_function_protocol() + def test_empty_like(self): + ret = pnp.empty_like(self.q) + assert ret.shape == (2, 2) + assert isinstance(ret, pnp.ndarray) + + # @helpers.requires_array_function_protocol() + def test_full_like(self): + helpers.assert_quantity_equal( + pnp.full_like(self.q, self.Q_(0, self.ureg.degC)), + self.Q_([[0, 0], [0, 0]], self.ureg.degC), + ) + self.assertNDArrayEqual(pnp.full_like(self.q, 2), np.array([[2, 2], [2, 2]])) + + +class TestNumpyArrayManipulation(TestNumpyMethods): + # TODO + # https://www.numpy.org/devdocs/reference/routines.array-manipulation.html + # copyto + # broadcast + # asarray asanyarray asmatrix asfarray asfortranarray ascontiguousarray asarray_chkfinite asscalar require + + # Changing array shape + + def test_flatten(self): + helpers.assert_quantity_equal(self.q.flatten(), [1, 2, 3, 4] * self.ureg.m) + + def test_flat(self): + for q, v in zip(self.q.flat, [1, 2, 3, 4]): + assert q == v * self.ureg.m + + def test_reshape(self): + helpers.assert_quantity_equal( + self.q.reshape([1, 4]), [[1, 2, 3, 4]] * self.ureg.m + ) + + def test_ravel(self): + helpers.assert_quantity_equal(self.q.ravel(), [1, 2, 3, 4] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_ravel_numpy_func(self): + helpers.assert_quantity_equal(pnp.ravel(self.q), [1, 2, 3, 4] * self.ureg.m) + + # Transpose-like operations + + # @helpers.requires_array_function_protocol() + def test_moveaxis(self): + helpers.assert_quantity_equal( + pnp.moveaxis(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_rollaxis(self): + helpers.assert_quantity_equal( + pnp.rollaxis(self.q, 1), np.array([[1, 2], [3, 4]]).T * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_swapaxes(self): + helpers.assert_quantity_equal( + pnp.swapaxes(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m + ) + + def test_transpose(self): + helpers.assert_quantity_equal( + self.q.transpose(), [[1, 3], [2, 4]] * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_transpose_numpy_func(self): + helpers.assert_quantity_equal( + pnp.transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_flip_numpy_func(self): + helpers.assert_quantity_equal( + pnp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m + ) + + # Changing number of dimensions + + # @helpers.requires_array_function_protocol() + def test_atleast_1d(self): + actual = pnp.atleast_1d(self.Q_(0, self.ureg.degC), self.q.flatten()) + expected = (self.Q_(np.array([0]), self.ureg.degC), self.q.flatten()) + for ind_actual, ind_expected in zip(actual, expected): + helpers.assert_quantity_equal(ind_actual, ind_expected) + helpers.assert_quantity_equal(pnp.atleast_1d(self.q), self.q) + + # @helpers.requires_array_function_protocol() + def test_atleast_2d(self): + actual = pnp.atleast_2d(self.Q_(0, self.ureg.degC), self.q.flatten()) + expected = ( + self.Q_(np.array([[0]]), self.ureg.degC), + np.array([[1, 2, 3, 4]]) * self.ureg.m, + ) + for ind_actual, ind_expected in zip(actual, expected): + helpers.assert_quantity_equal(ind_actual, ind_expected) + helpers.assert_quantity_equal(pnp.atleast_2d(self.q), self.q) + + # @helpers.requires_array_function_protocol() + def test_atleast_3d(self): + actual = pnp.atleast_3d(self.Q_(0, self.ureg.degC), self.q.flatten()) + expected = ( + self.Q_(np.array([[[0]]]), self.ureg.degC), + np.array([[[1], [2], [3], [4]]]) * self.ureg.m, + ) + for ind_actual, ind_expected in zip(actual, expected): + helpers.assert_quantity_equal(ind_actual, ind_expected) + helpers.assert_quantity_equal( + pnp.atleast_3d(self.q), np.array([[[1], [2]], [[3], [4]]]) * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_broadcast_to(self): + helpers.assert_quantity_equal( + pnp.broadcast_to(self.q[:, 1], (2, 2)), + np.array([[2, 4], [2, 4]]) * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + def test_expand_dims(self): + helpers.assert_quantity_equal( + pnp.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_squeeze(self): + helpers.assert_quantity_equal(pnp.squeeze(self.q), self.q) + helpers.assert_quantity_equal( + self.q.reshape([1, 4]).squeeze(), [1, 2, 3, 4] * self.ureg.m + ) + + # Changing number of dimensions + # Joining arrays + # @helpers.requires_array_function_protocol() + def test_concat_stack(self, subtests): + for func in (pnp.concatenate, pnp.stack, pnp.hstack, pnp.vstack, pnp.dstack): + with subtests.test(func=func): + helpers.assert_quantity_equal( + func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) + ) + # One or more of the args is a bare array full of zeros or NaNs + helpers.assert_quantity_equal( + func([self.q_zero_or_nan.m, self.q]), + self.Q_(func([self.q_zero_or_nan.m, self.q.m]), self.ureg.m), + ) + # One or more of the args is a bare array with at least one non-zero, + # non-NaN element + nz = self.q_zero_or_nan + nz.m[0, 0] = 1 + with pytest.raises(DimensionalityError): + func([nz.m, self.q]) + + # @helpers.requires_array_function_protocol() + def test_block_column_stack(self, subtests): + for func in (pnp.block, pnp.column_stack): + with subtests.test(func=func): + helpers.assert_quantity_equal( + func([self.q[:, 0], self.q[:, 1]]), + self.Q_(func([self.q[:, 0].m, self.q[:, 1].m]), self.ureg.m), + ) + + # One or more of the args is a bare array full of zeros or NaNs + helpers.assert_quantity_equal( + func( + [ + self.q_zero_or_nan[:, 0].m, + self.q[:, 0], + self.q_zero_or_nan[:, 1].m, + ] + ), + self.Q_( + func( + [ + self.q_zero_or_nan[:, 0].m, + self.q[:, 0].m, + self.q_zero_or_nan[:, 1].m, + ] + ), + self.ureg.m, + ), + ) + # One or more of the args is a bare array with at least one non-zero, + # non-NaN element + nz = self.q_zero_or_nan + nz.m[0, 0] = 1 + with pytest.raises(DimensionalityError): + func([nz[:, 0].m, self.q[:, 0]]) + + # @helpers.requires_array_function_protocol() + def test_append(self): + helpers.assert_quantity_equal( + pnp.append(self.q, [[0, 0]] * self.ureg.m, axis=0), + [[1, 2], [3, 4], [0, 0]] * self.ureg.m, + ) + + def test_astype(self): + actual = self.q.astype(pnp.float32) + expected = self.Q_(np.array([[1.0, 2.0], [3.0, 4.0]], dtype=pnp.float32), "m") + helpers.assert_quantity_equal(actual, expected) + assert actual.m.dtype == expected.m.dtype + + def test_item(self): + helpers.assert_quantity_equal(self.Q_([[0]], "m").item(), 0 * self.ureg.m) + + def test_broadcast_arrays(self): + x = self.Q_(np.array([[1, 2, 3]]), "m") + y = self.Q_(np.array([[4], [5]]), "nm") + result = pnp.broadcast_arrays(x, y) + expected = self.Q_( + [ + [[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]], + [[4e-09, 4e-09, 4e-09], [5e-09, 5e-09, 5e-09]], + ], + "m", + ) + helpers.assert_quantity_equal(result, expected) + + result = pnp.broadcast_arrays(x, y, subok=True) + helpers.assert_quantity_equal(result, expected) + + def test_roll(self): + helpers.assert_quantity_equal( + pnp.roll(self.q, 1), [[4, 1], [2, 3]] * self.ureg.m + ) + + +class TestNumpyMathematicalFunctions(TestNumpyMethods): + # https://www.numpy.org/devdocs/reference/routines.math.html + # Trigonometric functions + # @helpers.requires_array_function_protocol() + def test_unwrap(self): + helpers.assert_quantity_equal( + pnp.unwrap([0, 3 * pnp.pi] * self.ureg.radians), [0, pnp.pi] + ) + helpers.assert_quantity_equal( + pnp.unwrap([0, 540] * self.ureg.deg), [0, 180] * self.ureg.deg + ) + + # Rounding + + # @helpers.requires_array_function_protocol() + def test_fix(self): + helpers.assert_quantity_equal(pnp.fix(3.13 * self.ureg.m), 3.0 * self.ureg.m) + helpers.assert_quantity_equal(pnp.fix(3.0 * self.ureg.m), 3.0 * self.ureg.m) + helpers.assert_quantity_equal( + pnp.fix([2.1, 2.9, -2.1, -2.9] * self.ureg.m), + [2.0, 2.0, -2.0, -2.0] * self.ureg.m, + ) + + # Sums, products, differences + + # @helpers.requires_array_function_protocol() + def test_prod(self): + axis = 0 + where = [[True, False], [True, True]] + + helpers.assert_quantity_equal(self.q.prod(), 24 * self.ureg.m**4) + helpers.assert_quantity_equal(self.q.prod(axis=axis), [3, 8] * self.ureg.m**2) + helpers.assert_quantity_equal(self.q.prod(where=where), 12 * self.ureg.m**3) + + # @helpers.requires_array_function_protocol() + def test_prod_numpy_func(self): + axis = 0 + where = [[True, False], [True, True]] + + helpers.assert_quantity_equal(pnp.prod(self.q), 24 * self.ureg.m**4) + helpers.assert_quantity_equal( + pnp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 + ) + helpers.assert_quantity_equal(pnp.prod(self.q, where=where), 12 * self.ureg.m**3) + + with pytest.raises(DimensionalityError): + pnp.prod(self.q, axis=axis, where=where) + helpers.assert_quantity_equal( + pnp.prod(self.q, axis=axis, where=[[True, False], [False, True]]), + [1, 4] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 + ) + + # @helpers.requires_array_function_protocol() + def test_nanprod_numpy_func(self): + helpers.assert_quantity_equal(pnp.nanprod(self.q_nan), 6 * self.ureg.m**3) + helpers.assert_quantity_equal( + pnp.nanprod(self.q_nan, axis=0), [3, 2] * self.ureg.m**2 + ) + helpers.assert_quantity_equal( + pnp.nanprod(self.q_nan, axis=1), [2, 3] * self.ureg.m**2 + ) + + def test_sum(self): + assert self.q.sum() == 10 * self.ureg.m + helpers.assert_quantity_equal(self.q.sum(0), [4, 6] * self.ureg.m) + helpers.assert_quantity_equal(self.q.sum(1), [3, 7] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_sum_numpy_func(self): + helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) + with pytest.raises(OffsetUnitCalculusError): + pnp.sum(self.q_temperature) + + # @helpers.requires_array_function_protocol() + def test_nansum_numpy_func(self): + helpers.assert_quantity_equal( + pnp.nansum(self.q_nan, axis=0), [4, 2] * self.ureg.m + ) + + def test_cumprod(self): + with pytest.raises(DimensionalityError): + self.q.cumprod() + helpers.assert_quantity_equal((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) + + # @helpers.requires_array_function_protocol() + def test_cumprod_numpy_func(self): + with pytest.raises(DimensionalityError): + pnp.cumprod(self.q) + helpers.assert_quantity_equal(pnp.cumprod(self.q / self.ureg.m), [1, 2, 6, 24]) + helpers.assert_quantity_equal( + pnp.cumprod(self.q / self.ureg.m, axis=1), [[1, 2], [3, 12]] + ) + + # @helpers.requires_array_function_protocol() + def test_nancumprod_numpy_func(self): + with pytest.raises(DimensionalityError): + pnp.nancumprod(self.q_nan) + helpers.assert_quantity_equal( + pnp.nancumprod(self.q_nan / self.ureg.m), [1, 2, 6, 6] + ) + + # @helpers.requires_array_function_protocol() + def test_diff(self): + helpers.assert_quantity_equal(pnp.diff(self.q, 1), [[1], [1]] * self.ureg.m) + helpers.assert_quantity_equal( + pnp.diff(self.q_temperature, 1), [[1], [1]] * self.ureg.delta_degC + ) + + # @helpers.requires_array_function_protocol() + def test_ediff1d(self): + helpers.assert_quantity_equal(pnp.ediff1d(self.q), [1, 1, 1] * self.ureg.m) + helpers.assert_quantity_equal( + pnp.ediff1d(self.q_temperature), [1, 1, 1] * self.ureg.delta_degC + ) + + # @helpers.requires_array_function_protocol() + def test_gradient(self): + grad = pnp.gradient([[1, 1], [3, 4]] * self.ureg.m, 1 * self.ureg.J) + helpers.assert_quantity_equal( + grad[0], [[2.0, 3.0], [2.0, 3.0]] * self.ureg.m / self.ureg.J + ) + helpers.assert_quantity_equal( + grad[1], [[0.0, 0.0], [1.0, 1.0]] * self.ureg.m / self.ureg.J + ) + + grad = pnp.gradient(self.Q_([[1, 1], [3, 4]], self.ureg.degC), 1 * self.ureg.J) + helpers.assert_quantity_equal( + grad[0], [[2.0, 3.0], [2.0, 3.0]] * self.ureg.delta_degC / self.ureg.J + ) + helpers.assert_quantity_equal( + grad[1], [[0.0, 0.0], [1.0, 1.0]] * self.ureg.delta_degC / self.ureg.J + ) + + # @helpers.requires_array_function_protocol() + def test_cross(self): + a = [[3, -3, 1]] * self.ureg.kPa + b = [[4, 9, 2]] * self.ureg.m**2 + helpers.assert_quantity_equal( + pnp.cross(a, b), [[-15, -2, 39]] * self.ureg.kPa * self.ureg.m**2 + ) + + # NP2: Remove this when we only support np>=2.0 + # @helpers.requires_array_function_protocol() + def test_trapz(self): + helpers.assert_quantity_equal( + pnp.trapz([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m), + 7.5 * self.ureg.J * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + # NP2: Remove this when we only support np>=2.0 + # trapezoid added in numpy 2.0 + # @helpers.requires_numpy_at_least("2.0") + def test_trapezoid(self): + helpers.assert_quantity_equal( + pnp.trapezoid([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m), + 7.5 * self.ureg.J * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + def test_dot(self): + helpers.assert_quantity_equal( + self.q.ravel().dot(np.array([1, 0, 0, 1])), 5 * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_dot_numpy_func(self): + helpers.assert_quantity_equal( + pnp.dot(self.q.ravel(), [0, 0, 1, 0] * self.ureg.dimensionless), + 3 * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + def test_einsum(self): + a = pnp.arange(25).reshape(5, 5) * self.ureg.m + b = pnp.arange(5) * self.ureg.m + helpers.assert_quantity_equal(pnp.einsum("ii", a), 60 * self.ureg.m) + helpers.assert_quantity_equal( + pnp.einsum("ii->i", a), np.array([0, 6, 12, 18, 24]) * self.ureg.m + ) + helpers.assert_quantity_equal(pnp.einsum("i,i", b, b), 30 * self.ureg.m**2) + helpers.assert_quantity_equal( + pnp.einsum("ij,j", a, b), + np.array([30, 80, 130, 180, 230]) * self.ureg.m**2, + ) + + # @helpers.requires_array_function_protocol() + def test_solve(self): + A = self.q + b = [[3], [7]] * self.ureg.s + x = pnp.linalg.solve(A, b) + + helpers.assert_quantity_almost_equal(x, self.Q_([[1], [1]], "s / m")) + + helpers.assert_quantity_almost_equal(pnp.dot(A, x), b) + + # Arithmetic operations + def test_addition_with_scalar(self): + a = np.array([0, 1, 2]) + b = 10.0 * self.ureg("gram/kilogram") + helpers.assert_quantity_almost_equal( + a + b, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) + ) + helpers.assert_quantity_almost_equal( + b + a, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) + ) + + def test_addition_with_incompatible_scalar(self): + a = np.array([0, 1, 2]) + b = 1.0 * self.ureg.m + with pytest.raises(DimensionalityError): + op.add(a, b) + with pytest.raises(DimensionalityError): + op.add(b, a) + + def test_power(self): + arr = np.array(range(3), dtype=float) + q = self.Q_(arr, "meter") + + for op_ in (op.pow, op.ipow, pnp.power): + q_cp = copy.copy(q) + with pytest.raises(DimensionalityError): + op_(2.0, q_cp) + arr_cp = copy.copy(arr) + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + with pytest.raises(DimensionalityError): + op_(q_cp, arr_cp) + q_cp = copy.copy(q) + q2_cp = copy.copy(q) + with pytest.raises(DimensionalityError): + op_(q_cp, q2_cp) + + helpers.assert_quantity_equal( + pnp.power(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") + ) + helpers.assert_quantity_equal( + self.q ** self.Q_(2), self.Q_([[1, 4], [9, 16]], "m**2") + ) + self.assertNDArrayEqual(arr ** self.Q_(2), np.array([0, 1, 4])) + + def test_sqrt(self): + q = self.Q_(100, "m**2") + helpers.assert_quantity_equal(pnp.sqrt(q), self.Q_(10, "m")) + + def test_cbrt(self): + q = self.Q_(1000, "m**3") + helpers.assert_quantity_equal(pnp.cbrt(q), self.Q_(10, "m")) + + @pytest.mark.xfail + # @helpers.requires_numpy + def test_exponentiation_array_exp_2(self): + arr = np.array(range(3), dtype=float) + # q = self.Q_(copy.copy(arr), None) + q = self.Q_(copy.copy(arr), "meter") + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + # this fails as expected since numpy 1.8.0 but... + with pytest.raises(DimensionalityError): + op.pow(arr_cp, q_cp) + # ..not for op.ipow ! + # q_cp is treated as if it is an array. The units are ignored. + # Quantity.__ipow__ is never called + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + with pytest.raises(DimensionalityError): + op.ipow(arr_cp, q_cp) + + +class TestNumpyUnclassified(TestNumpyMethods): + def test_tolist(self): + with pytest.raises(AttributeError): + (5 * self.ureg.m).tolist() + + assert self.q.tolist() == [ + [1 * self.ureg.m, 2 * self.ureg.m], + [3 * self.ureg.m, 4 * self.ureg.m], + ] + + def test_fill(self): + tmp = self.q + tmp.fill(6 * self.ureg.ft) + helpers.assert_quantity_equal(tmp, [[6, 6], [6, 6]] * self.ureg.ft) + tmp.fill(5 * self.ureg.m) + helpers.assert_quantity_equal(tmp, [[5, 5], [5, 5]] * self.ureg.m) + + def test_take(self): + helpers.assert_quantity_equal(self.q.take([0, 1, 2, 3]), self.q.flatten()) + + def test_put(self): + q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m + q.put([0, 2], [10.0, 20.0] * self.ureg.m) + helpers.assert_quantity_equal(q, [10.0, 2.0, 20.0, 4.0] * self.ureg.m) + + q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m + q.put([0, 2], [1.0, 2.0] * self.ureg.mm) + helpers.assert_quantity_equal(q, [0.001, 2.0, 0.002, 4.0] * self.ureg.m) + + q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m / self.ureg.mm + q.put([0, 2], [1.0, 2.0]) + helpers.assert_quantity_equal( + q, [0.001, 2.0, 0.002, 4.0] * self.ureg.m / self.ureg.mm + ) + + q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m + with pytest.raises(DimensionalityError): + q.put([0, 2], [4.0, 6.0] * self.ureg.J) + with pytest.raises(DimensionalityError): + q.put([0, 2], [4.0, 6.0]) + + def test_repeat(self): + helpers.assert_quantity_equal( + self.q.repeat(2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m + ) + + def test_sort(self): + q = [4, 5, 2, 3, 1, 6] * self.ureg.m + q.sort() + helpers.assert_quantity_equal(q, [1, 2, 3, 4, 5, 6] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_sort_numpy_func(self): + q = [4, 5, 2, 3, 1, 6] * self.ureg.m + helpers.assert_quantity_equal(pnp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) + + def test_argsort(self): + q = [1, 4, 5, 6, 2, 9] * self.ureg.MeV + self.assertNDArrayEqual(q.argsort(), [0, 4, 1, 2, 3, 5]) + + # @helpers.requires_array_function_protocol() + def test_argsort_numpy_func(self): + self.assertNDArrayEqual(pnp.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) + + def test_diagonal(self): + q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m + helpers.assert_quantity_equal(q.diagonal(offset=1), [2, 3] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_diagonal_numpy_func(self): + q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m + helpers.assert_quantity_equal(pnp.diagonal(q, offset=-1), [1, 2] * self.ureg.m) + + def test_compress(self): + helpers.assert_quantity_equal( + self.q.compress([False, True], axis=0), [[3, 4]] * self.ureg.m + ) + helpers.assert_quantity_equal( + self.q.compress([False, True], axis=1), [[2], [4]] * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_compress_nep18(self): + helpers.assert_quantity_equal( + pnp.compress([False, True], self.q, axis=1), [[2], [4]] * self.ureg.m + ) + + def test_searchsorted(self): + q = self.q.flatten() + self.assertNDArrayEqual(q.searchsorted([1.5, 2.5] * self.ureg.m), [1, 2]) + q = self.q.flatten() + with pytest.raises(DimensionalityError): + q.searchsorted([1.5, 2.5]) + + # @helpers.requires_array_function_protocol() + def test_searchsorted_numpy_func(self): + """Test searchsorted as numpy function.""" + q = self.q.flatten() + self.assertNDArrayEqual(pnp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) + + def test_nonzero(self): + q = [1, 0, 5, 6, 0, 9] * self.ureg.m + self.assertNDArrayEqual(q.nonzero()[0], [0, 2, 3, 5]) + + # @helpers.requires_array_function_protocol() + def test_nonzero_numpy_func(self): + q = [1, 0, 5, 6, 0, 9] * self.ureg.m + self.assertNDArrayEqual(pnp.nonzero(q)[0], [0, 2, 3, 5]) + + # @helpers.requires_array_function_protocol() + def test_any_numpy_func(self): + q = [0, 1] * self.ureg.m + assert pnp.any(q) + with pytest.raises(ValueError): + pnp.any(self.q_temperature) + + # @helpers.requires_array_function_protocol() + def test_all_numpy_func(self): + q = [0, 1] * self.ureg.m + assert not pnp.all(q) + with pytest.raises(ValueError): + pnp.all(self.q_temperature) + + # @helpers.requires_array_function_protocol() + def test_count_nonzero_numpy_func(self): + q = [1, 0, 5, 6, 0, 9] * self.ureg.m + assert pnp.count_nonzero(q) == 4 + + def test_max(self): + assert self.q.max() == 4 * self.ureg.m + + def test_max_numpy_func(self): + assert pnp.max(self.q) == 4 * self.ureg.m + + # @helpers.requires_array_function_protocol() + def test_max_with_axis_arg(self): + helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_max_with_initial_arg(self): + helpers.assert_quantity_equal( + pnp.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), + [[3, 3], [3, 4]] * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + def test_nanmax(self): + assert pnp.nanmax(self.q_nan) == 3 * self.ureg.m + + def test_argmax(self): + assert self.q.argmax() == 3 + + # @helpers.requires_array_function_protocol() + def test_argmax_numpy_func(self): + self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) + + # @helpers.requires_array_function_protocol() + def test_nanargmax_numpy_func(self): + self.assertNDArrayEqual(pnp.nanargmax(self.q_nan, axis=0), np.array([1, 0])) + + def test_maximum(self): + helpers.assert_quantity_equal( + pnp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") + ) + + def test_min(self): + assert self.q.min() == 1 * self.ureg.m + + # @helpers.requires_array_function_protocol() + def test_min_numpy_func(self): + assert pnp.min(self.q) == 1 * self.ureg.m + + # @helpers.requires_array_function_protocol() + def test_min_with_axis_arg(self): + helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_min_with_initial_arg(self): + helpers.assert_quantity_equal( + pnp.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), + [[1, 2], [3, 3]] * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + def test_nanmin(self): + assert pnp.nanmin(self.q_nan) == 1 * self.ureg.m + + def test_argmin(self): + assert self.q.argmin() == 0 + + # @helpers.requires_array_function_protocol() + def test_argmin_numpy_func(self): + self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) + + # @helpers.requires_array_function_protocol() + def test_nanargmin_numpy_func(self): + self.assertNDArrayEqual(pnp.nanargmin(self.q_nan, axis=0), np.array([0, 0])) + + def test_minimum(self): + helpers.assert_quantity_equal( + pnp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") + ) + + # NP2: Can remove Q_(arr).ptp test when we only support numpy>=2 + def test_ptp(self): + if not pnp.lib.NumpyVersion(pnp.__version__) >= "2.0.0b1": + assert self.q.ptp() == 3 * self.ureg.m + + # NP2: Keep this test for numpy>=2, it's only arr.ptp() that is deprecated + # @helpers.requires_array_function_protocol() + def test_ptp_numpy_func(self): + helpers.assert_quantity_equal(pnp.ptp(self.q, axis=0), [2, 2] * self.ureg.m) + + def test_clip(self): + helpers.assert_quantity_equal( + self.q.clip(max=2 * self.ureg.m), [[1, 2], [2, 2]] * self.ureg.m + ) + helpers.assert_quantity_equal( + self.q.clip(min=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m + ) + helpers.assert_quantity_equal( + self.q.clip(min=2 * self.ureg.m, max=3 * self.ureg.m), + [[2, 2], [3, 3]] * self.ureg.m, + ) + helpers.assert_quantity_equal( + self.q.clip(3 * self.ureg.m, None), [[3, 3], [3, 4]] * self.ureg.m + ) + helpers.assert_quantity_equal( + self.q.clip(3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m + ) + with pytest.raises(DimensionalityError): + self.q.clip(self.ureg.J) + with pytest.raises(DimensionalityError): + self.q.clip(1) + + # @helpers.requires_array_function_protocol() + def test_clip_numpy_func(self): + helpers.assert_quantity_equal( + pnp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m + ) + + def test_round(self): + q = [1, 1.33, 5.67, 22] * self.ureg.m + helpers.assert_quantity_equal(q.round(0), [1, 1, 6, 22] * self.ureg.m) + helpers.assert_quantity_equal(q.round(-1), [0, 0, 10, 20] * self.ureg.m) + helpers.assert_quantity_equal(q.round(1), [1, 1.3, 5.7, 22] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_round_numpy_func(self): + helpers.assert_quantity_equal( + pnp.around(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m + ) + helpers.assert_quantity_equal( + pnp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m + ) + + def test_trace(self): + assert self.q.trace() == (1 + 4) * self.ureg.m + + def test_cumsum(self): + helpers.assert_quantity_equal(self.q.cumsum(), [1, 3, 6, 10] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_cumsum_numpy_func(self): + helpers.assert_quantity_equal( + pnp.cumsum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_nancumsum_numpy_func(self): + helpers.assert_quantity_equal( + pnp.nancumsum(self.q_nan, axis=0), [[1, 2], [4, 2]] * self.ureg.m + ) + + def test_mean(self): + assert self.q.mean() == 2.5 * self.ureg.m + + # @helpers.requires_array_function_protocol() + def test_mean_numpy_func(self): + assert pnp.mean(self.q) == 2.5 * self.ureg.m + assert pnp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) + + # @helpers.requires_array_function_protocol() + def test_nanmean_numpy_func(self): + assert pnp.nanmean(self.q_nan) == 2 * self.ureg.m + + # @helpers.requires_array_function_protocol() + def test_average_numpy_func(self): + helpers.assert_quantity_almost_equal( + pnp.average(self.q, axis=0, weights=[1, 2]), + [2.33333, 3.33333] * self.ureg.m, + rtol=1e-5, + ) + + # @helpers.requires_array_function_protocol() + def test_median_numpy_func(self): + assert pnp.median(self.q) == 2.5 * self.ureg.m + + # @helpers.requires_array_function_protocol() + def test_nanmedian_numpy_func(self): + assert pnp.nanmedian(self.q_nan) == 2 * self.ureg.m + + def test_var(self): + assert self.q.var() == 1.25 * self.ureg.m**2 + + # @helpers.requires_array_function_protocol() + def test_var_numpy_func(self): + assert pnp.var(self.q) == 1.25 * self.ureg.m**2 + + # @helpers.requires_array_function_protocol() + def test_nanvar_numpy_func(self): + helpers.assert_quantity_almost_equal( + pnp.nanvar(self.q_nan), 0.66667 * self.ureg.m**2, rtol=1e-5 + ) + + def test_std(self): + helpers.assert_quantity_almost_equal( + self.q.std(), 1.11803 * self.ureg.m, rtol=1e-5 + ) + + # @helpers.requires_array_function_protocol() + def test_std_numpy_func(self): + helpers.assert_quantity_almost_equal( + pnp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 + ) + with pytest.raises(OffsetUnitCalculusError): + pnp.std(self.q_temperature) + + def test_cumprod(self): + with pytest.raises(DimensionalityError): + self.q.cumprod() + helpers.assert_quantity_equal((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) + + # @helpers.requires_array_function_protocol() + def test_nanstd_numpy_func(self): + helpers.assert_quantity_almost_equal( + pnp.nanstd(self.q_nan), 0.81650 * self.ureg.m, rtol=1e-5 + ) + + def test_conj(self): + helpers.assert_quantity_equal((self.q * (1 + 1j)).conj(), self.q * (1 - 1j)) + helpers.assert_quantity_equal( + (self.q * (1 + 1j)).conjugate(), self.q * (1 - 1j) + ) + + def test_getitem(self): + with pytest.raises(IndexError): + self.q.__getitem__((0, 10)) + helpers.assert_quantity_equal(self.q[0], [1, 2] * self.ureg.m) + assert self.q[1, 1] == 4 * self.ureg.m + + def test_setitem(self): + with pytest.raises(TypeError): + self.q[0, 0] = 1 + with pytest.raises(DimensionalityError): + self.q[0, 0] = 1 * self.ureg.J + with pytest.raises(DimensionalityError): + self.q[0] = 1 + with pytest.raises(DimensionalityError): + self.q[0] = pnp.ndarray([1, 2]) + with pytest.raises(DimensionalityError): + self.q[0] = 1 * self.ureg.J + + q = self.q.copy() + q[0] = 1 * self.ureg.m + helpers.assert_quantity_equal(q, [[1, 1], [3, 4]] * self.ureg.m) + + q = self.q.copy() + q[...] = 1 * self.ureg.m + helpers.assert_quantity_equal(q, [[1, 1], [1, 1]] * self.ureg.m) + + q = self.q.copy() + q[:] = 1 * self.ureg.m + helpers.assert_quantity_equal(q, [[1, 1], [1, 1]] * self.ureg.m) + + # check and see that dimensionless numbers work correctly + q = [0, 1, 2, 3] * self.ureg.dimensionless + q[0] = 1 + helpers.assert_quantity_equal(q, pnp.asarray([1, 1, 2, 3])) + q[0] = self.ureg.m / self.ureg.mm + helpers.assert_quantity_equal(q, pnp.asarray([1000, 1, 2, 3])) + + q = [0.0, 1.0, 2.0, 3.0] * self.ureg.m / self.ureg.mm + q[0] = 1.0 + helpers.assert_quantity_equal(q, [0.001, 1, 2, 3] * self.ureg.m / self.ureg.mm) + + # Check that this properly masks the first item without warning + q = self.ureg.Quantity( + pnp.ma.array([0.0, 1.0, 2.0, 3.0], mask=[False, True, False, False]), "m" + ) + with warnings.catch_warnings(record=True) as w: + q[0] = pnp.ma.masked + # Check for no warnings + assert not w + assert q.mask[0] + + def test_setitem_mixed_masked(self): + masked = pnp.ma.array( + [ + 1, + 2, + ], + mask=[True, False], + ) + q = self.Q_(pnp.ones(shape=(2,)), "m") + with pytest.raises(DimensionalityError): + q[:] = masked + + masked_q = self.Q_(masked, "mm") + q[:] = masked_q + helpers.assert_quantity_equal(q, [1.0, 0.002] * self.ureg.m) + + def test_iterator(self): + for q, v in zip(self.q.flatten(), [1, 2, 3, 4]): + assert q == v * self.ureg.m + + def test_iterable(self): + assert pnp.iterable(self.q) + assert not pnp.iterable(1 * self.ureg.m) + + def test_reversible_op(self): + """ """ + x = self.q.magnitude + u = self.Q_(pnp.ones(x.shape)) + helpers.assert_quantity_equal(x / self.q, u * x / self.q) + helpers.assert_quantity_equal(x * self.q, u * x * self.q) + helpers.assert_quantity_equal(x + u, u + x) + helpers.assert_quantity_equal(x - u, -(u - x)) + + def test_pickle(self, subtests): + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + with subtests.test(protocol): + q1 = [10, 20] * self.ureg.m + q2 = pickle.loads(pickle.dumps(q1, protocol)) + self.assertNDArrayEqual(q1.magnitude, q2.magnitude) + assert q1.units == q2.units + + def test_equal(self): + x = self.q.magnitude + u = self.Q_(pnp.ones(x.shape)) + true = pnp.ones_like(x, dtype=pnp.bool_) + false = pnp.zeros_like(x, dtype=pnp.bool_) + + helpers.assert_quantity_equal(u, u) + helpers.assert_quantity_equal(u == u, u.magnitude == u.magnitude) + helpers.assert_quantity_equal(u == 1, u.magnitude == 1) + + v = self.Q_(pnp.zeros(x.shape), "m") + w = self.Q_(pnp.ones(x.shape), "m") + self.assertNDArrayEqual(v == 1, false) + self.assertNDArrayEqual( + self.Q_(pnp.zeros_like(x), "m") == self.Q_(pnp.zeros_like(x), "s"), + false, + ) + self.assertNDArrayEqual(v == v, true) + self.assertNDArrayEqual(v == w, false) + self.assertNDArrayEqual(v == w.to("mm"), false) + self.assertNDArrayEqual(u == v, false) + + def test_shape(self): + u = self.Q_(pnp.arange(12)) + u.shape = 4, 3 + assert u.magnitude.shape == (4, 3) + + def test_dtype(self): + u = self.Q_(pnp.arange(12, dtype="uint32")) + + assert u.dtype == "uint32" + + # @helpers.requires_array_function_protocol() + def test_shape_numpy_func(self): + assert pnp.shape(self.q) == (2, 2) + + # @helpers.requires_array_function_protocol() + def test_len_numpy_func(self): + assert len(self.q) == 2 + + # @helpers.requires_array_function_protocol() + def test_ndim_numpy_func(self): + assert pnp.ndim(self.q) == 2 + + # @helpers.requires_array_function_protocol() + def test_copy_numpy_func(self): + q_copy = pnp.copy(self.q) + helpers.assert_quantity_equal(self.q, q_copy) + assert self.q is not q_copy + + # @helpers.requires_array_function_protocol() + def test_trim_zeros_numpy_func(self): + q = [0, 4, 3, 0, 2, 2, 0, 0, 0] * self.ureg.m + helpers.assert_quantity_equal(pnp.trim_zeros(q), [4, 3, 0, 2, 2] * self.ureg.m) + + # @helpers.requires_array_function_protocol() + def test_result_type_numpy_func(self): + assert pnp.result_type(self.q) == pnp.dtype("int") + + # @helpers.requires_array_function_protocol() + def test_nan_to_num_numpy_func(self): + helpers.assert_quantity_equal( + pnp.nan_to_num(self.q_nan, nan=-999 * self.ureg.mm), + [[1, 2], [3, -0.999]] * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + def test_meshgrid_numpy_func(self): + x = [1, 2] * self.ureg.m + y = [0, 50, 100] * self.ureg.mm + xx, yy = pnp.meshgrid(x, y) + helpers.assert_quantity_equal(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) + helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) + + # @helpers.requires_array_function_protocol() + def test_isclose_numpy_func(self): + q2 = [[1000.05, 2000], [3000.00007, 4001]] * self.ureg.mm + self.assertNDArrayEqual( + pnp.isclose(self.q, q2), np.array([[False, True], [True, False]]) + ) + self.assertNDArrayEqual( + pnp.isclose(self.q, q2, atol=1e-5 * self.ureg.mm, rtol=1e-7), + np.array([[False, True], [True, False]]), + ) + self.assertNDArrayEqual( + pnp.isclose(self.q, q2, atol=1e-5, rtol=1e-7), + np.array([[False, True], [True, False]]), + ) + + # @helpers.requires_array_function_protocol() + def test_interp_numpy_func(self): + x = [1, 4] * self.ureg.m + xp = pnp.linspace(0, 3, 5) * self.ureg.m + fp = self.Q_([0, 5, 10, 15, 20], self.ureg.degC) + helpers.assert_quantity_almost_equal( + pnp.interp(x, xp, fp), self.Q_([6.66667, 20.0], self.ureg.degC), rtol=1e-5 + ) + + x_ = np.array([1, 4]) + xp_ = pnp.linspace(0, 3, 5) + fp_ = [0, 5, 10, 15, 20] + + helpers.assert_quantity_almost_equal( + pnp.interp(x_, xp_, fp), self.Q_([6.6667, 20.0], self.ureg.degC), rtol=1e-5 + ) + helpers.assert_quantity_almost_equal( + pnp.interp(x, xp, fp_), [6.6667, 20.0], rtol=1e-5 + ) + + def test_comparisons(self): + self.assertNDArrayEqual( + self.q > 2 * self.ureg.m, np.array([[False, False], [True, True]]) + ) + self.assertNDArrayEqual( + self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]]) + ) + + # @helpers.requires_array_function_protocol() + def test_where(self): + helpers.assert_quantity_equal( + pnp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), + [[20, 2], [3, 4]] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.where(self.q >= 2 * self.ureg.m, self.q, 0), + [[0, 2], [3, 4]] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.where(self.q >= 2 * self.ureg.m, self.q, pnp.nan), + [[pnp.nan, 2], [3, 4]] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.where(self.q >= 3 * self.ureg.m, 0, self.q), + [[1, 2], [0, 0]] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.where(self.q >= 3 * self.ureg.m, pnp.nan, self.q), + [[1, 2], [pnp.nan, pnp.nan]] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.where(self.q >= 2 * self.ureg.m, self.q, np.array(pnp.nan)), + [[pnp.nan, 2], [3, 4]] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.where(self.q >= 3 * self.ureg.m, np.array(pnp.nan), self.q), + [[1, 2], [pnp.nan, pnp.nan]] * self.ureg.m, + ) + with pytest.raises(DimensionalityError): + pnp.where( + self.q < 2 * self.ureg.m, + self.q, + 0 * self.ureg.J, + ) + + helpers.assert_quantity_equal( + pnp.where([-1, 0, 1] * self.ureg.m, [1, 2, 1] * self.ureg.s, pnp.nan), + [1, pnp.nan, 1] * self.ureg.s, + ) + with pytest.raises( + ValueError, + match=".*Boolean value of Quantity with offset unit is ambiguous", + ): + pnp.where( + self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pnp.nan + ) + + # @helpers.requires_array_function_protocol() + def test_fabs(self): + helpers.assert_quantity_equal( + pnp.fabs(self.q - 2 * self.ureg.m), self.Q_([[1, 0], [1, 2]], "m") + ) + + # @helpers.requires_array_function_protocol() + def test_isin(self): + self.assertNDArrayEqual( + pnp.isin(self.q, self.Q_([0, 2, 4], "m")), + np.array([[False, True], [False, True]]), + ) + self.assertNDArrayEqual( + pnp.isin(self.q, self.Q_([0, 2, 4], "J")), + np.array([[False, False], [False, False]]), + ) + self.assertNDArrayEqual( + pnp.isin(self.q, [self.Q_(2, "m"), self.Q_(4, "J")]), + np.array([[False, True], [False, False]]), + ) + self.assertNDArrayEqual( + pnp.isin(self.q, self.q.m), np.array([[False, False], [False, False]]) + ) + self.assertNDArrayEqual( + pnp.isin(self.q / self.ureg.cm, [1, 3]), + np.array([[True, False], [True, False]]), + ) + with pytest.raises(ValueError): + pnp.isin(self.q.m, self.q) + + # @helpers.requires_array_function_protocol() + def test_percentile(self): + helpers.assert_quantity_equal(pnp.percentile(self.q, 25), self.Q_(1.75, "m")) + + # @helpers.requires_array_function_protocol() + def test_nanpercentile(self): + helpers.assert_quantity_equal( + pnp.nanpercentile(self.q_nan, 25), self.Q_(1.5, "m") + ) + + # @helpers.requires_array_function_protocol() + def test_quantile(self): + helpers.assert_quantity_equal(pnp.quantile(self.q, 0.25), self.Q_(1.75, "m")) + + # @helpers.requires_array_function_protocol() + def test_nanquantile(self): + helpers.assert_quantity_equal( + pnp.nanquantile(self.q_nan, 0.25), self.Q_(1.5, "m") + ) + + # @helpers.requires_array_function_protocol() + def test_copyto(self): + a = self.q.m + q = copy.copy(self.q) + pnp.copyto(q, 2 * q, where=[True, False]) + helpers.assert_quantity_equal(q, self.Q_([[2, 2], [6, 4]], "m")) + pnp.copyto(q, 0, where=[[False, False], [True, False]]) + helpers.assert_quantity_equal(q, self.Q_([[2, 2], [0, 4]], "m")) + pnp.copyto(a, q) + self.assertNDArrayEqual(a, np.array([[2, 2], [0, 4]])) + + # @helpers.requires_array_function_protocol() + def test_tile(self): + helpers.assert_quantity_equal( + pnp.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m + ) + + # @helpers.requires_numpy_at_least("1.20") + # @helpers.requires_array_function_protocol() + def test_sliding_window_view(self): + q = self.Q_([[1, 2, 2, 1], [2, 1, 1, 2], [1, 2, 2, 1]], "m") + actual = pnp.lib.stride_tricks.sliding_window_view(q, window_shape=(3, 3)) + expected = self.Q_( + [[[[1, 2, 2], [2, 1, 1], [1, 2, 2]], [[2, 2, 1], [1, 1, 2], [2, 2, 1]]]], + "m", + ) + helpers.assert_quantity_equal(actual, expected) + + # @helpers.requires_array_function_protocol() + def test_rot90(self): + helpers.assert_quantity_equal( + pnp.rot90(self.q), np.array([[2, 4], [1, 3]]) * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_insert(self): + helpers.assert_quantity_equal( + pnp.insert(self.q, 1, 0 * self.ureg.m, axis=1), + np.array([[1, 0, 2], [3, 0, 4]]) * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + def test_delete(self): + q = self.Q_(np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]), "m") + helpers.assert_quantity_equal( + pnp.delete(q, 1, axis=0), + np.array([[1, 2, 3, 4], [9, 10, 11, 12]]) * self.ureg.m, + ) + + helpers.assert_quantity_equal( + pnp.delete(q, pnp.s_[::2], 1), + np.array([[2, 4], [6, 8], [10, 12]]) * self.ureg.m, + ) + + helpers.assert_quantity_equal( + pnp.delete(q, [1, 3, 5], None), + np.array([1, 3, 5, 7, 8, 9, 10, 11, 12]) * self.ureg.m, + ) + + def test_ndarray_downcast(self): + with pytest.warns(UnitStrippedWarning): + pnp.asarray(self.q) + + def test_ndarray_downcast_with_dtype(self): + with pytest.warns(UnitStrippedWarning): + qarr = pnp.asarray(self.q, dtype=pnp.float64) + assert qarr.dtype == pnp.float64 + + def test_array_protocol_unavailable(self): + for attr in ("__array_struct__", "__array_interface__"): + with pytest.raises(AttributeError): + getattr(self.q, attr) + + # @helpers.requires_array_function_protocol() + def test_resize(self): + helpers.assert_quantity_equal( + pnp.resize(self.q, (2, 4)), [[1, 2, 3, 4], [1, 2, 3, 4]] * self.ureg.m + ) + + # @helpers.requires_array_function_protocol() + def test_pad(self): + # Tests reproduced with modification from NumPy documentation + a = [1, 2, 3, 4, 5] * self.ureg.m + b = self.Q_([4.0, 6.0, 8.0, 9.0, -3.0], "degC") + + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "constant"), [0, 0, 1, 2, 3, 4, 5, 0, 0, 0] * self.ureg.m + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "constant", constant_values=(0, 600 * self.ureg.cm)), + [0, 0, 1, 2, 3, 4, 5, 6, 6, 6] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.pad( + b, (2, 1), "constant", constant_values=(pnp.nan, self.Q_(10, "degC")) + ), + self.Q_([pnp.nan, pnp.nan, 4, 6, 8, 9, -3, 10], "degC"), + ) + with pytest.raises(DimensionalityError): + pnp.pad(a, (2, 3), "constant", constant_values=4) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "edge"), [1, 1, 1, 2, 3, 4, 5, 5, 5, 5] * self.ureg.m + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "linear_ramp"), + [0, 0, 1, 2, 3, 4, 5, 3, 1, 0] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "linear_ramp", end_values=(5, -4) * self.ureg.m), + [5, 3, 1, 2, 3, 4, 5, 2, -1, -4] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2,), "maximum"), [5, 5, 1, 2, 3, 4, 5, 5, 5] * self.ureg.m + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2,), "mean"), [3, 3, 1, 2, 3, 4, 5, 3, 3] * self.ureg.m + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2,), "median"), [3, 3, 1, 2, 3, 4, 5, 3, 3] * self.ureg.m + ) + helpers.assert_quantity_equal( + pnp.pad(self.q, ((3, 2), (2, 3)), "minimum"), + [ + [1, 1, 1, 2, 1, 1, 1], + [1, 1, 1, 2, 1, 1, 1], + [1, 1, 1, 2, 1, 1, 1], + [1, 1, 1, 2, 1, 1, 1], + [3, 3, 3, 4, 3, 3, 3], + [1, 1, 1, 2, 1, 1, 1], + [1, 1, 1, 2, 1, 1, 1], + ] + * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "reflect"), [3, 2, 1, 2, 3, 4, 5, 4, 3, 2] * self.ureg.m + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "reflect", reflect_type="odd"), + [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "symmetric"), [2, 1, 1, 2, 3, 4, 5, 5, 4, 3] * self.ureg.m + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "symmetric", reflect_type="odd"), + [0, 1, 1, 2, 3, 4, 5, 5, 6, 7] * self.ureg.m, + ) + helpers.assert_quantity_equal( + pnp.pad(a, (2, 3), "wrap"), [4, 5, 1, 2, 3, 4, 5, 1, 2, 3] * self.ureg.m + ) + + def pad_with(vector, pad_width, iaxis, kwargs): + pad_value = kwargs.get("padder", 10) + vector[: pad_width[0]] = pad_value + vector[-pad_width[1] :] = pad_value + + b = self.Q_(pnp.arange(6).reshape((2, 3)), "degC") + helpers.assert_quantity_equal( + pnp.pad(b, 2, pad_with), + self.Q_( + [ + [10, 10, 10, 10, 10, 10, 10], + [10, 10, 10, 10, 10, 10, 10], + [10, 10, 0, 1, 2, 10, 10], + [10, 10, 3, 4, 5, 10, 10], + [10, 10, 10, 10, 10, 10, 10], + [10, 10, 10, 10, 10, 10, 10], + ], + "degC", + ), + ) + helpers.assert_quantity_equal( + pnp.pad(b, 2, pad_with, padder=100), + self.Q_( + [ + [100, 100, 100, 100, 100, 100, 100], + [100, 100, 100, 100, 100, 100, 100], + [100, 100, 0, 1, 2, 100, 100], + [100, 100, 3, 4, 5, 100, 100], + [100, 100, 100, 100, 100, 100, 100], + [100, 100, 100, 100, 100, 100, 100], + ], + "degC", + ), + ) # Note: Does not support Quantity pad_with vectorized callable use + + # @helpers.requires_array_function_protocol() + def test_allclose(self): + assert pnp.allclose([1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.m) + assert pnp.allclose( + [1e10, 1e-8] * self.ureg.m, [1.00001e13, 1e-6] * self.ureg.mm + ) + assert not pnp.allclose( + [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.mm + ) + assert pnp.allclose( + [1e10, 1e-8] * self.ureg.m, + [1.00001e10, 1e-9] * self.ureg.m, + atol=1e-8 * self.ureg.m, + ) + + assert not pnp.allclose([1.0, pnp.nan] * self.ureg.m, [1.0, pnp.nan] * self.ureg.m) + + assert pnp.allclose( + [1.0, pnp.nan] * self.ureg.m, [1.0, pnp.nan] * self.ureg.m, equal_nan=True + ) + + assert pnp.allclose( + [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.m, atol=1e-8 + ) + + with pytest.raises(DimensionalityError): + assert pnp.allclose( + [1e10, 1e-8] * self.ureg.m, + [1.00001e10, 1e-9] * self.ureg.m, + atol=1e-8 * self.ureg.s, + ) + + # @helpers.requires_array_function_protocol() + def test_intersect1d(self): + helpers.assert_quantity_equal( + pnp.intersect1d([1, 3, 4, 3] * self.ureg.m, [3, 1, 2, 1] * self.ureg.m), + [1, 3] * self.ureg.m, + ) + + # @helpers.requires_array_function_protocol() + def test_linalg_norm(self): + q = np.array([[3, 5, 8], [4, 12, 15]]) * self.ureg.m + expected = [5, 13, 17] * self.ureg.m + helpers.assert_quantity_equal(pnp.linalg.norm(q, axis=0), expected) + + +@pytest.mark.skip +class TestBitTwiddlingUfuncs(TestUFuncs): + """Universal functions (ufuncs) > Bittwiddling functions + + http://docs.scipy.org/doc/numpy/reference/ufuncs.html#bittwiddlingfunctions + + bitwise_and(x1, x2[, out]) Compute the bitwise AND of two arrays elementwise. + bitwise_or(x1, x2[, out]) Compute the bitwise OR of two arrays elementwise. + bitwise_xor(x1, x2[, out]) Compute the bitwise XOR of two arrays elementwise. + invert(x[, out]) Compute bitwise inversion, or bitwise NOT, elementwise. + left_shift(x1, x2[, out]) Shift the bits of an integer to the left. + right_shift(x1, x2[, out]) Shift the bits of an integer to the right. + + Parameters + ---------- + + Returns + ------- + + """ + + @property + def qless(self): + return pnp.asarray([1, 2, 3, 4], dtype=pnp.uint8) * self.ureg.dimensionless + + @property + def qs(self): + return 8 * self.ureg.J + + @property + def q1(self): + return pnp.asarray([1, 2, 3, 4], dtype=pnp.uint8) * self.ureg.J + + @property + def q2(self): + return 2 * self.q1 + + @property + def qm(self): + return pnp.asarray([1, 2, 3, 4], dtype=pnp.uint8) * self.ureg.m + + def test_bitwise_and(self): + self._test2(pnp.bitwise_and, self.q1, (self.q2, self.qs), (self.qm,), "same") + + def test_bitwise_or(self): + self._test2( + pnp.bitwise_or, self.q1, (self.q1, self.q2, self.qs), (self.qm,), "same" + ) + + def test_bitwise_xor(self): + self._test2( + pnp.bitwise_xor, self.q1, (self.q1, self.q2, self.qs), (self.qm,), "same" + ) + + def test_invert(self): + self._test1(pnp.invert, (self.q1, self.q2, self.qs), (), "same") + + def test_left_shift(self): + self._test2( + pnp.left_shift, self.q1, (self.qless, 2), (self.q1, self.q2, self.qs), "same" + ) + + def test_right_shift(self): + self._test2( + pnp.right_shift, + self.q1, + (self.qless, 2), + (self.q1, self.q2, self.qs), + "same", + ) From 62e5f238d7d0f15f58ee9fc0bfed688a04ac6141 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 4 Jan 2025 21:53:19 +0000 Subject: [PATCH 02/49] tests --- src/pint_array/__init__.py | 12 +- tests/test_array.py | 968 +------------------------------------ 2 files changed, 31 insertions(+), 949 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 019a635..dcbdc9b 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -610,7 +610,6 @@ def where(condition, x1, x2, /): "sin", "sinh", "square", - "sqrt", "tan", "tanh", "trunc", @@ -624,6 +623,17 @@ def fun(x, /, *args, func_str=func_str, **kwargs): return ArrayUnitQuantity(magnitude, x.units) setattr(mod, func_str, fun) + + for func_str in ["sqrt"]: + + def fun(x, /, *args, func_str=func_str, **kwargs): + x = asarray(x) + magnitude = xp.asarray(x.magnitude, copy=True) + magnitude = getattr(xp, func_str)(magnitude, *args, **kwargs) + return ArrayUnitQuantity(magnitude, x.units ** 0.5) + + setattr(mod, func_str, fun) + elementwise_two_arrays = [ "add", diff --git a/tests/test_array.py b/tests/test_array.py index 4fac87d..4f0532b 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -54,7 +54,7 @@ def q_temperature(self): def assertNDArrayEqual(self, actual, desired): # Assert that the given arrays are equal, and are not Quantities - pnp.testing.assert_array_equal(actual, desired) + np.testing.assert_array_equal(actual, desired) assert not isinstance(actual, self.Q_) assert not isinstance(desired, self.Q_) @@ -74,7 +74,7 @@ def test_zeros_like(self): def test_empty_like(self): ret = pnp.empty_like(self.q) assert ret.shape == (2, 2) - assert isinstance(ret, pnp.ndarray) + assert isinstance(ret, np.ndarray) # @helpers.requires_array_function_protocol() def test_full_like(self): @@ -106,13 +106,6 @@ def test_reshape(self): self.q.reshape([1, 4]), [[1, 2, 3, 4]] * self.ureg.m ) - def test_ravel(self): - helpers.assert_quantity_equal(self.q.ravel(), [1, 2, 3, 4] * self.ureg.m) - - # @helpers.requires_array_function_protocol() - def test_ravel_numpy_func(self): - helpers.assert_quantity_equal(pnp.ravel(self.q), [1, 2, 3, 4] * self.ureg.m) - # Transpose-like operations # @helpers.requires_array_function_protocol() @@ -122,26 +115,9 @@ def test_moveaxis(self): ) # @helpers.requires_array_function_protocol() - def test_rollaxis(self): - helpers.assert_quantity_equal( - pnp.rollaxis(self.q, 1), np.array([[1, 2], [3, 4]]).T * self.ureg.m - ) - - # @helpers.requires_array_function_protocol() - def test_swapaxes(self): - helpers.assert_quantity_equal( - pnp.swapaxes(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m - ) - def test_transpose(self): helpers.assert_quantity_equal( - self.q.transpose(), [[1, 3], [2, 4]] * self.ureg.m - ) - - # @helpers.requires_array_function_protocol() - def test_transpose_numpy_func(self): - helpers.assert_quantity_equal( - pnp.transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m + pnp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m ) # @helpers.requires_array_function_protocol() @@ -152,38 +128,6 @@ def test_flip_numpy_func(self): # Changing number of dimensions - # @helpers.requires_array_function_protocol() - def test_atleast_1d(self): - actual = pnp.atleast_1d(self.Q_(0, self.ureg.degC), self.q.flatten()) - expected = (self.Q_(np.array([0]), self.ureg.degC), self.q.flatten()) - for ind_actual, ind_expected in zip(actual, expected): - helpers.assert_quantity_equal(ind_actual, ind_expected) - helpers.assert_quantity_equal(pnp.atleast_1d(self.q), self.q) - - # @helpers.requires_array_function_protocol() - def test_atleast_2d(self): - actual = pnp.atleast_2d(self.Q_(0, self.ureg.degC), self.q.flatten()) - expected = ( - self.Q_(np.array([[0]]), self.ureg.degC), - np.array([[1, 2, 3, 4]]) * self.ureg.m, - ) - for ind_actual, ind_expected in zip(actual, expected): - helpers.assert_quantity_equal(ind_actual, ind_expected) - helpers.assert_quantity_equal(pnp.atleast_2d(self.q), self.q) - - # @helpers.requires_array_function_protocol() - def test_atleast_3d(self): - actual = pnp.atleast_3d(self.Q_(0, self.ureg.degC), self.q.flatten()) - expected = ( - self.Q_(np.array([[[0]]]), self.ureg.degC), - np.array([[[1], [2], [3], [4]]]) * self.ureg.m, - ) - for ind_actual, ind_expected in zip(actual, expected): - helpers.assert_quantity_equal(ind_actual, ind_expected) - helpers.assert_quantity_equal( - pnp.atleast_3d(self.q), np.array([[[1], [2]], [[3], [4]]]) * self.ureg.m - ) - # @helpers.requires_array_function_protocol() def test_broadcast_to(self): helpers.assert_quantity_equal( @@ -201,14 +145,14 @@ def test_expand_dims(self): def test_squeeze(self): helpers.assert_quantity_equal(pnp.squeeze(self.q), self.q) helpers.assert_quantity_equal( - self.q.reshape([1, 4]).squeeze(), [1, 2, 3, 4] * self.ureg.m + pnp.squeeze(self.q.reshape([1, 4])), [1, 2, 3, 4] * self.ureg.m ) # Changing number of dimensions # Joining arrays # @helpers.requires_array_function_protocol() def test_concat_stack(self, subtests): - for func in (pnp.concatenate, pnp.stack, pnp.hstack, pnp.vstack, pnp.dstack): + for func in (pnp.concat, pnp.stack): with subtests.test(func=func): helpers.assert_quantity_equal( func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) @@ -225,49 +169,6 @@ def test_concat_stack(self, subtests): with pytest.raises(DimensionalityError): func([nz.m, self.q]) - # @helpers.requires_array_function_protocol() - def test_block_column_stack(self, subtests): - for func in (pnp.block, pnp.column_stack): - with subtests.test(func=func): - helpers.assert_quantity_equal( - func([self.q[:, 0], self.q[:, 1]]), - self.Q_(func([self.q[:, 0].m, self.q[:, 1].m]), self.ureg.m), - ) - - # One or more of the args is a bare array full of zeros or NaNs - helpers.assert_quantity_equal( - func( - [ - self.q_zero_or_nan[:, 0].m, - self.q[:, 0], - self.q_zero_or_nan[:, 1].m, - ] - ), - self.Q_( - func( - [ - self.q_zero_or_nan[:, 0].m, - self.q[:, 0].m, - self.q_zero_or_nan[:, 1].m, - ] - ), - self.ureg.m, - ), - ) - # One or more of the args is a bare array with at least one non-zero, - # non-NaN element - nz = self.q_zero_or_nan - nz.m[0, 0] = 1 - with pytest.raises(DimensionalityError): - func([nz[:, 0].m, self.q[:, 0]]) - - # @helpers.requires_array_function_protocol() - def test_append(self): - helpers.assert_quantity_equal( - pnp.append(self.q, [[0, 0]] * self.ureg.m, axis=0), - [[1, 2], [3, 4], [0, 0]] * self.ureg.m, - ) - def test_astype(self): actual = self.q.astype(pnp.float32) expected = self.Q_(np.array([[1.0, 2.0], [3.0, 4.0]], dtype=pnp.float32), "m") @@ -301,37 +202,6 @@ def test_roll(self): class TestNumpyMathematicalFunctions(TestNumpyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html - # Trigonometric functions - # @helpers.requires_array_function_protocol() - def test_unwrap(self): - helpers.assert_quantity_equal( - pnp.unwrap([0, 3 * pnp.pi] * self.ureg.radians), [0, pnp.pi] - ) - helpers.assert_quantity_equal( - pnp.unwrap([0, 540] * self.ureg.deg), [0, 180] * self.ureg.deg - ) - - # Rounding - - # @helpers.requires_array_function_protocol() - def test_fix(self): - helpers.assert_quantity_equal(pnp.fix(3.13 * self.ureg.m), 3.0 * self.ureg.m) - helpers.assert_quantity_equal(pnp.fix(3.0 * self.ureg.m), 3.0 * self.ureg.m) - helpers.assert_quantity_equal( - pnp.fix([2.1, 2.9, -2.1, -2.9] * self.ureg.m), - [2.0, 2.0, -2.0, -2.0] * self.ureg.m, - ) - - # Sums, products, differences - - # @helpers.requires_array_function_protocol() - def test_prod(self): - axis = 0 - where = [[True, False], [True, True]] - - helpers.assert_quantity_equal(self.q.prod(), 24 * self.ureg.m**4) - helpers.assert_quantity_equal(self.q.prod(axis=axis), [3, 8] * self.ureg.m**2) - helpers.assert_quantity_equal(self.q.prod(where=where), 12 * self.ureg.m**3) # @helpers.requires_array_function_protocol() def test_prod_numpy_func(self): @@ -354,150 +224,12 @@ def test_prod_numpy_func(self): pnp.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 ) - # @helpers.requires_array_function_protocol() - def test_nanprod_numpy_func(self): - helpers.assert_quantity_equal(pnp.nanprod(self.q_nan), 6 * self.ureg.m**3) - helpers.assert_quantity_equal( - pnp.nanprod(self.q_nan, axis=0), [3, 2] * self.ureg.m**2 - ) - helpers.assert_quantity_equal( - pnp.nanprod(self.q_nan, axis=1), [2, 3] * self.ureg.m**2 - ) - - def test_sum(self): - assert self.q.sum() == 10 * self.ureg.m - helpers.assert_quantity_equal(self.q.sum(0), [4, 6] * self.ureg.m) - helpers.assert_quantity_equal(self.q.sum(1), [3, 7] * self.ureg.m) - # @helpers.requires_array_function_protocol() def test_sum_numpy_func(self): helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) with pytest.raises(OffsetUnitCalculusError): pnp.sum(self.q_temperature) - # @helpers.requires_array_function_protocol() - def test_nansum_numpy_func(self): - helpers.assert_quantity_equal( - pnp.nansum(self.q_nan, axis=0), [4, 2] * self.ureg.m - ) - - def test_cumprod(self): - with pytest.raises(DimensionalityError): - self.q.cumprod() - helpers.assert_quantity_equal((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) - - # @helpers.requires_array_function_protocol() - def test_cumprod_numpy_func(self): - with pytest.raises(DimensionalityError): - pnp.cumprod(self.q) - helpers.assert_quantity_equal(pnp.cumprod(self.q / self.ureg.m), [1, 2, 6, 24]) - helpers.assert_quantity_equal( - pnp.cumprod(self.q / self.ureg.m, axis=1), [[1, 2], [3, 12]] - ) - - # @helpers.requires_array_function_protocol() - def test_nancumprod_numpy_func(self): - with pytest.raises(DimensionalityError): - pnp.nancumprod(self.q_nan) - helpers.assert_quantity_equal( - pnp.nancumprod(self.q_nan / self.ureg.m), [1, 2, 6, 6] - ) - - # @helpers.requires_array_function_protocol() - def test_diff(self): - helpers.assert_quantity_equal(pnp.diff(self.q, 1), [[1], [1]] * self.ureg.m) - helpers.assert_quantity_equal( - pnp.diff(self.q_temperature, 1), [[1], [1]] * self.ureg.delta_degC - ) - - # @helpers.requires_array_function_protocol() - def test_ediff1d(self): - helpers.assert_quantity_equal(pnp.ediff1d(self.q), [1, 1, 1] * self.ureg.m) - helpers.assert_quantity_equal( - pnp.ediff1d(self.q_temperature), [1, 1, 1] * self.ureg.delta_degC - ) - - # @helpers.requires_array_function_protocol() - def test_gradient(self): - grad = pnp.gradient([[1, 1], [3, 4]] * self.ureg.m, 1 * self.ureg.J) - helpers.assert_quantity_equal( - grad[0], [[2.0, 3.0], [2.0, 3.0]] * self.ureg.m / self.ureg.J - ) - helpers.assert_quantity_equal( - grad[1], [[0.0, 0.0], [1.0, 1.0]] * self.ureg.m / self.ureg.J - ) - - grad = pnp.gradient(self.Q_([[1, 1], [3, 4]], self.ureg.degC), 1 * self.ureg.J) - helpers.assert_quantity_equal( - grad[0], [[2.0, 3.0], [2.0, 3.0]] * self.ureg.delta_degC / self.ureg.J - ) - helpers.assert_quantity_equal( - grad[1], [[0.0, 0.0], [1.0, 1.0]] * self.ureg.delta_degC / self.ureg.J - ) - - # @helpers.requires_array_function_protocol() - def test_cross(self): - a = [[3, -3, 1]] * self.ureg.kPa - b = [[4, 9, 2]] * self.ureg.m**2 - helpers.assert_quantity_equal( - pnp.cross(a, b), [[-15, -2, 39]] * self.ureg.kPa * self.ureg.m**2 - ) - - # NP2: Remove this when we only support np>=2.0 - # @helpers.requires_array_function_protocol() - def test_trapz(self): - helpers.assert_quantity_equal( - pnp.trapz([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m), - 7.5 * self.ureg.J * self.ureg.m, - ) - - # @helpers.requires_array_function_protocol() - # NP2: Remove this when we only support np>=2.0 - # trapezoid added in numpy 2.0 - # @helpers.requires_numpy_at_least("2.0") - def test_trapezoid(self): - helpers.assert_quantity_equal( - pnp.trapezoid([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m), - 7.5 * self.ureg.J * self.ureg.m, - ) - - # @helpers.requires_array_function_protocol() - def test_dot(self): - helpers.assert_quantity_equal( - self.q.ravel().dot(np.array([1, 0, 0, 1])), 5 * self.ureg.m - ) - - # @helpers.requires_array_function_protocol() - def test_dot_numpy_func(self): - helpers.assert_quantity_equal( - pnp.dot(self.q.ravel(), [0, 0, 1, 0] * self.ureg.dimensionless), - 3 * self.ureg.m, - ) - - # @helpers.requires_array_function_protocol() - def test_einsum(self): - a = pnp.arange(25).reshape(5, 5) * self.ureg.m - b = pnp.arange(5) * self.ureg.m - helpers.assert_quantity_equal(pnp.einsum("ii", a), 60 * self.ureg.m) - helpers.assert_quantity_equal( - pnp.einsum("ii->i", a), np.array([0, 6, 12, 18, 24]) * self.ureg.m - ) - helpers.assert_quantity_equal(pnp.einsum("i,i", b, b), 30 * self.ureg.m**2) - helpers.assert_quantity_equal( - pnp.einsum("ij,j", a, b), - np.array([30, 80, 130, 180, 230]) * self.ureg.m**2, - ) - - # @helpers.requires_array_function_protocol() - def test_solve(self): - A = self.q - b = [[3], [7]] * self.ureg.s - x = pnp.linalg.solve(A, b) - - helpers.assert_quantity_almost_equal(x, self.Q_([[1], [1]], "s / m")) - - helpers.assert_quantity_almost_equal(pnp.dot(A, x), b) - # Arithmetic operations def test_addition_with_scalar(self): a = np.array([0, 1, 2]) @@ -521,7 +253,7 @@ def test_power(self): arr = np.array(range(3), dtype=float) q = self.Q_(arr, "meter") - for op_ in (op.pow, op.ipow, pnp.power): + for op_ in [pnp.pow]: q_cp = copy.copy(q) with pytest.raises(DimensionalityError): op_(2.0, q_cp) @@ -536,7 +268,7 @@ def test_power(self): op_(q_cp, q2_cp) helpers.assert_quantity_equal( - pnp.power(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") + pnp.pow(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") ) helpers.assert_quantity_equal( self.q ** self.Q_(2), self.Q_([[1, 4], [9, 16]], "m**2") @@ -547,10 +279,6 @@ def test_sqrt(self): q = self.Q_(100, "m**2") helpers.assert_quantity_equal(pnp.sqrt(q), self.Q_(10, "m")) - def test_cbrt(self): - q = self.Q_(1000, "m**3") - helpers.assert_quantity_equal(pnp.cbrt(q), self.Q_(10, "m")) - @pytest.mark.xfail # @helpers.requires_numpy def test_exponentiation_array_exp_2(self): @@ -572,109 +300,27 @@ def test_exponentiation_array_exp_2(self): class TestNumpyUnclassified(TestNumpyMethods): - def test_tolist(self): - with pytest.raises(AttributeError): - (5 * self.ureg.m).tolist() - - assert self.q.tolist() == [ - [1 * self.ureg.m, 2 * self.ureg.m], - [3 * self.ureg.m, 4 * self.ureg.m], - ] - - def test_fill(self): - tmp = self.q - tmp.fill(6 * self.ureg.ft) - helpers.assert_quantity_equal(tmp, [[6, 6], [6, 6]] * self.ureg.ft) - tmp.fill(5 * self.ureg.m) - helpers.assert_quantity_equal(tmp, [[5, 5], [5, 5]] * self.ureg.m) - - def test_take(self): - helpers.assert_quantity_equal(self.q.take([0, 1, 2, 3]), self.q.flatten()) - - def test_put(self): - q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m - q.put([0, 2], [10.0, 20.0] * self.ureg.m) - helpers.assert_quantity_equal(q, [10.0, 2.0, 20.0, 4.0] * self.ureg.m) - - q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m - q.put([0, 2], [1.0, 2.0] * self.ureg.mm) - helpers.assert_quantity_equal(q, [0.001, 2.0, 0.002, 4.0] * self.ureg.m) - - q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m / self.ureg.mm - q.put([0, 2], [1.0, 2.0]) - helpers.assert_quantity_equal( - q, [0.001, 2.0, 0.002, 4.0] * self.ureg.m / self.ureg.mm - ) - - q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m - with pytest.raises(DimensionalityError): - q.put([0, 2], [4.0, 6.0] * self.ureg.J) - with pytest.raises(DimensionalityError): - q.put([0, 2], [4.0, 6.0]) def test_repeat(self): helpers.assert_quantity_equal( - self.q.repeat(2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m + pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m ) - def test_sort(self): - q = [4, 5, 2, 3, 1, 6] * self.ureg.m - q.sort() - helpers.assert_quantity_equal(q, [1, 2, 3, 4, 5, 6] * self.ureg.m) - # @helpers.requires_array_function_protocol() def test_sort_numpy_func(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m helpers.assert_quantity_equal(pnp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) - def test_argsort(self): - q = [1, 4, 5, 6, 2, 9] * self.ureg.MeV - self.assertNDArrayEqual(q.argsort(), [0, 4, 1, 2, 3, 5]) - # @helpers.requires_array_function_protocol() def test_argsort_numpy_func(self): self.assertNDArrayEqual(pnp.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) - def test_diagonal(self): - q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m - helpers.assert_quantity_equal(q.diagonal(offset=1), [2, 3] * self.ureg.m) - - # @helpers.requires_array_function_protocol() - def test_diagonal_numpy_func(self): - q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m - helpers.assert_quantity_equal(pnp.diagonal(q, offset=-1), [1, 2] * self.ureg.m) - - def test_compress(self): - helpers.assert_quantity_equal( - self.q.compress([False, True], axis=0), [[3, 4]] * self.ureg.m - ) - helpers.assert_quantity_equal( - self.q.compress([False, True], axis=1), [[2], [4]] * self.ureg.m - ) - - # @helpers.requires_array_function_protocol() - def test_compress_nep18(self): - helpers.assert_quantity_equal( - pnp.compress([False, True], self.q, axis=1), [[2], [4]] * self.ureg.m - ) - - def test_searchsorted(self): - q = self.q.flatten() - self.assertNDArrayEqual(q.searchsorted([1.5, 2.5] * self.ureg.m), [1, 2]) - q = self.q.flatten() - with pytest.raises(DimensionalityError): - q.searchsorted([1.5, 2.5]) - # @helpers.requires_array_function_protocol() def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() self.assertNDArrayEqual(pnp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) - def test_nonzero(self): - q = [1, 0, 5, 6, 0, 9] * self.ureg.m - self.assertNDArrayEqual(q.nonzero()[0], [0, 2, 3, 5]) - # @helpers.requires_array_function_protocol() def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m @@ -694,14 +340,6 @@ def test_all_numpy_func(self): with pytest.raises(ValueError): pnp.all(self.q_temperature) - # @helpers.requires_array_function_protocol() - def test_count_nonzero_numpy_func(self): - q = [1, 0, 5, 6, 0, 9] * self.ureg.m - assert pnp.count_nonzero(q) == 4 - - def test_max(self): - assert self.q.max() == 4 * self.ureg.m - def test_max_numpy_func(self): assert pnp.max(self.q) == 4 * self.ureg.m @@ -716,29 +354,15 @@ def test_max_with_initial_arg(self): [[3, 3], [3, 4]] * self.ureg.m, ) - # @helpers.requires_array_function_protocol() - def test_nanmax(self): - assert pnp.nanmax(self.q_nan) == 3 * self.ureg.m - - def test_argmax(self): - assert self.q.argmax() == 3 - # @helpers.requires_array_function_protocol() def test_argmax_numpy_func(self): self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) - # @helpers.requires_array_function_protocol() - def test_nanargmax_numpy_func(self): - self.assertNDArrayEqual(pnp.nanargmax(self.q_nan, axis=0), np.array([1, 0])) - def test_maximum(self): helpers.assert_quantity_equal( pnp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) - def test_min(self): - assert self.q.min() == 1 * self.ureg.m - # @helpers.requires_array_function_protocol() def test_min_numpy_func(self): assert pnp.min(self.q) == 1 * self.ureg.m @@ -754,162 +378,57 @@ def test_min_with_initial_arg(self): [[1, 2], [3, 3]] * self.ureg.m, ) - # @helpers.requires_array_function_protocol() - def test_nanmin(self): - assert pnp.nanmin(self.q_nan) == 1 * self.ureg.m - - def test_argmin(self): - assert self.q.argmin() == 0 - # @helpers.requires_array_function_protocol() def test_argmin_numpy_func(self): self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) - # @helpers.requires_array_function_protocol() - def test_nanargmin_numpy_func(self): - self.assertNDArrayEqual(pnp.nanargmin(self.q_nan, axis=0), np.array([0, 0])) - def test_minimum(self): helpers.assert_quantity_equal( pnp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) - # NP2: Can remove Q_(arr).ptp test when we only support numpy>=2 - def test_ptp(self): - if not pnp.lib.NumpyVersion(pnp.__version__) >= "2.0.0b1": - assert self.q.ptp() == 3 * self.ureg.m - - # NP2: Keep this test for numpy>=2, it's only arr.ptp() that is deprecated - # @helpers.requires_array_function_protocol() - def test_ptp_numpy_func(self): - helpers.assert_quantity_equal(pnp.ptp(self.q, axis=0), [2, 2] * self.ureg.m) - - def test_clip(self): - helpers.assert_quantity_equal( - self.q.clip(max=2 * self.ureg.m), [[1, 2], [2, 2]] * self.ureg.m - ) - helpers.assert_quantity_equal( - self.q.clip(min=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m - ) - helpers.assert_quantity_equal( - self.q.clip(min=2 * self.ureg.m, max=3 * self.ureg.m), - [[2, 2], [3, 3]] * self.ureg.m, - ) - helpers.assert_quantity_equal( - self.q.clip(3 * self.ureg.m, None), [[3, 3], [3, 4]] * self.ureg.m - ) - helpers.assert_quantity_equal( - self.q.clip(3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m - ) - with pytest.raises(DimensionalityError): - self.q.clip(self.ureg.J) - with pytest.raises(DimensionalityError): - self.q.clip(1) - # @helpers.requires_array_function_protocol() def test_clip_numpy_func(self): helpers.assert_quantity_equal( pnp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) - def test_round(self): - q = [1, 1.33, 5.67, 22] * self.ureg.m - helpers.assert_quantity_equal(q.round(0), [1, 1, 6, 22] * self.ureg.m) - helpers.assert_quantity_equal(q.round(-1), [0, 0, 10, 20] * self.ureg.m) - helpers.assert_quantity_equal(q.round(1), [1, 1.3, 5.7, 22] * self.ureg.m) - # @helpers.requires_array_function_protocol() def test_round_numpy_func(self): - helpers.assert_quantity_equal( - pnp.around(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m - ) helpers.assert_quantity_equal( pnp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) - def test_trace(self): - assert self.q.trace() == (1 + 4) * self.ureg.m - - def test_cumsum(self): - helpers.assert_quantity_equal(self.q.cumsum(), [1, 3, 6, 10] * self.ureg.m) - - # @helpers.requires_array_function_protocol() - def test_cumsum_numpy_func(self): - helpers.assert_quantity_equal( - pnp.cumsum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m - ) - # @helpers.requires_array_function_protocol() - def test_nancumsum_numpy_func(self): + def test_cumulative_sum(self): helpers.assert_quantity_equal( - pnp.nancumsum(self.q_nan, axis=0), [[1, 2], [4, 2]] * self.ureg.m + pnp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) - def test_mean(self): - assert self.q.mean() == 2.5 * self.ureg.m - # @helpers.requires_array_function_protocol() def test_mean_numpy_func(self): assert pnp.mean(self.q) == 2.5 * self.ureg.m assert pnp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) - # @helpers.requires_array_function_protocol() - def test_nanmean_numpy_func(self): - assert pnp.nanmean(self.q_nan) == 2 * self.ureg.m - - # @helpers.requires_array_function_protocol() - def test_average_numpy_func(self): - helpers.assert_quantity_almost_equal( - pnp.average(self.q, axis=0, weights=[1, 2]), - [2.33333, 3.33333] * self.ureg.m, - rtol=1e-5, - ) - - # @helpers.requires_array_function_protocol() - def test_median_numpy_func(self): - assert pnp.median(self.q) == 2.5 * self.ureg.m - - # @helpers.requires_array_function_protocol() - def test_nanmedian_numpy_func(self): - assert pnp.nanmedian(self.q_nan) == 2 * self.ureg.m - - def test_var(self): - assert self.q.var() == 1.25 * self.ureg.m**2 - # @helpers.requires_array_function_protocol() def test_var_numpy_func(self): assert pnp.var(self.q) == 1.25 * self.ureg.m**2 - - # @helpers.requires_array_function_protocol() - def test_nanvar_numpy_func(self): - helpers.assert_quantity_almost_equal( - pnp.nanvar(self.q_nan), 0.66667 * self.ureg.m**2, rtol=1e-5 - ) - - def test_std(self): - helpers.assert_quantity_almost_equal( - self.q.std(), 1.11803 * self.ureg.m, rtol=1e-5 - ) + assert pnp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 # @helpers.requires_array_function_protocol() def test_std_numpy_func(self): helpers.assert_quantity_almost_equal( pnp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 ) - with pytest.raises(OffsetUnitCalculusError): - pnp.std(self.q_temperature) + helpers.assert_quantity_almost_equal( + pnp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 + ) def test_cumprod(self): with pytest.raises(DimensionalityError): self.q.cumprod() helpers.assert_quantity_equal((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) - # @helpers.requires_array_function_protocol() - def test_nanstd_numpy_func(self): - helpers.assert_quantity_almost_equal( - pnp.nanstd(self.q_nan), 0.81650 * self.ureg.m, rtol=1e-5 - ) - def test_conj(self): helpers.assert_quantity_equal((self.q * (1 + 1j)).conj(), self.q * (1 - 1j)) helpers.assert_quantity_equal( @@ -930,7 +449,7 @@ def test_setitem(self): with pytest.raises(DimensionalityError): self.q[0] = 1 with pytest.raises(DimensionalityError): - self.q[0] = pnp.ndarray([1, 2]) + self.q[0] = np.ndarray([1, 2]) with pytest.raises(DimensionalityError): self.q[0] = 1 * self.ureg.J @@ -957,40 +476,6 @@ def test_setitem(self): q[0] = 1.0 helpers.assert_quantity_equal(q, [0.001, 1, 2, 3] * self.ureg.m / self.ureg.mm) - # Check that this properly masks the first item without warning - q = self.ureg.Quantity( - pnp.ma.array([0.0, 1.0, 2.0, 3.0], mask=[False, True, False, False]), "m" - ) - with warnings.catch_warnings(record=True) as w: - q[0] = pnp.ma.masked - # Check for no warnings - assert not w - assert q.mask[0] - - def test_setitem_mixed_masked(self): - masked = pnp.ma.array( - [ - 1, - 2, - ], - mask=[True, False], - ) - q = self.Q_(pnp.ones(shape=(2,)), "m") - with pytest.raises(DimensionalityError): - q[:] = masked - - masked_q = self.Q_(masked, "mm") - q[:] = masked_q - helpers.assert_quantity_equal(q, [1.0, 0.002] * self.ureg.m) - - def test_iterator(self): - for q, v in zip(self.q.flatten(), [1, 2, 3, 4]): - assert q == v * self.ureg.m - - def test_iterable(self): - assert pnp.iterable(self.q) - assert not pnp.iterable(1 * self.ureg.m) - def test_reversible_op(self): """ """ x = self.q.magnitude @@ -1011,8 +496,8 @@ def test_pickle(self, subtests): def test_equal(self): x = self.q.magnitude u = self.Q_(pnp.ones(x.shape)) - true = pnp.ones_like(x, dtype=pnp.bool_) - false = pnp.zeros_like(x, dtype=pnp.bool_) + true = pnp.ones_like(x, dtype=np.bool_) + false = pnp.zeros_like(x, dtype=np.bool_) helpers.assert_quantity_equal(u, u) helpers.assert_quantity_equal(u == u, u.magnitude == u.magnitude) @@ -1029,12 +514,7 @@ def test_equal(self): self.assertNDArrayEqual(v == w, false) self.assertNDArrayEqual(v == w.to("mm"), false) self.assertNDArrayEqual(u == v, false) - - def test_shape(self): - u = self.Q_(pnp.arange(12)) - u.shape = 4, 3 - assert u.magnitude.shape == (4, 3) - + def test_dtype(self): u = self.Q_(pnp.arange(12, dtype="uint32")) @@ -1042,37 +522,11 @@ def test_dtype(self): # @helpers.requires_array_function_protocol() def test_shape_numpy_func(self): - assert pnp.shape(self.q) == (2, 2) - - # @helpers.requires_array_function_protocol() - def test_len_numpy_func(self): - assert len(self.q) == 2 + assert pnp.asarray(self.q).shape == (2, 2) # @helpers.requires_array_function_protocol() def test_ndim_numpy_func(self): - assert pnp.ndim(self.q) == 2 - - # @helpers.requires_array_function_protocol() - def test_copy_numpy_func(self): - q_copy = pnp.copy(self.q) - helpers.assert_quantity_equal(self.q, q_copy) - assert self.q is not q_copy - - # @helpers.requires_array_function_protocol() - def test_trim_zeros_numpy_func(self): - q = [0, 4, 3, 0, 2, 2, 0, 0, 0] * self.ureg.m - helpers.assert_quantity_equal(pnp.trim_zeros(q), [4, 3, 0, 2, 2] * self.ureg.m) - - # @helpers.requires_array_function_protocol() - def test_result_type_numpy_func(self): - assert pnp.result_type(self.q) == pnp.dtype("int") - - # @helpers.requires_array_function_protocol() - def test_nan_to_num_numpy_func(self): - helpers.assert_quantity_equal( - pnp.nan_to_num(self.q_nan, nan=-999 * self.ureg.mm), - [[1, 2], [3, -0.999]] * self.ureg.m, - ) + assert pnp.asarray(self.q).ndim == 2 # @helpers.requires_array_function_protocol() def test_meshgrid_numpy_func(self): @@ -1082,41 +536,6 @@ def test_meshgrid_numpy_func(self): helpers.assert_quantity_equal(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) - # @helpers.requires_array_function_protocol() - def test_isclose_numpy_func(self): - q2 = [[1000.05, 2000], [3000.00007, 4001]] * self.ureg.mm - self.assertNDArrayEqual( - pnp.isclose(self.q, q2), np.array([[False, True], [True, False]]) - ) - self.assertNDArrayEqual( - pnp.isclose(self.q, q2, atol=1e-5 * self.ureg.mm, rtol=1e-7), - np.array([[False, True], [True, False]]), - ) - self.assertNDArrayEqual( - pnp.isclose(self.q, q2, atol=1e-5, rtol=1e-7), - np.array([[False, True], [True, False]]), - ) - - # @helpers.requires_array_function_protocol() - def test_interp_numpy_func(self): - x = [1, 4] * self.ureg.m - xp = pnp.linspace(0, 3, 5) * self.ureg.m - fp = self.Q_([0, 5, 10, 15, 20], self.ureg.degC) - helpers.assert_quantity_almost_equal( - pnp.interp(x, xp, fp), self.Q_([6.66667, 20.0], self.ureg.degC), rtol=1e-5 - ) - - x_ = np.array([1, 4]) - xp_ = pnp.linspace(0, 3, 5) - fp_ = [0, 5, 10, 15, 20] - - helpers.assert_quantity_almost_equal( - pnp.interp(x_, xp_, fp), self.Q_([6.6667, 20.0], self.ureg.degC), rtol=1e-5 - ) - helpers.assert_quantity_almost_equal( - pnp.interp(x, xp, fp_), [6.6667, 20.0], rtol=1e-5 - ) - def test_comparisons(self): self.assertNDArrayEqual( self.q > 2 * self.ureg.m, np.array([[False, False], [True, True]]) @@ -1174,355 +593,8 @@ def test_where(self): self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pnp.nan ) - # @helpers.requires_array_function_protocol() - def test_fabs(self): - helpers.assert_quantity_equal( - pnp.fabs(self.q - 2 * self.ureg.m), self.Q_([[1, 0], [1, 2]], "m") - ) - - # @helpers.requires_array_function_protocol() - def test_isin(self): - self.assertNDArrayEqual( - pnp.isin(self.q, self.Q_([0, 2, 4], "m")), - np.array([[False, True], [False, True]]), - ) - self.assertNDArrayEqual( - pnp.isin(self.q, self.Q_([0, 2, 4], "J")), - np.array([[False, False], [False, False]]), - ) - self.assertNDArrayEqual( - pnp.isin(self.q, [self.Q_(2, "m"), self.Q_(4, "J")]), - np.array([[False, True], [False, False]]), - ) - self.assertNDArrayEqual( - pnp.isin(self.q, self.q.m), np.array([[False, False], [False, False]]) - ) - self.assertNDArrayEqual( - pnp.isin(self.q / self.ureg.cm, [1, 3]), - np.array([[True, False], [True, False]]), - ) - with pytest.raises(ValueError): - pnp.isin(self.q.m, self.q) - - # @helpers.requires_array_function_protocol() - def test_percentile(self): - helpers.assert_quantity_equal(pnp.percentile(self.q, 25), self.Q_(1.75, "m")) - - # @helpers.requires_array_function_protocol() - def test_nanpercentile(self): - helpers.assert_quantity_equal( - pnp.nanpercentile(self.q_nan, 25), self.Q_(1.5, "m") - ) - - # @helpers.requires_array_function_protocol() - def test_quantile(self): - helpers.assert_quantity_equal(pnp.quantile(self.q, 0.25), self.Q_(1.75, "m")) - - # @helpers.requires_array_function_protocol() - def test_nanquantile(self): - helpers.assert_quantity_equal( - pnp.nanquantile(self.q_nan, 0.25), self.Q_(1.5, "m") - ) - - # @helpers.requires_array_function_protocol() - def test_copyto(self): - a = self.q.m - q = copy.copy(self.q) - pnp.copyto(q, 2 * q, where=[True, False]) - helpers.assert_quantity_equal(q, self.Q_([[2, 2], [6, 4]], "m")) - pnp.copyto(q, 0, where=[[False, False], [True, False]]) - helpers.assert_quantity_equal(q, self.Q_([[2, 2], [0, 4]], "m")) - pnp.copyto(a, q) - self.assertNDArrayEqual(a, np.array([[2, 2], [0, 4]])) - # @helpers.requires_array_function_protocol() def test_tile(self): helpers.assert_quantity_equal( pnp.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m ) - - # @helpers.requires_numpy_at_least("1.20") - # @helpers.requires_array_function_protocol() - def test_sliding_window_view(self): - q = self.Q_([[1, 2, 2, 1], [2, 1, 1, 2], [1, 2, 2, 1]], "m") - actual = pnp.lib.stride_tricks.sliding_window_view(q, window_shape=(3, 3)) - expected = self.Q_( - [[[[1, 2, 2], [2, 1, 1], [1, 2, 2]], [[2, 2, 1], [1, 1, 2], [2, 2, 1]]]], - "m", - ) - helpers.assert_quantity_equal(actual, expected) - - # @helpers.requires_array_function_protocol() - def test_rot90(self): - helpers.assert_quantity_equal( - pnp.rot90(self.q), np.array([[2, 4], [1, 3]]) * self.ureg.m - ) - - # @helpers.requires_array_function_protocol() - def test_insert(self): - helpers.assert_quantity_equal( - pnp.insert(self.q, 1, 0 * self.ureg.m, axis=1), - np.array([[1, 0, 2], [3, 0, 4]]) * self.ureg.m, - ) - - # @helpers.requires_array_function_protocol() - def test_delete(self): - q = self.Q_(np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]), "m") - helpers.assert_quantity_equal( - pnp.delete(q, 1, axis=0), - np.array([[1, 2, 3, 4], [9, 10, 11, 12]]) * self.ureg.m, - ) - - helpers.assert_quantity_equal( - pnp.delete(q, pnp.s_[::2], 1), - np.array([[2, 4], [6, 8], [10, 12]]) * self.ureg.m, - ) - - helpers.assert_quantity_equal( - pnp.delete(q, [1, 3, 5], None), - np.array([1, 3, 5, 7, 8, 9, 10, 11, 12]) * self.ureg.m, - ) - - def test_ndarray_downcast(self): - with pytest.warns(UnitStrippedWarning): - pnp.asarray(self.q) - - def test_ndarray_downcast_with_dtype(self): - with pytest.warns(UnitStrippedWarning): - qarr = pnp.asarray(self.q, dtype=pnp.float64) - assert qarr.dtype == pnp.float64 - - def test_array_protocol_unavailable(self): - for attr in ("__array_struct__", "__array_interface__"): - with pytest.raises(AttributeError): - getattr(self.q, attr) - - # @helpers.requires_array_function_protocol() - def test_resize(self): - helpers.assert_quantity_equal( - pnp.resize(self.q, (2, 4)), [[1, 2, 3, 4], [1, 2, 3, 4]] * self.ureg.m - ) - - # @helpers.requires_array_function_protocol() - def test_pad(self): - # Tests reproduced with modification from NumPy documentation - a = [1, 2, 3, 4, 5] * self.ureg.m - b = self.Q_([4.0, 6.0, 8.0, 9.0, -3.0], "degC") - - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "constant"), [0, 0, 1, 2, 3, 4, 5, 0, 0, 0] * self.ureg.m - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "constant", constant_values=(0, 600 * self.ureg.cm)), - [0, 0, 1, 2, 3, 4, 5, 6, 6, 6] * self.ureg.m, - ) - helpers.assert_quantity_equal( - pnp.pad( - b, (2, 1), "constant", constant_values=(pnp.nan, self.Q_(10, "degC")) - ), - self.Q_([pnp.nan, pnp.nan, 4, 6, 8, 9, -3, 10], "degC"), - ) - with pytest.raises(DimensionalityError): - pnp.pad(a, (2, 3), "constant", constant_values=4) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "edge"), [1, 1, 1, 2, 3, 4, 5, 5, 5, 5] * self.ureg.m - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "linear_ramp"), - [0, 0, 1, 2, 3, 4, 5, 3, 1, 0] * self.ureg.m, - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "linear_ramp", end_values=(5, -4) * self.ureg.m), - [5, 3, 1, 2, 3, 4, 5, 2, -1, -4] * self.ureg.m, - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2,), "maximum"), [5, 5, 1, 2, 3, 4, 5, 5, 5] * self.ureg.m - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2,), "mean"), [3, 3, 1, 2, 3, 4, 5, 3, 3] * self.ureg.m - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2,), "median"), [3, 3, 1, 2, 3, 4, 5, 3, 3] * self.ureg.m - ) - helpers.assert_quantity_equal( - pnp.pad(self.q, ((3, 2), (2, 3)), "minimum"), - [ - [1, 1, 1, 2, 1, 1, 1], - [1, 1, 1, 2, 1, 1, 1], - [1, 1, 1, 2, 1, 1, 1], - [1, 1, 1, 2, 1, 1, 1], - [3, 3, 3, 4, 3, 3, 3], - [1, 1, 1, 2, 1, 1, 1], - [1, 1, 1, 2, 1, 1, 1], - ] - * self.ureg.m, - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "reflect"), [3, 2, 1, 2, 3, 4, 5, 4, 3, 2] * self.ureg.m - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "reflect", reflect_type="odd"), - [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] * self.ureg.m, - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "symmetric"), [2, 1, 1, 2, 3, 4, 5, 5, 4, 3] * self.ureg.m - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "symmetric", reflect_type="odd"), - [0, 1, 1, 2, 3, 4, 5, 5, 6, 7] * self.ureg.m, - ) - helpers.assert_quantity_equal( - pnp.pad(a, (2, 3), "wrap"), [4, 5, 1, 2, 3, 4, 5, 1, 2, 3] * self.ureg.m - ) - - def pad_with(vector, pad_width, iaxis, kwargs): - pad_value = kwargs.get("padder", 10) - vector[: pad_width[0]] = pad_value - vector[-pad_width[1] :] = pad_value - - b = self.Q_(pnp.arange(6).reshape((2, 3)), "degC") - helpers.assert_quantity_equal( - pnp.pad(b, 2, pad_with), - self.Q_( - [ - [10, 10, 10, 10, 10, 10, 10], - [10, 10, 10, 10, 10, 10, 10], - [10, 10, 0, 1, 2, 10, 10], - [10, 10, 3, 4, 5, 10, 10], - [10, 10, 10, 10, 10, 10, 10], - [10, 10, 10, 10, 10, 10, 10], - ], - "degC", - ), - ) - helpers.assert_quantity_equal( - pnp.pad(b, 2, pad_with, padder=100), - self.Q_( - [ - [100, 100, 100, 100, 100, 100, 100], - [100, 100, 100, 100, 100, 100, 100], - [100, 100, 0, 1, 2, 100, 100], - [100, 100, 3, 4, 5, 100, 100], - [100, 100, 100, 100, 100, 100, 100], - [100, 100, 100, 100, 100, 100, 100], - ], - "degC", - ), - ) # Note: Does not support Quantity pad_with vectorized callable use - - # @helpers.requires_array_function_protocol() - def test_allclose(self): - assert pnp.allclose([1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.m) - assert pnp.allclose( - [1e10, 1e-8] * self.ureg.m, [1.00001e13, 1e-6] * self.ureg.mm - ) - assert not pnp.allclose( - [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.mm - ) - assert pnp.allclose( - [1e10, 1e-8] * self.ureg.m, - [1.00001e10, 1e-9] * self.ureg.m, - atol=1e-8 * self.ureg.m, - ) - - assert not pnp.allclose([1.0, pnp.nan] * self.ureg.m, [1.0, pnp.nan] * self.ureg.m) - - assert pnp.allclose( - [1.0, pnp.nan] * self.ureg.m, [1.0, pnp.nan] * self.ureg.m, equal_nan=True - ) - - assert pnp.allclose( - [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.m, atol=1e-8 - ) - - with pytest.raises(DimensionalityError): - assert pnp.allclose( - [1e10, 1e-8] * self.ureg.m, - [1.00001e10, 1e-9] * self.ureg.m, - atol=1e-8 * self.ureg.s, - ) - - # @helpers.requires_array_function_protocol() - def test_intersect1d(self): - helpers.assert_quantity_equal( - pnp.intersect1d([1, 3, 4, 3] * self.ureg.m, [3, 1, 2, 1] * self.ureg.m), - [1, 3] * self.ureg.m, - ) - - # @helpers.requires_array_function_protocol() - def test_linalg_norm(self): - q = np.array([[3, 5, 8], [4, 12, 15]]) * self.ureg.m - expected = [5, 13, 17] * self.ureg.m - helpers.assert_quantity_equal(pnp.linalg.norm(q, axis=0), expected) - - -@pytest.mark.skip -class TestBitTwiddlingUfuncs(TestUFuncs): - """Universal functions (ufuncs) > Bittwiddling functions - - http://docs.scipy.org/doc/numpy/reference/ufuncs.html#bittwiddlingfunctions - - bitwise_and(x1, x2[, out]) Compute the bitwise AND of two arrays elementwise. - bitwise_or(x1, x2[, out]) Compute the bitwise OR of two arrays elementwise. - bitwise_xor(x1, x2[, out]) Compute the bitwise XOR of two arrays elementwise. - invert(x[, out]) Compute bitwise inversion, or bitwise NOT, elementwise. - left_shift(x1, x2[, out]) Shift the bits of an integer to the left. - right_shift(x1, x2[, out]) Shift the bits of an integer to the right. - - Parameters - ---------- - - Returns - ------- - - """ - - @property - def qless(self): - return pnp.asarray([1, 2, 3, 4], dtype=pnp.uint8) * self.ureg.dimensionless - - @property - def qs(self): - return 8 * self.ureg.J - - @property - def q1(self): - return pnp.asarray([1, 2, 3, 4], dtype=pnp.uint8) * self.ureg.J - - @property - def q2(self): - return 2 * self.q1 - - @property - def qm(self): - return pnp.asarray([1, 2, 3, 4], dtype=pnp.uint8) * self.ureg.m - - def test_bitwise_and(self): - self._test2(pnp.bitwise_and, self.q1, (self.q2, self.qs), (self.qm,), "same") - - def test_bitwise_or(self): - self._test2( - pnp.bitwise_or, self.q1, (self.q1, self.q2, self.qs), (self.qm,), "same" - ) - - def test_bitwise_xor(self): - self._test2( - pnp.bitwise_xor, self.q1, (self.q1, self.q2, self.qs), (self.qm,), "same" - ) - - def test_invert(self): - self._test1(pnp.invert, (self.q1, self.q2, self.qs), (), "same") - - def test_left_shift(self): - self._test2( - pnp.left_shift, self.q1, (self.qless, 2), (self.q1, self.q2, self.qs), "same" - ) - - def test_right_shift(self): - self._test2( - pnp.right_shift, - self.q1, - (self.qless, 2), - (self.q1, self.q2, self.qs), - "same", - ) From 1ad9ca97ee86331347b1109ff31a64e73f84fe2a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Jan 2025 21:55:21 +0000 Subject: [PATCH 03/49] style: pre-commit fixes --- src/pint_array/__init__.py | 5 ++--- tests/test_array.py | 24 +++++++++++++----------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index dcbdc9b..56b7a72 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -623,18 +623,17 @@ def fun(x, /, *args, func_str=func_str, **kwargs): return ArrayUnitQuantity(magnitude, x.units) setattr(mod, func_str, fun) - + for func_str in ["sqrt"]: def fun(x, /, *args, func_str=func_str, **kwargs): x = asarray(x) magnitude = xp.asarray(x.magnitude, copy=True) magnitude = getattr(xp, func_str)(magnitude, *args, **kwargs) - return ArrayUnitQuantity(magnitude, x.units ** 0.5) + return ArrayUnitQuantity(magnitude, x.units**0.5) setattr(mod, func_str, fun) - elementwise_two_arrays = [ "add", "atan2", diff --git a/tests/test_array.py b/tests/test_array.py index 4f0532b..e9fca71 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -3,21 +3,22 @@ import copy import operator as op import pickle -import warnings +import numpy as np import pytest +from pint import DimensionalityError, OffsetUnitCalculusError -from pint import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning # from pint.compat import np from pint.testsuite import helpers -from pint.testsuite.test_umath import TestUFuncs -import pint_array; -import numpy as np; -pnp = pint_array.pint_namespace(np); -from pint import UnitRegistry; +import pint_array + +pnp = pint_array.pint_namespace(np) +from pint import UnitRegistry + ureg = UnitRegistry() + # @helpers.requires_numpy class TestNumpyMethods: @classmethod @@ -98,7 +99,7 @@ def test_flatten(self): helpers.assert_quantity_equal(self.q.flatten(), [1, 2, 3, 4] * self.ureg.m) def test_flat(self): - for q, v in zip(self.q.flat, [1, 2, 3, 4]): + for q, v in zip(self.q.flat, [1, 2, 3, 4], strict=False): assert q == v * self.ureg.m def test_reshape(self): @@ -212,7 +213,9 @@ def test_prod_numpy_func(self): helpers.assert_quantity_equal( pnp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 ) - helpers.assert_quantity_equal(pnp.prod(self.q, where=where), 12 * self.ureg.m**3) + helpers.assert_quantity_equal( + pnp.prod(self.q, where=where), 12 * self.ureg.m**3 + ) with pytest.raises(DimensionalityError): pnp.prod(self.q, axis=axis, where=where) @@ -300,7 +303,6 @@ def test_exponentiation_array_exp_2(self): class TestNumpyUnclassified(TestNumpyMethods): - def test_repeat(self): helpers.assert_quantity_equal( pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m @@ -514,7 +516,7 @@ def test_equal(self): self.assertNDArrayEqual(v == w, false) self.assertNDArrayEqual(v == w.to("mm"), false) self.assertNDArrayEqual(u == v, false) - + def test_dtype(self): u = self.Q_(pnp.arange(12, dtype="uint32")) From 984a3c9b0bce624b85a9102f12f783d1d5894e4a Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 15:41:46 +0000 Subject: [PATCH 04/49] tests --- src/pint_array/__init__.py | 1 + tests/test_array.py | 79 +++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index dcbdc9b..e37ca13 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -720,6 +720,7 @@ def linalg_fun(x1, x2, /, **kwargs): setattr(mod, name, get_linalg_fun(name)) def matrix_transpose(x): + x = asarray(x) return x.mT mod.matrix_transpose = matrix_transpose diff --git a/tests/test_array.py b/tests/test_array.py index 4f0532b..5d23986 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -18,7 +18,6 @@ from pint import UnitRegistry; ureg = UnitRegistry() -# @helpers.requires_numpy class TestNumpyMethods: @classmethod def setup_class(cls): @@ -62,21 +61,22 @@ def assertNDArrayEqual(self, actual, desired): class TestNumpyArrayCreation(TestNumpyMethods): # https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html - # @helpers.requires_array_function_protocol() + @pytest.mark.xfail(reason="Scalar arguement issue ") def test_ones_like(self): self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) - # @helpers.requires_array_function_protocol() + def test_zeros_like(self): self.assertNDArrayEqual(pnp.zeros_like(self.q), np.array([[0, 0], [0, 0]])) - # @helpers.requires_array_function_protocol() + def test_empty_like(self): ret = pnp.empty_like(self.q) assert ret.shape == (2, 2) - assert isinstance(ret, np.ndarray) + assert isinstance(ret.magnitude, np.ndarray) - # @helpers.requires_array_function_protocol() + + @pytest.mark.xfail(reason="Scalar arguement issue ") def test_full_like(self): helpers.assert_quantity_equal( pnp.full_like(self.q, self.Q_(0, self.ureg.degC)), @@ -108,19 +108,19 @@ def test_reshape(self): # Transpose-like operations - # @helpers.requires_array_function_protocol() + def test_moveaxis(self): helpers.assert_quantity_equal( pnp.moveaxis(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_transpose(self): helpers.assert_quantity_equal( pnp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_flip_numpy_func(self): helpers.assert_quantity_equal( pnp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m @@ -128,20 +128,20 @@ def test_flip_numpy_func(self): # Changing number of dimensions - # @helpers.requires_array_function_protocol() + def test_broadcast_to(self): helpers.assert_quantity_equal( pnp.broadcast_to(self.q[:, 1], (2, 2)), np.array([[2, 4], [2, 4]]) * self.ureg.m, ) - # @helpers.requires_array_function_protocol() + def test_expand_dims(self): helpers.assert_quantity_equal( pnp.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_squeeze(self): helpers.assert_quantity_equal(pnp.squeeze(self.q), self.q) helpers.assert_quantity_equal( @@ -150,7 +150,7 @@ def test_squeeze(self): # Changing number of dimensions # Joining arrays - # @helpers.requires_array_function_protocol() + def test_concat_stack(self, subtests): for func in (pnp.concat, pnp.stack): with subtests.test(func=func): @@ -203,7 +203,7 @@ def test_roll(self): class TestNumpyMathematicalFunctions(TestNumpyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html - # @helpers.requires_array_function_protocol() + def test_prod_numpy_func(self): axis = 0 where = [[True, False], [True, True]] @@ -224,7 +224,7 @@ def test_prod_numpy_func(self): pnp.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 ) - # @helpers.requires_array_function_protocol() + def test_sum_numpy_func(self): helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) with pytest.raises(OffsetUnitCalculusError): @@ -280,7 +280,6 @@ def test_sqrt(self): helpers.assert_quantity_equal(pnp.sqrt(q), self.Q_(10, "m")) @pytest.mark.xfail - # @helpers.requires_numpy def test_exponentiation_array_exp_2(self): arr = np.array(range(3), dtype=float) # q = self.Q_(copy.copy(arr), None) @@ -306,34 +305,34 @@ def test_repeat(self): pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_sort_numpy_func(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m helpers.assert_quantity_equal(pnp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) - # @helpers.requires_array_function_protocol() + def test_argsort_numpy_func(self): self.assertNDArrayEqual(pnp.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) - # @helpers.requires_array_function_protocol() + def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() self.assertNDArrayEqual(pnp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) - # @helpers.requires_array_function_protocol() + def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m self.assertNDArrayEqual(pnp.nonzero(q)[0], [0, 2, 3, 5]) - # @helpers.requires_array_function_protocol() + def test_any_numpy_func(self): q = [0, 1] * self.ureg.m assert pnp.any(q) with pytest.raises(ValueError): pnp.any(self.q_temperature) - # @helpers.requires_array_function_protocol() + def test_all_numpy_func(self): q = [0, 1] * self.ureg.m assert not pnp.all(q) @@ -343,18 +342,18 @@ def test_all_numpy_func(self): def test_max_numpy_func(self): assert pnp.max(self.q) == 4 * self.ureg.m - # @helpers.requires_array_function_protocol() + def test_max_with_axis_arg(self): helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) - # @helpers.requires_array_function_protocol() + def test_max_with_initial_arg(self): helpers.assert_quantity_equal( pnp.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m, ) - # @helpers.requires_array_function_protocol() + def test_argmax_numpy_func(self): self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) @@ -363,22 +362,22 @@ def test_maximum(self): pnp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) - # @helpers.requires_array_function_protocol() + def test_min_numpy_func(self): assert pnp.min(self.q) == 1 * self.ureg.m - # @helpers.requires_array_function_protocol() + def test_min_with_axis_arg(self): helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) - # @helpers.requires_array_function_protocol() + def test_min_with_initial_arg(self): helpers.assert_quantity_equal( pnp.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[1, 2], [3, 3]] * self.ureg.m, ) - # @helpers.requires_array_function_protocol() + def test_argmin_numpy_func(self): self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) @@ -387,35 +386,35 @@ def test_minimum(self): pnp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) - # @helpers.requires_array_function_protocol() + def test_clip_numpy_func(self): helpers.assert_quantity_equal( pnp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_round_numpy_func(self): helpers.assert_quantity_equal( pnp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_cumulative_sum(self): helpers.assert_quantity_equal( pnp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_mean_numpy_func(self): assert pnp.mean(self.q) == 2.5 * self.ureg.m assert pnp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) - # @helpers.requires_array_function_protocol() + def test_var_numpy_func(self): assert pnp.var(self.q) == 1.25 * self.ureg.m**2 assert pnp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 - # @helpers.requires_array_function_protocol() + def test_std_numpy_func(self): helpers.assert_quantity_almost_equal( pnp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 @@ -520,15 +519,15 @@ def test_dtype(self): assert u.dtype == "uint32" - # @helpers.requires_array_function_protocol() + def test_shape_numpy_func(self): assert pnp.asarray(self.q).shape == (2, 2) - # @helpers.requires_array_function_protocol() + def test_ndim_numpy_func(self): assert pnp.asarray(self.q).ndim == 2 - # @helpers.requires_array_function_protocol() + def test_meshgrid_numpy_func(self): x = [1, 2] * self.ureg.m y = [0, 50, 100] * self.ureg.mm @@ -544,7 +543,7 @@ def test_comparisons(self): self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]]) ) - # @helpers.requires_array_function_protocol() + def test_where(self): helpers.assert_quantity_equal( pnp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), @@ -593,7 +592,7 @@ def test_where(self): self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pnp.nan ) - # @helpers.requires_array_function_protocol() + def test_tile(self): helpers.assert_quantity_equal( pnp.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m From 849825612d91bd73ea7be7017f876269fa983b1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:43:00 +0000 Subject: [PATCH 05/49] style: pre-commit fixes --- tests/test_array.py | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index 61db7c6..3cf19b8 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -18,6 +18,7 @@ ureg = UnitRegistry() + class TestNumpyMethods: @classmethod def setup_class(cls): @@ -65,17 +66,14 @@ class TestNumpyArrayCreation(TestNumpyMethods): def test_ones_like(self): self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) - def test_zeros_like(self): self.assertNDArrayEqual(pnp.zeros_like(self.q), np.array([[0, 0], [0, 0]])) - def test_empty_like(self): ret = pnp.empty_like(self.q) assert ret.shape == (2, 2) assert isinstance(ret.magnitude, np.ndarray) - @pytest.mark.xfail(reason="Scalar arguement issue ") def test_full_like(self): helpers.assert_quantity_equal( @@ -108,19 +106,16 @@ def test_reshape(self): # Transpose-like operations - def test_moveaxis(self): helpers.assert_quantity_equal( pnp.moveaxis(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) - def test_transpose(self): helpers.assert_quantity_equal( pnp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m ) - def test_flip_numpy_func(self): helpers.assert_quantity_equal( pnp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m @@ -128,20 +123,17 @@ def test_flip_numpy_func(self): # Changing number of dimensions - def test_broadcast_to(self): helpers.assert_quantity_equal( pnp.broadcast_to(self.q[:, 1], (2, 2)), np.array([[2, 4], [2, 4]]) * self.ureg.m, ) - def test_expand_dims(self): helpers.assert_quantity_equal( pnp.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m ) - def test_squeeze(self): helpers.assert_quantity_equal(pnp.squeeze(self.q), self.q) helpers.assert_quantity_equal( @@ -150,7 +142,7 @@ def test_squeeze(self): # Changing number of dimensions # Joining arrays - + def test_concat_stack(self, subtests): for func in (pnp.concat, pnp.stack): with subtests.test(func=func): @@ -203,7 +195,6 @@ def test_roll(self): class TestNumpyMathematicalFunctions(TestNumpyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html - def test_prod_numpy_func(self): axis = 0 where = [[True, False], [True, True]] @@ -226,7 +217,6 @@ def test_prod_numpy_func(self): pnp.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 ) - def test_sum_numpy_func(self): helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) with pytest.raises(OffsetUnitCalculusError): @@ -306,34 +296,28 @@ def test_repeat(self): pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m ) - def test_sort_numpy_func(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m helpers.assert_quantity_equal(pnp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) - def test_argsort_numpy_func(self): self.assertNDArrayEqual(pnp.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) - def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() self.assertNDArrayEqual(pnp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) - def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m self.assertNDArrayEqual(pnp.nonzero(q)[0], [0, 2, 3, 5]) - def test_any_numpy_func(self): q = [0, 1] * self.ureg.m assert pnp.any(q) with pytest.raises(ValueError): pnp.any(self.q_temperature) - def test_all_numpy_func(self): q = [0, 1] * self.ureg.m assert not pnp.all(q) @@ -343,18 +327,15 @@ def test_all_numpy_func(self): def test_max_numpy_func(self): assert pnp.max(self.q) == 4 * self.ureg.m - def test_max_with_axis_arg(self): helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) - def test_max_with_initial_arg(self): helpers.assert_quantity_equal( pnp.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m, ) - def test_argmax_numpy_func(self): self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) @@ -363,22 +344,18 @@ def test_maximum(self): pnp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) - def test_min_numpy_func(self): assert pnp.min(self.q) == 1 * self.ureg.m - def test_min_with_axis_arg(self): helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) - def test_min_with_initial_arg(self): helpers.assert_quantity_equal( pnp.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[1, 2], [3, 3]] * self.ureg.m, ) - def test_argmin_numpy_func(self): self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) @@ -387,35 +364,29 @@ def test_minimum(self): pnp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) - def test_clip_numpy_func(self): helpers.assert_quantity_equal( pnp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) - def test_round_numpy_func(self): helpers.assert_quantity_equal( pnp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) - def test_cumulative_sum(self): helpers.assert_quantity_equal( pnp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) - def test_mean_numpy_func(self): assert pnp.mean(self.q) == 2.5 * self.ureg.m assert pnp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) - def test_var_numpy_func(self): assert pnp.var(self.q) == 1.25 * self.ureg.m**2 assert pnp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 - def test_std_numpy_func(self): helpers.assert_quantity_almost_equal( pnp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 @@ -520,15 +491,12 @@ def test_dtype(self): assert u.dtype == "uint32" - def test_shape_numpy_func(self): assert pnp.asarray(self.q).shape == (2, 2) - def test_ndim_numpy_func(self): assert pnp.asarray(self.q).ndim == 2 - def test_meshgrid_numpy_func(self): x = [1, 2] * self.ureg.m y = [0, 50, 100] * self.ureg.mm @@ -544,7 +512,6 @@ def test_comparisons(self): self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]]) ) - def test_where(self): helpers.assert_quantity_equal( pnp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), @@ -593,7 +560,6 @@ def test_where(self): self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pnp.nan ) - def test_tile(self): helpers.assert_quantity_equal( pnp.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m From 3d367ba9e0ccc2679b5bc7aef7993a71c0bbdc90 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 16:55:28 +0000 Subject: [PATCH 06/49] remove parts not in spec --- src/pint_array/__init__.py | 1 + tests/test_array.py | 46 +++++++++----------------------------- 2 files changed, 11 insertions(+), 36 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 169a111..4172954 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -339,6 +339,7 @@ def manip_fun(x, *args, **kwargs): args[0] = repeats.magnitude if func_str in arbitrary_num_arrays and not one_array: + args = [asarray(arg, units = x[0].units).magnitude for arg in args] magnitude = xp_func(*magnitude, *args, **kwargs) else: magnitude = xp_func(magnitude, *args, **kwargs) diff --git a/tests/test_array.py b/tests/test_array.py index 3cf19b8..3be6d5f 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -150,10 +150,10 @@ def test_concat_stack(self, subtests): func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) ) # One or more of the args is a bare array full of zeros or NaNs - helpers.assert_quantity_equal( - func([self.q_zero_or_nan.m, self.q]), - self.Q_(func([self.q_zero_or_nan.m, self.q.m]), self.ureg.m), - ) + # helpers.assert_quantity_equal( + # func([self.q_zero_or_nan.m, self.q]), + # self.Q_(func([self.q_zero_or_nan.m, self.q.m]), self.ureg.m), + # ) # One or more of the args is a bare array with at least one non-zero, # non-NaN element nz = self.q_zero_or_nan @@ -174,14 +174,13 @@ def test_broadcast_arrays(self): x = self.Q_(np.array([[1, 2, 3]]), "m") y = self.Q_(np.array([[4], [5]]), "nm") result = pnp.broadcast_arrays(x, y) - expected = self.Q_( - [ - [[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]], - [[4e-09, 4e-09, 4e-09], [5e-09, 5e-09, 5e-09]], - ], - "m", + expected = (self.Q_(np.array([[1, 2, 3], + [1, 2, 3]]),"m"), + self.Q_(np.array([[4, 4, 4], + [5, 5, 5]]),"nm") ) - helpers.assert_quantity_equal(result, expected) + helpers.assert_quantity_equal(result[0], expected[0]) + helpers.assert_quantity_equal(result[1], expected[1]) result = pnp.broadcast_arrays(x, y, subok=True) helpers.assert_quantity_equal(result, expected) @@ -203,19 +202,6 @@ def test_prod_numpy_func(self): helpers.assert_quantity_equal( pnp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 ) - helpers.assert_quantity_equal( - pnp.prod(self.q, where=where), 12 * self.ureg.m**3 - ) - - with pytest.raises(DimensionalityError): - pnp.prod(self.q, axis=axis, where=where) - helpers.assert_quantity_equal( - pnp.prod(self.q, axis=axis, where=[[True, False], [False, True]]), - [1, 4] * self.ureg.m, - ) - helpers.assert_quantity_equal( - pnp.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 - ) def test_sum_numpy_func(self): helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) @@ -330,12 +316,6 @@ def test_max_numpy_func(self): def test_max_with_axis_arg(self): helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) - def test_max_with_initial_arg(self): - helpers.assert_quantity_equal( - pnp.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), - [[3, 3], [3, 4]] * self.ureg.m, - ) - def test_argmax_numpy_func(self): self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) @@ -350,12 +330,6 @@ def test_min_numpy_func(self): def test_min_with_axis_arg(self): helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) - def test_min_with_initial_arg(self): - helpers.assert_quantity_equal( - pnp.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), - [[1, 2], [3, 3]] * self.ureg.m, - ) - def test_argmin_numpy_func(self): self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) From ea2e1c85cd32ad62dde12858a053fb1a149d4733 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:55:47 +0000 Subject: [PATCH 07/49] style: pre-commit fixes --- src/pint_array/__init__.py | 2 +- tests/test_array.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 4172954..773f04b 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -339,7 +339,7 @@ def manip_fun(x, *args, **kwargs): args[0] = repeats.magnitude if func_str in arbitrary_num_arrays and not one_array: - args = [asarray(arg, units = x[0].units).magnitude for arg in args] + args = [asarray(arg, units=x[0].units).magnitude for arg in args] magnitude = xp_func(*magnitude, *args, **kwargs) else: magnitude = xp_func(magnitude, *args, **kwargs) diff --git a/tests/test_array.py b/tests/test_array.py index 3be6d5f..6bc4f5d 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -174,10 +174,9 @@ def test_broadcast_arrays(self): x = self.Q_(np.array([[1, 2, 3]]), "m") y = self.Q_(np.array([[4], [5]]), "nm") result = pnp.broadcast_arrays(x, y) - expected = (self.Q_(np.array([[1, 2, 3], - [1, 2, 3]]),"m"), - self.Q_(np.array([[4, 4, 4], - [5, 5, 5]]),"nm") + expected = ( + self.Q_(np.array([[1, 2, 3], [1, 2, 3]]), "m"), + self.Q_(np.array([[4, 4, 4], [5, 5, 5]]), "nm"), ) helpers.assert_quantity_equal(result[0], expected[0]) helpers.assert_quantity_equal(result[1], expected[1]) From 29fef7e230c96c41349daec41f0183fa94927ad6 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 17:38:01 +0000 Subject: [PATCH 08/49] tets --- src/pint_array/__init__.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 773f04b..bb892ef 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -474,10 +474,23 @@ def searchsorted(x1, x2, /, *, side="left", sorter=None): # ignore units of condition, convert x2 to units of x1 def where(condition, x1, x2, /): + if not getattr(condition, "_is_multiplicative", True): + raise ValueError( + "Invalid units of the condition: Boolean value of Quantity with offset unit is ambiguous." + ) + condition = asarray(condition) - x1 = asarray(x1) - x2 = asarray(x2) + if hasattr(x1, "units") and hasattr(x2, "units"): + x1 = asarray(x1) + x2 = asarray(x2) + elif hasattr(x1, "units"): + x1 = asarray(x1) + x2 = asarray(x2, units=x1.units) + elif hasattr(x2, "units"): + x1 = asarray(x1, units=x2.units) + x2 = asarray(x2) units = x1.units + print(x2.m_as(units)) magnitude = xp.where(condition.magnitude, x1.magnitude, x2.m_as(units)) return ArrayUnitQuantity(magnitude, units) From 32b8bd2c91bb8f4d74d83df25aa1950858fd3447 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 23:29:03 +0000 Subject: [PATCH 09/49] implementations --- src/pint_array/__init__.py | 39 +++++++++++++++++++++++++++++++++++--- tests/test_array.py | 10 ---------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index bb892ef..06d5bf9 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -17,6 +17,7 @@ from array_api_compat import size from pint import Quantity from pint.facets.plain import MagnitudeT, PlainQuantity +from pint import DimensionalityError, OffsetUnitCalculusError __version__ = "0.0.1.dev0" __all__ = ["__version__", "pint_namespace"] @@ -352,7 +353,7 @@ def manip_fun(x, *args, **kwargs): return manip_fun - creation_manip_functions = ["tril", "triu", "meshgrid"] + creation_manip_functions = ["tril", "triu"] manip_names = [ "broadcast_arrays", "broadcast_to", @@ -372,6 +373,19 @@ def manip_fun(x, *args, **kwargs): for name in manip_names + creation_manip_functions: setattr(mod, name, get_manip_fun(name)) + def _meshgrid(*xi, **kwargs): + # Simply need to map input units to onto list of outputs + input_units = (x.units for x in xi) + res = xp.meshgrid(*(x.magnitude for x in xi), **kwargs) + return [out * unit for out, unit in zip(res, input_units)] + mod.meshgrid = _meshgrid + + def _broadcast_arrays(*arrays): + arrays = [asarray(array) for array in arrays] + res = xp.broadcast_arrays(*[array.magnitude for array in arrays]) + return [ArrayUnitQuantity(magnitude, array.units) for magnitude, array in zip(res, arrays)] + mod.broadcast_arrays = _broadcast_arrays + ## Data Type Functions and Data Types ## dtype_fun_names = ["can_cast", "finfo", "iinfo", "result_type"] for func_str in dtype_fun_names: @@ -671,9 +685,7 @@ def fun(x, /, *args, func_str=func_str, **kwargs): "logical_xor", "maximum", "minimum", - "multiply", "not_equal", - "pow", "remainder", "subtract", ] @@ -708,6 +720,27 @@ def multiply(x1, x2, /, *args, **kwargs): mod.multiply = multiply + + def pow(x1, x2, /, *args, **kwargs): + x1 = asarray(x1) + x2 = asarray(x2) + + if not x2.units.dimensionless: + raise DimensionalityError(x2.units, "dimensionless") + if x2.ndim > 0 and not xp.all(x2.magnitude == x2[0].magnitude): + raise DimensionalityError( + x2.units, + "dimensionless", + extra_msg="The exponent must be a scalar or an array of all the same value.", + ) + + units = x1.units ** x2.magnitude + + magnitude = xp.pow(x1.magnitude, x2.magnitude, *args, **kwargs) + return ArrayUnitQuantity(magnitude, units) + + mod.pow = pow + ## Indexing Functions def take(x, indices, /, **kwargs): magnitude = xp.take(x.magnitude, indices.magnitude, **kwargs) diff --git a/tests/test_array.py b/tests/test_array.py index 6bc4f5d..1992adb 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -178,10 +178,6 @@ def test_broadcast_arrays(self): self.Q_(np.array([[1, 2, 3], [1, 2, 3]]), "m"), self.Q_(np.array([[4, 4, 4], [5, 5, 5]]), "nm"), ) - helpers.assert_quantity_equal(result[0], expected[0]) - helpers.assert_quantity_equal(result[1], expected[1]) - - result = pnp.broadcast_arrays(x, y, subok=True) helpers.assert_quantity_equal(result, expected) def test_roll(self): @@ -235,7 +231,6 @@ def test_power(self): with pytest.raises(DimensionalityError): op_(2.0, q_cp) arr_cp = copy.copy(arr) - arr_cp = copy.copy(arr) q_cp = copy.copy(q) with pytest.raises(DimensionalityError): op_(q_cp, arr_cp) @@ -368,11 +363,6 @@ def test_std_numpy_func(self): pnp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 ) - def test_cumprod(self): - with pytest.raises(DimensionalityError): - self.q.cumprod() - helpers.assert_quantity_equal((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) - def test_conj(self): helpers.assert_quantity_equal((self.q * (1 + 1j)).conj(), self.q * (1 - 1j)) helpers.assert_quantity_equal( From 3ce421440c59c45b2a9837bd2758c5206a8ef645 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 23:29:13 +0000 Subject: [PATCH 10/49] style: pre-commit fixes --- src/pint_array/__init__.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 06d5bf9..c6f4fcb 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -15,9 +15,8 @@ from typing import Generic from array_api_compat import size -from pint import Quantity +from pint import DimensionalityError, OffsetUnitCalculusError, Quantity from pint.facets.plain import MagnitudeT, PlainQuantity -from pint import DimensionalityError, OffsetUnitCalculusError __version__ = "0.0.1.dev0" __all__ = ["__version__", "pint_namespace"] @@ -377,13 +376,18 @@ def _meshgrid(*xi, **kwargs): # Simply need to map input units to onto list of outputs input_units = (x.units for x in xi) res = xp.meshgrid(*(x.magnitude for x in xi), **kwargs) - return [out * unit for out, unit in zip(res, input_units)] + return [out * unit for out, unit in zip(res, input_units, strict=False)] + mod.meshgrid = _meshgrid - + def _broadcast_arrays(*arrays): arrays = [asarray(array) for array in arrays] res = xp.broadcast_arrays(*[array.magnitude for array in arrays]) - return [ArrayUnitQuantity(magnitude, array.units) for magnitude, array in zip(res, arrays)] + return [ + ArrayUnitQuantity(magnitude, array.units) + for magnitude, array in zip(res, arrays, strict=False) + ] + mod.broadcast_arrays = _broadcast_arrays ## Data Type Functions and Data Types ## @@ -720,7 +724,6 @@ def multiply(x1, x2, /, *args, **kwargs): mod.multiply = multiply - def pow(x1, x2, /, *args, **kwargs): x1 = asarray(x1) x2 = asarray(x2) @@ -729,12 +732,12 @@ def pow(x1, x2, /, *args, **kwargs): raise DimensionalityError(x2.units, "dimensionless") if x2.ndim > 0 and not xp.all(x2.magnitude == x2[0].magnitude): raise DimensionalityError( - x2.units, - "dimensionless", - extra_msg="The exponent must be a scalar or an array of all the same value.", - ) + x2.units, + "dimensionless", + extra_msg="The exponent must be a scalar or an array of all the same value.", + ) - units = x1.units ** x2.magnitude + units = x1.units**x2.magnitude magnitude = xp.pow(x1.magnitude, x2.magnitude, *args, **kwargs) return ArrayUnitQuantity(magnitude, units) From ba95c6c9d9f9ea9e0d6c5f4e1ec3defa4744724e Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 23:32:06 +0000 Subject: [PATCH 11/49] dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ae4be4b..91de1ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ ipython = "ipython" [tool.pixi.feature.tests.dependencies] pytest = "*" +pytest-subtests = "*" [tool.pixi.feature.tests.tasks] tests = "pytest" From e39e23e7a02ed6c49ec812217748bb8a1166168a Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Mon, 6 Jan 2025 11:57:21 +0000 Subject: [PATCH 12/49] update lockfile --- pixi.lock | 119 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 44 deletions(-) diff --git a/pixi.lock b/pixi.lock index ab1b665..96579da 100644 --- a/pixi.lock +++ b/pixi.lock @@ -42,7 +42,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py310ha75aee5_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py310h5851e9f_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -50,6 +50,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.10.16-he725a3c_1_cpython.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.10-5_cp310.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8228510_1.conda @@ -89,7 +90,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py310ha1ddda0_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -97,6 +98,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.10.16-h870587a_1_cpython.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.10-5_cp310.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda @@ -136,7 +138,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda - conda: https://prefix.dev/conda-forge/win-64/ndindex-1.9.2-py310ha8f682b_1.conda - conda: https://prefix.dev/conda-forge/win-64/numpy-2.2.1-py310hb9d903e_0.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -144,6 +146,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.10.16-h37870fc_1_cpython.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.10-5_cp310.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda @@ -201,7 +204,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py313h536fd9c_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -209,6 +212,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8228510_1.conda @@ -250,7 +254,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -258,6 +262,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda @@ -299,7 +304,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda - conda: https://prefix.dev/conda-forge/win-64/ndindex-1.9.2-py313ha7868ed_1.conda - conda: https://prefix.dev/conda-forge/win-64/numpy-2.2.1-py313hd65a2fa_0.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -307,6 +312,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda @@ -357,7 +363,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda @@ -391,7 +397,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.6-hdb05f8b_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda @@ -425,7 +431,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda - conda: https://prefix.dev/conda-forge/win-64/numpy-2.2.1-py313hd65a2fa_0.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda @@ -467,7 +473,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/ipython-8.31.0-pyh707e725_0.conda - conda: https://prefix.dev/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -494,7 +500,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py313h536fd9c_1.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda @@ -511,6 +517,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/pyyaml-6.0.2-py313h536fd9c_1.conda @@ -525,7 +532,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://prefix.dev/conda-forge/linux-64/ukkonen-1.0.1-py313h33d0bda_5.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - pypi: . @@ -548,7 +555,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/ipython-8.31.0-pyh707e725_0.conda - conda: https://prefix.dev/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -571,7 +578,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda @@ -588,6 +595,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pyyaml-6.0.2-py313h20a7fcf_1.conda @@ -602,7 +610,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ukkonen-1.0.1-py313hf9c7212_5.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 - pypi: . @@ -625,7 +633,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda - conda: https://prefix.dev/conda-forge/noarch/ipython-8.31.0-pyh7428d3b_0.conda @@ -648,7 +656,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/ndindex-1.9.2-py313ha7868ed_1.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/numpy-2.2.1-py313hd65a2fa_0.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda @@ -663,6 +671,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/pyyaml-6.0.2-py313ha7868ed_1.conda @@ -680,7 +689,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/ukkonen-1.0.1-py313h1ec8472_5.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-ha32ba9b_23.conda - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.42.34433-he29a5d6_23.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/vs2015_runtime-14.42.34433-hdffcdeb_23.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2 @@ -713,6 +722,7 @@ packages: depends: - python >=3.9 license: MIT + license_family: MIT purls: - pkg:pypi/array-api-compat?source=hash-mapping size: 38442 @@ -724,6 +734,7 @@ packages: - numpy - python >=3.9 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/array-api-strict?source=hash-mapping size: 53675 @@ -990,13 +1001,14 @@ packages: - setuptools - sortedcontainers >=2.1.0,<3.0.0 license: MPL-2.0 + license_family: MOZILLA purls: - pkg:pypi/hypothesis?source=hash-mapping size: 341990 timestamp: 1735341651189 -- conda: https://prefix.dev/conda-forge/noarch/identify-2.6.4-pyhd8ed1ab_0.conda - sha256: 8acc3bfc7781ea1ddc8c013faff5106a0539e5671e31bee0d81011a1e2df20d8 - md5: 5ec16e7ad9bab911ff0696940953f505 +- conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda + sha256: e8ea11b8e39a98a9c34efb5c21c3fca718e31e1f41fd9ae5f6918b8eb402da59 + md5: c1b0f663ff141265d1be1242259063f0 depends: - python >=3.9 - ukkonen @@ -1004,8 +1016,8 @@ packages: license_family: MIT purls: - pkg:pypi/identify?source=hash-mapping - size: 78570 - timestamp: 1735518781514 + size: 78415 + timestamp: 1736026672643 - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda sha256: 0ec8f4d02053cd03b0f3e63168316530949484f80e16f5e2fb199a1d117a89ca md5: 6837f3eff7dcea42ecd714ce1ac2b108 @@ -1796,6 +1808,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 7912254 @@ -1815,6 +1828,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 8478406 @@ -1834,6 +1848,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 5929029 @@ -1853,6 +1868,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 6513050 @@ -1872,6 +1888,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 6420026 @@ -1891,13 +1908,14 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 7147174 timestamp: 1734905243335 -- conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda - sha256: 814b9dff1847b132c676ee6cc1a8cb2d427320779b93e1b6d76552275c128705 - md5: 23cc74f77eb99315c0360ec3533147a9 +- conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda + sha256: f62f6bca4a33ca5109b6d571b052a394d836956d21b25b7ffd03376abf7a481f + md5: 4ce6875f75469b2757a65e10a5d05e31 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates @@ -1905,22 +1923,22 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 2947466 - timestamp: 1731377666602 -- conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda - sha256: bd1d58ced46e75efa3b842c61642fd12272c69e9fe4d7261078bc082153a1d53 - md5: df307bbc703324722df0293c9ca2e418 + size: 2937158 + timestamp: 1736086387286 +- conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda + sha256: 97772762abc70b3a537683ca9fc3ff3d6099eb64e4aba3b9c99e6fce48422d21 + md5: 22f971393637480bda8c679f374d8861 depends: - __osx >=11.0 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 2935176 - timestamp: 1731377561525 -- conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda - sha256: e03045a0837e01ff5c75e9273a572553e7522290799807f918c917a9826a6484 - md5: d0d805d9b5524a14efb51b3bff965e83 + size: 2936415 + timestamp: 1736086108693 +- conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda + sha256: 519a06eaab7c878fbebb8cab98ea4a4465eafb1e9ed8c6ce67226068a80a92f0 + md5: fb45308ba8bfe1abf1f4a27bad24a743 depends: - ca-certificates - ucrt >=10.0.20348.0 @@ -1929,8 +1947,8 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 8491156 - timestamp: 1731379715927 + size: 8462960 + timestamp: 1736088436984 - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda sha256: da157b19bcd398b9804c5c52fc000fcb8ab0525bdb9c70f95beaa0bb42f85af1 md5: 3bfed7e6228ebf2f7b9eaa47f1b4e2aa @@ -1995,7 +2013,7 @@ packages: - pypi: . name: pint-array version: 0.0.1.dev0 - sha256: f755911bc8a921cbb2e3867f4638c8fc32d2f9090b17c405eefd0019e5c5335e + sha256: 24493191fd5707853309652b03e9a153a4d9ace5f7f1b9c9ccb73d824f68d514 requires_python: '>=3.10' editable: true - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -2137,6 +2155,19 @@ packages: - pkg:pypi/pytest-metadata?source=hash-mapping size: 14532 timestamp: 1734146281190 +- conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda + sha256: 610b1f994b7409cc131b5ecc838e515bf2c89eddcddb0b18a85dc6823eddc3d9 + md5: c94aae4e1b32cddb4e6a3dedeee8092b + depends: + - attrs >=19.2.0 + - pytest >=7.4 + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pytest-subtests?source=hash-mapping + size: 17983 + timestamp: 1733872677650 - conda: https://prefix.dev/conda-forge/linux-64/python-3.10.16-he725a3c_1_cpython.conda build_number: 1 sha256: 3f90a2d5062a73cd2dd8a0027718aee1db93f7975b9cfe529e2c9aeec2db262e @@ -2627,9 +2658,9 @@ packages: purls: [] size: 754247 timestamp: 1731710681163 -- conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.0-pyhd8ed1ab_0.conda - sha256: 82776f74e90a296b79415361faa6b10f360755c1fb8e6d59ca68509e6fe7e115 - md5: 1d601bc1d28b5ce6d112b90f4b9b8ede +- conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda + sha256: c8bde4547ddbd21ea89e483a7c65d8a5e442c0db494b0b977e389b75b9d03d62 + md5: 680b1c287b10cefc8bda0530b217229f depends: - distlib >=0.3.7,<1 - filelock >=3.12.2,<4 @@ -2639,8 +2670,8 @@ packages: license_family: MIT purls: - pkg:pypi/virtualenv?source=hash-mapping - size: 3350255 - timestamp: 1732609542072 + size: 3350367 + timestamp: 1735929107438 - conda: https://prefix.dev/conda-forge/win-64/vs2015_runtime-14.42.34433-hdffcdeb_23.conda sha256: 568ce8151eaae256f1cef752fc78651ad7a86ff05153cc7a4740b52ae6536118 md5: 5c176975ca2b8366abad3c97b3cd1e83 From 573558d1071a1e87403ceceb5e9ace69466df286 Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Mon, 6 Jan 2025 12:42:11 +0000 Subject: [PATCH 13/49] fix xp-tests --- pixi.lock | 2 +- pyproject.toml | 2 +- src/pint_array/__init__.py | 43 ++++++++++++++++++++++++++------------ tests/test_array.py | 35 ++++++++++--------------------- xp-tests-xfails.txt | 2 ++ 5 files changed, 45 insertions(+), 39 deletions(-) diff --git a/pixi.lock b/pixi.lock index 96579da..523ac68 100644 --- a/pixi.lock +++ b/pixi.lock @@ -2013,7 +2013,7 @@ packages: - pypi: . name: pint-array version: 0.0.1.dev0 - sha256: 24493191fd5707853309652b03e9a153a4d9ace5f7f1b9c9ccb73d824f68d514 + sha256: 4a89db66462f8ea5ad75b3a9e4b4b623805532b36abed68b124c6e67a3022bfe requires_python: '>=3.10' editable: true - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda diff --git a/pyproject.toml b/pyproject.toml index 91de1ce..e5e5a11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ pytest = "*" pytest-subtests = "*" [tool.pixi.feature.tests.tasks] -tests = "pytest" +tests = "pytest tests" [tool.pixi.feature.xp-tests.dependencies] pytest = "*" diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index c6f4fcb..8e03f93 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -15,7 +15,7 @@ from typing import Generic from array_api_compat import size -from pint import DimensionalityError, OffsetUnitCalculusError, Quantity +from pint import DimensionalityError, Quantity from pint.facets.plain import MagnitudeT, PlainQuantity __version__ = "0.0.1.dev0" @@ -493,9 +493,11 @@ def searchsorted(x1, x2, /, *, side="left", sorter=None): # ignore units of condition, convert x2 to units of x1 def where(condition, x1, x2, /): if not getattr(condition, "_is_multiplicative", True): - raise ValueError( - "Invalid units of the condition: Boolean value of Quantity with offset unit is ambiguous." + msg = ( + "Invalid units of the condition: " + "Boolean value of Quantity with offset unit is ambiguous." ) + raise ValueError(msg) condition = asarray(condition) if hasattr(x1, "units") and hasattr(x2, "units"): @@ -508,7 +510,6 @@ def where(condition, x1, x2, /): x1 = asarray(x1, units=x2.units) x2 = asarray(x2) units = x1.units - print(x2.m_as(units)) magnitude = xp.where(condition.magnitude, x1.magnitude, x2.m_as(units)) return ArrayUnitQuantity(magnitude, units) @@ -730,16 +731,32 @@ def pow(x1, x2, /, *args, **kwargs): if not x2.units.dimensionless: raise DimensionalityError(x2.units, "dimensionless") - if x2.ndim > 0 and not xp.all(x2.magnitude == x2[0].magnitude): - raise DimensionalityError( - x2.units, - "dimensionless", - extra_msg="The exponent must be a scalar or an array of all the same value.", - ) - - units = x1.units**x2.magnitude + x2_magnitude = x2.magnitude + x2_magnitude_dtype = x2_magnitude.dtype + if xp.isdtype(x2_magnitude_dtype, "complex floating"): + as_scalar = complex + elif xp.isdtype(x2_magnitude_dtype, "real floating"): + as_scalar = float + elif xp.isdtype(x2_magnitude_dtype, "integral"): + as_scalar = int + else: + as_scalar = bool + if x2.ndim == 0: + units = x1.units ** as_scalar(x2_magnitude) + else: + x2_first_elem_magnitude = x2[(0,) * x2.ndim] + if not xp.all(x2_magnitude == x2_first_elem_magnitude): + extra_msg = ( + "The exponent must be a scalar or an array of all the same value." + ) + raise DimensionalityError( + x2.units, + "dimensionless", + extra_msg=extra_msg, + ) + units = x1.units ** as_scalar(x2_first_elem_magnitude) - magnitude = xp.pow(x1.magnitude, x2.magnitude, *args, **kwargs) + magnitude = xp.pow(x1.magnitude, x2_magnitude, *args, **kwargs) return ArrayUnitQuantity(magnitude, units) mod.pow = pow diff --git a/tests/test_array.py b/tests/test_array.py index 1992adb..3bd2f58 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -6,20 +6,16 @@ import numpy as np import pytest -from pint import DimensionalityError, OffsetUnitCalculusError - -# from pint.compat import np +from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry from pint.testsuite import helpers import pint_array pnp = pint_array.pint_namespace(np) -from pint import UnitRegistry - ureg = UnitRegistry() -class TestNumpyMethods: +class TestNumPyMethods: @classmethod def setup_class(cls): from pint import _DEFAULT_REGISTRY @@ -59,10 +55,10 @@ def assertNDArrayEqual(self, actual, desired): assert not isinstance(desired, self.Q_) -class TestNumpyArrayCreation(TestNumpyMethods): +class TestNumPyArrayCreation(TestNumPyMethods): # https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html - @pytest.mark.xfail(reason="Scalar arguement issue ") + @pytest.mark.xfail(reason="Scalar argument issue ") def test_ones_like(self): self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) @@ -74,7 +70,7 @@ def test_empty_like(self): assert ret.shape == (2, 2) assert isinstance(ret.magnitude, np.ndarray) - @pytest.mark.xfail(reason="Scalar arguement issue ") + @pytest.mark.xfail(reason="Scalar argument issue ") def test_full_like(self): helpers.assert_quantity_equal( pnp.full_like(self.q, self.Q_(0, self.ureg.degC)), @@ -83,13 +79,7 @@ def test_full_like(self): self.assertNDArrayEqual(pnp.full_like(self.q, 2), np.array([[2, 2], [2, 2]])) -class TestNumpyArrayManipulation(TestNumpyMethods): - # TODO - # https://www.numpy.org/devdocs/reference/routines.array-manipulation.html - # copyto - # broadcast - # asarray asanyarray asmatrix asfarray asfortranarray ascontiguousarray asarray_chkfinite asscalar require - +class TestNumPyArrayManipulation(TestNumPyMethods): # Changing array shape def test_flatten(self): @@ -186,12 +176,11 @@ def test_roll(self): ) -class TestNumpyMathematicalFunctions(TestNumpyMethods): +class TestNumPyMathematicalFunctions(TestNumPyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html def test_prod_numpy_func(self): axis = 0 - where = [[True, False], [True, True]] helpers.assert_quantity_equal(pnp.prod(self.q), 24 * self.ureg.m**4) helpers.assert_quantity_equal( @@ -270,7 +259,7 @@ def test_exponentiation_array_exp_2(self): op.ipow(arr_cp, q_cp) -class TestNumpyUnclassified(TestNumpyMethods): +class TestNumPyUnclassified(TestNumPyMethods): def test_repeat(self): helpers.assert_quantity_equal( pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m @@ -295,13 +284,13 @@ def test_nonzero_numpy_func(self): def test_any_numpy_func(self): q = [0, 1] * self.ureg.m assert pnp.any(q) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="offset unit is ambiguous"): pnp.any(self.q_temperature) def test_all_numpy_func(self): q = [0, 1] * self.ureg.m assert not pnp.all(q) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="offset unit is ambiguous"): pnp.all(self.q_temperature) def test_max_numpy_func(self): @@ -430,11 +419,10 @@ def test_pickle(self, subtests): def test_equal(self): x = self.q.magnitude u = self.Q_(pnp.ones(x.shape)) - true = pnp.ones_like(x, dtype=np.bool_) false = pnp.zeros_like(x, dtype=np.bool_) helpers.assert_quantity_equal(u, u) - helpers.assert_quantity_equal(u == u, u.magnitude == u.magnitude) + helpers.assert_quantity_equal(u, u.magnitude) helpers.assert_quantity_equal(u == 1, u.magnitude == 1) v = self.Q_(pnp.zeros(x.shape), "m") @@ -444,7 +432,6 @@ def test_equal(self): self.Q_(pnp.zeros_like(x), "m") == self.Q_(pnp.zeros_like(x), "s"), false, ) - self.assertNDArrayEqual(v == v, true) self.assertNDArrayEqual(v == w, false) self.assertNDArrayEqual(v == w.to("mm"), false) self.assertNDArrayEqual(u == v, false) diff --git a/xp-tests-xfails.txt b/xp-tests-xfails.txt index cc659de..6369ae4 100644 --- a/xp-tests-xfails.txt +++ b/xp-tests-xfails.txt @@ -38,3 +38,5 @@ array_api_tests/test_has_names.py::test_has_names[linalg-vecdot] array_api_tests/test_has_names.py::test_has_names[linalg-vector_norm] # flaky on macos array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt +# `pow` with array x2 is only defined when all elements of x2 are equal +array_api_tests/test_operators_and_elementwise_functions.py::test_pow[pow(x1, x2)] From 5a8f6d44635ffca02085ba26190f947432daa986 Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Tue, 7 Jan 2025 23:35:51 +0000 Subject: [PATCH 14/49] np -> xp --- tests/test_array.py | 199 +++++++++++++++++++++++--------------------- 1 file changed, 102 insertions(+), 97 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index 3bd2f58..b410d92 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -4,6 +4,7 @@ import operator as op import pickle +import array_api_strict as xp import numpy as np import pytest from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry @@ -11,7 +12,7 @@ import pint_array -pnp = pint_array.pint_namespace(np) +pxp = pint_array.pint_namespace(xp) ureg = UnitRegistry() @@ -34,15 +35,15 @@ def q(self): @property def q_scalar(self): - return np.array(5) * self.ureg.m + return pxp.asarray(5) * self.ureg.m @property def q_nan(self): - return [[1, 2], [3, pnp.nan]] * self.ureg.m + return [[1, 2], [3, pxp.nan]] * self.ureg.m @property def q_zero_or_nan(self): - return [[0, 0], [0, pnp.nan]] * self.ureg.m + return [[0, 0], [0, pxp.nan]] * self.ureg.m @property def q_temperature(self): @@ -60,23 +61,23 @@ class TestNumPyArrayCreation(TestNumPyMethods): @pytest.mark.xfail(reason="Scalar argument issue ") def test_ones_like(self): - self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) + self.assertNDArrayEqual(pxp.ones_like(self.q), pxp.asarray([[1, 1], [1, 1]])) def test_zeros_like(self): - self.assertNDArrayEqual(pnp.zeros_like(self.q), np.array([[0, 0], [0, 0]])) + self.assertNDArrayEqual(pxp.zeros_like(self.q), pxp.asarray([[0, 0], [0, 0]])) def test_empty_like(self): - ret = pnp.empty_like(self.q) + ret = pxp.empty_like(self.q) assert ret.shape == (2, 2) - assert isinstance(ret.magnitude, np.ndarray) + assert ret.magnitude.__array_namespace__() is xp @pytest.mark.xfail(reason="Scalar argument issue ") def test_full_like(self): helpers.assert_quantity_equal( - pnp.full_like(self.q, self.Q_(0, self.ureg.degC)), + pxp.full_like(self.q, self.Q_(0, self.ureg.degC)), self.Q_([[0, 0], [0, 0]], self.ureg.degC), ) - self.assertNDArrayEqual(pnp.full_like(self.q, 2), np.array([[2, 2], [2, 2]])) + self.assertNDArrayEqual(pxp.full_like(self.q, 2), pxp.asarray([[2, 2], [2, 2]])) class TestNumPyArrayManipulation(TestNumPyMethods): @@ -98,43 +99,43 @@ def test_reshape(self): def test_moveaxis(self): helpers.assert_quantity_equal( - pnp.moveaxis(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m + pxp.moveaxis(self.q, 1, 0), pxp.asarray([[1, 2], [3, 4]]).T * self.ureg.m ) def test_transpose(self): helpers.assert_quantity_equal( - pnp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m + pxp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m ) def test_flip_numpy_func(self): helpers.assert_quantity_equal( - pnp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m + pxp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m ) # Changing number of dimensions def test_broadcast_to(self): helpers.assert_quantity_equal( - pnp.broadcast_to(self.q[:, 1], (2, 2)), - np.array([[2, 4], [2, 4]]) * self.ureg.m, + pxp.broadcast_to(self.q[:, 1], (2, 2)), + pxp.asarray([[2, 4], [2, 4]]) * self.ureg.m, ) def test_expand_dims(self): helpers.assert_quantity_equal( - pnp.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m + pxp.expand_dims(self.q, 0), pxp.asarray([[[1, 2], [3, 4]]]) * self.ureg.m ) def test_squeeze(self): - helpers.assert_quantity_equal(pnp.squeeze(self.q), self.q) + helpers.assert_quantity_equal(pxp.squeeze(self.q), self.q) helpers.assert_quantity_equal( - pnp.squeeze(self.q.reshape([1, 4])), [1, 2, 3, 4] * self.ureg.m + pxp.squeeze(self.q.reshape([1, 4])), [1, 2, 3, 4] * self.ureg.m ) # Changing number of dimensions # Joining arrays def test_concat_stack(self, subtests): - for func in (pnp.concat, pnp.stack): + for func in (pxp.concat, pxp.stack): with subtests.test(func=func): helpers.assert_quantity_equal( func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) @@ -152,8 +153,10 @@ def test_concat_stack(self, subtests): func([nz.m, self.q]) def test_astype(self): - actual = self.q.astype(pnp.float32) - expected = self.Q_(np.array([[1.0, 2.0], [3.0, 4.0]], dtype=pnp.float32), "m") + actual = self.q.astype(pxp.float32) + expected = self.Q_( + pxp.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=pxp.float32), "m" + ) helpers.assert_quantity_equal(actual, expected) assert actual.m.dtype == expected.m.dtype @@ -161,18 +164,18 @@ def test_item(self): helpers.assert_quantity_equal(self.Q_([[0]], "m").item(), 0 * self.ureg.m) def test_broadcast_arrays(self): - x = self.Q_(np.array([[1, 2, 3]]), "m") - y = self.Q_(np.array([[4], [5]]), "nm") - result = pnp.broadcast_arrays(x, y) + x = self.Q_(pxp.asarray([[1, 2, 3]]), "m") + y = self.Q_(pxp.asarray([[4], [5]]), "nm") + result = pxp.broadcast_arrays(x, y) expected = ( - self.Q_(np.array([[1, 2, 3], [1, 2, 3]]), "m"), - self.Q_(np.array([[4, 4, 4], [5, 5, 5]]), "nm"), + self.Q_(pxp.asarray([[1, 2, 3], [1, 2, 3]]), "m"), + self.Q_(pxp.asarray([[4, 4, 4], [5, 5, 5]]), "nm"), ) helpers.assert_quantity_equal(result, expected) def test_roll(self): helpers.assert_quantity_equal( - pnp.roll(self.q, 1), [[4, 1], [2, 3]] * self.ureg.m + pxp.roll(self.q, 1), [[4, 1], [2, 3]] * self.ureg.m ) @@ -182,19 +185,19 @@ class TestNumPyMathematicalFunctions(TestNumPyMethods): def test_prod_numpy_func(self): axis = 0 - helpers.assert_quantity_equal(pnp.prod(self.q), 24 * self.ureg.m**4) + helpers.assert_quantity_equal(pxp.prod(self.q), 24 * self.ureg.m**4) helpers.assert_quantity_equal( - pnp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 + pxp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 ) def test_sum_numpy_func(self): - helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) + helpers.assert_quantity_equal(pxp.sum(self.q, axis=0), [4, 6] * self.ureg.m) with pytest.raises(OffsetUnitCalculusError): - pnp.sum(self.q_temperature) + pxp.sum(self.q_temperature) # Arithmetic operations def test_addition_with_scalar(self): - a = np.array([0, 1, 2]) + a = pxp.asarray([0, 1, 2]) b = 10.0 * self.ureg("gram/kilogram") helpers.assert_quantity_almost_equal( a + b, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) @@ -204,7 +207,7 @@ def test_addition_with_scalar(self): ) def test_addition_with_incompatible_scalar(self): - a = np.array([0, 1, 2]) + a = pxp.asarray([0, 1, 2]) b = 1.0 * self.ureg.m with pytest.raises(DimensionalityError): op.add(a, b) @@ -212,10 +215,10 @@ def test_addition_with_incompatible_scalar(self): op.add(b, a) def test_power(self): - arr = np.array(range(3), dtype=float) + arr = pxp.asarray(range(3), dtype=float) q = self.Q_(arr, "meter") - for op_ in [pnp.pow]: + for op_ in [pxp.pow]: q_cp = copy.copy(q) with pytest.raises(DimensionalityError): op_(2.0, q_cp) @@ -229,20 +232,20 @@ def test_power(self): op_(q_cp, q2_cp) helpers.assert_quantity_equal( - pnp.pow(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") + pxp.pow(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") ) helpers.assert_quantity_equal( self.q ** self.Q_(2), self.Q_([[1, 4], [9, 16]], "m**2") ) - self.assertNDArrayEqual(arr ** self.Q_(2), np.array([0, 1, 4])) + self.assertNDArrayEqual(arr ** self.Q_(2), pxp.asarray([0, 1, 4])) def test_sqrt(self): q = self.Q_(100, "m**2") - helpers.assert_quantity_equal(pnp.sqrt(q), self.Q_(10, "m")) + helpers.assert_quantity_equal(pxp.sqrt(q), self.Q_(10, "m")) @pytest.mark.xfail def test_exponentiation_array_exp_2(self): - arr = np.array(range(3), dtype=float) + arr = pxp.asarray(range(3), dtype=float) # q = self.Q_(copy.copy(arr), None) q = self.Q_(copy.copy(arr), "meter") arr_cp = copy.copy(arr) @@ -262,94 +265,96 @@ def test_exponentiation_array_exp_2(self): class TestNumPyUnclassified(TestNumPyMethods): def test_repeat(self): helpers.assert_quantity_equal( - pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m + pxp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m ) def test_sort_numpy_func(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m - helpers.assert_quantity_equal(pnp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) + helpers.assert_quantity_equal(pxp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) def test_argsort_numpy_func(self): - self.assertNDArrayEqual(pnp.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) + self.assertNDArrayEqual( + pxp.argsort(self.q, axis=0), pxp.asarray([[0, 0], [1, 1]]) + ) def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() - self.assertNDArrayEqual(pnp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) + self.assertNDArrayEqual(pxp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m - self.assertNDArrayEqual(pnp.nonzero(q)[0], [0, 2, 3, 5]) + self.assertNDArrayEqual(pxp.nonzero(q)[0], [0, 2, 3, 5]) def test_any_numpy_func(self): q = [0, 1] * self.ureg.m - assert pnp.any(q) + assert pxp.any(q) with pytest.raises(ValueError, match="offset unit is ambiguous"): - pnp.any(self.q_temperature) + pxp.any(self.q_temperature) def test_all_numpy_func(self): q = [0, 1] * self.ureg.m - assert not pnp.all(q) + assert not pxp.all(q) with pytest.raises(ValueError, match="offset unit is ambiguous"): - pnp.all(self.q_temperature) + pxp.all(self.q_temperature) def test_max_numpy_func(self): - assert pnp.max(self.q) == 4 * self.ureg.m + assert pxp.max(self.q) == 4 * self.ureg.m def test_max_with_axis_arg(self): - helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) + helpers.assert_quantity_equal(pxp.max(self.q, axis=1), [2, 4] * self.ureg.m) def test_argmax_numpy_func(self): - self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) + self.assertNDArrayEqual(pxp.argmax(self.q, axis=0), pxp.asarray([1, 1])) def test_maximum(self): helpers.assert_quantity_equal( - pnp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") + pxp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) def test_min_numpy_func(self): - assert pnp.min(self.q) == 1 * self.ureg.m + assert pxp.min(self.q) == 1 * self.ureg.m def test_min_with_axis_arg(self): - helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) + helpers.assert_quantity_equal(pxp.min(self.q, axis=1), [1, 3] * self.ureg.m) def test_argmin_numpy_func(self): - self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) + self.assertNDArrayEqual(pxp.argmin(self.q, axis=0), pxp.asarray([0, 0])) def test_minimum(self): helpers.assert_quantity_equal( - pnp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") + pxp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) def test_clip_numpy_func(self): helpers.assert_quantity_equal( - pnp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m + pxp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) def test_round_numpy_func(self): helpers.assert_quantity_equal( - pnp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m + pxp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) def test_cumulative_sum(self): helpers.assert_quantity_equal( - pnp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m + pxp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) def test_mean_numpy_func(self): - assert pnp.mean(self.q) == 2.5 * self.ureg.m - assert pnp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) + assert pxp.mean(self.q) == 2.5 * self.ureg.m + assert pxp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) def test_var_numpy_func(self): - assert pnp.var(self.q) == 1.25 * self.ureg.m**2 - assert pnp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 + assert pxp.var(self.q) == 1.25 * self.ureg.m**2 + assert pxp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 def test_std_numpy_func(self): helpers.assert_quantity_almost_equal( - pnp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 + pxp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 ) helpers.assert_quantity_almost_equal( - pnp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 + pxp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 ) def test_conj(self): @@ -372,7 +377,7 @@ def test_setitem(self): with pytest.raises(DimensionalityError): self.q[0] = 1 with pytest.raises(DimensionalityError): - self.q[0] = np.ndarray([1, 2]) + self.q[0] = pxp.asarray([1, 2]) with pytest.raises(DimensionalityError): self.q[0] = 1 * self.ureg.J @@ -391,9 +396,9 @@ def test_setitem(self): # check and see that dimensionless numbers work correctly q = [0, 1, 2, 3] * self.ureg.dimensionless q[0] = 1 - helpers.assert_quantity_equal(q, pnp.asarray([1, 1, 2, 3])) + helpers.assert_quantity_equal(q, pxp.asarray([1, 1, 2, 3])) q[0] = self.ureg.m / self.ureg.mm - helpers.assert_quantity_equal(q, pnp.asarray([1000, 1, 2, 3])) + helpers.assert_quantity_equal(q, pxp.asarray([1000, 1, 2, 3])) q = [0.0, 1.0, 2.0, 3.0] * self.ureg.m / self.ureg.mm q[0] = 1.0 @@ -402,7 +407,7 @@ def test_setitem(self): def test_reversible_op(self): """ """ x = self.q.magnitude - u = self.Q_(pnp.ones(x.shape)) + u = self.Q_(pxp.ones(x.shape)) helpers.assert_quantity_equal(x / self.q, u * x / self.q) helpers.assert_quantity_equal(x * self.q, u * x * self.q) helpers.assert_quantity_equal(x + u, u + x) @@ -418,18 +423,18 @@ def test_pickle(self, subtests): def test_equal(self): x = self.q.magnitude - u = self.Q_(pnp.ones(x.shape)) - false = pnp.zeros_like(x, dtype=np.bool_) + u = self.Q_(pxp.ones(x.shape)) + false = pxp.zeros_like(x, dtype=pxp.bool) helpers.assert_quantity_equal(u, u) helpers.assert_quantity_equal(u, u.magnitude) helpers.assert_quantity_equal(u == 1, u.magnitude == 1) - v = self.Q_(pnp.zeros(x.shape), "m") - w = self.Q_(pnp.ones(x.shape), "m") + v = self.Q_(pxp.zeros(x.shape), "m") + w = self.Q_(pxp.ones(x.shape), "m") self.assertNDArrayEqual(v == 1, false) self.assertNDArrayEqual( - self.Q_(pnp.zeros_like(x), "m") == self.Q_(pnp.zeros_like(x), "s"), + self.Q_(pxp.zeros_like(x), "m") == self.Q_(pxp.zeros_like(x), "s"), false, ) self.assertNDArrayEqual(v == w, false) @@ -437,80 +442,80 @@ def test_equal(self): self.assertNDArrayEqual(u == v, false) def test_dtype(self): - u = self.Q_(pnp.arange(12, dtype="uint32")) + u = self.Q_(pxp.arange(12, dtype="uint32")) assert u.dtype == "uint32" def test_shape_numpy_func(self): - assert pnp.asarray(self.q).shape == (2, 2) + assert pxp.asarray(self.q).shape == (2, 2) def test_ndim_numpy_func(self): - assert pnp.asarray(self.q).ndim == 2 + assert pxp.asarray(self.q).ndim == 2 def test_meshgrid_numpy_func(self): x = [1, 2] * self.ureg.m y = [0, 50, 100] * self.ureg.mm - xx, yy = pnp.meshgrid(x, y) + xx, yy = pxp.meshgrid(x, y) helpers.assert_quantity_equal(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) def test_comparisons(self): self.assertNDArrayEqual( - self.q > 2 * self.ureg.m, np.array([[False, False], [True, True]]) + self.q > 2 * self.ureg.m, pxp.asarray([[False, False], [True, True]]) ) self.assertNDArrayEqual( - self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]]) + self.q < 2 * self.ureg.m, pxp.asarray([[True, False], [False, False]]) ) def test_where(self): helpers.assert_quantity_equal( - pnp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), + pxp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), [[20, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 2 * self.ureg.m, self.q, 0), + pxp.where(self.q >= 2 * self.ureg.m, self.q, 0), [[0, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 2 * self.ureg.m, self.q, pnp.nan), - [[pnp.nan, 2], [3, 4]] * self.ureg.m, + pxp.where(self.q >= 2 * self.ureg.m, self.q, pxp.nan), + [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 3 * self.ureg.m, 0, self.q), + pxp.where(self.q >= 3 * self.ureg.m, 0, self.q), [[1, 2], [0, 0]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 3 * self.ureg.m, pnp.nan, self.q), - [[1, 2], [pnp.nan, pnp.nan]] * self.ureg.m, + pxp.where(self.q >= 3 * self.ureg.m, pxp.nan, self.q), + [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 2 * self.ureg.m, self.q, np.array(pnp.nan)), - [[pnp.nan, 2], [3, 4]] * self.ureg.m, + pxp.where(self.q >= 2 * self.ureg.m, self.q, pxp.asarray(pxp.nan)), + [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 3 * self.ureg.m, np.array(pnp.nan), self.q), - [[1, 2], [pnp.nan, pnp.nan]] * self.ureg.m, + pxp.where(self.q >= 3 * self.ureg.m, pxp.asarray(pxp.nan), self.q), + [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) with pytest.raises(DimensionalityError): - pnp.where( + pxp.where( self.q < 2 * self.ureg.m, self.q, 0 * self.ureg.J, ) helpers.assert_quantity_equal( - pnp.where([-1, 0, 1] * self.ureg.m, [1, 2, 1] * self.ureg.s, pnp.nan), - [1, pnp.nan, 1] * self.ureg.s, + pxp.where([-1, 0, 1] * self.ureg.m, [1, 2, 1] * self.ureg.s, pxp.nan), + [1, pxp.nan, 1] * self.ureg.s, ) with pytest.raises( ValueError, match=".*Boolean value of Quantity with offset unit is ambiguous", ): - pnp.where( - self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pnp.nan + pxp.where( + self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pxp.nan ) def test_tile(self): helpers.assert_quantity_equal( - pnp.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m + pxp.tile(self.q, 2), pxp.asarray([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m ) From 9a49b08f15f644433e2b0a21c4674a233c871b5c Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Tue, 7 Jan 2025 23:37:27 +0000 Subject: [PATCH 15/49] remove pickle test --- tests/test_array.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index b410d92..db864c0 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -2,7 +2,6 @@ import copy import operator as op -import pickle import array_api_strict as xp import numpy as np @@ -413,14 +412,6 @@ def test_reversible_op(self): helpers.assert_quantity_equal(x + u, u + x) helpers.assert_quantity_equal(x - u, -(u - x)) - def test_pickle(self, subtests): - for protocol in range(pickle.HIGHEST_PROTOCOL + 1): - with subtests.test(protocol): - q1 = [10, 20] * self.ureg.m - q2 = pickle.loads(pickle.dumps(q1, protocol)) - self.assertNDArrayEqual(q1.magnitude, q2.magnitude) - assert q1.units == q2.units - def test_equal(self): x = self.q.magnitude u = self.Q_(pxp.ones(x.shape)) From 135b2b9983eb258490133c9fbe0f5685642cf3f7 Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Tue, 7 Jan 2025 23:46:23 +0000 Subject: [PATCH 16/49] some tweaks --- src/pint_array/__init__.py | 2 ++ tests/test_array.py | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 8e03f93..eb440db 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -946,4 +946,6 @@ def clip(x, /, min=None, max=None): with contextlib.suppress(AttributeError, TypeError): mod_attr.__name__ = xp_attr.__name__ + mod.ArrayUnitQuantity = ArrayUnitQuantity + return mod diff --git a/tests/test_array.py b/tests/test_array.py index db864c0..2a37a2f 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -6,13 +6,12 @@ import array_api_strict as xp import numpy as np import pytest -from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry +from pint import DimensionalityError, OffsetUnitCalculusError from pint.testsuite import helpers import pint_array pxp = pint_array.pint_namespace(xp) -ureg = UnitRegistry() class TestNumPyMethods: @@ -21,7 +20,7 @@ def setup_class(cls): from pint import _DEFAULT_REGISTRY cls.ureg = _DEFAULT_REGISTRY - cls.Q_ = cls.ureg.Quantity + cls.Q_ = pxp.ArrayUnitQuantity @classmethod def teardown_class(cls): @@ -30,23 +29,23 @@ def teardown_class(cls): @property def q(self): - return [[1, 2], [3, 4]] * self.ureg.m + return pxp.asarray([[1, 2], [3, 4]], units=self.ureg.m) @property def q_scalar(self): - return pxp.asarray(5) * self.ureg.m + return pxp.asarray(5, units=self.ureg.m) @property def q_nan(self): - return [[1, 2], [3, pxp.nan]] * self.ureg.m + return pxp.asarray([[1, 2], [3, pxp.nan]], units=self.ureg.m) @property def q_zero_or_nan(self): - return [[0, 0], [0, pxp.nan]] * self.ureg.m + return pxp.asarray([[0, 0], [0, pxp.nan]], units=self.ureg.m) @property def q_temperature(self): - return self.Q_([[1, 2], [3, 4]], self.ureg.degC) + return pxp.asarray([[1, 2], [3, 4]], self.ureg.degC) def assertNDArrayEqual(self, actual, desired): # Assert that the given arrays are equal, and are not Quantities @@ -103,12 +102,12 @@ def test_moveaxis(self): def test_transpose(self): helpers.assert_quantity_equal( - pxp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m + pxp.matrix_transpose(self.q), pxp.asarray([[1, 3], [2, 4]]) * self.ureg.m ) def test_flip_numpy_func(self): helpers.assert_quantity_equal( - pxp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m + pxp.flip(self.q, axis=0), pxp.asarray([[3, 4], [1, 2]]) * self.ureg.m ) # Changing number of dimensions @@ -127,7 +126,7 @@ def test_expand_dims(self): def test_squeeze(self): helpers.assert_quantity_equal(pxp.squeeze(self.q), self.q) helpers.assert_quantity_equal( - pxp.squeeze(self.q.reshape([1, 4])), [1, 2, 3, 4] * self.ureg.m + pxp.squeeze(self.q.reshape([1, 4])), pxp.asarray([1, 2, 3, 4]) * self.ureg.m ) # Changing number of dimensions From bda9eba697f92e4bfe41a3f3813e589da7048eba Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 11 Jan 2025 18:11:28 +0000 Subject: [PATCH 17/49] pass some tests --- src/pint_array/__init__.py | 2 + tests/test_array.py | 114 ++++++++++++++++++++----------------- 2 files changed, 63 insertions(+), 53 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index eb440db..609891e 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -115,6 +115,8 @@ def __mul__(self, other): else: magnitude = self._call_super_method("__mul__", other) units = self.units + if magnitude is NotImplemented: + return NotImplemented return ArrayUnitQuantity(magnitude, units) def __gt__(self, other): diff --git a/tests/test_array.py b/tests/test_array.py index 2a37a2f..dace652 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -45,7 +45,7 @@ def q_zero_or_nan(self): @property def q_temperature(self): - return pxp.asarray([[1, 2], [3, 4]], self.ureg.degC) + return pxp.asarray([[1, 2], [3, 4]], units = self.ureg.degC) def assertNDArrayEqual(self, actual, desired): # Assert that the given arrays are equal, and are not Quantities @@ -61,6 +61,7 @@ class TestNumPyArrayCreation(TestNumPyMethods): def test_ones_like(self): self.assertNDArrayEqual(pxp.ones_like(self.q), pxp.asarray([[1, 1], [1, 1]])) + @pytest.mark.xfail(reason="should this be using NDArrayEqual?") def test_zeros_like(self): self.assertNDArrayEqual(pxp.zeros_like(self.q), pxp.asarray([[0, 0], [0, 0]])) @@ -81,16 +82,9 @@ def test_full_like(self): class TestNumPyArrayManipulation(TestNumPyMethods): # Changing array shape - def test_flatten(self): - helpers.assert_quantity_equal(self.q.flatten(), [1, 2, 3, 4] * self.ureg.m) - - def test_flat(self): - for q, v in zip(self.q.flat, [1, 2, 3, 4], strict=False): - assert q == v * self.ureg.m - def test_reshape(self): helpers.assert_quantity_equal( - self.q.reshape([1, 4]), [[1, 2, 3, 4]] * self.ureg.m + pxp.reshape(self.q, [1, 4]), pxp.asarray([[1, 2, 3, 4]] ) * self.ureg.m ) # Transpose-like operations @@ -120,13 +114,13 @@ def test_broadcast_to(self): def test_expand_dims(self): helpers.assert_quantity_equal( - pxp.expand_dims(self.q, 0), pxp.asarray([[[1, 2], [3, 4]]]) * self.ureg.m + pxp.expand_dims(self.q, axis=0), pxp.asarray([[[1, 2], [3, 4]]]) * self.ureg.m ) def test_squeeze(self): - helpers.assert_quantity_equal(pxp.squeeze(self.q), self.q) helpers.assert_quantity_equal( - pxp.squeeze(self.q.reshape([1, 4])), pxp.asarray([1, 2, 3, 4]) * self.ureg.m + pxp.squeeze(pxp.asarray([[[0], [1], [2]]]) *self.ureg.m, axis=0), + pxp.asarray([0,1,2]) * self.ureg.m ) # Changing number of dimensions @@ -136,7 +130,7 @@ def test_concat_stack(self, subtests): for func in (pxp.concat, pxp.stack): with subtests.test(func=func): helpers.assert_quantity_equal( - func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) + func([self.q] * 2), pxp.asarray(func([self.q.m] * 2), units = "m") ) # One or more of the args is a bare array full of zeros or NaNs # helpers.assert_quantity_equal( @@ -151,10 +145,9 @@ def test_concat_stack(self, subtests): func([nz.m, self.q]) def test_astype(self): - actual = self.q.astype(pxp.float32) - expected = self.Q_( - pxp.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=pxp.float32), "m" - ) + dtype=pxp.float32 + actual = pxp.astype(self.q, dtype) + expected = pxp.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=dtype, units = "m") helpers.assert_quantity_equal(actual, expected) assert actual.m.dtype == expected.m.dtype @@ -162,12 +155,12 @@ def test_item(self): helpers.assert_quantity_equal(self.Q_([[0]], "m").item(), 0 * self.ureg.m) def test_broadcast_arrays(self): - x = self.Q_(pxp.asarray([[1, 2, 3]]), "m") - y = self.Q_(pxp.asarray([[4], [5]]), "nm") + x = pxp.asarray([[1, 2, 3]], units= "m") + y = pxp.asarray([[4], [5]], units= "nm") result = pxp.broadcast_arrays(x, y) expected = ( - self.Q_(pxp.asarray([[1, 2, 3], [1, 2, 3]]), "m"), - self.Q_(pxp.asarray([[4, 4, 4], [5, 5, 5]]), "nm"), + pxp.asarray([[1, 2, 3], [1, 2, 3]], units= "m"), + pxp.asarray([[4, 4, 4], [5, 5, 5]], units= "nm") ) helpers.assert_quantity_equal(result, expected) @@ -195,13 +188,13 @@ def test_sum_numpy_func(self): # Arithmetic operations def test_addition_with_scalar(self): - a = pxp.asarray([0, 1, 2]) + a = pxp.asarray([0, 1, 2], dtype = pxp.float32) b = 10.0 * self.ureg("gram/kilogram") helpers.assert_quantity_almost_equal( - a + b, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) + a + b, pxp.asarray([0.01, 1.01, 2.01], units = "") ) helpers.assert_quantity_almost_equal( - b + a, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) + b + a, pxp.asarray([0.01, 1.01, 2.01], units = "") ) def test_addition_with_incompatible_scalar(self): @@ -213,7 +206,7 @@ def test_addition_with_incompatible_scalar(self): op.add(b, a) def test_power(self): - arr = pxp.asarray(range(3), dtype=float) + arr = pxp.asarray(range(3), dtype=pxp.float32) q = self.Q_(arr, "meter") for op_ in [pxp.pow]: @@ -238,8 +231,8 @@ def test_power(self): self.assertNDArrayEqual(arr ** self.Q_(2), pxp.asarray([0, 1, 4])) def test_sqrt(self): - q = self.Q_(100, "m**2") - helpers.assert_quantity_equal(pxp.sqrt(q), self.Q_(10, "m")) + q = self.Q_(100.0, "m**2") + helpers.assert_quantity_equal(pxp.sqrt(q), self.Q_(10.0, "m")) @pytest.mark.xfail def test_exponentiation_array_exp_2(self): @@ -326,12 +319,12 @@ def test_minimum(self): def test_clip_numpy_func(self): helpers.assert_quantity_equal( - pxp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m + pxp.clip(pxp.asarray(self.q, dtype=pxp.float32), 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) def test_round_numpy_func(self): helpers.assert_quantity_equal( - pxp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m + pxp.round(102.75 * self.ureg.m, ), 103 * self.ureg.m ) def test_cumulative_sum(self): @@ -340,19 +333,21 @@ def test_cumulative_sum(self): ) def test_mean_numpy_func(self): - assert pxp.mean(self.q) == 2.5 * self.ureg.m - assert pxp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) + assert pxp.mean(pxp.asarray(self.q, dtype=pxp.float32)) == 2.5 * self.ureg.m + assert pxp.mean(pxp.asarray(self.q_temperature, dtype=pxp.float32)) == self.Q_(2.5, self.ureg.degC) def test_var_numpy_func(self): - assert pxp.var(self.q) == 1.25 * self.ureg.m**2 - assert pxp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 + dtype=pxp.float32 + assert pxp.var(pxp.asarray(self.q, dtype=dtype)) == 1.25 * self.ureg.m**2 + assert pxp.var(pxp.asarray(self.q_temperature, dtype=dtype)) == 1.25 * self.ureg.delta_degC**2 def test_std_numpy_func(self): + dtype=pxp.float32 helpers.assert_quantity_almost_equal( - pxp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 + pxp.std(pxp.asarray(self.q, dtype=dtype)), 1.11803 * self.ureg.m, rtol=1e-5 ) helpers.assert_quantity_almost_equal( - pxp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 + pxp.std(pxp.asarray(self.q_temperature, dtype=dtype)), 1.11803 * self.ureg.delta_degC, rtol=1e-5 ) def test_conj(self): @@ -413,15 +408,15 @@ def test_reversible_op(self): def test_equal(self): x = self.q.magnitude - u = self.Q_(pxp.ones(x.shape)) + u = pxp.ones(x.shape) false = pxp.zeros_like(x, dtype=pxp.bool) helpers.assert_quantity_equal(u, u) helpers.assert_quantity_equal(u, u.magnitude) helpers.assert_quantity_equal(u == 1, u.magnitude == 1) - v = self.Q_(pxp.zeros(x.shape), "m") - w = self.Q_(pxp.ones(x.shape), "m") + v = pxp.asarray((pxp.zeros(x.shape)), units = "m") + w = pxp.asarray((pxp.ones(x.shape)), units = "m") self.assertNDArrayEqual(v == 1, false) self.assertNDArrayEqual( self.Q_(pxp.zeros_like(x), "m") == self.Q_(pxp.zeros_like(x), "s"), @@ -432,9 +427,10 @@ def test_equal(self): self.assertNDArrayEqual(u == v, false) def test_dtype(self): - u = self.Q_(pxp.arange(12, dtype="uint32")) + dtype=pxp.uint32 + u = pxp.asarray([1, 2, 3], dtype=dtype) * self.ureg.m - assert u.dtype == "uint32" + assert u.dtype == dtype def test_shape_numpy_func(self): assert pxp.asarray(self.q).shape == (2, 2) @@ -443,8 +439,8 @@ def test_ndim_numpy_func(self): assert pxp.asarray(self.q).ndim == 2 def test_meshgrid_numpy_func(self): - x = [1, 2] * self.ureg.m - y = [0, 50, 100] * self.ureg.mm + x = pxp.asarray([1, 2]) * self.ureg.m + y = pxp.asarray([0, 50, 100]) * self.ureg.mm xx, yy = pxp.meshgrid(x, y) helpers.assert_quantity_equal(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) @@ -466,36 +462,37 @@ def test_where(self): pxp.where(self.q >= 2 * self.ureg.m, self.q, 0), [[0, 2], [3, 4]] * self.ureg.m, ) + q_float = self.q.astype(float) helpers.assert_quantity_equal( - pxp.where(self.q >= 2 * self.ureg.m, self.q, pxp.nan), + pxp.where(q_float >= 2 * self.ureg.m, q_float, pxp.nan), [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(self.q >= 3 * self.ureg.m, 0, self.q), + pxp.where(q_float >= 3 * self.ureg.m, 0., q_float), [[1, 2], [0, 0]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(self.q >= 3 * self.ureg.m, pxp.nan, self.q), + pxp.where(q_float >= 3 * self.ureg.m, pxp.nan, q_float), [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(self.q >= 2 * self.ureg.m, self.q, pxp.asarray(pxp.nan)), + pxp.where(q_float >= 2 * self.ureg.m, q_float, pxp.asarray(pxp.nan)* self.ureg.m), [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(self.q >= 3 * self.ureg.m, pxp.asarray(pxp.nan), self.q), + pxp.where(q_float >= 3 * self.ureg.m, pxp.asarray(pxp.nan)* self.ureg.m, q_float), [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) with pytest.raises(DimensionalityError): pxp.where( - self.q < 2 * self.ureg.m, - self.q, + q_float < 2 * self.ureg.m, + q_float, 0 * self.ureg.J, ) helpers.assert_quantity_equal( - pxp.where([-1, 0, 1] * self.ureg.m, [1, 2, 1] * self.ureg.s, pxp.nan), - [1, pxp.nan, 1] * self.ureg.s, + pxp.where(pxp.asarray([-1., 0., 1.]) * self.ureg.m, pxp.asarray([1., 2., 1.]) * self.ureg.s, pxp.nan), + pxp.asarray([1., pxp.nan, 1.]) * self.ureg.s, ) with pytest.raises( ValueError, @@ -507,5 +504,16 @@ def test_where(self): def test_tile(self): helpers.assert_quantity_equal( - pxp.tile(self.q, 2), pxp.asarray([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m - ) + pxp.tile(pxp.asarray([1,2,3,4]) *self.ureg.m, (4,1)), + pxp.asarray([[1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4]]) * self.ureg.m + ) + helpers.assert_quantity_equal( + pxp.tile(pxp.asarray([[1, 2], [3, 4]]) *self.ureg.m, (2,1)), + pxp.asarray([[1, 2], + [3, 4], + [1, 2], + [3, 4]]) * self.ureg.m + ) \ No newline at end of file From 93626ba3a0994af69977a3f5936f7d72f8109707 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 15:41:46 +0000 Subject: [PATCH 18/49] tests --- src/pint_array/__init__.py | 1 + tests/test_array.py | 79 +++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index dcbdc9b..e37ca13 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -720,6 +720,7 @@ def linalg_fun(x1, x2, /, **kwargs): setattr(mod, name, get_linalg_fun(name)) def matrix_transpose(x): + x = asarray(x) return x.mT mod.matrix_transpose = matrix_transpose diff --git a/tests/test_array.py b/tests/test_array.py index 4f0532b..5d23986 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -18,7 +18,6 @@ from pint import UnitRegistry; ureg = UnitRegistry() -# @helpers.requires_numpy class TestNumpyMethods: @classmethod def setup_class(cls): @@ -62,21 +61,22 @@ def assertNDArrayEqual(self, actual, desired): class TestNumpyArrayCreation(TestNumpyMethods): # https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html - # @helpers.requires_array_function_protocol() + @pytest.mark.xfail(reason="Scalar arguement issue ") def test_ones_like(self): self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) - # @helpers.requires_array_function_protocol() + def test_zeros_like(self): self.assertNDArrayEqual(pnp.zeros_like(self.q), np.array([[0, 0], [0, 0]])) - # @helpers.requires_array_function_protocol() + def test_empty_like(self): ret = pnp.empty_like(self.q) assert ret.shape == (2, 2) - assert isinstance(ret, np.ndarray) + assert isinstance(ret.magnitude, np.ndarray) - # @helpers.requires_array_function_protocol() + + @pytest.mark.xfail(reason="Scalar arguement issue ") def test_full_like(self): helpers.assert_quantity_equal( pnp.full_like(self.q, self.Q_(0, self.ureg.degC)), @@ -108,19 +108,19 @@ def test_reshape(self): # Transpose-like operations - # @helpers.requires_array_function_protocol() + def test_moveaxis(self): helpers.assert_quantity_equal( pnp.moveaxis(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_transpose(self): helpers.assert_quantity_equal( pnp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_flip_numpy_func(self): helpers.assert_quantity_equal( pnp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m @@ -128,20 +128,20 @@ def test_flip_numpy_func(self): # Changing number of dimensions - # @helpers.requires_array_function_protocol() + def test_broadcast_to(self): helpers.assert_quantity_equal( pnp.broadcast_to(self.q[:, 1], (2, 2)), np.array([[2, 4], [2, 4]]) * self.ureg.m, ) - # @helpers.requires_array_function_protocol() + def test_expand_dims(self): helpers.assert_quantity_equal( pnp.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_squeeze(self): helpers.assert_quantity_equal(pnp.squeeze(self.q), self.q) helpers.assert_quantity_equal( @@ -150,7 +150,7 @@ def test_squeeze(self): # Changing number of dimensions # Joining arrays - # @helpers.requires_array_function_protocol() + def test_concat_stack(self, subtests): for func in (pnp.concat, pnp.stack): with subtests.test(func=func): @@ -203,7 +203,7 @@ def test_roll(self): class TestNumpyMathematicalFunctions(TestNumpyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html - # @helpers.requires_array_function_protocol() + def test_prod_numpy_func(self): axis = 0 where = [[True, False], [True, True]] @@ -224,7 +224,7 @@ def test_prod_numpy_func(self): pnp.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 ) - # @helpers.requires_array_function_protocol() + def test_sum_numpy_func(self): helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) with pytest.raises(OffsetUnitCalculusError): @@ -280,7 +280,6 @@ def test_sqrt(self): helpers.assert_quantity_equal(pnp.sqrt(q), self.Q_(10, "m")) @pytest.mark.xfail - # @helpers.requires_numpy def test_exponentiation_array_exp_2(self): arr = np.array(range(3), dtype=float) # q = self.Q_(copy.copy(arr), None) @@ -306,34 +305,34 @@ def test_repeat(self): pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_sort_numpy_func(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m helpers.assert_quantity_equal(pnp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) - # @helpers.requires_array_function_protocol() + def test_argsort_numpy_func(self): self.assertNDArrayEqual(pnp.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) - # @helpers.requires_array_function_protocol() + def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() self.assertNDArrayEqual(pnp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) - # @helpers.requires_array_function_protocol() + def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m self.assertNDArrayEqual(pnp.nonzero(q)[0], [0, 2, 3, 5]) - # @helpers.requires_array_function_protocol() + def test_any_numpy_func(self): q = [0, 1] * self.ureg.m assert pnp.any(q) with pytest.raises(ValueError): pnp.any(self.q_temperature) - # @helpers.requires_array_function_protocol() + def test_all_numpy_func(self): q = [0, 1] * self.ureg.m assert not pnp.all(q) @@ -343,18 +342,18 @@ def test_all_numpy_func(self): def test_max_numpy_func(self): assert pnp.max(self.q) == 4 * self.ureg.m - # @helpers.requires_array_function_protocol() + def test_max_with_axis_arg(self): helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) - # @helpers.requires_array_function_protocol() + def test_max_with_initial_arg(self): helpers.assert_quantity_equal( pnp.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m, ) - # @helpers.requires_array_function_protocol() + def test_argmax_numpy_func(self): self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) @@ -363,22 +362,22 @@ def test_maximum(self): pnp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) - # @helpers.requires_array_function_protocol() + def test_min_numpy_func(self): assert pnp.min(self.q) == 1 * self.ureg.m - # @helpers.requires_array_function_protocol() + def test_min_with_axis_arg(self): helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) - # @helpers.requires_array_function_protocol() + def test_min_with_initial_arg(self): helpers.assert_quantity_equal( pnp.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[1, 2], [3, 3]] * self.ureg.m, ) - # @helpers.requires_array_function_protocol() + def test_argmin_numpy_func(self): self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) @@ -387,35 +386,35 @@ def test_minimum(self): pnp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) - # @helpers.requires_array_function_protocol() + def test_clip_numpy_func(self): helpers.assert_quantity_equal( pnp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_round_numpy_func(self): helpers.assert_quantity_equal( pnp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_cumulative_sum(self): helpers.assert_quantity_equal( pnp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) - # @helpers.requires_array_function_protocol() + def test_mean_numpy_func(self): assert pnp.mean(self.q) == 2.5 * self.ureg.m assert pnp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) - # @helpers.requires_array_function_protocol() + def test_var_numpy_func(self): assert pnp.var(self.q) == 1.25 * self.ureg.m**2 assert pnp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 - # @helpers.requires_array_function_protocol() + def test_std_numpy_func(self): helpers.assert_quantity_almost_equal( pnp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 @@ -520,15 +519,15 @@ def test_dtype(self): assert u.dtype == "uint32" - # @helpers.requires_array_function_protocol() + def test_shape_numpy_func(self): assert pnp.asarray(self.q).shape == (2, 2) - # @helpers.requires_array_function_protocol() + def test_ndim_numpy_func(self): assert pnp.asarray(self.q).ndim == 2 - # @helpers.requires_array_function_protocol() + def test_meshgrid_numpy_func(self): x = [1, 2] * self.ureg.m y = [0, 50, 100] * self.ureg.mm @@ -544,7 +543,7 @@ def test_comparisons(self): self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]]) ) - # @helpers.requires_array_function_protocol() + def test_where(self): helpers.assert_quantity_equal( pnp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), @@ -593,7 +592,7 @@ def test_where(self): self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pnp.nan ) - # @helpers.requires_array_function_protocol() + def test_tile(self): helpers.assert_quantity_equal( pnp.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m From 1645d7b7c002357277e124749ad0cd4cf80c296f Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 11 Jan 2025 20:36:24 +0000 Subject: [PATCH 19/49] rebase --- src/pint_array/__init__.py | 5 ++--- tests/test_array.py | 28 +++++++++++++++++----------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index e37ca13..169a111 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -623,18 +623,17 @@ def fun(x, /, *args, func_str=func_str, **kwargs): return ArrayUnitQuantity(magnitude, x.units) setattr(mod, func_str, fun) - + for func_str in ["sqrt"]: def fun(x, /, *args, func_str=func_str, **kwargs): x = asarray(x) magnitude = xp.asarray(x.magnitude, copy=True) magnitude = getattr(xp, func_str)(magnitude, *args, **kwargs) - return ArrayUnitQuantity(magnitude, x.units ** 0.5) + return ArrayUnitQuantity(magnitude, x.units**0.5) setattr(mod, func_str, fun) - elementwise_two_arrays = [ "add", "atan2", diff --git a/tests/test_array.py b/tests/test_array.py index 5d23986..2f6c5a1 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -3,21 +3,26 @@ import copy import operator as op import pickle -import warnings +import numpy as np import pytest +from pint import DimensionalityError, OffsetUnitCalculusError -from pint import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning # from pint.compat import np from pint.testsuite import helpers -from pint.testsuite.test_umath import TestUFuncs -import pint_array; -import numpy as np; -pnp = pint_array.pint_namespace(np); -from pint import UnitRegistry; +import pint_array + +pnp = pint_array.pint_namespace(np) +from pint import UnitRegistry + ureg = UnitRegistry() +<<<<<<< HEAD +======= + +# @helpers.requires_numpy +>>>>>>> 1ad9ca9 (style: pre-commit fixes) class TestNumpyMethods: @classmethod def setup_class(cls): @@ -98,7 +103,7 @@ def test_flatten(self): helpers.assert_quantity_equal(self.q.flatten(), [1, 2, 3, 4] * self.ureg.m) def test_flat(self): - for q, v in zip(self.q.flat, [1, 2, 3, 4]): + for q, v in zip(self.q.flat, [1, 2, 3, 4], strict=False): assert q == v * self.ureg.m def test_reshape(self): @@ -212,7 +217,9 @@ def test_prod_numpy_func(self): helpers.assert_quantity_equal( pnp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 ) - helpers.assert_quantity_equal(pnp.prod(self.q, where=where), 12 * self.ureg.m**3) + helpers.assert_quantity_equal( + pnp.prod(self.q, where=where), 12 * self.ureg.m**3 + ) with pytest.raises(DimensionalityError): pnp.prod(self.q, axis=axis, where=where) @@ -299,7 +306,6 @@ def test_exponentiation_array_exp_2(self): class TestNumpyUnclassified(TestNumpyMethods): - def test_repeat(self): helpers.assert_quantity_equal( pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m @@ -513,7 +519,7 @@ def test_equal(self): self.assertNDArrayEqual(v == w, false) self.assertNDArrayEqual(v == w.to("mm"), false) self.assertNDArrayEqual(u == v, false) - + def test_dtype(self): u = self.Q_(pnp.arange(12, dtype="uint32")) From 219cc8e8dcac21e86a0721f81483c746ebcf43a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:43:00 +0000 Subject: [PATCH 20/49] style: pre-commit fixes --- tests/test_array.py | 41 +++++------------------------------------ 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index 2f6c5a1..dae6fea 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -18,11 +18,15 @@ ureg = UnitRegistry() +<<<<<<< HEAD <<<<<<< HEAD ======= # @helpers.requires_numpy >>>>>>> 1ad9ca9 (style: pre-commit fixes) +======= + +>>>>>>> 8498256 (style: pre-commit fixes) class TestNumpyMethods: @classmethod def setup_class(cls): @@ -70,17 +74,14 @@ class TestNumpyArrayCreation(TestNumpyMethods): def test_ones_like(self): self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) - def test_zeros_like(self): self.assertNDArrayEqual(pnp.zeros_like(self.q), np.array([[0, 0], [0, 0]])) - def test_empty_like(self): ret = pnp.empty_like(self.q) assert ret.shape == (2, 2) assert isinstance(ret.magnitude, np.ndarray) - @pytest.mark.xfail(reason="Scalar arguement issue ") def test_full_like(self): helpers.assert_quantity_equal( @@ -113,19 +114,16 @@ def test_reshape(self): # Transpose-like operations - def test_moveaxis(self): helpers.assert_quantity_equal( pnp.moveaxis(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) - def test_transpose(self): helpers.assert_quantity_equal( pnp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m ) - def test_flip_numpy_func(self): helpers.assert_quantity_equal( pnp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m @@ -133,20 +131,17 @@ def test_flip_numpy_func(self): # Changing number of dimensions - def test_broadcast_to(self): helpers.assert_quantity_equal( pnp.broadcast_to(self.q[:, 1], (2, 2)), np.array([[2, 4], [2, 4]]) * self.ureg.m, ) - def test_expand_dims(self): helpers.assert_quantity_equal( pnp.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m ) - def test_squeeze(self): helpers.assert_quantity_equal(pnp.squeeze(self.q), self.q) helpers.assert_quantity_equal( @@ -155,7 +150,7 @@ def test_squeeze(self): # Changing number of dimensions # Joining arrays - + def test_concat_stack(self, subtests): for func in (pnp.concat, pnp.stack): with subtests.test(func=func): @@ -208,7 +203,6 @@ def test_roll(self): class TestNumpyMathematicalFunctions(TestNumpyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html - def test_prod_numpy_func(self): axis = 0 where = [[True, False], [True, True]] @@ -231,7 +225,6 @@ def test_prod_numpy_func(self): pnp.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 ) - def test_sum_numpy_func(self): helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) with pytest.raises(OffsetUnitCalculusError): @@ -311,34 +304,28 @@ def test_repeat(self): pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m ) - def test_sort_numpy_func(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m helpers.assert_quantity_equal(pnp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) - def test_argsort_numpy_func(self): self.assertNDArrayEqual(pnp.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) - def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() self.assertNDArrayEqual(pnp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) - def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m self.assertNDArrayEqual(pnp.nonzero(q)[0], [0, 2, 3, 5]) - def test_any_numpy_func(self): q = [0, 1] * self.ureg.m assert pnp.any(q) with pytest.raises(ValueError): pnp.any(self.q_temperature) - def test_all_numpy_func(self): q = [0, 1] * self.ureg.m assert not pnp.all(q) @@ -348,18 +335,15 @@ def test_all_numpy_func(self): def test_max_numpy_func(self): assert pnp.max(self.q) == 4 * self.ureg.m - def test_max_with_axis_arg(self): helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) - def test_max_with_initial_arg(self): helpers.assert_quantity_equal( pnp.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m, ) - def test_argmax_numpy_func(self): self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) @@ -368,22 +352,18 @@ def test_maximum(self): pnp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) - def test_min_numpy_func(self): assert pnp.min(self.q) == 1 * self.ureg.m - def test_min_with_axis_arg(self): helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) - def test_min_with_initial_arg(self): helpers.assert_quantity_equal( pnp.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[1, 2], [3, 3]] * self.ureg.m, ) - def test_argmin_numpy_func(self): self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) @@ -392,35 +372,29 @@ def test_minimum(self): pnp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) - def test_clip_numpy_func(self): helpers.assert_quantity_equal( pnp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) - def test_round_numpy_func(self): helpers.assert_quantity_equal( pnp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) - def test_cumulative_sum(self): helpers.assert_quantity_equal( pnp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) - def test_mean_numpy_func(self): assert pnp.mean(self.q) == 2.5 * self.ureg.m assert pnp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) - def test_var_numpy_func(self): assert pnp.var(self.q) == 1.25 * self.ureg.m**2 assert pnp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 - def test_std_numpy_func(self): helpers.assert_quantity_almost_equal( pnp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 @@ -525,15 +499,12 @@ def test_dtype(self): assert u.dtype == "uint32" - def test_shape_numpy_func(self): assert pnp.asarray(self.q).shape == (2, 2) - def test_ndim_numpy_func(self): assert pnp.asarray(self.q).ndim == 2 - def test_meshgrid_numpy_func(self): x = [1, 2] * self.ureg.m y = [0, 50, 100] * self.ureg.mm @@ -549,7 +520,6 @@ def test_comparisons(self): self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]]) ) - def test_where(self): helpers.assert_quantity_equal( pnp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), @@ -598,7 +568,6 @@ def test_where(self): self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pnp.nan ) - def test_tile(self): helpers.assert_quantity_equal( pnp.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m From 80b05ab6b041056e931ec99cc65137a8bd11628d Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 16:55:28 +0000 Subject: [PATCH 21/49] remove parts not in spec --- src/pint_array/__init__.py | 1 + tests/test_array.py | 46 +++++++++----------------------------- 2 files changed, 11 insertions(+), 36 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 169a111..4172954 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -339,6 +339,7 @@ def manip_fun(x, *args, **kwargs): args[0] = repeats.magnitude if func_str in arbitrary_num_arrays and not one_array: + args = [asarray(arg, units = x[0].units).magnitude for arg in args] magnitude = xp_func(*magnitude, *args, **kwargs) else: magnitude = xp_func(magnitude, *args, **kwargs) diff --git a/tests/test_array.py b/tests/test_array.py index dae6fea..843c7f6 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -158,10 +158,10 @@ def test_concat_stack(self, subtests): func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) ) # One or more of the args is a bare array full of zeros or NaNs - helpers.assert_quantity_equal( - func([self.q_zero_or_nan.m, self.q]), - self.Q_(func([self.q_zero_or_nan.m, self.q.m]), self.ureg.m), - ) + # helpers.assert_quantity_equal( + # func([self.q_zero_or_nan.m, self.q]), + # self.Q_(func([self.q_zero_or_nan.m, self.q.m]), self.ureg.m), + # ) # One or more of the args is a bare array with at least one non-zero, # non-NaN element nz = self.q_zero_or_nan @@ -182,14 +182,13 @@ def test_broadcast_arrays(self): x = self.Q_(np.array([[1, 2, 3]]), "m") y = self.Q_(np.array([[4], [5]]), "nm") result = pnp.broadcast_arrays(x, y) - expected = self.Q_( - [ - [[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]], - [[4e-09, 4e-09, 4e-09], [5e-09, 5e-09, 5e-09]], - ], - "m", + expected = (self.Q_(np.array([[1, 2, 3], + [1, 2, 3]]),"m"), + self.Q_(np.array([[4, 4, 4], + [5, 5, 5]]),"nm") ) - helpers.assert_quantity_equal(result, expected) + helpers.assert_quantity_equal(result[0], expected[0]) + helpers.assert_quantity_equal(result[1], expected[1]) result = pnp.broadcast_arrays(x, y, subok=True) helpers.assert_quantity_equal(result, expected) @@ -211,19 +210,6 @@ def test_prod_numpy_func(self): helpers.assert_quantity_equal( pnp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 ) - helpers.assert_quantity_equal( - pnp.prod(self.q, where=where), 12 * self.ureg.m**3 - ) - - with pytest.raises(DimensionalityError): - pnp.prod(self.q, axis=axis, where=where) - helpers.assert_quantity_equal( - pnp.prod(self.q, axis=axis, where=[[True, False], [False, True]]), - [1, 4] * self.ureg.m, - ) - helpers.assert_quantity_equal( - pnp.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 - ) def test_sum_numpy_func(self): helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) @@ -338,12 +324,6 @@ def test_max_numpy_func(self): def test_max_with_axis_arg(self): helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) - def test_max_with_initial_arg(self): - helpers.assert_quantity_equal( - pnp.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), - [[3, 3], [3, 4]] * self.ureg.m, - ) - def test_argmax_numpy_func(self): self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) @@ -358,12 +338,6 @@ def test_min_numpy_func(self): def test_min_with_axis_arg(self): helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) - def test_min_with_initial_arg(self): - helpers.assert_quantity_equal( - pnp.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), - [[1, 2], [3, 3]] * self.ureg.m, - ) - def test_argmin_numpy_func(self): self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) From ae6b585ed7517c056dcc5264af3a07bb40436342 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 16:55:47 +0000 Subject: [PATCH 22/49] style: pre-commit fixes --- src/pint_array/__init__.py | 2 +- tests/test_array.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 4172954..773f04b 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -339,7 +339,7 @@ def manip_fun(x, *args, **kwargs): args[0] = repeats.magnitude if func_str in arbitrary_num_arrays and not one_array: - args = [asarray(arg, units = x[0].units).magnitude for arg in args] + args = [asarray(arg, units=x[0].units).magnitude for arg in args] magnitude = xp_func(*magnitude, *args, **kwargs) else: magnitude = xp_func(magnitude, *args, **kwargs) diff --git a/tests/test_array.py b/tests/test_array.py index 843c7f6..fe2d185 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -182,10 +182,9 @@ def test_broadcast_arrays(self): x = self.Q_(np.array([[1, 2, 3]]), "m") y = self.Q_(np.array([[4], [5]]), "nm") result = pnp.broadcast_arrays(x, y) - expected = (self.Q_(np.array([[1, 2, 3], - [1, 2, 3]]),"m"), - self.Q_(np.array([[4, 4, 4], - [5, 5, 5]]),"nm") + expected = ( + self.Q_(np.array([[1, 2, 3], [1, 2, 3]]), "m"), + self.Q_(np.array([[4, 4, 4], [5, 5, 5]]), "nm"), ) helpers.assert_quantity_equal(result[0], expected[0]) helpers.assert_quantity_equal(result[1], expected[1]) From 8b9f25255336fd79cdfa440b01ebe57b2a4f1faa Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 17:38:01 +0000 Subject: [PATCH 23/49] tets --- src/pint_array/__init__.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 773f04b..bb892ef 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -474,10 +474,23 @@ def searchsorted(x1, x2, /, *, side="left", sorter=None): # ignore units of condition, convert x2 to units of x1 def where(condition, x1, x2, /): + if not getattr(condition, "_is_multiplicative", True): + raise ValueError( + "Invalid units of the condition: Boolean value of Quantity with offset unit is ambiguous." + ) + condition = asarray(condition) - x1 = asarray(x1) - x2 = asarray(x2) + if hasattr(x1, "units") and hasattr(x2, "units"): + x1 = asarray(x1) + x2 = asarray(x2) + elif hasattr(x1, "units"): + x1 = asarray(x1) + x2 = asarray(x2, units=x1.units) + elif hasattr(x2, "units"): + x1 = asarray(x1, units=x2.units) + x2 = asarray(x2) units = x1.units + print(x2.m_as(units)) magnitude = xp.where(condition.magnitude, x1.magnitude, x2.m_as(units)) return ArrayUnitQuantity(magnitude, units) From 4b36af39bfb86d39f3e0f17d74d91300db20a08e Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 23:29:03 +0000 Subject: [PATCH 24/49] implementations --- src/pint_array/__init__.py | 39 +++++++++++++++++++++++++++++++++++--- tests/test_array.py | 10 ---------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index bb892ef..06d5bf9 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -17,6 +17,7 @@ from array_api_compat import size from pint import Quantity from pint.facets.plain import MagnitudeT, PlainQuantity +from pint import DimensionalityError, OffsetUnitCalculusError __version__ = "0.0.1.dev0" __all__ = ["__version__", "pint_namespace"] @@ -352,7 +353,7 @@ def manip_fun(x, *args, **kwargs): return manip_fun - creation_manip_functions = ["tril", "triu", "meshgrid"] + creation_manip_functions = ["tril", "triu"] manip_names = [ "broadcast_arrays", "broadcast_to", @@ -372,6 +373,19 @@ def manip_fun(x, *args, **kwargs): for name in manip_names + creation_manip_functions: setattr(mod, name, get_manip_fun(name)) + def _meshgrid(*xi, **kwargs): + # Simply need to map input units to onto list of outputs + input_units = (x.units for x in xi) + res = xp.meshgrid(*(x.magnitude for x in xi), **kwargs) + return [out * unit for out, unit in zip(res, input_units)] + mod.meshgrid = _meshgrid + + def _broadcast_arrays(*arrays): + arrays = [asarray(array) for array in arrays] + res = xp.broadcast_arrays(*[array.magnitude for array in arrays]) + return [ArrayUnitQuantity(magnitude, array.units) for magnitude, array in zip(res, arrays)] + mod.broadcast_arrays = _broadcast_arrays + ## Data Type Functions and Data Types ## dtype_fun_names = ["can_cast", "finfo", "iinfo", "result_type"] for func_str in dtype_fun_names: @@ -671,9 +685,7 @@ def fun(x, /, *args, func_str=func_str, **kwargs): "logical_xor", "maximum", "minimum", - "multiply", "not_equal", - "pow", "remainder", "subtract", ] @@ -708,6 +720,27 @@ def multiply(x1, x2, /, *args, **kwargs): mod.multiply = multiply + + def pow(x1, x2, /, *args, **kwargs): + x1 = asarray(x1) + x2 = asarray(x2) + + if not x2.units.dimensionless: + raise DimensionalityError(x2.units, "dimensionless") + if x2.ndim > 0 and not xp.all(x2.magnitude == x2[0].magnitude): + raise DimensionalityError( + x2.units, + "dimensionless", + extra_msg="The exponent must be a scalar or an array of all the same value.", + ) + + units = x1.units ** x2.magnitude + + magnitude = xp.pow(x1.magnitude, x2.magnitude, *args, **kwargs) + return ArrayUnitQuantity(magnitude, units) + + mod.pow = pow + ## Indexing Functions def take(x, indices, /, **kwargs): magnitude = xp.take(x.magnitude, indices.magnitude, **kwargs) diff --git a/tests/test_array.py b/tests/test_array.py index fe2d185..040730c 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -186,10 +186,6 @@ def test_broadcast_arrays(self): self.Q_(np.array([[1, 2, 3], [1, 2, 3]]), "m"), self.Q_(np.array([[4, 4, 4], [5, 5, 5]]), "nm"), ) - helpers.assert_quantity_equal(result[0], expected[0]) - helpers.assert_quantity_equal(result[1], expected[1]) - - result = pnp.broadcast_arrays(x, y, subok=True) helpers.assert_quantity_equal(result, expected) def test_roll(self): @@ -243,7 +239,6 @@ def test_power(self): with pytest.raises(DimensionalityError): op_(2.0, q_cp) arr_cp = copy.copy(arr) - arr_cp = copy.copy(arr) q_cp = copy.copy(q) with pytest.raises(DimensionalityError): op_(q_cp, arr_cp) @@ -376,11 +371,6 @@ def test_std_numpy_func(self): pnp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 ) - def test_cumprod(self): - with pytest.raises(DimensionalityError): - self.q.cumprod() - helpers.assert_quantity_equal((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) - def test_conj(self): helpers.assert_quantity_equal((self.q * (1 + 1j)).conj(), self.q * (1 - 1j)) helpers.assert_quantity_equal( From b71d11e7966dae29ea29fe06cb963c67ad4aa29f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 23:29:13 +0000 Subject: [PATCH 25/49] style: pre-commit fixes --- src/pint_array/__init__.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 06d5bf9..c6f4fcb 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -15,9 +15,8 @@ from typing import Generic from array_api_compat import size -from pint import Quantity +from pint import DimensionalityError, OffsetUnitCalculusError, Quantity from pint.facets.plain import MagnitudeT, PlainQuantity -from pint import DimensionalityError, OffsetUnitCalculusError __version__ = "0.0.1.dev0" __all__ = ["__version__", "pint_namespace"] @@ -377,13 +376,18 @@ def _meshgrid(*xi, **kwargs): # Simply need to map input units to onto list of outputs input_units = (x.units for x in xi) res = xp.meshgrid(*(x.magnitude for x in xi), **kwargs) - return [out * unit for out, unit in zip(res, input_units)] + return [out * unit for out, unit in zip(res, input_units, strict=False)] + mod.meshgrid = _meshgrid - + def _broadcast_arrays(*arrays): arrays = [asarray(array) for array in arrays] res = xp.broadcast_arrays(*[array.magnitude for array in arrays]) - return [ArrayUnitQuantity(magnitude, array.units) for magnitude, array in zip(res, arrays)] + return [ + ArrayUnitQuantity(magnitude, array.units) + for magnitude, array in zip(res, arrays, strict=False) + ] + mod.broadcast_arrays = _broadcast_arrays ## Data Type Functions and Data Types ## @@ -720,7 +724,6 @@ def multiply(x1, x2, /, *args, **kwargs): mod.multiply = multiply - def pow(x1, x2, /, *args, **kwargs): x1 = asarray(x1) x2 = asarray(x2) @@ -729,12 +732,12 @@ def pow(x1, x2, /, *args, **kwargs): raise DimensionalityError(x2.units, "dimensionless") if x2.ndim > 0 and not xp.all(x2.magnitude == x2[0].magnitude): raise DimensionalityError( - x2.units, - "dimensionless", - extra_msg="The exponent must be a scalar or an array of all the same value.", - ) + x2.units, + "dimensionless", + extra_msg="The exponent must be a scalar or an array of all the same value.", + ) - units = x1.units ** x2.magnitude + units = x1.units**x2.magnitude magnitude = xp.pow(x1.magnitude, x2.magnitude, *args, **kwargs) return ArrayUnitQuantity(magnitude, units) From f509f0d67d599db2ecea796f0ff7fedac17cc114 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 5 Jan 2025 23:32:06 +0000 Subject: [PATCH 26/49] dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ae4be4b..91de1ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ ipython = "ipython" [tool.pixi.feature.tests.dependencies] pytest = "*" +pytest-subtests = "*" [tool.pixi.feature.tests.tasks] tests = "pytest" From ef52c794be1f48f59d0b0378c4c30138fa962b1e Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Mon, 6 Jan 2025 11:57:21 +0000 Subject: [PATCH 27/49] update lockfile --- pixi.lock | 119 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 44 deletions(-) diff --git a/pixi.lock b/pixi.lock index ab1b665..96579da 100644 --- a/pixi.lock +++ b/pixi.lock @@ -42,7 +42,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py310ha75aee5_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py310h5851e9f_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -50,6 +50,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.10.16-he725a3c_1_cpython.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.10-5_cp310.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8228510_1.conda @@ -89,7 +90,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py310ha1ddda0_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -97,6 +98,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.10.16-h870587a_1_cpython.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.10-5_cp310.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda @@ -136,7 +138,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda - conda: https://prefix.dev/conda-forge/win-64/ndindex-1.9.2-py310ha8f682b_1.conda - conda: https://prefix.dev/conda-forge/win-64/numpy-2.2.1-py310hb9d903e_0.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -144,6 +146,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.10.16-h37870fc_1_cpython.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.10-5_cp310.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda @@ -201,7 +204,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py313h536fd9c_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -209,6 +212,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8228510_1.conda @@ -250,7 +254,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -258,6 +262,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda @@ -299,7 +304,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda - conda: https://prefix.dev/conda-forge/win-64/ndindex-1.9.2-py313ha7868ed_1.conda - conda: https://prefix.dev/conda-forge/win-64/numpy-2.2.1-py313hd65a2fa_0.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -307,6 +312,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda @@ -357,7 +363,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda @@ -391,7 +397,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.6-hdb05f8b_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda @@ -425,7 +431,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda - conda: https://prefix.dev/conda-forge/win-64/numpy-2.2.1-py313hd65a2fa_0.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda @@ -467,7 +473,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/ipython-8.31.0-pyh707e725_0.conda - conda: https://prefix.dev/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -494,7 +500,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py313h536fd9c_1.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda @@ -511,6 +517,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/pyyaml-6.0.2-py313h536fd9c_1.conda @@ -525,7 +532,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://prefix.dev/conda-forge/linux-64/ukkonen-1.0.1-py313h33d0bda_5.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - pypi: . @@ -548,7 +555,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/ipython-8.31.0-pyh707e725_0.conda - conda: https://prefix.dev/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda @@ -571,7 +578,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda @@ -588,6 +595,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pyyaml-6.0.2-py313h20a7fcf_1.conda @@ -602,7 +610,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ukkonen-1.0.1-py313hf9c7212_5.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 - pypi: . @@ -625,7 +633,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.4-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda - conda: https://prefix.dev/conda-forge/noarch/ipython-8.31.0-pyh7428d3b_0.conda @@ -648,7 +656,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/ndindex-1.9.2-py313ha7868ed_1.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/numpy-2.2.1-py313hd65a2fa_0.conda - - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda + - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/parso-0.8.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pickleshare-0.7.5-pyhd8ed1ab_1004.conda @@ -663,6 +671,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/pyyaml-6.0.2-py313ha7868ed_1.conda @@ -680,7 +689,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/ukkonen-1.0.1-py313h1ec8472_5.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-ha32ba9b_23.conda - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.42.34433-he29a5d6_23.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/vs2015_runtime-14.42.34433-hdffcdeb_23.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2 @@ -713,6 +722,7 @@ packages: depends: - python >=3.9 license: MIT + license_family: MIT purls: - pkg:pypi/array-api-compat?source=hash-mapping size: 38442 @@ -724,6 +734,7 @@ packages: - numpy - python >=3.9 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/array-api-strict?source=hash-mapping size: 53675 @@ -990,13 +1001,14 @@ packages: - setuptools - sortedcontainers >=2.1.0,<3.0.0 license: MPL-2.0 + license_family: MOZILLA purls: - pkg:pypi/hypothesis?source=hash-mapping size: 341990 timestamp: 1735341651189 -- conda: https://prefix.dev/conda-forge/noarch/identify-2.6.4-pyhd8ed1ab_0.conda - sha256: 8acc3bfc7781ea1ddc8c013faff5106a0539e5671e31bee0d81011a1e2df20d8 - md5: 5ec16e7ad9bab911ff0696940953f505 +- conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda + sha256: e8ea11b8e39a98a9c34efb5c21c3fca718e31e1f41fd9ae5f6918b8eb402da59 + md5: c1b0f663ff141265d1be1242259063f0 depends: - python >=3.9 - ukkonen @@ -1004,8 +1016,8 @@ packages: license_family: MIT purls: - pkg:pypi/identify?source=hash-mapping - size: 78570 - timestamp: 1735518781514 + size: 78415 + timestamp: 1736026672643 - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda sha256: 0ec8f4d02053cd03b0f3e63168316530949484f80e16f5e2fb199a1d117a89ca md5: 6837f3eff7dcea42ecd714ce1ac2b108 @@ -1796,6 +1808,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 7912254 @@ -1815,6 +1828,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 8478406 @@ -1834,6 +1848,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 5929029 @@ -1853,6 +1868,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 6513050 @@ -1872,6 +1888,7 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 6420026 @@ -1891,13 +1908,14 @@ packages: constrains: - numpy-base <0a0 license: BSD-3-Clause + license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping size: 7147174 timestamp: 1734905243335 -- conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-hb9d3cd8_0.conda - sha256: 814b9dff1847b132c676ee6cc1a8cb2d427320779b93e1b6d76552275c128705 - md5: 23cc74f77eb99315c0360ec3533147a9 +- conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda + sha256: f62f6bca4a33ca5109b6d571b052a394d836956d21b25b7ffd03376abf7a481f + md5: 4ce6875f75469b2757a65e10a5d05e31 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates @@ -1905,22 +1923,22 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 2947466 - timestamp: 1731377666602 -- conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h39f12f2_0.conda - sha256: bd1d58ced46e75efa3b842c61642fd12272c69e9fe4d7261078bc082153a1d53 - md5: df307bbc703324722df0293c9ca2e418 + size: 2937158 + timestamp: 1736086387286 +- conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda + sha256: 97772762abc70b3a537683ca9fc3ff3d6099eb64e4aba3b9c99e6fce48422d21 + md5: 22f971393637480bda8c679f374d8861 depends: - __osx >=11.0 - ca-certificates license: Apache-2.0 license_family: Apache purls: [] - size: 2935176 - timestamp: 1731377561525 -- conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-h2466b09_0.conda - sha256: e03045a0837e01ff5c75e9273a572553e7522290799807f918c917a9826a6484 - md5: d0d805d9b5524a14efb51b3bff965e83 + size: 2936415 + timestamp: 1736086108693 +- conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda + sha256: 519a06eaab7c878fbebb8cab98ea4a4465eafb1e9ed8c6ce67226068a80a92f0 + md5: fb45308ba8bfe1abf1f4a27bad24a743 depends: - ca-certificates - ucrt >=10.0.20348.0 @@ -1929,8 +1947,8 @@ packages: license: Apache-2.0 license_family: Apache purls: [] - size: 8491156 - timestamp: 1731379715927 + size: 8462960 + timestamp: 1736088436984 - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda sha256: da157b19bcd398b9804c5c52fc000fcb8ab0525bdb9c70f95beaa0bb42f85af1 md5: 3bfed7e6228ebf2f7b9eaa47f1b4e2aa @@ -1995,7 +2013,7 @@ packages: - pypi: . name: pint-array version: 0.0.1.dev0 - sha256: f755911bc8a921cbb2e3867f4638c8fc32d2f9090b17c405eefd0019e5c5335e + sha256: 24493191fd5707853309652b03e9a153a4d9ace5f7f1b9c9ccb73d824f68d514 requires_python: '>=3.10' editable: true - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda @@ -2137,6 +2155,19 @@ packages: - pkg:pypi/pytest-metadata?source=hash-mapping size: 14532 timestamp: 1734146281190 +- conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda + sha256: 610b1f994b7409cc131b5ecc838e515bf2c89eddcddb0b18a85dc6823eddc3d9 + md5: c94aae4e1b32cddb4e6a3dedeee8092b + depends: + - attrs >=19.2.0 + - pytest >=7.4 + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pytest-subtests?source=hash-mapping + size: 17983 + timestamp: 1733872677650 - conda: https://prefix.dev/conda-forge/linux-64/python-3.10.16-he725a3c_1_cpython.conda build_number: 1 sha256: 3f90a2d5062a73cd2dd8a0027718aee1db93f7975b9cfe529e2c9aeec2db262e @@ -2627,9 +2658,9 @@ packages: purls: [] size: 754247 timestamp: 1731710681163 -- conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.0-pyhd8ed1ab_0.conda - sha256: 82776f74e90a296b79415361faa6b10f360755c1fb8e6d59ca68509e6fe7e115 - md5: 1d601bc1d28b5ce6d112b90f4b9b8ede +- conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda + sha256: c8bde4547ddbd21ea89e483a7c65d8a5e442c0db494b0b977e389b75b9d03d62 + md5: 680b1c287b10cefc8bda0530b217229f depends: - distlib >=0.3.7,<1 - filelock >=3.12.2,<4 @@ -2639,8 +2670,8 @@ packages: license_family: MIT purls: - pkg:pypi/virtualenv?source=hash-mapping - size: 3350255 - timestamp: 1732609542072 + size: 3350367 + timestamp: 1735929107438 - conda: https://prefix.dev/conda-forge/win-64/vs2015_runtime-14.42.34433-hdffcdeb_23.conda sha256: 568ce8151eaae256f1cef752fc78651ad7a86ff05153cc7a4740b52ae6536118 md5: 5c176975ca2b8366abad3c97b3cd1e83 From c5a3424b33151d0ffa7e70cd5955c48e41443312 Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Mon, 6 Jan 2025 12:42:11 +0000 Subject: [PATCH 28/49] fix xp-tests --- pixi.lock | 2 +- pyproject.toml | 2 +- src/pint_array/__init__.py | 43 ++++++++++++++++++++++++++------------ tests/test_array.py | 37 +++++++++++++------------------- xp-tests-xfails.txt | 2 ++ 5 files changed, 48 insertions(+), 38 deletions(-) diff --git a/pixi.lock b/pixi.lock index 96579da..523ac68 100644 --- a/pixi.lock +++ b/pixi.lock @@ -2013,7 +2013,7 @@ packages: - pypi: . name: pint-array version: 0.0.1.dev0 - sha256: 24493191fd5707853309652b03e9a153a4d9ace5f7f1b9c9ccb73d824f68d514 + sha256: 4a89db66462f8ea5ad75b3a9e4b4b623805532b36abed68b124c6e67a3022bfe requires_python: '>=3.10' editable: true - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda diff --git a/pyproject.toml b/pyproject.toml index 91de1ce..e5e5a11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ pytest = "*" pytest-subtests = "*" [tool.pixi.feature.tests.tasks] -tests = "pytest" +tests = "pytest tests" [tool.pixi.feature.xp-tests.dependencies] pytest = "*" diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index c6f4fcb..8e03f93 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -15,7 +15,7 @@ from typing import Generic from array_api_compat import size -from pint import DimensionalityError, OffsetUnitCalculusError, Quantity +from pint import DimensionalityError, Quantity from pint.facets.plain import MagnitudeT, PlainQuantity __version__ = "0.0.1.dev0" @@ -493,9 +493,11 @@ def searchsorted(x1, x2, /, *, side="left", sorter=None): # ignore units of condition, convert x2 to units of x1 def where(condition, x1, x2, /): if not getattr(condition, "_is_multiplicative", True): - raise ValueError( - "Invalid units of the condition: Boolean value of Quantity with offset unit is ambiguous." + msg = ( + "Invalid units of the condition: " + "Boolean value of Quantity with offset unit is ambiguous." ) + raise ValueError(msg) condition = asarray(condition) if hasattr(x1, "units") and hasattr(x2, "units"): @@ -508,7 +510,6 @@ def where(condition, x1, x2, /): x1 = asarray(x1, units=x2.units) x2 = asarray(x2) units = x1.units - print(x2.m_as(units)) magnitude = xp.where(condition.magnitude, x1.magnitude, x2.m_as(units)) return ArrayUnitQuantity(magnitude, units) @@ -730,16 +731,32 @@ def pow(x1, x2, /, *args, **kwargs): if not x2.units.dimensionless: raise DimensionalityError(x2.units, "dimensionless") - if x2.ndim > 0 and not xp.all(x2.magnitude == x2[0].magnitude): - raise DimensionalityError( - x2.units, - "dimensionless", - extra_msg="The exponent must be a scalar or an array of all the same value.", - ) - - units = x1.units**x2.magnitude + x2_magnitude = x2.magnitude + x2_magnitude_dtype = x2_magnitude.dtype + if xp.isdtype(x2_magnitude_dtype, "complex floating"): + as_scalar = complex + elif xp.isdtype(x2_magnitude_dtype, "real floating"): + as_scalar = float + elif xp.isdtype(x2_magnitude_dtype, "integral"): + as_scalar = int + else: + as_scalar = bool + if x2.ndim == 0: + units = x1.units ** as_scalar(x2_magnitude) + else: + x2_first_elem_magnitude = x2[(0,) * x2.ndim] + if not xp.all(x2_magnitude == x2_first_elem_magnitude): + extra_msg = ( + "The exponent must be a scalar or an array of all the same value." + ) + raise DimensionalityError( + x2.units, + "dimensionless", + extra_msg=extra_msg, + ) + units = x1.units ** as_scalar(x2_first_elem_magnitude) - magnitude = xp.pow(x1.magnitude, x2.magnitude, *args, **kwargs) + magnitude = xp.pow(x1.magnitude, x2_magnitude, *args, **kwargs) return ArrayUnitQuantity(magnitude, units) mod.pow = pow diff --git a/tests/test_array.py b/tests/test_array.py index 040730c..5b2a214 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -6,28 +6,28 @@ import numpy as np import pytest -from pint import DimensionalityError, OffsetUnitCalculusError - -# from pint.compat import np +from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry from pint.testsuite import helpers import pint_array pnp = pint_array.pint_namespace(np) -from pint import UnitRegistry - ureg = UnitRegistry() <<<<<<< HEAD <<<<<<< HEAD ======= +<<<<<<< HEAD # @helpers.requires_numpy >>>>>>> 1ad9ca9 (style: pre-commit fixes) ======= >>>>>>> 8498256 (style: pre-commit fixes) class TestNumpyMethods: +======= +class TestNumPyMethods: +>>>>>>> 573558d (fix xp-tests) @classmethod def setup_class(cls): from pint import _DEFAULT_REGISTRY @@ -67,10 +67,10 @@ def assertNDArrayEqual(self, actual, desired): assert not isinstance(desired, self.Q_) -class TestNumpyArrayCreation(TestNumpyMethods): +class TestNumPyArrayCreation(TestNumPyMethods): # https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html - @pytest.mark.xfail(reason="Scalar arguement issue ") + @pytest.mark.xfail(reason="Scalar argument issue ") def test_ones_like(self): self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) @@ -82,7 +82,7 @@ def test_empty_like(self): assert ret.shape == (2, 2) assert isinstance(ret.magnitude, np.ndarray) - @pytest.mark.xfail(reason="Scalar arguement issue ") + @pytest.mark.xfail(reason="Scalar argument issue ") def test_full_like(self): helpers.assert_quantity_equal( pnp.full_like(self.q, self.Q_(0, self.ureg.degC)), @@ -91,13 +91,7 @@ def test_full_like(self): self.assertNDArrayEqual(pnp.full_like(self.q, 2), np.array([[2, 2], [2, 2]])) -class TestNumpyArrayManipulation(TestNumpyMethods): - # TODO - # https://www.numpy.org/devdocs/reference/routines.array-manipulation.html - # copyto - # broadcast - # asarray asanyarray asmatrix asfarray asfortranarray ascontiguousarray asarray_chkfinite asscalar require - +class TestNumPyArrayManipulation(TestNumPyMethods): # Changing array shape def test_flatten(self): @@ -194,12 +188,11 @@ def test_roll(self): ) -class TestNumpyMathematicalFunctions(TestNumpyMethods): +class TestNumPyMathematicalFunctions(TestNumPyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html def test_prod_numpy_func(self): axis = 0 - where = [[True, False], [True, True]] helpers.assert_quantity_equal(pnp.prod(self.q), 24 * self.ureg.m**4) helpers.assert_quantity_equal( @@ -278,7 +271,7 @@ def test_exponentiation_array_exp_2(self): op.ipow(arr_cp, q_cp) -class TestNumpyUnclassified(TestNumpyMethods): +class TestNumPyUnclassified(TestNumPyMethods): def test_repeat(self): helpers.assert_quantity_equal( pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m @@ -303,13 +296,13 @@ def test_nonzero_numpy_func(self): def test_any_numpy_func(self): q = [0, 1] * self.ureg.m assert pnp.any(q) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="offset unit is ambiguous"): pnp.any(self.q_temperature) def test_all_numpy_func(self): q = [0, 1] * self.ureg.m assert not pnp.all(q) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="offset unit is ambiguous"): pnp.all(self.q_temperature) def test_max_numpy_func(self): @@ -438,11 +431,10 @@ def test_pickle(self, subtests): def test_equal(self): x = self.q.magnitude u = self.Q_(pnp.ones(x.shape)) - true = pnp.ones_like(x, dtype=np.bool_) false = pnp.zeros_like(x, dtype=np.bool_) helpers.assert_quantity_equal(u, u) - helpers.assert_quantity_equal(u == u, u.magnitude == u.magnitude) + helpers.assert_quantity_equal(u, u.magnitude) helpers.assert_quantity_equal(u == 1, u.magnitude == 1) v = self.Q_(pnp.zeros(x.shape), "m") @@ -452,7 +444,6 @@ def test_equal(self): self.Q_(pnp.zeros_like(x), "m") == self.Q_(pnp.zeros_like(x), "s"), false, ) - self.assertNDArrayEqual(v == v, true) self.assertNDArrayEqual(v == w, false) self.assertNDArrayEqual(v == w.to("mm"), false) self.assertNDArrayEqual(u == v, false) diff --git a/xp-tests-xfails.txt b/xp-tests-xfails.txt index cc659de..6369ae4 100644 --- a/xp-tests-xfails.txt +++ b/xp-tests-xfails.txt @@ -38,3 +38,5 @@ array_api_tests/test_has_names.py::test_has_names[linalg-vecdot] array_api_tests/test_has_names.py::test_has_names[linalg-vector_norm] # flaky on macos array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt +# `pow` with array x2 is only defined when all elements of x2 are equal +array_api_tests/test_operators_and_elementwise_functions.py::test_pow[pow(x1, x2)] From 54038d5bc76e18d3a64d03ba51e4769569bfa74c Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Tue, 7 Jan 2025 23:35:51 +0000 Subject: [PATCH 29/49] np -> xp --- tests/test_array.py | 199 +++++++++++++++++++++++--------------------- 1 file changed, 102 insertions(+), 97 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index 5b2a214..d153916 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -4,6 +4,7 @@ import operator as op import pickle +import array_api_strict as xp import numpy as np import pytest from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry @@ -11,7 +12,7 @@ import pint_array -pnp = pint_array.pint_namespace(np) +pxp = pint_array.pint_namespace(xp) ureg = UnitRegistry() <<<<<<< HEAD @@ -46,15 +47,15 @@ def q(self): @property def q_scalar(self): - return np.array(5) * self.ureg.m + return pxp.asarray(5) * self.ureg.m @property def q_nan(self): - return [[1, 2], [3, pnp.nan]] * self.ureg.m + return [[1, 2], [3, pxp.nan]] * self.ureg.m @property def q_zero_or_nan(self): - return [[0, 0], [0, pnp.nan]] * self.ureg.m + return [[0, 0], [0, pxp.nan]] * self.ureg.m @property def q_temperature(self): @@ -72,23 +73,23 @@ class TestNumPyArrayCreation(TestNumPyMethods): @pytest.mark.xfail(reason="Scalar argument issue ") def test_ones_like(self): - self.assertNDArrayEqual(pnp.ones_like(self.q), np.array([[1, 1], [1, 1]])) + self.assertNDArrayEqual(pxp.ones_like(self.q), pxp.asarray([[1, 1], [1, 1]])) def test_zeros_like(self): - self.assertNDArrayEqual(pnp.zeros_like(self.q), np.array([[0, 0], [0, 0]])) + self.assertNDArrayEqual(pxp.zeros_like(self.q), pxp.asarray([[0, 0], [0, 0]])) def test_empty_like(self): - ret = pnp.empty_like(self.q) + ret = pxp.empty_like(self.q) assert ret.shape == (2, 2) - assert isinstance(ret.magnitude, np.ndarray) + assert ret.magnitude.__array_namespace__() is xp @pytest.mark.xfail(reason="Scalar argument issue ") def test_full_like(self): helpers.assert_quantity_equal( - pnp.full_like(self.q, self.Q_(0, self.ureg.degC)), + pxp.full_like(self.q, self.Q_(0, self.ureg.degC)), self.Q_([[0, 0], [0, 0]], self.ureg.degC), ) - self.assertNDArrayEqual(pnp.full_like(self.q, 2), np.array([[2, 2], [2, 2]])) + self.assertNDArrayEqual(pxp.full_like(self.q, 2), pxp.asarray([[2, 2], [2, 2]])) class TestNumPyArrayManipulation(TestNumPyMethods): @@ -110,43 +111,43 @@ def test_reshape(self): def test_moveaxis(self): helpers.assert_quantity_equal( - pnp.moveaxis(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m + pxp.moveaxis(self.q, 1, 0), pxp.asarray([[1, 2], [3, 4]]).T * self.ureg.m ) def test_transpose(self): helpers.assert_quantity_equal( - pnp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m + pxp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m ) def test_flip_numpy_func(self): helpers.assert_quantity_equal( - pnp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m + pxp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m ) # Changing number of dimensions def test_broadcast_to(self): helpers.assert_quantity_equal( - pnp.broadcast_to(self.q[:, 1], (2, 2)), - np.array([[2, 4], [2, 4]]) * self.ureg.m, + pxp.broadcast_to(self.q[:, 1], (2, 2)), + pxp.asarray([[2, 4], [2, 4]]) * self.ureg.m, ) def test_expand_dims(self): helpers.assert_quantity_equal( - pnp.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m + pxp.expand_dims(self.q, 0), pxp.asarray([[[1, 2], [3, 4]]]) * self.ureg.m ) def test_squeeze(self): - helpers.assert_quantity_equal(pnp.squeeze(self.q), self.q) + helpers.assert_quantity_equal(pxp.squeeze(self.q), self.q) helpers.assert_quantity_equal( - pnp.squeeze(self.q.reshape([1, 4])), [1, 2, 3, 4] * self.ureg.m + pxp.squeeze(self.q.reshape([1, 4])), [1, 2, 3, 4] * self.ureg.m ) # Changing number of dimensions # Joining arrays def test_concat_stack(self, subtests): - for func in (pnp.concat, pnp.stack): + for func in (pxp.concat, pxp.stack): with subtests.test(func=func): helpers.assert_quantity_equal( func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) @@ -164,8 +165,10 @@ def test_concat_stack(self, subtests): func([nz.m, self.q]) def test_astype(self): - actual = self.q.astype(pnp.float32) - expected = self.Q_(np.array([[1.0, 2.0], [3.0, 4.0]], dtype=pnp.float32), "m") + actual = self.q.astype(pxp.float32) + expected = self.Q_( + pxp.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=pxp.float32), "m" + ) helpers.assert_quantity_equal(actual, expected) assert actual.m.dtype == expected.m.dtype @@ -173,18 +176,18 @@ def test_item(self): helpers.assert_quantity_equal(self.Q_([[0]], "m").item(), 0 * self.ureg.m) def test_broadcast_arrays(self): - x = self.Q_(np.array([[1, 2, 3]]), "m") - y = self.Q_(np.array([[4], [5]]), "nm") - result = pnp.broadcast_arrays(x, y) + x = self.Q_(pxp.asarray([[1, 2, 3]]), "m") + y = self.Q_(pxp.asarray([[4], [5]]), "nm") + result = pxp.broadcast_arrays(x, y) expected = ( - self.Q_(np.array([[1, 2, 3], [1, 2, 3]]), "m"), - self.Q_(np.array([[4, 4, 4], [5, 5, 5]]), "nm"), + self.Q_(pxp.asarray([[1, 2, 3], [1, 2, 3]]), "m"), + self.Q_(pxp.asarray([[4, 4, 4], [5, 5, 5]]), "nm"), ) helpers.assert_quantity_equal(result, expected) def test_roll(self): helpers.assert_quantity_equal( - pnp.roll(self.q, 1), [[4, 1], [2, 3]] * self.ureg.m + pxp.roll(self.q, 1), [[4, 1], [2, 3]] * self.ureg.m ) @@ -194,19 +197,19 @@ class TestNumPyMathematicalFunctions(TestNumPyMethods): def test_prod_numpy_func(self): axis = 0 - helpers.assert_quantity_equal(pnp.prod(self.q), 24 * self.ureg.m**4) + helpers.assert_quantity_equal(pxp.prod(self.q), 24 * self.ureg.m**4) helpers.assert_quantity_equal( - pnp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 + pxp.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 ) def test_sum_numpy_func(self): - helpers.assert_quantity_equal(pnp.sum(self.q, axis=0), [4, 6] * self.ureg.m) + helpers.assert_quantity_equal(pxp.sum(self.q, axis=0), [4, 6] * self.ureg.m) with pytest.raises(OffsetUnitCalculusError): - pnp.sum(self.q_temperature) + pxp.sum(self.q_temperature) # Arithmetic operations def test_addition_with_scalar(self): - a = np.array([0, 1, 2]) + a = pxp.asarray([0, 1, 2]) b = 10.0 * self.ureg("gram/kilogram") helpers.assert_quantity_almost_equal( a + b, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) @@ -216,7 +219,7 @@ def test_addition_with_scalar(self): ) def test_addition_with_incompatible_scalar(self): - a = np.array([0, 1, 2]) + a = pxp.asarray([0, 1, 2]) b = 1.0 * self.ureg.m with pytest.raises(DimensionalityError): op.add(a, b) @@ -224,10 +227,10 @@ def test_addition_with_incompatible_scalar(self): op.add(b, a) def test_power(self): - arr = np.array(range(3), dtype=float) + arr = pxp.asarray(range(3), dtype=float) q = self.Q_(arr, "meter") - for op_ in [pnp.pow]: + for op_ in [pxp.pow]: q_cp = copy.copy(q) with pytest.raises(DimensionalityError): op_(2.0, q_cp) @@ -241,20 +244,20 @@ def test_power(self): op_(q_cp, q2_cp) helpers.assert_quantity_equal( - pnp.pow(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") + pxp.pow(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") ) helpers.assert_quantity_equal( self.q ** self.Q_(2), self.Q_([[1, 4], [9, 16]], "m**2") ) - self.assertNDArrayEqual(arr ** self.Q_(2), np.array([0, 1, 4])) + self.assertNDArrayEqual(arr ** self.Q_(2), pxp.asarray([0, 1, 4])) def test_sqrt(self): q = self.Q_(100, "m**2") - helpers.assert_quantity_equal(pnp.sqrt(q), self.Q_(10, "m")) + helpers.assert_quantity_equal(pxp.sqrt(q), self.Q_(10, "m")) @pytest.mark.xfail def test_exponentiation_array_exp_2(self): - arr = np.array(range(3), dtype=float) + arr = pxp.asarray(range(3), dtype=float) # q = self.Q_(copy.copy(arr), None) q = self.Q_(copy.copy(arr), "meter") arr_cp = copy.copy(arr) @@ -274,94 +277,96 @@ def test_exponentiation_array_exp_2(self): class TestNumPyUnclassified(TestNumPyMethods): def test_repeat(self): helpers.assert_quantity_equal( - pnp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m + pxp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m ) def test_sort_numpy_func(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m - helpers.assert_quantity_equal(pnp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) + helpers.assert_quantity_equal(pxp.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) def test_argsort_numpy_func(self): - self.assertNDArrayEqual(pnp.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) + self.assertNDArrayEqual( + pxp.argsort(self.q, axis=0), pxp.asarray([[0, 0], [1, 1]]) + ) def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() - self.assertNDArrayEqual(pnp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) + self.assertNDArrayEqual(pxp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m - self.assertNDArrayEqual(pnp.nonzero(q)[0], [0, 2, 3, 5]) + self.assertNDArrayEqual(pxp.nonzero(q)[0], [0, 2, 3, 5]) def test_any_numpy_func(self): q = [0, 1] * self.ureg.m - assert pnp.any(q) + assert pxp.any(q) with pytest.raises(ValueError, match="offset unit is ambiguous"): - pnp.any(self.q_temperature) + pxp.any(self.q_temperature) def test_all_numpy_func(self): q = [0, 1] * self.ureg.m - assert not pnp.all(q) + assert not pxp.all(q) with pytest.raises(ValueError, match="offset unit is ambiguous"): - pnp.all(self.q_temperature) + pxp.all(self.q_temperature) def test_max_numpy_func(self): - assert pnp.max(self.q) == 4 * self.ureg.m + assert pxp.max(self.q) == 4 * self.ureg.m def test_max_with_axis_arg(self): - helpers.assert_quantity_equal(pnp.max(self.q, axis=1), [2, 4] * self.ureg.m) + helpers.assert_quantity_equal(pxp.max(self.q, axis=1), [2, 4] * self.ureg.m) def test_argmax_numpy_func(self): - self.assertNDArrayEqual(pnp.argmax(self.q, axis=0), np.array([1, 1])) + self.assertNDArrayEqual(pxp.argmax(self.q, axis=0), pxp.asarray([1, 1])) def test_maximum(self): helpers.assert_quantity_equal( - pnp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") + pxp.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) def test_min_numpy_func(self): - assert pnp.min(self.q) == 1 * self.ureg.m + assert pxp.min(self.q) == 1 * self.ureg.m def test_min_with_axis_arg(self): - helpers.assert_quantity_equal(pnp.min(self.q, axis=1), [1, 3] * self.ureg.m) + helpers.assert_quantity_equal(pxp.min(self.q, axis=1), [1, 3] * self.ureg.m) def test_argmin_numpy_func(self): - self.assertNDArrayEqual(pnp.argmin(self.q, axis=0), np.array([0, 0])) + self.assertNDArrayEqual(pxp.argmin(self.q, axis=0), pxp.asarray([0, 0])) def test_minimum(self): helpers.assert_quantity_equal( - pnp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") + pxp.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) def test_clip_numpy_func(self): helpers.assert_quantity_equal( - pnp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m + pxp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) def test_round_numpy_func(self): helpers.assert_quantity_equal( - pnp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m + pxp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) def test_cumulative_sum(self): helpers.assert_quantity_equal( - pnp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m + pxp.cumulative_sum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) def test_mean_numpy_func(self): - assert pnp.mean(self.q) == 2.5 * self.ureg.m - assert pnp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) + assert pxp.mean(self.q) == 2.5 * self.ureg.m + assert pxp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) def test_var_numpy_func(self): - assert pnp.var(self.q) == 1.25 * self.ureg.m**2 - assert pnp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 + assert pxp.var(self.q) == 1.25 * self.ureg.m**2 + assert pxp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 def test_std_numpy_func(self): helpers.assert_quantity_almost_equal( - pnp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 + pxp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 ) helpers.assert_quantity_almost_equal( - pnp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 + pxp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 ) def test_conj(self): @@ -384,7 +389,7 @@ def test_setitem(self): with pytest.raises(DimensionalityError): self.q[0] = 1 with pytest.raises(DimensionalityError): - self.q[0] = np.ndarray([1, 2]) + self.q[0] = pxp.asarray([1, 2]) with pytest.raises(DimensionalityError): self.q[0] = 1 * self.ureg.J @@ -403,9 +408,9 @@ def test_setitem(self): # check and see that dimensionless numbers work correctly q = [0, 1, 2, 3] * self.ureg.dimensionless q[0] = 1 - helpers.assert_quantity_equal(q, pnp.asarray([1, 1, 2, 3])) + helpers.assert_quantity_equal(q, pxp.asarray([1, 1, 2, 3])) q[0] = self.ureg.m / self.ureg.mm - helpers.assert_quantity_equal(q, pnp.asarray([1000, 1, 2, 3])) + helpers.assert_quantity_equal(q, pxp.asarray([1000, 1, 2, 3])) q = [0.0, 1.0, 2.0, 3.0] * self.ureg.m / self.ureg.mm q[0] = 1.0 @@ -414,7 +419,7 @@ def test_setitem(self): def test_reversible_op(self): """ """ x = self.q.magnitude - u = self.Q_(pnp.ones(x.shape)) + u = self.Q_(pxp.ones(x.shape)) helpers.assert_quantity_equal(x / self.q, u * x / self.q) helpers.assert_quantity_equal(x * self.q, u * x * self.q) helpers.assert_quantity_equal(x + u, u + x) @@ -430,18 +435,18 @@ def test_pickle(self, subtests): def test_equal(self): x = self.q.magnitude - u = self.Q_(pnp.ones(x.shape)) - false = pnp.zeros_like(x, dtype=np.bool_) + u = self.Q_(pxp.ones(x.shape)) + false = pxp.zeros_like(x, dtype=pxp.bool) helpers.assert_quantity_equal(u, u) helpers.assert_quantity_equal(u, u.magnitude) helpers.assert_quantity_equal(u == 1, u.magnitude == 1) - v = self.Q_(pnp.zeros(x.shape), "m") - w = self.Q_(pnp.ones(x.shape), "m") + v = self.Q_(pxp.zeros(x.shape), "m") + w = self.Q_(pxp.ones(x.shape), "m") self.assertNDArrayEqual(v == 1, false) self.assertNDArrayEqual( - self.Q_(pnp.zeros_like(x), "m") == self.Q_(pnp.zeros_like(x), "s"), + self.Q_(pxp.zeros_like(x), "m") == self.Q_(pxp.zeros_like(x), "s"), false, ) self.assertNDArrayEqual(v == w, false) @@ -449,80 +454,80 @@ def test_equal(self): self.assertNDArrayEqual(u == v, false) def test_dtype(self): - u = self.Q_(pnp.arange(12, dtype="uint32")) + u = self.Q_(pxp.arange(12, dtype="uint32")) assert u.dtype == "uint32" def test_shape_numpy_func(self): - assert pnp.asarray(self.q).shape == (2, 2) + assert pxp.asarray(self.q).shape == (2, 2) def test_ndim_numpy_func(self): - assert pnp.asarray(self.q).ndim == 2 + assert pxp.asarray(self.q).ndim == 2 def test_meshgrid_numpy_func(self): x = [1, 2] * self.ureg.m y = [0, 50, 100] * self.ureg.mm - xx, yy = pnp.meshgrid(x, y) + xx, yy = pxp.meshgrid(x, y) helpers.assert_quantity_equal(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) def test_comparisons(self): self.assertNDArrayEqual( - self.q > 2 * self.ureg.m, np.array([[False, False], [True, True]]) + self.q > 2 * self.ureg.m, pxp.asarray([[False, False], [True, True]]) ) self.assertNDArrayEqual( - self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]]) + self.q < 2 * self.ureg.m, pxp.asarray([[True, False], [False, False]]) ) def test_where(self): helpers.assert_quantity_equal( - pnp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), + pxp.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), [[20, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 2 * self.ureg.m, self.q, 0), + pxp.where(self.q >= 2 * self.ureg.m, self.q, 0), [[0, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 2 * self.ureg.m, self.q, pnp.nan), - [[pnp.nan, 2], [3, 4]] * self.ureg.m, + pxp.where(self.q >= 2 * self.ureg.m, self.q, pxp.nan), + [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 3 * self.ureg.m, 0, self.q), + pxp.where(self.q >= 3 * self.ureg.m, 0, self.q), [[1, 2], [0, 0]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 3 * self.ureg.m, pnp.nan, self.q), - [[1, 2], [pnp.nan, pnp.nan]] * self.ureg.m, + pxp.where(self.q >= 3 * self.ureg.m, pxp.nan, self.q), + [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 2 * self.ureg.m, self.q, np.array(pnp.nan)), - [[pnp.nan, 2], [3, 4]] * self.ureg.m, + pxp.where(self.q >= 2 * self.ureg.m, self.q, pxp.asarray(pxp.nan)), + [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pnp.where(self.q >= 3 * self.ureg.m, np.array(pnp.nan), self.q), - [[1, 2], [pnp.nan, pnp.nan]] * self.ureg.m, + pxp.where(self.q >= 3 * self.ureg.m, pxp.asarray(pxp.nan), self.q), + [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) with pytest.raises(DimensionalityError): - pnp.where( + pxp.where( self.q < 2 * self.ureg.m, self.q, 0 * self.ureg.J, ) helpers.assert_quantity_equal( - pnp.where([-1, 0, 1] * self.ureg.m, [1, 2, 1] * self.ureg.s, pnp.nan), - [1, pnp.nan, 1] * self.ureg.s, + pxp.where([-1, 0, 1] * self.ureg.m, [1, 2, 1] * self.ureg.s, pxp.nan), + [1, pxp.nan, 1] * self.ureg.s, ) with pytest.raises( ValueError, match=".*Boolean value of Quantity with offset unit is ambiguous", ): - pnp.where( - self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pnp.nan + pxp.where( + self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, pxp.nan ) def test_tile(self): helpers.assert_quantity_equal( - pnp.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m + pxp.tile(self.q, 2), pxp.asarray([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m ) From 4f9d05c30ce7eb02ca0f7bbf9212e3b856be51f1 Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Tue, 7 Jan 2025 23:37:27 +0000 Subject: [PATCH 30/49] remove pickle test --- tests/test_array.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index d153916..1408881 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -2,7 +2,6 @@ import copy import operator as op -import pickle import array_api_strict as xp import numpy as np @@ -425,14 +424,6 @@ def test_reversible_op(self): helpers.assert_quantity_equal(x + u, u + x) helpers.assert_quantity_equal(x - u, -(u - x)) - def test_pickle(self, subtests): - for protocol in range(pickle.HIGHEST_PROTOCOL + 1): - with subtests.test(protocol): - q1 = [10, 20] * self.ureg.m - q2 = pickle.loads(pickle.dumps(q1, protocol)) - self.assertNDArrayEqual(q1.magnitude, q2.magnitude) - assert q1.units == q2.units - def test_equal(self): x = self.q.magnitude u = self.Q_(pxp.ones(x.shape)) From 2242bf33a86c27c59e062af2de5cd3151b16116d Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Tue, 7 Jan 2025 23:46:23 +0000 Subject: [PATCH 31/49] some tweaks --- src/pint_array/__init__.py | 2 ++ tests/test_array.py | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 8e03f93..eb440db 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -946,4 +946,6 @@ def clip(x, /, min=None, max=None): with contextlib.suppress(AttributeError, TypeError): mod_attr.__name__ = xp_attr.__name__ + mod.ArrayUnitQuantity = ArrayUnitQuantity + return mod diff --git a/tests/test_array.py b/tests/test_array.py index 1408881..83ae407 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -6,13 +6,12 @@ import array_api_strict as xp import numpy as np import pytest -from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry +from pint import DimensionalityError, OffsetUnitCalculusError from pint.testsuite import helpers import pint_array pxp = pint_array.pint_namespace(xp) -ureg = UnitRegistry() <<<<<<< HEAD <<<<<<< HEAD @@ -33,7 +32,7 @@ def setup_class(cls): from pint import _DEFAULT_REGISTRY cls.ureg = _DEFAULT_REGISTRY - cls.Q_ = cls.ureg.Quantity + cls.Q_ = pxp.ArrayUnitQuantity @classmethod def teardown_class(cls): @@ -42,23 +41,23 @@ def teardown_class(cls): @property def q(self): - return [[1, 2], [3, 4]] * self.ureg.m + return pxp.asarray([[1, 2], [3, 4]], units=self.ureg.m) @property def q_scalar(self): - return pxp.asarray(5) * self.ureg.m + return pxp.asarray(5, units=self.ureg.m) @property def q_nan(self): - return [[1, 2], [3, pxp.nan]] * self.ureg.m + return pxp.asarray([[1, 2], [3, pxp.nan]], units=self.ureg.m) @property def q_zero_or_nan(self): - return [[0, 0], [0, pxp.nan]] * self.ureg.m + return pxp.asarray([[0, 0], [0, pxp.nan]], units=self.ureg.m) @property def q_temperature(self): - return self.Q_([[1, 2], [3, 4]], self.ureg.degC) + return pxp.asarray([[1, 2], [3, 4]], self.ureg.degC) def assertNDArrayEqual(self, actual, desired): # Assert that the given arrays are equal, and are not Quantities @@ -115,12 +114,12 @@ def test_moveaxis(self): def test_transpose(self): helpers.assert_quantity_equal( - pxp.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m + pxp.matrix_transpose(self.q), pxp.asarray([[1, 3], [2, 4]]) * self.ureg.m ) def test_flip_numpy_func(self): helpers.assert_quantity_equal( - pxp.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m + pxp.flip(self.q, axis=0), pxp.asarray([[3, 4], [1, 2]]) * self.ureg.m ) # Changing number of dimensions @@ -139,7 +138,7 @@ def test_expand_dims(self): def test_squeeze(self): helpers.assert_quantity_equal(pxp.squeeze(self.q), self.q) helpers.assert_quantity_equal( - pxp.squeeze(self.q.reshape([1, 4])), [1, 2, 3, 4] * self.ureg.m + pxp.squeeze(self.q.reshape([1, 4])), pxp.asarray([1, 2, 3, 4]) * self.ureg.m ) # Changing number of dimensions From e2050efa30bf21a7eed504f28a5a579faae0ca0c Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 11 Jan 2025 18:11:28 +0000 Subject: [PATCH 32/49] pass some tests --- src/pint_array/__init__.py | 2 + tests/test_array.py | 114 ++++++++++++++++++++----------------- 2 files changed, 63 insertions(+), 53 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index eb440db..609891e 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -115,6 +115,8 @@ def __mul__(self, other): else: magnitude = self._call_super_method("__mul__", other) units = self.units + if magnitude is NotImplemented: + return NotImplemented return ArrayUnitQuantity(magnitude, units) def __gt__(self, other): diff --git a/tests/test_array.py b/tests/test_array.py index 83ae407..3c688b9 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -57,7 +57,7 @@ def q_zero_or_nan(self): @property def q_temperature(self): - return pxp.asarray([[1, 2], [3, 4]], self.ureg.degC) + return pxp.asarray([[1, 2], [3, 4]], units = self.ureg.degC) def assertNDArrayEqual(self, actual, desired): # Assert that the given arrays are equal, and are not Quantities @@ -73,6 +73,7 @@ class TestNumPyArrayCreation(TestNumPyMethods): def test_ones_like(self): self.assertNDArrayEqual(pxp.ones_like(self.q), pxp.asarray([[1, 1], [1, 1]])) + @pytest.mark.xfail(reason="should this be using NDArrayEqual?") def test_zeros_like(self): self.assertNDArrayEqual(pxp.zeros_like(self.q), pxp.asarray([[0, 0], [0, 0]])) @@ -93,16 +94,9 @@ def test_full_like(self): class TestNumPyArrayManipulation(TestNumPyMethods): # Changing array shape - def test_flatten(self): - helpers.assert_quantity_equal(self.q.flatten(), [1, 2, 3, 4] * self.ureg.m) - - def test_flat(self): - for q, v in zip(self.q.flat, [1, 2, 3, 4], strict=False): - assert q == v * self.ureg.m - def test_reshape(self): helpers.assert_quantity_equal( - self.q.reshape([1, 4]), [[1, 2, 3, 4]] * self.ureg.m + pxp.reshape(self.q, [1, 4]), pxp.asarray([[1, 2, 3, 4]] ) * self.ureg.m ) # Transpose-like operations @@ -132,13 +126,13 @@ def test_broadcast_to(self): def test_expand_dims(self): helpers.assert_quantity_equal( - pxp.expand_dims(self.q, 0), pxp.asarray([[[1, 2], [3, 4]]]) * self.ureg.m + pxp.expand_dims(self.q, axis=0), pxp.asarray([[[1, 2], [3, 4]]]) * self.ureg.m ) def test_squeeze(self): - helpers.assert_quantity_equal(pxp.squeeze(self.q), self.q) helpers.assert_quantity_equal( - pxp.squeeze(self.q.reshape([1, 4])), pxp.asarray([1, 2, 3, 4]) * self.ureg.m + pxp.squeeze(pxp.asarray([[[0], [1], [2]]]) *self.ureg.m, axis=0), + pxp.asarray([0,1,2]) * self.ureg.m ) # Changing number of dimensions @@ -148,7 +142,7 @@ def test_concat_stack(self, subtests): for func in (pxp.concat, pxp.stack): with subtests.test(func=func): helpers.assert_quantity_equal( - func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) + func([self.q] * 2), pxp.asarray(func([self.q.m] * 2), units = "m") ) # One or more of the args is a bare array full of zeros or NaNs # helpers.assert_quantity_equal( @@ -163,10 +157,9 @@ def test_concat_stack(self, subtests): func([nz.m, self.q]) def test_astype(self): - actual = self.q.astype(pxp.float32) - expected = self.Q_( - pxp.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=pxp.float32), "m" - ) + dtype=pxp.float32 + actual = pxp.astype(self.q, dtype) + expected = pxp.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=dtype, units = "m") helpers.assert_quantity_equal(actual, expected) assert actual.m.dtype == expected.m.dtype @@ -174,12 +167,12 @@ def test_item(self): helpers.assert_quantity_equal(self.Q_([[0]], "m").item(), 0 * self.ureg.m) def test_broadcast_arrays(self): - x = self.Q_(pxp.asarray([[1, 2, 3]]), "m") - y = self.Q_(pxp.asarray([[4], [5]]), "nm") + x = pxp.asarray([[1, 2, 3]], units= "m") + y = pxp.asarray([[4], [5]], units= "nm") result = pxp.broadcast_arrays(x, y) expected = ( - self.Q_(pxp.asarray([[1, 2, 3], [1, 2, 3]]), "m"), - self.Q_(pxp.asarray([[4, 4, 4], [5, 5, 5]]), "nm"), + pxp.asarray([[1, 2, 3], [1, 2, 3]], units= "m"), + pxp.asarray([[4, 4, 4], [5, 5, 5]], units= "nm") ) helpers.assert_quantity_equal(result, expected) @@ -207,13 +200,13 @@ def test_sum_numpy_func(self): # Arithmetic operations def test_addition_with_scalar(self): - a = pxp.asarray([0, 1, 2]) + a = pxp.asarray([0, 1, 2], dtype = pxp.float32) b = 10.0 * self.ureg("gram/kilogram") helpers.assert_quantity_almost_equal( - a + b, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) + a + b, pxp.asarray([0.01, 1.01, 2.01], units = "") ) helpers.assert_quantity_almost_equal( - b + a, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) + b + a, pxp.asarray([0.01, 1.01, 2.01], units = "") ) def test_addition_with_incompatible_scalar(self): @@ -225,7 +218,7 @@ def test_addition_with_incompatible_scalar(self): op.add(b, a) def test_power(self): - arr = pxp.asarray(range(3), dtype=float) + arr = pxp.asarray(range(3), dtype=pxp.float32) q = self.Q_(arr, "meter") for op_ in [pxp.pow]: @@ -250,8 +243,8 @@ def test_power(self): self.assertNDArrayEqual(arr ** self.Q_(2), pxp.asarray([0, 1, 4])) def test_sqrt(self): - q = self.Q_(100, "m**2") - helpers.assert_quantity_equal(pxp.sqrt(q), self.Q_(10, "m")) + q = self.Q_(100.0, "m**2") + helpers.assert_quantity_equal(pxp.sqrt(q), self.Q_(10.0, "m")) @pytest.mark.xfail def test_exponentiation_array_exp_2(self): @@ -338,12 +331,12 @@ def test_minimum(self): def test_clip_numpy_func(self): helpers.assert_quantity_equal( - pxp.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m + pxp.clip(pxp.asarray(self.q, dtype=pxp.float32), 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) def test_round_numpy_func(self): helpers.assert_quantity_equal( - pxp.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m + pxp.round(102.75 * self.ureg.m, ), 103 * self.ureg.m ) def test_cumulative_sum(self): @@ -352,19 +345,21 @@ def test_cumulative_sum(self): ) def test_mean_numpy_func(self): - assert pxp.mean(self.q) == 2.5 * self.ureg.m - assert pxp.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) + assert pxp.mean(pxp.asarray(self.q, dtype=pxp.float32)) == 2.5 * self.ureg.m + assert pxp.mean(pxp.asarray(self.q_temperature, dtype=pxp.float32)) == self.Q_(2.5, self.ureg.degC) def test_var_numpy_func(self): - assert pxp.var(self.q) == 1.25 * self.ureg.m**2 - assert pxp.var(self.q_temperature) == 1.25 * self.ureg.delta_degC**2 + dtype=pxp.float32 + assert pxp.var(pxp.asarray(self.q, dtype=dtype)) == 1.25 * self.ureg.m**2 + assert pxp.var(pxp.asarray(self.q_temperature, dtype=dtype)) == 1.25 * self.ureg.delta_degC**2 def test_std_numpy_func(self): + dtype=pxp.float32 helpers.assert_quantity_almost_equal( - pxp.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 + pxp.std(pxp.asarray(self.q, dtype=dtype)), 1.11803 * self.ureg.m, rtol=1e-5 ) helpers.assert_quantity_almost_equal( - pxp.std(self.q_temperature), 1.11803 * self.ureg.delta_degC, rtol=1e-5 + pxp.std(pxp.asarray(self.q_temperature, dtype=dtype)), 1.11803 * self.ureg.delta_degC, rtol=1e-5 ) def test_conj(self): @@ -425,15 +420,15 @@ def test_reversible_op(self): def test_equal(self): x = self.q.magnitude - u = self.Q_(pxp.ones(x.shape)) + u = pxp.ones(x.shape) false = pxp.zeros_like(x, dtype=pxp.bool) helpers.assert_quantity_equal(u, u) helpers.assert_quantity_equal(u, u.magnitude) helpers.assert_quantity_equal(u == 1, u.magnitude == 1) - v = self.Q_(pxp.zeros(x.shape), "m") - w = self.Q_(pxp.ones(x.shape), "m") + v = pxp.asarray((pxp.zeros(x.shape)), units = "m") + w = pxp.asarray((pxp.ones(x.shape)), units = "m") self.assertNDArrayEqual(v == 1, false) self.assertNDArrayEqual( self.Q_(pxp.zeros_like(x), "m") == self.Q_(pxp.zeros_like(x), "s"), @@ -444,9 +439,10 @@ def test_equal(self): self.assertNDArrayEqual(u == v, false) def test_dtype(self): - u = self.Q_(pxp.arange(12, dtype="uint32")) + dtype=pxp.uint32 + u = pxp.asarray([1, 2, 3], dtype=dtype) * self.ureg.m - assert u.dtype == "uint32" + assert u.dtype == dtype def test_shape_numpy_func(self): assert pxp.asarray(self.q).shape == (2, 2) @@ -455,8 +451,8 @@ def test_ndim_numpy_func(self): assert pxp.asarray(self.q).ndim == 2 def test_meshgrid_numpy_func(self): - x = [1, 2] * self.ureg.m - y = [0, 50, 100] * self.ureg.mm + x = pxp.asarray([1, 2]) * self.ureg.m + y = pxp.asarray([0, 50, 100]) * self.ureg.mm xx, yy = pxp.meshgrid(x, y) helpers.assert_quantity_equal(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) @@ -478,36 +474,37 @@ def test_where(self): pxp.where(self.q >= 2 * self.ureg.m, self.q, 0), [[0, 2], [3, 4]] * self.ureg.m, ) + q_float = self.q.astype(float) helpers.assert_quantity_equal( - pxp.where(self.q >= 2 * self.ureg.m, self.q, pxp.nan), + pxp.where(q_float >= 2 * self.ureg.m, q_float, pxp.nan), [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(self.q >= 3 * self.ureg.m, 0, self.q), + pxp.where(q_float >= 3 * self.ureg.m, 0., q_float), [[1, 2], [0, 0]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(self.q >= 3 * self.ureg.m, pxp.nan, self.q), + pxp.where(q_float >= 3 * self.ureg.m, pxp.nan, q_float), [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(self.q >= 2 * self.ureg.m, self.q, pxp.asarray(pxp.nan)), + pxp.where(q_float >= 2 * self.ureg.m, q_float, pxp.asarray(pxp.nan)* self.ureg.m), [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(self.q >= 3 * self.ureg.m, pxp.asarray(pxp.nan), self.q), + pxp.where(q_float >= 3 * self.ureg.m, pxp.asarray(pxp.nan)* self.ureg.m, q_float), [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) with pytest.raises(DimensionalityError): pxp.where( - self.q < 2 * self.ureg.m, - self.q, + q_float < 2 * self.ureg.m, + q_float, 0 * self.ureg.J, ) helpers.assert_quantity_equal( - pxp.where([-1, 0, 1] * self.ureg.m, [1, 2, 1] * self.ureg.s, pxp.nan), - [1, pxp.nan, 1] * self.ureg.s, + pxp.where(pxp.asarray([-1., 0., 1.]) * self.ureg.m, pxp.asarray([1., 2., 1.]) * self.ureg.s, pxp.nan), + pxp.asarray([1., pxp.nan, 1.]) * self.ureg.s, ) with pytest.raises( ValueError, @@ -519,5 +516,16 @@ def test_where(self): def test_tile(self): helpers.assert_quantity_equal( - pxp.tile(self.q, 2), pxp.asarray([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m - ) + pxp.tile(pxp.asarray([1,2,3,4]) *self.ureg.m, (4,1)), + pxp.asarray([[1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4], + [1, 2, 3, 4]]) * self.ureg.m + ) + helpers.assert_quantity_equal( + pxp.tile(pxp.asarray([[1, 2], [3, 4]]) *self.ureg.m, (2,1)), + pxp.asarray([[1, 2], + [3, 4], + [1, 2], + [3, 4]]) * self.ureg.m + ) \ No newline at end of file From 4939cda650f752f1a562edd6dea139d0e68bf742 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 11 Jan 2025 20:34:42 +0000 Subject: [PATCH 33/49] tests --- src/pint_array/__init__.py | 62 ++++++++++++++++++++++++++------------ tests/test_array.py | 21 +++++++------ 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 609891e..925f7ab 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -119,13 +119,6 @@ def __mul__(self, other): return NotImplemented return ArrayUnitQuantity(magnitude, units) - def __gt__(self, other): - if hasattr(other, "units"): - magnitude = self._call_super_method("__gt__", other.magnitude) - else: - magnitude = self._call_super_method("__gt__", other) - return ArrayUnitQuantity(magnitude, None) - ## Linear Algebra Methods ## def __matmul__(self, other): return mod.matmul(self, other) @@ -197,31 +190,59 @@ def fun(self, name=name): "__add__", "__sub__", "__and__", + "__mod__", + # "__mul__", + # "__pow__", + "__truediv__", + "__divmod__", + "__floordiv__", + ] + # Methods that return the result of an elementwise binary operation (reflected) + rbinary_names = [ + "__radd__", + "__rand__", + "__rdivmod__", + "__rfloordiv__", + "__rmod__", + "__rmul__", + "__ror__", + # "__rpow__", + "__rsub__", + "__rtruediv__", + "__rxor__", + ] + for name in binary_names + rbinary_names: + + def method(self, other, name=name): + units = self.units + magnitude_other = other.m_as(units) if hasattr(other, "units") else other + magnitude = self._call_super_method(name, magnitude_other) + # FIXME: correct units for op + return ArrayUnitQuantity(magnitude, units) + + setattr(ArrayQuantity, name, method) + + + # Methods that return the result of an elementwise binary operation + unitless_binary_names = [ "__eq__", "__ge__", - # "__gt__", + "__gt__", "__le__", - "__lshift__", "__lt__", - "__mod__", - # "__mul__", "__ne__", "__or__", - "__pow__", - "__rshift__", - "__sub__", "__truediv__", "__xor__", "__divmod__", "__floordiv__", ] # Methods that return the result of an elementwise binary operation (reflected) - rbinary_names = [ + unitless_rbinary_names = [ "__radd__", "__rand__", "__rdivmod__", "__rfloordiv__", - "__rlshift__", "__rmod__", "__rmul__", "__ror__", @@ -231,14 +252,14 @@ def fun(self, name=name): "__rtruediv__", "__rxor__", ] - for name in binary_names + rbinary_names: + for name in unitless_binary_names + unitless_rbinary_names: def method(self, other, name=name): units = self.units magnitude_other = other.m_as(units) if hasattr(other, "units") else other magnitude = self._call_super_method(name, magnitude_other) # FIXME: correct units for op - return ArrayUnitQuantity(magnitude, units) + return magnitude setattr(ArrayQuantity, name, method) @@ -456,7 +477,8 @@ def func(x, /, *args, func_str=func_str, **kwargs): setattr(mod, func_str, func) - # Functions which ignore units on input and output + # Functions which ignore units on input and output, and return a bool/int + # as a non-Quantity array for func_str in ( "argsort", "argmin", @@ -468,7 +490,7 @@ def func(x, /, *args, func_str=func_str, **kwargs): magnitude = xp.asarray(x.magnitude, copy=True) xp_func = getattr(xp, func_str) magnitude = xp_func(magnitude, *args, **kwargs) - return ArrayUnitQuantity(magnitude, None) + return magnitude setattr(mod, func_str, func) diff --git a/tests/test_array.py b/tests/test_array.py index 3c688b9..eb0cbef 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -308,7 +308,7 @@ def test_max_with_axis_arg(self): helpers.assert_quantity_equal(pxp.max(self.q, axis=1), [2, 4] * self.ureg.m) def test_argmax_numpy_func(self): - self.assertNDArrayEqual(pxp.argmax(self.q, axis=0), pxp.asarray([1, 1])) + self.assertNDArrayEqual(pxp.argmax(self.q, axis=0), xp.asarray([1, 1])) def test_maximum(self): helpers.assert_quantity_equal( @@ -322,7 +322,7 @@ def test_min_with_axis_arg(self): helpers.assert_quantity_equal(pxp.min(self.q, axis=1), [1, 3] * self.ureg.m) def test_argmin_numpy_func(self): - self.assertNDArrayEqual(pxp.argmin(self.q, axis=0), pxp.asarray([0, 0])) + self.assertNDArrayEqual(pxp.argmin(self.q, axis=0), xp.asarray([0, 0])) def test_minimum(self): helpers.assert_quantity_equal( @@ -363,10 +363,11 @@ def test_std_numpy_func(self): ) def test_conj(self): - helpers.assert_quantity_equal((self.q * (1 + 1j)).conj(), self.q * (1 - 1j)) - helpers.assert_quantity_equal( - (self.q * (1 + 1j)).conjugate(), self.q * (1 - 1j) - ) + arr = pxp.asarray(self.q, dtype = pxp.complex64) * (1 + 1j) + helpers.assert_quantity_equal(pxp.conj(arr), arr * (1 - 1j)) + # helpers.assert_quantity_equal( + # (self.q * (1 + 1j)).conjugate(), self.q * (1 - 1j) + # ) def test_getitem(self): with pytest.raises(IndexError): @@ -458,11 +459,11 @@ def test_meshgrid_numpy_func(self): helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) def test_comparisons(self): + # self.assertNDArrayEqual( + # pxp.asarray(self.q) > 2 * self.ureg.m, xp.asarray([[False, False], [True, True]]) + # ) self.assertNDArrayEqual( - self.q > 2 * self.ureg.m, pxp.asarray([[False, False], [True, True]]) - ) - self.assertNDArrayEqual( - self.q < 2 * self.ureg.m, pxp.asarray([[True, False], [False, False]]) + pxp.asarray(self.q) < 2 * self.ureg.m, xp.asarray([[True, False], [False, False]]) ) def test_where(self): From 0804790d165ea0c7f0742d0b7481cafd835145cc Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 11 Jan 2025 20:39:30 +0000 Subject: [PATCH 34/49] idk --- tests/test_array.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index c9a485e..d5f8ac7 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -13,25 +13,7 @@ pxp = pint_array.pint_namespace(xp) -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= - -<<<<<<< HEAD -# @helpers.requires_numpy ->>>>>>> 1ad9ca9 (style: pre-commit fixes) -======= - ->>>>>>> 8498256 (style: pre-commit fixes) -class TestNumpyMethods: -======= -class TestNumPyMethods: ->>>>>>> 573558d (fix xp-tests) -======= - class TestNumPyMethods: ->>>>>>> bda9eba697f92e4bfe41a3f3813e589da7048eba @classmethod def setup_class(cls): from pint import _DEFAULT_REGISTRY From 1a477bb508e6fdae695b2d7a9fdb36ab15d10b4b Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 11 Jan 2025 20:40:28 +0000 Subject: [PATCH 35/49] idk --- tests/test_array.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/test_array.py b/tests/test_array.py index d5f8ac7..013235d 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -295,11 +295,7 @@ def test_max_with_axis_arg(self): helpers.assert_quantity_equal(pxp.max(self.q, axis=1), [2, 4] * self.ureg.m) def test_argmax_numpy_func(self): -<<<<<<< HEAD self.assertNDArrayEqual(pxp.argmax(self.q, axis=0), xp.asarray([1, 1])) -======= - self.assertNDArrayEqual(pxp.argmax(self.q, axis=0), pxp.asarray([1, 1])) ->>>>>>> bda9eba697f92e4bfe41a3f3813e589da7048eba def test_maximum(self): helpers.assert_quantity_equal( @@ -313,11 +309,7 @@ def test_min_with_axis_arg(self): helpers.assert_quantity_equal(pxp.min(self.q, axis=1), [1, 3] * self.ureg.m) def test_argmin_numpy_func(self): -<<<<<<< HEAD self.assertNDArrayEqual(pxp.argmin(self.q, axis=0), xp.asarray([0, 0])) -======= - self.assertNDArrayEqual(pxp.argmin(self.q, axis=0), pxp.asarray([0, 0])) ->>>>>>> bda9eba697f92e4bfe41a3f3813e589da7048eba def test_minimum(self): helpers.assert_quantity_equal( @@ -458,14 +450,7 @@ def test_comparisons(self): # pxp.asarray(self.q) > 2 * self.ureg.m, xp.asarray([[False, False], [True, True]]) # ) self.assertNDArrayEqual( -<<<<<<< HEAD pxp.asarray(self.q) < 2 * self.ureg.m, xp.asarray([[True, False], [False, False]]) -======= - self.q > 2 * self.ureg.m, pxp.asarray([[False, False], [True, True]]) - ) - self.assertNDArrayEqual( - self.q < 2 * self.ureg.m, pxp.asarray([[True, False], [False, False]]) ->>>>>>> bda9eba697f92e4bfe41a3f3813e589da7048eba ) def test_where(self): From 97ad90de2f6bdffb979d57f769892dcef212d2b6 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sat, 11 Jan 2025 21:59:52 +0000 Subject: [PATCH 36/49] tests --- src/pint_array/__init__.py | 26 +++++++++++++------------ tests/test_array.py | 40 ++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 925f7ab..4b5618c 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -119,6 +119,9 @@ def __mul__(self, other): return NotImplemented return ArrayUnitQuantity(magnitude, units) + def __rmul__(self, other): + return self.__mul__(other) + ## Linear Algebra Methods ## def __matmul__(self, other): return mod.matmul(self, other) @@ -232,7 +235,6 @@ def method(self, other, name=name): "__lt__", "__ne__", "__or__", - "__truediv__", "__xor__", "__divmod__", "__floordiv__", @@ -241,22 +243,21 @@ def method(self, other, name=name): unitless_rbinary_names = [ "__radd__", "__rand__", - "__rdivmod__", - "__rfloordiv__", - "__rmod__", - "__rmul__", "__ror__", "__rpow__", - "__rrshift__", - "__rsub__", - "__rtruediv__", "__rxor__", ] for name in unitless_binary_names + unitless_rbinary_names: def method(self, other, name=name): units = self.units - magnitude_other = other.m_as(units) if hasattr(other, "units") else other + if name in ["__eq__", "__ne__", ]: + try: + magnitude_other = other.m_as(units) if hasattr(other, "units") else other + except DimensionalityError: + return xp.full_like(self.magnitude, False) + else: + magnitude_other = other.m_as(units) if hasattr(other, "units") else other magnitude = self._call_super_method(name, magnitude_other) # FIXME: correct units for op return magnitude @@ -498,7 +499,7 @@ def nonzero(x, /): x = asarray(x) magnitude = xp.asarray(x.magnitude, copy=True) res = xp.nonzero(magnitude) - return tuple(ArrayUnitQuantity(magnitude_i, None) for magnitude_i in res) + return res mod.nonzero = nonzero @@ -510,7 +511,7 @@ def searchsorted(x1, x2, /, *, side="left", sorter=None): magnitude_x2 = x2.m_as(x1.units) magnitude = xp.searchsorted(magnitude_x1, magnitude_x2, side=side) - return ArrayUnitQuantity(magnitude, None) + return magnitude mod.searchsorted = searchsorted @@ -784,6 +785,7 @@ def pow(x1, x2, /, *args, **kwargs): return ArrayUnitQuantity(magnitude, units) mod.pow = pow + setattr(ArrayUnitQuantity, "__pow__", pow) ## Indexing Functions def take(x, indices, /, **kwargs): @@ -827,7 +829,7 @@ def sort_fun(x, /, **kwargs): return sort_fun - sort_names = ["sort", "argsort"] + sort_names = ["sort"] for name in sort_names: setattr(mod, name, get_sort_fun(name)) diff --git a/tests/test_array.py b/tests/test_array.py index 013235d..492a574 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -205,29 +205,25 @@ def test_addition_with_incompatible_scalar(self): op.add(b, a) def test_power(self): - arr = pxp.asarray(range(3), dtype=pxp.float32) - q = self.Q_(arr, "meter") + arr = xp.asarray(range(3), dtype=pxp.float32) + q = pxp.asarray(range(3), dtype=pxp.float32, units="meter") for op_ in [pxp.pow]: - q_cp = copy.copy(q) with pytest.raises(DimensionalityError): - op_(2.0, q_cp) - arr_cp = copy.copy(arr) - q_cp = copy.copy(q) + op_(2.0, q) with pytest.raises(DimensionalityError): - op_(q_cp, arr_cp) - q_cp = copy.copy(q) - q2_cp = copy.copy(q) + op_(q, arr) with pytest.raises(DimensionalityError): - op_(q_cp, q2_cp) + op_(q, q) + q = pxp.asarray(self.q, dtype=pxp.float32) helpers.assert_quantity_equal( - pxp.pow(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") + pxp.pow(q, self.Q_(2.)), pxp.asarray([[1, 4], [9, 16]], dtype=pxp.float32, units= "m**2") ) helpers.assert_quantity_equal( - self.q ** self.Q_(2), self.Q_([[1, 4], [9, 16]], "m**2") + q ** self.Q_(2.), self.Q_([[1, 4], [9, 16]], "m**2") ) - self.assertNDArrayEqual(arr ** self.Q_(2), pxp.asarray([0, 1, 4])) + self.assertNDArrayEqual(arr ** self.Q_(2.), xp.asarray([0, 1, 4])) def test_sqrt(self): q = self.Q_(100.0, "m**2") @@ -264,13 +260,14 @@ def test_sort_numpy_func(self): def test_argsort_numpy_func(self): self.assertNDArrayEqual( - pxp.argsort(self.q, axis=0), pxp.asarray([[0, 0], [1, 1]]) + pxp.argsort(pxp.asarray(self.q), axis=0), xp.asarray([[0, 0], [1, 1]]) ) def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() - self.assertNDArrayEqual(pxp.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) + self.assertNDArrayEqual(pxp.searchsorted(q, pxp.asarray([1.5, 2.5], units="m")), xp.asarray([1, 2]) + ) def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m @@ -399,17 +396,18 @@ def test_setitem(self): def test_reversible_op(self): """ """ - x = self.q.magnitude - u = self.Q_(pxp.ones(x.shape)) - helpers.assert_quantity_equal(x / self.q, u * x / self.q) - helpers.assert_quantity_equal(x * self.q, u * x * self.q) + q=pxp.asarray(self.q, dtype=pxp.float64) + x = xp.asarray(self.q.magnitude, dtype=xp.float64) + u = pxp.asarray(pxp.ones(x.shape), dtype=pxp.float64) + helpers.assert_quantity_equal(x / q, u * x / q) + helpers.assert_quantity_equal(x * q, u * x * q) helpers.assert_quantity_equal(x + u, u + x) helpers.assert_quantity_equal(x - u, -(u - x)) def test_equal(self): x = self.q.magnitude u = pxp.ones(x.shape) - false = pxp.zeros_like(x, dtype=pxp.bool) + false = xp.zeros_like(x, dtype=xp.bool) helpers.assert_quantity_equal(u, u) helpers.assert_quantity_equal(u, u.magnitude) @@ -419,7 +417,7 @@ def test_equal(self): w = pxp.asarray((pxp.ones(x.shape)), units = "m") self.assertNDArrayEqual(v == 1, false) self.assertNDArrayEqual( - self.Q_(pxp.zeros_like(x), "m") == self.Q_(pxp.zeros_like(x), "s"), + pxp.asarray(pxp.zeros_like(x), units="m") == pxp.asarray(pxp.zeros_like(x), units="s") , false, ) self.assertNDArrayEqual(v == w, false) From 274dd2557e9f2c54bf369fcf0b0f1351702f1218 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 12 Jan 2025 11:00:20 +0000 Subject: [PATCH 37/49] operators --- src/pint_array/__init__.py | 96 ++++++++++++++++++---------- tests/test_array.py | 127 +++++++++++++++++++++---------------- 2 files changed, 135 insertions(+), 88 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 4b5618c..eab2e91 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -9,6 +9,7 @@ import contextlib import importlib import inspect +import operator import sys import textwrap import types @@ -108,20 +109,6 @@ def __repr__(self): f" '{self.units}'\n)>" ) - def __mul__(self, other): - if hasattr(other, "units"): - magnitude = self._call_super_method("__mul__", other.magnitude) - units = self.units * other.units - else: - magnitude = self._call_super_method("__mul__", other) - units = self.units - if magnitude is NotImplemented: - return NotImplemented - return ArrayUnitQuantity(magnitude, units) - - def __rmul__(self, other): - return self.__mul__(other) - ## Linear Algebra Methods ## def __matmul__(self, other): return mod.matmul(self, other) @@ -193,25 +180,25 @@ def fun(self, name=name): "__add__", "__sub__", "__and__", - "__mod__", + # "__mod__", # "__mul__", - # "__pow__", - "__truediv__", - "__divmod__", - "__floordiv__", + # # "__pow__", + # "__truediv__", + # "__divmod__", + # "__floordiv__", ] # Methods that return the result of an elementwise binary operation (reflected) rbinary_names = [ "__radd__", + "__rsub__", "__rand__", "__rdivmod__", "__rfloordiv__", "__rmod__", - "__rmul__", + # "__rmul__", "__ror__", # "__rpow__", - "__rsub__", - "__rtruediv__", + # "__rtruediv__", "__rxor__", ] for name in binary_names + rbinary_names: @@ -225,6 +212,43 @@ def method(self, other, name=name): setattr(ArrayQuantity, name, method) + calc_unit_names = [ + "__mul__", + "__rmul__", + "__truediv__", + "__rtruediv__", + "__floordiv__", + "__rfloordiv__", + "__mod__", + "__rmod__", + ] + for name in calc_unit_names: + + def method(self, other, name=name): + other = asarray(other) + op = getattr(operator, name) + if hasattr(other, "units"): + magnitude = self._call_super_method(name, other.magnitude) + units = op(self.units, other.units) + else: + magnitude = self._call_super_method(name, other) + units = self.units + if magnitude is NotImplemented: + return NotImplemented + return ArrayUnitQuantity(magnitude, units) + + def method(self, other, name=name): + other = asarray(other) + op = getattr(operator, name) + if hasattr(other, "units"): + magnitude = self._call_super_method(name, other.magnitude) + units = op(self.units, other.units) + else: + magnitude = self._call_super_method(name, other) + units = self.units + if magnitude is NotImplemented: + return NotImplemented + return ArrayUnitQuantity(magnitude, units) # Methods that return the result of an elementwise binary operation unitless_binary_names = [ @@ -251,16 +275,21 @@ def method(self, other, name=name): def method(self, other, name=name): units = self.units - if name in ["__eq__", "__ne__", ]: + if name in [ + "__eq__", + "__ne__", + ]: try: - magnitude_other = other.m_as(units) if hasattr(other, "units") else other + magnitude_other = ( + other.m_as(units) if hasattr(other, "units") else other + ) except DimensionalityError: return xp.full_like(self.magnitude, False) else: - magnitude_other = other.m_as(units) if hasattr(other, "units") else other - magnitude = self._call_super_method(name, magnitude_other) - # FIXME: correct units for op - return magnitude + magnitude_other = ( + other.m_as(units) if hasattr(other, "units") else other + ) + return self._call_super_method(name, magnitude_other) setattr(ArrayQuantity, name, method) @@ -490,16 +519,14 @@ def func(x, /, *args, func_str=func_str, **kwargs): x = asarray(x) magnitude = xp.asarray(x.magnitude, copy=True) xp_func = getattr(xp, func_str) - magnitude = xp_func(magnitude, *args, **kwargs) - return magnitude + return xp_func(magnitude, *args, **kwargs) setattr(mod, func_str, func) def nonzero(x, /): x = asarray(x) magnitude = xp.asarray(x.magnitude, copy=True) - res = xp.nonzero(magnitude) - return res + return xp.nonzero(magnitude) mod.nonzero = nonzero @@ -510,8 +537,7 @@ def searchsorted(x1, x2, /, *, side="left", sorter=None): magnitude_x1 = xp.asarray(x1.magnitude, copy=True) magnitude_x2 = x2.m_as(x1.units) - magnitude = xp.searchsorted(magnitude_x1, magnitude_x2, side=side) - return magnitude + return xp.searchsorted(magnitude_x1, magnitude_x2, side=side) mod.searchsorted = searchsorted @@ -785,7 +811,7 @@ def pow(x1, x2, /, *args, **kwargs): return ArrayUnitQuantity(magnitude, units) mod.pow = pow - setattr(ArrayUnitQuantity, "__pow__", pow) + ArrayUnitQuantity.__pow__ = pow ## Indexing Functions def take(x, indices, /, **kwargs): diff --git a/tests/test_array.py b/tests/test_array.py index 492a574..31d7aea 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -13,6 +13,7 @@ pxp = pint_array.pint_namespace(xp) + class TestNumPyMethods: @classmethod def setup_class(cls): @@ -44,7 +45,7 @@ def q_zero_or_nan(self): @property def q_temperature(self): - return pxp.asarray([[1, 2], [3, 4]], units = self.ureg.degC) + return pxp.asarray([[1, 2], [3, 4]], units=self.ureg.degC) def assertNDArrayEqual(self, actual, desired): # Assert that the given arrays are equal, and are not Quantities @@ -83,7 +84,7 @@ class TestNumPyArrayManipulation(TestNumPyMethods): def test_reshape(self): helpers.assert_quantity_equal( - pxp.reshape(self.q, [1, 4]), pxp.asarray([[1, 2, 3, 4]] ) * self.ureg.m + pxp.reshape(self.q, [1, 4]), pxp.asarray([[1, 2, 3, 4]]) * self.ureg.m ) # Transpose-like operations @@ -113,13 +114,14 @@ def test_broadcast_to(self): def test_expand_dims(self): helpers.assert_quantity_equal( - pxp.expand_dims(self.q, axis=0), pxp.asarray([[[1, 2], [3, 4]]]) * self.ureg.m + pxp.expand_dims(self.q, axis=0), + pxp.asarray([[[1, 2], [3, 4]]]) * self.ureg.m, ) def test_squeeze(self): helpers.assert_quantity_equal( - pxp.squeeze(pxp.asarray([[[0], [1], [2]]]) *self.ureg.m, axis=0), - pxp.asarray([0,1,2]) * self.ureg.m + pxp.squeeze(pxp.asarray([[[0], [1], [2]]]) * self.ureg.m, axis=0), + pxp.asarray([0, 1, 2]) * self.ureg.m, ) # Changing number of dimensions @@ -129,7 +131,7 @@ def test_concat_stack(self, subtests): for func in (pxp.concat, pxp.stack): with subtests.test(func=func): helpers.assert_quantity_equal( - func([self.q] * 2), pxp.asarray(func([self.q.m] * 2), units = "m") + func([self.q] * 2), pxp.asarray(func([self.q.m] * 2), units="m") ) # One or more of the args is a bare array full of zeros or NaNs # helpers.assert_quantity_equal( @@ -144,9 +146,9 @@ def test_concat_stack(self, subtests): func([nz.m, self.q]) def test_astype(self): - dtype=pxp.float32 + dtype = pxp.float32 actual = pxp.astype(self.q, dtype) - expected = pxp.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=dtype, units = "m") + expected = pxp.asarray([[1.0, 2.0], [3.0, 4.0]], dtype=dtype, units="m") helpers.assert_quantity_equal(actual, expected) assert actual.m.dtype == expected.m.dtype @@ -154,12 +156,12 @@ def test_item(self): helpers.assert_quantity_equal(self.Q_([[0]], "m").item(), 0 * self.ureg.m) def test_broadcast_arrays(self): - x = pxp.asarray([[1, 2, 3]], units= "m") - y = pxp.asarray([[4], [5]], units= "nm") + x = pxp.asarray([[1, 2, 3]], units="m") + y = pxp.asarray([[4], [5]], units="nm") result = pxp.broadcast_arrays(x, y) expected = ( - pxp.asarray([[1, 2, 3], [1, 2, 3]], units= "m"), - pxp.asarray([[4, 4, 4], [5, 5, 5]], units= "nm") + pxp.asarray([[1, 2, 3], [1, 2, 3]], units="m"), + pxp.asarray([[4, 4, 4], [5, 5, 5]], units="nm"), ) helpers.assert_quantity_equal(result, expected) @@ -187,13 +189,13 @@ def test_sum_numpy_func(self): # Arithmetic operations def test_addition_with_scalar(self): - a = pxp.asarray([0, 1, 2], dtype = pxp.float32) + a = pxp.asarray([0, 1, 2], dtype=pxp.float32) b = 10.0 * self.ureg("gram/kilogram") helpers.assert_quantity_almost_equal( - a + b, pxp.asarray([0.01, 1.01, 2.01], units = "") + a + b, pxp.asarray([0.01, 1.01, 2.01], units="") ) helpers.assert_quantity_almost_equal( - b + a, pxp.asarray([0.01, 1.01, 2.01], units = "") + b + a, pxp.asarray([0.01, 1.01, 2.01], units="") ) def test_addition_with_incompatible_scalar(self): @@ -206,7 +208,7 @@ def test_addition_with_incompatible_scalar(self): def test_power(self): arr = xp.asarray(range(3), dtype=pxp.float32) - q = pxp.asarray(range(3), dtype=pxp.float32, units="meter") + q = pxp.asarray(range(3), dtype=pxp.float32, units="meter") for op_ in [pxp.pow]: with pytest.raises(DimensionalityError): @@ -218,12 +220,13 @@ def test_power(self): q = pxp.asarray(self.q, dtype=pxp.float32) helpers.assert_quantity_equal( - pxp.pow(q, self.Q_(2.)), pxp.asarray([[1, 4], [9, 16]], dtype=pxp.float32, units= "m**2") + pxp.pow(q, self.Q_(2.0)), + pxp.asarray([[1, 4], [9, 16]], dtype=pxp.float32, units="m**2"), ) helpers.assert_quantity_equal( - q ** self.Q_(2.), self.Q_([[1, 4], [9, 16]], "m**2") + q ** self.Q_(2.0), self.Q_([[1, 4], [9, 16]], "m**2") ) - self.assertNDArrayEqual(arr ** self.Q_(2.), xp.asarray([0, 1, 4])) + self.assertNDArrayEqual(arr ** self.Q_(2.0), xp.asarray([0, 1, 4])) def test_sqrt(self): q = self.Q_(100.0, "m**2") @@ -266,7 +269,8 @@ def test_argsort_numpy_func(self): def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() - self.assertNDArrayEqual(pxp.searchsorted(q, pxp.asarray([1.5, 2.5], units="m")), xp.asarray([1, 2]) + self.assertNDArrayEqual( + pxp.searchsorted(q, pxp.asarray([1.5, 2.5], units="m")), xp.asarray([1, 2]) ) def test_nonzero_numpy_func(self): @@ -315,12 +319,16 @@ def test_minimum(self): def test_clip_numpy_func(self): helpers.assert_quantity_equal( - pxp.clip(pxp.asarray(self.q, dtype=pxp.float32), 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m + pxp.clip(pxp.asarray(self.q, dtype=pxp.float32), 150 * self.ureg.cm, None), + [[1.5, 2], [3, 4]] * self.ureg.m, ) def test_round_numpy_func(self): helpers.assert_quantity_equal( - pxp.round(102.75 * self.ureg.m, ), 103 * self.ureg.m + pxp.round( + 102.75 * self.ureg.m, + ), + 103 * self.ureg.m, ) def test_cumulative_sum(self): @@ -330,24 +338,31 @@ def test_cumulative_sum(self): def test_mean_numpy_func(self): assert pxp.mean(pxp.asarray(self.q, dtype=pxp.float32)) == 2.5 * self.ureg.m - assert pxp.mean(pxp.asarray(self.q_temperature, dtype=pxp.float32)) == self.Q_(2.5, self.ureg.degC) + assert pxp.mean(pxp.asarray(self.q_temperature, dtype=pxp.float32)) == self.Q_( + 2.5, self.ureg.degC + ) def test_var_numpy_func(self): - dtype=pxp.float32 + dtype = pxp.float32 assert pxp.var(pxp.asarray(self.q, dtype=dtype)) == 1.25 * self.ureg.m**2 - assert pxp.var(pxp.asarray(self.q_temperature, dtype=dtype)) == 1.25 * self.ureg.delta_degC**2 + assert ( + pxp.var(pxp.asarray(self.q_temperature, dtype=dtype)) + == 1.25 * self.ureg.delta_degC**2 + ) def test_std_numpy_func(self): - dtype=pxp.float32 + dtype = pxp.float32 helpers.assert_quantity_almost_equal( pxp.std(pxp.asarray(self.q, dtype=dtype)), 1.11803 * self.ureg.m, rtol=1e-5 ) helpers.assert_quantity_almost_equal( - pxp.std(pxp.asarray(self.q_temperature, dtype=dtype)), 1.11803 * self.ureg.delta_degC, rtol=1e-5 + pxp.std(pxp.asarray(self.q_temperature, dtype=dtype)), + 1.11803 * self.ureg.delta_degC, + rtol=1e-5, ) def test_conj(self): - arr = pxp.asarray(self.q, dtype = pxp.complex64) * (1 + 1j) + arr = pxp.asarray(self.q, dtype=pxp.complex64) * (1 + 1j) helpers.assert_quantity_equal(pxp.conj(arr), arr * (1 - 1j)) # helpers.assert_quantity_equal( # (self.q * (1 + 1j)).conjugate(), self.q * (1 - 1j) @@ -396,7 +411,7 @@ def test_setitem(self): def test_reversible_op(self): """ """ - q=pxp.asarray(self.q, dtype=pxp.float64) + q = pxp.asarray(self.q, dtype=pxp.float64) x = xp.asarray(self.q.magnitude, dtype=xp.float64) u = pxp.asarray(pxp.ones(x.shape), dtype=pxp.float64) helpers.assert_quantity_equal(x / q, u * x / q) @@ -413,11 +428,12 @@ def test_equal(self): helpers.assert_quantity_equal(u, u.magnitude) helpers.assert_quantity_equal(u == 1, u.magnitude == 1) - v = pxp.asarray((pxp.zeros(x.shape)), units = "m") - w = pxp.asarray((pxp.ones(x.shape)), units = "m") + v = pxp.asarray((pxp.zeros(x.shape)), units="m") + w = pxp.asarray((pxp.ones(x.shape)), units="m") self.assertNDArrayEqual(v == 1, false) self.assertNDArrayEqual( - pxp.asarray(pxp.zeros_like(x), units="m") == pxp.asarray(pxp.zeros_like(x), units="s") , + pxp.asarray(pxp.zeros_like(x), units="m") + == pxp.asarray(pxp.zeros_like(x), units="s"), false, ) self.assertNDArrayEqual(v == w, false) @@ -425,8 +441,8 @@ def test_equal(self): self.assertNDArrayEqual(u == v, false) def test_dtype(self): - dtype=pxp.uint32 - u = pxp.asarray([1, 2, 3], dtype=dtype) * self.ureg.m + dtype = pxp.uint32 + u = pxp.asarray([1, 2, 3], dtype=dtype) * self.ureg.m assert u.dtype == dtype @@ -445,10 +461,12 @@ def test_meshgrid_numpy_func(self): def test_comparisons(self): # self.assertNDArrayEqual( - # pxp.asarray(self.q) > 2 * self.ureg.m, xp.asarray([[False, False], [True, True]]) + # pxp.asarray(self.q) > 2 * self.ureg.m, + # xp.asarray([[False, False], [True, True]]) # ) self.assertNDArrayEqual( - pxp.asarray(self.q) < 2 * self.ureg.m, xp.asarray([[True, False], [False, False]]) + pxp.asarray(self.q) < 2 * self.ureg.m, + xp.asarray([[True, False], [False, False]]), ) def test_where(self): @@ -466,7 +484,7 @@ def test_where(self): [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(q_float >= 3 * self.ureg.m, 0., q_float), + pxp.where(q_float >= 3 * self.ureg.m, 0.0, q_float), [[1, 2], [0, 0]] * self.ureg.m, ) helpers.assert_quantity_equal( @@ -474,11 +492,15 @@ def test_where(self): [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(q_float >= 2 * self.ureg.m, q_float, pxp.asarray(pxp.nan)* self.ureg.m), + pxp.where( + q_float >= 2 * self.ureg.m, q_float, pxp.asarray(pxp.nan) * self.ureg.m + ), [[pxp.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( - pxp.where(q_float >= 3 * self.ureg.m, pxp.asarray(pxp.nan)* self.ureg.m, q_float), + pxp.where( + q_float >= 3 * self.ureg.m, pxp.asarray(pxp.nan) * self.ureg.m, q_float + ), [[1, 2], [pxp.nan, pxp.nan]] * self.ureg.m, ) with pytest.raises(DimensionalityError): @@ -489,8 +511,12 @@ def test_where(self): ) helpers.assert_quantity_equal( - pxp.where(pxp.asarray([-1., 0., 1.]) * self.ureg.m, pxp.asarray([1., 2., 1.]) * self.ureg.s, pxp.nan), - pxp.asarray([1., pxp.nan, 1.]) * self.ureg.s, + pxp.where( + pxp.asarray([-1.0, 0.0, 1.0]) * self.ureg.m, + pxp.asarray([1.0, 2.0, 1.0]) * self.ureg.s, + pxp.nan, + ), + pxp.asarray([1.0, pxp.nan, 1.0]) * self.ureg.s, ) with pytest.raises( ValueError, @@ -502,16 +528,11 @@ def test_where(self): def test_tile(self): helpers.assert_quantity_equal( - pxp.tile(pxp.asarray([1,2,3,4]) *self.ureg.m, (4,1)), - pxp.asarray([[1, 2, 3, 4], - [1, 2, 3, 4], - [1, 2, 3, 4], - [1, 2, 3, 4]]) * self.ureg.m - ) + pxp.tile(pxp.asarray([1, 2, 3, 4]) * self.ureg.m, (4, 1)), + pxp.asarray([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]) + * self.ureg.m, + ) helpers.assert_quantity_equal( - pxp.tile(pxp.asarray([[1, 2], [3, 4]]) *self.ureg.m, (2,1)), - pxp.asarray([[1, 2], - [3, 4], - [1, 2], - [3, 4]]) * self.ureg.m - ) \ No newline at end of file + pxp.tile(pxp.asarray([[1, 2], [3, 4]]) * self.ureg.m, (2, 1)), + pxp.asarray([[1, 2], [3, 4], [1, 2], [3, 4]]) * self.ureg.m, + ) From 10c204248b98aba641366b998c7394e32c734b85 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 12 Jan 2025 11:36:58 +0000 Subject: [PATCH 38/49] set/get item --- src/pint_array/__init__.py | 10 +++++++--- tests/test_array.py | 10 +++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index eab2e91..e4f7374 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -93,9 +93,13 @@ def __getitem__(self, key): def __setitem__(self, key, other): key = self._validate_key(key) - magnitude_other = ( - other.m_as(self.units) if hasattr(other, "units") else other - ) + if hasattr(other, "units"): + magnitude_other = other.m_as(self.units) + elif self.units.dimensionless: + magnitude_other = other + else: + other_units = "dimensionless" + raise DimensionalityError(other_units, self.units) return self.magnitude.__setitem__(key, magnitude_other) def __iter__(self): diff --git a/tests/test_array.py b/tests/test_array.py index 31d7aea..5ae1308 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -371,7 +371,7 @@ def test_conj(self): def test_getitem(self): with pytest.raises(IndexError): self.q.__getitem__((0, 10)) - helpers.assert_quantity_equal(self.q[0], [1, 2] * self.ureg.m) + helpers.assert_quantity_equal(self.q[0, :], [1, 2] * self.ureg.m) assert self.q[1, 1] == 4 * self.ureg.m def test_setitem(self): @@ -387,7 +387,7 @@ def test_setitem(self): self.q[0] = 1 * self.ureg.J q = self.q.copy() - q[0] = 1 * self.ureg.m + q[0, :] = 1 * self.ureg.m helpers.assert_quantity_equal(q, [[1, 1], [3, 4]] * self.ureg.m) q = self.q.copy() @@ -395,14 +395,14 @@ def test_setitem(self): helpers.assert_quantity_equal(q, [[1, 1], [1, 1]] * self.ureg.m) q = self.q.copy() - q[:] = 1 * self.ureg.m + q[:, :] = 1 * self.ureg.m helpers.assert_quantity_equal(q, [[1, 1], [1, 1]] * self.ureg.m) # check and see that dimensionless numbers work correctly - q = [0, 1, 2, 3] * self.ureg.dimensionless + q = pxp.asarray([0, 1, 2, 3], units=self.ureg.dimensionless) q[0] = 1 helpers.assert_quantity_equal(q, pxp.asarray([1, 1, 2, 3])) - q[0] = self.ureg.m / self.ureg.mm + q[0] = 1 * self.ureg.m / self.ureg.mm helpers.assert_quantity_equal(q, pxp.asarray([1000, 1, 2, 3])) q = [0.0, 1.0, 2.0, 3.0] * self.ureg.m / self.ureg.mm From 5431cc9358b15f6abd30fe2ddd43049289ab6f6f Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 12 Jan 2025 13:01:22 +0000 Subject: [PATCH 39/49] xfails --- src/pint_array/__init__.py | 1 + xp-tests-xfails.txt | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index e4f7374..e99fdd5 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -536,6 +536,7 @@ def nonzero(x, /): def searchsorted(x1, x2, /, *, side="left", sorter=None): if sorter is not None: + x1 = asarray(x1) x1 = take(x1, sorter) magnitude_x1 = xp.asarray(x1.magnitude, copy=True) diff --git a/xp-tests-xfails.txt b/xp-tests-xfails.txt index 6369ae4..22e7bc1 100644 --- a/xp-tests-xfails.txt +++ b/xp-tests-xfails.txt @@ -36,7 +36,13 @@ array_api_tests/test_has_names.py::test_has_names[linalg-tensordot] array_api_tests/test_has_names.py::test_has_names[linalg-trace] array_api_tests/test_has_names.py::test_has_names[linalg-vecdot] array_api_tests/test_has_names.py::test_has_names[linalg-vector_norm] +array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_left_shift[__lshift__(x1, x2)] +array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_left_shift[__lshift__(x, s)] +array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_shift[__rshift__(x1, x2)] +array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_shift[__rshift__(x, s)] # flaky on macos array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt # `pow` with array x2 is only defined when all elements of x2 are equal array_api_tests/test_operators_and_elementwise_functions.py::test_pow[pow(x1, x2)] +array_api_tests/test_operators_and_elementwise_functions.py::test_pow[__pow__(x1, x2)] +array_api_tests/test_operators_and_elementwise_functions.py::test_pow[__pow__(x, s)] From 83997b70ceb07ab881892162f7d7dccd6897e3fd Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 12 Jan 2025 13:06:29 +0000 Subject: [PATCH 40/49] take --- src/pint_array/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index e99fdd5..43ca353 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -536,7 +536,6 @@ def nonzero(x, /): def searchsorted(x1, x2, /, *, side="left", sorter=None): if sorter is not None: - x1 = asarray(x1) x1 = take(x1, sorter) magnitude_x1 = xp.asarray(x1.magnitude, copy=True) @@ -820,6 +819,7 @@ def pow(x1, x2, /, *args, **kwargs): ## Indexing Functions def take(x, indices, /, **kwargs): + x = asarray(x) magnitude = xp.take(x.magnitude, indices.magnitude, **kwargs) return ArrayUnitQuantity(magnitude, x.units) From 74f7b6bde6427d45f7272965abc22c24280fe05c Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 12 Jan 2025 13:30:39 +0000 Subject: [PATCH 41/49] take --- src/pint_array/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 43ca353..6745146 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -820,7 +820,7 @@ def pow(x1, x2, /, *args, **kwargs): ## Indexing Functions def take(x, indices, /, **kwargs): x = asarray(x) - magnitude = xp.take(x.magnitude, indices.magnitude, **kwargs) + magnitude = xp.take(x.magnitude, indices, **kwargs) return ArrayUnitQuantity(magnitude, x.units) mod.take = take From 1a3bddbf25d288c930e047b66f51ad38b520a5d7 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 12 Jan 2025 19:44:43 +0000 Subject: [PATCH 42/49] review fixes --- src/pint_array/__init__.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 6745146..c1e0da3 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -241,19 +241,6 @@ def method(self, other, name=name): return NotImplemented return ArrayUnitQuantity(magnitude, units) - def method(self, other, name=name): - other = asarray(other) - op = getattr(operator, name) - if hasattr(other, "units"): - magnitude = self._call_super_method(name, other.magnitude) - units = op(self.units, other.units) - else: - magnitude = self._call_super_method(name, other) - units = self.units - if magnitude is NotImplemented: - return NotImplemented - return ArrayUnitQuantity(magnitude, units) - # Methods that return the result of an elementwise binary operation unitless_binary_names = [ "__eq__", @@ -361,9 +348,8 @@ def fun(*args, func_str=func_str, units=None, **kwargs): setattr(mod, func_str, fun) ## Manipulation Functions ## - first_arg_arrays = {"broadcast_arrays", "concat", "stack", "meshgrid"} - output_arrays = {"broadcast_arrays", "unstack", "meshgrid"} - arbitrary_num_arrays = {"broadcast_arrays", "meshgrid"} + first_arg_arrays = {"concat", "stack"} + output_arrays = {"unstack"} def get_manip_fun(func_str): def manip_fun(x, *args, **kwargs): @@ -395,11 +381,7 @@ def manip_fun(x, *args, **kwargs): ): args[0] = repeats.magnitude - if func_str in arbitrary_num_arrays and not one_array: - args = [asarray(arg, units=x[0].units).magnitude for arg in args] - magnitude = xp_func(*magnitude, *args, **kwargs) - else: - magnitude = xp_func(magnitude, *args, **kwargs) + magnitude = xp_func(magnitude, *args, **kwargs) if func_str in output_arrays: return tuple( @@ -411,7 +393,6 @@ def manip_fun(x, *args, **kwargs): creation_manip_functions = ["tril", "triu"] manip_names = [ - "broadcast_arrays", "broadcast_to", "concat", "expand_dims", @@ -430,7 +411,7 @@ def manip_fun(x, *args, **kwargs): setattr(mod, name, get_manip_fun(name)) def _meshgrid(*xi, **kwargs): - # Simply need to map input units to onto list of outputs + # Simply need to map input units onto list of outputs input_units = (x.units for x in xi) res = xp.meshgrid(*(x.magnitude for x in xi), **kwargs) return [out * unit for out, unit in zip(res, input_units, strict=False)] From a7ff500eabd97221316958c261a94c310dbececf Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 12 Jan 2025 19:45:58 +0000 Subject: [PATCH 43/49] take --- src/pint_array/__init__.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index c1e0da3..d81f3ec 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -693,16 +693,15 @@ def fun(x, /, *args, func_str=func_str, **kwargs): setattr(mod, func_str, fun) - for func_str in ["sqrt"]: - def fun(x, /, *args, func_str=func_str, **kwargs): - x = asarray(x) - magnitude = xp.asarray(x.magnitude, copy=True) - magnitude = getattr(xp, func_str)(magnitude, *args, **kwargs) - return ArrayUnitQuantity(magnitude, x.units**0.5) - - setattr(mod, func_str, fun) + def _sqrt(x, /, *args, **kwargs): + x = asarray(x) + magnitude = xp.asarray(x.magnitude, copy=True) + magnitude = getattr(xp, "sqrt")(magnitude, *args, **kwargs) + return ArrayUnitQuantity(magnitude, x.units**0.5) + mod.sqrt = _sqrt + elementwise_two_arrays = [ "add", "atan2", From 314e2c600007e2049e6412a231ed4d0e5f082238 Mon Sep 17 00:00:00 2001 From: Andrew <andrewgsavage@gmail.com> Date: Sun, 12 Jan 2025 19:48:05 +0000 Subject: [PATCH 44/49] sort --- src/pint_array/__init__.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index d81f3ec..fc3a14f 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -701,7 +701,7 @@ def _sqrt(x, /, *args, **kwargs): return ArrayUnitQuantity(magnitude, x.units**0.5) mod.sqrt = _sqrt - + elementwise_two_arrays = [ "add", "atan2", @@ -829,20 +829,15 @@ def matrix_transpose(x): mod.matrix_transpose = matrix_transpose ## Sorting Functions ## - def get_sort_fun(func_str): - def sort_fun(x, /, **kwargs): - x = asarray(x) - magnitude = xp.asarray(x.magnitude, copy=True) - xp_func = getattr(xp, func_str) - magnitude = xp_func(magnitude, **kwargs) - units = x.units if func_str == "sort" else None - return ArrayUnitQuantity(magnitude, units) - - return sort_fun + def _sort(x, /, **kwargs): + x = asarray(x) + magnitude = xp.asarray(x.magnitude, copy=True) + xp_func = getattr(xp, "sort") + magnitude = xp_func(magnitude, **kwargs) + units = x.units + return ArrayUnitQuantity(magnitude, units) - sort_names = ["sort"] - for name in sort_names: - setattr(mod, name, get_sort_fun(name)) + mod.sort = _sort ## Set Functions ## def get_set_fun(func_str): From 18638d60045ce02341f545f0bbd205fd7f98ba2a Mon Sep 17 00:00:00 2001 From: Andrew Savage <andrewgsavage@gmail.com> Date: Mon, 13 Jan 2025 20:51:03 +0000 Subject: [PATCH 45/49] power --- src/pint_array/__init__.py | 9 ++++++--- xp-tests-xfails.txt | 6 +----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index fc3a14f..8a864be 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -780,16 +780,19 @@ def pow(x1, x2, /, *args, **kwargs): units = x1.units ** as_scalar(x2_magnitude) else: x2_first_elem_magnitude = x2[(0,) * x2.ndim] - if not xp.all(x2_magnitude == x2_first_elem_magnitude): + if x1.unitless: + units = "dimensionless" + elif not xp.all(x2_magnitude == x2_first_elem_magnitude): extra_msg = ( - "The exponent must be a scalar or an array of all the same value." + "The first array must be unitless, or the exponent must be a scalar or an array of all the same value." ) raise DimensionalityError( x2.units, "dimensionless", extra_msg=extra_msg, ) - units = x1.units ** as_scalar(x2_first_elem_magnitude) + else: + units = x1.units ** as_scalar(x2_first_elem_magnitude) magnitude = xp.pow(x1.magnitude, x2_magnitude, *args, **kwargs) return ArrayUnitQuantity(magnitude, units) diff --git a/xp-tests-xfails.txt b/xp-tests-xfails.txt index 22e7bc1..abe3124 100644 --- a/xp-tests-xfails.txt +++ b/xp-tests-xfails.txt @@ -41,8 +41,4 @@ array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_left_s array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_shift[__rshift__(x1, x2)] array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_shift[__rshift__(x, s)] # flaky on macos -array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt -# `pow` with array x2 is only defined when all elements of x2 are equal -array_api_tests/test_operators_and_elementwise_functions.py::test_pow[pow(x1, x2)] -array_api_tests/test_operators_and_elementwise_functions.py::test_pow[__pow__(x1, x2)] -array_api_tests/test_operators_and_elementwise_functions.py::test_pow[__pow__(x, s)] +array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt \ No newline at end of file From bcc5b69ababf78273468447a0527580cadc0b821 Mon Sep 17 00:00:00 2001 From: Andrew Savage <andrewgsavage@gmail.com> Date: Mon, 13 Jan 2025 20:53:25 +0000 Subject: [PATCH 46/49] power --- xp-tests-xfails.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xp-tests-xfails.txt b/xp-tests-xfails.txt index abe3124..1dccbbf 100644 --- a/xp-tests-xfails.txt +++ b/xp-tests-xfails.txt @@ -41,4 +41,6 @@ array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_left_s array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_shift[__rshift__(x1, x2)] array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_shift[__rshift__(x, s)] # flaky on macos -array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt \ No newline at end of file +array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt +# `pow` with array x2 is only defined when all elements of x2 are equal +#array_api_tests/test_operators_and_elementwise_functions.py::test_pow[__pow__(x, s)] From 898ce8474aa447d5f897a047e2360542a8d66e49 Mon Sep 17 00:00:00 2001 From: Andrew Savage <andrewgsavage@gmail.com> Date: Mon, 13 Jan 2025 20:58:47 +0000 Subject: [PATCH 47/49] power --- xp-tests-xfails.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xp-tests-xfails.txt b/xp-tests-xfails.txt index 1dccbbf..95d6393 100644 --- a/xp-tests-xfails.txt +++ b/xp-tests-xfails.txt @@ -43,4 +43,4 @@ array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_ # flaky on macos array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt # `pow` with array x2 is only defined when all elements of x2 are equal -#array_api_tests/test_operators_and_elementwise_functions.py::test_pow[__pow__(x, s)] +array_api_tests/test_operators_and_elementwise_functions.py::test_pow[__pow__(x, s)] From 072e40d3ccbdc6807aa3d2d7a7fb11b049dccdd9 Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Sun, 19 Jan 2025 12:52:38 +0000 Subject: [PATCH 48/49] bring back lshift, rshift --- src/pint_array/__init__.py | 2 ++ xp-tests-xfails.txt | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pint_array/__init__.py b/src/pint_array/__init__.py index 8a864be..31cf912 100644 --- a/src/pint_array/__init__.py +++ b/src/pint_array/__init__.py @@ -184,6 +184,8 @@ def fun(self, name=name): "__add__", "__sub__", "__and__", + "__lshift__", + "__rshift__", # "__mod__", # "__mul__", # # "__pow__", diff --git a/xp-tests-xfails.txt b/xp-tests-xfails.txt index 95d6393..31d1632 100644 --- a/xp-tests-xfails.txt +++ b/xp-tests-xfails.txt @@ -36,11 +36,7 @@ array_api_tests/test_has_names.py::test_has_names[linalg-tensordot] array_api_tests/test_has_names.py::test_has_names[linalg-trace] array_api_tests/test_has_names.py::test_has_names[linalg-vecdot] array_api_tests/test_has_names.py::test_has_names[linalg-vector_norm] -array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_left_shift[__lshift__(x1, x2)] -array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_left_shift[__lshift__(x, s)] -array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_shift[__rshift__(x1, x2)] -array_api_tests/test_operators_and_elementwise_functions.py::test_bitwise_right_shift[__rshift__(x, s)] # flaky on macos array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt -# `pow` with array x2 is only defined when all elements of x2 are equal +# dtype behaviour is not robust array_api_tests/test_operators_and_elementwise_functions.py::test_pow[__pow__(x, s)] From 4714160c5c7fc0741a320253c9023afe0ee1be81 Mon Sep 17 00:00:00 2001 From: Lucas Colley <lucas.colley8@gmail.com> Date: Sun, 19 Jan 2025 13:09:00 +0000 Subject: [PATCH 49/49] clean up tests --- pixi.lock | 332 ++++++++++++++++++++++---------------------- tests/test_array.py | 33 ++--- 2 files changed, 177 insertions(+), 188 deletions(-) diff --git a/pixi.lock b/pixi.lock index 523ac68..1e065e6 100644 --- a/pixi.lock +++ b/pixi.lock @@ -19,7 +19,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda @@ -34,12 +34,12 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.48.0-hee588c1_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_2.conda - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py310ha75aee5_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py310h5851e9f_0.conda - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda @@ -54,13 +54,13 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/python-3.10.16-he725a3c_1_cpython.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.10-5_cp310.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8228510_1.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - pypi: . osx-arm64: - conda: https://prefix.dev/conda-forge/noarch/array-api-compat-1.10.0-pyhd8ed1ab_0.conda @@ -73,21 +73,21 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libblas-3.9.0-26_osxarm64_openblas.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcblas-3.9.0-26_osxarm64_openblas.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.6-ha82da77_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://prefix.dev/conda-forge/osx-arm64/libgfortran-5.0.0-13_2_0_hd922786_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libgfortran5-13.2.0-hf226fd6_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/liblapack-3.9.0-26_osxarm64_openblas.conda - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.3-h39f12f2_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libopenblas-0.3.28-openmp_hf332438_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.47.2-h3f77e49_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.48.0-h3f77e49_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.6-hdb05f8b_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.7-hdb05f8b_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_2.conda - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py310ha1ddda0_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda @@ -102,13 +102,13 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.10.16-h870587a_1_cpython.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.10-5_cp310.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - pypi: . win-64: - conda: https://prefix.dev/conda-forge/noarch/array-api-compat-1.10.0-pyhd8ed1ab_0.conda @@ -121,7 +121,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda - conda: https://prefix.dev/conda-forge/win-64/libblas-3.9.0-26_win64_mkl.conda @@ -131,8 +131,8 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libiconv-1.17-hcfcfb64_2.conda - conda: https://prefix.dev/conda-forge/win-64/liblapack-3.9.0-26_win64_mkl.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.3-h2466b09_1.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.47.2-h67fdade_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_8.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.48.0-h67fdade_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - conda: https://prefix.dev/conda-forge/win-64/libxml2-2.13.5-he286e8c_1.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda @@ -149,14 +149,14 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.10.16-h37870fc_1_cpython.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.10-5_cp310.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/win-64/tbb-2021.13.0-h62715c5_1.conda - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-ha32ba9b_23.conda - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.42.34433-he29a5d6_23.conda @@ -181,7 +181,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda @@ -197,11 +197,11 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-h4bc722e_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.48.0-hee588c1_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_2.conda - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py313h536fd9c_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda @@ -213,16 +213,16 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_105_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8228510_1.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - pypi: . osx-arm64: - conda: https://prefix.dev/conda-forge/noarch/array-api-compat-1.10.0-pyhd8ed1ab_0.conda @@ -235,11 +235,11 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libblas-3.9.0-26_osxarm64_openblas.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcblas-3.9.0-26_osxarm64_openblas.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.6-ha82da77_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.6.4-h286801f_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://prefix.dev/conda-forge/osx-arm64/libgfortran-5.0.0-13_2_0_hd922786_3.conda @@ -248,10 +248,10 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.3-h39f12f2_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h99b78c6_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libopenblas-0.3.28-openmp_hf332438_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.47.2-h3f77e49_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.48.0-h3f77e49_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.6-hdb05f8b_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.7-hdb05f8b_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_2.conda - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda @@ -263,16 +263,16 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_105_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - pypi: . win-64: - conda: https://prefix.dev/conda-forge/noarch/array-api-compat-1.10.0-pyhd8ed1ab_0.conda @@ -285,7 +285,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda - conda: https://prefix.dev/conda-forge/win-64/libblas-3.9.0-26_win64_mkl.conda @@ -297,8 +297,8 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/liblapack-3.9.0-26_win64_mkl.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.3-h2466b09_1.conda - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.47.2-h67fdade_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_8.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.48.0-h67fdade_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - conda: https://prefix.dev/conda-forge/win-64/libxml2-2.13.5-he286e8c_1.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda @@ -313,16 +313,16 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_105_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.13-5_cp313.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/win-64/tbb-2021.13.0-h62715c5_1.conda - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-ha32ba9b_23.conda - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.42.34433-he29a5d6_23.conda @@ -357,22 +357,22 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-h4bc722e_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.48.0-hee588c1_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_2.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_105_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8228510_1.conda - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - pypi: . osx-arm64: - conda: https://prefix.dev/conda-forge/noarch/array-api-compat-1.10.0-pyhd8ed1ab_0.conda @@ -383,7 +383,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libblas-3.9.0-26_osxarm64_openblas.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcblas-3.9.0-26_osxarm64_openblas.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.6-ha82da77_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.6.4-h286801f_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://prefix.dev/conda-forge/osx-arm64/libgfortran-5.0.0-13_2_0_hd922786_3.conda @@ -392,21 +392,21 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.3-h39f12f2_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h99b78c6_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libopenblas-0.3.28-openmp_hf332438_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.47.2-h3f77e49_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.48.0-h3f77e49_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.6-hdb05f8b_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.7-hdb05f8b_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.0-h81ee809_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_105_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - pypi: . win-64: - conda: https://prefix.dev/conda-forge/noarch/array-api-compat-1.10.0-pyhd8ed1ab_0.conda @@ -425,8 +425,8 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/liblapack-3.9.0-26_win64_mkl.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.3-h2466b09_1.conda - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.47.2-h67fdade_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_8.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.48.0-h67fdade_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - conda: https://prefix.dev/conda-forge/win-64/libxml2-2.13.5-he286e8c_1.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda @@ -434,13 +434,13 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.0-ha4e3fda_1.conda - conda: https://prefix.dev/conda-forge/noarch/pint-0.24.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_105_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/tbb-2021.13.0-h62715c5_1.conda - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-ha32ba9b_23.conda - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.42.34433-he29a5d6_23.conda @@ -472,7 +472,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/ipython-8.31.0-pyh707e725_0.conda @@ -491,12 +491,12 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-h4bc722e_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.48.0-hee588c1_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://prefix.dev/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_2.conda - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py313h536fd9c_1.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/numpy-2.2.1-py313hb30382a_0.conda @@ -513,16 +513,16 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://prefix.dev/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda + - conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_105_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/linux-64/pyyaml-6.0.2-py313h536fd9c_1.conda - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8228510_1.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda @@ -530,9 +530,9 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/linux-64/ukkonen-1.0.1-py313h33d0bda_5.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.29.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - pypi: . @@ -554,14 +554,14 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/ipython-8.31.0-pyh707e725_0.conda - conda: https://prefix.dev/conda-forge/noarch/jedi-0.19.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libblas-3.9.0-26_osxarm64_openblas.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcblas-3.9.0-26_osxarm64_openblas.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.6-ha82da77_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libexpat-2.6.4-h286801f_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://prefix.dev/conda-forge/osx-arm64/libgfortran-5.0.0-13_2_0_hd922786_3.conda @@ -570,11 +570,11 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.3-h39f12f2_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h99b78c6_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libopenblas-0.3.28-openmp_hf332438_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.47.2-h3f77e49_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.48.0-h3f77e49_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.6-hdb05f8b_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.7-hdb05f8b_0.conda - conda: https://prefix.dev/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_2.conda - conda: https://prefix.dev/conda-forge/noarch/ndindex-1.8-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/numpy-2.2.1-py313ha4a2180_0.conda @@ -591,16 +591,16 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://prefix.dev/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_105_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pyyaml-6.0.2-py313h20a7fcf_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda @@ -608,9 +608,9 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ukkonen-1.0.1-py313hf9c7212_5.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.29.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 - pypi: . @@ -632,7 +632,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexcache-0.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/flexparser-0.4-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda @@ -647,8 +647,8 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/liblapack-3.9.0-26_win64_mkl.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.3-h2466b09_1.conda - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.47.2-h67fdade_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_8.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.48.0-h67fdade_0.conda + - conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda - conda: https://prefix.dev/conda-forge/win-64/libxml2-2.13.5-he286e8c_1.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda @@ -667,15 +667,15 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/prompt-toolkit-3.0.48-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://prefix.dev/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-json-report-1.5.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/pytest-metadata-3.1.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/pytest-subtests-0.14.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda + - conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_105_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/python_abi-3.13-5_cp313.conda - conda: https://prefix.dev/conda-forge/win-64/pyyaml-6.0.2-py313ha7868ed_1.conda - - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda + - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/tbb-2021.13.0-h62715c5_1.conda @@ -684,12 +684,12 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda + - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - conda: https://prefix.dev/conda-forge/win-64/ukkonen-1.0.1-py313h1ec8472_5.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-ha32ba9b_23.conda - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.42.34433-he29a5d6_23.conda - - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.29.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/vs2015_runtime-14.42.34433-hdffcdeb_23.conda - conda: https://prefix.dev/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2 @@ -990,9 +990,9 @@ packages: - pkg:pypi/flexparser?source=hash-mapping size: 28686 timestamp: 1733663636245 -- conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.123.2-pyha770c72_0.conda - sha256: cb9584d6b69edef23a4e5fedde8f77f12dec870b71d1ff4051b562b182697468 - md5: a3f74b10f4f6be84de2aeedd8b4da745 +- conda: https://prefix.dev/conda-forge/noarch/hypothesis-6.124.1-pyha770c72_0.conda + sha256: 5ae4e5ddd779db2f453877f7dc5c941a4c6f1a0620f045920ba525597a2579a7 + md5: d05625ce1c3445c4c21a778036173107 depends: - attrs >=22.2.0 - click >=7.0 @@ -1004,8 +1004,8 @@ packages: license_family: MOZILLA purls: - pkg:pypi/hypothesis?source=hash-mapping - size: 341990 - timestamp: 1735341651189 + size: 344304 + timestamp: 1737209246416 - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.5-pyhd8ed1ab_0.conda sha256: e8ea11b8e39a98a9c34efb5c21c3fca718e31e1f41fd9ae5f6918b8eb402da59 md5: c1b0f663ff141265d1be1242259063f0 @@ -1201,16 +1201,16 @@ packages: purls: [] size: 3732146 timestamp: 1734432785653 -- conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.6-ha82da77_1.conda - sha256: 2b2443404503cd862385fd2f2a2c73f9624686fd1e5a45050b4034cfc06904ec - md5: ce5252d8db110cdb4ae4173d0a63c7c5 +- conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda + sha256: 776092346da87a2a23502e14d91eb0c32699c4a1522b7331537bd1c3751dcff5 + md5: 5b3e1610ff8bd5443476b91d618f5b77 depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache purls: [] - size: 520992 - timestamp: 1734494699681 + size: 523505 + timestamp: 1736877862502 - conda: https://prefix.dev/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda sha256: 56541b98447b58e52d824bd59d6382d609e11de1f8adf20b23143e353d2b8d26 md5: db833e03127376d461e1e13e76f09b6c @@ -1532,38 +1532,38 @@ packages: purls: [] size: 4165774 timestamp: 1730772154295 -- conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - sha256: 48af21ebc2cbf358976f1e0f4a0ab9e91dfc83d0ef337cf3837c6f5bc22fb352 - md5: b58da17db24b6e08bcbf8fed2fb8c915 +- conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.48.0-hee588c1_0.conda + sha256: 7bb84f44e1bd756da4a3d0d43308324a5533e6ba9f4772475884bce44d405064 + md5: 84bd1c9a82b455e7a2f390375fb38f90 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libzlib >=1.3.1,<2.0a0 license: Unlicense purls: [] - size: 873551 - timestamp: 1733761824646 -- conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.47.2-h3f77e49_0.conda - sha256: f192f3c8973de9ec4c214990715f13b781965247a5cedf9162e7f9e699cfc3c4 - md5: 122d6f29470f1a991e85608e77e56a8a + size: 876582 + timestamp: 1737123945341 +- conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.48.0-h3f77e49_0.conda + sha256: b31169cf0ca7b6835baca4ab92d6cf2eee83b1a12a11b72f39521e8baf4d6acb + md5: 714719df4f49e30f9728956f240846ca depends: - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: Unlicense purls: [] - size: 850553 - timestamp: 1733762057506 -- conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.47.2-h67fdade_0.conda - sha256: ecfc0182c3b2e63c870581be1fa0e4dbdfec70d2011cb4f5bde416ece26c41df - md5: ff00095330e0d35a16bd3bdbd1a2d3e7 + size: 853163 + timestamp: 1737124192432 +- conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.48.0-h67fdade_0.conda + sha256: 2868c0df07b6d0682c9f3709523b6f3f3577f18e0d6f0e31022b48e6d0059f74 + md5: f4268a291ae1f885d4b96add05865cc8 depends: - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: Unlicense purls: [] - size: 891292 - timestamp: 1733762116902 + size: 897200 + timestamp: 1737124291192 - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda sha256: 4661af0eb9bdcbb5fb33e5d0023b001ad4be828fccdcc56500059d56f9869462 md5: 234a5554c53625688d51062645337328 @@ -1584,9 +1584,9 @@ packages: purls: [] size: 33601 timestamp: 1680112270483 -- conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_8.conda - sha256: 6d5e158813ab8d553fbb0fedd0abe7bf92970b0be3a9ddf12da0f6cbad78f506 - md5: 03cccbba200ee0523bde1f3dad60b1f3 +- conda: https://prefix.dev/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_9.conda + sha256: 373f2973b8a358528b22be5e8d84322c165b4c5577d24d94fd67ad1bb0a0f261 + md5: 08bfa5da6e242025304b206d152479ef depends: - ucrt constrains: @@ -1594,8 +1594,8 @@ packages: - msys2-conda-epoch <0.0a0 license: MIT AND BSD-3-Clause-Clear purls: [] - size: 35433 - timestamp: 1724681489463 + size: 35794 + timestamp: 1737099561703 - conda: https://prefix.dev/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c md5: 5aa797f8787fe7a17d1b0821485b5adc @@ -1658,18 +1658,18 @@ packages: purls: [] size: 55476 timestamp: 1727963768015 -- conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.6-hdb05f8b_0.conda - sha256: a0f3e9139ab16f0a67b9d2bbabc15b78977168f4a5b5503fed4962dcb9a96102 - md5: 34fdeffa0555a1a56f38839415cc066c +- conda: https://prefix.dev/conda-forge/osx-arm64/llvm-openmp-19.1.7-hdb05f8b_0.conda + sha256: b92a669f2059874ebdcb69041b6c243d68ffc3fb356ac1339cec44aeb27245d7 + md5: c4d54bfd3817313ce758aa76283b118d depends: - __osx >=11.0 constrains: - - openmp 19.1.6|19.1.6.* + - openmp 19.1.7|19.1.7.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 281251 - timestamp: 1734520462311 + size: 280830 + timestamp: 1736986295869 - conda: https://prefix.dev/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda sha256: 69b7dc7131703d3d60da9b0faa6dd8acbf6f6c396224cf6aef3e855b8c0c41c6 md5: af6ab708897df59bd6e7283ceab1b56b @@ -1693,25 +1693,25 @@ packages: purls: [] size: 103106385 timestamp: 1730232843711 -- conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - sha256: 6a1d5d8634c1a07913f1c525db6455918cbc589d745fac46d9d6e30340c8731a - md5: 70caf8bb6cf39a0b6b7efc885f51c0fe +- conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_2.conda + sha256: 17fe6afd8a00446010220d52256bd222b1e4fcb93bd587e7784b03219f3dc358 + md5: 04b34b9a40cdc48cfdab261ab176ff74 depends: - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 + - libgcc >=13 license: X11 AND BSD-3-Clause purls: [] - size: 889086 - timestamp: 1724658547447 -- conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda - sha256: 27d0b9ff78ad46e1f3a6c96c479ab44beda5f96def88e2fe626e0a49429d8afc - md5: cb2b0ea909b97b3d70cd3921d1445e1a + size: 894452 + timestamp: 1736683239706 +- conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_2.conda + sha256: b45c73348ec9841d5c893acc2e97adff24127548fe8c786109d03c41ed564e91 + md5: f6f7c5b7d0983be186c46c4f6f8f9af8 depends: - __osx >=11.0 license: X11 AND BSD-3-Clause purls: [] - size: 802321 - timestamp: 1724658775723 + size: 796754 + timestamp: 1736683572099 - conda: https://prefix.dev/conda-forge/linux-64/ndindex-1.9.2-py310ha75aee5_1.conda sha256: c76069d91b72b0f3c860f19d97d788bb3647262202925901cce0f9f3b1be15ef md5: 45bd8358c819898e546588bd38368162 @@ -2100,17 +2100,17 @@ packages: purls: [] size: 110100 timestamp: 1733195786147 -- conda: https://prefix.dev/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_1.conda - sha256: 0d6133545f268b2b89c2617c196fc791f365b538d4057ecd636d658c3b1e885d - md5: b38dc0206e2a530e5c2cf11dc086b31a +- conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda + sha256: 28a3e3161390a9d23bc02b4419448f8d27679d9e2c250e29849e37749c8de86b + md5: 232fb4577b6687b2d503ef8e254270c9 depends: - python >=3.9 license: BSD-2-Clause license_family: BSD purls: - pkg:pypi/pygments?source=hash-mapping - size: 876700 - timestamp: 1733221731178 + size: 888600 + timestamp: 1736243563082 - conda: https://prefix.dev/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda sha256: 75245ca9d0cbd6d38bb45ec02430189a9d4c21c055c5259739d738a2298d61b3 md5: 799ed216dc6af62520f32aa39bc1c2bb @@ -2195,10 +2195,10 @@ packages: purls: [] size: 25199631 timestamp: 1733409331823 -- conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_102_cp313.conda - build_number: 102 - sha256: b10f25c5edc203d15b3f54861bec4868b8200ebc16c8cbc82202e4c8da2b183e - md5: 6e7535f1d1faf524e9210d2689b3149b +- conda: https://prefix.dev/conda-forge/linux-64/python-3.13.1-ha99a958_105_cp313.conda + build_number: 105 + sha256: d3eb7d0820cf0189103bba1e60e242ffc15fd2f727640ac3a10394b27adf3cca + md5: 34945787453ee52a8f8271c1d19af1e8 depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 @@ -2208,7 +2208,7 @@ packages: - libgcc >=13 - liblzma >=5.6.3,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.47.0,<4.0a0 + - libsqlite >=3.47.2,<4.0a0 - libuuid >=2.38.1,<3.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 @@ -2219,8 +2219,8 @@ packages: - tzdata license: Python-2.0 purls: [] - size: 33263183 - timestamp: 1733436074842 + size: 33169840 + timestamp: 1736763984540 - conda: https://prefix.dev/conda-forge/osx-arm64/python-3.10.16-h870587a_1_cpython.conda build_number: 1 sha256: cd617b15712c4f9316b22c75459311ed106ccb0659c0bf36e281a9162b4e2d95 @@ -2243,10 +2243,10 @@ packages: purls: [] size: 12372048 timestamp: 1733408850559 -- conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_102_cp313.conda - build_number: 102 - sha256: 0379adf6bb35ca47036860983701e8f6fae89c028d422f2b9439f3110893bc24 - md5: 8c65c1dfc98312ef8666dbb7c7fc47ca +- conda: https://prefix.dev/conda-forge/osx-arm64/python-3.13.1-h4f43103_105_cp313.conda + build_number: 105 + sha256: 7d27cc8ef214abbdf7dd8a5d473e744f4bd9beb7293214a73c58e4895c2830b8 + md5: 11d916b508764b7d881dd5c75d222d6e depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 @@ -2254,7 +2254,7 @@ packages: - libffi >=3.4,<4.0a0 - liblzma >=5.6.3,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.47.0,<4.0a0 + - libsqlite >=3.47.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - openssl >=3.4.0,<4.0a0 @@ -2264,8 +2264,8 @@ packages: - tzdata license: Python-2.0 purls: [] - size: 12905237 - timestamp: 1733433280639 + size: 12919840 + timestamp: 1736761931666 - conda: https://prefix.dev/conda-forge/win-64/python-3.10.16-h37870fc_1_cpython.conda build_number: 1 sha256: 3392db6a7a90864d3fd1ce281859a49e27ee68121b63eece2ae6f1dbb2a8aaf1 @@ -2288,17 +2288,17 @@ packages: purls: [] size: 16061214 timestamp: 1733408154785 -- conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_102_cp313.conda - build_number: 102 - sha256: ee41eda85ebc3a257a3b21a76d255d986b08a285d891e418cbfb70113ee14684 - md5: 70568ba8bbd5f0c7b830e690775eb8b7 +- conda: https://prefix.dev/conda-forge/win-64/python-3.13.1-h071d269_105_cp313.conda + build_number: 105 + sha256: de3bb832ff3982c993c6af15e6c45bb647159f25329caceed6f73fd4769c7628 + md5: 3ddb0531ecfb2e7274d471203e053d78 depends: - bzip2 >=1.0.8,<2.0a0 - libexpat >=2.6.4,<3.0a0 - libffi >=3.4,<4.0a0 - liblzma >=5.6.3,<6.0a0 - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.47.0,<4.0a0 + - libsqlite >=3.47.2,<4.0a0 - libzlib >=1.3.1,<2.0a0 - openssl >=3.4.0,<4.0a0 - python_abi 3.13.* *_cp313 @@ -2309,8 +2309,8 @@ packages: - vc14_runtime >=14.29.30139 license: Python-2.0 purls: [] - size: 16753813 - timestamp: 1733433028707 + size: 16778758 + timestamp: 1736761341620 - conda: https://prefix.dev/conda-forge/linux-64/python_abi-3.10-5_cp310.conda build_number: 5 sha256: 074d2f0b31f0333b7e553042b17ea54714b74263f8adda9a68a4bd8c7e219971 @@ -2444,17 +2444,17 @@ packages: purls: [] size: 250351 timestamp: 1679532511311 -- conda: https://prefix.dev/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - sha256: abb12e1dd515b13660aacb5d0fd43835bc2186cab472df25b7716cd65e095111 - md5: fc80f7995e396cbaeabd23cf46c413dc +- conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda + sha256: e0778e4f276e9a81b51c56f51ec22a27b4d8fc955abc0be77ad09ca9bea06bb9 + md5: 8f28e299c11afdd79e0ec1e279dcdc52 depends: - python >=3.9 license: MIT license_family: MIT purls: - pkg:pypi/setuptools?source=hash-mapping - size: 774252 - timestamp: 1732632769210 + size: 775598 + timestamp: 1736512753595 - conda: https://prefix.dev/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 sha256: 0cea408397d50c2afb2d25e987ebac4546ae11e549d65b1403d80dc368dfaaa6 md5: 6d6552722448103793743dabfbda532d @@ -2570,13 +2570,13 @@ packages: - pkg:pypi/typing-extensions?source=hash-mapping size: 39637 timestamp: 1733188758212 -- conda: https://prefix.dev/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - sha256: 4fde5c3008bf5d2db82f2b50204464314cc3c91c1d953652f7bd01d9e52aefdf - md5: 8ac3367aafb1cc0a068483c580af8015 +- conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda + sha256: c4b1ae8a2931fe9b274c44af29c5475a85b37693999f8c792dad0f8c6734b1de + md5: dbcace4706afdfb7eb891f7b37d07c04 license: LicenseRef-Public-Domain purls: [] - size: 122354 - timestamp: 1728047496079 + size: 122921 + timestamp: 1737119101255 - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda sha256: db8dead3dd30fb1a032737554ce91e2819b43496a0db09927edf01c32b577450 md5: 6797b005cd0f439c4c5c9ac565783700 @@ -2658,9 +2658,9 @@ packages: purls: [] size: 754247 timestamp: 1731710681163 -- conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.28.1-pyhd8ed1ab_0.conda - sha256: c8bde4547ddbd21ea89e483a7c65d8a5e442c0db494b0b977e389b75b9d03d62 - md5: 680b1c287b10cefc8bda0530b217229f +- conda: https://prefix.dev/conda-forge/noarch/virtualenv-20.29.1-pyhd8ed1ab_0.conda + sha256: f09a9f2034669762ae875858253d472588f03689843e5f0b8ddc5cc48a1d0e50 + md5: de06336c9833cffd2a4bd6f27c4cf8ea depends: - distlib >=0.3.7,<1 - filelock >=3.12.2,<4 @@ -2669,9 +2669,9 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/virtualenv?source=hash-mapping - size: 3350367 - timestamp: 1735929107438 + - pkg:pypi/virtualenv?source=compressed-mapping + size: 3501167 + timestamp: 1737145224475 - conda: https://prefix.dev/conda-forge/win-64/vs2015_runtime-14.42.34433-hdffcdeb_23.conda sha256: 568ce8151eaae256f1cef752fc78651ad7a86ff05153cc7a4740b52ae6536118 md5: 5c176975ca2b8366abad3c97b3cd1e83 diff --git a/tests/test_array.py b/tests/test_array.py index 5ae1308..2d4c915 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import copy import operator as op @@ -14,7 +12,7 @@ pxp = pint_array.pint_namespace(xp) -class TestNumPyMethods: +class TestArrayMethods: @classmethod def setup_class(cls): from pint import _DEFAULT_REGISTRY @@ -54,9 +52,7 @@ def assertNDArrayEqual(self, actual, desired): assert not isinstance(desired, self.Q_) -class TestNumPyArrayCreation(TestNumPyMethods): - # https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html - +class TestArrayCreation(TestArrayMethods): @pytest.mark.xfail(reason="Scalar argument issue ") def test_ones_like(self): self.assertNDArrayEqual(pxp.ones_like(self.q), pxp.asarray([[1, 1], [1, 1]])) @@ -79,7 +75,7 @@ def test_full_like(self): self.assertNDArrayEqual(pxp.full_like(self.q, 2), pxp.asarray([[2, 2], [2, 2]])) -class TestNumPyArrayManipulation(TestNumPyMethods): +class TestArrayManipulation(TestArrayMethods): # Changing array shape def test_reshape(self): @@ -171,9 +167,7 @@ def test_roll(self): ) -class TestNumPyMathematicalFunctions(TestNumPyMethods): - # https://www.numpy.org/devdocs/reference/routines.math.html - +class TestArrayMathematicalFunctions(TestArrayMethods): def test_prod_numpy_func(self): axis = 0 @@ -251,7 +245,7 @@ def test_exponentiation_array_exp_2(self): op.ipow(arr_cp, q_cp) -class TestNumPyUnclassified(TestNumPyMethods): +class TestArrayUnclassified(TestArrayMethods): def test_repeat(self): helpers.assert_quantity_equal( pxp.repeat(self.q, 2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m @@ -325,9 +319,7 @@ def test_clip_numpy_func(self): def test_round_numpy_func(self): helpers.assert_quantity_equal( - pxp.round( - 102.75 * self.ureg.m, - ), + pxp.round(102.75 * self.ureg.m), 103 * self.ureg.m, ) @@ -364,9 +356,7 @@ def test_std_numpy_func(self): def test_conj(self): arr = pxp.asarray(self.q, dtype=pxp.complex64) * (1 + 1j) helpers.assert_quantity_equal(pxp.conj(arr), arr * (1 - 1j)) - # helpers.assert_quantity_equal( - # (self.q * (1 + 1j)).conjugate(), self.q * (1 - 1j) - # ) + helpers.assert_quantity_equal((arr * (1 + 1j)).conj(), arr * (1 - 1j)) def test_getitem(self): with pytest.raises(IndexError): @@ -410,7 +400,6 @@ def test_setitem(self): helpers.assert_quantity_equal(q, [0.001, 1, 2, 3] * self.ureg.m / self.ureg.mm) def test_reversible_op(self): - """ """ q = pxp.asarray(self.q, dtype=pxp.float64) x = xp.asarray(self.q.magnitude, dtype=xp.float64) u = pxp.asarray(pxp.ones(x.shape), dtype=pxp.float64) @@ -460,10 +449,10 @@ def test_meshgrid_numpy_func(self): helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) def test_comparisons(self): - # self.assertNDArrayEqual( - # pxp.asarray(self.q) > 2 * self.ureg.m, - # xp.asarray([[False, False], [True, True]]) - # ) + self.assertNDArrayEqual( + pxp.asarray(self.q) > 2 * self.ureg.m, + xp.asarray([[False, False], [True, True]]) + ) self.assertNDArrayEqual( pxp.asarray(self.q) < 2 * self.ureg.m, xp.asarray([[True, False], [False, False]]),