diff --git a/brainpy/_src/math/units/base.py b/brainpy/_src/math/units/base.py index 642cf754..50d03c6e 100644 --- a/brainpy/_src/math/units/base.py +++ b/brainpy/_src/math/units/base.py @@ -31,7 +31,7 @@ "have_same_dimensions", "in_unit", "in_best_unit", - "Quantity", + "UnitArray", "Unit", "register_new_unit", "check_units", @@ -300,7 +300,7 @@ def wrap_function_keep_dimensions(func): """ def f(x, *args, **kwds): # pylint: disable=C0111 - return Quantity(func(np.array(x, copy=False), *args, **kwds), dim=x.dim) + return UnitArray(func(np.array(x, copy=False), *args, **kwds), dim=x.dim) f._arg_units = [None] f._return_unit = lambda u: u @@ -325,7 +325,7 @@ def wrap_function_change_dimensions(func, change_dim_func): def f(x, *args, **kwds): # pylint: disable=C0111 ar = np.array(x, copy=False) - return Quantity(func(ar, *args, **kwds), dim=change_dim_func(ar, x.dim)) + return UnitArray(func(ar, *args, **kwds), dim=change_dim_func(ar, x.dim)) f._arg_units = [None] f._return_unit = change_dim_func @@ -781,7 +781,7 @@ def get_dimensions(obj): ] or isinstance(obj, (numbers.Number, np.number, np.ndarray)): return DIMENSIONLESS try: - return Quantity(obj).dim + return UnitArray(obj).dim except TypeError: raise TypeError(f"Object of type {type(obj)} does not have dimensions") @@ -938,7 +938,7 @@ def quantity_with_dimensions(floatval, dims): Returns ------- - q : `Quantity` + q : `UnitArray` A quantity with the given dimensions. Examples @@ -951,10 +951,10 @@ def quantity_with_dimensions(floatval, dims): -------- get_or_create_dimensions """ - return Quantity(floatval, get_or_create_dimension(dims._dims)) + return UnitArray(floatval, get_or_create_dimension(dims._dims)) -class Quantity(np.ndarray): +class UnitArray(np.ndarray): """ A number with an associated physical dimension. In most cases, it is not necessary to create a Quantity object by hand, instead use multiplication @@ -1061,7 +1061,7 @@ def __new__(cls, arr, dim=None, dtype=None, copy=False, force_quantity=False): if not isinstance(arr, (np.ndarray, np.number, numbers.Number)): # check whether it is an iterable containing Quantity objects try: - is_quantity = [isinstance(x, Quantity) for x in _flatten(arr)] + is_quantity = [isinstance(x, UnitArray) for x in _flatten(arr)] except TypeError: # Not iterable is_quantity = [False] @@ -1211,12 +1211,12 @@ def __array_wrap__(self, array, context=None): # a 1 * volt ** 2 quantitiy instead of volt ** 2. But this should # rarely be an issue. The alternative leads to more confusing # behaviour: np.float64(3) * mV would result in a dimensionless float64 - result = array.view(Quantity) + result = array.view(UnitArray) result.dim = dim return result def __deepcopy__(self, memo): - return Quantity(self, copy=True) + return UnitArray(self, copy=True) # ============================================================================== # Quantity-specific functions (not existing in ndarray) @@ -1237,7 +1237,7 @@ def with_dimensions(value, *args, **keywords): Returns ------- - q : `Quantity` + q : `UnitArray` A `Quantity` object with the given dim Examples @@ -1245,9 +1245,9 @@ def with_dimensions(value, *args, **keywords): All of these define an equivalent `Quantity` object: >>> from brainpy.math.units import * - >>> Quantity.with_dimensions(2, get_or_create_dimension(length=1)) + >>> UnitArray.with_dimensions(2, get_or_create_dimension(length=1)) 2. * metre - >>> Quantity.with_dimensions(2, length=1) + >>> UnitArray.with_dimensions(2, length=1) 2. * metre >>> 2 * metre 2. * metre @@ -1256,7 +1256,7 @@ def with_dimensions(value, *args, **keywords): dimensions = args[0] else: dimensions = get_or_create_dimension(*args, **keywords) - return Quantity(value, dim=dimensions) + return UnitArray(value, dim=dimensions) ### ATTRIBUTES ### is_dimensionless = property( @@ -1378,7 +1378,7 @@ def get_best_unit(self, *regs): Returns ------- - u : `Quantity` or `Unit` + u : `UnitArray` or `Unit` The best-fitting unit for the quantity `x`. """ if self.is_dimensionless: @@ -1389,7 +1389,7 @@ def get_best_unit(self, *regs): return r[self] except KeyError: pass - return Quantity(1, self.dim) + return UnitArray(1, self.dim) else: return self.get_best_unit( standard_unit_register, user_unit_register, additional_unit_register @@ -1447,11 +1447,11 @@ def __getitem__(self, key): """Overwritten to assure that single elements (i.e., indexed with a single integer or a tuple of integers) retain their unit. """ - return Quantity(np.ndarray.__getitem__(self, key), self.dim) + return UnitArray(np.ndarray.__getitem__(self, key), self.dim) def item(self, *args): """Overwritten to assure that the returned element retains its unit.""" - return Quantity(np.ndarray.item(self, *args), self.dim) + return UnitArray(np.ndarray.item(self, *args), self.dim) def __setitem__(self, key, value): fail_for_dimension_mismatch(self, value, "Inconsistent units in assignment") @@ -1522,7 +1522,7 @@ def _binary_operation( if inplace: if self.shape == (): - self_value = Quantity(self, copy=True) + self_value = UnitArray(self, copy=True) else: self_value = self operation(self_value, other) @@ -1533,7 +1533,7 @@ def _binary_operation( self_arr = np.array(self, copy=False) other_arr = np.array(other, copy=False) result = operation(self_arr, other_arr) - return Quantity(result, newdims) + return UnitArray(result, newdims) def __mul__(self, other): return self._binary_operation(other, operator.mul, operator.mul) @@ -1601,12 +1601,12 @@ def __rsub__(self, other): # We allow operations with 0 even for dimension mismatches, e.g. # 0 - 3*mV is allowed. In this case, the 0 is not represented by a # Quantity object so we cannot simply call Quantity.__sub__ - if (not isinstance(other, Quantity) or other.dim is DIMENSIONLESS) and np.all( + if (not isinstance(other, UnitArray) or other.dim is DIMENSIONLESS) and np.all( other == 0 ): return self.__neg__() else: - return Quantity(other, copy=False, force_quantity=True).__sub__(self) + return UnitArray(other, copy=False, force_quantity=True).__sub__(self) def __isub__(self, other): return self._binary_operation( @@ -1631,7 +1631,7 @@ def __pow__(self, other): exponent=other, ) other = np.array(other, copy=False) - return Quantity(np.array(self, copy=False) ** other, self.dim ** other) + return UnitArray(np.array(self, copy=False) ** other, self.dim ** other) else: return NotImplemented @@ -1639,7 +1639,7 @@ def __rpow__(self, other): if self.is_dimensionless: if isinstance(other, np.ndarray) or isinstance(other, np.ndarray): new_array = np.array(other, copy=False) ** np.array(self, copy=False) - return Quantity(new_array, DIMENSIONLESS) + return UnitArray(new_array, DIMENSIONLESS) else: return NotImplemented else: @@ -1671,13 +1671,13 @@ def __ipow__(self, other): return NotImplemented def __neg__(self): - return Quantity(-np.array(self, copy=False), self.dim) + return UnitArray(-np.array(self, copy=False), self.dim) def __pos__(self): return self def __abs__(self): - return Quantity(abs(np.array(self, copy=False)), self.dim) + return UnitArray(abs(np.array(self, copy=False)), self.dim) def tolist(self): """ @@ -1685,7 +1685,7 @@ def tolist(self): Returns ------- - l : list of `Quantity` + l : list of `UnitArray` A (possibly nested) list equivalent to the original array. """ @@ -1696,7 +1696,7 @@ def replace_with_quantity(seq, dim): """ # No recursion needed for single values if not isinstance(seq, list): - return Quantity(seq, dim) + return UnitArray(seq, dim) def top_replace(s): """ @@ -1704,7 +1704,7 @@ def top_replace(s): """ for i in s: if not isinstance(i, list): - yield Quantity(i, dim) + yield UnitArray(i, dim) else: yield type(i)(top_replace(i)) @@ -1834,7 +1834,7 @@ def put(self, indices, values, *args, **kwds): # pylint: disable=C0111 def clip(self, a_min, a_max, *args, **kwds): # pylint: disable=C0111 fail_for_dimension_mismatch(self, a_min, "clip") fail_for_dimension_mismatch(self, a_max, "clip") - return Quantity( + return UnitArray( np.clip( np.array(self, copy=False), np.array(a_min, copy=False), @@ -1849,7 +1849,7 @@ def clip(self, a_min, a_max, *args, **kwds): # pylint: disable=C0111 clip._do_not_run_doctests = True def dot(self, other, **kwds): # pylint: disable=C0111 - return Quantity( + return UnitArray( np.array(self).dot(np.array(other), **kwds), self.dim * get_dimensions(other), ) @@ -1879,7 +1879,7 @@ def prod(self, *args, **kwds): # pylint: disable=C0111 # identical if dim_exponent.size > 1: dim_exponent = dim_exponent[0] - return Quantity(np.array(prod_result, copy=False), self.dim ** dim_exponent) + return UnitArray(np.array(prod_result, copy=False), self.dim ** dim_exponent) prod.__doc__ = np.ndarray.prod.__doc__ prod._do_not_run_doctests = True @@ -1890,16 +1890,16 @@ def cumprod(self, *args, **kwds): # pylint: disable=C0111 "cumprod over array elements on quantities " "with dimensions is not possible." ) - return Quantity(np.array(self, copy=False).cumprod(*args, **kwds)) + return UnitArray(np.array(self, copy=False).cumprod(*args, **kwds)) cumprod.__doc__ = np.ndarray.cumprod.__doc__ cumprod._do_not_run_doctests = True -Quantity.__module__ = "brainpy.math.units" +UnitArray.__module__ = "brainpy.math.units" -class Unit(Quantity): +class Unit(UnitArray): r""" A physical unit. @@ -2287,7 +2287,7 @@ def __eq__(self, other): if isinstance(other, Unit): return other.dim is self.dim and other.scale == self.scale else: - return Quantity.__eq__(self, other) + return UnitArray.__eq__(self, other) def __neq__(self, other): return not self.__eq__(other) @@ -2562,13 +2562,13 @@ def new_f(*args, **kwds): arg_names = f.__code__.co_varnames[0: f.__code__.co_argcount] for n, v in zip(arg_names, args[0: f.__code__.co_argcount]): if ( - not isinstance(v, (Quantity, str, bool)) + not isinstance(v, (UnitArray, str, bool)) and v is not None and n in au ): try: # allow e.g. to pass a Python list of values - v = Quantity(v) + v = UnitArray(v) except TypeError: if have_same_dimensions(au[n], 1): raise TypeError( diff --git a/brainpy/_src/math/units/tests/test_units.py b/brainpy/_src/math/units/tests/test_units.py index 64518faf..601f333d 100644 --- a/brainpy/_src/math/units/tests/test_units.py +++ b/brainpy/_src/math/units/tests/test_units.py @@ -14,7 +14,7 @@ UFUNCS_INTEGERS, UFUNCS_LOGICAL, DimensionMismatchError, - Quantity, + UnitArray, Unit, check_units, fail_for_dimension_mismatch, @@ -55,7 +55,7 @@ def assert_allclose(actual, desired, rtol=4.5e8, atol=0, **kwds): def assert_quantity(q, values, unit): - assert isinstance(q, Quantity) or ( + assert isinstance(q, UnitArray) or ( have_same_dimensions(unit, 1) and (values.shape == () or isinstance(q, np.ndarray)) ), q @@ -76,49 +76,49 @@ def test_construction(): assert_quantity(q, 0.5, second) q = np.array([500, 1000]) * ms assert_quantity(q, np.array([0.5, 1]), second) - q = Quantity(500) + q = UnitArray(500) assert_quantity(q, 500, 1) - q = Quantity(500, dim=second.dim) + q = UnitArray(500, dim=second.dim) assert_quantity(q, 500, second) - q = Quantity([0.5, 1], dim=second.dim) + q = UnitArray([0.5, 1], dim=second.dim) assert_quantity(q, np.array([0.5, 1]), second) - q = Quantity(np.array([0.5, 1]), dim=second.dim) + q = UnitArray(np.array([0.5, 1]), dim=second.dim) assert_quantity(q, np.array([0.5, 1]), second) - q = Quantity([500 * ms, 1 * second]) + q = UnitArray([500 * ms, 1 * second]) assert_quantity(q, np.array([0.5, 1]), second) - q = Quantity.with_dimensions(np.array([0.5, 1]), second=1) + q = UnitArray.with_dimensions(np.array([0.5, 1]), second=1) assert_quantity(q, np.array([0.5, 1]), second) q = [0.5, 1] * second assert_quantity(q, np.array([0.5, 1]), second) # dimensionless quantities - q = Quantity([1, 2, 3]) + q = UnitArray([1, 2, 3]) assert_quantity(q, np.array([1, 2, 3]), Unit(1)) - q = Quantity(np.array([1, 2, 3])) + q = UnitArray(np.array([1, 2, 3])) assert_quantity(q, np.array([1, 2, 3]), Unit(1)) - q = Quantity([]) + q = UnitArray([]) assert_quantity(q, np.array([]), Unit(1)) # copying/referencing a quantity - q1 = Quantity.with_dimensions(np.array([0.5, 1]), second=1) - q2 = Quantity(q1) # no copy + q1 = UnitArray.with_dimensions(np.array([0.5, 1]), second=1) + q2 = UnitArray(q1) # no copy assert_quantity(q2, np.asarray(q1), q1) q2[0] = 3 * second assert_equal(q1[0], 3 * second) - q1 = Quantity.with_dimensions(np.array([0.5, 1]), second=1) - q2 = Quantity(q1, copy=True) # copy + q1 = UnitArray.with_dimensions(np.array([0.5, 1]), second=1) + q2 = UnitArray(q1, copy=True) # copy assert_quantity(q2, np.asarray(q1), q1) q2[0] = 3 * second assert_equal(q1[0], 0.5 * second) # Illegal constructor calls with pytest.raises(TypeError): - Quantity([500 * ms, 1]) + UnitArray([500 * ms, 1]) with pytest.raises(TypeError): - Quantity(["some", "nonsense"]) + UnitArray(["some", "nonsense"]) with pytest.raises(DimensionMismatchError): - Quantity([500 * ms, 1 * volt]) + UnitArray([500 * ms, 1 * volt]) @pytest.mark.codegen_independent @@ -938,7 +938,7 @@ def test_special_case_numpy_functions(): where(cond, ar1, ar1 / ms) # Check setasflat (for numpy < 1.7) - if hasattr(Quantity, "setasflat"): + if hasattr(UnitArray, "setasflat"): a = np.arange(10) * mV b = np.ones(10).reshape(5, 2) * volt c = np.ones(10).reshape(5, 2) * second @@ -1047,14 +1047,14 @@ def test_numpy_functions_dimensionless(): result_array = eval(f"np.{ufunc}(np.array(value))") assert isinstance( result_unitless, (np.ndarray, np.number) - ) and not isinstance(result_unitless, Quantity) + ) and not isinstance(result_unitless, UnitArray) assert_equal(result_unitless, result_array) for ufunc in UFUNCS_DIMENSIONLESS_TWOARGS: result_unitless = eval(f"np.{ufunc}(value, value)") result_array = eval(f"np.{ufunc}(np.array(value), np.array(value))") assert isinstance( result_unitless, (np.ndarray, np.number) - ) and not isinstance(result_unitless, Quantity) + ) and not isinstance(result_unitless, UnitArray) assert_equal(result_unitless, result_array) for value, unitless_value in zip(unit_values, unitless_values): @@ -1183,7 +1183,7 @@ def test_numpy_functions_logical(): assert result == NotImplemented except (ValueError, TypeError): pass # raised on numpy >= 0.10 - assert not isinstance(result_units, Quantity) + assert not isinstance(result_units, UnitArray) assert_equal(result_units, result_array) @@ -1247,7 +1247,7 @@ def test_list(): values = [3 * mV, np.array([1, 2]) * mV, np.arange(12).reshape(4, 3) * mV] for value in values: l = value.tolist() - from_list = Quantity(l) + from_list = UnitArray(l) assert have_same_dimensions(from_list, value) assert_equal(from_list, value) @@ -1417,7 +1417,7 @@ def test_inplace_on_scalars(): # in the same way as for Python scalars for scalar in [3 * mV, 3 * mV / mV]: scalar_reference = scalar - scalar_copy = Quantity(scalar, copy=True) + scalar_copy = UnitArray(scalar, copy=True) scalar += scalar_copy assert_equal(scalar_copy, scalar_reference) scalar *= 1.5 @@ -1431,7 +1431,7 @@ def test_inplace_on_scalars(): # For arrays, it should use reference semantics for vector in [[3] * mV, [3] * mV / mV]: vector_reference = vector - vector_copy = Quantity(vector, copy=True) + vector_copy = UnitArray(vector, copy=True) vector += vector_copy assert_equal(vector, vector_reference) vector *= 1.5 @@ -1458,10 +1458,10 @@ def test_units_vs_quantities(): # Using the unconventional type(x) == y since we want to test that # e.g. meter**2 stays a Unit and does not become a Quantity however Unit # inherits from Quantity and therefore both would pass the isinstance test - assert type(2 / meter) == Quantity - assert type(2 * meter) == Quantity - assert type(meter + meter) == Quantity - assert type(meter - meter) == Quantity + assert type(2 / meter) == UnitArray + assert type(2 * meter) == UnitArray + assert type(meter + meter) == UnitArray + assert type(meter - meter) == UnitArray @pytest.mark.codegen_independent diff --git a/brainpy/_src/math/units/unitsafefunctions.py b/brainpy/_src/math/units/unitsafefunctions.py index cdfedebc..b7c3fd42 100644 --- a/brainpy/_src/math/units/unitsafefunctions.py +++ b/brainpy/_src/math/units/unitsafefunctions.py @@ -7,14 +7,14 @@ import numpy as np from .base import ( - DIMENSIONLESS, - Quantity, - check_units, - fail_for_dimension_mismatch, - is_dimensionless, - wrap_function_dimensionless, - wrap_function_keep_dimensions, - wrap_function_remove_dimensions, + DIMENSIONLESS, + UnitArray, + check_units, + fail_for_dimension_mismatch, + is_dimensionless, + wrap_function_dimensionless, + wrap_function_keep_dimensions, + wrap_function_remove_dimensions, ) __all__ = [ @@ -64,7 +64,7 @@ def where(condition, *args, **kwds): # pylint: disable=C0111 else: # as both arguments have the same unit, just use the first one's dimensionless_args = [np.asarray(arg) for arg in args] - return Quantity.with_dimensions( + return UnitArray.with_dimensions( np.where(condition, *dimensionless_args), args[0].dimensions ) else: @@ -136,7 +136,7 @@ def wrap_function_to_method(func): @wraps(func) def f(x, *args, **kwds): # pylint: disable=C0111 - if isinstance(x, Quantity): + if isinstance(x, UnitArray): return getattr(x, func.__name__)(*args, **kwds) else: # no need to wrap anything @@ -202,7 +202,7 @@ def arange(*args, **kwargs): # https://numpy.org/devdocs/release/2.0.0-notes.html#arange-s-start-argument-is-positional-only # TODO: check whether this is still the case in the final release if start == 0: - return Quantity( + return UnitArray( np.arange( stop=np.asarray(stop), step=np.asarray(step), @@ -212,7 +212,7 @@ def arange(*args, **kwargs): copy=False, ) else: - return Quantity( + return UnitArray( np.arange( np.asarray(start), stop=np.asarray(stop), @@ -247,7 +247,7 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None): retstep=retstep, dtype=dtype, ) - return Quantity(result, dim=dim, copy=False) + return UnitArray(result, dim=dim, copy=False) linspace._do_not_run_doctests = True