summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHernan Grecco <hernan.grecco@gmail.com>2012-05-03 02:20:28 +0200
committerHernan Grecco <hernan.grecco@gmail.com>2012-05-03 02:20:28 +0200
commitd477026ed8a33aff9cad5c183e30fa721688b559 (patch)
tree4f8d1e5b93a995330a7bc31a5104c59805bab1d3
parent08053b48b2323d23fe88951e1757fb2f9104af6c (diff)
downloadpint-d477026ed8a33aff9cad5c183e30fa721688b559.tar.gz
Add code
-rw-r--r--pint/__init__.py17
-rw-r--r--pint/default_en.txt294
-rw-r--r--pint/pint.py829
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__