Source code for tkwant._common

# Copyright 2016-2023 tkwant authors.
#
# This file is part of tkwant.  It is subject to the license terms in the file
# LICENSE.rst found in the top-level directory of this distribution and at
# https://tkwant.kwant-project.org/doc/stable/pre/license.html.
# A list of tkwant authors can be found in
# the file AUTHORS.rst at the top-level directory of this distribution and at
# https://tkwant.kwant-project.org/doc/stable/pre/authors.html.
"""Utility routines."""


import subprocess
import os
import inspect
import collections
import numpy as np

__all__ = ['version', 'TkwantDeprecationWarning', 'is_type', 'is_type_array',
           'is_not_empty', 'is_zero', 'time_start', 'time_name', 'memoize']

# tkwant's default initial time and time argument name
time_start = 0
time_name = 'time'

package_root = os.path.dirname(os.path.realpath(__file__))
distr_root = os.path.dirname(package_root)


[docs]class TkwantDeprecationWarning(Warning): """Class of warnings about a deprecated feature of tkwant. DeprecationWarning has been made invisible by default in Python 2.7 in order to not confuse non-developer users with warnings that are not relevant to them. In the case of tkwant, by far most users are developers, so we feel that a TkwantDeprecationWarning that is visible by default is useful. """ pass
def ensure_isinstance(obj, typ, msg=None): if isinstance(obj, typ): return if msg is None: msg = "Expecting an instance of {}.".format(typ.__name__) raise TypeError(msg) # type checking and small helper routines def is_type(variable, generic_type, require_finite=False): """Return true if type(variable) matches the generic type.""" # TODO: require finite for all types if generic_type == 'integer': return np.issubdtype(type(variable), np.integer) if generic_type == 'number': return np.issubdtype(type(variable), np.number) if generic_type == 'real_number': _type = type(variable) is_number = np.issubdtype(_type, np.number) is_complex = np.issubdtype(_type, np.complexfloating) if is_number and require_finite: is_finite = np.isfinite(variable) return is_number and is_finite and not is_complex return is_number and not is_complex if generic_type == 'complex': _type = type(variable) is_number = np.issubdtype(_type, np.number) is_complex = np.issubdtype(_type, np.complexfloating) return is_number and is_complex raise NotImplementedError('generic_type= {} not implemented' .format(generic_type)) def is_type_array(array, generic_type): """Return true everywhere where type(array) matches the generic type""" array = np.array(array) return np.array([is_type(x, generic_type) for x in array.flatten()]) def is_zero(x, tol=1E-14): """Return true if |x| < tol.""" return np.abs(x) < tol def is_not_empty(obj): """Check if obj exists.""" try: return obj.any() or len(obj) > 0 except AttributeError: if obj: return True return False def get_default_function_argument(func, kwarg): """Return the default value of a function argument. Parameters ---------- func : callable Function to inspect. kwarg : str Function argument for which we like to obtain the default value. Returns ------- default : obj Default argument of function `func`. If func(kwarg=42), the returned value would be 42. Notes ----- A TypeError is raised, if the function `func` has no `kwarg` argument with a default value. """ P = inspect.Parameter pars = inspect.signature(func).parameters # an *ordered mapping* for k, v in pars.items(): if k == kwarg and v.kind in (P.POSITIONAL_OR_KEYWORD, P.KEYWORD_ONLY): if v.default is not inspect._empty: return v.default try: func_name = func.__name__ except AttributeError: func_name = '<unknown func>' # for functools.partial and friends raise ValueError("Function argument {} not present in {}". format(kwarg, func_name)) # memoize from kwant def _hashable(obj): return isinstance(obj, collections.abc.Hashable) def memoize(f): """Decorator to memoize a function that works even with unhashable args. This decorator will even work with functions whose args are not hashable. The cache key is made up by the hashable arguments and the ids of the non-hashable args. It is up to the user to make sure that non-hashable args do not change during the lifetime of the decorator. This decorator will keep reevaluating functions that return None. """ def lookup(*args): key = tuple(arg if _hashable(arg) else id(arg) for arg in args) result = cache.get(key) if result is None: cache[key] = result = f(*args) return result cache = {} return lookup