summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClark Willison <clarkgwillison@gmail.com>2020-07-15 12:00:47 -0700
committerClark Willison <clarkgwillison@gmail.com>2020-07-15 12:00:47 -0700
commit0cd5d5c1f45d652a693e1a221d0a125eece93c58 (patch)
tree6e6a1ed81bd154c67a63334d22f49700eada61b7
parent4b7171b2f899e05ca32a85eaafcd256173de303c (diff)
parent03be0544b749ee55fbd1253d18e2a84151dce716 (diff)
downloadpint-0cd5d5c1f45d652a693e1a221d0a125eece93c58.tar.gz
Merge branch 'master' into log-units WIP branch
fix merge conflicts with upstream master, and bring this branch up-to-date with present work
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml6
-rw-r--r--AUTHORS1
-rw-r--r--CHANGES80
-rw-r--r--MANIFEST.in2
-rw-r--r--README.rst10
-rw-r--r--docs/conf.py26
-rw-r--r--docs/contexts.rst29
-rw-r--r--docs/contributing.rst32
-rw-r--r--docs/defining-quantities.rst142
-rw-r--r--docs/defining.rst18
-rw-r--r--docs/developers_reference.rst61
-rw-r--r--docs/getting.rst28
-rw-r--r--docs/index.rst23
-rw-r--r--docs/measurement.rst31
-rw-r--r--docs/nonmult.rst62
-rw-r--r--docs/numpy.ipynb470
-rw-r--r--docs/pint-convert.rst91
-rw-r--r--docs/pint-pandas.ipynb1182
-rw-r--r--docs/pitheorem.rst2
-rw-r--r--docs/serialization.rst16
-rw-r--r--docs/systems.rst42
-rw-r--r--docs/tutorial.rst333
-rw-r--r--docs/wrapping.rst48
-rw-r--r--pint/__init__.py18
-rw-r--r--pint/babel_names.py2
-rw-r--r--pint/compat.py124
-rw-r--r--pint/context.py36
-rw-r--r--pint/default_en.txt3
-rw-r--r--pint/definitions.py96
-rw-r--r--pint/formatting.py7
-rw-r--r--pint/measurement.py37
-rw-r--r--pint/numpy_func.py136
-rwxr-xr-xpint/pint-convert121
-rw-r--r--pint/quantity.py377
-rw-r--r--pint/registry.py190
-rw-r--r--pint/systems.py13
-rw-r--r--pint/testsuite/__init__.py16
-rw-r--r--pint/testsuite/helpers.py16
-rw-r--r--pint/testsuite/test_application_registry.py99
-rw-r--r--pint/testsuite/test_babel.py37
-rw-r--r--pint/testsuite/test_compat.py104
-rw-r--r--pint/testsuite/test_compat_downcast.py9
-rw-r--r--pint/testsuite/test_contexts.py32
-rw-r--r--pint/testsuite/test_dask.py196
-rw-r--r--pint/testsuite/test_errors.py40
-rw-r--r--pint/testsuite/test_issues.py154
-rw-r--r--pint/testsuite/test_measurement.py31
-rw-r--r--pint/testsuite/test_non_int.py1077
-rw-r--r--pint/testsuite/test_numpy.py198
-rw-r--r--pint/testsuite/test_numpy_func.py3
-rw-r--r--pint/testsuite/test_quantity.py253
-rw-r--r--pint/testsuite/test_unit.py17
-rw-r--r--pint/testsuite/test_util.py14
-rw-r--r--pint/unit.py35
-rw-r--r--pint/util.py106
-rw-r--r--requirements_docs.txt10
-rw-r--r--setup.cfg8
-rw-r--r--version.py2
59 files changed, 3775 insertions, 2578 deletions
diff --git a/.gitignore b/.gitignore
index d3e751f..d2fddea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ dist/
MANIFEST
*pytest_cache*
.eggs
+.mypy_cache
# WebDAV file system cache files
.DAV/
diff --git a/.travis.yml b/.travis.yml
index ec164c3..7671c79 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,7 +17,7 @@ env:
- 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.7 ipython matplotlib nbsphinx numpy pandas jupyter_client ipykernel python-graphviz graphviz xarray sparse dask[complete] 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"
@@ -31,7 +31,7 @@ env:
- PKGS="python=3.6 numpy uncertainties"
- PKGS="python=3.7 numpy uncertainties"
- PKGS="python=3.8 numpy uncertainties"
- - PKGS="python xarray netCDF4"
+ - PKGS="python=3.8 numpy uncertainties sparse xarray netCDF4"
# TODO: pandas tests
# - PKGS="python=3.7 numpy pandas uncertainties pandas"
@@ -61,6 +61,7 @@ install:
- 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
+ - if [[ $DOCS == 1 ]]; then pip install pint-pandas; fi
# this is superslow but suck it up until updates to pandas are made
# - if [[ $PANDAS == '1' ]]; then pip install numpy cython pytest pytest-cov nbval; pip install git+https://github.com/pandas-dev/pandas.git@bdb7a1603f1e0948ca0cab011987f616e7296167; python -c 'import pandas; print(pandas.__version__)'; fi
- conda list
@@ -74,6 +75,7 @@ script:
- 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 [[ $DOCS == 1 ]]; then PYTHONPATH=$PWD sphinx-build -a -j auto -b doctest -d build/doctrees docs build/doctest; fi
- if [[ $LINT == 0 && $DOCS == 0 ]]; then coverage report -m; fi
after_success:
diff --git a/AUTHORS b/AUTHORS
index db3229e..2f51bae 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -43,6 +43,7 @@ Other contributors, listed alphabetically, are:
* Sundar Raman <cybertoast@gmail.com>
* Tiago Coutinho <coutinho@esrf.fr>
* Thomas Kluyver <takowl@gmail.com>
+* Tom Nicholas <tomnicholas1@gmail.com>
* Tom Ritchford <tom@swirly.com>
* Virgil Dupras <virgil.dupras@savoirfairelinux.com>
* Zebedee Nicholls <zebedee.nicholls@climate-energy-college.org>
diff --git a/CHANGES b/CHANGES
index ffb9242..6b07aa2 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,13 +1,84 @@
Pint Changelog
==============
-0.11 (unreleased)
+0.15 (unreleased)
-----------------
+- Implement Dask collection interface to support Pint Quantity wrapped Dask arrays.
+- Started automatically testing examples in the documentation
+
+0.14 (2020-07-01)
+-----------------
+
+- Changes required to support Pint-Pandas 0.1.
+
+
+0.13 (2020-06-17)
+-----------------
+- Reinstated support for pickle protocol 0 and 1, which is required by pytables
+ (Issue #1036, Thanks Guido Imperiale)
+- Fixed bug with multiplication of Quantity by dict (Issue #1032)
+- Bare zeros and NaNs (not wrapped by Quantity) are now gracefully accepted by all numpy
+ operations; e.g. np.stack([Quantity([1, 2], "m"), [0, np.nan]) is now valid, whereas
+ np.stack([Quantity([1, 2], "m"), [3, 4]) will continue raising DimensionalityError.
+ (Issue #1050, Thanks Guido Imperiale)
+- NaN is now treated the same as zero in addition, subtraction, equality, and
+ disequality (Issue #1051, Thanks Guido Imperiale)
+- Fixed issue where quantities with a very large magnitude would throw an IndexError
+ when using to_compact()
+- Fixed crash when a Unit with prefix is declared for the first time while a Context
+ containing unit redefinitions is active
+ (Issues #1062 and #1097, Thanks Guido Imperiale)
+- New implementation of 'Lx' String Format Type Option
+ The old implementation treated 'Lx' as 'S' as produced by 'uncertainties'
+ package, but that is not fully compatible with SIunitx. The new code protects
+ SIunitx by fixing what unceratinties produces.
+ (Issue #814)
+- Added link to budding `pint-xarray` interface library to the docs, next to
+ the link to pint-pandas. (Thanks Tom Nicholas.)
+- Removed outdated `_dir` attribute of `UnitsRegistry`, and added `__iter__`
+ method so that now `list(ureg)` returns a list of all units in registry.
+ (Issue #1072, Thanks Tom Nicholas)
+- Replace pkg_resources.version to importlib.metadata.version. (Issue #1083)
+- Fix typo in docs for wraps example with optional arguments. (Issue #1088)
+- Add momentum as a dimension
+- Fixed a bug where unit exponents were only partially superscripted in HTML format
+- Multiple contexts containing the same redefinition can now be stacked
+ (Issue #1108, Thanks Guido Imperiale)
+- Fixed crash when some specific combinations of contexts were enabled
+ (Issue #1112, Thanks Guido Imperiale)
+- Added support for checking prefixed units using `in` keyword (Issue #1086)
+- Updated many examples in the documentation to reflect Pint's current behavior
+
+
+0.12 (2020-05-29)
+-----------------
+
+- Add full support for Decimal and Fraction at the registry level.
+ **BREAKING CHANGE**:
+ `use_decimal` is deprecated. Use `non_int_type=Decimal` when instantiating
+ the registry.
+- Fixed bug where numpy.pad didn't work without specifying constant_values or
+ end_values (Issue #1026)
+
+
+0.11 (2020-02-19)
+-----------------
+
+- Added pint-convert script.
+- Remove `default_en_0.6.txt`.
+- Make `__str__` and `__format__` locale configurable.
+ (Issue #984)
+- Quantities wrapping NumPy arrays will no longer warning for the changed
+ array function behavior introduced in 0.10.
+ (Issue #1029, Thanks Jon Thielen)
+- **BREAKING CHANGE**:
+ The array protocol fallback deprecated in version 0.10 has been removed.
+ (Issue #1029, Thanks Jon Thielen)
- Now we use `pyproject.toml` for providing `setuptools_scm` settings
- Remove `default_en_0.6.txt`
- Reorganize long_description.
-- Moved Pi to defintions files.
+- Moved Pi to definitions files.
- Use ints (not floats) a defaults at many points in the codebase as in Python 3
the true division is the default one.
- **BREAKING CHANGE**:
@@ -22,6 +93,7 @@ Pint Changelog
- Implements Logarithmic Units like dBm, dB or decade
(Issue #71, Thanks Dima Pustakhod, Giorgio Signorello, Jonathan Wheeler)
+
0.10.1 (2020-01-07)
-------------------
@@ -32,6 +104,7 @@ Pint Changelog
(Issue #881, Thanks Guido Imperiale)
- Fixed __array__ signature to match numpy docs (Issue #974, Thanks Ryan May)
+
0.10 (2020-01-05)
-----------------
@@ -237,7 +310,7 @@ Pint Changelog
- Added several new units and Systems
(Issues #749, #737, )
- Started experimental pandas support
- (Issue #746 and others. Thanks andrewgsavage, znicholls and others)
+ (Issue #746 and others. Thanks andrewgsavage, znicholls and others)
- wraps and checks now supports kwargs and defaults.
(Issue #660, thanks jondoesntgit)
@@ -309,6 +382,7 @@ Pint Changelog
- Allow changing shape for Quantities with numpy arrays.
(Issue #344, thanks tecki)
+
0.7.2 (2016-03-02)
------------------
- Fixed backward incompatibility problem when parsing dimensionless units.
diff --git a/MANIFEST.in b/MANIFEST.in
index 0cf6865..cd897ad 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
-include AUTHORS CHANGES LICENSE README.rst BADGES.rst version.txt readthedocs.yml
+include AUTHORS CHANGES LICENSE README.rst BADGES.rst version.txt readthedocs.yml .coveragerc
recursive-include pint *
recursive-include docs *
recursive-include bench *
diff --git a/README.rst b/README.rst
index f90ea45..97cda30 100644
--- a/README.rst
+++ b/README.rst
@@ -87,12 +87,20 @@ Documentation
Full documentation is available at http://pint.readthedocs.org/
+
GUI Website
-----------
This Website_ wraps Pint's "dimensional analysis" methods to provide a GUI.
+Command-line converter
+----------------------
+
+A command-line script `pint-convert` provides a quick way to convert between
+units or get conversion factors.
+
+
Design principles
-----------------
@@ -158,4 +166,4 @@ see CHANGES_
.. _`Pandas Extension Types`: https://pandas.pydata.org/pandas-docs/stable/extending.html#extension-types
.. _`pint-pandas Jupyter notebook`: https://github.com/hgrecco/pint-pandas/blob/master/notebooks/pandas_support.ipynb
.. _`AUTHORS`: https://github.com/hgrecco/pint/blob/master/AUTHORS
-.. _`CHANGES`: https://github.com/hgrecco/pint/blob/master/CHANGES \ No newline at end of file
+.. _`CHANGES`: https://github.com/hgrecco/pint/blob/master/CHANGES
diff --git a/docs/conf.py b/docs/conf.py
index 1b772ba..67e156c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,7 +13,11 @@
import datetime
-import pkg_resources
+try:
+ from importlib.metadata import version
+except ImportError:
+ # Backport for Python < 3.8
+ from importlib_metadata import version
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -29,6 +33,7 @@ import pkg_resources
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
"sphinx.ext.autodoc",
+ "sphinx.ext.autosectionlabel",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.coverage",
@@ -60,7 +65,7 @@ author = "Hernan E. Grecco"
# built documents.
try: # pragma: no cover
- version = pkg_resources.get_distribution(project).version
+ version = version(project)
except Exception: # pragma: no cover
# we seem to have a local copy not installed without setuptools
# so the reported version will be unknown
@@ -317,3 +322,20 @@ epub_copyright = copyright
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"http://docs.python.org/": None}
+
+# -- Doctest configuration -----------------------------------------------------
+
+# fill a dictionary with package names that may be missing
+# this is checked by :skipif: clauses in certain doctests that rely
+# on optional dependencies
+doctest_global_setup = """
+from importlib.util import find_spec
+
+not_installed = {
+ pkg_name: find_spec(pkg_name) is None
+ for pkg_name in [
+ 'uncertainties',
+ 'serialize',
+ ]
+}
+"""
diff --git a/docs/contexts.rst b/docs/contexts.rst
index 27cb6ab..4077503 100644
--- a/docs/contexts.rst
+++ b/docs/contexts.rst
@@ -1,4 +1,3 @@
-.. _contexts:
Contexts
========
@@ -55,11 +54,15 @@ Contexts can be also enabled for blocks of code using the `with` statement:
<Quantity(5.99584916e+14, 'hertz')>
If you need a particular context in all your code, you can enable it for all
-operations with the registry::
+operations with the registry
+
+.. doctest::
>>> ureg.enable_contexts('sp')
-To disable the context, just call::
+To disable the context, just call
+
+.. doctest::
>>> ureg.disable_contexts()
@@ -69,17 +72,23 @@ Enabling multiple contexts
You can enable multiple contexts:
+.. doctest::
+
>>> q.to('Hz', 'sp', 'boltzmann')
<Quantity(5.99584916e+14, 'hertz')>
This works also using the `with` statement:
+.. doctest::
+
>>> with ureg.context('sp', 'boltzmann'):
... q.to('Hz')
<Quantity(5.99584916e+14, 'hertz')>
or in the registry:
+.. doctest::
+
>>> ureg.enable_contexts('sp', 'boltzmann')
>>> q.to('Hz')
<Quantity(5.99584916e+14, 'hertz')>
@@ -88,6 +97,8 @@ If a conversion rule between two dimensions appears in more than one context,
the one in the last context has precedence. This is easy to remember if you
think that the previous syntax is equivalent to nest contexts:
+.. doctest::
+
>>> with ureg.context('sp'):
... with ureg.context('boltzmann') :
... q.to('Hz')
@@ -106,7 +117,7 @@ calculate, for example, the wavelength in water of a laser which on air is 530 n
>>> wl = 530. * ureg.nm
>>> f = wl.to('Hz', 'sp')
>>> f.to('nm', 'sp', n=1.33)
- <Quantity(398.496240602, 'nanometer')>
+ <Quantity(398.4962..., 'nanometer')>
Contexts can also accept Pint Quantity objects as parameters. For example, the
'chemistry' context accepts the molecular weight of a substance (as a Quantity
@@ -135,7 +146,7 @@ context and the parameters that you wish to set.
... def f(wl):
... return wl.to('Hz').magnitude
>>> f(wl)
- 398.496240602
+ 425297855014895.6
This decorator can be combined with **wraps** or **check** decorators described in
@@ -194,14 +205,16 @@ functions. For example:
... lambda ureg, x: x * ureg.speed_of_light)
>>> ureg.add_context(c)
>>> ureg("1 s").to("km", "ab")
- 299792.458 kilometer
+ <Quantity(299792.458, 'kilometer')>
It is also possible to create anonymous contexts without invoking add_context:
+.. doctest::
+
>>> c = pint.Context()
- ...
+ >>> c.add_transformation('[time]', '[length]', lambda ureg, x: x * ureg.speed_of_light)
>>> ureg("1 s").to("km", c)
- 299792.458 kilometer
+ <Quantity(299792.458, 'kilometer')>
Using contexts for unit redefinition
------------------------------------
diff --git a/docs/contributing.rst b/docs/contributing.rst
index a7c7e41..d5c8703 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -32,6 +32,38 @@ Pint uses `bors-ng` as a merge bot and therefore every PR is tested before mergi
In any case, feel free to use the `issue tracker`_ to discuss ideas for new features or improvements.
+Setting up your environment
+---------------------------
+
+If you're contributing to this project for the fist time, you can set up your
+environment on Linux or OSX with the following commands::
+
+ $ git clone git@github.com:hgrecco/pint.git
+ $ cd pint
+ $ python -m virtualenv venv
+ $ source venv/bin/activate
+ $ pip install -e .
+ $ pip install -r requirements_docs.txt
+
+Running tests and building documentation
+----------------------------------------
+
+To run the test suite, invoke pytest from the ``pint`` directory::
+
+ $ cd pint
+ $ pytest
+
+To run the doctests, invoke Sphinx's doctest module from the ``docs`` directory::
+
+ $ cd docs
+ $ make doctest
+
+To build the documentation, invoke Sphinx from the ``docs`` directory::
+
+ $ cd docs
+ $ make html
+
+
.. _github: http://github.com/hgrecco/pint
.. _`issue tracker`: https://github.com/hgrecco/pint/issues
.. _`bors-ng`: https://github.com/bors-ng/bors-ng
diff --git a/docs/defining-quantities.rst b/docs/defining-quantities.rst
new file mode 100644
index 0000000..b0126f3
--- /dev/null
+++ b/docs/defining-quantities.rst
@@ -0,0 +1,142 @@
+Defining Quantities
+===================
+
+A quantity in Pint is the product of a unit and a magnitude.
+
+Pint supports several different ways of defining physical quantities, including
+a powerful string parsing system. These methods are largely interchangeable,
+though you may **need** to use the constructor form under certain circumstances
+(see :doc:`nonmult` for an example of where the constructor form is required).
+
+By multiplication
+-----------------
+
+If you've read the :ref:`Tutorial`, you're already familiar with defining a
+quantity by multiplying a ``Unit()`` and a scalar:
+
+.. doctest::
+
+ >>> from pint import UnitRegistry
+ >>> ureg = UnitRegistry()
+ >>> ureg.meter
+ <Unit('meter')>
+ >>> 30.0 * ureg.meter
+ <Quantity(30.0, 'meter')>
+
+This works to build up complex units as well:
+
+.. doctest::
+
+ >>> 9.8 * ureg.meter / ureg.second**2
+ <Quantity(9.8, 'meter / second ** 2')>
+
+
+Using the constructor
+---------------------
+
+In some cases it is useful to define :class:`Quantity() <pint.quantity.Quantity>`
+objects using it's class constructor. Using the constructor allows you to
+specify the units and magnitude separately.
+
+We typically abbreviate that constructor as `Q_` to make it's usage less verbose:
+
+.. doctest::
+
+ >>> Q_ = ureg.Quantity
+ >>> Q_(1.78, ureg.meter)
+ <Quantity(1.78, 'meter')>
+
+As you can see below, the multiplication and constructor methods should produce
+the same results:
+
+.. doctest::
+
+ >>> Q_(30.0, ureg.meter) == 30.0 * ureg.meter
+ True
+ >>> Q_(9.8, ureg.meter / ureg.second**2)
+ <Quantity(9.8, 'meter / second ** 2')>
+
+
+Using string parsing
+--------------------
+
+Pint includes a powerful parser for detecting magnitudes and units (with or
+without prefixes) in strings. Calling the ``UnitRegistry()`` directly
+invokes the parsing function:
+
+.. doctest::
+
+ >>> 30.0 * ureg('meter')
+ <Quantity(30.0, 'meter')>
+ >>> ureg('30.0 meters')
+ <Quantity(30.0, 'meter')>
+ >>> ureg('3000cm').to('meters')
+ <Quantity(30.0, 'meter')>
+
+The parsing function is also available to the ``Quantity()`` constructor and
+the various ``.to()`` methods:
+
+.. doctest::
+
+ >>> Q_('30.0 meters')
+ <Quantity(30.0, 'meter')>
+ >>> Q_(30.0, 'meter')
+ <Quantity(30.0, 'meter')>
+ >>> Q_('3000.0cm').to('meter')
+ <Quantity(30.0, 'meter')>
+
+Or as a standalone method on the ``UnitRegistry``:
+
+.. doctest::
+
+ >>> 2.54 * ureg.parse_expression('centimeter')
+ <Quantity(2.54, 'centimeter')>
+
+It is fairly good at detecting compound units:
+
+.. doctest::
+
+ >>> g = ureg('9.8 meters/second**2')
+ >>> g
+ <Quantity(9.8, 'meter / second ** 2')>
+ >>> g.to('furlongs/fortnight**2')
+ <Quantity(7.12770743e+10, 'furlong / fortnight ** 2')>
+
+And behaves well when given dimensionless quantities, which are parsed into
+their appropriate objects:
+
+.. doctest::
+
+ >>> ureg('2.54')
+ 2.54
+ >>> type(ureg('2.54'))
+ <class 'float'>
+ >>> Q_('2.54')
+ <Quantity(2.54, 'dimensionless')>
+ >>> type(Q_('2.54'))
+ <class 'pint.quantity.build_quantity_class.<locals>.Quantity'>
+
+.. note:: Pint's rule for parsing strings with a mixture of numbers and
+ units is that **units are treated with the same precedence as numbers**.
+
+For example, the units of
+
+.. doctest::
+
+ >>> Q_('3 l / 100 km')
+ <Quantity(0.03, 'kilometer * liter')>
+
+may be unexpected at first but, are a consequence of applying this rule. Use
+brackets to get the expected result:
+
+.. doctest::
+
+ >>> Q_('3 l / (100 km)')
+ <Quantity(0.03, 'liter / kilometer')>
+
+.. note:: Since version 0.7, Pint **does not** use eval_ under the hood.
+ This change removes the `serious security problems`_ that the system is
+ exposed to when parsing information from untrusted sources.
+
+.. _eval: http://docs.python.org/3/library/functions.html#eval
+.. _`serious security problems`: http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
diff --git a/docs/defining.rst b/docs/defining.rst
index 152893a..43344bc 100644
--- a/docs/defining.rst
+++ b/docs/defining.rst
@@ -112,7 +112,7 @@ Let's add a dog_year (sometimes written as dy) equivalent to 52 (human) days:
# We create a quantity based on that unit and we convert to years.
>>> lassie_lifespan = Q_(10, 'year')
>>> print(lassie_lifespan.to('dog_years'))
- 70.23888438100961 dog_year
+ 70.240384... dog_year
Note that we have used the name `dog_years` even though we have not defined the
plural form as an alias. Pint takes care of that, so you don't have to.
@@ -132,7 +132,7 @@ Same for aliases and derived dimensions:
.. doctest::
>>> ureg.define('@alias meter = metro = metr')
- >>> ureg.define('[hypervolume] = [length ** 4]')
+ >>> ureg.define('[hypervolume] = [length] ** 4')
.. warning::
@@ -152,3 +152,17 @@ leading underscore:
>>> ureg.define('mpg = 1 * mile / gallon')
>>> fuel_ec_europe = 5 * ureg.L / ureg._100km
>>> fuel_ec_us = (1 / fuel_ec_europe).to(ureg.mpg)
+
+
+Checking if a unit is already defined
+-------------------------------------
+
+The python ``in`` keyword works as expected with unit registries. Check if
+a unit has been defined with the following:
+
+.. doctest::
+
+ >>> 'MHz' in ureg
+ True
+ >>> 'gigatrees' in ureg
+ False
diff --git a/docs/developers_reference.rst b/docs/developers_reference.rst
index 1577328..f2be046 100644
--- a/docs/developers_reference.rst
+++ b/docs/developers_reference.rst
@@ -2,8 +2,8 @@
Developer reference
===================
-Pint
-====
+All Modules
+===========
.. automodule:: pint
:members:
@@ -55,60 +55,3 @@ Pint
.. 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 --git a/docs/getting.rst b/docs/getting.rst
index db3b2eb..84239e2 100644
--- a/docs/getting.rst
+++ b/docs/getting.rst
@@ -9,12 +9,18 @@ You can install it (or upgrade to the latest version) using pip_::
$ pip install -U pint
-That's all! You can check that Pint is correctly installed by starting up python, and importing pint:
+That's all! You can check that Pint is correctly installed by starting up python, and importing Pint:
-.. testcode::
+.. code-block:: python
- >>> import pint # doctest: +SKIP
- >>> pint.__version__ # doctest: +SKIP
+ >>> import pint
+ >>> pint.__version__ # doctest: +SKIP
+
+Or running the test suite:
+
+.. code-block:: python
+
+ >>> pint.test()
.. note:: If you have an old system installation of Python and you don't want to
mess with it, you can try `Anaconda CE`_. It is a free Python distribution by
@@ -23,16 +29,6 @@ That's all! You can check that Pint is correctly installed by starting up python
$ conda install -c conda-forge pint
-You can check the installation with the following command:
-
- >>> pint.test() # doctest: +SKIP
-
-
-On Arch Linux, you can alternatively install Pint from the Arch User Repository
-(AUR). The latest release is available as `python-pint`_, and packages tracking
-the master branch of the GitHub repository are available as `python-pint-git`_
-and `python2-pint-git`_.
-
Getting the code
----------------
@@ -54,13 +50,9 @@ Once you have a copy of the source, you can embed it in your Python package, or
$ python setup.py install
-
.. _easy_install: http://pypi.python.org/pypi/setuptools
.. _Python: http://www.python.org/
.. _pip: http://www.pip-installer.org/
.. _`Anaconda CE`: https://store.continuum.io/cshop/anaconda
-.. _`python-pint`: https://aur.archlinux.org/packages/python-pint/
-.. _`python-pint-git`: https://aur.archlinux.org/packages/python-pint-git/
-.. _`python2-pint-git`: https://aur.archlinux.org/packages/python2-pint-git/
.. _PyPI: https://pypi.python.org/pypi/Pint/
.. _GitHub: https://github.com/hgrecco/pint
diff --git a/docs/index.rst b/docs/index.rst
index fb7015e..831ab6a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -17,8 +17,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 3.6+ with no other
-dependency. It is licensed under BSD.
-
+dependencies. It is licensed under a `BSD 3-clause style license`_.
It is extremely easy and natural to use:
@@ -39,6 +38,7 @@ and you can make good use of numpy if you want:
>>> np.sum(_)
<Quantity(7.07, 'meter')>
+See the :ref:`Tutorial` for more help getting started.
Quick Installation
------------------
@@ -57,6 +57,8 @@ or utilizing conda, with the conda-forge channel:
and then simply enjoy it!
+(See :ref:`Installation <getting>` for more detail.)
+
Design principles
-----------------
@@ -80,12 +82,12 @@ 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.
+(``fraction``, ``float``, ``decimal``, ``numpy.ndarray``, etc). NumPy_ is not
+required, but is supported.
**Awesome NumPy integration**: When you choose to use a NumPy_ ndarray, its methods and
ufuncs are supported including automatic conversion of units. For example
-`numpy.arccos(q)` will require a dimensionless `q` and the units of the output
+``numpy.arccos(q)`` will require a dimensionless ``q`` and the units of the output
quantity will be radian.
**Uncertainties integration**: transparently handles calculations with
@@ -98,13 +100,15 @@ 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`_.
+**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.
+``numpy.arccos(q)`` will require a dimensionless ``q`` and the units
+of the output quantity will be radian.
User Guide
@@ -115,6 +119,7 @@ User Guide
getting
tutorial
+ defining-quantities
numpy
nonmult
log_units
@@ -129,6 +134,7 @@ User Guide
systems
currencies
pint-pandas.ipynb
+ pint-convert
More information
----------------
@@ -160,3 +166,4 @@ One last thing
.. _`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
+.. _`BSD 3-clause style license`: https://github.com/hgrecco/pint/blob/master/LICENSE
diff --git a/docs/measurement.rst b/docs/measurement.rst
index 12facfb..a49c821 100644
--- a/docs/measurement.rst
+++ b/docs/measurement.rst
@@ -4,9 +4,16 @@
Using Measurements
==================
-Measurements are the combination of two quantities: the mean value and the error (or uncertainty). The easiest ways to generate a measurement object is from a quantity using the `plus_minus` operator.
+If you have the `Uncertainties package`_ installed, you can use Pint to keep
+track of measurements with specified uncertainty, and not just exact physical
+quantities.
+
+Measurements are the combination of two quantities: the mean value and the error
+(or uncertainty). The easiest ways to generate a measurement object is from a
+quantity using the ``plus_minus()`` method.
.. doctest::
+ :skipif: not_installed['uncertainties']
>>> import numpy as np
>>> from pint import UnitRegistry
@@ -15,16 +22,10 @@ Measurements are the combination of two quantities: the mean value and the error
>>> print(book_length)
(20.0 +/- 2.0) centimeter
-.. testsetup:: *
-
- import numpy as np
- from pint import UnitRegistry
- ureg = UnitRegistry()
- Q_ = ureg.Quantity
-
You can inspect the mean value, the absolute error and the relative error:
.. doctest::
+ :skipif: not_installed['uncertainties']
>>> print(book_length.value)
20.0 centimeter
@@ -36,30 +37,36 @@ You can inspect the mean value, the absolute error and the relative error:
You can also create a Measurement object giving the relative error:
.. doctest::
+ :skipif: not_installed['uncertainties']
>>> book_length = (20. * ureg.centimeter).plus_minus(.1, relative=True)
>>> print(book_length)
(20.0 +/- 2.0) centimeter
-Measurements support the same formatting codes as Quantity. For example, to pretty print a measurement with 2 decimal positions:
+Measurements support the same formatting codes as Quantity. For example, to pretty
+print a measurement with 2 decimal positions:
.. doctest::
+ :skipif: not_installed['uncertainties']
>>> print('{:.02fP}'.format(book_length))
(20.00 ± 2.00) centimeter
-Mathematical operations with Measurements, return new measurements following the `Propagation of uncertainty`_ rules.
+Mathematical operations with Measurements, return new measurements following
+the `Propagation of uncertainty`_ rules.
.. doctest::
+ :skipif: not_installed['uncertainties']
>>> print(2 * book_length)
- (40.0 +/- 4.0) centimeter
+ (40 +/- 4) centimeter
>>> width = (10 * ureg.centimeter).plus_minus(1)
>>> print('{:.02f}'.format(book_length + width))
(30.00 +/- 2.24) centimeter
-.. note:: only linear combinations are currently supported.
+.. note:: Only linear combinations are currently supported.
.. _`Propagation of uncertainty`: http://en.wikipedia.org/wiki/Propagation_of_uncertainty
+.. _`Uncertainties package`: https://uncertainties-python-package.readthedocs.io/en/latest/
diff --git a/docs/nonmult.rst b/docs/nonmult.rst
index 4d792c1..a649d2a 100644
--- a/docs/nonmult.rst
+++ b/docs/nonmult.rst
@@ -14,30 +14,24 @@ kelvin and rankine abbreviated as degF, degC, degK, and degR.
For example, to convert from celsius to fahrenheit:
-.. testsetup::
-
- from pint import UnitRegistry
- ureg = UnitRegistry()
- ureg.default_format = '.3f'
- Q_ = ureg.Quantity
-
.. doctest::
- >>> from pint import UnitRegistry
- >>> ureg = UnitRegistry()
- >>> Q_ = ureg.Quantity
- >>> home = Q_(25.4, ureg.degC)
- >>> print(home.to('degF'))
- 77.7200004 degF
+ >>> from pint import UnitRegistry
+ >>> ureg = UnitRegistry()
+ >>> ureg.default_format = '.3f'
+ >>> Q_ = ureg.Quantity
+ >>> home = Q_(25.4, ureg.degC)
+ >>> print(home.to('degF'))
+ 77.720 degree_Fahrenheit
or to other kelvin or rankine:
.. doctest::
>>> print(home.to('kelvin'))
- 298.55 kelvin
+ 298.550 kelvin
>>> print(home.to('degR'))
- 537.39 degR
+ 537.390 degree_Rankine
Additionally, for every non-multiplicative temperature unit
in the registry, there is also a *delta* counterpart to specify
@@ -48,18 +42,18 @@ is different).
.. doctest::
- >>> increase = 12.3 * ureg.delta_degC
- >>> print(increase.to(ureg.kelvin))
- 12.3 kelvin
- >>> print(increase.to(ureg.delta_degF))
- 22.14 delta_degF
+ >>> increase = 12.3 * ureg.delta_degC
+ >>> print(increase.to(ureg.kelvin))
+ 12.300 kelvin
+ >>> print(increase.to(ureg.delta_degF))
+ 22.140 delta_degree_Fahrenheit
Subtraction of two temperatures given in offset units yields a *delta* unit:
.. doctest::
>>> Q_(25.4, ureg.degC) - Q_(10., ureg.degC)
- <Quantity(15.4, 'delta_degC')>
+ <Quantity(15.4, 'delta_degree_Celsius')>
You can add or subtract a quantity with *delta* unit and a quantity with
offset unit:
@@ -67,9 +61,9 @@ offset unit:
.. doctest::
>>> Q_(25.4, ureg.degC) + Q_(10., ureg.delta_degC)
- <Quantity(35.4, 'degC')>
+ <Quantity(35.4, 'degree_Celsius')>
>>> Q_(25.4, ureg.degC) - Q_(10., ureg.delta_degC)
- <Quantity(15.4, 'degC')>
+ <Quantity(15.4, 'degree_Celsius')>
If you want to add a quantity with absolute unit to one with offset unit, like here
@@ -94,7 +88,7 @@ or convert the absolute unit to a *delta* unit:
.. doctest::
>>> Q_(10., ureg.degC) + heating_rate.to('delta_degC/min') * Q_(30, ureg.min)
- <Quantity(25.0, 'degC')>
+ <Quantity(25.0, 'degree_Celsius')>
In contrast to subtraction, the addition of quantities with offset units
is ambiguous, e.g. for *10 degC + 100 degC* two different result are reasonable
@@ -108,7 +102,7 @@ Quantities with *delta* units are multiplicative:
>>> speed = 60. * ureg.delta_degC / ureg.min
>>> print(speed.to('delta_degC/second'))
- 1.0 delta_degC / second
+ 1.000 delta_degree_Celsius / second
However, multiplication, division and exponentiation of quantities with
offset units is problematic just like addition. Pint (since version 0.6)
@@ -125,7 +119,7 @@ to be explicitly created:
...
OffsetUnitCalculusError: Ambiguous operation with offset unit (degC).
>>> Q_(25.4, ureg.degC)
- <Quantity(25.4, 'degC')>
+ <Quantity(25.4, 'degree_Celsius')>
As an alternative to raising an error, pint can be configured to work more
relaxed via setting the UnitRegistry parameter *autoconvert_offset_to_baseunit*
@@ -139,20 +133,24 @@ to true. In this mode, pint behaves differently:
>>> ureg = UnitRegistry(autoconvert_offset_to_baseunit = True)
>>> T = 25.4 * ureg.degC
>>> T
- <Quantity(25.4, 'degC')>
+ <Quantity(25.4, 'degree_Celsius')>
* Before all other multiplications, all divisions and in case of
exponentiation [#f1]_ involving quantities with offset-units, pint
will convert the quantities with offset units automatically to the
corresponding base unit before performing the operation.
+.. doctest::
+
>>> 1/T
- <Quantity(0.00334952269302, '1 / kelvin')>
+ <Quantity(0.0033495..., '1 / kelvin')>
>>> T * 10 * ureg.meter
<Quantity(527.15, 'kelvin * meter')>
You can change the behaviour at any time:
+.. doctest::
+
>>> ureg.autoconvert_offset_to_baseunit = False
>>> 1/T
Traceback (most recent call last):
@@ -165,28 +163,28 @@ is found in a multiplicative context. For example, here:
.. doctest::
>>> print(ureg.parse_units('degC/meter'))
- delta_degC / meter
+ delta_degree_Celsius / meter
but not here:
.. doctest::
>>> print(ureg.parse_units('degC'))
- degC
+ degree_Celsius
You can override this behaviour:
.. doctest::
>>> print(ureg.parse_units('degC/meter', as_delta=False))
- degC / meter
+ degree_Celsius / meter
Note that the magnitude is left unchanged:
.. doctest::
>>> Q_(10, 'degC/meter')
- <Quantity(10, 'delta_degC / meter')>
+ <Quantity(10, 'delta_degree_Celsius / meter')>
To define a new temperature, you need to specify the offset. For example,
this is the definition of the celsius and fahrenheit::
diff --git a/docs/numpy.ipynb b/docs/numpy.ipynb
index 4ed462b..7b8a0a3 100644
--- a/docs/numpy.ipynb
+++ b/docs/numpy.ipynb
@@ -17,17 +17,13 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"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",
@@ -49,17 +45,9 @@
},
{
"cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[3.0 4.0] meter\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"legs1 = Q_(np.asarray([3., 4.]), 'meter')\n",
"print(legs1)"
@@ -67,17 +55,9 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[3.0 4.0] meter\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"legs1 = [3., 4.] * ureg.meter\n",
"print(legs1)"
@@ -92,51 +72,27 @@
},
{
"cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[0.003 0.004] kilometer\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"print(legs1.to('kilometer'))"
]
},
{
"cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[length]\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"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"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"try:\n",
" legs1.to('joule')\n",
@@ -153,17 +109,9 @@
},
{
"cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[400.0 300.0] centimeter\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"legs2 = [400., 300.] * ureg.centimeter\n",
"print(legs2)"
@@ -178,17 +126,9 @@
},
{
"cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[5.0 5.0] meter\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"hyps = np.hypot(legs1, legs2)\n",
"print(hyps)"
@@ -207,17 +147,9 @@
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[0.6435011087932843 0.9272952180016123] radian\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"angles = np.arccos(legs2/hyps)\n",
"print(angles)"
@@ -232,17 +164,9 @@
},
{
"cell_type": "code",
- "execution_count": 10,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[36.86989764584401 53.13010235415599] degree\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"print(angles.to('degree'))"
]
@@ -257,17 +181,9 @@
},
{
"cell_type": "code",
- "execution_count": 11,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Cannot convert from 'centimeter' ([length]) to 'dimensionless' (dimensionless)\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"try:\n",
" np.arccos(legs2)\n",
@@ -294,17 +210,9 @@
},
{
"cell_type": "code",
- "execution_count": 12,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "['alen', 'all', 'allclose', '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', 'intersect1d', '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"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"from pint.numpy_func import HANDLED_FUNCTIONS\n",
"print(sorted(list(HANDLED_FUNCTIONS)))"
@@ -361,175 +269,9 @@
},
{
"cell_type": "code",
- "execution_count": 13,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/svg+xml": [
- "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
- "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
- " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
- "<!-- Generated by graphviz version 2.38.0 (20140413.2041)\n",
- " -->\n",
- "<!-- Title: %3 Pages: 1 -->\n",
- "<svg width=\"576pt\" height=\"288pt\"\n",
- " viewBox=\"0.00 0.00 576.00 288.25\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
- "<g id=\"graph0\" class=\"graph\" transform=\"scale(0.86823 0.86823) rotate(0) translate(4 328)\">\n",
- "<title>%3</title>\n",
- "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-328 659.419,-328 659.419,4 -4,4\"/>\n",
- "<!-- Dask array -->\n",
- "<g id=\"node1\" class=\"node\"><title>Dask array</title>\n",
- "<ellipse fill=\"none\" stroke=\"black\" cx=\"186.393\" cy=\"-162\" rx=\"64.189\" ry=\"18\"/>\n",
- "<text text-anchor=\"middle\" x=\"186.393\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\">Dask array</text>\n",
- "</g>\n",
- "<!-- NumPy ndarray -->\n",
- "<g id=\"node2\" class=\"node\"><title>NumPy ndarray</title>\n",
- "<ellipse fill=\"none\" stroke=\"black\" cx=\"362.393\" cy=\"-18\" rx=\"80.6858\" ry=\"18\"/>\n",
- "<text text-anchor=\"middle\" x=\"362.393\" y=\"-14.3\" font-family=\"Courier,monospace\" font-size=\"14.00\">NumPy ndarray</text>\n",
- "</g>\n",
- "<!-- Dask array&#45;&gt;NumPy ndarray -->\n",
- "<g id=\"edge1\" class=\"edge\"><title>Dask array&#45;&gt;NumPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M183.487,-143.823C181.189,-124.181 180.714,-92.1479 197.393,-72 217.389,-47.8459 248.45,-34.5744 278.253,-27.3279\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"279.293,-30.6823 288.294,-25.0944 277.773,-23.8493 279.293,-30.6823\"/>\n",
- "</g>\n",
- "<!-- CuPy ndarray -->\n",
- "<g id=\"node3\" class=\"node\"><title>CuPy ndarray</title>\n",
- "<ellipse fill=\"none\" stroke=\"black\" cx=\"75.3933\" cy=\"-90\" rx=\"75.2868\" ry=\"18\"/>\n",
- "<text text-anchor=\"middle\" x=\"75.3933\" y=\"-86.3\" font-family=\"Courier,monospace\" font-size=\"14.00\">CuPy ndarray</text>\n",
- "</g>\n",
- "<!-- Dask array&#45;&gt;CuPy ndarray -->\n",
- "<g id=\"edge2\" class=\"edge\"><title>Dask array&#45;&gt;CuPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M161.483,-145.291C146.133,-135.611 126.244,-123.068 109.45,-112.477\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"111.316,-109.516 100.991,-107.142 107.582,-115.437 111.316,-109.516\"/>\n",
- "</g>\n",
- "<!-- Sparse COO -->\n",
- "<g id=\"node4\" class=\"node\"><title>Sparse COO</title>\n",
- "<ellipse fill=\"none\" stroke=\"black\" cx=\"270.393\" cy=\"-90\" rx=\"64.189\" ry=\"18\"/>\n",
- "<text text-anchor=\"middle\" x=\"270.393\" y=\"-86.3\" font-family=\"Courier,monospace\" font-size=\"14.00\">Sparse COO</text>\n",
- "</g>\n",
- "<!-- Dask array&#45;&gt;Sparse COO -->\n",
- "<g id=\"edge3\" class=\"edge\"><title>Dask array&#45;&gt;Sparse COO</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M205.876,-144.765C216.823,-135.642 230.668,-124.105 242.723,-114.059\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"245.284,-116.48 250.726,-107.39 240.803,-111.103 245.284,-116.48\"/>\n",
- "</g>\n",
- "<!-- NumPy masked array -->\n",
- "<g id=\"node5\" class=\"node\"><title>NumPy masked array</title>\n",
- "<ellipse fill=\"none\" stroke=\"black\" cx=\"497.393\" cy=\"-90\" rx=\"107.482\" ry=\"18\"/>\n",
- "<text text-anchor=\"middle\" x=\"497.393\" y=\"-86.3\" font-family=\"Courier,monospace\" font-size=\"14.00\">NumPy masked array</text>\n",
- "</g>\n",
- "<!-- Dask array&#45;&gt;NumPy masked array -->\n",
- "<g id=\"edge4\" class=\"edge\"><title>Dask array&#45;&gt;NumPy masked array</title>\n",
- "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M234.987,-150.062C286.262,-138.522 367.594,-120.215 426.124,-107.041\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"427.055,-110.419 436.043,-104.809 425.518,-103.59 427.055,-110.419\"/>\n",
- "</g>\n",
- "<!-- CuPy ndarray&#45;&gt;NumPy ndarray -->\n",
- "<g id=\"edge5\" class=\"edge\"><title>CuPy ndarray&#45;&gt;NumPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M126.043,-76.6465C174.571,-64.8104 247.927,-46.9187 300.028,-34.211\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"300.939,-37.5915 309.825,-31.8216 299.28,-30.7909 300.939,-37.5915\"/>\n",
- "</g>\n",
- "<!-- Sparse COO&#45;&gt;NumPy ndarray -->\n",
- "<g id=\"edge6\" class=\"edge\"><title>Sparse COO&#45;&gt;NumPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M291.731,-72.7646C303.952,-63.4665 319.469,-51.6599 332.847,-41.4806\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"335.013,-44.2305 340.852,-35.3898 330.775,-38.6596 335.013,-44.2305\"/>\n",
- "</g>\n",
- "<!-- NumPy masked array&#45;&gt;NumPy ndarray -->\n",
- "<g id=\"edge7\" class=\"edge\"><title>NumPy masked array&#45;&gt;NumPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M466.082,-72.7646C447.034,-62.8879 422.527,-50.1806 402.122,-39.5999\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"403.558,-36.402 393.069,-34.906 400.336,-42.6163 403.558,-36.402\"/>\n",
- "</g>\n",
- "<!-- Jax array -->\n",
- "<g id=\"node6\" class=\"node\"><title>Jax array</title>\n",
- "<ellipse fill=\"none\" stroke=\"black\" cx=\"583.393\" cy=\"-162\" rx=\"59.2899\" ry=\"18\"/>\n",
- "<text text-anchor=\"middle\" x=\"583.393\" y=\"-158.3\" font-family=\"Courier,monospace\" font-size=\"14.00\">Jax array</text>\n",
- "</g>\n",
- "<!-- Jax array&#45;&gt;NumPy ndarray -->\n",
- "<g id=\"edge8\" class=\"edge\"><title>Jax array&#45;&gt;NumPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M597.707,-144.223C612.335,-124.957 630.647,-93.2771 613.393,-72 592.658,-46.4286 512.161,-32.7163 447.843,-25.6941\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"447.982,-22.1895 437.671,-24.6237 447.25,-29.1511 447.982,-22.1895\"/>\n",
- "</g>\n",
- "<!-- Pint Quantity -->\n",
- "<g id=\"node7\" class=\"node\"><title>Pint Quantity</title>\n",
- "<ellipse fill=\"none\" stroke=\"black\" cx=\"387.393\" cy=\"-234\" rx=\"80.6858\" ry=\"18\"/>\n",
- "<text text-anchor=\"middle\" x=\"387.393\" y=\"-230.3\" font-family=\"Courier,monospace\" font-size=\"14.00\">Pint Quantity</text>\n",
- "</g>\n",
- "<!-- Pint Quantity&#45;&gt;Dask array -->\n",
- "<g id=\"edge9\" class=\"edge\"><title>Pint Quantity&#45;&gt;Dask array</title>\n",
- "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M345.746,-218.496C313.407,-207.234 268.515,-191.599 234.617,-179.794\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"235.518,-176.402 224.923,-176.418 233.216,-183.013 235.518,-176.402\"/>\n",
- "</g>\n",
- "<!-- Pint Quantity&#45;&gt;NumPy ndarray -->\n",
- "<g id=\"edge10\" class=\"edge\"><title>Pint Quantity&#45;&gt;NumPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M385.39,-215.849C381.065,-178.832 370.826,-91.1809 365.593,-46.3863\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"369.043,-45.7588 364.406,-36.2325 362.091,-46.5711 369.043,-45.7588\"/>\n",
- "</g>\n",
- "<!-- Pint Quantity&#45;&gt;CuPy ndarray -->\n",
- "<g id=\"edge11\" class=\"edge\"><title>Pint Quantity&#45;&gt;CuPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M313.751,-226.537C243.355,-219.009 143.6,-204.386 113.393,-180 94.4685,-164.722 84.7957,-138.263 79.9628,-118.154\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"83.371,-117.354 77.8533,-108.31 76.5264,-118.821 83.371,-117.354\"/>\n",
- "</g>\n",
- "<!-- Pint Quantity&#45;&gt;Sparse COO -->\n",
- "<g id=\"edge12\" class=\"edge\"><title>Pint Quantity&#45;&gt;Sparse COO</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M373.512,-216.153C353.018,-191.279 314.535,-144.574 290.817,-115.788\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"293.316,-113.317 284.256,-107.825 287.914,-117.768 293.316,-113.317\"/>\n",
- "</g>\n",
- "<!-- Pint Quantity&#45;&gt;NumPy masked array -->\n",
- "<g id=\"edge13\" class=\"edge\"><title>Pint Quantity&#45;&gt;NumPy masked array</title>\n",
- "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M400.444,-216.153C419.609,-191.413 455.504,-145.075 477.831,-116.253\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"480.66,-118.317 484.017,-108.268 475.126,-114.03 480.66,-118.317\"/>\n",
- "</g>\n",
- "<!-- xarray Dataset/DataArray/Variable -->\n",
- "<g id=\"node8\" class=\"node\"><title>xarray Dataset/DataArray/Variable</title>\n",
- "<ellipse fill=\"none\" stroke=\"black\" cx=\"387.393\" cy=\"-306\" rx=\"187.667\" ry=\"18\"/>\n",
- "<text text-anchor=\"middle\" x=\"387.393\" y=\"-302.3\" font-family=\"Courier,monospace\" font-size=\"14.00\">xarray Dataset/DataArray/Variable</text>\n",
- "</g>\n",
- "<!-- xarray Dataset/DataArray/Variable&#45;&gt;Dask array -->\n",
- "<g id=\"edge14\" class=\"edge\"><title>xarray Dataset/DataArray/Variable&#45;&gt;Dask array</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M335.932,-288.68C312.798,-279.919 285.922,-267.664 264.393,-252 239.936,-234.205 217.813,-207.445 203.428,-187.915\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"206.25,-185.844 197.568,-179.772 200.569,-189.933 206.25,-185.844\"/>\n",
- "</g>\n",
- "<!-- xarray Dataset/DataArray/Variable&#45;&gt;NumPy ndarray -->\n",
- "<g id=\"edge17\" class=\"edge\"><title>xarray Dataset/DataArray/Variable&#45;&gt;NumPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M445.846,-288.873C475.49,-279.738 511.635,-267.11 542.393,-252 594.504,-226.401 623.889,-231.131 651.393,-180 658.973,-165.909 653.554,-159.853 651.393,-144 646.908,-111.093 655.757,-94.57 631.393,-72 604.934,-47.4892 515.533,-33.4248 446.983,-26.0284\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"447.341,-22.5468 437.031,-24.985 446.611,-29.5086 447.341,-22.5468\"/>\n",
- "</g>\n",
- "<!-- xarray Dataset/DataArray/Variable&#45;&gt;CuPy ndarray -->\n",
- "<g id=\"edge15\" class=\"edge\"><title>xarray Dataset/DataArray/Variable&#45;&gt;CuPy ndarray</title>\n",
- "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M281.369,-291.128C212.822,-276.089 128.448,-244.835 84.3933,-180 72.1516,-161.984 70.6221,-136.994 71.7416,-118.08\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"75.2375,-118.278 72.6151,-108.013 68.2637,-117.673 75.2375,-118.278\"/>\n",
- "</g>\n",
- "<!-- xarray Dataset/DataArray/Variable&#45;&gt;Sparse COO -->\n",
- "<g id=\"edge16\" class=\"edge\"><title>xarray Dataset/DataArray/Variable&#45;&gt;Sparse COO</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M342.591,-288.285C325.913,-279.869 308.387,-267.987 297.393,-252 269.717,-211.751 267.068,-152.724 268.293,-118.445\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"271.799,-118.402 268.799,-108.241 264.808,-118.055 271.799,-118.402\"/>\n",
- "</g>\n",
- "<!-- xarray Dataset/DataArray/Variable&#45;&gt;NumPy masked array -->\n",
- "<g id=\"edge18\" class=\"edge\"><title>xarray Dataset/DataArray/Variable&#45;&gt;NumPy masked array</title>\n",
- "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M432.544,-288.519C449.273,-280.137 466.743,-268.218 477.393,-252 504.041,-211.424 503.873,-152.51 500.918,-118.34\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"504.378,-117.768 499.891,-108.17 497.414,-118.472 504.378,-117.768\"/>\n",
- "</g>\n",
- "<!-- xarray Dataset/DataArray/Variable&#45;&gt;Jax array -->\n",
- "<g id=\"edge20\" class=\"edge\"><title>xarray Dataset/DataArray/Variable&#45;&gt;Jax array</title>\n",
- "<path fill=\"none\" stroke=\"black\" stroke-dasharray=\"5,2\" d=\"M440.005,-288.657C462.977,-279.982 489.402,-267.804 510.393,-252 533.791,-234.384 554.324,-207.77 567.604,-188.233\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"570.577,-190.082 573.179,-179.812 564.74,-186.218 570.577,-190.082\"/>\n",
- "</g>\n",
- "<!-- xarray Dataset/DataArray/Variable&#45;&gt;Pint Quantity -->\n",
- "<g id=\"edge19\" class=\"edge\"><title>xarray Dataset/DataArray/Variable&#45;&gt;Pint Quantity</title>\n",
- "<path fill=\"none\" stroke=\"black\" d=\"M387.393,-287.697C387.393,-279.983 387.393,-270.712 387.393,-262.112\"/>\n",
- "<polygon fill=\"black\" stroke=\"black\" points=\"390.893,-262.104 387.393,-252.104 383.893,-262.104 390.893,-262.104\"/>\n",
- "</g>\n",
- "</g>\n",
- "</svg>\n"
- ],
- "text/plain": [
- "<graphviz.dot.Digraph at 0x7f68cc084208>"
- ]
- },
- "execution_count": 13,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"from graphviz import Digraph\n",
"\n",
@@ -568,44 +310,9 @@
},
{
"cell_type": "code",
- "execution_count": 14,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "<xarray.DataArray 'air' (lat: 25, lon: 53)>\n",
- "<Quantity([[241.2 242.5 243.5 ... 232.79999 235.5 238.59999]\n",
- " [243.79999 244.5 244.7 ... 232.79999 235.29999 239.29999]\n",
- " [250. 249.79999 248.89 ... 233.2 236.39 241.7 ]\n",
- " ...\n",
- " [296.6 296.19998 296.4 ... 295.4 295.1 294.69998]\n",
- " [295.9 296.19998 296.79 ... 295.9 295.9 295.19998]\n",
- " [296.29 296.79 297.1 ... 296.9 296.79 296.6 ]], 'kelvin')>\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",
- "<xarray.DataArray 'air' ()>\n",
- "<Quantity(302.6000061035156, 'kelvin')>\n",
- "Coordinates:\n",
- " time datetime64[ns] 2013-01-01\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"import xarray as xr\n",
"\n",
@@ -629,19 +336,9 @@
},
{
"cell_type": "code",
- "execution_count": 15,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "<COO: shape=(100, 100, 100), dtype=float64, nnz=99598, fill_value=0.0> meter\n",
- "\n",
- "0.09462606529121113 meter\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"from sparse import COO\n",
"\n",
@@ -667,22 +364,9 @@
},
{
"cell_type": "code",
- "execution_count": 16,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "<Quantity([2 -- 5 --], 'meter')>\n",
- "\n",
- "masked_array(data=[<Quantity(2, 'meter')>, --, <Quantity(5, 'meter')>, --],\n",
- " mask=[False, True, False, True],\n",
- " fill_value='?',\n",
- " dtype=object)\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"m = np.ma.masked_array([2, 3, 5, 7], mask=[False, True, False, True])\n",
"\n",
@@ -700,33 +384,44 @@
"cell_type": "markdown",
"metadata": {},
"source": [
+ "**Pint Quantity wrapping Dask Array**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import dask.array as da\n",
+ "\n",
+ "d = da.arange(500, chunks=50)\n",
+ "\n",
+ "# Must create using Quantity class, otherwise Dask will wrap Pint Quantity\n",
+ "q = ureg.Quantity(d, ureg.kelvin)\n",
+ "\n",
+ "print(repr(q))\n",
+ "print()\n",
+ "\n",
+ "# DO NOT create using multiplication on the right until\n",
+ "# https://github.com/dask/dask/issues/4583 is resolved, as\n",
+ "# unexpected behavior may result\n",
+ "print(repr(d * ureg.kelvin))\n",
+ "print(repr(ureg.kelvin * d))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
"**xarray wrapping Pint Quantity wrapping Dask array wrapping Sparse COO**"
]
},
{
"cell_type": "code",
- "execution_count": 17,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "<xarray.DataArray 'test' (z: 100, y: 100, x: 100)>\n",
- "<Quantity(dask.array<COO, shape=(100, 100, 100), dtype=float64, chunksize=(100, 1, 1), chunktype=sparse.COO>, '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",
- "<xarray.DataArray 'test' ()>\n",
- "<Quantity(dask.array<mean_agg-aggregate, shape=(), dtype=float64, chunksize=(), chunktype=numpy.ndarray>, 'meter')>\n",
- "Coordinates:\n",
- " y int64 -46\n",
- " x float64 125.5\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"import dask.array as da\n",
"\n",
@@ -758,7 +453,7 @@
"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",
+ "- [pint-xarray](https://github.com/TomNicholas/pint-xarray/) (in early development as of April 2020, with [extra discussion here](https://github.com/hgrecco/pint/issues/849#issuecomment-579992247) - please come and help!)\n",
"\n",
"(Note: if you have developed a compatibility package for Pint, please submit a pull request to add it to this list!)"
]
@@ -780,8 +475,7 @@
"\n",
"This behaviour introduces some performance penalties and increased memory usage. Quantities that must be converted to other units require additional memory and CPU cycles. Therefore, for numerically intensive code, you might want to convert the objects first and then use directly the magnitude, such as by using Pint's `wraps` utility (see [wrapping](wrapping.rst)).\n",
"\n",
- "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."
+ "Attempting to access array interface protocol attributes (such as `__array_struct__` and `__array_interface__`) on Pint Quantities will raise an AttributeError, since a Quantity is meant to behave as a \"duck array,\" and not a pure ndarray."
]
}
],
@@ -801,7 +495,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.6.7"
+ "version": "3.8.2"
}
},
"nbformat": 4,
diff --git a/docs/pint-convert.rst b/docs/pint-convert.rst
new file mode 100644
index 0000000..8d548f8
--- /dev/null
+++ b/docs/pint-convert.rst
@@ -0,0 +1,91 @@
+.. _convert:
+
+Command-line script
+===================
+
+The script `pint-convert` allows a quick conversion to a target system or
+between arbitrary compatible units.
+
+By default, `pint-convert` converts to SI units::
+
+ $ pint-convert 225lb
+ 225 pound = 102.05828325 kg
+
+use the `--sys` argument to change it::
+
+ $ pint-convert --sys US 102kg
+ 102 kilogram = 224.871507429 lb
+
+or specify directly the target units::
+
+ $ pint-convert 102kg lb
+ 102 kilogram = 224.871507429 lb
+
+The input quantity can contain expressions::
+
+ $ pint-convert 7ft+2in
+ 7.166666666666667 foot = 2.1844 m
+
+in some cases parentheses and quotes may be needed::
+
+ $ pint-convert "225lb/(7ft+2in)"
+ 31.3953488372093 pound / foot = 46.7214261353 kg/m
+
+If a number is omitted, 1 is assumed::
+
+ $ pint-convert km mi
+ 1 kilometer = 0.621371192237 mi
+
+The default precision is 12 significant figures, it can be changed with `-p`,
+but note that the accuracy may be affected by floating-point errors::
+
+ $ pint-convert -p 3 mi
+ 1 mile = 1.61e+03 m
+
+ $ pint-convert -p 30 ly km
+ 1 light_year = 9460730472580.80078125 km
+
+Some contexts are automatically enabled, allowing conversion between not fully
+compatible units::
+
+ $ pint-convert 540nm
+ 540 nanometer = 5.4e-07 m
+
+ $ pint-convert kcal/mol
+ $ 1.0 kilocalorie / mole = 4184 kg·m²/mol/s²
+
+ $ pint-convert 540nm kcal/mol
+ 540 nanometer = 52.9471025594 kcal/mol
+
+With the `uncertainties` package, the experimental uncertainty in the physical
+constants is considered, and the result is given in compact notation, with the
+uncertainty in the last figures in parentheses::
+
+ $ pint-convert Eh eV
+ 1 hartree = 27.21138624599(5) eV
+
+The precision is limited by both the maximum number of significant digits (`-p`)
+and the maximum number of uncertainty digits (`-u`, 2 by default)::
+
+ $ pint-convert -p 20 Eh eV
+ 1 hartree = 27.211386245988(52) eV
+
+ $ pint-convert -p 20 -u 4 Eh eV
+ 1 hartree = 27.21138624598847(5207) eV
+
+The uncertainty can be disabled with `-U`)::
+
+ $ pint-convert -p 20 -U Eh eV
+ 1 hartree = 27.211386245988471444 eV
+
+Correlations between experimental constants are also known, and taken into
+account. Use `-C` to disable it::
+
+ $ pint-convert --sys atomic m_p
+ 1 proton_mass = 1836.15267344(11) m_e
+
+ $ pint-convert --sys atomic -C m_p
+ 1 proton_mass = 1836.15267344(79) m_e
+
+Again, note that results may differ slightly, usually in the last figure, from
+more authoritative sources, mainly due to floating-point errors.
diff --git a/docs/pint-pandas.ipynb b/docs/pint-pandas.ipynb
index e213aff..2052f1f 100644
--- a/docs/pint-pandas.ipynb
+++ b/docs/pint-pandas.ipynb
@@ -22,9 +22,13 @@
"## Installation\n",
"\n",
"\n",
- "Pandas support is provided by `pint-pandas`. It is not available on PyPI yet, to install it use\n",
+ "Pandas support is provided by the `pint-pandas` package. To install it use either:\n",
"```\n",
- "python -m pip install git+https://github.com/hgrecco/pint-pandas.git\n",
+ "python -m pip install pint-pandas\n",
+ "```\n",
+ "Or:\n",
+ "```\n",
+ "conda install -c conda-forge pint-pandas\n",
"```"
]
},
@@ -41,12 +45,12 @@
"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"
+ "First some imports (you don't need to import `pint_pandas` for this to work)"
]
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -63,72 +67,9 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead th {\n",
- " text-align: right;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr style=\"text-align: right;\">\n",
- " <th></th>\n",
- " <th>torque</th>\n",
- " <th>angular_velocity</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1</td>\n",
- " <td>1</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>2</td>\n",
- " <td>2</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>2</td>\n",
- " <td>2</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>3</td>\n",
- " <td>3</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df = pd.DataFrame({\n",
" \"torque\": pd.Series([1, 2, 2, 3], dtype=\"pint[lbf ft]\"),\n",
@@ -146,77 +87,9 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead th {\n",
- " text-align: right;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr style=\"text-align: right;\">\n",
- " <th></th>\n",
- " <th>torque</th>\n",
- " <th>angular_velocity</th>\n",
- " <th>power</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1</td>\n",
- " <td>1</td>\n",
- " <td>1</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>2</td>\n",
- " <td>2</td>\n",
- " <td>4</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>2</td>\n",
- " <td>2</td>\n",
- " <td>4</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>3</td>\n",
- " <td>3</td>\n",
- " <td>9</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df['power'] = df['torque'] * df['angular_velocity']\n",
"df"
@@ -231,23 +104,9 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"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"
- }
- ],
+ "outputs": [],
"source": [
"df.dtypes"
]
@@ -261,24 +120,9 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"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"
- }
- ],
+ "outputs": [],
"source": [
"df.power"
]
@@ -292,24 +136,9 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"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"
- }
- ],
+ "outputs": [],
"source": [
"df.power.values"
]
@@ -323,26 +152,9 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"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]) <Unit('foot * force_pound * revolutions_per_minute')>"
- ]
- },
- "execution_count": 7,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"df.power.values.quantity"
]
@@ -356,48 +168,18 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"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": [
- "<Unit('foot * force_pound * revolutions_per_minute')>"
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"df.power.pint.units"
]
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": null,
"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"
- }
- ],
+ "outputs": [],
"source": [
"df.power.pint.to(\"kW\").values"
]
@@ -413,12 +195,13 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd \n",
"import pint\n",
+ "import pint_pandas\n",
"import io"
]
},
@@ -431,7 +214,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -453,102 +236,9 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead tr th {\n",
- " text-align: left;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr>\n",
- " <th></th>\n",
- " <th>speed</th>\n",
- " <th>mech power</th>\n",
- " <th>torque</th>\n",
- " <th>rail pressure</th>\n",
- " <th>fuel flow rate</th>\n",
- " <th>fluid power</th>\n",
- " </tr>\n",
- " <tr>\n",
- " <th></th>\n",
- " <th>rpm</th>\n",
- " <th>kW</th>\n",
- " <th>N m</th>\n",
- " <th>bar</th>\n",
- " <th>l/min</th>\n",
- " <th>kW</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1000.0</td>\n",
- " <td>NaN</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>NaN</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>1100.0</td>\n",
- " <td>NaN</td>\n",
- " <td>10.0</td>\n",
- " <td>100000000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>NaN</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>1200.0</td>\n",
- " <td>NaN</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>NaN</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>1200.0</td>\n",
- " <td>NaN</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>NaN</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"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",
@@ -564,118 +254,18 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": null,
"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"
- }
- ],
+ "outputs": [],
"source": [
"df.dtypes"
]
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead th {\n",
- " text-align: right;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr style=\"text-align: right;\">\n",
- " <th></th>\n",
- " <th>speed</th>\n",
- " <th>mech power</th>\n",
- " <th>torque</th>\n",
- " <th>rail pressure</th>\n",
- " <th>fuel flow rate</th>\n",
- " <th>fluid power</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1000.0</td>\n",
- " <td>nan</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>nan</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>1100.0</td>\n",
- " <td>nan</td>\n",
- " <td>10.0</td>\n",
- " <td>100000000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>nan</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>1200.0</td>\n",
- " <td>nan</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>nan</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>1200.0</td>\n",
- " <td>nan</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>nan</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df_ = df.pint.quantify(level=-1)\n",
"df_"
@@ -690,208 +280,27 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": null,
"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"
- }
- ],
+ "outputs": [],
"source": [
"df_.speed * df_.torque"
]
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead th {\n",
- " text-align: right;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr style=\"text-align: right;\">\n",
- " <th></th>\n",
- " <th>speed</th>\n",
- " <th>mech power</th>\n",
- " <th>torque</th>\n",
- " <th>rail pressure</th>\n",
- " <th>fuel flow rate</th>\n",
- " <th>fluid power</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1000.0</td>\n",
- " <td>nan</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>nan</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>1100.0</td>\n",
- " <td>nan</td>\n",
- " <td>10.0</td>\n",
- " <td>100000000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>nan</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>1200.0</td>\n",
- " <td>nan</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>nan</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>1200.0</td>\n",
- " <td>nan</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>nan</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df_"
]
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead th {\n",
- " text-align: right;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr style=\"text-align: right;\">\n",
- " <th></th>\n",
- " <th>speed</th>\n",
- " <th>mech power</th>\n",
- " <th>torque</th>\n",
- " <th>rail pressure</th>\n",
- " <th>fuel flow rate</th>\n",
- " <th>fluid power</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1000.0</td>\n",
- " <td>10000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>10000.0</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>1100.0</td>\n",
- " <td>11000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>100000000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1000000000.0</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>1200.0</td>\n",
- " <td>12000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>10000.0</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>1200.0</td>\n",
- " <td>12000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>10000.0</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df_['mech power'] = df_.speed * df_.torque\n",
"df_['fluid power'] = df_['fuel flow rate'] * df_['rail pressure']\n",
@@ -907,109 +316,9 @@
},
{
"cell_type": "code",
- "execution_count": 18,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead tr th {\n",
- " text-align: left;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr>\n",
- " <th></th>\n",
- " <th>speed</th>\n",
- " <th>mech power</th>\n",
- " <th>torque</th>\n",
- " <th>rail pressure</th>\n",
- " <th>fuel flow rate</th>\n",
- " <th>fluid power</th>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>unit</th>\n",
- " <th>revolutions_per_minute</th>\n",
- " <th>meter * newton * revolutions_per_minute</th>\n",
- " <th>meter * newton</th>\n",
- " <th>bar</th>\n",
- " <th>liter / minute</th>\n",
- " <th>bar * liter / minute</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1000.0</td>\n",
- " <td>10000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.000000e+04</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>1100.0</td>\n",
- " <td>11000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>100000000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.000000e+09</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>1200.0</td>\n",
- " <td>12000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.000000e+04</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>1200.0</td>\n",
- " <td>12000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.000000e+04</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df_.pint.dequantify()"
]
@@ -1023,109 +332,9 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead tr th {\n",
- " text-align: left;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr>\n",
- " <th></th>\n",
- " <th>speed</th>\n",
- " <th>mech power</th>\n",
- " <th>torque</th>\n",
- " <th>rail pressure</th>\n",
- " <th>fuel flow rate</th>\n",
- " <th>fluid power</th>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>unit</th>\n",
- " <th>revolutions_per_minute</th>\n",
- " <th>kilowatt</th>\n",
- " <th>meter * newton</th>\n",
- " <th>bar</th>\n",
- " <th>liter / minute</th>\n",
- " <th>kilowatt</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1000.0</td>\n",
- " <td>1.047198</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.666667e+01</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>1100.0</td>\n",
- " <td>1.151917</td>\n",
- " <td>10.0</td>\n",
- " <td>100000000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.666667e+06</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>1200.0</td>\n",
- " <td>1.256637</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.666667e+01</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>1200.0</td>\n",
- " <td>1.256637</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.666667e+01</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df_['fluid power'] = df_['fluid power'].pint.to(\"kW\")\n",
"df_['mech power'] = df_['mech power'].pint.to(\"kW\")\n",
@@ -1141,104 +350,11 @@
},
{
"cell_type": "code",
- "execution_count": 20,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead tr th {\n",
- " text-align: left;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr>\n",
- " <th></th>\n",
- " <th>speed</th>\n",
- " <th>mech power</th>\n",
- " <th>torque</th>\n",
- " <th>rail pressure</th>\n",
- " <th>fuel flow rate</th>\n",
- " <th>fluid power</th>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>unit</th>\n",
- " <th>rpm</th>\n",
- " <th>kW</th>\n",
- " <th>N·m</th>\n",
- " <th>bar</th>\n",
- " <th>l/min</th>\n",
- " <th>kW</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1000.0</td>\n",
- " <td>1.047198</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.666667e+01</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>1100.0</td>\n",
- " <td>1.151917</td>\n",
- " <td>10.0</td>\n",
- " <td>100000000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.666667e+06</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>1200.0</td>\n",
- " <td>1.256637</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.666667e+01</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>1200.0</td>\n",
- " <td>1.256637</td>\n",
- " <td>10.0</td>\n",
- " <td>1000.0</td>\n",
- " <td>10.0</td>\n",
- " <td>1.666667e+01</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
- "pintpandas.PintType.ureg.default_format = \"~P\"\n",
+ "pint_pandas.PintType.ureg.default_format = \"~P\"\n",
"df_.pint.dequantify()"
]
},
@@ -1251,109 +367,9 @@
},
{
"cell_type": "code",
- "execution_count": 21,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead tr th {\n",
- " text-align: left;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr>\n",
- " <th></th>\n",
- " <th>speed</th>\n",
- " <th>mech power</th>\n",
- " <th>torque</th>\n",
- " <th>rail pressure</th>\n",
- " <th>fuel flow rate</th>\n",
- " <th>fluid power</th>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>unit</th>\n",
- " <th>rad/s</th>\n",
- " <th>kg·m²/s³</th>\n",
- " <th>kg·m²/s²</th>\n",
- " <th>kg/m/s²</th>\n",
- " <th>m³/s</th>\n",
- " <th>kg·m²/s³</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>104.719755</td>\n",
- " <td>1047.197551</td>\n",
- " <td>10.0</td>\n",
- " <td>1.000000e+08</td>\n",
- " <td>0.000167</td>\n",
- " <td>1.666667e+04</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>115.191731</td>\n",
- " <td>1151.917306</td>\n",
- " <td>10.0</td>\n",
- " <td>1.000000e+13</td>\n",
- " <td>0.000167</td>\n",
- " <td>1.666667e+09</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>2</th>\n",
- " <td>125.663706</td>\n",
- " <td>1256.637061</td>\n",
- " <td>10.0</td>\n",
- " <td>1.000000e+08</td>\n",
- " <td>0.000167</td>\n",
- " <td>1.666667e+04</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>3</th>\n",
- " <td>125.663706</td>\n",
- " <td>1256.637061</td>\n",
- " <td>10.0</td>\n",
- " <td>1.000000e+08</td>\n",
- " <td>0.000167</td>\n",
- " <td>1.666667e+04</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df_.pint.to_base_units().pint.dequantify()"
]
@@ -1370,12 +386,13 @@
},
{
"cell_type": "code",
- "execution_count": 22,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd \n",
- "import pint"
+ "import pint\n",
+ "import pint_pandas"
]
},
{
@@ -1387,11 +404,11 @@
},
{
"cell_type": "code",
- "execution_count": 23,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
- "PA_ = pintpandas.PintArray"
+ "PA_ = pint_pandas.PintArray"
]
},
{
@@ -1403,7 +420,7 @@
},
{
"cell_type": "code",
- "execution_count": 24,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -1420,11 +437,11 @@
},
{
"cell_type": "code",
- "execution_count": 25,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
- "pintpandas.PintType.ureg = ureg"
+ "pint_pandas.PintType.ureg = ureg"
]
},
{
@@ -1438,69 +455,9 @@
},
{
"cell_type": "code",
- "execution_count": 26,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "<div>\n",
- "<style scoped>\n",
- " .dataframe tbody tr th:only-of-type {\n",
- " vertical-align: middle;\n",
- " }\n",
- "\n",
- " .dataframe tbody tr th {\n",
- " vertical-align: top;\n",
- " }\n",
- "\n",
- " .dataframe thead th {\n",
- " text-align: right;\n",
- " }\n",
- "</style>\n",
- "<table border=\"1\" class=\"dataframe\">\n",
- " <thead>\n",
- " <tr style=\"text-align: right;\">\n",
- " <th></th>\n",
- " <th>length</th>\n",
- " <th>width</th>\n",
- " <th>distance</th>\n",
- " <th>height</th>\n",
- " <th>depth</th>\n",
- " </tr>\n",
- " </thead>\n",
- " <tbody>\n",
- " <tr>\n",
- " <th>0</th>\n",
- " <td>1</td>\n",
- " <td>2</td>\n",
- " <td>2</td>\n",
- " <td>2</td>\n",
- " <td>2</td>\n",
- " </tr>\n",
- " <tr>\n",
- " <th>1</th>\n",
- " <td>2</td>\n",
- " <td>3</td>\n",
- " <td>3</td>\n",
- " <td>3</td>\n",
- " <td>3</td>\n",
- " </tr>\n",
- " </tbody>\n",
- "</table>\n",
- "</div>"
- ],
- "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"
- }
- ],
+ "outputs": [],
"source": [
"df = pd.DataFrame({\n",
" \"length\" : pd.Series([1,2], dtype=\"pint[m]\"),\n",
@@ -1514,26 +471,9 @@
},
{
"cell_type": "code",
- "execution_count": 27,
+ "execution_count": null,
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "meter"
- ],
- "text/latex": [
- "$\\mathrm{meter}$"
- ],
- "text/plain": [
- "<Unit('meter')>"
- ]
- },
- "execution_count": 27,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"df.length.values.units"
]
@@ -1556,7 +496,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.6.5"
+ "version": "3.7.6"
}
},
"nbformat": 4,
diff --git a/docs/pitheorem.rst b/docs/pitheorem.rst
index a2513d5..cd37165 100644
--- a/docs/pitheorem.rst
+++ b/docs/pitheorem.rst
@@ -73,7 +73,7 @@ There are 3 fundamental physical units in this equation: time, mass, and length,
... 'M': '[mass]',
... 'L': '[length]',
... 'g': '[acceleration]'})
- [{'T': 2.0, 'g': 1.0, 'L': -1.0}]
+ [{'T': 2.0, 'L': -1.0, 'g': 1.0}]
which means that the dimensionless quantity is:
diff --git a/docs/serialization.rst b/docs/serialization.rst
index 1ebd245..01fed50 100644
--- a/docs/serialization.rst
+++ b/docs/serialization.rst
@@ -10,10 +10,6 @@ deserialize the object.
The easiest way to do this is by converting the quantity to a string:
-.. testsetup:: *
-
- import pint
-
.. doctest::
>>> import pint
@@ -74,15 +70,19 @@ with the magnitude and the units:
>>> to_serialize = duration.to_tuple()
>>> print(to_serialize)
- (24.2, (('year', 1.0),))
+ (24.2, (('year', 1),))
And then you can just pickle that:
+.. doctest::
+
>>> import pickle
>>> serialized = pickle.dumps(to_serialize, -1)
To unpickle, just
+.. doctest::
+
>>> loaded = pickle.loads(serialized)
>>> ureg.Quantity.from_tuple(loaded)
<Quantity(24.2, 'year')>
@@ -97,6 +97,9 @@ numerical type such as `numpy.ndarray`).
Using the serialize_ package you can load and read from multiple formats:
+.. doctest::
+ :skipif: not_installed['serialize']
+
>>> from serialize import dump, load, register_class
>>> register_class(ureg.Quantity, ureg.Quantity.to_tuple, ureg.Quantity.from_tuple)
>>> dump(duration, 'output.yaml')
@@ -113,6 +116,3 @@ Using the serialize_ package you can load and read from multiple formats:
.. _hdf5: http://www.h5py.org/
.. _PyTables: http://www.pytables.org
.. _dill: https://pypi.python.org/pypi/dill
-
-
-
diff --git a/docs/systems.rst b/docs/systems.rst
index 7c095c5..d4a175d 100644
--- a/docs/systems.rst
+++ b/docs/systems.rst
@@ -5,6 +5,8 @@ Different Unit Systems (and default units)
Pint Unit Registry has the concept of system, which is a group of units
+.. doctest::
+
>>> import pint
>>> ureg = pint.UnitRegistry(system='mks')
>>> ureg.default_system
@@ -12,56 +14,54 @@ Pint Unit Registry has the concept of system, which is a group of units
This has an effect in the base units. For example:
+.. doctest::
+
>>> q = 3600. * ureg.meter / ureg.hour
>>> q.to_base_units()
<Quantity(1.0, 'meter / second')>
But if you change to cgs:
+.. doctest::
+
>>> ureg.default_system = 'cgs'
>>> q.to_base_units()
<Quantity(100.0, 'centimeter / second')>
or more drastically to:
+.. doctest::
+
>>> ureg.default_system = 'imperial'
>>> '{:.3f}'.format(q.to_base_units())
'1.094 yard / second'
-.. warning:: In versions previous to 0.7 ``to_base_units`` returns quantities in the
+.. 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``,
+ bundled with pint this is meter/gram/second. To get back this behaviour use ``to_root_units()``,
set ``ureg.system = None``
-
-You can also use system to narrow down the list of compatible units:
-
- >>> ureg.default_system = 'mks'
- >>> ureg.get_compatible_units('meter')
- frozenset({<Unit('light_year')>, <Unit('angstrom')>})
-
-or for imperial units:
-
- >>> ureg.default_system = 'imperial'
- >>> ureg.get_compatible_units('meter')
- frozenset({<Unit('thou')>, <Unit('league')>, <Unit('nautical_mile')>, <Unit('inch')>, <Unit('mile')>, <Unit('yard')>, <Unit('foot')>})
-
-
You can check which unit systems are available:
+.. doctest::
+
>>> dir(ureg.sys)
- ['US', 'cgs', 'imperial', 'mks']
+ ['Planck', 'SI', 'US', 'atomic', 'cgs', 'imperial', 'mks']
Or which units are available within a particular system:
+.. doctest::
+
>>> dir(ureg.sys.imperial)
- ['UK_hundredweight', 'UK_ton', 'acre_foot', 'cubic_foot', 'cubic_inch', 'cubic_yard', 'drachm', 'foot', 'grain', 'imperial_barrel', 'imperial_bushel', 'imperial_cup', 'imperial_fluid_drachm', 'imperial_fluid_ounce', 'imperial_gallon', 'imperial_gill', 'imperial_peck', 'imperial_pint', 'imperial_quart', 'inch', 'long_hunderweight', 'long_ton', 'mile', 'ounce', 'pound', 'quarter', 'short_hunderdweight', 'short_ton', 'square_foot', 'square_inch', 'square_mile', 'square_yard', 'stone', 'yard']
+ ['UK_force_ton', 'UK_hundredweight', ... 'cubic_foot', 'cubic_inch', ... 'thou', 'ton', 'yard']
Notice that this give you the opportunity to choose within units with colliding names:
+.. doctest::
+
>>> (1 * ureg.sys.imperial.pint).to('liter')
- <Quantity(0.5682612500000002, 'liter')>
+ <Quantity(0.568261..., 'liter')>
>>> (1 * ureg.sys.US.pint).to('liter')
- <Quantity(0.47317647300000004, 'liter')>
+ <Quantity(0.473176..., 'liter')>
>>> (1 * ureg.sys.US.pint).to(ureg.sys.imperial.pint)
- <Quantity(0.8326741846289889, 'imperial_pint')>
+ <Quantity(0.832674..., 'imperial_pint')>
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 9693444..e473ac4 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -1,68 +1,111 @@
-.. _tutorial:
-
Tutorial
========
-Converting Quantities
----------------------
+Follow the steps below and learn how to use Pint to track physical quantities
+and perform unit conversions in Python.
+
+Initializing a Registry
+-----------------------
-Pint has the concept of Unit Registry, an object within which units are defined
-and handled. You start by creating your registry:
+Before using Pint, initialize a :class:`UnitRegistry() <pint.registry.UnitRegistry>`
+object. The ``UnitRegistry`` stores the unit definitions, their relationships,
+and handles conversions between units.
+
+.. doctest::
>>> from pint import UnitRegistry
>>> ureg = UnitRegistry()
-.. testsetup:: *
-
- from pint import UnitRegistry
- ureg = UnitRegistry()
- Q_ = ureg.Quantity
+If no parameters are given to the constructor, the ``UnitRegistry`` is populated
+with the `default list of units`_ and prefixes.
+Defining a Quantity
+-------------------
-If no parameter is given to the constructor, the unit registry is populated
-with the default list of units and prefixes.
-You can now simply use the registry in the following way:
+Once you've initialized your ``UnitRegistry``, you can define quantities easily:
.. doctest::
>>> distance = 24.0 * ureg.meter
+ >>> distance
+ <Quantity(24.0, 'meter')>
>>> print(distance)
24.0 meter
- >>> time = 8.0 * ureg.second
- >>> print(time)
- 8.0 second
- >>> print(repr(time))
- <Quantity(8.0, 'second')>
-In this code `distance` and `time` are physical quantity objects (`Quantity`).
-Physical quantities can be queried for their magnitude, units, and
-dimensionality:
+As you can see, ``distance`` here is a :class:`Quantity() <pint.quantity.Quantity>`
+object that represents a physical quantity. Quantities can be queried for their
+magnitude, units, and dimensionality:
.. doctest::
- >>> print(distance.magnitude)
+ >>> distance.magnitude
24.0
- >>> print(distance.units)
- meter
+ >>> distance.units
+ <Unit('meter')>
>>> print(distance.dimensionality)
[length]
-and can handle mathematical operations between:
+and can correctly handle many mathematical operations, including with other
+:class:`Quantity() <pint.quantity.Quantity>` objects:
.. doctest::
+ >>> time = 8.0 * ureg.second
+ >>> print(time)
+ 8.0 second
>>> speed = distance / time
+ >>> speed
+ <Quantity(3.0, 'meter / second')>
>>> print(speed)
3.0 meter / second
+ >>> print(speed.dimensionality)
+ [length] / [time]
+
+Notice the built-in parser recognizes prefixed and pluralized units even though
+they are not in the definition list:
+
+.. doctest::
+
+ >>> distance = 42 * ureg.kilometers
+ >>> print(distance)
+ 42 kilometer
+ >>> print(distance.to(ureg.meter))
+ 42000.0 meter
+
+Pint will complain if you try to use a unit which is not in the registry:
+
+.. doctest::
+
+ >>> speed = 23 * ureg.snail_speed
+ Traceback (most recent call last):
+ ...
+ UndefinedUnitError: 'snail_speed' is not defined in the unit registry
+
+You can add your own units to the existing registry, or build your own list.
+See the page on :ref:`defining` for more information on that.
+
+See `String parsing`_ and :doc:`defining-quantities` for more ways of defining
+a ``Quantity()`` object.
+
+``Quantity()`` objects also work well with NumPy arrays, which you can
+read about in the section on :doc:`NumPy support <numpy>`.
-As unit registry knows about the relationship between different units, you can
-convert quantities to the unit of choice:
+Converting to Different Units
+-----------------------------
+
+As the underlying ``UnitRegistry`` knows the relationships between
+different units, you can convert a ``Quantity`` to the units of your choice using
+the ``to()`` method, which accepts a string or a :class:`Unit() <pint.unit.Unit>` object:
.. doctest::
- >>> speed.to(ureg.inch / ureg.minute )
- <Quantity(7086.614173228345, 'inch / minute')>
+ >>> speed.to('inch/minute')
+ <Quantity(7086.61417, 'inch / minute')>
+ >>> ureg.inch / ureg.minute
+ <Unit('inch / minute')>
+ >>> speed.to(ureg.inch / ureg.minute)
+ <Quantity(7086.61417, 'inch / minute')>
This method returns a new object leaving the original intact as can be seen by:
@@ -72,17 +115,18 @@ This method returns a new object leaving the original intact as can be seen by:
3.0 meter / second
If you want to convert in-place (i.e. without creating another object), you can
-use the `ito` method:
+use the ``ito()`` method:
.. doctest::
- >>> speed.ito(ureg.inch / ureg.minute )
+ >>> speed.ito(ureg.inch / ureg.minute)
>>> speed
- <Quantity(7086.614173228345, 'inch / minute')>
+ <Quantity(7086.61417, 'inch / minute')>
>>> print(speed)
- 7086.614173228345 inch / minute
+ 7086.6141... inch / minute
-If you ask Pint to perform an invalid conversion:
+Pint will complain if you ask it to perform a conversion it doesn't know
+how to do:
.. doctest::
@@ -91,9 +135,15 @@ If you ask Pint to perform an invalid conversion:
...
DimensionalityError: Cannot convert from 'inch / minute' ([length] / [time]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2)
+See the section on :doc:`contexts` for information about expanding Pint's
+automatic conversion capabilities for your application.
+
+Simplifying units
+-----------------
+
Sometimes, the magnitude of the quantity will be very large or very small.
-The method 'to_compact' can adjust the units to make the quantity more
-human-readable.
+The method ``to_compact()`` can adjust the units to make a quantity more
+human-readable:
.. doctest::
@@ -102,9 +152,9 @@ human-readable.
>>> print(frequency)
193414489032258.03 hertz
>>> print(frequency.to_compact())
- 193.41448903225802 terahertz
+ 193.414489032... terahertz
-There are also methods 'to_base_units' and 'ito_base_units' which automatically
+There are also methods ``to_base_units()`` and ``ito_base_units()`` which automatically
convert to the reference units with the correct dimensionality:
.. doctest::
@@ -113,14 +163,14 @@ convert to the reference units with the correct dimensionality:
>>> print(height)
5.75 foot
>>> print(height.to_base_units())
- 1.7526 meter
+ 1.752... meter
>>> print(height)
5.75 foot
>>> height.ito_base_units()
>>> print(height)
- 1.7526 meter
+ 1.752... meter
-There are also methods 'to_reduced_units' and 'ito_reduced_units' which perform
+There are also methods ``to_reduced_units()`` and ``ito_reduced_units()`` which perform
a simplified dimensional reduction, combining units with the same dimensionality
but otherwise keeping your unit definitions intact.
@@ -130,88 +180,44 @@ but otherwise keeping your unit definitions intact.
>>> volume = 10*ureg.cc
>>> mass = density*volume
>>> print(mass)
- 14.0 cc * gram / centimeter ** 3
+ 14.0 cubic_centimeter * gram / centimeter ** 3
>>> print(mass.to_reduced_units())
14.0 gram
>>> print(mass)
- 14.0 cc * gram / centimeter ** 3
+ 14.0 cubic_centimeter * gram / centimeter ** 3
>>> mass.ito_reduced_units()
>>> print(mass)
14.0 gram
If you want pint to automatically perform dimensional reduction when producing
-new quantities, the UnitRegistry accepts a parameter `auto_reduce_dimensions`.
+new quantities, the ``UnitRegistry`` class accepts a parameter ``auto_reduce_dimensions``.
Dimensional reduction can be slow, so auto-reducing is disabled by default.
-In some cases it is useful to define physical quantities objects using the
-class constructor:
-
-.. doctest::
-
- >>> Q_ = ureg.Quantity
- >>> Q_(1.78, ureg.meter) == 1.78 * ureg.meter
- True
-
-(I tend to abbreviate Quantity as `Q_`) The built-in parser recognizes prefixed
-and pluralized units even though they are not in the definition list:
-
-.. doctest::
-
- >>> distance = 42 * ureg.kilometers
- >>> print(distance)
- 42 kilometer
- >>> print(distance.to(ureg.meter))
- 42000.0 meter
-
-If you try to use a unit which is not in the registry:
-
-.. doctest::
-
- >>> speed = 23 * ureg.snail_speed
- Traceback (most recent call last):
- ...
- UndefinedUnitError: 'snail_speed' is not defined in the unit registry
-
-You can add your own units to the registry or build your own list. More info on
-that :ref:`defining`
-
-
String parsing
--------------
-Pint can also handle units provided as strings:
-
-.. doctest::
-
- >>> 2.54 * ureg.parse_expression('centimeter')
- <Quantity(2.54, 'centimeter')>
-
-or using the registry as a callable for a short form for `parse_expression`:
+Pint includes powerful string parsing for identifying magnitudes and units. In
+many cases, units can be defined as strings:
.. doctest::
>>> 2.54 * ureg('centimeter')
<Quantity(2.54, 'centimeter')>
-or using the `Quantity` constructor:
+or using the ``Quantity`` constructor:
.. doctest::
+ >>> Q_ = ureg.Quantity
>>> Q_(2.54, 'centimeter')
<Quantity(2.54, 'centimeter')>
-
Numbers are also parsed, so you can use an expression:
.. doctest::
>>> ureg('2.54 * centimeter')
<Quantity(2.54, 'centimeter')>
-
-or:
-
-.. doctest::
-
>>> Q_('2.54 * centimeter')
<Quantity(2.54, 'centimeter')>
@@ -231,64 +237,25 @@ This enables you to build a simple unit converter in 3 lines:
>>> Q_(src).to(dst)
<Quantity(1.0, 'inch')>
-Dimensionless quantities can also be parsed into an appropriate object:
-
-.. doctest::
-
- >>> ureg('2.54')
- 2.54
- >>> type(ureg('2.54'))
- <class 'float'>
-
-or
-
-.. doctest::
-
- >>> Q_('2.54')
- <Quantity(2.54, 'dimensionless')>
- >>> type(Q_('2.54'))
- <class 'pint.quantity.build_quantity_class.<locals>.Quantity'>
-
-.. note:: Pint´s rule for parsing strings with a mixture of numbers and
- units is that **units are treated with the same precedence as numbers**.
-
-For example, the unit of
-
-.. doctest::
-
- >>> Q_('3 l / 100 km')
- <Quantity(0.03, 'kilometer * liter')>
-
-may be unexpected first but is a consequence of applying this rule. Use
-brackets to get the expected result:
-
-.. doctest::
-
- >>> Q_('3 l / (100 km)')
- <Quantity(0.03, 'liter / kilometer')>
-
-.. note:: Since version 0.7, Pint **does not** use eval_ under the hood.
- This change removes the `serious security problems`_ that the system is
- exposed to when parsing information from untrusted sources.
-
-
-Strings containing values can be parsed using the ``ureg.parse_pattern`` function. A ``format``-like string with the units defined in it is used as the pattern:
+Strings containing values can be parsed using the ``ureg.parse_pattern()`` function.
+A ``format``-like string with the units defined in it is used as the pattern:
.. doctest::
>>> input_string = '10 feet 10 inches'
>>> pattern = '{feet} feet {inch} inches'
>>> ureg.parse_pattern(input_string, pattern)
- [10.0 <Unit('foot')>, 10.0 <Unit('inch')>]
+ [<Quantity(10.0, 'foot')>, <Quantity(10.0, 'inch')>]
-To search for multiple matches, set the ``many`` parameter to ``True``. The following example also demonstrates how the parser is able to find matches in amongst filler characters:
+To search for multiple matches, set the ``many`` parameter to ``True``. The following
+example also demonstrates how the parser is able to find matches in amongst filler characters:
.. doctest::
>>> input_string = '10 feet - 20 feet ! 30 feet.'
>>> pattern = '{feet} feet'
>>> ureg.parse_pattern(input_string, pattern, many=True)
- [[10.0 <Unit('foot')>], [20.0 <Unit('foot')>], [30.0 <Unit('foot')>]]
+ [[<Quantity(10.0, 'foot')>], [<Quantity(20.0, 'foot')>], [<Quantity(30.0, 'foot')>]]
The full power of regex can also be employed when writing patterns:
@@ -297,11 +264,12 @@ The full power of regex can also be employed when writing patterns:
>>> input_string = "10` - 20 feet ! 30 ft."
>>> pattern = r"{feet}(`| feet| ft)"
>>> ureg.parse_pattern(input_string, pattern, many=True)
- [[10.0 <Unit('foot')>], [20.0 <Unit('foot')>], [30.0 <Unit('foot')>]]
+ [[<Quantity(10.0, 'foot')>], [<Quantity(20.0, 'foot')>], [<Quantity(30.0, 'foot')>]]
*Note that the curly brackets (``{}``) are converted to a float-matching pattern by the parser.*
-This function is useful for tasks such as bulk extraction of units from thousands of uniform strings or even very large texts with units dotted around in no particular pattern.
+This function is useful for tasks such as bulk extraction of units from thousands
+of uniform strings or even very large texts with units dotted around in no particular pattern.
.. _sec-string-formatting:
@@ -328,6 +296,7 @@ Pint supports float formatting for numpy arrays as well:
.. doctest::
+ >>> import numpy as np
>>> accel = np.array([-1.1, 1e-6, 1.2505, 1.3]) * ureg['meter/second**2']
>>> # float formatting numpy arrays
>>> print('The array is {:.2f}'.format(accel))
@@ -336,7 +305,7 @@ Pint supports float formatting for numpy arrays as well:
>>> 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 :
+Pint also supports `f-strings`_ from python>=3.6 :
.. doctest::
@@ -349,8 +318,8 @@ Pint also supports 'f-strings'_ from python>=3.6 :
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²
+ >>> print(f'The str is {accel:~H}') # HTML format (displays well in Jupyter)
+ The str is \[1.3\ m/{s}^{2}\]
But Pint also extends the standard formatting capabilities for unicode and
LaTeX representations:
@@ -361,12 +330,12 @@ LaTeX representations:
>>> # Pretty print
>>> 'The pretty representation is {:P}'.format(accel)
'The pretty representation is 1.3 meter/second²'
- >>> # Latex print
- >>> 'The latex representation is {:L}'.format(accel)
- 'The latex representation is 1.3\\ \\frac{\\mathrm{meter}}{\\mathrm{second}^{2}}'
- >>> # HTML print
+ >>> # LaTeX print
+ >>> 'The LaTeX representation is {:L}'.format(accel)
+ 'The LaTeX representation is 1.3\\ \\frac{\\mathrm{meter}}{\\mathrm{second}^{2}}'
+ >>> # HTML print - good for Jupyter notebooks
>>> 'The HTML representation is {:H}'.format(accel)
- 'The HTML representation is 1.3 meter/second<sup>2</sup>'
+ 'The HTML representation is \\[1.3\\ meter/{second}^{2}\\]'
If you want to use abbreviated unit names, prefix the specification with `~`:
@@ -378,33 +347,40 @@ If you want to use abbreviated unit names, prefix the specification with `~`:
'The pretty representation is 1.3 m/s²'
-The same is true for latex (`L`) and HTML (`H`) specs.
+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:
+The formatting specs (ie 'L', 'H', 'P') can be used with Python string
+`formatting syntax`_ for custom float representations. For example, scientific
+notation:
+
+.. doctest::
-..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:
+Pint also supports the LaTeX `siunitx` package:
.. doctest::
+ :skipif: not_installed['uncertainties']
>>> accel = 1.3 * ureg['meter/second**2']
>>> # siunitx Latex print
>>> print('The siunitx representation is {:Lx}'.format(accel))
The siunitx representation is \SI[]{1.3}{\meter\per\second\squared}
+ >>> accel = accel.plus_minus(0.2)
+ >>> print('The siunitx representation is {:Lx}'.format(accel))
+ The siunitx representation is \SI{1.30 +- 0.20}{\meter\per\second\squared}
Additionally, you can specify a default format specification:
.. doctest::
+ >>> accel = 1.3 * ureg['meter/second**2']
>>> 'The acceleration is {}'.format(accel)
'The acceleration is 1.3 meter / second ** 2'
>>> ureg.default_format = 'P'
@@ -412,17 +388,40 @@ Additionally, you can specify a default format specification:
'The acceleration is 1.3 meter/second²'
-Finally, if Babel_ is installed you can translate unit names to any language
+Localizing
+----------
+
+If Babel_ is installed you can translate unit names to any language
.. doctest::
>>> accel.format_babel(locale='fr_FR')
'1.3 mètre par seconde²'
-You can also specify the format locale at u
+You can also specify the format locale at the registry level either at creation:
+
+.. doctest::
>>> ureg = UnitRegistry(fmt_locale='fr_FR')
+or later:
+
+.. doctest::
+
+ >>> ureg.set_fmt_locale('fr_FR')
+
+and by doing that, string formatting is now localized:
+
+.. doctest::
+
+ >>> accel = 1.3 * ureg['meter/second**2']
+ >>> str(accel)
+ '1.3 mètre par seconde²'
+ >>> "%s" % accel
+ '1.3 mètre par seconde²'
+ >>> "{}".format(accel)
+ '1.3 mètre par seconde²'
+
Using Pint in your projects
---------------------------
@@ -430,14 +429,18 @@ Using Pint in your projects
If you use Pint in multiple modules within your Python package, you normally
want to avoid creating multiple instances of the unit registry.
The best way to do this is by instantiating the registry in a single place. For
-example, you can add the following code to your package `__init__.py`::
+example, you can add the following code to your package ``__init__.py``
+
+.. code-block::
from pint import UnitRegistry
ureg = UnitRegistry()
Q_ = ureg.Quantity
-Then in `yourmodule.py` the code would be::
+Then in ``yourmodule.py`` the code would be
+
+.. code-block::
from . import ureg, Q_
@@ -445,14 +448,19 @@ Then in `yourmodule.py` the code would be::
my_speed = Q_(20, 'm/s')
If you are pickling and unplicking Quantities within your project, you should
-also define the registry as the application registry::
+also define the registry as the application registry
+
+.. code-block::
from pint import UnitRegistry, set_application_registry
ureg = UnitRegistry()
set_application_registry(ureg)
-.. warning:: There are no global units in Pint. All units belong to a registry and you can have multiple registries instantiated at the same time. However, you are not supposed to operate between quantities that belong to different registries. Never do things like this:
+.. warning:: There are no global units in Pint. All units belong to a registry and
+ you can have multiple registries instantiated at the same time. However, you
+ are not supposed to operate between quantities that belong to different registries.
+ Never do things like this:
.. doctest::
@@ -466,8 +474,7 @@ also define the registry as the application registry::
False
-.. _eval: http://docs.python.org/3/library/functions.html#eval
-.. _`serious security problems`: http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
+.. _`default list of units`: https://github.com/hgrecco/pint/blob/master/pint/default_en.txt
.. _`Babel`: http://babel.pocoo.org/
-.. _'formatting syntax': https://docs.python.org/3/library/string.html#format-specification-mini-language
-.. _'f-strings': https://www.python.org/dev/peps/pep-0498/
+.. _`formatting syntax`: https://docs.python.org/3/library/string.html#format-specification-mini-language
+.. _`f-strings`: https://www.python.org/dev/peps/pep-0498/
diff --git a/docs/wrapping.rst b/docs/wrapping.rst
index 7b56dcc..d04b0ba 100644
--- a/docs/wrapping.rst
+++ b/docs/wrapping.rst
@@ -13,19 +13,19 @@ requires you to provide numerical values in certain units:
.. testsetup:: *
- import math
- G = 9.806650
- def pendulum_period(length):
- return 2*math.pi*math.sqrt(length/G)
+ import math
+ G = 9.806650
+ def pendulum_period(length):
+ return 2*math.pi*math.sqrt(length/G)
- def pendulum_period2(length, swing_amplitude):
- pass
+ def pendulum_period2(length, swing_amplitude):
+ pass
- def pendulum_period_maxspeed(length, swing_amplitude):
- pass
+ def pendulum_period_maxspeed(length, swing_amplitude):
+ pass
- def pendulum_period_error(length):
- pass
+ def pendulum_period_error(length):
+ pass
.. doctest::
@@ -47,6 +47,7 @@ You could wrap this function to use Quantities instead:
>>> from pint import UnitRegistry
>>> ureg = UnitRegistry()
+ >>> Q_ = ureg.Quantity
>>> def mypp_caveman(length):
... return pendulum_period(length.to(ureg.meter).magnitude) * ureg.second
@@ -55,7 +56,7 @@ and:
.. doctest::
>>> mypp_caveman(100 * ureg.centimeter)
- <Quantity(2.0064092925890407, 'second')>
+ <Quantity(2.00640929, 'second')>
Pint provides a more convenient way to do this:
@@ -71,7 +72,7 @@ Or in the decorator format:
... def mypp(length):
... return pendulum_period(length)
>>> mypp(100 * ureg.centimeter)
- <Quantity(2.0064092925890407, 'second')>
+ <Quantity(2.00640929, 'second')>
`wraps` takes 3 input arguments:
@@ -94,7 +95,7 @@ the input arguments assigned to units must be a Quantities.
.. doctest::
>>> mypp(1. * ureg.meter)
- <Quantity(2.0064092925890407, 'second')>
+ <Quantity(2.00640929, 'second')>
>>> mypp(1.)
Traceback (most recent call last):
...
@@ -106,9 +107,9 @@ To enable using non-Quantity numerical values, set strict to False`.
>>> mypp_ns = ureg.wraps(ureg.second, ureg.meter, False)(pendulum_period)
>>> mypp_ns(1. * ureg.meter)
- <Quantity(2.0064092925890407, 'second')>
+ <Quantity(2.00640929, 'second')>
>>> mypp_ns(1.)
- <Quantity(2.0064092925890407, 'second')>
+ <Quantity(2.00640929, 'second')>
In this mode, the value is assumed to have the correct units.
@@ -142,7 +143,9 @@ Or if the function has multiple outputs:
If there are more return values than specified units, ``None`` is assumed for
the extra outputs. For example, given the NREL SOLPOS calculator that outputs
solar zenith, azimuth and air mass, the following wrapper assumes no units for
-airmass::
+airmass
+
+.. doctest::
@ureg.wraps(('deg', 'deg'), ('deg', 'deg', 'millibar', 'degC'))
def solar_position(lat, lon, press, tamb, timestamp):
@@ -156,7 +159,7 @@ arguments:
.. doctest::
- >>> @ureg.wraps(ureg.second, (ureg.meters, ureg.meters/ureg.second**2))
+ >>> @ureg.wraps(ureg.second, (ureg.meters, ureg.meters/ureg.second**2, None))
... def calculate_time_to_fall(height, gravity=Q_(9.8, 'm/s^2'), verbose=False):
... """Calculate time to fall from a height h.
...
@@ -166,18 +169,17 @@ arguments:
... d = .5 * g * t**2
... t = sqrt(2 * d / g)
... """
- ... t = sqrt(2 * height / gravity)
+ ... t = math.sqrt(2 * height / gravity)
... if verbose: print(str(t) + " seconds to fall")
... return t
...
>>> lunar_module_height = Q_(22, 'feet') + Q_(11, 'inches')
>>> calculate_time_to_fall(lunar_module_height, verbose=True)
1.1939473204801092 seconds to fall
- <Quantity(1.1939473204801092, 'second')>
- >>>
+ <Quantity(1.19394732, 'second')>
>>> moon_gravity = Q_(1.625, 'm/s^2')
- >>> tcalculate_time_to_fall(lunar_module_height, moon_gravity)
- <Quantity(2.932051001760214, 'second')>
+ >>> calculate_time_to_fall(lunar_module_height, gravity=moon_gravity)
+ <Quantity(2.932051, 'second')>
Specifying relations between arguments
@@ -199,6 +201,8 @@ has the unit of `x` squared (`A**2`)
You can use more than one label:
+.. doctest::
+
>>> @ureg.wraps('=A**2*B', ('=A', '=A*B', '=B'))
... def some_function(x, y, z):
... pass
diff --git a/pint/__init__.py b/pint/__init__.py
index f693707..8bea195 100644
--- a/pint/__init__.py
+++ b/pint/__init__.py
@@ -13,8 +13,6 @@
import sys
-import pkg_resources
-
from .context import Context
from .errors import ( # noqa: F401
DefinitionSyntaxError,
@@ -33,18 +31,24 @@ from .unit import Unit
from .util import logger, pi_theorem
try:
- from pintpandas import PintArray, PintType
+ from importlib.metadata import version
+except ImportError:
+ # Backport for Python < 3.8
+ from importlib_metadata import version
+
+try:
+ from pint_pandas import PintArray, PintType
del PintType
del PintArray
- _HAS_PINTPANDAS = True
+ _HAS_PINT_PANDAS = True
except ImportError:
- _HAS_PINTPANDAS = False
- _, _pintpandas_error, _ = sys.exc_info()
+ _HAS_PINT_PANDAS = False
+ _, _pint_pandas_error, _ = sys.exc_info()
try: # pragma: no cover
- __version__ = pkg_resources.get_distribution("pint").version
+ __version__ = version("pint")
except Exception: # pragma: no cover
# we seem to have a local copy not installed without setuptools
# so the reported version will be unknown
diff --git a/pint/babel_names.py b/pint/babel_names.py
index 9f23b83..d3b14c2 100644
--- a/pint/babel_names.py
+++ b/pint/babel_names.py
@@ -6,7 +6,7 @@
:license: BSD, see LICENSE for more details.
"""
-from pint.compat import HAS_BABEL
+from .compat import HAS_BABEL
_babel_units = dict(
standard_gravity="acceleration-g-force",
diff --git a/pint/compat.py b/pint/compat.py
index a6dd0ef..9574554 100644
--- a/pint/compat.py
+++ b/pint/compat.py
@@ -7,7 +7,7 @@
:copyright: 2013 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
-import os
+import math
import tokenize
from decimal import Decimal
from io import BytesIO
@@ -37,29 +37,9 @@ 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 datetime64 as np_datetime64
from numpy import ndarray
HAS_NUMPY = True
@@ -93,12 +73,9 @@ try:
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
@@ -106,13 +83,14 @@ except ImportError:
class ndarray:
pass
+ class np_datetime64:
+ 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:
@@ -166,7 +144,7 @@ upcast_types = []
# pint-pandas (PintArray)
try:
- from pintpandas import PintArray
+ from pint_pandas import PintArray
upcast_types.append(PintArray)
except ImportError:
@@ -188,8 +166,16 @@ try:
except ImportError:
pass
+try:
+ from dask import array as dask_array
+ from dask.base import compute, persist, visualize
+
+except ImportError:
+ compute, persist, visualize = None, None, None
+ dask_array = None
+
-def is_upcast_type(other):
+def is_upcast_type(other) -> bool:
"""Check if the type object is a upcast type using preset list.
Parameters
@@ -203,29 +189,29 @@ def is_upcast_type(other):
return other in upcast_types
-def is_duck_array_type(other):
+def is_duck_array_type(cls) -> bool:
"""Check if the type object represents a (non-Quantity) duck array type.
Parameters
----------
- other : object
+ cls : class
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")
+ return issubclass(cls, ndarray) or (
+ not hasattr(cls, "_magnitude")
+ and not hasattr(cls, "_units")
and HAS_NUMPY_ARRAY_FUNCTION
- and hasattr(other, "__array_function__")
- and hasattr(other, "ndim")
- and hasattr(other, "dtype")
+ and hasattr(cls, "__array_function__")
+ and hasattr(cls, "ndim")
+ and hasattr(cls, "dtype")
)
-def eq(lhs, rhs, check_all):
+def eq(lhs, rhs, check_all: bool):
"""Comparison of scalars and arrays.
Parameters
@@ -235,13 +221,69 @@ def eq(lhs, rhs, check_all):
rhs : object
right-hand side
check_all : bool
- if True, reduce sequence to single bool.
+ if True, reduce sequence to single bool;
+ return True if all the elements are equal.
Returns
-------
bool or array_like of bool
"""
out = lhs == rhs
- if check_all and isinstance(out, ndarray):
- return np.all(out)
+ if check_all and is_duck_array_type(type(out)):
+ return out.all()
+ return out
+
+
+def isnan(obj, check_all: bool):
+ """Test for NaN or NaT
+
+ Parameters
+ ----------
+ obj : object
+ scalar or vector
+ check_all : bool
+ if True, reduce sequence to single bool;
+ return True if any of the elements are NaN.
+
+ Returns
+ -------
+ bool or array_like of bool.
+ Always return False for non-numeric types.
+ """
+ if is_duck_array_type(type(obj)):
+ if obj.dtype.kind in "if":
+ out = np.isnan(obj)
+ elif obj.dtype.kind in "Mm":
+ out = np.isnat(obj)
+ else:
+ # Not a numeric or datetime type
+ out = np.full(obj.shape, False)
+ return out.any() if check_all else out
+ if isinstance(obj, np_datetime64):
+ return np.isnat(obj)
+ try:
+ return math.isnan(obj)
+ except TypeError:
+ return False
+
+
+def zero_or_nan(obj, check_all: bool):
+ """Test if obj is zero, NaN, or NaT
+
+ Parameters
+ ----------
+ obj : object
+ scalar or vector
+ check_all : bool
+ if True, reduce sequence to single bool;
+ return True if all the elements are zero, NaN, or NaT.
+
+ Returns
+ -------
+ bool or array_like of bool.
+ Always return False for non-numeric types.
+ """
+ out = eq(obj, 0, False) + isnan(obj, False)
+ if check_all and is_duck_array_type(type(out)):
+ return out.all()
return out
diff --git a/pint/context.py b/pint/context.py
index bcc34c3..f82bb13 100644
--- a/pint/context.py
+++ b/pint/context.py
@@ -63,22 +63,24 @@ class Context:
-------
>>> from pint.util import UnitsContainer
+ >>> from pint import Context, UnitRegistry
+ >>> ureg = UnitRegistry()
>>> timedim = UnitsContainer({'[time]': 1})
>>> spacedim = UnitsContainer({'[length]': 1})
- >>> def f(time):
+ >>> def time_to_len(ureg, 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):
+ >>> c.add_transformation(timedim, spacedim, time_to_len)
+ >>> c.transform(timedim, spacedim, ureg, 2)
+ 6.0
+ >>> def time_to_len_indexed(ureg, time, n=1):
... 'Time to length converter, n is the index of refraction of the material'
... return 3. * time / n
- >>> c = Context(n=3)
- >>> c.add_transformation(timedim, spacedim, f)
- >>> c.transform(timedim, spacedim, 2)
- 2
+ >>> c = Context(defaults={'n':3})
+ >>> c.add_transformation(timedim, spacedim, time_to_len_indexed)
+ >>> c.transform(timedim, spacedim, ureg, 2)
+ 2.0
>>> c.redefine("pound = 0.5 kg")
"""
@@ -133,7 +135,7 @@ class Context:
return context
@classmethod
- def from_lines(cls, lines, to_base_func=None):
+ def from_lines(cls, lines, to_base_func=None, non_int_type=float):
lines = SourceIterator(lines)
lineno, header = next(lines)
@@ -186,14 +188,20 @@ class Context:
func = _expression_to_function(eq)
if "<->" in rel:
- src, dst = (ParserHelper.from_string(s) for s in rel.split("<->"))
+ src, dst = (
+ ParserHelper.from_string(s, non_int_type)
+ 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("->"))
+ src, dst = (
+ ParserHelper.from_string(s, non_int_type)
+ for s in rel.split("->")
+ )
if to_base_func:
src = to_base_func(src)
dst = to_base_func(dst)
@@ -321,8 +329,8 @@ class ContextChain(ChainMap):
@property
def defaults(self):
- if self:
- return next(iter(self.maps[0].values())).defaults
+ for ctx in self.values():
+ return ctx.defaults
return {}
@property
diff --git a/pint/default_en.txt b/pint/default_en.txt
index 8316f78..13f2938 100644
--- a/pint/default_en.txt
+++ b/pint/default_en.txt
@@ -282,6 +282,9 @@ refrigeration_ton = 12e3 * Btu / hour = _ = ton_of_refrigeration # approximate,
standard_liter_per_minute = atmosphere * liter / minute = slpm = slm
conventional_watt_90 = K_J90 ** 2 * R_K90 / (K_J ** 2 * R_K) * watt = W_90
+# Momentum
+[momentum] = [length] * [mass] / [time]
+
# Density (as auxiliary for pressure)
[density] = [mass] / [volume]
mercury = 13.5951 * kilogram / liter = Hg = Hg_0C = Hg_32F = conventional_mercury
diff --git a/pint/definitions.py b/pint/definitions.py
index 54aa741..67adca1 100644
--- a/pint/definitions.py
+++ b/pint/definitions.py
@@ -15,22 +15,22 @@ from .errors import DefinitionSyntaxError
from .util import ParserHelper, UnitsContainer, _is_dim
-class ParsedDefinition(
- namedtuple("ParsedDefinition", "name symbol aliases value rhs_parts")
+class PreprocessedDefinition(
+ namedtuple("PreprocessedDefinition", "name symbol aliases value rhs_parts")
):
"""Splits a definition into the constitutive parts.
- A definition is given as a string with equalities in a single line.
+ A definition is given as a string with equalities in a single line::
---------------> rhs
- a = b = c = d = e
- | | | -------> aliases (optional)
- | | |
- | | -----------> symbol (use "_" to
- | |
- | ---------------> value
- |
- -------------------> name
+ a = b = c = d = e
+ | | | -------> aliases (optional)
+ | | |
+ | | -----------> symbol (use "_" for no symbol)
+ | |
+ | ---------------> value
+ |
+ -------------------> name
Attributes
----------
@@ -65,12 +65,13 @@ class _NotNumeric(Exception):
self.value = value
-def numeric_parse(s):
+def numeric_parse(s, non_int_type=float):
"""Try parse a string into a number (without using eval).
Parameters
----------
s : str
+ non_int_type : type
Returns
-------
@@ -81,7 +82,7 @@ def numeric_parse(s):
_NotNumeric
If the string cannot be parsed as a number.
"""
- ph = ParserHelper.from_string(s)
+ ph = ParserHelper.from_string(s, non_int_type)
if len(ph):
raise _NotNumeric(s)
@@ -124,12 +125,13 @@ class Definition:
return self._converter.is_logarithmic
@classmethod
- def from_string(cls, definition):
+ def from_string(cls, definition, non_int_type=float):
"""Parse a definition.
Parameters
----------
- definition : str or ParsedDefinition
+ definition : str or PreprocessedDefinition
+ non_int_type : type
Returns
-------
@@ -137,16 +139,16 @@ class Definition:
"""
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
if definition.name.startswith("@alias "):
- return AliasDefinition.from_string(definition)
+ return AliasDefinition.from_string(definition, non_int_type)
elif definition.name.startswith("["):
- return DimensionDefinition.from_string(definition)
+ return DimensionDefinition.from_string(definition, non_int_type)
elif definition.name.endswith("-"):
- return PrefixDefinition.from_string(definition)
+ return PrefixDefinition.from_string(definition, non_int_type)
else:
- return UnitDefinition.from_string(definition)
+ return UnitDefinition.from_string(definition, non_int_type)
@property
def name(self):
@@ -177,18 +179,19 @@ class Definition:
class PrefixDefinition(Definition):
- """Definition of a prefix.
+ """Definition of a prefix::
+
+ <prefix>- = <amount> [= <symbol>] [= <alias>] [ = <alias> ] [...]
- <prefix>- = <amount> [= <symbol>] [= <alias>] [ = <alias> ] [...]
+ Example::
- Example:
deca- = 1e+1 = da- = deka-
"""
@classmethod
- def from_string(cls, definition):
+ def from_string(cls, definition, non_int_type=float):
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
aliases = tuple(alias.strip("-") for alias in definition.aliases)
if definition.symbol:
@@ -197,7 +200,7 @@ class PrefixDefinition(Definition):
symbol = definition.symbol
try:
- converter = ScaleConverter(numeric_parse(definition.value))
+ converter = ScaleConverter(numeric_parse(definition.value, non_int_type))
except _NotNumeric as ex:
raise ValueError(
f"Prefix definition ('{definition.name}') must contain only numbers, not {ex.value}"
@@ -207,11 +210,12 @@ class PrefixDefinition(Definition):
class UnitDefinition(Definition):
- """Definition of a unit.
+ """Definition of a unit::
- <canonical name> = <relation to another unit or dimension> [= <symbol>] [= <alias>] [ = <alias> ] [...]
+ <canonical name> = <relation to another unit or dimension> [= <symbol>] [= <alias>] [ = <alias> ] [...]
+
+ Example::
- Example:
millennium = 1e3 * year = _ = millennia
Parameters
@@ -230,16 +234,16 @@ class UnitDefinition(Definition):
super().__init__(name, symbol, aliases, converter)
@classmethod
- def from_string(cls, definition):
+ def from_string(cls, definition, non_int_type=float):
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
if ";" in definition.value:
[converter, modifiers] = definition.value.split(";", 1)
try:
modifiers = dict(
- (key.strip(), numeric_parse(value))
+ (key.strip(), numeric_parse(value, non_int_type))
for key, value in (part.split(":") for part in modifiers.split(";"))
)
except _NotNumeric as ex:
@@ -251,7 +255,7 @@ class UnitDefinition(Definition):
converter = definition.value
modifiers = {}
- converter = ParserHelper.from_string(converter)
+ converter = ParserHelper.from_string(converter, non_int_type)
if not any(_is_dim(key) for key in converter.keys()):
is_base = False
@@ -293,11 +297,12 @@ class UnitDefinition(Definition):
class DimensionDefinition(Definition):
- """Definition of a dimension.
+ """Definition of a dimension::
- [dimension name] = <relation to other dimensions>
+ [dimension name] = <relation to other dimensions>
+
+ Example::
- Example:
[density] = [mass] / [volume]
"""
@@ -308,11 +313,11 @@ class DimensionDefinition(Definition):
super().__init__(name, symbol, aliases, converter=None)
@classmethod
- def from_string(cls, definition):
+ def from_string(cls, definition, non_int_type=float):
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
- converter = ParserHelper.from_string(definition.value)
+ converter = ParserHelper.from_string(definition.value, non_int_type)
if not converter:
is_base = True
@@ -324,7 +329,7 @@ class DimensionDefinition(Definition):
"Derived dimensions must only be referenced "
"to dimensions."
)
- reference = UnitsContainer(converter)
+ reference = UnitsContainer(converter, non_int_type=non_int_type)
return cls(
definition.name,
@@ -337,11 +342,12 @@ class DimensionDefinition(Definition):
class AliasDefinition(Definition):
- """Additional alias(es) for an already existing unit.
+ """Additional alias(es) for an already existing unit::
- @alias <canonical name or previous alias> = <alias> [ = <alias> ] [...]
+ @alias <canonical name or previous alias> = <alias> [ = <alias> ] [...]
+
+ Example::
- Example:
@alias meter = my_meter
"""
@@ -349,10 +355,10 @@ class AliasDefinition(Definition):
super().__init__(name=name, symbol=None, aliases=aliases, converter=None)
@classmethod
- def from_string(cls, definition):
+ def from_string(cls, definition, non_int_type=float):
if isinstance(definition, str):
- definition = ParsedDefinition.from_string(definition)
+ definition = PreprocessedDefinition.from_string(definition)
name = definition.name[len("@alias ") :].lstrip()
return AliasDefinition(name, tuple(definition.rhs_parts))
diff --git a/pint/formatting.py b/pint/formatting.py
index 0e84f82..495dfec 100644
--- a/pint/formatting.py
+++ b/pint/formatting.py
@@ -62,8 +62,8 @@ def _pretty_fmt_exponent(num):
str
"""
- # TODO: Will not work for decimals
- ret = f"{num:n}".replace("-", "⁻")
+ # unicode dot operator (U+22C5) looks like a superscript decimal
+ ret = f"{num:n}".replace("-", "⁻").replace(".", "\u22C5")
for n in range(10):
ret = ret.replace(str(n), _PRETTY_EXPONENTS[n])
return ret
@@ -89,12 +89,13 @@ _FORMATS = {
"power_fmt": "{}^[{}]",
"parentheses_fmt": r"\left({}\right)",
},
+ "Lx": {"siopts": "", "pm_fmt": " +- "}, # Latex format with SIunitx.
"H": { # HTML format.
"as_ratio": True,
"single_denominator": True,
"product_fmt": r" ",
"division_fmt": r"{}/{}",
- "power_fmt": "{}^{}",
+ "power_fmt": "{{{}}}^{{{}}}", # braces superscript whole exponent
"parentheses_fmt": r"({})",
},
"": { # Default format.
diff --git a/pint/measurement.py b/pint/measurement.py
index c6d99f6..826253f 100644
--- a/pint/measurement.py
+++ b/pint/measurement.py
@@ -49,9 +49,7 @@ class Measurement(Quantity):
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")
else:
mag = ufloat(value, error)
@@ -89,14 +87,33 @@ class Measurement(Quantity):
if "Lx" in spec: # the LaTeX siunitx code
# the uncertainties module supports formatting
# numbers in value(unc) notation (i.e. 1.23(45) instead of 1.23 +/- 0.45),
- # which siunitx actually accepts as input. we just need to give the 'S'
- # formatting option for the uncertainties module.
- spec = spec.replace("Lx", "S")
- # todo: add support for extracting options
- opts = "separate-uncertainty=true"
- mstr = format(self.magnitude, spec)
+ # using type code 'S', which siunitx actually accepts as input.
+ # However, the implimentation is incompatible with siunitx.
+ # Uncertainties will do 9.1(1.1), which is invalid, should be 9.1(11).
+ # TODO: add support for extracting options
+ #
+ # Get rid of this code, we'll deal with it here
+ spec = spec.replace("Lx", "")
+ # The most compatible format from uncertainties is the default format,
+ # but even this requires fixups.
+ # For one, SIUnitx does not except some formats that unc does, like 'P',
+ # and 'S' is broken as stated, so...
+ spec = spec.replace("S", "").replace("P", "")
+ # get SIunitx options
+ # TODO: allow user to set this value, somehow
+ opts = _FORMATS["Lx"]["siopts"]
+ if opts != "":
+ opts = r"[" + opts + r"]"
+ # SI requires space between "+-" (or "\pm") and the nominal value
+ # and uncertainty, and doesn't accept "+/-", so this setting
+ # selects the desired replacement.
+ pm_fmt = _FORMATS["Lx"]["pm_fmt"]
+ mstr = format(self.magnitude, spec).replace(r"+/-", pm_fmt)
+ # Also, SIunitx doesn't accept parentheses, which uncs uses with
+ # scientific notation ('e' or 'E' and somtimes 'g' or 'G').
+ mstr = mstr.replace("(", "").replace(")", " ")
ustr = siunitx_format_unit(self.units)
- return r"\SI[%s]{%s}{%s}" % (opts, mstr, ustr)
+ return r"\SI%s{%s}{%s}" % (opts, mstr, ustr)
# standard cases
if "L" in spec:
diff --git a/pint/numpy_func.py b/pint/numpy_func.py
index 10f4114..0f220b0 100644
--- a/pint/numpy_func.py
+++ b/pint/numpy_func.py
@@ -10,8 +10,8 @@ import warnings
from inspect import signature
from itertools import chain
-from .compat import eq, is_upcast_type, np
-from .errors import DimensionalityError
+from .compat import is_upcast_type, np, zero_or_nan
+from .errors import DimensionalityError, UnitStrippedWarning
from .util import iterable, sized
HANDLED_UFUNCS = {}
@@ -48,14 +48,13 @@ def _is_sequence_with_quantity_elements(obj):
Returns
-------
- bool
+ True if obj is a sequence and at least one element is a Quantity; False otherwise
"""
return (
iterable(obj)
and sized(obj)
and not isinstance(obj, str)
- and len(obj) > 0
- and all(_is_quantity(item) for item in obj)
+ and any(_is_quantity(item) for item in obj)
)
@@ -67,42 +66,43 @@ def _get_first_input_units(args, kwargs=None):
if _is_quantity(arg):
return arg.units
elif _is_sequence_with_quantity_elements(arg):
- return arg[0].units
+ return next(arg_i.units for arg_i in arg if _is_quantity(arg_i))
+ raise TypeError("Expected at least one Quantity; found none")
def convert_arg(arg, pre_calc_units):
"""Convert quantities and sequences of quantities to pre_calc_units and strip units.
- Helper function for convert_to_consistent_units. pre_calc_units must be given as a pint
- Unit or None.
-
+ 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]
+ return [convert_arg(item, pre_calc_units) for item in arg]
elif arg is not None:
if pre_calc_units.dimensionless:
return pre_calc_units._REGISTRY.Quantity(arg).m_as(pre_calc_units)
+ elif not _is_quantity(arg) and zero_or_nan(arg, True):
+ return arg
else:
raise DimensionalityError("dimensionless", pre_calc_units)
- else:
- if _is_quantity(arg):
- return arg.m
- elif _is_sequence_with_quantity_elements(arg):
- return [item.m for item in arg]
+ elif _is_quantity(arg):
+ return arg.m
+ elif _is_sequence_with_quantity_elements(arg):
+ return [convert_arg(item, pre_calc_units) for item in arg]
return arg
def convert_to_consistent_units(*args, pre_calc_units=None, **kwargs):
"""Prepare args and kwargs for wrapping by unit conversion and stripping.
- If pre_calc_units is not None, takes the args and kwargs for a NumPy function and converts
- any Quantity or Sequence of Quantities into the units of the first 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.
-
+ If pre_calc_units is not None, takes the args and kwargs for a NumPy function and
+ converts any Quantity or Sequence of Quantities into the units of the first
+ Quantity/Sequence of Quantities and returns the magnitudes. Other args/kwargs are
+ treated as dimensionless Quantities. If pre_calc_units is None, units are simply
+ stripped.
"""
return (
tuple(convert_arg(arg, pre_calc_units=pre_calc_units) for arg in args),
@@ -120,6 +120,9 @@ def unwrap_and_wrap_consistent_units(*args):
first arg with units, along with a wrapper to restore that unit to the output.
"""
+ if all(not _is_quantity(arg) for arg in args):
+ return args, lambda x: x
+
first_input_units = _get_first_input_units(args)
args, _ = convert_to_consistent_units(*args, pre_calc_units=first_input_units)
return (
@@ -260,7 +263,10 @@ def implement_func(func_type, func_str, input_units=None, output_unit=None):
# Handle functions in submodules
func_str_split = func_str.split(".")
- func = getattr(np, func_str_split[0])
+ func = getattr(np, func_str_split[0], None)
+ # If the function is not available, do not attempt to implement it
+ if func is None:
+ return
for func_str_piece in func_str_split[1:]:
func = getattr(func, func_str_piece)
@@ -410,7 +416,6 @@ matching_input_copy_units_output_ufuncs = [
copy_units_output_ufuncs = ["ldexp", "fmod", "mod", "remainder"]
op_units_output_ufuncs = {
"var": "square",
- "prod": "size",
"multiply": "mul",
"true_divide": "div",
"divide": "div",
@@ -484,28 +489,15 @@ def _power(x1, x2):
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)
+ (x1, x2), output_wrap = unwrap_and_wrap_consistent_units(x1, x2)
return output_wrap(np.add(x1, x2, *args, **kwargs))
@implements("subtract", "ufunc")
def _subtract(x1, x2, *args, **kwargs):
- x1, x2, output_wrap = _add_subtract_handle_non_quantity_zero(x1, x2)
+ (x1, x2), output_wrap = unwrap_and_wrap_consistent_units(x1, x2)
return output_wrap(np.subtract(x1, x2, *args, **kwargs))
@@ -549,26 +541,7 @@ def _interp(x, xp, fp, left=None, right=None, period=None):
@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)
+ args, output_wrap = unwrap_and_wrap_consistent_units(*args)
return output_wrap(np.where(condition, *args))
@@ -602,6 +575,7 @@ def _copyto(dst, src, casting="same_kind", where=True):
else:
warnings.warn(
"The unit of the quantity is stripped when copying to non-quantity",
+ UnitStrippedWarning,
stacklevel=2,
)
np.copyto(dst, src.m, casting=casting, where=where)
@@ -630,6 +604,8 @@ def _isin(element, test_elements, assume_unique=False, invert=False):
elif _is_sequence_with_quantity_elements(test_elements):
compatible_test_elements = []
for test_element in test_elements:
+ if not _is_quantity(test_element):
+ pass
try:
compatible_test_elements.append(test_element.m_as(element.units))
except DimensionalityError:
@@ -667,10 +643,9 @@ def _pad(array, pad_width, mode="constant", **kwargs):
# 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)
+ for key in ("constant_values", "end_values"):
+ if key in kwargs:
+ kwargs[key] = _recursive_convert(kwargs[key], units)
return units._REGISTRY.Quantity(
np.pad(array._magnitude, pad_width, mode=mode, **kwargs), units
@@ -695,6 +670,36 @@ def _all(a, *args, **kwargs):
raise ValueError("Boolean value of Quantity with offset unit is ambiguous.")
+@implements("prod", "function")
+def _prod(a, *args, **kwargs):
+ arg_names = ("axis", "dtype", "out", "keepdims", "initial", "where")
+ all_kwargs = dict(**dict(zip(arg_names, args)), **kwargs)
+ axis = all_kwargs.get("axis", None)
+ where = all_kwargs.get("where", None)
+
+ registry = a.units._REGISTRY
+
+ if axis is not None and where is not None:
+ _, where_ = np.broadcast_arrays(a._magnitude, where)
+ exponents = np.unique(np.sum(where_, axis=axis))
+ if len(exponents) == 1 or (len(exponents) == 2 and 0 in exponents):
+ units = a.units ** np.max(exponents)
+ else:
+ units = registry.dimensionless
+ a = a.to(units)
+ elif axis is not None:
+ units = a.units ** a.shape[axis]
+ elif where is not None:
+ exponent = np.sum(where)
+ units = a.units ** exponent
+ else:
+ units = a.units ** a.size
+
+ result = np.prod(a._magnitude, *args, **kwargs)
+
+ return registry.Quantity(result, units)
+
+
# Implement simple matching-unit or stripped-unit functions based on signature
@@ -703,7 +708,10 @@ def implement_consistent_units_by_argument(func_str, unit_arguments, wrap_output
if np is None:
return
- func = getattr(np, func_str)
+ func = getattr(np, func_str, None)
+ # if NumPy does not implement it, do not implement it either
+ if func is None:
+ return
@implements(func_str, "function")
def implementation(*args, **kwargs):
@@ -757,6 +765,8 @@ for func_str, unit_arguments, wrap_output in [
("nanmax", "a", True),
("percentile", "a", True),
("nanpercentile", "a", True),
+ ("quantile", "a", True),
+ ("nanquantile", "a", True),
("flip", "m", True),
("fix", "x", True),
("trim_zeros", ["filt"], True),
@@ -764,7 +774,7 @@ for func_str, unit_arguments, wrap_output in [
("amax", ["a", "initial"], True),
("amin", ["a", "initial"], True),
("searchsorted", ["a", "v"], False),
- ("isclose", ["a", "b", "rtol", "atol"], False),
+ ("isclose", ["a", "b"], False),
("nan_to_num", ["x", "nan", "posinf", "neginf"], True),
("clip", ["a", "a_min", "a_max"], True),
("append", ["arr", "values"], True),
diff --git a/pint/pint-convert b/pint/pint-convert
new file mode 100755
index 0000000..4fdf259
--- /dev/null
+++ b/pint/pint-convert
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+
+"""
+ pint-convert
+ ~~~~~~~~~~~~
+
+ :copyright: 2020 by Pint Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import argparse
+import re
+import sys
+
+from pint import UnitRegistry
+
+parser = argparse.ArgumentParser(description='Unit converter.', usage=argparse.SUPPRESS)
+parser.add_argument('-s', '--system', metavar='sys', default='SI', help='unit system to convert to (default: SI)')
+parser.add_argument('-p', '--prec', metavar='n', type=int, default=12, help='number of maximum significant figures (default: 12)')
+parser.add_argument('-u', '--prec-unc', metavar='n', type=int, default=2, help='number of maximum uncertainty digits (default: 2)')
+parser.add_argument('-U', '--no-unc', dest='unc', action='store_false', help='ignore uncertainties in constants')
+parser.add_argument('-C', '--no-corr', dest='corr', action='store_false', help='ignore correlations between constants')
+parser.add_argument('fr', metavar='from', type=str, help='unit or quantity to convert from')
+parser.add_argument('to', type=str, nargs='?', help='unit to convert to')
+try:
+ args = parser.parse_args()
+except SystemExit:
+ parser.print_help()
+ raise
+
+ureg = UnitRegistry()
+ureg.auto_reduce_dimensions = True
+ureg.autoconvert_offset_to_baseunit = True
+ureg.enable_contexts('Gau', 'ESU', 'sp', 'energy', 'boltzmann')
+ureg.default_system = args.system
+
+if args.unc:
+ import uncertainties
+
+ # Measured constans subject to correlation
+ # R_i: Rydberg constant
+ # g_e: Electron g factor
+ # m_u: Atomic mass constant
+ # m_e: Electron mass
+ # m_p: Proton mass
+ # m_n: Neutron mass
+ R_i = (ureg._units['R_inf'].converter.scale, 0.0000000000021e7)
+ g_e = (ureg._units['g_e'].converter.scale, 0.00000000000035)
+ m_u = (ureg._units['m_u'].converter.scale, 0.00000000050e-27)
+ m_e = (ureg._units['m_e'].converter.scale, 0.00000000028e-30)
+ m_p = (ureg._units['m_p'].converter.scale, 0.00000000051e-27)
+ m_n = (ureg._units['m_n'].converter.scale, 0.00000000095e-27)
+ if args.corr:
+ # Correlation matrix between measured constants (to be completed below)
+ # R_i g_e m_u m_e m_p m_n
+ corr = [[ 1.0 , -0.00206, 0.00369, 0.00436, 0.00194, 0.00233], # R_i
+ [ -0.00206, 1.0 , 0.99029, 0.99490, 0.97560, 0.52445], # g_e
+ [ 0.00369, 0.99029, 1.0 , 0.99536, 0.98516, 0.52959], # m_u
+ [ 0.00436, 0.99490, 0.99536, 1.0 , 0.98058, 0.52714], # m_e
+ [ 0.00194, 0.97560, 0.98516, 0.98058, 1.0 , 0.51521], # m_p
+ [ 0.00233, 0.52445, 0.52959, 0.52714, 0.51521, 1.0 ]] # m_n
+ (R_i, g_e, m_u, m_e, m_p, m_n) = uncertainties.correlated_values_norm([R_i, g_e, m_u, m_e, m_p, m_n], corr)
+ else:
+ R_i = uncertainties.ufloat(*R_i)
+ g_e = uncertainties.ufloat(*g_e)
+ m_u = uncertainties.ufloat(*m_u)
+ m_e = uncertainties.ufloat(*m_e)
+ m_p = uncertainties.ufloat(*m_p)
+ m_n = uncertainties.ufloat(*m_n)
+ ureg._units['R_inf'].converter.scale = R_i
+ ureg._units['g_e'].converter.scale = g_e
+ ureg._units['m_u'].converter.scale = m_u
+ ureg._units['m_e'].converter.scale = m_e
+ ureg._units['m_p'].converter.scale = m_p
+ ureg._units['m_n'].converter.scale = m_n
+
+ # Measured constants with zero correlation
+ ureg._units['gravitational_constant'].converter.scale = uncertainties.ufloat(ureg._units['gravitational_constant'].converter.scale, 0.00015e-11)
+ ureg._units['d_220'].converter.scale = uncertainties.ufloat(ureg._units['d_220'].converter.scale, 0.000000032e-10)
+ ureg._units['K_alpha_Cu_d_220'].converter.scale = uncertainties.ufloat(ureg._units['K_alpha_Cu_d_220'].converter.scale, 0.00000022)
+ ureg._units['K_alpha_Mo_d_220'].converter.scale = uncertainties.ufloat(ureg._units['K_alpha_Mo_d_220'].converter.scale, 0.00000019)
+ ureg._units['K_alpha_W_d_220'].converter.scale = uncertainties.ufloat(ureg._units['K_alpha_W_d_220'].converter.scale, 0.000000098)
+
+ ureg._root_units_cache = dict()
+ ureg._build_cache()
+
+def convert(u_from, u_to=None, unc=None, factor=None):
+ q = ureg.Quantity(u_from)
+ fmt = '.{}g'.format(args.prec)
+ if unc:
+ q = q.plus_minus(unc)
+ if u_to:
+ nq = q.to(u_to)
+ else:
+ nq = q.to_base_units()
+ if (factor):
+ q *= ureg.Quantity(factor)
+ nq *= ureg.Quantity(factor).to_base_units()
+ prec_unc = use_unc(nq.magnitude, fmt, args.prec_unc)
+ if (prec_unc > 0):
+ fmt = '.{}uS'.format(prec_unc)
+ else:
+ try:
+ nq = nq.magnitude.n * nq.units
+ except:
+ pass
+ fmt = '{:' + fmt + '} {:~P}'
+ print(('{:} = ' + fmt).format(q, nq.magnitude, nq.units))
+
+def use_unc(num, fmt, prec_unc):
+ unc = 0
+ try:
+ if (isinstance(num, uncertainties.UFloat)):
+ full = ('{:'+fmt+'}').format(num)
+ unc = re.search(r'\+\/-[0.]*([\d.]*)', full).group(1)
+ unc = len(unc.replace('.', ''))
+ except:
+ pass
+ return max(0, min(prec_unc, unc))
+
+convert(args.fr, args.to)
diff --git a/pint/quantity.py b/pint/quantity.py
index efb58c6..ad066f3 100644
--- a/pint/quantity.py
+++ b/pint/quantity.py
@@ -17,22 +17,25 @@ import numbers
import operator
import re
import warnings
+from typing import List
-from pkg_resources.extern.packaging import version
+from packaging import version
-from .compat import SKIP_ARRAY_FUNCTION_CHANGE_WARNING # noqa: F401
from .compat import (
- ARRAY_FALLBACK,
+ HAS_NUMPY_ARRAY_FUNCTION,
NUMPY_VER,
- BehaviorChangeWarning,
_to_magnitude,
- array_function_change_msg,
babel_parse,
+ compute,
+ dask_array,
eq,
is_duck_array_type,
is_upcast_type,
ndarray,
np,
+ persist,
+ visualize,
+ zero_or_nan,
)
from .definitions import UnitDefinition
from .errors import (
@@ -76,9 +79,12 @@ class _Exception(Exception): # pragma: no cover
def reduce_dimensions(f):
def wrapped(self, *args, **kwargs):
result = f(self, *args, **kwargs)
- if result._REGISTRY.auto_reduce_dimensions:
- return result.to_reduced_units()
- else:
+ try:
+ if result._REGISTRY.auto_reduce_dimensions:
+ return result.to_reduced_units()
+ else:
+ return result
+ except AttributeError:
return result
return wrapped
@@ -87,8 +93,11 @@ def reduce_dimensions(f):
def ireduce_dimensions(f):
def wrapped(self, *args, **kwargs):
result = f(self, *args, **kwargs)
- if result._REGISTRY.auto_reduce_dimensions:
- result.ito_reduced_units()
+ try:
+ if result._REGISTRY.auto_reduce_dimensions:
+ result.ito_reduced_units()
+ except AttributeError:
+ pass
return result
return wrapped
@@ -103,12 +112,37 @@ def check_implemented(f):
# and expects Quantity * array[Quantity] should return NotImplemented
elif isinstance(other, list) and other and isinstance(other[0], type(self)):
return NotImplemented
- result = f(self, *args, **kwargs)
- return result
+ return f(self, *args, **kwargs)
return wrapped
+def method_wraps(numpy_func):
+ if isinstance(numpy_func, str):
+ numpy_func = getattr(np, numpy_func, None)
+
+ def wrapper(func):
+ func.__wrapped__ = numpy_func
+
+ return func
+
+ return wrapper
+
+
+def check_dask_array(f):
+ @functools.wraps(f)
+ def wrapper(self, *args, **kwargs):
+ if isinstance(self._magnitude, dask_array.Array):
+ return f(self, *args, **kwargs)
+ else:
+ msg = "Method {} only implemented for objects of {}, not {}".format(
+ f.__name__, dask_array.Array, self._magnitude.__class__
+ )
+ raise AttributeError(msg)
+
+ return wrapper
+
+
@contextlib.contextmanager
def printoptions(*args, **kwargs):
"""Numpy printoptions context manager released with version 1.15.0
@@ -150,6 +184,10 @@ class Quantity(PrettyIPython, SharedRegistryObject):
def force_ndarray_like(self):
return self._REGISTRY.force_ndarray_like
+ @property
+ def UnitsContainer(self):
+ return self._REGISTRY.UnitsContainer
+
def __reduce__(self):
"""Allow pickling quantities. Since UnitRegistries are not pickled, upon
unpickling the new object is always attached to the application registry.
@@ -161,8 +199,6 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return _unpickle, (Quantity, self.magnitude, self._units)
def __new__(cls, value, units=None):
- global SKIP_ARRAY_FUNCTION_CHANGE_WARNING
-
if is_upcast_type(type(value)):
raise TypeError(f"Quantity cannot wrap upcast type {type(value)}")
elif units is None:
@@ -181,7 +217,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
inst._magnitude = _to_magnitude(
value, inst.force_ndarray, inst.force_ndarray_like
)
- inst._units = UnitsContainer()
+ inst._units = inst.UnitsContainer()
elif isinstance(units, (UnitsContainer, UnitDefinition)):
inst = SharedRegistryObject.__new__(cls)
inst._magnitude = _to_magnitude(
@@ -215,12 +251,6 @@ class Quantity(PrettyIPython, SharedRegistryObject):
inst.__used = False
inst.__handling = None
- if not SKIP_ARRAY_FUNCTION_CHANGE_WARNING and isinstance(
- inst._magnitude, ndarray
- ):
- warnings.warn(array_function_change_msg, BehaviorChangeWarning)
- SKIP_ARRAY_FUNCTION_CHANGE_WARNING = True
-
return inst
@property
@@ -252,13 +282,19 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return ret
def __str__(self):
+ if self._REGISTRY.fmt_locale is not None:
+ return self.format_babel()
+
return format(self)
def __bytes__(self):
return str(self).encode(locale.getpreferredencoding())
def __repr__(self):
- return f"<Quantity({self._magnitude}, '{self._units}')>"
+ if isinstance(self._magnitude, float):
+ return f"<Quantity({self._magnitude:.9}, '{self._units}')>"
+ else:
+ return f"<Quantity({self._magnitude}, '{self._units}')>"
def __hash__(self):
self_base = self.to_base_units()
@@ -270,6 +306,9 @@ class Quantity(PrettyIPython, SharedRegistryObject):
_exp_pattern = re.compile(r"([0-9]\.?[0-9]*)e(-?)\+?0*([0-9]+)")
def __format__(self, spec):
+ if self._REGISTRY.fmt_locale is not None:
+ return self.format_babel(spec)
+
spec = spec or self.default_format
if "L" in spec:
@@ -492,7 +531,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
@classmethod
def from_tuple(cls, tup):
- return cls(tup[0], UnitsContainer(tup[1]))
+ return cls(tup[0], cls._REGISTRY.UnitsContainer(tup[1]))
def to_tuple(self):
return self.m, tuple(self._units.items())
@@ -504,6 +543,40 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return self._REGISTRY.get_compatible_units(self._units)
+ def is_compatible_with(self, other, *contexts, **ctx_kwargs):
+ """ check if the other object is compatible
+
+ Parameters
+ ----------
+ other
+ The object to check. Treated as dimensionless if not a
+ Quantity, Unit or str.
+ *contexts : str or pint.Context
+ Contexts to use in the transformation.
+ **ctx_kwargs :
+ Values for the Context/s
+
+ Returns
+ -------
+ bool
+ """
+ if contexts:
+ try:
+ self.to(other, *contexts, **ctx_kwargs)
+ return True
+ except DimensionalityError:
+ return False
+
+ if isinstance(other, (self._REGISTRY.Quantity, self._REGISTRY.Unit)):
+ return self.dimensionality == other.dimensionality
+
+ if isinstance(other, str):
+ return (
+ self.dimensionality == self._REGISTRY.parse_units(other).dimensionality
+ )
+
+ return self.dimensionless
+
def _convert_magnitude_not_inplace(self, other, *contexts, **ctx_kwargs):
if contexts:
with self._REGISTRY.context(*contexts, **ctx_kwargs):
@@ -709,7 +782,12 @@ class Quantity(PrettyIPython, SharedRegistryObject):
else:
power = int(math.ceil(math.log10(abs(magnitude)) / unit_power / 3)) * 3
- prefix = SI_bases[bisect.bisect_left(SI_powers, power)]
+ index = bisect.bisect_left(SI_powers, power)
+
+ if index >= len(SI_bases):
+ index = -1
+
+ prefix = SI_bases[index]
new_unit_str = prefix + unit_str
new_unit_container = q_base._units.rename(unit_str, new_unit_str)
@@ -754,7 +832,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
raise
except TypeError:
return NotImplemented
- if eq(other, 0, True):
+ if zero_or_nan(other, True):
# If the other value is 0 (but not Quantity 0)
# do the operation without checking units.
# We do the calculation instead of just returning the same
@@ -762,7 +840,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
# the operation.
self._magnitude = op(self._magnitude, other_magnitude)
elif self.dimensionless:
- self.ito(UnitsContainer())
+ self.ito(self.UnitsContainer())
self._magnitude = op(self._magnitude, other_magnitude)
else:
raise DimensionalityError(self._units, "dimensionless")
@@ -855,13 +933,11 @@ class Quantity(PrettyIPython, SharedRegistryObject):
object to be added to / subtracted from self
op : function
operator function (e.g. operator.add, operator.isub)
-
-
"""
if not self._check(other):
# other not from same Registry or not a Quantity
- if eq(other, 0, True):
- # If the other value is 0 (but not Quantity 0)
+ if zero_or_nan(other, True):
+ # If the other value is 0 or NaN (but not a Quantity)
# do the operation without checking units.
# We do the calculation instead of just returning the same
# value to enforce any shape checking and type casting due to
@@ -872,7 +948,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
_to_magnitude(other, self.force_ndarray, self.force_ndarray_like),
)
elif self.dimensionless:
- units = UnitsContainer()
+ units = self.UnitsContainer()
magnitude = op(
self.to(units)._magnitude,
_to_magnitude(other, self.force_ndarray, self.force_ndarray_like),
@@ -1037,7 +1113,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
except TypeError:
return NotImplemented
self._magnitude = magnitude_op(self._magnitude, other_magnitude)
- self._units = units_op(self._units, UnitsContainer())
+ self._units = units_op(self._units, self.UnitsContainer())
return self
if isinstance(other, self._REGISTRY.Unit):
@@ -1108,7 +1184,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return NotImplemented
magnitude = magnitude_op(self._magnitude, other_magnitude)
- units = units_op(self._units, UnitsContainer())
+ units = units_op(self._units, self.UnitsContainer())
return self.__class__(magnitude, units)
@@ -1192,7 +1268,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
self._magnitude = self.to("")._magnitude // other
else:
raise DimensionalityError(self._units, "dimensionless")
- self._units = UnitsContainer({})
+ self._units = self.UnitsContainer({})
return self
@check_implemented
@@ -1203,7 +1279,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
magnitude = self.to("")._magnitude // other
else:
raise DimensionalityError(self._units, "dimensionless")
- return self.__class__(magnitude, UnitsContainer({}))
+ return self.__class__(magnitude, self.UnitsContainer({}))
@check_implemented
def __rfloordiv__(self, other):
@@ -1213,19 +1289,19 @@ class Quantity(PrettyIPython, SharedRegistryObject):
magnitude = other // self.to("")._magnitude
else:
raise DimensionalityError(self._units, "dimensionless")
- return self.__class__(magnitude, UnitsContainer({}))
+ return self.__class__(magnitude, self.UnitsContainer({}))
@check_implemented
def __imod__(self, other):
if not self._check(other):
- other = self.__class__(other, UnitsContainer({}))
+ other = self.__class__(other, self.UnitsContainer({}))
self._magnitude %= other.to(self._units)._magnitude
return self
@check_implemented
def __mod__(self, other):
if not self._check(other):
- other = self.__class__(other, UnitsContainer({}))
+ other = self.__class__(other, self.UnitsContainer({}))
magnitude = self._magnitude % other.to(self._units)._magnitude
return self.__class__(magnitude, self._units)
@@ -1236,16 +1312,19 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return self.__class__(magnitude, other._units)
elif self.dimensionless:
magnitude = other % self.to("")._magnitude
- return self.__class__(magnitude, UnitsContainer({}))
+ return self.__class__(magnitude, self.UnitsContainer({}))
else:
raise DimensionalityError(self._units, "dimensionless")
@check_implemented
def __divmod__(self, other):
if not self._check(other):
- other = self.__class__(other, UnitsContainer({}))
+ other = self.__class__(other, self.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, self.UnitsContainer({})),
+ self.__class__(r, self._units),
+ )
@check_implemented
def __rdivmod__(self, other):
@@ -1254,10 +1333,10 @@ class Quantity(PrettyIPython, SharedRegistryObject):
unit = other._units
elif self.dimensionless:
q, r = divmod(other, self.to("")._magnitude)
- unit = UnitsContainer({})
+ unit = self.UnitsContainer({})
else:
raise DimensionalityError(self._units, "dimensionless")
- return (self.__class__(q, UnitsContainer({})), self.__class__(r, unit))
+ return (self.__class__(q, self.UnitsContainer({})), self.__class__(r, unit))
@check_implemented
def __ipow__(self, other):
@@ -1298,7 +1377,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
if other == 1:
return self
elif other == 0:
- self._units = UnitsContainer()
+ self._units = self.UnitsContainer()
else:
if not self._is_multiplicative:
if self._REGISTRY.autoconvert_offset_to_baseunit:
@@ -1355,7 +1434,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return self
elif other == 0:
exponent = 0
- units = UnitsContainer()
+ units = self.UnitsContainer()
else:
if not self._is_multiplicative:
if self._REGISTRY.autoconvert_offset_to_baseunit:
@@ -1408,12 +1487,24 @@ class Quantity(PrettyIPython, SharedRegistryObject):
@check_implemented
def __eq__(self, other):
+ def bool_result(value):
+ nonlocal other
+
+ if not is_duck_array_type(type(self._magnitude)):
+ return value
+
+ if isinstance(other, Quantity):
+ other = other._magnitude
+
+ template, _ = np.broadcast_arrays(self._magnitude, other)
+ return np.full_like(template, fill_value=value, dtype=np.bool_)
+
# We compare to the base class of Quantity because
# each Quantity class is unique.
if not isinstance(other, Quantity):
- if eq(other, 0, True):
- # Handle the special case in which we compare to zero
- # (or an array of zeros)
+ if zero_or_nan(other, True):
+ # Handle the special case in which we compare to zero or NaN
+ # (or an array of zeros or NaNs)
if self._is_multiplicative:
# compare magnitude
return eq(self._magnitude, other, False)
@@ -1425,12 +1516,14 @@ class Quantity(PrettyIPython, SharedRegistryObject):
else:
raise OffsetUnitCalculusError(self._units)
- return self.dimensionless and eq(
- self._convert_magnitude(UnitsContainer()), other, False
- )
+ if self.dimensionless:
+ return eq(self._convert_magnitude(self.UnitsContainer()), other, False)
+ return bool_result(False)
+
+ # TODO: this might be expensive. Do we even need it?
if eq(self._magnitude, 0, True) and eq(other._magnitude, 0, True):
- return self.dimensionality == other.dimensionality
+ return bool_result(self.dimensionality == other.dimensionality)
if self._units == other._units:
return eq(self._magnitude, other._magnitude, False)
@@ -1442,7 +1535,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
False,
)
except DimensionalityError:
- return False
+ return bool_result(False)
@check_implemented
def __ne__(self, other):
@@ -1455,10 +1548,12 @@ class Quantity(PrettyIPython, SharedRegistryObject):
def compare(self, other, op):
if not isinstance(other, self.__class__):
if self.dimensionless:
- return op(self._convert_magnitude_not_inplace(UnitsContainer()), other)
- elif eq(other, 0, True):
- # Handle the special case in which we compare to zero
- # (or an array of zeros)
+ return op(
+ self._convert_magnitude_not_inplace(self.UnitsContainer()), other
+ )
+ elif zero_or_nan(other, True):
+ # Handle the special case in which we compare to zero or NaN
+ # (or an array of zeros or NaNs)
if self._is_multiplicative:
# compare magnitude
return op(self._magnitude, other)
@@ -1646,6 +1741,23 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return np.dot(self, b)
+ @method_wraps("prod")
+ def prod(self, *args, **kwargs):
+ """ Return the product of quantity elements over a given axis
+
+ Wraps np.prod().
+ """
+ # TODO: remove after support for 1.16 has been dropped
+ if not HAS_NUMPY_ARRAY_FUNCTION:
+ raise NotImplementedError(
+ "prod is only defined for"
+ " numpy == 1.16 with NUMPY_ARRAY_FUNCTION_PROTOCOL enabled"
+ f" or for numpy >= 1.17 ({np.__version__} is installed)."
+ " Please try setting the NUMPY_ARRAY_FUNCTION_PROTOCOL environment variable"
+ " or updating your numpy version."
+ )
+ return np.prod(self, *args, **kwargs)
+
def __ito_if_needed(self, to_units):
if self.unitless and to_units == "radian":
return
@@ -1658,34 +1770,7 @@ class Quantity(PrettyIPython, SharedRegistryObject):
def __getattr__(self, item):
if item.startswith("__array_"):
# Handle array protocol attributes other than `__array__`
- if ARRAY_FALLBACK:
- # Deprecated fallback behavior
- warnings.warn(
- (
- f"Array protocol attribute {item} accessed, with unit of the "
- "Quantity being stripped. This attribute will become unavailable "
- "in the next minor version of Pint. To make this potentially "
- "incorrect attribute unavailable now, set the "
- "PINT_ARRAY_PROTOCOL_FALLBACK environment variable to 0 before "
- "importing Pint."
- ),
- DeprecationWarning,
- stacklevel=2,
- )
-
- if is_duck_array_type(type(self._magnitude)):
- # Defer to magnitude, and don't catch any AttributeErrors
- return getattr(self._magnitude, item)
- else:
- # If an `__array_` attribute is requested but the magnitude is not
- # a duck array, we convert the magnitude to a numpy ndarray.
- magnitude_as_array = _to_magnitude(
- self._magnitude, force_ndarray=True
- )
- return getattr(magnitude_as_array, item)
- else:
- # TODO (next minor version): ARRAY_FALLBACK is removed and this becomes the standard behavior
- raise AttributeError(f"Array protocol attribute {item} not available.")
+ 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
@@ -1782,38 +1867,46 @@ class Quantity(PrettyIPython, SharedRegistryObject):
return self._REGISTRY.Measurement(copy.copy(self.magnitude), error, self._units)
+ def _get_unit_definition(self, unit: str) -> UnitDefinition:
+ try:
+ return self._REGISTRY._units[unit]
+ except KeyError:
+ # pint#1062: The __init__ method of this object added the unit to
+ # UnitRegistry._units (e.g. units with prefix are added on the fly the
+ # first time they're used) but the key was later removed, e.g. because
+ # a Context with unit redefinitions was deactivated.
+ self._REGISTRY.parse_units(unit)
+ return self._REGISTRY._units[unit]
+
# methods/properties that help for math operations with offset units
@property
- def _is_multiplicative(self):
+ def _is_multiplicative(self) -> bool:
"""Check if the Quantity object has only multiplicative units."""
return not self._get_non_multiplicative_units()
- def _get_non_multiplicative_units(self):
+ def _get_non_multiplicative_units(self) -> List[str]:
"""Return a list of the of non-multiplicative units of the Quantity object."""
- non_mul_units = [
+ return [
unit
- for unit in self._units.keys()
- if not self._REGISTRY._units[unit].is_multiplicative
+ for unit in self._units
+ if not self._get_unit_definition(unit).is_multiplicative
]
- return non_mul_units
- def _get_delta_units(self):
+ def _get_delta_units(self) -> List[str]:
"""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
+ return [u for u in self._units if u.startswith("delta_")]
- def _has_compatible_delta(self, unit):
+ def _has_compatible_delta(self, unit: str) -> bool:
""""Check if Quantity object has a delta_unit that is compatible with unit
"""
deltas = self._get_delta_units()
if "delta_" + unit in deltas:
return True
- else: # Look for delta units with same dimension as the offset unit
- offset_unit_dim = self._REGISTRY._units[unit].reference
- for d in deltas:
- if self._REGISTRY._units[d].reference == offset_unit_dim:
- return True
- return False
+ # Look for delta units with same dimension as the offset unit
+ offset_unit_dim = self._get_unit_definition(unit).reference
+ return any(
+ self._get_unit_definition(d).reference == offset_unit_dim for d in deltas
+ )
def _ok_for_muldiv(self, no_offset_units=None):
"""Checks if Quantity object can be multiplied or divided
@@ -1839,6 +1932,88 @@ class Quantity(PrettyIPython, SharedRegistryObject):
def to_timedelta(self):
return datetime.timedelta(microseconds=self.to("microseconds").magnitude)
+ # Dask.array.Array ducking
+ def __dask_graph__(self):
+ if isinstance(self._magnitude, dask_array.Array):
+ return self._magnitude.__dask_graph__()
+ else:
+ return None
+
+ def __dask_keys__(self):
+ return self._magnitude.__dask_keys__()
+
+ @property
+ def __dask_optimize__(self):
+ return dask_array.Array.__dask_optimize__
+
+ @property
+ def __dask_scheduler__(self):
+ return dask_array.Array.__dask_scheduler__
+
+ def __dask_postcompute__(self):
+ func, args = self._magnitude.__dask_postcompute__()
+ return self._dask_finalize, (func, args, self.units)
+
+ def __dask_postpersist__(self):
+ func, args = self._magnitude.__dask_postpersist__()
+ return self._dask_finalize, (func, args, self.units)
+
+ @staticmethod
+ def _dask_finalize(results, func, args, units):
+ values = func(results, *args)
+ return Quantity(values, units)
+
+ @check_dask_array
+ def compute(self, **kwargs):
+ """Compute the Dask array wrapped by pint.Quantity.
+
+ Parameters
+ ----------
+ **kwargs : dict
+ Any keyword arguments to pass to ``dask.compute``.
+
+ Returns
+ -------
+ pint.Quantity
+ A pint.Quantity wrapped numpy array.
+ """
+ (result,) = compute(self, **kwargs)
+ return result
+
+ @check_dask_array
+ def persist(self, **kwargs):
+ """Persist the Dask Array wrapped by pint.Quantity.
+
+ Parameters
+ ----------
+ **kwargs : dict
+ Any keyword arguments to pass to ``dask.persist``.
+
+ Returns
+ -------
+ pint.Quantity
+ A pint.Quantity wrapped Dask array.
+ """
+ (result,) = persist(self, **kwargs)
+ return result
+
+ @check_dask_array
+ def visualize(self, **kwargs):
+ """Produce a visual representation of the Dask graph.
+
+ The graphviz library is required.
+
+ Parameters
+ ----------
+ **kwargs : dict
+ Any keyword arguments to pass to the ``dask.base.visualize`` function.
+
+ Returns
+ -------
+
+ """
+ visualize(self, **kwargs)
+
_Quantity = Quantity
diff --git a/pint/registry.py b/pint/registry.py
index 2aee210..0d09975 100644
--- a/pint/registry.py
+++ b/pint/registry.py
@@ -165,6 +165,8 @@ class BaseRegistry(metaclass=RegistryMeta):
string
fmt_locale :
locale identifier string, used in `format_babel`
+ non_int_type : type
+ numerical type used for non integer values. (Default: float)
"""
@@ -172,24 +174,8 @@ class BaseRegistry(metaclass=RegistryMeta):
#: type: Dict[str, (SourceIterator -> None)]
_parsers = None
- #: List to be used in addition of units when dir(registry) is called.
- #: Also used for autocompletion in IPython.
- _dir = [
- "Quantity",
- "Unit",
- "Measurement",
- "define",
- "load_definitions",
- "get_name",
- "get_symbol",
- "get_dimensionality",
- "get_base_units",
- "get_root_units",
- "parse_unit_name",
- "parse_units",
- "parse_expression",
- "convert",
- ]
+ #: Babel.Locale instance or None
+ fmt_locale = None
def __init__(
self,
@@ -200,6 +186,7 @@ class BaseRegistry(metaclass=RegistryMeta):
auto_reduce_dimensions=False,
preprocessors=None,
fmt_locale=None,
+ non_int_type=float,
):
self._register_parsers()
self._init_dynamic_classes()
@@ -216,7 +203,10 @@ class BaseRegistry(metaclass=RegistryMeta):
self.auto_reduce_dimensions = auto_reduce_dimensions
#: Default locale identifier string, used when calling format_babel without explicit locale.
- self.fmt_locale = self.set_fmt_locale(fmt_locale)
+ self.set_fmt_locale(fmt_locale)
+
+ #: Numerical type used for non integer values.
+ self.non_int_type = non_int_type
#: Map between name (string) and value (string) of defaults stored in the
#: definitions file.
@@ -299,8 +289,28 @@ class BaseRegistry(metaclass=RegistryMeta):
)
return self.parse_expression(item)
+ def __contains__(self, item):
+ """Support checking prefixed units with the `in` operator
+ """
+ try:
+ self.__getattr__(item)
+ return True
+ except UndefinedUnitError:
+ return False
+
def __dir__(self):
- return list(self._units.keys()) + self._dir
+ #: Calling dir(registry) gives all units, methods, and attributes.
+ #: Also used for autocompletion in IPython.
+ return list(self._units.keys()) + list(object.__dir__(self))
+
+ def __iter__(self):
+ """Allows for listing all units in registry with `list(ureg)`.
+
+ Returns
+ -------
+ Iterator over names of all units in registry, ordered alphabetically.
+ """
+ return iter(sorted(self._units.keys()))
def set_fmt_locale(self, loc):
"""Change the locale used by default by `format_babel`.
@@ -319,6 +329,9 @@ class BaseRegistry(metaclass=RegistryMeta):
self.fmt_locale = loc
+ def UnitsContainer(self, *args, **kwargs):
+ return UnitsContainer(*args, non_int_type=self.non_int_type, **kwargs)
+
@property
def default_format(self):
"""Default formatting string for quantities."""
@@ -340,7 +353,7 @@ class BaseRegistry(metaclass=RegistryMeta):
if isinstance(definition, str):
for line in definition.split("\n"):
- self._define(Definition.from_string(line))
+ self._define(Definition.from_string(line, self.non_int_type))
else:
self._define(definition)
@@ -411,7 +424,7 @@ class BaseRegistry(metaclass=RegistryMeta):
"delta_" + alias for alias in definition.aliases
)
- d_reference = UnitsContainer(
+ d_reference = self.UnitsContainer(
{ref: value for ref, value in definition.reference.items()}
)
@@ -561,7 +574,7 @@ class BaseRegistry(metaclass=RegistryMeta):
raise ex
else:
try:
- self.define(Definition.from_string(line))
+ self.define(Definition.from_string(line, self.non_int_type))
except DefinitionSyntaxError as ex:
if ex.lineno is None:
ex.lineno = no
@@ -589,7 +602,7 @@ class BaseRegistry(metaclass=RegistryMeta):
prefix, base_name = "", unit_name
try:
- uc = ParserHelper.from_word(base_name)
+ uc = ParserHelper.from_word(base_name, self.non_int_type)
bu = self._get_root_units(uc)
di = self._get_dimensionality(uc)
@@ -635,7 +648,11 @@ class BaseRegistry(metaclass=RegistryMeta):
symbol = self.get_symbol(name)
prefix_def = self._prefixes[prefix]
self._units[name] = UnitDefinition(
- name, symbol, (), prefix_def.converter, UnitsContainer({unit_name: 1})
+ name,
+ symbol,
+ (),
+ prefix_def.converter,
+ self.UnitsContainer({unit_name: 1}),
)
return prefix + unit_name
@@ -665,6 +682,9 @@ class BaseRegistry(metaclass=RegistryMeta):
"""Convert unit or dict of units or dimensions to a dict of base dimensions
dimensions
"""
+
+ # TODO: This should be to_units_container(input_units, self)
+ # but this tries to reparse and fail for dimensions.
input_units = to_units_container(input_units)
return self._get_dimensionality(input_units)
@@ -673,7 +693,7 @@ class BaseRegistry(metaclass=RegistryMeta):
"""Convert a UnitsContainer to base dimensions.
"""
if not input_units:
- return UnitsContainer()
+ return self.UnitsContainer()
cache = self._cache.dimensionality
@@ -688,7 +708,7 @@ class BaseRegistry(metaclass=RegistryMeta):
if "[]" in accumulator:
del accumulator["[]"]
- dims = UnitsContainer({k: v for k, v in accumulator.items() if v != 0})
+ dims = self.UnitsContainer({k: v for k, v in accumulator.items() if v != 0})
cache[input_units] = dims
@@ -759,7 +779,7 @@ class BaseRegistry(metaclass=RegistryMeta):
multiplicative factor, base units
"""
- input_units = to_units_container(input_units)
+ input_units = to_units_container(input_units, self)
f, units = self._get_root_units(input_units, check_nonmult)
@@ -787,7 +807,7 @@ class BaseRegistry(metaclass=RegistryMeta):
"""
if not input_units:
- return 1, UnitsContainer()
+ return 1, self.UnitsContainer()
cache = self._cache.root_units
try:
@@ -799,7 +819,9 @@ class BaseRegistry(metaclass=RegistryMeta):
self._get_root_units_recurse(input_units, 1, accumulators)
factor = accumulators[0]
- units = UnitsContainer({k: v for k, v in accumulators[1].items() if v != 0})
+ units = self.UnitsContainer(
+ {k: v for k, v in accumulators[1].items() if v != 0}
+ )
# Check if any of the final units is non multiplicative and return None instead.
if check_nonmult:
@@ -865,6 +887,33 @@ class BaseRegistry(metaclass=RegistryMeta):
src_dim = self._get_dimensionality(input_units)
return self._cache.dimensional_equivalents[src_dim]
+ def is_compatible_with(self, obj1, obj2, *contexts, **ctx_kwargs):
+ """ check if the other object is compatible
+
+ Parameters
+ ----------
+ obj1, obj2
+ The objects to check against each other. Treated as
+ dimensionless if not a Quantity, Unit or str.
+ *contexts : str or pint.Context
+ Contexts to use in the transformation.
+ **ctx_kwargs :
+ Values for the Context/s
+
+ Returns
+ -------
+ bool
+ """
+ if isinstance(obj1, (self.Quantity, self.Unit)):
+ return obj1.is_compatible_with(obj2, *contexts, **ctx_kwargs)
+
+ if isinstance(obj1, str):
+ return self.parse_expression(obj1).is_compatible_with(
+ obj2, *contexts, **ctx_kwargs
+ )
+
+ return not isinstance(obj2, (self.Quantity, self.Unit))
+
def convert(self, value, src, dst, inplace=False):
"""Convert value from some source to destination units.
@@ -1041,19 +1090,19 @@ class BaseRegistry(metaclass=RegistryMeta):
"""
cache = self._cache.parse_unit
- if as_delta:
- try:
- return cache[input_string]
- except KeyError:
- pass
+ # Issue #1097: it is possible, when a unit was defined while a different context
+ # was active, that the unit is in self._cache.parse_unit but not in self._units.
+ # If this is the case, force self._units to be repopulated.
+ if as_delta and input_string in cache and input_string in self._units:
+ return cache[input_string]
if not input_string:
- return UnitsContainer()
+ return self.UnitsContainer()
# Sanitize input_string with whitespaces.
input_string = input_string.strip()
- units = ParserHelper.from_string(input_string)
+ units = ParserHelper.from_string(input_string, self.non_int_type)
if units.scale != 1:
raise ValueError("Unit expression cannot have a scaling factor.")
@@ -1070,7 +1119,7 @@ class BaseRegistry(metaclass=RegistryMeta):
cname = "delta_" + cname
ret[cname] = value
- ret = UnitsContainer(ret)
+ ret = self.UnitsContainer(ret)
if as_delta:
cache[input_string] = ret
@@ -1078,6 +1127,15 @@ class BaseRegistry(metaclass=RegistryMeta):
return ret
def _eval_token(self, token, case_sensitive=True, use_decimal=False, **values):
+
+ # TODO: remove this code when use_decimal is deprecated
+ if use_decimal:
+ raise DeprecationWarning(
+ "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n"
+ ">>> from decimal import Decimal\n"
+ ">>> ureg = UnitRegistry(non_int_type=Decimal)"
+ )
+
token_type = token[0]
token_text = token[1]
if token_type == NAME:
@@ -1088,12 +1146,12 @@ class BaseRegistry(metaclass=RegistryMeta):
else:
return self.Quantity(
1,
- UnitsContainer(
+ self.UnitsContainer(
{self.get_name(token_text, case_sensitive=case_sensitive): 1}
),
)
elif token_type == NUMBER:
- return ParserHelper.eval_token(token, use_decimal=use_decimal)
+ return ParserHelper.eval_token(token, non_int_type=self.non_int_type)
else:
raise Exception("unknown token type")
@@ -1123,7 +1181,7 @@ class BaseRegistry(metaclass=RegistryMeta):
"""
if not input_string:
- return self.Quantity(1)
+ return [] if many else None
# Parse string
pattern = pattern_to_regex(pattern)
@@ -1177,6 +1235,14 @@ class BaseRegistry(metaclass=RegistryMeta):
"""
+ # TODO: remove this code when use_decimal is deprecated
+ if use_decimal:
+ raise DeprecationWarning(
+ "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n"
+ ">>> from decimal import Decimal\n"
+ ">>> ureg = UnitRegistry(non_int_type=Decimal)"
+ )
+
if not input_string:
return self.Quantity(1)
@@ -1186,9 +1252,7 @@ class BaseRegistry(metaclass=RegistryMeta):
gen = tokenizer(input_string)
return build_eval_tree(gen).evaluate(
- lambda x: self._eval_token(
- x, case_sensitive=case_sensitive, use_decimal=use_decimal, **values
- )
+ lambda x: self._eval_token(x, case_sensitive=case_sensitive, **values)
)
__call__ = parse_expression
@@ -1429,7 +1493,11 @@ class ContextRegistry(BaseRegistry):
def _parse_context(self, ifile):
try:
self.add_context(
- Context.from_lines(ifile.block_iter(), self.get_dimensionality)
+ Context.from_lines(
+ ifile.block_iter(),
+ self.get_dimensionality,
+ non_int_type=self.non_int_type,
+ )
)
except KeyError as e:
raise DefinitionSyntaxError(f"unknown dimension {e} in context")
@@ -1506,7 +1574,7 @@ class ContextRegistry(BaseRegistry):
on_redefinition_backup = self._on_redefinition
self._on_redefinition = "ignore"
try:
- for ctx in self._active_ctx.contexts:
+ for ctx in reversed(self._active_ctx.contexts):
for definition in ctx.redefinitions:
self._redefine(definition)
finally:
@@ -1626,12 +1694,16 @@ class ContextRegistry(BaseRegistry):
Examples
--------
- Context can be called by their name::
+ Context can be called by their name:
+ >>> import pint
+ >>> ureg = pint.UnitRegistry()
+ >>> ureg.add_context(pint.Context('one'))
+ >>> ureg.add_context(pint.Context('two'))
>>> with ureg.context('one'):
... pass
- If a context has an argument, you can specify its value as a keyword argument::
+ If a context has an argument, you can specify its value as a keyword argument:
>>> with ureg.context('one', n=1):
... pass
@@ -1641,13 +1713,13 @@ class ContextRegistry(BaseRegistry):
>>> 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
- A nested context inherits the defaults from the containing context::
+ A nested context inherits the defaults from the containing context:
>>> with ureg.context('one', n=1):
... # Here n takes the value of the outer context
@@ -1687,9 +1759,9 @@ class ContextRegistry(BaseRegistry):
Example
-------
- >>> @ureg.with_context('sp')
- ... def my_cool_fun(wavelenght):
- ... print('This wavelength is equivalent to: %s', wavelength.to('terahertz'))
+ >>> @ureg.with_context('sp')
+ ... def my_cool_fun(wavelength):
+ ... print('This wavelength is equivalent to: %s', wavelength.to('terahertz'))
"""
def decorator(func):
@@ -1834,10 +1906,12 @@ class SystemRegistry(BaseRegistry):
self._register_parser("@system", self._parse_system)
def _parse_group(self, ifile):
- self.Group.from_lines(ifile.block_iter(), self.define)
+ self.Group.from_lines(ifile.block_iter(), self.define, self.non_int_type)
def _parse_system(self, ifile):
- self.System.from_lines(ifile.block_iter(), self.get_root_units)
+ self.System.from_lines(
+ ifile.block_iter(), self.get_root_units, self.non_int_type
+ )
def get_group(self, name, create_if_needed=True):
"""Return a Group.
@@ -1974,7 +2048,7 @@ class SystemRegistry(BaseRegistry):
# as it has a UnitsContainer intermediate
units = to_units_container(units, self)
- destination_units = UnitsContainer()
+ destination_units = self.UnitsContainer()
bu = self.get_system(system, False).base_units
@@ -1984,7 +2058,7 @@ class SystemRegistry(BaseRegistry):
new_unit = to_units_container(new_unit, self)
destination_units *= new_unit ** value
else:
- destination_units *= UnitsContainer({unit: value})
+ destination_units *= self.UnitsContainer({unit: value})
base_factor = self.convert(factor, units, destination_units)
@@ -2058,6 +2132,7 @@ class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry):
auto_reduce_dimensions=False,
preprocessors=None,
fmt_locale=None,
+ non_int_type=float,
):
super().__init__(
@@ -2071,6 +2146,7 @@ class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry):
auto_reduce_dimensions=auto_reduce_dimensions,
preprocessors=preprocessors,
fmt_locale=fmt_locale,
+ non_int_type=non_int_type,
)
def pi_theorem(self, quantities):
diff --git a/pint/systems.py b/pint/systems.py
index 05d2b6f..b72ce53 100644
--- a/pint/systems.py
+++ b/pint/systems.py
@@ -10,9 +10,8 @@
import re
-from pint.compat import babel_parse
-
from .babel_names import _babel_systems
+from .compat import babel_parse
from .definitions import Definition, UnitDefinition
from .errors import DefinitionSyntaxError, RedefinitionError
from .util import (
@@ -174,7 +173,7 @@ class Group(SharedRegistryObject):
self.invalidate_members()
@classmethod
- def from_lines(cls, lines, define_func):
+ def from_lines(cls, lines, define_func, non_int_type=float):
"""Return a Group object parsing an iterable of lines.
Parameters
@@ -208,7 +207,7 @@ class Group(SharedRegistryObject):
for lineno, line in lines:
if "=" in line:
# Is a definition
- definition = Definition.from_string(line)
+ definition = Definition.from_string(line, non_int_type=non_int_type)
if not isinstance(definition, UnitDefinition):
raise DefinitionSyntaxError(
"Only UnitDefinition are valid inside _used_groups, not "
@@ -357,7 +356,7 @@ class System(SharedRegistryObject):
return self.name
@classmethod
- def from_lines(cls, lines, get_root_func):
+ def from_lines(cls, lines, get_root_func, non_int_type=float):
lines = SourceIterator(lines)
lineno, header = next(lines)
@@ -399,7 +398,9 @@ class System(SharedRegistryObject):
)
# Here we find new_unit expanded in terms of root_units
- new_unit_expanded = to_units_container(get_root_func(new_unit)[1])
+ new_unit_expanded = to_units_container(
+ get_root_func(new_unit)[1], cls._REGISTRY
+ )
# We require that the old unit is present in the new_unit expanded
if old_unit not in new_unit_expanded:
diff --git a/pint/testsuite/__init__.py b/pint/testsuite/__init__.py
index 1614f59..1fbc233 100644
--- a/pint/testsuite/__init__.py
+++ b/pint/testsuite/__init__.py
@@ -101,17 +101,31 @@ class QuantityTestCase(BaseTestCase):
if isinstance(m1, ndarray) or isinstance(m2, ndarray):
np.testing.assert_array_equal(m1, m2, err_msg=msg)
+ elif math.isnan(m1):
+ self.assertTrue(math.isnan(m2), msg)
+ elif math.isnan(m2):
+ self.assertTrue(math.isnan(m1), msg)
else:
self.assertEqual(m1, m2, msg)
def assertQuantityAlmostEqual(self, first, second, rtol=1e-07, atol=0, msg=None):
if msg is None:
- msg = "Comparing %r and %r. " % (first, second)
+ try:
+ msg = "Comparing %r and %r. " % (first, second)
+ except TypeError:
+ try:
+ msg = "Comparing %s and %s. " % (first, second)
+ except Exception:
+ msg = "Comparing"
m1, m2 = self._get_comparable_magnitudes(first, second, msg)
if isinstance(m1, ndarray) or isinstance(m2, ndarray):
np.testing.assert_allclose(m1, m2, rtol=rtol, atol=atol, err_msg=msg)
+ elif math.isnan(m1):
+ self.assertTrue(math.isnan(m2), msg)
+ elif math.isnan(m2):
+ self.assertTrue(math.isnan(m1), msg)
else:
self.assertLessEqual(abs(m1 - m2), atol + rtol * abs(m2), msg=msg)
diff --git a/pint/testsuite/helpers.py b/pint/testsuite/helpers.py
index 5a91391..eae7650 100644
--- a/pint/testsuite/helpers.py
+++ b/pint/testsuite/helpers.py
@@ -29,14 +29,6 @@ def requires_not_array_function_protocol():
)
-def requires_numpy18():
- if not HAS_NUMPY:
- return unittest.skip("Requires NumPy")
- return unittest.skipUnless(
- StrictVersion(NUMPY_VER) >= StrictVersion("1.8"), "Requires NumPy >= 1.8"
- )
-
-
def requires_numpy_previous_than(version):
if not HAS_NUMPY:
return unittest.skip("Requires NumPy")
@@ -60,20 +52,24 @@ def requires_numpy():
def requires_not_numpy():
- return unittest.skipIf(HAS_NUMPY, "Requires NumPy is not installed.")
+ return unittest.skipIf(HAS_NUMPY, "Requires NumPy not to be installed.")
def requires_babel():
return unittest.skipUnless(HAS_BABEL, "Requires Babel with units support")
+def requires_not_babel():
+ return unittest.skipIf(HAS_BABEL, "Requires Babel not to be installed")
+
+
def requires_uncertainties():
return unittest.skipUnless(HAS_UNCERTAINTIES, "Requires Uncertainties")
def requires_not_uncertainties():
return unittest.skipIf(
- HAS_UNCERTAINTIES, "Requires Uncertainties is not installed."
+ HAS_UNCERTAINTIES, "Requires Uncertainties not to be installed."
)
diff --git a/pint/testsuite/test_application_registry.py b/pint/testsuite/test_application_registry.py
index 6e2055c..a068445 100644
--- a/pint/testsuite/test_application_registry.py
+++ b/pint/testsuite/test_application_registry.py
@@ -14,48 +14,59 @@ from pint import (
from pint.testsuite import BaseTestCase
from pint.testsuite.helpers import requires_uncertainties
+from .parameterized import ParameterizedTestMixin
-class TestDefaultApplicationRegistry(BaseTestCase):
- def test_unit(self):
+allprotos = ParameterizedTestMixin.parameterize(
+ ("protocol",), [(p,) for p in range(pickle.HIGHEST_PROTOCOL + 1)]
+)
+
+
+class TestDefaultApplicationRegistry(BaseTestCase, ParameterizedTestMixin):
+ @allprotos
+ def test_unit(self, protocol):
u = Unit("kg")
self.assertEqual(str(u), "kilogram")
- u = pickle.loads(pickle.dumps(u))
+ u = pickle.loads(pickle.dumps(u, protocol))
self.assertEqual(str(u), "kilogram")
- def test_quantity_1arg(self):
+ @allprotos
+ def test_quantity_1arg(self, protocol):
q = Quantity("123 kg")
self.assertEqual(str(q.units), "kilogram")
self.assertEqual(q.to("t").magnitude, 0.123)
- q = pickle.loads(pickle.dumps(q))
+ q = pickle.loads(pickle.dumps(q, protocol))
self.assertEqual(str(q.units), "kilogram")
self.assertEqual(q.to("t").magnitude, 0.123)
- def test_quantity_2args(self):
+ @allprotos
+ def test_quantity_2args(self, protocol):
q = Quantity(123, "kg")
self.assertEqual(str(q.units), "kilogram")
self.assertEqual(q.to("t").magnitude, 0.123)
- q = pickle.loads(pickle.dumps(q))
+ q = pickle.loads(pickle.dumps(q, protocol))
self.assertEqual(str(q.units), "kilogram")
self.assertEqual(q.to("t").magnitude, 0.123)
@requires_uncertainties()
- def test_measurement_2args(self):
+ @allprotos
+ def test_measurement_2args(self, protocol):
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))
+ m = pickle.loads(pickle.dumps(m, protocol))
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):
+ @allprotos
+ def test_measurement_3args(self, protocol):
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))
+ m = pickle.loads(pickle.dumps(m, protocol))
self.assertEqual(m.value.magnitude, 123)
self.assertEqual(m.error.magnitude, 15)
self.assertEqual(str(m.units), "kilogram")
@@ -65,25 +76,27 @@ class TestDefaultApplicationRegistry(BaseTestCase):
u = ureg.Unit("kg")
self.assertEqual(str(u), "kilogram")
- def test_pickle_crash(self):
+ @allprotos
+ def test_pickle_crash(self, protocol):
ureg = UnitRegistry(None)
ureg.define("foo = []")
q = ureg.Quantity(123, "foo")
- b = pickle.dumps(q)
+ b = pickle.dumps(q, protocol)
self.assertRaises(UndefinedUnitError, pickle.loads, b)
- b = pickle.dumps(q.units)
+ b = pickle.dumps(q.units, protocol)
self.assertRaises(UndefinedUnitError, pickle.loads, b)
@requires_uncertainties()
- def test_pickle_crash_measurement(self):
+ @allprotos
+ def test_pickle_crash_measurement(self, protocol):
ureg = UnitRegistry(None)
ureg.define("foo = []")
m = ureg.Quantity(123, "foo").plus_minus(10)
- b = pickle.dumps(m)
+ b = pickle.dumps(m, protocol)
self.assertRaises(UndefinedUnitError, pickle.loads, b)
-class TestCustomApplicationRegistry(BaseTestCase):
+class TestCustomApplicationRegistry(BaseTestCase, ParameterizedTestMixin):
def setUp(self):
super().setUp()
self.ureg_bak = get_application_registry()
@@ -97,52 +110,57 @@ class TestCustomApplicationRegistry(BaseTestCase):
super().tearDown()
set_application_registry(self.ureg_bak)
- def test_unit(self):
+ @allprotos
+ def test_unit(self, protocol):
u = Unit("foo")
self.assertEqual(str(u), "foo")
- u = pickle.loads(pickle.dumps(u))
+ u = pickle.loads(pickle.dumps(u, protocol))
self.assertEqual(str(u), "foo")
- def test_quantity_1arg(self):
+ @allprotos
+ def test_quantity_1arg(self, protocol):
q = Quantity("123 foo")
self.assertEqual(str(q.units), "foo")
self.assertEqual(q.to("bar").magnitude, 246)
- q = pickle.loads(pickle.dumps(q))
+ q = pickle.loads(pickle.dumps(q, protocol))
self.assertEqual(str(q.units), "foo")
self.assertEqual(q.to("bar").magnitude, 246)
- def test_quantity_2args(self):
+ @allprotos
+ def test_quantity_2args(self, protocol):
q = Quantity(123, "foo")
self.assertEqual(str(q.units), "foo")
self.assertEqual(q.to("bar").magnitude, 246)
- q = pickle.loads(pickle.dumps(q))
+ q = pickle.loads(pickle.dumps(q, protocol))
self.assertEqual(str(q.units), "foo")
self.assertEqual(q.to("bar").magnitude, 246)
@requires_uncertainties()
- def test_measurement_2args(self):
+ @allprotos
+ def test_measurement_2args(self, protocol):
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))
+ m = pickle.loads(pickle.dumps(m, protocol))
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):
+ @allprotos
+ def test_measurement_3args(self, protocol):
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))
+ m = pickle.loads(pickle.dumps(m, protocol))
self.assertEqual(m.value.magnitude, 123)
self.assertEqual(m.error.magnitude, 5)
self.assertEqual(str(m.units), "foo")
-class TestSwapApplicationRegistry(BaseTestCase):
+class TestSwapApplicationRegistry(BaseTestCase, ParameterizedTestMixin):
"""Test that the constructors of Quantity, Unit, and Measurement capture
the registry that is set as the application registry at creation time
@@ -167,12 +185,13 @@ class TestSwapApplicationRegistry(BaseTestCase):
def tearDown(self):
set_application_registry(self.ureg_bak)
- def test_quantity_1arg(self):
+ @allprotos
+ def test_quantity_1arg(self, protocol):
set_application_registry(self.ureg1)
q1 = Quantity("1 foo")
set_application_registry(self.ureg2)
q2 = Quantity("1 foo")
- q3 = pickle.loads(pickle.dumps(q1))
+ q3 = pickle.loads(pickle.dumps(q1, protocol))
assert q1.dimensionality == {"[dim1]": 1}
assert q2.dimensionality == {"[dim2]": 1}
assert q3.dimensionality == {"[dim2]": 1}
@@ -180,12 +199,13 @@ class TestSwapApplicationRegistry(BaseTestCase):
assert q2.to("bar").magnitude == 3
assert q3.to("bar").magnitude == 3
- def test_quantity_2args(self):
+ @allprotos
+ def test_quantity_2args(self, protocol):
set_application_registry(self.ureg1)
q1 = Quantity(1, "foo")
set_application_registry(self.ureg2)
q2 = Quantity(1, "foo")
- q3 = pickle.loads(pickle.dumps(q1))
+ q3 = pickle.loads(pickle.dumps(q1, protocol))
assert q1.dimensionality == {"[dim1]": 1}
assert q2.dimensionality == {"[dim2]": 1}
assert q3.dimensionality == {"[dim2]": 1}
@@ -193,23 +213,25 @@ class TestSwapApplicationRegistry(BaseTestCase):
assert q2.to("bar").magnitude == 3
assert q3.to("bar").magnitude == 3
- def test_unit(self):
+ @allprotos
+ def test_unit(self, protocol):
set_application_registry(self.ureg1)
u1 = Unit("bar")
set_application_registry(self.ureg2)
u2 = Unit("bar")
- u3 = pickle.loads(pickle.dumps(u1))
+ u3 = pickle.loads(pickle.dumps(u1, protocol))
assert u1.dimensionality == {"[dim1]": 1}
assert u2.dimensionality == {"[dim2]": 1}
assert u3.dimensionality == {"[dim2]": 1}
@requires_uncertainties()
- def test_measurement_2args(self):
+ @allprotos
+ def test_measurement_2args(self, protocol):
set_application_registry(self.ureg1)
m1 = Measurement(Quantity(10, "foo"), Quantity(1, "foo"))
set_application_registry(self.ureg2)
m2 = Measurement(Quantity(10, "foo"), Quantity(1, "foo"))
- m3 = pickle.loads(pickle.dumps(m1))
+ m3 = pickle.loads(pickle.dumps(m1, protocol))
assert m1.dimensionality == {"[dim1]": 1}
assert m2.dimensionality == {"[dim2]": 1}
@@ -222,12 +244,13 @@ class TestSwapApplicationRegistry(BaseTestCase):
self.assertEqual(m3.to("bar").error.magnitude, 3)
@requires_uncertainties()
- def test_measurement_3args(self):
+ @allprotos
+ def test_measurement_3args(self, protocol):
set_application_registry(self.ureg1)
m1 = Measurement(10, 1, "foo")
set_application_registry(self.ureg2)
m2 = Measurement(10, 1, "foo")
- m3 = pickle.loads(pickle.dumps(m1))
+ m3 = pickle.loads(pickle.dumps(m1, protocol))
assert m1.dimensionality == {"[dim1]": 1}
assert m2.dimensionality == {"[dim2]": 1}
diff --git a/pint/testsuite/test_babel.py b/pint/testsuite/test_babel.py
index 2aac0cd..b3d5303 100644
--- a/pint/testsuite/test_babel.py
+++ b/pint/testsuite/test_babel.py
@@ -5,6 +5,14 @@ from pint.testsuite import BaseTestCase, helpers
class TestBabel(BaseTestCase):
+ @helpers.requires_not_babel()
+ def test_no_babel(self):
+ ureg = UnitRegistry()
+ distance = 24.0 * ureg.meter
+ self.assertRaises(
+ Exception, distance.format_babel, locale="fr_FR", length="long"
+ )
+
@helpers.requires_babel()
def test_format(self):
ureg = UnitRegistry()
@@ -46,9 +54,30 @@ class TestBabel(BaseTestCase):
mks = ureg.get_system("mks")
self.assertEqual(mks.format_babel(locale="fr_FR"), "métrique")
- def test_nobabel(self):
+ @helpers.requires_babel()
+ def test_no_registry_locale(self):
ureg = UnitRegistry()
distance = 24.0 * ureg.meter
- self.assertRaises(
- Exception, distance.format_babel, locale="fr_FR", length="long"
- )
+ self.assertRaises(Exception, distance.format_babel)
+
+ @helpers.requires_babel()
+ def test_str(self):
+ ureg = UnitRegistry()
+ d = 24.0 * ureg.meter
+
+ s = "24.0 meter"
+ self.assertEqual(str(d), s)
+ self.assertEqual("%s" % d, s)
+ self.assertEqual("{}".format(d), s)
+
+ ureg.set_fmt_locale("fr_FR")
+ s = "24.0 mètres"
+ self.assertEqual(str(d), s)
+ self.assertEqual("%s" % d, s)
+ self.assertEqual("{}".format(d), s)
+
+ ureg.set_fmt_locale(None)
+ s = "24.0 meter"
+ self.assertEqual(str(d), s)
+ self.assertEqual("%s" % d, s)
+ self.assertEqual("{}".format(d), s)
diff --git a/pint/testsuite/test_compat.py b/pint/testsuite/test_compat.py
new file mode 100644
index 0000000..926a392
--- /dev/null
+++ b/pint/testsuite/test_compat.py
@@ -0,0 +1,104 @@
+import math
+from datetime import datetime, timedelta
+
+import pytest
+
+from pint.compat import eq, isnan, zero_or_nan
+
+from .helpers import requires_numpy
+
+
+@pytest.mark.parametrize("check_all", [False, True])
+def test_eq(check_all):
+ assert eq(0, 0, check_all)
+ assert not eq(0, 1, check_all)
+
+
+@requires_numpy()
+def test_eq_numpy():
+ import numpy as np
+
+ assert eq(np.array([1, 2]), np.array([1, 2]), True)
+ assert not eq(np.array([1, 2]), np.array([1, 3]), True)
+ np.testing.assert_equal(
+ eq(np.array([1, 2]), np.array([1, 2]), False), np.array([True, True])
+ )
+ np.testing.assert_equal(
+ eq(np.array([1, 2]), np.array([1, 3]), False), np.array([True, False])
+ )
+
+ # Mixed numpy/scalar
+ assert eq(1, np.array([1, 1]), True)
+ assert eq(np.array([1, 1]), 1, True)
+ assert not eq(1, np.array([1, 2]), True)
+ assert not eq(np.array([1, 2]), 1, True)
+
+
+@pytest.mark.parametrize("check_all", [False, True])
+def test_isnan(check_all):
+ assert not isnan(0, check_all)
+ assert not isnan(0.0, check_all)
+ assert isnan(math.nan, check_all)
+ assert not isnan(datetime(2000, 1, 1), check_all)
+ assert not isnan(timedelta(seconds=1), check_all)
+ assert not isnan("foo", check_all)
+
+
+@requires_numpy()
+def test_isnan_numpy():
+ import numpy as np
+
+ assert isnan(np.nan, True)
+ assert isnan(np.nan, False)
+ assert not isnan(np.array([0, 0]), True)
+ assert isnan(np.array([0, np.nan]), True)
+ assert not isnan(np.array(["A", "B"]), True)
+ np.testing.assert_equal(
+ isnan(np.array([1, np.nan]), False), np.array([False, True])
+ )
+ np.testing.assert_equal(
+ isnan(np.array(["A", "B"]), False), np.array([False, False])
+ )
+
+
+@requires_numpy()
+def test_isnan_nat():
+ import numpy as np
+
+ a = np.array(["2000-01-01", "NaT"], dtype="M8")
+ b = np.array(["2000-01-01", "2000-01-02"], dtype="M8")
+ assert isnan(a, True)
+ assert not isnan(b, True)
+ np.testing.assert_equal(isnan(a, False), np.array([False, True]))
+ np.testing.assert_equal(isnan(b, False), np.array([False, False]))
+
+ # Scalar numpy.datetime64
+ assert not isnan(a[0], True)
+ assert not isnan(a[0], False)
+ assert isnan(a[1], True)
+ assert isnan(a[1], False)
+
+
+@pytest.mark.parametrize("check_all", [False, True])
+def test_zero_or_nan(check_all):
+ assert zero_or_nan(0, check_all)
+ assert zero_or_nan(math.nan, check_all)
+ assert not zero_or_nan(1, check_all)
+ assert not zero_or_nan(datetime(2000, 1, 1), check_all)
+ assert not zero_or_nan(timedelta(seconds=1), check_all)
+ assert not zero_or_nan("foo", check_all)
+
+
+@requires_numpy()
+def test_zero_or_nan_numpy():
+ import numpy as np
+
+ assert zero_or_nan(np.nan, True)
+ assert zero_or_nan(np.nan, False)
+ assert zero_or_nan(np.array([0, np.nan]), True)
+ assert not zero_or_nan(np.array([1, np.nan]), True)
+ assert not zero_or_nan(np.array([0, 1]), True)
+ assert not zero_or_nan(np.array(["A", "B"]), True)
+ np.testing.assert_equal(
+ zero_or_nan(np.array([0, 1, np.nan]), False), np.array([True, False, True])
+ )
diff --git a/pint/testsuite/test_compat_downcast.py b/pint/testsuite/test_compat_downcast.py
index e85369a..a47f168 100644
--- a/pint/testsuite/test_compat_downcast.py
+++ b/pint/testsuite/test_compat_downcast.py
@@ -5,6 +5,7 @@ from pint import UnitRegistry
# Conditionally import NumPy and any upcast type libraries
np = pytest.importorskip("numpy", reason="NumPy is not available")
sparse = pytest.importorskip("sparse", reason="sparse is not available")
+da = pytest.importorskip("dask.array", reason="Dask is not available")
# Set up unit registry and sample
ureg = UnitRegistry(force_ndarray_like=True)
@@ -16,7 +17,7 @@ def identity(x):
return x
-@pytest.fixture(params=["sparse", "masked_array"])
+@pytest.fixture(params=["sparse", "masked_array", "dask_array"])
def array(request):
"""Generate 5x5 arrays of given type for tests."""
if request.param == "sparse":
@@ -30,6 +31,8 @@ def array(request):
np.arange(25, dtype=np.float).reshape((5, 5)),
mask=np.logical_not(np.triu(np.ones((5, 5)))),
)
+ elif request.param == "dask_array":
+ return da.arange(25, chunks=5, dtype=float).reshape((5, 5))
@pytest.mark.parametrize(
@@ -57,8 +60,8 @@ def array(request):
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),
+ lambda x: np.reshape(x, (25,)),
+ lambda x: np.reshape(x, (25,)),
identity,
id="reshape function",
),
diff --git a/pint/testsuite/test_contexts.py b/pint/testsuite/test_contexts.py
index bc575ed..b57a4df 100644
--- a/pint/testsuite/test_contexts.py
+++ b/pint/testsuite/test_contexts.py
@@ -835,6 +835,38 @@ class TestContextRedefinitions(QuantityTestCase):
with ureg.context("mult_to_nonmult"):
self.assertAlmostEqual(k.to("bogodegrees").magnitude, (100 - 123) / 5)
+ def test_stack_contexts(self):
+ ureg = UnitRegistry(
+ """
+ a = [dim1]
+ b = 1/2 a
+ c = 1/3 a
+ d = [dim2]
+
+ @context c1
+ b = 1/4 a
+ c = 1/6 a
+ [dim1]->[dim2]: value * 2 d/a
+ @end
+ @context c2
+ b = 1/5 a
+ [dim1]->[dim2]: value * 3 d/a
+ @end
+ """.splitlines()
+ )
+ q = ureg.Quantity(1, "a")
+ assert q.to("b").magnitude == 2
+ assert q.to("c").magnitude == 3
+ assert q.to("b", "c1").magnitude == 4
+ assert q.to("c", "c1").magnitude == 6
+ assert q.to("d", "c1").magnitude == 2
+ assert q.to("b", "c2").magnitude == 5
+ assert q.to("c", "c2").magnitude == 3
+ assert q.to("d", "c2").magnitude == 3
+ assert q.to("b", "c1", "c2").magnitude == 5 # c2 takes precedence
+ assert q.to("c", "c1", "c2").magnitude == 6 # c2 doesn't change it, so use c1
+ assert q.to("d", "c1", "c2").magnitude == 3 # c2 takes precedence
+
def test_err_to_base_unit(self):
with self.assertRaises(DefinitionSyntaxError) as e:
Context.from_lines(["@context c", "x = [d]"])
diff --git a/pint/testsuite/test_dask.py b/pint/testsuite/test_dask.py
new file mode 100644
index 0000000..3465752
--- /dev/null
+++ b/pint/testsuite/test_dask.py
@@ -0,0 +1,196 @@
+import importlib
+import os
+
+import pytest
+
+from pint import UnitRegistry
+
+# Conditionally import NumPy, Dask, and Distributed
+np = pytest.importorskip("numpy", reason="NumPy is not available")
+dask = pytest.importorskip("dask", reason="Dask is not available")
+distributed = pytest.importorskip("distributed", reason="Distributed is not available")
+
+from dask.distributed import Client # isort:skip
+from distributed.client import futures_of # isort:skip
+from distributed.utils_test import cluster, gen_cluster, loop # isort:skip
+
+loop = loop # flake8
+
+ureg = UnitRegistry(force_ndarray_like=True)
+units_ = "kilogram"
+
+
+def add_five(q):
+ return q + 5 * ureg(units_)
+
+
+@pytest.fixture
+def dask_array():
+ return dask.array.arange(0, 25, chunks=5, dtype=float).reshape((5, 5))
+
+
+@pytest.fixture
+def numpy_array():
+ return np.arange(0, 25, dtype=float).reshape((5, 5)) + 5
+
+
+def test_is_dask_collection(dask_array):
+ """Test that a pint.Quantity wrapped Dask array is a Dask collection."""
+ q = ureg.Quantity(dask_array, units_)
+ assert dask.is_dask_collection(q)
+
+
+def test_is_not_dask_collection(numpy_array):
+ """Test that other pint.Quantity wrapped objects are not Dask collections."""
+ q = ureg.Quantity(numpy_array, units_)
+ assert not dask.is_dask_collection(q)
+
+
+def test_dask_scheduler(dask_array):
+ """Test that a pint.Quantity wrapped Dask array has the correct default scheduler."""
+ q = ureg.Quantity(dask_array, units_)
+
+ scheduler = q.__dask_scheduler__
+ scheduler_name = f"{scheduler.__module__}.{scheduler.__name__}"
+
+ true_name = "dask.threaded.get"
+
+ assert scheduler == dask.array.Array.__dask_scheduler__
+ assert scheduler_name == true_name
+
+
+def test_dask_optimize(dask_array):
+ """Test that a pint.Quantity wrapped Dask array can be optimized."""
+ q = ureg.Quantity(dask_array, units_)
+
+ assert q.__dask_optimize__ == dask.array.Array.__dask_optimize__
+
+
+def test_compute(dask_array, numpy_array):
+ """Test the compute() method on a pint.Quantity wrapped Dask array."""
+ q = ureg.Quantity(dask_array, units_)
+
+ comps = add_five(q)
+ res = comps.compute()
+
+ assert np.all(res.m == numpy_array)
+ assert not dask.is_dask_collection(res)
+ assert res.units == units_
+ assert q.magnitude is dask_array
+
+
+def test_persist(dask_array, numpy_array):
+ """Test the persist() method on a pint.Quantity wrapped Dask array."""
+ q = ureg.Quantity(dask_array, units_)
+
+ comps = add_five(q)
+ res = comps.persist()
+
+ assert np.all(res.m == numpy_array)
+ assert dask.is_dask_collection(res)
+ assert res.units == units_
+ assert q.magnitude is dask_array
+
+
+@pytest.mark.skipif(
+ importlib.util.find_spec("graphviz") is None, reason="GraphViz is not available"
+)
+def test_visualize(dask_array):
+ """Test the visualize() method on a pint.Quantity wrapped Dask array."""
+ q = ureg.Quantity(dask_array, units_)
+
+ comps = add_five(q)
+ res = comps.visualize()
+
+ assert res is None
+ # These commands only work on Unix and Windows
+ assert os.path.exists("mydask.png")
+ os.remove("mydask.png")
+
+
+def test_compute_persist_equivalent(dask_array, numpy_array):
+ """Test that compute() and persist() return the same numeric results."""
+ q = ureg.Quantity(dask_array, units_)
+
+ comps = add_five(q)
+ res_compute = comps.compute()
+ res_persist = comps.persist()
+
+ assert np.all(res_compute == res_persist)
+ assert res_compute.units == res_persist.units == units_
+
+
+@pytest.mark.parametrize("method", ["compute", "persist", "visualize"])
+def test_exception_method_not_implemented(numpy_array, method):
+ """Test exception handling for convenience methods on a pint.Quantity wrapped
+ object that is not a dask.array.Array object.
+ """
+ q = ureg.Quantity(numpy_array, units_)
+
+ exctruth = (
+ f"Method {method} only implemented for objects of"
+ " <class 'dask.array.core.Array'>, not"
+ " <class 'numpy.ndarray'>"
+ )
+ with pytest.raises(AttributeError, match=exctruth):
+ obj_method = getattr(q, method)
+ obj_method()
+
+
+def test_distributed_compute(loop, dask_array, numpy_array):
+ """Test compute() for distributed machines."""
+ q = ureg.Quantity(dask_array, units_)
+
+ with cluster() as (s, [a, b]):
+ with Client(s["address"], loop=loop):
+ comps = add_five(q)
+ res = comps.compute()
+
+ assert np.all(res.m == numpy_array)
+ assert not dask.is_dask_collection(res)
+ assert res.units == units_
+
+ assert q.magnitude is dask_array
+
+
+def test_distributed_persist(loop, dask_array):
+ """Test persist() for distributed machines."""
+ q = ureg.Quantity(dask_array, units_)
+
+ with cluster() as (s, [a, b]):
+ with Client(s["address"], loop=loop):
+ comps = add_five(q)
+ persisted_q = comps.persist()
+
+ comps_truth = dask_array + 5
+ persisted_truth = comps_truth.persist()
+
+ assert np.all(persisted_q.m == persisted_truth)
+ assert dask.is_dask_collection(persisted_q)
+ assert persisted_q.units == units_
+
+ assert q.magnitude is dask_array
+
+
+@gen_cluster(client=True, timeout=None)
+async def test_async(c, s, a, b):
+ """Test asynchronous operations."""
+ da = dask.array.arange(0, 25, chunks=5, dtype=float).reshape((5, 5))
+ q = ureg.Quantity(da, units_)
+
+ x = q + ureg.Quantity(5, units_)
+ y = x.persist()
+ assert str(y)
+
+ assert dask.is_dask_collection(y)
+ assert len(x.__dask_graph__()) > len(y.__dask_graph__())
+
+ assert not futures_of(x)
+ assert futures_of(y)
+
+ future = c.compute(y)
+ w = await future
+ assert not dask.is_dask_collection(w)
+
+ truth = np.arange(0, 25, dtype=float).reshape((5, 5)) + 5
+ assert np.all(truth == w.m)
diff --git a/pint/testsuite/test_errors.py b/pint/testsuite/test_errors.py
index 57f6633..c89677e 100644
--- a/pint/testsuite/test_errors.py
+++ b/pint/testsuite/test_errors.py
@@ -117,24 +117,28 @@ class TestErrors(BaseTestCase):
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))
+ for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
+ for ex in [
+ DefinitionSyntaxError("foo", filename="a.txt", lineno=123),
+ RedefinitionError("foo", "bar"),
+ UndefinedUnitError("meter"),
+ DimensionalityError("a", "b", "c", "d", extra_msg=": msg"),
+ OffsetUnitCalculusError(
+ Quantity("1 kg")._units, Quantity("1 s")._units
+ ),
+ OffsetUnitCalculusError(q1._units, q2._units),
+ ]:
+ with self.subTest(protocol=protocol, etype=type(ex)):
+ pik = pickle.dumps(ureg.Quantity("1 foo"), protocol)
+ with self.assertRaises(UndefinedUnitError):
+ pickle.loads(pik)
+
+ # assert False, ex.__reduce__()
+ ex2 = pickle.loads(pickle.dumps(ex, protocol))
+ 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 --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py
index 5f42717..f71ce1a 100644
--- a/pint/testsuite/test_issues.py
+++ b/pint/testsuite/test_issues.py
@@ -325,16 +325,6 @@ class TestIssues(QuantityTestCase):
self.assertQuantityAlmostEqual(x + y, 5.1 * ureg.meter)
self.assertQuantityAlmostEqual(z, 5.1 * ureg.meter)
- @helpers.requires_numpy_previous_than("1.10")
- def test_issue94(self):
- v1 = np.array([5, 5]) * ureg.meter
- v2 = 0.1 * ureg.meter
- v3 = np.array([5, 5]) * ureg.meter
- v3 += v2
-
- np.testing.assert_array_equal((v1 + v2).magnitude, np.array([5.1, 5.1]))
- np.testing.assert_array_equal(v3.magnitude, np.array([5, 5]))
-
def test_issue104(self):
x = [ureg("1 meter"), ureg("1 meter"), ureg("1 meter")]
@@ -367,40 +357,6 @@ class TestIssues(QuantityTestCase):
func("METER")
self.assertEqual(val, func("METER", False))
- def test_issue121(self):
- z, v = 0, 2.0
- self.assertEqual(z + v * ureg.meter, v * ureg.meter)
- self.assertEqual(z - v * ureg.meter, -v * ureg.meter)
- self.assertEqual(v * ureg.meter + z, v * ureg.meter)
- self.assertEqual(v * ureg.meter - z, v * ureg.meter)
-
- self.assertEqual(sum([v * ureg.meter, v * ureg.meter]), 2 * v * ureg.meter)
-
- @helpers.requires_numpy18()
- def test_issue121b(self):
- sh = (2, 1)
-
- z, v = 0, 2.0
- self.assertEqual(z + v * ureg.meter, v * ureg.meter)
- self.assertEqual(z - v * ureg.meter, -v * ureg.meter)
- self.assertEqual(v * ureg.meter + z, v * ureg.meter)
- self.assertEqual(v * ureg.meter - z, v * ureg.meter)
-
- self.assertEqual(sum([v * ureg.meter, v * ureg.meter]), 2 * v * ureg.meter)
-
- z, v = np.zeros(sh), 2.0 * np.ones(sh)
- self.assertQuantityEqual(z + v * ureg.meter, v * ureg.meter)
- self.assertQuantityEqual(z - v * ureg.meter, -v * ureg.meter)
- self.assertQuantityEqual(v * ureg.meter + z, v * ureg.meter)
- self.assertQuantityEqual(v * ureg.meter - z, v * ureg.meter)
-
- z, v = np.zeros((3, 1)), 2.0 * np.ones(sh)
- for x, y in ((z, v), (z, v * ureg.meter), (v * ureg.meter, z)):
- with self.assertRaises(ValueError):
- x + y
- with self.assertRaises(ValueError):
- x - y
-
@helpers.requires_numpy()
def test_issue127(self):
q = [1.0, 2.0, 3.0, 4.0] * self.ureg.meter
@@ -503,6 +459,19 @@ class TestIssues(QuantityTestCase):
p = (q ** q).m
np.testing.assert_array_equal(p, a ** a)
+ def test_issue507(self):
+ # leading underscore in unit works with numbers
+ ureg.define("_100km = 100 * kilometer")
+ battery_ec = 16 * ureg.kWh / ureg._100km # noqa: F841
+ # ... but not with text
+ ureg.define("_home = 4700 * kWh / year")
+ with self.assertRaises(AttributeError):
+ home_elec_power = 1 * ureg._home # noqa: F841
+ # ... or with *only* underscores
+ ureg.define("_ = 45 * km")
+ with self.assertRaises(AttributeError):
+ one_blank = 1 * ureg._ # noqa: F841
+
def test_issue523(self):
src, dst = UnitsContainer({"meter": 1}), UnitsContainer({"degF": 1})
value = 10.0
@@ -697,28 +666,81 @@ class TestIssues(QuantityTestCase):
with self.assertRaises(DimensionalityError):
q.to("joule")
- def test_issue507(self):
- # leading underscore in unit works with numbers
- ureg.define("_100km = 100 * kilometer")
- battery_ec = 16 * ureg.kWh / ureg._100km # noqa: F841
- # ... but not with text
- ureg.define("_home = 4700 * kWh / year")
- with self.assertRaises(AttributeError):
- home_elec_power = 1 * ureg._home # noqa: F841
- # ... or with *only* underscores
- ureg.define("_ = 45 * km")
- with self.assertRaises(AttributeError):
- one_blank = 1 * ureg._ # noqa: F841
-
def test_issue960(self):
q = (1 * ureg.nanometer).to_compact("micrometer")
assert q.units == ureg.nanometer
assert q.magnitude == 1
+ def test_issue1032(self):
+ class MultiplicativeDictionary(dict):
+ def __rmul__(self, other):
+ return self.__class__(
+ {key: value * other for key, value in self.items()}
+ )
+
+ q = 3 * ureg.s
+ d = MultiplicativeDictionary({4: 5, 6: 7})
+ assert q * d == MultiplicativeDictionary({4: 15 * ureg.s, 6: 21 * ureg.s})
+ with self.assertRaises(TypeError):
+ d * q
+
+ @helpers.requires_numpy()
+ def test_issue973(self):
+ """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
+
+ def test_issue1062_issue1097(self):
+ # Must not be used by any other tests
+ assert "nanometer" not in ureg._units
+ for i in range(5):
+ ctx = Context.from_lines(["@context _", "cal = 4 J"])
+ with ureg.context("sp", ctx):
+ q = ureg.Quantity(1, "nm")
+ q.to("J")
+
+ def test_issue1086(self):
+ # units with prefixes should correctly test as 'in' the registry
+ assert "bits" in ureg
+ assert "gigabits" in ureg
+ assert "meters" in ureg
+ assert "kilometers" in ureg
+ # unknown or incorrect units should test as 'not in' the registry
+ assert "magicbits" not in ureg
+ assert "unknownmeters" not in ureg
+ assert "gigatrees" not in ureg
+
+ def test_issue1112(self):
+ ureg = UnitRegistry(
+ """
+ m = [length]
+ g = [mass]
+ s = [time]
+
+ ft = 0.305 m
+ lb = 454 g
+
+ @context c1
+ [time]->[length] : value * 10 m/s
+ @end
+ @context c2
+ ft = 0.3 m
+ @end
+ @context c3
+ lb = 500 g
+ @end
+ """.splitlines()
+ )
+ ureg.enable_contexts("c1")
+ ureg.enable_contexts("c2")
+ ureg.enable_contexts("c3")
-try:
- @pytest.mark.skipif(np is None, reason="NumPy is not available")
+if np is not None:
+
@pytest.mark.parametrize(
"callable",
[
@@ -750,17 +772,3 @@ try:
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 --git a/pint/testsuite/test_measurement.py b/pint/testsuite/test_measurement.py
index 8f20e9b..78d48a7 100644
--- a/pint/testsuite/test_measurement.py
+++ b/pint/testsuite/test_measurement.py
@@ -48,15 +48,15 @@ class TestMeasurement(QuantityTestCase):
("{!r}", "<Measurement(4.0, 0.1, second ** 2)>"),
("{:P}", "(4.00 ± 0.10) second²"),
("{:L}", r"\left(4.00 \pm 0.10\right)\ \mathrm{second}^{2}"),
- ("{:H}", r"\[(4.00 &plusmn; 0.10)\ second^2\]"),
+ ("{:H}", r"\[(4.00 &plusmn; 0.10)\ {second}^{2}\]"),
("{:C}", "(4.00+/-0.10) second**2"),
- ("{:Lx}", r"\SI[separate-uncertainty=true]{4.00(10)}{\second\squared}"),
+ ("{:Lx}", r"\SI{4.00 +- 0.10}{\second\squared}"),
("{:.1f}", "(4.0 +/- 0.1) second ** 2"),
("{:.1fP}", "(4.0 ± 0.1) second²"),
("{:.1fL}", r"\left(4.0 \pm 0.1\right)\ \mathrm{second}^{2}"),
- ("{:.1fH}", r"\[(4.0 &plusmn; 0.1)\ second^2\]"),
+ ("{:.1fH}", r"\[(4.0 &plusmn; 0.1)\ {second}^{2}\]"),
("{:.1fC}", "(4.0+/-0.1) second**2"),
- ("{:.1fLx}", r"\SI[separate-uncertainty=true]{4.0(1)}{\second\squared}"),
+ ("{:.1fLx}", r"\SI{4.0 +- 0.1}{\second\squared}"),
):
with self.subTest(spec):
self.assertEqual(spec.format(m), result)
@@ -70,7 +70,7 @@ class TestMeasurement(QuantityTestCase):
("{:.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\]"),
+ ("{:.3uSH}", r"\[0.2000(100)\ {second}^{2}\]"),
("{:.3uSC}", "0.2000(100) second**2"),
):
with self.subTest(spec):
@@ -84,13 +84,10 @@ class TestMeasurement(QuantityTestCase):
("{:.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 &plusmn; 0.0100)\ second^2\]"),
+ ("{:.3uH}", r"\[(0.2000 &plusmn; 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}"),
+ ("{:.3uLx}", r"\SI{0.2000 +- 0.0100}{\second\squared}",),
+ ("{:.1uLx}", r"\SI{0.20 +- 0.01}{\second\squared}"),
):
with self.subTest(spec):
self.assertEqual(spec.format(m), result)
@@ -104,7 +101,7 @@ class TestMeasurement(QuantityTestCase):
("{:.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 &plusmn; 1)%\ second^2\]"),
+ ("{:.1u%H}", r"\[(20 &plusmn; 1)%\ {second}^{2}\]"),
("{:.1u%C}", "(20+/-1)% second**2"),
):
with self.subTest(spec):
@@ -120,7 +117,7 @@ class TestMeasurement(QuantityTestCase):
"{:.1ueL}",
r"\left(2.0 \pm 0.1\right) \times 10^{-1}\ \mathrm{second}^{2}",
),
- ("{:.1ueH}", r"\[(2.0 &plusmn; 0.1)×10^{-1}\ second^2\]"),
+ ("{:.1ueH}", r"\[(2.0 &plusmn; 0.1)×10^{-1}\ {second}^{2}\]"),
("{:.1ueC}", "(2.0+/-0.1)e-01 second**2"),
):
with self.subTest(spec):
@@ -135,9 +132,9 @@ class TestMeasurement(QuantityTestCase):
("{!r}", "<Measurement(4e+20, 1e+19, second ** 2)>"),
("{: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 &plusmn; 0.10)×10^{20}\ second^2\]"),
+ ("{:H}", r"\[(4.00 &plusmn; 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}"),
+ ("{:Lx}", r"\SI{4.00 +- 0.10 e+20}{\second\squared}"),
):
with self.subTest(spec):
self.assertEqual(spec.format(m), result)
@@ -152,9 +149,9 @@ class TestMeasurement(QuantityTestCase):
"{:L}",
r"\left(4.00 \pm 0.10\right) \times 10^{-20}\ \mathrm{second}^{2}",
),
- ("{:H}", r"\[(4.00 &plusmn; 0.10)×10^{-20}\ second^2\]"),
+ ("{:H}", r"\[(4.00 &plusmn; 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}"),
+ ("{:Lx}", r"\SI{4.00 +- 0.10 e-20}{\second\squared}"),
):
with self.subTest(spec):
self.assertEqual(spec.format(m), result)
diff --git a/pint/testsuite/test_non_int.py b/pint/testsuite/test_non_int.py
new file mode 100644
index 0000000..4a377d2
--- /dev/null
+++ b/pint/testsuite/test_non_int.py
@@ -0,0 +1,1077 @@
+import copy
+import math
+import operator as op
+import pickle
+from decimal import Decimal
+from fractions import Fraction
+
+from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry
+from pint.testsuite import QuantityTestCase
+from pint.testsuite.parameterized import ParameterizedTestMixin as ParameterizedTestCase
+from pint.unit import UnitsContainer
+
+
+class FakeWrapper:
+ # Used in test_upcast_type_rejection_on_creation
+ def __init__(self, q):
+ self.q = q
+
+
+class NonIntTypeQuantityTestCase(QuantityTestCase):
+
+ NON_INT_TYPE = None
+
+ @classmethod
+ def setUpClass(cls):
+ cls.ureg = UnitRegistry(
+ force_ndarray=cls.FORCE_NDARRAY, non_int_type=cls.NON_INT_TYPE
+ )
+ cls.Q_ = cls.ureg.Quantity
+ cls.U_ = cls.ureg.Unit
+
+ def assertQuantityAlmostEqual(
+ self, first, second, rtol="1e-07", atol="0", msg=None
+ ):
+
+ if isinstance(first, self.Q_):
+ self.assertIsInstance(first.m, (self.NON_INT_TYPE, int))
+ else:
+ self.assertIsInstance(first, (self.NON_INT_TYPE, int))
+
+ if isinstance(second, self.Q_):
+ self.assertIsInstance(second.m, (self.NON_INT_TYPE, int))
+ else:
+ self.assertIsInstance(second, (self.NON_INT_TYPE, int))
+ super().assertQuantityAlmostEqual(
+ first, second, self.NON_INT_TYPE(rtol), self.NON_INT_TYPE(atol), msg
+ )
+
+ def QP_(self, value, units):
+ self.assertIsInstance(value, str)
+ return self.Q_(self.NON_INT_TYPE(value), units)
+
+
+class _TestBasic:
+ def test_quantity_creation(self):
+
+ value = self.NON_INT_TYPE("4.2")
+
+ for args in (
+ (value, "meter"),
+ (value, UnitsContainer(meter=1)),
+ (value, self.ureg.meter),
+ ("4.2*meter",),
+ ("4.2/meter**(-1)",),
+ (self.Q_(value, "meter"),),
+ ):
+ x = self.Q_(*args)
+ self.assertEqual(x.magnitude, value)
+ self.assertEqual(x.units, self.ureg.UnitsContainer(meter=1))
+
+ x = self.Q_(value, UnitsContainer(length=1))
+ y = self.Q_(x)
+ self.assertEqual(x.magnitude, y.magnitude)
+ self.assertEqual(x.units, y.units)
+ self.assertIsNot(x, y)
+
+ x = self.Q_(value, None)
+ self.assertEqual(x.magnitude, value)
+ self.assertEqual(x.units, UnitsContainer())
+
+ with self.capture_log() as buffer:
+ self.assertEqual(
+ value * self.ureg.meter,
+ self.Q_(value, self.NON_INT_TYPE("2") * self.ureg.meter),
+ )
+ self.assertEqual(len(buffer), 1)
+
+ def test_quantity_comparison(self):
+ x = self.QP_("4.2", "meter")
+ y = self.QP_("4.2", "meter")
+ z = self.QP_("5", "meter")
+ j = self.QP_("5", "meter*meter")
+
+ # identity for single object
+ self.assertTrue(x == x)
+ self.assertFalse(x != x)
+
+ # identity for multiple objects with same value
+ self.assertTrue(x == y)
+ self.assertFalse(x != y)
+
+ self.assertTrue(x <= y)
+ self.assertTrue(x >= y)
+ self.assertFalse(x < y)
+ self.assertFalse(x > y)
+
+ self.assertFalse(x == z)
+ self.assertTrue(x != z)
+ self.assertTrue(x < z)
+
+ self.assertTrue(z != j)
+
+ self.assertNotEqual(z, j)
+ self.assertEqual(self.QP_("0", "meter"), self.QP_("0", "centimeter"))
+ self.assertNotEqual(self.QP_("0", "meter"), self.QP_("0", "second"))
+
+ self.assertLess(self.QP_("10", "meter"), self.QP_("5", "kilometer"))
+
+ def test_quantity_comparison_convert(self):
+ self.assertEqual(self.QP_("1000", "millimeter"), self.QP_("1", "meter"))
+ self.assertEqual(
+ self.QP_("1000", "millimeter/min"),
+ self.Q_(
+ self.NON_INT_TYPE("1000") / self.NON_INT_TYPE("60"), "millimeter/s"
+ ),
+ )
+
+ def test_quantity_hash(self):
+ x = self.QP_("4.2", "meter")
+ x2 = self.QP_("4200", "millimeter")
+ y = self.QP_("2", "second")
+ z = self.QP_("0.5", "hertz")
+ self.assertEqual(hash(x), hash(x2))
+
+ # Dimensionless equality
+ self.assertEqual(hash(y * z), hash(1.0))
+
+ # Dimensionless equality from a different unit registry
+ ureg2 = UnitRegistry(force_ndarray=self.FORCE_NDARRAY)
+ y2 = ureg2.Quantity(self.NON_INT_TYPE("2"), "second")
+ z2 = ureg2.Quantity(self.NON_INT_TYPE("0.5"), "hertz")
+ self.assertEqual(hash(y * z), hash(y2 * z2))
+
+ def test_to_base_units(self):
+ x = self.Q_("1*inch")
+ self.assertQuantityAlmostEqual(x.to_base_units(), self.QP_("0.0254", "meter"))
+ x = self.Q_("1*inch*inch")
+ self.assertQuantityAlmostEqual(
+ x.to_base_units(),
+ self.Q_(
+ self.NON_INT_TYPE("0.0254") ** self.NON_INT_TYPE("2.0"), "meter*meter"
+ ),
+ )
+ x = self.Q_("1*inch/minute")
+ self.assertQuantityAlmostEqual(
+ x.to_base_units(),
+ self.Q_(
+ self.NON_INT_TYPE("0.0254") / self.NON_INT_TYPE("60"), "meter/second"
+ ),
+ )
+
+ def test_convert(self):
+ self.assertQuantityAlmostEqual(
+ self.Q_("2 inch").to("meter"),
+ self.Q_(self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254"), "meter"),
+ )
+ self.assertQuantityAlmostEqual(
+ self.Q_("2 meter").to("inch"),
+ self.Q_(self.NON_INT_TYPE("2") / self.NON_INT_TYPE("0.0254"), "inch"),
+ )
+ self.assertQuantityAlmostEqual(
+ self.Q_("2 sidereal_year").to("second"), self.QP_("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)
+
+ def test_convert_from(self):
+ x = self.Q_("2*inch")
+ meter = self.ureg.meter
+
+ # from quantity
+ self.assertQuantityAlmostEqual(
+ meter.from_(x),
+ self.Q_(self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254"), "meter"),
+ )
+ self.assertQuantityAlmostEqual(
+ meter.m_from(x), self.NON_INT_TYPE("2") * self.NON_INT_TYPE("0.0254")
+ )
+
+ # from unit
+ self.assertQuantityAlmostEqual(
+ meter.from_(self.ureg.inch), self.QP_("0.0254", "meter")
+ )
+ self.assertQuantityAlmostEqual(
+ meter.m_from(self.ureg.inch), self.NON_INT_TYPE("0.0254")
+ )
+
+ # from number
+ self.assertQuantityAlmostEqual(
+ meter.from_(2, strict=False), self.QP_("2", "meter")
+ )
+ self.assertQuantityAlmostEqual(
+ meter.m_from(self.NON_INT_TYPE("2"), strict=False), self.NON_INT_TYPE("2")
+ )
+
+ # from number (strict mode)
+ self.assertRaises(ValueError, meter.from_, self.NON_INT_TYPE("2"))
+ self.assertRaises(ValueError, meter.m_from, self.NON_INT_TYPE("2"))
+
+ def test_context_attr(self):
+ self.assertEqual(self.ureg.meter, self.QP_("1", "meter"))
+
+ def test_both_symbol(self):
+ self.assertEqual(self.QP_("2", "ms"), self.QP_("2", "millisecond"))
+ self.assertEqual(self.QP_("2", "cm"), self.QP_("2", "centimeter"))
+
+ def test_dimensionless_units(self):
+ twopi = self.NON_INT_TYPE("2") * self.ureg.pi
+ self.assertAlmostEqual(self.QP_("360", "degree").to("radian").magnitude, twopi)
+ self.assertAlmostEqual(self.Q_(twopi, "radian"), self.QP_("360", "degree"))
+ self.assertEqual(self.QP_("1", "radian").dimensionality, UnitsContainer())
+ self.assertTrue(self.QP_("1", "radian").dimensionless)
+ self.assertFalse(self.QP_("1", "radian").unitless)
+
+ self.assertEqual(self.QP_("1", "meter") / self.QP_("1", "meter"), 1)
+ self.assertEqual((self.QP_("1", "meter") / self.QP_("1", "mm")).to(""), 1000)
+
+ self.assertEqual(self.Q_(10) // self.QP_("360", "degree"), 1)
+ self.assertEqual(self.QP_("400", "degree") // self.Q_(twopi), 1)
+ self.assertEqual(self.QP_("400", "degree") // twopi, 1)
+ self.assertEqual(7 // self.QP_("360", "degree"), 1)
+
+ def test_offset(self):
+ self.assertQuantityAlmostEqual(
+ self.QP_("0", "kelvin").to("kelvin"), self.QP_("0", "kelvin")
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("0", "degC").to("kelvin"), self.QP_("273.15", "kelvin")
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("0", "degF").to("kelvin"),
+ self.QP_("255.372222", "kelvin"),
+ rtol=0.01,
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "kelvin").to("kelvin"), self.QP_("100", "kelvin")
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "degC").to("kelvin"), self.QP_("373.15", "kelvin")
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "degF").to("kelvin"),
+ self.QP_("310.92777777", "kelvin"),
+ rtol=0.01,
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("0", "kelvin").to("degC"), self.QP_("-273.15", "degC")
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "kelvin").to("degC"), self.QP_("-173.15", "degC")
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("0", "kelvin").to("degF"), self.QP_("-459.67", "degF"), rtol=0.01
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "kelvin").to("degF"), self.QP_("-279.67", "degF"), rtol=0.01
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("32", "degF").to("degC"), self.QP_("0", "degC"), atol=0.01
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "degC").to("degF"), self.QP_("212", "degF"), atol=0.01
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("54", "degF").to("degC"), self.QP_("12.2222", "degC"), atol=0.01
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("12", "degC").to("degF"), self.QP_("53.6", "degF"), atol=0.01
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("12", "kelvin").to("degC"), self.QP_("-261.15", "degC"), atol=0.01
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("12", "degC").to("kelvin"), self.QP_("285.15", "kelvin"), atol=0.01
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("12", "kelvin").to("degR"), self.QP_("21.6", "degR"), atol=0.01
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("12", "degR").to("kelvin"),
+ self.QP_("6.66666667", "kelvin"),
+ atol=0.01,
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("12", "degC").to("degR"), self.QP_("513.27", "degR"), atol=0.01
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("12", "degR").to("degC"),
+ self.QP_("-266.483333", "degC"),
+ atol=0.01,
+ )
+
+ def test_offset_delta(self):
+ self.assertQuantityAlmostEqual(
+ self.QP_("0", "delta_degC").to("kelvin"), self.QP_("0", "kelvin")
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("0", "delta_degF").to("kelvin"), self.QP_("0", "kelvin"), rtol=0.01
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "kelvin").to("delta_degC"), self.QP_("100", "delta_degC")
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "kelvin").to("delta_degF"),
+ self.QP_("180", "delta_degF"),
+ rtol=0.01,
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "delta_degF").to("kelvin"),
+ self.QP_("55.55555556", "kelvin"),
+ rtol=0.01,
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "delta_degC").to("delta_degF"),
+ self.QP_("180", "delta_degF"),
+ rtol=0.01,
+ )
+ self.assertQuantityAlmostEqual(
+ self.QP_("100", "delta_degF").to("delta_degC"),
+ self.QP_("55.55555556", "delta_degC"),
+ rtol=0.01,
+ )
+
+ self.assertQuantityAlmostEqual(
+ self.QP_("12.3", "delta_degC").to("delta_degF"),
+ self.QP_("22.14", "delta_degF"),
+ rtol=0.01,
+ )
+
+ def test_pickle(self):
+ for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
+ for magnitude, unit in (
+ ("32", ""),
+ ("2.4", ""),
+ ("32", "m/s"),
+ ("2.4", "m/s"),
+ ):
+ with self.subTest(protocol=protocol, magnitude=magnitude, unit=unit):
+ q1 = self.QP_(magnitude, unit)
+ q2 = pickle.loads(pickle.dumps(q1, protocol))
+ self.assertEqual(q1, q2)
+
+ def test_notiter(self):
+ # Verify that iter() crashes immediately, without needing to draw any
+ # element from it, if the magnitude isn't iterable
+ x = self.QP_("1", "m")
+ with self.assertRaises(TypeError):
+ iter(x)
+
+
+class _TestQuantityBasicMath:
+
+ FORCE_NDARRAY = False
+
+ def _test_inplace(self, operator, value1, value2, expected_result, unit=None):
+ if isinstance(value1, str):
+ value1 = self.Q_(value1)
+ if isinstance(value2, str):
+ value2 = self.Q_(value2)
+ if isinstance(expected_result, str):
+ expected_result = self.Q_(expected_result)
+
+ if unit is not None:
+ value1 = value1 * unit
+ value2 = value2 * unit
+ expected_result = expected_result * unit
+
+ value1 = copy.copy(value1)
+ value2 = copy.copy(value2)
+ id1 = id(value1)
+ id2 = id(value2)
+ value1 = operator(value1, value2)
+ value2_cpy = copy.copy(value2)
+ self.assertQuantityAlmostEqual(value1, expected_result)
+ self.assertEqual(id1, id(value1))
+ self.assertQuantityAlmostEqual(value2, value2_cpy)
+ self.assertEqual(id2, id(value2))
+
+ def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None):
+ if isinstance(value1, str):
+ value1 = self.Q_(value1)
+ if isinstance(value2, str):
+ value2 = self.Q_(value2)
+ if isinstance(expected_result, str):
+ expected_result = self.Q_(expected_result)
+
+ if unit is not None:
+ value1 = value1 * unit
+ value2 = value2 * unit
+ expected_result = expected_result * unit
+
+ id1 = id(value1)
+ id2 = id(value2)
+
+ value1_cpy = copy.copy(value1)
+ value2_cpy = copy.copy(value2)
+
+ result = operator(value1, value2)
+
+ self.assertQuantityAlmostEqual(expected_result, result)
+ self.assertQuantityAlmostEqual(value1, value1_cpy)
+ self.assertQuantityAlmostEqual(value2, value2_cpy)
+ self.assertNotEqual(id(result), id1)
+ self.assertNotEqual(id(result), id2)
+
+ def _test_quantity_add_sub(self, unit, func):
+ x = self.Q_(unit, "centimeter")
+ y = self.Q_(unit, "inch")
+ z = self.Q_(unit, "second")
+ a = self.Q_(unit, None)
+
+ func(op.add, x, x, self.Q_(unit + unit, "centimeter"))
+ func(
+ op.add, x, y, self.Q_(unit + self.NON_INT_TYPE("2.54") * unit, "centimeter")
+ )
+ func(
+ op.add,
+ y,
+ x,
+ self.Q_(unit + unit / (self.NON_INT_TYPE("2.54") * unit), "inch"),
+ )
+ func(op.add, a, unit, self.Q_(unit + unit, None))
+ self.assertRaises(DimensionalityError, op.add, self.NON_INT_TYPE("10"), x)
+ self.assertRaises(DimensionalityError, op.add, x, self.NON_INT_TYPE("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 - self.NON_INT_TYPE("2.54") * unit, "centimeter")
+ )
+ func(
+ op.sub,
+ y,
+ x,
+ self.Q_(unit - unit / (self.NON_INT_TYPE("2.54") * unit), "inch"),
+ )
+ func(op.sub, a, unit, self.Q_(unit - unit, None))
+ self.assertRaises(DimensionalityError, op.sub, self.NON_INT_TYPE("10"), x)
+ self.assertRaises(DimensionalityError, op.sub, x, self.NON_INT_TYPE("10"))
+ self.assertRaises(DimensionalityError, op.sub, x, z)
+
+ def _test_quantity_iadd_isub(self, unit, func):
+ x = self.Q_(unit, "centimeter")
+ y = self.Q_(unit, "inch")
+ z = self.Q_(unit, "second")
+ a = self.Q_(unit, None)
+
+ func(op.iadd, x, x, self.Q_(unit + unit, "centimeter"))
+ func(
+ op.iadd,
+ x,
+ y,
+ self.Q_(unit + self.NON_INT_TYPE("2.54") * unit, "centimeter"),
+ )
+ func(op.iadd, y, x, self.Q_(unit + unit / self.NON_INT_TYPE("2.54"), "inch"))
+ func(op.iadd, a, unit, self.Q_(unit + unit, None))
+ self.assertRaises(DimensionalityError, op.iadd, self.NON_INT_TYPE("10"), x)
+ self.assertRaises(DimensionalityError, op.iadd, x, self.NON_INT_TYPE("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 - self.NON_INT_TYPE("2.54"), "centimeter"))
+ func(op.isub, y, x, self.Q_(unit - unit / self.NON_INT_TYPE("2.54"), "inch"))
+ func(op.isub, a, unit, self.Q_(unit - unit, None))
+ self.assertRaises(DimensionalityError, op.sub, self.NON_INT_TYPE("10"), x)
+ self.assertRaises(DimensionalityError, op.sub, x, self.NON_INT_TYPE("10"))
+ self.assertRaises(DimensionalityError, op.sub, x, z)
+
+ def _test_quantity_mul_div(self, unit, func):
+ func(op.mul, unit * self.NON_INT_TYPE("10"), "4.2*meter", "42*meter", unit)
+ func(op.mul, "4.2*meter", unit * self.NON_INT_TYPE("10"), "42*meter", unit)
+ func(op.mul, "4.2*meter", "10*inch", "42*meter*inch", unit)
+ func(op.truediv, unit * self.NON_INT_TYPE("42"), "4.2*meter", "10/meter", unit)
+ func(
+ op.truediv, "4.2*meter", unit * self.NON_INT_TYPE("10"), "0.42*meter", unit
+ )
+ func(op.truediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit)
+
+ def _test_quantity_imul_idiv(self, unit, func):
+ # func(op.imul, 10.0, '4.2*meter', '42*meter')
+ func(op.imul, "4.2*meter", self.NON_INT_TYPE("10"), "42*meter", unit)
+ func(op.imul, "4.2*meter", "10*inch", "42*meter*inch", unit)
+ # func(op.truediv, 42, '4.2*meter', '10/meter')
+ func(
+ op.itruediv, "4.2*meter", unit * self.NON_INT_TYPE("10"), "0.42*meter", unit
+ )
+ func(op.itruediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit)
+
+ def _test_quantity_floordiv(self, unit, func):
+ a = self.Q_("10*meter")
+ b = self.Q_("3*second")
+ self.assertRaises(DimensionalityError, op.floordiv, a, b)
+ self.assertRaises(DimensionalityError, op.floordiv, self.NON_INT_TYPE("3"), b)
+ self.assertRaises(DimensionalityError, op.floordiv, a, self.NON_INT_TYPE("3"))
+ self.assertRaises(DimensionalityError, op.ifloordiv, a, b)
+ self.assertRaises(DimensionalityError, op.ifloordiv, self.NON_INT_TYPE("3"), b)
+ self.assertRaises(DimensionalityError, op.ifloordiv, a, self.NON_INT_TYPE("3"))
+ func(
+ op.floordiv,
+ unit * self.NON_INT_TYPE("10"),
+ "4.2*meter/meter",
+ self.NON_INT_TYPE("2"),
+ unit,
+ )
+ func(op.floordiv, "10*meter", "4.2*inch", self.NON_INT_TYPE("93"), unit)
+
+ def _test_quantity_mod(self, unit, func):
+ a = self.Q_("10*meter")
+ b = self.Q_("3*second")
+ 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 * self.NON_INT_TYPE("10"),
+ "4.2*meter/meter",
+ self.NON_INT_TYPE("1.6"),
+ unit,
+ )
+
+ def _test_quantity_ifloordiv(self, unit, func):
+ func(
+ op.ifloordiv,
+ self.NON_INT_TYPE("10"),
+ "4.2*meter/meter",
+ self.NON_INT_TYPE("2"),
+ unit,
+ )
+ func(op.ifloordiv, "10*meter", "4.2*inch", self.NON_INT_TYPE("93"), unit)
+
+ def _test_quantity_divmod_one(self, a, b):
+ if isinstance(a, str):
+ a = self.Q_(a)
+ if isinstance(b, str):
+ b = self.Q_(b)
+
+ q, r = divmod(a, b)
+ self.assertEqual(q, a // b)
+ self.assertEqual(r, a % b)
+ self.assertQuantityEqual(a, (q * b) + r)
+ self.assertEqual(q, math.floor(q))
+ if b > (0 * b):
+ self.assertTrue((0 * b) <= r < b)
+ else:
+ self.assertTrue((0 * b) >= r > b)
+ if isinstance(a, self.Q_):
+ self.assertEqual(r.units, a.units)
+ else:
+ self.assertTrue(r.unitless)
+ self.assertTrue(q.unitless)
+
+ copy_a = copy.copy(a)
+ a %= b
+ self.assertEqual(a, r)
+ copy_a //= b
+ self.assertEqual(copy_a, q)
+
+ def _test_quantity_divmod(self):
+ self._test_quantity_divmod_one("10*meter", "4.2*inch")
+
+ # Disabling these tests as it yields different results without Quantities
+ # >>> from decimal import Decimal as D
+ # >>> divmod(-D('100'), D('3'))
+ # (Decimal('-33'), Decimal('-1'))
+ # >>> divmod(-100, 3)
+ # (-34, 2)
+
+ # self._test_quantity_divmod_one("-10*meter", "4.2*inch")
+ # self._test_quantity_divmod_one("-10*meter", "-4.2*inch")
+ # self._test_quantity_divmod_one("10*meter", "-4.2*inch")
+
+ self._test_quantity_divmod_one("400*degree", "3")
+ self._test_quantity_divmod_one("4", "180 degree")
+ self._test_quantity_divmod_one(4, "180 degree")
+ self._test_quantity_divmod_one("20", 4)
+ self._test_quantity_divmod_one("300*degree", "100 degree")
+
+ a = self.Q_("10*meter")
+ b = self.Q_("3*second")
+ self.assertRaises(DimensionalityError, divmod, a, b)
+ self.assertRaises(DimensionalityError, divmod, 3, b)
+ self.assertRaises(DimensionalityError, divmod, a, 3)
+
+ def _test_numeric(self, unit, ifunc):
+ self._test_quantity_add_sub(unit, self._test_not_inplace)
+ self._test_quantity_iadd_isub(unit, ifunc)
+ self._test_quantity_mul_div(unit, self._test_not_inplace)
+ self._test_quantity_imul_idiv(unit, ifunc)
+ self._test_quantity_floordiv(unit, self._test_not_inplace)
+ self._test_quantity_mod(unit, self._test_not_inplace)
+ self._test_quantity_divmod()
+ # self._test_quantity_ifloordiv(unit, ifunc)
+
+ def test_quantity_abs_round(self):
+
+ value = self.NON_INT_TYPE("4.2")
+ x = self.Q_(-value, "meter")
+ y = self.Q_(value, "meter")
+
+ for fun in (abs, round, op.pos, op.neg):
+ zx = self.Q_(fun(x.magnitude), "meter")
+ zy = self.Q_(fun(y.magnitude), "meter")
+ rx = fun(x)
+ ry = fun(y)
+ 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.QP_("-4.2", None)
+ y = self.QP_("4.2", None)
+ z = self.QP_("1", "meter")
+ for fun in (float, complex):
+ self.assertEqual(fun(x), fun(x.magnitude))
+ self.assertEqual(fun(y), fun(y.magnitude))
+ self.assertRaises(DimensionalityError, fun, z)
+
+ def test_not_inplace(self):
+ self._test_numeric(self.NON_INT_TYPE("1.0"), self._test_not_inplace)
+
+
+class _TestOffsetUnitMath(ParameterizedTestCase):
+ def setup(self):
+ self.ureg.autoconvert_offset_to_baseunit = False
+ self.ureg.default_as_delta = True
+
+ additions = [
+ # --- input tuple -------------------- | -- expected result --
+ ((("100", "kelvin"), ("10", "kelvin")), ("110", "kelvin")),
+ ((("100", "kelvin"), ("10", "degC")), "error"),
+ ((("100", "kelvin"), ("10", "degF")), "error"),
+ ((("100", "kelvin"), ("10", "degR")), ("105.56", "kelvin")),
+ ((("100", "kelvin"), ("10", "delta_degC")), ("110", "kelvin")),
+ ((("100", "kelvin"), ("10", "delta_degF")), ("105.56", "kelvin")),
+ ((("100", "degC"), ("10", "kelvin")), "error"),
+ ((("100", "degC"), ("10", "degC")), "error"),
+ ((("100", "degC"), ("10", "degF")), "error"),
+ ((("100", "degC"), ("10", "degR")), "error"),
+ ((("100", "degC"), ("10", "delta_degC")), ("110", "degC")),
+ ((("100", "degC"), ("10", "delta_degF")), ("105.56", "degC")),
+ ((("100", "degF"), ("10", "kelvin")), "error"),
+ ((("100", "degF"), ("10", "degC")), "error"),
+ ((("100", "degF"), ("10", "degF")), "error"),
+ ((("100", "degF"), ("10", "degR")), "error"),
+ ((("100", "degF"), ("10", "delta_degC")), ("118", "degF")),
+ ((("100", "degF"), ("10", "delta_degF")), ("110", "degF")),
+ ((("100", "degR"), ("10", "kelvin")), ("118", "degR")),
+ ((("100", "degR"), ("10", "degC")), "error"),
+ ((("100", "degR"), ("10", "degF")), "error"),
+ ((("100", "degR"), ("10", "degR")), ("110", "degR")),
+ ((("100", "degR"), ("10", "delta_degC")), ("118", "degR")),
+ ((("100", "degR"), ("10", "delta_degF")), ("110", "degR")),
+ ((("100", "delta_degC"), ("10", "kelvin")), ("110", "kelvin")),
+ ((("100", "delta_degC"), ("10", "degC")), ("110", "degC")),
+ ((("100", "delta_degC"), ("10", "degF")), ("190", "degF")),
+ ((("100", "delta_degC"), ("10", "degR")), ("190", "degR")),
+ ((("100", "delta_degC"), ("10", "delta_degC")), ("110", "delta_degC")),
+ ((("100", "delta_degC"), ("10", "delta_degF")), ("105.56", "delta_degC")),
+ ((("100", "delta_degF"), ("10", "kelvin")), ("65.56", "kelvin")),
+ ((("100", "delta_degF"), ("10", "degC")), ("65.56", "degC")),
+ ((("100", "delta_degF"), ("10", "degF")), ("110", "degF")),
+ ((("100", "delta_degF"), ("10", "degR")), ("110", "degR")),
+ ((("100", "delta_degF"), ("10", "delta_degC")), ("118", "delta_degF")),
+ ((("100", "delta_degF"), ("10", "delta_degF")), ("110", "delta_degF")),
+ ]
+
+ @ParameterizedTestCase.parameterize(("input", "expected_output"), additions)
+ def test_addition(self, input_tuple, expected):
+ self.ureg.autoconvert_offset_to_baseunit = False
+ qin1, qin2 = input_tuple
+ q1, q2 = self.QP_(*qin1), self.QP_(*qin2)
+ # update input tuple with new values to have correct values on failure
+ input_tuple = q1, q2
+ if expected == "error":
+ self.assertRaises(OffsetUnitCalculusError, op.add, q1, q2)
+ else:
+ expected = self.QP_(*expected)
+ self.assertEqual(op.add(q1, q2).units, expected.units)
+ self.assertQuantityAlmostEqual(op.add(q1, q2), expected, atol="0.01")
+
+ subtractions = [
+ ((("100", "kelvin"), ("10", "kelvin")), ("90", "kelvin")),
+ ((("100", "kelvin"), ("10", "degC")), ("-183.15", "kelvin")),
+ ((("100", "kelvin"), ("10", "degF")), ("-160.93", "kelvin")),
+ ((("100", "kelvin"), ("10", "degR")), ("94.44", "kelvin")),
+ ((("100", "kelvin"), ("10", "delta_degC")), ("90", "kelvin")),
+ ((("100", "kelvin"), ("10", "delta_degF")), ("94.44", "kelvin")),
+ ((("100", "degC"), ("10", "kelvin")), ("363.15", "delta_degC")),
+ ((("100", "degC"), ("10", "degC")), ("90", "delta_degC")),
+ ((("100", "degC"), ("10", "degF")), ("112.22", "delta_degC")),
+ ((("100", "degC"), ("10", "degR")), ("367.59", "delta_degC")),
+ ((("100", "degC"), ("10", "delta_degC")), ("90", "degC")),
+ ((("100", "degC"), ("10", "delta_degF")), ("94.44", "degC")),
+ ((("100", "degF"), ("10", "kelvin")), ("541.67", "delta_degF")),
+ ((("100", "degF"), ("10", "degC")), ("50", "delta_degF")),
+ ((("100", "degF"), ("10", "degF")), ("90", "delta_degF")),
+ ((("100", "degF"), ("10", "degR")), ("549.67", "delta_degF")),
+ ((("100", "degF"), ("10", "delta_degC")), ("82", "degF")),
+ ((("100", "degF"), ("10", "delta_degF")), ("90", "degF")),
+ ((("100", "degR"), ("10", "kelvin")), ("82", "degR")),
+ ((("100", "degR"), ("10", "degC")), ("-409.67", "degR")),
+ ((("100", "degR"), ("10", "degF")), ("-369.67", "degR")),
+ ((("100", "degR"), ("10", "degR")), ("90", "degR")),
+ ((("100", "degR"), ("10", "delta_degC")), ("82", "degR")),
+ ((("100", "degR"), ("10", "delta_degF")), ("90", "degR")),
+ ((("100", "delta_degC"), ("10", "kelvin")), ("90", "kelvin")),
+ ((("100", "delta_degC"), ("10", "degC")), ("90", "degC")),
+ ((("100", "delta_degC"), ("10", "degF")), ("170", "degF")),
+ ((("100", "delta_degC"), ("10", "degR")), ("170", "degR")),
+ ((("100", "delta_degC"), ("10", "delta_degC")), ("90", "delta_degC")),
+ ((("100", "delta_degC"), ("10", "delta_degF")), ("94.44", "delta_degC")),
+ ((("100", "delta_degF"), ("10", "kelvin")), ("45.56", "kelvin")),
+ ((("100", "delta_degF"), ("10", "degC")), ("45.56", "degC")),
+ ((("100", "delta_degF"), ("10", "degF")), ("90", "degF")),
+ ((("100", "delta_degF"), ("10", "degR")), ("90", "degR")),
+ ((("100", "delta_degF"), ("10", "delta_degC")), ("82", "delta_degF")),
+ ((("100", "delta_degF"), ("10", "delta_degF")), ("90", "delta_degF")),
+ ]
+
+ @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.QP_(*qin1), self.QP_(*qin2)
+ input_tuple = q1, q2
+ if expected == "error":
+ self.assertRaises(OffsetUnitCalculusError, op.sub, q1, q2)
+ else:
+ expected = self.QP_(*expected)
+ self.assertEqual(op.sub(q1, q2).units, expected.units)
+ self.assertQuantityAlmostEqual(op.sub(q1, q2), expected, atol=0.01)
+
+ multiplications = [
+ ((("100", "kelvin"), ("10", "kelvin")), ("1000", "kelvin**2")),
+ ((("100", "kelvin"), ("10", "degC")), "error"),
+ ((("100", "kelvin"), ("10", "degF")), "error"),
+ ((("100", "kelvin"), ("10", "degR")), ("1000", "kelvin*degR")),
+ ((("100", "kelvin"), ("10", "delta_degC")), ("1000", "kelvin*delta_degC")),
+ ((("100", "kelvin"), ("10", "delta_degF")), ("1000", "kelvin*delta_degF")),
+ ((("100", "degC"), ("10", "kelvin")), "error"),
+ ((("100", "degC"), ("10", "degC")), "error"),
+ ((("100", "degC"), ("10", "degF")), "error"),
+ ((("100", "degC"), ("10", "degR")), "error"),
+ ((("100", "degC"), ("10", "delta_degC")), "error"),
+ ((("100", "degC"), ("10", "delta_degF")), "error"),
+ ((("100", "degF"), ("10", "kelvin")), "error"),
+ ((("100", "degF"), ("10", "degC")), "error"),
+ ((("100", "degF"), ("10", "degF")), "error"),
+ ((("100", "degF"), ("10", "degR")), "error"),
+ ((("100", "degF"), ("10", "delta_degC")), "error"),
+ ((("100", "degF"), ("10", "delta_degF")), "error"),
+ ((("100", "degR"), ("10", "kelvin")), ("1000", "degR*kelvin")),
+ ((("100", "degR"), ("10", "degC")), "error"),
+ ((("100", "degR"), ("10", "degF")), "error"),
+ ((("100", "degR"), ("10", "degR")), ("1000", "degR**2")),
+ ((("100", "degR"), ("10", "delta_degC")), ("1000", "degR*delta_degC")),
+ ((("100", "degR"), ("10", "delta_degF")), ("1000", "degR*delta_degF")),
+ ((("100", "delta_degC"), ("10", "kelvin")), ("1000", "delta_degC*kelvin")),
+ ((("100", "delta_degC"), ("10", "degC")), "error"),
+ ((("100", "delta_degC"), ("10", "degF")), "error"),
+ ((("100", "delta_degC"), ("10", "degR")), ("1000", "delta_degC*degR")),
+ ((("100", "delta_degC"), ("10", "delta_degC")), ("1000", "delta_degC**2")),
+ (
+ (("100", "delta_degC"), ("10", "delta_degF")),
+ ("1000", "delta_degC*delta_degF"),
+ ),
+ ((("100", "delta_degF"), ("10", "kelvin")), ("1000", "delta_degF*kelvin")),
+ ((("100", "delta_degF"), ("10", "degC")), "error"),
+ ((("100", "delta_degF"), ("10", "degF")), "error"),
+ ((("100", "delta_degF"), ("10", "degR")), ("1000", "delta_degF*degR")),
+ (
+ (("100", "delta_degF"), ("10", "delta_degC")),
+ ("1000", "delta_degF*delta_degC"),
+ ),
+ ((("100", "delta_degF"), ("10", "delta_degF")), ("1000", "delta_degF**2")),
+ ]
+
+ @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.QP_(*qin1), self.QP_(*qin2)
+ input_tuple = q1, q2
+ if expected == "error":
+ self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2)
+ else:
+ expected = self.QP_(*expected)
+ self.assertEqual(op.mul(q1, q2).units, expected.units)
+ self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01)
+
+ divisions = [
+ ((("100", "kelvin"), ("10", "kelvin")), ("10", "")),
+ ((("100", "kelvin"), ("10", "degC")), "error"),
+ ((("100", "kelvin"), ("10", "degF")), "error"),
+ ((("100", "kelvin"), ("10", "degR")), ("10", "kelvin/degR")),
+ ((("100", "kelvin"), ("10", "delta_degC")), ("10", "kelvin/delta_degC")),
+ ((("100", "kelvin"), ("10", "delta_degF")), ("10", "kelvin/delta_degF")),
+ ((("100", "degC"), ("10", "kelvin")), "error"),
+ ((("100", "degC"), ("10", "degC")), "error"),
+ ((("100", "degC"), ("10", "degF")), "error"),
+ ((("100", "degC"), ("10", "degR")), "error"),
+ ((("100", "degC"), ("10", "delta_degC")), "error"),
+ ((("100", "degC"), ("10", "delta_degF")), "error"),
+ ((("100", "degF"), ("10", "kelvin")), "error"),
+ ((("100", "degF"), ("10", "degC")), "error"),
+ ((("100", "degF"), ("10", "degF")), "error"),
+ ((("100", "degF"), ("10", "degR")), "error"),
+ ((("100", "degF"), ("10", "delta_degC")), "error"),
+ ((("100", "degF"), ("10", "delta_degF")), "error"),
+ ((("100", "degR"), ("10", "kelvin")), ("10", "degR/kelvin")),
+ ((("100", "degR"), ("10", "degC")), "error"),
+ ((("100", "degR"), ("10", "degF")), "error"),
+ ((("100", "degR"), ("10", "degR")), ("10", "")),
+ ((("100", "degR"), ("10", "delta_degC")), ("10", "degR/delta_degC")),
+ ((("100", "degR"), ("10", "delta_degF")), ("10", "degR/delta_degF")),
+ ((("100", "delta_degC"), ("10", "kelvin")), ("10", "delta_degC/kelvin")),
+ ((("100", "delta_degC"), ("10", "degC")), "error"),
+ ((("100", "delta_degC"), ("10", "degF")), "error"),
+ ((("100", "delta_degC"), ("10", "degR")), ("10", "delta_degC/degR")),
+ ((("100", "delta_degC"), ("10", "delta_degC")), ("10", "")),
+ (
+ (("100", "delta_degC"), ("10", "delta_degF")),
+ ("10", "delta_degC/delta_degF"),
+ ),
+ ((("100", "delta_degF"), ("10", "kelvin")), ("10", "delta_degF/kelvin")),
+ ((("100", "delta_degF"), ("10", "degC")), "error"),
+ ((("100", "delta_degF"), ("10", "degF")), "error"),
+ ((("100", "delta_degF"), ("10", "degR")), ("10", "delta_degF/degR")),
+ (
+ (("100", "delta_degF"), ("10", "delta_degC")),
+ ("10", "delta_degF/delta_degC"),
+ ),
+ ((("100", "delta_degF"), ("10", "delta_degF")), ("10", "")),
+ ]
+
+ @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.QP_(*qin1), self.QP_(*qin2)
+ input_tuple = q1, q2
+ if expected == "error":
+ self.assertRaises(OffsetUnitCalculusError, op.truediv, q1, q2)
+ else:
+ expected = self.QP_(*expected)
+ self.assertEqual(op.truediv(q1, q2).units, expected.units)
+ self.assertQuantityAlmostEqual(op.truediv(q1, q2), expected, atol=0.01)
+
+ multiplications_with_autoconvert_to_baseunit = [
+ ((("100", "kelvin"), ("10", "degC")), ("28315.0", "kelvin**2")),
+ ((("100", "kelvin"), ("10", "degF")), ("26092.78", "kelvin**2")),
+ ((("100", "degC"), ("10", "kelvin")), ("3731.5", "kelvin**2")),
+ ((("100", "degC"), ("10", "degC")), ("105657.42", "kelvin**2")),
+ ((("100", "degC"), ("10", "degF")), ("97365.20", "kelvin**2")),
+ ((("100", "degC"), ("10", "degR")), ("3731.5", "kelvin*degR")),
+ ((("100", "degC"), ("10", "delta_degC")), ("3731.5", "kelvin*delta_degC")),
+ ((("100", "degC"), ("10", "delta_degF")), ("3731.5", "kelvin*delta_degF")),
+ ((("100", "degF"), ("10", "kelvin")), ("3109.28", "kelvin**2")),
+ ((("100", "degF"), ("10", "degC")), ("88039.20", "kelvin**2")),
+ ((("100", "degF"), ("10", "degF")), ("81129.69", "kelvin**2")),
+ ((("100", "degF"), ("10", "degR")), ("3109.28", "kelvin*degR")),
+ ((("100", "degF"), ("10", "delta_degC")), ("3109.28", "kelvin*delta_degC")),
+ ((("100", "degF"), ("10", "delta_degF")), ("3109.28", "kelvin*delta_degF")),
+ ((("100", "degR"), ("10", "degC")), ("28315.0", "degR*kelvin")),
+ ((("100", "degR"), ("10", "degF")), ("26092.78", "degR*kelvin")),
+ ((("100", "delta_degC"), ("10", "degC")), ("28315.0", "delta_degC*kelvin")),
+ ((("100", "delta_degC"), ("10", "degF")), ("26092.78", "delta_degC*kelvin")),
+ ((("100", "delta_degF"), ("10", "degC")), ("28315.0", "delta_degF*kelvin")),
+ ((("100", "delta_degF"), ("10", "degF")), ("26092.78", "delta_degF*kelvin")),
+ ]
+
+ @ParameterizedTestCase.parameterize(
+ ("input", "expected_output"), multiplications_with_autoconvert_to_baseunit
+ )
+ def test_multiplication_with_autoconvert(self, input_tuple, expected):
+ self.ureg.autoconvert_offset_to_baseunit = True
+ qin1, qin2 = input_tuple
+ q1, q2 = self.QP_(*qin1), self.QP_(*qin2)
+ input_tuple = q1, q2
+ if expected == "error":
+ self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2)
+ else:
+ expected = self.QP_(*expected)
+ self.assertEqual(op.mul(q1, q2).units, expected.units)
+ self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01)
+
+ multiplications_with_scalar = [
+ ((("10", "kelvin"), "2"), ("20.0", "kelvin")),
+ ((("10", "kelvin**2"), "2"), ("20.0", "kelvin**2")),
+ ((("10", "degC"), "2"), ("20.0", "degC")),
+ ((("10", "1/degC"), "2"), "error"),
+ ((("10", "degC**0.5"), "2"), "error"),
+ ((("10", "degC**2"), "2"), "error"),
+ ((("10", "degC**-2"), "2"), "error"),
+ ]
+
+ @ParameterizedTestCase.parameterize(
+ ("input", "expected_output"), multiplications_with_scalar
+ )
+ def test_multiplication_with_scalar(self, input_tuple, expected):
+ self.ureg.default_as_delta = False
+ in1, in2 = input_tuple
+ if type(in1) is tuple:
+ in1, in2 = self.QP_(*in1), self.NON_INT_TYPE(in2)
+ else:
+ in1, in2 = in1, self.QP_(*in2)
+ input_tuple = in1, in2 # update input_tuple for better tracebacks
+ if expected == "error":
+ self.assertRaises(OffsetUnitCalculusError, op.mul, in1, in2)
+ else:
+ expected = self.QP_(*expected)
+ self.assertEqual(op.mul(in1, in2).units, expected.units)
+ self.assertQuantityAlmostEqual(op.mul(in1, in2), expected, atol="0.01")
+
+ divisions_with_scalar = [ # without / with autoconvert to base unit
+ ((("10", "kelvin"), "2"), [("5.0", "kelvin"), ("5.0", "kelvin")]),
+ ((("10", "kelvin**2"), "2"), [("5.0", "kelvin**2"), ("5.0", "kelvin**2")]),
+ ((("10", "degC"), "2"), ["error", "error"]),
+ ((("10", "degC**2"), "2"), ["error", "error"]),
+ ((("10", "degC**-2"), "2"), ["error", "error"]),
+ (("2", ("10", "kelvin")), [("0.2", "1/kelvin"), ("0.2", "1/kelvin")]),
+ # (('2', ('10', "degC")), ["error", (2 / 283.15, "1/kelvin")]),
+ (("2", ("10", "degC**2")), ["error", "error"]),
+ (("2", ("10", "degC**-2")), ["error", "error"]),
+ ]
+
+ @ParameterizedTestCase.parameterize(
+ ("input", "expected_output"), divisions_with_scalar
+ )
+ def test_division_with_scalar(self, input_tuple, expected):
+ self.ureg.default_as_delta = False
+ in1, in2 = input_tuple
+ if type(in1) is tuple:
+ in1, in2 = self.QP_(*in1), self.NON_INT_TYPE(in2)
+ else:
+ in1, in2 = self.NON_INT_TYPE(in1), self.QP_(*in2)
+ input_tuple = in1, in2 # update input_tuple for better tracebacks
+ expected_copy = expected[:]
+ for i, mode in enumerate([False, True]):
+ self.ureg.autoconvert_offset_to_baseunit = mode
+ if expected_copy[i] == "error":
+ self.assertRaises(OffsetUnitCalculusError, op.truediv, in1, in2)
+ else:
+ expected = self.QP_(*expected_copy[i])
+ self.assertEqual(op.truediv(in1, in2).units, expected.units)
+ self.assertQuantityAlmostEqual(op.truediv(in1, in2), expected)
+
+ exponentiation = [ # resuls without / with autoconvert
+ ((("10", "degC"), "1"), [("10", "degC"), ("10", "degC")]),
+ # ((('10', "degC"), 0.5), ["error", (283.15 ** '0.5', "kelvin**0.5")]),
+ ((("10", "degC"), "0"), [("1.0", ""), ("1.0", "")]),
+ # ((('10', "degC"), -1), ["error", (1 / (10 + 273.15), "kelvin**-1")]),
+ # ((('10', "degC"), -2), ["error", (1 / (10 + 273.15) ** 2.0, "kelvin**-2")]),
+ # ((('0', "degC"), -2), ["error", (1 / (273.15) ** 2, "kelvin**-2")]),
+ # ((('10', "degC"), ('2', "")), ["error", ((283.15) ** 2, "kelvin**2")]),
+ ((("10", "degC"), ("10", "degK")), ["error", "error"]),
+ (
+ (("10", "kelvin"), ("2", "")),
+ [("100.0", "kelvin**2"), ("100.0", "kelvin**2")],
+ ),
+ (("2", ("2", "kelvin")), ["error", "error"]),
+ # (('2', ('500.0', "millikelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]),
+ # (('2', ('0.5', "kelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]),
+ # (
+ # (('10', "degC"), ('500.0', "millikelvin/kelvin")),
+ # ["error", (283.15 ** '0.5', "kelvin**0.5")],
+ # ),
+ ]
+
+ @ParameterizedTestCase.parameterize(("input", "expected_output"), exponentiation)
+ def test_exponentiation(self, input_tuple, expected):
+ self.ureg.default_as_delta = False
+ in1, in2 = input_tuple
+ if type(in1) is tuple and type(in2) is tuple:
+ in1, in2 = self.QP_(*in1), self.QP_(*in2)
+ elif not type(in1) is tuple and type(in2) is tuple:
+ in1, in2 = self.NON_INT_TYPE(in1), self.QP_(*in2)
+ else:
+ in1, in2 = self.QP_(*in1), self.NON_INT_TYPE(in2)
+ input_tuple = in1, in2
+ expected_copy = expected[:]
+ for i, mode in enumerate([False, True]):
+ self.ureg.autoconvert_offset_to_baseunit = mode
+ if expected_copy[i] == "error":
+ self.assertRaises(
+ (OffsetUnitCalculusError, DimensionalityError), op.pow, in1, in2
+ )
+ else:
+ if type(expected_copy[i]) is tuple:
+ expected = self.QP_(*expected_copy[i])
+ self.assertEqual(op.pow(in1, in2).units, expected.units)
+ else:
+ expected = expected_copy[i]
+ self.assertQuantityAlmostEqual(op.pow(in1, in2), expected)
+
+
+class NonIntTypeQuantityTestQuantityFloat(_TestBasic, NonIntTypeQuantityTestCase):
+
+ NON_INT_TYPE = float
+
+
+class NonIntTypeQuantityTestQuantityBasicMathFloat(
+ _TestQuantityBasicMath, NonIntTypeQuantityTestCase
+):
+
+ NON_INT_TYPE = float
+
+
+class NonIntTypeQuantityTestOffsetUnitMathFloat(
+ _TestOffsetUnitMath, NonIntTypeQuantityTestCase
+):
+
+ NON_INT_TYPE = float
+
+
+class NonIntTypeQuantityTestQuantityDecimal(_TestBasic, NonIntTypeQuantityTestCase):
+
+ NON_INT_TYPE = Decimal
+
+
+class NonIntTypeQuantityTestQuantityBasicMathDecimal(
+ _TestQuantityBasicMath, NonIntTypeQuantityTestCase
+):
+
+ NON_INT_TYPE = Decimal
+
+
+class NonIntTypeQuantityTestOffsetUnitMathDecimal(
+ _TestOffsetUnitMath, NonIntTypeQuantityTestCase
+):
+
+ NON_INT_TYPE = Decimal
+
+
+class NonIntTypeQuantityTestQuantityFraction(_TestBasic, NonIntTypeQuantityTestCase):
+
+ NON_INT_TYPE = Fraction
+
+
+class NonIntTypeQuantityTestQuantityBasicMathFraction(
+ _TestQuantityBasicMath, NonIntTypeQuantityTestCase
+):
+
+ NON_INT_TYPE = Fraction
+
+
+class NonIntTypeQuantityTestOffsetUnitMathFraction(
+ _TestOffsetUnitMath, NonIntTypeQuantityTestCase
+):
+
+ NON_INT_TYPE = Fraction
diff --git a/pint/testsuite/test_numpy.py b/pint/testsuite/test_numpy.py
index 595aa3f..105ef6b 100644
--- a/pint/testsuite/test_numpy.py
+++ b/pint/testsuite/test_numpy.py
@@ -1,7 +1,7 @@
import copy
import operator as op
+import pickle
import unittest
-from unittest.mock import patch
from pint import DimensionalityError, OffsetUnitCalculusError, UnitStrippedWarning
from pint.compat import np
@@ -30,6 +30,10 @@ class TestNumpyMethods(QuantityTestCase):
return [[1, 2], [3, np.nan]] * self.ureg.m
@property
+ def q_zero_or_nan(self):
+ return [[0, 0], [0, np.nan]] * self.ureg.m
+
+ @property
def q_temperature(self):
return self.Q_([[1, 2], [3, 4]], self.ureg.degC)
@@ -182,45 +186,60 @@ class TestNumpyArrayManipulation(TestNumpyMethods):
# 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)
+ def test_concat_stack(self):
+ for func in (np.concatenate, np.stack, np.hstack, np.vstack, np.dstack):
+ with self.subTest(func=func):
+ self.assertQuantityEqual(
+ func([self.q] * 2), self.Q_(func([self.q.m] * 2), self.ureg.m)
+ )
+ # One or more of the args is a bare array full of zeros or NaNs
+ self.assertQuantityEqual(
+ func([self.q_zero_or_nan.m, self.q]),
+ self.Q_(func([self.q_zero_or_nan.m, self.q.m]), self.ureg.m),
+ )
+ # One or more of the args is a bare array with at least one non-zero,
+ # non-NaN element
+ nz = self.q_zero_or_nan
+ nz.m[0, 0] = 1
+ with self.assertRaises(DimensionalityError):
+ func([nz.m, 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)
- )
+ def test_block_column_stack(self):
+ for func in (np.block, np.column_stack):
+ with self.subTest(func=func):
- @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)
- )
+ self.assertQuantityEqual(
+ func([self.q[:, 0], self.q[:, 1]]),
+ self.Q_(func([self.q[:, 0].m, self.q[:, 1].m]), 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)
- )
+ # One or more of the args is a bare array full of zeros or NaNs
+ self.assertQuantityEqual(
+ func(
+ [
+ self.q_zero_or_nan[:, 0].m,
+ self.q[:, 0],
+ self.q_zero_or_nan[:, 1].m,
+ ]
+ ),
+ self.Q_(
+ func(
+ [
+ self.q_zero_or_nan[:, 0].m,
+ self.q[:, 0].m,
+ self.q_zero_or_nan[:, 1].m,
+ ]
+ ),
+ self.ureg.m,
+ ),
+ )
+ # One or more of the args is a bare array with at least one non-zero,
+ # non-NaN element
+ nz = self.q_zero_or_nan
+ nz.m[0, 0] = 1
+ with self.assertRaises(DimensionalityError):
+ func([nz[:, 0].m, self.q[:, 0]])
@helpers.requires_array_function_protocol()
def test_append(self):
@@ -264,8 +283,32 @@ class TestNumpyMathematicalFunctions(TestNumpyMethods):
# Sums, products, differences
+ @helpers.requires_array_function_protocol()
def test_prod(self):
- self.assertEqual(self.q.prod(), 24 * self.ureg.m ** 4)
+ axis = 0
+ where = [[True, False], [True, True]]
+
+ self.assertQuantityEqual(self.q.prod(), 24 * self.ureg.m ** 4)
+ self.assertQuantityEqual(self.q.prod(axis=axis), [3, 8] * self.ureg.m ** 2)
+ self.assertQuantityEqual(self.q.prod(where=where), 12 * self.ureg.m ** 3)
+
+ @helpers.requires_array_function_protocol()
+ def test_prod_numpy_func(self):
+ axis = 0
+ where = [[True, False], [True, True]]
+
+ self.assertQuantityEqual(np.prod(self.q), 24 * self.ureg.m ** 4)
+ self.assertQuantityEqual(np.prod(self.q, axis=axis), [3, 8] * self.ureg.m ** 2)
+ self.assertQuantityEqual(np.prod(self.q, where=where), 12 * self.ureg.m ** 3)
+
+ self.assertRaises(DimensionalityError, np.prod, self.q, axis=axis, where=where)
+ self.assertQuantityEqual(
+ np.prod(self.q, axis=axis, where=[[True, False], [False, True]]),
+ [1, 4] * self.ureg.m,
+ )
+ self.assertQuantityEqual(
+ np.prod(self.q, axis=axis, where=[True, False]), [3, 1] * self.ureg.m ** 2
+ )
def test_sum(self):
self.assertEqual(self.q.sum(), 10 * self.ureg.m)
@@ -753,9 +796,6 @@ class TestNumpyUnclassified(TestNumpyMethods):
self.assertQuantityAlmostEqual(np.std(self.q), 1.11803 * self.ureg.m, rtol=1e-5)
self.assertRaises(OffsetUnitCalculusError, np.std, self.q_temperature)
- def test_prod(self):
- self.assertEqual(self.q.prod(), 24 * self.ureg.m ** 4)
-
def test_cumprod(self):
self.assertRaises(DimensionalityError, self.q.cumprod)
self.assertQuantityEqual((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24])
@@ -766,16 +806,6 @@ class TestNumpyUnclassified(TestNumpyMethods):
np.nanstd(self.q_nan), 0.81650 * self.ureg.m, rtol=1e-5
)
- @helpers.requires_numpy_previous_than("1.10")
- def test_integer_div(self):
- a = [1] * self.ureg.m
- b = [2] * self.ureg.m
- c = a / b # Should be float division
- self.assertEqual(c.magnitude[0], 0.5)
-
- a /= b # Should be integer division
- self.assertEqual(a.magnitude[0], 0)
-
def test_conj(self):
self.assertQuantityEqual((self.q * (1 + 1j)).conj(), self.q * (1 - 1j))
self.assertQuantityEqual((self.q * (1 + 1j)).conjugate(), self.q * (1 - 1j))
@@ -838,23 +868,34 @@ class TestNumpyUnclassified(TestNumpyMethods):
self.assertQuantityEqual(x - u, -(u - x))
def test_pickle(self):
- import pickle
-
- def pickle_test(q):
- pq = pickle.loads(pickle.dumps(q))
- self.assertNDArrayEqual(q.magnitude, pq.magnitude)
- self.assertEqual(q.units, pq.units)
-
- pickle_test([10, 20] * self.ureg.m)
+ for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
+ with self.subTest(protocol):
+ q1 = [10, 20] * self.ureg.m
+ q2 = pickle.loads(pickle.dumps(q1, protocol))
+ self.assertNDArrayEqual(q1.magnitude, q2.magnitude)
+ self.assertEqual(q1.units, q2.units)
def test_equal(self):
x = self.q.magnitude
u = self.Q_(np.ones(x.shape))
+ true = np.ones_like(x, dtype=np.bool_)
+ false = np.zeros_like(x, dtype=np.bool_)
self.assertQuantityEqual(u, u)
self.assertQuantityEqual(u == u, u.magnitude == u.magnitude)
self.assertQuantityEqual(u == 1, u.magnitude == 1)
+ v = self.Q_(np.zeros(x.shape), "m")
+ w = self.Q_(np.ones(x.shape), "m")
+ self.assertNDArrayEqual(v == 1, false)
+ self.assertNDArrayEqual(
+ self.Q_(np.zeros_like(x), "m") == self.Q_(np.zeros_like(x), "s"), false,
+ )
+ self.assertNDArrayEqual(v == v, true)
+ self.assertNDArrayEqual(v == w, false)
+ self.assertNDArrayEqual(v == w.to("mm"), false)
+ self.assertNDArrayEqual(u == v, false)
+
def test_shape(self):
u = self.Q_(np.arange(12))
u.shape = 4, 3
@@ -908,6 +949,10 @@ class TestNumpyUnclassified(TestNumpyMethods):
self.assertNDArrayEqual(
np.isclose(self.q, q2), np.array([[False, True], [True, False]])
)
+ self.assertNDArrayEqual(
+ np.isclose(self.q, q2, atol=1e-5, rtol=1e-7),
+ np.array([[False, True], [True, False]]),
+ )
@helpers.requires_array_function_protocol()
def test_interp_numpy_func(self):
@@ -918,6 +963,15 @@ class TestNumpyUnclassified(TestNumpyMethods):
np.interp(x, xp, fp), self.Q_([6.66667, 20.0], self.ureg.degC), rtol=1e-5
)
+ x_ = np.array([1, 4])
+ xp_ = np.linspace(0, 3, 5)
+ fp_ = [0, 5, 10, 15, 20]
+
+ self.assertQuantityAlmostEqual(
+ np.interp(x_, xp_, fp), self.Q_([6.6667, 20.0], self.ureg.degC), rtol=1e-5
+ )
+ self.assertQuantityAlmostEqual(np.interp(x, xp, fp_), [6.6667, 20.0], rtol=1e-5)
+
def test_comparisons(self):
self.assertNDArrayEqual(
self.q > 2 * self.ureg.m, np.array([[False, False], [True, True]])
@@ -1002,6 +1056,14 @@ class TestNumpyUnclassified(TestNumpyMethods):
self.assertQuantityEqual(np.nanpercentile(self.q_nan, 25), self.Q_(1.5, "m"))
@helpers.requires_array_function_protocol()
+ def test_quantile(self):
+ self.assertQuantityEqual(np.quantile(self.q, 0.25), self.Q_(1.75, "m"))
+
+ @helpers.requires_array_function_protocol()
+ def test_nanquantile(self):
+ self.assertQuantityEqual(np.nanquantile(self.q_nan, 0.25), self.Q_(1.5, "m"))
+
+ @helpers.requires_array_function_protocol()
def test_copyto(self):
a = self.q.m
q = copy.copy(self.q)
@@ -1031,28 +1093,15 @@ class TestNumpyUnclassified(TestNumpyMethods):
np.array([[1, 0, 2], [3, 0, 4]]) * self.ureg.m,
)
- @patch("pint.quantity.ARRAY_FALLBACK", False)
def test_ndarray_downcast(self):
with self.assertWarns(UnitStrippedWarning):
np.asarray(self.q)
- @patch("pint.quantity.ARRAY_FALLBACK", False)
def test_ndarray_downcast_with_dtype(self):
with self.assertWarns(UnitStrippedWarning):
qarr = np.asarray(self.q, dtype=np.float64)
self.assertEqual(qarr.dtype, np.float64)
- def test_array_protocol_fallback(self):
- with self.assertWarns(DeprecationWarning) as cm:
- for attr in ("__array_struct__", "__array_interface__"):
- getattr(self.q, attr)
- warning_text = str(cm.warnings[0].message)
- self.assertTrue(
- f"unit of the Quantity being stripped" in warning_text
- and "will become unavailable" in warning_text
- )
-
- @patch("pint.quantity.ARRAY_FALLBACK", False)
def test_array_protocol_unavailable(self):
for attr in ("__array_struct__", "__array_interface__"):
self.assertRaises(AttributeError, getattr, self.q, attr)
@@ -1070,6 +1119,9 @@ class TestNumpyUnclassified(TestNumpyMethods):
b = self.Q_([4.0, 6.0, 8.0, 9.0, -3.0], "degC")
self.assertQuantityEqual(
+ np.pad(a, (2, 3), "constant"), [0, 0, 1, 2, 3, 4, 5, 0, 0, 0] * self.ureg.m
+ )
+ self.assertQuantityEqual(
np.pad(a, (2, 3), "constant", constant_values=(0, 600 * self.ureg.cm)),
[0, 0, 1, 2, 3, 4, 5, 6, 6, 6] * self.ureg.m,
)
@@ -1086,6 +1138,10 @@ class TestNumpyUnclassified(TestNumpyMethods):
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"),
+ [0, 0, 1, 2, 3, 4, 5, 3, 1, 0] * 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,
)
diff --git a/pint/testsuite/test_numpy_func.py b/pint/testsuite/test_numpy_func.py
index fb36c74..3a9453d 100644
--- a/pint/testsuite/test_numpy_func.py
+++ b/pint/testsuite/test_numpy_func.py
@@ -55,7 +55,8 @@ class TestNumPyFuncUtils(TestNumpyMethods):
)
)
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.assertTrue(_is_sequence_with_quantity_elements((self.Q_(0), 0)))
+ self.assertTrue(_is_sequence_with_quantity_elements((0, self.Q_(0))))
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)))
diff --git a/pint/testsuite/test_quantity.py b/pint/testsuite/test_quantity.py
index 17dba32..71fd46c 100644
--- a/pint/testsuite/test_quantity.py
+++ b/pint/testsuite/test_quantity.py
@@ -2,17 +2,12 @@ import copy
import datetime
import math
import operator as op
-import unittest
+import pickle
import warnings
from unittest.mock import patch
-from pint import (
- DimensionalityError,
- LogarithmicUnitCalculusError,
- OffsetUnitCalculusError,
- UnitRegistry,
-)
-from pint.compat import BehaviorChangeWarning, np
+from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry
+from pint.compat import np
from pint.testsuite import QuantityTestCase, helpers
from pint.testsuite.parameterized import ParameterizedTestCase
from pint.unit import UnitsContainer
@@ -139,7 +134,7 @@ class TestQuantity(QuantityTestCase):
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\]"),
+ ("{:H}", r"\[4.12345678\ kilogram\ {meter}^{2}/second\]"),
("{:C}", "4.12345678 kilogram*meter**2/second"),
("{:~}", "4.12345678 kg * m ** 2 / s"),
(
@@ -147,7 +142,7 @@ class TestQuantity(QuantityTestCase):
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\]"),
+ ("{: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}"),
):
@@ -214,12 +209,12 @@ class TestQuantity(QuantityTestCase):
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\]"),
+ ("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\]"),
+ ("H~", r"\[4.12345678\ kg\ {m}^{2}/s\]"),
("C~", "4.12345678 kg*m**2/s"),
):
with self.subTest(spec):
@@ -255,7 +250,7 @@ class TestQuantity(QuantityTestCase):
ureg = UnitRegistry()
x = 3.5 * ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1))
- self.assertEqual(x._repr_html_(), r"\[3.5\ kilogram\ meter^2/second\]")
+ self.assertEqual(x._repr_html_(), r"\[3.5\ kilogram\ {meter}^{2}/second\]")
self.assertEqual(
x._repr_latex_(),
r"$3.5\ \frac{\mathrm{kilogram} \cdot "
@@ -264,7 +259,7 @@ class TestQuantity(QuantityTestCase):
x._repr_pretty_(Pretty, False)
self.assertEqual("".join(alltext), "3.5 kilogram·meter²/second")
ureg.default_format = "~"
- self.assertEqual(x._repr_html_(), r"\[3.5\ kg\ m^2/s\]")
+ self.assertEqual(x._repr_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}}$",
@@ -489,15 +484,12 @@ class TestQuantity(QuantityTestCase):
)
def test_pickle(self):
- import pickle
-
- def pickle_test(q):
- self.assertEqual(q, pickle.loads(pickle.dumps(q)))
-
- pickle_test(self.Q_(32, ""))
- pickle_test(self.Q_(2.4, ""))
- pickle_test(self.Q_(32, "m/s"))
- pickle_test(self.Q_(2.4, "m/s"))
+ for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
+ for magnitude, unit in ((32, ""), (2.4, ""), (32, "m/s"), (2.4, "m/s")):
+ with self.subTest(protocol=protocol, magnitude=magnitude, unit=unit):
+ q1 = self.Q_(magnitude, unit)
+ q2 = pickle.loads(pickle.dumps(q1, protocol))
+ self.assertEqual(q1, q2)
@helpers.requires_numpy()
def test_from_sequence(self):
@@ -537,11 +529,8 @@ class TestQuantity(QuantityTestCase):
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_([])
+ def test_no_longer_array_function_warning_on_creation(self):
+ # Test that warning is no longer raised on first creation
with warnings.catch_warnings():
warnings.filterwarnings("error")
self.Q_([])
@@ -619,6 +608,12 @@ class TestQuantityToCompact(QuantityTestCase):
with self.assertWarns(RuntimeWarning):
self.compareQuantity_compact(x, x)
+ def test_very_large_to_compact(self):
+ # This should not raise an IndexError
+ self.compareQuantity_compact(
+ self.Q_(10000, "yottameter"), self.Q_(10 ** 28, "meter").to_compact()
+ )
+
class TestQuantityBasicMath(QuantityTestCase):
@@ -854,6 +849,106 @@ class TestQuantityBasicMath(QuantityTestCase):
self.assertRaises(DimensionalityError, fun, z)
+class TestQuantityNeutralAdd(QuantityTestCase):
+ """Addition to zero or NaN is allowed between a Quantity and a non-Quantity
+ """
+
+ FORCE_NDARRAY = False
+
+ def test_bare_zero(self):
+ v = self.Q_(2.0, "m")
+ self.assertEqual(v + 0, v)
+ self.assertEqual(v - 0, v)
+ self.assertEqual(0 + v, v)
+ self.assertEqual(0 - v, -v)
+
+ def test_bare_zero_inplace(self):
+ v = self.Q_(2.0, "m")
+ v2 = self.Q_(2.0, "m")
+ v2 += 0
+ self.assertEqual(v2, v)
+ v2 = self.Q_(2.0, "m")
+ v2 -= 0
+ self.assertEqual(v2, v)
+ v2 = 0
+ v2 += v
+ self.assertEqual(v2, v)
+ v2 = 0
+ v2 -= v
+ self.assertEqual(v2, -v)
+
+ def test_bare_nan(self):
+ v = self.Q_(2.0, "m")
+ self.assertQuantityEqual(v + math.nan, self.Q_(math.nan, v.units))
+ self.assertQuantityEqual(v - math.nan, self.Q_(math.nan, v.units))
+ self.assertQuantityEqual(math.nan + v, self.Q_(math.nan, v.units))
+ self.assertQuantityEqual(math.nan - v, self.Q_(math.nan, v.units))
+
+ def test_bare_nan_inplace(self):
+ v = self.Q_(2.0, "m")
+ v2 = self.Q_(2.0, "m")
+ v2 += math.nan
+ self.assertQuantityEqual(v2, self.Q_(math.nan, v.units))
+ v2 = self.Q_(2.0, "m")
+ v2 -= math.nan
+ self.assertQuantityEqual(v2, self.Q_(math.nan, v.units))
+ v2 = math.nan
+ v2 += v
+ self.assertQuantityEqual(v2, self.Q_(math.nan, v.units))
+ v2 = math.nan
+ v2 -= v
+ self.assertQuantityEqual(v2, self.Q_(math.nan, v.units))
+
+ @helpers.requires_numpy()
+ def test_bare_zero_or_nan_numpy(self):
+ z = np.array([0.0, np.nan])
+ v = self.Q_([1.0, 2.0], "m")
+ e = self.Q_([1.0, np.nan], "m")
+ self.assertQuantityEqual(z + v, e)
+ self.assertQuantityEqual(z - v, -e)
+ self.assertQuantityEqual(v + z, e)
+ self.assertQuantityEqual(v - z, e)
+
+ # If any element is non-zero and non-NaN, raise DimensionalityError
+ nz = np.array([0.0, 1.0])
+ with self.assertRaises(DimensionalityError):
+ nz + v
+ with self.assertRaises(DimensionalityError):
+ nz - v
+ with self.assertRaises(DimensionalityError):
+ v + nz
+ with self.assertRaises(DimensionalityError):
+ v - nz
+
+ # Mismatched shape
+ z = np.array([0.0, np.nan, 0.0])
+ v = self.Q_([1.0, 2.0], "m")
+ for x, y in ((z, v), (v, z)):
+ with self.assertRaises(ValueError):
+ x + y
+ with self.assertRaises(ValueError):
+ x - y
+
+ @helpers.requires_numpy()
+ def test_bare_zero_or_nan_numpy_inplace(self):
+ z = np.array([0.0, np.nan])
+ v = self.Q_([1.0, 2.0], "m")
+ e = self.Q_([1.0, np.nan], "m")
+ v += z
+ self.assertQuantityEqual(v, e)
+ v = self.Q_([1.0, 2.0], "m")
+ v -= z
+ self.assertQuantityEqual(v, e)
+ v = self.Q_([1.0, 2.0], "m")
+ z = np.array([0.0, np.nan])
+ z += v
+ self.assertQuantityEqual(z, e)
+ v = self.Q_([1.0, 2.0], "m")
+ z = np.array([0.0, np.nan])
+ z -= v
+ self.assertQuantityEqual(z, -e)
+
+
class TestDimensions(QuantityTestCase):
FORCE_NDARRAY = False
@@ -1517,38 +1612,36 @@ class TestTimedelta(QuantityTestCase):
after -= d
-class TestCompareZero(QuantityTestCase):
- """This test case checks the special treatment that the zero value
- receives in the comparisons: pint>=0.9 supports comparisons against zero
- even for non-dimensionless quantities
-
- Parameters
- ----------
-
- Returns
- -------
-
+class TestCompareNeutral(QuantityTestCase):
+ """Test comparisons against non-Quantity zero or NaN values for for
+ non-dimensionless quantities
"""
def test_equal_zero(self):
- ureg = self.ureg
- ureg.autoconvert_offset_to_baseunit = False
- self.assertTrue(ureg.Quantity(0, ureg.J) == 0)
- self.assertFalse(ureg.Quantity(0, ureg.J) == ureg.Quantity(0, ""))
- self.assertFalse(ureg.Quantity(5, ureg.J) == 0)
+ self.ureg.autoconvert_offset_to_baseunit = False
+ self.assertTrue(self.Q_(0, "J") == 0)
+ self.assertFalse(self.Q_(0, "J") == self.Q_(0, ""))
+ self.assertFalse(self.Q_(5, "J") == 0)
+
+ def test_equal_nan(self):
+ # nan == nan returns False
+ self.ureg.autoconvert_offset_to_baseunit = False
+ self.assertFalse(self.Q_(math.nan, "J") == 0)
+ self.assertFalse(self.Q_(math.nan, "J") == math.nan)
+ self.assertFalse(self.Q_(math.nan, "J") == self.Q_(math.nan, ""))
+ self.assertFalse(self.Q_(5, "J") == math.nan)
@helpers.requires_numpy()
- def test_equal_zero_NP(self):
- ureg = self.ureg
- ureg.autoconvert_offset_to_baseunit = False
+ def test_equal_zero_nan_NP(self):
+ self.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(self.Q_(0, "J") == np.array([0, np.nan]), np.array([True, False]))
+ aeq(self.Q_(5, "J") == np.array([0, np.nan]), np.array([False, False]))
aeq(
- ureg.Quantity(np.arange(3), ureg.J) == np.zeros(3),
+ self.Q_([0, 1, 2], "J") == np.array([0, 0, np.nan]),
np.asarray([True, False, False]),
)
- self.assertFalse(ureg.Quantity(np.arange(4), ureg.J) == np.zeros(3))
+ self.assertFalse(self.Q_(np.arange(4), "J") == np.zeros(3))
def test_offset_equal_zero(self):
ureg = self.ureg
@@ -1573,39 +1666,47 @@ class TestCompareZero(QuantityTestCase):
self.assertFalse(q0 == ureg.Quantity(0, ""))
def test_gt_zero(self):
- ureg = self.ureg
- ureg.autoconvert_offset_to_baseunit = False
- q0 = ureg.Quantity(0, "J")
- q0m = ureg.Quantity(0, "m")
- q0less = ureg.Quantity(0, "")
- qpos = ureg.Quantity(5, "J")
- qneg = ureg.Quantity(-5, "J")
+ self.ureg.autoconvert_offset_to_baseunit = False
+ q0 = self.Q_(0, "J")
+ q0m = self.Q_(0, "m")
+ q0less = self.Q_(0, "")
+ qpos = self.Q_(5, "J")
+ qneg = self.Q_(-5, "J")
self.assertTrue(qpos > q0)
self.assertTrue(qpos > 0)
self.assertFalse(qneg > 0)
- self.assertRaises(DimensionalityError, qpos.__gt__, q0less)
- self.assertRaises(DimensionalityError, qpos.__gt__, q0m)
+ with self.assertRaises(DimensionalityError):
+ qpos > q0less
+ with self.assertRaises(DimensionalityError):
+ qpos > q0m
+
+ def test_gt_nan(self):
+ self.ureg.autoconvert_offset_to_baseunit = False
+ qn = self.Q_(math.nan, "J")
+ qnm = self.Q_(math.nan, "m")
+ qnless = self.Q_(math.nan, "")
+ qpos = self.Q_(5, "J")
+ self.assertFalse(qpos > qn)
+ self.assertFalse(qpos > math.nan)
+ with self.assertRaises(DimensionalityError):
+ qpos > qnless
+ with self.assertRaises(DimensionalityError):
+ qpos > qnm
@helpers.requires_numpy()
- def test_gt_zero_NP(self):
- ureg = self.ureg
- ureg.autoconvert_offset_to_baseunit = False
- qpos = ureg.Quantity(5, "J")
- qneg = ureg.Quantity(-5, "J")
+ def test_gt_zero_nan_NP(self):
+ self.ureg.autoconvert_offset_to_baseunit = False
+ qpos = self.Q_(5, "J")
+ qneg = self.Q_(-5, "J")
aeq = np.testing.assert_array_equal
- aeq(qpos > np.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(qpos > np.array([0, np.nan]), np.asarray([True, False]))
+ aeq(qneg > np.array([0, np.nan]), np.asarray([False, False]))
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)
+ self.Q_(np.arange(-2, 3), "J") > np.array([np.nan, 0, 0, 0, np.nan]),
+ np.asarray([False, False, False, True, False]),
)
+ with self.assertRaises(ValueError):
+ self.Q_(np.arange(-1, 2), "J") > np.zeros(4)
def test_offset_gt_zero(self):
ureg = self.ureg
diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py
index d7eb649..2cce7e0 100644
--- a/pint/testsuite/test_unit.py
+++ b/pint/testsuite/test_unit.py
@@ -42,13 +42,13 @@ class TestUnit(QuantityTestCase):
r"\frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}",
),
("{:P}", "kilogram·meter²/second"),
- ("{:H}", r"\[kilogram\ meter^2/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\]"),
+ ("{:H~}", r"\[kg\ {m}^{2}/s\]"),
("{:C~}", "kg*m**2/s"),
):
with self.subTest(spec):
@@ -63,12 +63,12 @@ class TestUnit(QuantityTestCase):
r"\frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}",
),
("P", "kilogram·meter²/second"),
- ("H", r"\[kilogram\ meter^2/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\]"),
+ ("H~", r"\[kg\ {m}^{2}/s\]"),
("C~", "kg*m**2/s"),
):
with self.subTest(spec):
@@ -104,7 +104,7 @@ class TestUnit(QuantityTestCase):
ureg = UnitRegistry()
x = ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1))
- self.assertEqual(x._repr_html_(), r"\[kilogram\ meter^2/second\]")
+ self.assertEqual(x._repr_html_(), r"\[kilogram\ {meter}^{2}/second\]")
self.assertEqual(
x._repr_latex_(),
r"$\frac{\mathrm{kilogram} \cdot " r"\mathrm{meter}^{2}}{\mathrm{second}}$",
@@ -112,7 +112,7 @@ class TestUnit(QuantityTestCase):
x._repr_pretty_(Pretty, False)
self.assertEqual("".join(alltext), "kilogram·meter²/second")
ureg.default_format = "~"
- self.assertEqual(x._repr_html_(), r"\[kg\ m^2/s\]")
+ self.assertEqual(x._repr_html_(), r"\[kg\ {m}^{2}/s\]")
self.assertEqual(
x._repr_latex_(), r"$\frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}$"
)
@@ -219,6 +219,7 @@ class TestRegistry(QuantityTestCase):
def test_load(self):
import pkg_resources
+
from pint import unit
data = pkg_resources.resource_filename(unit.__name__, "default_en.txt")
@@ -240,6 +241,10 @@ class TestRegistry(QuantityTestCase):
self.assertNotEqual(s1, s3)
self.assertEqual(ureg.default_format, "~")
+ def test_iterate(self):
+ ureg = UnitRegistry()
+ self.assertTrue("meter" in list(ureg))
+
def test_parse_number(self):
self.assertEqual(self.ureg.parse_expression("pi"), math.pi)
self.assertEqual(self.ureg.parse_expression("x", x=2), 2)
diff --git a/pint/testsuite/test_util.py b/pint/testsuite/test_util.py
index 52c7c82..5f71173 100644
--- a/pint/testsuite/test_util.py
+++ b/pint/testsuite/test_util.py
@@ -2,7 +2,6 @@ import collections
import copy
import math
import operator as op
-from decimal import Decimal
from pint.testsuite import BaseTestCase, QuantityTestCase
from pint.util import (
@@ -51,8 +50,8 @@ class TestUnitsContainer(QuantityTestCase):
def test_unitcontainer_creation(self):
x = UnitsContainer(meter=1, second=2)
- y = UnitsContainer({"meter": 1.0, "second": 2.0})
- self.assertIsInstance(x["meter"], float)
+ y = UnitsContainer({"meter": 1, "second": 2})
+ self.assertIsInstance(x["meter"], int)
self.assertEqual(x, y)
self.assertIsNot(x, y)
z = copy.copy(x)
@@ -68,10 +67,10 @@ class TestUnitsContainer(QuantityTestCase):
self.assertEqual(repr(x), "<UnitsContainer({})>")
x = UnitsContainer(meter=1, second=2)
self.assertEqual(str(x), "meter * second ** 2")
- self.assertEqual(repr(x), "<UnitsContainer({'meter': 1.0, 'second': 2.0})>")
+ self.assertEqual(repr(x), "<UnitsContainer({'meter': 1, 'second': 2})>")
x = UnitsContainer(meter=1, second=2.5)
self.assertEqual(str(x), "meter * second ** 2.5")
- self.assertEqual(repr(x), "<UnitsContainer({'meter': 1.0, 'second': 2.5})>")
+ self.assertEqual(repr(x), "<UnitsContainer({'meter': 1, 'second': 2.5})>")
def test_unitcontainer_bool(self):
self.assertTrue(UnitsContainer(meter=1, second=2))
@@ -161,7 +160,7 @@ class TestParseHelper(BaseTestCase):
self.assertEqual(x(), xp())
self.assertNotEqual(x(), y())
self.assertEqual(ParserHelper.from_string(""), ParserHelper())
- self.assertEqual(repr(x()), "<ParserHelper(1, {'meter': 2.0})>")
+ self.assertEqual(repr(x()), "<ParserHelper(1, {'meter': 2})>")
self.assertEqual(ParserHelper(2), 2)
@@ -199,10 +198,7 @@ class TestParseHelper(BaseTestCase):
def test_eval_token(self):
self._test_eval_token(1000.0, "1e3")
self._test_eval_token(1000.0, "1E3")
- self._test_eval_token(Decimal(1000), "1e3", use_decimal=True)
self._test_eval_token(1000, "1000")
- # integer numbers are represented as ints, not Decimals
- self._test_eval_token(1000, "1000", use_decimal=True)
def test_nan(self):
for s in ("nan", "NAN", "NaN", "123 NaN nan NAN 456"):
diff --git a/pint/unit.py b/pint/unit.py
index 5db27cd..25084b4 100644
--- a/pint/unit.py
+++ b/pint/unit.py
@@ -15,6 +15,7 @@ from numbers import Number
from .compat import NUMERIC_TYPES, is_upcast_type
from .definitions import UnitDefinition
+from .errors import DimensionalityError
from .formatting import siunitx_format_unit
from .util import PrettyIPython, SharedRegistryObject, UnitsContainer
@@ -143,6 +144,40 @@ class Unit(PrettyIPython, SharedRegistryObject):
return self._REGISTRY.get_compatible_units(self)
+ def is_compatible_with(self, other, *contexts, **ctx_kwargs):
+ """ check if the other object is compatible
+
+ Parameters
+ ----------
+ other
+ The object to check. Treated as dimensionless if not a
+ Quantity, Unit or str.
+ *contexts : str or pint.Context
+ Contexts to use in the transformation.
+ **ctx_kwargs :
+ Values for the Context/s
+
+ Returns
+ -------
+ bool
+ """
+ if contexts:
+ try:
+ (1 * self).to(other, *contexts, **ctx_kwargs)
+ return True
+ except DimensionalityError:
+ return False
+
+ if isinstance(other, (self._REGISTRY.Quantity, self._REGISTRY.Unit)):
+ return self.dimensionality == other.dimensionality
+
+ if isinstance(other, str):
+ return (
+ self.dimensionality == self._REGISTRY.parse_units(other).dimensionality
+ )
+
+ return self.dimensionless
+
def __mul__(self, other):
if self._check(other):
if isinstance(other, self.__class__):
diff --git a/pint/util.py b/pint/util.py
index 4c62b6a..6148493 100644
--- a/pint/util.py
+++ b/pint/util.py
@@ -13,9 +13,8 @@ import math
import operator
import re
from collections.abc import Mapping
-from decimal import Decimal
from fractions import Fraction
-from functools import lru_cache
+from functools import lru_cache, partial
from logging import NullHandler
from numbers import Number
from token import NAME, NUMBER
@@ -178,14 +177,16 @@ def pi_theorem(quantities, registry=None):
if registry is None:
getdim = lambda x: x
+ non_int_type = float
else:
getdim = registry.get_dimensionality
+ non_int_type = registry.non_int_type
for name, value in quantities.items():
if isinstance(value, str):
- value = ParserHelper.from_string(value)
+ value = ParserHelper.from_string(value, non_int_type=non_int_type)
if isinstance(value, dict):
- dims = getdim(UnitsContainer(value))
+ dims = getdim(registry.UnitsContainer(value))
elif not hasattr(value, "dimensionality"):
dims = getdim(value)
else:
@@ -318,9 +319,21 @@ class UnitsContainer(Mapping):
"""
- __slots__ = ("_d", "_hash")
+ __slots__ = ("_d", "_hash", "_one", "_non_int_type")
def __init__(self, *args, **kwargs):
+ if args and isinstance(args[0], UnitsContainer):
+ default_non_int_type = args[0]._non_int_type
+ else:
+ default_non_int_type = float
+
+ self._non_int_type = kwargs.pop("non_int_type", default_non_int_type)
+
+ if self._non_int_type is float:
+ self._one = 1
+ else:
+ self._one = self._non_int_type("1")
+
d = udict(*args, **kwargs)
self._d = d
for key, value in d.items():
@@ -328,8 +341,8 @@ class UnitsContainer(Mapping):
raise TypeError("key must be a str, not {}".format(type(key)))
if not isinstance(value, Number):
raise TypeError("value must be a number, not {}".format(type(value)))
- if not isinstance(value, float):
- d[key] = float(value)
+ if not isinstance(value, int) and not isinstance(value, self._non_int_type):
+ d[key] = self._non_int_type(value)
self._hash = None
def copy(self):
@@ -399,6 +412,13 @@ class UnitsContainer(Mapping):
self._hash = hash(frozenset(self._d.items()))
return self._hash
+ # Only needed by pickle protocol 0 and 1 (used by pytables)
+ def __getstate__(self):
+ return self._d, self._hash, self._one, self._non_int_type
+
+ def __setstate__(self, state):
+ self._d, self._hash, self._one, self._non_int_type = state
+
def __eq__(self, other):
if isinstance(other, UnitsContainer):
# UnitsContainer.__hash__(self) is not the same as hash(self); see
@@ -411,7 +431,7 @@ class UnitsContainer(Mapping):
other = other._d
elif isinstance(other, str):
- other = ParserHelper.from_string(other)
+ other = ParserHelper.from_string(other, self._non_int_type)
other = other._d
return dict.__eq__(self._d, other)
@@ -436,6 +456,8 @@ class UnitsContainer(Mapping):
out = object.__new__(self.__class__)
out._d = self._d.copy()
out._hash = self._hash
+ out._non_int_type = self._non_int_type
+ out._one = self._one
return out
def __mul__(self, other):
@@ -512,7 +534,7 @@ class ParserHelper(UnitsContainer):
self.scale = scale
@classmethod
- def from_word(cls, input_word):
+ def from_word(cls, input_word, non_int_type=float):
"""Creates a ParserHelper object with a single variable with exponent one.
Equivalent to: ParserHelper({'word': 1})
@@ -526,27 +548,41 @@ class ParserHelper(UnitsContainer):
-------
"""
- return cls(1, [(input_word, 1)])
+ if non_int_type is float:
+ return cls(1, [(input_word, 1)], non_int_type=non_int_type)
+ else:
+ ONE = non_int_type("1.0")
+ return cls(ONE, [(input_word, ONE)], non_int_type=non_int_type)
@classmethod
- def eval_token(cls, token, use_decimal=False):
+ def eval_token(cls, token, use_decimal=False, non_int_type=float):
+
+ # TODO: remove this code when use_decimal is deprecated
+ if use_decimal:
+ raise DeprecationWarning(
+ "`use_decimal` is deprecated, use `non_int_type` keyword argument when instantiating the registry.\n"
+ ">>> from decimal import Decimal\n"
+ ">>> ureg = UnitRegistry(non_int_type=Decimal)"
+ )
+
token_type = token.type
token_text = token.string
if token_type == NUMBER:
- try:
- return int(token_text)
- except ValueError:
- if use_decimal:
- return Decimal(token_text)
- return float(token_text)
+ if non_int_type is float:
+ try:
+ return int(token_text)
+ except ValueError:
+ return float(token_text)
+ else:
+ return non_int_type(token_text)
elif token_type == NAME:
- return ParserHelper.from_word(token_text)
+ return ParserHelper.from_word(token_text, non_int_type=non_int_type)
else:
raise Exception("unknown token type")
@classmethod
@lru_cache()
- def from_string(cls, input_string):
+ def from_string(cls, input_string, non_int_type=float):
"""Parse linear expression mathematical units and return a quantity object.
Parameters
@@ -559,7 +595,7 @@ class ParserHelper(UnitsContainer):
"""
if not input_string:
- return cls()
+ return cls(non_int_type=non_int_type)
input_string = string_preprocessor(input_string)
if "[" in input_string:
@@ -571,10 +607,12 @@ class ParserHelper(UnitsContainer):
reps = False
gen = tokenizer(input_string)
- ret = build_eval_tree(gen).evaluate(cls.eval_token)
+ ret = build_eval_tree(gen).evaluate(
+ partial(cls.eval_token, non_int_type=non_int_type)
+ )
if isinstance(ret, Number):
- return ParserHelper(ret)
+ return ParserHelper(ret, non_int_type=non_int_type)
if reps:
ret = ParserHelper(
@@ -583,6 +621,7 @@ class ParserHelper(UnitsContainer):
key.replace("__obra__", "[").replace("__cbra__", "]"): value
for key, value in ret.items()
},
+ non_int_type=non_int_type,
)
for k in list(ret):
@@ -606,11 +645,19 @@ class ParserHelper(UnitsContainer):
raise ValueError(mess)
return super().__hash__()
+ # Only needed by pickle protocol 0 and 1 (used by pytables)
+ def __getstate__(self):
+ return super().__getstate__() + (self.scale,)
+
+ def __setstate__(self, state):
+ super().__setstate__(state[:-1])
+ self.scale = state[-1]
+
def __eq__(self, other):
if isinstance(other, ParserHelper):
return self.scale == other.scale and super().__eq__(other)
elif isinstance(other, str):
- return self == ParserHelper.from_string(other)
+ return self == ParserHelper.from_string(other, self._non_int_type)
elif isinstance(other, Number):
return self.scale == other and not len(self._d)
else:
@@ -626,7 +673,7 @@ class ParserHelper(UnitsContainer):
for key in keys:
del d[key]
- return self.__class__(self.scale, d)
+ return self.__class__(self.scale, d, non_int_type=self._non_int_type)
def __str__(self):
tmp = "{%s}" % ", ".join(
@@ -642,7 +689,7 @@ class ParserHelper(UnitsContainer):
def __mul__(self, other):
if isinstance(other, str):
- new = self.add(other, 1)
+ new = self.add(other, self._one)
elif isinstance(other, Number):
new = self.copy()
new.scale *= other
@@ -659,7 +706,7 @@ class ParserHelper(UnitsContainer):
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, non_int_type=self._non_int_type)
def __truediv__(self, other):
if isinstance(other, str):
@@ -679,7 +726,7 @@ class ParserHelper(UnitsContainer):
def __rtruediv__(self, other):
new = self.__pow__(-1)
if isinstance(other, str):
- new = new.add(other, 1)
+ new = new.add(other, self._one)
elif isinstance(other, Number):
new.scale *= other
elif isinstance(other, self.__class__):
@@ -833,7 +880,10 @@ def to_units_container(unit_like, registry=None):
else:
return ParserHelper.from_string(unit_like)
elif dict in mro:
- return UnitsContainer(unit_like)
+ if registry:
+ return registry.UnitsContainer(unit_like)
+ else:
+ return UnitsContainer(unit_like)
def infer_base_unit(q):
diff --git a/requirements_docs.txt b/requirements_docs.txt
index 06e3532..50e54ef 100644
--- a/requirements_docs.txt
+++ b/requirements_docs.txt
@@ -3,3 +3,13 @@ matplotlib
nbsphinx
numpy
pytest
+pandas
+git+https://github.com/hgrecco/pint-pandas.git
+jupyter_client
+ipykernel
+graphviz
+xarray
+sparse
+dask[complete]
+setuptools>=41.2
+Serialize
diff --git a/setup.cfg b/setup.cfg
index 83320c1..df22fae 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -27,9 +27,13 @@ packages = pint
zip_safe = True
include_package_data = True
python_requires = >=3.6
-install_requires = setuptools
+install_requires =
+ setuptools
+ packaging
+ importlib-metadata; python_version < '3.8'
setup_requires = setuptools; setuptools_scm
test_suite = pint.testsuite.testsuite
+scripts = pint/pint-convert
[options.extras_require]
numpy = numpy >= 1.14
@@ -74,4 +78,4 @@ use_parentheses=True
line_length=88
[zest.releaser]
-python-file-with-version = version.py \ No newline at end of file
+python-file-with-version = version.py
diff --git a/version.py b/version.py
index 17a86c7..fe18e34 100644
--- a/version.py
+++ b/version.py
@@ -2,5 +2,5 @@
# flake8: noqa
# fmt: off
-__version__ = '0.11.dev0'
+__version__ = '0.15.dev0'
# fmt: on