Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright 2011-2015 Kwant authors.
2#
3# This file is part of Kwant. It is subject to the license terms in the file
4# LICENSE.rst found in the top-level directory of this distribution and at
5# https://kwant-project.org/license. A list of Kwant authors can be found in
6# the file AUTHORS.rst at the top-level directory of this distribution and at
7# https://kwant-project.org/authors.
9import sys
10import numpy as np
11import numbers
12import inspect
13import warnings
14import importlib
15import functools
16import collections
17from contextlib import contextmanager
19__all__ = ['KwantDeprecationWarning', 'UserCodeError']
22class KwantDeprecationWarning(Warning):
23 """Class of warnings about a deprecated feature of Kwant.
25 DeprecationWarning has been made invisible by default in Python 2.7 in order
26 to not confuse non-developer users with warnings that are not relevant to
27 them. In the case of Kwant, by far most users are developers, so we feel
28 that a KwantDeprecationWarning that is visible by default is useful.
29 """
30 pass
33class UserCodeError(Exception):
34 """Class for errors that occur in user-provided code.
36 Usually users will define value functions that Kwant calls in order to
37 evaluate the Hamiltonian. If one of these function raises an exception
38 then it is caught and this error is raised in its place. This makes it
39 clear that the error is from the user's code (and not a bug in Kwant) and
40 also makes it possible for any libraries that wrap Kwant to detect when a
41 user's function causes an error.
42 """
43 pass
46def deprecate_parameter(parameter_name, version=None, help=None,
47 stacklevel=3):
48 """Trigger a deprecation warning if the wrapped function is called
49 with the provided parameter."""
51 message = ("The '{}' parameter has been deprecated since version {} -- {}"
52 .format(parameter_name, version, help))
54 def warn():
55 warnings.warn(message, KwantDeprecationWarning,
56 stacklevel=stacklevel)
58 def wrapper(f=None):
60 # Instead of being used as a decorator, can be called with
61 # no arguments to just raise the warning.
62 if f is None:
63 warn()
64 return
66 sig = inspect.signature(f)
68 @functools.wraps(f)
69 def inner(*args, **kwargs):
70 # If the named argument is truthy
71 if sig.bind(*args, **kwargs).arguments.get(parameter_name):
72 warn()
73 return f(*args, **kwargs)
75 return inner
77 return wrapper
80# Deprecation for 'args' parameter; defined once to minimize boilerplate,
81# as this parameter is present all over Kwant.
82deprecate_args = deprecate_parameter('args', version=1.4, help=
83 "Instead, provide named parameters as a dictionary via 'params'.")
86def interleave(seq):
87 """Return an iterator that yields pairs of elements from a sequence.
89 If 'seq' has an odd number of elements then the last element is dropped.
91 Examples
92 --------
93 >>> list(interleave(range(4)))
94 [(0, 1), (2, 3)]
95 >>> list(interleave(range(5))
96 [(0, 1), (2, 3)]
97 """
98 # zip, when given the same iterator twice, turns a sequence into a
99 # sequence of pairs.
100 iseq = iter(seq)
101 return zip(iseq, iseq)
104def ensure_isinstance(obj, class_or_tuple, msg=None):
105 if isinstance(obj, class_or_tuple): 105 ↛ 107line 105 didn't jump to line 107, because the condition on line 105 was never false
106 return
107 if msg is None:
108 if isinstance(class_or_tuple, tuple):
109 names = ", or ".join(t.__name__ for t in class_or_tuple)
110 else:
111 names = class_or_tuple.__name__
112 msg = "Expecting an instance of {}.".format(names)
113 raise TypeError(msg)
115def ensure_rng(rng=None):
116 """Turn rng into a random number generator instance
118 If rng is None, return the RandomState instance used by np.random.
119 If rng is an integer, return a new RandomState instance seeded with rng.
120 If rng is already a RandomState instance, return it.
121 Otherwise raise ValueError.
122 """
123 if rng is None:
124 return np.random.mtrand._rand
125 if isinstance(rng, numbers.Integral):
126 return np.random.RandomState(rng)
127 if all(hasattr(rng, attr) for attr in ('random_sample', 'randn', 127 ↛ 130line 127 didn't jump to line 130, because the condition on line 127 was never false
128 'randint', 'choice')):
129 return rng
130 raise ValueError("Expecting a seed or an object that offers the "
131 "numpy.random.RandomState interface.")
134@contextmanager
135def reraise_warnings(level=3):
136 with warnings.catch_warnings(record=True) as caught_warnings:
137 yield
138 for warning in caught_warnings:
139 warnings.warn(warning.message, stacklevel=level)
142def get_parameters(func):
143 """Return the names of parameters of a function.
145 It is made sure that the function can be called as func(*args) with
146 'args' corresponding to the returned parameter names.
148 Returns
149 -------
150 param_names : list
151 Names of positional parameters that appear in the signature of 'func'.
152 """
153 def error(msg):
154 fname = inspect.getsourcefile(func)
155 try:
156 line = inspect.getsourcelines(func)[1]
157 except OSError:
158 line = '<unknown line>'
159 raise ValueError("{}:\nFile {}, line {}, in {}".format(
160 msg, repr(fname), line, func.__name__))
162 P = inspect.Parameter
163 pars = inspect.signature(func).parameters # an *ordered mapping*
164 names = []
165 for k, v in pars.items():
166 if v.kind in (P.POSITIONAL_ONLY, P.POSITIONAL_OR_KEYWORD):
167 if v.default is P.empty:
168 names.append(k)
169 else:
170 error("Arguments of value functions "
171 "must not have default values")
172 elif v.kind is P.KEYWORD_ONLY:
173 error("Keyword-only arguments are not allowed in value functions")
174 elif v.kind in (P.VAR_POSITIONAL, P.VAR_KEYWORD): 174 ↛ 165line 174 didn't jump to line 165, because the condition on line 174 was never false
175 error("Value functions must not take *args or **kwargs")
176 return tuple(names)
179class lazy_import:
180 def __init__(self, module, package='kwant', deprecation_warning=False):
181 if module.startswith('.') and not package: 181 ↛ 182line 181 didn't jump to line 182, because the condition on line 181 was never true
182 raise ValueError('Cannot import a relative module without a package.')
183 self.__module = module
184 self.__package = package
185 self.__deprecation_warning = deprecation_warning
187 def __getattr__(self, name):
188 if self.__deprecation_warning: 188 ↛ 189line 188 didn't jump to line 189, because the condition on line 188 was never true
189 msg = ("Accessing {0} without an explicit import is deprecated. "
190 "Instead, explicitly 'import {0}'."
191 ).format('.'.join((self.__package, self.__module)))
192 warnings.warn(msg, KwantDeprecationWarning, stacklevel=2)
193 relative_module = '.' + self.__module
194 mod = importlib.import_module(relative_module, self.__package)
195 # Replace this _LazyModuleProxy with an actual module
196 package = sys.modules[self.__package]
197 setattr(package, self.__module, mod)
198 return getattr(mod, name)
201def _hashable(obj):
202 return isinstance(obj, collections.abc.Hashable)
205def memoize(f):
206 """Decorator to memoize a function that works even with unhashable args.
208 This decorator will even work with functions whose args are not hashable.
209 The cache key is made up by the hashable arguments and the ids of the
210 non-hashable args. It is up to the user to make sure that non-hashable
211 args do not change during the lifetime of the decorator.
213 This decorator will keep reevaluating functions that return None.
214 """
215 def lookup(*args):
216 key = tuple(arg if _hashable(arg) else id(arg) for arg in args)
217 result = cache.get(key)
218 if result is None:
219 cache[key] = result = f(*args)
220 return result
221 cache = {}
222 return lookup