unitx 0.6.1__tar.gz → 0.6.2__tar.gz
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.
- {unitx-0.6.1 → unitx-0.6.2}/CHANGELOG.md +13 -0
- {unitx-0.6.1 → unitx-0.6.2}/PKG-INFO +31 -2
- {unitx-0.6.1 → unitx-0.6.2}/README.md +30 -1
- {unitx-0.6.1 → unitx-0.6.2}/pyproject.toml +1 -1
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/factor.py +5 -1
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/quantity.py +31 -22
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/unit.py +8 -5
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_quantity.py +56 -3
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_si.py +5 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_unit.py +6 -1
- {unitx-0.6.1 → unitx-0.6.2}/tests/typing/check_quantity.py +13 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/typing/check_si.py +1 -0
- {unitx-0.6.1 → unitx-0.6.2}/.github/workflows/ci.yml +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/.github/workflows/release.yml +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/.gitignore +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/LICENSE +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/conftest.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/pyrightconfig.json +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/__init__.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/_constclass.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/_numerics.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/_unicode.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/_unit_data.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/dimension.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/exceptions.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/monomial.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/py.typed +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/si/__init__.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/si/constants.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/si/units.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/singleunit.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/src/unitx/unitparser.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/__init__.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_dimension.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_factor.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_import.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_monomial.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_numeric.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_singleunit.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_typing.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_unicode.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_unit_data.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_unit_definition.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_unitparser.py +0 -0
- {unitx-0.6.1 → unitx-0.6.2}/tests/test_warnings.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.2] - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Support multiplication and division between Quantity and Factor or SymbolicConstant in both operand orders. Multiplication and right-hand division preserve the magnitude and its type, combining the exact factor with the unit's scale; left-hand division takes the reciprocal magnitude and scales the reciprocal unit. Ordinary numeric operands continue to operate on the magnitude.
|
|
8
|
+
- Preserve generic magnitude types in the corresponding public type hints, with runtime and static regression coverage for exact scaling and reciprocal operations.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Combine the compact exact scale and formal units in ScaledUnit string output, grouping denominator factors into one product, such as `J⋅s/(2⋅π)`.
|
|
13
|
+
- Display quantities with scaled units using the combined product and multiplication dots between adjacent numeric factors, such as `3⋅60⋅s`, without evaluating the exact scale. String output remains readable display rather than parser input; `repr()` retains reconstructible state.
|
|
14
|
+
- Document exact-factor quantity arithmetic and the distinction between a symbolic expression such as `SI.h / (2 * PI)` and predefined `SI.hbar`, which continues to expose its numerical value in ordinary `J*s` units.
|
|
15
|
+
|
|
3
16
|
## [0.6.1] - 2026-10-17
|
|
4
17
|
|
|
5
18
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitx
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: A modern, intuitive Python library for units, quantities, and dimensional analysis.
|
|
5
5
|
Project-URL: Homepage, https://github.com/DaitTHU/unitx
|
|
6
6
|
Project-URL: Issues, https://github.com/DaitTHU/unitx/issues
|
|
@@ -146,7 +146,7 @@ True
|
|
|
146
146
|
2
|
|
147
147
|
>>> double_metre = metre * Factor(2)
|
|
148
148
|
>>> print(double_metre)
|
|
149
|
-
2
|
|
149
|
+
2⋅m
|
|
150
150
|
>>> (3 * double_metre).to('m').value
|
|
151
151
|
6
|
|
152
152
|
>>> arcmin = si.deg / 60
|
|
@@ -161,6 +161,35 @@ Multiplying a `ScaledUnit` by an ordinary number also constructs a quantity,
|
|
|
161
161
|
keeping the exact scale in its unit and preserving the supplied magnitude.
|
|
162
162
|
Combining a Unit with a Quantity continues to return a Quantity.
|
|
163
163
|
|
|
164
|
+
Quantities also accept `Factor` and `SymbolicConstant` operands on either side
|
|
165
|
+
of multiplication or division. Multiplication and right-hand division preserve
|
|
166
|
+
the magnitude and its type, combining the exact factor with the unit instead.
|
|
167
|
+
Left-hand division takes the reciprocal magnitude and unit. Ordinary numeric
|
|
168
|
+
operands continue to act on the magnitude: `q * 2` and `q * Factor(2)` represent
|
|
169
|
+
equal physical quantities but can have different `.value` and `.unit` pairs.
|
|
170
|
+
|
|
171
|
+
```pycon
|
|
172
|
+
>>> from unitx.factor import PI
|
|
173
|
+
>>> hbar = SI.h / (2 * PI)
|
|
174
|
+
>>> print(hbar)
|
|
175
|
+
6.62607015E-34 J⋅s/(2⋅π)
|
|
176
|
+
>>> hbar.value == SI.h.value
|
|
177
|
+
True
|
|
178
|
+
>>> hbar * (2 * PI) == SI.h
|
|
179
|
+
True
|
|
180
|
+
>>> round(float(hbar.to('J*s').value) / 1e-34, 8)
|
|
181
|
+
1.05457182
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The local `hbar` above retains the exact scale, whereas predefined `SI.hbar`
|
|
186
|
+
provides its numerical value in ordinary `J*s` units. `.value` is always the
|
|
187
|
+
magnitude paired with `.unit`, including its exact scale. Use
|
|
188
|
+
`.to('J*s').value` to evaluate the local expression in joule seconds;
|
|
189
|
+
conversion involving an irrational factor evaluates it approximately.
|
|
190
|
+
String output combines the scale and formal units into one product, with
|
|
191
|
+
grouped denominator factors and multiplication dots between adjacent numbers.
|
|
192
|
+
|
|
164
193
|
## Conversion and arithmetic
|
|
165
194
|
|
|
166
195
|
Use `.to()` to convert between units with the same physical dimensions.
|
|
@@ -124,7 +124,7 @@ True
|
|
|
124
124
|
2
|
|
125
125
|
>>> double_metre = metre * Factor(2)
|
|
126
126
|
>>> print(double_metre)
|
|
127
|
-
2
|
|
127
|
+
2⋅m
|
|
128
128
|
>>> (3 * double_metre).to('m').value
|
|
129
129
|
6
|
|
130
130
|
>>> arcmin = si.deg / 60
|
|
@@ -139,6 +139,35 @@ Multiplying a `ScaledUnit` by an ordinary number also constructs a quantity,
|
|
|
139
139
|
keeping the exact scale in its unit and preserving the supplied magnitude.
|
|
140
140
|
Combining a Unit with a Quantity continues to return a Quantity.
|
|
141
141
|
|
|
142
|
+
Quantities also accept `Factor` and `SymbolicConstant` operands on either side
|
|
143
|
+
of multiplication or division. Multiplication and right-hand division preserve
|
|
144
|
+
the magnitude and its type, combining the exact factor with the unit instead.
|
|
145
|
+
Left-hand division takes the reciprocal magnitude and unit. Ordinary numeric
|
|
146
|
+
operands continue to act on the magnitude: `q * 2` and `q * Factor(2)` represent
|
|
147
|
+
equal physical quantities but can have different `.value` and `.unit` pairs.
|
|
148
|
+
|
|
149
|
+
```pycon
|
|
150
|
+
>>> from unitx.factor import PI
|
|
151
|
+
>>> hbar = SI.h / (2 * PI)
|
|
152
|
+
>>> print(hbar)
|
|
153
|
+
6.62607015E-34 J⋅s/(2⋅π)
|
|
154
|
+
>>> hbar.value == SI.h.value
|
|
155
|
+
True
|
|
156
|
+
>>> hbar * (2 * PI) == SI.h
|
|
157
|
+
True
|
|
158
|
+
>>> round(float(hbar.to('J*s').value) / 1e-34, 8)
|
|
159
|
+
1.05457182
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The local `hbar` above retains the exact scale, whereas predefined `SI.hbar`
|
|
164
|
+
provides its numerical value in ordinary `J*s` units. `.value` is always the
|
|
165
|
+
magnitude paired with `.unit`, including its exact scale. Use
|
|
166
|
+
`.to('J*s').value` to evaluate the local expression in joule seconds;
|
|
167
|
+
conversion involving an irrational factor evaluates it approximately.
|
|
168
|
+
String output combines the scale and formal units into one product, with
|
|
169
|
+
grouped denominator factors and multiplication dots between adjacent numbers.
|
|
170
|
+
|
|
142
171
|
## Conversion and arithmetic
|
|
143
172
|
|
|
144
173
|
Use `.to()` to convert between units with the same physical dimensions.
|
|
@@ -398,6 +398,10 @@ class Factor:
|
|
|
398
398
|
"""
|
|
399
399
|
if not compact:
|
|
400
400
|
return str(self._monomial)
|
|
401
|
+
return str(self._display_monomial())
|
|
402
|
+
|
|
403
|
+
def _display_monomial(self) -> Monomial[int | str]:
|
|
404
|
+
"""Collect numeric bases for compact display, without changing exact state."""
|
|
401
405
|
rational, irrational = self.decompose(rationalize_denominator=True)
|
|
402
406
|
display_components: dict[int | str, Fraction] = {}
|
|
403
407
|
if rational.numerator > 1:
|
|
@@ -417,7 +421,7 @@ class Factor:
|
|
|
417
421
|
# Numeric roots use string keys to stay distinct from rational coefficients.
|
|
418
422
|
# Symbol validation and Factor construction prevent collisions with symbols.
|
|
419
423
|
display_components[str(base)] = exponent
|
|
420
|
-
return
|
|
424
|
+
return Monomial._from_dict(display_components)
|
|
421
425
|
|
|
422
426
|
def __str__(self) -> str:
|
|
423
427
|
return self.format()
|
|
@@ -7,10 +7,11 @@ from fractions import Fraction
|
|
|
7
7
|
from typing import Any, Generic, TypeVar, cast, overload
|
|
8
8
|
|
|
9
9
|
from ._numerics import Fractionable, Magnitude, Number, Real, as_fraction, as_int_if_integral
|
|
10
|
+
from ._unicode import DOT
|
|
10
11
|
from .dimension import DIMENSIONLESS, Dimension
|
|
11
12
|
from .exceptions import DimensionError
|
|
12
|
-
from .factor import ONE, Factor
|
|
13
|
-
from .unit import
|
|
13
|
+
from .factor import ONE, Factor, SymbolicConstant
|
|
14
|
+
from .unit import Unit
|
|
14
15
|
|
|
15
16
|
# Magnitudes are exposed read-only; operations construct new quantities.
|
|
16
17
|
T = TypeVar('T', bound=Magnitude, covariant=True)
|
|
@@ -22,7 +23,9 @@ class Quantity(Generic[T]):
|
|
|
22
23
|
Construction preserves the magnitude's type. Conversion uses Factor's
|
|
23
24
|
scaling rules and can change that type. Arithmetic converts finite Decimal
|
|
24
25
|
operands exactly to Fraction; other magnitudes use native arithmetic.
|
|
25
|
-
Multiplication or division by a Unit
|
|
26
|
+
Multiplication or division by a Unit, Factor, or SymbolicConstant preserves
|
|
27
|
+
the magnitude unchanged. Exact factors are combined with the unit's scale,
|
|
28
|
+
while ordinary numeric operands participate in magnitude arithmetic.
|
|
26
29
|
|
|
27
30
|
Magnitudes must be scalars whose equality returns a bool. NumPy scalars,
|
|
28
31
|
arrays, and NumPy operations are not supported yet; NumPy integration is
|
|
@@ -33,9 +36,9 @@ class Quantity(Generic[T]):
|
|
|
33
36
|
Scalar multiplication and division accept int, float, Fraction, and Decimal.
|
|
34
37
|
|
|
35
38
|
Type hints preserve the magnitude type at construction and when combining
|
|
36
|
-
with a Unit. Numeric conversions and arithmetic use unions
|
|
37
|
-
depends on runtime units or values. In particular, float
|
|
38
|
-
accept int inputs, which may produce Fraction after scaling. Custom
|
|
39
|
+
with a Unit or exact factor. Numeric conversions and arithmetic use unions
|
|
40
|
+
where the result depends on runtime units or values. In particular, float
|
|
41
|
+
annotations also accept int inputs, which may produce Fraction after scaling. Custom
|
|
39
42
|
magnitudes retain their arithmetic protocol rather than an assumed Self.
|
|
40
43
|
|
|
41
44
|
Equality and hashing retain the exact rational coefficient and the unit's
|
|
@@ -50,9 +53,9 @@ class Quantity(Generic[T]):
|
|
|
50
53
|
Powers normalize the exponent once using as_fraction(), just as Unit does,
|
|
51
54
|
and use that exponent for both the magnitude and the unit. Nonintegral
|
|
52
55
|
magnitude powers use native numerical evaluation and can be approximate.
|
|
53
|
-
str()
|
|
54
|
-
|
|
55
|
-
Unit expression.
|
|
56
|
+
str() displays the magnitude followed by the unit's exact product expression
|
|
57
|
+
without evaluating its scale. Adjacent numeric factors use a multiplication
|
|
58
|
+
dot. repr() uses a reconstructible Unit expression.
|
|
56
59
|
|
|
57
60
|
>>> Quantity(1, 'm') + Quantity(50, 'cm')
|
|
58
61
|
Quantity(Fraction(3, 2), Unit('m'))
|
|
@@ -106,11 +109,13 @@ class Quantity(Generic[T]):
|
|
|
106
109
|
return f'{type(self).__name__}({self.value!r}, {self.unit!r})'
|
|
107
110
|
|
|
108
111
|
def __str__(self) -> str:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
unit = str(self.unit)
|
|
113
|
+
if unit == '1':
|
|
114
|
+
return str(self.value)
|
|
115
|
+
if unit.startswith('1/'):
|
|
116
|
+
return f'{self.value}{unit[1:]}'
|
|
117
|
+
separator = DOT if unit[0].isdecimal() else ' '
|
|
118
|
+
return f'{self.value}{separator}{unit}'
|
|
114
119
|
|
|
115
120
|
def is_dimensionless(self) -> bool:
|
|
116
121
|
return self.dimension is DIMENSIONLESS
|
|
@@ -263,7 +268,7 @@ class Quantity(Generic[T]):
|
|
|
263
268
|
return NotImplemented
|
|
264
269
|
|
|
265
270
|
@overload
|
|
266
|
-
def __mul__(self, other: Unit, /) -> Quantity[T]: ...
|
|
271
|
+
def __mul__(self, other: Unit | Factor | SymbolicConstant, /) -> Quantity[T]: ...
|
|
267
272
|
|
|
268
273
|
@overload
|
|
269
274
|
def __mul__(self: Quantity[int], other: Quantity[int] | int, /) -> Quantity[int]: ...
|
|
@@ -281,7 +286,7 @@ class Quantity(Generic[T]):
|
|
|
281
286
|
quantity = cast(Quantity[Magnitude], other)
|
|
282
287
|
value = cast(Magnitude, quantity._arithmetic_value)
|
|
283
288
|
return Quantity(self._arithmetic_value * value, self.unit * quantity.unit)
|
|
284
|
-
if isinstance(other, Unit):
|
|
289
|
+
if isinstance(other, (Unit, Factor, SymbolicConstant)):
|
|
285
290
|
return Quantity(self.value, self.unit * other)
|
|
286
291
|
if isinstance(other, Fractionable):
|
|
287
292
|
value = cast(Magnitude, Fraction(other) if isinstance(other, Decimal) else other)
|
|
@@ -291,7 +296,7 @@ class Quantity(Generic[T]):
|
|
|
291
296
|
__rmul__ = __mul__
|
|
292
297
|
|
|
293
298
|
@overload
|
|
294
|
-
def __truediv__(self, other: Unit, /) -> Quantity[T]: ...
|
|
299
|
+
def __truediv__(self, other: Unit | Factor | SymbolicConstant, /) -> Quantity[T]: ...
|
|
295
300
|
|
|
296
301
|
@overload
|
|
297
302
|
def __truediv__(self: Quantity[int], other: Quantity[int] | int, /) -> Quantity[float]: ...
|
|
@@ -309,7 +314,7 @@ class Quantity(Generic[T]):
|
|
|
309
314
|
quantity = cast(Quantity[Magnitude], other)
|
|
310
315
|
value = cast(Magnitude, quantity._arithmetic_value)
|
|
311
316
|
return Quantity(self._arithmetic_value / value, self.unit / quantity.unit)
|
|
312
|
-
if isinstance(other, Unit):
|
|
317
|
+
if isinstance(other, (Unit, Factor, SymbolicConstant)):
|
|
313
318
|
return Quantity(self.value, self.unit / other)
|
|
314
319
|
if isinstance(other, Fractionable):
|
|
315
320
|
value = cast(Magnitude, Fraction(other) if isinstance(other, Decimal) else other)
|
|
@@ -317,18 +322,22 @@ class Quantity(Generic[T]):
|
|
|
317
322
|
return NotImplemented
|
|
318
323
|
|
|
319
324
|
@overload
|
|
320
|
-
def __rtruediv__(
|
|
325
|
+
def __rtruediv__(
|
|
326
|
+
self: Quantity[int], other: Unit | Factor | SymbolicConstant | int, /
|
|
327
|
+
) -> Quantity[float]: ...
|
|
321
328
|
|
|
322
329
|
@overload
|
|
323
330
|
def __rtruediv__[N: Fractionable](
|
|
324
|
-
self: Quantity[N], other: Unit | Fractionable, /
|
|
331
|
+
self: Quantity[N], other: Unit | Factor | SymbolicConstant | Fractionable, /
|
|
325
332
|
) -> Quantity[Real]: ...
|
|
326
333
|
|
|
327
334
|
@overload
|
|
328
|
-
def __rtruediv__(
|
|
335
|
+
def __rtruediv__(
|
|
336
|
+
self, other: Unit | Factor | SymbolicConstant | Fractionable, /
|
|
337
|
+
) -> Quantity[Magnitude]: ...
|
|
329
338
|
|
|
330
339
|
def __rtruediv__(self, other: object, /) -> Quantity[Magnitude]:
|
|
331
|
-
if isinstance(other, Unit):
|
|
340
|
+
if isinstance(other, (Unit, Factor, SymbolicConstant)):
|
|
332
341
|
return Quantity(1 / self._arithmetic_value, other / self.unit)
|
|
333
342
|
if isinstance(other, Fractionable):
|
|
334
343
|
value = Fraction(other) if isinstance(other, Decimal) else other
|
|
@@ -306,7 +306,8 @@ class ScaledUnit(Unit):
|
|
|
306
306
|
involving a ScaledUnit return ScaledUnit, even when the scale becomes one.
|
|
307
307
|
Equality compares the formal product and explicit scale, so a scale of one
|
|
308
308
|
compares equal to the unscaled Unit. `symbol` describes only the product;
|
|
309
|
-
str()
|
|
309
|
+
str() combines its compact exact scale and formal units into a single
|
|
310
|
+
product with a grouped denominator, and repr() reconstructs both.
|
|
310
311
|
|
|
311
312
|
Copying and pickling preserve the formal units and exact scale. Restoring
|
|
312
313
|
a unit requires its referenced unit definitions to be registered.
|
|
@@ -315,7 +316,7 @@ class ScaledUnit(Unit):
|
|
|
315
316
|
>>> minute
|
|
316
317
|
ScaledUnit('s', scale=Factor.from_dict({2: 2, 3: 1, 5: 1}))
|
|
317
318
|
>>> str(minute)
|
|
318
|
-
'60
|
|
319
|
+
'60⋅s'
|
|
319
320
|
>>> minute == Unit('s')
|
|
320
321
|
False
|
|
321
322
|
"""
|
|
@@ -358,9 +359,11 @@ class ScaledUnit(Unit):
|
|
|
358
359
|
def __str__(self) -> str:
|
|
359
360
|
if self.scale == ONE:
|
|
360
361
|
return self.symbol
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
362
|
+
components: dict[SingleUnit | int | str, Fraction] = dict(
|
|
363
|
+
self.scale._display_monomial().components()
|
|
364
|
+
)
|
|
365
|
+
components.update(self._monomial.components())
|
|
366
|
+
return str(Monomial(components))
|
|
364
367
|
|
|
365
368
|
def __hash__(self) -> int:
|
|
366
369
|
if self.scale == ONE:
|
|
@@ -38,9 +38,14 @@ def test_quantity_str(unit: str, expected: str) -> None:
|
|
|
38
38
|
@pytest.mark.parametrize(
|
|
39
39
|
('unit', 'expected'),
|
|
40
40
|
[
|
|
41
|
-
(Factor(2) * Unit(), '3
|
|
42
|
-
(Factor(2) / Unit('s'), '3
|
|
43
|
-
(Factor(60) * Unit('s'), '3
|
|
41
|
+
(Factor(2) * Unit(), '3⋅2'),
|
|
42
|
+
(Factor(2) / Unit('s'), '3⋅2/s'),
|
|
43
|
+
(Factor(60) * Unit('s'), '3⋅60⋅s'),
|
|
44
|
+
(Unit('J*s') / (2 * PI), '3 J⋅s/(2⋅π)'),
|
|
45
|
+
(Unit('1/s') / (2 * PI), '3/(2⋅π⋅s)'),
|
|
46
|
+
(Unit() / PI, '3/π'),
|
|
47
|
+
(PI * Unit('s'), '3 π⋅s'),
|
|
48
|
+
(Factor(12) * Unit('s'), '3⋅12⋅s'),
|
|
44
49
|
(ScaledUnit(Unit(), ONE), '3'),
|
|
45
50
|
(ScaledUnit(Unit('1/s'), ONE), '3/s'),
|
|
46
51
|
],
|
|
@@ -52,6 +57,54 @@ def test_quantity_scaled_str(unit: ScaledUnit, expected: str) -> None:
|
|
|
52
57
|
assert (quantity.value, quantity.unit, quantity.unit.factor) == before
|
|
53
58
|
|
|
54
59
|
|
|
60
|
+
@pytest.mark.parametrize('value', [0, -2, 2**60 + 1, 0.1, Fraction(3, 7), Decimal('1.2345'), 2j])
|
|
61
|
+
@pytest.mark.parametrize('unit', [Unit('cm'), ScaledUnit('cm', PI), Unit('km/m')])
|
|
62
|
+
@pytest.mark.parametrize('factor', [ONE, Factor(2), PI, 1 / (2 * PI), Factor(2) ** Fraction(1, 2)])
|
|
63
|
+
def test_quantity_factor_operations(value, unit: Unit, factor) -> None:
|
|
64
|
+
quantity = Quantity(value, unit)
|
|
65
|
+
before = (repr(quantity), hash(quantity))
|
|
66
|
+
with localcontext() as context:
|
|
67
|
+
context.prec = 2
|
|
68
|
+
products = (quantity * factor, factor * quantity)
|
|
69
|
+
quotient = quantity / factor
|
|
70
|
+
for result in products:
|
|
71
|
+
assert result.value is value
|
|
72
|
+
assert result.unit == unit * factor
|
|
73
|
+
assert (result / factor).unit == unit
|
|
74
|
+
assert result == value * (unit * factor)
|
|
75
|
+
assert quotient.value is value
|
|
76
|
+
assert quotient.unit == unit / factor
|
|
77
|
+
assert (quotient * factor) == quantity
|
|
78
|
+
assert hash(quotient * factor) == hash(quantity)
|
|
79
|
+
assert {quantity: 'found'}[quotient * factor] == 'found'
|
|
80
|
+
assert (repr(quantity), hash(quantity)) == before
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.parametrize('value', [2, Fraction(3, 7), Decimal('1.2345'), 2j])
|
|
84
|
+
@pytest.mark.parametrize('factor', [Factor(2), PI])
|
|
85
|
+
def test_quantity_factor_reciprocal(value, factor) -> None:
|
|
86
|
+
quantity = Quantity(value, ScaledUnit('cm', PI))
|
|
87
|
+
result = factor / quantity
|
|
88
|
+
reciprocal = 1 / quantity
|
|
89
|
+
assert result.value == reciprocal.value
|
|
90
|
+
assert type(result.value) is type(reciprocal.value)
|
|
91
|
+
assert result.unit == factor / quantity.unit
|
|
92
|
+
assert result == reciprocal * factor
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_quantity_factor_scaling() -> None:
|
|
96
|
+
quantity = Quantity(3, 'cm')
|
|
97
|
+
numeric = quantity * 2
|
|
98
|
+
exact = quantity * Factor(2)
|
|
99
|
+
assert numeric.value == 6
|
|
100
|
+
assert numeric.unit == quantity.unit
|
|
101
|
+
assert exact.value == 3
|
|
102
|
+
assert exact.unit == ScaledUnit('cm', Factor(2))
|
|
103
|
+
assert exact == numeric
|
|
104
|
+
assert hash(exact) == hash(numeric)
|
|
105
|
+
assert (exact.to('m').to(exact.unit)).value == exact.value
|
|
106
|
+
|
|
107
|
+
|
|
55
108
|
def test_quantity_scaled_operations() -> None:
|
|
56
109
|
minute = Factor(60) * Unit('s')
|
|
57
110
|
quantity = Quantity(1, minute)
|
|
@@ -132,6 +132,11 @@ def test_si_defining_constants(attribute: str, target: str, value: str) -> None:
|
|
|
132
132
|
assert result.value == Decimal(value)
|
|
133
133
|
|
|
134
134
|
|
|
135
|
+
def test_si_hbar_value() -> None:
|
|
136
|
+
assert SI.hbar.unit == SI.h.unit
|
|
137
|
+
assert SI.hbar.value == pytest.approx(1.0545718176461565e-34, rel=1e-15, abs=0)
|
|
138
|
+
|
|
139
|
+
|
|
135
140
|
@pytest.mark.parametrize(
|
|
136
141
|
('attribute', 'target'),
|
|
137
142
|
[('Phi0', si.Wb), ('G0', si.S), ('RK', si.ohm), ('a0', si.m), ('lambdaC', si.m), ('Eh', si.J)],
|
|
@@ -575,11 +575,16 @@ def test_scaled_unit_exact_power() -> None:
|
|
|
575
575
|
@pytest.mark.parametrize(
|
|
576
576
|
('base', 'scale', 'expected'),
|
|
577
577
|
[
|
|
578
|
-
('s', Factor(60), '60
|
|
578
|
+
('s', Factor(60), '60⋅s'),
|
|
579
579
|
('', Factor(2), '2'),
|
|
580
580
|
('1/s', Factor(2), '2/s'),
|
|
581
581
|
('1/(m*s)', Factor(2), '2/(m⋅s)'),
|
|
582
582
|
('cm', ONE, 'cm'),
|
|
583
|
+
('J*s', 1 / (2 * PI), 'J⋅s/(2⋅π)'),
|
|
584
|
+
('m/s', PI / 180, 'π⋅m/(180⋅s)'),
|
|
585
|
+
('1/(m*s)', 1 / (2 * PI), '1/(2⋅π⋅m⋅s)'),
|
|
586
|
+
('s', Factor(5040) ** Fraction(1, 2), '12⋅35¹ᐟ²⋅s'),
|
|
587
|
+
('m^(1/2)/s', PI ** Fraction(1, 2), 'π¹ᐟ²⋅m¹ᐟ²/s'),
|
|
583
588
|
],
|
|
584
589
|
)
|
|
585
590
|
def test_scaled_unit_str(base: str, scale: Factor, expected: str) -> None:
|
|
@@ -143,6 +143,19 @@ def test_quantity_unit_operations[T: Magnitude](value: T) -> Quantity[T]:
|
|
|
143
143
|
return quantity
|
|
144
144
|
|
|
145
145
|
|
|
146
|
+
def test_quantity_factor_operations[T: Magnitude](value: T) -> Quantity[T]:
|
|
147
|
+
quantity = Quantity(value, 'm')
|
|
148
|
+
for factor in (Factor(2), PI):
|
|
149
|
+
assert_type(quantity * factor, Quantity[T])
|
|
150
|
+
assert_type(factor * quantity, Quantity[T])
|
|
151
|
+
assert_type(quantity / factor, Quantity[T])
|
|
152
|
+
assert_type(factor / quantity, Quantity[Magnitude])
|
|
153
|
+
assert_type(Factor(2) / Quantity(2, 's'), Quantity[float])
|
|
154
|
+
assert_type(PI / Quantity(Decimal(2), 's'), Quantity[Real])
|
|
155
|
+
assert_type(Quantity(Decimal(2), 's') / (2 * PI), Quantity[Decimal])
|
|
156
|
+
return quantity
|
|
157
|
+
|
|
158
|
+
|
|
146
159
|
def test_quantity_numeric_operations() -> None:
|
|
147
160
|
integer = Quantity(1, 'm')
|
|
148
161
|
rational = Quantity(Fraction(1), 'm')
|
|
@@ -20,5 +20,6 @@ def test_si_unit_types() -> None:
|
|
|
20
20
|
def test_si_constant_types() -> None:
|
|
21
21
|
assert_type(SI.c, Quantity[int])
|
|
22
22
|
assert_type(SI.h, Quantity[Decimal])
|
|
23
|
+
assert_type(SI.hbar, Quantity[Real])
|
|
23
24
|
assert_type(SI.e, Quantity[Decimal])
|
|
24
25
|
assert_type(SI.h.to(si.J * si.s), Quantity[Real])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|