nmag-python-3 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anisotropy/__init__.py +28 -0
- anisotropy/anisotropy.py +13 -0
- anisotropy/evaluation.py +70 -0
- anisotropy/model.py +200 -0
- anisotropy/predefined.py +202 -0
- anisotropy/py.typed +1 -0
- anisotropy/values.py +84 -0
- mag_material/__init__.py +3 -0
- mag_material/mag_material.py +231 -0
- mag_material/parameters.py +150 -0
- mag_material/py.typed +1 -0
- nmag/__init__.py +36 -0
- nmag/backends.py +493 -0
- nmag/checkpoint.py +327 -0
- nmag/config.py +174 -0
- nmag/demag/__init__.py +39 -0
- nmag/demag/bem_operator.py +149 -0
- nmag/demag/geometry.py +134 -0
- nmag/demag/lindholm.py +133 -0
- nmag/demag/lindholm_fast.py +463 -0
- nmag/demag/linear.py +489 -0
- nmag/dynamics/__init__.py +255 -0
- nmag/output.py +27 -0
- nmag/parallel.py +45 -0
- nmag/py.typed +1 -0
- nmag/resources.py +38 -0
- nmag/simulation/__init__.py +519 -0
- nmag/simulation/anisotropy/__init__.py +5 -0
- nmag/simulation/anisotropy/fields.py +56 -0
- nmag/simulation/anisotropy/materials.py +138 -0
- nmag/simulation/demag/__init__.py +1 -0
- nmag/simulation/demag/bem/__init__.py +11 -0
- nmag/simulation/demag/bem/diagnostics.py +73 -0
- nmag/simulation/demag/bem/dirichlet.py +85 -0
- nmag/simulation/demag/bem/hierarchical.py +74 -0
- nmag/simulation/demag/bem/operator.py +292 -0
- nmag/simulation/demag/fem/__init__.py +11 -0
- nmag/simulation/demag/fem/assembly.py +114 -0
- nmag/simulation/demag/fem/charges.py +38 -0
- nmag/simulation/demag/fem/geometry.py +261 -0
- nmag/simulation/demag/fields/__init__.py +11 -0
- nmag/simulation/demag/fields/auxiliary.py +186 -0
- nmag/simulation/demag/fields/probe.py +77 -0
- nmag/simulation/demag/fields/recovery.py +207 -0
- nmag/simulation/demag/solver.py +17 -0
- nmag/simulation/dynamics/__init__.py +92 -0
- nmag/simulation/dynamics/advance.py +199 -0
- nmag/simulation/dynamics/integrator.py +263 -0
- nmag/simulation/exchange/__init__.py +13 -0
- nmag/simulation/exchange/coefficients.py +185 -0
- nmag/simulation/exchange/fields.py +106 -0
- nmag/simulation/exchange/llg_rhs.py +218 -0
- nmag/simulation/fields/__init__.py +19 -0
- nmag/simulation/fields/arrays.py +111 -0
- nmag/simulation/fields/availability.py +188 -0
- nmag/simulation/fields/averages.py +293 -0
- nmag/simulation/fields/derived.py +164 -0
- nmag/simulation/fields/maxangle.py +163 -0
- nmag/simulation/fields/probes.py +106 -0
- nmag/simulation/implicit_dynamics.py +221 -0
- nmag/simulation/mesh/__init__.py +13 -0
- nmag/simulation/mesh/geometry.py +153 -0
- nmag/simulation/mesh/materials.py +299 -0
- nmag/simulation/mesh/probe.py +234 -0
- nmag/simulation/restart.py +103 -0
- nmag/simulation/support.py +224 -0
- nmag_python_3-0.0.2.dist-info/METADATA +157 -0
- nmag_python_3-0.0.2.dist-info/RECORD +147 -0
- nmag_python_3-0.0.2.dist-info/WHEEL +5 -0
- nmag_python_3-0.0.2.dist-info/licenses/LICENSE +339 -0
- nmag_python_3-0.0.2.dist-info/top_level.txt +8 -0
- nmesh/__init__.py +130 -0
- nmesh/backend.py +286 -0
- nmesh/geometry/__init__.py +52 -0
- nmesh/geometry/boolean_operations.py +157 -0
- nmesh/geometry/primitives.py +453 -0
- nmesh/geometry/transform.py +126 -0
- nmesh/io/__init__.py +50 -0
- nmesh/io/ascii.py +132 -0
- nmesh/io/legacy_nmesh_hdf5.py +318 -0
- nmesh/io/meshio_support.py +170 -0
- nmesh/mesh_generation.py +182 -0
- nmesh/mesh_io.py +227 -0
- nmesh/mesh_model.py +147 -0
- nmesh/mesh_utilities.py +79 -0
- nmesh/mesher/__init__.py +21 -0
- nmesh/mesher/driver.py +146 -0
- nmesh/mesher/meshing_defaults.py +252 -0
- nmesh/mesher/meshing_parameters.py +185 -0
- nmesh/mesher/parity.py +21 -0
- nmesh/mesher/parity_canonical.py +142 -0
- nmesh/mesher/parity_comparison.py +191 -0
- nmesh/mesher/parity_metrics.py +114 -0
- nmesh/mesher/periodic.py +97 -0
- nmesh/mesher/relaxation/__init__.py +14 -0
- nmesh/mesher/relaxation/_constants.py +20 -0
- nmesh/mesher/relaxation/_types.py +15 -0
- nmesh/mesher/relaxation/density.py +170 -0
- nmesh/mesher/relaxation/engine/__init__.py +18 -0
- nmesh/mesher/relaxation/engine/state.py +155 -0
- nmesh/mesher/relaxation/engine/steps.py +248 -0
- nmesh/mesher/relaxation/engine/topology.py +230 -0
- nmesh/mesher/relaxation/forces/__init__.py +96 -0
- nmesh/mesher/relaxation/forces/jit.py +102 -0
- nmesh/mesher/relaxation/forces/neighbors.py +186 -0
- nmesh/mesher/relaxation/forces/simplex.py +302 -0
- nmesh/mesher/relaxation/forces/summary.py +207 -0
- nmesh/mesher/relaxation/forces/types.py +92 -0
- nmesh/mesher/relaxation/geometry/__init__.py +6 -0
- nmesh/mesher/relaxation/geometry/builder.py +154 -0
- nmesh/mesher/relaxation/geometry/model.py +194 -0
- nmesh/mesher/relaxation/seeding/__init__.py +74 -0
- nmesh/mesher/relaxation/seeding/periodic.py +88 -0
- nmesh/mesher/relaxation/seeding/points.py +88 -0
- nmesh/mesher/relaxation/seeding/sampling.py +142 -0
- nmesh/mesher/relaxation/topology/__init__.py +297 -0
- nmesh/mesher/relaxation/topology/finalize.py +78 -0
- nmesh/mesher/relaxation/topology/recovery.py +310 -0
- nmesh/mesher/sectioned_config.py +70 -0
- nmesh/nmesh.py +99 -0
- nmesh/py.typed +1 -0
- nmesh/utils/__init__.py +33 -0
- nmesh/utils/array_list_utils.py +128 -0
- nmesh/utils/constants.py +22 -0
- nmesh/utils/timing_memory_utils.py +51 -0
- nmesh/utils/types.py +13 -0
- si/constants.py +49 -0
- si/physical.py +722 -0
- si/py.typed +1 -0
- simulation/__init__.py +1 -0
- simulation/clock.py +237 -0
- simulation/data_writer.py +273 -0
- simulation/data_writer_collection.py +267 -0
- simulation/hysteresis.py +74 -0
- simulation/hysteresis_runner.py +286 -0
- simulation/hysteresis_schedule.py +180 -0
- simulation/inference/__init__.py +3 -0
- simulation/inference/inference.py +95 -0
- simulation/py.typed +1 -0
- simulation/quantity.py +88 -0
- simulation/simulation_core.py +458 -0
- throttler/__init__.py +3 -0
- throttler/py.typed +1 -0
- throttler/throttler.py +55 -0
- when/__init__.py +3 -0
- when/py.typed +1 -0
- when/when.py +416 -0
si/physical.py
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Original file: https://github.com/nmag-project/nmag-src/blob/master/interface/nsim/si_units/lib.py
|
|
3
|
+
Support for Physical quantities carrying SI units.
|
|
4
|
+
|
|
5
|
+
This module provides a backward-compatible wrapper around the 'pint' library,
|
|
6
|
+
replicating the interface of a legacy Physical/SI class.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from functools import cache
|
|
14
|
+
from typing import Any, TypeAlias, cast
|
|
15
|
+
|
|
16
|
+
# Pint's runtime classes are constructed by the active application registry.
|
|
17
|
+
# Keep the dynamic boundary explicit while retaining concrete types internally.
|
|
18
|
+
_pint_module_cache: Any | None = None
|
|
19
|
+
_pint_registry_cache: Any | None = None
|
|
20
|
+
_pint_quantity_type_cache: Any | None = None
|
|
21
|
+
_pint_unit_type_cache: Any | None = None
|
|
22
|
+
_pint_quantity_factory_cache: Any | None = None
|
|
23
|
+
|
|
24
|
+
Dims: TypeAlias = tuple[int, int, int, int, int, int, int]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _LazyUnitRegistry:
|
|
28
|
+
"""Proxy Pint's application registry without constructing it at import time."""
|
|
29
|
+
|
|
30
|
+
def __getattr__(self, name: str) -> Any:
|
|
31
|
+
return getattr(_pint_registry(), name)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
ureg = _LazyUnitRegistry()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class _FastUnit:
|
|
39
|
+
dims: Dims
|
|
40
|
+
scale: float
|
|
41
|
+
compact: str
|
|
42
|
+
long: str
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
return self.long
|
|
46
|
+
|
|
47
|
+
def __format__(self, spec: str) -> str:
|
|
48
|
+
return self.compact if spec == "~" else self.long
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _FastQuantity:
|
|
52
|
+
"""Small exact SI quantity used for common MVP units before Pint is needed."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, magnitude: int | float, unit: _FastUnit) -> None:
|
|
55
|
+
self.magnitude = float(magnitude)
|
|
56
|
+
self.units = unit
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def dimensionless(self) -> bool:
|
|
60
|
+
return all(power == 0 for power in self.units.dims)
|
|
61
|
+
|
|
62
|
+
def is_compatible_with(self, other: Any) -> bool:
|
|
63
|
+
if isinstance(other, _FastQuantity):
|
|
64
|
+
return self.units.dims == other.units.dims
|
|
65
|
+
if isinstance(other, (int, float)):
|
|
66
|
+
return self.dimensionless or float(other) == 0.0
|
|
67
|
+
return self._as_pint().is_compatible_with(other)
|
|
68
|
+
|
|
69
|
+
def to_base_units(self) -> _FastQuantity:
|
|
70
|
+
return _FastQuantity(
|
|
71
|
+
self.magnitude * self.units.scale,
|
|
72
|
+
_unit_from_dims(self.units.dims),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def _as_pint(self) -> Any:
|
|
76
|
+
# Generated fast units carry their factor separately from their compact
|
|
77
|
+
# spelling. Convert through base SI so the factor is never lost.
|
|
78
|
+
if self.dimensionless:
|
|
79
|
+
return _pint_registry().Quantity(self.magnitude * self.units.scale)
|
|
80
|
+
return _pint_registry().Quantity(
|
|
81
|
+
self.magnitude * self.units.scale,
|
|
82
|
+
_pint_registry().Unit(_compact_from_dims(self.units.dims)),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def __abs__(self) -> _FastQuantity:
|
|
86
|
+
return _FastQuantity(abs(self.magnitude), self.units)
|
|
87
|
+
|
|
88
|
+
def __neg__(self) -> _FastQuantity:
|
|
89
|
+
return _FastQuantity(-self.magnitude, self.units)
|
|
90
|
+
|
|
91
|
+
def __pos__(self) -> _FastQuantity:
|
|
92
|
+
return _FastQuantity(self.magnitude, self.units)
|
|
93
|
+
|
|
94
|
+
def __eq__(self, other: object) -> bool:
|
|
95
|
+
if isinstance(other, _FastQuantity):
|
|
96
|
+
return self.units.dims == other.units.dims and math.isclose(
|
|
97
|
+
self.magnitude * self.units.scale,
|
|
98
|
+
other.magnitude * other.units.scale,
|
|
99
|
+
rel_tol=0.0,
|
|
100
|
+
abs_tol=0.0,
|
|
101
|
+
)
|
|
102
|
+
if isinstance(other, (int, float)):
|
|
103
|
+
return self.magnitude == 0.0 and float(other) == 0.0
|
|
104
|
+
return self._as_pint() == other
|
|
105
|
+
|
|
106
|
+
def __ne__(self, other: object) -> bool:
|
|
107
|
+
return not self == other
|
|
108
|
+
|
|
109
|
+
def __lt__(self, other: Any) -> bool:
|
|
110
|
+
return self.to_base_units().magnitude < _fast_base_magnitude(other, self.units.dims)
|
|
111
|
+
|
|
112
|
+
def __le__(self, other: Any) -> bool:
|
|
113
|
+
return self.to_base_units().magnitude <= _fast_base_magnitude(other, self.units.dims)
|
|
114
|
+
|
|
115
|
+
def __gt__(self, other: Any) -> bool:
|
|
116
|
+
return self.to_base_units().magnitude > _fast_base_magnitude(other, self.units.dims)
|
|
117
|
+
|
|
118
|
+
def __ge__(self, other: Any) -> bool:
|
|
119
|
+
return self.to_base_units().magnitude >= _fast_base_magnitude(other, self.units.dims)
|
|
120
|
+
|
|
121
|
+
def __add__(self, other: Any) -> Any:
|
|
122
|
+
if isinstance(other, (int, float)) and float(other) == 0.0:
|
|
123
|
+
return _FastQuantity(self.magnitude, self.units)
|
|
124
|
+
other_base = _fast_base_magnitude(other, self.units.dims)
|
|
125
|
+
return _FastQuantity(
|
|
126
|
+
(self.to_base_units().magnitude + other_base) / self.units.scale,
|
|
127
|
+
self.units,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def __radd__(self, other: Any) -> Any:
|
|
131
|
+
return self.__add__(other)
|
|
132
|
+
|
|
133
|
+
def __sub__(self, other: Any) -> Any:
|
|
134
|
+
if isinstance(other, (int, float)) and float(other) == 0.0:
|
|
135
|
+
return _FastQuantity(self.magnitude, self.units)
|
|
136
|
+
other_base = _fast_base_magnitude(other, self.units.dims)
|
|
137
|
+
return _FastQuantity(
|
|
138
|
+
(self.to_base_units().magnitude - other_base) / self.units.scale,
|
|
139
|
+
self.units,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def __rsub__(self, other: Any) -> Any:
|
|
143
|
+
if isinstance(other, (int, float)) and float(other) == 0.0:
|
|
144
|
+
return _FastQuantity(-self.magnitude, self.units)
|
|
145
|
+
return _quantity_from_pint(other) - self
|
|
146
|
+
|
|
147
|
+
def __mul__(self, other: Any) -> Any:
|
|
148
|
+
if isinstance(other, (int, float)):
|
|
149
|
+
return _FastQuantity(self.magnitude * float(other), self.units)
|
|
150
|
+
if isinstance(other, _FastQuantity):
|
|
151
|
+
dims = _add_dims(self.units.dims, other.units.dims)
|
|
152
|
+
unit = _unit_from_dims(dims, self.units.scale * other.units.scale)
|
|
153
|
+
return _FastQuantity(self.magnitude * other.magnitude, unit)
|
|
154
|
+
return self._as_pint() * other
|
|
155
|
+
|
|
156
|
+
def __rmul__(self, other: Any) -> Any:
|
|
157
|
+
return self.__mul__(other)
|
|
158
|
+
|
|
159
|
+
def __truediv__(self, other: Any) -> Any:
|
|
160
|
+
if isinstance(other, (int, float)):
|
|
161
|
+
return _FastQuantity(self.magnitude / float(other), self.units)
|
|
162
|
+
if isinstance(other, _FastQuantity):
|
|
163
|
+
dims = _subtract_dims(self.units.dims, other.units.dims)
|
|
164
|
+
unit = _unit_from_dims(dims, self.units.scale / other.units.scale)
|
|
165
|
+
return _FastQuantity(self.magnitude / other.magnitude, unit)
|
|
166
|
+
return self._as_pint() / other
|
|
167
|
+
|
|
168
|
+
def __rtruediv__(self, other: Any) -> Any:
|
|
169
|
+
if isinstance(other, (int, float)):
|
|
170
|
+
dims = _negate_dims(self.units.dims)
|
|
171
|
+
unit = _unit_from_dims(dims, 1.0 / self.units.scale)
|
|
172
|
+
return _FastQuantity(float(other) / self.magnitude, unit)
|
|
173
|
+
return other / self._as_pint()
|
|
174
|
+
|
|
175
|
+
def __pow__(self, exponent: int | float) -> Any:
|
|
176
|
+
if not isinstance(exponent, int):
|
|
177
|
+
return self._as_pint() ** exponent
|
|
178
|
+
dims = _multiply_dims(self.units.dims, exponent)
|
|
179
|
+
unit = _unit_from_dims(dims, self.units.scale**exponent)
|
|
180
|
+
return _FastQuantity(self.magnitude**exponent, unit)
|
|
181
|
+
|
|
182
|
+
def __str__(self) -> str:
|
|
183
|
+
if self.dimensionless:
|
|
184
|
+
return str(self.magnitude)
|
|
185
|
+
return f"{self.magnitude} {self.units.long}"
|
|
186
|
+
|
|
187
|
+
def __format__(self, spec: str) -> str:
|
|
188
|
+
if spec == "~":
|
|
189
|
+
if self.dimensionless:
|
|
190
|
+
return str(self.magnitude)
|
|
191
|
+
return f"{self.magnitude} {self.units.compact}"
|
|
192
|
+
return str(self)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
_DIMS_DIMLESS: Dims = (0, 0, 0, 0, 0, 0, 0)
|
|
196
|
+
_DIMS_KG: Dims = (1, 0, 0, 0, 0, 0, 0)
|
|
197
|
+
_DIMS_M: Dims = (0, 1, 0, 0, 0, 0, 0)
|
|
198
|
+
_DIMS_S: Dims = (0, 0, 1, 0, 0, 0, 0)
|
|
199
|
+
_DIMS_A: Dims = (0, 0, 0, 1, 0, 0, 0)
|
|
200
|
+
_DIMS_K: Dims = (0, 0, 0, 0, 1, 0, 0)
|
|
201
|
+
_DIMS_MOL: Dims = (0, 0, 0, 0, 0, 1, 0)
|
|
202
|
+
_DIMS_CD: Dims = (0, 0, 0, 0, 0, 0, 1)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _dims(
|
|
206
|
+
*,
|
|
207
|
+
kg: int = 0,
|
|
208
|
+
m: int = 0,
|
|
209
|
+
s: int = 0,
|
|
210
|
+
A: int = 0,
|
|
211
|
+
K: int = 0,
|
|
212
|
+
mol: int = 0,
|
|
213
|
+
cd: int = 0,
|
|
214
|
+
) -> Dims:
|
|
215
|
+
return (kg, m, s, A, K, mol, cd)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _add_dims(left: Dims, right: Dims) -> Dims:
|
|
219
|
+
return (
|
|
220
|
+
left[0] + right[0],
|
|
221
|
+
left[1] + right[1],
|
|
222
|
+
left[2] + right[2],
|
|
223
|
+
left[3] + right[3],
|
|
224
|
+
left[4] + right[4],
|
|
225
|
+
left[5] + right[5],
|
|
226
|
+
left[6] + right[6],
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _subtract_dims(left: Dims, right: Dims) -> Dims:
|
|
231
|
+
return (
|
|
232
|
+
left[0] - right[0],
|
|
233
|
+
left[1] - right[1],
|
|
234
|
+
left[2] - right[2],
|
|
235
|
+
left[3] - right[3],
|
|
236
|
+
left[4] - right[4],
|
|
237
|
+
left[5] - right[5],
|
|
238
|
+
left[6] - right[6],
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _negate_dims(dims: Dims) -> Dims:
|
|
243
|
+
return (-dims[0], -dims[1], -dims[2], -dims[3], -dims[4], -dims[5], -dims[6])
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _multiply_dims(dims: Dims, factor: int) -> Dims:
|
|
247
|
+
return (
|
|
248
|
+
dims[0] * factor,
|
|
249
|
+
dims[1] * factor,
|
|
250
|
+
dims[2] * factor,
|
|
251
|
+
dims[3] * factor,
|
|
252
|
+
dims[4] * factor,
|
|
253
|
+
dims[5] * factor,
|
|
254
|
+
dims[6] * factor,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
_FAST_UNITS: dict[str, _FastUnit] = {
|
|
259
|
+
"": _FastUnit(_DIMS_DIMLESS, 1.0, "", "dimensionless"),
|
|
260
|
+
"1": _FastUnit(_DIMS_DIMLESS, 1.0, "", "dimensionless"),
|
|
261
|
+
"m": _FastUnit(_DIMS_M, 1.0, "m", "meter"),
|
|
262
|
+
"meter": _FastUnit(_DIMS_M, 1.0, "m", "meter"),
|
|
263
|
+
"km": _FastUnit(_DIMS_M, 1000.0, "km", "kilometer"),
|
|
264
|
+
"kilometer": _FastUnit(_DIMS_M, 1000.0, "km", "kilometer"),
|
|
265
|
+
"kg": _FastUnit(_DIMS_KG, 1.0, "kg", "kilogram"),
|
|
266
|
+
"kilogram": _FastUnit(_DIMS_KG, 1.0, "kg", "kilogram"),
|
|
267
|
+
"s": _FastUnit(_DIMS_S, 1.0, "s", "second"),
|
|
268
|
+
"second": _FastUnit(_DIMS_S, 1.0, "s", "second"),
|
|
269
|
+
"A": _FastUnit(_DIMS_A, 1.0, "A", "ampere"),
|
|
270
|
+
"ampere": _FastUnit(_DIMS_A, 1.0, "A", "ampere"),
|
|
271
|
+
"K": _FastUnit(_DIMS_K, 1.0, "K", "kelvin"),
|
|
272
|
+
"kelvin": _FastUnit(_DIMS_K, 1.0, "K", "kelvin"),
|
|
273
|
+
"mol": _FastUnit(_DIMS_MOL, 1.0, "mol", "mole"),
|
|
274
|
+
"mole": _FastUnit(_DIMS_MOL, 1.0, "mol", "mole"),
|
|
275
|
+
"cd": _FastUnit(_DIMS_CD, 1.0, "cd", "candela"),
|
|
276
|
+
"candela": _FastUnit(_DIMS_CD, 1.0, "cd", "candela"),
|
|
277
|
+
"A/m": _FastUnit(_dims(A=1, m=-1), 1.0, "A/m", "ampere / meter"),
|
|
278
|
+
"A/m**2": _FastUnit(_dims(A=1, m=-2), 1.0, "A/m**2", "ampere / meter ** 2"),
|
|
279
|
+
"A/m/s": _FastUnit(_dims(A=1, m=-1, s=-1), 1.0, "A/m/s", "ampere / meter / second"),
|
|
280
|
+
"m/s": _FastUnit(_dims(m=1, s=-1), 1.0, "m/s", "meter / second"),
|
|
281
|
+
"km/h": _FastUnit(_dims(m=1, s=-1), 1000.0 / 3600.0, "km/h", "kilometer / hour"),
|
|
282
|
+
"1/s": _FastUnit(_dims(s=-1), 1.0, "1/s", "1 / second"),
|
|
283
|
+
"m/A/s": _FastUnit(_dims(m=1, A=-1, s=-1), 1.0, "m/A/s", "meter / ampere / second"),
|
|
284
|
+
"J": _FastUnit(_dims(kg=1, m=2, s=-2), 1.0, "J", "joule"),
|
|
285
|
+
"J/m": _FastUnit(_dims(kg=1, m=1, s=-2), 1.0, "J/m", "joule / meter"),
|
|
286
|
+
"J/m**3": _FastUnit(_dims(kg=1, m=-1, s=-2), 1.0, "J/m**3", "joule / meter ** 3"),
|
|
287
|
+
"N": _FastUnit(_dims(kg=1, m=1, s=-2), 1.0, "N", "newton"),
|
|
288
|
+
"N*m": _FastUnit(_dims(kg=1, m=2, s=-2), 1.0, "m*N", "meter * newton"),
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _normalized_unit_string(unit: str) -> str:
|
|
293
|
+
return unit.strip().replace(" ", "").replace("^", "**")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _unit_from_dims(
|
|
297
|
+
dims: Dims,
|
|
298
|
+
scale: float = 1.0,
|
|
299
|
+
) -> _FastUnit:
|
|
300
|
+
for unit in _FAST_UNITS.values():
|
|
301
|
+
if unit.dims == dims and unit.scale == scale:
|
|
302
|
+
return unit
|
|
303
|
+
compact = _compact_from_dims(dims)
|
|
304
|
+
return _FastUnit(dims, scale, compact, compact or "dimensionless")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _compact_from_dims(dims: Dims) -> str:
|
|
308
|
+
names = ("kg", "m", "s", "A", "K", "mol", "cd")
|
|
309
|
+
parts: list[str] = []
|
|
310
|
+
for name, power in zip(names, dims, strict=True):
|
|
311
|
+
if power == 0:
|
|
312
|
+
continue
|
|
313
|
+
parts.append(name if power == 1 else f"{name}**{power}")
|
|
314
|
+
return "*".join(parts)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _fast_unit(unit: str) -> _FastUnit | None:
|
|
318
|
+
return _FAST_UNITS.get(_normalized_unit_string(unit))
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _fast_dimensions_list(dimensions: list[Any]) -> _FastUnit | None:
|
|
322
|
+
if not dimensions:
|
|
323
|
+
return _FAST_UNITS[""]
|
|
324
|
+
if len(dimensions) % 2 != 0:
|
|
325
|
+
raise ValueError(
|
|
326
|
+
"Physical quantity: Bad dimensions list given! "
|
|
327
|
+
"Must contain pairs of unit names and powers. "
|
|
328
|
+
f"Received: {dimensions}"
|
|
329
|
+
)
|
|
330
|
+
dims = _DIMS_DIMLESS
|
|
331
|
+
for i in range(0, len(dimensions), 2):
|
|
332
|
+
unit = _fast_unit(str(dimensions[i]))
|
|
333
|
+
if unit is None or unit.scale != 1.0:
|
|
334
|
+
return None
|
|
335
|
+
power = dimensions[i + 1]
|
|
336
|
+
if not isinstance(power, int):
|
|
337
|
+
return None
|
|
338
|
+
dims = _add_dims(dims, _multiply_dims(unit.dims, power))
|
|
339
|
+
return _unit_from_dims(dims)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _fast_base_magnitude(other: Any, expected_dims: Dims) -> float:
|
|
343
|
+
if isinstance(other, _FastQuantity):
|
|
344
|
+
if other.units.dims != expected_dims:
|
|
345
|
+
raise _pint_module().errors.DimensionalityError(
|
|
346
|
+
other.units,
|
|
347
|
+
_unit_from_dims(expected_dims),
|
|
348
|
+
)
|
|
349
|
+
return other.to_base_units().magnitude
|
|
350
|
+
if isinstance(other, (int, float)):
|
|
351
|
+
if any(expected_dims) and float(other) != 0.0:
|
|
352
|
+
raise _pint_module().errors.DimensionalityError(
|
|
353
|
+
"dimensionless",
|
|
354
|
+
_unit_from_dims(expected_dims),
|
|
355
|
+
)
|
|
356
|
+
return float(other)
|
|
357
|
+
return _quantity_from_pint(other).to_base_units().magnitude
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _quantity_from_pint(value: Any) -> Any:
|
|
361
|
+
if isinstance(value, _FastQuantity):
|
|
362
|
+
return value
|
|
363
|
+
quantity_type, unit_type, quantity_factory = _pint_handles()
|
|
364
|
+
if isinstance(value, unit_type):
|
|
365
|
+
return quantity_factory(1.0, value)
|
|
366
|
+
if isinstance(value, quantity_type):
|
|
367
|
+
return value
|
|
368
|
+
return quantity_factory(value)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _as_pint_quantity(value: Any) -> Any:
|
|
372
|
+
if isinstance(value, _FastQuantity):
|
|
373
|
+
return value._as_pint()
|
|
374
|
+
return value
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _quantities_compatible(left: Any, right: Any) -> bool:
|
|
378
|
+
if isinstance(left, _FastQuantity) and isinstance(right, _FastQuantity):
|
|
379
|
+
return left.units.dims == right.units.dims
|
|
380
|
+
return _as_pint_quantity(left).is_compatible_with(_as_pint_quantity(right))
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _base_magnitude(value: Any) -> float:
|
|
384
|
+
if isinstance(value, _FastQuantity):
|
|
385
|
+
return value.to_base_units().magnitude
|
|
386
|
+
return value.to_base_units().magnitude
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _pint_module() -> Any:
|
|
390
|
+
global _pint_module_cache
|
|
391
|
+
if _pint_module_cache is None:
|
|
392
|
+
import pint
|
|
393
|
+
|
|
394
|
+
_pint_module_cache = pint
|
|
395
|
+
return _pint_module_cache
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _pint_registry() -> Any:
|
|
399
|
+
global _pint_registry_cache
|
|
400
|
+
if _pint_registry_cache is None:
|
|
401
|
+
_pint_registry_cache = _pint_module().get_application_registry()
|
|
402
|
+
return _pint_registry_cache
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _pint_handles() -> tuple[Any, Any, Any]:
|
|
406
|
+
global _pint_quantity_factory_cache, _pint_quantity_type_cache, _pint_unit_type_cache
|
|
407
|
+
if (
|
|
408
|
+
_pint_quantity_type_cache is None
|
|
409
|
+
or _pint_unit_type_cache is None
|
|
410
|
+
or _pint_quantity_factory_cache is None
|
|
411
|
+
):
|
|
412
|
+
registry = _pint_registry()
|
|
413
|
+
quantity = registry.Quantity
|
|
414
|
+
_pint_quantity_factory_cache = quantity
|
|
415
|
+
_pint_quantity_type_cache = quantity
|
|
416
|
+
_pint_unit_type_cache = registry.Unit
|
|
417
|
+
assert _pint_quantity_type_cache is not None
|
|
418
|
+
assert _pint_unit_type_cache is not None
|
|
419
|
+
assert _pint_quantity_factory_cache is not None
|
|
420
|
+
return _pint_quantity_type_cache, _pint_unit_type_cache, _pint_quantity_factory_cache
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@cache
|
|
424
|
+
def _pint_unit(unit: str) -> Any:
|
|
425
|
+
return _pint_registry().Unit(unit)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class Physical:
|
|
429
|
+
"""Represent a numeric value with checked physical dimensions.
|
|
430
|
+
|
|
431
|
+
``nmag.SI`` is an alias for this class and is the preferred spelling in
|
|
432
|
+
simulation scripts. Common units use a lightweight internal representation;
|
|
433
|
+
other units are delegated to Pint without changing the public behavior.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
value: Numeric magnitude, another compatible quantity, a unit string
|
|
437
|
+
such as ``"A/m"``, or a complete quantity string.
|
|
438
|
+
dimensions: Unit string, legacy ``[unit, power, ...]`` list, or ``None``.
|
|
439
|
+
|
|
440
|
+
Raises:
|
|
441
|
+
TypeError: If the value or dimensions form is unsupported.
|
|
442
|
+
ValueError: If a legacy dimensions list is malformed.
|
|
443
|
+
"""
|
|
444
|
+
|
|
445
|
+
_quantity: Any
|
|
446
|
+
|
|
447
|
+
def __init__(self, value: Any, dimensions: Any | None = None) -> None:
|
|
448
|
+
"""Create a dimensionally checked quantity."""
|
|
449
|
+
fast_quantity = self._fast_quantity(value, dimensions)
|
|
450
|
+
if fast_quantity is not None:
|
|
451
|
+
self._quantity = fast_quantity
|
|
452
|
+
return
|
|
453
|
+
self._quantity = self._pint_quantity(value, dimensions)
|
|
454
|
+
|
|
455
|
+
@staticmethod
|
|
456
|
+
def _fast_quantity(value: Any, dimensions: Any | None) -> _FastQuantity | None:
|
|
457
|
+
if isinstance(value, _FastQuantity):
|
|
458
|
+
return value
|
|
459
|
+
if dimensions is None:
|
|
460
|
+
return Physical._fast_quantity_without_dimensions(value)
|
|
461
|
+
return Physical._fast_quantity_with_dimensions(value, dimensions)
|
|
462
|
+
|
|
463
|
+
@staticmethod
|
|
464
|
+
def _fast_quantity_without_dimensions(value: Any) -> _FastQuantity | None:
|
|
465
|
+
if isinstance(value, str):
|
|
466
|
+
unit = _fast_unit(value)
|
|
467
|
+
return None if unit is None else _FastQuantity(1.0, unit)
|
|
468
|
+
if isinstance(value, (int, float)):
|
|
469
|
+
return _FastQuantity(float(value), _FAST_UNITS[""])
|
|
470
|
+
return None
|
|
471
|
+
|
|
472
|
+
@staticmethod
|
|
473
|
+
def _fast_quantity_with_dimensions(value: Any, dimensions: Any) -> _FastQuantity | None:
|
|
474
|
+
if isinstance(dimensions, str):
|
|
475
|
+
unit = _fast_unit(dimensions)
|
|
476
|
+
return None if unit is None else _FastQuantity(value, unit)
|
|
477
|
+
if isinstance(dimensions, list):
|
|
478
|
+
unit = _fast_dimensions_list(cast(list[Any], dimensions))
|
|
479
|
+
return None if unit is None else _FastQuantity(value, unit)
|
|
480
|
+
return None
|
|
481
|
+
|
|
482
|
+
@staticmethod
|
|
483
|
+
def _pint_quantity(value: Any, dimensions: Any | None) -> Any:
|
|
484
|
+
quantity_type, unit_type, quantity_factory = _pint_handles()
|
|
485
|
+
if isinstance(value, quantity_type):
|
|
486
|
+
return value
|
|
487
|
+
if isinstance(value, unit_type):
|
|
488
|
+
return quantity_factory(1.0, value)
|
|
489
|
+
if dimensions is None and isinstance(value, str):
|
|
490
|
+
return Physical._pint_string_quantity(value, quantity_factory)
|
|
491
|
+
return Physical._pint_value_with_dimensions(value, dimensions, unit_type, quantity_factory)
|
|
492
|
+
|
|
493
|
+
@staticmethod
|
|
494
|
+
def _pint_string_quantity(value: str, quantity_factory: Any) -> Any:
|
|
495
|
+
try:
|
|
496
|
+
return quantity_factory(1.0, _pint_unit(value))
|
|
497
|
+
except Exception:
|
|
498
|
+
return quantity_factory(value)
|
|
499
|
+
|
|
500
|
+
@staticmethod
|
|
501
|
+
def _pint_value_with_dimensions(
|
|
502
|
+
value: Any,
|
|
503
|
+
dimensions: Any | None,
|
|
504
|
+
unit_type: Any,
|
|
505
|
+
quantity_factory: Any,
|
|
506
|
+
) -> Any:
|
|
507
|
+
magnitude = float(value)
|
|
508
|
+
if dimensions is None:
|
|
509
|
+
return quantity_factory(magnitude)
|
|
510
|
+
if isinstance(dimensions, str):
|
|
511
|
+
return quantity_factory(magnitude, _pint_unit(dimensions))
|
|
512
|
+
if isinstance(dimensions, unit_type):
|
|
513
|
+
return quantity_factory(magnitude, dimensions)
|
|
514
|
+
if isinstance(dimensions, list):
|
|
515
|
+
return Physical._pint_list_dimensions(
|
|
516
|
+
magnitude, cast(list[Any], dimensions), quantity_factory
|
|
517
|
+
)
|
|
518
|
+
raise TypeError(
|
|
519
|
+
"Unsupported type for 'dimensions'. "
|
|
520
|
+
f"Must be str, list, or None, but got {type(dimensions)}."
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
@staticmethod
|
|
524
|
+
def _pint_list_dimensions(
|
|
525
|
+
magnitude: float,
|
|
526
|
+
dimensions: list[Any],
|
|
527
|
+
quantity_factory: Any,
|
|
528
|
+
) -> Any:
|
|
529
|
+
if not dimensions:
|
|
530
|
+
return quantity_factory(magnitude)
|
|
531
|
+
if len(dimensions) % 2 != 0:
|
|
532
|
+
raise ValueError(
|
|
533
|
+
"Physical quantity: Bad dimensions list given! "
|
|
534
|
+
"Must contain pairs of unit names and powers. "
|
|
535
|
+
f"Received: {dimensions}"
|
|
536
|
+
)
|
|
537
|
+
unit_string = " * ".join(
|
|
538
|
+
f"{dimensions[index]}**{dimensions[index + 1]}"
|
|
539
|
+
for index in range(0, len(dimensions), 2)
|
|
540
|
+
)
|
|
541
|
+
return quantity_factory(magnitude, unit_string)
|
|
542
|
+
|
|
543
|
+
@property
|
|
544
|
+
def magnitude(self) -> Any:
|
|
545
|
+
"""Return the numeric magnitude in the quantity's stored unit."""
|
|
546
|
+
return self._quantity.magnitude
|
|
547
|
+
|
|
548
|
+
value = magnitude # Alias for backward compatibility
|
|
549
|
+
|
|
550
|
+
def dens_str(self) -> str:
|
|
551
|
+
"""Return a compact legacy-compatible representation such as ``<A/m>``."""
|
|
552
|
+
# Handle the special case for a dimensionless quantity of 1.0
|
|
553
|
+
if self._quantity.dimensionless and self._quantity.magnitude == 1.0:
|
|
554
|
+
return "<1>"
|
|
555
|
+
|
|
556
|
+
# For non-dimensionless quantities with a magnitude of 1.0, omit the number.
|
|
557
|
+
# pint.Unit will not have a magnitude, even though it can be thought of as it having a magnitude of 1.0.
|
|
558
|
+
if hasattr(self._quantity, "magnitude") and self._quantity.magnitude == 1.0:
|
|
559
|
+
# Format just the units
|
|
560
|
+
compact_str = f"{self._quantity.units:~}"
|
|
561
|
+
else:
|
|
562
|
+
# Format the whole quantity
|
|
563
|
+
compact_str = f"{self._quantity:~}"
|
|
564
|
+
|
|
565
|
+
# Clean up the formatting for backward compatibility
|
|
566
|
+
cleaned_str = compact_str.replace(" ", "").replace("**", "^")
|
|
567
|
+
return f"<{cleaned_str}>"
|
|
568
|
+
|
|
569
|
+
def in_units_of(self, unit_quantity: object) -> float:
|
|
570
|
+
"""Return the magnitude expressed in multiples of another quantity.
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
unit_quantity: A :class:`Physical` representing one requested unit,
|
|
574
|
+
for example ``nmag.SI(1, "A/m")``.
|
|
575
|
+
|
|
576
|
+
Returns:
|
|
577
|
+
Numeric magnitude in the requested unit.
|
|
578
|
+
|
|
579
|
+
Raises:
|
|
580
|
+
TypeError: If ``unit_quantity`` is not a :class:`Physical`.
|
|
581
|
+
DimensionalityError: If the dimensions are incompatible.
|
|
582
|
+
"""
|
|
583
|
+
if not isinstance(unit_quantity, Physical):
|
|
584
|
+
raise TypeError("Argument must be an instance of Physical.")
|
|
585
|
+
|
|
586
|
+
# The original class implicitly worked by dividing the magnitudes of
|
|
587
|
+
# quantities that were already converted to base SI units. This
|
|
588
|
+
# implementation faithfully reproduces that logic.
|
|
589
|
+
if not _quantities_compatible(self._quantity, unit_quantity._quantity):
|
|
590
|
+
raise _pint_module().errors.DimensionalityError(
|
|
591
|
+
self._quantity.units, unit_quantity._quantity.units
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
self_base_mag = _base_magnitude(self._quantity)
|
|
595
|
+
unit_base_mag = _base_magnitude(unit_quantity._quantity)
|
|
596
|
+
|
|
597
|
+
if unit_base_mag == 0:
|
|
598
|
+
raise ZeroDivisionError("Cannot express in units of a zero quantity.")
|
|
599
|
+
|
|
600
|
+
return self_base_mag / unit_base_mag
|
|
601
|
+
|
|
602
|
+
# Helper method to safely get the pint.Quantity from another object
|
|
603
|
+
def _unwrap(self, other: Any) -> Any:
|
|
604
|
+
if isinstance(other, Physical):
|
|
605
|
+
other_quantity = other._quantity
|
|
606
|
+
if not isinstance(self._quantity, _FastQuantity) and isinstance(
|
|
607
|
+
other_quantity, _FastQuantity
|
|
608
|
+
):
|
|
609
|
+
return other_quantity._as_pint()
|
|
610
|
+
return other_quantity
|
|
611
|
+
return other
|
|
612
|
+
|
|
613
|
+
# --- Magic Operators ---
|
|
614
|
+
|
|
615
|
+
def __str__(self) -> str:
|
|
616
|
+
"""'Nice' presentation of the pint object."""
|
|
617
|
+
# We can customize this, but pint's default is quite good.
|
|
618
|
+
# Example: "10.0 meter / second"
|
|
619
|
+
return str(self._quantity)
|
|
620
|
+
|
|
621
|
+
def __repr__(self) -> str:
|
|
622
|
+
"""
|
|
623
|
+
Returns a representation of the object that can be used to
|
|
624
|
+
re-instantiate it using eval.
|
|
625
|
+
We probably don't have to do this for dens_str since it is just
|
|
626
|
+
used for printing into large files. If we do have to do that
|
|
627
|
+
eventually, we might need to change the logic for that function.
|
|
628
|
+
"""
|
|
629
|
+
# This creates a string like 'Physical(10.0, "meter / second")'
|
|
630
|
+
return f"Physical({self._quantity.magnitude}, '{self._quantity.units}')"
|
|
631
|
+
|
|
632
|
+
def __float__(self) -> float:
|
|
633
|
+
"""
|
|
634
|
+
Return the value of the Physical Object if it is dimensionless.
|
|
635
|
+
"""
|
|
636
|
+
if not self._quantity.dimensionless:
|
|
637
|
+
raise _pint_module().errors.DimensionalityError(
|
|
638
|
+
self._quantity.units,
|
|
639
|
+
"dimensionless",
|
|
640
|
+
extra_msg="\nObjects can be converted to float only when they are dimensionless.",
|
|
641
|
+
)
|
|
642
|
+
return _base_magnitude(self._quantity)
|
|
643
|
+
|
|
644
|
+
# --- Unary Operators ---
|
|
645
|
+
|
|
646
|
+
def __abs__(self) -> Physical:
|
|
647
|
+
return Physical(abs(self._quantity))
|
|
648
|
+
|
|
649
|
+
def __neg__(self) -> Physical:
|
|
650
|
+
return Physical(-1 * self._quantity)
|
|
651
|
+
|
|
652
|
+
def __pos__(self) -> Physical:
|
|
653
|
+
return Physical(self._quantity)
|
|
654
|
+
|
|
655
|
+
# --- Comparison Operators ---
|
|
656
|
+
|
|
657
|
+
def __lt__(self, other: Any) -> bool:
|
|
658
|
+
return self._quantity < self._unwrap(other)
|
|
659
|
+
|
|
660
|
+
def __le__(self, other: Any) -> bool:
|
|
661
|
+
return self._quantity <= self._unwrap(other)
|
|
662
|
+
|
|
663
|
+
def __gt__(self, other: Any) -> bool:
|
|
664
|
+
return self._quantity > self._unwrap(other)
|
|
665
|
+
|
|
666
|
+
def __ge__(self, other: Any) -> bool:
|
|
667
|
+
return self._quantity >= self._unwrap(other)
|
|
668
|
+
|
|
669
|
+
def __eq__(self, other: object) -> bool:
|
|
670
|
+
try:
|
|
671
|
+
other_quantity = self._unwrap(other)
|
|
672
|
+
if isinstance(self._quantity, _FastQuantity) or isinstance(
|
|
673
|
+
other_quantity,
|
|
674
|
+
_FastQuantity,
|
|
675
|
+
):
|
|
676
|
+
if isinstance(other_quantity, (int, float)):
|
|
677
|
+
return self._quantity == other_quantity
|
|
678
|
+
return _quantities_compatible(self._quantity, other_quantity) and math.isclose(
|
|
679
|
+
_base_magnitude(self._quantity),
|
|
680
|
+
_base_magnitude(other_quantity),
|
|
681
|
+
rel_tol=0.0,
|
|
682
|
+
abs_tol=0.0,
|
|
683
|
+
)
|
|
684
|
+
return self._quantity == other_quantity
|
|
685
|
+
except _pint_module().errors.DimensionalityError:
|
|
686
|
+
# If units are not compatible, they cannot be equal.
|
|
687
|
+
return False
|
|
688
|
+
|
|
689
|
+
def __ne__(self, other: object) -> bool:
|
|
690
|
+
return not self == other
|
|
691
|
+
|
|
692
|
+
# --- Binary Arithmetic Operators ---
|
|
693
|
+
|
|
694
|
+
def __add__(self, other: Any) -> Physical:
|
|
695
|
+
return Physical(self._quantity + self._unwrap(other))
|
|
696
|
+
|
|
697
|
+
def __radd__(self, other: Any) -> Physical:
|
|
698
|
+
return Physical(self._unwrap(other) + self._quantity)
|
|
699
|
+
|
|
700
|
+
def __sub__(self, other: Any) -> Physical:
|
|
701
|
+
return Physical(self._quantity - self._unwrap(other))
|
|
702
|
+
|
|
703
|
+
def __rsub__(self, other: Any) -> Physical:
|
|
704
|
+
return Physical(self._unwrap(other) - self._quantity)
|
|
705
|
+
|
|
706
|
+
def __mul__(self, other: Any) -> Physical:
|
|
707
|
+
return Physical(self._quantity * self._unwrap(other))
|
|
708
|
+
|
|
709
|
+
def __rmul__(self, other: Any) -> Physical:
|
|
710
|
+
return Physical(self._unwrap(other) * self._quantity)
|
|
711
|
+
|
|
712
|
+
def __truediv__(self, other: Any) -> Physical:
|
|
713
|
+
return Physical(self._quantity / self._unwrap(other))
|
|
714
|
+
|
|
715
|
+
def __rtruediv__(self, other: Any) -> Physical:
|
|
716
|
+
return Physical(self._unwrap(other) / self._quantity)
|
|
717
|
+
|
|
718
|
+
def __pow__(self, exponent: int | float) -> Physical:
|
|
719
|
+
return Physical(self._quantity**exponent)
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
SI = Physical
|