Limnoria/others/convertcore.py

1184 lines
38 KiB
Python
Executable File

#!/usr/bin/env python
#****************************************************************************
# This file has been modified from its original version. It has been
# formatted to fit your irc bot.
#
# The original version is a nifty PyQt application written by Douglas Bell,
# available at http://www.bellz.org/convertall/.
#
# Below is the original copyright. Doug Bell rocks.
# The hijacker is Keith Jones, and he has no bomb in his shoe.
#
#****************************************************************************
#****************************************************************************
# convertcore.py, provides non-GUI base classes for data
#
# ConvertAll, a units conversion program
# Copyright (C) 2002, Douglas W. Bell
#
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License, Version 2. This program is
# distributed in the hope that it will be useful, but WITTHOUT ANY WARRANTY.
#*****************************************************************************
import re, copy, sys, os.path, StringIO
import supybot.conf as conf
import supybot.registry as registry
#*****************************************************************************
#
# Units are defined by an optional quantity and an equivalent unit or unit
# combination. A python expression may be used for the quantity, but is
# resticted to using only the following operators: *, /, +, -, **, (, ).
# Beware of integer division truncation: be sure to use a float for at
# least one of the values.
#
# The unit type must be placed in square brackets before a set of units.
# The first comment after the equivalent unit will be put in parenthesis after
# the unit name (usually used to give the full name of an abbreviated unit).
# The next comment will be used in the program list's comment column;
# later comments and full line comments are ignored.
#
# Non-linear units are indicated with an equivalent unit in square brackets,
# followed by either equations or equivalency lists for the definition.
# For equations, two are given, separated by a ';'. Both are functions of
# "x", the first going from the unit to the equivalent unit and the second
# one in reverse. Any valid Python expression returning a float (including
# the functions in the math module) should work. The equivalency list is a
# python list of tuples giving points for linear interpolation.
#
# All units must reduce to primitive units, which are indicated by an '!'
# as the equivalent unit. Circular refernces must also be avoided.
#
# Primitive units: kg, m, s, K, A, mol, cd, rad, sr, bit, unit
#
##############################################################################
unitData = \
"""
#
# mass units
#
[mass]
kg = ! # kilogram
kilogram = kg
key = kg # # drug slang
hectogram = 100 gram
dekagram = 10 gram
gram = 0.001 kg
g = gram # gram
decigram = 0.1 gram
centigram = 0.01 gram
milligram = 0.001 gram
mg = milligram # milligram
microgram = 0.001 mg
tonne = 1000 kg # # metric
metric ton = tonne
megagram = tonne
kilotonne = 1000 tonne # # metric
carat = 0.2 gram
ct = carat # carat
amu = 1.66053873e-27 kg # atomic mass
atomic mass unit = amu
pound = 0.45359237 kg
lb = pound # pound
lbm = pound # pound
ounce = 1/16.0 pound
oz = ounce # ounce
lid = ounce # # drug slang
ton = 2000 lb # # non-metric
kiloton = 1000 ton # # non-metric
slug = lbf*s^2/ft
stone = 14 lb
grain = 1/7000.0 lb
#
# length / distance units
#
[length]
m = ! # meter
meter = m
metre = m
decimeter = 0.1 m
cm = 0.01 m # centimeter
centimeter = cm
mm = 0.001 m # millimeter
millimeter = mm
micrometer = 1e-6 m
micron = micrometer
nanometer = 1e-9 m
nm = nanometer # nanometer
dekameter = 10 m
hectometer = 100 m
km = 1000 m # kilometer
kilometer = km
megameter = 1000 km
angstrom = 1e-10 m
fermi = 1e-15 m # # nuclear sizes
barn = 1e-24 m # # particle physics
inch = 2.54 cm
in = inch # inch
inches = inch
mil = 0.001 inch
microinch = 1e-6 inch
microinches = microinch
foot = 12 inch
ft = foot # foot
feet = foot
yard = 3 ft
yd = yard # yard
mile = 5280 ft
mi = mile # mile
nautical mile = 1852 m
nmi = nautical mile # nautical mile
league = 3 mile
chain = 66 ft
fathom = 6 ft
rod = 5.5 yard
furlong = 40 rod
hand = 4 inch
cubit = 21.8 inch # # biblical unit
point = 1/72.27 inch
pica = 12 point
caliber = 0.01 inch # # bullet sizes
football field = 100 yd
marathon = 46145 yd
au = 1.49597870691e11 m # astronomical unit
astronomical unit = au
light year = 365.25 light speed * day
light minute = light speed * min
light second = light speed * s
parsec = 3.0856775813e16 m
kiloparsec = 1000 parsec
megaparsec = 1000 kiloparsec
screw size = [in] 0.013*x + 0.06 ; (x - 0.06) / 0.013 \
# # Unified diameters, non-linear
AWG = [in] 92.0**((36-x)/39.0)/200.0 ; \
36 - 39.0*log(200.0*x)/log(92.0) \
# American Wire Gauge \
# use -1, -2 for 00, 000; non-linear
American Wire Gauge = [in] 92.0**((36-x)/39.0)/200.0 ; \
36 - 39.0*log(200.0*x)/log(92.0) \
# # use -1, -2 for 00, 000; non-linear
standard gauge = [in] [(-5, .448350), (1, .269010), (14, .0747250), \
(16, .0597800), (17, .0538020), (20, .0358680), \
(26, .0179340), (31, .0104615), (36, .00672525), \
(38, .00597800)] # steel \
# Manufacturers Std. Gauge, non-linear
zinc gauge = [in] [(1, .002), (10, .02), (15, .04), (19, .06), \
(23, .1), (24, .125), (27, .5), (28, 1)] \
# # sheet metal thickness, non-linear
ring size = [in] 0.1018*x + 1.4216 ; (x - 1.4216) / 0.1018 \
# # US size, circum., non-linear
shoe size mens = [in] x/3.0 + 7 + 1/3.0 ; (x - 7 - 1/3.0) * 3 \
# # US sizes, non-linear
shoe size womens = [in] x/3.0 + 6 + 5/6.0 ; (x - 6 - 5/6.0) * 3 \
# # US sizes, non-linear
#
# time units
#
[time]
s = ! # second
sec = s # second
second = s
ms = 0.001 s # millisecond
millisecond = ms
microsecond = 1e-6 s
ns = 1e-9 s # nanosecond
nanosecond = ns
minute = 60 s
min = minute # minute
hour = 60 min
hr = hour # hour
bell = 30 min # # naval definition
watch = 4 hour
watches = watch
day = 24 hr
week = 7 day
wk = week # week
fortnight = 14 day
month = 1/12.0 year
year = 365.242198781 day
yr = year # year
calendar year = 365 day
decade = 10 year
century = 100 year
centuries = century
millennium = 1000 year
millennia = millennium
[scheduling]
man hour = 168/40.0 hour
man week = 40 man hour
man month = 1/12.0 man year
man year = 52 man week
#
# temperature
#
[temperature]
K = ! # Kelvin
Kelvin = K
deg K = K # Kelvin
degree Kelvin = K
C = [K] x + 273.15 ; x - 273.15 # Celsius # non-linear
Celsius = [K] x + 273.15 ; x - 273.15 # # non-linear
deg C = [K] x + 273.15 ; x - 273.15 # Celsius # non-linear
degree Celsius = [K] x + 273.15 ; x - 273.15 # # non-linear
R = 5/9.0 K # Rankine
Rankine = R
deg R = R # Rankine
F = [R] x + 459.67 ; x - 459.67 # Fahrenheit # non-linear
Fahrenheit = [R] x + 459.67 ; x - 459.67 # # non-linear
deg F = [R] x + 459.67 ; x - 459.67 # Fahrenheit # non-linear
degree Fahrenheit = [R] x + 459.67 ; x - 459.67 # # non-linear
[temp. diff.]
C deg = K # Celsius degree
Celsius degree = C deg
F deg = R # Fahrenheit deg.
Fahrenheit degree = F deg
#
# electrical units
#
[current]
A = ! # ampere
ampere = A
amp = A
milliampere = 0.001 A
milliamp = milliampere
mA = milliampere # milliampere
microampere = 0.001 mA
kiloampere = 1000 A
kA = kiloampere # kiloampere
[charge]
coulomb = A*s
amp hour = A*hr
mAh = 0.001 amp hour # milliamp hour
milliamp hour = mAh
[potential]
volt = W/A
V = volt # volt
millivolt = 0.001 volt
mV = millivolt # millivolt
kilovolt = 1000 volt
kV = kilovolt # kilovolt
[resistance]
ohm = V/A
milliohm = 0.001 ohm
microhm = 0.001 milliohm
kilohm = 1000 ohm
[conductance]
siemens = A/V
[capacitance]
farad = coulomb/V
millifarad = 0.001 farad
microfarad = 0.001 millifarad
nanofarad = 1e-9 farad
picofarad = 1e-12 farad
[magn. flux]
weber = V*s
Wb = weber # weber
[inductance]
henry = Wb/A
H = henry # henry
millihenry = 0.001 henry
mH = millihenry # millihenry
microhenry = 0.001 mH
[flux density]
tesla = Wb/m^2
T = tesla # tesla
#
# molecular units
#
[molecular qty]
mol = ! # mole
mole = mol
kilomole = 1000 mol
kmol = kilomole # kilomole
[size of a mol]
avogadro = gram/amu*mol
#
# Illumination units
#
[lum. intens.]
cd = ! # candela
candela = cd
[luminous flux]
lumen = cd * sr
lm = lumen # lumen
[illuminance]
lux = lumen/m^2
footcandle = lumen/ft^2
metercandle = lumen/m^2
[luminance]
lambert = cd/pi*cm^2
millilambert = 0.001 lambert
footlambert = cd/pi*ft^2
#
# angular units
#
[angle]
radian = !
rad = radian # radian
circle = 2 pi*radian
turn = circle
revolution = circle
rev = revolution # revolution
degree = 1/360.0 circle
deg = degree # degree
arc min = 1/60.0 degree # minute
arc minute = arc min
min arc = arc min # minute
minute arc = arc min
arc sec = 1/60.0 arc min # second
arc second = arc sec
sec arc = arc sec # second
second arc = arc sec
quadrant = 1/4.0 circle
right angle = quadrant
gradian = 0.01 quadrant
#
# solid angle units
#
[solid angle]
sr = ! # steradian
steradian = sr
sphere = 4 pi*sr
hemisphere = 1/2.0 sphere
#
# information units
#
[data]
bit = !
kilobit = 1000 bit # # based on power of 10
byte = 8 bit
B = byte # byte
kilobyte = 1000 byte # # based on power of 2
kB = kilobyte # kilobyte # based on power of 2
megabyte = 1000 kB # # based on power of 2
MB = megabyte # megabyte # based on power of 2
gigabyte = 1000 MB # # based on power of 2
GB = gigabyte # gigabyte # based on power of 2
terabyte = 1000 GB # # based on power of 2
TB = terabyte # terabyte # based on power of 2
petabyte = 1000 TB # # based on power of 2
PB = petabyte # petabyte # based on power of 2
kibibyte = 1024 byte
KiB = kibibyte # kibibyte
mebibyte = 1024 KiB
MiB = mebibyte # mebibyte
gibibyte = 1024 MiB
GiB = gibibyte # gibibyte
tebibyte = 1024 GiB
TiB = tebibyte # tebibyte
pebibyte = 1024 TiB
PiB = pebibyte # pebibyte
[data transfer]
bps = bit/sec # bits / second
kbps = 1000 bps # kilobits / sec. # based on power of 10
#
# Unitless numbers
#
[quantity]
unit = !
1 = unit # unit
pi = 3.14159265358979323846 unit
pair = 2 unit
hat trick = 3 unit # # sports
dozen = 12 unit
doz = dozen # dozen
bakers dozen = 13 unit
score = 20 unit
gross = 144 unit
great gross = 12 gross
ream = 500 unit
percent = 0.01 unit
% = percent
mill = 0.001 unit
[interest rate]
APR = [unit] log(1 + x/100) ; (exp(x) - 1)*100 \
# annual % rate # based on continuous compounding
[concentration]
proof = 1/200.0 unit # # alcohol content
ppm = 1e-6 unit # parts per million
parts per million = ppm
ppb = 1e-9 unit # parts per billion
parts per billion = ppb
ppt = 1e-12 unit # parts per trillion
parts per trillion = ppt
karat = 1/24.0 unit # # gold purity
carat gold = karat # # gold purity
#
# force units
#
[force]
newton = kg*m/s^2
N = newton # newton
dekanewton = 10 newton
kilonewton = 1000 N
kN = kilonewton # kilonewton
meganewton = 1000 kN
millinewton = 0.001 N
dyne = cm*g/s^2
kg force = kg * gravity # kilogram f
kgf = kg force # kilogram force
kilogram force = kg force
gram force = g * gravity
pound force = lbm * gravity
lbf = pound force # pound force
ton force = ton * gravity
ounce force = ounce * gravity
ozf = ounce force # ounce force
#
# area units
#
[area]
square meter = m^2
are = 100 m^2
hectare = 100 are
acre = 10 chain^2
section = mile^2
township = 36 section
homestead = 160 acre
circular inch = 1/4.0 pi*in^2 # # area of 1 inch circle
circular mil = 1/4.0 pi*mil^2 # # area of 1 mil circle
#
# volume units
#
[volume]
cc = cm^3 # cubic centimeter
cubic centimeter = cc
liter = 1000 cc
L = liter # liter
litre = liter
deciliter = 0.1 liter
centiliter = 0.01 liter
milliliter = cc
mL = milliliter # milliliter
dekaliter = 10 liter
hectoliter = 100 liter
kiloliter = 1000 liter
kL = kiloliter # kiloliter
cubic meter = kiloliter
megaliter = 1000 kiloliter
gallon = 231 in^3
gal = gallon # gallon
quart = 1/4.0 gallon
qt = quart # quart
pint = 1/2.0 quart
pt = pint # pint
fluid ounce = 1/16.0 pint
fl oz = fluid ounce # fluid ounce
ounce fluid = fluid ounce
cup = 8 fl oz
tablespoon = 1/16.0 cup
tbsp = tablespoon # tablespoon
teaspoon = 1/3.0 tbsp
tsp = teaspoon # teaspoon
barrel = 42 gallon
bbl = barrel # barrel
shot = 1.5 fl oz
fifth = 1/5.0 gallon # # alcohol
wine bottle = 750 mL
magnum = 1.5 liter # # alcohol
keg = 15.5 gallon # # beer
bushel = 2150.42 in^3
peck = 1/4.0 bushel
cord = 128 ft^3
board foot = ft^2*in
board feet = board foot
#
# velocity units
#
[velocity]
knot = nmi/hr
kt = knot # knot
light speed = 2.99792458e8 m/s
mph = mi/hr # miles/hour
kph = km/hr # kilometers/hour
mach = 331.46 m/s # # speed sound at STP
[rot. velocity]
rpm = rev/min # rev/min
rps = rev/sec # rev/sec
#
# flow rate units
#
[fluid flow]
gph = gal/hr # gallons/hour
gpm = gal/min # gallons/minute
cfs = ft^3/sec # cu ft/second
cfm = ft^3/min # cu ft/minute
lpm = L/min # liter/min
[gas flow]
sccm = atm*cc/min # std cc/min # pressure * flow
sccs = atm*cc/sec # std cc/sec # pressure * flow
slpm = atm*L/min # std liter/min # pressure * flow
slph = atm*L/hr # std liter/hour # pressure * flow
scfh = atm*ft^3/hour # std cu ft/hour # pressure * flow
scfm = atm*ft^3/min # std cu ft/min # pressure * flow
#
# pressure units
#
[pressure]
Pa = N/m^2 # pascal
pascal = Pa
kPa = 1000 Pa # kilopascal
kilopascal = kPa
MPa = 1000 kPa # megapascal
megapascal = MPa
atm = 101325 Pa # atmosphere
atmosphere = atm
bar = 1e5 Pa
mbar = 0.001 bar # millibar
millibar = mbar
microbar = 0.001 mbar
decibar = 0.1 bar
kilobar = 1000 bar
mm Hg = mm*density Hg*gravity
millimeter of Hg = mm Hg
torr = mm Hg
in Hg = in*density Hg*gravity # inch of Hg
inch of Hg = in Hg
m water = m*density water*gravity # meter of H2O
m H2O = m water # meter of H2O
meter of water = m water
in water = in*density water*gravity # inch of H2O
in H2O = in water # inch of H2O
inch of water = in water
ft water = ft*density water*gravity # feet of H2O
ft H2O = ft water # feet of H20
feet of water = ft water
foot of head = ft water
ft hd = ft water # foot of head
psi = lbf/in^2 # pound / sq inch
pound per sq inch = psi
ksi = 1000 psi # 1000 lb / sq inch
#
# density units
#
[density]
density water = gram/cm^3
density sea water = 1.025 gram/cm^3
density Hg = 13.5950981 gram/cm^3
density air = 1.293 kg/m^3 # # at STP
density steel = 0.283 lb/in^3 # # carbon steel
density aluminum = 0.098 lb/in^3
density zinc = 0.230 lb/in^3
density brass = 0.310 lb/in^3 # # 80Cu-20Zn
density copper = 0.295 lb/in^3
density iron = 0.260 lb/in^3 # # cast iron
density nickel = 0.308 lb/in^3
density tin = 0.275 lb/in^3
density titanium = 0.170 lb/in^3
density silver = 0.379 lb/in^3
density nylon = 0.045 lb/in^3
density polycarbonate = 0.045 lb/in^3
#
# energy units
#
[energy]
joule = N*m
J = joule # joule
kilojoule = 1000 joule
kJ = kilojoule # kilojoule
megajoule = 1000 kilojoule
gigajoule = 1000 megajoule
millijoule = 0.001 joule
mJ = millijoule # millijoule
calorie = 4.1868 J
cal = calorie # calorie
kilocalorie = 1000 cal
kcal = kilocalorie # kilocalorie
calorie food = kilocalorie
Btu = cal*lb*R/g*K # British thermal unit
British thermal unit = Btu
erg = cm*dyne
electronvolt = 1.602176462e-19 J
eV = electronvolt # electronvolt
kWh = kW*hour # kilowatt-hour
kilowatt hour = kWh
ton TNT = 4.184e9 J
#
# power units
#
[power]
watt = J/s
W = watt # watt
kilowatt = 1000 W
kW = kilowatt # kilowatt
megawatt = 1000 kW
MW = megawatt # megawatt
gigawatt = 1000 MW
GW = gigawatt # gigawatt
milliwatt = 0.001 W
horsepower = 550 ft*lbf/sec
hp = horsepower # horsepower
metric horsepower = 75 kgf*m/s
#
# frequency
#
[frequency]
hertz = unit/sec
Hz = hertz # hertz
millihertz = 0.001 Hz
kilohertz = 1000 Hz
kHz = kilohertz # kilohertz
megahertz = 1000 kHz
MHz = megahertz # megahertz
gigahertz = 1000 MHz
GHz = gigahertz # gigahertz
#
# radioactivity
#
[radioactivity]
becquerel = unit/sec
Bq = becquerel # becquerel
curie = 3.7e10 Bq
millicurie = 0.001 curie
roentgen = 2.58e-4 coulomb/kg
[radiation dose]
gray = J/kg
Gy = gray # gray
rad. abs. dose = 0.001 Gy # # commonly rad
sievert = J/kg # # equiv. dose
millisievert = 0.001 sievert # # equiv. dose
Sv = sievert # sievert # equiv. dose
rem = 0.01 Sv # # roentgen equiv mammal
millirem = 0.001 rem # # roentgen equiv mammal
#
# viscosity
#
[dyn viscosity]
poise = g/cm*s
P = poise # poise
centipoise = 0.01 poise
cP = centipoise # centipoise
[kin viscosity]
stokes = cm^2/s
St = stokes # stokes
centistokes = 0.01 stokes
cSt = centistokes # centistokes
#
# misc. units
#
[acceleration]
gravity = 9.80665 m/s^2
[constant]
gravity constant = 6.673e-11 N*m^2/kg^2
gas constant = 8.314472 J/mol*K # R # based on gram mol
[fuel consumpt.]
mpg = mi/gal # miles/gallon
"""
class UnitGroup:
"Stores, updates and converts a group of units"
maxDecPlcs = 8
def __init__(self, unitData, option):
self.unitData = unitData
self.option = option
self.unitList = []
self.currentNum = 0
self.factor = 1.0
self.reducedList = []
self.linear = 1
def update(self, text, cursorPos=None):
"Decode user entered text into units"
self.unitList = self.parseGroup(text)
if cursorPos != None:
self.updateCurrentUnit(text, cursorPos)
else:
self.currentNum = len(self.unitList) - 1
def updateCurrentUnit(self, text, cursorPos):
"Set current unit number"
self.currentNum = len(re.findall('[\*/]', text[:cursorPos]))
def currentUnit(self):
"Return current unit if its a full match, o/w None"
if self.unitList and self.unitList[self.currentNum].equiv:
return self.unitList[self.currentNum]
return None
def currentPartialUnit(self):
"Return unit with at least a partial match, o/w None"
if not self.unitList:
return None
return self.unitData.findPartialMatch(self.unitList[self.currentNum]\
.name)
def currentSortPos(self):
"Return unit near current unit for sorting"
if not self.unitList:
return self.unitData[self.unitData.sortedKeys[0]]
return self.unitData.findSortPos(self.unitList[self.currentNum]\
.name)
def replaceCurrent(self, unit):
"Replace the current unit with unit"
if self.unitList:
exp = self.unitList[self.currentNum].exp
self.unitList[self.currentNum] = copy.copy(unit)
self.unitList[self.currentNum].exp = exp
else:
self.unitList.append(copy.copy(unit))
def completePartial(self):
"Replace a partial unit with a full one"
if self.unitList and not self.unitList[self.currentNum].equiv:
text = self.unitList[self.currentNum].name
unit = self.unitData.findPartialMatch(text)
if unit:
exp = self.unitList[self.currentNum].exp
self.unitList[self.currentNum] = copy.copy(unit)
self.unitList[self.currentNum].exp = exp
def moveToNext(self, upward):
"Replace unit with adjacent one based on match or sort position"
unit = self.currentSortPos()
num = self.unitData.sortedKeys.index(unit.name.\
replace(' ', '')) \
+ (upward and -1 or 1)
if 0 <= num < len(self.unitData.sortedKeys):
self.replaceCurrent(self.unitData[self.unitData.sortedKeys[num]])
def addOper(self, mult):
"Add new operator & blank unit after current, * if mult is true"
if self.unitList:
self.completePartial()
prevExp = self.unitList[self.currentNum].exp
self.currentNum += 1
self.unitList.insert(self.currentNum, Unit(''))
if (not mult and prevExp > 0) or (mult and prevExp < 0):
self.unitList[self.currentNum].exp = -1
def changeExp(self, newExp):
"Change the current unit's exponent"
if self.unitList:
self.completePartial()
if self.unitList[self.currentNum].exp > 0:
self.unitList[self.currentNum].exp = newExp
else:
self.unitList[self.currentNum].exp = -newExp
def clearUnit(self):
"Remove units"
self.unitList = []
def parseGroup(self, text):
"Return list of units from text string"
unitList = []
parts = [part.strip() for part in re.split('([\*/])', text)]
numerator = 1
while parts:
unit = self.parseUnit(parts.pop(0))
if not numerator:
unit.exp = -unit.exp
if parts and parts.pop(0) == '/':
numerator = not numerator
unitList.append(unit)
return unitList
def parseUnit(self, text):
"Return a valid or invalid unit with exponent from a text string"
parts = text.split('^', 1)
exp = 1
if len(parts) > 1: # has exponent
try:
exp = int(parts[1])
except ValueError:
if parts[1].lstrip().startswith('-'):
exp = -Unit.partialExp # tmp invalid exp
else:
exp = Unit.partialExp
unitText = parts[0].strip().replace(' ', '')
unit = copy.copy(self.unitData.get(unitText, None))
if not unit and unitText and unitText[-1] == 's' and not \
self.unitData.findPartialMatch(unitText): # check for plural
unit = copy.copy(self.unitData.get(unitText[:-1], None))
if not unit:
#unit = Unit(parts[0].strip()) # tmp invalid unit
raise UnitDataError('%s is not a valid unit.' % (unitText))
unit.exp = exp
return unit
def unitString(self, unitList=None):
"Return the full string for this group or a given group"
if unitList == None:
unitList = self.unitList[:]
fullText = ''
if unitList:
fullText = unitList[0].unitText(0)
numerator = 1
for unit in unitList[1:]:
if (numerator and unit.exp > 0) \
or (not numerator and unit.exp < 0):
fullText = '%s * %s' % (fullText, unit.unitText(1))
else:
fullText = '%s / %s' % (fullText, unit.unitText(1))
numerator = not numerator
return fullText
def groupValid(self):
"Return 1 if all unitself.reducedLists are valid"
if not self.unitList:
return 0
for unit in self.unitList:
if not unit.unitValid():
return 0
return 1
def reduceGroup(self):
"Update reduced list of units and factor"
self.linear = 1
self.reducedList = []
self.factor = 1.0
if not self.groupValid():
return
count = 0
tmpList = self.unitList[:]
while tmpList:
count += 1
if count > 5000:
raise UnitDataError, 'Circular unit definition'
unit = tmpList.pop(0)
if unit.equiv == '!':
self.reducedList.append(copy.copy(unit))
elif not unit.equiv:
raise UnitDataError, 'Invalid conversion for "%s"' % unit.name
else:
if unit.fromEqn:
self.linear = 0
newList = self.parseGroup(unit.equiv)
for newUnit in newList:
newUnit.exp *= unit.exp
tmpList.extend(newList)
self.factor *= unit.factor**unit.exp
self.reducedList.sort()
tmpList = self.reducedList[:]
self.reducedList = []
for unit in tmpList:
if self.reducedList and unit == self.reducedList[-1]:
self.reducedList[-1].exp += unit.exp
else:
self.reducedList.append(unit)
self.reducedList = [unit for unit in self.reducedList if \
unit.name != 'unit' and unit.exp != 0]
def categoryMatch(self, otherGroup):
"Return 1 if unit types are equivalent"
if not self.checkLinear() or not otherGroup.checkLinear():
return 0
return self.reducedList == otherGroup.reducedList and \
[unit.exp for unit in self.reducedList] \
== [unit.exp for unit in otherGroup.reducedList]
def checkLinear(self):
"Return 1 if linear or acceptable non-linear"
if not self.linear:
if len(self.unitList) > 1 or self.unitList[0].exp != 1:
return 0
return 1
def compatStr(self):
"Return string with reduced unit or linear compatability problem"
if self.checkLinear():
return self.unitString(self.reducedList)
return 'Cannot combine non-linear units'
def convert(self, num, toGroup):
"Return num of this group converted to toGroup"
if self.linear:
num *= self.factor
else:
num = self.nonLinearCalc(num, 1) * self.factor
n2 = -1
if toGroup.linear:
n2 = num / toGroup.factor
else:
n2 = toGroup.nonLinearCalc(num / toGroup.factor, 0)
return n2
def nonLinearCalc(self, num, isFrom):
"Return result of non-linear calculation"
x = num
try:
if self.unitList[0].toEqn: # regular equations
if isFrom:
temp = float(eval(self.unitList[0].fromEqn))
return temp
temp = float(eval(self.unitList[0].toEqn))
return temp
data = list(eval(self.unitList[0].fromEqn)) # extrapolation list
if isFrom:
data = [(float(group[0]), float(group[1])) for group in data]
else:
data = [(float(group[1]), float(group[0])) for group in data]
data.sort()
pos = len(data) - 1
for i in range(len(data)):
if num <= data[i][0]:
pos = i
break
if pos == 0:
pos = 1
y = (num-data[pos-1][0]) / float(data[pos][0]-data[pos-1][0]) \
* (data[pos][1]-data[pos-1][1]) + data[pos-1][1]
return y
except OverflowError:
return 1e9999
except:
raise UnitDataError, 'Bad equation for %s' % self.unitList[0].name
def convertStr(self, num, toGroup):
"Return formatted string of converted number"
return self.formatNumStr(self.convert(num, toGroup))
def formatNumStr(self, num):
"Return num string formatted per options"
decPlcs = self.option.intData('DecimalPlaces', 0, UnitGroup.maxDecPlcs)
if self.option.boolData('SciNotation'):
return ('%%0.%dE' % decPlcs) % num
if self.option.boolData('FixedDecimals'):
return ('%%0.%df' % decPlcs) % num
return ('%%0.%dG' % decPlcs) % num
class UnitDataError(Exception):
pass
class UnitData(dict):
def __init__(self):
dict.__init__(self)
self.sortedKeys = []
def readData(self):
"Read all unit data from file"
types = []
typeUnits = {}
try:
f = StringIO.StringIO(unitData)
lines = f.readlines()
f.close()
except IOError:
raise UnitDataError, 'Can not read "units.dat" file'
for i in range(len(lines)): # join continuation lines
delta = 1
while lines[i].rstrip().endswith('\\'):
lines[i] = ''.join([lines[i].rstrip()[:-1], lines[i+delta]])
lines[i+delta] = ''
delta += 1
units = [Unit(line) for line in lines if \
line.split('#', 1)[0].strip()] # remove comment lines
typeText = ''
for unit in units: # find & set headings
if unit.name.startswith('['):
typeText = unit.name[1:-1].strip()
types.append(typeText)
typeUnits[typeText] = []
unit.typeName = typeText
units = [unit for unit in units if unit.equiv] # keep valid units
for unit in units:
self[unit.name.replace(' ', '')] = unit
typeUnits[unit.typeName].append(unit.name)
self.sortedKeys = self.keys()
self.sortedKeys.sort()
if len(self.sortedKeys) < len(units):
raise UnitDataError, 'Duplicate unit names found'
return (types, typeUnits)
def findPartialMatch(self, text):
"Return first partially matching unit or None"
text = text.replace(' ', '')
if not text:
return None
for name in self.sortedKeys:
if name.startswith(text):
return self[name]
return None
def findSortPos(self, text):
"Return unit whose abbrev comes immediately after text"
text = text.replace(' ', '')
for name in self.sortedKeys:
if text <= name:
return self[name]
return self[self.sortedKeys[-1]]
class Unit:
"Reads and stores a single unit conversion"
partialExp = 1000
def __init__(self, dataStr):
dataList = dataStr.split('#')
unitList = dataList.pop(0).split('=', 1)
self.name = unitList.pop(0).strip()
self.equiv = ''
self.factor = 1.0
self.fromEqn = '' # used only for non-linear units
self.toEqn = '' # used only for non-linear units
if unitList:
self.equiv = unitList[0].strip()
if self.equiv[0] == '[': # used only for non-linear units
try:
self.equiv, self.fromEqn = re.match('\[(.*?)\](.*)', \
self.equiv).groups()
if ';' in self.fromEqn:
self.fromEqn, self.toEqn = self.fromEqn.split(';', 1)
self.toEqn = self.toEqn.strip()
self.fromEqn = self.fromEqn.strip()
except AttributeError:
raise UnitDataError, 'Bad equation for "%s"' % self.name
else: # split factor and equiv unit for linear
parts = self.equiv.split(None, 1)
if len(parts) > 1 and re.search('[^\d\.eE\+\-\*/]', parts[0]) \
== None: # only allowed digits and operators
try:
self.factor = float(eval(parts[0]))
self.equiv = parts[1]
except:
pass
self.comments = [comm.strip() for comm in dataList]
self.comments.extend([''] * (2 - len(self.comments)))
self.exp = 1
self.viewLink = [None, None]
self.typeName = ''
def description(self):
"Return name and 1st comment (usu. full name) if applicable"
if self.comments[0]:
return '%s (%s)' % (self.name, self.comments[0])
return self.name
def unitValid(self):
"Return 1 if unit and exponent are valid"
if self.equiv and -Unit.partialExp < self.exp < Unit.partialExp:
return 1
return 0
def unitText(self, absExp=0):
"Return text for unit name with exponent or absolute value of exp"
exp = self.exp
if absExp:
exp = abs(self.exp)
if exp == 1:
return self.name
if -Unit.partialExp < exp < Unit.partialExp:
return '%s^%d' % (self.name, exp)
if exp > 1:
return '%s^' % self.name
else:
return '%s^-' % self.name
def __cmp__(self, other):
return cmp(self.name, other.name)
############################################################################
# Wrapper functionality
#
############################################################################
# Parse the data file, and set everything up for conversion
data = UnitData()
(types, unitsByType) = data.readData()
# At the moment, we're not handling options
option = None
# set up the objects for unit conversion
fromUnit = UnitGroup(data, option)
toUnit = UnitGroup(data, option)
def convert(num, unit1, unit2):
""" Convert from one unit to another
num is the factor for the first unit. Raises UnitDataError for
various errors.
"""
fromUnit.update(unit1)
toUnit.update(unit2)
fromUnit.reduceGroup()
toUnit.reduceGroup()
# Match up unit categories
if not fromUnit.categoryMatch(toUnit):
raise UnitDataError('unit categories did not match')
return fromUnit.convert(num, toUnit)
def units(type):
""" Return comma separated string list of units of given type, or
a list of types if the argument is not valid.
"""
if type in types:
return '%s units: %s' % (type, ', '.join(unitsByType[type]))
else:
return 'valid types: ' + ', '.join(types)