Spaces:
Sleeping
Sleeping
""" | |
Unit system for physical quantities; include definition of constants. | |
""" | |
from typing import Dict as tDict, Set as tSet | |
from sympy.core.add import Add | |
from sympy.core.function import (Derivative, Function) | |
from sympy.core.mul import Mul | |
from sympy.core.power import Pow | |
from sympy.core.singleton import S | |
from sympy.physics.units.dimensions import _QuantityMapper | |
from sympy.physics.units.quantities import Quantity | |
from .dimensions import Dimension | |
class UnitSystem(_QuantityMapper): | |
""" | |
UnitSystem represents a coherent set of units. | |
A unit system is basically a dimension system with notions of scales. Many | |
of the methods are defined in the same way. | |
It is much better if all base units have a symbol. | |
""" | |
_unit_systems = {} # type: tDict[str, UnitSystem] | |
def __init__(self, base_units, units=(), name="", descr="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}): | |
UnitSystem._unit_systems[name] = self | |
self.name = name | |
self.descr = descr | |
self._base_units = base_units | |
self._dimension_system = dimension_system | |
self._units = tuple(set(base_units) | set(units)) | |
self._base_units = tuple(base_units) | |
self._derived_units = derived_units | |
super().__init__() | |
def __str__(self): | |
""" | |
Return the name of the system. | |
If it does not exist, then it makes a list of symbols (or names) of | |
the base dimensions. | |
""" | |
if self.name != "": | |
return self.name | |
else: | |
return "UnitSystem((%s))" % ", ".join( | |
str(d) for d in self._base_units) | |
def __repr__(self): | |
return '<UnitSystem: %s>' % repr(self._base_units) | |
def extend(self, base, units=(), name="", description="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}): | |
"""Extend the current system into a new one. | |
Take the base and normal units of the current system to merge | |
them to the base and normal units given in argument. | |
If not provided, name and description are overridden by empty strings. | |
""" | |
base = self._base_units + tuple(base) | |
units = self._units + tuple(units) | |
return UnitSystem(base, units, name, description, dimension_system, {**self._derived_units, **derived_units}) | |
def get_dimension_system(self): | |
return self._dimension_system | |
def get_quantity_dimension(self, unit): | |
qdm = self.get_dimension_system()._quantity_dimension_map | |
if unit in qdm: | |
return qdm[unit] | |
return super().get_quantity_dimension(unit) | |
def get_quantity_scale_factor(self, unit): | |
qsfm = self.get_dimension_system()._quantity_scale_factors | |
if unit in qsfm: | |
return qsfm[unit] | |
return super().get_quantity_scale_factor(unit) | |
def get_unit_system(unit_system): | |
if isinstance(unit_system, UnitSystem): | |
return unit_system | |
if unit_system not in UnitSystem._unit_systems: | |
raise ValueError( | |
"Unit system is not supported. Currently" | |
"supported unit systems are {}".format( | |
", ".join(sorted(UnitSystem._unit_systems)) | |
) | |
) | |
return UnitSystem._unit_systems[unit_system] | |
def get_default_unit_system(): | |
return UnitSystem._unit_systems["SI"] | |
def dim(self): | |
""" | |
Give the dimension of the system. | |
That is return the number of units forming the basis. | |
""" | |
return len(self._base_units) | |
def is_consistent(self): | |
""" | |
Check if the underlying dimension system is consistent. | |
""" | |
# test is performed in DimensionSystem | |
return self.get_dimension_system().is_consistent | |
def derived_units(self) -> tDict[Dimension, Quantity]: | |
return self._derived_units | |
def get_dimensional_expr(self, expr): | |
from sympy.physics.units import Quantity | |
if isinstance(expr, Mul): | |
return Mul(*[self.get_dimensional_expr(i) for i in expr.args]) | |
elif isinstance(expr, Pow): | |
return self.get_dimensional_expr(expr.base) ** expr.exp | |
elif isinstance(expr, Add): | |
return self.get_dimensional_expr(expr.args[0]) | |
elif isinstance(expr, Derivative): | |
dim = self.get_dimensional_expr(expr.expr) | |
for independent, count in expr.variable_count: | |
dim /= self.get_dimensional_expr(independent)**count | |
return dim | |
elif isinstance(expr, Function): | |
args = [self.get_dimensional_expr(arg) for arg in expr.args] | |
if all(i == 1 for i in args): | |
return S.One | |
return expr.func(*args) | |
elif isinstance(expr, Quantity): | |
return self.get_quantity_dimension(expr).name | |
return S.One | |
def _collect_factor_and_dimension(self, expr): | |
""" | |
Return tuple with scale factor expression and dimension expression. | |
""" | |
from sympy.physics.units import Quantity | |
if isinstance(expr, Quantity): | |
return expr.scale_factor, expr.dimension | |
elif isinstance(expr, Mul): | |
factor = 1 | |
dimension = Dimension(1) | |
for arg in expr.args: | |
arg_factor, arg_dim = self._collect_factor_and_dimension(arg) | |
factor *= arg_factor | |
dimension *= arg_dim | |
return factor, dimension | |
elif isinstance(expr, Pow): | |
factor, dim = self._collect_factor_and_dimension(expr.base) | |
exp_factor, exp_dim = self._collect_factor_and_dimension(expr.exp) | |
if self.get_dimension_system().is_dimensionless(exp_dim): | |
exp_dim = 1 | |
return factor ** exp_factor, dim ** (exp_factor * exp_dim) | |
elif isinstance(expr, Add): | |
factor, dim = self._collect_factor_and_dimension(expr.args[0]) | |
for addend in expr.args[1:]: | |
addend_factor, addend_dim = \ | |
self._collect_factor_and_dimension(addend) | |
if not self.get_dimension_system().equivalent_dims(dim, addend_dim): | |
raise ValueError( | |
'Dimension of "{}" is {}, ' | |
'but it should be {}'.format( | |
addend, addend_dim, dim)) | |
factor += addend_factor | |
return factor, dim | |
elif isinstance(expr, Derivative): | |
factor, dim = self._collect_factor_and_dimension(expr.args[0]) | |
for independent, count in expr.variable_count: | |
ifactor, idim = self._collect_factor_and_dimension(independent) | |
factor /= ifactor**count | |
dim /= idim**count | |
return factor, dim | |
elif isinstance(expr, Function): | |
fds = [self._collect_factor_and_dimension(arg) for arg in expr.args] | |
dims = [Dimension(1) if self.get_dimension_system().is_dimensionless(d[1]) else d[1] for d in fds] | |
return (expr.func(*(f[0] for f in fds)), *dims) | |
elif isinstance(expr, Dimension): | |
return S.One, expr | |
else: | |
return expr, Dimension(1) | |
def get_units_non_prefixed(self) -> tSet[Quantity]: | |
""" | |
Return the units of the system that do not have a prefix. | |
""" | |
return set(filter(lambda u: not u.is_prefixed and not u.is_physical_constant, self._units)) | |