summaryrefslogtreecommitdiff
path: root/lib/taurus/external/pint/pint/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/taurus/external/pint/pint/util.py')
-rw-r--r--lib/taurus/external/pint/pint/util.py468
1 files changed, 468 insertions, 0 deletions
diff --git a/lib/taurus/external/pint/pint/util.py b/lib/taurus/external/pint/pint/util.py
new file mode 100644
index 00000000..9a81ae14
--- /dev/null
+++ b/lib/taurus/external/pint/pint/util.py
@@ -0,0 +1,468 @@
+# -*- coding: utf-8 -*-
+"""
+ pint.util
+ ~~~~~~~~~
+
+ Miscellaneous functions for pint.
+
+ :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import re
+import sys
+import tokenize
+import operator
+from numbers import Number
+from fractions import Fraction
+
+import logging
+from token import STRING, NAME, OP
+from tokenize import untokenize
+
+from .compat import NullHandler
+
+logger = logging.getLogger(__name__)
+logger.addHandler(NullHandler())
+
+if sys.version < '3':
+ from StringIO import StringIO
+ string_types = basestring
+ ptok = lambda input_string: tokenize.generate_tokens(StringIO(input_string).readline)
+else:
+ from io import BytesIO
+ string_types = str
+ ptok = lambda input_string: tokenize.tokenize(BytesIO(input_string.encode('utf-8')).readline)
+
+
+def matrix_to_string(matrix, row_headers=None, col_headers=None, fmtfun=lambda x: str(int(x))):
+ """Takes a 2D matrix (as nested list) and returns a string.
+ """
+ ret = []
+ if col_headers:
+ ret.append('\t' if row_headers else '' + '\t'.join(col_headers))
+ if row_headers:
+ ret += [rh + '\t' + '\t'.join(fmtfun(f) for f in row)
+ for rh, row in zip(row_headers, matrix)]
+ else:
+ ret += ['\t'.join(fmtfun(f) for f in row)
+ for row in matrix]
+
+ return '\n'.join(ret)
+
+
+def transpose(matrix):
+ """Takes a 2D matrix (as nested list) and returns the transposed version.
+ """
+ return [list(val) for val in zip(*matrix)]
+
+
+def column_echelon_form(matrix, ntype=Fraction, transpose_result=False):
+ """Calculates the column echelon form using Gaussian elimination.
+
+ :param matrix: a 2D matrix as nested list.
+ :param ntype: the numerical type to use in the calculation.
+ :param transpose_result: indicates if the returned matrix should be transposed.
+ :return: column echelon form, transformed identity matrix, swapped rows
+ """
+ lead = 0
+
+ M = transpose(matrix)
+
+ _transpose = transpose if transpose_result else lambda x: x
+
+ rows, cols = len(M), len(M[0])
+
+ new_M = []
+ for row in M:
+ r = []
+ for x in row:
+ if isinstance(x, float):
+ x = ntype.from_float(x)
+ else:
+ x = ntype(x)
+ r.append(x)
+ new_M.append(r)
+ M = new_M
+
+# M = [[ntype(x) for x in row] for row in M]
+ I = [[ntype(1) if n == nc else ntype(0) for nc in range(rows)] for n in range(rows)]
+ swapped = []
+
+ for r in range(rows):
+ if lead >= cols:
+ return _transpose(M), _transpose(I), swapped
+ i = r
+ while M[i][lead] == 0:
+ i += 1
+ if i != rows:
+ continue
+ i = r
+ lead += 1
+ if cols == lead:
+ return _transpose(M), _transpose(I), swapped
+
+ M[i], M[r] = M[r], M[i]
+ I[i], I[r] = I[r], I[i]
+
+ swapped.append(i)
+ lv = M[r][lead]
+ M[r] = [mrx / lv for mrx in M[r]]
+ I[r] = [mrx / lv for mrx in I[r]]
+
+ for i in range(rows):
+ if i == r:
+ continue
+ lv = M[i][lead]
+ M[i] = [iv - lv*rv for rv, iv in zip(M[r], M[i])]
+ I[i] = [iv - lv*rv for rv, iv in zip(I[r], I[i])]
+
+ lead += 1
+
+ return _transpose(M), _transpose(I), swapped
+
+try:
+ import numpy as np
+ from numpy import ndarray
+
+ HAS_NUMPY = True
+ NUMERIC_TYPES = (Number, ndarray)
+
+except ImportError:
+
+ class ndarray(object):
+ pass
+
+ HAS_NUMPY = False
+ NUMERIC_TYPES = (Number, )
+
+
+__JOIN_REG_EXP = re.compile("\{\d*\}")
+
+def _join(fmt, iterable):
+ if not iter:
+ return ''
+ if not __JOIN_REG_EXP.search(fmt):
+ return fmt.join(iterable)
+ miter = iter(iterable)
+ first = next(miter)
+ for val in miter:
+ ret = fmt.format(first, val)
+ first = ret
+ return first
+
+
+def formatter(items, as_ratio=True, single_denominator=False,
+ product_fmt=' * ', division_fmt=' / ', power_fmt='{0} ** {1}',
+ parentheses_fmt='({0})', exp_call=lambda x: '{0:n}'.format(x)):
+ """Format a list of (name, exponent) pairs.
+
+ :param items: a list of (name, exponent) pairs.
+ :param as_ratio: True to display as ratio, False as negative powers.
+ :param single_denominator: all with terms with negative exponents are
+ collected together.
+ :param product_fmt: the format used for multiplication.
+ :param division_fmt: the format used for division.
+ :param power_fmt: the format used for exponentiation.
+ :param parentheses_fmt: the format used for parenthesis.
+
+ :return: the formula as a string.
+ """
+ if as_ratio:
+ fun = lambda x: exp_call(abs(x))
+ else:
+ fun = exp_call
+
+ pos_terms, neg_terms = [], []
+
+ for key, value in sorted(items):
+ if value == 1:
+ pos_terms.append(key)
+ elif value > 0:
+ pos_terms.append(power_fmt.format(key, fun(value)))
+ elif value == -1:
+ neg_terms.append(key)
+ else:
+ neg_terms.append(power_fmt.format(key, fun(value)))
+
+ if pos_terms:
+ pos_ret = _join(product_fmt, pos_terms)
+ elif as_ratio and neg_terms:
+ pos_ret = '1'
+ else:
+ pos_ret = ''
+
+ if not neg_terms:
+ return pos_ret
+
+ if as_ratio:
+ if single_denominator:
+ neg_ret = _join(product_fmt, neg_terms)
+ if len(neg_terms) > 1:
+ neg_ret = parentheses_fmt.format(neg_ret)
+ else:
+ neg_ret = _join(division_fmt, neg_terms)
+ else:
+ neg_ret = product_fmt.join(neg_terms)
+
+ return _join(division_fmt, [pos_ret, neg_ret])
+
+
+def pi_theorem(quantities, registry=None):
+ """Builds dimensionless quantities using the Buckingham π theorem
+
+ :param quantities: mapping between variable name and units
+ :type quantities: dict
+ :return: a list of dimensionless quantities expressed as dicts
+ """
+
+ # Preprocess input and build the dimensionality Matrix
+ quant = []
+ dimensions = set()
+
+ if registry is None:
+ getdim = lambda x: x
+ else:
+ getdim = registry.get_dimensionality
+
+ for name, value in quantities.items():
+ if isinstance(value, string_types):
+ value = ParserHelper.from_string(value)
+ if isinstance(value, dict):
+ dims = getdim(value)
+ elif not hasattr(value, 'dimensionality'):
+ dims = getdim(value)
+ else:
+ dims = value.dimensionality
+
+ if not registry and any(not key.startswith('[') for key in dims):
+ logger.warning('A non dimension was found and a registry was not provided. '
+ 'Assuming that it is a dimension name: {0}.'.format(dims))
+
+ quant.append((name, dims))
+ dimensions = dimensions.union(dims.keys())
+
+ dimensions = list(dimensions)
+
+ # Calculate dimensionless quantities
+ M = [[dimensionality[dimension] for name, dimensionality in quant]
+ for dimension in dimensions]
+
+ M, identity, pivot = column_echelon_form(M, transpose_result=False)
+
+ # Collect results
+ # Make all numbers integers and minimize the number of negative exponents.
+ # Remove zeros
+ results = []
+ for rowm, rowi in zip(M, identity):
+ if any(el != 0 for el in rowm):
+ continue
+ max_den = max(f.denominator for f in rowi)
+ neg = -1 if sum(f < 0 for f in rowi) > sum(f > 0 for f in rowi) else 1
+ results.append(dict((q[0], neg * f.numerator * max_den / f.denominator)
+ for q, f in zip(quant, rowi) if f.numerator != 0))
+ return results
+
+
+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
+ only of the previous tasks in the list.
+ """
+ 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
+
+
+def find_shortest_path(graph, start, end, path=[]):
+ path = path + [start]
+ if start == end:
+ return path
+ if not start in graph:
+ return None
+ shortest = None
+ for node in graph[start]:
+ if node not in path:
+ newpath = find_shortest_path(graph, node, end, path)
+ if newpath:
+ if not shortest or len(newpath) < len(shortest):
+ shortest = newpath
+ return shortest
+
+
+class ParserHelper(dict):
+ """The ParserHelper stores in place the product of variables and
+ their respective exponent and implements the corresponding operations.
+ """
+
+ __slots__ = ('scale', )
+
+ def __init__(self, scale=1, *args, **kwargs):
+ self.scale = scale
+ dict.__init__(self, *args, **kwargs)
+
+ @classmethod
+ def from_word(cls, input_word):
+ """Creates a ParserHelper object with a single variable with exponent one.
+
+ Equivalent to: ParserHelper({'word': 1})
+
+ """
+ ret = cls()
+ ret.add(input_word, 1)
+ return ret
+
+ @classmethod
+ def from_string(cls, input_string):
+ """Parse linear expression mathematical units and return a quantity object.
+ """
+
+ if not input_string:
+ return cls()
+
+ input_string = string_preprocessor(input_string)
+
+ if '[' in input_string:
+ input_string = input_string.replace('[', '__obra__').replace(']', '__cbra__')
+ reps = True
+ else:
+ reps = False
+
+ gen = ptok(input_string)
+ result = []
+ for toknum, tokval, _, _, _ in gen:
+ if toknum == NAME:
+ if not tokval:
+ continue
+ result.extend([
+ (NAME, 'L_'),
+ (OP, '('),
+ (STRING, '"' + tokval + '"'),
+ (OP, ')')
+ ])
+ else:
+ result.append((toknum, tokval))
+
+ ret = eval(untokenize(result),
+ {'__builtins__': None},
+ {'L_': cls.from_word})
+ if isinstance(ret, Number):
+ return ParserHelper(ret)
+
+ if not reps:
+ return ret
+
+ return ParserHelper(ret.scale,
+ dict((key.replace('__obra__', '[').replace('__cbra__', ']'), value)
+ for key, value in ret.items()))
+ return ParserHelper(ret.scale, data)
+
+ def __missing__(self, key):
+ return 0.0
+
+ def add(self, key, value):
+ newval = self.__getitem__(key) + value
+ if newval:
+ self.__setitem__(key, newval)
+ else:
+ del self[key]
+
+ def operate(self, items, op=operator.iadd, cleanup=True):
+ for key, value in items:
+ self[key] = op(self[key], value)
+
+ if cleanup:
+ keys = [key for key, value in self.items() if value == 0]
+ for key in keys:
+ del self[key]
+
+ def __str__(self):
+ tmp = '{%s}' % ', '.join(["'{0}': {1}".format(key, value) for key, value in sorted(self.items())])
+ return '{0} {1}'.format(self.scale, tmp)
+
+ def __repr__(self):
+ tmp = '{%s}' % ', '.join(["'{}': {}".format(key, value) for key, value in sorted(self.items())])
+ return '<ParserHelper({}, {})>'.format(self.scale, tmp)
+
+ def __mul__(self, other):
+ if isinstance(other, string_types):
+ self.add(other, 1)
+ elif isinstance(other, Number):
+ self.scale *= other
+ else:
+ self.operate(other.items())
+ return self
+
+ __imul__ = __mul__
+ __rmul__ = __mul__
+
+ def __pow__(self, other):
+ self.scale **= other
+ for key in self.keys():
+ self[key] *= other
+ return self
+
+ __ipow__ = __pow__
+
+ def __truediv__(self, other):
+ if isinstance(other, string_types):
+ self.add(other, -1)
+ elif isinstance(other, Number):
+ self.scale /= other
+ else:
+ self.operate(other.items(), operator.sub)
+ return self
+
+ __itruediv__ = __truediv__
+ __floordiv__ = __truediv__
+
+ def __rtruediv__(self, other):
+ self.__pow__(-1)
+ if isinstance(other, string_types):
+ self.add(other, 1)
+ elif isinstance(other, Number):
+ self.scale *= other
+ else:
+ self.operate(other.items(), operator.add)
+ return self
+
+
+#: List of regex substitution pairs.
+_subs_re = [(r"({0}) squared", r"\1**2"), # Handle square and cube
+ (r"({0}) cubed", r"\1**3"),
+ (r"cubic ({0})", r"\1**3"),
+ (r"square ({0})", r"\1**2"),
+ (r"sq ({0})", r"\1**2"),
+ (r"(\w)\s+(?=\w)", r"\1*"), # Handle space for multiplication
+ (r"([0-9])(?={0})(?!(?:[e|E][-+]?[0-9]+))", r"\1*")
+ ]
+
+#: Compiles the regex and replace {0} by a regex that matches an identifier.
+_subs_re = [(re.compile(a.format(r"[_a-zA-Z][_a-zA-Z0-9]*")), b) for a, b in _subs_re]
+
+
+def string_preprocessor(input_string):
+
+ input_string = input_string.replace(",", "")
+ input_string = input_string.replace(" per ", "/")
+
+ for a, b in _subs_re:
+ input_string = a.sub(b, input_string)
+
+ # Handle caret exponentiation
+ input_string = input_string.replace("^", "**")
+ return input_string