top of page
Search

# Using Units in Python - Technical Calculus and Everything Else

This post explains adding a "not too cumbersome" units management element into Python calculations, specifically using Jupyter Notebooks. The main usage case is engineering calculation, although it can, or could, potentially be used in every script or automation that calculates numerical magnitudes, from mass in kilograms and energy in joules to monetary value in Euros or Dollars; these are units too! For this second financial case, we will need to tweak a little bit the current example.

The internet is plagued with significant failures resulting from incorrect physical unit calculations and incorrect unit conversions, and many of these are generated automatically by a computer; these were and are not errors made by a human alone; it is also possible that the use of the computer, in part, causes the error. The problem has been so visible that even NASA has published a document with words of warning on the topics, with examples. Some newspapers call them "simple math errors."

If you use Python in our technical calculations, we recommend the forallpeople module; this is the source repository. The motto is also quite good:

In 1799 the Metre and Kilogram of the Archives, platinum embodiments of the new units, were declared the legal standards for all measurements in France, and the motto of the metric system expressed the hope that the new units would be "for all people, for all time." From the Encyclopaedia Britannica.

The chances are that we need to install into our system the Python module, do so with:

`!pip install forallpeople`

Once installed, import the module with:

`import forallpeople as si`

The documentation of the module tells us how to use it. We first need to generate an environment that can be the default environment, and we can also inspect the units included using .environment() method.

```si.environment('default', top_level=False)
si.environment()```

These are our international system units:

`{'Hz': 1.000 Hz, 'N': 1.000 N, 'Pa': 1.000 Pa, 'J': 1.000 J, 'W': 1.000 W, 'C': 1.000 C, 'V': 1.000 V, 'F': 1.000 F, 'Ohm': 1.000 Ω, 'S': 1.000 S, 'Wb': 1.000 Wb, 'T': 1.000 T, 'H': 1.000 H, 'Celsius': 1.000 °C, 'lux': 1.000 lux, 'Gy': 1.000 Gy, 'katal': 1.000 kat, 'minute': 1.000 minutes, 'hour': 1.000 hours, 'day': 1.000 days}   {'kg': 1.000 kg, 'm': 1.000 m, 's': 1.000 s, 'A': 1.000 A, 'cd': 1.000 cd, 'K': 1.000 °C, 'mol': 1.000 mol}`

We can calculate the values we are looking for by defining the corresponding numerical operation. In this case, we will calculate, including units, the potential energy of an object with a mass of 25000 kilograms at an altitude of 20000 meters over sea level on earth.

```mass = 25000 * si.kg
h = 20000 * si.m
g = 9.8 * si.m/(si.s**2)```

In a minor breach of Python style, we are not separating the operators in between physical units assignment; it may slightly increase the readability of the information of the unit system.

We can display the value of the result with the "print" function, which will include the units, potential energy is mass times the acceleration of gravity times the altitude difference from our reference sea level:

```potential = mass*g*h
print(f'Potential Energy: {potential:.2f}')```

By default, the resulting value minimizes the number of zeroes; we are formatting it into a two decimal place floating-point number with ":.2f", obtaining a result in gigajoules:

`Potential Energy: 4.90 GJ`

As a second example, we will calculate the kinetic energy of a 1250-kilogram aircraft traveling at 750 kilometers per hour. Kilometers per hour is not an international system base unit as velocity should be expressed as meters per second; this conversion can be performed explicitly as there are 1000 meters per kilometer and 3600 seconds in each hour:

```V = 750 * si.m*1000/si.s/3600
m = 1250*si.kg```

The units module will take care of the conversion and display the value with the correct units:

```print(V)
208.333 m⋅s-1```

The kinetic energy will then be one half the mass of the aircraft times the square of the velocity; after the calculation, the units are displayed automatically in megajoules:

```kinetic = 1/2*m*(V**2)
print(kinetic)
27.127 MJ```

And now, the check that will prevent the unit errors: what if we try to add together our calculated potential energy, kinetic energy, and velocity? This is, in terms of units, an inhomogeneous calculation:

```try:
print(Potential + kinetic + V)
except BaseException as err:
print(f"Error: {err}, {type(err)}")```
`Error: Cannot add between 4.927126736111112 GJ and 208.33333333333334 m·s⁻¹: .dimensions attributes are incompatible (not equal), <class 'ValueError'>`

We are managing the error that indeed the forallpeople module raises when trying to add inhomogeneous units. In this case, the error is printed, and the code will continue executing, just for illustration purposes; in any application, the execution should end, and the error reported.

The calculation of units in this manner adds a layer of security or safety to our code; we could think of it as a kind of "compilation error" added on top of interpreted Python that would not display these errors if forced to add values with no units. Although, of course, this consumes more time while writing technical code and also increases the complexity; if the cost of the error is losing a satellite, it is no doubt worth implementing unit management in your code!

Do not hesitate to contact us if you require quantitative model development, deployment, verification, or validation. We will also be glad to help you with your machine learning or artificial intelligence challenges when applied to asset management, automation, or intelligence gathering from satellite, drone, or fixed-point imagery.

The notebook for this units usage demonstration is here. The Mars Climate Orbiter, built at a cost of \$125 million, lost due to unit error.