Coverage for nodimo / power.py: 100%

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

6Power 

7===== 

8 

9This module contains the class to create the power of a quantity. 

10 

11Classes 

12------- 

13Power 

14 Creates the power of a quantity. 

15""" 

16 

17from sympy import srepr, Pow, Number, S, Abs 1abcdef

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

19 

20from nodimo.quantity import Quantity, Constant, One 1abcdef

21from nodimo._internal import _sympify_number, _unsympify_number, _prettify_name 1abcdef

22 

23 

24class Power(Quantity): 1abcdef

25 """Power of a quantity. 

26 

27 Parameters 

28 ---------- 

29 base : Quantity 

30 Quantity to be exponentiated. 

31 exponent : Number 

32 Exponent to which the quantity will be raised. 

33 name : str, default='' 

34 Name (symbol) to be used as the power representation. 

35 dependent : bool, default=False 

36 If ``True``, the power is dependent. 

37 scaling : bool, default=False 

38 If ``True``, the power can be used as scaling parameter. 

39 reduce : bool, default=True 

40 If ``True``, the exponentiation is simplified. 

41 

42 Attributes 

43 ---------- 

44 base : Quantity 

45 Quantity that is the base of the exponentiation. 

46 exponent : Number 

47 Exponent to which the quantity is raised. 

48 

49 Methods 

50 ------- 

51 reduce() 

52 Turns an unreduced quantity into a reduced quantity. 

53 

54 Raises 

55 ------ 

56 TypeError 

57 If the exponentiation base is not a quantity. 

58 

59 Examples 

60 -------- 

61 

62 >>> from nodimo import Quantity, Power 

63 >>> a = Quantity('a', A=-2, B=1) 

64 >>> b = Power(a, -2) 

65 

66 The ``**`` operator can also be used to create a power: 

67  

68 >>> c = a**3 

69 """ 

70 

71 _is_power = True 1abcdef

72 _is_derived = True 1abcdef

73 

74 def __new__( 1abcdef

75 cls, 

76 base: Quantity, 

77 exponent: Number, 

78 name: str = '', 

79 dependent: bool = False, 

80 scaling: bool = False, 

81 reduce: bool = True, 

82 ): 

83 if not isinstance(base, Quantity): 1abcdef

84 raise TypeError(f"{repr(base)} is not a quantity") 1abcdef

85 elif base._is_one: 1abcdef

86 return One() 1abcdef

87 exponent_sp = _sympify_number(exponent) 1abcdef

88 if exponent_sp == 0: 1abcdef

89 return One() 1abcdef

90 

91 if reduce: 1abcdef

92 if base._is_power: 1abcdef

93 exponent_sp *= base._exponent 1abcdef

94 base = base._base 1abcdef

95 if base._is_number: 1abcdef

96 return Constant(Pow(base._symbolic, exponent_sp)) 1abcdef

97 if base._is_product: 1abcdef

98 from nodimo.product import Product 1abcdef

99 

100 factors = [] 1abcdef

101 for qty in base._factors: 1abcdef

102 factors.append(Power(qty, exponent_sp)) 1abcdef

103 return Product( 1abcdef

104 *factors, name=name, dependent=dependent, scaling=scaling 

105 ) 

106 

107 if exponent_sp == 1: 1abcdef

108 return base 1abcdef

109 

110 return super().__new__(cls) 1abcdef

111 

112 def __init__( 1abcdef

113 self, 

114 base: Quantity, 

115 exponent: Number, 

116 name: str = '', 

117 dependent: bool = False, 

118 scaling: bool = False, 

119 reduce: bool = True, 

120 ): 

121 self._preset_power(base, exponent, reduce) 1abcdef

122 power_dimension = self._base.dimension**self._exponent 1abcdef

123 dummy_name = 'Power' if name == '' else name 1abcdef

124 super().__init__( 1abcdef

125 dummy_name, **power_dimension, dependent=dependent, scaling=scaling 

126 ) 

127 self._set_power(reduce) 1abcdef

128 self._validate_quantity() 1abcdef

129 if name == '': 1abcdef

130 self._name = name 1abcdef

131 self._set_symbolic_power() 1abcdef

132 

133 @property 1abcdef

134 def base(self) -> Quantity: 1abcdef

135 return self._base 1abcdef

136 

137 @property 1abcdef

138 def exponent(self) -> Number: 1abcdef

139 return self._exponent 1abcdef

140 

141 def _preset_power(self, base: Quantity, exponent: Number, reduce: bool): 1abcdef

142 exponent_sp = _sympify_number(exponent) 1abcdef

143 if reduce and base._is_power: 1abcdef

144 exponent_sp *= base._exponent 1abcdef

145 base = base._base 1abcdef

146 

147 self._base = base 1abcdef

148 self._exponent = exponent_sp 1abcdef

149 

150 def _set_power(self, reduce: bool): 1abcdef

151 self._is_constant = self._base._is_constant 1abcdef

152 self._is_number = self._base._is_number 1abcdef

153 self._is_reduced = bool(reduce) 1abcdef

154 if not self._base._is_derived and not self._is_number: 1abcdef

155 self._is_reduced = True 1abcdef

156 if self._base._is_quotient or self._exponent < 0: 1abcdef

157 self._is_quotient = True 1abcdef

158 if self._is_constant: 1abcdef

159 self._name = _prettify_name(self._name, bold=True) 1abcdef

160 self._set_symbolic_quantity() 1abcdef

161 

162 def _set_symbolic_power(self): 1abcdef

163 """Not currently used by the printing methods. 

164 

165 The symbolic attribute created here is not used for printing 

166 because unevaluated powers of numbers can not always be printed. 

167 """ 

168 

169 self._symbolic = Pow(self._base._symbolic, self._exponent, evaluate=False) 1abcdef

170 if self._is_number: 1abcdef

171 self._symbolic = self._symbolic.doit() 1abcdef

172 

173 def _copy(self): 1abcdef

174 if self._base._is_product: 1abcdef

175 from nodimo.product import Product 1abcdef

176 qty_copy = eval(srepr(self)) 1abcdef

177 qty_copy._unreduced = self._unreduced 1abcdef

178 return qty_copy 1abcdef

179 

180 def _key(self) -> tuple: 1abcdef

181 return (self._base.reduce(), self._exponent) 1abcdef

182 

183 def _sympyrepr(self, printer): 1abcdef

184 class_name = type(self).__name__ 1abcdef

185 base = printer._print(self._base) 1abcdef

186 unsymp_exp = _unsympify_number(self._exponent) 1abcdef

187 exponent = f', {repr(unsymp_exp)}' 1abcdef

188 name = f", name='{self._name}'" if self._name else '' 1abcdef

189 dependent = f', dependent=True' if self._is_dependent else '' 1abcdef

190 scaling = f', scaling=True' if self._is_scaling else '' 1abcdef

191 reduce = f', reduce=False' if not self._is_reduced else '' 1abcdef

192 

193 return f'{class_name}({base}{exponent}{name}{dependent}{scaling}{reduce})' 1abcdef

194 

195 def _sympystr(self, printer): 1abcdef

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

197 

198 if self._name: 1abcdef

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

200 

201 pb = self._base 1abcdef

202 bs = pb._symbolic 1abcdef

203 pe = Abs(self._exponent) 1abcdef

204 

205 if bs.is_Symbol or bs.is_NumberSymbol: 1abcdef

206 base = printer._print(pb) 1abcdef

207 elif (bs.is_Integer or bs.is_Float) and bs > 0: 1abcdef

208 base = printer._print(pb) 1abcdef

209 else: 

210 base = f'({printer._print(pb)})' 1abcdef

211 

212 if pe.is_Integer or pe.is_Float or pe.is_NumberSymbol: 1abcdef

213 exponent = printer._print(pe) 1abcdef

214 else: 

215 exponent = f'({printer._print(pe)})' 1abcdef

216 

217 if pe == 1: 1abcdef

218 power = base 1abcdef

219 else: 

220 power = f'{base}**{exponent}' 1abcdef

221 

222 if self._exponent > 0: 1abcdef

223 return power 1abcdef

224 else: 

225 return f'{1}/{power}' 1abcdef

226 

227 def _latex(self, printer): 1abcdef

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

229 

230 if self._name: 1abcdef

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

232 

233 pb = self._base 1abcdef

234 bs = pb._symbolic 1abcdef

235 pe = Abs(self._exponent) 1abcdef

236 

237 if bs.is_Symbol or bs.is_NumberSymbol or pe == 1: 1abcdef

238 base = printer._print(pb) 1abcdef

239 elif (bs.is_Integer or bs.is_Float) and bs > 0: 1abcdef

240 base = printer._print(pb) 1abcdef

241 else: 

242 base = f'\\left({printer._print(pb)}\\right)' 1abcdef

243 

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

245 if pe == 1: 1abcdef

246 power = base 1abcdef

247 else: 

248 exponent = printer._print(pe) 1abcdef

249 power = f'{{{base}}}^{{{exponent}}}' 1abcdef

250 

251 if self._exponent > 0: 1abcdef

252 return power 1abcdef

253 else: 

254 return f'\\frac{{{1}}}{{{power}}}' 1abcdef

255 

256 def _pretty(self, printer): 1abcdef

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

258 

259 if self._name: 1abcdef

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

261 

262 pb = self._base 1abcdef

263 bs = pb._symbolic 1abcdef

264 pe = Abs(self._exponent) 1abcdef

265 

266 if bs.is_Symbol or bs.is_NumberSymbol or pe == 1: 1abcdef

267 base = printer._print(pb) 1abcdef

268 # Binding is fixed to ATOM to avoid parenthesis. 

269 base.binding = prettyForm.ATOM 1abcdef

270 elif (bs.is_Integer or bs.is_Float) and bs > 0: 1abcdef

271 base = printer._print(pb) 1abcdef

272 else: 

273 base = prettyForm(*printer._print(pb).parens()) 1abcdef

274 

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

276 if pe == 1: 1abcdef

277 power = base 1abcdef

278 else: 

279 exponent = printer._print(pe) 1abcdef

280 power = base**exponent 1abcdef

281 

282 if self._exponent > 0: 1abcdef

283 return power 1abcdef

284 else: 

285 return printer._print(S.One) / power 1abcdef