pax_global_header00006660000000000000000000000064136052663450014524gustar00rootroot0000000000000052 comment=82b2c2f97b3f929568a07850de4f75b0d16f3c56 pint-0.10.1/000077500000000000000000000000001360526634500125555ustar00rootroot00000000000000pint-0.10.1/.editorconfig000066400000000000000000000001661360526634500152350ustar00rootroot00000000000000root = true [*.py] charset = utf-8 indent_style = space indent_size = 4 insert_final_newline = true end_of_line = lf pint-0.10.1/.gitignore000066400000000000000000000004561360526634500145520ustar00rootroot00000000000000*~ __pycache__ *egg-info* *.pyc .DS_Store docs/_build/ .idea build/ dist/ MANIFEST *pytest_cache* .eggs # 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 pint-0.10.1/.travis.yml000066400000000000000000000070001360526634500146630ustar00rootroot00000000000000language: python branches: # prevent bors temporary branches to be built except: - staging.tmp - trying.tmp env: # This project adheres to NEP-29 # https://numpy.org/neps/nep-0029-deprecation_policy.html # Refer to https://docs.scipy.org/doc/numpy/release.html for # min/max Python version supported by numpy # Refer to history of https://github.com/lebigot/uncertainties/blob/master/setup.py # for min/max Python versions supported by uncertainties - PKGS="python=3.7 flake8 black isort" # Have linters fail first and quickly # Pinned packages to match readthedocs CI https://readthedocs.org/projects/pint/ - PKGS="python=3.7 ipython matplotlib nbsphinx numpy sphinx Pygments==2.3.1 docutils==0.14 alabaster commonmark==0.8.1 recommonmark==0.5.0" - PKGS="python=3.6" - PKGS="python=3.7" - PKGS="python=3.8" - PKGS="python=3.6 uncertainties=3.0" - PKGS="python=3.7 uncertainties=3.0" - PKGS="python=3.6 numpy=1.14 matplotlib" - PKGS="python=3.7 numpy=1.14 matplotlib" - PKGS="python=3.8 numpy=1.17 matplotlib" - PKGS="python=3.6 numpy=1.14 uncertainties=3.0" - PKGS="python=3.7 numpy=1.14 uncertainties=3.0" - PKGS="python=3.6 numpy uncertainties" - PKGS="python=3.7 numpy uncertainties" - PKGS="python=3.8 numpy uncertainties" - PKGS="python xarray netCDF4" # TODO: pandas tests # - PKGS="python=3.7 numpy pandas uncertainties pandas" before_install: - sudo apt-get update - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" - hash -r - conda config --set always_yes yes --set changeps1 no - conda config --add channels conda-forge - conda update -q conda # Useful for debugging any issues with conda - conda info -a # The next couple lines fix a crash with multiprocessing on Travis and are not specific to using Miniconda # But broke travis 2019-08 # - sudo rm -rf /dev/shm # - sudo ln -s /run/shm /dev/shm - export TEST_OPTS="-rfsxEX -s --cov=pint" install: - conda create -n travis $PKGS pytest pytest-cov coveralls - source activate travis - if [[ $PKGS =~ pandas ]]; then PANDAS=1; else PANDAS=0; fi - if [[ $PKGS =~ flake8 ]]; then LINT=1; else LINT=0; fi - if [[ $PKGS =~ sphinx ]]; then DOCS=1; else DOCS=0; fi - if [[ $PKGS =~ matplotlib && $DOCS == 0 ]]; then pip install pytest-mpl; export TEST_OPTS="$TEST_OPTS --mpl"; fi # this is superslow but suck it up until updates to pandas are made # - if [[ $PANDAS == '1' ]]; then pip install numpy cython pytest pytest-cov nbval; pip install git+https://github.com/pandas-dev/pandas.git@bdb7a1603f1e0948ca0cab011987f616e7296167; python -c 'import pandas; print(pandas.__version__)'; fi - conda list script: # if we're doing the pandas tests and hence have pytest available, we can # simply use it to run all the tests # - if [[ $PANDAS == '1' ]]; then python -bb -m coverage run -p --source=pint --omit="*test*","*compat*" -m py.test -rfsxEX; fi # test notebooks too if pandas available # - if [[ $PANDAS == '1' ]]; then pip install -e .; pytest --nbval notebooks/*; fi - if [[ $PANDAS == 0 && $LINT == 0 && $DOCS == 0 ]]; then python -bb -m pytest $TEST_OPTS; fi - if [[ $LINT == 1 ]]; then black -t py36 --check . && isort -rc -c . && flake8; fi - if [[ $DOCS == 1 ]]; then PYTHONPATH=$PWD sphinx-build -n -j auto -b html -d build/doctrees docs build/html; fi - if [[ $LINT == 0 && $DOCS == 0 ]]; then coverage report -m; fi after_success: - coveralls --verbose pint-0.10.1/AUTHORS000066400000000000000000000034221360526634500136260ustar00rootroot00000000000000Pint is written and maintained by 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 * Kaido Kert * Kenneth D. Mankoff * Kevin Davies * Luke Campbell * Matthieu Dartiailh * Nate Bogdanowicz * Peter Grayson * Richard Barnes * Ryan Dwyer * Ryan Kingsbury * Ryan May * Sigvald Marholm * Sundar Raman * Tiago Coutinho * Thomas Kluyver * Tom Ritchford * Virgil Dupras * Zebedee Nicholls (If you think that your name belongs here, please let the maintainer know) pint-0.10.1/BADGES.rst000066400000000000000000000016121360526634500142340ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/pint.svg :target: https://pypi.python.org/pypi/pint :alt: Latest Version .. image:: https://readthedocs.org/projects/pip/badge/ :target: http://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://travis-ci.org/hgrecco/pint.svg?branch=master :target: https://travis-ci.org/hgrecco/pint :alt: CI .. image:: https://coveralls.io/repos/github/hgrecco/pint/badge.svg?branch=master :target: https://coveralls.io/github/hgrecco/pint?branch=master :alt: Coverage .. image:: https://readthedocs.org/projects/pint/badge/ :target: http://pint.readthedocs.org/ :alt: Docs pint-0.10.1/CHANGES000066400000000000000000000573041360526634500135610ustar00rootroot00000000000000Pint Changelog ============== 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.10.1/LICENSE000066400000000000000000000030601360526634500135610ustar00rootroot00000000000000Copyright (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.10.1/MANIFEST.in000066400000000000000000000005541360526634500143170ustar00rootroot00000000000000include AUTHORS CHANGES LICENSE README.rst BADGES.rst version.txt readthedocs.yml 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.10.1/README.rst000066400000000000000000000126351360526634500142530ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/pint.svg :target: https://pypi.python.org/pypi/pint :alt: Latest Version .. image:: https://readthedocs.org/projects/pip/badge/ :target: http://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://travis-ci.org/hgrecco/pint.svg?branch=master :target: https://travis-ci.org/hgrecco/pint :alt: CI .. image:: https://coveralls.io/repos/github/hgrecco/pint/badge.svg?branch=master :target: https://coveralls.io/github/hgrecco/pint?branch=master :alt: Coverage .. image:: https://readthedocs.org/projects/pint/badge/ :target: http://pint.readthedocs.org/ :alt: Docs 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.6+ with no other dependency. If you need Python 2.7 or 3.4/3.5 compatibility, use Pint 0.9. 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/ GUI Website ----------- This Website_ wraps Pint's "dimensional analysis" methods to provide a GUI. 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`_. 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. .. _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 pint-0.10.1/bench/000077500000000000000000000000001360526634500136345ustar00rootroot00000000000000pint-0.10.1/bench/bench.py000066400000000000000000000066201360526634500152710ustar00rootroot00000000000000import copy import fnmatch import os from timeit import Timer import yaml def time_stmt(stmt="pass", setup="pass", number=0, repeat=3): """Timer function with the same behaviour as running `python -m timeit ` in the command line. Parameters ---------- stmt : str (Default value = "pass") setup : str (Default value = "pass") number : int (Default value = 0) repeat : int (Default value = 3) Returns ------- float elapsed time in seconds or NaN if the command failed. """ t = Timer(stmt, setup) if not number: # determine number so that 0.2 <= total time < 2.0 for i in range(1, 10): number = 10 ** i try: x = t.timeit(number) except Exception: print(t.print_exc()) return float("NaN") if x >= 0.2: break try: r = t.repeat(repeat, number) except Exception: print(t.print_exc()) return float("NaN") best = min(r) return best / number def build_task(task, name="", setup="", number=0, repeat=3): nt = copy.copy(task) nt["name"] = (name + " " + task.get("name", "")).strip() nt["setup"] = (setup + "\n" + task.get("setup", "")).strip("\n") nt["stmt"] = task.get("stmt", "") nt["number"] = task.get("number", number) nt["repeat"] = task.get("repeat", repeat) return nt def time_task(name, stmt="pass", setup="pass", number=0, repeat=3, stmts="", base=""): if base: nvalue = time_stmt(stmt=base, setup=setup, number=number, repeat=repeat) yield name + " (base)", nvalue suffix = " (normalized)" else: nvalue = 1.0 suffix = "" if stmt: value = time_stmt(stmt=stmt, setup=setup, number=number, repeat=repeat) yield name, value / nvalue for task in stmts: new_task = build_task(task, name, setup, number, repeat) for task_name, value in time_task(**new_task): yield task_name + suffix, value / nvalue def time_file(filename, name="", setup="", number=0, repeat=3): """Open a yaml benchmark file an time each statement, yields a tuple with filename, task name, time in seconds. Parameters ---------- filename : name : (Default value = "") setup : (Default value = "") number : (Default value = 0) repeat : (Default value = 3) Returns ------- """ with open(filename, "r") as fp: tasks = yaml.load(fp) for task in tasks: new_task = build_task(task, name, setup, number, repeat) for task_name, value in time_task(**new_task): yield task_name, value def recursive_glob(rootdir=".", pattern="*"): return [ os.path.join(looproot, filename) for looproot, _, filenames in os.walk(rootdir) for filename in filenames if fnmatch.fnmatch(filename, pattern) ] def main(filenames=None): if not filenames: filenames = recursive_glob(".", "bench_*.yaml") elif isinstance(filenames, str): filenames = [filenames] for filename in filenames: print(filename) print("-" * len(filename)) print() for task_name, value in time_file(filename): print(f"{value:.2e} {task_name}") print() if __name__ == "__main__": main() pint-0.10.1/bench/bench_base.yaml000066400000000000000000000015631360526634500165760ustar00rootroot00000000000000 - name: importing stmt: import pint - name: empty registry setup: import pint stmt: ureg = pint.UnitRegistry(None) - name: default registry setup: import pint stmt: ureg = pint.UnitRegistry() - name: finding meter setup: | import pint ureg = pint.UnitRegistry() stmts: - name: (attr) stmt: q = ureg.meter - name: (item) stmt: q = ureg['meter'] - name: base units setup: | import pint ureg = pint.UnitRegistry() stmts: - name: meter stmt: ureg.get_base_units('meter') - name: yard stmt: ureg.get_base_units('yard') - name: meter / second stmt: ureg.get_base_units('meter / second') - name: yard / minute stmt: ureg.get_base_units('yard / minute') - name: build cache setup: | import pint ureg = pint.UnitRegistry() stmt: ureg._build_cache() pint-0.10.1/bench/bench_numpy.yaml000066400000000000000000000010301360526634500170210ustar00rootroot00000000000000 - name: NumPy setup: | import numpy as np import pint ureg = pint.UnitRegistry() stmts: - name: cosine setup: | d = np.arange(0, 90, 10) r = np.deg2rad(d) base: np.cos(r) stmts: - name: radian setup: x = r * ureg.radian stmt: np.cos(x) - name: dimensionless setup: x = r * ureg.dimensionless stmt: np.cos(x) - name: degree setup: x = d * ureg.degree stmt: np.cos(x) pint-0.10.1/bors.toml000066400000000000000000000000711360526634500144150ustar00rootroot00000000000000status = [ "continuous-integration/travis-ci/push", ] pint-0.10.1/docs/000077500000000000000000000000001360526634500135055ustar00rootroot00000000000000pint-0.10.1/docs/Makefile000066400000000000000000000130021360526634500151410ustar00rootroot00000000000000# 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.10.1/docs/_static/000077500000000000000000000000001360526634500151335ustar00rootroot00000000000000pint-0.10.1/docs/_static/logo-full.jpg000066400000000000000000000270071360526634500175430ustar00rootroot00000000000000JFIFHH@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.10.1/docs/_templates/000077500000000000000000000000001360526634500156425ustar00rootroot00000000000000pint-0.10.1/docs/_templates/sidebarintro.html000066400000000000000000000013361360526634500212200ustar00rootroot00000000000000

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.10.1/docs/_templates/sidebarlogo.html000066400000000000000000000015551360526634500210300ustar00rootroot00000000000000

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.10.1/docs/_themes/000077500000000000000000000000001360526634500151315ustar00rootroot00000000000000pint-0.10.1/docs/_themes/.gitignore000066400000000000000000000000261360526634500171170ustar00rootroot00000000000000*.pyc *.pyo .DS_Store pint-0.10.1/docs/_themes/LICENSE000066400000000000000000000033751360526634500161460ustar00rootroot00000000000000Copyright (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.10.1/docs/_themes/README000066400000000000000000000021051360526634500160070ustar00rootroot00000000000000Flask 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.10.1/docs/_themes/flask/000077500000000000000000000000001360526634500162315ustar00rootroot00000000000000pint-0.10.1/docs/_themes/flask/layout.html000066400000000000000000000017771360526634500204500ustar00rootroot00000000000000{%- 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.10.1/docs/_themes/flask/relations.html000066400000000000000000000007711360526634500211240ustar00rootroot00000000000000

Related Topics

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

Previous
{{ prev.title }}

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

Next
{{ next.title }}

{%- endif %} pint-0.10.1/docs/_themes/flask/static/000077500000000000000000000000001360526634500175205ustar00rootroot00000000000000pint-0.10.1/docs/_themes/flask/static/flasky.css_t000066400000000000000000000144441360526634500220550ustar00rootroot00000000000000/* * 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.10.1/docs/_themes/flask/static/small_flask.css000066400000000000000000000017201360526634500225220ustar00rootroot00000000000000/* * 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.10.1/docs/_themes/flask/theme.conf000066400000000000000000000002771360526634500202100ustar00rootroot00000000000000[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.10.1/docs/_themes/flask_theme_support.py000066400000000000000000000075011360526634500215640ustar00rootroot00000000000000# 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.10.1/docs/conf.py000066400000000000000000000234571360526634500150170ustar00rootroot00000000000000#!/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 import pkg_resources # 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.doctest", "sphinx.ext.intersphinx", "sphinx.ext.coverage", "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx.ext.mathjax", "matplotlib.sphinxext.plot_directive", "nbsphinx", ] # 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 = pkg_resources.get_distribution(project).version 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 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 = {"http://docs.python.org/": None} pint-0.10.1/docs/contexts.rst000066400000000000000000000250321360526634500161100ustar00rootroot00000000000000.. _contexts: 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:: >>> ureg.enable_contexts('sp') To disable the context, just call:: >>> ureg.disable_contexts() Enabling multiple contexts -------------------------- You can enable multiple contexts: >>> q.to('Hz', 'sp', 'boltzmann') This works also using the `with` statement: >>> with ureg.context('sp', 'boltzmann'): ... q.to('Hz') or in the registry: >>> 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: >>> 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) 398.496240602 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") 299792.458 kilometer It is also possible to create anonymous contexts without invoking add_context: >>> c = pint.Context() ... >>> ureg("1 s").to("km", c) 299792.458 kilometer 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.10.1/docs/contributing.rst000066400000000000000000000024351360526634500167520ustar00rootroot00000000000000.. _contributing: Contributing to Pint ==================== 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 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 submitting new code, add tests 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 ``black -t py36 . && isort -rc . && flake8`` and resolve any issues. Pint uses `bors-ng` as a merge bot and therefore every PR is tested before merging. In any case, feel free to use the `issue tracker`_ to discuss ideas for new features or improvements. .. _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/pint-0.10.1/docs/currencies.rst000066400000000000000000000061311360526634500164020ustar00rootroot00000000000000.. _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.10.1/docs/defining.rst000066400000000000000000000114501360526634500160230ustar00rootroot00000000000000.. _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.23888438100961 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. pint-0.10.1/docs/developers_reference.rst000066400000000000000000000036561360526634500204370ustar00rootroot00000000000000=================== Developer reference =================== Pint ==== .. 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.unit :members: .. automodule:: pint.util :members: .. automodule:: pint.testsuite.helpers :members: .. automodule:: pint.testsuite.parameterized :members: .. automodule:: pint.testsuite.test_babel :members: .. automodule:: pint.testsuite.test_contexts :members: .. automodule:: pint.testsuite.test_converters :members: .. automodule:: pint.testsuite.test_definitions :members: .. automodule:: pint.testsuite.test_errors :members: .. automodule:: pint.testsuite.test_formatter :members: .. automodule:: pint.testsuite.test_infer_base_unit :members: .. automodule:: pint.testsuite.test_issues :members: .. automodule:: pint.testsuite.test_measurement :members: .. automodule:: pint.testsuite.test_numpy :members: .. automodule:: pint.testsuite.test_pint_eval :members: .. automodule:: pint.testsuite.test_pitheorem :members: .. automodule:: pint.testsuite.test_quantity :members: .. automodule:: pint.testsuite.test_systems :members: .. automodule:: pint.testsuite.test_umath :members: .. automodule:: pint.testsuite.test_unit :members: .. automodule:: pint.testsuite.test_util :members:pint-0.10.1/docs/faq.rst000066400000000000000000000022031360526634500150030ustar00rootroot00000000000000.. _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.10.1/docs/getting.rst000066400000000000000000000041731360526634500157050ustar00rootroot00000000000000.. _getting: Installation ============ Pint has no dependencies except Python_ itself. In runs on Python 3.6+. 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: .. testcode:: >>> import pint # doctest: +SKIP >>> pint.__version__ # doctest: +SKIP .. 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 You can check the installation with the following command: >>> pint.test() # doctest: +SKIP On Arch Linux, you can alternatively install Pint from the Arch User Repository (AUR). The latest release is available as `python-pint`_, and packages tracking the master branch of the GitHub repository are available as `python-pint-git`_ and `python2-pint-git`_. 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 .. _`python-pint`: https://aur.archlinux.org/packages/python-pint/ .. _`python-pint-git`: https://aur.archlinux.org/packages/python-pint-git/ .. _`python2-pint-git`: https://aur.archlinux.org/packages/python2-pint-git/ .. _PyPI: https://pypi.python.org/pypi/Pint/ .. _GitHub: https://github.com/hgrecco/pint pint-0.10.1/docs/index.rst000066400000000000000000000140151360526634500153470ustar00rootroot00000000000000: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.6+ 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! 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`_. 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 numpy nonmult wrapping plotting serialization pitheorem contexts measurement defining performance systems currencies pint-pandas.ipynb 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/ .. _`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 pint-0.10.1/docs/make.bat000066400000000000000000000117441360526634500151210ustar00rootroot00000000000000@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.10.1/docs/measurement.rst000066400000000000000000000033231360526634500165650ustar00rootroot00000000000000.. _measurement: Using Measurements ================== 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` operator. .. doctest:: >>> 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 .. testsetup:: * import numpy as np from pint import UnitRegistry ureg = UnitRegistry() Q_ = ureg.Quantity You can inspect the mean value, the absolute error and the relative error: .. doctest:: >>> 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:: >>> 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:: >>> print('{:.02fP}'.format(book_length)) (20.00 ± 2.00) centimeter Mathematical operations with Measurements, return new measurements following the `Propagation of uncertainty`_ rules. .. doctest:: >>> print(2 * book_length) (40.0 +/- 4.0) 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 pint-0.10.1/docs/nonmult.rst000066400000000000000000000135611360526634500157410ustar00rootroot00000000000000.. _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: .. testsetup:: from pint import UnitRegistry ureg = UnitRegistry() ureg.default_format = '.3f' Q_ = ureg.Quantity .. doctest:: >>> from pint import UnitRegistry >>> ureg = UnitRegistry() >>> Q_ = ureg.Quantity >>> home = Q_(25.4, ureg.degC) >>> print(home.to('degF')) 77.7200004 degF or to other kelvin or rankine: .. doctest:: >>> print(home.to('kelvin')) 298.55 kelvin >>> print(home.to('degR')) 537.39 degR 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.3 kelvin >>> print(increase.to(ureg.delta_degF)) 22.14 delta_degF 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.0 delta_degC / 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. >>> 1/T >>> T * 10 * ureg.meter You can change the behaviour at any time: >>> 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_degC / meter but not here: .. doctest:: >>> print(ureg.parse_units('degC')) degC You can override this behaviour: .. doctest:: >>> print(ureg.parse_units('degC/meter', as_delta=False)) degC / 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.10.1/docs/numpy.ipynb000066400000000000000000001067771360526634500157420ustar00rootroot00000000000000{ "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", "First, we import the relevant packages:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Import NumPy\n", "import numpy as np\n", "\n", "# Disable Pint's old fallback behavior (must come before importing Pint)\n", "import os\n", "os.environ['PINT_ARRAY_PROTOCOL_FALLBACK'] = \"0\"\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": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3.0 4.0] meter\n" ] } ], "source": [ "legs1 = Q_(np.asarray([3., 4.]), 'meter')\n", "print(legs1)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3.0 4.0] meter\n" ] } ], "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": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0.003 0.004] kilometer\n" ] } ], "source": [ "print(legs1.to('kilometer'))" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[length]\n" ] } ], "source": [ "print(legs1.dimensionality)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cannot convert from 'meter' ([length]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2)\n" ] } ], "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": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[400.0 300.0] centimeter\n" ] } ], "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": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[5.0 5.0] meter\n" ] } ], "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": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0.6435011087932843 0.9272952180016123] radian\n" ] } ], "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": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[36.86989764584401 53.13010235415599] degree\n" ] } ], "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": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Cannot convert from 'centimeter' ([length]) to 'dimensionless' (dimensionless)\n" ] } ], "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`, `copysign`, `nextafter`, `modf`, `ldexp`, `frexp`, `fmod`, `floor`, `ceil`, `trunc`\n", "\n", "And the following NumPy functions:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['alen', 'all', 'amax', 'amin', 'any', 'append', 'argmax', 'argmin', 'argsort', 'around', 'atleast_1d', 'atleast_2d', 'atleast_3d', 'average', 'block', 'broadcast_to', 'clip', 'column_stack', 'compress', 'concatenate', 'copy', 'copyto', 'count_nonzero', 'cross', 'cumprod', 'cumproduct', 'cumsum', 'diagonal', 'diff', 'dot', 'dstack', 'ediff1d', 'einsum', 'empty_like', 'expand_dims', 'fix', 'flip', 'full_like', 'gradient', 'hstack', 'insert', 'interp', 'isclose', 'iscomplex', 'isin', 'isreal', 'linalg.solve', 'linspace', 'mean', 'median', 'meshgrid', 'moveaxis', 'nan_to_num', 'nanargmax', 'nanargmin', 'nancumprod', 'nancumsum', 'nanmax', 'nanmean', 'nanmedian', 'nanmin', 'nanpercentile', 'nanstd', 'nansum', 'nanvar', 'ndim', 'nonzero', 'ones_like', 'pad', 'percentile', 'ptp', 'ravel', 'reshape', 'resize', 'result_type', 'rollaxis', 'rot90', 'round_', 'searchsorted', 'shape', 'size', 'sort', 'squeeze', 'stack', 'std', 'sum', 'swapaxes', 'tile', 'transpose', 'trapz', 'trim_zeros', 'unwrap', 'var', 'vstack', 'where', 'zeros_like']\n" ] } ], "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": 13, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "%3\n", "\n", "\n", "Dask array\n", "\n", "Dask array\n", "\n", "\n", "NumPy ndarray\n", "\n", "NumPy ndarray\n", "\n", "\n", "Dask array->NumPy ndarray\n", "\n", "\n", "\n", "\n", "CuPy ndarray\n", "\n", "CuPy ndarray\n", "\n", "\n", "Dask array->CuPy ndarray\n", "\n", "\n", "\n", "\n", "Sparse COO\n", "\n", "Sparse COO\n", "\n", "\n", "Dask array->Sparse COO\n", "\n", "\n", "\n", "\n", "NumPy masked array\n", "\n", "NumPy masked array\n", "\n", "\n", "Dask array->NumPy masked array\n", "\n", "\n", "\n", "\n", "CuPy ndarray->NumPy ndarray\n", "\n", "\n", "\n", "\n", "Sparse COO->NumPy ndarray\n", "\n", "\n", "\n", "\n", "NumPy masked array->NumPy ndarray\n", "\n", "\n", "\n", "\n", "Jax array\n", "\n", "Jax array\n", "\n", "\n", "Jax array->NumPy ndarray\n", "\n", "\n", "\n", "\n", "Pint Quantity\n", "\n", "Pint Quantity\n", "\n", "\n", "Pint Quantity->Dask array\n", "\n", "\n", "\n", "\n", "Pint Quantity->NumPy ndarray\n", "\n", "\n", "\n", "\n", "Pint Quantity->CuPy ndarray\n", "\n", "\n", "\n", "\n", "Pint Quantity->Sparse COO\n", "\n", "\n", "\n", "\n", "Pint Quantity->NumPy masked array\n", "\n", "\n", "\n", "\n", "xarray Dataset/DataArray/Variable\n", "\n", "xarray Dataset/DataArray/Variable\n", "\n", "\n", "xarray Dataset/DataArray/Variable->Dask array\n", "\n", "\n", "\n", "\n", "xarray Dataset/DataArray/Variable->NumPy ndarray\n", "\n", "\n", "\n", "\n", "xarray Dataset/DataArray/Variable->CuPy ndarray\n", "\n", "\n", "\n", "\n", "xarray Dataset/DataArray/Variable->Sparse COO\n", "\n", "\n", "\n", "\n", "xarray Dataset/DataArray/Variable->NumPy masked array\n", "\n", "\n", "\n", "\n", "xarray Dataset/DataArray/Variable->Jax array\n", "\n", "\n", "\n", "\n", "xarray Dataset/DataArray/Variable->Pint Quantity\n", "\n", "\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "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": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "Coordinates:\n", " * lat (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0\n", " * lon (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0\n", " time datetime64[ns] 2013-01-01\n", "Attributes:\n", " long_name: 4xDaily Air temperature at sigma level 995\n", " precision: 2\n", " GRIB_id: 11\n", " GRIB_name: TMP\n", " var_desc: Air temperature\n", " dataset: NMC Reanalysis\n", " level_desc: Surface\n", " statistic: Individual Obs\n", " parent_stat: Other\n", " actual_range: [185.16 322.1 ]\n", "\n", "\n", "\n", "Coordinates:\n", " time datetime64[ns] 2013-01-01\n" ] } ], "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": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " meter\n", "\n", "0.09488747484058625 meter\n" ] } ], "source": [ "from sparse import COO\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": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "masked_array(data=[, --, , --],\n", " mask=[False, True, False, True],\n", " fill_value='?',\n", " dtype=object)\n" ] } ], "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": [ "**xarray wrapping Pint Quantity wrapping Dask array wrapping Sparse COO**" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", ", 'meter')>\n", "Coordinates:\n", " * z (z) int64 0 1 2 3 4 5 6 7 8 9 10 ... 90 91 92 93 94 95 96 97 98 99\n", " * y (y) int64 -50 -49 -48 -47 -46 -45 -44 -43 ... 43 44 45 46 47 48 49\n", " * x (x) float64 -20.0 -18.5 -17.0 -15.5 ... 124.0 125.5 127.0 128.5\n", "\n", "\n", ", 'meter')>\n", "Coordinates:\n", " y int64 -46\n", " x float64 125.5\n" ] } ], "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 ([in development](https://github.com/hgrecco/pint/issues/849), initial alpha release planned for January 2020)\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.html)).\n", "\n", "Array interface protocol attributes (such as `__array_struct__` and\n", "`__array_interface__`) are available on Pint Quantities by deferring to the corresponding `__array_*` attribute on the magnitude as casted to an ndarray. This has been found to be potentially incorrect and to cause unexpected behavior, and has therefore been deprecated. As of the next minor version of Pint (or when the `PINT_ARRAY_PROTOCOL_FALLBACK` environment variable is set to 0 prior to importing Pint as done at the beginning of this page), attempting to access these attributes will instead raise an AttributeError." ] } ], "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.6.7" } }, "nbformat": 4, "nbformat_minor": 4 } pint-0.10.1/docs/performance.rst000066400000000000000000000060541360526634500165450ustar00rootroot00000000000000.. _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 .. _`brentq method`: http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brentq.html pint-0.10.1/docs/pint-pandas.ipynb000066400000000000000000001231661360526634500167770ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Pandas support\n", "\n", "It is convenient to use the Pandas package when dealing with numerical data, so Pint provides PintArray. A PintArray is a Pandas Extension Array, which allows Pandas to recognise the Quantity and store it in Pandas DataFrames and Series." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example will show the simplist way to use pandas with pint and the underlying objects. It's slightly fiddly as you are not reading from a file. A more normal use case is given in Reading a csv.\n", "\n", "First some imports" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pandas as pd \n", "import pint" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we create a DataFrame with PintArrays as columns." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
torqueangular_velocity
011
122
222
333
\n", "
" ], "text/plain": [ " torque angular_velocity\n", "0 1 1\n", "1 2 2\n", "2 2 2\n", "3 3 3" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.DataFrame({\n", " \"torque\": pd.Series([1, 2, 2, 3], dtype=\"pint[lbf ft]\"),\n", " \"angular_velocity\": pd.Series([1, 2, 2, 3], dtype=\"pint[rpm]\"),\n", "})\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Operations with columns are units aware so behave as we would intuitively expect." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
torqueangular_velocitypower
0111
1224
2224
3339
\n", "
" ], "text/plain": [ " torque angular_velocity power\n", "0 1 1 1\n", "1 2 2 4\n", "2 2 2 4\n", "3 3 3 9" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df['power'] = df['torque'] * df['angular_velocity']\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see the columns' units in the dtypes attribute" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "torque pint[foot * force_pound]\n", "angular_velocity pint[revolutions_per_minute]\n", "power pint[foot * force_pound * revolutions_per_minute]\n", "dtype: object" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.dtypes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each column can be accessed as a Pandas Series" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 1\n", "1 4\n", "2 4\n", "3 9\n", "Name: power, dtype: pint[foot * force_pound * revolutions_per_minute]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.power" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Which contains a PintArray" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "PintArray([1 foot * force_pound * revolutions_per_minute,\n", " 4 foot * force_pound * revolutions_per_minute,\n", " 4 foot * force_pound * revolutions_per_minute,\n", " 9 foot * force_pound * revolutions_per_minute],\n", " dtype='pint[foot * force_pound * revolutions_per_minute]')" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.power.values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The PintArray contains a Quantity" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\\[\\begin{pmatrix}1 & 4 & 4 & 9\\end{pmatrix} foot force_pound revolutions_per_minute\\]" ], "text/latex": [ "$\\begin{pmatrix}1 & 4 & 4 & 9\\end{pmatrix}\\ \\mathrm{foot} \\cdot \\mathrm{force_pound} \\cdot \\mathrm{revolutions_per_minute}$" ], "text/plain": [ "array([1, 4, 4, 9]) " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.power.values.quantity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pandas Series accessors are provided for most Quantity properties and methods, which will convert the result to a Series where possible." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "foot force_pound revolutions_per_minute" ], "text/latex": [ "$\\mathrm{foot} \\cdot \\mathrm{force_pound} \\cdot \\mathrm{revolutions_per_minute}$" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.power.pint.units" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "PintArray([0.00014198092353610376 kilowatt, 0.000567923694144415 kilowatt,\n", " 0.000567923694144415 kilowatt, 0.0012778283118249339 kilowatt],\n", " dtype='pint[kilowatt]')" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.power.pint.to(\"kW\").values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reading from csv\n", "\n", "Reading from files is the far more standard way to use pandas. To facilitate this, DataFrame accessors are provided to make it easy to get to PintArrays. " ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "import pandas as pd \n", "import pint\n", "import io" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's the contents of the csv file." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "test_data = '''speed,mech power,torque,rail pressure,fuel flow rate,fluid power\n", "rpm,kW,N m,bar,l/min,kW\n", "1000.0,,10.0,1000.0,10.0,\n", "1100.0,,10.0,100000000.0,10.0,\n", "1200.0,,10.0,1000.0,10.0,\n", "1200.0,,10.0,1000.0,10.0,'''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's read that into a DataFrame.\n", "Here io.StringIO is used in place of reading a file from disk, whereas a csv file path would typically be used and is shown commented." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speedmech powertorquerail pressurefuel flow ratefluid power
rpmkWN mbarl/minkW
01000.0NaN10.01000.010.0NaN
11100.0NaN10.0100000000.010.0NaN
21200.0NaN10.01000.010.0NaN
31200.0NaN10.01000.010.0NaN
\n", "
" ], "text/plain": [ " speed mech power torque rail pressure fuel flow rate fluid power\n", " rpm kW N m bar l/min kW\n", "0 1000.0 NaN 10.0 1000.0 10.0 NaN\n", "1 1100.0 NaN 10.0 100000000.0 10.0 NaN\n", "2 1200.0 NaN 10.0 1000.0 10.0 NaN\n", "3 1200.0 NaN 10.0 1000.0 10.0 NaN" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.read_csv(io.StringIO(test_data),header=[0,1])\n", "# df = pd.read_csv(\"/path/to/test_data.csv\",header=[0,1])\n", "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then use the DataFrame's pint accessor's quantify method to convert the columns from `np.ndarray`s to PintArrays, with units from the bottom column level." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "speed rpm float64\n", "mech power kW float64\n", "torque N m float64\n", "rail pressure bar float64\n", "fuel flow rate l/min float64\n", "fluid power kW float64\n", "dtype: object" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.dtypes" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speedmech powertorquerail pressurefuel flow ratefluid power
01000.0nan10.01000.010.0nan
11100.0nan10.0100000000.010.0nan
21200.0nan10.01000.010.0nan
31200.0nan10.01000.010.0nan
\n", "
" ], "text/plain": [ " speed mech power torque rail pressure fuel flow rate fluid power\n", "0 1000.0 nan 10.0 1000.0 10.0 nan\n", "1 1100.0 nan 10.0 100000000.0 10.0 nan\n", "2 1200.0 nan 10.0 1000.0 10.0 nan\n", "3 1200.0 nan 10.0 1000.0 10.0 nan" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_ = df.pint.quantify(level=-1)\n", "df_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As previously, operations between DataFrame columns are unit aware" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0 10000.0\n", "1 11000.0\n", "2 12000.0\n", "3 12000.0\n", "dtype: pint[meter * newton * revolutions_per_minute]" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_.speed*df_.torque" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speedmech powertorquerail pressurefuel flow ratefluid power
01000.0nan10.01000.010.0nan
11100.0nan10.0100000000.010.0nan
21200.0nan10.01000.010.0nan
31200.0nan10.01000.010.0nan
\n", "
" ], "text/plain": [ " speed mech power torque rail pressure fuel flow rate fluid power\n", "0 1000.0 nan 10.0 1000.0 10.0 nan\n", "1 1100.0 nan 10.0 100000000.0 10.0 nan\n", "2 1200.0 nan 10.0 1000.0 10.0 nan\n", "3 1200.0 nan 10.0 1000.0 10.0 nan" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speedmech powertorquerail pressurefuel flow ratefluid power
01000.010000.010.01000.010.010000.0
11100.011000.010.0100000000.010.01000000000.0
21200.012000.010.01000.010.010000.0
31200.012000.010.01000.010.010000.0
\n", "
" ], "text/plain": [ " speed mech power torque rail pressure fuel flow rate fluid power\n", "0 1000.0 10000.0 10.0 1000.0 10.0 10000.0\n", "1 1100.0 11000.0 10.0 100000000.0 10.0 1000000000.0\n", "2 1200.0 12000.0 10.0 1000.0 10.0 10000.0\n", "3 1200.0 12000.0 10.0 1000.0 10.0 10000.0" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_['mech power'] = df_.speed*df_.torque\n", "df_['fluid power'] = df_['fuel flow rate'] * df_['rail pressure']\n", "df_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The DataFrame's `pint.dequantify` method then allows us to retrieve the units information as a header row once again." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speedmech powertorquerail pressurefuel flow ratefluid power
unitrevolutions_per_minutemeter * newton * revolutions_per_minutemeter * newtonbarliter / minutebar * liter / minute
01000.010000.010.01000.010.01.000000e+04
11100.011000.010.0100000000.010.01.000000e+09
21200.012000.010.01000.010.01.000000e+04
31200.012000.010.01000.010.01.000000e+04
\n", "
" ], "text/plain": [ " speed mech power \\\n", "unit revolutions_per_minute meter * newton * revolutions_per_minute \n", "0 1000.0 10000.0 \n", "1 1100.0 11000.0 \n", "2 1200.0 12000.0 \n", "3 1200.0 12000.0 \n", "\n", " torque rail pressure fuel flow rate fluid power \n", "unit meter * newton bar liter / minute bar * liter / minute \n", "0 10.0 1000.0 10.0 1.000000e+04 \n", "1 10.0 100000000.0 10.0 1.000000e+09 \n", "2 10.0 1000.0 10.0 1.000000e+04 \n", "3 10.0 1000.0 10.0 1.000000e+04 " ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_.pint.dequantify()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This allows for some rather powerful abilities. For example, to change single column units" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speedmech powertorquerail pressurefuel flow ratefluid power
unitrevolutions_per_minutekilowattmeter * newtonbarliter / minutekilowatt
01000.01.04719810.01000.010.01.666667e+01
11100.01.15191710.0100000000.010.01.666667e+06
21200.01.25663710.01000.010.01.666667e+01
31200.01.25663710.01000.010.01.666667e+01
\n", "
" ], "text/plain": [ " speed mech power torque rail pressure \\\n", "unit revolutions_per_minute kilowatt meter * newton bar \n", "0 1000.0 1.047198 10.0 1000.0 \n", "1 1100.0 1.151917 10.0 100000000.0 \n", "2 1200.0 1.256637 10.0 1000.0 \n", "3 1200.0 1.256637 10.0 1000.0 \n", "\n", " fuel flow rate fluid power \n", "unit liter / minute kilowatt \n", "0 10.0 1.666667e+01 \n", "1 10.0 1.666667e+06 \n", "2 10.0 1.666667e+01 \n", "3 10.0 1.666667e+01 " ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_['fluid power'] = df_['fluid power'].pint.to(\"kW\")\n", "df_['mech power'] = df_['mech power'].pint.to(\"kW\")\n", "df_.pint.dequantify()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The units are harder to read than they need be, so lets change pints default format for displaying units." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speedmech powertorquerail pressurefuel flow ratefluid power
unitrpmkWN·mbarl/minkW
01000.01.04719810.01000.010.01.666667e+01
11100.01.15191710.0100000000.010.01.666667e+06
21200.01.25663710.01000.010.01.666667e+01
31200.01.25663710.01000.010.01.666667e+01
\n", "
" ], "text/plain": [ " speed mech power torque rail pressure fuel flow rate fluid power\n", "unit rpm kW N·m bar l/min kW\n", "0 1000.0 1.047198 10.0 1000.0 10.0 1.666667e+01\n", "1 1100.0 1.151917 10.0 100000000.0 10.0 1.666667e+06\n", "2 1200.0 1.256637 10.0 1000.0 10.0 1.666667e+01\n", "3 1200.0 1.256637 10.0 1000.0 10.0 1.666667e+01" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pint.PintType.ureg.default_format = \"~P\"\n", "df_.pint.dequantify()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or the entire table's units" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
speedmech powertorquerail pressurefuel flow ratefluid power
unitrad/skg·m²/s³kg·m²/s²kg/m/s²m³/skg·m²/s³
0104.7197551047.19755110.01.000000e+080.0001671.666667e+04
1115.1917311151.91730610.01.000000e+130.0001671.666667e+09
2125.6637061256.63706110.01.000000e+080.0001671.666667e+04
3125.6637061256.63706110.01.000000e+080.0001671.666667e+04
\n", "
" ], "text/plain": [ " speed mech power torque rail pressure fuel flow rate \\\n", "unit rad/s kg·m²/s³ kg·m²/s² kg/m/s² m³/s \n", "0 104.719755 1047.197551 10.0 1.000000e+08 0.000167 \n", "1 115.191731 1151.917306 10.0 1.000000e+13 0.000167 \n", "2 125.663706 1256.637061 10.0 1.000000e+08 0.000167 \n", "3 125.663706 1256.637061 10.0 1.000000e+08 0.000167 \n", "\n", " fluid power \n", "unit kg·m²/s³ \n", "0 1.666667e+04 \n", "1 1.666667e+09 \n", "2 1.666667e+04 \n", "3 1.666667e+04 " ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df_.pint.to_base_units().pint.dequantify()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Advanced example\n", "This example shows alternative ways to use pint with pandas and other features.\n", "\n", "Start with the same imports." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "import pandas as pd \n", "import pint" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll be use a shorthand for PintArray" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "PA_ = pint.PintArray" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And set up a unit registry and quantity shorthand." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "ureg=pint.UnitRegistry()\n", "Q_=ureg.Quantity" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Operations between PintArrays of different unit registry will not work. We can change the unit registry that will be used in creating new PintArrays to prevent this issue." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "pint.PintType.ureg = ureg" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These are the possible ways to create a PintArray.\n", "\n", "Note that pint[unit] must be used for the Series constuctor, whereas the PintArray constructor allows the unit string or object." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
lengthwidthdistanceheightdepth
012222
123333
\n", "
" ], "text/plain": [ " length width distance height depth\n", "0 1 2 2 2 2\n", "1 2 3 3 3 3" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.DataFrame({\n", " \"length\" : pd.Series([1,2], dtype=\"pint[m]\"),\n", " \"width\" : PA_([2,3], dtype=\"pint[m]\"),\n", " \"distance\" : PA_([2,3], dtype=\"m\"),\n", " \"height\" : PA_([2,3], dtype=ureg.m),\n", " \"depth\" : PA_.from_1darray_quantity(Q_([2,3],ureg.m)),\n", " })\n", "df" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "meter" ], "text/latex": [ "$\\mathrm{meter}$" ], "text/plain": [ "" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.length.values.units" ] } ], "metadata": { "anaconda-cloud": {}, "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.6.5" } }, "nbformat": 4, "nbformat_minor": 2 } pint-0.10.1/docs/pitheorem.rst000066400000000000000000000071111360526634500162330ustar00rootroot00000000000000.. _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, 'g': 1.0, 'L': -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.10.1/docs/plotting.rst000066400000000000000000000034021360526634500160760ustar00rootroot00000000000000.. _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.10.1/docs/serialization.rst000066400000000000000000000104011360526634500171100ustar00rootroot00000000000000.. _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: .. testsetup:: * import pint .. 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.0),)) And then you can just pickle that: >>> import pickle >>> serialized = pickle.dumps(to_serialize, -1) To unpickle, just >>> 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: >>> 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.10.1/docs/systems.rst000066400000000000000000000050221360526634500157450ustar00rootroot00000000000000.. _systems: Different Unit Systems (and default units) ========================================== Pint Unit Registry has the concept of system, which is a group of units >>> import pint >>> ureg = pint.UnitRegistry(system='mks') >>> ureg.default_system 'mks' This has an effect in the base units. For example: >>> q = 3600. * ureg.meter / ureg.hour >>> q.to_base_units() But if you change to cgs: >>> ureg.default_system = 'cgs' >>> q.to_base_units() or more drastically to: >>> 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 also use system to narrow down the list of compatible units: >>> ureg.default_system = 'mks' >>> ureg.get_compatible_units('meter') frozenset({, }) or for imperial units: >>> ureg.default_system = 'imperial' >>> ureg.get_compatible_units('meter') frozenset({, , , , , , }) You can check which unit systems are available: >>> dir(ureg.sys) ['US', 'cgs', 'imperial', 'mks'] Or which units are available within a particular system: >>> dir(ureg.sys.imperial) ['UK_hundredweight', 'UK_ton', 'acre_foot', 'cubic_foot', 'cubic_inch', 'cubic_yard', 'drachm', 'foot', 'grain', 'imperial_barrel', 'imperial_bushel', 'imperial_cup', 'imperial_fluid_drachm', 'imperial_fluid_ounce', 'imperial_gallon', 'imperial_gill', 'imperial_peck', 'imperial_pint', 'imperial_quart', 'inch', 'long_hunderweight', 'long_ton', 'mile', 'ounce', 'pound', 'quarter', 'short_hunderdweight', 'short_ton', 'square_foot', 'square_inch', 'square_mile', 'square_yard', 'stone', 'yard'] Notice that this give you the opportunity to choose within units with colliding names: >>> (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.10.1/docs/tutorial.rst000066400000000000000000000276011360526634500161100ustar00rootroot00000000000000.. _tutorial: Tutorial ======== Converting Quantities --------------------- Pint has the concept of Unit Registry, an object within which units are defined and handled. You start by creating your registry: >>> from pint import UnitRegistry >>> ureg = UnitRegistry() .. testsetup:: * from pint import UnitRegistry ureg = UnitRegistry() Q_ = ureg.Quantity If no parameter is given to the constructor, the unit registry is populated with the default list of units and prefixes. You can now simply use the registry in the following way: .. doctest:: >>> distance = 24.0 * ureg.meter >>> print(distance) 24.0 meter >>> time = 8.0 * ureg.second >>> print(time) 8.0 second >>> print(repr(time)) In this code `distance` and `time` are physical quantity objects (`Quantity`). Physical quantities can be queried for their magnitude, units, and dimensionality: .. doctest:: >>> print(distance.magnitude) 24.0 >>> print(distance.units) meter >>> print(distance.dimensionality) [length] and can handle mathematical operations between: .. doctest:: >>> speed = distance / time >>> print(speed) 3.0 meter / second As unit registry knows about the relationship between different units, you can convert quantities to the unit of choice: .. doctest:: >>> 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.614173228345 inch / minute If you ask Pint to perform an invalid conversion: .. 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) Sometimes, the magnitude of the quantity will be very large or very small. The method 'to_compact' can adjust the units to make the 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.41448903225802 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.7526 meter >>> print(height) 5.75 foot >>> height.ito_base_units() >>> print(height) 1.7526 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 cc * gram / centimeter ** 3 >>> print(mass.to_reduced_units()) 14.0 gram >>> print(mass) 14.0 cc * 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 accepts a parameter `auto_reduce_dimensions`. Dimensional reduction can be slow, so auto-reducing is disabled by default. In some cases it is useful to define physical quantities objects using the class constructor: .. doctest:: >>> Q_ = ureg.Quantity >>> Q_(1.78, ureg.meter) == 1.78 * ureg.meter True (I tend to abbreviate Quantity as `Q_`) 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 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 registry or build your own list. More info on that :ref:`defining` String parsing -------------- Pint can also handle units provided as strings: .. doctest:: >>> 2.54 * ureg.parse_expression('centimeter') or using the registry as a callable for a short form for `parse_expression`: .. doctest:: >>> 2.54 * ureg('centimeter') or using the `Quantity` constructor: .. doctest:: >>> Q_(2.54, 'centimeter') Numbers are also parsed, so you can use an expression: .. doctest:: >>> ureg('2.54 * centimeter') or: .. doctest:: >>> 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) Dimensionless quantities can also be parsed into an appropriate object: .. doctest:: >>> ureg('2.54') 2.54 >>> type(ureg('2.54')) or .. doctest:: >>> 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 unit of .. doctest:: >>> Q_('3 l / 100 km') may be unexpected first but is a consequence of applying this rule. Use brackets to get the expected result: .. doctest:: >>> Q_('3 l / (100 km)') .. 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. .. _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:: >>> 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}') The str is 1.3 m/s² But Pint also extends the standard formatting capabilities for unicode and LaTeX 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 >>> '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:: >>> 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} Additionally, you can specify a default format specification: .. doctest:: >>> '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²' Finally, 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 u >>> ureg = UnitRegistry(fmt_locale='fr_FR') 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`:: from pint import UnitRegistry ureg = UnitRegistry() Q_ = ureg.Quantity Then in `yourmodule.py` the code would be:: from . import ureg, Q_ length = 10 * ureg.meter my_speed = Q_(20, 'm/s') If you are pickling and unplicking Quantities within your project, you should also define the registry as the application registry:: 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 .. _eval: http://docs.python.org/3/library/functions.html#eval .. _`serious security problems`: http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html .. _`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.10.1/docs/wrapping.rst000066400000000000000000000171451360526634500160760ustar00rootroot00000000000000.. _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() >>> 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:: @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)) ... 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 = 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') >>> tcalculate_time_to_fall(lunar_module_height, 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: >>> @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.10.1/pint/000077500000000000000000000000001360526634500135275ustar00rootroot00000000000000pint-0.10.1/pint/__init__.py000066400000000000000000000071271360526634500156470ustar00rootroot00000000000000""" 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. """ import sys import pkg_resources from .context import Context from .errors import ( DefinitionSyntaxError, DimensionalityError, OffsetUnitCalculusError, RedefinitionError, UndefinedUnitError, UnitStrippedWarning, ) from .formatting import formatter from .measurement import Measurement from .quantity import Quantity from .registry import LazyRegistry, UnitRegistry from .unit import Unit from .util import logger, pi_theorem try: from pintpandas import PintArray, PintType del PintType del PintArray _HAS_PINTPANDAS = True except ImportError: _HAS_PINTPANDAS = False _, _pintpandas_error, _ = sys.exc_info() try: # pragma: no cover __version__ = pkg_resources.get_distribution("pint").version 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. _APP_REGISTRY = _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: _APP_REGISTRY.parse_units(name) return cls(*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 """ if not isinstance(registry, (LazyRegistry, UnitRegistry)): raise TypeError("Expected UnitRegistry; got %s" % type(registry)) global _APP_REGISTRY logger.debug("Changing app registry from %r to %r.", _APP_REGISTRY, registry) _APP_REGISTRY = 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 _APP_REGISTRY def test(): """Run all tests. Returns ------- unittest.TestResult """ from .testsuite import run return run() # 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", "DefinitionSyntaxError", "DimensionalityError", "OffsetUnitCalculusError", "RedefinitionError", "UndefinedUnitError", "UnitStrippedWarning", "formatter", "get_application_registry", "set_application_registry", "pi_theorem", "__version__", ) pint-0.10.1/pint/babel_names.py000066400000000000000000000107421360526634500163350ustar00rootroot00000000000000""" pint.babel ~~~~~~~~~~ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from pint.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.10.1/pint/compat.py000066400000000000000000000141741360526634500153730ustar00rootroot00000000000000""" pint.compat ~~~~~~~~~~~ Compatibility layer. :copyright: 2013 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import os 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 array_function_change_msg = """The way Pint handles NumPy operations has changed with the implementation of NEP 18. Unimplemented NumPy operations will now fail instead of making assumptions about units. Some functions, eg concat, will now return Quanties with units, where they returned ndarrays previously. See https://github.com/hgrecco/pint/pull/905. To hide this warning, wrap your first creation of an array Quantity with warnings.catch_warnings(), like the following: import numpy as np import warnings from pint import Quantity with warnings.catch_warnings(): warnings.simplefilter("ignore") Quantity([]) To disable the new behavior, see https://www.numpy.org/neps/nep-0018-array-function-protocol.html#implementation """ try: import numpy as np 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() SKIP_ARRAY_FUNCTION_CHANGE_WARNING = not HAS_NUMPY_ARRAY_FUNCTION NP_NO_VALUE = np._NoValue ARRAY_FALLBACK = bool(int(os.environ.get("PINT_ARRAY_PROTOCOL_FALLBACK", 1))) except ImportError: np = None class ndarray: pass HAS_NUMPY = False NUMPY_VER = "0" NUMERIC_TYPES = (Number, Decimal) HAS_NUMPY_ARRAY_FUNCTION = False SKIP_ARRAY_FUNCTION_CHANGE_WARNING = True NP_NO_VALUE = None ARRAY_FALLBACK = False 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 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 pintpandas 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 def is_upcast_type(other): """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(other): """Check if the type object represents a (non-Quantity) duck array type. Parameters ---------- other : object Returns ------- bool """ # TODO (NEP 30): replace duck array check with hasattr(other, "__duckarray__") return other is ndarray or ( not hasattr(other, "_magnitude") and not hasattr(other, "_units") and HAS_NUMPY_ARRAY_FUNCTION and hasattr(other, "__array_function__") and hasattr(other, "ndim") and hasattr(other, "dtype") ) def eq(lhs, rhs, check_all): """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. Returns ------- bool or array_like of bool """ out = lhs == rhs if check_all and isinstance(out, ndarray): return np.all(out) return out pint-0.10.1/pint/constants_en.txt000066400000000000000000000104021360526634500167630ustar00rootroot00000000000000# 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 #### DEFINED EXACT CONSTANTS #### speed_of_light = 299792458 m/s = c = c_0 # since 1983 planck_constant = 6.62607015e-34 J s = h # 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 = h / (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 / h = G_0 magnetic_flux_quantum = h / (2 * e) = Φ_0 = Phi_0 josephson_constant = 2 * e / h = K_J von_klitzing_constant = h / e ** 2 = R_K stefan_boltzmann_constant = 2 / 15 * π ** 5 * k ** 4 / (h ** 3 * c ** 2) = σ = sigma first_radiation_constant = 2 * π * h * c ** 2 = c_1 second_radiation_constant = h * c / k = c_2 wien_wavelength_displacement_law_constant = h * c / (k * wien_x) wien_frequency_displacement_law_constant = wien_u * k / h #### 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 * h * R_inf / (m_e * c)) ** 0.5 = α = alpha vacuum_permeability = 2 * α * h / (e ** 2 * c) = µ_0 = mu_0 = mu0 = magnetic_constant vacuum_permittivity = e ** 2 / (2 * α * h * c) = ε_0 = epsilon_0 = eps_0 = eps0 = electric_constant impedance_of_free_space = 2 * α * h / 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.10.1/pint/context.py000066400000000000000000000262521360526634500155740ustar00rootroot00000000000000""" 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. """ import re import weakref from collections import ChainMap, defaultdict from .definitions import Definition, UnitDefinition from .errors import DefinitionSyntaxError from .util import ParserHelper, SourceIterator, to_units_container #: 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_]*") def _expression_to_function(eq): def func(ureg, value, **kwargs): return ureg.parse_expression(eq, value=value, **kwargs) return func 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. 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 >>> timedim = UnitsContainer({'[time]': 1}) >>> spacedim = UnitsContainer({'[length]': 1}) >>> def f(time): ... 'Time to length converter' ... return 3. * time >>> c = Context() >>> c.add_transformation(timedim, spacedim, f) >>> c.transform(timedim, spacedim, 2) 6 >>> def f(time, n): ... 'Time to length converter, n is the index of refraction of the material' ... return 3. * time / n >>> c = Context(n=3) >>> c.add_transformation(timedim, spacedim, f) >>> c.transform(timedim, spacedim, 2) 2 >>> c.redefine("pound = 0.5 kg") """ def __init__(self, name=None, aliases=(), defaults=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, **defaults): """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): 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 ctx = cls(name, aliases, defaults) else: ctx = cls(name, aliases) names = set() for lineno, line in lines: if "=" in line: ctx.redefine(line) continue try: rel, eq = line.split(":") names.update(_varname_re.findall(eq)) func = _expression_to_function(eq) if "<->" in rel: src, dst = (ParserHelper.from_string(s) for s in rel.split("<->")) if to_base_func: src = to_base_func(src) dst = to_base_func(dst) ctx.add_transformation(src, dst, func) ctx.add_transformation(dst, src, func) elif "->" in rel: src, dst = (ParserHelper.from_string(s) for s in rel.split("->")) if to_base_func: src = to_base_func(src) dst = to_base_func(dst) ctx.add_transformation(src, dst, func) else: raise Exception except Exception as exc: raise DefinitionSyntaxError( "Could not parse Context %s relation '%s'" % (name, line), lineno=lineno, ) from exc if defaults: missing_pars = defaults.keys() - set(names) if missing_pars: raise DefinitionSyntaxError( f"Context parameters {missing_pars} not found in any equation" ) return ctx def add_transformation(self, src, dst, func): """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): """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): 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(): d = Definition.from_string(line) if not isinstance(d, UnitDefinition): raise DefinitionSyntaxError( "Expected = ; got %s" % line.strip() ) if d.symbol != d.name or d.aliases: raise DefinitionSyntaxError( "Can't change a unit's symbol or aliases within a context" ) if d.is_base: raise DefinitionSyntaxError("Can't define base units within a context") self.redefinitions.append(d) 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__``. 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): if self: return next(iter(self.maps[0].values())).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.10.1/pint/converters.py000066400000000000000000000032171360526634500162760ustar00rootroot00000000000000""" 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. """ class Converter: """Base class for value converters.""" is_multiplicative = True def to_reference(self, value, inplace=False): return value def from_reference(self, value, inplace=False): return value class ScaleConverter(Converter): """A linear transformation.""" is_multiplicative = True 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 pint-0.10.1/pint/default_en.txt000066400000000000000000000710501360526634500164010ustar00rootroot00000000000000# 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 or 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 neper = [] = Np 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 # Logarithmic ratio bel = 0.5 * ln10 * neper # Information byte = 8 * bit = B = octet baud = bit / second = Bd = bps # 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 = 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 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 # 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 = h * 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 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 # 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 # 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 # 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 #### 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.10.1/pint/default_en_0.6.txt000066400000000000000000000267671360526634500170030ustar00rootroot00000000000000# Default Pint units definition file # Based on the International System of Units # Language: english # :copyright: 2013 by Pint Authors, see AUTHORS for more details. # 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- 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- # reference 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 radian = [] = rad bit = [] count = [] @import constants_en.txt # acceleration [acceleration] = [length] / [time] ** 2 # Angle turn = 2 * pi * radian = revolution = cycle = circle degree = pi / 180 * radian = deg = arcdeg = arcdegree = angular_degree arcminute = arcdeg / 60 = arcmin = arc_minute = angular_minute arcsecond = arcmin / 60 = arcsec = arc_second = angular_second steradian = radian ** 2 = sr # Area [area] = [length] ** 2 are = 100 * m**2 barn = 1e-28 * m ** 2 = b cmil = 5.067075e-10 * m ** 2 = circular_mils darcy = 9.869233e-13 * m ** 2 acre = 4046.8564224 * m ** 2 = international_acre hectare = 100 * are = ha US_survey_acre = 160 * rod ** 2 # EM esu = 1 * erg**0.5 * centimeter**0.5 = statcoulombs = statC = franklin = Fr esu_per_second = 1 * esu / second = statampere ampere_turn = 1 * A gilbert = 10 / (4 * pi ) * ampere_turn coulomb = ampere * second = C volt = joule / coulomb = V farad = coulomb / volt = F ohm = volt / ampere = Ω siemens = ampere / volt = S = mho weber = volt * second = Wb tesla = weber / meter ** 2 = T henry = weber / ampere = H elementary_charge = 1.602176487e-19 * coulomb = e chemical_faraday = 9.64957e4 * coulomb physical_faraday = 9.65219e4 * coulomb faraday = 96485.3399 * coulomb = C12_faraday gamma = 1e-9 * tesla gauss = 1e-4 * tesla maxwell = 1e-8 * weber = mx oersted = 1000 / (4 * pi) * A / m = Oe statfarad = 1.112650e-12 * farad = statF = stF stathenry = 8.987554e11 * henry = statH = stH statmho = 1.112650e-12 * siemens = statS = stS statohm = 8.987554e11 * ohm statvolt = 2.997925e2 * volt = statV = stV unit_pole = 1.256637e-7 * weber # Energy [energy] = [force] * [length] joule = newton * meter = J erg = dyne * centimeter btu = 1.05505585262e3 * joule = Btu = BTU = british_thermal_unit electron_volt = 1.60217653e-19 * J = eV quadrillion_btu = 10**15 * btu = quad thm = 100000 * BTU = therm = EC_therm cal = 4.184 * joule = calorie = thermochemical_calorie international_steam_table_calorie = 4.1868 * joule ton_TNT = 4.184e9 * joule = tTNT US_therm = 1.054804e8 * joule watt_hour = watt * hour = Wh = watthour hartree = 4.35974394e-18 * joule = E_h = hartree_energy # 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_ounce = g_0 * ounce = ozf = ounce_force force_pound = g_0 * lb = lbf = pound_force force_ton = 2000 * force_pound = ton_force poundal = lb * feet / second ** 2 = pdl kip = 1000*lbf # Frequency [frequency] = 1 / [time] hertz = 1 / second = Hz = rps revolutions_per_minute = revolution / minute = rpm counts_per_second = count / second = cps # Heat #RSI = degK * meter ** 2 / watt #clo = 0.155 * RSI = clos #R_value = foot ** 2 * degF * hour / btu # Information byte = 8 * bit = B = octet baud = bit / second = Bd = bps # Irradiance peak_sun_hour = 1000 * watt_hour / meter**2 = PSH langley = thermochemical_calorie / centimeter**2 = Langley # Length angstrom = 1e-10 * meter = Å = ångström = Å inch = 2.54 * centimeter = in = international_inch = inches = international_inches foot = 12 * inch = ft = international_foot = feet = international_feet mile = 5280 * foot = mi = international_mile yard = 3 * feet = yd = international_yard mil = inch / 1000 = thou parsec = 3.08568025e16 * meter = pc light_year = speed_of_light * julian_year = ly = lightyear astronomical_unit = 149597870691 * meter = au nautical_mile = 1.852e3 * meter = nmi printers_point = 127 * millimeter / 360 = point printers_pica = 12 * printers_point = pica US_survey_foot = 1200 * meter / 3937 US_survey_yard = 3 * US_survey_foot US_survey_mile = 5280 * US_survey_foot = US_statute_mile rod = 16.5 * US_survey_foot = pole = perch furlong = 660 * US_survey_foot fathom = 6 * US_survey_foot chain = 66 * US_survey_foot barleycorn = inch / 3 arpentlin = 191.835 * feet kayser = 1 / centimeter = wavenumber # Mass dram = oz / 16 = dr = avoirdupois_dram ounce = 28.349523125 * gram = oz = avoirdupois_ounce pound = 0.45359237 * kilogram = lb = avoirdupois_pound stone = 14 * lb = st carat = 200 * milligram grain = 64.79891 * milligram = gr long_hundredweight = 112 * lb short_hundredweight = 100 * lb metric_ton = 1000 * kilogram = t = tonne pennyweight = 24 * gram = dwt slug = 14.59390 * kilogram troy_ounce = 480 * grain = toz = apounce = apothecary_ounce troy_pound = 12 * toz = tlb = appound = apothecary_pound drachm = 60 * grain = apdram = apothecary_dram atomic_mass_unit = 1.660538782e-27 * kilogram = u = amu = dalton = Da scruple = 20 * grain bag = 94 * lb ton = 2000 * lb = short_ton # Textile denier = gram / (9000 * meter) tex = gram / (1000 * meter) dtex = decitex # Photometry lumen = candela * steradian = lm lux = lumen / meter ** 2 = lx # Power [power] = [energy] / [time] watt = joule / second = W = volt_ampere = VA horsepower = 33000 * ft * lbf / min = hp = UK_horsepower = British_horsepower boiler_horsepower = 33475 * btu / hour metric_horsepower = 75 * force_kilogram * meter / second electric_horsepower = 746 * watt hydraulic_horsepower = 550 * feet * lbf / second refrigeration_ton = 12000 * btu / hour = ton_of_refrigeration # Pressure [pressure] = [force] / [area] Hg = gravity * 13.59510 * gram / centimeter ** 3 = mercury = conventional_mercury mercury_60F = gravity * 13.5568 * gram / centimeter ** 3 H2O = gravity * 1000 * kilogram / meter ** 3 = h2o = water = conventional_water water_4C = gravity * 999.972 * kilogram / meter ** 3 = water_39F water_60F = gravity * 999.001 * kilogram / m ** 3 pascal = newton / meter ** 2 = Pa bar = 100000 * pascal atmosphere = 101325 * pascal = atm = standard_atmosphere technical_atmosphere = kilogram * gravity / centimeter ** 2 = at torr = atm / 760 pound_force_per_square_inch = pound * gravity / inch ** 2 = psi kip_per_square_inch = kip / inch ** 2 = ksi barye = 0.1 * newton / meter ** 2 = barie = barad = barrie = baryd = Ba mm_Hg = millimeter * Hg = mmHg = millimeter_Hg = millimeter_Hg_0C cm_Hg = centimeter * Hg = cmHg = centimeter_Hg in_Hg = inch * Hg = inHg = inch_Hg = inch_Hg_32F inch_Hg_60F = inch * mercury_60F inch_H2O_39F = inch * water_39F inch_H2O_60F = inch * water_60F footH2O = ft * water cmH2O = centimeter * water foot_H2O = ft * water = ftH2O standard_liter_per_minute = 1.68875 * Pa * m ** 3 / s = slpm = slm # Radiation Bq = Hz = becquerel curie = 3.7e10 * Bq = Ci rutherford = 1e6*Bq = rd = Rd Gy = joule / kilogram = gray = Sv = sievert rem = 1e-2 * sievert rads = 1e-2 * gray roentgen = 2.58e-4 * coulomb / kilogram # Temperature degC = kelvin; offset: 273.15 = celsius degR = 5 / 9 * kelvin; offset: 0 = rankine degF = 5 / 9 * kelvin; offset: 255.372222 = fahrenheit # Time minute = 60 * second = min hour = 60 * minute = hr day = 24 * hour week = 7 * day fortnight = 2 * week year = 31556925.9747 * second month = year / 12 shake = 1e-8 * second sidereal_day = day / 1.00273790935079524 sidereal_hour = sidereal_day / 24 sidereal_minute = sidereal_hour / 60 sidereal_second = sidereal_minute / 60 sidereal_year = 366.25636042 * sidereal_day sidereal_month = 27.321661 * sidereal_day tropical_month = 27.321661 * day synodic_month = 29.530589 * day = lunar_month common_year = 365 * day leap_year = 366 * day julian_year = 365.25 * day gregorian_year = 365.2425 * day millenium = 1000 * year = millenia = milenia = milenium eon = 1e9 * year work_year = 2056 * hour work_month = work_year / 12 # Velocity [speed] = [length] / [time] knot = nautical_mile / hour = kt = knot_international = international_knot = nautical_miles_per_hour mph = mile / hour = MPH kph = kilometer / hour = KPH # Viscosity [viscosity] = [pressure] * [time] poise = 1e-1 * Pa * second = P stokes = 1e-4 * meter ** 2 / second = St rhe = 10 / (Pa * s) # Volume [volume] = [length] ** 3 liter = 1e-3 * m ** 3 = l = L = litre cc = centimeter ** 3 = cubic_centimeter stere = meter ** 3 gross_register_ton = 100 * foot ** 3 = register_ton = GRT acre_foot = acre * foot = acre_feet board_foot = foot ** 2 * inch = FBM bushel = 2150.42 * inch ** 3 = bu = US_bushel dry_gallon = bushel / 8 = US_dry_gallon dry_quart = dry_gallon / 4 = US_dry_quart dry_pint = dry_quart / 2 = US_dry_pint gallon = 231 * inch ** 3 = liquid_gallon = US_liquid_gallon quart = gallon / 4 = liquid_quart = US_liquid_quart pint = quart / 2 = pt = liquid_pint = US_liquid_pint cup = pint / 2 = liquid_cup = US_liquid_cup gill = cup / 2 = liquid_gill = US_liquid_gill fluid_ounce = gill / 4 = floz = US_fluid_ounce = US_liquid_ounce imperial_bushel = 36.36872 * liter = UK_bushel imperial_gallon = imperial_bushel / 8 = UK_gallon imperial_quart = imperial_gallon / 4 = UK_quart imperial_pint = imperial_quart / 2 = UK_pint imperial_cup = imperial_pint / 2 = UK_cup imperial_gill = imperial_cup / 2 = UK_gill imperial_floz = imperial_gill / 5 = UK_fluid_ounce = imperial_fluid_ounce barrel = 42 * gallon = bbl tablespoon = floz / 2 = tbsp = Tbsp = Tblsp = tblsp = tbs = Tbl teaspoon = tablespoon / 3 = tsp peck = bushel / 4 = pk fluid_dram = floz / 8 = fldr = fluidram firkin = barrel / 4 @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 @context boltzmann [temperature] -> [energy]: boltzmann_constant * value [energy] -> [temperature]: value / boltzmann_constant @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 pint-0.10.1/pint/definitions.py000066400000000000000000000146641360526634500164270ustar00rootroot00000000000000""" 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 .converters import OffsetConverter, ScaleConverter from .errors import DefinitionSyntaxError from .util import ParserHelper, UnitsContainer, _is_dim class _NotNumeric(Exception): def __init__(self, value): self.value = value def numeric_parse(s): ph = ParserHelper.from_string(s) if len(ph): raise _NotNumeric(s) return ph.scale class Definition: """Base class for definitions. Parameters ---------- name : str Canonical name of the unit/prefix/etc. 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 an instance of Converter. """ def __init__(self, name, symbol, aliases, converter): self._name = name self._symbol = symbol self._aliases = aliases self._converter = converter @property def is_multiplicative(self): return self._converter.is_multiplicative @classmethod def from_string(cls, definition): """Parse a definition Parameters ---------- definition : Returns ------- """ name, definition = definition.split("=", 1) name = name.strip() result = [res.strip() for res in definition.split("=")] # @alias name = alias1 = alias2 = ... if name.startswith("@alias "): name = name[len("@alias ") :].lstrip() return AliasDefinition(name, tuple(result)) value, aliases = result[0], tuple([x for x in result[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 != "_"]) if name.startswith("["): return DimensionDefinition(name, symbol, aliases, value) elif name.endswith("-"): name = name.rstrip("-") return PrefixDefinition(name, symbol, aliases, value) else: return UnitDefinition(name, symbol, aliases, value) @property def name(self): return self._name @property def symbol(self): return self._symbol or self._name @property def has_symbol(self): return bool(self._symbol) @property def aliases(self): return self._aliases def add_aliases(self, *alias): alias = tuple(a for a in alias if a not in self._aliases) self._aliases = self._aliases + alias @property def converter(self): return self._converter def __str__(self): return self.name class PrefixDefinition(Definition): """Definition of a prefix.""" def __init__(self, name, symbol, aliases, converter): if isinstance(converter, str): try: converter = ScaleConverter(numeric_parse(converter)) except _NotNumeric as ex: raise ValueError( f"Prefix definition ('{name}') must contain only numbers, not {ex.value}" ) aliases = tuple(alias.strip("-") for alias in aliases) if symbol: symbol = symbol.strip("-") super().__init__(name, symbol, aliases, converter) class UnitDefinition(Definition): """Definition of a unit. Parameters ---------- reference : UnitsContainer Reference units. is_base : bool Indicates if it is a base unit. """ def __init__(self, name, symbol, aliases, converter, reference=None, is_base=False): self.reference = reference self.is_base = is_base if isinstance(converter, str): if ";" in converter: [converter, modifiers] = converter.split(";", 2) try: modifiers = dict( (key.strip(), numeric_parse(value)) for key, value in ( part.split(":") for part in modifiers.split(";") ) ) except _NotNumeric as ex: raise ValueError( f"Unit definition ('{name}') must contain only numbers in modifier, not {ex.value}" ) else: modifiers = {} converter = ParserHelper.from_string(converter) if not any(_is_dim(key) for key in converter.keys()): self.is_base = False elif all(_is_dim(key) for key in converter.keys()): self.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." ) self.reference = UnitsContainer(converter) if modifiers.get("offset", 0.0) != 0.0: converter = OffsetConverter(converter.scale, modifiers["offset"]) else: converter = ScaleConverter(converter.scale) super().__init__(name, symbol, aliases, converter) class DimensionDefinition(Definition): """Definition of a dimension.""" def __init__(self, name, symbol, aliases, converter, reference=None, is_base=False): self.reference = reference self.is_base = is_base if isinstance(converter, str): converter = ParserHelper.from_string(converter) if not converter: self.is_base = True elif all(_is_dim(key) for key in converter.keys()): self.is_base = False else: raise DefinitionSyntaxError( "Base dimensions must be referenced to None. " "Derived dimensions must only be referenced " "to dimensions." ) self.reference = UnitsContainer(converter) super().__init__(name, symbol, aliases, converter=None) class AliasDefinition(Definition): """Additional alias(es) for an already existing unit""" def __init__(self, name, aliases): super().__init__(name=name, symbol=None, aliases=aliases, converter=None) pint-0.10.1/pint/errors.py000066400000000000000000000067611360526634500154270ustar00rootroot00000000000000""" 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. """ 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 DefinitionSyntaxError(SyntaxError): """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): """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): """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): 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 https://pint.readthedocs.io/en/latest/nonmult.html for guidance." ) class UnitStrippedWarning(UserWarning): pass pint-0.10.1/pint/formatting.py000066400000000000000000000250541360526634500162610ustar00rootroot00000000000000""" 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 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 """ # TODO: Will not work for decimals ret = f"{num:n}".replace("-", "⁻") 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 = { "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)", }, "H": { # HTML format. "as_ratio": True, "single_denominator": True, "product_fmt": r" ", "division_fmt": r"{}/{}", "power_fmt": "{}^{}", "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"({})", }, } 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", ): """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}") 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 = [], [] for key, value in sorted(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-langage: 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(_FORMATS.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, **kwspec): if not unit: if spec.endswith("%"): return "" else: return "dimensionless" spec = _parse_spec(spec) fmt = dict(_FORMATS[spec]) fmt.update(kwspec) if spec == "L": # Latex rm = [ (r"\mathrm{{{}}}".format(u.replace("_", r"\_")), p) for u, p in unit.items() ] return formatter(rm, **fmt).replace("[", "{").replace("]", "}") elif spec == "H": # HTML (Jupyter Notebook) rm = [(u.replace("_", r"\_"), p) for u, p in unit.items()] return formatter(rm, **fmt) else: # Plain text return formatter(unit.items(), **fmt) def siunitx_format_unit(units): """Returns LaTeX code for the unit that can be put into an siunitx command. """ # NOTE: unit registry is required to identify unit prefixes. registry = units._REGISTRY 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._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 remove_custom_flags(spec): for flag in list(_FORMATS.keys()) + ["~"]: if flag: spec = spec.replace(flag, "") return spec 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.10.1/pint/matplotlib.py000066400000000000000000000046611360526634500162570ustar00rootroot00000000000000""" 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.10.1/pint/measurement.py000066400000000000000000000120521360526634500164260ustar00rootroot00000000000000""" 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, 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".format(value, error) ) 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 float(abs(self.magnitude.std_dev / self.magnitude.nominal_value)) def __reduce__(self): # See notes in Quantity.__reduce__ from . import _unpickle return _unpickle, (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): # 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), # which siunitx actually accepts as input. we just need to give the 'S' # formatting option for the uncertainties module. spec = spec.replace("Lx", "S") # todo: add support for extracting options opts = "separate-uncertainty=true" mstr = format(self.magnitude, spec) ustr = siunitx_format_unit(self.units) 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 or "H" in spec: space = r"\ " else: space = " " ustr = format(self.units, spec) 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) assert ustr[:2] == r"\[" assert ustr[-2:] == r"\]" return r"\[" + mag + space + ustr[2:] else: 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.10.1/pint/numpy_func.py000066400000000000000000000725411360526634500162750ustar00rootroot00000000000000""" 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 eq, is_upcast_type, np from .errors import DimensionalityError 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 ------- bool """ return ( iterable(obj) and sized(obj) and not isinstance(obj, str) and len(obj) > 0 and all(_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 arg[0].units 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 [item.m_as(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) else: raise DimensionalityError("dimensionless", pre_calc_units) else: if _is_quantity(arg): return arg.m elif _is_sequence_with_quantity_elements(arg): return [item.m 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 Quantiy/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. """ 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` 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 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]) 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", "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"] 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", "prod": "size", "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) def _add_subtract_handle_non_quantity_zero(x1, x2): # As in #121/#122, if a 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. if eq(x1, 0, True): (x2,), output_wrap = unwrap_and_wrap_consistent_units(x2) elif eq(x2, 0, True): (x1,), output_wrap = unwrap_and_wrap_consistent_units(x1) else: (x1, x2), output_wrap = unwrap_and_wrap_consistent_units(x1, x2) return x1, x2, output_wrap @implements("add", "ufunc") def _add(x1, x2, *args, **kwargs): x1, x2, output_wrap = _add_subtract_handle_non_quantity_zero(x1, x2) return output_wrap(np.add(x1, x2, *args, **kwargs)) @implements("subtract", "ufunc") def _subtract(x1, x2, *args, **kwargs): x1, x2, output_wrap = _add_subtract_handle_non_quantity_zero(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): if ( len(args) == 2 and not _is_quantity(args[1]) and not iterable(args[1]) and (args[1] == 0 or np.isnan(args[1])) ): # Special case for y being bare zero or nan (x,), output_wrap = unwrap_and_wrap_consistent_units(args[0]) args = x, args[1] elif ( len(args) == 2 and not _is_quantity(args[0]) and not iterable(args[0]) and (args[0] == 0 or np.isnan(args[0])) ): # Special case for x being bare zero or nan (y,), output_wrap = unwrap_and_wrap_consistent_units(args[1]) args = args[0], y else: 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", 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: 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 _is_quantity(arg): return arg.m_as(unit) else: return arg # 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 if mode == "constant": kwargs["constant_values"] = _recursive_convert(kwargs["constant_values"], units) elif mode == "linear_ramp": kwargs["end_values"] = _recursive_convert(kwargs["end_values"], 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.") # 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 func = getattr(np, func_str) @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), ("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", "rtol", "atol"], 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), ("rot90", "m", True), ("insert", ["arr", "values"], True), ("resize", "a", True), ("reshape", "a", 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="div") 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.10.1/pint/pint_eval.py000066400000000000000000000173371360526634500160750ustar00rootroot00000000000000""" 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, "+": 0, "-": 0, } _BINARY_OPERATOR_MAP = { "**": operator.pow, "*": operator.mul, "": operator.mul, # operator for implicit ops "/": operator.truediv, "+": operator.add, "-": operator.sub, } _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.10.1/pint/quantity.py000066400000000000000000002012401360526634500157560ustar00rootroot00000000000000""" pint.quantity ~~~~~~~~~~~~~ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import bisect import contextlib import copy import datetime import functools import locale import math import numbers import operator import re import warnings from pkg_resources.extern.packaging import version from .compat import SKIP_ARRAY_FUNCTION_CHANGE_WARNING # noqa: F401 from .compat import ( ARRAY_FALLBACK, NUMPY_VER, BehaviorChangeWarning, _to_magnitude, array_function_change_msg, babel_parse, eq, is_duck_array_type, is_upcast_type, ndarray, np, ) from .definitions import UnitDefinition from .errors import ( DimensionalityError, OffsetUnitCalculusError, PintTypeError, UnitStrippedWarning, ) from .formatting import ( _pretty_fmt_exponent, ndarray_to_latex, ndarray_to_latex_parts, remove_custom_flags, siunitx_format_unit, ) 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, logger, to_units_container, ) 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) if result._REGISTRY.auto_reduce_dimensions: return result.to_reduced_units() else: return result return wrapped def ireduce_dimensions(f): def wrapped(self, *args, **kwargs): result = f(self, *args, **kwargs) if result._REGISTRY.auto_reduce_dimensions: result.ito_reduced_units() 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 result = f(self, *args, **kwargs) return result return wrapped @contextlib.contextmanager def printoptions(*args, **kwargs): """Numpy printoptions context manager released with version 1.15.0 https://docs.scipy.org/doc/numpy/reference/generated/numpy.printoptions.html """ opts = np.get_printoptions() try: np.set_printoptions(*args, **kwargs) yield np.get_printoptions() finally: np.set_printoptions(**opts) class Quantity(PrettyIPython, SharedRegistryObject): """Implements a class to describe a physical quantity: the product of a numerical value and a unit of measurement. Parameters ---------- value : str, pint.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 = "" @property def force_ndarray(self): return self._REGISTRY.force_ndarray @property def force_ndarray_like(self): return self._REGISTRY.force_ndarray_like def __reduce__(self): """Allow pickling quantities. Since UnitRegistries are not pickled, upon unpickling the new object is always attached to the application registry. """ from . import _unpickle # Note: type(self) would be a mistake as subclasses built by # build_quantity_class can't be pickled return _unpickle, (Quantity, self.magnitude, self._units) def __new__(cls, value, units=None): global SKIP_ARRAY_FUNCTION_CHANGE_WARNING if is_upcast_type(type(value)): raise TypeError(f"Quantity cannot wrap upcast type {type(value)}") elif units is None: if isinstance(value, str): if value == "": raise ValueError( "Expression to parse as Quantity cannot " "be an empty string." ) ureg = SharedRegistryObject.__new__(cls)._REGISTRY inst = ureg.parse_expression(value) return cls.__new__(cls, inst) elif isinstance(value, cls): inst = copy.copy(value) else: inst = SharedRegistryObject.__new__(cls) inst._magnitude = _to_magnitude( value, inst.force_ndarray, inst.force_ndarray_like ) inst._units = UnitsContainer() elif isinstance(units, (UnitsContainer, UnitDefinition)): inst = SharedRegistryObject.__new__(cls) inst._magnitude = _to_magnitude( value, inst.force_ndarray, inst.force_ndarray_like ) inst._units = units elif isinstance(units, str): inst = SharedRegistryObject.__new__(cls) inst._magnitude = _to_magnitude( value, inst.force_ndarray, inst.force_ndarray_like ) inst._units = inst._REGISTRY.parse_units(units)._units elif isinstance(units, SharedRegistryObject): if isinstance(units, Quantity) and units.magnitude != 1: inst = copy.copy(units) logger.warning( "Creating new Quantity using a non unity " "Quantity as units." ) else: inst = SharedRegistryObject.__new__(cls) inst._units = units._units inst._magnitude = _to_magnitude( value, inst.force_ndarray, inst.force_ndarray_like ) else: raise TypeError( "units must be of type str, Quantity or " "UnitsContainer; not {}.".format(type(units)) ) inst.__used = False inst.__handling = None if not SKIP_ARRAY_FUNCTION_CHANGE_WARNING and isinstance( inst._magnitude, ndarray ): warnings.warn(array_function_change_msg, BehaviorChangeWarning) SKIP_ARRAY_FUNCTION_CHANGE_WARNING = True return inst @property def debug_used(self): return self.__used def __iter__(self): # 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): ret = self.__class__(copy.copy(self._magnitude), self._units) ret.__used = self.__used return ret def __deepcopy__(self, memo): ret = self.__class__( copy.deepcopy(self._magnitude, memo), copy.deepcopy(self._units, memo) ) ret.__used = self.__used return ret def __str__(self): return format(self) def __bytes__(self): return str(self).encode(locale.getpreferredencoding()) def __repr__(self): return f"" def __hash__(self): 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): spec = spec or self.default_format if "L" in spec: allf = plain_allf = r"{}\ {}" else: allf = plain_allf = "{} {}" # If Compact is selected, do it at the beginning if "#" in spec: spec = spec.replace("#", "") obj = self.to_compact() else: obj = self # the LaTeX siunitx code if "Lx" in spec: spec = spec.replace("Lx", "") # TODO: add support for extracting options opts = "" ustr = siunitx_format_unit(obj.units) allf = r"\SI[%s]{{{}}}{{{}}}" % opts elif "H" in spec: ustr = format(obj.units, spec) assert ustr[:2] == r"\[" assert ustr[-2:] == r"\]" ustr = ustr[2:-2] allf = r"\[{}\ {}\]" else: ustr = format(obj.units, spec) mspec = remove_custom_flags(spec) if isinstance(self.magnitude, ndarray): if "L" in spec: mstr = ndarray_to_latex(obj.magnitude, mspec) elif "H" in spec: allf = r"\[{} {}\]" # this is required to have the magnitude and unit in the same line parts = ndarray_to_latex_parts(obj.magnitude, mspec) if len(parts) > 1: return "\n".join(allf.format(part, ustr) for part in parts) mstr = parts[0] else: formatter = "{{:{}}}".format(mspec) with printoptions(formatter={"float_kind": formatter.format}): mstr = format(obj.magnitude).replace("\n", "") else: mstr = format(obj.magnitude, mspec).replace("\n", "") if "L" in spec: mstr = self._exp_pattern.sub(r"\1\\times 10^{\2\3}", mstr) elif "H" in spec: mstr = self._exp_pattern.sub(r"\1×10^{\2\3}", mstr) elif "P" in spec: m = self._exp_pattern.match(mstr) if m: exp = int(m.group(2) + m.group(3)) mstr = self._exp_pattern.sub(r"\1×10" + _pretty_fmt_exponent(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="", **kwspec): 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): """Quantity's magnitude. Long form for `m`""" return self._magnitude @property def m(self): """Quantity's magnitude. Short form for `magnitude`""" return self._magnitude def m_as(self, units): """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): """Quantity's units. Long form for `u`""" return self._REGISTRY.Unit(self._units) @property def u(self): """Quantity's units. Short form for `units`""" return self._REGISTRY.Unit(self._units) @property def unitless(self): """ """ return not bool(self.to_root_units()._units) @property def dimensionless(self): """ """ tmp = self.to_root_units() return not bool(tmp.dimensionality) _dimensionality = None @property def dimensionality(self): """ 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): """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, units=None): """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, units=None): """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], UnitsContainer(tup[1])) def to_tuple(self): 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 _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): """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): """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): """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): """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): """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): """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 ito_reduced_units(self): """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.ito({}) if len(self._units) == 1: return None newunits = self._units.copy() # loop through individual units and compare to each other unit # can we do better than a nested loop here? for unit1, exp in self._units.items(): for unit2 in newunits: if unit1 != unit2: power = self._REGISTRY._get_dimensionality_ratio(unit1, unit2) if power: newunits = newunits.add(unit2, exp / power).remove([unit1]) break return self.ito(newunits) def to_reduced_units(self): """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. """ # can we make this more efficient? newq = copy.copy(self) newq.ito_reduced_units() return newq def to_compact(self, unit=None): """"Return Quantity rescaled to compact, human-readable units. To get output in terms of a different unit, use the unit parameter. Example ------- >>> 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 = {} 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 = sorted(SI_prefixes.items()) SI_powers = [item[0] for item in SI_prefixes] SI_bases = [item[1] for item in SI_prefixes] if unit is None: unit = infer_base_unit(self) 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 = int(math.floor(math.log10(abs(magnitude)) / unit_power / 3)) * 3 else: power = int(math.ceil(math.log10(abs(magnitude)) / unit_power / 3)) * 3 prefix = SI_bases[bisect.bisect_left(SI_powers, power)] new_unit_str = prefix + 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): if self.dimensionless: return int(self._convert_magnitude_not_inplace(UnitsContainer())) raise DimensionalityError(self._units, "dimensionless") def __float__(self): if self.dimensionless: return float(self._convert_magnitude_not_inplace(UnitsContainer())) raise DimensionalityError(self._units, "dimensionless") def __complex__(self): 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 eq(other, 0, 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(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 eq(other, 0, 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. units = self._units magnitude = op( self._magnitude, _to_magnitude(other, self.force_ndarray, self.force_ndarray_like), ) elif self.dimensionless: units = 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(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(tu), other._magnitude) units = other._units else: raise OffsetUnitCalculusError(self._units, other._units) return self.__class__(magnitude, units) 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, 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, 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): # Use NumPy ufunc (existing since 1.16) for matrix multiplication if version.parse(NUMPY_VER) >= version.parse("1.16"): return np.matmul(self, other) else: return NotImplemented __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 = 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, 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, UnitsContainer({})) @check_implemented def __imod__(self, other): if not self._check(other): other = self.__class__(other, 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, 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, UnitsContainer({})) else: raise DimensionalityError(self._units, "dimensionless") @check_implemented def __divmod__(self, other): if not self._check(other): other = self.__class__(other, UnitsContainer({})) q, r = divmod(self._magnitude, other.to(self._units)._magnitude) return (self.__class__(q, 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 = UnitsContainer({}) else: raise DimensionalityError(self._units, "dimensionless") return (self.__class__(q, 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. if self.dimensionless: if getattr(other, "dimensionless", False): self._magnitude **= other.m_as("") return self elif not getattr(other, "dimensionless", True): raise DimensionalityError(other._units, "dimensionless") else: self._magnitude **= other 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 = 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): 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. if self.dimensionless: if getattr(other, "dimensionless", False): return self.__class__(self.m ** other.m_as("")) elif not getattr(other, "dimensionless", True): raise DimensionalityError(other._units, "dimensionless") else: return self.__class__(self.m ** 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 = 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, self.force_ndarray, self.force_ndarray_like ) units = new_self._units ** exponent magnitude = new_self._magnitude ** exponent return self.__class__(magnitude, units) @check_implemented def __rpow__(self, other): 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") if is_duck_array_type(type(self._magnitude)): if np.size(self._magnitude) > 1: raise DimensionalityError(self._units, "dimensionless") new_self = self.to_root_units() return other ** new_self._magnitude def __abs__(self): return self.__class__(abs(self._magnitude), self._units) def __round__(self, ndigits=0): return self.__class__(round(self._magnitude, ndigits=ndigits), self._units) def __pos__(self): return self.__class__(operator.pos(self._magnitude), self._units) def __neg__(self): return self.__class__(operator.neg(self._magnitude), self._units) @check_implemented def __eq__(self, other): # We compare to the base class of Quantity because # each Quantity class is unique. if not isinstance(other, Quantity): if eq(other, 0, True): # Handle the special case in which we compare to zero # (or an array of zeros) 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) return self.dimensionless and eq( self._convert_magnitude(UnitsContainer()), other, False ) if eq(self._magnitude, 0, True) and eq(other._magnitude, 0, True): return 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 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, self.__class__): if self.dimensionless: return op(self._convert_magnitude_not_inplace(UnitsContainer()), other) elif eq(other, 0, True): # Handle the special case in which we compare to zero # (or an array of zeros) 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))) 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): # 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): 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, first=None, second=None, out=None, **kwargs): minimum = kwargs.get("min", first) maximum = kwargs.get("max", second) if minimum is None and maximum is None: raise TypeError("clip() takes at least 3 arguments (2 given)") if maximum is None and "min" not in kwargs: minimum, maximum = maximum, minimum kwargs = {"out": out} if minimum is not None: if isinstance(minimum, self.__class__): kwargs["min"] = minimum.to(self).magnitude elif self.dimensionless: kwargs["min"] = minimum else: raise DimensionalityError("dimensionless", self._units) if maximum is not None: if isinstance(maximum, self.__class__): kwargs["max"] = maximum.to(self).magnitude elif self.dimensionless: kwargs["max"] = maximum else: raise DimensionalityError("dimensionless", self._units) return self.__class__(self.magnitude.clip(**kwargs), self._units) def fill(self, value): self._units = value._units return self.magnitude.fill(value.magnitude) def put(self, indices, values, mode="raise"): 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): return self.__class__(self._magnitude.real, self._units) @property def imag(self): 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): 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) def __ito_if_needed(self, to_units): if self.unitless and to_units == "radian": return self.ito(to_units) def __len__(self): return len(self._magnitude) def __getattr__(self, item): if item.startswith("__array_"): # Handle array protocol attributes other than `__array__` if ARRAY_FALLBACK: # Deprecated fallback behavior warnings.warn( ( f"Array protocol attribute {item} accessed, with unit of the " "Quantity being stripped. This attribute will become unavailable " "in the next minor version of Pint. To make this potentially " "incorrect attribute unavailable now, set the " "PINT_ARRAY_PROTOCOL_FALLBACK environment variable to 0 before " "importing Pint." ), DeprecationWarning, stacklevel=2, ) if is_duck_array_type(type(self._magnitude)): # Defer to magnitude, and don't catch any AttributeErrors return getattr(self._magnitude, item) else: # If an `__array_` attribute is requested but the magnitude is not # a duck array, we convert the magnitude to a numpy ndarray. magnitude_as_array = _to_magnitude( self._magnitude, force_ndarray=True ) return getattr(magnitude_as_array, item) else: # TODO (next minor version): ARRAY_FALLBACK is removed and this becomes the standard behavior 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 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 return [ self.__class__(value, units).tolist() if isinstance(value, list) else self.__class__(value, units) for value in self._magnitude.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) # methods/properties that help for math operations with offset units @property def _is_multiplicative(self): """Check if the Quantity object has only multiplicative units.""" return not self._get_non_multiplicative_units() def _get_non_multiplicative_units(self): """Return a list of the of non-multiplicative units of the Quantity object.""" offset_units = [ unit for unit in self._units.keys() if not self._REGISTRY._units[unit].is_multiplicative ] return offset_units def _get_delta_units(self): """Return list of delta units ot the Quantity object.""" delta_units = [u for u in self._units.keys() if u.startswith("delta_")] return delta_units def _has_compatible_delta(self, unit): """"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 else: # Look for delta units with same dimension as the offset unit offset_unit_dim = self._REGISTRY._units[unit].reference for d in deltas: if self._REGISTRY._units[d].reference == offset_unit_dim: return True return False def _ok_for_muldiv(self, no_offset_units=None): """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): return datetime.timedelta(microseconds=self.to("microseconds").magnitude) _Quantity = Quantity def build_quantity_class(registry): class Quantity(_Quantity): _REGISTRY = registry return Quantity pint-0.10.1/pint/registry.py000066400000000000000000002025661360526634500157640ustar00rootroot00000000000000""" pint.registry ~~~~~~~~~~~~~ Defines the Registry, a class to contain units and their relations. The module actually defines 5 registries with different capabilites: - 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. """ import copy import functools import itertools import locale import math import os import re from collections import ChainMap, defaultdict from contextlib import closing, contextmanager from decimal import Decimal from fractions import Fraction from io import StringIO from tokenize import NAME, NUMBER import pkg_resources from . import registry_helpers, systems from .compat import babel_parse, tokenizer from .context import Context, ContextChain 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 .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, ) _BLOCK_RE = re.compile(r" |\(") 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): #: Maps dimensionality (UnitsContainer) to Units (str) self.dimensional_equivalents = {} #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) self.root_units = {} #: Maps dimensionality (UnitsContainer) to Units (UnitsContainer) self.dimensionality = {} #: Cache the unit name associated to user input. ('mV' -> 'millivolt') self.parse_unit = {} 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): self.dimensional_equivalents = registry_cache.dimensional_equivalents self.root_units = {} self.dimensionality = registry_cache.dimensionality self.parse_unit = registry_cache.parse_unit 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` """ #: Map context prefix to function #: type: Dict[str, (SourceIterator -> None)] _parsers = None #: List to be used in addition of units when dir(registry) is called. #: Also used for autocompletion in IPython. _dir = [ "Quantity", "Unit", "Measurement", "define", "load_definitions", "get_name", "get_symbol", "get_dimensionality", "get_base_units", "get_root_units", "parse_unit_name", "parse_units", "parse_expression", "convert", ] def __init__( self, filename="", force_ndarray=False, force_ndarray_like=False, on_redefinition="warn", auto_reduce_dimensions=False, preprocessors=None, fmt_locale=None, ): self._register_parsers() self._init_dynamic_classes() self._filename = filename self.force_ndarray = force_ndarray self.force_ndarray_like = force_ndarray_like self.preprocessors = preprocessors or [] #: 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.fmt_locale = self.set_fmt_locale(fmt_locale) #: Map between name (string) and value (string) of defaults stored in the #: definitions file. self._defaults = {} #: Map dimension name (string) to its definition (DimensionDefinition). self._dimensions = {} #: Map unit name (string) to its definition (UnitDefinition). #: Might contain prefixed units. self._units = {} #: 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 = defaultdict(set) #: Map prefix name (string) to its definition (PrefixDefinition). self._prefixes = {"": PrefixDefinition("", "", (), 1)} #: Map suffix name (string) to canonical , and unit alias to canonical unit name self._suffixes = {"": "", "s": ""} #: Map contexts to RegistryCache self._cache = RegistryCache() self._initialized = False def _init_dynamic_classes(self): """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 = build_quantity_class(self) from .measurement import build_measurement_class self.Measurement = build_measurement_class(self) def _after_init(self): """This should be called after all __init__""" self.define(UnitDefinition("pi", "π", (), ScaleConverter(math.pi))) if self._filename == "": self.load_definitions("default_en.txt", True) elif self._filename is not None: self.load_definitions(self._filename) self._build_cache() self._initialized = True def _register_parsers(self): self._register_parser("@defaults", self._parse_defaults) def _parse_defaults(self, ifile): """Loader for a @default section. """ next(ifile) for lineno, part in ifile.block_iter(): k, v = part.split("=") self._defaults[k.strip()] = v.strip() def __deepcopy__(self, memo): 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 __dir__(self): return list(self._units.keys()) + self._dir def set_fmt_locale(self, loc): """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 @property def default_format(self): """Default formatting string for quantities.""" return self.Quantity.default_format @default_format.setter def default_format(self, value): self.Unit.default_format = value self.Quantity.default_format = value def define(self, definition): """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"): self._define(Definition.from_string(line)) else: self._define(definition) def _define(self, definition): """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, is_base=True) ) elif isinstance(definition, PrefixDefinition): d, di = self._prefixes, None elif isinstance(definition, AliasDefinition): d, di = self._units, self._units_casei self._define_alias(definition, d, di) return d[definition.name], d, di 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) != 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 = 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): unit = unit_dict[definition.name] unit.add_aliases(*definition.aliases) for alias in unit.aliases: unit_dict[alias] = unit casei_unit_dict[alias.lower()].add(alias) def _register_parser(self, prefix, parserfunc): """Register a loader for a given @ directive.. Parameters ---------- prefix : string identifying the section (e.g. @context) parserfunc : SourceIterator -> None A function that is able to parse a Definition section. Returns ------- """ if self._parsers is None: self._parsers = {} if prefix and prefix[0] == "@": self._parsers[prefix] = parserfunc else: raise ValueError("Prefix directives must start with '@'") def load_definitions(self, file, is_resource=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) Returns ------- """ # Permit both filenames and line-iterables if isinstance(file, str): try: if is_resource: with closing(pkg_resources.resource_stream(__name__, file)) as fp: rbytes = fp.read() return self.load_definitions( StringIO(rbytes.decode("utf-8")), is_resource ) else: with open(file, encoding="utf-8") as fp: return self.load_definitions(fp, is_resource) except (RedefinitionError, DefinitionSyntaxError) as e: if e.filename is None: e.filename = file raise e except Exception as e: msg = getattr(e, "message", "") or str(e) raise ValueError("While opening {}\n{}".format(file, msg)) ifile = SourceIterator(file) for no, line in ifile: if line.startswith("@") and not line.startswith("@alias"): if line.startswith("@import"): if is_resource: path = line[7:].strip() else: try: path = os.path.dirname(file.name) except AttributeError: path = os.getcwd() path = os.path.join(path, os.path.normpath(line[7:].strip())) self.load_definitions(path, is_resource) else: parts = _BLOCK_RE.split(line) loader = ( self._parsers.get(parts[0], None) if self._parsers else None ) if loader is None: raise DefinitionSyntaxError( "Unknown directive %s" % line, lineno=no ) try: loader(ifile) except DefinitionSyntaxError as ex: if ex.lineno is None: ex.lineno = no raise ex else: try: self.define(Definition.from_string(line)) except DefinitionSyntaxError as ex: if ex.lineno is None: ex.lineno = no raise ex except Exception as ex: logger.error("In line {}, cannot add '{}' {}".format(no, line, ex)) def _build_cache(self): """Build a cache of dimensionality and base units.""" 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) 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}") def get_name(self, name_or_alias, case_sensitive=True): """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) prefix_def = self._prefixes[prefix] self._units[name] = UnitDefinition( name, symbol, (), prefix_def.converter, UnitsContainer({unit_name: 1}) ) return prefix + unit_name return unit_name def get_symbol(self, name_or_alias): """Return the preferred alias for a unit. """ candidates = self.parse_unit_name(name_or_alias) 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): return self._units[name].symbol def get_dimensionality(self, input_units): """Convert unit or dict of units or dimensions to a dict of base dimensions dimensions """ input_units = to_units_container(input_units) return self._get_dimensionality(input_units) def _get_dimensionality(self, input_units): """Convert a UnitsContainer to base dimensions. """ if not input_units: return UnitsContainer() cache = self._cache.dimensionality try: return cache[input_units] except KeyError: pass accumulator = defaultdict(float) self._get_dimensionality_recurse(input_units, 1.0, accumulator) if "[]" in accumulator: del accumulator["[]"] dims = UnitsContainer({k: v for k, v in accumulator.items() if v != 0.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 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, 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 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) 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.0, UnitsContainer() cache = self._cache.root_units try: return cache[input_units] except KeyError: pass accumulators = [1.0, defaultdict(float)] self._get_root_units_recurse(input_units, 1.0, accumulators) factor = accumulators[0] units = 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): """ """ 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 convert(self, value, src, dst, inplace=False): """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, case_sensitive=True): """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 : (Default value = True) 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, case_sensitive=True): """Helper of parse_unit_name. """ stw = unit_name.startswith edw = unit_name.endswith for suffix, prefix in itertools.product(self._suffixes, self._prefixes): if stw(prefix) and edw(suffix): name = unit_name[len(prefix) :] if suffix: name = name[: -len(suffix)] if len(name) == 1: continue if case_sensitive: if name in self._units: yield ( self._prefixes[prefix].name, self._units[name].name, self._suffixes[suffix], ) else: for real_name in self._units_casei.get(name.lower(), ()): yield ( self._prefixes[prefix].name, self._units[real_name].name, self._suffixes[suffix], ) @staticmethod def _dedup_candidates(candidates): """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, as_delta=None): """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) Returns ------- """ for p in self.preprocessors: input_string = p(input_string) units = self._parse_units(input_string, as_delta) return self.Unit(units) def _parse_units(self, input_string, as_delta=True): """Parse a units expression and returns a UnitContainer with the canonical names. """ cache = self._cache.parse_unit if as_delta: try: return cache[input_string] except KeyError: pass if not input_string: return UnitsContainer() # Sanitize input_string with whitespaces. input_string = input_string.strip() units = ParserHelper.from_string(input_string) if units.scale != 1: raise ValueError("Unit expression cannot have a scaling factor.") ret = {} many = len(units) > 1 for name in units: cname = self.get_name(name) 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[cname] = value ret = UnitsContainer(ret) if as_delta: cache[input_string] = ret return ret def _eval_token(self, token, case_sensitive=True, use_decimal=False, **values): token_type = token[0] token_text = token[1] if token_type == NAME: if token_text == "dimensionless": return 1 * self.dimensionless elif token_text in values: return self.Quantity(values[token_text]) else: return self.Quantity( 1, UnitsContainer( {self.get_name(token_text, case_sensitive=case_sensitive): 1} ), ) elif token_type == NUMBER: return ParserHelper.eval_token(token, use_decimal=use_decimal) else: raise Exception("unknown token type") def parse_expression( self, input_string, case_sensitive=True, use_decimal=False, **values ): """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 = True) use_decimal : (Default value = False) **values : Returns ------- """ 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, use_decimal=use_decimal, **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=True, autoconvert_offset_to_baseunit=False, **kwargs ): 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, as_delta=None): """ """ if as_delta is None: as_delta = self.default_as_delta return super()._parse_units(input_string, as_delta) def _define(self, 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) != 0.0: self._define_adder(definition, d, di) return definition, d, di def _is_multiplicative(self, u): 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): 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 _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]) # clean dst units from offset units if dst_offset_unit: dst = dst.remove([dst_offset_unit]) # 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 dimenstions 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): # Map context name (string) or abbreviation to context. self._contexts = {} # 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_parsers(self): super()._register_parsers() self._register_parser("@context", self._parse_context) def _parse_context(self, ifile): try: self.add_context( Context.from_lines(ifile.block_iter(), 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) -> None: super()._build_cache() 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 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, 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, **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. ctxs = tuple(Context.from_context(ctx, **kwargs) for ctx in ctxs) # Finally we add them to the active context. self._active_ctx.insert_contexts(*ctxs) 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): """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:: >>> 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): """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(wavelenght): ... 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 dimenstions 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 = {} #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) self._base_units_cache = dict() #: Map group name to group. #: :type: dict[ str | Group] self._groups = {} self._groups["root"] = self.Group("root") self._default_system = system def _init_dynamic_classes(self): super()._init_dynamic_classes() self.Group = systems.build_group_class(self) self.System = systems.build_system_class(self) def _after_init(self): """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_parsers(self): super()._register_parsers() self._register_parser("@group", self._parse_group) self._register_parser("@system", self._parse_system) def _parse_group(self, ifile): self.Group.from_lines(ifile.block_iter(), self.define) def _parse_system(self, ifile): self.System.from_lines(ifile.block_iter(), self.get_root_units) def get_group(self, name, create_if_needed=True): """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("Unkown group %s" % name) return self.Group(name) @property def sys(self): return systems.Lister(self._systems) @property def default_system(self): 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, create_if_needed=True): """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("Unkown 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, 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. 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, check_nonmult=True, system=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 = 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 *= 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): 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 quantites 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 """ def __init__( self, filename="", force_ndarray=False, force_ndarray_like=False, default_as_delta=True, autoconvert_offset_to_baseunit=False, on_redefinition="warn", system=None, auto_reduce_dimensions=False, preprocessors=None, fmt_locale=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, ) 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=True): """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) pint-0.10.1/pint/registry_helpers.py000066400000000000000000000264621360526634500175050ustar00rootroot00000000000000""" pint.registry_helpers ~~~~~~~~~~~~~~~~~~~~~ Miscellaneous methods of the registry writen 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 .errors import DimensionalityError from .util import UnitsContainer, to_units_container 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, ret, args, strict=True): """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, iterable of str, or iterable of pint.Unit Units of each of the return values. Use `None` to skip argument conversion. args : str, pint.Unit, iterable of str, or iterable of 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 the 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): 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): 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, *args): """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.10.1/pint/systems.py000066400000000000000000000330741360526634500156170ustar00rootroot00000000000000""" 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. """ import re from pint.compat import babel_parse from .babel_names import _babel_systems from .definitions import Definition, UnitDefinition from .errors import DefinitionSyntaxError, RedefinitionError from .util import ( SharedRegistryObject, SourceIterator, getattr_maybe_raise, logger, to_units_container, ) 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. It can be specified in the definition file as:: @group [using , ..., ] ... @end """ #: Regex to match the header parts of a definition. _header_re = re.compile(r"@group\s+(?P\w+)\s*(using\s(?P.*))*") 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): """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: group_names = tuple(a.strip() for a in groups.split(",")) else: group_names = () unit_names = [] for lineno, line in lines: if "=" in line: # Is a definition definition = Definition.from_string(line) if not isinstance(definition, UnitDefinition): raise DefinitionSyntaxError( "Only UnitDefinition are valid inside _used_groups, not " + str(definition), lineno=lineno, ) try: define_func(definition) except (RedefinitionError, DefinitionSyntaxError) as ex: if ex.lineno is None: ex.lineno = lineno raise ex unit_names.append(definition.name) else: unit_names.append(line.strip()) grp = cls(name) grp.add_units(*unit_names) if group_names: grp.add_groups(*group_names) return grp def __getattr__(self, item): getattr_maybe_raise(self, item) return self._REGISTRY 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. It can be specified in the definition file as:: @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 ommited. """ #: Regex to match the header parts of a context. _header_re = re.compile(r"@system\s+(?P\w+)\s*(using\s(?P.*))*") 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): 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",) base_unit_names = {} derived_unit_names = [] 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() # 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 `:` must be a root unit." % line ) # Here we find new_unit expanded in terms of root_units new_unit_expanded = to_units_container(get_root_func(new_unit)[1]) # 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.0 / 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 else: # The syntax is new_unit # old_unit is inferred as the root unit with the same dimensionality. new_unit = line old_unit_dict = to_units_container(get_root_func(line)[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.0 / value} system = cls(name) system.add_groups(*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.10.1/pint/testsuite/000077500000000000000000000000001360526634500155605ustar00rootroot00000000000000pint-0.10.1/pint/testsuite/__init__.py000066400000000000000000000135361360526634500177010ustar00rootroot00000000000000import doctest import logging import math import os import unittest from contextlib import contextmanager from logging.handlers import BufferingHandler from pint import Quantity, UnitRegistry, logger from pint.compat import ndarray, np from pint.testsuite.helpers import PintOutputChecker class TestHandler(BufferingHandler): def __init__(self, only_warnings=False): # BufferingHandler takes a "capacity" argument # so as to know when to flush. As we're overriding # shouldFlush anyway, we can set a capacity of zero. # You can call flush() manually to clear out the # buffer. self.only_warnings = only_warnings BufferingHandler.__init__(self, 0) def shouldFlush(self): return False def emit(self, record): if self.only_warnings and record.level != logging.WARNING: return self.buffer.append(record.__dict__) class BaseTestCase(unittest.TestCase): CHECK_NO_WARNING = True @contextmanager def capture_log(self, level=logging.DEBUG): th = TestHandler() th.setLevel(level) logger.addHandler(th) if self._test_handler is not None: buflen = len(self._test_handler.buffer) yield th.buffer if self._test_handler is not None: self._test_handler.buffer = self._test_handler.buffer[:buflen] def setUp(self): self._test_handler = None if self.CHECK_NO_WARNING: self._test_handler = th = TestHandler() th.setLevel(logging.WARNING) logger.addHandler(th) def tearDown(self): if self._test_handler is not None: buf = self._test_handler.buffer msg = "\n".join(record.get("msg", str(record)) for record in buf) self.assertEqual(len(buf), 0, msg=f"{len(buf)} warnings raised.\n{msg}") class QuantityTestCase(BaseTestCase): FORCE_NDARRAY = False @classmethod def setUpClass(cls): cls.ureg = UnitRegistry(force_ndarray=cls.FORCE_NDARRAY) cls.Q_ = cls.ureg.Quantity cls.U_ = cls.ureg.Unit def _get_comparable_magnitudes(self, first, second, msg): if isinstance(first, Quantity) and isinstance(second, Quantity): second = second.to(first) self.assertEqual( first.units, second.units, msg=msg + " Units are not equal." ) m1, m2 = first.magnitude, second.magnitude elif isinstance(first, Quantity): self.assertTrue( first.dimensionless, msg=msg + " The first is not dimensionless." ) first = first.to("") m1, m2 = first.magnitude, second elif isinstance(second, Quantity): self.assertTrue( second.dimensionless, msg=msg + " The second is not dimensionless." ) second = second.to("") m1, m2 = first, second.magnitude else: m1, m2 = first, second return m1, m2 def assertQuantityEqual(self, first, second, msg=None): if msg is None: msg = "Comparing %r and %r. " % (first, second) m1, m2 = self._get_comparable_magnitudes(first, second, msg) if isinstance(m1, ndarray) or isinstance(m2, ndarray): np.testing.assert_array_equal(m1, m2, err_msg=msg) else: self.assertEqual(m1, m2, msg) def assertQuantityAlmostEqual(self, first, second, rtol=1e-07, atol=0, msg=None): if msg is None: msg = "Comparing %r and %r. " % (first, second) m1, m2 = self._get_comparable_magnitudes(first, second, msg) if isinstance(m1, ndarray) or isinstance(m2, ndarray): np.testing.assert_allclose(m1, m2, rtol=rtol, atol=atol, err_msg=msg) else: self.assertLessEqual(abs(m1 - m2), atol + rtol * abs(m2), msg=msg) 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.10.1/pint/testsuite/baseline/000077500000000000000000000000001360526634500173425ustar00rootroot00000000000000pint-0.10.1/pint/testsuite/baseline/test_basic_plot.png000066400000000000000000000421131360526634500232270ustar00rootroot00000000000000PNG  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.10.1/pint/testsuite/baseline/test_plot_with_set_units.png000066400000000000000000000433411360526634500252220ustar00rootroot00000000000000PNG  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.10.1/pint/testsuite/helpers.py000066400000000000000000000073141360526634500176010ustar00rootroot00000000000000import doctest import re import unittest from distutils.version import StrictVersion from ..compat import ( HAS_BABEL, HAS_NUMPY, HAS_NUMPY_ARRAY_FUNCTION, HAS_UNCERTAINTIES, NUMPY_VER, ) def requires_array_function_protocol(): if not HAS_NUMPY: return unittest.skip("Requires NumPy") return unittest.skipUnless( HAS_NUMPY_ARRAY_FUNCTION, "Requires __array_function__ protocol to be enabled" ) def requires_not_array_function_protocol(): if not HAS_NUMPY: return unittest.skip("Requires NumPy") return unittest.skipIf( HAS_NUMPY_ARRAY_FUNCTION, "Requires __array_function__ protocol to be unavailable or disabled", ) def requires_numpy18(): if not HAS_NUMPY: return unittest.skip("Requires NumPy") return unittest.skipUnless( StrictVersion(NUMPY_VER) >= StrictVersion("1.8"), "Requires NumPy >= 1.8" ) def requires_numpy_previous_than(version): if not HAS_NUMPY: return unittest.skip("Requires NumPy") return unittest.skipUnless( StrictVersion(NUMPY_VER) < StrictVersion(version), "Requires NumPy < %s" % version, ) def requires_numpy_at_least(version): if not HAS_NUMPY: return unittest.skip("Requires NumPy") return unittest.skipUnless( StrictVersion(NUMPY_VER) >= StrictVersion(version), "Requires NumPy >= %s" % version, ) def requires_numpy(): return unittest.skipUnless(HAS_NUMPY, "Requires NumPy") def requires_not_numpy(): return unittest.skipIf(HAS_NUMPY, "Requires NumPy is not installed.") def requires_babel(): return unittest.skipUnless(HAS_BABEL, "Requires Babel with units support") def requires_uncertainties(): return unittest.skipUnless(HAS_UNCERTAINTIES, "Requires Uncertainties") def requires_not_uncertainties(): return unittest.skipIf( HAS_UNCERTAINTIES, "Requires Uncertainties is not installed." ) _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 pint-0.10.1/pint/testsuite/parameterized.py000066400000000000000000000106361360526634500207740ustar00rootroot00000000000000# Adds Parameterized tests for Python's unittest module # # Code from: parameterizedtestcase, version: 0.1.0 # Homepage: https://github.com/msabramo/python_unittest_parameterized_test_case # Author: Marc Abramowitz, email: marc@marc-abramowitz.com # License: MIT # # Use like this: # # from parameterizedtestcase import ParameterizedTestCase # # class MyTests(ParameterizedTestCase): # @ParameterizedTestCase.parameterize( # ("input", "expected_output"), # [ # ("2+4", 6), # ("3+5", 8), # ("6*9", 54), # ] # ) # def test_eval(self, input, expected_output): # self.assertEqual(eval(input), expected_output) import unittest from collections.abc import Callable from functools import wraps def augment_method_docstring( method, new_class_dict, classname, param_names, param_values, new_method ): param_assignments_str = "; ".join( ["%s = %s" % (k, v) for (k, v) in zip(param_names, param_values)] ) extra_doc = "%s (%s.%s) [with %s] " % ( method.__name__, new_class_dict.get("__module__", ""), classname, param_assignments_str, ) try: new_method.__doc__ = extra_doc + new_method.__doc__ except TypeError: # Catches when new_method.__doc__ is None new_method.__doc__ = extra_doc class ParameterizedTestCaseMetaClass(type): method_counter = {} def __new__(meta, classname, bases, class_dict): new_class_dict = {} for attr_name, attr_value in list(class_dict.items()): if isinstance(attr_value, Callable) and hasattr(attr_value, "param_names"): # print("Processing attr_name = %r; attr_value = %r" % ( # attr_name, attr_value)) method = attr_value param_names = attr_value.param_names data = attr_value.data func_name_format = attr_value.func_name_format meta.process_method( classname, method, param_names, data, new_class_dict, func_name_format, ) else: new_class_dict[attr_name] = attr_value return type.__new__(meta, classname, bases, new_class_dict) @classmethod def process_method( cls, classname, method, param_names, data, new_class_dict, func_name_format ): method_counter = cls.method_counter for param_values in data: new_method = cls.new_method(method, param_values) method_counter[method.__name__] = method_counter.get(method.__name__, 0) + 1 case_data = dict(list(zip(param_names, param_values))) case_data["func_name"] = method.__name__ case_data["case_num"] = method_counter[method.__name__] new_method.__name__ = func_name_format.format(**case_data) augment_method_docstring( method, new_class_dict, classname, param_names, param_values, new_method ) new_class_dict[new_method.__name__] = new_method @classmethod def new_method(cls, method, param_values): @wraps(method) def new_method(self): return method(self, *param_values) return new_method class ParameterizedTestMixin(metaclass=ParameterizedTestCaseMetaClass): @classmethod def parameterize( cls, param_names, data, func_name_format="{func_name}_{case_num:05d}" ): """Decorator for parameterizing a test method - example: @ParameterizedTestCase.parameterize( ("isbn", "expected_title"), [ ("0262033844", "Introduction to Algorithms"), ("0321558146", "Campbell Essential Biology")]) Parameters ---------- param_names : data : func_name_format : (Default value = "{func_name}_{case_num:05d}") Returns ------- """ def decorator(func): @wraps(func) def newfunc(*arg, **kwargs): return func(*arg, **kwargs) newfunc.param_names = param_names newfunc.data = data newfunc.func_name_format = func_name_format return newfunc return decorator class ParameterizedTestCase(unittest.TestCase, ParameterizedTestMixin): pass pint-0.10.1/pint/testsuite/test_application_registry.py000066400000000000000000000207531360526634500234330ustar00rootroot00000000000000"""Tests for global UnitRegistry, Unit, and Quantity """ import pickle from pint import ( Measurement, Quantity, UndefinedUnitError, Unit, UnitRegistry, get_application_registry, set_application_registry, ) from pint.testsuite import BaseTestCase from pint.testsuite.helpers import requires_uncertainties class TestDefaultApplicationRegistry(BaseTestCase): def test_unit(self): u = Unit("kg") self.assertEqual(str(u), "kilogram") u = pickle.loads(pickle.dumps(u)) self.assertEqual(str(u), "kilogram") def test_quantity_1arg(self): q = Quantity("123 kg") self.assertEqual(str(q.units), "kilogram") self.assertEqual(q.to("t").magnitude, 0.123) q = pickle.loads(pickle.dumps(q)) self.assertEqual(str(q.units), "kilogram") self.assertEqual(q.to("t").magnitude, 0.123) def test_quantity_2args(self): q = Quantity(123, "kg") self.assertEqual(str(q.units), "kilogram") self.assertEqual(q.to("t").magnitude, 0.123) q = pickle.loads(pickle.dumps(q)) self.assertEqual(str(q.units), "kilogram") self.assertEqual(q.to("t").magnitude, 0.123) @requires_uncertainties() def test_measurement_2args(self): m = Measurement(Quantity(123, "kg"), Quantity(15, "kg")) self.assertEqual(m.value.magnitude, 123) self.assertEqual(m.error.magnitude, 15) self.assertEqual(str(m.units), "kilogram") m = pickle.loads(pickle.dumps(m)) self.assertEqual(m.value.magnitude, 123) self.assertEqual(m.error.magnitude, 15) self.assertEqual(str(m.units), "kilogram") @requires_uncertainties() def test_measurement_3args(self): m = Measurement(123, 15, "kg") self.assertEqual(m.value.magnitude, 123) self.assertEqual(m.error.magnitude, 15) self.assertEqual(str(m.units), "kilogram") m = pickle.loads(pickle.dumps(m)) self.assertEqual(m.value.magnitude, 123) self.assertEqual(m.error.magnitude, 15) self.assertEqual(str(m.units), "kilogram") def test_get_application_registry(self): ureg = get_application_registry() u = ureg.Unit("kg") self.assertEqual(str(u), "kilogram") def test_pickle_crash(self): ureg = UnitRegistry(None) ureg.define("foo = []") q = ureg.Quantity(123, "foo") b = pickle.dumps(q) self.assertRaises(UndefinedUnitError, pickle.loads, b) b = pickle.dumps(q.units) self.assertRaises(UndefinedUnitError, pickle.loads, b) @requires_uncertainties() def test_pickle_crash_measurement(self): ureg = UnitRegistry(None) ureg.define("foo = []") m = ureg.Quantity(123, "foo").plus_minus(10) b = pickle.dumps(m) self.assertRaises(UndefinedUnitError, pickle.loads, b) class TestCustomApplicationRegistry(BaseTestCase): def setUp(self): super().setUp() self.ureg_bak = get_application_registry() self.ureg = UnitRegistry(None) self.ureg.define("foo = []") self.ureg.define("bar = foo / 2") set_application_registry(self.ureg) assert get_application_registry() is self.ureg def tearDown(self): super().tearDown() set_application_registry(self.ureg_bak) def test_unit(self): u = Unit("foo") self.assertEqual(str(u), "foo") u = pickle.loads(pickle.dumps(u)) self.assertEqual(str(u), "foo") def test_quantity_1arg(self): q = Quantity("123 foo") self.assertEqual(str(q.units), "foo") self.assertEqual(q.to("bar").magnitude, 246) q = pickle.loads(pickle.dumps(q)) self.assertEqual(str(q.units), "foo") self.assertEqual(q.to("bar").magnitude, 246) def test_quantity_2args(self): q = Quantity(123, "foo") self.assertEqual(str(q.units), "foo") self.assertEqual(q.to("bar").magnitude, 246) q = pickle.loads(pickle.dumps(q)) self.assertEqual(str(q.units), "foo") self.assertEqual(q.to("bar").magnitude, 246) @requires_uncertainties() def test_measurement_2args(self): m = Measurement(Quantity(123, "foo"), Quantity(10, "bar")) self.assertEqual(m.value.magnitude, 123) self.assertEqual(m.error.magnitude, 5) self.assertEqual(str(m.units), "foo") m = pickle.loads(pickle.dumps(m)) self.assertEqual(m.value.magnitude, 123) self.assertEqual(m.error.magnitude, 5) self.assertEqual(str(m.units), "foo") @requires_uncertainties() def test_measurement_3args(self): m = Measurement(123, 5, "foo") self.assertEqual(m.value.magnitude, 123) self.assertEqual(m.error.magnitude, 5) self.assertEqual(str(m.units), "foo") m = pickle.loads(pickle.dumps(m)) self.assertEqual(m.value.magnitude, 123) self.assertEqual(m.error.magnitude, 5) self.assertEqual(str(m.units), "foo") class TestSwapApplicationRegistry(BaseTestCase): """Test that the constructors of Quantity, Unit, and Measurement capture the registry that is set as the application registry at creation time Parameters ---------- Returns ------- """ def setUp(self): super().setUp() self.ureg_bak = get_application_registry() self.ureg1 = UnitRegistry(None) self.ureg1.define("foo = [dim1]") self.ureg1.define("bar = foo / 2") self.ureg2 = UnitRegistry(None) self.ureg2.define("foo = [dim2]") self.ureg2.define("bar = foo / 3") def tearDown(self): set_application_registry(self.ureg_bak) def test_quantity_1arg(self): set_application_registry(self.ureg1) q1 = Quantity("1 foo") set_application_registry(self.ureg2) q2 = Quantity("1 foo") q3 = pickle.loads(pickle.dumps(q1)) 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 def test_quantity_2args(self): set_application_registry(self.ureg1) q1 = Quantity(1, "foo") set_application_registry(self.ureg2) q2 = Quantity(1, "foo") q3 = pickle.loads(pickle.dumps(q1)) 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 def test_unit(self): set_application_registry(self.ureg1) u1 = Unit("bar") set_application_registry(self.ureg2) u2 = Unit("bar") u3 = pickle.loads(pickle.dumps(u1)) assert u1.dimensionality == {"[dim1]": 1} assert u2.dimensionality == {"[dim2]": 1} assert u3.dimensionality == {"[dim2]": 1} @requires_uncertainties() def test_measurement_2args(self): 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)) assert m1.dimensionality == {"[dim1]": 1} assert m2.dimensionality == {"[dim2]": 1} assert m3.dimensionality == {"[dim2]": 1} self.assertEqual(m1.to("bar").value.magnitude, 20) self.assertEqual(m2.to("bar").value.magnitude, 30) self.assertEqual(m3.to("bar").value.magnitude, 30) self.assertEqual(m1.to("bar").error.magnitude, 2) self.assertEqual(m2.to("bar").error.magnitude, 3) self.assertEqual(m3.to("bar").error.magnitude, 3) @requires_uncertainties() def test_measurement_3args(self): 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)) assert m1.dimensionality == {"[dim1]": 1} assert m2.dimensionality == {"[dim2]": 1} self.assertEqual(m1.to("bar").value.magnitude, 20) self.assertEqual(m2.to("bar").value.magnitude, 30) self.assertEqual(m3.to("bar").value.magnitude, 30) self.assertEqual(m1.to("bar").error.magnitude, 2) self.assertEqual(m2.to("bar").error.magnitude, 3) self.assertEqual(m3.to("bar").error.magnitude, 3) pint-0.10.1/pint/testsuite/test_babel.py000066400000000000000000000037631360526634500202470ustar00rootroot00000000000000import os from pint import UnitRegistry from pint.testsuite import BaseTestCase, helpers class TestBabel(BaseTestCase): @helpers.requires_babel() def test_format(self): ureg = UnitRegistry() dirname = os.path.dirname(__file__) ureg.load_definitions(os.path.join(dirname, "../xtranslated.txt")) distance = 24.0 * ureg.meter self.assertEqual( distance.format_babel(locale="fr_FR", length="long"), "24.0 mètres" ) time = 8.0 * ureg.second self.assertEqual( time.format_babel(locale="fr_FR", length="long"), "8.0 secondes" ) self.assertEqual(time.format_babel(locale="ro", length="short"), "8.0 s") acceleration = distance / time ** 2 self.assertEqual( acceleration.format_babel(locale="fr_FR", length="long"), "0.375 mètre par seconde²", ) mks = ureg.get_system("mks") self.assertEqual(mks.format_babel(locale="fr_FR"), "métrique") @helpers.requires_babel() def test_registry_locale(self): 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 self.assertEqual(distance.format_babel(length="long"), "24.0 mètres") time = 8.0 * ureg.second self.assertEqual(time.format_babel(length="long"), "8.0 secondes") self.assertEqual(time.format_babel(locale="ro", length="short"), "8.0 s") acceleration = distance / time ** 2 self.assertEqual( acceleration.format_babel(length="long"), "0.375 mètre par seconde²" ) mks = ureg.get_system("mks") self.assertEqual(mks.format_babel(locale="fr_FR"), "métrique") def test_nobabel(self): ureg = UnitRegistry() distance = 24.0 * ureg.meter self.assertRaises( Exception, distance.format_babel, locale="fr_FR", length="long" ) pint-0.10.1/pint/testsuite/test_compat_downcast.py000066400000000000000000000072731360526634500223670ustar00rootroot00000000000000import 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") # Set up unit registry and sample ureg = UnitRegistry(force_ndarray_like=True) q_base = (np.arange(25).reshape(5, 5).T + 1) * ureg.kg # Define identity function for use in tests def identity(x): return x @pytest.fixture(params=["sparse", "masked_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=np.float).reshape((5, 5)), mask=np.logical_not(np.triu(np.ones((5, 5)))), ) @pytest.mark.parametrize( "op, magnitude_op, unit_op", [ pytest.param(identity, identity, identity, id="identity"), pytest.param( lambda x: x + 1 * ureg.m, lambda x: x + 1, identity, id="addition" ), pytest.param( lambda x: x - 20 * ureg.cm, lambda x: x - 0.2, identity, id="subtraction" ), pytest.param( lambda x: x * (2 * ureg.s), lambda x: 2 * x, lambda u: u * ureg.s, id="multiplication", ), pytest.param( lambda x: x / (1 * ureg.s), identity, lambda u: u / ureg.s, id="division" ), pytest.param(lambda x: x ** 2, lambda x: x ** 2, lambda u: u ** 2, id="square"), pytest.param(lambda x: x.T, lambda x: x.T, identity, id="transpose"), pytest.param(np.mean, np.mean, identity, id="mean ufunc"), pytest.param(np.sum, np.sum, identity, id="sum ufunc"), pytest.param(np.sqrt, np.sqrt, lambda u: u ** 0.5, id="sqrt ufunc"), pytest.param( lambda x: np.reshape(x, 25), lambda x: np.reshape(x, 25), identity, id="reshape function", ), pytest.param(np.amax, np.amax, identity, id="amax function"), ], ) def test_univariate_op_consistency(op, magnitude_op, unit_op, array): q = ureg.Quantity(array, "meter") res = op(q) assert np.all(res.magnitude == magnitude_op(array)) # Magnitude check assert res.units == unit_op(q.units) # Unit check assert q.magnitude is array # Immutability check @pytest.mark.parametrize( "op, unit", [ pytest.param(lambda x, y: x * y, ureg("kg m"), id="multiplication"), pytest.param(lambda x, y: x / y, ureg("m / kg"), id="division"), pytest.param(np.multiply, ureg("kg m"), id="multiply ufunc"), ], ) def test_bivariate_op_consistency(op, unit, array): q = ureg.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( lambda a, u: a * u, id="array-first", marks=pytest.mark.xfail(reason="upstream issue numpy/numpy#15200"), ), pytest.param(lambda a, u: u * a, id="unit-first"), ], ) @pytest.mark.parametrize( "unit", [pytest.param(ureg.m, id="Unit"), pytest.param(ureg("meter"), id="Quantity")], ) def test_array_quantity_creation_by_multiplication(op, unit, array): assert type(op(array, unit)) == ureg.Quantity pint-0.10.1/pint/testsuite/test_compat_upcast.py000066400000000000000000000065641360526634500220460ustar00rootroot00000000000000import pytest from pint import UnitRegistry # 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") # Set up unit registry and sample ureg = UnitRegistry() q = [[1.0, 2.0], [3.0, 4.0]] * ureg.m @pytest.fixture def da(): return xr.DataArray(q.copy()) @pytest.fixture def ds(): return xr.tutorial.load_dataset("air_temperature") def test_xarray_quantity_creation(): with pytest.raises(TypeError) as exc: ureg.Quantity(xr.DataArray(np.arange(4)), "m") assert "Quantity cannot wrap upcast type" in str(exc) assert xr.DataArray(q).data is q def test_quantification(ds): da = ds["air"][0] da.data = ureg.Quantity(da.values, da.attrs.pop("units")) mean = da.mean().item() assert mean.units == ureg.K assert np.isclose(mean, 274.166259765625 * ureg.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", [ (q, xr.DataArray(q)), ( xr.DataArray([1.0, 2.0] * ureg.m, dims=("y",)), xr.DataArray( np.arange(6, dtype="float").reshape(3, 2, 1), dims=("z", "y", "x") ) * ureg.km, ), (1 * ureg.m, xr.DataArray(q)), ], ) def test_binary_arithmetic_commutativity(op, 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): assert np.all((q.T == da) == (da.transpose() == q)) def test_ne_commutativity(da): assert np.all((q != da.transpose()) == (da != q.T)) def test_dataset_operation_with_unit(ds): ds0 = ureg.K * ds.isel(time=0) ds1 = (ds * ureg.K).isel(time=0) xr.testing.assert_identical(ds0, ds1) assert np.isclose(ds0["air"].mean().item(), 274.166259765625 * ureg.K) def test_dataarray_inplace_arithmetic_roundtrip(da): da_original = da.copy() q_to_modify = q.copy() da += q xr.testing.assert_identical(da, xr.DataArray([[2, 4], [6, 8]] * ureg.m)) da -= q xr.testing.assert_identical(da, da_original) da *= ureg.m xr.testing.assert_identical(da, xr.DataArray(q * ureg.m)) da /= ureg.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)) def test_dataarray_inequalities(da): xr.testing.assert_identical( 2 * ureg.m > da, xr.DataArray([[True, False], [False, False]]) ) xr.testing.assert_identical( 2 * ureg.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): lower = 2 * ureg.m upper = 3 * ureg.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): lower = 2 * ureg.m assert lower.__array_ufunc__(np.maximum, "__call__", lower, da) is NotImplemented pint-0.10.1/pint/testsuite/test_contexts.py000066400000000000000000000762271360526634500210560ustar00rootroot00000000000000import itertools import math from collections import defaultdict from pint import ( DefinitionSyntaxError, DimensionalityError, UndefinedUnitError, UnitRegistry, ) from pint.context import Context from pint.testsuite import QuantityTestCase 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(QuantityTestCase): def test_known_context(self): ureg = UnitRegistry() add_ctxs(ureg) with ureg.context("lc"): self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) with ureg.context("lc", n=1): self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) def test_known_context_enable(self): ureg = UnitRegistry() add_ctxs(ureg) ureg.enable_contexts("lc") self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) ureg.disable_contexts(1) self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) ureg.enable_contexts("lc", n=1) self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) ureg.disable_contexts(1) self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) def test_graph(self): ureg = UnitRegistry() 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"): self.assertEqual(ureg._active_ctx.graph, g_sp) with ureg.context("lc", n=1): self.assertEqual(ureg._active_ctx.graph, g_sp) with ureg.context("ab"): self.assertEqual(ureg._active_ctx.graph, g_ab) with ureg.context("lc"): with ureg.context("ab"): self.assertEqual(ureg._active_ctx.graph, g) with ureg.context("ab"): with ureg.context("lc"): self.assertEqual(ureg._active_ctx.graph, g) with ureg.context("lc", "ab"): self.assertEqual(ureg._active_ctx.graph, g) with ureg.context("ab", "lc"): self.assertEqual(ureg._active_ctx.graph, g) def test_graph_enable(self): ureg = UnitRegistry() 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") self.assertEqual(ureg._active_ctx.graph, g_sp) ureg.disable_contexts(1) ureg.enable_contexts("lc", n=1) self.assertEqual(ureg._active_ctx.graph, g_sp) ureg.disable_contexts(1) ureg.enable_contexts("ab") self.assertEqual(ureg._active_ctx.graph, g_ab) ureg.disable_contexts(1) ureg.enable_contexts("lc") ureg.enable_contexts("ab") self.assertEqual(ureg._active_ctx.graph, g) ureg.disable_contexts(2) ureg.enable_contexts("ab") ureg.enable_contexts("lc") self.assertEqual(ureg._active_ctx.graph, g) ureg.disable_contexts(2) ureg.enable_contexts("lc", "ab") self.assertEqual(ureg._active_ctx.graph, g) ureg.disable_contexts(2) ureg.enable_contexts("ab", "lc") self.assertEqual(ureg._active_ctx.graph, g) ureg.disable_contexts(2) def test_known_nested_context(self): ureg = UnitRegistry() add_ctxs(ureg) with ureg.context("lc"): x = dict(ureg._active_ctx) y = dict(ureg._active_ctx.graph) self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) with ureg.context("ab"): self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) self.assertNotEqual(x, ureg._active_ctx) self.assertNotEqual(y, ureg._active_ctx.graph) self.assertEqual(x, ureg._active_ctx) self.assertEqual(y, ureg._active_ctx.graph) self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) def test_unknown_context(self): ureg = UnitRegistry() add_ctxs(ureg) with self.assertRaises(KeyError): with ureg.context("la"): pass self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) def test_unknown_nested_context(self): ureg = UnitRegistry() add_ctxs(ureg) with ureg.context("lc"): x = dict(ureg._active_ctx) y = dict(ureg._active_ctx.graph) with self.assertRaises(KeyError): with ureg.context("la"): pass self.assertEqual(x, ureg._active_ctx) self.assertEqual(y, ureg._active_ctx.graph) self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) def test_one_context(self): ureg = UnitRegistry() 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) self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc"): self.assertEqual(q.to("Hz"), s) self.assertEqual(ureg.get_compatible_units(q), meter_units | hertz_units) self.assertRaises(DimensionalityError, q.to, "Hz") self.assertEqual(ureg.get_compatible_units(q), meter_units) def test_multiple_context(self): ureg = UnitRegistry() 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) self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc", "ab"): self.assertEqual(q.to("Hz"), s) self.assertEqual( ureg.get_compatible_units(q), meter_units | hertz_units | ampere_units ) self.assertRaises(DimensionalityError, q.to, "Hz") self.assertEqual(ureg.get_compatible_units(q), meter_units) def test_nested_context(self): ureg = UnitRegistry() add_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc"): self.assertEqual(q.to("Hz"), s) with ureg.context("ab"): self.assertEqual(q.to("Hz"), s) self.assertEqual(q.to("Hz"), s) with ureg.context("ab"): self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc"): self.assertEqual(q.to("Hz"), s) self.assertRaises(DimensionalityError, q.to, "Hz") def test_context_with_arg(self): ureg = UnitRegistry() add_arg_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc", n=1): self.assertEqual(q.to("Hz"), s) with ureg.context("ab"): self.assertEqual(q.to("Hz"), s) self.assertEqual(q.to("Hz"), s) with ureg.context("ab"): self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc", n=1): self.assertEqual(q.to("Hz"), s) self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc"): self.assertRaises(TypeError, q.to, "Hz") def test_enable_context_with_arg(self): ureg = UnitRegistry() add_arg_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") self.assertRaises(DimensionalityError, q.to, "Hz") ureg.enable_contexts("lc", n=1) self.assertEqual(q.to("Hz"), s) ureg.enable_contexts("ab") self.assertEqual(q.to("Hz"), s) self.assertEqual(q.to("Hz"), s) ureg.disable_contexts(1) ureg.disable_contexts(1) ureg.enable_contexts("ab") self.assertRaises(DimensionalityError, q.to, "Hz") ureg.enable_contexts("lc", n=1) self.assertEqual(q.to("Hz"), s) ureg.disable_contexts(1) self.assertRaises(DimensionalityError, q.to, "Hz") ureg.disable_contexts(1) ureg.enable_contexts("lc") self.assertRaises(TypeError, q.to, "Hz") ureg.disable_contexts(1) def test_context_with_arg_def(self): ureg = UnitRegistry() add_argdef_ctxs(ureg) q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc"): self.assertEqual(q.to("Hz"), s) with ureg.context("ab"): self.assertEqual(q.to("Hz"), s) self.assertEqual(q.to("Hz"), s) with ureg.context("ab"): self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc"): self.assertEqual(q.to("Hz"), s) self.assertRaises(DimensionalityError, q.to, "Hz") self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc", n=2): self.assertEqual(q.to("Hz"), s / 2) with ureg.context("ab"): self.assertEqual(q.to("Hz"), s / 2) self.assertEqual(q.to("Hz"), s / 2) with ureg.context("ab"): self.assertRaises(DimensionalityError, q.to, "Hz") with ureg.context("lc", n=2): self.assertEqual(q.to("Hz"), s / 2) self.assertRaises(DimensionalityError, q.to, "Hz") def test_context_with_sharedarg_def(self): ureg = UnitRegistry() 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"): self.assertEqual(q.to("Hz"), s) with ureg.context("ab"): self.assertEqual(q.to("ampere"), u) with ureg.context("ab"): self.assertEqual(q.to("ampere"), 0 * u) with ureg.context("lc"): self.assertRaises(ZeroDivisionError, ureg.Quantity.to, q, "Hz") with ureg.context("lc", n=2): self.assertEqual(q.to("Hz"), s / 2) with ureg.context("ab"): self.assertEqual(q.to("ampere"), 2 * u) with ureg.context("ab", n=3): self.assertEqual(q.to("ampere"), 3 * u) with ureg.context("lc"): self.assertEqual(q.to("Hz"), s / 3) with ureg.context("lc", n=2): self.assertEqual(q.to("Hz"), s / 2) with ureg.context("ab", n=4): self.assertEqual(q.to("ampere"), 4 * u) with ureg.context("ab", n=3): self.assertEqual(q.to("ampere"), 3 * u) with ureg.context("lc", n=6): self.assertEqual(q.to("Hz"), s / 6) def test_anonymous_context(self): ureg = UnitRegistry() c = Context() c.add_transformation("[length]", "[time]", lambda ureg, x: x / ureg("5 cm/s")) self.assertRaises(ValueError, ureg.add_context, c) x = ureg("10 cm") expect = ureg("2 s") self.assertQuantityEqual(x.to("s", c), expect) with ureg.context(c): self.assertQuantityEqual(x.to("s"), expect) ureg.enable_contexts(c) self.assertQuantityEqual(x.to("s"), expect) ureg.disable_contexts(1) self.assertRaises(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): self.assertQuantityEqual(x.to("s"), expect) # Transformations only in c2 are still working even if c takes priority self.assertQuantityEqual(ureg("100 kg").to("s"), ureg("10 s")) with ureg.context(c, c2): self.assertQuantityEqual(x.to("s"), ureg("1 s")) def _test_ctx(self, ctx): ureg = UnitRegistry() q = 500 * ureg.meter s = (ureg.speed_of_light / q).to("Hz") nctx = len(ureg._contexts) self.assertNotIn(ctx.name, ureg._contexts) ureg.add_context(ctx) self.assertIn(ctx.name, ureg._contexts) self.assertEqual(len(ureg._contexts), nctx + 1 + len(ctx.aliases)) with ureg.context(ctx.name): self.assertEqual(q.to("Hz"), s) self.assertEqual(s.to("meter"), q) ureg.remove_context(ctx.name) self.assertNotIn(ctx.name, ureg._contexts) self.assertEqual(len(ureg._contexts), nctx) def test_parse_invalid(self): for badrow in ( "[length] = 1 / [time]: c / value", "1 / [time] = [length]: c / value", "[length] <- [time] = c / value", "[length] - [time] = c / value", ): with self.subTest(badrow): with self.assertRaises(DefinitionSyntaxError): Context.from_lines(["@context c", badrow]) def test_parse_simple(self): a = Context.__keytransform__( UnitsContainer({"[time]": -1}), UnitsContainer({"[length]": 1}) ) b = Context.__keytransform__( UnitsContainer({"[length]": 1}), UnitsContainer({"[time]": -1}) ) s = [ "@context longcontextname", "[length] -> 1 / [time]: c / value", "1 / [time] -> [length]: c / value", ] c = Context.from_lines(s) self.assertEqual(c.name, "longcontextname") self.assertEqual(c.aliases, ()) self.assertEqual(c.defaults, {}) self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) s = ["@context longcontextname = lc", "[length] <-> 1 / [time]: c / value"] c = Context.from_lines(s) self.assertEqual(c.name, "longcontextname") self.assertEqual(c.aliases, ("lc",)) self.assertEqual(c.defaults, {}) self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) s = [ "@context longcontextname = lc = lcn", "[length] <-> 1 / [time]: c / value", ] c = Context.from_lines(s) self.assertEqual(c.name, "longcontextname") self.assertEqual(c.aliases, ("lc", "lcn")) self.assertEqual(c.defaults, {}) self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) def test_parse_auto_inverse(self): 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) self.assertEqual(c.defaults, {}) self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) def test_parse_define(self): 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) self.assertEqual(c.defaults, {}) self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) def test_parse_parameterized(self): 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) self.assertEqual(c.defaults, {"n": 1}) self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) s = [ "@context(n=1, bla=2) longcontextname", "[length] <-> 1 / [time]: n * c / value / bla", ] c = Context.from_lines(s) self.assertEqual(c.defaults, {"n": 1, "bla": 2}) self.assertEqual(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"] self.assertRaises(DefinitionSyntaxError, Context.from_lines, s) def test_warnings(self): ureg = UnitRegistry() with self.capture_log() as buffer: add_ctxs(ureg) d = Context("ab") ureg.add_context(d) self.assertEqual(len(buffer), 1) self.assertIn("ab", str(buffer[-1])) d = Context("ab1", aliases=("ab",)) ureg.add_context(d) self.assertEqual(len(buffer), 2) self.assertIn("ab", str(buffer[-1])) class TestDefinedContexts(QuantityTestCase): FORCE_NDARRAY = False def test_defined(self): ureg = self.ureg 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}) ) self.assertIn(a, ureg._contexts["sp"].funcs) self.assertIn(b, ureg._contexts["sp"].funcs) with ureg.context("sp"): self.assertIn(a, ureg._active_ctx) self.assertIn(b, ureg._active_ctx) def test_spectroscopy(self): ureg = self.ureg 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) self.assertTrue(p) msg = "{} <-> {}".format(a, b) # assertAlmostEqualRelError converts second to first self.assertQuantityAlmostEqual(b, a, rtol=0.01, msg=msg) for a, b in itertools.product(eq, eq): self.assertQuantityAlmostEqual(a.to(b.units, "sp"), b, rtol=0.01) def test_textile(self): ureg = self.ureg qty_direct = 1.331 * ureg.tex with self.assertRaises(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) self.assertTrue(p) msg = "{} <-> {}".format(a, b) self.assertQuantityAlmostEqual(b, a, rtol=0.01, msg=msg) # Check RKM <-> cN/tex conversion self.assertQuantityAlmostEqual(1 * ureg.RKM, 0.980665 * ureg.cN / ureg.tex) self.assertQuantityAlmostEqual( (1 / 0.980665) * ureg.RKM, 1 * ureg.cN / ureg.tex ) self.assertAlmostEqual((1 * ureg.RKM).to(ureg.cN / ureg.tex).m, 0.980665) self.assertAlmostEqual( (1 * ureg.cN / ureg.tex).to(ureg.RKM).m, 1 / 0.980665 ) def test_decorator(self): ureg = self.ureg a = 532.0 * ureg.nm with ureg.context("sp"): b = a.to("terahertz") def f(wl): return wl.to("terahertz") self.assertRaises(DimensionalityError, f, a) @ureg.with_context("sp") def g(wl): return wl.to("terahertz") self.assertEqual(b, g(a)) def test_decorator_composition(self): ureg = self.ureg 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") self.assertEqual(b, f(a)) self.assertEqual(b, g(a)) class TestContextRedefinitions(QuantityTestCase): def test_redefine(self): 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 self.subTest(enable_ctx): if enable_ctx: ureg.enable_contexts("c") k = 5 else: k = 2 self.assertEqual(foo.to("b").magnitude, 1 / k) self.assertEqual(foo.to("bar").magnitude, 1 / k) self.assertEqual(foo.to("bar_alias").magnitude, 1 / k) self.assertEqual(foo.to("baz").magnitude, 1 / k / 3) self.assertEqual(bar.to("foo").magnitude, k) self.assertEqual(bar.to("baz").magnitude, 1 / 3) self.assertEqual(asd.to("foo").magnitude, 4 * 3 * k) self.assertEqual(asd.to("bar").magnitude, 4 * 3) self.assertEqual(asd.to("baz").magnitude, 4) ureg.disable_contexts() def test_define_nan(self): 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") self.assertEquals(q.magnitude, 10) self.assertEquals(q.units.dimensionality, {"[currency]": 1}) self.assertEquals(q.to("GBP").magnitude, 10) self.assertTrue(math.isnan(q.to("USD").magnitude)) self.assertAlmostEqual(q.to("USD", "c").magnitude, 10 * 1.18 * 1.11) def test_non_multiplicative(self): 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 self.subTest("baseline"): self.assertAlmostEqual(k.to("fahrenheit").magnitude, (100 - 255) * 9 / 5) self.assertAlmostEqual(k.to("bogodegrees").magnitude, 100 / 9) with self.subTest("nonmult_to_nonmult"): with ureg.context("nonmult_to_nonmult"): self.assertAlmostEqual(k.to("fahrenheit").magnitude, (100 - 123) / 7) with self.subTest("nonmult_to_mult"): with ureg.context("nonmult_to_mult"): self.assertAlmostEqual(k.to("fahrenheit").magnitude, 100 / 123) with self.subTest("mult_to_nonmult"): with ureg.context("mult_to_nonmult"): self.assertAlmostEqual(k.to("bogodegrees").magnitude, (100 - 123) / 5) def test_err_to_base_unit(self): with self.assertRaises(DefinitionSyntaxError) as e: Context.from_lines(["@context c", "x = [d]"]) self.assertEquals(str(e.exception), "Can't define base units within a context") def test_err_change_base_unit(self): ureg = UnitRegistry( """ foo = [d1] bar = [d2] @context c bar = foo @end """.splitlines() ) with self.assertRaises(ValueError) as e: ureg.enable_contexts("c") self.assertEquals( str(e.exception), "Can't redefine a base unit to a derived one" ) def test_err_change_dimensionality(self): ureg = UnitRegistry( """ foo = [d1] bar = [d2] baz = foo @context c baz = bar @end """.splitlines() ) with self.assertRaises(ValueError) as e: ureg.enable_contexts("c") self.assertEquals( str(e.exception), "Can't change dimensionality of baz from [d1] to [d2] in a context", ) def test_err_cyclic_dependency(self): 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 self.assertRaises(RecursionError): q.to("foo") def test_err_dimension_redefinition(self): with self.assertRaises(DefinitionSyntaxError) as e: Context.from_lines(["@context c", "[d1] = [d2] * [d3]"]) self.assertEquals( str(e.exception), "Expected = ; got [d1] = [d2] * [d3]" ) def test_err_prefix_redefinition(self): with self.assertRaises(DefinitionSyntaxError) as e: Context.from_lines(["@context c", "[d1] = [d2] * [d3]"]) self.assertEquals( str(e.exception), "Expected = ; got [d1] = [d2] * [d3]" ) def test_err_redefine_alias(self): for s in ("foo = bar = f", "foo = bar = _ = baz"): with self.subTest(s): with self.assertRaises(DefinitionSyntaxError) as e: Context.from_lines(["@context c", s]) self.assertEquals( str(e.exception), "Can't change a unit's symbol or aliases within a context", ) def test_err_redefine_with_prefix(self): ureg = UnitRegistry( """ kilo- = 1000 gram = [mass] pound = 454 gram @context c kilopound = 500000 gram @end """.splitlines() ) with self.assertRaises(ValueError) as e: ureg.enable_contexts("c") self.assertEquals( str(e.exception), "Can't redefine a unit with a prefix: kilopound" ) def test_err_new_unit(self): ureg = UnitRegistry( """ foo = [d] @context c bar = foo @end """.splitlines() ) with self.assertRaises(UndefinedUnitError) as e: ureg.enable_contexts("c") self.assertEquals(str(e.exception), "'bar' is not defined in the unit registry") pint-0.10.1/pint/testsuite/test_converters.py000066400000000000000000000026461360526634500213730ustar00rootroot00000000000000import itertools from pint.compat import np from pint.converters import Converter, OffsetConverter, ScaleConverter from pint.testsuite import BaseTestCase, helpers class TestConverter(BaseTestCase): def test_converter(self): c = Converter() self.assertTrue(c.is_multiplicative) self.assertTrue(c.to_reference(8)) self.assertTrue(c.from_reference(8)) def test_multiplicative_converter(self): c = ScaleConverter(20.0) self.assertEqual(c.from_reference(c.to_reference(100)), 100) self.assertEqual(c.to_reference(c.from_reference(100)), 100) def test_offset_converter(self): c = OffsetConverter(20.0, 2) self.assertEqual(c.from_reference(c.to_reference(100)), 100) self.assertEqual(c.to_reference(c.from_reference(100)), 100) @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, self.assertIs), (False, self.assertIsNot)) ): a = np.ones((1, 10)) ac = np.ones((1, 10)) r = fun(a, inplace) np.testing.assert_allclose(r, ac) comp(a, r) pint-0.10.1/pint/testsuite/test_definitions.py000066400000000000000000000107151360526634500215100ustar00rootroot00000000000000from pint.converters import OffsetConverter, ScaleConverter from pint.definitions import ( AliasDefinition, Definition, DimensionDefinition, PrefixDefinition, UnitDefinition, ) from pint.errors import DefinitionSyntaxError from pint.testsuite import BaseTestCase from pint.util import UnitsContainer class TestDefinition(BaseTestCase): def test_invalid(self): with self.assertRaises(DefinitionSyntaxError): Definition.from_string("x = [time] * meter") with self.assertRaises(DefinitionSyntaxError): Definition.from_string("[x] = [time] * meter") def test_prefix_definition(self): self.assertRaises(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) self.assertIsInstance(x, PrefixDefinition) self.assertEqual(x.name, "m") self.assertEqual(x.aliases, ()) self.assertEqual(x.converter.to_reference(1000), 1) self.assertEqual(x.converter.from_reference(0.001), 1) self.assertEqual(str(x), "m") x = Definition.from_string("kilo- = 1e-3 = k-") self.assertIsInstance(x, PrefixDefinition) self.assertEqual(x.name, "kilo") self.assertEqual(x.aliases, ()) self.assertEqual(x.symbol, "k") self.assertEqual(x.converter.to_reference(1000), 1) self.assertEqual(x.converter.from_reference(0.001), 1) x = Definition.from_string("kilo- = 1e-3 = k- = anotherk-") self.assertIsInstance(x, PrefixDefinition) self.assertEqual(x.name, "kilo") self.assertEqual(x.aliases, ("anotherk",)) self.assertEqual(x.symbol, "k") self.assertEqual(x.converter.to_reference(1000), 1) self.assertEqual(x.converter.from_reference(0.001), 1) def test_baseunit_definition(self): x = Definition.from_string("meter = [length]") self.assertIsInstance(x, UnitDefinition) self.assertTrue(x.is_base) self.assertEqual(x.reference, UnitsContainer({"[length]": 1})) def test_unit_definition(self): x = Definition.from_string("coulomb = ampere * second") self.assertIsInstance(x, UnitDefinition) self.assertFalse(x.is_base) self.assertIsInstance(x.converter, ScaleConverter) self.assertEqual(x.converter.scale, 1) self.assertEqual(x.reference, UnitsContainer(ampere=1, second=1)) x = Definition.from_string("faraday = 96485.3399 * coulomb") self.assertIsInstance(x, UnitDefinition) self.assertFalse(x.is_base) self.assertIsInstance(x.converter, ScaleConverter) self.assertEqual(x.converter.scale, 96485.3399) self.assertEqual(x.reference, UnitsContainer(coulomb=1)) x = Definition.from_string("degF = 9 / 5 * kelvin; offset: 255.372222") self.assertIsInstance(x, UnitDefinition) self.assertFalse(x.is_base) self.assertIsInstance(x.converter, OffsetConverter) self.assertEqual(x.converter.scale, 9 / 5) self.assertEqual(x.converter.offset, 255.372222) self.assertEqual(x.reference, UnitsContainer(kelvin=1)) x = Definition.from_string( "turn = 6.28 * radian = _ = revolution = = cycle = _" ) self.assertIsInstance(x, UnitDefinition) self.assertEqual(x.name, "turn") self.assertEqual(x.aliases, ("revolution", "cycle")) self.assertEqual(x.symbol, "turn") self.assertFalse(x.is_base) self.assertIsInstance(x.converter, ScaleConverter) self.assertEqual(x.converter.scale, 6.28) self.assertEqual(x.reference, UnitsContainer(radian=1)) self.assertRaises( ValueError, Definition.from_string, "degF = 9 / 5 * kelvin; offset: 255.372222 bla", ) def test_dimension_definition(self): x = DimensionDefinition("[time]", "", (), converter="") self.assertTrue(x.is_base) self.assertEqual(x.name, "[time]") x = Definition.from_string("[speed] = [length]/[time]") self.assertIsInstance(x, DimensionDefinition) self.assertEqual(x.reference, UnitsContainer({"[length]": 1, "[time]": -1})) def test_alias_definition(self): x = Definition.from_string("@alias meter = metro = metr") self.assertIsInstance(x, AliasDefinition) self.assertEqual(x.name, "meter") self.assertEqual(x.aliases, ("metro", "metr")) pint-0.10.1/pint/testsuite/test_errors.py000066400000000000000000000111741360526634500205110ustar00rootroot00000000000000import pickle from pint import ( DefinitionSyntaxError, DimensionalityError, OffsetUnitCalculusError, Quantity, RedefinitionError, UndefinedUnitError, UnitRegistry, ) from pint.testsuite import BaseTestCase class TestErrors(BaseTestCase): def test_definition_syntax_error(self): ex = DefinitionSyntaxError("foo") self.assertEqual(str(ex), "foo") # filename and lineno can be attached after init ex.filename = "a.txt" ex.lineno = 123 self.assertEqual(str(ex), "While opening a.txt, in line 123: foo") ex = DefinitionSyntaxError("foo", lineno=123) self.assertEqual(str(ex), "In line 123: foo") ex = DefinitionSyntaxError("foo", filename="a.txt") self.assertEqual(str(ex), "While opening a.txt: foo") ex = DefinitionSyntaxError("foo", filename="a.txt", lineno=123) self.assertEqual(str(ex), "While opening a.txt, in line 123: foo") def test_redefinition_error(self): ex = RedefinitionError("foo", "bar") self.assertEqual(str(ex), "Cannot redefine 'foo' (bar)") # filename and lineno can be attached after init ex.filename = "a.txt" ex.lineno = 123 self.assertEqual( str(ex), "While opening a.txt, in line 123: Cannot redefine 'foo' (bar)" ) ex = RedefinitionError("foo", "bar", lineno=123) self.assertEqual(str(ex), "In line 123: Cannot redefine 'foo' (bar)") ex = RedefinitionError("foo", "bar", filename="a.txt") self.assertEqual(str(ex), "While opening a.txt: Cannot redefine 'foo' (bar)") ex = RedefinitionError("foo", "bar", filename="a.txt", lineno=123) self.assertEqual( str(ex), "While opening a.txt, in line 123: Cannot redefine 'foo' (bar)" ) def test_undefined_unit_error(self): x = ("meter",) msg = "'meter' is not defined in the unit registry" self.assertEqual(str(UndefinedUnitError(x)), msg) self.assertEqual(str(UndefinedUnitError(list(x))), msg) self.assertEqual(str(UndefinedUnitError(set(x))), msg) def test_undefined_unit_error_multi(self): x = ("meter", "kg") msg = "('meter', 'kg') are not defined in the unit registry" self.assertEqual(str(UndefinedUnitError(x)), msg) self.assertEqual(str(UndefinedUnitError(list(x))), msg) def test_dimensionality_error(self): ex = DimensionalityError("a", "b") self.assertEqual(str(ex), "Cannot convert from 'a' to 'b'") ex = DimensionalityError("a", "b", "c") self.assertEqual(str(ex), "Cannot convert from 'a' (c) to 'b' ()") ex = DimensionalityError("a", "b", "c", "d", extra_msg=": msg") self.assertEqual(str(ex), "Cannot convert from 'a' (c) to 'b' (d): msg") def test_offset_unit_calculus_error(self): ex = OffsetUnitCalculusError(Quantity("1 kg")._units) self.assertEqual( str(ex), "Ambiguous operation with offset unit (kilogram). See " "https://pint.readthedocs.io/en/latest/nonmult.html for guidance.", ) ex = OffsetUnitCalculusError(Quantity("1 kg")._units, Quantity("1 s")._units) self.assertEqual( str(ex), "Ambiguous operation with offset unit (kilogram, second). See " "https://pint.readthedocs.io/en/latest/nonmult.html for guidance.", ) def test_pickle_definition_syntax_error(self): # 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") pik = pickle.dumps(ureg.Quantity("1 foo")) with self.assertRaises(UndefinedUnitError): pickle.loads(pik) q1 = ureg.Quantity("1 foo") q2 = ureg.Quantity("1 bar") 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 self.subTest(etype=type(ex)): # assert False, ex.__reduce__() ex2 = pickle.loads(pickle.dumps(ex)) assert type(ex) is type(ex2) self.assertEqual(ex.args, ex2.args) self.assertEqual(ex.__dict__, ex2.__dict__) self.assertEqual(str(ex), str(ex2)) pint-0.10.1/pint/testsuite/test_formatter.py000066400000000000000000000035241360526634500212000ustar00rootroot00000000000000from pint import formatting as fmt from pint.testsuite import QuantityTestCase class TestFormatter(QuantityTestCase): def test_join(self): for empty in (tuple(), []): self.assertEqual(fmt._join("s", empty), "") self.assertEqual(fmt._join("*", "1 2 3".split()), "1*2*3") self.assertEqual(fmt._join("{0}*{1}", "1 2 3".split()), "1*2*3") def test_formatter(self): self.assertEqual(fmt.formatter(dict().items()), "") self.assertEqual(fmt.formatter(dict(meter=1).items()), "meter") self.assertEqual(fmt.formatter(dict(meter=-1).items()), "1 / meter") self.assertEqual( fmt.formatter(dict(meter=-1).items(), as_ratio=False), "meter ** -1" ) self.assertEqual( fmt.formatter(dict(meter=-1, second=-1).items(), as_ratio=False), "meter ** -1 * second ** -1", ) self.assertEqual( fmt.formatter(dict(meter=-1, second=-1).items()), "1 / meter / second" ) self.assertEqual( fmt.formatter(dict(meter=-1, second=-1).items(), single_denominator=True), "1 / (meter * second)", ) self.assertEqual( fmt.formatter(dict(meter=-1, second=-2).items()), "1 / meter / second ** 2" ) self.assertEqual( fmt.formatter(dict(meter=-1, second=-2).items(), single_denominator=True), "1 / (meter * second ** 2)", ) def test_parse_spec(self): self.assertEqual(fmt._parse_spec(""), "") self.assertEqual(fmt._parse_spec(""), "") self.assertRaises(ValueError, fmt._parse_spec, "W") self.assertRaises(ValueError, fmt._parse_spec, "PL") def test_format_unit(self): self.assertEqual(fmt.format_unit("", "C"), "dimensionless") self.assertRaises(ValueError, fmt.format_unit, "m", "W") pint-0.10.1/pint/testsuite/test_infer_base_unit.py000066400000000000000000000021641360526634500223300ustar00rootroot00000000000000from pint import Quantity as Q from pint.testsuite import QuantityTestCase from pint.util import infer_base_unit class TestInferBaseUnit(QuantityTestCase): def test_infer_base_unit(self): from pint.util import infer_base_unit self.assertEqual( infer_base_unit(Q(1, "millimeter * nanometer")), Q(1, "meter**2").units ) def test_units_adding_to_zero(self): self.assertEqual(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") self.assertQuantityAlmostEqual(compact_r, expected) r = (Q(1, "m") * Q(1, "mm") / Q(1, "m") / Q(2, "um") * Q(2, "s")).to_compact() self.assertQuantityAlmostEqual(r, Q(1000, "s")) 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) self.assertEqual(b, Q(1, "V").units) self.assertQuantityAlmostEqual(r, Q(1, "uV")) pint-0.10.1/pint/testsuite/test_issues.py000066400000000000000000000613501360526634500205110ustar00rootroot00000000000000import copy import math import pprint import unittest import pytest from pint import Context, DimensionalityError, UnitRegistry from pint.compat import np from pint.testsuite import QuantityTestCase, helpers from pint.unit import UnitsContainer from pint.util import ParserHelper ureg = UnitRegistry() class TestIssues(QuantityTestCase): FORCE_NDARRAY = False def setup(self): self.ureg.autoconvert_offset_to_baseunit = False @unittest.expectedFailure def test_issue25(self): x = ParserHelper.from_string("10 %") self.assertEqual(x, ParserHelper(10, {"%": 1})) x = ParserHelper.from_string("10 ‰") self.assertEqual(x, ParserHelper(10, {"‰": 1})) ureg.define("percent = [fraction]; offset: 0 = %") ureg.define("permille = percent / 10 = ‰") x = ureg.parse_expression("10 %") self.assertEqual(x, ureg.Quantity(10, {"%": 1})) y = ureg.parse_expression("10 ‰") self.assertEqual(y, ureg.Quantity(10, {"‰": 1})) self.assertEqual(x.to("‰"), ureg.Quantity(1, {"‰": 1})) def test_issue29(self): t = 4 * ureg("mW") self.assertEqual(t.magnitude, 4) self.assertEqual(t._units, UnitsContainer(milliwatt=1)) self.assertEqual(t.to("joule / second"), 4e-3 * ureg("W")) @unittest.expectedFailure @helpers.requires_numpy() def test_issue37(self): x = np.ma.masked_array([1, 2, 3], mask=[True, True, False]) q = ureg.meter * x self.assertIsInstance(q, ureg.Quantity) np.testing.assert_array_equal(q.magnitude, x) self.assertEqual(q.units, ureg.meter.units) q = x * ureg.meter self.assertIsInstance(q, ureg.Quantity) np.testing.assert_array_equal(q.magnitude, x) self.assertEqual(q.units, ureg.meter.units) m = np.ma.masked_array(2 * np.ones(3, 3)) qq = q * m self.assertIsInstance(qq, ureg.Quantity) np.testing.assert_array_equal(qq.magnitude, x * m) self.assertEqual(qq.units, ureg.meter.units) qq = m * q self.assertIsInstance(qq, ureg.Quantity) np.testing.assert_array_equal(qq.magnitude, x * m) self.assertEqual(qq.units, ureg.meter.units) @unittest.expectedFailure @helpers.requires_numpy() def test_issue39(self): x = np.matrix([[1, 2, 3], [1, 2, 3], [1, 2, 3]]) q = ureg.meter * x self.assertIsInstance(q, ureg.Quantity) np.testing.assert_array_equal(q.magnitude, x) self.assertEqual(q.units, ureg.meter.units) q = x * ureg.meter self.assertIsInstance(q, ureg.Quantity) np.testing.assert_array_equal(q.magnitude, x) self.assertEqual(q.units, ureg.meter.units) m = np.matrix(2 * np.ones(3, 3)) qq = q * m self.assertIsInstance(qq, ureg.Quantity) np.testing.assert_array_equal(qq.magnitude, x * m) self.assertEqual(qq.units, ureg.meter.units) qq = m * q self.assertIsInstance(qq, ureg.Quantity) np.testing.assert_array_equal(qq.magnitude, x * m) self.assertEqual(qq.units, ureg.meter.units) @helpers.requires_numpy() def test_issue44(self): x = 4.0 * ureg.dimensionless np.sqrt(x) self.assertQuantityAlmostEqual( np.sqrt([4.0] * ureg.dimensionless), [2.0] * ureg.dimensionless ) self.assertQuantityAlmostEqual( np.sqrt(4.0 * ureg.dimensionless), 2.0 * ureg.dimensionless ) def test_issue45(self): import math self.assertAlmostEqual(math.sqrt(4 * ureg.m / ureg.cm), math.sqrt(4 * 100)) self.assertAlmostEqual(float(ureg.V / ureg.mV), 1000.0) @helpers.requires_numpy() def test_issue45b(self): self.assertAlmostEqual( np.sin([np.pi / 2] * ureg.m / ureg.m), np.sin([np.pi / 2] * ureg.dimensionless), ) self.assertAlmostEqual( np.sin([np.pi / 2] * ureg.cm / ureg.m), np.sin([np.pi / 2] * ureg.dimensionless * 0.01), ) def test_issue50(self): Q_ = ureg.Quantity self.assertEqual(Q_(100), 100 * ureg.dimensionless) self.assertEqual(Q_("100"), 100 * ureg.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, ): self.assertRaises(ValueError, fun, q1, q2) def test_issue54(self): self.assertEqual((1 * ureg.km / ureg.m + 1).magnitude, 1001) def test_issue54_related(self): self.assertEqual(ureg.km / ureg.m, 1000) self.assertEqual(1000, ureg.km / ureg.m) self.assertLess(900, ureg.km / ureg.m) self.assertGreater(1100, ureg.km / ureg.m) def test_issue61(self): Q_ = ureg.Quantity for value in ({}, {"a": 3}, None): self.assertRaises(TypeError, Q_, value) self.assertRaises(TypeError, Q_, value, "meter") self.assertRaises(ValueError, Q_, "", "meter") self.assertRaises(ValueError, Q_, "") @helpers.requires_not_numpy() def test_issue61_notNP(self): Q_ = ureg.Quantity for value in ([1, 2, 3], (1, 2, 3)): self.assertRaises(TypeError, Q_, value) self.assertRaises(TypeError, Q_, value, "meter") def test_issue62(self): m = ureg("m**0.5") self.assertEqual(str(m.units), "meter ** 0.5") def test_issue66(self): self.assertEqual( ureg.get_dimensionality(UnitsContainer({"[temperature]": 1})), UnitsContainer({"[temperature]": 1}), ) self.assertEqual( ureg.get_dimensionality(ureg.kelvin), UnitsContainer({"[temperature]": 1}) ) self.assertEqual( ureg.get_dimensionality(ureg.degC), UnitsContainer({"[temperature]": 1}) ) def test_issue66b(self): self.assertEqual( ureg.get_base_units(ureg.kelvin), (1.0, ureg.Unit(UnitsContainer({"kelvin": 1}))), ) self.assertEqual( ureg.get_base_units(ureg.degC), (1.0, ureg.Unit(UnitsContainer({"kelvin": 1}))), ) def test_issue69(self): q = ureg("m").to(ureg("in")) self.assertEqual(q, ureg("m").to("in")) @helpers.requires_numpy() def test_issue74(self): v1 = np.asarray([1.0, 2.0, 3.0]) v2 = np.asarray([3.0, 2.0, 1.0]) q1 = v1 * ureg.ms q2 = v2 * ureg.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]) * ureg.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): v1 = np.asarray([1.0, 2.0, 3.0]) v2 = np.asarray([3.0, 2.0, 1.0]) q1 = v1 * ureg.ms q2 = v2 * ureg.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]) * ureg.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): acc = (5.0 * ureg("m/s/s")).plus_minus(0.25) tim = (37.0 * ureg("s")).plus_minus(0.16) dis = acc * tim ** 2 / 2 self.assertEqual(dis.value, acc.value * tim.value ** 2 / 2) def test_issue85(self): T = 4.0 * ureg.kelvin m = 1.0 * ureg.amu va = 2.0 * ureg.k * T / m va.to_base_units() boltmk = 1.380649e-23 * ureg.J / ureg.K vb = 2.0 * boltmk * T / m self.assertQuantityAlmostEqual(va.to_base_units(), vb.to_base_units()) def test_issue86(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = True def parts(q): return q.magnitude, q.units q1 = 10.0 * ureg.degC q2 = 10.0 * ureg.kelvin k1 = q1.to_base_units() q3 = 3.0 * ureg.meter q1m, q1u = parts(q1) q2m, q2u = parts(q2) q3m, q3u = parts(q3) k1m, k1u = parts(k1) self.assertEqual(parts(q2 * q3), (q2m * q3m, q2u * q3u)) self.assertEqual(parts(q2 / q3), (q2m / q3m, q2u / q3u)) self.assertEqual(parts(q3 * q2), (q3m * q2m, q3u * q2u)) self.assertEqual(parts(q3 / q2), (q3m / q2m, q3u / q2u)) self.assertEqual(parts(q2 ** 1), (q2m ** 1, q2u ** 1)) self.assertEqual(parts(q2 ** -1), (q2m ** -1, q2u ** -1)) self.assertEqual(parts(q2 ** 2), (q2m ** 2, q2u ** 2)) self.assertEqual(parts(q2 ** -2), (q2m ** -2, q2u ** -2)) self.assertEqual(parts(q1 * q3), (k1m * q3m, k1u * q3u)) self.assertEqual(parts(q1 / q3), (k1m / q3m, k1u / q3u)) self.assertEqual(parts(q3 * q1), (q3m * k1m, q3u * k1u)) self.assertEqual(parts(q3 / q1), (q3m / k1m, q3u / k1u)) self.assertEqual(parts(q1 ** -1), (k1m ** -1, k1u ** -1)) self.assertEqual(parts(q1 ** 2), (k1m ** 2, k1u ** 2)) self.assertEqual(parts(q1 ** -2), (k1m ** -2, k1u ** -2)) def test_issues86b(self): ureg = self.ureg T1 = 200.0 * ureg.degC T2 = T1.to(ureg.kelvin) m = 132.9054519 * ureg.amu v1 = 2 * ureg.k * T1 / m v2 = 2 * ureg.k * T2 / m self.assertQuantityAlmostEqual(v1, v2) self.assertQuantityAlmostEqual(v1, v2.to_base_units()) self.assertQuantityAlmostEqual(v1.to_base_units(), v2) self.assertQuantityAlmostEqual(v1.to_base_units(), v2.to_base_units()) @unittest.expectedFailure def test_issue86c(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = True T = ureg.degC T = 100.0 * T self.assertQuantityAlmostEqual(ureg.k * 2 * T, ureg.k * (2 * T)) def test_issue93(self): x = 5 * ureg.meter self.assertIsInstance(x.magnitude, int) y = 0.1 * ureg.meter self.assertIsInstance(y.magnitude, float) z = 5 * ureg.meter self.assertIsInstance(z.magnitude, int) z += y self.assertIsInstance(z.magnitude, float) self.assertQuantityAlmostEqual(x + y, 5.1 * ureg.meter) self.assertQuantityAlmostEqual(z, 5.1 * ureg.meter) @helpers.requires_numpy_previous_than("1.10") def test_issue94(self): v1 = np.array([5, 5]) * ureg.meter v2 = 0.1 * ureg.meter v3 = np.array([5, 5]) * ureg.meter v3 += v2 np.testing.assert_array_equal((v1 + v2).magnitude, np.array([5.1, 5.1])) np.testing.assert_array_equal(v3.magnitude, np.array([5, 5])) def test_issue104(self): x = [ureg("1 meter"), ureg("1 meter"), ureg("1 meter")] y = [ureg("1 meter")] * 3 def summer(values): if not values: return 0 total = values[0] for v in values[1:]: total += v return total self.assertQuantityAlmostEqual(summer(x), ureg.Quantity(3, "meter")) self.assertQuantityAlmostEqual(x[0], ureg.Quantity(1, "meter")) self.assertQuantityAlmostEqual(summer(y), ureg.Quantity(3, "meter")) self.assertQuantityAlmostEqual(y[0], ureg.Quantity(1, "meter")) def test_issue105(self): func = ureg.parse_unit_name val = list(func("meter")) self.assertEqual(list(func("METER")), []) self.assertEqual(val, list(func("METER", False))) for func in (ureg.get_name, ureg.parse_expression): val = func("meter") with self.assertRaises(AttributeError): func("METER") self.assertEqual(val, func("METER", False)) def test_issue121(self): z, v = 0, 2.0 self.assertEqual(z + v * ureg.meter, v * ureg.meter) self.assertEqual(z - v * ureg.meter, -v * ureg.meter) self.assertEqual(v * ureg.meter + z, v * ureg.meter) self.assertEqual(v * ureg.meter - z, v * ureg.meter) self.assertEqual(sum([v * ureg.meter, v * ureg.meter]), 2 * v * ureg.meter) @helpers.requires_numpy18() def test_issue121b(self): sh = (2, 1) z, v = 0, 2.0 self.assertEqual(z + v * ureg.meter, v * ureg.meter) self.assertEqual(z - v * ureg.meter, -v * ureg.meter) self.assertEqual(v * ureg.meter + z, v * ureg.meter) self.assertEqual(v * ureg.meter - z, v * ureg.meter) self.assertEqual(sum([v * ureg.meter, v * ureg.meter]), 2 * v * ureg.meter) z, v = np.zeros(sh), 2.0 * np.ones(sh) self.assertQuantityEqual(z + v * ureg.meter, v * ureg.meter) self.assertQuantityEqual(z - v * ureg.meter, -v * ureg.meter) self.assertQuantityEqual(v * ureg.meter + z, v * ureg.meter) self.assertQuantityEqual(v * ureg.meter - z, v * ureg.meter) z, v = np.zeros((3, 1)), 2.0 * np.ones(sh) for x, y in ((z, v), (z, v * ureg.meter), (v * ureg.meter, z)): with self.assertRaises(ValueError): x + y with self.assertRaises(ValueError): x - y @helpers.requires_numpy() def test_issue127(self): q = [1.0, 2.0, 3.0, 4.0] * self.ureg.meter q[0] = np.nan self.assertNotEqual(q[0], 1.0) self.assertTrue(math.isnan(q[0].magnitude)) q[1] = float("NaN") self.assertNotEqual(q[1], 2.0) self.assertTrue(math.isnan(q[1].magnitude)) def test_issue170(self): Q_ = UnitRegistry().Quantity q = Q_("1 kHz") / Q_("100 Hz") iq = int(q) self.assertEqual(iq, 10) self.assertIsInstance(iq, int) def test_angstrom_creation(self): ureg.Quantity(2, "Å") def test_alternative_angstrom_definition(self): ureg.Quantity(2, "\u212B") def test_micro_creation(self): ureg.Quantity(2, "µm") @helpers.requires_numpy() def test_issue171_real_imag(self): qr = [1.0, 2.0, 3.0, 4.0] * self.ureg.meter qi = [4.0, 3.0, 2.0, 1.0] * self.ureg.meter q = qr + 1j * qi self.assertQuantityEqual(q.real, qr) self.assertQuantityEqual(q.imag, qi) @helpers.requires_numpy() def test_issue171_T(self): a = np.asarray([[1.0, 2.0, 3.0, 4.0], [4.0, 3.0, 2.0, 1.0]]) q1 = a * self.ureg.meter q2 = a.T * self.ureg.meter self.assertQuantityEqual(q1.T, q2) @helpers.requires_numpy() def test_issue250(self): a = self.ureg.V b = self.ureg.mV self.assertEqual(np.float16(a / b), 1000.0) self.assertEqual(np.float32(a / b), 1000.0) self.assertEqual(np.float64(a / b), 1000.0) if "float128" in dir(np): self.assertEqual(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) self.assertQuantityEqual(q.to(ur.mF), u) def test_issue323(self): from fractions import Fraction as F self.assertEqual((self.Q_(F(2, 3), "s")).to("ms"), self.Q_(F(2000, 3), "ms")) self.assertEqual((self.Q_(F(2, 3), "m")).to("km"), self.Q_(F(1, 1500), "km")) def test_issue339(self): q1 = self.ureg("") self.assertEqual(q1.magnitude, 1) self.assertEqual(q1.units, self.ureg.dimensionless) q2 = self.ureg("1 dimensionless") self.assertEqual(q1, q2) def test_issue354_356_370(self): self.assertEqual( "{:~}".format(1 * self.ureg.second / self.ureg.millisecond), "1.0 s / ms" ) self.assertEqual("{:~}".format(1 * self.ureg.count), "1 count") self.assertEqual("{:~}".format(1 * self.ureg("MiB")), "1 MiB") def test_issue468(self): @ureg.wraps(("kg"), "meter") def f(x): return x x = ureg.Quantity(1.0, "meter") y = f(x) z = x * y self.assertEqual(z, ureg.Quantity(1.0, "meter * kilogram")) @helpers.requires_numpy() def test_issue482(self): q = self.ureg.Quantity(1, self.ureg.dimensionless) qe = np.exp(q) self.assertIsInstance(qe, self.ureg.Quantity) @helpers.requires_numpy() def test_issue483(self): ureg = self.ureg a = np.asarray([1, 2, 3]) q = [1, 2, 3] * ureg.dimensionless p = (q ** q).m np.testing.assert_array_equal(p, a ** a) def test_issue523(self): src, dst = UnitsContainer({"meter": 1}), UnitsContainer({"degF": 1}) value = 10.0 convert = self.ureg.convert self.assertRaises(DimensionalityError, convert, value, src, dst) self.assertRaises(DimensionalityError, convert, value, dst, src) def test_issue532(self): ureg = self.ureg @ureg.check(ureg("")) def f(x): return 2 * x self.assertEqual(f(ureg.Quantity(1, "")), 2) self.assertRaises(DimensionalityError, f, ureg.Quantity(1, "m")) def test_issue625a(self): Q_ = ureg.Quantity from math import sqrt @ureg.wraps(ureg.second, (ureg.meters, ureg.meters / ureg.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) self.assertAlmostEqual(t1, Q_(1.4285714285714286, "s")) moon_gravity = Q_(1.625, "m/s^2") t2 = calculate_time_to_fall(lunar_module_height, moon_gravity) self.assertAlmostEqual(t2, Q_(3.508232077228117, "s")) def test_issue625b(self): Q_ = ureg.Quantity @ureg.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")) self.assertAlmostEqual(d1, Q_(2, "m")) d2 = get_displacement(Q_(2, "s"), Q_(1, "deg/s")) self.assertAlmostEqual(d2, Q_(2, " deg")) 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 self.assertEqual(get_product(a=3 * u.m), 45 * u.m ** 3) self.assertEqual(get_product(b=2 * u.m), 20 * u.m ** 3) self.assertEqual(get_product(c=1 * u.dimensionless), 6 * u.m ** 2) def test_issue655a(self): distance = 1 * ureg.m time = 1 * ureg.s velocity = distance / time self.assertEqual(distance.check("[length]"), True) self.assertEqual(distance.check("[time]"), False) self.assertEqual(velocity.check("[length] / [time]"), True) self.assertEqual(velocity.check("1 / [time] * [length]"), True) def test_issue655b(self): Q_ = ureg.Quantity @ureg.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, ureg.m) # Assume earth gravity t = pendulum_period(length) self.assertAlmostEqual(t, Q_("2.0064092925890407 second")) # Use moon gravity moon_gravity = Q_(1.625, "m/s^2") t = pendulum_period(length, moon_gravity) self.assertAlmostEqual(t, Q_("4.928936075204336 second")) def test_issue783(self): assert not ureg("g") == [] def test_issue856(self): ph1 = ParserHelper(scale=123) ph2 = copy.deepcopy(ph1) assert ph2.scale == ph1.scale ureg1 = UnitRegistry() ureg2 = copy.deepcopy(ureg1) # Very basic functionality test assert ureg2("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): ureg = UnitRegistry(auto_reduce_dimensions=True) velocity = 1 * ureg.m / ureg.s cross_section = 1 * ureg.um ** 2 result = cross_section / velocity assert result == 1e-12 * ureg.m * ureg.s def test_issue912(self): """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 = ureg.get_compatible_units(ureg.meter) hertz_units = ureg.get_compatible_units(ureg.hertz) pprint.pformat(meter_units | hertz_units) def test_issue932(self): q = ureg.Quantity("1 kg") with self.assertRaises(DimensionalityError): q.to("joule") ureg.enable_contexts("energy", *(Context() for _ in range(20))) q.to("joule") ureg.disable_contexts() with self.assertRaises(DimensionalityError): q.to("joule") try: @pytest.mark.skipif(np is None, reason="NumPy is not available") @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", [ pytest.param(ureg.Quantity(1, "m"), id="python scalar int"), pytest.param(ureg.Quantity([1, 2, 3, 4], "m"), id="array int"), pytest.param(ureg.Quantity([1], "m")[0], id="numpy scalar int"), pytest.param(ureg.Quantity(1.0, "m"), id="python scalar float"), pytest.param(ureg.Quantity([1.0, 2.0, 3.0, 4.0], "m"), id="array float"), pytest.param(ureg.Quantity([1.0], "m")[0], id="numpy scalar float"), ], ) def test_issue925(callable, q): # Test for immutability of type type_before = type(q._magnitude) callable(q) assert isinstance(q._magnitude, type_before) @pytest.mark.skipif(np is None, reason="NumPy is not available") def test_issue973(): """Verify that an empty array Quantity can be created through multiplication.""" q0 = np.array([]) * ureg.m # by Unit q1 = np.array([]) * ureg("m") # by Quantity assert isinstance(q0, ureg.Quantity) assert isinstance(q1, ureg.Quantity) assert len(q0) == len(q1) == 0 except AttributeError: # Calling attributes on np will fail if NumPy is not available pass pint-0.10.1/pint/testsuite/test_matplotlib.py000066400000000000000000000022031360526634500213350ustar00rootroot00000000000000import 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") # Set up unit registry for matplotlib ureg = UnitRegistry() ureg.setup_matplotlib(True) # Set up matplotlib plt.switch_backend("agg") @pytest.mark.mpl_image_compare(tolerance=0, remove_text=True) def test_basic_plot(): 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") return fig @pytest.mark.mpl_image_compare(tolerance=0, remove_text=True) def test_plot_with_set_units(): 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") return fig pint-0.10.1/pint/testsuite/test_measurement.py000066400000000000000000000223231360526634500215200ustar00rootroot00000000000000from pint import DimensionalityError from pint.testsuite import QuantityTestCase, helpers @helpers.requires_not_uncertainties() class TestNotMeasurement(QuantityTestCase): FORCE_NDARRAY = False def test_instantiate(self): M_ = self.ureg.Measurement self.assertRaises(RuntimeError, M_, 4.0, 0.1, "s") @helpers.requires_uncertainties() class TestMeasurement(QuantityTestCase): FORCE_NDARRAY = False 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: self.assertEqual(m.value, v) self.assertEqual(m.error, u) self.assertEqual(m.rel, m.error / abs(m.value)) def test_format(self): 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}", r"\[(4.00 ± 0.10)\ second^2\]"), ("{:C}", "(4.00+/-0.10) second**2"), ("{:Lx}", r"\SI[separate-uncertainty=true]{4.00(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}", r"\[(4.0 ± 0.1)\ second^2\]"), ("{:.1fC}", "(4.0+/-0.1) second**2"), ("{:.1fLx}", r"\SI[separate-uncertainty=true]{4.0(1)}{\second\squared}"), ): with self.subTest(spec): self.assertEqual(spec.format(m), result) def test_format_paru(self): 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}", r"\[0.2000(100)\ second^2\]"), ("{:.3uSC}", "0.2000(100) second**2"), ): with self.subTest(spec): self.assertEqual(spec.format(m), result) def test_format_u(self): 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}", r"\[(0.2000 ± 0.0100)\ second^2\]"), ("{:.3uC}", "(0.2000+/-0.0100) second**2"), ( "{:.3uLx}", r"\SI[separate-uncertainty=true]{0.2000(100)}{\second\squared}", ), ("{:.1uLx}", r"\SI[separate-uncertainty=true]{0.20(1)}{\second\squared}"), ): with self.subTest(spec): self.assertEqual(spec.format(m), result) def test_format_percu(self): self.test_format_perce() 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}", r"\[(20 ± 1)%\ second^2\]"), ("{:.1u%C}", "(20+/-1)% second**2"), ): with self.subTest(spec): self.assertEqual(spec.format(m), result) def test_format_perce(self): 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}", r"\[(2.0 ± 0.1)×10^{-1}\ second^2\]"), ("{:.1ueC}", "(2.0+/-0.1)e-01 second**2"), ): with self.subTest(spec): self.assertEqual(spec.format(m), result) def test_format_exponential_pos(self): # 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}", r"\[(4.00 ± 0.10)×10^{20}\ second^2\]"), ("{:C}", "(4.00+/-0.10)e+20 second**2"), ("{:Lx}", r"\SI[separate-uncertainty=true]{4.00(10)e+20}{\second\squared}"), ): with self.subTest(spec): self.assertEqual(spec.format(m), result) def test_format_exponential_neg(self): 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}", r"\[(4.00 ± 0.10)×10^{-20}\ second^2\]"), ("{:C}", "(4.00+/-0.10)e-20 second**2"), ("{:Lx}", r"\SI[separate-uncertainty=true]{4.00(10)e-20}{\second\squared}"), ): with self.subTest(spec): self.assertEqual(spec.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 self.assertRaises(DimensionalityError): M_(v, o) with self.assertRaises(DimensionalityError): v.plus_minus(o) with self.assertRaises(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 self.assertAlmostEqual(r.value.magnitude, factor * m.value.magnitude) self.assertAlmostEqual(r.error.magnitude, abs(factor * m.error.magnitude)) self.assertEqual(r.value.units, m.value.units) for ml, mr in zip((m1, m1, m1, m3), (m1, m2, m3, m3)): r = ml + mr self.assertAlmostEqual( r.value.magnitude, ml.value.magnitude + mr.value.magnitude ) self.assertAlmostEqual( r.error.magnitude, ml.error.magnitude + mr.error.magnitude if ml is mr else (ml.error.magnitude ** 2 + mr.error.magnitude ** 2) ** 0.5, ) self.assertEqual(r.value.units, ml.value.units) for ml, mr in zip((m1, m1, m1, m3), (m1, m2, m3, m3)): r = ml - mr self.assertAlmostEqual( r.value.magnitude, ml.value.magnitude - mr.value.magnitude ) self.assertAlmostEqual( r.error.magnitude, 0 if ml is mr else (ml.error.magnitude ** 2 + mr.error.magnitude ** 2) ** 0.5, ) self.assertEqual(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 self.assertAlmostEqual( r.value.magnitude, ml.value.magnitude * mr.value.magnitude ) self.assertEqual(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 self.assertAlmostEqual( r.value.magnitude, ml.value.magnitude / mr.value.magnitude ) self.assertEqual(r.value.units, ml.value.units / mr.value.units) pint-0.10.1/pint/testsuite/test_numpy.py000066400000000000000000001276711360526634500203570ustar00rootroot00000000000000import copy import operator as op import unittest from unittest.mock import patch from pint import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning from pint.compat import np from pint.testsuite import QuantityTestCase, helpers from pint.testsuite.test_umath import TestUFuncs @helpers.requires_numpy() class TestNumpyMethods(QuantityTestCase): FORCE_NDARRAY = True @classmethod def setUpClass(cls): from pint import _DEFAULT_REGISTRY cls.ureg = _DEFAULT_REGISTRY cls.Q_ = cls.ureg.Quantity @property def q(self): return [[1, 2], [3, 4]] * self.ureg.m @property def q_nan(self): return [[1, 2], [3, 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) self.assertFalse(isinstance(actual, self.Q_)) self.assertFalse(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) self.assertEqual(ret.shape, (2, 2)) self.assertTrue(isinstance(ret, np.ndarray)) @helpers.requires_array_function_protocol() def test_full_like(self): self.assertQuantityEqual( 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): self.assertQuantityEqual(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]): self.assertEqual(q, v * self.ureg.m) def test_reshape(self): self.assertQuantityEqual(self.q.reshape([1, 4]), [[1, 2, 3, 4]] * self.ureg.m) def test_ravel(self): self.assertQuantityEqual(self.q.ravel(), [1, 2, 3, 4] * self.ureg.m) @helpers.requires_array_function_protocol() def test_ravel_numpy_func(self): self.assertQuantityEqual(np.ravel(self.q), [1, 2, 3, 4] * self.ureg.m) # Transpose-like operations @helpers.requires_array_function_protocol() def test_moveaxis(self): self.assertQuantityEqual( 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): self.assertQuantityEqual( np.rollaxis(self.q, 1), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) @helpers.requires_array_function_protocol() def test_swapaxes(self): self.assertQuantityEqual( np.swapaxes(self.q, 1, 0), np.array([[1, 2], [3, 4]]).T * self.ureg.m ) def test_transpose(self): self.assertQuantityEqual(self.q.transpose(), [[1, 3], [2, 4]] * self.ureg.m) @helpers.requires_array_function_protocol() def test_transpose_numpy_func(self): self.assertQuantityEqual(np.transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m) @helpers.requires_array_function_protocol() def test_flip_numpy_func(self): self.assertQuantityEqual( 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): self.assertQuantityEqual(ind_actual, ind_expected) self.assertQuantityEqual(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): self.assertQuantityEqual(ind_actual, ind_expected) self.assertQuantityEqual(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): self.assertQuantityEqual(ind_actual, ind_expected) self.assertQuantityEqual( np.atleast_3d(self.q), np.array([[[1], [2]], [[3], [4]]]) * self.ureg.m ) @helpers.requires_array_function_protocol() def test_broadcast_to(self): self.assertQuantityEqual( 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): self.assertQuantityEqual( np.expand_dims(self.q, 0), np.array([[[1, 2], [3, 4]]]) * self.ureg.m ) @helpers.requires_array_function_protocol() def test_squeeze(self): self.assertQuantityEqual(np.squeeze(self.q), self.q) self.assertQuantityEqual( 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_concatentate(self): self.assertQuantityEqual( np.concatenate([self.q] * 2), self.Q_(np.concatenate([self.q.m] * 2), self.ureg.m), ) @helpers.requires_array_function_protocol() def test_stack(self): self.assertQuantityEqual( np.stack([self.q] * 2), self.Q_(np.stack([self.q.m] * 2), self.ureg.m) ) @helpers.requires_array_function_protocol() def test_column_stack(self): self.assertQuantityEqual(np.column_stack([self.q[:, 0], self.q[:, 1]]), self.q) @helpers.requires_array_function_protocol() def test_dstack(self): self.assertQuantityEqual( np.dstack([self.q] * 2), self.Q_(np.dstack([self.q.m] * 2), self.ureg.m) ) @helpers.requires_array_function_protocol() def test_hstack(self): self.assertQuantityEqual( np.hstack([self.q] * 2), self.Q_(np.hstack([self.q.m] * 2), self.ureg.m) ) @helpers.requires_array_function_protocol() def test_vstack(self): self.assertQuantityEqual( np.vstack([self.q] * 2), self.Q_(np.vstack([self.q.m] * 2), self.ureg.m) ) @helpers.requires_array_function_protocol() def test_block(self): self.assertQuantityEqual( np.block([self.q[0, :], self.q[1, :]]), self.Q_([1, 2, 3, 4], self.ureg.m) ) @helpers.requires_array_function_protocol() def test_append(self): self.assertQuantityEqual( 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") self.assertQuantityEqual(actual, expected) self.assertEqual(actual.m.dtype, expected.m.dtype) def test_item(self): self.assertQuantityEqual(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): self.assertQuantityEqual( np.unwrap([0, 3 * np.pi] * self.ureg.radians), [0, np.pi] ) self.assertQuantityEqual( np.unwrap([0, 540] * self.ureg.deg), [0, 180] * self.ureg.deg ) # Rounding @helpers.requires_array_function_protocol() def test_fix(self): self.assertQuantityEqual(np.fix(3.14 * self.ureg.m), 3.0 * self.ureg.m) self.assertQuantityEqual(np.fix(3.0 * self.ureg.m), 3.0 * self.ureg.m) self.assertQuantityEqual( 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 def test_prod(self): self.assertEqual(self.q.prod(), 24 * self.ureg.m ** 4) def test_sum(self): self.assertEqual(self.q.sum(), 10 * self.ureg.m) self.assertQuantityEqual(self.q.sum(0), [4, 6] * self.ureg.m) self.assertQuantityEqual(self.q.sum(1), [3, 7] * self.ureg.m) @helpers.requires_array_function_protocol() def test_sum_numpy_func(self): self.assertQuantityEqual(np.sum(self.q, axis=0), [4, 6] * self.ureg.m) self.assertRaises(OffsetUnitCalculusError, np.sum, self.q_temperature) @helpers.requires_array_function_protocol() def test_nansum_numpy_func(self): self.assertQuantityEqual(np.nansum(self.q_nan, axis=0), [4, 2] * self.ureg.m) def test_cumprod(self): self.assertRaises(DimensionalityError, self.q.cumprod) self.assertQuantityEqual((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) @helpers.requires_array_function_protocol() def test_cumprod_numpy_func(self): self.assertRaises(DimensionalityError, np.cumprod, self.q) self.assertRaises(DimensionalityError, np.cumproduct, self.q) self.assertQuantityEqual(np.cumprod(self.q / self.ureg.m), [1, 2, 6, 24]) self.assertQuantityEqual(np.cumproduct(self.q / self.ureg.m), [1, 2, 6, 24]) self.assertQuantityEqual( np.cumprod(self.q / self.ureg.m, axis=1), [[1, 2], [3, 12]] ) @helpers.requires_array_function_protocol() def test_nancumprod_numpy_func(self): self.assertRaises(DimensionalityError, np.nancumprod, self.q_nan) self.assertQuantityEqual(np.nancumprod(self.q_nan / self.ureg.m), [1, 2, 6, 6]) @helpers.requires_array_function_protocol() def test_diff(self): self.assertQuantityEqual(np.diff(self.q, 1), [[1], [1]] * self.ureg.m) self.assertQuantityEqual( np.diff(self.q_temperature, 1), [[1], [1]] * self.ureg.delta_degC ) @helpers.requires_array_function_protocol() def test_ediff1d(self): self.assertQuantityEqual(np.ediff1d(self.q), [1, 1, 1] * self.ureg.m) self.assertQuantityEqual( 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) self.assertQuantityEqual( grad[0], [[2.0, 3.0], [2.0, 3.0]] * self.ureg.m / self.ureg.J ) self.assertQuantityEqual( 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) self.assertQuantityEqual( grad[0], [[2.0, 3.0], [2.0, 3.0]] * self.ureg.delta_degC / self.ureg.J ) self.assertQuantityEqual( 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 self.assertQuantityEqual( np.cross(a, b), [[-15, -2, 39]] * self.ureg.kPa * self.ureg.m ** 2 ) @helpers.requires_array_function_protocol() def test_trapz(self): self.assertQuantityEqual( 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): self.assertQuantityEqual( 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): self.assertQuantityEqual( 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 self.assertQuantityEqual(np.einsum("ii", a), 60 * self.ureg.m) self.assertQuantityEqual( np.einsum("ii->i", a), np.array([0, 6, 12, 18, 24]) * self.ureg.m ) self.assertQuantityEqual(np.einsum("i,i", b, b), 30 * self.ureg.m ** 2) self.assertQuantityEqual( 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): self.assertQuantityAlmostEqual( np.linalg.solve(self.q, [[3], [7]] * self.ureg.s), self.Q_([[1], [1]], "m / s"), ) # Arithmetic operations def test_addition_with_scalar(self): a = np.array([0, 1, 2]) b = 10.0 * self.ureg("gram/kilogram") self.assertQuantityAlmostEqual( a + b, self.Q_([0.01, 1.01, 2.01], self.ureg.dimensionless) ) self.assertQuantityAlmostEqual( 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 self.assertRaises(DimensionalityError, op.add, a, b) self.assertRaises(DimensionalityError, op.add, b, a) def test_power(self): arr = np.array(range(3), dtype=np.float) q = self.Q_(arr, "meter") for op_ in [op.pow, op.ipow, np.power]: q_cp = copy.copy(q) self.assertRaises(DimensionalityError, op_, 2.0, q_cp) arr_cp = copy.copy(arr) arr_cp = copy.copy(arr) q_cp = copy.copy(q) self.assertRaises(DimensionalityError, op_, q_cp, arr_cp) q_cp = copy.copy(q) q2_cp = copy.copy(q) self.assertRaises(DimensionalityError, op_, q_cp, q2_cp) self.assertQuantityEqual( np.power(self.q, self.Q_(2)), self.Q_([[1, 4], [9, 16]], "m**2") ) self.assertQuantityEqual( 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") self.assertQuantityEqual(np.sqrt(q), self.Q_(10, "m")) def test_cbrt(self): q = self.Q_(1000, "m**3") self.assertQuantityEqual(np.cbrt(q), self.Q_(10, "m")) @unittest.expectedFailure @helpers.requires_numpy() def test_exponentiation_array_exp_2(self): arr = np.array(range(3), dtype=np.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... self.assertRaises(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) self.assertRaises(DimensionalityError, op.ipow, arr_cp, q_cp) class TestNumpyUnclassified(TestNumpyMethods): def test_tolist(self): self.assertEqual( 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) self.assertQuantityEqual(tmp, [[6, 6], [6, 6]] * self.ureg.ft) tmp.fill(5 * self.ureg.m) self.assertQuantityEqual(tmp, [[5, 5], [5, 5]] * self.ureg.m) def test_take(self): self.assertQuantityEqual(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) self.assertQuantityEqual(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) self.assertQuantityEqual(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]) self.assertQuantityEqual( 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 self.assertRaises(DimensionalityError): q.put([0, 2], [4.0, 6.0] * self.ureg.J) with self.assertRaises(DimensionalityError): q.put([0, 2], [4.0, 6.0]) def test_repeat(self): self.assertQuantityEqual( 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() self.assertQuantityEqual(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 self.assertQuantityEqual(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 self.assertQuantityEqual(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 self.assertQuantityEqual(np.diagonal(q, offset=-1), [1, 2] * self.ureg.m) def test_compress(self): self.assertQuantityEqual( self.q.compress([False, True], axis=0), [[3, 4]] * self.ureg.m ) self.assertQuantityEqual( self.q.compress([False, True], axis=1), [[2], [4]] * self.ureg.m ) @helpers.requires_array_function_protocol() def test_compress_nep18(self): self.assertQuantityEqual( 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() self.assertRaises(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 self.assertTrue(np.any(q)) self.assertRaises(ValueError, np.any, self.q_temperature) @helpers.requires_array_function_protocol() def test_all_numpy_func(self): q = [0, 1] * self.ureg.m self.assertFalse(np.all(q)) self.assertRaises(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 self.assertEqual(np.count_nonzero(q), 4) def test_max(self): self.assertEqual(self.q.max(), 4 * self.ureg.m) def test_max_numpy_func(self): self.assertEqual(np.max(self.q), 4 * self.ureg.m) @helpers.requires_array_function_protocol() def test_max_with_axis_arg(self): self.assertQuantityEqual(np.max(self.q, axis=1), [2, 4] * self.ureg.m) @helpers.requires_array_function_protocol() def test_max_with_initial_arg(self): self.assertQuantityEqual( 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): self.assertEqual(np.nanmax(self.q_nan), 3 * self.ureg.m) def test_argmax(self): self.assertEqual(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): self.assertQuantityEqual( np.maximum(self.q, self.Q_([0, 5], "m")), self.Q_([[1, 5], [3, 5]], "m") ) def test_min(self): self.assertEqual(self.q.min(), 1 * self.ureg.m) @helpers.requires_array_function_protocol() def test_min_numpy_func(self): self.assertEqual(np.min(self.q), 1 * self.ureg.m) @helpers.requires_array_function_protocol() def test_min_with_axis_arg(self): self.assertQuantityEqual(np.min(self.q, axis=1), [1, 3] * self.ureg.m) @helpers.requires_array_function_protocol() def test_min_with_initial_arg(self): self.assertQuantityEqual( 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): self.assertEqual(np.nanmin(self.q_nan), 1 * self.ureg.m) def test_argmin(self): self.assertEqual(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): self.assertQuantityEqual( np.minimum(self.q, self.Q_([0, 5], "m")), self.Q_([[0, 2], [0, 4]], "m") ) def test_ptp(self): self.assertEqual(self.q.ptp(), 3 * self.ureg.m) @helpers.requires_array_function_protocol() def test_ptp_numpy_func(self): self.assertQuantityEqual(np.ptp(self.q, axis=0), [2, 2] * self.ureg.m) def test_clip(self): self.assertQuantityEqual( self.q.clip(max=2 * self.ureg.m), [[1, 2], [2, 2]] * self.ureg.m ) self.assertQuantityEqual( self.q.clip(min=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m ) self.assertQuantityEqual( self.q.clip(min=2 * self.ureg.m, max=3 * self.ureg.m), [[2, 2], [3, 3]] * self.ureg.m, ) self.assertRaises(DimensionalityError, self.q.clip, self.ureg.J) self.assertRaises(DimensionalityError, self.q.clip, 1) @helpers.requires_array_function_protocol() def test_clip_numpy_func(self): self.assertQuantityEqual( 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 self.assertQuantityEqual(q.round(0), [1, 1, 6, 22] * self.ureg.m) self.assertQuantityEqual(q.round(-1), [0, 0, 10, 20] * self.ureg.m) self.assertQuantityEqual(q.round(1), [1, 1.3, 5.7, 22] * self.ureg.m) @helpers.requires_array_function_protocol() def test_round_numpy_func(self): self.assertQuantityEqual( np.around(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) self.assertQuantityEqual( np.round_(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m ) def test_trace(self): self.assertEqual(self.q.trace(), (1 + 4) * self.ureg.m) def test_cumsum(self): self.assertQuantityEqual(self.q.cumsum(), [1, 3, 6, 10] * self.ureg.m) @helpers.requires_array_function_protocol() def test_cumsum_numpy_func(self): self.assertQuantityEqual( np.cumsum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m ) @helpers.requires_array_function_protocol() def test_nancumsum_numpy_func(self): self.assertQuantityEqual( np.nancumsum(self.q_nan, axis=0), [[1, 2], [4, 2]] * self.ureg.m ) def test_mean(self): self.assertEqual(self.q.mean(), 2.5 * self.ureg.m) @helpers.requires_array_function_protocol() def test_mean_numpy_func(self): self.assertEqual(np.mean(self.q), 2.5 * self.ureg.m) self.assertEqual(np.mean(self.q_temperature), self.Q_(2.5, self.ureg.degC)) @helpers.requires_array_function_protocol() def test_nanmean_numpy_func(self): self.assertEqual(np.nanmean(self.q_nan), 2 * self.ureg.m) @helpers.requires_array_function_protocol() def test_average_numpy_func(self): self.assertQuantityAlmostEqual( 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): self.assertEqual(np.median(self.q), 2.5 * self.ureg.m) @helpers.requires_array_function_protocol() def test_nanmedian_numpy_func(self): self.assertEqual(np.nanmedian(self.q_nan), 2 * self.ureg.m) def test_var(self): self.assertEqual(self.q.var(), 1.25 * self.ureg.m ** 2) @helpers.requires_array_function_protocol() def test_var_numpy_func(self): self.assertEqual(np.var(self.q), 1.25 * self.ureg.m ** 2) @helpers.requires_array_function_protocol() def test_nanvar_numpy_func(self): self.assertQuantityAlmostEqual( np.nanvar(self.q_nan), 0.66667 * self.ureg.m ** 2, rtol=1e-5 ) def test_std(self): self.assertQuantityAlmostEqual(self.q.std(), 1.11803 * self.ureg.m, rtol=1e-5) @helpers.requires_array_function_protocol() def test_std_numpy_func(self): self.assertQuantityAlmostEqual(np.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5) self.assertRaises(OffsetUnitCalculusError, np.std, self.q_temperature) def test_prod(self): self.assertEqual(self.q.prod(), 24 * self.ureg.m ** 4) def test_cumprod(self): self.assertRaises(DimensionalityError, self.q.cumprod) self.assertQuantityEqual((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) @helpers.requires_array_function_protocol() def test_nanstd_numpy_func(self): self.assertQuantityAlmostEqual( np.nanstd(self.q_nan), 0.81650 * self.ureg.m, rtol=1e-5 ) @helpers.requires_numpy_previous_than("1.10") def test_integer_div(self): a = [1] * self.ureg.m b = [2] * self.ureg.m c = a / b # Should be float division self.assertEqual(c.magnitude[0], 0.5) a /= b # Should be integer division self.assertEqual(a.magnitude[0], 0) def test_conj(self): self.assertQuantityEqual((self.q * (1 + 1j)).conj(), self.q * (1 - 1j)) self.assertQuantityEqual((self.q * (1 + 1j)).conjugate(), self.q * (1 - 1j)) def test_getitem(self): self.assertRaises(IndexError, self.q.__getitem__, (0, 10)) self.assertQuantityEqual(self.q[0], [1, 2] * self.ureg.m) self.assertEqual(self.q[1, 1], 4 * self.ureg.m) def test_setitem(self): with self.assertRaises(TypeError): self.q[0, 0] = 1 with self.assertRaises(DimensionalityError): self.q[0, 0] = 1 * self.ureg.J with self.assertRaises(DimensionalityError): self.q[0] = 1 with self.assertRaises(DimensionalityError): self.q[0] = np.ndarray([1, 2]) with self.assertRaises(DimensionalityError): self.q[0] = 1 * self.ureg.J q = self.q.copy() q[0] = 1 * self.ureg.m self.assertQuantityEqual(q, [[1, 1], [3, 4]] * self.ureg.m) q = self.q.copy() q[...] = 1 * self.ureg.m self.assertQuantityEqual(q, [[1, 1], [1, 1]] * self.ureg.m) q = self.q.copy() q[:] = 1 * self.ureg.m self.assertQuantityEqual(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 self.assertQuantityEqual(q, np.asarray([1, 1, 2, 3])) q[0] = self.ureg.m / self.ureg.mm self.assertQuantityEqual(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 self.assertQuantityEqual(q, [0.001, 1, 2, 3] * self.ureg.m / self.ureg.mm) def test_iterator(self): for q, v in zip(self.q.flatten(), [1, 2, 3, 4]): self.assertEqual(q, v * self.ureg.m) def test_iterable(self): self.assertTrue(np.iterable(self.q)) self.assertFalse(np.iterable(1 * self.ureg.m)) def test_reversible_op(self): """ """ x = self.q.magnitude u = self.Q_(np.ones(x.shape)) self.assertQuantityEqual(x / self.q, u * x / self.q) self.assertQuantityEqual(x * self.q, u * x * self.q) self.assertQuantityEqual(x + u, u + x) self.assertQuantityEqual(x - u, -(u - x)) def test_pickle(self): import pickle def pickle_test(q): pq = pickle.loads(pickle.dumps(q)) self.assertNDArrayEqual(q.magnitude, pq.magnitude) self.assertEqual(q.units, pq.units) pickle_test([10, 20] * self.ureg.m) def test_equal(self): x = self.q.magnitude u = self.Q_(np.ones(x.shape)) self.assertQuantityEqual(u, u) self.assertQuantityEqual(u == u, u.magnitude == u.magnitude) self.assertQuantityEqual(u == 1, u.magnitude == 1) def test_shape(self): u = self.Q_(np.arange(12)) u.shape = 4, 3 self.assertEqual(u.magnitude.shape, (4, 3)) @helpers.requires_array_function_protocol() def test_shape_numpy_func(self): self.assertEqual(np.shape(self.q), (2, 2)) @helpers.requires_array_function_protocol() def test_alen_numpy_func(self): self.assertEqual(np.alen(self.q), 2) @helpers.requires_array_function_protocol() def test_ndim_numpy_func(self): self.assertEqual(np.ndim(self.q), 2) @helpers.requires_array_function_protocol() def test_copy_numpy_func(self): q_copy = np.copy(self.q) self.assertQuantityEqual(self.q, q_copy) self.assertIsNot(self.q, 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 self.assertQuantityEqual(np.trim_zeros(q), [4, 3, 0, 2, 2] * self.ureg.m) @helpers.requires_array_function_protocol() def test_result_type_numpy_func(self): self.assertEqual(np.result_type(self.q), np.dtype("int64")) @helpers.requires_array_function_protocol() def test_nan_to_num_numpy_func(self): self.assertQuantityEqual( 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) self.assertQuantityEqual(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) self.assertQuantityEqual(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]]) ) @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) self.assertQuantityAlmostEqual( np.interp(x, xp, fp), self.Q_([6.66667, 20.0], self.ureg.degC), 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): self.assertQuantityEqual( np.where(self.q >= 2 * self.ureg.m, self.q, 20 * self.ureg.m), [[20, 2], [3, 4]] * self.ureg.m, ) self.assertQuantityEqual( np.where(self.q >= 2 * self.ureg.m, self.q, 0), [[0, 2], [3, 4]] * self.ureg.m, ) self.assertQuantityEqual( np.where(self.q >= 2 * self.ureg.m, self.q, np.nan), [[np.nan, 2], [3, 4]] * self.ureg.m, ) self.assertQuantityEqual( np.where(self.q >= 3 * self.ureg.m, 0, self.q), [[1, 2], [0, 0]] * self.ureg.m, ) self.assertQuantityEqual( np.where(self.q >= 3 * self.ureg.m, np.nan, self.q), [[1, 2], [np.nan, np.nan]] * self.ureg.m, ) self.assertQuantityEqual( np.where(self.q >= 2 * self.ureg.m, self.q, np.array(np.nan)), [[np.nan, 2], [3, 4]] * self.ureg.m, ) self.assertQuantityEqual( np.where(self.q >= 3 * self.ureg.m, np.array(np.nan), self.q), [[1, 2], [np.nan, np.nan]] * self.ureg.m, ) self.assertRaises( DimensionalityError, np.where, self.q < 2 * self.ureg.m, self.q, 0 * self.ureg.J, ) @helpers.requires_array_function_protocol() def test_fabs(self): self.assertQuantityEqual( 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]]), ) self.assertRaises(ValueError, np.isin, self.q.m, self.q) @helpers.requires_array_function_protocol() def test_percentile(self): self.assertQuantityEqual(np.percentile(self.q, 25), self.Q_(1.75, "m")) @helpers.requires_array_function_protocol() def test_nanpercentile(self): self.assertQuantityEqual(np.nanpercentile(self.q_nan, 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]) self.assertQuantityEqual(q, self.Q_([[2, 2], [6, 4]], "m")) np.copyto(q, 0, where=[[False, False], [True, False]]) self.assertQuantityEqual(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): self.assertQuantityEqual( np.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m ) @helpers.requires_array_function_protocol() def test_rot90(self): self.assertQuantityEqual( np.rot90(self.q), np.array([[2, 4], [1, 3]]) * self.ureg.m ) @helpers.requires_array_function_protocol() def test_insert(self): self.assertQuantityEqual( np.insert(self.q, 1, 0 * self.ureg.m, axis=1), np.array([[1, 0, 2], [3, 0, 4]]) * self.ureg.m, ) @patch("pint.quantity.ARRAY_FALLBACK", False) def test_ndarray_downcast(self): with self.assertWarns(UnitStrippedWarning): np.asarray(self.q) @patch("pint.quantity.ARRAY_FALLBACK", False) def test_ndarray_downcast_with_dtype(self): with self.assertWarns(UnitStrippedWarning): qarr = np.asarray(self.q, dtype=np.float64) self.assertEqual(qarr.dtype, np.float64) def test_array_protocol_fallback(self): with self.assertWarns(DeprecationWarning) as cm: for attr in ("__array_struct__", "__array_interface__"): getattr(self.q, attr) warning_text = str(cm.warnings[0].message) self.assertTrue( f"unit of the Quantity being stripped" in warning_text and "will become unavailable" in warning_text ) @patch("pint.quantity.ARRAY_FALLBACK", False) def test_array_protocol_unavailable(self): for attr in ("__array_struct__", "__array_interface__"): self.assertRaises(AttributeError, getattr, self.q, attr) @helpers.requires_array_function_protocol() def test_resize(self): self.assertQuantityEqual( 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 self.assertQuantityEqual( np.pad(a, (2, 3), "constant", constant_values=(4, 600 * self.ureg.cm)), [4, 4, 1, 2, 3, 4, 5, 6, 6, 6] * self.ureg.m, ) self.assertQuantityEqual( np.pad(a, (2, 3), "edge"), [1, 1, 1, 2, 3, 4, 5, 5, 5, 5] * self.ureg.m ) self.assertQuantityEqual( 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, ) self.assertQuantityEqual( np.pad(a, (2,), "maximum"), [5, 5, 1, 2, 3, 4, 5, 5, 5] * self.ureg.m ) self.assertQuantityEqual( np.pad(a, (2,), "mean"), [3, 3, 1, 2, 3, 4, 5, 3, 3] * self.ureg.m ) self.assertQuantityEqual( np.pad(a, (2,), "median"), [3, 3, 1, 2, 3, 4, 5, 3, 3] * self.ureg.m ) self.assertQuantityEqual( 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, ) self.assertQuantityEqual( np.pad(a, (2, 3), "reflect"), [3, 2, 1, 2, 3, 4, 5, 4, 3, 2] * self.ureg.m ) self.assertQuantityEqual( np.pad(a, (2, 3), "reflect", reflect_type="odd"), [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] * self.ureg.m, ) self.assertQuantityEqual( np.pad(a, (2, 3), "symmetric"), [2, 1, 1, 2, 3, 4, 5, 5, 4, 3] * self.ureg.m ) self.assertQuantityEqual( np.pad(a, (2, 3), "symmetric", reflect_type="odd"), [0, 1, 1, 2, 3, 4, 5, 5, 6, 7] * self.ureg.m, ) self.assertQuantityEqual( 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") self.assertQuantityEqual( 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", ), ) self.assertQuantityEqual( 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 @unittest.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.10.1/pint/testsuite/test_numpy_func.py000066400000000000000000000165061360526634500213640ustar00rootroot00000000000000from unittest.mock import patch 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.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 self.assertEqual(pint.numpy_func.HANDLED_FUNCTIONS["test"], test_function) # Test for ufuncs @implements("test", "ufunc") def test_ufunc(): pass self.assertEqual(pint.numpy_func.HANDLED_UFUNCS["test"], test_ufunc) # Test for invalid func type with self.assertRaises(ValueError): @implements("test", "invalid") def test_invalid(): pass def test_is_quantity(self): self.assertTrue(_is_quantity(self.Q_(0))) self.assertTrue(_is_quantity(np.arange(4) * self.ureg.m)) self.assertFalse(_is_quantity(1.0)) self.assertFalse(_is_quantity(np.array([1, 1, 2, 3, 5, 8]))) self.assertFalse(_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): self.assertTrue( _is_sequence_with_quantity_elements( (self.Q_(0, "m"), self.Q_(32.0, "degF")) ) ) self.assertTrue(_is_sequence_with_quantity_elements(np.arange(4) * self.ureg.m)) self.assertFalse(_is_sequence_with_quantity_elements((self.Q_(0), True))) self.assertFalse(_is_sequence_with_quantity_elements([1, 3, 5])) self.assertFalse(_is_sequence_with_quantity_elements(9 * self.ureg.m)) self.assertFalse(_is_sequence_with_quantity_elements(np.arange(4))) self.assertFalse(_is_sequence_with_quantity_elements("0123")) self.assertFalse(_is_sequence_with_quantity_elements([])) self.assertFalse(_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, ) self.assertEqual(args[0], 0.5) self.assertNDArrayEqual(args[1], np.array([[0, 1], [2, 3]])) self.assertNDArrayEqual(args[2], np.array([42])) self.assertEqual(args[3][0], 0) self.assertEqual(args[3][1], 10) self.assertEqual(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])) self.assertEqual(kwargs, {}) def test_convert_to_consistent_units_with_dimensionality_error(self): self.assertRaises( DimensionalityError, convert_to_consistent_units, self.Q_(32.0, "degF"), pre_calc_units=self.ureg.meter, ) self.assertRaises( 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, ) self.assertEqual(args[0][0], 0) self.assertEqual(args[0][1], 10) self.assertNDArrayEqual(args[1], np.array([1, 2, 3, 5, 7])) self.assertEqual(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])) self.assertEqual(c, 1) self.assertQuantityEqual(output_wrap_a(0), 0 * self.ureg.m) self.assertQuantityEqual(output_wrap_c(0), self.Q_(0, "g/kg")) def test_op_output_unit_sum(self): self.assertEqual(get_op_output_unit("sum", self.ureg.m), self.ureg.m) self.assertRaises( OffsetUnitCalculusError, get_op_output_unit, "sum", self.ureg.degC ) def test_op_output_unit_mul(self): self.assertEqual( 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): self.assertEqual(get_op_output_unit("delta", self.ureg.m), self.ureg.m) self.assertEqual( get_op_output_unit("delta", self.ureg.degC), self.ureg.delta_degC ) def test_op_output_unit_delta_div(self): self.assertEqual( get_op_output_unit( "delta,div", self.ureg.m, (self.Q_(1, "m"), self.Q_(1, "s")) ), self.ureg.m / self.ureg.s, ) self.assertEqual( 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): self.assertEqual( 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, ) self.assertEqual( get_op_output_unit("div", self.ureg.s, (1, self.Q_(1, "s"))), self.ureg.s ** -1, ) def test_op_output_unit_variance(self): self.assertEqual(get_op_output_unit("variance", self.ureg.m), self.ureg.m ** 2) self.assertRaises( OffsetUnitCalculusError, get_op_output_unit, "variance", self.ureg.degC ) def test_op_output_unit_square(self): self.assertEqual(get_op_output_unit("square", self.ureg.m), self.ureg.m ** 2) def test_op_output_unit_sqrt(self): self.assertEqual(get_op_output_unit("sqrt", self.ureg.m), self.ureg.m ** 0.5) def test_op_output_unit_reciprocal(self): self.assertEqual( get_op_output_unit("reciprocal", self.ureg.m), self.ureg.m ** -1 ) def test_op_output_unit_size(self): self.assertEqual( get_op_output_unit("size", self.ureg.m, size=3), self.ureg.m ** 3 ) self.assertRaises(ValueError, get_op_output_unit, "size", self.ureg.m) def test_numpy_wrap(self): self.assertRaises(ValueError, numpy_wrap, "invalid", np.ones, [], {}, []) # TODO (#905 follow-up): test that NotImplemented is returned when upcast types # present pint-0.10.1/pint/testsuite/test_pint_eval.py000066400000000000000000000056451360526634500211640ustar00rootroot00000000000000import unittest from pint.compat import tokenizer from pint.pint_eval import build_eval_tree class TestPintEval(unittest.TestCase): def _test_one(self, input_text, parsed): self.assertEqual(build_eval_tree(tokenizer(input_text)).to_string(), parsed) def test_build_eval_tree(self): self._test_one("3", "3") self._test_one("1 + 2", "(1 + 2)") # order of operations self._test_one("2 * 3 + 4", "((2 * 3) + 4)") # parentheses self._test_one("2 * (3 + 4)", "(2 * (3 + 4))") # more order of operations self._test_one("1 + 2 * 3 ** (4 + 3 / 5)", "(1 + (2 * (3 ** (4 + (3 / 5)))))") # nested parentheses at beginning self._test_one("1 * ((3 + 4) * 5)", "(1 * ((3 + 4) * 5))") # nested parentheses at end self._test_one("1 * (5 * (3 + 4))", "(1 * (5 * (3 + 4)))") # nested parentheses in middle self._test_one("1 * (5 * (3 + 4) / 6)", "(1 * ((5 * (3 + 4)) / 6))") # unary self._test_one("-1", "(- 1)") # unary self._test_one("3 * -1", "(3 * (- 1))") # double unary self._test_one("3 * --1", "(3 * (- (- 1)))") # parenthetical unary self._test_one("3 * -(2 + 4)", "(3 * (- (2 + 4)))") # parenthetical unary self._test_one("3 * -((2 + 4))", "(3 * (- (2 + 4)))") # implicit op self._test_one("3 4", "(3 4)") # implicit op, then parentheses self._test_one("3 (2 + 4)", "(3 (2 + 4))") # parentheses, then implicit self._test_one("(3 ** 4 ) 5", "((3 ** 4) 5)") # implicit op, then exponentiation self._test_one("3 4 ** 5", "(3 (4 ** 5))") # implicit op, then addition self._test_one("3 4 + 5", "((3 4) + 5)") # power followed by implicit self._test_one("3 ** 4 5", "((3 ** 4) 5)") # implicit with parentheses self._test_one("3 (4 ** 5)", "(3 (4 ** 5))") # exponent with e self._test_one("3e-1", "3e-1") # multiple units with exponents self._test_one("kg ** 1 * s ** 2", "((kg ** 1) * (s ** 2))") # multiple units with neg exponents self._test_one("kg ** -1 * s ** -2", "((kg ** (- 1)) * (s ** (- 2)))") # multiple units with neg exponents self._test_one("kg^-1 * s^-2", "((kg ^ (- 1)) * (s ^ (- 2)))") # multiple units with neg exponents, implicit op self._test_one("kg^-1 s^-2", "((kg ^ (- 1)) (s ^ (- 2)))") # nested power self._test_one("2 ^ 3 ^ 2", "(2 ^ (3 ^ 2))") # nested power self._test_one("gram * second / meter ** 2", "((gram * second) / (meter ** 2))") # nested power self._test_one("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 self._test_one("3 kg + 5", "((3 kg) + 5)") pint-0.10.1/pint/testsuite/test_pitheorem.py000066400000000000000000000022631360526634500211700ustar00rootroot00000000000000import itertools from pint import pi_theorem from pint.testsuite import QuantityTestCase class TestPiTheorem(QuantityTestCase): FORCE_NDARRAY = False def test_simple(self): # simple movement with self.capture_log() as buffer: self.assertEqual( pi_theorem({"V": "m/s", "T": "s", "L": "m"}), [{"V": 1, "T": 1, "L": -1}], ) # pendulum self.assertEqual( pi_theorem({"T": "s", "M": "grams", "L": "m", "g": "m/s**2"}), [{"g": 1, "T": 2, "L": -1}], ) self.assertEqual(len(buffer), 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) self.assertEqual( self.ureg.pi_theorem({"V": qv, "T": qt, "L": ql}), [{"V": 1.0, "T": 1.0, "L": -1.0}], ) pint-0.10.1/pint/testsuite/test_quantity.py000066400000000000000000002062221360526634500210530ustar00rootroot00000000000000import copy import datetime import math import operator as op import warnings from unittest.mock import patch from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry from pint.compat import BehaviorChangeWarning, np from pint.testsuite import QuantityTestCase, helpers from pint.testsuite.parameterized import ParameterizedTestCase from pint.unit import UnitsContainer class FakeWrapper: # Used in test_upcast_type_rejection_on_creation def __init__(self, q): self.q = q class TestQuantity(QuantityTestCase): FORCE_NDARRAY = False def test_quantity_creation(self): 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) self.assertEqual(x.magnitude, 4.2) self.assertEqual(x.units, UnitsContainer(meter=1)) x = self.Q_(4.2, UnitsContainer(length=1)) y = self.Q_(x) self.assertEqual(x.magnitude, y.magnitude) self.assertEqual(x.units, y.units) self.assertIsNot(x, y) x = self.Q_(4.2, None) self.assertEqual(x.magnitude, 4.2) self.assertEqual(x.units, UnitsContainer()) with self.capture_log() as buffer: self.assertEqual(4.2 * self.ureg.meter, self.Q_(4.2, 2 * self.ureg.meter)) self.assertEqual(len(buffer), 1) def test_quantity_bool(self): self.assertTrue(self.Q_(1, None)) self.assertTrue(self.Q_(1, "meter")) self.assertFalse(self.Q_(0, None)) self.assertFalse(self.Q_(0, "meter")) self.assertRaises(ValueError, bool, self.Q_(0, "degC")) self.assertFalse(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") # identity for single object self.assertTrue(x == x) self.assertFalse(x != x) # identity for multiple objects with same value self.assertTrue(x == y) self.assertFalse(x != y) self.assertTrue(x <= y) self.assertTrue(x >= y) self.assertFalse(x < y) self.assertFalse(x > y) self.assertFalse(x == z) self.assertTrue(x != z) self.assertTrue(x < z) self.assertTrue(z != j) self.assertNotEqual(z, j) self.assertEqual(self.Q_(0, "meter"), self.Q_(0, "centimeter")) self.assertNotEqual(self.Q_(0, "meter"), self.Q_(0, "second")) self.assertLess(self.Q_(10, "meter"), self.Q_(5, "kilometer")) def test_quantity_comparison_convert(self): self.assertEqual(self.Q_(1000, "millimeter"), self.Q_(1, "meter")) self.assertEqual( self.Q_(1000, "millimeter/min"), self.Q_(1000 / 60, "millimeter/s") ) def test_quantity_repr(self): x = self.Q_(4.2, UnitsContainer(meter=1)) self.assertEqual(str(x), "4.2 meter") self.assertEqual(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") self.assertEqual(hash(x), hash(x2)) # Dimensionless equality self.assertEqual(hash(y * z), hash(1.0)) # Dimensionless equality from a different unit registry ureg2 = UnitRegistry(force_ndarray=self.FORCE_NDARRAY) y2 = ureg2.Quantity(2, "second") z2 = ureg2.Quantity(0.5, "hertz") self.assertEqual(hash(y * z), hash(y2 * z2)) def test_quantity_format(self): 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}", r"\[4.12345678\ kilogram\ meter^2/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~}", r"\[4.12345678\ kg\ m^2/s\]"), ("{:C~}", "4.12345678 kg*m**2/s"), ("{:Lx}", r"\SI[]{4.12345678}{\kilo\gram\meter\squared\per\second}"), ): with self.subTest(spec): self.assertEqual(spec.format(x), result) # Check the special case that prevents e.g. '3 1 / second' x = self.Q_(3, UnitsContainer(second=-1)) self.assertEqual(f"{x}", "3 / second") @helpers.requires_numpy() def test_quantity_array_format(self): 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²"), ): with self.subTest(spec): self.assertEqual(spec.format(x), result) def test_format_compact(self): q1 = (200e-9 * self.ureg.s).to_compact() q1b = self.Q_(200.0, "nanosecond") self.assertAlmostEqual(q1.magnitude, q1b.magnitude) self.assertEqual(q1.units, q1b.units) q2 = (1e-2 * self.ureg("kg m/s^2")).to_compact("N") q2b = self.Q_(10.0, "millinewton") self.assertEqual(q2.magnitude, q2b.magnitude) self.assertEqual(q2.units, q2b.units) q3 = (-1000.0 * self.ureg("meters")).to_compact() q3b = self.Q_(-1.0, "kilometer") self.assertEqual(q3.magnitude, q3b.magnitude) self.assertEqual(q3.units, q3b.units) self.assertEqual(f"{q1:#.1f}", f"{q1b}") self.assertEqual(f"{q2:#.1f}", f"{q2b}") self.assertEqual(f"{q3:#.1f}", f"{q3b}") def test_default_formatting(self): 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", r"\[4.12345678\ kilogram\ meter^2/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~", r"\[4.12345678\ kg\ m^2/s\]"), ("C~", "4.12345678 kg*m**2/s"), ): with self.subTest(spec): ureg.default_format = spec self.assertEqual(f"{x}", result) def test_exponent_formatting(self): ureg = UnitRegistry() x = ureg.Quantity(1e20, "meter") self.assertEqual(f"{x:~H}", r"\[1×10^{20}\ m\]") self.assertEqual(f"{x:~L}", r"1\times 10^{20}\ \mathrm{m}") self.assertEqual(f"{x:~P}", r"1×10²⁰ m") x /= 1e40 self.assertEqual(f"{x:~H}", r"\[1×10^{-20}\ m\]") self.assertEqual(f"{x:~L}", r"1\times 10^{-20}\ \mathrm{m}") self.assertEqual(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)) self.assertEqual(x._repr_html_(), r"\[3.5\ kilogram\ meter^2/second\]") self.assertEqual( x._repr_latex_(), r"$3.5\ \frac{\mathrm{kilogram} \cdot " r"\mathrm{meter}^{2}}{\mathrm{second}}$", ) x._repr_pretty_(Pretty, False) self.assertEqual("".join(alltext), "3.5 kilogram·meter²/second") ureg.default_format = "~" self.assertEqual(x._repr_html_(), r"\[3.5\ kg\ m^2/s\]") self.assertEqual( x._repr_latex_(), r"$3.5\ \frac{\mathrm{kg} \cdot " r"\mathrm{m}^{2}}{\mathrm{s}}$", ) alltext = [] x._repr_pretty_(Pretty, False) self.assertEqual("".join(alltext), "3.5 kg·m²/s") def test_to_base_units(self): x = self.Q_("1*inch") self.assertQuantityAlmostEqual(x.to_base_units(), self.Q_(0.0254, "meter")) x = self.Q_("1*inch*inch") self.assertQuantityAlmostEqual( x.to_base_units(), self.Q_(0.0254 ** 2.0, "meter*meter") ) x = self.Q_("1*inch/minute") self.assertQuantityAlmostEqual( x.to_base_units(), self.Q_(0.0254 / 60.0, "meter/second") ) def test_convert(self): self.assertQuantityAlmostEqual( self.Q_("2 inch").to("meter"), self.Q_(2.0 * 0.0254, "meter") ) self.assertQuantityAlmostEqual( self.Q_("2 meter").to("inch"), self.Q_(2.0 / 0.0254, "inch") ) self.assertQuantityAlmostEqual( self.Q_("2 sidereal_year").to("second"), self.Q_(63116297.5325, "second") ) self.assertQuantityAlmostEqual( self.Q_("2.54 centimeter/second").to("inch/second"), self.Q_("1 inch/second"), ) self.assertAlmostEqual(self.Q_("2.54 centimeter").to("inch").magnitude, 1) self.assertAlmostEqual(self.Q_("2 second").to("millisecond").magnitude, 2000) @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) self.assertQuantityAlmostEqual(qac, r) self.assertIsNot(r, q) self.assertIsNot(r._magnitude, a) def test_convert_from(self): x = self.Q_("2*inch") meter = self.ureg.meter # from quantity self.assertQuantityAlmostEqual(meter.from_(x), self.Q_(2.0 * 0.0254, "meter")) self.assertQuantityAlmostEqual(meter.m_from(x), 2.0 * 0.0254) # from unit self.assertQuantityAlmostEqual( meter.from_(self.ureg.inch), self.Q_(0.0254, "meter") ) self.assertQuantityAlmostEqual(meter.m_from(self.ureg.inch), 0.0254) # from number self.assertQuantityAlmostEqual( meter.from_(2, strict=False), self.Q_(2.0, "meter") ) self.assertQuantityAlmostEqual(meter.m_from(2, strict=False), 2.0) # from number (strict mode) self.assertRaises(ValueError, meter.from_, 2) self.assertRaises(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") self.assertEqual(q.u, q.reshape(2, 3).u) self.assertEqual(q.u, q.swapaxes(0, 1).u) self.assertEqual(q.u, q.mean().u) self.assertEqual(q.u, np.compress((q == q[0, 0]).any(0), q).u) def test_context_attr(self): self.assertEqual(self.ureg.meter, self.Q_(1, "meter")) def test_both_symbol(self): self.assertEqual(self.Q_(2, "ms"), self.Q_(2, "millisecond")) self.assertEqual(self.Q_(2, "cm"), self.Q_(2, "centimeter")) def test_dimensionless_units(self): self.assertAlmostEqual( self.Q_(360, "degree").to("radian").magnitude, 2 * math.pi ) self.assertAlmostEqual(self.Q_(2 * math.pi, "radian"), self.Q_(360, "degree")) self.assertEqual(self.Q_(1, "radian").dimensionality, UnitsContainer()) self.assertTrue(self.Q_(1, "radian").dimensionless) self.assertFalse(self.Q_(1, "radian").unitless) self.assertEqual(self.Q_(1, "meter") / self.Q_(1, "meter"), 1) self.assertEqual((self.Q_(1, "meter") / self.Q_(1, "mm")).to(""), 1000) self.assertEqual(self.Q_(10) // self.Q_(360, "degree"), 1) self.assertEqual(self.Q_(400, "degree") // self.Q_(2 * math.pi), 1) self.assertEqual(self.Q_(400, "degree") // (2 * math.pi), 1) self.assertEqual(7 // self.Q_(360, "degree"), 1) def test_offset(self): self.assertQuantityAlmostEqual( self.Q_(0, "kelvin").to("kelvin"), self.Q_(0, "kelvin") ) self.assertQuantityAlmostEqual( self.Q_(0, "degC").to("kelvin"), self.Q_(273.15, "kelvin") ) self.assertQuantityAlmostEqual( self.Q_(0, "degF").to("kelvin"), self.Q_(255.372222, "kelvin"), rtol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(100, "kelvin").to("kelvin"), self.Q_(100, "kelvin") ) self.assertQuantityAlmostEqual( self.Q_(100, "degC").to("kelvin"), self.Q_(373.15, "kelvin") ) self.assertQuantityAlmostEqual( self.Q_(100, "degF").to("kelvin"), self.Q_(310.92777777, "kelvin"), rtol=0.01, ) self.assertQuantityAlmostEqual( self.Q_(0, "kelvin").to("degC"), self.Q_(-273.15, "degC") ) self.assertQuantityAlmostEqual( self.Q_(100, "kelvin").to("degC"), self.Q_(-173.15, "degC") ) self.assertQuantityAlmostEqual( self.Q_(0, "kelvin").to("degF"), self.Q_(-459.67, "degF"), rtol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(100, "kelvin").to("degF"), self.Q_(-279.67, "degF"), rtol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(32, "degF").to("degC"), self.Q_(0, "degC"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(100, "degC").to("degF"), self.Q_(212, "degF"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(54, "degF").to("degC"), self.Q_(12.2222, "degC"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(12, "degC").to("degF"), self.Q_(53.6, "degF"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(12, "kelvin").to("degC"), self.Q_(-261.15, "degC"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(12, "degC").to("kelvin"), self.Q_(285.15, "kelvin"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(12, "kelvin").to("degR"), self.Q_(21.6, "degR"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(12, "degR").to("kelvin"), self.Q_(6.66666667, "kelvin"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(12, "degC").to("degR"), self.Q_(513.27, "degR"), atol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(12, "degR").to("degC"), self.Q_(-266.483333, "degC"), atol=0.01 ) def test_offset_delta(self): self.assertQuantityAlmostEqual( self.Q_(0, "delta_degC").to("kelvin"), self.Q_(0, "kelvin") ) self.assertQuantityAlmostEqual( self.Q_(0, "delta_degF").to("kelvin"), self.Q_(0, "kelvin"), rtol=0.01 ) self.assertQuantityAlmostEqual( self.Q_(100, "kelvin").to("delta_degC"), self.Q_(100, "delta_degC") ) self.assertQuantityAlmostEqual( self.Q_(100, "kelvin").to("delta_degF"), self.Q_(180, "delta_degF"), rtol=0.01, ) self.assertQuantityAlmostEqual( self.Q_(100, "delta_degF").to("kelvin"), self.Q_(55.55555556, "kelvin"), rtol=0.01, ) self.assertQuantityAlmostEqual( self.Q_(100, "delta_degC").to("delta_degF"), self.Q_(180, "delta_degF"), rtol=0.01, ) self.assertQuantityAlmostEqual( self.Q_(100, "delta_degF").to("delta_degC"), self.Q_(55.55555556, "delta_degC"), rtol=0.01, ) self.assertQuantityAlmostEqual( self.Q_(12.3, "delta_degC").to("delta_degF"), self.Q_(22.14, "delta_degF"), rtol=0.01, ) def test_pickle(self): import pickle def pickle_test(q): self.assertEqual(q, pickle.loads(pickle.dumps(q))) pickle_test(self.Q_(32, "")) pickle_test(self.Q_(2.4, "")) pickle_test(self.Q_(32, "m/s")) pickle_test(self.Q_(2.4, "m/s")) @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) self.assertTrue(all(u_array == u_array_ref)) u_array_2 = self.Q_.from_sequence(u_seq_reversed) self.assertTrue(all(u_array_2 == u_array_ref_reversed)) self.assertFalse(u_array_2.u == u_array_ref_reversed.u) u_array_3 = self.Q_.from_sequence(u_seq_reversed, units="g") self.assertTrue(all(u_array_3 == u_array_ref_reversed)) self.assertTrue(u_array_3.u == u_array_ref_reversed.u) with self.assertRaises(ValueError): self.Q_.from_sequence([]) u_array_5 = self.Q_.from_list(u_seq) self.assertTrue(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") self.assertQuantityEqual(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 self.assertRaises(TypeError): iter(x) @helpers.requires_array_function_protocol() @patch("pint.quantity.SKIP_ARRAY_FUNCTION_CHANGE_WARNING", False) def test_array_function_warning_on_creation(self): # Test that warning is raised on first creation, but not second with self.assertWarns(BehaviorChangeWarning): self.Q_([]) with warnings.catch_warnings(): warnings.filterwarnings("error") self.Q_([]) @helpers.requires_not_numpy() def test_no_ndarray_coercion_without_numpy(self): self.assertRaises(ValueError, self.Q_(1, "m").__array__) @patch("pint.compat.upcast_types", [FakeWrapper]) def test_upcast_type_rejection_on_creation(self): self.assertRaises(TypeError, self.Q_, FakeWrapper(42), "m") self.assertEqual(FakeWrapper(self.Q_(42, "m")).q, self.Q_(42, "m")) class TestQuantityToCompact(QuantityTestCase): def assertQuantityAlmostIdentical(self, q1, q2): self.assertEqual(q1.units, q2.units) self.assertAlmostEqual(q1.magnitude, q2.magnitude) def compareQuantity_compact(self, q, expected_compact, unit=None): self.assertQuantityAlmostIdentical(q.to_compact(unit=unit), expected_compact) def test_dimensionally_simple_units(self): ureg = self.ureg self.compareQuantity_compact(1 * ureg.m, 1 * ureg.m) self.compareQuantity_compact(1e-9 * ureg.m, 1 * ureg.nm) def test_power_units(self): ureg = self.ureg self.compareQuantity_compact(900 * ureg.m ** 2, 900 * ureg.m ** 2) self.compareQuantity_compact(1e7 * ureg.m ** 2, 10 * ureg.km ** 2) def test_inverse_units(self): ureg = self.ureg self.compareQuantity_compact(1 / ureg.m, 1 / ureg.m) self.compareQuantity_compact(100e9 / ureg.m, 100 / ureg.nm) def test_inverse_square_units(self): ureg = self.ureg self.compareQuantity_compact(1 / ureg.m ** 2, 1 / ureg.m ** 2) self.compareQuantity_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.compareQuantity_compact(20e3 * ureg("hr^(-1) m"), 20 * ureg.km / ureg.hr) def test_fractional_exponent_units(self): ureg = self.ureg self.compareQuantity_compact(1 * ureg.m ** 0.5, 1 * ureg.m ** 0.5) self.compareQuantity_compact(1e-2 * ureg.m ** 0.5, 10 * ureg.um ** 0.5) def test_derived_units(self): ureg = self.ureg self.compareQuantity_compact(0.5 * ureg.megabyte, 500 * ureg.kilobyte) self.compareQuantity_compact(1e-11 * ureg.N, 10 * ureg.pN) def test_unit_parameter(self): ureg = self.ureg self.compareQuantity_compact( self.Q_(100e-9, "kg m / s^2"), 100 * ureg.nN, ureg.N ) self.compareQuantity_compact( self.Q_(101.3e3, "kg/m/s^2"), 101.3 * ureg.kPa, ureg.Pa ) def test_limits_magnitudes(self): ureg = self.ureg self.compareQuantity_compact(0 * ureg.m, 0 * ureg.m) self.compareQuantity_compact(float("inf") * ureg.m, float("inf") * ureg.m) def test_nonnumeric_magnitudes(self): ureg = self.ureg x = "some string" * ureg.m with self.assertWarns(RuntimeWarning): self.compareQuantity_compact(x, x) class TestQuantityBasicMath(QuantityTestCase): 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) self.assertQuantityAlmostEqual(value1, expected_result) self.assertEqual(id1, id(value1)) self.assertQuantityAlmostEqual(value2, value2_cpy) self.assertEqual(id2, id(value2)) def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None): if isinstance(value1, str): value1 = self.Q_(value1) if isinstance(value2, str): value2 = self.Q_(value2) if isinstance(expected_result, str): expected_result = self.Q_(expected_result) if unit is not None: value1 = value1 * unit value2 = value2 * unit expected_result = expected_result * unit id1 = id(value1) id2 = id(value2) value1_cpy = copy.copy(value1) value2_cpy = copy.copy(value2) result = operator(value1, value2) self.assertQuantityAlmostEqual(expected_result, result) self.assertQuantityAlmostEqual(value1, value1_cpy) self.assertQuantityAlmostEqual(value2, value2_cpy) self.assertNotEqual(id(result), id1) self.assertNotEqual(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)) self.assertRaises(DimensionalityError, op.add, 10, x) self.assertRaises(DimensionalityError, op.add, x, 10) self.assertRaises(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)) self.assertRaises(DimensionalityError, op.sub, 10, x) self.assertRaises(DimensionalityError, op.sub, x, 10) self.assertRaises(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)) self.assertRaises(DimensionalityError, op.iadd, 10, x) self.assertRaises(DimensionalityError, op.iadd, x, 10) self.assertRaises(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)) self.assertRaises(DimensionalityError, op.sub, 10, x) self.assertRaises(DimensionalityError, op.sub, x, 10) self.assertRaises(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") self.assertRaises(DimensionalityError, op.floordiv, a, b) self.assertRaises(DimensionalityError, op.floordiv, 3, b) self.assertRaises(DimensionalityError, op.floordiv, a, 3) self.assertRaises(DimensionalityError, op.ifloordiv, a, b) self.assertRaises(DimensionalityError, op.ifloordiv, 3, b) self.assertRaises(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") self.assertRaises(DimensionalityError, op.mod, a, b) self.assertRaises(DimensionalityError, op.mod, 3, b) self.assertRaises(DimensionalityError, op.mod, a, 3) self.assertRaises(DimensionalityError, op.imod, a, b) self.assertRaises(DimensionalityError, op.imod, 3, b) self.assertRaises(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) self.assertEqual(q, a // b) self.assertEqual(r, a % b) self.assertEqual(a, (q * b) + r) self.assertEqual(q, math.floor(q)) if b > (0 * b): self.assertTrue((0 * b) <= r < b) else: self.assertTrue((0 * b) >= r > b) if isinstance(a, self.Q_): self.assertEqual(r.units, a.units) else: self.assertTrue(r.unitless) self.assertTrue(q.unitless) copy_a = copy.copy(a) a %= b self.assertEqual(a, r) copy_a //= b self.assertEqual(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") self.assertRaises(DimensionalityError, divmod, a, b) self.assertRaises(DimensionalityError, divmod, 3, b) self.assertRaises(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) self.assertEqual(rx, zx, "while testing {0}".format(fun)) self.assertEqual(ry, zy, "while testing {0}".format(fun)) self.assertIsNot(rx, zx, "while testing {0}".format(fun)) self.assertIsNot(ry, 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): self.assertEqual(fun(x), fun(x.magnitude)) self.assertEqual(fun(y), fun(y.magnitude)) self.assertRaises(DimensionalityError, fun, z) class TestDimensions(QuantityTestCase): FORCE_NDARRAY = False def test_get_dimensionality(self): get = self.ureg.get_dimensionality self.assertEqual(get("[time]"), UnitsContainer({"[time]": 1})) self.assertEqual( get(UnitsContainer({"[time]": 1})), UnitsContainer({"[time]": 1}) ) self.assertEqual(get("seconds"), UnitsContainer({"[time]": 1})) self.assertEqual( get(UnitsContainer({"seconds": 1})), UnitsContainer({"[time]": 1}) ) self.assertEqual(get("[speed]"), UnitsContainer({"[length]": 1, "[time]": -1})) self.assertEqual( 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") self.assertEqual( x.dimensionality, UnitsContainer({"[length]": 1.0, "[time]": 1.0}) ) x = self.Q_(42, "meter*second*second") self.assertEqual( x.dimensionality, UnitsContainer({"[length]": 1.0, "[time]": 2.0}) ) x = self.Q_(42, "inch*second*second") self.assertEqual( x.dimensionality, UnitsContainer({"[length]": 1.0, "[time]": 2.0}) ) self.assertTrue(self.Q_(42, None).dimensionless) self.assertFalse(self.Q_(42, "meter").dimensionless) self.assertTrue((self.Q_(42, "meter") / self.Q_(1, "meter")).dimensionless) self.assertFalse((self.Q_(42, "meter") / self.Q_(1, "second")).dimensionless) self.assertTrue((self.Q_(42, "meter") / self.Q_(1, "inch")).dimensionless) def test_inclusion(self): dim = self.Q_(42, "meter").dimensionality self.assertTrue("[length]" in dim) self.assertFalse("[time]" in dim) dim = (self.Q_(42, "meter") / self.Q_(11, "second")).dimensionality self.assertTrue("[length]" in dim) self.assertTrue("[time]" in dim) dim = self.Q_(20.785, "J/(mol)").dimensionality for dimension in ("[length]", "[mass]", "[substance]", "[time]"): self.assertTrue(dimension in dim) self.assertFalse("[angle]" in dim) class TestQuantityWithDefaultRegistry(TestDimensions): @classmethod def setUpClass(cls): from pint import _DEFAULT_REGISTRY cls.ureg = _DEFAULT_REGISTRY cls.Q_ = cls.ureg.Quantity class TestDimensionsWithDefaultRegistry(TestDimensions): @classmethod def setUpClass(cls): from pint import _DEFAULT_REGISTRY cls.ureg = _DEFAULT_REGISTRY cls.Q_ = cls.ureg.Quantity class TestOffsetUnitMath(QuantityTestCase, ParameterizedTestCase): def setup(self): self.ureg.autoconvert_offset_to_baseunit = False self.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")), ] @ParameterizedTestCase.parameterize(("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.Q_(*qin1), self.Q_(*qin2) # update input tuple with new values to have correct values on failure input_tuple = q1, q2 if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.add, q1, q2) else: expected = self.Q_(*expected) self.assertEqual(op.add(q1, q2).units, expected.units) self.assertQuantityAlmostEqual(op.add(q1, q2), expected, atol=0.01) @helpers.requires_numpy() @ParameterizedTestCase.parameterize(("input", "expected_output"), 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=np.float), q1u), (np.array([q2v] * 2, dtype=np.float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.iadd, q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=np.float), expected[1] self.assertEqual(op.iadd(q1_cp, q2).units, Q_(*expected).units) q1_cp = copy.copy(q1) self.assertQuantityAlmostEqual(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")), ] @ParameterizedTestCase.parameterize(("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.Q_(*qin1), self.Q_(*qin2) input_tuple = q1, q2 if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.sub, q1, q2) else: expected = self.Q_(*expected) self.assertEqual(op.sub(q1, q2).units, expected.units) self.assertQuantityAlmostEqual(op.sub(q1, q2), expected, atol=0.01) # @unittest.expectedFailure @helpers.requires_numpy() @ParameterizedTestCase.parameterize(("input", "expected_output"), 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=np.float), q1u), (np.array([q2v] * 2, dtype=np.float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.isub, q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=np.float), expected[1] self.assertEqual(op.isub(q1_cp, q2).units, Q_(*expected).units) q1_cp = copy.copy(q1) self.assertQuantityAlmostEqual(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")), ] @ParameterizedTestCase.parameterize(("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.Q_(*qin1), self.Q_(*qin2) input_tuple = q1, q2 if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2) else: expected = self.Q_(*expected) self.assertEqual(op.mul(q1, q2).units, expected.units) self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01) @helpers.requires_numpy() @ParameterizedTestCase.parameterize(("input", "expected_output"), 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=np.float), q1u), (np.array([q2v] * 2, dtype=np.float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.imul, q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=np.float), expected[1] self.assertEqual(op.imul(q1_cp, q2).units, Q_(*expected).units) q1_cp = copy.copy(q1) self.assertQuantityAlmostEqual(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, "")), ] @ParameterizedTestCase.parameterize(("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.Q_(*qin1), self.Q_(*qin2) input_tuple = q1, q2 if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.truediv, q1, q2) else: expected = self.Q_(*expected) self.assertEqual(op.truediv(q1, q2).units, expected.units) self.assertQuantityAlmostEqual(op.truediv(q1, q2), expected, atol=0.01) @helpers.requires_numpy() @ParameterizedTestCase.parameterize(("input", "expected_output"), 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=np.float), q1u), (np.array([q2v] * 2, dtype=np.float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.itruediv, q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=np.float), expected[1] self.assertEqual(op.itruediv(q1_cp, q2).units, Q_(*expected).units) q1_cp = copy.copy(q1) self.assertQuantityAlmostEqual( 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")), ] @ParameterizedTestCase.parameterize( ("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.Q_(*qin1), self.Q_(*qin2) input_tuple = q1, q2 if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2) else: expected = self.Q_(*expected) self.assertEqual(op.mul(q1, q2).units, expected.units) self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01) @helpers.requires_numpy() @ParameterizedTestCase.parameterize( ("input", "expected_output"), 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=np.float), q1u), (np.array([q2v] * 2, dtype=np.float), q2u), ) Q_ = self.Q_ qin1, qin2 = input_tuple q1, q2 = Q_(*qin1), Q_(*qin2) q1_cp = copy.copy(q1) if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.imul, q1_cp, q2) else: expected = np.array([expected[0]] * 2, dtype=np.float), expected[1] self.assertEqual(op.imul(q1_cp, q2).units, Q_(*expected).units) q1_cp = copy.copy(q1) self.assertQuantityAlmostEqual(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"), ] @ParameterizedTestCase.parameterize( ("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.Q_(*in1), in2 else: in1, in2 = in1, self.Q_(*in2) input_tuple = in1, in2 # update input_tuple for better tracebacks if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.mul, in1, in2) else: expected = self.Q_(*expected) self.assertEqual(op.mul(in1, in2).units, expected.units) self.assertQuantityAlmostEqual(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"]), ] @ParameterizedTestCase.parameterize( ("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.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": self.assertRaises(OffsetUnitCalculusError, op.truediv, in1, in2) else: expected = self.Q_(*expected_copy[i]) self.assertEqual(op.truediv(in1, in2).units, expected.units) self.assertQuantityAlmostEqual(op.truediv(in1, in2), expected) exponentiation = [ # resuls 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")], ), ] @ParameterizedTestCase.parameterize(("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.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": self.assertRaises( (OffsetUnitCalculusError, DimensionalityError), op.pow, in1, in2 ) else: if type(expected_copy[i]) is tuple: expected = self.Q_(*expected_copy[i]) self.assertEqual(op.pow(in1, in2).units, expected.units) else: expected = expected_copy[i] self.assertQuantityAlmostEqual(op.pow(in1, in2), expected) @helpers.requires_numpy() @ParameterizedTestCase.parameterize(("input", "expected_output"), 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=np.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": self.assertRaises( (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=np.float), expected_copy[i][1], ) self.assertEqual(op.ipow(in1_cp, in2).units, expected.units) else: expected = np.array([expected_copy[i]] * 2, dtype=np.float) in1_cp = copy.copy(in1) self.assertQuantityAlmostEqual(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 self.assertQuantityEqual(A @ B, [[-2, -1], [-4, -3]] * self.ureg.m) self.assertQuantityEqual(A @ b, [[1], [3]] * self.ureg.m ** 2) self.assertQuantityEqual(B @ b, [[0], [-1]] * self.ureg.m) class TestDimensionReduction(QuantityTestCase): 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) self.assertEqual(mass.units, ureg.g) ureg = UnitRegistry(auto_reduce_dimensions=False) mass = self._calc_mass(ureg) self.assertEqual(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) self.assertEqual(mass.units, ureg.g) ureg = UnitRegistry(auto_reduce_dimensions=False, force_ndarray=True) mass = self._icalc_mass(ureg) self.assertEqual(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) self.assertEqual(x.units, UnitsContainer({})) ureg = UnitRegistry(auto_reduce_dimensions=False) x = (10 * ureg.feet) / (3 * ureg.inches) self.assertEqual(x.units, ureg.feet / ureg.inches) def test_nocoerce_creation(self): ureg = UnitRegistry(auto_reduce_dimensions=True) x = 1 * ureg.foot self.assertEqual(x.units, ureg.foot) 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 self.assertEqual(d + datetime.timedelta(seconds=3), after) after = 3 * self.ureg.second + d self.assertEqual(d + datetime.timedelta(seconds=3), after) after = d - 3 * self.ureg.second self.assertEqual(d - datetime.timedelta(seconds=3), after) with self.assertRaises(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 self.assertEqual(d + datetime.timedelta(seconds=3), after) after = 3 * self.ureg.second after += d self.assertEqual(d + datetime.timedelta(seconds=3), after) after = copy.copy(d) after -= 3 * self.ureg.second self.assertEqual(d - datetime.timedelta(seconds=3), after) after = 3 * self.ureg.second with self.assertRaises(DimensionalityError): after -= d class TestCompareZero(QuantityTestCase): """This test case checks the special treatment that the zero value receives in the comparisons: pint>=0.9 supports comparisons against zero even for non-dimensionless quantities Parameters ---------- Returns ------- """ def test_equal_zero(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = False self.assertTrue(ureg.Quantity(0, ureg.J) == 0) self.assertFalse(ureg.Quantity(0, ureg.J) == ureg.Quantity(0, "")) self.assertFalse(ureg.Quantity(5, ureg.J) == 0) @helpers.requires_numpy() def test_equal_zero_NP(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = False aeq = np.testing.assert_array_equal aeq(ureg.Quantity(0, ureg.J) == np.zeros(3), np.asarray([True, True, True])) aeq(ureg.Quantity(5, ureg.J) == np.zeros(3), np.asarray([False, False, False])) aeq( ureg.Quantity(np.arange(3), ureg.J) == np.zeros(3), np.asarray([True, False, False]), ) self.assertFalse(ureg.Quantity(np.arange(4), ureg.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") self.assertRaises(OffsetUnitCalculusError, q0.__eq__, 0) self.assertRaises(OffsetUnitCalculusError, q1.__eq__, 0) self.assertRaises(OffsetUnitCalculusError, q2.__eq__, 0) self.assertFalse(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") self.assertTrue(q0 == 0) self.assertFalse(q1 == 0) self.assertFalse(q2 == 0) self.assertFalse(q0 == ureg.Quantity(0, "")) def test_gt_zero(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = False q0 = ureg.Quantity(0, "J") q0m = ureg.Quantity(0, "m") q0less = ureg.Quantity(0, "") qpos = ureg.Quantity(5, "J") qneg = ureg.Quantity(-5, "J") self.assertTrue(qpos > q0) self.assertTrue(qpos > 0) self.assertFalse(qneg > 0) self.assertRaises(DimensionalityError, qpos.__gt__, q0less) self.assertRaises(DimensionalityError, qpos.__gt__, q0m) @helpers.requires_numpy() def test_gt_zero_NP(self): ureg = self.ureg ureg.autoconvert_offset_to_baseunit = False qpos = ureg.Quantity(5, "J") qneg = ureg.Quantity(-5, "J") aeq = np.testing.assert_array_equal aeq(qpos > np.zeros(3), np.asarray([True, True, True])) aeq(qneg > np.zeros(3), np.asarray([False, False, False])) aeq( ureg.Quantity(np.arange(-1, 2), ureg.J) > np.zeros(3), np.asarray([False, False, True]), ) aeq( ureg.Quantity(np.arange(-1, 2), ureg.J) > np.zeros(3), np.asarray([False, False, True]), ) self.assertRaises( ValueError, ureg.Quantity(np.arange(-1, 2), ureg.J).__gt__, 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") self.assertRaises(OffsetUnitCalculusError, q0.__gt__, 0) self.assertRaises(OffsetUnitCalculusError, q1.__gt__, 0) self.assertRaises(OffsetUnitCalculusError, q2.__gt__, 0) self.assertRaises(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") self.assertFalse(q0 > 0) self.assertTrue(q1 > 0) self.assertTrue(q2 > 0) self.assertRaises(DimensionalityError, q1.__gt__, ureg.Quantity(0, "")) pint-0.10.1/pint/testsuite/test_systems.py000066400000000000000000000235561360526634500207130ustar00rootroot00000000000000from pint import UnitRegistry from pint.testsuite import QuantityTestCase class TestGroup(QuantityTestCase): def _build_empty_reg_root(self): ureg = UnitRegistry(None) grp = ureg.get_group("root") grp.remove_units("pi") grp.invalidate_members() return ureg, ureg.get_group("root") def test_units_programatically(self): ureg, root = self._build_empty_reg_root() d = ureg._groups self.assertEqual(root._used_groups, set()) self.assertEqual(root._used_by, set()) root.add_units("meter", "second", "meter") self.assertEqual(root._unit_names, {"meter", "second"}) self.assertEqual(root.members, {"meter", "second"}) self.assertEqual(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") self.assertRaises(ValueError, g2.add_groups, "root") self.assertRaises(ValueError, g3.add_groups, "g2") self.assertRaises(ValueError, g3.add_groups, "root") def test_groups_programatically(self): ureg, root = self._build_empty_reg_root() d = ureg._groups g2 = ureg.Group("g2") self.assertEqual(d.keys(), {"root", "g2"}) self.assertEqual(root._used_groups, {"g2"}) self.assertEqual(root._used_by, set()) self.assertEqual(g2._used_groups, set()) self.assertEqual(g2._used_by, {"root"}) def test_simple(self): lines = ["@group mygroup", "meter", "second"] ureg, root = self._build_empty_reg_root() d = ureg._groups grp = ureg.Group.from_lines(lines, lambda x: None) self.assertEqual(d.keys(), {"root", "mygroup"}) self.assertEqual(grp.name, "mygroup") self.assertEqual(grp._unit_names, {"meter", "second"}) self.assertEqual(grp._used_groups, set()) self.assertEqual(grp._used_by, {root.name}) self.assertEqual(grp.members, frozenset(["meter", "second"])) def test_using1(self): lines = ["@group mygroup using group1", "meter", "second"] ureg, root = self._build_empty_reg_root() ureg.Group("group1") grp = ureg.Group.from_lines(lines, lambda x: None) self.assertEqual(grp.name, "mygroup") self.assertEqual(grp._unit_names, {"meter", "second"}) self.assertEqual(grp._used_groups, {"group1"}) self.assertEqual(grp.members, frozenset(["meter", "second"])) def test_using2(self): lines = ["@group mygroup using group1,group2", "meter", "second"] ureg, root = self._build_empty_reg_root() ureg.Group("group1") ureg.Group("group2") grp = ureg.Group.from_lines(lines, lambda x: None) self.assertEqual(grp.name, "mygroup") self.assertEqual(grp._unit_names, {"meter", "second"}) self.assertEqual(grp._used_groups, {"group1", "group2"}) self.assertEqual(grp.members, frozenset(["meter", "second"])) def test_spaces(self): lines = ["@group mygroup using group1 , group2", " meter ", " second "] ureg, root = self._build_empty_reg_root() ureg.Group("group1") ureg.Group("group2") grp = ureg.Group.from_lines(lines, lambda x: None) self.assertEqual(grp.name, "mygroup") self.assertEqual(grp._unit_names, {"meter", "second"}) self.assertEqual(grp._used_groups, {"group1", "group2"}) self.assertEqual(grp.members, frozenset(["meter", "second"])) def test_invalidate_members(self): lines = ["@group mygroup using group1", "meter", "second"] ureg, root = self._build_empty_reg_root() ureg.Group("group1") grp = ureg.Group.from_lines(lines, lambda x: None) self.assertIs(root._computed_members, None) self.assertIs(grp._computed_members, None) self.assertEqual(grp.members, frozenset(["meter", "second"])) self.assertIs(root._computed_members, None) self.assertIsNot(grp._computed_members, None) self.assertEqual(root.members, frozenset(["meter", "second"])) self.assertIsNot(root._computed_members, None) self.assertIsNot(grp._computed_members, None) grp.invalidate_members() self.assertIs(root._computed_members, None) self.assertIs(grp._computed_members, None) def test_with_defintions(self): lines = [ "@group imperial", "inch", "yard", "kings_leg = 2 * meter", "kings_head = 52 * inch" "pint", ] defs = [] def define(ud): defs.append(ud.name) ureg, root = self._build_empty_reg_root() ureg.Group.from_lines(lines, define) self.assertEqual(["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") self.assertEqual(root.members, frozenset(["meter", "second", "newton", "inch"])) self.assertEqual(g1.members, frozenset(["second", "inch"])) self.assertEqual(g2.members, frozenset(["second", "newton"])) self.assertEqual(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") self.assertEqual(c, frozenset([ureg.inch, ureg.yard])) class TestSystem(QuantityTestCase): def _build_empty_reg_root(self): ureg = UnitRegistry(None) grp = ureg.get_group("root") grp.remove_units("pi") 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) self.assertEqual(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") self.assertEqual(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) self.assertEqual(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) self.assertAlmostEqual(c[0], 1) self.assertEqual(c[1], {"inch": 1}) c = ureg.get_base_units("cm", system=sysname) self.assertAlmostEqual(c[0], 1.0 / 2.54) self.assertEqual(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) self.assertAlmostEqual(c[0], 0.326, places=3) self.assertEqual(c[1], {"pint": 1.0 / 3}) c = ureg.get_base_units("cm", system=sysname) self.assertAlmostEqual(c[0], 0.1283, places=3) self.assertEqual(c[1], {"pint": 1.0 / 3}) c = ureg.get_base_units("inch**2", system=sysname) self.assertAlmostEqual(c[0], 0.326 ** 2, places=3) self.assertEqual(c[1], {"pint": 2.0 / 3}) c = ureg.get_base_units("cm**2", system=sysname) self.assertAlmostEqual(c[0], 0.1283 ** 2, places=3) self.assertEqual(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) self.assertAlmostEqual(c[0], 0.056, places=2) self.assertEqual(c[1], {"mph": 1, "second": 1}) c = ureg.get_base_units("kph", system=sysname) self.assertAlmostEqual(c[0], 0.6213, places=3) self.assertEqual(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.10.1/pint/testsuite/test_umath.py000066400000000000000000000627121360526634500203170ustar00rootroot00000000000000from pint import DimensionalityError from pint.compat import np from pint.testsuite import QuantityTestCase, helpers # Following http://docs.scipy.org/doc/numpy/reference/ufuncs.html if np: pi = np.pi @helpers.requires_numpy() class TestUFuncs(QuantityTestCase): FORCE_NDARRAY = True @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) self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg) for x1 in raise_with: with self.assertRaises( DimensionalityError, msg=f"At {func.__name__} with {x1}" ): 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) self.assertQuantityAlmostEqual(qm, re, rtol=rtol, msg=err_msg) for x1 in raise_with: with self.assertRaises(ValueError, msg=f"At {func.__name__} with {x1}"): 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) self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg) for x2 in raise_with: with self.assertRaises( DimensionalityError, msg=f"At {func.__name__} with {x1} and {x2}" ): 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): self.assertTrue( np.hypot(3.0 * self.ureg.m, 4.0 * self.ureg.m) == 5.0 * self.ureg.m ) self.assertTrue( np.hypot(3.0 * self.ureg.m, 400.0 * self.ureg.cm) == 5.0 * self.ureg.m ) with self.assertRaises(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.10.1/pint/testsuite/test_unit.py000066400000000000000000000740541360526634500201620ustar00rootroot00000000000000import copy import functools import math import re from pint import ( DefinitionSyntaxError, DimensionalityError, RedefinitionError, UndefinedUnitError, ) from pint.compat import np from pint.registry import LazyRegistry, UnitRegistry from pint.testsuite import QuantityTestCase, helpers from pint.testsuite.parameterized import ParameterizedTestCase from pint.util import ParserHelper, UnitsContainer class TestUnit(QuantityTestCase): def test_creation(self): for arg in ("meter", UnitsContainer(meter=1), self.U_("m")): self.assertEqual(self.U_(arg)._units, UnitsContainer(meter=1)) self.assertRaises(TypeError, self.U_, 1) def test_deepcopy(self): x = self.U_(UnitsContainer(meter=1)) self.assertEqual(x, copy.deepcopy(x)) def test_unit_repr(self): x = self.U_(UnitsContainer(meter=1)) self.assertEqual(str(x), "meter") self.assertEqual(repr(x), "") def test_unit_formatting(self): 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}", r"\[kilogram\ meter^2/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~}", r"\[kg\ m^2/s\]"), ("{:C~}", "kg*m**2/s"), ): with self.subTest(spec): self.assertEqual(spec.format(x), result) def test_unit_default_formatting(self): 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", r"\[kilogram\ meter^2/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~", r"\[kg\ m^2/s\]"), ("C~", "kg*m**2/s"), ): with self.subTest(spec): ureg.default_format = spec self.assertEqual(f"{x}", result, f"Failed for {spec}, {result}") def test_unit_formatting_snake_case(self): # 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", r"\[oil\_barrel\]"), ("C", "oil_barrel"), ("~", "oil_bbl"), ("L~", r"\mathrm{oil\_bbl}"), ("P~", "oil_bbl"), ("H~", r"\[oil\_bbl\]"), ("C~", "oil_bbl"), ): with self.subTest(spec): ureg.default_format = spec self.assertEqual(f"{x}", result, f"Failed for {spec}, {result}") 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)) self.assertEqual(x._repr_html_(), r"\[kilogram\ meter^2/second\]") self.assertEqual( x._repr_latex_(), r"$\frac{\mathrm{kilogram} \cdot " r"\mathrm{meter}^{2}}{\mathrm{second}}$", ) x._repr_pretty_(Pretty, False) self.assertEqual("".join(alltext), "kilogram·meter²/second") ureg.default_format = "~" self.assertEqual(x._repr_html_(), r"\[kg\ m^2/s\]") self.assertEqual( x._repr_latex_(), r"$\frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}$" ) alltext = [] x._repr_pretty_(Pretty, False) self.assertEqual("".join(alltext), "kg·m²/s") def test_unit_mul(self): x = self.U_("m") self.assertEqual(x * 1, self.Q_(1, "m")) self.assertEqual(x * 0.5, self.Q_(0.5, "m")) self.assertEqual(x * self.Q_(1, "m"), self.Q_(1, "m**2")) self.assertEqual(1 * x, self.Q_(1, "m")) def test_unit_div(self): x = self.U_("m") self.assertEqual(x / 1, self.Q_(1, "m")) self.assertEqual(x / 0.5, self.Q_(2.0, "m")) self.assertEqual(x / self.Q_(1, "m"), self.Q_(1)) def test_unit_rdiv(self): x = self.U_("m") self.assertEqual(1 / x, self.Q_(1, "1/m")) def test_unit_pow(self): x = self.U_("m") self.assertEqual(x ** 2, self.U_("m**2")) def test_unit_hash(self): x = self.U_("m") self.assertEqual(hash(x), hash(x._units)) def test_unit_eqs(self): x = self.U_("m") self.assertEqual(x, self.U_("m")) self.assertNotEqual(x, self.U_("cm")) self.assertEqual(x, self.Q_(1, "m")) self.assertNotEqual(x, self.Q_(2, "m")) self.assertEqual(x, UnitsContainer({"meter": 1})) y = self.U_("cm/m") self.assertEqual(y, 0.01) self.assertEqual(self.U_("byte") == self.U_("byte"), True) self.assertEqual(self.U_("byte") != self.U_("byte"), False) def test_unit_cmp(self): x = self.U_("m") self.assertLess(x, self.U_("km")) self.assertGreater(x, self.U_("mm")) y = self.U_("m/mm") self.assertGreater(y, 1) self.assertLess(y, 1e6) def test_dimensionality(self): x = self.U_("m") self.assertEqual(x.dimensionality, UnitsContainer({"[length]": 1})) def test_dimensionless(self): self.assertTrue(self.U_("m/mm").dimensionless) self.assertFalse(self.U_("m").dimensionless) def test_unit_casting(self): self.assertEqual(int(self.U_("m/mm")), 1000) self.assertEqual(float(self.U_("mm/m")), 1e-3) self.assertEqual(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) self.assertQuantityEqual(arr * x, self.Q_(arr, "m")) self.assertQuantityEqual(arr / x, self.Q_(arr, "1/m")) self.assertQuantityEqual(x / arr, self.Q_(arr, "m")) class TestRegistry(QuantityTestCase): FORCE_NDARRAY = False def setup(self): self.ureg.autoconvert_offset_to_baseunit = False def test_base(self): ureg = UnitRegistry(None) ureg.define("meter = [length]") self.assertRaises(DefinitionSyntaxError, ureg.define, "meter = [length]") self.assertRaises(TypeError, ureg.define, list()) ureg.define("degC = kelvin; offset: 273.15") def test_define(self): ureg = UnitRegistry(None) self.assertIsInstance(dir(ureg), list) self.assertGreater(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) self.assertEqual(dir(ureg1), dir(ureg2)) self.assertRaises( 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}" self.assertEqual(s2, s3) self.assertNotEqual(s1, s3) self.assertEqual(ureg.default_format, "~") def test_parse_number(self): self.assertEqual(self.ureg.parse_expression("pi"), math.pi) self.assertEqual(self.ureg.parse_expression("x", x=2), 2) self.assertEqual(self.ureg.parse_expression("x", x=2.3), 2.3) self.assertEqual(self.ureg.parse_expression("x * y", x=2.3, y=3), 2.3 * 3) self.assertEqual(self.ureg.parse_expression("x", x=(1 + 1j)), (1 + 1j)) def test_parse_single(self): self.assertEqual( self.ureg.parse_expression("meter"), self.Q_(1, UnitsContainer(meter=1.0)) ) self.assertEqual( self.ureg.parse_expression("second"), self.Q_(1, UnitsContainer(second=1.0)) ) def test_parse_alias(self): self.assertEqual( self.ureg.parse_expression("metre"), self.Q_(1, UnitsContainer(meter=1.0)) ) def test_parse_plural(self): self.assertEqual( self.ureg.parse_expression("meters"), self.Q_(1, UnitsContainer(meter=1.0)) ) def test_parse_prefix(self): self.assertEqual( self.ureg.parse_expression("kilometer"), self.Q_(1, UnitsContainer(kilometer=1.0)), ) def test_parse_complex(self): self.assertEqual( self.ureg.parse_expression("kilometre"), self.Q_(1, UnitsContainer(kilometer=1.0)), ) self.assertEqual( self.ureg.parse_expression("kilometres"), self.Q_(1, UnitsContainer(kilometer=1.0)), ) def test_parse_mul_div(self): self.assertEqual( self.ureg.parse_expression("meter*meter"), self.Q_(1, UnitsContainer(meter=2.0)), ) self.assertEqual( self.ureg.parse_expression("meter**2"), self.Q_(1, UnitsContainer(meter=2.0)), ) self.assertEqual( self.ureg.parse_expression("meter*second"), self.Q_(1, UnitsContainer(meter=1.0, second=1)), ) self.assertEqual( self.ureg.parse_expression("meter/second"), self.Q_(1, UnitsContainer(meter=1.0, second=-1)), ) self.assertEqual( self.ureg.parse_expression("meter/second**2"), self.Q_(1, UnitsContainer(meter=1.0, second=-2)), ) def test_parse_pretty(self): self.assertEqual( self.ureg.parse_expression("meter/second²"), self.Q_(1, UnitsContainer(meter=1.0, second=-2)), ) self.assertEqual( self.ureg.parse_expression("m³/s³"), self.Q_(1, UnitsContainer(meter=3.0, second=-3)), ) self.assertEqual( self.ureg.parse_expression("meter² · second"), self.Q_(1, UnitsContainer(meter=2.0, second=1)), ) self.assertEqual( self.ureg.parse_expression("meter⁰.⁵·second"), self.Q_(1, UnitsContainer(meter=0.5, second=1)), ) self.assertEqual( self.ureg.parse_expression("meter³⁷/second⁴.³²¹"), self.Q_(1, UnitsContainer(meter=37, second=-4.321)), ) def test_parse_factor(self): self.assertEqual( self.ureg.parse_expression("42*meter"), self.Q_(42, UnitsContainer(meter=1.0)), ) self.assertEqual( 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)") self.assertEqual(self.Q_(q.magnitude, str(q.units)), q) def test_as_delta(self): parse = self.ureg.parse_units self.assertEqual(parse("kelvin", as_delta=True), UnitsContainer(kelvin=1)) self.assertEqual(parse("kelvin", as_delta=False), UnitsContainer(kelvin=1)) self.assertEqual( parse("kelvin**(-1)", as_delta=True), UnitsContainer(kelvin=-1) ) self.assertEqual( parse("kelvin**(-1)", as_delta=False), UnitsContainer(kelvin=-1) ) self.assertEqual(parse("kelvin**2", as_delta=True), UnitsContainer(kelvin=2)) self.assertEqual(parse("kelvin**2", as_delta=False), UnitsContainer(kelvin=2)) self.assertEqual( parse("kelvin*meter", as_delta=True), UnitsContainer(kelvin=1, meter=1) ) self.assertEqual( parse("kelvin*meter", as_delta=False), UnitsContainer(kelvin=1, meter=1) ) 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) self.assertEqual(str(x), "meter * second ** 2") self.assertEqual(repr(x), "") x = UnitsContainer(meter=1, second=2.5) self.assertEqual(str(x), "meter * second ** 2.5") self.assertEqual(repr(x), "") def test_unitcontainer_bool(self): self.assertTrue(UnitsContainer(meter=1, second=2)) self.assertFalse(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) self.assertTrue(x == y) self.assertFalse(x != y) self.assertFalse(x == z) self.assertTrue(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) self.assertEqual(x, "meter") self.assertEqual("meter", x) self.assertNotEqual(x, "meter ** 2") self.assertNotEqual(x, "meter * meter") self.assertNotEqual(x, "second") self.assertEqual(y, "second") self.assertEqual(z, "meter/second/second") def test_invalid(self): self.assertRaises(TypeError, UnitsContainer, {1: 2}) self.assertRaises(TypeError, UnitsContainer, {"1": "2"}) d = UnitsContainer() self.assertRaises(TypeError, d.__mul__, list()) self.assertRaises(TypeError, d.__pow__, list()) self.assertRaises(TypeError, d.__truediv__, list()) self.assertRaises(TypeError, d.__rtruediv__, list()) class TestToUnitsContainer(BaseTestCase): def test_str_conversion(self): self.assertEqual(to_units_container("m"), UnitsContainer(m=1)) def test_uc_conversion(self): a = UnitsContainer(m=1) self.assertIs(to_units_container(a), a) def test_quantity_conversion(self): from pint.registry import UnitRegistry ureg = UnitRegistry() self.assertEqual( to_units_container(ureg.Quantity(1, UnitsContainer(m=1))), UnitsContainer(m=1), ) def test_unit_conversion(self): from pint import Unit self.assertEqual( to_units_container(Unit(UnitsContainer(m=1))), UnitsContainer(m=1) ) def test_dict_conversion(self): self.assertEqual(to_units_container(dict(m=1)), UnitsContainer(m=1)) class TestParseHelper(BaseTestCase): 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) self.assertEqual(x(), xp()) self.assertNotEqual(x(), y()) self.assertEqual(ParserHelper.from_string(""), ParserHelper()) self.assertEqual(repr(x()), "") self.assertEqual(ParserHelper(2), 2) self.assertEqual(x(), dict(meter=2)) self.assertEqual(x(), "meter ** 2") self.assertNotEqual(y(), dict(meter=2)) self.assertNotEqual(y(), "meter ** 2") self.assertNotEqual(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) self.assertEqual(x() * 4.0, ParserHelper(4.0, meter=2)) self.assertEqual(x() * y(), ParserHelper(2.0)) self.assertEqual(x() * "second", ParserHelper(1.0, meter=2, second=1)) self.assertEqual(x() / 4.0, ParserHelper(0.25, meter=2)) self.assertEqual(x() / "second", ParserHelper(1.0, meter=2, second=-1)) self.assertEqual(x() / z(), ParserHelper(0.5)) self.assertEqual(4.0 / z(), ParserHelper(2.0, meter=-2)) self.assertEqual("seconds" / z(), ParserHelper(0.5, seconds=1, meter=-2)) self.assertEqual(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) self.assertEqual(expected, actual) self.assertEqual(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(Decimal(1000), "1e3", use_decimal=True) self._test_eval_token(1000, "1000") # integer numbers are represented as ints, not Decimals self._test_eval_token(1000, "1000", use_decimal=True) def test_nan(self): for s in ("nan", "NAN", "NaN", "123 NaN nan NAN 456"): with self.subTest(s): p = ParserHelper.from_string(s + " kg") assert math.isnan(p.scale) self.assertEqual(dict(p), {"kg": 1}) class TestStringProcessor(BaseTestCase): def _test(self, bef, aft): for pattern in ("{}", "+{}+"): b = pattern.format(bef) a = pattern.format(aft) self.assertEqual(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(BaseTestCase): def test_start_not_in_graph(self): g = collections.defaultdict(set) g[1] = {2} g[2] = {3} self.assertIs(find_connected_nodes(g, 9), None) def test_shortest_path(self): g = collections.defaultdict(set) g[1] = {2} g[2] = {3} p = find_shortest_path(g, 1, 2) self.assertEqual(p, [1, 2]) p = find_shortest_path(g, 1, 3) self.assertEqual(p, [1, 2, 3]) p = find_shortest_path(g, 3, 1) self.assertIs(p, None) g = collections.defaultdict(set) g[1] = {2} g[2] = {3, 1} g[3] = {2} p = find_shortest_path(g, 1, 2) self.assertEqual(p, [1, 2]) p = find_shortest_path(g, 1, 3) self.assertEqual(p, [1, 2, 3]) p = find_shortest_path(g, 3, 1) self.assertEqual(p, [3, 2, 1]) p = find_shortest_path(g, 2, 1) self.assertEqual(p, [2, 1]) class TestMatrix(BaseTestCase): def test_matrix_to_string(self): self.assertEqual( matrix_to_string([[1, 2], [3, 4]], row_headers=None, col_headers=None), "1\t2\n" "3\t4", ) self.assertEqual( 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", ) self.assertEqual( matrix_to_string( [[1, 2], [3, 4]], row_headers=["c", "d"], col_headers=None ), "c\t1\t2\n" "d\t3\t4", ) self.assertEqual( matrix_to_string( [[1, 2], [3, 4]], row_headers=None, col_headers=["a", "b"] ), "a\tb\n" "1\t2\n" "3\t4", ) self.assertEqual( 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): self.assertEqual(transpose([[1, 2], [3, 4]]), [[1, 3], [2, 4]]) class TestOtherUtils(BaseTestCase): def test_iterable(self): # Test with list, string, generator, and scalar self.assertTrue(iterable([0, 1, 2, 3])) self.assertTrue(iterable("test")) self.assertTrue(iterable((i for i in range(5)))) self.assertFalse(iterable(0)) def test_sized(self): # Test with list, string, generator, and scalar self.assertTrue(sized([0, 1, 2, 3])) self.assertTrue(sized("test")) self.assertFalse(sized((i for i in range(5)))) self.assertFalse(sized(0)) pint-0.10.1/pint/unit.py000066400000000000000000000236271360526634500150720ustar00rootroot00000000000000""" 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. """ import copy import locale import operator from numbers import Number from .compat import NUMERIC_TYPES, is_upcast_type from .definitions import UnitDefinition from .formatting import siunitx_format_unit from .util import PrettyIPython, SharedRegistryObject, UnitsContainer class Unit(PrettyIPython, SharedRegistryObject): """Implements a class to describe a unit supporting math operations.""" #: Default formatting string. default_format = "" def __reduce__(self): # See notes in Quantity.__reduce__ from . import _unpickle return _unpickle, (Unit, self._units) def __init__(self, units): 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): return self.__used def __copy__(self): ret = self.__class__(self._units) ret.__used = self.__used return ret def __deepcopy__(self, memo): ret = self.__class__(copy.deepcopy(self._units, memo)) ret.__used = self.__used return ret def __str__(self): return format(self) def __bytes__(self): return str(self).encode(locale.getpreferredencoding()) def __repr__(self): return "".format(self._units) def __format__(self, spec): spec = spec or self.default_format # special cases if "Lx" in spec: # the LaTeX siunitx code return r"\si[]{%s}" % siunitx_format_unit(self) if "~" in spec: if not self._units: return "" units = UnitsContainer( dict( (self._REGISTRY._get_symbol(key), value) for key, value in self._units.items() ) ) spec = spec.replace("~", "") else: units = self._units if "H" in spec: # HTML / Jupyter Notebook return r"\[" + format(units, spec).replace(" ", r"\ ") + r"\]" return format(units, spec) def format_babel(self, spec="", **kwspec): spec = spec or 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 return "%s" % (units.format_babel(spec, **kwspec)) @property def dimensionless(self): """Return True if the Unit is dimensionless; False otherwise. """ return not bool(self.dimensionality) @property def dimensionality(self): """ 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 __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.0, 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.0 * 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): 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): return self._units.__hash__() def __eq__(self, other): # 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): return not (self == other) def compare(self, other, op): 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): return int(self._REGISTRY.Quantity(1, self._units)) def __float__(self): return float(self._REGISTRY.Quantity(1, self._units)) def __complex__(self): 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 quanities 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 quanities 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): class Unit(_Unit): _REGISTRY = registry return Unit pint-0.10.1/pint/util.py000066400000000000000000000632021360526634500150610ustar00rootroot00000000000000""" pint.util ~~~~~~~~~ Miscellaneous functions for pint. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ import logging import math import operator import re from collections.abc import Mapping from decimal import Decimal from fractions import Fraction from functools import lru_cache from logging import NullHandler from numbers import Number from token import NAME, NUMBER from .compat import NUMERIC_TYPES, tokenizer from .errors import DefinitionSyntaxError from .formatting import format_unit from .pint_eval import build_eval_tree 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 else: getdim = registry.get_dimensionality for name, value in quantities.items(): if isinstance(value, str): value = ParserHelper.from_string(value) if isinstance(value, dict): dims = getdim(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.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") def __init__(self, *args, **kwargs): 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, float): d[key] = float(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): 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 def __eq__(self, other): 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): other = ParserHelper.from_string(other) other = other._d return dict.__eq__(self._d, other) def __str__(self): return self.__format__("") def __repr__(self): tmp = "{%s}" % ", ".join( ["'{}': {}".format(key, value) for key, value in sorted(self._d.items())] ) return "".format(tmp) def __format__(self, spec): return format_unit(self, spec) def format_babel(self, spec, **kwspec): return format_unit(self, spec, **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 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): """Creates a ParserHelper object with a single variable with exponent one. Equivalent to: ParserHelper({'word': 1}) Parameters ---------- input_word : Returns ------- """ return cls(1, [(input_word, 1)]) @classmethod def eval_token(cls, token, use_decimal=False): token_type = token.type token_text = token.string if token_type == NUMBER: try: return int(token_text) except ValueError: if use_decimal: return Decimal(token_text) return float(token_text) elif token_type == NAME: return ParserHelper.from_word(token_text) else: raise Exception("unknown token type") @classmethod @lru_cache() def from_string(cls, input_string): """Parse linear expression mathematical units and return a quantity object. Parameters ---------- input_string : Returns ------- """ if not input_string: return cls() 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(cls.eval_token) if isinstance(ret, Number): return ParserHelper(ret) if reps: ret = ParserHelper( ret.scale, { key.replace("__obra__", "[").replace("__cbra__", "]"): value for key, value in ret.items() }, ) 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.0: mess = "Only scale 1.0 ParserHelper instance should be considered hashable" raise ValueError(mess) return super().__hash__() 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) elif isinstance(other, Number): return self.scale == other and not len(self._d) else: return self.scale == 1.0 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) 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, 1) 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) 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, 1) 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 = [ ("\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] _pretty_table = str.maketrans("⁰¹²³⁴⁵⁶⁷⁸⁹·⁻", "0123456789*-") _pretty_exp_re = re.compile(r"⁻?[⁰¹²³⁴⁵⁶⁷⁸⁹]+(?:\.[⁰¹²³⁴⁵⁶⁷⁸⁹]*)?") def string_preprocessor(input_string): input_string = input_string.replace(",", "") input_string = input_string.replace(" per ", "/") for a, b in _subs_re: input_string = a.sub(b, input_string) # Replace pretty format characters for pretty_exp in _pretty_exp_re.findall(input_string): exp = "**" + pretty_exp.translate(_pretty_table) input_string = input_string.replace(pretty_exp, exp) input_string = input_string.translate(_pretty_table) # Handle caret exponentiation input_string = input_string.replace("^", "**") return input_string def _is_dim(name): 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 ------- """ 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 _APP_REGISTRY inst._REGISTRY = _APP_REGISTRY return inst def _check(self, other): """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""" 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, registry=None): """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: return UnitsContainer(unit_like) def infer_base_unit(q): """ Parameters ---------- q : Returns ------- type """ d = udict() for unit_name, power in q._units.items(): candidates = q._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 return UnitsContainer({k: v for k, v in d.items() if v != 0}) def getattr_maybe_raise(self, item): """Helper function to invoke at the beginning of all overridden ``__getattr__`` methods. Raise AttributeError if the user tries to ask for a _ or __ attribute. Parameters ---------- item : 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.startswith("_") or item.endswith("__"): 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): if isinstance(sequence, SourceIterator): return sequence obj = object.__new__(cls) if sequence is not None: obj.internal = enumerate(sequence, 1) obj.last = (None, None) 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): """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): """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.10.1/pint/xtranslated.txt000066400000000000000000000011041360526634500166150ustar00rootroot00000000000000 # a few unit definitions added to use the translations by unicode cldr dietary_calorie = 1000 * calorie = Cal = Calorie metric_cup = liter / 4 square_meter = kilometer ** 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.10.1/pull_request_template.md000066400000000000000000000003741360526634500175220ustar00rootroot00000000000000- [ ] Closes # (insert issue number) - [ ] Executed ``black -t py36 . && isort -rc . && flake8`` 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.10.1/readthedocs.yml000066400000000000000000000003221360526634500155620ustar00rootroot00000000000000version: 2 build: image: latest sphinx: configuration: docs/conf.py fail_on_warning: false python: version: 3.7 install: - requirements: requirements_docs.txt - method: pip path: . pint-0.10.1/requirements_docs.txt000066400000000000000000000000511360526634500170450ustar00rootroot00000000000000ipython matplotlib nbsphinx numpy pytest pint-0.10.1/setup.cfg000066400000000000000000000035001360526634500143740ustar00rootroot00000000000000[metadata] name = Pint author = Hernan E. Grecco author_email = hernan.grecco@gmail.com license = BSD description = Physical quantities module long_description = file: README.rst, AUTHORS, CHANGES 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.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 [options] packages = pint zip_safe = True include_package_data = True python_requires = >=3.6 install_requires = setuptools setup_requires = setuptools; setuptools_scm test_suite = pint.testsuite.testsuite [options.extras_require] numpy = numpy >= 1.14 uncertainties = uncertainties >= 3.0 test = pytest; pytest-mpl; pytest-cov [options.package_data] pint = default_en.txt; constants_en.txt [check-manifest] ignore = .travis.yml [bdist_wheel] universal = 1 [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.pypint-0.10.1/setup.py000066400000000000000000000001601360526634500142640ustar00rootroot00000000000000#!/usr/bin/env python3 from setuptools import setup if __name__ == "__main__": setup(use_scm_version=True) pint-0.10.1/version.py000066400000000000000000000001071360526634500146120ustar00rootroot00000000000000# This is just for zest.releaser. Do not touch __version__ = '0.10.1'