unitx 0.8.3__tar.gz → 0.8.4__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.3 → unitx-0.8.4}/CHANGELOG.md +25 -0
- {unitx-0.8.3 → unitx-0.8.4}/PKG-INFO +45 -22
- {unitx-0.8.3 → unitx-0.8.4}/README.md +43 -20
- {unitx-0.8.3 → unitx-0.8.4}/docs/numpy.md +70 -7
- unitx-0.8.4/examples/README.md +52 -0
- unitx-0.8.4/examples/blackbody.ipynb +143 -0
- unitx-0.8.4/examples/damped_oscillator.ipynb +153 -0
- unitx-0.8.4/examples/gaussian_wavepacket.ipynb +149 -0
- unitx-0.8.4/examples/optical_attenuation.ipynb +176 -0
- unitx-0.8.4/examples/pendulum_uncertainty.ipynb +188 -0
- {unitx-0.8.3 → unitx-0.8.4}/pyproject.toml +2 -2
- {unitx-0.8.3 → unitx-0.8.4}/pyrightconfig.json +3 -1
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/_numpy.py +105 -12
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/quantity.py +9 -22
- unitx-0.8.4/tests/_notebooks.py +11 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/numpy_typing/check_numpy.py +11 -0
- unitx-0.8.4/tests/test_examples.py +42 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_numpy.py +9 -6
- unitx-0.8.4/tests/test_numpy_integration.py +93 -0
- unitx-0.8.4/tests/test_numpy_products.py +296 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_typing.py +17 -9
- unitx-0.8.3/examples/blackbody.py +0 -20
- unitx-0.8.3/pyrightconfig.asarray.json +0 -5
- unitx-0.8.3/pyrightconfig.examples.json +0 -5
- unitx-0.8.3/pyrightconfig.numpy.json +0 -5
- unitx-0.8.3/pyrightconfig.scalar.json +0 -5
- {unitx-0.8.3 → unitx-0.8.4}/.github/workflows/ci.yml +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/.github/workflows/release.yml +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/.gitignore +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/LICENSE +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/conftest.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/docs/images/blackbody.png +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/docs/migration.md +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/docs/scalars.md +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/docs/units.md +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/__init__.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/_constnamespace.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/_numerics.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/_numpy_typing.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/_unicode.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/dimension.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/exceptions.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/factor.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/monomial.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/py.typed +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/si/__init__.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/si/bases.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/si/constants.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/si/units.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/singleunit.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/unit.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/unitparser.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/src/unitx/unitregistry.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/__init__.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/example_typing/check_blackbody.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_builtin_definitions.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_dimension.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_factor.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_import.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_monomial.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_numeric.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_numpy_outputs.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_quantity.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_si.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_singleunit.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_unicode.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_unit.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_unit_definition.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_unitparser.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_unitregistry.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/test_warnings.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/typing/check_quantity.py +0 -0
- {unitx-0.8.3 → unitx-0.8.4}/tests/typing/check_si.py +0 -0
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.4] - 2026-10-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Support prod and multiply.reduce with exact product units, scalar initial
|
|
8
|
+
values (including Quantities), boolean input masks, and staged output writes.
|
|
9
|
+
Require equal selected counts for dimensional outputs; normalize dimensionless
|
|
10
|
+
inputs before reduction. Document empty products and accumulator dtype rules.
|
|
11
|
+
- Support trapezoid integration with Quantity ordinates, coordinates, or scalar
|
|
12
|
+
spacing. Combine their units directly, preserving native coordinate order
|
|
13
|
+
and axis behavior without manual value extraction or unit reconstruction.
|
|
14
|
+
|
|
15
|
+
### Improved
|
|
16
|
+
|
|
17
|
+
- Keep Python-comparison-to-ufunc mapping in the optional NumPy adapter and
|
|
18
|
+
replace redundant Fractionable type guards with direct isinstance checks.
|
|
19
|
+
|
|
20
|
+
### Examples
|
|
21
|
+
|
|
22
|
+
- Replace the blackbody script with a notebook and add damped motion, free
|
|
23
|
+
Gaussian wave-packet propagation, optical attenuation, and correlated
|
|
24
|
+
measurement-uncertainty notebooks. Keep calculations as Quantities and derive
|
|
25
|
+
plot labels from their units. Check execution, typing, and output-free notebook
|
|
26
|
+
files without adding Jupyter as a library or test dependency.
|
|
27
|
+
|
|
3
28
|
## [0.8.3] - 2026-10-20
|
|
4
29
|
|
|
5
30
|
### Added
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitx
|
|
3
|
-
Version: 0.8.
|
|
4
|
-
Summary: A modern, intuitive
|
|
3
|
+
Version: 0.8.4
|
|
4
|
+
Summary: A modern, intuitive unit library, with exact unit algebra, expressive type hints, and NumPy integration.
|
|
5
5
|
Project-URL: Homepage, https://github.com/DaitTHU/unitx
|
|
6
6
|
Project-URL: Issues, https://github.com/DaitTHU/unitx/issues
|
|
7
7
|
Author: Dait
|
|
@@ -28,31 +28,37 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
[](https://pypi.org/project/unitx/)
|
|
29
29
|
[](LICENSE)
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
A modern, intuitive unit library for Python 3.12+.
|
|
32
|
+
Exact unit algebra, expressive type hints, and NumPy integration.
|
|
33
|
+
|
|
34
|
+
Write physical formulas with units and constants in Python. `unitx` provides
|
|
35
|
+
unit conversion and dimensional analysis for Python scalars and integrates
|
|
36
|
+
with NumPy array arithmetic, broadcasting, and supported functions.
|
|
37
|
+
Unit scales and rational exponents retain their exact structure.
|
|
34
38
|
|
|
35
39
|
The library is in early development; public APIs may change during the 0.x series.
|
|
36
40
|
|
|
37
41
|
## Install
|
|
38
42
|
|
|
39
|
-
Python **3.12 or later** is required.
|
|
40
|
-
runtime dependencies:
|
|
43
|
+
Python **3.12 or later** is required. Install unitx with:
|
|
41
44
|
|
|
42
45
|
```sh
|
|
43
|
-
|
|
46
|
+
pip install unitx
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
There are no mandatory runtime dependencies. If you already use NumPy **2.1
|
|
50
|
+
or later**, this installation also lets you use units in your NumPy calculations.
|
|
51
|
+
For NumPy work in a new environment or with an older NumPy version, use the
|
|
52
|
+
following command to install unitx and a compatible NumPy version:
|
|
47
53
|
|
|
48
54
|
```sh
|
|
49
|
-
|
|
55
|
+
pip install 'unitx[numpy]'
|
|
50
56
|
```
|
|
51
57
|
|
|
52
|
-
For development, install into
|
|
58
|
+
For development, install into your active Python environment:
|
|
53
59
|
|
|
54
60
|
```sh
|
|
55
|
-
|
|
61
|
+
pip install -e '.[test,numpy]'
|
|
56
62
|
```
|
|
57
63
|
|
|
58
64
|
This README describes the current source tree. For an older release, consult
|
|
@@ -60,8 +66,17 @@ its matching Git tag.
|
|
|
60
66
|
|
|
61
67
|
## Start with units and constants
|
|
62
68
|
|
|
63
|
-
`si` provides units
|
|
64
|
-
|
|
69
|
+
The `unitx.si` submodule provides ready-to-use units and physical constants
|
|
70
|
+
based on the International System of Units (SI), through two read-only
|
|
71
|
+
namespaces:
|
|
72
|
+
|
|
73
|
+
- `si` contains units such as `si.m`, `si.s`, and `si.MeV`, including common
|
|
74
|
+
prefixes and non-SI units.
|
|
75
|
+
- `SI` contains physical constants expressed in SI units, such as the speed
|
|
76
|
+
of light `SI.c` and the Planck constant `SI.h`.
|
|
77
|
+
|
|
78
|
+
In the import below, `unitx.si` is the module path; the imported `si` and `SI`
|
|
79
|
+
are the namespaces you use directly in formulas:
|
|
65
80
|
|
|
66
81
|
```pycon
|
|
67
82
|
>>> from unitx.si import SI, si
|
|
@@ -85,10 +100,13 @@ division combine units; addition and subtraction align compatible quantities
|
|
|
85
100
|
to the left operand's unit. Incompatible conversions and arithmetic raise
|
|
86
101
|
`DimensionError`.
|
|
87
102
|
|
|
88
|
-
## NumPy
|
|
103
|
+
## Units in NumPy: blackbody radiation
|
|
89
104
|
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
Attach units to NumPy arrays, then use broadcasting and NumPy functions to
|
|
106
|
+
evaluate a physical formula. Here, a column of wavelengths and three
|
|
107
|
+
temperatures broadcast to give Planck's spectral radiance.
|
|
108
|
+
|
|
109
|
+
Add `matplotlib` to your environment to plot the result:
|
|
92
110
|
|
|
93
111
|
```python
|
|
94
112
|
import numpy as np
|
|
@@ -121,7 +139,7 @@ values in the catalogue and enter NumPy calculations as floats.
|
|
|
121
139
|
`.value_in(unit)` also converts it to a chosen unit. Implicit unit removal with
|
|
122
140
|
`np.asarray(quantity)` or `np.array(quantity)` raises TypeError.
|
|
123
141
|
|
|
124
|
-
The [
|
|
142
|
+
The [blackbody notebook](examples/blackbody.ipynb) uses `np.asarray([...]) * unit`
|
|
125
143
|
without an explicit dtype. This automatic construction inference is checked
|
|
126
144
|
with NumPy 2.5 stubs and Pyright; the example also runs on NumPy 2.1. Older
|
|
127
145
|
NumPy stubs may still infer Any for this expression. NumPy's own stubs can
|
|
@@ -131,6 +149,11 @@ infer different types for `np.array` and `np.asarray`;
|
|
|
131
149
|
shape/dtype inference and wrapped return types for every NumPy function are not
|
|
132
150
|
promised. See the [supported operations, storage rules, and typing limits](docs/numpy.md).
|
|
133
151
|
|
|
152
|
+
The [physics notebooks](examples/README.md) also cover damped motion, Gaussian
|
|
153
|
+
wave-packet spreading, optical attenuation, and correlated measurement uncertainty.
|
|
154
|
+
They include explanations and numerical checks; plots are generated when run,
|
|
155
|
+
keeping saved notebooks small.
|
|
156
|
+
|
|
134
157
|
## Values and conversion
|
|
135
158
|
|
|
136
159
|
Use `Quantity` and `Unit` directly when constructing values from strings:
|
|
@@ -219,7 +242,7 @@ copying, and serialization.
|
|
|
219
242
|
|
|
220
243
|
## Scope and types
|
|
221
244
|
|
|
222
|
-
- Supported magnitudes are Python scalars and
|
|
245
|
+
- Supported magnitudes are Python scalars and plain numeric NumPy
|
|
223
246
|
arrays/scalars. Other array backends, ndarray subclasses, object arrays,
|
|
224
247
|
and implicit list/tuple magnitudes are unsupported.
|
|
225
248
|
- Conversion uses multiplication by a scale. Offset temperature scales such
|
|
@@ -250,9 +273,9 @@ with `_` are internal.
|
|
|
250
273
|
Run the checks from the repository root:
|
|
251
274
|
|
|
252
275
|
```sh
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
276
|
+
pytest -q
|
|
277
|
+
ruff check .
|
|
278
|
+
ruff format --check .
|
|
256
279
|
python -m doctest README.md docs/units.md docs/scalars.md
|
|
257
280
|
```
|
|
258
281
|
|
|
@@ -4,31 +4,37 @@
|
|
|
4
4
|
[](https://pypi.org/project/unitx/)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
A modern, intuitive unit library for Python 3.12+.
|
|
8
|
+
Exact unit algebra, expressive type hints, and NumPy integration.
|
|
9
|
+
|
|
10
|
+
Write physical formulas with units and constants in Python. `unitx` provides
|
|
11
|
+
unit conversion and dimensional analysis for Python scalars and integrates
|
|
12
|
+
with NumPy array arithmetic, broadcasting, and supported functions.
|
|
13
|
+
Unit scales and rational exponents retain their exact structure.
|
|
10
14
|
|
|
11
15
|
The library is in early development; public APIs may change during the 0.x series.
|
|
12
16
|
|
|
13
17
|
## Install
|
|
14
18
|
|
|
15
|
-
Python **3.12 or later** is required.
|
|
16
|
-
runtime dependencies:
|
|
19
|
+
Python **3.12 or later** is required. Install unitx with:
|
|
17
20
|
|
|
18
21
|
```sh
|
|
19
|
-
|
|
22
|
+
pip install unitx
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
There are no mandatory runtime dependencies. If you already use NumPy **2.1
|
|
26
|
+
or later**, this installation also lets you use units in your NumPy calculations.
|
|
27
|
+
For NumPy work in a new environment or with an older NumPy version, use the
|
|
28
|
+
following command to install unitx and a compatible NumPy version:
|
|
23
29
|
|
|
24
30
|
```sh
|
|
25
|
-
|
|
31
|
+
pip install 'unitx[numpy]'
|
|
26
32
|
```
|
|
27
33
|
|
|
28
|
-
For development, install into
|
|
34
|
+
For development, install into your active Python environment:
|
|
29
35
|
|
|
30
36
|
```sh
|
|
31
|
-
|
|
37
|
+
pip install -e '.[test,numpy]'
|
|
32
38
|
```
|
|
33
39
|
|
|
34
40
|
This README describes the current source tree. For an older release, consult
|
|
@@ -36,8 +42,17 @@ its matching Git tag.
|
|
|
36
42
|
|
|
37
43
|
## Start with units and constants
|
|
38
44
|
|
|
39
|
-
`si` provides units
|
|
40
|
-
|
|
45
|
+
The `unitx.si` submodule provides ready-to-use units and physical constants
|
|
46
|
+
based on the International System of Units (SI), through two read-only
|
|
47
|
+
namespaces:
|
|
48
|
+
|
|
49
|
+
- `si` contains units such as `si.m`, `si.s`, and `si.MeV`, including common
|
|
50
|
+
prefixes and non-SI units.
|
|
51
|
+
- `SI` contains physical constants expressed in SI units, such as the speed
|
|
52
|
+
of light `SI.c` and the Planck constant `SI.h`.
|
|
53
|
+
|
|
54
|
+
In the import below, `unitx.si` is the module path; the imported `si` and `SI`
|
|
55
|
+
are the namespaces you use directly in formulas:
|
|
41
56
|
|
|
42
57
|
```pycon
|
|
43
58
|
>>> from unitx.si import SI, si
|
|
@@ -61,10 +76,13 @@ division combine units; addition and subtraction align compatible quantities
|
|
|
61
76
|
to the left operand's unit. Incompatible conversions and arithmetic raise
|
|
62
77
|
`DimensionError`.
|
|
63
78
|
|
|
64
|
-
## NumPy
|
|
79
|
+
## Units in NumPy: blackbody radiation
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
Attach units to NumPy arrays, then use broadcasting and NumPy functions to
|
|
82
|
+
evaluate a physical formula. Here, a column of wavelengths and three
|
|
83
|
+
temperatures broadcast to give Planck's spectral radiance.
|
|
84
|
+
|
|
85
|
+
Add `matplotlib` to your environment to plot the result:
|
|
68
86
|
|
|
69
87
|
```python
|
|
70
88
|
import numpy as np
|
|
@@ -97,7 +115,7 @@ values in the catalogue and enter NumPy calculations as floats.
|
|
|
97
115
|
`.value_in(unit)` also converts it to a chosen unit. Implicit unit removal with
|
|
98
116
|
`np.asarray(quantity)` or `np.array(quantity)` raises TypeError.
|
|
99
117
|
|
|
100
|
-
The [
|
|
118
|
+
The [blackbody notebook](examples/blackbody.ipynb) uses `np.asarray([...]) * unit`
|
|
101
119
|
without an explicit dtype. This automatic construction inference is checked
|
|
102
120
|
with NumPy 2.5 stubs and Pyright; the example also runs on NumPy 2.1. Older
|
|
103
121
|
NumPy stubs may still infer Any for this expression. NumPy's own stubs can
|
|
@@ -107,6 +125,11 @@ infer different types for `np.array` and `np.asarray`;
|
|
|
107
125
|
shape/dtype inference and wrapped return types for every NumPy function are not
|
|
108
126
|
promised. See the [supported operations, storage rules, and typing limits](docs/numpy.md).
|
|
109
127
|
|
|
128
|
+
The [physics notebooks](examples/README.md) also cover damped motion, Gaussian
|
|
129
|
+
wave-packet spreading, optical attenuation, and correlated measurement uncertainty.
|
|
130
|
+
They include explanations and numerical checks; plots are generated when run,
|
|
131
|
+
keeping saved notebooks small.
|
|
132
|
+
|
|
110
133
|
## Values and conversion
|
|
111
134
|
|
|
112
135
|
Use `Quantity` and `Unit` directly when constructing values from strings:
|
|
@@ -195,7 +218,7 @@ copying, and serialization.
|
|
|
195
218
|
|
|
196
219
|
## Scope and types
|
|
197
220
|
|
|
198
|
-
- Supported magnitudes are Python scalars and
|
|
221
|
+
- Supported magnitudes are Python scalars and plain numeric NumPy
|
|
199
222
|
arrays/scalars. Other array backends, ndarray subclasses, object arrays,
|
|
200
223
|
and implicit list/tuple magnitudes are unsupported.
|
|
201
224
|
- Conversion uses multiplication by a scale. Offset temperature scales such
|
|
@@ -226,9 +249,9 @@ with `_` are internal.
|
|
|
226
249
|
Run the checks from the repository root:
|
|
227
250
|
|
|
228
251
|
```sh
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
252
|
+
pytest -q
|
|
253
|
+
ruff check .
|
|
254
|
+
ruff format --check .
|
|
232
255
|
python -m doctest README.md docs/units.md docs/scalars.md
|
|
233
256
|
```
|
|
234
257
|
|
|
@@ -80,13 +80,14 @@ methods, mixed array types, and keywords fail explicitly.
|
|
|
80
80
|
| sin, cos, tan, exp, expm1, log, log2, log10, log1p | Require coherent dimensionless values; return dimensionless Quantity |
|
|
81
81
|
| isfinite, isnan, isinf, signbit | Bare boolean results |
|
|
82
82
|
| add.reduce, add.accumulate | Preserve unit; accept axis, dtype, out, plus keepdims for reduce |
|
|
83
|
+
| multiply.reduce | Product unit rules below; accepts axis, dtype, keepdims, initial, where, out |
|
|
83
84
|
|
|
84
85
|
Ufunc `__call__` accepts `where`, `dtype`, `casting`, `order`, `subok`, and
|
|
85
86
|
single-output `out` as described below. Each kernel validates its own options.
|
|
86
87
|
`where` is a bare boolean scalar
|
|
87
88
|
or plain boolean ndarray; masked-out positions without an output buffer follow
|
|
88
89
|
NumPy's uninitialized-result behavior. Numeric outputs must stay in the
|
|
89
|
-
supported dtype domain. Product
|
|
90
|
+
supported dtype domain. Product accumulation, at, outer, and reduceat are
|
|
90
91
|
unsupported. Python `**` also accepts Fraction/Decimal exponents, retaining
|
|
91
92
|
exact unit exponents while passing a float exponent to the NumPy kernel.
|
|
92
93
|
Angles retain unitx's dimensionless model: deg/rad normalize before these
|
|
@@ -97,22 +98,82 @@ functions, including exp/log. Python scalar numerical semantics remain native.
|
|
|
97
98
|
| reshape, transpose | Preserve unit | shape/order/copy; axes |
|
|
98
99
|
| concatenate, stack | Convert to first Quantity's unit; bare arrays remain dimensionless | axis, dtype, casting, out; dtype and out are mutually exclusive |
|
|
99
100
|
| sum, mean | Preserve unit | axis, dtype, keepdims, out |
|
|
101
|
+
| prod | Product unit rules below | axis, dtype, keepdims, initial, where, out |
|
|
100
102
|
| median | Preserve unit | axis, keepdims, out; overwrite_input=False |
|
|
101
103
|
| std, var | Preserve unit / square unit | axis, dtype, ddof, keepdims, out |
|
|
102
104
|
| cumsum | Preserve unit | axis, dtype, out |
|
|
103
105
|
| argmin, argmax | Bare indices | axis, keepdims, out |
|
|
106
|
+
| trapezoid | Multiply ordinate and coordinate units | x, scalar dx, axis; no out |
|
|
104
107
|
| isclose, allclose | Bare mask / Python bool | rtol, atol, equal_nan |
|
|
105
108
|
|
|
106
109
|
Supply the first magnitude argument positionally; concatenate/stack take a
|
|
107
110
|
sequence. Options follow each supported signature, including positional out
|
|
108
111
|
checks. Function and reduction outputs follow the rules below;
|
|
109
|
-
overwrite_input=True is rejected.
|
|
110
|
-
|
|
112
|
+
overwrite_input=True is rejected. Reduction/statistical `where` and `initial`
|
|
113
|
+
are supported only for prod/multiply.reduce. Unsupported mean, correction,
|
|
114
|
+
and other keywords raise TypeError.
|
|
111
115
|
There is no `__setitem__`; `q += other` creates a result and rebinds q.
|
|
112
116
|
|
|
117
|
+
`np.trapezoid(y, x=x)` accepts a Quantity for y, x, or both and returns a
|
|
118
|
+
Quantity with unit `y.unit * x.unit`; bare inputs are dimensionless. With
|
|
119
|
+
`x=None`, scalar `dx` supplies the coordinate unit instead (default: dimensionless
|
|
120
|
+
1). NumPy dispatches this function on y and x, so at least one of those must be
|
|
121
|
+
a Quantity to enter unitx; a Quantity supplied only as dx is insufficient.
|
|
122
|
+
Explicit x makes dx irrelevant. Numeric arrays, scalar spacing, axis handling,
|
|
123
|
+
coordinate order (including reversed or nonmonotonic x), broadcasting, and
|
|
124
|
+
empty intervals follow [NumPy trapezoid](https://numpy.org/doc/stable/reference/generated/numpy.trapezoid.html).
|
|
125
|
+
No coordinate sorting, interpolation, dtype option, or output buffer is added.
|
|
126
|
+
|
|
127
|
+
## Product reductions
|
|
128
|
+
|
|
129
|
+
`np.prod(q)` reduces all axes by default; `np.multiply.reduce(q)` defaults to
|
|
130
|
+
axis 0, following NumPy. Both accept scalar, negative, or tuple axes, `axis=None`,
|
|
131
|
+
`axis=()`, and `keepdims`. For dimensional input with unit U, each result has
|
|
132
|
+
unit `U**N`, where N is the number of selected input elements in that slice.
|
|
133
|
+
Zero-valued elements still count. All output slices must have the same N;
|
|
134
|
+
otherwise unitx raises DimensionError because a single Quantity cannot hold
|
|
135
|
+
different dimensions. `where` accepts the same bare boolean masks as ufunc
|
|
136
|
+
calls and broadcasts to the input shape. It selects input elements; every
|
|
137
|
+
output position is written when an output buffer is supplied.
|
|
138
|
+
|
|
139
|
+
An empty or fully masked product has value 1 and unit `U**0`, before applying
|
|
140
|
+
`initial`. If the output itself has no elements, the unit uses the nominal
|
|
141
|
+
reduced-axis extent; scalar `where=False` sets that extent to zero.
|
|
142
|
+
Dimensionless inputs, including deg and km/m, first convert to coherent
|
|
143
|
+
dimensionless values and produce unit `Unit()`. Their masked slices may have
|
|
144
|
+
different counts because the result still has a single coherent unit.
|
|
145
|
+
|
|
146
|
+
`initial` is a bare numeric scalar or a scalar Quantity, including NumPy
|
|
147
|
+
scalars and zero-dimensional arrays. A bare initial is dimensionless; a
|
|
148
|
+
Quantity initial contributes its own unit once, so the result unit is
|
|
149
|
+
`U**N * initial.unit`. An explicit zero still contributes its declared unit.
|
|
150
|
+
The default is dimensionless 1. `initial=None` uses NumPy's first-element
|
|
151
|
+
semantics and fails for empty reductions. NumPy controls the accumulator dtype,
|
|
152
|
+
including casting the initial value to that dtype; initial does not select
|
|
153
|
+
a wider numerical domain. Decimal/Fraction initial values enter NumPy as floats.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
lengths = Quantity(np.array([[2.0, 3, 4], [5, 6, 7]]), 'm')
|
|
157
|
+
selected = np.array([True, False, True])
|
|
158
|
+
areas = np.prod(lengths, axis=1, where=selected)
|
|
159
|
+
assert areas.unit == Unit('m2')
|
|
160
|
+
np.testing.assert_array_equal(areas.value, [8, 35])
|
|
161
|
+
weighted = np.multiply.reduce(lengths, axis=1, where=selected, initial=Quantity(2, 's'))
|
|
162
|
+
assert weighted.unit == Unit('m2 s')
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Both entry points support the output rules below, including exact result
|
|
166
|
+
shape and unchanged targets on computation, unit, shape, or casting errors.
|
|
167
|
+
`cumprod` and `multiply.accumulate` remain unsupported: partial products
|
|
168
|
+
generally require different units at different positions. The supported
|
|
169
|
+
axis, empty-product, initial, and mask behavior follows
|
|
170
|
+
[NumPy prod](https://numpy.org/doc/stable/reference/generated/numpy.prod.html)
|
|
171
|
+
and [ufunc.reduce](https://numpy.org/doc/stable/reference/generated/numpy.ufunc.reduce.html),
|
|
172
|
+
with the additional single-unit and explicit-output rules described here.
|
|
173
|
+
|
|
113
174
|
## Output buffers
|
|
114
175
|
|
|
115
|
-
Supported single-output ufunc calls, add.reduce/accumulate, and the numeric
|
|
176
|
+
Supported single-output ufunc calls, add.reduce/accumulate, multiply.reduce, and the numeric
|
|
116
177
|
functions listed with `out` above accept a Quantity whose magnitude is a
|
|
117
178
|
writable plain numeric ndarray. `out=target`, `out=(target,)`, and positional
|
|
118
179
|
outputs for ufuncs return that same target; functions accept `out=target` or
|
|
@@ -157,7 +218,7 @@ The final copy is not a general transaction against concurrent changes or
|
|
|
157
218
|
external buffer failures. Temporaries cost memory proportional to the result
|
|
158
219
|
and target; `out` provides controlled writes, not a zero-allocation guarantee.
|
|
159
220
|
|
|
160
|
-
Functions and
|
|
221
|
+
Functions and supported reductions/accumulations use `same_kind` for the final cast and do
|
|
161
222
|
not accept a new `casting` keyword. Concatenate and stack use their existing
|
|
162
223
|
`casting` option for the final write as well as the kernel; their `dtype` and
|
|
163
224
|
`out` options cannot be combined. Index outputs also use `same_kind`, including
|
|
@@ -174,7 +235,7 @@ assert np.argmax(q, axis=0, out=indices) is indices
|
|
|
174
235
|
```
|
|
175
236
|
|
|
176
237
|
`out=None` and ufunc `out=(None,)` allocate ordinary results.
|
|
177
|
-
Multiple-output ufuncs and product
|
|
238
|
+
Multiple-output ufuncs and product accumulations remain unsupported.
|
|
178
239
|
Output writes preserve the magnitude's type and dtype, so the covariant
|
|
179
240
|
`Quantity[T]` contract remains valid; no setter or arbitrary replacement of T
|
|
180
241
|
is introduced. NumPy's own stubs do not accept Quantity outputs or infer wrapped
|
|
@@ -221,7 +282,9 @@ NumPy's many arithmetic overloads against Scalar's own return protocol.
|
|
|
221
282
|
`_numpy_typing.py` imports concrete NumPy types only for static analysis; core
|
|
222
283
|
runtime imports still do not load NumPy. Strict scalar-consumer checks also
|
|
223
284
|
run with NumPy absent. Checking the full library's optional NumPy annotations
|
|
224
|
-
requires NumPy; `
|
|
285
|
+
requires NumPy; `pyright tests/typing` checks scalar consumers without it.
|
|
286
|
+
All checks share `pyrightconfig.json`; the test runner selects the input
|
|
287
|
+
directories and skips optional cases when their dependencies are unavailable.
|
|
225
288
|
|
|
226
289
|
NumPy's left-operand and function stubs do not generally describe every
|
|
227
290
|
unit-aware dispatch. With NumPy 2.5 stubs and Pyright 1.1.414, the documented
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Physics notebooks
|
|
2
|
+
|
|
3
|
+
These notebooks use unitx for dimensional calculations and NumPy for numerical
|
|
4
|
+
work. Each contains a model, assumptions, plots, and numerical checks. The
|
|
5
|
+
material parameters and measurement draws are illustrative, not experimental data.
|
|
6
|
+
|
|
7
|
+
| Notebook | Topic | unitx features |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| [Blackbody radiation](blackbody.ipynb) | Planck spectra and Wien peak checks | Constants, broadcasting, spectral-density conversion |
|
|
10
|
+
| [Damped oscillator](damped_oscillator.ipynb) | Release from rest and energy loss | Dimensionless ufunc arguments, energy units, comparisons |
|
|
11
|
+
| [Gaussian wave packet](gaussian_wavepacket.ipynb) | Free electron propagation and uncertainty | Probability-density units, quantum constants, unit-aware integration |
|
|
12
|
+
| [Optical attenuation](optical_attenuation.ipynb) | Transmission through absorbing layers | prod/multiply.reduce, input masks, Quantity initial, fixed-unit out |
|
|
13
|
+
| [Pendulum uncertainty](pendulum_uncertainty.ipynb) | Correlated input uncertainties and Monte Carlo propagation | Mean/std/var units, mixed measurement units, explicit quantiles |
|
|
14
|
+
|
|
15
|
+
## Run
|
|
16
|
+
|
|
17
|
+
Use Python 3.12 or newer. From the repository root, install the current checkout
|
|
18
|
+
and notebook tools in the environment you will select as the Jupyter kernel:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
python -m pip install -e '.[numpy]' matplotlib jupyterlab
|
|
22
|
+
jupyter lab examples/
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Open a notebook and run all cells from the beginning. These notebooks require
|
|
26
|
+
unitx 0.8.4 or newer for product reductions and unit-aware integration.
|
|
27
|
+
The examples do not download datasets or write files. Keep physical quantities in calculations;
|
|
28
|
+
plot their `.value` and derive labels from their `.unit`. Convert only to choose
|
|
29
|
+
a useful display scale or to align units at a numerical boundary. Quantiles use
|
|
30
|
+
an explicit numerical boundary and reattach the input unit, without naming a
|
|
31
|
+
variable after its unit. Display equations use `$$...$$`.
|
|
32
|
+
|
|
33
|
+
## Keep Git history small
|
|
34
|
+
|
|
35
|
+
Commit only notebook source and minimal kernel metadata. Clear all outputs and
|
|
36
|
+
execution counts before committing; do not embed figures, widget state, or
|
|
37
|
+
attachments. Figures are regenerated when cells run. The test suite checks this
|
|
38
|
+
policy and executes the numerical examples when NumPy and Matplotlib are present.
|
|
39
|
+
|
|
40
|
+
To clear a notebook after running it, use JupyterLab's **Clear Outputs of All
|
|
41
|
+
Cells**, then save it. For a command-line cleanup using nbconvert:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
python -m pip install nbconvert
|
|
45
|
+
jupyter nbconvert --clear-output --inplace examples/*.ipynb
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The notebooks use ordinary Python code cells. Their code is also extracted for
|
|
49
|
+
the existing Pyright example check, without committing duplicate `.py` exports.
|
|
50
|
+
Assertions on NumPy results also check that dispatch retains the Quantity
|
|
51
|
+
wrapper. NumPy's stubs cannot describe every wrapped result; the optical
|
|
52
|
+
example documents local suppressions for its Quantity `initial` and `out` call.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"id": "blackbody-00",
|
|
6
|
+
"metadata": {},
|
|
7
|
+
"source": [
|
|
8
|
+
"# Blackbody spectral radiance\n",
|
|
9
|
+
"\n",
|
|
10
|
+
"Compare thermal spectra at three temperatures with wavelength on one array\n",
|
|
11
|
+
"axis and temperature on the other. This is the notebook version of the\n",
|
|
12
|
+
"README example. Run the setup in [examples/README.md](README.md) first.\n",
|
|
13
|
+
"\n",
|
|
14
|
+
"For an ideal blackbody, the spectral radiance per unit wavelength is\n",
|
|
15
|
+
"$$\n",
|
|
16
|
+
"B_\\lambda(T)=\\frac{2hc^2}{\\lambda^5}\n",
|
|
17
|
+
"\\left[\\exp\\!\\left(\\frac{hc}{\\lambda k_B T}\\right)-1\\right]^{-1}.\n",
|
|
18
|
+
"$$\n",
|
|
19
|
+
"Here $h$ is Planck's constant, $c$ is the speed of light, $k_B$ is Boltzmann's\n",
|
|
20
|
+
"constant, and $T$ is absolute temperature. The exponential argument is\n",
|
|
21
|
+
"dimensionless. Radiance is per unit solid angle; it is not the hemispheric\n",
|
|
22
|
+
"radiant exitance, which has an additional factor of $\\pi$ for this model.\n",
|
|
23
|
+
"\n",
|
|
24
|
+
"Reference: [CIE, Planck's law](https://cie.co.at/eilvterm/17-24-005)."
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"cell_type": "code",
|
|
29
|
+
"execution_count": null,
|
|
30
|
+
"id": "blackbody-01",
|
|
31
|
+
"metadata": {},
|
|
32
|
+
"outputs": [],
|
|
33
|
+
"source": [
|
|
34
|
+
"import numpy as np\n",
|
|
35
|
+
"from matplotlib import pyplot as plt\n",
|
|
36
|
+
"\n",
|
|
37
|
+
"from unitx.si import SI, si"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"cell_type": "markdown",
|
|
42
|
+
"id": "blackbody-02",
|
|
43
|
+
"metadata": {},
|
|
44
|
+
"source": [
|
|
45
|
+
"## Broadcast over wavelengths and temperatures\n",
|
|
46
|
+
"\n",
|
|
47
|
+
"The wavelength array has shape `(100, 1)` and the temperature array `(3,)`.\n",
|
|
48
|
+
"Each expression below produces a `(100, 3)` Quantity with one uniform unit.\n",
|
|
49
|
+
"`expm1` evaluates $\\exp(x)-1$ accurately near zero. The selected range also\n",
|
|
50
|
+
"keeps the short-wavelength exponential within floating-point range."
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"cell_type": "code",
|
|
55
|
+
"execution_count": null,
|
|
56
|
+
"id": "blackbody-03",
|
|
57
|
+
"metadata": {},
|
|
58
|
+
"outputs": [],
|
|
59
|
+
"source": [
|
|
60
|
+
"lam = np.linspace(0.01, 3, 100)[:, None] * si.um # wavelengths in microns\n",
|
|
61
|
+
"T = np.asarray([3000, 4000, 5000]) * si.K # temperatures in Kelvin\n",
|
|
62
|
+
"\n",
|
|
63
|
+
"nu = SI.c / lam\n",
|
|
64
|
+
"radiance = 2 * SI.h * SI.c**2 / (lam**5 * np.expm1(SI.h * nu / (SI.kB * T)))\n",
|
|
65
|
+
"radiance = radiance.to('kW/sr/m2/nm') # spectral radiance per unit wavelength\n",
|
|
66
|
+
"\n",
|
|
67
|
+
"print('Radiance shape:', radiance.shape)\n",
|
|
68
|
+
"print('Radiance unit:', radiance.unit)"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"cell_type": "markdown",
|
|
73
|
+
"id": "blackbody-04",
|
|
74
|
+
"metadata": {},
|
|
75
|
+
"source": [
|
|
76
|
+
"## Plot with explicit numerical exits\n",
|
|
77
|
+
"\n",
|
|
78
|
+
"Matplotlib receives values in the units written on the axes. The spectral\n",
|
|
79
|
+
"density conversion includes the change from per metre to per nanometre."
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"cell_type": "code",
|
|
84
|
+
"execution_count": null,
|
|
85
|
+
"id": "blackbody-05",
|
|
86
|
+
"metadata": {},
|
|
87
|
+
"outputs": [],
|
|
88
|
+
"source": [
|
|
89
|
+
"fig, ax = plt.subplots(figsize=(7, 4), layout='constrained')\n",
|
|
90
|
+
"ax.plot(lam.value, radiance.value)\n",
|
|
91
|
+
"ax.set(\n",
|
|
92
|
+
" xlabel=f'Wavelength [{lam.unit}]',\n",
|
|
93
|
+
" ylabel=f'Spectral radiance [{radiance.unit}]',\n",
|
|
94
|
+
" title='Blackbody radiation',\n",
|
|
95
|
+
")\n",
|
|
96
|
+
"ax.legend([f'T = {temperature}' for temperature in T])\n",
|
|
97
|
+
"ax.grid(True)\n",
|
|
98
|
+
"plt.show()"
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"cell_type": "markdown",
|
|
103
|
+
"id": "blackbody-06",
|
|
104
|
+
"metadata": {},
|
|
105
|
+
"source": [
|
|
106
|
+
"## Check the wavelength peaks\n",
|
|
107
|
+
"\n",
|
|
108
|
+
"Wien's displacement law gives $\\lambda_{\\max}T=b$, with `SI.b` provided by\n",
|
|
109
|
+
"unitx. A grid maximum has finite wavelength resolution; compare it to the\n",
|
|
110
|
+
"continuous prediction with an absolute tolerance tied to the grid spacing.\n",
|
|
111
|
+
"This is the peak of $B_\\lambda$, not the peak of $B_\\nu$ mapped to wavelength."
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"cell_type": "code",
|
|
116
|
+
"execution_count": null,
|
|
117
|
+
"id": "blackbody-07",
|
|
118
|
+
"metadata": {},
|
|
119
|
+
"outputs": [],
|
|
120
|
+
"source": [
|
|
121
|
+
"assert radiance.shape == (100, 3)\n",
|
|
122
|
+
"assert np.all(np.isfinite(radiance.value)) and np.all(radiance.value >= 0)\n",
|
|
123
|
+
"lambda_peak = lam[np.argmax(radiance.value, axis=0), 0]\n",
|
|
124
|
+
"lambda_formula = SI.b / T\n",
|
|
125
|
+
"assert np.all(lambda_peak.isclose(lambda_formula, rel_tol=0, abs_tol=0.02 * si.um))\n",
|
|
126
|
+
"print('Grid peaks:', lambda_peak)\n",
|
|
127
|
+
"print('Wien prediction:', lambda_formula.to(lambda_peak.unit))"
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
"metadata": {
|
|
132
|
+
"kernelspec": {
|
|
133
|
+
"display_name": "Python 3",
|
|
134
|
+
"language": "python",
|
|
135
|
+
"name": "python3"
|
|
136
|
+
},
|
|
137
|
+
"language_info": {
|
|
138
|
+
"name": "python"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"nbformat": 4,
|
|
142
|
+
"nbformat_minor": 5
|
|
143
|
+
}
|