diff options
author | Hernan Grecco <hernan.grecco@gmail.com> | 2012-05-03 02:20:28 +0200 |
---|---|---|
committer | Hernan Grecco <hernan.grecco@gmail.com> | 2012-05-03 02:20:28 +0200 |
commit | d477026ed8a33aff9cad5c183e30fa721688b559 (patch) | |
tree | 4f8d1e5b93a995330a7bc31a5104c59805bab1d3 | |
parent | 08053b48b2323d23fe88951e1757fb2f9104af6c (diff) | |
download | pint-d477026ed8a33aff9cad5c183e30fa721688b559.tar.gz |
Add code
-rw-r--r-- | pint/__init__.py | 17 | ||||
-rw-r--r-- | pint/default_en.txt | 294 | ||||
-rw-r--r-- | pint/pint.py | 829 |
3 files changed, 1140 insertions, 0 deletions
diff --git a/pint/__init__.py b/pint/__init__.py new file mode 100644 index 0000000..9d84be3 --- /dev/null +++ b/pint/__init__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" + pint + ~~~~ + + Pint is Python module/package to define, operate and manipulate + **physical quantities**: the product of a numerical value and a + unit of measurement. It allows arithmetic operations between them + and conversions from and to different units. + + :copyright: (c) 2012 by Hernan E. Grecco. + :license: BSD, see LICENSE for more details. +""" + +from .pint import UnitRegistry, DimensionalityError, UnitsContainer, UndefinedUnitError, logger, __version__ + + diff --git a/pint/default_en.txt b/pint/default_en.txt new file mode 100644 index 0000000..709e065 --- /dev/null +++ b/pint/default_en.txt @@ -0,0 +1,294 @@ +# Default Pint units definition file + +# decimal prefixes +yocto- = 10.0**-24 = y- +zepto- = 10.0**-21 = z- +atto- = 10.0**-18 = a- +femto- = 10.0**-15 = f- +pico- = 10.0**-12 = p- +nano- = 10.0**-9 = n- +micro- = 10.0**-6 = u- +milli- = 10.0**-3 = m- +centi- = 10.0**-2 = c- +deci- = 10.0**-1 = d- +deca- = 10.0 = da- +hecto- = 10.0**2 = h- +kilo- = 10.0**3 = k- +mega- = 10.0**6 = M- +giga- = 10.0**9 = G- +tera- = 10.0**12 = T- +peta- = 10.0**15 = P- +exa- = 10.0**18 = E- +zetta- = 10.0**21 = Z- +yotta- = 10.0**24 = Y- + +# binary_prefixes +kibi- = 2**10 +mebi- = 2**20 +gibi- = 2**30 +tebi- = 2**40 +pebi- = 2**50 +exbi- = 2**60 +zebi- = 2**70 +yobi- = 2**80 + +# reference +meter = [length] = m = metre +second = [time] = s = sec +ampere = [current] = A = amp +cd = [luminosity] = candela = candle +gram = [mass] +mol = [substance] = mole +degK = [temperature] = K = kelvin +radian = [] = rad +bit = [] +count = [] + +# acceleration +standard_gravity = 9.806650 * meter / second ** 2 = g_0 = g_n = gravity + +# Angle +turn = 2 * pi * radian = revolution = cycle = circle +degree = pi / 180 * radian = deg = arcdeg = arcdegree = angular_degree +arcminute = arcdeg / 60 = arcmin = arc_minute = angular_minute +arcsecond = arcmin / 60 = arcsec = arc_second = angular_second +steradian = radian ** 2 = sr +are = 100 * m**2 +barn = 1e-28 * m ** 2 = b +cmil = 5.067075e-10 * m ** 2 = circular_mils +darcy = 9.869233e-13 * m ** 2 +acre = 4046.8564224 * m ** 2 = international_acre +US_survey_acre = 160 * rod ** 2 + +# EM +esu = 1 * erg**0.5 * centimeter**0.5 = statcoulombs = statC = franklin = Fr +esu_per_second = 1 * esu / second = statampere +ampere_turn = 1 * A +gilbert = 10 / (4 * pi ) * ampere_turn = G +coulomb = ampere * second = C +volt = joule / coulomb = V +farad = coulomb / volt = F +ohm = volt / ampere +siemens = ampere / volt = S = mho +weber = volt * second = Wb +tesla = weber / meter ** 2 = T +henry = weber / ampere = H +elementary_charge = 1.602176487e-19 * coulomb = e +chemical_faraday = 9.64957e4 * coulomb +physical_faraday = 9.65219e4 * coulomb +faraday = 96485.3399 * coulomb = C12_faraday +gamma = 1e-9 * tesla +gauss = 1e-4 * tesla +maxwell = 1e-8 * weber = mx +oersted = 1000 / (4 * pi) * A / m = Oe +statfarad = 1.112650e-12 * farad = statF = stF +stathenry = 8.987554e11 * henry = statH = stH +statmho = 1.112650e-12 * siemens = statS = stS +statohm = 8.987554e11 * ohm +statvolt = 2.997925e2 * volt = statV = stV +unit_pole = 1.256637e-7 * weber +vacuum_permeability = 4 * pi * 1e-7 * newton / ampere ** 2 = mu_0 = magnetic_constant +vacuum_permittivity = 1 / (mu_0 * c **2 ) = epsilon_0 = electric_constant +Z_0 = mu_0 * c = impedance_of_free_space = characteristic_impedance_of_vacuum + +# Energy +joule = newton * meter = J +erg = dyne * centimeter +btu = 1.05505585262e3 * joule = Btu = BTU = british_thermal_unit +eV = 1.60217653e-19 * J = electron_volt +thm = 100000 * BTU = therm = EC_therm +cal = 4.184 * joule = calorie = thermochemical_calorie +international_steam_table_calorie = 4.1868 * joule +ton_TNT = 4.184e9 * joule = tTNT +US_therm = 1.054804e8 * joule +watt_hour = watt / hour = Wh = watthour +E_h = 4.35974394e-18 * joule = hartree = hartree_energy + +# Force +newton = kilogram * meter / second ** 2 = N +dyne = gram * centimeter / second ** 2 = dyn +force_kilogram = g_0 * kilogram = kgf = kilogram_force = pond +force_gram = g_0 * gram = gf = gram_force +force_ounce = g_0 * ounce = ozf = ounce_force +force_pound = g_0 * lb = lbf = pound_force +force_ton = 2000 * force_pound = ton_force +poundal = lb * feet / second ** 2 = pdl +kip = 1000*lbf + +# Frequency +hertz = 1 / second = Hz = rps +revolutions_per_minute = revolution / minute = rpm +counts_per_second = count / second = cps + +# Heat +#RSI = degK * meter ** 2 / watt +#clo = 0.155 * RSI = clos +#R_value = foot ** 2 * degF * hour / btu + +# Information +byte = 8 * bit = Bo = octet +baud = bit / second = Bd = bps + +# Length +angstrom = 1e-10 * meter +inch = 2.54 * centimeter = international_inch = inches = international_inches +foot = 12 * inch = international_foot = ft = feet = international_foot = international_feet +mile = 5280 * foot = mi = international_mile +yard = 3 * feet = yd = international_yard +mil = inch / 1000 = thou +parsec = 3.08568025e16 * meter = pc +light_year = speed_of_light * julian_year = ly +astronomical_unit = 149597870691 * meter = au +nautical_mile = 1.852e3 * meter = nmi +printers_point = 127 * millimeter / 360 = point +printers_pica = 12 * printers_point = pica +US_survey_foot = 1200 * meter / 3937 +US_survey_yard = 3 * US_survey_foot +US_survey_mile = 5280 * US_survey_foot = US_statute_mile +rod = 16.5 * US_survey_foot = pole = perch +furlong = 660 * US_survey_foot +fathom = 6 * US_survey_foot +chain = 66 * US_survey_foot +barleycorn = inch / 3 +arpentlin = 191.835 * feet +kayser = 1 / centimeter = wavenumber + +# Mass +dram = oz / 16 = dr = avoirdupois_dram +ounce = 28.349523125 * gram = oz = avoirdupois_ounce +pound = 0.45359237 * kilogram = lb = avoirdupois_pound +stone = 14 * lb = st +carat = 200 * milligram +grain = 64.79891 * milligram = gr +long_hundredweight = 112 * lb +short_hundredweight = 100 * lb +metric_ton = 1000 * kilogram = t = tonne +pennyweight = 24 * gram = dwt +slug = 14.59390 * kilogram +troy_ounce = 480 * gram = toz = apounce = apothecary_ounce +troy_pound = 12 * toz = tlb = appound = apothecary_pound +drachm = 60 * gram = apdram = apothecary_dram +atomic_mass_unit = 1.660538782e-27 * kilogram = u = amu = dalton = Da +scruple = 20 * gram +bag = 94 * lb +ton = 2000 * lb = short_ton + +# Textile +denier = gram / (9000 * meter) +tex = gram/ (1000 * meter) +dtex = decitex + +# Power +watt = joule / second = W = volt_ampere = VA +horsepower = 33000 * ft * lbf / min = hp = UK_horsepower = British_horsepower +boiler_horsepower = 33475 * btu / hour +metric_horsepower = 75 * force_kilogram * meter / second +electric_horsepower = 746 * watt +hydraulic_horsepower = 550 * feet * lbf / second +refrigeration_ton = 12000 * btu / hour = ton_of_refrigeration + +# Pressure +Hg = gravity * 13.59510 * gram / centimeter ** 3 = mercury = conventional_mercury +mercury_60F = gravity * 13.5568 * gram / centimeter ** 3 +H2O = gravity * 1000 * kilogram / meter ** 3 = h2o = water = conventional_water +water_4C = gravity * 999.972 * kilogram / meter ** 3 = water_39F +water_60F = gravity * 999.001 * kilogram / m ** 3 +pascal = newton / meter ** 2 = Pa +bar = 100000 * pascal +atmosphere = 101325 * pascal = atm = standard_atmosphere +technical_atmosphere = kilogram * gravity / centimeter ** 2 = at +torr = atm / 760 +psi = pound * gravity / inch ** 2 = pound_force_per_square_inch +ksi = kip / inch ** 2 = kip_per_square_inch +barye = 0.1 * newton / meter ** 2 = = barie = barad = barad = barrie = baryd = Ba +mmHg = millimeter * Hg = mm_Hg = millimeter_Hg = millimeter_Hg_0C +cmHg = centimeter * Hg = cm_Hg = centimeter_Hg +inHg = inch * Hg = in_Hg = inch_Hg = inch_Hg_32F +inch_Hg_60F = inch * mercury_60F +inch_H2O_39F = inch * water_39F +inch_H2O_60F = inch * water_60F +footH2O = ft * water +cmH2O = centimeter * water +foot_H2O = ft * water = ftH2O + +# Radiation +Bq = Hz = becquerel +curie = 3.7e10 * Bq = Ci +rutherford = 1e6*Bq = rd = Rd +Gy = joule / kilogram = gray = Sv = sievert +rem = 1e-2 * sievert +rads = 1e-2 * gray +roentgen = 2.58e-4 * coulomb / kilogram = R + + +# Temperature +degR = 9 / 5 * degK = rankine +degC = degK = celsius; offset = 273.15 +degF = 9 / 5 * degK = fahrenheit; offset = 255.372222 + +# Time +minute = 60 * second = min +hour = 60 * minute = h = hr +day = 24 * hour +week = 7 * day +fortnight = 2 * week +year = 31556925.9747 * second +month = year/12 +shake = 1e-8 * second +sidereal_day = day / 1.00273790935079524 +sidereal_hour = sidereal_day/24 +sidereal_minute=sidereal_hour/60 +sidereal_second =sidereal_minute/60 +sidereal_year = 366.25636042 * sidereal_day +sidereal_month = 27.321661 * sidereal_day +tropical_month = 27.321661 * day +synodic_month = 29.530589 * day = lunar_month +tropical_month = 27.321661 * day +common_year = 365 * day +leap_year = 366 * day +julian_year = 365.25 * day +gregorian_year = 365.2425 * day +millenium = 1000 * year = millenia = milenia = milenium +eon = 1e9 * year +work_year = 2056 * hour +work_month = work_year/12 + +# Velocity +speed_of_light = 299792458 * meter / second = c +knot = nautical_mile / hour = kt = knot_international = international_knot = nautical_miles_per_hour + +# Viscosity +poise = 1e-1 * Pa * second = P +stokes = 1e-4 * meter ** 2 / second = St +rhe = 10 / (Pa * s) + +# Volume +liter = 1e-3 * m ** 3 = l = L = litre +cc = centimeter ** 3 = cubic_centimeter +stere = meter ** 3 +gross_register_ton = 100 * foot ** 3 = register_ton = GRT +acre_foot = acre * foot = acre_feet +board_foot = foot ** 2 * inch = FBM +bushel = 2150.42 * inch ** 3 = bu = US_bushel +dry_gallon = bushel / 8 = US_dry_gallon +dry_quart = dry_gallon / 4 = US_dry_quart +dry_pint = dry_quart / 2 = US_dry_pint +gallon = 231 * inch ** 3 = liquid_gallon = US_liquid_gallon +quart = gallon / 4 = liquid_quart = US_liquid_quart +pint = quart / 2 = pt = liquid_pint = US_liquid_pint +cup = pint / 2 = liquid_cup = US_liquid_cup +gill = cup / 2 = liquid_gill = US_liquid_gill +floz = gill / 4 = fluid_ounce = US_fluid_ounce = US_liquid_ounce +imperial_bushel = 36.36872 * liter = UK_bushel +imperial_gallon = imperial_bushel / 4 = UK_gallon +imperial_quart = imperial_gallon / 4 = UK_quart +imperial_pint = imperial_quart / 2 = UK_pint +imperial_cup = imperial_pint / 2 = UK_cup +imperial_gill = imperial_cup / 2 = UK_gill +imperial_floz = imperial_gill / 5 = UK_fluid_ounce = imperial_fluid_ounce +barrel = 42 * gallon = bbl = US_liquid_gallon +tablespoon = floz / 2 = tbsp = Tbsp = Tblsp = tblsp = tbs = Tbl +teaspoon = tablespoon / 3 = tsp +peck = bushel / 4 = pk +fluid_dram = floz / 8 = fldr = fluidram +firkin = barrel / 4 diff --git a/pint/pint.py b/pint/pint.py new file mode 100644 index 0000000..4c31343 --- /dev/null +++ b/pint/pint.py @@ -0,0 +1,829 @@ +# -*- coding: utf-8 -*- +""" + pint + ~~~~ + + Pint is Python module/package to define, operate and manipulate + **physical quantities**: the product of a numerical value and a + unit of measurement. It allows arithmetic operations between them + and conversions from and to different units. + + :copyright: (c) 2012 by Hernan E. Grecco. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import division, unicode_literals, print_function, absolute_import + +__version__ = '0.1' + +import os +import sys +import copy +import math +import logging +import operator +import functools + +from io import BytesIO +from numbers import Number +import tokenize +from tokenize import untokenize, NUMBER, STRING, NAME, OP + +logger = logging.getLogger(__name__) +logger.addHandler(logging.NullHandler()) + +if sys.version < '3': + import codecs + from io import open + from StringIO import StringIO + string_types = basestring + _tokenize = lambda input: tokenize.generate_tokens(StringIO(input).readline) +else: + string_types = str + _tokenize = lambda input: tokenize.tokenize(BytesIO(input.encode('utf-8')).readline) + +PRETTY = '⁰¹²³⁴⁵⁶⁷⁸⁹·⁻' + +def _definitions_from_file(filename): + with open(filename, encoding='utf-8') as fp: + for line in fp: + line = line.strip() + if not line or line.startswith('#'): + continue + try: + if ';' in line: + [definition, modifiers] = line.split(';', 2) + modifiers = (modifier.split('=') for modifier in modifiers.split(';')) + modifiers = dict((key.strip(), float(value.strip())) for key, value in modifiers) + else: + definition = line + modifiers = {} + result = [res.strip() for res in definition.split('=')] + name, value, aliases = result[0], result[1], result[2:] + except Exception as ex: + logger.error("Exception: Cannot parse '{}' {}".format(line, ex)) + continue + yield name, value, aliases, modifiers + + +def solve_dependencies(dependencies): + """Solve a dependency graph. + + :param dependencies: dependency dictionary. For each key, the value is + an iterable indicating its dependencies. + :return: list of sets, each containing keys of independents tasks dependent + + """ + d = dict((key, set(dependencies[key])) for key in dependencies) + r = [] + while d: + # values not in keys (items without dep) + t = set(i for v in d.values() for i in v) - set(d.keys()) + # and keys without value (items without dep) + t.update(k for k, v in d.items() if not v) + # can be done right away + r.append(t) + # and cleaned up + d = dict(((k, v - t) for k, v in d.items() if v)) + return r + + +class UndefinedUnitError(ValueError): + """Raised when the units are not defined in the unit registry. + """ + + def __init__(self, unit_names): + super(ValueError, self).__init__() + self.unit_names = unit_names + + def __str__(self): + if isinstance(self.unit_names, string_types): + return "'{}' is not defined in the unit registry.".format(self.unit_names) + else: + return '{} are not defined in the unit registry.'.format(self.unit_names) + + +class DimensionalityError(ValueError): + """Raised when trying to convert between incompatible units. + """ + + def __init__(self, units1, units2, dim1=None, dim2=None): + super(DimensionalityError, self).__init__() + self.units1 = units1 + self.units2 = units2 + self.dim1 = dim1 + self.dim2 = dim2 + + def __str__(self): + if self.dim1 or self.dim2: + dim1 = ' ({})'.format(self.dim1) + dim2 = ' ({})'.format(self.dim2) + else: + dim1 = '' + dim2 = '' + return "Cannot convert from '{}'{} to '{}'{}.".format(self.units1, dim1, self.units2, dim2) + + +class AliasDict(dict): + + def add_alias(self, key, value): + if value not in self: + raise IndexError("The aliased value '{}' is not present in the dictionary".format(value)) + self[key] = self.get_aliased(value) + + def get_aliased(self, key): + value = self[key] + if isinstance(value, string_types): + return self.get_aliased(value) + return key + + +class UnitsContainer(dict): + """The UnitsContainer stores the product of units and their respective + exponent and implements the corresponding operations + """ + + def __init__(self, *args, **kwargs): + dict.__init__(self, *args, **kwargs) + for key, value in self.items(): + if not isinstance(key, string_types): + raise TypeError('key must be a str, not {}'.format(type(key))) + if not isinstance(value, Number): + raise TypeError('value must be a Number, not {}'.format(type(value))) + if not isinstance(value, float): + self[key] = float(value) + + def __missing__(self, key): + return 0.0 + + def __setitem__(self, key, value): + if not isinstance(key, string_types): + raise TypeError('key must be a str, not {}'.format(type(key))) + if not isinstance(value, Number): + raise TypeError('value must be a Number, not {}'.format(type(value))) + dict.__setitem__(self, key, float(value)) + + def _formatter(self, product_sign=' * ', superscript_format=' ** {:n}', + as_ratio=True, single_denominator=False): + if not self: + return 'dimensionless' + + if as_ratio: + fun = abs + else: + fun = lambda x: x + + tmp_plus = [] + tmp_minus = [] + for key, value in sorted(self.items()): + if value == 1: + tmp_plus.append(key) + elif value > 1: + tmp_plus.append(key + superscript_format.format(value)) + elif value == -1: + tmp_minus.append(key) + else: + tmp_minus.append(key + superscript_format.format(fun(value))) + + if tmp_plus: + ret = product_sign.join(tmp_plus) + elif as_ratio: + ret = '1' + else: + ret = '' + + if tmp_minus: + if as_ratio: + ret += ' / ' + if single_denominator: + ret += ' / '.join(tmp_minus) + else: + ret += product_sign.join(tmp_minus) + else: + ret += product_sign.join(tmp_minus) + + return ret + + def __str__(self): + return self._formatter() + + def __repr__(self): + tmp = '{%s}' % ', '.join(["'{}': {}".format(key, value) for key, value in sorted(self.items())]) + return '<UnitsContainer({})>'.format(tmp) + + def __format__(self, spec): + if spec == '!s' or spec == '': + return str(self) + elif spec == '!r': + return repr(self) + elif spec == '!l': + tmp = self._formatter(r' \cdot ', '^[{:n}]', True, True).replace('[', '{').replace(']', '}') + if '/' in tmp: + return r'\frac{%s}' % tmp.replace(' / ', '}{') + elif spec == '!p': + pretty = '{}'.format(self).replace(' ** ', '').replace(' * ', PRETTY[10]).replace('-', PRETTY[11]).replace(' / ', '/') + for n in range(10): + pretty = pretty.replace(str(n), PRETTY[n]) + return pretty + else: + raise ValueError('{} is not a valid format for UnitsContainer'.format(spec)) + + def __copy__(self): + ret = self.__class__() + ret.update(self) + return ret + + def __imul__(self, other): + if not isinstance(other, self.__class__): + raise TypeError('Cannot multiply UnitsContainer by {}'.format(type(other))) + for key, value in other.items(): + self[key] += value + keys = [key for key, value in self.items() if value == 0] + for key in keys: + del self[key] + + return self + + def __mul__(self, other): + if not isinstance(other, self.__class__): + raise TypeError('Cannot multiply UnitsContainer by {}'.format(type(other))) + ret = copy.copy(self) + ret *= other + return ret + + __rmul__ = __mul__ + + def __ipow__(self, other): + if not isinstance(other, Number): + raise TypeError('Cannot power UnitsContainer by {}'.format(type(other))) + for key, value in self.items(): + self[key] *= other + return self + + def __pow__(self, other): + if not isinstance(other, Number): + raise TypeError('Cannot power UnitsContainer by {}'.format(type(other))) + ret = copy.copy(self) + ret **= other + return ret + + def __itruediv__(self, other): + if not isinstance(other, self.__class__): + raise TypeError('Cannot divide UnitsContainer by {}'.format(type(other))) + + for key, value in other.items(): + self[key] -= value + + keys = [key for key, value in self.items() if value == 0] + for key in keys: + del self[key] + + return self + + def __truediv__(self, other): + if not isinstance(other, self.__class__): + raise TypeError('Cannot divide UnitsContainer by {}'.format(type(other))) + + ret = copy.copy(self) + ret /= other + return ret + + def __rtruediv__(self, other): + if not isinstance(other, self.__class__) and other != 1: + raise TypeError('Cannot divide {} by UnitsContainer'.format(type(other))) + + ret = copy.copy(self) + ret **= -1 + return ret + + +def converter_to_reference(scale, offset, log_base): + def _inner(value): + if log_base: + return log_base ** (value / scale + offset) + else: + return value * scale + offset + return _inner + +def converter_from_reference(scale, offset, log_base): + def _inner(value): + if log_base: + return (math.log10(value) / math.log10(log_base) - offset) / scale + else: + return (value - offset) / scale + return _inner + + +class UnitRegistry(object): + """The unit registry stores the definitions and relationships between + units. + + :param filename: path of the units definition file to load. + Empty to load the default definition file. + None to leave the UnitRegistry empty. + """ + + #: Map unit name (string) to unit value (Quantity), and unit alias to canonical unit name + _UNITS = AliasDict() + + #: Map prefix name (string) to prefix value (float), and unit alias to canonical prefix name + _PREFIXES = AliasDict({'': 1}) + + #: Map suffix name (string) to canonical , and unit alias to canonical unit name + _SUFFIXES = AliasDict({'': None, 's': ''}) + + def __init__(self, filename=''): + self.Quantity._REGISTRY = self + self._definition_files = [] + if filename == '': + self.add_from_file(os.path.join(os.path.dirname(__file__), 'default_en.txt')) + elif filename is not None: + self.add_from_file(filename) + + def __getattr__(self, item): + return self.Quantity(1, self._to_canonical(item)) + + def __getitem__(self, item): + return self.Quantity(1, self._parse_expression(item)) + + def add_unit(self, name, value, aliases=tuple(), **modifiers): + """Add unit to the registry. + """ + if not isinstance(value, self.Quantity): + value = self.Quantity(value, **modifiers) + + self._UNITS[name] = value + + for alias in aliases: + if ' ' in alias: + logger.warn('Alias cannot contain a space ' + alias) + self._UNITS.add_alias(alias.strip(), name) + + def add_prefix(self, name, value, aliases=tuple()): + """Add prefix to the registry. + """ + + if not isinstance(value, Number): + value = eval(value, {'__builtins__': None}, {}) + self._PREFIXES[name] = float(value) + + for alias in aliases: + self._PREFIXES.add_alias(alias.strip(), name) + + def add_from_file(self, filename): + """Add units and prefixes defined in a definition text file. + """ + self._definition_files.append(filename) + pending = dict() + dependencies = dict() + conv = dict() + for name, value, aliases, modifiers in _definitions_from_file(filename): + try: + if name.endswith('-'): + # Prefix + self.add_prefix(name[:-1], value, [alias[:-1] for alias in aliases]) + continue + if '[' in value: + # Reference units, indicates dimensionality + value = value.strip('[]') + if value: + value = self.Quantity(None, UnitsContainer({value: 1})) + else: + value = self.Quantity(None, None) + + conv[name] = name + conv.update({alias: name for alias in aliases}) + self.add_unit(name, value, aliases, **modifiers) + if modifiers: + self.add_unit('delta_' + name, value, tuple('delta_' + item for item in aliases)) + except UndefinedUnitError as ex: + pending[name] = (value, aliases) + dependencies[name] = ex.unit_names + except Exception as ex: + logger.error("Exception: Cannot add '{}' {}".format(line, ex)) + + dep2 = {} + for unit_name, deps in dependencies.items(): + dep2[unit_name] = set(conv[dep_name] for dep_name in deps) + + for unit_names in solve_dependencies(dep2): + for unit_name in unit_names: + if not unit_name in self._UNITS: + self.add_unit(unit_name, *pending[unit_name]) + + def _to_canonical(self, candidate): + try: + return self._UNITS.get_aliased(candidate) + except KeyError: + pass + + candidates = tuple(self._parse_candidate(candidate)) + if not candidates: + raise UndefinedUnitError(candidate) + elif len(candidates) == 1: + prefix, unit_name, _ = candidates[0] + else: + logger.warning('Parsing {} yield multiple results. Options are: {}'.format(candidate, candidates)) + prefix, unit_name, _ = candidates[0] + + if prefix: + self._UNITS[prefix + unit_name] = self.Quantity(self._PREFIXES[prefix], unit_name) + return prefix + unit_name + + return unit_name + + def _parse_candidate(self, candidate): + + for unit_name in self._UNITS.keys(): + if unit_name in candidate: + try: + [prefix, suffix] = candidate.split(unit_name) + if len(unit_name) == 1 and len(suffix) == 1: + continue + except ValueError: # too many values to unpack + continue + if prefix in self._PREFIXES and suffix in self._SUFFIXES: + yield (self._PREFIXES.get_aliased(prefix), + self._UNITS.get_aliased(unit_name), + self._SUFFIXES.get_aliased(suffix)) + + def _parse_expression(self, input): + gen = _tokenize(input) + result = [] + unknown = set() + for toknum, tokval, _, _, _ in gen: + if toknum in (STRING, NAME): # replace NUMBER tokens + # TODO: Integrate math better + if tokval == 'pi': + result.append((toknum, str(math.pi))) + continue + try: + tokval = self._to_canonical(tokval) + except UndefinedUnitError as ex: + unknown.add(ex.unit_names) + + result.extend([ + (NAME, 'Quantity'), + (OP, '('), + (NUMBER, '1'), + (OP, ','), + (NAME, 'U_'), + (OP, '('), + (STRING, tokval), + (OP, '='), + (NUMBER, '1'), + (OP, ')'), + (OP, ')') + ]) + else: + result.append((toknum, tokval)) + + if unknown: + raise UndefinedUnitError(unknown) + + return eval(untokenize(result), {'__builtins__': None}, + {'REGISTRY': self._UNITS, + 'Quantity': self.Quantity, + 'U_': UnitsContainer}) + + + @functools.total_ordering + class Quantity(object): + """Quantity object constituted by magnitude and units. + + :param value: value of the physical quantity to be created. + :type value: str, Quantity or any numeric type. + :param units: units of the physical quantity to be created. + :type units: UnitsContainer, str or Quantity. + + """ + + #: Unit registry containing this class. + _REGISTRY = None + + def __new__(cls, value, units=None, offset=0, log_base=0): + if units is None: + if isinstance(value, string_types): + inst = cls._REGISTRY._parse_expression(value) + elif isinstance(value, cls): + inst = copy.copy(value) + else: + inst = object.__new__(cls) + inst._magnitude = value + inst._units = UnitsContainer() + elif isinstance(units, UnitsContainer): + inst = object.__new__(cls) + inst._magnitude = value + inst._units = units + elif isinstance(units, string_types): + inst = cls._REGISTRY._parse_expression(units) + inst._magnitude = value + elif isinstance(units, cls): + inst = copy.copy(units) + inst._magnitude = value + else: + raise TypeError('units must be of type str, Quantity or UnitsContainer; not {}.'.format(type(units))) + + # This only works if expressed as reference + inst.multiplicative = offset or log_base + scale = 1 / inst.magnitude if inst.magnitude else 1 + inst._convert_to_reference = converter_to_reference(scale, offset, log_base) + inst._convert_from_reference = converter_from_reference(scale, offset, log_base) + + return inst + + def __copy__(self): + return self.__class__(self._magnitude, self._units) + + def __str__(self): + return '{} {}'.format(self._magnitude, self._units) + + def __repr__(self): + return "<Quantity({}, '{}')>".format(self._magnitude, self._units) + + def __format__(self, spec): + if not spec: + return str(self) + if '!' in spec: + fmt, conv = spec.split('!') + conv = '!' + conv + else: + fmt, conv = spec, '' + return format(self.magnitude, fmt) + ' ' + format(self.units, conv) + + @property + def magnitude(self): + """Quantity's magnitude. + """ + return self._magnitude + + @property + def units(self): + """Quantity's units. + + :rtype: UnitContainer + """ + return self._units + + @property + def unitless(self): + """Return true if the quantity does not have units. + """ + return not bool(self.convert_to_reference().units) + + @property + def dimensionless(self): + """Return true if the quantity is dimensionless. + """ + tmp = self.convert_to_reference() + + return not bool(self.convert_to_reference().dimensionality) + + @property + def dimensionality(self): + """Quantity's dimensionality (e.g. {length: 1, time: -1}) + """ + try: + return self._dimensionality + except AttributeError: + if self._magnitude is None: + return UnitsContainer(self.units) + + tmp = UnitsContainer() + for key, value in self.units.items(): + reg = self._REGISTRY._UNITS[key] + tmp = tmp * reg.dimensionality ** value + + self._dimensionality = tmp + + return self._dimensionality + + def ito(self, other=None): + """Inplace rescale to different units. + + :param other: destination units. + :type other: Quantity or str. + """ + if isinstance(other, string_types): + other = self._REGISTRY._parse_expression(other) + + if self._units == other._units: + return self.__class__(self._magnitude, other) + + if len(self.units) == 1: + unit, power = tuple(self.units.items())[0] + ounit, opower = tuple(other.units.items())[0] + if self.dimensionality != other.dimensionality: + raise DimensionalityError(self.units, other.units, + self.dimensionality, other.dimensionality) + + unit = self._REGISTRY._UNITS[unit] + ounit = self._REGISTRY._UNITS[ounit] + if unit.multiplicative or ounit.multiplicative: + value = unit._convert_to_reference(self.magnitude) + value = ounit._convert_from_reference(value) + + self._magnitude = value + self._units = copy.copy(other._units) + return self + + factor = self.__class__(1, self.units / other.units) + factor = factor.convert_to_reference() + if not factor.unitless: + raise DimensionalityError(self.units, other.units, + self.dimensionality, other.dimensionality) + + self._magnitude *= factor.magnitude + self._units = copy.copy(other._units) + return self + + def to(self, other=None): + """Return Quantity rescaled to different units. + + :param other: destination units. + :type other: Quantity or str. + """ + ret = copy.copy(self) + ret.ito(other) + return ret + + def convert_to_reference(self): + """Return Quantity rescaled to reference units. + + """ + + tmp = self._REGISTRY.Quantity(self.magnitude) + for key, value in self.units.items(): + reg = self._REGISTRY._UNITS[key] + if reg._magnitude is None: + factor = self.__class__(1, key) ** value + else: + factor = reg.convert_to_reference() ** value + + tmp *= factor + + return tmp + + # Mathematical operations + def __float__(self): + if self.dimensionless: + return float(self._magnitude) + raise DimensionalityError(self.units, 'dimensionless') + + def __complex__(self): + if self.dimensionless: + return complex(self._magnitude) + raise DimensionalityError(self.units, 'dimensionless') + + def iadd_sub(self, other, fun): + if isinstance(other, self.__class__): + if not self.dimensionality == other.dimensionality: + raise DimensionalityError(self.units, other.units, + self.dimensionality, other.dimensionality) + if self._units == other._units: + self._magnitude = fun(self._magnitude, other._magnitude) + else: + self._magnitude = fun(self._magnitude, other.to(self)._magnitude) + else: + if self.unitless: + self._magnitude = fun(self._magnitude, other) + else: + raise DimensionalityError(self.units, 'dimensionless') + + return self + + def add_sub(self, other, fun): + ret = copy.copy(self) + fun(ret, other) + return ret + + def __iadd__(self, other): + return self.iadd_sub(other, operator.iadd) + + def __add__(self, other): + return self.add_sub(other, operator.iadd) + + __radd__ = __add__ + + def __isub__(self, other): + return self.iadd_sub(other, operator.isub) + + def __sub__(self, other): + return self.add_sub(other, operator.isub) + + __rsub__ = __sub__ + + def __imul__(self, other): + if isinstance(other, self.__class__): + self._magnitude *= other._magnitude + self._units *= other._units + else: + self._magnitude *= other + + return self + + def __mul__(self, other): + ret = copy.copy(self) + ret *= other + return ret + + __rmul__ = __mul__ + + def __itruediv__(self, other): + if isinstance(other, self.__class__): + self._magnitude /= other._magnitude + self._units /= other._units + else: + self._magnitude /= other + + return self + + def __truediv__(self, other): + ret = copy.copy(self) + ret /= other + return ret + + def __rtruediv__(self, other): + if isinstance(other, Number): + return self.__class__(other / self._magnitude, 1 / self._units) + raise NotImplementedError + + def __ifloordiv__(self, other): + if isinstance(other, self.__class__): + self._magnitude //= other._magnitude + self._units /= other._units + else: + self._magnitude //= other + + return self + + def __floordiv__(self, other): + ret = copy.copy(self) + ret //= other + return ret + + __div__ = __floordiv__ + __idiv__ = __ifloordiv__ + + def __rfloordiv__(self, other): + if isinstance(other, self.__class__): + return self.__class__(other._magnitude // self._magnitude, other._units / self._units) + else: + return self.__class__(other // self._magnitude, 1.0 / self._units) + + def __ipow__(self, other): + self._magnitude **= other + self._units **= other + return self + + def __pow__(self, other): + ret = copy.copy(self) + ret **= other + return ret + + def __abs__(self): + return self.__class__(abs(self._magnitude), self._units) + + def __round__(self, ndigits=0): + return self.__class__(round(self._magnitude, ndigits=ndigits), self._units) + + def __pos__(self): + return self.__class__(operator.pos(self._magnitude), self._units) + + def __neg__(self): + return self.__class__(operator.neg(self._magnitude), self._units) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return self.dimensionless and self.magnitude == other + + if self._magnitude == 0 and other._magnitude == 0: + return self.dimensionality == other.dimensionality + + if self._units == other._units: + return self._magnitude == other._magnitude + + try: + return self.to(other).magnitude == other._magnitude + except DimensionalityError: + return False + + def __lt__(self, other): + if not isinstance(other, self.__class__): + if self.dimensionless: + return operator.lt(self.magnitude, other) + else: + raise ValueError('Cannot compare Quantity and {}'.format(type(other))) + + if self.units == other.units: + return operator.lt(self._magnitude, other._magnitude) + if self.dimensionality != other.dimensionality: + raise DimensionalityError(self.units, other.units, + self.dimensionality, other.dimensionality) + return operator.lt(self.convert_to_reference().magnitude, + self.convert_to_reference().magnitude) + + def __bool__(self): + return bool(self._magnitude) + + __nonzero__ = __bool__ |