"""
This recipe generates a module convert.py and convertTest.txt which is used
to test conversion.py when it is run.
conversion.py is built from the table defining the unit conversions
* uses the decimal module as a base class and unit types are class properties.
* provides exact decimal representation
* control over precision
* control over rounding to meet legal or regulatory requirements
* tracking of significant decimal places
* results match calculations done by hand
conversion.py supplies the following classes:
Distance
Area
Volumn
Time
Velocity
Acceleration
Mass
Force
Weight
Pressure
Frequency
Power
Temperature
"""
from decimal import *
header = """
conversion.py
Unit Conversion
Dave Bailey
4/10/2011
The conversion.py module uses Decimal from the decimal module as the base class
decimal is based on the General Decimal Arithmetic Specification
IEEE standard 854-1987
conversion provides:
exact decimal representation
control over precision,
control over rounding to meet legal or regulatory requirements,
tracking of significant decimal places
results match calculations done by hand.
"""
examples = """
-- Examples:
>>> d = Distance(0.0)
>>> d.mi = 1.0
>>> print 'ft -> mile %.3f, %f, %s, %r' % (d.ft,d.ft,d.ft,d.ft)
ft -> mile 5280.000, 5280.000000, 5280.000000000000000000000000, Decimal('5280.000000000000000000000000')
>>> getcontext().prec = 28
>>> d = Distance(0.0)
>>> d.mi = 1.0
>>> print 'ft -> mile %.3f, %f, %s, %r' % (d.ft,d.ft,d.ft,d.ft)
ft -> mile 5280.000, 5280.000000, 5280.000000000000000000000000, Decimal('5280.000000000000000000000000')
>>> getcontext().prec = 52
>>> d = Distance(0.0)
>>> d.mi = 1.0
>>> print 'ft -> mile %.3f, %f, %s, %r' % (d.ft,d.ft,d.ft,d.ft)
ft -> mile 5280.000, 5280.000000, 5279.999999999999999999999999588007935999999954845670, Decimal('5279.999999999999999999999999588007935999999954845670')
>>> getcontext().prec = 28
>>> with localcontext() as ctx:
... getcontext().prec = 52
... d = Distance(0.0)
... d.mi = 1.0
... print 'ft -> mile %.3f, %f, %s, %r' % (d.ft,d.ft,d.ft,d.ft)
ft -> mile 5280.000, 5280.000000, 5279.999999999999999999999999588007935999999954845670, Decimal('5279.999999999999999999999999588007935999999954845670')
>>> getcontext().prec
28
>>> d.ft
Decimal('5280.000000000000000000000000')
>>> d0 = Distance('.10')
>>> d = Distance(d0+d0+d0-Decimal('.30'))
>>> '%r' % d.m
"Decimal('0.00')"
>>> d = Distance(.10 + .10 + .10 - .30)
>>> '%r' % d.m
"Decimal('5.5511151231257827021181583404541015625E-17')"
>>> d.m = '1.0'
>>> d.ft
Decimal('3.28083989501312300000')
>>> d.inch
Decimal('39.370078740157476000000')
>>> d.m = 1.0
>>> d.ft
Decimal('3.2808398950131230000')
>>> d.inch
Decimal('39.37007874015747600000')
>>> print d
1 meters (m)
0.000621371 miles (mi)
1.09361 yard (yd)
3.28084 feet (ft)
39.3701 inch (inch)
0.001 kilometers (km)
100 centimeters (cm)
1000 millimeters (mm)
1e+09 nanometer (nm)
>>> d
Decimal('1') meters (m)
Decimal('0.0006213711922373339015151515152') miles (mi)
Decimal('1.093613298337707666666666667') yard (yd)
Decimal('3.2808398950131230000') feet (ft)
Decimal('39.37007874015747600000') inch (inch)
Decimal('0.0010') kilometers (km)
Decimal('1.0E+2') centimeters (cm)
Decimal('1.0E+3') millimeters (mm)
Decimal('1.0E+9') nanometer (nm)
# distance = vt+.5at**2
>>> v = Velocity(49.0332501432)
>>> a = Acceleration(-9.80665002864) # gravity
>>> t = Time(0.0)
>>> print 'initial velocity = %f mps = %f fps' % (v.mps,v.fps)
initial velocity = 49.033250 mps = 160.870243 fps
>>> for sec in range(20):
... t.sec = sec
... d = v*t + Decimal(.5)*a*t**2
... height = Distance(d)
... if height < 0: break
... print 't',t.sec,'height',height.m,'m',height.ft,'ft'
t 0 height 0E-47 m 0E-66 ft
t 1 height 44.12992512888000007365008059 m 144.7832189267716379167004149 ft
t 2 height 78.45320022912000013093347661 m 257.3923892031495785185785154 ft
t 3 height 102.9698253007200001718501880 m 337.8275108291338218056343013 ft
t 4 height 117.6798003436800001964002149 m 386.0885838047243677778677731 ft
t 5 height 122.5831253580000002045835572 m 402.1756081299212164352789303 ft
t 6 height 117.6798003436800001964002149 m 386.0885838047243677778677731 ft
t 7 height 102.9698253007200001718501881 m 337.8275108291338218056343016 ft
t 8 height 78.4532002291200001309334767 m 257.3923892031495785185785157 ft
t 9 height 44.1299251288800000736500806 m 144.7832189267716379167004149 ft
t 10 height 0E-25 m 0E-44 ft
from decimal import *
"""
constants = """
from decimal import *
GRAVITY = Decimal('9.80665002864') # m/s2
FT_IN_MI = Decimal('5280.0')
FT_IN_M = Decimal('3.2808398950131230000')
FT_IN_YD = Decimal('3.0')
INCH_IN_FT = Decimal('12.0')
MI_IN_M = FT_IN_M / FT_IN_MI
YD_IN_M = FT_IN_M / FT_IN_YD
INCH_IN_M = FT_IN_M * INCH_IN_FT
KM_IN_M = Decimal('1.0e-3')
CM_IN_M = Decimal('1.0e2')
MM_IN_M = Decimal('1.0e3')
NM_IN_M = Decimal('1.0e9')
SEC_IN_MIN = Decimal('60.0')
MIN_IN_HR = Decimal('60.0')
DAY_IN_WK = Decimal('7.0')
HR_IN_DAY = Decimal('24.0')
DAY_IN_YR = Decimal('365.24218967')
HR_IN_SEC = Decimal('1.0')/(SEC_IN_MIN * MIN_IN_HR)
G_IN_KG = Decimal('1.0e3')
LB_IN_NEWTON = Decimal('.224808942911188')
OZ_IN_G = Decimal('0.0352739619000')
OZ_IN_LB = Decimal('16.0')
W_IN_HP = Decimal('745.699872')
"""
tables = [
[
["Distance","meters"],
["meters","m","Decimal(self)","self._update(Decimal(value))"],
["miles","mi","Decimal(self) * MI_IN_M","self._update(Decimal(value) * Decimal('1.0')/MI_IN_M)"],
["yard","yd","Decimal(self) * YD_IN_M","self._update(Decimal(value) * Decimal('1.0')/YD_IN_M)"],
["feet","ft","Decimal(self) * FT_IN_M","self._update(Decimal(value) * Decimal('1.0')/FT_IN_M)"],
["inch","inch","Decimal(self) * INCH_IN_M","self._update(Decimal(value) * Decimal('1.0')/INCH_IN_M)"],
["kilometers","km","Decimal(self) * KM_IN_M","self._update(Decimal(value) * Decimal('1.0')/KM_IN_M)"],
["centimeters","cm","Decimal(self) * CM_IN_M","self._update(Decimal(value) * Decimal('1.0')/CM_IN_M)"],
["millimeters","mm","Decimal(self) * MM_IN_M","self._update(Decimal(value) * Decimal('1.0')/MM_IN_M)"],
["nanometer","nm","Decimal(self) * NM_IN_M","self._update(Decimal(value) * Decimal('1.0')/NM_IN_M)"],
],
[
["Area","sq_meters"],
["sq_meters","m2","Decimal(self)","self._update(Decimal(value))"],
["sq_miles","mi2","Decimal(self) * (MI_IN_M * MI_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(MI_IN_M * MI_IN_M))"],
["sq_yard","yd2","Decimal(self) * (YD_IN_M * YD_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(YD_IN_M * YD_IN_M))"],
["sq_feet","ft2","Decimal(self) * (FT_IN_M * FT_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(FT_IN_M * FT_IN_M))"],
["sq_inch","inch2","Decimal(self) * (INCH_IN_M * INCH_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(INCH_IN_M * INCH_IN_M))"],
["sq_kilometers","km2","Decimal(self) * (KM_IN_M * KM_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(KM_IN_M * KM_IN_M))"],
["sq_centimeters","cm2","Decimal(self) * (CM_IN_M * CM_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(CM_IN_M * CM_IN_M))"],
["sq_millimeters","mm2","Decimal(self) * (MM_IN_M * MM_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(MM_IN_M * MM_IN_M))"],
],
[
["Volumn","cubic_meters"],
["cubic_meters","m3","Decimal(self)","self._update(Decimal(value))"],
["cubic_miles","mi3","Decimal(self) * (MI_IN_M * MI_IN_M * MI_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(MI_IN_M * MI_IN_M * MI_IN_M))"],
["cubic_yard","yd3","Decimal(self) * (YD_IN_M * YD_IN_M * YD_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(YD_IN_M * YD_IN_M * YD_IN_M))"],
["cubic_feet","ft3","Decimal(self) * (FT_IN_M * FT_IN_M * FT_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(FT_IN_M * FT_IN_M * FT_IN_M))"],
["cubic_inch","inch3","Decimal(self) * (INCH_IN_M * INCH_IN_M * INCH_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(INCH_IN_M * INCH_IN_M * INCH_IN_M))"],
["cubic_kilometers","km3","Decimal(self) * (KM_IN_M * KM_IN_M * KM_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(KM_IN_M * KM_IN_M * KM_IN_M))"],
["cubic_centimeters","cm3","Decimal(self) * (CM_IN_M * CM_IN_M * CM_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(CM_IN_M * CM_IN_M * CM_IN_M))"],
["cubic_millimeters","mm3","Decimal(self) * (MM_IN_M * MM_IN_M * MM_IN_M)","self._update(Decimal(value) * Decimal('1.0')/(MM_IN_M * MM_IN_M * MM_IN_M))"],
],
[
["Time","sec"],
["sec","sec","Decimal(self)","self._update(Decimal(value))"],
["min","min","Decimal(self) * Decimal('1.0')/SEC_IN_MIN","self._update(Decimal(value) * SEC_IN_MIN)"],
["hour","hr","Decimal(self) * Decimal('1.0')/(SEC_IN_MIN*MIN_IN_HR)","self._update(Decimal(value) * (SEC_IN_MIN*MIN_IN_HR))"],
["day","day","Decimal(self) * Decimal('1.0')/(HR_IN_DAY*SEC_IN_MIN*MIN_IN_HR)","self._update(Decimal(value) * (HR_IN_DAY*SEC_IN_MIN*MIN_IN_HR))"],
["week","wk","Decimal(self) * Decimal('1.0')/(DAY_IN_WK*HR_IN_DAY*SEC_IN_MIN*MIN_IN_HR)","self._update(Decimal(value) * (DAY_IN_WK*HR_IN_DAY*SEC_IN_MIN*MIN_IN_HR))"],
["year","yr","Decimal(self) * Decimal('1.0')/(DAY_IN_YR*HR_IN_DAY*SEC_IN_MIN*MIN_IN_HR)","self._update(Decimal(value) * (DAY_IN_YR*HR_IN_DAY*SEC_IN_MIN*MIN_IN_HR))"],
],
[
["Velocity","meters_per_sec"],
["meters_per_sec","mps","Decimal(self)","self._update(Decimal(value))"],
["miles_per_sec","mips","Decimal(self) * MI_IN_M","self._update(Decimal(value) * Decimal('1.0')/MI_IN_M)"],
["miles_per_hr","mph","Decimal(self) * (MI_IN_M * SEC_IN_MIN * MIN_IN_HR)","self._update(Decimal(value) * Decimal('1.0')/(MI_IN_M * SEC_IN_MIN * MIN_IN_HR))"],
["ft_per_sec","fps","Decimal(self) * FT_IN_M","self._update(Decimal(value) * Decimal('1.0')/FT_IN_M)"],
["inch_per_sec","inchps","Decimal(self) * INCH_IN_M","self._update(Decimal(value) * Decimal('1.0')/INCH_IN_M)"],
["km_per_hour","kmph","Decimal(self) * (KM_IN_M * SEC_IN_MIN * MIN_IN_HR)","self._update(Decimal(value) * Decimal('1.0')/(KM_IN_M * SEC_IN_MIN * MIN_IN_HR))"],
["km_per_sec","kmps","Decimal(self) * KM_IN_M","self._update(Decimal(value) * Decimal('1.0')/KM_IN_M)"],
],
[
["Acceleration","meters_per_sq_sec"],
["meters_per_sq_sec","mps2","Decimal(self)","self._update(Decimal(value))"],
["miles_per_sq_sec","mips2","Decimal(self) * MI_IN_M","self._update(Decimal(value) * Decimal('1.0')/MI_IN_M)"],
["miles_per_hr_per_sec","mphps","Decimal(self) * (MI_IN_M * SEC_IN_MIN * MIN_IN_HR)","self._update(Decimal(value) * Decimal('1.0')/(MI_IN_M * SEC_IN_MIN * MIN_IN_HR))"],
["ft_per_sq_sec","fps2","Decimal(self) * FT_IN_M","self._update(Decimal(value) * Decimal('1.0')/FT_IN_M)"],
["inch_per_sq_sec","ips2","Decimal(self) * INCH_IN_M","self._update(Decimal(value) * Decimal('1.0')/INCH_IN_M)"],
["km_per_hour_per_sec","kmphps","Decimal(self) * (KM_IN_M * SEC_IN_MIN * MIN_IN_HR)","self._update(Decimal(value) * Decimal('1.0')/(KM_IN_M * SEC_IN_MIN * MIN_IN_HR))"],
["km_per_sq_sec","kmps2","Decimal(self) * KM_IN_M","self._update(Decimal(value) * Decimal('1.0')/KM_IN_M)"],
],
[
["Mass","kilogram"],
["kilogram","kg","Decimal(self)","self._update(Decimal(value))"],
["gram","g","Decimal(self) * Decimal('1000.0')","self._update(Decimal(value) / Decimal('1000.0'))"],
["ounce","oz","Decimal(self) * OZ_IN_G * Decimal('1000.0')","self._update(Decimal(value) / OZ_IN_G / Decimal('1000.0'))"],
["pounds","lbm","Decimal(self) * (OZ_IN_G / OZ_IN_LB) * Decimal('1000.0')","self._update(Decimal(value)* OZ_IN_LB / OZ_IN_G / Decimal('1000.0') )"],
],
[
["Force","newton"], # m*kg*s**2
["newton","N","Decimal(self)","self._update(Decimal(value))"],
["kilogram-force","kgf","Decimal(self) / GRAVITY","self._update(Decimal(value) * GRAVITY)"],
["dyne","dyn","Decimal(self) * Decimal('100000.0')","self._update(Decimal(value) / Decimal('100000.0'))"],
["pound-force","lbf","Decimal(self) * (G_IN_KG * OZ_IN_G) / (OZ_IN_LB*GRAVITY)","self._update(Decimal(value) * (OZ_IN_LB * GRAVITY) / (G_IN_KG * OZ_IN_G))"],
],
[
["Weight","kilogram"], # m*kg*s**2
["kilogram","kg","Decimal(self)","self._update(Decimal(value))"],
["gram","g","Decimal(self) * G_IN_KG ","self._update(Decimal(value) / G_IN_KG)"],
["ounce","oz","Decimal(self) * G_IN_KG * OZ_IN_G ","self._update(Decimal(value) / (G_IN_KG * OZ_IN_G))"],
["pounds","lbm","Decimal(self) * (G_IN_KG * OZ_IN_G) / (OZ_IN_LB)","self._update(Decimal(value) * (OZ_IN_LB ) / (G_IN_KG * OZ_IN_G))"],
],
[
["Pressure","pascal "],
["pascal","Pa","Decimal(self)","self._update(Decimal(value))"],
["newton_per_sq_m","Nm2","Decimal(self)","self._update(Decimal(value))"],
["kilogram_per_sq_m","kgfpm2","Decimal(self) * Decimal('1.0')/GRAVITY","self._update(Decimal(value) * GRAVITY)"],
["pound_per_sq_inch","psi","Decimal(self) * (LB_IN_NEWTON/(INCH_IN_M * INCH_IN_M))","self._update(Decimal(value) * (INCH_IN_M * INCH_IN_M) / LB_IN_NEWTON)"],
["pound_per_sq_ft","psf","Decimal(self) * LB_IN_NEWTON/(FT_IN_M * FT_IN_M)","self._update(Decimal(value) * (FT_IN_M * FT_IN_M) / LB_IN_NEWTON)"],
],
[
["Frequency","Frequency"],
["hertz","Hz","Decimal(self)","self._update(Decimal(value))"],
["KHz","KHz","Decimal(self) * Decimal('1.0')/Decimal(1.0e3)","self._update(Decimal(value) * Decimal('1.0e3'))"],
["MHz","MHz","Decimal(self) * Decimal('1.0')/Decimal(1.0e6)","self._update(Decimal(value) * Decimal('1.0e6'))"],
["GHz","GHz","Decimal(self) * Decimal('1.0')/Decimal(1.0e9)","self._update(Decimal(value) * Decimal('1.0e9'))"],
],
[
["Power","Power"],
["watts","W","Decimal(self)","self._update(Decimal(value))"],
["kilowatt","KW","Decimal(self) * Decimal('1.0')/Decimal('1.0e3')","self._update(Decimal(value) * Decimal('1.0e3'))"],
["megawatt","MW","Decimal(self) * Decimal('1.0')/Decimal('1.0e6')","self._update(Decimal(value) * Decimal('1.0e6'))"],
["Horsepower","hp","Decimal(self) * Decimal('1.0')/W_IN_HP","self._update(Decimal(value) * W_IN_HP)"],
["joulepersec","jps","Decimal(self)","self._update(Decimal(value))"],
],
[
["Temperature","degreeK"],
["Kelvin","K","Decimal(self)","self._update(Decimal(value))"],
["Fahrenheit","F","((Decimal(self) - Decimal('273.15')) * Decimal('9.0')/Decimal('5.0')) + Decimal('32.0')","self._update((Decimal(value) - Decimal('32.0')) * (Decimal('5.0')/Decimal('9.0')) + Decimal('273.15'))"],
["Celsius","C","Decimal(self) - Decimal('273.15')","self._update(Decimal(value) + Decimal('273.15'))"],
],
]
def build_class(table):
"build a class for each table i.e. Distance,Velocity,etc."
name, baseunits = table[0]
s = '\nclass %(name)s(Decimal):\n' % locals()
s += ' __slots__ = ("_update",) # generate AttributeError on illegal property; example: if d.yds instead of d.ydgenerate AttributeError example: if d.yds not d.yd\n'
return s
def build_init(table):
"update method"
s = """
def _update(self,dec):
self._exp = dec._exp
self._sign = dec._sign
self._int = dec._int
self._is_special = dec._is_special
"""
return s
def build_str_funct(table):
"str method"
fmt1 = " def __str__(self):\n s = ''\n"
fmt2 = " s += '%%g %(units)s (%(abrev)s)\\n' %% self.%(abrev)s\n"
name, baseunits = table[0]
s = fmt1 % locals()
for data in table[1:]:
if len(data) == 3:
units, abrev, value = data
else:
units, abrev, value, value2 = data
s += fmt2 % locals()
s += ' return s[:-1]\n'
return s
def build_repr_funct(table):
"repr method"
fmt1 = " def __repr__(self):\n s = ''\n"
fmt2 = " s += '%%r %(units)s (%(abrev)s)\\n' %% self.%(abrev)s\n"
name, baseunits = table[0]
s = fmt1 % locals()
for data in table[1:]:
if len(data) == 3:
units, abrev, value = data
else:
units, abrev, value, value2 = data
s += fmt2 % locals()
s += ' return s[:-1]\n'
return s
def build_methods(table):
"setter and getter property methods"
fmt = """ @property
def %(abrev)s(self):
return eval("%(value)s")
@%(abrev)s.setter
def %(abrev)s(self, value):
eval("%(value2)s")\n"""
s = ''
name, baseunits = table[0]
for data in table[1:]:
if len(data) == 3:
units, abrev, value = data
value2 = str(Decimal(1.0)/eval(value))
else:
units, abrev, value, value2 = data
s += fmt % locals()
return s
def build_header(header,tables,examples):
"build module description"
s = header
s += 'Conversions:\n'
for table in tables:
s += ' %s\n' % table[0][0]
s += examples
s += '\n"""\n'
return s
def build_doctest_call(modulename, testfilename):
"create method to call doctest on module and module test file"
s = """
def test():
"test method tests examples and testfile"
print '\\n**** %s test ****\\n'
import doctest
import %s
doctest.testmod(%s, verbose=True, report=True)
print doctest.master.summarize()
doctest.testfile('%s', verbose=True, report=True)
print doctest.master.summarize()
if __name__ == '__main__':
test()
""" % (modulename,modulename,modulename,testfilename)
return s
def build_module(tables, modulename):
"build module from data table "
s = '"""\n'
s += build_header(header, tables, examples)
s += constants
for table in tables:
s += build_class(table)
s += build_init(table)
s += build_str_funct(table)
s += build_repr_funct(table)
s += build_methods(table)
s += build_doctest_call(modulename, testfilename)
return s
def build_test(table):
"build a test for all getters and setters for each class"
name, baseunits = table[0]
s = '\n%s conversion class\n' % name
s += '>>> from conversion import %s\n' % (name)
s += '>>> %s = %s(0.0)\n' % (name.lower()[0],name)
args = [arg[1] for arg in table[1:]]
for arg in args:
s += '>>> %s.%s = 1.0\n' % (name.lower()[0],arg)
s += '>>> print %s\n' % (name.lower()[0])
x = eval('%s()' % name)
exec('x.%s = 1.0' % arg)
for line in str(x).split('\n'):
s += '%s\n' % line
s += '>>> %s\n' % (name.lower()[0])
x = eval('%s()' % name)
exec('x.%s = 1.0' % arg)
for line in repr(x).split('\n'):
s += '%s\n' % line
s += '\n'
return s
def build_doctest(modulename,testfilename):
"builds test file for testing %s.py based on table data" % (modulename)
s = 'building %s' % testfilename
s += '\n## **** %s Test ****\n' % (modulename)
s += 'from %s import *' % (modulename)
s += '"""'
for table in tables:
s += build_test(table)
return s
if __name__ == '__main__':
filename = 'conversion.py'
modulename = filename[:-3]
testfilename = modulename+'Test.txt'
print 'building', filename
fp = open(filename,'w')
s = build_module(tables, modulename)
print >>fp,s
fp.close()
from conversion import *
print 'building', testfilename
fp = open(testfilename,'w')
s = build_doctest(modulename,testfilename)
print >>fp,s
fp.close()