unitx 0.7.0__tar.gz → 0.8.0__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.7.0 → unitx-0.8.0}/.github/workflows/ci.yml +24 -0
- {unitx-0.7.0 → unitx-0.8.0}/CHANGELOG.md +62 -0
- {unitx-0.7.0 → unitx-0.8.0}/PKG-INFO +44 -20
- {unitx-0.7.0 → unitx-0.8.0}/README.md +41 -19
- {unitx-0.7.0 → unitx-0.8.0}/conftest.py +4 -0
- unitx-0.8.0/docs/numpy.md +133 -0
- {unitx-0.7.0 → unitx-0.8.0}/pyproject.toml +2 -1
- {unitx-0.7.0 → unitx-0.8.0}/pyrightconfig.json +1 -0
- unitx-0.8.0/pyrightconfig.numpy.json +5 -0
- unitx-0.8.0/src/unitx/_constnamespace.py +33 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/_numerics.py +47 -14
- unitx-0.8.0/src/unitx/_numpy.py +374 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/factor.py +2 -2
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/quantity.py +488 -40
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/si/bases.py +9 -7
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/si/constants.py +14 -22
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/si/units.py +125 -70
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/unit.py +38 -11
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/unitregistry.py +4 -4
- unitx-0.8.0/tests/numpy_typing/check_numpy.py +61 -0
- unitx-0.8.0/tests/test_numpy.py +423 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_si.py +62 -1
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_singleunit.py +6 -2
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_typing.py +25 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_unitregistry.py +28 -4
- {unitx-0.7.0 → unitx-0.8.0}/tests/typing/check_quantity.py +34 -30
- {unitx-0.7.0 → unitx-0.8.0}/tests/typing/check_si.py +9 -1
- unitx-0.7.0/src/unitx/_constclass.py +0 -34
- {unitx-0.7.0 → unitx-0.8.0}/.github/workflows/release.yml +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/.gitignore +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/LICENSE +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/__init__.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/_unicode.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/dimension.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/exceptions.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/monomial.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/py.typed +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/si/__init__.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/singleunit.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/src/unitx/unitparser.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/__init__.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_builtin_definitions.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_dimension.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_factor.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_import.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_monomial.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_numeric.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_quantity.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_unicode.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_unit.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_unit_definition.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_unitparser.py +0 -0
- {unitx-0.7.0 → unitx-0.8.0}/tests/test_warnings.py +0 -0
|
@@ -48,3 +48,27 @@ jobs:
|
|
|
48
48
|
|
|
49
49
|
- name: Run tests
|
|
50
50
|
run: pytest -q
|
|
51
|
+
|
|
52
|
+
numpy:
|
|
53
|
+
name: NumPy ${{ matrix.numpy }} (Python ${{ matrix.python-version }})
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
strategy:
|
|
56
|
+
fail-fast: false
|
|
57
|
+
matrix:
|
|
58
|
+
include:
|
|
59
|
+
- python-version: '3.12'
|
|
60
|
+
numpy: '2.1.*'
|
|
61
|
+
- python-version: '3.14'
|
|
62
|
+
numpy: 'latest'
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v6
|
|
65
|
+
- uses: actions/setup-python@v7
|
|
66
|
+
with:
|
|
67
|
+
python-version: ${{ matrix.python-version }}
|
|
68
|
+
- name: Install package and dependencies
|
|
69
|
+
run: python -m pip install -e '.[test,numpy]'
|
|
70
|
+
- name: Select minimum NumPy
|
|
71
|
+
if: matrix.numpy != 'latest'
|
|
72
|
+
run: python -m pip install 'numpy==${{ matrix.numpy }}'
|
|
73
|
+
- name: Run tests and typing
|
|
74
|
+
run: pytest -q
|
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.0] - 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add optional NumPy >= 2.1 support through `unitx[numpy]`, with one Quantity
|
|
8
|
+
wrapping an entire plain numeric ndarray or NumPy numeric scalar. Preserve
|
|
9
|
+
input objects in both `array * unit` and `unit * array` construction.
|
|
10
|
+
- Support unit-aware conversion, broadcasting arithmetic and comparisons,
|
|
11
|
+
matrix multiplication, indexing, iteration, array metadata, transpose, and
|
|
12
|
+
flattening. Preserve NumPy scalar indexing and view/copy behavior.
|
|
13
|
+
- Implement a documented set of NumPy ufuncs and functions, including roots,
|
|
14
|
+
dimensionless transcendental functions, concatenation, statistics, predicates,
|
|
15
|
+
and position queries. Keep exact unit exponents and ScaledUnit factors.
|
|
16
|
+
- Extend Quantity.isclose with symmetric array comparisons and broadcasting
|
|
17
|
+
tolerances; implement NumPy isclose/allclose with their own defaults and
|
|
18
|
+
asymmetric formula. Validate tolerance dimensions, finiteness, and signs.
|
|
19
|
+
- Add runtime and installed-consumer typing coverage, import-order and optional
|
|
20
|
+
dependency checks, and CI jobs for minimum and current NumPy versions.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Define `Scalar`, `Array`, and `Magnitude = Scalar | Array` in `_numerics`.
|
|
25
|
+
Preserve concrete magnitude types at construction and in unit-only operations;
|
|
26
|
+
use conservative result types for array numerical operations.
|
|
27
|
+
- Keep Python scalar arithmetic, Decimal behavior, and exact scalar comparisons
|
|
28
|
+
independent of the optional NumPy adapter. Array conversion follows the
|
|
29
|
+
documented NumPy promotion, rounding, and overflow rules.
|
|
30
|
+
|
|
31
|
+
### NumPy scope
|
|
32
|
+
|
|
33
|
+
- Document the supported function/keyword matrix and precision/storage rules in
|
|
34
|
+
`docs/numpy.md`. Other array backends, ndarray subclasses, object/boolean
|
|
35
|
+
magnitudes, and implicit list/tuple conversion are unsupported.
|
|
36
|
+
- Reject implicit unit removal by `np.asarray(q)` or `np.array(q)`; use `.value`
|
|
37
|
+
or `.value_in(unit)` explicitly. NumPy magnitudes produce unhashable Quantities.
|
|
38
|
+
- Reject writable out, item assignment, and operations that would require
|
|
39
|
+
heterogeneous units. Python augmented assignment creates and rebinds a result.
|
|
40
|
+
|
|
41
|
+
## [0.7.1] - 2026-10-19
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- Accept a single exact spelling string for `prefixable`, equivalent to a
|
|
46
|
+
one-element tuple, with the same spelling validation before registration.
|
|
47
|
+
- Expand the `si` namespace's prefixed shortcuts and common units, including
|
|
48
|
+
percent, permille, permyriad, rotational frequency (`rpm`), speed of light
|
|
49
|
+
(`c`), gram-force, volt-ampere, ampere-hour, mho, and nit. Enable Torr prefixes.
|
|
50
|
+
- Add `SI.kappa` and the wavelength and frequency Wien displacement constants,
|
|
51
|
+
`SI.b` and `SI.b_`.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- Use fixed Decimal approximations with about 50 significant digits for pi and
|
|
56
|
+
the positive roots in the Wien displacement laws. Derived constants still use
|
|
57
|
+
the Decimal context active at import.
|
|
58
|
+
- Treat `u` and `Da` as spellings of one formal unit, with prefixes enabled only
|
|
59
|
+
on `Da`. Rebuild stored units when migrating from the separate `u` identity;
|
|
60
|
+
the mass conversion scale is unchanged. Use `kDa` rather than `kilodalton`.
|
|
61
|
+
- Keep ASCII micro spellings such as `um` and `us` convenient to type without
|
|
62
|
+
warnings. The explicit product `u m` retains its separate mass-times-length
|
|
63
|
+
meaning.
|
|
64
|
+
|
|
3
65
|
## [0.7.0] - 2026-10-19
|
|
4
66
|
|
|
5
67
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A modern, intuitive Python library for units, quantities, and dimensional analysis.
|
|
5
5
|
Project-URL: Homepage, https://github.com/DaitTHU/unitx
|
|
6
6
|
Project-URL: Issues, https://github.com/DaitTHU/unitx/issues
|
|
@@ -14,6 +14,8 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.15
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
16
16
|
Requires-Python: >=3.12
|
|
17
|
+
Provides-Extra: numpy
|
|
18
|
+
Requires-Dist: numpy>=2.1; extra == 'numpy'
|
|
17
19
|
Provides-Extra: test
|
|
18
20
|
Requires-Dist: pyright>=1.1.414; extra == 'test'
|
|
19
21
|
Requires-Dist: pytest>=8; extra == 'test'
|
|
@@ -22,11 +24,15 @@ Description-Content-Type: text/markdown
|
|
|
22
24
|
|
|
23
25
|
# unitx
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
[](https://pypi.org/project/unitx/)
|
|
28
|
+
[](https://pypi.org/project/unitx/)
|
|
29
|
+
[](LICENSE)
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
Write Python formulas with ready-to-use units and physical constants. `unitx`
|
|
32
|
+
handles scalar and optional NumPy quantities, unit conversion, and dimensional
|
|
33
|
+
analysis, with exact rational unit scales and exponents.
|
|
34
|
+
|
|
35
|
+
`unitx` is in early development. Public APIs may change during the 0.x series.
|
|
30
36
|
|
|
31
37
|
## Installation
|
|
32
38
|
|
|
@@ -101,15 +107,17 @@ astronomical units are included. Units and constants can be combined directly:
|
|
|
101
107
|
|
|
102
108
|
Both namespaces are read-only and do not need to be instantiated. `si` exposes
|
|
103
109
|
a selection of convenient attributes; other registered prefix combinations
|
|
104
|
-
remain available through `Unit`, for example `Unit('Tm')`. Importing unitx
|
|
110
|
+
remain available through `Unit`, for example `Unit('Tm')`. Importing `unitx`
|
|
105
111
|
initializes the predefined unit catalogue. Defining a custom unit registers its
|
|
106
112
|
text spellings without adding an attribute to `si`.
|
|
107
113
|
|
|
108
114
|
SI defining constants retain their exact decimal values. Measured constants
|
|
109
115
|
are stored nominal values; measurement uncertainties are not propagated.
|
|
110
116
|
Derived Decimal constants are evaluated in the Decimal context active at
|
|
111
|
-
import.
|
|
112
|
-
|
|
117
|
+
import. Pi and the positive roots used by the Wien displacement constants
|
|
118
|
+
(`SI.b` for wavelength and `SI.b_` for frequency) use fixed Decimal
|
|
119
|
+
approximations with about 50 significant digits. Raising the context precision
|
|
120
|
+
does not add accuracy beyond those stored approximations.
|
|
113
121
|
|
|
114
122
|
## Constructing units and quantities
|
|
115
123
|
|
|
@@ -267,7 +275,7 @@ meaning rather than acting as string delimiters. Bare compound names are
|
|
|
267
275
|
not guessed from adjacent words, and ordinary parentheses still group products.
|
|
268
276
|
|
|
269
277
|
The unit table contains `s`, `m`, `g`, `A`, `K`, `mol`, and `cd`, plus the
|
|
270
|
-
dimensionless identity. Importing unitx also initializes the common units in
|
|
278
|
+
dimensionless identity. Importing `unitx` also initializes the common units in
|
|
271
279
|
`unitx.si`, including named derived units such as `N`. Mass scales are referenced
|
|
272
280
|
to kilograms: `g` has factor `1/1000`, and `kg` has factor `1`.
|
|
273
281
|
|
|
@@ -337,8 +345,9 @@ Bare multiword names are recognized only as complete inputs. For example,
|
|
|
337
345
|
as a factor; use `[astronomical unit] / arcsec`, symbols, or Unit objects instead.
|
|
338
346
|
|
|
339
347
|
SI prefixes are enabled by default. `prefixable: PrefixPolicy = True` accepts
|
|
340
|
-
`True` for all spellings, `False` for none,
|
|
341
|
-
and aliases supplied in that definition.
|
|
348
|
+
`True` for all spellings, `False` for none, a single spelling string, or a tuple
|
|
349
|
+
of exact symbols, names, and aliases supplied in that definition. A string such
|
|
350
|
+
as `prefixable='Da'` is equivalent to `prefixable=('Da',)`. Only the selected
|
|
342
351
|
spellings accept prefixes; an empty tuple disables prefixes. Unknown spellings
|
|
343
352
|
and non-string entries are errors. For example,
|
|
344
353
|
`prefixable=('arcsec', 'arcsecond')` enables all SI prefixes on those two
|
|
@@ -347,7 +356,7 @@ spellings, while other spellings of that unit remain unprefixable.
|
|
|
347
356
|
The tuple selects **unit spellings**, not prefix symbols. Prefix names attach
|
|
348
357
|
directly to the first word of a multiword name, as in `kiloastronomical unit`;
|
|
349
358
|
a separate `kilo astronomical unit` is not accepted. `PrefixPolicy` is available
|
|
350
|
-
from `unitx.unitregistry` for annotations and means `bool | tuple[str, ...]`.
|
|
359
|
+
from `unitx.unitregistry` for annotations and means `bool | str | tuple[str, ...]`.
|
|
351
360
|
|
|
352
361
|
```pycon
|
|
353
362
|
>>> tick = si.s.define(
|
|
@@ -390,10 +399,24 @@ form; `760 * si.mmHg` converts to `si.atm` with value 1 under the library's
|
|
|
390
399
|
existing pressure convention. Prefixes do not stack: `mmmHg` and `kmmHg` are
|
|
391
400
|
invalid. `kt` retains its meaning as a knot alias; tonne remains unprefixable.
|
|
392
401
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
402
|
+
`si.mas` and `si.uas` provide milli- and microarcseconds as namespace shortcuts;
|
|
403
|
+
their registered expressions are `marcsec` and `µarcsec`. The strings `mas`
|
|
404
|
+
and `uas` are not registered aliases. `si.Da` and `si.u` now share one formal
|
|
405
|
+
unit identity; only the spelling `Da` accepts prefixes, such as `kDa`.
|
|
406
|
+
`si.mTorr` is available, and Torr accepts SI prefixes. Tonne uses `t` or
|
|
407
|
+
`tonne`; the ambiguous spelling `ton` is not registered.
|
|
408
|
+
|
|
409
|
+
`si.rpm` represents rotational frequency: `60 * si.rpm` converts to `si.Hz`
|
|
410
|
+
with value 1. For angular velocity, multiply that frequency by `2 * PI` and
|
|
411
|
+
convert to `si.rad / si.s`; directly converting frequency to `rad/s` does
|
|
412
|
+
not insert the physical factor of 2π. `si.c` is a speed unit equal to the
|
|
413
|
+
speed of light, while `SI.c` is the corresponding physical quantity.
|
|
414
|
+
|
|
415
|
+
The spelling `u` denotes the unified atomic mass unit when used on its own.
|
|
416
|
+
ASCII micro spellings such as `um` and `us` remain supported without warnings,
|
|
417
|
+
so entering non-ASCII characters is optional. `um`, `µm`, `μm`, and
|
|
418
|
+
`micrometre` all mean micrometres; the explicit product `u m` instead means
|
|
419
|
+
unified atomic mass unit times metre.
|
|
397
420
|
|
|
398
421
|
Registration raises `UnitRegistryError` if a new spelling, including a generated
|
|
399
422
|
prefixed form, already resolves to a unit or conflicts with another spelling
|
|
@@ -549,9 +572,10 @@ python -m pip install sympy
|
|
|
549
572
|
|
|
550
573
|
## Current scope
|
|
551
574
|
|
|
552
|
-
-
|
|
553
|
-
|
|
554
|
-
|
|
575
|
+
- Optional NumPy support accepts plain numeric ndarrays and NumPy numeric
|
|
576
|
+
scalars. Install `unitx[numpy]`; see the [support matrix and numerical
|
|
577
|
+
contract](https://github.com/DaitTHU/unitx/blob/v0.8.0/docs/numpy.md). Array operations preserve units, comparisons return
|
|
578
|
+
masks, and implicit unit stripping and writable `out` are rejected.
|
|
555
579
|
- Custom magnitudes must support scalar arithmetic, return a Python `bool`
|
|
556
580
|
from equality, and defer raw operations with `Unit` operands to its reflected
|
|
557
581
|
operators.
|
|
@@ -567,7 +591,7 @@ python -m pip install sympy
|
|
|
567
591
|
| Class | Import module | Purpose |
|
|
568
592
|
| --- | --- | --- |
|
|
569
593
|
| `si` / `SI` | `unitx.si` | Ready-to-use units and physical constants |
|
|
570
|
-
| `Quantity` | `unitx.quantity` | Scalar
|
|
594
|
+
| `Quantity` | `unitx.quantity` | Scalar or NumPy magnitudes paired with units, conversion, and arithmetic |
|
|
571
595
|
| `Unit` | `unitx.unit` | Compound units, scaling factors, and physical dimensions |
|
|
572
596
|
| `ScaledUnit` | `unitx.unit` | Formal unit products with an additional exact scale |
|
|
573
597
|
| `Dimension` | `unitx.dimension` | Seven physical dimensions with rational exponents |
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# unitx
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
[](https://pypi.org/project/unitx/)
|
|
4
|
+
[](https://pypi.org/project/unitx/)
|
|
5
|
+
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Write Python formulas with ready-to-use units and physical constants. `unitx`
|
|
8
|
+
handles scalar and optional NumPy quantities, unit conversion, and dimensional
|
|
9
|
+
analysis, with exact rational unit scales and exponents.
|
|
10
|
+
|
|
11
|
+
`unitx` is in early development. Public APIs may change during the 0.x series.
|
|
8
12
|
|
|
9
13
|
## Installation
|
|
10
14
|
|
|
@@ -79,15 +83,17 @@ astronomical units are included. Units and constants can be combined directly:
|
|
|
79
83
|
|
|
80
84
|
Both namespaces are read-only and do not need to be instantiated. `si` exposes
|
|
81
85
|
a selection of convenient attributes; other registered prefix combinations
|
|
82
|
-
remain available through `Unit`, for example `Unit('Tm')`. Importing unitx
|
|
86
|
+
remain available through `Unit`, for example `Unit('Tm')`. Importing `unitx`
|
|
83
87
|
initializes the predefined unit catalogue. Defining a custom unit registers its
|
|
84
88
|
text spellings without adding an attribute to `si`.
|
|
85
89
|
|
|
86
90
|
SI defining constants retain their exact decimal values. Measured constants
|
|
87
91
|
are stored nominal values; measurement uncertainties are not propagated.
|
|
88
92
|
Derived Decimal constants are evaluated in the Decimal context active at
|
|
89
|
-
import.
|
|
90
|
-
|
|
93
|
+
import. Pi and the positive roots used by the Wien displacement constants
|
|
94
|
+
(`SI.b` for wavelength and `SI.b_` for frequency) use fixed Decimal
|
|
95
|
+
approximations with about 50 significant digits. Raising the context precision
|
|
96
|
+
does not add accuracy beyond those stored approximations.
|
|
91
97
|
|
|
92
98
|
## Constructing units and quantities
|
|
93
99
|
|
|
@@ -245,7 +251,7 @@ meaning rather than acting as string delimiters. Bare compound names are
|
|
|
245
251
|
not guessed from adjacent words, and ordinary parentheses still group products.
|
|
246
252
|
|
|
247
253
|
The unit table contains `s`, `m`, `g`, `A`, `K`, `mol`, and `cd`, plus the
|
|
248
|
-
dimensionless identity. Importing unitx also initializes the common units in
|
|
254
|
+
dimensionless identity. Importing `unitx` also initializes the common units in
|
|
249
255
|
`unitx.si`, including named derived units such as `N`. Mass scales are referenced
|
|
250
256
|
to kilograms: `g` has factor `1/1000`, and `kg` has factor `1`.
|
|
251
257
|
|
|
@@ -315,8 +321,9 @@ Bare multiword names are recognized only as complete inputs. For example,
|
|
|
315
321
|
as a factor; use `[astronomical unit] / arcsec`, symbols, or Unit objects instead.
|
|
316
322
|
|
|
317
323
|
SI prefixes are enabled by default. `prefixable: PrefixPolicy = True` accepts
|
|
318
|
-
`True` for all spellings, `False` for none,
|
|
319
|
-
and aliases supplied in that definition.
|
|
324
|
+
`True` for all spellings, `False` for none, a single spelling string, or a tuple
|
|
325
|
+
of exact symbols, names, and aliases supplied in that definition. A string such
|
|
326
|
+
as `prefixable='Da'` is equivalent to `prefixable=('Da',)`. Only the selected
|
|
320
327
|
spellings accept prefixes; an empty tuple disables prefixes. Unknown spellings
|
|
321
328
|
and non-string entries are errors. For example,
|
|
322
329
|
`prefixable=('arcsec', 'arcsecond')` enables all SI prefixes on those two
|
|
@@ -325,7 +332,7 @@ spellings, while other spellings of that unit remain unprefixable.
|
|
|
325
332
|
The tuple selects **unit spellings**, not prefix symbols. Prefix names attach
|
|
326
333
|
directly to the first word of a multiword name, as in `kiloastronomical unit`;
|
|
327
334
|
a separate `kilo astronomical unit` is not accepted. `PrefixPolicy` is available
|
|
328
|
-
from `unitx.unitregistry` for annotations and means `bool | tuple[str, ...]`.
|
|
335
|
+
from `unitx.unitregistry` for annotations and means `bool | str | tuple[str, ...]`.
|
|
329
336
|
|
|
330
337
|
```pycon
|
|
331
338
|
>>> tick = si.s.define(
|
|
@@ -368,10 +375,24 @@ form; `760 * si.mmHg` converts to `si.atm` with value 1 under the library's
|
|
|
368
375
|
existing pressure convention. Prefixes do not stack: `mmmHg` and `kmmHg` are
|
|
369
376
|
invalid. `kt` retains its meaning as a knot alias; tonne remains unprefixable.
|
|
370
377
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
378
|
+
`si.mas` and `si.uas` provide milli- and microarcseconds as namespace shortcuts;
|
|
379
|
+
their registered expressions are `marcsec` and `µarcsec`. The strings `mas`
|
|
380
|
+
and `uas` are not registered aliases. `si.Da` and `si.u` now share one formal
|
|
381
|
+
unit identity; only the spelling `Da` accepts prefixes, such as `kDa`.
|
|
382
|
+
`si.mTorr` is available, and Torr accepts SI prefixes. Tonne uses `t` or
|
|
383
|
+
`tonne`; the ambiguous spelling `ton` is not registered.
|
|
384
|
+
|
|
385
|
+
`si.rpm` represents rotational frequency: `60 * si.rpm` converts to `si.Hz`
|
|
386
|
+
with value 1. For angular velocity, multiply that frequency by `2 * PI` and
|
|
387
|
+
convert to `si.rad / si.s`; directly converting frequency to `rad/s` does
|
|
388
|
+
not insert the physical factor of 2π. `si.c` is a speed unit equal to the
|
|
389
|
+
speed of light, while `SI.c` is the corresponding physical quantity.
|
|
390
|
+
|
|
391
|
+
The spelling `u` denotes the unified atomic mass unit when used on its own.
|
|
392
|
+
ASCII micro spellings such as `um` and `us` remain supported without warnings,
|
|
393
|
+
so entering non-ASCII characters is optional. `um`, `µm`, `μm`, and
|
|
394
|
+
`micrometre` all mean micrometres; the explicit product `u m` instead means
|
|
395
|
+
unified atomic mass unit times metre.
|
|
375
396
|
|
|
376
397
|
Registration raises `UnitRegistryError` if a new spelling, including a generated
|
|
377
398
|
prefixed form, already resolves to a unit or conflicts with another spelling
|
|
@@ -527,9 +548,10 @@ python -m pip install sympy
|
|
|
527
548
|
|
|
528
549
|
## Current scope
|
|
529
550
|
|
|
530
|
-
-
|
|
531
|
-
|
|
532
|
-
|
|
551
|
+
- Optional NumPy support accepts plain numeric ndarrays and NumPy numeric
|
|
552
|
+
scalars. Install `unitx[numpy]`; see the [support matrix and numerical
|
|
553
|
+
contract](https://github.com/DaitTHU/unitx/blob/v0.8.0/docs/numpy.md). Array operations preserve units, comparisons return
|
|
554
|
+
masks, and implicit unit stripping and writable `out` are rejected.
|
|
533
555
|
- Custom magnitudes must support scalar arithmetic, return a Python `bool`
|
|
534
556
|
from equality, and defer raw operations with `Unit` operands to its reflected
|
|
535
557
|
operators.
|
|
@@ -545,7 +567,7 @@ python -m pip install sympy
|
|
|
545
567
|
| Class | Import module | Purpose |
|
|
546
568
|
| --- | --- | --- |
|
|
547
569
|
| `si` / `SI` | `unitx.si` | Ready-to-use units and physical constants |
|
|
548
|
-
| `Quantity` | `unitx.quantity` | Scalar
|
|
570
|
+
| `Quantity` | `unitx.quantity` | Scalar or NumPy magnitudes paired with units, conversion, and arithmetic |
|
|
549
571
|
| `Unit` | `unitx.unit` | Compound units, scaling factors, and physical dimensions |
|
|
550
572
|
| `ScaledUnit` | `unitx.unit` | Formal unit products with an additional exact scale |
|
|
551
573
|
| `Dimension` | `unitx.dimension` | Seven physical dimensions with rational exponents |
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
from importlib.util import find_spec
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
|
|
3
5
|
from unitx._numerics import as_fraction
|
|
4
6
|
from unitx.exceptions import InexactFloatWarning
|
|
5
7
|
from unitx.factor import Factor
|
|
6
8
|
|
|
9
|
+
collect_ignore = ['src/unitx/_numpy.py'] if find_spec('numpy') is None else []
|
|
10
|
+
|
|
7
11
|
_DOCTEST_FILTER: dict[str, tuple[pytest.MarkDecorator, ...]] = {
|
|
8
12
|
f'{obj.__module__}.{obj.__qualname__}': tuple(
|
|
9
13
|
pytest.mark.filterwarnings(f'ignore::{warning.__module__}.{warning.__qualname__}')
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# NumPy support
|
|
2
|
+
|
|
3
|
+
Install `unitx[numpy]` for NumPy >= 2.1. Core imports and Python scalar
|
|
4
|
+
arithmetic do not import NumPy.
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
import numpy as np
|
|
8
|
+
from unitx import Quantity, Unit
|
|
9
|
+
|
|
10
|
+
metre = Unit('m')
|
|
11
|
+
a = np.arange(6.0).reshape(2, 3)
|
|
12
|
+
q = a * metre
|
|
13
|
+
assert q.value is a and (metre * a).value is a
|
|
14
|
+
speed = q / Quantity(2, 's')
|
|
15
|
+
centimetres = q.value_in('cm')
|
|
16
|
+
column_means = np.mean(q, axis=0)
|
|
17
|
+
mask = q > Quantity(100, 'cm')
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
One Quantity holds one unit for its entire magnitude. Supported magnitudes
|
|
21
|
+
are plain ndarrays and NumPy numeric scalars with integer, floating, or complex
|
|
22
|
+
dtype. Object, boolean, string, structured, datetime, masked arrays, ndarray
|
|
23
|
+
subclasses, and other backends are unsupported. Boolean arrays are accepted
|
|
24
|
+
as masks and returned by predicates. Construct ndarrays explicitly: list/tuple
|
|
25
|
+
magnitudes and implicit Fraction/Decimal object arrays are rejected.
|
|
26
|
+
|
|
27
|
+
## Values, storage, and precision
|
|
28
|
+
|
|
29
|
+
`.value`, `.value_in(unit)`, and `.to(unit).value` are explicit numerical exits.
|
|
30
|
+
`np.asarray(q)` and `np.array(q)` raise TypeError even for dimensionless q;
|
|
31
|
+
use `np.asarray(q.value_in(target_unit))`.
|
|
32
|
+
|
|
33
|
+
Construction, unit-only multiplication/division, and an identity conversion
|
|
34
|
+
preserve the magnitude object. Factor/SymbolicConstant operands change the
|
|
35
|
+
exact unit scale. Indexing, iteration, `.T`, reshape, and transpose follow
|
|
36
|
+
NumPy's scalar/view/copy rules; `.flatten(order='C')` copies. Zero-dimensional
|
|
37
|
+
arrays cannot be iterated. `.shape`, `.ndim`, `.dtype`, and `.size` are available
|
|
38
|
+
for NumPy magnitudes. Python scalars do not acquire array attributes. Exposed
|
|
39
|
+
values and views may share writable storage.
|
|
40
|
+
|
|
41
|
+
Unit algebra stays exact. Nonidentity conversion uses `np.multiply`: integral
|
|
42
|
+
rational ratios become Python int multipliers; nonintegral rational ratios
|
|
43
|
+
become one Python float multiplier; irrational ratios use Factor's approximate
|
|
44
|
+
value. NumPy controls dtype promotion, rounding, and overflow. Conversion
|
|
45
|
+
allocates and never writes to the input. Integer overflow, multiplier
|
|
46
|
+
conversion errors, and float overflow/underflow remain possible, including for
|
|
47
|
+
extended precision inputs. Conversion never creates Fraction/Decimal arrays.
|
|
48
|
+
|
|
49
|
+
Comparisons broadcast and return bare boolean results. Equal unit factors
|
|
50
|
+
compare native values; otherwise both operands convert to coherent SI values.
|
|
51
|
+
Different dimensions yield False equality / True inequality masks; ordering
|
|
52
|
+
raises DimensionError. Bare numbers/arrays are dimensionless. These numerical
|
|
53
|
+
rules are distinct from Python scalar Quantity's exact comparison and hash
|
|
54
|
+
rules. All NumPy magnitudes, including scalars and zero-dimensional arrays,
|
|
55
|
+
produce unhashable Quantities. `bool(q)` delegates to `bool(q.value)`.
|
|
56
|
+
|
|
57
|
+
## Support matrix
|
|
58
|
+
|
|
59
|
+
Unit-bearing results are Quantities, including dimensionless values and scalar
|
|
60
|
+
reductions. Predicates and indices are bare results. Unsupported functions,
|
|
61
|
+
methods, mixed array types, and keywords fail explicitly.
|
|
62
|
+
|
|
63
|
+
| ufunc / Python operation | Result rule |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| add, subtract / `+`, `-` | Align compatible dimensions to the first Quantity's unit |
|
|
66
|
+
| multiply, divide/true_divide, matmul / `*`, `/`, `@` | Combine units |
|
|
67
|
+
| equal, not_equal, less, less_equal, greater, greater_equal / comparisons | Bare boolean results |
|
|
68
|
+
| positive, negative, absolute, fabs, conjugate / unary `+`, `-`, `abs` | Preserve unit |
|
|
69
|
+
| square, sqrt, cbrt, reciprocal | Exact unit exponents 2, 1/2, 1/3, -1 |
|
|
70
|
+
| power, float_power / `**` | Dimensional bases require a finite real scalar exponent; dimensionless bases/exponents normalize their scales |
|
|
71
|
+
| sin, cos, tan, exp, expm1, log, log2, log10, log1p | Require coherent dimensionless values; return dimensionless Quantity |
|
|
72
|
+
| isfinite, isnan, isinf, signbit | Bare boolean results |
|
|
73
|
+
| add.reduce, add.accumulate | Preserve unit; accept axis and dtype, plus keepdims for reduce |
|
|
74
|
+
|
|
75
|
+
Ufunc `__call__` accepts `where`, `dtype`, `casting`, `order`, `subok`, and empty
|
|
76
|
+
`out`. Each kernel validates its own options. `where` is a bare boolean scalar
|
|
77
|
+
or plain boolean ndarray; masked-out positions without an output buffer follow
|
|
78
|
+
NumPy's uninitialized-result behavior. Numeric outputs must stay in the
|
|
79
|
+
supported dtype domain. Product reductions, at, outer, and reduceat are
|
|
80
|
+
unsupported. Python `**` also accepts Fraction/Decimal exponents, retaining
|
|
81
|
+
exact unit exponents while passing a float exponent to the NumPy kernel.
|
|
82
|
+
Angles retain unitx's dimensionless model: deg/rad normalize before these
|
|
83
|
+
functions, including exp/log. Python scalar numerical semantics remain native.
|
|
84
|
+
|
|
85
|
+
| NumPy function | Unit/result rule | Supported options |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| reshape, transpose | Preserve unit | shape/order/copy; axes |
|
|
88
|
+
| concatenate, stack | Convert to first Quantity's unit; bare arrays remain dimensionless | axis, dtype, casting |
|
|
89
|
+
| sum, mean | Preserve unit | axis, dtype, keepdims |
|
|
90
|
+
| median | Preserve unit | axis, keepdims; overwrite_input=False |
|
|
91
|
+
| std, var | Preserve unit / square unit | axis, dtype, ddof, keepdims |
|
|
92
|
+
| cumsum | Preserve unit | axis, dtype |
|
|
93
|
+
| argmin, argmax | Bare indices | axis, keepdims |
|
|
94
|
+
| isclose, allclose | Bare mask / Python bool | rtol, atol, equal_nan |
|
|
95
|
+
|
|
96
|
+
Supply the first magnitude argument positionally; concatenate/stack take a
|
|
97
|
+
sequence. Options follow each supported signature, including positional out
|
|
98
|
+
checks. Writable `out` and overwrite_input=True are rejected. Unsupported
|
|
99
|
+
initial, statistical where, mean, correction, and other keywords raise TypeError.
|
|
100
|
+
There is no `__setitem__`; `q += other` creates a result and rebinds q.
|
|
101
|
+
|
|
102
|
+
## Close comparisons and typing
|
|
103
|
+
|
|
104
|
+
`q.isclose(other, rel_tol=1e-9, abs_tol=0)` uses the symmetric formula in
|
|
105
|
+
coherent SI coordinates:
|
|
106
|
+
`abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)`.
|
|
107
|
+
`np.isclose` instead uses `abs(a-b) <= atol + rtol*abs(b)` with defaults
|
|
108
|
+
rtol=1e-5 and atol=1e-8; `np.allclose` reduces that mask. Tolerances broadcast
|
|
109
|
+
and must be finite, nonnegative, and real. Relative tolerance is dimensionless;
|
|
110
|
+
absolute tolerance matches the operands' dimension. Nonzero bare absolute
|
|
111
|
+
tolerance is dimensionless, so dimensional `np.isclose` calls need a Quantity
|
|
112
|
+
atol or zero. Matching infinities are close; NaNs require equal_nan=True.
|
|
113
|
+
The NumPy path enters at least float64 before subtraction, preserving
|
|
114
|
+
longdouble when present; large integers may lose precision. Mixed NumPy
|
|
115
|
+
operations require supported arrays/scalars or Python int/float/complex.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
np.isclose(q, Quantity(a * 100, 'cm'), atol=Quantity(1e-8, 'm'))
|
|
119
|
+
q.isclose(Quantity(a * 100, 'cm'), abs_tol=Quantity(1e-8, 'm'))
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`_numerics.py` defines `Scalar`, `Array`, and `Magnitude = Scalar | Array`
|
|
123
|
+
without importing NumPy. Array is a typing protocol, not a guarantee of other
|
|
124
|
+
backend support. Construction and unit-only operators preserve Quantity[T];
|
|
125
|
+
numerical array results conservatively use Array. NumPy's left-operand and
|
|
126
|
+
function stubs cannot generally infer wrapped unitx results; use
|
|
127
|
+
Quantity(array, unit) or unit * array for precise construction types. Arbitrary
|
|
128
|
+
shape/dtype inference is not promised. NumPy-free scalar consumers retain
|
|
129
|
+
working type hints.
|
|
130
|
+
|
|
131
|
+
References: [NumPy dispatch](https://numpy.org/doc/stable/reference/arrays.classes.html),
|
|
132
|
+
[promotion](https://numpy.org/doc/stable/reference/arrays.promotion.html),
|
|
133
|
+
[isclose](https://numpy.org/doc/stable/reference/generated/numpy.isclose.html).
|
|
@@ -4,7 +4,7 @@ build-backend = 'hatchling.build'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'unitx'
|
|
7
|
-
version = '0.
|
|
7
|
+
version = '0.8.0'
|
|
8
8
|
description = 'A modern, intuitive Python library for units, quantities, and dimensional analysis.'
|
|
9
9
|
readme = 'README.md'
|
|
10
10
|
requires-python = '>=3.12'
|
|
@@ -23,6 +23,7 @@ classifiers = [
|
|
|
23
23
|
|
|
24
24
|
[project.optional-dependencies]
|
|
25
25
|
test = ['pytest>=8', 'ruff>=0.8', 'pyright>=1.1.414']
|
|
26
|
+
numpy = ['numpy>=2.1']
|
|
26
27
|
|
|
27
28
|
[project.urls]
|
|
28
29
|
Homepage = 'https://github.com/DaitTHU/unitx'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Class-based namespaces for constants accessed directly as class attributes.
|
|
2
|
+
|
|
3
|
+
Namespaces cannot be instantiated, and their attributes cannot be rebound or
|
|
4
|
+
deleted. Objects referenced by those attributes are not recursively frozen.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, NoReturn
|
|
8
|
+
|
|
9
|
+
__all__ = ['ConstNamespace']
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _ConstNamespaceMeta(type):
|
|
13
|
+
def __call__(metacls, *args: Any, **kwds: Any) -> NoReturn:
|
|
14
|
+
"""
|
|
15
|
+
This namespace cannot be instantiated; access constants via class attributes.
|
|
16
|
+
"""
|
|
17
|
+
raise TypeError(metacls.__call__.__doc__)
|
|
18
|
+
|
|
19
|
+
def __setattr__(cls, name, value) -> NoReturn:
|
|
20
|
+
"""
|
|
21
|
+
Constant namespace attributes cannot be assigned.
|
|
22
|
+
"""
|
|
23
|
+
raise AttributeError(cls.__class__.__setattr__.__doc__)
|
|
24
|
+
|
|
25
|
+
def __delattr__(cls, name) -> NoReturn:
|
|
26
|
+
"""
|
|
27
|
+
Constant namespace attributes cannot be deleted.
|
|
28
|
+
"""
|
|
29
|
+
raise AttributeError(cls.__class__.__delattr__.__doc__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConstNamespace(metaclass=_ConstNamespaceMeta):
|
|
33
|
+
"""Base for constant namespaces with protected class attribute bindings."""
|