pax_global_header00006660000000000000000000000064147131647400014521gustar00rootroot0000000000000052 comment=b6a6f68faf55bc4635c1cc1bcd567858ce8c418d pint-0.24.4/000077500000000000000000000000001471316474000125625ustar00rootroot00000000000000pint-0.24.4/.coveragerc000066400000000000000000000010011471316474000146730ustar00rootroot00000000000000[run] omit = pint/testsuite/* pint/_vendor/* [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about missing debug-only code: def __repr__ # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError AbstractMethodError # Don't complain if non-runnable code isn't run: if TYPE_CHECKING: return NotImplemented pint-0.24.4/.editorconfig000066400000000000000000000001661471316474000152420ustar00rootroot00000000000000root = true [*.py] charset = utf-8 indent_style = space indent_size = 4 insert_final_newline = true end_of_line = lf pint-0.24.4/.gitattributes000066400000000000000000000000241471316474000154510ustar00rootroot00000000000000CHANGES merge=union pint-0.24.4/.github/000077500000000000000000000000001471316474000141225ustar00rootroot00000000000000pint-0.24.4/.github/pull_request_template.md000066400000000000000000000003541471316474000210650ustar00rootroot00000000000000- [ ] Closes # (insert issue number) - [ ] Executed `pre-commit run --all-files` with no errors - [ ] The change is fully covered by automated unit tests - [ ] Documented in docs/ as appropriate - [ ] Added an entry to the CHANGES file pint-0.24.4/.github/workflows/000077500000000000000000000000001471316474000161575ustar00rootroot00000000000000pint-0.24.4/.github/workflows/bench.yml000066400000000000000000000013461471316474000177650ustar00rootroot00000000000000name: codspeed-benchmarks on: push: branches: - "master" - "develop" pull_request: # `workflow_dispatch` allows CodSpeed to trigger backtest # performance analysis in order to generate initial data. workflow_dispatch: jobs: benchmarks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v3 with: python-version: "3.12" - name: Install dependencies run: pip install "numpy>=1.23,<2.0.0" - name: Install bench dependencies run: pip install .[bench] - name: Run benchmarks uses: CodSpeedHQ/action@v1 with: token: ${{ secrets.CODSPEED_TOKEN }} run: pytest . --codspeed pint-0.24.4/.github/workflows/ci.yml000066400000000000000000000154221471316474000173010ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: test-linux: strategy: fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] numpy: [null, "numpy>=1.23,<2.0.0", "numpy>=2.0.0rc1"] uncertainties: [null, "uncertainties==3.1.6", "uncertainties>=3.1.6,<4.0.0"] extras: [null] include: - python-version: "3.10" # Minimal versions numpy: "numpy>=1.23,<2.0.0" extras: matplotlib==3.5.3 - python-version: "3.10" numpy: "numpy" uncertainties: "uncertainties" extras: "sparse xarray netCDF4 dask[complete]==2024.5.1 graphviz babel==2.8 mip>=1.13" - python-version: "3.10" numpy: "numpy==1.26.1" uncertainties: null extras: "babel==2.15 matplotlib==3.9.0" runs-on: ubuntu-latest env: TEST_OPTS: "-rfsxEX -s --cov=pint --cov-config=.coveragerc --benchmark-skip" steps: - uses: actions/checkout@v2 with: fetch-depth: 100 - name: Get tags run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Get pip cache dir id: pip-cache run: echo "::set-output name=dir::$(pip cache dir)" - name: Setup caching uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-${{ matrix.python-version }} restore-keys: | pip-${{ matrix.python-version }} - name: Install numpy if: ${{ matrix.numpy != null }} run: pip install "${{matrix.numpy}}" - name: Install uncertainties if: ${{ matrix.uncertainties != null }} run: pip install "${{matrix.uncertainties}}" - name: Install extras if: ${{ matrix.extras != null }} run: pip install ${{matrix.extras}} - name: Install locales if: ${{ matrix.extras != null }} run: | sudo apt-get install language-pack-es language-pack-fr language-pack-ro sudo localedef -i es_ES -f UTF-8 es_ES sudo localedef -i fr_FR -f UTF-8 fr_FR sudo localedef -i ro_RO -f UTF-8 ro_RO - name: Install dependencies run: | sudo apt install -y graphviz pip install packaging pip install .[testbase] - name: Install pytest-mpl if: contains(matrix.extras, 'matplotlib') run: pip install pytest-mpl - name: Run Tests run: | pytest $TEST_OPTS # - name: Coverage report # run: coverage report -m # - name: Coveralls Parallel # env: # COVERALLS_FLAG_NAME: ${{ matrix.test-number }} # COVERALLS_PARALLEL: true # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # COVERALLS_SERVICE_NAME: github # run: | # pip install coveralls "requests<2.29" # coveralls test-windows: strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12"] numpy: [ "numpy>=1.23,<2.0.0" ] runs-on: windows-latest env: TEST_OPTS: "-rfsxEX -s -k issue1498b --benchmark-skip" steps: - uses: actions/checkout@v2 with: fetch-depth: 100 - name: Get tags run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Get pip cache dir id: pip-cache run: echo "::set-output name=dir::$(pip cache dir)" - name: Setup caching uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-windows-${{ matrix.python-version }} restore-keys: | pip-windows-${{ matrix.python-version }} - name: Install numpy if: ${{ matrix.numpy != null }} run: pip install "${{matrix.numpy}}" # - name: Install uncertainties # if: ${{ matrix.uncertainties != null }} # run: pip install "${{matrix.uncertainties}}" # # - name: Install extras # if: ${{ matrix.extras != null }} # run: pip install ${{matrix.extras}} - name: Install dependencies run: | # sudo apt install -y graphviz pip install packaging pip install .[testbase] # - name: Install pytest-mpl # if: contains(matrix.extras, 'matplotlib') # run: pip install pytest-mpl - name: Run tests run: pytest -rfsxEX -s -k issue1498b --benchmark-skip test-macos: strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12"] numpy: [null, "numpy>=1.23,<2.0.0" ] runs-on: macos-latest env: TEST_OPTS: "-rfsxEX -s --cov=pint --cov-config=.coveragerc --benchmark-skip" steps: - uses: actions/checkout@v2 with: fetch-depth: 100 - name: Get tags run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Get pip cache dir id: pip-cache run: echo "::set-output name=dir::$(pip cache dir)" - name: Setup caching uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-${{ matrix.python-version }} restore-keys: | pip-${{ matrix.python-version }} - name: Install numpy if: ${{ matrix.numpy != null }} run: pip install "${{matrix.numpy}}" - name: Install dependencies run: | pip install packaging pip install .[testbase] - name: Run Tests run: | pytest $TEST_OPTS # - name: Coverage report # run: coverage report -m # - name: Coveralls Parallel # env: # COVERALLS_FLAG_NAME: ${{ matrix.test-number }} # COVERALLS_PARALLEL: true # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # COVERALLS_SERVICE_NAME: github # run: | # pip install coveralls "requests<2.29" # coveralls # coveralls: # needs: test-linux # runs-on: ubuntu-latest # steps: # - uses: actions/setup-python@v2 # with: # python-version: 3.x # - name: Coveralls Finished # continue-on-error: true # env: # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # COVERALLS_SERVICE_NAME: github # run: | # pip install coveralls "requests<2.29" # coveralls --finish pint-0.24.4/.github/workflows/docs.yml000066400000000000000000000025321471316474000176340ustar00rootroot00000000000000name: Documentation Build on: [push, pull_request] jobs: docbuild: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 100 - name: Get tags run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up minimal Python version uses: actions/setup-python@v2 with: python-version: "3.10" - name: Get pip cache dir id: pip-cache run: echo "::set-output name=dir::$(pip cache dir)" - name: Setup pip cache uses: actions/cache@v2 with: path: ${{ steps.pip-cache.outputs.dir }} key: pip-docs restore-keys: pip-docs - name: Install locales run: | sudo apt-get install language-pack-fr sudo localedef -i fr_FR -f UTF-8 fr_FR - name: Install dependencies run: | sudo apt install -y pandoc pip install --upgrade pip setuptools wheel pip install -r "requirements_docs.txt" pip install docutils==0.14 commonmark==0.8.1 recommonmark==0.5.0 babel==2.8 pip install . - name: Build documentation run: sphinx-build -n -j auto -b html -d build/doctrees docs build/html - name: Doc Tests run: sphinx-build -a -j auto -b doctest -d build/doctrees docs build/doctest pint-0.24.4/.github/workflows/lint-autoupdate.yml000066400000000000000000000026741471316474000220320ustar00rootroot00000000000000name: pre-commit on: schedule: - cron: "0 0 * * 0" # every Sunday at 00:00 UTC workflow_dispatch: jobs: autoupdate: name: autoupdate runs-on: ubuntu-latest if: github.repository == 'hgrecco/pint' steps: - name: checkout uses: actions/checkout@v2 - name: Cache pip and pre-commit uses: actions/cache@v2 with: path: | ~/.cache/pre-commit ~/.cache/pip key: ${{ runner.os }}-pre-commit-autoupdate - name: setup python uses: actions/setup-python@v2 with: python-version: 3.x - name: upgrade pip run: python -m pip install --upgrade pip - name: install dependencies run: python -m pip install --upgrade pre-commit - name: version info run: python -m pip list - name: autoupdate uses: technote-space/create-pr-action@bfd4392c80dbeb54e0bacbcf4750540aecae6ed4 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} EXECUTE_COMMANDS: | python -m pre_commit autoupdate python -m pre_commit run --all-files COMMIT_MESSAGE: 'pre-commit: autoupdate hook versions' COMMIT_NAME: 'github-actions[bot]' COMMIT_EMAIL: 'github-actions[bot]@users.noreply.github.com' PR_TITLE: 'pre-commit: autoupdate hook versions' PR_BRANCH_PREFIX: 'pre-commit/' PR_BRANCH_NAME: 'autoupdate-${PR_ID}' pint-0.24.4/.github/workflows/lint.yml000066400000000000000000000005201471316474000176450ustar00rootroot00000000000000name: Lint on: [push, pull_request] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: 3.x - name: Lint uses: pre-commit/action@v2.0.0 with: extra_args: --all-files --show-diff-on-failure pint-0.24.4/.github/workflows/publish.yml000066400000000000000000000007651471316474000203600ustar00rootroot00000000000000name: Build and publish to PyPI on: push: tags: - '*' jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies run: python -m pip install build - name: Build package run: python -m build - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} pint-0.24.4/.gitignore000066400000000000000000000007341471316474000145560ustar00rootroot00000000000000*~ __pycache__ *egg-info* *.pyc .DS_Store docs/_build/ .idea .vscode build/ dist/ MANIFEST *pytest_cache* .eggs .mypy_cache pip-wheel-metadata pint/testsuite/dask-worker-space venv .envrc # WebDAV file system cache files .DAV/ # tags files (from ctags) tags test/ .coverage* # notebook stuff *.ipynb_checkpoints* # test csv which should be user generated notebooks/pandas_test.csv # dask stuff dask-worker-space # airspeed velocity bechmark .asv/ benchmarks/hashes.txt pint-0.24.4/.pre-commit-config.yaml000066400000000000000000000014621471316474000170460ustar00rootroot00000000000000exclude: '^pint/_vendor' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.7 hooks: - id: ruff args: ["--fix", "--show-fixes"] types_or: [ python, pyi, jupyter ] - id: ruff-format types_or: [ python, pyi, jupyter ] - repo: https://github.com/executablebooks/mdformat rev: 0.7.17 hooks: - id: mdformat additional_dependencies: - mdformat-gfm # GitHub-flavored Markdown - mdformat-black - repo: https://github.com/kynan/nbstripout rev: 0.6.1 hooks: - id: nbstripout args: [--extra-keys=metadata.kernelspec metadata.language_info.version] pint-0.24.4/.readthedocs.yaml000066400000000000000000000003411471316474000160070ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.11" sphinx: configuration: docs/conf.py fail_on_warning: false python: install: - requirements: requirements_docs.txt - method: pip path: . pint-0.24.4/AUTHORS000066400000000000000000000040401471316474000136300ustar00rootroot00000000000000Pint was originally written by Hernan E. Grecco . and is currently maintained, listed alphabetically, by: * Jules Chéron * Hernan E. Grecco . Other contributors, listed alphabetically, are: * Aaron Coleman * Alexander Böhn * Ana Krivokapic * Andrea Zonca * Andrew Savage * Brend Wanders * choloepus * coutinho * Clément Pit-Claudel * Daniel Sokolowski * Dave Brooks * David Linke * Ed Schofield * Eduard Bopp * Eli * Felix Hummel * Francisco Couzo * Giel van Schijndel * Guido Imperiale * Ignacio Fdez. Galván * James Rowe * Jim Turner * Joel B. Mohler * John David Reaver * Jonas Olson * Jules Chéron * Kaido Kert * Kenneth D. Mankoff * Kevin Davies * Luke Campbell * Matthieu Dartiailh * Nate Bogdanowicz * Peter Grayson * Richard Barnes * Robert Booth * Ryan Dwyer * Ryan Kingsbury * Ryan May * Sebastian Kosmeier * Sigvald Marholm * Sundar Raman * Tiago Coutinho * Thomas Kluyver * Tom Nicholas * Tom Ritchford * Virgil Dupras * Zebedee Nicholls (If you think that your name belongs here, please let the maintainer know) pint-0.24.4/CHANGES000066400000000000000000001211651471316474000135630ustar00rootroot00000000000000Pint Changelog ============== 0.24.4 (2024-11-07) ------------------- - add error for prefixed non multi units (#1998) - build: typing_extensions version - build: switch from appdirs to platformdirs - fix GenericPlainRegistry getattr type (#2045) - Replace references to the deprecated `UnitRegistry.default_format` (#2058) - fix: upgrade to flexparser>=0.4, exceptions are no longer dataclasses. (required for Python 3.13) 0.24.2 (2024-07-28) ------------------- - Fix the default behaviour for pint-convert (cli) for importing uncertainties package (PR #2032, Issue #2016) - Added mu and mc as alternatives for SI micro prefix - Added ℓ as alternative for liter - Support permille units and `‰` symbol (PR #2033, Issue #1963) - Switch from appdirs to platformdirs. - Fixes issues related to GenericPlainRegistry.__getattr__ type (PR #2038, Issues #1946 and #1804) - Removed deprecated references in documentation and tests (PR #2058, Issue #2057) 0.24.1 (2024-06-24) ----------------- - Fix custom formatter needing the registry object. (PR #2011) - Support python 3.9 following difficulties installing with NumPy 2. (PR #2019) - Fix default formatting of dimensionless unit issue. (PR #2012) - Fix bug preventing custom formatters with modifiers working. (PR #2021) 0.24 (2024-06-07) ----------------- - Fix detection of invalid conversion between offset and delta units. (PR #1905) - Added dBW, decibel Watts, which is used in RF high power applications - NumPy 2.0 support (PR #1985, #1971) - Implement numpy roll (Related to issue #981) - Implement numpy correlate (PR #1990) - Add `dim_sort` function to _formatter_helpers. - Add `dim_order` and `default_sort_func` properties to FullFormatter. (PR #1926, fixes Issue #1841) - Minimum version requirement added for typing_extensions>=4.0.0. (PR #1996) - Documented packages using pint. (PR #1960) - Fixed bug causing operations between arrays of quantity scalars and quantity holding array resulting in incorrect units. (PR #1677) - Fix LaTeX siuntix formatting when using non_int_type=decimal.Decimal. (PR #1977) - Added refractive index units. (PR #1816) - Fix converting to offset units of higher dimension e.g. gauge pressure (PR #1949) - Fix unhandled TypeError when auto_reduce_dimensions=True and non_int_type=Decimal (PR #1853) - Creating prefixed offset units now raises an error. (PR #1998) - Improved error message in `get_dimensionality()` when non existent units are passed. (PR #1874, Issue #1716) 0.23 (2023-12-08) ----------------- - Add _get_conversion_factor to registry with cache. - Homogenize input and ouput of internal regitry functions to facility typing, subclassing and wrapping. (_yield_unit_triplets, ) - Generated downstream_status page to track the state of downstream projects. - Improve typing annotation. - Updated to flexparser 0.2. - Faster wraps (PR #1862) - Add codspeed github action. - Move benchmarks to pytest-benchmarks. - Support pytest on python 3.12 wrt Fraction formatting change (#1818) - Fixed Transformation type protocol. (PR #1805, PR #1832) - Documented to_preferred and created added an autoautoconvert_to_preferred registry option. (PR #1803) - Enable Pint to parse uncertainty numbers. (See #1611, #1614) - Optimize matplotlib unit conversion for Quantity arrays (PR #1819) - Add numpy.linalg.norm implementation. (PR #1251) 0.22 (2023-05-25) ----------------- - Drop Python 3.8 compatability following NEP-29. - Drop NumPy < 1.21 following NEP-29. - Improved typing experience. - Migrated fully to pyproject.toml. - Migrated to ruff. - In order to make static typing possible as required by mypy and similar tools, the way to subclass the registry has been changed. - Allow non-quantity atol parameters for isclose and allclose. (PR #1783) 0.21 (2023-05-01) ----------------- - Add PEP621/631 support. (Issue #1647) - Exposed matplotlib unit formatter (PR #1703) - Fix error when when re-registering a formatter. (PR #1629) - Add new SI prefixes: ronna-, ronto-, quetta-, quecto-. (PR #1652) - Fix unit check with `atol` using `np.allclose` & `np.isclose`. (Issue #1658) - Implementation for numpy.positive added for Quantity. (PR #1663) - Changed frequency to angular frequency in the docs. (PR #1668) - Remove deprecated `alen` numpy function (PR #1678) - Updated URLs for log and offset unit errors. (PR #1727) - Patched TYPE_CHECKING import regression. (PR #1686) - Parse '°' along with previous text, rather than adding a space, allowing, eg 'Δ°C' as a unit. (PR #1729) - Improved escaping of special characters for LaTeX format (PR #1712) - Avoid addition of spurious trailing zeros when converting units and non-int-type is Decimal (PR #1625). - Implementation for numpy.delete added for Quantity. (PR #1669) - Fixed Quantity type returned from `__dask_postcompute__`. (PR #1722) - Added Townsend unit (PR #1738) - Fix get_compatible_units() in dynamically added units. (Issue #1725) - Fix pint-convert script (Issue #1646) - Honor non_int_type when dividing. (Issue #1505) - Fix `trapz`, `dot`, and `cross` to work properly with non-multiplicative units (Issue #1593) ### Breaking Changes - Support percent and ppm units. Support the `%` symbol. (Issue #1277) - Fix error when parsing subtraction operator followed by white space. (PR #1701) - Removed Td as an alias for denier (within the Textile group) 0.20.1 (2022-10-27) ------------------- - Simplify registry subclassing. (Issue #1631) - Restore intersphinx cross reference functionality. (Issue #1637) - Use a newer version of flexparser that can deal with imports in linked/temporary folders. (Issue #1634) 0.20 (2022-10-25) ----------------- - Reorganized code into facets. Each facet encapsulate a Pint functionality. (See #1466, #1479) - The definition parser is now completely appart, making it easy to try other formats. (See #1595) - Extra requires for optional packages are now explicit in setup.cfg (See #1627) - Parse both Greek mu and micro Unicode points without error. (Issue #1030, #574) - Added angular frequency documentation page. - Move ASV benchmarks to dedicated folder. (Issue #1542) - An ndim attribute has been added to Quantity and DataFrame has been added to upcast types for pint-pandas compatibility. (#1596) - Fix a recursion error that would be raised when passing quantities to `cond` and `x`. (Issue #1510, #1530) - Update test_non_int tests for pytest. - Better support for uncertainties (See #1611, #1614) - Implement `numpy.broadcast_arrays` (#1607) - An ndim attribute has been added to Quantity and DataFrame has been added to upcast types for pint-pandas compatibility. (#1596) - Fix a recursion error that would be raised when passing quantities to `cond` and `x`. (Issue #1510, #1530) - Update test_non_int tests for pytest. - Create NaN-value quantities of appropriate non-int-type (Issue #1570). - New documentation format and organization! - Better support for pandas and dask. - Fix masked arrays (with multiple values) incorrectly being passed through setitem (Issue #1584) - Add Quantity.to_preferred 0.19.2 (2022-04-23) ------------------- - Add the ``separate_format_defaults`` registry setting (Issue #1501, PR #1503) - Handle definitions @import from relative paths on Windows (Issue #1508, thanks khaeru) 0.19.1 (2022-04-06) ------------------- - Provide a method to iter the definitions in the order they appear, recursing the imported files. (Issue #1498) 0.19 (2022-04-04) ----------------- - Better separation between parsing and loading of definitions. Implement a parsed "binary" version of "textual" definition files. Infrastructure to disk cache parsed definition files and RegistryCache resulting in a 10X speed up in registry instantiation when enabled. (Issue #1465) - Deprecate the old format defaulting behavior and prepare for the new one (Issue #1407) - Fix a bug for offset units of higher dimension, e.g. gauge pressure. (Issue #1066, thanks dalito) - Fix type hints of function wrapper (Issue #1431) - Upgrade min version of uncertainties to 3.1.4 - Add a example for `register_unit_format` to the formatting docs (Issue #1422). - Fix setting options of the application registry (Issue #1403). - Fix Quantity & Unit `is_compatible_with` with registry active contexts (Issue #1424). - Allow Quantity to parse 'NaN' and 'inf(inity)', case insensitive - Fix casting error when using to_reduced_units with array of int. (Issue #1184) - Use default numpy `np.printoptions` available since numpy 1.15. - Implement `numpy.nanprod` (Issue #1369) - Fix default_format ignored for measurement (Issue #1456) - Add `pint.testing` with functions to compare pint objects in tests (Issue #1421). - Fix handling modulo & floordiv operator in pint_eval (Issue #1470) - Fix `to_compact` and `infer_base_unit` for non-float non_int_type. - Fix `to_reduced_units` to work with dimensionless units. (Issue #919) - Fix parsing of units string with same canonalized name (Issue #1441 & #1142) - The pint-pandas example notebook has been moved to the pint-pandas package. ### New Units - `sverdrup` (PR #1404) - `cooling_tower_ton` (PR #1484) ### Breaking Changes - Update hour default symbol to `h`. (Issue #719) - Replace `h` with `ℎ` (U+210E) as default symbol for planck constant. - Change minimal Python version support to 3.8+ - Change minimal Numpy version support to 1.19+ 0.18 (2021-10-26) ----------------- ### Release Manager: jules-cheron - Implement use of Quantity in the Quantity constructor (convert to specified units). (Issue #1231) - Rename .readthedocs.yml to .readthedocs.yaml, update MANIFEST.in (Issue #1311) - Fix a few small typos. (Issue #1308) - Fix babel format for `Unit`. (Issue #1085) - Fix handling of positional max/min arguments in clip function. (Issue #1244) - Fix string formatting of numpy array scalars. - Fix default format for Measurement class (Issue #1300) - Fix parsing of pretty units with same exponents but different sign. (Issue #1360) - Convert the application registry to a wrapper object (Issue #1365) - Add documentation for the string format options. (Issue #1357, #1375, thanks keewis) - Support custom units formats. (Issue #1371, thanks keewis) - Autoupdate pre-commit hooks. - Improved the application registry. (Issue #1366, thanks keewis) - Improved testing isolation using pytest fixtures. ### Breaking Changes - pint no longer supports Python 3.6 - Minimum Numpy version supported is 1.17+ - Add supports for type hints for Quantity class. Quantity is now a Generic (PEP560). - Add support for [PEP561](https://www.python.org/dev/peps/pep-0561/) (Package Type information) 0.17 (2021-03-22) ----------------- - Add the Wh unit for battery capacity measurements (PR #1260, thanks Maciej Grela) - Fix issue with reducable dimensionless units when using power (Quantity**ndarray) (Issue #1185) - Fix comparisons between Quantities and Measurements. (Issue #1134, thanks lewisamarshall) - UnitsContainer returns false if other is str and cannnot be parsed (Issue #1179, thanks rfrowe) - Fix numpy.linalg.solve unit output. (Issue #1246) - Support numpy.lib.stride_tricks.sliding_window_view. (Issue #1255) - NEP29 Support docs. - Move all tests to pytest. - Fix to __pow__ and __ipow__ - Migrate to Github Actions. (Issue #1236) - Update linter to use pre-commit. - Quantity comparisons now ensure other is Quantity. - Add sign function compatibility. (thanks Robin Tesse) - Fix scalar to ndarray tolist. - Fix tolist function with scalar ndarray. (Issue #1195, thanks jules-ch) - Corrected typos and dacstrings - Implements a first benchmark suite in airspeed velocity (asv). - Power for pseudo-dimensionless units. (Issue #1185, thanks Kevin Fuhr) 0.16.1 (2020-09-22) ------------------- - Fix unpickling, now it is using the APP_REGISTRY as expected. (Issue #1175) 0.16 (2020-09-13) ----------------- - Fixed issue where performing an operation of a Quantity with certain units would perform an in-place unit conversion that modified the operand in addition to the returned value (Issues #1102 & #1144) - Implements Logarithmic Units like dBm, dB or decade (Issue #71, Thanks Dima Pustakhod, Clark Willison, Giorgio Signorello, Steven Casagrande, Jonathan Wheeler) - Drop dependency on setuptools pkg_resources to read package resources, using std lib importlib.resources instead. (Issue #1080) 0.15 (2020-08-22) ----------------- - Change `Quantity` and `Unit` HTML (i.e., Jupyter notebook) repr away from LaTeX to a simpler, more performant pretty-text and table based repr inspired by Sparse and Dask. (Issue #654) - Add `case_sensitive` option to registry for case (in)sensitive handling when parsing units (Issue #1145) - Implement Dask collection interface to support Pint Quantity wrapped Dask arrays. - Started automatically testing examples in the documentation - Fixed an exception generated when reducing dimensions with three or more units of the same type - Fixed right operand power for dimensionless Quantity to reflect numpy behavior. (Issue #1136) - Eliminated warning when setting a masked value on an underlying MaskedArray. - Add `sort` option to `formatting.formatter` to permit disabling sorting of component units in format string - Implements Logarithmic Units like dBm, dB or decade (Issue #71, Thanks Dima Pustakhod, Giorgio Signorello, Jonathan Wheeler) 0.14 (2020-07-01) ----------------- - Changes required to support Pint-Pandas 0.1. 0.13 (2020-06-17) ----------------- - Reinstated support for pickle protocol 0 and 1, which is required by pytables (Issue #1036, Thanks Guido Imperiale) - Fixed bug with multiplication of Quantity by dict (Issue #1032) - Bare zeros and NaNs (not wrapped by Quantity) are now gracefully accepted by all numpy operations; e.g. np.stack([Quantity([1, 2], "m"), [0, np.nan]) is now valid, whereas np.stack([Quantity([1, 2], "m"), [3, 4]) will continue raising DimensionalityError. (Issue #1050, Thanks Guido Imperiale) - NaN is now treated the same as zero in addition, subtraction, equality, and disequality (Issue #1051, Thanks Guido Imperiale) - Fixed issue where quantities with a very large magnitude would throw an IndexError when using to_compact() - Fixed crash when a Unit with prefix is declared for the first time while a Context containing unit redefinitions is active (Issues #1062 and #1097, Thanks Guido Imperiale) - New implementation of 'Lx' String Format Type Option The old implementation treated 'Lx' as 'S' as produced by 'uncertainties' package, but that is not fully compatible with SIunitx. The new code protects SIunitx by fixing what unceratinties produces. (Issue #814) - Added link to budding `pint-xarray` interface library to the docs, next to the link to pint-pandas. (Thanks Tom Nicholas.) - Removed outdated `_dir` attribute of `UnitsRegistry`, and added `__iter__` method so that now `list(ureg)` returns a list of all units in registry. (Issue #1072, Thanks Tom Nicholas) - Replace pkg_resources.version to importlib.metadata.version. (Issue #1083) - Fix typo in docs for wraps example with optional arguments. (Issue #1088) - Add momentum as a dimension - Fixed a bug where unit exponents were only partially superscripted in HTML format - Multiple contexts containing the same redefinition can now be stacked (Issue #1108, Thanks Guido Imperiale) - Fixed crash when some specific combinations of contexts were enabled (Issue #1112, Thanks Guido Imperiale) - Added support for checking prefixed units using `in` keyword (Issue #1086) - Updated many examples in the documentation to reflect Pint's current behavior 0.12 (2020-05-29) ----------------- - Add full support for Decimal and Fraction at the registry level. **BREAKING CHANGE**: `use_decimal` is deprecated. Use `non_int_type=Decimal` when instantiating the registry. - Fixed bug where numpy.pad didn't work without specifying constant_values or end_values (Issue #1026) 0.11 (2020-02-19) ----------------- - Added pint-convert script. - Remove `default_en_0.6.txt`. - Make `__str__` and `__format__` locale configurable. (Issue #984) - Quantities wrapping NumPy arrays will no longer warning for the changed array function behavior introduced in 0.10. (Issue #1029, Thanks Jon Thielen) - **BREAKING CHANGE**: The array protocol fallback deprecated in version 0.10 has been removed. (Issue #1029, Thanks Jon Thielen) - Now we use `pyproject.toml` for providing `setuptools_scm` settings - Remove `default_en_0.6.txt` - Reorganize long_description. - Moved Pi to definitions files. - Use ints (not floats) a defaults at many points in the codebase as in Python 3 the true division is the default one. - **BREAKING CHANGE**: Added `from_string` method to all Definitions subclasses. The value/converter argument of the constructor no longer accepts an string. It is unlikely that this change affects the end user. - Added additional NumPy function implementations (allclose, intersect1d) (Issue #979, Thanks Jon Thielen) - Allow constants in units by using a leading underscore (Issue #989, Thanks Juan Nunez-Iglesias) - Fixed bug where to_compact handled prefix units incorrectly (Issue #960) 0.10.1 (2020-01-07) ------------------- - Fixed bug introduced in 0.10 that prevented creation of size-zero Quantities from NumPy arrays by multiplication. (Issue #977, Thanks Jon Thielen) - Fixed several Sphinx issues. Fixed intersphinx hooks to all classes missing. (Issue #881, Thanks Guido Imperiale) - Fixed __array__ signature to match numpy docs (Issue #974, Thanks Ryan May) 0.10 (2020-01-05) ----------------- - **BREAKING CHANGE**: Boolean value of Quantities with offsets units is ambiguous, and so, now a ValueError is raised when attempting to cast such a Quantity to boolean. (Issue #965, Thanks Jon Thielen) - **BREAKING CHANGE**: `__array_ufunc__` has been implemented on `pint.Unit` to permit multiplication/division by units on the right of ufunc-reliant array types (like Sparse) with proper respect for the type casting hierarchy. However, until [an upstream issue with NumPy is resolved](https://github.com/numpy/numpy/issues/15200), this breaks creation of Masked Array Quantities by multiplication on the right. Read Pint's [NumPy support documentation](https://pint.readthedocs.io/en/latest/numpy.html) for more details. (Issues #963 and #966, Thanks Jon Thielen) - Documentation on Pint's array type compatibility has been added to the NumPy support page, including a graph of the duck array type casting hierarchy as understood by Pint for N-dimensional arrays. (Issue #963, Thanks Jon Thielen, Stephan Hoyer, and Guido Imperiale) - Improved compatibility for downcast duck array types like Sparse.COO. A collection of basic tests has been added. (Issue #963, Thanks Jon Thielen) - Improvements to wraps and check: - fail upon decoration (not execution) by checking wrapped function signature against wraps/check arguments. (might BREAK test code) - wraps only accepts strings and Units (not quantities) to avoid confusion with magnitude. (might BREAK code not conforming to documentation) - when strict=True, strings that can be parsed to quantities are accepted as arguments. - Add revolutions per second (rps) - Improved compatibility for upcast types like xarray's DataArray or Dataset, to which Pint Quantities now fully defer for arithmetic and NumPy operations. A collection of basic tests for proper deferral has been added (for full integration tests, see xarray's test suite). The list of upcast types is available at `pint.compat.upcast_types` in the API. (Issue #959, Thanks Jon Thielen) - Moved docstrings to Numpy Docs (Issue #958) - Added tests for immutability of the magnitude's type under common operations (Issue #957, Thanks Jon Thielen) - Switched test configuration to pytest and added tests of Pint's matplotlib support. (Issue #954, Thanks Jon Thielen) - Deprecate array protocol fallback except where explicitly defined (`__array__`, `__array_priority__`, `__array_function__`, `__array_ufunc__`). The fallback will remain until the next minor version, or if the environment variable `PINT_ARRAY_PROTOCOL_FALLBACK` is set to 0. (Issue #953, Thanks Jon Thielen) - Removed eval usage when creating UnitDefinition and PrefixDefinition from string. (Issue #942) - Added `fmt_locale` argument to registry. (Issue #904) - Better error message when Babel is not installed. (Issue #899) - It is now possible to redefine units within a context, and use pint for currency conversions. Read - https://pint.readthedocs.io/en/latest/contexts.html - https://pint.readthedocs.io/en/latest/currencies.html (Issue #938, Thanks Guido Imperiale) - NaN (any capitalization) in a definitions file is now treated as a number (Issue #938, Thanks Guido Imperiale) - Added slinch to Avoirdupois group (Issue #936, Thanks awcox21) - Fix bug where ureg.disable_contexts() would fail to fully disable throwaway contexts (Issue #932, Thanks Guido Imperiale) - Use black, flake8, and isort on the project (Issues #929, #931, and #937, Thanks Guido Imperiale) - Auto-increase package version at every commit when pint is installed from the git tip, e.g. pip install git+https://github.com/hgrecco/pint.git. (Issues #930 and #934, Thanks Guido Imperiale and KOLANICH) - Fix HTML (Jupyter Notebook) and LateX representation of some units (Issues #927 / #928 / #933, Thanks Guido Imperiale) - Fixed the definition of RKM unit as gf / tex (Issue #921, Thanks Giuseppe Corbelli) - **BREAKING CHANGE**: Implement NEP-18 for Pint Quantities. Most NumPy functions that previously stripped units when applied to Pint Quantities will now return Quantities with proper units (on NumPy v1.16 with the array_function protocol enabled or v1.17+ by default) instead of ndarrays. Any non-explictly-handled functions will now raise a "no implementation found" TypeError instead of stripping units. The previous behavior is maintained for NumPy < v1.16 and when the array_function protocol is disabled. (Issue #905, Thanks Jon Thielen and andrewgsavage) - Implementation of NumPy ufuncs has been refactored to share common utilities with NumPy function implementations (Issue #905, Thanks Jon Thielen) - Pint Quantities now support the `@` matrix mulitiplication operator (on NumPy v1.16+), as well as the `dot`, `flatten`, `astype`, and `item` methods. (Issue #905, Thanks Jon Thielen) - **BREAKING CHANGE**: Fix crash when applying pprint to large sets of Units. DefinitionSyntaxError is now a subclass of SyntaxError (was ValueError). DimensionalityError and OffsetUnitCalculusError are now subclasses of TypeError (was ValueError). (Issue #915, Thanks Guido Imperiale) - All Exceptions can now be pickled and can be accessed from the top-level package. (Issue #915, Thanks Guido Imperiale) - Mark regex as raw strings to avoid unnecessary warnings. (Issue #913, Thanks keewis) - Implement registry-based string preprocessing as list of callables. (Issues #429 and #851, thanks Jon Thielen) - Context activation and deactivation is now instantaneous; drastically reduced memory footprint of a context (it used to be ~1.6MB per context; now it's a few bytes) (Issues #909 / #923 / #938, Thanks Guido Imperiale) - **BREAKING CHANGE**: Drop support for Python < 3.6, numpy < 1.14, and uncertainties < 3.0; if you still need them, please install pint 0.9. Pint now adheres to NEP-29 as a rolling dependencies version policy. (Issues #908 and #910, Thanks Guido Imperiale) - Show proper code location of UnitStrippedWarning exception. (Issue #907, thanks Martin K. Scherer) - Reimplement _Quantity.__iter__ to return an iterator. (Issues #751 and #760, Thanks Jon Thielen) - Add http://www.dimensionalanalysis.org/ to README (Thanks Shiri Avni) - Allow for user defined units formatting. (Issue #873, Thanks Ryan Clary) - Quantity, Unit, and Measurement are now accessible as top-level classes (pint.Quantity, pint.Unit, pint.Measurement) and can be instantiated without explicitly creating a UnitRegistry (Issue #880, Thanks Guido Imperiale) - Contexts don't need to have a name anymore (Issue #870, Thanks Guido Imperiale) - "Board feet" unit added top default registry (Issue #869, Thanks Guido Imperiale) - New syntax to add aliases to already existing definitions (Issue #868, Thanks Guido Imperiale) - copy.deepcopy() can now copy a UnitRegistry (Issues #864 and #877, Thanks Guido Imperiale) - Enabled many tests in test_issues when numpy is not available (Issue #863, Thanks Guido Imperiale) - Document the '_' symbols found in the definitions files (Issue #862, Thanks Guido Imperiale) - Improve OffsetUnitCalculusError message. (Issue #839, Thanks Christoph Buchner) - Atomic units for intensity and electric field. (Issue #834, Thanks Øyvind Sigmundson Schøyen) - Allow np arrays of scalar quantities to be plotted. (Issue #825, Thanks andrewgsavage) - Updated gravitational constant to CODATA 2018. (Issue #816, Thanks Jellby) - Update to new SI definition and CODATA 2018. (Issue #811, Thanks Jellby) - Allow units with aliases but no symbol. (Issue #808, Thanks Jellby) - Fix definition of dimensionless units and constants. (Issue #805, Thanks Jellby) - Added RKM unit (used in textile industry). (Issue #802, Thanks Giuseppe Corbelli) - Remove __name__ method definition in BaseRegistry. (Issue #787, Thanks Carlos Pascual) - Added t_force, short_ton_force and long_ton_force. (Issue #796, Thanks Jan Hein de Jong) - Fixed error message of DefinitionSyntaxError (Issue #791, Thanks Clément Pit-Claudel) - Expanded the potential use of Decimal type to parsing. (Issue #788, Thanks Francisco Couzo) - Fixed gram name to allow translation by babel. (Issue #776, Thanks Hervé Cauwelier) - Default group should only have orphan units. (Issue #766, Thanks Jules Chéron) - Added custom constructors from_sequence and from_list. (Issue #761, Thanks deniz195) - Add quantity formatting with ndarray. (Issue #559, Thanks Jules Chéron) - Add pint-pandas notebook docs (Issue #754, Thanks andrewgsavage) - Use µ as default abbreviation for micro. (Issue #666, Thanks Eric Prestat) 0.9 (2019-01-12) ---------------- - Add support for registering with matplotlib's unit handling (Issue #317, thanks dopplershift) - Add converters for matplotlib's unit support. (Issue #317, thanks Ryan May) - Fix unwanted side effects in auto dimensionality reduction. (Issue #516, thanks Ben Loer) - Allow dimensionality check for non Quantity arguments. - Make Quantity and UnitContainer objects hashable. (Issue #286, thanks Nevada Sanchez) - Fix unit tests errors with numpy >=1.13. (Issue #577, thanks cpascual) - Avoid error in in-place exponentiation with numpy > 1.11. (Issue #577, thanks cpascual) - fix compatible units in context. (thanks enrico) - Added warning for unsupported ufunc. (Issue #626, thanks kanhua) - Improve IPython pretty printers. (Issue #590, thanks tecki) - Drop Support for Python 2.6, 3.0, 3.1 and 3.2. (Issue #567) - Prepare for deprecation announced in Python 3.7 (Issue #747, thanks Simon Willison) - Added several new units and Systems (Issues #749, #737, ) - Started experimental pandas support (Issue #746 and others. Thanks andrewgsavage, znicholls and others) - wraps and checks now supports kwargs and defaults. (Issue #660, thanks jondoesntgit) 0.8.1 (2017-06-05) ------------------ - Add support for datetime math. (Issue #510, thanks robertd) - Fixed _repr_html_ in Python 2.7. (Issue #512) - Implemented BaseRegistry.auto_reduce_dimensions. (Issue #500, thanks robertd) - Fixed dimension compatibility bug introduced on Registry refactoring (Issue #523, thanks dalito) 0.8 (2017-04-16) ---------------- - Refactored the Registry in multiple classes for better separation of concerns and clarity. - Implemented support for defining multiple units per `define` call (one definition per line). (Issue #462) - In pow and ipow, allow array exponents (with len > 1) when base is dimensionless. (Issue #483) - Wraps now gets the canonical name of the unit when passed as string. (Issue #468) - NumPy exp and log keeps the type (Issue #95) - Implemented a function decorator to ensure that a context is active (with_context) (Issue #465) - Add warning when a System contains an unknown Group. (Issue #472) - Add conda-forge installation snippet. (Issue #485, thanks stadelmanma) - Properly support floor division and modulo. (Issue #474, thanks tecki) - Measurement Correlated variable fix. (Issue #463, thanks tadhgmister) - Implement degree sign handling. (Issue #449, thanks iamthad) - Change `UndefinedUnitError` to inherit from `AttributeError` (Issue #480, thanks jhidding) - Simplified travis for faster testing. - Fixed order units in siunitx formatting. (Issue #441) - Changed Systems lister to return a list instead of frozenset. (Issue #425, thanks GloriaVictis) - Fixed issue with negative values in to_compact() method. (Issue #443, thanks nowox) - Improved defintions. (Issues #448, thanks gdonval) - Improved Parser to support capital "E" on scientific notation. (Issue #390, thanks javenoneal) - Make sure that prefixed units are defined on the registry when unpickling. (Issue #405) - Automatic unit names translation through babel. (Issue #338, thanks alexbodn) - Support pickling Unit objects. (Issue #349) - Add support for wavenumber/kayser in spectroscopy context. (Issue #321, thanks gerritholl) - Improved formatting. (thanks endolith and others) - Add support for inline comments in definitions file. (Issue #366) - Implement Unit.__deepcopy__. (Issue #357, thanks noahl) - Allow changing shape for Quantities with numpy arrays. (Issue #344, thanks tecki) 0.7.2 (2016-03-02) ------------------ - Fixed backward incompatibility problem when parsing dimensionless units. 0.7.1 (2016-02-23) ------------------ - Use NIST as source for most of the unit information. - Added message to assertQuantityEqual. - Added detection of circular dependencies in definitions. 0.7 (2016-02-20) ---------------- - Added Systems and groups. (Issue #215, #315) - Implemented references for wraps decorator. (Issue #195) - Added check decorator to UnitRegistry. (Issue #283, thanks kaidokert) - Added compact conversion. (See #224, thanks Ryan Dwyer) - Added compact formating code. (Issue #240) - New Unit Class. (thanks Matthieu Dartiailh) - Refactor UnitRegistry. (thanks Matthieu Dartiailh) - Move definitions, errors, and converters into their own modules. (thanks Matthieu Dartiailh) - UnitsContainer is now immutable (Issue #202, thanks Matthieu Dartiailh) - New parser and evaluator. (Issue #226, thanks Aaron Coleman) - Added support for Unicode identifiers. - Added m_as as way top retrieve the magnitude in different units. (Issue #227) - Added Short form for magnitude and units. (Issue #234) - Improved deepcopy. (Issue #252, thanks Emilien Kofman) - Improved testing infrastructure. - Improved docs. (thanks Ryan Dwyer, Martin Thoma, Andrea Zonca) - Fixed short names on electron_volt and hartree. - Fixed definitions of scruple and drachm. (Issue #262, thanks takowl) - Fixed troy ounce to 480 'grains'. (thanks elifab) - Added 'quad' as a unit of energy (= 10**15 Btu). (thanks Ed Schofield) - Added "hectare" as a supported unit of area and 'ha' as the symbol for hectare. (thanks Ed Schofield) - Added peak sun hour and Langley. (thanks Ed Schofield) - Added photometric units: lumen & lux. (Issue #230, thanks janpipek) - A fraction magnitude quantity is conserved (Issue #323, thanks emilienkofman) - Improved conversion performance by removing unnecessart try/except. (Issue #251) - Added to_tuple and from_tuple to facilitate serialization. - Fixed support for NumPy 1.10 due to a change in the Default casting rule (Issue #320) - Infrastructure: Added doctesting. - Infrastructure: Better way to specify exclude matrix in travis. 0.6 (2014-11-07) ---------------- - Fix operations with measurments and user defined units. (Issue #204) - Faster conversions through caching and other performance improvements. (Issue #193, thanks MatthieuDartiailh) - Better error messages on Quantity.__setitem__. (Issue #191) - Fixed abbreviation of fluid_ounce. (Issue #187, thanks hsoft) - Defined Angstrom symbol. (Issue #181, thanks JonasOlson) - Removed fetching version from git repo as it triggers XCode installation on OSX. (Issue #178, thanks deanishe) - Improved context documentation. (Issue #176 and 179, thanks rsking84) - Added Chemistry context. (Issue #179, thanks rsking84) - Fix help(UnitRegisty) (Issue #168) - Optimized "get_dimensionality" and "get_base_name". (Issue #166 and #167, thanks jbmohler) - Renamed ureg.parse_units parameter "to_delta" to "as_delta" to make clear. that no conversion happens. Accordingly, the parameter/property "default_to_delta" of UnitRegistry was renamed to "default_as_delta". (Issue #158, thanks dalit) - Fixed problem when adding two uncertainties. (thanks dalito) - Full support for Offset units (e.g. temperature) (Issue #88, #143, #147 and #161, thanks dalito) 0.5.2 (2014-07-31) ------------------ - Changed travis config to use miniconda for faster testing. - Added wheel configuration to setup.cfg. - Ensure resource streams are closed after reading. - Require setuptools. (Issue #169) - Implemented real, imag and T Quantity properties. (Issue #171) - Implemented __int__ and __long__ for Quantity (Issue #170) - Fixed SI prefix error on ureg.convert. (Issue #156, thanks jdreaver) - Fixed parsing of multiparemeter contexts. (Issue #174) 0.5.1 (2014-06-03) ------------------ - Implemented a standard way to change the registry used in unpickling operations. (Issue #148) - Fix bug where conversion would fail due to caching. (Issue #140, thanks jdreaver) - Allow assigning Not a Number to a quantity array. (Issue #127) - Decoupled Quantity in place and not in place unit conversion methods. - Return None in functions that modify quantities in place. - Improved testing infrastructure to check for unwanted warnings. - Added test function at the package level to run all tests. 0.5 (2014-05-07) ---------------- - Improved test suite helper functions. - Print honors default format w/o format(). (Issue #132, thanks mankoff) - Fixed sum() by treating number zero as a special case. (Issue #122, thanks rec) - Improved behaviour in ScaleConverter, OffsetConverter and Quantity.to. (Issue #120) - Reimplemented loading of default definitions to allow Pint in a cx_freeze or similar package. (Issue #118, thanks jbmohler) - Implemented parsing of pretty printed units. (Issue #117, thanks jpgrayson) - Fixed representation of dimensionless quantities. (Issue #112, thanks rec) - Raise error when invalid formatting code is given. (Issue #111, thanks rec) - Default registry to lazy load, raise error on redefinition (Issue #108, thanks rec, aepsil0n) - Added condensed format. (Issue #107, thanks rec) - Added UnitRegistry () operator to parse expression replacing []. (Issue #106, thanks rec) - Optional case insensitive unit parsing. (Issue #105, thanks rec, jeremyfreeman, dbrnz) - Change the Quantity mutability depending on magnitude type. (Issue #104, thanks rec) - Implemented API to list compatible units. (Issue #89) - Implemented cache of key UnitRegistry methods. - Rewrote the Measurement class to use uncertainties. (Issue #24) 0.4.2 (2014-02-14) ------------------ - Python 2.6 support (Issue #96, thanks tiagocoutinho) - Fixed symbol for inch. (Issue #102, thanks cybertoast) - Stop raising AttributeError when wrapping funcs without all of the attributes. (Issue #100, thanks jturner314) - Fixed warning appearing in Py2.x when comparing a Numpy Array with an empty string. (Issue #98, thanks jturner314) - Add links to AUR packages in docs. (Issue #91, thanks jturner314) - Fixed garbage collection related problem. (Issue #92, thanks jturner314) 0.4.1 (2014-01-12) ------------------ - Integer Division with Arrays. (Issue #80, thanks jdreaver) - Improved Documentation. (Issue #83, thanks choloepus) - Removed 'h' alias for hour due to conflict with Planck's constant. (Issue #82, thanks choloepus) - Improved get_base_units for non-multiplicative units. (Issue #85, thanks exxus) - Refactored code for multiplication. (Issue #84, thanks jturner314) - Removed 'R' alias for roentgen as it collides with molar_gas_constant. (Issue #87, thanks rsking84) - Improved naming of temperature units and multiplication of non-multiplicative units. (Issue #86, tahsnk exxus) 0.4 (2013-12-17) ---------------- - Introduced Contexts: relation between incompatible dimensions. (Issue #65) - Fixed get_base_units for non multiplicative units. (Related to issue #66) - Implemented default formatting for quantities. - Changed comparison between Quantities containing NumPy arrays. (Issue #75) - BACKWARDS INCOMPATIBLE CHANGE - Fixes for NumPy 1.8 due to changes in handling binary ops. (Issue #73) 0.3.3 (2013-11-29) ------------------ - ParseHelper can now parse units named like python keywords. (Issue #69) - Fix comparison of quantities. (Issue #74) - Fix Inequality operator. (Issue #70, thanks muggenhor) - Improved travis configuration. (thanks muggenhor) 0.3.2 (2013-10-22) ------------------ - Fix get_dimensionality for non multiplicative units. (Issue #66) - Proper handling of @import directive inside a file read using pkg_resources. (Issue #68) 0.3.1 (2013-09-15) ------------------ - fix right division on python 2.7 (Issue #58, thanks natezb) - fix formatting of fractional exponentials between 0 and 1. (Issue #62, thanks jdreaver) - fix installation as egg. (Issue #61) - fix handling of strange values as input of Quantity. (Issue #53) - math operations between quantities of different registries now raise a ValueError. (Issue #52) 0.3 (2013-09-02) ---------------- - support for IPython autocomplete and rich display. (Issues #30 and #31) - support for @import directive in definitions file. (Issue #22) - support for wrapping functions to make them pint-aware. (Issue #16) - support for comparing UnitsContainer to string. (Issue #35) - fix error raised while converting from a single unit to one expressed as the relation between many. (Issue #29) - fix error raised when unit symbol is missing. (Issue #41) - fix error raised when magnitude is Decimal. (Issue #46, thanks danielsokolowski) - support for non-installed pint. (Issue #42, thanks danielsokolowski) - support for application of numpy function on non-ndarray magnitudes. (Issue #44) - support for math operations on dimensionless Quantities (written with units). (Issue #45) - fix obtaining dimensionless quantity from string. (Issue #50) - fix adding and comparing numbers to a dimensionless quantity (written with units). (Issue #54) - Support for iter in Quantity. (Issue #55, thanks natezb) 0.2.1 (2013-07-02) ------------------ - fix error raised while converting from a single unit to one expressed as the relation between many. (Issue #29) 0.2 (2013-05-13) ---------------- - support for Measurement (Quantity +/- error). - implemented buckingham pi theorem for dimensional analysis. - support for temperature units and temperature difference units. - parser can infers if the user mean temperature or temperature difference. - support for derived dimensions (e.g. [speed] = [length] / [time]). - refactored the code into multiple files. - refactored code to isolate definitions and converters. - refactored formatter out of UnitParser class. - added tox and travis config files for CI. - comprehensive NumPy testing including almost all ufuncs. - full NumPy support (features is not longer experimental). - fixed bug preventing from having independent registries. (Issue #10, thanks bwanders) - forces real division as default for Quantities. (Issue #7, thanks dbrnz) - improved default unit definition file. (Issue #13, thanks r-barnes) - smarter parser supporting spaces as multiplications and other nice features. (Issue #13, thanks r-barnes) - moved testsuite inside package. - short forms of binary prefixes, more units and fix to less than comparison. (Issue #20, thanks muggenhor) - pint is now zip-safe (Issue #23, thanks muggenhor) Version 0.1.3 (2013-01-07) -------------------------- - abbreviated quantity string formating. - complete Python 2.7 compatibility. - implemented pickle support for Quantities objects. - extended NumPy support. - various bugfixes. Version 0.1.2 (2012-08-12) -------------------------- - experimenal NumPy support. - included default unit definitions file. (Issue #1, thanks fish2000) - better testing. - various bugfixes. - fixed some units definitions. (Issue #4, thanks craigholm) Version 0.1.1 (2012-07-31) -------------------------- - better packaging and installation. Version 0.1 (2012-07-26) -------------------------- - first public release. pint-0.24.4/LICENSE000066400000000000000000000030601471316474000135660ustar00rootroot00000000000000Copyright (c) 2012 by Hernan E. Grecco and contributors. See AUTHORS for more details. Some rights reserved. Redistribution and use in source and binary forms of the software as well as documentation, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pint-0.24.4/MANIFEST.in000066400000000000000000000006671471316474000143310ustar00rootroot00000000000000include AUTHORS CHANGES LICENSE README.rst BADGES.rst version.txt .coveragerc .readthedocs.yaml .pre-commit-config.yaml recursive-include pint * recursive-include docs * recursive-include benchmarks * prune docs/_build prune docs/_themes/.git prune pint/.pytest_cache exclude .editorconfig bors.toml pull_request_template.md requirements_docs.txt version.py global-exclude *.pyc *~ .DS_Store *__pycache__* *.pyo .travis-exclude.yml *.lock pint-0.24.4/README.rst000066400000000000000000000137361471316474000142630ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/pint.svg :target: https://pypi.python.org/pypi/pint :alt: Latest Version .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff :alt: Ruff .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/format.json :target: https://github.com/astral-sh/ruff :alt: Ruff-Format .. image:: https://readthedocs.org/projects/pint/badge/ :target: https://pint.readthedocs.org/ :alt: Documentation .. image:: https://img.shields.io/pypi/l/pint.svg :target: https://pypi.python.org/pypi/pint :alt: License .. image:: https://img.shields.io/pypi/pyversions/pint.svg :target: https://pypi.python.org/pypi/pint :alt: Python Versions .. image:: https://github.com/hgrecco/pint/workflows/CI/badge.svg :target: https://github.com/hgrecco/pint/actions?query=workflow%3ACI :alt: CI .. image:: https://github.com/hgrecco/pint/workflows/Lint/badge.svg :target: https://github.com/hgrecco/pint/actions?query=workflow%3ALint :alt: LINTER .. image:: https://coveralls.io/repos/github/hgrecco/pint/badge.svg?branch=master :target: https://coveralls.io/github/hgrecco/pint?branch=master :alt: Coverage Pint: makes units easy ====================== Pint is a Python package to define, operate and manipulate physical quantities: the product of a numerical value and a unit of measurement. It allows arithmetic operations between them and conversions from and to different units. It is distributed with a comprehensive list of physical units, prefixes and constants. Due to its modular design, you can extend (or even rewrite!) the complete list without changing the source code. It supports a lot of numpy mathematical operations **without monkey patching or wrapping numpy**. It has a complete test coverage. It runs in Python 3.9+ with no other dependency. It is licensed under BSD. It is extremely easy and natural to use: .. code-block:: python >>> import pint >>> ureg = pint.UnitRegistry() >>> 3 * ureg.meter + 4 * ureg.cm and you can make good use of numpy if you want: .. code-block:: python >>> import numpy as np >>> [3, 4] * ureg.meter + [4, 3] * ureg.cm >>> np.sum(_) Quick Installation ------------------ To install Pint, simply: .. code-block:: bash $ pip install pint or utilizing conda, with the conda-forge channel: .. code-block:: bash $ conda install -c conda-forge pint and then simply enjoy it! Documentation ------------- Full documentation is available at http://pint.readthedocs.org/ Command-line converter ---------------------- A command-line script `pint-convert` provides a quick way to convert between units or get conversion factors. Design principles ----------------- Although there are already a few very good Python packages to handle physical quantities, no one was really fitting my needs. Like most developers, I programmed Pint to scratch my own itches. **Unit parsing**: prefixed and pluralized forms of units are recognized without explicitly defining them. In other words: as the prefix *kilo* and the unit *meter* are defined, Pint understands *kilometers*. This results in a much shorter and maintainable unit definition list as compared to other packages. **Standalone unit definitions**: units definitions are loaded from a text file which is simple and easy to edit. Adding and changing units and their definitions does not involve changing the code. **Advanced string formatting**: a quantity can be formatted into string using `PEP 3101`_ syntax. Extended conversion flags are given to provide symbolic, LaTeX and pretty formatting. Unit name translation is available if Babel_ is installed. **Free to choose the numerical type**: You can use any numerical type (`fraction`, `float`, `decimal`, `numpy.ndarray`, etc). NumPy_ is not required but supported. **Awesome NumPy integration**: When you choose to use a NumPy_ ndarray, its methods and ufuncs are supported including automatic conversion of units. For example `numpy.arccos(q)` will require a dimensionless `q` and the units of the output quantity will be radian. **Uncertainties integration**: transparently handles calculations with quantities with uncertainties (like 3.14±0.01 meter) via the `uncertainties package`_. **Handle temperature**: conversion between units with different reference points, like positions on a map or absolute temperature scales. **Dependency free**: it depends only on Python and its standard library. It interacts with other packages like numpy and uncertainties if they are installed **Pandas integration**: Thanks to `Pandas Extension Types`_ it is now possible to use Pint with Pandas. Operations on DataFrames and between columns are units aware, providing even more convenience for users of Pandas DataFrames. For full details, see the `pint-pandas Jupyter notebook`_. Pint is maintained by a community of scientists, programmers and enthusiasts around the world. See AUTHORS_ for a complete list. To review an ordered list of notable changes for each version of a project, see CHANGES_ .. _Website: http://www.dimensionalanalysis.org/ .. _`comprehensive list of physical units, prefixes and constants`: https://github.com/hgrecco/pint/blob/master/pint/default_en.txt .. _`uncertainties package`: https://pythonhosted.org/uncertainties/ .. _`NumPy`: http://www.numpy.org/ .. _`PEP 3101`: https://www.python.org/dev/peps/pep-3101/ .. _`Babel`: http://babel.pocoo.org/ .. _`Pandas Extension Types`: https://pandas.pydata.org/pandas-docs/stable/development/extending.html#extension-types .. _`pint-pandas Jupyter notebook`: https://github.com/hgrecco/pint-pandas/blob/master/notebooks/pint-pandas.ipynb .. _`AUTHORS`: https://github.com/hgrecco/pint/blob/master/AUTHORS .. _`CHANGES`: https://github.com/hgrecco/pint/blob/master/CHANGES pint-0.24.4/docs/000077500000000000000000000000001471316474000135125ustar00rootroot00000000000000pint-0.24.4/docs/Makefile000066400000000000000000000130021471316474000151460ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = ifeq ($(wildcard sphinx-build), ) SPHINXBUILD = sphinx-build else SPHINXBUILD = ./sphinx-build endif PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pint.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pint.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pint" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pint" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pint-0.24.4/docs/_static/000077500000000000000000000000001471316474000151405ustar00rootroot00000000000000pint-0.24.4/docs/_static/index_api.svg000066400000000000000000000057321471316474000176300ustar00rootroot00000000000000 image/svg+xml pint-0.24.4/docs/_static/index_contribute.svg000066400000000000000000000042541471316474000212330ustar00rootroot00000000000000 image/svg+xml pint-0.24.4/docs/_static/index_getting_started.svg000066400000000000000000000072311471316474000222420ustar00rootroot00000000000000 image/svg+xml pint-0.24.4/docs/_static/index_user_guide.svg000066400000000000000000000140461471316474000212100ustar00rootroot00000000000000 image/svg+xml pint-0.24.4/docs/_static/logo-full.jpg000066400000000000000000000270071471316474000175500ustar00rootroot00000000000000JFIFHH@ICC_PROFILE0appl mntrRGB XYZ   acspAPPLappl-appl dscmdescogXYZlwtptrXYZbXYZrTRCcprt8chad,gTRCbTRCmluc enUS&~esES&daDK.deDE,fiFI(frFU(*itIT(VnlNL(nbNO&ptBR&svSE&jaJPRkoKR@zhTWlzhCNruRU"plPL,Yleinen RGB-profiiliGenerisk RGB-profilProfil Gnrique RVBN, RGB 000000u( RGB r_icϏPerfil RGB GenricoAllgemeines RGB-Profilfn RGB cϏeNGenerel RGB-beskrivelseAlgemeen RGB-profiel| RGB \ |Profilo RGB GenericoGeneric RGB Profile1I89 ?@>D8;L RGBUniwersalny profil RGBdescGeneric RGB ProfileGeneric RGB ProfileXYZ Zus4XYZ RXYZ tM=XYZ (6curvtextCopyright 2007 Apple Inc., all rights reserved.sf32 B&lExifMM*JR(iZHH\C     C  \" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?((((((()jES((((((((({PE!8{P%+}@ZE^C\zё^0>'pC[X[gE {LBmWLքzpVC3M'<Gxj1Y'fG^km"1#N,QJ599҄ԕ(1QEP(((((({R`s$& K1U(gkm_]&ZE!?,+Ļd[EcFLϢז]Û+ ӓ5y6Lp8d3>Ud.]T+4K,2wيkO)$}P$73mRq9׵xS<ϪO6";n$n|M \(;h'='xJ0|6O"oe?Cۚ_XmR_D,.0GCRƐ\*ZrU~.Qw+rcev~x;Ė/uxH`Ko?OċGTKI7Z. f%+'3QE} Q@Q@Q@Q@Q@Q@2OGyj,пh,R+p/-/c۷m  5! r?uH HSИZ{Hi |Vw׉osŬqke_B$sYrj& 0#$zϯ@nɎO+s؍8U@%k ԇS>H rg#y-P>*602jJ2]յ@sul Upyڙ.%#KaMx]ԟs_H"ڨ-:q׽}tU"%O?,A7(W0q4W?P u1UT43B((((((;F6 2,rR[6eH3*+M|,x4K?Wqߕ0[\x~XFq\ 0O5IT)f-o1$Oyկ5=KB>S57w$~4x-ʚ9=;tg(_3^ $(`QXh,Q{覚kQWv1mY'h 1^PT%m۳;9S$2+c8+'\izm'p縫L$;Bsr! }]C?&xrx9J2V5t^hxºm@{g??ayֿcom/L A!}FeIF&uE#)zk/ؼ'q~]*-IOgMI]N4f?_6Oz1R_N|(QEQEQEQEQEQE(3 IQw z~?o7S?' F-FţrsC#o=_"^p'8_@ /I¿6c c'!FkOp'#7<'U-#eU*X?~qO ?ni'r \lDbMxs=UlQ7g{ץO d2湺RǐbE;&Q3bGf_Kq]ia1q#X_FJk~ź9-~8,;CZ>8'l0:T-QL(((((7g\e 4߆^ T-ճ9 -_a% \+ =ᣫ:nuLeПBLQ&T/\y?j'H99d[I=0+s_bד'܄V8ٸ \y.ާaF0sy=jZ5ځ@I_|QE((((((=(4Y#6gOd .pap!kt@mt>+]ּhxbFm5[ wko+gD.˖K Nj(.)O!_~wM…}B% ʞ`]FY$WGQ߹6'cJZ(?5 ( ( ( ( ( ( CҖn' _ws?(`X{>YIٿ*|g[ cVC 3ȚMdB1]k\7˪:G&o@V7T5601^ilZ1ӥfeFW?T:]-~۹L~Ka>V]<y͞C/M`qSs]<#^x;@ǠU`%WTs69­AqJ LTMexLW3W6W$ۂG Wq=H {ׯJUW>G;T4>xGɟrj?i% k[YEpUߥ9VǁEMj1J)Qҿy~oT<QU5J((((((((@5jև%|>^V9Щ?Mȃڃĝ&8̗1my'| %MT(X"]?iц]yA>x׃$x,srӢ&It] d8S [HnĤCp9J/n?M9lm9W;-tM?Zؓҏ}U41E$?t?g>r(u9I(mD#c+˼R3,0*i$U %G;vvj=b6IvVO_%?moWTv2|Cp%QEQEQEQEQEQEQEQEVfaooP.mQ"?VD=s(vg.4{ vo.%,Xr6>^ͥ&q[WG?~38[jΚGϜmTt8ɱ^&]ٴ^c 7c[g/~ yn#VntsU UWT@xG#F'Ҭ4/JдGT ?xV1ғQ17qa#Uε1Vm|pkx#w9jލzǟxua1k?wySU/$˴ҡ#) ɖR:T'|G)l˜@k\ѿ?bo%RHq[߱mx)F9|ke/mi@HR-~w?J(c ( ( ( ( ( ( ( 1) jwl^[{(yR#CWã6(ҿo~#ig\}@gnp+h'IHܬA~(]O_(zk:Ճjo1cczZg7-dc?=ly)IJsHL# Ҩ]t9YfL uW*^h+/}N w< C-l~moH~"O`ڷ_ (7޹i^N5F: q_υ=G:f=0(((((((()JZC@ncM $e?0Gf6MFh}Y~?z:Ck?ڇ%O+ķ#1Rd3o 8{T|;UJ2a\U5m^K}LƀgVϐ}P83\Ѯ(b (g!j>fj *sɯg%>2 uC c}|(xG\gҿlzO~8|7RN|ik: BLq_χ2: v?nyFMEWڟ&QEQEQEQEQEQEQEQEwQ@O_zٿn?Po.r+}+1xReolʿ.sDө;f=Z?wQbR88@:Wة5"Ug ־Ӛp¦TcY:y'VοUǗ co`(((((((((-<eOOEX/9_wS0XrN08vz_ɾ..sJkeJ/)G0]6Y%¶uKIJ5񖛭M|J~5JNk͸Mԭ)E**&OB=8e_yڤ\o4^,xFmK,vQ+a uLu܍^;'f(zcO\LXyW|=*XJ]UFKsfY !#>չtXE~!'_|@_i8Ֆ +'?Я( 6㷭eHI|ΤRZ(((((((((?t:ʼnدoAx~ bqeSx⿝iCd<7ǐ{4<DA =7~+_Z=,_ W*+}ϽeF>[H5ۂt ^@ݏĐ05j)k Rw={TiFf|CH|C?:y$irzz,H @c^`#T6aX'*x]8L+#qXnXr=у_Z^Ys)૨a_&g[wFk yk_J%625u.Tsۼ[F}gE K_j|QEQEQEQEQEQEQEQETRc<!8=+S?dxƵ΋nu- ke+H r*Xr2y4bSf6~.a|C.>W9O5j>'*U|<9[lyUJڟҾ]`#Ʊ@)XS\lu|*l~y>f1)aPw@UASsOұgEe DKxKϊ,0#{J懅w%%:&Fʃw5xQ(};vZeF G޿CS&n>#|e;~&JHh_1fm'0J\^I׬F5/W:7e/ 6ۦ;xJ D{Vek"щڂ ~AgGׂxn s;:1FxLc޾4?%cxD|an. W̙`;LV"vP uך|&|O ·")!6qs^3ڻdĘQE ( ( ( ( ( ( (_|u?*-Z|=nXaC.7#{ʌ{J^8烚y u}+\r^JF!5t0RG]Fv4|>ÚuƃqCg'5vr<~%©|#//MapGКL%GYap+$ݎZCD|6/vp=+j #k-~1ҿY /Mh!A#oY|@vTa`g oiqk+P?AZu~_կW~] EIs``WCGſ~5<~$YƴoorucTlv7E~hv+nۂg=yt%RL#I+z4_YM<ʑirq6_=n<]ok][tϯҿhuxoJ35e q'r@9=o//vX1.,K]B'-v^^6*IFuPRLo睤ja+?nR Lj׼Y+RͶ*<$3QUCW p8:Sw4iuQ@Š(((((((G^AᦁSDžCA@\Om!bZL42/<0AaLIcOKkO~-c2Z\."LI뎵| [_35;,P9I*4au R+_rM''1N*ЖN<txp$_=_9#m&qiǣ~?|DYn=[j9NzKwt]:wQ,}My_BsR%?7W浍5 wdofj~>h|w79ǐ<÷vS骧ij>бXCߋ j8YmZ ~4a1-64b8#º.Ե?=)c[] N ˷Uǽ5NWSw M9t륕TIݛ;wg$址>x/]־$j0|;+iQyU{^ {~i |3"-[+,"U0:WRq|<𵗂hl47O[D8vII$Aa:cedfnEU((((((((L 4b B)T*\T*84 Ӱ=(֠QE0 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?pint-0.24.4/docs/_static/style.css000066400000000000000000000015721471316474000170170ustar00rootroot00000000000000@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&family=Open+Sans:ital,wght@0,400;0,600;1,400;1,600&display=swap'); body { font-family: 'Open Sans', sans-serif; } h1 { font-family: "Lato", sans-serif; } pre, code { font-size: 100%; line-height: 155%; } /* Main page overview cards */ .sd-card { border-radius: 0; padding: 30px 10px 20px 10px; margin: 10px 0px; } .sd-card .sd-card-header { text-align: center; } .sd-card .sd-card-header .sd-card-text { margin: 0px; } .sd-card .sd-card-img-top { height: 52px; width: 52px; margin-left: auto; margin-right: auto; } .sd-card .sd-card-header { border: none; color: #150458; font-size: var(--pst-font-size-h5); font-weight: bold; padding: 2.5rem 0rem 0.5rem 0rem; } html[data-theme=dark] { .sd-card .sd-card-header { color: #FFF; } } pint-0.24.4/docs/_templates/000077500000000000000000000000001471316474000156475ustar00rootroot00000000000000pint-0.24.4/docs/_templates/sidebarintro.html000066400000000000000000000013341471316474000212230ustar00rootroot00000000000000

About Pint

Units in Python. You are currently looking at the documentation of version {{ version }}.

Other Formats

You can download the documentation in other formats as well:

Useful Links

pint-0.24.4/docs/_templates/sidebarlogo.html000066400000000000000000000015551471316474000210350ustar00rootroot00000000000000

Logo

About Pint

Units in Python. You are currently looking at the documentation of version {{ version }}.

Other Formats

You can download the documentation in other formats as well:

Useful Links

pint-0.24.4/docs/advanced/000077500000000000000000000000001471316474000152575ustar00rootroot00000000000000pint-0.24.4/docs/advanced/currencies.rst000066400000000000000000000067061471316474000201640ustar00rootroot00000000000000.. _currencies: Using Pint for currency conversions =================================== Currency conversion tends to be substantially more complex than physical units. The exact exchange rate between two currencies: - changes every minute, - changes depending on the place, - changes depending on who you are and who changes the money, - may be not reversible, e.g. EUR->USD may not be the same as 1/(USD->EUR), - different rates may apply to different amounts of money, e.g. in a BUY/SELL ledger, - frequently involves fees, whose calculation can be more or less sophisticated. For example, a typical credit card contract may state that the bank will charge you a fee on all purchases in foreign currency of 1 USD or 2%, whichever is higher, for all amounts less than 1000 USD, and then 1.5% for anything in excess. You may implement currencies in two ways, both of which require you to be familiar with :ref:`contexts`. Simplified model ---------------- This model implies a few strong assumptions: - There are no conversion fees - All exchange rates are reversible - Any amount of money can be exchanged at the same rate - All exchanges can happen at the same time, between the same actors. In this simplified scenario, you can perform any round-trip across currencies and always come back with the original money; e.g. 1 USD -> EUR -> JPY -> GBP -> USD will always give you 1 USD. In reality, these assumptions almost never happen but can be a reasonable approximation, for example in the case of large financial institutions, which can use interbank exchange rates and have nearly-limitless liquidity and sub-second trading systems. This can be implemented by putting all currencies on the same dimension, with a default conversion rate of NaN, and then setting the rate within contexts:: USD = [currency] EUR = nan USD JPY = nan USD GBP = nan USD @context FX EUR = 1.11254 USD GBP = 1.16956 EUR @end Note how, in the example above: - USD is our *base currency*. It is arbitrary, only matters for the purpose of invoking ``to_base_units()``, and can be changed with :ref:`systems`. - We did not set a value for JPY - maybe because the trader has no availability, or because the data source was for some reason missing up-to-date data. Any conversion involving JPY will return NaN. - We redefined GBP to be a function of EUR instead of USD. This is fine as long as there is a path between two currencies. Full model ---------- If any of the assumptions of the simplified model fails, one can resort to putting each currency on its own dimension, and then implement transformations:: EUR = [currency_EUR] GBP = [currency_GBP] @context FX GBP -> EUR: value * 1.11108 EUR/GBP EUR -> GBP: value * 0.81227 GBP/EUR @end .. code-block:: python >>> q = ureg.Quantity("1 EUR") >>> with ureg.context("FX"): ... q = q.to("GBP").to("EUR") >>> q 0.9024969516 EUR More sophisticated formulas, e.g. dealing with flat fees and thresholds, can be implemented with arbitrary python code by programmatically defining a context (see :ref:`contexts`). Currency Symbols ---------------- Many common currency symbols are not supported by the pint parser. A preprocessor can be used as a workaround: .. doctest:: >>> import pint >>> ureg = pint.UnitRegistry(preprocessors = [lambda s: s.replace("€", "EUR")]) >>> ureg.define("euro = [currency] = € = EUR") >>> print(ureg.Quantity("1 €")) 1 euro pint-0.24.4/docs/advanced/custom-registry-class.rst000066400000000000000000000066221471316474000223020ustar00rootroot00000000000000.. _custom_registry_class: Custom registry class ===================== Pay as you go ------------- Pint registry functionality is divided into facets. The default UnitRegistry inherits from all of them, providing a full fledged and feature rich registry. However, in certain cases you might want to have a simpler and lighter registry. Just pick what you need and create your own. - FormattingRegistry: adds the capability to format quantities and units into string. - SystemRegistry: adds the capability to work with system of units. - GroupRegistry: adds the capability to group units. - MeasurementRegistry: adds the capability to handle measurements (quantities with uncertainties). - NumpyRegistry: adds the capability to interoperate with NumPy. - DaskRegistry: adds the capability to interoperate with Dask. - ContextRegistry: the capability to contexts: predefined conversions between incompatible dimensions. - NonMultiplicativeRegistry: adds the capability to handle nonmultiplicative units (offset, logarithmic). - PlainRegistry: base implementation for registry, units and quantities. The only required one is `PlainRegistry`, the rest are completely optional. For example: .. doctest:: >>> import pint >>> class MyRegistry(pint.facets.NonMultiplicativeRegistry): ... pass .. note:: `NonMultiplicativeRegistry` is a subclass from `PlainRegistry`, and therefore it is not required to add it explicitly to `MyRegistry` bases. You can add some specific functionality to your new registry. .. doctest:: >>> import pint >>> class MyRegistry(pint.UnitRegistry): ... ... def my_specific_function(self): ... """Do something ... """ Custom Quantity and Unit class ------------------------------ You can also create your own Quantity and Unit class, you must derive from Quantity (or Unit) and tell your registry about it. For example, if you want to create a new `UnitRegistry` subclass you need to derive the Quantity and Unit classes from it. .. doctest:: >>> import pint >>> class MyQuantity(pint.UnitRegistry.Quantity): ... ... # Notice that subclassing pint.Quantity ... # is not necessary. ... # Pint will inspect the Registry class and create ... # a Quantity class that contains all the ... # required parents. ... ... def to_my_desired_format(self): ... """Do something else ... """ ... >>> class MyUnit(pint.UnitRegistry.Unit): ... ... # Notice that subclassing pint.Quantity ... # is not necessary. ... # Pint will inspect the Registry class and create ... # a Quantity class that contains all the ... # required parents. ... ... def to_my_desired_format(self): ... """Do something else ... """ Then, you need to create a custom registry but deriving from `GenericUnitRegistry` so you can specify the types of .. doctest:: >>> from typing_extensions import TypeAlias # Python 3.9 >>> # from typing import TypeAlias # Python 3.10+ >>> class MyRegistry(pint.registry.GenericUnitRegistry[MyQuantity, pint.Unit]): ... ... Quantity: TypeAlias = MyQuantity ... Unit: TypeAlias = MyUnit ... While these examples demonstrate how to add functionality to the default registry class, you can actually subclass just the `PlainRegistry`, and `GenericPlainRegistry`. pint-0.24.4/docs/advanced/defining.rst000066400000000000000000000126351471316474000176030ustar00rootroot00000000000000.. _defining: Defining units ============== In a definition file -------------------- To define units in a persistent way you need to create a unit definition file. Such files are simple text files in which the units are defined as function of other units. For example this is how the minute and the hour are defined in `default_en.txt`:: hour = 60 * minute = h = hr minute = 60 * second = min It is quite straightforward, isn't it? We are saying that `minute` is `60 seconds` and is also known as `min`. 1. The first word is always the canonical name. 2. Next comes the definition (based on other units). 3. Next, optionally, there is the unit symbol. 4. Finally, again optionally, a list of aliases, separated by equal signs. If one wants to specify aliases but not a symbol, the symbol should be conventionally set to ``_``; e.g.:: millennium = 1e3 * year = _ = millennia The order in which units are defined does not matter, Pint will resolve the dependencies to define them in the right order. What is important is that if you transverse all definitions, a reference unit is reached. A reference unit is not defined as a function of another units but of a dimension. For the time in `default_en.txt`, this is the `second`:: second = [time] = s = sec By defining `second` as equal to a string `time` in square brackets we indicate that: * `time` is a physical dimension. * `second` is a reference unit. The ability to define basic physical dimensions as well as reference units allows to construct arbitrary units systems. Pint is shipped with a default definition file named `default_en.txt` where `en` stands for English. You can add your own definitions to the end of this file but you will have to be careful to merge when you update Pint. An easier way is to create a new file (e.g. `mydef.txt`) with your definitions:: dog_year = 52 * day = dy and then in Python, you can load it as: >>> from pint import UnitRegistry >>> # First we create the registry. >>> ureg = UnitRegistry() >>> # Then we append the new definitions >>> ureg.load_definitions('/your/path/to/my_def.txt') # doctest: +SKIP If you make a translation of the default units or define a completely new set, you don't want to append the translated definitions so you just give the filename to the constructor:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry('/your/path/to/default_es.txt') # doctest: +SKIP In the definition file, prefixes are identified by a trailing dash:: yocto- = 10.0**-24 = y- It is important to note that prefixed defined in this way can be used with any unit, including non-metric ones (e.g. kiloinch is valid for Pint). This simplifies definitions files enormously without introducing major problems. Pint, like Python, believes that we are all consenting adults. Derived dimensions are defined as follows:: [density] = [mass] / [volume] Note that primary dimensions don't need to be declared; they can be defined for the first time as part of a unit definition. Finally, one may add aliases to an already existing unit definition:: @alias meter = metro = metr This is particularly useful when one wants to enrich definitions from defaults_en.txt with new aliases from a custom file. It can also be used for translations (like in the example above) as long as one is happy to have the localized units automatically converted to English when they are parsed. Programmatically ---------------- You can easily add units, dimensions, or aliases to the registry programmatically. Let's add a dog_year (sometimes written as dy) equivalent to 52 (human) days: .. doctest:: >>> from pint import UnitRegistry >>> # We first instantiate the registry. >>> # If we do not provide any parameter, the default unit definitions are used. >>> ureg = UnitRegistry() >>> Q_ = ureg.Quantity # Here we add the unit >>> ureg.define('dog_year = 52 * day = dy') # We create a quantity based on that unit and we convert to years. >>> lassie_lifespan = Q_(10, 'year') >>> print(lassie_lifespan.to('dog_years')) 70.240384... dog_year Note that we have used the name `dog_years` even though we have not defined the plural form as an alias. Pint takes care of that, so you don't have to. Plural forms that aren't simply built by adding a 's' suffix to the singular form should be explicitly stated as aliases (see for example ``millennia`` above). You can also add prefixes programmatically: .. doctest:: >>> ureg.define('myprefix- = 30 = my-') where the number indicates the multiplication factor. Same for aliases and derived dimensions: .. doctest:: >>> ureg.define('@alias meter = metro = metr') >>> ureg.define('[hypervolume] = [length] ** 4') .. warning:: Units, prefixes, aliases and dimensions added programmatically are forgotten when the program ends. Units with constants -------------------- Some units, such as ``L/100km``, contain constants. These can be defined with a leading underscore: .. doctest:: >>> ureg.define('_100km = 100 * kilometer') >>> ureg.define('mpg = 1 * mile / gallon') >>> fuel_ec_europe = 5 * ureg.L / ureg._100km >>> fuel_ec_us = (1 / fuel_ec_europe).to(ureg.mpg) Checking if a unit is already defined ------------------------------------- The python ``in`` keyword works as expected with unit registries. Check if a unit has been defined with the following: .. doctest:: >>> 'MHz' in ureg True >>> 'gigatrees' in ureg False pint-0.24.4/docs/advanced/index.rst000066400000000000000000000004251471316474000171210ustar00rootroot00000000000000Advanced Guides =============== Pint contains some useful and fun feature. You will find them here. .. toctree:: :maxdepth: 2 :hidden: performance serialization defining wrapping measurement pitheorem currencies custom-registry-class pint-0.24.4/docs/advanced/measurement.rst000066400000000000000000000040631471316474000203410ustar00rootroot00000000000000.. _measurement: Using Measurements ================== If you have the `Uncertainties package`_ installed, you can use Pint to keep track of measurements with specified uncertainty, and not just exact physical quantities. Measurements are the combination of two quantities: the mean value and the error (or uncertainty). The easiest ways to generate a measurement object is from a quantity using the ``plus_minus()`` method. .. doctest:: :skipif: not_installed['uncertainties'] >>> import numpy as np >>> from pint import UnitRegistry >>> ureg = UnitRegistry() >>> book_length = (20. * ureg.centimeter).plus_minus(2.) >>> print(book_length) (20.0 +/- 2.0) centimeter You can inspect the mean value, the absolute error and the relative error: .. doctest:: :skipif: not_installed['uncertainties'] >>> print(book_length.value) 20.0 centimeter >>> print(book_length.error) 2.0 centimeter >>> print(book_length.rel) 0.1 You can also create a Measurement object giving the relative error: .. doctest:: :skipif: not_installed['uncertainties'] >>> book_length = (20. * ureg.centimeter).plus_minus(.1, relative=True) >>> print(book_length) (20.0 +/- 2.0) centimeter Measurements support the same formatting codes as Quantity. For example, to pretty print a measurement with 2 decimal positions: .. doctest:: :skipif: not_installed['uncertainties'] >>> print('{:.02fP}'.format(book_length)) (20.00 ± 2.00) centimeter Mathematical operations with Measurements, return new measurements following the `Propagation of uncertainty`_ rules. .. doctest:: :skipif: not_installed['uncertainties'] >>> print(2 * book_length) (40 +/- 4) centimeter >>> width = (10 * ureg.centimeter).plus_minus(1) >>> print('{:.02f}'.format(book_length + width)) (30.00 +/- 2.24) centimeter .. note:: Only linear combinations are currently supported. .. _`Propagation of uncertainty`: http://en.wikipedia.org/wiki/Propagation_of_uncertainty .. _`Uncertainties package`: https://uncertainties-python-package.readthedocs.io/en/latest/ pint-0.24.4/docs/advanced/performance.rst000066400000000000000000000132571471316474000203220ustar00rootroot00000000000000.. _performance: Optimizing Performance ====================== Pint can impose a significant performance overhead on computationally-intensive problems. The following are some suggestions for getting the best performance. .. note:: Examples below are based on the IPython shell (which provides the handy %timeit extension), so they will not work in a standard Python interpreter. Use magnitudes when possible ---------------------------- It's significantly faster to perform mathematical operations on magnitudes (even though you're still using pint to retrieve them from a quantity object). .. ipython:: :verbatim: In [1]: from pint import UnitRegistry In [2]: ureg = UnitRegistry() In [3]: q1 = ureg('1m') In [5]: q2 = ureg('2m') In [6]: %timeit (q1 - q2) 8.24 us +- 44.5 ns per loop (mean +- std. dev. of 7 runs, 100,000 loops each) In [7]: %timeit (q1.magnitude - q2.magnitude) 214 ns +- 2.39 ns per loop (mean +- std. dev. of 7 runs, 1,000,000 loops each) This is especially important when using pint Quantities in conjunction with an iterative solver, such as the `brentq method`_ from scipy: .. ipython:: :verbatim: In [1]: from scipy.optimize import brentq In [2]: def foobar_with_quantity(x): ...: # find the value of x that equals q2 ...: ...: # assign x the same units as q2 ...: qx = ureg(str(x)+str(q2.units)) ...: ...: # compare the two quantities, then take their magnitude because ...: # brentq requires a dimensionless return type ...: return (qx - q2).magnitude In [3]: def foobar_with_magnitude(x): ...: # find the value of x that equals q2 ...: ...: # don't bother converting x to a quantity, just compare it with q2's magnitude ...: return x - q2.magnitude In [4]: %timeit brentq(foobar_with_quantity,0,q2.magnitude) 286 us +- 9.05 us per loop (mean +- std. dev. of 7 runs, 1,000 loops each) In [5]: %timeit brentq(foobar_with_magnitude,0,q2.magnitude) 1.14 us +- 21.3 ns per loop (mean +- std. dev. of 7 runs, 1,000,000 loops each) Bear in mind that altering computations like this **loses the benefits of automatic unit conversion**, so use with care. A safer method: wrapping ------------------------ A better way to use magnitudes is to use pint's wraps decorator (See :ref:`wrapping`). By decorating a function with wraps, you pass only the magnitude of an argument to the function body according to units you specify. As such this method is safer in that you are sure the magnitude is supplied in the correct units. .. ipython:: In [1]: import pint In [2]: ureg = pint.UnitRegistry() In [3]: import numpy as np In [4]: def f(x, y): ...: return (x - y) / (x + y) * np.log(x/y) In [5]: @ureg.wraps(None, ('meter', 'meter')) ...: def g(x, y): ...: return (x - y) / (x + y) * np.log(x/y) In [6]: a = 1 * ureg.meter In [7]: b = 1 * ureg.centimeter In [8]: %timeit f(a, b) 1000 loops, best of 3: 312 µs per loop In [9]: %timeit g(a, b) 10000 loops, best of 3: 65.4 µs per loop Speed up registry instantiation ------------------------------- When the registry is instantiated, the definition file is parsed, loaded and some pre-calculations are made to speed-up certain common operations. This process can be time consuming for a large definition file such as the default one (and very comprehensive) provided with pint. This can have a significant impact in command line applications that create and drop registries. Since version 0.19, part of this process can be cached resulting in a 5x to 20x performance improvement for registry instantiation using an included version of flexcache_. This feature is experimental and therefore disabled by default, but might be enable in future versions. To enable this feature just use the `cache_folder` argument to provide (as a str or pathlib.Path) the location where the cache will be saved. .. code-block:: python >>> import pint >>> ureg = pint.UnitRegistry(cache_folder="/my/cache/folder") # doctest: +SKIP If you want to use the default cache folder provided by the OS, use **:auto:** .. code-block:: python >>> import pint >>> ureg = pint.UnitRegistry(cache_folder=":auto:") # doctest: +SKIP Pint use an external dependency of platformdirs_ to obtain the correct folder, for example in macOS is `/Users//Library/Caches/pint` In any case, you can check the location of the cache folder. .. code-block:: python >>> ureg.cache_folder # doctest: +SKIP .. note:: Cached files are stored in pickle format with a unique name generated from hashing the path of the original definition file. This hash also includes the platform (e.g. 'Linux'), python implementation (e.g. ‘CPython'), python version, pint version and the `non_int_type` setting of the UnitRegistry to avoid mixing incompatible caches. If the definition file includes another (using the `@import` directive), this latter file will be cached independently. Finally, when a definition file is loaded upon registry instantiation the RegistryCache is also cached. The cache is invalidated based on the content hash. Therefore, if you modify the text definition file a new cache file will be generated. Caching by content hash allows sharing the same cache across multiple environments that use the same python and pint versions. At any moment, you can delete the cache folder without any risk. .. _`brentq method`: http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brentq.html .. _platformdirs: https://pypi.org/project/platformdirs .. _flexcache: https://github.com/hgrecco/flexcache/ pint-0.24.4/docs/advanced/pitheorem.rst000066400000000000000000000073341471316474000200140ustar00rootroot00000000000000.. _pitheorem: Buckingham Pi Theorem ===================== `Buckingham π theorem`_ states that an equation involving *n* number of physical variables which are expressible in terms of *k* independent fundamental physical quantities can be expressed in terms of *p = n - k* dimensionless parameters. .. testsetup:: * from pint import UnitRegistry ureg = UnitRegistry() Q_ = ureg.Quantity To start with a very simple case, consider that you want to find a dimensionless quantity involving the magnitudes `V`, `T` and `L` with dimensions `[length]/[time]`, `[time]` and `[length]` respectively. .. doctest:: >>> from pint import pi_theorem >>> pi_theorem({'V': '[length]/[time]', 'T': '[time]', 'L': '[length]'}) [{'V': 1.0, 'T': 1.0, 'L': -1.0}] The result indicates that a dimensionless quantity can be obtained by multiplying `V` by `T` and the inverse of `L`. Which can be pretty printed using the `Pint` formatter: .. doctest:: >>> from pint import formatter >>> result = pi_theorem({'V': '[length]/[time]', 'T': '[time]', 'L': '[length]'}) >>> numerator = [item for item in result[0].items() if item[1]>0] >>> denominator = [item for item in result[0].items() if item[1]<0] >>> print(formatter(numerator, denominator)) V * T / L You can also apply the Buckingham π theorem associated to a Registry. In this case, you can use derived dimensions such as speed: .. doctest:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry() >>> ureg.pi_theorem({'V': '[speed]', 'T': '[time]', 'L': '[length]'}) [{'V': 1.0, 'T': 1.0, 'L': -1.0}] or unit names: .. doctest:: >>> ureg.pi_theorem({'V': 'meter/second', 'T': 'second', 'L': 'meter'}) [{'V': 1.0, 'T': 1.0, 'L': -1.0}] or quantities: >>> Q_ = ureg.Quantity >>> ureg.pi_theorem({'V': Q_(1, 'meter/second'), ... 'T': Q_(1, 'second'), ... 'L': Q_(1, 'meter')}) [{'V': 1.0, 'T': 1.0, 'L': -1.0}] Application to the pendulum --------------------------- There are 3 fundamental physical units in this equation: time, mass, and length, and 4 dimensional variables, T (oscillation period), M (mass), L (the length of the string), and g (earth gravity). Thus we need only 4 − 3 = 1 dimensionless parameter. .. doctest:: >>> ureg.pi_theorem({'T': '[time]', ... 'M': '[mass]', ... 'L': '[length]', ... 'g': '[acceleration]'}) [{'T': 2.0, 'L': -1.0, 'g': 1.0}] which means that the dimensionless quantity is: .. math:: \Pi = \frac{g T^2}{L} and therefore: .. math:: T = constant \sqrt{\frac{L}{g}} (In case you wonder, the constant is equal to 2 π, but this is outside the scope of this help) Pressure loss in a pipe ----------------------- What is the pressure loss `p` in a pipe with length `L` and diameter `D` for a fluid with density `d`, and viscosity `m` travelling with speed `v`? As pressure, mass, volume, viscosity and speed are defined as derived dimensions in the registry, we only need to explicitly write the density dimensions. .. doctest:: >>> ureg.pi_theorem({'p': '[pressure]', ... 'L': '[length]', ... 'D': '[length]', ... 'd': '[mass]/[volume]', ... 'm': '[viscosity]', ... 'v': '[speed]' ... }) # doctest: +SKIP [{'p': 1.0, 'm': -2.0, 'd': 1.0, 'L': 2.0}, {'v': 1.0, 'm': -1.0, 'd': 1.0, 'L': 1.0}, {'L': -1.0, 'D': 1.0}] The second dimensionless quantity is the `Reynolds Number`_ .. _`Buckingham π theorem`: http://en.wikipedia.org/wiki/Buckingham_%CF%80_theorem .. _`Reynolds Number`: http://en.wikipedia.org/wiki/Reynolds_number pint-0.24.4/docs/advanced/serialization.rst000066400000000000000000000104301471316474000206640ustar00rootroot00000000000000 Serialization ============= In order to dump a **Quantity** to disk, store it in a database or transmit it over the wire you need to be able to serialize and then deserialize the object. The easiest way to do this is by converting the quantity to a string: .. doctest:: >>> import pint >>> ureg = pint.UnitRegistry() >>> duration = 24.2 * ureg.years >>> duration >>> serialized = str(duration) >>> print(serialized) 24.2 year Remember that you can easily control the number of digits in the representation as shown in :ref:`sec-string-formatting`. You dump/store/transmit the content of serialized ('24.2 year'). When you want to recover it in another process/machine, you just: .. doctest:: >>> import pint >>> ureg = pint.UnitRegistry() >>> duration = ureg('24.2 year') >>> print(duration) 24.2 year Notice that the serialized quantity is likely to be parsed in **another** registry as shown in this example. Pint Quantities do not exist on their own but they are always related to a **UnitRegistry**. Everything will work as expected if both registries, are compatible (e.g. they were created using the same definition file). However, things could go wrong if the registries are incompatible. For example, **year** could not be defined in the target registry. Or what is even worse, it could be defined in a different way. Always have to keep in mind that the interpretation and conversion of Quantities are UnitRegistry dependent. In certain cases, you want a binary representation of the data. Python's standard algorithm for serialization is called Pickle_. Pint quantities implement the magic `__reduce__` method and therefore can be *Pickled* and *Unpickled*. However, you have to bear in mind, that the **application registry** is used for unpickling and this might be different from the one that was used during pickling. By default, the application registry is one initialized with :file:`defaults_en.txt`; in other words, the same as what you get when creating a :class:`pint.UnitRegistry` without arguments and without adding any definitions afterwards. If your application is fine just using :file:`defaults_en.txt`, you don't need to worry further. If your application needs a single, global registry with custom definitions, you must make sure that it is registered using :func:`pint.set_application_registry` before unpickling anything. You may use :func:`pint.get_application_registry` to get the current instance of the application registry. Finally, if you need multiple custom registries, it's impossible to correctly unpickle :class:`pint.Quantity` or :class:`pint.Unit` objects.The best way is to create a tuple with the magnitude and the units: .. doctest:: >>> to_serialize = duration.to_tuple() >>> print(to_serialize) (24.2, (('year', 1),)) And then you can just pickle that: .. doctest:: >>> import pickle >>> serialized = pickle.dumps(to_serialize, -1) To unpickle, just .. doctest:: >>> loaded = pickle.loads(serialized) >>> ureg.Quantity.from_tuple(loaded) (To pickle to and from a file just use the dump and load method as described in _Pickle) You can use the same mechanism with any serialization protocol, not only with binary ones. (In fact, version 0 of the Pickle protocol is ASCII). Other common serialization protocols/packages are json_, yaml_, shelve_, hdf5_ (or via PyTables_) and dill_. Notice that not all of these packages will serialize properly the magnitude (which can be any numerical type such as `numpy.ndarray`). Using the serialize_ package you can load and read from multiple formats: .. doctest:: :skipif: not_installed['serialize'] >>> from serialize import dump, load, register_class >>> register_class(ureg.Quantity, ureg.Quantity.to_tuple, ureg.Quantity.from_tuple) >>> dump(duration, 'output.yaml') >>> r = load('output.yaml') (Check out the serialize_ docs for more information) .. _serialize: https://github.com/hgrecco/serialize .. _Pickle: http://docs.python.org/3/library/pickle.html .. _json: http://docs.python.org/3/library/json.html .. _yaml: http://pyyaml.org/ .. _shelve: http://docs.python.org/3.6/library/shelve.html .. _hdf5: http://www.h5py.org/ .. _PyTables: http://www.pytables.org .. _dill: https://pypi.python.org/pypi/dill pint-0.24.4/docs/advanced/wrapping.rst000066400000000000000000000171771471316474000176550ustar00rootroot00000000000000.. _wrapping: Wrapping and checking functions =============================== In some cases you might want to use pint with a pre-existing web service or library which is not units aware. Or you might want to write a fast implementation of a numerical algorithm that requires the input values in some specific units. For example, consider a function to return the period of the pendulum within a hypothetical physics library. The library does not use units, but instead requires you to provide numerical values in certain units: .. testsetup:: * import math G = 9.806650 def pendulum_period(length): return 2*math.pi*math.sqrt(length/G) def pendulum_period2(length, swing_amplitude): pass def pendulum_period_maxspeed(length, swing_amplitude): pass def pendulum_period_error(length): pass .. doctest:: >>> from simple_physics import pendulum_period # doctest: +SKIP >>> help(pendulum_period) # doctest: +SKIP Help on function pendulum_period in module simple_physics: pendulum_period(length) Return the pendulum period in seconds. The length of the pendulum must be provided in meters. >>> pendulum_period(1) 2.0064092925890407 This behaviour is very error prone, in particular when combining multiple libraries. You could wrap this function to use Quantities instead: .. doctest:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry() >>> Q_ = ureg.Quantity >>> def mypp_caveman(length): ... return pendulum_period(length.to(ureg.meter).magnitude) * ureg.second and: .. doctest:: >>> mypp_caveman(100 * ureg.centimeter) Pint provides a more convenient way to do this: .. doctest:: >>> mypp = ureg.wraps(ureg.second, ureg.meter)(pendulum_period) Or in the decorator format: .. doctest:: >>> @ureg.wraps(ureg.second, ureg.meter) ... def mypp(length): ... return pendulum_period(length) >>> mypp(100 * ureg.centimeter) `wraps` takes 3 input arguments: - **ret**: the return units. Use None to skip conversion. - **args**: the inputs units for each argument, as an iterable. Use None to skip conversion of any given element. - **strict**: if `True` all convertible arguments must be a Quantity and others will raise a ValueError (True by default) Strict Mode ----------- By default, the function is wrapped in `strict` mode. In this mode, the input arguments assigned to units must be a Quantities. .. doctest:: >>> mypp(1. * ureg.meter) >>> mypp(1.) Traceback (most recent call last): ... ValueError: A wrapped function using strict=True requires quantity for all arguments with not None units. (error found for meter, 1.0) To enable using non-Quantity numerical values, set strict to False`. .. doctest:: >>> mypp_ns = ureg.wraps(ureg.second, ureg.meter, False)(pendulum_period) >>> mypp_ns(1. * ureg.meter) >>> mypp_ns(1.) In this mode, the value is assumed to have the correct units. Multiple arguments or return values ----------------------------------- For a function with more arguments, use a tuple: .. doctest:: >>> from simple_physics import pendulum_period2 # doctest: +SKIP >>> help(pendulum_period2) # doctest: +SKIP Help on function pendulum_period2 in module simple_physics: pendulum_period2(length, swing_amplitude) Return the pendulum period in seconds. The length of the pendulum must be provided in meters. The swing_amplitude must be in radians. >>> mypp2 = ureg.wraps(ureg.second, (ureg.meter, ureg.radians))(pendulum_period2) ... Or if the function has multiple outputs: .. doctest:: >>> mypp3 = ureg.wraps((ureg.second, ureg.meter / ureg.second), ... (ureg.meter, ureg.radians))(pendulum_period_maxspeed) ... If there are more return values than specified units, ``None`` is assumed for the extra outputs. For example, given the NREL SOLPOS calculator that outputs solar zenith, azimuth and air mass, the following wrapper assumes no units for airmass .. code-block:: python @ureg.wraps(('deg', 'deg'), ('deg', 'deg', 'millibar', 'degC')) def solar_position(lat, lon, press, tamb, timestamp): return zenith, azimuth, airmass Optional arguments ------------------ For a function with named keywords with optional values, use a tuple for all arguments: .. doctest:: >>> @ureg.wraps(ureg.second, (ureg.meters, ureg.meters/ureg.second**2, None)) ... def calculate_time_to_fall(height, gravity=Q_(9.8, 'm/s^2'), verbose=False): ... """Calculate time to fall from a height h. ... ... By default, the gravity is assumed to be earth gravity, ... but it can be modified. ... ... d = .5 * g * t**2 ... t = sqrt(2 * d / g) ... """ ... t = math.sqrt(2 * height / gravity) ... if verbose: print(str(t) + " seconds to fall") ... return t ... >>> lunar_module_height = Q_(22, 'feet') + Q_(11, 'inches') >>> calculate_time_to_fall(lunar_module_height, verbose=True) 1.1939473204801092 seconds to fall >>> moon_gravity = Q_(1.625, 'm/s^2') >>> calculate_time_to_fall(lunar_module_height, gravity=moon_gravity) Specifying relations between arguments -------------------------------------- In certain cases, you may not be concerned with the actual units and only care about the unit relations among arguments. This is done using a string starting with the equal sign `=`: .. doctest:: >>> @ureg.wraps('=A**2', ('=A', '=A')) ... def sqsum(x, y): ... return x * x + 2 * x * y + y * y which can be read as the first argument (`x`) has certain units (we labeled them `A`), the second argument (`y`) has the same units as the first (`A` again). The return value has the unit of `x` squared (`A**2`) You can use more than one label: .. doctest:: >>> @ureg.wraps('=A**2*B', ('=A', '=A*B', '=B')) ... def some_function(x, y, z): ... pass With optional arguments .. doctest:: >>> @ureg.wraps('=A*B', ('=A', '=B')) ... def get_displacement(time, rate=Q_(1, 'm/s')): ... return time * rate ... >>> get_displacement(Q_(2, 's')) >>> get_displacement(Q_(2, 's'), Q_(1, 'deg/s')) Ignoring an argument or return value ------------------------------------ To avoid the conversion of an argument or return value, use None .. doctest:: >>> mypp3 = ureg.wraps((ureg.second, None), ureg.meter)(pendulum_period_error) Checking dimensionality ----------------------- When you want pint quantities to be used as inputs to your functions, pint provides a wrapper to ensure units are of correct type - or more precisely, they match the expected dimensionality of the physical quantity. Similar to wraps(), you can pass None to skip checking of some parameters, but the return parameter type is not checked. .. doctest:: >>> mypp = ureg.check('[length]')(pendulum_period) In the decorator format: .. doctest:: >>> @ureg.check('[length]') ... def pendulum_period(length): ... return 2*math.pi*math.sqrt(length/G) If you just want to check the dimensionality of a quantity, you can do so with the built-in 'check' function. .. doctest:: >>> distance = 1 * ureg.m >>> distance.check('[length]') True >>> distance.check('[time]') False pint-0.24.4/docs/api/000077500000000000000000000000001471316474000142635ustar00rootroot00000000000000pint-0.24.4/docs/api/base.rst000066400000000000000000000023631471316474000157330ustar00rootroot00000000000000 Base API ======== .. currentmodule:: pint These are the classes, exceptions and functions that you will most likely use. Most important classes ---------------------- .. autoclass:: UnitRegistry :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. autoclass:: Quantity :members: :inherited-members: .. autoclass:: Unit :members: :inherited-members: .. autoclass:: Measurement :members: :inherited-members: Exceptions and warnings ----------------------- .. autoexception:: PintError :members: .. autoexception:: DefinitionSyntaxError :members: .. autoexception:: LogarithmicUnitCalculusError :members: .. autoexception:: DimensionalityError :members: .. autoexception:: OffsetUnitCalculusError :members: .. autoexception:: RedefinitionError :members: .. autoexception:: UndefinedUnitError :members: .. autoexception:: UnitStrippedWarning :members: Sharing registry among packages ------------------------------- .. autofunction:: get_application_registry .. autofunction:: set_application_registry Other functions --------------- .. autofunction:: formatter .. autofunction:: register_unit_format .. autofunction:: pi_theorem .. autoclass:: Context pint-0.24.4/docs/api/facets.rst000066400000000000000000000026421471316474000162660ustar00rootroot00000000000000API facets reference ==================== Registry functionality is divided into facet. Each provides classes and functions specific to a particular purpose. They expose at least a Registry, and in certain cases also a Quantity, Unit and other objects. The default UnitRegistry inherits from all of them. .. automodule:: pint.facets.plain :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. automodule:: pint.facets.nonmultiplicative :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. automodule:: pint.delegates.formatter :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. automodule:: pint.facets.numpy :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. automodule:: pint.facets.dask :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. automodule:: pint.facets.measurement :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. automodule:: pint.facets.group :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. automodule:: pint.facets.system :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System .. automodule:: pint.facets.context :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System pint-0.24.4/docs/api/index.rst000066400000000000000000000001531471316474000161230ustar00rootroot00000000000000API reference ============== .. toctree:: :maxdepth: 1 :hidden: base specific facets pint-0.24.4/docs/api/specific.rst000066400000000000000000000014561471316474000166100ustar00rootroot00000000000000Specific API ============ These are the classes, exceptions and functions that you will use if you need to dig deeper into Pint or develop Pint. .. automodule:: pint.babel_names :members: .. automodule:: pint.compat :members: .. automodule:: pint.converters :members: .. automodule:: pint.definitions :members: .. automodule:: pint.errors :members: .. automodule:: pint.formatting :members: .. automodule:: pint.matplotlib :members: .. automodule:: pint.pint_eval :members: .. automodule:: pint.registry :members: :exclude-members: Quantity, Unit, Measurement, Group, Context, System, UnitRegistry .. automodule:: pint.registry_helpers :members: .. automodule:: pint.testing :members: .. automodule:: pint.util :members: :exclude-members: Unit pint-0.24.4/docs/changes.rst000066400000000000000000000000301471316474000156450ustar00rootroot00000000000000.. include:: ../CHANGES pint-0.24.4/docs/conf.py000066400000000000000000000162031471316474000150130ustar00rootroot00000000000000#!/usr/bin/env python3 # # pint documentation build configuration file, created by # sphinx-quickstart on Thu Mar 1 13:33:14 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import annotations import datetime from importlib.metadata import version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.autosectionlabel", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx.ext.mathjax", "matplotlib.sphinxext.plot_directive", "nbsphinx", "sphinx_copybutton", "IPython.sphinxext.ipython_directive", "IPython.sphinxext.ipython_console_highlighting", "sphinx_design", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "pint" author = "Hernan E. Grecco" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. try: # pragma: no cover version = version(project) except Exception: # pragma: no cover # we seem to have a local copy not installed without setuptools # so the reported version will be unknown version = "unknown" release = version this_year = datetime.date.today().year copyright = f"2012-{this_year}, Pint Developers" exclude_patterns = ["_build"] # Napoleon configurations napoleon_google_docstring = False napoleon_numpy_docstring = True napoleon_use_param = False napoleon_use_rtype = False napoleon_preprocess_types = True napoleon_type_aliases = { # general terms "sequence": ":term:`sequence`", "iterable": ":term:`iterable`", "callable": ":py:func:`callable`", "dict_like": ":term:`dict-like `", "path-like": ":term:`path-like `", "mapping": ":term:`mapping`", "file-like": ":term:`file-like `", # stdlib type aliases "timedelta": "~datetime.timedelta", "datetime": "~datetime.datetime", "string": ":class:`string `", "Path": "~pathlib.Path", # numpy terms "array_like": ":term:`array_like`", "array-like": ":term:`array-like `", "scalar": ":term:`scalar`", "array": ":term:`array`", "hashable": ":term:`hashable `", # objects without namespace: pint "Quantity": "~pint.Quantity", "Unit": "~pint.Unit", "UnitsContainer": "~pint.UnitsContainer", # objects without namespace: numpy "ndarray": "~numpy.ndarray", "MaskedArray": "~numpy.ma.MaskedArray", "dtype": "~numpy.dtype", } html_theme = "sphinx_book_theme" html_theme_options = { "repository_url": "https://github.com/hgrecco/pint", "repository_branch": "master", "use_repository_button": True, "use_issues_button": True, "extra_navbar": "", "navbar_footer_text": "", } html_logo = "_static/logo-full.jpg" html_static_path = ["_static"] html_css_files = ["style.css"] # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # html_sidebars = { # "index": ["sidebarintro.html", "sourcelink.html", "searchbox.html"], # "**": [ # "sidebarlogo.html", # "localtoc.html", # "relations.html", # "sourcelink.html", # "searchbox.html", # ], # } # Output file base name for HTML help builder. htmlhelp_basename = "pintdoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. "preamble": "".join((r"\DeclareUnicodeCharacter{2212}{-}",)) # MINUS } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ("index", "pint.tex", "pint Documentation", "Hernan E. Grecco", "manual") ] # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("index", "pint", "pint Documentation", ["Hernan E. Grecco"], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "pint", "pint Documentation", "Hernan E. Grecco", "pint", "One line description of project.", "Miscellaneous", ) ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "numpy": ("https://numpy.org/doc/stable", None), "scipy": ("https://docs.scipy.org/doc/scipy", None), "matplotlib": ("https://matplotlib.org/stable/", None), "dask": ("https://docs.dask.org/en/latest", None), "sparse": ("https://sparse.pydata.org/en/latest/", None), } # -- Doctest configuration ----------------------------------------------------- # fill a dictionary with package names that may be missing # this is checked by :skipif: clauses in certain doctests that rely # on optional dependencies doctest_global_setup = """ from importlib.util import find_spec not_installed = { pkg_name: find_spec(pkg_name) is None for pkg_name in [ 'uncertainties', 'serialize', ] } """ pint-0.24.4/docs/dev/000077500000000000000000000000001471316474000142705ustar00rootroot00000000000000pint-0.24.4/docs/dev/contributing.rst000066400000000000000000000116241471316474000175350ustar00rootroot00000000000000.. _contributing: Contributing to Pint ==================== Pint uses (and thanks): - github_ to host the code - `github actions`_ to test all commits and PRs. - coveralls_ to monitor coverage test coverage - readthedocs_ to host the documentation. - black_, isort_ and flake8_ as code linters and pre-commit_ to enforce them. - pytest_ to write tests - sphinx_ to write docs. You can contribute in different ways: Report issues ------------- You can report any issues with the package, the documentation to the Pint `issue tracker`_. Also feel free to submit feature requests, comments or questions. Contribute code --------------- To contribute fixes, code or documentation to Pint, fork Pint in github_ and submit the changes using a pull request against the **master** branch. - If you are submitting new code, add tests (see below) and documentation. - Write "Closes #" in the PR description or a comment, as described in the `github docs`_. - Log the change in the CHANGES file. - Execute ``pre-commit run --all-files`` and resolve any issues. In any case, feel free to use the `issue tracker`_ to discuss ideas for new features or improvements. Notice that we will not merge a PR if tests are failing. In certain cases tests pass in your machine but not in travis. There might be multiple reasons for this but these are some of the most common - Your new code does not work for other Python or Numpy versions. - The documentation is not being built properly or the examples in the docs are not working. - linters are reporting that the code does no adhere to the standards. Setting up your environment --------------------------- If you're contributing to this project for the fist time, you can set up your environment on Linux or OSX with the following commands:: $ git clone git@github.com:hgrecco/pint.git $ cd pint $ python -m virtualenv venv $ source venv/bin/activate $ pip install -e '.[test]' $ pip install -r requirements_docs.txt $ pip install pre-commit # This step and the next are optional but recommended. $ pre-commit install Writing tests ------------- We use pytest_ for testing. If you contribute code you need to add tests: - If you are fixing a bug, add a test to `test_issues.py`, or amend/enrich the general test suite to cover the use case. - If you are adding a new feature, add a test in the appropiate place. There is usually a `test_X.py` for each `X.py` file. There are some other test files that deal with individual/specific features. If in doubt, ask. - Prefer functions to classes. - When using classes, derive from `QuantityTestCase`. - Use `parametrize` as much as possible. - Use `fixtures` (see conftest.py) instead of instantiating the registry yourself. Check out the existing fixtures before creating your own. - When your test does not modify the registry, use `sess_registry` fixture. - **Do not** create a unit registry outside a test or fixture setup. - If you need a specific registry, and you need to reuse it create a fixture in your test module called `local_registry` or similar. - Checkout `helpers.py` for some convenience functions before reinventing the wheel. Running tests and building documentation ---------------------------------------- To run the test suite, invoke pytest from the ``pint`` directory:: $ cd pint $ pytest To run the doctests, invoke Sphinx's doctest module from the ``docs`` directory:: $ cd docs $ make doctest To build the documentation, invoke Sphinx from the ``docs`` directory:: $ cd docs $ make html Extension Packages ------------------ Pint naturally integrates with other libraries in the scientific Python ecosystem, and a small _`ecosystem` have arisen to aid in compatibility between certain packages allowing to build an Pint's rule of thumb for integration features that work best as an extension package versus direct inclusion in Pint is: * Extension (separate packages) * Duck array types that wrap Pint (come above Pint in :ref:`the type casting hierarchy <_numpy#technical-commentary>` * Uses features independent/on top of the libraries * Examples: xarray, Pandas * Integration (built in to Pint) * Duck array types wrapped by Pint (below Pint in the type casting hierarchy) * Intermingling of APIs occurs * Examples: Dask .. _github: http://github.com/hgrecco/pint .. _`issue tracker`: https://github.com/hgrecco/pint/issues .. _`github docs`: https://help.github.com/articles/closing-issues-via-commit-messages/ .. _`github actions`: https://docs.github.com/en/actions .. _coveralls: https://coveralls.io/ .. _readthedocs: https://readthedocs.org/ .. _pre-commit: https://pre-commit.com/ .. _black: https://black.readthedocs.io/en/stable/ .. _isort: https://pycqa.github.io/isort/ .. _flake8: https://flake8.pycqa.org/en/latest/ .. _pytest: https://docs.pytest.org/en/stable/ .. _sphinx: https://www.sphinx-doc.org/en/master/ .. _`extension/compatibility packages`: pint-0.24.4/docs/dev/pint-convert.rst000066400000000000000000000056301471316474000174560ustar00rootroot00000000000000.. _convert: Command-line script =================== The script `pint-convert` allows a quick conversion to a target system or between arbitrary compatible units. By default, `pint-convert` converts to SI units: .. code-block:: console $ pint-convert 225lb 225 pound = 102.05828325 kg use the `--sys` argument to change it: .. code-block:: console $ pint-convert --sys US 102kg 102 kilogram = 224.871507429 lb or specify directly the target units: .. code-block:: console $ pint-convert 102kg lb 102 kilogram = 224.871507429 lb The input quantity can contain expressions: .. code-block:: console $ pint-convert 7ft+2in 7.166666666666667 foot = 2.1844 m in some cases parentheses and quotes may be needed: .. code-block:: console $ pint-convert "225lb/(7ft+2in)" 31.3953488372093 pound / foot = 46.7214261353 kg/m If a number is omitted, 1 is assumed: .. code-block:: console $ pint-convert km mi 1 kilometer = 0.621371192237 mi The default precision is 12 significant figures, it can be changed with `-p`, but note that the accuracy may be affected by floating-point errors: .. code-block:: console $ pint-convert -p 3 mi 1 mile = 1.61e+03 m $ pint-convert -p 30 ly km 1 light_year = 9460730472580.80078125 km Some contexts are automatically enabled, allowing conversion between not fully compatible units: .. code-block:: console $ pint-convert 540nm 540 nanometer = 5.4e-07 m $ pint-convert kcal/mol $ 1.0 kilocalorie / mole = 4184 kg·m²/mol/s² $ pint-convert 540nm kcal/mol 540 nanometer = 52.9471025594 kcal/mol With the `uncertainties` package, the experimental uncertainty in the physical constants is considered, and the result is given in compact notation, with the uncertainty in the last figures in parentheses: The uncertainty can be enabled with `-U` (by default it is not enabled): .. code-block:: console $ pint-convert -p 20 -U Eh eV 1 hartree = 27.211386245988(52) eV .. code-block:: console $ pint-convert -U Eh eV 1 hartree = 27.21138624599(5) eV The precision is limited by both the maximum number of significant digits (`-p`) and the maximum number of uncertainty digits (`-u`, 2 by default):: $ pint-convert -U -p 20 Eh eV 1 hartree = 27.211386245988(52) eV $ pint-convert -U -p 20 -u 4 Eh eV 1 hartree = 27.21138624598847(5207) eV Correlations between experimental constants are also known, and taken into account if uncertainties `-U` is enabled. Use `-C` to disable it: .. code-block:: console $ pint-convert --sys atomic m_p 1 proton_mass = 1836.15267344 m_e $ pint-convert -U --sys atomic m_p 1 proton_mass = 1836.15267344(11) m_e $ pint-convert -U --sys atomic -C m_p 1 proton_mass = 1836.15267344(79) m_e Again, note that results may differ slightly, usually in the last figure, from more authoritative sources, mainly due to floating-point errors. pint-0.24.4/docs/ecosystem.rst000066400000000000000000000021041471316474000162540ustar00rootroot00000000000000Ecosystem ========= Here is a list of known projects, packages and integrations using pint. Pint integrations: ------------------ - `ucumvert `_ `UCUM `_ (Unified Code for Units of Measure) integration - `pint-pandas `_ Pandas integration - `pint-xarray `_ Xarray integration Packages using pint: ------------------ - `fluids `_ Practical fluid dynamics calculations - `ht `_ Practical heat transfer calculations - `chemicals `_ Chemical property calculations and lookups - `thermo `_ Thermodynamic equilibrium calculations - `Taurus `_ Control system UI creation - `InstrumentKit `_ Interacting with laboratory equipment over various buses. - `NEMO `_ Electricity production cost model pint-0.24.4/docs/getting/000077500000000000000000000000001471316474000151535ustar00rootroot00000000000000pint-0.24.4/docs/getting/faq.rst000066400000000000000000000022051471316474000164530ustar00rootroot00000000000000.. _faq: Frequently asked questions ========================== Why the name *Pint*? -------------------- Pint is a unit and sounds like Python in the first syllable. Most important, it is a good unit for beer. You mention other similar Python libraries. Can you point me to those? ---------------------------------------------------------------------- `natu `_ `Buckingham `_ `Magnitude `_ `SciMath `_ `Python-quantities `_ `Unum `_ `Units `_ `udunitspy `_ `SymPy `_ `cf units `_ `astropy units `_ `yt `_ `measurement `_ If you're aware of another one, please contribute a patch to the docs. pint-0.24.4/docs/getting/index.rst000066400000000000000000000020331471316474000170120ustar00rootroot00000000000000Getting Started =============== The getting started guide aims to get you using pint productively as quickly as possible. Installation ------------ Pint has no dependencies except Python itself. It runs on Python 3.9+. .. grid:: 2 .. grid-item-card:: Prefer pip? **pint** can be installed via pip from `PyPI `__. ++++++++++++++++++++++ .. code-block:: bash pip install pint .. grid-item-card:: Working with conda? **pint** is part of the `Conda-Forge `__ channel and can be installed with Anaconda or Miniconda: ++++++++++++++++++++++ .. code-block:: bash conda install -c conda-forge pint That's all! You can check that Pint is correctly installed by starting up python, and importing Pint: .. code-block:: python >>> import pint >>> pint.__version__ # doctest: +SKIP .. toctree:: :maxdepth: 2 :hidden: overview tutorial pint-in-your-projects faq pint-0.24.4/docs/getting/overview.rst000066400000000000000000000130441471316474000175550ustar00rootroot00000000000000What is Pint ? ============== .. .. image:: _static/logo-full.jpg .. :alt: Pint: **physical quantities** .. :class: float-right Pint is a Python package to define, operate and manipulate **physical quantities**: the product of a numerical value and a unit of measurement. It allows arithmetic operations between them and conversions from and to different units. It is distributed with a `comprehensive list of physical units, prefixes and constants`_. Due to its modular design, you can extend (or even rewrite!) the complete list without changing the source code. It supports a lot of numpy mathematical operations **without monkey patching or wrapping numpy**. It has a complete test coverage. It runs in Python 3.9+ with no other dependencies. It is licensed under a `BSD 3-clause style license`_. It is extremely easy and natural to use: .. code-block:: python >>> import pint >>> ureg = pint.UnitRegistry() >>> 3 * ureg.meter + 4 * ureg.cm and you can make good use of numpy if you want: .. code-block:: python >>> import numpy as np >>> [3, 4] * ureg.meter + [4, 3] * ureg.cm >>> np.sum(_) See the :ref:`Tutorial` for more help getting started. Design principles ----------------- Although there are already a few very good Python packages to handle physical quantities, no one was really fitting my needs. Like most developers, I programmed Pint to scratch my own itches. **Unit parsing**: prefixed and pluralized forms of units are recognized without explicitly defining them. In other words: as the prefix *kilo* and the unit *meter* are defined, Pint understands *kilometers*. This results in a much shorter and maintainable unit definition list as compared to other packages. **Standalone unit definitions**: units definitions are loaded from a text file which is simple and easy to edit. Adding and changing units and their definitions does not involve changing the code. **Advanced string formatting**: a quantity can be formatted into string using `PEP 3101`_ syntax. Extended conversion flags are given to provide symbolic, LaTeX and pretty formatting. Unit name translation is available if Babel_ is installed. **Free to choose the numerical type**: You can use any numerical type (``fraction``, ``float``, ``decimal``, ``numpy.ndarray``, etc). NumPy_ is not required, but is supported. **Awesome NumPy integration**: When you choose to use a NumPy_ ndarray, its methods and ufuncs are supported including automatic conversion of units. For example ``numpy.arccos(q)`` will require a dimensionless ``q`` and the units of the output quantity will be radian. **Uncertainties integration**: transparently handles calculations with quantities with uncertainties (like 3.14±0.01) meter via the `uncertainties package`_. **Handle temperature**: conversion between units with different reference points, like positions on a map or absolute temperature scales. **Dependency free**: it depends only on Python and its standard library. It interacts with other packages like numpy and uncertainties if they are installed **Pandas integration**: The `pint-pandas`_ package makes it possible to use Pint with Pandas. Operations on DataFrames and between columns are units aware, providing even more convenience for users of Pandas DataFrames. For full details, see the `pint-pandas Jupyter notebook`_. When you choose to use a NumPy_ ndarray, its methods and ufuncs are supported including automatic conversion of units. For example ``numpy.arccos(q)`` will require a dimensionless ``q`` and the units of the output quantity will be radian. One last thing -------------- The MCO MIB has determined that the root cause for the loss of the MCO spacecraft was the failure to use metric units in the coding of a ground software file, “Small Forces,” used in trajectory models. Specifically, thruster performance data in English units instead of metric units was used in the software application code titled SM_FORCES (small forces). The output from the SM_FORCES application code as required by a MSOP Project Software Interface Specification (SIS) was to be in metric units of Newtonseconds (N-s). Instead, the data was reported in English units of pound-seconds (lbf-s). The Angular Momentum Desaturation (AMD) file contained the output data from the SM_FORCES software. The SIS, which was not followed, defines both the format and units of the AMD file generated by ground-based computers. Subsequent processing of the data from AMD file by the navigation software algorithm therefore, underestimated the effect on the spacecraft trajectory by a factor of 4.45, which is the required conversion factor from force in pounds to Newtons. An erroneous trajectory was computed using this incorrect data. `Mars Climate Orbiter Mishap Investigation Phase I Report` `PDF `_ License ------- .. literalinclude:: ../../LICENSE .. _`comprehensive list of physical units, prefixes and constants`: https://github.com/hgrecco/pint/blob/master/pint/default_en.txt .. _`uncertainties package`: https://pythonhosted.org/uncertainties/ .. _`NumPy`: http://www.numpy.org/ .. _`PEP 3101`: https://www.python.org/dev/peps/pep-3101/ .. _`Babel`: http://babel.pocoo.org/ .. _`pint-pandas`: https://github.com/hgrecco/pint-pandas .. _`pint-pandas Jupyter notebook`: https://github.com/hgrecco/pint-pandas/blob/master/notebooks/pint-pandas.ipynb .. _`BSD 3-clause style license`: https://github.com/hgrecco/pint/blob/master/LICENSE pint-0.24.4/docs/getting/pint-in-your-projects.rst000066400000000000000000000040531471316474000221100ustar00rootroot00000000000000.. _pint_in_your_projects: Using Pint in your projects =========================== Having a shared registry ------------------------ If you use Pint in multiple modules within your Python package, you normally want to avoid creating multiple instances of the unit registry. The best way to do this is by instantiating the registry in a single place. For example, you can add the following code to your package ``__init__.py`` .. doctest:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry() >>> Q_ = ureg.Quantity Then in ``yourmodule.py`` the code would be .. code-block:: python from . import ureg, Q_ length = 10 * ureg.meter my_speed = Q_(20, 'm/s') If you are pickling and unpickling Quantities within your project, you should also define the registry as the application registry .. code-block:: python from pint import UnitRegistry, set_application_registry ureg = UnitRegistry() set_application_registry(ureg) .. warning:: There are no global units in Pint. All units belong to a registry and you can have multiple registries instantiated at the same time. However, you are not supposed to operate between quantities that belong to different registries. Never do things like this: .. doctest:: >>> q1 = 10 * UnitRegistry().meter >>> q2 = 10 * UnitRegistry().meter >>> q1 + q2 Traceback (most recent call last): ... ValueError: Cannot operate with Quantity and Quantity of different registries. >>> id(q1._REGISTRY) == id(q2._REGISTRY) False Keeping up to date with Pint development ---------------------------------------- While we work hard to avoid breaking code using Pint, sometimes it happens. To help you track how Pint is evolving it is recommended that you run a daily or weekly job against pint master branch. For example, this is how xarray_ is doing it: If a new version of Pint breaks your code, please open an issue_ to let us know. .. _xarray: https://github.com/pydata/xarray/blob/main/.github/workflows/upstream-dev-ci.yaml .. _issue: https://github.com/hgrecco/pint/issues pint-0.24.4/docs/getting/tutorial.rst000066400000000000000000000341311471316474000175520ustar00rootroot00000000000000 Tutorial ======== Follow the steps below and learn how to use Pint to track physical quantities and perform unit conversions in Python. Initializing a Registry ----------------------- Before using Pint, initialize a :class:`UnitRegistry() ` object. The ``UnitRegistry`` stores the unit definitions, their relationships, and handles conversions between units. .. doctest:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry() If no parameters are given to the constructor, the ``UnitRegistry`` is populated with the `default list of units`_ and prefixes. Defining a Quantity ------------------- Once you've initialized your ``UnitRegistry``, you can define quantities easily: .. doctest:: >>> distance = 24.0 * ureg.meter >>> distance >>> print(distance) 24.0 meter As you can see, ``distance`` here is a :class:`Quantity() ` object that represents a physical quantity. Quantities can be queried for their magnitude, units, and dimensionality: .. doctest:: >>> distance.magnitude 24.0 >>> distance.units >>> print(distance.dimensionality) [length] and can correctly handle many mathematical operations, including with other :class:`Quantity() ` objects: .. doctest:: >>> time = 8.0 * ureg.second >>> print(time) 8.0 second >>> speed = distance / time >>> speed >>> print(speed) 3.0 meter / second >>> print(speed.dimensionality) [length] / [time] Notice the built-in parser recognizes prefixed and pluralized units even though they are not in the definition list: .. doctest:: >>> distance = 42 * ureg.kilometers >>> print(distance) 42 kilometer >>> print(distance.to(ureg.meter)) 42000.0 meter Pint will complain if you try to use a unit which is not in the registry: .. doctest:: >>> speed = 23 * ureg.snail_speed Traceback (most recent call last): ... UndefinedUnitError: 'snail_speed' is not defined in the unit registry You can add your own units to the existing registry, or build your own list. See the page on :ref:`defining` for more information on that. See `String parsing`_ and :ref:`Defining Quantities` for more ways of defining a ``Quantity()`` object. ``Quantity()`` objects also work well with NumPy arrays, which you can read about in the section on :doc:`NumPy support `. Converting to different units ----------------------------- As the underlying ``UnitRegistry`` knows the relationships between different units, you can convert a ``Quantity`` to the units of your choice using the ``to()`` method, which accepts a string or a :class:`Unit() ` object: .. doctest:: >>> speed.to('inch/minute') >>> ureg.inch / ureg.minute >>> speed.to(ureg.inch / ureg.minute) This method returns a new object leaving the original intact as can be seen by: .. doctest:: >>> print(speed) 3.0 meter / second If you want to convert in-place (i.e. without creating another object), you can use the ``ito()`` method: .. doctest:: >>> speed.ito(ureg.inch / ureg.minute) >>> speed >>> print(speed) 7086.6141... inch / minute Pint will complain if you ask it to perform a conversion it doesn't know how to do: .. doctest:: >>> speed.to(ureg.joule) Traceback (most recent call last): ... DimensionalityError: Cannot convert from 'inch / minute' ([length] / [time]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2) See the section on :doc:`contexts` for information about expanding Pint's automatic conversion capabilities for your application. Simplifying units ----------------- Sometimes, the magnitude of the quantity will be very large or very small. The method ``to_compact()`` can adjust the units to make a quantity more human-readable: .. doctest:: >>> wavelength = 1550 * ureg.nm >>> frequency = (ureg.speed_of_light / wavelength).to('Hz') >>> print(frequency) 193414489032258.03 hertz >>> print(frequency.to_compact()) 193.414489032... terahertz There are also methods ``to_base_units()`` and ``ito_base_units()`` which automatically convert to the reference units with the correct dimensionality: .. doctest:: >>> height = 5.0 * ureg.foot + 9.0 * ureg.inch >>> print(height) 5.75 foot >>> print(height.to_base_units()) 1.752... meter >>> print(height) 5.75 foot >>> height.ito_base_units() >>> print(height) 1.752... meter There are also methods ``to_reduced_units()`` and ``ito_reduced_units()`` which perform a simplified dimensional reduction, combining units with the same dimensionality but otherwise keeping your unit definitions intact. .. doctest:: >>> density = 1.4 * ureg.gram / ureg.cm**3 >>> volume = 10*ureg.cc >>> mass = density*volume >>> print(mass) 14.0 cubic_centimeter * gram / centimeter ** 3 >>> print(mass.to_reduced_units()) 14.0 gram >>> print(mass) 14.0 cubic_centimeter * gram / centimeter ** 3 >>> mass.ito_reduced_units() >>> print(mass) 14.0 gram If you want pint to automatically perform dimensional reduction when producing new quantities, the ``UnitRegistry`` class accepts a parameter ``auto_reduce_dimensions``. Dimensional reduction can be slow, so auto-reducing is disabled by default. The methods ``to_preferred()`` and ``ito_preferred()`` provide more control over dimensional reduction by specifying a list of units to combine to get the required dimensionality. .. doctest:: >>> preferred_units = [ ... ureg.ft, # distance L ... ureg.slug, # mass M ... ureg.s, # duration T ... ureg.rankine, # temperature Θ ... ureg.lbf, # force L M T^-2 ... ureg.W, # power L^2 M T^-3 ... ] >>> power = ((1 * ureg.lbf) * (1 * ureg.m / ureg.s)).to_preferred(preferred_units) >>> print(power) 4.4482216152605005 watt The list of preferred units can also be specified in the unit registry to prevent having to pass it to every call to ``to_preferred()``. .. doctest:: >>> ureg.default_preferred_units = preferred_units >>> power = ((1 * ureg.lbf) * (1 * ureg.m / ureg.s)).to_preferred() >>> print(power) 4.4482216152605005 watt The ``UnitRegistry`` class accepts a parameter ``autoconvert_to_preferred``. If set to ``True``, pint will automatically convert to preferred units when producing new quantities. This is disabled by default. Note when there are multiple good combinations of units to reduce to, to_preferred is not guaranteed to be repeatable. For example, ``(1 * ureg.lbf * ureg.m).to_preferred(preferred_units)`` may return ``W s`` or ``ft lbf``. String parsing -------------- Pint includes powerful string parsing for identifying magnitudes and units. In many cases, units can be defined as strings: .. doctest:: >>> 2.54 * ureg('centimeter') or using the ``Quantity`` constructor: .. doctest:: >>> Q_ = ureg.Quantity >>> Q_(2.54, 'centimeter') Numbers are also parsed, so you can use an expression: .. doctest:: >>> ureg('2.54 * centimeter') >>> Q_('2.54 * centimeter') or leave out the `*` altogether: .. doctest:: >>> Q_('2.54cm') This enables you to build a simple unit converter in 3 lines: .. doctest:: >>> user_input = '2.54 * centimeter to inch' >>> src, dst = user_input.split(' to ') >>> Q_(src).to(dst) Strings containing values can be parsed using the ``ureg.parse_pattern()`` function. A ``format``-like string with the units defined in it is used as the pattern: .. doctest:: >>> input_string = '10 feet 10 inches' >>> pattern = '{feet} feet {inch} inches' >>> ureg.parse_pattern(input_string, pattern) [, ] To search for multiple matches, set the ``many`` parameter to ``True``. The following example also demonstrates how the parser is able to find matches in amongst filler characters: .. doctest:: >>> input_string = '10 feet - 20 feet ! 30 feet.' >>> pattern = '{feet} feet' >>> ureg.parse_pattern(input_string, pattern, many=True) [[], [], []] The full power of regex can also be employed when writing patterns: .. doctest:: >>> input_string = "10` - 20 feet ! 30 ft." >>> pattern = r"{feet}(`| feet| ft)" >>> ureg.parse_pattern(input_string, pattern, many=True) [[], [], []] *Note that the curly brackets (``{}``) are converted to a float-matching pattern by the parser.* This function is useful for tasks such as bulk extraction of units from thousands of uniform strings or even very large texts with units dotted around in no particular pattern. .. _sec-string-formatting: String formatting ----------------- Pint's physical quantities can be easily printed: .. doctest:: >>> accel = 1.3 * ureg.parse_units('meter/second**2') >>> # The standard string formatting code >>> print('The str is {!s}'.format(accel)) The str is 1.3 meter / second ** 2 >>> # The standard representation formatting code >>> print('The repr is {!r}'.format(accel)) The repr is >>> # Accessing useful attributes >>> print('The magnitude is {0.magnitude} with units {0.units}'.format(accel)) The magnitude is 1.3 with units meter / second ** 2 Pint supports float formatting for numpy arrays as well: .. doctest:: >>> import numpy as np >>> accel = np.array([-1.1, 1e-6, 1.2505, 1.3]) * ureg.parse_units('meter/second**2') >>> # float formatting numpy arrays >>> print('The array is {:.2f}'.format(accel)) The array is [-1.10 0.00 1.25 1.30] meter / second ** 2 >>> # scientific form formatting with unit pretty printing >>> print('The array is {:+.2E~P}'.format(accel)) The array is [-1.10E+00 +1.00E-06 +1.25E+00 +1.30E+00] m/s² Pint also supports `f-strings`_ from python>=3.6 : .. doctest:: >>> accel = 1.3 * ureg.parse_units('meter/second**2') >>> print(f'The str is {accel}') The str is 1.3 meter / second ** 2 >>> print(f'The str is {accel:.3e}') The str is 1.300e+00 meter / second ** 2 >>> print(f'The str is {accel:~}') The str is 1.3 m / s ** 2 >>> print(f'The str is {accel:~.3e}') The str is 1.300e+00 m / s ** 2 >>> print(f'The str is {accel:~H}') # HTML format (displays well in Jupyter) The str is 1.3 m/s2 But Pint also extends the standard formatting capabilities for unicode, LaTeX, and HTML representations: .. doctest:: >>> accel = 1.3 * ureg['meter/second**2'] >>> # Pretty print >>> 'The pretty representation is {:P}'.format(accel) 'The pretty representation is 1.3 meter/second²' >>> # LaTeX print >>> 'The LaTeX representation is {:L}'.format(accel) 'The LaTeX representation is 1.3\\ \\frac{\\mathrm{meter}}{\\mathrm{second}^{2}}' >>> # HTML print - good for Jupyter notebooks >>> 'The HTML representation is {:H}'.format(accel) 'The HTML representation is 1.3 meter/second2' If you want to use abbreviated unit names, prefix the specification with `~`: .. doctest:: >>> 'The str is {:~}'.format(accel) 'The str is 1.3 m / s ** 2' >>> 'The pretty representation is {:~P}'.format(accel) 'The pretty representation is 1.3 m/s²' The same is true for LaTeX (`L`) and HTML (`H`) specs. .. note:: The abbreviated unit is drawn from the unit registry where the 3rd item in the equivalence chain (ie 1 = 2 = **3**) will be returned when the prefix '~' is used. The 1st item in the chain is the canonical name of the unit. The formatting specs (ie 'L', 'H', 'P') can be used with Python string `formatting syntax`_ for custom float representations. For example, scientific notation: .. doctest:: >>> 'Scientific notation: {:.3e~L}'.format(accel) 'Scientific notation: 1.300\\times 10^{0}\\ \\frac{\\mathrm{m}}{\\mathrm{s}^{2}}' Pint also supports the LaTeX `siunitx` package: .. doctest:: :skipif: not_installed['uncertainties'] >>> accel = 1.3 * ureg.parse_units('meter/second**2') >>> # siunitx Latex print >>> print('The siunitx representation is {:Lx}'.format(accel)) The siunitx representation is \SI[]{1.3}{\meter\per\second\squared} >>> accel = accel.plus_minus(0.2) >>> print('The siunitx representation is {:Lx}'.format(accel)) The siunitx representation is \SI{1.30 +- 0.20}{\meter\per\second\squared} Additionally, you can specify a default format specification: .. doctest:: >>> accel = 1.3 * ureg.parse_units('meter/second**2') >>> 'The acceleration is {}'.format(accel) 'The acceleration is 1.3 meter / second ** 2' >>> ureg.formatter.default_format = 'P' >>> 'The acceleration is {}'.format(accel) 'The acceleration is 1.3 meter/second²' Localizing ---------- If Babel_ is installed you can translate unit names to any language .. doctest:: >>> ureg.formatter.format_quantity(accel, locale='fr_FR') '1,3 mètres par seconde²' You can also specify the format locale at the registry level either at creation: .. doctest:: >>> ureg = UnitRegistry(fmt_locale='fr_FR') or later: .. doctest:: >>> ureg.formatter.set_locale('fr_FR') and by doing that, string formatting is now localized: .. doctest:: >>> ureg.formatter.default_format = 'P' >>> accel = 1.3 * ureg.parse_units('meter/second**2') >>> str(accel) '1,3 mètres par seconde²' >>> "%s" % accel '1,3 mètres par seconde²' >>> "{}".format(accel) '1,3 mètres par seconde²' If you want to customize string formatting, take a look at :ref:`formatting`. .. _`default list of units`: https://github.com/hgrecco/pint/blob/master/pint/default_en.txt .. _`Babel`: http://babel.pocoo.org/ .. _`formatting syntax`: https://docs.python.org/3/library/string.html#format-specification-mini-language .. _`f-strings`: https://www.python.org/dev/peps/pep-0498/ pint-0.24.4/docs/index.rst000066400000000000000000000042371471316474000153610ustar00rootroot00000000000000:orphan: Pint: makes units easy ====================== **Useful links**: `Code Repository `__ | `Issues `__ | `Discussions `__ .. grid:: 1 1 2 2 :gutter: 2 .. grid-item-card:: :img-top: _static/index_getting_started.svg :link: getting/index :link-type: doc Getting Started ^^^^^^^^^^^^^^^ New to Pint? Check out the getting started guides. They contain an introduction to Pint's main concepts and links to additional tutorials. .. grid-item-card:: :img-top: _static/index_user_guide.svg :link: user/index :link-type: doc User Guide ^^^^^^^^^^ The user guide provides in-depth information on the key concepts of Pint with useful background information and explanation. .. grid-item-card:: :img-top: _static/index_api.svg :link: api/base :link-type: doc API reference ^^^^^^^^^^^^^ The reference guide contains a detailed description of the pint API. The reference describes how the methods work and which parameters can be used. It assumes that you have an understanding of the key concepts. .. grid-item-card:: :img-top: _static/index_contribute.svg :link: dev/contributing :link-type: doc Developer guide ^^^^^^^^^^^^^^^ Saw a typo in the documentation? Want to improve existing functionalities? The contributing guidelines will guide you through the process of improving Pint. .. toctree:: :maxdepth: 2 :hidden: :caption: For users Getting started User Guide Advanced topics ecosystem API Reference changes .. toctree:: :maxdepth: 1 :hidden: :caption: For developers dev/contributing dev/pint-convert .. toctree:: :maxdepth: 1 :hidden: :caption: Community GitHub repository StackOverflow pint-0.24.4/docs/make.bat000066400000000000000000000117441471316474000151260ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pint.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pint.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pint-0.24.4/docs/user/000077500000000000000000000000001471316474000144705ustar00rootroot00000000000000pint-0.24.4/docs/user/angular_frequency.rst000066400000000000000000000063771471316474000207510ustar00rootroot00000000000000.. _angular_frequency: Angles and Angular Frequency ============================= Angles ------ pint treats angle quantities as `dimensionless`, following the conventions of SI. The base unit for angle is the `radian`. The SI BIPM Brochure (Bureau International des Poids et Mesures) states: .. note:: Plane and solid angles, when expressed in radians and steradians respectively, are in effect also treated within the SI as quantities with the unit one (see section 5.4.8). The symbols rad and sr are written explicitly where appropriate, in order to emphasize that, for radians or steradians, the quantity being considered is, or involves the plane angle or solid angle respectively. For steradians it emphasizes the distinction between units of flux and intensity in radiometry and photometry for example. However, it is a long-established practice in mathematics and across all areas of science to make use of rad = 1 and sr = 1. This leads to behavior some users may find unintuitive. For example, since angles have no dimensionality, it is not possible to check whether a quantity has an angle dimension. .. code-block:: python >>> import pint >>> ureg = pint.UnitRegistry() >>> angle = ureg('1 rad') >>> angle.dimensionality Angular Frequency ----------------- `Hertz` is a unit for frequency, that is often also used for angular frequency. For example, a shaft spinning at `60 revolutions per minute` will often be said to spin at `1 Hz`, rather than `1 revolution per second`. Since pint treats angle quantities as `dimensionless`, it allows conversions between frequencies and angular frequencies. This leads to some unintuitive behaviour, as pint will convert angular frequencies into frequencies by converting angles into `radians`, rather than `revolutions`. This leads to converted values `2 * pi` larger than expected: .. code-block:: python >> from pint import UnitRegistry >>> ureg = UnitRegistry() >>> angular_frequency = ureg('60rpm') >>> angular_frequency.to('Hz') The SI BIPM Brochure (Bureau International des Poids et Mesures) states: .. note:: The SI unit of frequency is hertz, the SI unit of angular velocity and angular frequency is radian per second, and the SI unit of activity is becquerel, implying counts per second. Although it is formally correct to write all three of these units as the reciprocal second, the use of the different names emphasizes the different nature of the quantities concerned. It is especially important to carefully distinguish frequencies from angular frequencies, because by definition their numerical values differ by a factor of 2π. Ignoring this fact may cause an error of 2π. Note that in some countries, frequency values are conventionally expressed using “cycle/s” or “cps” instead of the SI unit Hz, although “cycle” and “cps” are not units in the SI. Note also that it is common, although not recommended, to use the term frequency for quantities expressed in rad/s. Because of this, it is recommended that quantities called “frequency”, “angular frequency”, and “angular velocity” always be given explicit units of Hz or rad/s and not s−1 pint-0.24.4/docs/user/contexts.rst000066400000000000000000000253361471316474000171020ustar00rootroot00000000000000 Contexts ======== If you work frequently on certain topics, you will probably find the need to convert between dimensions based on some pre-established (physical) relationships. For example, in spectroscopy you need to transform from wavelength to frequency. These are incompatible units and therefore Pint will raise an error if you do this directly: .. doctest:: >>> import pint >>> ureg = pint.UnitRegistry() >>> q = 500 * ureg.nm >>> q.to('Hz') Traceback (most recent call last): ... DimensionalityError: Cannot convert from 'nanometer' ([length]) to 'hertz' (1 / [time]) You probably want to use the relation `frequency = speed_of_light / wavelength`: .. doctest:: >>> (ureg.speed_of_light / q).to('Hz') To make this task easy, Pint has the concept of `contexts` which provides conversion rules between dimensions. For example, the relation between wavelength and frequency is defined in the `spectroscopy` context (abbreviated `sp`). You can tell pint to use this context when you convert a quantity to different units. .. doctest:: >>> q.to('Hz', 'spectroscopy') or with the abbreviated form: .. doctest:: >>> q.to('Hz', 'sp') Contexts can be also enabled for blocks of code using the `with` statement: .. doctest:: >>> with ureg.context('sp'): ... q.to('Hz') If you need a particular context in all your code, you can enable it for all operations with the registry .. doctest:: >>> ureg.enable_contexts('sp') To disable the context, just call .. doctest:: >>> ureg.disable_contexts() Enabling multiple contexts -------------------------- You can enable multiple contexts: .. doctest:: >>> q.to('Hz', 'sp', 'boltzmann') This works also using the `with` statement: .. doctest:: >>> with ureg.context('sp', 'boltzmann'): ... q.to('Hz') or in the registry: .. doctest:: >>> ureg.enable_contexts('sp', 'boltzmann') >>> q.to('Hz') If a conversion rule between two dimensions appears in more than one context, the one in the last context has precedence. This is easy to remember if you think that the previous syntax is equivalent to nest contexts: .. doctest:: >>> with ureg.context('sp'): ... with ureg.context('boltzmann') : ... q.to('Hz') Parameterized contexts ---------------------- Contexts can also take named parameters. For example, in the spectroscopy you can specify the index of refraction of the medium (`n`). In this way you can calculate, for example, the wavelength in water of a laser which on air is 530 nm. .. doctest:: >>> wl = 530. * ureg.nm >>> f = wl.to('Hz', 'sp') >>> f.to('nm', 'sp', n=1.33) Contexts can also accept Pint Quantity objects as parameters. For example, the 'chemistry' context accepts the molecular weight of a substance (as a Quantity with dimensions of [mass]/[substance]) to allow conversion between moles and mass. .. doctest:: >>> substance = 95 * ureg('g') >>> substance.to('moles', 'chemistry', mw = 5 * ureg('g/mol')) Ensuring context when calling a function ---------------------------------------- Pint provides a decorator to make sure that a function called is done within a given context. Just like before, you have to provide as argument the name (or alias) of the context and the parameters that you wish to set. .. doctest:: >>> wl = 530. * ureg.nm >>> @ureg.with_context('sp', n=1.33) ... def f(wl): ... return wl.to('Hz').magnitude >>> f(wl) 425297855014895.6 This decorator can be combined with **wraps** or **check** decorators described in :doc:`wrapping`. Defining contexts in a file --------------------------- Like all units and dimensions in Pint, `contexts` are defined using an easy to read text syntax. For example, the definition of the spectroscopy context is:: @context(n=1) spectroscopy = sp # n index of refraction of the medium. [length] <-> [frequency]: speed_of_light / n / value [frequency] -> [energy]: planck_constant * value [energy] -> [frequency]: value / planck_constant @end The `@context` directive indicates the beginning of the transformations which are finished by the `@end` statement. You can optionally specify parameters for the context in parenthesis. All parameters are named and default values are mandatory. Multiple parameters are separated by commas (like in a python function definition). Finally, you provide the name of the context (e.g. spectroscopy) and, optionally, a short version of the name (e.g. sp) separated by an equal sign. See the definition of the 'chemistry' context in default_en.txt for an example of a multiple-parameter context. Conversions rules are specified by providing source and destination dimensions separated using a colon (`:`) from the equation. A special variable named `value` will be replaced by the source quantity. Other names will be looked first in the context arguments and then in registry. A single forward arrow (`->`) indicates that the equations is used to transform from the first dimension to the second one. A double arrow (`<->`) is used to indicate that the transformation operates both ways. Context definitions are stored and imported exactly like custom units definition file (and can be included in the same file as unit definitions). See "Defining units" for details. Defining contexts programmatically ---------------------------------- You can create `Context` object, and populate the conversion rules using python functions. For example: .. doctest:: >>> ureg = pint.UnitRegistry() >>> c = pint.Context('ab') >>> c.add_transformation('[length]', '[time]', ... lambda ureg, x: x / ureg.speed_of_light) >>> c.add_transformation('[time]', '[length]', ... lambda ureg, x: x * ureg.speed_of_light) >>> ureg.add_context(c) >>> ureg("1 s").to("km", "ab") It is also possible to create anonymous contexts without invoking add_context: .. doctest:: >>> c = pint.Context() >>> c.add_transformation('[time]', '[length]', lambda ureg, x: x * ureg.speed_of_light) >>> ureg("1 s").to("km", c) Using contexts for unit redefinition ------------------------------------ The exact definition of a unit of measure can change slightly depending on the country, year, and more in general convention. For example, the ISO board released over the years several revisions of its whitepapers, which subtly change the value of some of the more obscure units. And as soon as one steps out of the SI system and starts wandering into imperial and colonial measuring systems, the same unit may start being defined slightly differently every time - with no clear 'right' or 'wrong' definition. The default pint definitions file (default_en.txt) tries to mitigate the problem by offering multiple variants of the same unit by calling them with different names; for example, one will find multiple definitions of a "BTU":: british_thermal_unit = 1055.056 * joule = Btu = BTU = Btu_iso international_british_thermal_unit = 1e3 * pound / kilogram * degR / kelvin * international_calorie = Btu_it thermochemical_british_thermal_unit = 1e3 * pound / kilogram * degR / kelvin * calorie = Btu_th That's sometimes insufficient, as Wikipedia reports `no less than 6 different definitions `_ for BTU, and it's entirely possible that some companies in the energy sector, or even individual energy contracts, may redefine it to something new entirely, e.g. with a different rounding. Pint allows changing the definition of a unit within the scope of a context. This allows layering; in the example above, a company may use the global definition of BTU from default_en.txt above, then override it with a customer-specific one in a context, and then override it again with a contract-specific one on top of it. A redefinition follows the following syntax:: = where can be the base unit name or one of its aliases. For example:: BTU = 1055 J Programmatically: .. code-block:: python >>> ureg = pint.UnitRegistry() >>> q = ureg.Quantity("1 BTU") >>> q.to("J") 1055.056 joule >>> ctx = pint.Context() >>> ctx.redefine("BTU = 1055 J") >>> q.to("J", ctx) 1055.0 joule # When the context is disabled, pint reverts to the base definition >>> q.to("J") 1055.056 joule Or with a definitions file:: @context somecontract BTU = 1055 J @end .. code-block:: python >>> ureg = pint.UnitRegistry() >>> ureg.load_definitions("somefile.txt") >>> q = ureg.Quantity("1 BTU") >>> q.to("J") 1055.056 joule >>> q.to("J", "somecontract") 1055.0 joule .. note:: Redefinitions are transitive; if the registry defines B as a function of A and C as a function of B, redefining B will also impact the conversion from C to A. **Limitations** - You can't create brand new units ; all units must be defined outside of the context first. - You can't change the dimensionality of a unit within a context. For example, you can't define a context that redefines grams as a force instead of a mass (but see the unit ``force_gram`` in default_en.txt). - You can't redefine a unit with a prefix; e.g. you can redefine a liter, but not a decaliter. - You can't redefine a base unit, such as grams. - You can't add or remove aliases, or change the symbol. Symbol and aliases are automatically inherited from the UnitRegistry. - You can't redefine dimensions or prefixes. Working without a default definition ------------------------------------ In some cases, the definition of a certain unit may be so volatile to make it unwise to define a default conversion rate in the UnitRegistry. This can be solved by using 'NaN' (any capitalization) instead of a conversion rate rate in the UnitRegistry, and then override it in contexts:: truckload = nan kg @context Euro_TIR truckload = 2000 kg @end @context British_grocer truckload = 500 lb @end This allows you, before any context is activated, to define quantities and perform dimensional analysis: .. code-block:: python >>> ureg.truckload.dimensionality [mass] >>> q = ureg.Quantity("2 truckloads") >>> q.to("kg") nan kg >>> q.to("kg", "Euro_TIR") 4000 kilogram >>> q.to("kg", "British_grocer") 453.59237 kilogram pint-0.24.4/docs/user/defining-quantities.rst000066400000000000000000000107141471316474000211740ustar00rootroot00000000000000Defining Quantities =================== A quantity in Pint is the product of a unit and a magnitude. Pint supports several different ways of defining physical quantities, including a powerful string parsing system. These methods are largely interchangeable, though you may **need** to use the constructor form under certain circumstances (see :doc:`nonmult` for an example of where the constructor form is required). By multiplication ----------------- If you've read the :ref:`Tutorial`, you're already familiar with defining a quantity by multiplying a ``Unit()`` and a scalar: .. doctest:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry() >>> ureg.meter >>> 30.0 * ureg.meter This works to build up complex units as well: .. doctest:: >>> 9.8 * ureg.meter / ureg.second**2 Using the constructor --------------------- In some cases it is useful to define :class:`Quantity() ` objects using it's class constructor. Using the constructor allows you to specify the units and magnitude separately. We typically abbreviate that constructor as `Q_` to make it's usage less verbose: .. doctest:: >>> Q_ = ureg.Quantity >>> Q_(1.78, ureg.meter) As you can see below, the multiplication and constructor methods should produce the same results: .. doctest:: >>> Q_(30.0, ureg.meter) == 30.0 * ureg.meter True >>> Q_(9.8, ureg.meter / ureg.second**2) Quantity can be created with itself, if units is specified ``pint`` will try to convert it to the desired units. If not, pint will just copy the quantity. .. doctest:: >>> length = Q_(30.0, ureg.meter) >>> Q_(length, 'cm') >>> Q_(length) Using string parsing -------------------- Pint includes a powerful parser for detecting magnitudes and units (with or without prefixes) in strings. Calling the ``UnitRegistry()`` directly invokes the parsing function ``UnitRegistry.parse_expression``: .. doctest:: >>> 30.0 * ureg('meter') >>> ureg('30.0 meters') >>> ureg('3000cm').to('meters') The parsing function is also available to the ``Quantity()`` constructor and the various ``.to()`` methods: .. doctest:: >>> Q_('30.0 meters') >>> Q_(30.0, 'meter') >>> Q_('3000.0cm').to('meter') Or as a standalone method on the ``UnitRegistry``: .. doctest:: >>> 2.54 * ureg.parse_expression('centimeter') It is fairly good at detecting compound units: .. doctest:: >>> g = ureg('9.8 meters/second**2') >>> g >>> g.to('furlongs/fortnight**2') And behaves well when given dimensionless quantities, which are parsed into their appropriate objects: .. doctest:: >>> ureg('2.54') 2.54 >>> type(ureg('2.54')) >>> Q_('2.54') >>> type(Q_('2.54')) .. note:: Pint's rule for parsing strings with a mixture of numbers and units is that **units are treated with the same precedence as numbers**. For example, the units of .. doctest:: >>> Q_('3 l / 100 km') may be unexpected at first but, are a consequence of applying this rule. Use brackets to get the expected result: .. doctest:: >>> Q_('3 l / (100 km)') Special strings for NaN (Not a Number) and inf(inity) are also handled in a case-insensitive fashion. Note that, as usual, NaN != NaN. .. doctest:: >>> Q_('inf m') >>> Q_('-INFINITY m') >>> Q_('nan m') >>> Q_('NaN m') .. note:: Since version 0.7, Pint **does not** use eval_ under the hood. This change removes the `serious security problems`_ that the system is exposed to when parsing information from untrusted sources. .. _eval: http://docs.python.org/3/library/functions.html#eval .. _`serious security problems`: http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html pint-0.24.4/docs/user/formatting.rst000066400000000000000000000113311471316474000173730ustar00rootroot00000000000000.. currentmodule:: pint String formatting specification =============================== The conversion of :py:class:`Unit`, :py:class:`Quantity` and :py:class:`Measurement` objects to strings (e.g. through the :py:class:`str` builtin or f-strings) can be customized using :ref:`format specifications `. The basic format is: .. code-block:: none [magnitude format][modifier][pint format] where each part is optional and the order of these is arbitrary. .. doctest:: >>> import pint >>> ureg = pint.UnitRegistry() >>> q = 2.3e-6 * ureg.m ** 3 / (ureg.s ** 2 * ureg.kg) >>> f"{q:~P}" # short pretty '2.3×10⁻⁶ m³/kg/s²' >>> f"{q:~#P}" # compact short pretty '2.3 mm³/g/s²' >>> f"{q:P#~}" # also compact short pretty '2.3 mm³/g/s²' >>> f"{q:.2f~#P}" # short compact pretty with 2 float digits '2.30 mm³/g/s²' >>> f"{q:#~}" # short compact default '2.3 mm ** 3 / g / s ** 2' In case the format is omitted, the corresponding value in the formatter ``.default_format`` attribute is filled in. For example: .. doctest:: >>> ureg.formatter.default_format = "P" >>> f"{q}" '2.3×10⁻⁶ meter³/kilogram/second²' Pint Format Types ----------------- ``pint`` comes with a variety of unit formats. These impact the complete representation: ======= =============== ====================================================================== Spec Name Examples ======= =============== ====================================================================== ``D`` default ``3.4e+09 kilogram * meter / second ** 2`` ``P`` pretty ``3.4×10⁹ kilogram·meter/second²`` ``H`` HTML ``3.4×109 kilogram meter/second2`` ``L`` latex ``3.4\\times 10^{9}\\ \\frac{\\mathrm{kilogram} \\cdot \\mathrm{meter}}{\\mathrm{second}^{2}}`` ``Lx`` latex siunitx ``\\SI[]{3.4e+09}{\\kilo\\gram\\meter\\per\\second\\squared}`` ``C`` compact ``3.4e+09 kilogram*meter/second**2`` ======= =============== ====================================================================== These examples are using `g`` as numeric modifier. :py:class:`Measurement` are also affected by these modifiers. Quantity modifiers ------------------ ======== =================================================== ================================ Modifier Meaning Example ======== =================================================== ================================ ``#`` Call :py:meth:`Quantity.to_compact` first ``1.0 m·mg/s²`` (``f"{q:#~P}"``) ======== =================================================== ================================ Unit modifiers -------------- ======== =================================================== ================================ Modifier Meaning Example ======== =================================================== ================================ ``~`` Use the unit's symbol instead of its canonical name ``kg·m/s²`` (``f"{u:~P}"``) ======== =================================================== ================================ Magnitude modifiers ------------------- Pint uses the :ref:`format specifications `. However, it is important to remember that only the type honors the locale. Using any other numeric format (e.g. `g`, `e`, `f`) will result in a non-localized representation of the number. Custom formats -------------- Using :py:func:`pint.register_unit_format`, it is possible to add custom formats: .. doctest:: >>> @pint.register_unit_format("Z") ... def format_unit_simple(unit, registry, **options): ... return " * ".join(f"{u} ** {p}" for u, p in unit.items()) >>> f"{q:Z}" '2.3e-06 kilogram ** -1 * meter ** 3 * second ** -2' where ``unit`` is a :py:class:`dict` subclass containing the unit names and their exponents, ``registry`` is the current instance of :py:class:``UnitRegistry`` and ``options`` is not yet implemented. You can choose to replace the complete formatter. Briefly, the formatter if an object with the following methods: `format_magnitude`, `format_unit`, `format_quantity`, `format_uncertainty`, `format_measurement`. The easiest way to create your own formatter is to subclass one that you like. .. doctest:: >>> from pint.delegates.formatter.plain import DefaultFormatter >>> class MyFormatter(DefaultFormatter): ... ... default_format = "" ... ... def format_unit(self, unit, uspec, sort_func, **babel_kwds) -> str: ... return "ups!" ... >>> ureg.formatter = MyFormatter() >>> ureg.formatter._registry = ureg >>> str(q) '2.3e-06 ups!' By replacing other methods, you can customize the output as much as you need. pint-0.24.4/docs/user/index.rst000066400000000000000000000005241471316474000163320ustar00rootroot00000000000000User Guide ========== In this user guide, you will find detailed descriptions and examples that describe many common tasks that you can accomplish with pint. .. toctree:: :maxdepth: 2 :hidden: defining-quantities formatting nonmult log_units angular_frequency contexts systems numpy plotting pint-0.24.4/docs/user/log_units.rst000066400000000000000000000075001471316474000172270ustar00rootroot00000000000000.. _log_units: Logarithmic Units ================= .. warning:: Support for logarithmic units in Pint is currently in Beta. Please take careful note of the information below, particularly around `compound log units`_ to avoid calculation errors. Bug reports and pull requests are always welcome, please see :doc:`contributing` for more information on how you can help improve this feature (and Pint in general). Pint supports some logarithmic units, including `dB`, `dBm`, `octave`, and `decade` as well as some conversions between them and their base units where applicable. These units behave much like those described in :ref:`nonmult`, so many of the recommendations there apply here as well. Setting up the ``UnitRegistry()`` --------------------------------- Many of the examples below will fail without supplying the ``autoconvert_offset_to_baseunit=True`` flag. To use logarithmic units, intialize your ``UnitRegistry()`` like so: .. doctest:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry(autoconvert_offset_to_baseunit=True) >>> Q_ = ureg.Quantity If you can't pass that flag you will need to define all logarithmic units :ref:`using the Quantity() constructor`, and you will be restricted in the kinds of operations you can do without explicitly calling `.to_base_units()` first. Defining log quantities ----------------------- After you've set up your ``UnitRegistry()`` with the ``autoconvert...`` flag, you can define simple logarithmic quantities like most others: .. doctest:: >>> 20.0 * ureg.dBm >>> ureg('20.0 dBm') >>> ureg('20 dB') Converting to and from base units --------------------------------- Get a sense of how logarithmic units are handled by using the `.to()` and `.to_base_units()` methods: .. doctest:: >>> ureg('20 dBm').to('mW') >>> ureg('20 dB').to_base_units() .. note:: Notice in the above example how the `dB` unit is defined for power quantities (10*log(p/p0)) not field (amplitude) quantities (20*log(v/v0)). Take care that you're only using it to multiply power levels, and not e.g. Voltages. Convert back from a base unit to a logarithmic unit using the `.to()` method: .. doctest:: >>> (100.0 * ureg('mW')).to('dBm') >>> shift = Q_(4, '') >>> shift >>> shift.to('octave') Compound log units ------------------ .. warning:: Support for compound logarithmic units is not comprehensive. The following examples work, but many others will not. Consider converting the logarithmic portion to base units before adding more units. Pint sometimes works with mixtures of logarithmic and other units. Below is an example of computing RMS noise from a noise density and a bandwidth: .. doctest:: >>> noise_density = -161.0 * ureg.dBm / ureg.Hz >>> bandwidth = 10.0 * ureg.kHz >>> noise_power = noise_density * bandwidth >>> noise_power.to('dBm') >>> noise_power.to('mW') There are still issues with parsing compound units, so for now the following will not work: .. doctest:: >>> -161.0 * ureg('dBm/Hz') == (-161.0 * ureg.dBm / ureg.Hz) np.False_ But this will: .. doctest:: >>> ureg('-161.0 dBm/Hz') == (-161.0 * ureg.dBm / ureg.Hz) np.True_ >>> Q_(-161.0, 'dBm') / ureg.Hz == (-161.0 * ureg.dBm / ureg.Hz) np.True_ To begin using this feature while avoiding problems, define logarithmic units as single-unit quantities and convert them to their base units as quickly as possible. pint-0.24.4/docs/user/nonmult.rst000066400000000000000000000137271471316474000167300ustar00rootroot00000000000000.. _nonmult: Temperature conversion ====================== Unlike meters and seconds, the temperature units fahrenheits and celsius are non-multiplicative units. These temperature units are expressed in a system with a reference point, and relations between temperature units include not only a scaling factor but also an offset. Pint supports these type of units and conversions between them. The default definition file includes fahrenheits, celsius, kelvin and rankine abbreviated as degF, degC, degK, and degR. For example, to convert from celsius to fahrenheit: .. doctest:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry() >>> ureg.formatter.default_format = '.3f' >>> Q_ = ureg.Quantity >>> home = Q_(25.4, ureg.degC) >>> print(home.to('degF')) 77.720 degree_Fahrenheit or to other kelvin or rankine: .. doctest:: >>> print(home.to('kelvin')) 298.550 kelvin >>> print(home.to('degR')) 537.390 degree_Rankine Additionally, for every non-multiplicative temperature unit in the registry, there is also a *delta* counterpart to specify differences. Absolute units have no *delta* counterpart. For example, the change in celsius is equal to the change in kelvin, but not in fahrenheit (as the scaling factor is different). .. doctest:: >>> increase = 12.3 * ureg.delta_degC >>> print(increase.to(ureg.kelvin)) 12.300 kelvin >>> print(increase.to(ureg.delta_degF)) 22.140 delta_degree_Fahrenheit Subtraction of two temperatures given in offset units yields a *delta* unit: .. doctest:: >>> Q_(25.4, ureg.degC) - Q_(10., ureg.degC) You can add or subtract a quantity with *delta* unit and a quantity with offset unit: .. doctest:: >>> Q_(25.4, ureg.degC) + Q_(10., ureg.delta_degC) >>> Q_(25.4, ureg.degC) - Q_(10., ureg.delta_degC) If you want to add a quantity with absolute unit to one with offset unit, like here .. doctest:: >>> heating_rate = 0.5 * ureg.kelvin/ureg.min >>> Q_(10., ureg.degC) + heating_rate * Q_(30, ureg.min) Traceback (most recent call last): ... OffsetUnitCalculusError: Ambiguous operation with offset unit (degC, kelvin). you have to avoid the ambiguity by either converting the offset unit to the absolute unit before addition .. doctest:: >>> Q_(10., ureg.degC).to(ureg.kelvin) + heating_rate * Q_(30, ureg.min) or convert the absolute unit to a *delta* unit: .. doctest:: >>> Q_(10., ureg.degC) + heating_rate.to('delta_degC/min') * Q_(30, ureg.min) In contrast to subtraction, the addition of quantities with offset units is ambiguous, e.g. for *10 degC + 100 degC* two different result are reasonable depending on the context, *110 degC* or *383.15 °C (= 283.15 K + 373.15 K)*. Because of this ambiguity pint raises an error for the addition of two quantities with offset units (since pint-0.6). Quantities with *delta* units are multiplicative: .. doctest:: >>> speed = 60. * ureg.delta_degC / ureg.min >>> print(speed.to('delta_degC/second')) 1.000 delta_degree_Celsius / second However, multiplication, division and exponentiation of quantities with offset units is problematic just like addition. Pint (since version 0.6) will by default raise an error when a quantity with offset unit is used in these operations. Due to this quantities with offset units cannot be created like other quantities by multiplication of magnitude and unit but have to be explicitly created: .. doctest:: >>> ureg = UnitRegistry() >>> home = 25.4 * ureg.degC Traceback (most recent call last): ... OffsetUnitCalculusError: Ambiguous operation with offset unit (degC). >>> Q_(25.4, ureg.degC) As an alternative to raising an error, pint can be configured to work more relaxed via setting the UnitRegistry parameter *autoconvert_offset_to_baseunit* to true. In this mode, pint behaves differently: * Multiplication of a quantity with a single offset unit with order +1 by a number or ndarray yields the quantity in the given unit. .. doctest:: >>> ureg = UnitRegistry(autoconvert_offset_to_baseunit = True) >>> T = 25.4 * ureg.degC >>> T * Before all other multiplications, all divisions and in case of exponentiation [#f1]_ involving quantities with offset-units, pint will convert the quantities with offset units automatically to the corresponding base unit before performing the operation. .. doctest:: >>> 1/T >>> T * 10 * ureg.meter You can change the behaviour at any time: .. doctest:: >>> ureg.autoconvert_offset_to_baseunit = False >>> 1/T Traceback (most recent call last): ... OffsetUnitCalculusError: Ambiguous operation with offset unit (degC). The parser knows about *delta* units and uses them when a temperature unit is found in a multiplicative context. For example, here: .. doctest:: >>> print(ureg.parse_units('degC/meter')) delta_degree_Celsius / meter but not here: .. doctest:: >>> print(ureg.parse_units('degC')) degree_Celsius You can override this behaviour: .. doctest:: >>> print(ureg.parse_units('degC/meter', as_delta=False)) degree_Celsius / meter Note that the magnitude is left unchanged: .. doctest:: >>> Q_(10, 'degC/meter') To define a new temperature, you need to specify the offset. For example, this is the definition of the celsius and fahrenheit:: degC = degK; offset: 273.15 = celsius degF = 5 / 9 * degK; offset: 255.372222 = fahrenheit You do not need to define *delta* units, as they are defined automatically. .. [#f1] If the exponent is +1, the quantity will not be converted to base unit but remains unchanged. pint-0.24.4/docs/user/numpy.ipynb000066400000000000000000000464751471316474000167230ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "NumPy Support\n", "=============\n", "\n", "The magnitude of a Pint quantity can be of any numerical scalar type, and you are free\n", "to choose it according to your needs. For numerical applications requiring arrays, it is\n", "quite convenient to use [NumPy ndarray](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html) (or [ndarray-like types supporting NEP-18](https://numpy.org/neps/nep-0018-array-function-protocol.html)),\n", "and therefore these are the array types supported by Pint.\n", "\n", "Pint follows Numpy's recommendation ([NEP29](https://numpy.org/neps/nep-0029-deprecation_policy.html)) for minimal Numpy/Python versions support across the Scientific Python ecosystem.\n", "This ensures compatibility with other third party libraries (matplotlib, pandas, scipy).\n", "\n", "First, we import the relevant packages:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "# Import NumPy\n", "from __future__ import annotations\n", "\n", "import numpy as np\n", "\n", "# Import Pint\n", "import pint\n", "\n", "ureg = pint.UnitRegistry()\n", "Q_ = ureg.Quantity\n", "\n", "# Silence NEP 18 warning\n", "import warnings\n", "\n", "with warnings.catch_warnings():\n", " warnings.simplefilter(\"ignore\")\n", " Q_([])" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "and then we create a quantity the standard way" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "legs1 = Q_(np.asarray([3.0, 4.0]), \"meter\")\n", "print(legs1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "legs1 = [3.0, 4.0] * ureg.meter\n", "print(legs1)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "All usual Pint methods can be used with this quantity. For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print(legs1.to(\"kilometer\"))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print(legs1.dimensionality)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "try:\n", " legs1.to(\"joule\")\n", "except pint.DimensionalityError as exc:\n", " print(exc)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "NumPy functions are supported by Pint. For example if we define:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "legs2 = [400.0, 300.0] * ureg.centimeter\n", "print(legs2)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "we can calculate the hypotenuse of the right triangles with legs1 and legs2." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "hyps = np.hypot(legs1, legs2)\n", "print(hyps)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Notice that before the `np.hypot` was used, the numerical value of legs2 was\n", "internally converted to the units of legs1 as expected.\n", "\n", "Similarly, when you apply a function that expects angles in radians, a conversion\n", "is applied before the requested calculation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "angles = np.arccos(legs2 / hyps)\n", "print(angles)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "You can convert the result to degrees using usual unit conversion:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "print(angles.to(\"degree\"))" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Applying a function that expects angles to a quantity with a different dimensionality\n", "results in an error:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "try:\n", " np.arccos(legs2)\n", "except pint.DimensionalityError as exc:\n", " print(exc)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Function/Method Support\n", "-----------------------\n", "\n", "The following [ufuncs](http://docs.scipy.org/doc/numpy/reference/ufuncs.html) can be applied to a Quantity object:\n", "\n", "- **Math operations**: `add`, `subtract`, `multiply`, `divide`, `logaddexp`, `logaddexp2`, `true_divide`, `floor_divide`, `negative`, `remainder`, `mod`, `fmod`, `absolute`, `rint`, `sign`, `conj`, `exp`, `exp2`, `log`, `log2`, `log10`, `expm1`, `log1p`, `sqrt`, `square`, `cbrt`, `reciprocal`\n", "- **Trigonometric functions**: `sin`, `cos`, `tan`, `arcsin`, `arccos`, `arctan`, `arctan2`, `hypot`, `sinh`, `cosh`, `tanh`, `arcsinh`, `arccosh`, `arctanh`\n", "- **Comparison functions**: `greater`, `greater_equal`, `less`, `less_equal`, `not_equal`, `equal`\n", "- **Floating functions**: `isreal`, `iscomplex`, `isfinite`, `isinf`, `isnan`, `signbit`, `sign`, `copysign`, `nextafter`, `modf`, `ldexp`, `frexp`, `fmod`, `floor`, `ceil`, `trunc`\n", "\n", "And the following NumPy functions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from pint.facets.numpy.numpy_func import HANDLED_FUNCTIONS\n", "\n", "print(sorted(list(HANDLED_FUNCTIONS)))" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "And the following [NumPy ndarray methods](http://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html#array-methods):\n", "\n", "- `argmax`, `argmin`, `argsort`, `astype`, `clip`, `compress`, `conj`, `conjugate`, `cumprod`, `cumsum`, `diagonal`, `dot`, `fill`, `flatten`, `flatten`, `item`, `max`, `mean`, `min`, `nonzero`, `prod`, `ptp`, `put`, `ravel`, `repeat`, `reshape`, `round`, `searchsorted`, `sort`, `squeeze`, `std`, `sum`, `take`, `trace`, `transpose`, `var`\n", "\n", "Pull requests are welcome for any NumPy function, ufunc, or method that is not currently\n", "supported.\n" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "Array Type Support\n", "------------------\n", "\n", "### Overview\n", "\n", "When not wrapping a scalar type, a Pint `Quantity` can be considered a [\"duck array\"](https://numpy.org/neps/nep-0022-ndarray-duck-typing-overview.html), that is, an array-like type that implements (all or most of) NumPy's API for `ndarray`. Many other such duck arrays exist in the Python ecosystem, and Pint aims to work with as many of them as reasonably possible. To date, the following are specifically tested and known to work:\n", "\n", "- xarray: `DataArray`, `Dataset`, and `Variable`\n", "- Sparse: `COO`\n", "\n", "and the following have partial support, with full integration planned:\n", "\n", "- NumPy masked arrays (NOTE: Masked Array compatibility has changed with Pint 0.10 and versions of NumPy up to at least 1.18, see the example below)\n", "- Dask arrays\n", "- CuPy arrays\n", "\n", "### Technical Commentary\n", "\n", "Starting with version 0.10, Pint aims to interoperate with other duck arrays in a well-defined and well-supported fashion. Part of this support lies in implementing [`__array_ufunc__` to support NumPy ufuncs](https://numpy.org/neps/nep-0013-ufunc-overrides.html) and [`__array_function__` to support NumPy functions](https://numpy.org/neps/nep-0018-array-function-protocol.html). However, the central component to this interoperability is respecting a [type casting hierarchy](https://numpy.org/neps/nep-0018-array-function-protocol.html) of duck arrays. When all types in the hierarchy properly defer to those above it (in wrapping, arithmetic, and NumPy operations), a well-defined nesting and operator precedence order exists. When they don't, the graph of relations becomes cyclic, and the expected result of mixed-type operations becomes ambiguous.\n", "\n", "For Pint, following this hierarchy means declaring a list of types that are above it in the hierarchy and to which it defers (\"upcast types\") and assuming all others are below it and wrappable by it (\"downcast types\"). To date, Pint's declared upcast types are:\n", "\n", "- `PintArray`, as defined by pint-pandas\n", "- `Series`, as defined by Pandas\n", "- `DataArray`, `Dataset`, and `Variable`, as defined by xarray\n", "\n", "(Note: if your application requires extension of this collection of types, it is available in Pint's API at `pint.compat.upcast_types`.)\n", "\n", "While Pint assumes it can wrap any other duck array (meaning, for now, those that implement `__array_function__`, `shape`, `ndim`, and `dtype`, at least until [NEP 30](https://numpy.org/neps/nep-0030-duck-array-protocol.html) is implemented), there are a few common types that Pint explicitly tests (or plans to test) for optimal interoperability. These are listed above in the overview section and included in the below chart.\n", "\n", "This type casting hierarchy of ndarray-like types can be shown by the below acyclic graph, where solid lines represent declared support, and dashed lines represent planned support:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from graphviz import Digraph\n", "\n", "g = Digraph(graph_attr={\"size\": \"8,5\"}, node_attr={\"fontname\": \"courier\"})\n", "g.edge(\"Dask array\", \"NumPy ndarray\")\n", "g.edge(\"Dask array\", \"CuPy ndarray\")\n", "g.edge(\"Dask array\", \"Sparse COO\")\n", "g.edge(\"Dask array\", \"NumPy masked array\", style=\"dashed\")\n", "g.edge(\"CuPy ndarray\", \"NumPy ndarray\")\n", "g.edge(\"Sparse COO\", \"NumPy ndarray\")\n", "g.edge(\"NumPy masked array\", \"NumPy ndarray\")\n", "g.edge(\"Jax array\", \"NumPy ndarray\")\n", "g.edge(\"Pint Quantity\", \"Dask array\", style=\"dashed\")\n", "g.edge(\"Pint Quantity\", \"NumPy ndarray\")\n", "g.edge(\"Pint Quantity\", \"CuPy ndarray\", style=\"dashed\")\n", "g.edge(\"Pint Quantity\", \"Sparse COO\")\n", "g.edge(\"Pint Quantity\", \"NumPy masked array\", style=\"dashed\")\n", "g.edge(\"xarray Dataset/DataArray/Variable\", \"Dask array\")\n", "g.edge(\"xarray Dataset/DataArray/Variable\", \"CuPy ndarray\", style=\"dashed\")\n", "g.edge(\"xarray Dataset/DataArray/Variable\", \"Sparse COO\")\n", "g.edge(\"xarray Dataset/DataArray/Variable\", \"NumPy ndarray\")\n", "g.edge(\"xarray Dataset/DataArray/Variable\", \"NumPy masked array\", style=\"dashed\")\n", "g.edge(\"xarray Dataset/DataArray/Variable\", \"Pint Quantity\")\n", "g.edge(\"xarray Dataset/DataArray/Variable\", \"Jax array\", style=\"dashed\")\n", "g" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### Examples\n", "\n", "**xarray wrapping Pint Quantity**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import xarray as xr\n", "\n", "# Load tutorial data\n", "air = xr.tutorial.load_dataset(\"air_temperature\")[\"air\"][0]\n", "\n", "# Convert to Quantity\n", "air.data = Q_(air.data, air.attrs.pop(\"units\", \"\"))\n", "\n", "print(air)\n", "print()\n", "print(air.max())" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**Pint Quantity wrapping Sparse COO**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "from sparse import COO\n", "\n", "np.random.seed(80243963)\n", "\n", "x = np.random.random((100, 100, 100))\n", "x[x < 0.9] = 0 # fill most of the array with zeros\n", "s = COO(x)\n", "\n", "q = s * ureg.m\n", "\n", "print(q)\n", "print()\n", "print(np.mean(q))" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**Pint Quantity wrapping NumPy Masked Array**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "m = np.ma.masked_array([2, 3, 5, 7], mask=[False, True, False, True])\n", "\n", "# Must create using Quantity class\n", "print(repr(ureg.Quantity(m, \"m\")))\n", "print()\n", "\n", "# DO NOT create using multiplication until\n", "# https://github.com/numpy/numpy/issues/15200 is resolved, as\n", "# unexpected behavior may result\n", "print(repr(m * ureg.m))" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**Pint Quantity wrapping Dask Array**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import dask.array as da\n", "\n", "d = da.arange(500, chunks=50)\n", "\n", "# Must create using Quantity class, otherwise Dask will wrap Pint Quantity\n", "q = ureg.Quantity(d, ureg.kelvin)\n", "\n", "print(repr(q))\n", "print()\n", "\n", "# DO NOT create using multiplication on the right until\n", "# https://github.com/dask/dask/issues/4583 is resolved, as\n", "# unexpected behavior may result\n", "print(repr(d * ureg.kelvin))\n", "print(repr(ureg.kelvin * d))" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "**xarray wrapping Pint Quantity wrapping Dask array wrapping Sparse COO**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, "outputs": [], "source": [ "import dask.array as da\n", "\n", "x = da.random.random((100, 100, 100), chunks=(100, 1, 1))\n", "x[x < 0.95] = 0\n", "\n", "data = xr.DataArray(\n", " Q_(x.map_blocks(COO), \"m\"),\n", " dims=(\"z\", \"y\", \"x\"),\n", " coords={\n", " \"z\": np.arange(100),\n", " \"y\": np.arange(100) - 50,\n", " \"x\": np.arange(100) * 1.5 - 20,\n", " },\n", " name=\"test\",\n", ")\n", "\n", "print(data)\n", "print()\n", "print(data.sel(x=125.5, y=-46).mean())" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "### Compatibility Packages\n", "\n", "To aid in integration between various array types and Pint (such as by providing convenience methods), the following compatibility packages are available:\n", "\n", "- [pint-pandas](https://github.com/hgrecco/pint-pandas)\n", "- [pint-xarray](https://github.com/xarray-contrib/pint-xarray/)\n", "\n", "(Note: if you have developed a compatibility package for Pint, please submit a pull request to add it to this list!)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" } }, "source": [ "## Additional Comments\n", "\n", "What follows is a short discussion about how NumPy support is implemented in Pint's `Quantity` Object.\n", "\n", "For the supported functions, Pint expects certain units and attempts to convert the input (or inputs). For example, the argument of the exponential function (`numpy.exp`) must be dimensionless. Units will be simplified (converting the magnitude appropriately) and `numpy.exp` will be applied to the resulting magnitude. If the input is not dimensionless, a `DimensionalityError` exception will be raised.\n", "\n", "In some functions that take 2 or more arguments (e.g. `arctan2`), the second argument is converted to the units of the first. Again, a `DimensionalityError` exception will be raised if this is not possible. ndarray or downcast type arguments are generally treated as if they were dimensionless quantities, whereas Pint defers to its declared upcast types by always returning `NotImplemented` when they are encountered (see above).\n", "\n", "To achive these function and ufunc overrides, Pint uses the ``__array_function__`` and ``__array_ufunc__`` protocols respectively, as recommened by NumPy. This means that functions and ufuncs that Pint does not explicitly handle will error, rather than return a value with units stripped (in contrast to Pint's behavior prior to v0.10). For more\n", "information on these protocols, see .\n", "\n", "This behaviour introduces some performance penalties and increased memory usage. Quantities that must be converted to other units require additional memory and CPU cycles. Therefore, for numerically intensive code, you might want to convert the objects first and then use directly the magnitude, such as by using Pint's `wraps` utility (see [wrapping](wrapping.rst)).\n", "\n", "Attempting to access array interface protocol attributes (such as `__array_struct__` and `__array_interface__`) on Pint Quantities will raise an AttributeError, since a Quantity is meant to behave as a \"duck array,\" and not a pure ndarray." ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 } pint-0.24.4/docs/user/plotting.rst000066400000000000000000000045201471316474000170630ustar00rootroot00000000000000.. _plotting: Plotting with Matplotlib ======================== Matplotlib_ is a Python plotting library that produces a wide range of plot types with publication-quality images and support for typesetting mathematical formulas. Starting with Matplotlib 2.0, **Quantity** instances can be used with matplotlib's support for units when plotting. To do so, the support must be manually enabled on a **UnitRegistry**: .. testsetup:: * import pint ureg = pint.UnitRegistry() .. doctest:: >>> import pint >>> ureg = pint.UnitRegistry() >>> ureg.setup_matplotlib() This support can also be disabled with: .. doctest:: >>> ureg.setup_matplotlib(False) This allows plotting quantities with different units: .. plot:: :include-source: true import matplotlib.pyplot as plt import numpy as np import pint ureg = pint.UnitRegistry() ureg.setup_matplotlib(True) y = np.linspace(0, 30) * ureg.miles x = np.linspace(0, 5) * ureg.hours fig, ax = plt.subplots() ax.plot(x, y, 'tab:blue') ax.axhline(26400 * ureg.feet, color='tab:red') ax.axvline(120 * ureg.minutes, color='tab:green') This also allows controlling the actual plotting units for the x and y axes: .. plot:: :include-source: true import matplotlib.pyplot as plt import numpy as np import pint ureg = pint.UnitRegistry() ureg.setup_matplotlib(True) y = np.linspace(0, 30) * ureg.miles x = np.linspace(0, 5) * ureg.hours fig, ax = plt.subplots() ax.yaxis.set_units(ureg.inches) ax.xaxis.set_units(ureg.seconds) ax.plot(x, y, 'tab:blue') ax.axhline(26400 * ureg.feet, color='tab:red') ax.axvline(120 * ureg.minutes, color='tab:green') Users have the possibility to change the format of the units on the plot: .. plot:: :include-source: true import matplotlib.pyplot as plt import numpy as np import pint ureg = pint.UnitRegistry() ureg.setup_matplotlib(True) ureg.mpl_formatter = "{:~P}" y = np.linspace(0, 30) * ureg.miles x = np.linspace(0, 5) * ureg.hours fig, ax = plt.subplots() ax.yaxis.set_units(ureg.inches) ax.xaxis.set_units(ureg.seconds) ax.plot(x, y, 'tab:blue') ax.axhline(26400 * ureg.feet, color='tab:red') ax.axvline(120 * ureg.minutes, color='tab:green') For more information, visit the Matplotlib_ home page. .. _Matplotlib: https://matplotlib.org pint-0.24.4/docs/user/systems.rst000066400000000000000000000034021471316474000167300ustar00rootroot00000000000000.. _systems: Dealing with unit systems ========================= Pint Unit Registry has the concept of system, which is a group of units .. doctest:: >>> import pint >>> ureg = pint.UnitRegistry(system='mks') >>> ureg.default_system 'mks' This has an effect in the base units. For example: .. doctest:: >>> q = 3600. * ureg.meter / ureg.hour >>> q.to_base_units() But if you change to cgs: .. doctest:: >>> ureg.default_system = 'cgs' >>> q.to_base_units() or more drastically to: .. doctest:: >>> ureg.default_system = 'imperial' >>> '{:.3f}'.format(q.to_base_units()) '1.094 yard / second' .. warning:: In versions previous to 0.7, ``to_base_units()`` returns quantities in the units of the definition files (which are called root units). For the definition file bundled with pint this is meter/gram/second. To get back this behaviour use ``to_root_units()``, set ``ureg.system = None`` You can check which unit systems are available: .. doctest:: >>> dir(ureg.sys) ['Planck', 'SI', 'US', 'atomic', 'cgs', 'imperial', 'mks'] Or which units are available within a particular system: .. doctest:: >>> dir(ureg.sys.imperial) ['UK_force_ton', 'UK_hundredweight', ... 'cubic_foot', 'cubic_inch', ... 'thou', 'ton', 'yard'] Notice that this give you the opportunity to choose within units with colliding names: .. doctest:: >>> (1 * ureg.sys.imperial.pint).to('liter') >>> (1 * ureg.sys.US.pint).to('liter') >>> (1 * ureg.sys.US.pint).to(ureg.sys.imperial.pint) pint-0.24.4/downstream_status.md000066400000000000000000000111371471316474000166750ustar00rootroot00000000000000In Pint, we work hard to avoid breaking projects that depend on us. If you are the maintainer of one of such projects, you can help us get ahead of problems in simple way. Pint will publish a release candidate (rc) at least a week before each new version. By default, `pip` does not install these versions unless a [pre](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-pre) option is used so this will not affect your users. In addition to your standard CI routines, create a CI that install Pint's release candidates. You can also (or alternatively) create CI that install Pint's master branch in GitHub. Take a look at the [Pint Downstream Demo](https://github.com/hgrecco/pint-downstream-demo) if you need a template. Then, add your project badges to this file so it can be used as a Dashboard (always putting the stable first) | Project | stable | pre-release | nightly | | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Pint Downstream Demo](https://github.com/hgrecco/pint-downstream-demo) | [![CI](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci.yml) | [![CI-pint-pre](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-pre.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-pre.yml) | [![CI-pint-master](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-master.yml/badge.svg)](https://github.com/hgrecco/pint-downstream-demo/actions/workflows/ci-pint-master.yml) | | [Pint Pandas](https://github.com/hgrecco/pint-pandas) | [![CI](https://github.com/hgrecco/pint-pandas/actions/workflows/ci.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci.yml) | [![CI-pint-pre](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-pre.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-pre.yml) | [![CI-pint-master](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-master.yml/badge.svg)](https://github.com/hgrecco/pint-pandas/actions/workflows/ci-pint-master.yml) | | [MetPy](https://github.com/Unidata/MetPy) | [![CI](https://github.com/Unidata/MetPy/actions/workflows/tests-pypi.yml/badge.svg)](https://github.com/Unidata/MetPy/actions/workflows/tests-pypi.yml) | | [![CI-pint-master](https://github.com/Unidata/MetPy/actions/workflows/nightly-builds.yml/badge.svg)](https://github.com/Unidata/MetPy/actions/workflows/nightly-builds.yml) | | [pint-xarray](https://github.com/xarray-contrib/pint-xarray) | [![CI](https://github.com/xarray-contrib/pint-xarray/actions/workflows/ci.yml/badge.svg)](https://github.com/xarray-contrib/pint-xarray/actions/workflows/ci.yml) | | [![CI-pint-master](https://github.com/xarray-contrib/pint-xarray/actions/workflows/nightly.yml/badge.svg)](https://github.com/xarray-contrib/pint-xarray/actions/workflows/nightly.yml) | pint-0.24.4/pint/000077500000000000000000000000001471316474000135345ustar00rootroot00000000000000pint-0.24.4/pint/__init__.py000066400000000000000000000076731471316474000156620ustar00rootroot00000000000000""" pint ~~~~ Pint is Python module/package to define, operate and manipulate **physical quantities**: the product of a numerical value and a unit of measurement. It allows arithmetic operations between them and conversions from and to different units. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from importlib.metadata import version from .delegates.formatter._format_helpers import formatter from .errors import ( # noqa: F401 DefinitionSyntaxError, DimensionalityError, LogarithmicUnitCalculusError, OffsetUnitCalculusError, PintError, RedefinitionError, UndefinedUnitError, UnitStrippedWarning, ) from .formatting import register_unit_format from .registry import ApplicationRegistry, LazyRegistry, UnitRegistry from .util import logger, pi_theorem # noqa: F401 # Default Quantity, Unit and Measurement are the ones # build in the default registry. Quantity = UnitRegistry.Quantity Unit = UnitRegistry.Unit Measurement = UnitRegistry.Measurement Context = UnitRegistry.Context Group = UnitRegistry.Group try: # pragma: no cover __version__ = version("pint") except Exception: # pragma: no cover # we seem to have a local copy not installed without setuptools # so the reported version will be unknown __version__ = "unknown" #: A Registry with the default units and constants. _DEFAULT_REGISTRY = LazyRegistry() #: Registry used for unpickling operations. application_registry = ApplicationRegistry(_DEFAULT_REGISTRY) def _unpickle(cls, *args): """Rebuild object upon unpickling. All units must exist in the application registry. Parameters ---------- cls : Quantity, Magnitude, or Unit *args Returns ------- object of type cls """ from pint.util import UnitsContainer for arg in args: # Prefixed units are defined within the registry # on parsing (which does not happen here). # We need to make sure that this happens before using. if isinstance(arg, UnitsContainer): for name in arg: application_registry.parse_units(name) return cls(*args) def _unpickle_quantity(cls, *args): """Rebuild quantity upon unpickling using the application registry.""" return _unpickle(application_registry.Quantity, *args) def _unpickle_unit(cls, *args): """Rebuild unit upon unpickling using the application registry.""" return _unpickle(application_registry.Unit, *args) def _unpickle_measurement(cls, *args): """Rebuild measurement upon unpickling using the application registry.""" return _unpickle(application_registry.Measurement, *args) def set_application_registry(registry): """Set the application registry, which is used for unpickling operations and when invoking pint.Quantity or pint.Unit directly. Parameters ---------- registry : pint.UnitRegistry """ application_registry.set(registry) def get_application_registry(): """Return the application registry. If :func:`set_application_registry` was never invoked, return a registry built using :file:`defaults_en.txt` embedded in the pint package. Returns ------- pint.UnitRegistry """ return application_registry # Enumerate all user-facing objects # Hint to intersphinx that, when building objects.inv, these objects must be registered # under the top-level module and not in their original submodules __all__ = ( "Measurement", "Quantity", "Unit", "UnitRegistry", "PintError", "DefinitionSyntaxError", "LogarithmicUnitCalculusError", "DimensionalityError", "OffsetUnitCalculusError", "RedefinitionError", "UndefinedUnitError", "UnitStrippedWarning", "formatter", "get_application_registry", "set_application_registry", "register_unit_format", "pi_theorem", "__version__", "Context", ) pint-0.24.4/pint/_typing.py000066400000000000000000000022721471316474000155620ustar00rootroot00000000000000from __future__ import annotations from collections.abc import Callable from decimal import Decimal from fractions import Fraction from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union from .compat import Never, TypeAlias if TYPE_CHECKING: from .facets.plain import PlainQuantity as Quantity from .facets.plain import PlainUnit as Unit from .util import UnitsContainer HAS_NUMPY = False if TYPE_CHECKING: from .compat import HAS_NUMPY if HAS_NUMPY: from .compat import np Scalar: TypeAlias = Union[float, int, Decimal, Fraction, np.number[Any]] Array = np.ndarray[Any, Any] else: Scalar: TypeAlias = Union[float, int, Decimal, Fraction] Array: TypeAlias = Never # TODO: Change when Python 3.10 becomes minimal version. Magnitude = Union[Scalar, Array] UnitLike = Union[str, dict[str, Scalar], "UnitsContainer", "Unit"] QuantityOrUnitLike = Union["Quantity", UnitLike] Shape = tuple[int, ...] S = TypeVar("S") FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) # TODO: Improve or delete types QuantityArgument = Any T = TypeVar("T") class Handler(Protocol): def __getitem__(self, item: type[T]) -> Callable[[T], None]: ... pint-0.24.4/pint/babel_names.py000066400000000000000000000110551471316474000163400ustar00rootroot00000000000000""" pint.babel ~~~~~~~~~~ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .compat import HAS_BABEL _babel_units: dict[str, str] = dict( standard_gravity="acceleration-g-force", millibar="pressure-millibar", metric_ton="mass-metric-ton", megawatt="power-megawatt", degF="temperature-fahrenheit", dietary_calorie="energy-foodcalorie", millisecond="duration-millisecond", mph="speed-mile-per-hour", acre_foot="volume-acre-foot", mebibit="digital-megabit", gibibit="digital-gigabit", tebibit="digital-terabit", mebibyte="digital-megabyte", kibibyte="digital-kilobyte", mm_Hg="pressure-millimeter-of-mercury", month="duration-month", kilocalorie="energy-kilocalorie", cubic_mile="volume-cubic-mile", arcsecond="angle-arc-second", byte="digital-byte", metric_cup="volume-cup-metric", kilojoule="energy-kilojoule", meter_per_second_squared="acceleration-meter-per-second-squared", pint="volume-pint", square_centimeter="area-square-centimeter", in_Hg="pressure-inch-hg", milliampere="electric-milliampere", arcminute="angle-arc-minute", MPG="consumption-mile-per-gallon", hertz="frequency-hertz", day="duration-day", mps="speed-meter-per-second", kilometer="length-kilometer", square_yard="area-square-yard", kelvin="temperature-kelvin", kilogram="mass-kilogram", kilohertz="frequency-kilohertz", megahertz="frequency-megahertz", meter="length-meter", cubic_inch="volume-cubic-inch", kilowatt_hour="energy-kilowatt-hour", second="duration-second", yard="length-yard", light_year="length-light-year", millimeter="length-millimeter", metric_horsepower="power-horsepower", gibibyte="digital-gigabyte", # 'temperature-generic', liter="volume-liter", turn="angle-revolution", microsecond="duration-microsecond", pound="mass-pound", ounce="mass-ounce", calorie="energy-calorie", centimeter="length-centimeter", inch="length-inch", centiliter="volume-centiliter", troy_ounce="mass-ounce-troy", gram="mass-gram", kilowatt="power-kilowatt", knot="speed-knot", lux="light-lux", hectoliter="volume-hectoliter", microgram="mass-microgram", degC="temperature-celsius", tablespoon="volume-tablespoon", cubic_yard="volume-cubic-yard", square_foot="area-square-foot", tebibyte="digital-terabyte", square_inch="area-square-inch", carat="mass-carat", hectopascal="pressure-hectopascal", gigawatt="power-gigawatt", watt="power-watt", micrometer="length-micrometer", volt="electric-volt", bit="digital-bit", gigahertz="frequency-gigahertz", teaspoon="volume-teaspoon", ohm="electric-ohm", joule="energy-joule", cup="volume-cup", square_mile="area-square-mile", nautical_mile="length-nautical-mile", square_meter="area-square-meter", mile="length-mile", acre="area-acre", nanometer="length-nanometer", hour="duration-hour", astronomical_unit="length-astronomical-unit", liter_per_100kilometers="consumption-liter-per-100kilometers", megaliter="volume-megaliter", ton="mass-ton", hectare="area-hectare", square_kilometer="area-square-kilometer", kibibit="digital-kilobit", mile_scandinavian="length-mile-scandinavian", liter_per_kilometer="consumption-liter-per-kilometer", century="duration-century", cubic_foot="volume-cubic-foot", deciliter="volume-deciliter", # pint='volume-pint-metric', cubic_meter="volume-cubic-meter", cubic_kilometer="volume-cubic-kilometer", quart="volume-quart", cc="volume-cubic-centimeter", pound_force_per_square_inch="pressure-pound-per-square-inch", milligram="mass-milligram", kph="speed-kilometer-per-hour", minute="duration-minute", parsec="length-parsec", picometer="length-picometer", degree="angle-degree", milliwatt="power-milliwatt", week="duration-week", ampere="electric-ampere", milliliter="volume-milliliter", decimeter="length-decimeter", fluid_ounce="volume-fluid-ounce", nanosecond="duration-nanosecond", foot="length-foot", karat="proportion-karat", year="duration-year", gallon="volume-gallon", radian="angle-radian", ) if not HAS_BABEL: _babel_units = {} _babel_systems: dict[str, str] = dict(mks="metric", imperial="uksystem", US="ussystem") _babel_lengths: list[str] = ["narrow", "short", "long"] pint-0.24.4/pint/compat.py000066400000000000000000000245721471316474000154030ustar00rootroot00000000000000""" pint.compat ~~~~~~~~~~~ Compatibility layer. :copyright: 2013 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import math import sys from collections.abc import Callable, Iterable, Mapping from decimal import Decimal from importlib import import_module from numbers import Number from typing import ( Any, NoReturn, ) if sys.version_info >= (3, 10): from typing import TypeAlias # noqa else: from typing_extensions import TypeAlias # noqa if sys.version_info >= (3, 11): from typing import Self # noqa else: from typing_extensions import Self # noqa if sys.version_info >= (3, 11): from typing import Never # noqa else: from typing_extensions import Never # noqa if sys.version_info >= (3, 11): from typing import Unpack # noqa else: from typing_extensions import Unpack # noqa if sys.version_info >= (3, 13): from warnings import deprecated # noqa else: from typing_extensions import deprecated # noqa def missing_dependency( package: str, display_name: str | None = None ) -> Callable[..., NoReturn]: """Return a helper function that raises an exception when used. It provides a way delay a missing dependency exception until it is used. """ display_name = display_name or package def _inner(*args: Any, **kwargs: Any) -> NoReturn: raise Exception( "This feature requires %s. Please install it by running:\n" "pip install %s" % (display_name, package) ) return _inner # TODO: remove this warning after v0.10 class BehaviorChangeWarning(UserWarning): pass try: from uncertainties import UFloat, ufloat unp = None HAS_UNCERTAINTIES = True except ImportError: UFloat = ufloat = unp = None HAS_UNCERTAINTIES = False try: import numpy as np from numpy import datetime64 as np_datetime64 from numpy import ndarray HAS_NUMPY = True NUMPY_VER = np.__version__ if HAS_UNCERTAINTIES: from uncertainties import unumpy as unp NUMERIC_TYPES = (Number, Decimal, ndarray, np.number, UFloat) else: NUMERIC_TYPES = (Number, Decimal, ndarray, np.number) def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False): if isinstance(value, (dict, bool)) or value is None: raise TypeError(f"Invalid magnitude for Quantity: {value!r}") elif isinstance(value, str) and value == "": raise ValueError("Quantity magnitude cannot be an empty string.") elif isinstance(value, (list, tuple)): return np.asarray(value) elif HAS_UNCERTAINTIES: from pint.facets.measurement.objects import Measurement if isinstance(value, Measurement): return ufloat(value.value, value.error) if force_ndarray or ( force_ndarray_like and not is_duck_array_type(type(value)) ): return np.asarray(value) return value def _test_array_function_protocol(): # Test if the __array_function__ protocol is enabled try: class FakeArray: def __array_function__(self, *args, **kwargs): return np.concatenate([FakeArray()]) return True except ValueError: return False HAS_NUMPY_ARRAY_FUNCTION = _test_array_function_protocol() NP_NO_VALUE = np._NoValue except ImportError: np = None class ndarray: pass class np_datetime64: pass HAS_NUMPY = False NUMPY_VER = "0" NUMERIC_TYPES = (Number, Decimal) HAS_NUMPY_ARRAY_FUNCTION = False NP_NO_VALUE = None def _to_magnitude(value, force_ndarray=False, force_ndarray_like=False): if force_ndarray or force_ndarray_like: raise ValueError( "Cannot force to ndarray or ndarray-like when NumPy is not present." ) elif isinstance(value, (dict, bool)) or value is None: raise TypeError(f"Invalid magnitude for Quantity: {value!r}") elif isinstance(value, str) and value == "": raise ValueError("Quantity magnitude cannot be an empty string.") elif isinstance(value, (list, tuple)): raise TypeError( "lists and tuples are valid magnitudes for " "Quantity only when NumPy is present." ) elif HAS_UNCERTAINTIES: from pint.facets.measurement.objects import Measurement if isinstance(value, Measurement): return ufloat(value.value, value.error) return value try: from babel import Locale from babel import units as babel_units babel_parse = Locale.parse HAS_BABEL = hasattr(babel_units, "format_unit") except ImportError: HAS_BABEL = False babel_parse = missing_dependency("Babel") # noqa: F811 # type:ignore babel_units = babel_parse try: import mip mip_model = mip.model mip_Model = mip.Model mip_INF = mip.INF mip_INTEGER = mip.INTEGER mip_xsum = mip.xsum mip_OptimizationStatus = mip.OptimizationStatus HAS_MIP = True except ImportError: HAS_MIP = False mip_missing = missing_dependency("mip") mip_model = mip_missing mip_Model = mip_missing mip_INF = mip_missing mip_INTEGER = mip_missing mip_xsum = mip_missing mip_OptimizationStatus = mip_missing # Defines Logarithm and Exponential for Logarithmic Converter if HAS_NUMPY: from numpy import ( exp, # noqa: F401 log, # noqa: F401 ) else: from math import ( exp, # noqa: F401 log, # noqa: F401 ) # Define location of pint.Quantity in NEP-13 type cast hierarchy by defining upcast # types using guarded imports try: from dask import array as dask_array from dask.base import compute, persist, visualize except ImportError: compute, persist, visualize = None, None, None dask_array = None # TODO: merge with upcast_type_map #: List upcast type names upcast_type_names = ( "pint_pandas.pint_array.PintArray", "xarray.core.dataarray.DataArray", "xarray.core.dataset.Dataset", "xarray.core.variable.Variable", "pandas.core.series.Series", "pandas.core.frame.DataFrame", "pandas.Series", "pandas.DataFrame", "xarray.core.dataarray.DataArray", ) #: Map type name to the actual type (for upcast types). upcast_type_map: Mapping[str, type | None] = {k: None for k in upcast_type_names} def fully_qualified_name(t: type) -> str: """Return the fully qualified name of a type.""" module = t.__module__ name = t.__qualname__ if module is None or module == "builtins": return name return f"{module}.{name}" def check_upcast_type(obj: type) -> bool: """Check if the type object is an upcast type.""" # TODO: merge or unify name with is_upcast_type fqn = fully_qualified_name(obj) if fqn not in upcast_type_map: return False else: module_name, class_name = fqn.rsplit(".", 1) cls = getattr(import_module(module_name), class_name) upcast_type_map[fqn] = cls # This is to check we are importing the same thing. # and avoid weird problems. Maybe instead of return # we should raise an error if false. return obj in upcast_type_map.values() def is_upcast_type(other: type) -> bool: """Check if the type object is an upcast type.""" # TODO: merge or unify name with check_upcast_type if other in upcast_type_map.values(): return True return check_upcast_type(other) def is_duck_array_type(cls: type) -> bool: """Check if the type object represents a (non-Quantity) duck array type.""" # TODO (NEP 30): replace duck array check with hasattr(other, "__duckarray__") return issubclass(cls, ndarray) or ( not hasattr(cls, "_magnitude") and not hasattr(cls, "_units") and HAS_NUMPY_ARRAY_FUNCTION and hasattr(cls, "__array_function__") and hasattr(cls, "ndim") and hasattr(cls, "dtype") ) def is_duck_array(obj: type) -> bool: """Check if an object represents a (non-Quantity) duck array type.""" return is_duck_array_type(type(obj)) def eq(lhs: Any, rhs: Any, check_all: bool) -> bool | Iterable[bool]: """Comparison of scalars and arrays. Parameters ---------- lhs left-hand side rhs right-hand side check_all if True, reduce sequence to single bool; return True if all the elements are equal. Returns ------- bool or array_like of bool """ out = lhs == rhs if check_all and is_duck_array_type(type(out)): return out.all() return out def isnan(obj: Any, check_all: bool) -> bool | Iterable[bool]: """Test for NaN or NaT. Parameters ---------- obj scalar or vector check_all if True, reduce sequence to single bool; return True if any of the elements are NaN. Returns ------- bool or array_like of bool. Always return False for non-numeric types. """ if is_duck_array_type(type(obj)): if obj.dtype.kind in "ifc": out = np.isnan(obj) elif obj.dtype.kind in "Mm": out = np.isnat(obj) else: if HAS_UNCERTAINTIES: try: out = unp.isnan(obj) except TypeError: # Not a numeric or UFloat type out = np.full(obj.shape, False) else: # Not a numeric or datetime type out = np.full(obj.shape, False) return out.any() if check_all else out if isinstance(obj, np_datetime64): return np.isnat(obj) elif HAS_UNCERTAINTIES and isinstance(obj, UFloat): return unp.isnan(obj) try: return math.isnan(obj) except TypeError: return False def zero_or_nan(obj: Any, check_all: bool) -> bool | Iterable[bool]: """Test if obj is zero, NaN, or NaT. Parameters ---------- obj scalar or vector check_all if True, reduce sequence to single bool; return True if all the elements are zero, NaN, or NaT. Returns ------- bool or array_like of bool. Always return False for non-numeric types. """ out = eq(obj, 0, False) + isnan(obj, False) if check_all and is_duck_array_type(type(out)): return out.all() return out pint-0.24.4/pint/constants_en.txt000066400000000000000000000105451471316474000170000ustar00rootroot00000000000000# Default Pint constants definition file # Based on the International System of Units # Language: english # Source: https://physics.nist.gov/cuu/Constants/ # https://physics.nist.gov/PhysRefData/XrayTrans/Html/search.html # :copyright: 2013,2019 by Pint Authors, see AUTHORS for more details. #### MATHEMATICAL CONSTANTS #### # As computed by Maxima with fpprec:50 pi = 3.1415926535897932384626433832795028841971693993751 = π # pi tansec = 4.8481368111333441675396429478852851658848753880815e-6 # tangent of 1 arc-second ~ arc_second/radian ln10 = 2.3025850929940456840179914546843642076011014886288 # natural logarithm of 10 wien_x = 4.9651142317442763036987591313228939440555849867973 # solution to (x-5)*exp(x)+5 = 0 => x = W(5/exp(5))+5 wien_u = 2.8214393721220788934031913302944851953458817440731 # solution to (u-3)*exp(u)+3 = 0 => u = W(3/exp(3))+3 eulers_number = 2.71828182845904523536028747135266249775724709369995 #### DEFINED EXACT CONSTANTS #### speed_of_light = 299792458 m/s = c = c_0 # since 1983 planck_constant = 6.62607015e-34 J s = ℎ # since May 2019 elementary_charge = 1.602176634e-19 C = e # since May 2019 avogadro_number = 6.02214076e23 # since May 2019 boltzmann_constant = 1.380649e-23 J K^-1 = k = k_B # since May 2019 standard_gravity = 9.80665 m/s^2 = g_0 = g0 = g_n = gravity # since 1901 standard_atmosphere = 1.01325e5 Pa = atm = atmosphere # since 1954 conventional_josephson_constant = 4.835979e14 Hz / V = K_J90 # since Jan 1990 conventional_von_klitzing_constant = 2.5812807e4 ohm = R_K90 # since Jan 1990 #### DERIVED EXACT CONSTANTS #### # Floating-point conversion may introduce inaccuracies zeta = c / (cm/s) = ζ dirac_constant = ℎ / (2 * π) = ħ = hbar = atomic_unit_of_action = a_u_action avogadro_constant = avogadro_number * mol^-1 = N_A molar_gas_constant = k * N_A = R faraday_constant = e * N_A conductance_quantum = 2 * e ** 2 / ℎ = G_0 magnetic_flux_quantum = ℎ / (2 * e) = Φ_0 = Phi_0 josephson_constant = 2 * e / ℎ = K_J von_klitzing_constant = ℎ / e ** 2 = R_K stefan_boltzmann_constant = 2 / 15 * π ** 5 * k ** 4 / (ℎ ** 3 * c ** 2) = σ = sigma first_radiation_constant = 2 * π * ℎ * c ** 2 = c_1 second_radiation_constant = ℎ * c / k = c_2 wien_wavelength_displacement_law_constant = ℎ * c / (k * wien_x) wien_frequency_displacement_law_constant = wien_u * k / ℎ #### MEASURED CONSTANTS #### # Recommended CODATA-2018 values # To some extent, what is measured and what is derived is a bit arbitrary. # The choice of measured constants is based on convenience and on available uncertainty. # The uncertainty in the last significant digits is given in parentheses as a comment. newtonian_constant_of_gravitation = 6.67430e-11 m^3/(kg s^2) = _ = gravitational_constant # (15) rydberg_constant = 1.0973731568160e7 * m^-1 = R_∞ = R_inf # (21) electron_g_factor = -2.00231930436256 = g_e # (35) atomic_mass_constant = 1.66053906660e-27 kg = m_u # (50) electron_mass = 9.1093837015e-31 kg = m_e = atomic_unit_of_mass = a_u_mass # (28) proton_mass = 1.67262192369e-27 kg = m_p # (51) neutron_mass = 1.67492749804e-27 kg = m_n # (95) lattice_spacing_of_Si = 1.920155716e-10 m = d_220 # (32) K_alpha_Cu_d_220 = 0.80232719 # (22) K_alpha_Mo_d_220 = 0.36940604 # (19) K_alpha_W_d_220 = 0.108852175 # (98) #### DERIVED CONSTANTS #### fine_structure_constant = (2 * ℎ * R_inf / (m_e * c)) ** 0.5 = α = alpha vacuum_permeability = 2 * α * ℎ / (e ** 2 * c) = µ_0 = mu_0 = mu0 = magnetic_constant vacuum_permittivity = e ** 2 / (2 * α * ℎ * c) = ε_0 = epsilon_0 = eps_0 = eps0 = electric_constant impedance_of_free_space = 2 * α * ℎ / e ** 2 = Z_0 = characteristic_impedance_of_vacuum coulomb_constant = α * hbar * c / e ** 2 = k_C classical_electron_radius = α * hbar / (m_e * c) = r_e thomson_cross_section = 8 / 3 * π * r_e ** 2 = σ_e = sigma_e pint-0.24.4/pint/context.py000066400000000000000000000007141471316474000155740ustar00rootroot00000000000000""" pint.context ~~~~~~~~~~~~ Functions and classes related to context definitions and application. :copyright: 2016 by Pint Authors, see AUTHORS for more details.. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: pass #: Regex to match the header parts of a context. #: Regex to match variable names in an equation. # TODO: delete this file pint-0.24.4/pint/converters.py000066400000000000000000000043331471316474000163030ustar00rootroot00000000000000""" pint.converters ~~~~~~~~~~~~~~~ Functions and classes related to unit conversions. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from dataclasses import dataclass from dataclasses import fields as dc_fields from typing import Any, ClassVar from ._typing import Magnitude from .compat import HAS_NUMPY, Self, exp, log # noqa: F401 @dataclass(frozen=True) class Converter: """Base class for value converters.""" _subclasses: ClassVar[list[type[Converter]]] = [] _param_names_to_subclass: ClassVar[dict[frozenset[str], type[Converter]]] = {} @property def is_multiplicative(self) -> bool: return True @property def is_logarithmic(self) -> bool: return False def to_reference(self, value: Magnitude, inplace: bool = False) -> Magnitude: return value def from_reference(self, value: Magnitude, inplace: bool = False) -> Magnitude: return value def __init_subclass__(cls, **kwargs: Any): # Get constructor parameters super().__init_subclass__(**kwargs) cls._subclasses.append(cls) @classmethod def get_field_names(cls, new_cls: type) -> frozenset[str]: return frozenset(p.name for p in dc_fields(new_cls)) @classmethod def preprocess_kwargs(cls, **kwargs: Any) -> dict[str, Any] | None: return None @classmethod def from_arguments(cls, **kwargs: Any) -> Converter: kwk = frozenset(kwargs.keys()) try: new_cls = cls._param_names_to_subclass[kwk] except KeyError: for new_cls in cls._subclasses: p_names = frozenset(p.name for p in dc_fields(new_cls)) if p_names == kwk: cls._param_names_to_subclass[kwk] = new_cls break else: params = "(" + ", ".join(tuple(kwk)) + ")" raise ValueError( f"There is no class registered for parameters {params}" ) kw = new_cls.preprocess_kwargs(**kwargs) if kw is None: return new_cls(**kwargs) return cls.from_arguments(**kw) pint-0.24.4/pint/default_en.txt000066400000000000000000000743621471316474000164170ustar00rootroot00000000000000# Default Pint units definition file # Based on the International System of Units # Language: english # :copyright: 2013,2019 by Pint Authors, see AUTHORS for more details. # Syntax # ====== # Units # ----- # = [= ] [= ] [ = ] [...] # # The canonical name and aliases should be expressed in singular form. # Pint automatically deals with plurals built by adding 's' to the singular form; plural # forms that don't follow this rule should be instead explicitly listed as aliases. # # If a unit has no symbol and one wants to define aliases, then the symbol should be # conventionally set to _. # # Example: # millennium = 1e3 * year = _ = millennia # # # Prefixes # -------- # - = [= ] [= ] [ = ] [...] # # Example: # deca- = 1e+1 = da- = deka- # # # Derived dimensions # ------------------ # [dimension name] = # # Example: # [density] = [mass] / [volume] # # Note that primary dimensions don't need to be declared; they can be # defined for the first time in a unit definition. # E.g. see below `meter = [length]` # # # Additional aliases # ------------------ # @alias = [ = ] [...] # # Used to add aliases to already existing unit definitions. # Particularly useful when one wants to enrich definitions # from defaults_en.txt with custom aliases. # # Example: # @alias meter = my_meter # See also: https://pint.readthedocs.io/en/latest/defining.html @defaults group = international system = mks @end #### PREFIXES #### # decimal prefixes quecto- = 1e-30 = q- ronto- = 1e-27 = r- yocto- = 1e-24 = y- zepto- = 1e-21 = z- atto- = 1e-18 = a- femto- = 1e-15 = f- pico- = 1e-12 = p- nano- = 1e-9 = n- # The micro (U+00B5) and Greek mu (U+03BC) are both valid prefixes, # and they often use the same glyph. micro- = 1e-6 = µ- = μ- = u- = mu- = mc- milli- = 1e-3 = m- centi- = 1e-2 = c- deci- = 1e-1 = d- deca- = 1e+1 = da- = deka- hecto- = 1e2 = h- kilo- = 1e3 = k- mega- = 1e6 = M- giga- = 1e9 = G- tera- = 1e12 = T- peta- = 1e15 = P- exa- = 1e18 = E- zetta- = 1e21 = Z- yotta- = 1e24 = Y- ronna- = 1e27 = R- quetta- = 1e30 = Q- # binary_prefixes kibi- = 2**10 = Ki- mebi- = 2**20 = Mi- gibi- = 2**30 = Gi- tebi- = 2**40 = Ti- pebi- = 2**50 = Pi- exbi- = 2**60 = Ei- zebi- = 2**70 = Zi- yobi- = 2**80 = Yi- # extra_prefixes semi- = 0.5 = _ = demi- sesqui- = 1.5 #### BASE UNITS #### meter = [length] = m = metre second = [time] = s = sec ampere = [current] = A = amp candela = [luminosity] = cd = candle gram = [mass] = g mole = [substance] = mol kelvin = [temperature]; offset: 0 = K = degK = °K = degree_Kelvin = degreeK # older names supported for compatibility radian = [] = rad bit = [] count = [] #### CONSTANTS #### @import constants_en.txt #### UNITS #### # Common and less common, grouped by quantity. # Conversion factors are exact (except when noted), # although floating-point conversion may introduce inaccuracies # Angle turn = 2 * π * radian = _ = revolution = cycle = circle degree = π / 180 * radian = deg = arcdeg = arcdegree = angular_degree arcminute = degree / 60 = arcmin = arc_minute = angular_minute arcsecond = arcminute / 60 = arcsec = arc_second = angular_second milliarcsecond = 1e-3 * arcsecond = mas grade = π / 200 * radian = grad = gon mil = π / 32000 * radian # Solid angle steradian = radian ** 2 = sr square_degree = (π / 180) ** 2 * sr = sq_deg = sqdeg # Information baud = bit / second = Bd = bps byte = 8 * bit = B = octet # byte = 8 * bit = _ = octet ## NOTE: B (byte) symbol can conflict with Bell # Ratios percent = 0.01 = % permille = 0.001 = ‰ ppm = 1e-6 # Length angstrom = 1e-10 * meter = Å = ångström = Å micron = micrometer = µ = μ fermi = femtometer = fm light_year = speed_of_light * julian_year = ly = lightyear astronomical_unit = 149597870700 * meter = au # since Aug 2012 parsec = 1 / tansec * astronomical_unit = pc nautical_mile = 1852 * meter = nmi bohr = hbar / (alpha * m_e * c) = a_0 = a0 = bohr_radius = atomic_unit_of_length = a_u_length x_unit_Cu = K_alpha_Cu_d_220 * d_220 / 1537.4 = Xu_Cu x_unit_Mo = K_alpha_Mo_d_220 * d_220 / 707.831 = Xu_Mo angstrom_star = K_alpha_W_d_220 * d_220 / 0.2090100 = Å_star planck_length = (hbar * gravitational_constant / c ** 3) ** 0.5 # Mass metric_ton = 1e3 * kilogram = t = tonne unified_atomic_mass_unit = atomic_mass_constant = u = amu dalton = atomic_mass_constant = Da grain = 64.79891 * milligram = gr gamma_mass = microgram carat = 200 * milligram = ct = karat planck_mass = (hbar * c / gravitational_constant) ** 0.5 # Time minute = 60 * second = min hour = 60 * minute = h = hr day = 24 * hour = d week = 7 * day fortnight = 2 * week year = 365.25 * day = a = yr = julian_year month = year / 12 # decade = 10 * year ## NOTE: decade [time] can conflict with decade [dimensionless] century = 100 * year = _ = centuries millennium = 1e3 * year = _ = millennia eon = 1e9 * year shake = 1e-8 * second svedberg = 1e-13 * second atomic_unit_of_time = hbar / E_h = a_u_time gregorian_year = 365.2425 * day sidereal_year = 365.256363004 * day # approximate, as of J2000 epoch tropical_year = 365.242190402 * day # approximate, as of J2000 epoch common_year = 365 * day leap_year = 366 * day sidereal_day = day / 1.00273790935079524 # approximate sidereal_month = 27.32166155 * day # approximate tropical_month = 27.321582 * day # approximate synodic_month = 29.530589 * day = _ = lunar_month # approximate planck_time = (hbar * gravitational_constant / c ** 5) ** 0.5 # Temperature degree_Celsius = kelvin; offset: 273.15 = °C = celsius = degC = degreeC degree_Rankine = 5 / 9 * kelvin; offset: 0 = °R = rankine = degR = degreeR degree_Fahrenheit = 5 / 9 * kelvin; offset: 233.15 + 200 / 9 = °F = fahrenheit = degF = degreeF degree_Reaumur = 4 / 5 * kelvin; offset: 273.15 = °Re = reaumur = degRe = degreeRe = degree_Réaumur = réaumur atomic_unit_of_temperature = E_h / k = a_u_temp planck_temperature = (hbar * c ** 5 / gravitational_constant / k ** 2) ** 0.5 # Area [area] = [length] ** 2 are = 100 * meter ** 2 barn = 1e-28 * meter ** 2 = b darcy = centipoise * centimeter ** 2 / (second * atmosphere) hectare = 100 * are = ha # Volume [volume] = [length] ** 3 liter = decimeter ** 3 = l = L = ℓ = litre cubic_centimeter = centimeter ** 3 = cc lambda = microliter = λ stere = meter ** 3 # Frequency [frequency] = 1 / [time] hertz = 1 / second = Hz revolutions_per_minute = revolution / minute = rpm revolutions_per_second = revolution / second = rps counts_per_second = count / second = cps # Wavenumber [wavenumber] = 1 / [length] reciprocal_centimeter = 1 / cm = cm_1 = kayser # Velocity [velocity] = [length] / [time] [speed] = [velocity] knot = nautical_mile / hour = kt = knot_international = international_knot mile_per_hour = mile / hour = mph = MPH kilometer_per_hour = kilometer / hour = kph = KPH kilometer_per_second = kilometer / second = kps meter_per_second = meter / second = mps foot_per_second = foot / second = fps # Volumetric Flow Rate [volumetric_flow_rate] = [volume] / [time] sverdrup = 1e6 * meter ** 3 / second = sv # Acceleration [acceleration] = [velocity] / [time] galileo = centimeter / second ** 2 = Gal # Force [force] = [mass] * [acceleration] newton = kilogram * meter / second ** 2 = N dyne = gram * centimeter / second ** 2 = dyn force_kilogram = g_0 * kilogram = kgf = kilogram_force = pond force_gram = g_0 * gram = gf = gram_force force_metric_ton = g_0 * metric_ton = tf = metric_ton_force = force_t = t_force atomic_unit_of_force = E_h / a_0 = a_u_force # Energy [energy] = [force] * [length] joule = newton * meter = J erg = dyne * centimeter watt_hour = watt * hour = Wh = watthour electron_volt = e * volt = eV rydberg = ℎ * c * R_inf = Ry hartree = 2 * rydberg = E_h = Eh = hartree_energy = atomic_unit_of_energy = a_u_energy calorie = 4.184 * joule = cal = thermochemical_calorie = cal_th international_calorie = 4.1868 * joule = cal_it = international_steam_table_calorie fifteen_degree_calorie = 4.1855 * joule = cal_15 british_thermal_unit = 1055.056 * joule = Btu = BTU = Btu_iso international_british_thermal_unit = 1e3 * pound / kilogram * degR / kelvin * international_calorie = Btu_it thermochemical_british_thermal_unit = 1e3 * pound / kilogram * degR / kelvin * calorie = Btu_th quadrillion_Btu = 1e15 * Btu = quad therm = 1e5 * Btu = thm = EC_therm US_therm = 1.054804e8 * joule # approximate, no exact definition ton_TNT = 1e9 * calorie = tTNT tonne_of_oil_equivalent = 1e10 * international_calorie = toe atmosphere_liter = atmosphere * liter = atm_l # Power [power] = [energy] / [time] watt = joule / second = W volt_ampere = volt * ampere = VA horsepower = 550 * foot * force_pound / second = hp = UK_horsepower = hydraulic_horsepower boiler_horsepower = 33475 * Btu / hour # unclear which Btu metric_horsepower = 75 * force_kilogram * meter / second electrical_horsepower = 746 * watt refrigeration_ton = 12e3 * Btu / hour = _ = ton_of_refrigeration # approximate, no exact definition cooling_tower_ton = 1.25 * refrigeration_ton # approximate, no exact definition standard_liter_per_minute = atmosphere * liter / minute = slpm = slm conventional_watt_90 = K_J90 ** 2 * R_K90 / (K_J ** 2 * R_K) * watt = W_90 # Momentum [momentum] = [length] * [mass] / [time] # Density (as auxiliary for pressure) [density] = [mass] / [volume] mercury = 13.5951 * kilogram / liter = Hg = Hg_0C = Hg_32F = conventional_mercury water = 1.0 * kilogram / liter = H2O = conventional_water mercury_60F = 13.5568 * kilogram / liter = Hg_60F # approximate water_39F = 0.999972 * kilogram / liter = water_4C # approximate water_60F = 0.999001 * kilogram / liter # approximate # Pressure [pressure] = [force] / [area] pascal = newton / meter ** 2 = Pa barye = dyne / centimeter ** 2 = Ba = barie = barad = barrie = baryd bar = 1e5 * pascal technical_atmosphere = kilogram * g_0 / centimeter ** 2 = at torr = atm / 760 pound_force_per_square_inch = force_pound / inch ** 2 = psi kip_per_square_inch = kip / inch ** 2 = ksi millimeter_Hg = millimeter * Hg * g_0 = mmHg = mm_Hg = millimeter_Hg_0C centimeter_Hg = centimeter * Hg * g_0 = cmHg = cm_Hg = centimeter_Hg_0C inch_Hg = inch * Hg * g_0 = inHg = in_Hg = inch_Hg_32F inch_Hg_60F = inch * Hg_60F * g_0 inch_H2O_39F = inch * water_39F * g_0 inch_H2O_60F = inch * water_60F * g_0 foot_H2O = foot * water * g_0 = ftH2O = feet_H2O centimeter_H2O = centimeter * water * g_0 = cmH2O = cm_H2O sound_pressure_level = 20e-6 * pascal = SPL # Torque [torque] = [force] * [length] foot_pound = foot * force_pound = ft_lb = footpound # Viscosity [viscosity] = [pressure] * [time] poise = 0.1 * Pa * second = P reyn = psi * second # Kinematic viscosity [kinematic_viscosity] = [area] / [time] stokes = centimeter ** 2 / second = St # Fluidity [fluidity] = 1 / [viscosity] rhe = 1 / poise # Amount of substance particle = 1 / N_A = _ = molec = molecule # Concentration [concentration] = [substance] / [volume] molar = mole / liter = M # Catalytic activity [activity] = [substance] / [time] katal = mole / second = kat enzyme_unit = micromole / minute = U = enzymeunit # Entropy [entropy] = [energy] / [temperature] clausius = calorie / kelvin = Cl # Molar entropy [molar_entropy] = [entropy] / [substance] entropy_unit = calorie / kelvin / mole = eu # Radiation becquerel = counts_per_second = Bq curie = 3.7e10 * becquerel = Ci rutherford = 1e6 * becquerel = Rd gray = joule / kilogram = Gy sievert = joule / kilogram = Sv rads = 0.01 * gray rem = 0.01 * sievert roentgen = 2.58e-4 * coulomb / kilogram = _ = röntgen # approximate, depends on medium # Heat transimission [heat_transmission] = [energy] / [area] peak_sun_hour = 1e3 * watt_hour / meter ** 2 = PSH langley = thermochemical_calorie / centimeter ** 2 = Ly # Luminance [luminance] = [luminosity] / [area] nit = candela / meter ** 2 stilb = candela / centimeter ** 2 lambert = 1 / π * candela / centimeter ** 2 # Luminous flux [luminous_flux] = [luminosity] lumen = candela * steradian = lm # Illuminance [illuminance] = [luminous_flux] / [area] lux = lumen / meter ** 2 = lx # Intensity [intensity] = [power] / [area] atomic_unit_of_intensity = 0.5 * ε_0 * c * atomic_unit_of_electric_field ** 2 = a_u_intensity # Current biot = 10 * ampere = Bi abampere = biot = abA atomic_unit_of_current = e / atomic_unit_of_time = a_u_current mean_international_ampere = mean_international_volt / mean_international_ohm = A_it US_international_ampere = US_international_volt / US_international_ohm = A_US conventional_ampere_90 = K_J90 * R_K90 / (K_J * R_K) * ampere = A_90 planck_current = (c ** 6 / gravitational_constant / k_C) ** 0.5 # Charge [charge] = [current] * [time] coulomb = ampere * second = C abcoulomb = 10 * C = abC faraday = e * N_A * mole conventional_coulomb_90 = K_J90 * R_K90 / (K_J * R_K) * coulomb = C_90 ampere_hour = ampere * hour = Ah # Electric potential [electric_potential] = [energy] / [charge] volt = joule / coulomb = V abvolt = 1e-8 * volt = abV mean_international_volt = 1.00034 * volt = V_it # approximate US_international_volt = 1.00033 * volt = V_US # approximate conventional_volt_90 = K_J90 / K_J * volt = V_90 # Electric field [electric_field] = [electric_potential] / [length] atomic_unit_of_electric_field = e * k_C / a_0 ** 2 = a_u_electric_field # Electric displacement field [electric_displacement_field] = [charge] / [area] # Reduced electric field [reduced_electric_field] = [electric_field] * [area] townsend = 1e-21 * V * m^2 = Td # Resistance [resistance] = [electric_potential] / [current] ohm = volt / ampere = Ω abohm = 1e-9 * ohm = abΩ mean_international_ohm = 1.00049 * ohm = Ω_it = ohm_it # approximate US_international_ohm = 1.000495 * ohm = Ω_US = ohm_US # approximate conventional_ohm_90 = R_K / R_K90 * ohm = Ω_90 = ohm_90 # Resistivity [resistivity] = [resistance] * [length] # Conductance [conductance] = [current] / [electric_potential] siemens = ampere / volt = S = mho absiemens = 1e9 * siemens = abS = abmho # Capacitance [capacitance] = [charge] / [electric_potential] farad = coulomb / volt = F abfarad = 1e9 * farad = abF conventional_farad_90 = R_K90 / R_K * farad = F_90 # Magnetic flux [magnetic_flux] = [electric_potential] * [time] weber = volt * second = Wb unit_pole = µ_0 * biot * centimeter # Inductance [inductance] = [magnetic_flux] / [current] henry = weber / ampere = H abhenry = 1e-9 * henry = abH conventional_henry_90 = R_K / R_K90 * henry = H_90 # Magnetic field [magnetic_field] = [magnetic_flux] / [area] tesla = weber / meter ** 2 = T gamma = 1e-9 * tesla = γ # Magnetomotive force [magnetomotive_force] = [current] ampere_turn = ampere = At biot_turn = biot gilbert = 1 / (4 * π) * biot_turn = Gb # Magnetic field strength [magnetic_field_strength] = [current] / [length] # Electric dipole moment [electric_dipole] = [charge] * [length] debye = 1e-9 / ζ * coulomb * angstrom = D # formally 1 D = 1e-10 Fr*Å, but we generally want to use it outside the Gaussian context # Electric quadrupole moment [electric_quadrupole] = [charge] * [area] buckingham = debye * angstrom # Magnetic dipole moment [magnetic_dipole] = [current] * [area] bohr_magneton = e * hbar / (2 * m_e) = µ_B = mu_B nuclear_magneton = e * hbar / (2 * m_p) = µ_N = mu_N # Refractive index [refractive_index] = [] refractive_index_unit = [] = RIU # Logaritmic Unit Definition # Unit = scale; logbase; logfactor # x_dB = [logfactor] * log( x_lin / [scale] ) / log( [logbase] ) # Logaritmic Units of dimensionless quantity: [ https://en.wikipedia.org/wiki/Level_(logarithmic_quantity) ] decibelwatt = watt; logbase: 10; logfactor: 10 = dBW decibelmilliwatt = 1e-3 watt; logbase: 10; logfactor: 10 = dBm decibelmicrowatt = 1e-6 watt; logbase: 10; logfactor: 10 = dBu decibel = 1 ; logbase: 10; logfactor: 10 = dB # bell = 1 ; logbase: 10; logfactor: = B ## NOTE: B (Bell) symbol conflicts with byte decade = 1 ; logbase: 10; logfactor: 1 ## NOTE: decade [time] can conflict with decade [dimensionless] octave = 1 ; logbase: 2; logfactor: 1 = oct neper = 1 ; logbase: 2.71828182845904523536028747135266249775724709369995; logfactor: 0.5 = Np # neper = 1 ; logbase: eulers_number; logfactor: 0.5 = Np #### UNIT GROUPS #### # Mostly for length, area, volume, mass, force # (customary or specialized units) @group USCSLengthInternational thou = 1e-3 * inch = th = mil_length inch = yard / 36 = in = international_inch = inches = international_inches hand = 4 * inch foot = yard / 3 = ft = international_foot = feet = international_feet yard = 0.9144 * meter = yd = international_yard # since Jul 1959 mile = 1760 * yard = mi = international_mile circular_mil = π / 4 * mil_length ** 2 = cmil square_inch = inch ** 2 = sq_in = square_inches square_foot = foot ** 2 = sq_ft = square_feet square_yard = yard ** 2 = sq_yd square_mile = mile ** 2 = sq_mi cubic_inch = in ** 3 = cu_in cubic_foot = ft ** 3 = cu_ft = cubic_feet cubic_yard = yd ** 3 = cu_yd @end @group USCSLengthSurvey link = 1e-2 * chain = li = survey_link survey_foot = 1200 / 3937 * meter = sft fathom = 6 * survey_foot rod = 16.5 * survey_foot = rd = pole = perch chain = 4 * rod furlong = 40 * rod = fur cables_length = 120 * fathom survey_mile = 5280 * survey_foot = smi = us_statute_mile league = 3 * survey_mile square_rod = rod ** 2 = sq_rod = sq_pole = sq_perch acre = 10 * chain ** 2 square_survey_mile = survey_mile ** 2 = _ = section square_league = league ** 2 acre_foot = acre * survey_foot = _ = acre_feet @end @group USCSDryVolume dry_pint = bushel / 64 = dpi = US_dry_pint dry_quart = bushel / 32 = dqt = US_dry_quart dry_gallon = bushel / 8 = dgal = US_dry_gallon peck = bushel / 4 = pk bushel = 2150.42 cubic_inch = bu dry_barrel = 7056 cubic_inch = _ = US_dry_barrel board_foot = ft * ft * in = FBM = board_feet = BF = BDFT = super_foot = superficial_foot = super_feet = superficial_feet @end @group USCSLiquidVolume minim = pint / 7680 fluid_dram = pint / 128 = fldr = fluidram = US_fluid_dram = US_liquid_dram fluid_ounce = pint / 16 = floz = US_fluid_ounce = US_liquid_ounce gill = pint / 4 = gi = liquid_gill = US_liquid_gill pint = quart / 2 = pt = liquid_pint = US_pint fifth = gallon / 5 = _ = US_liquid_fifth quart = gallon / 4 = qt = liquid_quart = US_liquid_quart gallon = 231 * cubic_inch = gal = liquid_gallon = US_liquid_gallon @end @group USCSVolumeOther teaspoon = fluid_ounce / 6 = tsp tablespoon = fluid_ounce / 2 = tbsp shot = 3 * tablespoon = jig = US_shot cup = pint / 2 = cp = liquid_cup = US_liquid_cup barrel = 31.5 * gallon = bbl oil_barrel = 42 * gallon = oil_bbl beer_barrel = 31 * gallon = beer_bbl hogshead = 63 * gallon @end @group Avoirdupois dram = pound / 256 = dr = avoirdupois_dram = avdp_dram = drachm ounce = pound / 16 = oz = avoirdupois_ounce = avdp_ounce pound = 7e3 * grain = lb = avoirdupois_pound = avdp_pound stone = 14 * pound quarter = 28 * stone bag = 94 * pound hundredweight = 100 * pound = cwt = short_hundredweight long_hundredweight = 112 * pound ton = 2e3 * pound = _ = short_ton long_ton = 2240 * pound slug = g_0 * pound * second ** 2 / foot slinch = g_0 * pound * second ** 2 / inch = blob = slugette force_ounce = g_0 * ounce = ozf = ounce_force force_pound = g_0 * pound = lbf = pound_force force_ton = g_0 * ton = _ = ton_force = force_short_ton = short_ton_force force_long_ton = g_0 * long_ton = _ = long_ton_force kip = 1e3 * force_pound poundal = pound * foot / second ** 2 = pdl @end @group AvoirdupoisUK using Avoirdupois UK_hundredweight = long_hundredweight = UK_cwt UK_ton = long_ton UK_force_ton = force_long_ton = _ = UK_ton_force @end @group AvoirdupoisUS using Avoirdupois US_hundredweight = hundredweight = US_cwt US_ton = ton US_force_ton = force_ton = _ = US_ton_force @end @group Troy pennyweight = 24 * grain = dwt troy_ounce = 480 * grain = toz = ozt troy_pound = 12 * troy_ounce = tlb = lbt @end @group Apothecary scruple = 20 * grain apothecary_dram = 3 * scruple = ap_dr apothecary_ounce = 8 * apothecary_dram = ap_oz apothecary_pound = 12 * apothecary_ounce = ap_lb @end @group ImperialVolume imperial_minim = imperial_fluid_ounce / 480 imperial_fluid_scruple = imperial_fluid_ounce / 24 imperial_fluid_drachm = imperial_fluid_ounce / 8 = imperial_fldr = imperial_fluid_dram imperial_fluid_ounce = imperial_pint / 20 = imperial_floz = UK_fluid_ounce imperial_gill = imperial_pint / 4 = imperial_gi = UK_gill imperial_cup = imperial_pint / 2 = imperial_cp = UK_cup imperial_pint = imperial_gallon / 8 = imperial_pt = UK_pint imperial_quart = imperial_gallon / 4 = imperial_qt = UK_quart imperial_gallon = 4.54609 * liter = imperial_gal = UK_gallon imperial_peck = 2 * imperial_gallon = imperial_pk = UK_pk imperial_bushel = 8 * imperial_gallon = imperial_bu = UK_bushel imperial_barrel = 36 * imperial_gallon = imperial_bbl = UK_bbl @end @group Printer pica = inch / 6 = _ = printers_pica point = pica / 12 = pp = printers_point = big_point = bp didot = 1 / 2660 * m cicero = 12 * didot tex_point = inch / 72.27 tex_pica = 12 * tex_point tex_didot = 1238 / 1157 * tex_point tex_cicero = 12 * tex_didot scaled_point = tex_point / 65536 css_pixel = inch / 96 = px pixel = [printing_unit] = _ = dot = pel = picture_element pixels_per_centimeter = pixel / cm = PPCM pixels_per_inch = pixel / inch = dots_per_inch = PPI = ppi = DPI = printers_dpi bits_per_pixel = bit / pixel = bpp @end @group Textile tex = gram / kilometer = Tt dtex = decitex denier = gram / (9 * kilometer) = den jute = pound / (14400 * yard) = Tj aberdeen = jute = Ta RKM = gf / tex number_english = 840 * yard / pound = Ne = NeC = ECC number_meter = kilometer / kilogram = Nm @end #### CGS ELECTROMAGNETIC UNITS #### # === Gaussian system of units === @group Gaussian franklin = erg ** 0.5 * centimeter ** 0.5 = Fr = statcoulomb = statC = esu statvolt = erg / franklin = statV statampere = franklin / second = statA gauss = dyne / franklin = G maxwell = gauss * centimeter ** 2 = Mx oersted = dyne / maxwell = Oe = ørsted statohm = statvolt / statampere = statΩ statfarad = franklin / statvolt = statF statmho = statampere / statvolt @end # Note this system is not commensurate with SI, as ε_0 and µ_0 disappear; # some quantities with different dimensions in SI have the same # dimensions in the Gaussian system (e.g. [Mx] = [Fr], but [Wb] != [C]), # and therefore the conversion factors depend on the context (not in pint sense) [gaussian_charge] = [length] ** 1.5 * [mass] ** 0.5 / [time] [gaussian_current] = [gaussian_charge] / [time] [gaussian_electric_potential] = [gaussian_charge] / [length] [gaussian_electric_field] = [gaussian_electric_potential] / [length] [gaussian_electric_displacement_field] = [gaussian_charge] / [area] [gaussian_electric_flux] = [gaussian_charge] [gaussian_electric_dipole] = [gaussian_charge] * [length] [gaussian_electric_quadrupole] = [gaussian_charge] * [area] [gaussian_magnetic_field] = [force] / [gaussian_charge] [gaussian_magnetic_field_strength] = [gaussian_magnetic_field] [gaussian_magnetic_flux] = [gaussian_magnetic_field] * [area] [gaussian_magnetic_dipole] = [energy] / [gaussian_magnetic_field] [gaussian_resistance] = [gaussian_electric_potential] / [gaussian_current] [gaussian_resistivity] = [gaussian_resistance] * [length] [gaussian_capacitance] = [gaussian_charge] / [gaussian_electric_potential] [gaussian_inductance] = [gaussian_electric_potential] * [time] / [gaussian_current] [gaussian_conductance] = [gaussian_current] / [gaussian_electric_potential] @context Gaussian = Gau [gaussian_charge] -> [charge]: value / k_C ** 0.5 [charge] -> [gaussian_charge]: value * k_C ** 0.5 [gaussian_current] -> [current]: value / k_C ** 0.5 [current] -> [gaussian_current]: value * k_C ** 0.5 [gaussian_electric_potential] -> [electric_potential]: value * k_C ** 0.5 [electric_potential] -> [gaussian_electric_potential]: value / k_C ** 0.5 [gaussian_electric_field] -> [electric_field]: value * k_C ** 0.5 [electric_field] -> [gaussian_electric_field]: value / k_C ** 0.5 [gaussian_electric_displacement_field] -> [electric_displacement_field]: value / (4 * π / ε_0) ** 0.5 [electric_displacement_field] -> [gaussian_electric_displacement_field]: value * (4 * π / ε_0) ** 0.5 [gaussian_electric_dipole] -> [electric_dipole]: value / k_C ** 0.5 [electric_dipole] -> [gaussian_electric_dipole]: value * k_C ** 0.5 [gaussian_electric_quadrupole] -> [electric_quadrupole]: value / k_C ** 0.5 [electric_quadrupole] -> [gaussian_electric_quadrupole]: value * k_C ** 0.5 [gaussian_magnetic_field] -> [magnetic_field]: value / (4 * π / µ_0) ** 0.5 [magnetic_field] -> [gaussian_magnetic_field]: value * (4 * π / µ_0) ** 0.5 [gaussian_magnetic_flux] -> [magnetic_flux]: value / (4 * π / µ_0) ** 0.5 [magnetic_flux] -> [gaussian_magnetic_flux]: value * (4 * π / µ_0) ** 0.5 [gaussian_magnetic_field_strength] -> [magnetic_field_strength]: value / (4 * π * µ_0) ** 0.5 [magnetic_field_strength] -> [gaussian_magnetic_field_strength]: value * (4 * π * µ_0) ** 0.5 [gaussian_magnetic_dipole] -> [magnetic_dipole]: value * (4 * π / µ_0) ** 0.5 [magnetic_dipole] -> [gaussian_magnetic_dipole]: value / (4 * π / µ_0) ** 0.5 [gaussian_resistance] -> [resistance]: value * k_C [resistance] -> [gaussian_resistance]: value / k_C [gaussian_resistivity] -> [resistivity]: value * k_C [resistivity] -> [gaussian_resistivity]: value / k_C [gaussian_capacitance] -> [capacitance]: value / k_C [capacitance] -> [gaussian_capacitance]: value * k_C [gaussian_inductance] -> [inductance]: value * k_C [inductance] -> [gaussian_inductance]: value / k_C [gaussian_conductance] -> [conductance]: value / k_C [conductance] -> [gaussian_conductance]: value * k_C @end # === ESU system of units === # (where different from Gaussian) # See note for Gaussian system too @group ESU using Gaussian statweber = statvolt * second = statWb stattesla = statweber / centimeter ** 2 = statT stathenry = statweber / statampere = statH @end [esu_charge] = [length] ** 1.5 * [mass] ** 0.5 / [time] [esu_current] = [esu_charge] / [time] [esu_electric_potential] = [esu_charge] / [length] [esu_magnetic_flux] = [esu_electric_potential] * [time] [esu_magnetic_field] = [esu_magnetic_flux] / [area] [esu_magnetic_field_strength] = [esu_current] / [length] [esu_magnetic_dipole] = [esu_current] * [area] @context ESU = esu [esu_magnetic_field] -> [magnetic_field]: value * k_C ** 0.5 [magnetic_field] -> [esu_magnetic_field]: value / k_C ** 0.5 [esu_magnetic_flux] -> [magnetic_flux]: value * k_C ** 0.5 [magnetic_flux] -> [esu_magnetic_flux]: value / k_C ** 0.5 [esu_magnetic_field_strength] -> [magnetic_field_strength]: value / (4 * π / ε_0) ** 0.5 [magnetic_field_strength] -> [esu_magnetic_field_strength]: value * (4 * π / ε_0) ** 0.5 [esu_magnetic_dipole] -> [magnetic_dipole]: value / k_C ** 0.5 [magnetic_dipole] -> [esu_magnetic_dipole]: value * k_C ** 0.5 @end #### CONVERSION CONTEXTS #### @context(n=1) spectroscopy = sp # n index of refraction of the medium. [length] <-> [frequency]: speed_of_light / n / value [frequency] -> [energy]: planck_constant * value [energy] -> [frequency]: value / planck_constant # allow wavenumber / kayser [wavenumber] <-> [length]: 1 / value @end @context boltzmann [temperature] -> [energy]: boltzmann_constant * value [energy] -> [temperature]: value / boltzmann_constant @end @context energy [energy] -> [energy] / [substance]: value * N_A [energy] / [substance] -> [energy]: value / N_A [energy] -> [mass]: value / c ** 2 [mass] -> [energy]: value * c ** 2 @end @context(mw=0,volume=0,solvent_mass=0) chemistry = chem # mw is the molecular weight of the species # volume is the volume of the solution # solvent_mass is the mass of solvent in the solution # moles -> mass require the molecular weight [substance] -> [mass]: value * mw [mass] -> [substance]: value / mw # moles/volume -> mass/volume and moles/mass -> mass/mass # require the molecular weight [substance] / [volume] -> [mass] / [volume]: value * mw [mass] / [volume] -> [substance] / [volume]: value / mw [substance] / [mass] -> [mass] / [mass]: value * mw [mass] / [mass] -> [substance] / [mass]: value / mw # moles/volume -> moles requires the solution volume [substance] / [volume] -> [substance]: value * volume [substance] -> [substance] / [volume]: value / volume # moles/mass -> moles requires the solvent (usually water) mass [substance] / [mass] -> [substance]: value * solvent_mass [substance] -> [substance] / [mass]: value / solvent_mass # moles/mass -> moles/volume require the solvent mass and the volume [substance] / [mass] -> [substance]/[volume]: value * solvent_mass / volume [substance] / [volume] -> [substance] / [mass]: value / solvent_mass * volume @end @context textile # Allow switching between Direct count system (i.e. tex) and # Indirect count system (i.e. Ne, Nm) [mass] / [length] <-> [length] / [mass]: 1 / value @end #### SYSTEMS OF UNITS #### @system SI second meter kilogram ampere kelvin mole candela @end @system mks using international meter kilogram second @end @system cgs using international, Gaussian, ESU centimeter gram second @end @system atomic using international # based on unit m_e, e, hbar, k_C, k bohr: meter electron_mass: gram atomic_unit_of_time: second atomic_unit_of_current: ampere atomic_unit_of_temperature: kelvin @end @system Planck using international # based on unit c, gravitational_constant, hbar, k_C, k planck_length: meter planck_mass: gram planck_time: second planck_current: ampere planck_temperature: kelvin @end @system imperial using ImperialVolume, USCSLengthInternational, AvoirdupoisUK yard pound @end @system US using USCSLiquidVolume, USCSDryVolume, USCSVolumeOther, USCSLengthInternational, USCSLengthSurvey, AvoirdupoisUS yard pound @end pint-0.24.4/pint/definitions.py000066400000000000000000000024551471316474000164270ustar00rootroot00000000000000""" pint.definitions ~~~~~~~~~~~~~~~~ Kept for backwards compatibility :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import flexparser as fp from . import errors from .delegates import ParserConfig, txt_defparser class Definition: """This is kept for backwards compatibility""" @classmethod def from_string(cls, input_string: str, non_int_type: type = float) -> Definition: """Parse a string into a definition object. Parameters ---------- input_string Single line string. non_int_type Numerical type used for non integer values. Raises ------ DefinitionSyntaxError If a syntax error was found. """ cfg = ParserConfig(non_int_type) parser = txt_defparser.DefParser(cfg, None) pp = parser.parse_string(input_string) for definition in parser.iter_parsed_project(pp): if isinstance(definition, Exception): raise errors.DefinitionSyntaxError(str(definition)) if not isinstance(definition, (fp.BOS, fp.BOF, fp.BOS)): return definition # TODO: What shall we do in this return path. pint-0.24.4/pint/delegates/000077500000000000000000000000001471316474000154715ustar00rootroot00000000000000pint-0.24.4/pint/delegates/__init__.py000066400000000000000000000007271471316474000176100ustar00rootroot00000000000000""" pint.delegates ~~~~~~~~~~~~~~ Defines methods and classes to handle autonomous tasks. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from . import txt_defparser from .base_defparser import ParserConfig, build_disk_cache_class from .formatter import Formatter __all__ = ["txt_defparser", "ParserConfig", "build_disk_cache_class", "Formatter"] pint-0.24.4/pint/delegates/base_defparser.py000066400000000000000000000064221471316474000210140ustar00rootroot00000000000000""" pint.delegates.base_defparser ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Common class and function for all parsers. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import functools import itertools import numbers import pathlib from dataclasses import dataclass from typing import Any import flexcache as fc import flexparser as fp from pint import errors from pint.facets.plain.definitions import NotNumeric from pint.util import ParserHelper, UnitsContainer @dataclass(frozen=True) class ParserConfig: """Configuration used by the parser in Pint.""" #: Indicates the output type of non integer numbers. non_int_type: type[numbers.Number] = float def to_scaled_units_container(self, s: str): return ParserHelper.from_string(s, self.non_int_type) def to_units_container(self, s: str): v = self.to_scaled_units_container(s) if v.scale != 1: raise errors.UnexpectedScaleInContainer(str(v.scale)) return UnitsContainer(v) def to_dimension_container(self, s: str): v = self.to_units_container(s) invalid = tuple(itertools.filterfalse(errors.is_valid_dimension_name, v.keys())) if invalid: raise errors.DefinitionSyntaxError( f"Cannot build a dimension container with {', '.join(invalid)} that " + errors.MSG_INVALID_DIMENSION_NAME ) return v def to_number(self, s: str) -> numbers.Number: """Try parse a string into a number (without using eval). The string can contain a number or a simple equation (3 + 4) Raises ------ _NotNumeric If the string cannot be parsed as a number. """ val = self.to_scaled_units_container(s) if len(val): raise NotNumeric(s) return val.scale @dataclass(frozen=True) class PintParsedStatement(fp.ParsedStatement[ParserConfig]): """A parsed statement for pint, specialized in the actual config.""" @functools.lru_cache def build_disk_cache_class(chosen_non_int_type: type): """Build disk cache class, taking into account the non_int_type.""" @dataclass(frozen=True) class PintHeader(fc.InvalidateByExist, fc.NameByFields, fc.BasicPythonHeader): from .. import __version__ pint_version: str = __version__ non_int_type: str = chosen_non_int_type.__qualname__ @dataclass(frozen=True) class PathHeader(fc.NameByFileContent, PintHeader): pass @dataclass(frozen=True) class ParsedProjecHeader(fc.NameByHashIter, PintHeader): @classmethod def from_parsed_project( cls, pp: fp.ParsedProject[Any, ParserConfig], reader_id: str ): tmp = ( f"{stmt.content_hash.algorithm_name}:{stmt.content_hash.hexdigest}" for stmt in pp.iter_statements() if isinstance(stmt, fp.BOS) ) return cls(tuple(tmp), reader_id) class PintDiskCache(fc.DiskCache): _header_classes = { pathlib.Path: PathHeader, str: PathHeader.from_string, fp.ParsedProject: ParsedProjecHeader.from_parsed_project, } return PintDiskCache pint-0.24.4/pint/delegates/formatter/000077500000000000000000000000001471316474000174745ustar00rootroot00000000000000pint-0.24.4/pint/delegates/formatter/__init__.py000066400000000000000000000007741471316474000216150ustar00rootroot00000000000000""" pint.delegates.formatter ~~~~~~~~~~~~~~~~~~~~~~~~ Easy to replace and extend string formatting. See pint.delegates.formatter.plain.DefaultFormatter for a description of a formatter. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .full import FullFormatter class Formatter(FullFormatter): """Default Pint Formatter""" pass __all__ = [ "Formatter", ] pint-0.24.4/pint/delegates/formatter/_compound_unit_helpers.py000066400000000000000000000226051471316474000246170ustar00rootroot00000000000000""" pint.delegates.formatter._compound_unit_helpers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Convenient functions to help organize compount units. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import functools import locale from collections.abc import Callable, Iterable from functools import partial from itertools import filterfalse, tee from typing import ( TYPE_CHECKING, Any, Literal, TypedDict, TypeVar, ) from ...compat import TypeAlias, babel_parse from ...util import UnitsContainer T = TypeVar("T") U = TypeVar("U") V = TypeVar("V") W = TypeVar("W") if TYPE_CHECKING: from ...compat import Locale, Number from ...facets.plain import PlainUnit from ...registry import UnitRegistry class SortKwds(TypedDict): registry: UnitRegistry SortFunc: TypeAlias = Callable[ [Iterable[tuple[str, Any, str]], Any], Iterable[tuple[str, Any, str]] ] class BabelKwds(TypedDict): """Babel related keywords used in formatters.""" use_plural: bool length: Literal["short", "long", "narrow"] | None locale: Locale | str | None def partition( predicate: Callable[[T], bool], iterable: Iterable[T] ) -> tuple[filterfalse[T], filter[T]]: """Partition entries into false entries and true entries. If *predicate* is slow, consider wrapping it with functools.lru_cache(). """ # partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 t1, t2 = tee(iterable) return filterfalse(predicate, t1), filter(predicate, t2) def localize_per( length: Literal["short", "long", "narrow"] = "long", locale: Locale | str | None = locale.LC_NUMERIC, default: str | None = None, ) -> str: """Localized singular and plural form of a unit. THIS IS TAKEN FROM BABEL format_unit. But - No magnitude is returned in the string. - If the unit is not found, the default is given. - If the default is None, then the same value is given. """ locale = babel_parse(locale) patterns = locale._data["compound_unit_patterns"].get("per", None) if patterns is None: return default or "{}/{}" patterns = patterns.get(length, None) if patterns is None: return default or "{}/{}" # babel 2.8 if isinstance(patterns, str): return patterns # babe; 2.15 return patterns.get("compound", default or "{}/{}") @functools.lru_cache def localize_unit_name( measurement_unit: str, use_plural: bool, length: Literal["short", "long", "narrow"] = "long", locale: Locale | str | None = locale.LC_NUMERIC, default: str | None = None, ) -> str: """Localized singular and plural form of a unit. THIS IS TAKEN FROM BABEL format_unit. But - No magnitude is returned in the string. - If the unit is not found, the default is given. - If the default is None, then the same value is given. """ locale = babel_parse(locale) from babel.units import _find_unit_pattern, get_unit_name q_unit = _find_unit_pattern(measurement_unit, locale=locale) if not q_unit: return measurement_unit unit_patterns = locale._data["unit_patterns"][q_unit].get(length, {}) if use_plural: grammatical_number = "other" else: grammatical_number = "one" if grammatical_number in unit_patterns: return unit_patterns[grammatical_number].format("").replace("\xa0", "").strip() if default is not None: return default # Fall back to a somewhat bad representation. # nb: This is marked as no-cover, as the current CLDR seemingly has no way for this to happen. fallback_name = get_unit_name( measurement_unit, length=length, locale=locale ) # pragma: no cover return f"{fallback_name or measurement_unit}" # pragma: no cover def extract2(element: tuple[str, T, str]) -> tuple[str, T]: """Extract display name and exponent from a tuple containing display name, exponent and unit name.""" return element[:2] def to_name_exponent_name(element: tuple[str, T]) -> tuple[str, T, str]: """Convert unit name and exponent to unit name as display name, exponent and unit name.""" # TODO: write a generic typing return element + (element[0],) def to_symbol_exponent_name( el: tuple[str, T], registry: UnitRegistry ) -> tuple[str, T, str]: """Convert unit name and exponent to unit symbol as display name, exponent and unit name.""" return registry._get_symbol(el[0]), el[1], el[0] def localize_display_exponent_name( element: tuple[str, T, str], use_plural: bool, length: Literal["short", "long", "narrow"] = "long", locale: Locale | str | None = locale.LC_NUMERIC, default: str | None = None, ) -> tuple[str, T, str]: """Localize display name in a triplet display name, exponent and unit name.""" return ( localize_unit_name( element[2], use_plural, length, locale, default or element[0] ), element[1], element[2], ) ##################### # Sorting functions ##################### def sort_by_unit_name( items: Iterable[tuple[str, Number, str]], _registry: UnitRegistry | None ) -> Iterable[tuple[str, Number, str]]: return sorted(items, key=lambda el: el[2]) def sort_by_display_name( items: Iterable[tuple[str, Number, str]], _registry: UnitRegistry | None ) -> Iterable[tuple[str, Number, str]]: return sorted(items) def sort_by_dimensionality( items: Iterable[tuple[str, Number, str]], registry: UnitRegistry | None ) -> Iterable[tuple[str, Number, str]]: """Sort a list of units by dimensional order (from `registry.formatter.dim_order`). Parameters ---------- items : tuple a list of tuples containing (unit names, exponent values). registry : UnitRegistry | None the registry to use for looking up the dimensions of each unit. Returns ------- list the list of units sorted by most significant dimension first. Raises ------ KeyError If unit cannot be found in the registry. """ if registry is None: return items dim_order = registry.formatter.dim_order def sort_key(item: tuple[str, Number, str]): _display_name, _unit_exponent, unit_name = item cname = registry.get_name(unit_name) cname_dims = registry.get_dimensionality(cname) or {"[]": None} for cname_dim in cname_dims: if cname_dim in dim_order: return dim_order.index(cname_dim), cname raise KeyError(f"Unit {unit_name} (aka {cname}) has no recognized dimensions") return sorted(items, key=sort_key) def prepare_compount_unit( unit: PlainUnit | UnitsContainer | Iterable[tuple[str, T]], spec: str = "", sort_func: SortFunc | None = None, use_plural: bool = True, length: Literal["short", "long", "narrow"] | None = None, locale: Locale | str | None = None, as_ratio: bool = True, registry: UnitRegistry | None = None, ) -> tuple[Iterable[tuple[str, T]], Iterable[tuple[str, T]]]: """Format compound unit into unit container given an spec and locale. Returns ------- iterable of display name, exponent, canonical name """ if isinstance(unit, UnitsContainer): out = unit.items() elif hasattr(unit, "_units"): out = unit._units.items() else: out = unit # out: unit_name, unit_exponent if len(out) == 0: if "~" in spec: return ([], []) else: return ([("dimensionless", 1)], []) if "~" in spec: if registry is None: raise ValueError( f"Can't short format a {type(unit)} without a registry." " This is usually triggered when formatting a instance" " of the internal `UnitsContainer`." ) _to_symbol_exponent_name = partial(to_symbol_exponent_name, registry=registry) out = map(_to_symbol_exponent_name, out) else: out = map(to_name_exponent_name, out) # We keep unit_name because the sort or localizing functions might needed. # out: display_unit_name, unit_exponent, unit_name if as_ratio: numerator, denominator = partition(lambda el: el[1] < 0, out) else: numerator, denominator = out, () # numerator: display_unit_name, unit_name, unit_exponent # denominator: display_unit_name, unit_name, unit_exponent if locale is None: if sort_func is not None: numerator = sort_func(numerator, registry) denominator = sort_func(denominator, registry) return map(extract2, numerator), map(extract2, denominator) if length is None: length = "short" if "~" in spec else "long" mapper = partial( localize_display_exponent_name, use_plural=False, length=length, locale=locale ) numerator = map(mapper, numerator) denominator = map(mapper, denominator) if sort_func is not None: numerator = sort_func(numerator, registry) denominator = sort_func(denominator, registry) if use_plural: if not isinstance(numerator, list): numerator = list(numerator) numerator[-1] = localize_display_exponent_name( numerator[-1], use_plural, length=length, locale=locale, default=numerator[-1][0], ) return map(extract2, numerator), map(extract2, denominator) pint-0.24.4/pint/delegates/formatter/_format_helpers.py000066400000000000000000000151421471316474000232220ustar00rootroot00000000000000""" pint.delegates.formatter._format_helpers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Convenient functions to help string formatting operations. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import re from collections.abc import Callable, Generator, Iterable from contextlib import contextmanager from functools import partial from locale import LC_NUMERIC, getlocale, setlocale from typing import ( TYPE_CHECKING, Any, TypeVar, ) from ...compat import ndarray from ._spec_helpers import FORMATTER try: from numpy import integer as np_integer except ImportError: np_integer = None if TYPE_CHECKING: from ...compat import Locale, Number T = TypeVar("T") U = TypeVar("U") V = TypeVar("V") W = TypeVar("W") _PRETTY_EXPONENTS = "⁰¹²³⁴⁵⁶⁷⁸⁹" _JOIN_REG_EXP = re.compile(r"{\d*}") def format_number(value: Any, spec: str = "") -> str: """Format number This function might disapear in the future. Right now is aiding backwards compatible migration. """ if isinstance(value, float): return format(value, spec or ".16n") elif isinstance(value, int): return format(value, spec or "n") elif isinstance(value, ndarray) and value.ndim == 0: if issubclass(value.dtype.type, np_integer): return format(value, spec or "n") else: return format(value, spec or ".16n") else: return str(value) def builtin_format(value: Any, spec: str = "") -> str: """A keyword enabled replacement for builtin format format has positional only arguments and this cannot be partialized and np requires a callable. """ return format(value, spec) @contextmanager def override_locale( spec: str, locale: str | Locale | None ) -> Generator[Callable[[Any], str], Any, None]: """Given a spec a locale, yields a function to format a number. IMPORTANT: When the locale is not None, this function uses setlocale and therefore is not thread safe. """ if locale is None: # If locale is None, just return the builtin format function. yield ("{:" + spec + "}").format else: # If locale is not None, change it and return the backwards compatible # format_number. prev_locale_string = getlocale(LC_NUMERIC) if isinstance(locale, str): setlocale(LC_NUMERIC, locale) else: setlocale(LC_NUMERIC, str(locale)) yield partial(format_number, spec=spec) setlocale(LC_NUMERIC, prev_locale_string) def pretty_fmt_exponent(num: Number) -> str: """Format an number into a pretty printed exponent.""" # unicode dot operator (U+22C5) looks like a superscript decimal ret = f"{num:n}".replace("-", "⁻").replace(".", "\u22C5") for n in range(10): ret = ret.replace(str(n), _PRETTY_EXPONENTS[n]) return ret def join_u(fmt: str, iterable: Iterable[Any]) -> str: """Join an iterable with the format specified in fmt. The format can be specified in two ways: - PEP3101 format with two replacement fields (eg. '{} * {}') - The concatenating string (eg. ' * ') """ if not iterable: return "" if not _JOIN_REG_EXP.search(fmt): return fmt.join(iterable) miter = iter(iterable) first = next(miter) for val in miter: ret = fmt.format(first, val) first = ret return first def join_mu(joint_fstring: str, mstr: str, ustr: str) -> str: """Join magnitude and units. This avoids that `3 and `1 / m` becomes `3 1 / m` """ if ustr == "": return mstr if ustr.startswith("1 / "): return joint_fstring.format(mstr, ustr[2:]) return joint_fstring.format(mstr, ustr) def join_unc(joint_fstring: str, lpar: str, rpar: str, mstr: str, ustr: str) -> str: """Join uncertainty magnitude and units. Uncertainty magnitudes might require extra parenthesis when joined to units. - YES: 3 +/- 1 - NO : 3(1) - NO : (3 +/ 1)e-9 This avoids that `(3 + 1)` and `meter` becomes ((3 +/- 1) meter) """ if mstr.startswith(lpar) or mstr.endswith(rpar): return joint_fstring.format(mstr, ustr) return joint_fstring.format(lpar + mstr + rpar, ustr) def formatter( numerator: Iterable[tuple[str, Number]], denominator: Iterable[tuple[str, Number]], as_ratio: bool = True, single_denominator: bool = False, product_fmt: str = " * ", division_fmt: str = " / ", power_fmt: str = "{} ** {}", parentheses_fmt: str = "({0})", exp_call: FORMATTER = "{:n}".format, ) -> str: """Format a list of (name, exponent) pairs. Parameters ---------- items : list a list of (name, exponent) pairs. as_ratio : bool, optional True to display as ratio, False as negative powers. (Default value = True) single_denominator : bool, optional all with terms with negative exponents are collected together. (Default value = False) product_fmt : str the format used for multiplication. (Default value = " * ") division_fmt : str the format used for division. (Default value = " / ") power_fmt : str the format used for exponentiation. (Default value = "{} ** {}") parentheses_fmt : str the format used for parenthesis. (Default value = "({0})") exp_call : callable (Default value = lambda x: f"{x:n}") Returns ------- str the formula as a string. """ if as_ratio: fun = lambda x: exp_call(abs(x)) else: fun = exp_call pos_terms: list[str] = [] for key, value in numerator: if value == 1: pos_terms.append(key) else: pos_terms.append(power_fmt.format(key, fun(value))) neg_terms: list[str] = [] for key, value in denominator: if value == -1 and as_ratio: neg_terms.append(key) else: neg_terms.append(power_fmt.format(key, fun(value))) if not pos_terms and not neg_terms: return "" if not as_ratio: # Show as Product: positive * negative terms ** -1 return join_u(product_fmt, pos_terms + neg_terms) # Show as Ratio: positive terms / negative terms pos_ret = join_u(product_fmt, pos_terms) or "1" if not neg_terms: return pos_ret if single_denominator: neg_ret = join_u(product_fmt, neg_terms) if len(neg_terms) > 1: neg_ret = parentheses_fmt.format(neg_ret) else: neg_ret = join_u(division_fmt, neg_terms) return join_u(division_fmt, [pos_ret, neg_ret]) pint-0.24.4/pint/delegates/formatter/_spec_helpers.py000066400000000000000000000077221471316474000226710ustar00rootroot00000000000000""" pint.delegates.formatter._spec_helpers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Convenient functions to deal with format specifications. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import functools import re import warnings from collections.abc import Callable from typing import Any FORMATTER = Callable[ [ Any, ], str, ] # Extract just the type from the specification mini-language: see # http://docs.python.org/2/library/string.html#format-specification-mini-language # We also add uS for uncertainties. _BASIC_TYPES = frozenset("bcdeEfFgGnosxX%uS") REGISTERED_FORMATTERS: dict[str, Any] = {} def parse_spec(spec: str) -> str: """Parse and return spec. If an unknown item is found, raise a ValueError. This function still needs work: - what happens if two distinct values are found? """ result = "" for ch in reversed(spec): if ch == "~" or ch in _BASIC_TYPES: continue elif ch in list(REGISTERED_FORMATTERS.keys()) + ["~"]: if result: raise ValueError("expected ':' after format specifier") else: result = ch elif ch.isalpha(): raise ValueError("Unknown conversion specified " + ch) else: break return result def extract_custom_flags(spec: str) -> str: """Return custom flags present in a format specification (i.e those not part of Python's formatting mini language) """ if not spec: return "" # sort by length, with longer items first known_flags = sorted(REGISTERED_FORMATTERS.keys(), key=len, reverse=True) flag_re = re.compile("(" + "|".join(known_flags + ["~"]) + ")") custom_flags = flag_re.findall(spec) return "".join(custom_flags) def remove_custom_flags(spec: str) -> str: """Remove custom flags present in a format specification (i.e those not part of Python's formatting mini language) """ for flag in sorted(REGISTERED_FORMATTERS.keys(), key=len, reverse=True) + ["~"]: if flag: spec = spec.replace(flag, "") return spec @functools.lru_cache def split_format( spec: str, default: str, separate_format_defaults: bool = True ) -> tuple[str, str]: """Split format specification into magnitude and unit format.""" mspec = remove_custom_flags(spec) uspec = extract_custom_flags(spec) default_mspec = remove_custom_flags(default) default_uspec = extract_custom_flags(default) if separate_format_defaults in (False, None): # should we warn always or only if there was no explicit choice? # Given that we want to eventually remove the flag again, I'd say yes? if spec and separate_format_defaults is None: if not uspec and default_uspec: warnings.warn( ( "The given format spec does not contain a unit formatter." " Falling back to the builtin defaults, but in the future" " the unit formatter specified in the `default_format`" " attribute will be used instead." ), DeprecationWarning, ) if not mspec and default_mspec: warnings.warn( ( "The given format spec does not contain a magnitude formatter." " Falling back to the builtin defaults, but in the future" " the magnitude formatter specified in the `default_format`" " attribute will be used instead." ), DeprecationWarning, ) elif not spec: mspec, uspec = default_mspec, default_uspec else: mspec = mspec or default_mspec uspec = uspec or default_uspec return mspec, uspec pint-0.24.4/pint/delegates/formatter/_to_register.py000066400000000000000000000105311471316474000225330ustar00rootroot00000000000000""" pint.delegates.formatter.base_formatter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Common class and function for all formatters. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from collections.abc import Callable from typing import TYPE_CHECKING, Any, Iterable from ..._typing import Magnitude from ...compat import Unpack, ndarray, np from ...util import UnitsContainer from ._compound_unit_helpers import BabelKwds, prepare_compount_unit from ._format_helpers import join_mu, override_locale from ._spec_helpers import REGISTERED_FORMATTERS, split_format from .plain import BaseFormatter if TYPE_CHECKING: from ...facets.plain import MagnitudeT, PlainQuantity, PlainUnit from ...registry import UnitRegistry def register_unit_format(name: str): """register a function as a new format for units The registered function must have a signature of: .. code:: python def new_format(unit, registry, **options): pass Parameters ---------- name : str The name of the new format (to be used in the format mini-language). A error is raised if the new format would overwrite a existing format. Examples -------- .. code:: python @pint.register_unit_format("custom") def format_custom(unit, registry, **options): result = "" # do the formatting return result ureg = pint.UnitRegistry() u = ureg.m / ureg.s ** 2 f"{u:custom}" """ # TODO: kwargs missing in typing def wrapper(func: Callable[[PlainUnit, UnitRegistry], str]): if name in REGISTERED_FORMATTERS: raise ValueError(f"format {name!r} already exists") # or warn instead class NewFormatter(BaseFormatter): spec = name def format_magnitude( self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds], ) -> str: with override_locale( mspec, babel_kwds.get("locale", None) ) as format_number: if isinstance(magnitude, ndarray) and magnitude.ndim > 0: # Use custom ndarray text formatting--need to handle scalars differently # since they don't respond to printoptions with np.printoptions(formatter={"float_kind": format_number}): mstr = format(magnitude).replace("\n", "") else: mstr = format_number(magnitude) return mstr def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", **babel_kwds: Unpack[BabelKwds], ) -> str: numerator, _denominator = prepare_compount_unit( unit, uspec, **babel_kwds, as_ratio=False, registry=self._registry, ) if self._registry is None: units = UnitsContainer(numerator) else: units = self._registry.UnitsContainer(numerator) return func(units, registry=self._registry) def format_quantity( self, quantity: PlainQuantity[MagnitudeT], qspec: str = "", **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry if registry is None: mspec, uspec = split_format(qspec, "", True) else: mspec, uspec = split_format( qspec, registry.formatter.default_format, registry.separate_format_defaults, ) joint_fstring = "{} {}" return join_mu( joint_fstring, self.format_magnitude(quantity.magnitude, mspec, **babel_kwds), self.format_unit(quantity.unit_items(), uspec, **babel_kwds), ) REGISTERED_FORMATTERS[name] = NewFormatter() return wrapper pint-0.24.4/pint/delegates/formatter/full.py000066400000000000000000000176741471316474000210270ustar00rootroot00000000000000""" pint.delegates.formatter.full ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implements: - Full: dispatch to other formats, accept defaults. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import locale from typing import TYPE_CHECKING, Any, Iterable, Literal from ..._typing import Magnitude from ...compat import Unpack, babel_parse from ...util import iterable from ._compound_unit_helpers import BabelKwds, SortFunc, sort_by_unit_name from ._to_register import REGISTERED_FORMATTERS from .html import HTMLFormatter from .latex import LatexFormatter, SIunitxFormatter from .plain import ( BaseFormatter, CompactFormatter, DefaultFormatter, PrettyFormatter, RawFormatter, ) if TYPE_CHECKING: from ...compat import Locale from ...facets.measurement import Measurement from ...facets.plain import ( MagnitudeT, PlainQuantity, PlainUnit, ) from ...registry import UnitRegistry class FullFormatter(BaseFormatter): """A formatter that dispatch to other formatters. Has a default format, locale and babel_length """ _formatters: dict[str, Any] = {} default_format: str = "" # TODO: This can be over-riden by the registry definitions file dim_order: tuple[str, ...] = ( "[substance]", "[mass]", "[current]", "[luminosity]", "[length]", "[]", "[time]", "[temperature]", ) default_sort_func: SortFunc | None = staticmethod(sort_by_unit_name) locale: Locale | None = None def __init__(self, registry: UnitRegistry | None = None): super().__init__(registry) self._formatters = {} self._formatters["raw"] = RawFormatter(registry) self._formatters["D"] = DefaultFormatter(registry) self._formatters["H"] = HTMLFormatter(registry) self._formatters["P"] = PrettyFormatter(registry) self._formatters["Lx"] = SIunitxFormatter(registry) self._formatters["L"] = LatexFormatter(registry) self._formatters["C"] = CompactFormatter(registry) def set_locale(self, loc: str | None) -> None: """Change the locale used by default by `format_babel`. Parameters ---------- loc : str or None None (do not translate), 'sys' (detect the system locale) or a locale id string. """ if isinstance(loc, str): if loc == "sys": loc = locale.getdefaultlocale()[0] # We call babel parse to fail here and not in the formatting operation babel_parse(loc) self.locale = loc def get_formatter(self, spec: str): if spec == "": return self._formatters["D"] for k, v in self._formatters.items(): if k in spec: return v for k, v in REGISTERED_FORMATTERS.items(): if k in spec: orphan_fmt = REGISTERED_FORMATTERS[k] break else: return self._formatters["D"] try: fmt = orphan_fmt.__class__(self._registry) spec = getattr(fmt, "spec", spec) self._formatters[spec] = fmt return fmt except Exception: return orphan_fmt def format_magnitude( self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds] ) -> str: mspec = mspec or self.default_format return self.get_formatter(mspec).format_magnitude( magnitude, mspec, **babel_kwds ) def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: uspec = uspec or self.default_format sort_func = sort_func or self.default_sort_func return self.get_formatter(uspec).format_unit( unit, uspec, sort_func=sort_func, **babel_kwds ) def format_quantity( self, quantity: PlainQuantity[MagnitudeT], spec: str = "", **babel_kwds: Unpack[BabelKwds], ) -> str: spec = spec or self.default_format # If Compact is selected, do it at the beginning if "#" in spec: spec = spec.replace("#", "") obj = quantity.to_compact() else: obj = quantity del quantity locale = babel_kwds.get("locale", self.locale) if locale: if "use_plural" in babel_kwds: use_plural = babel_kwds["use_plural"] else: use_plural = obj.magnitude > 1 if iterable(use_plural): use_plural = True else: use_plural = False return self.get_formatter(spec).format_quantity( obj, spec, sort_func=self.default_sort_func, use_plural=use_plural, length=babel_kwds.get("length", None), locale=locale, ) def format_measurement( self, measurement: Measurement, meas_spec: str = "", **babel_kwds: Unpack[BabelKwds], ) -> str: meas_spec = meas_spec or self.default_format # If Compact is selected, do it at the beginning if "#" in meas_spec: meas_spec = meas_spec.replace("#", "") obj = measurement.to_compact() else: obj = measurement del measurement use_plural = obj.magnitude.nominal_value > 1 if iterable(use_plural): use_plural = True return self.get_formatter(meas_spec).format_measurement( obj, meas_spec, sort_func=self.default_sort_func, use_plural=babel_kwds.get("use_plural", use_plural), length=babel_kwds.get("length", None), locale=babel_kwds.get("locale", self.locale), ) ####################################### # This is for backwards compatibility ####################################### def format_unit_babel( self, unit: PlainUnit | Iterable[tuple[str, Any]], spec: str = "", length: Literal["short", "long", "narrow"] | None = None, locale: Locale | None = None, ) -> str: if self.locale is None and locale is None: raise ValueError( "format_babel requires a locale argumente if the Formatter locale is not set." ) return self.format_unit( unit, spec or self.default_format, sort_func=self.default_sort_func, use_plural=False, length=length, locale=locale or self.locale, ) def format_quantity_babel( self, quantity: PlainQuantity[MagnitudeT], spec: str = "", length: Literal["short", "long", "narrow"] | None = None, locale: Locale | None = None, ) -> str: if self.locale is None and locale is None: raise ValueError( "format_babel requires a locale argumente if the Formatter locale is not set." ) use_plural = quantity.magnitude > 1 if iterable(use_plural): use_plural = True return self.format_quantity( quantity, spec or self.default_format, sort_func=self.default_sort_func, use_plural=use_plural, length=length, locale=locale or self.locale, ) ################################################################ # This allows to format units independently of the registry # REGISTERED_FORMATTERS["raw"] = RawFormatter() REGISTERED_FORMATTERS["D"] = DefaultFormatter() REGISTERED_FORMATTERS["H"] = HTMLFormatter() REGISTERED_FORMATTERS["P"] = PrettyFormatter() REGISTERED_FORMATTERS["Lx"] = SIunitxFormatter() REGISTERED_FORMATTERS["L"] = LatexFormatter() REGISTERED_FORMATTERS["C"] = CompactFormatter() pint-0.24.4/pint/delegates/formatter/html.py000066400000000000000000000135341471316474000210200ustar00rootroot00000000000000""" pint.delegates.formatter.html ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implements: - HTML: suitable for web/jupyter notebook outputs. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import re from typing import TYPE_CHECKING, Any, Iterable from ..._typing import Magnitude from ...compat import Unpack, ndarray, np from ...util import iterable from ._compound_unit_helpers import ( BabelKwds, SortFunc, localize_per, prepare_compount_unit, ) from ._format_helpers import ( formatter, join_mu, join_unc, override_locale, ) from ._spec_helpers import ( remove_custom_flags, split_format, ) from .plain import BaseFormatter if TYPE_CHECKING: from ...facets.measurement import Measurement from ...facets.plain import MagnitudeT, PlainQuantity, PlainUnit _EXP_PATTERN = re.compile(r"([0-9]\.?[0-9]*)e(-?)\+?0*([0-9]+)") class HTMLFormatter(BaseFormatter): """HTML localizable text formatter.""" def format_magnitude( self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds] ) -> str: with override_locale(mspec, babel_kwds.get("locale", None)) as format_number: if hasattr(magnitude, "_repr_html_"): # If magnitude has an HTML repr, nest it within Pint's mstr = magnitude._repr_html_() # type: ignore assert isinstance(mstr, str) else: if isinstance(magnitude, ndarray): # Need to override for scalars, which are detected as iterable, # and don't respond to printoptions. if magnitude.ndim == 0: mstr = format_number(magnitude) else: with np.printoptions(formatter={"float_kind": format_number}): mstr = ( "
" + format(magnitude).replace("\n", "") + "
" ) elif not iterable(magnitude): # Use plain text for scalars mstr = format_number(magnitude) else: # Use monospace font for other array-likes mstr = ( "
"
                        + format_number(magnitude).replace("\n", "
") + "
" ) m = _EXP_PATTERN.match(mstr) _exp_formatter = lambda s: f"{s}" if m: exp = int(m.group(2) + m.group(3)) mstr = _EXP_PATTERN.sub(r"\1×10" + _exp_formatter(exp), mstr) return mstr def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: numerator, denominator = prepare_compount_unit( unit, uspec, sort_func=sort_func, **babel_kwds, registry=self._registry, ) if babel_kwds.get("locale", None): length = babel_kwds.get("length") or ("short" if "~" in uspec else "long") division_fmt = localize_per(length, babel_kwds.get("locale"), "{}/{}") else: division_fmt = "{}/{}" return formatter( numerator, denominator, as_ratio=True, single_denominator=True, product_fmt=r" ", division_fmt=division_fmt, power_fmt=r"{}{}", parentheses_fmt=r"({})", ) def format_quantity( self, quantity: PlainQuantity[MagnitudeT], qspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( qspec, registry.formatter.default_format, registry.separate_format_defaults ) if iterable(quantity.magnitude): # Use HTML table instead of plain text template for array-likes joint_fstring = ( "" "" "" "" "
Magnitude{}
Units{}
" ) else: joint_fstring = "{} {}" return join_mu( joint_fstring, self.format_magnitude(quantity.magnitude, mspec, **babel_kwds), self.format_unit(quantity.unit_items(), uspec, sort_func, **babel_kwds), ) def format_uncertainty( self, uncertainty, unc_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: unc_str = format(uncertainty, unc_spec).replace("+/-", " ± ") unc_str = re.sub(r"\)e\+0?(\d+)", r")×10\1", unc_str) unc_str = re.sub(r"\)e-0?(\d+)", r")×10-\1", unc_str) return unc_str def format_measurement( self, measurement: Measurement, meas_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( meas_spec, registry.formatter.default_format, registry.separate_format_defaults, ) unc_spec = remove_custom_flags(meas_spec) joint_fstring = "{} {}" return join_unc( joint_fstring, "(", ")", self.format_uncertainty(measurement.magnitude, unc_spec, **babel_kwds), self.format_unit(measurement.units, uspec, sort_func, **babel_kwds), ) pint-0.24.4/pint/delegates/formatter/latex.py000066400000000000000000000307061471316474000211710ustar00rootroot00000000000000""" pint.delegates.formatter.latex ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implements: - Latex: uses vainilla latex. - SIunitx: uses latex siunitx package format. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import functools import re from collections.abc import Iterable from typing import TYPE_CHECKING, Any from ..._typing import Magnitude from ...compat import Number, Unpack, ndarray from ._compound_unit_helpers import ( BabelKwds, SortFunc, prepare_compount_unit, ) from ._format_helpers import ( FORMATTER, formatter, join_mu, join_unc, override_locale, ) from ._spec_helpers import ( remove_custom_flags, split_format, ) from .plain import BaseFormatter if TYPE_CHECKING: from ...facets.measurement import Measurement from ...facets.plain import MagnitudeT, PlainQuantity, PlainUnit from ...registry import UnitRegistry from ...util import ItMatrix def vector_to_latex( vec: Iterable[Any], fmtfun: FORMATTER | str = "{:.2n}".format ) -> str: """Format a vector into a latex string.""" return matrix_to_latex([vec], fmtfun) def matrix_to_latex(matrix: ItMatrix, fmtfun: FORMATTER | str = "{:.2n}".format) -> str: """Format a matrix into a latex string.""" ret: list[str] = [] for row in matrix: ret += [" & ".join(fmtfun(f) for f in row)] return r"\begin{pmatrix}%s\end{pmatrix}" % "\\\\ \n".join(ret) def ndarray_to_latex_parts( ndarr: ndarray, fmtfun: FORMATTER = "{:.2n}".format, dim: tuple[int, ...] = tuple() ) -> list[str]: """Convert an numpy array into an iterable of elements to be print. e.g. - if the array is 2d, it will return an iterable of rows. - if the array is 3d, it will return an iterable of matrices. """ if isinstance(fmtfun, str): fmtfun = fmtfun.format if ndarr.ndim == 0: _ndarr = ndarr.reshape(1) return [vector_to_latex(_ndarr, fmtfun)] if ndarr.ndim == 1: return [vector_to_latex(ndarr, fmtfun)] if ndarr.ndim == 2: return [matrix_to_latex(ndarr, fmtfun)] else: ret = [] if ndarr.ndim == 3: header = ("arr[%s," % ",".join("%d" % d for d in dim)) + "%d,:,:]" for elno, el in enumerate(ndarr): ret += [header % elno + " = " + matrix_to_latex(el, fmtfun)] else: for elno, el in enumerate(ndarr): ret += ndarray_to_latex_parts(el, fmtfun, dim + (elno,)) return ret def ndarray_to_latex( ndarr: ndarray, fmtfun: FORMATTER | str = "{:.2n}".format, dim: tuple[int, ...] = tuple(), ) -> str: """Format a numpy array into string.""" return "\n".join(ndarray_to_latex_parts(ndarr, fmtfun, dim)) def latex_escape(string: str) -> str: """Prepend characters that have a special meaning in LaTeX with a backslash.""" return functools.reduce( lambda s, m: re.sub(m[0], m[1], s), ( (r"[\\]", r"\\textbackslash "), (r"[~]", r"\\textasciitilde "), (r"[\^]", r"\\textasciicircum "), (r"([&%$#_{}])", r"\\\1"), ), str(string), ) def siunitx_format_unit( units: Iterable[tuple[str, Number]], registry: UnitRegistry ) -> str: """Returns LaTeX code for the unit that can be put into an siunitx command.""" def _tothe(power) -> str: if power == int(power): if power == 1: return "" elif power == 2: return r"\squared" elif power == 3: return r"\cubed" else: return rf"\tothe{{{int(power):d}}}" else: # limit float powers to 3 decimal places return rf"\tothe{{{power:.3f}}}".rstrip("0") lpos = [] lneg = [] # loop through all units in the container for unit, power in sorted(units): # remove unit prefix if it exists # siunitx supports \prefix commands lpick = lpos if power >= 0 else lneg prefix = None # TODO: fix this to be fore efficient and detect also aliases. for p in registry._prefixes.values(): p = str(p.name) if len(p) > 0 and unit.find(p) == 0: prefix = p unit = unit.replace(prefix, "", 1) if power < 0: lpick.append(r"\per") if prefix is not None: lpick.append(rf"\{prefix}") lpick.append(rf"\{unit}") lpick.append(rf"{_tothe(abs(power))}") return "".join(lpos) + "".join(lneg) _EXP_PATTERN = re.compile(r"([0-9]\.?[0-9]*)e(-?)\+?0*([0-9]+)") class LatexFormatter(BaseFormatter): """Latex localizable text formatter.""" def format_magnitude( self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds] ) -> str: with override_locale(mspec, babel_kwds.get("locale", None)) as format_number: if isinstance(magnitude, ndarray): mstr = ndarray_to_latex(magnitude, mspec) else: mstr = format_number(magnitude) mstr = _EXP_PATTERN.sub(r"\1\\times 10^{\2\3}", mstr) return mstr def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: numerator, denominator = prepare_compount_unit( unit, uspec, sort_func=sort_func, **babel_kwds, registry=self._registry, ) numerator = ((rf"\mathrm{{{latex_escape(u)}}}", p) for u, p in numerator) denominator = ((rf"\mathrm{{{latex_escape(u)}}}", p) for u, p in denominator) # Localized latex # if babel_kwds.get("locale", None): # length = babel_kwds.get("length") or ("short" if "~" in uspec else "long") # division_fmt = localize_per(length, babel_kwds.get("locale"), "{}/{}") # else: # division_fmt = "{}/{}" # division_fmt = r"\frac" + division_fmt.format("[{}]", "[{}]") formatted = formatter( numerator, denominator, as_ratio=True, single_denominator=True, product_fmt=r" \cdot ", division_fmt=r"\frac[{}][{}]", power_fmt="{}^[{}]", parentheses_fmt=r"\left({}\right)", ) return formatted.replace("[", "{").replace("]", "}") def format_quantity( self, quantity: PlainQuantity[MagnitudeT], qspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( qspec, registry.formatter.default_format, registry.separate_format_defaults ) joint_fstring = r"{}\ {}" return join_mu( joint_fstring, self.format_magnitude(quantity.magnitude, mspec, **babel_kwds), self.format_unit(quantity.unit_items(), uspec, sort_func, **babel_kwds), ) def format_uncertainty( self, uncertainty, unc_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: # uncertainties handles everythin related to latex. unc_str = format(uncertainty, unc_spec) if unc_str.startswith(r"\left"): return unc_str return unc_str.replace("(", r"\left(").replace(")", r"\right)") def format_measurement( self, measurement: Measurement, meas_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( meas_spec, registry.formatter.default_format, registry.separate_format_defaults, ) unc_spec = remove_custom_flags(meas_spec) # TODO: ugly. uncertainties recognizes L if "L" not in unc_spec: unc_spec += "L" joint_fstring = r"{}\ {}" return join_unc( joint_fstring, r"\left(", r"\right)", self.format_uncertainty(measurement.magnitude, unc_spec, **babel_kwds), self.format_unit(measurement.units, uspec, sort_func, **babel_kwds), ) class SIunitxFormatter(BaseFormatter): """Latex localizable text formatter with siunitx format. See: https://ctan.org/pkg/siunitx """ def format_magnitude( self, magnitude: Magnitude, mspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: with override_locale(mspec, babel_kwds.get("locale", None)) as format_number: if isinstance(magnitude, ndarray): mstr = ndarray_to_latex(magnitude, mspec) else: mstr = format_number(magnitude) # TODO: Why this is not needed in siunitx? # mstr = _EXP_PATTERN.sub(r"\1\\times 10^{\2\3}", mstr) return mstr def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry if registry is None: raise ValueError( "Can't format as siunitx without a registry." " This is usually triggered when formatting a instance" ' of the internal `UnitsContainer` with a spec of `"Lx"`' " and might indicate a bug in `pint`." ) # TODO: not sure if I should call format_compound_unit here. # siunitx_format_unit requires certain specific names? # should unit names be translated? # should unit names be shortened? # units = format_compound_unit(unit, uspec, **babel_kwds) try: units = unit._units.items() except Exception: units = unit formatted = siunitx_format_unit(units, registry) if "~" in uspec: formatted = formatted.replace(r"\percent", r"\%") # TODO: is this the right behaviour? Should we return the \si[] when only # the units are returned? return rf"\si[]{{{formatted}}}" def format_quantity( self, quantity: PlainQuantity[MagnitudeT], qspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( qspec, registry.formatter.default_format, registry.separate_format_defaults ) joint_fstring = "{}{}" mstr = self.format_magnitude(quantity.magnitude, mspec, **babel_kwds) ustr = self.format_unit(quantity.unit_items(), uspec, sort_func, **babel_kwds)[ len(r"\si[]") : ] return r"\SI[]" + join_mu(joint_fstring, "{%s}" % mstr, ustr) def format_uncertainty( self, uncertainty, unc_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: # SIunitx requires space between "+-" (or "\pm") and the nominal value # and uncertainty, and doesn't accept "+/-" # SIunitx doesn't accept parentheses, which uncs uses with # scientific notation ('e' or 'E' and sometimes 'g' or 'G'). return ( format(uncertainty, unc_spec) .replace("+/-", r" +- ") .replace("(", "") .replace(")", " ") ) def format_measurement( self, measurement: Measurement, meas_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( meas_spec, registry.formatter.default_format, registry.separate_format_defaults, ) unc_spec = remove_custom_flags(meas_spec) joint_fstring = "{}{}" return r"\SI" + join_unc( joint_fstring, r"", r"", "{%s}" % self.format_uncertainty(measurement.magnitude, unc_spec, **babel_kwds), self.format_unit(measurement.units, uspec, sort_func, **babel_kwds)[ len(r"\si[]") : ], ) pint-0.24.4/pint/delegates/formatter/plain.py000066400000000000000000000351261471316474000211600ustar00rootroot00000000000000""" pint.delegates.formatter.plain ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implements plain text formatters: - Raw: as simple as it gets (no locale aware, no unit formatter.) - Default: used when no string spec is given. - Compact: like default but with less spaces. - Pretty: pretty printed formatter. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import itertools import re from typing import TYPE_CHECKING, Any, Iterable from ..._typing import Magnitude from ...compat import Unpack, ndarray, np from ._compound_unit_helpers import ( BabelKwds, SortFunc, localize_per, prepare_compount_unit, ) from ._format_helpers import ( formatter, join_mu, join_unc, override_locale, pretty_fmt_exponent, ) from ._spec_helpers import ( remove_custom_flags, split_format, ) if TYPE_CHECKING: from ...facets.measurement import Measurement from ...facets.plain import MagnitudeT, PlainQuantity, PlainUnit from ...registry import UnitRegistry _EXP_PATTERN = re.compile(r"([0-9]\.?[0-9]*)e(-?)\+?0*([0-9]+)") class BaseFormatter: def __init__(self, registry: UnitRegistry | None = None): self._registry = registry class DefaultFormatter(BaseFormatter): """Simple, localizable plain text formatter. A formatter is a class with methods to format into string each of the objects that appear in pint (magnitude, unit, quantity, uncertainty, measurement) """ def format_magnitude( self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds] ) -> str: """Format scalar/array into string given a string formatting specification and locale related arguments. """ with override_locale(mspec, babel_kwds.get("locale", None)) as format_number: if isinstance(magnitude, ndarray) and magnitude.ndim > 0: # Use custom ndarray text formatting--need to handle scalars differently # since they don't respond to printoptions with np.printoptions(formatter={"float_kind": format_number}): mstr = format(magnitude).replace("\n", "") else: mstr = format_number(magnitude) return mstr def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: """Format a unit (can be compound) into string given a string formatting specification and locale related arguments. """ numerator, denominator = prepare_compount_unit( unit, uspec, sort_func=sort_func, **babel_kwds, registry=self._registry, ) if babel_kwds.get("locale", None): length = babel_kwds.get("length") or ("short" if "~" in uspec else "long") division_fmt = localize_per(length, babel_kwds.get("locale"), "{} / {}") else: division_fmt = "{} / {}" return formatter( numerator, denominator, as_ratio=True, single_denominator=False, product_fmt="{} * {}", division_fmt=division_fmt, power_fmt="{} ** {}", parentheses_fmt=r"({})", ) def format_quantity( self, quantity: PlainQuantity[MagnitudeT], qspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: """Format a quantity (magnitude and unit) into string given a string formatting specification and locale related arguments. """ registry = self._registry mspec, uspec = split_format( qspec, registry.formatter.default_format, registry.separate_format_defaults ) joint_fstring = "{} {}" return join_mu( joint_fstring, self.format_magnitude(quantity.magnitude, mspec, **babel_kwds), self.format_unit(quantity.unit_items(), uspec, sort_func, **babel_kwds), ) def format_uncertainty( self, uncertainty, unc_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: """Format an uncertainty magnitude (nominal value and stdev) into string given a string formatting specification and locale related arguments. """ return format(uncertainty, unc_spec).replace("+/-", " +/- ") def format_measurement( self, measurement: Measurement, meas_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: """Format an measurement (uncertainty and units) into string given a string formatting specification and locale related arguments. """ registry = self._registry mspec, uspec = split_format( meas_spec, registry.formatter.default_format, registry.separate_format_defaults, ) unc_spec = remove_custom_flags(meas_spec) joint_fstring = "{} {}" return join_unc( joint_fstring, "(", ")", self.format_uncertainty(measurement.magnitude, unc_spec, **babel_kwds), self.format_unit(measurement.units, uspec, sort_func, **babel_kwds), ) class CompactFormatter(BaseFormatter): """Simple, localizable plain text formatter without extra spaces.""" def format_magnitude( self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds] ) -> str: with override_locale(mspec, babel_kwds.get("locale", None)) as format_number: if isinstance(magnitude, ndarray) and magnitude.ndim > 0: # Use custom ndarray text formatting--need to handle scalars differently # since they don't respond to printoptions with np.printoptions(formatter={"float_kind": format_number}): mstr = format(magnitude).replace("\n", "") else: mstr = format_number(magnitude) return mstr def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: numerator, denominator = prepare_compount_unit( unit, uspec, sort_func=sort_func, **babel_kwds, registry=self._registry, ) # Division format in compact formatter is not localized. division_fmt = "{}/{}" return formatter( numerator, denominator, as_ratio=True, single_denominator=False, product_fmt="*", # TODO: Should this just be ''? division_fmt=division_fmt, power_fmt="{}**{}", parentheses_fmt=r"({})", ) def format_quantity( self, quantity: PlainQuantity[MagnitudeT], qspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( qspec, registry.formatter.default_format, registry.separate_format_defaults ) joint_fstring = "{} {}" return join_mu( joint_fstring, self.format_magnitude(quantity.magnitude, mspec, **babel_kwds), self.format_unit(quantity.unit_items(), uspec, sort_func, **babel_kwds), ) def format_uncertainty( self, uncertainty, unc_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: return format(uncertainty, unc_spec).replace("+/-", "+/-") def format_measurement( self, measurement: Measurement, meas_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( meas_spec, registry.formatter.default_format, registry.separate_format_defaults, ) unc_spec = remove_custom_flags(meas_spec) joint_fstring = "{} {}" return join_unc( joint_fstring, "(", ")", self.format_uncertainty(measurement.magnitude, unc_spec, **babel_kwds), self.format_unit(measurement.units, uspec, sort_func, **babel_kwds), ) class PrettyFormatter(BaseFormatter): """Pretty printed localizable plain text formatter without extra spaces.""" def format_magnitude( self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds] ) -> str: with override_locale(mspec, babel_kwds.get("locale", None)) as format_number: if isinstance(magnitude, ndarray) and magnitude.ndim > 0: # Use custom ndarray text formatting--need to handle scalars differently # since they don't respond to printoptions with np.printoptions(formatter={"float_kind": format_number}): mstr = format(magnitude).replace("\n", "") else: mstr = format_number(magnitude) m = _EXP_PATTERN.match(mstr) if m: exp = int(m.group(2) + m.group(3)) mstr = _EXP_PATTERN.sub(r"\1×10" + pretty_fmt_exponent(exp), mstr) return mstr def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: numerator, denominator = prepare_compount_unit( unit, uspec, sort_func=sort_func, **babel_kwds, registry=self._registry, ) if babel_kwds.get("locale", None): length = babel_kwds.get("length") or ("short" if "~" in uspec else "long") division_fmt = localize_per(length, babel_kwds.get("locale"), "{}/{}") else: division_fmt = "{}/{}" return formatter( numerator, denominator, as_ratio=True, single_denominator=False, product_fmt="·", division_fmt=division_fmt, power_fmt="{}{}", parentheses_fmt="({})", exp_call=pretty_fmt_exponent, ) def format_quantity( self, quantity: PlainQuantity[MagnitudeT], qspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( qspec, registry.formatter.default_format, registry.separate_format_defaults ) joint_fstring = "{} {}" return join_mu( joint_fstring, self.format_magnitude(quantity.magnitude, mspec, **babel_kwds), self.format_unit(quantity.unit_items(), uspec, sort_func, **babel_kwds), ) def format_uncertainty( self, uncertainty, unc_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: return format(uncertainty, unc_spec).replace("±", " ± ") def format_measurement( self, measurement: Measurement, meas_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( meas_spec, registry.formatter.default_format, registry.separate_format_defaults, ) unc_spec = meas_spec joint_fstring = "{} {}" return join_unc( joint_fstring, "(", ")", self.format_uncertainty(measurement.magnitude, unc_spec, **babel_kwds), self.format_unit(measurement.units, uspec, sort_func, **babel_kwds), ) class RawFormatter(BaseFormatter): """Very simple non-localizable plain text formatter. Ignores all pint custom string formatting specification. """ def format_magnitude( self, magnitude: Magnitude, mspec: str = "", **babel_kwds: Unpack[BabelKwds] ) -> str: return str(magnitude) def format_unit( self, unit: PlainUnit | Iterable[tuple[str, Any]], uspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: numerator, denominator = prepare_compount_unit( unit, uspec, sort_func=sort_func, **babel_kwds, registry=self._registry, ) return " * ".join( k if v == 1 else f"{k} ** {v}" for k, v in itertools.chain(numerator, denominator) ) def format_quantity( self, quantity: PlainQuantity[MagnitudeT], qspec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( qspec, registry.formatter.default_format, registry.separate_format_defaults ) joint_fstring = "{} {}" return join_mu( joint_fstring, self.format_magnitude(quantity.magnitude, mspec, **babel_kwds), self.format_unit(quantity.unit_items(), uspec, sort_func, **babel_kwds), ) def format_uncertainty( self, uncertainty, unc_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: return format(uncertainty, unc_spec) def format_measurement( self, measurement: Measurement, meas_spec: str = "", sort_func: SortFunc | None = None, **babel_kwds: Unpack[BabelKwds], ) -> str: registry = self._registry mspec, uspec = split_format( meas_spec, registry.formatter.default_format, registry.separate_format_defaults, ) unc_spec = remove_custom_flags(meas_spec) joint_fstring = "{} {}" return join_unc( joint_fstring, "(", ")", self.format_uncertainty(measurement.magnitude, unc_spec, **babel_kwds), self.format_unit(measurement.units, uspec, sort_func, **babel_kwds), ) pint-0.24.4/pint/delegates/txt_defparser/000077500000000000000000000000001471316474000203435ustar00rootroot00000000000000pint-0.24.4/pint/delegates/txt_defparser/__init__.py000066400000000000000000000005401471316474000224530ustar00rootroot00000000000000""" pint.delegates.txt_defparser ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Parser for the original textual Pint Definition file. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .defparser import DefParser __all__ = [ "DefParser", ] pint-0.24.4/pint/delegates/txt_defparser/block.py000066400000000000000000000024571471316474000220170ustar00rootroot00000000000000""" pint.delegates.txt_defparser.block ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Classes for Pint Blocks, which are defined by: @ @end :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from dataclasses import dataclass from typing import Generic, TypeVar import flexparser as fp from ..base_defparser import ParserConfig, PintParsedStatement @dataclass(frozen=True) class EndDirectiveBlock(PintParsedStatement): """An EndDirectiveBlock is simply an "@end" statement.""" @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[EndDirectiveBlock]: if s == "@end": return cls() return None OPST = TypeVar("OPST", bound="PintParsedStatement") IPST = TypeVar("IPST", bound="PintParsedStatement") DefT = TypeVar("DefT") @dataclass(frozen=True) class DirectiveBlock( Generic[DefT, OPST, IPST], fp.Block[OPST, IPST, EndDirectiveBlock, ParserConfig] ): """Directive blocks have beginning statement starting with a @ character. and ending with a "@end" (captured using a EndDirectiveBlock). Subclass this class for convenience. """ def derive_definition(self) -> DefT: ... pint-0.24.4/pint/delegates/txt_defparser/common.py000066400000000000000000000032011471316474000222010ustar00rootroot00000000000000""" pint.delegates.txt_defparser.common ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Definitions for parsing an Import Statement Also DefinitionSyntaxError :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from dataclasses import dataclass import flexparser as fp from ... import errors from ..base_defparser import ParserConfig class DefinitionSyntaxError(errors.DefinitionSyntaxError, fp.ParsingError): """A syntax error was found in a definition. Combines: DefinitionSyntaxError: which provides a message placeholder. fp.ParsingError: which provides raw text, and start and end column and row and an extra location attribute in which the filename or reseource is stored. """ msg: str def __init__(self, msg: str, location: str = ""): self.msg = msg self.location = location def __str__(self) -> str: msg = ( self.msg + "\n " + (self.format_position or "") + " " + (self.raw or "") ) if self.location: msg += "\n " + self.location return msg def set_location(self, value: str) -> None: super().__setattr__("location", value) @dataclass(frozen=True) class ImportDefinition(fp.IncludeStatement[ParserConfig]): value: str @property def target(self) -> str: return self.value @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[ImportDefinition]: if s.startswith("@import"): return ImportDefinition(s[len("@import") :].strip()) return None pint-0.24.4/pint/delegates/txt_defparser/context.py000066400000000000000000000141541471316474000224060ustar00rootroot00000000000000""" pint.delegates.txt_defparser.context ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Definitions for parsing Context and their related objects Notices that some of the checks are done within the format agnostic parent definition class. See each one for a slighly longer description of the syntax. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import numbers import re import typing as ty from dataclasses import dataclass from typing import Union import flexparser as fp from ...facets.context import definitions from ..base_defparser import ParserConfig, PintParsedStatement from . import block, common, plain # TODO check syntax T = ty.TypeVar("T", bound="Union[ForwardRelation, BidirectionalRelation]") def _from_string_and_context_sep( cls: type[T], s: str, config: ParserConfig, separator: str ) -> T | None: if separator not in s: return None if ":" not in s: return None rel, eq = s.split(":") parts = rel.split(separator) src, dst = (config.to_dimension_container(s) for s in parts) return cls(src, dst, eq.strip()) @dataclass(frozen=True) class ForwardRelation(PintParsedStatement, definitions.ForwardRelation): """A relation connecting a dimension to another via a transformation function. -> : """ @classmethod def from_string_and_config( cls, s: str, config: ParserConfig ) -> fp.NullableParsedResult[ForwardRelation]: return _from_string_and_context_sep(cls, s, config, "->") @dataclass(frozen=True) class BidirectionalRelation(PintParsedStatement, definitions.BidirectionalRelation): """A bidirectional relation connecting a dimension to another via a simple transformation function. <-> : """ @classmethod def from_string_and_config( cls, s: str, config: ParserConfig ) -> fp.NullableParsedResult[BidirectionalRelation]: return _from_string_and_context_sep(cls, s, config, "<->") @dataclass(frozen=True) class BeginContext(PintParsedStatement): """Being of a context directive. @context[(defaults)] [= ] [= ] """ _header_re = re.compile( r"@context\s*(?P\(.*\))?\s+(?P\w+)\s*(=(?P.*))*" ) name: str aliases: tuple[str, ...] defaults: dict[str, numbers.Number] @classmethod def from_string_and_config( cls, s: str, config: ParserConfig ) -> fp.NullableParsedResult[BeginContext]: try: r = cls._header_re.search(s) if r is None: return None name = r.groupdict()["name"].strip() aliases = r.groupdict()["aliases"] if aliases: aliases = tuple(a.strip() for a in r.groupdict()["aliases"].split("=")) else: aliases = () defaults = r.groupdict()["defaults"] except Exception as exc: return common.DefinitionSyntaxError( f"Could not parse the Context header '{s}': {exc}" ) if defaults: txt = defaults try: defaults = (part.split("=") for part in defaults.strip("()").split(",")) defaults = {str(k).strip(): config.to_number(v) for k, v in defaults} except (ValueError, TypeError) as exc: return common.DefinitionSyntaxError( f"Could not parse Context definition defaults '{txt}' {exc}" ) else: defaults = {} return cls(name, tuple(aliases), defaults) @dataclass(frozen=True) class ContextDefinition( block.DirectiveBlock[ definitions.ContextDefinition, BeginContext, ty.Union[ plain.CommentDefinition, BidirectionalRelation, ForwardRelation, plain.UnitDefinition, ], ] ): """Definition of a Context @context[(defaults)] [= ] [= ] # units can be redefined within the context = # can establish unidirectional relationships between dimensions -> : # can establish bidirectionl relationships between dimensions <-> : @end See BeginContext, Equality, ForwardRelation, BidirectionalRelation and Comment for more parsing related information. Example:: @context(n=1) spectroscopy = sp # n index of refraction of the medium. [length] <-> [frequency]: speed_of_light / n / value [frequency] -> [energy]: planck_constant * value [energy] -> [frequency]: value / planck_constant # allow wavenumber / kayser [wavenumber] <-> [length]: 1 / value @end """ def derive_definition(self) -> definitions.ContextDefinition: return definitions.ContextDefinition( self.name, self.aliases, self.defaults, self.relations, self.redefinitions ) @property def name(self) -> str: assert isinstance(self.opening, BeginContext) return self.opening.name @property def aliases(self) -> tuple[str, ...]: assert isinstance(self.opening, BeginContext) return self.opening.aliases @property def defaults(self) -> dict[str, numbers.Number]: assert isinstance(self.opening, BeginContext) return self.opening.defaults @property def relations(self) -> tuple[BidirectionalRelation | ForwardRelation, ...]: return tuple( r for r in self.body if isinstance(r, (ForwardRelation, BidirectionalRelation)) ) @property def redefinitions(self) -> tuple[plain.UnitDefinition, ...]: return tuple(r for r in self.body if isinstance(r, plain.UnitDefinition)) pint-0.24.4/pint/delegates/txt_defparser/defaults.py000066400000000000000000000041051471316474000225240ustar00rootroot00000000000000""" pint.delegates.txt_defparser.defaults ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Definitions for parsing Default sections. See each one for a slighly longer description of the syntax. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import typing as ty from dataclasses import dataclass, fields import flexparser as fp from ...facets.plain import definitions from ..base_defparser import PintParsedStatement from . import block, plain @dataclass(frozen=True) class BeginDefaults(PintParsedStatement): """Being of a defaults directive. @defaults """ @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[BeginDefaults]: if s.strip() == "@defaults": return cls() return None @dataclass(frozen=True) class DefaultsDefinition( block.DirectiveBlock[ definitions.DefaultsDefinition, BeginDefaults, ty.Union[ plain.CommentDefinition, plain.Equality, ], ] ): """Directive to store values. @defaults system = mks @end See Equality and Comment for more parsing related information. """ @property def _valid_fields(self) -> tuple[str, ...]: return tuple(f.name for f in fields(definitions.DefaultsDefinition)) def derive_definition(self) -> definitions.DefaultsDefinition: for definition in self.filter_by(plain.Equality): if definition.lhs not in self._valid_fields: raise ValueError( f"`{definition.lhs}` is not a valid key " f"for the default section. {self._valid_fields}" ) return definitions.DefaultsDefinition( *tuple(self.get_key(key) for key in self._valid_fields) ) def get_key(self, key: str) -> str: for stmt in self.body: if isinstance(stmt, plain.Equality) and stmt.lhs == key: return stmt.rhs raise KeyError(key) pint-0.24.4/pint/delegates/txt_defparser/defparser.py000066400000000000000000000110621471316474000226700ustar00rootroot00000000000000from __future__ import annotations import pathlib import typing as ty import flexcache as fc import flexparser as fp from ..base_defparser import ParserConfig from . import block, common, context, defaults, group, plain, system class PintRootBlock( fp.RootBlock[ ty.Union[ plain.CommentDefinition, common.ImportDefinition, context.ContextDefinition, defaults.DefaultsDefinition, system.SystemDefinition, group.GroupDefinition, plain.AliasDefinition, plain.DerivedDimensionDefinition, plain.DimensionDefinition, plain.PrefixDefinition, plain.UnitDefinition, ], ParserConfig, ] ): pass class _PintParser(fp.Parser[PintRootBlock, ParserConfig]): """Parser for the original Pint definition file, with cache.""" _delimiters = { "#": ( fp.DelimiterInclude.SPLIT_BEFORE, fp.DelimiterAction.CAPTURE_NEXT_TIL_EOL, ), **fp.SPLIT_EOL, } _root_block_class = PintRootBlock _strip_spaces = True _diskcache: fc.DiskCache | None def __init__(self, config: ParserConfig, *args: ty.Any, **kwargs: ty.Any): self._diskcache = kwargs.pop("diskcache", None) super().__init__(config, *args, **kwargs) def parse_file( self, path: pathlib.Path ) -> fp.ParsedSource[PintRootBlock, ParserConfig]: if self._diskcache is None: return super().parse_file(path) content, _basename = self._diskcache.load(path, super().parse_file) return content class DefParser: skip_classes: tuple[type, ...] = ( fp.BOF, fp.BOR, fp.BOS, fp.EOS, plain.CommentDefinition, ) def __init__(self, default_config: ParserConfig, diskcache: fc.DiskCache): self._default_config = default_config self._diskcache = diskcache def iter_parsed_project( self, parsed_project: fp.ParsedProject[PintRootBlock, ParserConfig] ) -> ty.Generator[fp.ParsedStatement[ParserConfig], None, None]: last_location = None for stmt in parsed_project.iter_blocks(): if isinstance(stmt, fp.BOS): if isinstance(stmt, fp.BOF): last_location = str(stmt.path) continue elif isinstance(stmt, fp.BOR): last_location = ( f"[package: {stmt.package}, resource: {stmt.resource_name}]" ) continue else: last_location = "orphan string" continue if isinstance(stmt, self.skip_classes): continue assert isinstance(last_location, str) if isinstance(stmt, common.DefinitionSyntaxError): stmt.set_location(last_location) raise stmt elif isinstance(stmt, block.DirectiveBlock): for exc in stmt.errors: exc = common.DefinitionSyntaxError(str(exc)) exc.set_position(*stmt.get_position()) exc.set_raw( (stmt.opening.raw or "") + " [...] " + (stmt.closing.raw or "") ) exc.set_location(last_location) raise exc try: yield stmt.derive_definition() except Exception as exc: exc = common.DefinitionSyntaxError(str(exc)) exc.set_position(*stmt.get_position()) exc.set_raw(stmt.opening.raw + " [...] " + stmt.closing.raw) exc.set_location(last_location) raise exc else: yield stmt def parse_file( self, filename: pathlib.Path | str, cfg: ParserConfig | None = None ) -> fp.ParsedProject[PintRootBlock, ParserConfig]: return fp.parse( filename, _PintParser, cfg or self._default_config, diskcache=self._diskcache, strip_spaces=True, delimiters=_PintParser._delimiters, ) def parse_string( self, content: str, cfg: ParserConfig | None = None ) -> fp.ParsedProject[PintRootBlock, ParserConfig]: return fp.parse_bytes( content.encode("utf-8"), _PintParser, cfg or self._default_config, diskcache=self._diskcache, strip_spaces=True, delimiters=_PintParser._delimiters, ) pint-0.24.4/pint/delegates/txt_defparser/group.py000066400000000000000000000056431471316474000220610ustar00rootroot00000000000000""" pint.delegates.txt_defparser.group ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Definitions for parsing Group and their related objects Notices that some of the checks are done within the format agnostic parent definition class. See each one for a slighly longer description of the syntax. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import re import typing as ty from dataclasses import dataclass import flexparser as fp from ...facets.group import definitions from ..base_defparser import PintParsedStatement from . import block, common, plain @dataclass(frozen=True) class BeginGroup(PintParsedStatement): """Being of a group directive. @group [using , ..., ] """ #: Regex to match the header parts of a definition. _header_re = re.compile(r"@group\s+(?P\w+)\s*(using\s(?P.*))*") name: str using_group_names: ty.Tuple[str, ...] @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[BeginGroup]: if not s.startswith("@group"): return None r = cls._header_re.search(s) if r is None: return common.DefinitionSyntaxError(f"Invalid Group header syntax: '{s}'") name = r.groupdict()["name"].strip() groups = r.groupdict()["used_groups"] if groups: parent_group_names = tuple(a.strip() for a in groups.split(",")) else: parent_group_names = () return cls(name, parent_group_names) @dataclass(frozen=True) class GroupDefinition( block.DirectiveBlock[ definitions.GroupDefinition, BeginGroup, ty.Union[ plain.CommentDefinition, plain.UnitDefinition, ], ] ): """Definition of a group. @group [using , ..., ] ... @end See UnitDefinition and Comment for more parsing related information. Example:: @group AvoirdupoisUS using Avoirdupois US_hundredweight = hundredweight = US_cwt US_ton = ton US_force_ton = force_ton = _ = US_ton_force @end """ def derive_definition(self) -> definitions.GroupDefinition: return definitions.GroupDefinition( self.name, self.using_group_names, self.definitions ) @property def name(self) -> str: assert isinstance(self.opening, BeginGroup) return self.opening.name @property def using_group_names(self) -> tuple[str, ...]: assert isinstance(self.opening, BeginGroup) return self.opening.using_group_names @property def definitions(self) -> tuple[plain.UnitDefinition, ...]: return tuple(el for el in self.body if isinstance(el, plain.UnitDefinition)) pint-0.24.4/pint/delegates/txt_defparser/plain.py000066400000000000000000000174441471316474000220320ustar00rootroot00000000000000""" pint.delegates.txt_defparser.plain ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Definitions for parsing: - Equality - CommentDefinition - PrefixDefinition - UnitDefinition - DimensionDefinition - DerivedDimensionDefinition - AliasDefinition Notices that some of the checks are done within the format agnostic parent definition class. See each one for a slighly longer description of the syntax. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from dataclasses import dataclass import flexparser as fp from ...converters import Converter from ...facets.plain import definitions from ...util import UnitsContainer from ..base_defparser import ParserConfig, PintParsedStatement from . import common @dataclass(frozen=True) class Equality(PintParsedStatement, definitions.Equality): """An equality statement contains a left and right hand separated lhs and rhs should be space stripped. """ @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[Equality]: if "=" not in s: return None parts = [p.strip() for p in s.split("=")] if len(parts) != 2: return common.DefinitionSyntaxError( f"Exactly two terms expected, not {len(parts)} (`{s}`)" ) return cls(*parts) @dataclass(frozen=True) class CommentDefinition(PintParsedStatement, definitions.CommentDefinition): """Comments start with a # character. # This is a comment. ## This is also a comment. Captured value does not include the leading # character and space stripped. """ @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[CommentDefinition]: if not s.startswith("#"): return None return cls(s[1:].strip()) @dataclass(frozen=True) class PrefixDefinition(PintParsedStatement, definitions.PrefixDefinition): """Definition of a prefix:: - = [= ] [= ] [ = ] [...] Example:: deca- = 1e+1 = da- = deka- """ @classmethod def from_string_and_config( cls, s: str, config: ParserConfig ) -> fp.NullableParsedResult[PrefixDefinition]: if "=" not in s: return None name, value, *aliases = s.split("=") name = name.strip() if not name.endswith("-"): return None name = name.rstrip("-") aliases = tuple(alias.strip().rstrip("-") for alias in aliases) defined_symbol = None if aliases: if aliases[0] == "_": aliases = aliases[1:] else: defined_symbol, *aliases = aliases aliases = tuple(alias for alias in aliases if alias not in ("", "_")) try: value = config.to_number(value) except definitions.NotNumeric as ex: return common.DefinitionSyntaxError( f"Prefix definition ('{name}') must contain only numbers, not {ex.value}" ) try: return cls(name, value, defined_symbol, aliases) except Exception as exc: return common.DefinitionSyntaxError(str(exc)) @dataclass(frozen=True) class UnitDefinition(PintParsedStatement, definitions.UnitDefinition): """Definition of a unit:: = [= ] [= ] [ = ] [...] Example:: millennium = 1e3 * year = _ = millennia Parameters ---------- reference : UnitsContainer Reference units. is_base : bool Indicates if it is a base unit. """ @classmethod def from_string_and_config( cls, s: str, config: ParserConfig ) -> fp.NullableParsedResult[UnitDefinition]: if "=" not in s: return None name, value, *aliases = (p.strip() for p in s.split("=")) defined_symbol = None if aliases: if aliases[0] == "_": aliases = aliases[1:] else: defined_symbol, *aliases = aliases aliases = tuple(alias for alias in aliases if alias not in ("", "_")) if ";" in value: [converter, modifiers] = value.split(";", 1) try: modifiers = { key.strip(): config.to_number(value) for key, value in (part.split(":") for part in modifiers.split(";")) } except definitions.NotNumeric as ex: return common.DefinitionSyntaxError( f"Unit definition ('{name}') must contain only numbers in modifier, not {ex.value}" ) else: converter = value modifiers = {} converter = config.to_scaled_units_container(converter) try: reference = UnitsContainer(converter) # reference = converter.to_units_container() except common.DefinitionSyntaxError as ex: return common.DefinitionSyntaxError(f"While defining {name}: {ex}") try: converter = Converter.from_arguments(scale=converter.scale, **modifiers) except Exception as ex: return common.DefinitionSyntaxError( f"Unable to assign a converter to the unit {ex}" ) try: return cls(name, defined_symbol, tuple(aliases), converter, reference) except Exception as ex: return common.DefinitionSyntaxError(str(ex)) @dataclass(frozen=True) class DimensionDefinition(PintParsedStatement, definitions.DimensionDefinition): """Definition of a root dimension:: [dimension name] Example:: [volume] """ @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[DimensionDefinition]: s = s.strip() if not (s.startswith("[") and "=" not in s): return None return cls(s) @dataclass(frozen=True) class DerivedDimensionDefinition( PintParsedStatement, definitions.DerivedDimensionDefinition ): """Definition of a derived dimension:: [dimension name] = Example:: [density] = [mass] / [volume] """ @classmethod def from_string_and_config( cls, s: str, config: ParserConfig ) -> fp.NullableParsedResult[DerivedDimensionDefinition]: if not (s.startswith("[") and "=" in s): return None name, value, *aliases = s.split("=") if aliases: return common.DefinitionSyntaxError( "Derived dimensions cannot have aliases." ) try: reference = config.to_dimension_container(value) except common.DefinitionSyntaxError as exc: return common.DefinitionSyntaxError( f"In {name} derived dimensions must only be referenced " f"to dimensions. {exc}" ) try: return cls(name.strip(), reference) except Exception as exc: return common.DefinitionSyntaxError(str(exc)) @dataclass(frozen=True) class AliasDefinition(PintParsedStatement, definitions.AliasDefinition): """Additional alias(es) for an already existing unit:: @alias = [ = ] [...] Example:: @alias meter = my_meter """ @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[AliasDefinition]: if not s.startswith("@alias "): return None name, *aliases = s[len("@alias ") :].split("=") try: return cls(name.strip(), tuple(alias.strip() for alias in aliases)) except Exception as exc: return common.DefinitionSyntaxError(str(exc)) pint-0.24.4/pint/delegates/txt_defparser/system.py000066400000000000000000000064121471316474000222440ustar00rootroot00000000000000""" pint.delegates.txt_defparser.system ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import re import typing as ty from dataclasses import dataclass import flexparser as fp from ...facets.system import definitions from ..base_defparser import PintParsedStatement from . import block, common, plain @dataclass(frozen=True) class BaseUnitRule(PintParsedStatement, definitions.BaseUnitRule): @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[BaseUnitRule]: if ":" not in s: return cls(s.strip()) parts = [p.strip() for p in s.split(":")] if len(parts) != 2: return common.DefinitionSyntaxError( f"Exactly two terms expected for rule, not {len(parts)} (`{s}`)" ) return cls(*parts) @dataclass(frozen=True) class BeginSystem(PintParsedStatement): """Being of a system directive. @system [using , ..., ] """ #: Regex to match the header parts of a context. _header_re = re.compile(r"@system\s+(?P\w+)\s*(using\s(?P.*))*") name: str using_group_names: ty.Tuple[str, ...] @classmethod def from_string(cls, s: str) -> fp.NullableParsedResult[BeginSystem]: if not s.startswith("@system"): return None r = cls._header_re.search(s) if r is None: raise ValueError("Invalid System header syntax '%s'" % s) name = r.groupdict()["name"].strip() groups = r.groupdict()["used_groups"] # If the systems has no group, it automatically uses the root group. if groups: group_names = tuple(a.strip() for a in groups.split(",")) else: group_names = ("root",) return cls(name, group_names) @dataclass(frozen=True) class SystemDefinition( block.DirectiveBlock[ definitions.SystemDefinition, BeginSystem, ty.Union[plain.CommentDefinition, BaseUnitRule], ] ): """Definition of a System: @system [using , ..., ] ... @end See Rule and Comment for more parsing related information. The syntax for the rule is: new_unit_name : old_unit_name where: - old_unit_name: a root unit part which is going to be removed from the system. - new_unit_name: a non root unit which is going to replace the old_unit. If the new_unit_name and the old_unit_name, the later and the colon can be omitted. """ def derive_definition(self) -> definitions.SystemDefinition: return definitions.SystemDefinition( self.name, self.using_group_names, self.rules ) @property def name(self) -> str: assert isinstance(self.opening, BeginSystem) return self.opening.name @property def using_group_names(self) -> tuple[str, ...]: assert isinstance(self.opening, BeginSystem) return self.opening.using_group_names @property def rules(self) -> tuple[BaseUnitRule, ...]: return tuple(el for el in self.body if isinstance(el, BaseUnitRule)) pint-0.24.4/pint/errors.py000066400000000000000000000172121471316474000154250ustar00rootroot00000000000000""" pint.errors ~~~~~~~~~~~ Functions and classes related to unit definitions and conversions. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import typing as ty OFFSET_ERROR_DOCS_HTML = "https://pint.readthedocs.io/en/stable/user/nonmult.html" LOG_ERROR_DOCS_HTML = "https://pint.readthedocs.io/en/stable/user/log_units.html" MSG_INVALID_UNIT_NAME = "is not a valid unit name (must follow Python identifier rules)" MSG_INVALID_UNIT_SYMBOL = "is not a valid unit symbol (must not contain spaces)" MSG_INVALID_UNIT_ALIAS = "is not a valid unit alias (must not contain spaces)" MSG_INVALID_PREFIX_NAME = ( "is not a valid prefix name (must follow Python identifier rules)" ) MSG_INVALID_PREFIX_SYMBOL = "is not a valid prefix symbol (must not contain spaces)" MSG_INVALID_PREFIX_ALIAS = "is not a valid prefix alias (must not contain spaces)" MSG_INVALID_DIMENSION_NAME = "is not a valid dimension name (must follow Python identifier rules and enclosed by square brackets)" MSG_INVALID_CONTEXT_NAME = ( "is not a valid context name (must follow Python identifier rules)" ) MSG_INVALID_GROUP_NAME = "is not a valid group name (must not contain spaces)" MSG_INVALID_SYSTEM_NAME = ( "is not a valid system name (must follow Python identifier rules)" ) def is_dim(name: str) -> bool: """Return True if the name is flanked by square brackets `[` and `]`.""" return name[0] == "[" and name[-1] == "]" def is_valid_prefix_name(name: str) -> bool: """Return True if the name is a valid python identifier or empty.""" return str.isidentifier(name) or name == "" is_valid_unit_name = is_valid_system_name = is_valid_context_name = str.isidentifier def _no_space(name: str) -> bool: """Return False if the name contains a space in any position.""" return name.strip() == name and " " not in name is_valid_group_name = _no_space is_valid_unit_alias = ( is_valid_prefix_alias ) = is_valid_unit_symbol = is_valid_prefix_symbol = _no_space def is_valid_dimension_name(name: str) -> bool: """Return True if the name is consistent with a dimension name. - flanked by square brackets. - empty dimension name or identifier. """ # TODO: shall we check also fro spaces? return name == "[]" or ( len(name) > 1 and is_dim(name) and str.isidentifier(name[1:-1]) ) class WithDefErr: """Mixing class to make some classes more readable.""" def def_err(self, msg: str): return DefinitionError(self.name, self.__class__, msg) class PintError(Exception): """Base exception for all Pint errors.""" class DefinitionError(ValueError, PintError): """Raised when a definition is not properly constructed.""" name: str definition_type: type msg: str def __init__(self, name: str, definition_type: type, msg: str): self.name = name self.definition_type = definition_type self.msg = msg def __str__(self): msg = f"Cannot define '{self.name}' ({self.definition_type}): {self.msg}" return msg def __reduce__(self): return self.__class__, (self.name, self.definition_type, self.msg) class DefinitionSyntaxError(ValueError, PintError): """Raised when a textual definition has a syntax error.""" msg: str def __init__(self, msg: str): self.msg = msg def __str__(self): return self.msg def __reduce__(self): return self.__class__, (self.msg,) class RedefinitionError(ValueError, PintError): """Raised when a unit or prefix is redefined.""" name: str definition_type: type def __init__(self, name: str, definition_type: type): self.name = name self.definition_type = definition_type def __str__(self): msg = f"Cannot redefine '{self.name}' ({self.definition_type})" return msg def __reduce__(self): return self.__class__, (self.name, self.definition_type) class UndefinedUnitError(AttributeError, PintError): """Raised when the units are not defined in the unit registry.""" unit_names: tuple[str, ...] def __init__(self, unit_names: str | ty.Iterable[str]): if isinstance(unit_names, str): self.unit_names = (unit_names,) else: self.unit_names = tuple(unit_names) def __str__(self): if len(self.unit_names) == 1: return f"'{tuple(self.unit_names)[0]}' is not defined in the unit registry" return f"{tuple(self.unit_names)} are not defined in the unit registry" def __reduce__(self): return self.__class__, (self.unit_names,) class PintTypeError(TypeError, PintError): pass class DimensionalityError(PintTypeError): """Raised when trying to convert between incompatible units.""" units1: ty.Any units2: ty.Any dim1: str = "" dim2: str = "" extra_msg: str = "" def __init__( self, units1: ty.Any, units2: ty.Any, dim1: str = "", dim2: str = "", extra_msg: str = "", ) -> None: self.units1 = units1 self.units2 = units2 self.dim1 = dim1 self.dim2 = dim2 self.extra_msg = extra_msg def __str__(self): if self.dim1 or self.dim2: dim1 = f" ({self.dim1})" dim2 = f" ({self.dim2})" else: dim1 = "" dim2 = "" return ( f"Cannot convert from '{self.units1}'{dim1} to " f"'{self.units2}'{dim2}{self.extra_msg}" ) def __reduce__(self): return self.__class__, ( self.units1, self.units2, self.dim1, self.dim2, self.extra_msg, ) class OffsetUnitCalculusError(PintTypeError): """Raised on ambiguous operations with offset units.""" units1: ty.Any units2: ty.Optional[ty.Any] = None def __init__(self, units1: ty.Any, units2: ty.Optional[ty.Any] = None) -> None: self.units1 = units1 self.units2 = units2 def yield_units(self): yield self.units1 if self.units2: yield self.units2 def __str__(self): return ( "Ambiguous operation with offset unit (%s)." % ", ".join(str(u) for u in self.yield_units()) + " See " + OFFSET_ERROR_DOCS_HTML + " for guidance." ) def __reduce__(self): return self.__class__, (self.units1, self.units2) class LogarithmicUnitCalculusError(PintTypeError): """Raised on inappropriate operations with logarithmic units.""" units1: ty.Any units2: ty.Optional[ty.Any] = None def __init__(self, units1: ty.Any, units2: ty.Optional[ty.Any] = None) -> None: self.units1 = units1 self.units2 = units2 def yield_units(self): yield self.units1 if self.units2: yield self.units2 def __str__(self): return ( "Ambiguous operation with logarithmic unit (%s)." % ", ".join(str(u) for u in self.yield_units()) + " See " + LOG_ERROR_DOCS_HTML + " for guidance." ) def __reduce__(self): return self.__class__, (self.units1, self.units2) class UnitStrippedWarning(UserWarning, PintError): msg: str def __init__(self, msg: str): self.msg = msg def __reduce__(self): return self.__class__, (self.msg,) class UnexpectedScaleInContainer(Exception): pass class UndefinedBehavior(UserWarning, PintError): msg: str def __init__(self, msg: str): self.msg = msg def __reduce__(self): return self.__class__, (self.msg,) pint-0.24.4/pint/facets/000077500000000000000000000000001471316474000150015ustar00rootroot00000000000000pint-0.24.4/pint/facets/__init__.py000066400000000000000000000100661471316474000171150ustar00rootroot00000000000000""" pint.facets ~~~~~~~~~~~ Facets are way to add a specific set of funcionalities to Pint. It is more an organization logic than anything else. It aims to enable growth while keeping each part small enough to be hackable. Each facet contains one or more of the following modules: - definitions: classes describing specific unit-related definitons. These objects must be immutable, pickable and not reference the registry (e.g. ContextDefinition) - objects: classes and functions that encapsulate behavior (e.g. Context) - registry: implements a subclass of PlainRegistry or class that can be mixed with it (e.g. ContextRegistry) In certain cases, some of these modules might be collapsed into a single one as the code is very short (like in dask) or expanded as the code is too long (like in plain, where quantity and unit object are in their own module). Additionally, certain facets might not have one of them. An important part of this scheme is that each facet should export only a few classes in the __init__.py and everything else should not be accessed by any other module (except for testing). This is Python, so accessing it cannot be really limited. So is more an agreement than a rule. It is worth noticing that a Pint Quantity or Unit is always connected to a *specific* registry. Therefore we need to provide a way in which functionality can be added to a Quantity class in an easy way. This is achieved beautifully using specific class attributes. For example, the NumpyRegistry looks like this: class NumpyRegistry: Quantity = NumpyQuantity Unit = NumpyUnit This tells pint that it should use NumpyQuantity as base class for a quantity class that belongs to a registry that has NumpyRegistry as one of its bases. Currently the folowing facets are implemented: - plain: basic manipulation and calculation with multiplicative dimensions, units and quantities (e.g. length, time, mass, etc). - nonmultiplicative: manipulation and calculation with offset and log units and quantities (e.g. temperature and decibel). - measurement: manipulation and calculation of a quantity with an uncertainty. - numpy: using numpy array as magnitude and properly handling numpy functions operating on quantities. - dask: allows pint to interoperate with dask by implementing dask magic methods. - group: allow to make collections of units that can be then addressed together. - system: redefine base units for dimensions for a particular collection of units (e.g. imperial) - context: provides the means to interconvert between incompatible units through well defined relations (e.g. spectroscopy allows converting between spatial wavelength and temporal frequency) :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .context import ContextRegistry, GenericContextRegistry from .dask import DaskRegistry, GenericDaskRegistry from .group import GenericGroupRegistry, GroupRegistry from .measurement import GenericMeasurementRegistry, MeasurementRegistry from .nonmultiplicative import ( GenericNonMultiplicativeRegistry, NonMultiplicativeRegistry, ) from .numpy import GenericNumpyRegistry, NumpyRegistry from .plain import GenericPlainRegistry, MagnitudeT, PlainRegistry, QuantityT, UnitT from .system import GenericSystemRegistry, SystemRegistry __all__ = [ "ContextRegistry", "DaskRegistry", "FormattingRegistry", "GroupRegistry", "MeasurementRegistry", "NonMultiplicativeRegistry", "NumpyRegistry", "PlainRegistry", "SystemRegistry", "GenericContextRegistry", "GenericDaskRegistry", "GenericFormattingRegistry", "GenericGroupRegistry", "GenericMeasurementRegistry", "GenericNonMultiplicativeRegistry", "GenericNumpyRegistry", "GenericPlainRegistry", "GenericSystemRegistry", "QuantityT", "UnitT", "MagnitudeT", ] pint-0.24.4/pint/facets/context/000077500000000000000000000000001471316474000164655ustar00rootroot00000000000000pint-0.24.4/pint/facets/context/__init__.py000066400000000000000000000010311471316474000205710ustar00rootroot00000000000000""" pint.facets.context ~~~~~~~~~~~~~~~~~~~ Adds pint the capability to contexts: predefined conversions between incompatible dimensions. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .definitions import ContextDefinition from .objects import Context from .registry import ContextRegistry, GenericContextRegistry __all__ = ["ContextDefinition", "Context", "ContextRegistry", "GenericContextRegistry"] pint-0.24.4/pint/facets/context/definitions.py000066400000000000000000000113001471316474000213450ustar00rootroot00000000000000""" pint.facets.context.definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import itertools import numbers import re from collections.abc import Callable, Iterable from dataclasses import dataclass from typing import TYPE_CHECKING from ... import errors from ..plain import UnitDefinition if TYPE_CHECKING: from ..._typing import Quantity, UnitsContainer @dataclass(frozen=True) class Relation: """Base class for a relation between different dimensionalities.""" _varname_re = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") #: Source dimensionality src: UnitsContainer #: Destination dimensionality dst: UnitsContainer #: Equation connecting both dimensionalities from which the tranformation #: will be built. equation: str # Instead of defining __post_init__ here, # it will be added to the container class # so that the name and a meaningfull class # could be used. @property def variables(self) -> set[str]: """Find all variables names in the equation.""" return set(self._varname_re.findall(self.equation)) @property def transformation(self) -> Callable[..., Quantity]: """Return a transformation callable that uses the registry to parse the transformation equation. """ return lambda ureg, value, **kwargs: ureg.parse_expression( self.equation, value=value, **kwargs ) @property def bidirectional(self) -> bool: raise NotImplementedError @dataclass(frozen=True) class ForwardRelation(Relation): """A relation connecting a dimension to another via a transformation function. -> : """ @property def bidirectional(self) -> bool: return False @dataclass(frozen=True) class BidirectionalRelation(Relation): """A bidirectional relation connecting a dimension to another via a simple transformation function. <-> : """ @property def bidirectional(self) -> bool: return True @dataclass(frozen=True) class ContextDefinition(errors.WithDefErr): """Definition of a Context""" #: name of the context name: str #: other na aliases: tuple[str, ...] defaults: dict[str, numbers.Number] relations: tuple[Relation, ...] redefinitions: tuple[UnitDefinition, ...] @property def variables(self) -> set[str]: """Return all variable names in all transformations.""" return set().union(*(r.variables for r in self.relations)) @classmethod def from_lines(cls, lines: Iterable[str], non_int_type: type): # TODO: this is to keep it backwards compatible from ...delegates import ParserConfig, txt_defparser cfg = ParserConfig(non_int_type) parser = txt_defparser.DefParser(cfg, None) pp = parser.parse_string("\n".join(lines) + "\n@end") for definition in parser.iter_parsed_project(pp): if isinstance(definition, cls): return definition def __post_init__(self): if not errors.is_valid_context_name(self.name): raise self.def_err(errors.MSG_INVALID_GROUP_NAME) for k in self.aliases: if not errors.is_valid_context_name(k): raise self.def_err( f"refers to '{k}' that " + errors.MSG_INVALID_CONTEXT_NAME ) for relation in self.relations: invalid = tuple( itertools.filterfalse( errors.is_valid_dimension_name, relation.src.keys() ) ) + tuple( itertools.filterfalse( errors.is_valid_dimension_name, relation.dst.keys() ) ) if invalid: raise self.def_err( f"relation refers to {', '.join(invalid)} that " + errors.MSG_INVALID_DIMENSION_NAME ) for definition in self.redefinitions: if definition.symbol != definition.name or definition.aliases: raise self.def_err( "can't change a unit's symbol or aliases within a context" ) if definition.is_base: raise self.def_err("can't define plain units within a context") missing_pars = set(self.defaults.keys()) - self.variables if missing_pars: raise self.def_err( f"Context parameters {missing_pars} not found in any equation" ) pint-0.24.4/pint/facets/context/objects.py000066400000000000000000000257121471316474000204770ustar00rootroot00000000000000""" pint.facets.context.objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import weakref from collections import ChainMap, defaultdict from collections.abc import Callable, Iterable from typing import TYPE_CHECKING, Any, Generic, Protocol from ..._typing import Magnitude from ...facets.plain import MagnitudeT, PlainQuantity, PlainUnit, UnitDefinition from ...util import UnitsContainer, to_units_container from .definitions import ContextDefinition if TYPE_CHECKING: from ...registry import UnitRegistry class Transformation(Protocol): def __call__( self, ureg: UnitRegistry, value: PlainQuantity, **kwargs: Any ) -> PlainQuantity: ... from ..._typing import UnitLike ToBaseFunc = Callable[[UnitsContainer], UnitsContainer] SrcDst = tuple[UnitsContainer, UnitsContainer] class ContextQuantity(Generic[MagnitudeT], PlainQuantity[MagnitudeT]): pass class ContextUnit(PlainUnit): pass class Context: """A specialized container that defines transformation functions from one dimension to another. Each Dimension are specified using a UnitsContainer. Simple transformation are given with a function taking a single parameter. Conversion functions may take optional keyword arguments and the context can have default values for these arguments. Additionally, a context may host redefinitions. A redefinition must be performed among units that already exist in the registry. It cannot change the dimensionality of a unit. The symbol and aliases are automatically inherited from the registry. See ContextDefinition for the definition file syntax. Parameters ---------- name : str or None, optional Name of the context (must be unique within the registry). Use None for anonymous Context. (Default value = None). aliases : iterable of str Other names for the context. defaults : None or dict Maps variable names to values. Example ------- >>> from pint.util import UnitsContainer >>> from pint import Context, UnitRegistry >>> ureg = UnitRegistry() >>> timedim = UnitsContainer({'[time]': 1}) >>> spacedim = UnitsContainer({'[length]': 1}) >>> def time_to_len(ureg, time): ... 'Time to length converter' ... return 3. * time >>> c = Context() >>> c.add_transformation(timedim, spacedim, time_to_len) >>> c.transform(timedim, spacedim, ureg, 2) 6.0 >>> def time_to_len_indexed(ureg, time, n=1): ... 'Time to length converter, n is the index of refraction of the material' ... return 3. * time / n >>> c = Context(defaults={'n':3}) >>> c.add_transformation(timedim, spacedim, time_to_len_indexed) >>> c.transform(timedim, spacedim, ureg, 2) 2.0 >>> c.redefine("pound = 0.5 kg") """ def __init__( self, name: str | None = None, aliases: tuple[str, ...] = tuple(), defaults: dict[str, Any] | None = None, ) -> None: self.name: str | None = name self.aliases: tuple[str, ...] = aliases #: Maps (src, dst) -> transformation function self.funcs: dict[SrcDst, Transformation] = {} #: Maps defaults variable names to values self.defaults: dict[str, Any] = defaults or {} # Store Definition objects that are context-specific # TODO: narrow type this if possible. self.redefinitions: list[Any] = [] # Flag set to True by the Registry the first time the context is enabled self.checked = False #: Maps (src, dst) -> self #: Used as a convenience dictionary to be composed by ContextChain self.relation_to_context: weakref.WeakValueDictionary[ SrcDst, Context ] = weakref.WeakValueDictionary() @classmethod def from_context(cls, context: Context, **defaults: Any) -> Context: """Creates a new context that shares the funcs dictionary with the original context. The default values are copied from the original context and updated with the new defaults. If defaults is empty, return the same context. Parameters ---------- context : pint.Context Original context. **defaults Returns ------- pint.Context """ if defaults: newdef = dict(context.defaults, **defaults) c = cls(context.name, context.aliases, newdef) c.funcs = context.funcs c.redefinitions = context.redefinitions for edge in context.funcs: c.relation_to_context[edge] = c return c return context @classmethod def from_lines( cls, lines: Iterable[str], to_base_func: ToBaseFunc | None = None, non_int_type: type = float, ) -> Context: context_definition = ContextDefinition.from_lines(lines, non_int_type) if context_definition is None: raise ValueError(f"Could not define Context from from {lines}") return cls.from_definition(context_definition, to_base_func) @classmethod def from_definition( cls, cd: ContextDefinition, to_base_func: ToBaseFunc | None = None ) -> Context: ctx = cls(cd.name, cd.aliases, cd.defaults) for definition in cd.redefinitions: ctx._redefine(definition) for relation in cd.relations: try: # TODO: check to_base_func. Is it a good API idea? if to_base_func: src = to_base_func(relation.src) dst = to_base_func(relation.dst) else: src, dst = relation.src, relation.dst ctx.add_transformation(src, dst, relation.transformation) if relation.bidirectional: ctx.add_transformation(dst, src, relation.transformation) except Exception as exc: raise ValueError( f"Could not add Context {cd.name} relation {relation}" ) from exc return ctx def add_transformation( self, src: UnitLike, dst: UnitLike, func: Transformation ) -> None: """Add a transformation function to the context.""" _key = self.__keytransform__(src, dst) self.funcs[_key] = func self.relation_to_context[_key] = self def remove_transformation(self, src: UnitLike, dst: UnitLike) -> None: """Add a transformation function to the context.""" _key = self.__keytransform__(src, dst) del self.funcs[_key] del self.relation_to_context[_key] @staticmethod def __keytransform__(src: UnitLike, dst: UnitLike) -> SrcDst: return to_units_container(src), to_units_container(dst) def transform( self, src: UnitLike, dst: UnitLike, registry: Any, value: Magnitude ) -> Magnitude: """Transform a value.""" _key = self.__keytransform__(src, dst) func = self.funcs[_key] return func(registry, value, **self.defaults) def redefine(self, definition: str) -> None: """Override the definition of a unit in the registry. Parameters ---------- definition : str = ``, e.g. ``pound = 0.5 kg`` """ from ...delegates import ParserConfig, txt_defparser # TODO: kept for backwards compatibility. # this is not a good idea as we have no way of known the correct non_int_type cfg = ParserConfig(float) parser = txt_defparser.DefParser(cfg, None) pp = parser.parse_string(definition) for definition in parser.iter_parsed_project(pp): if isinstance(definition, UnitDefinition): self._redefine(definition) def _redefine(self, definition: UnitDefinition): self.redefinitions.append(definition) def hashable( self, ) -> tuple[ str | None, tuple[str, ...], frozenset[tuple[SrcDst, int]], frozenset[tuple[str, Any]], tuple[Any, ...], ]: """Generate a unique hashable and comparable representation of self, which can be used as a key in a dict. This class cannot define ``__hash__`` because it is mutable, and the Python interpreter does cache the output of ``__hash__``. Returns ------- tuple """ return ( self.name, tuple(self.aliases), frozenset((k, id(v)) for k, v in self.funcs.items()), frozenset(self.defaults.items()), tuple(self.redefinitions), ) class ContextChain(ChainMap[SrcDst, Context]): """A specialized ChainMap for contexts that simplifies finding rules to transform from one dimension to another. """ def __init__(self): super().__init__() self.contexts: list[Context] = [] self.maps.clear() # Remove default empty map self._graph: dict[SrcDst, set[UnitsContainer]] | None = None def insert_contexts(self, *contexts: Context): """Insert one or more contexts in reversed order the chained map. (A rule in last context will take precedence) To facilitate the identification of the context with the matching rule, the *relation_to_context* dictionary of the context is used. """ self.contexts = list(reversed(contexts)) + self.contexts self.maps = [ctx.relation_to_context for ctx in reversed(contexts)] + self.maps self._graph = None def remove_contexts(self, n: int | None = None): """Remove the last n inserted contexts from the chain. Parameters ---------- n: int (Default value = None) """ del self.contexts[:n] del self.maps[:n] self._graph = None @property def defaults(self) -> dict[str, Any]: for ctx in self.values(): return ctx.defaults return {} @property def graph(self): """The graph relating""" if self._graph is None: self._graph = defaultdict(set) for fr_, to_ in self: self._graph[fr_].add(to_) return self._graph # TODO: type registry def transform( self, src: UnitsContainer, dst: UnitsContainer, registry: Any, value: Magnitude ): """Transform the value, finding the rule in the chained context. (A rule in last context will take precedence) """ return self[(src, dst)].transform(src, dst, registry, value) def hashable(self) -> tuple[Any, ...]: """Generate a unique hashable and comparable representation of self, which can be used as a key in a dict. This class cannot define ``__hash__`` because it is mutable, and the Python interpreter does cache the output of ``__hash__``. """ return tuple(ctx.hashable() for ctx in self.contexts) pint-0.24.4/pint/facets/context/registry.py000066400000000000000000000352111471316474000207110ustar00rootroot00000000000000""" pint.facets.context.registry ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import functools from collections import ChainMap from collections.abc import Callable, Generator from contextlib import contextmanager from typing import Any, Generic from ..._typing import F, Magnitude from ...compat import TypeAlias from ...errors import UndefinedUnitError from ...util import UnitsContainer, find_connected_nodes, find_shortest_path, logger from ..plain import GenericPlainRegistry, QuantityT, UnitDefinition, UnitT from . import objects from .definitions import ContextDefinition # TODO: Put back annotation when possible # registry_cache: "RegistryCache" class ContextCacheOverlay: """Layer on top of the plain UnitRegistry cache, specific to a combination of active contexts which contain unit redefinitions. """ def __init__(self, registry_cache) -> None: self.dimensional_equivalents = registry_cache.dimensional_equivalents self.root_units = {} self.dimensionality = registry_cache.dimensionality self.parse_unit = registry_cache.parse_unit self.conversion_factor = {} class GenericContextRegistry( Generic[QuantityT, UnitT], GenericPlainRegistry[QuantityT, UnitT] ): """Handle of Contexts. Conversion between units with different dimensions according to previously established relations (contexts). (e.g. in the spectroscopy, conversion between frequency and energy is possible) Capabilities: - Register contexts. - Enable and disable contexts. - Parse @context directive. """ Context: type[objects.Context] = objects.Context def __init__(self, **kwargs: Any) -> None: # Map context name (string) or abbreviation to context. self._contexts: dict[str, objects.Context] = {} # Stores active contexts. self._active_ctx = objects.ContextChain() # Map context chain to cache self._caches = {} # Map context chain to units override self._context_units = {} super().__init__(**kwargs) # Allow contexts to add override layers to the units self._units: ChainMap[str, UnitDefinition] = ChainMap(self._units) def _register_definition_adders(self) -> None: super()._register_definition_adders() self._register_adder(ContextDefinition, self.add_context) def add_context(self, context: objects.Context | ContextDefinition) -> None: """Add a context object to the registry. The context will be accessible by its name and aliases. Notice that this method will NOT enable the context; see :meth:`enable_contexts`. """ if isinstance(context, ContextDefinition): context = objects.Context.from_definition(context, self.get_dimensionality) if not context.name: raise ValueError("Can't add unnamed context to registry") if context.name in self._contexts: logger.warning( "The name %s was already registered for another context.", context.name ) self._contexts[context.name] = context for alias in context.aliases: if alias in self._contexts: logger.warning( "The name %s was already registered for another context", context.name, ) self._contexts[alias] = context def remove_context(self, name_or_alias: str) -> objects.Context: """Remove a context from the registry and return it. Notice that this methods will not disable the context; see :meth:`disable_contexts`. """ context = self._contexts[name_or_alias] del self._contexts[context.name] for alias in context.aliases: del self._contexts[alias] return context def _build_cache(self, loaded_files=None) -> None: super()._build_cache(loaded_files) self._caches[()] = self._cache def _switch_context_cache_and_units(self) -> None: """If any of the active contexts redefine units, create variant self._cache and self._units specific to the combination of active contexts. The next time this method is invoked with the same combination of contexts, reuse the same variant self._cache and self._units as in the previous time. """ del self._units.maps[:-1] units_overlay = any(ctx.redefinitions for ctx in self._active_ctx.contexts) if not units_overlay: # Use the default _cache and _units self._cache = self._caches[()] return key = self._active_ctx.hashable() try: self._cache = self._caches[key] self._units.maps.insert(0, self._context_units[key]) except KeyError: pass # First time using this specific combination of contexts and it contains # unit redefinitions base_cache = self._caches[()] self._caches[key] = self._cache = ContextCacheOverlay(base_cache) self._context_units[key] = units_overlay = {} self._units.maps.insert(0, units_overlay) on_redefinition_backup = self._on_redefinition self._on_redefinition = "ignore" try: for ctx in reversed(self._active_ctx.contexts): for definition in ctx.redefinitions: self._redefine(definition) finally: self._on_redefinition = on_redefinition_backup def _redefine(self, definition: UnitDefinition) -> None: """Redefine a unit from a context""" # Find original definition in the UnitRegistry candidates = self.parse_unit_name(definition.name) if not candidates: raise UndefinedUnitError(definition.name) candidates_no_prefix = [c for c in candidates if not c[0]] if not candidates_no_prefix: raise ValueError(f"Can't redefine a unit with a prefix: {definition.name}") assert len(candidates_no_prefix) == 1 _, name, _ = candidates_no_prefix[0] try: basedef = self._units[name] except KeyError: raise UndefinedUnitError(name) # Rebuild definition as a variant of the plain if basedef.is_base: raise ValueError("Can't redefine a plain unit to a derived one") dims_old = self._get_dimensionality(basedef.reference) dims_new = self._get_dimensionality(definition.reference) if dims_old != dims_new: raise ValueError( f"Can't change dimensionality of {basedef.name} " f"from {dims_old} to {dims_new} in a context" ) # Do not modify in place the original definition, as (1) the context may # be shared by other registries, and (2) it would alter the cache key definition = UnitDefinition( name=basedef.name, defined_symbol=basedef.symbol, aliases=basedef.aliases, reference=definition.reference, converter=definition.converter, ) # Write into the context-specific self._units.maps[0] and self._cache.root_units self.define(definition) def enable_contexts( self, *names_or_contexts: str | objects.Context, **kwargs: Any ) -> None: """Enable contexts provided by name or by object. Parameters ---------- *names_or_contexts : one or more contexts or context names/aliases **kwargs : keyword arguments for the context(s) Examples -------- See :meth:`context` """ # If present, copy the defaults from the containing contexts if self._active_ctx.defaults: kwargs = dict(self._active_ctx.defaults, **kwargs) # For each name, we first find the corresponding context ctxs = [ self._contexts[name] if isinstance(name, str) else name for name in names_or_contexts ] # Check if the contexts have been checked first, if not we make sure # that dimensions are expressed in terms of plain dimensions. for ctx in ctxs: if ctx.checked: continue funcs_copy = dict(ctx.funcs) for (src, dst), func in funcs_copy.items(): src_ = self._get_dimensionality(src) dst_ = self._get_dimensionality(dst) if src != src_ or dst != dst_: ctx.remove_transformation(src, dst) ctx.add_transformation(src_, dst_, func) ctx.checked = True # and create a new one with the new defaults. contexts = tuple(objects.Context.from_context(ctx, **kwargs) for ctx in ctxs) # Finally we add them to the active context. self._active_ctx.insert_contexts(*contexts) self._switch_context_cache_and_units() def disable_contexts(self, n: int | None = None) -> None: """Disable the last n enabled contexts. Parameters ---------- n : int Number of contexts to disable. Default: disable all contexts. """ self._active_ctx.remove_contexts(n) self._switch_context_cache_and_units() @contextmanager def context( self: GenericContextRegistry[QuantityT, UnitT], *names: str, **kwargs: Any ) -> Generator[GenericContextRegistry[QuantityT, UnitT], None, None]: """Used as a context manager, this function enables to activate a context which is removed after usage. Parameters ---------- *names : name(s) of the context(s). **kwargs : keyword arguments for the contexts. Examples -------- Context can be called by their name: >>> import pint.facets.context.objects >>> import pint >>> ureg = pint.UnitRegistry() >>> ureg.add_context(pint.facets.context.objects.Context('one')) >>> ureg.add_context(pint.facets.context.objects.Context('two')) >>> with ureg.context('one'): ... pass If a context has an argument, you can specify its value as a keyword argument: >>> with ureg.context('one', n=1): ... pass Multiple contexts can be entered in single call: >>> with ureg.context('one', 'two', n=1): ... pass Or nested allowing you to give different values to the same keyword argument: >>> with ureg.context('one', n=1): ... with ureg.context('two', n=2): ... pass A nested context inherits the defaults from the containing context: >>> with ureg.context('one', n=1): ... # Here n takes the value of the outer context ... with ureg.context('two'): ... pass """ # Enable the contexts. self.enable_contexts(*names, **kwargs) try: # After adding the context and rebuilding the graph, the registry # is ready to use. yield self finally: # Upon leaving the with statement, # the added contexts are removed from the active one. self.disable_contexts(len(names)) def with_context(self, name: str, **kwargs: Any) -> Callable[[F], F]: """Decorator to wrap a function call in a Pint context. Use it to ensure that a certain context is active when calling a function. Parameters ---------- name : name of the context. **kwargs : keyword arguments for the context Returns ------- callable: the wrapped function. Examples -------- >>> @ureg.with_context('sp') ... def my_cool_fun(wavelength): ... print('This wavelength is equivalent to: %s', wavelength.to('terahertz')) """ def decorator(func): assigned = tuple( attr for attr in functools.WRAPPER_ASSIGNMENTS if hasattr(func, attr) ) updated = tuple( attr for attr in functools.WRAPPER_UPDATES if hasattr(func, attr) ) @functools.wraps(func, assigned=assigned, updated=updated) def wrapper(*values, **wrapper_kwargs): with self.context(name, **kwargs): return func(*values, **wrapper_kwargs) return wrapper return decorator def _convert( self, value: Magnitude, src: UnitsContainer, dst: UnitsContainer, inplace: bool = False, ) -> Magnitude: """Convert value from some source to destination units. In addition to what is done by the PlainRegistry, converts between units with different dimensions by following transformation rules defined in the context. Parameters ---------- value : value src : UnitsContainer source units. dst : UnitsContainer destination units. inplace : (Default value = False) Returns ------- callable converted value """ # If there is an active context, we look for a path connecting source and # destination dimensionality. If it exists, we transform the source value # by applying sequentially each transformation of the path. if self._active_ctx: src_dim = self._get_dimensionality(src) dst_dim = self._get_dimensionality(dst) path = find_shortest_path(self._active_ctx.graph, src_dim, dst_dim) if path: src = self.Quantity(value, src) for a, b in zip(path[:-1], path[1:]): src = self._active_ctx.transform(a, b, self, src) value, src = src._magnitude, src._units return super()._convert(value, src, dst, inplace) def _get_compatible_units( self, input_units: UnitsContainer, group_or_system: str | None = None ): src_dim = self._get_dimensionality(input_units) ret = super()._get_compatible_units(input_units, group_or_system) if self._active_ctx: ret = ret.copy() # Do not alter self._cache nodes = find_connected_nodes(self._active_ctx.graph, src_dim) if nodes: for node in nodes: ret |= self._cache.dimensional_equivalents[node] return ret class ContextRegistry( GenericContextRegistry[objects.ContextQuantity[Any], objects.ContextUnit] ): Quantity: TypeAlias = objects.ContextQuantity[Any] Unit: TypeAlias = objects.ContextUnit pint-0.24.4/pint/facets/dask/000077500000000000000000000000001471316474000157235ustar00rootroot00000000000000pint-0.24.4/pint/facets/dask/__init__.py000066400000000000000000000070431471316474000200400ustar00rootroot00000000000000""" pint.facets.dask ~~~~~~~~~~~~~~~~ Adds pint the capability to interoperate with Dask :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import functools from typing import Any, Generic from ...compat import TypeAlias, compute, dask_array, persist, visualize from ..plain import ( GenericPlainRegistry, MagnitudeT, PlainQuantity, PlainUnit, QuantityT, UnitT, ) def check_dask_array(f): @functools.wraps(f) def wrapper(self, *args, **kwargs): if isinstance(self._magnitude, dask_array.Array): return f(self, *args, **kwargs) else: msg = "Method {} only implemented for objects of {}, not {}".format( f.__name__, dask_array.Array, self._magnitude.__class__ ) raise AttributeError(msg) return wrapper class DaskQuantity(Generic[MagnitudeT], PlainQuantity[MagnitudeT]): # Dask.array.Array ducking def __dask_graph__(self): if isinstance(self._magnitude, dask_array.Array): return self._magnitude.__dask_graph__() return None def __dask_keys__(self): return self._magnitude.__dask_keys__() def __dask_tokenize__(self): from dask.base import tokenize return (type(self), tokenize(self._magnitude), self.units) @property def __dask_optimize__(self): return dask_array.Array.__dask_optimize__ @property def __dask_scheduler__(self): return dask_array.Array.__dask_scheduler__ def __dask_postcompute__(self): func, args = self._magnitude.__dask_postcompute__() return self._dask_finalize, (func, args, self.units) def __dask_postpersist__(self): func, args = self._magnitude.__dask_postpersist__() return self._dask_finalize, (func, args, self.units) def _dask_finalize(self, results, func, args, units): values = func(results, *args) return type(self)(values, units) @check_dask_array def compute(self, **kwargs): """Compute the Dask array wrapped by pint.PlainQuantity. Parameters ---------- **kwargs : dict Any keyword arguments to pass to ``dask.compute``. Returns ------- pint.PlainQuantity A pint.PlainQuantity wrapped numpy array. """ (result,) = compute(self, **kwargs) return result @check_dask_array def persist(self, **kwargs): """Persist the Dask Array wrapped by pint.PlainQuantity. Parameters ---------- **kwargs : dict Any keyword arguments to pass to ``dask.persist``. Returns ------- pint.PlainQuantity A pint.PlainQuantity wrapped Dask array. """ (result,) = persist(self, **kwargs) return result @check_dask_array def visualize(self, **kwargs): """Produce a visual representation of the Dask graph. The graphviz library is required. Parameters ---------- **kwargs : dict Any keyword arguments to pass to ``dask.visualize``. Returns ------- """ visualize(self, **kwargs) class DaskUnit(PlainUnit): pass class GenericDaskRegistry( Generic[QuantityT, UnitT], GenericPlainRegistry[QuantityT, UnitT] ): pass class DaskRegistry(GenericDaskRegistry[DaskQuantity[Any], DaskUnit]): Quantity: TypeAlias = DaskQuantity[Any] Unit: TypeAlias = DaskUnit pint-0.24.4/pint/facets/group/000077500000000000000000000000001471316474000161355ustar00rootroot00000000000000pint-0.24.4/pint/facets/group/__init__.py000066400000000000000000000010371471316474000202470ustar00rootroot00000000000000""" pint.facets.group ~~~~~~~~~~~~~~~~~ Adds pint the capability to group units. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .definitions import GroupDefinition from .objects import Group, GroupQuantity, GroupUnit from .registry import GenericGroupRegistry, GroupRegistry __all__ = [ "GroupDefinition", "Group", "GroupRegistry", "GenericGroupRegistry", "GroupQuantity", "GroupUnit", ] pint-0.24.4/pint/facets/group/definitions.py000066400000000000000000000033261471316474000210260ustar00rootroot00000000000000""" pint.facets.group.definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from collections.abc import Iterable from dataclasses import dataclass from ... import errors from ...compat import Self from .. import plain @dataclass(frozen=True) class GroupDefinition(errors.WithDefErr): """Definition of a group.""" #: name of the group name: str #: unit groups that will be included within the group using_group_names: tuple[str, ...] #: definitions for the units existing within the group definitions: tuple[plain.UnitDefinition, ...] @classmethod def from_lines( cls: type[Self], lines: Iterable[str], non_int_type: type ) -> Self | None: # TODO: this is to keep it backwards compatible from ...delegates import ParserConfig, txt_defparser cfg = ParserConfig(non_int_type) parser = txt_defparser.DefParser(cfg, None) pp = parser.parse_string("\n".join(lines) + "\n@end") for definition in parser.iter_parsed_project(pp): if isinstance(definition, cls): return definition @property def unit_names(self) -> tuple[str, ...]: return tuple(el.name for el in self.definitions) def __post_init__(self) -> None: if not errors.is_valid_group_name(self.name): raise self.def_err(errors.MSG_INVALID_GROUP_NAME) for k in self.using_group_names: if not errors.is_valid_group_name(k): raise self.def_err( f"refers to '{k}' that " + errors.MSG_INVALID_GROUP_NAME ) pint-0.24.4/pint/facets/group/objects.py000066400000000000000000000143771471316474000201540ustar00rootroot00000000000000""" pint.facets.group.objects ~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from collections.abc import Callable, Generator, Iterable from typing import TYPE_CHECKING, Any, Generic from ...util import SharedRegistryObject, getattr_maybe_raise from ..plain import MagnitudeT, PlainQuantity, PlainUnit from .definitions import GroupDefinition if TYPE_CHECKING: from ..plain import UnitDefinition DefineFunc = Callable[ [ Any, ], None, ] AddUnitFunc = Callable[ [ UnitDefinition, ], None, ] class GroupQuantity(Generic[MagnitudeT], PlainQuantity[MagnitudeT]): pass class GroupUnit(PlainUnit): pass class Group(SharedRegistryObject): """A group is a set of units. Units can be added directly or by including other groups. Members are computed dynamically, that is if a unit is added to a group X all groups that include X are affected. The group belongs to one Registry. See GroupDefinition for the definition file syntax. Parameters ---------- name If not given, a root Group will be created. """ def __init__(self, name: str): # The name of the group. self.name = name #: Names of the units in this group. #: :type: set[str] self._unit_names: set[str] = set() #: Names of the groups in this group. self._used_groups: set[str] = set() #: Names of the groups in which this group is contained. self._used_by: set[str] = set() # Add this group to the group dictionary self._REGISTRY._groups[self.name] = self if name != "root": # All groups are added to root group self._REGISTRY._groups["root"].add_groups(name) #: A cache of the included units. #: None indicates that the cache has been invalidated. self._computed_members: frozenset[str] | None = None @property def members(self) -> frozenset[str]: """Names of the units that are members of the group. Calculated to include to all units in all included _used_groups. """ if self._computed_members is None: tmp = set(self._unit_names) for _, group in self.iter_used_groups(): tmp |= group.members self._computed_members = frozenset(tmp) return self._computed_members def invalidate_members(self) -> None: """Invalidate computed members in this Group and all parent nodes.""" self._computed_members = None d = self._REGISTRY._groups for name in self._used_by: d[name].invalidate_members() def iter_used_groups(self) -> Generator[tuple[str, Group], None, None]: pending = set(self._used_groups) d = self._REGISTRY._groups while pending: name = pending.pop() group = d[name] pending |= group._used_groups yield name, d[name] def is_used_group(self, group_name: str) -> bool: for name, _ in self.iter_used_groups(): if name == group_name: return True return False def add_units(self, *unit_names: str) -> None: """Add units to group.""" for unit_name in unit_names: self._unit_names.add(unit_name) self.invalidate_members() @property def non_inherited_unit_names(self) -> frozenset[str]: return frozenset(self._unit_names) def remove_units(self, *unit_names: str) -> None: """Remove units from group.""" for unit_name in unit_names: self._unit_names.remove(unit_name) self.invalidate_members() def add_groups(self, *group_names: str) -> None: """Add groups to group.""" d = self._REGISTRY._groups for group_name in group_names: grp = d[group_name] if grp.is_used_group(self.name): raise ValueError( "Cyclic relationship found between %s and %s" % (self.name, group_name) ) self._used_groups.add(group_name) grp._used_by.add(self.name) self.invalidate_members() def remove_groups(self, *group_names: str) -> None: """Remove groups from group.""" d = self._REGISTRY._groups for group_name in group_names: grp = d[group_name] self._used_groups.remove(group_name) grp._used_by.remove(self.name) self.invalidate_members() @classmethod def from_lines( cls, lines: Iterable[str], define_func: DefineFunc, non_int_type: type = float ) -> Group: """Return a Group object parsing an iterable of lines. Parameters ---------- lines : list[str] iterable define_func : callable Function to define a unit in the registry; it must accept a single string as a parameter. Returns ------- """ group_definition = GroupDefinition.from_lines(lines, non_int_type) if group_definition is None: raise ValueError(f"Could not define group from {lines}") return cls.from_definition(group_definition, define_func) @classmethod def from_definition( cls, group_definition: GroupDefinition, add_unit_func: AddUnitFunc | None = None, ) -> Group: grp = cls(group_definition.name) add_unit_func = add_unit_func or grp._REGISTRY._add_unit # We first add all units defined within the group # to the registry. for definition in group_definition.definitions: add_unit_func(definition) # Then we add all units defined within the group # to this group (by name) grp.add_units(*group_definition.unit_names) # Finally, we add all grou0ps used by this group # tho this group (by name) if group_definition.using_group_names: grp.add_groups(*group_definition.using_group_names) return grp def __getattr__(self, item: str): getattr_maybe_raise(self, item) return self._REGISTRY pint-0.24.4/pint/facets/group/registry.py000066400000000000000000000110571471316474000203630ustar00rootroot00000000000000""" pint.facets.group.registry ~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from typing import TYPE_CHECKING, Any, Generic from ... import errors from ...compat import TypeAlias if TYPE_CHECKING: from ..._typing import Unit, UnitsContainer from ...util import create_class_with_registry, to_units_container from ..plain import ( GenericPlainRegistry, QuantityT, UnitDefinition, UnitT, ) from . import objects from .definitions import GroupDefinition class GenericGroupRegistry( Generic[QuantityT, UnitT], GenericPlainRegistry[QuantityT, UnitT] ): """Handle of Groups. Group units Capabilities: - Register groups. - Parse @group directive. """ # TODO: Change this to Group: Group to specify class # and use introspection to get system class as a way # to enjoy typing goodies Group = type[objects.Group] def __init__(self, **kwargs): super().__init__(**kwargs) #: Map group name to group. self._groups: dict[str, objects.Group] = {} self._groups["root"] = self.Group("root") def _init_dynamic_classes(self) -> None: """Generate subclasses on the fly and attach them to self""" super()._init_dynamic_classes() self.Group = create_class_with_registry(self, objects.Group) def _after_init(self) -> None: """Invoked at the end of ``__init__``. - Create default group and add all orphan units to it - Set default system """ super()._after_init() #: Copy units not defined in any group to the default group if "group" in self._defaults: grp = self.get_group(self._defaults["group"], True) group_units = frozenset( [ member for group in self._groups.values() if group.name != "root" for member in group.members ] ) all_units = self.get_group("root", False).members grp.add_units(*(all_units - group_units)) def _register_definition_adders(self) -> None: super()._register_definition_adders() self._register_adder(GroupDefinition, self._add_group) def _add_unit(self, definition: UnitDefinition): super()._add_unit(definition) # TODO: delta units are missing self.get_group("root").add_units(definition.name) def _add_group(self, gd: GroupDefinition): if gd.name in self._groups: raise ValueError(f"Group {gd.name} already present in registry") try: # As a Group is a SharedRegistryObject # it adds itself to the registry. self.Group.from_definition(gd) except KeyError as e: raise errors.DefinitionSyntaxError(f"unknown dimension {e} in context") def get_group(self, name: str, create_if_needed: bool = True) -> objects.Group: """Return a Group. Parameters ---------- name : str Name of the group to be create_if_needed : bool If True, create a group if not found. If False, raise an Exception. (Default value = True) Returns ------- Group Group """ if name in self._groups: return self._groups[name] if not create_if_needed: raise ValueError("Unknown group %s" % name) return self.Group(name) def get_compatible_units( self, input_units: UnitsContainer, group: str | None = None ) -> frozenset[Unit]: """ """ if group is None: return super().get_compatible_units(input_units) input_units = to_units_container(input_units) equiv = self._get_compatible_units(input_units, group) return frozenset(self.Unit(eq) for eq in equiv) def _get_compatible_units( self, input_units: UnitsContainer, group: str | None = None ) -> frozenset[str]: ret = super()._get_compatible_units(input_units) if not group: return ret if group in self._groups: members = self._groups[group].members else: raise ValueError("Unknown Group with name '%s'" % group) return frozenset(ret & members) class GroupRegistry( GenericGroupRegistry[objects.GroupQuantity[Any], objects.GroupUnit] ): Quantity: TypeAlias = objects.GroupQuantity[Any] Unit: TypeAlias = objects.GroupUnit pint-0.24.4/pint/facets/measurement/000077500000000000000000000000001471316474000173265ustar00rootroot00000000000000pint-0.24.4/pint/facets/measurement/__init__.py000066400000000000000000000010471471316474000214410ustar00rootroot00000000000000""" pint.facets.measurement ~~~~~~~~~~~~~~~~~~~~~~~ Adds pint the capability to handle measurements (quantities with uncertainties). :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .objects import Measurement, MeasurementQuantity from .registry import GenericMeasurementRegistry, MeasurementRegistry __all__ = [ "Measurement", "MeasurementQuantity", "MeasurementRegistry", "GenericMeasurementRegistry", ] pint-0.24.4/pint/facets/measurement/objects.py000066400000000000000000000151551471316474000213400ustar00rootroot00000000000000""" pint.facets.measurement.objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import copy import re from typing import Generic from ...compat import ufloat from ..plain import MagnitudeT, PlainQuantity, PlainUnit MISSING = object() class MeasurementQuantity(Generic[MagnitudeT], PlainQuantity[MagnitudeT]): # Measurement support def plus_minus(self, error, relative=False): if isinstance(error, self.__class__): if relative: raise ValueError(f"{error} is not a valid relative error.") error = error.to(self._units).magnitude else: if relative: error = error * abs(self.magnitude) return self._REGISTRY.Measurement(copy.copy(self.magnitude), error, self._units) class MeasurementUnit(PlainUnit): pass class Measurement(PlainQuantity): """Implements a class to describe a quantity with uncertainty. Parameters ---------- value : pint.Quantity or any numeric type The expected value of the measurement error : pint.Quantity or any numeric type The error or uncertainty of the measurement Returns ------- """ def __new__(cls, value, error=MISSING, units=MISSING): if units is MISSING: try: value, units = value.magnitude, value.units except AttributeError: # if called with two arguments and the first looks like a ufloat # then assume the second argument is the units, keep value intact if hasattr(value, "nominal_value"): units = error error = MISSING # used for check below else: units = "" if error is MISSING: # We've already extracted the units from the Quantity above mag = value else: try: error = error.to(units).magnitude except AttributeError: pass if error < 0: raise ValueError("The magnitude of the error cannot be negative") else: mag = ufloat(value, error) inst = super().__new__(cls, mag, units) return inst @property def value(self): return self._REGISTRY.Quantity(self.magnitude.nominal_value, self.units) @property def error(self): return self._REGISTRY.Quantity(self.magnitude.std_dev, self.units) @property def rel(self): return abs(self.magnitude.std_dev / self.magnitude.nominal_value) def __reduce__(self): # See notes in Quantity.__reduce__ from pint import _unpickle_measurement return _unpickle_measurement, (Measurement, self.magnitude, self._units) def __repr__(self): return "".format( self.magnitude.nominal_value, self.magnitude.std_dev, self.units ) def __str__(self): return f"{self}" def __format__(self, spec): spec = spec or self._REGISTRY.default_format return self._REGISTRY.formatter.format_measurement(self, spec) def old_format(self, spec): # TODO: provisional from ...formatting import _FORMATS, extract_custom_flags, siunitx_format_unit # special cases if "Lx" in spec: # the LaTeX siunitx code # the uncertainties module supports formatting # numbers in value(unc) notation (i.e. 1.23(45) instead of 1.23 +/- 0.45), # using type code 'S', which siunitx actually accepts as input. # However, the implementation is incompatible with siunitx. # Uncertainties will do 9.1(1.1), which is invalid, should be 9.1(11). # TODO: add support for extracting options # # Get rid of this code, we'll deal with it here spec = spec.replace("Lx", "") # The most compatible format from uncertainties is the default format, # but even this requires fixups. # For one, SIUnitx does not except some formats that unc does, like 'P', # and 'S' is broken as stated, so... spec = spec.replace("S", "").replace("P", "") # get SIunitx options # TODO: allow user to set this value, somehow opts = _FORMATS["Lx"]["siopts"] if opts != "": opts = r"[" + opts + r"]" # SI requires space between "+-" (or "\pm") and the nominal value # and uncertainty, and doesn't accept "+/-", so this setting # selects the desired replacement. pm_fmt = _FORMATS["Lx"]["pm_fmt"] mstr = format(self.magnitude, spec).replace(r"+/-", pm_fmt) # Also, SIunitx doesn't accept parentheses, which uncs uses with # scientific notation ('e' or 'E' and sometimes 'g' or 'G'). mstr = mstr.replace("(", "").replace(")", " ") ustr = siunitx_format_unit(self.units._units.items(), self._REGISTRY) return rf"\SI{opts}{{{mstr}}}{{{ustr}}}" # standard cases if "L" in spec: newpm = pm = r" \pm " pars = _FORMATS["L"]["parentheses_fmt"] elif "P" in spec: newpm = pm = "±" pars = _FORMATS["P"]["parentheses_fmt"] else: newpm = pm = "+/-" pars = _FORMATS[""]["parentheses_fmt"] if "C" in spec: sp = "" newspec = spec.replace("C", "") pars = _FORMATS["C"]["parentheses_fmt"] else: sp = " " newspec = spec if "H" in spec: newpm = "±" newspec = spec.replace("H", "") pars = _FORMATS["H"]["parentheses_fmt"] mag = format(self.magnitude, newspec).replace(pm, sp + newpm + sp) if "(" in mag: # Exponential format has its own parentheses pars = "{}" if "L" in newspec and "S" in newspec: mag = mag.replace("(", r"\left(").replace(")", r"\right)") if "L" in newspec: space = r"\ " else: space = " " uspec = extract_custom_flags(spec) ustr = format(self.units, uspec) if not ("uS" in newspec or "ue" in newspec or "u%" in newspec): mag = pars.format(mag) if "H" in spec: # Fix exponential format mag = re.sub(r"\)e\+0?(\d+)", r")×10\1", mag) mag = re.sub(r"\)e-0?(\d+)", r")×10-\1", mag) return mag + space + ustr pint-0.24.4/pint/facets/measurement/registry.py000066400000000000000000000025211471316474000215500ustar00rootroot00000000000000""" pint.facets.measurement.registry ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from typing import Any, Generic from ...compat import TypeAlias, ufloat from ...util import create_class_with_registry from ..plain import GenericPlainRegistry, QuantityT, UnitT from . import objects class GenericMeasurementRegistry( Generic[QuantityT, UnitT], GenericPlainRegistry[QuantityT, UnitT] ): Measurement = objects.Measurement def _init_dynamic_classes(self) -> None: """Generate subclasses on the fly and attach them to self""" super()._init_dynamic_classes() if ufloat is not None: self.Measurement = create_class_with_registry(self, self.Measurement) else: def no_uncertainties(*args, **kwargs): raise RuntimeError( "Pint requires the 'uncertainties' package to create a Measurement object." ) self.Measurement = no_uncertainties class MeasurementRegistry( GenericMeasurementRegistry[ objects.MeasurementQuantity[Any], objects.MeasurementUnit ] ): Quantity: TypeAlias = objects.MeasurementQuantity[Any] Unit: TypeAlias = objects.MeasurementUnit pint-0.24.4/pint/facets/nonmultiplicative/000077500000000000000000000000001471316474000205475ustar00rootroot00000000000000pint-0.24.4/pint/facets/nonmultiplicative/__init__.py000066400000000000000000000012241471316474000226570ustar00rootroot00000000000000""" pint.facets.nonmultiplicative ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Adds pint the capability to handle nonmultiplicative units: - offset - logarithmic :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations # This import register LogarithmicConverter and OffsetConverter to be usable # (via subclassing) from .definitions import LogarithmicConverter, OffsetConverter # noqa: F401 from .registry import GenericNonMultiplicativeRegistry, NonMultiplicativeRegistry __all__ = ["NonMultiplicativeRegistry", "GenericNonMultiplicativeRegistry"] pint-0.24.4/pint/facets/nonmultiplicative/definitions.py000066400000000000000000000063061471316474000234410ustar00rootroot00000000000000""" pint.facets.nonmultiplicative.definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from dataclasses import dataclass from ..._typing import Magnitude from ...compat import HAS_NUMPY, exp, log from ..plain import ScaleConverter @dataclass(frozen=True) class OffsetConverter(ScaleConverter): """An affine transformation.""" offset: float @property def is_multiplicative(self): return self.offset == 0 def to_reference(self, value: Magnitude, inplace: bool = False) -> Magnitude: if inplace: value *= self.scale value += self.offset else: value = value * self.scale + self.offset return value def from_reference(self, value: Magnitude, inplace: bool = False) -> Magnitude: if inplace: value -= self.offset value /= self.scale else: value = (value - self.offset) / self.scale return value @classmethod def preprocess_kwargs(cls, **kwargs): if "offset" in kwargs and kwargs["offset"] == 0: return {"scale": kwargs["scale"]} return None @dataclass(frozen=True) class LogarithmicConverter(ScaleConverter): """Converts between linear units and logarithmic units, such as dB, octave, neper or pH. Q_log = logfactor * log( Q_lin / scale ) / log(log_base) Parameters ---------- scale : float unit of reference at denominator for logarithmic unit conversion logbase : float plain of logarithm used in the logarithmic unit conversion logfactor : float factor multiplied to logarithm for unit conversion inplace : bool controls if computation is done in place """ # TODO: Can I use PintScalar here? logbase: float logfactor: float @property def is_multiplicative(self): return False @property def is_logarithmic(self): return True def from_reference(self, value: Magnitude, inplace: bool = False) -> Magnitude: """Converts value from the reference unit to the logarithmic unit dBm <------ mW y dBm = 10 log10( x / 1mW ) """ if inplace: value /= self.scale if HAS_NUMPY: log(value, value) else: value = log(value) value *= self.logfactor / log(self.logbase) else: value = self.logfactor * log(value / self.scale) / log(self.logbase) return value def to_reference(self, value: Magnitude, inplace: bool = False) -> Magnitude: """Converts value to the reference unit from the logarithmic unit dBm ------> mW y dBm = 10 log10( x / 1mW ) """ if inplace: value /= self.logfactor value *= log(self.logbase) if HAS_NUMPY: exp(value, value) else: value = exp(value) value *= self.scale else: value = self.scale * exp(log(self.logbase) * (value / self.logfactor)) return value pint-0.24.4/pint/facets/nonmultiplicative/objects.py000066400000000000000000000044651471316474000225630ustar00rootroot00000000000000""" pint.facets.nonmultiplicative.objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from typing import Generic from ..plain import MagnitudeT, PlainQuantity, PlainUnit class NonMultiplicativeQuantity(Generic[MagnitudeT], PlainQuantity[MagnitudeT]): @property def _is_multiplicative(self) -> bool: """Check if the PlainQuantity object has only multiplicative units.""" return not self._get_non_multiplicative_units() def _get_non_multiplicative_units(self) -> list[str]: """Return a list of the of non-multiplicative units of the PlainQuantity object.""" return [ unit for unit in self._units if not self._get_unit_definition(unit).is_multiplicative ] def _get_delta_units(self) -> list[str]: """Return list of delta units ot the PlainQuantity object.""" return [u for u in self._units if u.startswith("delta_")] def _has_compatible_delta(self, unit: str) -> bool: """ "Check if PlainQuantity object has a delta_unit that is compatible with unit""" deltas = self._get_delta_units() if "delta_" + unit in deltas: return True # Look for delta units with same dimension as the offset unit offset_unit_dim = self._get_unit_definition(unit).reference return any( self._get_unit_definition(d).reference == offset_unit_dim for d in deltas ) def _ok_for_muldiv(self, no_offset_units: int | None = None) -> bool: """Checks if PlainQuantity object can be multiplied or divided""" is_ok = True if no_offset_units is None: no_offset_units = len(self._get_non_multiplicative_units()) if no_offset_units > 1: is_ok = False if no_offset_units == 1: if len(self._units) > 1: is_ok = False if ( len(self._units) == 1 and not self._REGISTRY.autoconvert_offset_to_baseunit ): is_ok = False if next(iter(self._units.values())) != 1: is_ok = False return is_ok class NonMultiplicativeUnit(PlainUnit): pass pint-0.24.4/pint/facets/nonmultiplicative/registry.py000066400000000000000000000236011471316474000227730ustar00rootroot00000000000000""" pint.facets.nonmultiplicative.registry ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from typing import Any, Generic, TypeVar from ...compat import TypeAlias from ...errors import DimensionalityError, UndefinedUnitError from ...util import UnitsContainer, logger from ..plain import GenericPlainRegistry, QuantityT, UnitDefinition, UnitT from . import objects from .definitions import OffsetConverter, ScaleConverter T = TypeVar("T") class GenericNonMultiplicativeRegistry( Generic[QuantityT, UnitT], GenericPlainRegistry[QuantityT, UnitT] ): """Handle of non multiplicative units (e.g. Temperature). Capabilities: - Register non-multiplicative units and their relations. - Convert between non-multiplicative units. Parameters ---------- default_as_delta : bool If True, non-multiplicative units are interpreted as their *delta* counterparts in multiplications. autoconvert_offset_to_baseunit : bool If True, non-multiplicative units are converted to plain units in multiplications. """ def __init__( self, default_as_delta: bool = True, autoconvert_offset_to_baseunit: bool = False, **kwargs: Any, ) -> None: super().__init__(**kwargs) #: When performing a multiplication of units, interpret #: non-multiplicative units as their *delta* counterparts. self.default_as_delta = default_as_delta # Determines if quantities with offset units are converted to their # plain units on multiplication and division. self.autoconvert_offset_to_baseunit = autoconvert_offset_to_baseunit def parse_units_as_container( self, input_string: str, as_delta: bool | None = None, case_sensitive: bool | None = None, ) -> UnitsContainer: """ """ if as_delta is None: as_delta = self.default_as_delta return super().parse_units_as_container(input_string, as_delta, case_sensitive) def _add_unit(self, definition: UnitDefinition) -> None: super()._add_unit(definition) if definition.is_multiplicative: return if definition.is_logarithmic: return if not isinstance(definition.converter, OffsetConverter): logger.debug( "Cannot autogenerate delta version for a unit in " "which the converter is not an OffsetConverter" ) return delta_name = "delta_" + definition.name if definition.symbol: delta_symbol = "Δ" + definition.symbol else: delta_symbol = None delta_aliases = tuple("Δ" + alias for alias in definition.aliases) + tuple( "delta_" + alias for alias in definition.aliases ) delta_reference = self.UnitsContainer( {ref: value for ref, value in definition.reference.items()} ) delta_def = UnitDefinition( delta_name, delta_symbol, delta_aliases, ScaleConverter(definition.converter.scale), delta_reference, ) super()._add_unit(delta_def) def _is_multiplicative(self, unit_name: str) -> bool: """True if the unit is multiplicative. Parameters ---------- unit_name Name of the unit to check. Can be prefixed, pluralized or even an alias Raises ------ UndefinedUnitError If the unit is not in the registry. """ if unit_name in self._units: return self._units[unit_name].is_multiplicative # If the unit is not in the registry might be because it is not # registered with its prefixed version. # TODO: Might be better to register them. names = self.parse_unit_name(unit_name) assert len(names) == 1 _, base_name, _ = names[0] try: return self._units[base_name].is_multiplicative except KeyError: raise UndefinedUnitError(unit_name) def _validate_and_extract(self, units: UnitsContainer) -> str | None: """Used to check if a given units is suitable for a simple conversion. Return None if all units are non-multiplicative Return the unit name if a single non-multiplicative unit is found and is raised to a power equals to 1. Otherwise, raise an Exception. Parameters ---------- units Compound dictionary. Raises ------ ValueError If the more than a single non-multiplicative unit is present, or a single one is present but raised to a power different from 1. """ # TODO: document what happens if autoconvert_offset_to_baseunit # TODO: Clarify docs # u is for unit, e is for exponent nonmult_units = [ (u, e) for u, e in units.items() if not self._is_multiplicative(u) ] # Let's validate source offset units if len(nonmult_units) > 1: # More than one src offset unit is not allowed raise ValueError("more than one offset unit.") elif len(nonmult_units) == 1: # A single src offset unit is present. Extract it # But check that: # - the exponent is 1 # - is not used in multiplicative context nonmult_unit, exponent = nonmult_units.pop() if exponent != 1: raise ValueError("offset units in higher order.") if len(units) > 1 and not self.autoconvert_offset_to_baseunit: raise ValueError("offset unit used in multiplicative context.") return nonmult_unit return None def _add_ref_of_log_or_offset_unit( self, offset_unit: str, all_units: UnitsContainer ) -> UnitsContainer: slct_unit = self._units[offset_unit] if slct_unit.is_logarithmic: # Extract reference unit slct_ref = slct_unit.reference # TODO: Check that reference is None # If reference unit is not dimensionless if slct_ref != UnitsContainer(): # Extract reference unit (u, e) = [(u, e) for u, e in slct_ref.items()].pop() # Add it back to the unit list return all_units.add(u, e) if not slct_unit.is_multiplicative: # is offset unit # Extract reference unit return slct_unit.reference # Otherwise, return the units unmodified return all_units def _convert( self, value: T, src: UnitsContainer, dst: UnitsContainer, inplace: bool = False ) -> T: """Convert value from some source to destination units. In addition to what is done by the PlainRegistry, converts between non-multiplicative units. Parameters ---------- value : value src : UnitsContainer source units. dst : UnitsContainer destination units. inplace : (Default value = False) Returns ------- type converted value """ # Conversion needs to consider if non-multiplicative (AKA offset # units) are involved. Conversion is only possible if src and dst # have at most one offset unit per dimension. Other rules are applied # by validate and extract. try: src_offset_unit = self._validate_and_extract(src) except ValueError as ex: raise DimensionalityError(src, dst, extra_msg=f" - In source units, {ex}") try: dst_offset_unit = self._validate_and_extract(dst) except ValueError as ex: raise DimensionalityError( src, dst, extra_msg=f" - In destination units, {ex}" ) # convert if no offset units are present if not (src_offset_unit or dst_offset_unit): return super()._convert(value, src, dst, inplace) src_dim = self._get_dimensionality(src) dst_dim = self._get_dimensionality(dst) # If the source and destination dimensionality are different, # then the conversion cannot be performed. if src_dim != dst_dim: raise DimensionalityError(src, dst, src_dim, dst_dim) # clean src from offset units by converting to reference if src_offset_unit: if any(u.startswith("delta_") for u in dst): raise DimensionalityError(src, dst) value = self._units[src_offset_unit].converter.to_reference(value, inplace) src = src.remove([src_offset_unit]) # Add reference unit for multiplicative section src = self._add_ref_of_log_or_offset_unit(src_offset_unit, src) # clean dst units from offset units if dst_offset_unit: if any(u.startswith("delta_") for u in src): raise DimensionalityError(src, dst) dst = dst.remove([dst_offset_unit]) # Add reference unit for multiplicative section dst = self._add_ref_of_log_or_offset_unit(dst_offset_unit, dst) # Convert non multiplicative units to the dst. value = super()._convert(value, src, dst, inplace, False) # Finally convert to offset units specified in destination if dst_offset_unit: value = self._units[dst_offset_unit].converter.from_reference( value, inplace ) return value class NonMultiplicativeRegistry( GenericNonMultiplicativeRegistry[ objects.NonMultiplicativeQuantity[Any], objects.NonMultiplicativeUnit ] ): Quantity: TypeAlias = objects.NonMultiplicativeQuantity[Any] Unit: TypeAlias = objects.NonMultiplicativeUnit pint-0.24.4/pint/facets/numpy/000077500000000000000000000000001471316474000161515ustar00rootroot00000000000000pint-0.24.4/pint/facets/numpy/__init__.py000066400000000000000000000005671471316474000202720ustar00rootroot00000000000000""" pint.facets.numpy ~~~~~~~~~~~~~~~~~ Adds pint the capability to interoperate with NumPy :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .registry import GenericNumpyRegistry, NumpyRegistry __all__ = ["NumpyRegistry", "GenericNumpyRegistry"] pint-0.24.4/pint/facets/numpy/numpy_func.py000066400000000000000000001056751471316474000207240ustar00rootroot00000000000000""" pint.facets.numpy.numpy_func ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import warnings from inspect import signature from itertools import chain from ...compat import is_upcast_type, np, zero_or_nan from ...errors import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning from ...util import iterable, sized HANDLED_UFUNCS = {} HANDLED_FUNCTIONS = {} # Shared Implementation Utilities def _is_quantity(obj): """Test for _units and _magnitude attrs. This is done in place of isinstance(Quantity, arg), which would cause a circular import. Parameters ---------- obj : Object Returns ------- bool """ return hasattr(obj, "_units") and hasattr(obj, "_magnitude") def _is_sequence_with_quantity_elements(obj): """Test for sequences of quantities. Parameters ---------- obj : object Returns ------- True if obj is a sequence and at least one element is a Quantity; False otherwise """ if np is not None and isinstance(obj, np.ndarray) and not obj.dtype.hasobject: # If obj is a numpy array, avoid looping on all elements # if dtype does not have objects return False return ( iterable(obj) and sized(obj) and not isinstance(obj, str) and any(_is_quantity(item) for item in obj) ) def _get_first_input_units(args, kwargs=None): """Obtain the first valid unit from a collection of args and kwargs.""" kwargs = kwargs or {} for arg in chain(args, kwargs.values()): if _is_quantity(arg): return arg.units elif _is_sequence_with_quantity_elements(arg): return next(arg_i.units for arg_i in arg if _is_quantity(arg_i)) raise TypeError("Expected at least one Quantity; found none") def convert_arg(arg, pre_calc_units): """Convert quantities and sequences of quantities to pre_calc_units and strip units. Helper function for convert_to_consistent_units. pre_calc_units must be given as a pint Unit or None. """ if isinstance(arg, bool): return arg if pre_calc_units is not None: if _is_quantity(arg): return arg.m_as(pre_calc_units) elif _is_sequence_with_quantity_elements(arg): return [convert_arg(item, pre_calc_units) for item in arg] elif arg is not None: if pre_calc_units.dimensionless: return pre_calc_units._REGISTRY.Quantity(arg).m_as(pre_calc_units) elif not _is_quantity(arg) and zero_or_nan(arg, True): return arg else: raise DimensionalityError("dimensionless", pre_calc_units) elif _is_quantity(arg): return arg.m elif _is_sequence_with_quantity_elements(arg): return [convert_arg(item, pre_calc_units) for item in arg] return arg def convert_to_consistent_units(*args, pre_calc_units=None, **kwargs): """Prepare args and kwargs for wrapping by unit conversion and stripping. If pre_calc_units is not None, takes the args and kwargs for a NumPy function and converts any Quantity or Sequence of Quantities into the units of the first Quantity/Sequence of Quantities and returns the magnitudes. Other args/kwargs (except booleans) are treated as dimensionless Quantities. If pre_calc_units is None, units are simply stripped. """ return ( tuple(convert_arg(arg, pre_calc_units=pre_calc_units) for arg in args), { key: convert_arg(arg, pre_calc_units=pre_calc_units) for key, arg in kwargs.items() }, ) def unwrap_and_wrap_consistent_units(*args): """Strip units from args while providing a rewrapping function. Returns the given args as parsed by convert_to_consistent_units assuming units of first arg with units, along with a wrapper to restore that unit to the output. """ if all(not _is_quantity(arg) for arg in args): return args, lambda x: x first_input_units = _get_first_input_units(args) args, _ = convert_to_consistent_units(*args, pre_calc_units=first_input_units) return ( args, lambda value: first_input_units._REGISTRY.Quantity(value, first_input_units), ) def get_op_output_unit(unit_op, first_input_units, all_args=None, size=None): """Determine resulting unit from given operation. Options for `unit_op`: - "sum": `first_input_units`, unless non-multiplicative, which raises OffsetUnitCalculusError - "mul": product of all units in `all_args` - "delta": `first_input_units`, unless non-multiplicative, which uses delta version - "delta,div": like "delta", but divided by all units in `all_args` except the first - "div": unit of first argument in `all_args` (or dimensionless if not a Quantity) divided by all following units - "variance": square of `first_input_units`, unless non-multiplicative, which raises OffsetUnitCalculusError - "square": square of `first_input_units` - "sqrt": square root of `first_input_units` - "reciprocal": reciprocal of `first_input_units` - "size": `first_input_units` raised to the power of `size` - "invdiv": inverse of `div`, product of all following units divided by first argument unit Parameters ---------- unit_op : first_input_units : all_args : (Default value = None) size : (Default value = None) Returns ------- """ all_args = all_args or [] if unit_op == "sum": result_unit = (1 * first_input_units + 1 * first_input_units).units elif unit_op == "mul": product = first_input_units._REGISTRY.parse_units("") for x in all_args: if hasattr(x, "units"): product *= x.units result_unit = product elif unit_op == "delta": result_unit = (1 * first_input_units - 1 * first_input_units).units elif unit_op == "delta,div": product = (1 * first_input_units - 1 * first_input_units).units for x in all_args[1:]: if hasattr(x, "units"): product /= x.units result_unit = product elif unit_op == "div": # Start with first arg in numerator, all others in denominator product = getattr( all_args[0], "units", first_input_units._REGISTRY.parse_units("") ) for x in all_args[1:]: if hasattr(x, "units"): product /= x.units result_unit = product elif unit_op == "variance": result_unit = ((1 * first_input_units + 1 * first_input_units) ** 2).units elif unit_op == "square": result_unit = first_input_units**2 elif unit_op == "sqrt": result_unit = first_input_units**0.5 elif unit_op == "cbrt": result_unit = first_input_units ** (1 / 3) elif unit_op == "reciprocal": result_unit = first_input_units**-1 elif unit_op == "size": if size is None: raise ValueError('size argument must be given when unit_op=="size"') result_unit = first_input_units**size elif unit_op == "invdiv": # Start with first arg in numerator, all others in denominator product = getattr( all_args[0], "units", first_input_units._REGISTRY.parse_units("") ) for x in all_args[1:]: if hasattr(x, "units"): product /= x.units result_unit = product**-1 else: raise ValueError(f"Output unit method {unit_op} not understood") return result_unit def implements(numpy_func_string, func_type): """Register an __array_function__/__array_ufunc__ implementation for Quantity objects. """ def decorator(func): if func_type == "function": HANDLED_FUNCTIONS[numpy_func_string] = func elif func_type == "ufunc": HANDLED_UFUNCS[numpy_func_string] = func else: raise ValueError(f"Invalid func_type {func_type}") return func return decorator def implement_func(func_type, func_str, input_units=None, output_unit=None): """Add default-behavior NumPy function/ufunc to the handled list. Parameters ---------- func_type : str "function" for NumPy functions, "ufunc" for NumPy ufuncs func_str : str String representing the name of the NumPy function/ufunc to add input_units : pint.Unit or str or None Parameter to control how the function downcasts to magnitudes of arguments. If `pint.Unit`, converts all args and kwargs to this unit before downcasting to magnitude. If "all_consistent", converts all args and kwargs to the unit of the first Quantity in args and kwargs before downcasting to magnitude. If some other string, the string is parsed as a unit, and all args and kwargs are converted to that unit. If None, units are stripped without conversion. output_unit : pint.Unit or str or None Parameter to control the unit of the output. If `pint.Unit`, output is wrapped with that unit. If "match_input", output is wrapped with the unit of the first Quantity in args and kwargs. If a string representing a unit operation defined in `get_op_output_unit`, output is wrapped by the unit determined by `get_op_output_unit`. If some other string, the string is parsed as a unit, which becomes the unit of the output. If None, the bare magnitude is returned. """ # If NumPy is not available, do not attempt implement that which does not exist if np is None: return # Handle functions in submodules func_str_split = func_str.split(".") func = getattr(np, func_str_split[0], None) # If the function is not available, do not attempt to implement it if func is None: return for func_str_piece in func_str_split[1:]: func = getattr(func, func_str_piece) @implements(func_str, func_type) def implementation(*args, **kwargs): if func_str in ["multiply", "true_divide", "divide", "floor_divide"] and any( [ not _is_quantity(arg) and _is_sequence_with_quantity_elements(arg) for arg in args ] ): # the sequence may contain different units, so fall back to element-wise return np.array( [func(*func_args) for func_args in zip(*args)], dtype=object ) first_input_units = _get_first_input_units(args, kwargs) if input_units == "all_consistent": # Match all input args/kwargs to same units stripped_args, stripped_kwargs = convert_to_consistent_units( *args, pre_calc_units=first_input_units, **kwargs ) else: if isinstance(input_units, str): # Conversion requires Unit, not str pre_calc_units = first_input_units._REGISTRY.parse_units(input_units) else: pre_calc_units = input_units # Match all input args/kwargs to input_units, or if input_units is None, # simply strip units stripped_args, stripped_kwargs = convert_to_consistent_units( *args, pre_calc_units=pre_calc_units, **kwargs ) # Determine result through plain numpy function on stripped arguments result_magnitude = func(*stripped_args, **stripped_kwargs) if output_unit is None: # Short circuit and return magnitude alone return result_magnitude elif output_unit == "match_input": result_unit = first_input_units elif output_unit in ( "sum", "mul", "delta", "delta,div", "div", "invdiv", "variance", "square", "sqrt", "cbrt", "reciprocal", "size", ): result_unit = get_op_output_unit( output_unit, first_input_units, tuple(chain(args, kwargs.values())) ) else: result_unit = output_unit return first_input_units._REGISTRY.Quantity(result_magnitude, result_unit) """ Define ufunc behavior collections. - `strip_unit_input_output_ufuncs`: units should be ignored on both input and output - `matching_input_bare_output_ufuncs`: inputs are converted to matching units, but outputs are returned as-is - `matching_input_set_units_output_ufuncs`: inputs are converted to matching units, and the output units are as set by the dict value - `set_units_ufuncs`: dict values are specified as (in_unit, out_unit), so that inputs are converted to in_unit before having magnitude passed to NumPy ufunc, and outputs are set to have out_unit - `matching_input_copy_units_output_ufuncs`: inputs are converted to matching units, and outputs are set to that unit - `copy_units_output_ufuncs`: input units (except the first) are ignored, and output is set to that of the first input unit - `op_units_output_ufuncs`: determine output unit from input unit as determined by operation (see `get_op_output_unit`) """ strip_unit_input_output_ufuncs = ["isnan", "isinf", "isfinite", "signbit", "sign"] matching_input_bare_output_ufuncs = [ "equal", "greater", "greater_equal", "less", "less_equal", "not_equal", ] matching_input_set_units_output_ufuncs = {"arctan2": "radian"} set_units_ufuncs = { "cumprod": ("", ""), "arccos": ("", "radian"), "arcsin": ("", "radian"), "arctan": ("", "radian"), "arccosh": ("", "radian"), "arcsinh": ("", "radian"), "arctanh": ("", "radian"), "exp": ("", ""), "expm1": ("", ""), "exp2": ("", ""), "log": ("", ""), "log10": ("", ""), "log1p": ("", ""), "log2": ("", ""), "sin": ("radian", ""), "cos": ("radian", ""), "tan": ("radian", ""), "sinh": ("radian", ""), "cosh": ("radian", ""), "tanh": ("radian", ""), "radians": ("degree", "radian"), "degrees": ("radian", "degree"), "deg2rad": ("degree", "radian"), "rad2deg": ("radian", "degree"), "logaddexp": ("", ""), "logaddexp2": ("", ""), } # TODO (#905 follow-up): # while this matches previous behavior, some of these have optional arguments that # should not be Quantities. This should be fixed, and tests using these optional # arguments should be added. matching_input_copy_units_output_ufuncs = [ "compress", "conj", "conjugate", "copy", "diagonal", "max", "mean", "min", "ptp", "ravel", "repeat", "reshape", "round", "squeeze", "swapaxes", "take", "trace", "transpose", "roll", "ceil", "floor", "hypot", "rint", "copysign", "nextafter", "trunc", "absolute", "positive", "negative", "maximum", "minimum", "fabs", ] copy_units_output_ufuncs = ["ldexp", "fmod", "mod", "remainder"] op_units_output_ufuncs = { "var": "square", "multiply": "mul", "true_divide": "div", "divide": "div", "floor_divide": "div", "sqrt": "sqrt", "cbrt": "cbrt", "square": "square", "reciprocal": "reciprocal", "std": "sum", "sum": "sum", "cumsum": "sum", "matmul": "mul", } # Perform the standard ufunc implementations based on behavior collections for ufunc_str in strip_unit_input_output_ufuncs: # Ignore units implement_func("ufunc", ufunc_str, input_units=None, output_unit=None) for ufunc_str in matching_input_bare_output_ufuncs: # Require all inputs to match units, but output plain ndarray/duck array implement_func("ufunc", ufunc_str, input_units="all_consistent", output_unit=None) for ufunc_str, out_unit in matching_input_set_units_output_ufuncs.items(): # Require all inputs to match units, but output in specified unit implement_func( "ufunc", ufunc_str, input_units="all_consistent", output_unit=out_unit ) for ufunc_str, (in_unit, out_unit) in set_units_ufuncs.items(): # Require inputs in specified unit, and output in specified unit implement_func("ufunc", ufunc_str, input_units=in_unit, output_unit=out_unit) for ufunc_str in matching_input_copy_units_output_ufuncs: # Require all inputs to match units, and output as first unit in arguments implement_func( "ufunc", ufunc_str, input_units="all_consistent", output_unit="match_input" ) for ufunc_str in copy_units_output_ufuncs: # Output as first unit in arguments, but do not convert inputs implement_func("ufunc", ufunc_str, input_units=None, output_unit="match_input") for ufunc_str, unit_op in op_units_output_ufuncs.items(): implement_func("ufunc", ufunc_str, input_units=None, output_unit=unit_op) # Define custom ufunc implementations for atypical cases @implements("modf", "ufunc") def _modf(x, *args, **kwargs): (x,), output_wrap = unwrap_and_wrap_consistent_units(x) return tuple(output_wrap(y) for y in np.modf(x, *args, **kwargs)) @implements("frexp", "ufunc") def _frexp(x, *args, **kwargs): (x,), output_wrap = unwrap_and_wrap_consistent_units(x) mantissa, exponent = np.frexp(x, *args, **kwargs) return output_wrap(mantissa), exponent @implements("power", "ufunc") def _power(x1, x2): if _is_quantity(x1): return x1**x2 return x2.__rpow__(x1) @implements("add", "ufunc") def _add(x1, x2, *args, **kwargs): (x1, x2), output_wrap = unwrap_and_wrap_consistent_units(x1, x2) return output_wrap(np.add(x1, x2, *args, **kwargs)) @implements("subtract", "ufunc") def _subtract(x1, x2, *args, **kwargs): (x1, x2), output_wrap = unwrap_and_wrap_consistent_units(x1, x2) return output_wrap(np.subtract(x1, x2, *args, **kwargs)) # Define custom function implementations @implements("meshgrid", "function") def _meshgrid(*xi, **kwargs): # Simply need to map input units to onto list of outputs input_units = (x.units for x in xi) res = np.meshgrid(*(x.m for x in xi), **kwargs) return [out * unit for out, unit in zip(res, input_units)] @implements("full_like", "function") def _full_like(a, fill_value, **kwargs): # Make full_like by multiplying with array from ones_like in a # non-multiplicative-unit-safe way if hasattr(fill_value, "_REGISTRY"): return fill_value._REGISTRY.Quantity( np.ones_like(a, **kwargs) * fill_value.m, fill_value.units, ) return np.ones_like(a, **kwargs) * fill_value @implements("interp", "function") def _interp(x, xp, fp, left=None, right=None, period=None): # Need to handle x and y units separately (x, xp, period), _ = unwrap_and_wrap_consistent_units(x, xp, period) (fp, right, left), output_wrap = unwrap_and_wrap_consistent_units(fp, left, right) return output_wrap(np.interp(x, xp, fp, left=left, right=right, period=period)) @implements("where", "function") def _where(condition, *args): if not getattr(condition, "_is_multiplicative", True): raise ValueError( "Invalid units of the condition: Boolean value of Quantity with offset unit is ambiguous." ) condition = getattr(condition, "magnitude", condition) args, output_wrap = unwrap_and_wrap_consistent_units(*args) return output_wrap(np.where(condition, *args)) @implements("concatenate", "function") def _concatenate(sequence, *args, **kwargs): sequence, output_wrap = unwrap_and_wrap_consistent_units(*sequence) return output_wrap(np.concatenate(sequence, *args, **kwargs)) @implements("stack", "function") def _stack(arrays, *args, **kwargs): arrays, output_wrap = unwrap_and_wrap_consistent_units(*arrays) return output_wrap(np.stack(arrays, *args, **kwargs)) @implements("unwrap", "function") def _unwrap(p, discont=None, axis=-1): # np.unwrap only dispatches over p argument, so assume it is a Quantity discont = np.pi if discont is None else discont return p._REGISTRY.Quantity(np.unwrap(p.m_as("rad"), discont, axis=axis), "rad").to( p.units ) @implements("copyto", "function") def _copyto(dst, src, casting="same_kind", where=True): if _is_quantity(dst): if _is_quantity(src): src = src.m_as(dst.units) np.copyto(dst._magnitude, src, casting=casting, where=where) else: warnings.warn( "The unit of the quantity is stripped when copying to non-quantity", UnitStrippedWarning, stacklevel=2, ) np.copyto(dst, src.m, casting=casting, where=where) @implements("einsum", "function") def _einsum(subscripts, *operands, **kwargs): operand_magnitudes, _ = convert_to_consistent_units(*operands, pre_calc_units=None) output_unit = get_op_output_unit("mul", _get_first_input_units(operands), operands) return np.einsum(subscripts, *operand_magnitudes, **kwargs) * output_unit @implements("isin", "function") def _isin(element, test_elements, assume_unique=False, invert=False): if not _is_quantity(element): raise ValueError( "Cannot test if unit-aware elements are in not-unit-aware array" ) if _is_quantity(test_elements): try: test_elements = test_elements.m_as(element.units) except DimensionalityError: # Incompatible unit test elements cannot be in element return np.full(element.shape, False) elif _is_sequence_with_quantity_elements(test_elements): compatible_test_elements = [] for test_element in test_elements: if not _is_quantity(test_element): pass try: compatible_test_elements.append(test_element.m_as(element.units)) except DimensionalityError: # Incompatible unit test elements cannot be in element, but others in # sequence may pass test_elements = compatible_test_elements else: # Consider non-quantity like dimensionless quantity if not element.dimensionless: # Unit do not match, so all false return np.full(element.shape, False) else: # Convert to units of element element._REGISTRY.Quantity(test_elements).m_as(element.units) return np.isin(element.m, test_elements, assume_unique=assume_unique, invert=invert) @implements("pad", "function") def _pad(array, pad_width, mode="constant", **kwargs): def _recursive_convert(arg, unit): if iterable(arg): return tuple(_recursive_convert(a, unit=unit) for a in arg) elif not _is_quantity(arg): if arg == 0 or np.isnan(arg): arg = unit._REGISTRY.Quantity(arg, unit) else: arg = unit._REGISTRY.Quantity(arg, "dimensionless") return arg.m_as(unit) # pad only dispatches on array argument, so we know it is a Quantity units = array.units # Handle flexible constant_values and end_values, converting to units if Quantity # and ignoring if not for key in ("constant_values", "end_values"): if key in kwargs: kwargs[key] = _recursive_convert(kwargs[key], units) return units._REGISTRY.Quantity( np.pad(array._magnitude, pad_width, mode=mode, **kwargs), units ) @implements("any", "function") def _any(a, *args, **kwargs): # Only valid when multiplicative unit/no offset if a._is_multiplicative: return np.any(a._magnitude, *args, **kwargs) raise ValueError("Boolean value of Quantity with offset unit is ambiguous.") @implements("all", "function") def _all(a, *args, **kwargs): # Only valid when multiplicative unit/no offset if a._is_multiplicative: return np.all(a._magnitude, *args, **kwargs) else: raise ValueError("Boolean value of Quantity with offset unit is ambiguous.") def implement_prod_func(name): if np is None: return func = getattr(np, name, None) if func is None: return @implements(name, "function") def _prod(a, *args, **kwargs): arg_names = ("axis", "dtype", "out", "keepdims", "initial", "where") all_kwargs = dict(**dict(zip(arg_names, args)), **kwargs) axis = all_kwargs.get("axis", None) where = all_kwargs.get("where", None) registry = a.units._REGISTRY if axis is not None and where is not None: _, where_ = np.broadcast_arrays(a._magnitude, where) exponents = np.unique(np.sum(where_, axis=axis)) if len(exponents) == 1 or (len(exponents) == 2 and 0 in exponents): units = a.units ** np.max(exponents) else: units = registry.dimensionless a = a.to(units) elif axis is not None: units = a.units ** a.shape[axis] elif where is not None: exponent = np.sum(where) units = a.units**exponent else: exponent = ( np.sum(np.logical_not(np.isnan(a))) if name == "nanprod" else a.size ) units = a.units**exponent result = func(a._magnitude, *args, **kwargs) return registry.Quantity(result, units) for name in ("prod", "nanprod"): implement_prod_func(name) # Handle mutliplicative functions separately to deal with non-multiplicative units def _base_unit_if_needed(a): if a._is_multiplicative: return a else: if a.units._REGISTRY.autoconvert_offset_to_baseunit: return a.to_base_units() else: raise OffsetUnitCalculusError(a.units) # NP2 Can remove trapz wrapping when we only support numpy>=2 @implements("trapz", "function") @implements("trapezoid", "function") def _trapz(y, x=None, dx=1.0, **kwargs): trapezoid = np.trapezoid if hasattr(np, "trapezoid") else np.trapz y = _base_unit_if_needed(y) units = y.units if x is not None: if hasattr(x, "units"): x = _base_unit_if_needed(x) units *= x.units x = x._magnitude ret = trapezoid(y._magnitude, x, **kwargs) else: if hasattr(dx, "units"): dx = _base_unit_if_needed(dx) units *= dx.units dx = dx._magnitude ret = trapezoid(y._magnitude, dx=dx, **kwargs) return y.units._REGISTRY.Quantity(ret, units) @implements("correlate", "function") def _correlate(a, v, mode="valid", **kwargs): a = _base_unit_if_needed(a) v = _base_unit_if_needed(v) units = a.units * v.units ret = np.correlate(a._magnitude, v._magnitude, mode=mode, **kwargs) return a.units._REGISTRY.Quantity(ret, units) def implement_mul_func(func): # If NumPy is not available, do not attempt implement that which does not exist if np is None: return func = getattr(np, func_str) @implements(func_str, "function") def implementation(a, b, **kwargs): a = _base_unit_if_needed(a) units = a.units if hasattr(b, "units"): b = _base_unit_if_needed(b) units *= b.units b = b._magnitude mag = func(a._magnitude, b, **kwargs) return a.units._REGISTRY.Quantity(mag, units) for func_str in ("cross", "dot"): implement_mul_func(func_str) # Implement simple matching-unit or stripped-unit functions based on signature def implement_consistent_units_by_argument(func_str, unit_arguments, wrap_output=True): # If NumPy is not available, do not attempt implement that which does not exist if np is None: return if "." not in func_str: func = getattr(np, func_str, None) else: parts = func_str.split(".") module = np for part in parts[:-1]: module = getattr(module, part, None) func = getattr(module, parts[-1], None) # if NumPy does not implement it, do not implement it either if func is None: return @implements(func_str, "function") def implementation(*args, **kwargs): # Bind given arguments to the NumPy function signature bound_args = signature(func).bind(*args, **kwargs) # Skip unit arguments that are supplied as None valid_unit_arguments = [ label for label in unit_arguments if label in bound_args.arguments and bound_args.arguments[label] is not None ] # Unwrap valid unit arguments, ensure consistency, and obtain output wrapper unwrapped_unit_args, output_wrap = unwrap_and_wrap_consistent_units( *(bound_args.arguments[label] for label in valid_unit_arguments) ) # Call NumPy function with updated arguments for i, unwrapped_unit_arg in enumerate(unwrapped_unit_args): bound_args.arguments[valid_unit_arguments[i]] = unwrapped_unit_arg ret = func(*bound_args.args, **bound_args.kwargs) # Conditionally wrap output if wrap_output: return output_wrap(ret) return ret for func_str, unit_arguments, wrap_output in ( ("expand_dims", "a", True), ("squeeze", "a", True), ("rollaxis", "a", True), ("moveaxis", "a", True), ("around", "a", True), ("diagonal", "a", True), ("mean", "a", True), ("ptp", "a", True), ("ravel", "a", True), ("round_", "a", True), ("round", "a", True), ("sort", "a", True), ("median", "a", True), ("nanmedian", "a", True), ("transpose", "a", True), ("roll", "a", True), ("copy", "a", True), ("average", "a", True), ("nanmean", "a", True), ("swapaxes", "a", True), ("nanmin", "a", True), ("nanmax", "a", True), ("percentile", "a", True), ("nanpercentile", "a", True), ("quantile", "a", True), ("nanquantile", "a", True), ("flip", "m", True), ("fix", "x", True), ("trim_zeros", ["filt"], True), ("broadcast_to", ["array"], True), ("amax", ["a", "initial"], True), ("amin", ["a", "initial"], True), ("max", ["a", "initial"], True), ("min", ["a", "initial"], True), ("searchsorted", ["a", "v"], False), ("nan_to_num", ["x", "nan", "posinf", "neginf"], True), ("clip", ["a", "a_min", "a_max"], True), ("append", ["arr", "values"], True), ("compress", "a", True), ("linspace", ["start", "stop"], True), ("tile", "A", True), ("lib.stride_tricks.sliding_window_view", "x", True), ("rot90", "m", True), ("insert", ["arr", "values"], True), ("delete", ["arr"], True), ("resize", "a", True), ("reshape", "a", True), ("intersect1d", ["ar1", "ar2"], True), ): implement_consistent_units_by_argument(func_str, unit_arguments, wrap_output) # implement isclose and allclose def implement_close(func_str): if np is None: return func = getattr(np, func_str) @implements(func_str, "function") def implementation(*args, **kwargs): bound_args = signature(func).bind(*args, **kwargs) labels = ["a", "b"] arrays = {label: bound_args.arguments[label] for label in labels} if "atol" in bound_args.arguments: atol = bound_args.arguments["atol"] a = arrays["a"] if not hasattr(atol, "_REGISTRY") and hasattr(a, "_REGISTRY"): # always use the units of `a` atol_ = a._REGISTRY.Quantity(atol, a.units) else: atol_ = atol arrays["atol"] = atol_ args, _ = unwrap_and_wrap_consistent_units(*arrays.values()) for label, value in zip(arrays.keys(), args): bound_args.arguments[label] = value return func(*bound_args.args, **bound_args.kwargs) for func_str in ("isclose", "allclose"): implement_close(func_str) # Handle atleast_nd functions def implement_atleast_nd(func_str): # If NumPy is not available, do not attempt implement that which does not exist if np is None: return func = getattr(np, func_str) @implements(func_str, "function") def implementation(*arrays): stripped_arrays, _ = convert_to_consistent_units(*arrays) arrays_magnitude = func(*stripped_arrays) if len(arrays) > 1: return [ array_magnitude if not hasattr(original, "_REGISTRY") else original._REGISTRY.Quantity(array_magnitude, original.units) for array_magnitude, original in zip(arrays_magnitude, arrays) ] else: output_unit = arrays[0].units return output_unit._REGISTRY.Quantity(arrays_magnitude, output_unit) for func_str in ("atleast_1d", "atleast_2d", "atleast_3d"): implement_atleast_nd(func_str) # Handle cumulative products (which must be dimensionless for consistent units across # output array) def implement_single_dimensionless_argument_func(func_str): # If NumPy is not available, do not attempt implement that which does not exist if np is None: return func = getattr(np, func_str) @implements(func_str, "function") def implementation(a, *args, **kwargs): (a_stripped,), _ = convert_to_consistent_units( a, pre_calc_units=a._REGISTRY.parse_units("dimensionless") ) return a._REGISTRY.Quantity(func(a_stripped, *args, **kwargs)) for func_str in ("cumprod", "nancumprod"): implement_single_dimensionless_argument_func(func_str) # Handle single-argument consistent unit functions for func_str in ( "block", "hstack", "vstack", "dstack", "column_stack", "broadcast_arrays", ): implement_func( "function", func_str, input_units="all_consistent", output_unit="match_input" ) # Handle functions that ignore units on input and output for func_str in ( "size", "isreal", "iscomplex", "shape", "ones_like", "zeros_like", "empty_like", "argsort", "argmin", "argmax", "ndim", "nanargmax", "nanargmin", "count_nonzero", "nonzero", "result_type", ): implement_func("function", func_str, input_units=None, output_unit=None) # Handle functions with output unit defined by operation for func_str in ( "std", "nanstd", "sum", "nansum", "cumsum", "nancumsum", "linalg.norm", ): implement_func("function", func_str, input_units=None, output_unit="sum") for func_str in ("diff", "ediff1d"): implement_func("function", func_str, input_units=None, output_unit="delta") for func_str in ("gradient",): implement_func("function", func_str, input_units=None, output_unit="delta,div") for func_str in ("linalg.solve",): implement_func("function", func_str, input_units=None, output_unit="invdiv") for func_str in ("var", "nanvar"): implement_func("function", func_str, input_units=None, output_unit="variance") def numpy_wrap(func_type, func, args, kwargs, types): """Return the result from a NumPy function/ufunc as wrapped by Pint.""" if func_type == "function": handled = HANDLED_FUNCTIONS # Need to handle functions in submodules name = ".".join(func.__module__.split(".")[1:] + [func.__name__]) elif func_type == "ufunc": handled = HANDLED_UFUNCS # ufuncs do not have func.__module__ name = func.__name__ else: raise ValueError(f"Invalid func_type {func_type}") if name not in handled or any(is_upcast_type(t) for t in types): return NotImplemented return handled[name](*args, **kwargs) pint-0.24.4/pint/facets/numpy/quantity.py000066400000000000000000000234211471316474000204030ustar00rootroot00000000000000""" pint.facets.numpy.quantity ~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import functools import math import warnings from typing import Any, Generic from ..._typing import Shape from ...compat import HAS_NUMPY, _to_magnitude, np from ...errors import DimensionalityError, PintTypeError, UnitStrippedWarning from ..plain import MagnitudeT, PlainQuantity from .numpy_func import ( HANDLED_UFUNCS, copy_units_output_ufuncs, get_op_output_unit, matching_input_copy_units_output_ufuncs, matching_input_set_units_output_ufuncs, numpy_wrap, op_units_output_ufuncs, set_units_ufuncs, ) try: import uncertainties.unumpy as unp from uncertainties import UFloat, ufloat HAS_UNCERTAINTIES = True except ImportError: unp = np ufloat = Ufloat = None HAS_UNCERTAINTIES = False def method_wraps(numpy_func): if isinstance(numpy_func, str): numpy_func = getattr(np, numpy_func, None) def wrapper(func): func.__wrapped__ = numpy_func return func return wrapper class NumpyQuantity(Generic[MagnitudeT], PlainQuantity[MagnitudeT]): """ """ # NumPy function/ufunc support __array_priority__ = 17 def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): if method != "__call__": # Only handle ufuncs as callables return NotImplemented # Replicate types from __array_function__ types = { type(arg) for arg in list(inputs) + list(kwargs.values()) if hasattr(arg, "__array_ufunc__") } return numpy_wrap("ufunc", ufunc, inputs, kwargs, types) def __array_function__(self, func, types, args, kwargs): return numpy_wrap("function", func, args, kwargs, types) _wrapped_numpy_methods = ["flatten", "astype", "item"] def _numpy_method_wrap(self, func, *args, **kwargs): """Convenience method to wrap on the fly NumPy ndarray methods taking care of the units. """ # Set input units if needed if func.__name__ in set_units_ufuncs: self.__ito_if_needed(set_units_ufuncs[func.__name__][0]) value = func(*args, **kwargs) # Set output units as needed if func.__name__ in ( matching_input_copy_units_output_ufuncs + copy_units_output_ufuncs + self._wrapped_numpy_methods ): output_unit = self._units elif func.__name__ in set_units_ufuncs: output_unit = set_units_ufuncs[func.__name__][1] elif func.__name__ in matching_input_set_units_output_ufuncs: output_unit = matching_input_set_units_output_ufuncs[func.__name__] elif func.__name__ in op_units_output_ufuncs: output_unit = get_op_output_unit( op_units_output_ufuncs[func.__name__], self.units, list(args) + list(kwargs.values()), self._magnitude.size, ) else: output_unit = None if output_unit is not None: return self.__class__(value, output_unit) return value def __array__(self, t=None) -> np.ndarray: if HAS_NUMPY and isinstance(self._magnitude, np.ndarray): warnings.warn( "The unit of the quantity is stripped when downcasting to ndarray.", UnitStrippedWarning, stacklevel=2, ) return _to_magnitude(self._magnitude, force_ndarray=True) def clip(self, min=None, max=None, out=None, **kwargs): if min is not None: if isinstance(min, self.__class__): min = min.to(self).magnitude elif self.dimensionless: pass else: raise DimensionalityError("dimensionless", self._units) if max is not None: if isinstance(max, self.__class__): max = max.to(self).magnitude elif self.dimensionless: pass else: raise DimensionalityError("dimensionless", self._units) return self.__class__(self.magnitude.clip(min, max, out, **kwargs), self._units) def fill(self: NumpyQuantity, value) -> None: self._units = value._units return self.magnitude.fill(value.magnitude) def put(self: NumpyQuantity, indices, values, mode="raise") -> None: if isinstance(values, self.__class__): values = values.to(self).magnitude elif self.dimensionless: values = self.__class__(values, "").to(self) else: raise DimensionalityError("dimensionless", self._units) self.magnitude.put(indices, values, mode) @property def real(self) -> NumpyQuantity: return self.__class__(self._magnitude.real, self._units) @property def imag(self) -> NumpyQuantity: return self.__class__(self._magnitude.imag, self._units) @property def T(self): return self.__class__(self._magnitude.T, self._units) @property def flat(self): for v in self._magnitude.flat: yield self.__class__(v, self._units) @property def shape(self) -> Shape: return self._magnitude.shape @property def dtype(self): return self._magnitude.dtype @shape.setter def shape(self, value): self._magnitude.shape = value def searchsorted(self, v, side="left", sorter=None): if isinstance(v, self.__class__): v = v.to(self).magnitude elif self.dimensionless: v = self.__class__(v, "").to(self) else: raise DimensionalityError("dimensionless", self._units) return self.magnitude.searchsorted(v, side) def dot(self, b): """Dot product of two arrays. Wraps np.dot(). """ return np.dot(self, b) @method_wraps("prod") def prod(self, *args, **kwargs): """Return the product of quantity elements over a given axis Wraps np.prod(). """ return np.prod(self, *args, **kwargs) def __ito_if_needed(self, to_units): if self.unitless and to_units == "radian": return self.ito(to_units) def __len__(self) -> int: return len(self._magnitude) def __getattr__(self, item) -> Any: if item.startswith("__array_"): # Handle array protocol attributes other than `__array__` raise AttributeError(f"Array protocol attribute {item} not available.") elif item in HANDLED_UFUNCS or item in self._wrapped_numpy_methods: magnitude_as_duck_array = _to_magnitude( self._magnitude, force_ndarray_like=True ) try: attr = getattr(magnitude_as_duck_array, item) return functools.partial(self._numpy_method_wrap, attr) except AttributeError: raise AttributeError( f"NumPy method {item} not available on {type(magnitude_as_duck_array)}" ) except TypeError as exc: if "not callable" in str(exc): raise AttributeError( f"NumPy method {item} not callable on {type(magnitude_as_duck_array)}" ) else: raise exc elif ( HAS_UNCERTAINTIES and item == "ndim" and isinstance(self._magnitude, UFloat) ): # Dimensionality of a single UFloat is 0, like any other scalar return 0 try: return getattr(self._magnitude, item) except AttributeError: raise AttributeError( "Neither Quantity object nor its magnitude ({}) " "has attribute '{}'".format(self._magnitude, item) ) def __getitem__(self, key): try: return type(self)(self._magnitude[key], self._units) except PintTypeError: raise except TypeError: raise TypeError( "Neither Quantity object nor its magnitude ({})" "supports indexing".format(self._magnitude) ) def __setitem__(self, key, value): try: # If we're dealing with a masked single value or a nan, set it if ( isinstance(self._magnitude, np.ma.MaskedArray) and np.ma.is_masked(value) and getattr(value, "size", 0) == 1 ) or (getattr(value, "ndim", 0) == 0 and math.isnan(value)): self._magnitude[key] = value return except TypeError: pass try: if isinstance(value, self.__class__): factor = self.__class__( value.magnitude, value._units / self._units ).to_root_units() else: factor = self.__class__(value, self._units ** (-1)).to_root_units() if isinstance(factor, self.__class__): if not factor.dimensionless: raise DimensionalityError( value, self.units, extra_msg=". Assign a quantity with the same dimensionality " "or access the magnitude directly as " f"`obj.magnitude[{key}] = {value}`.", ) self._magnitude[key] = factor.magnitude else: self._magnitude[key] = factor except PintTypeError: raise except TypeError as exc: raise TypeError( f"Neither Quantity object nor its magnitude ({self._magnitude}) " "supports indexing" ) from exc pint-0.24.4/pint/facets/numpy/registry.py000066400000000000000000000012471471316474000203770ustar00rootroot00000000000000""" pint.facets.numpy.registry ~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from typing import Any, Generic from ...compat import TypeAlias from ..plain import GenericPlainRegistry, QuantityT, UnitT from .quantity import NumpyQuantity from .unit import NumpyUnit class GenericNumpyRegistry( Generic[QuantityT, UnitT], GenericPlainRegistry[QuantityT, UnitT] ): pass class NumpyRegistry(GenericPlainRegistry[NumpyQuantity[Any], NumpyUnit]): Quantity: TypeAlias = NumpyQuantity[Any] Unit: TypeAlias = NumpyUnit pint-0.24.4/pint/facets/numpy/unit.py000066400000000000000000000024201471316474000175000ustar00rootroot00000000000000""" pint.facets.numpy.unit ~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from ...compat import is_upcast_type from ..plain import PlainUnit class NumpyUnit(PlainUnit): __array_priority__ = 17 def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): if method != "__call__": # Only handle ufuncs as callables return NotImplemented # Check types and return NotImplemented when upcast type encountered types = { type(arg) for arg in list(inputs) + list(kwargs.values()) if hasattr(arg, "__array_ufunc__") } if any(is_upcast_type(other) for other in types): return NotImplemented # Act on limited implementations by conversion to multiplicative identity # Quantity if ufunc.__name__ in ("true_divide", "divide", "floor_divide", "multiply"): return ufunc( *tuple( self._REGISTRY.Quantity(1, self._units) if arg is self else arg for arg in inputs ), **kwargs, ) return NotImplemented pint-0.24.4/pint/facets/plain/000077500000000000000000000000001471316474000161045ustar00rootroot00000000000000pint-0.24.4/pint/facets/plain/__init__.py000066400000000000000000000015561471316474000202240ustar00rootroot00000000000000""" pint.facets.plain ~~~~~~~~~~~~~~~~~ Base implementation for registry, units and quantities. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .definitions import ( AliasDefinition, DefaultsDefinition, DimensionDefinition, PrefixDefinition, ScaleConverter, UnitDefinition, ) from .objects import PlainQuantity, PlainUnit from .quantity import MagnitudeT from .registry import GenericPlainRegistry, PlainRegistry, QuantityT, UnitT __all__ = [ "GenericPlainRegistry", "PlainUnit", "PlainQuantity", "PlainRegistry", "AliasDefinition", "DefaultsDefinition", "DimensionDefinition", "PrefixDefinition", "ScaleConverter", "UnitDefinition", "QuantityT", "UnitT", "MagnitudeT", ] pint-0.24.4/pint/facets/plain/definitions.py000066400000000000000000000207431471316474000207770ustar00rootroot00000000000000""" pint.facets.plain.definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import itertools import numbers import typing as ty from dataclasses import dataclass from functools import cached_property from typing import Any from ... import errors from ..._typing import Magnitude from ...converters import Converter from ...util import UnitsContainer class NotNumeric(Exception): """Internal exception. Do not expose outside Pint""" def __init__(self, value: Any): self.value = value ######################## # Convenience functions ######################## @dataclass(frozen=True) class Equality: """An equality statement contains a left and right hand separated by and equal (=) sign. lhs = rhs lhs and rhs are space stripped. """ lhs: str rhs: str @dataclass(frozen=True) class CommentDefinition: """A comment""" comment: str @dataclass(frozen=True) class DefaultsDefinition: """Directive to store default values.""" group: ty.Optional[str] system: ty.Optional[str] def items(self): if self.group is not None: yield "group", self.group if self.system is not None: yield "system", self.system @dataclass(frozen=True) class NamedDefinition: #: name of the prefix name: str @dataclass(frozen=True) class PrefixDefinition(NamedDefinition, errors.WithDefErr): """Definition of a prefix.""" #: scaling value for this prefix value: numbers.Number #: canonical symbol defined_symbol: str | None = "" #: additional names for the same prefix aliases: ty.Tuple[str, ...] = () @property def symbol(self) -> str: return self.defined_symbol or self.name @property def has_symbol(self) -> bool: return bool(self.defined_symbol) @cached_property def converter(self) -> ScaleConverter: return ScaleConverter(self.value) def __post_init__(self): if not errors.is_valid_prefix_name(self.name): raise self.def_err(errors.MSG_INVALID_PREFIX_NAME) if self.defined_symbol and not errors.is_valid_prefix_symbol(self.name): raise self.def_err( f"the symbol {self.defined_symbol} " + errors.MSG_INVALID_PREFIX_SYMBOL ) for alias in self.aliases: if not errors.is_valid_prefix_alias(alias): raise self.def_err( f"the alias {alias} " + errors.MSG_INVALID_PREFIX_ALIAS ) @dataclass(frozen=True) class UnitDefinition(NamedDefinition, errors.WithDefErr): """Definition of a unit.""" #: canonical symbol defined_symbol: str | None #: additional names for the same unit aliases: tuple[str, ...] #: A functiont that converts a value in these units into the reference units # TODO: this has changed as converter is now annotated as converter. # Briefly, in several places converter attributes like as_multiplicative were # accesed. So having a generic function is a no go. # I guess this was never used as errors where not raised. converter: Converter | None #: Reference units. reference: UnitsContainer | None def __post_init__(self): if not errors.is_valid_unit_name(self.name): raise self.def_err(errors.MSG_INVALID_UNIT_NAME) # TODO: check why reference: Optional[UnitsContainer] assert isinstance(self.reference, UnitsContainer) if not any(map(errors.is_dim, self.reference.keys())): invalid = tuple( itertools.filterfalse(errors.is_valid_unit_name, self.reference.keys()) ) if invalid: raise self.def_err( f"refers to {', '.join(invalid)} that " + errors.MSG_INVALID_UNIT_NAME ) is_base = False elif all(map(errors.is_dim, self.reference.keys())): invalid = tuple( itertools.filterfalse( errors.is_valid_dimension_name, self.reference.keys() ) ) if invalid: raise self.def_err( f"refers to {', '.join(invalid)} that " + errors.MSG_INVALID_DIMENSION_NAME ) is_base = True scale = getattr(self.converter, "scale", 1) if scale != 1: return self.def_err( "Base unit definitions cannot have a scale different to 1. " f"(`{scale}` found)" ) else: raise self.def_err( "Cannot mix dimensions and units in the same definition. " "Base units must be referenced only to dimensions. " "Derived units must be referenced only to units." ) super.__setattr__(self, "_is_base", is_base) if self.defined_symbol and not errors.is_valid_unit_symbol(self.name): raise self.def_err( f"the symbol {self.defined_symbol} " + errors.MSG_INVALID_UNIT_SYMBOL ) for alias in self.aliases: if not errors.is_valid_unit_alias(alias): raise self.def_err( f"the alias {alias} " + errors.MSG_INVALID_UNIT_ALIAS ) @property def is_base(self) -> bool: """Indicates if it is a base unit.""" # TODO: This is set in __post_init__ return self._is_base @property def is_multiplicative(self) -> bool: # TODO: Check how to avoid this check assert isinstance(self.converter, Converter) return self.converter.is_multiplicative @property def is_logarithmic(self) -> bool: # TODO: Check how to avoid this check assert isinstance(self.converter, Converter) return self.converter.is_logarithmic @property def symbol(self) -> str: return self.defined_symbol or self.name @property def has_symbol(self) -> bool: return bool(self.defined_symbol) @dataclass(frozen=True) class DimensionDefinition(NamedDefinition, errors.WithDefErr): """Definition of a root dimension""" @property def is_base(self) -> bool: return True def __post_init__(self) -> None: if not errors.is_valid_dimension_name(self.name): raise self.def_err(errors.MSG_INVALID_DIMENSION_NAME) @dataclass(frozen=True) class DerivedDimensionDefinition(DimensionDefinition): """Definition of a derived dimension.""" #: reference dimensions. reference: UnitsContainer @property def is_base(self) -> bool: return False def __post_init__(self): if not errors.is_valid_dimension_name(self.name): raise self.def_err(errors.MSG_INVALID_DIMENSION_NAME) if not all(map(errors.is_dim, self.reference.keys())): return self.def_err( "derived dimensions must only reference other dimensions" ) invalid = tuple( itertools.filterfalse(errors.is_valid_dimension_name, self.reference.keys()) ) if invalid: raise self.def_err( f"refers to {', '.join(invalid)} that " + errors.MSG_INVALID_DIMENSION_NAME ) @dataclass(frozen=True) class AliasDefinition(errors.WithDefErr): """Additional alias(es) for an already existing unit.""" #: name of the already existing unit name: str #: aditional names for the same unit aliases: ty.Tuple[str, ...] def __post_init__(self): if not errors.is_valid_unit_name(self.name): raise self.def_err(errors.MSG_INVALID_UNIT_NAME) for alias in self.aliases: if not errors.is_valid_unit_alias(alias): raise self.def_err( f"the alias {alias} " + errors.MSG_INVALID_UNIT_ALIAS ) @dataclass(frozen=True) class ScaleConverter(Converter): """A linear transformation without offset.""" scale: float def to_reference(self, value: Magnitude, inplace: bool = False) -> Magnitude: if inplace: value *= self.scale else: value = value * self.scale return value def from_reference(self, value: Magnitude, inplace: bool = False) -> Magnitude: if inplace: value /= self.scale else: value = value / self.scale return value pint-0.24.4/pint/facets/plain/objects.py000066400000000000000000000005521471316474000201110ustar00rootroot00000000000000""" pint.facets.plain.objects ~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .quantity import PlainQuantity from .unit import PlainUnit, UnitsContainer __all__ = ["PlainUnit", "PlainQuantity", "UnitsContainer"] pint-0.24.4/pint/facets/plain/qto.py000066400000000000000000000345421471316474000172710ustar00rootroot00000000000000from __future__ import annotations import bisect import math import numbers import warnings from typing import TYPE_CHECKING from ...compat import ( mip_INF, mip_INTEGER, mip_Model, mip_model, mip_OptimizationStatus, mip_xsum, ) from ...errors import UndefinedBehavior from ...util import infer_base_unit if TYPE_CHECKING: from ..._typing import UnitLike from ...util import UnitsContainer from .quantity import PlainQuantity def _get_reduced_units( quantity: PlainQuantity, units: UnitsContainer ) -> UnitsContainer: # loop through individual units and compare to each other unit # can we do better than a nested loop here? for unit1, exp in units.items(): # make sure it wasn't already reduced to zero exponent on prior pass if unit1 not in units: continue for unit2 in units: # get exponent after reduction exp = units[unit1] if unit1 != unit2: power = quantity._REGISTRY._get_dimensionality_ratio(unit1, unit2) if power: units = units.add(unit2, exp / power).remove([unit1]) break return units def ito_reduced_units(quantity: PlainQuantity) -> None: """Return PlainQuantity scaled in place to reduced units, i.e. one unit per dimension. This will not reduce compound units (e.g., 'J/kg' will not be reduced to m**2/s**2), nor can it make use of contexts at this time. """ # shortcuts in case we're dimensionless or only a single unit if quantity.dimensionless: return quantity.ito({}) if len(quantity._units) == 1: return None units = quantity._units.copy() new_units = _get_reduced_units(quantity, units) return quantity.ito(new_units) def to_reduced_units( quantity: PlainQuantity, ) -> PlainQuantity: """Return PlainQuantity scaled in place to reduced units, i.e. one unit per dimension. This will not reduce compound units (intentionally), nor can it make use of contexts at this time. """ # shortcuts in case we're dimensionless or only a single unit if quantity.dimensionless: return quantity.to({}) if len(quantity._units) == 1: return quantity units = quantity._units.copy() new_units = _get_reduced_units(quantity, units) return quantity.to(new_units) def to_compact( quantity: PlainQuantity, unit: UnitsContainer | None = None ) -> PlainQuantity: """ "Return PlainQuantity rescaled to compact, human-readable units. To get output in terms of a different unit, use the unit parameter. Examples -------- >>> import pint >>> ureg = pint.UnitRegistry() >>> (200e-9*ureg.s).to_compact() >>> (1e-2*ureg('kg m/s^2')).to_compact('N') """ if not isinstance(quantity.magnitude, numbers.Number) and not hasattr( quantity.magnitude, "nominal_value" ): warnings.warn( "to_compact applied to non numerical types has an undefined behavior.", UndefinedBehavior, stacklevel=2, ) return quantity if ( quantity.unitless or quantity.magnitude == 0 or math.isnan(quantity.magnitude) or math.isinf(quantity.magnitude) ): return quantity SI_prefixes: dict[int, str] = {} for prefix in quantity._REGISTRY._prefixes.values(): try: scale = prefix.converter.scale # Kludgy way to check if this is an SI prefix log10_scale = int(math.log10(scale)) if log10_scale == math.log10(scale): SI_prefixes[log10_scale] = prefix.name except Exception: SI_prefixes[0] = "" SI_prefixes_list = sorted(SI_prefixes.items()) SI_powers = [item[0] for item in SI_prefixes_list] SI_bases = [item[1] for item in SI_prefixes_list] if unit is None: unit = infer_base_unit(quantity, registry=quantity._REGISTRY) else: unit = infer_base_unit(quantity.__class__(1, unit), registry=quantity._REGISTRY) q_base = quantity.to(unit) magnitude = q_base.magnitude # Support uncertainties if hasattr(magnitude, "nominal_value"): magnitude = magnitude.nominal_value units = list(q_base._units.items()) units_numerator = [a for a in units if a[1] > 0] if len(units_numerator) > 0: unit_str, unit_power = units_numerator[0] else: unit_str, unit_power = units[0] if unit_power > 0: power = math.floor(math.log10(abs(magnitude)) / float(unit_power) / 3) * 3 else: power = math.ceil(math.log10(abs(magnitude)) / float(unit_power) / 3) * 3 index = bisect.bisect_left(SI_powers, power) if index >= len(SI_bases): index = -1 prefix_str = SI_bases[index] new_unit_str = prefix_str + unit_str new_unit_container = q_base._units.rename(unit_str, new_unit_str) return quantity.to(new_unit_container) def to_preferred( quantity: PlainQuantity, preferred_units: list[UnitLike] | None = None ) -> PlainQuantity: """Return Quantity converted to a unit composed of the preferred units. Examples -------- >>> import pint >>> ureg = pint.UnitRegistry() >>> (1*ureg.acre).to_preferred([ureg.meters]) >>> (1*(ureg.force_pound*ureg.m)).to_preferred([ureg.W]) """ units = _get_preferred(quantity, preferred_units) return quantity.to(units) def ito_preferred( quantity: PlainQuantity, preferred_units: list[UnitLike] | None = None ) -> PlainQuantity: """Return Quantity converted to a unit composed of the preferred units. Examples -------- >>> import pint >>> ureg = pint.UnitRegistry() >>> (1*ureg.acre).to_preferred([ureg.meters]) >>> (1*(ureg.force_pound*ureg.m)).to_preferred([ureg.W]) """ units = _get_preferred(quantity, preferred_units) return quantity.ito(units) def _get_preferred( quantity: PlainQuantity, preferred_units: list[UnitLike] | None = None ) -> PlainQuantity: if preferred_units is None: preferred_units = quantity._REGISTRY.default_preferred_units if not quantity.dimensionality: return quantity._units.copy() # The optimizer isn't perfect, and will sometimes miss obvious solutions. # This sub-algorithm is less powerful, but always finds the very simple solutions. def find_simple(): best_ratio = None best_unit = None self_dims = sorted(quantity.dimensionality) self_exps = [quantity.dimensionality[d] for d in self_dims] s_exps_head, *s_exps_tail = self_exps n = len(s_exps_tail) for preferred_unit in preferred_units: dims = sorted(preferred_unit.dimensionality) if dims == self_dims: p_exps_head, *p_exps_tail = ( preferred_unit.dimensionality[d] for d in dims ) if all( s_exps_tail[i] * p_exps_head == p_exps_tail[i] ** s_exps_head for i in range(n) ): ratio = p_exps_head / s_exps_head ratio = max(ratio, 1 / ratio) if best_ratio is None or ratio < best_ratio: best_ratio = ratio best_unit = preferred_unit ** (s_exps_head / p_exps_head) return best_unit simple = find_simple() if simple is not None: return simple # For each dimension (e.g. T(ime), L(ength), M(ass)), assign a default base unit from # the collection of base units unit_selections = { base_unit.dimensionality: base_unit for base_unit in map(quantity._REGISTRY.Unit, quantity._REGISTRY._base_units) } # Override the default unit of each dimension with the 1D-units used in this Quantity unit_selections.update( { unit.dimensionality: unit for unit in map(quantity._REGISTRY.Unit, quantity._units.keys()) } ) # Determine the preferred unit for each dimensionality from the preferred_units # (A prefered unit doesn't have to be only one dimensional, e.g. Watts) preferred_dims = { preferred_unit.dimensionality: preferred_unit for preferred_unit in map(quantity._REGISTRY.Unit, preferred_units) } # Combine the defaults and preferred, favoring the preferred unit_selections.update(preferred_dims) # This algorithm has poor asymptotic time complexity, so first reduce the considered # dimensions and units to only those that are useful to the problem # The dimensions (without powers) of this Quantity dimension_set = set(quantity.dimensionality) # Getting zero exponents in dimensions not in dimension_set can be facilitated # by units that interact with that dimension and one or more dimension_set members. # For example MT^1 * LT^-1 lets you get MLT^0 when T is not in dimension_set. # For each candidate unit that interacts with a dimension_set member, add the # candidate unit's other dimensions to dimension_set, and repeat until no more # dimensions are selected. discovery_done = False while not discovery_done: discovery_done = True for d in unit_selections: unit_dimensions = set(d) intersection = unit_dimensions.intersection(dimension_set) if 0 < len(intersection) < len(unit_dimensions): # there are dimensions in this unit that are in dimension set # and others that are not in dimension set dimension_set = dimension_set.union(unit_dimensions) discovery_done = False break # filter out dimensions and their unit selections that don't interact with any # dimension_set members unit_selections = { dimensionality: unit for dimensionality, unit in unit_selections.items() if set(dimensionality).intersection(dimension_set) } # update preferred_units with the selected units that were originally preferred preferred_units = list( {u for d, u in unit_selections.items() if d in preferred_dims} ) preferred_units.sort(key=str) # for determinism # and unpreferred_units are the selected units that weren't originally preferred unpreferred_units = list( {u for d, u in unit_selections.items() if d not in preferred_dims} ) unpreferred_units.sort(key=str) # for determinism # for indexability dimensions = list(dimension_set) dimensions.sort() # for determinism # the powers for each elemet of dimensions (the list) for this Quantity dimensionality = [quantity.dimensionality[dimension] for dimension in dimensions] # Now that the input data is minimized, setup the optimization problem # use mip to select units from preferred units model = mip_Model() model.verbose = 0 # Make one variable for each candidate unit vars = [ model.add_var(str(unit), lb=-mip_INF, ub=mip_INF, var_type=mip_INTEGER) for unit in (preferred_units + unpreferred_units) ] # where [u1 ... uN] are powers of N candidate units (vars) # and [d1(uI) ... dK(uI)] are the K dimensional exponents of candidate unit I # and [t1 ... tK] are the dimensional exponents of the quantity (quantity) # create the following constraints # # ⎡ d1(u1) ⋯ dK(u1) ⎤ # [ u1 ⋯ uN ] * ⎢ ⋮ ⋱ ⎢ = [ t1 ⋯ tK ] # ⎣ d1(uN) dK(uN) ⎦ # # in English, the units we choose, and their exponents, when combined, must have the # target dimensionality matrix = [ [preferred_unit.dimensionality[dimension] for dimension in dimensions] for preferred_unit in (preferred_units + unpreferred_units) ] # Do the matrix multiplication with mip_model.xsum for performance and create constraints for i in range(len(dimensions)): dot = mip_model.xsum([var * vector[i] for var, vector in zip(vars, matrix)]) # add constraint to the model model += dot == dimensionality[i] # where [c1 ... cN] are costs, 1 when a preferred variable, and a large value when not # minimize sum(abs(u1) * c1 ... abs(uN) * cN) # linearize the optimization variable via a proxy objective = model.add_var("objective", lb=0, ub=mip_INF, var_type=mip_INTEGER) # Constrain the objective to be equal to the sums of the absolute values of the preferred # unit powers. Do this by making a separate constraint for each permutation of signedness. # Also apply the cost coefficient, which causes the output to prefer the preferred units # prefer units that interact with fewer dimensions cost = [len(p.dimensionality) for p in preferred_units] # set the cost for non preferred units to a higher number bias = ( max(map(abs, dimensionality)) * max((1, *cost)) * 10 ) # arbitrary, just needs to be larger cost.extend([bias] * len(unpreferred_units)) for i in range(1 << len(vars)): sum = mip_xsum( [ (-1 if i & 1 << (len(vars) - j - 1) else 1) * cost[j] * var for j, var in enumerate(vars) ] ) model += objective >= sum model.objective = objective # run the mips minimizer and extract the result if successful if model.optimize() == mip_OptimizationStatus.OPTIMAL: optimal_units = [] min_objective = float("inf") for i in range(model.num_solutions): if model.objective_values[i] < min_objective: min_objective = model.objective_values[i] optimal_units.clear() elif model.objective_values[i] > min_objective: continue temp_unit = quantity._REGISTRY.Unit("") for var in vars: if var.xi(i): temp_unit *= quantity._REGISTRY.Unit(var.name) ** var.xi(i) optimal_units.append(temp_unit) sorting_keys = {tuple(sorted(unit._units)): unit for unit in optimal_units} min_key = sorted(sorting_keys)[0] result_unit = sorting_keys[min_key] return result_unit # for whatever reason, a solution wasn't found # return the original quantity return quantity._units.copy() pint-0.24.4/pint/facets/plain/quantity.py000066400000000000000000001522221471316474000203400ustar00rootroot00000000000000""" pint.facets.plain.quantity ~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import copy import datetime import locale import numbers import operator from collections.abc import Callable, Iterator, Sequence from typing import ( TYPE_CHECKING, Any, Generic, Iterable, TypeVar, overload, ) from ..._typing import Magnitude, QuantityOrUnitLike, Scalar, UnitLike from ...compat import ( HAS_NUMPY, _to_magnitude, deprecated, eq, is_duck_array_type, is_upcast_type, np, zero_or_nan, ) from ...errors import DimensionalityError, OffsetUnitCalculusError, PintTypeError from ...util import ( PrettyIPython, SharedRegistryObject, UnitsContainer, logger, to_units_container, ) from . import qto from .definitions import UnitDefinition if TYPE_CHECKING: from ..context import Context from .unit import PlainUnit as Unit from .unit import UnitsContainer as UnitsContainerT if HAS_NUMPY: import numpy as np # noqa try: import uncertainties.unumpy as unp from uncertainties import UFloat, ufloat HAS_UNCERTAINTIES = True except ImportError: unp = np ufloat = Ufloat = None HAS_UNCERTAINTIES = False MagnitudeT = TypeVar("MagnitudeT", bound=Magnitude) ScalarT = TypeVar("ScalarT", bound=Scalar) T = TypeVar("T", bound=Magnitude) def ireduce_dimensions(f): def wrapped(self, *args, **kwargs): result = f(self, *args, **kwargs) try: if result._REGISTRY.autoconvert_to_preferred: result.ito_preferred() except AttributeError: pass try: if result._REGISTRY.auto_reduce_dimensions: result.ito_reduced_units() except AttributeError: pass return result return wrapped def check_implemented(f): def wrapped(self, *args, **kwargs): other = args[0] if is_upcast_type(type(other)): return NotImplemented # pandas often gets to arrays of quantities [ Q_(1,"m"), Q_(2,"m")] # and expects PlainQuantity * array[PlainQuantity] should return NotImplemented elif isinstance(other, list) and other and isinstance(other[0], type(self)): return NotImplemented return f(self, *args, **kwargs) return wrapped def method_wraps(numpy_func): if isinstance(numpy_func, str): numpy_func = getattr(np, numpy_func, None) def wrapper(func): func.__wrapped__ = numpy_func return func return wrapper # TODO: remove all nonmultiplicative remnants class PlainQuantity(Generic[MagnitudeT], PrettyIPython, SharedRegistryObject): """Implements a class to describe a physical quantity: the product of a numerical value and a unit of measurement. Parameters ---------- value : str, pint.PlainQuantity or any numeric type Value of the physical quantity to be created. units : UnitsContainer, str or pint.PlainQuantity Units of the physical quantity to be created. Returns ------- """ _magnitude: MagnitudeT @property def ndim(self) -> int: if isinstance(self.magnitude, numbers.Number): return 0 if str(type(self.magnitude)) == "NAType": return 0 return self.magnitude.ndim @property def force_ndarray(self) -> bool: return self._REGISTRY.force_ndarray @property def force_ndarray_like(self) -> bool: return self._REGISTRY.force_ndarray_like def __reduce__(self) -> tuple[type, Magnitude, UnitsContainer]: """Allow pickling quantities. Since UnitRegistries are not pickled, upon unpickling the new object is always attached to the application registry. """ from pint import _unpickle_quantity # Note: type(self) would be a mistake as subclasses built by # dinamically can't be pickled # TODO: Check if this is still the case. return _unpickle_quantity, (PlainQuantity, self.magnitude, self._units) @overload def __new__( cls, value: MagnitudeT, units: UnitLike | None = None ) -> PlainQuantity[MagnitudeT]: ... @overload def __new__(cls, value: str, units: UnitLike | None = None) -> PlainQuantity[Any]: ... @overload def __new__( # type: ignore[misc] cls, value: Sequence[ScalarT], units: UnitLike | None = None ) -> PlainQuantity[Any]: ... @overload def __new__( cls, value: PlainQuantity[Any], units: UnitLike | None = None ) -> PlainQuantity[Any]: ... def __new__(cls, value, units=None): if is_upcast_type(type(value)): raise TypeError(f"PlainQuantity cannot wrap upcast type {type(value)}") if units is None and isinstance(value, str) and value == "": raise ValueError( "Expression to parse as PlainQuantity cannot be an empty string." ) if units is None and isinstance(value, str): ureg = SharedRegistryObject.__new__(cls)._REGISTRY inst = ureg.parse_expression(value) return cls.__new__(cls, inst) if units is None and isinstance(value, cls): return copy.copy(value) inst = SharedRegistryObject().__new__(cls) if units is None: units = inst.UnitsContainer() else: if isinstance(units, (UnitsContainer, UnitDefinition)): units = units elif isinstance(units, str): units = inst._REGISTRY.parse_units(units)._units elif isinstance(units, SharedRegistryObject): if isinstance(units, PlainQuantity) and units.magnitude != 1: units = copy.copy(units)._units logger.warning( "Creating new PlainQuantity using a non unity PlainQuantity as units." ) else: units = units._units else: raise TypeError( "units must be of type str, PlainQuantity or " "UnitsContainer; not {}.".format(type(units)) ) if isinstance(value, cls): magnitude = value.to(units)._magnitude else: magnitude = _to_magnitude( value, inst.force_ndarray, inst.force_ndarray_like ) inst._magnitude = magnitude inst._units = units return inst def __iter__(self: PlainQuantity[MagnitudeT]) -> Iterator[Any]: # Make sure that, if self.magnitude is not iterable, we raise TypeError as soon # as one calls iter(self) without waiting for the first element to be drawn from # the iterator it_magnitude = iter(self.magnitude) def it_outer(): for element in it_magnitude: yield self.__class__(element, self._units) return it_outer() def __copy__(self) -> PlainQuantity[MagnitudeT]: ret = self.__class__(copy.copy(self._magnitude), self._units) return ret def __deepcopy__(self, memo) -> PlainQuantity[MagnitudeT]: ret = self.__class__( copy.deepcopy(self._magnitude, memo), copy.deepcopy(self._units, memo) ) return ret @deprecated( "This function will be removed in future versions of pint.\n" "Use ureg.formatter.format_quantity_babel" ) def format_babel(self, spec: str = "", **kwspec: Any) -> str: return self._REGISTRY.formatter.format_quantity_babel(self, spec, **kwspec) def __format__(self, spec: str) -> str: return self._REGISTRY.formatter.format_quantity(self, spec) def __str__(self) -> str: return self._REGISTRY.formatter.format_quantity(self) def __bytes__(self) -> bytes: return str(self).encode(locale.getpreferredencoding()) def __repr__(self) -> str: if HAS_UNCERTAINTIES: if isinstance(self._magnitude, UFloat): return f"" else: return f"" elif isinstance(self._magnitude, float): return f"" return f"" def __hash__(self) -> int: self_base = self.to_base_units() if self_base.dimensionless: return hash(self_base.magnitude) return hash((self_base.__class__, self_base.magnitude, self_base.units)) @property def magnitude(self) -> MagnitudeT: """PlainQuantity's magnitude. Long form for `m`""" return self._magnitude @property def m(self) -> MagnitudeT: """PlainQuantity's magnitude. Short form for `magnitude`""" return self._magnitude def m_as(self, units) -> MagnitudeT: """PlainQuantity's magnitude expressed in particular units. Parameters ---------- units : pint.PlainQuantity, str or dict destination units Returns ------- """ return self.to(units).magnitude @property def units(self) -> Unit: """PlainQuantity's units. Long form for `u`""" return self._REGISTRY.Unit(self._units) @property def u(self) -> Unit: """PlainQuantity's units. Short form for `units`""" return self._REGISTRY.Unit(self._units) @property def unitless(self) -> bool: """ """ return not bool(self.to_root_units()._units) def unit_items(self) -> Iterable[tuple[str, Scalar]]: """A view of the unit items.""" return self._units.unit_items() @property def dimensionless(self) -> bool: """ """ tmp = self.to_root_units() return not bool(tmp.dimensionality) _dimensionality: UnitsContainerT | None = None @property def dimensionality(self) -> UnitsContainerT: """ Returns ------- dict Dimensionality of the PlainQuantity, e.g. ``{length: 1, time: -1}`` """ if self._dimensionality is None: self._dimensionality = self._REGISTRY._get_dimensionality(self._units) return self._dimensionality def check(self, dimension: UnitLike) -> bool: """Return true if the quantity's dimension matches passed dimension.""" return self.dimensionality == self._REGISTRY.get_dimensionality(dimension) @classmethod def from_list( cls, quant_list: list[PlainQuantity[MagnitudeT]], units=None ) -> PlainQuantity[MagnitudeT]: """Transforms a list of Quantities into an numpy.array quantity. If no units are specified, the unit of the first element will be used. Same as from_sequence. If units is not specified and list is empty, the unit cannot be determined and a ValueError is raised. Parameters ---------- quant_list : list of pint.PlainQuantity list of pint.PlainQuantity units : UnitsContainer, str or pint.PlainQuantity units of the physical quantity to be created (Default value = None) Returns ------- pint.PlainQuantity """ return cls.from_sequence(quant_list, units=units) @classmethod def from_sequence( cls, seq: Sequence[PlainQuantity[MagnitudeT]], units=None ) -> PlainQuantity[MagnitudeT]: """Transforms a sequence of Quantities into an numpy.array quantity. If no units are specified, the unit of the first element will be used. If units is not specified and sequence is empty, the unit cannot be determined and a ValueError is raised. Parameters ---------- seq : sequence of pint.PlainQuantity sequence of pint.PlainQuantity units : UnitsContainer, str or pint.PlainQuantity units of the physical quantity to be created (Default value = None) Returns ------- pint.PlainQuantity """ len_seq = len(seq) if units is None: if len_seq: units = seq[0].u else: raise ValueError("Cannot determine units from empty sequence!") a = np.empty(len_seq) for i, seq_i in enumerate(seq): a[i] = seq_i.m_as(units) # raises DimensionalityError if incompatible units are used in the sequence return cls(a, units) @classmethod def from_tuple(cls, tup): return cls(tup[0], cls._REGISTRY.UnitsContainer(tup[1])) def to_tuple(self) -> tuple[MagnitudeT, tuple[tuple[str, ...]]]: return self.m, tuple(self._units.items()) def compatible_units(self, *contexts): if contexts: with self._REGISTRY.context(*contexts): return self._REGISTRY.get_compatible_units(self._units) return self._REGISTRY.get_compatible_units(self._units) def is_compatible_with( self, other: Any, *contexts: str | Context, **ctx_kwargs: Any ) -> bool: """check if the other object is compatible Parameters ---------- other The object to check. Treated as dimensionless if not a PlainQuantity, Unit or str. *contexts : str or pint.Context Contexts to use in the transformation. **ctx_kwargs : Values for the Context/s Returns ------- bool """ from .unit import PlainUnit if contexts or self._REGISTRY._active_ctx: try: self.to(other, *contexts, **ctx_kwargs) return True except DimensionalityError: return False if isinstance(other, (PlainQuantity, PlainUnit)): return self.dimensionality == other.dimensionality if isinstance(other, str): return ( self.dimensionality == self._REGISTRY.parse_units(other).dimensionality ) return self.dimensionless def _convert_magnitude_not_inplace(self, other, *contexts, **ctx_kwargs): if contexts: with self._REGISTRY.context(*contexts, **ctx_kwargs): return self._REGISTRY.convert(self._magnitude, self._units, other) return self._REGISTRY.convert(self._magnitude, self._units, other) def _convert_magnitude(self, other, *contexts, **ctx_kwargs): if contexts: with self._REGISTRY.context(*contexts, **ctx_kwargs): return self._REGISTRY.convert(self._magnitude, self._units, other) return self._REGISTRY.convert( self._magnitude, self._units, other, inplace=is_duck_array_type(type(self._magnitude)), ) def ito( self, other: QuantityOrUnitLike | None = None, *contexts, **ctx_kwargs ) -> None: """Inplace rescale to different units. Parameters ---------- other : pint.PlainQuantity, str or dict Destination units. (Default value = None) *contexts : str or pint.Context Contexts to use in the transformation. **ctx_kwargs : Values for the Context/s """ other = to_units_container(other, self._REGISTRY) self._magnitude = self._convert_magnitude(other, *contexts, **ctx_kwargs) self._units = other return None def to( self, other: QuantityOrUnitLike | None = None, *contexts, **ctx_kwargs ) -> PlainQuantity: """Return PlainQuantity rescaled to different units. Parameters ---------- other : pint.PlainQuantity, str or dict destination units. (Default value = None) *contexts : str or pint.Context Contexts to use in the transformation. **ctx_kwargs : Values for the Context/s Returns ------- pint.PlainQuantity """ other = to_units_container(other, self._REGISTRY) magnitude = self._convert_magnitude_not_inplace(other, *contexts, **ctx_kwargs) return self.__class__(magnitude, other) def ito_root_units(self) -> None: """Return PlainQuantity rescaled to root units.""" _, other = self._REGISTRY._get_root_units(self._units) self._magnitude = self._convert_magnitude(other) self._units = other return None def to_root_units(self) -> PlainQuantity[MagnitudeT]: """Return PlainQuantity rescaled to root units.""" _, other = self._REGISTRY._get_root_units(self._units) magnitude = self._convert_magnitude_not_inplace(other) return self.__class__(magnitude, other) def ito_base_units(self) -> None: """Return PlainQuantity rescaled to plain units.""" _, other = self._REGISTRY._get_base_units(self._units) self._magnitude = self._convert_magnitude(other) self._units = other return None def to_base_units(self) -> PlainQuantity[MagnitudeT]: """Return PlainQuantity rescaled to plain units.""" _, other = self._REGISTRY._get_base_units(self._units) magnitude = self._convert_magnitude_not_inplace(other) return self.__class__(magnitude, other) # Functions not essential to a Quantity but it is # convenient that they live in PlainQuantity. # They are implemented elsewhere to keep Quantity class clean. to_compact = qto.to_compact to_preferred = qto.to_preferred ito_preferred = qto.ito_preferred to_reduced_units = qto.to_reduced_units ito_reduced_units = qto.ito_reduced_units # Mathematical operations def __int__(self) -> int: if self.dimensionless: return int(self._convert_magnitude_not_inplace(UnitsContainer())) raise DimensionalityError(self._units, "dimensionless") def __float__(self) -> float: if self.dimensionless: return float(self._convert_magnitude_not_inplace(UnitsContainer())) raise DimensionalityError(self._units, "dimensionless") def __complex__(self) -> complex: if self.dimensionless: return complex(self._convert_magnitude_not_inplace(UnitsContainer())) raise DimensionalityError(self._units, "dimensionless") @check_implemented def _iadd_sub(self, other, op): """Perform addition or subtraction operation in-place and return the result. Parameters ---------- other : pint.PlainQuantity or any type accepted by :func:`_to_magnitude` object to be added to / subtracted from self op : function operator function (e.g. operator.add, operator.isub) """ if not self._check(other): # other not from same Registry or not a PlainQuantity try: other_magnitude = _to_magnitude( other, self.force_ndarray, self.force_ndarray_like ) except PintTypeError: raise except TypeError: return NotImplemented if zero_or_nan(other, True): # If the other value is 0 (but not PlainQuantity 0) # do the operation without checking units. # We do the calculation instead of just returning the same # value to enforce any shape checking and type casting due to # the operation. self._magnitude = op(self._magnitude, other_magnitude) elif self.dimensionless: self.ito(self.UnitsContainer()) self._magnitude = op(self._magnitude, other_magnitude) else: raise DimensionalityError(self._units, "dimensionless") return self if not self.dimensionality == other.dimensionality: raise DimensionalityError( self._units, other._units, self.dimensionality, other.dimensionality ) # Next we define some variables to make if-clauses more readable. self_non_mul_units = self._get_non_multiplicative_units() is_self_multiplicative = len(self_non_mul_units) == 0 if len(self_non_mul_units) == 1: self_non_mul_unit = self_non_mul_units[0] other_non_mul_units = other._get_non_multiplicative_units() is_other_multiplicative = len(other_non_mul_units) == 0 if len(other_non_mul_units) == 1: other_non_mul_unit = other_non_mul_units[0] # Presence of non-multiplicative units gives rise to several cases. if is_self_multiplicative and is_other_multiplicative: if self._units == other._units: self._magnitude = op(self._magnitude, other._magnitude) # If only self has a delta unit, other determines unit of result. elif self._get_delta_units() and not other._get_delta_units(): self._magnitude = op( self._convert_magnitude(other._units), other._magnitude ) self._units = other._units else: self._magnitude = op(self._magnitude, other.to(self._units)._magnitude) elif ( op == operator.isub and len(self_non_mul_units) == 1 and self._units[self_non_mul_unit] == 1 and not other._has_compatible_delta(self_non_mul_unit) ): if self._units == other._units: self._magnitude = op(self._magnitude, other._magnitude) else: self._magnitude = op(self._magnitude, other.to(self._units)._magnitude) self._units = self._units.rename( self_non_mul_unit, "delta_" + self_non_mul_unit ) elif ( op == operator.isub and len(other_non_mul_units) == 1 and other._units[other_non_mul_unit] == 1 and not self._has_compatible_delta(other_non_mul_unit) ): # we convert to self directly since it is multiplicative self._magnitude = op(self._magnitude, other.to(self._units)._magnitude) elif ( len(self_non_mul_units) == 1 # order of the dimension of offset unit == 1 ? and self._units[self_non_mul_unit] == 1 and other._has_compatible_delta(self_non_mul_unit) ): # Replace offset unit in self by the corresponding delta unit. # This is done to prevent a shift by offset in the to()-call. tu = self._units.rename(self_non_mul_unit, "delta_" + self_non_mul_unit) self._magnitude = op(self._magnitude, other.to(tu)._magnitude) elif ( len(other_non_mul_units) == 1 # order of the dimension of offset unit == 1 ? and other._units[other_non_mul_unit] == 1 and self._has_compatible_delta(other_non_mul_unit) ): # Replace offset unit in other by the corresponding delta unit. # This is done to prevent a shift by offset in the to()-call. tu = other._units.rename(other_non_mul_unit, "delta_" + other_non_mul_unit) self._magnitude = op(self._convert_magnitude(tu), other._magnitude) self._units = other._units else: raise OffsetUnitCalculusError(self._units, other._units) return self @check_implemented def _add_sub(self, other, op): """Perform addition or subtraction operation and return the result. Parameters ---------- other : pint.PlainQuantity or any type accepted by :func:`_to_magnitude` object to be added to / subtracted from self op : function operator function (e.g. operator.add, operator.isub) """ if not self._check(other): # other not from same Registry or not a PlainQuantity if zero_or_nan(other, True): # If the other value is 0 or NaN (but not a PlainQuantity) # do the operation without checking units. # We do the calculation instead of just returning the same # value to enforce any shape checking and type casting due to # the operation. units = self._units magnitude = op( self._magnitude, _to_magnitude(other, self.force_ndarray, self.force_ndarray_like), ) elif self.dimensionless: units = self.UnitsContainer() magnitude = op( self.to(units)._magnitude, _to_magnitude(other, self.force_ndarray, self.force_ndarray_like), ) else: raise DimensionalityError(self._units, "dimensionless") return self.__class__(magnitude, units) if not self.dimensionality == other.dimensionality: raise DimensionalityError( self._units, other._units, self.dimensionality, other.dimensionality ) # Next we define some variables to make if-clauses more readable. self_non_mul_units = self._get_non_multiplicative_units() is_self_multiplicative = len(self_non_mul_units) == 0 if len(self_non_mul_units) == 1: self_non_mul_unit = self_non_mul_units[0] other_non_mul_units = other._get_non_multiplicative_units() is_other_multiplicative = len(other_non_mul_units) == 0 if len(other_non_mul_units) == 1: other_non_mul_unit = other_non_mul_units[0] # Presence of non-multiplicative units gives rise to several cases. if is_self_multiplicative and is_other_multiplicative: if self._units == other._units: magnitude = op(self._magnitude, other._magnitude) units = self._units # If only self has a delta unit, other determines unit of result. elif self._get_delta_units() and not other._get_delta_units(): magnitude = op( self._convert_magnitude_not_inplace(other._units), other._magnitude ) units = other._units else: units = self._units magnitude = op(self._magnitude, other.to(self._units).magnitude) elif ( op == operator.sub and len(self_non_mul_units) == 1 and self._units[self_non_mul_unit] == 1 and not other._has_compatible_delta(self_non_mul_unit) ): if self._units == other._units: magnitude = op(self._magnitude, other._magnitude) else: magnitude = op(self._magnitude, other.to(self._units)._magnitude) units = self._units.rename(self_non_mul_unit, "delta_" + self_non_mul_unit) elif ( op == operator.sub and len(other_non_mul_units) == 1 and other._units[other_non_mul_unit] == 1 and not self._has_compatible_delta(other_non_mul_unit) ): # we convert to self directly since it is multiplicative magnitude = op(self._magnitude, other.to(self._units)._magnitude) units = self._units elif ( len(self_non_mul_units) == 1 # order of the dimension of offset unit == 1 ? and self._units[self_non_mul_unit] == 1 and other._has_compatible_delta(self_non_mul_unit) ): # Replace offset unit in self by the corresponding delta unit. # This is done to prevent a shift by offset in the to()-call. tu = self._units.rename(self_non_mul_unit, "delta_" + self_non_mul_unit) magnitude = op(self._magnitude, other.to(tu).magnitude) units = self._units elif ( len(other_non_mul_units) == 1 # order of the dimension of offset unit == 1 ? and other._units[other_non_mul_unit] == 1 and self._has_compatible_delta(other_non_mul_unit) ): # Replace offset unit in other by the corresponding delta unit. # This is done to prevent a shift by offset in the to()-call. tu = other._units.rename(other_non_mul_unit, "delta_" + other_non_mul_unit) magnitude = op(self._convert_magnitude_not_inplace(tu), other._magnitude) units = other._units else: raise OffsetUnitCalculusError(self._units, other._units) return self.__class__(magnitude, units) @overload def __iadd__(self, other: datetime.datetime) -> datetime.timedelta: # type: ignore[misc] ... @overload def __iadd__(self, other) -> PlainQuantity[MagnitudeT]: ... def __iadd__(self, other): if isinstance(other, datetime.datetime): return self.to_timedelta() + other elif is_duck_array_type(type(self._magnitude)): return self._iadd_sub(other, operator.iadd) return self._add_sub(other, operator.add) def __add__(self, other): if isinstance(other, datetime.datetime): return self.to_timedelta() + other return self._add_sub(other, operator.add) __radd__ = __add__ def __isub__(self, other): if is_duck_array_type(type(self._magnitude)): return self._iadd_sub(other, operator.isub) return self._add_sub(other, operator.sub) def __sub__(self, other): return self._add_sub(other, operator.sub) def __rsub__(self, other): if isinstance(other, datetime.datetime): return other - self.to_timedelta() return -self._add_sub(other, operator.sub) @check_implemented @ireduce_dimensions def _imul_div(self, other, magnitude_op, units_op=None): """Perform multiplication or division operation in-place and return the result. Parameters ---------- other : pint.PlainQuantity or any type accepted by :func:`_to_magnitude` object to be multiplied/divided with self magnitude_op : function operator function to perform on the magnitudes (e.g. operator.mul) units_op : function or None operator function to perform on the units; if None, *magnitude_op* is used (Default value = None) Returns ------- """ if units_op is None: units_op = magnitude_op offset_units_self = self._get_non_multiplicative_units() no_offset_units_self = len(offset_units_self) if not self._check(other): if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, getattr(other, "units", "")) if len(offset_units_self) == 1: if self._units[offset_units_self[0]] != 1 or magnitude_op not in ( operator.mul, operator.imul, ): raise OffsetUnitCalculusError( self._units, getattr(other, "units", "") ) try: other_magnitude = _to_magnitude( other, self.force_ndarray, self.force_ndarray_like ) except PintTypeError: raise except TypeError: return NotImplemented self._magnitude = magnitude_op(self._magnitude, other_magnitude) self._units = units_op(self._units, self.UnitsContainer()) return self if isinstance(other, self._REGISTRY.Unit): other = 1 * other if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, other._units) elif no_offset_units_self == len(self._units) == 1: self.ito_root_units() no_offset_units_other = len(other._get_non_multiplicative_units()) if not other._ok_for_muldiv(no_offset_units_other): raise OffsetUnitCalculusError(self._units, other._units) elif no_offset_units_other == len(other._units) == 1: other.ito_root_units() self._magnitude = magnitude_op(self._magnitude, other._magnitude) self._units = units_op(self._units, other._units) return self @check_implemented @ireduce_dimensions def _mul_div(self, other, magnitude_op, units_op=None): """Perform multiplication or division operation and return the result. Parameters ---------- other : pint.PlainQuantity or any type accepted by :func:`_to_magnitude` object to be multiplied/divided with self magnitude_op : function operator function to perform on the magnitudes (e.g. operator.mul) units_op : function or None operator function to perform on the units; if None, *magnitude_op* is used (Default value = None) Returns ------- """ if units_op is None: units_op = magnitude_op offset_units_self = self._get_non_multiplicative_units() no_offset_units_self = len(offset_units_self) if not self._check(other): if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, getattr(other, "units", "")) if len(offset_units_self) == 1: if self._units[offset_units_self[0]] != 1 or magnitude_op not in ( operator.mul, operator.imul, ): raise OffsetUnitCalculusError( self._units, getattr(other, "units", "") ) try: other_magnitude = _to_magnitude( other, self.force_ndarray, self.force_ndarray_like ) except PintTypeError: raise except TypeError: return NotImplemented magnitude = magnitude_op(self._magnitude, other_magnitude) units = units_op(self._units, self.UnitsContainer()) return self.__class__(magnitude, units) if isinstance(other, self._REGISTRY.Unit): other = 1 * other new_self = self if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, other._units) elif no_offset_units_self == len(self._units) == 1: new_self = self.to_root_units() no_offset_units_other = len(other._get_non_multiplicative_units()) if not other._ok_for_muldiv(no_offset_units_other): raise OffsetUnitCalculusError(self._units, other._units) elif no_offset_units_other == len(other._units) == 1: other = other.to_root_units() magnitude = magnitude_op(new_self._magnitude, other._magnitude) units = units_op(new_self._units, other._units) return self.__class__(magnitude, units) def __imul__(self, other): if is_duck_array_type(type(self._magnitude)): return self._imul_div(other, operator.imul) return self._mul_div(other, operator.mul) def __mul__(self, other): return self._mul_div(other, operator.mul) __rmul__ = __mul__ def __matmul__(self, other): return np.matmul(self, other) __rmatmul__ = __matmul__ def _truedivide_cast_int(self, a, b): t = self._REGISTRY.non_int_type if isinstance(a, int): a = t(a) if isinstance(b, int): b = t(b) return operator.truediv(a, b) def __itruediv__(self, other): if is_duck_array_type(type(self._magnitude)): return self._imul_div(other, operator.itruediv) return self._mul_div(other, operator.truediv) def __truediv__(self, other): if isinstance(self.m, int) or isinstance(getattr(other, "m", None), int): return self._mul_div(other, self._truedivide_cast_int, operator.truediv) return self._mul_div(other, operator.truediv) def __rtruediv__(self, other): try: other_magnitude = _to_magnitude( other, self.force_ndarray, self.force_ndarray_like ) except PintTypeError: raise except TypeError: return NotImplemented no_offset_units_self = len(self._get_non_multiplicative_units()) if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, "") elif no_offset_units_self == len(self._units) == 1: self = self.to_root_units() return self.__class__(other_magnitude / self._magnitude, 1 / self._units) __div__ = __truediv__ __rdiv__ = __rtruediv__ __idiv__ = __itruediv__ def __ifloordiv__(self, other): if self._check(other): self._magnitude //= other.to(self._units)._magnitude elif self.dimensionless: self._magnitude = self.to("")._magnitude // other else: raise DimensionalityError(self._units, "dimensionless") self._units = self.UnitsContainer({}) return self @check_implemented def __floordiv__(self, other): if self._check(other): magnitude = self._magnitude // other.to(self._units)._magnitude elif self.dimensionless: magnitude = self.to("")._magnitude // other else: raise DimensionalityError(self._units, "dimensionless") return self.__class__(magnitude, self.UnitsContainer({})) @check_implemented def __rfloordiv__(self, other): if self._check(other): magnitude = other._magnitude // self.to(other._units)._magnitude elif self.dimensionless: magnitude = other // self.to("")._magnitude else: raise DimensionalityError(self._units, "dimensionless") return self.__class__(magnitude, self.UnitsContainer({})) @check_implemented def __imod__(self, other): if not self._check(other): other = self.__class__(other, self.UnitsContainer({})) self._magnitude %= other.to(self._units)._magnitude return self @check_implemented def __mod__(self, other): if not self._check(other): other = self.__class__(other, self.UnitsContainer({})) magnitude = self._magnitude % other.to(self._units)._magnitude return self.__class__(magnitude, self._units) @check_implemented def __rmod__(self, other): if self._check(other): magnitude = other._magnitude % self.to(other._units)._magnitude return self.__class__(magnitude, other._units) elif self.dimensionless: magnitude = other % self.to("")._magnitude return self.__class__(magnitude, self.UnitsContainer({})) else: raise DimensionalityError(self._units, "dimensionless") @check_implemented def __divmod__(self, other): if not self._check(other): other = self.__class__(other, self.UnitsContainer({})) q, r = divmod(self._magnitude, other.to(self._units)._magnitude) return ( self.__class__(q, self.UnitsContainer({})), self.__class__(r, self._units), ) @check_implemented def __rdivmod__(self, other): if self._check(other): q, r = divmod(other._magnitude, self.to(other._units)._magnitude) unit = other._units elif self.dimensionless: q, r = divmod(other, self.to("")._magnitude) unit = self.UnitsContainer({}) else: raise DimensionalityError(self._units, "dimensionless") return (self.__class__(q, self.UnitsContainer({})), self.__class__(r, unit)) @check_implemented def __ipow__(self, other): if not is_duck_array_type(type(self._magnitude)): return self.__pow__(other) try: _to_magnitude(other, self.force_ndarray, self.force_ndarray_like) except PintTypeError: raise except TypeError: return NotImplemented else: if not self._ok_for_muldiv: raise OffsetUnitCalculusError(self._units) if is_duck_array_type(type(getattr(other, "_magnitude", other))): # arrays are refused as exponent, because they would create # len(array) quantities of len(set(array)) different units # unless the plain is dimensionless. Ensure dimensionless # units are reduced to "dimensionless". # Note: this will strip Units of degrees or radians from PlainQuantity if self.dimensionless: if getattr(other, "dimensionless", False): self._magnitude = self.m_as("") ** other.m_as("") self._units = self.UnitsContainer() return self elif not getattr(other, "dimensionless", True): raise DimensionalityError(other._units, "dimensionless") else: self._magnitude = self.m_as("") ** other self._units = self.UnitsContainer() return self elif np.size(other) > 1: raise DimensionalityError( self._units, "dimensionless", extra_msg=". PlainQuantity array exponents are only allowed if the " "plain is dimensionless", ) if other == 1: return self elif other == 0: self._units = self.UnitsContainer() else: if not self._is_multiplicative: if self._REGISTRY.autoconvert_offset_to_baseunit: self.ito_base_units() else: raise OffsetUnitCalculusError(self._units) if getattr(other, "dimensionless", False): other = other.to_base_units().magnitude self._units **= other elif not getattr(other, "dimensionless", True): raise DimensionalityError(self._units, "dimensionless") else: self._units **= other self._magnitude **= _to_magnitude( other, self.force_ndarray, self.force_ndarray_like ) return self @check_implemented def __pow__(self, other) -> PlainQuantity[MagnitudeT]: try: _to_magnitude(other, self.force_ndarray, self.force_ndarray_like) except PintTypeError: raise except TypeError: return NotImplemented else: if not self._ok_for_muldiv: raise OffsetUnitCalculusError(self._units) if is_duck_array_type(type(getattr(other, "_magnitude", other))): # arrays are refused as exponent, because they would create # len(array) quantities of len(set(array)) different units # unless the plain is dimensionless. # Note: this will strip Units of degrees or radians from PlainQuantity if self.dimensionless: if getattr(other, "dimensionless", False): return self.__class__( self._convert_magnitude_not_inplace(self.UnitsContainer()) ** other.m_as("") ) elif not getattr(other, "dimensionless", True): raise DimensionalityError(other._units, "dimensionless") else: return self.__class__( self._convert_magnitude_not_inplace(self.UnitsContainer()) ** other ) elif np.size(other) > 1: raise DimensionalityError( self._units, "dimensionless", extra_msg=". PlainQuantity array exponents are only allowed if the " "plain is dimensionless", ) new_self = self if other == 1: return self elif other == 0: exponent = 0 units = self.UnitsContainer() else: if not self._is_multiplicative: if self._REGISTRY.autoconvert_offset_to_baseunit: new_self = self.to_root_units() else: raise OffsetUnitCalculusError(self._units) if getattr(other, "dimensionless", False): exponent = other.to_root_units().magnitude units = new_self._units**exponent elif not getattr(other, "dimensionless", True): raise DimensionalityError(other._units, "dimensionless") else: exponent = _to_magnitude( other, force_ndarray=False, force_ndarray_like=False ) units = new_self._units**exponent magnitude = new_self._magnitude**exponent return self.__class__(magnitude, units) @check_implemented def __rpow__(self, other) -> PlainQuantity[MagnitudeT]: try: _to_magnitude(other, self.force_ndarray, self.force_ndarray_like) except PintTypeError: raise except TypeError: return NotImplemented else: if not self.dimensionless: raise DimensionalityError(self._units, "dimensionless") new_self = self.to_root_units() return other**new_self._magnitude def __abs__(self) -> PlainQuantity[MagnitudeT]: return self.__class__(abs(self._magnitude), self._units) def __round__(self, ndigits: int | None = 0) -> PlainQuantity[MagnitudeT]: return self.__class__(round(self._magnitude, ndigits=ndigits), self._units) def __pos__(self) -> PlainQuantity[MagnitudeT]: return self.__class__(operator.pos(self._magnitude), self._units) def __neg__(self) -> PlainQuantity[MagnitudeT]: return self.__class__(operator.neg(self._magnitude), self._units) @check_implemented def __eq__(self, other): def bool_result(value): nonlocal other if not is_duck_array_type(type(self._magnitude)): return value if isinstance(other, PlainQuantity): other = other._magnitude template, _ = np.broadcast_arrays(self._magnitude, other) return np.full_like(template, fill_value=value, dtype=np.bool_) # We compare to the plain class of PlainQuantity because # each PlainQuantity class is unique. if not isinstance(other, PlainQuantity): if other is None: # A loop in pandas-dev/pandas/core/common.py(86)consensus_name_attr() can result in OTHER being None return bool_result(False) if zero_or_nan(other, True): # Handle the special case in which we compare to zero or NaN # (or an array of zeros or NaNs) if self._is_multiplicative: # compare magnitude return eq(self._magnitude, other, False) else: # compare the magnitude after converting the # non-multiplicative quantity to plain units if self._REGISTRY.autoconvert_offset_to_baseunit: return eq(self.to_base_units()._magnitude, other, False) else: raise OffsetUnitCalculusError(self._units) if self.dimensionless: return eq( self._convert_magnitude_not_inplace(self.UnitsContainer()), other, False, ) return bool_result(False) # TODO: this might be expensive. Do we even need it? if eq(self._magnitude, 0, True) and eq(other._magnitude, 0, True): return bool_result(self.dimensionality == other.dimensionality) if self._units == other._units: return eq(self._magnitude, other._magnitude, False) try: return eq( self._convert_magnitude_not_inplace(other._units), other._magnitude, False, ) except DimensionalityError: return bool_result(False) @check_implemented def __ne__(self, other): out = self.__eq__(other) if is_duck_array_type(type(out)): return np.logical_not(out) return not out @check_implemented def compare(self, other, op): if not isinstance(other, PlainQuantity): if self.dimensionless: return op( self._convert_magnitude_not_inplace(self.UnitsContainer()), other ) elif zero_or_nan(other, True): # Handle the special case in which we compare to zero or NaN # (or an array of zeros or NaNs) if self._is_multiplicative: # compare magnitude return op(self._magnitude, other) else: # compare the magnitude after converting the # non-multiplicative quantity to plain units if self._REGISTRY.autoconvert_offset_to_baseunit: return op(self.to_base_units()._magnitude, other) else: raise OffsetUnitCalculusError(self._units) else: raise ValueError(f"Cannot compare PlainQuantity and {type(other)}") # Registry equality check based on util.SharedRegistryObject if self._REGISTRY is not other._REGISTRY: mess = "Cannot operate with {} and {} of different registries." raise ValueError( mess.format(self.__class__.__name__, other.__class__.__name__) ) if self._units == other._units: return op(self._magnitude, other._magnitude) if self.dimensionality != other.dimensionality: raise DimensionalityError( self._units, other._units, self.dimensionality, other.dimensionality ) return op(self.to_root_units().magnitude, other.to_root_units().magnitude) __lt__ = lambda self, other: self.compare(other, op=operator.lt) __le__ = lambda self, other: self.compare(other, op=operator.le) __ge__ = lambda self, other: self.compare(other, op=operator.ge) __gt__ = lambda self, other: self.compare(other, op=operator.gt) def __bool__(self) -> bool: # Only cast when non-ambiguous (when multiplicative unit) if self._is_multiplicative: return bool(self._magnitude) else: raise ValueError( "Boolean value of PlainQuantity with offset unit is ambiguous." ) __nonzero__ = __bool__ def tolist(self): units = self._units try: values = self._magnitude.tolist() if not isinstance(values, list): return self.__class__(values, units) return [ self.__class__(value, units).tolist() if isinstance(value, list) else self.__class__(value, units) for value in self._magnitude.tolist() ] except AttributeError: raise AttributeError( f"Magnitude '{type(self._magnitude).__name__}' does not support tolist." ) def _get_unit_definition(self, unit: str) -> UnitDefinition: try: return self._REGISTRY._units[unit] except KeyError: # pint#1062: The __init__ method of this object added the unit to # UnitRegistry._units (e.g. units with prefix are added on the fly the # first time they're used) but the key was later removed, e.g. because # a Context with unit redefinitions was deactivated. self._REGISTRY.parse_units(unit) return self._REGISTRY._units[unit] # methods/properties that help for math operations with offset units @property def _is_multiplicative(self) -> bool: """Check if the PlainQuantity object has only multiplicative units.""" return True def _get_non_multiplicative_units(self) -> list[str]: """Return a list of the of non-multiplicative units of the PlainQuantity object.""" return [] def _get_delta_units(self) -> list[str]: """Return list of delta units ot the PlainQuantity object.""" return [u for u in self._units if u.startswith("delta_")] def _has_compatible_delta(self, unit: str) -> bool: """ "Check if PlainQuantity object has a delta_unit that is compatible with unit""" return False def _ok_for_muldiv(self, no_offset_units=None) -> bool: return True def to_timedelta(self: PlainQuantity[MagnitudeT]) -> datetime.timedelta: return datetime.timedelta(microseconds=self.to("microseconds").magnitude) # We put this last to avoid overriding UnitsContainer # and I do not want to rename it. # TODO: Maybe in the future we need to change it to a more meaningful # non-colliding name. @property def UnitsContainer(self) -> Callable[..., UnitsContainerT]: return self._REGISTRY.UnitsContainer pint-0.24.4/pint/facets/plain/registry.py000066400000000000000000001357641471316474000203460ustar00rootroot00000000000000""" pint.facets.plain.registry ~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. The registry contains the following important methods: - parse_unit_name: Parse a unit to identify prefix, unit name and suffix by walking the list of prefix and suffix. Result is cached: NO - parse_units: Parse a units expression and returns a UnitContainer with the canonical names. The expression can only contain products, ratios and powers of units; prefixed units and pluralized units. Result is cached: YES - parse_expression: Parse a mathematical expression including units and return a quantity object. Result is cached: NO """ from __future__ import annotations import copy import functools import inspect import itertools import pathlib import re from collections import defaultdict from collections.abc import Callable, Generator, Iterable, Iterator from decimal import Decimal from fractions import Fraction from token import NAME, NUMBER from tokenize import TokenInfo from typing import ( TYPE_CHECKING, Any, Generic, TypeVar, Union, ) if TYPE_CHECKING: from ...compat import Locale from ..context import Context # from ..._typing import Quantity, Unit import platformdirs from ... import pint_eval from ..._typing import ( Handler, QuantityArgument, QuantityOrUnitLike, Scalar, UnitLike, ) from ...compat import Self, TypeAlias, deprecated from ...errors import ( DimensionalityError, OffsetUnitCalculusError, RedefinitionError, UndefinedUnitError, ) from ...pint_eval import build_eval_tree from ...util import ( ParserHelper, _is_dim, create_class_with_registry, getattr_maybe_raise, logger, solve_dependencies, string_preprocessor, to_units_container, ) from ...util import UnitsContainer as UnitsContainer from .definitions import ( AliasDefinition, CommentDefinition, DefaultsDefinition, DerivedDimensionDefinition, DimensionDefinition, NamedDefinition, PrefixDefinition, UnitDefinition, ) from .objects import PlainQuantity, PlainUnit T = TypeVar("T") _BLOCK_RE = re.compile(r"[ (]") @functools.lru_cache def pattern_to_regex(pattern: str | re.Pattern[str]) -> re.Pattern[str]: # TODO: This has been changed during typing improvements. # if hasattr(pattern, "finditer"): if not isinstance(pattern, str): pattern = pattern.pattern # Replace "{unit_name}" match string with float regex with unit_name as group pattern = re.sub( r"{(\w+)}", r"(?P<\1>[+-]?[0-9]+(?:.[0-9]+)?(?:[Ee][+-]?[0-9]+)?)", pattern ) return re.compile(pattern) NON_INT_TYPE = type[Union[float, Decimal, Fraction]] PreprocessorType = Callable[[str], str] class RegistryCache: """Cache to speed up unit registries""" def __init__(self) -> None: #: Maps dimensionality (UnitsContainer) to Units (str) self.dimensional_equivalents: dict[UnitsContainer, frozenset[str]] = {} #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) # TODO: this description is not right. self.root_units: dict[UnitsContainer, tuple[Scalar, UnitsContainer]] = {} #: Maps dimensionality (UnitsContainer) to Units (UnitsContainer) self.dimensionality: dict[UnitsContainer, UnitsContainer] = {} #: Cache the unit name associated to user input. ('mV' -> 'millivolt') self.parse_unit: dict[str, UnitsContainer] = {} self.conversion_factor: dict[ tuple[UnitsContainer, UnitsContainer], Scalar | DimensionalityError ] = {} def __eq__(self, other: Any): if not isinstance(other, self.__class__): return False attrs = ( "dimensional_equivalents", "root_units", "dimensionality", "parse_unit", "conversion_factor", ) return all(getattr(self, attr) == getattr(other, attr) for attr in attrs) class RegistryMeta(type): """This is just to call after_init at the right time instead of asking the developer to do it when subclassing. """ def __call__(self, *args: Any, **kwargs: Any): obj = super().__call__(*args, **kwargs) obj._after_init() return obj # Generic types used to mark types associated to Registries. QuantityT = TypeVar("QuantityT", bound=PlainQuantity[Any]) UnitT = TypeVar("UnitT", bound=PlainUnit) class GenericPlainRegistry(Generic[QuantityT, UnitT], metaclass=RegistryMeta): """Base class for all registries. Capabilities: - Register units, prefixes, and dimensions, and their relations. - Convert between units. - Find dimensionality of a unit. - Parse units with prefix and/or suffix. - Parse expressions. - Parse a definition file. - Allow extending the definition file parser by registering @ directives. Parameters ---------- filename : str or None path of the units definition file to load or line iterable object. Empty to load the default definition file. None to leave the UnitRegistry empty. force_ndarray : bool convert any input, scalar or not to a numpy.ndarray. force_ndarray_like : bool convert all inputs other than duck arrays to a numpy.ndarray. on_redefinition : str action to take in case a unit is redefined: 'warn', 'raise', 'ignore' auto_reduce_dimensions : If True, reduce dimensionality on appropriate operations. autoconvert_to_preferred : If True, converts preferred units on appropriate operations. preprocessors : list of callables which are iteratively ran on any input expression or unit string fmt_locale : locale identifier string, used in `format_babel` non_int_type : type numerical type used for non integer values. (Default: float) case_sensitive : bool, optional Control default case sensitivity of unit parsing. (Default: True) cache_folder : str or pathlib.Path or None, optional Specify the folder in which cache files are saved and loaded from. If None, the cache is disabled. (default) separate_format_defaults : bool, optional Separate the default format into magnitude and unit formats as soon as possible. The deprecated default is not to separate. This will change in a future release. """ Quantity: type[QuantityT] Unit: type[UnitT] _diskcache = None _def_parser = None def __init__( self, filename="", force_ndarray: bool = False, force_ndarray_like: bool = False, on_redefinition: str = "warn", auto_reduce_dimensions: bool = False, autoconvert_to_preferred: bool = False, preprocessors: list[PreprocessorType] | None = None, fmt_locale: str | None = None, non_int_type: NON_INT_TYPE = float, case_sensitive: bool = True, cache_folder: str | pathlib.Path | None = None, separate_format_defaults: bool | None = None, mpl_formatter: str = "{:P}", ): #: Map a definition class to a adder methods. self._adders: Handler = {} self._register_definition_adders() self._init_dynamic_classes() if cache_folder == ":auto:": cache_folder = platformdirs.user_cache_path(appname="pint", appauthor=False) from ... import delegates # TODO: change thiss if cache_folder is not None: self._diskcache = delegates.build_disk_cache_class(non_int_type)( cache_folder ) self._def_parser = delegates.txt_defparser.DefParser( delegates.ParserConfig(non_int_type), diskcache=self._diskcache ) self.formatter = delegates.Formatter(self) self._filename = filename self.force_ndarray = force_ndarray self.force_ndarray_like = force_ndarray_like self.preprocessors = preprocessors or [] # use a default preprocessor to support "%" self.preprocessors.insert(0, lambda string: string.replace("%", " percent ")) # use a default preprocessor to support permille "‰" self.preprocessors.insert(0, lambda string: string.replace("‰", " permille ")) #: mode used to fill in the format defaults self.separate_format_defaults = separate_format_defaults #: Action to take in case a unit is redefined. 'warn', 'raise', 'ignore' self._on_redefinition = on_redefinition #: Determines if dimensionality should be reduced on appropriate operations. self.auto_reduce_dimensions = auto_reduce_dimensions #: Determines if units will be converted to preffered on appropriate operations. self.autoconvert_to_preferred = autoconvert_to_preferred #: Default locale identifier string, used when calling format_babel without explicit locale. self.formatter.set_locale(fmt_locale) #: sets the formatter used when plotting with matplotlib self.mpl_formatter = mpl_formatter #: Numerical type used for non integer values. self._non_int_type = non_int_type #: Default unit case sensitivity self.case_sensitive = case_sensitive #: Map between name (string) and value (string) of defaults stored in the #: definitions file. self._defaults: dict[str, str] = {} #: Map dimension name (string) to its definition (DimensionDefinition). self._dimensions: dict[ str, DimensionDefinition | DerivedDimensionDefinition ] = {} #: Map unit name (string) to its definition (UnitDefinition). #: Might contain prefixed units. self._units: dict[str, UnitDefinition] = {} #: List base unit names self._base_units: list[str] = [] #: Map unit name in lower case (string) to a set of unit names with the right #: case. #: Does not contain prefixed units. #: e.g: 'hz' - > set('Hz', ) self._units_casei: dict[str, set[str]] = defaultdict(set) #: Map prefix name (string) to its definition (PrefixDefinition). self._prefixes: dict[str, PrefixDefinition] = {"": PrefixDefinition("", 1)} #: Map suffix name (string) to canonical , and unit alias to canonical unit name self._suffixes: dict[str, str] = {"": "", "s": ""} #: Map contexts to RegistryCache self._cache = RegistryCache() self._initialized = False def _init_dynamic_classes(self) -> None: """Generate subclasses on the fly and attach them to self""" self.Unit = create_class_with_registry(self, self.Unit) self.Quantity = create_class_with_registry(self, self.Quantity) def _after_init(self) -> None: """This should be called after all __init__""" if self._filename == "": path = pathlib.Path(__file__).parent.parent.parent / "default_en.txt" loaded_files = self.load_definitions(path, True) elif self._filename is not None: loaded_files = self.load_definitions(self._filename) else: loaded_files = None self._build_cache(loaded_files) self._initialized = True def _register_adder( self, definition_class: type[T], adder_func: Callable[ [ T, ], None, ], ) -> None: """Register a block definition.""" self._adders[definition_class] = adder_func def _register_definition_adders(self) -> None: self._register_adder(AliasDefinition, self._add_alias) self._register_adder(DefaultsDefinition, self._add_defaults) self._register_adder(CommentDefinition, lambda o: o) self._register_adder(PrefixDefinition, self._add_prefix) self._register_adder(UnitDefinition, self._add_unit) self._register_adder(DimensionDefinition, self._add_dimension) self._register_adder(DerivedDimensionDefinition, self._add_derived_dimension) def __deepcopy__(self: Self, memo) -> type[Self]: new = object.__new__(type(self)) new.__dict__ = copy.deepcopy(self.__dict__, memo) new._init_dynamic_classes() return new def __getattr__(self, item: str) -> UnitT: getattr_maybe_raise(self, item) # self.Unit will call parse_units return self.Unit(item) def __getitem__(self, item: str) -> UnitT: logger.warning( "Calling the getitem method from a UnitRegistry is deprecated. " "use `parse_expression` method or use the registry as a callable." ) return self.parse_expression(item) def __contains__(self, item: str) -> bool: """Support checking prefixed units with the `in` operator""" try: self.__getattr__(item) return True except UndefinedUnitError: return False def __dir__(self) -> list[str]: #: Calling dir(registry) gives all units, methods, and attributes. #: Also used for autocompletion in IPython. return list(self._units.keys()) + list(object.__dir__(self)) def __iter__(self) -> Iterator[str]: """Allows for listing all units in registry with `list(ureg)`. Returns ------- Iterator over names of all units in registry, ordered alphabetically. """ return iter(sorted(self._units.keys())) @property @deprecated( "This function will be removed in future versions of pint.\n" "Use ureg.formatter.fmt_locale" ) def fmt_locale(self) -> Locale | None: return self.formatter.locale @fmt_locale.setter @deprecated( "This function will be removed in future versions of pint.\n" "Use ureg.formatter.set_locale" ) def fmt_locale(self, loc: str | None): self.formatter.set_locale(loc) @deprecated( "This function will be removed in future versions of pint.\n" "Use ureg.formatter.set_locale" ) def set_fmt_locale(self, loc: str | None) -> None: """Change the locale used by default by `format_babel`. Parameters ---------- loc : str or None None` (do not translate), 'sys' (detect the system locale) or a locale id string. """ self.formatter.set_locale(loc) @property @deprecated( "This function will be removed in future versions of pint.\n" "Use ureg.formatter.default_format" ) def default_format(self) -> str: """Default formatting string for quantities.""" return self.formatter.default_format @default_format.setter @deprecated( "This function will be removed in future versions of pint.\n" "Use ureg.formatter.default_format" ) def default_format(self, value: str) -> None: self.formatter.default_format = value @property def cache_folder(self) -> pathlib.Path | None: if self._diskcache: return self._diskcache.cache_folder return None @property def non_int_type(self): return self._non_int_type def define(self, definition: str | type) -> None: """Add unit to the registry. Parameters ---------- definition : str or Definition a dimension, unit or prefix definition. """ if isinstance(definition, str): parsed_project = self._def_parser.parse_string(definition) for definition in self._def_parser.iter_parsed_project(parsed_project): self._helper_dispatch_adder(definition) else: self._helper_dispatch_adder(definition) ############ # Adders # - we first provide some helpers that deal with repetitive task. # - then we define specific adder for each definition class. :-D ############ def _helper_dispatch_adder(self, definition: Any) -> None: """Helper function to add a single definition, choosing the appropiate method by class. """ for cls in inspect.getmro(definition.__class__): if cls in self._adders: adder_func = self._adders[cls] break else: raise TypeError( f"No loader function defined " f"for {definition.__class__.__name__}" ) adder_func(definition) def _helper_adder( self, definition: NamedDefinition, target_dict: dict[str, Any], casei_target_dict: dict[str, Any] | None, ) -> None: """Helper function to store a definition in the internal dictionaries. It stores the definition under its name, symbol and aliases. """ self._helper_single_adder( definition.name, definition, target_dict, casei_target_dict ) # TODO: Not sure why but using hasattr does not work here. if getattr(definition, "has_symbol", ""): self._helper_single_adder( definition.symbol, definition, target_dict, casei_target_dict ) for alias in getattr(definition, "aliases", ()): if " " in alias: logger.warn("Alias cannot contain a space: " + alias) self._helper_single_adder(alias, definition, target_dict, casei_target_dict) def _helper_single_adder( self, key: str, value: NamedDefinition, target_dict: dict[str, Any], casei_target_dict: dict[str, Any] | None, ) -> None: """Helper function to store a definition in the internal dictionaries. It warns or raise error on redefinition. """ if key in target_dict: if self._on_redefinition == "raise": raise RedefinitionError(key, type(value)) elif self._on_redefinition == "warn": logger.warning(f"Redefining '{key}' ({type(value)})") target_dict[key] = value if casei_target_dict is not None: casei_target_dict[key.lower()].add(key) def _add_defaults(self, defaults_definition: DefaultsDefinition) -> None: for k, v in defaults_definition.items(): self._defaults[k] = v def _add_alias(self, definition: AliasDefinition) -> None: unit_dict = self._units unit = unit_dict[definition.name] while not isinstance(unit, UnitDefinition): unit = unit_dict[unit.name] for alias in definition.aliases: self._helper_single_adder(alias, unit, self._units, self._units_casei) def _add_dimension(self, definition: DimensionDefinition) -> None: self._helper_adder(definition, self._dimensions, None) def _add_derived_dimension(self, definition: DerivedDimensionDefinition) -> None: for dim_name in definition.reference.keys(): if dim_name not in self._dimensions: self._add_dimension(DimensionDefinition(dim_name)) self._helper_adder(definition, self._dimensions, None) def _add_prefix(self, definition: PrefixDefinition) -> None: self._helper_adder(definition, self._prefixes, None) def _add_unit(self, definition: UnitDefinition) -> None: if definition.is_base: self._base_units.append(definition.name) for dim_name in definition.reference.keys(): if dim_name not in self._dimensions: self._add_dimension(DimensionDefinition(dim_name)) self._helper_adder(definition, self._units, self._units_casei) def load_definitions( self, file: Iterable[str] | str | pathlib.Path, is_resource: bool = False ): """Add units and prefixes defined in a definition text file. Parameters ---------- file : can be a filename or a line iterable. is_resource : used to indicate that the file is a resource file and therefore should be loaded from the package. (Default value = False) """ if isinstance(file, (list, tuple)): # TODO: this hack was to keep it backwards compatible. parsed_project = self._def_parser.parse_string("\n".join(file)) else: parsed_project = self._def_parser.parse_file(file) for definition in self._def_parser.iter_parsed_project(parsed_project): self._helper_dispatch_adder(definition) return parsed_project def _build_cache(self, loaded_files=None) -> None: """Build a cache of dimensionality and plain units.""" diskcache = self._diskcache if loaded_files and diskcache: cache, cache_basename = diskcache.load(loaded_files, "build_cache") if cache is None: self._build_cache() diskcache.save(self._cache, loaded_files, "build_cache") return self._cache = RegistryCache() deps: dict[str, set[str]] = { name: set(definition.reference.keys()) if definition.reference else set() for name, definition in self._units.items() } for unit_names in solve_dependencies(deps): for unit_name in unit_names: if "[" in unit_name: continue parsed_names = self.parse_unit_name(unit_name) if parsed_names: prefix, base_name, _ = parsed_names[0] else: prefix, base_name = "", unit_name try: uc = ParserHelper.from_word(base_name, self.non_int_type) bu = self._get_root_units(uc) di = self._get_dimensionality(uc) self._cache.root_units[uc] = bu self._cache.dimensionality[uc] = di if not prefix: dimeq_set = self._cache.dimensional_equivalents.setdefault( di, set() ) dimeq_set.add(self._units[base_name].name) except Exception as exc: logger.warning(f"Could not resolve {unit_name}: {exc!r}") return self._cache def get_name(self, name_or_alias: str, case_sensitive: bool | None = None) -> str: """Return the canonical name of a unit.""" if name_or_alias == "dimensionless": return "" try: return self._units[name_or_alias].name except KeyError: pass candidates = self.parse_unit_name(name_or_alias, case_sensitive) if not candidates: raise UndefinedUnitError(name_or_alias) prefix, unit_name, _ = candidates[0] if len(candidates) > 1: logger.warning( f"Parsing {name_or_alias} yield multiple results. Options are: {candidates!r}" ) if prefix: if not self._units[unit_name].is_multiplicative: raise OffsetUnitCalculusError( "Prefixing a unit requires multiplying the unit." ) name = prefix + unit_name symbol = self.get_symbol(name, case_sensitive) prefix_def = self._prefixes[prefix] self._units[name] = UnitDefinition( name, symbol, tuple(), prefix_def.converter, self.UnitsContainer({unit_name: 1}), ) return prefix + unit_name return unit_name def get_symbol(self, name_or_alias: str, case_sensitive: bool | None = None) -> str: """Return the preferred alias for a unit.""" candidates = self.parse_unit_name(name_or_alias, case_sensitive) if not candidates: raise UndefinedUnitError(name_or_alias) prefix, unit_name, _ = candidates[0] if len(candidates) > 1: logger.warning( f"Parsing {name_or_alias} yield multiple results. Options are: {candidates!r}" ) return self._prefixes[prefix].symbol + self._units[unit_name].symbol def _get_symbol(self, name: str) -> str: return self._units[name].symbol def get_dimensionality(self, input_units: UnitLike) -> UnitsContainer: """Convert unit or dict of units or dimensions to a dict of plain dimensions dimensions """ # TODO: This should be to_units_container(input_units, self) # but this tries to reparse and fail for dimensions. input_units = to_units_container(input_units) return self._get_dimensionality(input_units) def _get_dimensionality(self, input_units: UnitsContainer | None) -> UnitsContainer: """Convert a UnitsContainer to plain dimensions.""" if not input_units: return self.UnitsContainer() cache = self._cache.dimensionality try: return cache[input_units] except KeyError: pass accumulator: dict[str, int] = defaultdict(int) self._get_dimensionality_recurse(input_units, 1, accumulator) if "[]" in accumulator: del accumulator["[]"] dims = self.UnitsContainer({k: v for k, v in accumulator.items() if v != 0}) cache[input_units] = dims return dims def _get_dimensionality_recurse( self, ref: UnitsContainer, exp: Scalar, accumulator: dict[str, int] ) -> None: for key in ref: exp2 = exp * ref[key] if _is_dim(key): try: reg = self._dimensions[key] except KeyError: raise ValueError( f"{key} is not defined as dimension in the pint UnitRegistry" ) if isinstance(reg, DerivedDimensionDefinition): self._get_dimensionality_recurse(reg.reference, exp2, accumulator) else: # DimensionDefinition. accumulator[key] += exp2 else: reg = self._units[self.get_name(key)] if reg.reference is not None: self._get_dimensionality_recurse(reg.reference, exp2, accumulator) def _get_dimensionality_ratio( self, unit1: UnitLike, unit2: UnitLike ) -> Scalar | None: """Get the exponential ratio between two units, i.e. solve unit2 = unit1**x for x. Parameters ---------- unit1 : UnitsContainer compatible (str, Unit, UnitsContainer, dict) first unit unit2 : UnitsContainer compatible (str, Unit, UnitsContainer, dict) second unit Returns ------- number or None exponential proportionality or None if the units cannot be converted """ # shortcut in case of equal units if unit1 == unit2: return 1 dim1, dim2 = (self.get_dimensionality(unit) for unit in (unit1, unit2)) if dim1 == dim2: return 1 elif not dim1 or not dim2 or dim1.keys() != dim2.keys(): # not comparable return None ratios = (dim2[key] / val for key, val in dim1.items()) first = next(ratios) if all(r == first for r in ratios): # all are same, we're good return first return None def get_root_units( self, input_units: UnitLike, check_nonmult: bool = True ) -> tuple[Scalar, UnitT]: """Convert unit or dict of units to the root units. If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. Parameters ---------- input_units : UnitsContainer or str units check_nonmult : bool if True, None will be returned as the multiplicative factor if a non-multiplicative units is found in the final Units. (Default value = True) Returns ------- Number, pint.Unit multiplicative factor, plain units """ input_units = to_units_container(input_units, self) f, units = self._get_root_units(input_units, check_nonmult) return f, self.Unit(units) def _get_conversion_factor( self, src: UnitsContainer, dst: UnitsContainer ) -> Scalar | DimensionalityError: """Get conversion factor in non-multiplicative units. Parameters ---------- src Source units dst Target units Returns ------- Conversion factor or DimensionalityError """ cache = self._cache.conversion_factor try: return cache[(src, dst)] except KeyError: pass src_dim = self._get_dimensionality(src) dst_dim = self._get_dimensionality(dst) # If the source and destination dimensionality are different, # then the conversion cannot be performed. if src_dim != dst_dim: return DimensionalityError(src, dst, src_dim, dst_dim) # Here src and dst have only multiplicative units left. Thus we can # convert with a factor. factor, _ = self._get_root_units(src / dst) cache[(src, dst)] = factor return factor def _get_root_units( self, input_units: UnitsContainer, check_nonmult: bool = True ) -> tuple[Scalar, UnitsContainer]: """Convert unit or dict of units to the root units. If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. Parameters ---------- input_units : UnitsContainer or dict units check_nonmult : bool if True, None will be returned as the multiplicative factor if a non-multiplicative units is found in the final Units. (Default value = True) Returns ------- number, Unit multiplicative factor, plain units """ if not input_units: return 1, self.UnitsContainer() cache = self._cache.root_units try: return cache[input_units] except KeyError: pass accumulators: dict[str | None, int] = defaultdict(int) accumulators[None] = 1 self._get_root_units_recurse(input_units, 1, accumulators) factor = accumulators[None] units = self.UnitsContainer( {k: v for k, v in accumulators.items() if k is not None and v != 0} ) # Check if any of the final units is non multiplicative and return None instead. if check_nonmult: if any(not self._units[unit].converter.is_multiplicative for unit in units): factor = None cache[input_units] = factor, units return factor, units def get_base_units( self, input_units: UnitsContainer | str, check_nonmult: bool = True, system=None, ) -> tuple[Scalar, UnitT]: """Convert unit or dict of units to the plain units. If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. Parameters ---------- input_units : UnitsContainer or str units check_nonmult : bool If True, None will be returned as the multiplicative factor if non-multiplicative units are found in the final Units. (Default value = True) system : (Default value = None) Returns ------- Number, pint.Unit multiplicative factor, plain units """ return self.get_root_units(input_units, check_nonmult) # TODO: accumulators breaks typing list[int, dict[str, int]] # So we have changed the behavior here def _get_root_units_recurse( self, ref: UnitsContainer, exp: Scalar, accumulators: dict[str | None, int] ) -> None: """ accumulators None keeps the scalar prefactor not associated with a specific unit. """ for key in ref: exp2 = exp * ref[key] key = self.get_name(key) reg = self._units[key] if reg.is_base: accumulators[key] += exp2 else: accumulators[None] *= reg.converter.scale**exp2 if reg.reference is not None: self._get_root_units_recurse(reg.reference, exp2, accumulators) def get_compatible_units(self, input_units: QuantityOrUnitLike) -> frozenset[UnitT]: """ """ input_units = to_units_container(input_units) equiv = self._get_compatible_units(input_units) return frozenset(self.Unit(eq) for eq in equiv) def _get_compatible_units( self, input_units: UnitsContainer, *args, **kwargs ) -> frozenset[str]: """ """ if not input_units: return frozenset() src_dim = self._get_dimensionality(input_units) return self._cache.dimensional_equivalents.setdefault(src_dim, frozenset()) # TODO: remove context from here def is_compatible_with( self, obj1: Any, obj2: Any, *contexts: str | Context, **ctx_kwargs ) -> bool: """check if the other object is compatible Parameters ---------- obj1, obj2 The objects to check against each other. Treated as dimensionless if not a Quantity, Unit or str. *contexts : str or pint.Context Contexts to use in the transformation. **ctx_kwargs : Values for the Context/s Returns ------- bool """ if isinstance(obj1, (self.Quantity, self.Unit)): return obj1.is_compatible_with(obj2, *contexts, **ctx_kwargs) if isinstance(obj1, str): return self.parse_expression(obj1).is_compatible_with( obj2, *contexts, **ctx_kwargs ) return not isinstance(obj2, (self.Quantity, self.Unit)) def convert( self, value: T, src: QuantityOrUnitLike, dst: QuantityOrUnitLike, inplace: bool = False, ) -> T: """Convert value from some source to destination units. Parameters ---------- value : value src : pint.Quantity or str source units. dst : pint.Quantity or str destination units. inplace : (Default value = False) Returns ------- type converted value """ src = to_units_container(src, self) dst = to_units_container(dst, self) if src == dst: return value return self._convert(value, src, dst, inplace) def _convert( self, value: T, src: UnitsContainer, dst: UnitsContainer, inplace: bool = False, check_dimensionality: bool = True, ) -> T: """Convert value from some source to destination units. Parameters ---------- value : value src : UnitsContainer source units. dst : UnitsContainer destination units. inplace : (Default value = False) check_dimensionality : (Default value = True) Returns ------- type converted value """ factor = self._get_conversion_factor(src, dst) if isinstance(factor, DimensionalityError): raise factor # factor is type float and if our magnitude is type Decimal then # must first convert to Decimal before we can '*' the values if isinstance(value, Decimal): factor = Decimal(str(factor)) elif isinstance(value, Fraction): factor = Fraction(str(factor)) if inplace: value *= factor else: value = value * factor return value def parse_unit_name( self, unit_name: str, case_sensitive: bool | None = None ) -> tuple[tuple[str, str, str], ...]: """Parse a unit to identify prefix, unit name and suffix by walking the list of prefix and suffix. In case of equivalent combinations (e.g. ('kilo', 'gram', '') and ('', 'kilogram', ''), prefer those with prefix. Parameters ---------- unit_name : case_sensitive : bool or None Control if unit lookup is case sensitive. Defaults to None, which uses the registry's case_sensitive setting Returns ------- tuple of tuples (str, str, str) all non-equivalent combinations of (prefix, unit name, suffix) """ case_sensitive = ( self.case_sensitive if case_sensitive is None else case_sensitive ) return self._dedup_candidates( self._yield_unit_triplets(unit_name, case_sensitive) ) def _yield_unit_triplets( self, unit_name: str, case_sensitive: bool ) -> Generator[tuple[str, str, str], None, None]: """Helper of parse_unit_name.""" stw = unit_name.startswith edw = unit_name.endswith for suffix, prefix in itertools.product(self._suffixes, self._prefixes): if stw(prefix) and edw(suffix): name = unit_name[len(prefix) :] if suffix: name = name[: -len(suffix)] if len(name) == 1: continue if case_sensitive: if name in self._units: yield ( self._prefixes[prefix].name, self._units[name].name, self._suffixes[suffix], ) else: for real_name in self._units_casei.get(name.lower(), ()): yield ( self._prefixes[prefix].name, self._units[real_name].name, self._suffixes[suffix], ) # TODO: keep this for backward compatibility _parse_unit_name = _yield_unit_triplets @staticmethod def _dedup_candidates( candidates: Iterable[tuple[str, str, str]], ) -> tuple[tuple[str, str, str], ...]: """Helper of parse_unit_name. Given an iterable of unit triplets (prefix, name, suffix), remove those with different names but equal value, preferring those with a prefix. e.g. ('kilo', 'gram', '') and ('', 'kilogram', '') """ candidates = dict.fromkeys(candidates) # ordered set for cp, cu, cs in list(candidates): assert isinstance(cp, str) assert isinstance(cu, str) if cs != "": raise NotImplementedError("non-empty suffix") if cp: candidates.pop(("", cp + cu, ""), None) return tuple(candidates) def parse_units( self, input_string: str, as_delta: bool | None = None, case_sensitive: bool | None = None, ) -> UnitT: """Parse a units expression and returns a UnitContainer with the canonical names. The expression can only contain products, ratios and powers of units. Parameters ---------- input_string : str as_delta : bool or None if the expression has multiple units, the parser will interpret non multiplicative units as their `delta_` counterparts. (Default value = None) case_sensitive : bool or None Control if unit parsing is case sensitive. Defaults to None, which uses the registry's setting. Returns ------- pint.Unit """ return self.Unit( self.parse_units_as_container(input_string, as_delta, case_sensitive) ) def parse_units_as_container( self, input_string: str, as_delta: bool | None = None, case_sensitive: bool | None = None, ) -> UnitsContainer: as_delta = ( as_delta if as_delta is not None else True ) # TODO This only exists in nonmultiplicative case_sensitive = ( case_sensitive if case_sensitive is not None else self.case_sensitive ) return self._parse_units_as_container(input_string, as_delta, case_sensitive) def _parse_units_as_container( self, input_string: str, as_delta: bool = True, case_sensitive: bool = True, ) -> UnitsContainer: """Parse a units expression and returns a UnitContainer with the canonical names. """ cache = self._cache.parse_unit # Issue #1097: it is possible, when a unit was defined while a different context # was active, that the unit is in self._cache.parse_unit but not in self._units. # If this is the case, force self._units to be repopulated. if as_delta and input_string in cache and input_string in self._units: return cache[input_string] for p in self.preprocessors: input_string = p(input_string) if not input_string: return self.UnitsContainer() # Sanitize input_string with whitespaces. input_string = input_string.strip() units = ParserHelper.from_string(input_string, self.non_int_type) if units.scale != 1: raise ValueError("Unit expression cannot have a scaling factor.") ret = self.UnitsContainer({}) many = len(units) > 1 for name in units: cname = self.get_name(name, case_sensitive=case_sensitive) value = units[name] if not cname: continue if as_delta and (many or (not many and value != 1)): definition = self._units[cname] if not definition.is_multiplicative: cname = "delta_" + cname ret = ret.add(cname, value) if as_delta: cache[input_string] = ret return ret def _eval_token( self, token: TokenInfo, case_sensitive: bool | None = None, **values: QuantityArgument, ): """Evaluate a single token using the following rules: 1. numerical values as strings are replaced by their numeric counterparts - integers are parsed as integers - other numeric values are parses of non_int_type 2. strings in (inf, infinity, nan, dimensionless) with their numerical value. 3. strings in values.keys() are replaced by Quantity(values[key]) 4. in other cases, the values are parsed as units and replaced by their canonical name. Parameters ---------- token Token to evaluate. case_sensitive, optional If true, a case sensitive matching of the unit name will be done in the registry. If false, a case INsensitive matching of the unit name will be done in the registry. (Default value = None, which uses registry setting) **values Other string that will be parsed using the Quantity constructor on their corresponding value. """ token_type = token[0] token_text = token[1] if token_type == NAME: if token_text == "dimensionless": return self.Quantity(1) elif token_text.lower() in ("inf", "infinity"): return self.non_int_type("inf") elif token_text.lower() == "nan": return self.non_int_type("nan") elif token_text in values: return self.Quantity(values[token_text]) else: return self.Quantity( 1, self.UnitsContainer( {self.get_name(token_text, case_sensitive=case_sensitive): 1} ), ) elif token_type == NUMBER: return ParserHelper.eval_token(token, non_int_type=self.non_int_type) else: raise Exception("unknown token type") def parse_pattern( self, input_string: str, pattern: str, case_sensitive: bool | None = None, many: bool = False, ) -> list[str] | str | None: """Parse a string with a given regex pattern and returns result. Parameters ---------- input_string pattern_string: The regex parse string case_sensitive, optional If true, a case sensitive matching of the unit name will be done in the registry. If false, a case INsensitive matching of the unit name will be done in the registry. (Default value = None, which uses registry setting) many, optional Match many results (Default value = False) """ if not input_string: return [] if many else None # Parse string regex = pattern_to_regex(pattern) matched = re.finditer(regex, input_string) # Extract result(s) results = [] for match in matched: # Extract units from result match = match.groupdict() # Parse units units = [ float(value) * self.parse_expression(unit, case_sensitive) for unit, value in match.items() ] # Add to results results.append(units) # Return first match only if not many: return results[0] return results def parse_expression( self: Self, input_string: str, case_sensitive: bool | None = None, **values: QuantityArgument, ) -> QuantityT: """Parse a mathematical expression including units and return a quantity object. Numerical constants can be specified as keyword arguments and will take precedence over the names defined in the registry. Parameters ---------- input_string case_sensitive, optional If true, a case sensitive matching of the unit name will be done in the registry. If false, a case INsensitive matching of the unit name will be done in the registry. (Default value = None, which uses registry setting) **values Other string that will be parsed using the Quantity constructor on their corresponding value. """ if not input_string: return self.Quantity(1) for p in self.preprocessors: input_string = p(input_string) input_string = string_preprocessor(input_string) gen = pint_eval.tokenizer(input_string) def _define_op(s: str): return self._eval_token(s, case_sensitive=case_sensitive, **values) return build_eval_tree(gen).evaluate(_define_op) # We put this last to avoid overriding UnitsContainer # and I do not want to rename it. # TODO: Maybe in the future we need to change it to a more meaningful # non-colliding name. def UnitsContainer(self, *args: Any, **kwargs: Any) -> UnitsContainer: return UnitsContainer(*args, non_int_type=self.non_int_type, **kwargs) __call__ = parse_expression class PlainRegistry(GenericPlainRegistry[PlainQuantity[Any], PlainUnit]): Quantity: TypeAlias = PlainQuantity[Any] Unit: TypeAlias = PlainUnit pint-0.24.4/pint/facets/plain/unit.py000066400000000000000000000221351471316474000174400ustar00rootroot00000000000000""" pint.facets.plain.unit ~~~~~~~~~~~~~~~~~~~~~ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import copy import locale import operator from numbers import Number from typing import TYPE_CHECKING, Any from ..._typing import UnitLike from ...compat import NUMERIC_TYPES, deprecated from ...errors import DimensionalityError from ...util import PrettyIPython, SharedRegistryObject, UnitsContainer from .definitions import UnitDefinition if TYPE_CHECKING: from ..context import Context class PlainUnit(PrettyIPython, SharedRegistryObject): """Implements a class to describe a unit supporting math operations.""" def __reduce__(self): # See notes in Quantity.__reduce__ from pint import _unpickle_unit return _unpickle_unit, (PlainUnit, self._units) def __init__(self, units: UnitLike) -> None: super().__init__() if isinstance(units, (UnitsContainer, UnitDefinition)): self._units = units elif isinstance(units, str): self._units = self._REGISTRY.parse_units(units)._units elif isinstance(units, PlainUnit): self._units = units._units else: raise TypeError( "units must be of type str, Unit or " "UnitsContainer; not {}.".format( type(units) ) ) def __copy__(self) -> PlainUnit: ret = self.__class__(self._units) return ret def __deepcopy__(self, memo) -> PlainUnit: ret = self.__class__(copy.deepcopy(self._units, memo)) return ret @deprecated( "This function will be removed in future versions of pint.\n" "Use ureg.formatter.format_unit_babel" ) def format_babel(self, spec: str = "", **kwspec: Any) -> str: return self._REGISTRY.formatter.format_unit_babel(self, spec, **kwspec) def __format__(self, spec: str) -> str: return self._REGISTRY.formatter.format_unit(self, spec) def __str__(self) -> str: return self._REGISTRY.formatter.format_unit(self) def __bytes__(self) -> bytes: return str(self).encode(locale.getpreferredencoding()) def __repr__(self) -> str: return f"" @property def dimensionless(self) -> bool: """Return True if the PlainUnit is dimensionless; False otherwise.""" return not bool(self.dimensionality) @property def dimensionality(self) -> UnitsContainer: """ Returns ------- dict Dimensionality of the PlainUnit, e.g. ``{length: 1, time: -1}`` """ try: return self._dimensionality except AttributeError: dim = self._REGISTRY._get_dimensionality(self._units) self._dimensionality = dim return self._dimensionality def compatible_units(self, *contexts): if contexts: with self._REGISTRY.context(*contexts): return self._REGISTRY.get_compatible_units(self) return self._REGISTRY.get_compatible_units(self) def is_compatible_with( self, other: Any, *contexts: str | Context, **ctx_kwargs: Any ) -> bool: """check if the other object is compatible Parameters ---------- other The object to check. Treated as dimensionless if not a Quantity, PlainUnit or str. *contexts : str or pint.Context Contexts to use in the transformation. **ctx_kwargs : Values for the Context/s Returns ------- bool """ from .quantity import PlainQuantity if contexts or self._REGISTRY._active_ctx: try: (1 * self).to(other, *contexts, **ctx_kwargs) return True except DimensionalityError: return False if isinstance(other, (PlainQuantity, PlainUnit)): return self.dimensionality == other.dimensionality if isinstance(other, str): return ( self.dimensionality == self._REGISTRY.parse_units(other).dimensionality ) return self.dimensionless def __mul__(self, other): if self._check(other): if isinstance(other, self.__class__): return self.__class__(self._units * other._units) else: qself = self._REGISTRY.Quantity(1, self._units) return qself * other if isinstance(other, Number) and other == 1: return self._REGISTRY.Quantity(other, self._units) return self._REGISTRY.Quantity(1, self._units) * other __rmul__ = __mul__ def __truediv__(self, other): if self._check(other): if isinstance(other, self.__class__): return self.__class__(self._units / other._units) else: qself = 1 * self return qself / other return self._REGISTRY.Quantity(1 / other, self._units) def __rtruediv__(self, other): # As PlainUnit and Quantity both handle truediv with each other rtruediv can # only be called for something different. if isinstance(other, NUMERIC_TYPES): return self._REGISTRY.Quantity(other, 1 / self._units) elif isinstance(other, UnitsContainer): return self.__class__(other / self._units) return NotImplemented __div__ = __truediv__ __rdiv__ = __rtruediv__ def __pow__(self, other) -> PlainUnit: if isinstance(other, NUMERIC_TYPES): return self.__class__(self._units**other) else: mess = f"Cannot power PlainUnit by {type(other)}" raise TypeError(mess) def __hash__(self) -> int: return self._units.__hash__() def __eq__(self, other) -> bool: # We compare to the plain class of PlainUnit because each PlainUnit class is # unique. if self._check(other): if isinstance(other, self.__class__): return self._units == other._units else: return other == self._REGISTRY.Quantity(1, self._units) elif isinstance(other, NUMERIC_TYPES): return other == self._REGISTRY.Quantity(1, self._units) else: return self._units == other def __ne__(self, other) -> bool: return not (self == other) def compare(self, other, op) -> bool: self_q = self._REGISTRY.Quantity(1, self) if isinstance(other, NUMERIC_TYPES): return self_q.compare(other, op) elif isinstance(other, (PlainUnit, UnitsContainer, dict)): return self_q.compare(self._REGISTRY.Quantity(1, other), op) return NotImplemented __lt__ = lambda self, other: self.compare(other, op=operator.lt) __le__ = lambda self, other: self.compare(other, op=operator.le) __ge__ = lambda self, other: self.compare(other, op=operator.ge) __gt__ = lambda self, other: self.compare(other, op=operator.gt) def __int__(self) -> int: return int(self._REGISTRY.Quantity(1, self._units)) def __float__(self) -> float: return float(self._REGISTRY.Quantity(1, self._units)) def __complex__(self) -> complex: return complex(self._REGISTRY.Quantity(1, self._units)) @property def systems(self): out = set() for uname in self._units.keys(): for sname, sys in self._REGISTRY._systems.items(): if uname in sys.members: out.add(sname) return frozenset(out) def from_(self, value, strict=True, name="value"): """Converts a numerical value or quantity to this unit Parameters ---------- value : a Quantity (or numerical value if strict=False) to convert strict : boolean to indicate that only quantities are accepted (Default value = True) name : descriptive name to use if an exception occurs (Default value = "value") Returns ------- type The converted value as this unit """ if self._check(value): if not isinstance(value, self._REGISTRY.Quantity): value = self._REGISTRY.Quantity(1, value) return value.to(self) elif strict: raise ValueError("%s must be a Quantity" % value) else: return value * self def m_from(self, value, strict=True, name="value"): """Converts a numerical value or quantity to this unit, then returns the magnitude of the converted value Parameters ---------- value : a Quantity (or numerical value if strict=False) to convert strict : boolean to indicate that only quantities are accepted (Default value = True) name : descriptive name to use if an exception occurs (Default value = "value") Returns ------- type The magnitude of the converted value """ return self.from_(value, strict=strict, name=name).magnitude pint-0.24.4/pint/facets/system/000077500000000000000000000000001471316474000163255ustar00rootroot00000000000000pint-0.24.4/pint/facets/system/__init__.py000066400000000000000000000007321471316474000204400ustar00rootroot00000000000000""" pint.facets.system ~~~~~~~~~~~~~~~~~~ Adds pint the capability to system of units. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from .definitions import SystemDefinition from .objects import System from .registry import GenericSystemRegistry, SystemRegistry __all__ = ["SystemDefinition", "System", "SystemRegistry", "GenericSystemRegistry"] pint-0.24.4/pint/facets/system/definitions.py000066400000000000000000000056421471316474000212210ustar00rootroot00000000000000""" pint.facets.systems.definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from collections.abc import Iterable from dataclasses import dataclass from ... import errors from ...compat import Self @dataclass(frozen=True) class BaseUnitRule: """A rule to define a base unit within a system.""" #: name of the unit to become base unit #: (must exist in the registry) new_unit_name: str #: name of the unit to be kicked out to make room for the new base uni #: If None, the current base unit with the same dimensionality will be used old_unit_name: str | None = None # Instead of defining __post_init__ here, # it will be added to the container class # so that the name and a meaningfull class # could be used. @dataclass(frozen=True) class SystemDefinition(errors.WithDefErr): """Definition of a System.""" #: name of the system name: str #: unit groups that will be included within the system using_group_names: tuple[str, ...] #: rules to define new base unit within the system. rules: tuple[BaseUnitRule, ...] @classmethod def from_lines( cls: type[Self], lines: Iterable[str], non_int_type: type ) -> Self | None: # TODO: this is to keep it backwards compatible # TODO: check when is None returned. from ...delegates import ParserConfig, txt_defparser cfg = ParserConfig(non_int_type) parser = txt_defparser.DefParser(cfg, None) pp = parser.parse_string("\n".join(lines) + "\n@end") for definition in parser.iter_parsed_project(pp): if isinstance(definition, cls): return definition @property def unit_replacements(self) -> tuple[tuple[str, str | None], ...]: # TODO: check if None can be dropped. return tuple((el.new_unit_name, el.old_unit_name) for el in self.rules) def __post_init__(self): if not errors.is_valid_system_name(self.name): raise self.def_err(errors.MSG_INVALID_SYSTEM_NAME) for k in self.using_group_names: if not errors.is_valid_group_name(k): raise self.def_err( f"refers to '{k}' that " + errors.MSG_INVALID_GROUP_NAME ) for ndx, rule in enumerate(self.rules, 1): if not errors.is_valid_unit_name(rule.new_unit_name): raise self.def_err( f"rule #{ndx} refers to '{rule.new_unit_name}' that " + errors.MSG_INVALID_UNIT_NAME ) if rule.old_unit_name and not errors.is_valid_unit_name(rule.old_unit_name): raise self.def_err( f"rule #{ndx} refers to '{rule.old_unit_name}' that " + errors.MSG_INVALID_UNIT_NAME ) pint-0.24.4/pint/facets/system/objects.py000066400000000000000000000153431471316474000203360ustar00rootroot00000000000000""" pint.facets.systems.objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import numbers from collections.abc import Callable, Iterable from numbers import Number from typing import Any, Generic from ..._typing import UnitLike from ...babel_names import _babel_systems from ...compat import babel_parse from ...util import ( SharedRegistryObject, getattr_maybe_raise, logger, to_units_container, ) from .. import group from ..plain import MagnitudeT from .definitions import SystemDefinition GetRootUnits = Callable[[UnitLike, bool], tuple[Number, UnitLike]] class SystemQuantity(Generic[MagnitudeT], group.GroupQuantity[MagnitudeT]): pass class SystemUnit(group.GroupUnit): pass class System(SharedRegistryObject): """A system is a Group plus a set of plain units. Members are computed dynamically, that is if a unit is added to a group X all groups that include X are affected. The System belongs to one Registry. See SystemDefinition for the definition file syntax. Parameters ---------- name Name of the group. """ def __init__(self, name: str): #: Name of the system #: :type: str self.name = name #: Maps root unit names to a dict indicating the new unit and its exponent. self.base_units: dict[str, dict[str, numbers.Number]] = {} #: Derived unit names. self.derived_units: set[str] = set() #: Names of the _used_groups in used by this system. self._used_groups: set[str] = set() self._computed_members: frozenset[str] | None = None # Add this system to the system dictionary self._REGISTRY._systems[self.name] = self def __dir__(self): return list(self.members) def __getattr__(self, item: str) -> Any: getattr_maybe_raise(self, item) u = getattr(self._REGISTRY, self.name + "_" + item, None) if u is not None: return u return getattr(self._REGISTRY, item) @property def members(self): d = self._REGISTRY._groups if self._computed_members is None: tmp: set[str] = set() for group_name in self._used_groups: try: tmp |= d[group_name].members except KeyError: logger.warning( "Could not resolve {} in System {}".format( group_name, self.name ) ) self._computed_members = frozenset(tmp) return self._computed_members def invalidate_members(self): """Invalidate computed members in this Group and all parent nodes.""" self._computed_members = None def add_groups(self, *group_names: str) -> None: """Add groups to group.""" self._used_groups |= set(group_names) self.invalidate_members() def remove_groups(self, *group_names: str) -> None: """Remove groups from group.""" self._used_groups -= set(group_names) self.invalidate_members() def format_babel(self, locale: str) -> str: """translate the name of the system.""" if locale and self.name in _babel_systems: name = _babel_systems[self.name] locale = babel_parse(locale) return locale.measurement_systems[name] return self.name # TODO: When 3.11 is minimal version, use Self @classmethod def from_lines( cls: type[System], lines: Iterable[str], get_root_func: GetRootUnits, non_int_type: type = float, ) -> System: # TODO: we changed something here it used to be # system_definition = SystemDefinition.from_lines(lines, get_root_func) system_definition = SystemDefinition.from_lines(lines, non_int_type) if system_definition is None: raise ValueError(f"Could not define System from from {lines}") return cls.from_definition(system_definition, get_root_func) @classmethod def from_definition( cls: type[System], system_definition: SystemDefinition, get_root_func: GetRootUnits | None = None, ) -> System: if get_root_func is None: # TODO: kept for backwards compatibility get_root_func = cls._REGISTRY.get_root_units base_unit_names = {} derived_unit_names = [] for new_unit, old_unit in system_definition.unit_replacements: if old_unit is None: old_unit_dict = to_units_container(get_root_func(new_unit)[1]) if len(old_unit_dict) != 1: raise ValueError( "The new unit must be a root dimension if not discarded unit is specified." ) old_unit, value = dict(old_unit_dict).popitem() base_unit_names[old_unit] = {new_unit: 1 / value} else: # The old unit MUST be a root unit, if not raise an error. if old_unit != str(get_root_func(old_unit)[1]): raise ValueError( f"The old unit {old_unit} must be a root unit " f"in order to be replaced by new unit {new_unit}" ) # Here we find new_unit expanded in terms of root_units new_unit_expanded = to_units_container( get_root_func(new_unit)[1], cls._REGISTRY ) # We require that the old unit is present in the new_unit expanded if old_unit not in new_unit_expanded: raise ValueError("Old unit must be a component of new unit") # Here we invert the equation, in other words # we write old units in terms new unit and expansion new_unit_dict = { new_unit: -1 / value for new_unit, value in new_unit_expanded.items() if new_unit != old_unit } new_unit_dict[new_unit] = 1 / new_unit_expanded[old_unit] base_unit_names[old_unit] = new_unit_dict system = cls(system_definition.name) system.add_groups(*system_definition.using_group_names) system.base_units.update(**base_unit_names) system.derived_units |= set(derived_unit_names) return system class Lister: def __init__(self, d: dict[str, Any]): self.d = d def __dir__(self) -> list[str]: return list(self.d.keys()) def __getattr__(self, item: str) -> Any: getattr_maybe_raise(self, item) return self.d[item] pint-0.24.4/pint/facets/system/registry.py000066400000000000000000000201111471316474000205420ustar00rootroot00000000000000""" pint.facets.systems.registry ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from numbers import Number from typing import TYPE_CHECKING, Any, Generic from ... import errors from ...compat import TypeAlias from ..plain import QuantityT, UnitT if TYPE_CHECKING: from ..._typing import Quantity, Unit from ..._typing import UnitLike from ...util import UnitsContainer as UnitsContainerT from ...util import ( create_class_with_registry, to_units_container, ) from ..group import GenericGroupRegistry from . import objects from .definitions import SystemDefinition class GenericSystemRegistry( Generic[QuantityT, UnitT], GenericGroupRegistry[QuantityT, UnitT] ): """Handle of Systems. Conversion between units with different dimensions according to previously established relations (contexts). (e.g. in the spectroscopy, conversion between frequency and energy is possible) Capabilities: - Register systems. - List systems - Get or get the default system. - Parse @group directive. """ # TODO: Change this to System: System to specify class # and use introspection to get system class as a way # to enjoy typing goodies System: type[objects.System] def __init__(self, system: str | None = None, **kwargs): super().__init__(**kwargs) #: Map system name to system. self._systems: dict[str, objects.System] = {} #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) self._base_units_cache: dict[UnitsContainerT, UnitsContainerT] = {} self._default_system_name: str | None = system def _init_dynamic_classes(self) -> None: """Generate subclasses on the fly and attach them to self""" super()._init_dynamic_classes() self.System = create_class_with_registry(self, objects.System) def _after_init(self) -> None: """Invoked at the end of ``__init__``. - Create default group and add all orphan units to it - Set default system """ super()._after_init() #: System name to be used by default. self._default_system_name = self._default_system_name or self._defaults.get( "system", None ) def _register_definition_adders(self) -> None: super()._register_definition_adders() self._register_adder(SystemDefinition, self._add_system) def _add_system(self, sd: SystemDefinition) -> None: if sd.name in self._systems: raise ValueError(f"System {sd.name} already present in registry") try: # As a System is a SharedRegistryObject # it adds itself to the registry. self.System.from_definition(sd) except KeyError as e: # TODO: fix this error message raise errors.DefinitionError(f"unknown dimension {e} in context") @property def sys(self): return objects.Lister(self._systems) @property def default_system(self) -> str | None: return self._default_system_name @default_system.setter def default_system(self, name: str) -> None: if name: if name not in self._systems: raise ValueError("Unknown system %s" % name) self._base_units_cache = {} self._default_system_name = name def get_system(self, name: str, create_if_needed: bool = True) -> objects.System: """Return a Group. Parameters ---------- name : str Name of the group to be. create_if_needed : bool If True, create a group if not found. If False, raise an Exception. (Default value = True) Returns ------- type System """ if name in self._systems: return self._systems[name] if not create_if_needed: raise ValueError("Unknown system %s" % name) return self.System(name) def get_base_units( self, input_units: UnitLike | Quantity, check_nonmult: bool = True, system: str | objects.System | None = None, ) -> tuple[Number, Unit]: """Convert unit or dict of units to the plain units. If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. Unlike PlainRegistry, in this registry root_units might be different from base_units Parameters ---------- input_units : UnitsContainer or str units check_nonmult : bool if True, None will be returned as the multiplicative factor if a non-multiplicative units is found in the final Units. (Default value = True) system : (Default value = None) Returns ------- type multiplicative factor, plain units """ input_units = to_units_container(input_units) f, units = self._get_base_units(input_units, check_nonmult, system) return f, self.Unit(units) def _get_base_units( self, input_units: UnitsContainerT, check_nonmult: bool = True, system: str | objects.System | None = None, ): if system is None: system = self._default_system_name # The cache is only done for check_nonmult=True and the current system. if ( check_nonmult and system == self._default_system_name and input_units in self._base_units_cache ): return self._base_units_cache[input_units] factor, units = self.get_root_units(input_units, check_nonmult) if not system: return factor, units # This will not be necessary after integration with the registry # as it has a UnitsContainer intermediate units = to_units_container(units, self) destination_units = self.UnitsContainer() bu = self.get_system(system, False).base_units for unit, value in units.items(): if unit in bu: new_unit = bu[unit] new_unit = to_units_container(new_unit, self) destination_units *= new_unit**value else: destination_units *= self.UnitsContainer({unit: value}) base_factor = self.convert(factor, units, destination_units) if check_nonmult: self._base_units_cache[input_units] = base_factor, destination_units return base_factor, destination_units def get_compatible_units( self, input_units: UnitsContainerT, group_or_system: str | None = None ) -> frozenset[Unit]: """ """ group_or_system = group_or_system or self._default_system_name if group_or_system is None: return super().get_compatible_units(input_units) input_units = to_units_container(input_units) equiv = self._get_compatible_units(input_units, group_or_system) return frozenset(self.Unit(eq) for eq in equiv) def _get_compatible_units( self, input_units: UnitsContainerT, group_or_system: str | None = None ) -> frozenset[Unit]: if group_or_system and group_or_system in self._systems: members = self._systems[group_or_system].members # group_or_system has been handled by System return frozenset(members & super()._get_compatible_units(input_units)) try: # This will be handled by groups return super()._get_compatible_units(input_units, group_or_system) except ValueError as ex: # It might be also a system if "Unknown Group" in str(ex): raise ValueError( "Unknown Group o System with name '%s'" % group_or_system ) from ex raise ex class SystemRegistry( GenericSystemRegistry[objects.SystemQuantity[Any], objects.SystemUnit] ): Quantity: TypeAlias = objects.SystemQuantity[Any] Unit: TypeAlias = objects.SystemUnit pint-0.24.4/pint/formatting.py000066400000000000000000000111331471316474000162570ustar00rootroot00000000000000""" pint.formatter ~~~~~~~~~~~~~~ Format units for pint. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from numbers import Number from typing import Iterable from .delegates.formatter._format_helpers import ( _PRETTY_EXPONENTS, # noqa: F401 ) from .delegates.formatter._format_helpers import ( join_u as _join, # noqa: F401 ) from .delegates.formatter._format_helpers import ( pretty_fmt_exponent as _pretty_fmt_exponent, # noqa: F401 ) from .delegates.formatter._spec_helpers import ( _BASIC_TYPES, # noqa: F401 FORMATTER, # noqa: F401 REGISTERED_FORMATTERS, extract_custom_flags, # noqa: F401 remove_custom_flags, # noqa: F401 ) from .delegates.formatter._spec_helpers import ( parse_spec as _parse_spec, # noqa: F401 ) from .delegates.formatter._spec_helpers import ( split_format as split_format, # noqa: F401 ) # noqa from .delegates.formatter._to_register import register_unit_format # noqa: F401 # Backwards compatiblity stuff from .delegates.formatter.latex import ( _EXP_PATTERN, # noqa: F401 latex_escape, # noqa: F401 matrix_to_latex, # noqa: F401 ndarray_to_latex, # noqa: F401 ndarray_to_latex_parts, # noqa: F401 siunitx_format_unit, # noqa: F401 vector_to_latex, # noqa: F401 ) def formatter( items: Iterable[tuple[str, Number]], as_ratio: bool = True, single_denominator: bool = False, product_fmt: str = " * ", division_fmt: str = " / ", power_fmt: str = "{} ** {}", parentheses_fmt: str = "({0})", exp_call: FORMATTER = "{:n}".format, sort: bool = True, ) -> str: """Format a list of (name, exponent) pairs. Parameters ---------- items : list a list of (name, exponent) pairs. as_ratio : bool, optional True to display as ratio, False as negative powers. (Default value = True) single_denominator : bool, optional all with terms with negative exponents are collected together. (Default value = False) product_fmt : str the format used for multiplication. (Default value = " * ") division_fmt : str the format used for division. (Default value = " / ") power_fmt : str the format used for exponentiation. (Default value = "{} ** {}") parentheses_fmt : str the format used for parenthesis. (Default value = "({0})") exp_call : callable (Default value = lambda x: f"{x:n}") sort : bool, optional True to sort the formatted units alphabetically (Default value = True) Returns ------- str the formula as a string. """ join_u = _join if sort is False: items = tuple(items) else: items = sorted(items) if not items: return "" if as_ratio: fun = lambda x: exp_call(abs(x)) else: fun = exp_call pos_terms, neg_terms = [], [] for key, value in items: if value == 1: pos_terms.append(key) elif value > 0: pos_terms.append(power_fmt.format(key, fun(value))) elif value == -1 and as_ratio: neg_terms.append(key) else: neg_terms.append(power_fmt.format(key, fun(value))) if not as_ratio: # Show as Product: positive * negative terms ** -1 return _join(product_fmt, pos_terms + neg_terms) # Show as Ratio: positive terms / negative terms pos_ret = _join(product_fmt, pos_terms) or "1" if not neg_terms: return pos_ret if single_denominator: neg_ret = join_u(product_fmt, neg_terms) if len(neg_terms) > 1: neg_ret = parentheses_fmt.format(neg_ret) else: neg_ret = join_u(division_fmt, neg_terms) # TODO: first or last pos_ret should be pluralized return _join(division_fmt, [pos_ret, neg_ret]) def format_unit(unit, spec: str, registry=None, **options): # registry may be None to allow formatting `UnitsContainer` objects # in that case, the spec may not be "Lx" if not unit: if spec.endswith("%"): return "" else: return "dimensionless" if not spec: spec = "D" if registry is None: _formatter = REGISTERED_FORMATTERS.get(spec, None) else: try: _formatter = registry.formatter._formatters[spec] except Exception: _formatter = registry.formatter._formatters.get(spec, None) if _formatter is None: raise ValueError(f"Unknown conversion specified: {spec}") return _formatter.format_unit(unit) pint-0.24.4/pint/matplotlib.py000066400000000000000000000051001471316474000162510ustar00rootroot00000000000000""" pint.matplotlib ~~~~~~~~~~~~~~~ Functions and classes related to working with Matplotlib's support for plotting with units. :copyright: 2017 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import matplotlib.units from .util import iterable, sized class PintAxisInfo(matplotlib.units.AxisInfo): """Support default axis and tick labeling and default limits.""" def __init__(self, units): """Set the default label to the pretty-print of the unit.""" formatter = units._REGISTRY.mpl_formatter super().__init__(label=formatter.format(units)) class PintConverter(matplotlib.units.ConversionInterface): """Implement support for pint within matplotlib's unit conversion framework.""" def __init__(self, registry): super().__init__() self._reg = registry def convert(self, value, unit, axis): """Convert :`Quantity` instances for matplotlib to use.""" # Short circuit for arrays if hasattr(value, "units"): return value.to(unit).magnitude if iterable(value): return [self._convert_value(v, unit, axis) for v in value] return self._convert_value(value, unit, axis) def _convert_value(self, value, unit, axis): """Handle converting using attached unit or falling back to axis units.""" if hasattr(value, "units"): return value.to(unit).magnitude return self._reg.Quantity(value, axis.get_units()).to(unit).magnitude @staticmethod def axisinfo(unit, axis): """Return axis information for this particular unit.""" return PintAxisInfo(unit) @staticmethod def default_units(x, axis): """Get the default unit to use for the given combination of unit and axis.""" if iterable(x) and sized(x): return getattr(x[0], "units", None) return getattr(x, "units", None) def setup_matplotlib_handlers(registry, enable): """Set up matplotlib's unit support to handle units from a registry. Parameters ---------- registry : pint.UnitRegistry The registry that will be used. enable : bool Whether support should be enabled or disabled. Returns ------- """ if matplotlib.__version__ < "2.0": raise RuntimeError("Matplotlib >= 2.0 required to work with pint.") if enable: matplotlib.units.registry[registry.Quantity] = PintConverter(registry) else: matplotlib.units.registry.pop(registry.Quantity, None) pint-0.24.4/pint/pint_convert.py000066400000000000000000000132611471316474000166230ustar00rootroot00000000000000#!/usr/bin/env python3 """ pint-convert ~~~~~~~~~~~~ :copyright: 2020 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import argparse import contextlib import re from pint import UnitRegistry parser = argparse.ArgumentParser(description="Unit converter.", usage=argparse.SUPPRESS) parser.add_argument( "-s", "--system", metavar="sys", default="SI", help="unit system to convert to (default: SI)", ) parser.add_argument( "-p", "--prec", metavar="n", type=int, default=12, help="number of maximum significant figures (default: 12)", ) parser.add_argument( "-u", "--prec-unc", metavar="n", type=int, default=2, help="number of maximum uncertainty digits (default: 2)", ) parser.add_argument( "-U", "--with-unc", dest="unc", action="store_true", help="consider uncertainties in constants", ) parser.add_argument( "-C", "--no-corr", dest="corr", action="store_false", help="ignore correlations between constants", ) parser.add_argument( "fr", metavar="from", type=str, help="unit or quantity to convert from" ) parser.add_argument("to", type=str, nargs="?", help="unit to convert to") try: args = parser.parse_args() except SystemExit: parser.print_help() raise ureg = UnitRegistry() ureg.auto_reduce_dimensions = True ureg.autoconvert_offset_to_baseunit = True ureg.enable_contexts("Gau", "ESU", "sp", "energy", "boltzmann") ureg.default_system = args.system def _set(key: str, value): obj = ureg._units[key].converter object.__setattr__(obj, "scale", value) if args.unc: try: import uncertainties except ImportError: raise Exception( "Failed to import uncertainties library!\n Please install uncertainties package" ) # Measured constants subject to correlation # R_i: Rydberg constant # g_e: Electron g factor # m_u: Atomic mass constant # m_e: Electron mass # m_p: Proton mass # m_n: Neutron mass R_i = (ureg._units["R_inf"].converter.scale, 0.0000000000021e7) g_e = (ureg._units["g_e"].converter.scale, 0.00000000000035) m_u = (ureg._units["m_u"].converter.scale, 0.00000000050e-27) m_e = (ureg._units["m_e"].converter.scale, 0.00000000028e-30) m_p = (ureg._units["m_p"].converter.scale, 0.00000000051e-27) m_n = (ureg._units["m_n"].converter.scale, 0.00000000095e-27) if args.corr: # Correlation matrix between measured constants (to be completed below) # R_i g_e m_u m_e m_p m_n corr = [ [1.0, -0.00206, 0.00369, 0.00436, 0.00194, 0.00233], # R_i [-0.00206, 1.0, 0.99029, 0.99490, 0.97560, 0.52445], # g_e [0.00369, 0.99029, 1.0, 0.99536, 0.98516, 0.52959], # m_u [0.00436, 0.99490, 0.99536, 1.0, 0.98058, 0.52714], # m_e [0.00194, 0.97560, 0.98516, 0.98058, 1.0, 0.51521], # m_p [0.00233, 0.52445, 0.52959, 0.52714, 0.51521, 1.0], ] # m_n try: (R_i, g_e, m_u, m_e, m_p, m_n) = uncertainties.correlated_values_norm( [R_i, g_e, m_u, m_e, m_p, m_n], corr ) except AttributeError: raise Exception( "Correlation cannot be calculated!\n Please install numpy package" ) else: R_i = uncertainties.ufloat(*R_i) g_e = uncertainties.ufloat(*g_e) m_u = uncertainties.ufloat(*m_u) m_e = uncertainties.ufloat(*m_e) m_p = uncertainties.ufloat(*m_p) m_n = uncertainties.ufloat(*m_n) _set("R_inf", R_i) _set("g_e", g_e) _set("m_u", m_u) _set("m_e", m_e) _set("m_p", m_p) _set("m_n", m_n) # Measured constants with zero correlation _set( "gravitational_constant", uncertainties.ufloat( ureg._units["gravitational_constant"].converter.scale, 0.00015e-11 ), ) _set( "d_220", uncertainties.ufloat(ureg._units["d_220"].converter.scale, 0.000000032e-10), ) _set( "K_alpha_Cu_d_220", uncertainties.ufloat( ureg._units["K_alpha_Cu_d_220"].converter.scale, 0.00000022 ), ) _set( "K_alpha_Mo_d_220", uncertainties.ufloat( ureg._units["K_alpha_Mo_d_220"].converter.scale, 0.00000019 ), ) _set( "K_alpha_W_d_220", uncertainties.ufloat( ureg._units["K_alpha_W_d_220"].converter.scale, 0.000000098 ), ) ureg._root_units_cache = {} ureg._build_cache() def convert(u_from, u_to=None, unc=None, factor=None): prec_unc = 0 q = ureg.Quantity(u_from) fmt = f".{args.prec}g" if unc: q = q.plus_minus(unc) if u_to: nq = q.to(u_to) else: nq = q.to_base_units() if factor: q *= ureg.Quantity(factor) nq *= ureg.Quantity(factor).to_base_units() if args.unc: prec_unc = use_unc(nq.magnitude, fmt, args.prec_unc) if prec_unc > 0: fmt = f".{prec_unc}uS" else: with contextlib.suppress(Exception): nq = nq.magnitude.n * nq.units fmt = "{:" + fmt + "} {:~P}" print(("{:} = " + fmt).format(q, nq.magnitude, nq.units)) def use_unc(num, fmt, prec_unc): unc = 0 with contextlib.suppress(Exception): if isinstance(num, uncertainties.UFloat): full = ("{:" + fmt + "}").format(num) unc = re.search(r"\+/-[0.]*([\d.]*)", full).group(1) unc = len(unc.replace(".", "")) return max(0, min(prec_unc, unc)) def main(): convert(args.fr, args.to) if __name__ == "__main__": main() pint-0.24.4/pint/pint_eval.py000066400000000000000000000461271471316474000161010ustar00rootroot00000000000000""" pint.pint_eval ~~~~~~~~~~~~~~ An expression evaluator to be used as a safe replacement for builtin eval. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import operator import token as tokenlib import tokenize from io import BytesIO from tokenize import TokenInfo from typing import Any try: from uncertainties import ufloat HAS_UNCERTAINTIES = True except ImportError: HAS_UNCERTAINTIES = False ufloat = None from .errors import DefinitionSyntaxError # For controlling order of operations _OP_PRIORITY = { "+/-": 4, "**": 3, "^": 3, "unary": 2, "*": 1, "": 1, # operator for implicit ops "//": 1, "/": 1, "%": 1, "+": 0, "-": 0, } def _ufloat(left, right): if HAS_UNCERTAINTIES: return ufloat(left, right) raise TypeError("Could not import support for uncertainties") def _power(left: Any, right: Any) -> Any: from . import Quantity from .compat import is_duck_array if ( isinstance(left, Quantity) and is_duck_array(left.magnitude) and left.dtype.kind not in "cf" and right < 0 ): left = left.astype(float) return operator.pow(left, right) # https://stackoverflow.com/a/1517965/1291237 class tokens_with_lookahead: def __init__(self, iter): self.iter = iter self.buffer = [] def __iter__(self): return self def __next__(self): if self.buffer: return self.buffer.pop(0) else: return self.iter.__next__() def lookahead(self, n): """Return an item n entries ahead in the iteration.""" while n >= len(self.buffer): try: self.buffer.append(self.iter.__next__()) except StopIteration: return None return self.buffer[n] def _plain_tokenizer(input_string): for tokinfo in tokenize.tokenize(BytesIO(input_string.encode("utf-8")).readline): if tokinfo.type != tokenlib.ENCODING: yield tokinfo def uncertainty_tokenizer(input_string): def _number_or_nan(token): if token.type == tokenlib.NUMBER or ( token.type == tokenlib.NAME and token.string == "nan" ): return True return False def _get_possible_e(toklist, e_index): possible_e_token = toklist.lookahead(e_index) if ( possible_e_token.string[0] == "e" and len(possible_e_token.string) > 1 and possible_e_token.string[1].isdigit() ): end = possible_e_token.end possible_e = tokenize.TokenInfo( type=tokenlib.STRING, string=possible_e_token.string, start=possible_e_token.start, end=end, line=possible_e_token.line, ) elif ( possible_e_token.string[0] in ["e", "E"] and toklist.lookahead(e_index + 1).string in ["+", "-"] and toklist.lookahead(e_index + 2).type == tokenlib.NUMBER ): # Special case: Python allows a leading zero for exponents (i.e., 042) but not for numbers if ( toklist.lookahead(e_index + 2).string == "0" and toklist.lookahead(e_index + 3).type == tokenlib.NUMBER ): exp_number = toklist.lookahead(e_index + 3).string end = toklist.lookahead(e_index + 3).end else: exp_number = toklist.lookahead(e_index + 2).string end = toklist.lookahead(e_index + 2).end possible_e = tokenize.TokenInfo( type=tokenlib.STRING, string=f"e{toklist.lookahead(e_index+1).string}{exp_number}", start=possible_e_token.start, end=end, line=possible_e_token.line, ) else: possible_e = None return possible_e def _apply_e_notation(mantissa, exponent): if mantissa.string == "nan": return mantissa if float(mantissa.string) == 0.0: return mantissa return tokenize.TokenInfo( type=tokenlib.NUMBER, string=f"{mantissa.string}{exponent.string}", start=mantissa.start, end=exponent.end, line=exponent.line, ) def _finalize_e(nominal_value, std_dev, toklist, possible_e): nominal_value = _apply_e_notation(nominal_value, possible_e) std_dev = _apply_e_notation(std_dev, possible_e) next(toklist) # consume 'e' and positive exponent value if possible_e.string[1] in ["+", "-"]: next(toklist) # consume "+" or "-" in exponent exp_number = next(toklist) # consume exponent value if ( exp_number.string == "0" and toklist.lookahead(0).type == tokenlib.NUMBER ): exp_number = next(toklist) assert exp_number.end == end # We've already applied the number, we're just consuming all the tokens return nominal_value, std_dev # when tokenize encounters whitespace followed by an unknown character, # (such as ±) it proceeds to mark every character of the whitespace as ERRORTOKEN, # in addition to marking the unknown character as ERRORTOKEN. Rather than # wading through all that vomit, just eliminate the problem # in the input by rewriting ± as +/-. input_string = input_string.replace("±", "+/-") toklist = tokens_with_lookahead(_plain_tokenizer(input_string)) for tokinfo in toklist: line = tokinfo.line start = tokinfo.start if ( tokinfo.string == "+" and toklist.lookahead(0).string == "/" and toklist.lookahead(1).string == "-" ): plus_minus_op = tokenize.TokenInfo( type=tokenlib.OP, string="+/-", start=start, end=toklist.lookahead(1).end, line=line, ) for i in range(-1, 1): next(toklist) yield plus_minus_op elif ( tokinfo.string == "(" and ((seen_minus := 1 if toklist.lookahead(0).string == "-" else 0) or True) and _number_or_nan(toklist.lookahead(seen_minus)) and toklist.lookahead(seen_minus + 1).string == "+" and toklist.lookahead(seen_minus + 2).string == "/" and toklist.lookahead(seen_minus + 3).string == "-" and _number_or_nan(toklist.lookahead(seen_minus + 4)) and toklist.lookahead(seen_minus + 5).string == ")" ): # ( NUM_OR_NAN +/- NUM_OR_NAN ) POSSIBLE_E_NOTATION possible_e = _get_possible_e(toklist, seen_minus + 6) if possible_e: end = possible_e.end else: end = toklist.lookahead(seen_minus + 5).end if seen_minus: minus_op = next(toklist) yield minus_op nominal_value = next(toklist) tokinfo = next(toklist) # consume '+' next(toklist) # consume '/' plus_minus_op = tokenize.TokenInfo( type=tokenlib.OP, string="+/-", start=tokinfo.start, end=next(toklist).end, # consume '-' line=line, ) std_dev = next(toklist) next(toklist) # consume final ')' if possible_e: nominal_value, std_dev = _finalize_e( nominal_value, std_dev, toklist, possible_e ) yield nominal_value yield plus_minus_op yield std_dev elif ( tokinfo.type == tokenlib.NUMBER and toklist.lookahead(0).string == "(" and toklist.lookahead(1).type == tokenlib.NUMBER and toklist.lookahead(2).string == ")" ): # NUM_OR_NAN ( NUM_OR_NAN ) POSSIBLE_E_NOTATION possible_e = _get_possible_e(toklist, 3) if possible_e: end = possible_e.end else: end = toklist.lookahead(2).end nominal_value = tokinfo tokinfo = next(toklist) # consume '(' plus_minus_op = tokenize.TokenInfo( type=tokenlib.OP, string="+/-", start=tokinfo.start, end=tokinfo.end, # this is funky because there's no "+/-" in nominal(std_dev) notation line=line, ) std_dev = next(toklist) if "." not in std_dev.string: std_dev = tokenize.TokenInfo( type=std_dev.type, string="0." + std_dev.string, start=std_dev.start, end=std_dev.end, line=line, ) next(toklist) # consume final ')' if possible_e: nominal_value, std_dev = _finalize_e( nominal_value, std_dev, toklist, possible_e ) yield nominal_value yield plus_minus_op yield std_dev else: yield tokinfo if HAS_UNCERTAINTIES: tokenizer = uncertainty_tokenizer else: tokenizer = _plain_tokenizer import typing UnaryOpT = typing.Callable[ [ Any, ], Any, ] BinaryOpT = typing.Callable[[Any, Any], Any] _UNARY_OPERATOR_MAP: dict[str, UnaryOpT] = {"+": lambda x: x, "-": lambda x: x * -1} _BINARY_OPERATOR_MAP: dict[str, BinaryOpT] = { "+/-": _ufloat, "**": _power, "*": operator.mul, "": operator.mul, # operator for implicit ops "/": operator.truediv, "+": operator.add, "-": operator.sub, "%": operator.mod, "//": operator.floordiv, } class EvalTreeNode: """Single node within an evaluation tree left + operator + right --> binary op left + operator --> unary op left + right --> implicit op left --> single value """ def __init__( self, left: EvalTreeNode | TokenInfo, operator: TokenInfo | None = None, right: EvalTreeNode | None = None, ): self.left = left self.operator = operator self.right = right def to_string(self) -> str: # For debugging purposes if self.right: assert isinstance(self.left, EvalTreeNode), "self.left not EvalTreeNode (1)" comps = [self.left.to_string()] if self.operator: comps.append(self.operator.string) comps.append(self.right.to_string()) elif self.operator: assert isinstance(self.left, EvalTreeNode), "self.left not EvalTreeNode (2)" comps = [self.operator.string, self.left.to_string()] else: assert isinstance(self.left, TokenInfo), "self.left not TokenInfo (1)" return self.left.string return "(%s)" % " ".join(comps) def evaluate( self, define_op: typing.Callable[ [ Any, ], Any, ], bin_op: dict[str, BinaryOpT] | None = None, un_op: dict[str, UnaryOpT] | None = None, ): """Evaluate node. Parameters ---------- define_op : callable Translates tokens into objects. bin_op : dict or None, optional (Default value = _BINARY_OPERATOR_MAP) un_op : dict or None, optional (Default value = _UNARY_OPERATOR_MAP) Returns ------- """ bin_op = bin_op or _BINARY_OPERATOR_MAP un_op = un_op or _UNARY_OPERATOR_MAP if self.right: assert isinstance(self.left, EvalTreeNode), "self.left not EvalTreeNode (3)" # binary or implicit operator op_text = self.operator.string if self.operator else "" if op_text not in bin_op: raise DefinitionSyntaxError(f"missing binary operator '{op_text}'") return bin_op[op_text]( self.left.evaluate(define_op, bin_op, un_op), self.right.evaluate(define_op, bin_op, un_op), ) elif self.operator: assert isinstance(self.left, EvalTreeNode), "self.left not EvalTreeNode (4)" # unary operator op_text = self.operator.string if op_text not in un_op: raise DefinitionSyntaxError(f"missing unary operator '{op_text}'") return un_op[op_text](self.left.evaluate(define_op, bin_op, un_op)) # single value return define_op(self.left) from collections.abc import Iterable def _build_eval_tree( tokens: list[TokenInfo], op_priority: dict[str, int], index: int = 0, depth: int = 0, prev_op: str = "", ) -> tuple[EvalTreeNode, int]: """Build an evaluation tree from a set of tokens. Params: Index, depth, and prev_op used recursively, so don't touch. Tokens is an iterable of tokens from an expression to be evaluated. Transform the tokens from an expression into a recursive parse tree, following order of operations. Operations can include binary ops (3 + 4), implicit ops (3 kg), or unary ops (-1). General Strategy: 1) Get left side of operator 2) If no tokens left, return final result 3) Get operator 4) Use recursion to create tree starting at token on right side of operator (start at step #1) 4.1) If recursive call encounters an operator with lower or equal priority to step #2, exit recursion 5) Combine left side, operator, and right side into a new left side 6) Go back to step #2 Raises ------ DefinitionSyntaxError If there is a syntax error. """ result = None while True: current_token = tokens[index] token_type = current_token.type token_text = current_token.string if token_type == tokenlib.OP: if token_text == ")": if prev_op == "": raise DefinitionSyntaxError( f"unopened parentheses in tokens: {current_token}" ) elif prev_op == "(": # close parenthetical group assert result is not None return result, index else: # parenthetical group ending, but we need to close sub-operations within group assert result is not None return result, index - 1 elif token_text == "(": # gather parenthetical group right, index = _build_eval_tree( tokens, op_priority, index + 1, 0, token_text ) if not tokens[index][1] == ")": raise DefinitionSyntaxError("weird exit from parentheses") if result: # implicit op with a parenthetical group, i.e. "3 (kg ** 2)" result = EvalTreeNode(left=result, right=right) else: # get first token result = right elif token_text in op_priority: if result: # equal-priority operators are grouped in a left-to-right order, # unless they're exponentiation, in which case they're grouped # right-to-left this allows us to get the expected behavior for # multiple exponents # (2^3^4) --> (2^(3^4)) # (2 * 3 / 4) --> ((2 * 3) / 4) if op_priority[token_text] <= op_priority.get( prev_op, -1 ) and token_text not in ("**", "^"): # previous operator is higher priority, so end previous binary op return result, index - 1 # get right side of binary op right, index = _build_eval_tree( tokens, op_priority, index + 1, depth + 1, token_text ) result = EvalTreeNode( left=result, operator=current_token, right=right ) else: # unary operator right, index = _build_eval_tree( tokens, op_priority, index + 1, depth + 1, "unary" ) result = EvalTreeNode(left=right, operator=current_token) elif token_type in (tokenlib.NUMBER, tokenlib.NAME): if result: # tokens with an implicit operation i.e. "1 kg" if op_priority[""] <= op_priority.get(prev_op, -1): # previous operator is higher priority than implicit, so end # previous binary op return result, index - 1 right, index = _build_eval_tree( tokens, op_priority, index, depth + 1, "" ) result = EvalTreeNode(left=result, right=right) else: # get first token result = EvalTreeNode(left=current_token) if tokens[index][0] == tokenlib.ENDMARKER: if prev_op == "(": raise DefinitionSyntaxError("unclosed parentheses in tokens") if depth > 0 or prev_op: # have to close recursion assert result is not None return result, index else: # recursion all closed, so just return the final result assert result is not None return result, -1 if index + 1 >= len(tokens): # should hit ENDMARKER before this ever happens raise DefinitionSyntaxError("unexpected end to tokens") index += 1 def build_eval_tree( tokens: Iterable[TokenInfo], op_priority: dict[str, int] | None = None, ) -> EvalTreeNode: """Build an evaluation tree from a set of tokens. Params: Index, depth, and prev_op used recursively, so don't touch. Tokens is an iterable of tokens from an expression to be evaluated. Transform the tokens from an expression into a recursive parse tree, following order of operations. Operations can include binary ops (3 + 4), implicit ops (3 kg), or unary ops (-1). General Strategy: 1) Get left side of operator 2) If no tokens left, return final result 3) Get operator 4) Use recursion to create tree starting at token on right side of operator (start at step #1) 4.1) If recursive call encounters an operator with lower or equal priority to step #2, exit recursion 5) Combine left side, operator, and right side into a new left side 6) Go back to step #2 Raises ------ DefinitionSyntaxError If there is a syntax error. """ if op_priority is None: op_priority = _OP_PRIORITY if not isinstance(tokens, list): # ensure tokens is list so we can access by index tokens = list(tokens) result, _ = _build_eval_tree(tokens, op_priority, 0, 0) return result pint-0.24.4/pint/py.typed000066400000000000000000000000001471316474000152210ustar00rootroot00000000000000pint-0.24.4/pint/registry.py000066400000000000000000000203601471316474000157570ustar00rootroot00000000000000""" pint.registry ~~~~~~~~~~~~~ Defines the UnitRegistry, a class to contain units and their relations. This registry contains all pint capabilities, but you can build your customized registry by picking only the features that you actually need. :copyright: 2022 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations from typing import Generic from . import facets, registry_helpers from .compat import TypeAlias from .util import logger, pi_theorem # To build the Quantity and Unit classes # we follow the UnitRegistry bases # but class Quantity( facets.SystemRegistry.Quantity, facets.ContextRegistry.Quantity, facets.DaskRegistry.Quantity, facets.NumpyRegistry.Quantity, facets.MeasurementRegistry.Quantity, facets.NonMultiplicativeRegistry.Quantity, facets.PlainRegistry.Quantity, ): pass class Unit( facets.SystemRegistry.Unit, facets.ContextRegistry.Unit, facets.DaskRegistry.Unit, facets.NumpyRegistry.Unit, facets.MeasurementRegistry.Unit, facets.NonMultiplicativeRegistry.Unit, facets.PlainRegistry.Unit, ): pass class GenericUnitRegistry( Generic[facets.QuantityT, facets.UnitT], facets.GenericSystemRegistry[facets.QuantityT, facets.UnitT], facets.GenericContextRegistry[facets.QuantityT, facets.UnitT], facets.GenericDaskRegistry[facets.QuantityT, facets.UnitT], facets.GenericNumpyRegistry[facets.QuantityT, facets.UnitT], facets.GenericMeasurementRegistry[facets.QuantityT, facets.UnitT], facets.GenericNonMultiplicativeRegistry[facets.QuantityT, facets.UnitT], facets.GenericPlainRegistry[facets.QuantityT, facets.UnitT], ): pass class UnitRegistry(GenericUnitRegistry[Quantity, Unit]): """The unit registry stores the definitions and relationships between units. Parameters ---------- filename : path of the units definition file to load or line-iterable object. Empty string to load the default definition file. (default) None to leave the UnitRegistry empty. force_ndarray : bool convert any input, scalar or not to a numpy.ndarray. (Default: False) force_ndarray_like : bool convert all inputs other than duck arrays to a numpy.ndarray. (Default: False) default_as_delta : In the context of a multiplication of units, interpret non-multiplicative units as their *delta* counterparts. (Default: False) autoconvert_offset_to_baseunit : If True converts offset units in quantities are converted to their plain units in multiplicative context. If False no conversion happens. (Default: False) on_redefinition : str action to take in case a unit is redefined. 'warn', 'raise', 'ignore' (Default: 'raise') auto_reduce_dimensions : If True, reduce dimensionality on appropriate operations. (Default: False) autoconvert_to_preferred : If True, converts preferred units on appropriate operations. (Default: False) preprocessors : list of callables which are iteratively ran on any input expression or unit string or None for no preprocessor. (Default=None) fmt_locale : locale identifier string, used in `format_babel` or None. (Default=None) case_sensitive : bool, optional Control default case sensitivity of unit parsing. (Default: True) cache_folder : str or pathlib.Path or None, optional Specify the folder in which cache files are saved and loaded from. If None, the cache is disabled. (default) """ Quantity: TypeAlias = Quantity Unit: TypeAlias = Unit def __init__( self, filename="", force_ndarray: bool = False, force_ndarray_like: bool = False, default_as_delta: bool = True, autoconvert_offset_to_baseunit: bool = False, on_redefinition: str = "warn", system=None, auto_reduce_dimensions=False, autoconvert_to_preferred=False, preprocessors=None, fmt_locale=None, non_int_type=float, case_sensitive: bool = True, cache_folder=None, ): super().__init__( filename=filename, force_ndarray=force_ndarray, force_ndarray_like=force_ndarray_like, on_redefinition=on_redefinition, default_as_delta=default_as_delta, autoconvert_offset_to_baseunit=autoconvert_offset_to_baseunit, system=system, auto_reduce_dimensions=auto_reduce_dimensions, autoconvert_to_preferred=autoconvert_to_preferred, preprocessors=preprocessors, fmt_locale=fmt_locale, non_int_type=non_int_type, case_sensitive=case_sensitive, cache_folder=cache_folder, ) def pi_theorem(self, quantities): """Builds dimensionless quantities using the Buckingham π theorem Parameters ---------- quantities : dict mapping between variable name and units Returns ------- list a list of dimensionless quantities expressed as dicts """ return pi_theorem(quantities, self) def setup_matplotlib(self, enable: bool = True) -> None: """Set up handlers for matplotlib's unit support. Parameters ---------- enable : bool whether support should be enabled or disabled (Default value = True) """ # Delays importing matplotlib until it's actually requested from .matplotlib import setup_matplotlib_handlers setup_matplotlib_handlers(self, enable) wraps = registry_helpers.wraps check = registry_helpers.check class LazyRegistry(Generic[facets.QuantityT, facets.UnitT]): def __init__(self, args=None, kwargs=None): self.__dict__["params"] = args or (), kwargs or {} def __init(self): args, kwargs = self.__dict__["params"] kwargs["on_redefinition"] = "raise" self.__class__ = UnitRegistry self.__init__(*args, **kwargs) self._after_init() def __getattr__(self, item): if item == "_on_redefinition": return "raise" self.__init() return getattr(self, item) def __setattr__(self, key, value): if key == "__class__": super().__setattr__(key, value) else: self.__init() setattr(self, key, value) def __getitem__(self, item): self.__init() return self[item] def __call__(self, *args, **kwargs): self.__init() return self(*args, **kwargs) class ApplicationRegistry: """A wrapper class used to distribute changes to the application registry.""" __slots__ = ["_registry"] def __init__(self, registry): self._registry = registry def get(self): """Get the wrapped registry""" return self._registry def set(self, new_registry): """Set the new registry Parameters ---------- new_registry : ApplicationRegistry or LazyRegistry or UnitRegistry The new registry. See Also -------- set_application_registry """ if isinstance(new_registry, type(self)): new_registry = new_registry.get() if not isinstance(new_registry, (LazyRegistry, UnitRegistry)): raise TypeError("Expected UnitRegistry; got %s" % type(new_registry)) logger.debug( "Changing app registry from %r to %r.", self._registry, new_registry ) self._registry = new_registry def __getattr__(self, name): return getattr(self._registry, name) def __setattr__(self, name, value): if name in self.__slots__: super().__setattr__(name, value) else: setattr(self._registry, name, value) def __dir__(self): return dir(self._registry) def __getitem__(self, item): return self._registry[item] def __call__(self, *args, **kwargs): return self._registry(*args, **kwargs) def __contains__(self, item): return self._registry.__contains__(item) def __iter__(self): return iter(self._registry) pint-0.24.4/pint/registry_helpers.py000066400000000000000000000305521471316474000175050ustar00rootroot00000000000000""" pint.registry_helpers ~~~~~~~~~~~~~~~~~~~~~ Miscellaneous methods of the registry written as separate functions. :copyright: 2016 by Pint Authors, see AUTHORS for more details.. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import functools from collections.abc import Callable, Iterable from inspect import Parameter, signature from itertools import zip_longest from typing import TYPE_CHECKING, Any, TypeVar from ._typing import F from .errors import DimensionalityError from .util import UnitsContainer, to_units_container if TYPE_CHECKING: from ._typing import Quantity, Unit from .registry import UnitRegistry T = TypeVar("T") def _replace_units(original_units, values_by_name): """Convert a unit compatible type to a UnitsContainer. Parameters ---------- original_units : a UnitsContainer instance. values_by_name : a map between original names and the new values. Returns ------- """ q = 1 for arg_name, exponent in original_units.items(): q = q * values_by_name[arg_name] ** exponent return getattr(q, "_units", UnitsContainer({})) def _to_units_container(a, registry=None): """Convert a unit compatible type to a UnitsContainer, checking if it is string field prefixed with an equal (which is considered a reference) Parameters ---------- a : registry : (Default value = None) Returns ------- UnitsContainer, bool """ if isinstance(a, str) and "=" in a: return to_units_container(a.split("=", 1)[1]), True return to_units_container(a, registry), False def _parse_wrap_args(args, registry=None): # Arguments which contain definitions # (i.e. names that appear alone and for the first time) defs_args = set() defs_args_ndx = set() # Arguments which depend on others dependent_args_ndx = set() # Arguments which have units. unit_args_ndx = set() # _to_units_container args_as_uc = [_to_units_container(arg, registry) for arg in args] # Check for references in args, remove None values for ndx, (arg, is_ref) in enumerate(args_as_uc): if arg is None: continue elif is_ref: if len(arg) == 1: [(key, value)] = arg.items() if value == 1 and key not in defs_args: # This is the first time that # a variable is used => it is a definition. defs_args.add(key) defs_args_ndx.add(ndx) args_as_uc[ndx] = (key, True) else: # The variable was already found elsewhere, # we consider it a dependent variable. dependent_args_ndx.add(ndx) else: dependent_args_ndx.add(ndx) else: unit_args_ndx.add(ndx) # Check that all valid dependent variables for ndx in dependent_args_ndx: arg, is_ref = args_as_uc[ndx] if not isinstance(arg, dict): continue if not set(arg.keys()) <= defs_args: raise ValueError( "Found a missing token while wrapping a function: " "Not all variable referenced in %s are defined using !" % args[ndx] ) def _converter(ureg, sig, values, kw, strict): len_initial_values = len(values) # pack kwargs for i, param_name in enumerate(sig.parameters): if i >= len_initial_values: values.append(kw[param_name]) values_by_name = {} # first pass: Grab named values for ndx in defs_args_ndx: value = values[ndx] values_by_name[args_as_uc[ndx][0]] = value values[ndx] = getattr(value, "_magnitude", value) # second pass: calculate derived values based on named values for ndx in dependent_args_ndx: value = values[ndx] assert _replace_units(args_as_uc[ndx][0], values_by_name) is not None values[ndx] = ureg._convert( getattr(value, "_magnitude", value), getattr(value, "_units", UnitsContainer({})), _replace_units(args_as_uc[ndx][0], values_by_name), ) # third pass: convert other arguments for ndx in unit_args_ndx: if isinstance(values[ndx], ureg.Quantity): values[ndx] = ureg._convert( values[ndx]._magnitude, values[ndx]._units, args_as_uc[ndx][0] ) else: if strict: if isinstance(values[ndx], str): # if the value is a string, we try to parse it tmp_value = ureg.parse_expression(values[ndx]) values[ndx] = ureg._convert( tmp_value._magnitude, tmp_value._units, args_as_uc[ndx][0] ) else: raise ValueError( "A wrapped function using strict=True requires " "quantity or a string for all arguments with not None units. " "(error found for {}, {})".format( args_as_uc[ndx][0], values[ndx] ) ) # unpack kwargs for i, param_name in enumerate(sig.parameters): if i >= len_initial_values: kw[param_name] = values[i] return values[:len_initial_values], kw, values_by_name return _converter def _apply_defaults(sig, args, kwargs): """Apply default keyword arguments. Named keywords may have been left blank. This function applies the default values so that every argument is defined. """ for i, param in enumerate(sig.parameters.values()): if ( i >= len(args) and param.default != Parameter.empty and param.name not in kwargs ): kwargs[param.name] = param.default return list(args), kwargs def wraps( ureg: UnitRegistry, ret: str | Unit | Iterable[str | Unit | None] | None, args: str | Unit | Iterable[str | Unit | None] | None, strict: bool = True, ) -> Callable[[Callable[..., Any]], Callable[..., Quantity]]: """Wraps a function to become pint-aware. Use it when a function requires a numerical value but in some specific units. The wrapper function will take a pint quantity, convert to the units specified in `args` and then call the wrapped function with the resulting magnitude. The value returned by the wrapped function will be converted to the units specified in `ret`. Parameters ---------- ureg : pint.UnitRegistry a UnitRegistry instance. ret : str, pint.Unit, or iterable of str or pint.Unit Units of each of the return values. Use `None` to skip argument conversion. args : str, pint.Unit, or iterable of str or pint.Unit Units of each of the input arguments. Use `None` to skip argument conversion. strict : bool Indicates that only quantities are accepted. (Default value = True) Returns ------- callable the wrapper function. Raises ------ TypeError if the number of given arguments does not match the number of function parameters. if any of the provided arguments is not a unit a string or Quantity """ if not isinstance(args, (list, tuple)): args = (args,) for arg in args: if arg is not None and not isinstance(arg, (ureg.Unit, str)): raise TypeError( "wraps arguments must by of type str or Unit, not %s (%s)" % (type(arg), arg) ) converter = _parse_wrap_args(args) is_ret_container = isinstance(ret, (list, tuple)) if is_ret_container: for arg in ret: if arg is not None and not isinstance(arg, (ureg.Unit, str)): raise TypeError( "wraps 'ret' argument must by of type str or Unit, not %s (%s)" % (type(arg), arg) ) ret = ret.__class__([_to_units_container(arg, ureg) for arg in ret]) else: if ret is not None and not isinstance(ret, (ureg.Unit, str)): raise TypeError( "wraps 'ret' argument must by of type str or Unit, not %s (%s)" % (type(ret), ret) ) ret = _to_units_container(ret, ureg) def decorator(func: Callable[..., Any]) -> Callable[..., Quantity]: sig = signature(func) count_params = len(sig.parameters) if len(args) != count_params: raise TypeError( "%s takes %i parameters, but %i units were passed" % (func.__name__, count_params, len(args)) ) assigned = tuple( attr for attr in functools.WRAPPER_ASSIGNMENTS if hasattr(func, attr) ) updated = tuple( attr for attr in functools.WRAPPER_UPDATES if hasattr(func, attr) ) @functools.wraps(func, assigned=assigned, updated=updated) def wrapper(*values, **kw) -> Quantity: values, kw = _apply_defaults(sig, values, kw) # In principle, the values are used as is # When then extract the magnitudes when needed. new_values, new_kw, values_by_name = converter( ureg, sig, values, kw, strict ) result = func(*new_values, **new_kw) if is_ret_container: out_units = ( _replace_units(r, values_by_name) if is_ref else r for (r, is_ref) in ret ) return ret.__class__( res if unit is None else ureg.Quantity(res, unit) for unit, res in zip_longest(out_units, result) ) if ret[0] is None: return result return ureg.Quantity( result, _replace_units(ret[0], values_by_name) if ret[1] else ret[0] ) return wrapper return decorator def check( ureg: UnitRegistry, *args: str | UnitsContainer | Unit | None ) -> Callable[[F], F]: """Decorator to for quantity type checking for function inputs. Use it to ensure that the decorated function input parameters match the expected dimension of pint quantity. The wrapper function raises: - `pint.DimensionalityError` if an argument doesn't match the required dimensions. ureg : UnitRegistry a UnitRegistry instance. args : str or UnitContainer or None Dimensions of each of the input arguments. Use `None` to skip argument conversion. Returns ------- callable the wrapped function. Raises ------ TypeError If the number of given dimensions does not match the number of function parameters. ValueError If the any of the provided dimensions cannot be parsed as a dimension. """ dimensions = [ ureg.get_dimensionality(dim) if dim is not None else None for dim in args ] def decorator(func): sig = signature(func) count_params = len(sig.parameters) if len(dimensions) != count_params: raise TypeError( "%s takes %i parameters, but %i dimensions were passed" % (func.__name__, count_params, len(dimensions)) ) assigned = tuple( attr for attr in functools.WRAPPER_ASSIGNMENTS if hasattr(func, attr) ) updated = tuple( attr for attr in functools.WRAPPER_UPDATES if hasattr(func, attr) ) @functools.wraps(func, assigned=assigned, updated=updated) def wrapper(*args, **kwargs): list_args, kw = _apply_defaults(sig, args, kwargs) for i, param_name in enumerate(sig.parameters): if i >= len(args): list_args.append(kw[param_name]) for dim, value in zip(dimensions, list_args): if dim is None: continue if not ureg.Quantity(value).check(dim): val_dim = ureg.get_dimensionality(value) raise DimensionalityError(value, "a quantity of", val_dim, dim) return func(*args, **kwargs) return wrapper return decorator pint-0.24.4/pint/testing.py000066400000000000000000000061421471316474000155660ustar00rootroot00000000000000from __future__ import annotations import math import warnings from numbers import Number from . import Quantity from .compat import ndarray try: import numpy as np except ImportError: np = None def _get_comparable_magnitudes(first, second, msg): if isinstance(first, Quantity) and isinstance(second, Quantity): ctx = first._REGISTRY._active_ctx.contexts if first.is_compatible_with(second, *ctx): second = second.to(first) assert first.units == second.units, msg + " Units are not equal." m1, m2 = first.magnitude, second.magnitude elif isinstance(first, Quantity): assert first.dimensionless, msg + " The first is not dimensionless." first = first.to("") m1, m2 = first.magnitude, second elif isinstance(second, Quantity): assert second.dimensionless, msg + " The second is not dimensionless." second = second.to("") m1, m2 = first, second.magnitude else: m1, m2 = first, second return m1, m2 def assert_equal(first, second, msg: str | None = None) -> None: if msg is None: msg = f"Comparing {first!r} and {second!r}. " m1, m2 = _get_comparable_magnitudes(first, second, msg) msg += f" (Converted to {m1!r} and {m2!r}): Magnitudes are not equal" if isinstance(m1, ndarray) or isinstance(m2, ndarray): np.testing.assert_array_equal(m1, m2, err_msg=msg) elif not isinstance(m1, Number): warnings.warn("In assert_equal, m1 is not a number ", UserWarning) return elif not isinstance(m2, Number): warnings.warn("In assert_equal, m2 is not a number ", UserWarning) return elif math.isnan(m1): assert math.isnan(m2), msg elif math.isnan(m2): assert math.isnan(m1), msg else: assert m1 == m2, msg def assert_allclose( first, second, rtol: float = 1e-07, atol: float = 0, msg: str | None = None ) -> None: if msg is None: try: msg = f"Comparing {first!r} and {second!r}. " except (TypeError, ValueError): try: msg = f"Comparing {first} and {second}. " except Exception: msg = "Comparing" m1, m2 = _get_comparable_magnitudes(first, second, msg) msg += f" (Converted to {m1!r} and {m2!r})" if isinstance(m1, ndarray) or isinstance(m2, ndarray): np.testing.assert_allclose(m1, m2, rtol=rtol, atol=atol, err_msg=msg) elif not isinstance(m1, Number): warnings.warn("In assert_equal, m1 is not a number ", UserWarning) return elif not isinstance(m2, Number): warnings.warn("In assert_equal, m2 is not a number ", UserWarning) return elif math.isnan(m1): assert math.isnan(m2), msg elif math.isnan(m2): assert math.isnan(m1), msg elif math.isinf(m1): assert math.isinf(m2), msg elif math.isinf(m2): assert math.isinf(m1), msg else: # Numpy version (don't like because is not symmetric) # assert abs(m1 - m2) <= atol + rtol * abs(m2), msg assert abs(m1 - m2) <= max(rtol * max(abs(m1), abs(m2)), atol), msg pint-0.24.4/pint/testsuite/000077500000000000000000000000001471316474000155655ustar00rootroot00000000000000pint-0.24.4/pint/testsuite/__init__.py000066400000000000000000000053011471316474000176750ustar00rootroot00000000000000from __future__ import annotations import contextlib import doctest import math import os import pathlib import unittest import warnings from pint import UnitRegistry from pint.testsuite.helpers import PintOutputChecker class QuantityTestCase: kwargs = {} @classmethod def setup_class(cls): cls.ureg = UnitRegistry(**cls.kwargs) cls.Q_ = cls.ureg.Quantity cls.U_ = cls.ureg.Unit @classmethod def teardown_class(cls): cls.ureg = None cls.Q_ = None cls.U_ = None @contextlib.contextmanager def assert_no_warnings(): with warnings.catch_warnings(): warnings.simplefilter("error") yield def testsuite(): """A testsuite that has all the pint tests.""" suite = unittest.TestLoader().discover(os.path.dirname(__file__)) from pint.compat import HAS_NUMPY, HAS_UNCERTAINTIES # TESTING THE DOCUMENTATION requires pyyaml, serialize, numpy and uncertainties if HAS_NUMPY and HAS_UNCERTAINTIES: with contextlib.suppress(ImportError): import serialize # noqa: F401 import yaml # noqa: F401 add_docs(suite) return suite def main(): """Runs the testsuite as command line application.""" try: unittest.main() except Exception as e: print("Error: %s" % e) def run(): """Run all tests. :return: a :class:`unittest.TestResult` object Parameters ---------- Returns ------- """ test_runner = unittest.TextTestRunner() return test_runner.run(testsuite()) _GLOBS = { "wrapping.rst": { "pendulum_period": lambda length: 2 * math.pi * math.sqrt(length / 9.806650), "pendulum_period2": lambda length, swing_amplitude: 1.0, "pendulum_period_maxspeed": lambda length, swing_amplitude: (1.0, 2.0), "pendulum_period_error": lambda length: (1.0, False), } } def add_docs(suite): """Add docs to suite Parameters ---------- suite : Returns ------- """ docpath = os.path.join(os.path.dirname(__file__), "..", "..", "docs") docpath = os.path.abspath(docpath) if pathlib.Path(docpath).exists(): checker = PintOutputChecker() for name in (name for name in os.listdir(docpath) if name.endswith(".rst")): file = os.path.join(docpath, name) suite.addTest( doctest.DocFileSuite( file, module_relative=False, checker=checker, globs=_GLOBS.get(name, None), ) ) def test_docs(): suite = unittest.TestSuite() add_docs(suite) runner = unittest.TextTestRunner() return runner.run(suite) pint-0.24.4/pint/testsuite/baseline/000077500000000000000000000000001471316474000173475ustar00rootroot00000000000000pint-0.24.4/pint/testsuite/baseline/test_basic_plot.png000066400000000000000000000420071471316474000232360ustar00rootroot00000000000000PNG  IHDR Xvp9tEXtSoftwareMatplotlib version3.6.2, https://matplotlib.org/(b pHYsaa?iCtIDATx{TuBPH=PٸԖ_Nx 1/8]Ǯ:cY9e@dΤ˾lt-67"E??f}Ǚ,T}y>jԗɲ,K6 ܆6!@ mnCp ܆6!@ mnCp ܆6!@ mnCp ܆6!@ mnCp ܆6!@ mnCp ܆6!@ mnCp ܆6!@ mnCpV|.]Ԥ*?^QQQx%ă|'={9995k^ ?Ѓ6;nxb+g s3=nۿmt٩i ׎ +%%Cnx_t Pbr#Mρp/}eYwR|ZݪRtA=/q @ԓ_(^i}ԤD1˼Zu+ӓ| ݶREvVJnz!@f̘azܨ[Z7Fܿ ot#?$Z&+<$,D ˿DE7_x{ZW؄=4:N:u2=g [E)+.Kʝ'<~Dz,SVhX>3E!g~ܪee*,OF/&$((fz @7iTf]5MZ;3E6= e_tsD}=J{N^B~V{hEFByl N] v3 ќX5OsV hiJnzDXwկtPiqZ3c‚M  .MV~Z Вq`+OB'T4h~N-zgmzG{`´ኍ 5= WAk5:r=1znʭ 0= ߂W>QYv<ըI' x]Jfzڈp,zu! ^~"v6= ׀W8hM%uWqĮ!@ʪ5?Ǯ-ZpazeYhpt,ũ>B5;@v<:2s:VVOKVFJIh'Z`#P.\҂ vɉ;G1fZ-|X66>'0β,V}rPiqZ3c‚Mς @Ю.4]֣j{i-3@K RK7MEM2stѢwܮѦ' hEz`GPӓܐKz\н1z~ʭ 0= u>QYv<ըI~-oE-X@gPRL7ӓ\ҚO+ڎJ ^~"v6= ^@wh@/Vnݮmƌ1cESV]N;/M"MOkyyy˻ .Z;+(%% &eiݞ*BF)1=}vRSS - AV-/SaYe`3= 0!4*3Ǯ&C{t8²/e9>=LO܂pN^B~V{hEFByHv79uᒲ7UZ]g9 CG<m6PRcAt ˲*-.Rkf STXY1@itYn*նZ0f\ AsTh;snhӓ@j=Q_qQaZpFx 4:r=1znʭ 0= (@;>QYv<ըI'A#+,8P#$c 岴JR?WߦM<p;ZhcvW3P6pʪ5?Ǯ-Zpaz56,KyNjG4MzcvnbzU6hjqɂ/o촾zjR9bVwu(3HUuӓE|mNi #+$ ߠ͝G4~HVMMVxHY#@f-+cuzlBN8bh?(:~NYv9]R44= )v=Uzak3SbzsͭZ_²dTbBlg>~FeUSߤ3S4qhoӓFUX~L}"Q3$ \vz*MNI a_9uᒲ7Ur^''j~ ƞ#ZW@MNWjlwӓCgYuT>9H1LQag~>e-T姕ZA q` |VEMѢ͹]MO|R~Q(دQa*|`b#CMO VR {O=?V3_TV]k"#I?< |οѢ% 54&$߀^岴JRwЫoSDΦg x-Xݕgxl #vFTz^Yv9ZZ+Iڀ^Ų,;7i4tjz6"@hjqɂ/o촾zjR9b& UeΡӒcz@)-}TQa*X0R MOpZ.yDDkd #ml¼b:NЃw @):~NYv9]R+-.$ò,SVhX!ghGV-/SaYe`3= @;#@q4*3Ǯ&1+Ew'6= @!@Qe_j2袏~:Jz NV||PII 븗;uᒲ7UZ]g9 ՞#ZW@M='p#eYz{QO53)*,,nFpTm姕ZA qС*j4?Hu3vM6= A0W+.*LPӓFvԳ[ʕ)*$(,EeuTVf$ip. :tVW@gPRL7ӓx0ҚO+ڎJ ^~"v6= "@ 9hѢ%]yV+{@8bU ຕUk~][Z$̲,;7i4tjz/@kԓ_(^Y﫧''*8#v ڬ̜֡"U9zZ2RbLOe&KY F*WIUӥқ;hh ӳx)\UWZWc8 !@7*:~NYv9]R44=  @,Һ=Uzakn%Bkg(:<,>_s4jY~ jQ  @tL2s쪩oڙ)8I|TXm.S.0{ 3= "@c.1MNI Gԩ Z>9QsG]9Ry ٴtv7= @<ŋխ[+n1cf̘ahWXwկtPiqZ3c‚MQYv<ըI~- # =EKJ?܄G\|ZvTzum,8hѢ%]yV+{@8b"@recŖVF0= KY}'&1;]7Gt1= jjqɂ/o촾zjR9b#@2UeΡӒczv_*X0R MOkBZ.yDDkd׌~լy:=6!AYx-Vtrr,KSHӓ x ˲nO^Za}#E78[,Le5ɨńLvAAiTf]5MZ;3E6= (,R6OD}=J{NV||P&'ʌ$)onvɉ;Gi|~N?ͳ+fƇӕ$pnfY}TAEj͌a 6= ܂.]֣J%)%WT4h~N-ݜ5.1$p;7Z_qQaZpFF tVRܽ'455FMU!Ag1E-ȵTVf$ip.Gv:G+VXp3G()IڑeiͧzmGWߦMAN;ZhcvW3P6+ zϱbK?\{X}'&]MEpZz ۫5;@oCpj)RUC%+#%$ hہSZ~"C;`H% 7= @:]zy!֪  2= @ml¼b=V&$q<9\PtrryiJizx-,KT酭7BLQtxYUTXVykلLGOiTf]5MZ;3E6= |?(,R?\#Q3$)ZZ]Zz*ݓG+2̧Iho|dS.iʪ|rA_sV h齇ҕ$i/YvժO*-.Rkf STXYitYK7j{i-3@K RK_i"9ZΜ561$+oU뉂 +62$;5:̖rm{BSScܔ[`z%Ӫ_TV]O5jeFq GAg:tVW@gPRL7ӓ rYZi^Q+oSDΦgD|yGm,ʳZ<6^c1(.j5:IBeYwR?:oYi,7 @^ũ' PZI ]TkU:4?׮c_ideĘ+m;pJK/Udhg,^'ڀxVK/o?7wҴdh#5jj¼bhFeW(:~NYv9]R44= pG,KT酭ӳfz7ihhʕ+5~x 6L$;wNWÇ /hnO̖rOyHUWW뮻ɓ'oo:x+I=:~^{5K7>2sTSߤ3S4qhoӓigjllTIIz쩞={^)S:-e_j2Gi`0ӓim۶iJLLT]]ݿ>..N'O4 |eK+>>?|vLheFBTmԤ=z\n\t%-ȵdON8b| {@Æ s"5Mpٟw@hѢE;wSJ\.>gyF bY}TAEj͌a 6= Ё6={?'|RO<$??dYl6^|EM2H" .MV~Z Вq`kO?ua\. 0@3=FEMѢwܮѦ'܄F}ŋMG{`Gኍ 5= F-[zvKrн1z~ʭ 0= fUlk>SNjmmEཪ_Ԃ\*N5jeFq \"@駟#]Jfz */_nzx5ҚO+ڎJ ^~"v6= `/^nݮ 34c Cڜwh׍-ܢ~X>-[\y}NtC >ӨjƬݝ$#@駟 /oY~;+,R?\#裟Ҁa'|Fo&Nl\w+gU'Vd$)4Og6jiiĉ>ԅK`Wz=sI]@tM4I?czt=Gj57MpG|F/U^^,ٳ:wܿƲ,fW7p(v7= ࣸ $I*))o}շs:7e-T姵`-7H68H=\T4h~N-zgmz m|rU뉂 +62$ @4:r=1znʭ @;!@OT\jʌ$M.-~`סzb*?sbS0ҚO+ڎJCNM];c-Xݕgxl #v ʪ5?.GK?\w0= I˲烙t@{ߤӽY|ԓ_(^i}ԤDr. jkױگzZ2RbLO 8*2 TBpӓ*Tӥқ;臉ziZCL[ jj¼bhF@):~NYv9]R44= 6#@KXu{ vKJQtxY\UTXVFcw'((fz׌wL2s쪩oڙ)8I\7;}2#I|x?>9uᒲ7Ur^''j~ x=Gj0X66>'Юe]G꓃JԚlz.]֣J%)%WDA5 S:Gޙs&F@"@j=Q_TpF@#@͚[zvKrн1z~ʭ 0= @4\/>SZwB=D7uJfznG@s,m{B4(&v(Etlxf Ё;Zhc|℺~9)5ʪ57VYug$s3%Wn V;,<`@;jjqɂ/o촾zjRt2<֡vJ%+#%$<`{i-T`,^'N^~HoӨj꛴vf&mz^PX~L7Gtч٣4gIx-ӥ?лU>Z`>lp#L ԅK`Wz=sI]dϑZ-+Vͦ+5I β,V}rPiqZ3c‚M e=TOkZ2nl\r@{#@)RE̹]cMOg Z~Q(دQa*|`b#CMO RsSn)W75FOU!Ag~EeuTVf$ip.nB+#+,8P#$ /\|ZvTzum,wh3E!g|7}n+55"@*V-/SaYe`3= k>Өj꛴vf&mzFPX~L7Gtч٣4gI: ouUr^''j~ >?RhiJnzraY~~_Ӛlz+4\G7j[ie`pl\rk5 S:Gޙs&F| @G{`Gኍ 5= sͭN=\{O=?V|kFHҏ#v@"@?ghcB;jt 0= 3.5V+^~"v6= #-ZD+jxe(G7"@?QV]9v9ZZ+I ,K3kpqVbw5= )aM-N=Q_N뫧&%*8#v9࣪j)RUC%+#%$EM 묂#+$ISZ.uD?LKӒdzGml¼b=V&$q<98SV]N;/M"MOF,Һ=Uzak3SbzU r4jY~ jQ  >Өj꛴vf&mz@ ),R?\#Q3$6#@/qҋWϪtOrHRh0wa]xxS.){]%'=C4'=#vW"@H+fƇӕ$Fʲ,V}rPiqZ3c‚M!.]֣J%)%W i"9ZΜ561$vC$ZOW0>0\'+ͭN=\{O=?V ׮FHҏ#v"@v:G+Vh@m1't(0岴JRwЫoSDΦgt8p-Xݕgxl #v @7*.j5:InEn`YhpvVbw5= 58ԇ_hsQfS? @uh~]jiH1= ( OkɦEvVJnzqZ.~HoQYv<ըI~- =EKJfz"@\.Kk>k;*zem,F/wh=LOw @u.;]z Y&'ʌ$G ^ԅK`Wz-#q.!@5¼blpRckDYvժO*-.Rkf STXYMO!:Lӥқ;臉ziZCLA:f-+cuń=<:g5Wtrr,KSHӓ!˲nO^Zn3Sԫ[Y څUTXV_LHPP,x7FeUSߤ3S4qhoӓܐ²/eEfa' ..qJheFBo#F\S.){]%'|rhdϑZ-+Vͦ+5I"IJ,V}rPiqZ3c‚M!@.]֣J%)%WvUEMѢwܮѦ' ?ګ WldIrEsSn)Wܪӳ\E-ȵT~I~G_ӓC|mסzb*?sbC@.5VA|26Etlz|;ZhcvW3P6@!@XYuuU=LO#@eYwR?:}Ƭt,48dʷWkvZ_=5)Q @:TUiH1= ~K#+$!ǵ:]zy!֪  2= ~ag0Xqԉ#v`⣊SV]N;J4= @|eYZJ/lаz}fCL$ >ܪee*,ѼQlBlg_#@|3̱IoJIMO ˾7.解4GI7"@eK/~\w?=}"#IKxN] vV{hNz,G ^hϑZ-+VͦJWjlwӓ6!@eYzkQ"f0E%.]֣J%)%W.?ZwܮO >|D^^ pԛ7_xmGxj7>S΁*(M6= OX_.7_xm%Xթg+w MMsSnUHPY !@<&-s<ըI'@W+?sbă455IzYuznT.9"ãС.\ nzhQ9/:U^ZKLρp/GEE ׮eY\͞= |͚5 DxZ}'ׯtbzISS4~xEEEnp ܆6!@ mnCp/_N:!@ ms8' Vnݺŋ~}kk{9 0@ׯq577_tI˗/_~~yݺuԩvڥ,S111Y6m4_+Vn;C={W$i޼yZ~^-]T{Պ+TQQ>ݬ,CO?4Bհa^կTZZk޼y~'I_⥗^[cƌ_{vءv}<`Uff/y睪SCC>cIҒ%KxKJnzݿ>H|"@ߪo߾Wܽ{wIuql6 8ի"""tu?w!,;ut?ۻtr?'s +˥+n?}mݻwW}}oҢwLxpnIҫzW$M8 ݻw_voo,uKNNܹsV뮻o>_^SLy)33SqƩT|򉢢 Fn;C8[N|z{L/x|Pǎ{Oҝwީ۷ -ǿEmnCp ܆6!@ mnCp ܆6!@ mnCp ܆6!@ mnCp ܆6!@ mPbiIENDB`pint-0.24.4/pint/testsuite/baseline/test_plot_with_non_default_format.png000066400000000000000000000403511471316474000270560ustar00rootroot00000000000000PNG  IHDR Xvp9tEXtSoftwareMatplotlib version3.6.2, https://matplotlib.org/(b pHYsaa?i@VIDATxwx9L v!d "X& A**j Vm#'lPH]$$ ;@vrW<?H纅伓;>˲,  lC 6 lC 6 lC 6 lC 6 lC 6 lC 6 lC 6 lC 6 lC 6 lC 6 lC 6 lC 6 lC 6 lC 640=_qqV\DEEEQQQB]xᅊ7='  ~dʕ?~󎮿z3G%tϞ=͎#SLٳM+ɱL 辯S?Nqly~&v<ř39gcyyy?~^ ~]S 0r39IHERRr%>Ly39gr,q]ClC 6޸qLO;ɱ8cq&?y39gr,uaYez%##Cڵka/ ec5:z!@؆`m!@؆`m!@؆`m!@؆eY'<8˫<3^}AMr{g)?'4m(t]ӓ~¾#<7S,8Գh]izV#@}:/K.C6D;Ǜ4ߪk}z~Fe\ύIV|LYA$m?PtrvKz֑t:L :Bݺoai.E dUT{5sYmץ}[Q}82F $m{Twehr=5ƞNWbY~C,Q&z?eldzV @2TE,{ n%)*ezVH!@vVۣCezq\]D |^[UOؠ^m[}hӳBuJg鳍u4 o4=+ Jkkrzj}޸4ݽI SOn֋nЎµԲqY7AcwI&3ör^7}v<ß  մY s)aܱI ZUWO}Ao|]zгW'Ipӳ3eJug(O߸OSetۆhxxӓZ_Q~Y7c,&,9'lr{D]S(az<{[& õJngzRQer]m?Ƒag! U{*%-CQ}4vr8rGgY#KsԾiOn-F'iYn^3.KRT,8qXnU벾mLOB @>Vo+6WX{kmzIҁ*M6םgt +,<ףZ7o>Mguoazj>zM֩f#MB#@B MJ5npV<AմY s)aܱI@7药 u^ϖznL_EBeJug(O R}RύILO`cYf[N lHuncz *%5l٣.0Y Ab2~.$@,(_ {P%46= I@wR[5Κr~7g?P_mگ)s3%9-uz'05^f}Wآ]5~j(, P&=** {3:t7 @X[dQd9L;41= 8ayzmw+=5bLN 6+UJZ ur8rE!˲`mf,QH-{znlzp?SZU?^=;5f`yنd?~gR2h\OWkkzP?`Y\S'?ؠnb̓1Y@#@ ;TV{d}eDGh2= Am=IUxyI-MO`gϟm kP3ZF;f{Tjrzz@siz` Fmܧr(ֹIT W[uvznLD؎g+՝GХ=uˈr:gF hi.=h4 ׂ+]IQ@=jۡ˓WV0ӳ:qQehǡr==NW3e)zuhSF[Fg~G*ktuZ[׌˒2= ;)qX .Ke}ۘ-$|@Ϭب^mcvPkmzP\ZE~yF'M8Mpl)L,qi:{ ӓAZOd^lvl#M pvL9~wv<8Q(w]0,M2E?z۸q4n8CMU^=P'ԳWU\tYJJJ ={ `z!mkqR2ioآY8_mLBBƾgnRuU,  $|_˔;ш.'!AӬ[tFz~L70= YZ;kbGJt=tt:LB҇v 8*L훘@25^=ٰO_C g8NФLUzڍtnϖ'8A{^>ݬ9kPbS͹ZF$ )Ԥt/K_]gVlTﶱr>TFfwťU:/K_םgtҴ +4= Wk6kLy}޼4սI "@@4Mz֩^O-G0Թ]+4)ݣ5npV<ت=wA~P$~u֫'?ؠ7z꾊7= !@)+_TG˓tD9\p,EEzj8R~7\ƚ औUjƒ-(Ҩm5ފ_G prv(5ͣ=G*d`zAfYf_c4u:71= @!@q))YZW7/0Y ~m5ѝҪZz@]ثI~g/hG.NsW۸(ӳ0}G+u,}XwE.Ye~3/SC:D#ě H ?j>=*_|Egtk$yӳHv,t{;t  @ۭ/V0ͻkobz E*kz|yf.JOب0ӳ1yQyLO\[ n/+W@,K%9j$JKRFGƦg!V.^%4vP;=$Er}@XWTTwK5~_[ӓ  gY^PO} Vf Mj;?KlاFvz(,! H}[p@3UU7 9=Zҧ5|TsVg$R=&Utɳ= 醽6?[.n @pյ>=bzBώIVӆgO"@`)Q#eIeD\_]z`:5 7!$UV?o艫zQdYp\Ȇ=GQѡr=3 JB,Rw5si:7ԲԑҢYp\IEXNC%)2ez?~HnJ*jҾMOSB|>K@ϭܨmc}56= N).=e~ufgM\NӳN 7kL|q`٭IP@ק>ޤ?Y;7kEHӳ p&=8it]gvɳ='Vj;jPbSӓ^ PYSnЛk uARK=su_EfK}yE/0\ 6ZQ[V#&$ ʪj%(cFH+za>Prv(5ͣ=G*5d`zCPO,lԥyT1gQpy/֪ܽix"L5)=SU uAV' @#^WآYk@8͹E~;Z)s3fE.YwN5u^޹uFt7= Iܪzѭf]ӳ ˕hq~z'9ӳ f+6*L훘8Uxز\v]ڧ8bLBp6=4 WѸpp N/,K(Ҍ׫]h2R[52= 8ZYY4vP;=^ w'dVۣ՚sm?]ѯIeYzmV=bznnAUk,}anQ/Nӳ  H&{T]7 9=ZA4ҟ>٤?ݤjε*6,Z d)Ԥt/MO|>K@ϭܨ> r>TF  VyY2&YMa.YAכ5yn,[ ݚ z}zM5sfMVFg(;Wh#ώÚvAwM8Nh,5 Ӽ;j`'Nro@eY?>C xUxy?^-辊7= pooVXXw&M{7HnRye_f^K7 +W#ozk%ժq=\Ě8EqڵkMfK|L2E?$;n877ZZ٩4^j,rr?z[II5ǩo߾ڵk-k0`- %Ju{Hf]QLO~ 8pEGYf^Ӛ5kLOdYzFe\Z: w@O?X~JJJRr~{,Ybh!պwA>ݫ'Kz(A@!@SvvڷoR{&=*7 ZGq*,,4=g7kǛ4}\_mL3gl߾]Ծ}}G*5e^l9hҹ]ŏ%@( @~Fbb***_k:\oܧt:C4 VPB_]Caaa?5x}znFE\_ӳ6#@~M7˕h=pI6Nԫٻul5 ӂ կ]I@j\}]i'GQ0ӳ :iQyTxLOkOk$ :dYCvM~Huo,!@uheX^KvivqY/ELpʲ+%ͣCezq\]$"@'Ͳ,z^AI[}hӳ~e՚6?KnاO{/Nӳ~o hRG5^Kot$@ @O6O7ipǦzal4= @q]RI&MwE.'>۫i%C5S3ӓZp^zB^& M0 ˔hÞ#qYn(+WSC$sXN"hI5= $Ws4"]ѯE"@R2p L ,һnePKSGKFg!F-ևhz$EL1BTCJMHe^~.$@ @ |U[Q}b~Pkmz D B=2զbM89\Nӳ!zS˔eYz:[sӓ! Wi[4sfMVFgBAl Mr{qX^]wYN'C@ZGd+&9L;41= MeWO~s.JO0ӳD@PٲT)im_ǮC $-Ҍ%*6Rn46= c Jj5Z٩&h·w3;KhߑJ'/"@ Y76[-x:74= _E@9TV{d㼽yD"L @/*ϔpvQJZvsc5z@[\p8eoePKSGKӳݷ([ߣvЃTd, aChe^?@nmz_Yz-z~Ub~Pkmzh՛5Κr~7g 7kLI޺eN$[4sfMVFg4C嚔uutlV߭ (2L@P"@X٦ {3fzA/UJG[+{kr8r@}"@k4czԒGgƦ'!V3[E30A^K|(.|2,Qۣ}G*~[ӓ9gY\S'?ؠnbl,B *(wnQ"L d w[jRG5^t^RKӓyYg5| TsS(ӳAfJMN7[h9]zN5p9MFoܧPmC5s3ӓ @Z_Q~Y7sc,&,mr{D]S(az<{[& õJngzSQer]'Fag@({*5ͣm>fP;9\ P eY=4GFֲY ޑ=heָ5$EL'ײvVۣCezoӓ) @%kԫm޽m56= "9PZNzAw7pR,yi:{ ӓ@"@ZOd^lvl#Mu`ܮ=u"g{F}W.RTKw MMO`DUWO~Ao)y=[٫Ipӳ@=#@nkqR2io+_l.uM?5oazcRt\eh=tt:L~pV߭ (2LUx<6]ܻWQagB8.*%-Cez޺~H{9\',҂E$Gm"ٺY @ ~ViUZNe5oz):JOZD)iJs+5= X7.ԓ{FzJohz2ebccqiܸq!T*ֽ q>:_] \g`JJJ ={ `zB̷4)=SU^v ۳IS_ - >K/}Ys>נĦsm?2= )a{TjRGn=tU9]4= 1Qmܧr(ֹI  @ٕܘd50= !+՝GХ=uˈr:gB!bi.=h4 ׂ+]I  @jۡGQ0ӳ@"@ qQehǡr=5ƞNW9,R;9,Z琉TL @`sF/Zٻ5np{͸,IQ.ӳ$ @PqX .Ke}ۘ#|>K_]gVlTJm56= K4u^߯;itWizO"@fs&͔g͛OY[ z}&f L/#MU`vФtn;wӄ 0 @(wRtKspB TzBR^Wqg0s[˔M{KIqx\D~lH-^#wջmIPYUf,Œ"V3쭘+:wT1=0$:C~²,6=K/E>Wvq3EP}G+u,}XwE.Yle~3/SCo2D#ƛ`IקW/uM?5oaz;kbGJt=tt:LԳي Ӽi@&'Cƫǖoޭ辊 3= (UJG[Uur8r@uȲ,HM$ezjlz @:rF^K2wA z7_1ƫ#+*Q;CJ5~_[ӓ˲ׅzmjTkzHUU -[LOx}t54=%^R)PZJv{"^8Y++MOxj3JLJ2=#)+6hkqn+qTYUʻ_m}V ;8Irsk1=# ~(sgEezFH[K\f;oBIpRѹTUU'</^=4GmǫzQdYAm㞣JIЎCzft_ ;WuAȳ,Kvѥ9JlPKSFkFg%!F,Zv!5$ELZBg!=*)џK6=  9>}UgWnTr>TF*M/3;ir255in|>Ko|$C z}zM5S30Z44= $ jWhRGk4.r9y)֪=wA~P$G TzB]R\Wqg@LR=ڴT~;\ AcH.^V#&$V3haFF hǮ譆GJC@uD) )c5z`IeYzmzly4Ѳԑ<, Ffie^8"\g8 (kDwJj^LO @|>K/E>Wvq3ED;Z{f-J9&U \Nӳp/yr8z!%$~[tFuMc"L)"@wv,t{;t:@|n/VlT5L71= u_ezOkqTF:F*%ͣeU}4np;9\F,K(m(-IzD5zZKc#饨pYgl]tXnVkεtE'&lcY^PO}[CgFlqZ'鶑5 o4= 6#@P)8IUMtN'⧛O6Ħsm4=  {J*5)ݣ j]zNW<  snثۇjhf'O 3յ>=b^[Uh$ipӳGԉ2=ڰpYn(+W1lIN=xńkgzV^]Gv7mU(2,1'eÞ#JIPWc%p ,Kim̥PRGKFg @ 8n%5z`:-_[ㇶC&)2ez~HnJ*j>MO@"@|>K@Ϯܨmc}56= *.yY":^Ma.Y`~כ5yn,[ ݚ @Gj>&9^&EHӳ$ȳ㰦]],g{ $٣ 0ܥw ՠĦ'  !ƫ>ܠ7ꂤz꾊7= A aK}yE/0\ !jQFzoZ5⻇WXӓSVU?,YE;5z@f^K #c{3*QjG{Tj55 $$XMO,S1Z:RǘD՚ [rቺh2= ! b?DGe^W+ӓ Yz͚&o?6qQgHwRSej͖J9&U \NӳIHP|>M%ӡwn]MO~ 5^[Q~Q35׬kazp $8XTGw{;t$$-ޭe+6*L훘"$Ux5sYҾݮKғ*6*,W fޣJI@^WaYPWBh-IVhRz/m~Ň6=p&Lřq,Xɱ85ee/֧iε}Z߸߱8cq&L~8gr,X Zh e[ ٺ޺e:4khzpJ?TRQ[>ݰOPxYGԴ osz4=3$T3)|22v^e^II222L+ɱL BY}؍8gr,XɏIpeYG_}]? wyG_#ZrezGEE u*>>D i%!@؆`m!@؆`m!@؆`m!@ѣuEwy&L`z m!@؆`m!@؆`m!@؆`m!@؆`m!@؆`m!@؆`m!@؆`m!@؆`mAO{ʟIENDB`pint-0.24.4/pint/testsuite/baseline/test_plot_with_set_units.png000066400000000000000000000434001471316474000252230ustar00rootroot00000000000000PNG  IHDR Xvp9tEXtSoftwareMatplotlib version3.6.2, https://matplotlib.org/(b pHYsaa?iFmIDATxw`]$$fIaF'"J M[qOjm]-&ar B\ a0>?گҀH}Mr ~ =sleYw \2!@ e.Cp \2!@ e.Cp \2!@ e.Cp \2!@ e.Cp \2!@ e.Cp \2!@ e.Cp \2!@ e.Cp \rrrW_)&&F*;;[W^y"##MHꫯ4b3fΜ; DI kv8qOnz[TdljzjW"KLw&kRפ"6nܨ#Fv q#w*66V]v5}s=פ"w&AG'Xqq?ݯq=*T5krf?:!@ eÇv&qM*⚜Qפ"IE\T5eYTuM֭ㇽ/u4KÖ ӬkgUkG@ e.Cp \2!@ e.Cp \*ɲ,<T‰t5';td_)G@(wZzոv޹IG@38|HfGxI+2Lx?|XfnӧT'y 2\YaChzW!@I(1š rձ_s6ӳ4;:70=k Y%z~I٭k:5+7wTXY^OzLJczvQln|eY=xѢ~jSY>8YT'gjI TSSpY>OHsB/ûF'$^ʝzMj(L3uCLYѼbMo7Ѩ[hmPnzO#@Vmф49-}tEm=ӓ ^ܩ?ئWlUumU?,,q PӴv1M<;!@g SʨѼI8\~Ibixծ`z΂ڙTm9g}bdqʝ H Q'x2=p۫;7ҋ7uTh wk=Ocl$N]uU' p;NCo,۬p&uBLB @V*CӴr[h,TncM&?GPQ' 0ܩ_oѻoWVzxիdz/49l1s{x+|ZA=5mz+*-KK7_w- 7= .@SBRvkDf8r+eYn,ܠA쁾kfz\@+.ӟ> oh"_@Z/W>YuShӓ`jaY>^W>ߤCd\5iz #@PZS` ?ӳT_v K;' NKv/ߢhjlz  vd&iΣJZR ?YpC.wktm2$1祤̩7m~ءmx 4= n9}@)eSjdmg 8'K3y]3@sQ|ӓATJaI__vN oz< ?)%$jzsG l6\ 8+˲4=zv5E Ԧ~-ӳɢR=1?SK2hxrmLς#@PAJLvx~޹$x q:-}r^r7קT:!g $+9v&]V5g ۏj,-}|EضIR++w+[ջE]MYÂLς#@|ԁBOIctyJ~vՋA˳it)eToh^$/6食uYl}1"BLς!@|Μ|%&j<={]##Wp-|ا'd^X?GEx2MYAsM];(40}^*kI%$"14^CE Ʋ,\K/,ݨ5d\? 5= Dx܂R=:/C_n8;{7W*,7X%TQUWuhhzPNK~]ӾޢpKѵCLΈ`OYi{h,#8+MMC[G!Ô;5-z*RnZgBx 4.١zv}q 6ӳJ#@<ė葹٣{[ڦ'psEziFk. @Igpc)!)U;rtGϦ8rE!˲4w^MYA"mfzp7W\?[}-Z^!mwo2Y/W I:rX3n:76= R,K+oRjY, /sӵ|a\n~gՂ0藝4>šr,I@"@ (wZ˷-ެf stgՎpC'4!%MwոA8jM\po7֤)^ݲIK .PRԯ6?%mx 4= p9>ZTe8ȾeL @zb~j 1}$$(%]H/AM#@惧= -uk&8rH@,K)ѳ6Y-J6k ,*34〆h))8, (m %&D~޹$m ;վq&uBLpr5ivrD/nIWU@ Y#@Ѫ92Hgz1J*+wlnSumU?,, D&icz6zV"@Yt)eToh^$c nhĉ ?uÇ -77Uٺ<餈ӳ"JNN>ux M>]]v5=3'_ Iz(O^fȕ/97SSSխ[7C?>sӓ 2U/,HT %ep۫47vPh wĿ(IYO*!9Usx-$+ Yw酥2*TeTY"@-(գ2冃w3=quL|Һ]4.9Mye۟ LO||ik[ԥIf g>>"=4+]?mу[ieUnzS~rDNf{{oHӓDVZԴ6Qzshj,x= 4.š̽z|p;߿vYO#@W"`[]6= eJ,\[WwlWn`ӳƶçΜ|tSޣl6\ϲ,YW,ܠƵ053=  Z_ú73)$8_'X{s͸n$Dz,}S^b5PLdMӳT<ʱ=<']l:{5ףWS@ Y*c͎r}pWw][$ni4-SG3n묆g8pks4>š_)qPk<Vl:s2gӧRuMOpvJʜzMʝmgp++1١Nkbuo8rx L м}):$U]H/AMP `惧= -4{4G/F#,R/{ [SuZgfpR=1?SK3莞Mq 3=  PBRr KۻN MOBp ?V_nVJ 1=  kt}F_Bl+?Y @@Z-Gfi{.LO`EYS3٪wݦ-aU/,, ?Q)>I؁g= -pPP?ꥋbꘞ J7Uٺ<餈ӳ\GCyz8'F6GTD 2?uli}ԡqIK~q,ܠy{usz g p6UbCO͡[I<*Ͳ,k.tZFjqb? 5= !@@yj!ջ:VA~g0CvӸ4o+70= "@Y9~}E]Dh.jlzF3:|HJOsVpYk𳛞 Cd4ޞ*$^)-we[uq(94^QME Is@Rܛ[n @_dУ2czkڦ'R>\/.ջuuzN 7= #@QRBC;sMt{8rz ˲4g^=p„j ,>Z_ú73)$\8̽JLNUN^fY7tnlzD,҇?e/6*a>C4= "@bKt}\\N5ga^j͎r}xww jW$ @6NKئlE1u4.jdzH"@*s4>š_iܥ8<A%Vl:s2gWԫE]ӓWR_n?Vԥixթ`zu4_mP6UbCOiڭkI` @5,KZK/.ݨVQZO-BM=27C˲>1zv gzGPf4?un;-vMz6ЌۺQDYVSE8+MU%4֪g7= ~M&ͦTV'"@8ONl]&JnWdhYÞcJLvh\=>Bv,p{zt^ƒ5gLouiZ$TTZd5uMdžz 7= < @%l=tJ Ie7uMdq ,KsՔEդv%SLEpJZ_ú7ѳ׷WpY c %&;t4D3n:76= ,Kܩ׾ܤ؆ajVY5X~&I׊Mu_zv a7= ;j|C%eN}xww jW$J;-z{V]SG3nAg"@>`nƧ8k1D|ҊM4ivk)^բIO))s/7郕;uiz:4^uj>|%&;IM6NƑ+p%ӓ ֫nh'O"@QVVvءǏ˲ o; MAI[Yk΍T+,YH%m߾]#F/r$FtwP餡ݢ9r 4zheffꭷRUvmӓgaY~٭gydM-NVjRi?x %&&zb~fЈ^M5q 3= H%EFF*<Xw}\;jpdž'O1f͜9S妧ik{?+V>ן7# g1^n۶#GI&7쪉I9yzhv~rDcC_wEŐ!Cd~W'O|l!Ǟ|E[Rv;Ct1edd홙:~ rA ~-ѽ%0<RI'N͛z3}ъ|eJϻtezN 0= pJZbƎ{ַ_wuz\|#yJHrh<=C{W3l<#G(22o[>E۫W -x7 a ԰aC9}ݺu_8q Ϻ>|p >J>>2=zw-] U3/Y\+99Yɧ.77gJE_.\>whӧk׮UӬߗd,Ҵ[ushӓ3}855Uݺu3; j馛:H֯_t3x)95ks=#PfӸdK LOT#`Dһm[յifE"MT3lܸQ}vءǏ˲n7Z"MUۏ*VikT0{ڶmڵkW 6֤mޞϱ>H%=ҥ}BB;Ʋ;4M޼5^g\k= }zv_ 6ӳ ԩS'߿ 8K3y鯹csӓ4m4}Zj)JĂL=DiG@*^Sxxﯸ85mT~~~fi…z>Wn.j"#W222dԴiS)++m YkEԤv%SLMOvTO,X5GM>9ݻwK6mzn$c %$9ttZzzQԤNYA9rXNӏ[s4v`K=tyM|ʭ98;Me#{6Q'|^ܩ˷mWߖ6,^jQx}' 5>!ǞzʶsqK<Wꑹ ٣{[ڦ'@)*-+o'?U[:)<,$ USBCۏ;hDϦ8rpxjj諸Fa'P.LS>[}-Z^!|z'B[/W>YuShӓ]x ˲l&i%ydMӳCx%zxno<{X,*k1Kv\.ozPoi-ެf stgppsNiBJV-#MO Jʜzcf l7ƫnhY\0JLqhþ\=yu\v,ndI~=>/S55wlunazU7PXRlP/{tMz 7= *Ga[RBRv+Ы7w԰f; `eYJu[AMhQB?_,,*3$〆h))8,.:_wuLOepѷj(L3uCLOp܂RI?}kmPnxG@5[=GJgӝbMOդܩi_oX>j=U# P j|JfhP *#@-:s倫Qգye2= @@).+k_lև?e5uH'ծ`zn*Tm9g}bdL p3U+PMOm p J nnH/Q|Z''xҵ1G΁eYf^X58Z 5= A@%bAW3=yML P v׸dN]uU'N~خ7mQ|tRFR:!g8#4ܖZjmg7= Fܚ $Y:$ܩ_oѻoWߖ6,^j @?/49l1s{Pzdnjk^֬Ix%O+*-KK7_w- 7= EYۏ)!ɡG4gSl: |u{5ez5 *aIO+.Ӕkcvs7WH p*1١'ְκKcӓ9gY>^W>ߤ6 Bd\5iz>Վy:FmGU` ?ӳYcPaiqgw]W$|;-m|7;axY@/sd&iΣ7R ?Y?^͇5ivjٔt_/nY$?̩7m~ءKF鍡hz8G}@)eSjdmg @xؼ ծc(II " l[$Pnܩ?UoM[uV ӳ@#@D&icz6zVx#Q_gs倫Qգyӓ@5"@Q\VW>ߤWe:j 0= T3WBRӳ>18r/ @g}zrAi}ԡqI.QPR) 7hKcpc)_W.kI%$"14^CE !@T˲4s.tZFjɸ~jjz0P-r J }LO\ ?ӳa*nqKvTQMWuh`zp*tZzzsGk^bzp#*qTr4v@KMg7C`?n=%IC[G^༕;5-z*RnZg7F8/{h\C{sȕ4mg7G8g_?GfVfnj<ҊJҍ]ܡ^ƒMP))!)U;rtGϦ8r wYj jUl0ӳ"@U^qZz 8ܓpF*!)UGNkmuCƦ'/@8eYlFmPKC15M^qC'NTxxi>|nh|=<7]7ֽ둫*Y^kh @յkW3c8)i*.+wuץMO3}855Uݺu3; +wZzg6fь:axYK ;tHSZ jATnzbࣾ|XfϦzw˺'@>̩_mܩKF鍡hzCv-PbrSjdmgB>bq~=1?Skh>oazA K JeP/QaAgE^lSJHJ՞zvQl ,K)ѳ6Y-J6k@dQ4GSM6N~gH"@Su"TEvjdzi 8r^r7W}ԤNY +֤~Bh+?YgDlնMrH3= w *+wj7[ηԻE]5?Q)u\.o[s{@C<']!~5.cz9!@P\VW>ߤWe:"BL8gv+!)U[tWl^=`i}ԡqIP~q,ܠy{usz +|ܣLJHN"94^t6=  ,KZK/.ݨQZO-BMRr Jȼt}L_ ?ӳn1KNS^qn}ӓ tZzuiûqDYՊ 8|HJOsVpYk𳛞P~rDNdӿFT֑' HiSo.ۢ߮#5ΊhzK 9Vq)eciTmgT/2y 1յimӓ!@jRTZd55CzK'`TmO)!ɡ9zGSl @*dYݫgmPZW6**S´{B'߸wT̽JHNѼ͸n$D,}S^b5'PLdMӳc%zxNtXkGjvӳ5;j|J]uil}ӓ<rwVlӌo{L͸1,~yLZkܥg=↊oWa`/kk-a)劶بT%Lς)ܾC-khv<xJ Is@ICGf@U"@,R/{ [SuZgx%>-TOgSM6NA~gx->˱-(_nk:54=  9NCSڬÕ|/5bzO @Sr5ivrD+nz @3VmYir:-}|EضI>++w[ﶩwzkXg 2= ' jOj|COhm4v`+ynSxe৔QtQLӓ|S\VW>ߤWe늸z}H'E 2;)1١ugf8r.xzrz5 ҂}pӓ?x2MYAR殍 T3{ihYO*!9Usx-$ɲ,\K/,ݨVQZO-BM @qr Jȼt}L_ ?ӳP <ʺ]4.9MyezOtE' Nw߮i_oQ&1GsD>Uf9J_Z5g< pk?l9ffi=շUIRiSo.ۢ߮Diڭ 4= s@Rܛ[n*@|y@Px.Mk*D-%Ytn]ӱ^ƒM@#@`ܶçΜ||SG D6Gc,Ҝ{̢ j\;X ]0ӳPqTO}^ kX&z 3= Ռe=dhmuCƦ'EeYlF6 ?GP5M  pc%zxNtXkGjvӳbG5>š2>oz !@Pmʝ^Uf.uQ ӳ`jq0HS5]ZZs{:UnŦC4;]5Jzkz*SRk_n+wjPzzch 0= nAWbCidl\iycStIpS[AI[YkFzozI%$9x^vsbY~٭gydM-IVjAr KL-<kgz<Jq>dr K; MO"@NKq~Y+^jR',x(gWI#3&]F~vӳOr4aV,?GmLO @prZUnԴaW+,x wPr9W-e:$I_m8Gfff1uLO"@|\Qi^b>^+!`zvSBCOfSW -x7 7= >1ezzzOݧ[Fګf  !*1ɡ'4x5$XK/-ݨVB$ZDDx%zdne}bX,(ċ>q嗔?uGxrwۦ˷Kyx56= @E8;MU%4֪g7= Dx6֤myoOmizp ;Ʋ;tq(M5^g nϱ%&;~_No!fzpF[q@Px.Mk.%YJZ[WwlWn`ӳ?DxN)!ɡzGSlg @S1 3g;0=# n$''G_}bbblzGaauW*22Dp~!@ e.Cp \2!@^a8p?@p \s)M0A111 Tzt+55۬YFW]u觟~ۧ{W5R``7ocǪرCCU:u^ziҥ}N6MgK/hK/նm*Zl`C?c^!@uazƌs*!!Aqqq:zV\7k׮ZbnݺgnG}AT=$IW=t 5JڵӾ}4w\( @R>}TPPqƩnݺOtkܹ馛Nꫯnk믿;Ck֬6|F>}h„ ڱczթSGM4q;eY׉Ј#;TxeYj۶Zh/B6MTXX۫UVZl$鮻̙3fu޽DZl8qz-ׯ$)//O:ueYھ}v;]r%UZZ$I5~xeffC*--Utt5j5kv5j . p |LDD֬YWx[ZZnݪo]GUNNrrrK/T?NN>3]wuCo矫GŇ$jԨQVVViw=ԿI>%Ik׮Ç5f̘nww+<< U8>_]wݥ&M[nuwEںu?q6*))ɓ'աCvڥ={Vx}llooѴinWvmI$nE1z hٲe:u^{5͟?_NS4uTucǎU6??3=A 6<x@V׮]K/i钤0]veg}(i4kL7oM6sߺu KKKsNǟ?>\^zjԨխ[7lRo*#G$Iv]7x/^kV=bqW_~?+&&Fqqq紿{{wگuĉsX3x|ȩS!C(>>^Z|~W曲?}{Ըqc۷O~´xbI/e˖i5jbccu͙3G+WTDD{1%''k7nԩO>D;wԼyd/j4h ;w꣏>g@C CBBBhٲeGV׿Ucǎ$ 8P?^x;S ԳgO=ոqcYFO?>S T: return fun(*args, **kwargs) @pytest.fixture def setup(registry_tiny: pint.UnitRegistry) -> SetupType: data: dict[str, Any] = {} data["int"] = 1 data["float"] = 1.0 data["complex"] = complex(1, 2) return registry_tiny, data @pytest.fixture def my_setup(setup: SetupType) -> SetupType: ureg, data = setup for unit in UNITS + OTHER_UNITS: data["uc_%s" % unit] = pint.util.to_units_container(unit, ureg) return ureg, data def test_build_cache(setup: SetupType, benchmark): ureg, _ = setup benchmark(ureg._build_cache) @pytest.mark.parametrize("key", UNITS) @pytest.mark.parametrize("pre_run", (True, False)) def test_getattr(benchmark, setup: SetupType, key: str, pre_run: bool): ureg, _ = setup if pre_run: no_benchmark(getattr, ureg, key) benchmark(getattr, ureg, key) @pytest.mark.parametrize("key", UNITS) @pytest.mark.parametrize("pre_run", (True, False)) def test_getitem(benchmark, setup: SetupType, key: str, pre_run: bool): ureg, _ = setup if pre_run: no_benchmark(getitem, ureg, key) benchmark(getitem, ureg, key) @pytest.mark.parametrize("key", UNITS) @pytest.mark.parametrize("pre_run", (True, False)) def test_parse_unit_name(benchmark, setup: SetupType, key: str, pre_run: bool): ureg, _ = setup if pre_run: no_benchmark(ureg.parse_unit_name, key) benchmark(ureg.parse_unit_name, key) @pytest.mark.parametrize("key", UNITS) @pytest.mark.parametrize("pre_run", (True, False)) def test_parse_units(benchmark, setup: SetupType, key: str, pre_run: bool): ureg, _ = setup if pre_run: no_benchmark(ureg.parse_units, key) benchmark(ureg.parse_units, key) @pytest.mark.parametrize("key", UNITS) @pytest.mark.parametrize("pre_run", (True, False)) def test_parse_expression(benchmark, setup: SetupType, key: str, pre_run: bool): ureg, _ = setup if pre_run: no_benchmark(ureg.parse_expression, "1.0 " + key) benchmark(ureg.parse_expression, "1.0 " + key) @pytest.mark.parametrize("unit", OTHER_UNITS) @pytest.mark.parametrize("pre_run", (True, False)) def test_base_units(benchmark, setup: SetupType, unit: str, pre_run: bool): ureg, _ = setup if pre_run: no_benchmark(ureg.get_base_units, unit) benchmark(ureg.get_base_units, unit) @pytest.mark.parametrize("unit", OTHER_UNITS) @pytest.mark.parametrize("pre_run", (True, False)) def test_to_units_container_registry( benchmark, setup: SetupType, unit: str, pre_run: bool ): ureg, _ = setup if pre_run: no_benchmark(pint.util.to_units_container, unit, ureg) benchmark(pint.util.to_units_container, unit, ureg) @pytest.mark.parametrize("unit", OTHER_UNITS) @pytest.mark.parametrize("pre_run", (True, False)) def test_to_units_container_detached( benchmark, setup: SetupType, unit: str, pre_run: bool ): ureg, _ = setup if pre_run: no_benchmark(pint.util.to_units_container, unit, ureg) benchmark(pint.util.to_units_container, unit, ureg) @pytest.mark.parametrize( "key", (("uc_meter", "uc_kilometer"), ("uc_kilometer/second", "uc_angstrom/minute")) ) @pytest.mark.parametrize("pre_run", (True, False)) def test_convert_from_uc(benchmark, my_setup: SetupType, key: str, pre_run: bool): src, dst = key ureg, data = my_setup if pre_run: no_benchmark(ureg._convert, 1.0, data[src], data[dst]) benchmark(ureg._convert, 1.0, data[src], data[dst]) def test_parse_math_expression(benchmark, my_setup): ureg, _ = my_setup benchmark(ureg.parse_expression, "3 + 5 * 2 + value", value=10) # This code is duplicated with other benchmarks but simplify comparison @pytest.fixture def cache_folder(tmppath_factory: pathlib.Path): folder = tmppath_factory / "cache" folder.mkdir(parents=True, exist_ok=True) return folder @pytest.mark.parametrize("use_cache_folder", (None, True)) def test_load_definitions_stage_1(benchmark, cache_folder, use_cache_folder): """empty registry creation""" if use_cache_folder is True: use_cache_folder = cache_folder else: use_cache_folder = None benchmark(pint.UnitRegistry, None, cache_folder=use_cache_folder) @pytest.mark.skip( "Test failing ValueError: Group USCSLengthInternational already present in registry" ) @pytest.mark.parametrize("use_cache_folder", (None, True)) def test_load_definitions_stage_2(benchmark, cache_folder, use_cache_folder): """empty registry creation + parsing default files + definition object loading""" if use_cache_folder is True: use_cache_folder = cache_folder else: use_cache_folder = None from pint import errors defpath = pathlib.Path(errors.__file__).parent / "default_en.txt" empty_registry = pint.UnitRegistry(None, cache_folder=use_cache_folder) benchmark(empty_registry.load_definitions, defpath, True) @pytest.mark.parametrize("use_cache_folder", (None, True)) def test_load_definitions_stage_3(benchmark, cache_folder, use_cache_folder): """empty registry creation + parsing default files + definition object loading + cache building""" if use_cache_folder is True: use_cache_folder = cache_folder else: use_cache_folder = None from pint import errors defpath = pathlib.Path(errors.__file__).parent / "default_en.txt" empty_registry = pint.UnitRegistry(None, cache_folder=use_cache_folder) loaded_files = empty_registry.load_definitions(defpath, True) benchmark(empty_registry._build_cache, loaded_files) pint-0.24.4/pint/testsuite/benchmarks/test_20_quantity.py000066400000000000000000000042131471316474000234720ustar00rootroot00000000000000from __future__ import annotations import itertools as it import operator from typing import Any import pytest import pint UNITS = ("meter", "kilometer", "second", "minute", "angstrom") ALL_VALUES = ("int", "float", "complex") ALL_VALUES_Q = tuple( f"{a}_{b}" for a, b in it.product(ALL_VALUES, ("meter", "kilometer")) ) OP1 = (operator.neg, operator.truth) OP2_CMP = (operator.eq,) # operator.lt) OP2_MATH = (operator.add, operator.sub, operator.mul, operator.truediv) @pytest.fixture def setup(registry_tiny) -> tuple[pint.UnitRegistry, dict[str, Any]]: data = {} data["int"] = 1 data["float"] = 1.0 data["complex"] = complex(1, 2) ureg = registry_tiny for key in ALL_VALUES: data[key + "_meter"] = data[key] * ureg.meter data[key + "_kilometer"] = data[key] * ureg.kilometer return ureg, data @pytest.mark.parametrize("key", ALL_VALUES) def test_build_by_mul(benchmark, setup, key): ureg, data = setup benchmark(operator.mul, data[key], ureg.meter) @pytest.mark.parametrize("key", ALL_VALUES_Q) @pytest.mark.parametrize("op", OP1) def test_op1(benchmark, setup, key, op): _, data = setup benchmark(op, data[key]) @pytest.mark.parametrize("keys", tuple(it.product(ALL_VALUES_Q, ALL_VALUES_Q))) @pytest.mark.parametrize("op", OP2_MATH + OP2_CMP) def test_op2(benchmark, setup, keys, op): _, data = setup key1, key2 = keys benchmark(op, data[key1], data[key2]) @pytest.mark.parametrize("key", ALL_VALUES_Q) def test_wrapper(benchmark, setup, key): ureg, data = setup value, unit = key.split("_") @ureg.wraps(None, (unit,)) def f(a): pass benchmark(f, data[key]) @pytest.mark.parametrize("key", ALL_VALUES_Q) def test_wrapper_nonstrict(benchmark, setup, key): ureg, data = setup value, unit = key.split("_") @ureg.wraps(None, (unit,), strict=False) def f(a): pass benchmark(f, data[value]) @pytest.mark.parametrize("key", ALL_VALUES_Q) def test_wrapper_ret(benchmark, setup, key): ureg, data = setup value, unit = key.split("_") @ureg.wraps(unit, (unit,)) def f(a): return a benchmark(f, data[key]) pint-0.24.4/pint/testsuite/benchmarks/test_30_numpy.py000066400000000000000000000062251471316474000227720ustar00rootroot00000000000000from __future__ import annotations import itertools as it import operator from collections.abc import Generator from typing import Any import pytest import pint from pint.compat import np from ..helpers import requires_numpy SMALL_VEC_LEN = 3 MID_VEC_LEN = 1_000 LARGE_VEC_LEN = 1_000_000 LENGTHS = ("short", "mid") ALL_VALUES = tuple( f"{a}_{b}" for a, b in it.product(LENGTHS, ("list", "tuple", "array")) ) ALL_ARRAYS = ("short_array", "mid_array") UNITS = ("meter", "kilometer") ALL_ARRAYS_Q = tuple(f"{a}_{b}" for a, b in it.product(ALL_ARRAYS, UNITS)) OP1 = (operator.neg,) # operator.truth, OP2_CMP = (operator.eq, operator.lt) OP2_MATH = (operator.add, operator.sub, operator.mul, operator.truediv) if np is None: NUMPY_OP1_MATH = NUMPY_OP2_CMP = NUMPY_OP2_MATH = () else: NUMPY_OP1_MATH = (np.sqrt, np.square) NUMPY_OP2_CMP = (np.equal, np.less) NUMPY_OP2_MATH = (np.add, np.subtract, np.multiply, np.true_divide) def float_range(n: int) -> Generator[float, None, None]: return (float(x) for x in range(1, n + 1)) @pytest.fixture def setup(registry_tiny) -> tuple[pint.UnitRegistry, dict[str, Any]]: data = {} short = list(float_range(3)) mid = list(float_range(1_000)) data["short_list"] = short data["short_tuple"] = tuple(short) data["short_array"] = np.asarray(short) data["mid_list"] = mid data["mid_tuple"] = tuple(mid) data["mid_array"] = np.asarray(mid) ureg = registry_tiny for key in ALL_ARRAYS: data[key + "_meter"] = data[key] * ureg.meter data[key + "_kilometer"] = data[key] * ureg.kilometer return ureg, data @requires_numpy def test_finding_meter_getattr(benchmark, setup): ureg, _ = setup benchmark(getattr, ureg, "meter") @requires_numpy def test_finding_meter_getitem(benchmark, setup): ureg, _ = setup benchmark(operator.getitem, ureg, "meter") @requires_numpy @pytest.mark.parametrize( "unit", ["meter", "angstrom", "meter/second", "angstrom/minute"] ) def test_base_units(benchmark, setup, unit): ureg, _ = setup benchmark(ureg.get_base_units, unit) @requires_numpy @pytest.mark.parametrize("key", ALL_ARRAYS) def test_build_by_mul(benchmark, setup, key): ureg, data = setup benchmark(operator.mul, data[key], ureg.meter) @requires_numpy @pytest.mark.parametrize("key", ALL_ARRAYS_Q) @pytest.mark.parametrize("op", OP1 + NUMPY_OP1_MATH) def test_op1(benchmark, setup, key, op): _, data = setup benchmark(op, data[key]) @requires_numpy @pytest.mark.parametrize( "keys", ( ("short_array_meter", "short_array_meter"), ("short_array_meter", "short_array_kilometer"), ("short_array_kilometer", "short_array_meter"), ("short_array_kilometer", "short_array_kilometer"), ("mid_array_meter", "mid_array_meter"), ("mid_array_meter", "mid_array_kilometer"), ("mid_array_kilometer", "mid_array_meter"), ("mid_array_kilometer", "mid_array_kilometer"), ), ) @pytest.mark.parametrize("op", OP2_MATH + OP2_CMP + NUMPY_OP2_MATH + NUMPY_OP2_CMP) def test_op2(benchmark, setup, keys, op): _, data = setup key1, key2 = keys benchmark(op, data[key1], data[key2]) pint-0.24.4/pint/testsuite/conftest.py000066400000000000000000000043061471316474000177670ustar00rootroot00000000000000# pytest fixtures from __future__ import annotations import pathlib import pytest import pint _TINY = """ yocto- = 1e-24 = y- zepto- = 1e-21 = z- atto- = 1e-18 = a- femto- = 1e-15 = f- pico- = 1e-12 = p- nano- = 1e-9 = n- micro- = 1e-6 = µ- = μ- = u- = mu- = mc- milli- = 1e-3 = m- centi- = 1e-2 = c- deci- = 1e-1 = d- deca- = 1e+1 = da- = deka- hecto- = 1e2 = h- kilo- = 1e3 = k- mega- = 1e6 = M- giga- = 1e9 = G- tera- = 1e12 = T- peta- = 1e15 = P- exa- = 1e18 = E- zetta- = 1e21 = Z- yotta- = 1e24 = Y- meter = [length] = m = metre second = [time] = s = sec angstrom = 1e-10 * meter = Å = ångström = Å minute = 60 * second = min """ @pytest.fixture(scope="session") def tmppath_factory(tmpdir_factory) -> pathlib.Path: tmp = tmpdir_factory.mktemp("pint") return pathlib.Path(tmp) @pytest.fixture(scope="session") def tiny_definition_file(tmppath_factory: pathlib.Path) -> pathlib.Path: folder = tmppath_factory / "definitions" folder.mkdir(exist_ok=True, parents=True) path = folder / "tiny.txt" if not path.exists(): path.write_text(_TINY, encoding="utf-8") return path @pytest.fixture def registry_empty(): return pint.UnitRegistry(None) @pytest.fixture def registry_tiny(tiny_definition_file: pathlib.Path): return pint.UnitRegistry(tiny_definition_file) @pytest.fixture def func_registry(): return pint.UnitRegistry() @pytest.fixture(scope="class") def class_registry(): """Only use for those test that do not modify the registry.""" return pint.UnitRegistry() @pytest.fixture(scope="module") def module_registry(): """Only use for those test that do not modify the registry.""" return pint.UnitRegistry() @pytest.fixture(scope="session") def sess_registry(): """Only use for those test that do not modify the registry.""" return pint.UnitRegistry() @pytest.fixture(scope="class") def class_tiny_app_registry(): ureg_bak = pint.get_application_registry() ureg = pint.UnitRegistry(None) ureg.define("foo = []") ureg.define("bar = foo / 2") pint.set_application_registry(ureg) assert pint.get_application_registry() is ureg yield ureg pint.set_application_registry(ureg_bak) pint-0.24.4/pint/testsuite/helpers.py000066400000000000000000000111151471316474000176000ustar00rootroot00000000000000from __future__ import annotations import contextlib import doctest import pickle import re import pytest from packaging.version import parse as version_parse from pint.testing import assert_allclose as assert_quantity_almost_equal # noqa: F401 from pint.testing import assert_equal as assert_quantity_equal # noqa: F401 from ..compat import ( HAS_BABEL, HAS_MIP, HAS_NUMPY, HAS_NUMPY_ARRAY_FUNCTION, HAS_UNCERTAINTIES, NUMPY_VER, ) _number_re = r"([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)" _q_re = re.compile( r"%s)" % _number_re + r"\s*,\s*" + r"'(?P.*)'" + r"\s*" + r"\)>" ) _sq_re = re.compile( r"\s*" + r"(?P%s)" % _number_re + r"\s" + r"(?P.*)" ) _unit_re = re.compile(r"") def internal(ureg): return ureg class PintOutputChecker(doctest.OutputChecker): def check_output(self, want, got, optionflags): check = super().check_output(want, got, optionflags) if check: return check with contextlib.suppress(Exception): if eval(want) == eval(got): return True for regex in (_q_re, _sq_re): with contextlib.suppress(Exception): parsed_got = regex.match(got.replace(r"\\", "")).groupdict() parsed_want = regex.match(want.replace(r"\\", "")).groupdict() v1 = float(parsed_got["magnitude"]) v2 = float(parsed_want["magnitude"]) if abs(v1 - v2) > abs(v1) / 1000: return False if parsed_got["unit"] != parsed_want["unit"]: return False return True cnt = 0 for regex in (_unit_re,): with contextlib.suppress(Exception): parsed_got, tmp = regex.subn("\1", got) cnt += tmp parsed_want, temp = regex.subn("\1", want) cnt += tmp if parsed_got == parsed_want: return True if cnt: # If there was any replacement, we try again the previous methods. return self.check_output(parsed_want, parsed_got, optionflags) return False requires_numpy = pytest.mark.skipif(not HAS_NUMPY, reason="Requires NumPy") requires_not_numpy = pytest.mark.skipif( HAS_NUMPY, reason="Requires NumPy not to be installed." ) def requires_array_function_protocol(): if not HAS_NUMPY: return pytest.mark.skip("Requires NumPy") return pytest.mark.skipif( not HAS_NUMPY_ARRAY_FUNCTION, reason="Requires __array_function__ protocol to be enabled", ) def requires_not_array_function_protocol(): if not HAS_NUMPY: return pytest.mark.skip("Requires NumPy") return pytest.mark.skipif( HAS_NUMPY_ARRAY_FUNCTION, reason="Requires __array_function__ protocol to be unavailable or disabled", ) def requires_numpy_previous_than(version): if not HAS_NUMPY: return pytest.mark.skip("Requires NumPy") return pytest.mark.skipif( not version_parse(NUMPY_VER) < version_parse(version), reason="Requires NumPy < %s" % version, ) def requires_numpy_at_least(version): if not HAS_NUMPY: return pytest.mark.skip("Requires NumPy") return pytest.mark.skipif( not version_parse(NUMPY_VER) >= version_parse(version), reason="Requires NumPy >= %s" % version, ) def requires_babel(tested_locales=[]): if not HAS_BABEL: return pytest.mark.skip("Requires Babel with units support") import locale default_locale = locale.getlocale(locale.LC_NUMERIC) locales_unavailable = False try: for loc in tested_locales: locale.setlocale(locale.LC_NUMERIC, loc) except locale.Error: locales_unavailable = True locale.setlocale(locale.LC_NUMERIC, default_locale) return pytest.mark.skipif( locales_unavailable, reason="Tested locales not available." ) requires_not_babel = pytest.mark.skipif( HAS_BABEL, reason="Requires Babel not to be installed" ) requires_uncertainties = pytest.mark.skipif( not HAS_UNCERTAINTIES, reason="Requires Uncertainties" ) requires_not_uncertainties = pytest.mark.skipif( HAS_UNCERTAINTIES, reason="Requires Uncertainties not to be installed." ) requires_mip = pytest.mark.skipif(not HAS_MIP, reason="Requires MIP") # Parametrization allprotos = pytest.mark.parametrize( ("protocol",), [(p,) for p in range(pickle.HIGHEST_PROTOCOL + 1)] ) check_all_bool = pytest.mark.parametrize("check_all", [False, True]) pint-0.24.4/pint/testsuite/test_application_registry.py000066400000000000000000000222411471316474000234320ustar00rootroot00000000000000"""Tests for global UnitRegistry, Unit, and Quantity """ from __future__ import annotations import pickle import pytest from pint import ( ApplicationRegistry, Measurement, Quantity, UndefinedUnitError, Unit, UnitRegistry, get_application_registry, set_application_registry, ) from pint.testsuite import helpers @pytest.fixture def isolate_application_registry(monkeypatch): import pint new = ApplicationRegistry(UnitRegistry()) monkeypatch.setattr(pint, "application_registry", new) class TestDefaultApplicationRegistry: @helpers.allprotos def test_unit(self, protocol): u = Unit("kg") assert str(u) == "kilogram" u = pickle.loads(pickle.dumps(u, protocol)) assert str(u) == "kilogram" @helpers.allprotos def test_quantity_1arg(self, protocol): q = Quantity("123 kg") assert str(q.units) == "kilogram" assert q.to("t").magnitude == 0.123 q = pickle.loads(pickle.dumps(q, protocol)) assert str(q.units) == "kilogram" assert q.to("t").magnitude == 0.123 @helpers.allprotos def test_quantity_2args(self, protocol): q = Quantity(123, "kg") assert str(q.units) == "kilogram" assert q.to("t").magnitude == 0.123 q = pickle.loads(pickle.dumps(q, protocol)) assert str(q.units) == "kilogram" assert q.to("t").magnitude == 0.123 @helpers.requires_uncertainties() @helpers.allprotos def test_measurement_2args(self, protocol): m = Measurement(Quantity(123, "kg"), Quantity(15, "kg")) assert m.value.magnitude == 123 assert m.error.magnitude == 15 assert str(m.units) == "kilogram" m = pickle.loads(pickle.dumps(m, protocol)) assert m.value.magnitude == 123 assert m.error.magnitude == 15 assert str(m.units) == "kilogram" @helpers.requires_uncertainties() @helpers.allprotos def test_measurement_3args(self, protocol): m = Measurement(123, 15, "kg") assert m.value.magnitude == 123 assert m.error.magnitude == 15 assert str(m.units) == "kilogram" m = pickle.loads(pickle.dumps(m, protocol)) assert m.value.magnitude == 123 assert m.error.magnitude == 15 assert str(m.units) == "kilogram" def test_get_application_registry(self): ureg = get_application_registry() u = ureg.Unit("kg") assert str(u) == "kilogram" @helpers.allprotos def test_pickle_crash(self, protocol): ureg = UnitRegistry(None) ureg.define("foo = []") q = ureg.Quantity(123, "foo") b = pickle.dumps(q, protocol) with pytest.raises(UndefinedUnitError): pickle.loads(b) b = pickle.dumps(q.units, protocol) with pytest.raises(UndefinedUnitError): pickle.loads(b) @helpers.requires_uncertainties() @helpers.allprotos def test_pickle_crash_measurement(self, protocol): ureg = UnitRegistry(None) ureg.define("foo = []") m = ureg.Quantity(123, "foo").plus_minus(10) b = pickle.dumps(m, protocol) with pytest.raises(UndefinedUnitError): pickle.loads(b) @pytest.fixture def custom_registry(isolate_application_registry): ureg = UnitRegistry(None) ureg.define("foo = []") ureg.define("bar = foo / 2") set_application_registry(ureg) @pytest.mark.usefixtures("custom_registry") class TestCustomApplicationRegistry: @helpers.allprotos def test_unit(self, protocol): u = Unit("foo") assert str(u) == "foo" u = pickle.loads(pickle.dumps(u, protocol)) assert str(u) == "foo" @helpers.allprotos def test_quantity_1arg(self, protocol): q = Quantity("123 foo") assert str(q.units) == "foo" assert q.to("bar").magnitude == 246 q = pickle.loads(pickle.dumps(q, protocol)) assert str(q.units) == "foo" assert q.to("bar").magnitude == 246 @helpers.allprotos def test_quantity_2args(self, protocol): q = Quantity(123, "foo") assert str(q.units) == "foo" assert q.to("bar").magnitude == 246 q = pickle.loads(pickle.dumps(q, protocol)) assert str(q.units) == "foo" assert q.to("bar").magnitude == 246 @helpers.requires_uncertainties() @helpers.allprotos def test_measurement_2args(self, protocol): m = Measurement(Quantity(123, "foo"), Quantity(10, "bar")) assert m.value.magnitude == 123 assert m.error.magnitude == 5 assert str(m.units) == "foo" m = pickle.loads(pickle.dumps(m, protocol)) assert m.value.magnitude == 123 assert m.error.magnitude == 5 assert str(m.units) == "foo" @helpers.requires_uncertainties() @helpers.allprotos def test_measurement_3args(self, protocol): m = Measurement(123, 5, "foo") assert m.value.magnitude == 123 assert m.error.magnitude == 5 assert str(m.units) == "foo" m = pickle.loads(pickle.dumps(m, protocol)) assert m.value.magnitude == 123 assert m.error.magnitude == 5 assert str(m.units) == "foo" @pytest.mark.usefixtures("isolate_application_registry") class TestSwapApplicationRegistry: """Test that the constructors of Quantity, Unit, and Measurement capture the registry that is set as the application registry at creation time Parameters ---------- Returns ------- """ ureg1 = UnitRegistry(None) ureg1.define("foo = [dim1]") ureg1.define("bar = foo / 2") ureg2 = UnitRegistry(None) ureg2.define("foo = [dim2]") ureg2.define("bar = foo / 3") @helpers.allprotos def test_quantity_1arg(self, protocol): set_application_registry(self.ureg1) q1 = Quantity("1 foo") set_application_registry(self.ureg2) q2 = Quantity("1 foo") q3 = pickle.loads(pickle.dumps(q1, protocol)) assert q1.dimensionality == {"[dim1]": 1} assert q2.dimensionality == {"[dim2]": 1} assert q3.dimensionality == {"[dim2]": 1} assert q1.to("bar").magnitude == 2 assert q2.to("bar").magnitude == 3 assert q3.to("bar").magnitude == 3 @helpers.allprotos def test_quantity_2args(self, protocol): set_application_registry(self.ureg1) q1 = Quantity(1, "foo") set_application_registry(self.ureg2) q2 = Quantity(1, "foo") q3 = pickle.loads(pickle.dumps(q1, protocol)) assert q1.dimensionality == {"[dim1]": 1} assert q2.dimensionality == {"[dim2]": 1} assert q3.dimensionality == {"[dim2]": 1} assert q1.to("bar").magnitude == 2 assert q2.to("bar").magnitude == 3 assert q3.to("bar").magnitude == 3 @helpers.allprotos def test_unit(self, protocol): set_application_registry(self.ureg1) u1 = Unit("bar") set_application_registry(self.ureg2) u2 = Unit("bar") u3 = pickle.loads(pickle.dumps(u1, protocol)) assert u1.dimensionality == {"[dim1]": 1} assert u2.dimensionality == {"[dim2]": 1} assert u3.dimensionality == {"[dim2]": 1} @helpers.requires_uncertainties() @helpers.allprotos def test_measurement_2args(self, protocol): set_application_registry(self.ureg1) m1 = Measurement(Quantity(10, "foo"), Quantity(1, "foo")) set_application_registry(self.ureg2) m2 = Measurement(Quantity(10, "foo"), Quantity(1, "foo")) m3 = pickle.loads(pickle.dumps(m1, protocol)) assert m1.dimensionality == {"[dim1]": 1} assert m2.dimensionality == {"[dim2]": 1} assert m3.dimensionality == {"[dim2]": 1} assert m1.to("bar").value.magnitude == 20 assert m2.to("bar").value.magnitude == 30 assert m3.to("bar").value.magnitude == 30 assert m1.to("bar").error.magnitude == 2 assert m2.to("bar").error.magnitude == 3 assert m3.to("bar").error.magnitude == 3 @helpers.requires_uncertainties() @helpers.allprotos def test_measurement_3args(self, protocol): set_application_registry(self.ureg1) m1 = Measurement(10, 1, "foo") set_application_registry(self.ureg2) m2 = Measurement(10, 1, "foo") m3 = pickle.loads(pickle.dumps(m1, protocol)) assert m1.dimensionality == {"[dim1]": 1} assert m2.dimensionality == {"[dim2]": 1} assert m1.to("bar").value.magnitude == 20 assert m2.to("bar").value.magnitude == 30 assert m3.to("bar").value.magnitude == 30 assert m1.to("bar").error.magnitude == 2 assert m2.to("bar").error.magnitude == 3 assert m3.to("bar").error.magnitude == 3 @pytest.mark.usefixtures("isolate_application_registry") def test_update_saved_registries(): ureg1 = get_application_registry() ureg2 = get_application_registry() new = UnitRegistry() new.define("foo = []") set_application_registry(new) assert ureg1.Unit("foo") == ureg2.Unit("foo") @pytest.mark.usefixtures("isolate_application_registry") def test_modify_application_registry(): ar = get_application_registry() u = ar.get() ar.force_ndarray_like = True assert ar.force_ndarray_like == u.force_ndarray_like pint-0.24.4/pint/testsuite/test_babel.py000066400000000000000000000060351471316474000202470ustar00rootroot00000000000000from __future__ import annotations import os import pytest from pint import UnitRegistry from pint.testsuite import helpers @helpers.requires_not_babel() def test_no_babel(func_registry): ureg = func_registry distance = 24.0 * ureg.meter with pytest.raises(Exception): distance.format_babel(locale="fr_FR", length="long") @helpers.requires_babel(["fr_FR", "ro_RO"]) def test_format(func_registry): ureg = func_registry dirname = os.path.dirname(__file__) ureg.load_definitions(os.path.join(dirname, "../xtranslated.txt")) distance = 24.1 * ureg.meter assert distance.format_babel(locale="fr_FR", length="long") == "24,1 mètres" time = 8.1 * ureg.second assert time.format_babel(locale="fr_FR", length="long") == "8,1 secondes" assert time.format_babel(locale="ro_RO", length="short") == "8,1 s" acceleration = distance / time**2 assert ( acceleration.format_babel(spec=".3nP", locale="fr_FR", length="long") == "0,367 mètre par seconde²" ) mks = ureg.get_system("mks") assert mks.format_babel(locale="fr_FR") == "métrique" @helpers.requires_babel(["fr_FR", "ro_RO"]) def test_registry_locale(): ureg = UnitRegistry(fmt_locale="fr_FR") dirname = os.path.dirname(__file__) ureg.load_definitions(os.path.join(dirname, "../xtranslated.txt")) distance = 24.1 * ureg.meter assert distance.format_babel(length="long") == "24,1 mètres" time = 8.1 * ureg.second assert time.format_babel(length="long") == "8,1 secondes" assert time.format_babel(locale="ro_RO", length="short") == "8,1 s" acceleration = distance / time**2 assert ( acceleration.format_babel(spec=".3nC", length="long") == "0,367 mètre/seconde**2" ) assert ( acceleration.format_babel(spec=".3nP", length="long") == "0,367 mètre par seconde²" ) mks = ureg.get_system("mks") assert mks.format_babel(locale="fr_FR") == "métrique" @helpers.requires_babel(["fr_FR"]) def test_unit_format_babel(): ureg = UnitRegistry(fmt_locale="fr_FR") volume = ureg.Unit("ml") assert volume.format_babel() == "millilitre" ureg.formatter.default_format = "~" assert volume.format_babel() == "ml" dimensionless_unit = ureg.Unit("") assert dimensionless_unit.format_babel() == "" ureg.set_fmt_locale(None) with pytest.raises(ValueError): volume.format_babel() @helpers.requires_babel() def test_no_registry_locale(func_registry): ureg = func_registry distance = 24.0 * ureg.meter with pytest.raises(Exception): distance.format_babel() @helpers.requires_babel(["fr_FR"]) def test_str(func_registry): ureg = func_registry d = 24.1 * ureg.meter s = "24.1 meter" assert str(d) == s assert "%s" % d == s assert f"{d}" == s ureg.set_fmt_locale("fr_FR") s = "24,1 mètres" assert str(d) == s assert "%s" % d == s assert f"{d}" == s ureg.set_fmt_locale(None) s = "24.1 meter" assert str(d) == s assert "%s" % d == s assert f"{d}" == s pint-0.24.4/pint/testsuite/test_compat.py000066400000000000000000000056361471316474000204730ustar00rootroot00000000000000from __future__ import annotations import math from datetime import datetime, timedelta from pint.compat import eq, isnan, np, zero_or_nan from pint.testsuite import helpers @helpers.check_all_bool def test_eq(check_all): assert eq(0, 0, check_all) assert not eq(0, 1, check_all) @helpers.requires_numpy def test_eq_numpy(): assert eq(np.array([1, 2]), np.array([1, 2]), True) assert not eq(np.array([1, 2]), np.array([1, 3]), True) np.testing.assert_equal( eq(np.array([1, 2]), np.array([1, 2]), False), np.array([True, True]) ) np.testing.assert_equal( eq(np.array([1, 2]), np.array([1, 3]), False), np.array([True, False]) ) # Mixed numpy/scalar assert eq(1, np.array([1, 1]), True) assert eq(np.array([1, 1]), 1, True) assert not eq(1, np.array([1, 2]), True) assert not eq(np.array([1, 2]), 1, True) @helpers.check_all_bool def test_isnan(check_all): assert not isnan(0, check_all) assert not isnan(0.0, check_all) assert isnan(math.nan, check_all) assert not isnan(datetime(2000, 1, 1), check_all) assert not isnan(timedelta(seconds=1), check_all) assert not isnan("foo", check_all) @helpers.requires_numpy def test_isnan_numpy(): assert isnan(np.nan, True) assert isnan(np.nan, False) assert not isnan(np.array([0, 0]), True) assert isnan(np.array([0, np.nan]), True) assert not isnan(np.array(["A", "B"]), True) np.testing.assert_equal( isnan(np.array([1, np.nan]), False), np.array([False, True]) ) np.testing.assert_equal( isnan(np.array(["A", "B"]), False), np.array([False, False]) ) @helpers.requires_numpy def test_isnan_nat(): a = np.array(["2000-01-01", "NaT"], dtype="M8") b = np.array(["2000-01-01", "2000-01-02"], dtype="M8") assert isnan(a, True) assert not isnan(b, True) np.testing.assert_equal(isnan(a, False), np.array([False, True])) np.testing.assert_equal(isnan(b, False), np.array([False, False])) # Scalar numpy.datetime64 assert not isnan(a[0], True) assert not isnan(a[0], False) assert isnan(a[1], True) assert isnan(a[1], False) @helpers.check_all_bool def test_zero_or_nan(check_all): assert zero_or_nan(0, check_all) assert zero_or_nan(math.nan, check_all) assert not zero_or_nan(1, check_all) assert not zero_or_nan(datetime(2000, 1, 1), check_all) assert not zero_or_nan(timedelta(seconds=1), check_all) assert not zero_or_nan("foo", check_all) @helpers.requires_numpy def test_zero_or_nan_numpy(): assert zero_or_nan(np.nan, True) assert zero_or_nan(np.nan, False) assert zero_or_nan(np.array([0, np.nan]), True) assert not zero_or_nan(np.array([1, np.nan]), True) assert not zero_or_nan(np.array([0, 1]), True) assert not zero_or_nan(np.array(["A", "B"]), True) np.testing.assert_equal( zero_or_nan(np.array([0, 1, np.nan]), False), np.array([True, False, True]) ) pint-0.24.4/pint/testsuite/test_compat_downcast.py000066400000000000000000000126651471316474000223750ustar00rootroot00000000000000from __future__ import annotations import operator import pytest from pint import UnitRegistry # Conditionally import NumPy and any upcast type libraries np = pytest.importorskip("numpy", reason="NumPy is not available") sparse = pytest.importorskip("sparse", reason="sparse is not available") da = pytest.importorskip("dask.array", reason="Dask is not available") def WR(func): """Function to wrap another containing 1 argument. Used to parametrize tests in which some cases depend on the registry while avoiding to create it at the module level """ return lambda ureg, x: func(x) def WR2(func): """Function to wrap another containing 2 argument. Used to parametrize tests in which some cases depend on the registry while avoiding to create it at the module level """ return lambda ureg, x, y: func(x, y) @pytest.fixture(scope="module") def local_registry(): # Set up unit registry and sample return UnitRegistry(force_ndarray_like=True) @pytest.fixture(scope="module") def q_base(local_registry): # Set up unit registry and sample return (np.arange(25).reshape(5, 5).T + 1) * local_registry.kg # Define identity function for use in tests def id_matrix(ureg, x): return x @pytest.fixture(params=["sparse", "masked_array", "dask_array"]) def array(request): """Generate 5x5 arrays of given type for tests.""" if request.param == "sparse": # Create sample sparse COO as a permutation matrix. coords = [[0, 1, 2, 3, 4], [1, 3, 0, 2, 4]] data = [1.0] * 5 return sparse.COO(coords, data, shape=(5, 5)) elif request.param == "masked_array": # Create sample masked array as an upper triangular matrix. return np.ma.masked_array( np.arange(25, dtype=float).reshape((5, 5)), mask=np.logical_not(np.triu(np.ones((5, 5)))), ) elif request.param == "dask_array": return da.arange(25, chunks=5, dtype=float).reshape((5, 5)) @pytest.mark.parametrize( "op, magnitude_op, unit_op", [ pytest.param(id_matrix, id_matrix, id_matrix, id="identity"), pytest.param( lambda ureg, x: x + 1 * ureg.m, lambda ureg, x: x + 1, id_matrix, id="addition", ), pytest.param( lambda ureg, x: x - 20 * ureg.cm, lambda ureg, x: x - 0.2, id_matrix, id="subtraction", ), pytest.param( lambda ureg, x: x * (2 * ureg.s), lambda ureg, x: 2 * x, lambda ureg, u: u * ureg.s, id="multiplication", ), pytest.param( lambda ureg, x: x / (1 * ureg.s), id_matrix, lambda ureg, u: u / ureg.s, id="division", ), pytest.param( WR(lambda x: x**2), WR(lambda x: x**2), WR(lambda u: u**2), id="square", ), pytest.param(WR(lambda x: x.T), WR(lambda x: x.T), id_matrix, id="transpose"), pytest.param(WR(np.mean), WR(np.mean), id_matrix, id="mean ufunc"), pytest.param(WR(np.sum), WR(np.sum), id_matrix, id="sum ufunc"), pytest.param(WR(np.sqrt), WR(np.sqrt), WR(lambda u: u**0.5), id="sqrt ufunc"), pytest.param( WR(lambda x: np.reshape(x, (25,))), WR(lambda x: np.reshape(x, (25,))), id_matrix, id="reshape function", ), pytest.param(WR(np.amax), WR(np.amax), id_matrix, id="amax function"), ], ) def test_univariate_op_consistency( local_registry, q_base, op, magnitude_op, unit_op, array ): q = local_registry.Quantity(array, "meter") res = op(local_registry, q) assert np.all( res.magnitude == magnitude_op(local_registry, array) ) # Magnitude check assert res.units == unit_op(local_registry, q.units) # Unit check assert q.magnitude is array # Immutability check @pytest.mark.parametrize( "op, unit", [ pytest.param(operator.mul, lambda ureg: ureg("kg m"), id="multiplication"), pytest.param(operator.truediv, lambda ureg: ureg("m / kg"), id="division"), pytest.param(np.multiply, lambda ureg: ureg("kg m"), id="multiply ufunc"), ], ) def test_bivariate_op_consistency(local_registry, q_base, op, unit, array): # This is to avoid having a ureg built at the module level. unit = unit(local_registry) q = local_registry.Quantity(array, "meter") res = op(q, q_base) assert np.all(res.magnitude == op(array, q_base.magnitude)) # Magnitude check assert res.units == unit # Unit check assert q.magnitude is array # Immutability check @pytest.mark.parametrize( "op", [ pytest.param( WR2(operator.mul), id="array-first", marks=pytest.mark.xfail(reason="upstream issue numpy/numpy#15200"), ), pytest.param( WR2(operator.mul), id="unit-first", marks=pytest.mark.xfail(reason="upstream issue numpy/numpy#15200"), ), ], ) @pytest.mark.parametrize( "unit", [ pytest.param(lambda ureg: ureg.m, id="Unit"), pytest.param(lambda ureg: ureg("meter"), id="Quantity"), ], ) def test_array_quantity_creation_by_multiplication( local_registry, q_base, op, unit, array ): # This is to avoid having a ureg built at the module level. unit = unit(local_registry) assert type(op(local_registry, array, unit)) == local_registry.Quantity pint-0.24.4/pint/testsuite/test_compat_upcast.py000066400000000000000000000103541471316474000220430ustar00rootroot00000000000000from __future__ import annotations import operator import pytest # Conditionally import NumPy and any upcast type libraries np = pytest.importorskip("numpy", reason="NumPy is not available") xr = pytest.importorskip("xarray", reason="xarray is not available") @pytest.fixture(scope="module") def q_base(module_registry): # Set up unit registry and sample return [[1.0, 2.0], [3.0, 4.0]] * module_registry.m @pytest.fixture def da(q_base): return xr.DataArray(q_base.copy()) @pytest.fixture def ds(): return xr.Dataset( { "a": (("x", "y"), [[0, 1], [2, 3], [4, 5]], {"units": "K"}), "b": ("x", [0, 2, 4], {"units": "degC"}), "c": ("y", [-1, 1], {"units": "hPa"}), }, coords={ "x": ("x", [-1, 0, 1], {"units": "degree"}), "y": ("y", [0, 1], {"units": "degree"}), }, ) def test_xarray_quantity_creation(module_registry, q_base): with pytest.raises(TypeError) as exc: module_registry.Quantity(xr.DataArray(np.arange(4)), "m") assert "Quantity cannot wrap upcast type" in str(exc) assert xr.DataArray(q_base).data is q_base def test_quantification(module_registry, ds): da = ds["a"] da.data = module_registry.Quantity(da.values, da.attrs.pop("units")) mean = da.mean().item() assert mean.units == module_registry.K assert np.isclose(mean, 2.5 * module_registry.K) @pytest.mark.parametrize( "op", [ operator.add, lambda x, y: x - (-y), operator.mul, lambda x, y: x / (y**-1), ], ) @pytest.mark.parametrize( "pair", [ (lambda ureg, q: q, lambda ureg, q: xr.DataArray(q)), ( lambda ureg, q: xr.DataArray([1.0, 2.0] * ureg.m, dims=("y",)), lambda ureg, q: xr.DataArray( np.arange(6, dtype="float").reshape(3, 2, 1), dims=("z", "y", "x") ) * ureg.km, ), (lambda ureg, q: 1 * ureg.m, lambda ureg, q: xr.DataArray(q)), ], ) def test_binary_arithmetic_commutativity(module_registry, q_base, op, pair): pair = tuple(p(module_registry, q_base) for p in pair) z0 = op(*pair) z1 = op(*pair[::-1]) z1 = z1.transpose(*z0.dims) assert np.all(np.isclose(z0.data, z1.data.to(z0.data.units))) def test_eq_commutativity(da, q_base): assert np.all((q_base.T == da) == (da.transpose() == q_base)) def test_ne_commutativity(da, q_base): assert np.all((q_base != da.transpose()) == (da != q_base.T)) def test_dataset_operation_with_unit(ds, module_registry): ds0 = module_registry.K * ds.isel(x=0) ds1 = (ds * module_registry.K).isel(x=0) xr.testing.assert_identical(ds0, ds1) assert np.isclose(ds0["a"].mean().item(), 0.5 * module_registry.K) def test_dataarray_inplace_arithmetic_roundtrip(da, module_registry, q_base): da_original = da.copy() q_to_modify = q_base.copy() da += q_base xr.testing.assert_identical(da, xr.DataArray([[2, 4], [6, 8]] * module_registry.m)) da -= q_base xr.testing.assert_identical(da, da_original) da *= module_registry.m xr.testing.assert_identical(da, xr.DataArray(q_base * module_registry.m)) da /= module_registry.m xr.testing.assert_identical(da, da_original) # Operating inplace with DataArray converts to DataArray q_to_modify += da q_to_modify -= da assert np.all(np.isclose(q_to_modify.data, q_base)) def test_dataarray_inequalities(da, module_registry): xr.testing.assert_identical( 2 * module_registry.m > da, xr.DataArray([[True, False], [False, False]]) ) xr.testing.assert_identical( 2 * module_registry.m < da, xr.DataArray([[False, False], [True, True]]) ) with pytest.raises(ValueError) as exc: da > 2 assert "Cannot compare Quantity and " in str(exc) def test_array_function_deferral(da, module_registry): lower = 2 * module_registry.m upper = 3 * module_registry.m args = (da, lower, upper) assert ( lower.__array_function__(np.clip, tuple({type(arg) for arg in args}), args, {}) is NotImplemented ) def test_array_ufunc_deferral(da, module_registry): lower = 2 * module_registry.m assert lower.__array_ufunc__(np.maximum, "__call__", lower, da) is NotImplemented pint-0.24.4/pint/testsuite/test_contexts.py000066400000000000000000000771741471316474000210650ustar00rootroot00000000000000from __future__ import annotations import itertools import logging import math import re from collections import defaultdict import pytest from pint import ( Context, DefinitionSyntaxError, DimensionalityError, UndefinedUnitError, UnitRegistry, ) from pint.testsuite import helpers from pint.util import UnitsContainer from .helpers import internal def add_ctxs(ureg: UnitRegistry): a, b = UnitsContainer({"[length]": 1}), UnitsContainer({"[time]": -1}) d = Context("lc") d.add_transformation(a, b, lambda ureg, x: ureg.speed_of_light / x) d.add_transformation(b, a, lambda ureg, x: ureg.speed_of_light / x) ureg.add_context(d) a, b = UnitsContainer({"[length]": 1}), UnitsContainer({"[current]": 1}) d = Context("ab") d.add_transformation(a, b, lambda ureg, x: ureg.ampere * ureg.meter / x) d.add_transformation(b, a, lambda ureg, x: ureg.ampere * ureg.meter / x) ureg.add_context(d) def add_arg_ctxs(ureg: UnitRegistry): a, b = UnitsContainer({"[length]": 1}), UnitsContainer({"[time]": -1}) d = Context("lc") d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n) d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n) ureg.add_context(d) a, b = UnitsContainer({"[length]": 1}), UnitsContainer({"[current]": 1}) d = Context("ab") d.add_transformation(a, b, lambda ureg, x: ureg.ampere * ureg.meter / x) d.add_transformation(b, a, lambda ureg, x: ureg.ampere * ureg.meter / x) ureg.add_context(d) def add_argdef_ctxs(ureg: UnitRegistry): a, b = UnitsContainer({"[length]": 1}), UnitsContainer({"[time]": -1}) d = Context("lc", defaults=dict(n=1)) assert d.defaults == dict(n=1) d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n) d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n) ureg.add_context(d) a, b = UnitsContainer({"[length]": 1}), UnitsContainer({"[current]": 1}) d = Context("ab") d.add_transformation(a, b, lambda ureg, x: ureg.ampere * ureg.meter / x) d.add_transformation(b, a, lambda ureg, x: ureg.ampere * ureg.meter / x) ureg.add_context(d) def add_sharedargdef_ctxs(ureg: UnitRegistry): a, b = UnitsContainer({"[length]": 1}), UnitsContainer({"[time]": -1}) d = Context("lc", defaults=dict(n=1)) assert d.defaults == dict(n=1) d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n) d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n) ureg.add_context(d) a, b = UnitsContainer({"[length]": 1}), UnitsContainer({"[current]": 1}) d = Context("ab", defaults=dict(n=0)) d.add_transformation(a, b, lambda ureg, x, n: ureg.ampere * ureg.meter * n / x) d.add_transformation(b, a, lambda ureg, x, n: ureg.ampere * ureg.meter * n / x) ureg.add_context(d) class TestContexts: def test_known_context(self, func_registry): ureg = func_registry add_ctxs(ureg) with ureg.context("lc"): assert internal(ureg)._active_ctx assert internal(ureg)._active_ctx.graph assert not internal(ureg)._active_ctx assert not internal(ureg)._active_ctx.graph with ureg.context("lc", n=1): assert internal(ureg)._active_ctx assert internal(ureg)._active_ctx.graph assert not internal(ureg)._active_ctx assert not internal(ureg)._active_ctx.graph def test_known_context_enable(self, func_registry): ureg = func_registry add_ctxs(ureg) ureg.enable_contexts("lc") assert internal(ureg)._active_ctx assert internal(ureg)._active_ctx.graph ureg.disable_contexts(1) assert not internal(ureg)._active_ctx assert not internal(ureg)._active_ctx.graph ureg.enable_contexts("lc", n=1) assert internal(ureg)._active_ctx assert internal(ureg)._active_ctx.graph ureg.disable_contexts(1) assert not internal(ureg)._active_ctx assert not internal(ureg)._active_ctx.graph def test_graph(self, func_registry): ureg = func_registry add_ctxs(ureg) l = UnitsContainer({"[length]": 1.0}) # noqa: E741 t = UnitsContainer({"[time]": -1.0}) c = UnitsContainer({"[current]": 1.0}) g_sp = defaultdict(set) g_sp.update({l: {t}, t: {l}}) g_ab = defaultdict(set) g_ab.update({l: {c}, c: {l}}) g = defaultdict(set) g.update({l: {t, c}, t: {l}, c: {l}}) with ureg.context("lc"): assert internal(ureg)._active_ctx.graph == g_sp with ureg.context("lc", n=1): assert internal(ureg)._active_ctx.graph == g_sp with ureg.context("ab"): assert internal(ureg)._active_ctx.graph == g_ab with ureg.context("lc"): with ureg.context("ab"): assert internal(ureg)._active_ctx.graph == g with ureg.context("ab"): with ureg.context("lc"): assert internal(ureg)._active_ctx.graph == g with ureg.context("lc", "ab"): assert internal(ureg)._active_ctx.graph == g with ureg.context("ab", "lc"): assert internal(ureg)._active_ctx.graph == g def test_graph_enable(self, func_registry): ureg = func_registry add_ctxs(ureg) l = UnitsContainer({"[length]": 1.0}) # noqa: E741 t = UnitsContainer({"[time]": -1.0}) c = UnitsContainer({"[current]": 1.0}) g_sp = defaultdict(set) g_sp.update({l: {t}, t: {l}}) g_ab = defaultdict(set) g_ab.update({l: {c}, c: {l}}) g = defaultdict(set) g.update({l: {t, c}, t: {l}, c: {l}}) ureg.enable_contexts("lc") assert internal(ureg)._active_ctx.graph == g_sp ureg.disable_contexts(1) ureg.enable_contexts("lc", n=1) assert internal(ureg)._active_ctx.graph == g_sp ureg.disable_contexts(1) ureg.enable_contexts("ab") assert internal(ureg)._active_ctx.graph == g_ab ureg.disable_contexts(1) ureg.enable_contexts("lc") ureg.enable_contexts("ab") assert internal(ureg)._active_ctx.graph == g ureg.disable_contexts(2) ureg.enable_contexts("ab") ureg.enable_contexts("lc") assert internal(ureg)._active_ctx.graph == g ureg.disable_contexts(2) ureg.enable_contexts("lc", "ab") assert internal(ureg)._active_ctx.graph == g ureg.disable_contexts(2) ureg.enable_contexts("ab", "lc") assert internal(ureg)._active_ctx.graph == g ureg.disable_contexts(2) def test_known_nested_context(self, func_registry): ureg = func_registry add_ctxs(ureg) with ureg.context("lc"): x = dict(internal(ureg)._active_ctx) y = dict(internal(ureg)._active_ctx.graph) assert internal(ureg)._active_ctx assert internal(ureg)._active_ctx.graph with ureg.context("ab"): assert internal(ureg)._active_ctx assert internal(ureg)._active_ctx.graph assert x != internal(ureg)._active_ctx assert y != internal(ureg)._active_ctx.graph assert x == internal(ureg)._active_ctx assert y == internal(ureg)._active_ctx.graph assert not internal(ureg)._active_ctx assert not internal(ureg)._active_ctx.graph def test_unknown_context(self, func_registry): ureg = func_registry add_ctxs(ureg) with pytest.raises(KeyError): with ureg.context("la"): pass assert not internal(ureg)._active_ctx assert not internal(ureg)._active_ctx.graph def test_unknown_nested_context(self, func_registry): ureg = func_registry add_ctxs(ureg) with ureg.context("lc"): x = dict(internal(ureg)._active_ctx) y = dict(internal(ureg)._active_ctx.graph) with pytest.raises(KeyError): with ureg.context("la"): pass assert x == internal(ureg)._active_ctx assert y == internal(ureg)._active_ctx.graph assert not internal(ureg)._active_ctx assert not internal(ureg)._active_ctx.graph def test_one_context(self, func_registry): ureg = func_registry add_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") meter_units = ureg.get_compatible_units(ureg.meter) hertz_units = ureg.get_compatible_units(ureg.hertz) with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc"): assert q.to("Hz") == s assert ureg.get_compatible_units(q) == meter_units | hertz_units with pytest.raises(DimensionalityError): q.to("Hz") assert ureg.get_compatible_units(q) == meter_units def test_multiple_context(self, func_registry): ureg = func_registry add_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") meter_units = ureg.get_compatible_units(ureg.meter) hertz_units = ureg.get_compatible_units(ureg.hertz) ampere_units = ureg.get_compatible_units(ureg.ampere) with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc", "ab"): assert q.to("Hz") == s assert ( ureg.get_compatible_units(q) == meter_units | hertz_units | ampere_units ) with pytest.raises(DimensionalityError): q.to("Hz") assert ureg.get_compatible_units(q) == meter_units def test_nested_context(self, func_registry): ureg = func_registry add_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc"): assert q.to("Hz") == s with ureg.context("ab"): assert q.to("Hz") == s assert q.to("Hz") == s with ureg.context("ab"): with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc"): assert q.to("Hz") == s with pytest.raises(DimensionalityError): q.to("Hz") def test_context_with_arg(self, func_registry): ureg = func_registry add_arg_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc", n=1): assert q.to("Hz") == s with ureg.context("ab"): assert q.to("Hz") == s assert q.to("Hz") == s with ureg.context("ab"): with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc", n=1): assert q.to("Hz") == s with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc"): with pytest.raises(TypeError): q.to("Hz") def test_enable_context_with_arg(self, func_registry): ureg = func_registry add_arg_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") with pytest.raises(DimensionalityError): q.to("Hz") ureg.enable_contexts("lc", n=1) assert q.to("Hz") == s ureg.enable_contexts("ab") assert q.to("Hz") == s assert q.to("Hz") == s ureg.disable_contexts(1) ureg.disable_contexts(1) ureg.enable_contexts("ab") with pytest.raises(DimensionalityError): q.to("Hz") ureg.enable_contexts("lc", n=1) assert q.to("Hz") == s ureg.disable_contexts(1) with pytest.raises(DimensionalityError): q.to("Hz") ureg.disable_contexts(1) ureg.enable_contexts("lc") with pytest.raises(TypeError): q.to("Hz") ureg.disable_contexts(1) def test_context_with_arg_def(self, func_registry): ureg = func_registry add_argdef_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc"): assert q.to("Hz") == s with ureg.context("ab"): assert q.to("Hz") == s assert q.to("Hz") == s with ureg.context("ab"): with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc"): assert q.to("Hz") == s with pytest.raises(DimensionalityError): q.to("Hz") with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc", n=2): assert q.to("Hz") == s / 2 with ureg.context("ab"): assert q.to("Hz") == s / 2 assert q.to("Hz") == s / 2 with ureg.context("ab"): with pytest.raises(DimensionalityError): q.to("Hz") with ureg.context("lc", n=2): assert q.to("Hz") == s / 2 with pytest.raises(DimensionalityError): q.to("Hz") def test_context_with_sharedarg_def(self, func_registry): ureg = func_registry add_sharedargdef_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") u = (1 / 500) * ureg.ampere with ureg.context("lc"): assert q.to("Hz") == s with ureg.context("ab"): assert q.to("ampere") == u with ureg.context("ab"): assert q.to("ampere") == 0 * u with ureg.context("lc"): with pytest.raises(ZeroDivisionError): ureg.Quantity.to(q, "Hz") with ureg.context("lc", n=2): assert q.to("Hz") == s / 2 with ureg.context("ab"): assert q.to("ampere") == 2 * u with ureg.context("ab", n=3): assert q.to("ampere") == 3 * u with ureg.context("lc"): assert q.to("Hz") == s / 3 with ureg.context("lc", n=2): assert q.to("Hz") == s / 2 with ureg.context("ab", n=4): assert q.to("ampere") == 4 * u with ureg.context("ab", n=3): assert q.to("ampere") == 3 * u with ureg.context("lc", n=6): assert q.to("Hz") == s / 6 def test_anonymous_context(self, func_registry): ureg = func_registry c = Context() c.add_transformation("[length]", "[time]", lambda ureg, x: x / ureg("5 cm/s")) with pytest.raises(ValueError): ureg.add_context(c) x = ureg("10 cm") expect = ureg("2 s") helpers.assert_quantity_equal(x.to("s", c), expect) with ureg.context(c): helpers.assert_quantity_equal(x.to("s"), expect) ureg.enable_contexts(c) helpers.assert_quantity_equal(x.to("s"), expect) ureg.disable_contexts(1) with pytest.raises(DimensionalityError): x.to("s") # Multiple anonymous contexts c2 = Context() c2.add_transformation("[length]", "[time]", lambda ureg, x: x / ureg("10 cm/s")) c2.add_transformation("[mass]", "[time]", lambda ureg, x: x / ureg("10 kg/s")) with ureg.context(c2, c): helpers.assert_quantity_equal(x.to("s"), expect) # Transformations only in c2 are still working even if c takes priority helpers.assert_quantity_equal(ureg("100 kg").to("s"), ureg("10 s")) with ureg.context(c, c2): helpers.assert_quantity_equal(x.to("s"), ureg("1 s")) def _test_ctx(self, ctx, ureg): q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") nctx = len(internal(ureg)._contexts) assert ctx.name not in internal(ureg)._contexts ureg.add_context(ctx) assert ctx.name in internal(ureg)._contexts assert len(internal(ureg)._contexts) == nctx + 1 + len(ctx.aliases) with ureg.context(ctx.name): assert q.to("Hz") == s assert s.to("meter") == q ureg.remove_context(ctx.name) assert ctx.name not in internal(ureg)._contexts assert len(internal(ureg)._contexts) == nctx @pytest.mark.parametrize( "badrow", ( "[length] = 1 / [time]: c / value", "1 / [time] = [length]: c / value", "[length] <- [time] = c / value", "[length] - [time] = c / value", ), ) def test_parse_invalid(self, badrow): with pytest.raises(DefinitionSyntaxError): Context.from_lines(["@context c", badrow]) @pytest.mark.parametrize( "source, name, aliases, defaults", [ ( [ "@context longcontextname", "[length] -> 1 / [time]: c / value", "1 / [time] -> [length]: c / value", ], "longcontextname", (), {}, ), ( ["@context longcontextname = lc", "[length] <-> 1 / [time]: c / value"], "longcontextname", ("lc",), {}, ), ( [ "@context longcontextname = lc = lcn", "[length] <-> 1 / [time]: c / value", ], "longcontextname", ("lc", "lcn"), {}, ), ], ) def test_parse_simple(self, func_registry, source, name, aliases, defaults): a = Context.__keytransform__( UnitsContainer({"[time]": -1}), UnitsContainer({"[length]": 1}) ) b = Context.__keytransform__( UnitsContainer({"[length]": 1}), UnitsContainer({"[time]": -1}) ) c = Context.from_lines(source) assert c.name == name assert c.aliases == aliases assert c.defaults == defaults assert c.funcs.keys() == {a, b} self._test_ctx(c, func_registry) def test_parse_auto_inverse(self, func_registry): a = Context.__keytransform__( UnitsContainer({"[time]": -1.0}), UnitsContainer({"[length]": 1.0}) ) b = Context.__keytransform__( UnitsContainer({"[length]": 1.0}), UnitsContainer({"[time]": -1.0}) ) s = ["@context longcontextname", "[length] <-> 1 / [time]: c / value"] c = Context.from_lines(s) assert c.defaults == {} assert c.funcs.keys() == {a, b} self._test_ctx(c, func_registry) def test_parse_define(self, func_registry): a = Context.__keytransform__( UnitsContainer({"[time]": -1}), UnitsContainer({"[length]": 1.0}) ) b = Context.__keytransform__( UnitsContainer({"[length]": 1}), UnitsContainer({"[time]": -1.0}) ) s = ["@context longcontextname", "[length] <-> 1 / [time]: c / value"] c = Context.from_lines(s) assert c.defaults == {} assert c.funcs.keys() == {a, b} self._test_ctx(c, func_registry) def test_parse_parameterized(self, func_registry): a = Context.__keytransform__( UnitsContainer({"[time]": -1.0}), UnitsContainer({"[length]": 1.0}) ) b = Context.__keytransform__( UnitsContainer({"[length]": 1.0}), UnitsContainer({"[time]": -1.0}) ) s = ["@context(n=1) longcontextname", "[length] <-> 1 / [time]: n * c / value"] c = Context.from_lines(s) assert c.defaults == {"n": 1} assert c.funcs.keys() == {a, b} self._test_ctx(c, func_registry) s = [ "@context(n=1, bla=2) longcontextname", "[length] <-> 1 / [time]: n * c / value / bla", ] c = Context.from_lines(s) assert c.defaults == {"n": 1, "bla": 2} assert c.funcs.keys() == {a, b} # If the variable is not present in the definition, then raise an error s = ["@context(n=1) longcontextname", "[length] <-> 1 / [time]: c / value"] with pytest.raises(DefinitionSyntaxError): Context.from_lines(s) def test_warnings(self, caplog, func_registry): ureg = func_registry with caplog.at_level(logging.DEBUG, "pint"): add_ctxs(ureg) d = Context("ab") ureg.add_context(d) assert len(caplog.records) == 1 assert "ab" in str(caplog.records[-1].args) d = Context("ab1", aliases=("ab",)) ureg.add_context(d) assert len(caplog.records) == 2 assert "ab" in str(caplog.records[-1].args) class TestDefinedContexts: def test_defined(self, class_registry): ureg = class_registry with ureg.context("sp"): pass a = Context.__keytransform__( UnitsContainer({"[time]": -1.0}), UnitsContainer({"[length]": 1.0}) ) b = Context.__keytransform__( UnitsContainer({"[length]": 1.0}), UnitsContainer({"[time]": -1.0}) ) assert a in internal(ureg)._contexts["sp"].funcs assert b in internal(ureg)._contexts["sp"].funcs with ureg.context("sp"): assert a in internal(ureg)._active_ctx assert b in internal(ureg)._active_ctx def test_spectroscopy(self, class_registry): ureg = class_registry eq = (532.0 * ureg.nm, 563.5 * ureg.terahertz, 2.33053 * ureg.eV) with ureg.context("sp"): from pint.util import find_shortest_path for a, b in itertools.product(eq, eq): for x in range(2): if x == 1: a = a.to_base_units() b = b.to_base_units() da, db = Context.__keytransform__( a.dimensionality, b.dimensionality ) p = find_shortest_path(internal(ureg)._active_ctx.graph, da, db) assert p msg = f"{a} <-> {b}" # assertAlmostEqualRelError converts second to first helpers.assert_quantity_almost_equal(b, a, rtol=0.01, msg=msg) for a, b in itertools.product(eq, eq): helpers.assert_quantity_almost_equal(a.to(b.units, "sp"), b, rtol=0.01) def test_textile(self, class_registry): ureg = class_registry qty_direct = 1.331 * ureg.tex with pytest.raises(DimensionalityError): qty_indirect = qty_direct.to("Nm") with ureg.context("textile"): from pint.util import find_shortest_path qty_indirect = qty_direct.to("Nm") a = qty_direct.to_base_units() b = qty_indirect.to_base_units() da, db = Context.__keytransform__(a.dimensionality, b.dimensionality) p = find_shortest_path(internal(ureg)._active_ctx.graph, da, db) assert p msg = f"{a} <-> {b}" helpers.assert_quantity_almost_equal(b, a, rtol=0.01, msg=msg) # Check RKM <-> cN/tex conversion helpers.assert_quantity_almost_equal( 1 * ureg.RKM, 0.980665 * ureg.cN / ureg.tex ) helpers.assert_quantity_almost_equal( (1 / 0.980665) * ureg.RKM, 1 * ureg.cN / ureg.tex ) assert ( round(abs((1 * ureg.RKM).to(ureg.cN / ureg.tex).m - 0.980665), 7) == 0 ) assert ( round(abs((1 * ureg.cN / ureg.tex).to(ureg.RKM).m - 1 / 0.980665), 7) == 0 ) def test_decorator(self, class_registry): ureg = class_registry a = 532.0 * ureg.nm with ureg.context("sp"): b = a.to("terahertz") def f(wl): return wl.to("terahertz") with pytest.raises(DimensionalityError): f(a) @ureg.with_context("sp") def g(wl): return wl.to("terahertz") assert b == g(a) def test_decorator_composition(self, class_registry): ureg = class_registry a = 532.0 * ureg.nm with ureg.context("sp"): b = a.to("terahertz") @ureg.with_context("sp") @ureg.check("[length]") def f(wl): return wl.to("terahertz") @ureg.with_context("sp") @ureg.check("[length]") def g(wl): return wl.to("terahertz") assert b == f(a) assert b == g(a) def test_redefine(subtests): ureg = UnitRegistry( """ foo = [d] = f = foo_alias bar = 2 foo = b = bar_alias baz = 3 bar = _ = baz_alias asd = 4 baz @context c # Note how we're redefining a symbol, not the plain name, as a # function of another name b = 5 f @end """.splitlines() ) # Units that are somehow directly or indirectly defined as a function of the # overridden unit are also affected foo = ureg.Quantity(1, "foo") bar = ureg.Quantity(1, "bar") asd = ureg.Quantity(1, "asd") # Test without context before and after, to verify that the cache and units have # not been polluted for enable_ctx in (False, True, False): with subtests.test(enable_ctx): if enable_ctx: ureg.enable_contexts("c") k = 5 else: k = 2 assert foo.to("b").magnitude == 1 / k assert foo.to("bar").magnitude == 1 / k assert foo.to("bar_alias").magnitude == 1 / k assert foo.to("baz").magnitude == 1 / k / 3 assert bar.to("foo").magnitude == k assert bar.to("baz").magnitude == 1 / 3 assert asd.to("foo").magnitude == 4 * 3 * k assert asd.to("bar").magnitude == 4 * 3 assert asd.to("baz").magnitude == 4 ureg.disable_contexts() def test_define_nan(): ureg = UnitRegistry( """ USD = [currency] EUR = nan USD GBP = nan USD @context c EUR = 1.11 USD # Note that we're changing which unit GBP is defined against GBP = 1.18 EUR @end """.splitlines() ) q = ureg.Quantity("10 GBP") assert q.magnitude == 10 assert q.units.dimensionality == {"[currency]": 1} assert q.to("GBP").magnitude == 10 assert math.isnan(q.to("USD").magnitude) assert math.isclose(q.to("USD", "c").magnitude, 10 * 1.18 * 1.11) def test_non_multiplicative(subtests): ureg = UnitRegistry( """ kelvin = [temperature] fahrenheit = 5 / 9 * kelvin; offset: 255 bogodegrees = 9 * kelvin @context nonmult_to_nonmult fahrenheit = 7 * kelvin; offset: 123 @end @context nonmult_to_mult fahrenheit = 123 * kelvin @end @context mult_to_nonmult bogodegrees = 5 * kelvin; offset: 123 @end """.splitlines() ) k = ureg.Quantity(100, "kelvin") with subtests.test("baseline"): helpers.assert_quantity_almost_equal( k.to("fahrenheit").magnitude, (100 - 255) * 9 / 5 ) helpers.assert_quantity_almost_equal(k.to("bogodegrees").magnitude, 100 / 9) with subtests.test("nonmult_to_nonmult"): with ureg.context("nonmult_to_nonmult"): helpers.assert_quantity_almost_equal( k.to("fahrenheit").magnitude, (100 - 123) / 7 ) with subtests.test("nonmult_to_mult"): with ureg.context("nonmult_to_mult"): helpers.assert_quantity_almost_equal( k.to("fahrenheit").magnitude, 100 / 123 ) with subtests.test("mult_to_nonmult"): with ureg.context("mult_to_nonmult"): helpers.assert_quantity_almost_equal( k.to("bogodegrees").magnitude, (100 - 123) / 5 ) def test_stack_contexts(): ureg = UnitRegistry( """ a = [dim1] b = 1/2 a c = 1/3 a d = [dim2] @context c1 b = 1/4 a c = 1/6 a [dim1]->[dim2]: value * 2 d/a @end @context c2 b = 1/5 a [dim1]->[dim2]: value * 3 d/a @end """.splitlines() ) q = ureg.Quantity(1, "a") assert q.to("b").magnitude == 2 assert q.to("c").magnitude == 3 assert q.to("b", "c1").magnitude == 4 assert q.to("c", "c1").magnitude == 6 assert q.to("d", "c1").magnitude == 2 assert q.to("b", "c2").magnitude == 5 assert q.to("c", "c2").magnitude == 3 assert q.to("d", "c2").magnitude == 3 assert q.to("b", "c1", "c2").magnitude == 5 # c2 takes precedence assert q.to("c", "c1", "c2").magnitude == 6 # c2 doesn't change it, so use c1 assert q.to("d", "c1", "c2").magnitude == 3 # c2 takes precedence def test_err_change_base_unit(): ureg = UnitRegistry( """ foo = [d1] bar = [d2] @context c bar = foo @end """.splitlines() ) expected = "Can't redefine a plain unit to a derived one" with pytest.raises(ValueError, match=expected): ureg.enable_contexts("c") def test_err_to_base_unit(): expected = ".*can't define plain units within a context" with pytest.raises(DefinitionSyntaxError, match=expected): Context.from_lines(["@context c", "x = [d]"]) def test_err_change_dimensionality(): ureg = UnitRegistry( """ foo = [d1] bar = [d2] baz = foo @context c baz = bar @end """.splitlines() ) expected = re.escape( "Can't change dimensionality of baz from [d1] to [d2] in a context" ) with pytest.raises(ValueError, match=expected): ureg.enable_contexts("c") def test_err_cyclic_dependency(): ureg = UnitRegistry( """ foo = [d] bar = foo baz = bar @context c bar = baz @end """.splitlines() ) # TODO align this exception and the one you get when you implement a cyclic # dependency within the plain registry. Ideally this exception should be # raised by enable_contexts. ureg.enable_contexts("c") q = ureg.Quantity("bar") with pytest.raises(RecursionError): q.to("foo") def test_err_dimension_redefinition(): with pytest.raises(DefinitionSyntaxError): Context.from_lines(["@context c", "[d1] = [d2] * [d3]"]) def test_err_prefix_redefinition(): with pytest.raises(DefinitionSyntaxError): Context.from_lines(["@context c", "[d1] = [d2] * [d3]"]) def test_err_redefine_alias(subtests): expected = ".*can't change a unit's symbol or aliases within a context" for s in ("foo = bar = f", "foo = bar = _ = baz"): with subtests.test(s): with pytest.raises(DefinitionSyntaxError, match=expected): Context.from_lines(["@context c", s]) def test_err_redefine_with_prefix(): ureg = UnitRegistry( """ kilo- = 1000 gram = [mass] pound = 454 gram @context c kilopound = 500000 gram @end """.splitlines() ) expected = "Can't redefine a unit with a prefix: kilopound" with pytest.raises(ValueError, match=expected): ureg.enable_contexts("c") def test_err_new_unit(): ureg = UnitRegistry( """ foo = [d] @context c bar = foo @end """.splitlines() ) expected = "'bar' is not defined in the unit registry" with pytest.raises(UndefinedUnitError, match=expected): ureg.enable_contexts("c") pint-0.24.4/pint/testsuite/test_converters.py000066400000000000000000000071371471316474000214000ustar00rootroot00000000000000from __future__ import annotations import itertools from pint.compat import np from pint.converters import Converter from pint.facets.nonmultiplicative.definitions import ( LogarithmicConverter, OffsetConverter, ) from pint.facets.plain import ScaleConverter from pint.testsuite import helpers class TestConverter: def test_converter(self): c = Converter() assert c.is_multiplicative assert not c.is_logarithmic assert c.to_reference(8) assert c.from_reference(8) def test_multiplicative_converter(self): c = ScaleConverter(20.0) assert c.is_multiplicative assert not c.is_logarithmic assert c.from_reference(c.to_reference(100)) == 100 assert c.to_reference(c.from_reference(100)) == 100 def test_offset_converter(self): c = OffsetConverter(20.0, 2) assert not c.is_multiplicative assert not c.is_logarithmic assert c.from_reference(c.to_reference(100)) == 100 assert c.to_reference(c.from_reference(100)) == 100 def test_log_converter(self): c = LogarithmicConverter(scale=1, logbase=10, logfactor=1) assert not c.is_multiplicative assert c.is_logarithmic assert round(abs(c.to_reference(0) - 1), 7) == 0 assert round(abs(c.to_reference(1) - 10), 7) == 0 assert round(abs(c.to_reference(2) - 100), 7) == 0 assert round(abs(c.from_reference(1) - 0), 7) == 0 assert round(abs(c.from_reference(10) - 1), 7) == 0 assert round(abs(c.from_reference(100) - 2), 7) == 0 arb_value = 20.0 assert ( round(abs(c.from_reference(c.to_reference(arb_value)) - arb_value), 7) == 0 ) assert ( round(abs(c.to_reference(c.from_reference(arb_value)) - arb_value), 7) == 0 ) @helpers.requires_numpy def test_converter_inplace(self): for c in (ScaleConverter(20.0), OffsetConverter(20.0, 2)): fun1 = lambda x, y: c.from_reference(c.to_reference(x, y), y) fun2 = lambda x, y: c.to_reference(c.from_reference(x, y), y) for fun, (inplace, comp) in itertools.product( (fun1, fun2), ((True, True), (False, False)) ): a = np.ones((1, 10)) ac = np.ones((1, 10)) r = fun(a, inplace) np.testing.assert_allclose(r, ac) if comp: assert a is r else: assert a is not r @helpers.requires_numpy def test_log_converter_inplace(self): arb_value = 3.13 c = LogarithmicConverter(scale=1, logbase=10, logfactor=1) from_to = lambda value, inplace: c.from_reference( c.to_reference(value, inplace), inplace ) to_from = lambda value, inplace: c.to_reference( c.from_reference(value, inplace), inplace ) for fun, (inplace, comp) in itertools.product( (from_to, to_from), ((True, True), (False, False)) ): arb_array = arb_value * np.ones((1, 10)) result = fun(arb_array, inplace) np.testing.assert_allclose(result, arb_array) if comp: assert arb_array is result else: assert arb_array is not result def test_from_arguments(self): assert Converter.from_arguments(scale=1) == ScaleConverter(1) assert Converter.from_arguments(scale=2, offset=3) == OffsetConverter(2, 3) assert Converter.from_arguments( scale=4, logbase=5, logfactor=6 ) == LogarithmicConverter(4, 5, 6) pint-0.24.4/pint/testsuite/test_dask.py000066400000000000000000000161151471316474000201240ustar00rootroot00000000000000from __future__ import annotations import importlib import pathlib import pytest from pint import UnitRegistry # Conditionally import NumPy, Dask, and Distributed np = pytest.importorskip("numpy", reason="NumPy is not available") dask = pytest.importorskip("dask", reason="Dask is not available") distributed = pytest.importorskip("distributed", reason="Distributed is not available") from dask.distributed import Client from distributed.client import futures_of from distributed.utils_test import ( # noqa: F401 cleanup, cluster, gen_cluster, loop, loop_in_thread, ) loop = loop # flake8 units_ = "kilogram" @pytest.fixture(scope="module") def local_registry(): # Set up unit registry and sample return UnitRegistry(force_ndarray_like=True) def add_five(local_registry, q): return q + 5 * local_registry(units_) @pytest.fixture def dask_array(): return dask.array.arange(0, 25, chunks=5, dtype=float).reshape((5, 5)) @pytest.fixture def numpy_array(): return np.arange(0, 25, dtype=float).reshape((5, 5)) + 5 def test_is_dask_collection(local_registry, dask_array): """Test that a pint.Quantity wrapped Dask array is a Dask collection.""" q = local_registry.Quantity(dask_array, units_) assert dask.is_dask_collection(q) def test_is_not_dask_collection(local_registry, numpy_array): """Test that other pint.Quantity wrapped objects are not Dask collections.""" q = local_registry.Quantity(numpy_array, units_) assert not dask.is_dask_collection(q) def test_dask_scheduler(local_registry, dask_array): """Test that a pint.Quantity wrapped Dask array has the correct default scheduler.""" q = local_registry.Quantity(dask_array, units_) scheduler = q.__dask_scheduler__ scheduler_name = f"{scheduler.__module__}.{scheduler.__name__}" true_name = "dask.threaded.get" assert scheduler == dask.array.Array.__dask_scheduler__ assert scheduler_name == true_name @pytest.mark.parametrize( "item", ( pytest.param(1, id="int"), pytest.param(1.3, id="float"), pytest.param(np.array([1, 2]), id="numpy"), pytest.param( dask.array.arange(0, 25, chunks=5, dtype=float).reshape((5, 5)), id="dask" ), ), ) def test_dask_tokenize(local_registry, item): """Test that a pint.Quantity wrapping something has a unique token.""" dask_token = dask.base.tokenize(item) q = local_registry.Quantity(item, units_) assert dask.base.tokenize(item) != dask.base.tokenize(q) assert dask.base.tokenize(item) == dask_token def test_dask_optimize(local_registry, dask_array): """Test that a pint.Quantity wrapped Dask array can be optimized.""" q = local_registry.Quantity(dask_array, units_) assert q.__dask_optimize__ == dask.array.Array.__dask_optimize__ def test_compute(local_registry, dask_array, numpy_array): """Test the compute() method on a pint.Quantity wrapped Dask array.""" q = local_registry.Quantity(dask_array, units_) comps = add_five(local_registry, q) res = comps.compute() assert np.all(res.m == numpy_array) assert not dask.is_dask_collection(res) assert res.units == units_ assert q.magnitude is dask_array def test_persist(local_registry, dask_array, numpy_array): """Test the persist() method on a pint.Quantity wrapped Dask array.""" q = local_registry.Quantity(dask_array, units_) comps = add_five(local_registry, q) res = comps.persist() assert np.all(res.m == numpy_array) assert dask.is_dask_collection(res) assert res.units == units_ assert q.magnitude is dask_array @pytest.mark.skipif( importlib.util.find_spec("graphviz") is None, reason="GraphViz is not available" ) def test_visualize(local_registry, dask_array): """Test the visualize() method on a pint.Quantity wrapped Dask array.""" q = local_registry.Quantity(dask_array, units_) comps = add_five(local_registry, q) res = comps.visualize() assert res is None # These commands only work on Unix and Windows assert pathlib.Path("mydask.png").exists() pathlib.Path("mydask.png").unlink() def test_compute_persist_equivalent(local_registry, dask_array, numpy_array): """Test that compute() and persist() return the same numeric results.""" q = local_registry.Quantity(dask_array, units_) comps = add_five(local_registry, q) res_compute = comps.compute() res_persist = comps.persist() assert np.all(res_compute == res_persist) assert res_compute.units == res_persist.units == units_ assert type(res_compute) == local_registry.Quantity assert type(res_persist) == local_registry.Quantity @pytest.mark.parametrize("method", ["compute", "persist", "visualize"]) def test_exception_method_not_implemented(local_registry, numpy_array, method): """Test exception handling for convenience methods on a pint.Quantity wrapped object that is not a dask.array.Array object. """ q = local_registry.Quantity(numpy_array, units_) exctruth = ( f"Method {method} only implemented for objects of" " , not" " " ) with pytest.raises(AttributeError, match=exctruth): obj_method = getattr(q, method) obj_method() def test_distributed_compute(local_registry, loop, dask_array, numpy_array): """Test compute() for distributed machines.""" q = local_registry.Quantity(dask_array, units_) with cluster() as (s, [a, b]): with Client(s["address"], loop=loop): comps = add_five(local_registry, q) res = comps.compute() assert np.all(res.m == numpy_array) assert not dask.is_dask_collection(res) assert res.units == units_ assert q.magnitude is dask_array def test_distributed_persist(local_registry, loop, dask_array): """Test persist() for distributed machines.""" q = local_registry.Quantity(dask_array, units_) with cluster() as (s, [a, b]): with Client(s["address"], loop=loop): comps = add_five(local_registry, q) persisted_q = comps.persist() comps_truth = dask_array + 5 persisted_truth = comps_truth.persist() assert np.all(persisted_q.m == persisted_truth) assert dask.is_dask_collection(persisted_q) assert persisted_q.units == units_ assert q.magnitude is dask_array @gen_cluster(client=True) async def test_async(c, s, a, b): """Test asynchronous operations.""" # TODO: use a fixture for this. local_registry = UnitRegistry(force_ndarray_like=True) da = dask.array.arange(0, 25, chunks=5, dtype=float).reshape((5, 5)) q = local_registry.Quantity(da, units_) x = q + local_registry.Quantity(5, units_) y = x.persist() assert str(y) assert dask.is_dask_collection(y) assert len(x.__dask_graph__()) > len(y.__dask_graph__()) assert not futures_of(x) assert futures_of(y) future = c.compute(y) w = await future assert not dask.is_dask_collection(w) truth = np.arange(0, 25, dtype=float).reshape((5, 5)) + 5 assert np.all(truth == w.m) pint-0.24.4/pint/testsuite/test_definitions.py000066400000000000000000000150631471316474000215160ustar00rootroot00000000000000from __future__ import annotations import math import pytest from pint.definitions import Definition from pint.errors import DefinitionSyntaxError from pint.facets.nonmultiplicative.definitions import ( LogarithmicConverter, OffsetConverter, ) from pint.facets.plain import ( AliasDefinition, DimensionDefinition, PrefixDefinition, ScaleConverter, UnitDefinition, ) from pint.util import UnitsContainer class TestDefinition: def test_invalid(self): with pytest.raises(DefinitionSyntaxError): Definition.from_string("x = [time] * meter") with pytest.raises(DefinitionSyntaxError): Definition.from_string("[x] = [time] * meter") def test_prefix_definition(self): with pytest.raises(ValueError): Definition.from_string("m- = 1e-3 k") for definition in ("m- = 1e-3", "m- = 10**-3", "m- = 0.001"): x = Definition.from_string(definition) assert isinstance(x, PrefixDefinition) assert x.name == "m" assert x.aliases == () assert x.converter.to_reference(1000) == 1 assert x.converter.from_reference(0.001) == 1 x = Definition.from_string("kilo- = 1e-3 = k-") assert isinstance(x, PrefixDefinition) assert x.name == "kilo" assert x.aliases == () assert x.symbol == "k" assert x.converter.to_reference(1000) == 1 assert x.converter.from_reference(0.001) == 1 x = Definition.from_string("kilo- = 1e-3 = k- = anotherk-") assert isinstance(x, PrefixDefinition) assert x.name == "kilo" assert x.aliases == ("anotherk",) assert x.symbol == "k" assert x.converter.to_reference(1000) == 1 assert x.converter.from_reference(0.001) == 1 def test_baseunit_definition(self): x = Definition.from_string("meter = [length]") assert isinstance(x, UnitDefinition) assert x.is_base assert x.reference == UnitsContainer({"[length]": 1}) def test_unit_definition(self): x = Definition.from_string("coulomb = ampere * second") assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, ScaleConverter) assert x.converter.scale == 1 assert x.reference == UnitsContainer(ampere=1, second=1) x = Definition.from_string("faraday = 96485.3399 * coulomb") assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, ScaleConverter) assert x.converter.scale == 96485.3399 assert x.reference == UnitsContainer(coulomb=1) x = Definition.from_string("degF = 9 / 5 * kelvin; offset: 255.372222") assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, OffsetConverter) assert x.converter.scale == 9 / 5 assert x.converter.offset == 255.372222 assert x.reference == UnitsContainer(kelvin=1) x = Definition.from_string( f"turn = {math.tau} * radian = _ = revolution = = cycle = _" ) assert isinstance(x, UnitDefinition) assert x.name == "turn" assert x.aliases == ("revolution", "cycle") assert x.symbol == "turn" assert not x.is_base assert isinstance(x.converter, ScaleConverter) assert x.converter.scale == math.tau assert x.reference == UnitsContainer(radian=1) with pytest.raises(ValueError): Definition.from_string( "degF = 9 / 5 * kelvin; offset: 255.372222 bla", ) def test_log_unit_definition(self): x = Definition.from_string( "decibelmilliwatt = 1e-3 watt; logbase: 10; logfactor: 10 = dBm" ) assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, LogarithmicConverter) assert x.converter.scale == 1e-3 assert x.converter.logbase == 10 assert x.converter.logfactor == 10 assert x.reference == UnitsContainer(watt=1) x = Definition.from_string("decibel = 1 ; logbase: 10; logfactor: 10 = dB") assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, LogarithmicConverter) assert x.converter.scale == 1 assert x.converter.logbase == 10 assert x.converter.logfactor == 10 assert x.reference == UnitsContainer() x = Definition.from_string("bell = 1 ; logbase: 10; logfactor: 1 = B") assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, LogarithmicConverter) assert x.converter.scale == 1 assert x.converter.logbase == 10 assert x.converter.logfactor == 1 assert x.reference == UnitsContainer() x = Definition.from_string("decade = 1 ; logbase: 10; logfactor: 1") assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, LogarithmicConverter) assert x.converter.scale == 1 assert x.converter.logbase == 10 assert x.converter.logfactor == 1 assert x.reference == UnitsContainer() eulersnumber = math.e x = Definition.from_string( "neper = 1 ; logbase: %1.50f; logfactor: 0.5 = Np" % eulersnumber ) assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, LogarithmicConverter) assert x.converter.scale == 1 assert x.converter.logbase == eulersnumber assert x.converter.logfactor == 0.5 assert x.reference == UnitsContainer() x = Definition.from_string("octave = 1 ; logbase: 2; logfactor: 1 = oct") assert isinstance(x, UnitDefinition) assert not x.is_base assert isinstance(x.converter, LogarithmicConverter) assert x.converter.scale == 1 assert x.converter.logbase == 2 assert x.converter.logfactor == 1 assert x.reference == UnitsContainer() def test_dimension_definition(self): x = DimensionDefinition("[time]") assert x.is_base assert x.name == "[time]" x = Definition.from_string("[speed] = [length]/[time]") assert isinstance(x, DimensionDefinition) assert x.reference == UnitsContainer({"[length]": 1, "[time]": -1}) def test_alias_definition(self): x = Definition.from_string("@alias meter = metro = metr") assert isinstance(x, AliasDefinition) assert x.name == "meter" assert x.aliases == ("metro", "metr") pint-0.24.4/pint/testsuite/test_diskcache.py000066400000000000000000000063521471316474000211220ustar00rootroot00000000000000from __future__ import annotations import decimal import pickle import time import flexparser as fp import pytest import pint from pint.facets.plain import UnitDefinition FS_SLEEP = 0.010 from .helpers import internal @pytest.fixture def float_cache_filename(tmp_path): ureg = pint.UnitRegistry(cache_folder=tmp_path / "cache_with_float") assert internal(ureg)._diskcache assert internal(ureg)._diskcache.cache_folder return tuple(internal(ureg)._diskcache.cache_folder.glob("*.pickle")) def test_must_be_three_files(float_cache_filename): # There should be 3 files here: # - cached defaults_en.txt # - cached constants_en.txt # - cached RegistryCache assert len(float_cache_filename) == 3, float_cache_filename def test_no_cache(): ureg = pint.UnitRegistry(cache_folder=None) assert internal(ureg)._diskcache is None assert ureg.cache_folder is None def test_decimal(tmp_path, float_cache_filename): ureg = pint.UnitRegistry( cache_folder=tmp_path / "cache_with_decimal", non_int_type=decimal.Decimal ) assert internal(ureg)._diskcache assert internal(ureg)._diskcache.cache_folder == tmp_path / "cache_with_decimal" assert ureg.cache_folder == tmp_path / "cache_with_decimal" files = tuple(internal(ureg)._diskcache.cache_folder.glob("*.pickle")) assert len(files) == 3 # check that the filenames with decimal are different to the ones with float float_filenames = tuple(p.name for p in float_cache_filename) for p in files: assert p.name not in float_filenames for p in files: with p.open(mode="rb") as fi: obj = pickle.load(fi) if not isinstance(obj, fp.ParsedSource): continue for definition in obj.parsed_source.filter_by(UnitDefinition): if definition.name == "pi": assert isinstance(definition.converter.scale, decimal.Decimal) return assert False def test_auto(float_cache_filename): float_filenames = tuple(p.name for p in float_cache_filename) ureg = pint.UnitRegistry(cache_folder=":auto:") assert internal(ureg)._diskcache assert internal(ureg)._diskcache.cache_folder auto_files = tuple( p.name for p in internal(ureg)._diskcache.cache_folder.glob("*.pickle") ) for file in float_filenames: assert file in auto_files def test_change_file(tmp_path): # Generate a definition file dfile = tmp_path / "definitions.txt" dfile.write_text("x = 1234") # Load the definition file # (this will create two cache files, one for the file another for RegistryCache) ureg = pint.UnitRegistry(dfile, cache_folder=tmp_path) assert ureg.x == 1234 files = tuple(internal(ureg)._diskcache.cache_folder.glob("*.pickle")) assert len(files) == 2 # Modify the definition file # Add some sleep to make sure that the time stamp difference is signficant. time.sleep(FS_SLEEP) dfile.write_text("x = 1235") # Verify that the definiton file was loaded (the cache was invalidated). ureg = pint.UnitRegistry(dfile, cache_folder=tmp_path) assert ureg.x == 1235 files = tuple(internal(ureg)._diskcache.cache_folder.glob("*.pickle")) assert len(files) == 4 pint-0.24.4/pint/testsuite/test_errors.py000066400000000000000000000121501471316474000205110ustar00rootroot00000000000000from __future__ import annotations import pickle import pytest from pint import ( DefinitionSyntaxError, DimensionalityError, LogarithmicUnitCalculusError, OffsetUnitCalculusError, PintError, Quantity, RedefinitionError, UndefinedUnitError, UnitRegistry, ) from pint.errors import LOG_ERROR_DOCS_HTML, OFFSET_ERROR_DOCS_HTML class TestErrors: def test_definition_syntax_error(self): ex = DefinitionSyntaxError("foo") assert str(ex) == "foo" def test_redefinition_error(self): ex = RedefinitionError("foo", "bar") assert str(ex) == "Cannot redefine 'foo' (bar)" with pytest.raises(PintError): raise ex def test_undefined_unit_error(self): x = ("meter",) msg = "'meter' is not defined in the unit registry" ex = UndefinedUnitError(x) assert str(ex) == msg ex = UndefinedUnitError(list(x)) assert str(ex) == msg ex = UndefinedUnitError(set(x)) assert str(ex) == msg with pytest.raises(PintError): raise ex def test_undefined_unit_error_multi(self): x = ("meter", "kg") msg = "('meter', 'kg') are not defined in the unit registry" ex = UndefinedUnitError(x) assert str(ex) == msg ex = UndefinedUnitError(list(x)) assert str(ex) == msg with pytest.raises(PintError): raise ex def test_dimensionality_error(self): ex = DimensionalityError("a", "b") assert str(ex) == "Cannot convert from 'a' to 'b'" ex = DimensionalityError("a", "b", "c") assert str(ex) == "Cannot convert from 'a' (c) to 'b' ()" ex = DimensionalityError("a", "b", "c", "d", extra_msg=": msg") assert str(ex) == "Cannot convert from 'a' (c) to 'b' (d): msg" with pytest.raises(PintError): raise ex def test_offset_unit_calculus_error(self): ex = OffsetUnitCalculusError(Quantity("1 kg")._units) assert ( str(ex) == "Ambiguous operation with offset unit (kilogram). See " + OFFSET_ERROR_DOCS_HTML + " for guidance." ) ex = OffsetUnitCalculusError(Quantity("1 kg")._units, Quantity("1 s")._units) assert ( str(ex) == "Ambiguous operation with offset unit (kilogram, second). See " + OFFSET_ERROR_DOCS_HTML + " for guidance." ) with pytest.raises(PintError): raise ex def test_logarithmic_unit_calculus_error(self): Quantity = UnitRegistry(autoconvert_offset_to_baseunit=True).Quantity ex = LogarithmicUnitCalculusError(Quantity("1 dB")._units) assert ( str(ex) == "Ambiguous operation with logarithmic unit (decibel). See " + LOG_ERROR_DOCS_HTML + " for guidance." ) ex = LogarithmicUnitCalculusError( Quantity("1 dB")._units, Quantity("1 octave")._units ) assert ( str(ex) == "Ambiguous operation with logarithmic unit (decibel, octave). See " + LOG_ERROR_DOCS_HTML + " for guidance." ) with pytest.raises(PintError): raise ex def test_pickle_definition_syntax_error(self, subtests): # OffsetUnitCalculusError raised from a custom ureg must be pickleable even if # the ureg is not registered as the application ureg ureg = UnitRegistry(filename=None) ureg.define("foo = [bar]") ureg.define("bar = 2 foo") q1 = ureg.Quantity("1 foo") q2 = ureg.Quantity("1 bar") for protocol in range(pickle.HIGHEST_PROTOCOL + 1): for ex in ( DefinitionSyntaxError("foo"), RedefinitionError("foo", "bar"), UndefinedUnitError("meter"), DimensionalityError("a", "b", "c", "d", extra_msg=": msg"), OffsetUnitCalculusError( Quantity("1 kg")._units, Quantity("1 s")._units ), OffsetUnitCalculusError(q1._units, q2._units), ): with subtests.test(protocol=protocol, etype=type(ex)): pik = pickle.dumps(ureg.Quantity("1 foo"), protocol) with pytest.raises(UndefinedUnitError): pickle.loads(pik) # assert False, ex.__reduce__() ex2 = pickle.loads(pickle.dumps(ex, protocol)) print(ex) print(ex2) assert type(ex) is type(ex2) assert ex == ex # assert ex.__dict__ == ex2.__dict__ assert str(ex) == str(ex2) with pytest.raises(PintError): raise ex def test_dimensionality_error_message(self): ureg = UnitRegistry(system="SI") with pytest.raises(ValueError) as error: ureg.get_dimensionality("[bilbo]") assert ( str(error.value) == "[bilbo] is not defined as dimension in the pint UnitRegistry" ) pint-0.24.4/pint/testsuite/test_formatter.py000066400000000000000000000035251471316474000212060ustar00rootroot00000000000000from __future__ import annotations import pytest from pint import formatting as fmt from pint.delegates.formatter._format_helpers import formatter, join_u class TestFormatter: def test_join(self): for empty in ((), []): assert join_u("s", empty) == "" assert join_u("*", "1 2 3".split()) == "1*2*3" assert join_u("{0}*{1}", "1 2 3".split()) == "1*2*3" def test_formatter(self): assert formatter({}.items(), ()) == "" assert formatter(dict(meter=1).items(), ()) == "meter" assert formatter((), dict(meter=-1).items()) == "1 / meter" assert formatter((), dict(meter=-1).items(), as_ratio=False) == "meter ** -1" assert ( formatter((), dict(meter=-1, second=-1).items(), as_ratio=False) == "meter ** -1 * second ** -1" ) assert ( formatter( (), dict(meter=-1, second=-1).items(), ) == "1 / meter / second" ) assert ( formatter((), dict(meter=-1, second=-1).items(), single_denominator=True) == "1 / (meter * second)" ) assert ( formatter((), dict(meter=-1, second=-2).items()) == "1 / meter / second ** 2" ) assert ( formatter((), dict(meter=-1, second=-2).items(), single_denominator=True) == "1 / (meter * second ** 2)" ) def testparse_spec(self): assert fmt._parse_spec("") == "" assert fmt._parse_spec("") == "" with pytest.raises(ValueError): fmt._parse_spec("W") with pytest.raises(ValueError): fmt._parse_spec("PL") def test_format_unit(self): assert fmt.format_unit("", "C") == "dimensionless" with pytest.raises(ValueError): fmt.format_unit("m", "W") pint-0.24.4/pint/testsuite/test_formatting.py000066400000000000000000000071171471316474000213560ustar00rootroot00000000000000from __future__ import annotations import pytest import pint.formatting as fmt @pytest.mark.filterwarnings("ignore::DeprecationWarning:pint*") @pytest.mark.parametrize( ["format", "default", "flag", "expected"], ( pytest.param(".02fD", ".3fP", True, (".02f", "D"), id="both-both-separate"), pytest.param(".02fD", ".3fP", False, (".02f", "D"), id="both-both-combine"), pytest.param(".02fD", ".3fP", None, (".02f", "D"), id="both-both-default"), pytest.param("D", ".3fP", True, (".3f", "D"), id="unit-both-separate"), pytest.param("D", ".3fP", False, ("", "D"), id="unit-both-combine"), pytest.param("D", ".3fP", None, ("", "D"), id="unit-both-default"), pytest.param(".02f", ".3fP", True, (".02f", "P"), id="magnitude-both-separate"), pytest.param(".02f", ".3fP", False, (".02f", ""), id="magnitude-both-combine"), pytest.param(".02f", ".3fP", None, (".02f", ""), id="magnitude-both-default"), pytest.param("D", "P", True, ("", "D"), id="unit-unit-separate"), pytest.param("D", "P", False, ("", "D"), id="unit-unit-combine"), pytest.param("D", "P", None, ("", "D"), id="unit-unit-default"), pytest.param( ".02f", ".3f", True, (".02f", ""), id="magnitude-magnitude-separate" ), pytest.param( ".02f", ".3f", False, (".02f", ""), id="magnitude-magnitude-combine" ), pytest.param( ".02f", ".3f", None, (".02f", ""), id="magnitude-magnitude-default" ), pytest.param("D", ".3f", True, (".3f", "D"), id="unit-magnitude-separate"), pytest.param("D", ".3f", False, ("", "D"), id="unit-magnitude-combine"), pytest.param("D", ".3f", None, ("", "D"), id="unit-magnitude-default"), pytest.param(".02f", "P", True, (".02f", "P"), id="magnitude-unit-separate"), pytest.param(".02f", "P", False, (".02f", ""), id="magnitude-unit-combine"), pytest.param(".02f", "P", None, (".02f", ""), id="magnitude-unit-default"), pytest.param("", ".3fP", True, (".3f", "P"), id="none-both-separate"), pytest.param("", ".3fP", False, (".3f", "P"), id="none-both-combine"), pytest.param("", ".3fP", None, (".3f", "P"), id="none-both-default"), pytest.param("", "P", True, ("", "P"), id="none-unit-separate"), pytest.param("", "P", False, ("", "P"), id="none-unit-combine"), pytest.param("", "P", None, ("", "P"), id="none-unit-default"), pytest.param("", ".3f", True, (".3f", ""), id="none-magnitude-separate"), pytest.param("", ".3f", False, (".3f", ""), id="none-magnitude-combine"), pytest.param("", ".3f", None, (".3f", ""), id="none-magnitude-default"), pytest.param("", "", True, ("", ""), id="none-none-separate"), pytest.param("", "", False, ("", ""), id="none-none-combine"), pytest.param("", "", None, ("", ""), id="none-none-default"), ), ) def test_split_format(format, default, flag, expected): result = fmt.split_format(format, default, flag) assert result == expected def test_register_unit_format(func_registry): @fmt.register_unit_format("custom") def format_custom(unit, registry, **options): # Ensure the registry is correct.. registry.Unit(unit) return "" quantity = 1.0 * func_registry.meter assert f"{quantity:custom}" == "1.0 " with pytest.raises(ValueError, match="format 'custom' already exists"): @fmt.register_unit_format("custom") def format_custom_redefined(unit, registry, **options): return "" pint-0.24.4/pint/testsuite/test_infer_base_unit.py000066400000000000000000000074361471316474000223440ustar00rootroot00000000000000from __future__ import annotations from decimal import Decimal from fractions import Fraction import pytest from pint import UnitRegistry from pint.testsuite import helpers from pint.util import infer_base_unit def test_infer_base_unit(sess_registry): test_units = sess_registry.Quantity(1, "meter**2").units registry = sess_registry assert ( infer_base_unit(sess_registry.Quantity(1, "millimeter * nanometer")) == test_units ) assert infer_base_unit("millimeter * nanometer", registry) == test_units assert ( infer_base_unit( sess_registry.Quantity(1, "millimeter * nanometer").units, registry ) == test_units ) with pytest.raises(ValueError, match=r"No registry provided."): infer_base_unit("millimeter") def test_infer_base_unit_decimal(sess_registry): ureg = UnitRegistry(non_int_type=Decimal) QD = ureg.Quantity ibu_d = infer_base_unit(QD(Decimal(1), "millimeter * nanometer")) assert ibu_d == QD(Decimal(1), "meter**2").units assert all(isinstance(v, Decimal) for v in ibu_d.values()) def test_infer_base_unit_fraction(sess_registry): ureg = UnitRegistry(non_int_type=Fraction) QD = ureg.Quantity ibu_d = infer_base_unit(QD(Fraction("1"), "millimeter * nanometer")) assert ibu_d == QD(Fraction("1"), "meter**2").units assert all(isinstance(v, Fraction) for v in ibu_d.values()) def test_units_adding_to_zero(sess_registry): assert ( infer_base_unit(sess_registry.Quantity(1, "m * mm / m / um * s")) == sess_registry.Quantity(1, "s").units ) def test_to_compact(sess_registry): r = ( sess_registry.Quantity(1000000000, "m") * sess_registry.Quantity(1, "mm") / sess_registry.Quantity(1, "s") / sess_registry.Quantity(1, "ms") ) compact_r = r.to_compact() expected = sess_registry.Quantity(1000.0, "kilometer**2 / second**2") helpers.assert_quantity_almost_equal(compact_r, expected) r = ( sess_registry.Quantity(1, "m") * sess_registry.Quantity(1, "mm") / sess_registry.Quantity(1, "m") / sess_registry.Quantity(2, "um") * sess_registry.Quantity(2, "s") ).to_compact() helpers.assert_quantity_almost_equal(r, sess_registry.Quantity(1000, "s")) def test_to_compact_decimal(sess_registry): ureg = UnitRegistry(non_int_type=Decimal) Q = ureg.Quantity r = ( Q(Decimal("1000000000.0"), "m") * Q(Decimal(1), "mm") / Q(Decimal(1), "s") / Q(Decimal(1), "ms") ) compact_r = r.to_compact() expected = Q(Decimal("1000.0"), "kilometer**2 / second**2") assert compact_r == expected r = ( Q(Decimal(1), "m") * Q(1, "mm") / Q(1, "m**2") / Q(2, "um") * Q(2, "s") ).to_compact() assert r == Q(1000, "s/m") def test_to_compact_fraction(sess_registry): ureg = UnitRegistry(non_int_type=Fraction) Q = ureg.Quantity r = ( Q(Fraction("10000000000/10"), "m") * Q(Fraction("1"), "mm") / Q(Fraction("1"), "s") / Q(Fraction("1"), "ms") ) compact_r = r.to_compact() expected = Q(Fraction("1000.0"), "kilometer**2 / second**2") assert compact_r == expected r = ( sess_registry.Quantity(Fraction(1), "m") * sess_registry.Quantity(1, "mm") / sess_registry.Quantity(1, "m**2") / sess_registry.Quantity(2, "um") * sess_registry.Quantity(2, "s") ).to_compact() assert r == Q(1000, "s/m") def test_volts(sess_registry): r = ( sess_registry.Quantity(1, "V") * sess_registry.Quantity(1, "mV") / sess_registry.Quantity(1, "kV") ) b = infer_base_unit(r) assert b == sess_registry.Quantity(1, "V").units helpers.assert_quantity_almost_equal(r, sess_registry.Quantity(1, "uV")) pint-0.24.4/pint/testsuite/test_issues.py000066400000000000000000001310411471316474000205110ustar00rootroot00000000000000from __future__ import annotations import copy import decimal import math import pprint import pytest from pint import ( Context, DimensionalityError, UnitRegistry, get_application_registry, ) from pint.compat import np from pint.delegates.formatter._compound_unit_helpers import sort_by_dimensionality from pint.facets.plain.unit import UnitsContainer from pint.testing import assert_equal from pint.testsuite import QuantityTestCase, helpers from pint.util import ParserHelper from .helpers import internal # TODO: do not subclass from QuantityTestCase class TestIssues(QuantityTestCase): kwargs = dict(autoconvert_offset_to_baseunit=False) @pytest.mark.xfail def test_issue25(self, module_registry): x = ParserHelper.from_string("10 %") assert x == ParserHelper(10, {"%": 1}) x = ParserHelper.from_string("10 ‰") assert x == ParserHelper(10, {"‰": 1}) module_registry.define("percent = [fraction]; offset: 0 = %") module_registry.define("permille = percent / 10 = ‰") x = module_registry.parse_expression("10 %") assert x == module_registry.Quantity(10, {"%": 1}) y = module_registry.parse_expression("10 ‰") assert y == module_registry.Quantity(10, {"‰": 1}) assert x.to("‰") == module_registry.Quantity(1, {"‰": 1}) def test_issue29(self, module_registry): t = 4 * module_registry("mW") assert t.magnitude == 4 assert t._units == UnitsContainer(milliwatt=1) assert t.to("joule / second") == 4e-3 * module_registry("W") @pytest.mark.xfail @helpers.requires_numpy def test_issue37(self, module_registry): x = np.ma.masked_array([1, 2, 3], mask=[True, True, False]) q = module_registry.meter * x assert isinstance(q, module_registry.Quantity) np.testing.assert_array_equal(q.magnitude, x) assert q.units == module_registry.meter.units q = x * module_registry.meter assert isinstance(q, module_registry.Quantity) np.testing.assert_array_equal(q.magnitude, x) assert q.units == module_registry.meter.units m = np.ma.masked_array(2 * np.ones(3, 3)) qq = q * m assert isinstance(qq, module_registry.Quantity) np.testing.assert_array_equal(qq.magnitude, x * m) assert qq.units == module_registry.meter.units qq = m * q assert isinstance(qq, module_registry.Quantity) np.testing.assert_array_equal(qq.magnitude, x * m) assert qq.units == module_registry.meter.units @pytest.mark.xfail @helpers.requires_numpy def test_issue39(self, module_registry): x = np.matrix([[1, 2, 3], [1, 2, 3], [1, 2, 3]]) q = module_registry.meter * x assert isinstance(q, module_registry.Quantity) np.testing.assert_array_equal(q.magnitude, x) assert q.units == module_registry.meter.units q = x * module_registry.meter assert isinstance(q, module_registry.Quantity) np.testing.assert_array_equal(q.magnitude, x) assert q.units == module_registry.meter.units m = np.matrix(2 * np.ones(3, 3)) qq = q * m assert isinstance(qq, module_registry.Quantity) np.testing.assert_array_equal(qq.magnitude, x * m) assert qq.units == module_registry.meter.units qq = m * q assert isinstance(qq, module_registry.Quantity) np.testing.assert_array_equal(qq.magnitude, x * m) assert qq.units == module_registry.meter.units @helpers.requires_numpy def test_issue44(self, module_registry): x = 4.0 * module_registry.dimensionless np.sqrt(x) helpers.assert_quantity_almost_equal( np.sqrt([4.0] * module_registry.dimensionless), [2.0] * module_registry.dimensionless, ) helpers.assert_quantity_almost_equal( np.sqrt(4.0 * module_registry.dimensionless), 2.0 * module_registry.dimensionless, ) def test_issue45(self, module_registry): import math helpers.assert_quantity_almost_equal( math.sqrt(4 * module_registry.m / module_registry.cm), math.sqrt(4 * 100) ) helpers.assert_quantity_almost_equal( float(module_registry.V / module_registry.mV), 1000.0 ) @helpers.requires_numpy def test_issue45b(self, module_registry): helpers.assert_quantity_almost_equal( np.sin([np.pi / 2] * module_registry.m / module_registry.m), np.sin([np.pi / 2] * module_registry.dimensionless), ) helpers.assert_quantity_almost_equal( np.sin([np.pi / 2] * module_registry.cm / module_registry.m), np.sin([np.pi / 2] * module_registry.dimensionless * 0.01), ) def test_issue50(self, module_registry): Q_ = module_registry.Quantity assert Q_(100) == 100 * module_registry.dimensionless assert Q_("100") == 100 * module_registry.dimensionless def test_issue52(self): u1 = UnitRegistry() u2 = UnitRegistry() q1 = 1 * u1.meter q2 = 1 * u2.meter import operator as op for fun in ( op.add, op.iadd, op.sub, op.isub, op.mul, op.imul, op.floordiv, op.ifloordiv, op.truediv, op.itruediv, ): with pytest.raises(ValueError): fun(q1, q2) def test_issue54(self, module_registry): assert (1 * module_registry.km / module_registry.m + 1).magnitude == 1001 def test_issue54_related(self, module_registry): assert module_registry.km / module_registry.m == 1000 assert 1000 == module_registry.km / module_registry.m assert 900 < module_registry.km / module_registry.m assert 1100 > module_registry.km / module_registry.m def test_issue61(self, module_registry): Q_ = module_registry.Quantity for value in ({}, {"a": 3}, None): with pytest.raises(TypeError): Q_(value) with pytest.raises(TypeError): Q_(value, "meter") with pytest.raises(ValueError): Q_("", "meter") with pytest.raises(ValueError): Q_("") @helpers.requires_not_numpy() def test_issue61_notNP(self, module_registry): Q_ = module_registry.Quantity for value in ([1, 2, 3], (1, 2, 3)): with pytest.raises(TypeError): Q_(value) with pytest.raises(TypeError): Q_(value, "meter") def test_issue62(self, module_registry): m = module_registry("m**0.5") assert str(m.units) == "meter ** 0.5" def test_issue66(self, module_registry): assert module_registry.get_dimensionality( UnitsContainer({"[temperature]": 1}) ) == UnitsContainer({"[temperature]": 1}) assert module_registry.get_dimensionality( module_registry.kelvin ) == UnitsContainer({"[temperature]": 1}) assert module_registry.get_dimensionality( module_registry.degC ) == UnitsContainer({"[temperature]": 1}) def test_issue66b(self, module_registry): assert module_registry.get_base_units(module_registry.kelvin) == ( 1.0, module_registry.Unit(UnitsContainer({"kelvin": 1})), ) assert module_registry.get_base_units(module_registry.degC) == ( 1.0, module_registry.Unit(UnitsContainer({"kelvin": 1})), ) def test_issue69(self, module_registry): q = module_registry("m").to(module_registry("in")) assert q == module_registry("m").to("in") @helpers.requires_numpy def test_issue74(self, module_registry): v1 = np.asarray([1.0, 2.0, 3.0]) v2 = np.asarray([3.0, 2.0, 1.0]) q1 = v1 * module_registry.ms q2 = v2 * module_registry.ms np.testing.assert_array_equal(q1 < q2, v1 < v2) np.testing.assert_array_equal(q1 > q2, v1 > v2) np.testing.assert_array_equal(q1 <= q2, v1 <= v2) np.testing.assert_array_equal(q1 >= q2, v1 >= v2) q2s = np.asarray([0.003, 0.002, 0.001]) * module_registry.s v2s = q2s.to("ms").magnitude np.testing.assert_array_equal(q1 < q2s, v1 < v2s) np.testing.assert_array_equal(q1 > q2s, v1 > v2s) np.testing.assert_array_equal(q1 <= q2s, v1 <= v2s) np.testing.assert_array_equal(q1 >= q2s, v1 >= v2s) @helpers.requires_numpy def test_issue75(self, module_registry): v1 = np.asarray([1.0, 2.0, 3.0]) v2 = np.asarray([3.0, 2.0, 1.0]) q1 = v1 * module_registry.ms q2 = v2 * module_registry.ms np.testing.assert_array_equal(q1 == q2, v1 == v2) np.testing.assert_array_equal(q1 != q2, v1 != v2) q2s = np.asarray([0.003, 0.002, 0.001]) * module_registry.s v2s = q2s.to("ms").magnitude np.testing.assert_array_equal(q1 == q2s, v1 == v2s) np.testing.assert_array_equal(q1 != q2s, v1 != v2s) @helpers.requires_uncertainties() def test_issue77(self, module_registry): acc = (5.0 * module_registry("m/s/s")).plus_minus(0.25) tim = (37.0 * module_registry("s")).plus_minus(0.16) dis = acc * tim**2 / 2 assert dis.value == acc.value * tim.value**2 / 2 def test_issue85(self, module_registry): T = 4.0 * module_registry.kelvin m = 1.0 * module_registry.amu va = 2.0 * module_registry.k * T / m va.to_base_units() boltmk = 1.380649e-23 * module_registry.J / module_registry.K vb = 2.0 * boltmk * T / m helpers.assert_quantity_almost_equal(va.to_base_units(), vb.to_base_units()) def test_issue86(self, module_registry): module_registry.autoconvert_offset_to_baseunit = True def parts(q): return q.magnitude, q.units q1 = 10.0 * module_registry.degC q2 = 10.0 * module_registry.kelvin k1 = q1.to_base_units() q3 = 3.0 * module_registry.meter q1m, q1u = parts(q1) q2m, q2u = parts(q2) q3m, q3u = parts(q3) k1m, k1u = parts(k1) assert parts(q2 * q3) == (q2m * q3m, q2u * q3u) assert parts(q2 / q3) == (q2m / q3m, q2u / q3u) assert parts(q3 * q2) == (q3m * q2m, q3u * q2u) assert parts(q3 / q2) == (q3m / q2m, q3u / q2u) assert parts(q2**1) == (q2m**1, q2u**1) assert parts(q2**-1) == (q2m**-1, q2u**-1) assert parts(q2**2) == (q2m**2, q2u**2) assert parts(q2**-2) == (q2m**-2, q2u**-2) assert parts(q1 * q3) == (k1m * q3m, k1u * q3u) assert parts(q1 / q3) == (k1m / q3m, k1u / q3u) assert parts(q3 * q1) == (q3m * k1m, q3u * k1u) assert parts(q3 / q1) == (q3m / k1m, q3u / k1u) assert parts(q1**-1) == (k1m**-1, k1u**-1) assert parts(q1**2) == (k1m**2, k1u**2) assert parts(q1**-2) == (k1m**-2, k1u**-2) def test_issues86b(self, module_registry): T1 = module_registry.Quantity(200, module_registry.degC) # T1 = 200.0 * module_registry.degC T2 = T1.to(module_registry.kelvin) m = 132.9054519 * module_registry.amu v1 = 2 * module_registry.k * T1 / m v2 = 2 * module_registry.k * T2 / m helpers.assert_quantity_almost_equal(v1, v2) helpers.assert_quantity_almost_equal(v1, v2.to_base_units()) helpers.assert_quantity_almost_equal(v1.to_base_units(), v2) helpers.assert_quantity_almost_equal(v1.to_base_units(), v2.to_base_units()) @pytest.mark.xfail def test_issue86c(self, module_registry): module_registry.autoconvert_offset_to_baseunit = True T = module_registry.degC T = 100.0 * T helpers.assert_quantity_almost_equal( module_registry.k * 2 * T, module_registry.k * (2 * T) ) def test_issue93(self, module_registry): x = 5 * module_registry.meter assert isinstance(x.magnitude, int) y = 0.1 * module_registry.meter assert isinstance(y.magnitude, float) z = 5 * module_registry.meter assert isinstance(z.magnitude, int) z += y assert isinstance(z.magnitude, float) helpers.assert_quantity_almost_equal(x + y, 5.1 * module_registry.meter) helpers.assert_quantity_almost_equal(z, 5.1 * module_registry.meter) def test_issue104(self, module_registry): x = [ module_registry("1 meter"), module_registry("1 meter"), module_registry("1 meter"), ] y = [module_registry("1 meter")] * 3 def summer(values): if not values: return 0 total = values[0] for v in values[1:]: total += v return total helpers.assert_quantity_almost_equal( summer(x), module_registry.Quantity(3, "meter") ) helpers.assert_quantity_almost_equal(x[0], module_registry.Quantity(1, "meter")) helpers.assert_quantity_almost_equal( summer(y), module_registry.Quantity(3, "meter") ) helpers.assert_quantity_almost_equal(y[0], module_registry.Quantity(1, "meter")) def test_issue105(self, module_registry): func = module_registry.parse_unit_name val = list(func("meter")) assert list(func("METER")) == [] assert val == list(func("METER", False)) for func in (module_registry.get_name, module_registry.parse_expression): val = func("meter") with pytest.raises(AttributeError): func("METER") assert val == func("METER", False) @helpers.requires_numpy def test_issue127(self, module_registry): q = [1.0, 2.0, 3.0, 4.0] * module_registry.meter q[0] = np.nan assert q[0] != 1.0 assert math.isnan(q[0].magnitude) q[1] = float("NaN") assert q[1] != 2.0 assert math.isnan(q[1].magnitude) def test_issue170(self): Q_ = UnitRegistry().Quantity q = Q_("1 kHz") / Q_("100 Hz") iq = int(q) assert iq == 10 assert isinstance(iq, int) def test_angstrom_creation(self, module_registry): module_registry.Quantity(2, "Å") def test_alternative_angstrom_definition(self, module_registry): module_registry.Quantity(2, "\u212B") def test_micro_creation_U03bc(self, module_registry): module_registry.Quantity(2, "μm") def test_micro_creation_U00b5(self, module_registry): module_registry.Quantity(2, "µm") def test_micro_creation_mu(self, module_registry): module_registry.Quantity(2, "mug") def test_micro_creation_mc(self, module_registry): module_registry.Quantity(2, "mcg") def test_liter_creation_U2113(self, module_registry): module_registry.Quantity(2, "ℓ") @helpers.requires_numpy def test_issue171_real_imag(self, module_registry): qr = [1.0, 2.0, 3.0, 4.0] * module_registry.meter qi = [4.0, 3.0, 2.0, 1.0] * module_registry.meter q = qr + 1j * qi helpers.assert_quantity_equal(q.real, qr) helpers.assert_quantity_equal(q.imag, qi) @helpers.requires_numpy def test_issue171_T(self, module_registry): a = np.asarray([[1.0, 2.0, 3.0, 4.0], [4.0, 3.0, 2.0, 1.0]]) q1 = a * module_registry.meter q2 = a.T * module_registry.meter helpers.assert_quantity_equal(q1.T, q2) @helpers.requires_numpy def test_issue250(self, module_registry): a = module_registry.V b = module_registry.mV assert np.float16(a / b) == 1000.0 assert np.float32(a / b) == 1000.0 assert np.float64(a / b) == 1000.0 if "float128" in dir(np): assert np.float128(a / b) == 1000.0 def test_issue252(self): ur = UnitRegistry() q = ur("3 F") t = copy.deepcopy(q) u = t.to(ur.mF) helpers.assert_quantity_equal(q.to(ur.mF), u) def test_issue323(self, module_registry): from fractions import Fraction as F assert (self.Q_(F(2, 3), "s")).to("ms") == self.Q_(F(2000, 3), "ms") assert (self.Q_(F(2, 3), "m")).to("km") == self.Q_(F(1, 1500), "km") def test_issue339(self, module_registry): q1 = module_registry("") assert q1.magnitude == 1 assert q1.units == module_registry.dimensionless q2 = module_registry("1 dimensionless") assert q1 == q2 def test_issue354_356_370(self, module_registry): assert ( f"{1 * module_registry.second / module_registry.millisecond:~}" == "1.0 s / ms" ) assert f"{1 * module_registry.count:~}" == "1 count" assert "{:~}".format(1 * module_registry("MiB")) == "1 MiB" def test_issue468(self, module_registry): @module_registry.wraps("kg", "meter") def f(x): return x x = module_registry.Quantity(1.0, "meter") y = f(x) z = x * y assert z == module_registry.Quantity(1.0, "meter * kilogram") @helpers.requires_numpy def test_issue482(self, module_registry): q = module_registry.Quantity(1, module_registry.dimensionless) qe = np.exp(q) assert isinstance(qe, module_registry.Quantity) @helpers.requires_numpy def test_issue483(self, module_registry): a = np.asarray([1, 2, 3]) q = [1, 2, 3] * module_registry.dimensionless p = (q**q).m np.testing.assert_array_equal(p, a**a) def test_issue507(self, module_registry): # leading underscore in unit works with numbers module_registry.define("_100km = 100 * kilometer") battery_ec = 16 * module_registry.kWh / module_registry._100km # noqa: F841 # ... but not with text module_registry.define("_home = 4700 * kWh / year") with pytest.raises(AttributeError): home_elec_power = 1 * module_registry._home # noqa: F841 # ... or with *only* underscores module_registry.define("_ = 45 * km") with pytest.raises(AttributeError): one_blank = 1 * module_registry._ # noqa: F841 def test_issue523(self, module_registry): src, dst = UnitsContainer({"meter": 1}), UnitsContainer({"degF": 1}) value = 10.0 convert = module_registry.convert with pytest.raises(DimensionalityError): convert(value, src, dst) with pytest.raises(DimensionalityError): convert(value, dst, src) def test_issue532(self, module_registry): @module_registry.check(module_registry("")) def f(x): return 2 * x assert f(module_registry.Quantity(1, "")) == 2 with pytest.raises(DimensionalityError): f(module_registry.Quantity(1, "m")) def test_issue625a(self, module_registry): Q_ = module_registry.Quantity from math import sqrt @module_registry.wraps( module_registry.second, ( module_registry.meters, module_registry.meters / module_registry.second**2, ), ) def calculate_time_to_fall(height, gravity=Q_(9.8, "m/s^2")): """Calculate time to fall from a height h with a default gravity. By default, the gravity is assumed to be earth gravity, but it can be modified. d = .5 * g * t**2 t = sqrt(2 * d / g) Parameters ---------- height : gravity : (Default value = Q_(9.8) "m/s^2") : Returns ------- """ return sqrt(2 * height / gravity) lunar_module_height = Q_(10, "m") t1 = calculate_time_to_fall(lunar_module_height) # print(t1) assert round(abs(t1 - Q_(1.4285714285714286, "s")), 7) == 0 moon_gravity = Q_(1.625, "m/s^2") t2 = calculate_time_to_fall(lunar_module_height, moon_gravity) assert round(abs(t2 - Q_(3.508232077228117, "s")), 7) == 0 def test_issue625b(self, module_registry): Q_ = module_registry.Quantity @module_registry.wraps("=A*B", ("=A", "=B")) def get_displacement(time, rate=Q_(1, "m/s")): """Calculates displacement from a duration and default rate. Parameters ---------- time : rate : (Default value = Q_(1) "m/s") : Returns ------- """ return time * rate d1 = get_displacement(Q_(2, "s")) assert round(abs(d1 - Q_(2, "m")), 7) == 0 d2 = get_displacement(Q_(2, "s"), Q_(1, "deg/s")) assert round(abs(d2 - Q_(2, " deg")), 7) == 0 def test_issue625c(self): u = UnitRegistry() @u.wraps("=A*B*C", ("=A", "=B", "=C")) def get_product(a=2 * u.m, b=3 * u.m, c=5 * u.m): return a * b * c assert get_product(a=3 * u.m) == 45 * u.m**3 assert get_product(b=2 * u.m) == 20 * u.m**3 assert get_product(c=1 * u.dimensionless) == 6 * u.m**2 def test_issue655a(self, module_registry): distance = 1 * module_registry.m time = 1 * module_registry.s velocity = distance / time assert distance.check("[length]") assert not distance.check("[time]") assert velocity.check("[length] / [time]") assert velocity.check("1 / [time] * [length]") def test_issue655b(self, module_registry): Q_ = module_registry.Quantity @module_registry.check("[length]", "[length]/[time]^2") def pendulum_period(length, G=Q_(1, "standard_gravity")): # print(length) return (2 * math.pi * (length / G) ** 0.5).to("s") length = Q_(1, module_registry.m) # Assume earth gravity t = pendulum_period(length) assert round(abs(t - Q_("2.0064092925890407 second")), 7) == 0 # Use moon gravity moon_gravity = Q_(1.625, "m/s^2") t = pendulum_period(length, moon_gravity) assert round(abs(t - Q_("4.928936075204336 second")), 7) == 0 def test_issue783(self, module_registry): assert not module_registry("g") == [] def test_issue856(self, module_registry): ph1 = ParserHelper(scale=123) ph2 = copy.deepcopy(ph1) assert ph2.scale == ph1.scale module_registry1 = UnitRegistry() module_registry2 = copy.deepcopy(module_registry1) # Very basic functionality test assert module_registry2("1 t").to("kg").magnitude == 1000 def test_issue856b(self): # Test that, after a deepcopy(), the two UnitRegistries are # independent from each other ureg1 = UnitRegistry() ureg2 = copy.deepcopy(ureg1) ureg1.define("test123 = 123 kg") ureg2.define("test123 = 456 kg") assert ureg1("1 test123").to("kg").magnitude == 123 assert ureg2("1 test123").to("kg").magnitude == 456 def test_issue876(self): # Same hash must not imply equality. # As an implementation detail of CPython, hash(-1) == hash(-2). # This test is useless in potential alternative Python implementations where # hash(-1) != hash(-2); one would need to find hash collisions specific for each # implementation a = UnitsContainer({"[mass]": -1}) b = UnitsContainer({"[mass]": -2}) c = UnitsContainer({"[mass]": -3}) # Guarantee working on alternative Python implementations assert (hash(-1) == hash(-2)) == (hash(a) == hash(b)) assert (hash(-1) == hash(-3)) == (hash(a) == hash(c)) assert a != b assert a != c def test_issue902(self): module_registry = UnitRegistry(auto_reduce_dimensions=True) velocity = 1 * module_registry.m / module_registry.s cross_section = 1 * module_registry.um**2 result = cross_section / velocity assert result == 1e-12 * module_registry.m * module_registry.s def test_issue912(self, module_registry): """pprint.pformat() invokes sorted() on large sets and frozensets and graciously handles TypeError, but not generic Exceptions. This test will fail if pint.DimensionalityError stops being a subclass of TypeError. Parameters ---------- Returns ------- """ meter_units = module_registry.get_compatible_units(module_registry.meter) hertz_units = module_registry.get_compatible_units(module_registry.hertz) pprint.pformat(meter_units | hertz_units) def test_issue932(self, module_registry): q = module_registry.Quantity("1 kg") with pytest.raises(DimensionalityError): q.to("joule") module_registry.enable_contexts("energy", *(Context() for _ in range(20))) q.to("joule") module_registry.disable_contexts() with pytest.raises(DimensionalityError): q.to("joule") def test_issue960(self, module_registry): q = (1 * module_registry.nanometer).to_compact("micrometer") assert q.units == module_registry.nanometer assert q.magnitude == 1 def test_issue1032(self, module_registry): class MultiplicativeDictionary(dict): def __rmul__(self, other): return self.__class__( {key: value * other for key, value in self.items()} ) q = 3 * module_registry.s d = MultiplicativeDictionary({4: 5, 6: 7}) assert q * d == MultiplicativeDictionary( {4: 15 * module_registry.s, 6: 21 * module_registry.s} ) with pytest.raises(TypeError): d * q @helpers.requires_numpy def test_issue973(self, module_registry): """Verify that an empty array Quantity can be created through multiplication.""" q0 = np.array([]) * module_registry.m # by Unit q1 = np.array([]) * module_registry("m") # by Quantity assert isinstance(q0, module_registry.Quantity) assert isinstance(q1, module_registry.Quantity) assert len(q0) == len(q1) == 0 def test_issue1058(self, module_registry): """verify that auto-reducing quantities with three or more units of same plain type succeeds""" q = 1 * module_registry.mg / module_registry.g / module_registry.kg q.ito_reduced_units() assert isinstance(q, module_registry.Quantity) def test_issue1062_issue1097(self): # Must not be used by any other tests ureg = UnitRegistry() assert "nanometer" not in internal(ureg)._units for i in range(5): ctx = Context.from_lines(["@context _", "cal = 4 J"]) with ureg.context("sp", ctx): q = ureg.Quantity(1, "nm") q.to("J") def test_issue1066(self): """Verify calculations for offset units of higher dimension""" ureg = UnitRegistry() ureg.define("barga = 1e5 * Pa; offset: 1e5") ureg.define("bargb = 1 * bar; offset: 1") q_4barg_a = ureg.Quantity(4, ureg.barga) q_4barg_b = ureg.Quantity(4, ureg.bargb) q_5bar = ureg.Quantity(5, ureg.bar) helpers.assert_quantity_equal(q_4barg_a, q_5bar) helpers.assert_quantity_equal(q_4barg_b, q_5bar) def test_issue1086(self, module_registry): # units with prefixes should correctly test as 'in' the registry assert "bits" in module_registry assert "gigabits" in module_registry assert "meters" in module_registry assert "kilometers" in module_registry # unknown or incorrect units should test as 'not in' the registry assert "magicbits" not in module_registry assert "unknownmeters" not in module_registry assert "gigatrees" not in module_registry def test_issue1112(self): ureg = UnitRegistry( """ m = [length] g = [mass] s = [time] ft = 0.305 m lb = 454 g @context c1 [time]->[length] : value * 10 m/s @end @context c2 ft = 0.3 m @end @context c3 lb = 500 g @end """.splitlines() ) ureg.enable_contexts("c1") ureg.enable_contexts("c2") ureg.enable_contexts("c3") @helpers.requires_numpy def test_issue1144_1102(self, module_registry): # Performing operations shouldn't modify the original objects # Issue 1144 ddc = "delta_degree_Celsius" q1 = module_registry.Quantity([-287.78, -32.24, -1.94], ddc) q2 = module_registry.Quantity(70.0, "degree_Fahrenheit") q1 - q2 assert all(q1 == module_registry.Quantity([-287.78, -32.24, -1.94], ddc)) assert q2 == module_registry.Quantity(70.0, "degree_Fahrenheit") q2 - q1 assert all(q1 == module_registry.Quantity([-287.78, -32.24, -1.94], ddc)) assert q2 == module_registry.Quantity(70.0, "degree_Fahrenheit") # Issue 1102 val = [30.0, 45.0, 60.0] * module_registry.degree val == 1 1 == val assert all(val == module_registry.Quantity([30.0, 45.0, 60.0], "degree")) # Test for another bug identified by searching on "_convert_magnitude" q2 = module_registry.Quantity(3, "degree_Kelvin") q1 - q2 assert all(q1 == module_registry.Quantity([-287.78, -32.24, -1.94], ddc)) @helpers.requires_numpy def test_issue_1136(self, module_registry): assert ( 2 ** module_registry.Quantity([2, 3], "") == 2 ** np.array([2, 3]) ).all() with pytest.raises(DimensionalityError): 2 ** module_registry.Quantity([2, 3], "m") def test_issue1175(self): import pickle foo1 = get_application_registry().Quantity(1, "s") foo2 = pickle.loads(pickle.dumps(foo1)) assert isinstance(foo1, foo2.__class__) assert isinstance(foo2, foo1.__class__) @helpers.requires_numpy def test_issue1174(self, module_registry): q = [1.0, -2.0, 3.0, -4.0] * module_registry.meter assert np.sign(q[0].magnitude) assert np.sign(q[1].magnitude) @helpers.requires_numpy() def test_issue_1185(self, module_registry): # Test __pow__ foo = module_registry.Quantity((3, 3), "mm / cm") assert np.allclose( foo ** module_registry.Quantity([2, 3], ""), 0.3 ** np.array([2, 3]) ) assert np.allclose(foo ** np.array([2, 3]), 0.3 ** np.array([2, 3])) assert np.allclose(np.array([2, 3]) ** foo, np.array([2, 3]) ** 0.3) # Test __ipow__ foo **= np.array([2, 3]) assert np.allclose(foo, 0.3 ** np.array([2, 3])) # Test __rpow__ assert np.allclose( np.array((1, 1)).__rpow__(module_registry.Quantity((2, 3), "mm / cm")), np.array((0.2, 0.3)), ) assert np.allclose( module_registry.Quantity((20, 20), "mm / cm").__rpow__( np.array((0.2, 0.3)) ), np.array((0.04, 0.09)), ) def test_issue1277(self, module_registry): ureg = module_registry assert ureg("%") == ureg("percent") assert ureg("%") == ureg.percent assert ureg("ppm") == ureg.ppm a = ureg.Quantity("10 %") b = ureg.Quantity("100 ppm") c = ureg.Quantity("0.5") assert f"{a}" == "10 percent" assert f"{a:~}" == "10 %" assert f"{b}" == "100 ppm" assert f"{b:~}" == "100 ppm" assert_equal(a, 0.1) assert_equal(1000 * b, a) assert_equal(c, 5 * a) assert_equal((1 * ureg.meter) / (1 * ureg.kilometer), 0.1 * ureg.percent) assert c.to("percent").m == 50 # assert c.to("%").m == 50 # TODO: fails. def test_issue1963(self, module_registry): ureg = module_registry assert ureg("‰") == ureg("permille") assert ureg("‰") == ureg.permille a = ureg.Quantity("10 ‰") b = ureg.Quantity("100 ppm") c = ureg.Quantity("0.5") assert f"{a}" == "10 permille" assert f"{a:~}" == "10 ‰" assert_equal(a, 0.01) assert_equal(1e2 * b, a) assert_equal(c, 50 * a) assert_equal((1 * ureg.milligram) / (1 * ureg.gram), ureg.permille) @pytest.mark.xfail @helpers.requires_uncertainties() def test_issue_1300(self): # TODO: THIS is not longer necessary after moving to formatter module_registry = UnitRegistry() module_registry.default_format = "~P" m = module_registry.Measurement(1, 0.1, "meter") assert m.default_format == "~P" @helpers.requires_numpy() def test_issue1674(self, module_registry): Q_ = module_registry.Quantity arr_of_q = np.array([Q_(2, "m"), Q_(4, "m")], dtype="object") q_arr = Q_(np.array([1, 2]), "m") helpers.assert_quantity_equal( arr_of_q * q_arr, np.array([Q_(2, "m^2"), Q_(8, "m^2")], dtype="object") ) helpers.assert_quantity_equal( arr_of_q / q_arr, np.array([Q_(2, ""), Q_(2, "")], dtype="object") ) arr_of_q = np.array([Q_(2, "m"), Q_(4, "s")], dtype="object") q_arr = Q_(np.array([1, 2]), "m") helpers.assert_quantity_equal( arr_of_q * q_arr, np.array([Q_(2, "m^2"), Q_(8, "m s")], dtype="object") ) @helpers.requires_babel(["es_ES"]) def test_issue_1400(self, sess_registry): q1 = 3.1 * sess_registry.W q2 = 3.1 * sess_registry.W / sess_registry.cm assert q1.format_babel("~", locale="es_ES") == "3,1 W" assert q1.format_babel("", locale="es_ES") == "3,1 vatios" assert q2.format_babel("~", locale="es_ES") == "3,1 W/cm" assert q2.format_babel("", locale="es_ES") == "3,1 vatios por centímetro" @helpers.requires_numpy() @helpers.requires_uncertainties() def test_issue1611(self, module_registry): from numpy.testing import assert_almost_equal from uncertainties import ufloat from pint import pint_eval pint_eval.tokenizer = pint_eval.uncertainty_tokenizer u1 = ufloat(1.2, 0.34) u2 = ufloat(5.6, 0.78) q1_u = module_registry.Quantity(u2 - u1, "m") q1_str = str(q1_u) q1_str = f"{q1_u:.4uS}" q1_m = q1_u.magnitude q2_u = module_registry.Quantity(q1_str) # Not equal because the uncertainties are differently random! assert q1_u != q2_u q2_m = q2_u.magnitude assert_almost_equal(q2_m.nominal_value, q1_m.nominal_value, decimal=9) assert_almost_equal(q2_m.std_dev, q1_m.std_dev, decimal=4) q3_str = "12.34(5678)e-066 m" q3_u = module_registry.Quantity(q3_str) q3_m = q3_u.magnitude assert q3_m < 1 @helpers.requires_uncertainties def test_issue1614(self, module_registry): from uncertainties import UFloat, ufloat q = module_registry.Quantity(1.0, "m") assert isinstance(q, module_registry.Quantity) m = module_registry.Measurement(2.0, 0.3, "m") assert isinstance(m, module_registry.Measurement) u1 = ufloat(1.2, 3.4) u2 = ufloat(5.6, 7.8) q1_u = module_registry.Quantity(u1, "m") m1 = module_registry.Measurement(q1_u) assert m1.value.magnitude == u1.nominal_value assert m1.error.magnitude == u1.std_dev m2 = module_registry.Measurement(5.6, 7.8) # dimensionless q2_u = module_registry.Quantity(m2) assert isinstance(q2_u.magnitude, UFloat) assert q2_u.magnitude.nominal_value == m2.value assert q2_u.magnitude.nominal_value == u2.nominal_value assert q2_u.magnitude.std_dev == m2.error assert q2_u.magnitude.std_dev == u2.std_dev if np is not None: @pytest.mark.filterwarnings("ignore::pint.UnitStrippedWarning") @pytest.mark.parametrize( "callable", [ lambda x: np.sin(x / x.units), # Issue 399 lambda x: np.cos(x / x.units), # Issue 399 np.isfinite, # Issue 481 np.shape, # Issue 509 np.size, # Issue 509 np.sqrt, # Issue 622 lambda x: x.mean(), # Issue 678 lambda x: x.copy(), # Issue 678 np.array, lambda x: x.conjugate, ], ) @pytest.mark.parametrize( "q_params", [ pytest.param((1, "m"), id="python scalar int"), pytest.param(([1, 2, 3, 4], "m"), id="array int"), pytest.param(([1], "m", 0), id="numpy scalar int"), pytest.param((1.0, "m"), id="python scalar float"), pytest.param(([1.0, 2.0, 3.0, 4.0], "m"), id="array float"), pytest.param(([1.0], "m", 0), id="numpy scalar float"), ], ) def test_issue925(module_registry, callable, q_params): # Test for immutability of type if len(q_params) == 3: q_params, el = q_params[:2], q_params[2] else: el = None q = module_registry.Quantity(*q_params) if el is not None: q = q[el] type_before = type(q._magnitude) callable(q) assert isinstance(q._magnitude, type_before) @helpers.requires_numpy def test_issue1498(tmp_path): def0 = tmp_path / "def0.txt" def1 = tmp_path / "def1.txt" def2 = tmp_path / "def2.txt" # A file that defines a new plain unit and uses it in a context def0.write_text( """ foo = [FOO] @context BAR [FOO] -> [mass]: value / foo * 10.0 kg @end """ ) # A file that defines a new plain unit, then imports another file… def1.write_text( f""" foo = [FOO] @import {def2.name} """ ) # …that, in turn, uses it in a context def2.write_text( """ @context BAR [FOO] -> [mass]: value / foo * 10.0 kg @end """ ) ureg1 = UnitRegistry() ureg1.load_definitions(def1) assert 12.0 == ureg1("1.2 foo").to("kg", "BAR").magnitude @helpers.requires_numpy def test_issue1498b(tmp_path): def0 = tmp_path / "def0.txt" def1 = tmp_path / "dir_a" / "def1.txt" def1_1 = tmp_path / "dir_a" / "def1_1.txt" def1_2 = tmp_path / "dir_a" / "def1_2.txt" def2 = tmp_path / "def2.txt" # A file that defines a new plain unit and uses it in a context def0.write_text( """ foo = [FOO] @context BAR [FOO] -> [mass]: value / foo * 10.0 kg @end @import dir_a/def1.txt @import def2.txt """ ) # A file that defines a new plain unit, then imports another file… def1.parent.mkdir() def1.write_text( """ @import def1_1.txt @import def1_2.txt """ ) def1_1.write_text( """ @context BAR1_1 [FOO] -> [mass]: value / foo * 10.0 kg @end """ ) def1_2.write_text( """ @context BAR1_2 [FOO] -> [mass]: value / foo * 10.0 kg @end """ ) # …that, in turn, uses it in a context def2.write_text( """ @context BAR2 [FOO] -> [mass]: value / foo * 10.0 kg @end """ ) # Succeeds with pint 0.18; fails with pint 0.19 ureg1 = UnitRegistry() ureg1.load_definitions(def0) # ← FAILS assert 12.0 == ureg1("1.2 foo").to("kg", "BAR").magnitude def test_backcompat_speed_velocity(func_registry): get = func_registry.get_dimensionality assert get("[velocity]") == UnitsContainer({"[length]": 1, "[time]": -1}) assert get("[speed]") == UnitsContainer({"[length]": 1, "[time]": -1}) def test_issue1433(func_registry): assert func_registry.Quantity("1 micron") == func_registry.Quantity("1 micrometer") def test_issue1527(): ureg = UnitRegistry(non_int_type=decimal.Decimal) x = ureg.parse_expression("2 microliter milligram/liter") assert x.magnitude.as_tuple()[1] == (2,) assert x.to_compact().as_tuple()[1] == (2,) assert x.to_base_units().as_tuple()[1] == (2,) assert x.to("ng").as_tuple()[1] == (2,) def test_issue1621(): ureg = UnitRegistry(non_int_type=decimal.Decimal) digits = ureg.Quantity("5.0 mV/m").to_base_units().magnitude.as_tuple()[1] assert digits == (5, 0) def test_issue1631(): import pint # Test registry subclassing class MyRegistry(pint.UnitRegistry): pass assert MyRegistry.Quantity is pint.UnitRegistry.Quantity assert MyRegistry.Unit is pint.UnitRegistry.Unit ureg = MyRegistry() u = ureg.meter assert isinstance(u, ureg.Unit) assert isinstance(u, pint.Unit) q = 2 * ureg.meter assert isinstance(q, ureg.Quantity) assert isinstance(q, pint.Quantity) def test_issue1725(registry_empty): registry_empty.define("dollar = [currency]") assert registry_empty.get_compatible_units("dollar") == set() def test_issue1505(): ur = UnitRegistry(non_int_type=decimal.Decimal) assert isinstance(ur.Quantity("1m/s").magnitude, decimal.Decimal) assert not isinstance( ur.Quantity("m/s").magnitude, float ) # unexpected success (magnitude should not be a float) assert isinstance( ur.Quantity("m/s").magnitude, decimal.Decimal ) # unexpected fail (magnitude should be a decimal) def test_issue_1845(): ur = UnitRegistry(auto_reduce_dimensions=True, non_int_type=decimal.Decimal) # before issue 1845 these inputs would have resulted in a TypeError assert ur("km / h * m").units == ur.Quantity("meter ** 2 / hour") assert ur("kW / min * W").units == ur.Quantity("watts ** 2 / minute") @pytest.mark.parametrize( "units,spec,expected", [ # (dict(hour=1, watt=1), "P~", "W·h"), (dict(ampere=1, volt=1), "P~", "V·A"), # (dict(meter=1, newton=1), "P~", "N·m"), ], ) def test_issues_1841(func_registry, units, spec, expected): ur = func_registry ur.formatter.default_sort_func = sort_by_dimensionality ur.default_format = spec value = ur.Unit(UnitsContainer(**units)) assert f"{value}" == expected @pytest.mark.xfail def test_issues_1841_xfail(): from pint import formatting as fmt from pint.delegates.formatter._compound_unit_helpers import sort_by_dimensionality # sets compact display mode by default ur = UnitRegistry() ur.default_format = "~P" ur.formatter.default_sort_func = sort_by_dimensionality q = ur.Quantity("2*pi radian * hour") # Note that `radian` (and `bit` and `count`) are treated as dimensionless. # And note that dimensionless quantities are stripped by this process, # leading to errorneous output. Suggestions? assert ( fmt.format_unit(q.u._units, spec="", registry=ur, sort_dims=True) == "radian * hour" ) assert ( fmt.format_unit(q.u._units, spec="", registry=ur, sort_dims=False) == "hour * radian" ) # this prints "2*pi hour * radian", not "2*pi radian * hour" unless sort_dims is True # print(q) def test_issue1949(registry_empty): ureg = UnitRegistry() ureg.define( "in_Hg_gauge = 3386389 * gram / metre / second ** 2; offset:101325000 = inHg_g = in_Hg_g = inHg_gauge" ) q = ureg.Quantity("1 atm").to("inHg_gauge") assert q.units == ureg.in_Hg_gauge assert_equal(q.magnitude, 0.0) @pytest.mark.parametrize( "given,expected", [ ( "8.989e9 newton * meter^2 / coulomb^2", r"\SI[]{8.989E+9}{\meter\squared\newton\per\coulomb\squared}", ), ("5 * meter / second", r"\SI[]{5}{\meter\per\second}"), ("2.2 * meter^4", r"\SI[]{2.2}{\meter\tothe{4}}"), ("2.2 * meter^-4", r"\SI[]{2.2}{\per\meter\tothe{4}}"), ], ) def test_issue1772(given, expected): ureg = UnitRegistry(non_int_type=decimal.Decimal) assert f"{ureg(given):Lx}" == expected def test_issue2017(): ureg = UnitRegistry() from pint import formatting as fmt @fmt.register_unit_format("test") def _test_format(unit, registry, **options): print("format called") proc = {u.replace("µ", "u"): e for u, e in unit.items()} return fmt.formatter( proc.items(), as_ratio=True, single_denominator=False, product_fmt="*", division_fmt="/", power_fmt="{}{}", parentheses_fmt="({})", **options, ) base_unit = ureg.microsecond assert f"{base_unit:~test}" == "us" assert f"{base_unit:test}" == "microsecond" def test_issue2007(): ureg = UnitRegistry() q = ureg.Quantity(1, "") assert f"{q:P}" == "1 dimensionless" assert f"{q:C}" == "1 dimensionless" assert f"{q:D}" == "1 dimensionless" assert f"{q:H}" == "1 dimensionless" assert f"{q:L}" == "1\\ \\mathrm{dimensionless}" # L returned '1\\ dimensionless' in pint 0.23 assert f"{q:Lx}" == "\\SI[]{1}{}" assert f"{q:~P}" == "1" assert f"{q:~C}" == "1" assert f"{q:~D}" == "1" assert f"{q:~H}" == "1" pint-0.24.4/pint/testsuite/test_log_units.py000066400000000000000000000232321471316474000212030ustar00rootroot00000000000000from __future__ import annotations import logging import math import pytest from pint import OffsetUnitCalculusError, Unit, UnitRegistry from pint.facets.plain.unit import UnitsContainer from pint.testsuite import QuantityTestCase, helpers @pytest.fixture(scope="module") def module_registry_auto_offset(): return UnitRegistry(autoconvert_offset_to_baseunit=True) # TODO: do not subclass from QuantityTestCase class TestLogarithmicQuantity(QuantityTestCase): def test_log_quantity_creation(self, caplog): # Following Quantity Creation Pattern for args in ( (4.2, "dBm"), (4.2, UnitsContainer(decibelmilliwatt=1)), (4.2, self.ureg.dBm), ): x = self.Q_(*args) assert x.magnitude == 4.2 assert x.units == UnitsContainer(decibelmilliwatt=1) x = self.Q_(self.Q_(4.2, "dBm")) assert x.magnitude == 4.2 assert x.units == UnitsContainer(decibelmilliwatt=1) x = self.Q_(4.2, UnitsContainer(decibelmilliwatt=1)) y = self.Q_(x) assert x.magnitude == y.magnitude assert x.units == y.units assert x is not y # Using multiplications for dB units requires autoconversion to baseunits new_reg = UnitRegistry(autoconvert_offset_to_baseunit=True) x = new_reg.Quantity("4.2 * dBm") assert x.magnitude == 4.2 assert x.units == UnitsContainer(decibelmilliwatt=1) with caplog.at_level(logging.DEBUG): assert "wally" not in caplog.text assert 4.2 * new_reg.dBm == new_reg.Quantity(4.2, 2 * new_reg.dBm) assert len(caplog.records) == 1 def test_log_convert(self): # # 1 dB = 1/10 * bel # helpers.assert_quantity_almost_equal(self.Q_(1.0, "dB").to("dimensionless"), self.Q_(1, "bell") / 10) # # Uncomment Bell unit in default_en.txt # ## Test dB to dB units octave - decade # 1 decade = log2(10) octave helpers.assert_quantity_almost_equal( self.Q_(1.0, "decade"), self.Q_(math.log2(10), "octave") ) # ## Test dB to dB units dBm - dBu # 0 dBm = 1mW = 1e3 uW = 30 dBu helpers.assert_quantity_almost_equal( self.Q_(0.0, "dBm"), self.Q_(29.999999999999996, "dBu"), atol=1e-7 ) # ## Test dB to dB units dBm - dBW # 0 dBW = 1W = 1e3 mW = 30 dBm helpers.assert_quantity_almost_equal( self.Q_(0.0, "dBW"), self.Q_(29.999999999999996, "dBm"), atol=1e-7 ) def test_mix_regular_log_units(self): # Test regular-logarithmic mixed definition, such as dB/km or dB/cm # Multiplications and divisions with a mix of Logarithmic Units and regular Units is normally not possible. # The reason is that dB are considered by pint like offset units. # Multiplications and divisions that involve offset units are badly defined, so pint raises an error with pytest.raises(OffsetUnitCalculusError): (-10.0 * self.ureg.dB) / (1 * self.module_registry.cm) # However, if the flag autoconvert_offset_to_baseunit=True is given to UnitRegistry, then pint converts the unit to plain. # With this flag on multiplications and divisions are now possible: new_reg = UnitRegistry(autoconvert_offset_to_baseunit=True) helpers.assert_quantity_almost_equal( -10 * new_reg.dB / new_reg.cm, 0.1 / new_reg.cm ) log_unit_names = [ "decibelwatt", "dBW", "decibelmilliwatt", "dBm", "decibelmicrowatt", "dBu", "decibel", "dB", "decade", "octave", "oct", ] @pytest.mark.parametrize("unit_name", log_unit_names) def test_unit_by_attribute(module_registry, unit_name): """Can the logarithmic units be accessed by attribute lookups?""" unit = getattr(module_registry, unit_name) assert isinstance(unit, Unit) @pytest.mark.parametrize("unit_name", log_unit_names) def test_unit_parsing(module_registry, unit_name): """Can the logarithmic units be understood by the parser?""" unit = module_registry.parse_units(unit_name) assert isinstance(unit, Unit) @pytest.mark.parametrize("mag", [1.0, 4.2]) @pytest.mark.parametrize("unit_name", log_unit_names) def test_quantity_by_constructor(module_registry, unit_name, mag): """Can Quantity() objects be constructed using logarithmic units?""" q = module_registry.Quantity(mag, unit_name) assert q.magnitude == pytest.approx(mag) assert q.units == getattr(module_registry, unit_name) @pytest.mark.parametrize("mag", [1.0, 4.2]) @pytest.mark.parametrize("unit_name", log_unit_names) def test_quantity_by_multiplication(module_registry_auto_offset, unit_name, mag): """Test that logarithmic units can be defined with multiplication Requires setting `autoconvert_offset_to_baseunit` to True """ unit = getattr(module_registry_auto_offset, unit_name) q = mag * unit assert q.magnitude == pytest.approx(mag) assert q.units == unit @pytest.mark.parametrize( "unit1,unit2", [ ("decibelwatt", "dBW"), ("decibelmilliwatt", "dBm"), ("decibelmicrowatt", "dBu"), ("decibel", "dB"), ("octave", "oct"), ], ) def test_unit_equivalence(module_registry, unit1, unit2): """Are certain pairs of units equivalent?""" assert getattr(module_registry, unit1) == getattr(module_registry, unit2) @pytest.mark.parametrize( "db_value,scalar", [ (0.0, 1.0), # 0 dB == 1x (-10.0, 0.1), # -10 dB == 0.1x (10.0, 10.0), (30.0, 1e3), (60.0, 1e6), ], ) def test_db_conversion(module_registry, db_value, scalar): """Test that a dB value can be converted to a scalar and back.""" Q_ = module_registry.Quantity assert Q_(db_value, "dB").to("dimensionless").magnitude == pytest.approx(scalar) assert Q_(scalar, "dimensionless").to("dB").magnitude == pytest.approx(db_value) @pytest.mark.parametrize( "octave,scalar", [ (2.0, 4.0), # 2 octave == 4x (1.0, 2.0), # 1 octave == 2x (0.0, 1.0), (-1.0, 0.5), (-2.0, 0.25), ], ) def test_octave_conversion(module_registry, octave, scalar): """Test that an octave can be converted to a scalar and back.""" Q_ = module_registry.Quantity assert Q_(octave, "octave").to("dimensionless").magnitude == pytest.approx(scalar) assert Q_(scalar, "dimensionless").to("octave").magnitude == pytest.approx(octave) @pytest.mark.parametrize( "decade,scalar", [ (2.0, 100.0), # 2 decades == 100x (1.0, 10.0), # 1 octave == 2x (0.0, 1.0), (-1.0, 0.1), (-2.0, 0.01), ], ) def test_decade_conversion(module_registry, decade, scalar): """Test that a decade can be converted to a scalar and back.""" Q_ = module_registry.Quantity assert Q_(decade, "decade").to("dimensionless").magnitude == pytest.approx(scalar) assert Q_(scalar, "dimensionless").to("decade").magnitude == pytest.approx(decade) @pytest.mark.parametrize( "dbm_value,mw_value", [ (0.0, 1.0), # 0.0 dBm == 1.0 mW (10.0, 10.0), (20.0, 100.0), (-10.0, 0.1), (-20.0, 0.01), ], ) def test_dbm_mw_conversion(module_registry, dbm_value, mw_value): """Test dBm values can convert to mW and back.""" Q_ = module_registry.Quantity assert Q_(dbm_value, "dBm").to("mW").magnitude == pytest.approx(mw_value) assert Q_(mw_value, "mW").to("dBm").magnitude == pytest.approx(dbm_value) @pytest.mark.xfail def test_compound_log_unit_multiply_definition(module_registry_auto_offset): """Check that compound log units can be defined using multiply.""" Q_ = module_registry_auto_offset.Quantity canonical_def = Q_(-161, "dBm") / module_registry_auto_offset.Hz mult_def = -161 * module_registry_auto_offset("dBm/Hz") assert mult_def == canonical_def @pytest.mark.xfail def test_compound_log_unit_quantity_definition(module_registry_auto_offset): """Check that compound log units can be defined using ``Quantity()``.""" Q_ = module_registry_auto_offset.Quantity canonical_def = Q_(-161, "dBm") / module_registry_auto_offset.Hz quantity_def = Q_(-161, "dBm/Hz") assert quantity_def == canonical_def def test_compound_log_unit_parse_definition(module_registry_auto_offset): Q_ = module_registry_auto_offset.Quantity canonical_def = Q_(-161, "dBm") / module_registry_auto_offset.Hz parse_def = module_registry_auto_offset("-161 dBm/Hz") assert parse_def == canonical_def def test_compound_log_unit_parse_expr(module_registry_auto_offset): """Check that compound log units can be defined using ``parse_expression()``.""" Q_ = module_registry_auto_offset.Quantity canonical_def = Q_(-161, "dBm") / module_registry_auto_offset.Hz parse_def = module_registry_auto_offset.parse_expression("-161 dBm/Hz") assert canonical_def == parse_def @pytest.mark.xfail def test_dbm_db_addition(module_registry_auto_offset): """Test a dB value can be added to a dBm and the answer is correct.""" power = (5 * module_registry_auto_offset.dBm) + ( 10 * module_registry_auto_offset.dB ) assert power.to("dBm").magnitude == pytest.approx(15) @pytest.mark.xfail @pytest.mark.parametrize( "freq1,octaves,freq2", [ (100, 2.0, 400), (50, 1.0, 100), (200, 0.0, 200), ], # noqa: E231 ) def test_frequency_octave_addition(module_registry_auto_offset, freq1, octaves, freq2): """Test an Octave can be added to a frequency correctly""" freq1 = freq1 * module_registry_auto_offset.Hz shift = octaves * module_registry_auto_offset.Octave new_freq = freq1 + shift assert new_freq.units == freq1.units assert new_freq.magnitude == pytest.approx(freq2) pint-0.24.4/pint/testsuite/test_matplotlib.py000066400000000000000000000036501471316474000213510ustar00rootroot00000000000000from __future__ import annotations import pytest from pint import UnitRegistry # Conditionally import matplotlib and NumPy plt = pytest.importorskip("matplotlib.pyplot", reason="matplotlib is not available") np = pytest.importorskip("numpy", reason="NumPy is not available") @pytest.fixture(scope="module") def local_registry(): # Set up unit registry for matplotlib ureg = UnitRegistry() ureg.setup_matplotlib(True) return ureg # Set up matplotlib plt.switch_backend("agg") @pytest.mark.mpl_image_compare(tolerance=0, remove_text=True) def test_basic_plot(local_registry): y = np.linspace(0, 30) * local_registry.miles x = np.linspace(0, 5) * local_registry.hours fig, ax = plt.subplots() ax.plot(x, y, "tab:blue") ax.axhline(26400 * local_registry.feet, color="tab:red") ax.axvline(120 * local_registry.minutes, color="tab:green") return fig @pytest.mark.mpl_image_compare(tolerance=0, remove_text=True) def test_plot_with_set_units(local_registry): y = np.linspace(0, 30) * local_registry.miles x = np.linspace(0, 5) * local_registry.hours fig, ax = plt.subplots() ax.yaxis.set_units(local_registry.inches) ax.xaxis.set_units(local_registry.seconds) ax.plot(x, y, "tab:blue") ax.axhline(26400 * local_registry.feet, color="tab:red") ax.axvline(120 * local_registry.minutes, color="tab:green") return fig @pytest.mark.mpl_image_compare(tolerance=0, remove_text=True) def test_plot_with_non_default_format(local_registry): local_registry.mpl_formatter = "{:~P}" y = np.linspace(0, 30) * local_registry.miles x = np.linspace(0, 5) * local_registry.hours fig, ax = plt.subplots() ax.yaxis.set_units(local_registry.inches) ax.xaxis.set_units(local_registry.seconds) ax.plot(x, y, "tab:blue") ax.axhline(26400 * local_registry.feet, color="tab:red") ax.axvline(120 * local_registry.minutes, color="tab:green") return fig pint-0.24.4/pint/testsuite/test_measurement.py000066400000000000000000000256761471316474000215430ustar00rootroot00000000000000from __future__ import annotations import pytest from pint import DimensionalityError from pint.testsuite import QuantityTestCase, helpers # TODO: do not subclass from QuantityTestCase @helpers.requires_not_uncertainties() class TestNotMeasurement(QuantityTestCase): def test_instantiate(self): M_ = self.ureg.Measurement with pytest.raises(RuntimeError): M_(4.0, 0.1, "s") # TODO: do not subclass from QuantityTestCase @helpers.requires_uncertainties() class TestMeasurement(QuantityTestCase): def test_simple(self): M_ = self.ureg.Measurement m = M_(4.0, 0.1, "s * s") assert repr(m) == "" def test_build(self): M_ = self.ureg.Measurement v, u = self.Q_(4.0, "s"), self.Q_(0.1, "s") M_(v.magnitude, u.magnitude, "s") ms = ( M_(v.magnitude, u.magnitude, "s"), M_(v, u.magnitude), M_(v, u), v.plus_minus(0.1), v.plus_minus(0.025, True), v.plus_minus(u), ) for m in ms: assert m.value == v assert m.error == u assert m.rel == m.error / abs(m.value) @pytest.mark.parametrize( "spec, expected", [ ("", "(4.00 +/- 0.10) second ** 2"), ("P", "(4.00 ± 0.10) second²"), ("L", r"\left(4.00 \pm 0.10\right)\ \mathrm{second}^{2}"), ("H", "(4.00 ± 0.10) second2"), ("C", "(4.00+/-0.10) second**2"), ("Lx", r"\SI{4.00 +- 0.10}{\second\squared}"), (".1f", "(4.0 +/- 0.1) second ** 2"), (".1fP", "(4.0 ± 0.1) second²"), (".1fL", r"\left(4.0 \pm 0.1\right)\ \mathrm{second}^{2}"), (".1fH", "(4.0 ± 0.1) second2"), (".1fC", "(4.0+/-0.1) second**2"), (".1fLx", r"\SI{4.0 +- 0.1}{\second\squared}"), ], ) def test_format(self, func_registry, spec, expected): Q_ = func_registry.Quantity v, u = Q_(4.0, "s ** 2"), Q_(0.1, "s ** 2") m = func_registry.Measurement(v, u) assert format(m, spec) == expected @pytest.mark.parametrize( "spec, expected", [ ("uS", "0.200(10) second ** 2"), (".3uS", "0.2000(100) second ** 2"), (".3uSP", "0.2000(100) second²"), (".3uSL", r"0.2000\left(100\right)\ \mathrm{second}^{2}"), (".3uSH", "0.2000(100) second2"), (".3uSC", "0.2000(100) second**2"), ], ) def test_format_paru(self, func_registry, spec, expected): Q_ = func_registry.Quantity v, u = Q_(0.20, "s ** 2"), Q_(0.01, "s ** 2") m = func_registry.Measurement(v, u) assert format(m, spec) == expected @pytest.mark.parametrize( "spec, expected", [ (".3u", "(0.2000 +/- 0.0100) second ** 2"), (".3uP", "(0.2000 ± 0.0100) second²"), (".3uL", r"\left(0.2000 \pm 0.0100\right)\ \mathrm{second}^{2}"), (".3uH", "(0.2000 ± 0.0100) second2"), (".3uC", "(0.2000+/-0.0100) second**2"), ( ".3uLx", r"\SI{0.2000 +- 0.0100}{\second\squared}", ), (".1uLx", r"\SI{0.20 +- 0.01}{\second\squared}"), ], ) def test_format_u(self, func_registry, spec, expected): Q_ = func_registry.Quantity v, u = Q_(0.20, "s ** 2"), Q_(0.01, "s ** 2") m = func_registry.Measurement(v, u) assert format(m, spec) == expected @pytest.mark.parametrize( "spec, expected", [ (".1u%", "(20 +/- 1)% second ** 2"), (".1u%P", "(20 ± 1)% second²"), (".1u%L", r"\left(20 \pm 1\right) \%\ \mathrm{second}^{2}"), (".1u%H", "(20 ± 1)% second2"), (".1u%C", "(20+/-1)% second**2"), ], ) def test_format_percu(self, func_registry, spec, expected): Q_ = func_registry.Quantity v, u = Q_(0.20, "s ** 2"), Q_(0.01, "s ** 2") m = func_registry.Measurement(v, u) assert format(m, spec) == expected @pytest.mark.parametrize( "spec, expected", [ (".1ue", "(2.0 +/- 0.1)e-01 second ** 2"), (".1ueP", "(2.0 ± 0.1)×10⁻¹ second²"), ( ".1ueL", r"\left(2.0 \pm 0.1\right) \times 10^{-1}\ \mathrm{second}^{2}", ), (".1ueH", "(2.0 ± 0.1)×10-1 second2"), (".1ueC", "(2.0+/-0.1)e-01 second**2"), ], ) def test_format_perce(self, func_registry, spec, expected): Q_ = func_registry.Quantity v, u = Q_(0.20, "s ** 2"), Q_(0.01, "s ** 2") m = func_registry.Measurement(v, u) assert format(m, spec) == expected @pytest.mark.parametrize( "spec, expected", [ ("", "(4.00 +/- 0.10)e+20 second ** 2"), # ("!r", ""), ("P", "(4.00 ± 0.10)×10²⁰ second²"), ("L", r"\left(4.00 \pm 0.10\right) \times 10^{20}\ \mathrm{second}^{2}"), ("H", "(4.00 ± 0.10)×1020 second2"), ("C", "(4.00+/-0.10)e+20 second**2"), ("Lx", r"\SI{4.00 +- 0.10 e+20}{\second\squared}"), ], ) def test_format_exponential_pos(self, func_registry, spec, expected): # Quantities in exponential format come with their own parenthesis, don't wrap # them twice m = func_registry.Quantity(4e20, "s^2").plus_minus(1e19) assert format(m, spec) == expected @pytest.mark.parametrize( "spec, expected", [ ("", "(4.00 +/- 0.10)e-20 second ** 2"), # ("!r", ""), ("P", "(4.00 ± 0.10)×10⁻²⁰ second²"), ( "L", r"\left(4.00 \pm 0.10\right) \times 10^{-20}\ \mathrm{second}^{2}", ), ("H", "(4.00 ± 0.10)×10-20 second2"), ("C", "(4.00+/-0.10)e-20 second**2"), ("Lx", r"\SI{4.00 +- 0.10 e-20}{\second\squared}"), ], ) def test_format_exponential_neg(self, func_registry, spec, expected): m = func_registry.Quantity(4e-20, "s^2").plus_minus(1e-21) assert format(m, spec) == expected @pytest.mark.parametrize( "spec, expected", [ ("", "(4.00 +/- 0.10) second ** 2"), ("P", "(4.00 ± 0.10) second²"), ("L", r"\left(4.00 \pm 0.10\right)\ \mathrm{second}^{2}"), ("H", "(4.00 ± 0.10) second2"), ("C", "(4.00+/-0.10) second**2"), ("Lx", r"\SI{4.00 +- 0.10}{\second\squared}"), (".1f", "(4.0 +/- 0.1) second ** 2"), (".1fP", "(4.0 ± 0.1) second²"), (".1fL", r"\left(4.0 \pm 0.1\right)\ \mathrm{second}^{2}"), (".1fH", "(4.0 ± 0.1) second2"), (".1fC", "(4.0+/-0.1) second**2"), (".1fLx", r"\SI{4.0 +- 0.1}{\second\squared}"), ], ) def test_format_default(self, func_registry, spec, expected): v, u = ( func_registry.Quantity(4.0, "s ** 2"), func_registry.Quantity(0.1, "s ** 2"), ) m = func_registry.Measurement(v, u) func_registry.default_format = spec assert f"{m}" == expected def test_raise_build(self): v, u = self.Q_(1.0, "s"), self.Q_(0.1, "s") o = self.Q_(0.1, "m") M_ = self.ureg.Measurement with pytest.raises(DimensionalityError): M_(v, o) with pytest.raises(DimensionalityError): v.plus_minus(o) with pytest.raises(ValueError): v.plus_minus(u, relative=True) def test_propagate_linear(self): v1, u1 = self.Q_(8.0, "s"), self.Q_(0.7, "s") v2, u2 = self.Q_(5.0, "s"), self.Q_(0.6, "s") v2, u3 = self.Q_(-5.0, "s"), self.Q_(0.6, "s") m1 = v1.plus_minus(u1) m2 = v2.plus_minus(u2) m3 = v2.plus_minus(u3) for factor, m in zip((3, -3, 3, -3), (m1, m3, m1, m3)): r = factor * m helpers.assert_quantity_almost_equal( r.value.magnitude, factor * m.value.magnitude ) helpers.assert_quantity_almost_equal( r.error.magnitude, abs(factor * m.error.magnitude) ) assert r.value.units == m.value.units for ml, mr in zip((m1, m1, m1, m3), (m1, m2, m3, m3)): r = ml + mr helpers.assert_quantity_almost_equal( r.value.magnitude, ml.value.magnitude + mr.value.magnitude ) helpers.assert_quantity_almost_equal( r.error.magnitude, ( ml.error.magnitude + mr.error.magnitude if ml is mr else (ml.error.magnitude**2 + mr.error.magnitude**2) ** 0.5 ), ) assert r.value.units == ml.value.units for ml, mr in zip((m1, m1, m1, m3), (m1, m2, m3, m3)): r = ml - mr helpers.assert_quantity_almost_equal( r.value.magnitude, ml.value.magnitude - mr.value.magnitude ) helpers.assert_quantity_almost_equal( r.error.magnitude, 0 if ml is mr else (ml.error.magnitude**2 + mr.error.magnitude**2) ** 0.5, ) assert r.value.units == ml.value.units def test_propagate_product(self): v1, u1 = self.Q_(8.0, "s"), self.Q_(0.7, "s") v2, u2 = self.Q_(5.0, "s"), self.Q_(0.6, "s") v2, u3 = self.Q_(-5.0, "s"), self.Q_(0.6, "s") m1 = v1.plus_minus(u1) m2 = v2.plus_minus(u2) m3 = v2.plus_minus(u3) m4 = (2.3 * self.ureg.meter).plus_minus(0.1) m5 = (1.4 * self.ureg.meter).plus_minus(0.2) for ml, mr in zip((m1, m1, m1, m3, m4), (m1, m2, m3, m3, m5)): r = ml * mr helpers.assert_quantity_almost_equal( r.value.magnitude, ml.value.magnitude * mr.value.magnitude ) assert r.value.units == ml.value.units * mr.value.units for ml, mr in zip((m1, m1, m1, m3, m4), (m1, m2, m3, m3, m5)): r = ml / mr helpers.assert_quantity_almost_equal( r.value.magnitude, ml.value.magnitude / mr.value.magnitude ) assert r.value.units == ml.value.units / mr.value.units def test_measurement_comparison(self): x = self.Q_(4.2, "meter") y = self.Q_(5.0, "meter").plus_minus(0.1) assert x <= y assert not (x >= y) def test_tokenization(self): from pint import pint_eval pint_eval.tokenizer = pint_eval.uncertainty_tokenizer for p in pint_eval.tokenizer("8 + / - 4"): print(p) assert True pint-0.24.4/pint/testsuite/test_non_int.py000066400000000000000000001360221471316474000206460ustar00rootroot00000000000000from __future__ import annotations import copy import math import operator as op import pickle from decimal import Decimal from fractions import Fraction import pytest from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry from pint.facets.plain.unit import UnitsContainer from pint.testsuite import QuantityTestCase, helpers # TODO: do not subclass from QuantityTestCase class NonIntTypeTestCase(QuantityTestCase): def assert_quantity_almost_equal( self, first, second, rtol="1e-07", atol="0", msg=None ): if isinstance(first, self.Q_): assert isinstance(first.m, (self.kwargs["non_int_type"], int)) else: assert isinstance(first, (self.kwargs["non_int_type"], int)) if isinstance(second, self.Q_): assert isinstance(second.m, (self.kwargs["non_int_type"], int)) else: assert isinstance(second, (self.kwargs["non_int_type"], int)) helpers.assert_quantity_almost_equal( first, second, self.kwargs["non_int_type"](rtol), self.kwargs["non_int_type"](atol), msg, ) def QP_(self, value, units): assert isinstance(value, str) return self.Q_(self.kwargs["non_int_type"](value), units) class _TestBasic(NonIntTypeTestCase): def test_quantity_creation(self, caplog): value = self.kwargs["non_int_type"]("4.2") for args in ( (value, "meter"), (value, UnitsContainer(meter=1)), (value, self.ureg.meter), ("4.2*meter",), ("4.2/meter**(-1)",), (self.Q_(value, "meter"),), ): x = self.Q_(*args) assert x.magnitude == value assert x.units == self.ureg.UnitsContainer(meter=1) x = self.Q_(value, UnitsContainer(length=1)) y = self.Q_(x) assert x.magnitude == y.magnitude assert x.units == y.units assert x is not y x = self.Q_(value, None) assert x.magnitude == value assert x.units == UnitsContainer() caplog.clear() assert value * self.ureg.meter == self.Q_( value, self.kwargs["non_int_type"]("2") * self.ureg.meter ) assert len(caplog.records) == 1 assert ( caplog.records[0].message == "Creating new PlainQuantity using a non unity PlainQuantity as units." ) def test_nan_creation(self): if self.SUPPORTS_NAN: value = self.kwargs["non_int_type"]("nan") for args in ( (value, "meter"), (value, UnitsContainer(meter=1)), (value, self.ureg.meter), ("NaN*meter",), ("nan/meter**(-1)",), (self.Q_(value, "meter"),), ): x = self.Q_(*args) assert math.isnan(x.magnitude) assert type(x.magnitude) == self.kwargs["non_int_type"] assert x.units == self.ureg.UnitsContainer(meter=1) else: with pytest.raises(ValueError): self.Q_("NaN meters") def test_quantity_comparison(self): x = self.QP_("4.2", "meter") y = self.QP_("4.2", "meter") z = self.QP_("5", "meter") j = self.QP_("5", "meter*meter") # identity for single object assert x == x assert not (x != x) # identity for multiple objects with same value assert x == y assert not (x != y) assert x <= y assert x >= y assert not (x < y) assert not (x > y) assert not (x == z) assert x != z assert x < z assert z != j assert z != j assert self.QP_("0", "meter") == self.QP_("0", "centimeter") assert self.QP_("0", "meter") != self.QP_("0", "second") assert self.QP_("10", "meter") < self.QP_("5", "kilometer") def test_quantity_comparison_convert(self): assert self.QP_("1000", "millimeter") == self.QP_("1", "meter") assert self.QP_("1000", "millimeter/min") == self.Q_( self.kwargs["non_int_type"]("1000") / self.kwargs["non_int_type"]("60"), "millimeter/s", ) def test_quantity_hash(self): x = self.QP_("4.2", "meter") x2 = self.QP_("4200", "millimeter") y = self.QP_("2", "second") z = self.QP_("0.5", "hertz") assert hash(x) == hash(x2) # Dimensionless equality assert hash(y * z) == hash(1.0) # Dimensionless equality from a different unit registry ureg2 = UnitRegistry() y2 = ureg2.Quantity(self.kwargs["non_int_type"]("2"), "second") z2 = ureg2.Quantity(self.kwargs["non_int_type"]("0.5"), "hertz") assert hash(y * z) == hash(y2 * z2) def test_to_base_units(self): x = self.Q_("1*inch") self.assert_quantity_almost_equal( x.to_base_units(), self.QP_("0.0254", "meter") ) x = self.Q_("1*inch*inch") self.assert_quantity_almost_equal( x.to_base_units(), self.Q_( self.kwargs["non_int_type"]("0.0254") ** self.kwargs["non_int_type"]("2.0"), "meter*meter", ), ) x = self.Q_("1*inch/minute") self.assert_quantity_almost_equal( x.to_base_units(), self.Q_( self.kwargs["non_int_type"]("0.0254") / self.kwargs["non_int_type"]("60"), "meter/second", ), ) def test_convert(self): self.assert_quantity_almost_equal( self.Q_("2 inch").to("meter"), self.Q_( self.kwargs["non_int_type"]("2") * self.kwargs["non_int_type"]("0.0254"), "meter", ), ) self.assert_quantity_almost_equal( self.Q_("2 meter").to("inch"), self.Q_( self.kwargs["non_int_type"]("2") / self.kwargs["non_int_type"]("0.0254"), "inch", ), ) self.assert_quantity_almost_equal( self.Q_("2 sidereal_year").to("second"), self.QP_("63116297.5325", "second") ) self.assert_quantity_almost_equal( self.Q_("2.54 centimeter/second").to("inch/second"), self.Q_("1 inch/second"), ) assert round(abs(self.Q_("2.54 centimeter").to("inch").magnitude - 1), 7) == 0 assert ( round(abs(self.Q_("2 second").to("millisecond").magnitude - 2000), 7) == 0 ) def test_convert_from(self): x = self.Q_("2*inch") meter = self.ureg.meter # from quantity self.assert_quantity_almost_equal( meter.from_(x), self.Q_( self.kwargs["non_int_type"]("2") * self.kwargs["non_int_type"]("0.0254"), "meter", ), ) self.assert_quantity_almost_equal( meter.m_from(x), self.kwargs["non_int_type"]("2") * self.kwargs["non_int_type"]("0.0254"), ) # from unit self.assert_quantity_almost_equal( meter.from_(self.ureg.inch), self.QP_("0.0254", "meter") ) self.assert_quantity_almost_equal( meter.m_from(self.ureg.inch), self.kwargs["non_int_type"]("0.0254") ) # from number self.assert_quantity_almost_equal( meter.from_(2, strict=False), self.QP_("2", "meter") ) self.assert_quantity_almost_equal( meter.m_from(self.kwargs["non_int_type"]("2"), strict=False), self.kwargs["non_int_type"]("2"), ) # from number (strict mode) with pytest.raises(ValueError): meter.from_(self.kwargs["non_int_type"]("2")) with pytest.raises(ValueError): meter.m_from(self.kwargs["non_int_type"]("2")) def test_context_attr(self): assert self.ureg.meter == self.QP_("1", "meter") def test_both_symbol(self): assert self.QP_("2", "ms") == self.QP_("2", "millisecond") assert self.QP_("2", "cm") == self.QP_("2", "centimeter") def test_dimensionless_units(self): twopi = self.kwargs["non_int_type"]("2") * self.ureg.pi assert ( round(abs(self.QP_("360", "degree").to("radian").magnitude - twopi), 7) == 0 ) assert round(abs(self.Q_(twopi, "radian") - self.QP_("360", "degree")), 7) == 0 assert self.QP_("1", "radian").dimensionality == UnitsContainer() assert self.QP_("1", "radian").dimensionless assert not self.QP_("1", "radian").unitless assert self.QP_("1", "meter") / self.QP_("1", "meter") == 1 assert (self.QP_("1", "meter") / self.QP_("1", "mm")).to("") == 1000 assert self.Q_(10) // self.QP_("360", "degree") == 1 assert self.QP_("400", "degree") // self.Q_(twopi) == 1 assert self.QP_("400", "degree") // twopi == 1 assert 7 // self.QP_("360", "degree") == 1 def test_offset(self): self.assert_quantity_almost_equal( self.QP_("0", "kelvin").to("kelvin"), self.QP_("0", "kelvin") ) self.assert_quantity_almost_equal( self.QP_("0", "degC").to("kelvin"), self.QP_("273.15", "kelvin") ) self.assert_quantity_almost_equal( self.QP_("0", "degF").to("kelvin"), self.QP_("255.372222", "kelvin"), rtol=0.01, ) self.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("kelvin"), self.QP_("100", "kelvin") ) self.assert_quantity_almost_equal( self.QP_("100", "degC").to("kelvin"), self.QP_("373.15", "kelvin") ) self.assert_quantity_almost_equal( self.QP_("100", "degF").to("kelvin"), self.QP_("310.92777777", "kelvin"), rtol=0.01, ) self.assert_quantity_almost_equal( self.QP_("0", "kelvin").to("degC"), self.QP_("-273.15", "degC") ) self.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("degC"), self.QP_("-173.15", "degC") ) self.assert_quantity_almost_equal( self.QP_("0", "kelvin").to("degF"), self.QP_("-459.67", "degF"), rtol=0.01 ) self.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("degF"), self.QP_("-279.67", "degF"), rtol=0.01 ) self.assert_quantity_almost_equal( self.QP_("32", "degF").to("degC"), self.QP_("0", "degC"), atol=0.01 ) self.assert_quantity_almost_equal( self.QP_("100", "degC").to("degF"), self.QP_("212", "degF"), atol=0.01 ) self.assert_quantity_almost_equal( self.QP_("54", "degF").to("degC"), self.QP_("12.2222", "degC"), atol=0.01 ) self.assert_quantity_almost_equal( self.QP_("12", "degC").to("degF"), self.QP_("53.6", "degF"), atol=0.01 ) self.assert_quantity_almost_equal( self.QP_("12", "kelvin").to("degC"), self.QP_("-261.15", "degC"), atol=0.01 ) self.assert_quantity_almost_equal( self.QP_("12", "degC").to("kelvin"), self.QP_("285.15", "kelvin"), atol=0.01 ) self.assert_quantity_almost_equal( self.QP_("12", "kelvin").to("degR"), self.QP_("21.6", "degR"), atol=0.01 ) self.assert_quantity_almost_equal( self.QP_("12", "degR").to("kelvin"), self.QP_("6.66666667", "kelvin"), atol=0.01, ) self.assert_quantity_almost_equal( self.QP_("12", "degC").to("degR"), self.QP_("513.27", "degR"), atol=0.01 ) self.assert_quantity_almost_equal( self.QP_("12", "degR").to("degC"), self.QP_("-266.483333", "degC"), atol=0.01, ) def test_offset_delta(self): self.assert_quantity_almost_equal( self.QP_("0", "delta_degC").to("kelvin"), self.QP_("0", "kelvin") ) self.assert_quantity_almost_equal( self.QP_("0", "delta_degF").to("kelvin"), self.QP_("0", "kelvin"), rtol=0.01 ) self.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("delta_degC"), self.QP_("100", "delta_degC") ) self.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("delta_degF"), self.QP_("180", "delta_degF"), rtol=0.01, ) self.assert_quantity_almost_equal( self.QP_("100", "delta_degF").to("kelvin"), self.QP_("55.55555556", "kelvin"), rtol=0.01, ) self.assert_quantity_almost_equal( self.QP_("100", "delta_degC").to("delta_degF"), self.QP_("180", "delta_degF"), rtol=0.01, ) self.assert_quantity_almost_equal( self.QP_("100", "delta_degF").to("delta_degC"), self.QP_("55.55555556", "delta_degC"), rtol=0.01, ) self.assert_quantity_almost_equal( self.QP_("12.3", "delta_degC").to("delta_degF"), self.QP_("22.14", "delta_degF"), rtol=0.01, ) def test_pickle(self, subtests): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): for magnitude, unit in ( ("32", ""), ("2.4", ""), ("32", "m/s"), ("2.4", "m/s"), ): with subtests.test(protocol=protocol, magnitude=magnitude, unit=unit): q1 = self.QP_(magnitude, unit) q2 = pickle.loads(pickle.dumps(q1, protocol)) assert q1 == q2 def test_notiter(self): # Verify that iter() crashes immediately, without needing to draw any # element from it, if the magnitude isn't iterable x = self.QP_("1", "m") with pytest.raises(TypeError): iter(x) class _TestQuantityBasicMath(NonIntTypeTestCase): def _test_inplace(self, operator, value1, value2, expected_result, unit=None): if isinstance(value1, str): value1 = self.Q_(value1) if isinstance(value2, str): value2 = self.Q_(value2) if isinstance(expected_result, str): expected_result = self.Q_(expected_result) if unit is not None: value1 = value1 * unit value2 = value2 * unit expected_result = expected_result * unit value1 = copy.copy(value1) value2 = copy.copy(value2) id1 = id(value1) id2 = id(value2) value1 = operator(value1, value2) value2_cpy = copy.copy(value2) self.assert_quantity_almost_equal(value1, expected_result) assert id1 == id(value1) self.assert_quantity_almost_equal(value2, value2_cpy) assert id2 == id(value2) def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None): if isinstance(value1, str): value1 = self.Q_(value1) if isinstance(value2, str): value2 = self.Q_(value2) if isinstance(expected_result, str): expected_result = self.Q_(expected_result) if unit is not None: value1 = value1 * unit value2 = value2 * unit expected_result = expected_result * unit id1 = id(value1) id2 = id(value2) value1_cpy = copy.copy(value1) value2_cpy = copy.copy(value2) result = operator(value1, value2) self.assert_quantity_almost_equal(expected_result, result) self.assert_quantity_almost_equal(value1, value1_cpy) self.assert_quantity_almost_equal(value2, value2_cpy) assert id(result) != id1 assert id(result) != id2 def _test_quantity_add_sub(self, unit, func): x = self.Q_(unit, "centimeter") y = self.Q_(unit, "inch") z = self.Q_(unit, "second") a = self.Q_(unit, None) func(op.add, x, x, self.Q_(unit + unit, "centimeter")) func( op.add, x, y, self.Q_(unit + self.kwargs["non_int_type"]("2.54") * unit, "centimeter"), ) func( op.add, y, x, self.Q_(unit + unit / (self.kwargs["non_int_type"]("2.54") * unit), "inch"), ) func(op.add, a, unit, self.Q_(unit + unit, None)) with pytest.raises(DimensionalityError): op.add(self.kwargs["non_int_type"]("10"), x) with pytest.raises(DimensionalityError): op.add(x, self.kwargs["non_int_type"]("10")) with pytest.raises(DimensionalityError): op.add(x, z) func(op.sub, x, x, self.Q_(unit - unit, "centimeter")) func( op.sub, x, y, self.Q_(unit - self.kwargs["non_int_type"]("2.54") * unit, "centimeter"), ) func( op.sub, y, x, self.Q_(unit - unit / (self.kwargs["non_int_type"]("2.54") * unit), "inch"), ) func(op.sub, a, unit, self.Q_(unit - unit, None)) with pytest.raises(DimensionalityError): op.sub(self.kwargs["non_int_type"]("10"), x) with pytest.raises(DimensionalityError): op.sub(x, self.kwargs["non_int_type"]("10")) with pytest.raises(DimensionalityError): op.sub(x, z) def _test_quantity_iadd_isub(self, unit, func): x = self.Q_(unit, "centimeter") y = self.Q_(unit, "inch") z = self.Q_(unit, "second") a = self.Q_(unit, None) func(op.iadd, x, x, self.Q_(unit + unit, "centimeter")) func( op.iadd, x, y, self.Q_(unit + self.kwargs["non_int_type"]("2.54") * unit, "centimeter"), ) func( op.iadd, y, x, self.Q_(unit + unit / self.kwargs["non_int_type"]("2.54"), "inch"), ) func(op.iadd, a, unit, self.Q_(unit + unit, None)) with pytest.raises(DimensionalityError): op.iadd(self.kwargs["non_int_type"]("10"), x) with pytest.raises(DimensionalityError): op.iadd(x, self.kwargs["non_int_type"]("10")) with pytest.raises(DimensionalityError): op.iadd(x, z) func(op.isub, x, x, self.Q_(unit - unit, "centimeter")) func( op.isub, x, y, self.Q_(unit - self.kwargs["non_int_type"]("2.54"), "centimeter"), ) func( op.isub, y, x, self.Q_(unit - unit / self.kwargs["non_int_type"]("2.54"), "inch"), ) func(op.isub, a, unit, self.Q_(unit - unit, None)) with pytest.raises(DimensionalityError): op.sub(self.kwargs["non_int_type"]("10"), x) with pytest.raises(DimensionalityError): op.sub(x, self.kwargs["non_int_type"]("10")) with pytest.raises(DimensionalityError): op.sub(x, z) def _test_quantity_mul_div(self, unit, func): func( op.mul, unit * self.kwargs["non_int_type"]("10"), "4.2*meter", "42*meter", unit, ) func( op.mul, "4.2*meter", unit * self.kwargs["non_int_type"]("10"), "42*meter", unit, ) func(op.mul, "4.2*meter", "10*inch", "42*meter*inch", unit) func( op.truediv, unit * self.kwargs["non_int_type"]("42"), "4.2*meter", "10/meter", unit, ) func( op.truediv, "4.2*meter", unit * self.kwargs["non_int_type"]("10"), "0.42*meter", unit, ) func(op.truediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit) def _test_quantity_imul_idiv(self, unit, func): # func(op.imul, 10.0, '4.2*meter', '42*meter') func(op.imul, "4.2*meter", self.kwargs["non_int_type"]("10"), "42*meter", unit) func(op.imul, "4.2*meter", "10*inch", "42*meter*inch", unit) # func(op.truediv, 42, '4.2*meter', '10/meter') func( op.itruediv, "4.2*meter", unit * self.kwargs["non_int_type"]("10"), "0.42*meter", unit, ) func(op.itruediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit) def _test_quantity_floordiv(self, unit, func): a = self.Q_("10*meter") b = self.Q_("3*second") with pytest.raises(DimensionalityError): op.floordiv(a, b) with pytest.raises(DimensionalityError): op.floordiv(self.kwargs["non_int_type"]("3"), b) with pytest.raises(DimensionalityError): op.floordiv(a, self.kwargs["non_int_type"]("3")) with pytest.raises(DimensionalityError): op.ifloordiv(a, b) with pytest.raises(DimensionalityError): op.ifloordiv(self.kwargs["non_int_type"]("3"), b) with pytest.raises(DimensionalityError): op.ifloordiv(a, self.kwargs["non_int_type"]("3")) func( op.floordiv, unit * self.kwargs["non_int_type"]("10"), "4.2*meter/meter", self.kwargs["non_int_type"]("2"), unit, ) func( op.floordiv, "10*meter", "4.2*inch", self.kwargs["non_int_type"]("93"), unit ) def _test_quantity_mod(self, unit, func): a = self.Q_("10*meter") b = self.Q_("3*second") with pytest.raises(DimensionalityError): op.mod(a, b) with pytest.raises(DimensionalityError): op.mod(3, b) with pytest.raises(DimensionalityError): op.mod(a, 3) with pytest.raises(DimensionalityError): op.imod(a, b) with pytest.raises(DimensionalityError): op.imod(3, b) with pytest.raises(DimensionalityError): op.imod(a, 3) func( op.mod, unit * self.kwargs["non_int_type"]("10"), "4.2*meter/meter", self.kwargs["non_int_type"]("1.6"), unit, ) def _test_quantity_ifloordiv(self, unit, func): func( op.ifloordiv, self.kwargs["non_int_type"]("10"), "4.2*meter/meter", self.kwargs["non_int_type"]("2"), unit, ) func( op.ifloordiv, "10*meter", "4.2*inch", self.kwargs["non_int_type"]("93"), unit, ) def _test_quantity_divmod_one(self, a, b): if isinstance(a, str): a = self.Q_(a) if isinstance(b, str): b = self.Q_(b) q, r = divmod(a, b) assert q == a // b assert r == a % b helpers.assert_quantity_equal(a, (q * b) + r) assert q == math.floor(q) if b > (0 * b): assert (0 * b) <= r < b else: assert (0 * b) >= r > b if isinstance(a, self.Q_): assert r.units == a.units else: assert r.unitless assert q.unitless copy_a = copy.copy(a) a %= b assert a == r copy_a //= b assert copy_a == q def _test_quantity_divmod(self): self._test_quantity_divmod_one("10*meter", "4.2*inch") # Disabling these tests as it yields different results without Quantities # >>> from decimal import Decimal as D # >>> divmod(-D('100'), D('3')) # (Decimal('-33'), Decimal('-1')) # >>> divmod(-100, 3) # (-34, 2) # self._test_quantity_divmod_one("-10*meter", "4.2*inch") # self._test_quantity_divmod_one("-10*meter", "-4.2*inch") # self._test_quantity_divmod_one("10*meter", "-4.2*inch") self._test_quantity_divmod_one("400*degree", "3") self._test_quantity_divmod_one("4", "180 degree") self._test_quantity_divmod_one(4, "180 degree") self._test_quantity_divmod_one("20", 4) self._test_quantity_divmod_one("300*degree", "100 degree") a = self.Q_("10*meter") b = self.Q_("3*second") with pytest.raises(DimensionalityError): divmod(a, b) with pytest.raises(DimensionalityError): divmod(3, b) with pytest.raises(DimensionalityError): divmod(a, 3) def _test_numeric(self, unit, ifunc): self._test_quantity_add_sub(unit, self._test_not_inplace) self._test_quantity_iadd_isub(unit, ifunc) self._test_quantity_mul_div(unit, self._test_not_inplace) self._test_quantity_imul_idiv(unit, ifunc) self._test_quantity_floordiv(unit, self._test_not_inplace) self._test_quantity_mod(unit, self._test_not_inplace) self._test_quantity_divmod() # self._test_quantity_ifloordiv(unit, ifunc) def test_quantity_abs_round(self): value = self.kwargs["non_int_type"]("4.2") x = self.Q_(-value, "meter") y = self.Q_(value, "meter") for fun in (abs, round, op.pos, op.neg): zx = self.Q_(fun(x.magnitude), "meter") zy = self.Q_(fun(y.magnitude), "meter") rx = fun(x) ry = fun(y) assert rx == zx, f"while testing {fun}" assert ry == zy, f"while testing {fun}" assert rx is not zx, f"while testing {fun}" assert ry is not zy, f"while testing {fun}" def test_quantity_float_complex(self): x = self.QP_("-4.2", None) y = self.QP_("4.2", None) z = self.QP_("1", "meter") for fun in (float, complex): assert fun(x) == fun(x.magnitude) assert fun(y) == fun(y.magnitude) with pytest.raises(DimensionalityError): fun(z) def test_not_inplace(self): self._test_numeric(self.kwargs["non_int_type"]("1.0"), self._test_not_inplace) class _TestOffsetUnitMath(NonIntTypeTestCase): @classmethod def setup_class(cls): super().setup_class() cls.ureg.autoconvert_offset_to_baseunit = False cls.ureg.default_as_delta = True additions = [ # --- input tuple -------------------- | -- expected result -- ((("100", "kelvin"), ("10", "kelvin")), ("110", "kelvin")), ((("100", "kelvin"), ("10", "degC")), "error"), ((("100", "kelvin"), ("10", "degF")), "error"), ((("100", "kelvin"), ("10", "degR")), ("105.56", "kelvin")), ((("100", "kelvin"), ("10", "delta_degC")), ("110", "kelvin")), ((("100", "kelvin"), ("10", "delta_degF")), ("105.56", "kelvin")), ((("100", "degC"), ("10", "kelvin")), "error"), ((("100", "degC"), ("10", "degC")), "error"), ((("100", "degC"), ("10", "degF")), "error"), ((("100", "degC"), ("10", "degR")), "error"), ((("100", "degC"), ("10", "delta_degC")), ("110", "degC")), ((("100", "degC"), ("10", "delta_degF")), ("105.56", "degC")), ((("100", "degF"), ("10", "kelvin")), "error"), ((("100", "degF"), ("10", "degC")), "error"), ((("100", "degF"), ("10", "degF")), "error"), ((("100", "degF"), ("10", "degR")), "error"), ((("100", "degF"), ("10", "delta_degC")), ("118", "degF")), ((("100", "degF"), ("10", "delta_degF")), ("110", "degF")), ((("100", "degR"), ("10", "kelvin")), ("118", "degR")), ((("100", "degR"), ("10", "degC")), "error"), ((("100", "degR"), ("10", "degF")), "error"), ((("100", "degR"), ("10", "degR")), ("110", "degR")), ((("100", "degR"), ("10", "delta_degC")), ("118", "degR")), ((("100", "degR"), ("10", "delta_degF")), ("110", "degR")), ((("100", "delta_degC"), ("10", "kelvin")), ("110", "kelvin")), ((("100", "delta_degC"), ("10", "degC")), ("110", "degC")), ((("100", "delta_degC"), ("10", "degF")), ("190", "degF")), ((("100", "delta_degC"), ("10", "degR")), ("190", "degR")), ((("100", "delta_degC"), ("10", "delta_degC")), ("110", "delta_degC")), ((("100", "delta_degC"), ("10", "delta_degF")), ("105.56", "delta_degC")), ((("100", "delta_degF"), ("10", "kelvin")), ("65.56", "kelvin")), ((("100", "delta_degF"), ("10", "degC")), ("65.56", "degC")), ((("100", "delta_degF"), ("10", "degF")), ("110", "degF")), ((("100", "delta_degF"), ("10", "degR")), ("110", "degR")), ((("100", "delta_degF"), ("10", "delta_degC")), ("118", "delta_degF")), ((("100", "delta_degF"), ("10", "delta_degF")), ("110", "delta_degF")), ] @pytest.mark.parametrize(("input_tuple", "expected_output"), additions) def test_addition(self, input_tuple, expected_output): self.ureg.autoconvert_offset_to_baseunit = False qin1, qin2 = input_tuple q1, q2 = self.QP_(*qin1), self.QP_(*qin2) # update input tuple with new values to have correct values on failure input_tuple = q1, q2 if expected_output == "error": with pytest.raises(OffsetUnitCalculusError): op.add(q1, q2) else: expected_output = self.QP_(*expected_output) assert op.add(q1, q2).units == expected_output.units self.assert_quantity_almost_equal( op.add(q1, q2), expected_output, atol="0.01" ) subtractions = [ ((("100", "kelvin"), ("10", "kelvin")), ("90", "kelvin")), ((("100", "kelvin"), ("10", "degC")), ("-183.15", "kelvin")), ((("100", "kelvin"), ("10", "degF")), ("-160.93", "kelvin")), ((("100", "kelvin"), ("10", "degR")), ("94.44", "kelvin")), ((("100", "kelvin"), ("10", "delta_degC")), ("90", "kelvin")), ((("100", "kelvin"), ("10", "delta_degF")), ("94.44", "kelvin")), ((("100", "degC"), ("10", "kelvin")), ("363.15", "delta_degC")), ((("100", "degC"), ("10", "degC")), ("90", "delta_degC")), ((("100", "degC"), ("10", "degF")), ("112.22", "delta_degC")), ((("100", "degC"), ("10", "degR")), ("367.59", "delta_degC")), ((("100", "degC"), ("10", "delta_degC")), ("90", "degC")), ((("100", "degC"), ("10", "delta_degF")), ("94.44", "degC")), ((("100", "degF"), ("10", "kelvin")), ("541.67", "delta_degF")), ((("100", "degF"), ("10", "degC")), ("50", "delta_degF")), ((("100", "degF"), ("10", "degF")), ("90", "delta_degF")), ((("100", "degF"), ("10", "degR")), ("549.67", "delta_degF")), ((("100", "degF"), ("10", "delta_degC")), ("82", "degF")), ((("100", "degF"), ("10", "delta_degF")), ("90", "degF")), ((("100", "degR"), ("10", "kelvin")), ("82", "degR")), ((("100", "degR"), ("10", "degC")), ("-409.67", "degR")), ((("100", "degR"), ("10", "degF")), ("-369.67", "degR")), ((("100", "degR"), ("10", "degR")), ("90", "degR")), ((("100", "degR"), ("10", "delta_degC")), ("82", "degR")), ((("100", "degR"), ("10", "delta_degF")), ("90", "degR")), ((("100", "delta_degC"), ("10", "kelvin")), ("90", "kelvin")), ((("100", "delta_degC"), ("10", "degC")), ("90", "degC")), ((("100", "delta_degC"), ("10", "degF")), ("170", "degF")), ((("100", "delta_degC"), ("10", "degR")), ("170", "degR")), ((("100", "delta_degC"), ("10", "delta_degC")), ("90", "delta_degC")), ((("100", "delta_degC"), ("10", "delta_degF")), ("94.44", "delta_degC")), ((("100", "delta_degF"), ("10", "kelvin")), ("45.56", "kelvin")), ((("100", "delta_degF"), ("10", "degC")), ("45.56", "degC")), ((("100", "delta_degF"), ("10", "degF")), ("90", "degF")), ((("100", "delta_degF"), ("10", "degR")), ("90", "degR")), ((("100", "delta_degF"), ("10", "delta_degC")), ("82", "delta_degF")), ((("100", "delta_degF"), ("10", "delta_degF")), ("90", "delta_degF")), ] @pytest.mark.parametrize(("input_tuple", "expected_output"), subtractions) def test_subtraction(self, input_tuple, expected_output): self.ureg.autoconvert_offset_to_baseunit = False qin1, qin2 = input_tuple q1, q2 = self.QP_(*qin1), self.QP_(*qin2) input_tuple = q1, q2 if expected_output == "error": with pytest.raises(OffsetUnitCalculusError): op.sub(q1, q2) else: expected_output = self.QP_(*expected_output) assert op.sub(q1, q2).units == expected_output.units self.assert_quantity_almost_equal( op.sub(q1, q2), expected_output, atol=0.01 ) multiplications = [ ((("100", "kelvin"), ("10", "kelvin")), ("1000", "kelvin**2")), ((("100", "kelvin"), ("10", "degC")), "error"), ((("100", "kelvin"), ("10", "degF")), "error"), ((("100", "kelvin"), ("10", "degR")), ("1000", "kelvin*degR")), ((("100", "kelvin"), ("10", "delta_degC")), ("1000", "kelvin*delta_degC")), ((("100", "kelvin"), ("10", "delta_degF")), ("1000", "kelvin*delta_degF")), ((("100", "degC"), ("10", "kelvin")), "error"), ((("100", "degC"), ("10", "degC")), "error"), ((("100", "degC"), ("10", "degF")), "error"), ((("100", "degC"), ("10", "degR")), "error"), ((("100", "degC"), ("10", "delta_degC")), "error"), ((("100", "degC"), ("10", "delta_degF")), "error"), ((("100", "degF"), ("10", "kelvin")), "error"), ((("100", "degF"), ("10", "degC")), "error"), ((("100", "degF"), ("10", "degF")), "error"), ((("100", "degF"), ("10", "degR")), "error"), ((("100", "degF"), ("10", "delta_degC")), "error"), ((("100", "degF"), ("10", "delta_degF")), "error"), ((("100", "degR"), ("10", "kelvin")), ("1000", "degR*kelvin")), ((("100", "degR"), ("10", "degC")), "error"), ((("100", "degR"), ("10", "degF")), "error"), ((("100", "degR"), ("10", "degR")), ("1000", "degR**2")), ((("100", "degR"), ("10", "delta_degC")), ("1000", "degR*delta_degC")), ((("100", "degR"), ("10", "delta_degF")), ("1000", "degR*delta_degF")), ((("100", "delta_degC"), ("10", "kelvin")), ("1000", "delta_degC*kelvin")), ((("100", "delta_degC"), ("10", "degC")), "error"), ((("100", "delta_degC"), ("10", "degF")), "error"), ((("100", "delta_degC"), ("10", "degR")), ("1000", "delta_degC*degR")), ((("100", "delta_degC"), ("10", "delta_degC")), ("1000", "delta_degC**2")), ( (("100", "delta_degC"), ("10", "delta_degF")), ("1000", "delta_degC*delta_degF"), ), ((("100", "delta_degF"), ("10", "kelvin")), ("1000", "delta_degF*kelvin")), ((("100", "delta_degF"), ("10", "degC")), "error"), ((("100", "delta_degF"), ("10", "degF")), "error"), ((("100", "delta_degF"), ("10", "degR")), ("1000", "delta_degF*degR")), ( (("100", "delta_degF"), ("10", "delta_degC")), ("1000", "delta_degF*delta_degC"), ), ((("100", "delta_degF"), ("10", "delta_degF")), ("1000", "delta_degF**2")), ] @pytest.mark.parametrize(("input_tuple", "expected_output"), multiplications) def test_multiplication(self, input_tuple, expected_output): self.ureg.autoconvert_offset_to_baseunit = False qin1, qin2 = input_tuple q1, q2 = self.QP_(*qin1), self.QP_(*qin2) input_tuple = q1, q2 if expected_output == "error": with pytest.raises(OffsetUnitCalculusError): op.mul(q1, q2) else: expected_output = self.QP_(*expected_output) assert op.mul(q1, q2).units == expected_output.units self.assert_quantity_almost_equal( op.mul(q1, q2), expected_output, atol=0.01 ) divisions = [ ((("100", "kelvin"), ("10", "kelvin")), ("10", "")), ((("100", "kelvin"), ("10", "degC")), "error"), ((("100", "kelvin"), ("10", "degF")), "error"), ((("100", "kelvin"), ("10", "degR")), ("10", "kelvin/degR")), ((("100", "kelvin"), ("10", "delta_degC")), ("10", "kelvin/delta_degC")), ((("100", "kelvin"), ("10", "delta_degF")), ("10", "kelvin/delta_degF")), ((("100", "degC"), ("10", "kelvin")), "error"), ((("100", "degC"), ("10", "degC")), "error"), ((("100", "degC"), ("10", "degF")), "error"), ((("100", "degC"), ("10", "degR")), "error"), ((("100", "degC"), ("10", "delta_degC")), "error"), ((("100", "degC"), ("10", "delta_degF")), "error"), ((("100", "degF"), ("10", "kelvin")), "error"), ((("100", "degF"), ("10", "degC")), "error"), ((("100", "degF"), ("10", "degF")), "error"), ((("100", "degF"), ("10", "degR")), "error"), ((("100", "degF"), ("10", "delta_degC")), "error"), ((("100", "degF"), ("10", "delta_degF")), "error"), ((("100", "degR"), ("10", "kelvin")), ("10", "degR/kelvin")), ((("100", "degR"), ("10", "degC")), "error"), ((("100", "degR"), ("10", "degF")), "error"), ((("100", "degR"), ("10", "degR")), ("10", "")), ((("100", "degR"), ("10", "delta_degC")), ("10", "degR/delta_degC")), ((("100", "degR"), ("10", "delta_degF")), ("10", "degR/delta_degF")), ((("100", "delta_degC"), ("10", "kelvin")), ("10", "delta_degC/kelvin")), ((("100", "delta_degC"), ("10", "degC")), "error"), ((("100", "delta_degC"), ("10", "degF")), "error"), ((("100", "delta_degC"), ("10", "degR")), ("10", "delta_degC/degR")), ((("100", "delta_degC"), ("10", "delta_degC")), ("10", "")), ( (("100", "delta_degC"), ("10", "delta_degF")), ("10", "delta_degC/delta_degF"), ), ((("100", "delta_degF"), ("10", "kelvin")), ("10", "delta_degF/kelvin")), ((("100", "delta_degF"), ("10", "degC")), "error"), ((("100", "delta_degF"), ("10", "degF")), "error"), ((("100", "delta_degF"), ("10", "degR")), ("10", "delta_degF/degR")), ( (("100", "delta_degF"), ("10", "delta_degC")), ("10", "delta_degF/delta_degC"), ), ((("100", "delta_degF"), ("10", "delta_degF")), ("10", "")), ] @pytest.mark.parametrize(("input_tuple", "expected_output"), divisions) def test_truedivision(self, input_tuple, expected_output): self.ureg.autoconvert_offset_to_baseunit = False qin1, qin2 = input_tuple q1, q2 = self.QP_(*qin1), self.QP_(*qin2) input_tuple = q1, q2 if expected_output == "error": with pytest.raises(OffsetUnitCalculusError): op.truediv(q1, q2) else: expected_output = self.QP_(*expected_output) assert op.truediv(q1, q2).units == expected_output.units self.assert_quantity_almost_equal( op.truediv(q1, q2), expected_output, atol=0.01 ) multiplications_with_autoconvert_to_baseunit = [ ((("100", "kelvin"), ("10", "degC")), ("28315.0", "kelvin**2")), ((("100", "kelvin"), ("10", "degF")), ("26092.78", "kelvin**2")), ((("100", "degC"), ("10", "kelvin")), ("3731.5", "kelvin**2")), ((("100", "degC"), ("10", "degC")), ("105657.42", "kelvin**2")), ((("100", "degC"), ("10", "degF")), ("97365.20", "kelvin**2")), ((("100", "degC"), ("10", "degR")), ("3731.5", "kelvin*degR")), ((("100", "degC"), ("10", "delta_degC")), ("3731.5", "kelvin*delta_degC")), ((("100", "degC"), ("10", "delta_degF")), ("3731.5", "kelvin*delta_degF")), ((("100", "degF"), ("10", "kelvin")), ("3109.28", "kelvin**2")), ((("100", "degF"), ("10", "degC")), ("88039.20", "kelvin**2")), ((("100", "degF"), ("10", "degF")), ("81129.69", "kelvin**2")), ((("100", "degF"), ("10", "degR")), ("3109.28", "kelvin*degR")), ((("100", "degF"), ("10", "delta_degC")), ("3109.28", "kelvin*delta_degC")), ((("100", "degF"), ("10", "delta_degF")), ("3109.28", "kelvin*delta_degF")), ((("100", "degR"), ("10", "degC")), ("28315.0", "degR*kelvin")), ((("100", "degR"), ("10", "degF")), ("26092.78", "degR*kelvin")), ((("100", "delta_degC"), ("10", "degC")), ("28315.0", "delta_degC*kelvin")), ((("100", "delta_degC"), ("10", "degF")), ("26092.78", "delta_degC*kelvin")), ((("100", "delta_degF"), ("10", "degC")), ("28315.0", "delta_degF*kelvin")), ((("100", "delta_degF"), ("10", "degF")), ("26092.78", "delta_degF*kelvin")), ] @pytest.mark.parametrize( ("input_tuple", "expected_output"), multiplications_with_autoconvert_to_baseunit ) def test_multiplication_with_autoconvert(self, input_tuple, expected_output): self.ureg.autoconvert_offset_to_baseunit = True qin1, qin2 = input_tuple q1, q2 = self.QP_(*qin1), self.QP_(*qin2) input_tuple = q1, q2 if expected_output == "error": with pytest.raises(OffsetUnitCalculusError): op.mul(q1, q2) else: expected_output = self.QP_(*expected_output) assert op.mul(q1, q2).units == expected_output.units self.assert_quantity_almost_equal( op.mul(q1, q2), expected_output, atol=0.01 ) multiplications_with_scalar = [ ((("10", "kelvin"), "2"), ("20.0", "kelvin")), ((("10", "kelvin**2"), "2"), ("20.0", "kelvin**2")), ((("10", "degC"), "2"), ("20.0", "degC")), ((("10", "1/degC"), "2"), "error"), ((("10", "degC**0.5"), "2"), "error"), ((("10", "degC**2"), "2"), "error"), ((("10", "degC**-2"), "2"), "error"), ] @pytest.mark.parametrize( ("input_tuple", "expected_output"), multiplications_with_scalar ) def test_multiplication_with_scalar(self, input_tuple, expected_output): self.ureg.default_as_delta = False in1, in2 = input_tuple if type(in1) is tuple: in1, in2 = self.QP_(*in1), self.kwargs["non_int_type"](in2) else: in1, in2 = in1, self.QP_(*in2) input_tuple = in1, in2 # update input_tuple for better tracebacks if expected_output == "error": with pytest.raises(OffsetUnitCalculusError): op.mul(in1, in2) else: expected_output = self.QP_(*expected_output) assert op.mul(in1, in2).units == expected_output.units self.assert_quantity_almost_equal( op.mul(in1, in2), expected_output, atol="0.01" ) divisions_with_scalar = [ # without / with autoconvert to plain unit ((("10", "kelvin"), "2"), [("5.0", "kelvin"), ("5.0", "kelvin")]), ((("10", "kelvin**2"), "2"), [("5.0", "kelvin**2"), ("5.0", "kelvin**2")]), ((("10", "degC"), "2"), ["error", "error"]), ((("10", "degC**2"), "2"), ["error", "error"]), ((("10", "degC**-2"), "2"), ["error", "error"]), (("2", ("10", "kelvin")), [("0.2", "1/kelvin"), ("0.2", "1/kelvin")]), # (('2', ('10', "degC")), ["error", (2 / 283.15, "1/kelvin")]), (("2", ("10", "degC**2")), ["error", "error"]), (("2", ("10", "degC**-2")), ["error", "error"]), ] @pytest.mark.parametrize(("input_tuple", "expected_output"), divisions_with_scalar) def test_division_with_scalar(self, input_tuple, expected_output): self.ureg.default_as_delta = False in1, in2 = input_tuple if type(in1) is tuple: in1, in2 = self.QP_(*in1), self.kwargs["non_int_type"](in2) else: in1, in2 = self.kwargs["non_int_type"](in1), self.QP_(*in2) input_tuple = in1, in2 # update input_tuple for better tracebacks expected_copy = expected_output.copy() for i, mode in enumerate([False, True]): self.ureg.autoconvert_offset_to_baseunit = mode if expected_copy[i] == "error": with pytest.raises(OffsetUnitCalculusError): op.truediv(in1, in2) else: expected_output = self.QP_(*expected_copy[i]) assert op.truediv(in1, in2).units == expected_output.units self.assert_quantity_almost_equal(op.truediv(in1, in2), expected_output) exponentiation = [ # results without / with autoconvert ((("10", "degC"), "1"), [("10", "degC"), ("10", "degC")]), # ((('10', "degC"), 0.5), ["error", (283.15 ** '0.5', "kelvin**0.5")]), ((("10", "degC"), "0"), [("1.0", ""), ("1.0", "")]), # ((('10', "degC"), -1), ["error", (1 / (10 + 273.15), "kelvin**-1")]), # ((('10', "degC"), -2), ["error", (1 / (10 + 273.15) ** 2.0, "kelvin**-2")]), # ((('0', "degC"), -2), ["error", (1 / (273.15) ** 2, "kelvin**-2")]), # ((('10', "degC"), ('2', "")), ["error", ((283.15) ** 2, "kelvin**2")]), ((("10", "degC"), ("10", "degK")), ["error", "error"]), ( (("10", "kelvin"), ("2", "")), [("100.0", "kelvin**2"), ("100.0", "kelvin**2")], ), (("2", ("2", "kelvin")), ["error", "error"]), # (('2', ('500.0', "millikelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]), # (('2', ('0.5', "kelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]), # ( # (('10', "degC"), ('500.0', "millikelvin/kelvin")), # ["error", (283.15 ** '0.5', "kelvin**0.5")], # ), ] @pytest.mark.parametrize(("input_tuple", "expected_output"), exponentiation) def test_exponentiation(self, input_tuple, expected_output): self.ureg.default_as_delta = False in1, in2 = input_tuple if type(in1) is type(in2) is tuple: in1, in2 = self.QP_(*in1), self.QP_(*in2) elif type(in1) is not tuple and type(in2) is tuple: in1, in2 = self.kwargs["non_int_type"](in1), self.QP_(*in2) else: in1, in2 = self.QP_(*in1), self.kwargs["non_int_type"](in2) input_tuple = in1, in2 expected_copy = expected_output.copy() for i, mode in enumerate([False, True]): self.ureg.autoconvert_offset_to_baseunit = mode if expected_copy[i] == "error": with pytest.raises((OffsetUnitCalculusError, DimensionalityError)): op.pow(in1, in2) else: if type(expected_copy[i]) is tuple: expected_output = self.QP_(*expected_copy[i]) assert op.pow(in1, in2).units == expected_output.units else: expected_output = expected_copy[i] self.assert_quantity_almost_equal(op.pow(in1, in2), expected_output) class TestNonIntTypeQuantityFloat(_TestBasic): kwargs = dict(non_int_type=float) SUPPORTS_NAN = True class TestNonIntTypeQuantityBasicMathFloat(_TestQuantityBasicMath): kwargs = dict(non_int_type=float) class TestNonIntTypeOffsetUnitMathFloat(_TestOffsetUnitMath): kwargs = dict(non_int_type=float) class TestNonIntTypeQuantityDecimal(_TestBasic): kwargs = dict(non_int_type=Decimal) SUPPORTS_NAN = True class TestNonIntTypeQuantityBasicMathDecimal(_TestQuantityBasicMath): kwargs = dict(non_int_type=Decimal) class TestNonIntTypeOffsetUnitMathDecimal(_TestOffsetUnitMath): kwargs = dict(non_int_type=Decimal) class TestNonIntTypeQuantityFraction(_TestBasic): kwargs = dict(non_int_type=Fraction) SUPPORTS_NAN = False class TestNonIntTypeQuantityBasicMathFraction(_TestQuantityBasicMath): kwargs = dict(non_int_type=Fraction) class TestNonIntTypeOffsetUnitMathFraction(_TestOffsetUnitMath): kwargs = dict(non_int_type=Fraction) pint-0.24.4/pint/testsuite/test_numpy.py000066400000000000000000001544561471316474000203650ustar00rootroot00000000000000from __future__ import annotations import copy import operator as op import pickle import warnings import pytest from pint import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning from pint.compat import np from pint.testsuite import helpers from pint.testsuite.test_umath import TestUFuncs @helpers.requires_numpy class TestNumpyMethods: @classmethod def setup_class(cls): from pint import _DEFAULT_REGISTRY cls.ureg = _DEFAULT_REGISTRY cls.Q_ = cls.ureg.Quantity @classmethod def teardown_class(cls): cls.ureg = None cls.Q_ = None @property def q(self): return [[1, 2], [3, 4]] * self.ureg.m @property def q_scalar(self): return np.array(5) * self.ureg.m @property def q_nan(self): return [[1, 2], [3, np.nan]] * self.ureg.m @property def q_zero_or_nan(self): return [[0, 0], [0, np.nan]] * self.ureg.m @property def q_temperature(self): return self.Q_([[1, 2], [3, 4]], self.ureg.degC) def assertNDArrayEqual(self, actual, desired): # Assert that the given arrays are equal, and are not Quantities np.testing.assert_array_equal(actual, desired) assert not isinstance(actual, self.Q_) assert not isinstance(desired, self.Q_) class TestNumpyArrayCreation(TestNumpyMethods): # https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html @helpers.requires_array_function_protocol() def test_ones_like(self): self.assertNDArrayEqual(np.ones_like(self.q), np.array([[1, 1], [1, 1]])) @helpers.requires_array_function_protocol() def test_zeros_like(self): self.assertNDArrayEqual(np.zeros_like(self.q), np.array([[0, 0], [0, 0]])) @helpers.requires_array_function_protocol() def test_empty_like(self): ret = np.empty_like(self.q) assert ret.shape == (2, 2) assert isinstance(ret, np.ndarray) @helpers.requires_array_function_protocol() def test_full_like(self): helpers.assert_quantity_equal( np.full_like(self.q, self.Q_(0, self.ureg.degC)), self.Q_([[0, 0], [0, 0]], self.ureg.degC), ) self.assertNDArrayEqual(np.full_like(self.q, 2), np.array([[2, 2], [2, 2]])) class TestNumpyArrayManipulation(TestNumpyMethods): # TODO # https://www.numpy.org/devdocs/reference/routines.array-manipulation.html # copyto # broadcast # asarray asanyarray asmatrix asfarray asfortranarray ascontiguousarray asarray_chkfinite asscalar require # Changing array shape def test_flatten(self): helpers.assert_quantity_equal(self.q.flatten(), [1, 2, 3, 4] * self.ureg.m) def test_flat(self): for q, v in zip(self.q.flat, [1, 2, 3, 4]): assert q == v * self.ureg.m def test_reshape(self): helpers.assert_quantity_equal( self.q.reshape([1, 4]), [[1, 2, 3, 4]] * self.ureg.m ) def test_ravel(self): helpers.assert_quantity_equal(self.q.ravel(), [1, 2, 3, 4] * self.ureg.m) @helpers.requires_array_function_protocol() def test_ravel_numpy_func(self): helpers.assert_quantity_equal(np.ravel(self.q), [1, 2, 3, 4] * self.ureg.m) # Transpose-like operations @helpers.requires_array_function_protocol() def test_moveaxis(self): helpers.assert_quantity_equal( np.moveaxis(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) @helpers.requires_array_function_protocol() def test_rollaxis(self): helpers.assert_quantity_equal( np.rollaxis(self.q, 1), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) @helpers.requires_array_function_protocol() def test_swapaxes(self): helpers.assert_quantity_equal( np.swapaxes(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) def test_transpose(self): helpers.assert_quantity_equal( self.q.transpose(), [[1, 3], [2, 4]] * self.ureg.m ) @helpers.requires_array_function_protocol() def test_transpose_numpy_func(self): helpers.assert_quantity_equal( np.transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m ) @helpers.requires_array_function_protocol() def test_flip_numpy_func(self): helpers.assert_quantity_equal( np.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m ) # Changing number of dimensions @helpers.requires_array_function_protocol() def test_atleast_1d(self): actual = np.atleast_1d(self.Q_(0, self.ureg.degC), self.q.flatten()) expected = (self.Q_(np.array([0]), self.ureg.degC), self.q.flatten()) for ind_actual, ind_expected in zip(actual, expected): helpers.assert_quantity_equal(ind_actual, ind_expected) helpers.assert_quantity_equal(np.atleast_1d(self.q), self.q) @helpers.requires_array_function_protocol() def test_atleast_2d(self): actual = np.atleast_2d(self.Q_(0, self.ureg.degC), self.q.flatten()) expected = ( self.Q_(np.array([[0]]), self.ureg.degC), np.array([[1, 2, 3, 4]]) * self.ureg.m, ) for ind_actual, ind_expected in zip(actual, expected): helpers.assert_quantity_equal(ind_actual, ind_expected) helpers.assert_quantity_equal(np.atleast_2d(self.q), self.q) @helpers.requires_array_function_protocol() def test_atleast_3d(self): actual = np.atleast_3d(self.Q_(0, self.ureg.degC), self.q.flatten()) expected = ( self.Q_(np.array([[[0]]]), self.ureg.degC), np.array([[[1], [2], [3], [4]]]) * self.ureg.m, ) for ind_actual, ind_expected in zip(actual, expected): helpers.assert_quantity_equal(ind_actual, ind_expected) helpers.assert_quantity_equal( np.atleast_3d(self.q), np.array([[[1], [2]], [[3], [4]]]) * self.ureg.m ) @helpers.requires_array_function_protocol() def test_broadcast_to(self): helpers.assert_quantity_equal( np.broadcast_to(self.q[:, 1], (2, 2)), np.array([[2, 4], [2, 4]]) * self.ureg.m, ) @helpers.requires_array_function_protocol() def test_expand_dims(self): helpers.assert_quantity_equal( np.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m ) @helpers.requires_array_function_protocol() def test_squeeze(self): helpers.assert_quantity_equal(np.squeeze(self.q), self.q) helpers.assert_quantity_equal( self.q.reshape([1, 4]).squeeze(), [1, 2, 3, 4] * self.ureg.m ) # Changing number of dimensions # Joining arrays @helpers.requires_array_function_protocol() def test_concat_stack(self, subtests): for func in (np.concatenate, np.stack, np.hstack, np.vstack, np.dstack): with subtests.test(func=func): helpers.assert_quantity_equal( func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m) ) # One or more of the args is a bare array full of zeros or NaNs helpers.assert_quantity_equal( func([self.q_zero_or_nan.m, self.q]), self.Q_(func([self.q_zero_or_nan.m, self.q.m]), self.ureg.m), ) # One or more of the args is a bare array with at least one non-zero, # non-NaN element nz = self.q_zero_or_nan nz.m[0, 0] = 1 with pytest.raises(DimensionalityError): func([nz.m, self.q]) @helpers.requires_array_function_protocol() def test_block_column_stack(self, subtests): for func in (np.block, np.column_stack): with subtests.test(func=func): helpers.assert_quantity_equal( func([self.q[:, 0], self.q[:, 1]]), self.Q_(func([self.q[:, 0].m, self.q[:, 1].m]), self.ureg.m), ) # One or more of the args is a bare array full of zeros or NaNs helpers.assert_quantity_equal( func( [ self.q_zero_or_nan[:, 0].m, self.q[:, 0], self.q_zero_or_nan[:, 1].m, ] ), self.Q_( func( [ self.q_zero_or_nan[:, 0].m, self.q[:, 0].m, self.q_zero_or_nan[:, 1].m, ] ), self.ureg.m, ), ) # One or more of the args is a bare array with at least one non-zero, # non-NaN element nz = self.q_zero_or_nan nz.m[0, 0] = 1 with pytest.raises(DimensionalityError): func([nz[:, 0].m, self.q[:, 0]]) @helpers.requires_array_function_protocol() def test_append(self): helpers.assert_quantity_equal( np.append(self.q, [[0, 0]] * self.ureg.m, axis=0), [[1, 2], [3, 4], [0, 0]] * self.ureg.m, ) def test_astype(self): actual = self.q.astype(np.float32) expected = self.Q_(np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32), "m") helpers.assert_quantity_equal(actual, expected) assert actual.m.dtype == expected.m.dtype def test_item(self): helpers.assert_quantity_equal(self.Q_([[0]], "m").item(), 0 * self.ureg.m) def test_broadcast_arrays(self): x = self.Q_(np.array([[1, 2, 3]]), "m") y = self.Q_(np.array([[4], [5]]), "nm") result = np.broadcast_arrays(x, y) expected = self.Q_( [ [[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]], [[4e-09, 4e-09, 4e-09], [5e-09, 5e-09, 5e-09]], ], "m", ) helpers.assert_quantity_equal(result, expected) result = np.broadcast_arrays(x, y, subok=True) helpers.assert_quantity_equal(result, expected) def test_roll(self): helpers.assert_quantity_equal( np.roll(self.q, 1), [[4, 1], [2, 3]] * self.ureg.m ) class TestNumpyMathematicalFunctions(TestNumpyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html # Trigonometric functions @helpers.requires_array_function_protocol() def test_unwrap(self): helpers.assert_quantity_equal( np.unwrap([0, 3 * np.pi] * self.ureg.radians), [0, np.pi] ) helpers.assert_quantity_equal( np.unwrap([0, 540] * self.ureg.deg), [0, 180] * self.ureg.deg ) # Rounding @helpers.requires_array_function_protocol() def test_fix(self): helpers.assert_quantity_equal(np.fix(3.13 * self.ureg.m), 3.0 * self.ureg.m) helpers.assert_quantity_equal(np.fix(3.0 * self.ureg.m), 3.0 * self.ureg.m) helpers.assert_quantity_equal( np.fix([2.1, 2.9, -2.1, -2.9] * self.ureg.m), [2.0, 2.0, -2.0, -2.0] * self.ureg.m, ) # Sums, products, differences @helpers.requires_array_function_protocol() def test_prod(self): axis = 0 where = [[True, False], [True, True]] helpers.assert_quantity_equal(self.q.prod(), 24 * self.ureg.m**4) helpers.assert_quantity_equal(self.q.prod(axis=axis), [3, 8] * self.ureg.m**2) helpers.assert_quantity_equal(self.q.prod(where=where), 12 * self.ureg.m**3) @helpers.requires_array_function_protocol() def test_prod_numpy_func(self): axis = 0 where = [[True, False], [True, True]] helpers.assert_quantity_equal(np.prod(self.q), 24 * self.ureg.m**4) helpers.assert_quantity_equal( np.prod(self.q, axis=axis), [3, 8] * self.ureg.m**2 ) helpers.assert_quantity_equal(np.prod(self.q, where=where), 12 * self.ureg.m**3) with pytest.raises(DimensionalityError): np.prod(self.q, axis=axis, where=where) helpers.assert_quantity_equal( np.prod(self.q, axis=axis, where=[[True, False], [False, True]]), [1, 4] * self.ureg.m, ) helpers.assert_quantity_equal( np.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m**2 ) @helpers.requires_array_function_protocol() def test_nanprod_numpy_func(self): helpers.assert_quantity_equal(np.nanprod(self.q_nan), 6 * self.ureg.m**3) helpers.assert_quantity_equal( np.nanprod(self.q_nan, axis=0), [3, 2] * self.ureg.m**2 ) helpers.assert_quantity_equal( np.nanprod(self.q_nan, axis=1), [2, 3] * self.ureg.m**2 ) def test_sum(self): assert self.q.sum() == 10 * self.ureg.m helpers.assert_quantity_equal(self.q.sum(0), [4, 6] * self.ureg.m) helpers.assert_quantity_equal(self.q.sum(1), [3, 7] * self.ureg.m) @helpers.requires_array_function_protocol() def test_sum_numpy_func(self): helpers.assert_quantity_equal(np.sum(self.q, axis=0), [4, 6] * self.ureg.m) with pytest.raises(OffsetUnitCalculusError): np.sum(self.q_temperature) @helpers.requires_array_function_protocol() def test_nansum_numpy_func(self): helpers.assert_quantity_equal( np.nansum(self.q_nan, axis=0), [4, 2] * self.ureg.m ) def test_cumprod(self): with pytest.raises(DimensionalityError): self.q.cumprod() helpers.assert_quantity_equal((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) @helpers.requires_array_function_protocol() def test_cumprod_numpy_func(self): with pytest.raises(DimensionalityError): np.cumprod(self.q) helpers.assert_quantity_equal(np.cumprod(self.q / self.ureg.m), [1, 2, 6, 24]) helpers.assert_quantity_equal( np.cumprod(self.q / self.ureg.m, axis=1), [[1, 2], [3, 12]] ) @helpers.requires_array_function_protocol() def test_nancumprod_numpy_func(self): with pytest.raises(DimensionalityError): np.nancumprod(self.q_nan) helpers.assert_quantity_equal( np.nancumprod(self.q_nan / self.ureg.m), [1, 2, 6, 6] ) @helpers.requires_array_function_protocol() def test_diff(self): helpers.assert_quantity_equal(np.diff(self.q, 1), [[1], [1]] * self.ureg.m) helpers.assert_quantity_equal( np.diff(self.q_temperature, 1), [[1], [1]] * self.ureg.delta_degC ) @helpers.requires_array_function_protocol() def test_ediff1d(self): helpers.assert_quantity_equal(np.ediff1d(self.q), [1, 1, 1] * self.ureg.m) helpers.assert_quantity_equal( np.ediff1d(self.q_temperature), [1, 1, 1] * self.ureg.delta_degC ) @helpers.requires_array_function_protocol() def test_gradient(self): grad = np.gradient([[1, 1], [3, 4]] * self.ureg.m, 1 * self.ureg.J) helpers.assert_quantity_equal( grad[0], [[2.0, 3.0], [2.0, 3.0]] * self.ureg.m / self.ureg.J ) helpers.assert_quantity_equal( grad[1], [[0.0, 0.0], [1.0, 1.0]] * self.ureg.m / self.ureg.J ) grad = np.gradient(self.Q_([[1, 1], [3, 4]], self.ureg.degC), 1 * self.ureg.J) helpers.assert_quantity_equal( grad[0], [[2.0, 3.0], [2.0, 3.0]] * self.ureg.delta_degC / self.ureg.J ) helpers.assert_quantity_equal( grad[1], [[0.0, 0.0], [1.0, 1.0]] * self.ureg.delta_degC / self.ureg.J ) @helpers.requires_array_function_protocol() def test_cross(self): a = [[3, -3, 1]] * self.ureg.kPa b = [[4, 9, 2]] * self.ureg.m**2 helpers.assert_quantity_equal( np.cross(a, b), [[-15, -2, 39]] * self.ureg.kPa * self.ureg.m**2 ) # NP2: Remove this when we only support np>=2.0 @helpers.requires_array_function_protocol() def test_trapz(self): helpers.assert_quantity_equal( np.trapz([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m), 7.5 * self.ureg.J * self.ureg.m, ) @helpers.requires_array_function_protocol() # NP2: Remove this when we only support np>=2.0 # trapezoid added in numpy 2.0 @helpers.requires_numpy_at_least("2.0") def test_trapezoid(self): helpers.assert_quantity_equal( np.trapezoid([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m), 7.5 * self.ureg.J * self.ureg.m, ) @helpers.requires_array_function_protocol() def test_dot(self): helpers.assert_quantity_equal( self.q.ravel().dot(np.array([1, 0, 0, 1])), 5 * self.ureg.m ) @helpers.requires_array_function_protocol() def test_dot_numpy_func(self): helpers.assert_quantity_equal( np.dot(self.q.ravel(), [0, 0, 1, 0] * self.ureg.dimensionless), 3 * self.ureg.m, ) @helpers.requires_array_function_protocol() def test_einsum(self): a = np.arange(25).reshape(5, 5) * self.ureg.m b = np.arange(5) * self.ureg.m helpers.assert_quantity_equal(np.einsum("ii", a), 60 * self.ureg.m) helpers.assert_quantity_equal( np.einsum("ii->i", a), np.array([0, 6, 12, 18, 24]) * self.ureg.m ) helpers.assert_quantity_equal(np.einsum("i,i", b, b), 30 * self.ureg.m**2) helpers.assert_quantity_equal( np.einsum("ij,j", a, b), np.array([30, 80, 130, 180, 230]) * self.ureg.m**2, ) @helpers.requires_array_function_protocol() def test_solve(self): A = self.q b = [[3], [7]] * self.ureg.s x = np.linalg.solve(A, b) helpers.assert_quantity_almost_equal(x, self.Q_([[1], [1]], "s / m")) helpers.assert_quantity_almost_equal(np.dot(A, x), b) # Arithmetic operations def test_addition_with_scalar(self): a = np.array([0, 1, 2]) b = 10.0 * self.ureg("gram/kilogram") helpers.assert_quantity_almost_equal( a + b, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) ) helpers.assert_quantity_almost_equal( b + a, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) ) def test_addition_with_incompatible_scalar(self): a = np.array([0, 1, 2]) b = 1.0 * self.ureg.m with pytest.raises(DimensionalityError): op.add(a, b) with pytest.raises(DimensionalityError): op.add(b, a) def test_power(self): arr = np.array(range(3), dtype=float) q = self.Q_(arr, "meter") for op_ in (op.pow, op.ipow, np.power): q_cp = copy.copy(q) with pytest.raises(DimensionalityError): op_(2.0, q_cp) arr_cp = copy.copy(arr) arr_cp = copy.copy(arr) q_cp = copy.copy(q) with pytest.raises(DimensionalityError): op_(q_cp, arr_cp) q_cp = copy.copy(q) q2_cp = copy.copy(q) with pytest.raises(DimensionalityError): op_(q_cp, q2_cp) helpers.assert_quantity_equal( np.power(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") ) helpers.assert_quantity_equal( self.q ** self.Q_(2), self.Q_([[1, 4], [9, 16]], "m**2") ) self.assertNDArrayEqual(arr ** self.Q_(2), np.array([0, 1, 4])) def test_sqrt(self): q = self.Q_(100, "m**2") helpers.assert_quantity_equal(np.sqrt(q), self.Q_(10, "m")) def test_cbrt(self): q = self.Q_(1000, "m**3") helpers.assert_quantity_equal(np.cbrt(q), self.Q_(10, "m")) @pytest.mark.xfail @helpers.requires_numpy def test_exponentiation_array_exp_2(self): arr = np.array(range(3), dtype=float) # q = self.Q_(copy.copy(arr), None) q = self.Q_(copy.copy(arr), "meter") arr_cp = copy.copy(arr) q_cp = copy.copy(q) # this fails as expected since numpy 1.8.0 but... with pytest.raises(DimensionalityError): op.pow(arr_cp, q_cp) # ..not for op.ipow ! # q_cp is treated as if it is an array. The units are ignored. # Quantity.__ipow__ is never called arr_cp = copy.copy(arr) q_cp = copy.copy(q) with pytest.raises(DimensionalityError): op.ipow(arr_cp, q_cp) class TestNumpyUnclassified(TestNumpyMethods): def test_tolist(self): with pytest.raises(AttributeError): (5 * self.ureg.m).tolist() assert self.q.tolist() == [ [1 * self.ureg.m, 2 * self.ureg.m], [3 * self.ureg.m, 4 * self.ureg.m], ] def test_fill(self): tmp = self.q tmp.fill(6 * self.ureg.ft) helpers.assert_quantity_equal(tmp, [[6, 6], [6, 6]] * self.ureg.ft) tmp.fill(5 * self.ureg.m) helpers.assert_quantity_equal(tmp, [[5, 5], [5, 5]] * self.ureg.m) def test_take(self): helpers.assert_quantity_equal(self.q.take([0, 1, 2, 3]), self.q.flatten()) def test_put(self): q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m q.put([0, 2], [10.0, 20.0] * self.ureg.m) helpers.assert_quantity_equal(q, [10.0, 2.0, 20.0, 4.0] * self.ureg.m) q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m q.put([0, 2], [1.0, 2.0] * self.ureg.mm) helpers.assert_quantity_equal(q, [0.001, 2.0, 0.002, 4.0] * self.ureg.m) q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m / self.ureg.mm q.put([0, 2], [1.0, 2.0]) helpers.assert_quantity_equal( q, [0.001, 2.0, 0.002, 4.0] * self.ureg.m / self.ureg.mm ) q = [1.0, 2.0, 3.0, 4.0] * self.ureg.m with pytest.raises(DimensionalityError): q.put([0, 2], [4.0, 6.0] * self.ureg.J) with pytest.raises(DimensionalityError): q.put([0, 2], [4.0, 6.0]) def test_repeat(self): helpers.assert_quantity_equal( self.q.repeat(2), [1, 1, 2, 2, 3, 3, 4, 4] * self.ureg.m ) def test_sort(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m q.sort() helpers.assert_quantity_equal(q, [1, 2, 3, 4, 5, 6] * self.ureg.m) @helpers.requires_array_function_protocol() def test_sort_numpy_func(self): q = [4, 5, 2, 3, 1, 6] * self.ureg.m helpers.assert_quantity_equal(np.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) def test_argsort(self): q = [1, 4, 5, 6, 2, 9] * self.ureg.MeV self.assertNDArrayEqual(q.argsort(), [0, 4, 1, 2, 3, 5]) @helpers.requires_array_function_protocol() def test_argsort_numpy_func(self): self.assertNDArrayEqual(np.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) def test_diagonal(self): q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m helpers.assert_quantity_equal(q.diagonal(offset=1), [2, 3] * self.ureg.m) @helpers.requires_array_function_protocol() def test_diagonal_numpy_func(self): q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m helpers.assert_quantity_equal(np.diagonal(q, offset=-1), [1, 2] * self.ureg.m) def test_compress(self): helpers.assert_quantity_equal( self.q.compress([False, True], axis=0), [[3, 4]] * self.ureg.m ) helpers.assert_quantity_equal( self.q.compress([False, True], axis=1), [[2], [4]] * self.ureg.m ) @helpers.requires_array_function_protocol() def test_compress_nep18(self): helpers.assert_quantity_equal( np.compress([False, True], self.q, axis=1), [[2], [4]] * self.ureg.m ) def test_searchsorted(self): q = self.q.flatten() self.assertNDArrayEqual(q.searchsorted([1.5, 2.5] * self.ureg.m), [1, 2]) q = self.q.flatten() with pytest.raises(DimensionalityError): q.searchsorted([1.5, 2.5]) @helpers.requires_array_function_protocol() def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() self.assertNDArrayEqual(np.searchsorted(q, [1.5, 2.5] * self.ureg.m), [1, 2]) def test_nonzero(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m self.assertNDArrayEqual(q.nonzero()[0], [0, 2, 3, 5]) @helpers.requires_array_function_protocol() def test_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m self.assertNDArrayEqual(np.nonzero(q)[0], [0, 2, 3, 5]) @helpers.requires_array_function_protocol() def test_any_numpy_func(self): q = [0, 1] * self.ureg.m assert np.any(q) with pytest.raises(ValueError): np.any(self.q_temperature) @helpers.requires_array_function_protocol() def test_all_numpy_func(self): q = [0, 1] * self.ureg.m assert not np.all(q) with pytest.raises(ValueError): np.all(self.q_temperature) @helpers.requires_array_function_protocol() def test_count_nonzero_numpy_func(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m assert np.count_nonzero(q) == 4 def test_max(self): assert self.q.max() == 4 * self.ureg.m def test_max_numpy_func(self): assert np.max(self.q) == 4 * self.ureg.m @helpers.requires_array_function_protocol() def test_max_with_axis_arg(self): helpers.assert_quantity_equal(np.max(self.q, axis=1), [2, 4] * self.ureg.m) @helpers.requires_array_function_protocol() def test_max_with_initial_arg(self): helpers.assert_quantity_equal( np.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m, ) @helpers.requires_array_function_protocol() def test_nanmax(self): assert np.nanmax(self.q_nan) == 3 * self.ureg.m def test_argmax(self): assert self.q.argmax() == 3 @helpers.requires_array_function_protocol() def test_argmax_numpy_func(self): self.assertNDArrayEqual(np.argmax(self.q, axis=0), np.array([1, 1])) @helpers.requires_array_function_protocol() def test_nanargmax_numpy_func(self): self.assertNDArrayEqual(np.nanargmax(self.q_nan, axis=0), np.array([1, 0])) def test_maximum(self): helpers.assert_quantity_equal( np.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) def test_min(self): assert self.q.min() == 1 * self.ureg.m @helpers.requires_array_function_protocol() def test_min_numpy_func(self): assert np.min(self.q) == 1 * self.ureg.m @helpers.requires_array_function_protocol() def test_min_with_axis_arg(self): helpers.assert_quantity_equal(np.min(self.q, axis=1), [1, 3] * self.ureg.m) @helpers.requires_array_function_protocol() def test_min_with_initial_arg(self): helpers.assert_quantity_equal( np.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[1, 2], [3, 3]] * self.ureg.m, ) @helpers.requires_array_function_protocol() def test_nanmin(self): assert np.nanmin(self.q_nan) == 1 * self.ureg.m def test_argmin(self): assert self.q.argmin() == 0 @helpers.requires_array_function_protocol() def test_argmin_numpy_func(self): self.assertNDArrayEqual(np.argmin(self.q, axis=0), np.array([0, 0])) @helpers.requires_array_function_protocol() def test_nanargmin_numpy_func(self): self.assertNDArrayEqual(np.nanargmin(self.q_nan, axis=0), np.array([0, 0])) def test_minimum(self): helpers.assert_quantity_equal( np.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) # NP2: Can remove Q_(arr).ptp test when we only support numpy>=2 def test_ptp(self): if not np.lib.NumpyVersion(np.__version__) >= "2.0.0b1": assert self.q.ptp() == 3 * self.ureg.m # NP2: Keep this test for numpy>=2, it's only arr.ptp() that is deprecated @helpers.requires_array_function_protocol() def test_ptp_numpy_func(self): helpers.assert_quantity_equal(np.ptp(self.q, axis=0), [2, 2] * self.ureg.m) def test_clip(self): helpers.assert_quantity_equal( self.q.clip(max=2 * self.ureg.m), [[1, 2], [2, 2]] * self.ureg.m ) helpers.assert_quantity_equal( self.q.clip(min=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m ) helpers.assert_quantity_equal( self.q.clip(min=2 * self.ureg.m, max=3 * self.ureg.m), [[2, 2], [3, 3]] * self.ureg.m, ) helpers.assert_quantity_equal( self.q.clip(3 * self.ureg.m, None), [[3, 3], [3, 4]] * self.ureg.m ) helpers.assert_quantity_equal( self.q.clip(3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m ) with pytest.raises(DimensionalityError): self.q.clip(self.ureg.J) with pytest.raises(DimensionalityError): self.q.clip(1) @helpers.requires_array_function_protocol() def test_clip_numpy_func(self): helpers.assert_quantity_equal( np.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m ) def test_round(self): q = [1, 1.33, 5.67, 22] * self.ureg.m helpers.assert_quantity_equal(q.round(0), [1, 1, 6, 22] * self.ureg.m) helpers.assert_quantity_equal(q.round(-1), [0, 0, 10, 20] * self.ureg.m) helpers.assert_quantity_equal(q.round(1), [1, 1.3, 5.7, 22] * self.ureg.m) @helpers.requires_array_function_protocol() def test_round_numpy_func(self): helpers.assert_quantity_equal( np.around(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) helpers.assert_quantity_equal( np.round(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) def test_trace(self): assert self.q.trace() == (1 + 4) * self.ureg.m def test_cumsum(self): helpers.assert_quantity_equal(self.q.cumsum(), [1, 3, 6, 10] * self.ureg.m) @helpers.requires_array_function_protocol() def test_cumsum_numpy_func(self): helpers.assert_quantity_equal( np.cumsum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) @helpers.requires_array_function_protocol() def test_nancumsum_numpy_func(self): helpers.assert_quantity_equal( np.nancumsum(self.q_nan, axis=0), [[1, 2], [4, 2]] * self.ureg.m ) def test_mean(self): assert self.q.mean() == 2.5 * self.ureg.m @helpers.requires_array_function_protocol() def test_mean_numpy_func(self): assert np.mean(self.q) == 2.5 * self.ureg.m assert np.mean(self.q_temperature) == self.Q_(2.5, self.ureg.degC) @helpers.requires_array_function_protocol() def test_nanmean_numpy_func(self): assert np.nanmean(self.q_nan) == 2 * self.ureg.m @helpers.requires_array_function_protocol() def test_average_numpy_func(self): helpers.assert_quantity_almost_equal( np.average(self.q, axis=0, weights=[1, 2]), [2.33333, 3.33333] * self.ureg.m, rtol=1e-5, ) @helpers.requires_array_function_protocol() def test_median_numpy_func(self): assert np.median(self.q) == 2.5 * self.ureg.m @helpers.requires_array_function_protocol() def test_nanmedian_numpy_func(self): assert np.nanmedian(self.q_nan) == 2 * self.ureg.m def test_var(self): assert self.q.var() == 1.25 * self.ureg.m**2 @helpers.requires_array_function_protocol() def test_var_numpy_func(self): assert np.var(self.q) == 1.25 * self.ureg.m**2 @helpers.requires_array_function_protocol() def test_nanvar_numpy_func(self): helpers.assert_quantity_almost_equal( np.nanvar(self.q_nan), 0.66667 * self.ureg.m**2, rtol=1e-5 ) def test_std(self): helpers.assert_quantity_almost_equal( self.q.std(), 1.11803 * self.ureg.m, rtol=1e-5 ) @helpers.requires_array_function_protocol() def test_std_numpy_func(self): helpers.assert_quantity_almost_equal( np.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5 ) with pytest.raises(OffsetUnitCalculusError): np.std(self.q_temperature) def test_cumprod(self): with pytest.raises(DimensionalityError): self.q.cumprod() helpers.assert_quantity_equal((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) @helpers.requires_array_function_protocol() def test_nanstd_numpy_func(self): helpers.assert_quantity_almost_equal( np.nanstd(self.q_nan), 0.81650 * self.ureg.m, rtol=1e-5 ) def test_conj(self): helpers.assert_quantity_equal((self.q * (1 + 1j)).conj(), self.q * (1 - 1j)) helpers.assert_quantity_equal( (self.q * (1 + 1j)).conjugate(), self.q * (1 - 1j) ) def test_getitem(self): with pytest.raises(IndexError): self.q.__getitem__((0, 10)) helpers.assert_quantity_equal(self.q[0], [1, 2] * self.ureg.m) assert self.q[1, 1] == 4 * self.ureg.m def test_setitem(self): with pytest.raises(TypeError): self.q[0, 0] = 1 with pytest.raises(DimensionalityError): self.q[0, 0] = 1 * self.ureg.J with pytest.raises(DimensionalityError): self.q[0] = 1 with pytest.raises(DimensionalityError): self.q[0] = np.ndarray([1, 2]) with pytest.raises(DimensionalityError): self.q[0] = 1 * self.ureg.J q = self.q.copy() q[0] = 1 * self.ureg.m helpers.assert_quantity_equal(q, [[1, 1], [3, 4]] * self.ureg.m) q = self.q.copy() q[...] = 1 * self.ureg.m helpers.assert_quantity_equal(q, [[1, 1], [1, 1]] * self.ureg.m) q = self.q.copy() q[:] = 1 * self.ureg.m helpers.assert_quantity_equal(q, [[1, 1], [1, 1]] * self.ureg.m) # check and see that dimensionless numbers work correctly q = [0, 1, 2, 3] * self.ureg.dimensionless q[0] = 1 helpers.assert_quantity_equal(q, np.asarray([1, 1, 2, 3])) q[0] = self.ureg.m / self.ureg.mm helpers.assert_quantity_equal(q, np.asarray([1000, 1, 2, 3])) q = [0.0, 1.0, 2.0, 3.0] * self.ureg.m / self.ureg.mm q[0] = 1.0 helpers.assert_quantity_equal(q, [0.001, 1, 2, 3] * self.ureg.m / self.ureg.mm) # Check that this properly masks the first item without warning q = self.ureg.Quantity( np.ma.array([0.0, 1.0, 2.0, 3.0], mask=[False, True, False, False]), "m" ) with warnings.catch_warnings(record=True) as w: q[0] = np.ma.masked # Check for no warnings assert not w assert q.mask[0] def test_setitem_mixed_masked(self): masked = np.ma.array( [ 1, 2, ], mask=[True, False], ) q = self.Q_(np.ones(shape=(2,)), "m") with pytest.raises(DimensionalityError): q[:] = masked masked_q = self.Q_(masked, "mm") q[:] = masked_q helpers.assert_quantity_equal(q, [1.0, 0.002] * self.ureg.m) def test_iterator(self): for q, v in zip(self.q.flatten(), [1, 2, 3, 4]): assert q == v * self.ureg.m def test_iterable(self): assert np.iterable(self.q) assert not np.iterable(1 * self.ureg.m) def test_reversible_op(self): """ """ x = self.q.magnitude u = self.Q_(np.ones(x.shape)) helpers.assert_quantity_equal(x / self.q, u * x / self.q) helpers.assert_quantity_equal(x * self.q, u * x * self.q) helpers.assert_quantity_equal(x + u, u + x) helpers.assert_quantity_equal(x - u, -(u - x)) def test_pickle(self, subtests): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): with subtests.test(protocol): q1 = [10, 20] * self.ureg.m q2 = pickle.loads(pickle.dumps(q1, protocol)) self.assertNDArrayEqual(q1.magnitude, q2.magnitude) assert q1.units == q2.units def test_equal(self): x = self.q.magnitude u = self.Q_(np.ones(x.shape)) true = np.ones_like(x, dtype=np.bool_) false = np.zeros_like(x, dtype=np.bool_) helpers.assert_quantity_equal(u, u) helpers.assert_quantity_equal(u == u, u.magnitude == u.magnitude) helpers.assert_quantity_equal(u == 1, u.magnitude == 1) v = self.Q_(np.zeros(x.shape), "m") w = self.Q_(np.ones(x.shape), "m") self.assertNDArrayEqual(v == 1, false) self.assertNDArrayEqual( self.Q_(np.zeros_like(x), "m") == self.Q_(np.zeros_like(x), "s"), false, ) self.assertNDArrayEqual(v == v, true) self.assertNDArrayEqual(v == w, false) self.assertNDArrayEqual(v == w.to("mm"), false) self.assertNDArrayEqual(u == v, false) def test_shape(self): u = self.Q_(np.arange(12)) u.shape = 4, 3 assert u.magnitude.shape == (4, 3) def test_dtype(self): u = self.Q_(np.arange(12, dtype="uint32")) assert u.dtype == "uint32" @helpers.requires_array_function_protocol() def test_shape_numpy_func(self): assert np.shape(self.q) == (2, 2) @helpers.requires_array_function_protocol() def test_len_numpy_func(self): assert len(self.q) == 2 @helpers.requires_array_function_protocol() def test_ndim_numpy_func(self): assert np.ndim(self.q) == 2 @helpers.requires_array_function_protocol() def test_copy_numpy_func(self): q_copy = np.copy(self.q) helpers.assert_quantity_equal(self.q, q_copy) assert self.q is not q_copy @helpers.requires_array_function_protocol() def test_trim_zeros_numpy_func(self): q = [0, 4, 3, 0, 2, 2, 0, 0, 0] * self.ureg.m helpers.assert_quantity_equal(np.trim_zeros(q), [4, 3, 0, 2, 2] * self.ureg.m) @helpers.requires_array_function_protocol() def test_result_type_numpy_func(self): assert np.result_type(self.q) == np.dtype("int") @helpers.requires_array_function_protocol() def test_nan_to_num_numpy_func(self): helpers.assert_quantity_equal( np.nan_to_num(self.q_nan, nan=-999 * self.ureg.mm), [[1, 2], [3, -0.999]] * self.ureg.m, ) @helpers.requires_array_function_protocol() def test_meshgrid_numpy_func(self): x = [1, 2] * self.ureg.m y = [0, 50, 100] * self.ureg.mm xx, yy = np.meshgrid(x, y) helpers.assert_quantity_equal(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) helpers.assert_quantity_equal(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) @helpers.requires_array_function_protocol() def test_isclose_numpy_func(self): q2 = [[1000.05, 2000], [3000.00007, 4001]] * self.ureg.mm self.assertNDArrayEqual( np.isclose(self.q, q2), np.array([[False, True], [True, False]]) ) self.assertNDArrayEqual( np.isclose(self.q, q2, atol=1e-5 * self.ureg.mm, rtol=1e-7), np.array([[False, True], [True, False]]), ) self.assertNDArrayEqual( np.isclose(self.q, q2, atol=1e-5, rtol=1e-7), np.array([[False, True], [True, False]]), ) @helpers.requires_array_function_protocol() def test_interp_numpy_func(self): x = [1, 4] * self.ureg.m xp = np.linspace(0, 3, 5) * self.ureg.m fp = self.Q_([0, 5, 10, 15, 20], self.ureg.degC) helpers.assert_quantity_almost_equal( np.interp(x, xp, fp), self.Q_([6.66667, 20.0], self.ureg.degC), rtol=1e-5 ) x_ = np.array([1, 4]) xp_ = np.linspace(0, 3, 5) fp_ = [0, 5, 10, 15, 20] helpers.assert_quantity_almost_equal( np.interp(x_, xp_, fp), self.Q_([6.6667, 20.0], self.ureg.degC), rtol=1e-5 ) helpers.assert_quantity_almost_equal( np.interp(x, xp, fp_), [6.6667, 20.0], rtol=1e-5 ) def test_comparisons(self): self.assertNDArrayEqual( self.q > 2 * self.ureg.m, np.array([[False, False], [True, True]]) ) self.assertNDArrayEqual( self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]]) ) @helpers.requires_array_function_protocol() def test_where(self): helpers.assert_quantity_equal( np.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), [[20, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( np.where(self.q >= 2 * self.ureg.m, self.q, 0), [[0, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( np.where(self.q >= 2 * self.ureg.m, self.q, np.nan), [[np.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( np.where(self.q >= 3 * self.ureg.m, 0, self.q), [[1, 2], [0, 0]] * self.ureg.m, ) helpers.assert_quantity_equal( np.where(self.q >= 3 * self.ureg.m, np.nan, self.q), [[1, 2], [np.nan, np.nan]] * self.ureg.m, ) helpers.assert_quantity_equal( np.where(self.q >= 2 * self.ureg.m, self.q, np.array(np.nan)), [[np.nan, 2], [3, 4]] * self.ureg.m, ) helpers.assert_quantity_equal( np.where(self.q >= 3 * self.ureg.m, np.array(np.nan), self.q), [[1, 2], [np.nan, np.nan]] * self.ureg.m, ) with pytest.raises(DimensionalityError): np.where( self.q < 2 * self.ureg.m, self.q, 0 * self.ureg.J, ) helpers.assert_quantity_equal( np.where([-1, 0, 1] * self.ureg.m, [1, 2, 1] * self.ureg.s, np.nan), [1, np.nan, 1] * self.ureg.s, ) with pytest.raises( ValueError, match=".*Boolean value of Quantity with offset unit is ambiguous", ): np.where( self.ureg.Quantity([-1, 0, 1], "degC"), [1, 2, 1] * self.ureg.s, np.nan ) @helpers.requires_array_function_protocol() def test_fabs(self): helpers.assert_quantity_equal( np.fabs(self.q - 2 * self.ureg.m), self.Q_([[1, 0], [1, 2]], "m") ) @helpers.requires_array_function_protocol() def test_isin(self): self.assertNDArrayEqual( np.isin(self.q, self.Q_([0, 2, 4], "m")), np.array([[False, True], [False, True]]), ) self.assertNDArrayEqual( np.isin(self.q, self.Q_([0, 2, 4], "J")), np.array([[False, False], [False, False]]), ) self.assertNDArrayEqual( np.isin(self.q, [self.Q_(2, "m"), self.Q_(4, "J")]), np.array([[False, True], [False, False]]), ) self.assertNDArrayEqual( np.isin(self.q, self.q.m), np.array([[False, False], [False, False]]) ) self.assertNDArrayEqual( np.isin(self.q / self.ureg.cm, [1, 3]), np.array([[True, False], [True, False]]), ) with pytest.raises(ValueError): np.isin(self.q.m, self.q) @helpers.requires_array_function_protocol() def test_percentile(self): helpers.assert_quantity_equal(np.percentile(self.q, 25), self.Q_(1.75, "m")) @helpers.requires_array_function_protocol() def test_nanpercentile(self): helpers.assert_quantity_equal( np.nanpercentile(self.q_nan, 25), self.Q_(1.5, "m") ) @helpers.requires_array_function_protocol() def test_quantile(self): helpers.assert_quantity_equal(np.quantile(self.q, 0.25), self.Q_(1.75, "m")) @helpers.requires_array_function_protocol() def test_nanquantile(self): helpers.assert_quantity_equal( np.nanquantile(self.q_nan, 0.25), self.Q_(1.5, "m") ) @helpers.requires_array_function_protocol() def test_copyto(self): a = self.q.m q = copy.copy(self.q) np.copyto(q, 2 * q, where=[True, False]) helpers.assert_quantity_equal(q, self.Q_([[2, 2], [6, 4]], "m")) np.copyto(q, 0, where=[[False, False], [True, False]]) helpers.assert_quantity_equal(q, self.Q_([[2, 2], [0, 4]], "m")) np.copyto(a, q) self.assertNDArrayEqual(a, np.array([[2, 2], [0, 4]])) @helpers.requires_array_function_protocol() def test_tile(self): helpers.assert_quantity_equal( np.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m ) @helpers.requires_numpy_at_least("1.20") @helpers.requires_array_function_protocol() def test_sliding_window_view(self): q = self.Q_([[1, 2, 2, 1], [2, 1, 1, 2], [1, 2, 2, 1]], "m") actual = np.lib.stride_tricks.sliding_window_view(q, window_shape=(3, 3)) expected = self.Q_( [[[[1, 2, 2], [2, 1, 1], [1, 2, 2]], [[2, 2, 1], [1, 1, 2], [2, 2, 1]]]], "m", ) helpers.assert_quantity_equal(actual, expected) @helpers.requires_array_function_protocol() def test_rot90(self): helpers.assert_quantity_equal( np.rot90(self.q), np.array([[2, 4], [1, 3]]) * self.ureg.m ) @helpers.requires_array_function_protocol() def test_insert(self): helpers.assert_quantity_equal( np.insert(self.q, 1, 0 * self.ureg.m, axis=1), np.array([[1, 0, 2], [3, 0, 4]]) * self.ureg.m, ) @helpers.requires_array_function_protocol() def test_delete(self): q = self.Q_(np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]), "m") helpers.assert_quantity_equal( np.delete(q, 1, axis=0), np.array([[1, 2, 3, 4], [9, 10, 11, 12]]) * self.ureg.m, ) helpers.assert_quantity_equal( np.delete(q, np.s_[::2], 1), np.array([[2, 4], [6, 8], [10, 12]]) * self.ureg.m, ) helpers.assert_quantity_equal( np.delete(q, [1, 3, 5], None), np.array([1, 3, 5, 7, 8, 9, 10, 11, 12]) * self.ureg.m, ) def test_ndarray_downcast(self): with pytest.warns(UnitStrippedWarning): np.asarray(self.q) def test_ndarray_downcast_with_dtype(self): with pytest.warns(UnitStrippedWarning): qarr = np.asarray(self.q, dtype=np.float64) assert qarr.dtype == np.float64 def test_array_protocol_unavailable(self): for attr in ("__array_struct__", "__array_interface__"): with pytest.raises(AttributeError): getattr(self.q, attr) @helpers.requires_array_function_protocol() def test_resize(self): helpers.assert_quantity_equal( np.resize(self.q, (2, 4)), [[1, 2, 3, 4], [1, 2, 3, 4]] * self.ureg.m ) @helpers.requires_array_function_protocol() def test_pad(self): # Tests reproduced with modification from NumPy documentation a = [1, 2, 3, 4, 5] * self.ureg.m b = self.Q_([4.0, 6.0, 8.0, 9.0, -3.0], "degC") helpers.assert_quantity_equal( np.pad(a, (2, 3), "constant"), [0, 0, 1, 2, 3, 4, 5, 0, 0, 0] * self.ureg.m ) helpers.assert_quantity_equal( np.pad(a, (2, 3), "constant", constant_values=(0, 600 * self.ureg.cm)), [0, 0, 1, 2, 3, 4, 5, 6, 6, 6] * self.ureg.m, ) helpers.assert_quantity_equal( np.pad( b, (2, 1), "constant", constant_values=(np.nan, self.Q_(10, "degC")) ), self.Q_([np.nan, np.nan, 4, 6, 8, 9, -3, 10], "degC"), ) with pytest.raises(DimensionalityError): np.pad(a, (2, 3), "constant", constant_values=4) helpers.assert_quantity_equal( np.pad(a, (2, 3), "edge"), [1, 1, 1, 2, 3, 4, 5, 5, 5, 5] * self.ureg.m ) helpers.assert_quantity_equal( np.pad(a, (2, 3), "linear_ramp"), [0, 0, 1, 2, 3, 4, 5, 3, 1, 0] * self.ureg.m, ) helpers.assert_quantity_equal( np.pad(a, (2, 3), "linear_ramp", end_values=(5, -4) * self.ureg.m), [5, 3, 1, 2, 3, 4, 5, 2, -1, -4] * self.ureg.m, ) helpers.assert_quantity_equal( np.pad(a, (2,), "maximum"), [5, 5, 1, 2, 3, 4, 5, 5, 5] * self.ureg.m ) helpers.assert_quantity_equal( np.pad(a, (2,), "mean"), [3, 3, 1, 2, 3, 4, 5, 3, 3] * self.ureg.m ) helpers.assert_quantity_equal( np.pad(a, (2,), "median"), [3, 3, 1, 2, 3, 4, 5, 3, 3] * self.ureg.m ) helpers.assert_quantity_equal( np.pad(self.q, ((3, 2), (2, 3)), "minimum"), [ [1, 1, 1, 2, 1, 1, 1], [1, 1, 1, 2, 1, 1, 1], [1, 1, 1, 2, 1, 1, 1], [1, 1, 1, 2, 1, 1, 1], [3, 3, 3, 4, 3, 3, 3], [1, 1, 1, 2, 1, 1, 1], [1, 1, 1, 2, 1, 1, 1], ] * self.ureg.m, ) helpers.assert_quantity_equal( np.pad(a, (2, 3), "reflect"), [3, 2, 1, 2, 3, 4, 5, 4, 3, 2] * self.ureg.m ) helpers.assert_quantity_equal( np.pad(a, (2, 3), "reflect", reflect_type="odd"), [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] * self.ureg.m, ) helpers.assert_quantity_equal( np.pad(a, (2, 3), "symmetric"), [2, 1, 1, 2, 3, 4, 5, 5, 4, 3] * self.ureg.m ) helpers.assert_quantity_equal( np.pad(a, (2, 3), "symmetric", reflect_type="odd"), [0, 1, 1, 2, 3, 4, 5, 5, 6, 7] * self.ureg.m, ) helpers.assert_quantity_equal( np.pad(a, (2, 3), "wrap"), [4, 5, 1, 2, 3, 4, 5, 1, 2, 3] * self.ureg.m ) def pad_with(vector, pad_width, iaxis, kwargs): pad_value = kwargs.get("padder", 10) vector[: pad_width[0]] = pad_value vector[-pad_width[1] :] = pad_value b = self.Q_(np.arange(6).reshape((2, 3)), "degC") helpers.assert_quantity_equal( np.pad(b, 2, pad_with), self.Q_( [ [10, 10, 10, 10, 10, 10, 10], [10, 10, 10, 10, 10, 10, 10], [10, 10, 0, 1, 2, 10, 10], [10, 10, 3, 4, 5, 10, 10], [10, 10, 10, 10, 10, 10, 10], [10, 10, 10, 10, 10, 10, 10], ], "degC", ), ) helpers.assert_quantity_equal( np.pad(b, 2, pad_with, padder=100), self.Q_( [ [100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100], [100, 100, 0, 1, 2, 100, 100], [100, 100, 3, 4, 5, 100, 100], [100, 100, 100, 100, 100, 100, 100], [100, 100, 100, 100, 100, 100, 100], ], "degC", ), ) # Note: Does not support Quantity pad_with vectorized callable use @helpers.requires_array_function_protocol() def test_allclose(self): assert np.allclose([1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.m) assert np.allclose( [1e10, 1e-8] * self.ureg.m, [1.00001e13, 1e-6] * self.ureg.mm ) assert not np.allclose( [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.mm ) assert np.allclose( [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.m, atol=1e-8 * self.ureg.m, ) assert not np.allclose([1.0, np.nan] * self.ureg.m, [1.0, np.nan] * self.ureg.m) assert np.allclose( [1.0, np.nan] * self.ureg.m, [1.0, np.nan] * self.ureg.m, equal_nan=True ) assert np.allclose( [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.m, atol=1e-8 ) with pytest.raises(DimensionalityError): assert np.allclose( [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.m, atol=1e-8 * self.ureg.s, ) @helpers.requires_array_function_protocol() def test_intersect1d(self): helpers.assert_quantity_equal( np.intersect1d([1, 3, 4, 3] * self.ureg.m, [3, 1, 2, 1] * self.ureg.m), [1, 3] * self.ureg.m, ) @helpers.requires_array_function_protocol() def test_linalg_norm(self): q = np.array([[3, 5, 8], [4, 12, 15]]) * self.ureg.m expected = [5, 13, 17] * self.ureg.m helpers.assert_quantity_equal(np.linalg.norm(q, axis=0), expected) @pytest.mark.skip class TestBitTwiddlingUfuncs(TestUFuncs): """Universal functions (ufuncs) > Bittwiddling functions http://docs.scipy.org/doc/numpy/reference/ufuncs.html#bittwiddlingfunctions bitwise_and(x1, x2[, out]) Compute the bitwise AND of two arrays elementwise. bitwise_or(x1, x2[, out]) Compute the bitwise OR of two arrays elementwise. bitwise_xor(x1, x2[, out]) Compute the bitwise XOR of two arrays elementwise. invert(x[, out]) Compute bitwise inversion, or bitwise NOT, elementwise. left_shift(x1, x2[, out]) Shift the bits of an integer to the left. right_shift(x1, x2[, out]) Shift the bits of an integer to the right. Parameters ---------- Returns ------- """ @property def qless(self): return np.asarray([1, 2, 3, 4], dtype=np.uint8) * self.ureg.dimensionless @property def qs(self): return 8 * self.ureg.J @property def q1(self): return np.asarray([1, 2, 3, 4], dtype=np.uint8) * self.ureg.J @property def q2(self): return 2 * self.q1 @property def qm(self): return np.asarray([1, 2, 3, 4], dtype=np.uint8) * self.ureg.m def test_bitwise_and(self): self._test2(np.bitwise_and, self.q1, (self.q2, self.qs), (self.qm,), "same") def test_bitwise_or(self): self._test2( np.bitwise_or, self.q1, (self.q1, self.q2, self.qs), (self.qm,), "same" ) def test_bitwise_xor(self): self._test2( np.bitwise_xor, self.q1, (self.q1, self.q2, self.qs), (self.qm,), "same" ) def test_invert(self): self._test1(np.invert, (self.q1, self.q2, self.qs), (), "same") def test_left_shift(self): self._test2( np.left_shift, self.q1, (self.qless, 2), (self.q1, self.q2, self.qs), "same" ) def test_right_shift(self): self._test2( np.right_shift, self.q1, (self.qless, 2), (self.q1, self.q2, self.qs), "same", ) pint-0.24.4/pint/testsuite/test_numpy_func.py000066400000000000000000000234771471316474000213760ustar00rootroot00000000000000from __future__ import annotations from contextlib import ExitStack from unittest.mock import patch import pytest import pint.facets.numpy.numpy_func from pint import DimensionalityError, OffsetUnitCalculusError from pint.compat import np from pint.facets.numpy.numpy_func import ( _is_quantity, _is_sequence_with_quantity_elements, convert_to_consistent_units, get_op_output_unit, implements, numpy_wrap, unwrap_and_wrap_consistent_units, ) from pint.testsuite import helpers from pint.testsuite.test_numpy import TestNumpyMethods class TestNumPyFuncUtils(TestNumpyMethods): @patch("pint.facets.numpy.numpy_func.HANDLED_FUNCTIONS", {}) @patch("pint.facets.numpy.numpy_func.HANDLED_UFUNCS", {}) def test_implements(self): # Test for functions @implements("test", "function") def test_function(): pass assert pint.facets.numpy.numpy_func.HANDLED_FUNCTIONS["test"] == test_function # Test for ufuncs @implements("test", "ufunc") def test_ufunc(): pass assert pint.facets.numpy.numpy_func.HANDLED_UFUNCS["test"] == test_ufunc # Test for invalid func type with pytest.raises(ValueError): @implements("test", "invalid") def test_invalid(): pass def test_is_quantity(self): assert _is_quantity(self.Q_(0)) assert _is_quantity(np.arange(4) * self.ureg.m) assert not _is_quantity(1.0) assert not _is_quantity(np.array([1, 1, 2, 3, 5, 8])) assert not _is_quantity("not-a-quantity") # TODO (#905 follow-up): test other duck arrays that wrap or are wrapped by Pint def test_is_sequence_with_quantity_elements(self): assert _is_sequence_with_quantity_elements( (self.Q_(0, "m"), self.Q_(32.0, "degF")) ) assert _is_sequence_with_quantity_elements(np.arange(4) * self.ureg.m) assert _is_sequence_with_quantity_elements((self.Q_(0), 0)) assert _is_sequence_with_quantity_elements((0, self.Q_(0))) assert not _is_sequence_with_quantity_elements([1, 3, 5]) assert not _is_sequence_with_quantity_elements(9 * self.ureg.m) assert not _is_sequence_with_quantity_elements(np.arange(4)) assert not _is_sequence_with_quantity_elements("0123") assert not _is_sequence_with_quantity_elements([]) assert not _is_sequence_with_quantity_elements(np.array([])) def test_convert_to_consistent_units_with_pre_calc_units(self): args, kwargs = convert_to_consistent_units( self.Q_(50, "cm"), np.arange(4).reshape(2, 2) * self.ureg.m, [0.042] * self.ureg.km, (self.Q_(0, "m"), self.Q_(1, "dam")), a=6378 * self.ureg.km, pre_calc_units=self.ureg.meter, ) assert args[0] == 0.5 self.assertNDArrayEqual(args[1], np.array([[0, 1], [2, 3]])) self.assertNDArrayEqual(args[2], np.array([42])) assert args[3][0] == 0 assert args[3][1] == 10 assert kwargs["a"] == 6.378e6 def test_convert_to_consistent_units_with_dimensionless(self): args, kwargs = convert_to_consistent_units( np.arange(2), pre_calc_units=self.ureg.g / self.ureg.kg ) self.assertNDArrayEqual(args[0], np.array([0, 1000])) assert kwargs == {} def test_convert_to_consistent_units_with_dimensionality_error(self): with pytest.raises(DimensionalityError): convert_to_consistent_units( self.Q_(32.0, "degF"), pre_calc_units=self.ureg.meter, ) with pytest.raises(DimensionalityError): convert_to_consistent_units( np.arange(4), pre_calc_units=self.ureg.meter, ) def test_convert_to_consistent_units_without_pre_calc_units(self): args, kwargs = convert_to_consistent_units( (self.Q_(0), self.Q_(10, "degC")), [1, 2, 3, 5, 7] * self.ureg.m, pre_calc_units=None, ) assert args[0][0] == 0 assert args[0][1] == 10 self.assertNDArrayEqual(args[1], np.array([1, 2, 3, 5, 7])) assert kwargs == {} def test_unwrap_and_wrap_constistent_units(self): (a,), output_wrap_a = unwrap_and_wrap_consistent_units([2, 4, 8] * self.ureg.m) (b, c), output_wrap_c = unwrap_and_wrap_consistent_units( np.arange(4), self.Q_(1, "g/kg") ) self.assertNDArrayEqual(a, np.array([2, 4, 8])) self.assertNDArrayEqual(b, np.array([0, 1000, 2000, 3000])) assert c == 1 helpers.assert_quantity_equal(output_wrap_a(0), 0 * self.ureg.m) helpers.assert_quantity_equal(output_wrap_c(0), self.Q_(0, "g/kg")) def test_op_output_unit_sum(self): assert get_op_output_unit("sum", self.ureg.m) == self.ureg.m with pytest.raises(OffsetUnitCalculusError): get_op_output_unit("sum", self.ureg.degC) def test_op_output_unit_mul(self): assert ( get_op_output_unit( "mul", self.ureg.s, (self.Q_(1, "m"), self.Q_(1, "m**2")) ) == self.ureg.m**3 ) def test_op_output_unit_delta(self): assert get_op_output_unit("delta", self.ureg.m) == self.ureg.m assert get_op_output_unit("delta", self.ureg.degC) == self.ureg.delta_degC def test_op_output_unit_delta_div(self): assert ( get_op_output_unit( "delta,div", self.ureg.m, (self.Q_(1, "m"), self.Q_(1, "s")) ) == self.ureg.m / self.ureg.s ) assert ( get_op_output_unit( "delta,div", self.ureg.degC, (self.Q_(1, "degC"), self.Q_(1, "m")) ) == self.ureg.delta_degC / self.ureg.m ) def test_op_output_unit_div(self): assert ( get_op_output_unit( "div", self.ureg.m, (self.Q_(1, "m"), self.Q_(1, "s"), self.Q_(1, "K")) ) == self.ureg.m / self.ureg.s / self.ureg.K ) assert ( get_op_output_unit("div", self.ureg.s, (1, self.Q_(1, "s"))) == self.ureg.s**-1 ) def test_op_output_unit_variance(self): assert get_op_output_unit("variance", self.ureg.m) == self.ureg.m**2 with pytest.raises(OffsetUnitCalculusError): get_op_output_unit("variance", self.ureg.degC) def test_op_output_unit_square(self): assert get_op_output_unit("square", self.ureg.m) == self.ureg.m**2 def test_op_output_unit_sqrt(self): assert get_op_output_unit("sqrt", self.ureg.m) == self.ureg.m**0.5 def test_op_output_unit_reciprocal(self): assert get_op_output_unit("reciprocal", self.ureg.m) == self.ureg.m**-1 def test_op_output_unit_size(self): assert get_op_output_unit("size", self.ureg.m, size=3) == self.ureg.m**3 with pytest.raises(ValueError): get_op_output_unit("size", self.ureg.m) def test_numpy_wrap(self): with pytest.raises(ValueError): numpy_wrap("invalid", np.ones, [], {}, []) # TODO (#905 follow-up): test that NotImplemented is returned when upcast types # present def test_trapz(self): with ExitStack() as stack: stack.callback( setattr, self.ureg, "autoconvert_offset_to_baseunit", self.ureg.autoconvert_offset_to_baseunit, ) self.ureg.autoconvert_offset_to_baseunit = True t = self.Q_(np.array([0.0, 4.0, 8.0]), "degC") z = self.Q_(np.array([0.0, 2.0, 4.0]), "m") helpers.assert_quantity_equal( np.trapz(t, x=z), self.Q_(1108.6, "kelvin meter") ) def test_trapz_no_autoconvert(self): t = self.Q_(np.array([0.0, 4.0, 8.0]), "degC") z = self.Q_(np.array([0.0, 2.0, 4.0]), "m") with pytest.raises(OffsetUnitCalculusError): np.trapz(t, x=z) def test_correlate(self): a = self.Q_(np.array([1, 2, 3]), "m") v = self.Q_(np.array([0, 1, 0.5]), "s") res = np.correlate(a, v, "full") ref = np.array([0.5, 2.0, 3.5, 3.0, 0.0]) assert np.array_equal(res.magnitude, ref) assert res.units == "meter * second" def test_dot(self): with ExitStack() as stack: stack.callback( setattr, self.ureg, "autoconvert_offset_to_baseunit", self.ureg.autoconvert_offset_to_baseunit, ) self.ureg.autoconvert_offset_to_baseunit = True t = self.Q_(np.array([0.0, 5.0, 10.0]), "degC") z = self.Q_(np.array([1.0, 2.0, 3.0]), "m") helpers.assert_quantity_almost_equal( np.dot(t, z), self.Q_(1678.9, "kelvin meter") ) def test_dot_no_autoconvert(self): t = self.Q_(np.array([0.0, 5.0, 10.0]), "degC") z = self.Q_(np.array([1.0, 2.0, 3.0]), "m") with pytest.raises(OffsetUnitCalculusError): np.dot(t, z) def test_cross(self): with ExitStack() as stack: stack.callback( setattr, self.ureg, "autoconvert_offset_to_baseunit", self.ureg.autoconvert_offset_to_baseunit, ) self.ureg.autoconvert_offset_to_baseunit = True t = self.Q_(np.array([0.0, 5.0, 10.0]), "degC") z = self.Q_(np.array([1.0, 2.0, 3.0]), "m") helpers.assert_quantity_almost_equal( np.cross(t, z), self.Q_([268.15, -536.3, 268.15], "kelvin meter") ) def test_cross_no_autoconvert(self): t = self.Q_(np.array([0.0, 5.0, 10.0]), "degC") z = self.Q_(np.array([1.0, 2.0, 3.0]), "m") with pytest.raises(OffsetUnitCalculusError): np.cross(t, z) pint-0.24.4/pint/testsuite/test_pint_eval.py000066400000000000000000000143271471316474000211660ustar00rootroot00000000000000from __future__ import annotations import pytest from pint.pint_eval import build_eval_tree, tokenizer from pint.util import string_preprocessor # This is how we enable the parsing of uncertainties # tokenizer = pint.pint_eval.uncertainty_tokenizer class TestPintEval: def _test_one(self, input_text, parsed, preprocess=False): if preprocess: input_text = string_preprocessor(input_text) assert build_eval_tree(tokenizer(input_text)).to_string() == parsed @pytest.mark.parametrize( ("input_text", "parsed"), ( ("3", "3"), ("1 + 2", "(1 + 2)"), ("1 - 2", "(1 - 2)"), ("2 * 3 + 4", "((2 * 3) + 4)"), # order of operations ("2 * (3 + 4)", "(2 * (3 + 4))"), # parentheses ( "1 + 2 * 3 ** (4 + 3 / 5)", "(1 + (2 * (3 ** (4 + (3 / 5)))))", ), # more order of operations ( "1 * ((3 + 4) * 5)", "(1 * ((3 + 4) * 5))", ), # nested parentheses at beginning ("1 * (5 * (3 + 4))", "(1 * (5 * (3 + 4)))"), # nested parentheses at end ( "1 * (5 * (3 + 4) / 6)", "(1 * ((5 * (3 + 4)) / 6))", ), # nested parentheses in middle ("-1", "(- 1)"), # unary ("3 * -1", "(3 * (- 1))"), # unary ("3 * --1", "(3 * (- (- 1)))"), # double unary ("3 * -(2 + 4)", "(3 * (- (2 + 4)))"), # parenthetical unary ("3 * -((2 + 4))", "(3 * (- (2 + 4)))"), # parenthetical unary # implicit op ("3 4", "(3 4)"), # implicit op, then parentheses ("3 (2 + 4)", "(3 (2 + 4))"), # parentheses, then implicit ("(3 ** 4 ) 5", "((3 ** 4) 5)"), # implicit op, then exponentiation ("3 4 ** 5", "(3 (4 ** 5))"), # implicit op, then addition ("3 4 + 5", "((3 4) + 5)"), # power followed by implicit ("3 ** 4 5", "((3 ** 4) 5)"), # implicit with parentheses ("3 (4 ** 5)", "(3 (4 ** 5))"), # exponent with e ("3e-1", "3e-1"), # multiple units with exponents ("kg ** 1 * s ** 2", "((kg ** 1) * (s ** 2))"), # multiple units with neg exponents ("kg ** -1 * s ** -2", "((kg ** (- 1)) * (s ** (- 2)))"), # multiple units with neg exponents ("kg^-1 * s^-2", "((kg ^ (- 1)) * (s ^ (- 2)))"), # multiple units with neg exponents, implicit op ("kg^-1 s^-2", "((kg ^ (- 1)) (s ^ (- 2)))"), # nested power ("2 ^ 3 ^ 2", "(2 ^ (3 ^ 2))"), # nested power ("gram * second / meter ** 2", "((gram * second) / (meter ** 2))"), # nested power ("gram / meter ** 2 / second", "((gram / (meter ** 2)) / second)"), # units should behave like numbers, so we don't need a bunch of extra tests for them # implicit op, then addition ("3 kg + 5", "((3 kg) + 5)"), ("(5 % 2) m", "((5 % 2) m)"), # mod operator ("(5 // 2) m", "((5 // 2) m)"), # floordiv operator ), ) def test_build_eval_tree(self, input_text, parsed): self._test_one(input_text, parsed, preprocess=False) @pytest.mark.parametrize( ("input_text", "parsed"), ( ("3", "3"), ("1 + 2", "(1 + 2)"), ("1 - 2", "(1 - 2)"), ("2 * 3 + 4", "((2 * 3) + 4)"), # order of operations ("2 * (3 + 4)", "(2 * (3 + 4))"), # parentheses ( "1 + 2 * 3 ** (4 + 3 / 5)", "(1 + (2 * (3 ** (4 + (3 / 5)))))", ), # more order of operations ( "1 * ((3 + 4) * 5)", "(1 * ((3 + 4) * 5))", ), # nested parentheses at beginning ("1 * (5 * (3 + 4))", "(1 * (5 * (3 + 4)))"), # nested parentheses at end ( "1 * (5 * (3 + 4) / 6)", "(1 * ((5 * (3 + 4)) / 6))", ), # nested parentheses in middle ("-1", "(- 1)"), # unary ("3 * -1", "(3 * (- 1))"), # unary ("3 * --1", "(3 * (- (- 1)))"), # double unary ("3 * -(2 + 4)", "(3 * (- (2 + 4)))"), # parenthetical unary ("3 * -((2 + 4))", "(3 * (- (2 + 4)))"), # parenthetical unary # implicit op ("3 4", "(3 * 4)"), # implicit op, then parentheses ("3 (2 + 4)", "(3 * (2 + 4))"), # parentheses, then implicit ("(3 ** 4 ) 5", "((3 ** 4) * 5)"), # implicit op, then exponentiation ("3 4 ** 5", "(3 * (4 ** 5))"), # implicit op, then addition ("3 4 + 5", "((3 * 4) + 5)"), # power followed by implicit ("3 ** 4 5", "((3 ** 4) * 5)"), # implicit with parentheses ("3 (4 ** 5)", "(3 * (4 ** 5))"), # exponent with e ("3e-1", "3e-1"), # multiple units with exponents ("kg ** 1 * s ** 2", "((kg ** 1) * (s ** 2))"), # multiple units with neg exponents ("kg ** -1 * s ** -2", "((kg ** (- 1)) * (s ** (- 2)))"), # multiple units with neg exponents ("kg^-1 * s^-2", "((kg ** (- 1)) * (s ** (- 2)))"), # multiple units with neg exponents, implicit op ("kg^-1 s^-2", "((kg ** (- 1)) * (s ** (- 2)))"), # nested power ("2 ^ 3 ^ 2", "(2 ** (3 ** 2))"), # nested power ("gram * second / meter ** 2", "((gram * second) / (meter ** 2))"), # nested power ("gram / meter ** 2 / second", "((gram / (meter ** 2)) / second)"), # units should behave like numbers, so we don't need a bunch of extra tests for them # implicit op, then addition ("3 kg + 5", "((3 * kg) + 5)"), ("(5 % 2) m", "((5 % 2) * m)"), # mod operator ("(5 // 2) m", "((5 // 2) * m)"), # floordiv operator ), ) def test_preprocessed_eval_tree(self, input_text, parsed): self._test_one(input_text, parsed, preprocess=True) pint-0.24.4/pint/testsuite/test_pitheorem.py000066400000000000000000000022561471316474000211770ustar00rootroot00000000000000from __future__ import annotations import itertools import logging from pint import pi_theorem from pint.testsuite import QuantityTestCase # TODO: do not subclass from QuantityTestCase class TestPiTheorem(QuantityTestCase): def test_simple(self, caplog): # simple movement with caplog.at_level(logging.DEBUG): assert pi_theorem({"V": "m/s", "T": "s", "L": "m"}) == [ {"V": 1, "T": 1, "L": -1} ] # pendulum assert pi_theorem({"T": "s", "M": "grams", "L": "m", "g": "m/s**2"}) == [ {"g": 1, "T": 2, "L": -1} ] assert len(caplog.records) == 7 def test_inputs(self): V = "km/hour" T = "ms" L = "cm" f1 = lambda x: x f2 = lambda x: self.Q_(1, x) f3 = lambda x: self.Q_(1, x).units f4 = lambda x: self.Q_(1, x).dimensionality fs = f1, f2, f3, f4 for fv, ft, fl in itertools.product(fs, fs, fs): qv = fv(V) qt = ft(T) ql = ft(L) assert self.ureg.pi_theorem({"V": qv, "T": qt, "L": ql}) == [ {"V": 1.0, "T": 1.0, "L": -1.0} ] pint-0.24.4/pint/testsuite/test_quantity.py000066400000000000000000002363611471316474000210670ustar00rootroot00000000000000from __future__ import annotations import copy import datetime import logging import math import operator as op import pickle import warnings from unittest.mock import patch import pytest from pint import ( DimensionalityError, OffsetUnitCalculusError, UnitRegistry, get_application_registry, ) from pint.compat import np from pint.errors import UndefinedBehavior from pint.facets.plain.unit import UnitsContainer from pint.testsuite import QuantityTestCase, assert_no_warnings, helpers class FakeWrapper: # Used in test_upcast_type_rejection_on_creation def __init__(self, q): self.q = q # TODO: do not subclass from QuantityTestCase class TestQuantity(QuantityTestCase): kwargs = dict(autoconvert_offset_to_baseunit=False) def test_quantity_creation(self, caplog): for args in ( (4.2, "meter"), (4.2, UnitsContainer(meter=1)), (4.2, self.ureg.meter), ("4.2*meter",), ("4.2/meter**(-1)",), (self.Q_(4.2, "meter"),), ): x = self.Q_(*args) assert x.magnitude == 4.2 assert x.units == UnitsContainer(meter=1) x = self.Q_(4.2, UnitsContainer(length=1)) y = self.Q_(x) assert x.magnitude == y.magnitude assert x.units == y.units assert x is not y x = self.Q_(4.2, None) assert x.magnitude == 4.2 assert x.units == UnitsContainer() with caplog.at_level(logging.DEBUG): assert 4.2 * self.ureg.meter == self.Q_(4.2, 2 * self.ureg.meter) assert len(caplog.records) == 1 def test_quantity_with_quantity(self): x = self.Q_(4.2, "m") assert self.Q_(x, "m").magnitude == 4.2 assert self.Q_(x, "cm").magnitude == 420.0 def test_quantity_bool(self): assert self.Q_(1, None) assert self.Q_(1, "meter") assert not self.Q_(0, None) assert not self.Q_(0, "meter") with pytest.raises(ValueError): bool(self.Q_(0, "degC")) assert not self.Q_(0, "delta_degC") def test_quantity_comparison(self): x = self.Q_(4.2, "meter") y = self.Q_(4.2, "meter") z = self.Q_(5, "meter") j = self.Q_(5, "meter*meter") # Include a comparison to the application registry 5 * get_application_registry().meter # Include a comparison to a directly created Quantity from pint import Quantity Quantity(5, "meter") # identity for single object assert x == x assert not (x != x) # identity for multiple objects with same value assert x == y assert not (x != y) assert x <= y assert x >= y assert not (x < y) assert not (x > y) assert not (x == z) assert x != z assert x < z # TODO: Reinstate this in the near future. # Compare with items to the separate application registry # assert k >= m # These should both be from application registry # if z._REGISTRY._subregistry != m._REGISTRY._subregistry: # with pytest.raises(ValueError): # z > m # One from local registry, one from application registry assert z != j assert z != j assert self.Q_(0, "meter") == self.Q_(0, "centimeter") assert self.Q_(0, "meter") != self.Q_(0, "second") assert self.Q_(10, "meter") < self.Q_(5, "kilometer") def test_quantity_comparison_convert(self): assert self.Q_(1000, "millimeter") == self.Q_(1, "meter") assert self.Q_(1000, "millimeter/min") == self.Q_(1000 / 60, "millimeter/s") def test_quantity_repr(self): x = self.Q_(4.2, UnitsContainer(meter=1)) assert str(x) == "4.2 meter" assert repr(x) == "" def test_quantity_hash(self): x = self.Q_(4.2, "meter") x2 = self.Q_(4200, "millimeter") y = self.Q_(2, "second") z = self.Q_(0.5, "hertz") assert hash(x) == hash(x2) # Dimensionless equality assert hash(y * z) == hash(1.0) # Dimensionless equality from a different unit registry ureg2 = UnitRegistry(**self.kwargs) y2 = ureg2.Quantity(2, "second") z2 = ureg2.Quantity(0.5, "hertz") assert hash(y * z) == hash(y2 * z2) def test_quantity_format(self, subtests): x = self.Q_(4.12345678, UnitsContainer(meter=2, kilogram=1, second=-1)) for spec, result in ( ("{}", str(x)), ("{!s}", str(x)), ("{!r}", repr(x)), ("{.magnitude}", str(x.magnitude)), ("{.units}", str(x.units)), ("{.magnitude!s}", str(x.magnitude)), ("{.units!s}", str(x.units)), ("{.magnitude!r}", repr(x.magnitude)), ("{.units!r}", repr(x.units)), ("{:.4f}", f"{x.magnitude:.4f} {x.units!s}"), ( "{:L}", r"4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}", ), ("{:P}", "4.12345678 kilogram·meter²/second"), ("{:H}", "4.12345678 kilogram meter2/second"), ("{:C}", "4.12345678 kilogram*meter**2/second"), ("{:~}", "4.12345678 kg * m ** 2 / s"), ( "{:L~}", r"4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}", ), ("{:P~}", "4.12345678 kg·m²/s"), ("{:H~}", "4.12345678 kg m2/s"), ("{:C~}", "4.12345678 kg*m**2/s"), ("{:Lx}", r"\SI[]{4.12345678}{\kilo\gram\meter\squared\per\second}"), ): with subtests.test(spec): assert spec.format(x) == result, spec # Check the special case that prevents e.g. '3 1 / second' x = self.Q_(3, UnitsContainer(second=-1)) assert f"{x}" == "3 / second" @helpers.requires_numpy def test_quantity_array_format(self, subtests): x = self.Q_( np.array([1e-16, 1.0000001, 10000000.0, 1e12, np.nan, np.inf]), "kg * m ** 2", ) for spec, result in ( ("{}", str(x)), ("{.magnitude}", str(x.magnitude)), ( "{:e}", "[1.000000e-16 1.000000e+00 1.000000e+07 1.000000e+12 nan inf] kilogram * meter ** 2", ), ( "{:E}", "[1.000000E-16 1.000000E+00 1.000000E+07 1.000000E+12 NAN INF] kilogram * meter ** 2", ), ( "{:.2f}", "[0.00 1.00 10000000.00 1000000000000.00 nan inf] kilogram * meter ** 2", ), ("{:.2f~P}", "[0.00 1.00 10000000.00 1000000000000.00 nan inf] kg·m²"), ("{:g~P}", "[1e-16 1 1e+07 1e+12 nan inf] kg·m²"), ( "{:.2f~H}", ( "" "" "
Magnitude" "
[0.00 1.00 10000000.00 1000000000000.00 nan inf]
Unitskg m2
" ), ), ): with subtests.test(spec): assert spec.format(x) == result @helpers.requires_numpy def test_quantity_array_scalar_format(self, subtests): x = self.Q_(np.array(4.12345678), "kg * m ** 2") for spec, result in ( ("{:.2f}", "4.12 kilogram * meter ** 2"), ("{:.2fH}", "4.12 kilogram meter2"), ): with subtests.test(spec): assert spec.format(x) == result def test_format_compact(self): q1 = (200e-9 * self.ureg.s).to_compact() q1b = self.Q_(200.0, "nanosecond") assert round(abs(q1.magnitude - q1b.magnitude), 7) == 0 assert q1.units == q1b.units q2 = (1e-2 * self.ureg("kg m/s^2")).to_compact("N") q2b = self.Q_(10.0, "millinewton") assert q2.magnitude == q2b.magnitude assert q2.units == q2b.units q3 = (-1000.0 * self.ureg("meters")).to_compact() q3b = self.Q_(-1.0, "kilometer") assert q3.magnitude == q3b.magnitude assert q3.units == q3b.units assert f"{q1:#.1f}" == f"{q1b}" assert f"{q2:#.1f}" == f"{q2b}" assert f"{q3:#.1f}" == f"{q3b}" def test_default_formatting(self, subtests): ureg = UnitRegistry() x = ureg.Quantity(4.12345678, UnitsContainer(meter=2, kilogram=1, second=-1)) for spec, result in ( ( "L", r"4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}", ), ("P", "4.12345678 kilogram·meter²/second"), ("H", "4.12345678 kilogram meter2/second"), ("C", "4.12345678 kilogram*meter**2/second"), ("~", "4.12345678 kg * m ** 2 / s"), ("L~", r"4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}"), ("P~", "4.12345678 kg·m²/s"), ("H~", "4.12345678 kg m2/s"), ("C~", "4.12345678 kg*m**2/s"), ): with subtests.test(spec): ureg.formatter.default_format = spec assert f"{x}" == result @pytest.mark.xfail(reason="Still not clear how default formatting will work.") def test_formatting_override_default_units(self): ureg = UnitRegistry() ureg.formatter.default_format = "~" x = ureg.Quantity(4, "m ** 2") assert f"{x:dP}" == "4 meter²" with pytest.warns(DeprecationWarning): assert f"{x:d}" == "4 meter ** 2" ureg.separate_format_defaults = True with assert_no_warnings(): assert f"{x:d}" == "4 m ** 2" @pytest.mark.xfail(reason="Still not clear how default formatting will work.") def test_formatting_override_default_magnitude(self): ureg = UnitRegistry() ureg.formatter.default_format = ".2f" x = ureg.Quantity(4, "m ** 2") assert f"{x:dP}" == "4 meter²" with pytest.warns(DeprecationWarning): assert f"{x:D}" == "4 meter ** 2" ureg.separate_format_defaults = True with assert_no_warnings(): assert f"{x:D}" == "4.00 meter ** 2" def test_exponent_formatting(self): ureg = UnitRegistry() x = ureg.Quantity(1e20, "meter") assert f"{x:~H}" == r"1×1020 m" assert f"{x:~L}" == r"1\times 10^{20}\ \mathrm{m}" assert f"{x:~Lx}" == r"\SI[]{1e+20}{\meter}" assert f"{x:~P}" == r"1×10²⁰ m" x = ureg.Quantity(1e-20, "meter") assert f"{x:~H}" == r"1×10-20 m" assert f"{x:~L}" == r"1\times 10^{-20}\ \mathrm{m}" assert f"{x:~Lx}" == r"\SI[]{1e-20}{\meter}" assert f"{x:~P}" == r"1×10⁻²⁰ m" def test_ipython(self): alltext = [] class Pretty: @staticmethod def text(text): alltext.append(text) @classmethod def pretty(cls, data): try: data._repr_pretty_(cls, False) except AttributeError: alltext.append(str(data)) ureg = UnitRegistry() x = 3.5 * ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1)) assert x._repr_html_() == "3.5 kilogram meter2/second" assert ( x._repr_latex_() == r"$3.5\ \frac{\mathrm{kilogram} \cdot " r"\mathrm{meter}^{2}}{\mathrm{second}}$" ) x._repr_pretty_(Pretty, False) assert "".join(alltext) == "3.5 kilogram·meter²/second" ureg.formatter.default_format = "~" assert x._repr_html_() == "3.5 kg m2/s" assert ( x._repr_latex_() == r"$3.5\ \frac{\mathrm{kg} \cdot " r"\mathrm{m}^{2}}{\mathrm{s}}$" ) alltext = [] x._repr_pretty_(Pretty, False) assert "".join(alltext) == "3.5 kg·m²/s" def test_to_base_units(self): x = self.Q_("1*inch") helpers.assert_quantity_almost_equal( x.to_base_units(), self.Q_(0.0254, "meter") ) x = self.Q_("1*inch*inch") helpers.assert_quantity_almost_equal( x.to_base_units(), self.Q_(0.0254**2.0, "meter*meter") ) x = self.Q_("1*inch/minute") helpers.assert_quantity_almost_equal( x.to_base_units(), self.Q_(0.0254 / 60.0, "meter/second") ) def test_convert(self): helpers.assert_quantity_almost_equal( self.Q_("2 inch").to("meter"), self.Q_(2.0 * 0.0254, "meter") ) helpers.assert_quantity_almost_equal( self.Q_("2 meter").to("inch"), self.Q_(2.0 / 0.0254, "inch") ) helpers.assert_quantity_almost_equal( self.Q_("2 sidereal_year").to("second"), self.Q_(63116297.5325, "second") ) helpers.assert_quantity_almost_equal( self.Q_("2.54 centimeter/second").to("inch/second"), self.Q_("1 inch/second"), ) assert round(abs(self.Q_("2.54 centimeter").to("inch").magnitude - 1), 7) == 0 assert ( round(abs(self.Q_("2 second").to("millisecond").magnitude - 2000), 7) == 0 ) @helpers.requires_mip def test_to_preferred(self): ureg = self.ureg Q_ = self.Q_ ureg.define("pound_force_per_square_foot = 47.8803 pascals = psf") ureg.define("pound_mass = 0.45359237 kg = lbm") preferred_units = [ ureg.ft, # distance L ureg.slug, # mass M ureg.s, # duration T ureg.rankine, # temperature Θ ureg.lbf, # force L M T^-2 ureg.psf, # pressure M L^−1 T^−2 ureg.lbm * ureg.ft**-3, # density M L^-3 ureg.W, # power L^2 M T^-3 ] temp = (Q_("1 lbf") * Q_("1 m/s")).to_preferred(preferred_units) assert temp.units == ureg.W temp = (Q_(" 1 lbf*m")).to_preferred(preferred_units) # would prefer this to be repeatable, but mip doesn't guarantee that currently assert temp.units in (ureg.W * ureg.s, ureg.ft * ureg.lbf) temp = Q_("1 kg").to_preferred(preferred_units) assert temp.units == ureg.slug result = Q_("1 slug/m**3").to_preferred(preferred_units) assert result.units == ureg.lbm * ureg.ft**-3 result = Q_("1 amp").to_preferred(preferred_units) assert result.units == ureg.amp result = Q_("1 volt").to_preferred(preferred_units) assert result.units == ureg.volts @helpers.requires_mip def test_to_preferred_registry(self): ureg = self.ureg Q_ = self.Q_ ureg.default_preferred_units = [ ureg.m, # distance L ureg.kg, # mass M ureg.s, # duration T ureg.N, # force L M T^-2 ureg.Pa, # pressure M L^−1 T^−2 ureg.W, # power L^2 M T^-3 ] pressure = (Q_(1, "N") * Q_("1 m**-2")).to_preferred() assert pressure.units == ureg.Pa @helpers.requires_mip def test_autoconvert_to_preferred(self): ureg = self.ureg Q_ = self.Q_ ureg.autoconvert_to_preferred = True ureg.default_preferred_units = [ ureg.m, # distance L ureg.kg, # mass M ureg.s, # duration T ureg.N, # force L M T^-2 ureg.Pa, # pressure M L^−1 T^−2 ureg.W, # power L^2 M T^-3 ] pressure = Q_(1, "N") * Q_("1 m**-2") assert pressure.units == ureg.Pa @helpers.requires_numpy def test_convert_numpy(self): # Conversions with single units take a different codepath than # Conversions with more than one unit. src_dst1 = UnitsContainer(meter=1), UnitsContainer(inch=1) src_dst2 = UnitsContainer(meter=1, second=-1), UnitsContainer(inch=1, minute=-1) for src, dst in (src_dst1, src_dst2): a = np.ones((3, 1)) ac = np.ones((3, 1)) q = self.Q_(a, src) qac = self.Q_(ac, src).to(dst) r = q.to(dst) helpers.assert_quantity_almost_equal(qac, r) assert r is not q assert r._magnitude is not a def test_convert_from(self): x = self.Q_("2*inch") meter = self.ureg.meter # from quantity helpers.assert_quantity_almost_equal( meter.from_(x), self.Q_(2.0 * 0.0254, "meter") ) helpers.assert_quantity_almost_equal(meter.m_from(x), 2.0 * 0.0254) # from unit helpers.assert_quantity_almost_equal( meter.from_(self.ureg.inch), self.Q_(0.0254, "meter") ) helpers.assert_quantity_almost_equal(meter.m_from(self.ureg.inch), 0.0254) # from number helpers.assert_quantity_almost_equal( meter.from_(2, strict=False), self.Q_(2.0, "meter") ) helpers.assert_quantity_almost_equal(meter.m_from(2, strict=False), 2.0) # from number (strict mode) with pytest.raises(ValueError): meter.from_(2) with pytest.raises(ValueError): meter.m_from(2) @helpers.requires_numpy def test_retain_unit(self): # Test that methods correctly retain units and do not degrade into # ordinary ndarrays. List contained in __copy_units. a = np.ones((3, 2)) q = self.Q_(a, "km") assert q.u == q.reshape(2, 3).u assert q.u == q.swapaxes(0, 1).u assert q.u == q.mean().u assert q.u == np.compress((q == q[0, 0]).any(0), q).u def test_context_attr(self): assert self.ureg.meter == self.Q_(1, "meter") def test_both_symbol(self): assert self.Q_(2, "ms") == self.Q_(2, "millisecond") assert self.Q_(2, "cm") == self.Q_(2, "centimeter") def test_dimensionless_units(self): assert ( round(abs(self.Q_(360, "degree").to("radian").magnitude - 2 * math.pi), 7) == 0 ) assert ( round(abs(self.Q_(2 * math.pi, "radian") - self.Q_(360, "degree")), 7) == 0 ) assert self.Q_(1, "radian").dimensionality == UnitsContainer() assert self.Q_(1, "radian").dimensionless assert not self.Q_(1, "radian").unitless assert self.Q_(1, "meter") / self.Q_(1, "meter") == 1 assert (self.Q_(1, "meter") / self.Q_(1, "mm")).to("") == 1000 assert self.Q_(10) // self.Q_(360, "degree") == 1 assert self.Q_(400, "degree") // self.Q_(2 * math.pi) == 1 assert self.Q_(400, "degree") // (2 * math.pi) == 1 assert 7 // self.Q_(360, "degree") == 1 def test_offset(self): helpers.assert_quantity_almost_equal( self.Q_(0, "kelvin").to("kelvin"), self.Q_(0, "kelvin") ) helpers.assert_quantity_almost_equal( self.Q_(0, "degC").to("kelvin"), self.Q_(273.15, "kelvin") ) helpers.assert_quantity_almost_equal( self.Q_(0, "degF").to("kelvin"), self.Q_(255.372222, "kelvin"), rtol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(100, "kelvin").to("kelvin"), self.Q_(100, "kelvin") ) helpers.assert_quantity_almost_equal( self.Q_(100, "degC").to("kelvin"), self.Q_(373.15, "kelvin") ) helpers.assert_quantity_almost_equal( self.Q_(100, "degF").to("kelvin"), self.Q_(310.92777777, "kelvin"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.Q_(0, "kelvin").to("degC"), self.Q_(-273.15, "degC") ) helpers.assert_quantity_almost_equal( self.Q_(100, "kelvin").to("degC"), self.Q_(-173.15, "degC") ) helpers.assert_quantity_almost_equal( self.Q_(0, "kelvin").to("degF"), self.Q_(-459.67, "degF"), rtol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(100, "kelvin").to("degF"), self.Q_(-279.67, "degF"), rtol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(32, "degF").to("degC"), self.Q_(0, "degC"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(100, "degC").to("degF"), self.Q_(212, "degF"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(54, "degF").to("degC"), self.Q_(12.2222, "degC"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(12, "degC").to("degF"), self.Q_(53.6, "degF"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(12, "kelvin").to("degC"), self.Q_(-261.15, "degC"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(12, "degC").to("kelvin"), self.Q_(285.15, "kelvin"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(12, "kelvin").to("degR"), self.Q_(21.6, "degR"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(12, "degR").to("kelvin"), self.Q_(6.66666667, "kelvin"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(12, "degC").to("degR"), self.Q_(513.27, "degR"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(12, "degR").to("degC"), self.Q_(-266.483333, "degC"), atol=0.01 ) def test_offset_delta(self): helpers.assert_quantity_almost_equal( self.Q_(0, "delta_degC").to("kelvin"), self.Q_(0, "kelvin") ) helpers.assert_quantity_almost_equal( self.Q_(0, "delta_degF").to("kelvin"), self.Q_(0, "kelvin"), rtol=0.01 ) helpers.assert_quantity_almost_equal( self.Q_(100, "kelvin").to("delta_degC"), self.Q_(100, "delta_degC") ) helpers.assert_quantity_almost_equal( self.Q_(100, "kelvin").to("delta_degF"), self.Q_(180, "delta_degF"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.Q_(100, "delta_degF").to("kelvin"), self.Q_(55.55555556, "kelvin"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.Q_(100, "delta_degC").to("delta_degF"), self.Q_(180, "delta_degF"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.Q_(100, "delta_degF").to("delta_degC"), self.Q_(55.55555556, "delta_degC"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.Q_(12.3, "delta_degC").to("delta_degF"), self.Q_(22.14, "delta_degF"), rtol=0.01, ) def test_pickle(self, subtests): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): for magnitude, unit in ((32, ""), (2.4, ""), (32, "m/s"), (2.4, "m/s")): with subtests.test(protocol=protocol, magnitude=magnitude, unit=unit): q1 = self.Q_(magnitude, unit) q2 = pickle.loads(pickle.dumps(q1, protocol)) assert q1 == q2 @helpers.requires_numpy def test_from_sequence(self): u_array_ref = self.Q_([200, 1000], "g") u_array_ref_reversed = self.Q_([1000, 200], "g") u_seq = [self.Q_("200g"), self.Q_("1kg")] u_seq_reversed = u_seq[::-1] u_array = self.Q_.from_sequence(u_seq) assert all(u_array == u_array_ref) u_array_2 = self.Q_.from_sequence(u_seq_reversed) assert all(u_array_2 == u_array_ref_reversed) assert not (u_array_2.u == u_array_ref_reversed.u) u_array_3 = self.Q_.from_sequence(u_seq_reversed, units="g") assert all(u_array_3 == u_array_ref_reversed) assert u_array_3.u == u_array_ref_reversed.u with pytest.raises(ValueError): self.Q_.from_sequence([]) u_array_5 = self.Q_.from_list(u_seq) assert all(u_array_5 == u_array_ref) @helpers.requires_numpy def test_iter(self): # Verify that iteration gives element as Quantity with same units x = self.Q_([0, 1, 2, 3], "m") helpers.assert_quantity_equal(next(iter(x)), self.Q_(0, "m")) def test_notiter(self): # Verify that iter() crashes immediately, without needing to draw any # element from it, if the magnitude isn't iterable x = self.Q_(1, "m") with pytest.raises(TypeError): iter(x) @helpers.requires_array_function_protocol() def test_no_longer_array_function_warning_on_creation(self): # Test that warning is no longer raised on first creation with warnings.catch_warnings(): warnings.filterwarnings("error") self.Q_([]) @helpers.requires_not_numpy() def test_no_ndarray_coercion_without_numpy(self): with pytest.raises(ValueError): self.Q_(1, "m").__array__() @patch( "pint.compat.upcast_type_names", ("pint.testsuite.test_quantity.FakeWrapper",) ) @patch( "pint.compat.upcast_type_map", {"pint.testsuite.test_quantity.FakeWrapper": FakeWrapper}, ) def test_upcast_type_rejection_on_creation(self): with pytest.raises(TypeError): self.Q_(FakeWrapper(42), "m") assert FakeWrapper(self.Q_(42, "m")).q == self.Q_(42, "m") def test_is_compatible_with(self): a = self.Q_(1, "kg") b = self.Q_(20, "g") c = self.Q_(550) assert a.is_compatible_with(b) assert a.is_compatible_with("lb") assert a.is_compatible_with(self.U_("lb")) assert not a.is_compatible_with("km") assert not a.is_compatible_with("") assert not a.is_compatible_with(12) assert c.is_compatible_with(12) def test_is_compatible_with_with_context(self): a = self.Q_(532.0, "nm") b = self.Q_(563.5, "terahertz") assert a.is_compatible_with(b, "sp") with self.ureg.context("sp"): assert a.is_compatible_with(b) @pytest.mark.parametrize(["inf_str"], [("inf",), ("-infinity",), ("INFINITY",)]) @pytest.mark.parametrize(["has_unit"], [(True,), (False,)]) def test_infinity(self, inf_str, has_unit): inf = float(inf_str) ref = self.Q_(inf, "meter" if has_unit else None) test = self.Q_(inf_str + (" meter" if has_unit else "")) assert ref == test @pytest.mark.parametrize(["nan_str"], [("nan",), ("NAN",)]) @pytest.mark.parametrize(["has_unit"], [(True,), (False,)]) def test_nan(self, nan_str, has_unit): nan = float(nan_str) ref = self.Q_(nan, " meter" if has_unit else None) test = self.Q_(nan_str + (" meter" if has_unit else "")) assert ref.units == test.units assert math.isnan(test.magnitude) assert ref != test @helpers.requires_numpy def test_to_reduced_units(self): q = self.Q_([3, 4], "s * ms") helpers.assert_quantity_equal( q.to_reduced_units(), self.Q_([3000.0, 4000.0], "ms**2") ) q = self.Q_(0.5, "g*t/kg") helpers.assert_quantity_equal(q.to_reduced_units(), self.Q_(0.5, "kg")) def test_to_reduced_units_dimensionless(self): ureg = UnitRegistry(preprocessors=[lambda x: x.replace("%", " percent ")]) ureg.define("percent = 0.01 count = %") Q_ = ureg.Quantity reduced_quantity = (Q_("1 s") * Q_("5 %") / Q_("1 count")).to_reduced_units() assert reduced_quantity == ureg.Quantity(0.05, ureg.second) @pytest.mark.parametrize( ("unit_str", "expected_unit"), [ ("hour/hr", {}), ("cm centimeter cm centimeter", {"centimeter": 4}), ], ) def test_unit_canonical_name_parsing(self, unit_str, expected_unit): q = self.Q_(1, unit_str) assert q._units == UnitsContainer(expected_unit) # TODO: do not subclass from QuantityTestCase class TestQuantityToCompact(QuantityTestCase): def assertQuantityAlmostIdentical(self, q1, q2): assert q1.units == q2.units assert round(abs(q1.magnitude - q2.magnitude), 7) == 0 def compare_quantity_compact(self, q, expected_compact, unit=None): helpers.assert_quantity_almost_equal(q.to_compact(unit=unit), expected_compact) def test_dimensionally_simple_units(self): ureg = self.ureg self.compare_quantity_compact(1 * ureg.m, 1 * ureg.m) self.compare_quantity_compact(1e-9 * ureg.m, 1 * ureg.nm) def test_power_units(self): ureg = self.ureg self.compare_quantity_compact(900 * ureg.m**2, 900 * ureg.m**2) self.compare_quantity_compact(1e7 * ureg.m**2, 10 * ureg.km**2) def test_inverse_units(self): ureg = self.ureg self.compare_quantity_compact(1 / ureg.m, 1 / ureg.m) self.compare_quantity_compact(100e9 / ureg.m, 100 / ureg.nm) def test_inverse_square_units(self): ureg = self.ureg self.compare_quantity_compact(1 / ureg.m**2, 1 / ureg.m**2) self.compare_quantity_compact(1e11 / ureg.m**2, 1e5 / ureg.mm**2) def test_fractional_units(self): ureg = self.ureg # Typing denominator first to provoke potential error self.compare_quantity_compact(20e3 * ureg("hr^(-1) m"), 20 * ureg.km / ureg.hr) def test_fractional_exponent_units(self): ureg = self.ureg self.compare_quantity_compact(1 * ureg.m**0.5, 1 * ureg.m**0.5) self.compare_quantity_compact(1e-2 * ureg.m**0.5, 10 * ureg.um**0.5) def test_derived_units(self): ureg = self.ureg self.compare_quantity_compact(0.5 * ureg.megabyte, 500 * ureg.kilobyte) self.compare_quantity_compact(1e-11 * ureg.N, 10 * ureg.pN) def test_unit_parameter(self): ureg = self.ureg self.compare_quantity_compact( self.Q_(100e-9, "kg m / s^2"), 100 * ureg.nN, ureg.N ) self.compare_quantity_compact( self.Q_(101.3e3, "kg/m/s^2"), 101.3 * ureg.kPa, ureg.Pa ) def test_limits_magnitudes(self): ureg = self.ureg self.compare_quantity_compact(0 * ureg.m, 0 * ureg.m) self.compare_quantity_compact(float("inf") * ureg.m, float("inf") * ureg.m) def test_nonnumeric_magnitudes(self): ureg = self.ureg x = "some string" * ureg.m with pytest.warns(UndefinedBehavior): self.compare_quantity_compact(x, x) def test_very_large_to_compact(self): # This should not raise an IndexError self.compare_quantity_compact( self.Q_(10000, "yottameter"), self.Q_(10**28, "meter").to_compact() ) # TODO: do not subclass from QuantityTestCase class TestQuantityBasicMath(QuantityTestCase): def _test_inplace(self, operator, value1, value2, expected_result, unit=None): if isinstance(value1, str): value1 = self.Q_(value1) if isinstance(value2, str): value2 = self.Q_(value2) if isinstance(expected_result, str): expected_result = self.Q_(expected_result) if unit is not None: value1 = value1 * unit value2 = value2 * unit expected_result = expected_result * unit value1 = copy.copy(value1) value2 = copy.copy(value2) id1 = id(value1) id2 = id(value2) value1 = operator(value1, value2) value2_cpy = copy.copy(value2) helpers.assert_quantity_almost_equal(value1, expected_result) assert id1 == id(value1) helpers.assert_quantity_almost_equal(value2, value2_cpy) assert id2 == id(value2) def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None): if isinstance(value1, str): value1 = self.Q_(value1) if isinstance(value2, str): value2 = self.Q_(value2) if isinstance(expected_result, str): expected_result = self.Q_(expected_result) if unit is not None: value1 = value1 * unit value2 = value2 * unit expected_result = expected_result * unit id1 = id(value1) id2 = id(value2) value1_cpy = copy.copy(value1) value2_cpy = copy.copy(value2) result = operator(value1, value2) helpers.assert_quantity_almost_equal(expected_result, result) helpers.assert_quantity_almost_equal(value1, value1_cpy) helpers.assert_quantity_almost_equal(value2, value2_cpy) assert id(result) != id1 assert id(result) != id2 def _test_quantity_add_sub(self, unit, func): x = self.Q_(unit, "centimeter") y = self.Q_(unit, "inch") z = self.Q_(unit, "second") a = self.Q_(unit, None) func(op.add, x, x, self.Q_(unit + unit, "centimeter")) func(op.add, x, y, self.Q_(unit + 2.54 * unit, "centimeter")) func(op.add, y, x, self.Q_(unit + unit / (2.54 * unit), "inch")) func(op.add, a, unit, self.Q_(unit + unit, None)) with pytest.raises(DimensionalityError): op.add(10, x) with pytest.raises(DimensionalityError): op.add(x, 10) with pytest.raises(DimensionalityError): op.add(x, z) func(op.sub, x, x, self.Q_(unit - unit, "centimeter")) func(op.sub, x, y, self.Q_(unit - 2.54 * unit, "centimeter")) func(op.sub, y, x, self.Q_(unit - unit / (2.54 * unit), "inch")) func(op.sub, a, unit, self.Q_(unit - unit, None)) with pytest.raises(DimensionalityError): op.sub(10, x) with pytest.raises(DimensionalityError): op.sub(x, 10) with pytest.raises(DimensionalityError): op.sub(x, z) def _test_quantity_iadd_isub(self, unit, func): x = self.Q_(unit, "centimeter") y = self.Q_(unit, "inch") z = self.Q_(unit, "second") a = self.Q_(unit, None) func(op.iadd, x, x, self.Q_(unit + unit, "centimeter")) func(op.iadd, x, y, self.Q_(unit + 2.54 * unit, "centimeter")) func(op.iadd, y, x, self.Q_(unit + unit / 2.54, "inch")) func(op.iadd, a, unit, self.Q_(unit + unit, None)) with pytest.raises(DimensionalityError): op.iadd(10, x) with pytest.raises(DimensionalityError): op.iadd(x, 10) with pytest.raises(DimensionalityError): op.iadd(x, z) func(op.isub, x, x, self.Q_(unit - unit, "centimeter")) func(op.isub, x, y, self.Q_(unit - 2.54, "centimeter")) func(op.isub, y, x, self.Q_(unit - unit / 2.54, "inch")) func(op.isub, a, unit, self.Q_(unit - unit, None)) with pytest.raises(DimensionalityError): op.sub(10, x) with pytest.raises(DimensionalityError): op.sub(x, 10) with pytest.raises(DimensionalityError): op.sub(x, z) def _test_quantity_mul_div(self, unit, func): func(op.mul, unit * 10.0, "4.2*meter", "42*meter", unit) func(op.mul, "4.2*meter", unit * 10.0, "42*meter", unit) func(op.mul, "4.2*meter", "10*inch", "42*meter*inch", unit) func(op.truediv, unit * 42, "4.2*meter", "10/meter", unit) func(op.truediv, "4.2*meter", unit * 10.0, "0.42*meter", unit) func(op.truediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit) def _test_quantity_imul_idiv(self, unit, func): # func(op.imul, 10.0, '4.2*meter', '42*meter') func(op.imul, "4.2*meter", 10.0, "42*meter", unit) func(op.imul, "4.2*meter", "10*inch", "42*meter*inch", unit) # func(op.truediv, 42, '4.2*meter', '10/meter') func(op.itruediv, "4.2*meter", unit * 10.0, "0.42*meter", unit) func(op.itruediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit) def _test_quantity_floordiv(self, unit, func): a = self.Q_("10*meter") b = self.Q_("3*second") with pytest.raises(DimensionalityError): op.floordiv(a, b) with pytest.raises(DimensionalityError): op.floordiv(3, b) with pytest.raises(DimensionalityError): op.floordiv(a, 3) with pytest.raises(DimensionalityError): op.ifloordiv(a, b) with pytest.raises(DimensionalityError): op.ifloordiv(3, b) with pytest.raises(DimensionalityError): op.ifloordiv(a, 3) func(op.floordiv, unit * 10.0, "4.2*meter/meter", 2, unit) func(op.floordiv, "10*meter", "4.2*inch", 93, unit) def _test_quantity_mod(self, unit, func): a = self.Q_("10*meter") b = self.Q_("3*second") with pytest.raises(DimensionalityError): op.mod(a, b) with pytest.raises(DimensionalityError): op.mod(3, b) with pytest.raises(DimensionalityError): op.mod(a, 3) with pytest.raises(DimensionalityError): op.imod(a, b) with pytest.raises(DimensionalityError): op.imod(3, b) with pytest.raises(DimensionalityError): op.imod(a, 3) func(op.mod, unit * 10.0, "4.2*meter/meter", 1.6, unit) def _test_quantity_ifloordiv(self, unit, func): func(op.ifloordiv, 10.0, "4.2*meter/meter", 2, unit) func(op.ifloordiv, "10*meter", "4.2*inch", 93, unit) def _test_quantity_divmod_one(self, a, b): if isinstance(a, str): a = self.Q_(a) if isinstance(b, str): b = self.Q_(b) q, r = divmod(a, b) assert q == a // b assert r == a % b assert a == (q * b) + r assert q == math.floor(q) if b > (0 * b): assert (0 * b) <= r < b else: assert (0 * b) >= r > b if isinstance(a, self.Q_): assert r.units == a.units else: assert r.unitless assert q.unitless copy_a = copy.copy(a) a %= b assert a == r copy_a //= b assert copy_a == q def _test_quantity_divmod(self): self._test_quantity_divmod_one("10*meter", "4.2*inch") self._test_quantity_divmod_one("-10*meter", "4.2*inch") self._test_quantity_divmod_one("-10*meter", "-4.2*inch") self._test_quantity_divmod_one("10*meter", "-4.2*inch") self._test_quantity_divmod_one("400*degree", "3") self._test_quantity_divmod_one("4", "180 degree") self._test_quantity_divmod_one(4, "180 degree") self._test_quantity_divmod_one("20", 4) self._test_quantity_divmod_one("300*degree", "100 degree") a = self.Q_("10*meter") b = self.Q_("3*second") with pytest.raises(DimensionalityError): divmod(a, b) with pytest.raises(DimensionalityError): divmod(3, b) with pytest.raises(DimensionalityError): divmod(a, 3) def _test_numeric(self, unit, ifunc): self._test_quantity_add_sub(unit, self._test_not_inplace) self._test_quantity_iadd_isub(unit, ifunc) self._test_quantity_mul_div(unit, self._test_not_inplace) self._test_quantity_imul_idiv(unit, ifunc) self._test_quantity_floordiv(unit, self._test_not_inplace) self._test_quantity_mod(unit, self._test_not_inplace) self._test_quantity_divmod() # self._test_quantity_ifloordiv(unit, ifunc) def test_float(self): self._test_numeric(1.0, self._test_not_inplace) def test_fraction(self): import fractions self._test_numeric(fractions.Fraction(1, 1), self._test_not_inplace) @helpers.requires_numpy def test_nparray(self): self._test_numeric(np.ones((1, 3)), self._test_inplace) def test_quantity_abs_round(self): x = self.Q_(-4.2, "meter") y = self.Q_(4.2, "meter") for fun in (abs, round, op.pos, op.neg): zx = self.Q_(fun(x.magnitude), "meter") zy = self.Q_(fun(y.magnitude), "meter") rx = fun(x) ry = fun(y) assert rx == zx, f"while testing {fun}" assert ry == zy, f"while testing {fun}" assert rx is not zx, f"while testing {fun}" assert ry is not zy, f"while testing {fun}" def test_quantity_float_complex(self): x = self.Q_(-4.2, None) y = self.Q_(4.2, None) z = self.Q_(1, "meter") for fun in (float, complex): assert fun(x) == fun(x.magnitude) assert fun(y) == fun(y.magnitude) with pytest.raises(DimensionalityError): fun(z) # TODO: do not subclass from QuantityTestCase class TestQuantityNeutralAdd(QuantityTestCase): """Addition to zero or NaN is allowed between a Quantity and a non-Quantity""" def test_bare_zero(self): v = self.Q_(2.0, "m") assert v + 0 == v assert v - 0 == v assert 0 + v == v assert 0 - v == -v def test_bare_zero_inplace(self): v = self.Q_(2.0, "m") v2 = self.Q_(2.0, "m") v2 += 0 assert v2 == v v2 = self.Q_(2.0, "m") v2 -= 0 assert v2 == v v2 = 0 v2 += v assert v2 == v v2 = 0 v2 -= v assert v2 == -v def test_bare_nan(self): v = self.Q_(2.0, "m") helpers.assert_quantity_equal(v + math.nan, self.Q_(math.nan, v.units)) helpers.assert_quantity_equal(v - math.nan, self.Q_(math.nan, v.units)) helpers.assert_quantity_equal(math.nan + v, self.Q_(math.nan, v.units)) helpers.assert_quantity_equal(math.nan - v, self.Q_(math.nan, v.units)) def test_bare_nan_inplace(self): v = self.Q_(2.0, "m") v2 = self.Q_(2.0, "m") v2 += math.nan helpers.assert_quantity_equal(v2, self.Q_(math.nan, v.units)) v2 = self.Q_(2.0, "m") v2 -= math.nan helpers.assert_quantity_equal(v2, self.Q_(math.nan, v.units)) v2 = math.nan v2 += v helpers.assert_quantity_equal(v2, self.Q_(math.nan, v.units)) v2 = math.nan v2 -= v helpers.assert_quantity_equal(v2, self.Q_(math.nan, v.units)) @helpers.requires_numpy def test_bare_zero_or_nan_numpy(self): z = np.array([0.0, np.nan]) v = self.Q_([1.0, 2.0], "m") e = self.Q_([1.0, np.nan], "m") helpers.assert_quantity_equal(z + v, e) helpers.assert_quantity_equal(z - v, -e) helpers.assert_quantity_equal(v + z, e) helpers.assert_quantity_equal(v - z, e) # If any element is non-zero and non-NaN, raise DimensionalityError nz = np.array([0.0, 1.0]) with pytest.raises(DimensionalityError): nz + v with pytest.raises(DimensionalityError): nz - v with pytest.raises(DimensionalityError): v + nz with pytest.raises(DimensionalityError): v - nz # Mismatched shape z = np.array([0.0, np.nan, 0.0]) v = self.Q_([1.0, 2.0], "m") for x, y in ((z, v), (v, z)): with pytest.raises(ValueError): x + y with pytest.raises(ValueError): x - y @helpers.requires_numpy def test_bare_zero_or_nan_numpy_inplace(self): z = np.array([0.0, np.nan]) v = self.Q_([1.0, 2.0], "m") e = self.Q_([1.0, np.nan], "m") v += z helpers.assert_quantity_equal(v, e) v = self.Q_([1.0, 2.0], "m") v -= z helpers.assert_quantity_equal(v, e) v = self.Q_([1.0, 2.0], "m") z = np.array([0.0, np.nan]) z += v helpers.assert_quantity_equal(z, e) v = self.Q_([1.0, 2.0], "m") z = np.array([0.0, np.nan]) z -= v helpers.assert_quantity_equal(z, -e) # TODO: do not subclass from QuantityTestCase class TestDimensions(QuantityTestCase): def test_get_dimensionality(self): get = self.ureg.get_dimensionality assert get("[time]") == UnitsContainer({"[time]": 1}) assert get(UnitsContainer({"[time]": 1})) == UnitsContainer({"[time]": 1}) assert get("seconds") == UnitsContainer({"[time]": 1}) assert get(UnitsContainer({"seconds": 1})) == UnitsContainer({"[time]": 1}) assert get("[velocity]") == UnitsContainer({"[length]": 1, "[time]": -1}) assert get("[acceleration]") == UnitsContainer({"[length]": 1, "[time]": -2}) def test_dimensionality(self): x = self.Q_(42, "centimeter") x.to_base_units() x = self.Q_(42, "meter*second") assert x.dimensionality == UnitsContainer({"[length]": 1.0, "[time]": 1.0}) x = self.Q_(42, "meter*second*second") assert x.dimensionality == UnitsContainer({"[length]": 1.0, "[time]": 2.0}) x = self.Q_(42, "inch*second*second") assert x.dimensionality == UnitsContainer({"[length]": 1.0, "[time]": 2.0}) assert self.Q_(42, None).dimensionless assert not self.Q_(42, "meter").dimensionless assert (self.Q_(42, "meter") / self.Q_(1, "meter")).dimensionless assert not (self.Q_(42, "meter") / self.Q_(1, "second")).dimensionless assert (self.Q_(42, "meter") / self.Q_(1, "inch")).dimensionless def test_inclusion(self): dim = self.Q_(42, "meter").dimensionality assert "[length]" in dim assert "[time]" not in dim dim = (self.Q_(42, "meter") / self.Q_(11, "second")).dimensionality assert "[length]" in dim assert "[time]" in dim dim = self.Q_(20.785, "J/(mol)").dimensionality for dimension in ("[length]", "[mass]", "[substance]", "[time]"): assert dimension in dim assert "[angle]" not in dim class TestQuantityWithDefaultRegistry(TestQuantity): @classmethod def setup_class(cls): from pint import _DEFAULT_REGISTRY cls.ureg = _DEFAULT_REGISTRY cls.U_ = cls.ureg.Unit cls.Q_ = cls.ureg.Quantity class TestDimensionsWithDefaultRegistry(TestDimensions): @classmethod def setup_class(cls): from pint import _DEFAULT_REGISTRY cls.ureg = _DEFAULT_REGISTRY cls.Q_ = cls.ureg.Quantity # TODO: do not subclass from QuantityTestCase class TestOffsetUnitMath(QuantityTestCase): @classmethod def setup_class(cls): super().setup_class() cls.ureg.autoconvert_offset_to_baseunit = False cls.ureg.default_as_delta = True additions = [ # --- input tuple -------------------- | -- expected result -- (((100, "kelvin"), (10, "kelvin")), (110, "kelvin")), (((100, "kelvin"), (10, "degC")), "error"), (((100, "kelvin"), (10, "degF")), "error"), (((100, "kelvin"), (10, "degR")), (105.56, "kelvin")), (((100, "kelvin"), (10, "delta_degC")), (110, "kelvin")), (((100, "kelvin"), (10, "delta_degF")), (105.56, "kelvin")), (((100, "degC"), (10, "kelvin")), "error"), (((100, "degC"), (10, "degC")), "error"), (((100, "degC"), (10, "degF")), "error"), (((100, "degC"), (10, "degR")), "error"), (((100, "degC"), (10, "delta_degC")), (110, "degC")), (((100, "degC"), (10, "delta_degF")), (105.56, "degC")), (((100, "degF"), (10, "kelvin")), "error"), (((100, "degF"), (10, "degC")), "error"), (((100, "degF"), (10, "degF")), "error"), (((100, "degF"), (10, "degR")), "error"), (((100, "degF"), (10, "delta_degC")), (118, "degF")), (((100, "degF"), (10, "delta_degF")), (110, "degF")), (((100, "degR"), (10, "kelvin")), (118, "degR")), (((100, "degR"), (10, "degC")), "error"), (((100, "degR"), (10, "degF")), "error"), (((100, "degR"), (10, "degR")), (110, "degR")), (((100, "degR"), (10, "delta_degC")), (118, "degR")), (((100, "degR"), (10, "delta_degF")), (110, "degR")), (((100, "delta_degC"), (10, "kelvin")), (110, "kelvin")), (((100, "delta_degC"), (10, "degC")), (110, "degC")), (((100, "delta_degC"), (10, "degF")), (190, "degF")), (((100, "delta_degC"), (10, "degR")), (190, "degR")), (((100, "delta_degC"), (10, "delta_degC")), (110, "delta_degC")), (((100, "delta_degC"), (10, "delta_degF")), (105.56, "delta_degC")), (((100, "delta_degF"), (10, "kelvin")), (65.56, "kelvin")), (((100, "delta_degF"), (10, "degC")), (65.56, "degC")), (((100, "delta_degF"), (10, "degF")), (110, "degF")), (((100, "delta_degF"), (10, "degR")), (110, "degR")), (((100, "delta_degF"), (10, "delta_degC")), (118, "delta_degF")), (((100, "delta_degF"), (10, "delta_degF")), (110, "delta_degF")), ] @pytest.mark.parametrize(("input_tuple", "expected"), additions) def test_addition(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = False qin1, qin2 = input_tuple q1, q2 = self.Q_(*qin1), self.Q_(*qin2) # update input tuple with new values to have correct values on failure input_tuple = q1, q2 if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.add(q1, q2) else: expected = self.Q_(*expected) assert op.add(q1, q2).units == expected.units helpers.assert_quantity_almost_equal(op.add(q1, q2), expected, atol=0.01) @helpers.requires_numpy @pytest.mark.parametrize(("input_tuple", "expected"), additions) def test_inplace_addition(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = False (q1v, q1u), (q2v, q2u) = input_tuple # update input tuple with new values to have correct values on failure input_tuple = ( (np.array([q1v] * 2, dtype=float), q1u), (np.array([q2v] * 2, dtype=float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.iadd(q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=float), expected[1] assert op.iadd(q1_cp, q2).units == Q_(*expected).units q1_cp = copy.copy(q1) helpers.assert_quantity_almost_equal( op.iadd(q1_cp, q2), Q_(*expected), atol=0.01 ) subtractions = [ (((100, "kelvin"), (10, "kelvin")), (90, "kelvin")), (((100, "kelvin"), (10, "degC")), (-183.15, "kelvin")), (((100, "kelvin"), (10, "degF")), (-160.93, "kelvin")), (((100, "kelvin"), (10, "degR")), (94.44, "kelvin")), (((100, "kelvin"), (10, "delta_degC")), (90, "kelvin")), (((100, "kelvin"), (10, "delta_degF")), (94.44, "kelvin")), (((100, "degC"), (10, "kelvin")), (363.15, "delta_degC")), (((100, "degC"), (10, "degC")), (90, "delta_degC")), (((100, "degC"), (10, "degF")), (112.22, "delta_degC")), (((100, "degC"), (10, "degR")), (367.59, "delta_degC")), (((100, "degC"), (10, "delta_degC")), (90, "degC")), (((100, "degC"), (10, "delta_degF")), (94.44, "degC")), (((100, "degF"), (10, "kelvin")), (541.67, "delta_degF")), (((100, "degF"), (10, "degC")), (50, "delta_degF")), (((100, "degF"), (10, "degF")), (90, "delta_degF")), (((100, "degF"), (10, "degR")), (549.67, "delta_degF")), (((100, "degF"), (10, "delta_degC")), (82, "degF")), (((100, "degF"), (10, "delta_degF")), (90, "degF")), (((100, "degR"), (10, "kelvin")), (82, "degR")), (((100, "degR"), (10, "degC")), (-409.67, "degR")), (((100, "degR"), (10, "degF")), (-369.67, "degR")), (((100, "degR"), (10, "degR")), (90, "degR")), (((100, "degR"), (10, "delta_degC")), (82, "degR")), (((100, "degR"), (10, "delta_degF")), (90, "degR")), (((100, "delta_degC"), (10, "kelvin")), (90, "kelvin")), (((100, "delta_degC"), (10, "degC")), (90, "degC")), (((100, "delta_degC"), (10, "degF")), (170, "degF")), (((100, "delta_degC"), (10, "degR")), (170, "degR")), (((100, "delta_degC"), (10, "delta_degC")), (90, "delta_degC")), (((100, "delta_degC"), (10, "delta_degF")), (94.44, "delta_degC")), (((100, "delta_degF"), (10, "kelvin")), (45.56, "kelvin")), (((100, "delta_degF"), (10, "degC")), (45.56, "degC")), (((100, "delta_degF"), (10, "degF")), (90, "degF")), (((100, "delta_degF"), (10, "degR")), (90, "degR")), (((100, "delta_degF"), (10, "delta_degC")), (82, "delta_degF")), (((100, "delta_degF"), (10, "delta_degF")), (90, "delta_degF")), ] @pytest.mark.parametrize(("input_tuple", "expected"), subtractions) def test_subtraction(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = False qin1, qin2 = input_tuple q1, q2 = self.Q_(*qin1), self.Q_(*qin2) input_tuple = q1, q2 if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.sub(q1, q2) else: expected = self.Q_(*expected) assert op.sub(q1, q2).units == expected.units helpers.assert_quantity_almost_equal(op.sub(q1, q2), expected, atol=0.01) # @pytest.mark.xfail @helpers.requires_numpy @pytest.mark.parametrize(("input_tuple", "expected"), subtractions) def test_inplace_subtraction(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = False (q1v, q1u), (q2v, q2u) = input_tuple # update input tuple with new values to have correct values on failure input_tuple = ( (np.array([q1v] * 2, dtype=float), q1u), (np.array([q2v] * 2, dtype=float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.isub(q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=float), expected[1] assert op.isub(q1_cp, q2).units == Q_(*expected).units q1_cp = copy.copy(q1) helpers.assert_quantity_almost_equal( op.isub(q1_cp, q2), Q_(*expected), atol=0.01 ) multiplications = [ (((100, "kelvin"), (10, "kelvin")), (1000, "kelvin**2")), (((100, "kelvin"), (10, "degC")), "error"), (((100, "kelvin"), (10, "degF")), "error"), (((100, "kelvin"), (10, "degR")), (1000, "kelvin*degR")), (((100, "kelvin"), (10, "delta_degC")), (1000, "kelvin*delta_degC")), (((100, "kelvin"), (10, "delta_degF")), (1000, "kelvin*delta_degF")), (((100, "degC"), (10, "kelvin")), "error"), (((100, "degC"), (10, "degC")), "error"), (((100, "degC"), (10, "degF")), "error"), (((100, "degC"), (10, "degR")), "error"), (((100, "degC"), (10, "delta_degC")), "error"), (((100, "degC"), (10, "delta_degF")), "error"), (((100, "degF"), (10, "kelvin")), "error"), (((100, "degF"), (10, "degC")), "error"), (((100, "degF"), (10, "degF")), "error"), (((100, "degF"), (10, "degR")), "error"), (((100, "degF"), (10, "delta_degC")), "error"), (((100, "degF"), (10, "delta_degF")), "error"), (((100, "degR"), (10, "kelvin")), (1000, "degR*kelvin")), (((100, "degR"), (10, "degC")), "error"), (((100, "degR"), (10, "degF")), "error"), (((100, "degR"), (10, "degR")), (1000, "degR**2")), (((100, "degR"), (10, "delta_degC")), (1000, "degR*delta_degC")), (((100, "degR"), (10, "delta_degF")), (1000, "degR*delta_degF")), (((100, "delta_degC"), (10, "kelvin")), (1000, "delta_degC*kelvin")), (((100, "delta_degC"), (10, "degC")), "error"), (((100, "delta_degC"), (10, "degF")), "error"), (((100, "delta_degC"), (10, "degR")), (1000, "delta_degC*degR")), (((100, "delta_degC"), (10, "delta_degC")), (1000, "delta_degC**2")), (((100, "delta_degC"), (10, "delta_degF")), (1000, "delta_degC*delta_degF")), (((100, "delta_degF"), (10, "kelvin")), (1000, "delta_degF*kelvin")), (((100, "delta_degF"), (10, "degC")), "error"), (((100, "delta_degF"), (10, "degF")), "error"), (((100, "delta_degF"), (10, "degR")), (1000, "delta_degF*degR")), (((100, "delta_degF"), (10, "delta_degC")), (1000, "delta_degF*delta_degC")), (((100, "delta_degF"), (10, "delta_degF")), (1000, "delta_degF**2")), ] @pytest.mark.parametrize(("input_tuple", "expected"), multiplications) def test_multiplication(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = False qin1, qin2 = input_tuple q1, q2 = self.Q_(*qin1), self.Q_(*qin2) input_tuple = q1, q2 if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.mul(q1, q2) else: expected = self.Q_(*expected) assert op.mul(q1, q2).units == expected.units helpers.assert_quantity_almost_equal(op.mul(q1, q2), expected, atol=0.01) @helpers.requires_numpy @pytest.mark.parametrize(("input_tuple", "expected"), multiplications) def test_inplace_multiplication(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = False (q1v, q1u), (q2v, q2u) = input_tuple # update input tuple with new values to have correct values on failure input_tuple = ( (np.array([q1v] * 2, dtype=float), q1u), (np.array([q2v] * 2, dtype=float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.imul(q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=float), expected[1] assert op.imul(q1_cp, q2).units == Q_(*expected).units q1_cp = copy.copy(q1) helpers.assert_quantity_almost_equal( op.imul(q1_cp, q2), Q_(*expected), atol=0.01 ) divisions = [ (((100, "kelvin"), (10, "kelvin")), (10, "")), (((100, "kelvin"), (10, "degC")), "error"), (((100, "kelvin"), (10, "degF")), "error"), (((100, "kelvin"), (10, "degR")), (10, "kelvin/degR")), (((100, "kelvin"), (10, "delta_degC")), (10, "kelvin/delta_degC")), (((100, "kelvin"), (10, "delta_degF")), (10, "kelvin/delta_degF")), (((100, "degC"), (10, "kelvin")), "error"), (((100, "degC"), (10, "degC")), "error"), (((100, "degC"), (10, "degF")), "error"), (((100, "degC"), (10, "degR")), "error"), (((100, "degC"), (10, "delta_degC")), "error"), (((100, "degC"), (10, "delta_degF")), "error"), (((100, "degF"), (10, "kelvin")), "error"), (((100, "degF"), (10, "degC")), "error"), (((100, "degF"), (10, "degF")), "error"), (((100, "degF"), (10, "degR")), "error"), (((100, "degF"), (10, "delta_degC")), "error"), (((100, "degF"), (10, "delta_degF")), "error"), (((100, "degR"), (10, "kelvin")), (10, "degR/kelvin")), (((100, "degR"), (10, "degC")), "error"), (((100, "degR"), (10, "degF")), "error"), (((100, "degR"), (10, "degR")), (10, "")), (((100, "degR"), (10, "delta_degC")), (10, "degR/delta_degC")), (((100, "degR"), (10, "delta_degF")), (10, "degR/delta_degF")), (((100, "delta_degC"), (10, "kelvin")), (10, "delta_degC/kelvin")), (((100, "delta_degC"), (10, "degC")), "error"), (((100, "delta_degC"), (10, "degF")), "error"), (((100, "delta_degC"), (10, "degR")), (10, "delta_degC/degR")), (((100, "delta_degC"), (10, "delta_degC")), (10, "")), (((100, "delta_degC"), (10, "delta_degF")), (10, "delta_degC/delta_degF")), (((100, "delta_degF"), (10, "kelvin")), (10, "delta_degF/kelvin")), (((100, "delta_degF"), (10, "degC")), "error"), (((100, "delta_degF"), (10, "degF")), "error"), (((100, "delta_degF"), (10, "degR")), (10, "delta_degF/degR")), (((100, "delta_degF"), (10, "delta_degC")), (10, "delta_degF/delta_degC")), (((100, "delta_degF"), (10, "delta_degF")), (10, "")), ] @pytest.mark.parametrize(("input_tuple", "expected"), divisions) def test_truedivision(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = False qin1, qin2 = input_tuple q1, q2 = self.Q_(*qin1), self.Q_(*qin2) input_tuple = q1, q2 if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.truediv(q1, q2) else: expected = self.Q_(*expected) assert op.truediv(q1, q2).units == expected.units helpers.assert_quantity_almost_equal( op.truediv(q1, q2), expected, atol=0.01 ) @helpers.requires_numpy @pytest.mark.parametrize(("input_tuple", "expected"), divisions) def test_inplace_truedivision(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = False (q1v, q1u), (q2v, q2u) = input_tuple # update input tuple with new values to have correct values on failure input_tuple = ( (np.array([q1v] * 2, dtype=float), q1u), (np.array([q2v] * 2, dtype=float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.itruediv(q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=float), expected[1] assert op.itruediv(q1_cp, q2).units == Q_(*expected).units q1_cp = copy.copy(q1) helpers.assert_quantity_almost_equal( op.itruediv(q1_cp, q2), Q_(*expected), atol=0.01 ) multiplications_with_autoconvert_to_baseunit = [ (((100, "kelvin"), (10, "degC")), (28315.0, "kelvin**2")), (((100, "kelvin"), (10, "degF")), (26092.78, "kelvin**2")), (((100, "degC"), (10, "kelvin")), (3731.5, "kelvin**2")), (((100, "degC"), (10, "degC")), (105657.42, "kelvin**2")), (((100, "degC"), (10, "degF")), (97365.20, "kelvin**2")), (((100, "degC"), (10, "degR")), (3731.5, "kelvin*degR")), (((100, "degC"), (10, "delta_degC")), (3731.5, "kelvin*delta_degC")), (((100, "degC"), (10, "delta_degF")), (3731.5, "kelvin*delta_degF")), (((100, "degF"), (10, "kelvin")), (3109.28, "kelvin**2")), (((100, "degF"), (10, "degC")), (88039.20, "kelvin**2")), (((100, "degF"), (10, "degF")), (81129.69, "kelvin**2")), (((100, "degF"), (10, "degR")), (3109.28, "kelvin*degR")), (((100, "degF"), (10, "delta_degC")), (3109.28, "kelvin*delta_degC")), (((100, "degF"), (10, "delta_degF")), (3109.28, "kelvin*delta_degF")), (((100, "degR"), (10, "degC")), (28315.0, "degR*kelvin")), (((100, "degR"), (10, "degF")), (26092.78, "degR*kelvin")), (((100, "delta_degC"), (10, "degC")), (28315.0, "delta_degC*kelvin")), (((100, "delta_degC"), (10, "degF")), (26092.78, "delta_degC*kelvin")), (((100, "delta_degF"), (10, "degC")), (28315.0, "delta_degF*kelvin")), (((100, "delta_degF"), (10, "degF")), (26092.78, "delta_degF*kelvin")), ] @pytest.mark.parametrize( ("input_tuple", "expected"), multiplications_with_autoconvert_to_baseunit ) def test_multiplication_with_autoconvert(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = True qin1, qin2 = input_tuple q1, q2 = self.Q_(*qin1), self.Q_(*qin2) input_tuple = q1, q2 if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.mul(q1, q2) else: expected = self.Q_(*expected) assert op.mul(q1, q2).units == expected.units helpers.assert_quantity_almost_equal(op.mul(q1, q2), expected, atol=0.01) @helpers.requires_numpy @pytest.mark.parametrize( ("input_tuple", "expected"), multiplications_with_autoconvert_to_baseunit ) def test_inplace_multiplication_with_autoconvert(self, input_tuple, expected): self.ureg.autoconvert_offset_to_baseunit = True (q1v, q1u), (q2v, q2u) = input_tuple # update input tuple with new values to have correct values on failure input_tuple = ( (np.array([q1v] * 2, dtype=float), q1u), (np.array([q2v] * 2, dtype=float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.imul(q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=float), expected[1] assert op.imul(q1_cp, q2).units == Q_(*expected).units q1_cp = copy.copy(q1) helpers.assert_quantity_almost_equal( op.imul(q1_cp, q2), Q_(*expected), atol=0.01 ) multiplications_with_scalar = [ (((10, "kelvin"), 2), (20.0, "kelvin")), (((10, "kelvin**2"), 2), (20.0, "kelvin**2")), (((10, "degC"), 2), (20.0, "degC")), (((10, "1/degC"), 2), "error"), (((10, "degC**0.5"), 2), "error"), (((10, "degC**2"), 2), "error"), (((10, "degC**-2"), 2), "error"), ] @pytest.mark.parametrize(("input_tuple", "expected"), multiplications_with_scalar) def test_multiplication_with_scalar(self, input_tuple, expected): self.ureg.default_as_delta = False in1, in2 = input_tuple if type(in1) is tuple: in1, in2 = self.Q_(*in1), in2 else: in1, in2 = in1, self.Q_(*in2) input_tuple = in1, in2 # update input_tuple for better tracebacks if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.mul(in1, in2) else: expected = self.Q_(*expected) assert op.mul(in1, in2).units == expected.units helpers.assert_quantity_almost_equal(op.mul(in1, in2), expected, atol=0.01) divisions_with_scalar = [ # without / with autoconvert to plain unit (((10, "kelvin"), 2), [(5.0, "kelvin"), (5.0, "kelvin")]), (((10, "kelvin**2"), 2), [(5.0, "kelvin**2"), (5.0, "kelvin**2")]), (((10, "degC"), 2), ["error", "error"]), (((10, "degC**2"), 2), ["error", "error"]), (((10, "degC**-2"), 2), ["error", "error"]), ((2, (10, "kelvin")), [(0.2, "1/kelvin"), (0.2, "1/kelvin")]), ((2, (10, "degC")), ["error", (2 / 283.15, "1/kelvin")]), ((2, (10, "degC**2")), ["error", "error"]), ((2, (10, "degC**-2")), ["error", "error"]), ] @pytest.mark.parametrize(("input_tuple", "expected"), divisions_with_scalar) def test_division_with_scalar(self, input_tuple, expected): self.ureg.default_as_delta = False in1, in2 = input_tuple if type(in1) is tuple: in1, in2 = self.Q_(*in1), in2 else: in1, in2 = in1, self.Q_(*in2) input_tuple = in1, in2 # update input_tuple for better tracebacks expected_copy = expected.copy() for i, mode in enumerate([False, True]): self.ureg.autoconvert_offset_to_baseunit = mode if expected_copy[i] == "error": with pytest.raises(OffsetUnitCalculusError): op.truediv(in1, in2) else: expected = self.Q_(*expected_copy[i]) assert op.truediv(in1, in2).units == expected.units helpers.assert_quantity_almost_equal(op.truediv(in1, in2), expected) exponentiation = [ # results without / with autoconvert (((10, "degC"), 1), [(10, "degC"), (10, "degC")]), (((10, "degC"), 0.5), ["error", (283.15**0.5, "kelvin**0.5")]), (((10, "degC"), 0), [(1.0, ""), (1.0, "")]), (((10, "degC"), -1), ["error", (1 / (10 + 273.15), "kelvin**-1")]), (((10, "degC"), -2), ["error", (1 / (10 + 273.15) ** 2.0, "kelvin**-2")]), (((0, "degC"), -2), ["error", (1 / 273.15**2, "kelvin**-2")]), (((10, "degC"), (2, "")), ["error", (283.15**2, "kelvin**2")]), (((10, "degC"), (10, "degK")), ["error", "error"]), (((10, "kelvin"), (2, "")), [(100.0, "kelvin**2"), (100.0, "kelvin**2")]), ((2, (2, "kelvin")), ["error", "error"]), ((2, (500.0, "millikelvin/kelvin")), [2**0.5, 2**0.5]), ((2, (0.5, "kelvin/kelvin")), [2**0.5, 2**0.5]), ( ((10, "degC"), (500.0, "millikelvin/kelvin")), ["error", (283.15**0.5, "kelvin**0.5")], ), ] @pytest.mark.parametrize(("input_tuple", "expected"), exponentiation) def test_exponentiation(self, input_tuple, expected): self.ureg.default_as_delta = False in1, in2 = input_tuple if type(in1) is type(in2) is tuple: in1, in2 = self.Q_(*in1), self.Q_(*in2) elif type(in1) is not tuple and type(in2) is tuple: in2 = self.Q_(*in2) else: in1 = self.Q_(*in1) input_tuple = in1, in2 expected_copy = expected.copy() for i, mode in enumerate([False, True]): self.ureg.autoconvert_offset_to_baseunit = mode if expected_copy[i] == "error": with pytest.raises((OffsetUnitCalculusError, DimensionalityError)): op.pow(in1, in2) else: if type(expected_copy[i]) is tuple: expected = self.Q_(*expected_copy[i]) assert op.pow(in1, in2).units == expected.units else: expected = expected_copy[i] helpers.assert_quantity_almost_equal(op.pow(in1, in2), expected) @helpers.requires_numpy def test_exponentiation_force_ndarray(self): ureg = UnitRegistry(force_ndarray_like=True) q = ureg.Quantity(1, "1 / hours") q1 = q**2 assert all(isinstance(v, int) for v in q1._units.values()) q2 = q.copy() q2 **= 2 assert all(isinstance(v, int) for v in q2._units.values()) @helpers.requires_numpy @pytest.mark.parametrize(("input_tuple", "expected"), exponentiation) def test_inplace_exponentiation(self, input_tuple, expected): self.ureg.default_as_delta = False in1, in2 = input_tuple if type(in1) is type(in2) is tuple: (q1v, q1u), (q2v, q2u) = in1, in2 in1 = self.Q_(*(np.array([q1v] * 2, dtype=float), q1u)) in2 = self.Q_(q2v, q2u) elif type(in1) is not tuple and type(in2) is tuple: in2 = self.Q_(*in2) else: in1 = self.Q_(*in1) input_tuple = in1, in2 expected_copy = expected.copy() for i, mode in enumerate([False, True]): self.ureg.autoconvert_offset_to_baseunit = mode in1_cp = copy.copy(in1) if expected_copy[i] == "error": with pytest.raises((OffsetUnitCalculusError, DimensionalityError)): op.ipow(in1_cp, in2) else: if type(expected_copy[i]) is tuple: expected = self.Q_( np.array([expected_copy[i][0]] * 2, dtype=float), expected_copy[i][1], ) assert op.ipow(in1_cp, in2).units == expected.units else: expected = np.array([expected_copy[i]] * 2, dtype=float) in1_cp = copy.copy(in1) helpers.assert_quantity_almost_equal(op.ipow(in1_cp, in2), expected) # matmul is only a ufunc since 1.16 @helpers.requires_numpy_at_least("1.16") def test_matmul_with_numpy(self): A = [[1, 2], [3, 4]] * self.ureg.m B = np.array([[0, -1], [-1, 0]]) b = [[1], [0]] * self.ureg.m helpers.assert_quantity_equal(A @ B, [[-2, -1], [-4, -3]] * self.ureg.m) helpers.assert_quantity_equal(A @ b, [[1], [3]] * self.ureg.m**2) helpers.assert_quantity_equal(B @ b, [[0], [-1]] * self.ureg.m) class TestDimensionReduction: def _calc_mass(self, ureg): density = 3 * ureg.g / ureg.L volume = 32 * ureg.milliliter return density * volume def _icalc_mass(self, ureg): res = ureg.Quantity(3.0, "gram/liter") res *= ureg.Quantity(32.0, "milliliter") return res def test_mul_and_div_reduction(self): ureg = UnitRegistry(auto_reduce_dimensions=True) mass = self._calc_mass(ureg) assert mass.units == ureg.g ureg = UnitRegistry(auto_reduce_dimensions=False) mass = self._calc_mass(ureg) assert mass.units == ureg.g / ureg.L * ureg.milliliter @helpers.requires_numpy def test_imul_and_div_reduction(self): ureg = UnitRegistry(auto_reduce_dimensions=True, force_ndarray=True) mass = self._icalc_mass(ureg) assert mass.units == ureg.g ureg = UnitRegistry(auto_reduce_dimensions=False, force_ndarray=True) mass = self._icalc_mass(ureg) assert mass.units == ureg.g / ureg.L * ureg.milliliter def test_reduction_to_dimensionless(self): ureg = UnitRegistry(auto_reduce_dimensions=True) x = (10 * ureg.feet) / (3 * ureg.inches) assert x.units == UnitsContainer({}) ureg = UnitRegistry(auto_reduce_dimensions=False) x = (10 * ureg.feet) / (3 * ureg.inches) assert x.units == ureg.feet / ureg.inches def test_nocoerce_creation(self): ureg = UnitRegistry(auto_reduce_dimensions=True) x = 1 * ureg.foot assert x.units == ureg.foot # TODO: do not subclass from QuantityTestCase class TestTimedelta(QuantityTestCase): def test_add_sub(self): d = datetime.datetime(year=1968, month=1, day=10, hour=3, minute=42, second=24) after = d + 3 * self.ureg.second assert d + datetime.timedelta(seconds=3) == after after = 3 * self.ureg.second + d assert d + datetime.timedelta(seconds=3) == after after = d - 3 * self.ureg.second assert d - datetime.timedelta(seconds=3) == after with pytest.raises(DimensionalityError): 3 * self.ureg.second - d def test_iadd_isub(self): d = datetime.datetime(year=1968, month=1, day=10, hour=3, minute=42, second=24) after = copy.copy(d) after += 3 * self.ureg.second assert d + datetime.timedelta(seconds=3) == after after = 3 * self.ureg.second after += d assert d + datetime.timedelta(seconds=3) == after after = copy.copy(d) after -= 3 * self.ureg.second assert d - datetime.timedelta(seconds=3) == after after = 3 * self.ureg.second with pytest.raises(DimensionalityError): after -= d # TODO: do not subclass from QuantityTestCase class TestCompareNeutral(QuantityTestCase): """Test comparisons against non-Quantity zero or NaN values for for non-dimensionless quantities """ def test_equal_zero(self): self.ureg.autoconvert_offset_to_baseunit = False assert self.Q_(0, "J") == 0 assert not (self.Q_(0, "J") == self.Q_(0, "")) assert not (self.Q_(5, "J") == 0) def test_equal_nan(self): # nan == nan returns False self.ureg.autoconvert_offset_to_baseunit = False assert not (self.Q_(math.nan, "J") == 0) assert not (self.Q_(math.nan, "J") == math.nan) assert not (self.Q_(math.nan, "J") == self.Q_(math.nan, "")) assert not (self.Q_(5, "J") == math.nan) @helpers.requires_numpy def test_equal_zero_nan_NP(self): self.ureg.autoconvert_offset_to_baseunit = False aeq = np.testing.assert_array_equal aeq(self.Q_(0, "J") == np.array([0, np.nan]), np.array([True, False])) aeq(self.Q_(5, "J") == np.array([0, np.nan]), np.array([False, False])) aeq( self.Q_([0, 1, 2], "J") == np.array([0, 0, np.nan]), np.asarray([True, False, False]), ) # This raise an exception on NumPy 1.25 as dimensions # are different # assert not (self.Q_(np.arange(4), "J") == np.zeros(3)) def test_offset_equal_zero(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = False q0 = ureg.Quantity(-273.15, "degC") q1 = ureg.Quantity(0, "degC") q2 = ureg.Quantity(5, "degC") with pytest.raises(OffsetUnitCalculusError): q0.__eq__(0) with pytest.raises(OffsetUnitCalculusError): q1.__eq__(0) with pytest.raises(OffsetUnitCalculusError): q2.__eq__(0) assert not (q0 == ureg.Quantity(0, "")) def test_offset_autoconvert_equal_zero(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = True q0 = ureg.Quantity(-273.15, "degC") q1 = ureg.Quantity(0, "degC") q2 = ureg.Quantity(5, "degC") assert q0 == 0 assert not (q1 == 0) assert not (q2 == 0) assert not (q0 == ureg.Quantity(0, "")) def test_gt_zero(self): self.ureg.autoconvert_offset_to_baseunit = False q0 = self.Q_(0, "J") q0m = self.Q_(0, "m") q0less = self.Q_(0, "") qpos = self.Q_(5, "J") qneg = self.Q_(-5, "J") assert qpos > q0 assert qpos > 0 assert not (qneg > 0) with pytest.raises(DimensionalityError): qpos > q0less with pytest.raises(DimensionalityError): qpos > q0m def test_gt_nan(self): self.ureg.autoconvert_offset_to_baseunit = False qn = self.Q_(math.nan, "J") qnm = self.Q_(math.nan, "m") qnless = self.Q_(math.nan, "") qpos = self.Q_(5, "J") assert not (qpos > qn) assert not (qpos > math.nan) with pytest.raises(DimensionalityError): qpos > qnless with pytest.raises(DimensionalityError): qpos > qnm @helpers.requires_numpy def test_gt_zero_nan_NP(self): self.ureg.autoconvert_offset_to_baseunit = False qpos = self.Q_(5, "J") qneg = self.Q_(-5, "J") aeq = np.testing.assert_array_equal aeq(qpos > np.array([0, np.nan]), np.asarray([True, False])) aeq(qneg > np.array([0, np.nan]), np.asarray([False, False])) aeq( self.Q_(np.arange(-2, 3), "J") > np.array([np.nan, 0, 0, 0, np.nan]), np.asarray([False, False, False, True, False]), ) with pytest.raises(ValueError): self.Q_(np.arange(-1, 2), "J") > np.zeros(4) def test_offset_gt_zero(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = False q0 = ureg.Quantity(-273.15, "degC") q1 = ureg.Quantity(0, "degC") q2 = ureg.Quantity(5, "degC") with pytest.raises(OffsetUnitCalculusError): q0.__gt__(0) with pytest.raises(OffsetUnitCalculusError): q1.__gt__(0) with pytest.raises(OffsetUnitCalculusError): q2.__gt__(0) with pytest.raises(DimensionalityError): q1.__gt__(ureg.Quantity(0, "")) def test_offset_autoconvert_gt_zero(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = True q0 = ureg.Quantity(-273.15, "degC") q1 = ureg.Quantity(0, "degC") q2 = ureg.Quantity(5, "degC") assert not (q0 > 0) assert q1 > 0 assert q2 > 0 with pytest.raises(DimensionalityError): q1.__gt__(ureg.Quantity(0, "")) def test_types(self): quantity = self.Q_(1.0, "m") assert isinstance(quantity, self.Q_) assert isinstance(quantity.units, self.ureg.Unit) assert isinstance(quantity.m, float) assert isinstance(self.ureg.m, self.ureg.Unit) pint-0.24.4/pint/testsuite/test_systems.py000066400000000000000000000230661471316474000207140ustar00rootroot00000000000000from __future__ import annotations import pytest from pint import UnitRegistry from pint.testsuite import QuantityTestCase from .helpers import internal class TestGroup: def _build_empty_reg_root(self): ureg = UnitRegistry(None) grp = ureg.get_group("root") grp.invalidate_members() return ureg, ureg.get_group("root") def test_units_programmatically(self): ureg, root = self._build_empty_reg_root() d = internal(ureg)._groups assert root._used_groups == set() assert root._used_by == set() root.add_units("meter", "second", "meter") assert root._unit_names == {"meter", "second"} assert root.members == {"meter", "second"} assert d.keys() == {"root"} def test_cyclic(self): ureg, root = self._build_empty_reg_root() g2 = ureg.Group("g2") g3 = ureg.Group("g3") g2.add_groups("g3") with pytest.raises(ValueError): g2.add_groups("root") with pytest.raises(ValueError): g3.add_groups("g2") with pytest.raises(ValueError): g3.add_groups("root") def test_groups_programmatically(self): ureg, root = self._build_empty_reg_root() d = internal(ureg)._groups g2 = ureg.Group("g2") assert d.keys() == {"root", "g2"} assert root._used_groups == {"g2"} assert root._used_by == set() assert g2._used_groups == set() assert g2._used_by == {"root"} def test_simple(self): lines = ["@group mygroup", "meter = 3", "second = 2"] ureg, root = self._build_empty_reg_root() d = internal(ureg)._groups grp = ureg.Group.from_lines(lines, lambda x: None) assert d.keys() == {"root", "mygroup"} assert grp.name == "mygroup" assert grp._unit_names == {"meter", "second"} assert grp._used_groups == set() assert grp._used_by == {root.name} assert grp.members == frozenset(["meter", "second"]) def test_using1(self): lines = ["@group mygroup using group1", "meter = 2", "second = 3"] ureg, root = self._build_empty_reg_root() ureg.Group("group1") grp = ureg.Group.from_lines(lines, lambda x: None) assert grp.name == "mygroup" assert grp._unit_names == {"meter", "second"} assert grp._used_groups == {"group1"} assert grp.members == frozenset(["meter", "second"]) def test_using2(self): lines = ["@group mygroup using group1,group2", "meter = 2", "second = 3"] ureg, root = self._build_empty_reg_root() ureg.Group("group1") ureg.Group("group2") grp = ureg.Group.from_lines(lines, lambda x: None) assert grp.name == "mygroup" assert grp._unit_names == {"meter", "second"} assert grp._used_groups == {"group1", "group2"} assert grp.members == frozenset(["meter", "second"]) def test_spaces(self): lines = [ "@group mygroup using group1 , group2", " meter = 2", " second = 3", ] ureg, root = self._build_empty_reg_root() ureg.Group("group1") ureg.Group("group2") grp = ureg.Group.from_lines(lines, lambda x: None) assert grp.name == "mygroup" assert grp._unit_names == {"meter", "second"} assert grp._used_groups == {"group1", "group2"} assert grp.members == frozenset(["meter", "second"]) def test_invalidate_members(self): lines = ["@group mygroup using group1", "meter = 2 ", "second = 3"] ureg, root = self._build_empty_reg_root() ureg.Group("group1") grp = ureg.Group.from_lines(lines, lambda x: None) assert root._computed_members is None assert grp._computed_members is None assert grp.members == frozenset(["meter", "second"]) assert root._computed_members is None assert grp._computed_members is not None assert root.members == frozenset(["meter", "second"]) assert root._computed_members is not None assert grp._computed_members is not None grp.invalidate_members() assert root._computed_members is None assert grp._computed_members is None def test_with_defintions(self): lines = [ "@group imperial", "kings_leg = 2 * meter", "kings_head = 52 * inch", ] defs = [] def define(ud): defs.append(ud.name) ureg, root = self._build_empty_reg_root() ureg.Group.from_lines(lines, define) assert ["kings_leg", "kings_head"] == defs def test_members_including(self): ureg, root = self._build_empty_reg_root() g1 = ureg.Group("group1") g1.add_units("second", "inch") g2 = ureg.Group("group2") g2.add_units("second", "newton") g3 = ureg.Group("group3") g3.add_units("meter", "second") g3.add_groups("group1", "group2") assert root.members == frozenset(["meter", "second", "newton", "inch"]) assert g1.members == frozenset(["second", "inch"]) assert g2.members == frozenset(["second", "newton"]) assert g3.members == frozenset(["meter", "second", "newton", "inch"]) def test_get_compatible_units(self): ureg = UnitRegistry() g = ureg.get_group("test-imperial") g.add_units("inch", "yard", "pint") c = ureg.get_compatible_units("meter", "test-imperial") assert c == frozenset([ureg.inch, ureg.yard]) # TODO: do not subclass from QuantityTestCase class TestSystem(QuantityTestCase): def _build_empty_reg_root(self): ureg = UnitRegistry(None) grp = ureg.get_group("root") grp.invalidate_members() return ureg, ureg.get_group("root") def test_implicit_root(self): lines = ["@system mks", "meter", "kilogram", "second"] ureg, root = self._build_empty_reg_root() s = ureg.System.from_lines(lines, lambda x: x) s._used_groups = {"root"} def test_simple_using(self): lines = ["@system mks using g1", "meter", "kilogram", "second"] ureg, root = self._build_empty_reg_root() s = ureg.System.from_lines(lines, lambda x: x) s._used_groups = {"root", "g1"} def test_members_group(self): lines = ["@system mk", "meter", "kilogram"] ureg, root = self._build_empty_reg_root() root.add_units("second") s = ureg.System.from_lines(lines, lambda x: x) assert s.members == frozenset(["second"]) def test_get_compatible_units(self): sysname = "mysys1" ureg = UnitRegistry() g = ureg.get_group("test-imperial") g.add_units("inch", "yard", "pint") c = ureg.get_compatible_units("meter", "test-imperial") assert c == frozenset([ureg.inch, ureg.yard]) lines = ["@system %s using test-imperial" % sysname, "inch"] ureg.System.from_lines(lines, lambda x: x) c = ureg.get_compatible_units("meter", sysname) assert c == frozenset([ureg.inch, ureg.yard]) def test_get_base_units(self): sysname = "mysys2" ureg = UnitRegistry() g = ureg.get_group("test-imperial") g.add_units("inch", "yard", "pint") lines = ["@system %s using test-imperial" % sysname, "inch"] s = ureg.System.from_lines(lines, ureg.get_base_units) internal(ureg)._systems[s.name] = s # base_factor, destination_units c = ureg.get_base_units("inch", system=sysname) assert round(abs(c[0] - 1), 7) == 0 assert c[1] == {"inch": 1} c = ureg.get_base_units("cm", system=sysname) assert round(abs(c[0] - 1.0 / 2.54), 7) == 0 assert c[1] == {"inch": 1} def test_get_base_units_different_exponent(self): sysname = "mysys3" ureg = UnitRegistry() g = ureg.get_group("test-imperial") g.add_units("inch", "yard", "pint") ureg.get_compatible_units("meter", "test-imperial") lines = ["@system %s using test-imperial" % sysname, "pint:meter"] s = ureg.System.from_lines(lines, ureg.get_base_units) internal(ureg)._systems[s.name] = s # base_factor, destination_units c = ureg.get_base_units("inch", system=sysname) assert round(abs(c[0] - 0.326), 3) == 0 assert c[1] == {"pint": 1.0 / 3} c = ureg.get_base_units("cm", system=sysname) assert round(abs(c[0] - 0.1283), 3) == 0 assert c[1] == {"pint": 1.0 / 3} c = ureg.get_base_units("inch**2", system=sysname) assert round(abs(c[0] - 0.326**2), 3) == 0 assert c[1] == {"pint": 2.0 / 3} c = ureg.get_base_units("cm**2", system=sysname) assert round(abs(c[0] - 0.1283**2), 3) == 0 assert c[1] == {"pint": 2.0 / 3} def test_get_base_units_relation(self): sysname = "mysys4" ureg = UnitRegistry() g = ureg.get_group("test-imperial") g.add_units("inch", "yard", "pint") lines = ["@system %s using test-imperial" % sysname, "mph:meter"] s = ureg.System.from_lines(lines, ureg.get_base_units) internal(ureg)._systems[s.name] = s # base_factor, destination_units c = ureg.get_base_units("inch", system=sysname) assert round(abs(c[0] - 0.056), 2) == 0 assert c[1] == {"mph": 1, "second": 1} c = ureg.get_base_units("kph", system=sysname) assert round(abs(c[0] - 0.6213), 3) == 0 assert c[1] == {"mph": 1} def test_members_nowarning(self): ureg = self.ureg for name in dir(ureg.sys): dir(getattr(ureg.sys, name)) pint-0.24.4/pint/testsuite/test_testing.py000066400000000000000000000065371471316474000206660ustar00rootroot00000000000000from __future__ import annotations from typing import Any import pytest from .. import testing np = pytest.importorskip("numpy") class QuantityToBe(tuple[Any]): def from_many(*args): return QuantityToBe(args) @pytest.mark.parametrize( ["first", "second", "error", "message"], ( pytest.param( np.array([0, 1]), np.array([0, 1]), False, "", id="ndarray-None-None-equal" ), pytest.param( QuantityToBe.from_many(1, "m"), 1, True, "The first is not dimensionless", id="mixed1-int-not equal-equal", ), pytest.param( 1, QuantityToBe.from_many(1, "m"), True, "The second is not dimensionless", id="mixed2-int-not equal-equal", ), pytest.param( QuantityToBe.from_many(1, "m"), QuantityToBe.from_many(1, "m"), False, "", id="QuantityToBe.from_many-int-equal-equal", ), pytest.param( QuantityToBe.from_many(1, "m"), QuantityToBe.from_many(1, "s"), True, "Units are not equal", id="QuantityToBe.from_many-int-equal-not equal", ), pytest.param( QuantityToBe.from_many(1, "m"), QuantityToBe.from_many(2, "m"), True, "Magnitudes are not equal", id="QuantityToBe.from_many-int-not equal-equal", ), pytest.param( QuantityToBe.from_many(1, "m"), QuantityToBe.from_many(2, "s"), True, "Units are not equal", id="QuantityToBe.from_many-int-not equal-not equal", ), pytest.param( QuantityToBe.from_many(1, "m"), QuantityToBe.from_many(float("nan"), "m"), True, "Magnitudes are not equal", id="QuantityToBe.from_many-float-not equal-equal", ), pytest.param( QuantityToBe.from_many([1, 2], "m"), QuantityToBe.from_many([1, 2], "m"), False, "", id="QuantityToBe.from_many-ndarray-equal-equal", ), pytest.param( QuantityToBe.from_many([1, 2], "m"), QuantityToBe.from_many([1, 2], "s"), True, "Units are not equal", id="QuantityToBe.from_many-ndarray-equal-not equal", ), pytest.param( QuantityToBe.from_many([1, 2], "m"), QuantityToBe.from_many([2, 2], "m"), True, "Magnitudes are not equal", id="QuantityToBe.from_many-ndarray-not equal-equal", ), pytest.param( QuantityToBe.from_many([1, 2], "m"), QuantityToBe.from_many([2, 2], "s"), True, "Units are not equal", id="QuantityToBe.from_many-ndarray-not equal-not equal", ), ), ) def test_assert_equal(sess_registry, first, second, error, message): if isinstance(first, QuantityToBe): first = sess_registry.Quantity(*first) if isinstance(second, QuantityToBe): second = sess_registry.Quantity(*second) if error: with pytest.raises(AssertionError, match=message): testing.assert_equal(first, second) else: testing.assert_equal(first, second) pint-0.24.4/pint/testsuite/test_umath.py000066400000000000000000000656341471316474000203320ustar00rootroot00000000000000from __future__ import annotations import pytest from pint import DimensionalityError, UnitRegistry from pint.compat import np from pint.testsuite import helpers # Following http://docs.scipy.org/doc/numpy/reference/ufuncs.html if np: pi = np.pi @helpers.requires_numpy class TestUFuncs: @classmethod def setup_class(cls): cls.ureg = UnitRegistry(force_ndarray=True) cls.Q_ = cls.ureg.Quantity @classmethod def teardown_class(cls): cls.ureg = None cls.Q_ = None @property def qless(self): return np.asarray([1.0, 2.0, 3.0, 4.0]) * self.ureg.dimensionless @property def qs(self): return 8 * self.ureg.J @property def q1(self): return np.asarray([1.0, 2.0, 3.0, 4.0]) * self.ureg.J @property def q2(self): return 2 * self.q1 @property def qm(self): return np.asarray([1.0, 2.0, 3.0, 4.0]) * self.ureg.m @property def qi(self): return np.asarray([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j]) * self.ureg.m def _test1( self, func, ok_with, raise_with=(), output_units="same", results=None, rtol=1e-6 ): """Test function that takes a single argument and returns Quantity. Parameters ---------- func : function callable. ok_with : iterables of values that work fine. raise_with : iterables of values that raise exceptions. (Default value = ()) output_units : units to be used when building results. 'same': ok_with[n].units (default). is float: ok_with[n].units ** output_units. None: no output units, the result should be an ndarray. Other value will be parsed as unit. results : iterable of results. If None, the result will be obtained by applying func to each ok_with value (Default value = None) rtol : relative tolerance. (Default value = 1e-6) Returns ------- """ if results is None: results = [None] * len(ok_with) for x1, res in zip(ok_with, results): err_msg = f"At {func.__name__} with {x1}" if output_units == "same": ou = x1.units elif isinstance(output_units, (int, float)): ou = x1.units**output_units else: ou = output_units qm = func(x1) if res is None: res = func(x1.magnitude) if ou is not None: res = self.Q_(res, ou) helpers.assert_quantity_almost_equal(qm, res, rtol=rtol, msg=err_msg) for x1 in raise_with: with pytest.raises(DimensionalityError): func(x1) def _testn(self, func, ok_with, raise_with=(), results=None): """Test function that takes a single argument and returns and ndarray (not a Quantity) Parameters ---------- func : function callable. ok_with : iterables of values that work fine. raise_with : iterables of values that raise exceptions. (Default value = ()) results : iterable of results. If None, the result will be obtained by applying func to each ok_with value (Default value = None) Returns ------- """ self._test1(func, ok_with, raise_with, output_units=None, results=results) def _test1_2o( self, func, ok_with, raise_with=(), output_units=("same", "same"), results=None, rtol=1e-6, ): """Test functions that takes a single argument and return two Quantities. Parameters ---------- func : function callable. ok_with : iterables of values that work fine. raise_with : iterables of values that raise exceptions. (Default value = ()) output_units : tuple of units to be used when building the result tuple. 'same': ok_with[n].units (default). is float: ok_with[n].units ** output_units. None: no output units, the result should be an ndarray. Other value will be parsed as unit. results : iterable of results. If None, the result will be obtained by applying func to each ok_with value (Default value = None) rtol : relative tolerance. (Default value = 1e-6) "same") : Returns ------- """ if results is None: results = [None] * len(ok_with) for x1, res in zip(ok_with, results): err_msg = f"At {func.__name__} with {x1}" qms = func(x1) if res is None: res = func(x1.magnitude) for ndx, (qm, re, ou) in enumerate(zip(qms, res, output_units)): if ou == "same": ou = x1.units elif isinstance(ou, (int, float)): ou = x1.units**ou if ou is not None: re = self.Q_(re, ou) helpers.assert_quantity_almost_equal(qm, re, rtol=rtol, msg=err_msg) for x1 in raise_with: with pytest.raises(ValueError): func(x1) def _test2( self, func, x1, ok_with, raise_with=(), output_units="same", rtol=1e-6, convert2=True, ): """Test function that takes two arguments and return a Quantity. Parameters ---------- func : function callable. x1 : first argument of func. ok_with : iterables of values that work fine. raise_with : iterables of values that raise exceptions. (Default value = ()) output_units : units to be used when building results. 'same': x1.units (default). 'prod': x1.units * ok_with[n].units 'div': x1.units / ok_with[n].units 'second': x1.units * ok_with[n] None: no output units, the result should be an ndarray. Other value will be parsed as unit. rtol : relative tolerance. (Default value = 1e-6) convert2 : if the ok_with[n] should be converted to x1.units. (Default value = True) Returns ------- """ for x2 in ok_with: err_msg = f"At {func.__name__} with {x1} and {x2}" if output_units == "same": ou = x1.units elif output_units == "prod": ou = x1.units * x2.units elif output_units == "div": ou = x1.units / x2.units elif output_units == "second": ou = x1.units**x2 else: ou = output_units qm = func(x1, x2) if convert2 and hasattr(x2, "magnitude"): m2 = x2.to(getattr(x1, "units", "")).magnitude else: m2 = getattr(x2, "magnitude", x2) res = func(x1.magnitude, m2) if ou is not None: res = self.Q_(res, ou) helpers.assert_quantity_almost_equal(qm, res, rtol=rtol, msg=err_msg) for x2 in raise_with: with pytest.raises(DimensionalityError): func(x1, x2) def _testn2(self, func, x1, ok_with, raise_with=()): """Test function that takes two arguments and return a ndarray. Parameters ---------- func : function callable. x1 : first argument of func. ok_with : iterables of values that work fine. raise_with : iterables of values that raise exceptions. (Default value = ()) Returns ------- """ self._test2(func, x1, ok_with, raise_with, output_units=None) @helpers.requires_numpy class TestMathUfuncs(TestUFuncs): """Universal functions (ufunc) > Math operations http://docs.scipy.org/doc/numpy/reference/ufuncs.html#math-operations add(x1, x2, /[, out, where, casting, order, ...] Add arguments element-wise. subtract(x1, x2, /[, out, where, casting, ...] Subtract arguments, element-wise. multiply(x1, x2, /[, out, where, casting, ...] Multiply arguments element-wise. matmul(x1, x2, /[, out, casting, order, ...] Matrix product of two arrays. divide(x1, x2, /[, out, where, casting, ...] Divide arguments element-wise. logaddexp(x1, x2, /[, out, where, casting, ...] Logarithm of the sum of exponentiations of the inputs. logaddexp2(x1, x2, /[, out, where, casting, ...] Logarithm of the sum of exponentiations of the inputs in base-2. true_divide(x1, x2, /[, out, where, ...] Divide arguments element-wise. floor_divide(x1, x2, /[, out, where, ...] Return the largest integer smaller or equal to the division of the inputs. negative(x, /[, out, where, casting, order, ...] Numerical negative, element-wise. positive(x, /[, out, where, casting, order, ...] Numerical positive, element-wise. power(x1, x2, /[, out, where, casting, ...] First array elements raised to powers from second array, element-wise. float_power(x1, x2, /[, out, where, ...] First array elements raised to powers from second array, element-wise. remainder(x1, x2, /[, out, where, casting, ...] Returns the element-wise remainder of division. mod(x1, x2, /[, out, where, casting, order, ...] Returns the element-wise remainder of division. fmod(x1, x2, /[, out, where, casting, ...] Returns the element-wise remainder of division. divmod(x1, x2[, out1, out2], / [[, out, ...] Return element-wise quotient and remainder simultaneously. absolute(x, /[, out, where, casting, order, ...] Calculate the absolute value element-wise. fabs(x, /[, out, where, casting, order, ...] Compute the absolute values element-wise. rint(x, /[, out, where, casting, order, ...] Round elements of the array to the nearest integer. sign(x, /[, out, where, casting, order, ...] Returns an element-wise indication of the sign of a number. heaviside(x1, x2, /[, out, where, casting, ...] Compute the Heaviside step function. conj(x, /[, out, where, casting, order, ...] Return the complex conjugate, element-wise. conjugate(x, /[, out, where, casting, ...] Return the complex conjugate, element-wise. exp(x, /[, out, where, casting, order, ...] Calculate the exponential of all elements in the input array. exp2(x, /[, out, where, casting, order, ...] Calculate 2**p for all p in the input array. log(x, /[, out, where, casting, order, ...] Natural logarithm, element-wise. log2(x, /[, out, where, casting, order, ...] Base-2 logarithm of x. log10(x, /[, out, where, casting, order, ...] Return the base 10 logarithm of the input array, element-wise. expm1(x, /[, out, where, casting, order, ...] Calculate exp(x) - 1 for all elements in the array. log1p(x, /[, out, where, casting, order, ...] Return the natural logarithm of one plus the input array, element-wise. sqrt(x, /[, out, where, casting, order, ...] Return the non-negative square-root of an array, element-wise. square(x, /[, out, where, casting, order, ...] Return the element-wise square of the input. cbrt(x, /[, out, where, casting, order, ...] Return the cube-root of an array, element-wise. reciprocal(x, /[, out, where, casting, ...] Return the reciprocal of the argument, element-wise. gcd(x1, x2, /[, out, where, casting, order, ...] Returns the greatest common divisor of |x1| and |x2| lcm(x1, x2, /[, out, where, casting, order, ...] Returns the lowest common multiple of |x1| and |x2| Parameters ---------- Returns ------- """ def test_add(self): self._test2(np.add, self.q1, (self.q2, self.qs), (self.qm,)) def test_subtract(self): self._test2(np.subtract, self.q1, (self.q2, self.qs), (self.qm,)) def test_multiply(self): self._test2(np.multiply, self.q1, (self.q2, self.qs), (), "prod") def test_divide(self): self._test2( np.divide, self.q1, (self.q2, self.qs, self.qless), (), "div", convert2=False, ) def test_logaddexp(self): self._test2(np.logaddexp, self.qless, (self.qless,), (self.q1,), "") def test_logaddexp2(self): self._test2(np.logaddexp2, self.qless, (self.qless,), (self.q1,), "div") def test_true_divide(self): self._test2( np.true_divide, self.q1, (self.q2, self.qs, self.qless), (), "div", convert2=False, ) def test_floor_divide(self): self._test2( np.floor_divide, self.q1, (self.q2, self.qs, self.qless), (), "div", convert2=False, ) def test_negative(self): self._test1(np.negative, (self.qless, self.q1), ()) def test_positive(self): self._test1(np.positive, (self.qless, self.q1), ()) def test_remainder(self): self._test2( np.remainder, self.q1, (self.q2, self.qs, self.qless), (), "same", convert2=False, ) def test_mod(self): self._test2( np.mod, self.q1, (self.q2, self.qs, self.qless), (), "same", convert2=False ) def test_fmod(self): self._test2( np.fmod, self.q1, (self.q2, self.qs, self.qless), (), "same", convert2=False ) def test_absolute(self): self._test1(np.absolute, (self.q2, self.qs, self.qless, self.qi), (), "same") def test_rint(self): self._test1(np.rint, (self.q2, self.qs, self.qless, self.qi), (), "same") def test_conj(self): self._test1(np.conj, (self.q2, self.qs, self.qless, self.qi), (), "same") def test_exp(self): self._test1(np.exp, (self.qless,), (self.q1,), "") def test_exp2(self): self._test1(np.exp2, (self.qless,), (self.q1,), "") def test_log(self): self._test1(np.log, (self.qless,), (self.q1,), "") def test_log2(self): self._test1(np.log2, (self.qless,), (self.q1,), "") def test_log10(self): self._test1(np.log10, (self.qless,), (self.q1,), "") def test_expm1(self): self._test1(np.expm1, (self.qless,), (self.q1,), "") def test_sqrt(self): self._test1(np.sqrt, (self.q2, self.qs, self.qless, self.qi), (), 0.5) def test_square(self): self._test1(np.square, (self.q2, self.qs, self.qless, self.qi), (), 2) def test_reciprocal(self): self._test1(np.reciprocal, (self.q2, self.qs, self.qless, self.qi), (), -1) @helpers.requires_numpy class TestTrigUfuncs(TestUFuncs): """Universal functions (ufunc) > Trigonometric functions http://docs.scipy.org/doc/numpy/reference/ufuncs.html#trigonometric-functions sin(x[, out]) Trigonometric sine, element-wise. cos(x[, out]) Cosine elementwise. tan(x[, out]) Compute tangent element-wise. arcsin(x[, out]) Inverse sine, element-wise. arccos(x[, out]) Trigonometric inverse cosine, element-wise. arctan(x[, out]) Trigonometric inverse tangent, element-wise. arctan2(x1, x2[, out]) Element-wise arc tangent of x1/x2 choosing the quadrant correctly. hypot(x1, x2[, out]) Given the “legs” of a right triangle, return its hypotenuse. sinh(x[, out]) Hyperbolic sine, element-wise. cosh(x[, out]) Hyperbolic cosine, element-wise. tanh(x[, out]) Compute hyperbolic tangent element-wise. arcsinh(x[, out]) Inverse hyperbolic sine elementwise. arccosh(x[, out]) Inverse hyperbolic cosine, elementwise. arctanh(x[, out]) Inverse hyperbolic tangent elementwise. deg2rad(x[, out]) Convert angles from degrees to radians. rad2deg(x[, out]) Convert angles from radians to degrees. Parameters ---------- Returns ------- """ def test_sin(self): self._test1( np.sin, ( np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless, np.arange(0, pi / 2, pi / 4) * self.ureg.radian, np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m, ), (1 * self.ureg.m,), "", results=(None, None, np.sin(np.arange(0, pi / 2, pi / 4) * 0.001)), ) self._test1( np.sin, (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,), results=(np.sin(np.arange(0, pi / 2, pi / 4)),), ) def test_cos(self): self._test1( np.cos, ( np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless, np.arange(0, pi / 2, pi / 4) * self.ureg.radian, np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m, ), (1 * self.ureg.m,), "", results=(None, None, np.cos(np.arange(0, pi / 2, pi / 4) * 0.001)), ) self._test1( np.cos, (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,), results=(np.cos(np.arange(0, pi / 2, pi / 4)),), ) def test_tan(self): self._test1( np.tan, ( np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless, np.arange(0, pi / 2, pi / 4) * self.ureg.radian, np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m, ), (1 * self.ureg.m,), "", results=(None, None, np.tan(np.arange(0, pi / 2, pi / 4) * 0.001)), ) self._test1( np.tan, (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,), results=(np.tan(np.arange(0, pi / 2, pi / 4)),), ) def test_arcsin(self): self._test1( np.arcsin, ( np.arange(0, 0.9, 0.1) * self.ureg.dimensionless, np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m, ), (1 * self.ureg.m,), "radian", ) def test_arccos(self): self._test1( np.arccos, ( np.arange(0, 0.9, 0.1) * self.ureg.dimensionless, np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m, ), (1 * self.ureg.m,), "radian", ) def test_arctan(self): self._test1( np.arctan, ( np.arange(0, 0.9, 0.1) * self.ureg.dimensionless, np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m, ), (1 * self.ureg.m,), "radian", ) def test_arctan2(self): m = self.ureg.m j = self.ureg.J km = self.ureg.km self._test2( np.arctan2, np.arange(0, 0.9, 0.1) * m, ( np.arange(0, 0.9, 0.1) * m, np.arange(0.9, 0.0, -0.1) * m, np.arange(0, 0.9, 0.1) * km, np.arange(0.9, 0.0, -0.1) * km, ), raise_with=np.arange(0, 0.9, 0.1) * j, output_units="radian", ) def test_hypot(self): assert np.hypot(3.0 * self.ureg.m, 4.0 * self.ureg.m) == 5.0 * self.ureg.m assert np.hypot(3.0 * self.ureg.m, 400.0 * self.ureg.cm) == 5.0 * self.ureg.m with pytest.raises(DimensionalityError): np.hypot(1.0 * self.ureg.m, 2.0 * self.ureg.J) def test_sinh(self): self._test1( np.sinh, ( np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless, np.arange(0, pi / 2, pi / 4) * self.ureg.radian, np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m, ), (1 * self.ureg.m,), "", results=(None, None, np.sinh(np.arange(0, pi / 2, pi / 4) * 0.001)), ) self._test1( np.sinh, (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,), results=(np.sinh(np.arange(0, pi / 2, pi / 4)),), ) def test_cosh(self): self._test1( np.cosh, ( np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless, np.arange(0, pi / 2, pi / 4) * self.ureg.radian, np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m, ), (1 * self.ureg.m,), "", results=(None, None, np.cosh(np.arange(0, pi / 2, pi / 4) * 0.001)), ) self._test1( np.cosh, (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,), results=(np.cosh(np.arange(0, pi / 2, pi / 4)),), ) def test_tanh(self): self._test1( np.tanh, ( np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless, np.arange(0, pi / 2, pi / 4) * self.ureg.radian, np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m, ), (1 * self.ureg.m,), "", results=(None, None, np.tanh(np.arange(0, pi / 2, pi / 4) * 0.001)), ) self._test1( np.tanh, (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,), results=(np.tanh(np.arange(0, pi / 2, pi / 4)),), ) def test_arcsinh(self): self._test1( np.arcsinh, ( np.arange(0, 0.9, 0.1) * self.ureg.dimensionless, np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m, ), (1 * self.ureg.m,), "radian", ) def test_arccosh(self): self._test1( np.arccosh, ( np.arange(1.0, 1.9, 0.1) * self.ureg.dimensionless, np.arange(1.0, 1.9, 0.1) * self.ureg.m / self.ureg.m, ), (1 * self.ureg.m,), "radian", ) def test_arctanh(self): self._test1( np.arctanh, ( np.arange(0, 0.9, 0.1) * self.ureg.dimensionless, np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m, ), (0.1 * self.ureg.m,), "radian", ) def test_deg2rad(self): self._test1( np.deg2rad, (np.arange(0, pi / 2, pi / 4) * self.ureg.degrees,), (1 * self.ureg.m,), "radians", ) def test_rad2deg(self): self._test1( np.rad2deg, ( np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless, np.arange(0, pi / 2, pi / 4) * self.ureg.radian, np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m, ), (1 * self.ureg.m,), "degree", results=( None, None, np.rad2deg(np.arange(0, pi / 2, pi / 4) * 0.001) * self.ureg.degree, ), ) class TestComparisonUfuncs(TestUFuncs): """Universal functions (ufunc) > Comparison functions http://docs.scipy.org/doc/numpy/reference/ufuncs.html#comparison-functions greater(x1, x2[, out]) Return the truth value of (x1 > x2) element-wise. greater_equal(x1, x2[, out]) Return the truth value of (x1 >= x2) element-wise. less(x1, x2[, out]) Return the truth value of (x1 < x2) element-wise. less_equal(x1, x2[, out]) Return the truth value of (x1 =< x2) element-wise. not_equal(x1, x2[, out]) Return (x1 != x2) element-wise. equal(x1, x2[, out]) Return (x1 == x2) element-wise. Parameters ---------- Returns ------- """ def test_greater(self): self._testn2(np.greater, self.q1, (self.q2,), (self.qm,)) def test_greater_equal(self): self._testn2(np.greater_equal, self.q1, (self.q2,), (self.qm,)) def test_less(self): self._testn2(np.less, self.q1, (self.q2,), (self.qm,)) def test_less_equal(self): self._testn2(np.less_equal, self.q1, (self.q2,), (self.qm,)) def test_not_equal(self): self._testn2(np.not_equal, self.q1, (self.q2,), (self.qm,)) def test_equal(self): self._testn2(np.equal, self.q1, (self.q2,), (self.qm,)) class TestFloatingUfuncs(TestUFuncs): """Universal functions (ufunc) > Floating functions http://docs.scipy.org/doc/numpy/reference/ufuncs.html#floating-functions isreal(x) Returns a bool array, where True if input element is real. iscomplex(x) Returns a bool array, where True if input element is complex. isfinite(x[, out]) Test element-wise for finite-ness (not infinity or not Not a Number). isinf(x[, out]) Test element-wise for positive or negative infinity. isnan(x[, out]) Test element-wise for Not a Number (NaN), return result as a bool array. signbit(x[, out]) Returns element-wise True where signbit is set (less than zero). copysign(x1, x2[, out]) Change the sign of x1 to that of x2, element-wise. nextafter(x1, x2[, out]) Return the next representable floating-point value after x1 in the direction of x2 element-wise. modf(x[, out1, out2]) Return the fractional and integral parts of an array, element-wise. ldexp(x1, x2[, out]) Compute y = x1 * 2**x2. frexp(x[, out1, out2]) Split the number, x, into a normalized fraction (y1) and exponent (y2) fmod(x1, x2[, out]) Return the element-wise remainder of division. floor(x[, out]) Return the floor of the input, element-wise. ceil(x[, out]) Return the ceiling of the input, element-wise. trunc(x[, out]) Return the truncated value of the input, element-wise. Parameters ---------- Returns ------- """ def test_isreal(self): self._testn(np.isreal, (self.q1, self.qm, self.qless)) def test_iscomplex(self): self._testn(np.iscomplex, (self.q1, self.qm, self.qless)) def test_isfinite(self): self._testn(np.isfinite, (self.q1, self.qm, self.qless)) def test_isinf(self): self._testn(np.isinf, (self.q1, self.qm, self.qless)) def test_isnan(self): self._testn(np.isnan, (self.q1, self.qm, self.qless)) def test_signbit(self): self._testn(np.signbit, (self.q1, self.qm, self.qless)) def test_copysign(self): self._test2(np.copysign, self.q1, (self.q2, self.qs), (self.qm,)) def test_nextafter(self): self._test2(np.nextafter, self.q1, (self.q2, self.qs), (self.qm,)) def test_modf(self): self._test1_2o(np.modf, (self.q2, self.qs)) def test_ldexp(self): x1, x2 = np.frexp(self.q2) self._test2(np.ldexp, x1, (x2,)) def test_frexp(self): self._test1_2o(np.frexp, (self.q2, self.qs), output_units=("same", None)) def test_fmod(self): # See TestMathUfuncs.test_fmod pass def test_floor(self): self._test1(np.floor, (self.q1, self.qm, self.qless)) def test_ceil(self): self._test1(np.ceil, (self.q1, self.qm, self.qless)) def test_trunc(self): self._test1(np.trunc, (self.q1, self.qm, self.qless)) pint-0.24.4/pint/testsuite/test_unit.py000066400000000000000000001110661471316474000201620ustar00rootroot00000000000000from __future__ import annotations import copy import functools import logging import math import operator import re from contextlib import nullcontext as does_not_raise import pytest from pint import DimensionalityError, RedefinitionError, UndefinedUnitError, errors from pint.compat import np from pint.registry import LazyRegistry, UnitRegistry from pint.testsuite import QuantityTestCase, assert_no_warnings, helpers from pint.util import ParserHelper, UnitsContainer from .helpers import internal # TODO: do not subclass from QuantityTestCase class TestUnit(QuantityTestCase): def test_creation(self): for arg in ("meter", UnitsContainer(meter=1), self.U_("m")): assert self.U_(arg)._units == UnitsContainer(meter=1) with pytest.raises(TypeError): self.U_(1) def test_deepcopy(self): x = self.U_(UnitsContainer(meter=1)) assert x == copy.deepcopy(x) def test_unit_repr(self): x = self.U_(UnitsContainer(meter=1)) assert str(x) == "meter" assert repr(x) == "" def test_unit_formatting(self, subtests): x = self.U_(UnitsContainer(meter=2, kilogram=1, second=-1)) for spec, result in ( ("{}", str(x)), ("{!s}", str(x)), ("{!r}", repr(x)), ( "{:L}", r"\frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}", ), ("{:P}", "kilogram·meter²/second"), ("{:H}", "kilogram meter2/second"), ("{:C}", "kilogram*meter**2/second"), ("{:Lx}", r"\si[]{\kilo\gram\meter\squared\per\second}"), ("{:~}", "kg * m ** 2 / s"), ("{:L~}", r"\frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}"), ("{:P~}", "kg·m²/s"), ("{:H~}", "kg m2/s"), ("{:C~}", "kg*m**2/s"), ): with subtests.test(spec): assert spec.format(x) == result def test_latex_escaping(self, subtests): ureg = UnitRegistry() ureg.define(r"percent = 1e-2 = %") x = ureg.Unit(UnitsContainer(percent=1)) for spec, result in { "L": r"\mathrm{percent}", "L~": r"\mathrm{\%}", "Lx": r"\si[]{\percent}", "Lx~": r"\si[]{\%}", }.items(): with subtests.test(spec): ureg.formatter.default_format = spec assert f"{x}" == result, f"Failed for {spec}, got {x} expected {result}" # no '#' here as it's a comment char when define()ing new units ureg.define(r"weirdunit = 1 = \~_^&%$_{}") x = ureg.Unit(UnitsContainer(weirdunit=1)) for spec, result in { "L": r"\mathrm{weirdunit}", "L~": r"\mathrm{\textbackslash \textasciitilde \_\textasciicircum \&\%\$\_\{\}}", "Lx": r"\si[]{\weirdunit}", # TODO: Currently makes \si[]{\\~_^&%$_{}} (broken). What do we even want this to be? # "Lx~": r"\si[]{\textbackslash \textasciitilde \_\textasciicircum \&\%\$\_\{\}}", }.items(): with subtests.test(spec): ureg.formatter.default_format = spec assert f"{x}" == result, f"Failed for {spec}, {result}" def test_unit_default_formatting(self, subtests): ureg = UnitRegistry() x = ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1)) for spec, result in ( ( "L", r"\frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}", ), ("P", "kilogram·meter²/second"), ("H", "kilogram meter2/second"), ("C", "kilogram*meter**2/second"), ("~", "kg * m ** 2 / s"), ("L~", r"\frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}"), ("P~", "kg·m²/s"), ("H~", "kg m2/s"), ("C~", "kg*m**2/s"), ): with subtests.test(spec): ureg.formatter.default_format = spec assert f"{x}" == result, f"Failed for {spec}, {result}" @pytest.mark.xfail(reason="Still not clear how default formatting will work.") def test_unit_formatting_defaults_warning(self): ureg = UnitRegistry() ureg.formatter.default_format = "~P" x = ureg.Unit("m / s ** 2") with pytest.warns(DeprecationWarning): assert f"{x:.2f}" == "meter / second ** 2" ureg.separate_format_defaults = True with assert_no_warnings(): assert f"{x:.2f}" == "m/s²" def test_unit_formatting_snake_case(self, subtests): # Test that snake_case units are escaped where appropriate ureg = UnitRegistry() x = ureg.Unit(UnitsContainer(oil_barrel=1)) for spec, result in ( ("L", r"\mathrm{oil\_barrel}"), ("P", "oil_barrel"), ("H", "oil_barrel"), ("C", "oil_barrel"), ("~", "oil_bbl"), ("L~", r"\mathrm{oil\_bbl}"), ("P~", "oil_bbl"), ("H~", "oil_bbl"), ("C~", "oil_bbl"), ): with subtests.test(spec): ureg.formatter.default_format = spec assert f"{x}" == result, f"Failed for {spec}, {result}" def test_unit_formatting_custom(self, monkeypatch): from pint import register_unit_format from pint.delegates.formatter._spec_helpers import REGISTERED_FORMATTERS @register_unit_format("new") def format_new(unit, *args, **options): return "new format" ureg = UnitRegistry() assert f"{ureg.m:new}" == "new format" del REGISTERED_FORMATTERS["new"] def test_ipython(self): alltext = [] class Pretty: @staticmethod def text(text): alltext.append(text) @classmethod def pretty(cls, data): try: data._repr_pretty_(cls, False) except AttributeError: alltext.append(str(data)) ureg = UnitRegistry() x = ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1)) assert x._repr_html_() == "kilogram meter2/second" assert ( x._repr_latex_() == r"$\frac{\mathrm{kilogram} \cdot " r"\mathrm{meter}^{2}}{\mathrm{second}}$" ) x._repr_pretty_(Pretty, False) assert "".join(alltext) == "kilogram·meter²/second" ureg.formatter.default_format = "~" assert x._repr_html_() == "kg m2/s" assert ( x._repr_latex_() == r"$\frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}$" ) alltext = [] x._repr_pretty_(Pretty, False) assert "".join(alltext) == "kg·m²/s" def test_unit_mul(self): x = self.U_("m") assert x * 1 == self.Q_(1, "m") assert x * 0.5 == self.Q_(0.5, "m") assert x * self.Q_(1, "m") == self.Q_(1, "m**2") assert 1 * x == self.Q_(1, "m") def test_unit_div(self): x = self.U_("m") assert x / 1 == self.Q_(1, "m") assert x / 0.5 == self.Q_(2.0, "m") assert x / self.Q_(1, "m") == self.Q_(1) def test_unit_rdiv(self): x = self.U_("m") assert 1 / x == self.Q_(1, "1/m") @pytest.mark.parametrize( ("unit", "power_ratio", "expectation", "expected_unit"), [ ("m", 2, does_not_raise(), "m**2"), ("m", {}, pytest.raises(TypeError), None), ], ) def test_unit_pow(self, unit, power_ratio, expectation, expected_unit): with expectation: x = self.U_(unit) assert x**power_ratio == self.U_(expected_unit) def test_is_compatible_with(self): unit = self.ureg.Unit("m") assert unit.is_compatible_with("m") assert not unit.is_compatible_with("m**2") assert unit.is_compatible_with(self.ureg.Unit("m")) assert not unit.is_compatible_with(self.ureg.Unit("m**2")) # test other type considered as dimensionless unit = self.ureg.Unit("") assert unit.is_compatible_with(0.5) def test_systems(self): unit = self.ureg.Unit("m") assert unit.systems == frozenset({"cgs", "atomic", "Planck", "mks", "SI"}) def test_unit_hash(self): x = self.U_("m") assert hash(x) == hash(x._units) def test_unit_eqs(self): x = self.U_("m") assert x == self.U_("m") assert x != self.U_("cm") assert x == self.Q_(1, "m") assert x != self.Q_(2, "m") assert x == UnitsContainer({"meter": 1}) y = self.U_("cm/m") assert y == 0.01 assert self.U_("byte") == self.U_("byte") assert not (self.U_("byte") != self.U_("byte")) def test_unit_cmp(self): x = self.U_("m") assert x < self.U_("km") assert x > self.U_("mm") y = self.U_("m/mm") assert y > 1 assert y < 1e6 def test_dimensionality(self): x = self.U_("m") assert x.dimensionality == UnitsContainer({"[length]": 1}) def test_dimensionless(self): assert self.U_("m/mm").dimensionless assert not self.U_("m").dimensionless def test_unit_casting(self): assert int(self.U_("m/mm")) == 1000 assert float(self.U_("mm/m")) == 1e-3 assert complex(self.U_("mm/mm")) == 1 + 0j @helpers.requires_numpy def test_array_interface(self): import numpy as np x = self.U_("m") arr = np.ones(10) helpers.assert_quantity_equal(arr * x, self.Q_(arr, "m")) helpers.assert_quantity_equal(arr / x, self.Q_(arr, "1/m")) helpers.assert_quantity_equal(x / arr, self.Q_(arr, "m")) class TestRegistry(QuantityTestCase): @classmethod def setup_class(cls): super().setup_class() cls.ureg.autoconvert_offset_to_baseunit = False def test_base(self): ureg = UnitRegistry(None, on_redefinition="raise") ureg.define("meter = [length]") with pytest.raises(errors.RedefinitionError): ureg.define("meter = [length]") with pytest.raises(TypeError): ureg.define([]) ureg.define("degC = kelvin; offset: 273.15") def test_define(self): ureg = UnitRegistry(None) assert isinstance(dir(ureg), list) assert len(dir(ureg)) > 0 def test_load(self): from importlib.resources import files from .. import compat data = files(compat.__package__).joinpath("default_en.txt") ureg1 = UnitRegistry() ureg2 = UnitRegistry(data) assert dir(ureg1) == dir(ureg2) with pytest.raises(FileNotFoundError): UnitRegistry(None).load_definitions("notexisting") def test_default_format(self): ureg = UnitRegistry() q = ureg.meter s1 = f"{q}" s2 = f"{q:~}" ureg.formatter.default_format = "~" s3 = f"{q}" assert s2 == s3 assert s1 != s3 assert ureg.formatter.default_format == "~" def test_iterate(self): ureg = UnitRegistry() assert "meter" in list(ureg) def test_parse_number(self): assert self.ureg.parse_expression("pi") == math.pi assert self.ureg.parse_expression("x", x=2) == 2 assert self.ureg.parse_expression("x", x=2.3) == 2.3 assert self.ureg.parse_expression("x * y", x=2.3, y=3) == 2.3 * 3 assert self.ureg.parse_expression("x", x=(1 + 1j)) == (1 + 1j) def test_parse_single(self): assert self.ureg.parse_expression("meter") == self.Q_( 1, UnitsContainer(meter=1.0) ) assert self.ureg.parse_expression("second") == self.Q_( 1, UnitsContainer(second=1.0) ) def test_parse_alias(self): assert self.ureg.parse_expression("metre") == self.Q_( 1, UnitsContainer(meter=1.0) ) def test_parse_plural(self): assert self.ureg.parse_expression("meters") == self.Q_( 1, UnitsContainer(meter=1.0) ) def test_parse_prefix(self): assert self.ureg.parse_expression("kilometer") == self.Q_( 1, UnitsContainer(kilometer=1.0) ) def test_parse_complex(self): assert self.ureg.parse_expression("kilometre") == self.Q_( 1, UnitsContainer(kilometer=1.0) ) assert self.ureg.parse_expression("kilometres") == self.Q_( 1, UnitsContainer(kilometer=1.0) ) def test_parse_mul_div(self): assert self.ureg.parse_expression("meter*meter") == self.Q_( 1, UnitsContainer(meter=2.0) ) assert self.ureg.parse_expression("meter**2") == self.Q_( 1, UnitsContainer(meter=2.0) ) assert self.ureg.parse_expression("meter*second") == self.Q_( 1, UnitsContainer(meter=1.0, second=1) ) assert self.ureg.parse_expression("meter/second") == self.Q_( 1, UnitsContainer(meter=1.0, second=-1) ) assert self.ureg.parse_expression("meter/second**2") == self.Q_( 1, UnitsContainer(meter=1.0, second=-2) ) def test_parse_pretty(self): assert self.ureg.parse_expression("meter/second²") == self.Q_( 1, UnitsContainer(meter=1.0, second=-2) ) assert self.ureg.parse_expression("m³/s³") == self.Q_( 1, UnitsContainer(meter=3.0, second=-3) ) assert self.ureg.parse_expression("meter² · second") == self.Q_( 1, UnitsContainer(meter=2.0, second=1) ) assert self.ureg.parse_expression("m²·s⁻²") == self.Q_( 1, UnitsContainer(meter=2, second=-2) ) assert self.ureg.parse_expression("meter⁰.⁵·second") == self.Q_( 1, UnitsContainer(meter=0.5, second=1) ) assert self.ureg.parse_expression("meter³⁷/second⁴.³²¹") == self.Q_( 1, UnitsContainer(meter=37, second=-4.321) ) def test_parse_pretty_degrees(self): for exp in ("1Δ°C", "1 Δ°C", "ΔdegC", "delta_°C"): assert self.ureg.parse_expression(exp) == self.Q_( 1, UnitsContainer(delta_degree_Celsius=1) ) assert self.ureg.parse_expression("") assert self.ureg.parse_expression("mol °K") == self.Q_( 1, UnitsContainer(mol=1, kelvin=1) ) def test_parse_factor(self): assert self.ureg.parse_expression("42*meter") == self.Q_( 42, UnitsContainer(meter=1.0) ) assert self.ureg.parse_expression("meter*42") == self.Q_( 42, UnitsContainer(meter=1.0) ) def test_rep_and_parse(self): q = self.Q_(1, "g/(m**2*s)") assert self.Q_(q.magnitude, str(q.units)) == q def test_as_delta(self): parse = self.ureg.parse_units assert parse("kelvin", as_delta=True) == UnitsContainer(kelvin=1) assert parse("kelvin", as_delta=False) == UnitsContainer(kelvin=1) assert parse("kelvin**(-1)", as_delta=True) == UnitsContainer(kelvin=-1) assert parse("kelvin**(-1)", as_delta=False) == UnitsContainer(kelvin=-1) assert parse("kelvin**2", as_delta=True) == UnitsContainer(kelvin=2) assert parse("kelvin**2", as_delta=False) == UnitsContainer(kelvin=2) assert parse("kelvin*meter", as_delta=True) == UnitsContainer(kelvin=1, meter=1) assert parse("kelvin*meter", as_delta=False) == UnitsContainer( kelvin=1, meter=1 ) @helpers.requires_numpy def test_parse_with_force_ndarray(self): ureg = UnitRegistry(force_ndarray=True) assert ureg.parse_expression("m * s ** -2").units == ureg.m / ureg.s**2 def test_parse_expression_with_preprocessor(self): # Add parsing of UDUNITS-style power self.ureg.preprocessors.append( functools.partial( re.sub, r"(?<=[A-Za-z])(?![A-Za-z])(?" x = UnitsContainer(meter=1, second=2) assert str(x) == "meter * second ** 2" assert repr(x) == "" x = UnitsContainer(meter=1, second=2.5) assert str(x) == "meter * second ** 2.5" assert repr(x) == "" def test_unitcontainer_bool(self): assert UnitsContainer(meter=1, second=2) assert not UnitsContainer() def test_unitcontainer_comp(self): x = UnitsContainer(meter=1, second=2) y = UnitsContainer(meter=1.0, second=2) z = UnitsContainer(meter=1, second=3) assert x == y assert not (x != y) assert not (x == z) assert x != z def test_unitcontainer_arithmetic(self): x = UnitsContainer(meter=1) y = UnitsContainer(second=1) z = UnitsContainer(meter=1, second=-2) self._test_not_inplace(op.mul, x, y, UnitsContainer(meter=1, second=1)) self._test_not_inplace(op.truediv, x, y, UnitsContainer(meter=1, second=-1)) self._test_not_inplace(op.pow, z, 2, UnitsContainer(meter=2, second=-4)) self._test_not_inplace(op.pow, z, -2, UnitsContainer(meter=-2, second=4)) self._test_inplace(op.imul, x, y, UnitsContainer(meter=1, second=1)) self._test_inplace(op.itruediv, x, y, UnitsContainer(meter=1, second=-1)) self._test_inplace(op.ipow, z, 2, UnitsContainer(meter=2, second=-4)) self._test_inplace(op.ipow, z, -2, UnitsContainer(meter=-2, second=4)) def test_string_comparison(self): x = UnitsContainer(meter=1) y = UnitsContainer(second=1) z = UnitsContainer(meter=1, second=-2) assert x == "meter" assert "meter" == x assert x != "meter ** 2" assert x != "meter * meter" assert x != "second" assert y == "second" assert z == "meter/second/second" def test_invalid(self): with pytest.raises(TypeError): UnitsContainer({1: 2}) with pytest.raises(TypeError): UnitsContainer({"1": "2"}) d = UnitsContainer() with pytest.raises(TypeError): d.__mul__([]) with pytest.raises(TypeError): d.__pow__([]) with pytest.raises(TypeError): d.__truediv__([]) with pytest.raises(TypeError): d.__rtruediv__([]) class TestToUnitsContainer: def test_str_conversion(self): assert to_units_container("m") == UnitsContainer(m=1) def test_uc_conversion(self): a = UnitsContainer(m=1) assert to_units_container(a) is a def test_quantity_conversion(self): from pint.registry import UnitRegistry ureg = UnitRegistry() assert to_units_container( ureg.Quantity(1, UnitsContainer(m=1)) ) == UnitsContainer(m=1) def test_unit_conversion(self): from pint import Unit assert to_units_container(Unit(UnitsContainer(m=1))) == UnitsContainer(m=1) def test_dict_conversion(self): assert to_units_container(dict(m=1)) == UnitsContainer(m=1) class TestParseHelper: def test_basic(self): # Parse Helper ar mutables, so we build one everytime x = lambda: ParserHelper(1, meter=2) xp = lambda: ParserHelper(1, meter=2) y = lambda: ParserHelper(2, meter=2) assert x() == xp() assert x() != y() assert ParserHelper.from_string("") == ParserHelper() assert repr(x()) == "" assert ParserHelper(2) == 2 assert x() == dict(meter=2) assert x() == "meter ** 2" assert y() != dict(meter=2) assert y() != "meter ** 2" assert xp() != object() def test_calculate(self): # Parse Helper ar mutables, so we build one everytime x = lambda: ParserHelper(1.0, meter=2) y = lambda: ParserHelper(2.0, meter=-2) z = lambda: ParserHelper(2.0, meter=2) assert x() * 4.0 == ParserHelper(4.0, meter=2) assert x() * y() == ParserHelper(2.0) assert x() * "second" == ParserHelper(1.0, meter=2, second=1) assert x() / 4.0 == ParserHelper(0.25, meter=2) assert x() / "second" == ParserHelper(1.0, meter=2, second=-1) assert x() / z() == ParserHelper(0.5) assert 4.0 / z() == ParserHelper(2.0, meter=-2) assert "seconds" / z() == ParserHelper(0.5, seconds=1, meter=-2) assert dict(seconds=1) / z() == ParserHelper(0.5, seconds=1, meter=-2) def _test_eval_token(self, expected, expression): token = next(pint_eval.tokenizer(expression)) actual = ParserHelper.eval_token(token) assert expected == actual assert type(expected) == type(actual) def test_eval_token(self): self._test_eval_token(1000.0, "1e3") self._test_eval_token(1000.0, "1E3") self._test_eval_token(1000, "1000") def test_nan(self, subtests): for s in ("nan", "NAN", "NaN", "123 NaN nan NAN 456"): with subtests.test(s): p = ParserHelper.from_string(s + " kg") assert math.isnan(p.scale) assert dict(p) == {"kg": 1} class TestStringProcessor: def _test(self, bef, aft): for pattern in ("{}", "+{}+"): b = pattern.format(bef) a = pattern.format(aft) assert string_preprocessor(b) == a def test_square_cube(self): self._test("bcd^3", "bcd**3") self._test("bcd^ 3", "bcd** 3") self._test("bcd ^3", "bcd **3") self._test("bcd squared", "bcd**2") self._test("bcd squared", "bcd**2") self._test("bcd cubed", "bcd**3") self._test("sq bcd", "bcd**2") self._test("square bcd", "bcd**2") self._test("cubic bcd", "bcd**3") self._test("bcd efg", "bcd*efg") def test_per(self): self._test("miles per hour", "miles/hour") def test_numbers(self): self._test("1,234,567", "1234567") self._test("1e-24", "1e-24") self._test("1e+24", "1e+24") self._test("1e24", "1e24") self._test("1E-24", "1E-24") self._test("1E+24", "1E+24") self._test("1E24", "1E24") def test_space_multiplication(self): self._test("bcd efg", "bcd*efg") self._test("bcd efg", "bcd*efg") self._test("1 hour", "1*hour") self._test("1. hour", "1.*hour") self._test("1.1 hour", "1.1*hour") self._test("1E24 hour", "1E24*hour") self._test("1E-24 hour", "1E-24*hour") self._test("1E+24 hour", "1E+24*hour") self._test("1.2E24 hour", "1.2E24*hour") self._test("1.2E-24 hour", "1.2E-24*hour") self._test("1.2E+24 hour", "1.2E+24*hour") def test_joined_multiplication(self): self._test("1hour", "1*hour") self._test("1.hour", "1.*hour") self._test("1.1hour", "1.1*hour") self._test("1h", "1*h") self._test("1.h", "1.*h") self._test("1.1h", "1.1*h") def test_names(self): self._test("g_0", "g_0") self._test("g0", "g0") self._test("g", "g") self._test("water_60F", "water_60F") class TestGraph: def test_start_not_in_graph(self): g = collections.defaultdict(set) g[1] = {2} g[2] = {3} assert find_connected_nodes(g, 9) is None def test_shortest_path(self): g = collections.defaultdict(set) g[1] = {2} g[2] = {3} p = find_shortest_path(g, 1, 2) assert p == [1, 2] p = find_shortest_path(g, 1, 3) assert p == [1, 2, 3] p = find_shortest_path(g, 3, 1) assert p is None g = collections.defaultdict(set) g[1] = {2} g[2] = {3, 1} g[3] = {2} p = find_shortest_path(g, 1, 2) assert p == [1, 2] p = find_shortest_path(g, 1, 3) assert p == [1, 2, 3] p = find_shortest_path(g, 3, 1) assert p == [3, 2, 1] p = find_shortest_path(g, 2, 1) assert p == [2, 1] class TestMatrix: def test_matrix_to_string(self): assert ( matrix_to_string([[1, 2], [3, 4]], row_headers=None, col_headers=None) == "1\t2\n" "3\t4" ) assert ( matrix_to_string( [[1, 2], [3, 4]], row_headers=None, col_headers=None, fmtfun=lambda x: f"{x:.2f}", ) == "1.00\t2.00\n" "3.00\t4.00" ) assert ( matrix_to_string([[1, 2], [3, 4]], row_headers=["c", "d"], col_headers=None) == "c\t1\t2\n" "d\t3\t4" ) assert ( matrix_to_string([[1, 2], [3, 4]], row_headers=None, col_headers=["a", "b"]) == "a\tb\n" "1\t2\n" "3\t4" ) assert ( matrix_to_string( [[1, 2], [3, 4]], row_headers=["c", "d"], col_headers=["a", "b"] ) == "\ta\tb\n" "c\t1\t2\n" "d\t3\t4" ) def test_transpose(self): assert transpose([[1, 2], [3, 4]]) == [[1, 3], [2, 4]] class TestOtherUtils: def test_iterable(self): # Test with list, string, generator, and scalar assert iterable([0, 1, 2, 3]) assert iterable("test") assert iterable(i for i in range(5)) assert not iterable(0) def test_sized(self): # Test with list, string, generator, and scalar assert sized([0, 1, 2, 3]) assert sized("test") assert not sized(i for i in range(5)) assert not sized(0) pint-0.24.4/pint/toktest.py000066400000000000000000000013651471316474000156100ustar00rootroot00000000000000from __future__ import annotations import tokenize from pint.pint_eval import _plain_tokenizer, uncertainty_tokenizer tokenizer = _plain_tokenizer input_lines = [ "( 8.0 + / - 4.0 ) e6 m", "( 8.0 ± 4.0 ) e6 m", "( 8.0 + / - 4.0 ) e-6 m", "( nan + / - 0 ) e6 m", "( nan ± 4.0 ) m", "8.0 + / - 4.0 m", "8.0 ± 4.0 m", "8.0(4)m", "8.0(.4)m", "8.0(-4)m", # error! "pint == wonderfulness ^ N + - + / - * ± m J s", ] for line in input_lines: result = [] g = list(uncertainty_tokenizer(line)) # tokenize the string for toknum, tokval, _, _, _ in g: result.append((toknum, tokval)) print("====") print(f"input line: {line}") print(result) print(tokenize.untokenize(result)) pint-0.24.4/pint/util.py000066400000000000000000001015121471316474000150630ustar00rootroot00000000000000""" pint.util ~~~~~~~~~ Miscellaneous functions for pint. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import logging import math import operator import re import tokenize import types from collections.abc import Callable, Generator, Hashable, Iterable, Iterator, Mapping from fractions import Fraction from functools import lru_cache, partial from logging import NullHandler from numbers import Number from token import NAME, NUMBER from typing import ( TYPE_CHECKING, Any, ClassVar, TypeVar, ) from . import pint_eval from ._typing import Scalar from .compat import NUMERIC_TYPES, Self from .errors import DefinitionSyntaxError from .pint_eval import build_eval_tree if TYPE_CHECKING: from ._typing import QuantityOrUnitLike from .registry import UnitRegistry logger = logging.getLogger(__name__) logger.addHandler(NullHandler()) T = TypeVar("T") TH = TypeVar("TH", bound=Hashable) TT = TypeVar("TT", bound=type) # TODO: Change when Python 3.10 becomes minimal version. # ItMatrix: TypeAlias = Iterable[Iterable[PintScalar]] # Matrix: TypeAlias = list[list[PintScalar]] ItMatrix = Iterable[Iterable[Scalar]] Matrix = list[list[Scalar]] def _noop(x: T) -> T: return x def matrix_to_string( matrix: ItMatrix, row_headers: Iterable[str] | None = None, col_headers: Iterable[str] | None = None, fmtfun: Callable[ [ Scalar, ], str, ] = "{:0.0f}".format, ) -> str: """Return a string representation of a matrix. Parameters ---------- matrix A matrix given as an iterable of an iterable of numbers. row_headers An iterable of strings to serve as row headers. (default = None, meaning no row headers are printed.) col_headers An iterable of strings to serve as column headers. (default = None, meaning no col headers are printed.) fmtfun A callable to convert a number into string. (default = `"{:0.0f}".format`) Returns ------- str String representation of the matrix. """ ret: list[str] = [] if col_headers: ret.append(("\t" if row_headers else "") + "\t".join(col_headers)) if row_headers: ret += [ rh + "\t" + "\t".join(fmtfun(f) for f in row) for rh, row in zip(row_headers, matrix) ] else: ret += ["\t".join(fmtfun(f) for f in row) for row in matrix] return "\n".join(ret) def transpose(matrix: ItMatrix) -> Matrix: """Return the transposed version of a matrix. Parameters ---------- matrix A matrix given as an iterable of an iterable of numbers. Returns ------- Matrix The transposed version of the matrix. """ return [list(val) for val in zip(*matrix)] def matrix_apply( matrix: ItMatrix, func: Callable[ [ Scalar, ], Scalar, ], ) -> Matrix: """Apply a function to individual elements within a matrix. Parameters ---------- matrix A matrix given as an iterable of an iterable of numbers. func A callable that converts a number to another. Returns ------- A new matrix in which each element has been replaced by new one. """ return [[func(x) for x in row] for row in matrix] def column_echelon_form( matrix: ItMatrix, ntype: type = Fraction, transpose_result: bool = False ) -> tuple[Matrix, Matrix, list[int]]: """Calculate the column echelon form using Gaussian elimination. Parameters ---------- matrix A 2D matrix as nested list. ntype The numerical type to use in the calculation. (default = Fraction) transpose_result Indicates if the returned matrix should be transposed. (default = False) Returns ------- ech_matrix Column echelon form. id_matrix Transformed identity matrix. swapped Swapped rows. """ _transpose: Callable[ [ ItMatrix, ], Matrix, ] = transpose if transpose_result else _noop ech_matrix = matrix_apply( transpose(matrix), lambda x: ntype.from_float(x) if isinstance(x, float) else ntype(x), # type: ignore ) rows, cols = len(ech_matrix), len(ech_matrix[0]) # M = [[ntype(x) for x in row] for row in M] id_matrix: list[list[Scalar]] = [ # noqa: E741 [ntype(1) if n == nc else ntype(0) for nc in range(rows)] for n in range(rows) ] swapped: list[int] = [] lead = 0 for r in range(rows): if lead >= cols: return _transpose(ech_matrix), _transpose(id_matrix), swapped s = r while ech_matrix[s][lead] == 0: # type: ignore s += 1 if s != rows: continue s = r lead += 1 if cols == lead: return _transpose(ech_matrix), _transpose(id_matrix), swapped ech_matrix[s], ech_matrix[r] = ech_matrix[r], ech_matrix[s] id_matrix[s], id_matrix[r] = id_matrix[r], id_matrix[s] swapped.append(s) lv = ech_matrix[r][lead] ech_matrix[r] = [mrx / lv for mrx in ech_matrix[r]] id_matrix[r] = [mrx / lv for mrx in id_matrix[r]] for s in range(rows): if s == r: continue lv = ech_matrix[s][lead] ech_matrix[s] = [ iv - lv * rv for rv, iv in zip(ech_matrix[r], ech_matrix[s]) ] id_matrix[s] = [iv - lv * rv for rv, iv in zip(id_matrix[r], id_matrix[s])] lead += 1 return _transpose(ech_matrix), _transpose(id_matrix), swapped def pi_theorem(quantities: dict[str, Any], registry: UnitRegistry | None = None): """Builds dimensionless quantities using the Buckingham π theorem Parameters ---------- quantities : dict mapping between variable name and units registry : (default value = None) Returns ------- type a list of dimensionless quantities expressed as dicts """ # Preprocess input and build the dimensionality Matrix quant = [] dimensions = set() if registry is None: getdim = _noop non_int_type = float else: getdim = registry.get_dimensionality non_int_type = registry.non_int_type for name, value in quantities.items(): if isinstance(value, str): value = ParserHelper.from_string(value, non_int_type=non_int_type) if isinstance(value, dict): dims = getdim(registry.UnitsContainer(value)) elif not hasattr(value, "dimensionality"): dims = getdim(value) else: dims = value.dimensionality if not registry and any(not key.startswith("[") for key in dims): logger.warning( "A non dimension was found and a registry was not provided. " "Assuming that it is a dimension name: {}.".format(dims) ) quant.append((name, dims)) dimensions = dimensions.union(dims.keys()) dimensions = list(dimensions) # Calculate dimensionless quantities matrix = [ [dimensionality[dimension] for name, dimensionality in quant] for dimension in dimensions ] ech_matrix, id_matrix, pivot = column_echelon_form(matrix, transpose_result=False) # Collect results # Make all numbers integers and minimize the number of negative exponents. # Remove zeros results = [] for rowm, rowi in zip(ech_matrix, id_matrix): if any(el != 0 for el in rowm): continue max_den = max(f.denominator for f in rowi) neg = -1 if sum(f < 0 for f in rowi) > sum(f > 0 for f in rowi) else 1 results.append( { q[0]: neg * f.numerator * max_den / f.denominator for q, f in zip(quant, rowi) if f.numerator != 0 } ) return results def solve_dependencies( dependencies: dict[TH, set[TH]], ) -> Generator[set[TH], None, None]: """Solve a dependency graph. Parameters ---------- dependencies : dependency dictionary. For each key, the value is an iterable indicating its dependencies. Yields ------ set iterator of sets, each containing keys of independents tasks dependent only of the previous tasks in the list. Raises ------ ValueError if a cyclic dependency is found. """ while dependencies: # values not in keys (items without dep) t = {i for v in dependencies.values() for i in v} - dependencies.keys() # and keys without value (items without dep) t.update(k for k, v in dependencies.items() if not v) # can be done right away if not t: raise ValueError( "Cyclic dependencies exist among these items: {}".format( ", ".join(repr(x) for x in dependencies.items()) ) ) # and cleaned up dependencies = {k: v - t for k, v in dependencies.items() if v} yield t def find_shortest_path( graph: dict[TH, set[TH]], start: TH, end: TH, path: list[TH] | None = None ): """Find shortest path between two nodes within a graph. Parameters ---------- graph A graph given as a mapping of nodes to a set of all connected nodes to it. start Starting node. end End node. path Path to prepend to the one found. (default = None, empty path.) Returns ------- list[TH] The shortest path between two nodes. """ path = (path or []) + [start] if start == end: return path # TODO: raise ValueError when start not in graph if start not in graph: return None shortest = None for node in graph[start]: if node not in path: newpath = find_shortest_path(graph, node, end, path) if newpath: if not shortest or len(newpath) < len(shortest): shortest = newpath return shortest def find_connected_nodes( graph: dict[TH, set[TH]], start: TH, visited: set[TH] | None = None ) -> set[TH] | None: """Find all nodes connected to a start node within a graph. Parameters ---------- graph A graph given as a mapping of nodes to a set of all connected nodes to it. start Starting node. visited Mutable set to collect visited nodes. (default = None, empty set) Returns ------- set[TH] The shortest path between two nodes. """ # TODO: raise ValueError when start not in graph if start not in graph: return None visited = visited or set() visited.add(start) for node in graph[start]: if node not in visited: find_connected_nodes(graph, node, visited) return visited class udict(dict[str, Scalar]): """Custom dict implementing __missing__.""" def __missing__(self, key: str): return 0 def copy(self: Self) -> Self: return udict(self) class UnitsContainer(Mapping[str, Scalar]): """The UnitsContainer stores the product of units and their respective exponent and implements the corresponding operations. UnitsContainer is a read-only mapping. All operations (even in place ones) return new instances. Parameters ---------- non_int_type Numerical type used for non integer values. """ __slots__ = ("_d", "_hash", "_one", "_non_int_type") _d: udict _hash: int | None _one: Scalar _non_int_type: type def __init__( self, *args: Any, non_int_type: type | None = None, **kwargs: Any ) -> None: if args and isinstance(args[0], UnitsContainer): default_non_int_type = args[0]._non_int_type else: default_non_int_type = float self._non_int_type = non_int_type or default_non_int_type if self._non_int_type is float: self._one = 1 else: self._one = self._non_int_type("1") d = udict(*args, **kwargs) self._d = d for key, value in d.items(): if not isinstance(key, str): raise TypeError(f"key must be a str, not {type(key)}") if not isinstance(value, Number): raise TypeError(f"value must be a number, not {type(value)}") if not isinstance(value, int) and not isinstance(value, self._non_int_type): d[key] = self._non_int_type(value) self._hash = None def copy(self: Self) -> Self: """Create a copy of this UnitsContainer.""" return self.__copy__() def add(self: Self, key: str, value: Number) -> Self: """Create a new UnitsContainer adding value to the value existing for a given key. Parameters ---------- key unit to which the value will be added. value value to be added. Returns ------- UnitsContainer A copy of this container. """ newval = self._d[key] + self._normalize_nonfloat_value(value) new = self.copy() if newval: new._d[key] = newval else: new._d.pop(key) new._hash = None return new def remove(self: Self, keys: Iterable[str]) -> Self: """Create a new UnitsContainer purged from given entries. Parameters ---------- keys Iterable of keys (units) to remove. Returns ------- UnitsContainer A copy of this container. """ new = self.copy() for k in keys: new._d.pop(k) new._hash = None return new def rename(self: Self, oldkey: str, newkey: str) -> Self: """Create a new UnitsContainer in which an entry has been renamed. Parameters ---------- oldkey Existing key (unit). newkey New key (unit). Returns ------- UnitsContainer A copy of this container. """ new = self.copy() new._d[newkey] = new._d.pop(oldkey) new._hash = None return new def unit_items(self) -> Iterable[tuple[str, Scalar]]: return self._d.items() def __iter__(self) -> Iterator[str]: return iter(self._d) def __len__(self) -> int: return len(self._d) def __getitem__(self, key: str) -> Scalar: return self._d[key] def __contains__(self, key: str) -> bool: return key in self._d def __hash__(self) -> int: if self._hash is None: self._hash = hash(frozenset(self._d.items())) return self._hash # Only needed by pickle protocol 0 and 1 (used by pytables) def __getstate__(self) -> tuple[udict, Scalar, type]: return self._d, self._one, self._non_int_type def __setstate__(self, state: tuple[udict, Scalar, type]): self._d, self._one, self._non_int_type = state self._hash = None def __eq__(self, other: Any) -> bool: if isinstance(other, UnitsContainer): # UnitsContainer.__hash__(self) is not the same as hash(self); see # ParserHelper.__hash__ and __eq__. # Different hashes guarantee that the actual contents are different, but # identical hashes give no guarantee of equality. # e.g. in CPython, hash(-1) == hash(-2) if UnitsContainer.__hash__(self) != UnitsContainer.__hash__(other): return False other = other._d elif isinstance(other, str): try: other = ParserHelper.from_string(other, self._non_int_type) except DefinitionSyntaxError: return False other = other._d return dict.__eq__(self._d, other) def __str__(self) -> str: return self.__format__("") def __repr__(self) -> str: tmp = "{%s}" % ", ".join( [f"'{key}': {value}" for key, value in sorted(self._d.items())] ) return f"" def __format__(self, spec: str) -> str: # TODO: provisional from .formatting import format_unit return format_unit(self, spec) def format_babel(self, spec: str, registry=None, **kwspec) -> str: # TODO: provisional from .formatting import format_unit return format_unit(self, spec, registry=registry, **kwspec) def __copy__(self): # Skip expensive health checks performed by __init__ out = object.__new__(self.__class__) out._d = self._d.copy() out._hash = self._hash out._non_int_type = self._non_int_type out._one = self._one return out def __mul__(self, other: Any): if not isinstance(other, self.__class__): err = "Cannot multiply UnitsContainer by {}" raise TypeError(err.format(type(other))) new = self.copy() for key, value in other.items(): new._d[key] += value if new._d[key] == 0: del new._d[key] new._hash = None return new __rmul__ = __mul__ def __pow__(self, other: Any): if not isinstance(other, NUMERIC_TYPES): err = "Cannot power UnitsContainer by {}" raise TypeError(err.format(type(other))) new = self.copy() for key, value in new._d.items(): new._d[key] *= other new._hash = None return new def __truediv__(self, other: Any): if not isinstance(other, self.__class__): err = "Cannot divide UnitsContainer by {}" raise TypeError(err.format(type(other))) new = self.copy() for key, value in other.items(): new._d[key] -= self._normalize_nonfloat_value(value) if new._d[key] == 0: del new._d[key] new._hash = None return new def __rtruediv__(self, other: Any): if not isinstance(other, self.__class__) and other != 1: err = "Cannot divide {} by UnitsContainer" raise TypeError(err.format(type(other))) return self**-1 def _normalize_nonfloat_value(self, value: Scalar) -> Scalar: if not isinstance(value, int) and not isinstance(value, self._non_int_type): return self._non_int_type(value) # type: ignore[no-any-return] return value class ParserHelper(UnitsContainer): """The ParserHelper stores in place the product of variables and their respective exponent and implements the corresponding operations. It also provides a scaling factor. For example: `3 * m ** 2` becomes ParserHelper(3, m=2) Briefly is a UnitsContainer with a scaling factor. ParserHelper is a read-only mapping. All operations (even in place ones) return new instances. WARNING : The hash value used does not take into account the scale attribute so be careful if you use it as a dict key and then two unequal object can have the same hash. Parameters ---------- scale Scaling factor. (default = 1) **kwargs Used to populate the dict of units and exponents. """ __slots__ = ("scale",) scale: Scalar def __init__(self, scale: Scalar = 1, *args, **kwargs): super().__init__(*args, **kwargs) self.scale = scale @classmethod def from_word(cls, input_word: str, non_int_type: type = float) -> ParserHelper: """Creates a ParserHelper object with a single variable with exponent one. Equivalent to: ParserHelper(1, {input_word: 1}) Parameters ---------- input_word non_int_type Numerical type used for non integer values. """ if non_int_type is float: return cls(1, [(input_word, 1)], non_int_type=non_int_type) else: ONE = non_int_type("1") return cls(ONE, [(input_word, ONE)], non_int_type=non_int_type) @classmethod def eval_token(cls, token: tokenize.TokenInfo, non_int_type: type = float): token_type = token.type token_text = token.string if token_type == NUMBER: if non_int_type is float: try: return int(token_text) except ValueError: return float(token_text) else: return non_int_type(token_text) elif token_type == NAME: return ParserHelper.from_word(token_text, non_int_type=non_int_type) else: raise Exception("unknown token type") @classmethod @lru_cache def from_string(cls, input_string: str, non_int_type: type = float) -> ParserHelper: """Parse linear expression mathematical units and return a quantity object. Parameters ---------- input_string non_int_type Numerical type used for non integer values. """ if not input_string: return cls(non_int_type=non_int_type) input_string = string_preprocessor(input_string) if "[" in input_string: input_string = input_string.replace("[", "__obra__").replace( "]", "__cbra__" ) reps = True else: reps = False gen = pint_eval.tokenizer(input_string) ret = build_eval_tree(gen).evaluate( partial(cls.eval_token, non_int_type=non_int_type) ) if isinstance(ret, Number): return cls(ret, non_int_type=non_int_type) if reps: ret = cls( ret.scale, { key.replace("__obra__", "[").replace("__cbra__", "]"): value for key, value in ret.items() }, non_int_type=non_int_type, ) for k in list(ret): if k.lower() == "nan": del ret._d[k] ret.scale = non_int_type(math.nan) return ret def __copy__(self): new = super().__copy__() new.scale = self.scale return new def copy(self): return self.__copy__() def __hash__(self): if self.scale != 1: mess = "Only scale 1 ParserHelper instance should be considered hashable" raise ValueError(mess) return super().__hash__() # Only needed by pickle protocol 0 and 1 (used by pytables) def __getstate__(self): return super().__getstate__() + (self.scale,) def __setstate__(self, state): super().__setstate__(state[:-1]) self.scale = state[-1] def __eq__(self, other: Any) -> bool: if isinstance(other, ParserHelper): return self.scale == other.scale and super().__eq__(other) elif isinstance(other, str): return self == ParserHelper.from_string(other, self._non_int_type) elif isinstance(other, Number): return self.scale == other and not len(self._d) return self.scale == 1 and super().__eq__(other) def operate(self, items, op=operator.iadd, cleanup: bool = True): d = udict(self._d) for key, value in items: d[key] = op(d[key], value) if cleanup: keys = [key for key, value in d.items() if value == 0] for key in keys: del d[key] return self.__class__(self.scale, d, non_int_type=self._non_int_type) def __str__(self): tmp = "{%s}" % ", ".join( [f"'{key}': {value}" for key, value in sorted(self._d.items())] ) return f"{self.scale} {tmp}" def __repr__(self): tmp = "{%s}" % ", ".join( [f"'{key}': {value}" for key, value in sorted(self._d.items())] ) return f"" def __mul__(self, other): if isinstance(other, str): new = self.add(other, self._one) elif isinstance(other, Number): new = self.copy() new.scale *= other elif isinstance(other, self.__class__): new = self.operate(other.items()) new.scale *= other.scale else: new = self.operate(other.items()) return new __rmul__ = __mul__ def __pow__(self, other): d = self._d.copy() for key in self._d: d[key] *= other return self.__class__(self.scale**other, d, non_int_type=self._non_int_type) def __truediv__(self, other): if isinstance(other, str): new = self.add(other, -1) elif isinstance(other, Number): new = self.copy() new.scale /= other elif isinstance(other, self.__class__): new = self.operate(other.items(), operator.sub) new.scale /= other.scale else: new = self.operate(other.items(), operator.sub) return new __floordiv__ = __truediv__ def __rtruediv__(self, other): new = self.__pow__(-1) if isinstance(other, str): new = new.add(other, self._one) elif isinstance(other, Number): new.scale *= other elif isinstance(other, self.__class__): new = self.operate(other.items(), operator.add) new.scale *= other.scale else: new = new.operate(other.items(), operator.add) return new #: List of regex substitution pairs. _subs_re_list = [ ("\N{DEGREE SIGN}", "degree"), (r"([\w\.\-\+\*\\\^])\s+", r"\1 "), # merge multiple spaces (r"({}) squared", r"\1**2"), # Handle square and cube (r"({}) cubed", r"\1**3"), (r"cubic ({})", r"\1**3"), (r"square ({})", r"\1**2"), (r"sq ({})", r"\1**2"), ( r"\b([0-9]+\.?[0-9]*)(?=[e|E][a-zA-Z]|[a-df-zA-DF-Z])", r"\1*", ), # Handle numberLetter for multiplication (r"([\w\.\)])\s+(?=[\w\(])", r"\1*"), # Handle space for multiplication ] #: Compiles the regex and replace {} by a regex that matches an identifier. _subs_re = [ (re.compile(a.format(r"[_a-zA-Z][_a-zA-Z0-9]*")), b) for a, b in _subs_re_list ] _pretty_table = str.maketrans("⁰¹²³⁴⁵⁶⁷⁸⁹·⁻", "0123456789*-") _pretty_exp_re = re.compile(r"(⁻?[⁰¹²³⁴⁵⁶⁷⁸⁹]+(?:\.[⁰¹²³⁴⁵⁶⁷⁸⁹]*)?)") def string_preprocessor(input_string: str) -> str: input_string = input_string.replace(",", "") input_string = input_string.replace(" per ", "/") for a, b in _subs_re: input_string = a.sub(b, input_string) input_string = _pretty_exp_re.sub(r"**(\1)", input_string) # Replace pretty format characters input_string = input_string.translate(_pretty_table) # Handle caret exponentiation input_string = input_string.replace("^", "**") return input_string def _is_dim(name: str) -> bool: return name[0] == "[" and name[-1] == "]" class SharedRegistryObject: """Base class for object keeping a reference to the registree. Such object are for now Quantity and Unit, in a number of places it is that an object from this class has a '_units' attribute. Parameters ---------- Returns ------- """ _REGISTRY: ClassVar[UnitRegistry] _units: UnitsContainer def __new__(cls, *args, **kwargs): inst = object.__new__(cls) if not hasattr(cls, "_REGISTRY"): # Base class, not subclasses dynamically by # UnitRegistry._init_dynamic_classes from . import application_registry inst._REGISTRY = application_registry.get() return inst def _check(self, other: Any) -> bool: """Check if the other object use a registry and if so that it is the same registry. Parameters ---------- other Returns ------- bool Raises ------ ValueError if other don't use the same unit registry. """ if self._REGISTRY is getattr(other, "_REGISTRY", None): return True elif isinstance(other, SharedRegistryObject): mess = "Cannot operate with {} and {} of different registries." raise ValueError( mess.format(self.__class__.__name__, other.__class__.__name__) ) else: return False class PrettyIPython: """Mixin to add pretty-printers for IPython""" default_format: str def _repr_html_(self) -> str: if "~" in self._REGISTRY.formatter.default_format: return f"{self:~H}" return f"{self:H}" def _repr_latex_(self) -> str: if "~" in self._REGISTRY.formatter.default_format: return f"${self:~L}$" return f"${self:L}$" def _repr_pretty_(self, p, cycle: bool): # if cycle: if "~" in self._REGISTRY.formatter.default_format: p.text(f"{self:~P}") else: p.text(f"{self:P}") # else: # p.pretty(self.magnitude) # p.text(" ") # p.pretty(self.units) def to_units_container( unit_like: QuantityOrUnitLike, registry: UnitRegistry | None = None ) -> UnitsContainer: """Convert a unit compatible type to a UnitsContainer. Parameters ---------- unit_like Quantity or Unit to infer the plain units from. registry If provided, uses the registry's UnitsContainer and parse_unit_name. If None, uses the registry attached to unit_like. Returns ------- UnitsContainer """ mro = type(unit_like).mro() if UnitsContainer in mro: return unit_like elif SharedRegistryObject in mro: return unit_like._units elif str in mro: if registry: # TODO: document how to whether to lift preprocessing loop out to caller for p in registry.preprocessors: unit_like = p(unit_like) return registry.parse_units_as_container(unit_like) else: return ParserHelper.from_string(unit_like) elif dict in mro: if registry: return registry.UnitsContainer(unit_like) else: return UnitsContainer(unit_like) def infer_base_unit( unit_like: QuantityOrUnitLike, registry: UnitRegistry | None = None ) -> UnitsContainer: """ Given a Quantity or UnitLike, give the UnitsContainer for it's plain units. Parameters ---------- unit_like Quantity or Unit to infer the plain units from. registry If provided, uses the registry's UnitsContainer and parse_unit_name. If None, uses the registry attached to unit_like. Returns ------- UnitsContainer Raises ------ ValueError The unit_like did not reference a registry, and no registry was provided. """ d = udict() original_units = to_units_container(unit_like, registry) if registry is None and hasattr(unit_like, "_REGISTRY"): registry = unit_like._REGISTRY if registry is None: raise ValueError("No registry provided.") for unit_name, power in original_units.items(): candidates = registry.parse_unit_name(unit_name) assert len(candidates) == 1 _, base_unit, _ = candidates[0] d[base_unit] += power # remove values that resulted in a power of 0 nonzero_dict = {k: v for k, v in d.items() if v != 0} return registry.UnitsContainer(nonzero_dict) def getattr_maybe_raise(obj: Any, item: str): """Helper function invoked at start of all overridden ``__getattr__``. Raise AttributeError if the user tries to ask for a _ or __ attribute, *unless* it is immediately followed by a number, to enable units encompassing constants, such as ``L / _100km``. Parameters ---------- item attribute to be found. Raises ------ AttributeError """ # Double-underscore attributes are tricky to detect because they are # automatically prefixed with the class name - which may be a subclass of obj if ( item.endswith("__") or len(item.lstrip("_")) == 0 or (item.startswith("_") and not item.lstrip("_")[0].isdigit()) ): raise AttributeError(f"{obj!r} object has no attribute {item!r}") def iterable(y: Any) -> bool: """Check whether or not an object can be iterated over.""" try: iter(y) except TypeError: return False return True def sized(y: Any) -> bool: """Check whether or not an object has a defined length.""" try: len(y) except TypeError: return False return True def create_class_with_registry( registry: UnitRegistry, base_class: type[TT] ) -> type[TT]: """Create new class inheriting from base_class and filling _REGISTRY class attribute with an actual instanced registry. """ class_body = { "__module__": "pint", "_REGISTRY": registry, } return types.new_class( base_class.__name__, bases=(base_class,), exec_body=lambda ns: ns.update(class_body), ) pint-0.24.4/pint/xtranslated.txt000066400000000000000000000011001471316474000166160ustar00rootroot00000000000000 # a few unit definitions added to use the translations by unicode cldr dietary_calorie = 1000 * calorie = Cal = Calorie metric_cup = liter / 4 square_meter = meter ** 2 = sq_m square_kilometer = kilometer ** 2 = sq_km mile_scandinavian = 10000 * meter cubic_mile = 1 * mile ** 3 = cu_mile = cubic_miles cubic_meter = 1 * meter ** 3 = cu_m cubic_kilometer = 1 * kilometer ** 3 = cu_km [consumption] = [volume] / [length] liter_per_kilometer = liter / kilometer liter_per_100kilometers = liter / (100 * kilometers) [US_consumption] = [length] / [volume] MPG = mile / gallon pint-0.24.4/pyproject.toml000066400000000000000000000050051471316474000154760ustar00rootroot00000000000000[project] name = "Pint" authors = [ {name="Hernan E. Grecco", email="hernan.grecco@gmail.com"} ] license = {text = "BSD"} description = "Physical quantities module" readme = "README.rst" maintainers = [ {name="Hernan E. Grecco", email="hernan.grecco@gmail.com"}, {name="Jules Chéron", email="julescheron@gmail.com"} ] keywords = ["physical", "quantities", "unit", "conversion", "science"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python", "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] requires-python = ">=3.9" dynamic = ["version", "dependencies"] [tool.setuptools.package-data] pint = [ "default_en.txt", "constants_en.txt", "py.typed"] [tool.setuptools.dynamic] dependencies = {file = "requirements.txt"} [project.optional-dependencies] testbase = [ "pytest", "pytest-cov", "pytest-subtests", "pytest-benchmark" ] test = [ "pytest", "pytest-mpl", "pytest-cov", "pytest-subtests", "pytest-benchmark" ] bench = [ "pytest", "pytest-codspeed" ] numpy = ["numpy >= 1.23"] uncertainties = ["uncertainties >= 3.1.6"] babel = ["babel <= 2.8"] pandas = ["pint-pandas >= 0.3"] xarray = ["xarray"] dask = ["dask"] mip = ["mip >= 1.13"] [project.urls] Homepage = "https://github.com/hgrecco/pint" Documentation = "https://pint.readthedocs.io/" [project.scripts] pint-convert = "pint.pint_convert:main" [tool.setuptools] packages = ["pint"] [build-system] requires = ["setuptools>=61", "wheel", "setuptools_scm[toml]>=3.4.3"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] [tool.ruff] extend-exclude = ["build"] line-length=88 [tool.ruff.lint.isort] required-imports = ["from __future__ import annotations"] known-first-party= ["pint"] [tool.ruff.lint] extend-select = [ "I", # isort ] ignore = [ # whitespace before ':' - doesn't work well with black # "E203", "E402", # line too long - let black worry about that "E501", # do not assign a lambda expression, use a def "E731", # line break before binary operator # "W503" ] pint-0.24.4/requirements.txt000066400000000000000000000001141471316474000160420ustar00rootroot00000000000000platformdirs>=2.1.0 typing_extensions>=4.0.0 flexcache>=0.3 flexparser>=0.4 pint-0.24.4/requirements_docs.txt000066400000000000000000000004041471316474000170540ustar00rootroot00000000000000sphinx>=6 ipython<=8.12 matplotlib mip>=1.13 nbsphinx numpy pytest jupyter_client ipykernel ipython graphviz xarray pooch sparse dask[complete] setuptools>=41.2 Serialize pygments>=2.4 sphinx-book-theme>=1.1.0 sphinx_copybutton sphinx_design typing_extensions