Coverage for /usr/lib/python3/dist-packages/sympy/physics/units/unitsystem.py: 46%
125 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""
2Unit system for physical quantities; include definition of constants.
3"""
5from typing import Dict as tDict, Set as tSet
7from sympy.core.add import Add
8from sympy.core.function import (Derivative, Function)
9from sympy.core.mul import Mul
10from sympy.core.power import Pow
11from sympy.core.singleton import S
12from sympy.physics.units.dimensions import _QuantityMapper
13from sympy.physics.units.quantities import Quantity
15from .dimensions import Dimension
18class UnitSystem(_QuantityMapper):
19 """
20 UnitSystem represents a coherent set of units.
22 A unit system is basically a dimension system with notions of scales. Many
23 of the methods are defined in the same way.
25 It is much better if all base units have a symbol.
26 """
28 _unit_systems = {} # type: tDict[str, UnitSystem]
30 def __init__(self, base_units, units=(), name="", descr="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}):
32 UnitSystem._unit_systems[name] = self
34 self.name = name
35 self.descr = descr
37 self._base_units = base_units
38 self._dimension_system = dimension_system
39 self._units = tuple(set(base_units) | set(units))
40 self._base_units = tuple(base_units)
41 self._derived_units = derived_units
43 super().__init__()
45 def __str__(self):
46 """
47 Return the name of the system.
49 If it does not exist, then it makes a list of symbols (or names) of
50 the base dimensions.
51 """
53 if self.name != "":
54 return self.name
55 else:
56 return "UnitSystem((%s))" % ", ".join(
57 str(d) for d in self._base_units)
59 def __repr__(self):
60 return '<UnitSystem: %s>' % repr(self._base_units)
62 def extend(self, base, units=(), name="", description="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}):
63 """Extend the current system into a new one.
65 Take the base and normal units of the current system to merge
66 them to the base and normal units given in argument.
67 If not provided, name and description are overridden by empty strings.
68 """
70 base = self._base_units + tuple(base)
71 units = self._units + tuple(units)
73 return UnitSystem(base, units, name, description, dimension_system, {**self._derived_units, **derived_units})
75 def get_dimension_system(self):
76 return self._dimension_system
78 def get_quantity_dimension(self, unit):
79 qdm = self.get_dimension_system()._quantity_dimension_map
80 if unit in qdm:
81 return qdm[unit]
82 return super().get_quantity_dimension(unit)
84 def get_quantity_scale_factor(self, unit):
85 qsfm = self.get_dimension_system()._quantity_scale_factors
86 if unit in qsfm:
87 return qsfm[unit]
88 return super().get_quantity_scale_factor(unit)
90 @staticmethod
91 def get_unit_system(unit_system):
92 if isinstance(unit_system, UnitSystem):
93 return unit_system
95 if unit_system not in UnitSystem._unit_systems:
96 raise ValueError(
97 "Unit system is not supported. Currently"
98 "supported unit systems are {}".format(
99 ", ".join(sorted(UnitSystem._unit_systems))
100 )
101 )
103 return UnitSystem._unit_systems[unit_system]
105 @staticmethod
106 def get_default_unit_system():
107 return UnitSystem._unit_systems["SI"]
109 @property
110 def dim(self):
111 """
112 Give the dimension of the system.
114 That is return the number of units forming the basis.
115 """
116 return len(self._base_units)
118 @property
119 def is_consistent(self):
120 """
121 Check if the underlying dimension system is consistent.
122 """
123 # test is performed in DimensionSystem
124 return self.get_dimension_system().is_consistent
126 @property
127 def derived_units(self) -> tDict[Dimension, Quantity]:
128 return self._derived_units
130 def get_dimensional_expr(self, expr):
131 from sympy.physics.units import Quantity
132 if isinstance(expr, Mul):
133 return Mul(*[self.get_dimensional_expr(i) for i in expr.args])
134 elif isinstance(expr, Pow):
135 return self.get_dimensional_expr(expr.base) ** expr.exp
136 elif isinstance(expr, Add):
137 return self.get_dimensional_expr(expr.args[0])
138 elif isinstance(expr, Derivative):
139 dim = self.get_dimensional_expr(expr.expr)
140 for independent, count in expr.variable_count:
141 dim /= self.get_dimensional_expr(independent)**count
142 return dim
143 elif isinstance(expr, Function):
144 args = [self.get_dimensional_expr(arg) for arg in expr.args]
145 if all(i == 1 for i in args):
146 return S.One
147 return expr.func(*args)
148 elif isinstance(expr, Quantity):
149 return self.get_quantity_dimension(expr).name
150 return S.One
152 def _collect_factor_and_dimension(self, expr):
153 """
154 Return tuple with scale factor expression and dimension expression.
155 """
156 from sympy.physics.units import Quantity
157 if isinstance(expr, Quantity):
158 return expr.scale_factor, expr.dimension
159 elif isinstance(expr, Mul):
160 factor = 1
161 dimension = Dimension(1)
162 for arg in expr.args:
163 arg_factor, arg_dim = self._collect_factor_and_dimension(arg)
164 factor *= arg_factor
165 dimension *= arg_dim
166 return factor, dimension
167 elif isinstance(expr, Pow):
168 factor, dim = self._collect_factor_and_dimension(expr.base)
169 exp_factor, exp_dim = self._collect_factor_and_dimension(expr.exp)
170 if self.get_dimension_system().is_dimensionless(exp_dim):
171 exp_dim = 1
172 return factor ** exp_factor, dim ** (exp_factor * exp_dim)
173 elif isinstance(expr, Add):
174 factor, dim = self._collect_factor_and_dimension(expr.args[0])
175 for addend in expr.args[1:]:
176 addend_factor, addend_dim = \
177 self._collect_factor_and_dimension(addend)
178 if not self.get_dimension_system().equivalent_dims(dim, addend_dim):
179 raise ValueError(
180 'Dimension of "{}" is {}, '
181 'but it should be {}'.format(
182 addend, addend_dim, dim))
183 factor += addend_factor
184 return factor, dim
185 elif isinstance(expr, Derivative):
186 factor, dim = self._collect_factor_and_dimension(expr.args[0])
187 for independent, count in expr.variable_count:
188 ifactor, idim = self._collect_factor_and_dimension(independent)
189 factor /= ifactor**count
190 dim /= idim**count
191 return factor, dim
192 elif isinstance(expr, Function):
193 fds = [self._collect_factor_and_dimension(arg) for arg in expr.args]
194 dims = [Dimension(1) if self.get_dimension_system().is_dimensionless(d[1]) else d[1] for d in fds]
195 return (expr.func(*(f[0] for f in fds)), *dims)
196 elif isinstance(expr, Dimension):
197 return S.One, expr
198 else:
199 return expr, Dimension(1)
201 def get_units_non_prefixed(self) -> tSet[Quantity]:
202 """
203 Return the units of the system that do not have a prefix.
204 """
205 return set(filter(lambda u: not u.is_prefixed and not u.is_physical_constant, self._units))