214 lines
7.3 KiB
Python
214 lines
7.3 KiB
Python
"""
|
|
Unit system for physical quantities; include definition of constants.
|
|
"""
|
|
|
|
from typing import Dict
|
|
|
|
from sympy import S, Mul, Pow, Add, Function, Derivative
|
|
from sympy.physics.units.dimensions import _QuantityMapper
|
|
|
|
from sympy.utilities.exceptions import SymPyDeprecationWarning
|
|
|
|
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: Dict[str, UnitSystem]
|
|
|
|
def __init__(self, base_units, units=(), name="", descr="", dimension_system=None):
|
|
|
|
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)
|
|
|
|
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):
|
|
"""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)
|
|
|
|
def print_unit_base(self, unit):
|
|
"""
|
|
Useless method.
|
|
|
|
DO NOT USE, use instead ``convert_to``.
|
|
|
|
Give the string expression of a unit in term of the basis.
|
|
|
|
Units are displayed by decreasing power.
|
|
"""
|
|
SymPyDeprecationWarning(
|
|
deprecated_since_version="1.2",
|
|
issue=13336,
|
|
feature="print_unit_base",
|
|
useinstead="convert_to",
|
|
).warn()
|
|
from sympy.physics.units import convert_to
|
|
return convert_to(unit, self._base_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)
|
|
|
|
@staticmethod
|
|
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]
|
|
|
|
@staticmethod
|
|
def get_default_unit_system():
|
|
return UnitSystem._unit_systems["SI"]
|
|
|
|
@property
|
|
def dim(self):
|
|
"""
|
|
Give the dimension of the system.
|
|
|
|
That is return the number of units forming the basis.
|
|
"""
|
|
return len(self._base_units)
|
|
|
|
@property
|
|
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 get_dimensional_expr(self, expr):
|
|
from sympy import Mul, Add, Pow, Derivative
|
|
from sympy import Function
|
|
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 exp_dim.is_dimensionless:
|
|
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 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]
|
|
return (expr.func(*(f[0] for f in fds)),
|
|
expr.func(*(d[1] for d in fds)))
|
|
elif isinstance(expr, Dimension):
|
|
return 1, expr
|
|
else:
|
|
return expr, Dimension(1)
|