unitx 0.8.0__tar.gz → 0.8.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.8.0 → unitx-0.8.2}/.github/workflows/ci.yml +7 -3
- {unitx-0.8.0 → unitx-0.8.2}/.github/workflows/release.yml +1 -0
- {unitx-0.8.0 → unitx-0.8.2}/CHANGELOG.md +35 -0
- unitx-0.8.2/PKG-INFO +268 -0
- unitx-0.8.2/README.md +244 -0
- {unitx-0.8.0 → unitx-0.8.2}/conftest.py +4 -1
- unitx-0.8.2/docs/images/blackbody.png +0 -0
- unitx-0.8.2/docs/migration.md +16 -0
- unitx-0.8.2/docs/numpy.md +225 -0
- unitx-0.8.2/docs/scalars.md +106 -0
- unitx-0.8.0/README.md → unitx-0.8.2/docs/units.md +7 -274
- unitx-0.8.2/examples/blackbody.py +20 -0
- {unitx-0.8.0 → unitx-0.8.2}/pyproject.toml +1 -1
- unitx-0.8.2/pyrightconfig.asarray.json +5 -0
- unitx-0.8.2/pyrightconfig.examples.json +5 -0
- {unitx-0.8.0 → unitx-0.8.2}/pyrightconfig.json +1 -0
- unitx-0.8.2/pyrightconfig.scalar.json +5 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/_numerics.py +20 -17
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/_numpy.py +85 -10
- unitx-0.8.2/src/unitx/_numpy_typing.py +21 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/quantity.py +57 -44
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/unit.py +3 -2
- unitx-0.8.2/tests/example_typing/check_blackbody.py +27 -0
- unitx-0.8.2/tests/numpy_typing/check_numpy.py +127 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_numpy.py +313 -6
- unitx-0.8.2/tests/test_typing.py +82 -0
- unitx-0.8.0/PKG-INFO +0 -651
- unitx-0.8.0/docs/numpy.md +0 -133
- unitx-0.8.0/tests/numpy_typing/check_numpy.py +0 -61
- unitx-0.8.0/tests/test_typing.py +0 -56
- {unitx-0.8.0 → unitx-0.8.2}/.gitignore +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/LICENSE +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/pyrightconfig.numpy.json +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/__init__.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/_constnamespace.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/_unicode.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/dimension.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/exceptions.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/factor.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/monomial.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/py.typed +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/si/__init__.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/si/bases.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/si/constants.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/si/units.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/singleunit.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/unitparser.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/src/unitx/unitregistry.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/__init__.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_builtin_definitions.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_dimension.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_factor.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_import.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_monomial.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_numeric.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_quantity.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_si.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_singleunit.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_unicode.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_unit.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_unit_definition.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_unitparser.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_unitregistry.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/test_warnings.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/typing/check_quantity.py +0 -0
- {unitx-0.8.0 → unitx-0.8.2}/tests/typing/check_si.py +0 -0
|
@@ -47,7 +47,9 @@ jobs:
|
|
|
47
47
|
ruff format --check .
|
|
48
48
|
|
|
49
49
|
- name: Run tests
|
|
50
|
-
run:
|
|
50
|
+
run: |
|
|
51
|
+
pytest -q
|
|
52
|
+
python -m doctest README.md docs/units.md docs/scalars.md
|
|
51
53
|
|
|
52
54
|
numpy:
|
|
53
55
|
name: NumPy ${{ matrix.numpy }} (Python ${{ matrix.python-version }})
|
|
@@ -66,9 +68,11 @@ jobs:
|
|
|
66
68
|
with:
|
|
67
69
|
python-version: ${{ matrix.python-version }}
|
|
68
70
|
- name: Install package and dependencies
|
|
69
|
-
run: python -m pip install -e '.[test,numpy]'
|
|
71
|
+
run: python -m pip install -e '.[test,numpy]' 'matplotlib>=3.9'
|
|
70
72
|
- name: Select minimum NumPy
|
|
71
73
|
if: matrix.numpy != 'latest'
|
|
72
74
|
run: python -m pip install 'numpy==${{ matrix.numpy }}'
|
|
73
75
|
- name: Run tests and typing
|
|
74
|
-
run:
|
|
76
|
+
run: |
|
|
77
|
+
pytest -q
|
|
78
|
+
python -m doctest README.md docs/units.md docs/scalars.md
|
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.2] - 2026-10-20
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Support Decimal and Fraction scalar operands in NumPy calculations, including
|
|
8
|
+
SI constants such as h and kB. Convert them to Python floats at the NumPy
|
|
9
|
+
boundary without changing stored constants or Python scalar arithmetic.
|
|
10
|
+
Keep exact Fraction/Decimal unit exponents in dimensional NumPy powers.
|
|
11
|
+
- Remove recursive arithmetic return constraints from the Scalar protocol to
|
|
12
|
+
avoid excessive type-checker work against NumPy overloads. Preserve scalar
|
|
13
|
+
boolean equality and public Quantity result overloads.
|
|
14
|
+
|
|
15
|
+
### Improved
|
|
16
|
+
|
|
17
|
+
- Describe array arithmetic, conversion, indexing, and iteration with concrete
|
|
18
|
+
ndarray/NumPy-numeric-scalar result unions. Preserve the magnitude type and
|
|
19
|
+
dtype for transpose, expose NumPy dtype metadata, and type flatten as ndarray.
|
|
20
|
+
- Add strict NumPy method-completion and no-NumPy scalar-consumer checks,
|
|
21
|
+
installed-wheel validation, and a runnable blackbody example using asarray.
|
|
22
|
+
- Rewrite the README as a concise user guide, retaining detailed unit, scalar,
|
|
23
|
+
and migration documentation under docs/. Document NumPy stub limitations and
|
|
24
|
+
validate the example's supported type-inference path without an explicit dtype.
|
|
25
|
+
|
|
26
|
+
## [0.8.1] - 2026-10-19
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Support `out` for covered single-output NumPy ufunc calls. Numeric results
|
|
31
|
+
write to compatible Quantity ndarray buffers after unit conversion; boolean
|
|
32
|
+
comparisons and predicates write to bare boolean ndarrays. Preserve target
|
|
33
|
+
identity, dtype, unit, and masked-out values, including overlapping views.
|
|
34
|
+
- Stage computation, unit conversion, and casting before writing; validation
|
|
35
|
+
and temporary computation failures leave the target unchanged. Function and
|
|
36
|
+
reduction outputs, item assignment, and multi-output ufuncs remain unsupported.
|
|
37
|
+
|
|
3
38
|
## [0.8.0] - 2026-10-19
|
|
4
39
|
|
|
5
40
|
### Added
|
unitx-0.8.2/PKG-INFO
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: unitx
|
|
3
|
+
Version: 0.8.2
|
|
4
|
+
Summary: A modern, intuitive Python library for units, quantities, and dimensional analysis.
|
|
5
|
+
Project-URL: Homepage, https://github.com/DaitTHU/unitx
|
|
6
|
+
Project-URL: Issues, https://github.com/DaitTHU/unitx/issues
|
|
7
|
+
Author: Dait
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Provides-Extra: numpy
|
|
18
|
+
Requires-Dist: numpy>=2.1; extra == 'numpy'
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pyright>=1.1.414; extra == 'test'
|
|
21
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
22
|
+
Requires-Dist: ruff>=0.8; extra == 'test'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# unitx
|
|
26
|
+
|
|
27
|
+
[](https://pypi.org/project/unitx/)
|
|
28
|
+
[](https://pypi.org/project/unitx/)
|
|
29
|
+
[](LICENSE)
|
|
30
|
+
|
|
31
|
+
Write physical formulas with units and constants. `unitx` combines unit
|
|
32
|
+
conversion and dimensional analysis with Python scalar arithmetic and optional
|
|
33
|
+
NumPy arrays. Unit scales and rational exponents retain their exact structure.
|
|
34
|
+
|
|
35
|
+
The library is in early development; public APIs may change during the 0.x series.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Python **3.12 or later** is required. The scalar library has no mandatory
|
|
40
|
+
runtime dependencies:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
python -m pip install unitx
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For arrays, install NumPy support. Add Matplotlib to run the plotting example:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
python -m pip install 'unitx[numpy]' matplotlib
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For development, install into the same interpreter that runs your code:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
python -m pip install -e '.[test,numpy]'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This README describes the current source tree. For an older release, consult
|
|
59
|
+
its matching Git tag.
|
|
60
|
+
|
|
61
|
+
## Start with units and constants
|
|
62
|
+
|
|
63
|
+
`si` provides units; `SI` provides physical constants. Both are read-only
|
|
64
|
+
namespaces in `unitx.si`:
|
|
65
|
+
|
|
66
|
+
```pycon
|
|
67
|
+
>>> from unitx.si import SI, si
|
|
68
|
+
>>> distance = 3 * si.m
|
|
69
|
+
>>> elapsed = 2 * si.s
|
|
70
|
+
>>> speed = distance / elapsed
|
|
71
|
+
>>> print(speed)
|
|
72
|
+
1.5 m/s
|
|
73
|
+
>>> print(speed.to(si.cm / si.s))
|
|
74
|
+
150.0 cm/s
|
|
75
|
+
>>> print(SI.c)
|
|
76
|
+
299792458 m/s
|
|
77
|
+
>>> rest_energy = (SI.me * SI.c**2).to(si.MeV)
|
|
78
|
+
>>> round(float(rest_energy.value), 6)
|
|
79
|
+
0.510999
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Multiplying a number by a unit constructs a Quantity. Multiplication and
|
|
84
|
+
division combine units; addition and subtraction align compatible quantities
|
|
85
|
+
to the left operand's unit. Incompatible conversions and arithmetic raise
|
|
86
|
+
`DimensionError`.
|
|
87
|
+
|
|
88
|
+
## NumPy example: blackbody radiation
|
|
89
|
+
|
|
90
|
+
Broadcast a column of wavelengths against three temperatures, then plot
|
|
91
|
+
Planck's spectral radiance:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import numpy as np
|
|
95
|
+
from matplotlib import pyplot as plt
|
|
96
|
+
|
|
97
|
+
from unitx.si import SI, si
|
|
98
|
+
|
|
99
|
+
lam = np.linspace(0.01, 3, 100)[:, None] * si.um # wavelength
|
|
100
|
+
T = np.asarray([3000, 4000, 5000]) * si.K # temperature
|
|
101
|
+
|
|
102
|
+
nu = SI.c / lam # frequency
|
|
103
|
+
radiance = 2 * SI.h * SI.c**2 / (lam**5 * np.expm1(SI.h * nu / (SI.kB * T)))
|
|
104
|
+
radiance = radiance.to('kW/sr/m2/nm') # spectral radiance per unit wavelength
|
|
105
|
+
|
|
106
|
+
plt.plot(lam.value, radiance.value)
|
|
107
|
+
plt.xlabel(f'Wavelength [{lam.unit}]')
|
|
108
|
+
plt.ylabel(f'Spectral radiance [{radiance.unit}]')
|
|
109
|
+
plt.legend([f'T = {t}' for t in T])
|
|
110
|
+
plt.title('Blackbody radiation')
|
|
111
|
+
plt.show()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+

|
|
115
|
+
|
|
116
|
+
One Quantity wraps the entire ndarray with one unit. NumPy handles broadcasting;
|
|
117
|
+
unitx checks dimensions and combines units. `SI.h` and `SI.kB` retain Decimal
|
|
118
|
+
values in the catalogue and enter NumPy calculations as floats.
|
|
119
|
+
|
|
120
|
+
`.to()` returns a new Quantity. `.value` exposes the numerical data for plotting;
|
|
121
|
+
`.value_in(unit)` also converts it to a chosen unit. Implicit unit removal with
|
|
122
|
+
`np.asarray(quantity)` or `np.array(quantity)` raises TypeError.
|
|
123
|
+
|
|
124
|
+
The [runnable example](examples/blackbody.py) uses `np.asarray([...]) * unit`
|
|
125
|
+
without an explicit dtype. This automatic construction inference is checked
|
|
126
|
+
with NumPy 2.5 stubs and Pyright; the example also runs on NumPy 2.1. Older
|
|
127
|
+
NumPy stubs may still infer Any for this expression. NumPy's own stubs can
|
|
128
|
+
infer different types for `np.array` and `np.asarray`;
|
|
129
|
+
`np.array([...]) * unit` can become Any. `unit * array` and
|
|
130
|
+
`Quantity(array, unit)` provide alternative construction forms. Arbitrary
|
|
131
|
+
shape/dtype inference and wrapped return types for every NumPy function are not
|
|
132
|
+
promised. See the [supported operations, storage rules, and typing limits](docs/numpy.md).
|
|
133
|
+
|
|
134
|
+
## Values and conversion
|
|
135
|
+
|
|
136
|
+
Use `Quantity` and `Unit` directly when constructing values from strings:
|
|
137
|
+
|
|
138
|
+
```pycon
|
|
139
|
+
>>> from unitx import Quantity, Unit
|
|
140
|
+
>>> length = Quantity(1, 'm')
|
|
141
|
+
>>> print(length + Quantity(50, 'cm'))
|
|
142
|
+
3/2 m
|
|
143
|
+
>>> length.value_in('cm')
|
|
144
|
+
100
|
|
145
|
+
>>> Quantity(1, 'cm').value_in('m')
|
|
146
|
+
Fraction(1, 100)
|
|
147
|
+
>>> Quantity(1, 'm') == Quantity(100, 'cm')
|
|
148
|
+
True
|
|
149
|
+
>>> Unit('m') == Unit('cm')
|
|
150
|
+
False
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A quantity's `.value` is paired with its `.unit`. Units retain their formal
|
|
155
|
+
identity: metre and centimetre are different units, although quantities can
|
|
156
|
+
compare equal after accounting for their scales.
|
|
157
|
+
|
|
158
|
+
Python scalar operations follow the magnitude's numerical domain. Decimal
|
|
159
|
+
operations retain Decimal context behavior; Fraction magnitudes provide exact
|
|
160
|
+
rational arithmetic. Scalar equality compares exact stored values, so binary
|
|
161
|
+
float `0.1` is distinct from Fraction(1, 10). Use symmetric `.isclose()` for
|
|
162
|
+
approximate comparisons:
|
|
163
|
+
|
|
164
|
+
```pycon
|
|
165
|
+
>>> Quantity(0.1 + 0.2, 'm').isclose(Quantity(0.3, 'm'))
|
|
166
|
+
True
|
|
167
|
+
>>> a, b = Quantity(1, 'm'), Quantity(100.05, 'cm')
|
|
168
|
+
>>> a.isclose(b, rel_tol=0, abs_tol=1 * si.mm)
|
|
169
|
+
True
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
NumPy numerical operations instead follow the documented array conversion,
|
|
174
|
+
promotion, rounding, and overflow rules. Array comparisons return boolean
|
|
175
|
+
masks, and NumPy-backed quantities are unhashable.
|
|
176
|
+
|
|
177
|
+
Read the [scalar numerical contract](docs/scalars.md) for Decimal conversion,
|
|
178
|
+
exact comparisons, powers, tolerances, and optional SymPy factorization.
|
|
179
|
+
|
|
180
|
+
## Expressions and custom units
|
|
181
|
+
|
|
182
|
+
Parse symbols, registered names, prefixes, products, division, and rational
|
|
183
|
+
powers:
|
|
184
|
+
|
|
185
|
+
```pycon
|
|
186
|
+
>>> Unit('kg m/s2') == Unit('kg*m/s**2')
|
|
187
|
+
True
|
|
188
|
+
>>> Unit('m^(1/2)') ** 2 == si.m
|
|
189
|
+
True
|
|
190
|
+
>>> Unit('ms') == Unit('m s')
|
|
191
|
+
False
|
|
192
|
+
>>> Unit('[astronomical unit] / s') == si.au / si.s
|
|
193
|
+
True
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Multiplication and division associate left to right: write `m/(s A)` for a
|
|
198
|
+
shared denominator. Multiword names can stand alone; use `[registered name]`
|
|
199
|
+
inside expressions. Readable `str()` output is not a guaranteed parser input.
|
|
200
|
+
|
|
201
|
+
Define a unit from an existing unit or a positive finite quantity:
|
|
202
|
+
|
|
203
|
+
```pycon
|
|
204
|
+
>>> stride = (2 * si.m).define('demoStride', 'demo stride', prefixable=False)
|
|
205
|
+
>>> (3 * stride).value_in('m')
|
|
206
|
+
6
|
|
207
|
+
>>> Unit('demo stride') == stride
|
|
208
|
+
True
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Definitions register spellings in the current process and return a new Unit.
|
|
213
|
+
Use `Factor` or `SymbolicConstant` with a unit when an additional exact unit
|
|
214
|
+
scale is intended; ordinary numbers construct quantities.
|
|
215
|
+
|
|
216
|
+
See the [unit and quantity guide](docs/units.md) for ScaledUnit, dynamic
|
|
217
|
+
registration, spelling-specific prefix permissions, catalogue conventions,
|
|
218
|
+
copying, and serialization.
|
|
219
|
+
|
|
220
|
+
## Scope and types
|
|
221
|
+
|
|
222
|
+
- Supported magnitudes are Python scalars and optional plain numeric NumPy
|
|
223
|
+
arrays/scalars. Other array backends, ndarray subclasses, object arrays,
|
|
224
|
+
and implicit list/tuple magnitudes are unsupported.
|
|
225
|
+
- Conversion uses multiplication by a scale. Offset temperature scales such
|
|
226
|
+
as Celsius and Fahrenheit are unsupported.
|
|
227
|
+
- Covered single-output NumPy ufuncs support unit-aware `out` buffers.
|
|
228
|
+
Item assignment, true in-place operators, function/reduction `out`, and
|
|
229
|
+
heterogeneous-unit reductions remain outside the current support matrix.
|
|
230
|
+
- The package ships inline annotations and `py.typed`. Construction and
|
|
231
|
+
unit-only operations preserve the magnitude type; `.T` also retains it.
|
|
232
|
+
Array calculations and conversion use concrete ndarray/NumPy-scalar result
|
|
233
|
+
unions, making native `.value` methods available without claiming an
|
|
234
|
+
unchanged dtype. Scalar protocols avoid recursive arithmetic return types
|
|
235
|
+
so editor analysis remains responsive.
|
|
236
|
+
|
|
237
|
+
The package root exports `Quantity`, `Unit`, `Dimension`, `DIMENSIONLESS`,
|
|
238
|
+
`ONE`, `TEN`, and `PI`; `SI` and `si` are imported from `unitx.si`.
|
|
239
|
+
Other building blocks are available from their modules. Modules beginning
|
|
240
|
+
with `_` are internal.
|
|
241
|
+
|
|
242
|
+
## Documentation and development
|
|
243
|
+
|
|
244
|
+
- [Unit and quantity guide](docs/units.md)
|
|
245
|
+
- [Scalar numerical behavior](docs/scalars.md)
|
|
246
|
+
- [NumPy support matrix and typing](docs/numpy.md)
|
|
247
|
+
- [Migration from 0.3.0](docs/migration.md)
|
|
248
|
+
- [Changelog](CHANGELOG.md)
|
|
249
|
+
|
|
250
|
+
Run the checks from the repository root:
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
python -m pytest -q
|
|
254
|
+
python -m ruff check .
|
|
255
|
+
python -m ruff format --check .
|
|
256
|
+
python -m doctest README.md docs/units.md docs/scalars.md
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Pytest includes runtime tests, source doctests, and Pyright checks. CI covers
|
|
260
|
+
Python 3.12–3.15, optional SymPy, and minimum/current NumPy versions. Strict
|
|
261
|
+
scalar consumers are also checked without NumPy installed.
|
|
262
|
+
|
|
263
|
+
Publishing a GitHub Release triggers the [release workflow](.github/workflows/release.yml)
|
|
264
|
+
to validate and upload wheel/sdist distributions to PyPI. Manual workflow runs
|
|
265
|
+
validate and store distributions without uploading them. Release tags must
|
|
266
|
+
match the project version.
|
|
267
|
+
|
|
268
|
+
Licensed under the [MIT License](LICENSE).
|
unitx-0.8.2/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# unitx
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/unitx/)
|
|
4
|
+
[](https://pypi.org/project/unitx/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Write physical formulas with units and constants. `unitx` combines unit
|
|
8
|
+
conversion and dimensional analysis with Python scalar arithmetic and optional
|
|
9
|
+
NumPy arrays. Unit scales and rational exponents retain their exact structure.
|
|
10
|
+
|
|
11
|
+
The library is in early development; public APIs may change during the 0.x series.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Python **3.12 or later** is required. The scalar library has no mandatory
|
|
16
|
+
runtime dependencies:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
python -m pip install unitx
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For arrays, install NumPy support. Add Matplotlib to run the plotting example:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
python -m pip install 'unitx[numpy]' matplotlib
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For development, install into the same interpreter that runs your code:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
python -m pip install -e '.[test,numpy]'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This README describes the current source tree. For an older release, consult
|
|
35
|
+
its matching Git tag.
|
|
36
|
+
|
|
37
|
+
## Start with units and constants
|
|
38
|
+
|
|
39
|
+
`si` provides units; `SI` provides physical constants. Both are read-only
|
|
40
|
+
namespaces in `unitx.si`:
|
|
41
|
+
|
|
42
|
+
```pycon
|
|
43
|
+
>>> from unitx.si import SI, si
|
|
44
|
+
>>> distance = 3 * si.m
|
|
45
|
+
>>> elapsed = 2 * si.s
|
|
46
|
+
>>> speed = distance / elapsed
|
|
47
|
+
>>> print(speed)
|
|
48
|
+
1.5 m/s
|
|
49
|
+
>>> print(speed.to(si.cm / si.s))
|
|
50
|
+
150.0 cm/s
|
|
51
|
+
>>> print(SI.c)
|
|
52
|
+
299792458 m/s
|
|
53
|
+
>>> rest_energy = (SI.me * SI.c**2).to(si.MeV)
|
|
54
|
+
>>> round(float(rest_energy.value), 6)
|
|
55
|
+
0.510999
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Multiplying a number by a unit constructs a Quantity. Multiplication and
|
|
60
|
+
division combine units; addition and subtraction align compatible quantities
|
|
61
|
+
to the left operand's unit. Incompatible conversions and arithmetic raise
|
|
62
|
+
`DimensionError`.
|
|
63
|
+
|
|
64
|
+
## NumPy example: blackbody radiation
|
|
65
|
+
|
|
66
|
+
Broadcast a column of wavelengths against three temperatures, then plot
|
|
67
|
+
Planck's spectral radiance:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import numpy as np
|
|
71
|
+
from matplotlib import pyplot as plt
|
|
72
|
+
|
|
73
|
+
from unitx.si import SI, si
|
|
74
|
+
|
|
75
|
+
lam = np.linspace(0.01, 3, 100)[:, None] * si.um # wavelength
|
|
76
|
+
T = np.asarray([3000, 4000, 5000]) * si.K # temperature
|
|
77
|
+
|
|
78
|
+
nu = SI.c / lam # frequency
|
|
79
|
+
radiance = 2 * SI.h * SI.c**2 / (lam**5 * np.expm1(SI.h * nu / (SI.kB * T)))
|
|
80
|
+
radiance = radiance.to('kW/sr/m2/nm') # spectral radiance per unit wavelength
|
|
81
|
+
|
|
82
|
+
plt.plot(lam.value, radiance.value)
|
|
83
|
+
plt.xlabel(f'Wavelength [{lam.unit}]')
|
|
84
|
+
plt.ylabel(f'Spectral radiance [{radiance.unit}]')
|
|
85
|
+
plt.legend([f'T = {t}' for t in T])
|
|
86
|
+
plt.title('Blackbody radiation')
|
|
87
|
+
plt.show()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
|
|
92
|
+
One Quantity wraps the entire ndarray with one unit. NumPy handles broadcasting;
|
|
93
|
+
unitx checks dimensions and combines units. `SI.h` and `SI.kB` retain Decimal
|
|
94
|
+
values in the catalogue and enter NumPy calculations as floats.
|
|
95
|
+
|
|
96
|
+
`.to()` returns a new Quantity. `.value` exposes the numerical data for plotting;
|
|
97
|
+
`.value_in(unit)` also converts it to a chosen unit. Implicit unit removal with
|
|
98
|
+
`np.asarray(quantity)` or `np.array(quantity)` raises TypeError.
|
|
99
|
+
|
|
100
|
+
The [runnable example](examples/blackbody.py) uses `np.asarray([...]) * unit`
|
|
101
|
+
without an explicit dtype. This automatic construction inference is checked
|
|
102
|
+
with NumPy 2.5 stubs and Pyright; the example also runs on NumPy 2.1. Older
|
|
103
|
+
NumPy stubs may still infer Any for this expression. NumPy's own stubs can
|
|
104
|
+
infer different types for `np.array` and `np.asarray`;
|
|
105
|
+
`np.array([...]) * unit` can become Any. `unit * array` and
|
|
106
|
+
`Quantity(array, unit)` provide alternative construction forms. Arbitrary
|
|
107
|
+
shape/dtype inference and wrapped return types for every NumPy function are not
|
|
108
|
+
promised. See the [supported operations, storage rules, and typing limits](docs/numpy.md).
|
|
109
|
+
|
|
110
|
+
## Values and conversion
|
|
111
|
+
|
|
112
|
+
Use `Quantity` and `Unit` directly when constructing values from strings:
|
|
113
|
+
|
|
114
|
+
```pycon
|
|
115
|
+
>>> from unitx import Quantity, Unit
|
|
116
|
+
>>> length = Quantity(1, 'm')
|
|
117
|
+
>>> print(length + Quantity(50, 'cm'))
|
|
118
|
+
3/2 m
|
|
119
|
+
>>> length.value_in('cm')
|
|
120
|
+
100
|
|
121
|
+
>>> Quantity(1, 'cm').value_in('m')
|
|
122
|
+
Fraction(1, 100)
|
|
123
|
+
>>> Quantity(1, 'm') == Quantity(100, 'cm')
|
|
124
|
+
True
|
|
125
|
+
>>> Unit('m') == Unit('cm')
|
|
126
|
+
False
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A quantity's `.value` is paired with its `.unit`. Units retain their formal
|
|
131
|
+
identity: metre and centimetre are different units, although quantities can
|
|
132
|
+
compare equal after accounting for their scales.
|
|
133
|
+
|
|
134
|
+
Python scalar operations follow the magnitude's numerical domain. Decimal
|
|
135
|
+
operations retain Decimal context behavior; Fraction magnitudes provide exact
|
|
136
|
+
rational arithmetic. Scalar equality compares exact stored values, so binary
|
|
137
|
+
float `0.1` is distinct from Fraction(1, 10). Use symmetric `.isclose()` for
|
|
138
|
+
approximate comparisons:
|
|
139
|
+
|
|
140
|
+
```pycon
|
|
141
|
+
>>> Quantity(0.1 + 0.2, 'm').isclose(Quantity(0.3, 'm'))
|
|
142
|
+
True
|
|
143
|
+
>>> a, b = Quantity(1, 'm'), Quantity(100.05, 'cm')
|
|
144
|
+
>>> a.isclose(b, rel_tol=0, abs_tol=1 * si.mm)
|
|
145
|
+
True
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
NumPy numerical operations instead follow the documented array conversion,
|
|
150
|
+
promotion, rounding, and overflow rules. Array comparisons return boolean
|
|
151
|
+
masks, and NumPy-backed quantities are unhashable.
|
|
152
|
+
|
|
153
|
+
Read the [scalar numerical contract](docs/scalars.md) for Decimal conversion,
|
|
154
|
+
exact comparisons, powers, tolerances, and optional SymPy factorization.
|
|
155
|
+
|
|
156
|
+
## Expressions and custom units
|
|
157
|
+
|
|
158
|
+
Parse symbols, registered names, prefixes, products, division, and rational
|
|
159
|
+
powers:
|
|
160
|
+
|
|
161
|
+
```pycon
|
|
162
|
+
>>> Unit('kg m/s2') == Unit('kg*m/s**2')
|
|
163
|
+
True
|
|
164
|
+
>>> Unit('m^(1/2)') ** 2 == si.m
|
|
165
|
+
True
|
|
166
|
+
>>> Unit('ms') == Unit('m s')
|
|
167
|
+
False
|
|
168
|
+
>>> Unit('[astronomical unit] / s') == si.au / si.s
|
|
169
|
+
True
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Multiplication and division associate left to right: write `m/(s A)` for a
|
|
174
|
+
shared denominator. Multiword names can stand alone; use `[registered name]`
|
|
175
|
+
inside expressions. Readable `str()` output is not a guaranteed parser input.
|
|
176
|
+
|
|
177
|
+
Define a unit from an existing unit or a positive finite quantity:
|
|
178
|
+
|
|
179
|
+
```pycon
|
|
180
|
+
>>> stride = (2 * si.m).define('demoStride', 'demo stride', prefixable=False)
|
|
181
|
+
>>> (3 * stride).value_in('m')
|
|
182
|
+
6
|
|
183
|
+
>>> Unit('demo stride') == stride
|
|
184
|
+
True
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Definitions register spellings in the current process and return a new Unit.
|
|
189
|
+
Use `Factor` or `SymbolicConstant` with a unit when an additional exact unit
|
|
190
|
+
scale is intended; ordinary numbers construct quantities.
|
|
191
|
+
|
|
192
|
+
See the [unit and quantity guide](docs/units.md) for ScaledUnit, dynamic
|
|
193
|
+
registration, spelling-specific prefix permissions, catalogue conventions,
|
|
194
|
+
copying, and serialization.
|
|
195
|
+
|
|
196
|
+
## Scope and types
|
|
197
|
+
|
|
198
|
+
- Supported magnitudes are Python scalars and optional plain numeric NumPy
|
|
199
|
+
arrays/scalars. Other array backends, ndarray subclasses, object arrays,
|
|
200
|
+
and implicit list/tuple magnitudes are unsupported.
|
|
201
|
+
- Conversion uses multiplication by a scale. Offset temperature scales such
|
|
202
|
+
as Celsius and Fahrenheit are unsupported.
|
|
203
|
+
- Covered single-output NumPy ufuncs support unit-aware `out` buffers.
|
|
204
|
+
Item assignment, true in-place operators, function/reduction `out`, and
|
|
205
|
+
heterogeneous-unit reductions remain outside the current support matrix.
|
|
206
|
+
- The package ships inline annotations and `py.typed`. Construction and
|
|
207
|
+
unit-only operations preserve the magnitude type; `.T` also retains it.
|
|
208
|
+
Array calculations and conversion use concrete ndarray/NumPy-scalar result
|
|
209
|
+
unions, making native `.value` methods available without claiming an
|
|
210
|
+
unchanged dtype. Scalar protocols avoid recursive arithmetic return types
|
|
211
|
+
so editor analysis remains responsive.
|
|
212
|
+
|
|
213
|
+
The package root exports `Quantity`, `Unit`, `Dimension`, `DIMENSIONLESS`,
|
|
214
|
+
`ONE`, `TEN`, and `PI`; `SI` and `si` are imported from `unitx.si`.
|
|
215
|
+
Other building blocks are available from their modules. Modules beginning
|
|
216
|
+
with `_` are internal.
|
|
217
|
+
|
|
218
|
+
## Documentation and development
|
|
219
|
+
|
|
220
|
+
- [Unit and quantity guide](docs/units.md)
|
|
221
|
+
- [Scalar numerical behavior](docs/scalars.md)
|
|
222
|
+
- [NumPy support matrix and typing](docs/numpy.md)
|
|
223
|
+
- [Migration from 0.3.0](docs/migration.md)
|
|
224
|
+
- [Changelog](CHANGELOG.md)
|
|
225
|
+
|
|
226
|
+
Run the checks from the repository root:
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
python -m pytest -q
|
|
230
|
+
python -m ruff check .
|
|
231
|
+
python -m ruff format --check .
|
|
232
|
+
python -m doctest README.md docs/units.md docs/scalars.md
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Pytest includes runtime tests, source doctests, and Pyright checks. CI covers
|
|
236
|
+
Python 3.12–3.15, optional SymPy, and minimum/current NumPy versions. Strict
|
|
237
|
+
scalar consumers are also checked without NumPy installed.
|
|
238
|
+
|
|
239
|
+
Publishing a GitHub Release triggers the [release workflow](.github/workflows/release.yml)
|
|
240
|
+
to validate and upload wheel/sdist distributions to PyPI. Manual workflow runs
|
|
241
|
+
validate and store distributions without uploading them. Release tags must
|
|
242
|
+
match the project version.
|
|
243
|
+
|
|
244
|
+
Licensed under the [MIT License](LICENSE).
|
|
@@ -6,7 +6,10 @@ from unitx._numerics import as_fraction
|
|
|
6
6
|
from unitx.exceptions import InexactFloatWarning
|
|
7
7
|
from unitx.factor import Factor
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# Static-only fixtures run through Pyright in tests/test_typing.py.
|
|
10
|
+
collect_ignore = ['tests/example_typing']
|
|
11
|
+
if find_spec('numpy') is None:
|
|
12
|
+
collect_ignore.append('src/unitx/_numpy.py')
|
|
10
13
|
|
|
11
14
|
_DOCTEST_FILTER: dict[str, tuple[pytest.MarkDecorator, ...]] = {
|
|
12
15
|
f'{obj.__module__}.{obj.__qualname__}': tuple(
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Upgrading from 0.3.0
|
|
2
|
+
|
|
3
|
+
Version 0.4.0 replaced the implementation previously published on PyPI as 0.3.0.
|
|
4
|
+
It introduced incompatible changes:
|
|
5
|
+
|
|
6
|
+
- Python 3.12 or later is required; Python 3.11 is no longer supported.
|
|
7
|
+
- The former `unitx.utils` namespace was removed. Classes can be imported from
|
|
8
|
+
their modules, as shown above; the current source tree also exports `Unit`,
|
|
9
|
+
`Quantity`, and `Dimension` at the package root.
|
|
10
|
+
- `Unit` no longer defines `len()` or special truth-value behavior. Use
|
|
11
|
+
`unit == Unit('')` to check the formal identity, or `unit.is_dimensionless()`
|
|
12
|
+
to check physical dimensions.
|
|
13
|
+
- Conversion and arithmetic can produce different magnitude types, especially
|
|
14
|
+
for Decimal inputs. Review the [scalar numerical behavior](scalars.md)
|
|
15
|
+
before relying on a result's concrete type or approximate equality.
|
|
16
|
+
|