BACK TO HOME PAGE

Units module

Description

This package defines new python objects called Quantities, which represent a physical quantity, that is to say a number and a physical unit. Quantities are handled exaclty like float or int objects: they can be added, multiplied, etc.

Such a module enables your code to be units-aware. The main benefits of having units-aware code is to detect dimension mistakes (for instance when you try to add meters with kilograms) and conversion mistakes (when you want to add ‘10 cm’ with ‘2 mm’, you need to convert one of the two numbers first).

Handling units with python is theoretically a very simple problem, so I wanted this module to be very simple. Features that increase complexity a lot (like Kelvin to Celcius conversion) should be discared. At the moment the module is less than 400 lines of code, and should ideally remain that way.

Why not use existing packages?

There are MANY units packages out there. You can find a comparison table here. I’ll just compare PYSICS.units with packages I uncountered before writing this module.

After writting this module, I realized unum was very similar to what I needed: short and simple code. You may want to try it. It is released under the LGPL, while pysics is under MIT license.

Depending on what you need, pysics.units may not be for you and you might prefer other packages. Possible reasons why pysics.units would not be for you:

[UPDATE] There is another package called physipy which is originally based on pysics but provides far better support for working with numpy, matplotlib and ipython: see https://github.com/mocquin/physipy

Features and examples

Defined units

SI units [ ‘m’, ‘s’, ‘kg’, ‘A’, ‘K’, ‘cd’,’mol’] as well as ‘rad’ and all their multiples (km, cm, etc.) are defined in the module’s namespace.

Some other common units (Hz, N, Pa, J, W, C, V, F, Ohm, S, hr (hour), mn (minute), sr, ft, NM, inch, yard) are also defined, but not their multiples.

Usual operations

from units import *
mass = 10*kg
v = 36*km/hr
Ec= 1./2 * mass * v**2
Ec       # outputs: 500.000  kg*m**2/s**2 [PHYS]
Ec / J   # outputs: 500.0

# Defining new units:
kJ = 1000*J
Ec / kJ  # outputs : 0.5

Conversions

1*cm/m # outputs: 0.01
# By default quantities are displayed in their SI unit:
speed = 90*km/hr 
speed # outputs: 25.000  m/s [PHYS]
# To display it in another unit, the easiest way is to divide by that unit:
speed / (km/hr) # outputs: 90.0

Custom units

# Defining custom units is as simple as:
MPH = 1.609344*km/hr
70*MPH #outputs: 31.293  m/s [PHYS]
# Any result can be expressed in that custom unit:
speed = 90*km/hr 
speed / MPH  # outputs: 55.92340730136006

Dimension checks

# Trying to do an invalid operation results in an exception
1*km + 3*kg # raises: units.DimensionError: Error with physical units: cannot add, substract or compare m with kg.

Function calls

Units work transparently with functions:

def myfunc_1(d):
    return d+5*km

myfunc_1(2*m) # Outputs:   5.002E+03  m [PHYS]
myfunc_1(2*s) # Raise DimensionError

Functions behave with units the same way than with types, ie with a ‘duck typing’ philosophy. For instance, the following function can be called with any dimension (or even with a number):

def myfunc_2(d):
    return 2*d

myfunc_2(10*km) # Outputs:   2.000E+04  m [PHYS]
myfunc_2(3*s)   # Outputs:   6.000  s [PHYS]
myfunc_2(5)     # Outputs:  10

Numpy arrays

import numpy as np
a = np.array([1,2,3]) * km
2*a # outputs: [ 2000.  4000.  6000.]  m [PHYS]

It is also possible to create this array directly from a list:

a = [1, 2, 3]*km

Accessing an item is easy:

a[1] # Outputs: 2.000E+03  m [PHYS]

Removing units

# One can access the unit and the value separately
Ec = 500*J
Ec.unit  # outputs: <unit: kg*m**2/s**2>
Ec.value # outputs:  500.0

float(Ec) # Ec is not unitless ==> raises:
          # TypeError: can't convert physical quantity (unit = kg*m**2/s**2) to float

# when a quantity is dimensionless, it is automatically converted to a float
type(Ec/J) # outputs: <class 'float'>

Integrals

Scipy integration functions (like scipy.integrate.quad) are not units-aware. The solution is to use pysics.integrate.integ function.

from pysics.integrate import integ
def f(x):
    return x + 1*m

integ(f, 1*m,3*m) # output: 6.000  m**2 [PHYS]

Numpy broadcasting and vectorizing issues

In some situations (see Vectorization page for more details), the output of a function call is not a Quantity (with a numpy array as a value), but a numpy array of Quantities:

def myfunc_2(d):
    if d < 2*m:
        return d+5*km
    else:
        return 0*m

d_array = np.arange(3)*m
out_array = np.array([ myfunc_2(d) for d in d_array ])

print(out_array) 
# Outputs:    [5.000E+03  m [PHYS] 5.001E+03  m [PHYS] 0.000E+00  m [PHYS]]
# instead of  [5.000E+03           5.001E+03           0.000E+00          ]  m [PHYS]

Each item of the output array has a unit, instead of having a unitless array associated with a unique unit. This might cause problems when using this array with functions that expect a Quantity. To convert this kind of array to a proper quantity, use the ‘factorize_units’ function:

factorized_array = units.factorize_units(out_array)  # Outputs: [5000. 5001.    0.]  m [PHYS]

This function is automatically called when performing basic operations on an array, so it is generally not necessary to call this function manually. See Vectorization page for more details.

How does it work (internally)?

PYSICS.units defines two classes:

Dimensions are basically a python dictionary which keys are SI units. The value corresponding to each key is the exponent of the unit. For instance, the Dimension object corresponding to meters per second (‘m/s’) would look like:

{ 'm': 1, 's':-1, 'A': 0, 'K': 0, 'cd': 0, 'kg': 0, 'mol': 0, 'rad': 0}

Operations on units (like adding and multiplying) is just a matter of doing operations on those exponents. For instance adding two units returns the same unit (if they are both equal, otherwise it raises an exception), while multiplying two units requires adding the exponents.

The Dimension class code is mainly about redefining usual operators (__mul__,__div__, etc.) to describe what the exponents should become.

The Quantity class is basically a number and a Dimension. Applying an operation (adding, multiplying…) on a Quantity is done by applying this operation on both the number and the Dimension. Like Dimension’s code, Quantity’s code mainly contains operator redefinitions.

BACK TO HOME PAGE