Make your functions units-aware with Pint 0.3

Pint is Python package to define, operate and manipulate physical quantities: the product of a numerical value and a unit of measurement. It allows arithmetic operations between them and conversions from and to different units.

>>> from pint import UnitRegistry
>>> ureg = UnitRegistry()
>>> q1 = 24. * ureg.meter
>>> q2 = 10. * ureg.centimeters
>>> q1 + q2
<Quantity(24.1, 'meter')>
It provides a comprehensive and extensive list of physical units, prefixes and constants defined in a standalone text file. The registry can parse prefixed and pluralized forms of units resulting in a much shorter and maintainable unit definition list.

It also provides great NumPy integration, with implicit unit conversion and an emphasis on correctness.
>>> import numpy as np
>>> np.cos([0, 45] * ureg.degrees)
<Quantity([ 1.          0.70710678], 'dimensionless')>
>>> np.cos([0, 45] * ureg.meter)
Traceback (most recent call last):
pint.unit.DimensionalityError: Cannot convert from 'meter' to 'radian'

Wrapping your functions with Pint

Scientific code usually needs to interface with services, libraries or data structures in which physical parameters must be given in specific units. It is common to specify the units in the docstrings like this:
>>> def somefun(length, force):
...    """
...    :param length: length in meters.
...    :param force: applied force in Newton.
...    :return: elapsed time in seconds.
...    """
...    # Do something
Later in the code, you will have to remember the required units and make sure that you are doing the right conversions. This is very inconvenient and error prone. Pint 0.3 helps you to write units-aware functions:
>>> from pint import UnitRegistry
>>> ureg = UnitRegistry()
>>> @ureg.wraps('second', ('meter', 'newton'))
... def somefun(length, force):
...    # Do something
and you will use it like this:
>>> somefun(200 *, 8e+5 ureg.dyne)
8 second
The first argument in the decorator factory indicates the return units and the second argument indicates the input units. The decorator will convert input quantities to the required units and then call the wrapped function using their magnitudes as arguments. The return value will be used as the magnitude of a new quantity with the specified output units.
This is very convenient to wrap functions from other packages. For example, the Polymode package provides the index of refraction as a function of the wavelength (given in micrometers) for a variety of optical materials.
>>> from Polymode import Material
>>> nbk7 = Material.BK7().index
>>> nbk7(.5) # The index of refraction for a wavelength given in micrometer
Instead of remembering the input units, you can just wrap it using Pint:
>>> nbk7 = ureg.wraps(None, ['micrometer', ])(Material.BK7().index)
>>> nbk7(500 * ureg.nanometer)

Calling the wrapped function with an argument with incompatible units (e.g. in this case, seconds) will raise a `DimensionalityError`. There is more information in the documentation.

IPython integration

Pint now integrates better with IPython providing pretty printing and autocomplete.

In the qtconsole:

and in the notebook:

Thanks to the people that contributed bug reports, suggestions and patches in this release. In particular to: Eric Lebigot, Nate Bogdanowicz, Daniel Sokolowski, 

Interested? Install it and give it a try!

Submit your bug reports, comments and suggestions in the Issue Tracker. There are already some ideas for version 0.4. Check them out, comment and add yours.

Read the docs:
or fork the code:


Popular posts from this blog

Communicating with instruments using PyVISA but without NI-VISA

The state of the PyVISA ecosystem (and more)

PyVISA-sim. Test your PyVISA applications without connected instruments