unitx 0.13.4__tar.gz → 0.14.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.
- unitx-0.14.2/.github/consumers/arrays.py +35 -0
- unitx-0.14.2/.github/consumers/pyrightconfig.json +4 -0
- unitx-0.14.2/.github/consumers/scalars.py +90 -0
- unitx-0.14.2/.github/workflows/ci.yml +141 -0
- {unitx-0.13.4 → unitx-0.14.2}/.github/workflows/release.yml +17 -17
- {unitx-0.13.4 → unitx-0.14.2}/.gitignore +0 -3
- unitx-0.14.2/.readthedocs.yaml +14 -0
- {unitx-0.13.4 → unitx-0.14.2}/CHANGELOG.md +68 -0
- {unitx-0.13.4 → unitx-0.14.2}/CONTRIBUTING.md +33 -1
- {unitx-0.13.4 → unitx-0.14.2}/PKG-INFO +8 -5
- {unitx-0.13.4 → unitx-0.14.2}/README.md +7 -4
- unitx-0.14.2/benchmarks/README.md +30 -0
- unitx-0.14.2/benchmarks/scalars.py +68 -0
- {unitx-0.13.4 → unitx-0.14.2}/docs/README.md +20 -14
- unitx-0.14.2/docs/affine.md +153 -0
- unitx-0.14.2/docs/architecture.md +162 -0
- unitx-0.14.2/docs/catalogues/README.md +69 -0
- unitx-0.14.2/docs/catalogues/imperial.md +103 -0
- unitx-0.13.4/docs/catalogue.md → unitx-0.14.2/docs/catalogues/si.md +13 -12
- unitx-0.14.2/docs/custom-units.md +408 -0
- {unitx-0.13.4 → unitx-0.14.2}/docs/display.md +8 -8
- unitx-0.14.2/docs/javascripts/mermaid.mjs +16 -0
- unitx-0.14.2/docs/logarithmic.md +165 -0
- unitx-0.14.2/docs/migration.md +173 -0
- {unitx-0.13.4 → unitx-0.14.2}/docs/numpy.md +7 -5
- unitx-0.14.2/docs/requirements.txt +1 -0
- {unitx-0.13.4 → unitx-0.14.2}/docs/scalars.md +1 -1
- {unitx-0.13.4 → unitx-0.14.2}/docs/serialization.md +98 -32
- {unitx-0.13.4 → unitx-0.14.2}/docs/typing.md +1 -1
- {unitx-0.13.4 → unitx-0.14.2}/docs/units.md +19 -7
- unitx-0.14.2/mkdocs.yml +41 -0
- {unitx-0.13.4 → unitx-0.14.2}/pyproject.toml +1 -1
- {unitx-0.13.4 → unitx-0.14.2}/pyrightconfig.json +17 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/__init__.py +10 -1
- unitx-0.14.2/src/unitx/_conversion.py +28 -0
- unitx-0.14.2/src/unitx/_coordinates/__init__.py +1 -0
- unitx-0.14.2/src/unitx/_coordinates/affine.py +138 -0
- unitx-0.14.2/src/unitx/_coordinates/definition.py +42 -0
- unitx-0.14.2/src/unitx/_coordinates/logarithmic.py +175 -0
- unitx-0.14.2/src/unitx/_coordinates/logarithms.py +130 -0
- unitx-0.14.2/src/unitx/_coordinates/points.py +151 -0
- unitx-0.14.2/src/unitx/_coordinates/resolve.py +63 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/_display.py +28 -34
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/_numpy.py +73 -84
- unitx-0.14.2/src/unitx/_operands.py +56 -0
- unitx-0.14.2/src/unitx/_registry/__init__.py +1 -0
- unitx-0.14.2/src/unitx/_registry/builtin_units.py +18 -0
- unitx-0.14.2/src/unitx/_registry/prefixes.py +107 -0
- unitx-0.14.2/src/unitx/_registry/reconstruction.py +221 -0
- unitx-0.14.2/src/unitx/_scaling.py +70 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/affinequantity.py +91 -227
- unitx-0.14.2/src/unitx/affineunit.py +343 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/dimension.py +50 -34
- unitx-0.14.2/src/unitx/imperial/__init__.py +8 -0
- unitx-0.14.2/src/unitx/imperial/units.py +347 -0
- unitx-0.14.2/src/unitx/logquantity.py +479 -0
- unitx-0.14.2/src/unitx/logunit.py +361 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/math.py +1 -1
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/quantity.py +90 -86
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/serialization.py +179 -72
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/si/bases.py +3 -1
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/si/units.py +62 -60
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/singleunit.py +39 -27
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/time.py +0 -3
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/unit.py +164 -249
- unitx-0.14.2/src/unitx/unitcatalog.py +69 -0
- unitx-0.14.2/src/unitx/unitdefinition.py +301 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/unitparser.py +16 -7
- unitx-0.14.2/src/unitx/unitregistry.py +275 -0
- unitx-0.14.2/tests/numpy_typing/check_logarithmic.py +22 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/numpy_typing/check_numpy.py +2 -1
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_affine.py +161 -99
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_affinequantity.py +14 -13
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_builtin_definitions.py +12 -9
- unitx-0.14.2/tests/test_catalog_collection.py +198 -0
- unitx-0.14.2/tests/test_definition_scope.py +328 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_dimension.py +3 -2
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_display.py +29 -28
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_examples.py +33 -1
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_immutable.py +8 -10
- unitx-0.14.2/tests/test_imperial.py +118 -0
- unitx-0.14.2/tests/test_linear_boundaries.py +84 -0
- unitx-0.14.2/tests/test_logarithmic_model.py +221 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_math.py +2 -2
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy.py +2 -2
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy_affine.py +24 -20
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy_mutation.py +1 -1
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_quantity.py +12 -12
- unitx-0.14.2/tests/test_registry.py +414 -0
- unitx-0.14.2/tests/test_registry_scope.py +223 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_serialization.py +160 -62
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_singleunit.py +4 -5
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_siunitx_display.py +13 -13
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_time.py +5 -5
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_unicode_units.py +10 -2
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_unit.py +49 -49
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_unit_definition.py +222 -128
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_unitregistry.py +148 -60
- {unitx-0.13.4 → unitx-0.14.2}/tests/typing/check_affine.py +4 -1
- unitx-0.14.2/tests/typing/check_imperial.py +13 -0
- unitx-0.14.2/tests/typing/check_logarithmic.py +25 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/typing/check_quantity.py +25 -25
- unitx-0.14.2/tests/typing/check_registry.py +56 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/typing/check_serialization.py +10 -2
- unitx-0.13.4/.github/workflows/ci.yml +0 -78
- unitx-0.13.4/docs/affine.md +0 -259
- unitx-0.13.4/docs/custom-units.md +0 -156
- unitx-0.13.4/docs/migration.md +0 -78
- unitx-0.13.4/src/unitx/_conversion.py +0 -228
- unitx-0.13.4/src/unitx/affineunit.py +0 -427
- unitx-0.13.4/src/unitx/unitregistry.py +0 -378
- {unitx-0.13.4 → unitx-0.14.2}/LICENSE +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/benchmarks/numpy_writes.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/conftest.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/docs/images/blackbody.svg +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/docs/stdlib.md +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/examples/README.md +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/examples/arrhenius_fit.ipynb +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/examples/blackbody.ipynb +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/examples/damped_oscillator.ipynb +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/examples/gaussian_wavepacket.ipynb +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/examples/optical_attenuation.ipynb +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/examples/pendulum_uncertainty.ipynb +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/_constnamespace.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/_immutable.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/_numerics.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/_numpy_typing.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/_unicode.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/exceptions.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/factor.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/monomial.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/py.typed +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/si/__init__.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/src/unitx/si/constants.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/__init__.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/_notebooks.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/example_typing/check_blackbody.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/numpy_typing/check_math.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_factor.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_import.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_math_numpy.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_monomial.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numeric.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy_differentiation.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy_integration.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy_multi_outputs.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy_outputs.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy_products.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_numpy_statistics.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_si.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_typing.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_unicode.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_unitparser.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/test_warnings.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/typing/check_math_time.py +0 -0
- {unitx-0.13.4 → unitx-0.14.2}/tests/typing/check_si.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Check array runtime and inferred types using the installed wheel."""
|
|
2
|
+
|
|
3
|
+
from typing import assert_type
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
|
|
8
|
+
from unitx import LogQuantity, LogUnit, Quantity, Unit
|
|
9
|
+
from unitx.exceptions import DimensionError
|
|
10
|
+
from unitx.si import si
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def check_arrays(values: NDArray[np.float64]) -> None:
|
|
14
|
+
level = LogQuantity(values, si.dBm)
|
|
15
|
+
assert_type(level, LogQuantity[NDArray[np.float64]])
|
|
16
|
+
assert_type(level.unit, LogUnit)
|
|
17
|
+
assert level.value is values
|
|
18
|
+
np.testing.assert_allclose(level.to_quantity(si.W).value, [0.001, 0.1, 1.0])
|
|
19
|
+
|
|
20
|
+
distance = Quantity(values.copy(), si.m)
|
|
21
|
+
assert_type(distance, Quantity[NDArray[np.float64]])
|
|
22
|
+
assert_type(distance.unit, Unit)
|
|
23
|
+
np.testing.assert_allclose(distance.to(si.cm).value, values * 100)
|
|
24
|
+
saved = distance.value.copy()
|
|
25
|
+
try:
|
|
26
|
+
distance[:] = Quantity(values, si.s)
|
|
27
|
+
except DimensionError:
|
|
28
|
+
pass
|
|
29
|
+
else:
|
|
30
|
+
error_message = 'An incompatible assignment must fail.'
|
|
31
|
+
raise AssertionError(error_message)
|
|
32
|
+
np.testing.assert_array_equal(distance.value, saved)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
check_arrays(np.asarray([0.0, 20.0, 30.0], dtype=np.float64))
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Check the installed wheel before optional NumPy is installed."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pickle
|
|
5
|
+
import sys
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
from fractions import Fraction
|
|
9
|
+
from importlib.metadata import distribution
|
|
10
|
+
from importlib.util import find_spec
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Final, assert_type
|
|
13
|
+
|
|
14
|
+
import unitx
|
|
15
|
+
from unitx import AffineQuantity, LogQuantity, Quantity, Registry, Unit, UnitCatalog, math, time
|
|
16
|
+
from unitx.factor import Factor
|
|
17
|
+
from unitx.imperial import imperial
|
|
18
|
+
from unitx.serialization import from_dict, to_dict
|
|
19
|
+
from unitx.si import si
|
|
20
|
+
|
|
21
|
+
installed = distribution('unitx')
|
|
22
|
+
package_root = Path(str(installed.locate_file('unitx')))
|
|
23
|
+
assert installed.version == os.environ['EXPECTED_VERSION']
|
|
24
|
+
assert Path(unitx.__file__).resolve() == (package_root / '__init__.py').resolve()
|
|
25
|
+
assert (package_root / 'py.typed').is_file()
|
|
26
|
+
assert find_spec('numpy') is None
|
|
27
|
+
|
|
28
|
+
distance = Quantity(Decimal('1.25'), si.m)
|
|
29
|
+
assert_type(distance.to(si.cm), Quantity[Decimal])
|
|
30
|
+
assert distance.value_in(si.cm) == Decimal(125)
|
|
31
|
+
assert Quantity(1, si.cm).value_in(si.m) == Fraction(1, 100)
|
|
32
|
+
assert 2 * Unit(si.m, scale=Factor(3)) == 6 * si.m
|
|
33
|
+
|
|
34
|
+
root = math.sqrt(9 * si.m**2)
|
|
35
|
+
assert_type(root, Quantity[float])
|
|
36
|
+
assert root == 3 * si.m
|
|
37
|
+
assert math.sin(90 * si.deg).value == 1.0
|
|
38
|
+
assert math.hypot(3 * si.m, 400 * si.cm) == 5 * si.m
|
|
39
|
+
|
|
40
|
+
stamp = time.time()
|
|
41
|
+
start = time.perf_counter()
|
|
42
|
+
elapsed = time.perf_counter() - start
|
|
43
|
+
assert_type(stamp, AffineQuantity[int])
|
|
44
|
+
assert_type(start, AffineQuantity[int])
|
|
45
|
+
assert_type(elapsed.unit, Unit)
|
|
46
|
+
assert type(stamp.value) is int and stamp.unit.delta_unit == si.ns
|
|
47
|
+
assert type(elapsed.value) is int and elapsed.unit == si.ns and elapsed.value >= 0
|
|
48
|
+
local_zone = timezone(timedelta(hours=8))
|
|
49
|
+
instant = datetime(2000, 1, 1, 12, 34, 56, 123456, tzinfo=local_zone)
|
|
50
|
+
point = time.from_datetime(instant)
|
|
51
|
+
assert_type(point, AffineQuantity[int])
|
|
52
|
+
assert_type(time.to_datetime(point), datetime)
|
|
53
|
+
utc_instant = time.to_datetime(point)
|
|
54
|
+
local_instant = time.to_datetime(point, tz=local_zone)
|
|
55
|
+
assert utc_instant == instant and utc_instant.tzinfo is timezone.utc
|
|
56
|
+
assert local_instant == instant and local_instant.tzinfo is local_zone
|
|
57
|
+
assert time.to_datetime(AffineQuantity(1500, si.ns)) == datetime(
|
|
58
|
+
1970, 1, 1, 0, 0, 0, 2, tzinfo=timezone.utc
|
|
59
|
+
)
|
|
60
|
+
duration = time.from_timedelta(timedelta(microseconds=123456))
|
|
61
|
+
assert_type(duration, Quantity[int])
|
|
62
|
+
assert duration == 123456 * si.us
|
|
63
|
+
assert time.to_timedelta(Quantity(Decimal('0.0000015'), si.s)) == timedelta(microseconds=2)
|
|
64
|
+
|
|
65
|
+
celsius = 20 @ si.degC
|
|
66
|
+
level = 30 @ si.dBm
|
|
67
|
+
assert_type(celsius, AffineQuantity[int])
|
|
68
|
+
assert_type(level, LogQuantity[int])
|
|
69
|
+
assert celsius.to_quantity(si.K) == Fraction(5863, 20) * si.K
|
|
70
|
+
assert (32 @ imperial.degF).to(si.degC) == 0 @ si.degC
|
|
71
|
+
assert level.to_quantity(si.W) == 1 * si.W
|
|
72
|
+
assert LogQuantity.from_quantity(1 * si.W, si.dBm) == level
|
|
73
|
+
|
|
74
|
+
registry = Registry([si.m])
|
|
75
|
+
with registry.defining():
|
|
76
|
+
span = (2 * si.m).define('installedSpan')
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class laboratory(UnitCatalog, units=(span,)):
|
|
80
|
+
length: Final = span
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
assert_type(laboratory.parse('installedSpan'), Unit)
|
|
84
|
+
assert laboratory.parse('installedSpan') == span
|
|
85
|
+
quantity = 3 * span
|
|
86
|
+
assert from_dict(to_dict(quantity), registry=registry) == quantity
|
|
87
|
+
for value in (quantity, celsius, level):
|
|
88
|
+
assert pickle.loads(pickle.dumps(value)) == value
|
|
89
|
+
assert 'numpy' not in sys.modules
|
|
90
|
+
assert 'sympy' not in sys.modules
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
docs:
|
|
15
|
+
name: Documentation
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v6
|
|
19
|
+
- uses: actions/setup-python@v7
|
|
20
|
+
with:
|
|
21
|
+
python-version: '3.12'
|
|
22
|
+
- name: Install documentation dependencies
|
|
23
|
+
run: python -m pip install -r docs/requirements.txt
|
|
24
|
+
- name: Build documentation with strict link checks
|
|
25
|
+
run: python -m mkdocs build --strict
|
|
26
|
+
|
|
27
|
+
test:
|
|
28
|
+
name: Test Python ${{ matrix.python-version }} (${{ matrix.backend }})
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
|
|
31
|
+
strategy:
|
|
32
|
+
fail-fast: false
|
|
33
|
+
matrix:
|
|
34
|
+
python-version: ["3.12", "3.13", "3.14", "3.15"]
|
|
35
|
+
backend: ["builtin", "sympy"]
|
|
36
|
+
|
|
37
|
+
steps:
|
|
38
|
+
- name: Checkout repository
|
|
39
|
+
uses: actions/checkout@v6
|
|
40
|
+
|
|
41
|
+
- name: Set up Python
|
|
42
|
+
uses: actions/setup-python@v7
|
|
43
|
+
with:
|
|
44
|
+
python-version: ${{ matrix.python-version }}
|
|
45
|
+
cache: pip
|
|
46
|
+
cache-dependency-path: pyproject.toml
|
|
47
|
+
|
|
48
|
+
- name: Install package and test dependencies
|
|
49
|
+
run: |
|
|
50
|
+
python -m pip install --upgrade pip
|
|
51
|
+
python -m pip install -e ".[test]"
|
|
52
|
+
|
|
53
|
+
- name: Install SymPy
|
|
54
|
+
if: matrix.backend == 'sympy'
|
|
55
|
+
run: python -m pip install sympy
|
|
56
|
+
|
|
57
|
+
- name: Run Ruff
|
|
58
|
+
run: |
|
|
59
|
+
ruff check .
|
|
60
|
+
ruff format --check .
|
|
61
|
+
|
|
62
|
+
- name: Run tests
|
|
63
|
+
run: |
|
|
64
|
+
pytest -q
|
|
65
|
+
python -m doctest README.md docs/*.md docs/catalogues/*.md
|
|
66
|
+
|
|
67
|
+
numpy:
|
|
68
|
+
name: NumPy ${{ matrix.numpy }} (Python ${{ matrix.python-version }})
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
strategy:
|
|
71
|
+
fail-fast: false
|
|
72
|
+
matrix:
|
|
73
|
+
include:
|
|
74
|
+
- python-version: '3.12'
|
|
75
|
+
numpy: '2.1.*'
|
|
76
|
+
- python-version: '3.14'
|
|
77
|
+
numpy: 'latest'
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v6
|
|
80
|
+
- uses: actions/setup-python@v7
|
|
81
|
+
with:
|
|
82
|
+
python-version: ${{ matrix.python-version }}
|
|
83
|
+
- name: Install package and dependencies
|
|
84
|
+
run: python -m pip install -e '.[test,numpy]' 'matplotlib>=3.9'
|
|
85
|
+
- name: Select minimum NumPy
|
|
86
|
+
if: matrix.numpy != 'latest'
|
|
87
|
+
run: python -m pip install 'numpy==${{ matrix.numpy }}'
|
|
88
|
+
- name: Run tests and typing
|
|
89
|
+
run: |
|
|
90
|
+
pytest -q
|
|
91
|
+
python -m doctest README.md docs/*.md docs/catalogues/*.md
|
|
92
|
+
|
|
93
|
+
installed:
|
|
94
|
+
name: Installed wheel (Python 3.12)
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@v6
|
|
98
|
+
- uses: actions/setup-python@v7
|
|
99
|
+
with:
|
|
100
|
+
python-version: '3.12'
|
|
101
|
+
|
|
102
|
+
- name: Install build and type-check dependencies
|
|
103
|
+
run: python -m pip install build twine 'pyright>=1.1.414'
|
|
104
|
+
|
|
105
|
+
- name: Build and check distributions
|
|
106
|
+
id: version
|
|
107
|
+
run: |
|
|
108
|
+
python - <<'PY'
|
|
109
|
+
import os
|
|
110
|
+
import tomllib
|
|
111
|
+
from pathlib import Path
|
|
112
|
+
|
|
113
|
+
version = tomllib.loads(Path('pyproject.toml').read_text())['project']['version']
|
|
114
|
+
with Path(os.environ['GITHUB_OUTPUT']).open('a') as output:
|
|
115
|
+
output.write(f'version={version}\n')
|
|
116
|
+
PY
|
|
117
|
+
python -m build
|
|
118
|
+
python -m twine check --strict dist/*
|
|
119
|
+
|
|
120
|
+
- name: Install built wheel and copy consumers outside the checkout
|
|
121
|
+
run: |
|
|
122
|
+
python -m pip install --no-deps dist/*.whl
|
|
123
|
+
mkdir -p "$RUNNER_TEMP/unitx-consumers"
|
|
124
|
+
cp .github/consumers/*.py .github/consumers/pyrightconfig.json "$RUNNER_TEMP/unitx-consumers/"
|
|
125
|
+
|
|
126
|
+
- name: Check installed scalar runtime and types without NumPy
|
|
127
|
+
working-directory: ${{ runner.temp }}/unitx-consumers
|
|
128
|
+
env:
|
|
129
|
+
EXPECTED_VERSION: ${{ steps.version.outputs.version }}
|
|
130
|
+
run: |
|
|
131
|
+
python -I scalars.py
|
|
132
|
+
python -m pyright --project pyrightconfig.json --pythonpath "$(command -v python)" scalars.py
|
|
133
|
+
|
|
134
|
+
- name: Install optional NumPy
|
|
135
|
+
run: python -m pip install 'numpy>=2.1'
|
|
136
|
+
|
|
137
|
+
- name: Check installed NumPy runtime and types
|
|
138
|
+
working-directory: ${{ runner.temp }}/unitx-consumers
|
|
139
|
+
run: |
|
|
140
|
+
python -I arrays.py
|
|
141
|
+
python -m pyright --project pyrightconfig.json --pythonpath "$(command -v python)" arrays.py
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
python -m ruff check .
|
|
55
55
|
python -m ruff format --check .
|
|
56
56
|
python -m pytest -q
|
|
57
|
-
python -m doctest README.md docs/*.md
|
|
57
|
+
python -m doctest README.md docs/*.md docs/catalogues/*.md
|
|
58
58
|
|
|
59
59
|
- name: Build wheel and source distribution
|
|
60
60
|
run: python -m build
|
|
@@ -62,28 +62,28 @@ jobs:
|
|
|
62
62
|
- name: Check distribution metadata
|
|
63
63
|
run: python -m twine check --strict dist/*
|
|
64
64
|
|
|
65
|
-
- name: Install built wheel
|
|
66
|
-
run:
|
|
65
|
+
- name: Install built wheel and copy consumers outside the checkout
|
|
66
|
+
run: |
|
|
67
|
+
python -m pip install --force-reinstall --no-deps dist/*.whl
|
|
68
|
+
mkdir -p "$RUNNER_TEMP/unitx-consumers"
|
|
69
|
+
cp .github/consumers/*.py .github/consumers/pyrightconfig.json "$RUNNER_TEMP/unitx-consumers/"
|
|
67
70
|
|
|
68
|
-
- name: Check installed
|
|
69
|
-
working-directory: ${{ runner.temp }}
|
|
71
|
+
- name: Check installed scalar runtime and types without NumPy
|
|
72
|
+
working-directory: ${{ runner.temp }}/unitx-consumers
|
|
70
73
|
env:
|
|
71
74
|
EXPECTED_VERSION: ${{ steps.version.outputs.version }}
|
|
72
75
|
run: |
|
|
73
|
-
python -
|
|
74
|
-
|
|
75
|
-
from fractions import Fraction
|
|
76
|
-
from importlib.metadata import version
|
|
77
|
-
from importlib.resources import files
|
|
76
|
+
python -I scalars.py
|
|
77
|
+
python -m pyright --project pyrightconfig.json --pythonpath "$(command -v python)" scalars.py
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
- name: Install optional NumPy
|
|
80
|
+
run: python -m pip install 'numpy>=2.1'
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
- name: Check installed NumPy runtime and types
|
|
83
|
+
working-directory: ${{ runner.temp }}/unitx-consumers
|
|
84
|
+
run: |
|
|
85
|
+
python -I arrays.py
|
|
86
|
+
python -m pyright --project pyrightconfig.json --pythonpath "$(command -v python)" arrays.py
|
|
87
87
|
|
|
88
88
|
- name: Store distributions
|
|
89
89
|
uses: actions/upload-artifact@v5
|
|
@@ -2,6 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.14.2] - 2026-10-24
|
|
6
|
+
|
|
7
|
+
### Documentation
|
|
8
|
+
|
|
9
|
+
- Add a MkDocs site with pinned build requirements, Mermaid diagrams,
|
|
10
|
+
Read the Docs configuration and strict CI checks for internal links and anchors.
|
|
11
|
+
Hosting can use an available project name; no documentation domain is assumed.
|
|
12
|
+
- Fix the README's logarithmic-guide link for package-index rendering.
|
|
13
|
+
|
|
14
|
+
### Internal
|
|
15
|
+
|
|
16
|
+
- Execute the README's Python code blocks directly and check the blackbody
|
|
17
|
+
plot's units, curve count and numerical data.
|
|
18
|
+
- Extend installed-wheel runtime and type consumers with mathematical functions,
|
|
19
|
+
integer clock readings, duration arithmetic and datetime precision checks.
|
|
20
|
+
- Add reproducible scalar benchmarks for parsing, conversion, comparison and
|
|
21
|
+
exact factors, recording the environment and timing parameters as JSON.
|
|
22
|
+
|
|
23
|
+
## [0.14.1] - 2026-10-24
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
- Clarify concurrent Registry reads, application synchronization during
|
|
28
|
+
registration, the absence of a freeze/snapshot API, and custom-definition
|
|
29
|
+
sharing within and across processes.
|
|
30
|
+
|
|
31
|
+
## [0.14.0] - 2026-10-24
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- `Registry` provides independent parsing environments, atomic `include(*units)`
|
|
36
|
+
and explicit `with registry.defining():` blocks with configurable prefix defaults.
|
|
37
|
+
`UnitCatalog` collects read-only unit attributes and provides catalogue parsing.
|
|
38
|
+
- `unitx.imperial` supplies British Imperial, historical English and US customary
|
|
39
|
+
catalogues with independent spellings and exact documented scales, including
|
|
40
|
+
US survey feet, thermochemical Btu, circular mil and conventional water/mercury
|
|
41
|
+
pressure units. Importing a catalogue does not change default parsing.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- **Breaking:** `Unit` now stores its complete exact extra scale; `ScaledUnit` is
|
|
46
|
+
removed. Construct scaled units with `Unit(base, scale=...)` or Factor operands.
|
|
47
|
+
- **Breaking:** `.define()` creates standalone definitions outside an explicit
|
|
48
|
+
Registry block. Extend default parsing explicitly with `default_registry`.
|
|
49
|
+
Formal identity follows shared definition objects rather than symbols;
|
|
50
|
+
independent same-name definitions remain distinct. Import `UnitDefinition`
|
|
51
|
+
and `PrefixPolicy` from `unitx.unitdefinition`; registry table exports are removed.
|
|
52
|
+
- **Breaking:** `LogUnit` and `LogQuantity` replace logarithmic uses of AffineUnit
|
|
53
|
+
and AffineQuantity. Point `.to()` stays within its coordinate family; use
|
|
54
|
+
`.to_quantity()` and `LogQuantity.from_quantity()` at physical boundaries.
|
|
55
|
+
- **Breaking:** Dimension equality includes the internal logarithmic axis.
|
|
56
|
+
Use `.physical` for seven-axis physical comparisons; separate `log_degree`
|
|
57
|
+
attributes are removed.
|
|
58
|
+
- **Breaking:** Fahrenheit and Rankine move from SI/default parsing to
|
|
59
|
+
`imperial.degF` and `imperial.degR`.
|
|
60
|
+
- **Breaking:** Scalar JSON uses format 2 with definition IDs and an optional
|
|
61
|
+
receiving Registry; format 1 and older pickle payloads are unsupported.
|
|
62
|
+
Reconstruction requires existing definitions and checks their metadata.
|
|
63
|
+
- Logarithmic coordinates retain exact `reference` and `at` anchors; `origin`
|
|
64
|
+
is a derived numerical view. Affine defining readings that require an
|
|
65
|
+
unrepresentable symbolic origin sum are rejected; supply the origin at `at=0`.
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- Preserve private coordinate mappings through retained linear projections,
|
|
70
|
+
and reconstruct private coordinate/point reprs even when their symbols coincide
|
|
71
|
+
with default logarithmic difference units.
|
|
72
|
+
|
|
5
73
|
## [0.13.4] - 2026-10-23
|
|
6
74
|
|
|
7
75
|
### Fixed
|
|
@@ -23,11 +23,16 @@ Pytest collects runtime tests and source doctests. `tests/test_typing.py` runs
|
|
|
23
23
|
Pyright against scalar and optional NumPy consumers, including scalar checks
|
|
24
24
|
with NumPy absent. The [typing guide](docs/typing.md) explains the boundaries.
|
|
25
25
|
CI covers Python 3.12–3.15, built-in/SymPy factorization and minimum/current NumPy.
|
|
26
|
+
An additional CI job builds the distributions and checks their wheel with the
|
|
27
|
+
consumers in `.github/consumers/`. It copies those scripts and their independent
|
|
28
|
+
Pyright configuration outside the checkout, first checking scalar runtime and
|
|
29
|
+
types without NumPy, then installing NumPy and checking array runtime and types.
|
|
30
|
+
The release workflow runs the same consumers before uploading distributions.
|
|
26
31
|
|
|
27
32
|
Run the executable Markdown examples as well:
|
|
28
33
|
|
|
29
34
|
```sh
|
|
30
|
-
python -m doctest README.md docs/*.md
|
|
35
|
+
python -m doctest README.md docs/*.md docs/catalogues/*.md
|
|
31
36
|
```
|
|
32
37
|
|
|
33
38
|
Use tracked public guides for release validation; local ignored design notes
|
|
@@ -35,6 +40,33 @@ are not release documentation. Documentation examples should be reproducible
|
|
|
35
40
|
and avoid modifying source files. Notebook source is checked and executed by
|
|
36
41
|
the tests when its optional dependencies are present; see the
|
|
37
42
|
[notebook policy](examples/README.md#keep-git-history-small).
|
|
43
|
+
With NumPy and Matplotlib installed, the tests also execute the README's
|
|
44
|
+
`python` code blocks in order and check the blackbody plot's numerical data.
|
|
45
|
+
|
|
46
|
+
## Documentation site
|
|
47
|
+
|
|
48
|
+
Build or preview the existing Markdown guides with MkDocs:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
python -m pip install -r docs/requirements.txt
|
|
52
|
+
python -m mkdocs build --strict
|
|
53
|
+
python -m mkdocs serve
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The build writes to the ignored `site/` directory. CI checks the strict build,
|
|
57
|
+
including internal links and anchors. `.readthedocs.yaml` uses the same pinned
|
|
58
|
+
MkDocs requirement; connect the repository to a Read the Docs project to host
|
|
59
|
+
it. These configuration files alone do not create or publish a hosted site.
|
|
60
|
+
Mermaid diagrams load a pinned JavaScript module from jsDelivr in the browser;
|
|
61
|
+
building the HTML does not require that CDN. Repository files outside `docs/`
|
|
62
|
+
use GitHub links so they also work on a hosted documentation site.
|
|
63
|
+
|
|
64
|
+
## Performance measurements
|
|
65
|
+
|
|
66
|
+
Run the [scalar and NumPy benchmarks](benchmarks/README.md) when investigating
|
|
67
|
+
performance changes. Their JSON reports record the environment and per-call
|
|
68
|
+
timings; compare runs under the same conditions rather than setting CI timing
|
|
69
|
+
thresholds.
|
|
38
70
|
|
|
39
71
|
## Keep changes reviewable
|
|
40
72
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.2
|
|
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
|
|
@@ -102,13 +102,14 @@ Use `.isclose()` for approximate comparisons.
|
|
|
102
102
|
Use `@` for an absolute coordinate reading and `*` for a linear difference:
|
|
103
103
|
|
|
104
104
|
```pycon
|
|
105
|
+
>>> from unitx.imperial import imperial
|
|
105
106
|
>>> difference = 20 * si.degC # A temperature difference of 20 degrees
|
|
106
107
|
>>> room = 20 @ si.degC # A reading of 20 degrees Celsius
|
|
107
108
|
>>> difference.value_in(si.K)
|
|
108
109
|
20
|
|
109
110
|
>>> room.value_in(si.K)
|
|
110
111
|
Fraction(5863, 20)
|
|
111
|
-
>>> print(room.to(
|
|
112
|
+
>>> print(room.to(imperial.degF))
|
|
112
113
|
68 °F
|
|
113
114
|
>>> print(room + 5 * si.K)
|
|
114
115
|
25 °C
|
|
@@ -119,13 +120,15 @@ True
|
|
|
119
120
|
|
|
120
121
|
```
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
Linear affine points are `AffineQuantity` objects and logarithmic readings are
|
|
124
|
+
`LogQuantity` objects; differences are `Quantity` objects.
|
|
123
125
|
Multiplying by `si.degC` constructs a difference; use `@` or the explicit
|
|
124
126
|
`AffineQuantity(20, si.degC)` constructor for an absolute reading.
|
|
125
127
|
Adding two points raises `TypeError`; subtract them to obtain a difference.
|
|
126
128
|
Use `.to_quantity()` to turn a point into an ordinary physical quantity for
|
|
127
129
|
further arithmetic. Read the [point and difference guide](https://github.com/DaitTHU/unitx/blob/main/docs/affine.md)
|
|
128
|
-
for
|
|
130
|
+
for temperatures and [logarithmic quantities](https://github.com/DaitTHU/unitx/blob/main/docs/logarithmic.md) for
|
|
131
|
+
dBm/dBW/dBV and ratio rules.
|
|
129
132
|
|
|
130
133
|
## Mathematical functions and time
|
|
131
134
|
|
|
@@ -240,7 +243,7 @@ or go directly to a topic:
|
|
|
240
243
|
| Topic | Guide |
|
|
241
244
|
| --- | --- |
|
|
242
245
|
| Construction, arithmetic, conversion and unit expressions | [Units and quantities](https://github.com/DaitTHU/unitx/blob/main/docs/units.md) |
|
|
243
|
-
| Ready-to-use units, constants and spelling conventions | [Catalogue](https://github.com/DaitTHU/unitx/blob/main/docs/
|
|
246
|
+
| Ready-to-use units, constants and spelling conventions | [Catalogue](https://github.com/DaitTHU/unitx/blob/main/docs/catalogues/README.md) |
|
|
244
247
|
| Exact extra scales and process-local registration | [Custom units](https://github.com/DaitTHU/unitx/blob/main/docs/custom-units.md) |
|
|
245
248
|
| Fraction, Decimal, equality, powers and tolerances | [Scalar numerical behavior](https://github.com/DaitTHU/unitx/blob/main/docs/scalars.md) |
|
|
246
249
|
| Notebook, text, LaTeX, HTML and siunitx output | [Display and formatting](https://github.com/DaitTHU/unitx/blob/main/docs/display.md) |
|
|
@@ -74,13 +74,14 @@ Use `.isclose()` for approximate comparisons.
|
|
|
74
74
|
Use `@` for an absolute coordinate reading and `*` for a linear difference:
|
|
75
75
|
|
|
76
76
|
```pycon
|
|
77
|
+
>>> from unitx.imperial import imperial
|
|
77
78
|
>>> difference = 20 * si.degC # A temperature difference of 20 degrees
|
|
78
79
|
>>> room = 20 @ si.degC # A reading of 20 degrees Celsius
|
|
79
80
|
>>> difference.value_in(si.K)
|
|
80
81
|
20
|
|
81
82
|
>>> room.value_in(si.K)
|
|
82
83
|
Fraction(5863, 20)
|
|
83
|
-
>>> print(room.to(
|
|
84
|
+
>>> print(room.to(imperial.degF))
|
|
84
85
|
68 °F
|
|
85
86
|
>>> print(room + 5 * si.K)
|
|
86
87
|
25 °C
|
|
@@ -91,13 +92,15 @@ True
|
|
|
91
92
|
|
|
92
93
|
```
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
Linear affine points are `AffineQuantity` objects and logarithmic readings are
|
|
96
|
+
`LogQuantity` objects; differences are `Quantity` objects.
|
|
95
97
|
Multiplying by `si.degC` constructs a difference; use `@` or the explicit
|
|
96
98
|
`AffineQuantity(20, si.degC)` constructor for an absolute reading.
|
|
97
99
|
Adding two points raises `TypeError`; subtract them to obtain a difference.
|
|
98
100
|
Use `.to_quantity()` to turn a point into an ordinary physical quantity for
|
|
99
101
|
further arithmetic. Read the [point and difference guide](https://github.com/DaitTHU/unitx/blob/main/docs/affine.md)
|
|
100
|
-
for
|
|
102
|
+
for temperatures and [logarithmic quantities](https://github.com/DaitTHU/unitx/blob/main/docs/logarithmic.md) for
|
|
103
|
+
dBm/dBW/dBV and ratio rules.
|
|
101
104
|
|
|
102
105
|
## Mathematical functions and time
|
|
103
106
|
|
|
@@ -212,7 +215,7 @@ or go directly to a topic:
|
|
|
212
215
|
| Topic | Guide |
|
|
213
216
|
| --- | --- |
|
|
214
217
|
| Construction, arithmetic, conversion and unit expressions | [Units and quantities](https://github.com/DaitTHU/unitx/blob/main/docs/units.md) |
|
|
215
|
-
| Ready-to-use units, constants and spelling conventions | [Catalogue](https://github.com/DaitTHU/unitx/blob/main/docs/
|
|
218
|
+
| Ready-to-use units, constants and spelling conventions | [Catalogue](https://github.com/DaitTHU/unitx/blob/main/docs/catalogues/README.md) |
|
|
216
219
|
| Exact extra scales and process-local registration | [Custom units](https://github.com/DaitTHU/unitx/blob/main/docs/custom-units.md) |
|
|
217
220
|
| Fraction, Decimal, equality, powers and tolerances | [Scalar numerical behavior](https://github.com/DaitTHU/unitx/blob/main/docs/scalars.md) |
|
|
218
221
|
| Notebook, text, LaTeX, HTML and siunitx output | [Display and formatting](https://github.com/DaitTHU/unitx/blob/main/docs/display.md) |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Performance measurements
|
|
2
|
+
|
|
3
|
+
Install the checkout in the environment to be measured, then run from the
|
|
4
|
+
repository root:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
python -m pip install -e .
|
|
8
|
+
python benchmarks/scalars.py --repeat 7 --number 1000
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The scalar script measures fresh expression parsing, Fraction/Decimal
|
|
12
|
+
conversion, cross-unit equality and ordering, factor construction, rational
|
|
13
|
+
powers and symbolic cancellation. Input quantities and factors are prepared
|
|
14
|
+
outside the timed operations; each parsing call constructs a new parser.
|
|
15
|
+
The JSON output records median seconds per call, Python/platform, the selected
|
|
16
|
+
factorization backend, Decimal precision and timing parameters. SymPy is used
|
|
17
|
+
automatically when installed; compare backends in separate environments.
|
|
18
|
+
|
|
19
|
+
Array write measurements require the optional NumPy dependency:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
python -m pip install -e '.[numpy]'
|
|
23
|
+
python benchmarks/numpy_writes.py --size 1000000 --repeat 7 --number 20
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That script also reports peak traced allocations for each operation. Timing
|
|
27
|
+
and allocation tracing are separate measurements. Run baseline and candidate
|
|
28
|
+
with the same interpreter, dependencies, arguments and machine conditions.
|
|
29
|
+
These are development measurements, not performance guarantees; CI does not
|
|
30
|
+
enforce timing thresholds.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Time scalar parsing, conversion, comparison and exact factor operations.
|
|
2
|
+
|
|
3
|
+
Run from an installed checkout: python benchmarks/scalars.py
|
|
4
|
+
Each parse creates a new UnitParser; conversion inputs are prepared beforehand.
|
|
5
|
+
Results describe this process and machine, not a cross-machine performance claim.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import platform
|
|
11
|
+
import statistics
|
|
12
|
+
import timeit
|
|
13
|
+
from decimal import Decimal, getcontext
|
|
14
|
+
from fractions import Fraction
|
|
15
|
+
|
|
16
|
+
from unitx import Quantity, Unit, pi
|
|
17
|
+
from unitx.factor import Factor, factorint
|
|
18
|
+
from unitx.si import si
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
23
|
+
parser.add_argument('--repeat', type=int, default=7)
|
|
24
|
+
parser.add_argument('--number', type=int, default=1000)
|
|
25
|
+
options = parser.parse_args()
|
|
26
|
+
if options.repeat < 1 or options.number < 1:
|
|
27
|
+
parser.error('--repeat and --number must be positive integers.')
|
|
28
|
+
|
|
29
|
+
rational = Quantity(Fraction(1, 3), si.m)
|
|
30
|
+
decimal = Quantity(Decimal('1.25'), si.m)
|
|
31
|
+
equivalent = Quantity(Fraction(100, 3), si.cm)
|
|
32
|
+
greater = Quantity(Fraction(101, 3), si.cm)
|
|
33
|
+
base = Factor(2) * pi
|
|
34
|
+
radical = base ** Fraction(1, 3)
|
|
35
|
+
cases = {
|
|
36
|
+
'parse_expression': lambda: Unit('kg m2/s3'),
|
|
37
|
+
'convert_fraction': lambda: rational.value_in(si.cm),
|
|
38
|
+
'convert_decimal': lambda: decimal.value_in(si.cm),
|
|
39
|
+
'equal_cross_unit': lambda: rational == equivalent,
|
|
40
|
+
'order_cross_unit': lambda: rational < greater,
|
|
41
|
+
'factor_construct': lambda: Factor(Fraction(123456, 1001)),
|
|
42
|
+
'factor_rational_power': lambda: base ** Fraction(1, 3),
|
|
43
|
+
'factor_cancel': lambda: radical**3 / base,
|
|
44
|
+
}
|
|
45
|
+
results = {}
|
|
46
|
+
for name, operation in cases.items():
|
|
47
|
+
operation()
|
|
48
|
+
samples = timeit.repeat(operation, repeat=options.repeat, number=options.number)
|
|
49
|
+
results[name] = {'seconds': statistics.median(samples) / options.number}
|
|
50
|
+
backend = 'sympy' if factorint.__module__.startswith('sympy') else 'builtin'
|
|
51
|
+
print(
|
|
52
|
+
json.dumps(
|
|
53
|
+
{
|
|
54
|
+
'python': platform.python_version(),
|
|
55
|
+
'platform': platform.platform(),
|
|
56
|
+
'factorization_backend': backend,
|
|
57
|
+
'decimal_precision': getcontext().prec,
|
|
58
|
+
'repeat': options.repeat,
|
|
59
|
+
'number': options.number,
|
|
60
|
+
'cases': results,
|
|
61
|
+
},
|
|
62
|
+
indent=2,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == '__main__':
|
|
68
|
+
main()
|