#**************************************************************************** # 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)