Channels ▼
RSS

Tools

Quantities and Units in Python


The following code generates a pint.unit.DimensionalityError exception with the following message: pint.unit.DimensionalityError: Cannot convert from 'volt' ([length] ** 2 * [mass] / [current] / [time] ** 3) to 'ohm' ([length] ** 2 * [mass] / [current] ** 2 / [time] ** 3). The units of measure are incompatible and you cannot sum volts to ohms because Pint cannot perform a conversion from volts to ohms. This way, Pint makes sure you don't make mistakes by mixing incompatible units of measure.

sum = ((10 * ur.volts) + (500 * ur.ohms))

If you want to try the same example I used to described the desired behavior for a language incorporated unit of measure, you can enter the following line and Pint will also raise an exception because it cannot convert volts to ohms. In this case, the code mixes three incompatible units: volts, ohms, and inches.

# Code equivalent to the supposed language incorporated unit of measure
# sum = (10 <volts> + 500 <ohms>) <inches>
sum = ((10 * ur.volts) + (500 * ur.ohms)).to(ur.inches)

Pint also allows you to work with more complex units. For example, the following code specifies a speed in miles per minute and then converts it to miles per hour:

speed1 = 2 * ur.miles / ur.minute
miles_per_hour = speed1.to(ur.miles / ur.hour)

You can also perform operations in compatible complex units. For example, the following code assigns 2 miles per minute to speed1 and 100 kilometers per hour to speed2. Then, the total_speed variable holds a Quantity instance with the result of adding the two speed values.

speed1 = 2 * ur.miles / ur.minute
speed2 = 100 * ur.kilometers / ur.hour
total_speed = speed1 + speed2

In this case, the unit of measure associated with total_speed is miles per minute (mile / minute for Pint) because Pint converts the other value to the unit associated with the first variable (speed1). You have to be careful with this precedence of the operators because if you change the last line with the following line, the unit of measure associated with total_speed is going to be kilometers per hour (kilometer / hour for Pint) because the first variable is speed2:

total_speed = speed2 + speed1

Thus, if you want to be sure of the unit of measure that the resulting Quantity object will use, it is convenient to specify it, as in the next line:

total_speed = (speed2 + speed1).to(ur.miles / ur.minute)

There are many other ways to create a Quantity instance with a value and an associated unit of measure. The following lines are equivalent and use the Quantity constructor instead of multiplication. The Quantity constructor can parse both units and values.

speed1 = ur.Quantity(2, ur.miles / ur.minute)
speed1 = ur.Quantity(2, 'miles / minute')
speed1 = ur.Quantity('2 * miles / minute')

Each time you are defining a new value with an associated unit of measure, you are creating a new instance of Quantity; therefore, Pint introduces a performance overhead compared with working with just numeric values. However, Pint provides the necessary features to avoid the most common mistakes related to working with values expressed in different units of measure and extremely powerful customization options. In addition, the unit conversion features allow you to simplify your code and focus on your problem domain because the most common units are already defined with good accuracy.

Units: A Different Approach

Units is another Python package that allows you to work with numerical values associated to units of measure. Unit provides unit objects that you use to create Quantity instances that save the magnitude and the associated units for any numerical type. You can perform arithmetic operations between compatible units. When you try to perform arithmetic operations on magnitudes that have incompatible units of measure, Unit raises a units.exception.IncompatibleUnitsError exception indicating that it cannot perform the operation when the units are different. However, Units doesn't allow you to work with prefixes, so it doesn't provide a complete set of unit conversion capabilities.

Units is easy to use and includes many common units already defined without the possibility of adding prefixes. You just need to add the following lines to import unit and predefined from units and call the define_units method that initializes the most common units included in the package. You can check the predefined units by checking the source code of predefined.py.

from units import unit, predefined
units.predefined.define_units()

Then, to assign a unit of measure to a value, use the unit object with the desired units of measure and pass the desired value as a parameter. Units will create an instance of the Quantity class. The num property will hold the specified value and the unit property will include the units of measure.

You can read the following line of code as "assign 500 ohms to r1."

r1 = unit('Ohm')(500)

If you call print(r1) the result will be 500.00 Ohm. If you enter r1 in the Python Console, the result will be:

Quantity(500, NamedComposedUnit('Ohm', 
ComposedUnit([NamedComposedUnit('V', 
ComposedUnit([NamedComposedUnit('W', 
ComposedUnit([NamedComposedUnit('J', 
ComposedUnit([NamedComposedUnit('N', 
ComposedUnit([LeafUnit('m', True), 
NamedComposedUnit('kg', ComposedUnit([LeafUnit('g', 
True)], [], 1000), False)], [LeafUnit('s', True), LeafUnit('s', 
True)], 1), True), LeafUnit('m', True)], [], 1), True)], 
[LeafUnit('s', True)], 1), True)], [LeafUnit('A', True)], 1), 
True)], [LeafUnit('A', True)], 1), True)).

As you can see, the way in which Units stores the composed unit is a bit complex, but you can easily check the unit with the help of the unit object. For example, the following expression will be True:

r1.unit == unit('Ohm')

Because Units doesn't define a kilo-Ohm unit, if you want to be able to convert from Ohms to kilo-ohms, you need to create the kilo-Ohm unit as a scalar multiple of the existing Ohm unit. The following code creates the new unit and displays the value of r1 in kilo-Ohm:

from units import scaled_unit
kiloOhm = scaled_unit('kilo-Ohm', 'Ohm', 1000)
print(kiloOhm(r1))

The following line displays the value of r1 expressed in ohms, no matter the unit in which r1 has the value saved.

print(unit('Ohm')(r1))

The following code uses Units to sum the values of r1 and r2. The r1_plus_r2 variable holds the result of the sum operation expressed in ohms and r1_plus_r2_kiloOhm holds the result converted to the new unit defined in the previous code, kilo-Ohm. I don't use kilo-ohms just to follow the way Units works with the predefined units, without plurals. Units performs the necessary conversion to allow you to sum the values of r1 and r2.

ohm = unit('Ohm')
r1 = ohm(500)
r2 = kiloOhm(5.2)
r1_plus_r2 = r1 + r2
r1_plus_r2_kiloOhm = kiloOhm(r1_plus_r2)

The following code uses Units to declare four distance values expressed in four different units of measure: meters, miles, centimeters and feet.

distance1 = unit('m')(2500) # meters
distance2 = unit('mi')(2) # miles
distance3 = unit('cm')(3000) # centimeters
distance4 = unit('ft')(3500) # feet

However, if you try to sum the four distances with the following line, Units will raise a units.exception.IncompatibleUnitsError because the four units are different.

total_distance = distance1 + distance2 + distance3 + distance4

Conclusion

Of the packages discussed in this article, Numericalunits is very simple, but it doesn't avoid the most common problems. Pint is definitely the most complete package when you just want to focus on the problem domain and take advantage of the prefixes, plurals, alias names, and the simple customization of the units definition file. Units provides basic features that you can improve by defining your own units. If you have to write an algorithm that involves complex formulas with different units of measure in Python, you will definitely want to consider one of these packages before reinventing the wheel.


Gaston Hillar is a frequent contributor to Dr. Dobb's.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video