unitx 0.13.2__tar.gz → 0.13.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitx-0.13.2 → unitx-0.13.4}/CHANGELOG.md +26 -0
- {unitx-0.13.2 → unitx-0.13.4}/PKG-INFO +5 -5
- {unitx-0.13.2 → unitx-0.13.4}/README.md +4 -4
- {unitx-0.13.2 → unitx-0.13.4}/docs/README.md +2 -1
- {unitx-0.13.2 → unitx-0.13.4}/docs/display.md +20 -0
- unitx-0.13.4/docs/serialization.md +225 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/typing.md +5 -0
- {unitx-0.13.2 → unitx-0.13.4}/examples/README.md +4 -3
- unitx-0.13.4/examples/arrhenius_fit.ipynb +236 -0
- {unitx-0.13.2 → unitx-0.13.4}/pyproject.toml +1 -1
- {unitx-0.13.2 → unitx-0.13.4}/pyrightconfig.json +2 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/_display.py +21 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/affineunit.py +1 -1
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/exceptions.py +10 -0
- unitx-0.13.4/src/unitx/serialization.py +354 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_display.py +40 -0
- unitx-0.13.4/tests/test_serialization.py +548 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_typing.py +25 -0
- unitx-0.13.4/tests/typing/check_serialization.py +32 -0
- {unitx-0.13.2 → unitx-0.13.4}/.github/workflows/ci.yml +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/.github/workflows/release.yml +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/.gitignore +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/CONTRIBUTING.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/LICENSE +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/benchmarks/numpy_writes.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/conftest.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/affine.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/catalogue.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/custom-units.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/images/blackbody.svg +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/migration.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/numpy.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/scalars.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/stdlib.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/docs/units.md +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/examples/blackbody.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/examples/damped_oscillator.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/examples/gaussian_wavepacket.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/examples/optical_attenuation.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/examples/pendulum_uncertainty.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/__init__.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/_constnamespace.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/_conversion.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/_immutable.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/_numerics.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/_numpy.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/_numpy_typing.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/_unicode.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/affinequantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/dimension.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/factor.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/math.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/monomial.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/py.typed +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/quantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/si/__init__.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/si/bases.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/si/constants.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/si/units.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/singleunit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/time.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/unit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/unitparser.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/src/unitx/unitregistry.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/__init__.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/_notebooks.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/example_typing/check_blackbody.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/numpy_typing/check_math.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/numpy_typing/check_numpy.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_affine.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_affinequantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_builtin_definitions.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_dimension.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_examples.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_factor.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_immutable.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_import.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_math.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_math_numpy.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_monomial.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numeric.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy_affine.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy_differentiation.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy_integration.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy_multi_outputs.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy_mutation.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy_outputs.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy_products.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_numpy_statistics.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_quantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_si.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_singleunit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_siunitx_display.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_time.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_unicode.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_unicode_units.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_unit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_unit_definition.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_unitparser.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_unitregistry.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/test_warnings.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/typing/check_affine.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/typing/check_math_time.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/typing/check_quantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.4}/tests/typing/check_si.py +0 -0
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.13.4] - 2026-10-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Preserve exact extra scales and structural unit formatting for affine points
|
|
10
|
+
in anonymous zero-origin linear coordinates across text, LaTeX, HTML and siunitx.
|
|
11
|
+
Stored readings and named coordinate labels are unchanged.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Add an Arrhenius fitting notebook with absolute temperatures, activation-energy
|
|
16
|
+
estimates and standard errors, a fitted curve and first-order rate predictions.
|
|
17
|
+
|
|
18
|
+
## [0.13.3] - 2026-10-22
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `unitx.serialization.to_dict()` / `from_dict()` exchange scalar Quantities,
|
|
23
|
+
affine points and their units as versioned JSON-compatible records. Preserve
|
|
24
|
+
int/float/Fraction/Decimal magnitudes, exact extra scales, formal unit identity
|
|
25
|
+
and coordinate references; validate existing registry meanings without
|
|
26
|
+
registering units. NumPy, complex magnitudes and subclasses are not supported.
|
|
27
|
+
- Add `SerializationError` for invalid records and failed restoration, and an
|
|
28
|
+
independent `VersionError` for unsupported explicit versions. Both derive
|
|
29
|
+
from `UnitxError` and `ValueError`; decoding preserves underlying causes.
|
|
30
|
+
|
|
5
31
|
## [0.13.2] - 2026-10-22
|
|
6
32
|
|
|
7
33
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitx
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.4
|
|
4
4
|
Summary: Physical units, quantities, and unit conversion with exact unit algebra, type hints, and NumPy integration.
|
|
5
5
|
Project-URL: Homepage, https://github.com/DaitTHU/unitx
|
|
6
6
|
Project-URL: Documentation, https://github.com/DaitTHU/unitx/blob/main/docs/README.md
|
|
@@ -195,9 +195,9 @@ Use `.value` or `.value_in(unit)` at plotting and external-library boundaries.
|
|
|
195
195
|
implicit unit removal. NumPy's type stubs do not describe every wrapped
|
|
196
196
|
operation; see the [typing guide](https://github.com/DaitTHU/unitx/blob/main/docs/typing.md).
|
|
197
197
|
|
|
198
|
-
The [
|
|
199
|
-
cover blackbody radiation, damped motion, wave-packet
|
|
200
|
-
and measurement uncertainty.
|
|
198
|
+
The [scientific examples](https://github.com/DaitTHU/unitx/blob/main/examples/README.md)
|
|
199
|
+
cover reaction-rate fitting, blackbody radiation, damped motion, wave-packet
|
|
200
|
+
spreading, attenuation, and measurement uncertainty.
|
|
201
201
|
|
|
202
202
|
## Blackbody radiation
|
|
203
203
|
|
|
@@ -213,7 +213,7 @@ from matplotlib import pyplot as plt
|
|
|
213
213
|
from unitx.si import SI, si
|
|
214
214
|
|
|
215
215
|
wavelength = np.linspace(0.01, 3, 100)[:, None] * si.um
|
|
216
|
-
temperature = np.
|
|
216
|
+
temperature = np.asarray([3000, 4000, 5000]) * si.K
|
|
217
217
|
exponent = SI.h * SI.c / (wavelength * SI.kB * temperature)
|
|
218
218
|
radiance = 2 * SI.h * SI.c**2 / (wavelength**5 * np.expm1(exponent))
|
|
219
219
|
radiance = radiance.to('kW/sr/m2/nm')
|
|
@@ -167,9 +167,9 @@ Use `.value` or `.value_in(unit)` at plotting and external-library boundaries.
|
|
|
167
167
|
implicit unit removal. NumPy's type stubs do not describe every wrapped
|
|
168
168
|
operation; see the [typing guide](https://github.com/DaitTHU/unitx/blob/main/docs/typing.md).
|
|
169
169
|
|
|
170
|
-
The [
|
|
171
|
-
cover blackbody radiation, damped motion, wave-packet
|
|
172
|
-
and measurement uncertainty.
|
|
170
|
+
The [scientific examples](https://github.com/DaitTHU/unitx/blob/main/examples/README.md)
|
|
171
|
+
cover reaction-rate fitting, blackbody radiation, damped motion, wave-packet
|
|
172
|
+
spreading, attenuation, and measurement uncertainty.
|
|
173
173
|
|
|
174
174
|
## Blackbody radiation
|
|
175
175
|
|
|
@@ -185,7 +185,7 @@ from matplotlib import pyplot as plt
|
|
|
185
185
|
from unitx.si import SI, si
|
|
186
186
|
|
|
187
187
|
wavelength = np.linspace(0.01, 3, 100)[:, None] * si.um
|
|
188
|
-
temperature = np.
|
|
188
|
+
temperature = np.asarray([3000, 4000, 5000]) * si.K
|
|
189
189
|
exponent = SI.h * SI.c / (wavelength * SI.kB * temperature)
|
|
190
190
|
radiance = 2 * SI.h * SI.c**2 / (wavelength**5 * np.expm1(exponent))
|
|
191
191
|
radiance = radiance.to('kW/sr/m2/nm')
|
|
@@ -16,9 +16,10 @@ reading about an older version.
|
|
|
16
16
|
| Call unit-aware mathematical functions or read a clock | [Standard-library enhancements](stdlib.md) |
|
|
17
17
|
| Calculate with arrays, masks, reductions and output buffers | [NumPy support](numpy.md) |
|
|
18
18
|
| Format text or export LaTeX, HTML and siunitx | [Display and formatting](display.md) |
|
|
19
|
+
| Save and restore scalar quantities as JSON-compatible records | [Scalar data exchange](serialization.md) |
|
|
19
20
|
| Understand annotations and NumPy inference limits | [Typing](typing.md) |
|
|
20
21
|
| Update code written for an earlier release | [Migration notes](migration.md) |
|
|
21
|
-
| Run scientific examples | [
|
|
22
|
+
| Run scientific examples | [Scientific examples](../examples/README.md) |
|
|
22
23
|
| Test or contribute a change | [Contributing](../CONTRIBUTING.md) |
|
|
23
24
|
|
|
24
25
|
For a first read, follow units and quantities, then the catalogue. Read the
|
|
@@ -17,6 +17,26 @@ markup or raw TeX commands. Unicode symbols remain Unicode and require font
|
|
|
17
17
|
support in the renderer. These strings are for presentation, not serialization.
|
|
18
18
|
`repr()` and `Unit.expression()` keep their existing reconstruction contracts.
|
|
19
19
|
|
|
20
|
+
An AffineQuantity in an anonymous zero-origin linear coordinate displays its
|
|
21
|
+
complete difference Unit, including any extra scale and rational powers. The
|
|
22
|
+
stored reading is unchanged. Named coordinates keep their own labels:
|
|
23
|
+
|
|
24
|
+
```pycon
|
|
25
|
+
>>> from unitx.unit import ScaledUnit
|
|
26
|
+
>>> from unitx.factor import Factor
|
|
27
|
+
>>> point = 3 @ ScaledUnit('m', scale=Factor(2))
|
|
28
|
+
>>> str(point)
|
|
29
|
+
'3⋅2⋅m'
|
|
30
|
+
>>> format(point, '.2f')
|
|
31
|
+
'3.00 (2⋅m)'
|
|
32
|
+
>>> point.to_quantity('m').value
|
|
33
|
+
Fraction(6, 1)
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
LaTeX, HTML and siunitx use the same structural unit rendering as a linear
|
|
38
|
+
Quantity with that complete Unit; formatting never converts the reading.
|
|
39
|
+
|
|
20
40
|
## Quantity format syntax
|
|
21
41
|
|
|
22
42
|
`format(q, 'numeric_spec;style')`, or `f'{q:numeric_spec;style}'`, selects:
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Scalar data exchange
|
|
2
|
+
|
|
3
|
+
`unitx.serialization.to_dict()` returns a versioned record made of dictionaries,
|
|
4
|
+
lists, strings, null and an integer format version. `from_dict()` restores it.
|
|
5
|
+
Use Python's JSON functions, or another transport for the same data structure:
|
|
6
|
+
|
|
7
|
+
```pycon
|
|
8
|
+
>>> import json
|
|
9
|
+
>>> from decimal import Decimal
|
|
10
|
+
>>> from fractions import Fraction
|
|
11
|
+
>>> from unitx import AffineQuantity, AffineUnit, Quantity, Unit
|
|
12
|
+
>>> from unitx.serialization import from_dict, to_dict
|
|
13
|
+
>>> original = Quantity(Fraction(1, 3), 'cm')
|
|
14
|
+
>>> text = json.dumps(to_dict(original), allow_nan=False)
|
|
15
|
+
>>> restored = from_dict(json.loads(text))
|
|
16
|
+
>>> type(restored.value) is Fraction
|
|
17
|
+
True
|
|
18
|
+
>>> restored.value, restored.unit
|
|
19
|
+
(Fraction(1, 3), Unit('cm'))
|
|
20
|
+
>>> restored == original
|
|
21
|
+
True
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The module supports `Unit`, `ScaledUnit`, `AffineUnit`, scalar `Quantity` and
|
|
26
|
+
scalar `AffineQuantity`. Import the two functions from `unitx.serialization`;
|
|
27
|
+
they are not package-root exports. No optional dependency is required.
|
|
28
|
+
|
|
29
|
+
## Stored numbers
|
|
30
|
+
|
|
31
|
+
Magnitudes must be exact instances of Python `int`, `float`, `Fraction` or
|
|
32
|
+
`Decimal`. Booleans, complex numbers, numeric subclasses, custom magnitudes,
|
|
33
|
+
NumPy scalars and arrays are rejected with TypeError. A round trip preserves
|
|
34
|
+
the stored magnitude's type and value without converting its unit.
|
|
35
|
+
|
|
36
|
+
| Type tag | `value` field | Meaning |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `int` | A canonical decimal integer string | Retains integers beyond the exact range of a JSON consumer's floating-point numbers |
|
|
39
|
+
| `fraction` | Two integer strings, `[numerator, denominator]` | Exact rational, with positive denominator |
|
|
40
|
+
| `float` | `float.hex()` text | Exact finite binary value, including subnormals and signed zero; `inf`, `-inf` and `nan` are also supported |
|
|
41
|
+
| `decimal` | Decimal text | Retains sign, coefficient, trailing zeros and exponent; infinities and NaNs are supported |
|
|
42
|
+
|
|
43
|
+
Float NaNs preserve their category; NaN payload bits and sign are not part of
|
|
44
|
+
the format. Decimal NaN payloads and signaling status are retained. Export
|
|
45
|
+
and import do not round a Decimal magnitude in the active context. Invalid
|
|
46
|
+
Decimal strings are rejected even when the caller disables InvalidOperation
|
|
47
|
+
traps, without setting flags in the caller's context.
|
|
48
|
+
|
|
49
|
+
All numerical data except the format version use strings. Consequently strict
|
|
50
|
+
JSON encoding with `allow_nan=False` works even for nonfinite magnitudes.
|
|
51
|
+
Python's normal integer-string conversion and Decimal representation limits
|
|
52
|
+
still apply.
|
|
53
|
+
|
|
54
|
+
## Units and points
|
|
55
|
+
|
|
56
|
+
The format preserves the formal product, including prefixes and rational
|
|
57
|
+
exponents. Equal physical scale does not merge distinct units such as Hz and
|
|
58
|
+
Bq, and `km/m` retains its nontrivial dimensionless scale. A ScaledUnit stores
|
|
59
|
+
its additional Factor separately, including when that Factor is one:
|
|
60
|
+
|
|
61
|
+
```pycon
|
|
62
|
+
>>> from unitx import pi
|
|
63
|
+
>>> from unitx.unit import ScaledUnit
|
|
64
|
+
>>> scaled = ScaledUnit('m', pi ** Fraction(1, 3))
|
|
65
|
+
>>> rebuilt = from_dict(json.loads(json.dumps(to_dict(scaled))))
|
|
66
|
+
>>> type(rebuilt) is ScaledUnit and rebuilt.scale == scaled.scale
|
|
67
|
+
True
|
|
68
|
+
>>> rebuilt**3 == scaled**3
|
|
69
|
+
True
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
An AffineUnit stores its physical origin, difference unit and logarithmic kind.
|
|
74
|
+
Registered coordinates also retain their canonical registered symbol;
|
|
75
|
+
anonymous coordinates remain anonymous. Points stay AffineQuantity objects:
|
|
76
|
+
|
|
77
|
+
```pycon
|
|
78
|
+
>>> point = AffineQuantity(Decimal('20.00'), 'degC')
|
|
79
|
+
>>> rebuilt = from_dict(json.loads(json.dumps(to_dict(point))))
|
|
80
|
+
>>> type(rebuilt) is AffineQuantity and rebuilt.unit == point.unit
|
|
81
|
+
True
|
|
82
|
+
>>> rebuilt.value.as_tuple() == point.value.as_tuple()
|
|
83
|
+
True
|
|
84
|
+
>>> epoch = AffineUnit(Quantity(946684800, 's'), delta_unit='s')
|
|
85
|
+
>>> custom = AffineQuantity(Fraction(1, 2), epoch)
|
|
86
|
+
>>> from_dict(to_dict(custom)).unit == epoch
|
|
87
|
+
True
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This preserves the existing model's information. It does not add clock identity,
|
|
92
|
+
timezone or source labels to time points. Point arithmetic and the existing
|
|
93
|
+
hashability rules are unchanged.
|
|
94
|
+
|
|
95
|
+
## Registry requirements
|
|
96
|
+
|
|
97
|
+
Import resolves spellings against the receiving process's existing registry.
|
|
98
|
+
Register custom units explicitly during application initialization before
|
|
99
|
+
decoding records that refer to them. Unknown spellings raise SerializationError
|
|
100
|
+
with the original UnitSymbolError retained as `__cause__`.
|
|
101
|
+
Decoding never registers or replaces definitions, and neither function changes
|
|
102
|
+
the input object or record.
|
|
103
|
+
|
|
104
|
+
A linear unit record contains its canonical formal identity and its combined
|
|
105
|
+
dimension, SI Factor and logarithmic degree. Import checks these against the
|
|
106
|
+
resolved unit. A registered coordinate must also match the saved physical
|
|
107
|
+
origin, its formal reference and difference units, and logarithmic kind.
|
|
108
|
+
Conflicts raise SerializationError rather than silently changing the result.
|
|
109
|
+
|
|
110
|
+
These checks describe the resulting unit, not a complete registry snapshot.
|
|
111
|
+
They do not compare names, aliases, prefix policies or each individual unit's
|
|
112
|
+
conversion metadata when changes cancel within a product. The receiving
|
|
113
|
+
application remains responsible for compatible unit definitions. Factor checks
|
|
114
|
+
compare exact formal state: a changed symbolic approximation or a different
|
|
115
|
+
unresolved-integer decomposition can cause rejection even if physical values
|
|
116
|
+
are approximately equal. No floating-point tolerance is used to infer equality.
|
|
117
|
+
|
|
118
|
+
## Version 1 record format
|
|
119
|
+
|
|
120
|
+
Only the outermost object has `"version": 1`; every object record has a `type`.
|
|
121
|
+
For example, an integer length is represented as:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"version": 1,
|
|
126
|
+
"type": "quantity",
|
|
127
|
+
"value": {"type": "int", "value": "3"},
|
|
128
|
+
"unit": {
|
|
129
|
+
"type": "unit",
|
|
130
|
+
"expression": "m",
|
|
131
|
+
"identity": [["", "m", "1", "1"]],
|
|
132
|
+
"dimension": [["0", "1"], ["1", "1"], ["0", "1"], ["0", "1"], ["0", "1"], ["0", "1"], ["0", "1"]],
|
|
133
|
+
"factor": [],
|
|
134
|
+
"log_degree": ["0", "1"]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The object records have these exact fields:
|
|
140
|
+
|
|
141
|
+
| `type` | Fields in addition to `type` |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| `quantity`, `affine_quantity` | `value` scalar record, `unit` linear/affine unit record respectively |
|
|
144
|
+
| `unit` | `expression`, `identity`, `dimension`, `factor`, `log_degree` |
|
|
145
|
+
| `scaled_unit` | The unit fields plus `scale`, a Factor component list |
|
|
146
|
+
| `affine_unit` | `origin` linear Quantity record, `delta_unit` linear unit record, `kind` null/`power`/`amplitude`, `symbol` canonical registered string or null |
|
|
147
|
+
|
|
148
|
+
`identity` is a sorted list of `[prefix_symbol, unprefixed_symbol, numerator,
|
|
149
|
+
denominator]` terms; the last two strings encode the nonzero exponent.
|
|
150
|
+
`dimension` lists seven rational pairs in T, L, M, I, Theta, N, J order.
|
|
151
|
+
`log_degree` is another rational pair.
|
|
152
|
+
|
|
153
|
+
Factor component lists preserve the formal integer/symbolic bases and rational
|
|
154
|
+
exponents. The empty list represents one. Each component has an `exponent`
|
|
155
|
+
pair and one of these base descriptions:
|
|
156
|
+
|
|
157
|
+
| Component `type` | Other fields |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `prime` | `value`: prime integer string |
|
|
160
|
+
| `symbolic` | `symbol`: literal label, `value`: positive finite float.hex() approximation |
|
|
161
|
+
| `unfactored` | `value`: unresolved integer string, `limit`: stored factorization-limit integer string |
|
|
162
|
+
|
|
163
|
+
Symbolic labels are reconstructed as SymbolicConstant objects, never evaluated
|
|
164
|
+
as code. Their mathematical construction requirements remain the caller's
|
|
165
|
+
responsibility. Unresolved bases keep the existing incomplete-factorization
|
|
166
|
+
limitations and emit IncompleteFactorWarning on reconstruction.
|
|
167
|
+
|
|
168
|
+
Unknown versions, tags, missing or extra fields and invalid nested records are
|
|
169
|
+
rejected. The version is independent of the package release number; future
|
|
170
|
+
incompatible record changes require a new format version. This is a data
|
|
171
|
+
format, not a Python class/subclass reconstruction protocol. No `eval`, dynamic
|
|
172
|
+
class imports, JSON encoder subclasses, file I/O or configurable codec registry
|
|
173
|
+
are involved. Applications parsing external input should apply their own size
|
|
174
|
+
and resource limits.
|
|
175
|
+
|
|
176
|
+
## Errors
|
|
177
|
+
|
|
178
|
+
Import the error classes from `unitx.exceptions`. The public boundaries are:
|
|
179
|
+
|
|
180
|
+
| Operation and failure | Exception |
|
|
181
|
+
| --- | --- |
|
|
182
|
+
| `to_dict()` receives an unsupported object or magnitude | TypeError |
|
|
183
|
+
| `from_dict()` receives a non-dictionary top-level input | TypeError |
|
|
184
|
+
| A record specifies an unsupported integer format version | VersionError |
|
|
185
|
+
| A record has no version or the version is not an integer | SerializationError |
|
|
186
|
+
| Invalid fields, missing definitions, conflicting unit meanings or failed reconstruction | SerializationError |
|
|
187
|
+
|
|
188
|
+
VersionError and SerializationError both inherit UnitxError and ValueError.
|
|
189
|
+
They are independent classes: VersionError is available for explicit version
|
|
190
|
+
checks elsewhere in unitx as well. It currently describes the record format
|
|
191
|
+
version, not the installed package version. Catch the two classes separately
|
|
192
|
+
for different recovery actions, or catch UnitxError for both:
|
|
193
|
+
|
|
194
|
+
```pycon
|
|
195
|
+
>>> from unitx.exceptions import SerializationError, UnitxError, VersionError
|
|
196
|
+
>>> try:
|
|
197
|
+
... from_dict({'version': 2})
|
|
198
|
+
... except VersionError:
|
|
199
|
+
... print('Unsupported record version')
|
|
200
|
+
Unsupported record version
|
|
201
|
+
>>> try:
|
|
202
|
+
... from_dict({'version': 1, 'type': 'unknown'})
|
|
203
|
+
... except SerializationError:
|
|
204
|
+
... print('Invalid record')
|
|
205
|
+
Invalid record
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Underlying numeric, parsing and coordinate exceptions, including
|
|
210
|
+
UnitSymbolError, UnitSyntaxError and DimensionError, are retained as
|
|
211
|
+
`error.__cause__` when wrapped. Explicit record-validation errors use
|
|
212
|
+
SerializationError directly. Exceptions outside the expected validation
|
|
213
|
+
families, such as RuntimeError, are not wrapped.
|
|
214
|
+
|
|
215
|
+
The return type of `from_dict()` is a union of the supported object classes;
|
|
216
|
+
use `isinstance` when code requires a specific class. Its scalar magnitude type
|
|
217
|
+
is the supported union, since arbitrary input data cannot statically determine
|
|
218
|
+
the original numerical type.
|
|
219
|
+
|
|
220
|
+
Numerical encodings follow the Python documentation for
|
|
221
|
+
[float hexadecimal conversion](https://docs.python.org/3/library/stdtypes.html#float.hex),
|
|
222
|
+
[Decimal construction](https://docs.python.org/3/library/decimal.html#decimal.Decimal)
|
|
223
|
+
and [JSON data exchange](https://docs.python.org/3/library/json.html).
|
|
224
|
+
|
|
225
|
+
[Documentation index](README.md) · [Scalar behavior](scalars.md) · [Custom units](custom-units.md)
|
|
@@ -26,6 +26,11 @@ for its array overloads. `unitx.time` clocks return `AffineQuantity[int]`;
|
|
|
26
26
|
duration adapters use ordinary Quantity objects. `time.from_datetime()` returns
|
|
27
27
|
`AffineQuantity[int]`; `time.to_datetime()` returns an aware datetime.
|
|
28
28
|
|
|
29
|
+
`unitx.serialization.from_dict()` returns a union of the supported unit and
|
|
30
|
+
scalar quantity classes. Use `isinstance` to narrow it. The magnitude type is
|
|
31
|
+
`int | float | Fraction | Decimal`; the input record determines its runtime type.
|
|
32
|
+
See [scalar data exchange](serialization.md).
|
|
33
|
+
|
|
29
34
|
A signature such as `Quantity[float]` does not require metres rather than
|
|
30
35
|
seconds. An `Annotated` label alone does not teach a type checker dimensional
|
|
31
36
|
algebra. unitx does not currently supply dimension-parameterized quantities
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Scientific examples
|
|
2
2
|
|
|
3
3
|
These notebooks use unitx for dimensional calculations and NumPy for numerical
|
|
4
4
|
work. Each contains a model, assumptions, plots, and numerical checks. The
|
|
@@ -11,6 +11,7 @@ material parameters and measurement draws are illustrative, not experimental dat
|
|
|
11
11
|
| [Gaussian wave packet](gaussian_wavepacket.ipynb) | Free electron propagation and uncertainty | Probability-density units, quantum constants, unit-aware integration |
|
|
12
12
|
| [Optical attenuation](optical_attenuation.ipynb) | Transmission through absorbing layers | prod/multiply.reduce, input masks, Quantity initial, fixed-unit out |
|
|
13
13
|
| [Pendulum uncertainty](pendulum_uncertainty.ipynb) | Correlated input uncertainties and Monte Carlo propagation | Mean/std/var units, mixed measurement units, explicit quantiles |
|
|
14
|
+
| [Arrhenius fit](arrhenius_fit.ipynb) | Estimate activation energy from synthetic reaction rates | Absolute temperatures, regression boundary, parameter units and model-based standard errors |
|
|
14
15
|
|
|
15
16
|
## Run
|
|
16
17
|
|
|
@@ -22,8 +23,8 @@ python -m pip install -e '.[numpy]' matplotlib jupyterlab
|
|
|
22
23
|
jupyter lab examples/
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
Open a notebook and run all cells from the beginning.
|
|
26
|
-
|
|
26
|
+
Open a notebook and run all cells from the beginning. Use the current checkout
|
|
27
|
+
so that all demonstrated APIs are available.
|
|
27
28
|
The examples do not download datasets or write files. Keep physical quantities in calculations;
|
|
28
29
|
plot their `.value` and derive labels from their `.unit`. Convert only to choose
|
|
29
30
|
a useful display scale or to align units at a numerical boundary. Quantiles use
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"id": "arrhenius_fit-00",
|
|
6
|
+
"metadata": {},
|
|
7
|
+
"source": [
|
|
8
|
+
"# Estimate an activation energy from reaction-rate data\n",
|
|
9
|
+
"\n",
|
|
10
|
+
"Fit a temperature-dependent first-order rate constant, estimate its molar\n",
|
|
11
|
+
"activation energy, and predict a rate and half-life inside the observed interval.\n",
|
|
12
|
+
"Run the setup in [examples/README.md](README.md) first.\n",
|
|
13
|
+
"\n",
|
|
14
|
+
"The [IUPAC Arrhenius equation](https://goldbook.iupac.org/terms/view/A00446) is\n",
|
|
15
|
+
"$$\n",
|
|
16
|
+
"k(T)=A\\exp\\!\\left(-\\frac{E_a}{RT}\\right).\n",
|
|
17
|
+
"$$\n",
|
|
18
|
+
"Here $T$ is absolute temperature, $R$ is the molar gas constant, $E_a$ is the\n",
|
|
19
|
+
"molar activation energy, and $A$ has the same units as the rate constant $k$.\n",
|
|
20
|
+
"We assume $E_a$ and $A$ remain constant over this temperature interval.\n",
|
|
21
|
+
"\n",
|
|
22
|
+
"These are **synthetic data**, not measurements of a named reaction. They were\n",
|
|
23
|
+
"generated with $E_a=52\\,\\mathrm{kJ/mol}$ and $A=2\\times10^7\\,\\mathrm{s^{-1}}$,\n",
|
|
24
|
+
"with fixed log-rate perturbations $[0.020,-0.025,0.010,-0.015,0.025,-0.015]$,\n",
|
|
25
|
+
"then rounded. The known generating parameters provide a numerical check;\n",
|
|
26
|
+
"they do not validate the model for a real reaction."
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"cell_type": "code",
|
|
31
|
+
"id": "arrhenius_fit-01",
|
|
32
|
+
"metadata": {},
|
|
33
|
+
"source": [
|
|
34
|
+
"import numpy as np\n",
|
|
35
|
+
"from matplotlib import pyplot as plt\n",
|
|
36
|
+
"\n",
|
|
37
|
+
"from unitx import math\n",
|
|
38
|
+
"from unitx.si import SI, si\n",
|
|
39
|
+
"\n",
|
|
40
|
+
"# Recorded Celsius temperatures are points, not temperature differences.\n",
|
|
41
|
+
"temperature_readings = np.asarray([15, 25, 35, 45, 55, 65]) @ si.degC\n",
|
|
42
|
+
"temperature = temperature_readings.to_quantity(si.K)\n",
|
|
43
|
+
"rate = np.asarray([0.458888, 0.908486, 1.858462, 3.430419, 6.499562, 10.972105]) / si.min\n",
|
|
44
|
+
"reference_rate = 1 / si.min"
|
|
45
|
+
],
|
|
46
|
+
"execution_count": null,
|
|
47
|
+
"outputs": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"cell_type": "markdown",
|
|
51
|
+
"id": "arrhenius_fit-02",
|
|
52
|
+
"metadata": {},
|
|
53
|
+
"source": [
|
|
54
|
+
"## Fit at an explicit numerical boundary\n",
|
|
55
|
+
"\n",
|
|
56
|
+
"Normalize the rate before taking its logarithm:\n",
|
|
57
|
+
"$$\n",
|
|
58
|
+
"x=\\frac{1}{T},\\qquad y=\\ln\\!\\left(\\frac{k}{k_{\\rm ref}}\\right),\\qquad\n",
|
|
59
|
+
" y=mx+b.\n",
|
|
60
|
+
"$$\n",
|
|
61
|
+
"Thus $m=-E_a/R$ has unit K, $b=\\ln(A/k_{\\rm ref})$ is dimensionless, and\n",
|
|
62
|
+
"$$\n",
|
|
63
|
+
"E_a=-mR,\\qquad A=k_{\\rm ref}\\exp(b).\n",
|
|
64
|
+
"$$\n",
|
|
65
|
+
"\n",
|
|
66
|
+
"The temperatures have already been converted to K, so `(1 / temperature).value`\n",
|
|
67
|
+
"contains inverse temperatures in K$^{-1}$. `np.polyfit` receives these numerical\n",
|
|
68
|
+
"values and the dimensionless log rates. Attach K to its fitted slope before\n",
|
|
69
|
+
"continuing the physical calculation. Keep the fitted scalars in NumPy's\n",
|
|
70
|
+
"floating-point domain; unitx's NumPy adapter evaluates catalogue constants in\n",
|
|
71
|
+
"that domain without changing `SI.R`."
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"cell_type": "code",
|
|
76
|
+
"id": "arrhenius_fit-03",
|
|
77
|
+
"metadata": {},
|
|
78
|
+
"source": [
|
|
79
|
+
"log_rate = math.log(rate / reference_rate)\n",
|
|
80
|
+
"coefficients, covariance = np.polyfit(\n",
|
|
81
|
+
" (1 / temperature).value,\n",
|
|
82
|
+
" log_rate.value,\n",
|
|
83
|
+
" deg=1,\n",
|
|
84
|
+
" cov=True,\n",
|
|
85
|
+
")\n",
|
|
86
|
+
"slope = np.float64(coefficients[0]) * si.K\n",
|
|
87
|
+
"slope_standard_error = np.sqrt(np.float64(covariance[0, 0])) * slope.unit\n",
|
|
88
|
+
"activation_energy = (-slope * SI.R).to('kJ/mol')\n",
|
|
89
|
+
"energy_standard_error = (slope_standard_error * SI.R).to(activation_energy.unit)\n",
|
|
90
|
+
"prefactor = (reference_rate * math.exp(np.float64(coefficients[1]))).to('s^-1')\n",
|
|
91
|
+
"\n",
|
|
92
|
+
"print(f'Activation energy: {activation_energy:.3f}')\n",
|
|
93
|
+
"print(f'Standard error from residual scatter: {energy_standard_error:.3f}')\n",
|
|
94
|
+
"print(f'Prefactor: {prefactor:.4g}')"
|
|
95
|
+
],
|
|
96
|
+
"execution_count": null,
|
|
97
|
+
"outputs": []
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"cell_type": "markdown",
|
|
101
|
+
"id": "arrhenius_fit-04",
|
|
102
|
+
"metadata": {},
|
|
103
|
+
"source": [
|
|
104
|
+
"## Interpret and plot the fit\n",
|
|
105
|
+
"\n",
|
|
106
|
+
"Ordinary least squares here assumes negligible temperature error and independent\n",
|
|
107
|
+
"log-rate errors with a common variance. The covariance returned by `polyfit`\n",
|
|
108
|
+
"estimates that variance from residual scatter, using $6-2=4$ residual degrees\n",
|
|
109
|
+
"of freedom. Therefore $R\\sqrt{\\operatorname{Cov}_{00}}$ is a model-based standard\n",
|
|
110
|
+
"error for $E_a$, not a confidence interval or a measure of systematic error.\n",
|
|
111
|
+
"The estimates of $A$ and $E_a$ are correlated; no prediction uncertainty band is\n",
|
|
112
|
+
"computed here.\n",
|
|
113
|
+
"\n",
|
|
114
|
+
"Expect an activation energy near $51.9\\,\\mathrm{kJ/mol}$ with standard error\n",
|
|
115
|
+
"near $0.45\\,\\mathrm{kJ/mol}$. Plot a dimensionless inverse-temperature coordinate,\n",
|
|
116
|
+
"$1000\\,\\mathrm{K}/T$. The label is derived from the same scale used in the plot.\n",
|
|
117
|
+
"The curve spans only the observed temperature range."
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"cell_type": "code",
|
|
122
|
+
"id": "arrhenius_fit-05",
|
|
123
|
+
"metadata": {},
|
|
124
|
+
"source": [
|
|
125
|
+
"curve_temperature = (\n",
|
|
126
|
+
" np.linspace(temperature.value.min(), temperature.value.max(), 200, dtype=np.float64) * si.K\n",
|
|
127
|
+
")\n",
|
|
128
|
+
"curve_rate = prefactor * math.exp(-activation_energy / (SI.R * curve_temperature))\n",
|
|
129
|
+
"temperature_scale = 1000 * si.K\n",
|
|
130
|
+
"plot_temperature = temperature_scale / temperature\n",
|
|
131
|
+
"curve_coordinate = temperature_scale / curve_temperature\n",
|
|
132
|
+
"\n",
|
|
133
|
+
"fig, ax = plt.subplots(figsize=(6, 4), layout='constrained')\n",
|
|
134
|
+
"ax.scatter(plot_temperature.value, log_rate.value, label='Synthetic observations')\n",
|
|
135
|
+
"ax.plot(\n",
|
|
136
|
+
" curve_coordinate.value,\n",
|
|
137
|
+
" math.log(curve_rate / reference_rate).value,\n",
|
|
138
|
+
" label='Arrhenius fit',\n",
|
|
139
|
+
")\n",
|
|
140
|
+
"ax.set(\n",
|
|
141
|
+
" xlabel=f'{temperature_scale} / T',\n",
|
|
142
|
+
" ylabel='ln(k / k_ref)',\n",
|
|
143
|
+
" title='Activation-energy fit',\n",
|
|
144
|
+
")\n",
|
|
145
|
+
"ax.legend()\n",
|
|
146
|
+
"ax.grid(True, alpha=0.25)\n",
|
|
147
|
+
"plt.show()"
|
|
148
|
+
],
|
|
149
|
+
"execution_count": null,
|
|
150
|
+
"outputs": []
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"cell_type": "markdown",
|
|
154
|
+
"id": "arrhenius_fit-06",
|
|
155
|
+
"metadata": {},
|
|
156
|
+
"source": [
|
|
157
|
+
"## Predict inside the observed interval\n",
|
|
158
|
+
"\n",
|
|
159
|
+
"At a fixed temperature, a first-order decay obeys $c(t)=c(0)\\exp(-kt)$, hence\n",
|
|
160
|
+
"$$\n",
|
|
161
|
+
"t_{1/2}=\\frac{\\ln 2}{k}.\n",
|
|
162
|
+
"$$\n",
|
|
163
|
+
"Use the fitted parameters to predict at $40\\,{}^\\circ\\mathrm C$, inside the\n",
|
|
164
|
+
"15--65 $^\\circ\\mathrm C$ data interval. Convert the temperature point to a Kelvin\n",
|
|
165
|
+
"quantity before evaluating the exponent. This is a prediction under the stated\n",
|
|
166
|
+
"Arrhenius and first-order models, with no allowance for model error."
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"cell_type": "code",
|
|
171
|
+
"id": "arrhenius_fit-07",
|
|
172
|
+
"metadata": {},
|
|
173
|
+
"source": [
|
|
174
|
+
"prediction_reading = np.float64(40) @ si.degC\n",
|
|
175
|
+
"prediction_temperature = prediction_reading.to_quantity(si.K)\n",
|
|
176
|
+
"predicted_rate = prefactor * math.exp(-activation_energy / (SI.R * prediction_temperature))\n",
|
|
177
|
+
"half_life = (math.log(2) / predicted_rate).to(si.s)\n",
|
|
178
|
+
"\n",
|
|
179
|
+
"print(f'Predicted rate at {prediction_reading}: {predicted_rate.to(rate.unit):.4f}')\n",
|
|
180
|
+
"print(f'First-order half-life: {half_life:.2f}')"
|
|
181
|
+
],
|
|
182
|
+
"execution_count": null,
|
|
183
|
+
"outputs": []
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"cell_type": "markdown",
|
|
187
|
+
"id": "arrhenius_fit-08",
|
|
188
|
+
"metadata": {},
|
|
189
|
+
"source": [
|
|
190
|
+
"## Numerical checks\n",
|
|
191
|
+
"\n",
|
|
192
|
+
"Compare the estimates with the known generating parameters. Also check that\n",
|
|
193
|
+
"converting the input rate storage unit does not alter the normalized fit data,\n",
|
|
194
|
+
"and that the reconstructed physical model agrees with the fitted straight line.\n",
|
|
195
|
+
"The half-life identity checks the prediction's time/rate units together."
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"cell_type": "code",
|
|
200
|
+
"id": "arrhenius_fit-09",
|
|
201
|
+
"metadata": {},
|
|
202
|
+
"source": [
|
|
203
|
+
"assert activation_energy.isclose(52 * si.kJ / si.mol, rel_tol=0.02)\n",
|
|
204
|
+
"assert prefactor.isclose(2e7 / si.s, rel_tol=0.1)\n",
|
|
205
|
+
"assert (predicted_rate * half_life).isclose(math.log(2), rel_tol=1e-12)\n",
|
|
206
|
+
"np.testing.assert_allclose(\n",
|
|
207
|
+
" math.log(rate.to('s^-1') / reference_rate).value,\n",
|
|
208
|
+
" log_rate.value,\n",
|
|
209
|
+
" rtol=1e-12,\n",
|
|
210
|
+
" atol=1e-12,\n",
|
|
211
|
+
")\n",
|
|
212
|
+
"fitted_rate = prefactor * math.exp(-activation_energy / (SI.R * temperature))\n",
|
|
213
|
+
"np.testing.assert_allclose(\n",
|
|
214
|
+
" math.log(fitted_rate / reference_rate).value,\n",
|
|
215
|
+
" coefficients[0] * (1 / temperature).value + coefficients[1],\n",
|
|
216
|
+
" rtol=1e-12,\n",
|
|
217
|
+
" atol=1e-12,\n",
|
|
218
|
+
")"
|
|
219
|
+
],
|
|
220
|
+
"execution_count": null,
|
|
221
|
+
"outputs": []
|
|
222
|
+
}
|
|
223
|
+
],
|
|
224
|
+
"metadata": {
|
|
225
|
+
"kernelspec": {
|
|
226
|
+
"display_name": "Python 3",
|
|
227
|
+
"language": "python",
|
|
228
|
+
"name": "python3"
|
|
229
|
+
},
|
|
230
|
+
"language_info": {
|
|
231
|
+
"name": "python"
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
"nbformat": 4,
|
|
235
|
+
"nbformat_minor": 5
|
|
236
|
+
}
|
|
@@ -4,7 +4,7 @@ build-backend = 'hatchling.build'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'unitx'
|
|
7
|
-
version = '0.13.
|
|
7
|
+
version = '0.13.4'
|
|
8
8
|
description = 'Physical units, quantities, and unit conversion with exact unit algebra, type hints, and NumPy integration.'
|
|
9
9
|
readme = 'README.md'
|
|
10
10
|
requires-python = '>=3.12'
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"src/unitx/_display.py",
|
|
4
4
|
"src/unitx/math.py",
|
|
5
5
|
"src/unitx/time.py",
|
|
6
|
+
"src/unitx/serialization.py",
|
|
6
7
|
"src/unitx/affineunit.py",
|
|
7
8
|
"src/unitx/affinequantity.py",
|
|
8
9
|
"src/unitx/_conversion.py",
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
"tests/typing"
|
|
19
20
|
],
|
|
20
21
|
"strict": [
|
|
22
|
+
"src/unitx/serialization.py",
|
|
21
23
|
"src/unitx/quantity.py",
|
|
22
24
|
"tests/typing",
|
|
23
25
|
"tests/numpy_typing",
|