Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/numexpr/expressions.py: 41%
308 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-10 06:15 +0000
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-10 06:15 +0000
1###################################################################
2# Numexpr - Fast numerical array expression evaluator for NumPy.
3#
4# License: MIT
5# Author: See AUTHORS.txt
6#
7# See LICENSE.txt and LICENSES/*.txt for details about copyright and
8# rights to use.
9####################################################################
11__all__ = ['E']
13import operator
14import sys
15import threading
17import numpy
19# Declare a double type that does not exist in Python space
20double = numpy.double
22# The default kind for undeclared variables
23default_kind = 'double'
24int_ = numpy.int32
25long_ = numpy.int64
27type_to_kind = {bool: 'bool', int_: 'int', long_: 'long', float: 'float',
28 double: 'double', complex: 'complex', bytes: 'bytes', str: 'str'}
29kind_to_type = {'bool': bool, 'int': int_, 'long': long_, 'float': float,
30 'double': double, 'complex': complex, 'bytes': bytes, 'str': str}
31kind_rank = ('bool', 'int', 'long', 'float', 'double', 'complex', 'none')
32scalar_constant_types = [bool, int_, int, float, double, complex, bytes, str]
34scalar_constant_types = tuple(scalar_constant_types)
36from numexpr import interpreter
38class Expression(object):
39 def __init__(self):
40 object.__init__(self)
42 def __getattr__(self, name):
43 if name.startswith('_'):
44 try:
45 return self.__dict__[name]
46 except KeyError:
47 raise AttributeError
48 else:
49 return VariableNode(name, default_kind)
52E = Expression()
55class Context(threading.local):
57 def get(self, value, default):
58 return self.__dict__.get(value, default)
60 def get_current_context(self):
61 return self.__dict__
63 def set_new_context(self, dict_):
64 self.__dict__.update(dict_)
66# This will be called each time the local object is used in a separate thread
67_context = Context()
70def get_optimization():
71 return _context.get('optimization', 'none')
74# helper functions for creating __magic__ methods
75def ophelper(f):
76 def func(*args):
77 args = list(args)
78 for i, x in enumerate(args):
79 if isConstant(x):
80 args[i] = x = ConstantNode(x)
81 if not isinstance(x, ExpressionNode):
82 raise TypeError("unsupported object type: %s" % type(x))
83 return f(*args)
85 func.__name__ = f.__name__
86 func.__doc__ = f.__doc__
87 func.__dict__.update(f.__dict__)
88 return func
91def allConstantNodes(args):
92 "returns True if args are all ConstantNodes."
93 for x in args:
94 if not isinstance(x, ConstantNode):
95 return False
96 return True
99def isConstant(ex):
100 "Returns True if ex is a constant scalar of an allowed type."
101 return isinstance(ex, scalar_constant_types)
104def commonKind(nodes):
105 node_kinds = [node.astKind for node in nodes]
106 str_count = node_kinds.count('bytes') + node_kinds.count('str')
107 if 0 < str_count < len(node_kinds): # some args are strings, but not all
108 raise TypeError("strings can only be operated with strings")
109 if str_count > 0: # if there are some, all of them must be
110 return 'bytes'
111 n = -1
112 for x in nodes:
113 n = max(n, kind_rank.index(x.astKind))
114 return kind_rank[n]
117max_int32 = 2147483647
118min_int32 = -max_int32 - 1
121def bestConstantType(x):
122 # ``numpy.string_`` is a subclass of ``bytes``
123 if isinstance(x, (bytes, str)):
124 return bytes
125 # Numeric conversion to boolean values is not tried because
126 # ``bool(1) == True`` (same for 0 and False), so 0 and 1 would be
127 # interpreted as booleans when ``False`` and ``True`` are already
128 # supported.
129 if isinstance(x, (bool, numpy.bool_)):
130 return bool
131 # ``long`` objects are kept as is to allow the user to force
132 # promotion of results by using long constants, e.g. by operating
133 # a 32-bit array with a long (64-bit) constant.
134 if isinstance(x, (long_, numpy.int64)):
135 return long_
136 # ``double`` objects are kept as is to allow the user to force
137 # promotion of results by using double constants, e.g. by operating
138 # a float (32-bit) array with a double (64-bit) constant.
139 if isinstance(x, double):
140 return double
141 if isinstance(x, numpy.float32):
142 return float
143 if isinstance(x, (int, numpy.integer)):
144 # Constants needing more than 32 bits are always
145 # considered ``long``, *regardless of the platform*, so we
146 # can clearly tell 32- and 64-bit constants apart.
147 if not (min_int32 <= x <= max_int32):
148 return long_
149 return int_
150 # The duality of float and double in Python avoids that we have to list
151 # ``double`` too.
152 for converter in float, complex:
153 try:
154 y = converter(x)
155 except Exception as err:
156 continue
157 if y == x or numpy.isnan(y):
158 return converter
161def getKind(x):
162 converter = bestConstantType(x)
163 return type_to_kind[converter]
166def binop(opname, reversed=False, kind=None):
167 # Getting the named method from self (after reversal) does not
168 # always work (e.g. int constants do not have a __lt__ method).
169 opfunc = getattr(operator, "__%s__" % opname)
171 @ophelper
172 def operation(self, other):
173 if reversed:
174 self, other = other, self
175 if allConstantNodes([self, other]):
176 return ConstantNode(opfunc(self.value, other.value))
177 else:
178 return OpNode(opname, (self, other), kind=kind)
180 return operation
183def func(func, minkind=None, maxkind=None):
184 @ophelper
185 def function(*args):
186 if allConstantNodes(args):
187 return ConstantNode(func(*[x.value for x in args]))
188 kind = commonKind(args)
189 if kind in ('int', 'long'):
190 # Exception for following NumPy casting rules
191 #FIXME: this is not always desirable. The following
192 # functions which return ints (for int inputs) on numpy
193 # but not on numexpr: copy, abs, fmod, ones_like
194 kind = 'double'
195 else:
196 # Apply regular casting rules
197 if minkind and kind_rank.index(minkind) > kind_rank.index(kind):
198 kind = minkind
199 if maxkind and kind_rank.index(maxkind) < kind_rank.index(kind):
200 kind = maxkind
201 return FuncNode(func.__name__, args, kind)
203 return function
206@ophelper
207def where_func(a, b, c):
208 if isinstance(a, ConstantNode):
209 return b if a.value else c
210 if allConstantNodes([a, b, c]):
211 return ConstantNode(numpy.where(a, b, c))
212 return FuncNode('where', [a, b, c])
215def encode_axis(axis):
216 if isinstance(axis, ConstantNode):
217 axis = axis.value
218 if axis is None:
219 axis = interpreter.allaxes
220 else:
221 if axis < 0:
222 raise ValueError("negative axis are not supported")
223 if axis > 254:
224 raise ValueError("cannot encode axis")
225 return RawNode(axis)
228def gen_reduce_axis_func(name):
229 def _func(a, axis=None):
230 axis = encode_axis(axis)
231 if isinstance(a, ConstantNode):
232 return a
233 if isinstance(a, (bool, int_, long_, float, double, complex)):
234 a = ConstantNode(a)
235 return FuncNode(name, [a, axis], kind=a.astKind)
236 return _func
239@ophelper
240def contains_func(a, b):
241 return FuncNode('contains', [a, b], kind='bool')
244@ophelper
245def div_op(a, b):
246 if get_optimization() in ('moderate', 'aggressive'):
247 if (isinstance(b, ConstantNode) and
248 (a.astKind == b.astKind) and
249 a.astKind in ('float', 'double', 'complex')):
250 return OpNode('mul', [a, ConstantNode(1. / b.value)])
251 return OpNode('div', [a, b])
254@ophelper
255def truediv_op(a, b):
256 if get_optimization() in ('moderate', 'aggressive'):
257 if (isinstance(b, ConstantNode) and
258 (a.astKind == b.astKind) and
259 a.astKind in ('float', 'double', 'complex')):
260 return OpNode('mul', [a, ConstantNode(1. / b.value)])
261 kind = commonKind([a, b])
262 if kind in ('bool', 'int', 'long'):
263 kind = 'double'
264 return OpNode('div', [a, b], kind=kind)
267@ophelper
268def rtruediv_op(a, b):
269 return truediv_op(b, a)
272@ophelper
273def pow_op(a, b):
274 if (b.astKind in ('int', 'long') and
275 a.astKind in ('int', 'long') and
276 numpy.any(b.value < 0)):
278 raise ValueError(
279 'Integers to negative integer powers are not allowed.')
281 if allConstantNodes([a, b]):
282 return ConstantNode(a.value ** b.value)
283 if isinstance(b, ConstantNode):
284 x = b.value
285 if get_optimization() == 'aggressive':
286 RANGE = 50 # Approximate break even point with pow(x,y)
287 # Optimize all integral and half integral powers in [-RANGE, RANGE]
288 # Note: for complex numbers RANGE could be larger.
289 if (int(2 * x) == 2 * x) and (-RANGE <= abs(x) <= RANGE):
290 n = int_(abs(x))
291 ishalfpower = int_(abs(2 * x)) % 2
293 def multiply(x, y):
294 if x is None: return y
295 return OpNode('mul', [x, y])
297 r = None
298 p = a
299 mask = 1
300 while True:
301 if (n & mask):
302 r = multiply(r, p)
303 mask <<= 1
304 if mask > n:
305 break
306 p = OpNode('mul', [p, p])
307 if ishalfpower:
308 kind = commonKind([a])
309 if kind in ('int', 'long'):
310 kind = 'double'
311 r = multiply(r, OpNode('sqrt', [a], kind))
312 if r is None:
313 r = OpNode('ones_like', [a])
314 if x < 0:
315 r = OpNode('div', [ConstantNode(1), r])
316 return r
317 if get_optimization() in ('moderate', 'aggressive'):
318 if x == -1:
319 return OpNode('div', [ConstantNode(1), a])
320 if x == 0:
321 return OpNode('ones_like', [a])
322 if x == 0.5:
323 kind = a.astKind
324 if kind in ('int', 'long'): kind = 'double'
325 return FuncNode('sqrt', [a], kind=kind)
326 if x == 1:
327 return a
328 if x == 2:
329 return OpNode('mul', [a, a])
330 return OpNode('pow', [a, b])
332# The functions and the minimum and maximum types accepted
333numpy.expm1x = numpy.expm1
334functions = {
335 'copy': func(numpy.copy),
336 'ones_like': func(numpy.ones_like),
337 'sqrt': func(numpy.sqrt, 'float'),
339 'sin': func(numpy.sin, 'float'),
340 'cos': func(numpy.cos, 'float'),
341 'tan': func(numpy.tan, 'float'),
342 'arcsin': func(numpy.arcsin, 'float'),
343 'arccos': func(numpy.arccos, 'float'),
344 'arctan': func(numpy.arctan, 'float'),
346 'sinh': func(numpy.sinh, 'float'),
347 'cosh': func(numpy.cosh, 'float'),
348 'tanh': func(numpy.tanh, 'float'),
349 'arcsinh': func(numpy.arcsinh, 'float'),
350 'arccosh': func(numpy.arccosh, 'float'),
351 'arctanh': func(numpy.arctanh, 'float'),
353 'fmod': func(numpy.fmod, 'float'),
354 'arctan2': func(numpy.arctan2, 'float'),
356 'log': func(numpy.log, 'float'),
357 'log1p': func(numpy.log1p, 'float'),
358 'log10': func(numpy.log10, 'float'),
359 'exp': func(numpy.exp, 'float'),
360 'expm1': func(numpy.expm1, 'float'),
362 'abs': func(numpy.absolute, 'float'),
363 'ceil': func(numpy.ceil, 'float', 'double'),
364 'floor': func(numpy.floor, 'float', 'double'),
366 'where': where_func,
368 'real': func(numpy.real, 'double', 'double'),
369 'imag': func(numpy.imag, 'double', 'double'),
370 'complex': func(complex, 'complex'),
371 'conj': func(numpy.conj, 'complex'),
373 'sum': gen_reduce_axis_func('sum'),
374 'prod': gen_reduce_axis_func('prod'),
375 'min': gen_reduce_axis_func('min'),
376 'max': gen_reduce_axis_func('max'),
377 'contains': contains_func,
378}
381class ExpressionNode(object):
382 """
383 An object that represents a generic number object.
385 This implements the number special methods so that we can keep
386 track of how this object has been used.
387 """
388 astType = 'generic'
390 def __init__(self, value=None, kind=None, children=None):
391 object.__init__(self)
392 self.value = value
393 if kind is None:
394 kind = 'none'
395 self.astKind = kind
396 if children is None:
397 self.children = ()
398 else:
399 self.children = tuple(children)
401 def get_real(self):
402 if self.astType == 'constant':
403 return ConstantNode(complex(self.value).real)
404 return OpNode('real', (self,), 'double')
406 real = property(get_real)
408 def get_imag(self):
409 if self.astType == 'constant':
410 return ConstantNode(complex(self.value).imag)
411 return OpNode('imag', (self,), 'double')
413 imag = property(get_imag)
415 def __str__(self):
416 return '%s(%s, %s, %s)' % (self.__class__.__name__, self.value,
417 self.astKind, self.children)
419 def __repr__(self):
420 return self.__str__()
422 def __neg__(self):
423 return OpNode('neg', (self,))
425 def __invert__(self):
426 return OpNode('invert', (self,))
428 def __pos__(self):
429 return self
431 # The next check is commented out. See #24 for more info.
433 def __bool__(self):
434 raise TypeError("You can't use Python's standard boolean operators in "
435 "NumExpr expressions. You should use their bitwise "
436 "counterparts instead: '&' instead of 'and', "
437 "'|' instead of 'or', and '~' instead of 'not'.")
439 __add__ = __radd__ = binop('add')
440 __sub__ = binop('sub')
441 __rsub__ = binop('sub', reversed=True)
442 __mul__ = __rmul__ = binop('mul')
443 __truediv__ = truediv_op
444 __rtruediv__ = rtruediv_op
445 __pow__ = pow_op
446 __rpow__ = binop('pow', reversed=True)
447 __mod__ = binop('mod')
448 __rmod__ = binop('mod', reversed=True)
450 __lshift__ = binop('lshift')
451 __rlshift__ = binop('lshift', reversed=True)
452 __rshift__ = binop('rshift')
453 __rrshift__ = binop('rshift', reversed=True)
455 # boolean operations
457 __and__ = binop('and', kind='bool')
458 __or__ = binop('or', kind='bool')
460 __gt__ = binop('gt', kind='bool')
461 __ge__ = binop('ge', kind='bool')
462 __eq__ = binop('eq', kind='bool')
463 __ne__ = binop('ne', kind='bool')
464 __lt__ = binop('gt', reversed=True, kind='bool')
465 __le__ = binop('ge', reversed=True, kind='bool')
468class LeafNode(ExpressionNode):
469 leafNode = True
472class VariableNode(LeafNode):
473 astType = 'variable'
475 def __init__(self, value=None, kind=None, children=None):
476 LeafNode.__init__(self, value=value, kind=kind)
479class RawNode(object):
480 """
481 Used to pass raw integers to interpreter.
482 For instance, for selecting what function to use in func1.
483 Purposely don't inherit from ExpressionNode, since we don't wan't
484 this to be used for anything but being walked.
485 """
486 astType = 'raw'
487 astKind = 'none'
489 def __init__(self, value):
490 self.value = value
491 self.children = ()
493 def __str__(self):
494 return 'RawNode(%s)' % (self.value,)
496 __repr__ = __str__
499class ConstantNode(LeafNode):
500 astType = 'constant'
502 def __init__(self, value=None, children=None):
503 kind = getKind(value)
504 # Python float constants are double precision by default
505 if kind == 'float' and isinstance(value, float):
506 kind = 'double'
507 LeafNode.__init__(self, value=value, kind=kind)
509 def __neg__(self):
510 return ConstantNode(-self.value)
512 def __invert__(self):
513 return ConstantNode(~self.value)
516class OpNode(ExpressionNode):
517 astType = 'op'
519 def __init__(self, opcode=None, args=None, kind=None):
520 if (kind is None) and (args is not None):
521 kind = commonKind(args)
522 ExpressionNode.__init__(self, value=opcode, kind=kind, children=args)
525class FuncNode(OpNode):
526 def __init__(self, opcode=None, args=None, kind=None):
527 if (kind is None) and (args is not None):
528 kind = commonKind(args)
529 OpNode.__init__(self, opcode, args, kind)