def assert_allclose_units(actual, desired, rtol=1e-7, atol=0, **kwargs): """Raise an error if two objects are not equal up to desired tolerance This is a wrapper for :func:`numpy.testing.assert_allclose` that also verifies unit consistency Parameters ---------- actual : array-like Array obtained (possibly with attached units) desired : array-like Array to compare with (possibly with attached units) rtol : float, optional Relative tolerance, defaults to 1e-7 atol : float or quantity, optional Absolute tolerance. If units are attached, they must be consistent with the units of ``actual`` and ``desired``. If no units are attached, assumes the same units as ``desired``. Defaults to zero. Notes ----- Also accepts additional keyword arguments accepted by :func:`numpy.testing.assert_allclose`, see the documentation of that function for details. """ # Create a copy to ensure this function does not alter input arrays act = unyt_array(actual) des = unyt_array(desired) try: des = des.in_units(act.units) except (UnitOperationError, UnitConversionError): raise AssertionError( "Units of actual (%s) and desired (%s) do not have " "equivalent dimensions" % (act.units, des.units)) rt = unyt_array(rtol) if not rt.units.is_dimensionless: raise AssertionError("Units of rtol (%s) are not " "dimensionless" % rt.units) if not isinstance(atol, unyt_array): at = unyt_quantity(atol, des.units) try: at = at.in_units(act.units) except UnitOperationError: raise AssertionError("Units of atol (%s) and actual (%s) do not have " "equivalent dimensions" % (at.units, act.units)) # units have been validated, so we strip units before calling numpy # to avoid spurious errors act = act.value des = des.value rt = rt.value at = at.value return assert_allclose(act, des, rt, at, **kwargs)
def __new__(cls, matrix, units): """Create `Tensor` instance.""" if array(matrix).size != 6: raise TypeError("`matrix` must have six values") else: new = unyt_array(empty((3, 3)), units, ureg).view(cls) return new
def test_atol_conversion_error(): a1 = unyt_array([1.0, 2.0, 3.0], "cm") a2 = unyt_array([1.0, 2.0, 3.0], "cm") with pytest.raises(AssertionError): assert_allclose_units(a1, a2, atol=unyt_quantity(0.0, "kg"))
def test_runtime_error(): a1 = unyt_array([1.0, 2.0, 3.0], "cm") a2 = unyt_array([1.0, 2.0, 3.0], "cm") with pytest.raises(RuntimeError): assert_allclose_units(a1, a2, rtol=unyt_quantity(1e-7, "cm"))
def test_unequal_error(): a1 = unyt_array([1.0, 2.0, 3.0], "cm") a2 = unyt_array([4.0, 5.0, 6.0], "cm") with pytest.raises(AssertionError): assert_allclose_units(a1, a2)
def test_equality(): a1 = unyt_array([1.0, 2.0, 3.0], "cm") a2 = unyt_array([1.0, 2.0, 3.0], "cm") assert_allclose_units(a1, a2)
def uappend(self, val): import numpy as np return unyt_array(np.append(self.value, val), self.units)
def array_ufunc(self, ufunc, method, *inputs, **kwargs): func = getattr(ufunc, method) if "out" not in kwargs: out = None out_func = None else: # we need to get both the actual "out" object and a view onto it # in case we need to do in-place operations out = kwargs.pop("out")[0] if out.dtype.kind in ("u", "i"): new_dtype = "f" + str(out.dtype.itemsize) float_values = out.astype(new_dtype) out.dtype = new_dtype np.copyto(out, float_values) out_func = out.view(np.ndarray) if len(inputs) == 1: # Unary ufuncs inp = inputs[0] u = getattr(inp, "units", None) if u.dimensions is angle and ufunc in trigonometric_operators: # ensure np.sin(90*degrees) works as expected inp = inp.in_units("radian").v # evaluate the ufunc out_arr = func(np.asarray(inp), out=out_func, **kwargs) if ufunc in (multiply, divide) and method == "reduce": # a reduction of a multiply or divide corresponds to # a repeated product which we implement as an exponent mul = 1 power_sign = POWER_SIGN_MAPPING[ufunc] if "axis" in kwargs and kwargs["axis"] is not None: unit = u**(power_sign * inp.shape[kwargs["axis"]]) else: unit = u**(power_sign * inp.size) else: # get unit of result mul, unit = self._ufunc_registry[ufunc](u) # use type(self) here so we can support user-defined # subclasses of unyt_array ret_class = type(self) elif len(inputs) == 2: # binary ufuncs i0 = inputs[0] i1 = inputs[1] # coerce inputs to be ndarrays if they aren't already inp0 = _coerce_iterable_units(i0) inp1 = _coerce_iterable_units(i1) u0 = getattr(i0, "units", None) or getattr(inp0, "units", None) u1 = getattr(i1, "units", None) or getattr(inp1, "units", None) ret_class = _get_binary_op_return_class(type(i0), type(i1)) if u0 is None: u0 = Unit(registry=getattr(u1, "registry", None)) if u1 is None and ufunc is not power: u1 = Unit(registry=getattr(u0, "registry", None)) elif ufunc is power: u1 = inp1 if inp0.shape != () and inp1.shape != (): raise UnitOperationError(ufunc, u0, u1) if isinstance(u1, unyt_array): if u1.units.is_dimensionless: pass else: raise UnitOperationError(ufunc, u0, u1) if u1.shape == (): u1 = float(u1) else: u1 = 1.0 unit_operator = self._ufunc_registry[ufunc] if unit_operator in (_preserve_units, _comparison_unit, _arctan2_unit): # check "is" equality first for speed if u0 is not u1 and u0 != u1: # we allow adding, multiplying, comparisons with # zero-filled arrays, lists, etc or scalar zero. We # do not allow zero-filled unyt_array instances for # performance reasons. If we did allow it, every # binary operation would need to scan over all the # elements of both arrays to check for arrays filled # with zeros if not isinstance(i0, unyt_array) or not isinstance( i1, unyt_array): any_nonzero = [np.count_nonzero(i0), np.count_nonzero(i1)] if any_nonzero[0] == 0: u0 = u1 elif any_nonzero[1] == 0: u1 = u0 if not u0.same_dimensions_as(u1): if unit_operator is _comparison_unit: # we allow comparisons between data with # units and dimensionless data if u0.is_dimensionless: u0 = u1 elif u1.is_dimensionless: u1 = u0 else: raise UnitOperationError(ufunc, u0, u1) else: raise UnitOperationError(ufunc, u0, u1) conv, offset = u1.get_conversion_factor(u0, inp1.dtype) new_dtype = np.dtype("f" + str(inp1.dtype.itemsize)) conv = new_dtype.type(conv) '''if offset is not None: raise InvalidUnitOperation( "Quantities with units of Fahrenheit or Celsius " "cannot by multiplied, divided, subtracted or " "added with data that has different units." )''' inp1 = np.asarray(inp1) * conv # get the unit of the result mul, unit = unit_operator(u0, u1) # actually evaluate the ufunc out_arr = func(inp0.view(np.ndarray), inp1.view(np.ndarray), out=out_func, **kwargs) if unit_operator in (_multiply_units, _divide_units): if unit.is_dimensionless and unit.base_value != 1.0: if not u0.is_dimensionless: if u0.dimensions == u1.dimensions: out_arr = np.multiply(out_arr.view(np.ndarray), unit.base_value, out=out_func) unit = Unit(registry=unit.registry) '''if ( u0.base_offset and u0.dimensions is temperature or u1.base_offset and u1.dimensions is temperature ): print(u0.base_offset,u0.dimensions,u1.base_offset,u1.dimensions) raise InvalidUnitOperation( "Quantities with units of Fahrenheit or Celsius TODO " "cannot by multiplied, divide, subtracted or added." )''' else: raise RuntimeError( "Support for the %s ufunc with %i inputs has not been" "added to unyt_array." % (str(ufunc), len(inputs))) if unit is None: out_arr = np.array(out_arr, copy=False) elif ufunc in (modf, divmod_): out_arr = tuple((ret_class(o, unit) for o in out_arr)) elif out_arr.size == 1: out_arr = unyt_quantity(np.asarray(out_arr), unit) else: if ret_class is unyt_quantity: # This happens if you do ndarray * unyt_quantity. # Explicitly casting to unyt_array avoids creating a # unyt_quantity with size > 1 out_arr = unyt_array(out_arr, unit) else: out_arr = ret_class(out_arr, unit, bypass_validation=True) if out is not None: if mul != 1: multiply(out, mul, out=out) if np.shares_memory(out_arr, out): mul = 1 if isinstance(out, unyt_array): try: out.units = out_arr.units except AttributeError: # out_arr is an ndarray out.units = Unit("", registry=self.units.registry) if mul == 1: return out_arr return mul * out_arr