## Saturday, July 17, 2010

### A simple example of how to add engineering units to numbers in Python

Problem Statement:
When building numeric analyses, one source of errors is units. It is very common to merge data from multiple sources, where each source uses a different, and incompatible set of engineering units. Once an error like this is introduced, it can be difficult to find the error and correct it.
This tutorial shows how to create a class which behaves like a floating point number, but carries a descriptive string which can be used for various purposes in a program. This is a very simple approach is provided to understand how to create a class which inherits from an immutable class in python.

Use Case:

The use case of this class is simple. It is intended to allow a unit to be attached to a floating point value when the value is passed between functions. However, the units will not be preserved through math operations. There are several libraries which will do all of this.

>>x = FloatWithUnits(3.1415,'miles')

>>y = FloatWithUnits(2.7182,'km')

>>print x

... 3.1415+0.0j miles

>>print y

... 2.7182+0.0j km

>>z = x*y

>>print z

... 8.5392253

Solution and Discussion:

Any class which emulates numeric types needs to override many class methods. By inheriting from an existing type, like a float, all of these methods will be defined with default behavior.   For this problem, the challenge is to attach and engineering unit, while preserving all of the methods of a floating point number. The obvious approach to this problem is to create a class which inherits from floats.

When inheriting from an immutable type, things are a little different than inheriting from other classes. The __new__ method must generally be overridden. Otherwise, the __new__ method will prevent an overridden __init__ method from executing.  Also, since we’d like the class to display properly, the __str__ and __repr__ methods are overridden. Here is the new class:

class FloatWithUnits(float):
def __new__(cls,value,units=None):
return float.__new__(cls,value)

def __init__(self,value,units=None):
self.units=units

def __repr__(self):
return 'FloatWithUnits('+str(self.real)+'+'+str(self.imag)+'j,"'+self.units+'")'

def __str__(self):
return str(self.real)+'+'+str(self.imag)+'j '+self.units

if __name__=='__main__':

print FloatWithUnits(4.543)**2

import unittest

class Test_FloatWithUnits(unittest.TestCase):

def setUp(self):
pass

def test_DefaultInstanciation(self):
self.assertTrue(FloatWithUnits(1.45)==1.45)
self.assertAlmostEqual(FloatWithUnits(1.45)*2,2*1.45)
self.assertTrue(FloatWithUnits(-1.45)<0)
self.assertTrue(FloatWithUnits(1.45)>0)

def test_Units(self):
x = FloatWithUnits(2.3,'miles')
#print x.units
self.assertTrue(x.units=='miles')

#unittest.main()
unittest.TextTestRunner(verbosity=2).run(suite)

What doesn’t work and why.

As important as it is to see a working example, seeing a nonworking example is useful. A naive approach to solving this problem is to inherit from float and simply override the __init__ method. This will fail for several reasons, but, the most perplexing error will be the related to the number of arguments during class creation. Try the following code snippet.

class FloatWithUnits(float):
def __init__(self,value,units=None):
super(FloatWithUnits,self).__init__()
self.units=units

>>x = FloatWithUnits(2.3,'miles’)

... TypeError: float() takes at most 1 argument (2 given)

This exception occurs because the __new__ method for float only accepts one value. The inheritance of floats in this class brings in the float __new__ method, and the __init__ method never has a chance to run.

Related Libraries and Packages:

There are several libraries which add engineering units to scalar and matrix math. Here are a few of them:

References: