Coverage for nodimo / power.py: 100%
148 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-26 03:10 +0000
« 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
5"""
6Power
7=====
9This module contains the class to create the power of a quantity.
11Classes
12-------
13Power
14 Creates the power of a quantity.
15"""
17from sympy import srepr, Pow, Number, S, Abs 1abcdef
18from sympy.printing.pretty.stringpict import prettyForm 1abcdef
20from nodimo.quantity import Quantity, Constant, One 1abcdef
21from nodimo._internal import _sympify_number, _unsympify_number, _prettify_name 1abcdef
24class Power(Quantity): 1abcdef
25 """Power of a quantity.
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.
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.
49 Methods
50 -------
51 reduce()
52 Turns an unreduced quantity into a reduced quantity.
54 Raises
55 ------
56 TypeError
57 If the exponentiation base is not a quantity.
59 Examples
60 --------
62 >>> from nodimo import Quantity, Power
63 >>> a = Quantity('a', A=-2, B=1)
64 >>> b = Power(a, -2)
66 The ``**`` operator can also be used to create a power:
68 >>> c = a**3
69 """
71 _is_power = True 1abcdef
72 _is_derived = True 1abcdef
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
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
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 )
107 if exponent_sp == 1: 1abcdef
108 return base 1abcdef
110 return super().__new__(cls) 1abcdef
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
133 @property 1abcdef
134 def base(self) -> Quantity: 1abcdef
135 return self._base 1abcdef
137 @property 1abcdef
138 def exponent(self) -> Number: 1abcdef
139 return self._exponent 1abcdef
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
147 self._base = base 1abcdef
148 self._exponent = exponent_sp 1abcdef
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
162 def _set_symbolic_power(self): 1abcdef
163 """Not currently used by the printing methods.
165 The symbolic attribute created here is not used for printing
166 because unevaluated powers of numbers can not always be printed.
167 """
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
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
180 def _key(self) -> tuple: 1abcdef
181 return (self._base.reduce(), self._exponent) 1abcdef
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
193 return f'{class_name}({base}{exponent}{name}{dependent}{scaling}{reduce})' 1abcdef
195 def _sympystr(self, printer): 1abcdef
196 printer._settings['root_notation'] = False 1abcdef
198 if self._name: 1abcdef
199 return printer._print(self._symbolic) 1abcdef
201 pb = self._base 1abcdef
202 bs = pb._symbolic 1abcdef
203 pe = Abs(self._exponent) 1abcdef
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
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
217 if pe == 1: 1abcdef
218 power = base 1abcdef
219 else:
220 power = f'{base}**{exponent}' 1abcdef
222 if self._exponent > 0: 1abcdef
223 return power 1abcdef
224 else:
225 return f'{1}/{power}' 1abcdef
227 def _latex(self, printer): 1abcdef
228 printer._settings['root_notation'] = False 1abcdef
230 if self._name: 1abcdef
231 return printer._print(self._symbolic) 1abcdef
233 pb = self._base 1abcdef
234 bs = pb._symbolic 1abcdef
235 pe = Abs(self._exponent) 1abcdef
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
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
251 if self._exponent > 0: 1abcdef
252 return power 1abcdef
253 else:
254 return f'\\frac{{{1}}}{{{power}}}' 1abcdef
256 def _pretty(self, printer): 1abcdef
257 printer._settings['root_notation'] = False 1abcdef
259 if self._name: 1abcdef
260 return printer._print(self._symbolic) 1abcdef
262 pb = self._base 1abcdef
263 bs = pb._symbolic 1abcdef
264 pe = Abs(self._exponent) 1abcdef
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
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
282 if self._exponent > 0: 1abcdef
283 return power 1abcdef
284 else:
285 return printer._print(S.One) / power 1abcdef