Coverage for nodimo / dimension.py: 100%
113 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"""
6Product
7=======
9This module contains the class to create a quantity dimension.
11Classes
12-------
13Dimension
14 Creates the dimension of a quantity.
15"""
17from sympy import sstr, srepr, latex, Symbol, Mul, Number, S 1abcdef
18from sympy.printing.pretty.stringpict import prettyForm 1abcdef
19from typing import Union 1abcdef
21from nodimo._internal import _sympify_number, _unsympify_number 1abcdef
24class Dimension(dict): 1abcdef
25 """The dimension of a quantity.
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.
31 Parameters
32 ----------
33 **dimensions : Number
34 Base dimensions and exponents given as keyword arguments.
35 """
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
45 def _set_dimensions(self, **dimensions: Number): 1abcdef
46 """Sympifies and clear null dimensional exponents."""
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
54 self._dimensions = dimensions_sp 1abcdef
55 self._is_dimensionless = all(dim == 0 for dim in dimensions_sp.values()) 1abcdef
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
67 def _copy(self): 1abcdef
68 return eval(srepr(self)) 1abcdef
70 def __mul__(self, other): 1abcdef
71 if not isinstance(other, Dimension): 1abcdef
72 raise NotImplementedError(f"{self} and {other} cannot be multiplied") 1abcdef
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
81 return Dimension(**dimensions) 1abcdef
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
89 return Dimension(**dimensions) 1abcdef
91 def __truediv__(self, other): 1abcdef
92 if not isinstance(other, Dimension): 1abcdef
93 raise NotImplementedError(f"{other} cannot divide {self}") 1abcdef
95 return self * other**-1 1abcdef
97 def __str__(self) -> str: 1abcdef
98 return sstr(self) 1abcdef
100 __repr__ = __str__ 1abcdef
102 def _repr_latex_(self): 1abcdef
103 """Latex representation according to IPython/Jupyter."""
105 return f'$\\displaystyle {latex(self)}$' 1abcdef
107 def _sympyrepr(self, printer) -> str: 1abcdef
108 """Developer string representation according to Sympy."""
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
119 return f'{class_name}({dimensions})' 1abcdef
121 def _sympystr(self, printer) -> str: 1abcdef
122 """User string representation according to Sympy."""
124 if self._is_dimensionless: 1abcdef
125 return printer._print(self._symbolic) 1abcdef
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
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
146 return '*'.join(dimensions) 1abcdef
148 def _latex(self, printer) -> str: 1abcdef
149 """User string representation according to Sympy.
151 The dimension's symbol use the Sans Serif font to follow the
152 conventional dimension representation.
153 """
155 if self._is_dimensionless: 1abcdef
156 return f'\\mathsf{{{printer._print(self._symbolic)}}}' 1abcdef
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
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
171 return ' '.join(dimensions) 1abcdef
173 def _pretty(self, printer) -> prettyForm: 1abcdef
174 """Pretty representation according to Sympy."""
176 if self._is_dimensionless: 1abcdef
177 return printer._print(self._symbolic) 1abcdef
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
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
194 return dimension 1abcdef