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

1""" 

2Unit system for physical quantities; include definition of constants. 

3""" 

4 

5from typing import Dict as tDict, Set as tSet 

6 

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 

14 

15from .dimensions import Dimension 

16 

17 

18class UnitSystem(_QuantityMapper): 

19 """ 

20 UnitSystem represents a coherent set of units. 

21 

22 A unit system is basically a dimension system with notions of scales. Many 

23 of the methods are defined in the same way. 

24 

25 It is much better if all base units have a symbol. 

26 """ 

27 

28 _unit_systems = {} # type: tDict[str, UnitSystem] 

29 

30 def __init__(self, base_units, units=(), name="", descr="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}): 

31 

32 UnitSystem._unit_systems[name] = self 

33 

34 self.name = name 

35 self.descr = descr 

36 

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 

42 

43 super().__init__() 

44 

45 def __str__(self): 

46 """ 

47 Return the name of the system. 

48 

49 If it does not exist, then it makes a list of symbols (or names) of 

50 the base dimensions. 

51 """ 

52 

53 if self.name != "": 

54 return self.name 

55 else: 

56 return "UnitSystem((%s))" % ", ".join( 

57 str(d) for d in self._base_units) 

58 

59 def __repr__(self): 

60 return '<UnitSystem: %s>' % repr(self._base_units) 

61 

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. 

64 

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 """ 

69 

70 base = self._base_units + tuple(base) 

71 units = self._units + tuple(units) 

72 

73 return UnitSystem(base, units, name, description, dimension_system, {**self._derived_units, **derived_units}) 

74 

75 def get_dimension_system(self): 

76 return self._dimension_system 

77 

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) 

83 

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) 

89 

90 @staticmethod 

91 def get_unit_system(unit_system): 

92 if isinstance(unit_system, UnitSystem): 

93 return unit_system 

94 

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 ) 

102 

103 return UnitSystem._unit_systems[unit_system] 

104 

105 @staticmethod 

106 def get_default_unit_system(): 

107 return UnitSystem._unit_systems["SI"] 

108 

109 @property 

110 def dim(self): 

111 """ 

112 Give the dimension of the system. 

113 

114 That is return the number of units forming the basis. 

115 """ 

116 return len(self._base_units) 

117 

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 

125 

126 @property 

127 def derived_units(self) -> tDict[Dimension, Quantity]: 

128 return self._derived_units 

129 

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 

151 

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) 

200 

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))