unitx 0.13.0__tar.gz → 0.13.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.
Files changed (104) hide show
  1. {unitx-0.13.0 → unitx-0.13.2}/CHANGELOG.md +36 -0
  2. unitx-0.13.0/README.md → unitx-0.13.2/PKG-INFO +90 -6
  3. unitx-0.13.0/PKG-INFO → unitx-0.13.2/README.md +62 -30
  4. {unitx-0.13.0 → unitx-0.13.2}/docs/catalogue.md +53 -5
  5. {unitx-0.13.0 → unitx-0.13.2}/docs/custom-units.md +12 -1
  6. unitx-0.13.2/docs/images/blackbody.svg +1003 -0
  7. {unitx-0.13.0 → unitx-0.13.2}/docs/migration.md +15 -0
  8. {unitx-0.13.0 → unitx-0.13.2}/docs/stdlib.md +66 -9
  9. {unitx-0.13.0 → unitx-0.13.2}/docs/typing.md +3 -1
  10. {unitx-0.13.0 → unitx-0.13.2}/docs/units.md +6 -0
  11. {unitx-0.13.0 → unitx-0.13.2}/pyproject.toml +23 -2
  12. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/quantity.py +0 -4
  13. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/si/__init__.py +2 -2
  14. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/si/constants.py +113 -1
  15. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/singleunit.py +24 -4
  16. unitx-0.13.2/src/unitx/time.py +148 -0
  17. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/unitparser.py +115 -76
  18. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/unitregistry.py +8 -3
  19. {unitx-0.13.0 → unitx-0.13.2}/tests/test_si.py +62 -3
  20. {unitx-0.13.0 → unitx-0.13.2}/tests/test_singleunit.py +35 -0
  21. unitx-0.13.2/tests/test_time.py +259 -0
  22. {unitx-0.13.0 → unitx-0.13.2}/tests/test_unitparser.py +4 -3
  23. {unitx-0.13.0 → unitx-0.13.2}/tests/typing/check_math_time.py +10 -7
  24. {unitx-0.13.0 → unitx-0.13.2}/tests/typing/check_si.py +14 -1
  25. unitx-0.13.0/docs/images/blackbody.png +0 -0
  26. unitx-0.13.0/src/unitx/time.py +0 -104
  27. unitx-0.13.0/tests/test_time.py +0 -100
  28. {unitx-0.13.0 → unitx-0.13.2}/.github/workflows/ci.yml +0 -0
  29. {unitx-0.13.0 → unitx-0.13.2}/.github/workflows/release.yml +0 -0
  30. {unitx-0.13.0 → unitx-0.13.2}/.gitignore +0 -0
  31. {unitx-0.13.0 → unitx-0.13.2}/CONTRIBUTING.md +0 -0
  32. {unitx-0.13.0 → unitx-0.13.2}/LICENSE +0 -0
  33. {unitx-0.13.0 → unitx-0.13.2}/benchmarks/numpy_writes.py +0 -0
  34. {unitx-0.13.0 → unitx-0.13.2}/conftest.py +0 -0
  35. {unitx-0.13.0 → unitx-0.13.2}/docs/README.md +0 -0
  36. {unitx-0.13.0 → unitx-0.13.2}/docs/affine.md +0 -0
  37. {unitx-0.13.0 → unitx-0.13.2}/docs/display.md +0 -0
  38. {unitx-0.13.0 → unitx-0.13.2}/docs/numpy.md +0 -0
  39. {unitx-0.13.0 → unitx-0.13.2}/docs/scalars.md +0 -0
  40. {unitx-0.13.0 → unitx-0.13.2}/examples/README.md +0 -0
  41. {unitx-0.13.0 → unitx-0.13.2}/examples/blackbody.ipynb +0 -0
  42. {unitx-0.13.0 → unitx-0.13.2}/examples/damped_oscillator.ipynb +0 -0
  43. {unitx-0.13.0 → unitx-0.13.2}/examples/gaussian_wavepacket.ipynb +0 -0
  44. {unitx-0.13.0 → unitx-0.13.2}/examples/optical_attenuation.ipynb +0 -0
  45. {unitx-0.13.0 → unitx-0.13.2}/examples/pendulum_uncertainty.ipynb +0 -0
  46. {unitx-0.13.0 → unitx-0.13.2}/pyrightconfig.json +0 -0
  47. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/__init__.py +0 -0
  48. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/_constnamespace.py +0 -0
  49. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/_conversion.py +0 -0
  50. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/_display.py +0 -0
  51. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/_immutable.py +0 -0
  52. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/_numerics.py +0 -0
  53. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/_numpy.py +0 -0
  54. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/_numpy_typing.py +0 -0
  55. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/_unicode.py +0 -0
  56. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/affinequantity.py +0 -0
  57. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/affineunit.py +0 -0
  58. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/dimension.py +0 -0
  59. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/exceptions.py +0 -0
  60. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/factor.py +0 -0
  61. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/math.py +0 -0
  62. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/monomial.py +0 -0
  63. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/py.typed +0 -0
  64. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/si/bases.py +0 -0
  65. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/si/units.py +0 -0
  66. {unitx-0.13.0 → unitx-0.13.2}/src/unitx/unit.py +0 -0
  67. {unitx-0.13.0 → unitx-0.13.2}/tests/__init__.py +0 -0
  68. {unitx-0.13.0 → unitx-0.13.2}/tests/_notebooks.py +0 -0
  69. {unitx-0.13.0 → unitx-0.13.2}/tests/example_typing/check_blackbody.py +0 -0
  70. {unitx-0.13.0 → unitx-0.13.2}/tests/numpy_typing/check_math.py +0 -0
  71. {unitx-0.13.0 → unitx-0.13.2}/tests/numpy_typing/check_numpy.py +0 -0
  72. {unitx-0.13.0 → unitx-0.13.2}/tests/test_affine.py +0 -0
  73. {unitx-0.13.0 → unitx-0.13.2}/tests/test_affinequantity.py +0 -0
  74. {unitx-0.13.0 → unitx-0.13.2}/tests/test_builtin_definitions.py +0 -0
  75. {unitx-0.13.0 → unitx-0.13.2}/tests/test_dimension.py +0 -0
  76. {unitx-0.13.0 → unitx-0.13.2}/tests/test_display.py +0 -0
  77. {unitx-0.13.0 → unitx-0.13.2}/tests/test_examples.py +0 -0
  78. {unitx-0.13.0 → unitx-0.13.2}/tests/test_factor.py +0 -0
  79. {unitx-0.13.0 → unitx-0.13.2}/tests/test_immutable.py +0 -0
  80. {unitx-0.13.0 → unitx-0.13.2}/tests/test_import.py +0 -0
  81. {unitx-0.13.0 → unitx-0.13.2}/tests/test_math.py +0 -0
  82. {unitx-0.13.0 → unitx-0.13.2}/tests/test_math_numpy.py +0 -0
  83. {unitx-0.13.0 → unitx-0.13.2}/tests/test_monomial.py +0 -0
  84. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numeric.py +0 -0
  85. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy.py +0 -0
  86. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy_affine.py +0 -0
  87. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy_differentiation.py +0 -0
  88. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy_integration.py +0 -0
  89. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy_multi_outputs.py +0 -0
  90. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy_mutation.py +0 -0
  91. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy_outputs.py +0 -0
  92. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy_products.py +0 -0
  93. {unitx-0.13.0 → unitx-0.13.2}/tests/test_numpy_statistics.py +0 -0
  94. {unitx-0.13.0 → unitx-0.13.2}/tests/test_quantity.py +0 -0
  95. {unitx-0.13.0 → unitx-0.13.2}/tests/test_siunitx_display.py +0 -0
  96. {unitx-0.13.0 → unitx-0.13.2}/tests/test_typing.py +0 -0
  97. {unitx-0.13.0 → unitx-0.13.2}/tests/test_unicode.py +0 -0
  98. {unitx-0.13.0 → unitx-0.13.2}/tests/test_unicode_units.py +0 -0
  99. {unitx-0.13.0 → unitx-0.13.2}/tests/test_unit.py +0 -0
  100. {unitx-0.13.0 → unitx-0.13.2}/tests/test_unit_definition.py +0 -0
  101. {unitx-0.13.0 → unitx-0.13.2}/tests/test_unitregistry.py +0 -0
  102. {unitx-0.13.0 → unitx-0.13.2}/tests/test_warnings.py +0 -0
  103. {unitx-0.13.0 → unitx-0.13.2}/tests/typing/check_affine.py +0 -0
  104. {unitx-0.13.0 → unitx-0.13.2}/tests/typing/check_quantity.py +0 -0
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## [0.13.2] - 2026-10-22
6
+
7
+ ### Added
8
+
9
+ - `unitx.time.from_datetime()` and `to_datetime()` explicitly convert aware
10
+ datetimes and Unix-referenced affine time points, including custom known
11
+ epoch coordinates. Import preserves integer ns; export rounds once to
12
+ microseconds with ties to even and defaults to UTC.
13
+ - `SingleUnit.from_name(name)` constructs a single unit from a registered name
14
+ or name alias, including permitted prefixes, without accepting symbol spellings.
15
+
16
+ ### Changed
17
+
18
+ - Clock functions in `unitx.time` return `AffineQuantity[int]` points in ns.
19
+ Point subtraction returns a duration Quantity; point addition and
20
+ multiplicative point arithmetic are rejected. Duration adapters are unchanged.
21
+ Clock domains remain untagged; only compare/subtract readings from the same context.
22
+ - Clarify process-wide registration and unsupported concurrent registry access.
23
+ - Expand the README with a runnable blackbody example and a theme-aware SVG plot.
24
+
25
+ ### Internal
26
+
27
+ - Route name-only parsing through `SingleUnit.from_name()`, clarify recursive-descent
28
+ grammar, and remove a duplicate NumPy comparison branch.
29
+
30
+ ## [0.13.1] - 2026-10-22
31
+
32
+ ### Added
33
+
34
+ - Public `unitx.si.au`, `nu` and `Planck` read-only namespaces of SI-valued
35
+ atomic, electron-mass natural, and Planck scales. These are physical
36
+ Quantities, not parser registrations or implicit conversion contexts.
37
+ - Export the existing `Particle` namespace directly from `unitx.si`.
38
+
3
39
  ## [0.13.0] - 2026-10-21
4
40
 
5
41
  ### Added
@@ -1,3 +1,31 @@
1
+ Metadata-Version: 2.5
2
+ Name: unitx
3
+ Version: 0.13.2
4
+ Summary: Physical units, quantities, and unit conversion with exact unit algebra, type hints, and NumPy integration.
5
+ Project-URL: Homepage, https://github.com/DaitTHU/unitx
6
+ Project-URL: Documentation, https://github.com/DaitTHU/unitx/blob/main/docs/README.md
7
+ Project-URL: Source, https://github.com/DaitTHU/unitx
8
+ Project-URL: Changelog, https://github.com/DaitTHU/unitx/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/DaitTHU/unitx/issues
10
+ Author: Dait
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: SI units,dimensional analysis,engineering,exact arithmetic,logarithmic units,numpy,physical constants,physical quantities,physics,quantity,scientific computing,temperature conversion,unit,unit algebra,unit conversion,units
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: 3.15
19
+ Classifier: Topic :: Scientific/Engineering :: Physics
20
+ Requires-Python: >=3.12
21
+ Provides-Extra: numpy
22
+ Requires-Dist: numpy>=2.1; extra == 'numpy'
23
+ Provides-Extra: test
24
+ Requires-Dist: pyright>=1.1.414; extra == 'test'
25
+ Requires-Dist: pytest>=8; extra == 'test'
26
+ Requires-Dist: ruff>=0.8; extra == 'test'
27
+ Description-Content-Type: text/markdown
28
+
1
29
  # unitx
2
30
 
3
31
  [![PyPI version](https://img.shields.io/pypi/v/unitx)](https://pypi.org/project/unitx/)
@@ -74,7 +102,12 @@ Use `.isclose()` for approximate comparisons.
74
102
  Use `@` for an absolute coordinate reading and `*` for a linear difference:
75
103
 
76
104
  ```pycon
77
- >>> room = 20 @ si.degC
105
+ >>> difference = 20 * si.degC # A temperature difference of 20 degrees
106
+ >>> room = 20 @ si.degC # A reading of 20 degrees Celsius
107
+ >>> difference.value_in(si.K)
108
+ 20
109
+ >>> room.value_in(si.K)
110
+ Fraction(5863, 20)
78
111
  >>> print(room.to(si.degF))
79
112
  68 °F
80
113
  >>> print(room + 5 * si.K)
@@ -87,6 +120,9 @@ True
87
120
  ```
88
121
 
89
122
  Points are `AffineQuantity` objects; differences are `Quantity` objects.
123
+ Multiplying by `si.degC` constructs a difference; use `@` or the explicit
124
+ `AffineQuantity(20, si.degC)` constructor for an absolute reading.
125
+ Adding two points raises `TypeError`; subtract them to obtain a difference.
90
126
  Use `.to_quantity()` to turn a point into an ordinary physical quantity for
91
127
  further arithmetic. Read the [point and difference guide](https://github.com/DaitTHU/unitx/blob/main/docs/affine.md)
92
128
  for temperature, dBm/dBW/dBV, and logarithmic ratio rules.
@@ -109,8 +145,13 @@ True
109
145
  ```
110
146
 
111
147
  `unitx.math` checks units for roots, trigonometry, exponentials, logarithms,
112
- `hypot`, and `atan2`. `unitx.time` returns integer nanosecond clock readings
113
- and adapts durations to `sleep` and `datetime.timedelta`.
148
+ `hypot`, and `atan2`. `unitx.time` returns `AffineQuantity[int]` clock readings
149
+ in nanoseconds; subtract readings from the same clock to obtain a Quantity
150
+ duration. It also adapts durations to `sleep` and `datetime.timedelta`, and
151
+ converts aware `datetime` objects to and from Unix time points with
152
+ `time.from_datetime()` / `time.to_datetime()`.
153
+ Readings do not carry clock, process, or thread identities; mixing clock
154
+ contexts is not detected.
114
155
  See [standard-library enhancements](https://github.com/DaitTHU/unitx/blob/main/docs/stdlib.md)
115
156
  for the supported functions, numerical backends, and clock semantics.
116
157
 
@@ -136,6 +177,19 @@ units. Comparisons return boolean masks. Supported reductions, integration,
136
177
  gradients, and output buffers follow the [NumPy support matrix](https://github.com/DaitTHU/unitx/blob/main/docs/numpy.md).
137
178
  Array assignment and augmented arithmetic preserve the destination unit and dtype.
138
179
 
180
+ Exact unit algebra preserves the scale `1/100` for centimetres to metres.
181
+ Applying that scale to a numerical array follows NumPy's dtype, rounding, and
182
+ overflow rules; it does not turn the array into exact rational numbers:
183
+
184
+ ```python
185
+ from fractions import Fraction
186
+
187
+ exact = Quantity(1, 'cm').value_in('m')
188
+ approximate = Quantity(np.array([1.0]), 'cm').value_in('m')
189
+ assert exact == Fraction(1, 100)
190
+ assert Fraction(float(approximate[0])) != exact # Binary float approximates 1/100
191
+ ```
192
+
139
193
  Use `.value` or `.value_in(unit)` at plotting and external-library boundaries.
140
194
  `np.asarray(quantity)` and `np.array(quantity)` raise TypeError to prevent
141
195
  implicit unit removal. NumPy's type stubs do not describe every wrapped
@@ -143,10 +197,40 @@ operation; see the [typing guide](https://github.com/DaitTHU/unitx/blob/main/doc
143
197
 
144
198
  The [physics notebooks](https://github.com/DaitTHU/unitx/blob/main/examples/README.md)
145
199
  cover blackbody radiation, damped motion, wave-packet spreading, attenuation,
146
- and measurement uncertainty. For example, Planck spectra combine physical
147
- constants, broadcasting, and spectral-density conversion:
200
+ and measurement uncertainty.
201
+
202
+ ## Blackbody radiation
203
+
204
+ Install plotting support with `pip install 'unitx[numpy]' matplotlib`, then run
205
+ this complete example. Planck's formula keeps units and constants throughout;
206
+ NumPy broadcasts the wavelength column against three temperatures. Convert the
207
+ spectral radiance to a readable display unit before plotting:
208
+
209
+ ```python
210
+ import numpy as np
211
+ from matplotlib import pyplot as plt
212
+
213
+ from unitx.si import SI, si
214
+
215
+ wavelength = np.linspace(0.01, 3, 100)[:, None] * si.um
216
+ temperature = np.array([3000, 4000, 5000]) * si.K
217
+ exponent = SI.h * SI.c / (wavelength * SI.kB * temperature)
218
+ radiance = 2 * SI.h * SI.c**2 / (wavelength**5 * np.expm1(exponent))
219
+ radiance = radiance.to('kW/sr/m2/nm')
220
+
221
+ fig, ax = plt.subplots(figsize=(7, 4), layout='constrained')
222
+ ax.plot(wavelength.value, radiance.value)
223
+ ax.set(
224
+ xlabel=f'Wavelength [{wavelength.unit}]',
225
+ ylabel=f'Spectral radiance [{radiance.unit}]',
226
+ title='Blackbody radiation',
227
+ )
228
+ ax.legend([f'T = {value}' for value in temperature], framealpha=1)
229
+ ax.grid(True, alpha=0.25)
230
+ plt.show()
231
+ ```
148
232
 
149
- ![Blackbody spectral radiance](https://raw.githubusercontent.com/DaitTHU/unitx/main/docs/images/blackbody.png)
233
+ ![Blackbody spectral radiance at 3000, 4000 and 5000 K](https://raw.githubusercontent.com/DaitTHU/unitx/main/docs/images/blackbody.svg)
150
234
 
151
235
  ## Read more
152
236
 
@@ -1,27 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: unitx
3
- Version: 0.13.0
4
- Summary: A modern, intuitive unit library, with exact unit algebra, expressive type hints, and NumPy integration.
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
1
  # unitx
26
2
 
27
3
  [![PyPI version](https://img.shields.io/pypi/v/unitx)](https://pypi.org/project/unitx/)
@@ -98,7 +74,12 @@ Use `.isclose()` for approximate comparisons.
98
74
  Use `@` for an absolute coordinate reading and `*` for a linear difference:
99
75
 
100
76
  ```pycon
101
- >>> room = 20 @ si.degC
77
+ >>> difference = 20 * si.degC # A temperature difference of 20 degrees
78
+ >>> room = 20 @ si.degC # A reading of 20 degrees Celsius
79
+ >>> difference.value_in(si.K)
80
+ 20
81
+ >>> room.value_in(si.K)
82
+ Fraction(5863, 20)
102
83
  >>> print(room.to(si.degF))
103
84
  68 °F
104
85
  >>> print(room + 5 * si.K)
@@ -111,6 +92,9 @@ True
111
92
  ```
112
93
 
113
94
  Points are `AffineQuantity` objects; differences are `Quantity` objects.
95
+ Multiplying by `si.degC` constructs a difference; use `@` or the explicit
96
+ `AffineQuantity(20, si.degC)` constructor for an absolute reading.
97
+ Adding two points raises `TypeError`; subtract them to obtain a difference.
114
98
  Use `.to_quantity()` to turn a point into an ordinary physical quantity for
115
99
  further arithmetic. Read the [point and difference guide](https://github.com/DaitTHU/unitx/blob/main/docs/affine.md)
116
100
  for temperature, dBm/dBW/dBV, and logarithmic ratio rules.
@@ -133,8 +117,13 @@ True
133
117
  ```
134
118
 
135
119
  `unitx.math` checks units for roots, trigonometry, exponentials, logarithms,
136
- `hypot`, and `atan2`. `unitx.time` returns integer nanosecond clock readings
137
- and adapts durations to `sleep` and `datetime.timedelta`.
120
+ `hypot`, and `atan2`. `unitx.time` returns `AffineQuantity[int]` clock readings
121
+ in nanoseconds; subtract readings from the same clock to obtain a Quantity
122
+ duration. It also adapts durations to `sleep` and `datetime.timedelta`, and
123
+ converts aware `datetime` objects to and from Unix time points with
124
+ `time.from_datetime()` / `time.to_datetime()`.
125
+ Readings do not carry clock, process, or thread identities; mixing clock
126
+ contexts is not detected.
138
127
  See [standard-library enhancements](https://github.com/DaitTHU/unitx/blob/main/docs/stdlib.md)
139
128
  for the supported functions, numerical backends, and clock semantics.
140
129
 
@@ -160,6 +149,19 @@ units. Comparisons return boolean masks. Supported reductions, integration,
160
149
  gradients, and output buffers follow the [NumPy support matrix](https://github.com/DaitTHU/unitx/blob/main/docs/numpy.md).
161
150
  Array assignment and augmented arithmetic preserve the destination unit and dtype.
162
151
 
152
+ Exact unit algebra preserves the scale `1/100` for centimetres to metres.
153
+ Applying that scale to a numerical array follows NumPy's dtype, rounding, and
154
+ overflow rules; it does not turn the array into exact rational numbers:
155
+
156
+ ```python
157
+ from fractions import Fraction
158
+
159
+ exact = Quantity(1, 'cm').value_in('m')
160
+ approximate = Quantity(np.array([1.0]), 'cm').value_in('m')
161
+ assert exact == Fraction(1, 100)
162
+ assert Fraction(float(approximate[0])) != exact # Binary float approximates 1/100
163
+ ```
164
+
163
165
  Use `.value` or `.value_in(unit)` at plotting and external-library boundaries.
164
166
  `np.asarray(quantity)` and `np.array(quantity)` raise TypeError to prevent
165
167
  implicit unit removal. NumPy's type stubs do not describe every wrapped
@@ -167,10 +169,40 @@ operation; see the [typing guide](https://github.com/DaitTHU/unitx/blob/main/doc
167
169
 
168
170
  The [physics notebooks](https://github.com/DaitTHU/unitx/blob/main/examples/README.md)
169
171
  cover blackbody radiation, damped motion, wave-packet spreading, attenuation,
170
- and measurement uncertainty. For example, Planck spectra combine physical
171
- constants, broadcasting, and spectral-density conversion:
172
+ and measurement uncertainty.
173
+
174
+ ## Blackbody radiation
175
+
176
+ Install plotting support with `pip install 'unitx[numpy]' matplotlib`, then run
177
+ this complete example. Planck's formula keeps units and constants throughout;
178
+ NumPy broadcasts the wavelength column against three temperatures. Convert the
179
+ spectral radiance to a readable display unit before plotting:
180
+
181
+ ```python
182
+ import numpy as np
183
+ from matplotlib import pyplot as plt
184
+
185
+ from unitx.si import SI, si
186
+
187
+ wavelength = np.linspace(0.01, 3, 100)[:, None] * si.um
188
+ temperature = np.array([3000, 4000, 5000]) * si.K
189
+ exponent = SI.h * SI.c / (wavelength * SI.kB * temperature)
190
+ radiance = 2 * SI.h * SI.c**2 / (wavelength**5 * np.expm1(exponent))
191
+ radiance = radiance.to('kW/sr/m2/nm')
192
+
193
+ fig, ax = plt.subplots(figsize=(7, 4), layout='constrained')
194
+ ax.plot(wavelength.value, radiance.value)
195
+ ax.set(
196
+ xlabel=f'Wavelength [{wavelength.unit}]',
197
+ ylabel=f'Spectral radiance [{radiance.unit}]',
198
+ title='Blackbody radiation',
199
+ )
200
+ ax.legend([f'T = {value}' for value in temperature], framealpha=1)
201
+ ax.grid(True, alpha=0.25)
202
+ plt.show()
203
+ ```
172
204
 
173
- ![Blackbody spectral radiance](https://raw.githubusercontent.com/DaitTHU/unitx/main/docs/images/blackbody.png)
205
+ ![Blackbody spectral radiance at 3000, 4000 and 5000 K](https://raw.githubusercontent.com/DaitTHU/unitx/main/docs/images/blackbody.svg)
174
206
 
175
207
  ## Read more
176
208
 
@@ -10,9 +10,11 @@ True
10
10
 
11
11
  ```
12
12
 
13
- `unitx.si` is a module containing two read-only namespaces: lowercase `si`
14
- contains ready-to-use units and coordinate descriptors; uppercase `SI` contains
15
- physical-constant Quantities. Importing unitx initializes this catalogue.
13
+ `unitx.si` provides read-only namespaces: lowercase `si` contains ready-to-use
14
+ units and coordinate descriptors; uppercase `SI` contains physical-constant
15
+ Quantities. `Particle` contains particle data, while `au`, `nu` and `Planck`
16
+ contain physical unit scales expressed as SI Quantities.
17
+ Importing unitx initializes this catalogue.
16
18
 
17
19
  ## Available groups
18
20
 
@@ -43,8 +45,8 @@ For the complete definitions see [unit source](../src/unitx/si/units.py) and
43
45
 
44
46
  `SI` includes the seven SI defining constants, plus constants such as hbar,
45
47
  G, mu0, epsilon0, me, mp, mn, alpha and the Wien displacement constants.
46
- Particle data are separately available from
47
- `from unitx.si.constants import Particle`.
48
+ Particle data are available from `from unitx.si import Particle`; the existing
49
+ `unitx.si.constants.Particle` import remains supported.
48
50
 
49
51
  Defining decimal values are stored using Decimal/int magnitudes. Measured
50
52
  constants carry the digits in the catalogue; Quantity does not attach an
@@ -59,6 +61,52 @@ from predefined `SI.hbar`, whose numerical magnitude is expressed in ordinary
59
61
  J s units. See [symbolic factors](scalars.md#symbolic-factors-and-pi) and
60
62
  [exact extra scales](custom-units.md#exact-extra-scales).
61
63
 
64
+ ## Atomic, natural and Planck scales
65
+
66
+ ```pycon
67
+ >>> from unitx.si import Particle, Planck, au, nu
68
+ >>> Particle.me is SI.me
69
+ True
70
+ >>> au.length is SI.a0
71
+ True
72
+ >>> # A physical length equal to three Bohr radii:
73
+ >>> length = 3 * au.length
74
+ >>> float((length / au.length).value_in(''))
75
+ 3.0
76
+ >>> nu.velocity is SI.c
77
+ True
78
+ >>> Planck.length.unit == si.m
79
+ True
80
+
81
+ ```
82
+
83
+ | Namespace | Convention | Representative scales |
84
+ | --- | --- | --- |
85
+ | `au` | Hartree atomic units: ħ = e = mₑ = 4πε₀ = 1 | `length` (Bohr radius), `energy` (Hartree), `time`, `charge` |
86
+ | `nu` | Electron-mass natural units: c = ħ = mₑ = ε₀ = 1 | `length` (reduced electron Compton wavelength), `energy`, `charge = sqrt(ε₀ ħ c)` |
87
+ | `Planck` | c = ħ = G = kB = 1 | `mass`, `length`, `time`, `energy`, `temperature` |
88
+
89
+ All three expose `velocity`, `action`, `mass`, `energy`, `momentum`, `length`
90
+ and `time`. `au` and `nu` also expose `permittivity`, `force`, `charge`,
91
+ `current`, `charge_density`, `electric_potential`, `electric_field`,
92
+ `electric_dipole_moment`, `electric_quadrupole_moment`,
93
+ `electric_polarizability`, `magnetic_flux_density`, `magnetic_dipole_moment`
94
+ and `magnetizability`. `au` and `Planck` expose `temperature`.
95
+
96
+ The atomic magnetic dipole scale is **twice** the Bohr magneton. In `nu`,
97
+ the elementary charge has numerical value sqrt(4πα); other natural-unit
98
+ conventions need not use the electron mass or this charge normalization.
99
+ `Planck` uses ħ rather than h and does not select a charge convention.
100
+
101
+ These attributes are **Quantities**, not Unit objects accepted as `.to()`
102
+ targets. Divide by a scale and use `.value_in('')` to obtain a dimensionless
103
+ number, or multiply a number by a scale to construct a physical quantity.
104
+ The namespaces do not activate cross-dimension conversion or register new
105
+ parser spellings. In particular, `si.au` and `Unit('au')` still mean the
106
+ astronomical unit; `au.length` is the atomic length scale.
107
+ All derived scales inherit the catalogue's finite numerical precision and
108
+ lack of uncertainty propagation.
109
+
62
110
  ## Spelling and scale conventions
63
111
 
64
112
  Symbols and names are case-sensitive. Lowercase `k` is kilo and uppercase
@@ -131,7 +131,18 @@ The registry and spelling rules live in `unitx.unitregistry`; parsing expression
131
131
  does not change unit definitions. A parser instance caches only a successful
132
132
  result; failed parses may be retried after a definition is added. Registration
133
133
  preserves existing valid spellings and validates before updating its indexes.
134
- Concurrent registry writes and isolated registry contexts are not supported.
134
+ The registry is shared by all users of unitx in the current process; a Python
135
+ attribute namespace does not create an isolated registry. Conflicting names or
136
+ symbols raise `UnitRegistryError` rather than replace an existing definition.
137
+
138
+ Registration is not thread-safe: validation and updates to the spelling indexes
139
+ are not protected by a shared lock. Neither concurrent registration nor parsing
140
+ while another thread registers a unit is supported. Register custom units during
141
+ single-threaded initialization, before starting worker threads. Applications
142
+ that register later must synchronize both registration and all registry reads.
143
+ Validation failure leaves the indexes unchanged; this guarantee does not imply
144
+ an atomic transaction for concurrent readers. Independent registry contexts
145
+ are not provided.
135
146
 
136
147
  Quantity definitions require positive finite `int`, `Fraction`, `Decimal`, or
137
148
  `float` magnitudes. Prefer `Fraction` or `Decimal` for exact scales. Floats use