diff --git a/src/ansys/fluent/core/quantity.py b/src/ansys/fluent/core/quantity.py new file mode 100644 index 000000000000..9ebc5bda89ae --- /dev/null +++ b/src/ansys/fluent/core/quantity.py @@ -0,0 +1,732 @@ +from collections import OrderedDict + + +class _UnitsTable(object): + fundamental_units = { + "kg": "M", + "g": "M", + "lb": "M", + "lbm": "M", + "slug": "M", + "m": "L", + "cm": "L", + "ft": "L", + "in": "L", + "s": "T", + "A": "I", + "K": "Temp", + "C": "Temp", + "F": "Temp", + "R": "Temp", + "mol": "N", + "slugmol": "N", + "cd": "Iv", + "sr": "SAngle", + "radian": "Angle", + "degree": "Angle", + } + + derived_units = { + "N": "kg m s^-2", + "Pa": "N m^-2", + "W": "N m s^-1", + "J": "N m", + "V": "A ohm", + "F": "N m V^-2", + "H": "N m A^-2", + "S": "ohm^-1", + "Wb": "N m A^-1", + "T": "Wb m^-2", + "dyne": "g cm s^-2", + "erg": "dyne cm", + "pdl": "lbm ft s^-2", + "BTU": "J", + "psi": "lbf in^-2", + "lbf": "slug ft s^-2", + "psf": "lbf ft^-2", + "ohm": "kg m^2 s^-3 A^-2", + "Hz": "s^-1", + "cal": "J", + } + + derived_units_with_conversion_factor = { + "l": (0.001, "m^3"), + "gal": (0.0037854117839999993, "m^3"), + "BTU": (1055.056, "J"), + "cal": (4.184, "J"), + } + + multipliers = { + "d": 10**-1, + "c": 10**-2, + "m": 10**-3, + "u": 10**-6, + "n": 10**-9, + "p": 10**-12, + "f": 10**-15, + "a": 10**-18, + "z": 10**-21, + "y": 10**-24, + "da": 10**1, + "h": 10**2, + "k": 10**3, + "M": 10**6, + "G": 10**9, + "T": 10**12, + "P": 10**15, + "E": 10**18, + "Z": 10**21, + "Y": 10**24, + } + + dimension_order = OrderedDict( + [ + ("Mass", "M"), + ("Length", "L"), + ("Time", "T"), + ("Temperature", "Temp"), + ("Angle", "Angle"), + ("ChemicalAmount", "N"), + ("Light", "Iv"), + ("Current", "I"), + ("SolidAngle", "SAngle"), + ("", ""), + ] + ) + + conversion_map = { + "kg": 1, + "g": 0.001, + "lb": 0.45359237, + "lbm": 0.45359237, + "slug": 14.59390293720637, + "m": 1, + "cm": 0.01, + "ft": 0.30479999999999996, + "in": 0.0254, + "s": 1, + "A": 1, + "mol": 1, + "slugmol": 14593.9, + "cd": 1, + "sr": 1, + "radian": 1, + "degree": 0.017453292519943295, + "K": 1, + "C": 1, + "F": 0.5555555555555556, + "R": 0.5555555555555556, + } + + si_map = { + "kg": "kg", + "g": "kg", + "lb": "kg", + "lbm": "kg", + "slug": "kg", + "m": "m", + "cm": "m", + "ft": "m", + "in": "m", + "s": "s", + "A": "A", + "mol": "mol", + "slugmol": "mol", + "cd": "cd", + "sr": "sr", + "radian": "radian", + "degree": "radian", + "K": "K", + "C": "K", + "F": "K", + "R": "K", + "": "", + } + + temperature_conversions = { + # from : { to : formula } + "C": { + "K": lambda c: c + 273.15, + "R": lambda c: (c + 273.15) * 1.8, + "F": lambda c: c * 9 / 5 + 32, + "C": lambda c: c, + }, + "F": { + "K": lambda f: (f - 32) * 5 / 9 + 273.15, + "R": lambda f: f + 459.67, + "F": lambda f: f, + "C": lambda f: (f - 32) * 5 / 9, + }, + "R": { + "K": lambda r: r / 1.8, + "R": lambda r: r, + "F": lambda r: r - 459.67, + "C": lambda r: (r - 491.67) / 1.8, + }, + "K": { + "K": lambda k: k, + "R": lambda k: k * 1.8, + "F": lambda k: k * 1.8 - 459.67, + "C": lambda k: k - 273.15, + }, + } + + +def get_si_conversion_factor(unit_str): + return ( + _UnitsTable.conversion_map[unit_str] + if unit_str in _UnitsTable.conversion_map.keys() + else 1 + ) + + +def filter_multiplier(unit_str, predicate=None): + result = False + matched = "" + for item in _UnitsTable.multipliers.keys(): + result = predicate(item, unit_str) if predicate else item == unit_str + if result: + matched = item + result = True + temp_unit_str = unit_str[len(item) :] + if ( + temp_unit_str in _UnitsTable.fundamental_units + or temp_unit_str in _UnitsTable.derived_units + ): + break + else: + result = False + continue + return result, matched + + +def remove_multiplier(unit_str): + has_multiplier, prefix = filter_multiplier( + unit_str, lambda item, unit: len(unit) > 0.0 and unit.startswith(item) + ) + if has_multiplier: + unit_str = unit_str[len(prefix) :] + return unit_str + + +def is_temperature_quantity(dim_obj): + temp_dim = dim_obj["Temp"] + total_dims_sum = sum([abs(val) for val in dim_obj.values()]) + return temp_dim == 1 and total_dims_sum == temp_dim + + +class Unit(object): + def __init__(self, unit_str): + self._unit = unit_str + self._si_multiplier = 1 + self._si_offset = 0 + self._si_unit = "" + self._computemultipliers_and_offsets(unit_str, 1) + self._reduce_to_si_unit(self._si_unit) + + @property + def user_unit(self): + return self._unit + + @property + def si_factor(self): + return self._si_multiplier + + @property + def si_unit(self): + return self._si_unit + + def __call__(self): + return self.user_unit + + def _reduce_to_si_unit(self, unit_str): + term_power_dict = OrderedDict() + unit_split = unit_str.split(" ") + + for term in unit_split: + if "^" in term: + term_split = term.split("^") + if term_split[0] in term_power_dict.keys(): + term_power_dict[term_split[0]] += float(term_split[1]) + else: + term_power_dict[term_split[0]] = float(term_split[1]) + else: + if term in term_power_dict.keys(): + term_power_dict[term] += 1.0 + else: + term_power_dict[term] = 1.0 + + self._si_unit = "" + + for key, power in term_power_dict.items(): + spacechar = " " if len(self._si_unit) > 0 else "" + + if power == 1.0: + self._si_unit += spacechar + key + elif power != 0.0: + self._si_unit += ( + spacechar + + key + + "^" + + str(int(power) if power.is_integer() else power) + ) + else: + self._si_unit += spacechar + key + + def _computemultipliers_and_offsets(self, unit_str, power): + if len(unit_str) == 0: + return + + unit_list = unit_str.split(" ") + + for term in unit_list: + unit_str = term + term_power = 1 + + if "^" in unit_str: + unit_str, term_power = unit_str[: unit_str.index("^")], float( + unit_str[unit_str.index("^") + 1 :] + ) + + term_power *= power + has_multiplier = not ( + unit_str in _UnitsTable.fundamental_units + or unit_str in _UnitsTable.derived_units + ) + + if has_multiplier: + _, prefix = filter_multiplier( + unit_str, + lambda item, unit: len(unit) > 0.0 and unit.startswith(item), + ) + + if len(prefix): + self._si_multiplier *= _UnitsTable.multipliers[prefix] ** term_power + + unit_str = remove_multiplier(unit_str) + + if unit_str in _UnitsTable.fundamental_units: + spacechar = " " if len(self._si_unit) > 0 else "" + + if term_power == 1.0: + self._si_unit += spacechar + _UnitsTable.si_map[unit_str] + elif term_power != 0.0: + self._si_unit += ( + spacechar + _UnitsTable.si_map[unit_str] + "^" + str(term_power) + ) + elif term_power == 0.0: + self._si_unit += "" + else: + self._si_unit += spacechar + _UnitsTable.si_map[unit_str] + + self._si_multiplier *= get_si_conversion_factor(unit_str) ** term_power + + elif unit_str in _UnitsTable.derived_units_with_conversion_factor: + ( + conversion_factor, + unit_str, + ) = _UnitsTable.derived_units_with_conversion_factor[unit_str] + self._si_multiplier *= conversion_factor**term_power + self._computemultipliers_and_offsets(unit_str, term_power) + elif unit_str in _UnitsTable.derived_units: + self._computemultipliers_and_offsets( + _UnitsTable.derived_units[unit_str], term_power + ) + + +class Dimension(object): + def __init__(self, unit_str): + self._dimensions = { + "M": 0.0, + "L": 0.0, + "T": 0.0, + "Temp": 0.0, + "I": 0.0, + "N": 0.0, + "Iv": 0.0, + "Angle": 0.0, + "SAngle": 0.0, + "": 0.0, + } + self._parser(unit_str) + + def _add_or_update_dimension(self, dim_dict): + for key in dim_dict.keys(): + self._dimensions[key] += dim_dict[key] + + def _parser(self, unit, power=1): + if len(unit) == 0: + return + + unit_list = unit.split(" ") + for term in unit_list: + unit_dim = "" + has_multiplier = not ( + term in _UnitsTable.fundamental_units + or term in _UnitsTable.derived_units + ) + unit_str = remove_multiplier(term) if has_multiplier else term + term_power = 1 + + if "^" in term: + unit_str, term_power = term[: term.index("^")], float( + term[term.index("^") + 1 :] + ) + has_multiplier = not ( + unit_str in _UnitsTable.fundamental_units + or unit_str in _UnitsTable.derived_units + ) + unit_str = remove_multiplier(unit_str) if has_multiplier else unit_str + + term_power *= power + unit_dim = self._get_dim(unit_str, term_power) + + if unit_dim != None: + self._add_or_update_dimension(unit_dim) + + def as_dict(self): + return self._dimensions + + def _get_dim(self, unit_str, power): + if unit_str in _UnitsTable.fundamental_units: + return {_UnitsTable.fundamental_units[unit_str]: power} + elif unit_str in _UnitsTable.derived_units: + self._parser(_UnitsTable.derived_units[unit_str], power) + elif unit_str in _UnitsTable.derived_units_with_conversion_factor: + _, unit_str = _UnitsTable.derived_units_with_conversion_factor[unit_str] + self._parser(unit_str, power) + else: + raise ValueError("Not implemented") + + +def get_si_unit_from_dim(dim_list): + si_unit = "" + dim_to_unit_map = UnitSystem("SI").base_units() + + for key, power in zip(_UnitsTable.dimension_order.values(), dim_list): + unit_str = dim_to_unit_map[key] + spacechar = " " if len(si_unit) > 0 else "" + if power == 1.0: + si_unit += spacechar + unit_str + elif power != 0.0: + si_unit += ( + spacechar + + unit_str + + "^" + + str(int(power) if power.is_integer() else power) + ) + return si_unit + + +class UnitSystem: + + _dim_to_unit_sys_map = { + "SI": { + "M": "kg", + "L": "m", + "T": "s", + "Temp": "K", + "I": "A", + "N": "mol", + "Iv": "cd", + "Angle": "radian", + "SAngle": "sr", + "": "", + }, + "CGS": { + "M": "g", + "L": "cm", + "T": "s", + "Temp": "K", + "I": "A", + "N": "mol", + "Iv": "cd", + "Angle": "radian", + "SAngle": "sr", + "": "", + }, + "BTU": { + "M": "slug", + "L": "ft", + "T": "s", + "Temp": "R", + "I": "A", + "N": "slugmol", + "Iv": "cd", + "Angle": "radian", + "SAngle": "sr", + "": "", + }, + } + + _supported_unit_sys = ("SI", "CGS", "BTU") + + def __init__(self, unit_sys): + self._unit_system = unit_sys.upper() + + if self._unit_system not in UnitSystem._supported_unit_sys: + raise ValueError( + "Unsupported unit system, only 'SI', 'CGS', 'BTU' is allowed." + ) + + def _get_unit_from_dim(self, dim_list): + unit = "" + + base_units = UnitSystem._dim_to_unit_sys_map[self._unit_system] + for key, power in zip(_UnitsTable.dimension_order.values(), dim_list): + spacechar = " " if len(unit) > 0 else "" + if power == 1: + unit += spacechar + base_units[key] + elif power != 0: + unit += ( + spacechar + + base_units[key] + + "^" + + str(int(power) if power.is_integer() else power) + ) + return unit + + def convert(self, quantity): + if not isinstance(quantity, Quantity): + raise TypeError("Not instance of Quantity.") + dims = quantity.get_dimensions_list() + unit_str = self._get_unit_from_dim(dims) + return quantity.to(unit_str) + + def base_units(self): + return UnitSystem._dim_to_unit_sys_map[self._unit_system] + + +class Quantity(float): + """This class instantiates physical quantities using their real values and + units. + + Attributes + ---------- + value: Real value + Value of quantity is stored as float. + + unit: Unit string + Unit of quantity is stored as string. + + Methods + ------- + to(to_unit) + Converts to given unit string. + + getDimensions(unit) + Extracts dimensions from unit. + + isDimensionless() + Determines type of quantity. + + Returns + ------- + Quantity instance. + + All the instances of this class are converted to base SI units system to have + consistency in all arithmetic operations. + """ + + def __init__(self, real_value, unit_str): + self._value = float(real_value) + self._unit = Unit(unit_str) + self._dimension = Dimension(unit_str) + self._si_value = self._unit.si_factor * self._value + self._si_unit = self._unit.si_unit + float.__init__(self._si_value) + + def __new__(cls, real_value, unit_str): + _unit = Unit(unit_str) + return float.__new__(cls, _unit.si_factor * real_value) + + @property + def value(self): + return self._value + + @property + def unit(self): + return self._unit.user_unit + + @value.setter + def value(self, val): + self._value = val + + @property + def dimension(self): + return self._dimension + + def is_dimension_less(self): + return all([value == 0 for value in self.get_dimensions_list()]) + + def get_dimensions_list(self): + dims = self._dimension.as_dict() + return [ + dims[_UnitsTable.dimension_order[key]] + for key in _UnitsTable.dimension_order.keys() + ] + + def to(self, to_unit_str): + temp_quantity = Quantity(1, to_unit_str) + + curr_unit_dim_obj = self.dimension + to_unit_dim_obj = temp_quantity.dimension + + if curr_unit_dim_obj.as_dict() != to_unit_dim_obj.as_dict(): + raise ValueError( + f"Incompatible conversion from {self.unit} : to {to_unit_str}" + ) + else: + pass + + if is_temperature_quantity(curr_unit_dim_obj.as_dict()): + return self._get_converted_temperature(to_unit_str) + else: + curr_unit_obj = self._unit + to_unit_obj = temp_quantity._unit + temp_quantity.value = ( + curr_unit_obj.si_factor / to_unit_obj.si_factor + ) * self.value + return temp_quantity + + def _get_converted_temperature(self, to_unit_str): + from_unit_str = self.unit + + if "^" in from_unit_str: + from_unit_str = from_unit_str.split("^")[0] + + if "^" in to_unit_str: + to_unit_str = to_unit_str.split("^")[0] + + _, prefix = filter_multiplier( + from_unit_str, lambda item, unit: len(unit) > 0.0 and unit.startswith(item) + ) + from_unit_multiplier = ( + _UnitsTable.multipliers[prefix] if len(prefix) != 0 else 1.0 + ) + + _, prefix = filter_multiplier( + to_unit_str, lambda item, unit: len(unit) > 0.0 and unit.startswith(item) + ) + to_unit_multiplier = ( + _UnitsTable.multipliers[prefix] if len(prefix) != 0 else 1.0 + ) + + from_key = remove_multiplier(from_unit_str) + to_key = remove_multiplier(to_unit_str) + + return Quantity( + _UnitsTable.temperature_conversions[from_key][to_key]( + from_unit_multiplier * self.value + ) + / to_unit_multiplier, + to_unit_str, + ) + + def _get_si_unit(self, other, func): + curr_dim = self.get_dimensions_list() + other_dim = other.get_dimensions_list() + temp_dim = [func(curr_dim[i], other_dim[i]) for i in range(len(other_dim))] + temp_unit = get_si_unit_from_dim(temp_dim) + return temp_unit + + def __pow__(self, exponent): + new_dims = list(map(lambda x: x * exponent, self.get_dimensions_list())) + new_si_unit = get_si_unit_from_dim(new_dims) + new_si_value = pow(self._si_value, exponent) + return Quantity(new_si_value, new_si_unit) + + def __str__(self): + return f'({self.value}, "{self.unit}")' + + def __repr__(self): + return f'Quantity ({self.value}, "{self.unit}")' + + def __mul__(self, other): + if isinstance(other, Quantity): + temp_value = self._si_value * other._si_value + temp_unit = self._get_si_unit(other, lambda x, y: x + y) + return Quantity(temp_value, temp_unit) + elif isinstance(other, int) or isinstance(other, float): + return Quantity(self._si_value * other, self._si_unit) + + def __rmul__(self, other): + return self.__mul__(other) + + def __truediv__(self, other): + if isinstance(other, Quantity): + temp_value = self._si_value / other._si_value + temp_unit = self._get_si_unit(other, lambda x, y: x - y) + return Quantity(temp_value, temp_unit) + elif isinstance(other, int) or isinstance(other, float): + temp = Quantity(self._si_value / other, self._si_unit) + return temp + + def __rtruediv__(self, other): + if not isinstance(other, Quantity) and isinstance(other, float): + return Quantity(other / self._si_value, self._si_unit) + else: + return other / self + + def __add__(self, other): + if isinstance(other, Quantity): + if self.get_dimensions_list() == other.get_dimensions_list(): + temp_value = float(self) + float(other) + else: + raise ValueError("Incompatible dimensions.") + elif isinstance(other, (float, int)): + if self.is_dimension_less(): + temp_value = self._si_value + other + else: + raise ValueError("Incompatible dimensions.") + else: + return super().__add__(other) + return Quantity(temp_value, self._si_unit) + + def __radd__(self, other): + return super().__add__(other) + + def __sub__(self, other): + if isinstance(other, Quantity) and ( + self.get_dimensions_list() == other.get_dimensions_list() + ): + temp_value = self._si_value - other._si_value + elif ( + not isinstance(other, Quantity) + and self.is_dimension_less() + and (isinstance(other, int) or isinstance(other, float)) + ): + temp_value = self._si_value - other + else: + raise ValueError("Incompatible dimensions.") + return Quantity(temp_value, self._si_unit) + + def __rsub__(self, other): + return Quantity(other, "") - self + + def __eq__(self, other): + if isinstance(other, Quantity): + return self._si_value == other._si_value and self._si_unit == other._si_unit + elif ( + self.is_dimension_less() + and not isinstance(other, Quantity) + and isinstance(other, float) + ): + return float(self) == other + + def __neg__(self): + return Quantity(-self.value, self.unit) + + def __ge__(self, other): + if ( + isinstance(other, Quantity) + and self.get_dimensions_list() == other.get_dimensions_list() + and self._si_unit == other._si_unit + ): + return float(self) > float(other) + elif ( + self.is_dimension_less() + and (not isinstance(other, Quantity)) + and isinstance(other, (float, int)) + ): + return float(self) > other diff --git a/tests/test_quantity.py b/tests/test_quantity.py new file mode 100644 index 000000000000..bff828af6735 --- /dev/null +++ b/tests/test_quantity.py @@ -0,0 +1,693 @@ +import math + +import pytest + +import ansys.fluent.core.quantity as q + +DELTA = 1.0e-5 + + +def test_value_unit_1(): + v = q.Quantity(1, "m s^-1") + assert v.value == 1 + assert v.unit == "m s^-1" + + +def test_value_unit_2(): + v = q.Quantity(10.6, "m") + assert v.value == 10.6 + assert v.unit == "m" + + +def test_value_unit_3(): + v = q.Quantity(99.85, "radian") + assert v.value == pytest.approx(99.85, DELTA) + assert v.unit == "radian" + + +def test_value_unit_4(): + v = q.Quantity(5.7, "") + assert v.value == pytest.approx(5.7, DELTA) + assert v.unit == "" + + +def test_dims_5(): + v = q.Quantity(1.0, "kPa") + assert v.get_dimensions_list() == [ + 1.0, + -1.0, + -2.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ] + + +def test_dims_6(): + v = q.Quantity(1.0, "ft") + assert v.get_dimensions_list() == [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + + +def test_dims_7(): + v = q.Quantity(1.0, "m m^-1") + assert v.get_dimensions_list() == [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + + +def test_to_8(): + v = q.Quantity(1.0, "m") + convert = v.to("ft") + assert convert.value == pytest.approx(3.2808398, DELTA) + assert convert.unit == "ft" + + +def test_to_9(): + v = q.Quantity(1.0, "m") + convert = v.to("mm") + assert convert.value == 1000 + assert convert.unit == "mm" + + +def test_to_10(): + v = q.Quantity(100000.0, "Pa") + convert = v.to("kPa") + assert convert.value == 100.0 + assert convert.unit == "kPa" + + +def test_to_11(): + v = q.Quantity(1.0, "dm^3") + convert = v.to("m^3") + assert convert.value == pytest.approx(0.001, DELTA) + assert convert.unit == "m^3" + + +def test_to_12(): + v = q.Quantity(1.0, "radian") + convert = v.to("degree") + assert convert.value == pytest.approx(57.295779, DELTA) + assert convert.unit == "degree" + + +def test_to_13(): + v = q.Quantity(1.0, "degree") + convert = v.to("radian") + assert convert.value == pytest.approx(0.01745329251, DELTA) + assert convert.unit == "radian" + + +def test_to_14(): + v = q.Quantity(1.0, "Pa s") + convert = v.to("dyne cm^-2 s") + assert convert.value == pytest.approx(10.0, DELTA) + assert convert.unit == "dyne cm^-2 s" + + +def test_to_15(): + v = q.Quantity(1.0, "kg m^-1 s^-1") + convert = v.to("dyne cm^-2 s") + assert convert.value == pytest.approx(10.0, DELTA) + assert convert.unit == "dyne cm^-2 s" + + +def test_to_16(): + v = q.Quantity(1.0, "Pa s") + convert = v.to("slug in^-1 s^-1") + assert convert.value == pytest.approx(0.00174045320, DELTA) + assert convert.unit == "slug in^-1 s^-1" + + +def test_to_17(): + v = q.Quantity(1.0, "kg m^-1 s^-1") + convert = v.to("slug in^-1 s^-1") + assert convert.value == pytest.approx(0.00174045320, DELTA) + assert convert.unit == "slug in^-1 s^-1" + + +def test_to_18(): + v = q.Quantity(1.0, "lb ft^-1 s^-1") + convert = v.to("Pa s") + assert convert.value == pytest.approx(1.488164, DELTA) + assert convert.unit == "Pa s" + + +def test_to_19(): + v = q.Quantity(1.0, "lb ft^-1 s^-1") + convert = v.to("kg m^-1 s^-1") + assert convert.value == pytest.approx(1.488164, DELTA) + assert convert.unit == "kg m^-1 s^-1" + + +def test_to_20(): + v = q.Quantity(1.0, "Hz") + with pytest.raises(ValueError) as e_info: + convert = v.to("radian s^-1") + + +def test_to_21(): + v = q.Quantity(1.0, "radian s^-1") + with pytest.raises(ValueError) as e_info: + convert = v.to("Hz") + + +def test_to_22(): + v = q.Quantity(1.0, "lbf ft^-2") + convert = v.to("N m^-2") + assert convert.value == pytest.approx(47.88024159, DELTA) + assert convert.unit == "N m^-2" + + +def test_to_23(): + v = q.Quantity(1.0, "ft^-3 s^-1") + convert = v.to("m^-3 s^-1") + assert convert.value == pytest.approx(35.3146667, DELTA) + assert convert.unit == "m^-3 s^-1" + + +def test_to_24(): + v = q.Quantity(1.0, "m^-2") + convert = v.to("cm^-2") + assert convert.value == pytest.approx(0.0001, DELTA) + assert convert.unit == "cm^-2" + + +def test_to_25(): + v = q.Quantity(1.0, "m^2") + convert = v.to("in^2") + assert convert.value == pytest.approx(1550.0031, DELTA) + assert convert.unit == "in^2" + + +def test_to_26(): + v = q.Quantity(1.0, "radian s^-1") + convert = v.to("degree s^-1") + assert convert.value == pytest.approx(57.295779, DELTA) + assert convert.unit == "degree s^-1" + + +def test_to_27(): + v = q.Quantity(1.0, "degree s^-1") + convert = v.to("radian s^-1") + assert convert.value == pytest.approx(0.01745329251, DELTA) + assert convert.unit == "radian s^-1" + + +def test_to_28(): + v = q.Quantity(1.0, "dyne cm^-2") + convert = v.to("N m^-2") + assert convert.value == pytest.approx(0.1, DELTA) + assert convert.unit == "N m^-2" + + +def test_to_29(): + v = q.Quantity(1.0, "psi") + convert = v.to("Pa") + assert convert.value == pytest.approx(6894.76, DELTA) + assert convert.unit == "Pa" + + +def test_to_30(): + v = q.Quantity(1.0, "pdl") + convert = v.to("N") + assert convert.value == pytest.approx(0.138254999, DELTA) + assert convert.unit == "N" + + +def test_to_31(): + v = q.Quantity(1.0, "ohm cm") + convert = v.to("ohm m") + assert convert.value == pytest.approx(0.01, DELTA) + assert convert.unit == "ohm m" + + +def test_to_32(): + v = q.Quantity(1.0, "erg") + convert = v.to("J") + assert convert.value == pytest.approx(1.0e-7, DELTA) + assert convert.unit == "J" + + +def test_to_33(): + v = q.Quantity(1.0, "BTU") + convert = v.to("J") + assert convert.value == pytest.approx(1055.056, DELTA) + assert convert.unit == "J" + + +def test_to_34(): + v = q.Quantity(1.0, "gal") + convert = v.to("m^3") + assert convert.value == pytest.approx(0.00378541, DELTA) + assert convert.unit == "m^3" + + +def test_to_35(): + v = q.Quantity(1.0, "l") + convert = v.to("m^3") + assert convert.value == pytest.approx(0.001, DELTA) + assert convert.unit == "m^3" + + +def test_to_36(): + v = q.Quantity(1.0, "BTU lb^-1 R^-1") + convert = v.to("J kg^-1 K^-1") + assert convert.value == pytest.approx(4186.8161854, DELTA) + assert convert.unit == "J kg^-1 K^-1" + + +def test_to_37(): + v = q.Quantity(1.0, "BTU lb^-1 F^-1") + convert = v.to("J kg^-1 K^-1") + assert convert.value == pytest.approx(4186.8161854, DELTA) + assert convert.unit == "J kg^-1 K^-1" + + +def test_to_38(): + v = q.Quantity(1.0, "gal^-1") + convert = v.to("m^-3") + assert convert.value == pytest.approx(264.172, DELTA) + assert convert.unit == "m^-3" + + +def test_to_39(): + v = q.Quantity(1.0, "BTU ft^-2") + convert = v.to("J m^-2") + assert convert.value == pytest.approx(11356.5713242, DELTA) + assert convert.unit == "J m^-2" + + +def test_prop_constants_40(): + c = q.Quantity(10.5, "s^-0.5") + x = q.Quantity(5.7, "m") + t = q.Quantity(4.8, "s") + + y = c * x * t + print(y) + assert float(y) == 287.28 + + +def test_unit_system_41(): + unitSysSI = q.UnitSystem("SI") + myVel = q.Quantity(3.0, "ft s^-1") + siVel = unitSysSI.convert(myVel) # Returns velocity in m/s + assert siVel.value == pytest.approx(0.9143999, DELTA) + assert siVel.unit == "m s^-1" + + unitSysCGS = q.UnitSystem("CGS") + cgsVel = unitSysCGS.convert(myVel) # Returns velocity in cm/s + assert cgsVel.value == 91.44 + assert cgsVel.unit == "cm s^-1" + + unitSysBT = q.UnitSystem("BTU") + btuVel = unitSysBT.convert(myVel) # Returns velocity in ft/s + assert btuVel.value == 3.0 + assert btuVel.unit == "ft s^-1" + + +def test_equality_42(): + qt1 = q.Quantity(5.0, "m s^-1") + qt2 = q.Quantity(5.0, "m s^-1") + assert qt1 == qt2 + + +def test_math_fun_43(): + deg = q.Quantity(90, "degree") + assert math.sin(deg) == 1.0 + + rad = q.Quantity(math.pi / 2, "radian") + assert math.sin(rad) == 1.0 + + root = q.Quantity(100.0, "") + assert math.sqrt(root) == 10.0 + + +def test_subtraction_44(): + q1 = q.Quantity(10.0, "m s^-1") + q2 = q.Quantity(5.0, "m s^-1") + + assert float(q1 - q2) == 5.0 + assert float(q2 - q1) == -5.0 + assert float(q1) - 2.0 == 8.0 + assert 2.0 - float(q1) == -8.0 + assert float(q1) - 3 == 7.0 + assert 3 - float(q1) == -7.0 + + +def test_pow_45(): + q1 = q.Quantity(10.0, "m s^-1") + q2 = q.Quantity(5.0, "m s^-1") + + q1_sq = q1**2 + assert q1_sq.unit == "m^2 s^-2" + + assert float(q1) ** 2 == 100.0 + assert float(q2) ** 2 == 25.0 + + +def test_eq_46(): + q1 = q.Quantity(10.0, "m s^-1") + q2 = q.Quantity(5.0, "m s^-1") + q3 = q.Quantity(10.0, "m s^-1") + q4 = q.Quantity(10.0, "") + + assert q1 != q2 + assert q1 == q3 + assert float(q1) == 10.0 + assert q4 == 10.0 + + +def test_rdiv_47(): + q1 = q.Quantity(10.0, "m s^-1") + q2 = q.Quantity(5.0, "m s^-1") + + assert float(q1) / float(q2) == 2.0 + assert float(q2) / float(q1) == 0.5 + assert float(q1) / 2 == 5.0 + assert 2.0 / float(q1) == 0.2 + + +def test_tempK_48(): + k = q.Quantity(-40, "K") + + kc = k.to("C") + assert kc.value == -313.15 + assert kc.unit == "C" + + kc = k.to("R") + assert kc.value == -72.0 + assert kc.unit == "R" + + kc = k.to("F") + assert kc.value == pytest.approx(-531.67, DELTA) + assert kc.unit == "F" + + +def test_temp_49(): + mk = q.Quantity(-40_000, "mK") + uc = mk.to("uC^1") + assert uc.value == -3.13150000e08 + + +def test_temp_50(): + k = q.Quantity(1.0, "K") + + f = k.to("F") + r = k.to("R") + c = k.to("C") + + assert f.value == -457.87 + assert r.value == 1.8 + assert c.value == -272.15 + + +def test_temp_51(): + c = q.Quantity(1.0, "C") + + f = c.to("F") + r = c.to("R") + k = c.to("K") + + assert f.value == 33.80 + assert r.value == pytest.approx(493.469, DELTA) + assert k.value == 274.15 + + +def test_temp_52(): + r = q.Quantity(1.0, "R") + + f = r.to("F") + c = r.to("C") + k = r.to("K") + + assert f.value == pytest.approx(-458.6699, DELTA) + assert c.value == pytest.approx(-272.5944, DELTA) + assert k.value == pytest.approx(0.555556, DELTA) + + +def test_temp_53(): + f = q.Quantity(1.0, "F") + + c = f.to("C") + r = f.to("R") + k = f.to("K") + + assert c.value == pytest.approx(-17.2222, DELTA) + assert r.value == pytest.approx(460.670, DELTA) + assert k.value == pytest.approx(255.927, DELTA) + + +def test_temp_54(): + hc = q.Quantity(1.0, "J g^-1 K^-1") + + hcto1 = hc.to("kJ kg^-1 K^-1") + + assert hcto1.value == pytest.approx(1.0, DELTA) + assert hcto1.unit == "kJ kg^-1 K^-1" + + hcto2 = hc.to("J kg^-1 C^-1") + + assert hcto2.value == pytest.approx(1000.0, DELTA) + assert hcto2.unit == "J kg^-1 C^-1" + + hcto3 = hc.to("kJ kg^-1 C^-1") + + assert hcto3.value == pytest.approx(1.0, DELTA) + assert hcto3.unit == "kJ kg^-1 C^-1" + + hcto4 = hc.to("cal g^-1 C^-1") + + assert hcto4.value == pytest.approx(0.2390057, DELTA) + assert hcto4.unit == "cal g^-1 C^-1" + + hcto5 = hc.to("cal kg^-1 C^-1") + + assert hcto5.value == pytest.approx(239.0057, DELTA) + assert hcto5.unit == "cal kg^-1 C^-1" + + hcto6 = hc.to("kcal kg^-1 C^-1") + + assert hcto6.value == pytest.approx(0.2390057, DELTA) + assert hcto6.unit == "kcal kg^-1 C^-1" + + hcto7 = hc.to("BTU lb^-1 F^-1") + + assert hcto7.value == pytest.approx(0.238845, DELTA) + assert hcto7.unit == "BTU lb^-1 F^-1" + + +def test_temp_54(): + temp_var = q.Quantity(1.0, "kg m^-3 s^-1 K^2") + + temp_varto1 = temp_var.to("g cm^-3 s^-1 K^2") + + assert temp_varto1.value == pytest.approx(0.001, DELTA) + assert temp_varto1.unit == "g cm^-3 s^-1 K^2" + + temp_varto2 = temp_var.to("kg mm^-3 s^-1 K^2") + + assert temp_varto2.value == pytest.approx(1e-09, DELTA) + assert temp_varto2.unit == "kg mm^-3 s^-1 K^2" + + temp_varto3 = temp_var.to("kg um^-3 s^-1 K^2") + + assert temp_varto3.value == pytest.approx(9.999999999999999e-19, DELTA) + assert temp_varto3.unit == "kg um^-3 s^-1 K^2" + + temp_varto4 = temp_var.to("mg mm^-3 ms^-1 K^2") + + assert temp_varto4.value == pytest.approx(1.0000000000000002e-06, DELTA) + assert temp_varto4.unit == "mg mm^-3 ms^-1 K^2" + + temp_varto5 = temp_var.to("g cm^-3 us^-1 K^2") + + assert temp_varto5.value == pytest.approx(1e-09, DELTA) + assert temp_varto5.unit == "g cm^-3 us^-1 K^2" + + temp_varto6 = temp_var.to("pg um^-3 ms^-1 K^2") + + assert temp_varto6.value == pytest.approx(9.999999999999997e-07, DELTA) + assert temp_varto6.unit == "pg um^-3 ms^-1 K^2" + + +def test_power_56(): + qt = q.Quantity(5.0, "m^0") + qtm = qt * 2 + + assert qtm.value == 10.0 + assert qtm.unit == "" + + +def testing_dimensions(): + print(f"{'*' * 25} {testing_dimensions.__name__} {'*' * 25}") + + def dim_test(unit_str, dim_list): + qt = q.Quantity(10, unit_str) + print(f"{unit_str} : {qt.get_dimensions_list()}") + assert qt.get_dimensions_list() == dim_list + + dim_test("m", [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("m s^-1", [0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("kg m s^-2 m^-2", [1.0, -1.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("Pa", [1.0, -1.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("kPa", [1.0, -1.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("Pa^2", [2.0, -2.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("daPa", [1.0, -1.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("MPa", [1.0, -1.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("kPa^2", [2.0, -2.0, -4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("slug in^-1 s^-1", [1.0, -1.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("radian", [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + dim_test("ohm", [1.0, 2.0, -3.0, 0.0, 0.0, 0.0, 0.0, -2.0, 0.0, 0.0]) + dim_test("lb cm s^-2", [1.0, 1.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + print("-" * 75) + + +def testing_to_systems(): + print(f"{'*' * 25} {testing_to_systems.__name__} {'*' * 25}") + test = q.Quantity(90, "lb cm s^-2") + print(f"test : {test.get_dimensions_list()}") # [1 1 -2 0 -2 0 0 0 0] + + test.to("kg m s^-2") + test.to("g m s^-2") + test.to("g cm s^-2") + test.to("g in s^-2") + test.to("g ft s^-2") + test.to("kg ft s^-2") + test.to("kg in s^-1 s^-1") + try: + test.to("ft s^-2") + except ValueError as ve: + print(ve) + + print("-" * 75) + + +def testing_multipliers(): + print(f"{'*' * 25} {testing_multipliers.__name__} {'*' * 25}") + + def from_to(from_str, to_str): + qt = q.Quantity(1, from_str) + to = qt.to(to_str) + print(f"from {qt} -> to {to}") + + from_to("mm", "cm") + from_to("m", "ft") + from_to("dm^3", "m^3") + from_to("m s^-1", "cm s^-1") + from_to("N", "dyne") + from_to("m^2", "in^2") + from_to("degree s^-1", "radian s^-1") + from_to("radian s^-1", "degree s^-1") + from_to("Pa", "lb m s^-2 ft^-2") + from_to("lb m s^-2 ft^-2", "Pa") + + from_to("J kg^-1 K^-1", "J kg^-1 C^-1") + from_to("J kg^-1 K^-1", "J kg^-1 R^-1") + from_to("J kg^-1 K^-1", "J kg^-1 F^-1") + + from_to("K", "C") + from_to("K", "R") + from_to("K", "F") + + print("-" * 75) + + +def testing_arithmetic_operators(): + print(f"{'*' * 25} {testing_arithmetic_operators.__name__} {'*' * 25}") + + qt1 = q.Quantity(10, "m s^-1") + qt2 = q.Quantity(5, "m s^-1") + + qt3 = qt1 * qt2 + + print(f"{qt1} * {qt2} = {qt3}") + assert qt3.value == 50 + assert qt3.unit == "m^2 s^-2" + + result = qt1 * 2 + print(f"{qt1} * {2} = {result}") + assert result.value == 20 + assert result.unit == "m s^-1" + + result1 = 2 * qt1 + print(f"{2} * {qt1} = {result1}") + assert result1.value == 20 + assert result1.unit == "m s^-1" + + q3 = qt1 / qt2 + + print(f"{qt1} / {qt2} = {q3}") + assert q3.value == 2 + assert q3.unit == "" + + result3 = qt1 / 2 + print(f"{qt1} / {2} = {qt1 / 2}") + assert result3.value == 5 + assert result3.unit == "m s^-1" + + qa3 = qt1 + qt2 + + print(f"{qt1} + {qt2} = {qa3}") + assert qa3.value == 15 + assert qa3.unit == "m s^-1" + + try: + result5 = qt1 + 2 + print(f"{qt1} + {2} = {result5}") + except ValueError as ve: + print(ve) + + try: + result6 = 2 + qt1 + print(f"{2} + {qt1} = {result6}") + except ValueError as ve: + print(ve) + + qs3 = qt1 - qt2 + + print(f"{qt1} - {qt2} = {qs3}") + assert qs3.value == 5 + assert qs3.unit == "m s^-1" + + try: + result7 = qt1 - 2 + print(f"{qt1} - {2} = {result7}") + except ValueError as ve: + print(ve) + + try: + result8 = 2 - qt1 + print(f"{2} - {qt1} = {result8}") + except ValueError as ve: + print(ve) + + +def testing_properties(): + print(f"{'*' * 25} {testing_properties.__name__} {'*' * 25}") + + v = q.Quantity(1, "cm s^-1") + print(f"value = {v.value}") + print(f"unit = {v.unit}") + print(f"si value = {v._si_value}") + print(f"si unit = {v._si_unit}") + print(f"is dimensionless? = {v.is_dimension_less()}") + print(f"dimensions = {v.get_dimensions_list()}") + + qt1 = q.Quantity(10, "m s^-1") + qt2 = q.Quantity(5, "m s^-1") + + +# if __name__ == "__main__": +# test_value_unit_1() +# testing_dimensions() +# testing_multipliers() +# testing_to_systems() +# testing_arithmetic_operators() +# testing_properties() + +# x = q.Quantity(1, "ft") +# print( +# f"User unit: {x._unit.user_unit}, multiplier: {x._unit.si_factor}, reduced_si_unit: {x._unit.si_unit}, si_value: {x._si_value}" +# )