diff -Nru python-pint-0.9/AUTHORS python-pint-0.10.1/AUTHORS --- python-pint-0.9/AUTHORS 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/AUTHORS 2020-01-08 05:47:17.000000000 +0000 @@ -10,6 +10,7 @@ * Brend Wanders * choloepus * coutinho +* Clément Pit-Claudel * Daniel Sokolowski * Dave Brooks * David Linke @@ -17,7 +18,10 @@ * Eduard Bopp * Eli * Felix Hummel +* Francisco Couzo * Giel van Schijndel +* Guido Imperiale +* Ignacio Fdez. Galván * James Rowe * Jim Turner * Joel B. Mohler diff -Nru python-pint-0.9/bench/bench.py python-pint-0.10.1/bench/bench.py --- python-pint-0.9/bench/bench.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/bench/bench.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,21 +1,31 @@ - - -from __future__ import division, unicode_literals, print_function, absolute_import - +import copy import fnmatch import os -import copy from timeit import Timer import yaml -def time_stmt(stmt='pass', setup='pass', number=0, repeat=3): +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. - :return: elapsed time in seconds or NaN if the command failed. - :rtype: float + 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) @@ -23,49 +33,49 @@ if not number: # determine number so that 0.2 <= total time < 2.0 for i in range(1, 10): - number = 10**i + number = 10 ** i try: x = t.timeit(number) - except: + except Exception: print(t.print_exc()) - return float('NaN') + return float("NaN") if x >= 0.2: break try: r = t.repeat(repeat, number) - except: + except Exception: print(t.print_exc()) - return float('NaN') + return float("NaN") best = min(r) return best / number -def build_task(task, name='', setup='', number=0, repeat=3): +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) + 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=''): +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)' + yield name + " (base)", nvalue + suffix = " (normalized)" else: - nvalue = 1. - suffix = '' + nvalue = 1.0 + suffix = "" if stmt: value = time_stmt(stmt=stmt, setup=setup, number=number, repeat=repeat) @@ -77,12 +87,29 @@ yield task_name + suffix, value / nvalue -def time_file(filename, name='', setup='', number=0, repeat=3): +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: + with open(filename, "r") as fp: tasks = yaml.load(fp) for task in tasks: @@ -91,24 +118,29 @@ 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 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, basestring): - filenames = [filenames, ] + filenames = recursive_glob(".", "bench_*.yaml") + elif isinstance(filenames, str): + filenames = [filenames] for filename in filenames: print(filename) - print('-' * len(filename)) + print("-" * len(filename)) print() for task_name, value in time_file(filename): - print('%.2e %s' % (value, task_name)) + print(f"{value:.2e} {task_name}") print() -main() + +if __name__ == "__main__": + main() diff -Nru python-pint-0.9/CHANGES python-pint-0.10.1/CHANGES --- python-pint-0.9/CHANGES 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/CHANGES 2020-01-08 05:47:17.000000000 +0000 @@ -1,6 +1,192 @@ Pint 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) ---------------- diff -Nru python-pint-0.9/CHANGES_DEV python-pint-0.10.1/CHANGES_DEV --- python-pint-0.9/CHANGES_DEV 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/CHANGES_DEV 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ - - - -0.7 (unreleased) ----------------- diff -Nru python-pint-0.9/debian/changelog python-pint-0.10.1/debian/changelog --- python-pint-0.9/debian/changelog 2019-12-17 12:53:15.000000000 +0000 +++ python-pint-0.10.1/debian/changelog 2020-01-14 12:49:58.000000000 +0000 @@ -1,8 +1,15 @@ -python-pint (0.9-2build1) focal; urgency=medium +python-pint (0.10.1-1) unstable; urgency=medium - * No-change rebuild to generate dependencies on python2. + * New upstream release (Closes: #948422). + * Bump Standards-Version to 4.4.1. + * Rebase patches. + * Bump debhelper compat level to 12 and use debhelper-compat + * Add python3-setuptools-scm to B-D. + * Add python3-nbsphinx and pandoc to B-D-I. + * Switch from nose to pytest (same as upstream). + * d/copyright: Fix for new upstream release. - -- Matthias Klose Tue, 17 Dec 2019 12:53:15 +0000 + -- Ondřej Nový Tue, 14 Jan 2020 13:49:58 +0100 python-pint (0.9-2) unstable; urgency=medium diff -Nru python-pint-0.9/debian/compat python-pint-0.10.1/debian/compat --- python-pint-0.9/debian/compat 2019-10-10 15:49:11.000000000 +0000 +++ python-pint-0.10.1/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -11 diff -Nru python-pint-0.9/debian/control python-pint-0.10.1/debian/control --- python-pint-0.9/debian/control 2019-10-10 15:49:11.000000000 +0000 +++ python-pint-0.10.1/debian/control 2020-01-14 10:08:17.000000000 +0000 @@ -4,16 +4,19 @@ Maintainer: Debian Python Modules Team Uploaders: Thomas Goirand , Ondřej Nový , -Build-Depends: debhelper (>= 11.1.4~), +Build-Depends: debhelper-compat (= 12), dh-python, python3-all, python3-setuptools, + python3-setuptools-scm, python3-sphinx, -Build-Depends-Indep: python3-matplotlib, - python3-nose, +Build-Depends-Indep: pandoc, + python3-matplotlib, + python3-pytest, + python3-nbsphinx, python3-numpy, python3-yaml, -Standards-Version: 4.4.0 +Standards-Version: 4.4.1 Vcs-Browser: https://salsa.debian.org/python-team/modules/python-pint Vcs-Git: https://salsa.debian.org/python-team/modules/python-pint.git Homepage: https://github.com/hgrecco/pint diff -Nru python-pint-0.9/debian/copyright python-pint-0.10.1/debian/copyright --- python-pint-0.9/debian/copyright 2019-10-10 15:49:11.000000000 +0000 +++ python-pint-0.10.1/debian/copyright 2020-01-14 12:49:23.000000000 +0000 @@ -4,7 +4,8 @@ Source: https://github.com/hgrecco/pint Files: * -Copyright: (c) 2012-2017, See AUTHORS file +Copyright: (c) 2012-2019 by Hernan E. Grecco and contributors. + See AUTHORS for more details. License: BSD-3-clauses Files: debian/* @@ -12,14 +13,6 @@ (c) 2016-2019, Ondřej Nový License: BSD-3-clauses -Files: pint/compat/chainmap.py -Copyright: (c) 2013, the Python Software Foundation ("PSF") -License: Python - -Files: pint/compat/lrucache.py -Copyright: (c) 2004, Raymond Hettinger -License: Expat - Files: docs/_themes/* Copyright: (c) 2010 by Armin Ronacher License: Flask-Design @@ -53,68 +46,6 @@ SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -License: Python - 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), - and the Individual or Organization ("Licensee") accessing and otherwise - using this software ("Python") in source or binary form and its associated - documentation. - . - 2. Subject to the terms and conditions of this License Agreement, PSF hereby - grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, - analyze, test, perform and/or display publicly, prepare derivative works, - distribute, and otherwise use Python alone or in any derivative version, - provided, however, that PSF's License Agreement and PSF's notice of copyright, - i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, - 2010 Python Software Foundation; All Rights Reserved" are retained in Python - alone or in any derivative version prepared by Licensee. - . - 3. In the event Licensee prepares a derivative work that is based on or - incorporates Python or any part thereof, and wants to make the derivative work - available to others as provided herein, then Licensee hereby agrees to include - in any such work a brief summary of the changes made to Python. - . - 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES - NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT - NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF - MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF - PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. - . - 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY - INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF - MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, - EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - . - 6. This License Agreement will automatically terminate upon a material breach - of its terms and conditions. - . - 7. Nothing in this License Agreement shall be deemed to create any - relationship of agency, partnership, or joint venture between PSF and - Licensee. This License Agreement does not grant permission to use PSF - trademarks or trade name in a trademark sense to endorse or promote products - or services of Licensee, or any third party. - . - 8. By copying, installing or otherwise using Python, Licensee agrees to be - bound by the terms and conditions of this License Agreement. - -License: Expat - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - . - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - . - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - License: Flask-Design Redistribution and use in source and binary forms of the theme, with or without modification, are permitted provided that the following conditions are diff -Nru python-pint-0.9/debian/patches/0001-Do-not-use-intersphinx-when-building-docs.patch python-pint-0.10.1/debian/patches/0001-Do-not-use-intersphinx-when-building-docs.patch --- python-pint-0.9/debian/patches/0001-Do-not-use-intersphinx-when-building-docs.patch 2019-10-10 15:49:11.000000000 +0000 +++ python-pint-0.10.1/debian/patches/0001-Do-not-use-intersphinx-when-building-docs.patch 2020-01-12 13:03:24.000000000 +0000 @@ -11,12 +11,11 @@ --- a/docs/conf.py +++ b/docs/conf.py -@@ -28,7 +28,7 @@ - - # 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.viewcode', 'sphinx.ext.mathjax', 'matplotlib.sphinxext.plot_directive'] -+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.mathjax', 'matplotlib.sphinxext.plot_directive'] - - # Add any paths that contain templates here, relative to this directory. - templates_path = ['_templates'] +@@ -30,7 +30,6 @@ + extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", +- "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", diff -Nru python-pint-0.9/debian/patches/0002-Removes-privacy-breach-in-docs.patch python-pint-0.10.1/debian/patches/0002-Removes-privacy-breach-in-docs.patch --- python-pint-0.9/debian/patches/0002-Removes-privacy-breach-in-docs.patch 2019-10-10 15:49:11.000000000 +0000 +++ python-pint-0.10.1/debian/patches/0002-Removes-privacy-breach-in-docs.patch 2020-01-12 13:03:49.000000000 +0000 @@ -23,12 +23,11 @@ {% block header %} --- a/docs/conf.py +++ b/docs/conf.py -@@ -28,7 +28,7 @@ - - # 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.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.mathjax', 'matplotlib.sphinxext.plot_directive'] -+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'matplotlib.sphinxext.plot_directive'] - - # Add any paths that contain templates here, relative to this directory. - templates_path = ['_templates'] +@@ -33,7 +33,6 @@ + "sphinx.ext.coverage", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", +- "sphinx.ext.mathjax", + "matplotlib.sphinxext.plot_directive", + "nbsphinx", + ] diff -Nru python-pint-0.9/debian/patches/0003-Skip-not-working-unit-tests.patch python-pint-0.10.1/debian/patches/0003-Skip-not-working-unit-tests.patch --- python-pint-0.9/debian/patches/0003-Skip-not-working-unit-tests.patch 2019-10-10 15:49:11.000000000 +0000 +++ python-pint-0.10.1/debian/patches/0003-Skip-not-working-unit-tests.patch 2020-01-12 13:05:44.000000000 +0000 @@ -12,8 +12,8 @@ --- a/pint/testsuite/__init__.py +++ b/pint/testsuite/__init__.py -@@ -183,6 +183,7 @@ - globs=_GLOBS.get(name, None))) +@@ -195,6 +195,7 @@ + ) +@unittest.skip("Not working on Debian") @@ -22,16 +22,15 @@ add_docs(suite) --- a/pint/testsuite/test_babel.py +++ b/pint/testsuite/test_babel.py -@@ -4,10 +4,12 @@ - from pint.testsuite import helpers, BaseTestCase - from pint import UnitRegistry +@@ -1,9 +1,11 @@ import os +import unittest - class TestBabel(BaseTestCase): + from pint import UnitRegistry + from pint.testsuite import BaseTestCase, helpers - @helpers.requires_proper_babel() -+ @unittest.skip("Not working on Debian") - def test_babel(self): - ureg = UnitRegistry() - dirname = os.path.dirname(__file__) + ++@unittest.skip("Not working on Debian") + class TestBabel(BaseTestCase): + @helpers.requires_babel() + def test_format(self): diff -Nru python-pint-0.9/debian/patches/0004-Use-SOURCE_DATE_EPOCH-for-copyright-year-to-make-bui.patch python-pint-0.10.1/debian/patches/0004-Use-SOURCE_DATE_EPOCH-for-copyright-year-to-make-bui.patch --- python-pint-0.9/debian/patches/0004-Use-SOURCE_DATE_EPOCH-for-copyright-year-to-make-bui.patch 2019-10-10 15:49:11.000000000 +0000 +++ python-pint-0.10.1/debian/patches/0004-Use-SOURCE_DATE_EPOCH-for-copyright-year-to-make-bui.patch 2020-01-12 13:17:30.000000000 +0000 @@ -9,11 +9,17 @@ docs/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) -diff --git a/docs/conf.py b/docs/conf.py -index 4cbf87b..e241524 100644 --- a/docs/conf.py +++ b/docs/conf.py -@@ -58,7 +58,10 @@ except: # pragma: no cover +@@ -12,6 +12,7 @@ + # serve to show the default. + + import datetime ++import os + + import pkg_resources + +@@ -65,7 +66,10 @@ version = "unknown" release = version @@ -22,6 +28,6 @@ + this_year = datetime.datetime.utcfromtimestamp(float(os.environ['SOURCE_DATE_EPOCH'])).year +else: + this_year = datetime.date.today().year - copyright = '%s, %s' % (this_year, author) + copyright = "%s, %s" % (this_year, author) # The language for content autogenerated by Sphinx. Refer to documentation diff -Nru python-pint-0.9/debian/patches/0005-use_looseversion.patch python-pint-0.10.1/debian/patches/0005-use_looseversion.patch --- python-pint-0.9/debian/patches/0005-use_looseversion.patch 2019-10-10 15:49:11.000000000 +0000 +++ python-pint-0.10.1/debian/patches/0005-use_looseversion.patch 2020-01-12 13:07:10.000000000 +0000 @@ -15,28 +15,38 @@ --- a/pint/testsuite/helpers.py +++ b/pint/testsuite/helpers.py -@@ -4,7 +4,7 @@ - - - import doctest --from distutils.version import StrictVersion -+from distutils.version import LooseVersion +@@ -2,6 +2,7 @@ import re import unittest + from distutils.version import StrictVersion ++from distutils.version import LooseVersion -@@ -14,13 +14,13 @@ - def requires_numpy18(): + from ..compat import ( + HAS_BABEL, +@@ -33,7 +34,7 @@ if not HAS_NUMPY: - return unittest.skip('Requires NumPy') -- return unittest.skipUnless(StrictVersion(NUMPY_VER) >= StrictVersion('1.8'), 'Requires NumPy >= 1.8') -+ return unittest.skipUnless(LooseVersion(NUMPY_VER) >= LooseVersion('1.8'), 'Requires NumPy >= 1.8') + return unittest.skip("Requires NumPy") + return unittest.skipUnless( +- StrictVersion(NUMPY_VER) >= StrictVersion("1.8"), "Requires NumPy >= 1.8" ++ LooseVersion(NUMPY_VER) >= LooseVersion("1.8"), "Requires NumPy >= 1.8" + ) - def requires_numpy_previous_than(version): +@@ -41,7 +42,7 @@ if not HAS_NUMPY: - return unittest.skip('Requires NumPy') -- return unittest.skipUnless(StrictVersion(NUMPY_VER) < StrictVersion(version), 'Requires NumPy < %s' % version) -+ return unittest.skipUnless(LooseVersion(NUMPY_VER) < LooseVersion(version), 'Requires NumPy < %s' % version) + return unittest.skip("Requires NumPy") + return unittest.skipUnless( +- StrictVersion(NUMPY_VER) < StrictVersion(version), ++ LooseVersion(NUMPY_VER) < LooseVersion(version), + "Requires NumPy < %s" % version, + ) +@@ -50,7 +51,7 @@ + if not HAS_NUMPY: + return unittest.skip("Requires NumPy") + return unittest.skipUnless( +- StrictVersion(NUMPY_VER) >= StrictVersion(version), ++ LooseVersion(NUMPY_VER) >= LooseVersion(version), + "Requires NumPy >= %s" % version, + ) - def requires_numpy(): diff -Nru python-pint-0.9/docs/conf.py python-pint-0.10.1/docs/conf.py --- python-pint-0.9/docs/conf.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/conf.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # pint documentation build configuration file, created by # sphinx-quickstart on Thu Mar 1 13:33:14 2012. @@ -12,231 +11,238 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os -import pkg_resources 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('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# 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.viewcode', 'sphinx.ext.mathjax', 'matplotlib.sphinxext.plot_directive'] +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'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'pint' -author = 'Hernan E. Grecco' +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 +try: # pragma: no cover version = pkg_resources.get_distribution(project).version -except: # pragma: no cover +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) +copyright = "%s, %s" % (this_year, author) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# 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'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# 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 +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# 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' +# 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 = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] -html_theme_path = ['_themes'] +# 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 +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# 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 +# 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 +# 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'] +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' +# 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 +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} html_sidebars = { - 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], - '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', - 'sourcelink.html', 'searchbox.html'] + "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 = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = 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 = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'pintdoc' +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(( - '\DeclareUnicodeCharacter{2212}{-}', # MINUS - )), + # 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'), + ("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 +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# 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) -] +man_pages = [("index", "pint", "pint Documentation", ["Hernan E. Grecco"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -245,19 +251,25 @@ # (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'), + ( + "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 = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # -- Options for Epub output --------------------------------------------------- @@ -270,38 +282,38 @@ # The language of the text. It defaults to the language option # or en if the language is not set. -#epub_language = '' +# epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' +# epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. -#epub_identifier = '' +# epub_identifier = '' # A unique identification for the text. -#epub_uid = '' +# epub_uid = '' # A tuple containing the cover image and cover page html template filenames. -#epub_cover = () +# 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 = [] +# 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 = [] +# epub_post_files = [] # A list of files that should not be packed into the epub file. -#epub_exclude_files = [] +# epub_exclude_files = [] # The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 +# epub_tocdepth = 3 # Allow duplicate toc entries. -#epub_tocdup = True +# epub_tocdup = True # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = {"http://docs.python.org/": None} diff -Nru python-pint-0.9/docs/contexts.rst python-pint-0.10.1/docs/contexts.rst --- python-pint-0.9/docs/contexts.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/contexts.rst 2020-01-08 05:47:17.000000000 +0000 @@ -7,7 +7,7 @@ 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 your do this directly: +raise an error if you do this directly: .. doctest:: @@ -17,7 +17,7 @@ >>> q.to('Hz') Traceback (most recent call last): ... - pint.errors.DimensionalityError: Cannot convert from 'nanometer' ([length]) to 'hertz' (1 / [time]) + DimensionalityError: Cannot convert from 'nanometer' ([length]) to 'hertz' (1 / [time]) You probably want to use the relation `frequency = speed_of_light / wavelength`: @@ -138,7 +138,8 @@ 398.496240602 -This decorator can be combined with **wraps** or **check** decorators described in `wrapping`_ +This decorator can be combined with **wraps** or **check** decorators described in +:doc:`wrapping`. Defining contexts in a file @@ -188,7 +189,139 @@ >>> ureg = pint.UnitRegistry() >>> c = pint.Context('ab') >>> c.add_transformation('[length]', '[time]', - ... lambda ureg, x: ureg.speed_of_light / x) + ... lambda ureg, x: x / ureg.speed_of_light) >>> c.add_transformation('[time]', '[length]', - ... lambda ureg, x: ureg.speed_of_light * x) + ... 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 diff -Nru python-pint-0.9/docs/contributing.rst python-pint-0.10.1/docs/contributing.rst --- python-pint-0.9/docs/contributing.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/contributing.rst 2020-01-08 05:47:17.000000000 +0000 @@ -19,9 +19,13 @@ 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 - Also add "Close # as described in the `github docs`_. +- 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. diff -Nru python-pint-0.9/docs/currencies.rst python-pint-0.10.1/docs/currencies.rst --- python-pint-0.9/docs/currencies.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/docs/currencies.rst 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,86 @@ +.. _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`). diff -Nru python-pint-0.9/docs/defining.rst python-pint-0.10.1/docs/defining.rst --- python-pint-0.9/docs/defining.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/defining.rst 2020-01-08 05:47:17.000000000 +0000 @@ -16,9 +16,16 @@ 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`. The first word is always the canonical -name. Next comes the definition (based on other units). Finally, a list of -aliases, separated by equal signs. +`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 @@ -68,12 +75,28 @@ 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 to the registry programmatically. Let's add a dog_year -(sometimes written as dy) equivalent to 52 (human) days: +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:: @@ -93,6 +116,8 @@ 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: @@ -102,4 +127,14 @@ where the number indicates the multiplication factor. -.. warning:: Units and prefixes added programmatically are forgotten when the program ends. +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. diff -Nru python-pint-0.9/docs/developers_reference.rst python-pint-0.10.1/docs/developers_reference.rst --- python-pint-0.9/docs/developers_reference.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/docs/developers_reference.rst 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,114 @@ +=================== +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: \ No newline at end of file diff -Nru python-pint-0.9/docs/faq.rst python-pint-0.10.1/docs/faq.rst --- python-pint-0.9/docs/faq.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/faq.rst 2020-01-08 05:47:17.000000000 +0000 @@ -31,13 +31,12 @@ `SymPy `_ -`Units `_ - `cf units `_ `astropy units `_ `yt `_ +`measurement `_ -If your are aware of another one, please contribute a patch to the docs. +If you're aware of another one, please contribute a patch to the docs. diff -Nru python-pint-0.9/docs/getting.rst python-pint-0.10.1/docs/getting.rst --- python-pint-0.9/docs/getting.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/getting.rst 2020-01-08 05:47:17.000000000 +0000 @@ -3,7 +3,7 @@ Installation ============ -Pint has no dependencies except Python_ itself. In runs on Python 2.7 and 3.3+. +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_:: @@ -19,7 +19,8 @@ .. 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: + 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: diff -Nru python-pint-0.9/docs/index.rst python-pint-0.10.1/docs/index.rst --- python-pint-0.9/docs/index.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/index.rst 2020-01-08 05:47:17.000000000 +0000 @@ -16,7 +16,7 @@ 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 2.7 and 3.3+ with no other +It has a complete test coverage. It runs in Python 3.6+ with no other dependency. It is licensed under BSD. @@ -83,7 +83,7 @@ (`fraction`, `float`, `decimal`, `numpy.ndarray`, etc). NumPy_ is not required but supported. -**NumPy integration**: When you choose to use a NumPy_ ndarray, its methods and +**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. @@ -95,14 +95,10 @@ **Handle temperature**: conversion between units with different reference points, like positions on a map or absolute temperature scales. -**Small codebase**: easy to maintain codebase with a flat hierarchy. +**Dependency free**: it depends only on Python and its standard library. It interacts with other packages +like numpy and uncertainties if they are installed -**Dependency free**: it depends only on Python and its standard library. - -**Python 2 and 3**: a single codebase that runs unchanged in Python 2.7+ and -Python 3.3+. - -**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 `Pandas Support Documentation`_. +**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 @@ -130,7 +126,8 @@ defining performance systems - + currencies + pint-pandas.ipynb More information ---------------- @@ -138,6 +135,7 @@ .. toctree:: :maxdepth: 1 + developers_reference contributing faq @@ -150,7 +148,7 @@ 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 `_ + `PDF `_ @@ -160,4 +158,4 @@ .. _`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 -.. _`Pandas Support Documentation`: ./pandas.rst +.. _`pint-pandas Jupyter notebook`: https://github.com/hgrecco/pint-pandas/blob/master/notebooks/pandas_support.ipynb diff -Nru python-pint-0.9/docs/nonmult.rst python-pint-0.10.1/docs/nonmult.rst --- python-pint-0.9/docs/nonmult.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/nonmult.rst 2020-01-08 05:47:17.000000000 +0000 @@ -79,7 +79,7 @@ >>> Q_(10., ureg.degC) + heating_rate * Q_(30, ureg.min) Traceback (most recent call last): ... - pint.errors.OffsetUnitCalculusError: Ambiguous operation with offset unit (degC, kelvin). + 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 @@ -123,7 +123,7 @@ >>> home = 25.4 * ureg.degC Traceback (most recent call last): ... - pint.errors.OffsetUnitCalculusError: Ambiguous operation with offset unit (degC). + OffsetUnitCalculusError: Ambiguous operation with offset unit (degC). >>> Q_(25.4, ureg.degC) @@ -157,7 +157,7 @@ >>> 1/T Traceback (most recent call last): ... - pint.errors.OffsetUnitCalculusError: Ambiguous operation with offset unit (degC). + 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: diff -Nru python-pint-0.9/docs/numpy.ipynb python-pint-0.10.1/docs/numpy.ipynb --- python-pint-0.9/docs/numpy.ipynb 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/docs/numpy.ipynb 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,807 @@ +{ + "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 +} diff -Nru python-pint-0.9/docs/numpy.rst python-pint-0.10.1/docs/numpy.rst --- python-pint-0.9/docs/numpy.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/numpy.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -.. _numpy: - - -NumPy support -============= - -The magnitude of a Pint quantity can be of any numerical type and you are free -to choose it according to your needs. In numerical applications, it is quite -convenient to use `NumPy ndarray`_ and therefore they are supported by Pint. - -First, we import the relevant packages: - -.. doctest:: - - >>> import numpy as np - >>> from pint import UnitRegistry - >>> ureg = UnitRegistry() - >>> Q_ = ureg.Quantity - -.. testsetup:: * - - import numpy as np - from pint import UnitRegistry - ureg = UnitRegistry() - Q_ = ureg.Quantity - -and then we create a quantity the standard way - -.. doctest:: - - >>> legs1 = Q_(np.asarray([3., 4.]), 'meter') - >>> print(legs1) - [ 3. 4.] meter - -or we use the property that Pint converts iterables into NumPy ndarrays to simply write: - -.. doctest:: - - >>> legs1 = [3., 4.] * ureg.meter - >>> print(legs1) - [ 3. 4.] meter - -All usual Pint methods can be used with this quantity. For example: - -.. doctest:: - - >>> print(legs1.to('kilometer')) - [ 0.003 0.004] kilometer - >>> print(legs1.dimensionality) - [length] - >>> legs1.to('joule') - Traceback (most recent call last): - ... - pint.errors.DimensionalityError: Cannot convert from 'meter' ([length]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2) - -NumPy functions are supported by Pint. For example if we define: - -.. doctest:: - - >>> legs2 = [400., 300.] * ureg.centimeter - >>> print(legs2) - [ 400. 300.] centimeter - -we can calculate the hypotenuse of the right triangles with legs1 and legs2. - -.. doctest:: - - >>> hyps = np.hypot(legs1, legs2) - >>> print(hyps) - [ 5. 5.] meter - -Notice that before the `np.hypot` was used, the numerical value of legs2 was -internally converted to the units of legs1 as expected. - -Similarly, when you apply a function that expects angles in radians, a conversion -is applied before the requested calculation: - -.. doctest:: - - >>> angles = np.arccos(legs2/hyps) - >>> print(angles) - [ 0.64350111 0.92729522] radian - -You can convert the result to degrees using the corresponding NumPy function: - -.. doctest:: - - >>> print(np.rad2deg(angles)) - [ 36.86989765 53.13010235] degree - -Applying a function that expects angles to a quantity with a different dimensionality -results in an error: - -.. doctest:: - - >>> np.arccos(legs2) - Traceback (most recent call last): - ... - pint.errors.DimensionalityError: Cannot convert from 'centimeter' ([length]) to 'dimensionless' (dimensionless) - - -Support --------- - -The following ufuncs_ can be applied to a Quantity object: - -- **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, reciprocal -- **Trigonometric functions**: sin, cos, tan, arcsin, arccos, arctan, arctan2, hypot, sinh, cosh, tanh, arcsinh, arccosh, arctanh, deg2rad, rad2deg -- **Comparison functions**: greater, greater_equal, less, less_equal, not_equal, equal -- **Floating functions**: isreal,iscomplex, isfinite, isinf, isnan, signbit, copysign, nextafter, modf, ldexp, frexp, fmod, floor, ceil, trunc - -And the following `ndarrays methods`_ and functions: - -- sum, fill, reshape, transpose, flatten, ravel, squeeze, take, put, repeat, sort, argsort, diagonal, compress, nonzero, searchsorted, max, argmax, min, argmin, ptp, clip, round, trace, cumsum, mean, var, std, prod, cumprod, conj, conjugate, flatten - -`Quantity` is not a subclass of `ndarray`. This might change in the future, but for this reason functions that call `numpy.asanyarray` are currently not supported. These functions are: - -- unwrap, trapz, diff, ediff1d, fix, gradient, cross, ones_like - - -Comments --------- - -What follows is a short discussion about how NumPy support is implemented in -Pint's `Quantity` Object. - -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. - -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. - -This behaviour introduces some performance penalties and increased memory -usage. Quantities that must be converted to other units require additional -memory and CPU cycles. On top of this, all `ufuncs` are implemented in the -`Quantity` class by overriding `__array_wrap__`, a NumPy hook that is executed -after the calculation and before returning the value. To our knowledge, there -is no way to signal back to NumPy that our code will take care of the -calculation. For this reason the calculation is actually done twice: -first in the original ndarray and then in then in the one that has been -converted to the right units. Therefore, for numerically intensive code, you -might want to convert the objects first and then use directly the magnitude. - - - - -.. _`NumPy ndarray`: http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html -.. _ufuncs: http://docs.scipy.org/doc/numpy/reference/ufuncs.html -.. _`ndarrays methods`: http://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html#array-methods diff -Nru python-pint-0.9/docs/pint-pandas.ipynb python-pint-0.10.1/docs/pint-pandas.ipynb --- python-pint-0.9/docs/pint-pandas.ipynb 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/docs/pint-pandas.ipynb 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,1545 @@ +{ + "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 +} diff -Nru python-pint-0.9/docs/plotting.rst python-pint-0.10.1/docs/plotting.rst --- python-pint-0.9/docs/plotting.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/plotting.rst 2020-01-08 05:47:17.000000000 +0000 @@ -27,7 +27,7 @@ >>> ureg.setup_matplotlib(False) -This allows then plotting quantities with different units: +This allows plotting quantities with different units: .. plot:: :include-source: true diff -Nru python-pint-0.9/docs/serialization.rst python-pint-0.10.1/docs/serialization.rst --- python-pint-0.9/docs/serialization.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/serialization.rst 2020-01-08 05:47:17.000000000 +0000 @@ -51,9 +51,24 @@ 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 **DEFAULT_REGISTRY** is used for unpickling and this might be different from the one -that was used during pickling. If you want to have control over the deserialization, the -best way is to create a tuple with the magnitude and the units: +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:: @@ -94,7 +109,7 @@ .. _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.4/library/shelve.html +.. _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 diff -Nru python-pint-0.9/docs/systems.rst python-pint-0.10.1/docs/systems.rst --- python-pint-0.9/docs/systems.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/systems.rst 2020-01-08 05:47:17.000000000 +0000 @@ -28,10 +28,10 @@ >>> '{:.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` +.. 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: diff -Nru python-pint-0.9/docs/_themes/flask_theme_support.py python-pint-0.10.1/docs/_themes/flask_theme_support.py --- python-pint-0.9/docs/_themes/flask_theme_support.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/_themes/flask_theme_support.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,7 +1,19 @@ # flasky extensions. flasky pygments style based on tango style from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal +from pygments.token import ( + Comment, + Error, + Generic, + Keyword, + Literal, + Name, + Number, + Operator, + Other, + Punctuation, + String, + Whitespace, +) class FlaskyStyle(Style): @@ -10,77 +22,68 @@ 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' - + # 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' + 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' } diff -Nru python-pint-0.9/docs/tutorial.rst python-pint-0.10.1/docs/tutorial.rst --- python-pint-0.9/docs/tutorial.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/tutorial.rst 2020-01-08 05:47:17.000000000 +0000 @@ -89,7 +89,7 @@ >>> speed.to(ureg.joule) Traceback (most recent call last): ... - pint.errors.DimensionalityError: Cannot convert from 'inch / minute' ([length] / [time]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2) + 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 @@ -170,7 +170,7 @@ >>> speed = 23 * ureg.snail_speed Traceback (most recent call last): ... - pint.errors.UndefinedUnitError: 'snail_speed' is not defined in the unit registry + 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` @@ -292,6 +292,33 @@ >>> 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: @@ -309,10 +336,6 @@ >>> 'The HTML representation is {:H}'.format(accel) 'The HTML representation is 1.3 meter/second2' -.. note:: - In Python 2, run ``from __future__ import unicode_literals`` - or prefix pretty formatted strings with `u` to prevent ``UnicodeEncodeError``. - If you want to use abbreviated unit names, prefix the specification with `~`: .. doctest:: @@ -325,6 +348,18 @@ 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:: @@ -352,6 +387,10 @@ >>> 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 --------------------------- @@ -398,3 +437,5 @@ .. _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/ \ No newline at end of file diff -Nru python-pint-0.9/docs/wrapping.rst python-pint-0.10.1/docs/wrapping.rst --- python-pint-0.9/docs/wrapping.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/docs/wrapping.rst 2020-01-08 05:47:17.000000000 +0000 @@ -144,7 +144,7 @@ solar zenith, azimuth and air mass, the following wrapper assumes no units for airmass:: - @UREG.wraps(('deg', 'deg'), ('deg', 'deg', 'millibar', 'degC')) + @ureg.wraps(('deg', 'deg'), ('deg', 'deg', 'millibar', 'degC')) def solar_position(lat, lon, press, tamb, timestamp): return zenith, azimuth, airmass @@ -183,8 +183,9 @@ Specifying relations between arguments -------------------------------------- -In certain cases the actual units but just their relation. This is done using string -starting with the equal sign `=`: +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:: diff -Nru python-pint-0.9/.gitignore python-pint-0.10.1/.gitignore --- python-pint-0.9/.gitignore 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/.gitignore 2020-01-08 05:47:17.000000000 +0000 @@ -8,8 +8,8 @@ build/ dist/ MANIFEST -.tox *pytest_cache* +.eggs # WebDAV file system cache files .DAV/ diff -Nru python-pint-0.9/MANIFEST.in python-pint-0.10.1/MANIFEST.in --- python-pint-0.9/MANIFEST.in 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/MANIFEST.in 2020-01-08 05:47:17.000000000 +0000 @@ -1,7 +1,8 @@ -include README AUTHORS CHANGES LICENSE CHANGES_DEV README.rst BADGES.rst +include 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 diff -Nru python-pint-0.9/pint/babel_names.py python-pint-0.10.1/pint/babel_names.py --- python-pint-0.9/pint/babel_names.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/babel_names.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.babel ~~~~~~~~~~ @@ -7,144 +6,139 @@ :license: BSD, see LICENSE for more details. """ -from pint.compat import HAS_PROPER_BABEL +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', - gream='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', + 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_PROPER_BABEL: - _babel_units = dict() +if not HAS_BABEL: + _babel_units = {} -_babel_systems = dict( - mks='metric', - imperial='uksystem', - US='ussystem', -) - -_babel_lengths = ['narrow', 'short', 'long'] +_babel_systems = dict(mks="metric", imperial="uksystem", US="ussystem") +_babel_lengths = ["narrow", "short", "long"] diff -Nru python-pint-0.9/pint/compat/chainmap.py python-pint-0.10.1/pint/compat/chainmap.py --- python-pint-0.9/pint/compat/chainmap.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/compat/chainmap.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pint.compat.chainmap - ~~~~~~~~~~~~~~~~~~~~ - - Taken from the Python 3.3 source code. - - :copyright: 2013, PSF - :license: PSF License -""" - -from __future__ import division, unicode_literals, print_function, absolute_import - -import sys - -if sys.version_info < (3, 3): - from collections import MutableMapping -else: - from collections.abc import MutableMapping - -if sys.version_info < (3, 0): - from thread import get_ident -else: - from threading import get_ident - - -def _recursive_repr(fillvalue='...'): - 'Decorator to make a repr function return fillvalue for a recursive call' - - def decorating_function(user_function): - repr_running = set() - - def wrapper(self): - key = id(self), get_ident() - if key in repr_running: - return fillvalue - repr_running.add(key) - try: - result = user_function(self) - finally: - repr_running.discard(key) - return result - - # Can't use functools.wraps() here because of bootstrap issues - wrapper.__module__ = getattr(user_function, '__module__') - wrapper.__doc__ = getattr(user_function, '__doc__') - wrapper.__name__ = getattr(user_function, '__name__') - wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) - return wrapper - - return decorating_function - - -class ChainMap(MutableMapping): - ''' A ChainMap groups multiple dicts (or other mappings) together - to create a single, updateable view. - - The underlying mappings are stored in a list. That list is public and can - accessed or updated using the *maps* attribute. There is no other state. - - Lookups search the underlying mappings successively until a key is found. - In contrast, writes, updates, and deletions only operate on the first - mapping. - - ''' - - def __init__(self, *maps): - '''Initialize a ChainMap by setting *maps* to the given mappings. - If no mappings are provided, a single empty dictionary is used. - - ''' - self.maps = list(maps) or [{}] # always at least one map - - def __missing__(self, key): - raise KeyError(key) - - def __getitem__(self, key): - for mapping in self.maps: - try: - return mapping[key] # can't use 'key in mapping' with defaultdict - except KeyError: - pass - return self.__missing__(key) # support subclasses that define __missing__ - - def get(self, key, default=None): - return self[key] if key in self else default - - def __len__(self): - return len(set().union(*self.maps)) # reuses stored hash values if possible - - def __iter__(self): - return iter(set().union(*self.maps)) - - def __contains__(self, key): - return any(key in m for m in self.maps) - - def __bool__(self): - return any(self.maps) - - @_recursive_repr() - def __repr__(self): - return '{0.__class__.__name__}({1})'.format( - self, ', '.join(map(repr, self.maps))) - - @classmethod - def fromkeys(cls, iterable, *args): - 'Create a ChainMap with a single dict created from the iterable.' - return cls(dict.fromkeys(iterable, *args)) - - def copy(self): - 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' - return self.__class__(self.maps[0].copy(), *self.maps[1:]) - - __copy__ = copy - - def new_child(self, m=None): # like Django's _Context.push() - ''' - New ChainMap with a new map followed by all previous maps. If no - map is provided, an empty dict is used. - ''' - if m is None: - m = {} - return self.__class__(m, *self.maps) - - @property - def parents(self): # like Django's _Context.pop() - 'New ChainMap from maps[1:].' - return self.__class__(*self.maps[1:]) - - def __setitem__(self, key, value): - self.maps[0][key] = value - - def __delitem__(self, key): - try: - del self.maps[0][key] - except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) - - def popitem(self): - 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' - try: - return self.maps[0].popitem() - except KeyError: - raise KeyError('No keys found in the first mapping.') - - def pop(self, key, *args): - 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' - try: - return self.maps[0].pop(key, *args) - except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) - - def clear(self): - 'Clear maps[0], leaving maps[1:] intact.' - self.maps[0].clear() diff -Nru python-pint-0.9/pint/compat/__init__.py python-pint-0.10.1/pint/compat/__init__.py --- python-pint-0.9/pint/compat/__init__.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/compat/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,139 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pint.compat - ~~~~~~~~~~~ - - Compatibility layer. - - :copyright: 2013 by Pint Authors, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" - -from __future__ import division, unicode_literals, print_function, absolute_import - -import sys - -from io import BytesIO -from numbers import Number -from decimal import Decimal - -from . import tokenize - -ENCODING_TOKEN = tokenize.ENCODING - -PYTHON3 = sys.version >= '3' - -def tokenizer(input_string): - for tokinfo in tokenize.tokenize(BytesIO(input_string.encode('utf-8')).readline): - if tokinfo.type == ENCODING_TOKEN: - continue - yield tokinfo - - -if PYTHON3: - string_types = str - - def u(x): - return x - - maketrans = str.maketrans - - long_type = int -else: - string_types = basestring - - import codecs - - def u(x): - return codecs.unicode_escape_decode(x)[0] - - maketrans = lambda f, t: dict((ord(a), b) for a, b in zip(f, t)) - - long_type = long - -try: - from collections import Chainmap -except ImportError: - from .chainmap import ChainMap - -try: - from functools import lru_cache -except ImportError: - from .lrucache import lru_cache - -try: - from itertools import zip_longest -except ImportError: - from itertools import izip_longest as zip_longest - -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): - if isinstance(value, (dict, bool)) or value is None: - raise TypeError('Invalid magnitude for Quantity: {0!r}'.format(value)) - elif isinstance(value, string_types) and value == '': - raise ValueError('Quantity magnitude cannot be an empty string.') - elif isinstance(value, (list, tuple)): - return np.asarray(value) - if force_ndarray: - return np.asarray(value) - return value - -except ImportError: - - np = None - - class ndarray(object): - pass - - HAS_NUMPY = False - NUMPY_VER = '0' - NUMERIC_TYPES = (Number, Decimal) - - def _to_magnitude(value, force_ndarray=False): - if isinstance(value, (dict, bool)) or value is None: - raise TypeError('Invalid magnitude for Quantity: {0!r}'.format(value)) - elif isinstance(value, string_types) 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 - HAS_BABEL = True - HAS_PROPER_BABEL = hasattr(babel_units, 'format_unit') -except ImportError: - HAS_PROPER_BABEL = HAS_BABEL = False - -if not HAS_PROPER_BABEL: - Loc = babel_units = None - -try: - import pandas as pd - HAS_PANDAS = True - # pin Pandas version for now - HAS_PROPER_PANDAS = pd.__version__.startswith("0.24.0.dev0+625.gbdb7a16") -except ImportError: - HAS_PROPER_PANDAS = HAS_PANDAS = False - -try: - import pytest - HAS_PYTEST = True -except ImportError: - HAS_PYTEST = False diff -Nru python-pint-0.9/pint/compat/lrucache.py python-pint-0.10.1/pint/compat/lrucache.py --- python-pint-0.9/pint/compat/lrucache.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/compat/lrucache.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,177 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pint.compat.lrucache - ~~~~~~~~~~~~~~~~~~~~ - - LRU (least recently used) cache backport. - - From https://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/ - - :copyright: 2004, Raymond Hettinger, - :license: MIT License -""" - -from collections import namedtuple -from functools import update_wrapper -from threading import RLock - -_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) - -class _HashedSeq(list): - __slots__ = 'hashvalue' - - def __init__(self, tup, hash=hash): - self[:] = tup - self.hashvalue = hash(tup) - - def __hash__(self): - return self.hashvalue - -def _make_key(args, kwds, typed, - kwd_mark = (object(),), - fasttypes = set((int, str, frozenset, type(None))), - sorted=sorted, tuple=tuple, type=type, len=len): - 'Make a cache key from optionally typed positional and keyword arguments' - key = args - if kwds: - sorted_items = sorted(kwds.items()) - key += kwd_mark - for item in sorted_items: - key += item - if typed: - key += tuple(type(v) for v in args) - if kwds: - key += tuple(type(v) for k, v in sorted_items) - elif len(key) == 1 and type(key[0]) in fasttypes: - return key[0] - return _HashedSeq(key) - -def lru_cache(maxsize=100, typed=False): - """Least-recently-used cache decorator. - - If *maxsize* is set to None, the LRU features are disabled and the cache - can grow without bound. - - If *typed* is True, arguments of different types will be cached separately. - For example, f(3.0) and f(3) will be treated as distinct calls with - distinct results. - - Arguments to the cached function must be hashable. - - View the cache statistics named tuple (hits, misses, maxsize, currsize) with - f.cache_info(). Clear the cache and statistics with f.cache_clear(). - Access the underlying function with f.__wrapped__. - - See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used - - """ - - # Users should only access the lru_cache through its public API: - # cache_info, cache_clear, and f.__wrapped__ - # The internals of the lru_cache are encapsulated for thread safety and - # to allow the implementation to change (including a possible C version). - - def decorating_function(user_function): - - cache = dict() - stats = [0, 0] # make statistics updateable non-locally - HITS, MISSES = 0, 1 # names for the stats fields - make_key = _make_key - cache_get = cache.get # bound method to lookup key or return None - _len = len # localize the global len() function - lock = RLock() # because linkedlist updates aren't threadsafe - root = [] # root of the circular doubly linked list - root[:] = [root, root, None, None] # initialize by pointing to self - nonlocal_root = [root] # make updateable non-locally - PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields - - if maxsize == 0: - - def wrapper(*args, **kwds): - # no caching, just do a statistics update after a successful call - result = user_function(*args, **kwds) - stats[MISSES] += 1 - return result - - elif maxsize is None: - - def wrapper(*args, **kwds): - # simple caching without ordering or size limit - key = make_key(args, kwds, typed) - result = cache_get(key, root) # root used here as a unique not-found sentinel - if result is not root: - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - cache[key] = result - stats[MISSES] += 1 - return result - - else: - - def wrapper(*args, **kwds): - # size limited caching that tracks accesses by recency - key = make_key(args, kwds, typed) if kwds or typed else args - with lock: - link = cache_get(key) - if link is not None: - # record recent use of the key by moving it to the front of the list - root, = nonlocal_root - link_prev, link_next, key, result = link - link_prev[NEXT] = link_next - link_next[PREV] = link_prev - last = root[PREV] - last[NEXT] = root[PREV] = link - link[PREV] = last - link[NEXT] = root - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - with lock: - root, = nonlocal_root - if key in cache: - # getting here means that this same key was added to the - # cache while the lock was released. since the link - # update is already done, we need only return the - # computed result and update the count of misses. - pass - elif _len(cache) >= maxsize: - # use the old root to store the new key and result - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - # empty the oldest link and make it the new root - root = nonlocal_root[0] = oldroot[NEXT] - oldkey = root[KEY] - oldvalue = root[RESULT] - root[KEY] = root[RESULT] = None - # now update the cache dictionary for the new links - del cache[oldkey] - cache[key] = oldroot - else: - # put result in a new link at the front of the list - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - stats[MISSES] += 1 - return result - - def cache_info(): - """Report cache statistics""" - with lock: - return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache)) - - def cache_clear(): - """Clear the cache and cache statistics""" - with lock: - cache.clear() - root = nonlocal_root[0] - root[:] = [root, root, None, None] - stats[:] = [0, 0] - - wrapper.__wrapped__ = user_function - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return update_wrapper(wrapper, user_function) - - return decorating_function diff -Nru python-pint-0.9/pint/compat/meta.py python-pint-0.10.1/pint/compat/meta.py --- python-pint-0.9/pint/compat/meta.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/compat/meta.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pint.compat.meta - ~~~~~~~~~~~~~~~~ - - Compatibility layer. - - :copyright: 2016 by Pint Authors, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - - # Taken from six - - class metaclass(meta): - - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) \ No newline at end of file diff -Nru python-pint-0.9/pint/compat/tokenize.py python-pint-0.10.1/pint/compat/tokenize.py --- python-pint-0.9/pint/compat/tokenize.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/compat/tokenize.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,640 +0,0 @@ -"""Tokenization help for Python programs. - -tokenize(readline) is a generator that breaks a stream of bytes into -Python tokens. It decodes the bytes according to PEP-0263 for -determining source file encoding. - -It accepts a readline-like method which is called repeatedly to get the -next line of input (or b"" for EOF). It generates 5-tuples with these -members: - - the token type (see token.py) - the token (a string) - the starting (row, column) indices of the token (a 2-tuple of ints) - the ending (row, column) indices of the token (a 2-tuple of ints) - the original line (string) - -It is designed to match the working of the Python tokenizer exactly, except -that it produces COMMENT tokens for comments and gives type OP for all -operators. Additionally, all token lists start with an ENCODING token -which tells you which encoding was used to decode the bytes stream. -""" - -__author__ = 'Ka-Ping Yee ' -__credits__ = ('GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, ' - 'Skip Montanaro, Raymond Hettinger, Trent Nelson, ' - 'Michael Foord') - -from codecs import lookup, BOM_UTF8 -import collections -import io -from io import TextIOWrapper -from itertools import chain -import re -import sys -from token import * - -try: - reASCII = re.ASCII -except: - reASCII = 0 - - -try: - unicode - _name_re = re.compile(r"\w*$", re.UNICODE) - def isidentifier(s): - if s[0] in '0123456789': - return False - return bool(_name_re.match(s)) -except NameError: - def isidentifier(s): - return s.isidentifier() - - -cookie_re = re.compile(r'^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)', reASCII) -blank_re = re.compile(br'^[ \t\f]*(?:[#\r\n]|$)', reASCII) - -COMMENT = N_TOKENS -tok_name[COMMENT] = 'COMMENT' -NL = N_TOKENS + 1 -tok_name[NL] = 'NL' -ENCODING = N_TOKENS + 2 -tok_name[ENCODING] = 'ENCODING' -N_TOKENS += 3 -EXACT_TOKEN_TYPES = { - '(': LPAR, - ')': RPAR, - '[': LSQB, - ']': RSQB, - ':': COLON, - ',': COMMA, - ';': SEMI, - '+': PLUS, - '-': MINUS, - '*': STAR, - '/': SLASH, - '|': VBAR, - '&': AMPER, - '<': LESS, - '>': GREATER, - '=': EQUAL, - '.': DOT, - '%': PERCENT, - '{': LBRACE, - '}': RBRACE, - '==': EQEQUAL, - '!=': NOTEQUAL, - '<=': LESSEQUAL, - '>=': GREATEREQUAL, - '~': TILDE, - '^': CIRCUMFLEX, - '<<': LEFTSHIFT, - '>>': RIGHTSHIFT, - '**': DOUBLESTAR, - '+=': PLUSEQUAL, - '-=': MINEQUAL, - '*=': STAREQUAL, - '/=': SLASHEQUAL, - '%=': PERCENTEQUAL, - '&=': AMPEREQUAL, - '|=': VBAREQUAL, - '^=': CIRCUMFLEXEQUAL, - '<<=': LEFTSHIFTEQUAL, - '>>=': RIGHTSHIFTEQUAL, - '**=': DOUBLESTAREQUAL, - '//': DOUBLESLASH, - '//=': DOUBLESLASHEQUAL, - '@': AT -} - -class TokenInfo(collections.namedtuple('TokenInfo', 'type string start end line')): - def __repr__(self): - annotated_type = '%d (%s)' % (self.type, tok_name[self.type]) - return ('TokenInfo(type=%s, string=%r, start=%r, end=%r, line=%r)' % - self._replace(type=annotated_type)) - - @property - def exact_type(self): - if self.type == OP and self.string in EXACT_TOKEN_TYPES: - return EXACT_TOKEN_TYPES[self.string] - else: - return self.type - -def group(*choices): return '(' + '|'.join(choices) + ')' -def any(*choices): return group(*choices) + '*' -def maybe(*choices): return group(*choices) + '?' - -# Note: we use unicode matching for names ("\w") but ascii matching for -# number literals. -Whitespace = r'[ \f\t]*' -Comment = r'#[^\r\n]*' -Ignore = Whitespace + any(r'\\\r?\n' + Whitespace) + maybe(Comment) -Name = r'\w+' - -Hexnumber = r'0[xX][0-9a-fA-F]+' -Binnumber = r'0[bB][01]+' -Octnumber = r'0[oO][0-7]+' -Decnumber = r'(?:0+|[1-9][0-9]*)' -Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber) -Exponent = r'[eE][-+]?[0-9]+' -Pointfloat = group(r'[0-9]+\.[0-9]*', r'\.[0-9]+') + maybe(Exponent) -Expfloat = r'[0-9]+' + Exponent -Floatnumber = group(Pointfloat, Expfloat) -Imagnumber = group(r'[0-9]+[jJ]', Floatnumber + r'[jJ]') -Number = group(Imagnumber, Floatnumber, Intnumber) - -StringPrefix = r'(?:[bB][rR]?|[rR][bB]?|[uU])?' - -# Tail end of ' string. -Single = r"[^'\\]*(?:\\.[^'\\]*)*'" -# Tail end of " string. -Double = r'[^"\\]*(?:\\.[^"\\]*)*"' -# Tail end of ''' string. -Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''" -# Tail end of """ string. -Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""' -Triple = group(StringPrefix + "'''", StringPrefix + '"""') -# Single-line ' or " string. -String = group(StringPrefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*'", - StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*"') - -# Because of leftmost-then-longest match semantics, be sure to put the -# longest operators first (e.g., if = came before ==, == would get -# recognized as two instances of =). -Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"!=", - r"//=?", r"->", - r"[+\-*/%&|^=<>]=?", - r"~") - -Bracket = '[][(){}]' -Special = group(r'\r?\n', r'\.\.\.', r'[:;.,@]') -Funny = group(Operator, Bracket, Special) - -PlainToken = group(Number, Funny, String, Name) -Token = Ignore + PlainToken - -# First (or only) line of ' or " string. -ContStr = group(StringPrefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*" + - group("'", r'\\\r?\n'), - StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*' + - group('"', r'\\\r?\n')) -PseudoExtras = group(r'\\\r?\n|\Z', Comment, Triple) -PseudoToken = Whitespace + group(PseudoExtras, Number, Funny, ContStr, Name) - -def _compile(expr): - return re.compile(expr, re.UNICODE) - -endpats = {"'": Single, '"': Double, - "'''": Single3, '"""': Double3, - "r'''": Single3, 'r"""': Double3, - "b'''": Single3, 'b"""': Double3, - "R'''": Single3, 'R"""': Double3, - "B'''": Single3, 'B"""': Double3, - "br'''": Single3, 'br"""': Double3, - "bR'''": Single3, 'bR"""': Double3, - "Br'''": Single3, 'Br"""': Double3, - "BR'''": Single3, 'BR"""': Double3, - "rb'''": Single3, 'rb"""': Double3, - "Rb'''": Single3, 'Rb"""': Double3, - "rB'''": Single3, 'rB"""': Double3, - "RB'''": Single3, 'RB"""': Double3, - "u'''": Single3, 'u"""': Double3, - "R'''": Single3, 'R"""': Double3, - "U'''": Single3, 'U"""': Double3, - 'r': None, 'R': None, 'b': None, 'B': None, - 'u': None, 'U': None} - -triple_quoted = {} -for t in ("'''", '"""', - "r'''", 'r"""', "R'''", 'R"""', - "b'''", 'b"""', "B'''", 'B"""', - "br'''", 'br"""', "Br'''", 'Br"""', - "bR'''", 'bR"""', "BR'''", 'BR"""', - "rb'''", 'rb"""', "rB'''", 'rB"""', - "Rb'''", 'Rb"""', "RB'''", 'RB"""', - "u'''", 'u"""', "U'''", 'U"""', - ): - triple_quoted[t] = t -single_quoted = {} -for t in ("'", '"', - "r'", 'r"', "R'", 'R"', - "b'", 'b"', "B'", 'B"', - "br'", 'br"', "Br'", 'Br"', - "bR'", 'bR"', "BR'", 'BR"' , - "rb'", 'rb"', "rB'", 'rB"', - "Rb'", 'Rb"', "RB'", 'RB"' , - "u'", 'u"', "U'", 'U"', - ): - single_quoted[t] = t - -tabsize = 8 - -class TokenError(Exception): pass - -class StopTokenizing(Exception): pass - - -class Untokenizer: - - def __init__(self): - self.tokens = [] - self.prev_row = 1 - self.prev_col = 0 - self.encoding = None - - def add_whitespace(self, start): - row, col = start - if row < self.prev_row or row == self.prev_row and col < self.prev_col: - raise ValueError("start ({},{}) precedes previous end ({},{})" - .format(row, col, self.prev_row, self.prev_col)) - row_offset = row - self.prev_row - if row_offset: - self.tokens.append("\\\n" * row_offset) - self.prev_col = 0 - col_offset = col - self.prev_col - if col_offset: - self.tokens.append(" " * col_offset) - - def untokenize(self, iterable): - it = iter(iterable) - for t in it: - if len(t) == 2: - self.compat(t, it) - break - tok_type, token, start, end, line = t - if tok_type == ENCODING: - self.encoding = token - continue - if tok_type == ENDMARKER: - break - self.add_whitespace(start) - self.tokens.append(token) - self.prev_row, self.prev_col = end - if tok_type in (NEWLINE, NL): - self.prev_row += 1 - self.prev_col = 0 - return "".join(self.tokens) - - def compat(self, token, iterable): - indents = [] - toks_append = self.tokens.append - startline = token[0] in (NEWLINE, NL) - prevstring = False - - for tok in chain([token], iterable): - toknum, tokval = tok[:2] - if toknum == ENCODING: - self.encoding = tokval - continue - - if toknum in (NAME, NUMBER): - tokval += ' ' - - # Insert a space between two consecutive strings - if toknum == STRING: - if prevstring: - tokval = ' ' + tokval - prevstring = True - else: - prevstring = False - - if toknum == INDENT: - indents.append(tokval) - continue - elif toknum == DEDENT: - indents.pop() - continue - elif toknum in (NEWLINE, NL): - startline = True - elif startline and indents: - toks_append(indents[-1]) - startline = False - toks_append(tokval) - - -def untokenize(iterable): - """Transform tokens back into Python source code. - It returns a bytes object, encoded using the ENCODING - token, which is the first token sequence output by tokenize. - - Each element returned by the iterable must be a token sequence - with at least two elements, a token number and token value. If - only two tokens are passed, the resulting output is poor. - - Round-trip invariant for full input: - Untokenized source will match input source exactly - - Round-trip invariant for limited intput: - # Output bytes will tokenize the back to the input - t1 = [tok[:2] for tok in tokenize(f.readline)] - newcode = untokenize(t1) - readline = BytesIO(newcode).readline - t2 = [tok[:2] for tok in tokenize(readline)] - assert t1 == t2 - """ - ut = Untokenizer() - out = ut.untokenize(iterable) - if ut.encoding is not None: - out = out.encode(ut.encoding) - return out - - -def _get_normal_name(orig_enc): - """Imitates get_normal_name in tokenizer.c.""" - # Only care about the first 12 characters. - enc = orig_enc[:12].lower().replace("_", "-") - if enc == "utf-8" or enc.startswith("utf-8-"): - return "utf-8" - if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \ - enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")): - return "iso-8859-1" - return orig_enc - -def detect_encoding(readline): - """ - The detect_encoding() function is used to detect the encoding that should - be used to decode a Python source file. It requires one argument, readline, - in the same way as the tokenize() generator. - - It will call readline a maximum of twice, and return the encoding used - (as a string) and a list of any lines (left as bytes) it has read in. - - It detects the encoding from the presence of a utf-8 bom or an encoding - cookie as specified in pep-0263. If both a bom and a cookie are present, - but disagree, a SyntaxError will be raised. If the encoding cookie is an - invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found, - 'utf-8-sig' is returned. - - If no encoding is specified, then the default of 'utf-8' will be returned. - """ - try: - filename = readline.__self__.name - except AttributeError: - filename = None - bom_found = False - encoding = None - default = 'utf-8' - def read_or_stop(): - try: - return readline() - except StopIteration: - return b'' - - def find_cookie(line): - try: - # Decode as UTF-8. Either the line is an encoding declaration, - # in which case it should be pure ASCII, or it must be UTF-8 - # per default encoding. - line_string = line.decode('utf-8') - except UnicodeDecodeError: - msg = "invalid or missing encoding declaration" - if filename is not None: - msg = '{} for {!r}'.format(msg, filename) - raise SyntaxError(msg) - - match = cookie_re.match(line_string) - if not match: - return None - encoding = _get_normal_name(match.group(1)) - try: - codec = lookup(encoding) - except LookupError: - # This behaviour mimics the Python interpreter - if filename is None: - msg = "unknown encoding: " + encoding - else: - msg = "unknown encoding for {!r}: {}".format(filename, - encoding) - raise SyntaxError(msg) - - if bom_found: - if encoding != 'utf-8': - # This behaviour mimics the Python interpreter - if filename is None: - msg = 'encoding problem: utf-8' - else: - msg = 'encoding problem for {!r}: utf-8'.format(filename) - raise SyntaxError(msg) - encoding += '-sig' - return encoding - - first = read_or_stop() - if first.startswith(BOM_UTF8): - bom_found = True - first = first[3:] - default = 'utf-8-sig' - if not first: - return default, [] - - encoding = find_cookie(first) - if encoding: - return encoding, [first] - if not blank_re.match(first): - return default, [first] - - second = read_or_stop() - if not second: - return default, [first] - - encoding = find_cookie(second) - if encoding: - return encoding, [first, second] - - return default, [first, second] - - -def open(filename): - """Open a file in read only mode using the encoding detected by - detect_encoding(). - """ - buffer = io.open(filename, 'rb') - encoding, lines = detect_encoding(buffer.readline) - buffer.seek(0) - text = TextIOWrapper(buffer, encoding, line_buffering=True) - text.mode = 'r' - return text - - -def tokenize(readline): - """ - The tokenize() generator requires one argment, readline, which - must be a callable object which provides the same interface as the - readline() method of built-in file objects. Each call to the function - should return one line of input as bytes. Alternately, readline - can be a callable function terminating with StopIteration: - readline = open(myfile, 'rb').__next__ # Example of alternate readline - - The generator produces 5-tuples with these members: the token type; the - token string; a 2-tuple (srow, scol) of ints specifying the row and - column where the token begins in the source; a 2-tuple (erow, ecol) of - ints specifying the row and column where the token ends in the source; - and the line on which the token was found. The line passed is the - logical line; continuation lines are included. - - The first token sequence will always be an ENCODING token - which tells you which encoding was used to decode the bytes stream. - """ - # This import is here to avoid problems when the itertools module is not - # built yet and tokenize is imported. - from itertools import chain, repeat - encoding, consumed = detect_encoding(readline) - rl_gen = iter(readline, b"") - empty = repeat(b"") - - try: - return _tokenize(chain(consumed, rl_gen, empty).__next__, encoding) - except AttributeError: - return _tokenize(chain(consumed, rl_gen, empty).next, encoding) - - -def _tokenize(readline, encoding): - lnum = parenlev = continued = 0 - numchars = '0123456789' - contstr, needcont = '', 0 - contline = None - indents = [0] - - if encoding is not None: - if encoding == "utf-8-sig": - # BOM will already have been stripped. - encoding = "utf-8" - yield TokenInfo(ENCODING, encoding, (0, 0), (0, 0), '') - while True: # loop over lines in stream - try: - line = readline() - except StopIteration: - line = b'' - - if encoding is not None: - line = line.decode(encoding) - lnum += 1 - pos, max = 0, len(line) - - if contstr: # continued string - if not line: - raise TokenError("EOF in multi-line string", strstart) - endmatch = endprog.match(line) - if endmatch: - pos = end = endmatch.end(0) - yield TokenInfo(STRING, contstr + line[:end], - strstart, (lnum, end), contline + line) - contstr, needcont = '', 0 - contline = None - elif needcont and line[-2:] != '\\\n' and line[-3:] != '\\\r\n': - yield TokenInfo(ERRORTOKEN, contstr + line, - strstart, (lnum, len(line)), contline) - contstr = '' - contline = None - continue - else: - contstr = contstr + line - contline = contline + line - continue - - elif parenlev == 0 and not continued: # new statement - if not line: break - column = 0 - while pos < max: # measure leading whitespace - if line[pos] == ' ': - column += 1 - elif line[pos] == '\t': - column = (column//tabsize + 1)*tabsize - elif line[pos] == '\f': - column = 0 - else: - break - pos += 1 - if pos == max: - break - - if line[pos] in '#\r\n': # skip comments or blank lines - if line[pos] == '#': - comment_token = line[pos:].rstrip('\r\n') - nl_pos = pos + len(comment_token) - yield TokenInfo(COMMENT, comment_token, - (lnum, pos), (lnum, pos + len(comment_token)), line) - yield TokenInfo(NL, line[nl_pos:], - (lnum, nl_pos), (lnum, len(line)), line) - else: - yield TokenInfo((NL, COMMENT)[line[pos] == '#'], line[pos:], - (lnum, pos), (lnum, len(line)), line) - continue - - if column > indents[-1]: # count indents or dedents - indents.append(column) - yield TokenInfo(INDENT, line[:pos], (lnum, 0), (lnum, pos), line) - while column < indents[-1]: - if column not in indents: - raise IndentationError( - "unindent does not match any outer indentation level", - ("", lnum, pos, line)) - indents = indents[:-1] - yield TokenInfo(DEDENT, '', (lnum, pos), (lnum, pos), line) - - else: # continued statement - if not line: - raise TokenError("EOF in multi-line statement", (lnum, 0)) - continued = 0 - - while pos < max: - pseudomatch = _compile(PseudoToken).match(line, pos) - if pseudomatch: # scan for tokens - start, end = pseudomatch.span(1) - spos, epos, pos = (lnum, start), (lnum, end), end - if start == end: - continue - token, initial = line[start:end], line[start] - - if (initial in numchars or # ordinary number - (initial == '.' and token != '.' and token != '...')): - yield TokenInfo(NUMBER, token, spos, epos, line) - elif initial in '\r\n': - yield TokenInfo(NL if parenlev > 0 else NEWLINE, - token, spos, epos, line) - elif initial == '#': - assert not token.endswith("\n") - yield TokenInfo(COMMENT, token, spos, epos, line) - elif token in triple_quoted: - endprog = _compile(endpats[token]) - endmatch = endprog.match(line, pos) - if endmatch: # all on one line - pos = endmatch.end(0) - token = line[start:pos] - yield TokenInfo(STRING, token, spos, (lnum, pos), line) - else: - strstart = (lnum, start) # multiple lines - contstr = line[start:] - contline = line - break - elif initial in single_quoted or \ - token[:2] in single_quoted or \ - token[:3] in single_quoted: - if token[-1] == '\n': # continued string - strstart = (lnum, start) - endprog = _compile(endpats[initial] or - endpats[token[1]] or - endpats[token[2]]) - contstr, needcont = line[start:], 1 - contline = line - break - else: # ordinary string - yield TokenInfo(STRING, token, spos, epos, line) - elif isidentifier(initial): # ordinary name - yield TokenInfo(NAME, token, spos, epos, line) - elif initial == '\\': # continued stmt - continued = 1 - else: - if initial in '([{': - parenlev += 1 - elif initial in ')]}': - parenlev -= 1 - yield TokenInfo(OP, token, spos, epos, line) - else: - yield TokenInfo(ERRORTOKEN, line[pos], - (lnum, pos), (lnum, pos+1), line) - pos += 1 - - for indent in indents[1:]: # pop remaining indent levels - yield TokenInfo(DEDENT, '', (lnum, 0), (lnum, 0), '') - yield TokenInfo(ENDMARKER, '', (lnum, 0), (lnum, 0), '') diff -Nru python-pint-0.9/pint/compat.py python-pint-0.10.1/pint/compat.py --- python-pint-0.9/pint/compat.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/pint/compat.py 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,239 @@ +""" + 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 diff -Nru python-pint-0.9/pint/constants_en.txt python-pint-0.10.1/pint/constants_en.txt --- python-pint-0.9/pint/constants_en.txt 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/constants_en.txt 2020-01-08 05:47:17.000000000 +0000 @@ -1,51 +1,73 @@ # Default Pint constants definition file # Based on the International System of Units # Language: english -# Source: http://physics.nist.gov/cuu/Constants/Table/allascii.txt -# :copyright: 2013 by Pint Authors, see AUTHORS for more details. - -speed_of_light = 299792458 * meter / second = c -standard_gravity = 9.806650 * meter / second ** 2 = g_0 = g_n = gravity -vacuum_permeability = 4 * pi * 1e-7 * newton / ampere ** 2 = mu_0 = magnetic_constant -vacuum_permittivity = 1 / (mu_0 * c **2 ) = epsilon_0 = electric_constant -Z_0 = mu_0 * c = impedance_of_free_space = characteristic_impedance_of_vacuum - -# 0.000 000 29 e-34 -planck_constant = 6.62606957e-34 J s = h -hbar = planck_constant / (2 * pi) = ħ - -# 0.000 80 e-11 -newtonian_constant_of_gravitation = 6.67384e-11 m^3 kg^-1 s^-2 - -# 0.000 000 035 e-19 -# elementary_charge = 1.602176565e-19 C = e - -# 0.000 0075 -molar_gas_constant = 8.3144621 J mol^-1 K^-1 = R - -# 0.000 000 0024 e-3 -fine_structure_constant = 7.2973525698e-3 - -# 0.000 000 27 e23 -avogadro_number = 6.02214129e23 mol^-1 =N_A - -# 0.000 0013 e-23 -boltzmann_constant = 1.3806488e-23 J K^-1 = k - -# 0.000 021 e-8 -stefan_boltzmann_constant = 5.670373e-8 W m^-2 K^-4 = σ - -# 0.000 0053 e10 -wien_frequency_displacement_law_constant = 5.8789254e10 Hz K^-1 - -# 0.000 055 -rydberg_constant = 10973731.568539 m^-1 - -# 0.000 000 40 e-31 -electron_mass = 9.10938291e-31 kg = m_e - -# 0.000 000 074 e-27 -neutron_mass = 1.674927351e-27 kg = m_n - -# 0.000 000 074 e-27 -proton_mass = 1.672621777e-27 kg = m_p +# 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 diff -Nru python-pint-0.9/pint/context.py python-pint-0.10.1/pint/context.py --- python-pint-0.9/pint/context.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/context.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.context ~~~~~~~~~~~~ @@ -9,60 +8,81 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import - - import re -from collections import defaultdict import weakref +from collections import ChainMap, defaultdict -from .compat import ChainMap -from .util import (ParserHelper, UnitsContainer, string_types, - to_units_container, SourceIterator) +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('@context\s*(?P\(.*\))?\s+(?P\w+)\s*(=(?P.*))*') +_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('[A-Za-z_][A-Za-z0-9_]*') +_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(object): +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. - >>> 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 Conversion functions may take optional keyword arguments and the context can have default values for these arguments. - >>> 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 + 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, aliases=(), defaults=None): + def __init__(self, name=None, aliases=(), defaults=None): self.name = name self.aliases = aliases @@ -73,6 +93,12 @@ #: 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() @@ -84,12 +110,24 @@ 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 - for edge in context.funcs.keys(): + c.redefinitions = context.redefinitions + for edge in context.funcs: c.relation_to_context[edge] = c return c return context @@ -101,32 +139,35 @@ lineno, header = next(lines) try: r = _header_re.search(header) - name = r.groupdict()['name'].strip() - aliases = r.groupdict()['aliases'] + name = r.groupdict()["name"].strip() + aliases = r.groupdict()["aliases"] if aliases: - aliases = tuple(a.strip() for a in r.groupdict()['aliases'].split('=')) + aliases = tuple(a.strip() for a in r.groupdict()["aliases"].split("=")) else: aliases = () - defaults = r.groupdict()['defaults'] - except: - raise DefinitionSyntaxError("Could not parse the Context header '%s'" % header, - lineno=lineno) + 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 + txt = defaults try: - defaults = (part.split('=') for part in defaults.strip('()').split(',')) - defaults = dict((str(k).strip(), to_num(v)) - for k, v in defaults) - except (ValueError, TypeError): - raise DefinitionSyntaxError("Could not parse Context definition defaults: '%s'", _txt, - lineno=lineno) + 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: @@ -134,43 +175,50 @@ names = set() for lineno, line in lines: + if "=" in line: + ctx.redefine(line) + continue + try: - rel, eq = line.split(':') + 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 "<->" 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('->')) + 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: - raise DefinitionSyntaxError("Could not parse Context %s relation '%s'" % (name, line), - lineno=lineno) + except Exception as exc: + raise DefinitionSyntaxError( + "Could not parse Context %s relation '%s'" % (name, line), + lineno=lineno, + ) from exc if defaults: - missing_pars = set(defaults.keys()).difference(set(names)) + missing_pars = defaults.keys() - set(names) if missing_pars: - raise DefinitionSyntaxError('Context parameters {} not found in any equation.'.format(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 @@ -178,6 +226,7 @@ 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] @@ -189,19 +238,61 @@ 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, *args, **kwargs): - super(ContextChain, self).__init__(*args, **kwargs) + def __init__(self): + super().__init__() + self.contexts = [] + self.maps.clear() # Remove default empty map self._graph = None - self._contexts = [] def insert_contexts(self, *contexts): """Insert one or more contexts in reversed order the chained map. @@ -210,27 +301,33 @@ To facilitate the identification of the context with the matching rule, the *relation_to_context* dictionary of the context is used. """ - self._contexts.insert(0, contexts) + + 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): + def remove_contexts(self, n: int = None): """Remove the last n inserted contexts from the chain. + + Parameters + ---------- + n: int + (Default value = None) """ - self._contexts = self._contexts[n:] - self.maps = self.maps[n:] + + del self.contexts[:n] + del self.maps[:n] self._graph = None @property def defaults(self): if self: - return list(self.maps[0].values())[0].defaults + return next(iter(self.maps[0].values())).defaults return {} @property def graph(self): - """The graph relating - """ + """The graph relating""" if self._graph is None: self._graph = defaultdict(set) for fr_, to_ in self: @@ -240,7 +337,12 @@ 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) - - :raises: KeyError if the rule is not found. """ 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) diff -Nru python-pint-0.9/pint/converters.py python-pint-0.10.1/pint/converters.py --- python-pint-0.9/pint/converters.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/converters.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,20 +1,16 @@ -# -*- coding: utf-8 -*- """ pint.converters - ~~~~~~~~~ + ~~~~~~~~~~~~~~~ Functions and classes related to unit conversions. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ -from __future__ import (division, unicode_literals, print_function, - absolute_import) -class Converter(object): - """Base class for value converters. - """ +class Converter: + """Base class for value converters.""" is_multiplicative = True @@ -26,8 +22,7 @@ class ScaleConverter(Converter): - """A linear transformation - """ + """A linear transformation.""" is_multiplicative = True @@ -52,8 +47,7 @@ class OffsetConverter(Converter): - """An affine transformation - """ + """An affine transformation.""" def __init__(self, scale, offset): self.scale = scale diff -Nru python-pint-0.9/pint/default_en.txt python-pint-0.10.1/pint/default_en.txt --- python-pint-0.9/pint/default_en.txt 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/default_en.txt 2020-01-08 05:47:17.000000000 +0000 @@ -1,13 +1,66 @@ # Default Pint units definition file # Based on the International System of Units # Language: english -# :copyright: 2013 by Pint Authors, see AUTHORS for more details. +# :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- @@ -15,11 +68,11 @@ femto- = 1e-15 = f- pico- = 1e-12 = p- nano- = 1e-9 = n- -micro- = 1e-6 = u- = µ- +micro- = 1e-6 = µ- = u- milli- = 1e-3 = m- centi- = 1e-2 = c- deci- = 1e-1 = d- -deca- = 1e+1 = da- = deka +deca- = 1e+1 = da- = deka- hecto- = 1e2 = h- kilo- = 1e3 = k- mega- = 1e6 = M- @@ -40,89 +93,150 @@ zebi- = 2**70 = Zi- yobi- = 2**80 = Yi- -# reference +# 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 +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 -# acceleration -[acceleration] = [length] / [time] ** 2 + +#### 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 * 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 +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 * m**2 -barn = 1e-28 * m ** 2 = b -cmil = 5.067075e-10 * m ** 2 = circular_mils -darcy = 9.869233e-13 * m ** 2 +are = 100 * meter ** 2 +barn = 1e-28 * meter ** 2 = b +darcy = centipoise * centimeter ** 2 / (second * atmosphere) hectare = 100 * are = ha -# Concentration -[concentration] = [substance] / [volume] -molar = mol / (1e-3 * m ** 3) = M +# Volume +[volume] = [length] ** 3 +liter = decimeter ** 3 = l = L = litre +cubic_centimeter = centimeter ** 3 = cc +lambda = microliter = λ +stere = meter ** 3 -# Activity -[activity] = [substance] / [time] -katal = mole / second = kat +# 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 -# 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 +# Wavenumber +[wavenumber] = 1 / [length] +reciprocal_centimeter = 1 / cm = cm_1 = kayser -# 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 -calorie = 4.184 * joule = cal = 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 = = Eh = E_h = hartree_energy -toe = 41.868e9 * joule = tonne_of_oil_equivalent +# 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] @@ -130,265 +244,298 @@ 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 +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 -# 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 = Å -parsec = 3.08568025e16 * meter = pc -light_year = speed_of_light * julian_year = ly = lightyear -astronomical_unit = 149597870691 * meter = au - -# Mass -carat = 200 * milligram -metric_ton = 1000 * kilogram = t = tonne -atomic_mass_unit = 1.660538782e-27 * kilogram = u = amu = dalton = Da -bag = 94 * lb - -# Textile -denier = gram / (9000 * meter) -tex = gram / (1000 * meter) -dtex = decitex - -# These are Indirect yarn numbering systems (length/unit mass) -jute = lb / (14400 * yd) = Tj -Ne = 1/590.5 / tex -Nm = 1/1000 / tex - -@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 - -# Photometry -lumen = candela * steradian = lm -lux = lumen / meter ** 2 = lx +# 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 = 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 +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] -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 +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 = pound * gravity / inch ** 2 = psi +pound_force_per_square_inch = force_pound / 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 +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 -# Radiation -Bq = Hz = becquerel -curie = 3.7e10 * Bq = Ci -rutherford = 1e6*Bq = Rd -Gy = joule / kilogram = gray = Sv = sievert -rem = 1e-2 * sievert -rads = 1e-2 * gray -roentgen = 2.58e-4 * coulomb / kilogram +# Viscosity +[viscosity] = [pressure] * [time] +poise = 0.1 * Pa * second = P +reyn = psi * second -# Temperature -degC = kelvin; offset: 273.15 = celsius -degR = 5 / 9 * kelvin; offset: 0 = rankine -degF = 5 / 9 * kelvin; offset: 255.372222 = fahrenheit +# Kinematic viscosity +[kinematic_viscosity] = [area] / [time] +stokes = centimeter ** 2 / second = St + +# Fluidity +[fluidity] = 1 / [viscosity] +rhe = 1 / poise -# 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 +# Amount of substance +particle = 1 / N_A = _ = molec = molecule -# Velocity -[speed] = [length] / [time] -nautical_mile = 1852 m = nmi # exact -knot = nautical_mile / hour = kt = knot_international = international_knot = nautical_miles_per_hour -mph = mile / hour = MPH -kph = kilometer / hour = KPH +# Concentration +[concentration] = [substance] / [volume] +molar = mole / liter = M -# Viscosity -[viscosity] = [pressure] * [time] -poise = 1e-1 * Pa * second = P -stokes = 1e-4 * meter ** 2 / second = St -rhe = 10 / (Pa * s) +# Catalytic activity +[activity] = [substance] / [time] +katal = mole / second = kat +enzyme_unit = micromole / minute = U = enzymeunit -# Volume -[volume] = [length] ** 3 -liter = 1e-3 * m ** 3 = l = L = litre -cc = centimeter ** 3 = cubic_centimeter -stere = meter ** 3 +# 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 -@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 - 1 / [length] <-> [length]: 1 / value -@end +# Luminous flux +[luminous_flux] = [luminosity] +lumen = candela * steradian = lm -@context boltzmann - [temperature] -> [energy]: boltzmann_constant * value - [energy] -> [temperature]: value / boltzmann_constant -@end +# Illuminance +[illuminance] = [luminous_flux] / [area] +lux = lumen / meter ** 2 = lx -@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 +# 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 - # moles -> mass require the molecular weight - [substance] -> [mass]: value * mw - [mass] -> [substance]: value / mw +# 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 - # 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 +# 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 - # moles/volume -> moles requires the solution volume - [substance] / [volume] -> [substance]: value * volume - [substance] -> [substance] / [volume]: value / volume +# Electric displacement field +[electric_displacement_field] = [charge] / [area] - # moles/mass -> moles requires the solvent (usually water) mass - [substance] / [mass] -> [substance]: value * solvent_mass - [substance] -> [substance] / [mass]: value / solvent_mass +# 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 - # 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 +# Resistivity +[resistivity] = [resistance] * [length] -@end +# 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) -# Most of the definitions that follows are derived from: -# See http://www.nist.gov/pml/wmd/pubs/hb44.cfm @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 metres = yd = international_yard - mile = 1760 yard = mi = international_mile - - square_inch = 1 inch ** 2 = sq_in = square_inches - square_foot = 1 foot ** 2 = sq_ft = square_feet - square_yard = 1 yard ** 2 = sq_yd - square_mile = 1 mile ** 2 = sq_mi - - cubic_inch = 1 in ** 3 = cu_in - cubic_foot = 1 ft ** 3 = cu_ft = cubic_feet - cubic_yard = 1 yd ** 3 = cu_yd + yard = 0.9144 * meter = yd = international_yard # since Jul 1959 + mile = 1760 * yard = mi = international_mile - acre_foot = acre * foot = acre_feet + 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 = 0.66 survey_foot = li = survey_link - survey_foot = foot / 0.999998 = sft - rod = 16.5 survey_foot = rd = pole = perch - chain = 66 survey_foot - survey_mile = 5280 survey_foot - - acre = 43560 survey_foot ** 2 - square_rod = 1 rod ** 2 = sq_rod = sq_pole = sq_perch - - fathom = 6 survey_foot - us_statute_mile = 5280 survey_foot - league = 3 us_statute_mile - furlong = us_statute_mile / 8 + 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 = 33.6003125 cubic_inch = dpi = US_dry_pint - dry_quart = 2 dry_pint = dqt = US_dry_quart - dry_gallon = 8 dry_pint = dgal = US_dry_gallon - peck = 16 dry_pint = pk - bushel = 64 dry_pint = bu - dry_barrel = 7065 cubic_inch = US_dry_barrel + 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 = liquid_pint / 7680 - fluid_dram = liquid_pint / 128 = fldr = fluidram = US_fluid_dram - fluid_ounce = liquid_pint / 16 = floz = US_fluid_ounce = US_liquid_ounce - gill = liquid_pint / 4 = gi = liquid_gill = US_liquid_gill - - pint = 28.875 cubic_inch = pt = liquid_pint = US_pint - - quart = 2 liquid_pint = qt = liquid_quart = US_liquid_quart - gallon = 8 liquid_pint = gal = liquid_gallon = US_liquid_gallon + 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 = tablespoon / 3 = tsp - tablespoon = floz / 2 = tbsp = Tbsp = Tblsp = tblsp = tbs = Tbl + teaspoon = fluid_ounce / 6 = tsp + tablespoon = fluid_ounce / 2 = tbsp shot = 3 * tablespoon = jig = US_shot - cup = 8 fluid_ounce = cp = liquid_cup = US_liquid_cup + 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 @@ -396,65 +543,265 @@ @end @group Avoirdupois - grain = avdp_pound / 7000 = gr - drachm = pound / 256 = dr = avoirdupois_dram = avdp_dram = dram + dram = pound / 256 = dr = avoirdupois_dram = avdp_dram = drachm ounce = pound / 16 = oz = avoirdupois_ounce = avdp_ounce - pound = 453.59237 gram = lb = avoirdupois_pound = avdp_pound + 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 - short_hunderdweight = 100 avoirdupois_pound = ch_cwt - long_hunderweight = 112 avoirdupois_pound = lg_cwt - short_ton = 2000 avoirdupois_pound - long_ton = 2240 avoirdupois_pound +@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 Troy - pennyweight = 24 grain = dwt - troy_ounce = 480 grain = toz - troy_pound = 12 troy_ounce = tlb +@group AvoirdupoisUS using Avoirdupois + US_hundredweight = hundredweight = US_cwt + US_ton = ton + US_force_ton = force_ton = _ = US_ton_force @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 +@group Troy + pennyweight = 24 * grain = dwt + troy_ounce = 480 * grain = toz = ozt + troy_pound = 12 * troy_ounce = tlb = lbt @end -@group AvoirdupoisUK using Avoirdupois - stone = 14 pound - quarter = 28 stone - UK_hundredweight = long_hunderweight = UK_cwt - UK_ton = long_ton +@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 AvoirdupoisUS using Avoirdupois - US_hundredweight = short_hunderdweight = US_cwt - US_ton = short_ton = ton +@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 - # Length - pixel = [printing_unit] = dot = px = pel = picture_element + 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 - point = yard / 216 / 12 = pp = printers_point - thou = yard / 36000 = th = mil - pica = yard / 216 = P̸ = printers_pica +@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 -@group ImperialVolume - imperial_fluid_ounce = imperial_pint / 20 = imperial_floz = UK_fluid_ounce - imperial_fluid_drachm = imperial_fluid_ounce / 8 = imperial_fluid_dram - imperial_gill = imperial_pint / 4 = imperial_gi = UK_gill - imperial_cup = imperial_pint / 2 = imperial_cp = UK_cup - imperial_pint = 568.26125 * milliliter = imperial_pt = UK_pint - imperial_quart = 2 * imperial_pint = imperial_qt = UK_quart - imperial_gallon = 8 * imperial_pint = imperial_gal = UK_gallon - imperial_peck = 16 * imperial_pint = imperial_pk = UK_pk - imperial_bushel = 64 * imperial_pint = imperial_bu = UK_bushel - imperial_barrel = 288 * imperial_pint = imperial_bbl = UK_bbl + +#### 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 @@ -463,12 +810,30 @@ second @end -@system cgs using international +@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 diff -Nru python-pint-0.9/pint/definitions.py python-pint-0.10.1/pint/definitions.py --- python-pint-0.9/pint/definitions.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/definitions.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- """ pint.definitions - ~~~~~~~~~ + ~~~~~~~~~~~~~~~~ Functions and classes related to unit definitions. @@ -9,21 +8,38 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import (division, unicode_literals, print_function, - absolute_import) +from .converters import OffsetConverter, ScaleConverter +from .errors import DefinitionSyntaxError +from .util import ParserHelper, UnitsContainer, _is_dim -from .converters import ScaleConverter, OffsetConverter -from .util import UnitsContainer, _is_dim, ParserHelper -from .compat import string_types +class _NotNumeric(Exception): + def __init__(self, value): + self.value = value -class Definition(object): + +def numeric_parse(s): + ph = ParserHelper.from_string(s) + + if len(ph): + raise _NotNumeric(s) + + return ph.scale + + +class Definition: """Base class for definitions. - :param name: name. - :param symbol: a short name or symbol for the definition - :param aliases: iterable of other names. - :param converter: an instance of Converter. + 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): @@ -39,19 +55,36 @@ @classmethod def from_string(cls, definition): """Parse a definition + + Parameters + ---------- + definition : + + + Returns + ------- + """ - name, definition = definition.split('=', 1) + name, definition = definition.split("=", 1) name = name.strip() - result = [res.strip() for res in definition.split('=')] - value, aliases = result[0], tuple(result[1:]) - symbol, aliases = (aliases[0], aliases[1:]) if aliases else (None, - aliases) + result = [res.strip() for res in definition.split("=")] - if name.startswith('['): + # @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('-') + elif name.endswith("-"): + name = name.rstrip("-") return PrefixDefinition(name, symbol, aliases, value) else: return UnitDefinition(name, symbol, aliases, value) @@ -72,6 +105,10 @@ 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 @@ -81,77 +118,102 @@ class PrefixDefinition(Definition): - """Definition of a prefix. - """ + """Definition of a prefix.""" def __init__(self, name, symbol, aliases, converter): - if isinstance(converter, string_types): - converter = ScaleConverter(eval(converter)) - aliases = tuple(alias.strip('-') for alias in aliases) + 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(PrefixDefinition, self).__init__(name, symbol, aliases, - converter) + symbol = symbol.strip("-") + super().__init__(name, symbol, aliases, converter) class UnitDefinition(Definition): """Definition of a unit. - :param reference: Units container with reference units. - :param is_base: indicates if it is a base 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): + def __init__(self, name, symbol, aliases, converter, reference=None, is_base=False): self.reference = reference self.is_base = is_base - if isinstance(converter, string_types): - if ';' in converter: - [converter, modifiers] = converter.split(';', 2) - modifiers = dict((key.strip(), eval(value)) for key, value in - (part.split(':') - for part in modifiers.split(';'))) + 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 all(_is_dim(key) for key in converter.keys()): - self.is_base = True - elif not any(_is_dim(key) for key in converter.keys()): + 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 ValueError('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.') + 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.: - converter = OffsetConverter(converter.scale, - modifiers['offset']) + if modifiers.get("offset", 0.0) != 0.0: + converter = OffsetConverter(converter.scale, modifiers["offset"]) else: converter = ScaleConverter(converter.scale) - super(UnitDefinition, self).__init__(name, symbol, aliases, converter) + super().__init__(name, symbol, aliases, converter) class DimensionDefinition(Definition): - """Definition of a dimension. - """ + """Definition of a dimension.""" - def __init__(self, name, symbol, aliases, converter, - reference=None, is_base=False): + def __init__(self, name, symbol, aliases, converter, reference=None, is_base=False): self.reference = reference self.is_base = is_base - if isinstance(converter, string_types): + 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 ValueError('Base dimensions must be referenced to None. ' - 'Derived dimensions must only be referenced ' - 'to dimensions.') + raise DefinitionSyntaxError( + "Base dimensions must be referenced to None. " + "Derived dimensions must only be referenced " + "to dimensions." + ) self.reference = UnitsContainer(converter) - super(DimensionDefinition, self).__init__(name, symbol, aliases, - converter=None) + 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) diff -Nru python-pint-0.9/pint/errors.py python-pint-0.10.1/pint/errors.py --- python-pint-0.9/pint/errors.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/errors.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,83 +1,86 @@ -# -*- coding: utf-8 -*- """ pint.errors - ~~~~~~~~~ + ~~~~~~~~~~~ Functions and classes related to unit definitions and conversions. :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ -from __future__ import (division, unicode_literals, print_function, - absolute_import) -from .compat import string_types +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 -class DefinitionSyntaxError(ValueError): - """Raised when a textual definition has a syntax error. - """ + def __str__(self): + return _file_prefix(self.filename, self.lineno) + str(self.args[0]) - def __init__(self, msg, filename=None, lineno=None): - super(DefinitionSyntaxError, self).__init__() - self.msg = msg - self.filename = None - self.lineno = None + @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 __str__(self): - mess = "While opening {}, in line {}: " - return mess.format(self.filename, self.lineno) + self.msg + def __reduce__(self): + return DefinitionSyntaxError, self.args, self.__dict__ class RedefinitionError(ValueError): - """Raised when a unit or prefix is redefined. - """ + """Raised when a unit or prefix is redefined.""" - def __init__(self, name, definition_type): - super(RedefinitionError, self).__init__() - self.name = name - self.definition_type = definition_type - self.filename = None - self.lineno = None + 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 = "cannot redefine '{}' ({})".format(self.name, - self.definition_type) - if self.filename: - mess = "While opening {}, in line {}: " - return mess.format(self.filename, self.lineno) + msg - return msg + 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. - """ + """Raised when the units are not defined in the unit registry.""" - def __init__(self, unit_names): - super(UndefinedUnitError, self).__init__() - self.unit_names = unit_names + 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): - mess = "'{}' is not defined in the unit registry" - mess_plural = "'{}' are not defined in the unit registry" - if isinstance(self.unit_names, string_types): - return mess.format(self.unit_names) - elif isinstance(self.unit_names, (list, tuple))\ - and len(self.unit_names) == 1: - return mess.format(self.unit_names[0]) - elif isinstance(self.unit_names, set) and len(self.unit_names) == 1: - uname = list(self.unit_names)[0] - return mess.format(uname) - else: - return mess_plural.format(self.unit_names) + 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(ValueError): - """Raised when trying to convert between incompatible units. - """ - def __init__(self, units1, units2, dim1=None, dim2=None, extra_msg=''): - super(DimensionalityError, self).__init__() +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 @@ -86,31 +89,30 @@ def __str__(self): if self.dim1 or self.dim2: - dim1 = ' ({})'.format(self.dim1) - dim2 = ' ({})'.format(self.dim2) + dim1 = f" ({self.dim1})" + dim2 = f" ({self.dim2})" else: - dim1 = '' - dim2 = '' + dim1 = "" + dim2 = "" - msg = "Cannot convert from '{}'{} to '{}'{}" + self.extra_msg + return ( + f"Cannot convert from '{self.units1}'{dim1} to " + f"'{self.units2}'{dim2}{self.extra_msg}" + ) - return msg.format(self.units1, dim1, self.units2, dim2) + def __reduce__(self): + return TypeError.__new__, (DimensionalityError,), self.__dict__ -class OffsetUnitCalculusError(ValueError): - """Raised on ambiguous operations with offset units. - """ - def __init__(self, units1, units2='', extra_msg=''): - super(ValueError, self).__init__() - self.units1 = units1 - self.units2 = units2 - self.extra_msg = extra_msg +class OffsetUnitCalculusError(PintTypeError): + """Raised on ambiguous operations with offset units.""" def __str__(self): - msg = ("Ambiguous operation with offset unit (%s)." % - ', '.join(['%s' % u for u in [self.units1, self.units2] if u]) - + self.extra_msg) - return msg.format(self.units1, self.units2) + 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): diff -Nru python-pint-0.9/pint/formatting.py python-pint-0.10.1/pint/formatting.py --- python-pint-0.9/pint/formatting.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/formatting.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.formatter ~~~~~~~~~~~~~~ @@ -9,14 +8,12 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import - import re -from .babel_names import _babel_units, _babel_lengths -from pint.compat import babel_units, Loc, string_types +from .babel_names import _babel_lengths, _babel_units +from .compat import babel_parse -__JOIN_REG_EXP = re.compile("\{\d*\}") +__JOIN_REG_EXP = re.compile(r"\{\d*\}") def _join(fmt, iterable): @@ -25,9 +22,21 @@ 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 '' + return "" if not __JOIN_REG_EXP.search(fmt): return fmt.join(iterable) miter = iter(iterable) @@ -37,14 +46,24 @@ first = ret return first -_PRETTY_EXPONENTS = '⁰¹²³⁴⁵⁶⁷⁸⁹' + +_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 = '{0:n}'.format(num).replace('-', '⁻') + ret = f"{num:n}".replace("-", "⁻") for n in range(10): ret = ret.replace(str(n), _PRETTY_EXPONENTS[n]) return ret @@ -53,77 +72,100 @@ #: _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: '{0:n}'.format(x), - locale=None, babel_length='long', babel_plural_form='one'): + "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. - :param items: a list of (name, exponent) pairs. - :param as_ratio: True to display as ratio, False as negative powers. - :param single_denominator: all with terms with negative exponents are - collected together. - :param product_fmt: the format used for multiplication. - :param division_fmt: the format used for division. - :param power_fmt: the format used for exponentiation. - :param parentheses_fmt: the format used for parenthesis. - :param locale: the locale object as defined in babel. - :param babel_length: the length of the translated unit, as defined in babel cldr. - :param babel_plural_form: the plural form, calculated as defined in babel. + 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. - :return: the formula as a string. """ if not items: - return '' + return "" if as_ratio: fun = lambda x: exp_call(abs(x)) @@ -135,13 +177,14 @@ 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 = Loc.parse(locale) - unit_patterns = locale._data['unit_patterns'] + 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 + 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) \ + _babel_length + for _babel_length in reversed(_babel_lengths) if babel_length != _babel_length ] else: @@ -149,10 +192,13 @@ for _babel_length in [babel_length] + other_lengths: pat = unit_patterns.get(_key, {}).get(_babel_length, {}).get(plural) if pat is not None: - key = pat.replace('{}', '').strip() + # 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 = '{}{}' + 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) @@ -168,7 +214,7 @@ return _join(product_fmt, pos_terms + neg_terms) # Show as Ratio: positive terms / negative terms - pos_ret = _join(product_fmt, pos_terms) or '1' + pos_ret = _join(product_fmt, pos_terms) or "1" if not neg_terms: return pos_ret @@ -182,18 +228,19 @@ 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') -_KNOWN_TYPES = frozenset(list(_FORMATS.keys()) + ['~']) +_BASIC_TYPES = frozenset("bcdeEfFgGnosxX%uS") + def _parse_spec(spec): - result = '' + result = "" for ch in reversed(spec): - if ch == '~' or ch in _BASIC_TYPES: + if ch == "~" or ch in _BASIC_TYPES: continue - elif ch in _KNOWN_TYPES: + elif ch in list(_FORMATS.keys()) + ["~"]: if result: raise ValueError("expected ':' after format specifier") else: @@ -201,49 +248,56 @@ elif ch.isalpha(): raise ValueError("Unknown conversion specified " + ch) else: - break + break return result def format_unit(unit, spec, **kwspec): if not unit: - if spec.endswith('%'): - return '' + if spec.endswith("%"): + return "" else: - return 'dimensionless' + return "dimensionless" spec = _parse_spec(spec) fmt = dict(_FORMATS[spec]) fmt.update(kwspec) - if spec == 'L': - rm = [(r'\mathrm{{{}}}'.format(u), p) for u, p in unit.items()] - result = formatter(rm, **fmt) + 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: - result = formatter(unit.items(), **fmt) - if spec == 'L': - result = result.replace('[', '{').replace(']', '}') - return result + # 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.''' + """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 '' + return "" elif power == 2: - return r'\squared' + return r"\squared" elif power == 3: - return r'\cubed' + return r"\cubed" else: - return r'\tothe{{{:d}}}'.format(int(power)) + return r"\tothe{{{:d}}}".format(int(power)) else: # limit float powers to 3 decimal places - return r'\tothe{{{:.3f}}}'.format(power).rstrip('0') + return r"\tothe{{{:.3f}}}".format(power).rstrip("0") lpos = [] lneg = [] @@ -252,46 +306,46 @@ # remove unit prefix if it exists # siunitx supports \prefix commands - l = lpos if power >= 0 else lneg + 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) + unit = unit.replace(prefix, "", 1) if power < 0: - l.append(r'\per') + lpick.append(r"\per") if prefix is not None: - l.append(r'\{}'.format(prefix)) - l.append(r'\{}'.format(unit)) - l.append(r'{}'.format(_tothe(abs(power)))) + lpick.append(r"\{}".format(prefix)) + lpick.append(r"\{}".format(unit)) + lpick.append(r"{}".format(_tothe(abs(power)))) - return ''.join(lpos) + ''.join(lneg) + return "".join(lpos) + "".join(lneg) def remove_custom_flags(spec): - for flag in _KNOWN_TYPES: - if flag: - spec = spec.replace(flag, '') + 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')): +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')): +def matrix_to_latex(matrix, fmtfun=lambda x: format(x, ".2f")): ret = [] for row in matrix: - ret += [' & '.join(fmtfun(f) for f in row)] + ret += [" & ".join(fmtfun(f) for f in row)] - return r'\begin{pmatrix}%s\end{pmatrix}' % '\\\\ \n'.join(ret) + 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, string_types): +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) @@ -305,15 +359,15 @@ else: ret = [] if ndarr.ndim == 3: - header = ('arr[%s,' % ','.join('%d' % d for d in dim)) + '%d,:,:]' + 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)] + ret += [header % elno + " = " + matrix_to_latex(el, fmtfun)] else: for elno, el in enumerate(ndarr): - ret += ndarray_to_latex_parts(el, fmtfun, dim + (elno, )) + 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)) +def ndarray_to_latex(ndarr, fmtfun=lambda x: format(x, ".2f"), dim=()): + return "\n".join(ndarray_to_latex_parts(ndarr, fmtfun, dim)) diff -Nru python-pint-0.9/pint/__init__.py python-pint-0.10.1/pint/__init__.py --- python-pint-0.9/pint/__init__.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/__init__.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint ~~~~ @@ -11,29 +10,41 @@ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement +import sys import pkg_resources -from .formatting import formatter -from .registry import (UnitRegistry, LazyRegistry) -from .errors import (DimensionalityError, OffsetUnitCalculusError, - UndefinedUnitError, UnitStrippedWarning) -from .util import pi_theorem, logger 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 -import sys try: - from pintpandas import PintType, PintArray + from pintpandas import PintArray, PintType + + del PintType + del PintArray + _HAS_PINTPANDAS = True -except Exception: +except ImportError: _HAS_PINTPANDAS = False _, _pintpandas_error, _ = sys.exc_info() -try: # pragma: no cover - __version__ = pkg_resources.get_distribution('pint').version -except: # pragma: no cover +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" @@ -46,57 +57,90 @@ _APP_REGISTRY = _DEFAULT_REGISTRY -def _build_quantity(value, units): - """Build Quantity using the Application registry. - Used only for unpickling operations. - """ - from .unit import UnitsContainer - - global _APP_REGISTRY - - # 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(units, UnitsContainer): - for name in units.keys(): - _APP_REGISTRY.parse_units(name) - - return _APP_REGISTRY.Quantity(value, units) - +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 -def _build_unit(units): - """Build Unit using the Application registry. - Used only for unpickling operations. """ from .unit import UnitsContainer - global _APP_REGISTRY + 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) - # 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(units, UnitsContainer): - for name in units.keys(): - _APP_REGISTRY.parse_units(name) - - return _APP_REGISTRY.Unit(units) + return cls(*args) def set_application_registry(registry): - """Set the application registry which is used for unpickling operations. + """Set the application registry, which is used for unpickling operations + and when invoking pint.Quantity or pint.Unit directly. - :param registry: a UnitRegistry instance. + Parameters + ---------- + registry : pint.UnitRegistry """ - assert isinstance(registry, 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) + 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. - :return: a :class:`unittest.TestResult` object + 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__", +) diff -Nru python-pint-0.9/pint/matplotlib.py python-pint-0.10.1/pint/matplotlib.py --- python-pint-0.9/pint/matplotlib.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/matplotlib.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- """ pint.matplotlib - ~~~~~~~~~ + ~~~~~~~~~~~~~~~ Functions and classes related to working with Matplotlib's support for plotting with units. @@ -9,34 +8,39 @@ :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(PintAxisInfo, self).__init__(label='{:P}'.format(units)) + 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(PintConverter, self).__init__() + super().__init__() self._reg = registry def convert(self, value, unit, axis): - """Convert :`Quantity` instances for matplotlib to use.""" - if isinstance(value, (tuple, list)): + """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'): + """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 @@ -44,23 +48,34 @@ @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.""" - return getattr(x, 'units', None) + """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. - :param registry: the registry that will be used - :type registry: UnitRegistry - :param enable: whether support should be enabled or disabled - :type enable: bool + + 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 matplotlib.__version__ < "2.0": + raise RuntimeError("Matplotlib >= 2.0 required to work with pint.") if enable: matplotlib.units.registry[registry.Quantity] = PintConverter(registry) diff -Nru python-pint-0.9/pint/measurement.py python-pint-0.10.1/pint/measurement.py --- python-pint-0.9/pint/measurement.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/measurement.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.measurement ~~~~~~~~~~~~~~~~ @@ -6,22 +5,28 @@ :copyright: 2016 by Pint Authors, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ - -from __future__ import division, unicode_literals, print_function, absolute_import +import re from .compat import ufloat from .formatting import _FORMATS, siunitx_format_unit +from .quantity import Quantity MISSING = object() -class _Measurement(object): +class Measurement(Quantity): """Implements a class to describe a quantity with uncertainty. - :param value: The most likely value of the measurement. - :type value: Quantity or Number - :param error: The error or uncertainty of the measurement. - :type error: Quantity or Number + 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): @@ -29,28 +34,30 @@ try: value, units = value.magnitude, value.units except AttributeError: - #if called with two arguments and the first looks like a ufloat + # 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"): + if hasattr(value, "nominal_value"): units = error - error = MISSING #used for check below + error = MISSING # used for check below else: - units = '' + 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)) + raise ValueError( + "The magnitude of the error cannot be negative".format(value, error) + ) else: - mag = ufloat(value,error) - - inst = super(_Measurement, cls).__new__(cls, mag, units) + 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) @@ -63,83 +70,105 @@ 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) + return "".format( + self.magnitude.nominal_value, self.magnitude.std_dev, self.units + ) def __str__(self): - return '{0}'.format(self) + return "{}".format(self) def __format__(self, spec): # special cases - if 'Lx' in spec: # the LaTeX siunitx code + 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') + spec = spec.replace("Lx", "S") # todo: add support for extracting options - opts = 'separate-uncertainty=true' - mstr = format( self.magnitude, spec ) + opts = "separate-uncertainty=true" + mstr = format(self.magnitude, spec) ustr = siunitx_format_unit(self.units) - ret = r'\SI[%s]{%s}{%s}'%( opts, mstr, ustr ) - return ret - + 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'] + 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'] + newpm = pm = "+/-" + pars = _FORMATS[""]["parentheses_fmt"] - if 'C' in spec: - sp = '' - newspec = spec.replace('C', '') - pars = _FORMATS['C']['parentheses_fmt'] + if "C" in spec: + sp = "" + newspec = spec.replace("C", "") + pars = _FORMATS["C"]["parentheses_fmt"] else: - sp = ' ' + sp = " " newspec = spec - if 'H' in spec: - newpm = '±' - newspec = spec.replace('H', '') - pars = _FORMATS['H']['parentheses_fmt'] + 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 and "S" in newspec: + mag = mag.replace("(", r"\left(").replace(")", r"\right)") - if 'L' in newspec: - space = r'\ ' + if "L" in newspec or "H" in spec: + space = r"\ " else: - space = ' ' + space = " " - if 'uS' in newspec or 'ue' in newspec or 'u%' in newspec: - return mag + space + format(self.units, spec) + 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 pars.format(mag) + space + format(self.units, spec) + return mag + space + ustr + +_Measurement = Measurement -def build_measurement_class(registry, force_ndarray=False): + +def build_measurement_class(registry): if ufloat is None: - class Measurement(object): + + class Measurement: + _REGISTRY = registry def __init__(self, *args): - raise RuntimeError("Pint requires the 'uncertainties' package to create a Measurement object.") + raise RuntimeError( + "Pint requires the 'uncertainties' package to create a Measurement object." + ) else: - class Measurement(_Measurement, registry.Quantity): - pass - Measurement._REGISTRY = registry - Measurement.force_ndarray = force_ndarray + class Measurement(_Measurement): + _REGISTRY = registry return Measurement diff -Nru python-pint-0.9/pint/numpy_func.py python-pint-0.10.1/pint/numpy_func.py --- python-pint-0.9/pint/numpy_func.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/pint/numpy_func.py 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,889 @@ +""" + 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) diff -Nru python-pint-0.9/pint/pint_eval.py python-pint-0.10.1/pint/pint_eval.py --- python-pint-0.9/pint/pint_eval.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/pint_eval.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.pint_eval ~~~~~~~~~~~~~~ @@ -9,54 +8,49 @@ :license: BSD, see LICENSE for more details. """ -from decimal import Decimal -import math 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 + "**": 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 + "**": 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 -} +_UNARY_OPERATOR_MAP = {"+": lambda x: x, "-": lambda x: x * -1} -class EvalTreeNode(object): - +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): - """ - left + operator + right --> binary op - left + operator --> unary op - left + right --> implicit op - left --> single value - """ self.left = left self.operator = operator self.right = right - + def to_string(self): # For debugging purposes if self.right: @@ -68,17 +62,31 @@ comps = [self.operator[1], self.left.to_string()] else: return self.left[1] - return '(%s)' % ' '.join(comps) - - def evaluate(self, define_op, bin_op=_BINARY_OPERATOR_MAP, un_op=_UNARY_OPERATOR_MAP): - """ - define_op is a callable that translates tokens into objects - bin_op and un_op provide functions for performing binary and unary operations + 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 '' + 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) @@ -92,17 +100,19 @@ else: # single value return define_op(self.left) - -def build_eval_tree(tokens, op_priority=_OP_PRIORITY, index=0, depth=0, prev_op=None, ): - """ + +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). - + + 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 @@ -111,34 +121,39 @@ 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 == None: + 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 token_text == ")": if prev_op is None: - raise DefinitionSyntaxError('unopened parentheses in tokens: %s' % current_token) - elif prev_op == '(': + 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 == '(': + 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') + 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) @@ -147,46 +162,57 @@ 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 + # 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 ['**', '^']: + 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) + 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') + 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 + 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, '') + 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 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') + raise DefinitionSyntaxError("unexpected end to tokens") index += 1 - diff -Nru python-pint-0.9/pint/quantity.py python-pint-0.10.1/pint/quantity.py --- python-pint-0.9/pint/quantity.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/quantity.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.quantity ~~~~~~~~~~~~~ @@ -7,40 +6,69 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import - +import bisect +import contextlib import copy import datetime -import math -import operator import functools -import bisect -import warnings +import locale +import math import numbers +import operator import re +import warnings -from .formatting import (remove_custom_flags, siunitx_format_unit, ndarray_to_latex, - ndarray_to_latex_parts) -from .errors import (DimensionalityError, OffsetUnitCalculusError, - UndefinedUnitError, UnitStrippedWarning) -from .definitions import UnitDefinition -from .compat import string_types, ndarray, np, _to_magnitude, long_type -from .util import (PrettyIPython, logger, UnitsContainer, SharedRegistryObject, - to_units_container, infer_base_unit, - fix_str_conversions) -from pint.compat import Loc - -def _eq(first, second, check_all): - """Comparison of scalars and arrays - """ - out = first == second - if check_all and isinstance(out, ndarray): - return np.all(out) - return out +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 +class _Exception(Exception): # pragma: no cover def __init__(self, internal): self.internal = internal @@ -52,6 +80,7 @@ return result.to_reduced_units() else: return result + return wrapped @@ -61,109 +90,175 @@ 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 other.__class__.__name__ in ["PintArray", "Series"]: + 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 isinstance(other[0], type(self)): + elif isinstance(other, list) and other and isinstance(other[0], type(self)): return NotImplemented result = f(self, *args, **kwargs) return result + return wrapped -@fix_str_conversions -class _Quantity(PrettyIPython, SharedRegistryObject): +@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. - :param value: value of the physical quantity to be created. - :type value: str, Quantity or any numeric type. - :param units: units of the physical quantity to be created. - :type units: UnitsContainer, str or Quantity. + 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 = '' + 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): - from . import _build_quantity - return _build_quantity, (self.magnitude, self._units) + """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): - if units is None: - if isinstance(value, string_types): - if value == '': - raise ValueError('Expression to parse as Quantity cannot ' - 'be an empty string.') - inst = cls._REGISTRY.parse_expression(value) + 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 = object.__new__(cls) - inst._magnitude = _to_magnitude(value, inst.force_ndarray) + 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 = object.__new__(cls) - inst._magnitude = _to_magnitude(value, inst.force_ndarray) + inst = SharedRegistryObject.__new__(cls) + inst._magnitude = _to_magnitude( + value, inst.force_ndarray, inst.force_ndarray_like + ) inst._units = units - elif isinstance(units, string_types): - inst = object.__new__(cls) - inst._magnitude = _to_magnitude(value, inst.force_ndarray) + 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: + if isinstance(units, Quantity) and units.magnitude != 1: inst = copy.copy(units) - logger.warning('Creating new Quantity using a non unity ' - 'Quantity as units.') + logger.warning( + "Creating new Quantity using a non unity " "Quantity as units." + ) else: - inst = object.__new__(cls) + inst = SharedRegistryObject.__new__(cls) inst._units = units._units - inst._magnitude = _to_magnitude(value, inst.force_ndarray) - else: - raise TypeError('units must be of type str, Quantity or ' - 'UnitsContainer; not {}.'.format(type(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 - # Only instances where the magnitude is iterable should have __iter__() - if hasattr(inst._magnitude,"__iter__"): - inst.__iter__ = cls._iter + + 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 - def _iter(self): - """ - Will be become __iter__() for instances with iterable magnitudes - """ - # # Allow exception to propagate in case of non-iterable magnitude - it_mag = iter(self.magnitude) - return iter((self.__class__(mag, self._units) for mag in it_mag)) @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 = 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 "".format(self._magnitude, self._units) + return f"" def __hash__(self): self_base = self.to_base_units() @@ -177,130 +272,143 @@ def __format__(self, spec): spec = spec or self.default_format - if 'L' in spec: - allf = plain_allf = r'{}\ {}' + if "L" in spec: + allf = plain_allf = r"{}\ {}" else: - allf = plain_allf = '{} {}' - - mstr, ustr = None, None + allf = plain_allf = "{} {}" # If Compact is selected, do it at the beginning - if '#' in spec: - spec = spec.replace('#', '') + 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 = '' + 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 + 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: + if "L" in spec: mstr = ndarray_to_latex(obj.magnitude, mspec) - elif 'H' in spec: + elif "H" in spec: + allf = r"\[{} {}\]" # this is required to have the magnitude and unit in the same line - allf = r'\[{} {}\]' parts = ndarray_to_latex_parts(obj.magnitude, mspec) if len(parts) > 1: - return '\n'.join(allf.format(part, ustr) for part in parts) + return "\n".join(allf.format(part, ustr) for part in parts) mstr = parts[0] else: - mstr = format(obj.magnitude, mspec).replace('\n', '') + formatter = "{{:{}}}".format(mspec) + with printoptions(formatter={"float_kind": formatter.format}): + mstr = format(obj.magnitude).replace("\n", "") else: - mstr = format(obj.magnitude, mspec).replace('\n', '') + mstr = format(obj.magnitude, mspec).replace("\n", "") - if 'L' in spec: + 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 "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 /'): + 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(_Quantity, self)._repr_pretty_(p, cycle) + super()._repr_pretty_(p, cycle) else: p.pretty(self.magnitude) p.text(" ") p.pretty(self.units) - def format_babel(self, spec='', **kwspec): + def format_babel(self, spec="", **kwspec): spec = spec or self.default_format # standard cases - if '#' in spec: - spec = spec.replace('#', '') + 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') - kwspec['locale'] = Loc.parse(kwspec['locale']) - kwspec['babel_plural_form'] = kwspec['locale'].plural_form(obj.magnitude) - return '{} {}'.format( + 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', '') + obj.units.format_babel(spec, **kwspec), + ).replace("\n", "") @property def magnitude(self): - """Quantity's magnitude. Long form for `m` - """ + """Quantity's magnitude. Long form for `m`""" return self._magnitude @property def m(self): - """Quantity's magnitude. Short form for `magnitude` - """ + """Quantity's magnitude. Short form for `magnitude`""" return self._magnitude def m_as(self, units): """Quantity's magnitude expressed in particular units. - :param units: destination units - :type units: Quantity, str or dict + 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` - - :rtype: UnitContainer - """ + """Quantity's units. Long form for `u`""" return self._REGISTRY.Unit(self._units) @property def u(self): - """Quantity's units. Short form for `units` - - :rtype: UnitContainer - """ + """Quantity's units. Short form for `units`""" return self._REGISTRY.Unit(self._units) @property def unitless(self): - """Return true if the quantity does not have units. - """ + """ """ return not bool(self.to_root_units()._units) @property def dimensionless(self): - """Return true if the quantity is dimensionless. - """ + """ """ tmp = self.to_root_units() return not bool(tmp.dimensionality) @@ -309,7 +417,11 @@ @property def dimensionality(self): - """Quantity's dimensionality (e.g. {length: 1, time: -1}) + """ + 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) @@ -322,6 +434,63 @@ 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])) @@ -347,19 +516,28 @@ 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=isinstance(self._magnitude, ndarray)) + 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. - :param other: destination units. - :type other: Quantity, str or dict + 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._magnitude = self._convert_magnitude(other, *contexts, **ctx_kwargs) self._units = other return None @@ -367,8 +545,18 @@ def to(self, other=None, *contexts, **ctx_kwargs): """Return Quantity rescaled to different units. - :param other: destination units. - :type other: Quantity, str or dict + 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) @@ -377,8 +565,7 @@ return self.__class__(magnitude, other) def ito_root_units(self): - """Return Quantity rescaled to base units - """ + """Return Quantity rescaled to root units.""" _, other = self._REGISTRY._get_root_units(self._units) @@ -388,8 +575,8 @@ return None def to_root_units(self): - """Return Quantity rescaled to base units - """ + """Return Quantity rescaled to root units.""" + _, other = self._REGISTRY._get_root_units(self._units) magnitude = self._convert_magnitude_not_inplace(other) @@ -397,8 +584,7 @@ return self.__class__(magnitude, other) def ito_base_units(self): - """Return Quantity rescaled to base units - """ + """Return Quantity rescaled to base units.""" _, other = self._REGISTRY._get_base_units(self._units) @@ -408,36 +594,35 @@ return None def to_base_units(self): - """Return Quantity rescaled to base units - """ + """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 + + # 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? + # 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) + power = self._REGISTRY._get_dimensionality_ratio(unit1, unit2) if power: - newunits = newunits.add(unit2, exp/power).remove(unit1) + newunits = newunits.add(unit2, exp / power).remove([unit1]) break return self.ito(newunits) @@ -447,17 +632,21 @@ dimension. This will not reduce compound units (intentionally), nor can it make use of contexts at this time. """ - #can we make this more efficient? + + # 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. + """"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() @@ -465,15 +654,22 @@ >>> (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.") + 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)): + if ( + self.unitless + or self.magnitude == 0 + or math.isnan(self.magnitude) + or math.isinf(self.magnitude) + ): return self SI_prefixes = {} @@ -484,8 +680,8 @@ log10_scale = int(math.log10(scale)) if log10_scale == math.log10(scale): SI_prefixes[log10_scale] = prefix.name - except: - SI_prefixes[0] = '' + except Exception: + SI_prefixes[0] = "" SI_prefixes = sorted(SI_prefixes.items()) SI_powers = [item[0] for item in SI_prefixes] @@ -499,7 +695,7 @@ magnitude = q_base.magnitude units = list(q_base._units.items()) - units_numerator = list(filter(lambda a: a[1]>0, units)) + units_numerator = [a for a in units if a[1] > 0] if len(units_numerator) > 0: unit_str, unit_power = units_numerator[0] @@ -513,7 +709,7 @@ prefix = SI_bases[bisect.bisect_left(SI_powers, power)] - new_unit_str = prefix+unit_str + new_unit_str = prefix + unit_str new_unit_container = q_base._units.rename(unit_str, new_unit_str) return self.to(new_unit_container) @@ -522,38 +718,41 @@ def __int__(self): if self.dimensionless: return int(self._convert_magnitude_not_inplace(UnitsContainer())) - raise DimensionalityError(self._units, 'dimensionless') - - def __long__(self): - if self.dimensionless: - return long_type(self._convert_magnitude_not_inplace(UnitsContainer())) - raise DimensionalityError(self._units, 'dimensionless') + raise DimensionalityError(self._units, "dimensionless") def __float__(self): if self.dimensionless: return float(self._convert_magnitude_not_inplace(UnitsContainer())) - raise DimensionalityError(self._units, 'dimensionless') + raise DimensionalityError(self._units, "dimensionless") def __complex__(self): if self.dimensionless: return complex(self._convert_magnitude_not_inplace(UnitsContainer())) - raise DimensionalityError(self._units, 'dimensionless') + raise DimensionalityError(self._units, "dimensionless") + @check_implemented def _iadd_sub(self, other, op): """Perform addition or subtraction operation in-place and return the result. - :param other: object to be added to / subtracted from self - :type other: Quantity or any type accepted by :func:`_to_magnitude` - :param op: operator function (e.g. operator.add, operator.isub) - :type op: function + 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) + 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 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 @@ -564,13 +763,13 @@ self.ito(UnitsContainer()) self._magnitude = op(self._magnitude, other_magnitude) else: - raise DimensionalityError(self._units, 'dimensionless') + raise DimensionalityError(self._units, "dimensionless") return self if not self.dimensionality == other.dimensionality: - raise DimensionalityError(self._units, other._units, - 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() @@ -588,48 +787,55 @@ 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._magnitude = op( + self._convert_magnitude(other._units), other._magnitude + ) self._units = other._units else: - self._magnitude = op(self._magnitude, - other.to(self._units)._magnitude) + 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)): + 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)): + 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) + 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)): + 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) + 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)): + 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) + 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: @@ -641,34 +847,42 @@ def _add_sub(self, other, op): """Perform addition or subtraction operation and return the result. - :param other: object to be added to / subtracted from self - :type other: Quantity or any type accepted by :func:`_to_magnitude` - :param op: operator function (e.g. operator.add, operator.isub) - :type op: function + 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 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)) + 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)) + magnitude = op( + self.to(units)._magnitude, + _to_magnitude(other, self.force_ndarray, self.force_ndarray_like), + ) else: - raise DimensionalityError(self._units, 'dimensionless') + 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) + 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() @@ -687,51 +901,54 @@ 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) + 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) + 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)): + 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)): + 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) + 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)): + 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) + 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)): + 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) + 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: @@ -742,10 +959,10 @@ def __iadd__(self, other): if isinstance(other, datetime.datetime): return self.to_timedelta() + other - elif not isinstance(self._magnitude, ndarray): - return self._add_sub(other, operator.add) - else: + 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): @@ -756,10 +973,10 @@ __radd__ = __add__ def __isub__(self, other): - if not isinstance(self._magnitude, ndarray): - return self._add_sub(other, operator.sub) - else: + 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) @@ -770,19 +987,26 @@ 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. - :param other: object to be multiplied/divided with self - :type other: Quantity or any type accepted by :func:`_to_magnitude` - :param magnitude_op: operator function to perform on the magnitudes + 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) - :type magnitude_op: function - :param units_op: operator function to perform on the units; if None, - *magnitude_op* is used - :type units_op: function or None + 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 @@ -793,15 +1017,21 @@ if not self._check(other): if not self._ok_for_muldiv(no_offset_units_self): - raise OffsetUnitCalculusError(self._units, - getattr(other, 'units', '')) + 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', '')) + 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) + 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) @@ -809,12 +1039,12 @@ return self if isinstance(other, self._REGISTRY.Unit): - other = 1.0 * other + 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() + self.ito_root_units() no_offset_units_other = len(other._get_non_multiplicative_units()) @@ -833,14 +1063,20 @@ def _mul_div(self, other, magnitude_op, units_op=None): """Perform multiplication or division operation and return the result. - :param other: object to be multiplied/divided with self - :type other: Quantity or any type accepted by :func:`_to_magnitude` - :param magnitude_op: operator function to perform on the magnitudes + 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) - :type magnitude_op: function - :param units_op: operator function to perform on the units; if None, - *magnitude_op* is used - :type units_op: function or None + 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 @@ -851,15 +1087,21 @@ if not self._check(other): if not self._ok_for_muldiv(no_offset_units_self): - raise OffsetUnitCalculusError(self._units, - getattr(other, 'units', '')) + 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', '')) + 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) + other_magnitude = _to_magnitude( + other, self.force_ndarray, self.force_ndarray_like + ) + except PintTypeError: + raise except TypeError: return NotImplemented @@ -869,7 +1111,7 @@ return self.__class__(magnitude, units) if isinstance(other, self._REGISTRY.Unit): - other = 1.0 * other + other = 1 * other new_self = self @@ -891,38 +1133,52 @@ return self.__class__(magnitude, units) def __imul__(self, other): - if not isinstance(self._magnitude, ndarray): - return self._mul_div(other, operator.mul) - else: + 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 __itruediv__(self, other): - if not isinstance(self._magnitude, ndarray): - return self._mul_div(other, operator.truediv) + 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) + 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, '') + 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__ @@ -931,9 +1187,9 @@ if self._check(other): self._magnitude //= other.to(self._units)._magnitude elif self.dimensionless: - self._magnitude = self.to('')._magnitude // other + self._magnitude = self.to("")._magnitude // other else: - raise DimensionalityError(self._units, 'dimensionless') + raise DimensionalityError(self._units, "dimensionless") self._units = UnitsContainer({}) return self @@ -942,9 +1198,9 @@ if self._check(other): magnitude = self._magnitude // other.to(self._units)._magnitude elif self.dimensionless: - magnitude = self.to('')._magnitude // other + magnitude = self.to("")._magnitude // other else: - raise DimensionalityError(self._units, 'dimensionless') + raise DimensionalityError(self._units, "dimensionless") return self.__class__(magnitude, UnitsContainer({})) @check_implemented @@ -952,11 +1208,12 @@ if self._check(other): magnitude = other._magnitude // self.to(other._units)._magnitude elif self.dimensionless: - magnitude = other // self.to('')._magnitude + magnitude = other // self.to("")._magnitude else: - raise DimensionalityError(self._units, 'dimensionless') + 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({})) @@ -976,18 +1233,17 @@ magnitude = other._magnitude % self.to(other._units)._magnitude return self.__class__(magnitude, other._units) elif self.dimensionless: - magnitude = other % self.to('')._magnitude + magnitude = other % self.to("")._magnitude return self.__class__(magnitude, UnitsContainer({})) else: - raise DimensionalityError(self._units, 'dimensionless') + 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)) + return (self.__class__(q, UnitsContainer({})), self.__class__(r, self._units)) @check_implemented def __rdivmod__(self, other): @@ -995,41 +1251,47 @@ q, r = divmod(other._magnitude, self.to(other._units)._magnitude) unit = other._units elif self.dimensionless: - q, r = divmod(other, self.to('')._magnitude) + q, r = divmod(other, self.to("")._magnitude) unit = UnitsContainer({}) else: - raise DimensionalityError(self._units, 'dimensionless') + raise DimensionalityError(self._units, "dimensionless") return (self.__class__(q, UnitsContainer({})), self.__class__(r, unit)) + @check_implemented def __ipow__(self, other): - if not isinstance(self._magnitude, ndarray): + if not is_duck_array_type(type(self._magnitude)): return self.__pow__(other) try: - other_magnitude = _to_magnitude(other, self.force_ndarray) + _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 isinstance(getattr(other, '_magnitude', other), ndarray): + 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('') + if getattr(other, "dimensionless", False): + self._magnitude **= other.m_as("") return self - elif not getattr(other, 'dimensionless', True): - raise DimensionalityError(other._units, 'dimensionless') + 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') + raise DimensionalityError( + self._units, + "dimensionless", + extra_msg=". Quantity array exponents are only allowed if the " + "base is dimensionless", + ) if other == 1: return self @@ -1042,47 +1304,55 @@ else: raise OffsetUnitCalculusError(self._units) - if getattr(other, 'dimensionless', False): + 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') + elif not getattr(other, "dimensionless", True): + raise DimensionalityError(self._units, "dimensionless") else: self._units **= other - self._magnitude **= _to_magnitude(other, self.force_ndarray) + self._magnitude **= _to_magnitude( + other, self.force_ndarray, self.force_ndarray_like + ) return self @check_implemented def __pow__(self, other): try: - other_magnitude = _to_magnitude(other, self.force_ndarray) + _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 isinstance(getattr(other, '_magnitude', other), ndarray): + 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') + 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') + 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: @@ -1091,30 +1361,36 @@ else: raise OffsetUnitCalculusError(self._units) - if getattr(other, 'dimensionless', False): - units = new_self._units ** other.to_root_units().magnitude - elif not getattr(other, 'dimensionless', True): - raise DimensionalityError(other._units, 'dimensionless') + 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: - units = new_self._units ** other + exponent = _to_magnitude( + other, self.force_ndarray, self.force_ndarray_like + ) + units = new_self._units ** exponent - magnitude = new_self._magnitude ** _to_magnitude(other, self.force_ndarray) + magnitude = new_self._magnitude ** exponent return self.__class__(magnitude, units) @check_implemented def __rpow__(self, other): try: - other_magnitude = _to_magnitude(other, self.force_ndarray) + _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 isinstance(self._magnitude, ndarray): + raise DimensionalityError(self._units, "dimensionless") + if is_duck_array_type(type(self._magnitude)): if np.size(self._magnitude) > 1: - raise DimensionalityError(self._units, 'dimensionless') + raise DimensionalityError(self._units, "dimensionless") new_self = self.to_root_units() - return other**new_self._magnitude + return other ** new_self._magnitude def __abs__(self): return self.__class__(abs(self._magnitude), self._units) @@ -1132,39 +1408,44 @@ 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): + 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) + 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) + 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)) + return self.dimensionless and eq( + self._convert_magnitude(UnitsContainer()), other, False + ) - if _eq(self._magnitude, 0, True) and _eq(other._magnitude, 0, True): + 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) + return eq(self._magnitude, other._magnitude, False) try: - return _eq(self._convert_magnitude_not_inplace(other._units), - other._magnitude, False) + 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 isinstance(out, ndarray): + if is_duck_array_type(type(out)): return np.logical_not(out) return not out @@ -1173,7 +1454,7 @@ if not isinstance(other, self.__class__): if self.dimensionless: return op(self._convert_magnitude_not_inplace(UnitsContainer()), other) - elif _eq(other, 0, True): + elif eq(other, 0, True): # Handle the special case in which we compare to zero # (or an array of zeros) if self._is_multiplicative: @@ -1187,15 +1468,15 @@ else: raise OffsetUnitCalculusError(self._units) else: - raise ValueError('Cannot compare Quantity and {}'.format(type(other))) + 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) + 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) @@ -1203,94 +1484,108 @@ __gt__ = lambda self, other: self.compare(other, op=operator.gt) def __bool__(self): - return bool(self._magnitude) + # 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 Support - __radian = 'radian' - __same_units = 'equal greater greater_equal less less_equal not_equal arctan2'.split() - #: Dictionary mapping ufunc/attributes names to the units that they - #: require (conversion will be tried). - __require_units = {'cumprod': '', - 'arccos': '', 'arcsin': '', 'arctan': '', - 'arccosh': '', 'arcsinh': '', 'arctanh': '', - 'exp': '', 'expm1': '', 'exp2': '', - 'log': '', 'log10': '', 'log1p': '', 'log2': '', - 'sin': __radian, 'cos': __radian, 'tan': __radian, - 'sinh': __radian, 'cosh': __radian, 'tanh': __radian, - 'radians': 'degree', 'degrees': __radian, - 'deg2rad': 'degree', 'rad2deg': __radian, - 'logaddexp': '', 'logaddexp2': ''} - - #: Dictionary mapping ufunc/attributes names to the units that they - #: will set on output. - __set_units = {'cos': '', 'sin': '', 'tan': '', - 'cosh': '', 'sinh': '', 'tanh': '', - 'log': '', 'exp': '', - 'arccos': __radian, 'arcsin': __radian, - 'arctan': __radian, 'arctan2': __radian, - 'arccosh': __radian, 'arcsinh': __radian, - 'arctanh': __radian, - 'degrees': 'degree', 'radians': __radian, - 'expm1': '', 'cumprod': '', - 'rad2deg': 'degree', 'deg2rad': __radian} - - #: List of ufunc/attributes names in which units are copied from the - #: original. - __copy_units = 'compress conj conjugate copy cumsum diagonal flatten ' \ - 'max mean min ptp ravel repeat reshape round ' \ - 'squeeze std sum swapaxes take trace transpose ' \ - 'ceil floor hypot rint ' \ - 'add subtract ' \ - 'copysign nextafter trunc ' \ - 'frexp ldexp modf modf__1 ' \ - 'absolute negative remainder fmod mod'.split() - - #: Dictionary mapping ufunc/attributes names to the units that they will - #: set on output. The value is interpreted as the power to which the unit - #: will be raised. - __prod_units = {'var': 2, 'prod': 'size', 'multiply': 'mul', - 'true_divide': 'div', 'divide': 'div', 'floor_divide': 'div', - 'remainder': 'div', - 'sqrt': .5, 'square': 2, 'reciprocal': -1} - - __skip_other_args = 'ldexp multiply ' \ - 'true_divide divide floor_divide fmod mod ' \ - 'remainder'.split() - - __handled = tuple(__same_units) + \ - tuple(__require_units.keys()) + \ - tuple(__prod_units.keys()) + \ - tuple(__copy_units) + tuple(__skip_other_args) + # 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): - min = kwargs.get('min', first) - max = kwargs.get('max', second) + minimum = kwargs.get("min", first) + maximum = kwargs.get("max", second) - if min is None and max is None: - raise TypeError('clip() takes at least 3 arguments (2 given)') + if minimum is None and maximum is None: + raise TypeError("clip() takes at least 3 arguments (2 given)") - if max is None and 'min' not in kwargs: - min, max = max, min + if maximum is None and "min" not in kwargs: + minimum, maximum = maximum, minimum - kwargs = {'out': out} + kwargs = {"out": out} - if min is not None: - if isinstance(min, self.__class__): - kwargs['min'] = min.to(self).magnitude + if minimum is not None: + if isinstance(minimum, self.__class__): + kwargs["min"] = minimum.to(self).magnitude elif self.dimensionless: - kwargs['min'] = min + kwargs["min"] = minimum else: - raise DimensionalityError('dimensionless', self._units) + raise DimensionalityError("dimensionless", self._units) - if max is not None: - if isinstance(max, self.__class__): - kwargs['max'] = max.to(self).magnitude + if maximum is not None: + if isinstance(maximum, self.__class__): + kwargs["max"] = maximum.to(self).magnitude elif self.dimensionless: - kwargs['max'] = max + kwargs["max"] = maximum else: - raise DimensionalityError('dimensionless', self._units) + raise DimensionalityError("dimensionless", self._units) return self.__class__(self.magnitude.clip(**kwargs), self._units) @@ -1298,13 +1593,13 @@ self._units = value._units return self.magnitude.fill(value.magnitude) - def put(self, indices, values, mode='raise'): + 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) + values = self.__class__(values, "").to(self) else: - raise DimensionalityError('dimensionless', self._units) + raise DimensionalityError("dimensionless", self._units) self.magnitude.put(indices, values, mode) @property @@ -1332,281 +1627,152 @@ def shape(self, value): self._magnitude.shape = value - def searchsorted(self, v, side='left', sorter=None): + 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) + v = self.__class__(v, "").to(self) else: - raise DimensionalityError('dimensionless', self._units) + raise DimensionalityError("dimensionless", self._units) return self.magnitude.searchsorted(v, side) - def __ito_if_needed(self, to_units): - if self.unitless and to_units == 'radian': - return - - self.ito(to_units) + def dot(self, b): + """Dot product of two arrays. - def __numpy_method_wrap(self, func, *args, **kwargs): - """Convenience method to wrap on the fly numpy method taking - care of the units. + Wraps np.dot(). """ - if func.__name__ in self.__require_units: - self.__ito_if_needed(self.__require_units[func.__name__]) - value = func(*args, **kwargs) - - if func.__name__ in self.__copy_units: - return self.__class__(value, self._units) + return np.dot(self, b) - if func.__name__ in self.__prod_units: - tmp = self.__prod_units[func.__name__] - if tmp == 'size': - return self.__class__(value, self._units ** self._magnitude.size) - return self.__class__(value, self._units ** tmp) + def __ito_if_needed(self, to_units): + if self.unitless and to_units == "radian": + return - return value + self.ito(to_units) def __len__(self): return len(self._magnitude) def __getattr__(self, item): - # Attributes starting with `__array_` are common attributes of NumPy ndarray. - # They are requested by numpy functions. - if item.startswith('__array_'): - warnings.warn("The unit of the quantity is stripped.", UnitStrippedWarning) - if isinstance(self._magnitude, ndarray): - return getattr(self._magnitude, item) - else: - # If an `__array_` attributes is requested but the magnitude is not an ndarray, - # we convert the magnitude to a numpy ndarray. - self._magnitude = _to_magnitude(self._magnitude, force_ndarray=True) - return getattr(self._magnitude, item) - elif item in self.__handled: - if not isinstance(self._magnitude, ndarray): - self._magnitude = _to_magnitude(self._magnitude, True) - attr = getattr(self._magnitude, item) - if callable(attr): - return functools.partial(self.__numpy_method_wrap, attr) - return attr + 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 as ex: - raise AttributeError("Neither Quantity object nor its magnitude ({}) " - "has attribute '{}'".format(self._magnitude, item)) + except AttributeError: + raise AttributeError( + "Neither Quantity object nor its magnitude ({}) " + "has attribute '{}'".format(self._magnitude, item) + ) def __getitem__(self, key): try: - value = self._magnitude[key] - return self.__class__(value, self._units) + 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)) + 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, DimensionalityError): + except TypeError: pass try: if isinstance(value, self.__class__): - factor = self.__class__(value.magnitude, value._units / self._units).to_root_units() + 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 ' - '`obj.magnitude[%s] = %s`' % (key, value)) + 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 TypeError: - raise TypeError("Neither Quantity object nor its magnitude ({})" - "supports indexing".format(self._magnitude)) + 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()] - - __array_priority__ = 17 - - def _call_ufunc(self, ufunc, *inputs, **kwargs): - # Store the destination units - dst_units = None - # List of magnitudes of Quantities with the right units - # to be used as argument of the ufunc - mobjs = None - - if ufunc.__name__ in self.__require_units: - # ufuncs in __require_units - # require specific units - # This is more complex that it should be due to automatic - # conversion between radians/dimensionless - # TODO: maybe could be simplified using Contexts - dst_units = self.__require_units[ufunc.__name__] - if dst_units == 'radian': - mobjs = [] - for other in inputs: - unt = getattr(other, '_units', '') - if unt == 'radian': - mobjs.append(getattr(other, 'magnitude', other)) - else: - factor, units = self._REGISTRY._get_root_units(unt) - if units and units != UnitsContainer({'radian': 1}): - raise DimensionalityError(units, dst_units) - mobjs.append(getattr(other, 'magnitude', other) * factor) - mobjs = tuple(mobjs) - else: - dst_units = self._REGISTRY.parse_expression(dst_units)._units - - elif len(inputs) > 1 and ufunc.__name__ not in self.__skip_other_args: - # ufunc with multiple arguments require that all inputs have - # the same arguments unless they are in __skip_other_args - dst_units = getattr(inputs[0], "_units", None) - - # Do the conversion (if needed) and extract the magnitude for each input. - if mobjs is None: - if dst_units is not None: - mobjs = tuple(self._REGISTRY.convert(getattr(other, 'magnitude', other), - getattr(other, 'units', ''), - dst_units) - for other in inputs) - else: - mobjs = tuple(getattr(other, 'magnitude', other) - for other in inputs) - - # call the ufunc - try: - return ufunc(*mobjs) - except Exception as ex: - raise _Exception(ex) - - - def _wrap_output(self, ufname, i, objs, out): - """we set the units of the output value""" - if i > 0: - ufname = "{}__{}".format(ufname, i) - - if ufname in self.__set_units: - try: - out = self.__class__(out, self.__set_units[ufname]) - except: - raise _Exception(ValueError) - elif ufname in self.__copy_units: - try: - out = self.__class__(out, self._units) - except: - raise _Exception(ValueError) - elif ufname in self.__prod_units: - tmp = self.__prod_units[ufname] - if tmp == 'size': - out = self.__class__(out, self._units ** self._magnitude.size) - elif tmp == 'div': - units1 = objs[0]._units if isinstance(objs[0], self.__class__) else UnitsContainer() - units2 = objs[1]._units if isinstance(objs[1], self.__class__) else UnitsContainer() - out = self.__class__(out, units1 / units2) - elif tmp == 'mul': - units1 = objs[0]._units if isinstance(objs[0], self.__class__) else UnitsContainer() - units2 = objs[1]._units if isinstance(objs[1], self.__class__) else UnitsContainer() - out = self.__class__(out, units1 * units2) - else: - out = self.__class__(out, self._units ** tmp) - - return out - - - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - if method != "__call__": - return NotImplemented - - try: - out = self._call_ufunc(ufunc, *inputs, **kwargs) - if isinstance(out, tuple): - ret = tuple(self._wrap_output(ufunc.__name__, i, inputs, o) - for i, o in enumerate(out)) - return ret - else: - return self._wrap_output(ufunc.__name__, 0, inputs, out) - except (DimensionalityError, UndefinedUnitError): - raise - except _Exception as ex: - raise ex.internal - except: - return NotImplemented - - - def __array_prepare__(self, obj, context=None): - # If this uf is handled by Pint, write it down in the handling dictionary. - - # name of the ufunc, argument of the ufunc, domain of the ufunc - # In ufuncs with multiple outputs, domain indicates which output - # is currently being prepared (eg. see modf). - # In ufuncs with a single output, domain is 0 - uf, objs, i_out = context - - if uf.__name__ in self.__handled and i_out == 0: - # Only one ufunc should be handled at a time. - # If a ufunc is already being handled (and this is not another domain), - # something is wrong.. - if self.__handling: - raise Exception('Cannot handled nested ufuncs.\n' - 'Current: {}\n' - 'New: {}'.format(context, self.__handling)) - self.__handling = context - - return obj - - def __array_wrap__(self, obj, context=None): - uf, objs, i_out = context - - # if this ufunc is not handled by Pint, pass it to the magnitude. - if uf.__name__ not in self.__handled: - return self.magnitude.__array_wrap__(obj, context) - - try: - # First, we check the units of the input arguments. - - if i_out == 0: - out = self._call_ufunc(uf, *objs) - # If there are multiple outputs, - # store them in __handling (uf, objs, i_out, out0, out1, ...) - # and return the first - if uf.nout > 1: - self.__handling += out - out = out[0] - else: - # If this is not the first output, - # just grab the result that was previously calculated. - out = self.__handling[3 + i_out] - - return self._wrap_output(uf.__name__, i_out, objs, out) - except (DimensionalityError, UndefinedUnitError) as ex: - raise ex - except _Exception as ex: - raise ex.internal - except Exception as ex: - print(ex) - finally: - # If this is the last output argument for the ufunc, - # we are done handling this ufunc. - if uf.nout == i_out + 1: - self.__handling = None - - return self.magnitude.__array_wrap__(obj, context) + 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)) + raise ValueError("{} is not a valid relative error.".format(error)) error = error.to(self._units).magnitude else: if relative: @@ -1617,20 +1783,20 @@ # methods/properties that help for math operations with offset units @property def _is_multiplicative(self): - """Check if the Quantity object has only multiplicative units. - """ + """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 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 - """ + """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 @@ -1638,7 +1804,7 @@ """"Check if Quantity object has a delta_unit that is compatible with unit """ deltas = self._get_delta_units() - if 'delta_' + unit in deltas: + 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 @@ -1649,10 +1815,8 @@ def _ok_for_muldiv(self, no_offset_units=None): """Checks if Quantity object can be multiplied or divided - - :q: quantity object that is checked - :no_offset_units: number of offset units in q """ + is_ok = True if no_offset_units is None: no_offset_units = len(self._get_non_multiplicative_units()) @@ -1661,24 +1825,24 @@ 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): + 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) + return datetime.timedelta(microseconds=self.to("microseconds").magnitude) +_Quantity = Quantity -def build_quantity_class(registry, force_ndarray=False): +def build_quantity_class(registry): class Quantity(_Quantity): - pass - - Quantity._REGISTRY = registry - Quantity.force_ndarray = force_ndarray + _REGISTRY = registry return Quantity diff -Nru python-pint-0.9/pint/registry_helpers.py python-pint-0.10.1/pint/registry_helpers.py --- python-pint-0.9/pint/registry_helpers.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/registry_helpers.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.registry_helpers ~~~~~~~~~~~~~~~~~~~~~ @@ -10,23 +9,26 @@ """ import functools +from inspect import signature +from itertools import zip_longest -from .compat import string_types, zip_longest from .errors import DimensionalityError -from .util import to_units_container, UnitsContainer - -try: - from inspect import signature -except ImportError: - # Python2 does not have the inspect library. Import the backport. - from funcsigs import signature +from .util import UnitsContainer, to_units_container def _replace_units(original_units, values_by_name): """Convert a unit compatible type to a UnitsContainer. - :param original_units: a UnitsContainer instance. - :param values_by_name: a map between original names and the new values. + 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(): @@ -40,10 +42,21 @@ checking if it is string field prefixed with an equal (which is considered a reference) - Return a tuple with the unit container and a boolean indicating if it was a reference. + Parameters + ---------- + a : + + registry : + (Default value = None) + + Returns + ------- + UnitsContainer, bool + + """ - if isinstance(a, string_types) and '=' in a: - return to_units_container(a.split('=', 1)[1]), True + if isinstance(a, str) and "=" in a: + return to_units_container(a.split("=", 1)[1]), True return to_units_container(a, registry), False @@ -91,8 +104,10 @@ 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]) + 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) @@ -109,27 +124,41 @@ 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)) + 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]) + new_values[ndx] = ureg._convert( + values[ndx]._magnitude, values[ndx]._units, args_as_uc[ndx][0] + ) else: if strict: - raise ValueError('A wrapped function using strict=True requires ' - 'quantity for all arguments with not None units. ' - '(error found for {}, {})'.format(args_as_uc[ndx][0], new_values[ndx])) + 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. @@ -143,7 +172,8 @@ 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, {} + return args, {} + def wraps(ureg, ret, args, strict=True): """Wraps a function to become pint-aware. @@ -156,56 +186,105 @@ The value returned by the wrapped function will be converted to the units specified in `ret`. - Use None to skip argument conversion. - Set strict to False, to accept also numerical values. + 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 - :param ureg: a UnitRegistry instance. - :param ret: output units. - :param args: iterable of input units. - :param strict: boolean to indicate that only quantities are accepted. - :return: the wrapped function. - :raises: - :class:`ValueError` if strict and one of the arguments is not a Quantity. """ if not isinstance(args, (list, tuple)): - args = (args, ) + 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) - if isinstance(ret, (list, tuple)): - container, ret = True, ret.__class__([_to_units_container(arg, ureg) for arg in ret]) + 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: - container, ret = False, _to_units_container(ret, ureg) + 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): - 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)) + + 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 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 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 ureg.Quantity( + result, _replace_units(ret[0], values_by_name) if ret[1] else ret[0] + ) return wrapper + return decorator @@ -213,28 +292,54 @@ """Decorator to for quantity type checking for function inputs. Use it to ensure that the decorated function input parameters match - the expected type of pint quantity. + the expected dimension of pint quantity. - Use None to skip argument checking. + The wrapper function raises: + - `pint.DimensionalityError` if an argument doesn't match the required dimensions. - :param ureg: a UnitRegistry instance. - :param args: iterable of input units. - :return: the wrapped function. - :raises: - :class:`DimensionalityError` if the parameters don't match 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] + dimensions = [ + ureg.get_dimensionality(dim) if dim is not None else None for dim in args + ] 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)) + + 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) - if len(dimensions) > len(list_args): - raise TypeError("%s takes %i parameters, but %i dimensions were passed" - % (func.__name__, len(list_args), len(dimensions))) + for dim, value in zip(dimensions, list_args): if dim is None: @@ -242,8 +347,9 @@ if not ureg.Quantity(value).check(dim): val_dim = ureg.get_dimensionality(value) - raise DimensionalityError(value, 'a quantity of', - val_dim, dim) + raise DimensionalityError(value, "a quantity of", val_dim, dim) return func(*args, **kwargs) + return wrapper + return decorator diff -Nru python-pint-0.9/pint/registry.py python-pint-0.10.1/pint/registry.py --- python-pint-0.9/pint/registry.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/registry.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,85 +1,132 @@ -# -*- coding: utf-8 -*- """ - pint.registry - ~~~~~~~~~~~~~ +pint.registry +~~~~~~~~~~~~~ - Defines the Registry, a class to contain units and their relations. +Defines the Registry, a class to contain units and their relations. - The module actually defines 5 registries with different capabilites: +The module actually defines 5 registries with different capabilites: - - BaseRegistry: Basic unit definition and querying. - Conversion between multiplicative units. +- BaseRegistry: Basic unit definition and querying. + Conversion between multiplicative units. - - NonMultiplicativeRegistry: Conversion between non multiplicative (offset) units. - (e.g. Temperature) - * Inherits from BaseRegistry +- NonMultiplicativeRegistry: Conversion between non multiplicative (offset) units. + (e.g. Temperature) - - ContextRegisty: Conversion between units with different dimenstions according - to previously established relations (contexts). - (e.g. in the spectroscopy, conversion between frequency and energy is possible) - * Inherits from BaseRegistry + * Inherits from BaseRegistry - - SystemRegistry: Group unit and changing of base units. - (e.g. in MKS, meter, kilogram and second are base units.) +- 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 + * Inherits from BaseRegistry - - UnitRegistry: Combine all previous capabilities, it is exposed by Pint. +- SystemRegistry: Group unit and changing of base units. + (e.g. in MKS, meter, kilogram and second are base units.) - :copyright: 2016 by Pint Authors, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" + * Inherits from BaseRegistry -from __future__ import division, unicode_literals, print_function, absolute_import +- UnitRegistry: Combine all previous capabilities, it is exposed by Pint. -import os -import re -import math +:copyright: 2016 by Pint Authors, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" + +import copy import functools import itertools -import pkg_resources +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 contextlib import contextmanager, closing -from io import open, StringIO -from collections import defaultdict -from tokenize import NUMBER, NAME +from io import StringIO +from tokenize import NAME, NUMBER + +import pkg_resources -from . import registry_helpers +from . import registry_helpers, systems +from .compat import babel_parse, tokenizer from .context import Context, ContextChain -from .util import (logger, pi_theorem, solve_dependencies, ParserHelper, - string_preprocessor, find_connected_nodes, - find_shortest_path, UnitsContainer, _is_dim, - to_units_container, SourceIterator) - -from .compat import tokenizer, string_types, meta -from .definitions import (Definition, UnitDefinition, PrefixDefinition, - DimensionDefinition) from .converters import ScaleConverter -from .errors import (DimensionalityError, UndefinedUnitError, - DefinitionSyntaxError, RedefinitionError) - +from .definitions import ( + AliasDefinition, + Definition, + DimensionDefinition, + PrefixDefinition, + UnitDefinition, +) +from .errors import ( + DefinitionSyntaxError, + DimensionalityError, + RedefinitionError, + UndefinedUnitError, +) from .pint_eval import build_eval_tree -from . import systems +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' |\(') +_BLOCK_RE = re.compile(r" |\(") -class _Meta(type): +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(_Meta, self).__call__(*args, **kwargs) + obj = super().__call__(*args, **kwargs) obj._after_init() return obj -class BaseRegistry(meta.with_metaclass(_Meta)): +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. @@ -88,15 +135,25 @@ - Parse a definition file. - Allow extending the definition file parser by registering @ directives. - :param 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. - :type filename: str | None - :param force_ndarray: convert any input, scalar or not to a numpy.ndarray. - :param on_redefinition: action to take in case a unit is redefined. - 'warn', 'raise', 'ignore' - :type on_redefinition: str - :param auto_reduce_dimensions: If True, reduce dimensionality on appropriate operations. + 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 @@ -105,27 +162,40 @@ #: 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, on_redefinition='warn', auto_reduce_dimensions=False): - + _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() - - from .unit import build_unit_class - self.Unit = build_unit_class(self) - - from .quantity import build_quantity_class - self.Quantity = build_quantity_class(self, force_ndarray) - - from .measurement import build_measurement_class - self.Measurement = build_measurement_class(self, force_ndarray) + 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 @@ -133,7 +203,11 @@ #: Determines if dimensionality should be reduced on appropriate operations. self.auto_reduce_dimensions = auto_reduce_dimensions - #: Map between name (string) and value (string) of defaults stored in the definitions file. + #: 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). @@ -143,77 +217,100 @@ #: Might contain prefixed units. self._units = {} - #: Map unit name in lower case (string) to a set of unit names with the right case. + #: 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)} + self._prefixes = {"": PrefixDefinition("", "", (), 1)} #: Map suffix name (string) to canonical , and unit alias to canonical unit name - self._suffixes = {'': None, 's': ''} + self._suffixes = {"": "", "s": ""} - #: Maps dimensionality (UnitsContainer) to Units (str) - self._dimensional_equivalents = dict() + #: Map contexts to RegistryCache + self._cache = RegistryCache() - #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) - self._root_units_cache = dict() + self._initialized = False - #: Maps dimensionality (UnitsContainer) to Units (UnitsContainer) - self._dimensionality_cache = dict() + def _init_dynamic_classes(self): + """Generate subclasses on the fly and attach them to self""" + from .unit import build_unit_class - #: Cache the unit name associated to user input. ('mV' -> 'millivolt') - self._parse_unit_cache = dict() + self.Unit = build_unit_class(self) - self._initialized = False + 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__ - """ - if self._filename == '': - self.load_definitions('default_en.txt', True) + """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.define(UnitDefinition('pi', 'π', (), ScaleConverter(math.pi))) - self._build_cache() self._initialized = True def _register_parsers(self): - self._register_parser('@defaults', self._parse_defaults) + self._register_parser("@defaults", self._parse_defaults) def _parse_defaults(self, ifile): """Loader for a @default section. - - :type ifile: SourceITerator """ next(ifile) for lineno, part in ifile.block_iter(): - k, v = part.split('=') + k, v = part.split("=") self._defaults[k.strip()] = v.strip() - def __name__(self): - return 'UnitRegistry' + 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): - if item[0] == '_': - return super(BaseRegistry, self).__getattribute__(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.') + 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. - """ + """Default formatting string for quantities.""" return self.Quantity.default_format @default_format.setter @@ -224,12 +321,14 @@ def define(self, definition): """Add unit to the registry. - :param definition: a dimension, unit or prefix definition. - :type definition: str | Definition + Parameters + ---------- + definition : str or Definition + a dimension, unit or prefix definition. """ - if isinstance(definition, string_types): - for line in definition.split('\n'): + if isinstance(definition, str): + for line in definition.split("\n"): self._define(Definition.from_string(line)) else: self._define(definition) @@ -240,10 +339,16 @@ This method defines only multiplicative units, converting any other type to `delta_` units. - :param definition: a dimension, unit or prefix definition. - :type definition: Definition - :return: Definition instance, case sensitive unit dict, case insensitive unit dict. - :rtype: Definition, dict, dict + 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): @@ -257,39 +362,56 @@ 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.') + if dimension != "[]": + raise DefinitionSyntaxError( + "Only one unit per dimension can be a base unit" + ) continue - self.define(DimensionDefinition(dimension, '', (), None, is_base=True)) + 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)) + 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:] + if definition.name.startswith("["): + d_name = "[delta_" + definition.name[1:] else: - d_name = 'delta_' + definition.name + d_name = "delta_" + definition.name if definition.symbol: - d_symbol = 'Δ' + definition.symbol + d_symbol = "Δ" + definition.symbol else: d_symbol = None - d_aliases = tuple('Δ' + alias for alias in definition.aliases) - - d_reference = UnitsContainer(dict((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) + 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 @@ -301,14 +423,18 @@ """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) + 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) + 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) + if " " in alias: + logger.warn("Alias cannot contain a space: " + alias) self._define_single_adder(alias, definition, unit_dict, casei_unit_dict) @@ -318,26 +444,40 @@ It warns or raise error on redefinition. """ if key in unit_dict: - if self._on_redefinition == 'raise': + if self._on_redefinition == "raise": raise RedefinitionError(key, type(value)) - elif self._on_redefinition == 'warn': - logger.warning("Redefining '%s' (%s)", 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.. - :param prefix: string identifying the section (e.g. @context) - :param parserfunc: A function that is able to parse a Definition section. - :type parserfunc: SourceIterator -> None + 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 = dict() + self._parsers = {} - if prefix and prefix[0] == '@': + if prefix and prefix[0] == "@": self._parsers[prefix] = parserfunc else: raise ValueError("Prefix directives must start with '@'") @@ -345,32 +485,42 @@ def load_definitions(self, file, is_resource=False): """Add units and prefixes defined in a definition text file. - :param file: can be a filename or a line iterable. - :param is_resource: used to indicate that the file is a resource file - and therefore should be loaded from the package. + 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, string_types): + 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) + return self.load_definitions( + StringIO(rbytes.decode("utf-8")), is_resource + ) else: - with open(file, encoding='utf-8') as fp: + 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)) + 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 and line[0] == '@': - if line.startswith('@import'): + if line.startswith("@") and not line.startswith("@alias"): + if line.startswith("@import"): if is_resource: path = line[7:].strip() else: @@ -383,10 +533,14 @@ else: parts = _BLOCK_RE.split(line) - loader = self._parsers.get(parts[0], None) if self._parsers else None + loader = ( + self._parsers.get(parts[0], None) if self._parsers else None + ) if loader is None: - raise DefinitionSyntaxError('Unknown directive %s' % line, lineno=no) + raise DefinitionSyntaxError( + "Unknown directive %s" % line, lineno=no + ) try: loader(ifile) @@ -405,105 +559,90 @@ logger.error("In line {}, cannot add '{}' {}".format(no, line, ex)) def _build_cache(self): - """Build a cache of dimensionality and base units. - """ - self._dimensional_equivalents = dict() + """Build a cache of dimensionality and base units.""" + self._cache = RegistryCache() - deps = dict((name, set(definition.reference.keys() if definition.reference else {})) - for name, definition in self._units.items()) + 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: + if "[" in unit_name: continue - parsed_names = tuple(self.parse_unit_name(unit_name)) - _prefix = None + parsed_names = self.parse_unit_name(unit_name) if parsed_names: - _prefix, base_name, _suffix = parsed_names[0] + prefix, base_name, _ = parsed_names[0] else: - base_name = unit_name - prefixed = True if _prefix else False + prefix, base_name = "", unit_name + try: uc = ParserHelper.from_word(base_name) bu = self._get_root_units(uc) di = self._get_dimensionality(uc) - self._root_units_cache[uc] = bu - self._dimensionality_cache[uc] = di - - if not prefixed: - if di not in self._dimensional_equivalents: - self._dimensional_equivalents[di] = set() + self._cache.root_units[uc] = bu + self._cache.dimensionality[uc] = di - self._dimensional_equivalents[di].add(self._units[base_name]._name) + if not prefix: + dimeq_set = self._cache.dimensional_equivalents.setdefault( + di, set() + ) + dimeq_set.add(self._units[base_name]._name) - except Exception as e: - logger.warning('Could not resolve {0}: {1!r}'.format(unit_name, e)) - - def _dedup_candidates(self, candidates): - """Given a list of unit triplets (prefix, name, suffix), - remove those with different names but equal value. - - e.g. ('kilo', 'gram', '') and ('', 'kilogram', '') - """ - candidates = tuple(candidates) - if len(candidates) < 2: - return candidates - - unique = [candidates[0]] - for c in candidates[2:]: - for u in unique: - if c == u: - break - else: - unique.append(c) - - return tuple(unique) + 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 '' + if name_or_alias == "dimensionless": + return "" try: return self._units[name_or_alias]._name except KeyError: pass - candidates = self._dedup_candidates(self.parse_unit_name(name_or_alias, case_sensitive)) + 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)) + 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})) + 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 + """Return the preferred alias for a unit. """ - candidates = self._dedup_candidates(self.parse_unit_name(name_or_alias)) + 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)) + 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 @@ -514,42 +653,39 @@ def get_dimensionality(self, input_units): """Convert unit or dict of units or dimensions to a dict of base dimensions dimensions - - :param input_units: - :return: dimensionality """ 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. - - :param input_units: - :return: dimensionality + """Convert a UnitsContainer to base dimensions. """ if not input_units: return UnitsContainer() - if input_units in self._dimensionality_cache: - return self._dimensionality_cache[input_units] + 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['[]'] + if "[]" in accumulator: + del accumulator["[]"] - dims = UnitsContainer(dict((k, v) for k, v in accumulator.items() - if v != 0.0)) + dims = UnitsContainer({k: v for k, v in accumulator.items() if v != 0.0}) - self._dimensionality_cache[input_units] = dims + cache[input_units] = dims return dims def _get_dimensionality_recurse(self, ref, exp, accumulator): for key in ref: - exp2 = exp*ref[key] + exp2 = exp * ref[key] if _is_dim(key): reg = self._dimensions[key] if reg.is_base: @@ -562,24 +698,32 @@ 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. - :param unit1: first unit - :type unit1: UnitsContainer compatible (str, Unit, UnitsContainer, dict) - :param unit2: second unit - :type unit2: UnitsContainer compatible (str, Unit, UnitsContainer, dict) - :returns: exponential proportionality or None if the units cannot be converted + """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 + # 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 + 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()) + 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 + if all(r == first for r in ratios): # all are same, we're good return first return None @@ -589,12 +733,20 @@ If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. - :param input_units: units - :type input_units: UnitsContainer or str - :param check_nonmult: if True, None will be returned as the - multiplicative factor if a non-multiplicative - units is found in the final Units. - :return: multiplicative factor, 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) + + Returns + ------- + Number, pint.Unit + multiplicative factor, base units + """ input_units = to_units_container(input_units) @@ -608,36 +760,42 @@ If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. - :param input_units: units - :type input_units: UnitsContainer or dict - :param check_nonmult: if True, None will be returned as the - multiplicative factor if a non-multiplicative - units is found in the final Units. - :return: multiplicative factor, base units + 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., UnitsContainer() + return 1.0, UnitsContainer() - # The cache is only done for check_nonmult=True - if check_nonmult and input_units in self._root_units_cache: - return self._root_units_cache[input_units] + cache = self._cache.root_units + try: + return cache[input_units] + except KeyError: + pass - accumulators = [1., defaultdict(float)] + accumulators = [1.0, defaultdict(float)] self._get_root_units_recurse(input_units, 1.0, accumulators) factor = accumulators[0] - units = UnitsContainer(dict((k, v) for k, v in accumulators[1].items() - if v != 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: - for unit in units.keys(): - if not self._units[unit].converter.is_multiplicative: - return None, units - - if check_nonmult: - self._root_units_cache[input_units] = factor, units + 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): @@ -646,19 +804,29 @@ If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. - :param input_units: units - :type input_units: UnitsContainer or str - :param check_nonmult: if True, None will be returned as the - multiplicative factor if a non-multiplicative - units is found in the final Units. - :return: multiplicative factor, base units + 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 sorted(ref): - exp2 = exp*ref[key] + for key in ref: + exp2 = exp * ref[key] key = self.get_name(key) reg = self._units[key] if reg.is_base: @@ -666,8 +834,7 @@ else: accumulators[0] *= reg._converter.scale ** exp2 if reg.reference is not None: - self._get_root_units_recurse(reg.reference, exp2, - accumulators) + self._get_root_units_recurse(reg.reference, exp2, accumulators) def get_compatible_units(self, input_units, group_or_system=None): """ @@ -685,21 +852,27 @@ return frozenset() src_dim = self._get_dimensionality(input_units) - - ret = self._dimensional_equivalents[src_dim] - - return ret + return self._cache.dimensional_equivalents[src_dim] def convert(self, value, src, dst, inplace=False): """Convert value from some source to destination units. - :param value: value - :param src: source units. - :type src: Quantity or str - :param dst: destination units. - :type dst: Quantity or str + 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 - :return: converted value """ src = to_units_container(src, self) @@ -713,13 +886,24 @@ def _convert(self, value, src, dst, inplace=False, check_dimensionality=True): """Convert value from some source to destination units. - :param value: value - :param src: source units. - :type src: UnitsContainer - :param dst: destination units. - :type dst: UnitsContainer + Parameters + ---------- + value : + value + src : UnitsContainer + source units. + dst : UnitsContainer + destination units. + inplace : + (Default value = False) + check_dimensionality : + (Default value = True) + + Returns + ------- + type + converted value - :return: converted value """ if check_dimensionality: @@ -734,7 +918,7 @@ # Here src and dst have only multiplicative units left. Thus we can # convert with a factor. - factor, units = self._get_root_units(src / dst) + 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 @@ -753,28 +937,70 @@ 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. - :rtype: (str, str, str) + 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):] + name = unit_name[len(prefix) :] if suffix: - name = name[:-len(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]) + 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]) + 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 @@ -782,24 +1008,33 @@ The expression can only contain products, ratios and powers of units. - :param as_delta: if the expression has multiple units, the parser will - interpret non multiplicative units as their `delta_` counterparts. + 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 + ------- - :raises: - :class:`pint.UndefinedUnitError` if a unit is not in the registry - :class:`ValueError` if the expression is invalid. """ + 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=None): - """ + def _parse_units(self, input_string, as_delta=True): + """Parse a units expression and returns a UnitContainer with + the canonical names. """ - if as_delta is None: - as_delta = True - if as_delta and input_string in self._parse_unit_cache: - return self._parse_unit_cache[input_string] + cache = self._cache.parse_unit + if as_delta: + try: + return cache[input_string] + except KeyError: + pass if not input_string: return UnitsContainer() @@ -809,7 +1044,7 @@ units = ParserHelper.from_string(input_string) if units.scale != 1: - raise ValueError('Unit expression cannot have a scaling factor.') + raise ValueError("Unit expression cannot have a scaling factor.") ret = {} many = len(units) > 1 @@ -821,50 +1056,73 @@ if as_delta and (many or (not many and value != 1)): definition = self._units[cname] if not definition.is_multiplicative: - cname = 'delta_' + cname + cname = "delta_" + cname ret[cname] = value ret = UnitsContainer(ret) if as_delta: - self._parse_unit_cache[input_string] = ret + cache[input_string] = ret return ret - def _eval_token(self, token, case_sensitive=True, **values): + 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 == 'pi': - return self.Quantity(math.pi) - elif token_text == 'dimensionless': + 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})) + return self.Quantity( + 1, + UnitsContainer( + {self.get_name(token_text, case_sensitive=case_sensitive): 1} + ), + ) elif token_type == NUMBER: - return ParserHelper.eval_token(token) + return ParserHelper.eval_token(token, use_decimal=use_decimal) else: - raise Exception('unknown token type') + raise Exception("unknown token type") - def parse_expression(self, input_string, case_sensitive=True, **values): + 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, - **values)) + return build_eval_tree(gen).evaluate( + lambda x: self._eval_token( + x, case_sensitive=case_sensitive, use_decimal=use_decimal, **values + ) + ) __call__ = parse_expression @@ -876,14 +1134,21 @@ - Register non-multiplicative units and their relations. - Convert between non-multiplicative units. - :param default_as_delta: If True, non-multiplicative units are interpreted as - their *delta* counterparts in multiplications. - :param autoconvert_offset_to_baseunit: If True, non-multiplicative units are - converted to base units in multiplications. + 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(NonMultiplicativeRegistry, self).__init__(**kwargs) + 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. @@ -899,7 +1164,7 @@ if as_delta is None: as_delta = self.default_as_delta - return super(NonMultiplicativeRegistry, self)._parse_units(input_string, as_delta) + return super()._parse_units(input_string, as_delta) def _define(self, definition): """Add unit to the registry. @@ -907,13 +1172,19 @@ In addition to what is done by the BaseRegistry, registers also non-multiplicative units. - :param definition: a dimension, unit or prefix definition. - :type definition: str | Definition - :return: Definition instance, case sensitive unit dict, case insensitive unit dict. - :rtype: Definition, dict, dict + 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(NonMultiplicativeRegistry, self)._define(definition) + definition, d, di = super()._define(definition) # define additional units for units with an offset if getattr(definition.converter, "offset", 0.0) != 0.0: @@ -928,22 +1199,24 @@ # 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. - l = self._dedup_candidates(self.parse_unit_name(u)) + names = self.parse_unit_name(u) + assert len(names) == 1 + _, base_name, _ = names[0] try: - u = l[0][1] - return self._units[u].is_multiplicative + 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)] + 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.') + raise ValueError("more than one offset unit.") elif len(nonmult_units) == 1: # A single src offset unit is present. Extract it @@ -953,10 +1226,10 @@ nonmult_unit, exponent = nonmult_units.pop() if exponent != 1: - raise ValueError('offset units in higher order.') + 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.') + raise ValueError("offset unit used in multiplicative context.") return nonmult_unit @@ -968,13 +1241,22 @@ In addition to what is done by the BaseRegistry, converts between non-multiplicative units. - :param value: value - :param src: source units. - :type src: UnitsContainer - :param dst: destination units. - :type dst: UnitsContainer + Parameters + ---------- + value : + value + src : UnitsContainer + source units. + dst : UnitsContainer + destination units. + inplace : + (Default value = False) + + Returns + ------- + type + converted value - :return: converted value """ # Conversion needs to consider if non-multiplicative (AKA offset @@ -984,15 +1266,17 @@ try: src_offset_unit = self._validate_and_extract(src) except ValueError as ex: - raise DimensionalityError(src, dst, extra_msg=' - In source units, %s ' % 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=' - In destination units, %s ' % ex) + raise DimensionalityError( + src, dst, extra_msg=f" - In destination units, {ex}" + ) if not (src_offset_unit or dst_offset_unit): - return super(NonMultiplicativeRegistry, self)._convert(value, src, dst, inplace) + return super()._convert(value, src, dst, inplace) src_dim = self._get_dimensionality(src) dst_dim = self._get_dimensionality(dst) @@ -1005,18 +1289,20 @@ # 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]) + src = src.remove([src_offset_unit]) # clean dst units from offset units - dst = dst.remove([dst_offset_unit]) + if dst_offset_unit: + dst = dst.remove([dst_offset_unit]) # Convert non multiplicative units to the dst. - value = super(NonMultiplicativeRegistry, self)._convert(value, src, dst, inplace, False) + 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) + value = self._units[dst_offset_unit].converter.from_reference( + value, inplace + ) return value @@ -1029,53 +1315,67 @@ (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): - super(ContextRegistry, self).__init__(**kwargs) - - #: Map context name (string) or abbreviation to context. + # Map context name (string) or abbreviation to context. self._contexts = {} - - #: Stores active 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(ContextRegistry, self)._register_parsers() - self._register_parser('@context', self._parse_context) + 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)) + self.add_context( + Context.from_lines(ifile.block_iter(), self.get_dimensionality) + ) except KeyError as e: - raise DefinitionSyntaxError('unknown dimension {} in context'.format(str(e))) + raise DefinitionSyntaxError(f"unknown dimension {e} in context") - def add_context(self, 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. Use `enable_contexts`. + 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) + 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) + logger.warning( + "The name %s was already registered for another context", + context.name, + ) self._contexts[alias] = context - def remove_context(self, name_or_alias): + 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. Use `disable_contexts`. + Notice that this methods will not disable the context; + see :meth:`disable_contexts`. """ context = self._contexts[name_or_alias] @@ -1085,11 +1385,103 @@ return context - def enable_contexts(self, *names_or_contexts, **kwargs): + 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. - :param names_or_contexts: sequence of the contexts or contexts names/alias - :param kwargs: keyword arguments for the context + 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 @@ -1097,76 +1489,85 @@ kwargs = dict(self._active_ctx.defaults, **kwargs) # For each name, we first find the corresponding context - ctxs = list((self._contexts[name] if isinstance(name, string_types) else name) - for name in names_or_contexts) + 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 getattr(ctx, '_checked', False): + if ctx.checked: continue - for (src, dst), func in ctx.funcs.items(): + 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 + ctx.checked = True # and create a new one with the new defaults. - ctxs = tuple(Context.from_context(ctx, **kwargs) - for ctx in ctxs) + 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._build_cache() + self._switch_context_cache_and_units() - def disable_contexts(self, n=None): + 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. """ - if n is None: - n = len(self._contexts) self._active_ctx.remove_contexts(n) - self._build_cache() + 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. - :param names: name of the context. - :param kwargs: keyword arguments for the contexts. - - Context are called by their name:: + 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 + >>> with ureg.context('one'): + ... pass - If the context has an argument, you can specify its value as a keyword - argument:: + If a context has an argument, you can specify its value as a keyword argument:: - >>> with ureg.context('one', n=1): - ... pass + >>> with ureg.context('one', n=1): + ... pass Multiple contexts can be entered in single call: - >>> with ureg.context('one', 'two', n=1): - ... pass + >>> with ureg.context('one', 'two', n=1): + ... pass - or nested allowing you to give different values to the same keyword argument:: + 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 + >>> 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): - ... with ureg.context('two'): # Here n takes the value of the upper context - ... pass - + >>> 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) @@ -1179,29 +1580,44 @@ # the added contexts are removed from the active one. self.disable_contexts(len(names)) - def with_context(self, name, **kw): + 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:: - >>> @ureg.with_context('sp') + 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')) - - - :param names: name of the context. - :param kwargs: keyword arguments for the contexts. - :return: the wrapped function. """ + 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)) + 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, **kwargs): - with self.context(name, **kw): - return func(*values, **kwargs) + def wrapper(*values, **wrapper_kwargs): + with self.context(name, **kwargs): + return func(*values, **wrapper_kwargs) return wrapper @@ -1214,15 +1630,22 @@ converts between units with different dimensions by following transformation rules defined in the context. - :param value: value - :param src: source units. - :type src: UnitsContainer - :param dst: destination units. - :type dst: UnitsContainer - - :return: converted value + 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. @@ -1239,21 +1662,19 @@ value, src = src._magnitude, src._units - return super(ContextRegistry, self)._convert(value, src, dst, inplace) + 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(ContextRegistry, self)._get_compatible_units(input_units, group_or_system) + 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._dimensional_equivalents[node] + ret |= self._cache.dimensional_equivalents[node] return ret @@ -1266,15 +1687,15 @@ (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(SystemRegistry, self).__init__(**kwargs) + super().__init__(**kwargs) #: Map system name to system. #: :type: dict[ str | System] @@ -1286,27 +1707,45 @@ #: 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._groups['root'] = self.Group('root') self.System = systems.build_system_class(self) - self._default_system = system - def _after_init(self): - super(SystemRegistry, self)._after_init() + """Invoked at the end of ``__init__``. + + - Create default group and add all orphan units to it + - Set default system + """ + super()._after_init() - #: Copy units in root group to the default group - if 'group' in self._defaults: - grp = self.get_group(self._defaults['group'], True) - grp.add_units(*self.get_group('root', False).non_inherited_unit_names) + #: 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) + self._default_system = self._default_system or self._defaults.get( + "system", None + ) def _register_parsers(self): - super(SystemRegistry, self)._register_parsers() - self._register_parser('@group', self._parse_group) - self._register_parser('@system', self._parse_system) + 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) @@ -1317,15 +1756,24 @@ def get_group(self, name, create_if_needed=True): """Return a Group. - :param name: Name of the group to be - :param create_if_needed: Create a group if not Found. If False, raise an Exception. - :return: 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) + raise ValueError("Unkown group %s" % name) return self.Group(name) @@ -1341,7 +1789,7 @@ def default_system(self, name): if name: if name not in self._systems: - raise ValueError('Unknown system %s' % name) + raise ValueError("Unknown system %s" % name) self._base_units_cache = {} @@ -1350,15 +1798,25 @@ def get_system(self, name, create_if_needed=True): """Return a Group. - :param name: Name of the group to be - :param create_if_needed: Create a group if not Found. If False, raise an Exception. - :return: System + 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) + raise ValueError("Unkown system %s" % name) return self.System(name) @@ -1367,11 +1825,11 @@ # In addition to the what is done by the BaseRegistry, # this adds all units to the `root` group. - definition, d, di = super(SystemRegistry, self)._define(definition) + 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) + self.get_group("root").add_units(definition.name) return definition, d, di @@ -1384,12 +1842,22 @@ Unlike BaseRegistry, in this registry root_units might be different from base_units - :param input_units: units - :type input_units: UnitsContainer or str - :param check_nonmult: if True, None will be returned as the - multiplicative factor if a non-multiplicative - units is found in the final Units. - :return: multiplicative factor, 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) @@ -1404,7 +1872,11 @@ 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: + 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) @@ -1436,13 +1908,11 @@ 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(SystemRegistry, self)._get_compatible_units(input_units, group_or_system) + ret = super()._get_compatible_units(input_units, group_or_system) if group_or_system: if group_or_system in self._systems: @@ -1450,8 +1920,10 @@ 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.intersection(members)) + raise ValueError( + "Unknown Group o System with name '%s'" % group_or_system + ) + return frozenset(ret & members) return ret @@ -1459,48 +1931,90 @@ class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry): """The unit registry stores the definitions and relationships between units. - :param 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. - :param force_ndarray: convert any input, scalar or not to a numpy.ndarray. - :param default_as_delta: In the context of a multiplication of units, interpret - non-multiplicative units as their *delta* counterparts. - :param 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. - :param on_redefinition: action to take in case a unit is redefined. - 'warn', 'raise', 'ignore' - :type on_redefinition: str - :param auto_reduce_dimensions: If True, reduce dimensionality on appropriate operations. + 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, default_as_delta=True, - autoconvert_offset_to_baseunit=False, - on_redefinition='warn', system=None, - auto_reduce_dimensions=False): - - super(UnitRegistry, self).__init__(filename=filename, force_ndarray=force_ndarray, - 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) + 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 - :param quantities: mapping between variable name and units - :type quantities: dict - :return: a list of dimensionless quantities expressed as dicts + + 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. - :param enable: whether support should be enabled or disabled - :type enable: bool + + 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 @@ -1508,27 +2022,26 @@ check = registry_helpers.check -class LazyRegistry(object): - +class LazyRegistry: def __init__(self, args=None, kwargs=None): - self.__dict__['params'] = args or (), kwargs or {} + self.__dict__["params"] = args or (), kwargs or {} def __init(self): - args, kwargs = self.__dict__['params'] - kwargs['on_redefinition'] = 'raise' + 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' + if item == "_on_redefinition": + return "raise" self.__init() return getattr(self, item) def __setattr__(self, key, value): - if key == '__class__': - super(LazyRegistry, self).__setattr__(key, value) + if key == "__class__": + super().__setattr__(key, value) else: self.__init() setattr(self, key, value) diff -Nru python-pint-0.9/pint/systems.py python-pint-0.10.1/pint/systems.py --- python-pint-0.9/pint/systems.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/systems.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.systems ~~~~~~~~~~~~ @@ -9,18 +8,23 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import - 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 to_units_container, SharedRegistryObject, SourceIterator, logger -from .babel_names import _babel_systems -from pint.compat import Loc +from .util import ( + SharedRegistryObject, + SourceIterator, + getattr_maybe_raise, + logger, + to_units_container, +) -class _Group(SharedRegistryObject): +class Group(SharedRegistryObject): """A group is a set of units. Units can be added directly or by including other groups. @@ -30,18 +34,17 @@ The group belongs to one Registry. - It can be specified in the definition file as: + 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('@group\s+(?P\w+)\s*(using\s(?P.*))*') + _header_re = re.compile(r"@group\s+(?P\w+)\s*(using\s(?P.*))*") def __init__(self, name): """ @@ -71,23 +74,21 @@ # Add this group to the group dictionary self._REGISTRY._groups[self.name] = self - if name != 'root': + if name != "root": # All groups are added to root group - self._REGISTRY._groups['root'].add_groups(name) + 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. - :rtype: frozenset[str] """ if self._computed_members is None: self._computed_members = set(self._unit_names) @@ -100,8 +101,7 @@ return self._computed_members def invalidate_members(self): - """Invalidate computed members in this Group and all parent nodes. - """ + """Invalidate computed members in this Group and all parent nodes.""" self._computed_members = None d = self._REGISTRY._groups for name in self._used_by: @@ -124,8 +124,6 @@ def add_units(self, *unit_names): """Add units to group. - - :type unit_names: str """ for unit_name in unit_names: self._unit_names.add(unit_name) @@ -138,8 +136,6 @@ def remove_units(self, *unit_names): """Remove units from group. - - :type unit_names: str """ for unit_name in unit_names: self._unit_names.remove(unit_name) @@ -148,8 +144,6 @@ def add_groups(self, *group_names): """Add groups to group. - - :type group_names: str """ d = self._REGISTRY._groups for group_name in group_names: @@ -157,7 +151,10 @@ grp = d[group_name] if grp.is_used_group(self.name): - raise ValueError('Cyclic relationship found between %s and %s' % (self.name, group_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) @@ -166,8 +163,6 @@ def remove_groups(self, *group_names): """Remove groups from group. - - :type group_names: str """ d = self._REGISTRY._groups for group_name in group_names: @@ -182,10 +177,17 @@ def from_lines(cls, lines, define_func): """Return a Group object parsing an iterable of lines. - :param lines: iterable - :type lines: list[str] - :param define_func: Function to define a unit in the registry. - :type define_func: str -> None + 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) @@ -195,21 +197,24 @@ if r is None: raise ValueError("Invalid Group header syntax: '%s'" % header) - name = r.groupdict()['name'].strip() - groups = r.groupdict()['used_groups'] + name = r.groupdict()["name"].strip() + groups = r.groupdict()["used_groups"] if groups: - group_names = tuple(a.strip() for a in groups.split(',')) + group_names = tuple(a.strip() for a in groups.split(",")) else: group_names = () unit_names = [] for lineno, line in lines: - if '=' in line: + 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 %s' % type(definition), lineno=lineno) + raise DefinitionSyntaxError( + "Only UnitDefinition are valid inside _used_groups, not " + + str(definition), + lineno=lineno, + ) try: define_func(definition) @@ -232,10 +237,11 @@ return grp def __getattr__(self, item): + getattr_maybe_raise(self, item) return self._REGISTRY -class _System(SharedRegistryObject): +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 @@ -243,7 +249,7 @@ The System belongs to one Registry. - It can be specified in the definition file as: + It can be specified in the definition file as:: @system [using , ..., ] @@ -263,7 +269,7 @@ """ #: Regex to match the header parts of a context. - _header_re = re.compile('@system\s+(?P\w+)\s*(using\s(?P.*))*') + _header_re = re.compile(r"@system\s+(?P\w+)\s*(using\s(?P.*))*") def __init__(self, name): """ @@ -297,7 +303,8 @@ return list(self.members) def __getattr__(self, item): - u = getattr(self._REGISTRY, self.name + '_' + item, None) + 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) @@ -312,21 +319,22 @@ try: self._computed_members |= d[group_name].members except KeyError: - logger.warning('Could not resolve {} in System {}'.format(group_name, self.name)) + 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. - """ + """Invalidate computed members in this Group and all parent nodes.""" self._computed_members = None def add_groups(self, *group_names): """Add groups to group. - - :type group_names: str """ self._used_groups |= set(group_names) @@ -334,21 +342,17 @@ def remove_groups(self, *group_names): """Remove groups from group. - - :type group_names: str """ self._used_groups -= set(group_names) self.invalidate_members() def format_babel(self, locale): - """translate the name of the system - - :type locale: Locale + """translate the name of the system. """ if locale and self.name in _babel_systems: name = _babel_systems[self.name] - locale = Loc.parse(locale) + locale = babel_parse(locale) return locale.measurement_systems[name] return self.name @@ -363,14 +367,14 @@ if r is None: raise ValueError("Invalid System header syntax '%s'" % header) - name = r.groupdict()['name'].strip() - groups = r.groupdict()['used_groups'] + 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(',')) + group_names = tuple(a.strip() for a in groups.split(",")) else: - group_names = ('root', ) + group_names = ("root",) base_unit_names = {} derived_unit_names = [] @@ -381,28 +385,33 @@ # - 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: + if ":" in line: # The syntax is new_unit:old_unit - new_unit, old_unit = line.split(':') + 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) + 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') + 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 = dict((new_unit, -1./value) - for new_unit, value in new_unit_expanded.items() - if new_unit != old_unit) + new_unit_dict = { + new_unit: -1.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 @@ -415,11 +424,13 @@ 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.') + raise ValueError( + "The new base must be a root dimension if not discarded unit is specified." + ) old_unit, value = dict(old_unit_dict).popitem() - base_unit_names[old_unit] = {new_unit: 1./value} + base_unit_names[old_unit] = {new_unit: 1.0 / value} system = cls(name) @@ -431,8 +442,7 @@ return system -class Lister(object): - +class Lister: def __init__(self, d): self.d = d @@ -440,22 +450,23 @@ return list(self.d.keys()) def __getattr__(self, item): + getattr_maybe_raise(self, item) return self.d[item] -def build_group_class(registry): +_Group = Group +_System = System + +def build_group_class(registry): class Group(_Group): - pass + _REGISTRY = registry - Group._REGISTRY = registry return Group def build_system_class(registry): - class System(_System): - pass + _REGISTRY = registry - System._REGISTRY = registry return System Binary files /tmp/tmpWQPJkb/qjldTRfSmA/python-pint-0.9/pint/testsuite/baseline/test_basic_plot.png and /tmp/tmpWQPJkb/Db9_v8xmJJ/python-pint-0.10.1/pint/testsuite/baseline/test_basic_plot.png differ Binary files /tmp/tmpWQPJkb/qjldTRfSmA/python-pint-0.9/pint/testsuite/baseline/test_plot_with_set_units.png and /tmp/tmpWQPJkb/Db9_v8xmJJ/python-pint-0.10.1/pint/testsuite/baseline/test_plot_with_set_units.png differ diff -Nru python-pint-0.9/pint/testsuite/helpers.py python-pint-0.10.1/pint/testsuite/helpers.py --- python-pint-0.9/pint/testsuite/helpers.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/helpers.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,109 +1,142 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - - import doctest -from distutils.version import StrictVersion import re import unittest +from distutils.version import StrictVersion -from pint.compat import HAS_NUMPY, HAS_PROPER_BABEL, HAS_UNCERTAINTIES, NUMPY_VER, PYTHON3 +from ..compat import ( + HAS_BABEL, + HAS_NUMPY, + HAS_NUMPY_ARRAY_FUNCTION, + HAS_UNCERTAINTIES, + NUMPY_VER, +) -def requires_numpy18(): +def requires_array_function_protocol(): if not HAS_NUMPY: - return unittest.skip('Requires NumPy') - return unittest.skipUnless(StrictVersion(NUMPY_VER) >= StrictVersion('1.8'), 'Requires NumPy >= 1.8') + return unittest.skip("Requires NumPy") + return unittest.skipUnless( + HAS_NUMPY_ARRAY_FUNCTION, "Requires __array_function__ protocol to be enabled" + ) -def requires_numpy_previous_than(version): +def requires_not_array_function_protocol(): if not HAS_NUMPY: - return unittest.skip('Requires NumPy') - return unittest.skipUnless(StrictVersion(NUMPY_VER) < StrictVersion(version), 'Requires NumPy < %s' % version) - + return unittest.skip("Requires NumPy") + return unittest.skipIf( + HAS_NUMPY_ARRAY_FUNCTION, + "Requires __array_function__ protocol to be unavailable or disabled", + ) -def requires_numpy(): - return unittest.skipUnless(HAS_NUMPY, 'Requires NumPy') +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_not_numpy(): - return unittest.skipIf(HAS_NUMPY, 'Requires NumPy is not installed.') +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_proper_babel(): - return unittest.skipUnless(HAS_PROPER_BABEL, 'Requires Babel with units support') +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_uncertainties(): - return unittest.skipUnless(HAS_UNCERTAINTIES, 'Requires Uncertainties') +def requires_numpy(): + return unittest.skipUnless(HAS_NUMPY, "Requires NumPy") -def requires_not_uncertainties(): - return unittest.skipIf(HAS_UNCERTAINTIES, 'Requires Uncertainties is not installed.') +def requires_not_numpy(): + return unittest.skipIf(HAS_NUMPY, "Requires NumPy is not installed.") -def requires_python2(): - return unittest.skipIf(PYTHON3, 'Requires Python 2.X.') +def requires_babel(): + return unittest.skipUnless(HAS_BABEL, "Requires Babel with units support") -def requires_python3(): - return unittest.skipUnless(PYTHON3, 'Requires Python 3.X.') +def requires_uncertainties(): + return unittest.skipUnless(HAS_UNCERTAINTIES, "Requires Uncertainties") -_number_re = '([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)' -_q_re = re.compile('%s)' % _number_re + - '\s*,\s*' + "'(?P.*)'" + '\s*' + '\)>') -_sq_re = re.compile('\s*' + '(?P%s)' % _number_re + - '\s' + "(?P.*)") +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('') +_unit_re = re.compile(r"") class PintOutputChecker(doctest.OutputChecker): - def check_output(self, want, got, optionflags): - check = super(PintOutputChecker, self).check_output(want, got, optionflags) + check = super().check_output(want, got, optionflags) if check: return check try: if eval(want) == eval(got): return True - except: + 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() + 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']) + 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']: + if parsed_got["unit"] != parsed_want["unit"]: return False return True - except: + except Exception: pass cnt = 0 - for regex in (_unit_re, ): + for regex in (_unit_re,): try: - parsed_got, tmp = regex.subn('\1', got) + parsed_got, tmp = regex.subn("\1", got) cnt += tmp - parsed_want, temp = regex.subn('\1', want) + parsed_want, temp = regex.subn("\1", want) cnt += tmp if parsed_got == parsed_want: return True - except: + except Exception: pass if cnt: @@ -111,4 +144,3 @@ return self.check_output(parsed_want, parsed_got, optionflags) return False - diff -Nru python-pint-0.9/pint/testsuite/__init__.py python-pint-0.10.1/pint/testsuite/__init__.py --- python-pint-0.9/pint/testsuite/__init__.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/__init__.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,25 +1,17 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - import doctest import logging +import math import os -import sys 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 import logger, UnitRegistry -from pint.quantity import _Quantity from pint.testsuite.helpers import PintOutputChecker -from logging.handlers import BufferingHandler 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 @@ -48,10 +40,10 @@ th.setLevel(level) logger.addHandler(th) if self._test_handler is not None: - l = len(self._test_handler.buffer) + buflen = len(self._test_handler.buffer) yield th.buffer if self._test_handler is not None: - self._test_handler.buffer = self._test_handler.buffer[:l] + self._test_handler.buffer = self._test_handler.buffer[:buflen] def setUp(self): self._test_handler = None @@ -63,9 +55,8 @@ def tearDown(self): if self._test_handler is not None: buf = self._test_handler.buffer - l = len(buf) - msg = '\n'.join(record.get('msg', str(record)) for record in buf) - self.assertEqual(l, 0, msg='%d warnings raised.\n%s' % (l, msg)) + 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): @@ -79,17 +70,23 @@ cls.U_ = cls.ureg.Unit def _get_comparable_magnitudes(self, first, second, msg): - if isinstance(first, _Quantity) and isinstance(second, _Quantity): + if isinstance(first, Quantity) and isinstance(second, Quantity): second = second.to(first) - self.assertEqual(first.units, second.units, msg=msg + ' Units are not equal.') + 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('') + 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('') + 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 @@ -98,7 +95,7 @@ def assertQuantityEqual(self, first, second, msg=None): if msg is None: - msg = 'Comparing %r and %r. ' % (first, second) + msg = "Comparing %r and %r. " % (first, second) m1, m2 = self._get_comparable_magnitudes(first, second, msg) @@ -109,7 +106,7 @@ def assertQuantityAlmostEqual(self, first, second, rtol=1e-07, atol=0, msg=None): if msg is None: - msg = 'Comparing %r and %r. ' % (first, second) + msg = "Comparing %r and %r. " % (first, second) m1, m2 = self._get_comparable_magnitudes(first, second, msg) @@ -120,15 +117,16 @@ def testsuite(): - """A testsuite that has all the pint tests. - """ + """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 yaml, serialize + import serialize # noqa: F401 + import yaml # noqa: F401 + add_docs(suite) except ImportError: pass @@ -136,32 +134,35 @@ def main(): - """Runs the testsuite as command line application. - """ + """Runs the testsuite as command line application.""" try: unittest.main() except Exception as e: - print('Error: %s' % 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()) - -import math - _GLOBS = { - 'wrapping.rst': { - 'pendulum_period': lambda length: 2*math.pi*math.sqrt(length/9.806650), - 'pendulum_period2': lambda length, swing_amplitude: 1., - 'pendulum_period_maxspeed': lambda length, swing_amplitude: (1., 2.), - 'pendulum_period_error': lambda length: (1., False), + "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), } } @@ -169,18 +170,29 @@ def add_docs(suite): """Add docs to suite - :type suite: unittest.TestSuite + Parameters + ---------- + suite : + + + Returns + ------- + """ - docpath = os.path.join(os.path.dirname(__file__), '..', '..', 'docs') + 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')): + 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))) + suite.addTest( + doctest.DocFileSuite( + file, + module_relative=False, + checker=checker, + globs=_GLOBS.get(name, None), + ) + ) def test_docs(): @@ -188,5 +200,3 @@ add_docs(suite) runner = unittest.TextTestRunner() return runner.run(suite) - - diff -Nru python-pint-0.9/pint/testsuite/parameterized.py python-pint-0.10.1/pint/testsuite/parameterized.py --- python-pint-0.9/pint/testsuite/parameterized.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/parameterized.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Adds Parameterized tests for Python's unittest module # # Code from: parameterizedtestcase, version: 0.1.0 @@ -7,11 +5,6 @@ # Author: Marc Abramowitz, email: marc@marc-abramowitz.com # License: MIT # -# Fixed for to work in Python 2 & 3 with "add_metaclass" decorator from six -# https://pypi.python.org/pypi/six -# Author: Benjamin Peterson -# License: MIT -# # Use like this: # # from parameterizedtestcase import ParameterizedTestCase @@ -28,33 +21,23 @@ # def test_eval(self, input, expected_output): # self.assertEqual(eval(input), expected_output) -from functools import wraps -import collections import unittest +from collections.abc import Callable +from functools import wraps + -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -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)]) +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) + method.__name__, + new_class_dict.get("__module__", ""), + classname, + param_assignments_str, + ) try: new_method.__doc__ = extra_doc + new_method.__doc__ @@ -69,7 +52,7 @@ new_class_dict = {} for attr_name, attr_value in list(class_dict.items()): - if isinstance(attr_value, collections.Callable) and hasattr(attr_value, 'param_names'): + if isinstance(attr_value, Callable) and hasattr(attr_value, "param_names"): # print("Processing attr_name = %r; attr_value = %r" % ( # attr_name, attr_value)) @@ -79,8 +62,13 @@ func_name_format = attr_value.func_name_format meta.process_method( - classname, method, param_names, data, new_class_dict, - func_name_format) + classname, + method, + param_names, + data, + new_class_dict, + func_name_format, + ) else: new_class_dict[attr_name] = attr_value @@ -88,23 +76,22 @@ @classmethod def process_method( - cls, classname, method, param_names, data, new_class_dict, - func_name_format): + 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 + 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__] + 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) + method, new_class_dict, classname, param_names, param_values, new_method + ) new_class_dict[new_method.__name__] = new_method @classmethod @@ -115,11 +102,12 @@ return new_method -@add_metaclass(ParameterizedTestCaseMetaClass) -class ParameterizedTestMixin(object): + +class ParameterizedTestMixin(metaclass=ParameterizedTestCaseMetaClass): @classmethod - def parameterize(cls, param_names, data, - func_name_format='{func_name}_{case_num:05d}'): + def parameterize( + cls, param_names, data, func_name_format="{func_name}_{case_num:05d}" + ): """Decorator for parameterizing a test method - example: @ParameterizedTestCase.parameterize( @@ -127,6 +115,18 @@ ("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): @@ -142,6 +142,6 @@ return decorator -@add_metaclass(ParameterizedTestCaseMetaClass) + class ParameterizedTestCase(unittest.TestCase, ParameterizedTestMixin): pass diff -Nru python-pint-0.9/pint/testsuite/test_application_registry.py python-pint-0.10.1/pint/testsuite/test_application_registry.py --- python-pint-0.9/pint/testsuite/test_application_registry.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_application_registry.py 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,239 @@ +"""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) diff -Nru python-pint-0.9/pint/testsuite/test_babel.py python-pint-0.10.1/pint/testsuite/test_babel.py --- python-pint-0.9/pint/testsuite/test_babel.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_babel.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,39 +1,54 @@ -# -*- coding: utf-8 -*- -from __future__ import division, unicode_literals, print_function, absolute_import +import os -from pint.testsuite import helpers, BaseTestCase from pint import UnitRegistry -import os +from pint.testsuite import BaseTestCase, helpers -class TestBabel(BaseTestCase): - @helpers.requires_proper_babel() - def test_babel(self): +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')) + 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" + 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" + 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( - time.format_babel(locale='ro', length='short'), - "8.0 s" + 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(locale='fr_FR', length='long'), - "0.375 mètre par seconde²" + 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" + 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" ) diff -Nru python-pint-0.9/pint/testsuite/test_compat_downcast.py python-pint-0.10.1/pint/testsuite/test_compat_downcast.py --- python-pint-0.9/pint/testsuite/test_compat_downcast.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_compat_downcast.py 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,108 @@ +import pytest + +from pint import UnitRegistry + +# Conditionally import NumPy and any upcast type libraries +np = pytest.importorskip("numpy", reason="NumPy is not available") +sparse = pytest.importorskip("sparse", reason="sparse is not available") + +# 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 diff -Nru python-pint-0.9/pint/testsuite/test_compat_upcast.py python-pint-0.10.1/pint/testsuite/test_compat_upcast.py --- python-pint-0.9/pint/testsuite/test_compat_upcast.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_compat_upcast.py 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,127 @@ +import 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 diff -Nru python-pint-0.9/pint/testsuite/test_contexts.py python-pint-0.10.1/pint/testsuite/test_contexts.py --- python-pint-0.9/pint/testsuite/test_contexts.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_contexts.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,26 +1,28 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - import itertools +import math from collections import defaultdict -from pint import UnitRegistry, errors +from pint import ( + DefinitionSyntaxError, + DimensionalityError, + UndefinedUnitError, + UnitRegistry, +) from pint.context import Context -from pint.util import UnitsContainer 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') + 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') + 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) @@ -28,15 +30,15 @@ def add_arg_ctxs(ureg): - a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}) - d = Context('lc') + 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') + 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) @@ -44,8 +46,8 @@ def add_argdef_ctxs(ureg): - a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}) - d = Context('lc', defaults=dict(n=1)) + 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) @@ -53,8 +55,8 @@ ureg.add_context(d) - a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': 1}) - d = Context('ab') + 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) @@ -62,8 +64,8 @@ def add_sharedargdef_ctxs(ureg): - a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}) - d = Context('lc', defaults=dict(n=1)) + 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) @@ -71,8 +73,8 @@ ureg.add_context(d) - a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': 1}) - d = Context('ab', defaults=dict(n=0)) + 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) @@ -80,18 +82,17 @@ class TestContexts(QuantityTestCase): - def test_known_context(self): ureg = UnitRegistry() add_ctxs(ureg) - with ureg.context('lc'): + 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): + with ureg.context("lc", n=1): self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) @@ -101,7 +102,7 @@ def test_known_context_enable(self): ureg = UnitRegistry() add_ctxs(ureg) - ureg.enable_contexts('lc') + ureg.enable_contexts("lc") self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) ureg.disable_contexts(1) @@ -109,7 +110,7 @@ self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) - ureg.enable_contexts('lc', n=1) + ureg.enable_contexts("lc", n=1) self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) ureg.disable_contexts(1) @@ -120,93 +121,85 @@ def test_graph(self): ureg = UnitRegistry() add_ctxs(ureg) - l = UnitsContainer({'[length]': 1.}) - t = UnitsContainer({'[time]': -1.}) - c = UnitsContainer({'[current]': 1.}) + 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: set((t,)), - t: set((l,))}) + g_sp.update({l: {t}, t: {l}}) g_ab = defaultdict(set) - g_ab.update({l: set((c,)), - c: set((l,))}) + g_ab.update({l: {c}, c: {l}}) g = defaultdict(set) - g.update({l: set((t, c)), - t: set((l,)), - c: set((l,))}) + g.update({l: {t, c}, t: {l}, c: {l}}) - with ureg.context('lc'): + with ureg.context("lc"): self.assertEqual(ureg._active_ctx.graph, g_sp) - with ureg.context('lc', n=1): + with ureg.context("lc", n=1): self.assertEqual(ureg._active_ctx.graph, g_sp) - with ureg.context('ab'): + with ureg.context("ab"): self.assertEqual(ureg._active_ctx.graph, g_ab) - with ureg.context('lc'): - with ureg.context('ab'): + with ureg.context("lc"): + with ureg.context("ab"): self.assertEqual(ureg._active_ctx.graph, g) - with ureg.context('ab'): - with ureg.context('lc'): + with ureg.context("ab"): + with ureg.context("lc"): self.assertEqual(ureg._active_ctx.graph, g) - with ureg.context('lc', 'ab'): + with ureg.context("lc", "ab"): self.assertEqual(ureg._active_ctx.graph, g) - with ureg.context('ab', 'lc'): + 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.}) - t = UnitsContainer({'[time]': -1.}) - c = UnitsContainer({'[current]': 1.}) + 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: set((t,)), - t: set((l,))}) + g_sp.update({l: {t}, t: {l}}) g_ab = defaultdict(set) - g_ab.update({l: set((c,)), - c: set((l,))}) + g_ab.update({l: {c}, c: {l}}) g = defaultdict(set) - g.update({l: set((t, c)), - t: set((l,)), - c: set((l,))}) + g.update({l: {t, c}, t: {l}, c: {l}}) - ureg.enable_contexts('lc') + ureg.enable_contexts("lc") self.assertEqual(ureg._active_ctx.graph, g_sp) ureg.disable_contexts(1) - ureg.enable_contexts('lc', n=1) + ureg.enable_contexts("lc", n=1) self.assertEqual(ureg._active_ctx.graph, g_sp) ureg.disable_contexts(1) - ureg.enable_contexts('ab') + ureg.enable_contexts("ab") self.assertEqual(ureg._active_ctx.graph, g_ab) ureg.disable_contexts(1) - ureg.enable_contexts('lc') - ureg.enable_contexts('ab') + 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') + ureg.enable_contexts("ab") + ureg.enable_contexts("lc") self.assertEqual(ureg._active_ctx.graph, g) ureg.disable_contexts(2) - ureg.enable_contexts('lc', 'ab') + ureg.enable_contexts("lc", "ab") self.assertEqual(ureg._active_ctx.graph, g) ureg.disable_contexts(2) - ureg.enable_contexts('ab', 'lc') + ureg.enable_contexts("ab", "lc") self.assertEqual(ureg._active_ctx.graph, g) ureg.disable_contexts(2) @@ -214,13 +207,13 @@ ureg = UnitRegistry() add_ctxs(ureg) - with ureg.context('lc'): + 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'): + with ureg.context("ab"): self.assertTrue(ureg._active_ctx) self.assertTrue(ureg._active_ctx.graph) self.assertNotEqual(x, ureg._active_ctx) @@ -235,14 +228,9 @@ def test_unknown_context(self): ureg = UnitRegistry() add_ctxs(ureg) - try: - with ureg.context('la'): + with self.assertRaises(KeyError): + with ureg.context("la"): pass - except KeyError as e: - value = True - except Exception as e: - value = False - self.assertTrue(value) self.assertFalse(ureg._active_ctx) self.assertFalse(ureg._active_ctx.graph) @@ -250,18 +238,12 @@ ureg = UnitRegistry() add_ctxs(ureg) - with ureg.context('lc'): + with ureg.context("lc"): x = dict(ureg._active_ctx) y = dict(ureg._active_ctx.graph) - try: - with ureg.context('la'): + with self.assertRaises(KeyError): + with ureg.context("la"): pass - except KeyError as e: - value = True - except Exception as e: - value = False - - self.assertTrue(value) self.assertEqual(x, ureg._active_ctx) self.assertEqual(y, ureg._active_ctx.graph) @@ -269,66 +251,65 @@ 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') + 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(ValueError, q.to, 'Hz') - with ureg.context('lc'): - self.assertEqual(q.to('Hz'), s) + 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(ValueError, q.to, 'Hz') + 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') + 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(ValueError, 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(ValueError, q.to, 'Hz') + 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') + s = (ureg.speed_of_light / q).to("Hz") - self.assertRaises(ValueError, 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(ValueError, q.to, 'Hz') - with ureg.context('lc'): - self.assertEqual(q.to('Hz'), s) - self.assertRaises(ValueError, 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): @@ -337,23 +318,23 @@ add_arg_ctxs(ureg) q = 500 * ureg.meter - s = (ureg.speed_of_light / q).to('Hz') + s = (ureg.speed_of_light / q).to("Hz") - self.assertRaises(ValueError, 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(ValueError, q.to, 'Hz') - with ureg.context('lc', n=1): - self.assertEqual(q.to('Hz'), s) - self.assertRaises(ValueError, 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') + with ureg.context("lc"): + self.assertRaises(TypeError, q.to, "Hz") def test_enable_context_with_arg(self): @@ -362,30 +343,29 @@ add_arg_ctxs(ureg) q = 500 * ureg.meter - s = (ureg.speed_of_light / q).to('Hz') + s = (ureg.speed_of_light / q).to("Hz") - self.assertRaises(ValueError, 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) + 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(ValueError, q.to, 'Hz') - ureg.enable_contexts('lc', n=1) - self.assertEqual(q.to('Hz'), s) + 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(ValueError, q.to, 'Hz') + self.assertRaises(DimensionalityError, q.to, "Hz") ureg.disable_contexts(1) - ureg.enable_contexts('lc') - self.assertRaises(TypeError, q.to, 'Hz') + ureg.enable_contexts("lc") + self.assertRaises(TypeError, q.to, "Hz") ureg.disable_contexts(1) - def test_context_with_arg_def(self): ureg = UnitRegistry() @@ -393,34 +373,33 @@ add_argdef_ctxs(ureg) q = 500 * ureg.meter - s = (ureg.speed_of_light / q).to('Hz') - - self.assertRaises(ValueError, 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(ValueError, q.to, 'Hz') - with ureg.context('lc'): - self.assertEqual(q.to('Hz'), s) - self.assertRaises(ValueError, q.to, 'Hz') - - self.assertRaises(ValueError, 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(ValueError, q.to, 'Hz') - with ureg.context('lc', n=2): - self.assertEqual(q.to('Hz'), s / 2) - self.assertRaises(ValueError, q.to, 'Hz') + 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): @@ -429,43 +408,72 @@ add_sharedargdef_ctxs(ureg) q = 500 * ureg.meter - s = (ureg.speed_of_light / q).to('Hz') + 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) + 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') + s = (ureg.speed_of_light / q).to("Hz") nctx = len(ureg._contexts) @@ -476,103 +484,124 @@ 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) + 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): - s = ['@context longcontextname', - '[length] = 1 / [time]: c / value', - '1 / [time] = [length]: c / value'] - - self.assertRaises(ValueError, Context.from_lines, s) + 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'] + 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.name, "longcontextname") self.assertEqual(c.aliases, ()) self.assertEqual(c.defaults, {}) - self.assertEqual(set(c.funcs.keys()), set((a, b))) + self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) - s = ['@context longcontextname = lc', - '[length] <-> 1 / [time]: c / value'] + 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.name, "longcontextname") + self.assertEqual(c.aliases, ("lc",)) self.assertEqual(c.defaults, {}) - self.assertEqual(set(c.funcs.keys()), set((a, b))) + self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) - s = ['@context longcontextname = lc = lcn', - '[length] <-> 1 / [time]: c / value'] + 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.name, "longcontextname") + self.assertEqual(c.aliases, ("lc", "lcn")) self.assertEqual(c.defaults, {}) - self.assertEqual(set(c.funcs.keys()), set((a, b))) + self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) def test_parse_auto_inverse(self): - a = Context.__keytransform__(UnitsContainer({'[time]': -1.}), UnitsContainer({'[length]': 1.})) - b = Context.__keytransform__(UnitsContainer({'[length]': 1.}), UnitsContainer({'[time]': -1.})) + 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'] + s = ["@context longcontextname", "[length] <-> 1 / [time]: c / value"] c = Context.from_lines(s) self.assertEqual(c.defaults, {}) - self.assertEqual(set(c.funcs.keys()), set((a, b))) + 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.})) - b = Context.__keytransform__(UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1.})) + 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'] + s = ["@context longcontextname", "[length] <-> 1 / [time]: c / value"] c = Context.from_lines(s) self.assertEqual(c.defaults, {}) - self.assertEqual(set(c.funcs.keys()), set((a, b))) + self.assertEqual(c.funcs.keys(), {a, b}) self._test_ctx(c) def test_parse_parameterized(self): - a = Context.__keytransform__(UnitsContainer({'[time]': -1.}), UnitsContainer({'[length]': 1.})) - b = Context.__keytransform__(UnitsContainer({'[length]': 1.}), UnitsContainer({'[time]': -1.})) + 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'] + s = ["@context(n=1) longcontextname", "[length] <-> 1 / [time]: n * c / value"] c = Context.from_lines(s) - self.assertEqual(c.defaults, {'n': 1}) - self.assertEqual(set(c.funcs.keys()), set((a, b))) + 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'] + 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(set(c.funcs.keys()), set((a, b))) + 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(ValueError, Context.from_lines, s) + s = ["@context(n=1) longcontextname", "[length] <-> 1 / [time]: c / value"] + self.assertRaises(DefinitionSyntaxError, Context.from_lines, s) def test_warnings(self): @@ -581,13 +610,13 @@ with self.capture_log() as buffer: add_ctxs(ureg) - d = Context('ab') + d = Context("ab") ureg.add_context(d) self.assertEqual(len(buffer), 1) self.assertIn("ab", str(buffer[-1])) - d = Context('ab1', aliases=('ab',)) + d = Context("ab1", aliases=("ab",)) ureg.add_context(d) self.assertEqual(len(buffer), 2) @@ -600,91 +629,325 @@ def test_defined(self): ureg = self.ureg - with ureg.context('sp'): + with ureg.context("sp"): pass - a = Context.__keytransform__(UnitsContainer({'[time]': -1.}), UnitsContainer({'[length]': 1.})) - b = Context.__keytransform__(UnitsContainer({'[length]': 1.}), UnitsContainer({'[time]': -1.})) - self.assertIn(a, ureg._contexts['sp'].funcs) - self.assertIn(b, ureg._contexts['sp'].funcs) - with ureg.context('sp'): + 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. * ureg.nm, 563.5 * ureg.terahertz, 2.33053 * ureg.eV) - with ureg.context('sp'): + 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) + 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) + 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) + 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(errors.DimensionalityError): - qty_indirect = qty_direct.to('Nm') + with self.assertRaises(DimensionalityError): + qty_indirect = qty_direct.to("Nm") - with ureg.context('textile'): + with ureg.context("textile"): from pint.util import find_shortest_path - qty_indirect = qty_direct.to('Nm') + + 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) + 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) + 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. * ureg.nm - with ureg.context('sp'): - b = a.to('terahertz') + a = 532.0 * ureg.nm + with ureg.context("sp"): + b = a.to("terahertz") def f(wl): - return wl.to('terahertz') + return wl.to("terahertz") - self.assertRaises(errors.DimensionalityError, f, a) + self.assertRaises(DimensionalityError, f, a) - @ureg.with_context('sp') + @ureg.with_context("sp") def g(wl): - return wl.to('terahertz') + return wl.to("terahertz") self.assertEqual(b, g(a)) def test_decorator_composition(self): ureg = self.ureg - a = 532. * ureg.nm - with ureg.context('sp'): - b = a.to('terahertz') + a = 532.0 * ureg.nm + with ureg.context("sp"): + b = a.to("terahertz") - @ureg.with_context('sp') - @ureg.check('[length]') + @ureg.with_context("sp") + @ureg.check("[length]") def f(wl): - return wl.to('terahertz') + return wl.to("terahertz") - @ureg.with_context('sp') - @ureg.check('[length]') + @ureg.with_context("sp") + @ureg.check("[length]") def g(wl): - return wl.to('terahertz') + 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") diff -Nru python-pint-0.9/pint/testsuite/test_converters.py python-pint-0.10.1/pint/testsuite/test_converters.py --- python-pint-0.9/pint/testsuite/test_converters.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_converters.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,14 +1,11 @@ -# -*- coding: utf-8 -*- -from __future__ import division, unicode_literals, print_function, absolute_import - import itertools from pint.compat import np -from pint.converters import (ScaleConverter, OffsetConverter, Converter) -from pint.testsuite import helpers, BaseTestCase +from pint.converters import Converter, OffsetConverter, ScaleConverter +from pint.testsuite import BaseTestCase, helpers -class TestConverter(BaseTestCase): +class TestConverter(BaseTestCase): def test_converter(self): c = Converter() self.assertTrue(c.is_multiplicative) @@ -16,22 +13,23 @@ self.assertTrue(c.from_reference(8)) def test_multiplicative_converter(self): - c = ScaleConverter(20.) + 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., 2) + 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.), OffsetConverter(20., 2)): + 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))): + 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) diff -Nru python-pint-0.9/pint/testsuite/test_definitions.py python-pint-0.10.1/pint/testsuite/test_definitions.py --- python-pint-0.9/pint/testsuite/test_definitions.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_definitions.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,80 +1,110 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - -from pint.util import (UnitsContainer) -from pint.converters import (ScaleConverter, OffsetConverter) -from pint.definitions import (Definition, PrefixDefinition, UnitDefinition, - DimensionDefinition) - +from 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): +class TestDefinition(BaseTestCase): def test_invalid(self): - self.assertRaises(ValueError, Definition.from_string, 'x = [time] * meter') - self.assertRaises(ValueError, Definition.from_string, '[x] = [time] * meter') + 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): - for definition in ('m- = 1e-3', 'm- = 10**-3', 'm- = 0.001'): + + 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.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') + self.assertEqual(str(x), "m") - x = Definition.from_string('kilo- = 1e-3 = k-') + x = Definition.from_string("kilo- = 1e-3 = k-") self.assertIsInstance(x, PrefixDefinition) - self.assertEqual(x.name, 'kilo') + self.assertEqual(x.name, "kilo") self.assertEqual(x.aliases, ()) - self.assertEqual(x.symbol, 'k') + self.assertEqual(x.symbol, "k") self.assertEqual(x.converter.to_reference(1000), 1) - self.assertEqual(x.converter.from_reference(.001), 1) + self.assertEqual(x.converter.from_reference(0.001), 1) - x = Definition.from_string('kilo- = 1e-3 = k- = anotherk-') + 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.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(.001), 1) + self.assertEqual(x.converter.from_reference(0.001), 1) def test_baseunit_definition(self): - x = Definition.from_string('meter = [length]') + x = Definition.from_string("meter = [length]") self.assertIsInstance(x, UnitDefinition) self.assertTrue(x.is_base) - self.assertEqual(x.reference, UnitsContainer({'[length]': 1})) + self.assertEqual(x.reference, UnitsContainer({"[length]": 1})) def test_unit_definition(self): - x = Definition.from_string('coulomb = ampere * second') + 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') + 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.converter.scale, 96485.3399) self.assertEqual(x.reference, UnitsContainer(coulomb=1)) - x = Definition.from_string('degF = 9 / 5 * kelvin; offset: 255.372222') + 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.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='') + x = DimensionDefinition("[time]", "", (), converter="") self.assertTrue(x.is_base) - self.assertEqual(x.name, '[time]') + self.assertEqual(x.name, "[time]") - x = Definition.from_string('[speed] = [length]/[time]') + x = Definition.from_string("[speed] = [length]/[time]") self.assertIsInstance(x, DimensionDefinition) - self.assertEqual(x.reference, UnitsContainer({'[length]': 1, '[time]': -1})) + 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")) diff -Nru python-pint-0.9/pint/testsuite/test_errors.py python-pint-0.10.1/pint/testsuite/test_errors.py --- python-pint-0.9/pint/testsuite/test_errors.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_errors.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,19 +1,117 @@ -# -*- coding: utf-8 -*- -from __future__ import division, unicode_literals, print_function, absolute_import +import pickle - -from pint.errors import DimensionalityError, UndefinedUnitError +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)") - def test_errors(self): - x = ('meter', ) + 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) - msg = "Cannot convert from 'a' (c) to 'b' (d)msg" - ex = DimensionalityError('a', 'b', 'c', 'd', 'msg') - self.assertEqual(str(ex), 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)) diff -Nru python-pint-0.9/pint/testsuite/test_formatter.py python-pint-0.10.1/pint/testsuite/test_formatter.py --- python-pint-0.9/pint/testsuite/test_formatter.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_formatter.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,43 +1,47 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - from 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') - + 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)') + 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') + 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') + self.assertEqual(fmt.format_unit("", "C"), "dimensionless") + self.assertRaises(ValueError, fmt.format_unit, "m", "W") diff -Nru python-pint-0.9/pint/testsuite/test_infer_base_unit.py python-pint-0.10.1/pint/testsuite/test_infer_base_unit.py --- python-pint-0.9/pint/testsuite/test_infer_base_unit.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_infer_base_unit.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,32 +1,32 @@ -from pint import UnitRegistry, set_application_registry +from pint import Quantity as Q from pint.testsuite import QuantityTestCase from pint.util import infer_base_unit -ureg = UnitRegistry() -set_application_registry(ureg) -Q = ureg.Quantity - 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) + + 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) + 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') + r = Q(1000000000, "m") * Q(1, "mm") / Q(1, "s") / Q(1, "ms") compact_r = r.to_compact() - expected = Q(1000., 'kilometer**2 / second**2') + 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')) + 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') + + 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')) \ No newline at end of file + self.assertEqual(b, Q(1, "V").units) + self.assertQuantityAlmostEqual(r, Q(1, "uV")) diff -Nru python-pint-0.9/pint/testsuite/test_issues.py python-pint-0.10.1/pint/testsuite/test_issues.py --- python-pint-0.9/pint/testsuite/test_issues.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_issues.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,18 +1,17 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - -import math import copy +import math +import pprint import unittest -from pint import UnitRegistry +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 -from pint.compat import np, long_type -from pint.errors import UndefinedUnitError, DimensionalityError -from pint.testsuite import QuantityTestCase, helpers +ureg = UnitRegistry() class TestIssues(QuantityTestCase): @@ -24,110 +23,233 @@ @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 = UnitRegistry() - 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})) + 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): - ureg = UnitRegistry() - t = 4 * ureg('mM') + t = 4 * ureg("mW") self.assertEqual(t.magnitude, 4) - self.assertEqual(t._units, UnitsContainer(millimolar=1)) - self.assertEqual(t.to('mole / liter'), 4e-3 * ureg('M')) + 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 + 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): + + 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): - ureg = UnitRegistry() - self.assertEqual((1*ureg.km/ureg.m + 1).magnitude, 1001) + self.assertEqual((1 * ureg.km / ureg.m + 1).magnitude, 1001) def test_issue54_related(self): - ureg = UnitRegistry() - 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) + 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): - ureg = UnitRegistry() Q_ = ureg.Quantity - for value in ({}, {'a': 3}, None): + 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_, '') + self.assertRaises(TypeError, Q_, value, "meter") + self.assertRaises(ValueError, Q_, "", "meter") + self.assertRaises(ValueError, Q_, "") @helpers.requires_not_numpy() def test_issue61_notNP(self): - ureg = UnitRegistry() Q_ = ureg.Quantity for value in ([1, 2, 3], (1, 2, 3)): self.assertRaises(TypeError, Q_, value) - self.assertRaises(TypeError, Q_, value, 'meter') + 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): - ureg = UnitRegistry() - 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})) + 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): - ureg = UnitRegistry() - 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})))) + 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): - ureg = UnitRegistry() - q = ureg('m').to(ureg('in')) - self.assertEqual(q, ureg('m').to('in')) + 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): - ureg = UnitRegistry() - acc = (5.0 * ureg('m/s/s')).plus_minus(0.25) - tim = (37.0 * ureg('s')).plus_minus(0.16) + 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): - ureg = UnitRegistry() - T = 4. * ureg.kelvin - m = 1. * ureg.amu - va = 2. * ureg.k * T / m - - try: - va.to_base_units() - except: - self.assertTrue(False, 'Error while trying to get base units for {}'.format(va)) + T = 4.0 * ureg.kelvin + m = 1.0 * ureg.amu + va = 2.0 * ureg.k * T / m - boltmk = 1.3806488e-23*ureg.J/ureg.K - vb = 2. * boltmk * 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()) @@ -138,12 +260,12 @@ def parts(q): return q.magnitude, q.units - q1 = 10. * ureg.degC - q2 = 10. * ureg.kelvin + q1 = 10.0 * ureg.degC + q2 = 10.0 * ureg.kelvin k1 = q1.to_base_units() - q3 = 3. * ureg.meter + q3 = 3.0 * ureg.meter q1m, q1u = parts(q1) q2m, q2u = parts(q2) @@ -155,9 +277,9 @@ 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 ** -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(q2 ** -2), (q2m ** -2, q2u ** -2)) self.assertEqual(parts(q1 * q3), (k1m * q3m, k1u * q3u)) @@ -165,13 +287,13 @@ 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)) self.assertEqual(parts(q1 ** -2), (k1m ** -2, k1u ** -2)) def test_issues86b(self): ureg = self.ureg - T1 = 200. * ureg.degC + T1 = 200.0 * ureg.degC T2 = T1.to(ureg.kelvin) m = 132.9054519 * ureg.amu v1 = 2 * ureg.k * T1 / m @@ -187,11 +309,10 @@ ureg = self.ureg ureg.autoconvert_offset_to_baseunit = True T = ureg.degC - T = 100. * T - self.assertQuantityAlmostEqual(ureg.k*2*T, ureg.k*(2*T)) + T = 100.0 * T + self.assertQuantityAlmostEqual(ureg.k * 2 * T, ureg.k * (2 * T)) def test_issue93(self): - ureg = UnitRegistry() x = 5 * ureg.meter self.assertIsInstance(x.magnitude, int) y = 0.1 * ureg.meter @@ -204,51 +325,20 @@ self.assertQuantityAlmostEqual(x + y, 5.1 * ureg.meter) self.assertQuantityAlmostEqual(z, 5.1 * ureg.meter) - def test_issue523(self): - ureg = UnitRegistry() - src, dst = UnitsContainer({'meter': 1}), UnitsContainer({'degF': 1}) - value = 10. - convert = self.ureg.convert - self.assertRaises(DimensionalityError, convert, value, src, dst) - self.assertRaises(DimensionalityError, convert, value, dst, src) - - def _test_issueXX(self): - ureg = UnitRegistry() - try: - ureg.convert(1, ureg.degC, ureg.kelvin * ureg.meter / ureg.nanometer) - except: - self.assertTrue(False, - 'Error while trying to convert {} to {}'.format(ureg.degC, ureg.kelvin * ureg.meter / ureg.nanometer)) - - def test_issue121(self): - sh = (2, 1) - ureg = UnitRegistry() - z, v = 0, 2. - 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) - - def test_issue105(self): - ureg = UnitRegistry() - - func = ureg.parse_unit_name - val = list(func('meter')) - self.assertEqual(list(func('METER')), []) - self.assertEqual(val, list(func('METER', False))) + @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 - for func in (ureg.get_name, ureg.parse_expression): - val = func('meter') - self.assertRaises(AttributeError, func, 'METER') - self.assertEqual(val, func('METER', False)) + 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): - ureg = UnitRegistry() - x = [ureg('1 meter'), ureg('1 meter'), ureg('1 meter')] - y = [ureg('1 meter')] * 3 + x = [ureg("1 meter"), ureg("1 meter"), ureg("1 meter")] + y = [ureg("1 meter")] * 3 def summer(values): if not values: @@ -259,197 +349,38 @@ 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')) + 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_issue170(self): - Q_ = UnitRegistry().Quantity - q = Q_('1 kHz')/Q_('100 Hz') - iq = int(q) - self.assertEqual(iq, 10) - self.assertIsInstance(iq, int) - - @helpers.requires_python2() - def test_issue170b(self): - Q_ = UnitRegistry().Quantity - q = Q_('1 kHz')/Q_('100 Hz') - iq = long(q) - self.assertEqual(iq, long(10)) - self.assertIsInstance(iq, long) - - def test_angstrom_creation(self): - ureg = UnitRegistry() - try: - ureg.Quantity(2, 'Å') - except SyntaxError: - self.fail('Quantity with Å could not be created.') - - def test_alternative_angstrom_definition(self): - ureg = UnitRegistry() - try: - ureg.Quantity(2, '\u212B') - except UndefinedUnitError: - self.fail('Quantity with Å could not be created.') - - def test_micro_creation(self): - ureg = UnitRegistry() - try: - ureg.Quantity(2, 'µm') - except SyntaxError: - self.fail('Quantity with µ prefix could not be created.') - - -@helpers.requires_numpy() -class TestIssuesNP(QuantityTestCase): - - FORCE_NDARRAY = False - - @unittest.expectedFailure - def test_issue37(self): - x = np.ma.masked_array([1, 2, 3], mask=[True, True, False]) - ureg = UnitRegistry() - 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 - def test_issue39(self): - x = np.matrix([[1, 2, 3], [1, 2, 3], [1, 2, 3]]) - ureg = UnitRegistry() - 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) - - def test_issue44(self): - ureg = UnitRegistry() - x = 4. * ureg.dimensionless - np.sqrt(x) - self.assertQuantityAlmostEqual(np.sqrt([4.] * ureg.dimensionless), [2.] * ureg.dimensionless) - self.assertQuantityAlmostEqual(np.sqrt(4. * ureg.dimensionless), 2. * ureg.dimensionless) - - def test_issue45(self): - import math - ureg = UnitRegistry() - self.assertAlmostEqual(math.sqrt(4 * ureg.m/ureg.cm), math.sqrt(4 * 100)) - self.assertAlmostEqual(float(ureg.V / ureg.mV), 1000.) - - def test_issue45b(self): - ureg = UnitRegistry() - 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): - ureg = UnitRegistry() - Q_ = ureg.Quantity - self.assertEqual(Q_(100), 100 * ureg.dimensionless) - self.assertEqual(Q_('100'), 100 * ureg.dimensionless) - - def test_issue62(self): - ureg = UnitRegistry() - m = ureg('m**0.5') - self.assertEqual(str(m.units), 'meter ** 0.5') - - def test_issue74(self): - ureg = UnitRegistry() - v1 = np.asarray([1., 2., 3.]) - v2 = np.asarray([3., 2., 1.]) - 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) - - def test_issue75(self): - ureg = UnitRegistry() - v1 = np.asarray([1., 2., 3.]) - v2 = np.asarray([3., 2., 1.]) - 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) + def test_issue105(self): - def test_issue93(self): - ureg = UnitRegistry() - 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) + func = ureg.parse_unit_name + val = list(func("meter")) + self.assertEqual(list(func("METER")), []) + self.assertEqual(val, list(func("METER", False))) - self.assertQuantityAlmostEqual(x + y, 5.1 * ureg.meter) - self.assertQuantityAlmostEqual(z, 5.1 * ureg.meter) + for func in (ureg.get_name, ureg.parse_expression): + val = func("meter") + with self.assertRaises(AttributeError): + func("METER") + self.assertEqual(val, func("METER", False)) - @helpers.requires_numpy_previous_than('1.10') - def test_issue94(self): - ureg = UnitRegistry() - v1 = np.array([5, 5]) * ureg.meter - v2 = 0.1 * ureg.meter - v3 = np.array([5, 5]) * ureg.meter - v3 += v2 + 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) - 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])) + self.assertEqual(sum([v * ureg.meter, v * ureg.meter]), 2 * v * ureg.meter) @helpers.requires_numpy18() - def test_issue121(self): + def test_issue121b(self): sh = (2, 1) - ureg = UnitRegistry() - z, v = 0, 2. + 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) @@ -457,58 +388,69 @@ self.assertEqual(sum([v * ureg.meter, v * ureg.meter]), 2 * v * ureg.meter) - z, v = np.zeros(sh), 2. * np.ones(sh) + 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. * np.ones(sh) - for x, y in ((z, v), - (z, v * ureg.meter), - (v * ureg.meter, z) - ): - try: - w = x + y - self.assertTrue(False, "ValueError not raised") - except ValueError: - pass - try: - w = x - y - self.assertTrue(False, "ValueError not raised") - except ValueError: - pass + 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., 2., 3., 4.] * self.ureg.meter + q = [1.0, 2.0, 3.0, 4.0] * self.ureg.meter q[0] = np.nan - self.assertNotEqual(q[0], 1.) + self.assertNotEqual(q[0], 1.0) self.assertTrue(math.isnan(q[0].magnitude)) - q[1] = float('NaN') - self.assertNotEqual(q[1], 2.) + 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., 2., 3., 4.] * self.ureg.meter - qi = [4., 3., 2., 1.] * self.ureg.meter + 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., 2., 3., 4.],[4., 3., 2., 1.]]) + 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.) - self.assertEqual(np.float32(a/b), 1000.) - self.assertEqual(np.float64(a/b), 1000.) + 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.) + self.assertEqual(np.float128(a / b), 1000.0) def test_issue252(self): ur = UnitRegistry() @@ -519,42 +461,41 @@ 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')) + + 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('') + q1 = self.ureg("") self.assertEqual(q1.magnitude, 1) self.assertEqual(q1.units, self.ureg.dimensionless) - q2 = self.ureg('1 dimensionless') + q2 = self.ureg("1 dimensionless") self.assertEqual(q1, q2) def test_issue354_356_370(self): - q = 1 * self.ureg.second / self.ureg.millisecond - self.assertEqual('{0:~}'.format(1 * self.ureg.second / self.ureg.millisecond), - '1.0 s / ms') - self.assertEqual("{0:~}".format(1 * self.ureg.count), - '1 count') - self.assertEqual('{0:~}'.format(1 * self.ureg('MiB')), - '1 MiB') - - def test_issue482(self): - q = self.ureg.Quantity(1, self.ureg.dimensionless) - qe = np.exp(q) - self.assertIsInstance(qe, self.ureg.Quantity) + 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 = UnitRegistry() - - @ureg.wraps(('kg'), 'meter') + @ureg.wraps(("kg"), "meter") def f(x): return x - x = ureg.Quantity(1., 'meter') + x = ureg.Quantity(1.0, "meter") y = f(x) z = x * y - self.assertEquals(z, ureg.Quantity(1., 'meter * kilogram')) + 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]) @@ -562,29 +503,29 @@ 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('')) + @ureg.check(ureg("")) def f(x): return 2 * x - self.assertEqual(f(ureg.Quantity(1, '')), 2) - self.assertRaises(DimensionalityError, f, ureg.Quantity(1, 'm')) + self.assertEqual(f(ureg.Quantity(1, "")), 2) + self.assertRaises(DimensionalityError, f, ureg.Quantity(1, "m")) def test_issue625a(self): - try: - from inspect import signature - except ImportError: - # Python2 does not have the inspect library. Import the backport. - from funcsigs import signature - - ureg = UnitRegistry() 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')): + @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, @@ -592,88 +533,216 @@ 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') + lunar_module_height = Q_(10, "m") t1 = calculate_time_to_fall(lunar_module_height) print(t1) - self.assertAlmostEqual(t1, Q_(1.4285714285714286, 's')) + self.assertAlmostEqual(t1, Q_(1.4285714285714286, "s")) - moon_gravity = Q_(1.625, 'm/s^2') + 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')) + self.assertAlmostEqual(t2, Q_(3.508232077228117, "s")) def test_issue625b(self): - try: - from inspect import signature - except ImportError: - # Python2 does not have the inspect library. Import the backport. - from funcsigs import signature - - ureg = UnitRegistry() Q_ = ureg.Quantity - @ureg.wraps('=A*B', ('=A', '=B')) - def get_displacement(time, rate=Q_(1, 'm/s')): + @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')) + d1 = get_displacement(Q_(2, "s")) + self.assertAlmostEqual(d1, Q_(2, "m")) - def test_issue625c(self): - try: - from inspect import signature - except ImportError: - # Python2 does not have the inspect library. Import the backport. - from funcsigs import signature + 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) + @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): - ureg = UnitRegistry() 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_issue(self): - import math - try: - from inspect import signature - except ImportError: - # Python2 does not have the inspect library. Import the backport - from funcsigs import signature + 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) - ureg = UnitRegistry() + def test_issue655b(self): Q_ = ureg.Quantity - @ureg.check('[length]', '[length]/[time]^2') - def pendulum_period(length, G=Q_(1, 'standard_gravity')): + + @ureg.check("[length]", "[length]/[time]^2") + def pendulum_period(length, G=Q_(1, "standard_gravity")): print(length) - return (2*math.pi*(length/G)**.5).to('s') - l = 1 * ureg.m + return (2 * math.pi * (length / G) ** 0.5).to("s") + + length = Q_(1, ureg.m) # Assume earth gravity - t = pendulum_period(l) - self.assertAlmostEqual(t, Q_('2.0064092925890407 second')) + 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(l, moon_gravity) - self.assertAlmostEqual(t, Q_('4.928936075204336 second')) - - + 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 diff -Nru python-pint-0.9/pint/testsuite/test_matplotlib.py python-pint-0.10.1/pint/testsuite/test_matplotlib.py --- python-pint-0.9/pint/testsuite/test_matplotlib.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_matplotlib.py 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,43 @@ +import pytest + +from pint import UnitRegistry + +# Conditionally import matplotlib and NumPy +plt = pytest.importorskip("matplotlib.pyplot", reason="matplotlib is not available") +np = pytest.importorskip("numpy", reason="NumPy is not available") + +# 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 diff -Nru python-pint-0.9/pint/testsuite/test_measurement.py python-pint-0.10.1/pint/testsuite/test_measurement.py --- python-pint-0.9/pint/testsuite/test_measurement.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_measurement.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - +from pint import DimensionalityError from pint.testsuite import QuantityTestCase, helpers @@ -12,7 +9,7 @@ def test_instantiate(self): M_ = self.ureg.Measurement - self.assertRaises(RuntimeError, M_, 4.0, 0.1, 's') + self.assertRaises(RuntimeError, M_, 4.0, 0.1, "s") @helpers.requires_uncertainties() @@ -22,18 +19,20 @@ def test_simple(self): M_ = self.ureg.Measurement - M_(4.0, 0.1, 's') + M_(4.0, 0.1, "s") def test_build(self): M_ = self.ureg.Measurement - v, u = self.Q_(4.0, 's'), self.Q_(.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(.1), - v.plus_minus(0.025, True), - v.plus_minus(u),) + 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) @@ -41,78 +40,142 @@ self.assertEqual(m.rel, m.error / abs(m.value)) def test_format(self): - v, u = self.Q_(4.0, 's ** 2'), self.Q_(.1, 's ** 2') + v, u = self.Q_(4.0, "s ** 2"), self.Q_(0.1, "s ** 2") m = self.ureg.Measurement(v, u) - self.assertEqual(str(m), '(4.00 +/- 0.10) second ** 2') - self.assertEqual(repr(m), '') - #self.assertEqual('{:!s}'.format(m), '(4.00 +/- 0.10) second ** 2') - #self.assertEqual('{:!r}'.format(m), '') - self.assertEqual('{0:P}'.format(m), '(4.00 ± 0.10) second²') - self.assertEqual('{0:L}'.format(m), r'\left(4.00 \pm 0.10\right)\ \mathrm{second}^{2}') - self.assertEqual('{0:H}'.format(m), '(4.00 ± 0.10) second2') - self.assertEqual('{0:C}'.format(m), '(4.00+/-0.10) second**2') - self.assertEqual('{0:Lx}'.format(m), r'\SI[separate-uncertainty=true]{4.00(10)}{\second\squared}') - self.assertEqual('{0:.1f}'.format(m), '(4.0 +/- 0.1) second ** 2') - self.assertEqual('{0:.1fP}'.format(m), '(4.0 ± 0.1) second²') - self.assertEqual('{0:.1fL}'.format(m), r'\left(4.0 \pm 0.1\right)\ \mathrm{second}^{2}') - self.assertEqual('{0:.1fH}'.format(m), '(4.0 ± 0.1) second2') - self.assertEqual('{0:.1fC}'.format(m), '(4.0+/-0.1) second**2') - self.assertEqual('{0:.1fLx}'.format(m), '\SI[separate-uncertainty=true]{4.0(1)}{\second\squared}') + + 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') + v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2") m = self.ureg.Measurement(v, u) - self.assertEqual('{0:uS}'.format(m), '0.200(10) second ** 2') - self.assertEqual('{0:.3uS}'.format(m), '0.2000(100) second ** 2') - self.assertEqual('{0:.3uSP}'.format(m), '0.2000(100) second²') - self.assertEqual('{0:.3uSL}'.format(m), r'0.2000\left(100\right)\ \mathrm{second}^{2}') - self.assertEqual('{0:.3uSH}'.format(m), '0.2000(100) second2') - self.assertEqual('{0:.3uSC}'.format(m), '0.2000(100) second**2') + + 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') + v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2") m = self.ureg.Measurement(v, u) - self.assertEqual('{0:.3u}'.format(m), '(0.2000 +/- 0.0100) second ** 2') - self.assertEqual('{0:.3uP}'.format(m), '(0.2000 ± 0.0100) second²') - self.assertEqual('{0:.3uL}'.format(m), r'\left(0.2000 \pm 0.0100\right)\ \mathrm{second}^{2}') - self.assertEqual('{0:.3uH}'.format(m), '(0.2000 ± 0.0100) second2') - self.assertEqual('{0:.3uC}'.format(m), '(0.2000+/-0.0100) second**2') - self.assertEqual('{0:.3uLx}'.format(m), '\SI[separate-uncertainty=true]{0.2000(100)}{\second\squared}') - self.assertEqual('{0:.1uLx}'.format(m), '\SI[separate-uncertainty=true]{0.20(1)}{\second\squared}') + + 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') + v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2") m = self.ureg.Measurement(v, u) - self.assertEqual('{0:.1u%}'.format(m), '(20 +/- 1)% second ** 2') - self.assertEqual('{0:.1u%P}'.format(m), '(20 ± 1)% second²') - self.assertEqual('{0:.1u%L}'.format(m), r'\left(20 \pm 1\right) \%\ \mathrm{second}^{2}') - self.assertEqual('{0:.1u%H}'.format(m), '(20 ± 1)% second2') - self.assertEqual('{0:.1u%C}'.format(m), '(20+/-1)% second**2') + + 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') + v, u = self.Q_(0.20, "s ** 2"), self.Q_(0.01, "s ** 2") m = self.ureg.Measurement(v, u) - self.assertEqual('{0:.1ue}'.format(m), '(2.0 +/- 0.1)e-01 second ** 2') - self.assertEqual('{0:.1ueP}'.format(m), '(2.0 ± 0.1)×10⁻¹ second²') - self.assertEqual('{0:.1ueL}'.format(m), r'\left(2.0 \pm 0.1\right) \times 10^{-1}\ \mathrm{second}^{2}') - self.assertEqual('{0:.1ueH}'.format(m), '(2.0 ± 0.1)e-01 second2') - self.assertEqual('{0:.1ueC}'.format(m), '(2.0+/-0.1)e-01 second**2') + 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_(.1, 's') - o = self.Q_(.1, 'm') + v, u = self.Q_(1.0, "s"), self.Q_(0.1, "s") + o = self.Q_(0.1, "m") M_ = self.ureg.Measurement - self.assertRaises(ValueError, M_, v, o) - self.assertRaises(ValueError, v.plus_minus, o) - self.assertRaises(ValueError, v.plus_minus, u, True) + 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_(.7, 's') - v2, u2 = self.Q_(5.0, 's'), self.Q_(.6, 's') - v2, u3 = self.Q_(-5.0, 's'), self.Q_(.6, 's') + 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) @@ -126,25 +189,35 @@ 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) ** .5) + 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) ** .5) + 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_(.7, 's') - v2, u2 = self.Q_(5.0, 's'), self.Q_(.6, 's') - v2, u3 = self.Q_(-5.0, 's'), self.Q_(.6, 's') + 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) @@ -155,10 +228,14 @@ 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.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.assertAlmostEqual( + r.value.magnitude, ml.value.magnitude / mr.value.magnitude + ) self.assertEqual(r.value.units, ml.value.units / mr.value.units) diff -Nru python-pint-0.9/pint/testsuite/test_numpy_func.py python-pint-0.10.1/pint/testsuite/test_numpy_func.py --- python-pint-0.9/pint/testsuite/test_numpy_func.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_numpy_func.py 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,199 @@ +from 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 diff -Nru python-pint-0.9/pint/testsuite/test_numpy.py python-pint-0.10.1/pint/testsuite/test_numpy.py --- python-pint-0.9/pint/testsuite/test_numpy.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_numpy.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - import copy import operator as op import unittest +from unittest.mock import patch -from pint import DimensionalityError, set_application_registry +from pint import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning from pint.compat import np from pint.testsuite import QuantityTestCase, helpers from pint.testsuite.test_umath import TestUFuncs @@ -20,33 +17,63 @@ @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 + return [[1, 2], [3, 4]] * self.ureg.m - 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]]) + @property + def q_nan(self): + return [[1, 2], [3, np.nan]] * self.ureg.m - 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) + @property + def q_temperature(self): + return self.Q_([[1, 2], [3, 4]], self.ureg.degC) - 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 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_)) - def test_reshape(self): - self.assertQuantityEqual(self.q.reshape([1,4]), [[1, 2, 3, 4]] * self.ureg.m) - def test_transpose(self): - self.assertQuantityEqual(self.q.transpose(), [[1, 3], [2, 4]] * self.ureg.m) +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) @@ -55,104 +82,594 @@ 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.reshape([1,4]).squeeze(), - [1, 2, 3, 4] * self.ureg.m + 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()) + self.assertQuantityEqual(self.q.take([0, 1, 2, 3]), self.q.flatten()) def test_put(self): - q = [1., 2., 3., 4.] * self.ureg.m - q.put([0, 2], [10.,20.]*self.ureg.m) - self.assertQuantityEqual(q, [10., 2., 20., 4.]*self.ureg.m) - - q = [1., 2., 3., 4.] * self.ureg.m - q.put([0, 2], [1., 2.]*self.ureg.mm) - self.assertQuantityEqual(q, [0.001, 2., 0.002, 4.]*self.ureg.m) - - q = [1., 2., 3., 4.] * self.ureg.m / self.ureg.mm - q.put([0, 2], [1., 2.]) - self.assertQuantityEqual(q, [0.001, 2., 0.002, 4.]*self.ureg.m/self.ureg.mm) - - q = [1., 2., 3., 4.] * self.ureg.m - self.assertRaises(ValueError, q.put, [0, 2], [4., 6.] * self.ureg.J) - self.assertRaises(ValueError, q.put, [0, 2], [4., 6.]) + 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) + 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 - np.testing.assert_array_equal(q.argsort(), [0, 4, 1, 2, 3, 5]) + 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) + 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() - np.testing.assert_array_equal(q.searchsorted([1.5, 2.5] * self.ureg.m), - [1, 2]) + self.assertNDArrayEqual(q.searchsorted([1.5, 2.5] * self.ureg.m), [1, 2]) q = self.q.flatten() - self.assertRaises(ValueError, q.searchsorted, [1.5, 2.5]) + 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() - np.testing.assert_array_equal(np.searchsorted(q, [1.5, 2.5] * self.ureg.m), - [1, 2]) + 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 - np.testing.assert_array_equal(q.nonzero()[0], [0, 2, 3, 5]) + 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) + 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.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.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.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 ) - self.assertRaises(ValueError, self.q.clip, self.ureg.J) - self.assertRaises(ValueError, self.q.clip, 1) def test_round(self): q = [1, 1.33, 5.67, 22] * self.ureg.m @@ -160,84 +677,159 @@ 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) + 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) + 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) + 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) + self.assertEqual(self.q.prod(), 24 * self.ureg.m ** 4) def test_cumprod(self): - self.assertRaises(ValueError, self.q.cumprod) + self.assertRaises(DimensionalityError, self.q.cumprod) self.assertQuantityEqual((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) - @helpers.requires_numpy_previous_than('1.10') + @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 + 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)) + 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) + 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): - self.assertRaises(ValueError, self.q.__setitem__, (0,0), 1) - self.assertRaises(ValueError, self.q.__setitem__, (0,0), 1*self.ureg.J) - self.assertRaises(ValueError, self.q.__setitem__, 0, 1) - self.assertRaises(ValueError, self.q.__setitem__, 0, np.ndarray([1, 2])) - self.assertRaises(ValueError, self.q.__setitem__, 0, 1*self.ureg.J) + 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[0] = 1 * self.ureg.m + self.assertQuantityEqual(q, [[1, 1], [3, 4]] * self.ureg.m) q = self.q.copy() - q.__setitem__(Ellipsis, 1*self.ureg.m) - self.assertQuantityEqual(q, [[1,1],[1,1]]*self.ureg.m) + 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) + 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, 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.,1.,2.,3.] * self.ureg.m / self.ureg.mm - q[0] = 1. - self.assertQuantityEqual(q, [0.001,1,2,3]*self.ureg.m / self.ureg.mm) + 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) @@ -247,13 +839,13 @@ def test_pickle(self): import pickle - set_application_registry(self.ureg) + def pickle_test(q): pq = pickle.loads(pickle.dumps(q)) - np.testing.assert_array_equal(q.magnitude, pq.magnitude) + self.assertNDArrayEqual(q.magnitude, pq.magnitude) self.assertEqual(q.units, pq.units) - pickle_test([10,20]*self.ureg.m) + pickle_test([10, 20] * self.ureg.m) def test_equal(self): x = self.q.magnitude @@ -268,88 +860,298 @@ 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_numpy() -class TestNumpyNeedsSubclassing(TestUFuncs): + @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, + ) - FORCE_NDARRAY = True + @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]]) + ) - @property - def q(self): - return [1. ,2., 3., 4.] * self.ureg.J + @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 + ) - @unittest.expectedFailure - def test_unwrap(self): - """unwrap depends on diff - """ - 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) + 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]]) + ) - @unittest.expectedFailure - def test_trapz(self): - """Units are erased by asanyarray, Quantity does not inherit from NDArray - """ - self.assertQuantityEqual(np.trapz(self.q, dx=1*self.ureg.m), 7.5 * self.ureg.J*self.ureg.m) + @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, + ) - @unittest.expectedFailure - def test_diff(self): - """Units are erased by asanyarray, Quantity does not inherit from NDArray - """ - self.assertQuantityEqual(np.diff(self.q, 1), [1, 1, 1] * 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") + ) - @unittest.expectedFailure - def test_ediff1d(self): - """Units are erased by asanyarray, Quantity does not inherit from NDArray - """ - self.assertQuantityEqual(np.ediff1d(self.q, 1 * self.ureg.J), [1, 1, 1] * self.ureg.J) + @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) - @unittest.expectedFailure - def test_fix(self): - """Units are erased by asanyarray, Quantity does not inherit from NDArray - """ - 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) + @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.fix([2.1, 2.9, -2.1, -2.9] * self.ureg.m), - [2., 2., -2., -2.] * self.ureg.m + np.tile(self.q, 2), np.array([[1, 2, 1, 2], [3, 4, 3, 4]]) * self.ureg.m ) - @unittest.expectedFailure - def test_gradient(self): - """shape is a property not a function - """ - l = np.gradient([[1,1],[3,4]] * self.ureg.J, 1 * self.ureg.m) - self.assertQuantityEqual(l[0], [[2., 3.], [2., 3.]] * self.ureg.J / self.ureg.m) - self.assertQuantityEqual(l[1], [[0., 0.], [1., 1.]] * self.ureg.J / 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 + ) - @unittest.expectedFailure - def test_cross(self): - """Units are erased by asarray, Quantity does not inherit from NDArray - """ - 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_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, + ) - @unittest.expectedFailure - def test_power(self): - """This is not supported as different elements might end up with different units + @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 + ) - eg. ([1, 1] * m) ** [2, 3] + @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 + ) - Must force exponent to single value - """ - self._test2(np.power, self.q1, - (self.qless, np.asarray([1., 2, 3, 4])), - (self.q2, ),) + 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 - @unittest.expectedFailure - def test_ones_like(self): - """Units are erased by emptyarra, Quantity does not inherit from NDArray - """ - self._test1(np.ones_like, - (self.q2, self.qs, self.qless, self.qi), - (), - 2) + 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 @@ -364,6 +1166,13 @@ 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 @@ -387,78 +1196,31 @@ 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') + 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') + 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') + 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') + 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') + 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') - - -class TestNDArrayQuantityMath(QuantityTestCase): - - @helpers.requires_numpy() - def test_exponentiation_array_exp(self): - arr = np.array(range(3), dtype=np.float) - q = self.Q_(arr, 'meter') - - for op_ in [op.pow, op.ipow]: - q_cp = copy.copy(q) - self.assertRaises(DimensionalityError, op_, 2., 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) - - @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) + self._test2( + np.right_shift, + self.q1, + (self.qless, 2), + (self.q1, self.q2, self.qs), + "same", + ) diff -Nru python-pint-0.9/pint/testsuite/test_pint_eval.py python-pint-0.10.1/pint/testsuite/test_pint_eval.py --- python-pint-0.9/pint/testsuite/test_pint_eval.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_pint_eval.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import import unittest from pint.compat import tokenizer @@ -8,65 +5,64 @@ 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)') + self._test_one("3", "3") + self._test_one("1 + 2", "(1 + 2)") # order of operations - self._test_one('2 * 3 + 4', '((2 * 3) + 4)') + self._test_one("2 * 3 + 4", "((2 * 3) + 4)") # parentheses - self._test_one('2 * (3 + 4)', '(2 * (3 + 4))') + 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)))))') + 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))') + 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)))') + 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))') + self._test_one("1 * (5 * (3 + 4) / 6)", "(1 * ((5 * (3 + 4)) / 6))") # unary - self._test_one('-1', '(- 1)') + self._test_one("-1", "(- 1)") # unary - self._test_one('3 * -1', '(3 * (- 1))') + self._test_one("3 * -1", "(3 * (- 1))") # double unary - self._test_one('3 * --1', '(3 * (- (- 1)))') + self._test_one("3 * --1", "(3 * (- (- 1)))") # parenthetical unary - self._test_one('3 * -(2 + 4)', '(3 * (- (2 + 4)))') + self._test_one("3 * -(2 + 4)", "(3 * (- (2 + 4)))") # parenthetical unary - self._test_one('3 * -((2 + 4))', '(3 * (- (2 + 4)))') + self._test_one("3 * -((2 + 4))", "(3 * (- (2 + 4)))") # implicit op - self._test_one('3 4', '(3 4)') + self._test_one("3 4", "(3 4)") # implicit op, then parentheses - self._test_one('3 (2 + 4)', '(3 (2 + 4))') + self._test_one("3 (2 + 4)", "(3 (2 + 4))") # parentheses, then implicit - self._test_one('(3 ** 4 ) 5', '((3 ** 4) 5)') + self._test_one("(3 ** 4 ) 5", "((3 ** 4) 5)") # implicit op, then exponentiation - self._test_one('3 4 ** 5', '(3 (4 ** 5))') + self._test_one("3 4 ** 5", "(3 (4 ** 5))") # implicit op, then addition - self._test_one('3 4 + 5', '((3 4) + 5)') + self._test_one("3 4 + 5", "((3 4) + 5)") # power followed by implicit - self._test_one('3 ** 4 5', '((3 ** 4) 5)') + self._test_one("3 ** 4 5", "((3 ** 4) 5)") # implicit with parentheses - self._test_one('3 (4 ** 5)', '(3 (4 ** 5))') + self._test_one("3 (4 ** 5)", "(3 (4 ** 5))") # exponent with e - self._test_one('3e-1', '3e-1') + self._test_one("3e-1", "3e-1") # multiple units with exponents - self._test_one('kg ** 1 * s ** 2', '((kg ** 1) * (s ** 2))') + 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)))') + 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)))') + 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)))') + self._test_one("kg^-1 s^-2", "((kg ^ (- 1)) (s ^ (- 2)))") # nested power - self._test_one('2 ^ 3 ^ 2', '(2 ^ (3 ^ 2))') + self._test_one("2 ^ 3 ^ 2", "(2 ^ (3 ^ 2))") # nested power - self._test_one('gram * second / meter ** 2', '((gram * second) / (meter ** 2))') + self._test_one("gram * second / meter ** 2", "((gram * second) / (meter ** 2))") # nested power - self._test_one('gram / meter ** 2 / second', '((gram / (meter ** 2)) / second)') + 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)') + self._test_one("3 kg + 5", "((3 kg) + 5)") diff -Nru python-pint-0.9/pint/testsuite/test_pitheorem.py python-pint-0.10.1/pint/testsuite/test_pitheorem.py --- python-pint-0.9/pint/testsuite/test_pitheorem.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_pitheorem.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,11 +1,6 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - import itertools from pint import pi_theorem - from pint.testsuite import QuantityTestCase @@ -17,18 +12,22 @@ # 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}]) + 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( + 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' + V = "km/hour" + T = "ms" + L = "cm" f1 = lambda x: x f2 = lambda x: self.Q_(1, x) @@ -40,5 +39,7 @@ 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}]) + self.assertEqual( + self.ureg.pi_theorem({"V": qv, "T": qt, "L": ql}), + [{"V": 1.0, "T": 1.0, "L": -1.0}], + ) diff -Nru python-pint-0.9/pint/testsuite/test_quantity.py python-pint-0.10.1/pint/testsuite/test_quantity.py --- python-pint-0.9/pint/testsuite/test_quantity.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_quantity.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,18 +1,21 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - import copy import datetime import math import operator as op import warnings +from unittest.mock import patch from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry -from pint.unit import UnitsContainer -from pint.compat import string_types, PYTHON3, np +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): @@ -20,12 +23,14 @@ 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'),)): + 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)) @@ -46,15 +51,17 @@ def test_quantity_bool(self): self.assertTrue(self.Q_(1, None)) - self.assertTrue(self.Q_(1, 'meter')) + self.assertTrue(self.Q_(1, "meter")) self.assertFalse(self.Q_(0, None)) - self.assertFalse(self.Q_(0, 'meter')) + 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') + 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) @@ -76,25 +83,27 @@ 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.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')) + 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')) + 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(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') + 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 @@ -102,83 +111,131 @@ # 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') + 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 (('{0}', str(x)), ('{0!s}', str(x)), ('{0!r}', repr(x)), - ('{0.magnitude}', str(x.magnitude)), ('{0.units}', str(x.units)), - ('{0.magnitude!s}', str(x.magnitude)), ('{0.units!s}', str(x.units)), - ('{0.magnitude!r}', repr(x.magnitude)), ('{0.units!r}', repr(x.units)), - ('{0:.4f}', '{0:.4f} {1!s}'.format(x.magnitude, x.units)), - ('{0:L}', r'4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}'), - ('{0:P}', '4.12345678 kilogram·meter²/second'), - ('{0:H}', '4.12345678 kilogram meter2/second'), - ('{0:C}', '4.12345678 kilogram*meter**2/second'), - ('{0:~}', '4.12345678 kg * m ** 2 / s'), - ('{0:L~}', r'4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}'), - ('{0:P~}', '4.12345678 kg·m²/s'), - ('{0:H~}', '4.12345678 kg m2/s'), - ('{0:C~}', '4.12345678 kg*m**2/s'), - ('{0:Lx}', r'\SI[]{4.12345678}{\kilo\gram\meter\squared\per\second}'), - ): - self.assertEqual(spec.format(x), result) + 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('{0}'.format(x), '3 / second') + 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., 'nanosecond') + 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., 'millinewton') + 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., 'kilometer') + 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('{0:#.1f}'.format(q1), '{0}'.format(q1b)) - self.assertEqual('{0:#.1f}'.format(q2), '{0}'.format(q2b)) - self.assertEqual('{0:#.1f}'.format(q3), '{0}'.format(q3b)) - + 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', '4.12345678 kilogram meter2/second'), - ('C', '4.12345678 kilogram*meter**2/second'), - ('~', '4.12345678 kg * m ** 2 / s'), - ('L~', r'4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}'), - ('P~', '4.12345678 kg·m²/s'), - ('H~', '4.12345678 kg m2/s'), - ('C~', '4.12345678 kg*m**2/s'), - ): - ureg.default_format = spec - self.assertEqual('{0}'.format(x), result) + 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, UnitsContainer(meter=1)) - self.assertEqual("{:~H}".format(x), "1×1020 m") - self.assertEqual("{:~L}".format(x), r"1\times 10^{20}\ \mathrm{m}") + 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("{:~H}".format(x), "1×10-20 m") - self.assertEqual("{:~L}".format(x), r"1\times 10^{-20}\ \mathrm{m}") + 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(object): + class Pretty: @staticmethod def text(text): alltext.append(text) @@ -192,45 +249,55 @@ ureg = UnitRegistry() x = 3.5 * ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1)) - self.assertEqual(x._repr_html_(), - "3.5 kilogram meter2/second") - self.assertEqual(x._repr_latex_(), - r'$3.5\ \frac{\mathrm{kilogram} \cdot ' - r'\mathrm{meter}^{2}}{\mathrm{second}}$') + 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_(), "3.5 kg m2/s") - self.assertEqual(x._repr_latex_(), - r'$3.5\ \frac{\mathrm{kg} \cdot ' - r'\mathrm{m}^{2}}{\mathrm{s}}$') + 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., 'meter/second')) + 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): - x = self.Q_('2*inch') - self.assertQuantityAlmostEqual(x.to('meter'), self.Q_(2. * 0.0254, 'meter')) - x = self.Q_('2*meter') - self.assertQuantityAlmostEqual(x.to('inch'), self.Q_(2. / 0.0254, 'inch')) - x = self.Q_('2*sidereal_second') - self.assertQuantityAlmostEqual(x.to('second'), self.Q_(1.994539133 , 'second')) - x = self.Q_('2.54*centimeter/second') - self.assertQuantityAlmostEqual(x.to('inch/second'), self.Q_(1, 'inch/second')) - x = self.Q_('2.54*centimeter') - self.assertQuantityAlmostEqual(x.to('inch').magnitude, 1) - self.assertQuantityAlmostEqual(self.Q_(2, 'second').to('millisecond').magnitude, 2000) + 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(self): + def test_convert_numpy(self): # Conversions with single units take a different codepath than # Conversions with more than one unit. @@ -248,20 +315,24 @@ self.assertIsNot(r._magnitude, a) def test_convert_from(self): - x = self.Q_('2*inch') + x = self.Q_("2*inch") meter = self.ureg.meter # from quantity - self.assertQuantityAlmostEqual(meter.from_(x), self.Q_(2. * 0.0254, 'meter')) - self.assertQuantityAlmostEqual(meter.m_from(x), 2. * 0.0254) + 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.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., 'meter')) - self.assertQuantityAlmostEqual(meter.m_from(2, strict=False), 2.) + 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) @@ -276,72 +347,140 @@ 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) + 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')) + 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')) + 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) + 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) - + 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) - + 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 @@ -349,88 +488,145 @@ 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')) + 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) -class TestQuantityToCompact(QuantityTestCase): + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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) + 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 - self.assertRaises(RuntimeError, self.compareQuantity_compact(x,x)) + 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, string_types): + if isinstance(value1, str): value1 = self.Q_(value1) - if isinstance(value2, string_types): + if isinstance(value2, str): value2 = self.Q_(value2) - if isinstance(expected_result, string_types): + if isinstance(expected_result, str): expected_result = self.Q_(expected_result) - if not unit is None: + if unit is not None: value1 = value1 * unit value2 = value2 * unit expected_result = expected_result * unit @@ -447,14 +643,14 @@ self.assertEqual(id2, id(value2)) def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None): - if isinstance(value1, string_types): + if isinstance(value1, str): value1 = self.Q_(value1) - if isinstance(value2, string_types): + if isinstance(value2, str): value2 = self.Q_(value2) - if isinstance(expected_result, string_types): + if isinstance(expected_result, str): expected_result = self.Q_(expected_result) - if not unit is None: + if unit is not None: value1 = value1 * unit value2 = value2 * unit expected_result = expected_result * unit @@ -474,96 +670,96 @@ 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') + 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, 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, 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') + 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, 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, 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) + 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) + # 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') + 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) + 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') + 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) + 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) + 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, string_types): + if isinstance(a, str): a = self.Q_(a) - if isinstance(b, string_types): + if isinstance(b, str): b = self.Q_(b) q, r = divmod(a, b) @@ -588,19 +784,19 @@ 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') + 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') + 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) @@ -613,13 +809,14 @@ 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) + # self._test_quantity_ifloordiv(unit, ifunc) def test_float(self): - self._test_numeric(1., self._test_not_inplace) + 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() @@ -628,24 +825,23 @@ def test_quantity_abs_round(self): - x = self.Q_(-4.2, 'meter') - y = self.Q_(4.2, 'meter') - # In Python 3+ round of x is delegated to x.__round__, instead of round(x.__float__) - # and therefore it can be properly implemented by Pint - for fun in (abs, op.pos, op.neg) + (round, ) if PYTHON3 else (): - zx = self.Q_(fun(x.magnitude), 'meter') - zy = self.Q_(fun(y.magnitude), 'meter') + 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)) + 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') + z = self.Q_(1, "meter") for fun in (float, complex): self.assertEqual(fun(x), fun(x.magnitude)) self.assertEqual(fun(y), fun(y.magnitude)) @@ -658,471 +854,452 @@ 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})) + 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 = self.Q_(42, "centimeter") x.to_base_units() - x = self.Q_(42, 'meter*second') - self.assertEqual(x.dimensionality, UnitsContainer({'[length]': 1., '[time]': 1.})) - x = self.Q_(42, 'meter*second*second') - self.assertEqual(x.dimensionality, UnitsContainer({'[length]': 1., '[time]': 2.})) - x = self.Q_(42, 'inch*second*second') - self.assertEqual(x.dimensionality, UnitsContainer({'[length]': 1., '[time]': 2.})) + 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) + 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]'): + 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) + 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')), - ] + (((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) + @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': + 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) + self.assertQuantityAlmostEqual(op.add(q1, q2), expected, atol=0.01) @helpers.requires_numpy() - @ParameterizedTestCase.parameterize(("input", "expected_output"), - additions) + @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)) + 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': + if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.iadd, q1_cp, q2) else: - expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + 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) + 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')), - ] + (((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) + @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': + 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) + self.assertQuantityAlmostEqual(op.sub(q1, q2), expected, atol=0.01) -# @unittest.expectedFailure + # @unittest.expectedFailure @helpers.requires_numpy() - @ParameterizedTestCase.parameterize(("input", "expected_output"), - subtractions) + @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)) + 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': + if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.isub, q1_cp, q2) else: - expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + 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) + 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')), - ] + (((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) + @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': + 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) + self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01) @helpers.requires_numpy() - @ParameterizedTestCase.parameterize(("input", "expected_output"), - multiplications) + @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)) + 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': + if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.imul, q1_cp, q2) else: - expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + 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) + 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, '')), - ] + (((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) + @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': + 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) + self.assertQuantityAlmostEqual(op.truediv(q1, q2), expected, atol=0.01) @helpers.requires_numpy() - @ParameterizedTestCase.parameterize(("input", "expected_output"), - divisions) + @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)) + 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': + if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.itruediv, q1_cp, q2) else: - expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + 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) + self.assertQuantityAlmostEqual( + op.itruediv(q1_cp, q2), Q_(*expected), atol=0.01 + ) multiplications_with_autoconvert_to_baseunit = [ - (((100, 'kelvin'), (10, 'degC')), (28315., '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., 'degR*kelvin')), - (((100, 'degR'), (10, 'degF')), (26092.78, 'degR*kelvin')), - - (((100, 'delta_degC'), (10, 'degC')), (28315., 'delta_degC*kelvin')), - (((100, 'delta_degC'), (10, 'degF')), (26092.78, 'delta_degC*kelvin')), - - (((100, 'delta_degF'), (10, 'degC')), (28315., 'delta_degF*kelvin')), - (((100, 'delta_degF'), (10, 'degF')), (26092.78, 'delta_degF*kelvin')), - ] + (((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) + ("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': + 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) + self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01) @helpers.requires_numpy() @ParameterizedTestCase.parameterize( - ("input", "expected_output"), - multiplications_with_autoconvert_to_baseunit) + ("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)) + 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': + if expected == "error": self.assertRaises(OffsetUnitCalculusError, op.imul, q1_cp, q2) else: - expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + 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) + self.assertQuantityAlmostEqual(op.imul(q1_cp, q2), Q_(*expected), atol=0.01) multiplications_with_scalar = [ - (((10, 'kelvin'), 2), (20., 'kelvin')), - (((10, 'kelvin**2'), 2), (20., 'kelvin**2')), - (((10, 'degC'), 2), (20., 'degC')), - (((10, '1/degC'), 2), 'error'), - (((10, 'degC**0.5'), 2), 'error'), - (((10, 'degC**2'), 2), 'error'), - (((10, 'degC**-2'), 2), 'error'), - ] + (((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) + ("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 @@ -1131,29 +1308,28 @@ else: in1, in2 = in1, self.Q_(*in2) input_tuple = in1, in2 # update input_tuple for better tracebacks - if expected == 'error': + 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) + self.assertQuantityAlmostEqual(op.mul(in1, in2), expected, atol=0.01) - divisions_with_scalar = [ # without / with autoconvert to base unit - (((10, 'kelvin'), 2), [(5., 'kelvin'), (5., 'kelvin')]), - (((10, 'kelvin**2'), 2), [(5., 'kelvin**2'), (5., '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']), - ] + 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) + ("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 @@ -1165,34 +1341,33 @@ expected_copy = expected[:] for i, mode in enumerate([False, True]): self.ureg.autoconvert_offset_to_baseunit = mode - if expected_copy[i] == 'error': + 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., ''), (1., '')]), - (((10, 'degC'), -1), ['error', (1/(10+273.15), 'kelvin**-1')]), - (((10, 'degC'), -2), ['error', (1/(10+273.15)**2., '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., 'kelvin**2'), (100., 'kelvin**2')]), - - (( 2, (2, 'kelvin')), ['error', 'error']), - (( 2, (500., 'millikelvin/kelvin')), [2**0.5, 2**0.5]), - (( 2, (0.5, 'kelvin/kelvin')), [2**0.5, 2**0.5]), - (((10, 'degC'), (500., 'millikelvin/kelvin')), - ['error', (283.15**0.5, 'kelvin**0.5')]), - ] + 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) + @ParameterizedTestCase.parameterize(("input", "expected_output"), exponentiation) def test_exponentiation(self, input_tuple, expected): self.ureg.default_as_delta = False in1, in2 = input_tuple @@ -1206,9 +1381,10 @@ 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) + 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]) @@ -1218,14 +1394,13 @@ self.assertQuantityAlmostEqual(op.pow(in1, in2), expected) @helpers.requires_numpy() - @ParameterizedTestCase.parameterize( - ("input", "expected_output"), exponentiation) + @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)) + 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) @@ -1238,22 +1413,33 @@ 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) + 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]) + 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) - + 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): @@ -1262,8 +1448,8 @@ return density * volume def _icalc_mass(self, ureg): - res = ureg.Quantity(3.0, 'gram/liter') - res *= ureg.Quantity(32.0, 'milliliter') + res = ureg.Quantity(3.0, "gram/liter") + res *= ureg.Quantity(32.0, "milliliter") return res def test_mul_and_div_reduction(self): @@ -1296,6 +1482,7 @@ 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) @@ -1328,13 +1515,20 @@ """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(0, ureg.J) == ureg.Quantity(0, "")) self.assertFalse(ureg.Quantity(5, ureg.J) == 0) @helpers.requires_numpy() @@ -1342,44 +1536,44 @@ 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])) + 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 = self.ureg ureg.autoconvert_offset_to_baseunit = False - q0 = ureg.Quantity(-273.15, 'degC') - q1 = ureg.Quantity(0, 'degC') - q2 = ureg.Quantity(5, 'degC') + 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, '')) + self.assertFalse(q0 == ureg.Quantity(0, "")) def test_offset_autoconvert_equal_zero(self): - ureg = self.ureg + 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') + 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, '')) + 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') + 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) @@ -1390,39 +1584,41 @@ 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') + 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)) + 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 = self.ureg ureg.autoconvert_offset_to_baseunit = False - q0 = ureg.Quantity(-273.15, 'degC') - q1 = ureg.Quantity(0, 'degC') - q2 = ureg.Quantity(5, 'degC') + 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, '')) + self.assertRaises(DimensionalityError, q1.__gt__, ureg.Quantity(0, "")) def test_offset_autoconvert_gt_zero(self): - ureg = self.ureg + 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') + 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, '')) + self.assertRaises(DimensionalityError, q1.__gt__, ureg.Quantity(0, "")) diff -Nru python-pint-0.9/pint/testsuite/test_systems.py python-pint-0.10.1/pint/testsuite/test_systems.py --- python-pint-0.9/pint/testsuite/test_systems.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_systems.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,144 +1,113 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - - from 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 = ureg.get_group("root") + grp.remove_units("pi") grp.invalidate_members() - return ureg, ureg.get_group('root') + return ureg, ureg.get_group("root") def test_units_programatically(self): ureg, root = self._build_empty_reg_root() - d = ureg._groups + 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'}) + root.add_units("meter", "second", "meter") + self.assertEqual(root._unit_names, {"meter", "second"}) + self.assertEqual(root.members, {"meter", "second"}) - self.assertEqual(set(d.keys()), {'root'}) + 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') + 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') + g2 = ureg.Group("g2") - self.assertEqual(set(d.keys()), {'root', 'g2'}) + self.assertEqual(d.keys(), {"root", "g2"}) - self.assertEqual(root._used_groups, {'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'}) - + self.assertEqual(g2._used_by, {"root"}) def test_simple(self): - lines = ['@group mygroup', - 'meter', - 'second', - ] + 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(set(d.keys()), {'root', 'mygroup'}) + self.assertEqual(d.keys(), {"root", "mygroup"}) - self.assertEqual(grp.name, 'mygroup') - self.assertEqual(grp._unit_names, {'meter', 'second'}) + 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'])) + self.assertEqual(grp.members, frozenset(["meter", "second"])) def test_using1(self): - lines = ['@group mygroup using group1', - 'meter', - 'second', - ] + lines = ["@group mygroup using group1", "meter", "second"] ureg, root = self._build_empty_reg_root() - d = ureg._groups - - g = ureg.Group('group1') + 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'])) + 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', - ] + lines = ["@group mygroup using group1,group2", "meter", "second"] ureg, root = self._build_empty_reg_root() - d = ureg._groups - - ureg.Group('group1') - ureg.Group('group2') + 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'])) + 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 ', - ] + lines = ["@group mygroup using group1 , group2", " meter ", " second "] ureg, root = self._build_empty_reg_root() - d = ureg._groups - - ureg.Group('group1') - ureg.Group('group2') + 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'])) + 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', - ] + lines = ["@group mygroup using group1", "meter", "second"] ureg, root = self._build_empty_reg_root() - d = ureg._groups - - g1 = ureg.Group('group1') + 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.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.assertEqual(root.members, frozenset(["meter", "second"])) self.assertIsNot(root._computed_members, None) self.assertIsNot(grp._computed_members, None) grp.invalidate_members() @@ -146,202 +115,168 @@ 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' - ] + 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() - d = ureg._groups - - grp = ureg.Group.from_lines(lines, define) + ureg.Group.from_lines(lines, define) - self.assertEqual(['kings_leg', 'kings_head'], defs) + self.assertEqual(["kings_leg", "kings_head"], defs) def test_members_including(self): - ureg, root = self._build_empty_reg_root() - d = ureg._groups - g1 = ureg.Group('group1') + g1 = ureg.Group("group1") + g1.add_units("second", "inch") + + g2 = ureg.Group("group2") + g2.add_units("second", "newton") - 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'])) + 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') + 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 = ureg.get_group("root") + grp.remove_units("pi") grp.invalidate_members() - return ureg, ureg.get_group('root') + return ureg, ureg.get_group("root") def test_implicit_root(self): - lines = ['@system mks', - 'meter', - 'kilogram', - 'second', - ] + lines = ["@system mks", "meter", "kilogram", "second"] ureg, root = self._build_empty_reg_root() - d = ureg._groups - s = ureg.System.from_lines(lines, lambda x: x) - s._used_groups = {'root'} + s._used_groups = {"root"} def test_simple_using(self): - lines = ['@system mks using g1', - 'meter', - 'kilogram', - 'second', - ] + lines = ["@system mks using g1", "meter", "kilogram", "second"] ureg, root = self._build_empty_reg_root() - d = ureg._groups - s = ureg.System.from_lines(lines, lambda x: x) - s._used_groups = {'root', 'g1'} - + s._used_groups = {"root", "g1"} def test_members_group(self): - lines = ['@system mk', - 'meter', - 'kilogram', - ] + lines = ["@system mk", "meter", "kilogram"] ureg, root = self._build_empty_reg_root() - d = ureg._groups - - root.add_units('second') + root.add_units("second") s = ureg.System.from_lines(lines, lambda x: x) - self.assertEqual(s.members, frozenset(['second'])) + self.assertEqual(s.members, frozenset(["second"])) def test_get_compatible_units(self): - sysname = 'mysys1' + sysname = "mysys1" ureg = UnitRegistry() - g = ureg.get_group('test-imperial') + g = ureg.get_group("test-imperial") - g.add_units('inch', 'yard', 'pint') - c = ureg.get_compatible_units('meter', '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', - ] + lines = ["@system %s using test-imperial" % sysname, "inch"] - s = ureg.System.from_lines(lines, lambda x: x) - c = ureg.get_compatible_units('meter', sysname) + 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' - + sysname = "mysys2" ureg = UnitRegistry() - g = ureg.get_group('test-imperial') - g.add_units('inch', 'yard', 'pint') + g = ureg.get_group("test-imperial") + g.add_units("inch", "yard", "pint") - lines = ['@system %s using test-imperial' % sysname, - 'inch', - ] + 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) + c = ureg.get_base_units("inch", system=sysname) self.assertAlmostEqual(c[0], 1) - self.assertEqual(c[1], {'inch': 1}) + self.assertEqual(c[1], {"inch": 1}) - c = ureg.get_base_units('cm', system=sysname) - self.assertAlmostEqual(c[0], 1./2.54) - 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' - + sysname = "mysys3" ureg = UnitRegistry() - g = ureg.get_group('test-imperial') - g.add_units('inch', 'yard', 'pint') - c = ureg.get_compatible_units('meter', 'test-imperial') - - lines = ['@system %s using test-imperial' % sysname, - 'pint:meter', - ] + 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) + c = ureg.get_base_units("inch", system=sysname) self.assertAlmostEqual(c[0], 0.326, places=3) - self.assertEqual(c[1], {'pint': 1./3}) + self.assertEqual(c[1], {"pint": 1.0 / 3}) - c = ureg.get_base_units('cm', system=sysname) + c = ureg.get_base_units("cm", system=sysname) self.assertAlmostEqual(c[0], 0.1283, places=3) - self.assertEqual(c[1], {'pint': 1./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./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./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' - + sysname = "mysys4" ureg = UnitRegistry() - g = ureg.get_group('test-imperial') - g.add_units('inch', 'yard', 'pint') + g = ureg.get_group("test-imperial") + g.add_units("inch", "yard", "pint") - lines = ['@system %s using test-imperial' % sysname, - 'mph:meter', - ] + 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) + c = ureg.get_base_units("inch", system=sysname) self.assertAlmostEqual(c[0], 0.056, places=2) - self.assertEqual(c[1], {'mph': 1, 'second': 1}) + self.assertEqual(c[1], {"mph": 1, "second": 1}) - c = ureg.get_base_units('kph', system=sysname) + c = ureg.get_base_units("kph", system=sysname) self.assertAlmostEqual(c[0], 0.6213, places=3) - self.assertEqual(c[1], {'mph': 1}) + self.assertEqual(c[1], {"mph": 1}) def test_members_nowarning(self): ureg = self.ureg for name in dir(ureg.sys): - s = dir(getattr(ureg.sys, name)) + dir(getattr(ureg.sys, name)) diff -Nru python-pint-0.9/pint/testsuite/test_umath.py python-pint-0.10.1/pint/testsuite/test_umath.py --- python-pint-0.9/pint/testsuite/test_umath.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_umath.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - +from pint import DimensionalityError from pint.compat import np from pint.testsuite import QuantityTestCase, helpers @@ -18,7 +15,7 @@ @property def qless(self): - return np.asarray([1., 2., 3., 4.]) * self.ureg.dimensionless + return np.asarray([1.0, 2.0, 3.0, 4.0]) * self.ureg.dimensionless @property def qs(self): @@ -26,7 +23,7 @@ @property def q1(self): - return np.asarray([1., 2., 3., 4.]) * self.ureg.J + return np.asarray([1.0, 2.0, 3.0, 4.0]) * self.ureg.J @property def q2(self): @@ -34,42 +31,47 @@ @property def qm(self): - return np.asarray([1., 2., 3., 4.]) * self.ureg.m + 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 assertRaisesMsg(self, msg, ExcType, func, *args, **kwargs): - try: - func(*args, **kwargs) - self.assertFalse(True, msg='Exception {} not raised {}'.format(ExcType, msg)) - except ExcType as e: - pass - except Exception as e: - self.assertFalse(True, msg='{} not raised but {}\n{}'.format(ExcType, e, msg)) - - def _test1(self, func, ok_with, raise_with=(), output_units='same', results=None, rtol=1e-6): + 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. - :param func: function callable. - :param ok_with: iterables of values that work fine. - :param raise_with: iterables of values that raise exceptions. - :param 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. - :param results: iterable of results. - If None, the result will be obtained by applying - func to each ok_with value - :param rtol: relative tolerance. + 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) + 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': + 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 @@ -85,49 +87,82 @@ self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg) for x1 in raise_with: - self.assertRaisesMsg('At {} with {}'.format(func.__name__, x1), - ValueError, func, x1) + with self.assertRaises( + DimensionalityError, msg=f"At {func.__name__} with {x1}" + ): + func(x1) - def _testn(self, func, ok_with, raise_with=(), results=None): + def _testn(self, func, ok_with, raise_with=(), results=None): """Test function that takes a single argument and returns and ndarray (not a Quantity) - :param func: function callable. - :param ok_with: iterables of values that work fine. - :param raise_with: iterables of values that raise exceptions. - :param results: iterable of results. - If None, the result will be obtained by applying - func to each ok_with value + 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): + 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. - :param func: function callable. - :param ok_with: iterables of values that work fine. - :param raise_with: iterables of values that raise exceptions. - :param 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. - :param results: iterable of results. - If None, the result will be obtained by applying - func to each ok_with value - :param rtol: relative tolerance. + 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) + results = [None] * len(ok_with) for x1, res in zip(ok_with, results): - err_msg = 'At {} with {}'.format(func.__name__, x1) + 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': + if ou == "same": ou = x1.units elif isinstance(ou, (int, float)): ou = x1.units ** ou @@ -138,45 +173,67 @@ self.assertQuantityAlmostEqual(qm, re, rtol=rtol, msg=err_msg) for x1 in raise_with: - self.assertRaisesMsg('At {} with {}'.format(func.__name__, x1), - ValueError, func, x1) + 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): + 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. - :param func: function callable. - :param x1: first argument of func. - :param ok_with: iterables of values that work fine. - :param raise_with: iterables of values that raise exceptions. - :param 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. - :param rtol: relative tolerance. - :param convert2: if the ok_with[n] should be converted to x1.units. + 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': + err_msg = "At {} with {} and {}".format(func.__name__, x1, x2) + if output_units == "same": ou = x1.units - elif output_units == 'prod': + elif output_units == "prod": ou = x1.units * x2.units - elif output_units == 'div': + elif output_units == "div": ou = x1.units / x2.units - elif output_units == 'second': + 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 + if convert2 and hasattr(x2, "magnitude"): + m2 = x2.to(getattr(x1, "units", "")).magnitude else: - m2 = getattr(x2, 'magnitude', x2) + m2 = getattr(x2, "magnitude", x2) res = func(x1.magnitude, m2) if ou is not None: @@ -185,16 +242,28 @@ self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg) for x2 in raise_with: - self.assertRaisesMsg('At {} with {} and {}'.format(func.__name__, x1, x2), - ValueError, func, x1, x2) + 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. - :param func: function callable. - :param x1: first argument of func. - :param ok_with: iterables of values that work fine. - :param raise_with: iterables of values that raise exceptions. + 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) @@ -233,158 +302,118 @@ 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, )) + 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, )) + 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') + 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) + 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, ), - '') + 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') + 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) + 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) - + 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), - ()) + 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) + 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) + 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) + 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') + 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') + 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') + 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, ), - '') + self._test1(np.exp, (self.qless,), (self.q1,), "") def test_exp2(self): - self._test1(np.exp2, - (self.qless,), - (self.q1, ), - '') + self._test1(np.exp2, (self.qless,), (self.q1,), "") def test_log(self): - self._test1(np.log, - (self.qless,), - (self.q1, ), - '') + self._test1(np.log, (self.qless,), (self.q1,), "") def test_log2(self): - self._test1(np.log2, - (self.qless,), - (self.q1, ), - '') + self._test1(np.log2, (self.qless,), (self.q1,), "") def test_log10(self): - self._test1(np.log10, - (self.qless,), - (self.q1, ), - '') + self._test1(np.log10, (self.qless,), (self.q1,), "") def test_expm1(self): - self._test1(np.expm1, - (self.qless,), - (self.q1, ), - '') + 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) + 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) + 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) + self._test1(np.reciprocal, (self.q2, self.qs, self.qless, self.qi), (), -1) @helpers.requires_numpy() @@ -409,127 +438,240 @@ 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)), )) + 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)), ) + 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)), )) + 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, .9, .1) * self.ureg.dimensionless, - np.arange(0, .9, .1) * self.ureg.m / self.ureg.m - ), (1*self.ureg.m, ), 'radian') + 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): - x = np.arange(0, .9, .1) * self.ureg.m - self._test1(np.arccos, (np.arange(0, .9, .1) * self.ureg.dimensionless, - np.arange(0, .9, .1) * self.ureg.m / self.ureg.m - ), (1*self.ureg.m, ), 'radian') + 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, .9, .1) * self.ureg.dimensionless, - np.arange(0, .9, .1) * self.ureg.m / self.ureg.m - ), (1*self.ureg.m, ), 'radian') + 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, .9, .1) * m, - (np.arange(0, .9, .1) * m, np.arange(.9, 0., -.1) * m, - np.arange(0, .9, .1) * km, np.arange(.9, 0., -.1) * km, - ), - raise_with=np.arange(0, .9, .1) * j, - output_units='radian') + 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. * self.ureg.m, 4. * self.ureg.m) == 5. * self.ureg.m) - self.assertTrue(np.hypot(3. * self.ureg.m, 400. * self.ureg.cm) == 5. * self.ureg.m) - self.assertRaises(ValueError, np.hypot, 1. * self.ureg.m, 2. * self.ureg.J) + 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)), )) + 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)), )) + 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)), )) + 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, .9, .1) * self.ureg.dimensionless, - np.arange(0, .9, .1) * self.ureg.m / self.ureg.m - ), (1*self.ureg.m, ), 'radian') + 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., 1.9, .1) * self.ureg.dimensionless, - np.arange(1., 1.9, .1) * self.ureg.m / self.ureg.m - ), (1*self.ureg.m, ), 'radian') + 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, .9, .1) * self.ureg.dimensionless, - np.arange(0, .9, .1) * self.ureg.m / self.ureg.m - ), (.1 * self.ureg.m, ), 'radian') + 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') + 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, - )) - + 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): @@ -543,43 +685,32 @@ 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, )) + 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, )) + 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, )) + 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, )) + 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, )) + 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, )) + self._testn2(np.equal, self.q1, (self.q2,), (self.qm,)) class TestFloatingUfuncs(TestUFuncs): @@ -602,72 +733,58 @@ 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)) + self._testn(np.isreal, (self.q1, self.qm, self.qless)) def test_iscomplex(self): - self._testn(np.iscomplex, - (self.q1, self.qm, self.qless)) + self._testn(np.iscomplex, (self.q1, self.qm, self.qless)) def test_isfinite(self): - self._testn(np.isfinite, - (self.q1, self.qm, self.qless)) + self._testn(np.isfinite, (self.q1, self.qm, self.qless)) def test_isinf(self): - self._testn(np.isinf, - (self.q1, self.qm, self.qless)) + self._testn(np.isinf, (self.q1, self.qm, self.qless)) def test_isnan(self): - self._testn(np.isnan, - (self.q1, self.qm, self.qless)) + self._testn(np.isnan, (self.q1, self.qm, self.qless)) def test_signbit(self): - self._testn(np.signbit, - (self.q1, self.qm, self.qless)) + 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, )) + 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, )) + 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), - ) + 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, )) + self._test2(np.ldexp, x1, (x2,)) def test_frexp(self): - self._test1_2o(np.frexp, - (self.q2, self.qs), - output_units=('same', None)) + 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)) + self._test1(np.floor, (self.q1, self.qm, self.qless)) def test_ceil(self): - self._test1(np.ceil, - (self.q1, self.qm, self.qless)) + self._test1(np.ceil, (self.q1, self.qm, self.qless)) def test_trunc(self): - self._test1(np.trunc, - (self.q1, self.qm, self.qless)) + self._test1(np.trunc, (self.q1, self.qm, self.qless)) diff -Nru python-pint-0.9/pint/testsuite/test_unit.py python-pint-0.10.1/pint/testsuite/test_unit.py --- python-pint-0.9/pint/testsuite/test_unit.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/testsuite/test_unit.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,22 +1,24 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, unicode_literals, print_function, absolute_import - import copy +import functools import math +import re -from pint.registry import (UnitRegistry, LazyRegistry) -from pint.util import (UnitsContainer, ParserHelper) -from pint import DimensionalityError, UndefinedUnitError -from pint.compat import u, np, string_types +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')): + 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) @@ -26,142 +28,174 @@ def test_unit_repr(self): x = self.U_(UnitsContainer(meter=1)) - self.assertEqual(str(x), 'meter') + 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 (('{0}', str(x)), ('{0!s}', str(x)), - ('{0!r}', repr(x)), - ('{0:L}', r'\frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}'), - ('{0:P}', 'kilogram·meter²/second'), - ('{0:H}', 'kilogram meter2/second'), - ('{0:C}', 'kilogram*meter**2/second'), - ('{0:Lx}', r'\si[]{\kilo\gram\meter\squared\per\second}'), - ('{0:~}', 'kg * m ** 2 / s'), - ('{0:L~}', r'\frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}'), - ('{0:P~}', 'kg·m²/s'), - ('{0:H~}', 'kg m2/s'), - ('{0:C~}', 'kg*m**2/s'), - ): - self.assertEqual(spec.format(x), result) + 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', 'kilogram meter2/second'), - ('C', 'kilogram*meter**2/second'), - ('~', 'kg * m ** 2 / s'), - ('L~', r'\frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}'), - ('P~', 'kg·m²/s'), - ('H~', 'kg m2/s'), - ('C~', 'kg*m**2/s'), - ): - ureg.default_format = spec - self.assertEqual('{0}'.format(x), result, - 'Failed for {0}, {1}'.format(spec, result)) + 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(object): + 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_(), "kilogram meter2/second") - self.assertEqual(x._repr_latex_(), r'$\frac{\mathrm{kilogram} \cdot ' - r'\mathrm{meter}^{2}}{\mathrm{second}}$') + 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_(), "kg m2/s") - self.assertEqual(x._repr_latex_(), - r'$\frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}$') + 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')) + 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)) + 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')) + 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')) + x = self.U_("m") + self.assertEqual(x ** 2, self.U_("m**2")) def test_unit_hash(self): - x = self.U_('m') + 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')) + 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, self.Q_(1, "m")) + self.assertNotEqual(x, self.Q_(2, "m")) - self.assertEqual(x, UnitsContainer({'meter': 1})) + self.assertEqual(x, UnitsContainer({"meter": 1})) - y = self.U_('cm/m') + 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) + 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')) + x = self.U_("m") + self.assertLess(x, self.U_("km")) + self.assertGreater(x, self.U_("mm")) - y = self.U_('m/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})) + 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) + 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) + 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') + 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')) + 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): @@ -173,10 +207,10 @@ def test_base(self): ureg = UnitRegistry(None) - ureg.define('meter = [length]') - self.assertRaises(ValueError, ureg.define, 'meter = [length]') + ureg.define("meter = [length]") + self.assertRaises(DefinitionSyntaxError, ureg.define, "meter = [length]") self.assertRaises(TypeError, ureg.define, list()) - x = ureg.define('degC = kelvin; offset: 273.15') + ureg.define("degC = kelvin; offset: 273.15") def test_define(self): ureg = UnitRegistry(None) @@ -186,122 +220,212 @@ def test_load(self): import pkg_resources from pint import unit - data = pkg_resources.resource_filename(unit.__name__, 'default_en.txt') + + 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') + self.assertRaises( + ValueError, UnitRegistry(None).load_definitions, "notexisting" + ) def test_default_format(self): ureg = UnitRegistry() q = ureg.meter - s1 = '{0}'.format(q) - s2 = '{0:~}'.format(q) - ureg.default_format = '~' - s3 = '{0}'.format(q) + 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, '~') + 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)) + 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.))) - self.assertEqual(self.ureg.parse_expression('second'), self.Q_(1, UnitsContainer(second=1.))) + 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.))) + 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.))) + 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.))) - #self.assertEqual(self.ureg._units['kilometer'], self.Q_(1000., UnitsContainer(meter=1.))) + 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.))) - self.assertEqual(self.ureg.parse_expression('kilometres'), self.Q_(1, UnitsContainer(kilometer=1.))) - - def test_str_errors(self): - self.assertEqual(str(UndefinedUnitError('rabbits')), "'{0!s}' is not defined in the unit registry".format('rabbits')) - self.assertEqual(str(UndefinedUnitError(('rabbits', 'horses'))), "'{0!s}' are not defined in the unit registry".format(('rabbits', 'horses'))) - self.assertEqual(u(str(DimensionalityError('meter', 'second'))), - "Cannot convert from 'meter' to 'second'") - self.assertEqual(str(DimensionalityError('meter', 'second', 'length', 'time')), - "Cannot convert from 'meter' (length) to 'second' (time)") + 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.))) - self.assertEqual(self.ureg.parse_expression('meter**2'), self.Q_(1, UnitsContainer(meter=2.))) - self.assertEqual(self.ureg.parse_expression('meter*second'), self.Q_(1, UnitsContainer(meter=1., second=1))) - self.assertEqual(self.ureg.parse_expression('meter/second'), self.Q_(1, UnitsContainer(meter=1., second=-1))) - self.assertEqual(self.ureg.parse_expression('meter/second**2'), self.Q_(1, UnitsContainer(meter=1., second=-2))) + 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., second=-2))) - self.assertEqual(self.ureg.parse_expression('m³/s³'), - self.Q_(1, UnitsContainer(meter=3., second=-3))) - self.assertEqual(self.ureg.parse_expression('meter² · second'), - self.Q_(1, UnitsContainer(meter=2., 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))) + 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.))) - self.assertEqual(self.ureg.parse_expression('meter*42'), self.Q_(42, UnitsContainer(meter=1.))) + 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)') + 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)) + 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])(?') + self.assertEqual(str(x), "dimensionless") + self.assertEqual(repr(x), "") x = UnitsContainer(meter=1, second=2) - self.assertEqual(str(x), 'meter * second ** 2') - self.assertEqual(repr(x), - "") + 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), - "") + self.assertEqual(str(x), "meter * second ** 2.5") + self.assertEqual(repr(x), "") def test_unitcontainer_bool(self): self.assertTrue(UnitsContainer(meter=1, second=2)) @@ -74,7 +79,7 @@ def test_unitcontainer_comp(self): x = UnitsContainer(meter=1, second=2) - y = 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) @@ -100,17 +105,17 @@ 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') + 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'}) + self.assertRaises(TypeError, UnitsContainer, {"1": "2"}) d = UnitsContainer() self.assertRaises(TypeError, d.__mul__, list()) self.assertRaises(TypeError, d.__pow__, list()) @@ -119,9 +124,8 @@ class TestToUnitsContainer(BaseTestCase): - def test_str_conversion(self): - self.assertEqual(to_units_container('m'), UnitsContainer(m=1)) + self.assertEqual(to_units_container("m"), UnitsContainer(m=1)) def test_uc_conversion(self): a = UnitsContainer(m=1) @@ -129,21 +133,25 @@ 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)) + self.assertEqual( + to_units_container(ureg.Quantity(1, UnitsContainer(m=1))), + UnitsContainer(m=1), + ) def test_unit_conversion(self): - from pint.unit import _Unit - self.assertEqual(to_units_container(_Unit(UnitsContainer(m=1))), - UnitsContainer(m=1)) + 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) @@ -152,37 +160,35 @@ self.assertEqual(x(), xp()) self.assertNotEqual(x(), y()) - self.assertEqual(ParserHelper.from_string(''), ParserHelper()) + 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.assertEqual(x(), "meter ** 2") self.assertNotEqual(y(), dict(meter=2)) - self.assertNotEqual(y(), '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., meter=2) - y = lambda: ParserHelper(2., meter=-2) - z = lambda: ParserHelper(2., meter=2) - - self.assertEqual(x() * 4., ParserHelper(4., meter=2)) - self.assertEqual(x() * y(), ParserHelper(2.)) - self.assertEqual(x() * 'second', ParserHelper(1., meter=2, second=1)) + 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., ParserHelper(0.25, meter=2)) - self.assertEqual(x() / 'second', ParserHelper(1., 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. / z(), ParserHelper(2., 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)) + 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)) @@ -190,88 +196,92 @@ 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') + 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) + 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): +class TestStringProcessor(BaseTestCase): def _test(self, bef, aft): - for pattern in ('{0}', '+{0}+'): + 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') + 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') + 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') + 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') + 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') + 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') + 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(list) - g[1] = set((2,)) - g[2] = set((3,)) + 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(list) - g[1] = set((2,)) - g[2] = set((3,)) + 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) @@ -279,10 +289,10 @@ p = find_shortest_path(g, 3, 1) self.assertIs(p, None) - g = collections.defaultdict(list) - g[1] = set((2,)) - g[2] = set((3, 1)) - g[3] = set((2,)) + 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) @@ -294,42 +304,62 @@ 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: '{0:.2f}'.format(x)), - '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') + 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)) diff -Nru python-pint-0.9/pint/unit.py python-pint-0.10.1/pint/unit.py --- python-pint-0.9/pint/unit.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/unit.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.unit ~~~~~~~~~ @@ -9,50 +8,45 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import - import copy +import locale import operator from numbers import Number -from .util import ( - PrettyIPython, UnitsContainer, SharedRegistryObject, fix_str_conversions) - -from .compat import string_types, NUMERIC_TYPES, long_type -from .formatting import siunitx_format_unit +from .compat import NUMERIC_TYPES, is_upcast_type from .definitions import UnitDefinition +from .formatting import siunitx_format_unit +from .util import PrettyIPython, SharedRegistryObject, UnitsContainer -@fix_str_conversions -class _Unit(PrettyIPython, SharedRegistryObject): - """Implements a class to describe a unit supporting math operations. - - :type units: UnitsContainer, str, Unit or Quantity. - - """ +class Unit(PrettyIPython, SharedRegistryObject): + """Implements a class to describe a unit supporting math operations.""" #: Default formatting string. - default_format = '' + default_format = "" def __reduce__(self): - from . import _build_unit - return _build_unit, (self._units, ) + # See notes in Quantity.__reduce__ + from . import _unpickle + + return _unpickle, (Unit, self._units) - def __new__(cls, units): - inst = object.__new__(cls) + def __init__(self, units): + super().__init__() if isinstance(units, (UnitsContainer, UnitDefinition)): - inst._units = units - elif isinstance(units, string_types): - inst._units = inst._REGISTRY.parse_units(units)._units - elif isinstance(units, _Unit): - inst._units = units._units + 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))) + raise TypeError( + "units must be of type str, Unit or " + "UnitsContainer; not {}.".format(type(units)) + ) - inst.__used = False - inst.__handling = None - return inst + self.__used = False + self.__handling = None @property def debug_used(self): @@ -64,64 +58,75 @@ return ret def __deepcopy__(self, memo): - ret = self.__class__(copy.deepcopy(self._units)) - ret.__used = self.__used - return ret + 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 - opts = '' - ustr = siunitx_format_unit(self) - ret = r'\si[%s]{%s}'%( opts, ustr ) - return ret + if "Lx" in spec: # the LaTeX siunitx code + return r"\si[]{%s}" % siunitx_format_unit(self) - - if '~' in spec: + 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('~', '') + 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' % (format(units, spec)) + 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): + def format_babel(self, spec="", **kwspec): spec = spec or self.default_format - if '~' in spec: + 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('~', '') + 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)) + return "%s" % (units.format_babel(spec, **kwspec)) @property def dimensionless(self): - """Return true if the Unit is dimensionless. - + """Return True if the Unit is dimensionless; False otherwise. """ return not bool(self.dimensionality) @property def dimensionality(self): - """Unit's dimensionality (e.g. {length: 1, time: -1}) - + """ + Returns + ------- + dict + Dimensionality of the Unit, e.g. ``{length: 1, time: -1}`` """ try: return self._dimensionality @@ -141,7 +146,7 @@ def __mul__(self, other): if self._check(other): if isinstance(other, self.__class__): - return self.__class__(self._units*other._units) + return self.__class__(self._units * other._units) else: qself = self._REGISTRY.Quantity(1.0, self._units) return qself * other @@ -156,20 +161,20 @@ def __truediv__(self, other): if self._check(other): if isinstance(other, self.__class__): - return self.__class__(self._units/other._units) + return self.__class__(self._units / other._units) else: qself = 1.0 * self return qself / other - return self._REGISTRY.Quantity(1/other, self._units) + 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) + return self._REGISTRY.Quantity(other, 1 / self._units) elif isinstance(other, UnitsContainer): - return self.__class__(other/self._units) + return self.__class__(other / self._units) else: return NotImplemented @@ -178,10 +183,10 @@ def __pow__(self, other): if isinstance(other, NUMERIC_TYPES): - return self.__class__(self._units**other) + return self.__class__(self._units ** other) else: - mess = 'Cannot power Unit by {}'.format(type(other)) + mess = "Cannot power Unit by {}".format(type(other)) raise TypeError(mess) def __hash__(self): @@ -210,7 +215,7 @@ if isinstance(other, NUMERIC_TYPES): return self_q.compare(other, op) - elif isinstance(other, (_Unit, UnitsContainer, dict)): + elif isinstance(other, (Unit, UnitsContainer, dict)): return self_q.compare(self._REGISTRY.Quantity(1, other), op) else: return NotImplemented @@ -223,9 +228,6 @@ def __int__(self): return int(self._REGISTRY.Quantity(1, self._units)) - def __long__(self): - return long_type(self._REGISTRY.Quantity(1, self._units)) - def __float__(self): return float(self._REGISTRY.Quantity(1, self._units)) @@ -234,18 +236,32 @@ __array_priority__ = 17 - def __array_prepare__(self, array, context=None): - return 1 + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + if method != "__call__": + # Only handle ufuncs as callables + return NotImplemented - def __array_wrap__(self, array, context=None): - uf, objs, huh = context + # 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 - if uf.__name__ in ('true_divide', 'divide', 'floor_divide'): - return self._REGISTRY.Quantity(array, 1/self._units) - elif uf.__name__ in ('multiply',): - return self._REGISTRY.Quantity(array, self._units) + # 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: - raise ValueError('Unsupproted operation for Unit') + return NotImplemented @property def systems(self): @@ -256,15 +272,23 @@ out.add(sname) return frozenset(out) - def from_(self, value, strict=True, name='value'): + def from_(self, value, strict=True, name="value"): """Converts a numerical value or quantity to this unit - :param value: a Quantity (or numerical value if strict=False) to convert - :param strict: boolean to indicate that only quanities are accepted - :param name: descriptive name to use if an exception occurs - :return: The converted value as this unit - :raises: - :class:`ValueError` if strict and one of the arguments is not a Quantity. + 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): @@ -275,23 +299,33 @@ else: return value * self - def m_from(self, value, strict=True, name='value'): + 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 - :param value: a Quantity (or numerical value if strict=False) to convert - :param strict: boolean to indicate that only quanities are accepted - :param name: descriptive name to use if an exception occurs - :return: The magnitude of the converted value - :raises: - :class:`ValueError` if strict and one of the arguments is not a Quantity. + 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 -def build_unit_class(registry): +_Unit = Unit + + +def build_unit_class(registry): class Unit(_Unit): - pass + _REGISTRY = registry - Unit._REGISTRY = registry return Unit diff -Nru python-pint-0.9/pint/util.py python-pint-0.10.1/pint/util.py --- python-pint-0.9/pint/util.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/util.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ pint.util ~~~~~~~~~ @@ -9,54 +8,72 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import division, unicode_literals, print_function, absolute_import - -from decimal import Decimal -import locale -import sys -import re +import logging +import math import operator -from numbers import Number +import re +from collections.abc import Mapping +from decimal import Decimal from fractions import Fraction - -try: - from collections.abc import Mapping -except ImportError: - from collections import Mapping - +from functools import lru_cache from logging import NullHandler +from numbers import Number +from token import NAME, NUMBER -import logging -from token import STRING, NAME, OP, NUMBER -from tokenize import untokenize - -from .compat import string_types, tokenizer, lru_cache, maketrans, NUMERIC_TYPES -from .formatting import format_unit,siunitx_format_unit -from .pint_eval import build_eval_tree +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))): +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)) + 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)] + 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] + ret += ["\t".join(fmtfun(f) for f in row) for row in matrix] - return '\n'.join(ret) + 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)] @@ -64,10 +81,20 @@ def column_echelon_form(matrix, ntype=Fraction, transpose_result=False): """Calculates the column echelon form using Gaussian elimination. - :param matrix: a 2D matrix as nested list. - :param ntype: the numerical type to use in the calculation. - :param transpose_result: indicates if the returned matrix should be transposed. - :return: column echelon form, transformed identity matrix, swapped rows + 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 @@ -89,8 +116,10 @@ new_M.append(r) M = new_M -# M = [[ntype(x) for x in row] for row in M] - I = [[ntype(1) if n == nc else ntype(0) for nc in range(rows)] for n in range(rows)] + # 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): @@ -118,8 +147,8 @@ 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])] + 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 @@ -129,9 +158,18 @@ def pi_theorem(quantities, registry=None): """Builds dimensionless quantities using the Buckingham π theorem - :param quantities: mapping between variable name and units - :type quantities: dict - :return: a list of dimensionless quantities expressed as dicts + 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 @@ -144,18 +182,20 @@ getdim = registry.get_dimensionality for name, value in quantities.items(): - if isinstance(value, string_types): + if isinstance(value, str): value = ParserHelper.from_string(value) if isinstance(value, dict): dims = getdim(UnitsContainer(value)) - elif not hasattr(value, 'dimensionality'): + 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)) + 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()) @@ -163,8 +203,10 @@ dimensions = list(dimensions) # Calculate dimensionless quantities - M = [[dimensionality[dimension] for name, dimensionality in quant] - for dimension in dimensions] + M = [ + [dimensionality[dimension] for name, dimensionality in quant] + for dimension in dimensions + ] M, identity, pivot = column_echelon_form(M, transpose_result=False) @@ -177,40 +219,54 @@ 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)) + 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. - :param dependencies: dependency dictionary. For each key, the value is - an iterable indicating its dependencies. - :return: list of sets, each containing keys of independents tasks dependent - only of the previous tasks in the list. - """ - d = dict((key, set(dependencies[key])) for key in dependencies) - r = [] - while d: + 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 = set(i for v in d.values() for i in v) - set(d.keys()) + 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 d.items() if not v) + 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 d.items()))) - r.append(t) + raise ValueError( + "Cyclic dependencies exist among these items: {}".format( + ", ".join(repr(x) for x in dependencies.items()) + ) + ) # and cleaned up - d = dict(((k, v - t) for k, v in d.items() if v)) - return r + 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 not start in graph: + if start not in graph: return None shortest = None for node in graph[start]: @@ -223,10 +279,10 @@ def find_connected_nodes(graph, start, visited=None): - if not start in graph: + if start not in graph: return None - visited = (visited or set()) + visited = visited or set() visited.add(start) for node in graph[start]: @@ -237,11 +293,13 @@ class udict(dict): - """ Custom dict implementing __missing__. + """Custom dict implementing __missing__.""" - """ def __missing__(self, key): - return 0. + return 0.0 + + def copy(self): + return udict(self) class UnitsContainer(Mapping): @@ -249,22 +307,30 @@ exponent and implements the corresponding operations. UnitsContainer is a read-only mapping. All operations (even in place ones) - return new instances. + + Parameters + ---------- + + Returns + ------- + type + """ - __slots__ = ('_d', '_hash') + + __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, string_types): - raise TypeError('key must be a str, not {}'.format(type(key))) + 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))) + raise TypeError("value must be a number, not {}".format(type(value))) if not isinstance(value, float): d[key] = float(value) - self._hash = hash(frozenset(self._d.items())) + self._hash = None def copy(self): return self.__copy__() @@ -275,24 +341,46 @@ if newval: new._d[key] = newval else: - del new._d[key] - + new._d.pop(key) + new._hash = None return new def remove(self, keys): - """ Create a new UnitsContainer purged from given keys. + """Create a new UnitsContainer purged from given keys. + + Parameters + ---------- + keys : + + + Returns + ------- """ - d = udict(self._d) - return UnitsContainer(((key, d[key]) for key in d if key not in keys)) + 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. + """Create a new UnitsContainer in which an entry has been renamed. + + Parameters + ---------- + oldkey : + + newkey : + + + Returns + ------- """ - d = udict(self._d) - d[newkey] = d.pop(oldkey) - return UnitsContainer(d) + new = self.copy() + new._d[newkey] = new._d.pop(oldkey) + new._hash = None + return new def __iter__(self): return iter(self._d) @@ -307,31 +395,35 @@ return key in self._d def __hash__(self): + if self._hash is None: + self._hash = hash(frozenset(self._d.items())) return self._hash - def __getstate__(self): - return {'_d': self._d, '_hash': self._hash} - - def __setstate__(self, state): - self._d = state['_d'] - self._hash = state['_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, string_types): + + elif isinstance(other, str): other = ParserHelper.from_string(other) other = other._d return dict.__eq__(self._d, other) def __str__(self): - return self.__format__('') + return self.__format__("") def __repr__(self): - tmp = '{%s}' % ', '.join(["'{}': {}".format(key, value) - for key, value in sorted(self._d.items())]) - return ''.format(tmp) + 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) @@ -340,73 +432,83 @@ return format_unit(self, spec, **kwspec) def __copy__(self): - return UnitsContainer(self._d) + # 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): - d = udict(self._d) if not isinstance(other, self.__class__): - err = 'Cannot multiply UnitsContainer by {}' + err = "Cannot multiply UnitsContainer by {}" raise TypeError(err.format(type(other))) + + new = self.copy() for key, value in other.items(): - d[key] += value - keys = [key for key, value in d.items() if value == 0] - for key in keys: - del d[key] + new._d[key] += value + if new._d[key] == 0: + del new._d[key] - return UnitsContainer(d) + new._hash = None + return new __rmul__ = __mul__ def __pow__(self, other): if not isinstance(other, NUMERIC_TYPES): - err = 'Cannot power UnitsContainer by {}' + err = "Cannot power UnitsContainer by {}" raise TypeError(err.format(type(other))) - d = udict(self._d) - for key, value in d.items(): - d[key] *= other - return UnitsContainer(d) + + 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 {}' + err = "Cannot divide UnitsContainer by {}" raise TypeError(err.format(type(other))) - d = udict(self._d) - + new = self.copy() for key, value in other.items(): - d[key] -= value - - keys = [key for key, value in d.items() if value == 0] - for key in keys: - del d[key] + new._d[key] -= value + if new._d[key] == 0: + del new._d[key] - return UnitsContainer(d) + new._hash = None + return new def __rtruediv__(self, other): if not isinstance(other, self.__class__) and other != 1: - err = 'Cannot divide {} by UnitsContainer' + err = "Cannot divide {} by UnitsContainer" raise TypeError(err.format(type(other))) - return self**-1 + return self ** -1 class ParserHelper(UnitsContainer): - """ The ParserHelper stores in place the product of variables and + """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) - return new instances. - WARNING : The hash value used does not take into account the scale - attribute so be careful if you use it as a dict key and then two unequal - object can have the same hash. + Parameters + ---------- + + 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', ) + __slots__ = ("scale",) def __init__(self, scale=1, *args, **kwargs): - super(ParserHelper, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.scale = scale @classmethod @@ -415,14 +517,18 @@ Equivalent to: ParserHelper({'word': 1}) + Parameters + ---------- + input_word : + + + Returns + ------- + """ return cls(1, [(input_word, 1)]) @classmethod - def from_string(cls, input_string): - return cls._from_string(input_string) - - @classmethod def eval_token(cls, token, use_decimal=False): token_type = token.type token_text = token.string @@ -436,20 +542,30 @@ elif token_type == NAME: return ParserHelper.from_word(token_text) else: - raise Exception('unknown token type') + raise Exception("unknown token type") @classmethod @lru_cache() - def _from_string(cls, input_string): + 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__') + if "[" in input_string: + input_string = input_string.replace("[", "__obra__").replace( + "]", "__cbra__" + ) reps = True else: reps = False @@ -460,35 +576,45 @@ if isinstance(ret, Number): return ParserHelper(ret) - if not reps: - return 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 ParserHelper(ret.scale, - dict((key.replace('__obra__', '[').replace('__cbra__', ']'), value) - for key, value in ret.items())) + return ret def __copy__(self): - return ParserHelper(scale=self.scale, **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' + mess = "Only scale 1.0 ParserHelper instance should be considered hashable" raise ValueError(mess) - return self._hash + return super().__hash__() def __eq__(self, other): - if isinstance(other, self.__class__): - return self.scale == other.scale and\ - super(ParserHelper, self).__eq__(other) - elif isinstance(other, string_types): + 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. and super(ParserHelper, self).__eq__(other) + return self.scale == 1.0 and super().__eq__(other) def operate(self, items, op=operator.iadd, cleanup=True): d = udict(self._d) @@ -503,17 +629,19 @@ 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) + 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) + 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, string_types): + if isinstance(other, str): new = self.add(other, 1) elif isinstance(other, Number): new = self.copy() @@ -531,10 +659,10 @@ d = self._d.copy() for key in self._d: d[key] *= other - return self.__class__(self.scale**other, d) + return self.__class__(self.scale ** other, d) def __truediv__(self, other): - if isinstance(other, string_types): + if isinstance(other, str): new = self.add(other, -1) elif isinstance(other, Number): new = self.copy() @@ -550,7 +678,7 @@ def __rtruediv__(self, other): new = self.__pow__(-1) - if isinstance(other, string_types): + if isinstance(other, str): new = new.add(other, 1) elif isinstance(other, Number): new.scale *= other @@ -563,25 +691,28 @@ #: 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 - ] +_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 = maketrans('⁰¹²³⁴⁵⁶⁷⁸⁹·⁻', '0123456789*-') +_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 ", "/") @@ -590,7 +721,7 @@ # Replace pretty format characters for pretty_exp in _pretty_exp_re.findall(input_string): - exp = '**' + pretty_exp.translate(_pretty_table) + exp = "**" + pretty_exp.translate(_pretty_table) input_string = input_string.replace(pretty_exp, exp) input_string = input_string.translate(_pretty_table) @@ -600,38 +731,62 @@ def _is_dim(name): - return name[0] == '[' and name[-1] == ']' + return name[0] == "[" and name[-1] == "]" -class SharedRegistryObject(object): - """Base class for object keeping a refrence to the registree. +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 + 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. - Return True is both use a registry and they use the same, False is - other don't use a registry and raise ValueError if other don't use the - same unit 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): + 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__)) + mess = "Cannot operate with {} and {} of different registries." + raise ValueError( + mess.format(self.__class__.__name__, other.__class__.__name__) + ) else: return False -class PrettyIPython(object): +class PrettyIPython: """Mixin to add pretty-printers for IPython""" def _repr_html_(self): @@ -654,7 +809,17 @@ def to_units_container(unit_like, registry=None): - """ Convert a unit compatible type to a UnitsContainer. + """Convert a unit compatible type to a UnitsContainer. + + Parameters + ---------- + unit_like : + + registry : + (Default value = None) + + Returns + ------- """ mro = type(unit_like).mro() @@ -662,7 +827,7 @@ return unit_like elif SharedRegistryObject in mro: return unit_like._units - elif string_types in mro: + elif str in mro: if registry: return registry._parse_units(unit_like) else: @@ -672,31 +837,50 @@ def infer_base_unit(q): - """Return UnitsContainer of q with all prefixes stripped.""" + """ + + Parameters + ---------- + q : + + + Returns + ------- + type + + + """ d = udict() - parse = q._REGISTRY.parse_unit_name for unit_name, power in q._units.items(): - completely_parsed_unit = list(parse(unit_name))[-1] - - _, base_unit, __ = completely_parsed_unit + candidates = q._REGISTRY.parse_unit_name(unit_name) + assert len(candidates) == 1 + _, base_unit, _ = candidates[0] d[base_unit] += power - return UnitsContainer(dict((k, v) for k, v in d.items() if v != 0)) # remove values that resulted in a power of 0 + # remove values that resulted in a power of 0 + return UnitsContainer({k: v for k, v in d.items() if v != 0}) -def fix_str_conversions(cls): - """Enable python2/3 compatible behaviour for __str__.""" - def __bytes__(self): - return self.__unicode__().encode(locale.getpreferredencoding()) - cls.__unicode__ = __unicode__ = cls.__str__ - cls.__bytes__ = __bytes__ - if sys.version_info[0] == 2: - cls.__str__ = __bytes__ - else: - cls.__str__ = __unicode__ - return cls +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(object): + +class SourceIterator: """Iterator to facilitate reading the definition files. Accepts any sequence (like a list of lines, a file or another SourceIterator) @@ -725,10 +909,10 @@ return self def __next__(self): - line = '' - while not line or line.startswith('#'): + line = "" + while not line or line.startswith("#"): lineno, line = next(self.internal) - line = line.split('#', 1)[0].strip() + line = line.split("#", 1)[0].strip() self.last = lineno, line return lineno, line @@ -736,8 +920,7 @@ next = __next__ def block_iter(self): - """Iterate block including header. - """ + """Iterate block including header.""" return BlockIterator(self) @@ -759,11 +942,52 @@ return self.last lineno, line = SourceIterator.__next__(self) - if line.startswith('@end'): + if line.startswith("@end"): raise StopIteration - elif line.startswith('@'): - raise DefinitionSyntaxError('cannot nest @ directives', lineno=lineno) + 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 diff -Nru python-pint-0.9/pint/xtranslated.txt python-pint-0.10.1/pint/xtranslated.txt --- python-pint-0.9/pint/xtranslated.txt 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/pint/xtranslated.txt 2020-01-08 05:47:17.000000000 +0000 @@ -1,22 +1,14 @@ # a few unit definitions added to use the translations by unicode cldr -dietary_calorie = 1000 * calorie = Calorie +dietary_calorie = 1000 * calorie = Cal = Calorie metric_cup = liter / 4 -mps = meter / second -square_inch = inch ** 2 = sq_in -square_mile = mile ** 2 = sq_mile square_meter = kilometer ** 2 = sq_m square_kilometer = kilometer ** 2 = sq_km mile_scandinavian = 10000 * meter -century = 100 * year cubic_mile = 1 * mile ** 3 = cu_mile = cubic_miles -cubic_yard = 1 * yard ** 3 = cu_yd = cubic_yards -cubic_foot = 1 * foot ** 3 = cu_ft = cubic_feet -cubic_inch = 1 * inch ** 3 = cu_in = cubic_inches cubic_meter = 1 * meter ** 3 = cu_m cubic_kilometer = 1 * kilometer ** 3 = cu_km -karat = [purity] = Karat [consumption] = [volume] / [length] liter_per_kilometer = liter / kilometer diff -Nru python-pint-0.9/pull_request_template.md python-pint-0.10.1/pull_request_template.md --- python-pint-0.9/pull_request_template.md 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/pull_request_template.md 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,5 @@ +- [ ] 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 diff -Nru python-pint-0.9/README python-pint-0.10.1/README --- python-pint-0.9/README 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/README 1970-01-01 00:00:00.000000000 +0000 @@ -1,88 +0,0 @@ -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 2.7 and 3.3+ -with no other dependency. It is licensed under BSD. - -It is extremely easy and natural to use: - -.. code-block:: python - - >>> import pint - >>> ureg = pint.UnitRegistry() - >>> 3 * ureg.meter + 4 * ureg.cm - - -and you can make good use of numpy if you want: - -.. code-block:: python - - >>> import numpy as np - >>> [3, 4] * ureg.meter + [4, 3] * ureg.cm - - >>> np.sum(_) - - - -Quick Installation ------------------- - -To install Pint, simply: - -.. code-block:: bash - - $ pip install pint - -or utilizing conda with, the conda-forge channel: - -.. code-block:: bash - - $ conda install -c conda-forge pint - -and then simply enjoy it! - - -Documentation -------------- - -Full documentation is available at http://pint.readthedocs.org/ - - -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 programed -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 simple and - easy to edit text file. 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 latex and pretty - formatting. - -- Small codebase: small and easy to maintain with a flat hierarchy. - -- Dependency free: it depends only on Python and its standard library. - -- Python 2 and 3: A single codebase that runs unchanged in Python 2.7 and Python 3.3+. - -- Advanced NumPy support: While NumPy is not a requirement for Pint, - when available ndarray methods and ufuncs can be used in Quantity objects. diff -Nru python-pint-0.9/README.rst python-pint-0.10.1/README.rst --- python-pint-0.9/README.rst 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/README.rst 2020-01-08 05:47:17.000000000 +0000 @@ -40,8 +40,9 @@ 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 2.7 and 3.3+ -with no other dependency. It is licensed under BSD. +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: @@ -86,32 +87,66 @@ 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 programed -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 simple and - easy to edit text file. 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 latex and pretty - formatting. - -- Small codebase: small and easy to maintain with a flat hierarchy. - -- Dependency free: it depends only on Python and its standard library. - -- Python 2 and 3: A single codebase that runs unchanged in Python 2.7 and Python 3.3+. +quantities, no one was really fitting my needs. Like most developers, I +programmed Pint to scratch my own itches. -- Advanced NumPy support: While NumPy is not a requirement for Pint, - when available ndarray methods and ufuncs can be used in Quantity objects. +**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 diff -Nru python-pint-0.9/readthedocs.yml python-pint-0.10.1/readthedocs.yml --- python-pint-0.9/readthedocs.yml 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/readthedocs.yml 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,12 @@ +version: 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: . diff -Nru python-pint-0.9/requirements_docs.txt python-pint-0.10.1/requirements_docs.txt --- python-pint-0.9/requirements_docs.txt 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/requirements_docs.txt 2020-01-08 05:47:17.000000000 +0000 @@ -1,2 +1,5 @@ -matplotlib>=2 +ipython +matplotlib +nbsphinx numpy +pytest diff -Nru python-pint-0.9/setup.cfg python-pint-0.10.1/setup.cfg --- python-pint-0.9/setup.cfg 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/setup.cfg 2020-01-08 05:47:17.000000000 +0000 @@ -1,8 +1,77 @@ +[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 - tox.ini [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.py \ No newline at end of file diff -Nru python-pint-0.9/setup.py python-pint-0.10.1/setup.py --- python-pint-0.9/setup.py 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/setup.py 2020-01-08 05:47:17.000000000 +0000 @@ -1,69 +1,5 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 +from setuptools import setup -import sys - -try: - reload(sys).setdefaultencoding("UTF-8") -except: - pass - -try: - from setuptools import setup -except ImportError: - print('Please install or upgrade setuptools or pip to continue') - sys.exit(1) - -import codecs - - -def read(filename): - return codecs.open(filename, encoding='utf-8').read() - - -long_description = '\n\n'.join([read('README'), - read('AUTHORS'), - read('CHANGES')]) - -__doc__ = long_description - -setup( - name='Pint', - version='0.9', - description='Physical quantities module', - long_description=long_description, - keywords='physical quantities unit conversion science', - author='Hernan E. Grecco', - author_email='hernan.grecco@gmail.com', - url='https://github.com/hgrecco/pint', - test_suite='pint.testsuite.testsuite', - zip_safe=True, - packages=['pint'], - package_data={ - 'pint': ['default_en.txt', - 'constants_en.txt'] - }, - include_package_data=True, - license='BSD', - 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 :: 2.7', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - ], - extras_require={ - ':python_version == "2.7"': [ - 'funcsigs', - ], - }, - ) +if __name__ == "__main__": + setup(use_scm_version=True) diff -Nru python-pint-0.9/tox.ini python-pint-0.10.1/tox.ini --- python-pint-0.9/tox.ini 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/tox.ini 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -[tox] -envlist = py27, py33, py34, numpy27, numpy33, numpy34, - py27u,py33u,py34u,numpy27u,numpy33u,numpy34u - -[testenv] -deps = coverage -commands = coverage run -p --source=pint setup.py test - -setenv= - COVERAGE_FILE=.coverage.{envname} - - -[testenv:numpy27] -basepython=python2.7 -deps = numpy - coverage - -[testenv:numpy33] -basepython=python3.3 -deps = numpy - coverage - -[testenv:numpy34] -basepython=python3.4 -deps = numpy - coverage - -[testenv:py27u] -basepython=python2.7 -deps = uncertainties - coverage - -[testenv:py33u] -basepython=python3.3 -deps = uncertainties - coverage - -[testenv:py34u] -basepython=python3.4 -deps = uncertainties - coverage - -[testenv:numpy27u] -basepython=python2.7 -deps = numpy - uncertainties - coverage - -[testenv:numpy33u] -basepython=python3.3 -deps = numpy - uncertainties - coverage - -[testenv:numpy34u] -basepython=python3.4 -deps = numpy - uncertainties - coverage diff -Nru python-pint-0.9/.travis-exclude.yml python-pint-0.10.1/.travis-exclude.yml --- python-pint-0.9/.travis-exclude.yml 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/.travis-exclude.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -# This file is used to autogenerate the travis exclude matrix. -rules: - "3.5": "NUMPY_VERSION>=1.9.2" - "3.4": "NUMPY_VERSION>=1.8.1" - "3.3": "NUMPY_VERSION>=1.7.0,<=1.9.2" - "2.7": "NUMPY_VERSION>1.5.1" - diff -Nru python-pint-0.9/.travis-full.yml python-pint-0.10.1/.travis-full.yml --- python-pint-0.9/.travis-full.yml 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/.travis-full.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,106 +0,0 @@ -language: python - -python: - - "2.7" - - "3.3" - - "3.4" - - "3.5" -env: - - UNCERTAINTIES="N" NUMPY_VERSION=0 - - UNCERTAINTIES="N" NUMPY_VERSION=1.6.2 - - UNCERTAINTIES="N" NUMPY_VERSION=1.7.1 - - UNCERTAINTIES="N" NUMPY_VERSION=1.8.2 - - UNCERTAINTIES="N" NUMPY_VERSION=1.9.3 - - UNCERTAINTIES="N" NUMPY_VERSION=1.10.4 - - UNCERTAINTIES="Y" NUMPY_VERSION=0 - - UNCERTAINTIES="Y" NUMPY_VERSION=1.6.2 - - UNCERTAINTIES="Y" NUMPY_VERSION=1.7.1 - - UNCERTAINTIES="Y" NUMPY_VERSION=1.8.2 - - UNCERTAINTIES="Y" NUMPY_VERSION=1.9.3 - - UNCERTAINTIES="Y" NUMPY_VERSION=1.10.4 - -branches: - only: - - master - - develop - -before_install: - - sudo apt-get update - - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; - else - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - fi - - 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 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 - - sudo rm -rf /dev/shm - - sudo ln -s /run/shm /dev/shm - - - export ENV_NAME=travis - -install: - - conda create -c mwcraig --yes -n $ENV_NAME python=$TRAVIS_PYTHON_VERSION pip - - source activate $ENV_NAME - - if [ $UNCERTAINTIES == 'Y' ]; then pip install 'uncertainties==2.4.7.1'; fi - - if [ $NUMPY_VERSION != '0' ]; then conda install -c mwcraig --yes numpy==$NUMPY_VERSION; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.5' && $NUMPY_VERSION == 1.10.4 && $UNCERTAINTIES == "Y" ]]; then pip install serialize pyyaml; fi - - pip install coveralls - -script: - - python -bb -m coverage run -p --source=pint --omit="*test*","*compat*" setup.py test - - coverage combine - - coverage report -m - -after_success: - - coveralls --verbose - -matrix: - exclude: -# Do not edit after this line - - python: "3.5" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.8.2 - - python: "3.5" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.6.2 - - python: "3.5" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.8.2 - - python: "3.5" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.6.2 - - python: "3.5" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.7.1 - - python: "3.5" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.7.1 - - python: "3.4" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.6.2 - - python: "3.4" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.6.2 - - python: "3.4" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.7.1 - - python: "3.4" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.7.1 - - python: "3.3" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.6.2 - - python: "3.3" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.9.3 - - python: "3.3" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.10.4 - - python: "3.3" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.6.2 - - python: "3.3" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.9.3 - - python: "3.3" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.10.4 - - python: "2.6" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.9.3 - - python: "2.6" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.10.4 - - python: "2.6" - env: UNCERTAINTIES="Y" NUMPY_VERSION=1.9.3 - - python: "2.6" - env: UNCERTAINTIES="N" NUMPY_VERSION=1.10.4 diff -Nru python-pint-0.9/.travis.yml python-pint-0.10.1/.travis.yml --- python-pint-0.9/.travis.yml 2019-01-12 23:30:17.000000000 +0000 +++ python-pint-0.10.1/.travis.yml 2020-01-08 05:47:17.000000000 +0000 @@ -7,62 +7,74 @@ - trying.tmp env: - # Should pandas tests be removed or replaced wih import checks? - #- UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.14 PANDAS=1 - - UNCERTAINTIES="N" PYTHON="3.3" NUMPY_VERSION=1.9.2 PANDAS=0 - - UNCERTAINTIES="N" PYTHON="3.4" NUMPY_VERSION=1.11.2 PANDAS=0 - - UNCERTAINTIES="N" PYTHON="3.5" NUMPY_VERSION=1.11.2 PANDAS=0 - - UNCERTAINTIES="Y" PYTHON="3.5" NUMPY_VERSION=1.11.2 PANDAS=0 - - UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.11.2 PANDAS=0 - - UNCERTAINTIES="N" PYTHON="2.7" NUMPY_VERSION=0 PANDAS=0 - - UNCERTAINTIES="N" PYTHON="3.5" NUMPY_VERSION=0 PANDAS=0 - # Test with the latest numpy version - - UNCERTAINTIES="N" PYTHON="2.7" NUMPY_VERSION=1.14 PANDAS=0 - - UNCERTAINTIES="N" PYTHON="3.4" NUMPY_VERSION=1.14 PANDAS=0 - - UNCERTAINTIES="N" PYTHON="3.5" NUMPY_VERSION=1.14 PANDAS=0 - - UNCERTAINTIES="Y" PYTHON="3.5" NUMPY_VERSION=1.14 PANDAS=0 - - UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.14 PANDAS=0 + # 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 - - if [[ "$PYTHON" == "2.7" ]]; then - wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; - else - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - fi + - 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 - - sudo rm -rf /dev/shm - - sudo ln -s /run/shm /dev/shm - - - export ENV_NAME=travis + # 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 --yes -n $ENV_NAME python=$PYTHON pip - - source activate $ENV_NAME - - if [ $UNCERTAINTIES == 'Y' ]; then pip install 'uncertainties==2.4.7.1'; fi - - if [ $NUMPY_VERSION != '0' ]; then conda install --yes numpy==$NUMPY_VERSION; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.5' && $NUMPY_VERSION == 1.11.2 && $UNCERTAINTIES == "Y" ]]; then pip install babel serialize pyyaml; fi + - 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 - - pip install coveralls + # - 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 + # - 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' ]]; then python -bb -m coverage run -p --source=pint --omit="*test*","*compat*","*pandas*" setup.py test; fi - - coverage combine - - coverage report -m + # - 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 diff -Nru python-pint-0.9/version.py python-pint-0.10.1/version.py --- python-pint-0.9/version.py 1970-01-01 00:00:00.000000000 +0000 +++ python-pint-0.10.1/version.py 2020-01-08 05:47:17.000000000 +0000 @@ -0,0 +1,3 @@ +# This is just for zest.releaser. Do not touch + +__version__ = '0.10.1'