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