unitx 0.8.2__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.
Files changed (73) hide show
  1. {unitx-0.8.2 → unitx-0.8.4}/CHANGELOG.md +37 -0
  2. {unitx-0.8.2 → unitx-0.8.4}/PKG-INFO +45 -22
  3. {unitx-0.8.2 → unitx-0.8.4}/README.md +43 -20
  4. {unitx-0.8.2 → unitx-0.8.4}/docs/numpy.md +105 -17
  5. unitx-0.8.4/examples/README.md +52 -0
  6. unitx-0.8.4/examples/blackbody.ipynb +143 -0
  7. unitx-0.8.4/examples/damped_oscillator.ipynb +153 -0
  8. unitx-0.8.4/examples/gaussian_wavepacket.ipynb +149 -0
  9. unitx-0.8.4/examples/optical_attenuation.ipynb +176 -0
  10. unitx-0.8.4/examples/pendulum_uncertainty.ipynb +188 -0
  11. {unitx-0.8.2 → unitx-0.8.4}/pyproject.toml +2 -2
  12. {unitx-0.8.2 → unitx-0.8.4}/pyrightconfig.json +3 -1
  13. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/_numpy.py +155 -41
  14. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/quantity.py +9 -22
  15. unitx-0.8.4/tests/_notebooks.py +11 -0
  16. {unitx-0.8.2 → unitx-0.8.4}/tests/numpy_typing/check_numpy.py +17 -0
  17. unitx-0.8.4/tests/test_examples.py +42 -0
  18. {unitx-0.8.2 → unitx-0.8.4}/tests/test_numpy.py +17 -13
  19. unitx-0.8.4/tests/test_numpy_integration.py +93 -0
  20. unitx-0.8.4/tests/test_numpy_outputs.py +229 -0
  21. unitx-0.8.4/tests/test_numpy_products.py +296 -0
  22. {unitx-0.8.2 → unitx-0.8.4}/tests/test_typing.py +17 -9
  23. unitx-0.8.2/examples/blackbody.py +0 -20
  24. unitx-0.8.2/pyrightconfig.asarray.json +0 -5
  25. unitx-0.8.2/pyrightconfig.examples.json +0 -5
  26. unitx-0.8.2/pyrightconfig.numpy.json +0 -5
  27. unitx-0.8.2/pyrightconfig.scalar.json +0 -5
  28. {unitx-0.8.2 → unitx-0.8.4}/.github/workflows/ci.yml +0 -0
  29. {unitx-0.8.2 → unitx-0.8.4}/.github/workflows/release.yml +0 -0
  30. {unitx-0.8.2 → unitx-0.8.4}/.gitignore +0 -0
  31. {unitx-0.8.2 → unitx-0.8.4}/LICENSE +0 -0
  32. {unitx-0.8.2 → unitx-0.8.4}/conftest.py +0 -0
  33. {unitx-0.8.2 → unitx-0.8.4}/docs/images/blackbody.png +0 -0
  34. {unitx-0.8.2 → unitx-0.8.4}/docs/migration.md +0 -0
  35. {unitx-0.8.2 → unitx-0.8.4}/docs/scalars.md +0 -0
  36. {unitx-0.8.2 → unitx-0.8.4}/docs/units.md +0 -0
  37. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/__init__.py +0 -0
  38. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/_constnamespace.py +0 -0
  39. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/_numerics.py +0 -0
  40. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/_numpy_typing.py +0 -0
  41. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/_unicode.py +0 -0
  42. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/dimension.py +0 -0
  43. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/exceptions.py +0 -0
  44. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/factor.py +0 -0
  45. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/monomial.py +0 -0
  46. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/py.typed +0 -0
  47. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/si/__init__.py +0 -0
  48. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/si/bases.py +0 -0
  49. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/si/constants.py +0 -0
  50. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/si/units.py +0 -0
  51. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/singleunit.py +0 -0
  52. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/unit.py +0 -0
  53. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/unitparser.py +0 -0
  54. {unitx-0.8.2 → unitx-0.8.4}/src/unitx/unitregistry.py +0 -0
  55. {unitx-0.8.2 → unitx-0.8.4}/tests/__init__.py +0 -0
  56. {unitx-0.8.2 → unitx-0.8.4}/tests/example_typing/check_blackbody.py +0 -0
  57. {unitx-0.8.2 → unitx-0.8.4}/tests/test_builtin_definitions.py +0 -0
  58. {unitx-0.8.2 → unitx-0.8.4}/tests/test_dimension.py +0 -0
  59. {unitx-0.8.2 → unitx-0.8.4}/tests/test_factor.py +0 -0
  60. {unitx-0.8.2 → unitx-0.8.4}/tests/test_import.py +0 -0
  61. {unitx-0.8.2 → unitx-0.8.4}/tests/test_monomial.py +0 -0
  62. {unitx-0.8.2 → unitx-0.8.4}/tests/test_numeric.py +0 -0
  63. {unitx-0.8.2 → unitx-0.8.4}/tests/test_quantity.py +0 -0
  64. {unitx-0.8.2 → unitx-0.8.4}/tests/test_si.py +0 -0
  65. {unitx-0.8.2 → unitx-0.8.4}/tests/test_singleunit.py +0 -0
  66. {unitx-0.8.2 → unitx-0.8.4}/tests/test_unicode.py +0 -0
  67. {unitx-0.8.2 → unitx-0.8.4}/tests/test_unit.py +0 -0
  68. {unitx-0.8.2 → unitx-0.8.4}/tests/test_unit_definition.py +0 -0
  69. {unitx-0.8.2 → unitx-0.8.4}/tests/test_unitparser.py +0 -0
  70. {unitx-0.8.2 → unitx-0.8.4}/tests/test_unitregistry.py +0 -0
  71. {unitx-0.8.2 → unitx-0.8.4}/tests/test_warnings.py +0 -0
  72. {unitx-0.8.2 → unitx-0.8.4}/tests/typing/check_quantity.py +0 -0
  73. {unitx-0.8.2 → unitx-0.8.4}/tests/typing/check_si.py +0 -0
@@ -1,5 +1,42 @@
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
+
28
+ ## [0.8.3] - 2026-10-20
29
+
30
+ ### Added
31
+
32
+ - Support unit-aware `out` for sum, mean, median, std, var, cumsum,
33
+ concatenate, stack, and add.reduce/accumulate. Require the exact result shape,
34
+ preserve target identity, unit, and dtype, and stage computation, conversion,
35
+ and casting before writing. Argmin/argmax accept bare integer ndarray outputs.
36
+ - Document function/reduction output casting and add regressions for scaled
37
+ units, positional outputs, overlapping/strided buffers, and unchanged outputs
38
+ on failure, plus installed-consumer type checks.
39
+
3
40
  ## [0.8.2] - 2026-10-20
4
41
 
5
42
  ### Fixed
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: unitx
3
- Version: 0.8.2
4
- Summary: A modern, intuitive Python library for units, quantities, and dimensional analysis.
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
  [![Python versions](https://img.shields.io/pypi/pyversions/unitx)](https://pypi.org/project/unitx/)
29
29
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
30
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.
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. The scalar library has no mandatory
40
- runtime dependencies:
43
+ Python **3.12 or later** is required. Install unitx with:
41
44
 
42
45
  ```sh
43
- python -m pip install unitx
46
+ pip install unitx
44
47
  ```
45
48
 
46
- For arrays, install NumPy support. Add Matplotlib to run the plotting example:
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
- python -m pip install 'unitx[numpy]' matplotlib
55
+ pip install 'unitx[numpy]'
50
56
  ```
51
57
 
52
- For development, install into the same interpreter that runs your code:
58
+ For development, install into your active Python environment:
53
59
 
54
60
  ```sh
55
- python -m pip install -e '.[test,numpy]'
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; `SI` provides physical constants. Both are read-only
64
- namespaces in `unitx.si`:
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 example: blackbody radiation
103
+ ## Units in NumPy: blackbody radiation
89
104
 
90
- Broadcast a column of wavelengths against three temperatures, then plot
91
- Planck's spectral radiance:
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 [runnable example](examples/blackbody.py) uses `np.asarray([...]) * unit`
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 optional plain numeric NumPy
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
- python -m pytest -q
254
- python -m ruff check .
255
- python -m ruff format --check .
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
  [![Python versions](https://img.shields.io/pypi/pyversions/unitx)](https://pypi.org/project/unitx/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
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.
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. The scalar library has no mandatory
16
- runtime dependencies:
19
+ Python **3.12 or later** is required. Install unitx with:
17
20
 
18
21
  ```sh
19
- python -m pip install unitx
22
+ pip install unitx
20
23
  ```
21
24
 
22
- For arrays, install NumPy support. Add Matplotlib to run the plotting example:
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
- python -m pip install 'unitx[numpy]' matplotlib
31
+ pip install 'unitx[numpy]'
26
32
  ```
27
33
 
28
- For development, install into the same interpreter that runs your code:
34
+ For development, install into your active Python environment:
29
35
 
30
36
  ```sh
31
- python -m pip install -e '.[test,numpy]'
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; `SI` provides physical constants. Both are read-only
40
- namespaces in `unitx.si`:
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 example: blackbody radiation
79
+ ## Units in NumPy: blackbody radiation
65
80
 
66
- Broadcast a column of wavelengths against three temperatures, then plot
67
- Planck's spectral radiance:
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 [runnable example](examples/blackbody.py) uses `np.asarray([...]) * unit`
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 optional plain numeric NumPy
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
- python -m pytest -q
230
- python -m ruff check .
231
- python -m ruff format --check .
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
 
@@ -79,14 +79,15 @@ methods, mixed array types, and keywords fail explicitly.
79
79
  | power, float_power / `**` | Dimensional bases require a finite real scalar exponent; dimensionless bases/exponents normalize their scales |
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
- | add.reduce, add.accumulate | Preserve unit; accept axis and dtype, plus keepdims for reduce |
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 reductions, at, outer, and reduceat are
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
@@ -95,25 +96,89 @@ functions, including exp/log. Python scalar numerical semantics remain native.
95
96
  | NumPy function | Unit/result rule | Supported options |
96
97
  | --- | --- | --- |
97
98
  | reshape, transpose | Preserve unit | shape/order/copy; axes |
98
- | concatenate, stack | Convert to first Quantity's unit; bare arrays remain dimensionless | axis, dtype, casting |
99
- | sum, mean | Preserve unit | axis, dtype, keepdims |
100
- | median | Preserve unit | axis, keepdims; overwrite_input=False |
101
- | std, var | Preserve unit / square unit | axis, dtype, ddof, keepdims |
102
- | cumsum | Preserve unit | axis, dtype |
103
- | argmin, argmax | Bare indices | axis, keepdims |
99
+ | concatenate, stack | Convert to first Quantity's unit; bare arrays remain dimensionless | axis, dtype, casting, out; dtype and out are mutually exclusive |
100
+ | sum, mean | Preserve unit | axis, dtype, keepdims, out |
101
+ | prod | Product unit rules below | axis, dtype, keepdims, initial, where, out |
102
+ | median | Preserve unit | axis, keepdims, out; overwrite_input=False |
103
+ | std, var | Preserve unit / square unit | axis, dtype, ddof, keepdims, out |
104
+ | cumsum | Preserve unit | axis, dtype, out |
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
- checks. Function and reduction `out` and overwrite_input=True are rejected. Unsupported
109
- initial, statistical where, mean, correction, and other keywords raise TypeError.
111
+ checks. Function and reduction outputs follow the rules below;
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.
110
115
  There is no `__setitem__`; `q += other` creates a result and rebinds q.
111
116
 
112
- ## Ufunc output buffers
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.
113
154
 
114
- Supported single-output ufunc calls accept a Quantity whose magnitude is a
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
+
174
+ ## Output buffers
175
+
176
+ Supported single-output ufunc calls, add.reduce/accumulate, multiply.reduce, and the numeric
177
+ functions listed with `out` above accept a Quantity whose magnitude is a
115
178
  writable plain numeric ndarray. `out=target`, `out=(target,)`, and positional
116
- outputs return that same target. Its magnitude object, dtype, and unit stay
179
+ outputs for ufuncs return that same target; functions accept `out=target` or
180
+ the positional output argument in their NumPy signature, without a tuple.
181
+ The target's magnitude object, dtype, and unit stay
117
182
  fixed. Zero-dimensional, empty, and noncontiguous output arrays are supported.
118
183
  NumPy scalars and Python scalars cannot serve as output buffers.
119
184
 
@@ -127,7 +192,10 @@ converts that result to the target unit and casts it into a staging buffer.
127
192
  The numerical kernel uses the input dtypes and any explicit `dtype`; the
128
193
  target dtype is used for the final cast. This is not a promise of NumPy's
129
194
  output-dependent kernel selection or allocation-free execution. Both the
130
- result and `where` mask must broadcast to the target shape. Only true mask
195
+ result and `where` mask must broadcast to the target shape for ufunc calls.
196
+ Function and reduction outputs must have exactly the ordinary result's shape,
197
+ including axes retained by `keepdims`; they do not broadcast into a larger
198
+ target. Only true mask
131
199
  positions are written; false positions retain their original target values.
132
200
  Unit conversion also skips false positions. Input/output overlap and shared
133
201
  slices are handled through temporaries; a mask sharing output storage is copied.
@@ -138,6 +206,8 @@ length target. `ScaledUnit` factors participate in the conversion. Every numeric
138
206
  result, including a coherent or scaled dimensionless result, requires a Quantity
139
207
  target; writing it to a bare ndarray would discard its unit. Boolean predicates
140
208
  and comparisons instead accept only a bare boolean ndarray output.
209
+ `argmin` and `argmax` accept only a bare integer ndarray output, with the
210
+ same exact shape requirement as other function outputs.
141
211
 
142
212
  Final casting uses `casting='same_kind'` by default, or the explicitly requested
143
213
  NumPy casting policy. It does not silently enable `unsafe`; nonintegral unit
@@ -148,8 +218,24 @@ The final copy is not a general transaction against concurrent changes or
148
218
  external buffer failures. Temporaries cost memory proportional to the result
149
219
  and target; `out` provides controlled writes, not a zero-allocation guarantee.
150
220
 
151
- `out=None` and `out=(None,)` allocate ordinary results. Multiple-output ufuncs,
152
- ufunc reductions with an output, and function outputs remain unsupported.
221
+ Functions and supported reductions/accumulations use `same_kind` for the final cast and do
222
+ not accept a new `casting` keyword. Concatenate and stack use their existing
223
+ `casting` option for the final write as well as the kernel; their `dtype` and
224
+ `out` options cannot be combined. Index outputs also use `same_kind`, including
225
+ its allowance for integer narrowing. These policies intentionally do not
226
+ reproduce every function's native output casting rule. An explicit `dtype`
227
+ controls the temporary numerical computation for supported reductions; the
228
+ target dtype does not select the accumulator or kernel dtype.
229
+
230
+ ```python
231
+ totals = Quantity(np.empty(3), 'cm')
232
+ assert np.sum(q, axis=0, out=totals) is totals
233
+ indices = np.empty(3, dtype=np.intp)
234
+ assert np.argmax(q, axis=0, out=indices) is indices
235
+ ```
236
+
237
+ `out=None` and ufunc `out=(None,)` allocate ordinary results.
238
+ Multiple-output ufuncs and product accumulations remain unsupported.
153
239
  Output writes preserve the magnitude's type and dtype, so the covariant
154
240
  `Quantity[T]` contract remains valid; no setter or arbitrary replacement of T
155
241
  is introduced. NumPy's own stubs do not accept Quantity outputs or infer wrapped
@@ -196,7 +282,9 @@ NumPy's many arithmetic overloads against Scalar's own return protocol.
196
282
  `_numpy_typing.py` imports concrete NumPy types only for static analysis; core
197
283
  runtime imports still do not load NumPy. Strict scalar-consumer checks also
198
284
  run with NumPy absent. Checking the full library's optional NumPy annotations
199
- requires NumPy; `pyrightconfig.scalar.json` checks scalar consumers without it.
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.
200
288
 
201
289
  NumPy's left-operand and function stubs do not generally describe every
202
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.