def GetValue(self, unit=None): return UnitDatabase.GetSingleton().Convert( self._value_quantity.GetQuantityType(), self._value_quantity.GetUnit(), unit or self._unit, self._value, )
def _CreateDerived( cls, category_to_unit_and_exps: Dict[str, Sequence[Union[str, int]]], validate_category_and_units: bool = True, unknown_unit_caption: Optional[str] = None, ) -> "Quantity": """ Create a category that represents a derived quantity (usually resulting from operations among quantities). :type category_to_unit_and_exps: OrderedDict(str->(list(str, int))) :param category_to_unit_and_exps: This odict defines the category as well as the unit in a way that we can specify exponents. :param bool validate_category_and_units: If True, the category and units will be validated (otherwise, it'll just create it without trying to validate any value (note that it's meant to be used internally to make creating the quantity faster without having to actually validate it) :rtype: cls :returns: An instance that represents a quantity with the given categories, units and exponents. """ unit_database = UnitDatabase.GetSingleton() if validate_category_and_units: assert hasattr( category_to_unit_and_exps, "items" ), "validate_category_and_units needs to be a dict" for category, (unit, _exp) in category_to_unit_and_exps.items(): # will do the checkings needed for validation (category/unit) # so that we can later just store the internal information without # any other check. # changes to accomodate making operations with derived units (internally the # information must be a dict) if category.__class__ != str: raise TypeError("Only str is accepted. %s is not." % category.__class__) # Getting the category should be enough to know that it's valid. category_info = unit_database.GetCategoryInfo(category) # if no unit was given, assume base unit for this quantity_type if unit is None: unit = category_info.default_unit else: if not isinstance(unit, str): raise TypeError("Only str is accepted. %s is not." % unit.__class__) unit_database.CheckQuantityTypeUnit(category_info.quantity_type, unit) return ObtainQuantity( OrderedDict( (category, unit_and_exp[:]) for (category, unit_and_exp) in category_to_unit_and_exps.items() ), unknown_unit_caption=unknown_unit_caption, )
def _ObtainQuantity(unit, category=None): if not category: unit_database = UnitDatabase.GetSingleton() category = unit_database.GetDefaultCategory(unit) if not category: raise InvalidUnitError(unit) key = (category, unit) quantity = _quantities_cache.get(key) if quantity is None: quantity = _quantities_cache.setdefault( key, _SimpleQuantity(category, unit)) return quantity
def _InternalCreateWithQuantity(self, quantity, values=None, unit_database=None, value=None): if value is not None: if values is not None: raise ValueError("Duplicated values parameter given") values = value self._value = values self._unit_database = unit_database or UnitDatabase.GetSingleton() self._quantity = quantity self._is_valid = None self._validity_exception = None
def _InternalCreateWithQuantity( # type:ignore[override] self, quantity: Quantity, values: Optional[ValuesType] = None, unit_database: Optional[UnitDatabase] = None, value: Optional[ValuesType] = None, ) -> None: if value is not None: if values is not None: raise ValueError("Duplicated values parameter given") values = value assert values is not None self._value = values self._unit_database = unit_database or UnitDatabase.GetSingleton() self._quantity = quantity self._is_valid: Optional[bool] = None self._validity_exception: Optional[Exception] = None
def FromScalars(cls, scalars: Iterable[Scalar], *, unit: Optional[str] = None, category: Optional[str] = None) -> "Array": """ Create an Array from a sequence of Scalars. When not defined, the unit and category assumed will be from the first Scalar on the sequence. :param values: A sequence of Scalars. When the values parameter is an empty sequence and the unit is not provided an Array with an empty quantity will be returned. :param unit: A string representing the unit, if not defined the unit from the first Scalar on the sequence will be used. :param category: A string representing the category, if not defined the category from the first Scalar on the sequence will be used. """ scalars = iter(scalars) try: first_scalar = next(scalars) except StopIteration: if unit is None and category is None: return cls.CreateEmptyArray() elif category is None: category = UnitDatabase.GetSingleton().GetDefaultCategory(unit) return cls(values=[], unit=unit, category=category) # type:ignore[arg-type] else: assert unit is None return cls( # type:ignore[call-overload] values=[], unit=unit, category=category) # This actually will raise an exception unit = unit or first_scalar.unit category = category or first_scalar.category values = [first_scalar.GetValue(unit) ] + [scalar.GetValue(unit) for scalar in scalars] return cls(values=values, unit=unit, category=category)
def __init__(self, category: Union[str, Quantity], value: Any = None, unit: Optional[str] = None): # NOTE: we don't even try to add overloads here because each subclass defines their own # overloads, often in confusing ways; adding them here would just add to the # confusion. unit_database = UnitDatabase.GetSingleton() if isinstance(category, Quantity): quantity = category assert unit is None, "If quantity is given, the unit must not!" if value is None: value = self._GetDefaultValue(quantity.GetCategoryInfo()) else: if not isinstance(category, str): # Support for creating a scalar as: # Scalar(10, 'm') # Scalar(10, 'm', 'length') value, unit, category = category, value, unit # type:ignore[assignment] if value is None or unit is None: if value is None: category_info = unit_database.GetCategoryInfo(category) value = self._GetDefaultValue(category_info, unit) if unit is None: unit = category_info.default_unit assert unit is not None else: # Note: if the category is given with a value, the unit MUST also be given. # (because if later we change the default unit, the original category would be wrong). assert ( unit is not None ), "If category and value are given, the unit must be specified too." quantity = ObtainQuantity( unit, category) # type:ignore[arg-type, assignment] self._quantity = quantity self._InternalCreateWithQuantity(quantity, value, unit_database)
def __init__(self, category, value=None, unit=None): unit_database = UnitDatabase.GetSingleton() if category.__class__ == _Quantity: quantity = category assert unit is None, "If quantity is given, the unit must not!" if value is None: value = self._GetDefaultValue(quantity.GetCategoryInfo()) else: if category.__class__ != six.text_type: # Support for creating a scalar as: # Scalar(10, 'm') # Scalar(10, 'm', 'length') value, unit, category = category, value, unit if value is None or unit is None: if value is None: category_info = unit_database.GetCategoryInfo(category) value = self._GetDefaultValue(category_info, unit) if unit is None: unit = category_info.default_unit assert unit is not None else: # Note: if the category is given with a value, the unit MUST also be given. # (because if later we change the default unit, the original category would be wrong). assert ( unit is not None ), "If category and value are given, the unit must be specified too." assert type(unit) is not bytes assert type(category) is not bytes quantity = ObtainQuantity(unit, category) self._InternalCreateWithQuantity(quantity, value, unit_database)
def testPoscPermeabilityLength(unit_database_posc): unit_database = UnitDatabase.GetSingleton() assert "volume" == unit_database.GetQuantityType("mD.ft") assert "permeability length" == unit_database.GetDefaultCategory("mD.ft")
def GetUnitDatabase(self): return UnitDatabase.GetSingleton()
def GetQuantityType(self): unit_database = UnitDatabase.GetSingleton() return unit_database.GetCategoryQuantityType(self._category)
def __init__(self, category, unit, unknown_unit_caption=None): """ :type category: str or odict :param category: The category to which the new quantity should be bound or an odict with information on the derived category/unit (in which case the unit parameter is ignored). :param str unit: The unit which the new quantity should have. """ try: # Bail out if unit is already configured: this may happen when the Quantity constructor # is called directly: if Quantity() is called, the protocol will call __new__ and even # if a configured instance is returned, it'll call __init__ again, so, this is a check # for this situation so that we return if the quantity is already configured. self._unknown_unit_caption return except AttributeError: pass # Keep self._unit_database for faster access. unit_database = self._unit_database = UnitDatabase.GetSingleton() if unknown_unit_caption is not None: assert ( unknown_unit_caption.__class__ == str ), "Unit caption must be a string. Note: the unit database and unit system parameters were removed." self._unknown_unit_caption = unknown_unit_caption else: self._unknown_unit_caption = "" if category.__class__ is OrderedDict: assert unit is None self._category_to_unit_and_exps = category self._is_derived = True rep_and_exp = OrderedDict() for category, (_unit, exp) in self._category_to_unit_and_exps.items(): quantity_type = unit_database.GetCategoryQuantityType(category) existing = rep_and_exp.get(quantity_type, 0) rep_and_exp[quantity_type] = existing + exp self._category = self._MakeStr([ (category, exp) for category, (_unit, exp) in self._category_to_unit_and_exps.items() ]) self._quantity_type = self._MakeStr(list(rep_and_exp.items())) self._unit = self._CreateUnitsWithJoinedExponentsString() self._composing_units = tuple((unit, exp) for _category, ( unit, exp) in self._category_to_unit_and_exps.items()) self._composing_categories = tuple( self._category_to_unit_and_exps.keys()) self._category_info = None return self._is_derived = False # changes to accommodate making operations with derived units (internally the # information must be a dict) if category.__class__ != str: raise TypeError("Only str is accepted. %s is not." % category.__class__) # Getting the category should be enough to check that it's valid. category_info = unit_database.GetCategoryInfo(category) self._category_info = category_info # if no unit was given, assume base unit for this quantity_type if unit is None: unit = category_info.default_unit else: if unit.__class__ != str: raise TypeError("Only str is accepted. %s is not." % unit.__class__) try: unit_database.CheckCategoryUnit(category, unit) except InvalidUnitError as e: is_legacy, unit = FixUnitIfIsLegacy(unit) if is_legacy: unit_database.CheckCategoryUnit(category, unit) else: raise e # store it as odict so that we can have a decent order when creating a string from # an operation. category_to_unit_and_exps = OrderedDict() category_to_unit_and_exps[category] = [unit, 1] self._category_to_unit_and_exps = category_to_unit_and_exps self._category = category self._quantity_type = unit_database.GetCategoryQuantityType(category) self._unit = unit self._tobase = self._unit_database.GetInfo(self._quantity_type, self._unit, fix_unknown=True).tobase self._composing_units = unit self._composing_categories = category
def ObtainQuantity(unit, category=None, unknown_unit_caption=None): """ :type unit: str or OrderedDict(str -> list(str, int)) :param unit: Either the string representing the unit or an ordered dict with the composing unit information (if composing all the info, including the category will be received in this parameter). :param str category: The category for the quantity. If not given it's gotten based on the unit passed. :param str unknown_unit_caption: The caption for the unit (used if unknown). :rtype Quantity: """ unit_database = UnitDatabase.GetSingleton() quantities_cache = unit_database.quantities_cache if unit.__class__ in (list, tuple): # It may be a derived unit with list(tuple(str, int)) -- in which case the category # must also be a list (of the same size) if len(unit) == 1 and unit[0][1] == 1: # Although passed as composing, it's a simple case unit = unit[0][0] if category.__class__ in (list, tuple): category = category[0] else: assert category.__class__ in (list, tuple) unit = OrderedDict((cat, unit_and_exp) for (cat, unit_and_exp) in zip(category, unit)) category = None if hasattr(unit, "items"): assert category is None if len(unit) == 1 and next(iter(unit.values()))[1] == 1: # Although passed as composing, it's a simple case category, (unit, _exp) = next(iter(unit.items())) else: key = tuple((category, tuple(unit_and_exp)) for (category, unit_and_exp) in unit.items()) if unknown_unit_caption: key += (unknown_unit_caption, ) try: return quantities_cache[key] except KeyError: quantity = quantities_cache[key] = _Quantity( unit, None, unknown_unit_caption) return quantity key = (category, unit, unknown_unit_caption) try: return quantities_cache[key] except KeyError: pass # Just go on with the regular flow. assert unit.__class__ != bytes, "unit must be given as str string always" if unit.__class__ != str: if category is None: raise AssertionError("Currently only supporting unit as a string.") else: # Unit is given by the category unit = unit_database.GetDefaultUnit(category) quantity = quantities_cache[key] = _Quantity(category, unit, unknown_unit_caption) return quantity elif category is None: category = unit_database.GetDefaultCategory(unit) if not category: is_legacy, unit = FixUnitIfIsLegacy(unit) if is_legacy: category = unit_database.GetDefaultCategory(unit) else: raise UnitsError(f"Unable to get default category for: {unit}") key_with_resolved_category = (category, unit, unknown_unit_caption) try: return quantities_cache[key_with_resolved_category] except KeyError: quantity = quantities_cache[ key_with_resolved_category] = _Quantity( category, unit, unknown_unit_caption) # Cache it with None category too. quantities_cache[key] = quantity return quantity else: quantities_cache[key] = quantity = _Quantity(category, unit, unknown_unit_caption) return quantity
def ObtainQuantity( unit: Union[str, None, List[UnitExponentTuple], Dict[str, Sequence[Union[str, int]]]], category: Optional[Union[Tuple[str, ...], str]] = None, unknown_unit_caption: Optional[str] = None, ) -> "Quantity": """ :type unit: str or OrderedDict(str -> list(str, int)) :param unit: Either the string representing the unit or an ordered dict with the composing unit information (if composing all the info, including the category will be received in this parameter). :param str category: The category for the quantity. If not given it's gotten based on the unit passed. :param str unknown_unit_caption: The caption for the unit (used if unknown). :rtype Quantity: """ unit_database = UnitDatabase.GetSingleton() quantities_cache = unit_database.quantities_cache if isinstance(unit, (list, tuple)): # It may be a derived unit with list(tuple(str, int)) -- in which case the category # must also be a list (of the same size) if len(unit) == 1 and unit[0][1] == 1: # Although passed as composing, it's a simple case unit = unit[0][0] if isinstance(category, (list, tuple)): category = category[0] else: assert isinstance(category, (list, tuple)) unit = OrderedDict((cat, unit_and_exp) for (cat, unit_and_exp) in zip(category, unit)) category = None if isinstance(unit, dict): assert category is None if len(unit) == 1 and next(iter(unit.values()))[1] == 1: # type:ignore[comparison-overlap] # Although passed as composing, it's a simple case category, (unit, _exp) = next(iter(unit.items())) # type:ignore[assignment] else: key: List[Any] = [ (category, tuple(unit_and_exp)) for (category, unit_and_exp) in unit.items() ] if unknown_unit_caption: key.append(unknown_unit_caption) try: return quantities_cache[tuple(key)] except KeyError: quantity = quantities_cache[tuple(key)] = Quantity(unit, None, unknown_unit_caption) return quantity key = (category, unit, unknown_unit_caption) # type:ignore[assignment] try: return quantities_cache[key] except KeyError: pass # Just go on with the regular flow. if not isinstance(unit, str): if category is None: raise AssertionError("Currently only supporting unit as a string.") else: # Unit is given by the category unit = unit_database.GetDefaultUnit(category) quantity = quantities_cache[key] = Quantity(category, unit, unknown_unit_caption) return quantity elif category is None: category = unit_database.GetDefaultCategory(unit) if not category: is_legacy, unit = FixUnitIfIsLegacy(unit) if is_legacy: category = unit_database.GetDefaultCategory(unit) else: raise UnitsError(f"Unable to get default category for: {unit}") key_with_resolved_category = (category, unit, unknown_unit_caption) try: return quantities_cache[key_with_resolved_category] except KeyError: quantity = quantities_cache[key_with_resolved_category] = Quantity( category, unit, unknown_unit_caption ) # Cache it with None category too. quantities_cache[key] = quantity return quantity else: quantities_cache[key] = quantity = Quantity(category, unit, unknown_unit_caption) return quantity
def testOperation(): unit_database = UnitDatabase.GetSingleton() unit_database.CheckDefaultUnitDatabase() q = ObtainQuantity(unit="m", category="length") SCALAR_OPERATION = [ operator.add, operator.sub, operator.truediv, operator.mul, operator.mod, ] SCALAR_TYPES = six.integer_types + (float, ) for operation in SCALAR_OPERATION: for cast_type in SCALAR_TYPES: # Left q2 = operation(cast_type(2), q) assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" # Right q2 = operation(q, cast_type(2)) assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" q2 = abs(q) assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" q2 = q + q assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" q2 = q - q assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" q2 = q * int(2) assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" q2 = q * 2.0 assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" q2 = q * q assert q2.GetQuantityType() == "(length) ** 2" assert q2.GetUnit() == "m2" q2 = q * q * q assert q2.GetQuantityType() == "(length) ** 3" assert q2.GetUnit() == "m3" # Check pow q2 = q**1 assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" q2 = q**2 assert q2.GetQuantityType() == "(length) ** 2" assert q2.GetUnit() == "m2" q2 = q**3 assert q2.GetQuantityType() == "(length) ** 3" assert q2.GetUnit() == "m3" q2 = pow(q, 3) assert q2.GetQuantityType() == "(length) ** 3" assert q2.GetUnit() == "m3" q2 = (q * q) / q assert q2.GetQuantityType() == "length" assert q2.GetUnit() == "m" q2 = (q * q) / ObtainQuantity(unit="d", category="time") assert q2.GetQuantityType() == "(length) ** 2 / time" assert q2.GetUnit() == "m2/d" with pytest.raises(TypeError): operator.mul(q, "s")