Hide keyboard shortcuts

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. 

8 

9import sys 

10import numpy as np 

11import numbers 

12import inspect 

13import warnings 

14import importlib 

15import functools 

16import collections 

17from contextlib import contextmanager 

18 

19__all__ = ['KwantDeprecationWarning', 'UserCodeError'] 

20 

21 

22class KwantDeprecationWarning(Warning): 

23 """Class of warnings about a deprecated feature of Kwant. 

24 

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 

31 

32 

33class UserCodeError(Exception): 

34 """Class for errors that occur in user-provided code. 

35 

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 

44 

45 

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.""" 

50 

51 message = ("The '{}' parameter has been deprecated since version {} -- {}" 

52 .format(parameter_name, version, help)) 

53 

54 def warn(): 

55 warnings.warn(message, KwantDeprecationWarning, 

56 stacklevel=stacklevel) 

57 

58 def wrapper(f=None): 

59 

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 

65 

66 sig = inspect.signature(f) 

67 

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) 

74 

75 return inner 

76 

77 return wrapper 

78 

79 

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'.") 

84 

85 

86def interleave(seq): 

87 """Return an iterator that yields pairs of elements from a sequence. 

88 

89 If 'seq' has an odd number of elements then the last element is dropped. 

90 

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) 

102 

103 

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) 

114 

115def ensure_rng(rng=None): 

116 """Turn rng into a random number generator instance 

117 

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.") 

132 

133 

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) 

140 

141 

142def get_parameters(func): 

143 """Return the names of parameters of a function. 

144 

145 It is made sure that the function can be called as func(*args) with 

146 'args' corresponding to the returned parameter names. 

147 

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__)) 

161 

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) 

177 

178 

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 

186 

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) 

199 

200 

201def _hashable(obj): 

202 return isinstance(obj, collections.abc.Hashable) 

203 

204 

205def memoize(f): 

206 """Decorator to memoize a function that works even with unhashable args. 

207 

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. 

212 

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