diff --git a/CadQuery/Libs/pint/__init__.py b/CadQuery/Libs/pint/__init__.py new file mode 100644 index 0000000..35166dd --- /dev/null +++ b/CadQuery/Libs/pint/__init__.py @@ -0,0 +1,125 @@ +# -*- 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 with_statement +import os +import subprocess +import pkg_resources +from .formatting import formatter +from .unit import (UnitRegistry, DimensionalityError, OffsetUnitCalculusError, + UndefinedUnitError, LazyRegistry) +from .util import pi_theorem, logger + +from .context import Context + + +try: # pragma: no cover + __version__ = pkg_resources.get_distribution('pint').version +except: # pragma: no cover + # we seem to have a local copy not installed without setuptools + # so the reported version will be unknown + __version__ = "unknown" + + +#: A Registry with the default units and constants. +_DEFAULT_REGISTRY = LazyRegistry() + +#: Registry used for unpickling operations. +_APP_REGISTRY = _DEFAULT_REGISTRY + + +def _build_quantity(value, units): + """Build Quantity using the Application registry. + Used only for unpickling operations. + """ + global _APP_REGISTRY + return _APP_REGISTRY.Quantity(value, units) + + +def set_application_registry(registry): + """Set the application registry which is used for unpickling operations. + + :param registry: a UnitRegistry instance. + """ + assert isinstance(registry, UnitRegistry) + global _APP_REGISTRY + logger.debug('Changing app registry from %r to %r.', _APP_REGISTRY, registry) + _APP_REGISTRY = registry + + +def _run_pyroma(data): # pragma: no cover + """Run pyroma (used to perform checks before releasing a new version). + """ + import sys + from zest.releaser.utils import ask + if not ask("Run pyroma on the package before uploading?"): + return + try: + from pyroma import run + result = run(data['tagdir']) + if result != 10: + if not ask("Continue?"): + sys.exit(1) + except ImportError: + if not ask("pyroma not available. Continue?"): + sys.exit(1) + + +def _check_travis(data): # pragma: no cover + """Check if Travis reports that everything is ok. + (used to perform checks before releasing a new version). + """ + import json + import sys + + from zest.releaser.utils import system, ask + if not ask('Check with Travis before releasing?'): + return + + try: + # Python 3 + from urllib.request import urlopen + def get(url): + return urlopen(url).read().decode('utf-8') + + except ImportError: + # Python 2 + from urllib2 import urlopen + def get(url): + return urlopen(url).read() + + url = 'https://api.github.com/repos/%s/%s/status/%s' + + username = 'hgrecco' + repo = 'pint' + commit = system('git rev-parse HEAD') + + try: + result = json.loads(get(url % (username, repo, commit)))['state'] + print('Travis says: %s' % result) + if result != 'success': + if not ask('Do you want to continue anyway?', default=False): + sys.exit(1) + except Exception: + print('Could not determine the commit state with Travis.') + if ask('Do you want to continue anyway?', default=False): + sys.exit(1) + + +def test(): + """Run all tests. + + :return: a :class:`unittest.TestResult` object + """ + from .testsuite import run + return run() diff --git a/CadQuery/Libs/pint/compat/__init__.py b/CadQuery/Libs/pint/compat/__init__.py new file mode 100644 index 0000000..72e011b --- /dev/null +++ b/CadQuery/Libs/pint/compat/__init__.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +""" + pint.compat + ~~~~~~~~~~~ + + Compatibility layer. + + :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 sys +import tokenize + +from numbers import Number +from decimal import Decimal + + +PYTHON3 = sys.version >= '3' + +if PYTHON3: + from io import BytesIO + string_types = str + tokenizer = lambda input_string: tokenize.tokenize(BytesIO(input_string.encode('utf-8')).readline) + + def u(x): + return x + + maketrans = str.maketrans + + long_type = int +else: + from StringIO import StringIO + string_types = basestring + tokenizer = lambda input_string: tokenize.generate_tokens(StringIO(input_string).readline) + + import codecs + + def u(x): + return codecs.unicode_escape_decode(x)[0] + + maketrans = lambda f, t: dict((ord(a), b) for a, b in zip(f, t)) + + long_type = long + +if sys.version_info < (2, 7): + try: + import unittest2 as unittest + except ImportError: + raise Exception("Testing Pint in Python 2.6 requires package 'unittest2'") +else: + import unittest + + +try: + from collections import Chainmap +except ImportError: + from .chainmap import ChainMap + +try: + from collections import TransformDict +except ImportError: + from .transformdict import TransformDict + +try: + from functools import lru_cache +except ImportError: + from .lrucache import lru_cache + +try: + from logging import NullHandler +except ImportError: + from .nullhandler import NullHandler + +try: + import numpy as np + from numpy import ndarray + + HAS_NUMPY = True + NUMPY_VER = np.__version__ + NUMERIC_TYPES = (Number, Decimal, ndarray, np.number) + + def _to_magnitude(value, force_ndarray=False): + if isinstance(value, (dict, bool)) or value is None: + raise TypeError('Invalid magnitude for Quantity: {0!r}'.format(value)) + elif isinstance(value, string_types) and value == '': + raise ValueError('Quantity magnitude cannot be an empty string.') + elif isinstance(value, (list, tuple)): + return np.asarray(value) + if force_ndarray: + return np.asarray(value) + return value + +except ImportError: + + np = None + + class ndarray(object): + pass + + HAS_NUMPY = False + NUMPY_VER = '0' + NUMERIC_TYPES = (Number, Decimal) + + def _to_magnitude(value, force_ndarray=False): + if isinstance(value, (dict, bool)) or value is None: + raise TypeError('Invalid magnitude for Quantity: {0!r}'.format(value)) + elif isinstance(value, string_types) and value == '': + raise ValueError('Quantity magnitude cannot be an empty string.') + elif isinstance(value, (list, tuple)): + raise TypeError('lists and tuples are valid magnitudes for ' + 'Quantity only when NumPy is present.') + return value + +try: + from uncertainties import ufloat + HAS_UNCERTAINTIES = True +except ImportError: + ufloat = None + HAS_UNCERTAINTIES = False + diff --git a/CadQuery/Libs/pint/compat/chainmap.py b/CadQuery/Libs/pint/compat/chainmap.py new file mode 100644 index 0000000..f4c9a4e --- /dev/null +++ b/CadQuery/Libs/pint/compat/chainmap.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +""" + pint.compat.chainmap + ~~~~~~~~~~~~~~~~~~~~ + + Taken from the Python 3.3 source code. + + :copyright: 2013, PSF + :license: PSF License +""" + +from __future__ import division, unicode_literals, print_function, absolute_import + +import sys + +from collections import MutableMapping +if sys.version_info < (3, 0): + from thread import get_ident +elif sys.version_info < (3, 3): + from _thread import get_ident +else: + from threading import get_ident + + +def _recursive_repr(fillvalue='...'): + 'Decorator to make a repr function return fillvalue for a recursive call' + + def decorating_function(user_function): + repr_running = set() + + def wrapper(self): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self) + finally: + repr_running.discard(key) + return result + + # Can't use functools.wraps() here because of bootstrap issues + wrapper.__module__ = getattr(user_function, '__module__') + wrapper.__doc__ = getattr(user_function, '__doc__') + wrapper.__name__ = getattr(user_function, '__name__') + wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) + return wrapper + + return decorating_function + + +class ChainMap(MutableMapping): + ''' A ChainMap groups multiple dicts (or other mappings) together + to create a single, updateable view. + + The underlying mappings are stored in a list. That list is public and can + accessed or updated using the *maps* attribute. There is no other state. + + Lookups search the underlying mappings successively until a key is found. + In contrast, writes, updates, and deletions only operate on the first + mapping. + + ''' + + def __init__(self, *maps): + '''Initialize a ChainMap by setting *maps* to the given mappings. + If no mappings are provided, a single empty dictionary is used. + + ''' + self.maps = list(maps) or [{}] # always at least one map + + def __missing__(self, key): + raise KeyError(key) + + def __getitem__(self, key): + for mapping in self.maps: + try: + return mapping[key] # can't use 'key in mapping' with defaultdict + except KeyError: + pass + return self.__missing__(key) # support subclasses that define __missing__ + + def get(self, key, default=None): + return self[key] if key in self else default + + def __len__(self): + return len(set().union(*self.maps)) # reuses stored hash values if possible + + def __iter__(self): + return iter(set().union(*self.maps)) + + def __contains__(self, key): + return any(key in m for m in self.maps) + + def __bool__(self): + return any(self.maps) + + @_recursive_repr() + def __repr__(self): + return '{0.__class__.__name__}({1})'.format( + self, ', '.join(map(repr, self.maps))) + + @classmethod + def fromkeys(cls, iterable, *args): + 'Create a ChainMap with a single dict created from the iterable.' + return cls(dict.fromkeys(iterable, *args)) + + def copy(self): + 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' + return self.__class__(self.maps[0].copy(), *self.maps[1:]) + + __copy__ = copy + + def new_child(self, m=None): # like Django's _Context.push() + ''' + New ChainMap with a new map followed by all previous maps. If no + map is provided, an empty dict is used. + ''' + if m is None: + m = {} + return self.__class__(m, *self.maps) + + @property + def parents(self): # like Django's _Context.pop() + 'New ChainMap from maps[1:].' + return self.__class__(*self.maps[1:]) + + def __setitem__(self, key, value): + self.maps[0][key] = value + + def __delitem__(self, key): + try: + del self.maps[0][key] + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def popitem(self): + 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' + try: + return self.maps[0].popitem() + except KeyError: + raise KeyError('No keys found in the first mapping.') + + def pop(self, key, *args): + 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' + try: + return self.maps[0].pop(key, *args) + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def clear(self): + 'Clear maps[0], leaving maps[1:] intact.' + self.maps[0].clear() diff --git a/CadQuery/Libs/pint/compat/lrucache.py b/CadQuery/Libs/pint/compat/lrucache.py new file mode 100644 index 0000000..868b598 --- /dev/null +++ b/CadQuery/Libs/pint/compat/lrucache.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +""" + pint.compat.lrucache + ~~~~~~~~~~~~~~~~~~~~ + + LRU (least recently used) cache backport. + + From https://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/ + + :copyright: 2004, Raymond Hettinger, + :license: MIT License +""" + +from collections import namedtuple +from functools import update_wrapper +from threading import RLock + +_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + +class _HashedSeq(list): + __slots__ = 'hashvalue' + + def __init__(self, tup, hash=hash): + self[:] = tup + self.hashvalue = hash(tup) + + def __hash__(self): + return self.hashvalue + +def _make_key(args, kwds, typed, + kwd_mark = (object(),), + fasttypes = set((int, str, frozenset, type(None))), + sorted=sorted, tuple=tuple, type=type, len=len): + 'Make a cache key from optionally typed positional and keyword arguments' + key = args + if kwds: + sorted_items = sorted(kwds.items()) + key += kwd_mark + for item in sorted_items: + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for k, v in sorted_items) + elif len(key) == 1 and type(key[0]) in fasttypes: + return key[0] + return _HashedSeq(key) + +def lru_cache(maxsize=100, typed=False): + """Least-recently-used cache decorator. + + If *maxsize* is set to None, the LRU features are disabled and the cache + can grow without bound. + + If *typed* is True, arguments of different types will be cached separately. + For example, f(3.0) and f(3) will be treated as distinct calls with + distinct results. + + Arguments to the cached function must be hashable. + + View the cache statistics named tuple (hits, misses, maxsize, currsize) with + f.cache_info(). Clear the cache and statistics with f.cache_clear(). + Access the underlying function with f.__wrapped__. + + See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used + + """ + + # Users should only access the lru_cache through its public API: + # cache_info, cache_clear, and f.__wrapped__ + # The internals of the lru_cache are encapsulated for thread safety and + # to allow the implementation to change (including a possible C version). + + def decorating_function(user_function): + + cache = dict() + stats = [0, 0] # make statistics updateable non-locally + HITS, MISSES = 0, 1 # names for the stats fields + make_key = _make_key + cache_get = cache.get # bound method to lookup key or return None + _len = len # localize the global len() function + lock = RLock() # because linkedlist updates aren't threadsafe + root = [] # root of the circular doubly linked list + root[:] = [root, root, None, None] # initialize by pointing to self + nonlocal_root = [root] # make updateable non-locally + PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields + + if maxsize == 0: + + def wrapper(*args, **kwds): + # no caching, just do a statistics update after a successful call + result = user_function(*args, **kwds) + stats[MISSES] += 1 + return result + + elif maxsize is None: + + def wrapper(*args, **kwds): + # simple caching without ordering or size limit + key = make_key(args, kwds, typed) + result = cache_get(key, root) # root used here as a unique not-found sentinel + if result is not root: + stats[HITS] += 1 + return result + result = user_function(*args, **kwds) + cache[key] = result + stats[MISSES] += 1 + return result + + else: + + def wrapper(*args, **kwds): + # size limited caching that tracks accesses by recency + key = make_key(args, kwds, typed) if kwds or typed else args + with lock: + link = cache_get(key) + if link is not None: + # record recent use of the key by moving it to the front of the list + root, = nonlocal_root + link_prev, link_next, key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + stats[HITS] += 1 + return result + result = user_function(*args, **kwds) + with lock: + root, = nonlocal_root + if key in cache: + # getting here means that this same key was added to the + # cache while the lock was released. since the link + # update is already done, we need only return the + # computed result and update the count of misses. + pass + elif _len(cache) >= maxsize: + # use the old root to store the new key and result + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + # empty the oldest link and make it the new root + root = nonlocal_root[0] = oldroot[NEXT] + oldkey = root[KEY] + oldvalue = root[RESULT] + root[KEY] = root[RESULT] = None + # now update the cache dictionary for the new links + del cache[oldkey] + cache[key] = oldroot + else: + # put result in a new link at the front of the list + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + stats[MISSES] += 1 + return result + + def cache_info(): + """Report cache statistics""" + with lock: + return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache)) + + def cache_clear(): + """Clear the cache and cache statistics""" + with lock: + cache.clear() + root = nonlocal_root[0] + root[:] = [root, root, None, None] + stats[:] = [0, 0] + + wrapper.__wrapped__ = user_function + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return update_wrapper(wrapper, user_function) + + return decorating_function diff --git a/CadQuery/Libs/pint/compat/nullhandler.py b/CadQuery/Libs/pint/compat/nullhandler.py new file mode 100644 index 0000000..288cbb3 --- /dev/null +++ b/CadQuery/Libs/pint/compat/nullhandler.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +""" + pint.compat.nullhandler + ~~~~~~~~~~~~~~~~~~~~~~~ + + Taken from the Python 2.7 source code. + + :copyright: 2013, PSF + :license: PSF License +""" + + +import logging + +class NullHandler(logging.Handler): + """ + This handler does nothing. It's intended to be used to avoid the + "No handlers could be found for logger XXX" one-off warning. This is + important for library code, which may contain code to log events. If a user + of the library does not configure logging, the one-off warning might be + produced; to avoid this, the library developer simply needs to instantiate + a NullHandler and add it to the top-level logger of the library module or + package. + """ + def handle(self, record): + pass + + def emit(self, record): + pass + + def createLock(self): + self.lock = None diff --git a/CadQuery/Libs/pint/compat/transformdict.py b/CadQuery/Libs/pint/compat/transformdict.py new file mode 100644 index 0000000..c01ea30 --- /dev/null +++ b/CadQuery/Libs/pint/compat/transformdict.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +""" + pint.compat.transformdict + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Taken from the Python 3.4 source code. + + :copyright: 2013, PSF + :license: PSF License +""" + +from collections import MutableMapping + +_sentinel = object() + +class TransformDict(MutableMapping): + '''Dictionary that calls a transformation function when looking + up keys, but preserves the original keys. + + >>> d = TransformDict(str.lower) + >>> d['Foo'] = 5 + >>> d['foo'] == d['FOO'] == d['Foo'] == 5 + True + >>> set(d.keys()) + {'Foo'} + ''' + + __slots__ = ('_transform', '_original', '_data') + + def __init__(self, transform, init_dict=None, **kwargs): + '''Create a new TransformDict with the given *transform* function. + *init_dict* and *kwargs* are optional initializers, as in the + dict constructor. + ''' + if not callable(transform): + raise TypeError("expected a callable, got %r" % transform.__class__) + self._transform = transform + # transformed => original + self._original = {} + self._data = {} + if init_dict: + self.update(init_dict) + if kwargs: + self.update(kwargs) + + def getitem(self, key): + 'D.getitem(key) -> (stored key, value)' + transformed = self._transform(key) + original = self._original[transformed] + value = self._data[transformed] + return original, value + + @property + def transform_func(self): + "This TransformDict's transformation function" + return self._transform + + # Minimum set of methods required for MutableMapping + + def __len__(self): + return len(self._data) + + def __iter__(self): + return iter(self._original.values()) + + def __getitem__(self, key): + return self._data[self._transform(key)] + + def __setitem__(self, key, value): + transformed = self._transform(key) + self._data[transformed] = value + self._original.setdefault(transformed, key) + + def __delitem__(self, key): + transformed = self._transform(key) + del self._data[transformed] + del self._original[transformed] + + # Methods overriden to mitigate the performance overhead. + + def clear(self): + 'D.clear() -> None. Remove all items from D.' + self._data.clear() + self._original.clear() + + def __contains__(self, key): + return self._transform(key) in self._data + + def get(self, key, default=None): + 'D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.' + return self._data.get(self._transform(key), default) + + def pop(self, key, default=_sentinel): + '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + ''' + transformed = self._transform(key) + if default is _sentinel: + del self._original[transformed] + return self._data.pop(transformed) + else: + self._original.pop(transformed, None) + return self._data.pop(transformed, default) + + def popitem(self): + '''D.popitem() -> (k, v), remove and return some (key, value) pair + as a 2-tuple; but raise KeyError if D is empty. + ''' + transformed, value = self._data.popitem() + return self._original.pop(transformed), value + + # Other methods + + def copy(self): + 'D.copy() -> a shallow copy of D' + other = self.__class__(self._transform) + other._original = self._original.copy() + other._data = self._data.copy() + return other + + __copy__ = copy + + def __getstate__(self): + return (self._transform, self._data, self._original) + + def __setstate__(self, state): + self._transform, self._data, self._original = state + + def __repr__(self): + try: + equiv = dict(self) + except TypeError: + # Some keys are unhashable, fall back on .items() + equiv = list(self.items()) + return '%s(%r, %s)' % (self.__class__.__name__, + self._transform, repr(equiv)) diff --git a/CadQuery/Libs/pint/constants_en.txt b/CadQuery/Libs/pint/constants_en.txt new file mode 100644 index 0000000..2b67545 --- /dev/null +++ b/CadQuery/Libs/pint/constants_en.txt @@ -0,0 +1,51 @@ +# Default Pint constants definition file +# Based on the International System of Units +# Language: english +# Source: http://physics.nist.gov/cuu/Constants/Table/allascii.txt +# :copyright: 2013 by Pint Authors, see AUTHORS for more details. + +speed_of_light = 299792458 * meter / second = c +standard_gravity = 9.806650 * meter / second ** 2 = g_0 = g_n = gravity +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 + +# 0.000 000 29 e-34 +planck_constant = 6.62606957e-34 J s = h +hbar = planck_constant / (2 * pi) = ħ + +# 0.000 80 e-11 +newtonian_constant_of_gravitation = 6.67384e-11 m^3 kg^-1 s^-2 + +# 0.000 000 035 e-19 +# elementary_charge = 1.602176565e-19 C = e + +# 0.000 0075 +molar_gas_constant = 8.3144621 J mol^-1 K^-1 = R + +# 0.000 000 0024 e-3 +fine_structure_constant = 7.2973525698e-3 + +# 0.000 000 27 e23 +avogadro_number = 6.02214129e23 mol^-1 =N_A + +# 0.000 0013 e-23 +boltzmann_constant = 1.3806488e-23 J K^-1 = k + +# 0.000 021 e-8 +stefan_boltzmann_constant = 5.670373e-8 W m^-2 K^-4 = σ + +# 0.000 0053 e10 +wien_frequency_displacement_law_constant = 5.8789254e10 Hz K^-1 + +# 0.000 055 +rydberg_constant = 10973731.568539 m^-1 + +# 0.000 000 40 e-31 +electron_mass = 9.10938291e-31 kg = m_e + +# 0.000 000 074 e-27 +neutron_mass = 1.674927351e-27 kg = m_n + +# 0.000 000 074 e-27 +proton_mass = 1.672621777e-27 kg = m_p diff --git a/CadQuery/Libs/pint/context.py b/CadQuery/Libs/pint/context.py new file mode 100644 index 0000000..86df972 --- /dev/null +++ b/CadQuery/Libs/pint/context.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +""" + pint.context + ~~~~~~~~~~~~ + + Functions and classes related to context definitions and application. + + :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 +from collections import defaultdict +import weakref + +from .compat import ChainMap +from .util import ParserHelper, string_types + +#: Regex to match the header parts of a context. +_header_re = re.compile('@context\s*(?P\(.*\))?\s+(?P\w+)\s*(=(?P.*))*') + +#: Regex to match variable names in an equation. +_varname_re = re.compile('[A-Za-z_][A-Za-z0-9_]*') + + +def _freeze(d): + """Return a hashable view of dict. + """ + if isinstance(d, string_types): + d = ParserHelper.from_string(d) + if isinstance(d, frozenset): + return d + return frozenset(d.items()) + + +def _expression_to_function(eq): + def func(ureg, value, **kwargs): + return ureg.parse_expression(eq, value=value, **kwargs) + return func + + +class Context(object): + """A specialized container that defines transformation functions from + one dimension to another. Each Dimension are specified using a UnitsContainer. + Simple transformation are given with a function taking a single parameter. + + >>> timedim = UnitsContainer({'[time]': 1}) + >>> spacedim = UnitsContainer({'[length]': 1}) + >>> def f(time): + ... 'Time to length converter' + ... return 3. * time + >>> c = Context() + >>> c.add_transformation(timedim, spacedim, f) + >>> c.transform(timedim, spacedim, 2) + 6 + + Conversion functions may take optional keyword arguments and the context can + have default values for these arguments. + + >>> def f(time, n): + ... 'Time to length converter, n is the index of refraction of the material' + ... return 3. * time / n + >>> c = Context(n=3) + >>> c.add_transformation(timedim, spacedim, f) + >>> c.transform(timedim, spacedim, 2) + 2 + + """ + + def __init__(self, name, aliases=(), defaults=None): + + self.name = name + self.aliases = aliases + + #: Maps (src, dst) -> transformation function + self.funcs = {} + + #: Maps defaults variable names to values + self.defaults = defaults or {} + + #: Maps (src, dst) -> self + #: Used as a convenience dictionary to be composed by ContextChain + self.relation_to_context = weakref.WeakValueDictionary() + + @classmethod + def from_context(cls, context, **defaults): + """Creates a new context that shares the funcs dictionary with the original + context. The default values are copied from the original context and updated + with the new defaults. + + If defaults is empty, return the same context. + """ + if defaults: + newdef = dict(context.defaults, **defaults) + c = cls(context.name, context.aliases, newdef) + c.funcs = context.funcs + for edge in context.funcs.keys(): + c.relation_to_context[edge] = c + return c + return context + + @classmethod + def from_lines(cls, lines, to_base_func=None): + header, lines = lines[0], lines[1:] + + r = _header_re.search(header) + name = r.groupdict()['name'].strip() + aliases = r.groupdict()['aliases'] + if aliases: + aliases = tuple(a.strip() for a in r.groupdict()['aliases'].split('=')) + else: + aliases = () + defaults = r.groupdict()['defaults'] + + if defaults: + def to_num(val): + val = complex(val) + if not val.imag: + return val.real + return val + + try: + _txt = defaults + defaults = (part.split('=') for part in defaults.strip('()').split(',')) + defaults = dict((str(k).strip(), to_num(v)) + for k, v in defaults) + except (ValueError, TypeError): + raise ValueError('Could not parse Context definition defaults: %s', _txt) + + ctx = cls(name, aliases, defaults) + else: + ctx = cls(name, aliases) + + names = set() + for line in lines: + line = line.strip() + if not line or line.startswith('#'): + continue + + rel, eq = line.split(':') + names.update(_varname_re.findall(eq)) + + func = _expression_to_function(eq) + + if '<->' in rel: + src, dst = (ParserHelper.from_string(s) for s in rel.split('<->')) + if to_base_func: + src = to_base_func(src) + dst = to_base_func(dst) + ctx.add_transformation(src, dst, func) + ctx.add_transformation(dst, src, func) + elif '->' in rel: + src, dst = (ParserHelper.from_string(s) for s in rel.split('->')) + if to_base_func: + src = to_base_func(src) + dst = to_base_func(dst) + ctx.add_transformation(src, dst, func) + else: + raise ValueError('Relationships must be specified with <-> or ->.') + + if defaults: + missing_pars = set(defaults.keys()).difference(set(names)) + if missing_pars: + raise ValueError('Context parameters {0} not found in any equation.'.format(missing_pars)) + + return ctx + + def add_transformation(self, src, dst, func): + """Add a transformation function to the context. + """ + _key = self.__keytransform__(src, dst) + self.funcs[_key] = func + self.relation_to_context[_key] = self + + def remove_transformation(self, src, dst): + """Add a transformation function to the context. + """ + _key = self.__keytransform__(src, dst) + del self.funcs[_key] + del self.relation_to_context[_key] + + @staticmethod + def __keytransform__(src, dst): + return _freeze(src), _freeze(dst) + + def transform(self, src, dst, registry, value): + """Transform a value. + """ + _key = self.__keytransform__(src, dst) + return self.funcs[_key](registry, value, **self.defaults) + + +class ContextChain(ChainMap): + """A specialized ChainMap for contexts that simplifies finding rules + to transform from one dimension to another. + """ + + def __init__(self, *args, **kwargs): + super(ContextChain, self).__init__(*args, **kwargs) + self._graph = None + self._contexts = [] + + def insert_contexts(self, *contexts): + """Insert one or more contexts in reversed order the chained map. + (A rule in last context will take precedence) + + To facilitate the identification of the context with the matching rule, + the *relation_to_context* dictionary of the context is used. + """ + self._contexts.insert(0, contexts) + self.maps = [ctx.relation_to_context for ctx in reversed(contexts)] + self.maps + self._graph = None + + def remove_contexts(self, n): + """Remove the last n inserted contexts from the chain. + """ + self._contexts = self._contexts[n:] + self.maps = self.maps[n:] + self._graph = None + + @property + def defaults(self): + if self: + return list(self.maps[0].values())[0].defaults + return {} + + @property + def graph(self): + """The graph relating + """ + if self._graph is None: + self._graph = defaultdict(set) + for fr_, to_ in self: + self._graph[fr_].add(to_) + return self._graph + + def transform(self, src, dst, registry, value): + """Transform the value, finding the rule in the chained context. + (A rule in last context will take precedence) + + :raises: KeyError if the rule is not found. + """ + return self[(src, dst)].transform(src, dst, registry, value) diff --git a/CadQuery/Libs/pint/default_en.txt b/CadQuery/Libs/pint/default_en.txt new file mode 100644 index 0000000..d8cb3cc --- /dev/null +++ b/CadQuery/Libs/pint/default_en.txt @@ -0,0 +1,350 @@ +# Default Pint units definition file +# Based on the International System of Units +# Language: english +# :copyright: 2013 by Pint Authors, see AUTHORS for more details. + +# decimal prefixes +yocto- = 1e-24 = y- +zepto- = 1e-21 = z- +atto- = 1e-18 = a- +femto- = 1e-15 = f- +pico- = 1e-12 = p- +nano- = 1e-9 = n- +micro- = 1e-6 = u- = µ- +milli- = 1e-3 = m- +centi- = 1e-2 = c- +deci- = 1e-1 = d- +deca- = 1e+1 = da- +hecto- = 1e2 = h- +kilo- = 1e3 = k- +mega- = 1e6 = M- +giga- = 1e9 = G- +tera- = 1e12 = T- +peta- = 1e15 = P- +exa- = 1e18 = E- +zetta- = 1e21 = Z- +yotta- = 1e24 = Y- + +# binary_prefixes +kibi- = 2**10 = Ki- +mebi- = 2**20 = Mi- +gibi- = 2**30 = Gi- +tebi- = 2**40 = Ti- +pebi- = 2**50 = Pi- +exbi- = 2**60 = Ei- +zebi- = 2**70 = Zi- +yobi- = 2**80 = Yi- + +# reference +meter = [length] = m = metre +second = [time] = s = sec +ampere = [current] = A = amp +candela = [luminosity] = cd = candle +gram = [mass] = g +mole = [substance] = mol +kelvin = [temperature]; offset: 0 = K = degK +radian = [] = rad +bit = [] +count = [] + +@import constants_en.txt + +# acceleration +[acceleration] = [length] / [time] ** 2 + +# 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 + +# Area +[area] = [length] ** 2 +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 +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 + +# Energy +[energy] = [force] * [length] +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 +[force] = [mass] * [acceleration] +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 +[frequency] = 1 / [time] +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 = ångström = Å +inch = 2.54 * centimeter = in = international_inch = inches = international_inches +foot = 12 * inch = ft = international_foot = feet = 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 = lightyear +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 +[power] = [energy] / [time] +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 +[pressure] = [force] / [area] +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 = 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 +standard_liter_per_minute = 1.68875 * Pa * m ** 3 / s = slpm = slm + +# 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 + +# Temperature +degC = kelvin; offset: 273.15 = celsius +degR = 5 / 9 * kelvin; offset: 0 = rankine +degF = 5 / 9 * kelvin; offset: 255.372222 = fahrenheit + +# Time +minute = 60 * second = min +hour = 60 * minute = 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 +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] = [length] / [time] +knot = nautical_mile / hour = kt = knot_international = international_knot = nautical_miles_per_hour +mph = mile / hour = MPH +kph = kilometer / hour = KPH + +# Viscosity +[viscosity] = [pressure] * [time] +poise = 1e-1 * Pa * second = P +stokes = 1e-4 * meter ** 2 / second = St +rhe = 10 / (Pa * s) + +# Volume +[volume] = [length] ** 3 +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 +fluid_ounce = gill / 4 = floz = US_fluid_ounce = US_liquid_ounce +imperial_bushel = 36.36872 * liter = UK_bushel +imperial_gallon = imperial_bushel / 8 = 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 +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 + + +@context(n=1) spectroscopy = sp + # n index of refraction of the medium. + [length] <-> [frequency]: speed_of_light / n / value + [frequency] -> [energy]: planck_constant * value + [energy] -> [frequency]: value / planck_constant +@end + +@context boltzmann + [temperature] -> [energy]: boltzmann_constant * value + [energy] -> [temperature]: value / boltzmann_constant +@end + +@context(mw=0,volume=0,solvent_mass=0) chemistry = chem + # mw is the molecular weight of the species + # volume is the volume of the solution + # solvent_mass is the mass of solvent in the solution + + # moles -> mass require the molecular weight + [substance] -> [mass]: value * mw + [mass] -> [substance]: value / mw + + # moles/volume -> mass/volume and moles/mass -> mass / mass + # require the molecular weight + [substance] / [volume] -> [mass] / [volume]: value * mw + [mass] / [volume] -> [substance] / [volume]: value / mw + [substance] / [mass] -> [mass] / [mass]: value * mw + [mass] / [mass] -> [substance] / [mass]: value / mw + + # moles/volume -> moles requires the solution volume + [substance] / [volume] -> [substance]: value * volume + [substance] -> [substance] / [volume]: value / volume + + # moles/mass -> moles requires the solvent (usually water) mass + [substance] / [mass] -> [substance]: value * solvent_mass + [substance] -> [substance] / [mass]: value / solvent_mass + + # moles/mass -> moles/volume require the solvent mass and the volume + [substance] / [mass] -> [substance]/[volume]: value * solvent_mass / volume + [substance] / [volume] -> [substance] / [mass]: value / solvent_mass * volume + +@end diff --git a/CadQuery/Libs/pint/formatting.py b/CadQuery/Libs/pint/formatting.py new file mode 100644 index 0000000..490dcd6 --- /dev/null +++ b/CadQuery/Libs/pint/formatting.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +""" + pint.formatter + ~~~~~~~~~~~~~~ + + Format units 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 + +__JOIN_REG_EXP = re.compile("\{\d*\}") + + +def _join(fmt, iterable): + """Join an iterable with the format specified in fmt. + + The format can be specified in two ways: + - PEP3101 format with two replacement fields (eg. '{0} * {1}') + - The concatenating string (eg. ' * ') + """ + if not iterable: + 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 + +_PRETTY_EXPONENTS = '⁰¹²³⁴⁵⁶⁷⁸⁹' + + +def _pretty_fmt_exponent(num): + """Format an number into a pretty printed exponent. + """ + # TODO: Will not work for decimals + ret = '{0:n}'.format(num).replace('-', '⁻') + for n in range(10): + ret = ret.replace(str(n), _PRETTY_EXPONENTS[n]) + return ret + + +#: _FORMATS maps format specifications to the corresponding argument set to +#: formatter(). +_FORMATS = { + 'P': { # Pretty format. + 'as_ratio': True, + 'single_denominator': False, + 'product_fmt': '·', + 'division_fmt': '/', + 'power_fmt': '{0}{1}', + 'parentheses_fmt': '({0})', + 'exp_call': _pretty_fmt_exponent, + }, + + 'L': { # Latex format. + 'as_ratio': True, + 'single_denominator': True, + 'product_fmt': r' \cdot ', + 'division_fmt': r'\frac[{0}][{1}]', + 'power_fmt': '{0}^[{1}]', + 'parentheses_fmt': r'\left({0}\right)', + }, + + 'H': { # HTML format. + 'as_ratio': True, + 'single_denominator': True, + 'product_fmt': r' ', + 'division_fmt': r'{0}/{1}', + 'power_fmt': '{0}{1}', + 'parentheses_fmt': r'({0})', + }, + + '': { # Default format. + 'as_ratio': True, + 'single_denominator': False, + 'product_fmt': ' * ', + 'division_fmt': ' / ', + 'power_fmt': '{0} ** {1}', + 'parentheses_fmt': r'({0})', + }, + + 'C': { # Compact format. + 'as_ratio': True, + 'single_denominator': False, + 'product_fmt': '*', # TODO: Should this just be ''? + 'division_fmt': '/', + 'power_fmt': '{0}**{1}', + 'parentheses_fmt': r'({0})', + }, + } + + +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 not items: + return '' + + 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 and as_ratio: + neg_terms.append(key) + else: + neg_terms.append(power_fmt.format(key, fun(value))) + + if not as_ratio: + # Show as Product: positive * negative terms ** -1 + return _join(product_fmt, pos_terms + neg_terms) + + # Show as Ratio: positive terms / negative terms + pos_ret = _join(product_fmt, pos_terms) or '1' + + if not neg_terms: + return pos_ret + + 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) + + return _join(division_fmt, [pos_ret, neg_ret]) + +# Extract just the type from the specification mini-langage: see +# http://docs.python.org/2/library/string.html#format-specification-mini-language +# We also add uS for uncertainties. +_BASIC_TYPES = frozenset('bcdeEfFgGnosxX%uS') +_KNOWN_TYPES = frozenset(_FORMATS.keys()) + +def _parse_spec(spec): + result = '' + for ch in reversed(spec): + if ch == '~' or ch in _BASIC_TYPES: + continue + elif ch in _KNOWN_TYPES: + if result: + raise ValueError("expected ':' after format specifier") + else: + result = ch + elif ch.isalpha(): + raise ValueError("Unknown conversion specified " + ch) + else: + break + return result + + +def format_unit(unit, spec): + if not unit: + return 'dimensionless' + + spec = _parse_spec(spec) + fmt = _FORMATS[spec] + + result = formatter(unit.items(), **fmt) + if spec == 'L': + result = result.replace('[', '{').replace(']', '}') + return result + + +def remove_custom_flags(spec): + for flag in _FORMATS.keys(): + if flag: + spec = spec.replace(flag, '') + return spec diff --git a/CadQuery/Libs/pint/measurement.py b/CadQuery/Libs/pint/measurement.py new file mode 100644 index 0000000..305d84a --- /dev/null +++ b/CadQuery/Libs/pint/measurement.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +""" + pint.measurement + ~~~~~~~~~~~~~~~~ + + :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 + +from .compat import ufloat +from .formatting import _FORMATS + +MISSING = object() + +class _Measurement(object): + """Implements a class to describe a quantity with uncertainty. + + :param value: The most likely value of the measurement. + :type value: Quantity or Number + :param error: The error or uncertainty of the measurement. + :type error: Quantity or Number + """ + + def __new__(cls, value, error, units=MISSING): + if units is MISSING: + try: + value, units = value.magnitude, value.units + except AttributeError: + try: + value, error, units = value.nominal_value, value.std_dev, error + except AttributeError: + units = '' + try: + error = error.to(units).magnitude + except AttributeError: + pass + + inst = super(_Measurement, cls).__new__(cls, ufloat(value, error), units) + + if error < 0: + raise ValueError('The magnitude of the error cannot be negative'.format(value, error)) + return inst + + @property + def value(self): + return self._REGISTRY.Quantity(self.magnitude.nominal_value, self.units) + + @property + def error(self): + return self._REGISTRY.Quantity(self.magnitude.std_dev, self.units) + + @property + def rel(self): + return float(abs(self.magnitude.std_dev / self.magnitude.nominal_value)) + + def __repr__(self): + return "".format(self.magnitude.nominal_value, + self.magnitude.std_dev, + self.units) + + def __str__(self): + return '{0}'.format(self) + + def __format__(self, spec): + if 'L' in spec: + newpm = pm = r' \pm ' + pars = _FORMATS['L']['parentheses_fmt'] + elif 'P' in spec: + newpm = pm = '±' + pars = _FORMATS['P']['parentheses_fmt'] + else: + newpm = pm = '+/-' + pars = _FORMATS['']['parentheses_fmt'] + + if 'C' in spec: + sp = '' + newspec = spec.replace('C', '') + pars = _FORMATS['C']['parentheses_fmt'] + else: + sp = ' ' + newspec = spec + + if 'H' in spec: + newpm = '±' + newspec = spec.replace('H', '') + pars = _FORMATS['H']['parentheses_fmt'] + + mag = format(self.magnitude, newspec).replace(pm, sp + newpm + sp) + + if 'L' in newspec and 'S' in newspec: + mag = mag.replace('(', r'\left(').replace(')', r'\right)') + + if 'uS' in newspec or 'ue' in newspec or 'u%' in newspec: + return mag + ' ' + format(self.units, spec) + else: + return pars.format(mag) + ' ' + format(self.units, spec) + + + diff --git a/CadQuery/Libs/pint/quantity.py b/CadQuery/Libs/pint/quantity.py new file mode 100644 index 0000000..a5f6614 --- /dev/null +++ b/CadQuery/Libs/pint/quantity.py @@ -0,0 +1,1249 @@ +# -*- coding: utf-8 -*- +""" + pint.quantity + ~~~~~~~~~~~~~ + + :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 copy +import math +import operator +import functools + +from .formatting import remove_custom_flags +from .unit import (DimensionalityError, OffsetUnitCalculusError, + UnitsContainer, UnitDefinition, UndefinedUnitError) +from .compat import string_types, ndarray, np, _to_magnitude, long_type +from .util import logger + + +def _eq(first, second, check_all): + """Comparison of scalars and arrays + """ + out = first == second + if check_all and isinstance(out, ndarray): + return np.all(out) + return out + + +class _Exception(Exception): # pragma: no cover + + def __init__(self, internal): + self.internal = internal + + +def _check(q1, other): + """Check Quantities before math operations. + + Return True if q1 and other are from the same class. + Raise a ValueError if other has a different _REGISTRY than q1. + + In other case, return False. + """ + + if isinstance(other, q1.__class__): + # Both quantities are the same class and therefore from the same registry. + # (Each registry has its own Quantity class) + return True + elif q1._REGISTRY is getattr(other, '_REGISTRY', None): + return True + elif isinstance(other, _Quantity): + # The other object is a Quantity but from another registry. + raise ValueError('Cannot operate between quantities of different registries') + + return False + + +class _Quantity(object): + """Implements a class to describe a physical quantity: + the product of a numerical value and a unit of measurement. + + :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. + """ + + #: Default formatting string. + default_format = '' + + def __reduce__(self): + from . import _build_quantity + return _build_quantity, (self.magnitude, self.units) + + def __new__(cls, value, units=None): + if units is None: + if isinstance(value, string_types): + if value == '': + raise ValueError('Expression to parse as Quantity cannot be an empty string.') + inst = cls._REGISTRY.parse_expression(value) + return cls.__new__(cls, inst) + elif isinstance(value, cls): + inst = copy.copy(value) + else: + inst = object.__new__(cls) + inst._magnitude = _to_magnitude(value, inst.force_ndarray) + inst._units = UnitsContainer() + elif isinstance(units, (UnitsContainer, UnitDefinition)): + inst = object.__new__(cls) + inst._magnitude = _to_magnitude(value, inst.force_ndarray) + inst._units = units + elif isinstance(units, string_types): + inst = object.__new__(cls) + inst._magnitude = _to_magnitude(value, inst.force_ndarray) + inst._units = inst._REGISTRY.parse_units(units) + elif isinstance(units, cls): + if units.magnitude != 1: + logger.warning('Creating new Quantity using a non unity Quantity as units.') + inst = copy.copy(units) + inst._magnitude = _to_magnitude(value, inst.force_ndarray) + else: + raise TypeError('units must be of type str, Quantity or ' + 'UnitsContainer; not {0}.'.format(type(units))) + + inst.__used = False + inst.__handling = None + return inst + + @property + def debug_used(self): + return self.__used + + def __copy__(self): + ret = self.__class__(copy.copy(self._magnitude), copy.copy(self._units)) + ret.__used = self.__used + return ret + + def __str__(self): + return format(self) + + def __repr__(self): + return "".format(self._magnitude, self._units) + + def __format__(self, spec): + spec = spec or self.default_format + + if '~' in spec: + units = UnitsContainer(dict((self._REGISTRY._get_symbol(key), value) + for key, value in self.units.items())) + spec = spec.replace('~', '') + else: + units = self.units + + return '%s %s' % (format(self.magnitude, remove_custom_flags(spec)), + format(units, spec)) + + # IPython related code + def _repr_html_(self): + return self.__format__('H') + + def _repr_latex_(self): + return "$" + self.__format__('L') + "$" + + @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.to_base_units().units) + + @property + def dimensionless(self): + """Return true if the quantity is dimensionless. + """ + tmp = copy.copy(self).to_base_units() + + return not bool(tmp.dimensionality) + + @property + def dimensionality(self): + """Quantity's dimensionality (e.g. {length: 1, time: -1}) + """ + try: + return self._dimensionality + except AttributeError: + self._dimensionality = self._REGISTRY.get_dimensionality(self.units) + + return self._dimensionality + + def compatible_units(self, *contexts): + if contexts: + with self._REGISTRY.context(*contexts): + return self._REGISTRY.get_compatible_units(self._units) + + return self._REGISTRY.get_compatible_units(self._units) + + def _convert_magnitude_not_inplace(self, other, *contexts, **ctx_kwargs): + if contexts: + with self._REGISTRY.context(*contexts, **ctx_kwargs): + return self._REGISTRY.convert(self._magnitude, self._units, other) + + return self._REGISTRY.convert(self._magnitude, self._units, other) + + def _convert_magnitude(self, other, *contexts, **ctx_kwargs): + if contexts: + with self._REGISTRY.context(*contexts, **ctx_kwargs): + return self._REGISTRY.convert(self._magnitude, self._units, other) + + return self._REGISTRY.convert(self._magnitude, self._units, other, + inplace=isinstance(self._magnitude, ndarray)) + + def ito(self, other=None, *contexts, **ctx_kwargs): + """Inplace rescale to different units. + + :param other: destination units. + :type other: Quantity, str or dict + """ + if isinstance(other, string_types): + other = self._REGISTRY.parse_units(other) + elif isinstance(other, self.__class__): + other = copy.copy(other.units) + elif isinstance(other, UnitsContainer): + pass + else: + other = UnitsContainer(other) + + self._magnitude = self._convert_magnitude(other, *contexts, **ctx_kwargs) + self._units = other + + return None + + def to(self, other=None, *contexts, **ctx_kwargs): + """Return Quantity rescaled to different units. + + :param other: destination units. + :type other: Quantity, str or dict + """ + if isinstance(other, string_types): + other = self._REGISTRY.parse_units(other) + elif isinstance(other, self.__class__): + other = copy.copy(other.units) + elif isinstance(other, UnitsContainer): + pass + else: + other = UnitsContainer(other) + + magnitude = self._convert_magnitude_not_inplace(other, *contexts, **ctx_kwargs) + + return self.__class__(magnitude, other) + + def ito_base_units(self): + """Return Quantity rescaled to base units + """ + + _, other = self._REGISTRY.get_base_units(self.units) + + self._magnitude = self._convert_magnitude(other) + self._units = other + + return None + + def to_base_units(self): + """Return Quantity rescaled to base units + """ + + _, other = self._REGISTRY.get_base_units(self.units) + + magnitude = self._convert_magnitude_not_inplace(other) + + return self.__class__(magnitude, other) + + # Mathematical operations + def __int__(self): + if self.dimensionless: + return int(self._convert_magnitude_not_inplace(UnitsContainer())) + raise DimensionalityError(self.units, 'dimensionless') + + def __long__(self): + if self.dimensionless: + return long_type(self._convert_magnitude_not_inplace(UnitsContainer())) + raise DimensionalityError(self.units, 'dimensionless') + + def __float__(self): + if self.dimensionless: + return float(self._convert_magnitude_not_inplace(UnitsContainer())) + raise DimensionalityError(self.units, 'dimensionless') + + def __complex__(self): + if self.dimensionless: + return complex(self._convert_magnitude_not_inplace(UnitsContainer())) + raise DimensionalityError(self.units, 'dimensionless') + + def _iadd_sub(self, other, op): + """Perform addition or subtraction operation in-place and return the result. + + :param other: object to be added to / subtracted from self + :type other: Quantity or any type accepted by :func:`_to_magnitude` + :param op: operator function (e.g. operator.add, operator.isub) + :type op: function + """ + if not _check(self, other): + # other not from same Registry or not a Quantity + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + if _eq(other, 0, True): + # If the other value is 0 (but not Quantity 0) + # do the operation without checking units. + # We do the calculation instead of just returning the same value to + # enforce any shape checking and type casting due to the operation. + self._magnitude = op(self._magnitude, other_magnitude) + elif self.dimensionless: + self.ito(UnitsContainer()) + self._magnitude = op(self._magnitude, other_magnitude) + else: + raise DimensionalityError(self.units, 'dimensionless') + return self + + if not self.dimensionality == other.dimensionality: + raise DimensionalityError(self.units, other.units, + self.dimensionality, + other.dimensionality) + + # Next we define some variables to make if-clauses more readable. + self_non_mul_units = self._get_non_multiplicative_units() + is_self_multiplicative = len(self_non_mul_units) == 0 + if len(self_non_mul_units) == 1: + self_non_mul_unit = self_non_mul_units[0] + other_non_mul_units = other._get_non_multiplicative_units() + is_other_multiplicative = len(other_non_mul_units) == 0 + if len(other_non_mul_units) == 1: + other_non_mul_unit = other_non_mul_units[0] + + # Presence of non-multiplicative units gives rise to several cases. + if is_self_multiplicative and is_other_multiplicative: + if self._units == other._units: + self._magnitude = op(self._magnitude, other._magnitude) + # If only self has a delta unit, other determines unit of result. + elif self._get_delta_units() and not other._get_delta_units(): + self._magnitude = op(self._convert_magnitude(other.units), + other._magnitude) + self._units = copy.copy(other.units) + else: + self._magnitude = op(self._magnitude, + other.to(self.units)._magnitude) + + elif (op == operator.isub and len(self_non_mul_units) == 1 + and self.units[self_non_mul_unit] == 1 + and not other._has_compatible_delta(self_non_mul_unit)): + if self.units == other.units: + self._magnitude = op(self._magnitude, other._magnitude) + else: + self._magnitude = op(self._magnitude, + other.to(self.units)._magnitude) + self.units['delta_' + self_non_mul_unit + ] = self.units.pop(self_non_mul_unit) + + elif (op == operator.isub and len(other_non_mul_units) == 1 + and other.units[other_non_mul_unit] == 1 + and not self._has_compatible_delta(other_non_mul_unit)): + # we convert to self directly since it is multiplicative + self._magnitude = op(self._magnitude, + other.to(self.units)._magnitude) + + elif (len(self_non_mul_units) == 1 + # order of the dimension of offset unit == 1 ? + and self._units[self_non_mul_unit] == 1 + and other._has_compatible_delta(self_non_mul_unit)): + tu = copy.copy(self.units) + # Replace offset unit in self by the corresponding delta unit. + # This is done to prevent a shift by offset in the to()-call. + tu['delta_' + self_non_mul_unit] = tu.pop(self_non_mul_unit) + self._magnitude = op(self._magnitude, other.to(tu)._magnitude) + elif (len(other_non_mul_units) == 1 + # order of the dimension of offset unit == 1 ? + and other._units[other_non_mul_unit] == 1 + and self._has_compatible_delta(other_non_mul_unit)): + tu = copy.copy(other.units) + # Replace offset unit in other by the corresponding delta unit. + # This is done to prevent a shift by offset in the to()-call. + tu['delta_' + other_non_mul_unit] = tu.pop(other_non_mul_unit) + self._magnitude = op(self._convert_magnitude(tu), other._magnitude) + self._units = copy.copy(other.units) + else: + raise OffsetUnitCalculusError(self.units, other.units) + + return self + + def _add_sub(self, other, op): + """Perform addition or subtraction operation and return the result. + + :param other: object to be added to / subtracted from self + :type other: Quantity or any type accepted by :func:`_to_magnitude` + :param op: operator function (e.g. operator.add, operator.isub) + :type op: function + """ + if not _check(self, other): + # other not from same Registry or not a Quantity + if _eq(other, 0, True): + # If the other value is 0 (but not Quantity 0) + # do the operation without checking units. + # We do the calculation instead of just returning the same value to + # enforce any shape checking and type casting due to the operation. + units = self.units + magnitude = op(self._magnitude, + _to_magnitude(other, self.force_ndarray)) + elif self.dimensionless: + units = UnitsContainer() + magnitude = op(self.to(units)._magnitude, + _to_magnitude(other, self.force_ndarray)) + else: + raise DimensionalityError(self.units, 'dimensionless') + return self.__class__(magnitude, units) + + if not self.dimensionality == other.dimensionality: + raise DimensionalityError(self.units, other.units, + self.dimensionality, + other.dimensionality) + + # Next we define some variables to make if-clauses more readable. + self_non_mul_units = self._get_non_multiplicative_units() + is_self_multiplicative = len(self_non_mul_units) == 0 + if len(self_non_mul_units) == 1: + self_non_mul_unit = self_non_mul_units[0] + other_non_mul_units = other._get_non_multiplicative_units() + is_other_multiplicative = len(other_non_mul_units) == 0 + if len(other_non_mul_units) == 1: + other_non_mul_unit = other_non_mul_units[0] + + # Presence of non-multiplicative units gives rise to several cases. + if is_self_multiplicative and is_other_multiplicative: + if self._units == other._units: + magnitude = op(self._magnitude, other._magnitude) + units = copy.copy(self.units) + # If only self has a delta unit, other determines unit of result. + elif self._get_delta_units() and not other._get_delta_units(): + magnitude = op(self._convert_magnitude(other.units), + other._magnitude) + units = copy.copy(other.units) + else: + units = copy.copy(self.units) + magnitude = op(self._magnitude, + other.to(self.units).magnitude) + + elif (op == operator.sub and len(self_non_mul_units) == 1 + and self.units[self_non_mul_unit] == 1 + and not other._has_compatible_delta(self_non_mul_unit)): + if self.units == other.units: + magnitude = op(self._magnitude, other._magnitude) + else: + magnitude = op(self._magnitude, + other.to(self.units)._magnitude) + units = copy.copy(self.units) + units['delta_' + self_non_mul_unit] = units.pop(self_non_mul_unit) + + elif (op == operator.sub and len(other_non_mul_units) == 1 + and other.units[other_non_mul_unit] == 1 + and not self._has_compatible_delta(other_non_mul_unit)): + # we convert to self directly since it is multiplicative + magnitude = op(self._magnitude, + other.to(self.units)._magnitude) + units = copy.copy(self.units) + + elif (len(self_non_mul_units) == 1 + # order of the dimension of offset unit == 1 ? + and self._units[self_non_mul_unit] == 1 + and other._has_compatible_delta(self_non_mul_unit)): + tu = copy.copy(self.units) + # Replace offset unit in self by the corresponding delta unit. + # This is done to prevent a shift by offset in the to()-call. + tu['delta_' + self_non_mul_unit] = tu.pop(self_non_mul_unit) + magnitude = op(self._magnitude, other.to(tu).magnitude) + units = copy.copy(self.units) + elif (len(other_non_mul_units) == 1 + # order of the dimension of offset unit == 1 ? + and other._units[other_non_mul_unit] == 1 + and self._has_compatible_delta(other_non_mul_unit)): + tu = copy.copy(other.units) + # Replace offset unit in other by the corresponding delta unit. + # This is done to prevent a shift by offset in the to()-call. + tu['delta_' + other_non_mul_unit] = tu.pop(other_non_mul_unit) + magnitude = op(self._convert_magnitude(tu), other._magnitude) + units = copy.copy(other.units) + else: + raise OffsetUnitCalculusError(self.units, other.units) + + return self.__class__(magnitude, units) + + def __iadd__(self, other): + if not isinstance(self._magnitude, ndarray): + return self._add_sub(other, operator.add) + else: + return self._iadd_sub(other, operator.iadd) + + def __add__(self, other): + return self._add_sub(other, operator.add) + + __radd__ = __add__ + + def __isub__(self, other): + if not isinstance(self._magnitude, ndarray): + return self._add_sub(other, operator.sub) + else: + return self._iadd_sub(other, operator.isub) + + def __sub__(self, other): + return self._add_sub(other, operator.sub) + + def __rsub__(self, other): + return -self._add_sub(other, operator.sub) + + def _imul_div(self, other, magnitude_op, units_op=None): + """Perform multiplication or division operation in-place and return the result. + + :param other: object to be multiplied/divided with self + :type other: Quantity or any type accepted by :func:`_to_magnitude` + :param magnitude_op: operator function to perform on the magnitudes (e.g. operator.mul) + :type magnitude_op: function + :param units_op: operator function to perform on the units; if None, *magnitude_op* is used + :type units_op: function or None + """ + if units_op is None: + units_op = magnitude_op + + offset_units_self = self._get_non_multiplicative_units() + no_offset_units_self = len(offset_units_self) + + if not _check(self, other): + if not self._ok_for_muldiv(no_offset_units_self): + raise OffsetUnitCalculusError(self.units, + getattr(other, 'units', '')) + if len(offset_units_self) == 1: + if (self.units[offset_units_self[0]] != 1 + or magnitude_op not in [operator.mul, operator.imul]): + raise OffsetUnitCalculusError(self.units, + getattr(other, 'units', '')) + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + self._magnitude = magnitude_op(self._magnitude, other_magnitude) + self._units = units_op(self._units, UnitsContainer()) + return self + + if not self._ok_for_muldiv(no_offset_units_self): + raise OffsetUnitCalculusError(self.units, other.units) + elif no_offset_units_self == 1 and len(self.units) == 1: + self.ito_base_units() + + no_offset_units_other = len(other._get_non_multiplicative_units()) + + if not other._ok_for_muldiv(no_offset_units_other): + raise OffsetUnitCalculusError(self.units, other.units) + elif no_offset_units_other == 1 and len(other.units) == 1: + other.ito_base_units() + + self._magnitude = magnitude_op(self._magnitude, other._magnitude) + self._units = units_op(self._units, other._units) + + return self + + def _mul_div(self, other, magnitude_op, units_op=None): + """Perform multiplication or division operation and return the result. + + :param other: object to be multiplied/divided with self + :type other: Quantity or any type accepted by :func:`_to_magnitude` + :param magnitude_op: operator function to perform on the magnitudes (e.g. operator.mul) + :type magnitude_op: function + :param units_op: operator function to perform on the units; if None, *magnitude_op* is used + :type units_op: function or None + """ + if units_op is None: + units_op = magnitude_op + + offset_units_self = self._get_non_multiplicative_units() + no_offset_units_self = len(offset_units_self) + + if not _check(self, other): + if not self._ok_for_muldiv(no_offset_units_self): + raise OffsetUnitCalculusError(self.units, + getattr(other, 'units', '')) + if len(offset_units_self) == 1: + if (self.units[offset_units_self[0]] != 1 + or magnitude_op not in [operator.mul, operator.imul]): + raise OffsetUnitCalculusError(self.units, + getattr(other, 'units', '')) + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + + magnitude = magnitude_op(self._magnitude, other_magnitude) + units = units_op(self._units, UnitsContainer()) + + return self.__class__(magnitude, units) + + new_self = self + + if not self._ok_for_muldiv(no_offset_units_self): + raise OffsetUnitCalculusError(self.units, other.units) + elif no_offset_units_self == 1 and len(self.units) == 1: + new_self = self.to_base_units() + + no_offset_units_other = len(other._get_non_multiplicative_units()) + + if not other._ok_for_muldiv(no_offset_units_other): + raise OffsetUnitCalculusError(self.units, other.units) + elif no_offset_units_other == 1 and len(other.units) == 1: + other = other.to_base_units() + + magnitude = magnitude_op(new_self._magnitude, other._magnitude) + units = units_op(new_self._units, other._units) + + return self.__class__(magnitude, units) + + def __imul__(self, other): + if not isinstance(self._magnitude, ndarray): + return self._mul_div(other, operator.mul) + else: + return self._imul_div(other, operator.imul) + + def __mul__(self, other): + return self._mul_div(other, operator.mul) + + __rmul__ = __mul__ + + def __itruediv__(self, other): + if not isinstance(self._magnitude, ndarray): + return self._mul_div(other, operator.truediv) + else: + return self._imul_div(other, operator.itruediv) + + def __truediv__(self, other): + return self._mul_div(other, operator.truediv) + + def __ifloordiv__(self, other): + if not isinstance(self._magnitude, ndarray): + return self._mul_div(other, operator.floordiv, units_op=operator.itruediv) + else: + return self._imul_div(other, operator.ifloordiv, units_op=operator.itruediv) + + def __floordiv__(self, other): + return self._mul_div(other, operator.floordiv, units_op=operator.truediv) + + def __rtruediv__(self, other): + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + + no_offset_units_self = len(self._get_non_multiplicative_units()) + if not self._ok_for_muldiv(no_offset_units_self): + raise OffsetUnitCalculusError(self.units, '') + elif no_offset_units_self == 1 and len(self.units) == 1: + self = self.to_base_units() + + return self.__class__(other_magnitude / self._magnitude, 1 / self._units) + + def __rfloordiv__(self, other): + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + + no_offset_units_self = len(self._get_non_multiplicative_units()) + if not self._ok_for_muldiv(no_offset_units_self): + raise OffsetUnitCalculusError(self.units, '') + elif no_offset_units_self == 1 and len(self.units) == 1: + self = self.to_base_units() + + return self.__class__(other_magnitude // self._magnitude, 1 / self._units) + + __div__ = __truediv__ + __rdiv__ = __rtruediv__ + __idiv__ = __itruediv__ + + def __ipow__(self, other): + if not isinstance(self._magnitude, ndarray): + return self.__pow__(other) + + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + else: + if not self._ok_for_muldiv: + raise OffsetUnitCalculusError(self.units) + + if isinstance(getattr(other, '_magnitude', other), ndarray): + # arrays are refused as exponent, because they would create + # len(array) quanitites of len(set(array)) different units + if np.size(other) > 1: + raise DimensionalityError(self.units, 'dimensionless') + + new_self = self + if other == 1: + return self + elif other == 0: + self._units = UnitsContainer() + else: + if not self._is_multiplicative: + if self._REGISTRY.autoconvert_offset_to_baseunit: + self.ito_base_units() + else: + raise OffsetUnitCalculusError(self.units) + + if getattr(other, 'dimensionless', False): + other = other.to_base_units() + self._units **= other.magnitude + elif not getattr(other, 'dimensionless', True): + raise DimensionalityError(self.units, 'dimensionless') + else: + self._units **= other + + self._magnitude **= _to_magnitude(other, self.force_ndarray) + return self + + def __pow__(self, other): + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + else: + if not self._ok_for_muldiv: + raise OffsetUnitCalculusError(self.units) + + if isinstance(getattr(other, '_magnitude', other), ndarray): + # arrays are refused as exponent, because they would create + # len(array) quantities of len(set(array)) different units + if np.size(other) > 1: + raise DimensionalityError(self.units, 'dimensionless') + + new_self = self + if other == 1: + return self + elif other == 0: + units = UnitsContainer() + else: + if not self._is_multiplicative: + if self._REGISTRY.autoconvert_offset_to_baseunit: + new_self = self.to_base_units() + else: + raise OffsetUnitCalculusError(self.units) + + if getattr(other, 'dimensionless', False): + units = new_self._units ** other.to_base_units().magnitude + elif not getattr(other, 'dimensionless', True): + raise DimensionalityError(self.units, 'dimensionless') + else: + units = new_self._units ** other + + magnitude = new_self._magnitude ** _to_magnitude(other, self.force_ndarray) + return self.__class__(magnitude, units) + + def __rpow__(self, other): + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + else: + if not self.dimensionless: + raise DimensionalityError(self.units, 'dimensionless') + if isinstance(self._magnitude, ndarray): + if np.size(self._magnitude) > 1: + raise DimensionalityError(self.units, 'dimensionless') + new_self = self.to_base_units() + return other**new_self._magnitude + + 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): + # We compare to the base class of Quantity because + # each Quantity class is unique. + if not isinstance(other, _Quantity): + return (self.dimensionless and + _eq(self._convert_magnitude(UnitsContainer()), other, False)) + + if _eq(self._magnitude, 0, True) and _eq(other._magnitude, 0, True): + return self.dimensionality == other.dimensionality + + if self._units == other._units: + return _eq(self._magnitude, other._magnitude, False) + + try: + return _eq(self.to(other).magnitude, other._magnitude, False) + except DimensionalityError: + return False + + def __ne__(self, other): + out = self.__eq__(other) + if isinstance(out, ndarray): + return np.logical_not(out) + return not out + + def compare(self, other, op): + if not isinstance(other, self.__class__): + if self.dimensionless: + return op(self._convert_magnitude_not_inplace(UnitsContainer()), other) + else: + raise ValueError('Cannot compare Quantity and {0}'.format(type(other))) + + if self.units == other.units: + return op(self._magnitude, other._magnitude) + if self.dimensionality != other.dimensionality: + raise DimensionalityError(self.units, other.units, + self.dimensionality, other.dimensionality) + return op(self.to_base_units().magnitude, + other.to_base_units().magnitude) + + __lt__ = lambda self, other: self.compare(other, op=operator.lt) + __le__ = lambda self, other: self.compare(other, op=operator.le) + __ge__ = lambda self, other: self.compare(other, op=operator.ge) + __gt__ = lambda self, other: self.compare(other, op=operator.gt) + + def __bool__(self): + return bool(self._magnitude) + + __nonzero__ = __bool__ + + # NumPy Support + __radian = 'radian' + __same_units = 'equal greater greater_equal less less_equal not_equal arctan2'.split() + #: Dictionary mapping ufunc/attributes names to the units that they + #: require (conversion will be tried). + __require_units = {'cumprod': '', + 'arccos': '', 'arcsin': '', 'arctan': '', + 'arccosh': '', 'arcsinh': '', 'arctanh': '', + 'exp': '', 'expm1': '', 'exp2': '', + 'log': '', 'log10': '', 'log1p': '', 'log2': '', + 'sin': __radian, 'cos': __radian, 'tan': __radian, + 'sinh': __radian, 'cosh': __radian, 'tanh': __radian, + 'radians': 'degree', 'degrees': __radian, + 'deg2rad': 'degree', 'rad2deg': __radian, + 'logaddexp': '', 'logaddexp2': ''} + + #: Dictionary mapping ufunc/attributes names to the units that they + #: will set on output. + __set_units = {'cos': '', 'sin': '', 'tan': '', + 'cosh': '', 'sinh': '', 'tanh': '', + 'arccos': __radian, 'arcsin': __radian, + 'arctan': __radian, 'arctan2': __radian, + 'arccosh': __radian, 'arcsinh': __radian, + 'arctanh': __radian, + 'degrees': 'degree', 'radians': __radian, + 'expm1': '', 'cumprod': '', + 'rad2deg': 'degree', 'deg2rad': __radian} + + #: List of ufunc/attributes names in which units are copied from the + #: original. + __copy_units = 'compress conj conjugate copy cumsum diagonal flatten ' \ + 'max mean min ptp ravel repeat reshape round ' \ + 'squeeze std sum take trace transpose ' \ + 'ceil floor hypot rint ' \ + 'add subtract ' \ + 'copysign nextafter trunc ' \ + 'frexp ldexp modf modf__1 ' \ + 'absolute negative remainder fmod mod'.split() + + #: Dictionary mapping ufunc/attributes names to the units that they will + #: set on output. The value is interpreted as the power to which the unit + #: will be raised. + __prod_units = {'var': 2, 'prod': 'size', 'multiply': 'mul', + 'true_divide': 'div', 'divide': 'div', 'floor_divide': 'div', + 'remainder': 'div', + 'sqrt': .5, 'square': 2, 'reciprocal': -1} + + __skip_other_args = 'ldexp multiply ' \ + 'true_divide divide floor_divide fmod mod ' \ + 'remainder'.split() + + __handled = tuple(__same_units) + \ + tuple(__require_units.keys()) + \ + tuple(__prod_units.keys()) + \ + tuple(__copy_units) + tuple(__skip_other_args) + + def clip(self, first=None, second=None, out=None, **kwargs): + min = kwargs.get('min', first) + max = kwargs.get('max', second) + + if min is None and max is None: + raise TypeError('clip() takes at least 3 arguments (2 given)') + + if max is None and 'min' not in kwargs: + min, max = max, min + + kwargs = {'out': out} + + if min is not None: + if isinstance(min, self.__class__): + kwargs['min'] = min.to(self).magnitude + elif self.dimensionless: + kwargs['min'] = min + else: + raise DimensionalityError('dimensionless', self.units) + + if max is not None: + if isinstance(max, self.__class__): + kwargs['max'] = max.to(self).magnitude + elif self.dimensionless: + kwargs['max'] = max + else: + raise DimensionalityError('dimensionless', self.units) + + return self.__class__(self.magnitude.clip(**kwargs), self._units) + + def fill(self, value): + self._units = value.units + return self.magnitude.fill(value.magnitude) + + def put(self, indices, values, mode='raise'): + if isinstance(values, self.__class__): + values = values.to(self).magnitude + elif self.dimensionless: + values = self.__class__(values, '').to(self) + else: + raise DimensionalityError('dimensionless', self.units) + self.magnitude.put(indices, values, mode) + + @property + def real(self): + return self.__class__(self._magnitude.real, self.units) + + @property + def imag(self): + return self.__class__(self._magnitude.imag, self.units) + + @property + def T(self): + return self.__class__(self._magnitude.T, self.units) + + def searchsorted(self, v, side='left'): + if isinstance(v, self.__class__): + v = v.to(self).magnitude + elif self.dimensionless: + v = self.__class__(v, '').to(self) + else: + raise DimensionalityError('dimensionless', self.units) + return self.magnitude.searchsorted(v, side) + + def __ito_if_needed(self, to_units): + if self.unitless and to_units == 'radian': + return + + self.ito(to_units) + + def __numpy_method_wrap(self, func, *args, **kwargs): + """Convenience method to wrap on the fly numpy method taking + care of the units. + """ + if func.__name__ in self.__require_units: + self.__ito_if_needed(self.__require_units[func.__name__]) + + value = func(*args, **kwargs) + + if func.__name__ in self.__copy_units: + return self.__class__(value, self._units) + + if func.__name__ in self.__prod_units: + tmp = self.__prod_units[func.__name__] + if tmp == 'size': + return self.__class__(value, self._units ** self._magnitude.size) + return self.__class__(value, self._units ** tmp) + + return value + + def __len__(self): + return len(self._magnitude) + + def __iter__(self): + # Allow exception to propagate in case of non-iterable magnitude + it_mag = iter(self.magnitude) + return iter((self.__class__(mag, self._units) for mag in it_mag)) + + def __getattr__(self, item): + # Attributes starting with `__array_` are common attributes of NumPy ndarray. + # They are requested by numpy functions. + if item.startswith('__array_'): + if isinstance(self._magnitude, ndarray): + return getattr(self._magnitude, item) + else: + # If an `__array_` attributes is requested but the magnitude is not an ndarray, + # we convert the magnitude to a numpy ndarray. + self._magnitude = _to_magnitude(self._magnitude, force_ndarray=True) + return getattr(self._magnitude, item) + elif item in self.__handled: + if not isinstance(self._magnitude, ndarray): + self._magnitude = _to_magnitude(self._magnitude, True) + attr = getattr(self._magnitude, item) + if callable(attr): + return functools.partial(self.__numpy_method_wrap, attr) + return attr + try: + return getattr(self._magnitude, item) + except AttributeError as ex: + raise AttributeError("Neither Quantity object nor its magnitude ({0})" + "has attribute '{1}'".format(self._magnitude, item)) + + def __getitem__(self, key): + try: + value = self._magnitude[key] + return self.__class__(value, self._units) + except TypeError: + raise TypeError("Neither Quantity object nor its magnitude ({0})" + "supports indexing".format(self._magnitude)) + + def __setitem__(self, key, value): + try: + if math.isnan(value): + self._magnitude[key] = value + return + except (TypeError, DimensionalityError): + pass + + try: + if isinstance(value, self.__class__): + factor = self.__class__(value.magnitude, value.units / self.units).to_base_units() + else: + factor = self.__class__(value, self._units ** (-1)).to_base_units() + + if isinstance(factor, self.__class__): + if not factor.dimensionless: + raise DimensionalityError(value, self.units, + extra_msg='. Assign a quantity with the same dimensionality or ' + 'access the magnitude directly as ' + '`obj.magnitude[%s] = %s`' % (key, value)) + self._magnitude[key] = factor.magnitude + else: + self._magnitude[key] = factor + + except TypeError: + raise TypeError("Neither Quantity object nor its magnitude ({0})" + "supports indexing".format(self._magnitude)) + + def tolist(self): + units = self._units + return [self.__class__(value, units).tolist() if isinstance(value, list) else self.__class__(value, units) + for value in self._magnitude.tolist()] + + __array_priority__ = 17 + + def __array_prepare__(self, obj, context=None): + # If this uf is handled by Pint, write it down in the handling dictionary. + + # name of the ufunc, argument of the ufunc, domain of the ufunc + # In ufuncs with multiple outputs, domain indicates which output + # is currently being prepared (eg. see modf). + # In ufuncs with a single output, domain is 0 + uf, objs, huh = context + + if uf.__name__ in self.__handled and huh == 0: + # Only one ufunc should be handled at a time. + # If a ufunc is already being handled (and this is not another domain), + # something is wrong.. + if self.__handling: + raise Exception('Cannot handled nested ufuncs.\n' + 'Current: {0}\n' + 'New: {1}'.format(context, self.__handling)) + self.__handling = context + + return obj + + def __array_wrap__(self, obj, context=None): + uf, objs, huh = context + + # if this ufunc is not handled by Pint, pass it to the magnitude. + if uf.__name__ not in self.__handled: + return self.magnitude.__array_wrap__(obj, context) + + try: + ufname = uf.__name__ if huh == 0 else '{0}__{1}'.format(uf.__name__, huh) + + # First, we check the units of the input arguments. + + if huh == 0: + # Do this only when the wrap is called for the first ouput. + + # Store the destination units + dst_units = None + # List of magnitudes of Quantities with the right units + # to be used as argument of the ufunc + mobjs = None + + if uf.__name__ in self.__require_units: + # ufuncs in __require_units + # require specific units + # This is more complex that it should be due to automatic + # conversion between radians/dimensionless + # TODO: maybe could be simplified using Contexts + dst_units = self.__require_units[uf.__name__] + if dst_units == 'radian': + mobjs = [] + for other in objs: + unt = getattr(other, 'units', '') + if unt == 'radian': + mobjs.append(getattr(other, 'magnitude', other)) + else: + factor, units = self._REGISTRY.get_base_units(unt) + if units and units != UnitsContainer({'radian': 1}): + raise DimensionalityError(units, dst_units) + mobjs.append(getattr(other, 'magnitude', other) * factor) + mobjs = tuple(mobjs) + else: + dst_units = self._REGISTRY.parse_expression(dst_units).units + + elif len(objs) > 1 and uf.__name__ not in self.__skip_other_args: + # ufunc with multiple arguments require that all inputs have + # the same arguments unless they are in __skip_other_args + dst_units = objs[0].units + + # Do the conversion (if needed) and extract the magnitude for each input. + if mobjs is None: + if dst_units is not None: + mobjs = tuple(self._REGISTRY.convert(getattr(other, 'magnitude', other), + getattr(other, 'units', ''), + dst_units) + for other in objs) + else: + mobjs = tuple(getattr(other, 'magnitude', other) + for other in objs) + + # call the ufunc + out = uf(*mobjs) + + # If there are multiple outputs, + # store them in __handling (uf, objs, huh, out0, out1, ...) + # and return the first + if uf.nout > 1: + self.__handling += out + out = out[0] + else: + # If this is not the first output, + # just grab the result that was previously calculated. + out = self.__handling[3 + huh] + + + # Second, we set the units of the output value. + if ufname in self.__set_units: + try: + out = self.__class__(out, self.__set_units[ufname]) + except: + raise _Exception(ValueError) + elif ufname in self.__copy_units: + try: + out = self.__class__(out, self.units) + except: + raise _Exception(ValueError) + elif ufname in self.__prod_units: + tmp = self.__prod_units[ufname] + if tmp == 'size': + out = self.__class__(out, self.units ** self._magnitude.size) + elif tmp == 'div': + units1 = objs[0].units if isinstance(objs[0], self.__class__) else UnitsContainer() + units2 = objs[1].units if isinstance(objs[1], self.__class__) else UnitsContainer() + out = self.__class__(out, units1 / units2) + elif tmp == 'mul': + units1 = objs[0].units if isinstance(objs[0], self.__class__) else UnitsContainer() + units2 = objs[1].units if isinstance(objs[1], self.__class__) else UnitsContainer() + out = self.__class__(out, units1 * units2) + else: + out = self.__class__(out, self.units ** tmp) + + return out + except (DimensionalityError, UndefinedUnitError) as ex: + raise ex + except _Exception as ex: + raise ex.internal + except Exception as ex: + print(ex) + finally: + # If this is the last output argument for the ufunc, + # we are done handling this ufunc. + if uf.nout == huh + 1: + self.__handling = None + + return self.magnitude.__array_wrap__(obj, context) + + # Measurement support + def plus_minus(self, error, relative=False): + if isinstance(error, self.__class__): + if relative: + raise ValueError('{} is not a valid relative error.'.format(error)) + error = error.to(self.units).magnitude + else: + if relative: + error = error * abs(self.magnitude) + + return self._REGISTRY.Measurement(copy.copy(self.magnitude), error, self.units) + + # methods/properties that help for math operations with offset units + @property + def _is_multiplicative(self): + """Check if the Quantity object has only multiplicative units. + """ + # XXX Turn this into a method/property of _Quantity? + return not self._get_non_multiplicative_units() + + def _get_non_multiplicative_units(self): + """Return a list of the of non-multiplicative units of the Quantity object + """ + offset_units = [unit for unit in self.units.keys() + if not self._REGISTRY._units[unit].is_multiplicative] + return offset_units + + def _get_delta_units(self): + """Return list of delta units ot the Quantity object + """ + delta_units = [u for u in self.units.keys() if u.startswith("delta_")] + return delta_units + + def _has_compatible_delta(self, unit): + """"Check if Quantity object has a delta_unit that is compatible with unit + """ + deltas = self._get_delta_units() + if 'delta_' + unit in deltas: + return True + else: # Look for delta units with same dimension as the offset unit + offset_unit_dim = self._REGISTRY._units[unit].reference + for d in deltas: + if self._REGISTRY._units[d].reference == offset_unit_dim: + return True + return False + + def _ok_for_muldiv(self, no_offset_units=None): + """Checks if Quantity object can be multiplied or divided + + :q: quantity object that is checked + :no_offset_units: number of offset units in q + """ + is_ok = True + if no_offset_units is None: + no_offset_units = len(self._get_non_multiplicative_units()) + if no_offset_units > 1: + is_ok = False + if no_offset_units == 1: + if len(self._units) > 1: + is_ok = False + if (len(self._units) == 1 + and not self._REGISTRY.autoconvert_offset_to_baseunit): + is_ok = False + if next(iter(self._units.values())) != 1: + is_ok = False + return is_ok diff --git a/CadQuery/Libs/pint/testsuite/__init__.py b/CadQuery/Libs/pint/testsuite/__init__.py new file mode 100644 index 0000000..7efab13 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/__init__.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import os +import logging +from contextlib import contextmanager + +from pint.compat import ndarray, unittest, np + +from pint import logger, UnitRegistry +from pint.quantity import _Quantity +from logging.handlers import BufferingHandler + + +class TestHandler(BufferingHandler): + + def __init__(self, only_warnings=False): + # BufferingHandler takes a "capacity" argument + # so as to know when to flush. As we're overriding + # shouldFlush anyway, we can set a capacity of zero. + # You can call flush() manually to clear out the + # buffer. + self.only_warnings = only_warnings + BufferingHandler.__init__(self, 0) + + def shouldFlush(self): + return False + + def emit(self, record): + if self.only_warnings and record.level != logging.WARNING: + return + self.buffer.append(record.__dict__) + + +class BaseTestCase(unittest.TestCase): + + CHECK_NO_WARNING = True + + @contextmanager + def capture_log(self, level=logging.DEBUG): + th = TestHandler() + th.setLevel(level) + logger.addHandler(th) + if self._test_handler is not None: + l = len(self._test_handler.buffer) + yield th.buffer + if self._test_handler is not None: + self._test_handler.buffer = self._test_handler.buffer[:l] + + def setUp(self): + self._test_handler = None + if self.CHECK_NO_WARNING: + self._test_handler = th = TestHandler() + th.setLevel(logging.WARNING) + logger.addHandler(th) + + def tearDown(self): + if self._test_handler is not None: + buf = self._test_handler.buffer + l = len(buf) + msg = '\n'.join(record.get('msg', str(record)) for record in buf) + self.assertEqual(l, 0, msg='%d warnings raised.\n%s' % (l, msg)) + + +class QuantityTestCase(BaseTestCase): + + FORCE_NDARRAY = False + + @classmethod + def setUpClass(cls): + cls.ureg = UnitRegistry(force_ndarray=cls.FORCE_NDARRAY) + cls.Q_ = cls.ureg.Quantity + + def _get_comparable_magnitudes(self, first, second, msg): + if isinstance(first, _Quantity) and isinstance(second, _Quantity): + second = second.to(first) + self.assertEqual(first.units, second.units, msg=msg + 'Units are not equal.') + m1, m2 = first.magnitude, second.magnitude + elif isinstance(first, _Quantity): + self.assertTrue(first.dimensionless, msg=msg + 'The first is not dimensionless.') + first = first.to('') + m1, m2 = first.magnitude, second + elif isinstance(second, _Quantity): + self.assertTrue(second.dimensionless, msg=msg + 'The second is not dimensionless.') + second = second.to('') + m1, m2 = first, second.magnitude + else: + m1, m2 = first, second + + return m1, m2 + + def assertQuantityEqual(self, first, second, msg=None): + if msg is None: + msg = 'Comparing %r and %r. ' % (first, second) + + m1, m2 = self._get_comparable_magnitudes(first, second, msg) + + if isinstance(m1, ndarray) or isinstance(m2, ndarray): + np.testing.assert_array_equal(m1, m2, err_msg=msg) + else: + unittest.TestCase.assertEqual(self, m1, m2, msg) + + def assertQuantityAlmostEqual(self, first, second, rtol=1e-07, atol=0, msg=None): + if msg is None: + msg = 'Comparing %r and %r. ' % (first, second) + + m1, m2 = self._get_comparable_magnitudes(first, second, msg) + + if isinstance(m1, ndarray) or isinstance(m2, ndarray): + np.testing.assert_allclose(m1, m2, rtol=rtol, atol=atol, err_msg=msg) + else: + self.assertLessEqual(abs(m1 - m2), atol + rtol * abs(m2)) + + +def testsuite(): + """A testsuite that has all the pint tests. + """ + return unittest.TestLoader().discover(os.path.dirname(__file__)) + + +def main(): + """Runs the testsuite as command line application. + """ + try: + unittest.main() + except Exception as e: + print('Error: %s' % e) + + +def run(): + """Run all tests. + + :return: a :class:`unittest.TestResult` object + """ + test_runner = unittest.TextTestRunner() + return test_runner.run(testsuite()) + diff --git a/CadQuery/Libs/pint/testsuite/helpers.py b/CadQuery/Libs/pint/testsuite/helpers.py new file mode 100644 index 0000000..6e6d323 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/helpers.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +from pint.compat import unittest, HAS_NUMPY, HAS_UNCERTAINTIES, NUMPY_VER, PYTHON3 + + +def requires_numpy18(): + return unittest.skipUnless(NUMPY_VER >= '1.8', 'Requires NumPy >= 1.8') + + +def requires_numpy(): + return unittest.skipUnless(HAS_NUMPY, 'Requires NumPy') + + +def requires_not_numpy(): + return unittest.skipIf(HAS_NUMPY, 'Requires NumPy is not installed.') + + +def requires_uncertainties(): + return unittest.skipUnless(HAS_UNCERTAINTIES, 'Requires Uncertainties') + + +def requires_not_uncertainties(): + return unittest.skipIf(HAS_UNCERTAINTIES, 'Requires Uncertainties is not installed.') + + +def requires_python2(): + return unittest.skipIf(PYTHON3, 'Requires Python 2.X.') + + +def requires_python3(): + return unittest.skipUnless(PYTHON3, 'Requires Python 3.X.') diff --git a/CadQuery/Libs/pint/testsuite/parameterized.py b/CadQuery/Libs/pint/testsuite/parameterized.py new file mode 100644 index 0000000..0b452a6 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/parameterized.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# +# Adds Parameterized tests for Python's unittest module +# +# Code from: parameterizedtestcase, version: 0.1.0 +# Homepage: https://github.com/msabramo/python_unittest_parameterized_test_case +# Author: Marc Abramowitz, email: marc@marc-abramowitz.com +# License: MIT +# +# Fixed for to work in Python 2 & 3 with "add_metaclass" decorator from six +# https://pypi.python.org/pypi/six +# Author: Benjamin Peterson +# License: MIT +# +# Use like this: +# +# from parameterizedtestcase import ParameterizedTestCase +# +# class MyTests(ParameterizedTestCase): +# @ParameterizedTestCase.parameterize( +# ("input", "expected_output"), +# [ +# ("2+4", 6), +# ("3+5", 8), +# ("6*9", 54), +# ] +# ) +# def test_eval(self, input, expected_output): +# self.assertEqual(eval(input), expected_output) + +try: + import unittest2 as unittest +except ImportError: # pragma: no cover + import unittest + +from functools import wraps +import collections + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def augment_method_docstring(method, new_class_dict, classname, + param_names, param_values, new_method): + param_assignments_str = '; '.join( + ['%s = %s' % (k, v) for (k, v) in zip(param_names, param_values)]) + extra_doc = "%s (%s.%s) [with %s] " % ( + method.__name__, new_class_dict.get('__module__', ''), + classname, param_assignments_str) + + try: + new_method.__doc__ = extra_doc + new_method.__doc__ + except TypeError: # Catches when new_method.__doc__ is None + new_method.__doc__ = extra_doc + + +class ParameterizedTestCaseMetaClass(type): + method_counter = {} + + def __new__(meta, classname, bases, class_dict): + new_class_dict = {} + + for attr_name, attr_value in list(class_dict.items()): + if isinstance(attr_value, collections.Callable) and hasattr(attr_value, 'param_names'): + # print("Processing attr_name = %r; attr_value = %r" % ( + # attr_name, attr_value)) + + method = attr_value + param_names = attr_value.param_names + data = attr_value.data + func_name_format = attr_value.func_name_format + + meta.process_method( + classname, method, param_names, data, new_class_dict, + func_name_format) + else: + new_class_dict[attr_name] = attr_value + + return type.__new__(meta, classname, bases, new_class_dict) + + @classmethod + def process_method( + cls, classname, method, param_names, data, new_class_dict, + func_name_format): + method_counter = cls.method_counter + + for param_values in data: + new_method = cls.new_method(method, param_values) + method_counter[method.__name__] = \ + method_counter.get(method.__name__, 0) + 1 + case_data = dict(list(zip(param_names, param_values))) + case_data['func_name'] = method.__name__ + case_data['case_num'] = method_counter[method.__name__] + + new_method.__name__ = func_name_format.format(**case_data) + + augment_method_docstring( + method, new_class_dict, classname, + param_names, param_values, new_method) + new_class_dict[new_method.__name__] = new_method + + @classmethod + def new_method(cls, method, param_values): + @wraps(method) + def new_method(self): + return method(self, *param_values) + + return new_method + +@add_metaclass(ParameterizedTestCaseMetaClass) +class ParameterizedTestMixin(object): + @classmethod + def parameterize(cls, param_names, data, + func_name_format='{func_name}_{case_num:05d}'): + """Decorator for parameterizing a test method - example: + + @ParameterizedTestCase.parameterize( + ("isbn", "expected_title"), [ + ("0262033844", "Introduction to Algorithms"), + ("0321558146", "Campbell Essential Biology")]) + + """ + + def decorator(func): + @wraps(func) + def newfunc(*arg, **kwargs): + return func(*arg, **kwargs) + + newfunc.param_names = param_names + newfunc.data = data + newfunc.func_name_format = func_name_format + + return newfunc + + return decorator + +@add_metaclass(ParameterizedTestCaseMetaClass) +class ParameterizedTestCase(unittest.TestCase, ParameterizedTestMixin): + pass diff --git a/CadQuery/Libs/pint/testsuite/test_contexts.py b/CadQuery/Libs/pint/testsuite/test_contexts.py new file mode 100644 index 0000000..b75ed7d --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_contexts.py @@ -0,0 +1,632 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import itertools +from collections import defaultdict + +from pint import UnitRegistry +from pint.context import Context, _freeze +from pint.unit import UnitsContainer +from pint.testsuite import QuantityTestCase + + +def add_ctxs(ureg): + a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}) + d = Context('lc') + d.add_transformation(a, b, lambda ureg, x: ureg.speed_of_light / x) + d.add_transformation(b, a, lambda ureg, x: ureg.speed_of_light / x) + + ureg.add_context(d) + + a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': -1}) + d = Context('ab') + d.add_transformation(a, b, lambda ureg, x: 1 / x) + d.add_transformation(b, a, lambda ureg, x: 1 / x) + + ureg.add_context(d) + + +def add_arg_ctxs(ureg): + a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}) + d = Context('lc') + d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n) + d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n) + + ureg.add_context(d) + + a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': -1}) + d = Context('ab') + d.add_transformation(a, b, lambda ureg, x: 1 / x) + d.add_transformation(b, a, lambda ureg, x: 1 / x) + + ureg.add_context(d) + + +def add_argdef_ctxs(ureg): + a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}) + d = Context('lc', defaults=dict(n=1)) + assert d.defaults == dict(n=1) + + d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n) + d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n) + + ureg.add_context(d) + + a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': -1}) + d = Context('ab') + d.add_transformation(a, b, lambda ureg, x: 1 / x) + d.add_transformation(b, a, lambda ureg, x: 1 / x) + + ureg.add_context(d) + + +def add_sharedargdef_ctxs(ureg): + a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1}) + d = Context('lc', defaults=dict(n=1)) + assert d.defaults == dict(n=1) + + d.add_transformation(a, b, lambda ureg, x, n: ureg.speed_of_light / x / n) + d.add_transformation(b, a, lambda ureg, x, n: ureg.speed_of_light / x / n) + + ureg.add_context(d) + + a, b = UnitsContainer({'[length]': 1}), UnitsContainer({'[current]': 1}) + d = Context('ab', defaults=dict(n=0)) + d.add_transformation(a, b, lambda ureg, x, n: ureg.ampere * ureg.meter * n / x) + d.add_transformation(b, a, lambda ureg, x, n: ureg.ampere * ureg.meter * n / x) + + ureg.add_context(d) + + +class TestContexts(QuantityTestCase): + + def test_freeze(self): + self.assertEqual(_freeze('meter'), frozenset([('meter', 1)])) + self.assertEqual(_freeze('meter/second'), frozenset((('meter', 1), ('second', -1)))) + x = frozenset((('meter', 1))) + self.assertIs(_freeze(x), x) + self.assertEqual(_freeze({'meter': 1}), + frozenset([('meter', 1)])) + self.assertEqual(_freeze({'meter': -1, 'second': -1}), + frozenset((('meter', -1), ('second', -1)))) + + + def test_known_context(self): + ureg = UnitRegistry() + add_ctxs(ureg) + with ureg.context('lc'): + self.assertTrue(ureg._active_ctx) + self.assertTrue(ureg._active_ctx.graph) + + self.assertFalse(ureg._active_ctx) + self.assertFalse(ureg._active_ctx.graph) + + with ureg.context('lc', n=1): + self.assertTrue(ureg._active_ctx) + self.assertTrue(ureg._active_ctx.graph) + + self.assertFalse(ureg._active_ctx) + self.assertFalse(ureg._active_ctx.graph) + + def test_known_context_enable(self): + ureg = UnitRegistry() + add_ctxs(ureg) + ureg.enable_contexts('lc') + self.assertTrue(ureg._active_ctx) + self.assertTrue(ureg._active_ctx.graph) + ureg.disable_contexts(1) + + self.assertFalse(ureg._active_ctx) + self.assertFalse(ureg._active_ctx.graph) + + ureg.enable_contexts('lc', n=1) + self.assertTrue(ureg._active_ctx) + self.assertTrue(ureg._active_ctx.graph) + ureg.disable_contexts(1) + + self.assertFalse(ureg._active_ctx) + self.assertFalse(ureg._active_ctx.graph) + + def test_graph(self): + ureg = UnitRegistry() + add_ctxs(ureg) + l = _freeze({'[length]': 1.}) + t = _freeze({'[time]': -1.}) + c = _freeze({'[current]': -1.}) + + g_sp = defaultdict(set) + g_sp.update({l: set((t,)), + t: set((l,))}) + + g_ab = defaultdict(set) + g_ab.update({l: set((c,)), + c: set((l,))}) + + g = defaultdict(set) + g.update({l: set((t, c)), + t: set((l,)), + c: set((l,))}) + + with ureg.context('lc'): + self.assertEqual(ureg._active_ctx.graph, g_sp) + + with ureg.context('lc', n=1): + self.assertEqual(ureg._active_ctx.graph, g_sp) + + with ureg.context('ab'): + self.assertEqual(ureg._active_ctx.graph, g_ab) + + with ureg.context('lc'): + with ureg.context('ab'): + self.assertEqual(ureg._active_ctx.graph, g) + + with ureg.context('ab'): + with ureg.context('lc'): + self.assertEqual(ureg._active_ctx.graph, g) + + with ureg.context('lc', 'ab'): + self.assertEqual(ureg._active_ctx.graph, g) + + with ureg.context('ab', 'lc'): + self.assertEqual(ureg._active_ctx.graph, g) + + def test_graph_enable(self): + ureg = UnitRegistry() + add_ctxs(ureg) + l = _freeze({'[length]': 1.}) + t = _freeze({'[time]': -1.}) + c = _freeze({'[current]': -1.}) + + g_sp = defaultdict(set) + g_sp.update({l: set((t,)), + t: set((l,))}) + + g_ab = defaultdict(set) + g_ab.update({l: set((c,)), + c: set((l,))}) + + g = defaultdict(set) + g.update({l: set((t, c)), + t: set((l,)), + c: set((l,))}) + + ureg.enable_contexts('lc') + self.assertEqual(ureg._active_ctx.graph, g_sp) + ureg.disable_contexts(1) + + ureg.enable_contexts('lc', n=1) + self.assertEqual(ureg._active_ctx.graph, g_sp) + ureg.disable_contexts(1) + + ureg.enable_contexts('ab') + self.assertEqual(ureg._active_ctx.graph, g_ab) + ureg.disable_contexts(1) + + ureg.enable_contexts('lc') + ureg.enable_contexts('ab') + self.assertEqual(ureg._active_ctx.graph, g) + ureg.disable_contexts(2) + + ureg.enable_contexts('ab') + ureg.enable_contexts('lc') + self.assertEqual(ureg._active_ctx.graph, g) + ureg.disable_contexts(2) + + ureg.enable_contexts('lc', 'ab') + self.assertEqual(ureg._active_ctx.graph, g) + ureg.disable_contexts(2) + + ureg.enable_contexts('ab', 'lc') + self.assertEqual(ureg._active_ctx.graph, g) + ureg.disable_contexts(2) + + def test_known_nested_context(self): + ureg = UnitRegistry() + add_ctxs(ureg) + + with ureg.context('lc'): + x = dict(ureg._active_ctx) + y = dict(ureg._active_ctx.graph) + self.assertTrue(ureg._active_ctx) + self.assertTrue(ureg._active_ctx.graph) + + with ureg.context('ab'): + self.assertTrue(ureg._active_ctx) + self.assertTrue(ureg._active_ctx.graph) + self.assertNotEqual(x, ureg._active_ctx) + self.assertNotEqual(y, ureg._active_ctx.graph) + + self.assertEqual(x, ureg._active_ctx) + self.assertEqual(y, ureg._active_ctx.graph) + + self.assertFalse(ureg._active_ctx) + self.assertFalse(ureg._active_ctx.graph) + + def test_unknown_context(self): + ureg = UnitRegistry() + add_ctxs(ureg) + try: + with ureg.context('la'): + pass + except KeyError as e: + value = True + except Exception as e: + value = False + self.assertTrue(value) + self.assertFalse(ureg._active_ctx) + self.assertFalse(ureg._active_ctx.graph) + + def test_unknown_nested_context(self): + ureg = UnitRegistry() + add_ctxs(ureg) + + with ureg.context('lc'): + x = dict(ureg._active_ctx) + y = dict(ureg._active_ctx.graph) + try: + with ureg.context('la'): + pass + except KeyError as e: + value = True + except Exception as e: + value = False + + self.assertTrue(value) + + self.assertEqual(x, ureg._active_ctx) + self.assertEqual(y, ureg._active_ctx.graph) + + self.assertFalse(ureg._active_ctx) + self.assertFalse(ureg._active_ctx.graph) + + + def test_one_context(self): + ureg = UnitRegistry() + + add_ctxs(ureg) + + q = 500 * ureg.meter + s = (ureg.speed_of_light / q).to('Hz') + + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc'): + self.assertEqual(q.to('Hz'), s) + self.assertRaises(ValueError, q.to, 'Hz') + + def test_multiple_context(self): + ureg = UnitRegistry() + + add_ctxs(ureg) + + q = 500 * ureg.meter + s = (ureg.speed_of_light / q).to('Hz') + + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc', 'ab'): + self.assertEqual(q.to('Hz'), s) + self.assertRaises(ValueError, q.to, 'Hz') + + def test_nested_context(self): + ureg = UnitRegistry() + + add_ctxs(ureg) + + q = 500 * ureg.meter + s = (ureg.speed_of_light / q).to('Hz') + + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc'): + self.assertEqual(q.to('Hz'), s) + with ureg.context('ab'): + self.assertEqual(q.to('Hz'), s) + self.assertEqual(q.to('Hz'), s) + + with ureg.context('ab'): + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc'): + self.assertEqual(q.to('Hz'), s) + self.assertRaises(ValueError, q.to, 'Hz') + + def test_context_with_arg(self): + + ureg = UnitRegistry() + + add_arg_ctxs(ureg) + + q = 500 * ureg.meter + s = (ureg.speed_of_light / q).to('Hz') + + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc', n=1): + self.assertEqual(q.to('Hz'), s) + with ureg.context('ab'): + self.assertEqual(q.to('Hz'), s) + self.assertEqual(q.to('Hz'), s) + + with ureg.context('ab'): + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc', n=1): + self.assertEqual(q.to('Hz'), s) + self.assertRaises(ValueError, q.to, 'Hz') + + with ureg.context('lc'): + self.assertRaises(TypeError, q.to, 'Hz') + + def test_enable_context_with_arg(self): + + ureg = UnitRegistry() + + add_arg_ctxs(ureg) + + q = 500 * ureg.meter + s = (ureg.speed_of_light / q).to('Hz') + + self.assertRaises(ValueError, q.to, 'Hz') + ureg.enable_contexts('lc', n=1) + self.assertEqual(q.to('Hz'), s) + ureg.enable_contexts('ab') + self.assertEqual(q.to('Hz'), s) + self.assertEqual(q.to('Hz'), s) + ureg.disable_contexts(1) + ureg.disable_contexts(1) + + ureg.enable_contexts('ab') + self.assertRaises(ValueError, q.to, 'Hz') + ureg.enable_contexts('lc', n=1) + self.assertEqual(q.to('Hz'), s) + ureg.disable_contexts(1) + self.assertRaises(ValueError, q.to, 'Hz') + ureg.disable_contexts(1) + + ureg.enable_contexts('lc') + self.assertRaises(TypeError, q.to, 'Hz') + ureg.disable_contexts(1) + + + def test_context_with_arg_def(self): + + ureg = UnitRegistry() + + add_argdef_ctxs(ureg) + + q = 500 * ureg.meter + s = (ureg.speed_of_light / q).to('Hz') + + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc'): + self.assertEqual(q.to('Hz'), s) + with ureg.context('ab'): + self.assertEqual(q.to('Hz'), s) + self.assertEqual(q.to('Hz'), s) + + with ureg.context('ab'): + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc'): + self.assertEqual(q.to('Hz'), s) + self.assertRaises(ValueError, q.to, 'Hz') + + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc', n=2): + self.assertEqual(q.to('Hz'), s / 2) + with ureg.context('ab'): + self.assertEqual(q.to('Hz'), s / 2) + self.assertEqual(q.to('Hz'), s / 2) + + with ureg.context('ab'): + self.assertRaises(ValueError, q.to, 'Hz') + with ureg.context('lc', n=2): + self.assertEqual(q.to('Hz'), s / 2) + self.assertRaises(ValueError, q.to, 'Hz') + + + def test_context_with_sharedarg_def(self): + + ureg = UnitRegistry() + + add_sharedargdef_ctxs(ureg) + + q = 500 * ureg.meter + s = (ureg.speed_of_light / q).to('Hz') + u = (1 / 500) * ureg.ampere + + with ureg.context('lc'): + self.assertEqual(q.to('Hz'), s) + with ureg.context('ab'): + self.assertEqual(q.to('ampere'), u) + + with ureg.context('ab'): + self.assertEqual(q.to('ampere'), 0 * u) + with ureg.context('lc'): + self.assertRaises(ZeroDivisionError, ureg.Quantity.to, q, 'Hz') + + with ureg.context('lc', n=2): + self.assertEqual(q.to('Hz'), s / 2) + with ureg.context('ab'): + self.assertEqual(q.to('ampere'), 2 * u) + + with ureg.context('ab', n=3): + self.assertEqual(q.to('ampere'), 3 * u) + with ureg.context('lc'): + self.assertEqual(q.to('Hz'), s / 3) + + with ureg.context('lc', n=2): + self.assertEqual(q.to('Hz'), s / 2) + with ureg.context('ab', n=4): + self.assertEqual(q.to('ampere'), 4 * u) + + with ureg.context('ab', n=3): + self.assertEqual(q.to('ampere'), 3 * u) + with ureg.context('lc', n=6): + self.assertEqual(q.to('Hz'), s / 6) + + def _test_ctx(self, ctx): + ureg = UnitRegistry() + q = 500 * ureg.meter + s = (ureg.speed_of_light / q).to('Hz') + + nctx = len(ureg._contexts) + + self.assertNotIn(ctx.name, ureg._contexts) + ureg.add_context(ctx) + + self.assertIn(ctx.name, ureg._contexts) + self.assertEqual(len(ureg._contexts), nctx + 1 + len(ctx.aliases)) + + with ureg.context(ctx.name): + self.assertEqual(q.to('Hz'), s) + self.assertEqual(s.to('meter'), q) + + ureg.remove_context(ctx.name) + self.assertNotIn(ctx.name, ureg._contexts) + self.assertEqual(len(ureg._contexts), nctx) + + def test_parse_invalid(self): + s = ['@context longcontextname', + '[length] = 1 / [time]: c / value', + '1 / [time] = [length]: c / value'] + + self.assertRaises(ValueError, Context.from_lines, s) + + def test_parse_simple(self): + + a = Context.__keytransform__(UnitsContainer({'[time]': -1}), UnitsContainer({'[length]': 1})) + b = Context.__keytransform__(UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1})) + + s = ['@context longcontextname', + '[length] -> 1 / [time]: c / value', + '1 / [time] -> [length]: c / value'] + + c = Context.from_lines(s) + self.assertEqual(c.name, 'longcontextname') + self.assertEqual(c.aliases, ()) + self.assertEqual(c.defaults, {}) + self.assertEqual(set(c.funcs.keys()), set((a, b))) + self._test_ctx(c) + + s = ['@context longcontextname = lc', + '[length] <-> 1 / [time]: c / value'] + + c = Context.from_lines(s) + self.assertEqual(c.name, 'longcontextname') + self.assertEqual(c.aliases, ('lc', )) + self.assertEqual(c.defaults, {}) + self.assertEqual(set(c.funcs.keys()), set((a, b))) + self._test_ctx(c) + + s = ['@context longcontextname = lc = lcn', + '[length] <-> 1 / [time]: c / value'] + + c = Context.from_lines(s) + self.assertEqual(c.name, 'longcontextname') + self.assertEqual(c.aliases, ('lc', 'lcn', )) + self.assertEqual(c.defaults, {}) + self.assertEqual(set(c.funcs.keys()), set((a, b))) + self._test_ctx(c) + + def test_parse_auto_inverse(self): + + a = Context.__keytransform__(UnitsContainer({'[time]': -1.}), UnitsContainer({'[length]': 1.})) + b = Context.__keytransform__(UnitsContainer({'[length]': 1.}), UnitsContainer({'[time]': -1.})) + + s = ['@context longcontextname', + '[length] <-> 1 / [time]: c / value'] + + c = Context.from_lines(s) + self.assertEqual(c.defaults, {}) + self.assertEqual(set(c.funcs.keys()), set((a, b))) + self._test_ctx(c) + + def test_parse_define(self): + a = Context.__keytransform__(UnitsContainer({'[time]': -1}), UnitsContainer({'[length]': 1.})) + b = Context.__keytransform__(UnitsContainer({'[length]': 1}), UnitsContainer({'[time]': -1.})) + + s = ['@context longcontextname', + '[length] <-> 1 / [time]: c / value'] + c = Context.from_lines(s) + self.assertEqual(c.defaults, {}) + self.assertEqual(set(c.funcs.keys()), set((a, b))) + self._test_ctx(c) + + def test_parse_parameterized(self): + a = Context.__keytransform__(UnitsContainer({'[time]': -1.}), UnitsContainer({'[length]': 1.})) + b = Context.__keytransform__(UnitsContainer({'[length]': 1.}), UnitsContainer({'[time]': -1.})) + + s = ['@context(n=1) longcontextname', + '[length] <-> 1 / [time]: n * c / value'] + + c = Context.from_lines(s) + self.assertEqual(c.defaults, {'n': 1}) + self.assertEqual(set(c.funcs.keys()), set((a, b))) + self._test_ctx(c) + + s = ['@context(n=1, bla=2) longcontextname', + '[length] <-> 1 / [time]: n * c / value / bla'] + + c = Context.from_lines(s) + self.assertEqual(c.defaults, {'n': 1, 'bla': 2}) + self.assertEqual(set(c.funcs.keys()), set((a, b))) + + # If the variable is not present in the definition, then raise an error + s = ['@context(n=1) longcontextname', + '[length] <-> 1 / [time]: c / value'] + self.assertRaises(ValueError, Context.from_lines, s) + + def test_warnings(self): + + ureg = UnitRegistry() + + with self.capture_log() as buffer: + add_ctxs(ureg) + + d = Context('ab') + ureg.add_context(d) + + self.assertEqual(len(buffer), 1) + self.assertIn("ab", str(buffer[-1])) + + d = Context('ab1', aliases=('ab',)) + ureg.add_context(d) + + self.assertEqual(len(buffer), 2) + self.assertIn("ab", str(buffer[-1])) + + +class TestDefinedContexts(QuantityTestCase): + + FORCE_NDARRAY = False + + def test_defined(self): + ureg = self.ureg + with ureg.context('sp'): + pass + + a = Context.__keytransform__(UnitsContainer({'[time]': -1.}), UnitsContainer({'[length]': 1.})) + b = Context.__keytransform__(UnitsContainer({'[length]': 1.}), UnitsContainer({'[time]': -1.})) + self.assertIn(a, ureg._contexts['sp'].funcs) + self.assertIn(b, ureg._contexts['sp'].funcs) + with ureg.context('sp'): + self.assertIn(a, ureg._active_ctx) + self.assertIn(b, ureg._active_ctx) + + def test_spectroscopy(self): + ureg = self.ureg + eq = (532. * ureg.nm, 563.5 * ureg.terahertz, 2.33053 * ureg.eV) + with ureg.context('sp'): + from pint.util import find_shortest_path + for a, b in itertools.product(eq, eq): + for x in range(2): + if x == 1: + a = a.to_base_units() + b = b.to_base_units() + da, db = Context.__keytransform__(a.dimensionality, + b.dimensionality) + p = find_shortest_path(ureg._active_ctx.graph, da, db) + self.assertTrue(p) + msg = '{0} <-> {1}'.format(a, b) + # assertAlmostEqualRelError converts second to first + self.assertQuantityAlmostEqual(b, a, rtol=0.01, msg=msg) + + + for a, b in itertools.product(eq, eq): + self.assertQuantityAlmostEqual(a.to(b.units, 'sp'), b, rtol=0.01) diff --git a/CadQuery/Libs/pint/testsuite/test_formatter.py b/CadQuery/Libs/pint/testsuite/test_formatter.py new file mode 100644 index 0000000..9f2015b --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_formatter.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +from pint import formatting as fmt +from pint.testsuite import QuantityTestCase + + +class TestFormatter(QuantityTestCase): + + def test_join(self): + for empty in (tuple(), []): + self.assertEqual(fmt._join('s', empty), '') + self.assertEqual(fmt._join('*', '1 2 3'.split()), '1*2*3') + self.assertEqual(fmt._join('{0}*{1}', '1 2 3'.split()), '1*2*3') + + + def test_formatter(self): + self.assertEqual(fmt.formatter(dict().items()), '') + self.assertEqual(fmt.formatter(dict(meter=1).items()), 'meter') + self.assertEqual(fmt.formatter(dict(meter=-1).items()), '1 / meter') + self.assertEqual(fmt.formatter(dict(meter=-1).items(), as_ratio=False), 'meter ** -1') + + self.assertEqual(fmt.formatter(dict(meter=-1, second=-1).items(), as_ratio=False), + 'meter ** -1 * second ** -1') + self.assertEqual(fmt.formatter(dict(meter=-1, second=-1).items()), + '1 / meter / second') + self.assertEqual(fmt.formatter(dict(meter=-1, second=-1).items(), single_denominator=True), + '1 / (meter * second)') + self.assertEqual(fmt.formatter(dict(meter=-1, second=-2).items()), + '1 / meter / second ** 2') + self.assertEqual(fmt.formatter(dict(meter=-1, second=-2).items(), single_denominator=True), + '1 / (meter * second ** 2)') + + def test_parse_spec(self): + self.assertEqual(fmt._parse_spec(''), '') + self.assertEqual(fmt._parse_spec(''), '') + self.assertRaises(ValueError, fmt._parse_spec, 'W') + self.assertRaises(ValueError, fmt._parse_spec, 'PL') + + def test_format_unit(self): + self.assertEqual(fmt.format_unit('', 'C'), 'dimensionless') + self.assertRaises(ValueError, fmt.format_unit, 'm', 'W') diff --git a/CadQuery/Libs/pint/testsuite/test_issues.py b/CadQuery/Libs/pint/testsuite/test_issues.py new file mode 100644 index 0000000..ab14e21 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_issues.py @@ -0,0 +1,473 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import math + +from pint import UnitRegistry +from pint.unit import UnitsContainer +from pint.util import ParserHelper + +from pint.compat import np, unittest, long_type +from pint.testsuite import QuantityTestCase, helpers + + +class TestIssues(QuantityTestCase): + + FORCE_NDARRAY = False + + def setup(self): + self.ureg.autoconvert_offset_to_baseunit = False + + @unittest.expectedFailure + def test_issue25(self): + x = ParserHelper.from_string('10 %') + self.assertEqual(x, ParserHelper(10, {'%': 1})) + x = ParserHelper.from_string('10 ‰') + self.assertEqual(x, ParserHelper(10, {'‰': 1})) + ureg = UnitRegistry() + ureg.define('percent = [fraction]; offset: 0 = %') + ureg.define('permille = percent / 10 = ‰') + x = ureg.parse_expression('10 %') + self.assertEqual(x, ureg.Quantity(10, {'%': 1})) + y = ureg.parse_expression('10 ‰') + self.assertEqual(y, ureg.Quantity(10, {'‰': 1})) + self.assertEqual(x.to('‰'), ureg.Quantity(1, {'‰': 1})) + + def test_issue29(self): + ureg = UnitRegistry() + ureg.define('molar = mole / liter = M') + t = 4 * ureg('mM') + self.assertEqual(t.magnitude, 4) + self.assertEqual(t.units, UnitsContainer(millimolar=1)) + self.assertEqual(t.to('mole / liter'), 4e-3 * ureg('M')) + + def test_issue52(self): + u1 = UnitRegistry() + u2 = UnitRegistry() + q1 = u1.meter + q2 = u2.meter + import operator as op + for fun in (op.add, op.iadd, + op.sub, op.isub, + op.mul, op.imul, + op.floordiv, op.ifloordiv, + op.truediv, op.itruediv): + self.assertRaises(ValueError, fun, q1, q2) + + def test_issue54(self): + ureg = UnitRegistry() + self.assertEqual((ureg.km/ureg.m + 1).magnitude, 1001) + + def test_issue54_related(self): + ureg = UnitRegistry() + self.assertEqual(ureg.km/ureg.m, 1000) + self.assertEqual(1000, ureg.km/ureg.m) + self.assertLess(900, ureg.km/ureg.m) + self.assertGreater(1100, ureg.km/ureg.m) + + def test_issue61(self): + ureg = UnitRegistry() + Q_ = ureg.Quantity + for value in ({}, {'a': 3}, None): + self.assertRaises(TypeError, Q_, value) + self.assertRaises(TypeError, Q_, value, 'meter') + self.assertRaises(ValueError, Q_, '', 'meter') + self.assertRaises(ValueError, Q_, '') + + @helpers.requires_not_numpy() + def test_issue61_notNP(self): + ureg = UnitRegistry() + Q_ = ureg.Quantity + for value in ([1, 2, 3], (1, 2, 3)): + self.assertRaises(TypeError, Q_, value) + self.assertRaises(TypeError, Q_, value, 'meter') + + def test_issue66(self): + ureg = UnitRegistry() + self.assertEqual(ureg.get_dimensionality(UnitsContainer({'[temperature]': 1})), + UnitsContainer({'[temperature]': 1})) + self.assertEqual(ureg.get_dimensionality(ureg.kelvin.units), + UnitsContainer({'[temperature]': 1})) + self.assertEqual(ureg.get_dimensionality(ureg.degC.units), + UnitsContainer({'[temperature]': 1})) + + def test_issue66b(self): + ureg = UnitRegistry() + self.assertEqual(ureg.get_base_units(ureg.kelvin.units), + (1.0, UnitsContainer({'kelvin': 1}))) + self.assertEqual(ureg.get_base_units(ureg.degC.units), + (1.0, UnitsContainer({'kelvin': 1}))) + + def test_issue69(self): + ureg = UnitRegistry() + q = ureg('m').to(ureg('in')) + self.assertEqual(q, ureg('m').to('in')) + + @helpers.requires_uncertainties() + def test_issue77(self): + ureg = UnitRegistry() + acc = (5.0 * ureg('m/s/s')).plus_minus(0.25) + tim = (37.0 * ureg('s')).plus_minus(0.16) + dis = acc * tim ** 2 / 2 + self.assertEqual(dis.value, acc.value * tim.value ** 2 / 2) + + def test_issue85(self): + ureg = UnitRegistry() + + T = 4. * ureg.kelvin + m = 1. * ureg.amu + va = 2. * ureg.k * T / m + + try: + va.to_base_units() + except: + self.assertTrue(False, 'Error while trying to get base units for {}'.format(va)) + + boltmk = 1.3806488e-23*ureg.J/ureg.K + vb = 2. * boltmk * T / m + + self.assertQuantityAlmostEqual(va.to_base_units(), vb.to_base_units()) + + def test_issue86(self): + ureg = self.ureg + ureg.autoconvert_offset_to_baseunit = True + + def parts(q): + return q.magnitude, q.units + + q1 = 10. * ureg.degC + q2 = 10. * ureg.kelvin + + k1 = q1.to_base_units() + + q3 = 3. * ureg.meter + + q1m, q1u = parts(q1) + q2m, q2u = parts(q2) + q3m, q3u = parts(q3) + + k1m, k1u = parts(k1) + + self.assertEqual(parts(q2 * q3), (q2m * q3m, q2u * q3u)) + self.assertEqual(parts(q2 / q3), (q2m / q3m, q2u / q3u)) + self.assertEqual(parts(q3 * q2), (q3m * q2m, q3u * q2u)) + self.assertEqual(parts(q3 / q2), (q3m / q2m, q3u / q2u)) + self.assertEqual(parts(q2 ** 1), (q2m ** 1, q2u ** 1)) + self.assertEqual(parts(q2 ** -1), (q2m ** -1, q2u ** -1)) + self.assertEqual(parts(q2 ** 2), (q2m ** 2, q2u ** 2)) + self.assertEqual(parts(q2 ** -2), (q2m ** -2, q2u ** -2)) + + self.assertEqual(parts(q1 * q3), (k1m * q3m, k1u * q3u)) + self.assertEqual(parts(q1 / q3), (k1m / q3m, k1u / q3u)) + self.assertEqual(parts(q3 * q1), (q3m * k1m, q3u * k1u)) + self.assertEqual(parts(q3 / q1), (q3m / k1m, q3u / k1u)) + self.assertEqual(parts(q1 ** -1), (k1m ** -1, k1u ** -1)) + self.assertEqual(parts(q1 ** 2), (k1m ** 2, k1u ** 2)) + self.assertEqual(parts(q1 ** -2), (k1m ** -2, k1u ** -2)) + + def test_issues86b(self): + ureg = self.ureg + + T1 = 200. * ureg.degC + T2 = T1.to(ureg.kelvin) + m = 132.9054519 * ureg.amu + v1 = 2 * ureg.k * T1 / m + v2 = 2 * ureg.k * T2 / m + + self.assertQuantityAlmostEqual(v1, v2) + self.assertQuantityAlmostEqual(v1, v2.to_base_units()) + self.assertQuantityAlmostEqual(v1.to_base_units(), v2) + self.assertQuantityAlmostEqual(v1.to_base_units(), v2.to_base_units()) + + @unittest.expectedFailure + def test_issue86c(self): + ureg = self.ureg + ureg.autoconvert_offset_to_baseunit = True + T = ureg.degC + T = 100. * T + self.assertQuantityAlmostEqual(ureg.k*2*T, ureg.k*(2*T)) + + def test_issue93(self): + ureg = UnitRegistry() + self.assertIsInstance(ureg.meter.magnitude, int) + x = 5 * ureg.meter + self.assertIsInstance(x.magnitude, int) + y = 0.1 * ureg.meter + self.assertIsInstance(y.magnitude, float) + z = 5 * ureg.meter + self.assertIsInstance(z.magnitude, int) + z += y + self.assertIsInstance(z.magnitude, float) + + self.assertQuantityAlmostEqual(x + y, 5.1 * ureg.meter) + self.assertQuantityAlmostEqual(z, 5.1 * ureg.meter) + + def _test_issueXX(self): + ureg = UnitRegistry() + try: + ureg.convert(1, ureg.degC, ureg.kelvin * ureg.meter / ureg.nanometer) + except: + self.assertTrue(False, + 'Error while trying to convert {} to {}'.format(ureg.degC, ureg.kelvin * ureg.meter / ureg.nanometer)) + + def test_issue121(self): + sh = (2, 1) + ureg = UnitRegistry() + z, v = 0, 2. + self.assertEqual(z + v * ureg.meter, v * ureg.meter) + self.assertEqual(z - v * ureg.meter, -v * ureg.meter) + self.assertEqual(v * ureg.meter + z, v * ureg.meter) + self.assertEqual(v * ureg.meter - z, v * ureg.meter) + + self.assertEqual(sum([v * ureg.meter, v * ureg.meter]), 2 * v * ureg.meter) + + def test_issue105(self): + ureg = UnitRegistry() + + func = ureg.parse_unit_name + val = list(func('meter')) + self.assertEqual(list(func('METER')), []) + self.assertEqual(val, list(func('METER', False))) + + for func in (ureg.get_name, ureg.parse_expression): + val = func('meter') + self.assertRaises(ValueError, func, 'METER') + self.assertEqual(val, func('METER', False)) + + def test_issue104(self): + ureg = UnitRegistry() + + x = [ureg('1 meter'), ureg('1 meter'), ureg('1 meter')] + y = [ureg('1 meter')] * 3 + + def summer(values): + if not values: + return 0 + total = values[0] + for v in values[1:]: + total += v + + return total + + self.assertQuantityAlmostEqual(summer(x), ureg.Quantity(3, 'meter')) + self.assertQuantityAlmostEqual(x[0], ureg.Quantity(1, 'meter')) + self.assertQuantityAlmostEqual(summer(y), ureg.Quantity(3, 'meter')) + self.assertQuantityAlmostEqual(y[0], ureg.Quantity(1, 'meter')) + + def test_issue170(self): + Q_ = UnitRegistry().Quantity + q = Q_('1 kHz')/Q_('100 Hz') + iq = int(q) + self.assertEqual(iq, 10) + self.assertIsInstance(iq, int) + + @helpers.requires_python2() + def test_issue170b(self): + Q_ = UnitRegistry().Quantity + q = Q_('1 kHz')/Q_('100 Hz') + iq = long(q) + self.assertEqual(iq, long(10)) + self.assertIsInstance(iq, long) + + +@helpers.requires_numpy() +class TestIssuesNP(QuantityTestCase): + + FORCE_NDARRAY = False + + @unittest.expectedFailure + def test_issue37(self): + x = np.ma.masked_array([1, 2, 3], mask=[True, True, False]) + ureg = UnitRegistry() + q = ureg.meter * x + self.assertIsInstance(q, ureg.Quantity) + np.testing.assert_array_equal(q.magnitude, x) + self.assertEqual(q.units, ureg.meter.units) + q = x * ureg.meter + self.assertIsInstance(q, ureg.Quantity) + np.testing.assert_array_equal(q.magnitude, x) + self.assertEqual(q.units, ureg.meter.units) + + m = np.ma.masked_array(2 * np.ones(3,3)) + qq = q * m + self.assertIsInstance(qq, ureg.Quantity) + np.testing.assert_array_equal(qq.magnitude, x * m) + self.assertEqual(qq.units, ureg.meter.units) + qq = m * q + self.assertIsInstance(qq, ureg.Quantity) + np.testing.assert_array_equal(qq.magnitude, x * m) + self.assertEqual(qq.units, ureg.meter.units) + + @unittest.expectedFailure + def test_issue39(self): + x = np.matrix([[1, 2, 3], [1, 2, 3], [1, 2, 3]]) + ureg = UnitRegistry() + q = ureg.meter * x + self.assertIsInstance(q, ureg.Quantity) + np.testing.assert_array_equal(q.magnitude, x) + self.assertEqual(q.units, ureg.meter.units) + q = x * ureg.meter + self.assertIsInstance(q, ureg.Quantity) + np.testing.assert_array_equal(q.magnitude, x) + self.assertEqual(q.units, ureg.meter.units) + + m = np.matrix(2 * np.ones(3,3)) + qq = q * m + self.assertIsInstance(qq, ureg.Quantity) + np.testing.assert_array_equal(qq.magnitude, x * m) + self.assertEqual(qq.units, ureg.meter.units) + qq = m * q + self.assertIsInstance(qq, ureg.Quantity) + np.testing.assert_array_equal(qq.magnitude, x * m) + self.assertEqual(qq.units, ureg.meter.units) + + def test_issue44(self): + ureg = UnitRegistry() + x = 4. * ureg.dimensionless + np.sqrt(x) + self.assertQuantityAlmostEqual(np.sqrt([4.] * ureg.dimensionless), [2.] * ureg.dimensionless) + self.assertQuantityAlmostEqual(np.sqrt(4. * ureg.dimensionless), 2. * ureg.dimensionless) + + def test_issue45(self): + import math + ureg = UnitRegistry() + self.assertAlmostEqual(math.sqrt(4 * ureg.m/ureg.cm), math.sqrt(4 * 100)) + self.assertAlmostEqual(float(ureg.V / ureg.mV), 1000.) + + def test_issue45b(self): + ureg = UnitRegistry() + self.assertAlmostEqual(np.sin([np.pi/2] * ureg.m / ureg.m ), np.sin([np.pi/2] * ureg.dimensionless)) + self.assertAlmostEqual(np.sin([np.pi/2] * ureg.cm / ureg.m ), np.sin([np.pi/2] * ureg.dimensionless * 0.01)) + + def test_issue50(self): + ureg = UnitRegistry() + Q_ = ureg.Quantity + self.assertEqual(Q_(100), 100 * ureg.dimensionless) + self.assertEqual(Q_('100'), 100 * ureg.dimensionless) + + def test_issue62(self): + ureg = UnitRegistry() + m = ureg('m**0.5') + self.assertEqual(str(m.units), 'meter ** 0.5') + + def test_issue74(self): + ureg = UnitRegistry() + v1 = np.asarray([1., 2., 3.]) + v2 = np.asarray([3., 2., 1.]) + q1 = v1 * ureg.ms + q2 = v2 * ureg.ms + + np.testing.assert_array_equal(q1 < q2, v1 < v2) + np.testing.assert_array_equal(q1 > q2, v1 > v2) + + np.testing.assert_array_equal(q1 <= q2, v1 <= v2) + np.testing.assert_array_equal(q1 >= q2, v1 >= v2) + + q2s = np.asarray([0.003, 0.002, 0.001]) * ureg.s + v2s = q2s.to('ms').magnitude + + np.testing.assert_array_equal(q1 < q2s, v1 < v2s) + np.testing.assert_array_equal(q1 > q2s, v1 > v2s) + + np.testing.assert_array_equal(q1 <= q2s, v1 <= v2s) + np.testing.assert_array_equal(q1 >= q2s, v1 >= v2s) + + def test_issue75(self): + ureg = UnitRegistry() + v1 = np.asarray([1., 2., 3.]) + v2 = np.asarray([3., 2., 1.]) + q1 = v1 * ureg.ms + q2 = v2 * ureg.ms + + np.testing.assert_array_equal(q1 == q2, v1 == v2) + np.testing.assert_array_equal(q1 != q2, v1 != v2) + + q2s = np.asarray([0.003, 0.002, 0.001]) * ureg.s + v2s = q2s.to('ms').magnitude + + np.testing.assert_array_equal(q1 == q2s, v1 == v2s) + np.testing.assert_array_equal(q1 != q2s, v1 != v2s) + + def test_issue93(self): + ureg = UnitRegistry() + self.assertIsInstance(ureg.meter.magnitude, int) + x = 5 * ureg.meter + self.assertIsInstance(x.magnitude, int) + y = 0.1 * ureg.meter + self.assertIsInstance(y.magnitude, float) + z = 5 * ureg.meter + self.assertIsInstance(z.magnitude, int) + z += y + self.assertIsInstance(z.magnitude, float) + + self.assertQuantityAlmostEqual(x + y, 5.1 * ureg.meter) + self.assertQuantityAlmostEqual(z, 5.1 * ureg.meter) + + + def test_issue94(self): + ureg = UnitRegistry() + v1 = np.array([5, 5]) * ureg.meter + v2 = 0.1 * ureg.meter + v3 = np.array([5, 5]) * ureg.meter + v3 += v2 + + np.testing.assert_array_equal((v1 + v2).magnitude, np.array([5.1, 5.1])) + np.testing.assert_array_equal(v3.magnitude, np.array([5, 5])) + + @helpers.requires_numpy18() + def test_issue121(self): + sh = (2, 1) + ureg = UnitRegistry() + + z, v = 0, 2. + self.assertEqual(z + v * ureg.meter, v * ureg.meter) + self.assertEqual(z - v * ureg.meter, -v * ureg.meter) + self.assertEqual(v * ureg.meter + z, v * ureg.meter) + self.assertEqual(v * ureg.meter - z, v * ureg.meter) + + self.assertEqual(sum([v * ureg.meter, v * ureg.meter]), 2 * v * ureg.meter) + + z, v = np.zeros(sh), 2. * np.ones(sh) + self.assertQuantityEqual(z + v * ureg.meter, v * ureg.meter) + self.assertQuantityEqual(z - v * ureg.meter, -v * ureg.meter) + self.assertQuantityEqual(v * ureg.meter + z, v * ureg.meter) + self.assertQuantityEqual(v * ureg.meter - z, v * ureg.meter) + + z, v = np.zeros((3, 1)), 2. * np.ones(sh) + for x, y in ((z, v), + (z, v * ureg.meter), + (v * ureg.meter, z) + ): + try: + w = x + y + self.assertTrue(False, "ValueError not raised") + except ValueError: + pass + try: + w = x - y + self.assertTrue(False, "ValueError not raised") + except ValueError: + pass + + def test_issue127(self): + q = [1., 2., 3., 4.] * self.ureg.meter + q[0] = np.nan + self.assertNotEqual(q[0], 1.) + self.assertTrue(math.isnan(q[0].magnitude)) + q[1] = float('NaN') + self.assertNotEqual(q[1], 2.) + self.assertTrue(math.isnan(q[1].magnitude)) + + def test_issue171_real_imag(self): + qr = [1., 2., 3., 4.] * self.ureg.meter + qi = [4., 3., 2., 1.] * self.ureg.meter + q = qr + 1j * qi + self.assertQuantityEqual(q.real, qr) + self.assertQuantityEqual(q.imag, qi) + + def test_issue171_T(self): + a = np.asarray([[1., 2., 3., 4.],[4., 3., 2., 1.]]) + q1 = a * self.ureg.meter + q2 = a.T * self.ureg.meter + self.assertQuantityEqual(q1.T, q2) diff --git a/CadQuery/Libs/pint/testsuite/test_measurement.py b/CadQuery/Libs/pint/testsuite/test_measurement.py new file mode 100644 index 0000000..bf1487c --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_measurement.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +from pint.testsuite import QuantityTestCase, helpers + + +@helpers.requires_not_uncertainties() +class TestNotMeasurement(QuantityTestCase): + + FORCE_NDARRAY = False + + def test_instantiate(self): + M_ = self.ureg.Measurement + self.assertRaises(RuntimeError, M_, 4.0, 0.1, 's') + + +@helpers.requires_uncertainties() +class TestMeasurement(QuantityTestCase): + + FORCE_NDARRAY = False + + def test_simple(self): + M_ = self.ureg.Measurement + M_(4.0, 0.1, 's') + + def test_build(self): + M_ = self.ureg.Measurement + v, u = self.Q_(4.0, 's'), self.Q_(.1, 's') + M_(v.magnitude, u.magnitude, 's') + ms = (M_(v.magnitude, u.magnitude, 's'), + M_(v, u.magnitude), + M_(v, u), + v.plus_minus(.1), + v.plus_minus(0.025, True), + v.plus_minus(u),) + + for m in ms: + self.assertEqual(m.value, v) + self.assertEqual(m.error, u) + self.assertEqual(m.rel, m.error / abs(m.value)) + + def test_format(self): + v, u = self.Q_(4.0, 's ** 2'), self.Q_(.1, 's ** 2') + m = self.ureg.Measurement(v, u) + self.assertEqual(str(m), '(4.00 +/- 0.10) second ** 2') + self.assertEqual(repr(m), '') + #self.assertEqual('{:!s}'.format(m), '(4.00 +/- 0.10) second ** 2') + #self.assertEqual('{:!r}'.format(m), '') + self.assertEqual('{0:P}'.format(m), '(4.00 ± 0.10) second²') + self.assertEqual('{0:L}'.format(m), r'\left(4.00 \pm 0.10\right) second^{2}') + self.assertEqual('{0:H}'.format(m), '(4.00 ± 0.10) second2') + self.assertEqual('{0:C}'.format(m), '(4.00+/-0.10) second**2') + self.assertEqual('{0:.1f}'.format(m), '(4.0 +/- 0.1) second ** 2') + self.assertEqual('{0:.1fP}'.format(m), '(4.0 ± 0.1) second²') + self.assertEqual('{0:.1fL}'.format(m), r'\left(4.0 \pm 0.1\right) second^{2}') + self.assertEqual('{0:.1fH}'.format(m), '(4.0 ± 0.1) second2') + self.assertEqual('{0:.1fC}'.format(m), '(4.0+/-0.1) second**2') + + def test_format_paru(self): + v, u = self.Q_(0.20, 's ** 2'), self.Q_(0.01, 's ** 2') + m = self.ureg.Measurement(v, u) + self.assertEqual('{0:uS}'.format(m), '0.200(10) second ** 2') + self.assertEqual('{0:.3uS}'.format(m), '0.2000(100) second ** 2') + self.assertEqual('{0:.3uSP}'.format(m), '0.2000(100) second²') + self.assertEqual('{0:.3uSL}'.format(m), r'0.2000\left(100\right) second^{2}') + self.assertEqual('{0:.3uSH}'.format(m), '0.2000(100) second2') + self.assertEqual('{0:.3uSC}'.format(m), '0.2000(100) second**2') + + def test_format_u(self): + v, u = self.Q_(0.20, 's ** 2'), self.Q_(0.01, 's ** 2') + m = self.ureg.Measurement(v, u) + self.assertEqual('{0:.3u}'.format(m), '(0.2000 +/- 0.0100) second ** 2') + self.assertEqual('{0:.3uP}'.format(m), '(0.2000 ± 0.0100) second²') + self.assertEqual('{0:.3uL}'.format(m), r'\left(0.2000 \pm 0.0100\right) second^{2}') + self.assertEqual('{0:.3uH}'.format(m), '(0.2000 ± 0.0100) second2') + self.assertEqual('{0:.3uC}'.format(m), '(0.2000+/-0.0100) second**2') + + def test_format_percu(self): + self.test_format_perce() + v, u = self.Q_(0.20, 's ** 2'), self.Q_(0.01, 's ** 2') + m = self.ureg.Measurement(v, u) + self.assertEqual('{0:.1u%}'.format(m), '(20 +/- 1)% second ** 2') + self.assertEqual('{0:.1u%P}'.format(m), '(20 ± 1)% second²') + self.assertEqual('{0:.1u%L}'.format(m), r'\left(20 \pm 1\right) \% second^{2}') + self.assertEqual('{0:.1u%H}'.format(m), '(20 ± 1)% second2') + self.assertEqual('{0:.1u%C}'.format(m), '(20+/-1)% second**2') + + def test_format_perce(self): + v, u = self.Q_(0.20, 's ** 2'), self.Q_(0.01, 's ** 2') + m = self.ureg.Measurement(v, u) + self.assertEqual('{0:.1ue}'.format(m), '(2.0 +/- 0.1)e-01 second ** 2') + self.assertEqual('{0:.1ueP}'.format(m), '(2.0 ± 0.1)×10⁻¹ second²') + self.assertEqual('{0:.1ueL}'.format(m), r'\left(2.0 \pm 0.1\right) \times 10^{-1} second^{2}') + self.assertEqual('{0:.1ueH}'.format(m), '(2.0 ± 0.1)e-01 second2') + self.assertEqual('{0:.1ueC}'.format(m), '(2.0+/-0.1)e-01 second**2') + + def test_raise_build(self): + v, u = self.Q_(1.0, 's'), self.Q_(.1, 's') + o = self.Q_(.1, 'm') + + M_ = self.ureg.Measurement + self.assertRaises(ValueError, M_, v, o) + self.assertRaises(ValueError, v.plus_minus, o) + self.assertRaises(ValueError, v.plus_minus, u, True) + + def test_propagate_linear(self): + + v1, u1 = self.Q_(8.0, 's'), self.Q_(.7, 's') + v2, u2 = self.Q_(5.0, 's'), self.Q_(.6, 's') + v2, u3 = self.Q_(-5.0, 's'), self.Q_(.6, 's') + + m1 = v1.plus_minus(u1) + m2 = v2.plus_minus(u2) + m3 = v2.plus_minus(u3) + + for factor, m in zip((3, -3, 3, -3), (m1, m3, m1, m3)): + r = factor * m + self.assertAlmostEqual(r.value.magnitude, factor * m.value.magnitude) + self.assertAlmostEqual(r.error.magnitude, abs(factor * m.error.magnitude)) + self.assertEqual(r.value.units, m.value.units) + + for ml, mr in zip((m1, m1, m1, m3), (m1, m2, m3, m3)): + r = ml + mr + self.assertAlmostEqual(r.value.magnitude, ml.value.magnitude + mr.value.magnitude) + self.assertAlmostEqual(r.error.magnitude, + ml.error.magnitude + mr.error.magnitude if ml is mr else + (ml.error.magnitude ** 2 + mr.error.magnitude ** 2) ** .5) + self.assertEqual(r.value.units, ml.value.units) + + for ml, mr in zip((m1, m1, m1, m3), (m1, m2, m3, m3)): + r = ml - mr + self.assertAlmostEqual(r.value.magnitude, ml.value.magnitude - mr.value.magnitude) + self.assertAlmostEqual(r.error.magnitude, + 0 if ml is mr else + (ml.error.magnitude ** 2 + mr.error.magnitude ** 2) ** .5) + self.assertEqual(r.value.units, ml.value.units) + + def test_propagate_product(self): + + v1, u1 = self.Q_(8.0, 's'), self.Q_(.7, 's') + v2, u2 = self.Q_(5.0, 's'), self.Q_(.6, 's') + v2, u3 = self.Q_(-5.0, 's'), self.Q_(.6, 's') + + m1 = v1.plus_minus(u1) + m2 = v2.plus_minus(u2) + m3 = v2.plus_minus(u3) + + m4 = (2.3 * self.ureg.meter).plus_minus(0.1) + m5 = (1.4 * self.ureg.meter).plus_minus(0.2) + + for ml, mr in zip((m1, m1, m1, m3, m4), (m1, m2, m3, m3, m5)): + r = ml * mr + self.assertAlmostEqual(r.value.magnitude, ml.value.magnitude * mr.value.magnitude) + self.assertEqual(r.value.units, ml.value.units * mr.value.units) + + for ml, mr in zip((m1, m1, m1, m3, m4), (m1, m2, m3, m3, m5)): + r = ml / mr + self.assertAlmostEqual(r.value.magnitude, ml.value.magnitude / mr.value.magnitude) + self.assertEqual(r.value.units, ml.value.units / mr.value.units) diff --git a/CadQuery/Libs/pint/testsuite/test_numpy.py b/CadQuery/Libs/pint/testsuite/test_numpy.py new file mode 100644 index 0000000..5ce0f04 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_numpy.py @@ -0,0 +1,441 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import copy +import operator as op + +from pint import DimensionalityError +from pint.compat import np, unittest +from pint.testsuite import QuantityTestCase, helpers +from pint.testsuite.test_umath import TestUFuncs + + +@helpers.requires_numpy() +class TestNumpyMethods(QuantityTestCase): + + FORCE_NDARRAY = True + + @property + def q(self): + return [[1,2],[3,4]] * self.ureg.m + + def test_tolist(self): + self.assertEqual(self.q.tolist(), [[1*self.ureg.m, 2*self.ureg.m], [3*self.ureg.m, 4*self.ureg.m]]) + + def test_sum(self): + self.assertEqual(self.q.sum(), 10*self.ureg.m) + self.assertQuantityEqual(self.q.sum(0), [4, 6]*self.ureg.m) + self.assertQuantityEqual(self.q.sum(1), [3, 7]*self.ureg.m) + + def test_fill(self): + tmp = self.q + tmp.fill(6 * self.ureg.ft) + self.assertQuantityEqual(tmp, [[6, 6], [6, 6]] * self.ureg.ft) + tmp.fill(5 * self.ureg.m) + self.assertQuantityEqual(tmp, [[5, 5], [5, 5]] * self.ureg.m) + + def test_reshape(self): + self.assertQuantityEqual(self.q.reshape([1,4]), [[1, 2, 3, 4]] * self.ureg.m) + + def test_transpose(self): + self.assertQuantityEqual(self.q.transpose(), [[1, 3], [2, 4]] * self.ureg.m) + + def test_flatten(self): + self.assertQuantityEqual(self.q.flatten(), [1, 2, 3, 4] * self.ureg.m) + + def test_ravel(self): + self.assertQuantityEqual(self.q.ravel(), [1, 2, 3, 4] * self.ureg.m) + + def test_squeeze(self): + self.assertQuantityEqual( + self.q.reshape([1,4]).squeeze(), + [1, 2, 3, 4] * self.ureg.m + ) + + def test_take(self): + self.assertQuantityEqual(self.q.take([0,1,2,3]), self.q.flatten()) + + def test_put(self): + q = [1., 2., 3., 4.] * self.ureg.m + q.put([0, 2], [10.,20.]*self.ureg.m) + self.assertQuantityEqual(q, [10., 2., 20., 4.]*self.ureg.m) + + q = [1., 2., 3., 4.] * self.ureg.m + q.put([0, 2], [1., 2.]*self.ureg.mm) + self.assertQuantityEqual(q, [0.001, 2., 0.002, 4.]*self.ureg.m) + + q = [1., 2., 3., 4.] * self.ureg.m / self.ureg.mm + q.put([0, 2], [1., 2.]) + self.assertQuantityEqual(q, [0.001, 2., 0.002, 4.]*self.ureg.m/self.ureg.mm) + + q = [1., 2., 3., 4.] * self.ureg.m + self.assertRaises(ValueError, q.put, [0, 2], [4., 6.] * self.ureg.J) + self.assertRaises(ValueError, q.put, [0, 2], [4., 6.]) + + def test_repeat(self): + self.assertQuantityEqual(self.q.repeat(2), [1,1,2,2,3,3,4,4]*self.ureg.m) + + def test_sort(self): + q = [4, 5, 2, 3, 1, 6] * self.ureg.m + q.sort() + self.assertQuantityEqual(q, [1, 2, 3, 4, 5, 6] * self.ureg.m) + + def test_argsort(self): + q = [1, 4, 5, 6, 2, 9] * self.ureg.MeV + np.testing.assert_array_equal(q.argsort(), [0, 4, 1, 2, 3, 5]) + + def test_diagonal(self): + q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m + self.assertQuantityEqual(q.diagonal(offset=1), [2, 3] * self.ureg.m) + + def test_compress(self): + self.assertQuantityEqual(self.q.compress([False, True], axis=0), + [[3, 4]] * self.ureg.m) + self.assertQuantityEqual(self.q.compress([False, True], axis=1), + [[2], [4]] * self.ureg.m) + + def test_searchsorted(self): + q = self.q.flatten() + np.testing.assert_array_equal(q.searchsorted([1.5, 2.5] * self.ureg.m), + [1, 2]) + q = self.q.flatten() + self.assertRaises(ValueError, q.searchsorted, [1.5, 2.5]) + + def test_nonzero(self): + q = [1, 0, 5, 6, 0, 9] * self.ureg.m + np.testing.assert_array_equal(q.nonzero()[0], [0, 2, 3, 5]) + + def test_max(self): + self.assertEqual(self.q.max(), 4*self.ureg.m) + + def test_argmax(self): + self.assertEqual(self.q.argmax(), 3) + + def test_min(self): + self.assertEqual(self.q.min(), 1 * self.ureg.m) + + def test_argmin(self): + self.assertEqual(self.q.argmin(), 0) + + def test_ptp(self): + self.assertEqual(self.q.ptp(), 3 * self.ureg.m) + + def test_clip(self): + self.assertQuantityEqual( + self.q.clip(max=2*self.ureg.m), + [[1, 2], [2, 2]] * self.ureg.m + ) + self.assertQuantityEqual( + self.q.clip(min=3*self.ureg.m), + [[3, 3], [3, 4]] * self.ureg.m + ) + self.assertQuantityEqual( + self.q.clip(min=2*self.ureg.m, max=3*self.ureg.m), + [[2, 2], [3, 3]] * self.ureg.m + ) + self.assertRaises(ValueError, self.q.clip, self.ureg.J) + self.assertRaises(ValueError, self.q.clip, 1) + + def test_round(self): + q = [1, 1.33, 5.67, 22] * self.ureg.m + self.assertQuantityEqual(q.round(0), [1, 1, 6, 22] * self.ureg.m) + self.assertQuantityEqual(q.round(-1), [0, 0, 10, 20] * self.ureg.m) + self.assertQuantityEqual(q.round(1), [1, 1.3, 5.7, 22] * self.ureg.m) + + def test_trace(self): + self.assertEqual(self.q.trace(), (1+4) * self.ureg.m) + + def test_cumsum(self): + self.assertQuantityEqual(self.q.cumsum(), [1, 3, 6, 10] * self.ureg.m) + + def test_mean(self): + self.assertEqual(self.q.mean(), 2.5 * self.ureg.m) + + def test_var(self): + self.assertEqual(self.q.var(), 1.25*self.ureg.m**2) + + def test_std(self): + self.assertQuantityAlmostEqual(self.q.std(), 1.11803*self.ureg.m, rtol=1e-5) + + def test_prod(self): + self.assertEqual(self.q.prod(), 24 * self.ureg.m**4) + + def test_cumprod(self): + self.assertRaises(ValueError, self.q.cumprod) + self.assertQuantityEqual((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) + + def test_integer_div(self): + a = [1] * self.ureg.m + b = [2] * self.ureg.m + c = a/b # Should be float division + self.assertEqual(c.magnitude[0], 0.5) + + a /= b # Should be integer division + self.assertEqual(a.magnitude[0], 0) + + def test_conj(self): + self.assertQuantityEqual((self.q*(1+1j)).conj(), self.q*(1-1j)) + self.assertQuantityEqual((self.q*(1+1j)).conjugate(), self.q*(1-1j)) + + def test_getitem(self): + self.assertRaises(IndexError, self.q.__getitem__, (0,10)) + self.assertQuantityEqual(self.q[0], [1,2]*self.ureg.m) + self.assertEqual(self.q[1,1], 4*self.ureg.m) + + def test_setitem(self): + self.assertRaises(ValueError, self.q.__setitem__, (0,0), 1) + self.assertRaises(ValueError, self.q.__setitem__, (0,0), 1*self.ureg.J) + self.assertRaises(ValueError, self.q.__setitem__, 0, 1) + self.assertRaises(ValueError, self.q.__setitem__, 0, np.ndarray([1, 2])) + self.assertRaises(ValueError, self.q.__setitem__, 0, 1*self.ureg.J) + + q = self.q.copy() + q[0] = 1*self.ureg.m + self.assertQuantityEqual(q, [[1,1],[3,4]]*self.ureg.m) + + q = self.q.copy() + q.__setitem__(Ellipsis, 1*self.ureg.m) + self.assertQuantityEqual(q, [[1,1],[1,1]]*self.ureg.m) + + q = self.q.copy() + q[:] = 1*self.ureg.m + self.assertQuantityEqual(q, [[1,1],[1,1]]*self.ureg.m) + + # check and see that dimensionless num bers work correctly + q = [0,1,2,3]*self.ureg.dimensionless + q[0] = 1 + self.assertQuantityEqual(q, np.asarray([1,1,2,3])) + q[0] = self.ureg.m/self.ureg.mm + self.assertQuantityEqual(q, np.asarray([1000, 1,2,3])) + + q = [0.,1.,2.,3.] * self.ureg.m / self.ureg.mm + q[0] = 1. + self.assertQuantityEqual(q, [0.001,1,2,3]*self.ureg.m / self.ureg.mm) + + def test_iterator(self): + for q, v in zip(self.q.flatten(), [1, 2, 3, 4]): + self.assertEqual(q, v * self.ureg.m) + + def test_reversible_op(self): + """ + """ + x = self.q.magnitude + u = self.Q_(np.ones(x.shape)) + self.assertQuantityEqual(x / self.q, u * x / self.q) + self.assertQuantityEqual(x * self.q, u * x * self.q) + self.assertQuantityEqual(x + u, u + x) + self.assertQuantityEqual(x - u, -(u - x)) + + def test_pickle(self): + import pickle + + def pickle_test(q): + pq = pickle.loads(pickle.dumps(q)) + np.testing.assert_array_equal(q.magnitude, pq.magnitude) + self.assertEqual(q.units, pq.units) + + pickle_test([10,20]*self.ureg.m) + + def test_equal(self): + x = self.q.magnitude + u = self.Q_(np.ones(x.shape)) + + self.assertQuantityEqual(u, u) + self.assertQuantityEqual(u == u, u.magnitude == u.magnitude) + self.assertQuantityEqual(u == 1, u.magnitude == 1) + + +@helpers.requires_numpy() +class TestNumpyNeedsSubclassing(TestUFuncs): + + FORCE_NDARRAY = True + + @property + def q(self): + return [1. ,2., 3., 4.] * self.ureg.J + + @unittest.expectedFailure + def test_unwrap(self): + """unwrap depends on diff + """ + self.assertQuantityEqual(np.unwrap([0,3*np.pi]*self.ureg.radians), [0,np.pi]) + self.assertQuantityEqual(np.unwrap([0,540]*self.ureg.deg), [0,180]*self.ureg.deg) + + @unittest.expectedFailure + def test_trapz(self): + """Units are erased by asanyarray, Quantity does not inherit from NDArray + """ + self.assertQuantityEqual(np.trapz(self.q, dx=1*self.ureg.m), 7.5 * self.ureg.J*self.ureg.m) + + @unittest.expectedFailure + def test_diff(self): + """Units are erased by asanyarray, Quantity does not inherit from NDArray + """ + self.assertQuantityEqual(np.diff(self.q, 1), [1, 1, 1] * self.ureg.J) + + @unittest.expectedFailure + def test_ediff1d(self): + """Units are erased by asanyarray, Quantity does not inherit from NDArray + """ + self.assertQuantityEqual(np.ediff1d(self.q, 1 * self.ureg.J), [1, 1, 1] * self.ureg.J) + + @unittest.expectedFailure + def test_fix(self): + """Units are erased by asanyarray, Quantity does not inherit from NDArray + """ + self.assertQuantityEqual(np.fix(3.14 * self.ureg.m), 3.0 * self.ureg.m) + self.assertQuantityEqual(np.fix(3.0 * self.ureg.m), 3.0 * self.ureg.m) + self.assertQuantityEqual( + np.fix([2.1, 2.9, -2.1, -2.9] * self.ureg.m), + [2., 2., -2., -2.] * self.ureg.m + ) + + @unittest.expectedFailure + def test_gradient(self): + """shape is a property not a function + """ + l = np.gradient([[1,1],[3,4]] * self.ureg.J, 1 * self.ureg.m) + self.assertQuantityEqual(l[0], [[2., 3.], [2., 3.]] * self.ureg.J / self.ureg.m) + self.assertQuantityEqual(l[1], [[0., 0.], [1., 1.]] * self.ureg.J / self.ureg.m) + + @unittest.expectedFailure + def test_cross(self): + """Units are erased by asarray, Quantity does not inherit from NDArray + """ + a = [[3,-3, 1]] * self.ureg.kPa + b = [[4, 9, 2]] * self.ureg.m**2 + self.assertQuantityEqual(np.cross(a, b), [-15, -2, 39] * self.ureg.kPa * self.ureg.m**2) + + @unittest.expectedFailure + def test_power(self): + """This is not supported as different elements might end up with different units + + eg. ([1, 1] * m) ** [2, 3] + + Must force exponent to single value + """ + self._test2(np.power, self.q1, + (self.qless, np.asarray([1., 2, 3, 4])), + (self.q2, ),) + + @unittest.expectedFailure + def test_ones_like(self): + """Units are erased by emptyarra, Quantity does not inherit from NDArray + """ + self._test1(np.ones_like, + (self.q2, self.qs, self.qless, self.qi), + (), + 2) + + +@unittest.skip +class TestBitTwiddlingUfuncs(TestUFuncs): + """Universal functions (ufuncs) > Bittwiddling functions + + http://docs.scipy.org/doc/numpy/reference/ufuncs.html#bittwiddlingfunctions + + bitwise_and(x1, x2[, out]) Compute the bitwise AND of two arrays elementwise. + bitwise_or(x1, x2[, out]) Compute the bitwise OR of two arrays elementwise. + bitwise_xor(x1, x2[, out]) Compute the bitwise XOR of two arrays elementwise. + invert(x[, out]) Compute bitwise inversion, or bitwise NOT, elementwise. + left_shift(x1, x2[, out]) Shift the bits of an integer to the left. + right_shift(x1, x2[, out]) Shift the bits of an integer to the right. + """ + + @property + def qless(self): + return np.asarray([1, 2, 3, 4], dtype=np.uint8) * self.ureg.dimensionless + + @property + def qs(self): + return 8 * self.ureg.J + + @property + def q1(self): + return np.asarray([1, 2, 3, 4], dtype=np.uint8) * self.ureg.J + + @property + def q2(self): + return 2 * self.q1 + + @property + def qm(self): + return np.asarray([1, 2, 3, 4], dtype=np.uint8) * self.ureg.m + + def test_bitwise_and(self): + self._test2(np.bitwise_and, + self.q1, + (self.q2, self.qs,), + (self.qm, ), + 'same') + + def test_bitwise_or(self): + self._test2(np.bitwise_or, + self.q1, + (self.q1, self.q2, self.qs, ), + (self.qm,), + 'same') + + def test_bitwise_xor(self): + self._test2(np.bitwise_xor, + self.q1, + (self.q1, self.q2, self.qs, ), + (self.qm, ), + 'same') + + def test_invert(self): + self._test1(np.invert, + (self.q1, self.q2, self.qs, ), + (), + 'same') + + def test_left_shift(self): + self._test2(np.left_shift, + self.q1, + (self.qless, 2), + (self.q1, self.q2, self.qs, ), + 'same') + + def test_right_shift(self): + self._test2(np.right_shift, + self.q1, + (self.qless, 2), + (self.q1, self.q2, self.qs, ), + 'same') + + +class TestNDArrayQunatityMath(QuantityTestCase): + + @helpers.requires_numpy() + def test_exponentiation_array_exp(self): + arr = np.array(range(3), dtype=np.float) + q = self.Q_(arr, None) + + for op_ in [op.pow, op.ipow]: + q_cp = copy.copy(q) + self.assertRaises(DimensionalityError, op_, 2., q_cp) + arr_cp = copy.copy(arr) + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + self.assertRaises(DimensionalityError, op_, q_cp, arr_cp) + q_cp = copy.copy(q) + q2_cp = copy.copy(q) + self.assertRaises(DimensionalityError, op_, q_cp, q2_cp) + + @unittest.expectedFailure + @helpers.requires_numpy() + def test_exponentiation_array_exp_2(self): + arr = np.array(range(3), dtype=np.float) + #q = self.Q_(copy.copy(arr), None) + q = self.Q_(copy.copy(arr), 'meter') + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + # this fails as expected since numpy 1.8.0 but... + self.assertRaises(DimensionalityError, op.pow, arr_cp, q_cp) + # ..not for op.ipow ! + # q_cp is treated as if it is an array. The units are ignored. + # _Quantity.__ipow__ is never called + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + self.assertRaises(DimensionalityError, op.ipow, arr_cp, q_cp) diff --git a/CadQuery/Libs/pint/testsuite/test_pitheorem.py b/CadQuery/Libs/pint/testsuite/test_pitheorem.py new file mode 100644 index 0000000..47f3790 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_pitheorem.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import itertools + +from pint import pi_theorem + +from pint.testsuite import QuantityTestCase + + +class TestPiTheorem(QuantityTestCase): + + FORCE_NDARRAY = False + + def test_simple(self): + + # simple movement + with self.capture_log() as buffer: + self.assertEqual(pi_theorem({'V': 'm/s', 'T': 's', 'L': 'm'}), + [{'V': 1, 'T': 1, 'L': -1}]) + + # pendulum + self.assertEqual(pi_theorem({'T': 's', 'M': 'grams', 'L': 'm', 'g': 'm/s**2'}), + [{'g': 1, 'T': 2, 'L': -1}]) + self.assertEqual(len(buffer), 7) + + def test_inputs(self): + V = 'km/hour' + T = 'ms' + L = 'cm' + + f1 = lambda x: x + f2 = lambda x: self.Q_(1, x) + f3 = lambda x: self.Q_(1, x).units + f4 = lambda x: self.Q_(1, x).dimensionality + + fs = f1, f2, f3, f4 + for fv, ft, fl in itertools.product(fs, fs, fs): + qv = fv(V) + qt = ft(T) + ql = ft(L) + self.assertEqual(self.ureg.pi_theorem({'V': qv, 'T': qt, 'L': ql}), + [{'V': 1.0, 'T': 1.0, 'L': -1.0}]) diff --git a/CadQuery/Libs/pint/testsuite/test_quantity.py b/CadQuery/Libs/pint/testsuite/test_quantity.py new file mode 100644 index 0000000..72122e1 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_quantity.py @@ -0,0 +1,995 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import copy +import math +import operator as op + +from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry +from pint.unit import UnitsContainer +from pint.compat import string_types, PYTHON3, np, unittest +from pint.testsuite import QuantityTestCase, helpers +from pint.testsuite.parameterized import ParameterizedTestCase + + +class TestQuantity(QuantityTestCase): + + FORCE_NDARRAY = False + + def test_quantity_creation(self): + for args in ((4.2, 'meter'), + (4.2, UnitsContainer(meter=1)), + (4.2, self.ureg.meter), + ('4.2*meter', ), + ('4.2/meter**(-1)', ), + (self.Q_(4.2, 'meter'),)): + x = self.Q_(*args) + self.assertEqual(x.magnitude, 4.2) + self.assertEqual(x.units, UnitsContainer(meter=1)) + + x = self.Q_(4.2, UnitsContainer(length=1)) + y = self.Q_(x) + self.assertEqual(x.magnitude, y.magnitude) + self.assertEqual(x.units, y.units) + self.assertIsNot(x, y) + + x = self.Q_(4.2, None) + self.assertEqual(x.magnitude, 4.2) + self.assertEqual(x.units, UnitsContainer()) + + with self.capture_log() as buffer: + self.assertEqual(4.2 * self.ureg.meter, self.Q_(4.2, 2 * self.ureg.meter)) + self.assertEqual(len(buffer), 1) + + def test_quantity_bool(self): + self.assertTrue(self.Q_(1, None)) + self.assertTrue(self.Q_(1, 'meter')) + self.assertFalse(self.Q_(0, None)) + self.assertFalse(self.Q_(0, 'meter')) + + def test_quantity_comparison(self): + x = self.Q_(4.2, 'meter') + y = self.Q_(4.2, 'meter') + z = self.Q_(5, 'meter') + j = self.Q_(5, 'meter*meter') + + # identity for single object + self.assertTrue(x == x) + self.assertFalse(x != x) + + # identity for multiple objects with same value + self.assertTrue(x == y) + self.assertFalse(x != y) + + self.assertTrue(x <= y) + self.assertTrue(x >= y) + self.assertFalse(x < y) + self.assertFalse(x > y) + + self.assertFalse(x == z) + self.assertTrue(x != z) + self.assertTrue(x < z) + + self.assertTrue(z != j) + + self.assertNotEqual(z, j) + self.assertEqual(self.Q_(0, 'meter'), self.Q_(0, 'centimeter')) + self.assertNotEqual(self.Q_(0, 'meter'), self.Q_(0, 'second')) + + self.assertLess(self.Q_(10, 'meter'), self.Q_(5, 'kilometer')) + + def test_quantity_comparison_convert(self): + self.assertEqual(self.Q_(1000, 'millimeter'), self.Q_(1, 'meter')) + self.assertEqual(self.Q_(1000, 'millimeter/min'), self.Q_(1000/60, 'millimeter/s')) + + def test_quantity_repr(self): + x = self.Q_(4.2, UnitsContainer(meter=1)) + self.assertEqual(str(x), '4.2 meter') + self.assertEqual(repr(x), "") + + def test_quantity_format(self): + x = self.Q_(4.12345678, UnitsContainer(meter=2, kilogram=1, second=-1)) + for spec, result in (('{0}', str(x)), ('{0!s}', str(x)), ('{0!r}', repr(x)), + ('{0.magnitude}', str(x.magnitude)), ('{0.units}', str(x.units)), + ('{0.magnitude!s}', str(x.magnitude)), ('{0.units!s}', str(x.units)), + ('{0.magnitude!r}', repr(x.magnitude)), ('{0.units!r}', repr(x.units)), + ('{0:.4f}', '{0:.4f} {1!s}'.format(x.magnitude, x.units)), + ('{0:L}', r'4.12345678 \frac{kilogram \cdot meter^{2}}{second}'), + ('{0:P}', '4.12345678 kilogram·meter²/second'), + ('{0:H}', '4.12345678 kilogram meter2/second'), + ('{0:C}', '4.12345678 kilogram*meter**2/second'), + ('{0:~}', '4.12345678 kg * m ** 2 / s'), + ('{0:L~}', r'4.12345678 \frac{kg \cdot m^{2}}{s}'), + ('{0:P~}', '4.12345678 kg·m²/s'), + ('{0:H~}', '4.12345678 kg m2/s'), + ('{0:C~}', '4.12345678 kg*m**2/s'), + ): + self.assertEqual(spec.format(x), result) + + def test_default_formatting(self): + ureg = UnitRegistry() + x = ureg.Quantity(4.12345678, UnitsContainer(meter=2, kilogram=1, second=-1)) + for spec, result in (('L', r'4.12345678 \frac{kilogram \cdot meter^{2}}{second}'), + ('P', '4.12345678 kilogram·meter²/second'), + ('H', '4.12345678 kilogram meter2/second'), + ('C', '4.12345678 kilogram*meter**2/second'), + ('~', '4.12345678 kg * m ** 2 / s'), + ('L~', r'4.12345678 \frac{kg \cdot m^{2}}{s}'), + ('P~', '4.12345678 kg·m²/s'), + ('H~', '4.12345678 kg m2/s'), + ('C~', '4.12345678 kg*m**2/s'), + ): + ureg.default_format = spec + self.assertEqual('{0}'.format(x), result) + + def test_to_base_units(self): + x = self.Q_('1*inch') + self.assertQuantityAlmostEqual(x.to_base_units(), self.Q_(0.0254, 'meter')) + x = self.Q_('1*inch*inch') + self.assertQuantityAlmostEqual(x.to_base_units(), self.Q_(0.0254 ** 2.0, 'meter*meter')) + x = self.Q_('1*inch/minute') + self.assertQuantityAlmostEqual(x.to_base_units(), self.Q_(0.0254 / 60., 'meter/second')) + + def test_convert(self): + x = self.Q_('2*inch') + self.assertQuantityAlmostEqual(x.to('meter'), self.Q_(2. * 0.0254, 'meter')) + x = self.Q_('2*meter') + self.assertQuantityAlmostEqual(x.to('inch'), self.Q_(2. / 0.0254, 'inch')) + x = self.Q_('2*sidereal_second') + self.assertQuantityAlmostEqual(x.to('second'), self.Q_(1.994539133 , 'second')) + x = self.Q_('2.54*centimeter/second') + self.assertQuantityAlmostEqual(x.to('inch/second'), self.Q_(1, 'inch/second')) + x = self.Q_('2.54*centimeter') + self.assertQuantityAlmostEqual(x.to('inch').magnitude, 1) + self.assertQuantityAlmostEqual(self.Q_(2, 'second').to('millisecond').magnitude, 2000) + + @helpers.requires_numpy() + def test_convert(self): + + # Conversions with single units take a different codepath than + # Conversions with more than one unit. + src_dst1 = UnitsContainer(meter=1), UnitsContainer(inch=1) + src_dst2 = UnitsContainer(meter=1, second=-1), UnitsContainer(inch=1, minute=-1) + for src, dst in (src_dst1, src_dst2): + a = np.ones((3, 1)) + ac = np.ones((3, 1)) + + q = self.Q_(a, src) + qac = self.Q_(ac, src).to(dst) + r = q.to(dst) + self.assertQuantityAlmostEqual(qac, r) + self.assertIsNot(r, q) + self.assertIsNot(r._magnitude, a) + + def test_context_attr(self): + self.assertEqual(self.ureg.meter, self.Q_(1, 'meter')) + + def test_both_symbol(self): + self.assertEqual(self.Q_(2, 'ms'), self.Q_(2, 'millisecond')) + self.assertEqual(self.Q_(2, 'cm'), self.Q_(2, 'centimeter')) + + def test_dimensionless_units(self): + self.assertAlmostEqual(self.Q_(360, 'degree').to('radian').magnitude, 2 * math.pi) + self.assertAlmostEqual(self.Q_(2 * math.pi, 'radian'), self.Q_(360, 'degree')) + self.assertEqual(self.Q_(1, 'radian').dimensionality, UnitsContainer()) + self.assertTrue(self.Q_(1, 'radian').dimensionless) + self.assertFalse(self.Q_(1, 'radian').unitless) + + self.assertEqual(self.Q_(1, 'meter')/self.Q_(1, 'meter'), 1) + self.assertEqual((self.Q_(1, 'meter')/self.Q_(1, 'mm')).to(''), 1000) + + def test_offset(self): + self.assertQuantityAlmostEqual(self.Q_(0, 'kelvin').to('kelvin'), self.Q_(0, 'kelvin')) + self.assertQuantityAlmostEqual(self.Q_(0, 'degC').to('kelvin'), self.Q_(273.15, 'kelvin')) + self.assertQuantityAlmostEqual(self.Q_(0, 'degF').to('kelvin'), self.Q_(255.372222, 'kelvin'), rtol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(100, 'kelvin').to('kelvin'), self.Q_(100, 'kelvin')) + self.assertQuantityAlmostEqual(self.Q_(100, 'degC').to('kelvin'), self.Q_(373.15, 'kelvin')) + self.assertQuantityAlmostEqual(self.Q_(100, 'degF').to('kelvin'), self.Q_(310.92777777, 'kelvin'), rtol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(0, 'kelvin').to('degC'), self.Q_(-273.15, 'degC')) + self.assertQuantityAlmostEqual(self.Q_(100, 'kelvin').to('degC'), self.Q_(-173.15, 'degC')) + self.assertQuantityAlmostEqual(self.Q_(0, 'kelvin').to('degF'), self.Q_(-459.67, 'degF'), rtol=0.01) + self.assertQuantityAlmostEqual(self.Q_(100, 'kelvin').to('degF'), self.Q_(-279.67, 'degF'), rtol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(32, 'degF').to('degC'), self.Q_(0, 'degC'), atol=0.01) + self.assertQuantityAlmostEqual(self.Q_(100, 'degC').to('degF'), self.Q_(212, 'degF'), atol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(54, 'degF').to('degC'), self.Q_(12.2222, 'degC'), atol=0.01) + self.assertQuantityAlmostEqual(self.Q_(12, 'degC').to('degF'), self.Q_(53.6, 'degF'), atol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(12, 'kelvin').to('degC'), self.Q_(-261.15, 'degC'), atol=0.01) + self.assertQuantityAlmostEqual(self.Q_(12, 'degC').to('kelvin'), self.Q_(285.15, 'kelvin'), atol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(12, 'kelvin').to('degR'), self.Q_(21.6, 'degR'), atol=0.01) + self.assertQuantityAlmostEqual(self.Q_(12, 'degR').to('kelvin'), self.Q_(6.66666667, 'kelvin'), atol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(12, 'degC').to('degR'), self.Q_(513.27, 'degR'), atol=0.01) + self.assertQuantityAlmostEqual(self.Q_(12, 'degR').to('degC'), self.Q_(-266.483333, 'degC'), atol=0.01) + + + def test_offset_delta(self): + self.assertQuantityAlmostEqual(self.Q_(0, 'delta_degC').to('kelvin'), self.Q_(0, 'kelvin')) + self.assertQuantityAlmostEqual(self.Q_(0, 'delta_degF').to('kelvin'), self.Q_(0, 'kelvin'), rtol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(100, 'kelvin').to('delta_degC'), self.Q_(100, 'delta_degC')) + self.assertQuantityAlmostEqual(self.Q_(100, 'kelvin').to('delta_degF'), self.Q_(180, 'delta_degF'), rtol=0.01) + self.assertQuantityAlmostEqual(self.Q_(100, 'delta_degF').to('kelvin'), self.Q_(55.55555556, 'kelvin'), rtol=0.01) + self.assertQuantityAlmostEqual(self.Q_(100, 'delta_degC').to('delta_degF'), self.Q_(180, 'delta_degF'), rtol=0.01) + self.assertQuantityAlmostEqual(self.Q_(100, 'delta_degF').to('delta_degC'), self.Q_(55.55555556, 'delta_degC'), rtol=0.01) + + self.assertQuantityAlmostEqual(self.Q_(12.3, 'delta_degC').to('delta_degF'), self.Q_(22.14, 'delta_degF'), rtol=0.01) + + + def test_pickle(self): + import pickle + + def pickle_test(q): + self.assertEqual(q, pickle.loads(pickle.dumps(q))) + + pickle_test(self.Q_(32, '')) + pickle_test(self.Q_(2.4, '')) + pickle_test(self.Q_(32, 'm/s')) + pickle_test(self.Q_(2.4, 'm/s')) + + +class TestQuantityBasicMath(QuantityTestCase): + + FORCE_NDARRAY = False + + def _test_inplace(self, operator, value1, value2, expected_result, unit=None): + if isinstance(value1, string_types): + value1 = self.Q_(value1) + if isinstance(value2, string_types): + value2 = self.Q_(value2) + if isinstance(expected_result, string_types): + expected_result = self.Q_(expected_result) + + if not unit is None: + value1 = value1 * unit + value2 = value2 * unit + expected_result = expected_result * unit + + value1 = copy.copy(value1) + value2 = copy.copy(value2) + id1 = id(value1) + id2 = id(value2) + value1 = operator(value1, value2) + value2_cpy = copy.copy(value2) + self.assertQuantityAlmostEqual(value1, expected_result) + self.assertEqual(id1, id(value1)) + self.assertQuantityAlmostEqual(value2, value2_cpy) + self.assertEqual(id2, id(value2)) + + def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None): + if isinstance(value1, string_types): + value1 = self.Q_(value1) + if isinstance(value2, string_types): + value2 = self.Q_(value2) + if isinstance(expected_result, string_types): + expected_result = self.Q_(expected_result) + + if not unit is None: + value1 = value1 * unit + value2 = value2 * unit + expected_result = expected_result * unit + + id1 = id(value1) + id2 = id(value2) + + value1_cpy = copy.copy(value1) + value2_cpy = copy.copy(value2) + + result = operator(value1, value2) + + self.assertQuantityAlmostEqual(expected_result, result) + self.assertQuantityAlmostEqual(value1, value1_cpy) + self.assertQuantityAlmostEqual(value2, value2_cpy) + self.assertNotEqual(id(result), id1) + self.assertNotEqual(id(result), id2) + + def _test_quantity_add_sub(self, unit, func): + x = self.Q_(unit, 'centimeter') + y = self.Q_(unit, 'inch') + z = self.Q_(unit, 'second') + a = self.Q_(unit, None) + + func(op.add, x, x, self.Q_(unit + unit, 'centimeter')) + func(op.add, x, y, self.Q_(unit + 2.54 * unit, 'centimeter')) + func(op.add, y, x, self.Q_(unit + unit / (2.54 * unit), 'inch')) + func(op.add, a, unit, self.Q_(unit + unit, None)) + self.assertRaises(DimensionalityError, op.add, 10, x) + self.assertRaises(DimensionalityError, op.add, x, 10) + self.assertRaises(DimensionalityError, op.add, x, z) + + func(op.sub, x, x, self.Q_(unit - unit, 'centimeter')) + func(op.sub, x, y, self.Q_(unit - 2.54 * unit, 'centimeter')) + func(op.sub, y, x, self.Q_(unit - unit / (2.54 * unit), 'inch')) + func(op.sub, a, unit, self.Q_(unit - unit, None)) + self.assertRaises(DimensionalityError, op.sub, 10, x) + self.assertRaises(DimensionalityError, op.sub, x, 10) + self.assertRaises(DimensionalityError, op.sub, x, z) + + def _test_quantity_iadd_isub(self, unit, func): + x = self.Q_(unit, 'centimeter') + y = self.Q_(unit, 'inch') + z = self.Q_(unit, 'second') + a = self.Q_(unit, None) + + func(op.iadd, x, x, self.Q_(unit + unit, 'centimeter')) + func(op.iadd, x, y, self.Q_(unit + 2.54 * unit, 'centimeter')) + func(op.iadd, y, x, self.Q_(unit + unit / 2.54, 'inch')) + func(op.iadd, a, unit, self.Q_(unit + unit, None)) + self.assertRaises(DimensionalityError, op.iadd, 10, x) + self.assertRaises(DimensionalityError, op.iadd, x, 10) + self.assertRaises(DimensionalityError, op.iadd, x, z) + + func(op.isub, x, x, self.Q_(unit - unit, 'centimeter')) + func(op.isub, x, y, self.Q_(unit - 2.54, 'centimeter')) + func(op.isub, y, x, self.Q_(unit - unit / 2.54, 'inch')) + func(op.isub, a, unit, self.Q_(unit - unit, None)) + self.assertRaises(DimensionalityError, op.sub, 10, x) + self.assertRaises(DimensionalityError, op.sub, x, 10) + self.assertRaises(DimensionalityError, op.sub, x, z) + + def _test_quantity_mul_div(self, unit, func): + func(op.mul, unit * 10.0, '4.2*meter', '42*meter', unit) + func(op.mul, '4.2*meter', unit * 10.0, '42*meter', unit) + func(op.mul, '4.2*meter', '10*inch', '42*meter*inch', unit) + func(op.truediv, unit * 42, '4.2*meter', '10/meter', unit) + func(op.truediv, '4.2*meter', unit * 10.0, '0.42*meter', unit) + func(op.truediv, '4.2*meter', '10*inch', '0.42*meter/inch', unit) + + def _test_quantity_imul_idiv(self, unit, func): + #func(op.imul, 10.0, '4.2*meter', '42*meter') + func(op.imul, '4.2*meter', 10.0, '42*meter', unit) + func(op.imul, '4.2*meter', '10*inch', '42*meter*inch', unit) + #func(op.truediv, 42, '4.2*meter', '10/meter') + func(op.itruediv, '4.2*meter', unit * 10.0, '0.42*meter', unit) + func(op.itruediv, '4.2*meter', '10*inch', '0.42*meter/inch', unit) + + def _test_quantity_floordiv(self, unit, func): + func(op.floordiv, unit * 10.0, '4.2*meter', '2/meter', unit) + func(op.floordiv, '24*meter', unit * 10.0, '2*meter', unit) + func(op.floordiv, '10*meter', '4.2*inch', '2*meter/inch', unit) + + def _test_quantity_ifloordiv(self, unit, func): + func(op.ifloordiv, 10.0, '4.2*meter', '2/meter', unit) + func(op.ifloordiv, '24*meter', 10.0, '2*meter', unit) + func(op.ifloordiv, '10*meter', '4.2*inch', '2*meter/inch', unit) + + def _test_numeric(self, unit, ifunc): + self._test_quantity_add_sub(unit, self._test_not_inplace) + self._test_quantity_iadd_isub(unit, ifunc) + self._test_quantity_mul_div(unit, self._test_not_inplace) + self._test_quantity_imul_idiv(unit, ifunc) + self._test_quantity_floordiv(unit, self._test_not_inplace) + #self._test_quantity_ifloordiv(unit, ifunc) + + def test_float(self): + self._test_numeric(1., self._test_not_inplace) + + def test_fraction(self): + import fractions + self._test_numeric(fractions.Fraction(1, 1), self._test_not_inplace) + + @helpers.requires_numpy() + def test_nparray(self): + self._test_numeric(np.ones((1, 3)), self._test_inplace) + + def test_quantity_abs_round(self): + + x = self.Q_(-4.2, 'meter') + y = self.Q_(4.2, 'meter') + # In Python 3+ round of x is delegated to x.__round__, instead of round(x.__float__) + # and therefore it can be properly implemented by Pint + for fun in (abs, op.pos, op.neg) + (round, ) if PYTHON3 else (): + zx = self.Q_(fun(x.magnitude), 'meter') + zy = self.Q_(fun(y.magnitude), 'meter') + rx = fun(x) + ry = fun(y) + self.assertEqual(rx, zx, 'while testing {0}'.format(fun)) + self.assertEqual(ry, zy, 'while testing {0}'.format(fun)) + self.assertIsNot(rx, zx, 'while testing {0}'.format(fun)) + self.assertIsNot(ry, zy, 'while testing {0}'.format(fun)) + + def test_quantity_float_complex(self): + x = self.Q_(-4.2, None) + y = self.Q_(4.2, None) + z = self.Q_(1, 'meter') + for fun in (float, complex): + self.assertEqual(fun(x), fun(x.magnitude)) + self.assertEqual(fun(y), fun(y.magnitude)) + self.assertRaises(DimensionalityError, fun, z) + + +class TestDimensions(QuantityTestCase): + + FORCE_NDARRAY = False + + def test_get_dimensionality(self): + get = self.ureg.get_dimensionality + self.assertEqual(get('[time]'), UnitsContainer({'[time]': 1})) + self.assertEqual(get(UnitsContainer({'[time]': 1})), UnitsContainer({'[time]': 1})) + self.assertEqual(get('seconds'), UnitsContainer({'[time]': 1})) + self.assertEqual(get(UnitsContainer({'seconds': 1})), UnitsContainer({'[time]': 1})) + self.assertEqual(get('[speed]'), UnitsContainer({'[length]': 1, '[time]': -1})) + self.assertEqual(get('[acceleration]'), UnitsContainer({'[length]': 1, '[time]': -2})) + + def test_dimensionality(self): + x = self.Q_(42, 'centimeter') + x.to_base_units() + x = self.Q_(42, 'meter*second') + self.assertEqual(x.dimensionality, UnitsContainer({'[length]': 1., '[time]': 1.})) + x = self.Q_(42, 'meter*second*second') + self.assertEqual(x.dimensionality, UnitsContainer({'[length]': 1., '[time]': 2.})) + x = self.Q_(42, 'inch*second*second') + self.assertEqual(x.dimensionality, UnitsContainer({'[length]': 1., '[time]': 2.})) + self.assertTrue(self.Q_(42, None).dimensionless) + self.assertFalse(self.Q_(42, 'meter').dimensionless) + self.assertTrue((self.Q_(42, 'meter') / self.Q_(1, 'meter')).dimensionless) + self.assertFalse((self.Q_(42, 'meter') / self.Q_(1, 'second')).dimensionless) + self.assertTrue((self.Q_(42, 'meter') / self.Q_(1, 'inch')).dimensionless) + + +class TestQuantityWithDefaultRegistry(TestDimensions): + + @classmethod + def setUpClass(cls): + from pint import _DEFAULT_REGISTRY + cls.ureg = _DEFAULT_REGISTRY + cls.Q_ = cls.ureg.Quantity + + +class TestDimensionsWithDefaultRegistry(TestDimensions): + + @classmethod + def setUpClass(cls): + from pint import _DEFAULT_REGISTRY + cls.ureg = _DEFAULT_REGISTRY + cls.Q_ = cls.ureg.Quantity + + +class TestOffsetUnitMath(QuantityTestCase, ParameterizedTestCase): + + def setup(self): + self.ureg.autoconvert_offset_to_baseunit = False + self.ureg.default_as_delta = True + + additions = [ + # --- input tuple -------------------- | -- expected result -- + (((100, 'kelvin'), (10, 'kelvin')), (110, 'kelvin')), + (((100, 'kelvin'), (10, 'degC')), 'error'), + (((100, 'kelvin'), (10, 'degF')), 'error'), + (((100, 'kelvin'), (10, 'degR')), (105.56, 'kelvin')), + (((100, 'kelvin'), (10, 'delta_degC')), (110, 'kelvin')), + (((100, 'kelvin'), (10, 'delta_degF')), (105.56, 'kelvin')), + + (((100, 'degC'), (10, 'kelvin')), 'error'), + (((100, 'degC'), (10, 'degC')), 'error'), + (((100, 'degC'), (10, 'degF')), 'error'), + (((100, 'degC'), (10, 'degR')), 'error'), + (((100, 'degC'), (10, 'delta_degC')), (110, 'degC')), + (((100, 'degC'), (10, 'delta_degF')), (105.56, 'degC')), + + (((100, 'degF'), (10, 'kelvin')), 'error'), + (((100, 'degF'), (10, 'degC')), 'error'), + (((100, 'degF'), (10, 'degF')), 'error'), + (((100, 'degF'), (10, 'degR')), 'error'), + (((100, 'degF'), (10, 'delta_degC')), (118, 'degF')), + (((100, 'degF'), (10, 'delta_degF')), (110, 'degF')), + + (((100, 'degR'), (10, 'kelvin')), (118, 'degR')), + (((100, 'degR'), (10, 'degC')), 'error'), + (((100, 'degR'), (10, 'degF')), 'error'), + (((100, 'degR'), (10, 'degR')), (110, 'degR')), + (((100, 'degR'), (10, 'delta_degC')), (118, 'degR')), + (((100, 'degR'), (10, 'delta_degF')), (110, 'degR')), + + (((100, 'delta_degC'), (10, 'kelvin')), (110, 'kelvin')), + (((100, 'delta_degC'), (10, 'degC')), (110, 'degC')), + (((100, 'delta_degC'), (10, 'degF')), (190, 'degF')), + (((100, 'delta_degC'), (10, 'degR')), (190, 'degR')), + (((100, 'delta_degC'), (10, 'delta_degC')), (110, 'delta_degC')), + (((100, 'delta_degC'), (10, 'delta_degF')), (105.56, 'delta_degC')), + + (((100, 'delta_degF'), (10, 'kelvin')), (65.56, 'kelvin')), + (((100, 'delta_degF'), (10, 'degC')), (65.56, 'degC')), + (((100, 'delta_degF'), (10, 'degF')), (110, 'degF')), + (((100, 'delta_degF'), (10, 'degR')), (110, 'degR')), + (((100, 'delta_degF'), (10, 'delta_degC')), (118, 'delta_degF')), + (((100, 'delta_degF'), (10, 'delta_degF')), (110, 'delta_degF')), + ] + + @ParameterizedTestCase.parameterize(("input", "expected_output"), + additions) + def test_addition(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = False + qin1, qin2 = input_tuple + q1, q2 = self.Q_(*qin1), self.Q_(*qin2) + # update input tuple with new values to have correct values on failure + input_tuple = q1, q2 + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.add, q1, q2) + else: + expected = self.Q_(*expected) + self.assertEqual(op.add(q1, q2).units, expected.units) + self.assertQuantityAlmostEqual(op.add(q1, q2), expected, + atol=0.01) + + @helpers.requires_numpy() + @ParameterizedTestCase.parameterize(("input", "expected_output"), + additions) + def test_inplace_addition(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = False + (q1v, q1u), (q2v, q2u) = input_tuple + # update input tuple with new values to have correct values on failure + input_tuple = ((np.array([q1v]*2, dtype=np.float), q1u), + (np.array([q2v]*2, dtype=np.float), q2u)) + Q_ = self.Q_ + qin1, qin2 = input_tuple + q1, q2 = Q_(*qin1), Q_(*qin2) + q1_cp = copy.copy(q1) + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.iadd, q1_cp, q2) + else: + expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + self.assertEqual(op.iadd(q1_cp, q2).units, Q_(*expected).units) + q1_cp = copy.copy(q1) + self.assertQuantityAlmostEqual(op.iadd(q1_cp, q2), Q_(*expected), + atol=0.01) + + subtractions = [ + (((100, 'kelvin'), (10, 'kelvin')), (90, 'kelvin')), + (((100, 'kelvin'), (10, 'degC')), (-183.15, 'kelvin')), + (((100, 'kelvin'), (10, 'degF')), (-160.93, 'kelvin')), + (((100, 'kelvin'), (10, 'degR')), (94.44, 'kelvin')), + (((100, 'kelvin'), (10, 'delta_degC')), (90, 'kelvin')), + (((100, 'kelvin'), (10, 'delta_degF')), (94.44, 'kelvin')), + + (((100, 'degC'), (10, 'kelvin')), (363.15, 'delta_degC')), + (((100, 'degC'), (10, 'degC')), (90, 'delta_degC')), + (((100, 'degC'), (10, 'degF')), (112.22, 'delta_degC')), + (((100, 'degC'), (10, 'degR')), (367.59, 'delta_degC')), + (((100, 'degC'), (10, 'delta_degC')), (90, 'degC')), + (((100, 'degC'), (10, 'delta_degF')), (94.44, 'degC')), + + (((100, 'degF'), (10, 'kelvin')), (541.67, 'delta_degF')), + (((100, 'degF'), (10, 'degC')), (50, 'delta_degF')), + (((100, 'degF'), (10, 'degF')), (90, 'delta_degF')), + (((100, 'degF'), (10, 'degR')), (549.67, 'delta_degF')), + (((100, 'degF'), (10, 'delta_degC')), (82, 'degF')), + (((100, 'degF'), (10, 'delta_degF')), (90, 'degF')), + + (((100, 'degR'), (10, 'kelvin')), (82, 'degR')), + (((100, 'degR'), (10, 'degC')), (-409.67, 'degR')), + (((100, 'degR'), (10, 'degF')), (-369.67, 'degR')), + (((100, 'degR'), (10, 'degR')), (90, 'degR')), + (((100, 'degR'), (10, 'delta_degC')), (82, 'degR')), + (((100, 'degR'), (10, 'delta_degF')), (90, 'degR')), + + (((100, 'delta_degC'), (10, 'kelvin')), (90, 'kelvin')), + (((100, 'delta_degC'), (10, 'degC')), (90, 'degC')), + (((100, 'delta_degC'), (10, 'degF')), (170, 'degF')), + (((100, 'delta_degC'), (10, 'degR')), (170, 'degR')), + (((100, 'delta_degC'), (10, 'delta_degC')), (90, 'delta_degC')), + (((100, 'delta_degC'), (10, 'delta_degF')), (94.44, 'delta_degC')), + + (((100, 'delta_degF'), (10, 'kelvin')), (45.56, 'kelvin')), + (((100, 'delta_degF'), (10, 'degC')), (45.56, 'degC')), + (((100, 'delta_degF'), (10, 'degF')), (90, 'degF')), + (((100, 'delta_degF'), (10, 'degR')), (90, 'degR')), + (((100, 'delta_degF'), (10, 'delta_degC')), (82, 'delta_degF')), + (((100, 'delta_degF'), (10, 'delta_degF')), (90, 'delta_degF')), + ] + + @ParameterizedTestCase.parameterize(("input", "expected_output"), + subtractions) + def test_subtraction(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = False + qin1, qin2 = input_tuple + q1, q2 = self.Q_(*qin1), self.Q_(*qin2) + input_tuple = q1, q2 + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.sub, q1, q2) + else: + expected = self.Q_(*expected) + self.assertEqual(op.sub(q1, q2).units, expected.units) + self.assertQuantityAlmostEqual(op.sub(q1, q2), expected, + atol=0.01) + +# @unittest.expectedFailure + @helpers.requires_numpy() + @ParameterizedTestCase.parameterize(("input", "expected_output"), + subtractions) + def test_inplace_subtraction(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = False + (q1v, q1u), (q2v, q2u) = input_tuple + # update input tuple with new values to have correct values on failure + input_tuple = ((np.array([q1v]*2, dtype=np.float), q1u), + (np.array([q2v]*2, dtype=np.float), q2u)) + Q_ = self.Q_ + qin1, qin2 = input_tuple + q1, q2 = Q_(*qin1), Q_(*qin2) + q1_cp = copy.copy(q1) + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.isub, q1_cp, q2) + else: + expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + self.assertEqual(op.isub(q1_cp, q2).units, Q_(*expected).units) + q1_cp = copy.copy(q1) + self.assertQuantityAlmostEqual(op.isub(q1_cp, q2), Q_(*expected), + atol=0.01) + + multiplications = [ + (((100, 'kelvin'), (10, 'kelvin')), (1000, 'kelvin**2')), + (((100, 'kelvin'), (10, 'degC')), 'error'), + (((100, 'kelvin'), (10, 'degF')), 'error'), + (((100, 'kelvin'), (10, 'degR')), (1000, 'kelvin*degR')), + (((100, 'kelvin'), (10, 'delta_degC')), (1000, 'kelvin*delta_degC')), + (((100, 'kelvin'), (10, 'delta_degF')), (1000, 'kelvin*delta_degF')), + + (((100, 'degC'), (10, 'kelvin')), 'error'), + (((100, 'degC'), (10, 'degC')), 'error'), + (((100, 'degC'), (10, 'degF')), 'error'), + (((100, 'degC'), (10, 'degR')), 'error'), + (((100, 'degC'), (10, 'delta_degC')), 'error'), + (((100, 'degC'), (10, 'delta_degF')), 'error'), + + (((100, 'degF'), (10, 'kelvin')), 'error'), + (((100, 'degF'), (10, 'degC')), 'error'), + (((100, 'degF'), (10, 'degF')), 'error'), + (((100, 'degF'), (10, 'degR')), 'error'), + (((100, 'degF'), (10, 'delta_degC')), 'error'), + (((100, 'degF'), (10, 'delta_degF')), 'error'), + + (((100, 'degR'), (10, 'kelvin')), (1000, 'degR*kelvin')), + (((100, 'degR'), (10, 'degC')), 'error'), + (((100, 'degR'), (10, 'degF')), 'error'), + (((100, 'degR'), (10, 'degR')), (1000, 'degR**2')), + (((100, 'degR'), (10, 'delta_degC')), (1000, 'degR*delta_degC')), + (((100, 'degR'), (10, 'delta_degF')), (1000, 'degR*delta_degF')), + + (((100, 'delta_degC'), (10, 'kelvin')), (1000, 'delta_degC*kelvin')), + (((100, 'delta_degC'), (10, 'degC')), 'error'), + (((100, 'delta_degC'), (10, 'degF')), 'error'), + (((100, 'delta_degC'), (10, 'degR')), (1000, 'delta_degC*degR')), + (((100, 'delta_degC'), (10, 'delta_degC')), (1000, 'delta_degC**2')), + (((100, 'delta_degC'), (10, 'delta_degF')), (1000, 'delta_degC*delta_degF')), + + (((100, 'delta_degF'), (10, 'kelvin')), (1000, 'delta_degF*kelvin')), + (((100, 'delta_degF'), (10, 'degC')), 'error'), + (((100, 'delta_degF'), (10, 'degF')), 'error'), + (((100, 'delta_degF'), (10, 'degR')), (1000, 'delta_degF*degR')), + (((100, 'delta_degF'), (10, 'delta_degC')), (1000, 'delta_degF*delta_degC')), + (((100, 'delta_degF'), (10, 'delta_degF')), (1000, 'delta_degF**2')), + ] + + @ParameterizedTestCase.parameterize(("input", "expected_output"), + multiplications) + def test_multiplication(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = False + qin1, qin2 = input_tuple + q1, q2 = self.Q_(*qin1), self.Q_(*qin2) + input_tuple = q1, q2 + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2) + else: + expected = self.Q_(*expected) + self.assertEqual(op.mul(q1, q2).units, expected.units) + self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, + atol=0.01) + + @helpers.requires_numpy() + @ParameterizedTestCase.parameterize(("input", "expected_output"), + multiplications) + def test_inplace_multiplication(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = False + (q1v, q1u), (q2v, q2u) = input_tuple + # update input tuple with new values to have correct values on failure + input_tuple = ((np.array([q1v]*2, dtype=np.float), q1u), + (np.array([q2v]*2, dtype=np.float), q2u)) + Q_ = self.Q_ + qin1, qin2 = input_tuple + q1, q2 = Q_(*qin1), Q_(*qin2) + q1_cp = copy.copy(q1) + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.imul, q1_cp, q2) + else: + expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + self.assertEqual(op.imul(q1_cp, q2).units, Q_(*expected).units) + q1_cp = copy.copy(q1) + self.assertQuantityAlmostEqual(op.imul(q1_cp, q2), Q_(*expected), + atol=0.01) + + divisions = [ + (((100, 'kelvin'), (10, 'kelvin')), (10, '')), + (((100, 'kelvin'), (10, 'degC')), 'error'), + (((100, 'kelvin'), (10, 'degF')), 'error'), + (((100, 'kelvin'), (10, 'degR')), (10, 'kelvin/degR')), + (((100, 'kelvin'), (10, 'delta_degC')), (10, 'kelvin/delta_degC')), + (((100, 'kelvin'), (10, 'delta_degF')), (10, 'kelvin/delta_degF')), + + (((100, 'degC'), (10, 'kelvin')), 'error'), + (((100, 'degC'), (10, 'degC')), 'error'), + (((100, 'degC'), (10, 'degF')), 'error'), + (((100, 'degC'), (10, 'degR')), 'error'), + (((100, 'degC'), (10, 'delta_degC')), 'error'), + (((100, 'degC'), (10, 'delta_degF')), 'error'), + + (((100, 'degF'), (10, 'kelvin')), 'error'), + (((100, 'degF'), (10, 'degC')), 'error'), + (((100, 'degF'), (10, 'degF')), 'error'), + (((100, 'degF'), (10, 'degR')), 'error'), + (((100, 'degF'), (10, 'delta_degC')), 'error'), + (((100, 'degF'), (10, 'delta_degF')), 'error'), + + (((100, 'degR'), (10, 'kelvin')), (10, 'degR/kelvin')), + (((100, 'degR'), (10, 'degC')), 'error'), + (((100, 'degR'), (10, 'degF')), 'error'), + (((100, 'degR'), (10, 'degR')), (10, '')), + (((100, 'degR'), (10, 'delta_degC')), (10, 'degR/delta_degC')), + (((100, 'degR'), (10, 'delta_degF')), (10, 'degR/delta_degF')), + + (((100, 'delta_degC'), (10, 'kelvin')), (10, 'delta_degC/kelvin')), + (((100, 'delta_degC'), (10, 'degC')), 'error'), + (((100, 'delta_degC'), (10, 'degF')), 'error'), + (((100, 'delta_degC'), (10, 'degR')), (10, 'delta_degC/degR')), + (((100, 'delta_degC'), (10, 'delta_degC')), (10, '')), + (((100, 'delta_degC'), (10, 'delta_degF')), (10, 'delta_degC/delta_degF')), + + (((100, 'delta_degF'), (10, 'kelvin')), (10, 'delta_degF/kelvin')), + (((100, 'delta_degF'), (10, 'degC')), 'error'), + (((100, 'delta_degF'), (10, 'degF')), 'error'), + (((100, 'delta_degF'), (10, 'degR')), (10, 'delta_degF/degR')), + (((100, 'delta_degF'), (10, 'delta_degC')), (10, 'delta_degF/delta_degC')), + (((100, 'delta_degF'), (10, 'delta_degF')), (10, '')), + ] + + @ParameterizedTestCase.parameterize(("input", "expected_output"), + divisions) + def test_truedivision(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = False + qin1, qin2 = input_tuple + q1, q2 = self.Q_(*qin1), self.Q_(*qin2) + input_tuple = q1, q2 + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.truediv, q1, q2) + else: + expected = self.Q_(*expected) + self.assertEqual(op.truediv(q1, q2).units, expected.units) + self.assertQuantityAlmostEqual(op.truediv(q1, q2), expected, + atol=0.01) + + @helpers.requires_numpy() + @ParameterizedTestCase.parameterize(("input", "expected_output"), + divisions) + def test_inplace_truedivision(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = False + (q1v, q1u), (q2v, q2u) = input_tuple + # update input tuple with new values to have correct values on failure + input_tuple = ((np.array([q1v]*2, dtype=np.float), q1u), + (np.array([q2v]*2, dtype=np.float), q2u)) + Q_ = self.Q_ + qin1, qin2 = input_tuple + q1, q2 = Q_(*qin1), Q_(*qin2) + q1_cp = copy.copy(q1) + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.itruediv, q1_cp, q2) + else: + expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + self.assertEqual(op.itruediv(q1_cp, q2).units, Q_(*expected).units) + q1_cp = copy.copy(q1) + self.assertQuantityAlmostEqual(op.itruediv(q1_cp, q2), + Q_(*expected), atol=0.01) + + multiplications_with_autoconvert_to_baseunit = [ + (((100, 'kelvin'), (10, 'degC')), (28315., 'kelvin**2')), + (((100, 'kelvin'), (10, 'degF')), (26092.78, 'kelvin**2')), + + (((100, 'degC'), (10, 'kelvin')), (3731.5, 'kelvin**2')), + (((100, 'degC'), (10, 'degC')), (105657.42, 'kelvin**2')), + (((100, 'degC'), (10, 'degF')), (97365.20, 'kelvin**2')), + (((100, 'degC'), (10, 'degR')), (3731.5, 'kelvin*degR')), + (((100, 'degC'), (10, 'delta_degC')), (3731.5, 'kelvin*delta_degC')), + (((100, 'degC'), (10, 'delta_degF')), (3731.5, 'kelvin*delta_degF')), + + (((100, 'degF'), (10, 'kelvin')), (3109.28, 'kelvin**2')), + (((100, 'degF'), (10, 'degC')), (88039.20, 'kelvin**2')), + (((100, 'degF'), (10, 'degF')), (81129.69, 'kelvin**2')), + (((100, 'degF'), (10, 'degR')), (3109.28, 'kelvin*degR')), + (((100, 'degF'), (10, 'delta_degC')), (3109.28, 'kelvin*delta_degC')), + (((100, 'degF'), (10, 'delta_degF')), (3109.28, 'kelvin*delta_degF')), + + (((100, 'degR'), (10, 'degC')), (28315., 'degR*kelvin')), + (((100, 'degR'), (10, 'degF')), (26092.78, 'degR*kelvin')), + + (((100, 'delta_degC'), (10, 'degC')), (28315., 'delta_degC*kelvin')), + (((100, 'delta_degC'), (10, 'degF')), (26092.78, 'delta_degC*kelvin')), + + (((100, 'delta_degF'), (10, 'degC')), (28315., 'delta_degF*kelvin')), + (((100, 'delta_degF'), (10, 'degF')), (26092.78, 'delta_degF*kelvin')), + ] + + @ParameterizedTestCase.parameterize( + ("input", "expected_output"), + multiplications_with_autoconvert_to_baseunit) + def test_multiplication_with_autoconvert(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = True + qin1, qin2 = input_tuple + q1, q2 = self.Q_(*qin1), self.Q_(*qin2) + input_tuple = q1, q2 + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2) + else: + expected = self.Q_(*expected) + self.assertEqual(op.mul(q1, q2).units, expected.units) + self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, + atol=0.01) + + @helpers.requires_numpy() + @ParameterizedTestCase.parameterize( + ("input", "expected_output"), + multiplications_with_autoconvert_to_baseunit) + def test_inplace_multiplication_with_autoconvert(self, input_tuple, expected): + self.ureg.autoconvert_offset_to_baseunit = True + (q1v, q1u), (q2v, q2u) = input_tuple + # update input tuple with new values to have correct values on failure + input_tuple = ((np.array([q1v]*2, dtype=np.float), q1u), + (np.array([q2v]*2, dtype=np.float), q2u)) + Q_ = self.Q_ + qin1, qin2 = input_tuple + q1, q2 = Q_(*qin1), Q_(*qin2) + q1_cp = copy.copy(q1) + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.imul, q1_cp, q2) + else: + expected = np.array([expected[0]]*2, dtype=np.float), expected[1] + self.assertEqual(op.imul(q1_cp, q2).units, Q_(*expected).units) + q1_cp = copy.copy(q1) + self.assertQuantityAlmostEqual(op.imul(q1_cp, q2), Q_(*expected), + atol=0.01) + + multiplications_with_scalar = [ + (((10, 'kelvin'), 2), (20., 'kelvin')), + (((10, 'kelvin**2'), 2), (20., 'kelvin**2')), + (((10, 'degC'), 2), (20., 'degC')), + (((10, '1/degC'), 2), 'error'), + (((10, 'degC**0.5'), 2), 'error'), + (((10, 'degC**2'), 2), 'error'), + (((10, 'degC**-2'), 2), 'error'), + ] + + @ParameterizedTestCase.parameterize( + ("input", "expected_output"), multiplications_with_scalar) + def test_multiplication_with_scalar(self, input_tuple, expected): + self.ureg.default_as_delta = False + in1, in2 = input_tuple + if type(in1) is tuple: + in1, in2 = self.Q_(*in1), in2 + else: + in1, in2 = in1, self.Q_(*in2) + input_tuple = in1, in2 # update input_tuple for better tracebacks + if expected == 'error': + self.assertRaises(OffsetUnitCalculusError, op.mul, in1, in2) + else: + expected = self.Q_(*expected) + self.assertEqual(op.mul(in1, in2).units, expected.units) + self.assertQuantityAlmostEqual(op.mul(in1, in2), expected, + atol=0.01) + + divisions_with_scalar = [ # without / with autoconvert to base unit + (((10, 'kelvin'), 2), [(5., 'kelvin'), (5., 'kelvin')]), + (((10, 'kelvin**2'), 2), [(5., 'kelvin**2'), (5., 'kelvin**2')]), + (((10, 'degC'), 2), ['error', 'error']), + (((10, 'degC**2'), 2), ['error', 'error']), + (((10, 'degC**-2'), 2), ['error', 'error']), + + ((2, (10, 'kelvin')), [(0.2, '1/kelvin'), (0.2, '1/kelvin')]), + ((2, (10, 'degC')), ['error', (2/283.15, '1/kelvin')]), + ((2, (10, 'degC**2')), ['error', 'error']), + ((2, (10, 'degC**-2')), ['error', 'error']), + ] + + @ParameterizedTestCase.parameterize( + ("input", "expected_output"), divisions_with_scalar) + def test_division_with_scalar(self, input_tuple, expected): + self.ureg.default_as_delta = False + in1, in2 = input_tuple + if type(in1) is tuple: + in1, in2 = self.Q_(*in1), in2 + else: + in1, in2 = in1, self.Q_(*in2) + input_tuple = in1, in2 # update input_tuple for better tracebacks + expected_copy = expected[:] + for i, mode in enumerate([False, True]): + self.ureg.autoconvert_offset_to_baseunit = mode + if expected_copy[i] == 'error': + self.assertRaises(OffsetUnitCalculusError, op.truediv, in1, in2) + else: + expected = self.Q_(*expected_copy[i]) + self.assertEqual(op.truediv(in1, in2).units, expected.units) + self.assertQuantityAlmostEqual(op.truediv(in1, in2), expected) + + exponentiation = [ # resuls without / with autoconvert + (((10, 'degC'), 1), [(10, 'degC'), (10, 'degC')]), + (((10, 'degC'), 0.5), ['error', (283.15**0.5, 'kelvin**0.5')]), + (((10, 'degC'), 0), [(1., ''), (1., '')]), + (((10, 'degC'), -1), ['error', (1/(10+273.15), 'kelvin**-1')]), + (((10, 'degC'), -2), ['error', (1/(10+273.15)**2., 'kelvin**-2')]), + ((( 0, 'degC'), -2), ['error', (1/(273.15)**2, 'kelvin**-2')]), + (((10, 'degC'), (2, '')), ['error', ((283.15)**2, 'kelvin**2')]), + (((10, 'degC'), (10, 'degK')), ['error', 'error']), + + (((10, 'kelvin'), (2, '')), [(100., 'kelvin**2'), (100., 'kelvin**2')]), + + (( 2, (2, 'kelvin')), ['error', 'error']), + (( 2, (500., 'millikelvin/kelvin')), [2**0.5, 2**0.5]), + (( 2, (0.5, 'kelvin/kelvin')), [2**0.5, 2**0.5]), + (((10, 'degC'), (500., 'millikelvin/kelvin')), + ['error', (283.15**0.5, 'kelvin**0.5')]), + ] + + @ParameterizedTestCase.parameterize( + ("input", "expected_output"), exponentiation) + def test_exponentiation(self, input_tuple, expected): + self.ureg.default_as_delta = False + in1, in2 = input_tuple + if type(in1) is tuple and type(in2) is tuple: + in1, in2 = self.Q_(*in1), self.Q_(*in2) + elif not type(in1) is tuple and type(in2) is tuple: + in2 = self.Q_(*in2) + else: + in1 = self.Q_(*in1) + input_tuple = in1, in2 + expected_copy = expected[:] + for i, mode in enumerate([False, True]): + self.ureg.autoconvert_offset_to_baseunit = mode + if expected_copy[i] == 'error': + self.assertRaises((OffsetUnitCalculusError, + DimensionalityError), op.pow, in1, in2) + else: + if type(expected_copy[i]) is tuple: + expected = self.Q_(*expected_copy[i]) + self.assertEqual(op.pow(in1, in2).units, expected.units) + else: + expected = expected_copy[i] + self.assertQuantityAlmostEqual(op.pow(in1, in2), expected) + + @helpers.requires_numpy() + @ParameterizedTestCase.parameterize( + ("input", "expected_output"), exponentiation) + def test_inplace_exponentiation(self, input_tuple, expected): + self.ureg.default_as_delta = False + in1, in2 = input_tuple + if type(in1) is tuple and type(in2) is tuple: + (q1v, q1u), (q2v, q2u) = in1, in2 + in1 = self.Q_(*(np.array([q1v]*2, dtype=np.float), q1u)) + in2 = self.Q_(q2v, q2u) + elif not type(in1) is tuple and type(in2) is tuple: + in2 = self.Q_(*in2) + else: + in1 = self.Q_(*in1) + + input_tuple = in1, in2 + + expected_copy = expected[:] + for i, mode in enumerate([False, True]): + self.ureg.autoconvert_offset_to_baseunit = mode + in1_cp = copy.copy(in1) + if expected_copy[i] == 'error': + self.assertRaises((OffsetUnitCalculusError, + DimensionalityError), op.ipow, in1_cp, in2) + else: + if type(expected_copy[i]) is tuple: + expected = self.Q_(np.array([expected_copy[i][0]]*2, + dtype=np.float), + expected_copy[i][1]) + self.assertEqual(op.ipow(in1_cp, in2).units, expected.units) + else: + expected = np.array([expected_copy[i]]*2, dtype=np.float) + + + in1_cp = copy.copy(in1) + self.assertQuantityAlmostEqual(op.ipow(in1_cp, in2), expected) diff --git a/CadQuery/Libs/pint/testsuite/test_umath.py b/CadQuery/Libs/pint/testsuite/test_umath.py new file mode 100644 index 0000000..cfbba86 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_umath.py @@ -0,0 +1,676 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +from pint.compat import np +from pint.testsuite import QuantityTestCase, helpers + +# Following http://docs.scipy.org/doc/numpy/reference/ufuncs.html + +if np: + pi = np.pi + + +@helpers.requires_numpy() +class TestUFuncs(QuantityTestCase): + + FORCE_NDARRAY = True + + @property + def qless(self): + return np.asarray([1., 2., 3., 4.]) * self.ureg.dimensionless + + @property + def qs(self): + return 8 * self.ureg.J + + @property + def q1(self): + return np.asarray([1., 2., 3., 4.]) * self.ureg.J + + @property + def q2(self): + return 2 * self.q1 + + @property + def qm(self): + return np.asarray([1., 2., 3., 4.]) * self.ureg.m + + @property + def qi(self): + return np.asarray([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j]) * self.ureg.m + + def assertEqual(self, first, second, msg=None): + np.testing.assert_equal(first, second, msg) + + def assertRaisesMsg(self, msg, ExcType, func, *args, **kwargs): + try: + func(*args, **kwargs) + self.assertFalse(True, msg='Exception {0} not raised {1}'.format(ExcType, msg)) + except ExcType as e: + pass + except Exception as e: + self.assertFalse(True, msg='{0} not raised but {1}\n{2}'.format(ExcType, e, msg)) + + def _test1(self, func, ok_with, raise_with=(), output_units='same', results=None, rtol=1e-6): + """Test function that takes a single argument and returns Quantity. + + :param func: function callable. + :param ok_with: iterables of values that work fine. + :param raise_with: iterables of values that raise exceptions. + :param output_units: units to be used when building results. + 'same': ok_with[n].units (default). + is float: ok_with[n].units ** output_units. + None: no output units, the result should be an ndarray. + Other value will be parsed as unit. + :param results: iterable of results. + If None, the result will be obtained by applying + func to each ok_with value + :param rtol: relative tolerance. + """ + if results is None: + results = [None, ] * len(ok_with) + for x1, res in zip(ok_with, results): + err_msg = 'At {0} with {1}'.format(func.__name__, x1) + if output_units == 'same': + ou = x1.units + elif isinstance(output_units, (int, float)): + ou = x1.units ** output_units + else: + ou = output_units + + qm = func(x1) + if res is None: + res = func(x1.magnitude) + if ou is not None: + res = self.Q_(res, ou) + + self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg) + + for x1 in raise_with: + self.assertRaisesMsg('At {0} with {1}'.format(func.__name__, x1), + ValueError, func, x1) + + def _testn(self, func, ok_with, raise_with=(), results=None): + """Test function that takes a single argument and returns and ndarray (not a Quantity) + + :param func: function callable. + :param ok_with: iterables of values that work fine. + :param raise_with: iterables of values that raise exceptions. + :param results: iterable of results. + If None, the result will be obtained by applying + func to each ok_with value + """ + self._test1(func, ok_with, raise_with, output_units=None, results=results) + + def _test1_2o(self, func, ok_with, raise_with=(), output_units=('same', 'same'), + results=None, rtol=1e-6): + """Test functions that takes a single argument and return two Quantities. + + :param func: function callable. + :param ok_with: iterables of values that work fine. + :param raise_with: iterables of values that raise exceptions. + :param output_units: tuple of units to be used when building the result tuple. + 'same': ok_with[n].units (default). + is float: ok_with[n].units ** output_units. + None: no output units, the result should be an ndarray. + Other value will be parsed as unit. + :param results: iterable of results. + If None, the result will be obtained by applying + func to each ok_with value + :param rtol: relative tolerance. + """ + + if results is None: + results = [None, ] * len(ok_with) + for x1, res in zip(ok_with, results): + err_msg = 'At {0} with {1}'.format(func.__name__, x1) + qms = func(x1) + if res is None: + res = func(x1.magnitude) + + for ndx, (qm, re, ou) in enumerate(zip(qms, res, output_units)): + if ou == 'same': + ou = x1.units + elif isinstance(ou, (int, float)): + ou = x1.units ** ou + + if ou is not None: + re = self.Q_(re, ou) + + self.assertQuantityAlmostEqual(qm, re, rtol=rtol, msg=err_msg) + + for x1 in raise_with: + self.assertRaisesMsg('At {0} with {1}'.format(func.__name__, x1), + ValueError, func, x1) + + def _test2(self, func, x1, ok_with, raise_with=(), output_units='same', rtol=1e-6, convert2=True): + """Test function that takes two arguments and return a Quantity. + + :param func: function callable. + :param x1: first argument of func. + :param ok_with: iterables of values that work fine. + :param raise_with: iterables of values that raise exceptions. + :param output_units: units to be used when building results. + 'same': x1.units (default). + 'prod': x1.units * ok_with[n].units + 'div': x1.units / ok_with[n].units + 'second': x1.units * ok_with[n] + None: no output units, the result should be an ndarray. + Other value will be parsed as unit. + :param rtol: relative tolerance. + :param convert2: if the ok_with[n] should be converted to x1.units. + """ + for x2 in ok_with: + err_msg = 'At {0} with {1} and {2}'.format(func.__name__, x1, x2) + if output_units == 'same': + ou = x1.units + elif output_units == 'prod': + ou = x1.units * x2.units + elif output_units == 'div': + ou = x1.units / x2.units + elif output_units == 'second': + ou = x1.units ** x2 + else: + ou = output_units + + qm = func(x1, x2) + + if convert2 and hasattr(x2, 'magnitude'): + m2 = x2.to(getattr(x1, 'units', '')).magnitude + else: + m2 = getattr(x2, 'magnitude', x2) + + res = func(x1.magnitude, m2) + if ou is not None: + res = self.Q_(res, ou) + + self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg) + + for x2 in raise_with: + self.assertRaisesMsg('At {0} with {1} and {2}'.format(func.__name__, x1, x2), + ValueError, func, x1, x2) + + def _testn2(self, func, x1, ok_with, raise_with=()): + """Test function that takes two arguments and return a ndarray. + + :param func: function callable. + :param x1: first argument of func. + :param ok_with: iterables of values that work fine. + :param raise_with: iterables of values that raise exceptions. + """ + self._test2(func, x1, ok_with, raise_with, output_units=None) + + +@helpers.requires_numpy() +class TestMathUfuncs(TestUFuncs): + """Universal functions (ufunc) > Math operations + + http://docs.scipy.org/doc/numpy/reference/ufuncs.html#math-operations + + add(x1, x2[, out]) Add arguments element-wise. + subtract(x1, x2[, out]) Subtract arguments, element-wise. + multiply(x1, x2[, out]) Multiply arguments element-wise. + divide(x1, x2[, out]) Divide arguments element-wise. + logaddexp(x1, x2[, out]) Logarithm of the sum of exponentiations of the inputs. + logaddexp2(x1, x2[, out]) Logarithm of the sum of exponentiations of the inputs in base-2. + true_divide(x1, x2[, out]) Returns a true division of the inputs, element-wise. + floor_divide(x1, x2[, out]) Return the largest integer smaller or equal to the division of the inputs. + negative(x[, out]) Returns an array with the negative of each element of the original array. + power(x1, x2[, out]) First array elements raised to powers from second array, element-wise. NOT IMPLEMENTED + remainder(x1, x2[, out]) Return element-wise remainder of division. + mod(x1, x2[, out]) Return element-wise remainder of division. + fmod(x1, x2[, out]) Return the element-wise remainder of division. + absolute(x[, out]) Calculate the absolute value element-wise. + rint(x[, out]) Round elements of the array to the nearest integer. + sign(x[, out]) Returns an element-wise indication of the sign of a number. + conj(x[, out]) Return the complex conjugate, element-wise. + exp(x[, out]) Calculate the exponential of all elements in the input array. + exp2(x[, out]) Calculate 2**p for all p in the input array. + log(x[, out]) Natural logarithm, element-wise. + log2(x[, out]) Base-2 logarithm of x. + log10(x[, out]) Return the base 10 logarithm of the input array, element-wise. + expm1(x[, out]) Calculate exp(x) - 1 for all elements in the array. + log1p(x[, out]) Return the natural logarithm of one plus the input array, element-wise. + sqrt(x[, out]) Return the positive square-root of an array, element-wise. + square(x[, out]) Return the element-wise square of the input. + reciprocal(x[, out]) Return the reciprocal of the argument, element-wise. + ones_like(x[, out]) Returns an array of ones with the same shape and type as a given array. + """ + def test_add(self): + self._test2(np.add, + self.q1, + (self.q2, self.qs), + (self.qm, )) + + def test_subtract(self): + self._test2(np.subtract, + self.q1, + (self.q2, self.qs), + (self.qm, )) + + def test_multiply(self): + self._test2(np.multiply, + self.q1, + (self.q2, self.qs), (), + 'prod') + + def test_divide(self): + self._test2(np.divide, + self.q1, + (self.q2, self.qs, self.qless), + (), + 'div', convert2=False) + + def test_logaddexp(self): + self._test2(np.logaddexp, + self.qless, + (self.qless, ), + (self.q1, ), + '') + + def test_logaddexp2(self): + self._test2(np.logaddexp2, + self.qless, + (self.qless, ), + (self.q1, ), + 'div') + + def test_true_divide(self): + self._test2(np.true_divide, + self.q1, + (self.q2, self.qs, self.qless), + (), + 'div', convert2=False) + + def test_floor_divide(self): + self._test2(np.floor_divide, + self.q1, + (self.q2, self.qs, self.qless), + (), + 'div', convert2=False) + + + def test_negative(self): + self._test1(np.negative, + (self.qless, self.q1), + ()) + + def test_remainder(self): + self._test2(np.remainder, + self.q1, + (self.q2, self.qs, self.qless), + (), + 'same', convert2=False) + + def test_mod(self): + self._test2(np.mod, + self.q1, + (self.q2, self.qs, self.qless), + (), + 'same', convert2=False) + + def test_fmod(self): + self._test2(np.fmod, + self.q1, + (self.q2, self.qs, self.qless), + (), + 'same', convert2=False) + + def test_absolute(self): + self._test1(np.absolute, + (self.q2, self.qs, self.qless, self.qi), + (), + 'same') + + def test_rint(self): + self._test1(np.rint, + (self.q2, self.qs, self.qless, self.qi), + (), + 'same') + + def test_conj(self): + self._test1(np.conj, + (self.q2, self.qs, self.qless, self.qi), + (), + 'same') + + def test_exp(self): + self._test1(np.exp, + (self.qless, ), + (self.q1, ), + '') + + def test_exp2(self): + self._test1(np.exp2, + (self.qless,), + (self.q1, ), + '') + + def test_log(self): + self._test1(np.log, + (self.qless,), + (self.q1, ), + '') + + def test_log2(self): + self._test1(np.log2, + (self.qless,), + (self.q1, ), + '') + + def test_log10(self): + self._test1(np.log10, + (self.qless,), + (self.q1, ), + '') + + def test_expm1(self): + self._test1(np.expm1, + (self.qless,), + (self.q1, ), + '') + + def test_sqrt(self): + self._test1(np.sqrt, + (self.q2, self.qs, self.qless, self.qi), + (), + 0.5) + + def test_square(self): + self._test1(np.square, + (self.q2, self.qs, self.qless, self.qi), + (), + 2) + + def test_reciprocal(self): + self._test1(np.reciprocal, + (self.q2, self.qs, self.qless, self.qi), + (), + -1) + + +@helpers.requires_numpy() +class TestTrigUfuncs(TestUFuncs): + """Universal functions (ufunc) > Trigonometric functions + + http://docs.scipy.org/doc/numpy/reference/ufuncs.html#trigonometric-functions + + sin(x[, out]) Trigonometric sine, element-wise. + cos(x[, out]) Cosine elementwise. + tan(x[, out]) Compute tangent element-wise. + arcsin(x[, out]) Inverse sine, element-wise. + arccos(x[, out]) Trigonometric inverse cosine, element-wise. + arctan(x[, out]) Trigonometric inverse tangent, element-wise. + arctan2(x1, x2[, out]) Element-wise arc tangent of x1/x2 choosing the quadrant correctly. + hypot(x1, x2[, out]) Given the “legs” of a right triangle, return its hypotenuse. + sinh(x[, out]) Hyperbolic sine, element-wise. + cosh(x[, out]) Hyperbolic cosine, element-wise. + tanh(x[, out]) Compute hyperbolic tangent element-wise. + arcsinh(x[, out]) Inverse hyperbolic sine elementwise. + arccosh(x[, out]) Inverse hyperbolic cosine, elementwise. + arctanh(x[, out]) Inverse hyperbolic tangent elementwise. + deg2rad(x[, out]) Convert angles from degrees to radians. + rad2deg(x[, out]) Convert angles from radians to degrees. + """ + + def test_sin(self): + self._test1(np.sin, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, + np.arange(0, pi/2, pi/4) * self.ureg.radian, + np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m + ), (self.ureg.m, ), '', results=(None, None, np.sin(np.arange(0, pi/2, pi/4)*0.001))) + self._test1(np.sin, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, + ), results=(np.sin(np.arange(0, pi/2, pi/4)), )) + + def test_cos(self): + self._test1(np.cos, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, + np.arange(0, pi/2, pi/4) * self.ureg.radian, + np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m, + ), (self.ureg.m, ), '', + results=(None, + None, + np.cos(np.arange(0, pi/2, pi/4)*0.001), + ) + ) + self._test1(np.cos, + (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, + ), + results=(np.cos(np.arange(0, pi/2, pi/4)), ) + ) + + def test_tan(self): + self._test1(np.tan, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, + np.arange(0, pi/2, pi/4) * self.ureg.radian, + np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m + ), (self.ureg.m, ), '', results=(None, None, np.tan(np.arange(0, pi/2, pi/4)*0.001))) + self._test1(np.tan, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, + ), results=(np.tan(np.arange(0, pi/2, pi/4)), )) + + def test_arcsin(self): + self._test1(np.arcsin, (np.arange(0, .9, .1) * self.ureg.dimensionless, + np.arange(0, .9, .1) * self.ureg.m / self.ureg.m + ), (self.ureg.m, ), 'radian') + + def test_arccos(self): + x = np.arange(0, .9, .1) * self.ureg.m + self._test1(np.arccos, (np.arange(0, .9, .1) * self.ureg.dimensionless, + np.arange(0, .9, .1) * self.ureg.m / self.ureg.m + ), (self.ureg.m, ), 'radian') + + def test_arctan(self): + self._test1(np.arctan, (np.arange(0, .9, .1) * self.ureg.dimensionless, + np.arange(0, .9, .1) * self.ureg.m / self.ureg.m + ), (self.ureg.m, ), 'radian') + + def test_arctan2(self): + m = self.ureg.m + j = self.ureg.J + km = self.ureg.km + self._test2(np.arctan2, np.arange(0, .9, .1) * m, + (np.arange(0, .9, .1) * m, np.arange(.9, 0., -.1) * m, + np.arange(0, .9, .1) * km, np.arange(.9, 0., -.1) * km, + ), + raise_with=np.arange(0, .9, .1) * j, + output_units='radian') + + def test_hypot(self): + self.assertTrue(np.hypot(3. * self.ureg.m, 4. * self.ureg.m) == 5. * self.ureg.m) + self.assertTrue(np.hypot(3. * self.ureg.m, 400. * self.ureg.cm) == 5. * self.ureg.m) + self.assertRaises(ValueError, np.hypot, 1. * self.ureg.m, 2. * self.ureg.J) + + def test_sinh(self): + self._test1(np.sinh, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, + np.arange(0, pi/2, pi/4) * self.ureg.radian, + np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m + ), (self.ureg.m, ), '', results=(None, None, np.sinh(np.arange(0, pi/2, pi/4)*0.001))) + self._test1(np.sinh, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, + ), results=(np.sinh(np.arange(0, pi/2, pi/4)), )) + + def test_cosh(self): + self._test1(np.cosh, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, + np.arange(0, pi/2, pi/4) * self.ureg.radian, + np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m + ), (self.ureg.m, ), '', results=(None, None, np.cosh(np.arange(0, pi/2, pi/4)*0.001))) + self._test1(np.cosh, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, + ), results=(np.cosh(np.arange(0, pi/2, pi/4)), )) + + def test_tanh(self): + self._test1(np.tanh, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, + np.arange(0, pi/2, pi/4) * self.ureg.radian, + np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m + ), (self.ureg.m, ), '', results=(None, None, np.tanh(np.arange(0, pi/2, pi/4)*0.001))) + self._test1(np.tanh, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, + ), results=(np.tanh(np.arange(0, pi/2, pi/4)), )) + + def test_arcsinh(self): + self._test1(np.arcsinh, (np.arange(0, .9, .1) * self.ureg.dimensionless, + np.arange(0, .9, .1) * self.ureg.m / self.ureg.m + ), (self.ureg.m, ), 'radian') + + def test_arccosh(self): + self._test1(np.arccosh, (np.arange(1., 1.9, .1) * self.ureg.dimensionless, + np.arange(1., 1.9, .1) * self.ureg.m / self.ureg.m + ), (self.ureg.m, ), 'radian') + + def test_arctanh(self): + self._test1(np.arctanh, (np.arange(0, .9, .1) * self.ureg.dimensionless, + np.arange(0, .9, .1) * self.ureg.m / self.ureg.m + ), (.1 * self.ureg.m, ), 'radian') + + def test_deg2rad(self): + self._test1(np.deg2rad, (np.arange(0, pi/2, pi/4) * self.ureg.degrees, + ), (self.ureg.m, ), 'radians') + + def test_rad2deg(self): + self._test1(np.rad2deg, + (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, + np.arange(0, pi/2, pi/4) * self.ureg.radian, + np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m, + ), + (self.ureg.m, ), 'degree', + results=(None, + None, + np.rad2deg(np.arange(0, pi/2, pi/4)*0.001) * self.ureg.degree, + )) + + + +class TestComparisonUfuncs(TestUFuncs): + """Universal functions (ufunc) > Comparison functions + + http://docs.scipy.org/doc/numpy/reference/ufuncs.html#comparison-functions + + greater(x1, x2[, out]) Return the truth value of (x1 > x2) element-wise. + greater_equal(x1, x2[, out]) Return the truth value of (x1 >= x2) element-wise. + less(x1, x2[, out]) Return the truth value of (x1 < x2) element-wise. + less_equal(x1, x2[, out]) Return the truth value of (x1 =< x2) element-wise. + not_equal(x1, x2[, out]) Return (x1 != x2) element-wise. + equal(x1, x2[, out]) Return (x1 == x2) element-wise. + """ + + def test_greater(self): + self._testn2(np.greater, + self.q1, + (self.q2, ), + (self.qm, )) + + def test_greater_equal(self): + self._testn2(np.greater_equal, + self.q1, + (self.q2, ), + (self.qm, )) + + def test_less(self): + self._testn2(np.less, + self.q1, + (self.q2, ), + (self.qm, )) + + def test_less_equal(self): + self._testn2(np.less_equal, + self.q1, + (self.q2, ), + (self.qm, )) + + def test_not_equal(self): + self._testn2(np.not_equal, + self.q1, + (self.q2, ), + (self.qm, )) + + def test_equal(self): + self._testn2(np.equal, + self.q1, + (self.q2, ), + (self.qm, )) + + +class TestFloatingUfuncs(TestUFuncs): + """Universal functions (ufunc) > Floating functions + + http://docs.scipy.org/doc/numpy/reference/ufuncs.html#floating-functions + + isreal(x) Returns a bool array, where True if input element is real. + iscomplex(x) Returns a bool array, where True if input element is complex. + isfinite(x[, out]) Test element-wise for finite-ness (not infinity or not Not a Number). + isinf(x[, out]) Test element-wise for positive or negative infinity. + isnan(x[, out]) Test element-wise for Not a Number (NaN), return result as a bool array. + signbit(x[, out]) Returns element-wise True where signbit is set (less than zero). + copysign(x1, x2[, out]) Change the sign of x1 to that of x2, element-wise. + nextafter(x1, x2[, out]) Return the next representable floating-point value after x1 in the direction of x2 element-wise. + modf(x[, out1, out2]) Return the fractional and integral parts of an array, element-wise. + ldexp(x1, x2[, out]) Compute y = x1 * 2**x2. + frexp(x[, out1, out2]) Split the number, x, into a normalized fraction (y1) and exponent (y2) + fmod(x1, x2[, out]) Return the element-wise remainder of division. + floor(x[, out]) Return the floor of the input, element-wise. + ceil(x[, out]) Return the ceiling of the input, element-wise. + trunc(x[, out]) Return the truncated value of the input, element-wise. + """ + + def test_isreal(self): + self._testn(np.isreal, + (self.q1, self.qm, self.qless)) + + def test_iscomplex(self): + self._testn(np.iscomplex, + (self.q1, self.qm, self.qless)) + + def test_isfinite(self): + self._testn(np.isreal, + (self.q1, self.qm, self.qless)) + + def test_isinf(self): + self._testn(np.isinf, + (self.q1, self.qm, self.qless)) + + def test_isnan(self): + self._testn(np.isnan, + (self.q1, self.qm, self.qless)) + + def test_signbit(self): + self._testn(np.signbit, + (self.q1, self.qm, self.qless)) + + def test_copysign(self): + self._test2(np.copysign, + self.q1, + (self.q2, self.qs), + (self.qm, )) + + def test_nextafter(self): + self._test2(np.nextafter, + self.q1, + (self.q2, self.qs), + (self.qm, )) + + def test_modf(self): + self._test1_2o(np.modf, + (self.q2, self.qs), + ) + + def test_ldexp(self): + x1, x2 = np.frexp(self.q2) + self._test2(np.ldexp, + x1, + (x2, )) + + def test_frexp(self): + self._test1_2o(np.frexp, + (self.q2, self.qs), + output_units=('same', None)) + + def test_fmod(self): + # See TestMathUfuncs.test_fmod + pass + + def test_floor(self): + self._test1(np.floor, + (self.q1, self.qm, self.qless)) + + def test_ceil(self): + self._test1(np.ceil, + (self.q1, self.qm, self.qless)) + + def test_trunc(self): + self._test1(np.trunc, + (self.q1, self.qm, self.qless)) diff --git a/CadQuery/Libs/pint/testsuite/test_unit.py b/CadQuery/Libs/pint/testsuite/test_unit.py new file mode 100644 index 0000000..c0f4602 --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_unit.py @@ -0,0 +1,642 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import math +import copy +import itertools +import operator as op + +from pint.unit import (ScaleConverter, OffsetConverter, UnitsContainer, + Definition, PrefixDefinition, UnitDefinition, + DimensionDefinition, _freeze, Converter, UnitRegistry, + LazyRegistry, ParserHelper) +from pint import DimensionalityError, UndefinedUnitError +from pint.compat import u, unittest, np, string_types +from pint.testsuite import QuantityTestCase, helpers, BaseTestCase +from pint.testsuite.parameterized import ParameterizedTestCase + + +class TestConverter(BaseTestCase): + + def test_converter(self): + c = Converter() + self.assertTrue(c.is_multiplicative) + self.assertTrue(c.to_reference(8)) + self.assertTrue(c.from_reference(8)) + + def test_multiplicative_converter(self): + c = ScaleConverter(20.) + self.assertEqual(c.from_reference(c.to_reference(100)), 100) + self.assertEqual(c.to_reference(c.from_reference(100)), 100) + + def test_offset_converter(self): + c = OffsetConverter(20., 2) + self.assertEqual(c.from_reference(c.to_reference(100)), 100) + self.assertEqual(c.to_reference(c.from_reference(100)), 100) + + @helpers.requires_numpy() + def test_converter_inplace(self): + for c in (ScaleConverter(20.), OffsetConverter(20., 2)): + fun1 = lambda x, y: c.from_reference(c.to_reference(x, y), y) + fun2 = lambda x, y: c.to_reference(c.from_reference(x, y), y) + for fun, (inplace, comp) in itertools.product((fun1, fun2), + ((True, self.assertIs), (False, self.assertIsNot))): + a = np.ones((1, 10)) + ac = np.ones((1, 10)) + r = fun(a, inplace) + np.testing.assert_allclose(r, ac) + comp(a, r) + + +class TestDefinition(BaseTestCase): + + def test_invalid(self): + self.assertRaises(ValueError, Definition.from_string, 'x = [time] * meter') + self.assertRaises(ValueError, Definition.from_string, '[x] = [time] * meter') + + def test_prefix_definition(self): + for definition in ('m- = 1e-3', 'm- = 10**-3', 'm- = 0.001'): + x = Definition.from_string(definition) + self.assertIsInstance(x, PrefixDefinition) + self.assertEqual(x.name, 'm') + self.assertEqual(x.aliases, ()) + self.assertEqual(x.converter.to_reference(1000), 1) + self.assertEqual(x.converter.from_reference(0.001), 1) + self.assertEqual(str(x), 'm') + + x = Definition.from_string('kilo- = 1e-3 = k-') + self.assertIsInstance(x, PrefixDefinition) + self.assertEqual(x.name, 'kilo') + self.assertEqual(x.aliases, ()) + self.assertEqual(x.symbol, 'k') + self.assertEqual(x.converter.to_reference(1000), 1) + self.assertEqual(x.converter.from_reference(.001), 1) + + x = Definition.from_string('kilo- = 1e-3 = k- = anotherk-') + self.assertIsInstance(x, PrefixDefinition) + self.assertEqual(x.name, 'kilo') + self.assertEqual(x.aliases, ('anotherk', )) + self.assertEqual(x.symbol, 'k') + self.assertEqual(x.converter.to_reference(1000), 1) + self.assertEqual(x.converter.from_reference(.001), 1) + + def test_baseunit_definition(self): + x = Definition.from_string('meter = [length]') + self.assertIsInstance(x, UnitDefinition) + self.assertTrue(x.is_base) + self.assertEqual(x.reference, UnitsContainer({'[length]': 1})) + + def test_unit_definition(self): + x = Definition.from_string('coulomb = ampere * second') + self.assertIsInstance(x, UnitDefinition) + self.assertFalse(x.is_base) + self.assertIsInstance(x.converter, ScaleConverter) + self.assertEqual(x.converter.scale, 1) + self.assertEqual(x.reference, UnitsContainer(ampere=1, second=1)) + + x = Definition.from_string('faraday = 96485.3399 * coulomb') + self.assertIsInstance(x, UnitDefinition) + self.assertFalse(x.is_base) + self.assertIsInstance(x.converter, ScaleConverter) + self.assertEqual(x.converter.scale, 96485.3399) + self.assertEqual(x.reference, UnitsContainer(coulomb=1)) + + x = Definition.from_string('degF = 9 / 5 * kelvin; offset: 255.372222') + self.assertIsInstance(x, UnitDefinition) + self.assertFalse(x.is_base) + self.assertIsInstance(x.converter, OffsetConverter) + self.assertEqual(x.converter.scale, 9/5) + self.assertEqual(x.converter.offset, 255.372222) + self.assertEqual(x.reference, UnitsContainer(kelvin=1)) + + def test_dimension_definition(self): + x = DimensionDefinition('[time]', '', (), converter='') + self.assertTrue(x.is_base) + self.assertEqual(x.name, '[time]') + + x = Definition.from_string('[speed] = [length]/[time]') + self.assertIsInstance(x, DimensionDefinition) + self.assertEqual(x.reference, UnitsContainer({'[length]': 1, '[time]': -1})) + + +class TestUnitsContainer(QuantityTestCase): + + def _test_inplace(self, operator, value1, value2, expected_result): + value1 = copy.copy(value1) + value2 = copy.copy(value2) + id1 = id(value1) + id2 = id(value2) + value1 = operator(value1, value2) + value2_cpy = copy.copy(value2) + self.assertEqual(value1, expected_result) + self.assertEqual(id1, id(value1)) + self.assertEqual(value2, value2_cpy) + self.assertEqual(id2, id(value2)) + + def _test_not_inplace(self, operator, value1, value2, expected_result): + id1 = id(value1) + id2 = id(value2) + + value1_cpy = copy.copy(value1) + value2_cpy = copy.copy(value2) + + result = operator(value1, value2) + + self.assertEqual(expected_result, result) + self.assertEqual(value1, value1_cpy) + self.assertEqual(value2, value2_cpy) + self.assertNotEqual(id(result), id1) + self.assertNotEqual(id(result), id2) + + def test_unitcontainer_creation(self): + x = UnitsContainer(meter=1, second=2) + y = UnitsContainer({'meter': 1.0, 'second': 2.0}) + self.assertIsInstance(x['meter'], float) + self.assertEqual(x, y) + self.assertIsNot(x, y) + z = copy.copy(x) + self.assertEqual(x, z) + self.assertIsNot(x, z) + z = UnitsContainer(x) + self.assertEqual(x, z) + self.assertIsNot(x, z) + + def test_unitcontainer_repr(self): + x = UnitsContainer() + self.assertEqual(str(x), 'dimensionless') + self.assertEqual(repr(x), '') + x = UnitsContainer(meter=1, second=2) + self.assertEqual(str(x), 'meter * second ** 2') + self.assertEqual(repr(x), "") + x = UnitsContainer(meter=1, second=2.5) + self.assertEqual(str(x), 'meter * second ** 2.5') + self.assertEqual(repr(x), "") + + def test_unitcontainer_bool(self): + self.assertTrue(UnitsContainer(meter=1, second=2)) + self.assertFalse(UnitsContainer()) + + def test_unitcontainer_comp(self): + x = UnitsContainer(meter=1, second=2) + y = UnitsContainer(meter=1., second=2) + z = UnitsContainer(meter=1, second=3) + self.assertTrue(x == y) + self.assertFalse(x != y) + self.assertFalse(x == z) + self.assertTrue(x != z) + + def test_unitcontainer_arithmetic(self): + x = UnitsContainer(meter=1) + y = UnitsContainer(second=1) + z = UnitsContainer(meter=1, second=-2) + + self._test_not_inplace(op.mul, x, y, UnitsContainer(meter=1, second=1)) + self._test_not_inplace(op.truediv, x, y, UnitsContainer(meter=1, second=-1)) + self._test_not_inplace(op.pow, z, 2, UnitsContainer(meter=2, second=-4)) + self._test_not_inplace(op.pow, z, -2, UnitsContainer(meter=-2, second=4)) + + self._test_inplace(op.imul, x, y, UnitsContainer(meter=1, second=1)) + self._test_inplace(op.itruediv, x, y, UnitsContainer(meter=1, second=-1)) + self._test_inplace(op.ipow, z, 2, UnitsContainer(meter=2, second=-4)) + self._test_inplace(op.ipow, z, -2, UnitsContainer(meter=-2, second=4)) + + def test_string_comparison(self): + x = UnitsContainer(meter=1) + y = UnitsContainer(second=1) + z = UnitsContainer(meter=1, second=-2) + self.assertEqual(x, 'meter') + self.assertEqual('meter', x) + self.assertNotEqual(x, 'meter ** 2') + self.assertNotEqual(x, 'meter * meter') + self.assertNotEqual(x, 'second') + self.assertEqual(y, 'second') + self.assertEqual(z, 'meter/second/second') + + def test_invalid(self): + self.assertRaises(TypeError, UnitsContainer, {1: 2}) + self.assertRaises(TypeError, UnitsContainer, {'1': '2'}) + d = UnitsContainer() + self.assertRaises(TypeError, d.__setitem__, 1, 2) + self.assertRaises(TypeError, d.__setitem__, '1', '2') + self.assertRaises(TypeError, d.__mul__, list()) + self.assertRaises(TypeError, d.__imul__, list()) + self.assertRaises(TypeError, d.__pow__, list()) + self.assertRaises(TypeError, d.__ipow__, list()) + self.assertRaises(TypeError, d.__truediv__, list()) + self.assertRaises(TypeError, d.__itruediv__, list()) + self.assertRaises(TypeError, d.__rtruediv__, list()) + + +class TestRegistry(QuantityTestCase): + + FORCE_NDARRAY = False + + def setup(self): + self.ureg.autoconvert_offset_to_baseunit = False + + def test_base(self): + ureg = UnitRegistry(None) + ureg.define('meter = [length]') + self.assertRaises(ValueError, ureg.define, 'meter = [length]') + self.assertRaises(TypeError, ureg.define, list()) + x = ureg.define('degC = kelvin; offset: 273.15') + + def test_define(self): + ureg = UnitRegistry(None) + self.assertIsInstance(dir(ureg), list) + self.assertGreater(len(dir(ureg)), 0) + + def test_load(self): + import pkg_resources + from pint import unit + data = pkg_resources.resource_filename(unit.__name__, 'default_en.txt') + ureg1 = UnitRegistry() + ureg2 = UnitRegistry(data) + self.assertEqual(dir(ureg1), dir(ureg2)) + self.assertRaises(ValueError, UnitRegistry(None).load_definitions, 'notexisting') + + def test_default_format(self): + ureg = UnitRegistry() + q = ureg.meter + s1 = '{0}'.format(q) + s2 = '{0:~}'.format(q) + ureg.default_format = '~' + s3 = '{0}'.format(q) + self.assertEqual(s2, s3) + self.assertNotEqual(s1, s3) + self.assertEqual(ureg.default_format, '~') + + def test_parse_number(self): + self.assertEqual(self.ureg.parse_expression('pi'), math.pi) + self.assertEqual(self.ureg.parse_expression('x', x=2), 2) + self.assertEqual(self.ureg.parse_expression('x', x=2.3), 2.3) + self.assertEqual(self.ureg.parse_expression('x * y', x=2.3, y=3), 2.3 * 3) + self.assertEqual(self.ureg.parse_expression('x', x=(1+1j)), (1+1j)) + + def test_parse_single(self): + self.assertEqual(self.ureg.parse_expression('meter'), self.Q_(1, UnitsContainer(meter=1.))) + self.assertEqual(self.ureg.parse_expression('second'), self.Q_(1, UnitsContainer(second=1.))) + + def test_parse_alias(self): + self.assertEqual(self.ureg.parse_expression('metre'), self.Q_(1, UnitsContainer(meter=1.))) + + def test_parse_plural(self): + self.assertEqual(self.ureg.parse_expression('meters'), self.Q_(1, UnitsContainer(meter=1.))) + + def test_parse_prefix(self): + self.assertEqual(self.ureg.parse_expression('kilometer'), self.Q_(1, UnitsContainer(kilometer=1.))) + #self.assertEqual(self.ureg._units['kilometer'], self.Q_(1000., UnitsContainer(meter=1.))) + + def test_parse_complex(self): + self.assertEqual(self.ureg.parse_expression('kilometre'), self.Q_(1, UnitsContainer(kilometer=1.))) + self.assertEqual(self.ureg.parse_expression('kilometres'), self.Q_(1, UnitsContainer(kilometer=1.))) + + + def test_str_errors(self): + self.assertEqual(str(UndefinedUnitError('rabbits')), "'{0!s}' is not defined in the unit registry".format('rabbits')) + self.assertEqual(str(UndefinedUnitError(('rabbits', 'horses'))), "{0!s} are not defined in the unit registry".format(('rabbits', 'horses'))) + self.assertEqual(u(str(DimensionalityError('meter', 'second'))), + "Cannot convert from 'meter' to 'second'") + self.assertEqual(str(DimensionalityError('meter', 'second', 'length', 'time')), + "Cannot convert from 'meter' (length) to 'second' (time)") + + def test_parse_mul_div(self): + self.assertEqual(self.ureg.parse_expression('meter*meter'), self.Q_(1, UnitsContainer(meter=2.))) + self.assertEqual(self.ureg.parse_expression('meter**2'), self.Q_(1, UnitsContainer(meter=2.))) + self.assertEqual(self.ureg.parse_expression('meter*second'), self.Q_(1, UnitsContainer(meter=1., second=1))) + self.assertEqual(self.ureg.parse_expression('meter/second'), self.Q_(1, UnitsContainer(meter=1., second=-1))) + self.assertEqual(self.ureg.parse_expression('meter/second**2'), self.Q_(1, UnitsContainer(meter=1., second=-2))) + + def test_parse_pretty(self): + self.assertEqual(self.ureg.parse_expression('meter/second²'), + self.Q_(1, UnitsContainer(meter=1., second=-2))) + self.assertEqual(self.ureg.parse_expression('m³/s³'), + self.Q_(1, UnitsContainer(meter=3., second=-3))) + self.assertEqual(self.ureg.parse_expression('meter² · second'), + self.Q_(1, UnitsContainer(meter=2., second=1))) + self.assertEqual(self.ureg.parse_expression('meter⁰.⁵·second'), + self.Q_(1, UnitsContainer(meter=0.5, second=1))) + self.assertEqual(self.ureg.parse_expression('meter³⁷/second⁴.³²¹'), + self.Q_(1, UnitsContainer(meter=37, second=-4.321))) + + def test_parse_factor(self): + self.assertEqual(self.ureg.parse_expression('42*meter'), self.Q_(42, UnitsContainer(meter=1.))) + self.assertEqual(self.ureg.parse_expression('meter*42'), self.Q_(42, UnitsContainer(meter=1.))) + + def test_rep_and_parse(self): + q = self.Q_(1, 'g/(m**2*s)') + self.assertEqual(self.Q_(q.magnitude, str(q.units)), q) + + def test_as_delta(self): + parse = self.ureg.parse_units + self.assertEqual(parse('kelvin', as_delta=True), UnitsContainer(kelvin=1)) + self.assertEqual(parse('kelvin', as_delta=False), UnitsContainer(kelvin=1)) + self.assertEqual(parse('kelvin**(-1)', as_delta=True), UnitsContainer(kelvin=-1)) + self.assertEqual(parse('kelvin**(-1)', as_delta=False), UnitsContainer(kelvin=-1)) + self.assertEqual(parse('kelvin**2', as_delta=True), UnitsContainer(kelvin=2)) + self.assertEqual(parse('kelvin**2', as_delta=False), UnitsContainer(kelvin=2)) + self.assertEqual(parse('kelvin*meter', as_delta=True), UnitsContainer(kelvin=1, meter= 1)) + self.assertEqual(parse('kelvin*meter', as_delta=False), UnitsContainer(kelvin=1, meter=1)) + + def test_name(self): + self.assertRaises(UndefinedUnitError, self.ureg.get_name, 'asdf') + + def test_symbol(self): + self.assertRaises(UndefinedUnitError, self.ureg.get_symbol, 'asdf') + + self.assertEqual(self.ureg.get_symbol('meter'), 'm') + self.assertEqual(self.ureg.get_symbol('second'), 's') + self.assertEqual(self.ureg.get_symbol('hertz'), 'Hz') + + self.assertEqual(self.ureg.get_symbol('kilometer'), 'km') + self.assertEqual(self.ureg.get_symbol('megahertz'), 'MHz') + self.assertEqual(self.ureg.get_symbol('millisecond'), 'ms') + + def test_imperial_symbol(self): + self.assertEqual(self.ureg.get_symbol('inch'), 'in') + self.assertEqual(self.ureg.get_symbol('foot'), 'ft') + self.assertEqual(self.ureg.get_symbol('inches'), 'in') + self.assertEqual(self.ureg.get_symbol('feet'), 'ft') + self.assertEqual(self.ureg.get_symbol('international_foot'), 'ft') + self.assertEqual(self.ureg.get_symbol('international_inch'), 'in') + + def test_pint(self): + p = self.ureg.pint + l = self.ureg.liter + ip = self.ureg.imperial_pint + self.assertLess(p, l) + self.assertLess(p, ip) + + def test_wraps(self): + def func(x): + return x + + ureg = self.ureg + + f0 = ureg.wraps(None, [None, ])(func) + self.assertEqual(f0(3.), 3.) + + f0 = ureg.wraps(None, None, )(func) + self.assertEqual(f0(3.), 3.) + + f1 = ureg.wraps(None, ['meter', ])(func) + self.assertRaises(ValueError, f1, 3.) + self.assertEqual(f1(3. * ureg.centimeter), 0.03) + self.assertEqual(f1(3. * ureg.meter), 3.) + self.assertRaises(ValueError, f1, 3 * ureg.second) + + f1b = ureg.wraps(None, [ureg.meter, ])(func) + self.assertRaises(ValueError, f1b, 3.) + self.assertEqual(f1b(3. * ureg.centimeter), 0.03) + self.assertEqual(f1b(3. * ureg.meter), 3.) + self.assertRaises(ValueError, f1b, 3 * ureg.second) + + f1 = ureg.wraps(None, 'meter')(func) + self.assertRaises(ValueError, f1, 3.) + self.assertEqual(f1(3. * ureg.centimeter), 0.03) + self.assertEqual(f1(3. * ureg.meter), 3.) + self.assertRaises(ValueError, f1, 3 * ureg.second) + + f2 = ureg.wraps('centimeter', ['meter', ])(func) + self.assertRaises(ValueError, f2, 3.) + self.assertEqual(f2(3. * ureg.centimeter), 0.03 * ureg.centimeter) + self.assertEqual(f2(3. * ureg.meter), 3 * ureg.centimeter) + + f3 = ureg.wraps('centimeter', ['meter', ], strict=False)(func) + self.assertEqual(f3(3), 3 * ureg.centimeter) + self.assertEqual(f3(3. * ureg.centimeter), 0.03 * ureg.centimeter) + self.assertEqual(f3(3. * ureg.meter), 3. * ureg.centimeter) + + def gfunc(x, y): + return x + y + + g0 = ureg.wraps(None, [None, None])(gfunc) + self.assertEqual(g0(3, 1), 4) + + g1 = ureg.wraps(None, ['meter', 'centimeter'])(gfunc) + self.assertRaises(ValueError, g1, 3 * ureg.meter, 1) + self.assertEqual(g1(3 * ureg.meter, 1 * ureg.centimeter), 4) + self.assertEqual(g1(3 * ureg.meter, 1 * ureg.meter), 3 + 100) + + def hfunc(x, y): + return x, y + + h0 = ureg.wraps(None, [None, None])(hfunc) + self.assertEqual(h0(3, 1), (3, 1)) + + h1 = ureg.wraps(['meter', 'cm'], [None, None])(hfunc) + self.assertEqual(h1(3, 1), [3 * ureg.meter, 1 * ureg.cm]) + + h2 = ureg.wraps(('meter', 'cm'), [None, None])(hfunc) + self.assertEqual(h2(3, 1), (3 * ureg.meter, 1 * ureg.cm)) + + def test_to_ref_vs_to(self): + self.ureg.autoconvert_offset_to_baseunit = True + q = 8. * self.ureg.inch + t = 8. * self.ureg.degF + dt = 8. * self.ureg.delta_degF + self.assertEqual(q.to('cm').magnitude, self.ureg._units['inch'].converter.to_reference(8.)) + self.assertEqual(t.to('kelvin').magnitude, self.ureg._units['degF'].converter.to_reference(8.)) + self.assertEqual(dt.to('kelvin').magnitude, self.ureg._units['delta_degF'].converter.to_reference(8.)) + + def test_redefinition(self): + d = UnitRegistry().define + + with self.capture_log() as buffer: + d('meter = [fruits]') + d('kilo- = 1000') + d('[speed] = [vegetables]') + + # aliases + d('bla = 3.2 meter = inch') + d('myk- = 1000 = kilo-') + + self.assertEqual(len(buffer), 5) + + def test_convert_parse_str(self): + ureg = self.ureg + self.assertEqual(ureg.convert(1, 'meter', 'inch'), + ureg.convert(1, UnitsContainer(meter=1), UnitsContainer(inch=1))) + + @helpers.requires_numpy() + def test_convert_inplace(self): + ureg = self.ureg + + # Conversions with single units take a different codepath than + # Conversions with more than one unit. + src_dst1 = UnitsContainer(meter=1), UnitsContainer(inch=1) + src_dst2 = UnitsContainer(meter=1, second=-1), UnitsContainer(inch=1, minute=-1) + for src, dst in (src_dst1, src_dst2): + v = ureg.convert(1, src, dst), + + a = np.ones((3, 1)) + ac = np.ones((3, 1)) + + r1 = ureg.convert(a, src, dst) + np.testing.assert_allclose(r1, v * ac) + self.assertIsNot(r1, a) + + r2 = ureg.convert(a, src, dst, inplace=True) + np.testing.assert_allclose(r2, v * ac) + self.assertIs(r2, a) + + def test_repeated_convert(self): + # Because of caching, repeated conversions were failing. + self.ureg.convert(1, "m", "ft") + self.ureg.convert(1, "m", "ft") + + def test_singular_SI_prefix_convert(self): + # Fix for issue 156 + self.ureg.convert(1, 'mm', 'm') + self.ureg.convert(1, 'ms', 's') + self.ureg.convert(1, 'm', 'mm') + self.ureg.convert(1, 's', 'ms') + + def test_parse_units(self): + ureg = self.ureg + self.assertEqual(ureg.parse_units(''), UnitsContainer()) + self.assertRaises(ValueError, ureg.parse_units, '2 * meter') + + +class TestCompatibleUnits(QuantityTestCase): + + FORCE_NDARRAY= False + + def _test(self, input_units): + gd = self.ureg.get_dimensionality + dim = gd(input_units) + equiv = self.ureg.get_compatible_units(input_units) + for eq in equiv: + self.assertEqual(gd(eq), dim) + self.assertEqual(equiv, self.ureg.get_compatible_units(dim)) + + def _test2(self, units1, units2): + equiv1 = self.ureg.get_compatible_units(units1) + equiv2 = self.ureg.get_compatible_units(units2) + self.assertEqual(equiv1, equiv2) + + def test_many(self): + self._test(self.ureg.meter.units) + self._test(self.ureg.seconds.units) + self._test(self.ureg.newton.units) + self._test(self.ureg.kelvin.units) + + def test_context_sp(self): + + + gd = self.ureg.get_dimensionality + + # length, frequency, energy + valid = [gd(self.ureg.meter.units), gd(self.ureg.hertz.units), gd(self.ureg.joule.units)] + + with self.ureg.context('sp'): + equiv = self.ureg.get_compatible_units(self.ureg.meter.units) + result = set() + for eq in equiv: + dim = gd(eq) + result.add(_freeze(dim)) + self.assertIn(dim, valid) + + self.assertEqual(len(result), len(valid)) + + def test_get_base_units(self): + ureg = UnitRegistry() + self.assertEqual(ureg.get_base_units(''), (1, UnitsContainer())) + self.assertEqual(ureg.get_base_units('meter'), ureg.get_base_units(ParserHelper(meter=1))) + + def test_get_compatible_units(self): + ureg = UnitRegistry() + self.assertEqual(ureg.get_compatible_units(''), (1, UnitsContainer())) + self.assertEqual(ureg.get_compatible_units('meter'), ureg.get_compatible_units(ParserHelper(meter=1))) + + +class TestRegistryWithDefaultRegistry(TestRegistry): + + @classmethod + def setUpClass(cls): + from pint import _DEFAULT_REGISTRY + cls.ureg = _DEFAULT_REGISTRY + cls.Q_ = cls.ureg.Quantity + + def test_lazy(self): + x = LazyRegistry() + x.test = 'test' + self.assertIsInstance(x, UnitRegistry) + y = LazyRegistry() + q = y('meter') + self.assertIsInstance(y, UnitRegistry) + + def test_redefinition(self): + d = self.ureg.define + self.assertRaises(ValueError, d, 'meter = [time]') + self.assertRaises(ValueError, d, 'kilo- = 1000') + self.assertRaises(ValueError, d, '[speed] = [length]') + + # aliases + self.assertIn('inch', self.ureg._units) + self.assertRaises(ValueError, d, 'bla = 3.2 meter = inch') + self.assertRaises(ValueError, d, 'myk- = 1000 = kilo-') + + +class TestErrors(BaseTestCase): + + def test_errors(self): + x = ('meter', ) + msg = "'meter' is not defined in the unit registry" + self.assertEqual(str(UndefinedUnitError(x)), msg) + self.assertEqual(str(UndefinedUnitError(list(x))), msg) + self.assertEqual(str(UndefinedUnitError(set(x))), msg) + + msg = "Cannot convert from 'a' (c) to 'b' (d)msg" + ex = DimensionalityError('a', 'b', 'c', 'd', 'msg') + self.assertEqual(str(ex), msg) + + +class TestConvertWithOffset(QuantityTestCase, ParameterizedTestCase): + + # The dicts in convert_with_offset are used to create a UnitsContainer. + # We create UnitsContainer to avoid any auto-conversion of units. + convert_with_offset = [ + (({'degC': 1}, {'degC': 1}), 10), + (({'degC': 1}, {'kelvin': 1}), 283.15), + (({'degC': 1}, {'degC': 1, 'millimeter': 1, 'meter': -1}), 'error'), + (({'degC': 1}, {'kelvin': 1, 'millimeter': 1, 'meter': -1}), 283150), + + (({'kelvin': 1}, {'degC': 1}), -263.15), + (({'kelvin': 1}, {'kelvin': 1}), 10), + (({'kelvin': 1}, {'degC': 1, 'millimeter': 1, 'meter': -1}), 'error'), + (({'kelvin': 1}, {'kelvin': 1, 'millimeter': 1, 'meter': -1}), 10000), + + (({'degC': 1, 'millimeter': 1, 'meter': -1}, {'degC': 1}), 'error'), + (({'degC': 1, 'millimeter': 1, 'meter': -1}, {'kelvin': 1}), 'error'), + (({'degC': 1, 'millimeter': 1, 'meter': -1}, {'degC': 1, 'millimeter': 1, 'meter': -1}), 10), + (({'degC': 1, 'millimeter': 1, 'meter': -1}, {'kelvin': 1, 'millimeter': 1, 'meter': -1}), 'error'), + + (({'kelvin': 1, 'millimeter': 1, 'meter': -1}, {'degC': 1}), -273.14), + (({'kelvin': 1, 'millimeter': 1, 'meter': -1}, {'kelvin': 1}), 0.01), + (({'kelvin': 1, 'millimeter': 1, 'meter': -1}, {'degC': 1, 'millimeter': 1, 'meter': -1}), 'error'), + (({'kelvin': 1, 'millimeter': 1, 'meter': -1}, {'kelvin': 1, 'millimeter': 1, 'meter': -1}), 10), + + (({'degC': 2}, {'kelvin': 2}), 'error'), + (({'degC': 1, 'degF': 1}, {'kelvin': 2}), 'error'), + (({'degC': 1, 'kelvin': 1}, {'kelvin': 2}), 'error'), + ] + + @ParameterizedTestCase.parameterize(("input", "expected_output"), + convert_with_offset) + def test_to_and_from_offset_units(self, input_tuple, expected): + src, dst = input_tuple + src, dst = UnitsContainer(src), UnitsContainer(dst) + value = 10. + convert = self.ureg.convert + if isinstance(expected, string_types): + self.assertRaises(DimensionalityError, convert, value, src, dst) + if src != dst: + self.assertRaises(DimensionalityError, convert, value, dst, src) + else: + self.assertQuantityAlmostEqual(convert(value, src, dst), + expected, atol=0.001) + if src != dst: + self.assertQuantityAlmostEqual(convert(expected, dst, src), + value, atol=0.001) diff --git a/CadQuery/Libs/pint/testsuite/test_util.py b/CadQuery/Libs/pint/testsuite/test_util.py new file mode 100644 index 0000000..82ce89d --- /dev/null +++ b/CadQuery/Libs/pint/testsuite/test_util.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- + +from __future__ import division, unicode_literals, print_function, absolute_import + +import collections + +from pint.testsuite import BaseTestCase +from pint.util import (string_preprocessor, find_shortest_path, matrix_to_string, + transpose, find_connected_nodes, ParserHelper) + + +class TestParseHelper(BaseTestCase): + + def test_basic(self): + # Parse Helper ar mutables, so we build one everytime + x = lambda: ParserHelper(1, meter=2) + xp = lambda: ParserHelper(1, meter=2) + y = lambda: ParserHelper(2, meter=2) + + self.assertEqual(x(), xp()) + self.assertNotEqual(x(), y()) + self.assertEqual(ParserHelper.from_string(''), ParserHelper()) + self.assertEqual(repr(x()), "") + + self.assertEqual(ParserHelper(2), 2) + + self.assertEqual(x(), dict(meter=2)) + self.assertEqual(x(), 'meter ** 2') + self.assertNotEqual(y(), dict(meter=2)) + self.assertNotEqual(y(), 'meter ** 2') + + self.assertNotEqual(xp(), object()) + + def test_calculate(self): + # Parse Helper ar mutables, so we build one everytime + x = lambda: ParserHelper(1., meter=2) + y = lambda: ParserHelper(2., meter=-2) + z = lambda: ParserHelper(2., meter=2) + + self.assertEqual(x() * 4., ParserHelper(4., meter=2)) + self.assertEqual(x() * y(), ParserHelper(2.)) + self.assertEqual(x() * 'second', ParserHelper(1., meter=2, second=1)) + + self.assertEqual(x() / 4., ParserHelper(0.25, meter=2)) + self.assertEqual(x() / 'second', ParserHelper(1., meter=2, second=-1)) + self.assertEqual(x() / z(), ParserHelper(0.5)) + + self.assertEqual(4. / z(), ParserHelper(2., meter=-2)) + self.assertEqual('seconds' / z(), ParserHelper(0.5, seconds=1, meter=-2)) + self.assertEqual(dict(seconds=1) / z(), ParserHelper(0.5, seconds=1, meter=-2)) + + +class TestStringProcessor(BaseTestCase): + + def _test(self, bef, aft): + for pattern in ('{0}', '+{0}+'): + b = pattern.format(bef) + a = pattern.format(aft) + self.assertEqual(string_preprocessor(b), a) + + def test_square_cube(self): + self._test('bcd^3', 'bcd**3') + self._test('bcd^ 3', 'bcd** 3') + self._test('bcd ^3', 'bcd **3') + self._test('bcd squared', 'bcd**2') + self._test('bcd squared', 'bcd**2') + self._test('bcd cubed', 'bcd**3') + self._test('sq bcd', 'bcd**2') + self._test('square bcd', 'bcd**2') + self._test('cubic bcd', 'bcd**3') + self._test('bcd efg', 'bcd*efg') + + def test_per(self): + self._test('miles per hour', 'miles/hour') + + def test_numbers(self): + self._test('1,234,567', '1234567') + self._test('1e-24', '1e-24') + self._test('1e+24', '1e+24') + self._test('1e24', '1e24') + self._test('1E-24', '1E-24') + self._test('1E+24', '1E+24') + self._test('1E24', '1E24') + + def test_space_multiplication(self): + self._test('bcd efg', 'bcd*efg') + self._test('bcd efg', 'bcd*efg') + self._test('1 hour', '1*hour') + self._test('1. hour', '1.*hour') + self._test('1.1 hour', '1.1*hour') + self._test('1E24 hour', '1E24*hour') + self._test('1E-24 hour', '1E-24*hour') + self._test('1E+24 hour', '1E+24*hour') + self._test('1.2E24 hour', '1.2E24*hour') + self._test('1.2E-24 hour', '1.2E-24*hour') + self._test('1.2E+24 hour', '1.2E+24*hour') + + def test_joined_multiplication(self): + self._test('1hour', '1*hour') + self._test('1.hour', '1.*hour') + self._test('1.1hour', '1.1*hour') + self._test('1h', '1*h') + self._test('1.h', '1.*h') + self._test('1.1h', '1.1*h') + + def test_names(self): + self._test('g_0', 'g_0') + self._test('g0', 'g0') + self._test('g', 'g') + self._test('water_60F', 'water_60F') + + +class TestGraph(BaseTestCase): + + def test_start_not_in_graph(self): + g = collections.defaultdict(list) + g[1] = set((2,)) + g[2] = set((3,)) + self.assertIs(find_connected_nodes(g, 9), None) + + def test_shortest_path(self): + g = collections.defaultdict(list) + g[1] = set((2,)) + g[2] = set((3,)) + p = find_shortest_path(g, 1, 2) + self.assertEqual(p, [1, 2]) + p = find_shortest_path(g, 1, 3) + self.assertEqual(p, [1, 2, 3]) + p = find_shortest_path(g, 3, 1) + self.assertIs(p, None) + + g = collections.defaultdict(list) + g[1] = set((2,)) + g[2] = set((3, 1)) + g[3] = set((2,)) + p = find_shortest_path(g, 1, 2) + self.assertEqual(p, [1, 2]) + p = find_shortest_path(g, 1, 3) + self.assertEqual(p, [1, 2, 3]) + p = find_shortest_path(g, 3, 1) + self.assertEqual(p, [3, 2, 1]) + p = find_shortest_path(g, 2, 1) + self.assertEqual(p, [2, 1]) + + +class TestMatrix(BaseTestCase): + + def test_matrix_to_string(self): + + self.assertEqual(matrix_to_string([[1, 2], [3, 4]], + row_headers=None, + col_headers=None), + '1\t2\n' + '3\t4') + + self.assertEqual(matrix_to_string([[1, 2], [3, 4]], + row_headers=None, + col_headers=None, + fmtfun=lambda x: '{0:.2f}'.format(x)), + '1.00\t2.00\n' + '3.00\t4.00') + + self.assertEqual(matrix_to_string([[1, 2], [3, 4]], + row_headers=['c', 'd'], + col_headers=None), + 'c\t1\t2\n' + 'd\t3\t4') + + self.assertEqual(matrix_to_string([[1, 2], [3, 4]], + row_headers=None, + col_headers=['a', 'b']), + 'a\tb\n' + '1\t2\n' + '3\t4') + + self.assertEqual(matrix_to_string([[1, 2], [3, 4]], + row_headers=['c', 'd'], + col_headers=['a', 'b']), + '\ta\tb\n' + 'c\t1\t2\n' + 'd\t3\t4') + + def test_transpose(self): + + self.assertEqual(transpose([[1, 2], [3, 4]]), [[1, 3], [2, 4]]) diff --git a/CadQuery/Libs/pint/unit.py b/CadQuery/Libs/pint/unit.py new file mode 100644 index 0000000..f0f0518 --- /dev/null +++ b/CadQuery/Libs/pint/unit.py @@ -0,0 +1,1396 @@ +# -*- coding: utf-8 -*- +""" + pint.unit + ~~~~~~~~~ + + Functions and classes related to unit definitions and conversions. + + :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 os +import copy +import math +import itertools +import functools +import pkg_resources +from decimal import Decimal +from contextlib import contextmanager, closing +from io import open, StringIO +from numbers import Number +from collections import defaultdict +from tokenize import untokenize, NUMBER, STRING, NAME, OP + +from .context import Context, ContextChain, _freeze +from .util import (logger, pi_theorem, solve_dependencies, ParserHelper, + string_preprocessor, find_connected_nodes, find_shortest_path) +from .compat import tokenizer, string_types, NUMERIC_TYPES, TransformDict +from .formatting import format_unit + + +class DefinitionSyntaxError(ValueError): + """Raised when a textual definition has a syntax error. + """ + + def __init__(self, msg, filename=None, lineno=None): + super(ValueError, self).__init__() + self.msg = msg + self.filename = None + self.lineno = None + + def __str__(self): + return "While opening {0}, in line {1}: ".format(self.filename, self.lineno) + self.msg + + +class RedefinitionError(ValueError): + """Raised when a unit or prefix is redefined. + """ + + def __init__(self, name, definition_type): + super(ValueError, self).__init__() + self.name = name + self.definition_type = definition_type + self.filename = None + self.lineno = None + + def __str__(self): + msg = "cannot redefine '{0}' ({1})".format(self.name, self.definition_type) + if self.filename: + return "While opening {0}, in line {1}: ".format(self.filename, self.lineno) + msg + return msg + + +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 "'{0}' is not defined in the unit registry".format(self.unit_names) + elif isinstance(self.unit_names, (list, tuple)) and len(self.unit_names) == 1: + return "'{0}' is not defined in the unit registry".format(self.unit_names[0]) + elif isinstance(self.unit_names, set) and len(self.unit_names) == 1: + uname = list(self.unit_names)[0] + return "'{0}' is not defined in the unit registry".format(uname) + else: + return '{0} 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, extra_msg=''): + super(DimensionalityError, self).__init__() + self.units1 = units1 + self.units2 = units2 + self.dim1 = dim1 + self.dim2 = dim2 + self.extra_msg = extra_msg + + def __str__(self): + if self.dim1 or self.dim2: + dim1 = ' ({0})'.format(self.dim1) + dim2 = ' ({0})'.format(self.dim2) + else: + dim1 = '' + dim2 = '' + + msg = "Cannot convert from '{0}'{1} to '{2}'{3}" + self.extra_msg + + return msg.format(self.units1, dim1, self.units2, dim2) + + +class OffsetUnitCalculusError(ValueError): + """Raised on ambiguous operations with offset units. + """ + def __init__(self, units1, units2='', extra_msg=''): + super(ValueError, self).__init__() + self.units1 = units1 + self.units2 = units2 + self.extra_msg = extra_msg + + def __str__(self): + msg = ("Ambiguous operation with offset unit (%s)." % + ', '.join(['%s' % u for u in [self.units1, self.units2] if u]) + + self.extra_msg) + return msg.format(self.units1, self.units2) + + +class Converter(object): + """Base class for value converters. + """ + + is_multiplicative = True + + def to_reference(self, value, inplace=False): + return value + + def from_reference(self, value, inplace=False): + return value + + +class ScaleConverter(Converter): + """A linear transformation + """ + + is_multiplicative = True + + def __init__(self, scale): + self.scale = scale + + def to_reference(self, value, inplace=False): + if inplace: + value *= self.scale + else: + value = value * self.scale + + return value + + def from_reference(self, value, inplace=False): + if inplace: + value /= self.scale + else: + value = value / self.scale + + return value + + +class OffsetConverter(Converter): + """An affine transformation + """ + + def __init__(self, scale, offset): + self.scale = scale + self.offset = offset + + @property + def is_multiplicative(self): + return self.offset == 0 + + def to_reference(self, value, inplace=False): + if inplace: + value *= self.scale + value += self.offset + else: + value = value * self.scale + self.offset + + return value + + def from_reference(self, value, inplace=False): + if inplace: + value -= self.offset + value /= self.scale + else: + value = (value - self.offset) / self.scale + + return value + + +class Definition(object): + """Base class for definitions. + + :param name: name. + :param symbol: a short name or symbol for the definition + :param aliases: iterable of other names. + :param converter: an instance of Converter. + """ + + def __init__(self, name, symbol, aliases, converter): + self._name = name + self._symbol = symbol + self._aliases = aliases + self._converter = converter + + @property + def is_multiplicative(self): + return self._converter.is_multiplicative + + @classmethod + def from_string(cls, definition): + """Parse a definition + """ + name, definition = definition.split('=', 1) + name = name.strip() + + result = [res.strip() for res in definition.split('=')] + value, aliases = result[0], tuple(result[1:]) + symbol, aliases = (aliases[0], aliases[1:]) if aliases else (None, aliases) + + if name.startswith('['): + return DimensionDefinition(name, symbol, aliases, value) + elif name.endswith('-'): + name = name.rstrip('-') + return PrefixDefinition(name, symbol, aliases, value) + else: + return UnitDefinition(name, symbol, aliases, value) + + @property + def name(self): + return self._name + + @property + def symbol(self): + return self._symbol or self._name + + @property + def has_symbol(self): + return bool(self._symbol) + + @property + def aliases(self): + return self._aliases + + @property + def converter(self): + return self._converter + + def __str__(self): + return self.name + + +def _is_dim(name): + return name[0] == '[' and name[-1] == ']' + + +class PrefixDefinition(Definition): + """Definition of a prefix. + """ + + def __init__(self, name, symbol, aliases, converter): + if isinstance(converter, string_types): + converter = ScaleConverter(eval(converter)) + aliases = tuple(alias.strip('-') for alias in aliases) + if symbol: + symbol = symbol.strip('-') + super(PrefixDefinition, self).__init__(name, symbol, aliases, converter) + + +class UnitDefinition(Definition): + """Definition of a unit. + + :param reference: Units container with reference units. + :param is_base: indicates if it is a base unit. + """ + + def __init__(self, name, symbol, aliases, converter, + reference=None, is_base=False): + self.reference = reference + self.is_base = is_base + if isinstance(converter, string_types): + if ';' in converter: + [converter, modifiers] = converter.split(';', 2) + modifiers = dict((key.strip(), eval(value)) for key, value in + (part.split(':') for part in modifiers.split(';'))) + else: + modifiers = {} + + converter = ParserHelper.from_string(converter) + if all(_is_dim(key) for key in converter.keys()): + self.is_base = True + elif not any(_is_dim(key) for key in converter.keys()): + self.is_base = False + else: + raise ValueError('Cannot mix dimensions and units in the same definition. ' + 'Base units must be referenced only to dimensions. ' + 'Derived units must be referenced only to units.') + self.reference = UnitsContainer(converter.items()) + if modifiers.get('offset', 0.) != 0.: + converter = OffsetConverter(converter.scale, modifiers['offset']) + else: + converter = ScaleConverter(converter.scale) + + super(UnitDefinition, self).__init__(name, symbol, aliases, converter) + + +class DimensionDefinition(Definition): + """Definition of a dimension. + """ + + def __init__(self, name, symbol, aliases, converter, + reference=None, is_base=False): + self.reference = reference + self.is_base = is_base + if isinstance(converter, string_types): + converter = ParserHelper.from_string(converter) + if not converter: + self.is_base = True + elif all(_is_dim(key) for key in converter.keys()): + self.is_base = False + else: + raise ValueError('Base dimensions must be referenced to None. ' + 'Derived dimensions must only be referenced to dimensions.') + self.reference = UnitsContainer(converter.items()) + + super(DimensionDefinition, self).__init__(name, symbol, aliases, converter=None) + + +class UnitsContainer(dict): + """The UnitsContainer stores the product of units and their respective + exponent and implements the corresponding operations + """ + __slots__ = () + + 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 {0}'.format(type(key))) + if not isinstance(value, Number): + raise TypeError('value must be a number, not {0}'.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 {0}'.format(type(key))) + if not isinstance(value, NUMERIC_TYPES): + raise TypeError('value must be a NUMERIC_TYPES, not {0}'.format(type(value))) + dict.__setitem__(self, key, float(value)) + + def add(self, key, value): + newval = self.__getitem__(key) + value + if newval: + self.__setitem__(key, newval) + else: + del self[key] + + def __eq__(self, other): + if isinstance(other, string_types): + other = ParserHelper.from_string(other) + other = dict(other.items()) + return dict.__eq__(self, other) + + def __str__(self): + return self.__format__('') + + def __repr__(self): + tmp = '{%s}' % ', '.join(["'{0}': {1}".format(key, value) for key, value in sorted(self.items())]) + return ''.format(tmp) + + def __format__(self, spec): + return format_unit(self, 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 {0}'.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 {0}'.format(type(other))) + ret = copy.copy(self) + ret *= other + return ret + + __rmul__ = __mul__ + + def __ipow__(self, other): + if not isinstance(other, NUMERIC_TYPES): + raise TypeError('Cannot power UnitsContainer by {0}'.format(type(other))) + for key, value in self.items(): + self[key] *= other + return self + + def __pow__(self, other): + if not isinstance(other, NUMERIC_TYPES): + raise TypeError('Cannot power UnitsContainer by {0}'.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 {0}'.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 {0}'.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 {0} by UnitsContainer'.format(type(other))) + + ret = copy.copy(self) + ret **= -1 + return ret + + +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. + :param force_ndarray: convert any input, scalar or not to a numpy.ndarray. + :param default_as_delta: In the context of a multiplication of units, interpret + non-multiplicative units as their *delta* counterparts. + :autoconvert_offset_to_baseunit: If True converts offset units in quantites are + converted to their base units in multiplicative + context. If False no conversion happens. + :param on_redefinition: action to take in case a unit is redefined. + 'warn', 'raise', 'ignore' + :type on_redefintion: str + """ + + def __init__(self, filename='', force_ndarray=False, default_as_delta=True, + autoconvert_offset_to_baseunit=False, + on_redefinition='warn'): + self.Quantity = build_quantity_class(self, force_ndarray) + self.Measurement = build_measurement_class(self, force_ndarray) + + #: Action to take in case a unit is redefined. 'warn', 'raise', 'ignore' + self._on_redefinition = on_redefinition + + #: Map dimension name (string) to its definition (DimensionDefinition). + self._dimensions = {} + + #: Map unit name (string) to its definition (UnitDefinition). + #: Might contain prefixed units. + self._units = {} + + #: Map unit name in lower case (string) to a set of unit names with the right case. + #: Does not contain prefixed units. + #: e.g: 'hz' - > set('Hz', ) + self._units_casei = defaultdict(set) + + #: Map prefix name (string) to its definition (PrefixDefinition). + self._prefixes = {'': PrefixDefinition('', '', (), 1)} + + #: Map suffix name (string) to canonical , and unit alias to canonical unit name + self._suffixes = {'': None, 's': ''} + + #: Map context name (string) or abbreviation to context. + self._contexts = {} + + #: Stores active contexts. + self._active_ctx = ContextChain() + + #: Maps dimensionality (_freeze(UnitsContainer)) to Units (str) + self._dimensional_equivalents = TransformDict(_freeze) + + #: Maps dimensionality (_freeze(UnitsContainer)) to Dimensionality (_freeze(UnitsContainer)) + self._base_units_cache = TransformDict(_freeze) + #: Maps dimensionality (_freeze(UnitsContainer)) to Units (_freeze(UnitsContainer)) + self._dimensionality_cache = TransformDict(_freeze) + + #: Cache the unit name associated to user input. ('mV' -> 'millivolt') + self._parse_unit_cache = dict() + + #: When performing a multiplication of units, interpret + #: non-multiplicative units as their *delta* counterparts. + self.default_as_delta = default_as_delta + + # Determines if quantities with offset units are converted to their + # base units on multiplication and division. + self.autoconvert_offset_to_baseunit = autoconvert_offset_to_baseunit + + if filename == '': + self.load_definitions('default_en.txt', True) + elif filename is not None: + self.load_definitions(filename) + + self.define(UnitDefinition('pi', 'π', (), ScaleConverter(math.pi))) + + self._build_cache() + + def __name__(self): + return 'UnitRegistry' + + def __getattr__(self, item): + return self.Quantity(1, item) + + def __getitem__(self, item): + logger.warning('Calling the getitem method from a UnitRegistry is deprecated. ' + 'use `parse_expression` method or use the registry as a callable.') + return self.parse_expression(item) + + def __dir__(self): + return list(self._units.keys()) + \ + ['define', 'load_definitions', 'get_name', 'get_symbol', + 'get_dimensionality', 'Quantity', 'wraps', 'parse_unit', + 'parse_units', 'parse_expression', 'pi_theorem', + 'convert', 'get_base_units'] + + @property + def default_format(self): + """Default formatting string for quantities. + """ + return self.Quantity.default_format + + @default_format.setter + def default_format(self, value): + self.Quantity.default_format = value + + def add_context(self, context): + """Add a context object to the registry. + + The context will be accessible by its name and aliases. + + Notice that this method will NOT enable the context. Use `enable_contexts`. + """ + if context.name in self._contexts: + logger.warning('The name %s was already registered for another context.', + context.name) + self._contexts[context.name] = context + for alias in context.aliases: + if alias in self._contexts: + logger.warning('The name %s was already registered for another context', + context.name) + self._contexts[alias] = context + + def remove_context(self, name_or_alias): + """Remove a context from the registry and return it. + + Notice that this methods will not disable the context. Use `disable_contexts`. + """ + context = self._contexts[name_or_alias] + + del self._contexts[context.name] + for alias in context.aliases: + del self._contexts[alias] + + return context + + def enable_contexts(self, *names_or_contexts, **kwargs): + """Enable contexts provided by name or by object. + + :param names_or_contexts: sequence of the contexts or contexts names/alias + :param kwargs: keyword arguments for the context + """ + + # If present, copy the defaults from the containing contexts + if self._active_ctx.defaults: + kwargs = dict(self._active_ctx.defaults, **kwargs) + + # For each name, we first find the corresponding context + ctxs = tuple((self._contexts[name] if isinstance(name, string_types) else name) + for name in names_or_contexts) + + # Check if the contexts have been checked first, if not we make sure + # that dimensions are expressed in terms of base dimensions. + for ctx in ctxs: + if getattr(ctx, '_checked', False): + continue + for (src, dst), func in ctx.funcs.items(): + src_ = self.get_dimensionality(dict(src)) + dst_ = self.get_dimensionality(dict(dst)) + if src != src_ or dst != dst_: + ctx.remove_transformation(src, dst) + ctx.add_transformation(src_, dst_, func) + ctx._checked = True + + # and create a new one with the new defaults. + ctxs = tuple(Context.from_context(ctx, **kwargs) + for ctx in ctxs) + + # Finally we add them to the active context. + self._active_ctx.insert_contexts(*ctxs) + + def disable_contexts(self, n=None): + """Disable the last n enabled contexts. + """ + if n is None: + n = len(self._contexts) + self._active_ctx.remove_contexts(n) + + @contextmanager + def context(self, *names, **kwargs): + """Used as a context manager, this function enables to activate a context + which is removed after usage. + + :param names: name of the context. + :param kwargs: keyword arguments for the contexts. + + Context are called by their name:: + + >>> with ureg.context('one'): + ... pass + + If the context has an argument, you can specify its value as a keyword + argument:: + + >>> with ureg.context('one', n=1): + ... pass + + Multiple contexts can be entered in single call: + + >>> with ureg.context('one', 'two', n=1): + ... pass + + or nested allowing you to give different values to the same keyword argument:: + + >>> with ureg.context('one', n=1): + ... with ureg.context('two', n=2): + ... pass + + A nested context inherits the defaults from the containing context:: + + >>> with ureg.context('one', n=1): + ... with ureg.context('two'): # Here n takes the value of the upper context + ... pass + + """ + + # Enable the contexts. + self.enable_contexts(*names, **kwargs) + + try: + # After adding the context and rebuilding the graph, the registry + # is ready to use. + yield self + finally: + # Upon leaving the with statement, + # the added contexts are removed from the active one. + self.disable_contexts(len(names)) + + def define(self, definition): + """Add unit to the registry. + """ + if isinstance(definition, string_types): + definition = Definition.from_string(definition) + + if isinstance(definition, DimensionDefinition): + d, di = self._dimensions, None + elif isinstance(definition, UnitDefinition): + d, di = self._units, self._units_casei + if definition.is_base: + for dimension in definition.reference.keys(): + if dimension in self._dimensions: + if dimension != '[]': + raise DefinitionSyntaxError('only one unit per dimension can be a base unit.') + continue + + self.define(DimensionDefinition(dimension, '', (), None, is_base=True)) + + elif isinstance(definition, PrefixDefinition): + d, di = self._prefixes, None + else: + raise TypeError('{0} is not a valid definition.'.format(definition)) + + def _adder(key, value, action=self._on_redefinition, selected_dict=d, casei_dict=di): + if key in selected_dict: + if action == 'raise': + raise RedefinitionError(key, type(value)) + elif action == 'warn': + logger.warning("Redefining '%s' (%s)", key, type(value)) + + selected_dict[key] = value + if casei_dict is not None: + casei_dict[key.lower()].add(key) + + _adder(definition.name, definition) + + if definition.has_symbol: + _adder(definition.symbol, definition) + + for alias in definition.aliases: + if ' ' in alias: + logger.warn('Alias cannot contain a space: ' + alias) + + _adder(alias, definition) + + # define additional "delta_" units for units with an offset + if getattr(definition.converter, "offset", 0.0) != 0.0: + d_name = 'delta_' + definition.name + if definition.symbol: + d_symbol = 'Δ' + definition.symbol + else: + d_symbol = None + d_aliases = tuple('Δ' + alias for alias in definition.aliases) + + def prep(_name): + if _name.startswith('['): + return '[delta_' + _name[1:] + return 'delta_' + _name + + d_reference = UnitsContainer(dict((ref, value) + for ref, value in definition.reference.items())) + + self.define(UnitDefinition(d_name, d_symbol, d_aliases, + ScaleConverter(definition.converter.scale), + d_reference, definition.is_base)) + + def load_definitions(self, file, is_resource=False): + """Add units and prefixes defined in a definition text file. + """ + # Permit both filenames and line-iterables + if isinstance(file, string_types): + try: + if is_resource: + with closing(pkg_resources.resource_stream(__name__, file)) as fp: + rbytes = fp.read() + return self.load_definitions(StringIO(rbytes.decode('utf-8')), is_resource) + else: + with open(file, encoding='utf-8') as fp: + return self.load_definitions(fp, is_resource) + except (RedefinitionError, DefinitionSyntaxError) as e: + if e.filename is None: + e.filename = file + raise e + except Exception as e: + msg = getattr(e, 'message', '') or str(e) + raise ValueError('While opening {0}\n{1}'.format(file, msg)) + + ifile = enumerate(file, 1) + for no, line in ifile: + line = line.strip() + if not line or line.startswith('#'): + continue + if line.startswith('@import'): + if is_resource: + path = line[7:].strip() + else: + try: + path = os.path.dirname(file.name) + except AttributeError: + path = os.getcwd() + path = os.path.join(path, os.path.normpath(line[7:].strip())) + self.load_definitions(path, is_resource) + elif line.startswith('@context'): + context = [line, ] + for no, line in ifile: + line = line.strip() + if line.startswith('@end'): + try: + self.add_context(Context.from_lines(context, self.get_dimensionality)) + except KeyError as e: + raise DefinitionSyntaxError('unknown dimension {0} in context'.format(str(e)), lineno=no) + break + elif line.startswith('@'): + raise DefinitionSyntaxError('cannot nest @ directives', lineno=no) + context.append(line) + else: + try: + self.define(Definition.from_string(line)) + except (RedefinitionError, DefinitionSyntaxError) as ex: + if ex.lineno is None: + ex.lineno = no + raise ex + except Exception as ex: + logger.error("In line {0}, cannot add '{1}' {2}".format(no, line, ex)) + + def _build_cache(self): + """Build a cache of dimensionality and base units. + """ + + deps = dict((name, set(definition.reference.keys() if definition.reference else {})) + for name, definition in self._units.items()) + + for unit_names in solve_dependencies(deps): + for unit_name in unit_names: + prefixed = False + for p in self._prefixes.keys(): + if p and unit_name.startswith(p): + prefixed = True + break + if '[' in unit_name: + continue + try: + uc = ParserHelper.from_word(unit_name) + + bu = self.get_base_units(uc) + di = self.get_dimensionality(uc) + + self._base_units_cache[uc] = bu + self._dimensionality_cache[uc] = di + + if not prefixed: + if di not in self._dimensional_equivalents: + self._dimensional_equivalents[di] = set() + + self._dimensional_equivalents[di].add(self._units[unit_name].name) + + except Exception as e: + logger.warning('Could not resolve {0}: {1!r}'.format(unit_name, e)) + + def get_name(self, name_or_alias, case_sensitive=True): + """Return the canonical name of a unit. + """ + + if name_or_alias == 'dimensionless': + return '' + + try: + return self._units[name_or_alias]._name + except KeyError: + pass + + candidates = self._dedup_candidates(self.parse_unit_name(name_or_alias, case_sensitive)) + if not candidates: + raise UndefinedUnitError(name_or_alias) + elif len(candidates) == 1: + prefix, unit_name, _ = candidates[0] + else: + logger.warning('Parsing {0} yield multiple results. ' + 'Options are: {1}'.format(name_or_alias, candidates)) + prefix, unit_name, _ = candidates[0] + + if prefix: + name = prefix + unit_name + symbol = self.get_symbol(name) + prefix_def = self._prefixes[prefix] + self._units[name] = UnitDefinition(name, symbol, (), prefix_def.converter, + UnitsContainer({unit_name: 1})) + return prefix + unit_name + + return unit_name + + def get_symbol(self, name_or_alias): + """Return the preferred alias for a unit + """ + candidates = self._dedup_candidates(self.parse_unit_name(name_or_alias)) + if not candidates: + raise UndefinedUnitError(name_or_alias) + elif len(candidates) == 1: + prefix, unit_name, _ = candidates[0] + else: + logger.warning('Parsing {0} yield multiple results. ' + 'Options are: {1!r}'.format(name_or_alias, candidates)) + prefix, unit_name, _ = candidates[0] + + return self._prefixes[prefix].symbol + self._units[unit_name].symbol + + def _get_symbol(self, name): + return self._units[name].symbol + + def get_dimensionality(self, input_units): + """Convert unit or dict of units or dimensions to a dict of base dimensions + + :param input_units: + :return: dimensionality + """ + if not input_units: + return UnitsContainer() + + if isinstance(input_units, string_types): + input_units = ParserHelper.from_string(input_units) + + if input_units in self._dimensionality_cache: + return copy.copy(self._dimensionality_cache[input_units]) + + accumulator = defaultdict(float) + self._get_dimensionality_recurse(input_units, 1.0, accumulator) + + dims = UnitsContainer(dict((k, v) for k, v in accumulator.items() if v != 0.)) + + if '[]' in dims: + del dims['[]'] + + self._dimensionality_cache[input_units] = copy.copy(dims) + + return dims + + def _get_dimensionality_recurse(self, ref, exp, accumulator): + for key in ref: + exp2 = exp*ref[key] + if _is_dim(key): + reg = self._dimensions[key] + if reg.is_base: + accumulator[key] += exp2 + elif reg.reference is not None: + self._get_dimensionality_recurse(reg.reference, exp2, accumulator) + else: + reg = self._units[self.get_name(key)] + if reg.reference is not None: + self._get_dimensionality_recurse(reg.reference, exp2, accumulator) + + def get_base_units(self, input_units, check_nonmult=True): + """Convert unit or dict of units to the base units. + + If any unit is non multiplicative and check_converter is True, + then None is returned as the multiplicative factor. + + :param input_units: units + :type input_units: UnitsContainer or str + :param check_nonmult: if True, None will be returned as the multiplicative factor + is a non-multiplicative units is found in the final Units. + :return: multiplicative factor, base units + """ + if not input_units: + return 1., UnitsContainer() + + if isinstance(input_units, string_types): + input_units = ParserHelper.from_string(input_units) + + # The cache is only done for check_nonmult=True + if check_nonmult and input_units in self._base_units_cache: + return copy.deepcopy(self._base_units_cache[input_units]) + + accumulators = [1., defaultdict(float)] + self._get_base_units(input_units, 1.0, accumulators) + + factor = accumulators[0] + units = UnitsContainer(dict((k, v) for k, v in accumulators[1].items() if v != 0.)) + + # Check if any of the final units is non multiplicative and return None instead. + if check_nonmult: + for unit in units.keys(): + if not self._units[unit].converter.is_multiplicative: + return None, units + + return factor, units + + def _get_base_units(self, ref, exp, accumulators): + for key in ref: + exp2 = exp*ref[key] + key = self.get_name(key) + reg = self._units[key] + if reg.is_base: + accumulators[1][key] += exp2 + else: + accumulators[0] *= reg._converter.scale ** exp2 + if reg.reference is not None: + self._get_base_units(reg.reference, exp2, accumulators) + + def get_compatible_units(self, input_units): + if not input_units: + return 1., UnitsContainer() + + if isinstance(input_units, string_types): + input_units = ParserHelper.from_string(input_units) + + src_dim = self.get_dimensionality(input_units) + + ret = self._dimensional_equivalents[src_dim] + + if self._active_ctx: + nodes = find_connected_nodes(self._active_ctx.graph, _freeze(src_dim)) + ret = set() + if nodes: + for node in nodes: + ret |= self._dimensional_equivalents[node] + + return frozenset(ret) + + def convert(self, value, src, dst, inplace=False): + """Convert value from some source to destination units. + + :param value: value + :param src: source units. + :type src: UnitsContainer or str + :param dst: destination units. + :type dst: UnitsContainer or str + + :return: converted value + """ + if isinstance(src, string_types): + src = self.parse_units(src) + if isinstance(dst, string_types): + dst = self.parse_units(dst) + if src == dst: + return value + + src_dim = self.get_dimensionality(src) + dst_dim = self.get_dimensionality(dst) + + # If there is an active context, we look for a path connecting source and + # destination dimensionality. If it exists, we transform the source value + # by applying sequentially each transformation of the path. + if self._active_ctx: + path = find_shortest_path(self._active_ctx.graph, + *Context.__keytransform__(src_dim, dst_dim)) + if path: + src = self.Quantity(value, src) + for a, b in zip(path[:-1], path[1:]): + src = self._active_ctx.transform(a, b, self, src) + + value, src = src.magnitude, src.units + + src_dim = self.get_dimensionality(src) + + # If the source and destination dimensionality are different, + # then the conversion cannot be performed. + + if src_dim != dst_dim: + raise DimensionalityError(src, dst, src_dim, dst_dim) + + # Conversion needs to consider if non-multiplicative (AKA offset + # units) are involved. Conversion is only possible if src and dst + # have at most one offset unit per dimension. + src_offset_units = [(u, e) for u, e in src.items() + if not self._units[u].is_multiplicative] + dst_offset_units = [(u, e) for u, e in dst.items() + if not self._units[u].is_multiplicative] + + # For offset units we need to check if the conversion is allowed. + if src_offset_units or dst_offset_units: + + # Validate that not more than one offset unit is present + if len(src_offset_units) > 1 or len(dst_offset_units) > 1: + raise DimensionalityError( + src, dst, src_dim, dst_dim, + extra_msg=' - more than one offset unit.') + + # validate that offset unit is not used in multiplicative context + if ((len(src_offset_units) == 1 and len(src) > 1) + or (len(dst_offset_units) == 1 and len(dst) > 1) + and not self.autoconvert_offset_to_baseunit): + raise DimensionalityError( + src, dst, src_dim, dst_dim, + extra_msg=' - offset unit used in multiplicative context.') + + # Validate that order of offset unit is exactly one. + if src_offset_units: + if src_offset_units[0][1] != 1: + raise DimensionalityError( + src, dst, src_dim, dst_dim, + extra_msg=' - offset units in higher order.') + else: + if dst_offset_units[0][1] != 1: + raise DimensionalityError( + src, dst, src_dim, dst_dim, + extra_msg=' - offset units in higher order.') + + # Here we convert only the offset quantities. Any remaining scaled + # quantities will be converted later. + + # clean src from offset units by converting to reference + for u, e in src_offset_units: + value = self._units[u].converter.to_reference(value, inplace) + src.pop(u) + + # clean dst units from offset units + for u, e in dst_offset_units: + dst.pop(u) + + # Here src and dst have only multiplicative units left. Thus we can + # convert with a factor. + factor, units = self.get_base_units(src / dst) + + # factor is type float and if our magnitude is type Decimal then + # must first convert to Decimal before we can '*' the values + if isinstance(value, Decimal): + factor = Decimal(str(factor)) + + if inplace: + value *= factor + else: + value = value * factor + + # Finally convert to offset units specified in destination + for u, e in dst_offset_units: + value = self._units[u].converter.from_reference(value, inplace) + # add back offset units to dst + dst[u] = e + + # restore offset conversion of src units + for u, e in src_offset_units: + src[u] = e + + return value + + def pi_theorem(self, quantities): + """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 + """ + return pi_theorem(quantities, self) + + def _dedup_candidates(self, candidates): + """Given a list of units, remove those with different names but equal value. + """ + candidates = tuple(candidates) + if len(candidates) < 2: + return candidates + + unique = [candidates[0]] + for c in candidates[2:]: + for u in unique: + if c == u: + break + else: + unique.append(c) + + return tuple(unique) + + def parse_unit_name(self, unit_name, case_sensitive=True): + """Parse a unit to identify prefix, unit name and suffix + by walking the list of prefix and suffix. + """ + stw = unit_name.startswith + edw = unit_name.endswith + for suffix, prefix in itertools.product(self._suffixes, self._prefixes): + if stw(prefix) and edw(suffix): + name = unit_name[len(prefix):] + if suffix: + name = name[:-len(suffix)] + if len(name) == 1: + continue + if case_sensitive: + if name in self._units: + yield (self._prefixes[prefix]._name, + self._units[name]._name, + self._suffixes[suffix]) + else: + for real_name in self._units_casei.get(name.lower(), ()): + yield (self._prefixes[prefix]._name, + self._units[real_name]._name, + self._suffixes[suffix]) + + def parse_units(self, input_string, as_delta=None): + """Parse a units expression and returns a UnitContainer with + the canonical names. + + The expression can only contain products, ratios and powers of units. + + :param as_delta: if the expression has multiple units, the parser will + interpret non multiplicative units as their `delta_` counterparts. + + :raises: + :class:`pint.UndefinedUnitError` if a unit is not in the registry + :class:`ValueError` if the expression is invalid. + """ + if input_string in self._parse_unit_cache: + return self._parse_unit_cache[input_string] + + if as_delta is None: + as_delta = self.default_as_delta + + if not input_string: + return UnitsContainer() + + units = ParserHelper.from_string(input_string) + if units.scale != 1: + raise ValueError('Unit expression cannot have a scaling factor.') + + ret = UnitsContainer() + many = len(units) > 1 + for name in units: + cname = self.get_name(name) + value = units[name] + if not cname: + continue + if as_delta and (many or (not many and value != 1)): + definition = self._units[cname] + if not definition.is_multiplicative: + cname = 'delta_' + cname + ret[cname] = value + + self._parse_unit_cache[input_string] = ret + + return ret + + def parse_expression(self, input_string, case_sensitive=True, **values): + """Parse a mathematical expression including units and return a quantity object. + + Numerical constants can be specified as keyword arguments and will take precedence + over the names defined in the registry. + """ + + if not input_string: + return self.Quantity(1) + + input_string = string_preprocessor(input_string) + gen = tokenizer(input_string) + result = [] + unknown = set() + for toknum, tokval, _, _, _ in gen: + if toknum == NAME: + # TODO: Integrate math better, Replace eval, make as_delta-aware + if tokval == 'pi' or tokval in values: + result.append((toknum, tokval)) + continue + try: + tokval = self.get_name(tokval, case_sensitive) + except UndefinedUnitError as ex: + unknown.add(ex.unit_names) + if tokval: + result.extend([ + (NAME, 'Q_'), (OP, '('), (NUMBER, '1'), (OP, ','), + (NAME, 'U_'), (OP, '('), (STRING, tokval), (OP, '='), (NUMBER, '1'), (OP, ')'), + (OP, ')') + ]) + else: + result.extend([ + (NAME, 'Q_'), (OP, '('), (NUMBER, '1'), (OP, ','), + (NAME, 'U_'), (OP, '('), (OP, ')'), + (OP, ')') + ]) + else: + result.append((toknum, tokval)) + + if unknown: + raise UndefinedUnitError(unknown) + return eval(untokenize(result), + {'__builtins__': None, + 'REGISTRY': self._units, + 'Q_': self.Quantity, + 'U_': UnitsContainer, + 'pi': math.pi}, + values + ) + + __call__ = parse_expression + + def wraps(self, ret, args, strict=True): + """Wraps a function to become pint-aware. + + Use it when a function requires a numerical value but in some specific + units. The wrapper function will take a pint quantity, convert to the units + specified in `args` and then call the wrapped function with the resulting + magnitude. + + The value returned by the wrapped function will be converted to the units + specified in `ret`. + + Use None to skip argument conversion. + Set strict to False, to accept also numerical values. + + :param ret: output units. + :param args: iterable of input units. + :param strict: boolean to indicate that only quantities are accepted. + :return: the wrapped function. + :raises: + :class:`ValueError` if strict and one of the arguments is not a Quantity. + """ + + Q_ = self.Quantity + + if not isinstance(args, (list, tuple)): + args = (args, ) + + def to_units(x): + if isinstance(x, string_types): + return self.parse_units(x) + elif isinstance(x, Q_): + return x.units + return x + + units = [to_units(arg) for arg in args] + + if isinstance(ret, (list, tuple)): + ret = ret.__class__([to_units(arg) for arg in ret]) + elif isinstance(ret, string_types): + ret = self.parse_units(ret) + + def decorator(func): + assigned = tuple(attr for attr in functools.WRAPPER_ASSIGNMENTS if hasattr(func, attr)) + updated = tuple(attr for attr in functools.WRAPPER_UPDATES if hasattr(func, attr)) + @functools.wraps(func, assigned=assigned, updated=updated) + def wrapper(*values, **kw): + new_args = [] + for unit, value in zip(units, values): + if unit is None: + new_args.append(value) + elif isinstance(value, Q_): + new_args.append(self.convert(value.magnitude, value.units, unit)) + elif not strict: + new_args.append(value) + else: + raise ValueError('A wrapped function using strict=True requires ' + 'quantity for all arguments with not None units. ' + '(error found for {0}, {1})'.format(unit, value)) + + result = func(*new_args, **kw) + + if isinstance(ret, (list, tuple)): + return ret.__class__(res if unit is None else Q_(res, unit) + for unit, res in zip(ret, result)) + elif ret is not None: + return Q_(result, ret) + + return result + return wrapper + return decorator + + +def build_quantity_class(registry, force_ndarray=False): + from .quantity import _Quantity + + class Quantity(_Quantity): + pass + + Quantity._REGISTRY = registry + Quantity.force_ndarray = force_ndarray + + return Quantity + + +def build_measurement_class(registry, force_ndarray=False): + from .measurement import _Measurement, ufloat + + if ufloat is None: + class Measurement(object): + + def __init__(self, *args): + raise RuntimeError("Pint requires the 'uncertainties' package to create a Measurement object.") + + else: + class Measurement(_Measurement, registry.Quantity): + pass + + Measurement._REGISTRY = registry + Measurement.force_ndarray = force_ndarray + + return Measurement + + +class LazyRegistry(object): + + def __init__(self, args=None, kwargs=None): + self.__dict__['params'] = args or (), kwargs or {} + + def __init(self): + args, kwargs = self.__dict__['params'] + kwargs['on_redefinition'] = 'raise' + self.__class__ = UnitRegistry + self.__init__(*args, **kwargs) + + def __getattr__(self, item): + if item == '_on_redefinition': + return 'raise' + self.__init() + return getattr(self, item) + + def __setattr__(self, key, value): + if key == '__class__': + super(LazyRegistry, self).__setattr__(key, value) + else: + self.__init() + setattr(self, key, value) + + def __getitem__(self, item): + self.__init() + return self[item] + + def __call__(self, *args, **kwargs): + self.__init() + return self(*args, **kwargs) diff --git a/CadQuery/Libs/pint/util.py b/CadQuery/Libs/pint/util.py new file mode 100644 index 0000000..6589d83 --- /dev/null +++ b/CadQuery/Libs/pint/util.py @@ -0,0 +1,425 @@ +# -*- 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 operator +from numbers import Number +from fractions import Fraction + +import logging +from token import STRING, NAME, OP +from tokenize import untokenize + +from .compat import string_types, tokenizer, lru_cache, NullHandler, maketrans + +logger = logging.getLogger(__name__) +logger.addHandler(NullHandler()) + + +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 + + +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=None): + path = (path or []) + [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 + + +def find_connected_nodes(graph, start, visited=None): + if not start in graph: + return None + + visited = (visited or set()) + visited.add(start) + + for node in graph[start]: + if node not in visited: + find_connected_nodes(graph, node, visited) + + return visited + + +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): + return cls._from_string(input_string).copy() + + @classmethod + @lru_cache() + 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 = tokenizer(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())) + + def copy(self): + return ParserHelper(scale=self.scale, **self) + + def __missing__(self, key): + return 0.0 + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.scale == other.scale and super(ParserHelper, self).__eq__(other) + elif isinstance(other, dict): + return self.scale == 1 and super(ParserHelper, self).__eq__(other) + elif isinstance(other, string_types): + return self == ParserHelper.from_string(other) + elif isinstance(other, Number): + return self.scale == other and not len(self) + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + 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(["'{0}': {1}".format(key, value) for key, value in sorted(self.items())]) + return ''.format(self.scale, tmp) + + def __mul__(self, other): + if isinstance(other, string_types): + self.add(other, 1) + elif isinstance(other, Number): + self.scale *= other + elif isinstance(other, self.__class__): + self.scale *= other.scale + self.operate(other.items()) + 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 + elif isinstance(other, self.__class__): + self.scale /= other.scale + self.operate(other.items(), operator.sub) + 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 + elif isinstance(other, self.__class__): + self.scale *= other.scale + self.operate(other.items(), operator.add) + else: + self.operate(other.items(), operator.add) + return self + + +#: List of regex substitution pairs. +_subs_re = [(r"([\w\.\-\+\*\\\^])\s+", r"\1 "), # merge multiple spaces + (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"\b([0-9]+\.?[0-9]*)(?=[e|E][a-zA-Z]|[a-df-zA-DF-Z])", r"\1*"), # Handle numberLetter for multiplication + (r"([\w\.\-])\s+(?=\w)", r"\1*"), # Handle space for multiplication + ] + +#: 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] +_pretty_table = maketrans('⁰¹²³⁴⁵⁶⁷⁸⁹·⁻', '0123456789*-') +_pretty_exp_re = re.compile(r"⁻?[⁰¹²³⁴⁵⁶⁷⁸⁹]+(?:\.[⁰¹²³⁴⁵⁶⁷⁸⁹]*)?") + + +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) + + # Replace pretty format characters + for pretty_exp in _pretty_exp_re.findall(input_string): + exp = '**' + pretty_exp.translate(_pretty_table) + input_string = input_string.replace(pretty_exp, exp) + input_string = input_string.translate(_pretty_table) + + # Handle caret exponentiation + input_string = input_string.replace("^", "**") + return input_string diff --git a/CadQuery/Tools/package_libraries.py b/CadQuery/Tools/package_libraries.py index 01e740d..f7a00c7 100644 --- a/CadQuery/Tools/package_libraries.py +++ b/CadQuery/Tools/package_libraries.py @@ -12,10 +12,10 @@ ZIP = os.path.join(os.getcwd(), 'libs.zip') if len(sys.argv) == 2 and sys.argv[1] == 'gen': #--- gen zip file - import jedi, pep8, pyqode, pyqode.core, pyqode.python, pyqode.qt, pygments, frosted, pies, builtins, future, pyflakes, docutils + import jedi, pep8, pyqode, pyqode.core, pyqode.python, pyqode.qt, pygments, frosted, pies, builtins, future, pyflakes, docutils, pint from qidle.system import embed_package_into_zip embed_package_into_zip([jedi, pep8, pyqode, pyqode.core, pyqode.python, - pyqode.qt, pygments, pyflakes, builtins, future, docutils], ZIP) + pyqode.qt, pygments, pyflakes, builtins, future, docutils, pint], ZIP) else: # remove all pyqode path from sys.path (to make sure the package are # imported from the zip archive)