Coverage for nodimo / dimension.py: 100%

113 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-26 03:10 +0000

1# ┓• Licensed under the MIT License 

2# ┏┓┏┓┏┫┓┏┳┓┏┓ Copyright (c) 2024 Rodrigo Castro 

3# ┛┗┗┛┗┻┗┛┗┗┗┛ https://nodimo.readthedocs.io 

4 

5""" 

6Product 

7======= 

8 

9This module contains the class to create a quantity dimension. 

10 

11Classes 

12------- 

13Dimension 

14 Creates the dimension of a quantity. 

15""" 

16 

17from sympy import sstr, srepr, latex, Symbol, Mul, Number, S 1abcdef

18from sympy.printing.pretty.stringpict import prettyForm 1abcdef

19from typing import Union 1abcdef

20 

21from nodimo._internal import _sympify_number, _unsympify_number 1abcdef

22 

23 

24class Dimension(dict): 1abcdef

25 """The dimension of a quantity. 

26 

27 This class inherits from dictionary and adds to it representation 

28 methods to display the dimension of a quantity in the way that is 

29 conventionally done by the literature and by the ISO 80000-1. 

30 

31 Parameters 

32 ---------- 

33 **dimensions : Number 

34 Base dimensions and exponents given as keyword arguments. 

35 """ 

36 

37 def __init__(self, **dimensions: Number): 1abcdef

38 self._dimensions: dict[str, Number] 1abcdef

39 self._is_dimensionless: bool 1abcdef

40 self._symbolic: Mul 1abcdef

41 self._set_dimensions(**dimensions) 1abcdef

42 super().__init__(**self._dimensions) 1abcdef

43 self._set_symbolic_dimension() 1abcdef

44 

45 def _set_dimensions(self, **dimensions: Number): 1abcdef

46 """Sympifies and clear null dimensional exponents.""" 

47 

48 dimensions_sp = {} 1abcdef

49 for dim, exp in dimensions.items(): 1abcdef

50 exp_sp = _sympify_number(exp) 1abcdef

51 if exp_sp != 0: 1abcdef

52 dimensions_sp[dim] = exp_sp 1abcdef

53 

54 self._dimensions = dimensions_sp 1abcdef

55 self._is_dimensionless = all(dim == 0 for dim in dimensions_sp.values()) 1abcdef

56 

57 def _set_symbolic_dimension(self): 1abcdef

58 if self._is_dimensionless: 1abcdef

59 self._symbolic = S.One 1abcdef

60 else: 

61 factors = [] 1abcdef

62 for dim, exp in self.items(): 1abcdef

63 dim_symbol = Symbol(dim, commutative=False) 1abcdef

64 factors.append(dim_symbol**exp) 1abcdef

65 self._symbolic = Mul(*factors, evaluate=False) 1abcdef

66 

67 def _copy(self): 1abcdef

68 return eval(srepr(self)) 1abcdef

69 

70 def __mul__(self, other): 1abcdef

71 if not isinstance(other, Dimension): 1abcdef

72 raise NotImplementedError(f"{self} and {other} cannot be multiplied") 1abcdef

73 

74 dimensions = self._dimensions.copy() 1abcdef

75 for dim, exp in other.items(): 1abcdef

76 if dim in dimensions: 1abcdef

77 dimensions[dim] += exp 1abcdef

78 else: 

79 dimensions[dim] = exp 1abcdef

80 

81 return Dimension(**dimensions) 1abcdef

82 

83 def __pow__(self, exponent: Number): 1abcdef

84 exponent_sp = _sympify_number(exponent) 1abcdef

85 dimensions = {} 1abcdef

86 for dim, exp in self.items(): 1abcdef

87 dimensions[dim] = exp * exponent_sp 1abcdef

88 

89 return Dimension(**dimensions) 1abcdef

90 

91 def __truediv__(self, other): 1abcdef

92 if not isinstance(other, Dimension): 1abcdef

93 raise NotImplementedError(f"{other} cannot divide {self}") 1abcdef

94 

95 return self * other**-1 1abcdef

96 

97 def __str__(self) -> str: 1abcdef

98 return sstr(self) 1abcdef

99 

100 __repr__ = __str__ 1abcdef

101 

102 def _repr_latex_(self): 1abcdef

103 """Latex representation according to IPython/Jupyter.""" 

104 

105 return f'$\\displaystyle {latex(self)}$' 1abcdef

106 

107 def _sympyrepr(self, printer) -> str: 1abcdef

108 """Developer string representation according to Sympy.""" 

109 

110 class_name = type(self).__name__ 1abcdef

111 dimensions = '' 1abcdef

112 if not self._is_dimensionless: 1abcdef

113 dim_exp = [] 1abcdef

114 for dim, exp in self.items(): 1abcdef

115 exp_ = _unsympify_number(exp) 1abcdef

116 dim_exp.append(f'{dim}={repr(exp_)}') 1abcdef

117 dimensions = ', '.join(dim_exp) 1abcdef

118 

119 return f'{class_name}({dimensions})' 1abcdef

120 

121 def _sympystr(self, printer) -> str: 1abcdef

122 """User string representation according to Sympy.""" 

123 

124 if self._is_dimensionless: 1abcdef

125 return printer._print(self._symbolic) 1abcdef

126 

127 printer._settings['root_notation'] = True 1abcdef

128 exponents = [] 1abcdef

129 for expt in self._dimensions.values(): 1abcdef

130 if expt < 0 or expt.is_Mul: 1abcdef

131 exponent = f'({printer._print(expt)})' 1abcdef

132 elif expt.is_Rational and not expt.is_Integer: 1abcdef

133 exponent = f'({printer._print(expt)})' 1abcdef

134 else: 

135 exponent = printer._print(expt) 1abcdef

136 exponents.append(exponent) 1abcdef

137 

138 printer._settings['root_notation'] = False 1abcdef

139 dimensions = [] 1abcdef

140 for dim, exp in zip(self._dimensions, exponents): 1abcdef

141 if self._dimensions[dim] != 1: 1abcdef

142 dimensions.append(f'{printer._print(dim)}**{exp}') 1abcdef

143 else: 

144 dimensions.append(printer._print(dim)) 1abcdef

145 

146 return '*'.join(dimensions) 1abcdef

147 

148 def _latex(self, printer) -> str: 1abcdef

149 """User string representation according to Sympy. 

150 

151 The dimension's symbol use the Sans Serif font to follow the 

152 conventional dimension representation. 

153 """ 

154 

155 if self._is_dimensionless: 1abcdef

156 return f'\\mathsf{{{printer._print(self._symbolic)}}}' 1abcdef

157 

158 printer._settings['root_notation'] = True 1abcdef

159 exponents = [] 1abcdef

160 for exp in self._dimensions.values(): 1abcdef

161 exponents.append(printer._print(exp)) 1abcdef

162 

163 printer._settings['root_notation'] = False 1abcdef

164 dimensions = [] 1abcdef

165 for dim, exp in zip(self._dimensions, exponents): 1abcdef

166 if self._dimensions[dim] != 1: 1abcdef

167 dimensions.append(f'\\mathsf{{{dim}}}^{{{exp}}}') 1abcdef

168 else: 

169 dimensions.append(f'\\mathsf{{{dim}}}') 1abcdef

170 

171 return ' '.join(dimensions) 1abcdef

172 

173 def _pretty(self, printer) -> prettyForm: 1abcdef

174 """Pretty representation according to Sympy.""" 

175 

176 if self._is_dimensionless: 1abcdef

177 return printer._print(self._symbolic) 1abcdef

178 

179 printer._settings['root_notation'] = True 1abcdef

180 exponents = [] 1abcdef

181 for exp in self._dimensions.values(): 1abcdef

182 exponents.append(printer._print(exp)) 1abcdef

183 

184 printer._settings['root_notation'] = False 1abcdef

185 for i, (dim, exp) in enumerate(zip(self._dimensions, exponents)): 1abcdef

186 dimexp = printer._print(dim) 1abcdef

187 if self._dimensions[dim] != 1: 1abcdef

188 dimexp = dimexp**exp 1abcdef

189 if i == 0: 1abcdef

190 dimension = dimexp 1abcdef

191 else: 

192 dimension *= dimexp 1abcdef

193 

194 return dimension 1abcdef