pax_global_header00006660000000000000000000000064142276004300014510gustar00rootroot0000000000000052 comment=f0f9002fbbd5704dbd86cbcb397f6eac1217a8ef pint-0.19.2/000077500000000000000000000000001422760043000125535ustar00rootroot00000000000000pint-0.19.2/.coveragerc000066400000000000000000000010011422760043000146640ustar00rootroot00000000000000[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.19.2/.editorconfig000066400000000000000000000001661422760043000152330ustar00rootroot00000000000000root = true [*.py] charset = utf-8 indent_style = space indent_size = 4 insert_final_newline = true end_of_line = lf pint-0.19.2/.gitattributes000066400000000000000000000000241422760043000154420ustar00rootroot00000000000000CHANGES merge=union pint-0.19.2/.github/000077500000000000000000000000001422760043000141135ustar00rootroot00000000000000pint-0.19.2/.github/pull_request_template.md000066400000000000000000000003561422760043000210600ustar00rootroot00000000000000- [ ] 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.19.2/.github/workflows/000077500000000000000000000000001422760043000161505ustar00rootroot00000000000000pint-0.19.2/.github/workflows/ci.yml000066400000000000000000000122401422760043000172650ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: test-linux: strategy: fail-fast: false matrix: python-version: [3.8, 3.9, "3.10"] numpy: [null, "numpy>=1.19,<2.0.0"] uncertainties: [null, "uncertainties==3.1.6", "uncertainties>=3.1.6,<4.0.0"] extras: [null] include: - python-version: 3.8 # Minimal versions numpy: numpy==1.19.5 extras: matplotlib==2.2.5 - python-version: 3.8 numpy: "numpy" uncertainties: "uncertainties" extras: "sparse xarray netCDF4 dask[complete] graphviz babel==2.8" runs-on: ubuntu-latest env: TEST_OPTS: "-rfsxEX -s --cov=pint --cov-config=.coveragerc" 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 dependencies run: | sudo apt install -y graphviz pip install pytest pytest-cov pytest-subtests pip install . - 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 coveralls test-windows: strategy: fail-fast: false matrix: python-version: [3.8, 3.9, "3.10"] numpy: [ "numpy>=1.19,<2.0.0" ] # uncertainties: [null, "uncertainties==3.1.6", "uncertainties>=3.1.6,<4.0.0"] # extras: [null] # include: # - python-version: 3.8 # Minimal versions # numpy: numpy==1.19.5 # extras: matplotlib==2.2.5 # - python-version: 3.8 # numpy: "numpy" # uncertainties: "uncertainties" # extras: "sparse xarray netCDF4 dask[complete] graphviz babel==2.8" runs-on: windows-latest env: TEST_OPTS: "-rfsxEX -s -k issue1498b" 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 pytest pytest-cov pytest-subtests pip install . # - name: Install pytest-mpl # if: contains(matrix.extras, 'matplotlib') # run: pip install pytest-mpl - name: Run tests run: pytest ${env:TEST_OPTS} 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 coveralls --finish # Dummy task to summarize all. See https://github.com/bors-ng/bors-ng/issues/1300 ci-success: name: ci if: ${{ success() }} needs: test-linux runs-on: ubuntu-latest steps: - name: CI succeeded run: exit 0 pint-0.19.2/.github/workflows/docs.yml000066400000000000000000000022741422760043000176300ustar00rootroot00000000000000name: 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 Python 3.8 uses: actions/setup-python@v2 with: python-version: 3.8 - 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 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.19.2/.github/workflows/lint-autoupdate.yml000066400000000000000000000026741422760043000220230ustar00rootroot00000000000000name: 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.19.2/.github/workflows/lint.yml000066400000000000000000000005201422760043000176360ustar00rootroot00000000000000name: 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.19.2/.gitignore000066400000000000000000000007201422760043000145420ustar00rootroot00000000000000*~ __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 # 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.19.2/.pre-commit-config.yaml000066400000000000000000000006761422760043000170450ustar00rootroot00000000000000exclude: '^pint/_vendor' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/psf/black rev: 22.3.0 hooks: - id: black - repo: https://github.com/pycqa/isort rev: 5.10.1 hooks: - id: isort - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 pint-0.19.2/.readthedocs.yaml000066400000000000000000000003541422760043000160040ustar00rootroot00000000000000version: 2 build: image: latest sphinx: configuration: docs/conf.py fail_on_warning: false python: version: 3.8 install: - requirements: requirements_docs.txt - method: pip path: . system_packages: false pint-0.19.2/AUTHORS000066400000000000000000000040401422760043000136210ustar00rootroot00000000000000Pint 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.19.2/CHANGES000066400000000000000000001030441422760043000135500ustar00rootroot00000000000000Pint Changelog ============== 0.20 (unreleased) ----------------- - Add the ``separate_format_defaults`` registry setting (Issue #1501, PR #1503) 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.19.2/LICENSE000066400000000000000000000030601422760043000135570ustar00rootroot00000000000000Copyright (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.19.2/MANIFEST.in000066400000000000000000000005721422760043000143150ustar00rootroot00000000000000include AUTHORS CHANGES LICENSE README.rst BADGES.rst version.txt .coveragerc .readthedocs.yaml recursive-include pint * recursive-include docs * recursive-include bench * prune docs/_build prune docs/_themes/.git exclude .editorconfig bors.toml pull_request_template.md requirements_docs.txt version.py global-exclude *.pyc *~ .DS_Store *__pycache__* *.pyo .travis-exclude.yml pint-0.19.2/README.rst000066400000000000000000000133141422760043000142440ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/pint.svg :target: https://pypi.python.org/pypi/pint :alt: Latest Version .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/python/black .. 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.8+ 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/extending.html#extension-types .. _`pint-pandas Jupyter notebook`: https://github.com/hgrecco/pint-pandas/blob/master/notebooks/pandas_support.ipynb .. _`AUTHORS`: https://github.com/hgrecco/pint/blob/master/AUTHORS .. _`CHANGES`: https://github.com/hgrecco/pint/blob/master/CHANGES pint-0.19.2/asv.conf.json000066400000000000000000000152551422760043000151730ustar00rootroot00000000000000{ // The version of the config file format. Do not change, unless // you know what you are doing. "version": 1, // The name of the project being benchmarked "project": "pint", // The project's homepage "project_url": "https://github.com/hgrecco/pint", // The URL or local path of the source code repository for the // project being benchmarked "repo": ".", // The Python project's subdirectory in your repo. If missing or // the empty string, the project is assumed to be located at the root // of the repository. // "repo_subdir": "", // Customizable commands for building, installing, and // uninstalling the project. See asv.conf.json documentation. // // "install_command": ["in-dir={env_dir} python -mpip install {wheel_file}"], // "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"], // "build_command": [ // "python setup.py build", // "PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}" // ], // List of branches to benchmark. If not provided, defaults to "master" // (for git) or "default" (for mercurial). // "branches": ["master"], // for git // "branches": ["default"], // for mercurial // The DVCS being used. If not set, it will be automatically // determined from "repo" by looking at the protocol in the URL // (if remote), or by looking for special directories, such as // ".git" (if local). // "dvcs": "git", // The tool to use to create environments. May be "conda", // "virtualenv" or other value depending on the plugins in use. // If missing or the empty string, the tool will be automatically // determined by looking for tools on the PATH environment // variable. "environment_type": "conda", // timeout in seconds for installing any dependencies in environment // defaults to 10 min //"install_timeout": 600, // the base URL to show a commit for the project. "show_commit_url": "http://github.com/hgrecco/pint/commit/", // The Pythons you'd like to test against. If not provided, defaults // to the current version of Python used to run `asv`. "pythons": ["3.9"], // The list of conda channel names to be searched for benchmark // dependency packages in the specified order // "conda_channels": ["conda-forge", "defaults"], // The matrix of dependencies to test. Each key is the name of a // package (in PyPI) and the values are version numbers. An empty // list or empty string indicates to just test against the default // (latest) version. null indicates that the package is to not be // installed. If the package to be tested is only available from // PyPi, and the 'environment_type' is conda, then you can preface // the package name by 'pip+', and the package will be installed via // pip (with all the conda available packages installed first, // followed by the pip installed packages). "matrix": { "numpy": ["1.19"], // "six": ["", null], // test with and without six installed // "pip+emcee": [""], // emcee is only available for install with pip. }, // Combinations of libraries/python versions can be excluded/included // from the set to test. Each entry is a dictionary containing additional // key-value pairs to include/exclude. // // An exclude entry excludes entries where all values match. The // values are regexps that should match the whole string. // // An include entry adds an environment. Only the packages listed // are installed. The 'python' key is required. The exclude rules // do not apply to includes. // // In addition to package names, the following keys are available: // // - python // Python version, as in the *pythons* variable above. // - environment_type // Environment type, as above. // - sys_platform // Platform, as in sys.platform. Possible values for the common // cases: 'linux2', 'win32', 'cygwin', 'darwin'. // // "exclude": [ // {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows // {"environment_type": "conda", "six": null}, // don't run without six on conda // ], // // "include": [ // // additional env for python2.7 // {"python": "2.7", "numpy": "1.8"}, // // additional env if run on windows+conda // {"platform": "win32", "environment_type": "conda", "python": "2.7", "libpython": ""}, // ], // The directory (relative to the current directory) that benchmarks are // stored in. If not provided, defaults to "benchmarks" // "benchmark_dir": "benchmarks", // The directory (relative to the current directory) to cache the Python // environments in. If not provided, defaults to "env" "env_dir": ".asv/env", // The directory (relative to the current directory) that raw benchmark // results are stored in. If not provided, defaults to "results". "results_dir": ".asv/results", // The directory (relative to the current directory) that the html tree // should be written to. If not provided, defaults to "html". "html_dir": ".asv/html", // The number of characters to retain in the commit hashes. // "hash_length": 8, // `asv` will cache results of the recent builds in each // environment, making them faster to install next time. This is // the number of builds to keep, per environment. // "build_cache_size": 2, // The commits after which the regression search in `asv publish` // should start looking for regressions. Dictionary whose keys are // regexps matching to benchmark names, and values corresponding to // the commit (exclusive) after which to start looking for // regressions. The default is to start from the first commit // with results. If the commit is `null`, regression detection is // skipped for the matching benchmark. // // "regressions_first_commits": { // "some_benchmark": "352cdf", // Consider regressions only after this commit // "another_benchmark": null, // Skip regression detection altogether // }, // The thresholds for relative change in results, after which `asv // publish` starts reporting regressions. Dictionary of the same // form as in ``regressions_first_commits``, with values // indicating the thresholds. If multiple entries match, the // maximum is taken. If no entry matches, the default is 5%. // // "regressions_thresholds": { // "some_benchmark": 0.01, // Threshold of 1% // "another_benchmark": 0.5, // Threshold of 50% // }, } pint-0.19.2/benchmarks/000077500000000000000000000000001422760043000146705ustar00rootroot00000000000000pint-0.19.2/benchmarks/00_common.py000066400000000000000000000000611422760043000170260ustar00rootroot00000000000000def time_import(): import pint # noqa: F401 pint-0.19.2/benchmarks/01_registry_creation.py000066400000000000000000000004261422760043000213000ustar00rootroot00000000000000import pint from . import util def time_create_registry(args): if len(args) == 2: pint.UnitRegistry(args[0], cache_folder=args[1]) else: pint.UnitRegistry(*args) time_create_registry.params = [[(None,), tuple(), (util.get_tiny_def(),), ("", None)]] pint-0.19.2/benchmarks/10_registry.py000066400000000000000000000066151422760043000174220ustar00rootroot00000000000000import pathlib import pint from . import util units = ("meter", "kilometer", "second", "minute", "angstrom") other_units = ("meter", "angstrom", "kilometer/second", "angstrom/minute") all_values = ("int", "float", "complex") ureg = None data = {} def setup(*args): global ureg, data data["int"] = 1 data["float"] = 1.0 data["complex"] = complex(1, 2) ureg = pint.UnitRegistry(util.get_tiny_def()) def my_setup(*args): global data setup(*args) for unit in units + other_units: data["uc_%s" % unit] = pint.registry.to_units_container(unit, ureg) def time_build_cache(): ureg._build_cache() def time_getattr(key): getattr(ureg, key) time_getattr.params = units def time_getitem(key): ureg[key] time_getitem.params = units def time_parse_unit_name(key): ureg.parse_unit_name(key) time_parse_unit_name.params = units def time_parse_units(key): ureg.parse_units(key) time_parse_units.params = units def time_parse_expression(key): ureg.parse_expression("1.0 " + key) time_parse_expression.params = units def time_base_units(unit): ureg.get_base_units(unit) time_base_units.params = other_units def time_to_units_container_registry(unit): pint.registry.to_units_container(unit, ureg) time_to_units_container_registry.params = other_units def time_to_units_container_detached(unit): pint.registry.to_units_container(unit, ureg) time_to_units_container_detached.params = other_units def time_convert_from_uc(key): src, dst = key ureg._convert(1.0, data[src], data[dst]) time_convert_from_uc.setup = my_setup time_convert_from_uc.params = [ (("uc_meter", "uc_kilometer"), ("uc_kilometer/second", "uc_angstrom/minute")) ] def time_parse_math_expression(): ureg.parse_expression("3 + 5 * 2 + value", value=10) # This code is duplicated with other benchmarks but simplify comparison CACHE_FOLDER = pathlib.Path(".cache") CACHE_FOLDER.mkdir(exist_ok=True) pint.UnitRegistry(cache_folder=CACHE_FOLDER) def time_load_definitions_stage_1(cache_folder): """empty registry creation""" # Change this into a single part benchmark using setup _ = pint.UnitRegistry(None, cache_folder=None) time_load_definitions_stage_1.param_names = [ "cache_folder", ] time_load_definitions_stage_1.params = [ None, CACHE_FOLDER, ] def time_load_definitions_stage_2(cache_folder, *args, **kwargs): """empty registry creation + parsing default files + definition object loading""" # Change this into a single part benchmark using setup empty_registry = pint.UnitRegistry(None, cache_folder=cache_folder) empty_registry.load_definitions("default_en.txt", True) time_load_definitions_stage_2.param_names = time_load_definitions_stage_1.param_names time_load_definitions_stage_2.params = time_load_definitions_stage_1.params def time_load_definitions_stage_3(cache_folder, *args, **kwargs): """empty registry creation + parsing default files + definition object loading + cache building""" # Change this into a single part benchmark using setup empty_registry = pint.UnitRegistry(None, cache_folder=cache_folder) loaded_files = empty_registry.load_definitions("default_en.txt", True) empty_registry._build_cache(loaded_files) time_load_definitions_stage_3.param_names = time_load_definitions_stage_1.param_names time_load_definitions_stage_3.params = time_load_definitions_stage_1.params pint-0.19.2/benchmarks/20_quantity.py000066400000000000000000000021411422760043000174170ustar00rootroot00000000000000import itertools as it import operator import pint from . import util units = ("meter", "kilometer", "second", "minute", "angstrom") all_values = ("int", "float", "complex") all_values_q = tuple( "%s_%s" % (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) ureg = None data = {} def setup(*args): global ureg, data data["int"] = 1 data["float"] = 1.0 data["complex"] = complex(1, 2) ureg = pint.UnitRegistry(util.get_tiny_def()) for key in all_values: data[key + "_meter"] = data[key] * ureg.meter data[key + "_kilometer"] = data[key] * ureg.kilometer def time_build_by_mul(key): data[key] * ureg.meter time_build_by_mul.params = all_values def time_op1(key, op): op(data[key]) time_op1.params = [all_values_q, op1] def time_op2(keys, op): key1, key2 = keys op(data[key1], data[key2]) time_op2.params = [tuple(it.product(all_values_q, all_values_q)), op2_math + op2_cmp] pint-0.19.2/benchmarks/30_numpy.py000066400000000000000000000043761422760043000167260ustar00rootroot00000000000000import itertools as it import operator import numpy as np import pint from . import util lengths = ("short", "mid") all_values = tuple( "%s_%s" % (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("%s_%s" % (a, b) for a, b in it.product(all_arrays, units)) ureg = None data = {} op1 = (operator.neg,) # operator.truth, op2_cmp = (operator.eq, operator.lt) op2_math = (operator.add, operator.sub, operator.mul, operator.truediv) numpy_op2_cmp = (np.equal, np.less) numpy_op2_math = (np.add, np.subtract, np.multiply, np.true_divide) def float_range(n): return (float(x) for x in range(1, n + 1)) def setup(*args): global ureg, 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 = pint.UnitRegistry(util.get_tiny_def()) for key in all_arrays: data[key + "_meter"] = data[key] * ureg.meter data[key + "_kilometer"] = data[key] * ureg.kilometer def time_finding_meter_getattr(): ureg.meter def time_finding_meter_getitem(): ureg["meter"] def time_base_units(unit): ureg.get_base_units(unit) time_base_units.params = ["meter", "angstrom", "meter/second", "angstrom/minute"] def time_build_by_mul(key): data[key] * ureg.meter time_build_by_mul.params = all_arrays def time_op1(key, op): op(data[key]) time_op1.params = [all_arrays_q, op1 + (np.sqrt, np.square)] def time_op2(keys, op): key1, key2 = keys op(data[key1], data[key2]) time_op2.params = [ ( ("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"), ), op2_math + op2_cmp + numpy_op2_math + numpy_op2_cmp, ] pint-0.19.2/benchmarks/__init__.py000066400000000000000000000000001422760043000167670ustar00rootroot00000000000000pint-0.19.2/benchmarks/util.py000066400000000000000000000012721422760043000162210ustar00rootroot00000000000000import io SMALL_VEC_LEN = 3 MID_VEC_LEN = 1_000 LARGE_VEC_LEN = 1_000_000 TINY_DEF = """ 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- 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 """ def get_tiny_def(): return io.StringIO(TINY_DEF) pint-0.19.2/bors.toml000066400000000000000000000002041422760043000144110ustar00rootroot00000000000000status = [ "ci", "docbuild", "lint" ] delete_merged_branches = true timeout_sec = 10800 block_labels = [ "do-not-merge-yet" ] pint-0.19.2/docs/000077500000000000000000000000001422760043000135035ustar00rootroot00000000000000pint-0.19.2/docs/Makefile000066400000000000000000000130021422760043000151370ustar00rootroot00000000000000# 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.19.2/docs/_static/000077500000000000000000000000001422760043000151315ustar00rootroot00000000000000pint-0.19.2/docs/_static/logo-full.jpg000066400000000000000000000270071422760043000175410ustar00rootroot00000000000000JFIFHH@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.19.2/docs/_templates/000077500000000000000000000000001422760043000156405ustar00rootroot00000000000000pint-0.19.2/docs/_templates/sidebarintro.html000066400000000000000000000013341422760043000212140ustar00rootroot00000000000000

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.19.2/docs/_templates/sidebarlogo.html000066400000000000000000000015551422760043000210260ustar00rootroot00000000000000

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.19.2/docs/_themes/000077500000000000000000000000001422760043000151275ustar00rootroot00000000000000pint-0.19.2/docs/_themes/.gitignore000066400000000000000000000000261422760043000171150ustar00rootroot00000000000000*.pyc *.pyo .DS_Store pint-0.19.2/docs/_themes/LICENSE000066400000000000000000000033751422760043000161440ustar00rootroot00000000000000Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, 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. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME 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 THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pint-0.19.2/docs/_themes/README000066400000000000000000000021051422760043000160050ustar00rootroot00000000000000Flask Sphinx Styles =================== This repository contains sphinx styles for Flask and Flask related projects. To use this style in your Sphinx documentation, follow this guide: 1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. 2. add this to your conf.py: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' The following themes exist: - 'flask' - the standard flask documentation theme for large projects - 'flask_small' - small one-page theme. Intended to be used by very small addon libraries for flask. The following options exist for the flask_small theme: [options] index_logo = '' filename of a picture in _static to be used as replacement for the h1 in the index.rst file. index_logo_height = 120px height of the index logo github_fork = '' repository name on github for the "fork me" badge pint-0.19.2/docs/_themes/flask/000077500000000000000000000000001422760043000162275ustar00rootroot00000000000000pint-0.19.2/docs/_themes/flask/layout.html000066400000000000000000000017771422760043000204460ustar00rootroot00000000000000{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if theme_touch_icon %} {% endif %} {% endblock %} {%- block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {%- block footer %} {% if pagename == 'index' %}
{% endif %} {%- endblock %} pint-0.19.2/docs/_themes/flask/relations.html000066400000000000000000000007711422760043000211220ustar00rootroot00000000000000

Related Topics

{%- endfor %} {%- if prev %}

Previous
{{ prev.title }}

{%- endif %} {%- if next %}

Next
{{ next.title }}

{%- endif %} pint-0.19.2/docs/_themes/flask/static/000077500000000000000000000000001422760043000175165ustar00rootroot00000000000000pint-0.19.2/docs/_themes/flask/static/flasky.css_t000066400000000000000000000144131422760043000220470ustar00rootroot00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ {% set page_width = '940px' %} {% set sidebar_width = '220px' %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; background-color: white; color: #000; margin: 0; padding: 0; } div.document { width: {{ page_width }}; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}; } div.sphinxsidebar { width: {{ sidebar_width }}; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.related { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0 0 20px 0; margin: 0; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Garamond', 'Georgia', serif; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Georgia', serif; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } dd div.admonition { margin-left: -60px; padding-left: 60px; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { margin: 10px 0 10px 30px; padding: 0; } pre { background: #eee; padding: 7px 30px; margin: 15px -30px; line-height: 1.3em; } dl pre, blockquote pre, li pre { margin-left: -60px; padding-left: 60px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt { background: #EEE; } pint-0.19.2/docs/_themes/flask/static/small_flask.css000066400000000000000000000017201422760043000225200ustar00rootroot00000000000000/* * small_flask.css_t * ~~~~~~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } pint-0.19.2/docs/_themes/flask/theme.conf000066400000000000000000000002761422760043000202050ustar00rootroot00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px touch_icon = github_fork = hgrecco/pint pint-0.19.2/docs/_themes/flask_theme_support.py000066400000000000000000000075011422760043000215620ustar00rootroot00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import ( Comment, Error, Generic, Keyword, Literal, Name, Number, Operator, Other, Punctuation, String, Whitespace, ) class FlaskyStyle(Style): background_color = "#f8f8f8" default_style = "" styles = { # No corresponding class for the following: # Text: "", # class: '' Whitespace: "underline #f8f8f8", # class: 'w' Error: "#a40000 border:#ef2929", # class: 'err' Other: "#000000", # class 'x' Comment: "italic #8f5902", # class: 'c' Comment.Preproc: "noitalic", # class: 'cp' Keyword: "bold #004461", # class: 'k' Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Namespace: "bold #004461", # class: 'kn' Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Type: "bold #004461", # class: 'kt' Operator: "#582800", # class: 'o' Operator.Word: "bold #004461", # class: 'ow' - like keywords Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: "#000000", # class: 'n' Name.Attribute: "#c4a000", # class: 'na' - to be revised Name.Builtin: "#004461", # class: 'nb' Name.Builtin.Pseudo: "#3465a4", # class: 'bp' Name.Class: "#000000", # class: 'nc' - to be revised Name.Constant: "#000000", # class: 'no' - to be revised Name.Decorator: "#888", # class: 'nd' - to be revised Name.Entity: "#ce5c00", # class: 'ni' Name.Exception: "bold #cc0000", # class: 'ne' Name.Function: "#000000", # class: 'nf' Name.Property: "#000000", # class: 'py' Name.Label: "#f57900", # class: 'nl' Name.Namespace: "#000000", # class: 'nn' - to be revised Name.Other: "#000000", # class: 'nx' Name.Tag: "bold #004461", # class: 'nt' - like a keyword Name.Variable: "#000000", # class: 'nv' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Number: "#990000", # class: 'm' Literal: "#000000", # class: 'l' Literal.Date: "#000000", # class: 'ld' String: "#4e9a06", # class: 's' String.Backtick: "#4e9a06", # class: 'sb' String.Char: "#4e9a06", # class: 'sc' String.Doc: "italic #8f5902", # class: 'sd' - like a comment String.Double: "#4e9a06", # class: 's2' String.Escape: "#4e9a06", # class: 'se' String.Heredoc: "#4e9a06", # class: 'sh' String.Interpol: "#4e9a06", # class: 'si' String.Other: "#4e9a06", # class: 'sx' String.Regex: "#4e9a06", # class: 'sr' String.Single: "#4e9a06", # class: 's1' String.Symbol: "#4e9a06", # class: 'ss' Generic: "#000000", # class: 'g' Generic.Deleted: "#a40000", # class: 'gd' Generic.Emph: "italic #000000", # class: 'ge' Generic.Error: "#ef2929", # class: 'gr' Generic.Heading: "bold #000080", # class: 'gh' Generic.Inserted: "#00A000", # class: 'gi' Generic.Output: "#888", # class: 'go' Generic.Prompt: "#745334", # class: 'gp' Generic.Strong: "bold #000000", # class: 'gs' Generic.Subheading: "bold #800080", # class: 'gu' Generic.Traceback: "bold #a40000", # class: 'gt' } pint-0.19.2/docs/conf.py000066400000000000000000000255021422760043000150060ustar00rootroot00000000000000#!/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. import datetime try: from importlib.metadata import version except ImportError: # Backport for Python < 3.8 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.autosectionlabel", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx.ext.mathjax", "matplotlib.sphinxext.plot_directive", "nbsphinx", "IPython.sphinxext.ipython_directive", "IPython.sphinxext.ipython_console_highlighting", ] # 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 = "%s, %s" % (this_year, author) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for extensions ---------------------------------------------------- # napoleon napoleon_preprocess_types = True # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'default' html_theme = "flask" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] html_theme_path = ["_themes"] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # 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", ], } # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # 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") ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- 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 # The language of the text. It defaults to the language option # or en if the language is not set. # epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. # epub_identifier = '' # A unique identification for the text. # epub_uid = '' # A tuple containing the cover image and cover page html template filenames. # epub_cover = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_post_files = [] # A list of files that should not be packed into the epub file. # epub_exclude_files = [] # The depth of the table of contents in toc.ncx. # epub_tocdepth = 3 # Allow duplicate toc entries. # epub_tocdup = True # 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), "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.19.2/docs/contexts.rst000066400000000000000000000253361422760043000161150ustar00rootroot00000000000000 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.19.2/docs/contributing.rst000066400000000000000000000117621422760043000167530ustar00rootroot00000000000000.. _contributing: Contributing to Pint ==================== Pint uses (and thanks): - github_ to host the code - travis_ to test all commits and PRs. - coveralls_ to monitor coverage test coverage - readthedocs_ to host the documentation. - `bors-ng`_ as a merge bot and therefore every PR is tested before merging. - 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 . $ 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 number of `extension/compatibility packages`_ have arisen to aid in compatibility between certain packages. 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 `the type casting hierarchy`_) * 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 .. _`bors-ng`: https://github.com/bors-ng/bors-ng .. _`github docs`: https://help.github.com/articles/closing-issues-via-commit-messages/ .. _travis: https://travis-ci.com/ .. _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/ pint-0.19.2/docs/currencies.rst000066400000000000000000000061311422760043000164000ustar00rootroot00000000000000.. _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`). pint-0.19.2/docs/defining-quantities.rst000066400000000000000000000107211422760043000202050ustar00rootroot00000000000000Defining 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: .. 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')) .Quantity'> .. 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.19.2/docs/defining.rst000066400000000000000000000126351422760043000160270ustar00rootroot00000000000000.. _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.19.2/docs/developers_reference.rst000066400000000000000000000015771422760043000204350ustar00rootroot00000000000000=================== Developer reference =================== All Modules =========== .. automodule:: pint :members: .. automodule:: pint.babel_names :members: .. automodule:: pint.compat :members: .. automodule:: pint.context :members: .. automodule:: pint.converters :members: .. automodule:: pint.definitions :members: .. automodule:: pint.errors :members: .. automodule:: pint.formatting :members: .. automodule:: pint.matplotlib :members: .. automodule:: pint.measurement :members: .. automodule:: pint.pint_eval :members: .. automodule:: pint.quantity :members: .. automodule:: pint.registry :members: .. automodule:: pint.registry_helpers :members: .. automodule:: pint.systems :members: .. automodule:: pint.testing :members: .. automodule:: pint.unit :members: .. automodule:: pint.util :members: pint-0.19.2/docs/faq.rst000066400000000000000000000022051422760043000150030ustar00rootroot00000000000000.. _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.19.2/docs/formatting.rst000066400000000000000000000116311422760043000164110ustar00rootroot00000000000000.. _formatting: .. currentmodule:: pint .. ipython:: python :suppress: import pint String formatting ================= The conversion of :py:class:`Unit` and :py:class:`Quantity` 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][unit format] where each part is optional and the order of these is arbitrary. In case the format is omitted, the corresponding value in the object's ``.default_format`` attribute (:py:attr:`Quantity.default_format` or :py:attr:`Unit.default_format`) is filled in. For example: .. ipython:: In [1]: ureg = pint.UnitRegistry() ...: ureg.default_format = "~P" In [2]: u = ureg.Unit("m ** 2 / s ** 2") ...: f"{u}" In [3]: u.default_format = "~C" ...: f"{u}" In [4]: u.default_format, ureg.default_format In [5]: q = ureg.Quantity(1.25, "m ** 2 / s ** 2") ...: f"{q}" In [6]: q.default_format = ".3fP" ...: f"{q}" In [7]: q.default_format, ureg.default_format .. note:: In the future, the magnitude and unit format spec will be evaluated independently, such that with a global default of ``ureg.default_format = ".3f"`` and ``f"{q:P}`` the format that will be used is ``".3fP"``. This behavior can be opted into by setting :py:attr:`UnitRegistry.separate_format_defaults` to :py:obj:`True`. If both are not set, the global default of ``"D"`` and the magnitude's default format are used instead. .. note:: Modifiers may be used without specifying any format: ``"~"`` is a valid format specification and is equal to ``"~D"``. Unit Format Specifications -------------------------- The :py:class:`Unit` class ignores the magnitude format part, and the unit format consists of just the format type. Let's look at some examples: .. ipython:: python ureg = pint.UnitRegistry() u = ureg.kg * ureg.m / ureg.s ** 2 f"{u:P}" # using the pretty format f"{u:~P}" # short pretty f"{u:P~}" # also short pretty # default format u.default_format ureg.default_format str(u) # default: default f"{u:~}" # default: short default ureg.default_format = "C" # registry default to compact str(u) # default: compact f"{u}" # default: compact u.default_format = "P" f"{u}" # default: pretty u.default_format = "" # TODO: switch to None ureg.default_format = "" # TODO: switch to None f"{u}" # default: default Unit Format Types ----------------- ``pint`` comes with a variety of unit formats: ======= =============== ====================================================================== Spec Name Example ======= =============== ====================================================================== ``D`` default ``kilogram * meter / second ** 2`` ``P`` pretty ``kilogram·meter/second²`` ``H`` HTML ``kilogram meter/second2`` ``L`` latex ``\frac{\mathrm{kilogram} \cdot \mathrm{meter}}{\mathrm{second}^{2}}`` ``Lx`` latex siunitx ``\si[]{\kilo\gram\meter\per\second\squared}`` ``C`` compact ``kilogram*meter/second**2`` ======= =============== ====================================================================== Custom Unit Format Types ------------------------ Using :py:func:`pint.register_unit_format`, it is possible to add custom formats: .. ipython:: In [1]: u = ureg.Unit("m ** 3 / (s ** 2 * kg)") In [2]: @pint.register_unit_format("simple") ...: def format_unit_simple(unit, registry, **options): ...: return " * ".join(f"{u} ** {p}" for u, p in unit.items()) In [3]: f"{u:~simple}" where ``unit`` is a :py:class:`dict` subclass containing the unit names and their exponents. Quantity Format Specifications ------------------------------ The magnitude format is forwarded to the magnitude (for a unit-spec of ``H`` the magnitude's ``_repr_html_`` is called). Let's look at some more examples: .. ipython:: python q = 1e-6 * u # modifiers f"{q:~P}" # short pretty f"{q:~#P}" # compact short pretty f"{q:P#~}" # also compact short pretty # additional magnitude format f"{q:.2f~#P}" # short compact pretty with 2 float digits f"{q:#~}" # short compact default Quantity Format Types --------------------- There are no special quantity formats yet. Modifiers --------- ======== =================================================== ================================ Modifier Meaning Example ======== =================================================== ================================ ``~`` Use the unit's symbol instead of its canonical name ``kg·m/s²`` (``f"{u:~P}"``) ``#`` Call :py:meth:`Quantity.to_compact` first ``1.0 m·mg/s²`` (``f"{q:#~P}"``) ======== =================================================== ================================ pint-0.19.2/docs/getting.rst000066400000000000000000000031221422760043000156740ustar00rootroot00000000000000.. _getting: Installation ============ Pint has no dependencies except Python_ itself. In runs on Python 3.8+. You can install it (or upgrade to the latest version) using pip_:: $ pip install -U 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 Or running the test suite: .. code-block:: python >>> pint.test() .. note:: If you have an old system installation of Python and you don't want to mess with it, you can try `Anaconda CE`_. It is a free Python distribution by Continuum Analytics that includes many scientific packages. To install pint from the conda-forge channel instead of through pip use:: $ conda install -c conda-forge pint Getting the code ---------------- You can also get the code from PyPI_ or GitHub_. You can either clone the public repository:: $ git clone git://github.com/hgrecco/pint.git Download the tarball:: $ curl -OL https://github.com/hgrecco/pint/tarball/master Or, download the zipball:: $ curl -OL https://github.com/hgrecco/pint/zipball/master Once you have a copy of the source, you can embed it in your Python package, or install it into your site-packages easily:: $ python setup.py install .. _easy_install: http://pypi.python.org/pypi/setuptools .. _Python: http://www.python.org/ .. _pip: http://www.pip-installer.org/ .. _`Anaconda CE`: https://store.continuum.io/cshop/anaconda .. _PyPI: https://pypi.python.org/pypi/Pint/ .. _GitHub: https://github.com/hgrecco/pint pint-0.19.2/docs/index.rst000066400000000000000000000143641422760043000153540ustar00rootroot00000000000000:orphan: Pint: makes units easy ====================== .. image:: _static/logo-full.jpg :alt: Pint: **physical quantities** :class: floatingflask 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.8+ 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. 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! (See :ref:`Installation ` for more detail.) 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. User Guide ---------- .. toctree:: :maxdepth: 1 getting tutorial defining-quantities formatting numpy nonmult log_units wrapping plotting serialization pitheorem contexts measurement defining performance systems currencies pint-convert More information ---------------- .. toctree:: :maxdepth: 1 developers_reference contributing faq One last thing -------------- .. epigraph:: 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 `_ .. _`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.19.2/docs/log_units.rst000066400000000000000000000074641422760043000162530ustar00rootroot00000000000000.. _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) False But this will: .. doctest:: >>> ureg('-161.0 dBm/Hz') == (-161.0 * ureg.dBm / ureg.Hz) True >>> Q_(-161.0, 'dBm') / ureg.Hz == (-161.0 * ureg.dBm / ureg.Hz) 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.19.2/docs/make.bat000066400000000000000000000117441422760043000151170ustar00rootroot00000000000000@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.19.2/docs/measurement.rst000066400000000000000000000040631422760043000165650ustar00rootroot00000000000000.. _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.19.2/docs/nonmult.rst000066400000000000000000000137151422760043000157400ustar00rootroot00000000000000.. _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.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.19.2/docs/numpy.ipynb000066400000000000000000000427171422760043000157310ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "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": {}, "outputs": [], "source": [ "# Import NumPy\n", "import numpy as np\n", "\n", "# Import Pint\n", "import pint\n", "ureg = pint.UnitRegistry()\n", "Q_ = ureg.Quantity\n", "\n", "# Silence NEP 18 warning\n", "import warnings\n", "with warnings.catch_warnings():\n", " warnings.simplefilter(\"ignore\")\n", " Q_([])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and then we create a quantity the standard way" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "legs1 = Q_(np.asarray([3., 4.]), 'meter')\n", "print(legs1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "legs1 = [3., 4.] * ureg.meter\n", "print(legs1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All usual Pint methods can be used with this quantity. For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(legs1.to('kilometer'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(legs1.dimensionality)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "try:\n", " legs1.to('joule')\n", "except pint.DimensionalityError as exc:\n", " print(exc)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "NumPy functions are supported by Pint. For example if we define:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "legs2 = [400., 300.] * ureg.centimeter\n", "print(legs2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "we can calculate the hypotenuse of the right triangles with legs1 and legs2." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hyps = np.hypot(legs1, legs2)\n", "print(hyps)" ] }, { "cell_type": "markdown", "metadata": {}, "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": {}, "outputs": [], "source": [ "angles = np.arccos(legs2/hyps)\n", "print(angles)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can convert the result to degrees using usual unit conversion:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(angles.to('degree'))" ] }, { "cell_type": "markdown", "metadata": {}, "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": {}, "outputs": [], "source": [ "try:\n", " np.arccos(legs2)\n", "except pint.DimensionalityError as exc:\n", " print(exc)" ] }, { "cell_type": "markdown", "metadata": {}, "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": {}, "outputs": [], "source": [ "from pint.numpy_func import HANDLED_FUNCTIONS\n", "print(sorted(list(HANDLED_FUNCTIONS)))" ] }, { "cell_type": "markdown", "metadata": {}, "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": {}, "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": {}, "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": {}, "source": [ "### Examples\n", "\n", "**xarray wrapping Pint Quantity**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "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": {}, "source": [ "**Pint Quantity wrapping Sparse COO**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "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": {}, "source": [ "**Pint Quantity wrapping NumPy Masked Array**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "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": {}, "source": [ "**Pint Quantity wrapping Dask Array**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "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": {}, "source": [ "**xarray wrapping Pint Quantity wrapping Dask array wrapping Sparse COO**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "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": {}, "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": {}, "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": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.2" } }, "nbformat": 4, "nbformat_minor": 4 } pint-0.19.2/docs/performance.rst000066400000000000000000000127061422760043000165440ustar00rootroot00000000000000.. _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 your'e still using pint to retrieve them from a quantity object). .. doctest:: 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) 100000 loops, best of 3: 7.9 µs per loop In [7]: %timeit (q1.magnitude-q2.magnitude) 1000000 loops, best of 3: 356 ns per loop This is especially important when using pint Quantities in conjunction with an iterative solver, such as the `brentq method`_ from scipy: .. doctest:: 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) 1000 loops, best of 3: 310 µs per loop In [5]: %timeit brentq(foobar_with_magnitude,0,q2.magnitude) 1000000 loops, best of 3: 1.63 µs per loop 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. .. doctest:: 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 included version of appdirs_ 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 .. _appdirs: https://pypi.org/project/appdirs/ .. _flexcache: https://github.com/hgrecco/flexcache/ pint-0.19.2/docs/pint-convert.rst000066400000000000000000000047721422760043000166770ustar00rootroot00000000000000.. _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:: $ pint-convert 225lb 225 pound = 102.05828325 kg use the `--sys` argument to change it:: $ pint-convert --sys US 102kg 102 kilogram = 224.871507429 lb or specify directly the target units:: $ pint-convert 102kg lb 102 kilogram = 224.871507429 lb The input quantity can contain expressions:: $ pint-convert 7ft+2in 7.166666666666667 foot = 2.1844 m in some cases parentheses and quotes may be needed:: $ pint-convert "225lb/(7ft+2in)" 31.3953488372093 pound / foot = 46.7214261353 kg/m If a number is omitted, 1 is assumed:: $ 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:: $ 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:: $ 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:: $ pint-convert 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 -p 20 Eh eV 1 hartree = 27.211386245988(52) eV $ pint-convert -p 20 -u 4 Eh eV 1 hartree = 27.21138624598847(5207) eV The uncertainty can be disabled with `-U`):: $ pint-convert -p 20 -U Eh eV 1 hartree = 27.211386245988471444 eV Correlations between experimental constants are also known, and taken into account. Use `-C` to disable it:: $ pint-convert --sys atomic m_p 1 proton_mass = 1836.15267344(11) m_e $ pint-convert --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.19.2/docs/pitheorem.rst000066400000000000000000000071111422760043000162310ustar00rootroot00000000000000.. _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]'}) >>> print(formatter(result[0].items())) T * V / 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.19.2/docs/plotting.rst000066400000000000000000000034021422760043000160740ustar00rootroot00000000000000.. _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') For more information, visit the Matplotlib_ home page. .. _Matplotlib: https://matplotlib.org pint-0.19.2/docs/serialization.rst000066400000000000000000000104541422760043000171160ustar00rootroot00000000000000.. _serialization: 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.19.2/docs/systems.rst000066400000000000000000000034441422760043000157510ustar00rootroot00000000000000.. _systems: Different Unit Systems (and default units) ========================================== 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.19.2/docs/tutorial.rst000066400000000000000000000337001422760043000161030ustar00rootroot00000000000000 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 :doc:`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. 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['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['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['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['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['meter/second**2'] >>> 'The acceleration is {}'.format(accel) 'The acceleration is 1.3 meter / second ** 2' >>> ureg.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:: >>> accel.format_babel(locale='fr_FR') '1.3 mètre 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.set_fmt_locale('fr_FR') and by doing that, string formatting is now localized: .. doctest:: >>> accel = 1.3 * ureg['meter/second**2'] >>> str(accel) '1.3 mètre par seconde²' >>> "%s" % accel '1.3 mètre par seconde²' >>> "{}".format(accel) '1.3 mètre par seconde²' Using Pint in your projects --------------------------- 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`` .. code-block:: python 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 .. _`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.19.2/docs/wrapping.rst000066400000000000000000000171651422760043000160760ustar00rootroot00000000000000.. _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 .. doctest:: @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.19.2/pint/000077500000000000000000000000001422760043000135255ustar00rootroot00000000000000pint-0.19.2/pint/__init__.py000066400000000000000000000073501422760043000156430ustar00rootroot00000000000000""" 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 importlib.metadata import version from .context import Context from .errors import ( # noqa: F401 DefinitionSyntaxError, DimensionalityError, LogarithmicUnitCalculusError, OffsetUnitCalculusError, PintError, RedefinitionError, UndefinedUnitError, UnitStrippedWarning, ) from .formatting import formatter, register_unit_format from .measurement import Measurement from .quantity import Quantity from .registry import ApplicationRegistry, LazyRegistry, UnitRegistry from .unit import Unit from .util import logger, pi_theorem # noqa: F401 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 .unit 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__ = ( "Context", "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__", ) pint-0.19.2/pint/_typing.py000066400000000000000000000006661422760043000155600ustar00rootroot00000000000000from typing import TYPE_CHECKING, Any, Callable, Tuple, TypeVar, Union if TYPE_CHECKING: from .quantity import Quantity from .unit import Unit from .util import UnitsContainer UnitLike = Union[str, "UnitsContainer", "Unit"] QuantityOrUnitLike = Union["Quantity", UnitLike] Shape = Tuple[int, ...] _MagnitudeType = TypeVar("_MagnitudeType") S = TypeVar("S") FuncType = Callable[..., Any] F = TypeVar("F", bound=FuncType) pint-0.19.2/pint/_vendor/000077500000000000000000000000001422760043000151615ustar00rootroot00000000000000pint-0.19.2/pint/_vendor/appdirs.py000066400000000000000000000602201422760043000171750ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c) 2005-2010 ActiveState Software Inc. # Copyright (c) 2013 Eddy Petrișor """Utilities for determining application-specific dirs. See for details and usage. """ # Dev Notes: # - MSDN on where to store app data files: # http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html __version__ = "1.4.4" __version_info__ = tuple(int(segment) for segment in __version__.split(".")) import sys import os PY3 = sys.version_info[0] == 3 if PY3: unicode = str if sys.platform.startswith('java'): import platform os_name = platform.java_ver()[3][0] if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. system = 'win32' elif os_name.startswith('Mac'): # "Mac OS X", etc. system = 'darwin' else: # "Linux", "SunOS", "FreeBSD", etc. # Setting this to "linux2" is not ideal, but only Windows or Mac # are actually checked for and the rest of the module expects # *sys.platform* style strings. system = 'linux2' else: system = sys.platform def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): r"""Return full path to the user-specific data dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. You may pass False to disable it. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "roaming" (boolean, default False) can be set True to use the Windows roaming appdata directory. That means that for users on a Windows network setup for roaming profiles, this user data will be sync'd on login. See for a discussion of issues. Typical user data directories are: Mac OS X: ~/Library/Application Support/ Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined Win XP (not roaming): C:\Documents and Settings\\Application Data\\ Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ Win 7 (not roaming): C:\Users\\AppData\Local\\ Win 7 (roaming): C:\Users\\AppData\Roaming\\ For Unix, we follow the XDG spec and support $XDG_DATA_HOME. That means, by default "~/.local/share/". """ if system == "win32": if appauthor is None: appauthor = appname const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" path = os.path.normpath(_get_win_folder(const)) if appname: if appauthor is not False: path = os.path.join(path, appauthor, appname) else: path = os.path.join(path, appname) elif system == 'darwin': path = os.path.expanduser('~/Library/Application Support/') if appname: path = os.path.join(path, appname) else: path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) if appname: path = os.path.join(path, appname) if appname and version: path = os.path.join(path, version) return path def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): r"""Return full path to the user-shared data dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. You may pass False to disable it. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "multipath" is an optional parameter only applicable to *nix which indicates that the entire list of data dirs should be returned. By default, the first item from XDG_DATA_DIRS is returned, or '/usr/local/share/', if XDG_DATA_DIRS is not set Typical site data directories are: Mac OS X: /Library/Application Support/ Unix: /usr/local/share/ or /usr/share/ Win XP: C:\Documents and Settings\All Users\Application Data\\ Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. For Unix, this is using the $XDG_DATA_DIRS[0] default. WARNING: Do not use this on Windows. See the Vista-Fail note above for why. """ if system == "win32": if appauthor is None: appauthor = appname path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) if appname: if appauthor is not False: path = os.path.join(path, appauthor, appname) else: path = os.path.join(path, appname) elif system == 'darwin': path = os.path.expanduser('/Library/Application Support') if appname: path = os.path.join(path, appname) else: # XDG default for $XDG_DATA_DIRS # only first, if multipath is False path = os.getenv('XDG_DATA_DIRS', os.pathsep.join(['/usr/local/share', '/usr/share'])) pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] if appname: if version: appname = os.path.join(appname, version) pathlist = [os.sep.join([x, appname]) for x in pathlist] if multipath: path = os.pathsep.join(pathlist) else: path = pathlist[0] return path if appname and version: path = os.path.join(path, version) return path def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): r"""Return full path to the user-specific config dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. You may pass False to disable it. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "roaming" (boolean, default False) can be set True to use the Windows roaming appdata directory. That means that for users on a Windows network setup for roaming profiles, this user data will be sync'd on login. See for a discussion of issues. Typical user config directories are: Mac OS X: same as user_data_dir Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined Win *: same as user_data_dir For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. That means, by default "~/.config/". """ if system in ["win32", "darwin"]: path = user_data_dir(appname, appauthor, None, roaming) else: path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) if appname: path = os.path.join(path, appname) if appname and version: path = os.path.join(path, version) return path def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): r"""Return full path to the user-shared data dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. You may pass False to disable it. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "multipath" is an optional parameter only applicable to *nix which indicates that the entire list of config dirs should be returned. By default, the first item from XDG_CONFIG_DIRS is returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set Typical site config directories are: Mac OS X: same as site_data_dir Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in $XDG_CONFIG_DIRS Win *: same as site_data_dir Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False WARNING: Do not use this on Windows. See the Vista-Fail note above for why. """ if system in ["win32", "darwin"]: path = site_data_dir(appname, appauthor) if appname and version: path = os.path.join(path, version) else: # XDG default for $XDG_CONFIG_DIRS # only first, if multipath is False path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] if appname: if version: appname = os.path.join(appname, version) pathlist = [os.sep.join([x, appname]) for x in pathlist] if multipath: path = os.pathsep.join(pathlist) else: path = pathlist[0] return path def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): r"""Return full path to the user-specific cache dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. You may pass False to disable it. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "opinion" (boolean) can be False to disable the appending of "Cache" to the base app data dir for Windows. See discussion below. Typical user cache directories are: Mac OS X: ~/Library/Caches/ Unix: ~/.cache/ (XDG default) Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache Vista: C:\Users\\AppData\Local\\\Cache On Windows the only suggestion in the MSDN docs is that local settings go in the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming app data dir (the default returned by `user_data_dir` above). Apps typically put cache data somewhere *under* the given dir here. Some examples: ...\Mozilla\Firefox\Profiles\\Cache ...\Acme\SuperApp\Cache\1.0 OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. This can be disabled with the `opinion=False` option. """ if system == "win32": if appauthor is None: appauthor = appname path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) if appname: if appauthor is not False: path = os.path.join(path, appauthor, appname) else: path = os.path.join(path, appname) if opinion: path = os.path.join(path, "Cache") elif system == 'darwin': path = os.path.expanduser('~/Library/Caches') if appname: path = os.path.join(path, appname) else: path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) if appname: path = os.path.join(path, appname) if appname and version: path = os.path.join(path, version) return path def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): r"""Return full path to the user-specific state dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. You may pass False to disable it. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "roaming" (boolean, default False) can be set True to use the Windows roaming appdata directory. That means that for users on a Windows network setup for roaming profiles, this user data will be sync'd on login. See for a discussion of issues. Typical user state directories are: Mac OS X: same as user_data_dir Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined Win *: same as user_data_dir For Unix, we follow this Debian proposal to extend the XDG spec and support $XDG_STATE_HOME. That means, by default "~/.local/state/". """ if system in ["win32", "darwin"]: path = user_data_dir(appname, appauthor, None, roaming) else: path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) if appname: path = os.path.join(path, appname) if appname and version: path = os.path.join(path, version) return path def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): r"""Return full path to the user-specific log dir for this application. "appname" is the name of application. If None, just the system directory is returned. "appauthor" (only used on Windows) is the name of the appauthor or distributing body for this application. Typically it is the owning company name. This falls back to appname. You may pass False to disable it. "version" is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be ".". Only applied when appname is present. "opinion" (boolean) can be False to disable the appending of "Logs" to the base app data dir for Windows, and "log" to the base cache dir for Unix. See discussion below. Typical user log directories are: Mac OS X: ~/Library/Logs/ Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs Vista: C:\Users\\AppData\Local\\\Logs On Windows the only suggestion in the MSDN docs is that local settings go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in examples of what some windows apps use for a logs dir.) OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` value for Windows and appends "log" to the user cache dir for Unix. This can be disabled with the `opinion=False` option. """ if system == "darwin": path = os.path.join( os.path.expanduser('~/Library/Logs'), appname) elif system == "win32": path = user_data_dir(appname, appauthor, version) version = False if opinion: path = os.path.join(path, "Logs") else: path = user_cache_dir(appname, appauthor, version) version = False if opinion: path = os.path.join(path, "log") if appname and version: path = os.path.join(path, version) return path class AppDirs(object): """Convenience wrapper for getting application dirs.""" def __init__(self, appname=None, appauthor=None, version=None, roaming=False, multipath=False): self.appname = appname self.appauthor = appauthor self.version = version self.roaming = roaming self.multipath = multipath @property def user_data_dir(self): return user_data_dir(self.appname, self.appauthor, version=self.version, roaming=self.roaming) @property def site_data_dir(self): return site_data_dir(self.appname, self.appauthor, version=self.version, multipath=self.multipath) @property def user_config_dir(self): return user_config_dir(self.appname, self.appauthor, version=self.version, roaming=self.roaming) @property def site_config_dir(self): return site_config_dir(self.appname, self.appauthor, version=self.version, multipath=self.multipath) @property def user_cache_dir(self): return user_cache_dir(self.appname, self.appauthor, version=self.version) @property def user_state_dir(self): return user_state_dir(self.appname, self.appauthor, version=self.version) @property def user_log_dir(self): return user_log_dir(self.appname, self.appauthor, version=self.version) #---- internal support stuff def _get_win_folder_from_registry(csidl_name): """This is a fallback technique at best. I'm not sure if using the registry for this guarantees us the correct answer for all CSIDL_* names. """ if PY3: import winreg as _winreg else: import _winreg shell_folder_name = { "CSIDL_APPDATA": "AppData", "CSIDL_COMMON_APPDATA": "Common AppData", "CSIDL_LOCAL_APPDATA": "Local AppData", }[csidl_name] key = _winreg.OpenKey( _winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" ) dir, type = _winreg.QueryValueEx(key, shell_folder_name) return dir def _get_win_folder_with_pywin32(csidl_name): from win32com.shell import shellcon, shell dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) # Try to make this a unicode path because SHGetFolderPath does # not return unicode strings when there is unicode data in the # path. try: dir = unicode(dir) # Downgrade to short path name if have highbit chars. See # . has_high_char = False for c in dir: if ord(c) > 255: has_high_char = True break if has_high_char: try: import win32api dir = win32api.GetShortPathName(dir) except ImportError: pass except UnicodeError: pass return dir def _get_win_folder_with_ctypes(csidl_name): import ctypes csidl_const = { "CSIDL_APPDATA": 26, "CSIDL_COMMON_APPDATA": 35, "CSIDL_LOCAL_APPDATA": 28, }[csidl_name] buf = ctypes.create_unicode_buffer(1024) ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) # Downgrade to short path name if have highbit chars. See # . has_high_char = False for c in buf: if ord(c) > 255: has_high_char = True break if has_high_char: buf2 = ctypes.create_unicode_buffer(1024) if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): buf = buf2 return buf.value def _get_win_folder_with_jna(csidl_name): import array from com.sun import jna from com.sun.jna.platform import win32 buf_size = win32.WinDef.MAX_PATH * 2 buf = array.zeros('c', buf_size) shell = win32.Shell32.INSTANCE shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) dir = jna.Native.toString(buf.tostring()).rstrip("\0") # Downgrade to short path name if have highbit chars. See # . has_high_char = False for c in dir: if ord(c) > 255: has_high_char = True break if has_high_char: buf = array.zeros('c', buf_size) kernel = win32.Kernel32.INSTANCE if kernel.GetShortPathName(dir, buf, buf_size): dir = jna.Native.toString(buf.tostring()).rstrip("\0") return dir if system == "win32": try: import win32com.shell _get_win_folder = _get_win_folder_with_pywin32 except ImportError: try: from ctypes import windll _get_win_folder = _get_win_folder_with_ctypes except ImportError: try: import com.sun.jna _get_win_folder = _get_win_folder_with_jna except ImportError: _get_win_folder = _get_win_folder_from_registry #---- self test code if __name__ == "__main__": appname = "MyApp" appauthor = "MyCompany" props = ("user_data_dir", "user_config_dir", "user_cache_dir", "user_state_dir", "user_log_dir", "site_data_dir", "site_config_dir") print("-- app dirs %s --" % __version__) print("-- app dirs (with optional 'version')") dirs = AppDirs(appname, appauthor, version="1.0") for prop in props: print("%s: %s" % (prop, getattr(dirs, prop))) print("\n-- app dirs (without optional 'version')") dirs = AppDirs(appname, appauthor) for prop in props: print("%s: %s" % (prop, getattr(dirs, prop))) print("\n-- app dirs (without optional 'appauthor')") dirs = AppDirs(appname) for prop in props: print("%s: %s" % (prop, getattr(dirs, prop))) print("\n-- app dirs (with disabled 'appauthor')") dirs = AppDirs(appname, appauthor=False) for prop in props: print("%s: %s" % (prop, getattr(dirs, prop))) pint-0.19.2/pint/_vendor/flexcache.py000066400000000000000000000315161422760043000174630ustar00rootroot00000000000000""" flexcache.flexcache ~~~~~~~~~~~~~~~~~~~ Classes for persistent caching and invalidating cached objects, which are built from a source object and a (potentially expensive) conversion function. Header ------ Contains summary information about the source object that will be saved together with the cached file. It's capabilities are divided in three groups: - The Header itself which contains the information that will be saved alongside the cached file - The Naming logic which indicates how the cached filename is built. - The Invalidation logic which indicates whether a cached file is valid (i.e. truthful to the actual source file). DiskCache --------- Saves and loads to the cache a transformed versions of a source object. :copyright: 2022 by flexcache Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import abc import hashlib import json import pathlib import pickle import platform import typing from dataclasses import asdict as dc_asdict from dataclasses import dataclass from dataclasses import fields as dc_fields from typing import Any, Iterable ######### # Header ######### @dataclass(frozen=True) class BaseHeader(abc.ABC): """Header with no information except the converter_id All header files must inherit from this. """ # The actual source of the data (or a reference to it) # that is going to be converted. source: Any # An identification of the function that is used to # convert the source into the result object. converter_id: str _source_type = object def __post_init__(self): # TODO: In more modern python versions it would be # good to check for things like tuple[str]. if not isinstance(self.source, self._source_type): raise TypeError( f"Source must be {self._source_type}, " f"not {type(self.source)}" ) def for_cache_name(self) -> typing.Generator[bytes]: """The basename for the cache file is a hash hexdigest built by feeding this collection of values. A class can provide it's own set of values by rewriting `_for_cache_name`. """ for el in self._for_cache_name(): if isinstance(el, str): yield el.encode("utf-8") else: yield el def _for_cache_name(self) -> typing.Generator[bytes | str]: """The basename for the cache file is a hash hexdigest built by feeding this collection of values. Change the behavior by writing your own. """ yield self.converter_id @abc.abstractmethod def is_valid(self, cache_path: pathlib.Path) -> bool: """Return True if the cache_path is an cached version of the source_object represented by this header. """ @dataclass(frozen=True) class BasicPythonHeader(BaseHeader): """Header with basic Python information.""" system: str = platform.system() python_implementation: str = platform.python_implementation() python_version: str = platform.python_version() ##################### # Invalidation logic ##################### class InvalidateByExist: """The cached file is valid if exists and is newer than the source file.""" def is_valid(self, cache_path: pathlib.Path) -> bool: return cache_path.exists() class InvalidateByPathMTime(abc.ABC): """The cached file is valid if exists and is newer than the source file.""" @property @abc.abstractmethod def source_path(self) -> pathlib.Path: ... def is_valid(self, cache_path: pathlib.Path): return ( cache_path.exists() and cache_path.stat().st_mtime > self.source_path.stat().st_mtime ) class InvalidateByMultiPathsMtime(abc.ABC): """The cached file is valid if exists and is newer than the newest source file.""" @property @abc.abstractmethod def source_paths(self) -> pathlib.Path: ... @property def newest_date(self): return max((t.stat().st_mtime for t in self.source_paths), default=0) def is_valid(self, cache_path: pathlib.Path): return cache_path.exists() and cache_path.stat().st_mtime > self.newest_date ############### # Naming logic ############### class NameByFields: """Name is built taking into account all fields in the Header (except the source itself). """ def _for_cache_name(self): yield from super()._for_cache_name() for field in dc_fields(self): if field.name not in ("source", "converter_id"): yield getattr(self, field.name) class NameByFileContent: """Given a file source object, the name is built from its content.""" _source_type = pathlib.Path @property def source_path(self) -> pathlib.Path: return self.source def _for_cache_name(self): yield from super()._for_cache_name() yield self.source_path.read_bytes() @classmethod def from_string(cls, s: str, converter_id: str): return cls(pathlib.Path(s), converter_id) @dataclass(frozen=True) class NameByObj: """Given a pickable source object, the name is built from its content.""" pickle_protocol: int = pickle.HIGHEST_PROTOCOL def _for_cache_name(self): yield from super()._for_cache_name() yield pickle.dumps(self.source, protocol=self.pickle_protocol) class NameByPath: """Given a file source object, the name is built from its resolved path.""" _source_type = pathlib.Path @property def source_path(self) -> pathlib.Path: return self.source def _for_cache_name(self): yield from super()._for_cache_name() yield bytes(self.source_path.resolve()) @classmethod def from_string(cls, s: str, converter_id: str): return cls(pathlib.Path(s), converter_id) class NameByMultiPaths: """Given multiple file source object, the name is built from their resolved path in ascending order. """ _source_type = tuple @property def source_paths(self) -> tuple[pathlib.Path]: return self.source def _for_cache_name(self): yield from super()._for_cache_name() yield from sorted(bytes(p.resolve()) for p in self.source_paths) @classmethod def from_strings(cls, ss: Iterable[str], converter_id: str): return cls(tuple(pathlib.Path(s) for s in ss), converter_id) class NameByHashIter: """Given multiple hashes, the name is built from them in ascending order.""" _source_type = tuple def _for_cache_name(self): yield from super()._for_cache_name() yield from sorted(h for h in self.source) class DiskCache: """A class to store and load cached objects to disk, which are built from a source object and conversion function. The basename for the cache file is a hash hexdigest built by feeding a collection of values determined by the Header object. Parameters ---------- cache_folder indicates where the cache files will be saved. """ # Maps classes to header class _header_classes: dict[type, BaseHeader] = None # Hasher object constructor (e.g. a member of hashlib) # must implement update(b: bytes) and hexdigest() methods _hasher = hashlib.sha1 # If True, for each cached file the header is also stored. _store_header: bool = True def __init__(self, cache_folder: str | pathlib.Path): self.cache_folder = pathlib.Path(cache_folder) self.cache_folder.mkdir(parents=True, exist_ok=True) self._header_classes = self._header_classes or {} def register_header_class(self, object_class: type, header_class: BaseHeader): self._header_classes[object_class] = header_class def cache_stem_for(self, header: BaseHeader) -> str: """Generate a hash representing the basename of a memoized file for a given header. The naming strategy is defined by the header class used. """ hd = self._hasher() for value in header.for_cache_name(): hd.update(value) return hd.hexdigest() def cache_path_for(self, header: BaseHeader) -> pathlib.Path: """Generate a Path representing the location of a memoized file for a given filepath or object. The naming strategy is defined by the header class used. """ h = self.cache_stem_for(header) return self.cache_folder.joinpath(h).with_suffix(".pickle") def _get_header_class(self, source_object) -> BaseHeader: for k, v in self._header_classes.items(): if isinstance(source_object, k): return v raise TypeError(f"Cannot find header class for {type(source_object)}") def load(self, source_object, converter=None, pass_hash=False) -> tuple[Any, str]: """Given a source_object, return the converted value stored in the cache together with the cached path stem When the cache is not found: - If a converter callable is given, use it on the source object, store the result in the cache and return it. - Return None, otherwise. Two signatures for the converter are valid: - source_object -> transformed object - (source_object, cached_path_stem) -> transformed_object To use the second one, use `pass_hash=True`. If you want to do the conversion yourself outside this class, use the converter argument to provide a name for it. This is important as the cached_path_stem depends on the converter name. """ header_class = self._get_header_class(source_object) if isinstance(converter, str): converter_id = converter converter = None else: converter_id = getattr(converter, "__name__", "") header = header_class(source_object, converter_id) cache_path = self.cache_path_for(header) converted_object = self.rawload(header, cache_path) if converted_object: return converted_object, cache_path.stem if converter is None: return None, cache_path.stem if pass_hash: converted_object = converter(source_object, cache_path.stem) else: converted_object = converter(source_object) self.rawsave(header, converted_object, cache_path) return converted_object, cache_path.stem def save(self, converted_object, source_object, converter_id="") -> str: """Given a converted_object and its corresponding source_object, store it in the cache and return the cached_path_stem. """ header_class = self._get_header_class(source_object) header = header_class(source_object, converter_id) return self.rawsave(header, converted_object, self.cache_path_for(header)).stem def rawload( self, header: BaseHeader, cache_path: pathlib.Path = None ) -> Any | None: """Load the converted_object from the cache if it is valid. The invalidating strategy is defined by the header class used. The cache_path is optional, it will be calculated from the header if not given. """ if cache_path is None: cache_path = self.cache_path_for(header) if header.is_valid(cache_path): with cache_path.open(mode="rb") as fi: return pickle.load(fi) def rawsave( self, header: BaseHeader, converted, cache_path: pathlib.Path = None ) -> pathlib.Path: """Save the converted object (in pickle format) and its header (in json format) to the cache folder. The cache_path is optional, it will be calculated from the header if not given. """ if cache_path is None: cache_path = self.cache_path_for(header) if self._store_header: with cache_path.with_suffix(".json").open("w", encoding="utf-8") as fo: json.dump({k: str(v) for k, v in dc_asdict(header).items()}, fo) with cache_path.open(mode="wb") as fo: pickle.dump(converted, fo) return cache_path class DiskCacheByHash(DiskCache): """Convenience class used for caching conversions that take a path, naming by hashing its content. """ @dataclass(frozen=True) class Header(NameByFileContent, InvalidateByExist, BaseHeader): pass _header_classes = { pathlib.Path: Header, str: Header.from_string, } class DiskCacheByMTime(DiskCache): """Convenience class used for caching conversions that take a path, naming by hashing its full path and invalidating by the file modification time. """ @dataclass(frozen=True) class Header(NameByPath, InvalidateByPathMTime, BaseHeader): pass _header_classes = { pathlib.Path: Header, str: Header.from_string, } pint-0.19.2/pint/babel_names.py000066400000000000000000000107361422760043000163360ustar00rootroot00000000000000""" pint.babel ~~~~~~~~~~ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from .compat import HAS_BABEL _babel_units = 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(mks="metric", imperial="uksystem", US="ussystem") _babel_lengths = ["narrow", "short", "long"] pint-0.19.2/pint/compat.py000066400000000000000000000162611422760043000153700ustar00rootroot00000000000000""" pint.compat ~~~~~~~~~~~ Compatibility layer. :copyright: 2013 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import math import tokenize from decimal import Decimal from io import BytesIO from numbers import Number def missing_dependency(package, display_name=None): display_name = display_name or package def _inner(*args, **kwargs): raise Exception( "This feature requires %s. Please install it by running:\n" "pip install %s" % (display_name, package) ) return _inner def tokenizer(input_string): for tokinfo in tokenize.tokenize(BytesIO(input_string.encode("utf-8")).readline): if tokinfo.type != tokenize.ENCODING: yield tokinfo # TODO: remove this warning after v0.10 class BehaviorChangeWarning(UserWarning): pass try: import numpy as np from numpy import datetime64 as np_datetime64 from numpy import ndarray HAS_NUMPY = True NUMPY_VER = np.__version__ 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("Invalid magnitude for Quantity: {0!r}".format(value)) elif isinstance(value, str) and value == "": raise ValueError("Quantity magnitude cannot be an empty string.") elif isinstance(value, (list, tuple)): return np.asarray(value) 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("Invalid magnitude for Quantity: {0!r}".format(value)) 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." ) return value try: from uncertainties import ufloat HAS_UNCERTAINTIES = True except ImportError: ufloat = None HAS_UNCERTAINTIES = False try: from babel import Locale as Loc from babel import units as babel_units babel_parse = Loc.parse HAS_BABEL = hasattr(babel_units, "format_unit") except ImportError: HAS_BABEL = False # Defines Logarithm and Exponential for Logarithmic Converter if HAS_NUMPY: from numpy import exp # noqa: F401 from numpy import log # noqa: F401 else: from math import exp # noqa: F401 from math import log # noqa: F401 if not HAS_BABEL: babel_parse = babel_units = missing_dependency("Babel") # noqa: F811 # Define location of pint.Quantity in NEP-13 type cast hierarchy by defining upcast # types using guarded imports upcast_types = [] # pint-pandas (PintArray) try: from pint_pandas import PintArray upcast_types.append(PintArray) except ImportError: pass # Pandas (Series) try: from pandas import Series upcast_types.append(Series) except ImportError: pass # xarray (DataArray, Dataset, Variable) try: from xarray import DataArray, Dataset, Variable upcast_types += [DataArray, Dataset, Variable] except ImportError: pass 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 def is_upcast_type(other) -> bool: """Check if the type object is a upcast type using preset list. Parameters ---------- other : object Returns ------- bool """ return other in upcast_types def is_duck_array_type(cls) -> bool: """Check if the type object represents a (non-Quantity) duck array type. Parameters ---------- cls : class Returns ------- bool """ # 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): return is_duck_array_type(type(obj)) def eq(lhs, rhs, check_all: bool): """Comparison of scalars and arrays. Parameters ---------- lhs : object left-hand side rhs : object right-hand side check_all : bool 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, check_all: bool): """Test for NaN or NaT Parameters ---------- obj : object scalar or vector check_all : bool 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 "if": out = np.isnan(obj) elif obj.dtype.kind in "Mm": out = np.isnat(obj) 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) try: return math.isnan(obj) except TypeError: return False def zero_or_nan(obj, check_all: bool): """Test if obj is zero, NaN, or NaT Parameters ---------- obj : object scalar or vector check_all : bool 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.19.2/pint/constants_en.txt000066400000000000000000000105451422760043000167710ustar00rootroot00000000000000# 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.19.2/pint/context.py000066400000000000000000000364271422760043000155770ustar00rootroot00000000000000""" 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 import numbers import re import weakref from collections import ChainMap, defaultdict from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple from .definitions import Definition, UnitDefinition from .errors import DefinitionSyntaxError, RedefinitionError from .util import ParserHelper, SourceIterator, to_units_container if TYPE_CHECKING: from .quantity import Quantity from .registry import UnitRegistry from .util import UnitsContainer #: Regex to match the header parts of a context. _header_re = re.compile( r"@context\s*(?P\(.*\))?\s+(?P\w+)\s*(=(?P.*))*" ) #: Regex to match variable names in an equation. _varname_re = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") class Expression: def __init__(self, eq): self._eq = eq def __call__(self, ureg: UnitRegistry, value: Any, **kwargs: Any): return ureg.parse_expression(self._eq, value=value, **kwargs) @dataclass(frozen=True) class Relation: bidirectional: True src: ParserHelper dst: ParserHelper tranformation: Callable[..., Quantity[Any]] @dataclass(frozen=True) class ContextDefinition: """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 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 """ name: str aliases: Tuple[str, ...] variables: Tuple[str, ...] defaults: Dict[str, numbers.Number] # Each element indicates: line number, is_bidirectional, src, dst, transformation func relations: Tuple[Tuple[int, Relation], ...] redefinitions: Tuple[Tuple[int, UnitDefinition], ...] @staticmethod def parse_definition(line, non_int_type) -> UnitDefinition: definition = Definition.from_string(line, non_int_type) if not isinstance(definition, UnitDefinition): raise DefinitionSyntaxError( "Expected = ; got %s" % line.strip() ) if definition.symbol != definition.name or definition.aliases: raise DefinitionSyntaxError( "Can't change a unit's symbol or aliases within a context" ) if definition.is_base: raise DefinitionSyntaxError("Can't define base units within a context") return definition @classmethod def from_lines(cls, lines, non_int_type=float) -> ContextDefinition: lines = SourceIterator(lines) lineno, header = next(lines) try: r = _header_re.search(header) 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: raise DefinitionSyntaxError( "Could not parse the Context header '%s'" % header, lineno=lineno ) from exc if defaults: def to_num(val): val = complex(val) if not val.imag: return val.real return val txt = defaults try: defaults = (part.split("=") for part in defaults.strip("()").split(",")) defaults = {str(k).strip(): to_num(v) for k, v in defaults} except (ValueError, TypeError) as exc: raise DefinitionSyntaxError( f"Could not parse Context definition defaults: '{txt}'", lineno=lineno, ) from exc else: defaults = {} variables = set() redefitions = [] relations = [] for lineno, line in lines: try: if "=" in line: definition = cls.parse_definition(line, non_int_type) redefitions.append((lineno, definition)) elif ":" in line: rel, eq = line.split(":") variables.update(_varname_re.findall(eq)) func = Expression(eq) bidir = True parts = rel.split("<->") if len(parts) != 2: bidir = False parts = rel.split("->") if len(parts) != 2: raise Exception src, dst = ( ParserHelper.from_string(s, non_int_type) for s in parts ) relation = Relation(bidir, src, dst, func) relations.append((lineno, relation)) else: raise Exception except Exception as exc: raise DefinitionSyntaxError( "Could not parse Context %s relation '%s': %s" % (name, line, exc), lineno=lineno, ) from exc if defaults: missing_pars = defaults.keys() - set(variables) if missing_pars: raise DefinitionSyntaxError( f"Context parameters {missing_pars} not found in any equation" ) return cls( name, aliases, tuple(variables), defaults, tuple(relations), tuple(redefitions), ) 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: Optional[str] = None, aliases: Tuple[str, ...] = (), defaults: Optional[dict] = None, ) -> None: self.name = name self.aliases = aliases #: Maps (src, dst) -> transformation function self.funcs = {} #: Maps defaults variable names to values self.defaults = defaults or {} # Store Definition objects that are context-specific self.redefinitions = [] # 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() @classmethod def from_context(cls, context: Context, **defaults) -> 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, to_base_func=None, non_int_type=float) -> Context: cd = ContextDefinition.from_lines(lines, non_int_type) return cls.from_definition(cd, to_base_func) @classmethod def from_definition(cls, cd: ContextDefinition, to_base_func=None) -> Context: ctx = cls(cd.name, cd.aliases, cd.defaults) for lineno, definition in cd.redefinitions: try: ctx._redefine(definition) except (RedefinitionError, DefinitionSyntaxError) as ex: if ex.lineno is None: ex.lineno = lineno raise ex for lineno, relation in cd.relations: try: 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.tranformation) if relation.bidirectional: ctx.add_transformation(dst, src, relation.tranformation) except Exception as exc: raise DefinitionSyntaxError( "Could not add Context %s relation on line '%s'" % (cd.name, lineno), lineno=lineno, ) from exc return ctx def add_transformation(self, src, dst, func) -> 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, dst) -> 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, dst) -> Tuple[UnitsContainer, UnitsContainer]: return to_units_container(src), to_units_container(dst) def transform(self, src, dst, registry, value): """Transform a value.""" _key = self.__keytransform__(src, dst) return self.funcs[_key](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`` """ for line in definition.splitlines(): # TODO: What is the right non_int_type value. definition = ContextDefinition.parse_definition(line, float) self._redefine(definition) def _redefine(self, definition: UnitDefinition): self.redefinitions.append(definition) def hashable( self, ) -> Tuple[Optional[str], Tuple[str, ...], frozenset, frozenset, tuple]: """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): """A specialized ChainMap for contexts that simplifies finding rules to transform from one dimension to another. """ def __init__(self): super().__init__() self.contexts = [] self.maps.clear() # Remove default empty map self._graph = None def insert_contexts(self, *contexts): """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): """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): 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 def transform(self, src, dst, registry, value): """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): """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.19.2/pint/converters.py000066400000000000000000000077611422760043000163040ustar00rootroot00000000000000""" 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 .compat import HAS_NUMPY, exp, log # noqa: F401 class Converter: """Base class for value converters.""" @property def is_multiplicative(self): return True @property def is_logarithmic(self): return False def to_reference(self, value, inplace=False): return value def from_reference(self, value, inplace=False): return value class ScaleConverter(Converter): """A linear transformation.""" def __init__(self, scale): self.scale = scale def to_reference(self, value, inplace=False): if inplace: value *= self.scale else: value = value * self.scale return value def from_reference(self, value, inplace=False): if inplace: value /= self.scale else: value = value / self.scale return value class OffsetConverter(Converter): """An affine transformation.""" def __init__(self, scale, offset): self.scale = scale self.offset = offset @property def is_multiplicative(self): return self.offset == 0 def to_reference(self, value, inplace=False): if inplace: value *= self.scale value += self.offset else: value = value * self.scale + self.offset return value def from_reference(self, value, inplace=False): if inplace: value -= self.offset value /= self.scale else: value = (value - self.offset) / self.scale return value class LogarithmicConverter(Converter): """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 base 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 """ def __init__(self, scale, logbase, logfactor): """ Parameters ---------- scale : float unit of reference at denominator inside logarithm for unit conversion logbase: float base of logarithm used in unit conversion logfactor: float factor multiplied to logarithm for unit conversion """ self.scale = scale self.logbase = logbase self.logfactor = logfactor @property def is_multiplicative(self): return False @property def is_logarithmic(self): return True def from_reference(self, value, inplace=False): """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, inplace=False): """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.19.2/pint/default_en.txt000066400000000000000000000733451422760043000164100ustar00rootroot00000000000000# 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 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- 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- # 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 # 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] 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] # 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 # 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 flux [magnetic_flux] = [electric_potential] * [time] weber = volt * second = Wb unit_pole = µ_0 * biot * centimeter # 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 # 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) ] 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 = Td 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.19.2/pint/definitions.py000066400000000000000000000241431422760043000164160ustar00rootroot00000000000000""" pint.definitions ~~~~~~~~~~~~~~~~ Functions and classes related to unit definitions. :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 typing import Callable, Iterable, Optional, Tuple, Union from .converters import Converter, LogarithmicConverter, OffsetConverter, ScaleConverter from .errors import DefinitionSyntaxError from .util import ParserHelper, UnitsContainer, _is_dim @dataclass(frozen=True) class PreprocessedDefinition: """Splits a definition into the constitutive parts. A definition is given as a string with equalities in a single line:: ---------------> rhs a = b = c = d = e | | | -------> aliases (optional) | | | | | -----------> symbol (use "_" for no symbol) | | | ---------------> value | -------------------> name """ name: str symbol: Optional[str] aliases: Tuple[str, ...] value: str rhs_parts: Tuple[str, ...] @classmethod def from_string(cls, definition: str) -> PreprocessedDefinition: name, definition = definition.split("=", 1) name = name.strip() rhs_parts = tuple(res.strip() for res in definition.split("=")) value, aliases = rhs_parts[0], tuple([x for x in rhs_parts[1:] if x != ""]) symbol, aliases = (aliases[0], aliases[1:]) if aliases else (None, aliases) if symbol == "_": symbol = None aliases = tuple([x for x in aliases if x != "_"]) return cls(name, symbol, aliases, value, rhs_parts) class _NotNumeric(Exception): """Internal exception. Do not expose outside Pint""" def __init__(self, value): self.value = value def numeric_parse(s: str, non_int_type: type = float): """Try parse a string into a number (without using eval). Parameters ---------- s : str non_int_type : type Returns ------- Number Raises ------ _NotNumeric If the string cannot be parsed as a number. """ ph = ParserHelper.from_string(s, non_int_type) if len(ph): raise _NotNumeric(s) return ph.scale @dataclass(frozen=True) class Definition: """Base class for definitions. Parameters ---------- name : str Canonical name of the unit/prefix/etc. defined_symbol : str or None A short name or symbol for the definition. aliases : iterable of str Other names for the unit/prefix/etc. converter : callable or Converter or None """ name: str defined_symbol: Optional[str] aliases: Tuple[str, ...] converter: Optional[Union[Callable, Converter]] def __post_init__(self): if isinstance(self.converter, str): raise TypeError( "The converter parameter cannot be an instance of `str`. Use `from_string` method" ) @property def is_multiplicative(self) -> bool: return self.converter.is_multiplicative @property def is_logarithmic(self) -> bool: return self.converter.is_logarithmic @classmethod def from_string( cls, definition: Union[str, PreprocessedDefinition], non_int_type: type = float ) -> Definition: """Parse a definition. Parameters ---------- definition : str or PreprocessedDefinition non_int_type : type Returns ------- Definition or subclass of Definition """ if isinstance(definition, str): definition = PreprocessedDefinition.from_string(definition) if definition.name.startswith("["): return DimensionDefinition.from_string(definition, non_int_type) elif definition.name.endswith("-"): return PrefixDefinition.from_string(definition, non_int_type) else: return UnitDefinition.from_string(definition, non_int_type) @property def symbol(self) -> str: return self.defined_symbol or self.name @property def has_symbol(self) -> bool: return bool(self.defined_symbol) def add_aliases(self, *alias: str) -> None: raise Exception("Cannot add aliases, definitions are inmutable.") def __str__(self) -> str: return self.name @dataclass(frozen=True) class PrefixDefinition(Definition): """Definition of a prefix:: - = [= ] [= ] [ = ] [...] Example:: deca- = 1e+1 = da- = deka- """ @classmethod def from_string( cls, definition: Union[str, PreprocessedDefinition], non_int_type: type = float ) -> PrefixDefinition: if isinstance(definition, str): definition = PreprocessedDefinition.from_string(definition) aliases = tuple(alias.strip("-") for alias in definition.aliases) if definition.symbol: symbol = definition.symbol.strip("-") else: symbol = definition.symbol try: converter = ScaleConverter(numeric_parse(definition.value, non_int_type)) except _NotNumeric as ex: raise ValueError( f"Prefix definition ('{definition.name}') must contain only numbers, not {ex.value}" ) return cls(definition.name.rstrip("-"), symbol, aliases, converter) @dataclass(frozen=True) class UnitDefinition(Definition): """Definition of a unit:: = [= ] [= ] [ = ] [...] Example:: millennium = 1e3 * year = _ = millennia Parameters ---------- reference : UnitsContainer Reference units. is_base : bool Indicates if it is a base unit. """ reference: Optional[UnitsContainer] = None is_base: bool = False @classmethod def from_string( cls, definition: Union[str, PreprocessedDefinition], non_int_type: type = float ) -> "UnitDefinition": if isinstance(definition, str): definition = PreprocessedDefinition.from_string(definition) if ";" in definition.value: [converter, modifiers] = definition.value.split(";", 1) try: modifiers = dict( (key.strip(), numeric_parse(value, non_int_type)) for key, value in (part.split(":") for part in modifiers.split(";")) ) except _NotNumeric as ex: raise ValueError( f"Unit definition ('{definition.name}') must contain only numbers in modifier, not {ex.value}" ) else: converter = definition.value modifiers = {} converter = ParserHelper.from_string(converter, non_int_type) if not any(_is_dim(key) for key in converter.keys()): is_base = False elif all(_is_dim(key) for key in converter.keys()): is_base = True else: raise DefinitionSyntaxError( "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." ) reference = UnitsContainer(converter) if not modifiers: converter = ScaleConverter(converter.scale) elif "offset" in modifiers: if modifiers.get("offset", 0.0) != 0.0: converter = OffsetConverter(converter.scale, modifiers["offset"]) else: converter = ScaleConverter(converter.scale) elif "logbase" in modifiers and "logfactor" in modifiers: converter = LogarithmicConverter( converter.scale, modifiers["logbase"], modifiers["logfactor"] ) else: raise DefinitionSyntaxError("Unable to assign a converter to the unit") return cls( definition.name, definition.symbol, definition.aliases, converter, reference, is_base, ) @dataclass(frozen=True) class DimensionDefinition(Definition): """Definition of a dimension:: [dimension name] = Example:: [density] = [mass] / [volume] """ reference: Optional[UnitsContainer] = None is_base: bool = False @classmethod def from_string( cls, definition: Union[str, PreprocessedDefinition], non_int_type: type = float ) -> DimensionDefinition: if isinstance(definition, str): definition = PreprocessedDefinition.from_string(definition) converter = ParserHelper.from_string(definition.value, non_int_type) if not converter: is_base = True elif all(_is_dim(key) for key in converter.keys()): is_base = False else: raise DefinitionSyntaxError( "Base dimensions must be referenced to None. " "Derived dimensions must only be referenced " "to dimensions." ) reference = UnitsContainer(converter, non_int_type=non_int_type) return cls( definition.name, definition.symbol, definition.aliases, converter, reference, is_base, ) class AliasDefinition(Definition): """Additional alias(es) for an already existing unit:: @alias = [ = ] [...] Example:: @alias meter = my_meter """ def __init__(self, name: str, aliases: Iterable[str]) -> None: super().__init__( name=name, defined_symbol=None, aliases=aliases, converter=None ) @classmethod def from_string( cls, definition: Union[str, PreprocessedDefinition], non_int_type: type = float ) -> AliasDefinition: if isinstance(definition, str): definition = PreprocessedDefinition.from_string(definition) name = definition.name[len("@alias ") :].lstrip() return AliasDefinition(name, tuple(definition.rhs_parts)) pint-0.19.2/pint/errors.py000066400000000000000000000102071422760043000154130ustar00rootroot00000000000000""" 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. """ OFFSET_ERROR_DOCS_HTML = "https://pint.readthedocs.io/en/latest/nonmult.html" LOG_ERROR_DOCS_HTML = "https://pint.readthedocs.io/en/latest/nonmult.html" def _file_prefix(filename=None, lineno=None): if filename and lineno is not None: return f"While opening {filename}, in line {lineno}: " elif filename: return f"While opening {filename}: " elif lineno is not None: return f"In line {lineno}: " else: return "" class PintError(Exception): """Base exception for all Pint errors.""" class DefinitionSyntaxError(SyntaxError, PintError): """Raised when a textual definition has a syntax error.""" def __init__(self, msg, *, filename=None, lineno=None): super().__init__(msg) self.filename = filename self.lineno = lineno def __str__(self): return _file_prefix(self.filename, self.lineno) + str(self.args[0]) @property def __dict__(self): # SyntaxError.filename and lineno are special fields that don't appear in # the __dict__. This messes up pickling and deepcopy, as well # as any other Python library that expects sane behaviour. return {"filename": self.filename, "lineno": self.lineno} def __reduce__(self): return DefinitionSyntaxError, self.args, self.__dict__ class RedefinitionError(ValueError, PintError): """Raised when a unit or prefix is redefined.""" def __init__(self, name, definition_type, *, filename=None, lineno=None): super().__init__(name, definition_type) self.filename = filename self.lineno = lineno def __str__(self): msg = f"Cannot redefine '{self.args[0]}' ({self.args[1]})" return _file_prefix(self.filename, self.lineno) + msg def __reduce__(self): return RedefinitionError, self.args, self.__dict__ class UndefinedUnitError(AttributeError, PintError): """Raised when the units are not defined in the unit registry.""" def __init__(self, *unit_names): if len(unit_names) == 1 and not isinstance(unit_names[0], str): unit_names = unit_names[0] super().__init__(*unit_names) def __str__(self): if len(self.args) == 1: return f"'{self.args[0]}' is not defined in the unit registry" return f"{self.args} are not defined in the unit registry" class PintTypeError(TypeError, PintError): pass class DimensionalityError(PintTypeError): """Raised when trying to convert between incompatible units.""" def __init__(self, units1, units2, dim1="", dim2="", *, extra_msg=""): super().__init__() 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 TypeError.__new__, (DimensionalityError,), self.__dict__ class OffsetUnitCalculusError(PintTypeError): """Raised on ambiguous operations with offset units.""" def __str__(self): return ( "Ambiguous operation with offset unit (%s)." % ", ".join(str(u) for u in self.args) + " See " + OFFSET_ERROR_DOCS_HTML + " for guidance." ) class LogarithmicUnitCalculusError(PintTypeError): """Raised on inappropriate operations with logarithmic units.""" def __str__(self): return ( "Ambiguous operation with logarithmic unit (%s)." % ", ".join(str(u) for u in self.args) + " See " + LOG_ERROR_DOCS_HTML + " for guidance." ) class UnitStrippedWarning(UserWarning, PintError): pass pint-0.19.2/pint/formatting.py000066400000000000000000000377161422760043000162670ustar00rootroot00000000000000""" pint.formatter ~~~~~~~~~~~~~~ Format units for pint. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import re import warnings from typing import Callable, Dict from .babel_names import _babel_lengths, _babel_units from .compat import babel_parse __JOIN_REG_EXP = re.compile(r"{\d*}") def _join(fmt, iterable): """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. ' * ') Parameters ---------- fmt : str iterable : Returns ------- str """ 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 _PRETTY_EXPONENTS = "⁰¹²³⁴⁵⁶⁷⁸⁹" def _pretty_fmt_exponent(num): """Format an number into a pretty printed exponent. Parameters ---------- num : int Returns ------- str """ # 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 #: _FORMATS maps format specifications to the corresponding argument set to #: formatter(). _FORMATS: Dict[str, dict] = { "P": { # Pretty format. "as_ratio": True, "single_denominator": False, "product_fmt": "·", "division_fmt": "/", "power_fmt": "{}{}", "parentheses_fmt": "({})", "exp_call": _pretty_fmt_exponent, }, "L": { # Latex format. "as_ratio": True, "single_denominator": True, "product_fmt": r" \cdot ", "division_fmt": r"\frac[{}][{}]", "power_fmt": "{}^[{}]", "parentheses_fmt": r"\left({}\right)", }, "Lx": {"siopts": "", "pm_fmt": " +- "}, # Latex format with SIunitx. "H": { # HTML format. "as_ratio": True, "single_denominator": True, "product_fmt": r" ", "division_fmt": r"{}/{}", "power_fmt": r"{}{}", "parentheses_fmt": r"({})", }, "": { # Default format. "as_ratio": True, "single_denominator": False, "product_fmt": " * ", "division_fmt": " / ", "power_fmt": "{} ** {}", "parentheses_fmt": r"({})", }, "C": { # Compact format. "as_ratio": True, "single_denominator": False, "product_fmt": "*", # TODO: Should this just be ''? "division_fmt": "/", "power_fmt": "{}**{}", "parentheses_fmt": r"({})", }, } #: _FORMATTERS maps format names to callables doing the formatting _FORMATTERS: Dict[str, Callable] = {} def register_unit_format(name): """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}" """ def wrapper(func): if name in _FORMATTERS: raise ValueError(f"format {name:!r} already exists") # or warn instead _FORMATTERS[name] = func return wrapper @register_unit_format("P") def format_pretty(unit, registry, **options): return formatter( unit.items(), as_ratio=True, single_denominator=False, product_fmt="·", division_fmt="/", power_fmt="{}{}", parentheses_fmt="({})", exp_call=_pretty_fmt_exponent, **options, ) @register_unit_format("L") def format_latex(unit, registry, **options): preprocessed = { r"\mathrm{{{}}}".format(u.replace("_", r"\_")): p for u, p in unit.items() } formatted = formatter( preprocessed.items(), as_ratio=True, single_denominator=True, product_fmt=r" \cdot ", division_fmt=r"\frac[{}][{}]", power_fmt="{}^[{}]", parentheses_fmt=r"\left({}\right)", **options, ) return formatted.replace("[", "{").replace("]", "}") @register_unit_format("Lx") def format_latex_siunitx(unit, registry, **options): 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`." ) formatted = siunitx_format_unit(unit, registry) return rf"\si[]{{{formatted}}}" @register_unit_format("H") def format_html(unit, registry, **options): return formatter( unit.items(), as_ratio=True, single_denominator=True, product_fmt=r" ", division_fmt=r"{}/{}", power_fmt=r"{}{}", parentheses_fmt=r"({})", **options, ) @register_unit_format("D") def format_default(unit, registry, **options): return formatter( unit.items(), as_ratio=True, single_denominator=False, product_fmt=" * ", division_fmt=" / ", power_fmt="{} ** {}", parentheses_fmt=r"({})", **options, ) @register_unit_format("C") def format_compact(unit, registry, **options): return formatter( unit.items(), as_ratio=True, single_denominator=False, product_fmt="*", # TODO: Should this just be ''? division_fmt="/", power_fmt="{}**{}", parentheses_fmt=r"({})", **options, ) def formatter( items, as_ratio=True, single_denominator=False, product_fmt=" * ", division_fmt=" / ", power_fmt="{} ** {}", parentheses_fmt="({0})", exp_call=lambda x: f"{x:n}", locale=None, babel_length="long", babel_plural_form="one", sort=True, ): """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})") locale : str the locale object as defined in babel. (Default value = None) babel_length : str the length of the translated unit, as defined in babel cldr. (Default value = "long") babel_plural_form : str the plural form, calculated as defined in babel. (Default value = "one") 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. """ if not items: return "" if as_ratio: fun = lambda x: exp_call(abs(x)) else: fun = exp_call pos_terms, neg_terms = [], [] if sort: items = sorted(items) for key, value in items: if locale and babel_length and babel_plural_form and key in _babel_units: _key = _babel_units[key] locale = babel_parse(locale) unit_patterns = locale._data["unit_patterns"] compound_unit_patterns = locale._data["compound_unit_patterns"] plural = "one" if abs(value) <= 0 else babel_plural_form if babel_length not in _babel_lengths: other_lengths = [ _babel_length for _babel_length in reversed(_babel_lengths) if babel_length != _babel_length ] else: other_lengths = [] for _babel_length in [babel_length] + other_lengths: pat = unit_patterns.get(_key, {}).get(_babel_length, {}).get(plural) if pat is not None: # Don't remove this positional! This is the format used in Babel key = pat.replace("{0}", "").strip() break division_fmt = compound_unit_patterns.get("per", {}).get( babel_length, division_fmt ) power_fmt = "{}{}" exp_call = _pretty_fmt_exponent 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(product_fmt, neg_terms) if len(neg_terms) > 1: neg_ret = parentheses_fmt.format(neg_ret) else: neg_ret = _join(division_fmt, neg_terms) return _join(division_fmt, [pos_ret, neg_ret]) # 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") def _parse_spec(spec): result = "" for ch in reversed(spec): if ch == "~" or ch in _BASIC_TYPES: continue elif ch in list(_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 format_unit(unit, spec, 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" fmt = _FORMATTERS.get(spec) if fmt is None: raise ValueError(f"Unknown conversion specified: {spec}") return fmt(unit, registry=registry, **options) def siunitx_format_unit(units, registry): """Returns LaTeX code for the unit that can be put into an siunitx command.""" def _tothe(power): if isinstance(power, int) or (isinstance(power, float) and power.is_integer()): if power == 1: return "" elif power == 2: return r"\squared" elif power == 3: return r"\cubed" else: return r"\tothe{{{:d}}}".format(int(power)) else: # limit float powers to 3 decimal places return r"\tothe{{{:.3f}}}".format(power).rstrip("0") lpos = [] lneg = [] # loop through all units in the container for unit, power in sorted(units.items()): # remove unit prefix if it exists # siunitx supports \prefix commands lpick = lpos if power >= 0 else lneg prefix = None for p in registry._prefixes.values(): p = str(p) 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(r"\{}".format(prefix)) lpick.append(r"\{}".format(unit)) lpick.append(r"{}".format(_tothe(abs(power)))) return "".join(lpos) + "".join(lneg) def extract_custom_flags(spec): import re if not spec: return "" # sort by length, with longer items first known_flags = sorted(_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): for flag in sorted(_FORMATTERS.keys(), key=len, reverse=True) + ["~"]: if flag: spec = spec.replace(flag, "") return spec def split_format(spec, default, separate_format_defaults=True): 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 if mspec else default_mspec uspec = uspec if uspec else default_uspec return mspec, uspec def vector_to_latex(vec, fmtfun=lambda x: format(x, ".2f")): return matrix_to_latex([vec], fmtfun) def matrix_to_latex(matrix, fmtfun=lambda x: format(x, ".2f")): ret = [] 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, fmtfun=lambda x: format(x, ".2f"), dim=()): if isinstance(fmtfun, str): fmt = fmtfun fmtfun = lambda x: format(x, fmt) 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, fmtfun=lambda x: format(x, ".2f"), dim=()): return "\n".join(ndarray_to_latex_parts(ndarr, fmtfun, dim)) pint-0.19.2/pint/matplotlib.py000066400000000000000000000046261422760043000162560ustar00rootroot00000000000000""" 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. """ 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.""" super().__init__(label="{:P}".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.""" if iterable(value): return [self._convert_value(v, unit, axis) for v in value] else: 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 else: 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.19.2/pint/measurement.py000066400000000000000000000141271422760043000164310ustar00rootroot00000000000000""" pint.measurement ~~~~~~~~~~~~~~~~ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import re from .compat import ufloat from .formatting import _FORMATS, extract_custom_flags, siunitx_format_unit from .quantity import Quantity MISSING = object() class Measurement(Quantity): """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, 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 = "" try: error = error.to(units).magnitude except AttributeError: pass if error is MISSING: mag = value elif 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 . 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 "{}".format(self) def __format__(self, spec): spec = spec or self.default_format # 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, self._REGISTRY) return r"\SI%s{%s}{%s}" % (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 _Measurement = Measurement def build_measurement_class(registry): if ufloat is None: class Measurement: _REGISTRY = registry def __init__(self, *args): raise RuntimeError( "Pint requires the 'uncertainties' package to create a Measurement object." ) else: class Measurement(_Measurement): _REGISTRY = registry return Measurement pint-0.19.2/pint/numpy_func.py000066400000000000000000000761541422760043000162770ustar00rootroot00000000000000""" pint.numpy_func ~~~~~~~~~~~~~~~ :copyright: 2019 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import warnings from inspect import signature from itertools import chain from .compat import is_upcast_type, np, zero_or_nan from .errors import DimensionalityError, 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 """ 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 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 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("Output unit method {} not understood".format(unit_op)) 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("Invalid func_type {}".format(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): 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 base 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", "ceil", "floor", "hypot", "rint", "copysign", "nextafter", "trunc", "absolute", "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 base 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 else: 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, dtype=None, order="K", subok=True, shape=None): # 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, dtype=dtype, order=order, subok=subok, shape=shape) * fill_value.m ), fill_value.units, ) else: return ( np.ones_like(a, dtype=dtype, order=order, subok=subok, shape=shape) * 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): 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) else: 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) # 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) else: 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), ("sort", "a", True), ("median", "a", True), ("nanmedian", "a", True), ("transpose", "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), ("searchsorted", ["a", "v"], False), ("isclose", ["a", "b"], 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), ("resize", "a", True), ("reshape", "a", True), ("allclose", ["a", "b"], False), ("intersect1d", ["ar1", "ar2"], True), ]: implement_consistent_units_by_argument(func_str, unit_arguments, wrap_output) # 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", "cumproduct", "nancumprod"]: implement_single_dimensionless_argument_func(func_str) # Handle single-argument consistent unit functions for func_str in ["block", "hstack", "vstack", "dstack", "column_stack"]: 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", "alen", "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"]: implement_func("function", func_str, input_units=None, output_unit="sum") for func_str in ["cross", "trapz", "dot"]: implement_func("function", func_str, input_units=None, output_unit="mul") 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("Invalid func_type {}".format(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.19.2/pint/parser.py000066400000000000000000000311461422760043000154000ustar00rootroot00000000000000""" pint.parser ~~~~~~~~~~~ Classes and methods to parse a definition text file into a DefinitionFile. :copyright: 2019 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import pathlib import re from dataclasses import dataclass, field from functools import cached_property from importlib import resources from io import StringIO from typing import Any, Callable, Dict, Generator, Iterable, Optional, Tuple from ._vendor import flexcache as fc from .definitions import Definition from .errors import DefinitionSyntaxError from .util import SourceIterator, logger _BLOCK_RE = re.compile(r"[ (]") ParserFuncT = Callable[[SourceIterator, type], Any] @dataclass(frozen=True) class DefinitionFile: """Represents a definition file after parsing.""" # Fullpath of the original file, None if a text was provided filename: Optional[pathlib.Path] is_resource: bool # Modification time of the file or None. mtime: Optional[float] # SHA-1 hash content_hash: Optional[str] # collection of line number and corresponding definition. parsed_lines: Tuple[Tuple[int, Any], ...] def filter_by(self, *klass): yield from ( (lineno, d) for lineno, d in self.parsed_lines if isinstance(d, klass) ) @cached_property def errors(self): return tuple(self.filter_by(Exception)) def has_errors(self): return bool(self.errors) class DefinitionFiles(tuple): """Wrapper class that allows handling a tuple containing DefinitionFile.""" @staticmethod def _iter_definitions( pending_files: list[DefinitionFile], ) -> Generator[Tuple[int, Definition]]: """Internal method to iterate definitions. pending_files is a mutable list of definitions files and elements are being removed as they are yielded. """ if not pending_files: return current_file = pending_files.pop(0) for lineno, definition in current_file.parsed_lines: if isinstance(definition, ImportDefinition): if not pending_files: raise ValueError( f"No more files while trying to import {definition.path}." ) if not str(pending_files[0].filename).endswith(str(definition.path)): raise ValueError( "The order of the files do not match. " f"(expected: {definition.path}, " f"found {pending_files[0].filename})" ) yield from DefinitionFiles._iter_definitions(pending_files) else: yield lineno, definition def iter_definitions(self): """Iter all definitions in the order they appear, going into the included files. Important: This assumes that the order of the imported files is the one that they will appear in the definitions. """ yield from self._iter_definitions(list(self)) def build_disk_cache_class(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 = field(default_factory=lambda: non_int_type.__qualname__) class PathHeader(fc.NameByFileContent, PintHeader): pass class DefinitionFilesHeader(fc.NameByHashIter, PintHeader): @classmethod def from_definition_files(cls, dfs: DefinitionFiles, reader_id): return cls(tuple(df.content_hash for df in dfs), reader_id) class PintDiskCache(fc.DiskCache): _header_classes = { pathlib.Path: PathHeader, str: PathHeader.from_string, DefinitionFiles: DefinitionFilesHeader.from_definition_files, } return PintDiskCache @dataclass(frozen=True) class ImportDefinition: """Definition for the @import directive""" path: pathlib.Path @classmethod def from_string( cls, definition: str, non_int_type: type = float ) -> ImportDefinition: return ImportDefinition(pathlib.Path(definition[7:].strip())) class Parser: """Class to parse a definition file into an intermediate object representation. non_int_type numerical type used for non integer values. (Default: float) raise_on_error if True, an exception will be raised as soon as a Definition Error it is found. if False, the exception will be added to the ParedDefinitionFile """ #: Map context prefix to function _directives: Dict[str, ParserFuncT] _diskcache: fc.DiskCache handled_classes = (ImportDefinition,) def __init__(self, non_int_type=float, raise_on_error=True, cache_folder=None): self._directives = {} self._non_int_type = non_int_type self._raise_on_error = raise_on_error self.register_class("@import", ImportDefinition) if isinstance(cache_folder, (str, pathlib.Path)): self._diskcache = build_disk_cache_class(non_int_type)(cache_folder) else: self._diskcache = cache_folder def register_directive( self, prefix: str, parserfunc: ParserFuncT, single_line: bool ): """Register a parser for a given @ directive.. Parameters ---------- prefix string identifying the section (e.g. @context) parserfunc function that is able to parse a definition into a DefinitionObject single_line indicates that the directive spans in a single line, i.e. and @end is not required. """ if prefix and prefix[0] == "@": if single_line: self._directives[prefix] = lambda si, non_int_type: parserfunc( si.last[1], non_int_type ) else: self._directives[prefix] = lambda si, non_int_type: parserfunc( si.block_iter(), non_int_type ) else: raise ValueError("Prefix directives must start with '@'") def register_class(self, prefix: str, klass): """Register a definition class for a directive and try to guess if it is a line or block directive from the signature. """ if hasattr(klass, "from_string"): self.register_directive(prefix, klass.from_string, True) elif hasattr(klass, "from_lines"): self.register_directive(prefix, klass.from_lines, False) else: raise ValueError( f"While registering {prefix}, {klass} does not have `from_string` or from_lines` method" ) def parse(self, file, is_resource: bool = False) -> DefinitionFiles: """Parse a file or resource into a collection of DefinitionFile that will include all other files imported. Parameters ---------- file definitions or file containing definition. is_resource indicates that the file is a resource file and therefore should be loaded from the package. (Default value = False) """ if is_resource: parsed = self.parse_single_resource(file) else: path = pathlib.Path(file) if self._diskcache is None: parsed = self.parse_single(path, None) else: parsed, content_hash = self._diskcache.load( path, self.parse_single, True ) out = [parsed] for lineno, content in parsed.filter_by(ImportDefinition): if parsed.is_resource: path = content.path else: try: basedir = parsed.filename.parent except AttributeError: basedir = pathlib.Path.cwd() path = basedir.joinpath(content.path) out.extend(self.parse(path, parsed.is_resource)) return DefinitionFiles(out) def parse_single_resource(self, resource_name: str) -> DefinitionFile: """Parse a resource in the package into a DefinitionFile. Imported files will appear as ImportDefinition objects and will not be followed. This method will try to load it first as a regular file (with a path and mtime) to allow caching. If this files (i.e. the resource is not filesystem file) it will use python importlib.resources.read_binary """ with resources.path(__package__, resource_name) as p: filepath = p.resolve() if filepath.exists(): if self._diskcache is None: return self.parse_single(filepath, None) else: definition_file, content_hash = self._diskcache.load( filepath, self.parse_single, True ) return definition_file logger.debug("Cannot use_cache resource (yet) without a real path") return self._parse_single_resource(resource_name) def _parse_single_resource(self, resource_name: str) -> DefinitionFile: rbytes = resources.read_binary(__package__, resource_name) if self._diskcache: hdr = self._diskcache.PathHeader(rbytes) content_hash = self._diskcache.cache_stem_for(hdr) else: content_hash = None si = SourceIterator( StringIO(rbytes.decode("utf-8")), resource_name, is_resource=True ) parsed_lines = tuple(self.yield_from_source_iterator(si)) return DefinitionFile( filename=pathlib.Path(resource_name), is_resource=True, mtime=None, content_hash=content_hash, parsed_lines=parsed_lines, ) def parse_single( self, filepath: pathlib.Path, content_hash: Optional[str] ) -> DefinitionFile: """Parse a filepath without nesting into dependent files. Imported files will appear as ImportDefinition objects and will not be followed. Parameters ---------- filepath definitions or file containing definition. """ with filepath.open(encoding="utf-8") as fp: si = SourceIterator(fp, filepath, is_resource=False) parsed_lines = tuple(self.yield_from_source_iterator(si)) filename = filepath.resolve() mtime = filepath.stat().st_mtime return DefinitionFile( filename=filename, is_resource=False, mtime=mtime, content_hash=content_hash, parsed_lines=parsed_lines, ) def parse_lines(self, lines: Iterable[str]) -> DefinitionFile: """Parse an iterable of strings into a dependent file""" si = SourceIterator(lines, None, False) parsed_lines = tuple(self.yield_from_source_iterator(si)) df = DefinitionFile(None, False, None, "", parsed_lines=parsed_lines) if any(df.filter_by(ImportDefinition)): raise ValueError( "Cannot use the @import directive when parsing " "an iterable of strings." ) return df def yield_from_source_iterator( self, source_iterator: SourceIterator ) -> Generator[Tuple[int, Any]]: """Iterates through the source iterator, yields line numbers and the coresponding parsed definition object. Parameters ---------- source_iterator """ for lineno, line in source_iterator: try: if line.startswith("@"): # Handle @ directives dispatching to the appropriate parsers parts = _BLOCK_RE.split(line) subparser = self._directives.get(parts[0], None) if subparser is None: raise DefinitionSyntaxError( "Unknown directive %s" % line, lineno=lineno ) d = subparser(source_iterator, self._non_int_type) yield lineno, d else: yield lineno, Definition.from_string(line, self._non_int_type) except DefinitionSyntaxError as ex: if ex.lineno is None: ex.lineno = lineno if self._raise_on_error: raise ex yield lineno, ex except Exception as ex: logger.error("In line {}, cannot add '{}' {}".format(lineno, line, ex)) raise ex pint-0.19.2/pint/pint-convert000077500000000000000000000123551422760043000161110ustar00rootroot00000000000000#!/usr/bin/env python3 """ pint-convert ~~~~~~~~~~~~ :copyright: 2020 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import argparse 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", "--no-unc", dest="unc", action="store_false", help="ignore 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 if args.unc: import uncertainties # 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 (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 ) 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) ureg._units["R_inf"].converter.scale = R_i ureg._units["g_e"].converter.scale = g_e ureg._units["m_u"].converter.scale = m_u ureg._units["m_e"].converter.scale = m_e ureg._units["m_p"].converter.scale = m_p ureg._units["m_n"].converter.scale = m_n # Measured constants with zero correlation ureg._units["gravitational_constant"].converter.scale = uncertainties.ufloat( ureg._units["gravitational_constant"].converter.scale, 0.00015e-11 ) ureg._units["d_220"].converter.scale = uncertainties.ufloat( ureg._units["d_220"].converter.scale, 0.000000032e-10 ) ureg._units["K_alpha_Cu_d_220"].converter.scale = uncertainties.ufloat( ureg._units["K_alpha_Cu_d_220"].converter.scale, 0.00000022 ) ureg._units["K_alpha_Mo_d_220"].converter.scale = uncertainties.ufloat( ureg._units["K_alpha_Mo_d_220"].converter.scale, 0.00000019 ) ureg._units["K_alpha_W_d_220"].converter.scale = uncertainties.ufloat( ureg._units["K_alpha_W_d_220"].converter.scale, 0.000000098 ) ureg._root_units_cache = dict() ureg._build_cache() def convert(u_from, u_to=None, unc=None, factor=None): q = ureg.Quantity(u_from) fmt = ".{}g".format(args.prec) 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() prec_unc = use_unc(nq.magnitude, fmt, args.prec_unc) if prec_unc > 0: fmt = ".{}uS".format(prec_unc) else: try: nq = nq.magnitude.n * nq.units except Exception: pass fmt = "{:" + fmt + "} {:~P}" print(("{:} = " + fmt).format(q, nq.magnitude, nq.units)) def use_unc(num, fmt, prec_unc): unc = 0 try: if isinstance(num, uncertainties.UFloat): full = ("{:" + fmt + "}").format(num) unc = re.search(r"\+/-[0.]*([\d.]*)", full).group(1) unc = len(unc.replace(".", "")) except Exception: pass return max(0, min(prec_unc, unc)) convert(args.fr, args.to) pint-0.19.2/pint/pint_eval.py000066400000000000000000000201571422760043000160650ustar00rootroot00000000000000""" 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. """ import operator import token as tokenlib from .errors import DefinitionSyntaxError # For controlling order of operations _OP_PRIORITY = { "**": 3, "^": 3, "unary": 2, "*": 1, "": 1, # operator for implicit ops "//": 1, "/": 1, "%": 1, "+": 0, "-": 0, } def _power(left, right): from .compat import is_duck_array from .quantity import Quantity 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) _BINARY_OPERATOR_MAP = { "**": _power, "*": operator.mul, "": operator.mul, # operator for implicit ops "/": operator.truediv, "+": operator.add, "-": operator.sub, "%": operator.mod, "//": operator.floordiv, } _UNARY_OPERATOR_MAP = {"+": lambda x: x, "-": lambda x: x * -1} 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, operator=None, right=None): self.left = left self.operator = operator self.right = right def to_string(self): # For debugging purposes if self.right: comps = [self.left.to_string()] if self.operator: comps.append(self.operator[1]) comps.append(self.right.to_string()) elif self.operator: comps = [self.operator[1], self.left.to_string()] else: return self.left[1] return "(%s)" % " ".join(comps) def evaluate(self, define_op, bin_op=None, un_op=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: # binary or implicit operator op_text = self.operator[1] if self.operator else "" if op_text not in bin_op: raise DefinitionSyntaxError('missing binary operator "%s"' % op_text) left = self.left.evaluate(define_op, bin_op, un_op) return bin_op[op_text](left, self.right.evaluate(define_op, bin_op, un_op)) elif self.operator: # unary operator op_text = self.operator[1] if op_text not in un_op: raise DefinitionSyntaxError('missing unary operator "%s"' % op_text) return un_op[op_text](self.left.evaluate(define_op, bin_op, un_op)) else: # single value return define_op(self.left) def build_eval_tree(tokens, op_priority=_OP_PRIORITY, index=0, depth=0, prev_op=None): """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 """ if depth == 0 and prev_op is None: # ensure tokens is list so we can access by index tokens = list(tokens) result = None while True: current_token = tokens[index] token_type = current_token[0] token_text = current_token[1] if token_type == tokenlib.OP: if token_text == ")": if prev_op is None: raise DefinitionSyntaxError( "unopened parentheses in tokens: %s" % current_token ) elif prev_op == "(": # close parenthetical group return result, index else: # parenthetical group ending, but we need to close sub-operations within group 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 == tokenlib.NUMBER or token_type == 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 return result, index else: # recursion all closed, so just return the final result return result if index + 1 >= len(tokens): # should hit ENDMARKER before this ever happens raise DefinitionSyntaxError("unexpected end to tokens") index += 1 pint-0.19.2/pint/py.typed000066400000000000000000000000001422760043000152120ustar00rootroot00000000000000pint-0.19.2/pint/quantity.py000066400000000000000000002241231422760043000157610ustar00rootroot00000000000000""" pint.quantity ~~~~~~~~~~~~~ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import bisect import copy import datetime import functools import locale import math import numbers import operator import re import warnings from typing import ( TYPE_CHECKING, Any, Callable, Dict, Generic, Iterable, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union, overload, ) from ._typing import S, Shape, UnitLike, _MagnitudeType from .compat import ( HAS_NUMPY, _to_magnitude, babel_parse, compute, dask_array, eq, is_duck_array_type, is_upcast_type, ndarray, np, persist, visualize, zero_or_nan, ) from .definitions import UnitDefinition from .errors import ( DimensionalityError, OffsetUnitCalculusError, PintTypeError, UnitStrippedWarning, ) from .formatting import ( _pretty_fmt_exponent, ndarray_to_latex, remove_custom_flags, siunitx_format_unit, split_format, ) 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, ) from .util import ( PrettyIPython, SharedRegistryObject, UnitsContainer, infer_base_unit, iterable, logger, to_units_container, ) if TYPE_CHECKING: from . import Context, Unit from .registry import BaseRegistry from .unit import UnitsContainer as UnitsContainerT if HAS_NUMPY: import numpy as np # noqa class _Exception(Exception): # pragma: no cover def __init__(self, internal): self.internal = internal def reduce_dimensions(f): def wrapped(self, *args, **kwargs): result = f(self, *args, **kwargs) try: if result._REGISTRY.auto_reduce_dimensions: return result.to_reduced_units() else: return result except AttributeError: return result return wrapped def ireduce_dimensions(f): def wrapped(self, *args, **kwargs): result = f(self, *args, **kwargs) 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 Quantity * array[Quantity] 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 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 # Workaround to bypass dynamically generated Quantity with overload method Magnitude = TypeVar("Magnitude") class Quantity(PrettyIPython, SharedRegistryObject, Generic[_MagnitudeType]): """Implements a class to describe a physical quantity: the product of a numerical value and a unit of measurement. Parameters ---------- value : str, pint.Quantity or any numeric type Value of the physical quantity to be created. units : UnitsContainer, str or pint.Quantity Units of the physical quantity to be created. Returns ------- """ #: Default formatting string. default_format: str = "" _magnitude: _MagnitudeType @property def force_ndarray(self) -> bool: return self._REGISTRY.force_ndarray @property def force_ndarray_like(self) -> bool: return self._REGISTRY.force_ndarray_like @property def UnitsContainer(self) -> Callable[..., UnitsContainerT]: return self._REGISTRY.UnitsContainer def __reduce__(self) -> tuple: """Allow pickling quantities. Since UnitRegistries are not pickled, upon unpickling the new object is always attached to the application registry. """ from . import _unpickle_quantity # Note: type(self) would be a mistake as subclasses built by # build_quantity_class can't be pickled return _unpickle_quantity, (Quantity, self.magnitude, self._units) @overload def __new__( cls, value: str, units: Optional[UnitLike] = None ) -> Quantity[Magnitude]: ... @overload def __new__( # type: ignore[misc] cls, value: Sequence, units: Optional[UnitLike] = None ) -> Quantity[np.ndarray]: ... @overload def __new__( cls, value: Quantity[Magnitude], units: Optional[UnitLike] = None ) -> Quantity[Magnitude]: ... @overload def __new__( cls, value: Magnitude, units: Optional[UnitLike] = None ) -> Quantity[Magnitude]: ... def __new__(cls, value, units=None): if is_upcast_type(type(value)): raise TypeError(f"Quantity cannot wrap upcast type {type(value)}") if units is None and isinstance(value, str) and value == "": raise ValueError( "Expression to parse as Quantity 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, Quantity) and units.magnitude != 1: units = copy.copy(units)._units logger.warning( "Creating new Quantity using a non unity Quantity as units." ) else: units = units._units else: raise TypeError( "units must be of type str, Quantity 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 inst.__used = False inst.__handling = None return inst @property def debug_used(self): return self.__used def __iter__(self: Quantity[Iterable[S]]) -> Iterator[S]: # 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) -> Quantity[_MagnitudeType]: ret = self.__class__(copy.copy(self._magnitude), self._units) ret.__used = self.__used return ret def __deepcopy__(self, memo) -> Quantity[_MagnitudeType]: ret = self.__class__( copy.deepcopy(self._magnitude, memo), copy.deepcopy(self._units, memo) ) ret.__used = self.__used return ret def __str__(self) -> str: if self._REGISTRY.fmt_locale is not None: return self.format_babel() return format(self) def __bytes__(self) -> bytes: return str(self).encode(locale.getpreferredencoding()) def __repr__(self) -> str: if isinstance(self._magnitude, float): return f"" else: return f"" def __hash__(self) -> int: self_base = self.to_base_units() if self_base.dimensionless: return hash(self_base.magnitude) else: return hash((self_base.__class__, self_base.magnitude, self_base.units)) _exp_pattern = re.compile(r"([0-9]\.?[0-9]*)e(-?)\+?0*([0-9]+)") def __format__(self, spec: str) -> str: if self._REGISTRY.fmt_locale is not None: return self.format_babel(spec) mspec, uspec = split_format( spec, self.default_format, self._REGISTRY.separate_format_defaults ) # If Compact is selected, do it at the beginning if "#" in spec: # TODO: don't replace '#' mspec = mspec.replace("#", "") uspec = uspec.replace("#", "") obj = self.to_compact() else: obj = self if "L" in uspec: allf = plain_allf = r"{}\ {}" elif "H" in uspec: allf = plain_allf = "{} {}" if iterable(obj.magnitude): # Use HTML table instead of plain text template for array-likes allf = ( "" "" "" "" "
Magnitude{}
Units{}
" ) else: allf = plain_allf = "{} {}" if "Lx" in uspec: # the LaTeX siunitx code # TODO: add support for extracting options opts = "" ustr = siunitx_format_unit(obj.units._units, obj._REGISTRY) allf = r"\SI[%s]{{{}}}{{{}}}" % opts else: # Hand off to unit formatting # TODO: only use `uspec` after completing the deprecation cycle ustr = format(obj.units, mspec + uspec) # mspec = remove_custom_flags(spec) if "H" in uspec: # HTML formatting if hasattr(obj.magnitude, "_repr_html_"): # If magnitude has an HTML repr, nest it within Pint's mstr = obj.magnitude._repr_html_() else: if isinstance(self.magnitude, ndarray): # Use custom ndarray text formatting with monospace font formatter = "{{:{}}}".format(mspec) # Need to override for scalars, which are detected as iterable, # and don't respond to printoptions. if self.magnitude.ndim == 0: allf = plain_allf = "{} {}" mstr = formatter.format(obj.magnitude) else: with np.printoptions( formatter={"float_kind": formatter.format} ): mstr = ( "
"
                                + format(obj.magnitude).replace("\n", "
") + "
" ) elif not iterable(obj.magnitude): # Use plain text for scalars mstr = format(obj.magnitude, mspec) else: # Use monospace font for other array-likes mstr = ( "
"
                        + format(obj.magnitude, mspec).replace("\n", "
") + "
" ) elif isinstance(self.magnitude, ndarray): if "L" in uspec: # Use ndarray LaTeX special formatting mstr = ndarray_to_latex(obj.magnitude, mspec) else: # Use custom ndarray text formatting--need to handle scalars differently # since they don't respond to printoptions formatter = "{{:{}}}".format(mspec) if obj.magnitude.ndim == 0: mstr = formatter.format(obj.magnitude) else: with np.printoptions(formatter={"float_kind": formatter.format}): mstr = format(obj.magnitude).replace("\n", "") else: mstr = format(obj.magnitude, mspec).replace("\n", "") if "L" in uspec: mstr = self._exp_pattern.sub(r"\1\\times 10^{\2\3}", mstr) elif "H" in uspec or "P" in uspec: m = self._exp_pattern.match(mstr) _exp_formatter = ( _pretty_fmt_exponent if "P" in uspec else lambda s: f"{s}" ) if m: exp = int(m.group(2) + m.group(3)) mstr = self._exp_pattern.sub(r"\1×10" + _exp_formatter(exp), mstr) if allf == plain_allf and ustr.startswith("1 /"): # Write e.g. "3 / s" instead of "3 1 / s" ustr = ustr[2:] return allf.format(mstr, ustr).strip() def _repr_pretty_(self, p, cycle): if cycle: super()._repr_pretty_(p, cycle) else: p.pretty(self.magnitude) p.text(" ") p.pretty(self.units) def format_babel(self, spec: str = "", **kwspec: Any) -> str: spec = spec or self.default_format # standard cases if "#" in spec: spec = spec.replace("#", "") obj = self.to_compact() else: obj = self kwspec = dict(kwspec) if "length" in kwspec: kwspec["babel_length"] = kwspec.pop("length") loc = kwspec.get("locale", self._REGISTRY.fmt_locale) if loc is None: raise ValueError("Provide a `locale` value to localize translation.") kwspec["locale"] = babel_parse(loc) kwspec["babel_plural_form"] = kwspec["locale"].plural_form(obj.magnitude) return "{} {}".format( format(obj.magnitude, remove_custom_flags(spec)), obj.units.format_babel(spec, **kwspec), ).replace("\n", "") @property def magnitude(self) -> _MagnitudeType: """Quantity's magnitude. Long form for `m`""" return self._magnitude @property def m(self) -> _MagnitudeType: """Quantity's magnitude. Short form for `magnitude`""" return self._magnitude def m_as(self, units) -> _MagnitudeType: """Quantity's magnitude expressed in particular units. Parameters ---------- units : pint.Quantity, str or dict destination units Returns ------- """ return self.to(units).magnitude @property def units(self) -> "Unit": """Quantity's units. Long form for `u`""" return self._REGISTRY.Unit(self._units) @property def u(self) -> "Unit": """Quantity'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) @property def dimensionless(self) -> bool: """ """ tmp = self.to_root_units() return not bool(tmp.dimensionality) _dimensionality: Optional[UnitsContainerT] = None @property def dimensionality(self) -> UnitsContainerT: """ Returns ------- dict Dimensionality of the Quantity, 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[Quantity], units=None) -> Quantity[np.ndarray]: """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.Quantity list of pint.Quantity units : UnitsContainer, str or pint.Quantity units of the physical quantity to be created (Default value = None) Returns ------- pint.Quantity """ return cls.from_sequence(quant_list, units=units) @classmethod def from_sequence(cls, seq: Sequence[Quantity], units=None) -> Quantity[np.ndarray]: """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.Quantity sequence of pint.Quantity units : UnitsContainer, str or pint.Quantity units of the physical quantity to be created (Default value = None) Returns ------- pint.Quantity """ 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[_MagnitudeType, 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: Union[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, 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 Unit if contexts or self._REGISTRY._active_ctx: try: self.to(other, *contexts, **ctx_kwargs) return True except DimensionalityError: return False if isinstance(other, (Quantity, Unit)): 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=None, *contexts, **ctx_kwargs) -> None: """Inplace rescale to different units. Parameters ---------- other : pint.Quantity, 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=None, *contexts, **ctx_kwargs) -> Quantity[_MagnitudeType]: """Return Quantity rescaled to different units. Parameters ---------- other : pint.Quantity, 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.Quantity """ 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 Quantity 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) -> Quantity[_MagnitudeType]: """Return Quantity 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 Quantity rescaled to base 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) -> Quantity[_MagnitudeType]: """Return Quantity rescaled to base units.""" _, other = self._REGISTRY._get_base_units(self._units) magnitude = self._convert_magnitude_not_inplace(other) return self.__class__(magnitude, other) def _get_reduced_units(self, units): # 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 = self._REGISTRY._get_dimensionality_ratio(unit1, unit2) if power: units = units.add(unit2, exp / power).remove([unit1]) break return units def ito_reduced_units(self) -> None: """Return Quantity 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 self.dimensionless: return self.ito({}) if len(self._units) == 1: return None units = self._units.copy() new_units = self._get_reduced_units(units) return self.ito(new_units) def to_reduced_units(self) -> Quantity[_MagnitudeType]: """Return Quantity 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 self.dimensionless: return self.to({}) if len(self._units) == 1: return self units = self._units.copy() new_units = self._get_reduced_units(units) return self.to(new_units) def to_compact(self, unit=None) -> Quantity[_MagnitudeType]: """ "Return Quantity 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(self.magnitude, numbers.Number): msg = ( "to_compact applied to non numerical types " "has an undefined behavior." ) w = RuntimeWarning(msg) warnings.warn(w, stacklevel=2) return self if ( self.unitless or self.magnitude == 0 or math.isnan(self.magnitude) or math.isinf(self.magnitude) ): return self SI_prefixes: Dict[int, str] = {} for prefix in self._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(self, registry=self._REGISTRY) else: unit = infer_base_unit(self.__class__(1, unit), registry=self._REGISTRY) q_base = self.to(unit) magnitude = q_base.magnitude 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 self.to(new_unit_container) # 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.Quantity 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 Quantity 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 Quantity 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.Quantity 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 Quantity if zero_or_nan(other, True): # If the other value is 0 or NaN (but not a Quantity) # 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) -> Quantity[_MagnitudeType]: ... 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) else: return self._add_sub(other, operator.add) def __add__(self, other): if isinstance(other, datetime.datetime): return self.to_timedelta() + other else: 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) else: 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() else: 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.Quantity 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 == 1 and 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 == 1 and 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.Quantity 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 == 1 and 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 == 1 and 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) else: 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 __itruediv__(self, other): if is_duck_array_type(type(self._magnitude)): return self._imul_div(other, operator.itruediv) else: return self._mul_div(other, operator.truediv) def __truediv__(self, other): 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 == 1 and 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 base is dimensionless. Ensure dimensionless # units are reduced to "dimensionless". # Note: this will strip Units of degrees or radians from Quantity 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=". Quantity array exponents are only allowed if the " "base 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) -> Quantity[_MagnitudeType]: 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 base is dimensionless. # Note: this will strip Units of degrees or radians from Quantity 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=". Quantity array exponents are only allowed if the " "base 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) -> Quantity[_MagnitudeType]: 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) -> Quantity[_MagnitudeType]: return self.__class__(abs(self._magnitude), self._units) def __round__(self, ndigits: Optional[int] = 0) -> Quantity[int]: return self.__class__(round(self._magnitude, ndigits=ndigits), self._units) def __pos__(self) -> Quantity[_MagnitudeType]: return self.__class__(operator.pos(self._magnitude), self._units) def __neg__(self) -> Quantity[_MagnitudeType]: 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, Quantity): 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 base class of Quantity because # each Quantity class is unique. if not isinstance(other, Quantity): 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 base 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, Quantity): 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 base units if self._REGISTRY.autoconvert_offset_to_baseunit: return op(self.to_base_units()._magnitude, other) else: raise OffsetUnitCalculusError(self._units) else: raise ValueError("Cannot compare Quantity and {}".format(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 Quantity with offset unit is ambiguous.") __nonzero__ = __bool__ # 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 = set( 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) else: return value def __array__(self, t=None) -> 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: Quantity[np.ndarray], value) -> None: self._units = value._units return self.magnitude.fill(value.magnitude) def put(self: Quantity[np.ndarray], 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) -> Quantity[_MagnitudeType]: return self.__class__(self._magnitude.real, self._units) @property def imag(self) -> Quantity[_MagnitudeType]: 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 @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 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 np.ma.is_masked(value) or 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 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." ) # Measurement support def plus_minus(self, error, relative=False): if isinstance(error, self.__class__): if relative: raise ValueError("{} is not a valid relative error.".format(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) 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 Quantity 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 Quantity 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 Quantity object.""" return [u for u in self._units if u.startswith("delta_")] def _has_compatible_delta(self, unit: str) -> bool: """ "Check if Quantity 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=None) -> bool: """Checks if Quantity 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 def to_timedelta(self: Quantity[float]) -> datetime.timedelta: return datetime.timedelta(microseconds=self.to("microseconds").magnitude) # Dask.array.Array ducking def __dask_graph__(self): if isinstance(self._magnitude, dask_array.Array): return self._magnitude.__dask_graph__() else: return None def __dask_keys__(self): return self._magnitude.__dask_keys__() def __dask_tokenize__(self): from dask.base import tokenize return (Quantity, 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) @staticmethod def _dask_finalize(results, func, args, units): values = func(results, *args) return Quantity(values, units) @check_dask_array def compute(self, **kwargs): """Compute the Dask array wrapped by pint.Quantity. Parameters ---------- **kwargs : dict Any keyword arguments to pass to ``dask.compute``. Returns ------- pint.Quantity A pint.Quantity wrapped numpy array. """ (result,) = compute(self, **kwargs) return result @check_dask_array def persist(self, **kwargs): """Persist the Dask Array wrapped by pint.Quantity. Parameters ---------- **kwargs : dict Any keyword arguments to pass to ``dask.persist``. Returns ------- pint.Quantity A pint.Quantity 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) _Quantity = Quantity def build_quantity_class(registry: BaseRegistry) -> Type[Quantity]: class Quantity(_Quantity): _REGISTRY = registry return Quantity pint-0.19.2/pint/registry.py000066400000000000000000002353161422760043000157610ustar00rootroot00000000000000""" pint.registry ~~~~~~~~~~~~~ Defines the Registry, a class to contain units and their relations. The module actually defines 5 registries with different capabilities: - BaseRegistry: Basic unit definition and querying. Conversion between multiplicative units. - NonMultiplicativeRegistry: Conversion between non multiplicative (offset) units. (e.g. Temperature) * Inherits from BaseRegistry - ContextRegisty: Conversion between units with different dimensions according to previously established relations (contexts) - e.g. in spectroscopy, conversion between frequency and energy is possible. May also override conversions between units on the same dimension - e.g. different rounding conventions. * Inherits from BaseRegistry - SystemRegistry: Group unit and changing of base units. (e.g. in MKS, meter, kilogram and second are base units.) * Inherits from BaseRegistry - UnitRegistry: Combine all previous capabilities, it is exposed by Pint. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import annotations import copy import functools import itertools import locale import pathlib import re from collections import ChainMap, defaultdict from contextlib import contextmanager from dataclasses import dataclass from decimal import Decimal from fractions import Fraction from numbers import Number from tokenize import NAME, NUMBER from typing import ( TYPE_CHECKING, Any, Callable, ContextManager, Dict, FrozenSet, Iterable, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union, ) from . import parser, registry_helpers, systems from ._typing import F, QuantityOrUnitLike from ._vendor import appdirs from .compat import HAS_BABEL, babel_parse, tokenizer from .context import Context, ContextChain, ContextDefinition from .converters import ScaleConverter from .definitions import ( AliasDefinition, Definition, DimensionDefinition, PrefixDefinition, UnitDefinition, ) from .errors import ( DefinitionSyntaxError, DimensionalityError, RedefinitionError, UndefinedUnitError, ) from .pint_eval import build_eval_tree from .systems import Group, GroupDefinition, System, SystemDefinition from .util import ( ParserHelper, SourceIterator, UnitsContainer, _is_dim, find_connected_nodes, find_shortest_path, getattr_maybe_raise, logger, pi_theorem, solve_dependencies, string_preprocessor, to_units_container, ) if TYPE_CHECKING: from ._typing import UnitLike from .quantity import Quantity from .unit import Unit from .unit import UnitsContainer as UnitsContainerT if HAS_BABEL: import babel Locale = babel.Locale else: Locale = None T = TypeVar("T") _BLOCK_RE = re.compile(r"[ (]") @dataclass(frozen=True) class DefaultsDefinition: """Definition for the @default directive""" content: Tuple[Tuple[str, str], ...] @classmethod def from_lines(cls, lines, non_int_type=float) -> DefaultsDefinition: source_iterator = SourceIterator(lines) next(source_iterator) out = [] for lineno, part in source_iterator: k, v = part.split("=") out.append((k.strip(), v.strip())) return DefaultsDefinition(tuple(out)) @functools.lru_cache() def pattern_to_regex(pattern): if hasattr(pattern, "finditer"): 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) 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, **kwargs): obj = super().__call__(*args, **kwargs) obj._after_init() return obj class RegistryCache: """Cache to speed up unit registries""" def __init__(self) -> None: #: Maps dimensionality (UnitsContainer) to Units (str) self.dimensional_equivalents: Dict[UnitsContainer, Set[str]] = {} #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) self.root_units = {} #: 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] = {} def __eq__(self, other): if not isinstance(other, self.__class__): return False attrs = ( "dimensional_equivalents", "root_units", "dimensionality", "parse_unit", ) return all(getattr(self, attr) == getattr(other, attr) for attr in attrs) class ContextCacheOverlay: """Layer on top of the base UnitRegistry cache, specific to a combination of active contexts which contain unit redefinitions. """ def __init__(self, registry_cache: RegistryCache) -> None: self.dimensional_equivalents = registry_cache.dimensional_equivalents self.root_units = {} self.dimensionality = registry_cache.dimensionality self.parse_unit = registry_cache.parse_unit NON_INT_TYPE = Type[Union[float, Decimal, Fraction]] PreprocessorType = Callable[[str], str] class BaseRegistry(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. 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. """ #: Babel.Locale instance or None fmt_locale: Optional[Locale] = None _diskcache = None def __init__( self, filename="", force_ndarray: bool = False, force_ndarray_like: bool = False, on_redefinition: str = "warn", auto_reduce_dimensions: bool = False, preprocessors: Optional[List[PreprocessorType]] = None, fmt_locale: Optional[str] = None, non_int_type: NON_INT_TYPE = float, case_sensitive: bool = True, cache_folder: Union[str, pathlib.Path, None] = None, separate_format_defaults: Optional[bool] = None, ): #: Map context prefix to (loader function, parser function, single_line) #: type: Dict[str, Tuple[Callable[[Any], None]], Any] self._directives = {} self._register_directives() self._init_dynamic_classes() if cache_folder == ":auto:": cache_folder = appdirs.user_cache_dir(appname="pint", appauthor=False) if cache_folder is not None: self._diskcache = parser.build_disk_cache_class(non_int_type)(cache_folder) self._filename = filename self.force_ndarray = force_ndarray self.force_ndarray_like = force_ndarray_like self.preprocessors = preprocessors or [] #: 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 #: Default locale identifier string, used when calling format_babel without explicit locale. self.set_fmt_locale(fmt_locale) #: 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] = {} #: Map unit name (string) to its definition (UnitDefinition). #: Might contain prefixed units. self._units: Dict[str, UnitDefinition] = {} #: 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""" from .unit import build_unit_class self.Unit = build_unit_class(self) from .quantity import build_quantity_class self.Quantity: Type["Quantity"] = build_quantity_class(self) from .measurement import build_measurement_class self.Measurement = build_measurement_class(self) def _after_init(self) -> None: """This should be called after all __init__""" if self._filename == "": loaded_files = self.load_definitions("default_en.txt", 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_directives(self) -> None: self._register_directive("@alias", self._load_alias, AliasDefinition) self._register_directive("@defaults", self._load_defaults, DefaultsDefinition) def _load_defaults(self, defaults_definition: DefaultsDefinition) -> None: """Loader for a @default section.""" for k, v in defaults_definition.content: self._defaults[k] = v def _load_alias(self, alias_definition: AliasDefinition) -> None: """Loader for an @alias directive""" self._define_alias(alias_definition) def __deepcopy__(self, memo) -> "BaseRegistry": new = object.__new__(type(self)) new.__dict__ = copy.deepcopy(self.__dict__, memo) new._init_dynamic_classes() return new def __getattr__(self, item): getattr_maybe_raise(self, item) return self.Unit(item) def __getitem__(self, item): 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) -> 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())) def set_fmt_locale(self, loc: Optional[str]) -> 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.fmt_locale = loc def UnitsContainer(self, *args, **kwargs) -> UnitsContainerT: return UnitsContainer(*args, non_int_type=self.non_int_type, **kwargs) @property def default_format(self) -> str: """Default formatting string for quantities.""" return self.Quantity.default_format @default_format.setter def default_format(self, value: str): self.Unit.default_format = value self.Quantity.default_format = value self.Measurement.default_format = value @property def cache_folder(self) -> Optional[pathlib.Path]: if self._diskcache: return self._diskcache.cache_folder return None def define(self, definition: Union[str, Definition]) -> None: """Add unit to the registry. Parameters ---------- definition : str or Definition a dimension, unit or prefix definition. """ if isinstance(definition, str): for line in definition.split("\n"): if line.startswith("@alias"): # TODO why alias can be defined like this but not other directives? self._define_alias( AliasDefinition.from_string(line, self.non_int_type) ) else: self._define(Definition.from_string(line, self.non_int_type)) else: self._define(definition) def _define(self, definition: Definition) -> Tuple[Definition, dict, dict]: """Add unit to the registry. This method defines only multiplicative units, converting any other type to `delta_` units. Parameters ---------- definition : Definition a dimension, unit or prefix definition. Returns ------- Definition, dict, dict Definition instance, case sensitive unit dict, case insensitive unit dict. """ if isinstance(definition, DimensionDefinition): d, di = self._dimensions, None elif isinstance(definition, UnitDefinition): d, di = self._units, self._units_casei # For a base units, we need to define the related dimension # (making sure there is only one to define) if definition.is_base: for dimension in definition.reference.keys(): if dimension in self._dimensions: if dimension != "[]": raise DefinitionSyntaxError( "Only one unit per dimension can be a base unit" ) continue self.define( DimensionDefinition(dimension, "", (), None, None, True) ) elif isinstance(definition, PrefixDefinition): d, di = self._prefixes, None else: raise TypeError("{} is not a valid definition.".format(definition)) # define "delta_" units for units with an offset if getattr(definition.converter, "offset", 0) != 0: if definition.name.startswith("["): d_name = "[delta_" + definition.name[1:] else: d_name = "delta_" + definition.name if definition.symbol: d_symbol = "Δ" + definition.symbol else: d_symbol = None d_aliases = tuple("Δ" + alias for alias in definition.aliases) + tuple( "delta_" + alias for alias in definition.aliases ) d_reference = self.UnitsContainer( {ref: value for ref, value in definition.reference.items()} ) d_def = UnitDefinition( d_name, d_symbol, d_aliases, ScaleConverter(definition.converter.scale), d_reference, definition.is_base, ) else: d_def = definition self._define_adder(d_def, d, di) return definition, d, di def _define_adder(self, definition, unit_dict, casei_unit_dict): """Helper function to store a definition in the internal dictionaries. It stores the definition under its name, symbol and aliases. """ self._define_single_adder( definition.name, definition, unit_dict, casei_unit_dict ) if definition.has_symbol: self._define_single_adder( definition.symbol, definition, unit_dict, casei_unit_dict ) for alias in definition.aliases: if " " in alias: logger.warn("Alias cannot contain a space: " + alias) self._define_single_adder(alias, definition, unit_dict, casei_unit_dict) def _define_single_adder(self, key, value, unit_dict, casei_unit_dict): """Helper function to store a definition in the internal dictionaries. It warns or raise error on redefinition. """ if key in unit_dict: if self._on_redefinition == "raise": raise RedefinitionError(key, type(value)) elif self._on_redefinition == "warn": logger.warning("Redefining '%s' (%s)" % (key, type(value))) unit_dict[key] = value if casei_unit_dict is not None: casei_unit_dict[key.lower()].add(key) def _define_alias(self, definition): unit_dict, casei_unit_dict = self._units, self._units_casei unit = unit_dict[definition.name] while not isinstance(unit, UnitDefinition): unit = unit_dict[unit.name] for alias in definition.aliases: unit_dict[alias] = unit casei_unit_dict[alias.lower()].add(alias) def _register_directive(self, prefix: str, loaderfunc, definition_class): """Register a loader for a given @ directive. Parameters ---------- prefix string identifying the section (e.g. @context). loaderfunc function to load the definition into the registry. definition_class a class that represents the directive content. """ if prefix and prefix[0] == "@": self._directives[prefix] = (loaderfunc, definition_class) else: raise ValueError("Prefix directives must start with '@'") def load_definitions(self, file, 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) """ loaders = { AliasDefinition: self._define, UnitDefinition: self._define, DimensionDefinition: self._define, PrefixDefinition: self._define, } p = parser.Parser(self.non_int_type, cache_folder=self._diskcache) for prefix, (loaderfunc, definition_class) in self._directives.items(): loaders[definition_class] = loaderfunc p.register_class(prefix, definition_class) if isinstance(file, (str, pathlib.Path)): try: parsed_files = p.parse(file, is_resource) except Exception as ex: # TODO: Change this is in the future # this is kept for backwards compatibility msg = getattr(ex, "message", "") or str(ex) raise ValueError("While opening {}\n{}".format(file, msg)) else: parsed_files = parser.DefinitionFiles([p.parse_lines(file)]) for lineno, definition in parsed_files.iter_definitions(): if definition.__class__ in p.handled_classes: continue loaderfunc = loaders.get(definition.__class__, None) if not loaderfunc: raise ValueError( f"No loader function defined " f"for {definition.__class__.__name__}" ) loaderfunc(definition) return parsed_files def _build_cache(self, loaded_files=None) -> None: """Build a cache of dimensionality and base units.""" if loaded_files and self._diskcache and all(loaded_files): cache, cache_basename = self._diskcache.load(loaded_files, "build_cache") if cache is None: self._build_cache() self._diskcache.save(self._cache, loaded_files, "build_cache") return self._cache = RegistryCache() deps = { name: 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: Optional[bool] = 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) elif len(candidates) == 1: prefix, unit_name, _ = candidates[0] else: logger.warning( "Parsing {} yield multiple results. " "Options are: {}".format(name_or_alias, candidates) ) prefix, unit_name, _ = candidates[0] if prefix: name = prefix + unit_name symbol = self.get_symbol(name, case_sensitive) prefix_def = self._prefixes[prefix] self._units[name] = UnitDefinition( name, symbol, (), 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: Optional[bool] = 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) elif len(candidates) == 1: prefix, unit_name, _ = candidates[0] else: logger.warning( "Parsing {0} yield multiple results. " "Options are: {1!r}".format(name_or_alias, candidates) ) prefix, unit_name, _ = candidates[0] 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) -> UnitsContainerT: """Convert unit or dict of units or dimensions to a dict of base 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: Optional[UnitsContainerT] ) -> UnitsContainerT: """Convert a UnitsContainer to base dimensions.""" if not input_units: return self.UnitsContainer() cache = self._cache.dimensionality try: return cache[input_units] except KeyError: pass accumulator = 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, exp, accumulator): for key in ref: exp2 = exp * ref[key] if _is_dim(key): reg = self._dimensions[key] if reg.is_base: accumulator[key] += exp2 elif reg.reference is not None: self._get_dimensionality_recurse(reg.reference, exp2, accumulator) 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, unit2): """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[Number, Unit]: """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, base 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_root_units(self, input_units, check_nonmult=True): """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, base units """ if not input_units: return 1, self.UnitsContainer() cache = self._cache.root_units try: return cache[input_units] except KeyError: pass accumulators = [1, defaultdict(int)] self._get_root_units_recurse(input_units, 1, accumulators) factor = accumulators[0] units = self.UnitsContainer( {k: v for k, v in accumulators[1].items() if 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, check_nonmult=True, system=None): """Convert unit or dict of units to the base 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, base units """ return self.get_root_units(input_units, check_nonmult) def _get_root_units_recurse(self, ref, exp, accumulators): for key in ref: exp2 = exp * ref[key] key = self.get_name(key) reg = self._units[key] if reg.is_base: accumulators[1][key] += exp2 else: accumulators[0] *= 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, group_or_system=None ) -> FrozenSet["Unit"]: """ """ 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, group_or_system): """ """ if not input_units: return frozenset() src_dim = self._get_dimensionality(input_units) return self._cache.dimensional_equivalents[src_dim] def is_compatible_with( self, obj1: Any, obj2: Any, *contexts: Union[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, src, dst, inplace=False, check_dimensionality=True): """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 """ if check_dimensionality: 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) # Here src and dst have only multiplicative units left. Thus we can # convert with a factor. factor, _ = self._get_root_units(src / dst) # 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: Optional[bool] = 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) """ return self._dedup_candidates( self._parse_unit_name(unit_name, case_sensitive=case_sensitive) ) def _parse_unit_name( self, unit_name: str, case_sensitive: Optional[bool] = None ) -> Iterator[Tuple[str, str, str]]: """Helper of parse_unit_name.""" case_sensitive = ( self.case_sensitive if case_sensitive is None else case_sensitive ) 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], ) @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: Optional[bool] = None, case_sensitive: Optional[bool] = None, ) -> Unit: """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 """ for p in self.preprocessors: input_string = p(input_string) units = self._parse_units(input_string, as_delta, case_sensitive) return self.Unit(units) def _parse_units( self, input_string: str, as_delta: bool = True, case_sensitive: Optional[bool] = None, ) -> UnitsContainerT: """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] 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, case_sensitive=None, use_decimal=False, **values): # TODO: remove this code when use_decimal is deprecated if use_decimal: raise DeprecationWarning( "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n" ">>> from decimal import Decimal\n" ">>> ureg = UnitRegistry(non_int_type=Decimal)" ) token_type = token[0] token_text = token[1] if token_type == NAME: if token_text == "dimensionless": return 1 * self.dimensionless elif token_text.lower() in ("inf", "infinity"): return float("inf") elif token_text.lower() == "nan": return float("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: Optional[bool] = None, use_decimal: bool = False, many: bool = False, ) -> Union[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 : (Default value = None, which uses registry setting) use_decimal : (Default value = False) many : Match many results (Default value = False) Returns ------- """ if not input_string: return [] if many else None # Parse string pattern = pattern_to_regex(pattern) matched = re.finditer(pattern, input_string) # Extract result(s) results = [] for match in matched: # Extract units from result match = match.groupdict() # Parse units units = [] for unit, value in match.items(): # Construct measure by multiplying value by unit units.append( float(value) * self.parse_expression(unit, case_sensitive, use_decimal) ) # Add to results results.append(units) # Return first match only if not many: return results[0] return results def parse_expression( self, input_string: str, case_sensitive: Optional[bool] = None, use_decimal: bool = False, **values, ) -> Quantity: """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 : (Default value = None, which uses registry setting) use_decimal : (Default value = False) **values : Returns ------- """ # TODO: remove this code when use_decimal is deprecated if use_decimal: raise DeprecationWarning( "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n" ">>> from decimal import Decimal\n" ">>> ureg = UnitRegistry(non_int_type=Decimal)" ) 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 = tokenizer(input_string) return build_eval_tree(gen).evaluate( lambda x: self._eval_token(x, case_sensitive=case_sensitive, **values) ) __call__ = parse_expression class NonMultiplicativeRegistry(BaseRegistry): """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 base 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 # base units on multiplication and division. self.autoconvert_offset_to_baseunit = autoconvert_offset_to_baseunit def _parse_units( self, input_string: str, as_delta: Optional[bool] = None, case_sensitive: Optional[bool] = None, ): """ """ if as_delta is None: as_delta = self.default_as_delta return super()._parse_units(input_string, as_delta, case_sensitive) def _define(self, definition: Union[str, Definition]): """Add unit to the registry. In addition to what is done by the BaseRegistry, registers also non-multiplicative units. Parameters ---------- definition : str or Definition A dimension, unit or prefix definition. Returns ------- Definition, dict, dict Definition instance, case sensitive unit dict, case insensitive unit dict. """ definition, d, di = super()._define(definition) # define additional units for units with an offset if getattr(definition.converter, "offset", 0) != 0: self._define_adder(definition, d, di) return definition, d, di def _is_multiplicative(self, u) -> bool: if u in self._units: return self._units[u].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(u) assert len(names) == 1 _, base_name, _ = names[0] try: return self._units[base_name].is_multiplicative except KeyError: raise UndefinedUnitError(u) def _validate_and_extract(self, units): # 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, all_units): slct_unit = self._units[offset_unit] if slct_unit.is_logarithmic or (not slct_unit.is_multiplicative): # Extract reference unit slct_ref = slct_unit.reference # 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) # Otherwise, return the units unmodified return all_units def _convert(self, value, src, dst, inplace=False): """Convert value from some source to destination units. In addition to what is done by the BaseRegistry, 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}" ) 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: 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: 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 ContextRegistry(BaseRegistry): """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. """ def __init__(self, **kwargs: Any) -> None: # Map context name (string) or abbreviation to context. self._contexts: Dict[str, Context] = {} # Stores active contexts. self._active_ctx = 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(self._units) def _register_directives(self) -> None: super()._register_directives() self._register_directive("@context", self._load_context, ContextDefinition) def _load_context(self, cd: ContextDefinition) -> None: try: self.add_context(Context.from_definition(cd, self.get_dimensionality)) except KeyError as e: raise DefinitionSyntaxError(f"unknown dimension {e} in context") def add_context(self, context: Context) -> 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 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) -> 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 base if basedef.is_base: raise ValueError("Can't redefine a base 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, is_base=False, 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: Union[str, Context], **kwargs ) -> 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 base 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(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: """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, *names, **kwargs) -> ContextManager[Context]: """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 >>> ureg = pint.UnitRegistry() >>> ureg.add_context(pint.Context('one')) >>> ureg.add_context(pint.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, **kwargs) -> 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. Example ------- >>> @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, src, dst, inplace=False): """Convert value from some source to destination units. In addition to what is done by the BaseRegistry, 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, group_or_system): 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 SystemRegistry(BaseRegistry): """Handle of Systems and Groups. 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 and groups. - List systems - Get or get the default system. - Parse @system and @group directive. """ def __init__(self, system=None, **kwargs): super().__init__(**kwargs) #: Map system name to system. #: :type: dict[ str | System] self._systems: Dict[str, System] = {} #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) self._base_units_cache = dict() #: Map group name to group. #: :type: dict[ str | Group] self._groups: Dict[str, Group] = {} self._groups["root"] = self.Group("root") self._default_system = system def _init_dynamic_classes(self) -> None: super()._init_dynamic_classes() self.Group = systems.build_group_class(self) self.System = systems.build_system_class(self) 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)) #: System name to be used by default. self._default_system = self._default_system or self._defaults.get( "system", None ) def _register_directives(self) -> None: super()._register_directives() self._register_directive( "@system", lambda gd: self.System.from_definition(gd, self.get_root_units), SystemDefinition, ) self._register_directive( "@group", lambda gd: self.Group.from_definition(gd, self.define), GroupDefinition, ) def get_group(self, name: str, create_if_needed: bool = True) -> 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 ------- type 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) @property def sys(self): return systems.Lister(self._systems) @property def default_system(self) -> System: return self._default_system @default_system.setter def default_system(self, name): if name: if name not in self._systems: raise ValueError("Unknown system %s" % name) self._base_units_cache = {} self._default_system = name def get_system(self, name: str, create_if_needed: bool = True) -> 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 _define(self, definition): # In addition to the what is done by the BaseRegistry, # this adds all units to the `root` group. definition, d, di = super()._define(definition) if isinstance(definition, UnitDefinition): # We add all units to the root group self.get_group("root").add_units(definition.name) return definition, d, di def get_base_units( self, input_units: Union[UnitLike, Quantity], check_nonmult: bool = True, system: Union[str, System, None] = None, ) -> Tuple[Number, Unit]: """Convert unit or dict of units to the base units. If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. Unlike BaseRegistry, 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, base 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: Union[str, System, None] = None, ): if system is None: system = self._default_system # The cache is only done for check_nonmult=True and the current system. if ( check_nonmult and system == self._default_system 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, group_or_system) -> FrozenSet[Unit]: if group_or_system is None: group_or_system = self._default_system ret = super()._get_compatible_units(input_units, group_or_system) if group_or_system: if group_or_system in self._systems: members = self._systems[group_or_system].members elif group_or_system in self._groups: members = self._groups[group_or_system].members else: raise ValueError( "Unknown Group o System with name '%s'" % group_or_system ) return frozenset(ret & members) return ret class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry): """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 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. default_as_delta : In the context of a multiplication of units, interpret non-multiplicative units as their *delta* counterparts. autoconvert_offset_to_baseunit : If True converts offset units in quantities are converted to their base units in multiplicative context. If False no conversion happens. 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. preprocessors : list of callables which are iteratively ran on any input expression or unit string fmt_locale : locale identifier string, used in `format_babel`. Default to 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) """ 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, 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, 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: 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.19.2/pint/registry_helpers.py000066400000000000000000000275631422760043000175060ustar00rootroot00000000000000""" 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. """ import functools from inspect import signature from itertools import zip_longest from typing import TYPE_CHECKING, Callable, Iterable, TypeVar, Union from ._typing import F from .errors import DimensionalityError from .quantity import Quantity from .util import UnitsContainer, to_units_container if TYPE_CHECKING: from .registry import UnitRegistry from .unit import Unit 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, values, strict): new_values = list(value for value in values) 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 new_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 new_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): new_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]) new_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], new_values[ndx] ) ) return new_values, values_by_name return _converter def _apply_defaults(func, 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. """ sig = signature(func) bound_arguments = sig.bind(*args, **kwargs) for param in sig.parameters.values(): if param.name not in bound_arguments.arguments: bound_arguments.arguments[param.name] = param.default args = [bound_arguments.arguments[key] for key in sig.parameters.keys()] return args, {} def wraps( ureg: "UnitRegistry", ret: Union[str, "Unit", Iterable[Union[str, "Unit", None]], None], args: Union[str, "Unit", Iterable[Union[str, "Unit", None]], None], strict: bool = True, ) -> Callable[[Callable[..., T]], Callable[..., Quantity[T]]]: """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[..., T]) -> Callable[..., Quantity[T]]: count_params = len(signature(func).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[T]: values, kw = _apply_defaults(func, values, kw) # In principle, the values are used as is # When then extract the magnitudes when needed. new_values, values_by_name = converter(ureg, values, strict) result = func(*new_values, **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: Union[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): count_params = len(signature(func).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, empty = _apply_defaults(func, args, kwargs) 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.19.2/pint/systems.py000066400000000000000000000374561422760043000156250ustar00rootroot00000000000000""" pint.systems ~~~~~~~~~~~~ Functions and classes related to system 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 re from dataclasses import dataclass from typing import Tuple from .babel_names import _babel_systems from .compat import babel_parse from .definitions import Definition, UnitDefinition from .errors import DefinitionSyntaxError, RedefinitionError from .util import ( SharedRegistryObject, SourceIterator, getattr_maybe_raise, logger, to_units_container, ) @dataclass(frozen=True) class GroupDefinition: """Definition of a group. @group [using , ..., ] ... @end Example:: @group AvoirdupoisUS using Avoirdupois US_hundredweight = hundredweight = US_cwt US_ton = ton US_force_ton = force_ton = _ = US_ton_force @end """ #: Regex to match the header parts of a definition. _header_re = re.compile(r"@group\s+(?P\w+)\s*(using\s(?P.*))*") name: str units: Tuple[Tuple[int, UnitDefinition], ...] using_group_names: Tuple[str, ...] @property def unit_names(self) -> Tuple[str, ...]: return tuple(u.name for lineno, u in self.units) @classmethod def from_lines(cls, lines, non_int_type=float): """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 ------- """ lines = SourceIterator(lines) lineno, header = next(lines) r = cls._header_re.search(header) if r is None: raise ValueError("Invalid Group header syntax: '%s'" % header) 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 = () units = [] for lineno, line in lines: definition = Definition.from_string(line, non_int_type=non_int_type) if not isinstance(definition, UnitDefinition): raise DefinitionSyntaxError( "Only UnitDefinition are valid inside _used_groups, not " + str(definition), lineno=lineno, ) units.append((lineno, definition)) return cls(name, tuple(units), parent_group_names) 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. """ def __init__(self, name): """ :param name: Name of the group. If not given, a root Group will be created. :type name: str :param groups: dictionary like object groups and system. The newly created group will be added after creation. :type groups: dict[str | Group] """ # The name of the group. #: type: str self.name = name #: Names of the units in this group. #: :type: set[str] self._unit_names = set() #: Names of the groups in this group. #: :type: set[str] self._used_groups = set() #: Names of the groups in which this group is contained. #: :type: set[str] self._used_by = 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. #: :type: frozenset[str] | None self._computed_members = None @property def members(self): """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: self._computed_members = set(self._unit_names) for _, group in self.iter_used_groups(): self._computed_members |= group.members self._computed_members = frozenset(self._computed_members) return self._computed_members def invalidate_members(self): """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): 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): for name, _ in self.iter_used_groups(): if name == group_name: return True return False def add_units(self, *unit_names): """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): return frozenset(self._unit_names) def remove_units(self, *unit_names): """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): """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): """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, define_func, non_int_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) return cls.from_definition(group_definition, define_func) @classmethod def from_definition(cls, group_definition: GroupDefinition, define_func) -> Group: for lineno, definition in group_definition.units: try: define_func(definition) except (RedefinitionError, DefinitionSyntaxError) as ex: if ex.lineno is None: ex.lineno = lineno raise ex grp = cls(group_definition.name) grp.add_units(*(unit.name for lineno, unit in group_definition.units)) if group_definition.using_group_names: grp.add_groups(*group_definition.using_group_names) return grp def __getattr__(self, item): getattr_maybe_raise(self, item) return self._REGISTRY @dataclass(frozen=True) class SystemDefinition: """Definition of a System: @system [using , ..., ] ... @end 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. """ #: Regex to match the header parts of a context. _header_re = re.compile(r"@system\s+(?P\w+)\s*(using\s(?P.*))*") name: str unit_replacements: Tuple[Tuple[int, str, str], ...] using_group_names: Tuple[str, ...] @classmethod def from_lines(cls, lines, non_int_type=float): lines = SourceIterator(lines) lineno, header = next(lines) r = cls._header_re.search(header) if r is None: raise ValueError("Invalid System header syntax '%s'" % header) 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",) unit_replacements = [] for lineno, line in lines: line = line.strip() # We would identify a # - old_unit: a root unit part which is going to be removed from the system. # - new_unit: a non root unit which is going to replace the old_unit. if ":" in line: # The syntax is new_unit:old_unit new_unit, old_unit = line.split(":") new_unit, old_unit = new_unit.strip(), old_unit.strip() unit_replacements.append((lineno, new_unit, old_unit)) else: # The syntax is new_unit # old_unit is inferred as the root unit with the same dimensionality. unit_replacements.append((lineno, line, None)) return cls(name, tuple(unit_replacements), group_names) class System(SharedRegistryObject): """A system is a Group plus a set of base 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. """ def __init__(self, name): """ :param name: Name of the group :type 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. #: :type: dict[str, dict[str, number]]] self.base_units = {} #: Derived unit names. #: :type: set(str) self.derived_units = set() #: Names of the _used_groups in used by this system. #: :type: set(str) self._used_groups = set() #: :type: frozenset | None self._computed_members = 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): 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: self._computed_members = set() for group_name in self._used_groups: try: self._computed_members |= d[group_name].members except KeyError: logger.warning( "Could not resolve {} in System {}".format( group_name, self.name ) ) self._computed_members = frozenset(self._computed_members) 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): """Add groups to group.""" self._used_groups |= set(group_names) self.invalidate_members() def remove_groups(self, *group_names): """Remove groups from group.""" self._used_groups -= set(group_names) self.invalidate_members() def format_babel(self, locale): """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 @classmethod def from_lines(cls, lines, get_root_func, non_int_type=float): system_definition = SystemDefinition.from_lines(lines, get_root_func) return cls.from_definition(system_definition, get_root_func) @classmethod def from_definition(cls, system_definition: SystemDefinition, get_root_func): base_unit_names = {} derived_unit_names = [] for lineno, 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 base 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( "In `%s`, the unit at the right of the `:` (%s) must be a root unit." % (lineno, old_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): self.d = d def __dir__(self): return list(self.d.keys()) def __getattr__(self, item): getattr_maybe_raise(self, item) return self.d[item] _Group = Group _System = System def build_group_class(registry): class Group(_Group): _REGISTRY = registry return Group def build_system_class(registry): class System(_System): _REGISTRY = registry return System pint-0.19.2/pint/testing.py000066400000000000000000000055561422760043000155670ustar00rootroot00000000000000import math import warnings from numbers import Number from .compat import ndarray from .quantity import Quantity 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=None): if msg is None: msg = "Comparing %r and %r. " % (first, second) m1, m2 = _get_comparable_magnitudes(first, second, msg) msg += " (Converted to %r and %r): Magnitudes are not equal" % (m1, m2) 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(RuntimeWarning) return elif not isinstance(m2, Number): warnings.warn(RuntimeWarning) 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=1e-07, atol=0, msg=None): if msg is None: try: msg = "Comparing %r and %r. " % (first, second) except TypeError: try: msg = "Comparing %s and %s. " % (first, second) except Exception: msg = "Comparing" m1, m2 = _get_comparable_magnitudes(first, second, msg) msg += " (Converted to %r and %r)" % (m1, m2) 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(RuntimeWarning) return elif not isinstance(m2, Number): warnings.warn(RuntimeWarning) 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.19.2/pint/testsuite/000077500000000000000000000000001422760043000155565ustar00rootroot00000000000000pint-0.19.2/pint/testsuite/__init__.py000066400000000000000000000052321422760043000176710ustar00rootroot00000000000000import doctest import math import os import unittest import warnings from contextlib import contextmanager 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 @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: try: import serialize # noqa: F401 import yaml # noqa: F401 add_docs(suite) except ImportError: pass 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 os.path.exists(docpath): 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.19.2/pint/testsuite/baseline/000077500000000000000000000000001422760043000173405ustar00rootroot00000000000000pint-0.19.2/pint/testsuite/baseline/test_basic_plot.png000066400000000000000000000421131422760043000232250ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i8tEXtSoftwarematplotlib version3.1.2, http://matplotlib.org/%J IDATxT (80P=PY>w0- ZZKw;{IâKOMu9\UTT]Cc6+Q׏휝 r]^=_},˲`3=@ @x cC <1!@x cC <1!@x cC <1!@x cC <1!@x cC <1!@x cC <1!@x cC <1!@xL566꣏>҈#ԯ_?sT[[;S111$ċ|G?MI1b3f1e˖ig5hk`z<ۿ՜n/?V%m>ċˮƌ4k s š?HɩJN6=w`,RсSZ?5ƛ49~XK\. uL*i~p 9nz#@STn]Һ9J3=ɯ _8ԠG9*͛#MO;`Pvv MOX|.[/g-9-. e(sTI~@,K5~x^0ӳҊzbFBlg 3)MiCLO 8BK=͡i{d.D]rZݝ:TR`S~BUyY=V 29b0~iF-)Pͦ3?$,K9_2!{b"BM_ -փQuZySGid+oB/T׷(\MNMK5= AkSL 1ڼpMO7 @:\z*?{nRXHY|RZ\hVJя& 7= W}yB*ə'  n ݞ8XSTgN-ZGjٴDO-G^Q׬.vvi4%qIJeY*:pJ;1C!YR[K+mu1\+g%+4#v}sɩ7~usRgzz괖oTtx_MR' ].Fٸ AT׷(\眝zc-kz QR^K?ȘvG/D_r鹲*mR{nRXHYRZZ\hVJ[q.OgR*ə' ҆Oj5=q}Mς @p;;R{jjٴDO-G\Ю]MIlz|oeYҪi̐zu~ngz|o3^õrVB9bWe6:SP&IUVZIɎC ztAEGUi$%]izr#zmQ96VkKUdXY#$Ig[;B7IzhJw5G#@紸.[*\QѦ'O ̲,mW>Qze^b#Lς#@K+J*s'GI ?G/δ*6m71= 0e/bCCi{d.$ @\rzsqN5Y)  :$k?sWpIݛIa!Agqu/*ЮV)ʞ0#vY=vk[nn2= Vr-mF/\륹տYw"@|ygWjOY-e]8ꚕ[`Kt ,RсSZ!2@g(nPӳn#@\[Kϔ~{g Y ]&6:SP&IUVZI5!@ΪZR}U7IIG\3tzqHկ*2,,G ^lkUh&=9#IMI+ZlDzmh紸.[*\QѦ'"} .Z?/~z<IJ,mW>Qze^b#L xnWzzE0ѥ%9hH Y@!@ Lr onyiyӓ^GPRos膨~ڞ?Y0= .zkoNY) -Z?w5= jU8J9e?ul |'u-bg6?8AS  YC3d^ uL*i~p`NɩusRgzoP}砢4o4= ietzq*2,, lkUh&=9#IMIP> 8ŅvR e6= +r}zjW)62, 9;ġ2G~2y Y_"@@@Lr onyiyӓFURoshhT?mϟE=K.~a[٩C&+E᡼-<4P.+]jv8#v"@@wQK*lL2= 8{e7iG jzZ/ѭ>Jql\rBU]ߢ܂r59; nXӓGTR^K?Ș-pӓ~˥g߯Җ'uoz&7_B׷juV~t0 ®?J ֶLez @Os-mF/\륹տY|ygWjOY-e] '<լŅv9; -qI|eY*:pJ;1C oz+D3^õrVB9b% NɩusRgz@P}b"BU7IIG* kuzq*2,,׀^lkU@9=uW~jx紸.[*\}e$D kXMj?3^?\?`pepj)(WmS*+-$>Wdǡ=AEUi$%]izDorŝGڮslޗȰӳ(|Ư:Bkғ3Дj OB\npQ2GE k,Ҧ}zj)62,~ҊzbFBlg#$}qU9v7i4ͼyITR+94$O"LO%[?<7ԡZP=<.+]jv8#v:hU(fSÙJdzAxe˖i_{,;;[نeYz}1 jzTTT.\`h @땖fzϴ_c[jGiMoTKorovZ@u}r  nѴXӓ(?WR^K?Ș-pӓ0?sWpIݛIa!Agp~E-.pCdh8bW @3tFK+Jݿ <Oݖ6|R?mq,?p٩ŕSsV˦%*h8b"@qfuK)MOoD,RсSZ!2@ QLoE:]zs4?cVJVh0G~>ѩr69nNLO+FCvjУTtx_MR'@ .[/Qze^b#LkFe]ZQP^? mВ ?ޤ'g$) w(?qN rEmz,o`Y6 Tk(2/MagO#@ gGV8Tע#bFBlg#@_iUN]m8/M3obz SRos膨~ڞ?Y0= .Vw[SjuVCy3+ 5\hWuZ5;YLK@@wQK*l2?$k Y>VFB6dWLDY@iiGΪʛ:J˧ߨ \' R]ߢ܂r59;Ƃ[4-9$( `ϔ '(>:$8utUڲK/Ia!Ag@@"@~E-.pCdh8b "@~kz E$gR҆Oj5=q}MN--ԞZ6-QSG5p5+]MIlz gYҪi̐x7= puL*i~p`oE|VmSvoJ*+-$w @>iǡ=AEUi$%]iz  rŝGڮ#9VȰӳWƯ:Bkғ3Дj>紸.[*\QѦ'_֦?2=eYzkq}S _,L|#@effj$w5K?+гW遉#TPb#L\>qjooc=&hӦMW_h4 ^LM?u]wB?>JC9z$I'kC /%n;#Itu/\rzsqN5Y)  K.]cgΜр<vUyYf'끉#8b`uӭުǶmۦL/ǣM|ԏ'$>Onz'tw?,X>}hz7m60=|eYz}1 jz 4m4m޼YK.%ݨ(mڴI'O6|CK%=vTVQZ>F;*̟?_o}ӊѤInz٩7ܢiɱ'<Jӿ˿>]{g7o  WɓÇ%:\z*?{7e IDAT=7),$, W`Ĉrzq WhWuCdh8 r|M^(*>rV]`LTJ@ӓ W? snK>Z?w5= `⅖-[leggZs٩ŕSsV˦%*h8b)**RQQpႡ5o5sLEGG뷿w \?oJKKkVn]; 8$*nWzzE?駟*::;/ӧ5"˲TtVwHcF3uCT?ӳ^Ǐא!CktUbZ9+Y g_Fr :IUVZI/F\Rĉjoo飃ZP}b"BU7IIGrH7]V+V5zhrŝGڮslޗȰӳ>W_}U .믿 oxζvhIQԞSw%?Hf+FtSSS͛G|H'iq].TH6= clI& (˲`dpU H7KaÆ__շo_ӓW9;ġ2GM3WѣGkڴi>}_x>}tCg}qU9v7tWӓ>\ƍ1c~ Q&kӓ~M6iŊZz)+:ZzkoNY) 3xE&˥ӧBUyY=V 29bУ;Ч~jz}G5?ηL=0qqV\s*<<\3g5{ eYcZae$DkCxDSH7J/_˗_׸\.ON~In=U7uOQA6~=H7?~EjrvhZrI@tӪULOkVR^K?SBL6/pӓsWpIݗ_sBLDZ\hVJ[qI)#oW("4X%97$@"@ݖ6|R?mq,@#@OwvjiqԜղiʟ:Z6Fr5+.gg6=8A%6= I˲TtVwHc P,?3^õrVB9b]Nu+8ӓ,|܎C zҼIJ>$>֋;]GuGr~='Ua!gA_uhIQ>=֤'g$) 1'iq].T(CMO #,Ҧ}zj)62,ҊZ8i+I!A6ӳ6gZS`W}s6K̛U#@9Ԋm 5ӓ&xK.VxXo=٩C&+E:$zsWpIݛIa!AgxЙv=QGnh՚ͽuG HqJhzG@/s-mRtc﹓տUA@/:Jɓ?Rf%f3=Ykֳw$G@\rukT̀P-W,Am.=SJu1\+g%+48HL/; Cj-xWZ7'UYiq'u;NkJD4o4= D5r#zmQ96VkKUdXYx-RWZRT驻$pWsW9-. }_ Ѧ'˲i_^ZGyi 3= ArvtiECez-zi8Ekz;;R{jjٴDO-G`9ꚕ[`K@@#@%˲TtVwHc P,uL*i~p` JmS9mrjݜTeřo8ԠG9*͛#MO\n^uTwRbz O;ڡ%E:P{NOݕ /l2 3ggg+;;";8ŅvR+#!$)**RQQpႡ5BׯWZZײ,K5~x^0ӳ~rJOO7? |K+J*skZ1#I!A6ӳ"@/δ*6m71= t'9!O"LOW:ZzkoNY)  _ū8p]y[:xY=V 29bGJ6jIQm6?A'@*e7iG jz!~Im=U7uOQA6. Bu}r  nѴXӓ@/ @WR^K?Ș-pӓ@/!@sWpIݛIa!Ag^D0E-.pCVgG]v9GޮPDhJr&*%nICv[I^F%Ks)_ӳ <⼳SK+欖MKTѲq.u-bg6?8AS !@˲TtVwHc Ы3uCT?ӳA^3^õrVB9b@GqNu+8ӓ @U|k4o4= x@rn*2,,e׬-)$=4%"@\紸.[*\}e$DXMj?.Zaf՚wvUyYf'끉#8b\57&Ȯ`Mg*=~I eY{WI=mPӳ @|MK%=vTG;nTK@ @Mu}r  nѴXӓ!@H޵}1*[8A'?D˥ޯR7=NsBL~XZ\hVJяnW @3ZZ\ږLOmi'5zݖ8X/}MygWjOY-e]A u-٥MNmMOeYrञ}Jc P,utgzgSNɩusRgzփҼIJ>$IW\nzfQݑ_IUdXYC~lkUh&=9#IMI (?qN rEmze ,K5~x^0ӳ(gGV8TO&3d3= [ Lr onyiyӓ2Ǘz|C7D}]IW|%[ZoݩC:+E| »4\hW*O5ٻjAfjM2V`}Ӧ--k] y'']zigxIsnqRT]#M\UTUiҜ9&  ~cǚЪ[[Pn4=9$Ц>R:*A|:=]FDlG7:\z*mR{nRXHwF\1TwuUkR4a ]:ŕ VID 4= o Xn kt{`;NQN--ԞZ6-QSGA9xY ٥NД' ò,8UҘzL,B utgzgF6:SP&IUVZI_qAn=舾*͛#MOhR˭wkαZ{_"BLx-)Ёsz$ K +'iq].TH6= _,K5~x^0ӳw|lk7m?ϳolLݗN|$W6yϤKNys!= 6DT*ثy =&="˞@#@6o}/zpyx33N=4t+>>‡Zfs.t6c/{me\sZ;?6**{m}h'_{g];= v@osrˤ˞@;"@I. >Oٓhgٹ[/y)sxcӷgg w/O=,b}Dt` 79yw+= vNt@J%z#u<#/{|ۣYtiޒ.Cׯy5_.N @1HZcS.jlʏ:&G*{ ?6΂'= Hs;Zv懿y1?+y1ٯ{ײgA vlmb3yj[ G䢓)v(i~\6>-;uF){RdrïVf}s9#e$]iڶ#Wo†5!#ҵsg.xyݖ\2>k66sG = G o?Ԑ#_=)]$@ {egnL8|zyFݴ5>Wo?ML? mlINr?ٿI6R^͋^5]],Hy{փ˳xśqƸOs'\ж6`͙e663!>ډy=mƲ'P i?:w٭K~ֿIEX6~S(w!XԶ-|ܠ|̣ҽkg'"@Z777eya`|yeOBBWl>5̿ ԧ9V9I2Ҙ+{%J>iӦח=մےWdk߭eϡ^Xre{WUT*eOΝ;sɹ[6I"6lȢE2xѣ9?YjUƏڲI( #@0(!IDAT #@0( #@@uuuʆ ʞ@;"@ZT*g>ԛo>;}I3mڴlڴiϷnݚYfeȐ!֭[<̘1#7nTUU} v]={vxL6-KϞ=m۶}(PкM4)SLse֬YI;J%gyf,YYfONCCC,[,˖-KuuirKSSSv7% |ӧg̙Ic_wߝ+/΢ErM7͸qrAeʔ){sE3&wy^{CM0aG}tnݚueҥI!TI2y+K,4i.CvjnnNccct~v 0 {| 챚ر#ׯJkצvuA?)UUU{w,`3&I2gΜݮ?~v<ٮvҥK;>7n\Ə*7oΉ',X#FԩSwvԩkrSNɊ+rmO>%>&@cUUUY`Ar=nHmmmNoqSΜ937oٳs-dԨQysg(ZUW0( #@0( #@0( #@0( #@0( #@0( #@0( #@0( #@0( ?W9 O` IENDB`pint-0.19.2/pint/testsuite/baseline/test_plot_with_set_units.png000066400000000000000000000433411422760043000252200ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i8tEXtSoftwarematplotlib version3.1.2, http://matplotlib.org/%J IDATxwxek&H-PvZ1ꮽ't]+bٵb& E "J&P%'3߾J {95r03sfY%cC <1!@x cC <1!@x cC <1!@x cC <1!@x cC <1!@x cC <1!@x cC <1!@x c_qqVZN:)<<JܹS\rZlizO"@ȪUtM7_1w\x㍦g$ċtIݻ1^dʔ)={^kr"o&ۏnC_=~N]wȏӸ'⚜kr"OmڴI7t^/][ 2{DGGs=D~M)0\IJMȏӸ'⚜kr"q\M<1!@RRRLO:\qMN5)lj&'⚜kf,2='33SZ~=7j 7vǾ xvx <1!@x cC <1!@x cYez#@SpFϬdzkbzYI)5=3\nK~UV- MOycUI-#CM+ i e8T^[,fz!@VC sԼi;B77=o X5.=|vkLz~ 6=˯ H[]R gƑF Xyj4azV"@Uk{|!uNMBM hҚbMR?n=KlezDԹgg[5s^~ZG J+5ɑvhn sooB/|/Vxp2!cLOI iu.=f㛝ݻ^6I-Ag(.W#SeD'lf|Ғ=zdQ⚅j}#7>$>NO,{tՀvz~ e|Ʀ}ǔGumrG| gY~[O/ߨ.-jY(ukizNVZY a 2= }DiN7 ҥ}ۚ3Dݖr^hq00= W9tZSgmźܮrQMB!@5Z$YmCtv8ӓWrkz}uFukKRfag 0H&ed)9vr>= G‚5J$42WUVlҿ%}ZqI6= @,Sjz+aeq*P ˲`}f,SazJlez<@+ob]죈^"QmS4SUi$]=0$DQXwԳlV֑Z>lun,F) rɦud'=tY/6 2= ^@ZD2uo[/BAܖ6$_;hN7= ^;pJ3va]]/&Avӳ/Դ C5kKӓ:^hr')62,x9p2ڸTFvn3= >@٧E-g77= >)qX[c駨`ӳcǕ%z~pV{lB Yei,Ob"4uznfz|:VUGjy> cdz|dUé#5z톁ۿI;w1g $.C.vqO4?C@yNչ,sY:g+ӓVrOmӫm.=aZG?F}vhE=tydh\@dM_ e5\C:ǘA:pNZ/!g! bGq_W$dq E{\ ӢFo|IPӌ%yZHWWUd(/a?ƽǔԾUzi|MN0= @eYv^I]Z65.,@WJ+j9Z_Q\[aAgAJ4ёUzAo[ӓ >8_I њw0%0= 8)<^MA=\CAvӳEҔyYlmCtv8ӓ_EZ[> 4[Kͺn⚅DSEz^.mg+7Ӄ r,,X-LOrU.a&{.]ַ_gb)5=SۋU}u8rEx!˲`}f,SaZrHnezp/SV]ߠ='죈^?3lSL:^9ЕMO,;kv6GH!ܲY@#@ ;R^dMue=$,Q QINUֺ ֦'0ϷO5c PpӳFGx؁cU;kݕvA75 xAo9idS4kIG PS֋6o_=$FxvP#Sccz붑eL @ѲzdQZ4 т{F(}sӓFPYSXW+WU`ӳeqgHO n/#WD4˲}f.S-MxËr"gRtЌ  2= :*<4Gjlv'^8Mn]/ܢ>JcDx54Ukl?iTHY#@iMA&gdm{2= )sOϷiXXr 3= )){R3îMCMAv+>x@,Vxp2!cLO|ⅦL|,%%E)))\zzgN]Z/^_#BLς89|AxٳgkРAgv+5=S[iyD'l $'pff -x߹G.U0-oG ?ӌ%yZH SWUd(/į(IS#SK$KN0= Khei]zz&uԲQizJ+j9Z_Q\[aAg~iMtdNo6YiczPnK.Ь5}sIg*Mo uy4ytw5 /i,l6ͽ}Fvkizj]n8_QszIkjz K*41éܢR=|Y/yv6ӳFa>~aƒ5ԡID?SU3+6jݺ_={MEGWjS;!dq &yeEzbI[kIHjez @O+ӣs$k& n'~^ފ_g*͑͹~oz_AcYfpz;QMMp SJk{tA>~i/4 g|&edΥn {6= @= ܖ^l|b4jnz@J2~g.讉vW{{^44kINM[/ܬCKjz@pNmwLGuƑ+_ k,ޫ*62D  MO`\eKO.S"xu_5 6= @# @Q[Wjz Tq5~pG?F#,˒c]\NM,unfzFF+#r"wnAMTXpY<UxT*՟o1ۚn]/ܢr9Lc"Lahte՚6?[sh%=d7= hTkk,ܖ޹,׳I "@@s5ӭzm%VLVQag0 nJMp*sQM=Gy5 R]tVӓx4:`YS%֋Wӳxʔpj2ͼ"Q7$#WND3(HA´mz/FR^]K0H SWUd(/-2%@-UZSUI`zASfYvYI]",mE VZw@7輻/ﭰ ӳJ4ё:d]ҧI|~mqo9)<,>'ux7źn<Mp/i,l6ͽ}Fvkiz?AuGzcu')YY$I%TnQ~aƒ5ԡI@u5wn]ޯÃMԶǕԎr꾺aHlиeYzo}XZ:RD @ :=8WKjz7[T4Gj4r@I,KoS}IF韷 QئgP~FO7:K{),O}&edΥo z6= m϶iΧ:S\?PmLI~ei&e8M. ;=gh{9 +a%$8s녕w^$4 1= N:\4ScuN8r{ {Ȣ\Fh#?I|LEM\Q~(oWU`ӳ )5ݩ#za\+>XuԲܲRVLz#@rzdQVӍC; 2= N s>4S 4[ӓ x!_ڮVmQh91g#@2e՚:?[_=vմ{(8nz4/ͶbM%?os{ę  Թz哭4kf_7@LG`؞pYxT/{ ; Z_.QӐ ͻkw1= U.=ff.Nl!g@#@ʔԶez>8r 0 xТ"= GOhӓ~aÃ=5C ӓg Z^Q~[c`ӳ +5ݩǫ)eH{l"@e4coԳM3ӳg Uzt-ޫ ko(<$,i'StTihtxӓ/ ˲;6Jm:65= Jk4l}Y^K!Mg_!@vaMpέo z6= h.?}U~UguќMtY@_ZIN}D. )ν=1 6?[M~0 kzPjz~f]ث^!g@ @cgqNmL3&֑dq <%Y{ Ѣ{G_BIpq}ڵk*++f?nhUɥ5B]9NcƌQIII'@{lLN9R øk=M2EZnv2=eYz]onӲnp{Snn5x`S'QZYGjE>4 ӳ?"@)..p>4SzA_[ӓ#X7xCemƷk&M|S0k֬M4p@3F?sfӔ)S<9ZqY֗ty]5 kS0}~<''焏 9l+yY,Km1Yv)رc RrO_lȮ-5kBZ5 3= p SбcG?sRN9 j=u]el=k'}lںu@Xa.{K+cxN=zs=e˖)??_K.5 WUK~Iv.ZϏ!gNs9礏{=[2]5r+SW7%xJKKyu/p}_Am´.$"@)>>^֭ѣOxlݺujo|5eEGlJJRRR NA{4nP) g99|y=]uUz4|p_|^~g֠AhÞR9:pJK5LONL%''Zz1cVZѣGGJHHPQQ󕘘3g>%D/N IDAT,sN*RFKɏ|߄^OZvfΜڵK111z'***D9G+jt׿k沍ah-~w@NCddq=㦧ag&:*q鯿M}ژhD_lOjPs@knz aܹJOO׮]TYYl6 -pXҚJ=&]]M8 ^?Pӓ|堦ϖnӻՈn-MOxRO_uW_5=|J˭>ڢ7Wo׹=uIjq  ~]}զgO),Pé {JtǨ.mg m=%''=P+r9_py;Bwӕ; 4k,tM4hw_PUS7*ӯOQag #@[oÇ5diF?yf);;:[WjS;kf]Rojْ߱NƲ,PO,SZ:J=43= Ez/LOtV,ޠe{2f ӳ^9wV۶mݻw?ߡCStTN)ѫ)uER;ӓ^9;wַ~!CSNzryheYz_Ym4az V׮]|#%H%5^>|PwY\K!M97|[n175)éZr$ @O϶jH2aD!+Ԥ,DGwSw4Un:e+Iw./pgU׹[7;4w+xmZ4 1= I,.WéiD:r~ 8=zdQZ6 բ{G_BI?AN3iEr@;=sU_5 6= G$iӾcJMޣUz+@#@ YwۭoTM,mjfzO J+k}anAITXpY?F@}DiN7eښ_ڮVmQhe5Lc"Lȡ՚:?K_m-ֽuԋz(8nz   Z)dYu#$@"@չܚIEFvmYԪYYEsRN9 KzsnsS9 mwWr'@u6ҥ}qlz+ʔT2=}U_4l6\~b"XAm}#.$N@+ӌ7hsMNSWQDOg(a*cU=!IWL0= _D,K٩g?جm"|ܲY*|̑= Gl:[GvCRh ӳ8%whéZ(I >Ϸi'1FsRmtY^*MvAwMM >rPg+nӻw Ո-MO jz-u^8<>Igp2W(-é=z޺}Tg6ӳhx9{\5oЀMOA *k\zjy 5[={M?E@#@Ǖ%z~pV{l' ,Keyԣu3ӳhTpV,}JA3&*<$,]xTiՔ"Ix 8sNiUӓ(0 h`l< kHm<, Υ?ܢ١ѽ[kEӳ*4Jud*"Q$fz^3$kYfZt6= EiK"]5"CyZNæ}ǔG$ϑ+N`Y~[O/ߨ.-jY(ukiz>STZQ a 2= B)X&::^U7nK5= D/p-e^(_I ʸkD"@g:^b{nWMg8k,Iu=$R&$U0ӳH&ed)9v@C"@@ 5 )cI%@@u+6kw>¸$EG"@CeJMwPnA6GhL`}f,٠aZrHnezPʪ4 Zܣ z>O.aONm e8qo[,fz"gZMCJj$+>ƥocn]NN8tvqt8r @˲4B\1Z:J=Z73= #rpʪj]ÊM]o=7ÃM>pJ,Sjz뙫ơdq Y4cI5ӒGw(ӳ"@:=8Wgu47}8}pR*5=SWktxӓ @eY7;쇛ԳM3suj,'/4eEGGc)))JII1HyXO6:K{*IYp8p8~RCkfϞAÚ:޺y.$:333lh @r[zmiwќmtYO @;pJ2nG&^]itS Y @|AM ]cMOLM[/ڬ}CKjz@vP#Sccz붑eLIJzdQZ4 т{F(}sӓ@"@?WYSXWSTXY @ ۲R3UxB]Oj/#WCe)B\Z:J=Z73= ͱZ=(W+r)eH ӳ$ _*<4Gjlv'߿ޮVnQh1Lc"L8jMt]4 pRl+yYr-sY:g+ӓ~:[s>ݪ>ߦ]bʄjfz"@h&e8~M=!o=0Gp $cTպzݺo=7ÃM0ǕԎr꾺aHl @dYHO,S|p-I^mL@W7hI^]78A3G!o:@nQR:\V9ЕMOJp,snR6QCԩeSӳ=^>|P_K!Mgx58 m?IYs魛ޭMO P.>ۦ9kp͹~F38VINQ kdAxU#ImʸUͦgT?lW~*(0= ^hEX/-% i)Q{[K>^8]LOyj ꔘhzF_ZWn֎r2/q |a{Euji:qtug4 u>}Lh˲UmN5Pz&mBvQx,_`_V]mz#@RYғ}Hj?^W‚M-+5=SG*¸?8A6ggY 0fMTXpY~Ar>4S 4[ӓv[W--ǝ>&,@ Ukl?Tp,A `VIv[zֳt^V'~+l՟ئ]bʄjfz@@"@Ԥ 2wմz)ν=L!@>ۯiH2:Ř:`YS' Wӳ ~f29zLO~7l6\x ~cH.ޠ6QaZ|imzW^]K0H WUP~jx#^m{LL/4.9$>ɲ,]KOؤnqZ6J]"M @sJ+jl;w×VXpY8|]%RYud]ܧI>8_7לonz;xJSe뛂bM.&Avӳpx/i,l6ͽ}FvkizTr#NKRPӳpx’ Mp*T_KwEv,4^}zpaÃwAUf\\ I`aHmdbxc&f4F:lwm$V+vbtb bt ]»]hR|_|?3{^3ss^8/OG@'"@%4Wy~<"\1BVNFr*ꕺP_Wү"-W=W:K=}6EY= ojћ8}gc#pz,<`LwJdf\bz$i Ї*oհp?xaz,)jn5럲tBֲjX0ZIÔjHЖ|>*P>7Za^V  x ꚴB(/krs=N[R>-G/WZG ]N|M跟}!0s{{X=ꖖd~-J..r`N]{(~8E 54њb>yYG׺gc #@7))]rA?cpbv}qIiQ{kx z,tju-Z:7ݬ JZU7gz$tCS[ϋCZ@@7E>-ұ*GSCjX՟.Twa8hH?G@@;mt5lkF)C hwQK UxV?5T?\]lt_iNvQVqrM-}_ UzHz,PXRwBŷJKy-Wxp'~Eo8 oHpu~دc(-iz{csӉ|yNKvf6cX=  ЎoY KH>V'Dp ZcqR⢴rZ<{Y=҃,/ uU60G#@z6C];GktD_m=Zz[=@47ꧧ_JMIHwB}Z$WWe,/۠`G: @z6m9m=vQ?OgRpO -*ԙuZt#ruuz,nZݕ z$; PSKkK6"LڗjStcZSZJr,>a'؉#vpNGN_O|ܒ \'sq|؉#v∝t>G;AgsW݈ag_k ӎi`Po~ kl'[REkhy*tHd]Ôj8@!@FIRrMQ߆ۯZDIRQQ4 $)77WCRRRРǧO#GJIRAA4gΜi p2wւ m6M0A?T^^.IJNN{0 ƍjmmՀ󹪫p_AAA$566v8?,,w;zp2ڴi6mڤK.)''G+VPEE^}UIҖ-[{Vʕ+\AAA~k׮r?(eee)**꾮0Tjj|Ivl6W;v]<<<'*;;[UUU~bbrssۃ/v!{FνKߦxH]]+::Z:q:3fO>ڲe,X%''+$$D***Ree}IƍkŊ4h˕[WWzKԁ.? @֮]Ei̙|~mnn'kΝ*--UKK"##|?;w\EFF*==]/_kԨQ/zj\R ɓ!I:tj*+jll԰aGuHKKS޽{iΝ| 6L`~i!@`i!@`i!@`i!@`i!@`i!@`i!@`i!@`i!@X64-"LIENDB`pint-0.19.2/pint/testsuite/conftest.py000066400000000000000000000032371422760043000177620ustar00rootroot00000000000000# pytest fixtures import io import pytest import pint @pytest.fixture def registry_empty(): return pint.UnitRegistry(None) @pytest.fixture def registry_tiny(): return pint.UnitRegistry( io.StringIO( """ 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- 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 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.19.2/pint/testsuite/helpers.py000066400000000000000000000077671422760043000176130ustar00rootroot00000000000000import doctest import pickle import re from distutils.version import LooseVersion import pytest 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_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"") class PintOutputChecker(doctest.OutputChecker): def check_output(self, want, got, optionflags): check = super().check_output(want, got, optionflags) if check: return check try: if eval(want) == eval(got): return True except Exception: pass for regex in (_q_re, _sq_re): try: 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 except Exception: pass cnt = 0 for regex in (_unit_re,): try: 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 except Exception: pass 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 LooseVersion(NUMPY_VER) < LooseVersion(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 LooseVersion(NUMPY_VER) >= LooseVersion(version), reason="Requires NumPy >= %s" % version, ) requires_babel = pytest.mark.skipif( not HAS_BABEL, reason="Requires Babel with units support" ) 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." ) # 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.19.2/pint/testsuite/test_application_registry.py000066400000000000000000000221751422760043000234310ustar00rootroot00000000000000"""Tests for global UnitRegistry, Unit, and Quantity """ 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.19.2/pint/testsuite/test_babel.py000066400000000000000000000054401422760043000202370ustar00rootroot00000000000000import 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() def test_format(func_registry): ureg = func_registry dirname = os.path.dirname(__file__) ureg.load_definitions(os.path.join(dirname, "../xtranslated.txt")) distance = 24.0 * ureg.meter assert distance.format_babel(locale="fr_FR", length="long") == "24.0 mètres" time = 8.0 * ureg.second assert time.format_babel(locale="fr_FR", length="long") == "8.0 secondes" assert time.format_babel(locale="ro", length="short") == "8.0 s" acceleration = distance / time**2 assert ( acceleration.format_babel(locale="fr_FR", length="long") == "0.375 mètre par seconde²" ) mks = ureg.get_system("mks") assert mks.format_babel(locale="fr_FR") == "métrique" @helpers.requires_babel() 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.0 * ureg.meter assert distance.format_babel(length="long") == "24.0 mètres" time = 8.0 * ureg.second assert time.format_babel(length="long") == "8.0 secondes" assert time.format_babel(locale="ro", length="short") == "8.0 s" acceleration = distance / time**2 assert acceleration.format_babel(length="long") == "0.375 mètre par seconde²" mks = ureg.get_system("mks") assert mks.format_babel(locale="fr_FR") == "métrique" @helpers.requires_babel() def test_unit_format_babel(): ureg = UnitRegistry(fmt_locale="fr_FR") volume = ureg.Unit("ml") assert volume.format_babel() == "millilitre" ureg.default_format = "~" assert volume.format_babel() == "ml" dimensionless_unit = ureg.Unit("") assert dimensionless_unit.format_babel() == "" ureg.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() def test_str(func_registry): ureg = func_registry d = 24.0 * ureg.meter s = "24.0 meter" assert str(d) == s assert "%s" % d == s assert "{}".format(d) == s ureg.set_fmt_locale("fr_FR") s = "24.0 mètres" assert str(d) == s assert "%s" % d == s assert "{}".format(d) == s ureg.set_fmt_locale(None) s = "24.0 meter" assert str(d) == s assert "%s" % d == s assert "{}".format(d) == s pint-0.19.2/pint/testsuite/test_compat.py000066400000000000000000000055721422760043000204630ustar00rootroot00000000000000import 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.19.2/pint/testsuite/test_compat_downcast.py000066400000000000000000000124551422760043000223630ustar00rootroot00000000000000import 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 identity(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(identity, identity, identity, id="identity"), pytest.param( lambda ureg, x: x + 1 * ureg.m, lambda ureg, x: x + 1, identity, id="addition", ), pytest.param( lambda ureg, x: x - 20 * ureg.cm, lambda ureg, x: x - 0.2, identity, 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), identity, 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), identity, id="transpose"), pytest.param(WR(np.mean), WR(np.mean), identity, id="mean ufunc"), pytest.param(WR(np.sum), WR(np.sum), identity, 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,))), identity, id="reshape function", ), pytest.param(WR(np.amax), WR(np.amax), identity, 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( lambda x, y: x * y, lambda ureg: ureg("kg m"), id="multiplication" ), pytest.param(lambda x, y: x / y, 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(lambda a, u: a * u), id="array-first", marks=pytest.mark.xfail(reason="upstream issue numpy/numpy#15200"), ), pytest.param(WR2(lambda a, u: u * a), id="unit-first"), ], ) @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.19.2/pint/testsuite/test_compat_upcast.py000066400000000000000000000103341422760043000220320ustar00rootroot00000000000000import 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", [ lambda x, y: x + y, lambda x, y: x - (-y), lambda x, y: x * y, 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(set(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.19.2/pint/testsuite/test_contexts.py000066400000000000000000000761641422760043000210540ustar00rootroot00000000000000import itertools import logging import math import re from collections import defaultdict import pytest from pint import ( DefinitionSyntaxError, DimensionalityError, UndefinedUnitError, UnitRegistry, ) from pint.context import Context from pint.testsuite import helpers from pint.util import UnitsContainer def add_ctxs(ureg): 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): 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): 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): 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 ureg._active_ctx assert ureg._active_ctx.graph assert not ureg._active_ctx assert not ureg._active_ctx.graph with ureg.context("lc", n=1): assert ureg._active_ctx assert ureg._active_ctx.graph assert not ureg._active_ctx assert not ureg._active_ctx.graph def test_known_context_enable(self, func_registry): ureg = func_registry add_ctxs(ureg) ureg.enable_contexts("lc") assert ureg._active_ctx assert ureg._active_ctx.graph ureg.disable_contexts(1) assert not ureg._active_ctx assert not ureg._active_ctx.graph ureg.enable_contexts("lc", n=1) assert ureg._active_ctx assert ureg._active_ctx.graph ureg.disable_contexts(1) assert not ureg._active_ctx assert not 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 ureg._active_ctx.graph == g_sp with ureg.context("lc", n=1): assert ureg._active_ctx.graph == g_sp with ureg.context("ab"): assert ureg._active_ctx.graph == g_ab with ureg.context("lc"): with ureg.context("ab"): assert ureg._active_ctx.graph == g with ureg.context("ab"): with ureg.context("lc"): assert ureg._active_ctx.graph == g with ureg.context("lc", "ab"): assert ureg._active_ctx.graph == g with ureg.context("ab", "lc"): assert 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 ureg._active_ctx.graph == g_sp ureg.disable_contexts(1) ureg.enable_contexts("lc", n=1) assert ureg._active_ctx.graph == g_sp ureg.disable_contexts(1) ureg.enable_contexts("ab") assert ureg._active_ctx.graph == g_ab ureg.disable_contexts(1) ureg.enable_contexts("lc") ureg.enable_contexts("ab") assert ureg._active_ctx.graph == g ureg.disable_contexts(2) ureg.enable_contexts("ab") ureg.enable_contexts("lc") assert ureg._active_ctx.graph == g ureg.disable_contexts(2) ureg.enable_contexts("lc", "ab") assert ureg._active_ctx.graph == g ureg.disable_contexts(2) ureg.enable_contexts("ab", "lc") assert 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(ureg._active_ctx) y = dict(ureg._active_ctx.graph) assert ureg._active_ctx assert ureg._active_ctx.graph with ureg.context("ab"): assert ureg._active_ctx assert ureg._active_ctx.graph assert x != ureg._active_ctx assert y != ureg._active_ctx.graph assert x == ureg._active_ctx assert y == ureg._active_ctx.graph assert not ureg._active_ctx assert not 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 ureg._active_ctx assert not ureg._active_ctx.graph def test_unknown_nested_context(self, func_registry): ureg = func_registry add_ctxs(ureg) with ureg.context("lc"): x = dict(ureg._active_ctx) y = dict(ureg._active_ctx.graph) with pytest.raises(KeyError): with ureg.context("la"): pass assert x == ureg._active_ctx assert y == ureg._active_ctx.graph assert not ureg._active_ctx assert not 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(ureg._contexts) assert ctx.name not in ureg._contexts ureg.add_context(ctx) assert ctx.name in ureg._contexts assert len(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 ureg._contexts assert len(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 ureg._contexts["sp"].funcs assert b in ureg._contexts["sp"].funcs with ureg.context("sp"): assert a in ureg._active_ctx assert b in 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(ureg._active_ctx.graph, da, db) assert p msg = "{} <-> {}".format(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(ureg._active_ctx.graph, da, db) assert p msg = "{} <-> {}".format(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 base name, as a # function of another name b = 5 f """.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 base 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 base 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 base 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(): expected = re.escape("Expected = ; got [d1] = [d2] * [d3]") with pytest.raises(DefinitionSyntaxError, match=expected): Context.from_lines(["@context c", "[d1] = [d2] * [d3]"]) def test_err_prefix_redefinition(): expected = re.escape("Expected = ; got [d1] = [d2] * [d3]") with pytest.raises(DefinitionSyntaxError, match=expected): 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.19.2/pint/testsuite/test_converters.py000066400000000000000000000062631422760043000213700ustar00rootroot00000000000000import itertools from pint.compat import np from pint.converters import ( Converter, LogarithmicConverter, OffsetConverter, 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.14 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 pint-0.19.2/pint/testsuite/test_dask.py000066400000000000000000000156131422760043000201170ustar00rootroot00000000000000import importlib import os 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 # isort:skip from distributed.client import futures_of # isort:skip from distributed.utils_test import cluster, gen_cluster, loop # isort:skip 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 os.path.exists("mydask.png") os.remove("mydask.png") 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_ @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.19.2/pint/testsuite/test_definitions.py000066400000000000000000000150451422760043000215070ustar00rootroot00000000000000import pytest from pint.converters import LogarithmicConverter, OffsetConverter, ScaleConverter from pint.definitions import ( AliasDefinition, Definition, DimensionDefinition, PrefixDefinition, UnitDefinition, ) from pint.errors import DefinitionSyntaxError 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 assert str(x) == "m" 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( "turn = 6.28 * 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 == 6.28 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 = 2.71828182845904523536028747135266249775724709369995 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]", "", (), None, is_base=True) 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 = AliasDefinition.from_string("@alias meter = metro = metr") assert isinstance(x, AliasDefinition) assert x.name == "meter" assert x.aliases == ("metro", "metr") pint-0.19.2/pint/testsuite/test_diskcache.py000066400000000000000000000060251422760043000211100ustar00rootroot00000000000000import decimal import pickle import time import pytest import pint from pint.definitions import UnitDefinition from pint.parser import DefinitionFile FS_SLEEP = 0.010 @pytest.fixture def float_cache_filename(tmp_path): ureg = pint.UnitRegistry(cache_folder=tmp_path / "cache_with_float") assert ureg._diskcache assert ureg._diskcache.cache_folder return tuple(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 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 ureg._diskcache assert ureg._diskcache.cache_folder == tmp_path / "cache_with_decimal" assert ureg.cache_folder == tmp_path / "cache_with_decimal" files = tuple(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, DefinitionFile): continue for lineno, adef in obj.filter_by(UnitDefinition): if adef.name == "pi": assert isinstance(adef.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 ureg._diskcache assert ureg._diskcache.cache_folder auto_files = tuple(p.name for p in 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(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(ureg._diskcache.cache_folder.glob("*.pickle")) assert len(files) == 4 pint-0.19.2/pint/testsuite/test_errors.py000066400000000000000000000136531422760043000205130ustar00rootroot00000000000000import 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" # filename and lineno can be attached after init ex.filename = "a.txt" ex.lineno = 123 assert str(ex) == "While opening a.txt, in line 123: foo" ex = DefinitionSyntaxError("foo", lineno=123) assert str(ex) == "In line 123: foo" ex = DefinitionSyntaxError("foo", filename="a.txt") assert str(ex) == "While opening a.txt: foo" ex = DefinitionSyntaxError("foo", filename="a.txt", lineno=123) assert str(ex) == "While opening a.txt, in line 123: foo" def test_redefinition_error(self): ex = RedefinitionError("foo", "bar") assert str(ex) == "Cannot redefine 'foo' (bar)" # filename and lineno can be attached after init ex.filename = "a.txt" ex.lineno = 123 assert ( str(ex) == "While opening a.txt, in line 123: Cannot redefine 'foo' (bar)" ) ex = RedefinitionError("foo", "bar", lineno=123) assert str(ex) == "In line 123: Cannot redefine 'foo' (bar)" ex = RedefinitionError("foo", "bar", filename="a.txt") assert str(ex) == "While opening a.txt: Cannot redefine 'foo' (bar)" ex = RedefinitionError("foo", "bar", filename="a.txt", lineno=123) assert ( str(ex) == "While opening a.txt, in line 123: 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", filename="a.txt", lineno=123), 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)) assert type(ex) is type(ex2) assert ex.args == ex2.args assert ex.__dict__ == ex2.__dict__ assert str(ex) == str(ex2) with pytest.raises(PintError): raise ex pint-0.19.2/pint/testsuite/test_formatter.py000066400000000000000000000032521422760043000211740ustar00rootroot00000000000000import pytest from pint import formatting as fmt class TestFormatter: def test_join(self): for empty in (tuple(), []): assert fmt._join("s", empty) == "" assert fmt._join("*", "1 2 3".split()) == "1*2*3" assert fmt._join("{0}*{1}", "1 2 3".split()) == "1*2*3" def test_formatter(self): assert fmt.formatter(dict().items()) == "" assert fmt.formatter(dict(meter=1).items()) == "meter" assert fmt.formatter(dict(meter=-1).items()) == "1 / meter" assert fmt.formatter(dict(meter=-1).items(), as_ratio=False) == "meter ** -1" assert ( fmt.formatter(dict(meter=-1, second=-1).items(), as_ratio=False) == "meter ** -1 * second ** -1" ) assert fmt.formatter(dict(meter=-1, second=-1).items()) == "1 / meter / second" assert ( fmt.formatter(dict(meter=-1, second=-1).items(), single_denominator=True) == "1 / (meter * second)" ) assert ( fmt.formatter(dict(meter=-1, second=-2).items()) == "1 / meter / second ** 2" ) assert ( fmt.formatter(dict(meter=-1, second=-2).items(), single_denominator=True) == "1 / (meter * second ** 2)" ) def test_parse_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.19.2/pint/testsuite/test_formatting.py000066400000000000000000000057671422760043000213600ustar00rootroot00000000000000import 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 pint-0.19.2/pint/testsuite/test_infer_base_unit.py000066400000000000000000000067141422760043000223330ustar00rootroot00000000000000from decimal import Decimal from fractions import Fraction import pytest from pint import Quantity as Q from pint import UnitRegistry from pint.testsuite import helpers from pint.util import infer_base_unit class TestInferBaseUnit: def test_infer_base_unit(self): from pint.util import infer_base_unit test_units = Q(1, "meter**2").units registry = Q(1, "meter**2")._REGISTRY assert infer_base_unit(Q(1, "millimeter * nanometer")) == test_units assert infer_base_unit("millimeter * nanometer", registry) == test_units assert ( infer_base_unit(Q(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(self): from pint.util import infer_base_unit 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(self): from pint.util import infer_base_unit 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(self): assert infer_base_unit(Q(1, "m * mm / m / um * s")) == Q(1, "s").units def test_to_compact(self): r = Q(1000000000, "m") * Q(1, "mm") / Q(1, "s") / Q(1, "ms") compact_r = r.to_compact() expected = Q(1000.0, "kilometer**2 / second**2") helpers.assert_quantity_almost_equal(compact_r, expected) r = (Q(1, "m") * Q(1, "mm") / Q(1, "m") / Q(2, "um") * Q(2, "s")).to_compact() helpers.assert_quantity_almost_equal(r, Q(1000, "s")) def test_to_compact_decimal(self): 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(self): 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 = ( Q(Fraction(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_volts(self): from pint.util import infer_base_unit r = Q(1, "V") * Q(1, "mV") / Q(1, "kV") b = infer_base_unit(r) assert b == Q(1, "V").units helpers.assert_quantity_almost_equal(r, Q(1, "uV")) pint-0.19.2/pint/testsuite/test_issues.py000066400000000000000000001040511422760043000205030ustar00rootroot00000000000000import copy import math import pprint import pytest from pint import Context, DimensionalityError, UnitRegistry, get_application_registry from pint.compat import np from pint.testsuite import QuantityTestCase, helpers from pint.unit import UnitsContainer from pint.util import ParserHelper # 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(self, module_registry): module_registry.Quantity(2, "µm") @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 ( "{:~}".format(1 * module_registry.second / module_registry.millisecond) == "1.0 s / ms" ) assert "{:~}".format(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 base 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 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)), ) @helpers.requires_uncertainties() def test_issue_1300(self): module_registry = UnitRegistry() module_registry.default_format = "~P" m = module_registry.Measurement(1, 0.1, "meter") assert m.default_format == "~P" 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 base 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 base unit, then imports another file… def1.write_text( f""" foo = [FOO] @import {str(def2)} """ ) # …that, in turn, uses it in a context def2.write_text( """ @context BAR [FOO] -> [mass]: value / foo * 10.0 kg @end """ ) # Succeeds with pint 0.18; fails with pint 0.19 ureg1 = UnitRegistry() ureg1.load_definitions(def1) # ← FAILS 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 base 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 base 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 pint-0.19.2/pint/testsuite/test_log_units.py000066400000000000000000000225241422760043000211770ustar00rootroot00000000000000import logging import math import pytest from pint import OffsetUnitCalculusError, UnitRegistry from pint.testsuite import QuantityTestCase, helpers from pint.unit import Unit, UnitsContainer @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.log(10, 2), "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 ) 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 base. # 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 = [ "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", [ ("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.19.2/pint/testsuite/test_matplotlib.py000066400000000000000000000025271422760043000213440ustar00rootroot00000000000000import 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 pint-0.19.2/pint/testsuite/test_measurement.py000066400000000000000000000247611422760043000215260ustar00rootroot00000000000000import 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_(4.0, 0.1, "s") 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) def test_format(self, subtests): v, u = self.Q_(4.0, "s ** 2"), self.Q_(0.1, "s ** 2") m = self.ureg.Measurement(v, u) for spec, result in ( ("{}", "(4.00 +/- 0.10) second ** 2"), ("{!r}", ""), ("{: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}"), ): with subtests.test(spec): assert spec.format(m) == result def test_format_paru(self, subtests): v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2") m = self.ureg.Measurement(v, u) for spec, result in ( ("{: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"), ): with subtests.test(spec): assert spec.format(m) == result def test_format_u(self, subtests): v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2") m = self.ureg.Measurement(v, u) for spec, result in ( ("{:.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}"), ): with subtests.test(spec): assert spec.format(m) == result def test_format_percu(self, subtests): self.test_format_perce(subtests) v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2") m = self.ureg.Measurement(v, u) for spec, result in ( ("{:.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"), ): with subtests.test(spec): assert spec.format(m) == result def test_format_perce(self, subtests): v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2") m = self.ureg.Measurement(v, u) for spec, result in ( ("{:.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"), ): with subtests.test(spec): assert spec.format(m) == result def test_format_exponential_pos(self, subtests): # Quantities in exponential format come with their own parenthesis, don't wrap # them twice m = self.ureg.Quantity(4e20, "s^2").plus_minus(1e19) for spec, result in ( ("{}", "(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}"), ): with subtests.test(spec): assert spec.format(m) == result def test_format_exponential_neg(self, subtests): m = self.ureg.Quantity(4e-20, "s^2").plus_minus(1e-21) for spec, result in ( ("{}", "(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}"), ): with subtests.test(spec): assert spec.format(m) == result def test_format_default(self, subtests): v, u = self.Q_(4.0, "s ** 2"), self.Q_(0.1, "s ** 2") m = self.ureg.Measurement(v, u) for spec, result in ( ("", "(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}"), ): with subtests.test(spec): self.ureg.default_format = spec assert "{}".format(m) == result 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) pint-0.19.2/pint/testsuite/test_non_int.py000066400000000000000000001310421422760043000206340ustar00rootroot00000000000000import 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.testsuite import QuantityTestCase, helpers from pint.unit import UnitsContainer class FakeWrapper: # Used in test_upcast_type_rejection_on_creation def __init__(self, q): self.q = q # TODO: do not subclass from QuantityTestCase class NonIntTypeQuantityTestCase(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.NON_INT_TYPE, int)) else: assert isinstance(first, (self.NON_INT_TYPE, int)) if isinstance(second, self.Q_): assert isinstance(second.m, (self.NON_INT_TYPE, int)) else: assert isinstance(second, (self.NON_INT_TYPE, int)) super().assert_quantity_almost_equal( first, second, self.NON_INT_TYPE(rtol), self.NON_INT_TYPE(atol), msg ) def QP_(self, value, units): assert isinstance(value, str) return self.Q_(self.NON_INT_TYPE(value), units) class _TestBasic: def test_quantity_creation(self): value = self.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() with self.capture_log() as buffer: assert value * self.ureg.meter == self.Q_( value, self.NON_INT_TYPE("2") * self.ureg.meter ) assert len(buffer) == 1 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.NON_INT_TYPE("1000") / self.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(force_ndarray=self.FORCE_NDARRAY) y2 = ureg2.Quantity(self.NON_INT_TYPE("2"), "second") z2 = ureg2.Quantity(self.NON_INT_TYPE("0.5"), "hertz") assert hash(y * z) == hash(y2 * z2) def test_to_base_units(self): x = self.Q_("1*inch") helpers.assert_quantity_almost_equal( x.to_base_units(), self.QP_("0.0254", "meter") ) x = self.Q_("1*inch*inch") helpers.assert_quantity_almost_equal( x.to_base_units(), self.Q_( self.NON_INT_TYPE("0.0254") ** self.NON_INT_TYPE("2.0"), "meter*meter" ), ) x = self.Q_("1*inch/minute") helpers.assert_quantity_almost_equal( x.to_base_units(), self.Q_( self.NON_INT_TYPE("0.0254") / self.NON_INT_TYPE("60"), "meter/second" ), ) def test_convert(self): helpers.assert_quantity_almost_equal( self.Q_("2 inch").to("meter"), self.Q_(self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254"), "meter"), ) helpers.assert_quantity_almost_equal( self.Q_("2 meter").to("inch"), self.Q_(self.NON_INT_TYPE("2") / self.NON_INT_TYPE("0.0254"), "inch"), ) helpers.assert_quantity_almost_equal( self.Q_("2 sidereal_year").to("second"), self.QP_("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 ) 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_(self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254"), "meter"), ) helpers.assert_quantity_almost_equal( meter.m_from(x), self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254") ) # from unit helpers.assert_quantity_almost_equal( meter.from_(self.ureg.inch), self.QP_("0.0254", "meter") ) helpers.assert_quantity_almost_equal( meter.m_from(self.ureg.inch), self.NON_INT_TYPE("0.0254") ) # from number helpers.assert_quantity_almost_equal( meter.from_(2, strict=False), self.QP_("2", "meter") ) helpers.assert_quantity_almost_equal( meter.m_from(self.NON_INT_TYPE("2"), strict=False), self.NON_INT_TYPE("2") ) # from number (strict mode) with pytest.raises(ValueError): meter.from_(self.NON_INT_TYPE("2")) with pytest.raises(ValueError): meter.m_from(self.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.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): helpers.assert_quantity_almost_equal( self.QP_("0", "kelvin").to("kelvin"), self.QP_("0", "kelvin") ) helpers.assert_quantity_almost_equal( self.QP_("0", "degC").to("kelvin"), self.QP_("273.15", "kelvin") ) helpers.assert_quantity_almost_equal( self.QP_("0", "degF").to("kelvin"), self.QP_("255.372222", "kelvin"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("kelvin"), self.QP_("100", "kelvin") ) helpers.assert_quantity_almost_equal( self.QP_("100", "degC").to("kelvin"), self.QP_("373.15", "kelvin") ) helpers.assert_quantity_almost_equal( self.QP_("100", "degF").to("kelvin"), self.QP_("310.92777777", "kelvin"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.QP_("0", "kelvin").to("degC"), self.QP_("-273.15", "degC") ) helpers.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("degC"), self.QP_("-173.15", "degC") ) helpers.assert_quantity_almost_equal( self.QP_("0", "kelvin").to("degF"), self.QP_("-459.67", "degF"), rtol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("degF"), self.QP_("-279.67", "degF"), rtol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("32", "degF").to("degC"), self.QP_("0", "degC"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("100", "degC").to("degF"), self.QP_("212", "degF"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("54", "degF").to("degC"), self.QP_("12.2222", "degC"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("12", "degC").to("degF"), self.QP_("53.6", "degF"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("12", "kelvin").to("degC"), self.QP_("-261.15", "degC"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("12", "degC").to("kelvin"), self.QP_("285.15", "kelvin"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("12", "kelvin").to("degR"), self.QP_("21.6", "degR"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("12", "degR").to("kelvin"), self.QP_("6.66666667", "kelvin"), atol=0.01, ) helpers.assert_quantity_almost_equal( self.QP_("12", "degC").to("degR"), self.QP_("513.27", "degR"), atol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("12", "degR").to("degC"), self.QP_("-266.483333", "degC"), atol=0.01, ) def test_offset_delta(self): helpers.assert_quantity_almost_equal( self.QP_("0", "delta_degC").to("kelvin"), self.QP_("0", "kelvin") ) helpers.assert_quantity_almost_equal( self.QP_("0", "delta_degF").to("kelvin"), self.QP_("0", "kelvin"), rtol=0.01 ) helpers.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("delta_degC"), self.QP_("100", "delta_degC") ) helpers.assert_quantity_almost_equal( self.QP_("100", "kelvin").to("delta_degF"), self.QP_("180", "delta_degF"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.QP_("100", "delta_degF").to("kelvin"), self.QP_("55.55555556", "kelvin"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.QP_("100", "delta_degC").to("delta_degF"), self.QP_("180", "delta_degF"), rtol=0.01, ) helpers.assert_quantity_almost_equal( self.QP_("100", "delta_degF").to("delta_degC"), self.QP_("55.55555556", "delta_degC"), rtol=0.01, ) helpers.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): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): for magnitude, unit in ( ("32", ""), ("2.4", ""), ("32", "m/s"), ("2.4", "m/s"), ): with self.subTest(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: FORCE_NDARRAY = False 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 + self.NON_INT_TYPE("2.54") * unit, "centimeter") ) func( op.add, y, x, self.Q_(unit + unit / (self.NON_INT_TYPE("2.54") * unit), "inch"), ) func(op.add, a, unit, self.Q_(unit + unit, None)) with pytest.raises(DimensionalityError): op.add(self.NON_INT_TYPE("10"), x) with pytest.raises(DimensionalityError): op.add(x, self.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.NON_INT_TYPE("2.54") * unit, "centimeter") ) func( op.sub, y, x, self.Q_(unit - unit / (self.NON_INT_TYPE("2.54") * unit), "inch"), ) func(op.sub, a, unit, self.Q_(unit - unit, None)) with pytest.raises(DimensionalityError): op.sub(self.NON_INT_TYPE("10"), x) with pytest.raises(DimensionalityError): op.sub(x, self.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.NON_INT_TYPE("2.54") * unit, "centimeter"), ) func(op.iadd, y, x, self.Q_(unit + unit / self.NON_INT_TYPE("2.54"), "inch")) func(op.iadd, a, unit, self.Q_(unit + unit, None)) with pytest.raises(DimensionalityError): op.iadd(self.NON_INT_TYPE("10"), x) with pytest.raises(DimensionalityError): op.iadd(x, self.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.NON_INT_TYPE("2.54"), "centimeter")) func(op.isub, y, x, self.Q_(unit - unit / self.NON_INT_TYPE("2.54"), "inch")) func(op.isub, a, unit, self.Q_(unit - unit, None)) with pytest.raises(DimensionalityError): op.sub(self.NON_INT_TYPE("10"), x) with pytest.raises(DimensionalityError): op.sub(x, self.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.NON_INT_TYPE("10"), "4.2*meter", "42*meter", unit) func(op.mul, "4.2*meter", unit * self.NON_INT_TYPE("10"), "42*meter", unit) func(op.mul, "4.2*meter", "10*inch", "42*meter*inch", unit) func(op.truediv, unit * self.NON_INT_TYPE("42"), "4.2*meter", "10/meter", unit) func( op.truediv, "4.2*meter", unit * self.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.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.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.NON_INT_TYPE("3"), b) with pytest.raises(DimensionalityError): op.floordiv(a, self.NON_INT_TYPE("3")) with pytest.raises(DimensionalityError): op.ifloordiv(a, b) with pytest.raises(DimensionalityError): op.ifloordiv(self.NON_INT_TYPE("3"), b) with pytest.raises(DimensionalityError): op.ifloordiv(a, self.NON_INT_TYPE("3")) func( op.floordiv, unit * self.NON_INT_TYPE("10"), "4.2*meter/meter", self.NON_INT_TYPE("2"), unit, ) func(op.floordiv, "10*meter", "4.2*inch", self.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.NON_INT_TYPE("10"), "4.2*meter/meter", self.NON_INT_TYPE("1.6"), unit, ) def _test_quantity_ifloordiv(self, unit, func): func( op.ifloordiv, self.NON_INT_TYPE("10"), "4.2*meter/meter", self.NON_INT_TYPE("2"), unit, ) func(op.ifloordiv, "10*meter", "4.2*inch", self.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.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, "while testing {0}".format(fun) assert ry == zy, "while testing {0}".format(fun) assert rx is not zx, "while testing {0}".format(fun) assert ry is not zy, "while testing {0}".format(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.NON_INT_TYPE("1.0"), self._test_not_inplace) class _TestOffsetUnitMath: @classmethod def setup_class(cls): 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", "expected_output"), additions) def test_addition(self, input_tuple, expected): 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 == "error": with pytest.raises(OffsetUnitCalculusError): op.add(q1, q2) else: expected = self.QP_(*expected) assert op.add(q1, q2).units == expected.units helpers.assert_quantity_almost_equal(op.add(q1, q2), 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", "expected_output"), subtractions) def test_subtraction(self, input_tuple, expected): 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 == "error": with pytest.raises(OffsetUnitCalculusError): op.sub(q1, q2) else: expected = self.QP_(*expected) assert op.sub(q1, q2).units == expected.units helpers.assert_quantity_almost_equal(op.sub(q1, q2), 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", "expected_output"), multiplications) def test_multiplication(self, input_tuple, expected): 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 == "error": with pytest.raises(OffsetUnitCalculusError): op.mul(q1, q2) else: expected = self.QP_(*expected) assert op.mul(q1, q2).units == expected.units helpers.assert_quantity_almost_equal(op.mul(q1, q2), 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", "expected_output"), divisions) def test_truedivision(self, input_tuple, expected): 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 == "error": with pytest.raises(OffsetUnitCalculusError): op.truediv(q1, q2) else: expected = self.QP_(*expected) assert op.truediv(q1, q2).units == expected.units helpers.assert_quantity_almost_equal( op.truediv(q1, q2), 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", "expected_output"), 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.QP_(*qin1), self.QP_(*qin2) input_tuple = q1, q2 if expected == "error": with pytest.raises(OffsetUnitCalculusError): op.mul(q1, q2) else: expected = self.QP_(*expected) assert op.mul(q1, q2).units == expected.units helpers.assert_quantity_almost_equal(op.mul(q1, q2), 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", "expected_output"), 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.QP_(*in1), self.NON_INT_TYPE(in2) else: in1, in2 = in1, self.QP_(*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.QP_(*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 base 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", "expected_output"), 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.QP_(*in1), self.NON_INT_TYPE(in2) else: in1, in2 = self.NON_INT_TYPE(in1), self.QP_(*in2) input_tuple = in1, in2 # update input_tuple for better tracebacks expected_copy = expected[:] 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.QP_(*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", "expected_output"), exponentiation) def test_exponentiation(self, input_tuple, expected): self.ureg.default_as_delta = False in1, in2 = input_tuple if type(in1) is tuple and type(in2) is tuple: in1, in2 = self.QP_(*in1), self.QP_(*in2) elif not type(in1) is tuple and type(in2) is tuple: in1, in2 = self.NON_INT_TYPE(in1), self.QP_(*in2) else: in1, in2 = self.QP_(*in1), self.NON_INT_TYPE(in2) input_tuple = in1, in2 expected_copy = expected[:] 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.QP_(*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) class NonIntTypeQuantityTestQuantityFloat(_TestBasic, NonIntTypeQuantityTestCase): kwargs = dict(non_int_type=float) class NonIntTypeQuantityTestQuantityBasicMathFloat( _TestQuantityBasicMath, NonIntTypeQuantityTestCase ): kwargs = dict(non_int_type=float) class NonIntTypeQuantityTestOffsetUnitMathFloat( _TestOffsetUnitMath, NonIntTypeQuantityTestCase ): kwargs = dict(non_int_type=float) class NonIntTypeQuantityTestQuantityDecimal(_TestBasic, NonIntTypeQuantityTestCase): kwargs = dict(non_int_type=Decimal) class NonIntTypeQuantityTestQuantityBasicMathDecimal( _TestQuantityBasicMath, NonIntTypeQuantityTestCase ): kwargs = dict(non_int_type=Decimal) class NonIntTypeQuantityTestOffsetUnitMathDecimal( _TestOffsetUnitMath, NonIntTypeQuantityTestCase ): kwargs = dict(non_int_type=Decimal) class NonIntTypeQuantityTestQuantityFraction(_TestBasic, NonIntTypeQuantityTestCase): kwargs = dict(non_int_type=Fraction) class NonIntTypeQuantityTestQuantityBasicMathFraction( _TestQuantityBasicMath, NonIntTypeQuantityTestCase ): kwargs = dict(non_int_type=Fraction) class NonIntTypeQuantityTestOffsetUnitMathFraction( _TestOffsetUnitMath, NonIntTypeQuantityTestCase ): kwargs = dict(non_int_type=Fraction) pint-0.19.2/pint/testsuite/test_numpy.py000066400000000000000000001445641422760043000203550ustar00rootroot00000000000000import 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 , broadcast_arrays # 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) 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.14 * 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) with pytest.raises(DimensionalityError): np.cumproduct(self.q) helpers.assert_quantity_equal(np.cumprod(self.q / self.ureg.m), [1, 2, 6, 24]) helpers.assert_quantity_equal( np.cumproduct(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 ) @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() 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") ) def test_ptp(self): assert self.q.ptp() == 3 * self.ureg.m @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 num bers 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_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) @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, 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.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, ) 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 not np.allclose( [1e10, 1e-8] * self.ureg.m, [1.00001e10, 1e-9] * self.ureg.mm ) @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, ) @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.19.2/pint/testsuite/test_numpy_func.py000066400000000000000000000160261422760043000213570ustar00rootroot00000000000000from unittest.mock import patch import pytest import pint.numpy_func from pint import DimensionalityError, OffsetUnitCalculusError from pint.compat import np from pint.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.numpy_func.HANDLED_FUNCTIONS", {}) @patch("pint.numpy_func.HANDLED_UFUNCS", {}) def test_implements(self): # Test for functions @implements("test", "function") def test_function(): pass assert pint.numpy_func.HANDLED_FUNCTIONS["test"] == test_function # Test for ufuncs @implements("test", "ufunc") def test_ufunc(): pass assert pint.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 pint-0.19.2/pint/testsuite/test_pint_eval.py000066400000000000000000000060331422760043000211520ustar00rootroot00000000000000import pytest from pint.compat import tokenizer from pint.pint_eval import build_eval_tree class TestPintEval: def _test_one(self, input_text, parsed): assert build_eval_tree(tokenizer(input_text)).to_string() == parsed @pytest.mark.parametrize( ("input_text", "parsed"), ( ("3", "3"), ("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) pint-0.19.2/pint/testsuite/test_pitheorem.py000066400000000000000000000022131422760043000211610ustar00rootroot00000000000000import 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.19.2/pint/testsuite/test_quantity.py000066400000000000000000002274471422760043000210650ustar00rootroot00000000000000import 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, Quantity, UnitRegistry, get_application_registry, ) from pint.compat import np from pint.testsuite import QuantityTestCase, assert_no_warnings, helpers from pint.unit import UnitsContainer 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 k = 5 * get_application_registry().meter m = Quantity(5, "meter") # Include a comparison to a directly created Quantity # 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 # Compare with items to the separate application registry assert k >= m # These should both be from application registry if z._REGISTRY != m._REGISTRY: 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 # 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.default_format = spec assert f"{x}" == result def test_formatting_override_default_units(self): ureg = UnitRegistry() ureg.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" def test_formatting_override_default_magnitude(self): ureg = UnitRegistry() ureg.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:~P}" == r"1×10²⁰ m" x /= 1e40 assert f"{x:~H}" == r"1×10-20 m" assert f"{x:~L}" == r"1\times 10^{-20}\ \mathrm{m}" 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.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_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_types", [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(RuntimeWarning): 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, "while testing {0}".format(fun) assert ry == zy, "while testing {0}".format(fun) assert rx is not zx, "while testing {0}".format(fun) assert ry is not zy, "while testing {0}".format(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("[speed]") == 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 not ("[time]" 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 not ("[angle]" 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 base 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[:] 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 tuple and type(in2) is tuple: in1, in2 = self.Q_(*in1), self.Q_(*in2) elif not type(in1) is tuple and type(in2) is tuple: in2 = self.Q_(*in2) else: in1 = self.Q_(*in1) input_tuple = in1, in2 expected_copy = expected[:] 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 tuple and 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 not type(in1) is tuple and type(in2) is tuple: in2 = self.Q_(*in2) else: in1 = self.Q_(*in1) input_tuple = in1, in2 expected_copy = expected[:] 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]), ) 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, "")) pint-0.19.2/pint/testsuite/test_systems.py000066400000000000000000000226671422760043000207130ustar00rootroot00000000000000import pytest from pint import UnitRegistry from pint.testsuite import QuantityTestCase 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 = 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 = 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 = 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) 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) 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) 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.19.2/pint/testsuite/test_testing.py000066400000000000000000000051361422760043000206510ustar00rootroot00000000000000import pytest from .. import testing from ..quantity import Quantity np = pytest.importorskip("numpy") @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( Quantity(1, "m"), 1, True, "The first is not dimensionless", id="mixed1-int-not equal-equal", ), pytest.param( 1, Quantity(1, "m"), True, "The second is not dimensionless", id="mixed2-int-not equal-equal", ), pytest.param( Quantity(1, "m"), Quantity(1, "m"), False, "", id="Quantity-int-equal-equal" ), pytest.param( Quantity(1, "m"), Quantity(1, "s"), True, "Units are not equal", id="Quantity-int-equal-not equal", ), pytest.param( Quantity(1, "m"), Quantity(2, "m"), True, "Magnitudes are not equal", id="Quantity-int-not equal-equal", ), pytest.param( Quantity(1, "m"), Quantity(2, "s"), True, "Units are not equal", id="Quantity-int-not equal-not equal", ), pytest.param( Quantity(1, "m"), Quantity(float("nan"), "m"), True, "Magnitudes are not equal", id="Quantity-float-not equal-equal", ), pytest.param( Quantity([1, 2], "m"), Quantity([1, 2], "m"), False, "", id="Quantity-ndarray-equal-equal", ), pytest.param( Quantity([1, 2], "m"), Quantity([1, 2], "s"), True, "Units are not equal", id="Quantity-ndarray-equal-not equal", ), pytest.param( Quantity([1, 2], "m"), Quantity([2, 2], "m"), True, "Magnitudes are not equal", id="Quantity-ndarray-not equal-equal", ), pytest.param( Quantity([1, 2], "m"), Quantity([2, 2], "s"), True, "Units are not equal", id="Quantity-ndarray-not equal-not equal", ), ), ) def test_assert_equal(first, second, error, message): if error: with pytest.raises(AssertionError, match=message): testing.assert_equal(first, second) else: testing.assert_equal(first, second) pint-0.19.2/pint/testsuite/test_umath.py000066400000000000000000000625111422760043000203120ustar00rootroot00000000000000import 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 = "At {} with {}".format(func.__name__, 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 = "At {} with {}".format(func.__name__, 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 = "At {} with {} and {}".format(func.__name__, x1, 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]) Add arguments element-wise. subtract(x1, x2[, out]) Subtract arguments, element-wise. multiply(x1, x2[, out]) Multiply arguments element-wise. divide(x1, x2[, out]) Divide arguments element-wise. logaddexp(x1, x2[, out]) Logarithm of the sum of exponentiations of the inputs. logaddexp2(x1, x2[, out]) Logarithm of the sum of exponentiations of the inputs in base-2. true_divide(x1, x2[, out]) Returns a true division of the inputs, element-wise. floor_divide(x1, x2[, out]) Return the largest integer smaller or equal to the division of the inputs. negative(x[, out]) Returns an array with the negative of each element of the original array. power(x1, x2[, out]) First array elements raised to powers from second array, element-wise. NOT IMPLEMENTED remainder(x1, x2[, out]) Return element-wise remainder of division. mod(x1, x2[, out]) Return element-wise remainder of division. fmod(x1, x2[, out]) Return the element-wise remainder of division. absolute(x[, out]) Calculate the absolute value element-wise. rint(x[, out]) Round elements of the array to the nearest integer. sign(x[, out]) Returns an element-wise indication of the sign of a number. conj(x[, out]) Return the complex conjugate, element-wise. exp(x[, out]) Calculate the exponential of all elements in the input array. exp2(x[, out]) Calculate 2**p for all p in the input array. log(x[, out]) Natural logarithm, element-wise. log2(x[, out]) Base-2 logarithm of x. log10(x[, out]) Return the base 10 logarithm of the input array, element-wise. expm1(x[, out]) Calculate exp(x) - 1 for all elements in the array. log1p(x[, out]) Return the natural logarithm of one plus the input array, element-wise. sqrt(x[, out]) Return the positive square-root of an array, element-wise. square(x[, out]) Return the element-wise square of the input. reciprocal(x[, out]) Return the reciprocal of the argument, element-wise. ones_like(x[, out]) Returns an array of ones with the same shape and type as a given array. 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_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.19.2/pint/testsuite/test_unit.py000066400000000000000000001027361422760043000201570ustar00rootroot00000000000000import copy import functools import logging import math import re from contextlib import nullcontext as does_not_raise import pytest from pint import ( DefinitionSyntaxError, DimensionalityError, RedefinitionError, UndefinedUnitError, ) 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 # 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_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.default_format = spec assert f"{x}" == result, f"Failed for {spec}, {result}" def test_unit_formatting_defaults_warning(self): ureg = UnitRegistry() ureg.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.default_format = spec assert f"{x}" == result, f"Failed for {spec}, {result}" def test_unit_formatting_custom(self, monkeypatch): from pint import formatting, register_unit_format monkeypatch.setattr(formatting, "_FORMATTERS", formatting._FORMATTERS.copy()) @register_unit_format("new") def format_new(unit, **options): return "new format" ureg = UnitRegistry() assert "{:new}".format(ureg.m) == "new format" def test_ipython(self): alltext = [] class Pretty: @staticmethod def text(text): alltext.append(text) 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.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", dict(), 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) ureg.define("meter = [length]") with pytest.raises(DefinitionSyntaxError): ureg.define("meter = [length]") with pytest.raises(TypeError): ureg.define(list()) 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): import pkg_resources from pint import unit data = pkg_resources.resource_filename(unit.__name__, "default_en.txt") ureg1 = UnitRegistry() ureg2 = UnitRegistry(data) assert dir(ureg1) == dir(ureg2) with pytest.raises(ValueError): UnitRegistry(None).load_definitions("notexisting") def test_default_format(self): ureg = UnitRegistry() q = ureg.meter s1 = f"{q}" s2 = f"{q:~}" ureg.default_format = "~" s3 = f"{q}" assert s2 == s3 assert s1 != s3 assert ureg.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_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__(list()) with pytest.raises(TypeError): d.__pow__(list()) with pytest.raises(TypeError): d.__truediv__(list()) with pytest.raises(TypeError): d.__rtruediv__(list()) 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, use_decimal=False): token = next(tokenizer(expression)) actual = ParserHelper.eval_token(token, use_decimal=use_decimal) 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.19.2/pint/unit.py000066400000000000000000000270611422760043000150640ustar00rootroot00000000000000""" pint.unit ~~~~~~~~~ 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 copy import locale import operator from numbers import Number from typing import TYPE_CHECKING, Any, Type, Union from ._typing import UnitLike from .compat import NUMERIC_TYPES, babel_parse, is_upcast_type from .definitions import UnitDefinition from .errors import DimensionalityError from .formatting import extract_custom_flags, format_unit, split_format from .util import PrettyIPython, SharedRegistryObject, UnitsContainer if TYPE_CHECKING: from .context import Context class Unit(PrettyIPython, SharedRegistryObject): """Implements a class to describe a unit supporting math operations.""" #: Default formatting string. default_format: str = "" def __reduce__(self): # See notes in Quantity.__reduce__ from . import _unpickle_unit return _unpickle_unit, (Unit, 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, Unit): self._units = units._units else: raise TypeError( "units must be of type str, Unit or " "UnitsContainer; not {}.".format(type(units)) ) self.__used = False self.__handling = None @property def debug_used(self) -> Any: return self.__used def __copy__(self) -> Unit: ret = self.__class__(self._units) ret.__used = self.__used return ret def __deepcopy__(self, memo) -> Unit: ret = self.__class__(copy.deepcopy(self._units, memo)) ret.__used = self.__used return ret def __str__(self) -> str: return format(self) def __bytes__(self) -> bytes: return str(self).encode(locale.getpreferredencoding()) def __repr__(self) -> str: return "".format(self._units) def __format__(self, spec) -> str: _, uspec = split_format( spec, self.default_format, self._REGISTRY.separate_format_defaults ) if "~" in uspec: if not self._units: return "" units = UnitsContainer( dict( (self._REGISTRY._get_symbol(key), value) for key, value in self._units.items() ) ) uspec = uspec.replace("~", "") else: units = self._units return format_unit(units, uspec, registry=self._REGISTRY) def format_babel(self, spec="", locale=None, **kwspec: Any) -> str: spec = spec or extract_custom_flags(self.default_format) if "~" in spec: if self.dimensionless: return "" units = UnitsContainer( dict( (self._REGISTRY._get_symbol(key), value) for key, value in self._units.items() ) ) spec = spec.replace("~", "") else: units = self._units locale = self._REGISTRY.fmt_locale if locale is None else locale if locale is None: raise ValueError("Provide a `locale` value to localize translation.") else: kwspec["locale"] = babel_parse(locale) return units.format_babel(spec, registry=self._REGISTRY, **kwspec) @property def dimensionless(self) -> bool: """Return True if the Unit is dimensionless; False otherwise.""" return not bool(self.dimensionality) @property def dimensionality(self) -> UnitsContainer: """ Returns ------- dict Dimensionality of the Unit, 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: Union[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, Unit 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 Quantity if contexts or self._REGISTRY._active_ctx: try: (1 * self).to(other, *contexts, **ctx_kwargs) return True except DimensionalityError: return False if isinstance(other, (Quantity, Unit)): 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 Unit 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) else: return NotImplemented __div__ = __truediv__ __rdiv__ = __rtruediv__ def __pow__(self, other) -> "Unit": if isinstance(other, NUMERIC_TYPES): return self.__class__(self._units**other) else: mess = "Cannot power Unit by {}".format(type(other)) raise TypeError(mess) def __hash__(self) -> int: return self._units.__hash__() def __eq__(self, other) -> bool: # We compare to the base class of Unit because each Unit 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, (Unit, UnitsContainer, dict)): return self_q.compare(self._REGISTRY.Quantity(1, other), op) else: 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)) __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 = set( 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, ) else: return NotImplemented @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 _Unit = Unit def build_unit_class(registry) -> Type[Unit]: class Unit(_Unit): _REGISTRY = registry return Unit pint-0.19.2/pint/util.py000066400000000000000000000730311422760043000150600ustar00rootroot00000000000000""" 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 from collections.abc import 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, ClassVar, Optional, Union from .compat import NUMERIC_TYPES, tokenizer from .errors import DefinitionSyntaxError from .formatting import format_unit from .pint_eval import build_eval_tree if TYPE_CHECKING: from ._typing import UnitLike from .quantity import Quantity from .registry import BaseRegistry logger = logging.getLogger(__name__) logger.addHandler(NullHandler()) def matrix_to_string( matrix, row_headers=None, col_headers=None, fmtfun=lambda x: str(int(x)) ): """Takes a 2D matrix (as nested list) and returns a string. Parameters ---------- matrix : row_headers : (Default value = None) col_headers : (Default value = None) fmtfun : (Default value = lambda x: str(int(x))) Returns ------- """ ret = [] 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): """Takes a 2D matrix (as nested list) and returns the transposed version. Parameters ---------- matrix : Returns ------- """ return [list(val) for val in zip(*matrix)] def column_echelon_form(matrix, ntype=Fraction, transpose_result=False): """Calculates 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 value = Fraction) transpose_result : indicates if the returned matrix should be transposed. (Default value = False) Returns ------- type column echelon form, transformed identity matrix, swapped rows """ lead = 0 M = transpose(matrix) _transpose = transpose if transpose_result else lambda x: x rows, cols = len(M), len(M[0]) new_M = [] for row in M: r = [] for x in row: if isinstance(x, float): x = ntype.from_float(x) else: x = ntype(x) r.append(x) new_M.append(r) M = new_M # M = [[ntype(x) for x in row] for row in M] I = [ # noqa: E741 [ntype(1) if n == nc else ntype(0) for nc in range(rows)] for n in range(rows) ] swapped = [] for r in range(rows): if lead >= cols: return _transpose(M), _transpose(I), swapped i = r while M[i][lead] == 0: i += 1 if i != rows: continue i = r lead += 1 if cols == lead: return _transpose(M), _transpose(I), swapped M[i], M[r] = M[r], M[i] I[i], I[r] = I[r], I[i] swapped.append(i) lv = M[r][lead] M[r] = [mrx / lv for mrx in M[r]] I[r] = [mrx / lv for mrx in I[r]] for i in range(rows): if i == r: continue lv = M[i][lead] M[i] = [iv - lv * rv for rv, iv in zip(M[r], M[i])] I[i] = [iv - lv * rv for rv, iv in zip(I[r], I[i])] lead += 1 return _transpose(M), _transpose(I), swapped def pi_theorem(quantities, registry=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 = lambda x: x 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 M = [ [dimensionality[dimension] for name, dimensionality in quant] for dimension in dimensions ] M, identity, pivot = column_echelon_form(M, transpose_result=False) # Collect results # Make all numbers integers and minimize the number of negative exponents. # Remove zeros results = [] for rowm, rowi in zip(M, identity): 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( dict( (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): """Solve a dependency graph. Parameters ---------- dependencies : dependency dictionary. For each key, the value is an iterable indicating its dependencies. Returns ------- type iterator of sets, each containing keys of independents tasks dependent only of the previous tasks in the list. """ 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, start, end, path=None): path = (path or []) + [start] if start == end: return path 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, start, visited=None): 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): """Custom dict implementing __missing__.""" def __missing__(self, key): return 0 def copy(self): return udict(self) class UnitsContainer(Mapping): """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) Parameters ---------- Returns ------- type """ __slots__ = ("_d", "_hash", "_one", "_non_int_type") def __init__(self, *args, **kwargs) -> 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 = kwargs.pop("non_int_type", 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("key must be a str, not {}".format(type(key))) if not isinstance(value, Number): raise TypeError("value must be a number, not {}".format(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): return self.__copy__() def add(self, key, value): newval = self._d[key] + value new = self.copy() if newval: new._d[key] = newval else: new._d.pop(key) new._hash = None return new def remove(self, keys): """Create a new UnitsContainer purged from given keys. Parameters ---------- keys : Returns ------- """ new = self.copy() for k in keys: new._d.pop(k) new._hash = None return new def rename(self, oldkey, newkey): """Create a new UnitsContainer in which an entry has been renamed. Parameters ---------- oldkey : newkey : Returns ------- """ new = self.copy() new._d[newkey] = new._d.pop(oldkey) new._hash = None return new def __iter__(self): return iter(self._d) def __len__(self) -> int: return len(self._d) def __getitem__(self, key): return self._d[key] def __contains__(self, key): return key in self._d def __hash__(self): 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): return self._d, self._one, self._non_int_type def __setstate__(self, state): self._d, self._one, self._non_int_type = state self._hash = None def __eq__(self, other) -> 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( ["'{}': {}".format(key, value) for key, value in sorted(self._d.items())] ) return "".format(tmp) def __format__(self, spec: str) -> str: return format_unit(self, spec) def format_babel(self, spec: str, registry=None, **kwspec) -> str: 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): 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): 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): 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] -= value if new._d[key] == 0: del new._d[key] new._hash = None return new def __rtruediv__(self, other): if not isinstance(other, self.__class__) and other != 1: err = "Cannot divide {} by UnitsContainer" raise TypeError(err.format(type(other))) return self**-1 class ParserHelper(UnitsContainer): """The ParserHelper stores in place the product of variables and their respective exponent and implements the corresponding operations. ParserHelper is a read-only mapping. All operations (even in place ones) Parameters ---------- Returns ------- type 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. """ __slots__ = ("scale",) def __init__(self, scale=1, *args, **kwargs): super().__init__(*args, **kwargs) self.scale = scale @classmethod def from_word(cls, input_word, non_int_type=float): """Creates a ParserHelper object with a single variable with exponent one. Equivalent to: ParserHelper({'word': 1}) Parameters ---------- input_word : Returns ------- """ if non_int_type is float: return cls(1, [(input_word, 1)], non_int_type=non_int_type) else: ONE = non_int_type("1.0") return cls(ONE, [(input_word, ONE)], non_int_type=non_int_type) @classmethod def eval_token(cls, token, use_decimal=False, non_int_type=float): # TODO: remove this code when use_decimal is deprecated if use_decimal: raise DeprecationWarning( "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n" ">>> from decimal import Decimal\n" ">>> ureg = UnitRegistry(non_int_type=Decimal)" ) 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, non_int_type=float): """Parse linear expression mathematical units and return a quantity object. Parameters ---------- input_string : Returns ------- """ 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 = tokenizer(input_string) ret = build_eval_tree(gen).evaluate( partial(cls.eval_token, non_int_type=non_int_type) ) if isinstance(ret, Number): return ParserHelper(ret, non_int_type=non_int_type) if reps: ret = ParserHelper( 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 = 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): 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) else: return self.scale == 1 and super().__eq__(other) def operate(self, items, op=operator.iadd, cleanup=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( ["'{}': {}".format(key, value) for key, value in sorted(self._d.items())] ) return "{} {}".format(self.scale, tmp) def __repr__(self): tmp = "{%s}" % ", ".join( ["'{}': {}".format(key, value) for key, value in sorted(self._d.items())] ) return "".format(self.scale, tmp) 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[BaseRegistry] _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) -> bool: """Check if the other object use a registry and if so that it is the same registry. Parameters ---------- other : Returns ------- type other don't use a registry and raise 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): if "~" in self.default_format: return "{:~H}".format(self) else: return "{:H}".format(self) def _repr_latex_(self): if "~" in self.default_format: return "${:~L}$".format(self) else: return "${:L}$".format(self) def _repr_pretty_(self, p, cycle): if "~" in self.default_format: p.text("{:~P}".format(self)) else: p.text("{:P}".format(self)) def to_units_container( unit_like: Union[UnitLike, Quantity], registry: Optional[BaseRegistry] = None ) -> UnitsContainer: """Convert a unit compatible type to a UnitsContainer. Parameters ---------- unit_like : registry : (Default value = None) Returns ------- """ 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: return registry._parse_units(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: Union[UnitLike, Quantity], registry: Optional[BaseRegistry] = None ) -> UnitsContainer: """ Given a Quantity or UnitLike, give the UnitsContainer for it's base units. Parameters ---------- unit_like : Union[UnitLike, Quantity] Quantity or Unit to infer the base units from. registry: Optional[BaseRegistry] 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(self, item): """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 : string Item to be found. Returns ------- """ # Double-underscore attributes are tricky to detect because they are # automatically prefixed with the class name - which may be a subclass of self if ( item.endswith("__") or len(item.lstrip("_")) == 0 or (item.startswith("_") and not item.lstrip("_")[0].isdigit()) ): raise AttributeError("%r object has no attribute %r" % (self, item)) class SourceIterator: """Iterator to facilitate reading the definition files. Accepts any sequence (like a list of lines, a file or another SourceIterator) The iterator yields the line number and line (skipping comments and empty lines) and stripping white spaces. for lineno, line in SourceIterator(sequence): # do something here """ def __new__(cls, sequence, filename=None, is_resource=False): if isinstance(sequence, SourceIterator): return sequence obj = object.__new__(cls) if sequence is not None: obj.internal = enumerate(sequence, 1) obj.last = (None, None) obj.filename = filename or getattr(sequence, "name", None) obj.is_resource = is_resource return obj def __iter__(self): return self def __next__(self): line = "" while not line or line.startswith("#"): lineno, line = next(self.internal) line = line.split("#", 1)[0].strip() self.last = lineno, line return lineno, line next = __next__ def block_iter(self): """Iterate block including header.""" return BlockIterator(self) class BlockIterator(SourceIterator): """Like SourceIterator but stops when it finds '@end' It also raises an error if another '@' directive is found inside. """ def __new__(cls, line_iterator): obj = SourceIterator.__new__(cls, None) obj.internal = line_iterator.internal obj.last = line_iterator.last obj.done_last = False return obj def __next__(self): if not self.done_last: self.done_last = True return self.last lineno, line = SourceIterator.__next__(self) if line.startswith("@end"): raise StopIteration elif line.startswith("@"): raise DefinitionSyntaxError("cannot nest @ directives", lineno=lineno) return lineno, line next = __next__ def iterable(y) -> bool: """Check whether or not an object can be iterated over. Vendored from numpy under the terms of the BSD 3-Clause License. (Copyright (c) 2005-2019, NumPy Developers.) Parameters ---------- value : Input object. type : object y : """ try: iter(y) except TypeError: return False return True def sized(y) -> bool: """Check whether or not an object has a defined length. Parameters ---------- value : Input object. type : object y : """ try: len(y) except TypeError: return False return True pint-0.19.2/pint/xtranslated.txt000066400000000000000000000011001422760043000166070ustar00rootroot00000000000000 # 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.19.2/pyproject.toml000066400000000000000000000002241422760043000154650ustar00rootroot00000000000000[build-system] requires = ["setuptools>=41", "wheel", "setuptools_scm[toml]>=3.4.3"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] pint-0.19.2/requirements_docs.txt000066400000000000000000000002501422760043000170440ustar00rootroot00000000000000sphinx>4 ipython matplotlib nbsphinx numpy pytest jupyter_client ipykernel ipython graphviz xarray pooch sparse dask[complete] setuptools>=41.2 Serialize pygments>=2.4 pint-0.19.2/setup.cfg000066400000000000000000000033531422760043000144000ustar00rootroot00000000000000[metadata] name = Pint author = Hernan E. Grecco author_email = hernan.grecco@gmail.com license = BSD description = Physical quantities module long_description = file: README.rst keywords = physical, quantities, unit, conversion, science url = https://github.com/hgrecco/pint 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.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 [options] packages = pint zip_safe = True include_package_data = True python_requires = >=3.8 setup_requires = setuptools; setuptools_scm scripts = pint/pint-convert [options.extras_require] numpy = numpy >= 1.19.5 uncertainties = uncertainties >= 3.1.6 test = pytest pytest-mpl pytest-cov pytest-subtests [options.package_data] pint = default_en.txt; constants_en.txt; py.typed [build-system] requires = ["setuptools", "setuptools_scm", "wheel"] [flake8] 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 exclude= build [isort] default_section=THIRDPARTY known_first_party=pint multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 use_parentheses=True line_length=88 [zest.releaser] python-file-with-version = version.py pint-0.19.2/setup.py000066400000000000000000000001341422760043000142630ustar00rootroot00000000000000#!/usr/bin/env python3 from setuptools import setup if __name__ == "__main__": setup() pint-0.19.2/version.py000066400000000000000000000001561422760043000146140ustar00rootroot00000000000000# This is just for zest.releaser. Do not touch # flake8: noqa # fmt: off __version__ = '0.20.dev0' # fmt: on