A Pint a day ...


A few months ago I wrote about Pint, a Python units library that we developed for Lantz. With Pint  you can define physical quantities: the product of a numerical value and a unit of measurement. Pint can convert between units and perform mathematical operation between quantities.

For example:
>>> from pint import UnitRegistry
>>> ureg = UnitRegistry()
>>> my_beer = ureg.pint
>>> print(my_beer.to('liter'))
0.473176473 liter
>>> print(my_beer.dimensionality)
[length] ** 3 
>>> ureg.pint < ureg.imperial_pint
True
>>> ureg.pint < ureg.liter
True

As described in the Design principles section of the documentation, units definitions are loaded from a text file which is simple and easy to edit. Adding and changing units and their definitions does not involve changing the code. Prefixed and pluralized forms of units are recognized without explicitly defining them. In other words: as the prefix kilo and the unit meter are defined, Pint understands kilometers. This results in a much shorter and maintainable unit definition list as compared to other packages. Pint also supports advanced string formatting using PEP 3101 syntax. Extended conversion flags are given to provide latex and pretty formatting.

After the previous post, I got valuable comments, suggestions, bug reports and patches. Today we are releasing Pint 0.2 with a lot of nice new features.

Extended NumPy Support

Pint does not require NumPy but provides support for NumPy arrays and functions since version 0.12. You can create a Quantity based on a ndarray:
>>> x1 = numpy.asarray([2., 3.]) * ureg.meter
>>> # or simply by multipliying an iterable by the units.
>>> x1 = [2., 3.] * ureg.meter
Pint 0.2 extends the support to almost all ndarray methods and ufuncs. Each function/method has been tagged according to expected units and how to handle arguments and Pint attempts to convert them on the fly. For example, arctan2 expects two arguments with the same dimensionality and converts the second argument to the units of the first:
>>> x2 = [2., 3.] * ureg.meter
>>> x2 = [300., 200.] * ureg.centimeter
>>> numpy.arctan2(x1, x2)
<Quantity([ 0.5880026   0.98279372], 'radian')> 
The cosine function expects a quantity that can be converted to radians:
>>> numpy.cos(x1)
Traceback (most recent call last):
...
pint.unit.DimensionalityError: Cannot convert from 'meter' ([length]) to 'radian' (dimensionless)

Temperature conversion

Support for non-multiplicative (e.g. temperature) units was one of the most requested features. Pint is now able to to convert between different temperature scales:
>>> home = 25.4 * ureg.degC
>>> print(home.to('degF'))
77.7200004 degF

and also supports a multiplicative version for differences:
>>> difference = 5 * ureg.delta_degC
>>> print(difference.to('delta_degF'))
2.7777777777777777 delta_degF

For practicality, the parser infers from the units if you mean a difference or not:
>>> print(ureg.parse_units('degC/meter'))
delta_degC / meter

(of course, you can disable this behaviour if you want)

Pi Theorem

Buckingham π theorem states that an equation involving n number of physical variables which are expressible in terms of k independent fundamental physical quantities can be expressed in terms of p = n - k dimensionless parameters. Pint can find these dimensionless parameters:
>>> result = ureg.pi_theorem({'V': 'meter/second', 'T': 'second', 'L': 'meter'})
>>> print(result)
[{'V': 1.0, 'T': 1.0, 'L': -1.0}] 
The returned value is a list containing the dimensionless parameters (in this case only one) each specified using a dictionary. The keys corresponds to the names of the physical variables and the values to the exponents. You can print it nicely using Pint's formatter function:
>>> from pint import formatter
>>> print(formatter(result[0].items()))
T * V / L
or using the dimensions
>>> result = ureg.pi_theorem({'V': '[speed]', 'T': '[time]', 'L': '[length]'})
>>> print(formatter(result[0].items()))
T * V / L 

Measurement

A new class provides provides support for Measurements: a value with uncertainty.
>>> length = (2.3 * ureg.meter).plus_minus(.23)
>>> print(length)
(2.3 +/- 0.23) meter
>>> print('{:.02f!p}'.format(length))
(2.30 ± 0.23) meter
>>> print('The relative error is: {}'.format(length.rel))
The relative error is: 0.1
The measurement class also supports some basic uncertainty propagation:
>>> width = (4.6 * ureg.meter).plus_minus(.23)
>>> print('{:.02f!p}'.format(width / length))
(2.00 ± 0.22)


Interested? Install it and give it a try!

Thanks to the people that contributed bug reports, suggestions and patches. In particular to: Richard Barnes, Alexander Böhn, Dave Brooks, Giel van Schijndel, Brend Wanders

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

Read the docs: https://pint.readthedocs.org/
or fork the code: https://github.com/hgrecco/pint

Popular posts from this blog

Communicating with instruments using PyVISA but without NI-VISA

Running Python code in a LaTeX document

The state of the PyVISA ecosystem (and more)