07 - Units#

Units must be annotated in your code. You can use the standard SI units, or you can use the pint package to annotate your units. The pint package is more flexible, but it is also more complicated. I recommend you annotate your units at each step and define unit conversions at the end of your code. This will make it easier to debug your code.

Topics we’ll review:

  • Dimensional Analysis/Consistency

  • SI Units

  • CGS Units

  • English Units

  • Python Unit Packages

  • Equation Unit Check

  • Round-off Error

Annotating Units#

When working with units in your technical code, you should work in either SI or English units to avoid confusion. For example, if I’m working in SI units, I could write the following code and know that the final answer is also in SI units. The SI units are m, kg, s, K, mol. When working with those units, your pressure and energy will be in Joules and your pressure in Pascals.

SI Units#

meter, second, kilogram, Kelvin, and mole (base units)
Newton, Joule, Pascal, Watt (derived units)
kilojoules, millimeter, etc (prefixes)

A Joule is a Newton-meter, a Newton is kg-m/s^2, a Pascal is a Newton/m^2, and a Watt is a Joule/s.
The gas constant, R, is 8.314 J/mol-K in those consistent units.

Example calculation below of the kinetic energy of a 2 kilogram mass moving at 20 miles per hour.

# example calculation of kinetic energy
conv = 0.44704 # miles per hour to m/s
velocity = 20*conv # 20 miles per hour to m/s
mass = 2 # kg
kinetic_energy = 0.5 * mass * velocity**2 # J or kg m^2/s^2
print(f'kinetic energy = {kinetic_energy:.2f} J') # I know this is Joules as I used the SI units
kinetic energy = 79.94 J

Note that each line has a comment with the units. If perhaps the units are not SI, for instance the velocity is given in miles per hour, then you’d convert miles per hour to the SI unit of m/s as shown on the first and second lines above.

Dimensional Analysis like the conversion of the velocity from miles per hour to meters per second is needed when completing engineering calculations. For example:

\[\begin{split} \begin{align} 8.9408\frac{m}{s} &= 20 \, miles/hr \times 0.44704 \frac{m/s}{ miles/hr} \\ 8.9408\frac{m}{s} &= 20 \, miles/ hr \times \frac{5280\, ft}{ mile} \times \frac{12\, in}{ ft} \times \frac{2.54\, cm}{ in} \times \frac{1\,m}{100\, cm} \times \frac{1\, hr}{3600\,s} \\ \end{align} \end{split}\]
# Ideal gas calculation
#PV = nRT
# What's the pressure of 1 mole of an ideal gas at 300 K in a 1 m^3 container?
Pres = 1*8.314 * 300 / 1 # Pa
print(f'Pressure = {Pres:.2f} Pa') # I know this is Pascals as I used the SI units
Pressure = 2494.20 Pa

CGS Units#

centimeter, gram, second, Kelvin, and mole (base units)
dyne, erg, barye, erg/s (derived units)
kiloergs, millimeters, etc (prefixes)

A dyne is a g-cm/s^2, an erg is a dyne-cm, a barye is a dyne/cm^2, and an erg/s is a dyne-cm/s.

English Units#

foot, second, pound-mass, Rankine, and mole (base units)
pound-force, foot-pound, pound-force/square foot, and foot-pound per second (derived units)
BTU, psi, and horsepower (other derived units)

Thus if you work in feet, seconds, pound-mass, and Rankine, you’ll need conversion factors to report energy, work, pressure, and power to commonly used english units such as BTU, psi, and horsepower. Plus you’ll need to make sure you correctly use gc in your calculations.

\[ g_c = 32.174 \frac{lb_m \cdot ft}{lbf \cdot s^2} \]

A pound-force is a pound-mass times the acceleration of gravity divided by \(g_c\) (numerically 1 lb-m yields 1 lb-f).
The gas constant, R, is 3.4088 foot-lb/mol-R in those consistent units.

Example calculation below of the kinetic energy of a 2 kilogram mass moving at 20 miles per hour.

# example calculation of kinetic energy
gc = 32.174 # ft/s^2*lbm/lbf
conv = 1.467 # miles per hour to ft/s
velocity = 20*conv # 20 miles per hour to ft/s
convm = 2.205 # kg to lbm
mass = 2*convm # kg to lbm
kinetic_energy_e = 0.5 * mass * velocity**2 /gc # lbm*ft^2/s^2 *lbf*s^2/lbm/ft or foot pounds (ft*lbf)
print(f'kinetic energy = {kinetic_energy_e:.2f} foot-pounds') # I know this is foot pounds as I used the english units with gc
kinetic energy = 59.00 foot-pounds

Converting 59 foot pounds to Joules yields 80 Joules with is the same as the answer above. Note the necessary use of gc in the conversion from lbm to lbf in the above calculation (if left out, you get the wrong answer). Keeping track of units when working in English units is more difficult than working in SI units.

Python Unit Packages#

One option is to use pint, a python package that allows you to annotate your units. You can then convert to other units as needed. For example, if you want to convert 20 miles per hour to feet per second, you can do the following:

import pint
ureg = pint.UnitRegistry()
Q_ = ureg.Quantity
# example calculation of kinetic energy
velocity = Q_(20,'miles/hour')
mass = Q_(2,'kg')
kinetic_energy_p = 0.5 * mass * velocity**2
print(f'kinetic energy = {kinetic_energy_p.to("J"):.2f}') 
print(f'kinetic energy = {kinetic_energy_p.to("footpound"):.2f}') 
print(f'type of kinetic energy = {type(kinetic_energy_p)}')
#Documentation is lacking on what units are already defined and how to reference them
kinetic energy = 79.94 joule
kinetic energy = 58.96 foot_pound
type of kinetic energy = <class 'pint.Quantity'>
2**kinetic_energy_p
---------------------------------------------------------------------------
DimensionalityError                       Traceback (most recent call last)
Cell In[5], line 1
----> 1 2**kinetic_energy_p

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/pint/facets/plain/quantity.py:100, in check_implemented.<locals>.wrapped(self, *args, **kwargs)
     98 elif isinstance(other, list) and other and isinstance(other[0], type(self)):
     99     return NotImplemented
--> 100 return f(self, *args, **kwargs)

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/pint/facets/plain/quantity.py:1264, in PlainQuantity.__rpow__(self, other)
   1262 else:
   1263     if not self.dimensionless:
-> 1264         raise DimensionalityError(self._units, "dimensionless")
   1265     new_self = self.to_root_units()
   1266     return other**new_self._magnitude

DimensionalityError: Cannot convert from 'kilogram * mile ** 2 / hour ** 2' to 'dimensionless'

An alternative to pint is unum. Example code is in the next code cell. You’ll need to install unum with pip install unum to run the below code.

from unum.units import * # Load a number of common units.
distance = 100*m
time = 9.683*s
speed = distance / time
print(speed)
speed.asUnit(mile/h)
print(speed.asUnit(mile/h))
10.327377878756584 [m/s]
23.10174379778276 [mile/h]

Note

I don’t recommend you use pint or unum for the following reasons:

  • for unum, the units used may be defined elsewhere as something else; m for instance may have been defined previously as mass

  • for pint, the object type of the number you’re working with is not a string or a float, but a pint object. This can cause problems with other packages or calculations.

I recommend you switch your english input units to SI units (as above) and then convert back to english units at the end of your code when required.

Equation Unit Check#

Consider the ideal gas equation: \(PV = nRT\). You need to make sure that the units on both sides of the equation are consistent. For example, if you’re working in SI units, then the units are: Pa * m^3 = mol * J/(mol-K) * K and they are consistent (you should confirm this).

Another equation may be a van der Waals equation: \(P = RT/(V-b) - a/V^2\). You need to make sure that the units on both sides of the equation are consistent. You can check that the units work out by inserting units of each term and then making sure they cancel or the proper unit multiplier is present to yield the correct units.

You may be given an equation and asked to evaluate if it’s dimensionally consistent.

Round-off Error#

Round-off error occurs when you truncate a decimal number before completing the calculation. Using a calculation program with variables helps to avoid round-off error. Of course, with any finite decimal number, there is some round-off error, although small as there are 16 decimal places that are stored in a float. For example, the following code shows that the round-off error is small, but it is still there.

#Significant digits:
import numpy as np
number = np.pi * np.log(2)*3/4
print(number)
1.6331895677277015
numberBig = number * 1.245e15
print(numberBig)
2033321011820988.5
numberRBig = numberBig*1e5
print(numberRBig)
2.0333210118209887e+20

Floating point numbers are tracked to 16 decimal places.

#code to show accumulation of round off error
cutnumb = float(int(np.pi*100))*float(int(np.pi*100))*float(int(np.pi*100))/1000000
percenterror = (np.pi**3 - cutnumb)/(np.pi**3)*100
print(f'Error when truncated after 2nd digit {percenterror:0.2f}%')
Error when truncated after 2nd digit 0.15%
#code to show accumulation of round off error
for n in range(5):
    cutnumb = float(int(np.pi*10))**(n+1)/10**(n+1)
    percenterror = (np.pi**(n+1) - cutnumb)/(np.pi**(n+1))*100
    print(f'Error when truncated after 1 decimal place when multiplied {(n+1)} times together: {percenterror:0.2f}%')
Error when truncated after 1 decimal place when multiplied 1 times together: 1.32%
Error when truncated after 1 decimal place when multiplied 2 times together: 2.63%
Error when truncated after 1 decimal place when multiplied 3 times together: 3.92%
Error when truncated after 1 decimal place when multiplied 4 times together: 5.19%
Error when truncated after 1 decimal place when multiplied 5 times together: 6.45%