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()
    suite = unittest.TestLoader().loadTestsFromTestCase(Test_FloatWithUnits)
    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:



All text is copyright © 2010, Ed Tate, All Rights Reserved.
All software and example codes are subject to the MIT License
Copyright (c) 2010, Ed Tate, Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

No comments:

Post a Comment