123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- """
- Unit system for physical quantities; include definition of constants.
- """
- from typing import Dict as tDict
- 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 .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):
- 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 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.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 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 S.One, expr
- else:
- return expr, Dimension(1)
|