numexpr 2.10.2__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
numexpr/necompiler.py ADDED
@@ -0,0 +1,1005 @@
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
+ from typing import Optional, Dict
12
+ import __future__
13
+ import sys
14
+ import os
15
+ import threading
16
+ import re
17
+
18
+ import numpy
19
+
20
+ is_cpu_amd_intel = False # DEPRECATION WARNING: WILL BE REMOVED IN FUTURE RELEASE
21
+ from numexpr import interpreter, expressions, use_vml
22
+ from numexpr.utils import CacheDict, ContextDict
23
+
24
+ # Declare a double type that does not exist in Python space
25
+ double = numpy.double
26
+ double = numpy.double
27
+
28
+ int_ = numpy.int32
29
+ long_ = numpy.int64
30
+
31
+ typecode_to_kind = {'b': 'bool', 'i': 'int', 'l': 'long', 'f': 'float', 'd': 'double',
32
+ 'c': 'complex', 'n': 'none', 's': 'str'}
33
+ kind_to_typecode = {'bool': 'b', 'int': 'i', 'long': 'l', 'float': 'f', 'double': 'd',
34
+ 'complex': 'c', 'bytes': 's', 'str': 's', 'none': 'n'}
35
+ type_to_typecode = {bool: 'b', int_: 'i', long_: 'l', float: 'f',
36
+ double: 'd', complex: 'c', bytes: 's', str: 's'}
37
+ type_to_kind = expressions.type_to_kind
38
+ kind_to_type = expressions.kind_to_type
39
+ default_type = kind_to_type[expressions.default_kind]
40
+ scalar_constant_kinds = list(kind_to_typecode.keys())
41
+
42
+ # VML functions that are implemented in numexpr
43
+ vml_functions = [
44
+ "div", # interp_body.cpp
45
+ "inv", # interp_body.cpp
46
+ "pow", # interp_body.cpp
47
+ # Keep the rest of this list in sync with the ones listed in functions.hpp
48
+ "sqrt",
49
+ "sin",
50
+ "cos",
51
+ "tan",
52
+ "arcsin",
53
+ "arccos",
54
+ "arctan",
55
+ "sinh",
56
+ "cosh",
57
+ "tanh",
58
+ "arcsinh",
59
+ "arccosh",
60
+ "arctanh",
61
+ "log",
62
+ "log1p",
63
+ "log10",
64
+ "exp",
65
+ "expm1",
66
+ "absolute",
67
+ "conjugate",
68
+ "arctan2",
69
+ "fmod",
70
+ "ceil",
71
+ "floor"
72
+ ]
73
+
74
+
75
+ class ASTNode():
76
+ """Abstract Syntax Tree node.
77
+
78
+ Members:
79
+
80
+ astType -- type of node (op, constant, variable, raw, or alias)
81
+ astKind -- the type of the result (bool, float, etc.)
82
+ value -- value associated with this node.
83
+ An opcode, numerical value, a variable name, etc.
84
+ children -- the children below this node
85
+ reg -- the register assigned to the result for this node.
86
+ """
87
+ cmpnames = ['astType', 'astKind', 'value', 'children']
88
+
89
+ def __init__(self, astType='generic', astKind='unknown', value=None, children=()):
90
+ self.astType = astType
91
+ self.astKind = astKind
92
+ self.value = value
93
+ self.children = tuple(children)
94
+ self.reg = None
95
+
96
+ def __eq__(self, other):
97
+ if self.astType == 'alias':
98
+ self = self.value
99
+ if other.astType == 'alias':
100
+ other = other.value
101
+ if not isinstance(other, ASTNode):
102
+ return False
103
+ for name in self.cmpnames:
104
+ if getattr(self, name) != getattr(other, name):
105
+ return False
106
+ return True
107
+
108
+ def __lt__(self,other):
109
+ # RAM: this is a fix for issue #88 whereby sorting on constants
110
+ # that may be of astKind == 'complex' but type(self.value) == int or float
111
+ # Here we let NumPy sort as it will cast data properly for comparison
112
+ # when the Python built-ins will raise an error.
113
+ if self.astType == 'constant':
114
+ if self.astKind == other.astKind:
115
+ return numpy.array(self.value) < numpy.array(other.value)
116
+ return self.astKind < other.astKind
117
+ else:
118
+ raise TypeError('Sorting not implemented for astType: %s'%self.astType)
119
+
120
+ def __hash__(self):
121
+ if self.astType == 'alias':
122
+ self = self.value
123
+ return hash((self.astType, self.astKind, self.value, self.children))
124
+
125
+ def __str__(self):
126
+ return 'AST(%s, %s, %s, %s, %s)' % (self.astType, self.astKind,
127
+ self.value, self.children, self.reg)
128
+
129
+ def __repr__(self):
130
+ return '<AST object at %s>' % id(self)
131
+
132
+ def key(self):
133
+ return (self.astType, self.astKind, self.value, self.children)
134
+
135
+ def typecode(self):
136
+ return kind_to_typecode[self.astKind]
137
+
138
+ def postorderWalk(self):
139
+ for c in self.children:
140
+ for w in c.postorderWalk():
141
+ yield w
142
+ yield self
143
+
144
+ def allOf(self, *astTypes):
145
+ astTypes = set(astTypes)
146
+ for w in self.postorderWalk():
147
+ if w.astType in astTypes:
148
+ yield w
149
+
150
+
151
+ def expressionToAST(ex):
152
+ """Take an expression tree made out of expressions.ExpressionNode,
153
+ and convert to an AST tree.
154
+
155
+ This is necessary as ExpressionNode overrides many methods to act
156
+ like a number.
157
+ """
158
+ return ASTNode(ex.astType, ex.astKind, ex.value,
159
+ [expressionToAST(c) for c in ex.children])
160
+
161
+
162
+ def sigPerms(s):
163
+ """Generate all possible signatures derived by upcasting the given
164
+ signature.
165
+ """
166
+ codes = 'bilfdc'
167
+ if not s:
168
+ yield ''
169
+ elif s[0] in codes:
170
+ start = codes.index(s[0])
171
+ for x in codes[start:]:
172
+ for y in sigPerms(s[1:]):
173
+ yield x + y
174
+ elif s[0] == 's': # numbers shall not be cast to strings
175
+ for y in sigPerms(s[1:]):
176
+ yield 's' + y
177
+ else:
178
+ yield s
179
+
180
+
181
+ def typeCompileAst(ast):
182
+ """Assign appropriate types to each node in the AST.
183
+
184
+ Will convert opcodes and functions to appropriate upcast version,
185
+ and add "cast" ops if needed.
186
+ """
187
+ children = list(ast.children)
188
+ if ast.astType == 'op':
189
+ retsig = ast.typecode()
190
+ basesig = ''.join(x.typecode() for x in list(ast.children))
191
+ # Find some operation that will work on an acceptable casting of args.
192
+ for sig in sigPerms(basesig):
193
+ value = (ast.value + '_' + retsig + sig).encode('ascii')
194
+ if value in interpreter.opcodes:
195
+ break
196
+ else:
197
+ for sig in sigPerms(basesig):
198
+ funcname = (ast.value + '_' + retsig + sig).encode('ascii')
199
+ if funcname in interpreter.funccodes:
200
+ value = ('func_%sn' % (retsig + sig)).encode('ascii')
201
+ children += [ASTNode('raw', 'none',
202
+ interpreter.funccodes[funcname])]
203
+ break
204
+ else:
205
+ raise NotImplementedError(
206
+ "couldn't find matching opcode for '%s'"
207
+ % (ast.value + '_' + retsig + basesig))
208
+ # First just cast constants, then cast variables if necessary:
209
+ for i, (have, want) in enumerate(zip(basesig, sig)):
210
+ if have != want:
211
+ kind = typecode_to_kind[want]
212
+ if children[i].astType == 'constant':
213
+ children[i] = ASTNode('constant', kind, children[i].value)
214
+ else:
215
+ opname = "cast"
216
+ children[i] = ASTNode('op', kind, opname, [children[i]])
217
+ else:
218
+ value = ast.value
219
+ children = ast.children
220
+ return ASTNode(ast.astType, ast.astKind, value,
221
+ [typeCompileAst(c) for c in children])
222
+
223
+
224
+ class Register():
225
+ """Abstraction for a register in the VM.
226
+
227
+ Members:
228
+ node -- the AST node this corresponds to
229
+ temporary -- True if this isn't an input or output
230
+ immediate -- not a register, but an immediate value
231
+ n -- the physical register number.
232
+ None if no number assigned yet.
233
+ """
234
+
235
+ def __init__(self, astnode, temporary=False):
236
+ self.node = astnode
237
+ self.temporary = temporary
238
+ self.immediate = False
239
+ self.n = None
240
+
241
+ def __str__(self):
242
+ if self.temporary:
243
+ name = 'Temporary'
244
+ else:
245
+ name = 'Register'
246
+ return '%s(%s, %s, %s)' % (name, self.node.astType,
247
+ self.node.astKind, self.n,)
248
+
249
+ def __repr__(self):
250
+ return self.__str__()
251
+
252
+
253
+ class Immediate(Register):
254
+ """Representation of an immediate (integer) operand, instead of
255
+ a register.
256
+ """
257
+
258
+ def __init__(self, astnode):
259
+ Register.__init__(self, astnode)
260
+ self.immediate = True
261
+
262
+ def __str__(self):
263
+ return 'Immediate(%d)' % (self.node.value,)
264
+
265
+
266
+ _flow_pat = r'[\;\[\:]'
267
+ _dunder_pat = r'(^|[^\w])__[\w]+__($|[^\w])'
268
+ _attr_pat = r'\.\b(?!(real|imag|(\d*[eE]?[+-]?\d+)|\d*j)\b)'
269
+ _blacklist_re = re.compile(f'{_flow_pat}|{_dunder_pat}|{_attr_pat}')
270
+
271
+ def stringToExpression(s, types, context, sanitize: bool=True):
272
+ """Given a string, convert it to a tree of ExpressionNode's.
273
+ """
274
+ # sanitize the string for obvious attack vectors that NumExpr cannot
275
+ # parse into its homebrew AST. This is to protect the call to `eval` below.
276
+ # We forbid `;`, `:`. `[` and `__`, and attribute access via '.'.
277
+ # We cannot ban `.real` or `.imag` however...
278
+ # We also cannot ban `.\d*j`, where `\d*` is some digits (or none), e.g. 1.5j, 1.j
279
+ if sanitize:
280
+ no_whitespace = re.sub(r'\s+', '', s)
281
+ skip_quotes = re.sub(r'(\'[^\']*\')', '', no_whitespace)
282
+ if _blacklist_re.search(skip_quotes) is not None:
283
+ raise ValueError(f'Expression {s} has forbidden control characters.')
284
+
285
+ old_ctx = expressions._context.get_current_context()
286
+ try:
287
+ expressions._context.set_new_context(context)
288
+ # first compile to a code object to determine the names
289
+ if context.get('truediv', False):
290
+ flags = __future__.division.compiler_flag
291
+ else:
292
+ flags = 0
293
+ c = compile(s, '<expr>', 'eval', flags)
294
+ # make VariableNode's for the names
295
+ names = {}
296
+ for name in c.co_names:
297
+ if name == "None":
298
+ names[name] = None
299
+ elif name == "True":
300
+ names[name] = True
301
+ elif name == "False":
302
+ names[name] = False
303
+ else:
304
+ t = types.get(name, default_type)
305
+ names[name] = expressions.VariableNode(name, type_to_kind[t])
306
+ names.update(expressions.functions)
307
+
308
+ # now build the expression
309
+ ex = eval(c, names)
310
+
311
+ if expressions.isConstant(ex):
312
+ ex = expressions.ConstantNode(ex, expressions.getKind(ex))
313
+ elif not isinstance(ex, expressions.ExpressionNode):
314
+ raise TypeError("unsupported expression type: %s" % type(ex))
315
+ finally:
316
+ expressions._context.set_new_context(old_ctx)
317
+ return ex
318
+
319
+
320
+ def isReduction(ast):
321
+ prefixes = (b'sum_', b'prod_', b'min_', b'max_')
322
+ return any(ast.value.startswith(p) for p in prefixes)
323
+
324
+
325
+ def getInputOrder(ast, input_order=None):
326
+ """
327
+ Derive the input order of the variables in an expression.
328
+ """
329
+ variables = {}
330
+ for a in ast.allOf('variable'):
331
+ variables[a.value] = a
332
+ variable_names = set(variables.keys())
333
+
334
+ if input_order:
335
+ if variable_names != set(input_order):
336
+ raise ValueError(
337
+ "input names (%s) don't match those found in expression (%s)"
338
+ % (input_order, variable_names))
339
+
340
+ ordered_names = input_order
341
+ else:
342
+ ordered_names = list(variable_names)
343
+ ordered_names.sort()
344
+ ordered_variables = [variables[v] for v in ordered_names]
345
+ return ordered_variables
346
+
347
+
348
+ def convertConstantToKind(x, kind):
349
+ # Exception for 'float' types that will return the NumPy float32 type
350
+ if kind == 'float':
351
+ return numpy.float32(x)
352
+ elif isinstance(x,str):
353
+ return x.encode('ascii')
354
+ return kind_to_type[kind](x)
355
+
356
+
357
+ def getConstants(ast):
358
+ """
359
+ RAM: implemented magic method __lt__ for ASTNode to fix issues
360
+ #88 and #209. The following test code works now, as does the test suite.
361
+
362
+ import numexpr as ne
363
+ a = 1 + 3j; b = 5.0
364
+ ne.evaluate('a*2 + 15j - b')
365
+ """
366
+ constant_registers = set([node.reg for node in ast.allOf("constant")])
367
+ constants_order = sorted([r.node for r in constant_registers])
368
+ constants = [convertConstantToKind(a.value, a.astKind)
369
+ for a in constants_order]
370
+ return constants_order, constants
371
+
372
+
373
+ def sortNodesByOrder(nodes, order):
374
+ order_map = {}
375
+ for i, (_, v, _) in enumerate(order):
376
+ order_map[v] = i
377
+ dec_nodes = [(order_map[n.value], n) for n in nodes]
378
+ dec_nodes.sort()
379
+ return [a[1] for a in dec_nodes]
380
+
381
+
382
+ def assignLeafRegisters(inodes, registerMaker):
383
+ """
384
+ Assign new registers to each of the leaf nodes.
385
+ """
386
+ leafRegisters = {}
387
+ for node in inodes:
388
+ key = node.key()
389
+ if key in leafRegisters:
390
+ node.reg = leafRegisters[key]
391
+ else:
392
+ node.reg = leafRegisters[key] = registerMaker(node)
393
+
394
+
395
+ def assignBranchRegisters(inodes, registerMaker):
396
+ """
397
+ Assign temporary registers to each of the branch nodes.
398
+ """
399
+ for node in inodes:
400
+ node.reg = registerMaker(node, temporary=True)
401
+
402
+
403
+ def collapseDuplicateSubtrees(ast):
404
+ """
405
+ Common subexpression elimination.
406
+ """
407
+ seen = {}
408
+ aliases = []
409
+ for a in ast.allOf('op'):
410
+ if a in seen:
411
+ target = seen[a]
412
+ a.astType = 'alias'
413
+ a.value = target
414
+ a.children = ()
415
+ aliases.append(a)
416
+ else:
417
+ seen[a] = a
418
+ # Set values and registers so optimizeTemporariesAllocation
419
+ # doesn't get confused
420
+ for a in aliases:
421
+ while a.value.astType == 'alias':
422
+ a.value = a.value.value
423
+ return aliases
424
+
425
+
426
+ def optimizeTemporariesAllocation(ast):
427
+ """
428
+ Attempt to minimize the number of temporaries needed, by reusing old ones.
429
+ """
430
+ nodes = [n for n in ast.postorderWalk() if n.reg.temporary]
431
+ users_of = dict((n.reg, set()) for n in nodes)
432
+
433
+ node_regs = dict((n, set(c.reg for c in n.children if c.reg.temporary))
434
+ for n in nodes)
435
+ if nodes and nodes[-1] is not ast:
436
+ nodes_to_check = nodes + [ast]
437
+ else:
438
+ nodes_to_check = nodes
439
+ for n in nodes_to_check:
440
+ for c in n.children:
441
+ if c.reg.temporary:
442
+ users_of[c.reg].add(n)
443
+
444
+ unused = dict([(tc, set()) for tc in scalar_constant_kinds])
445
+ for n in nodes:
446
+ for c in n.children:
447
+ reg = c.reg
448
+ if reg.temporary:
449
+ users = users_of[reg]
450
+ users.discard(n)
451
+ if not users:
452
+ unused[reg.node.astKind].add(reg)
453
+ if unused[n.astKind]:
454
+ reg = unused[n.astKind].pop()
455
+ users_of[reg] = users_of[n.reg]
456
+ n.reg = reg
457
+
458
+
459
+ def setOrderedRegisterNumbers(order, start):
460
+ """
461
+ Given an order of nodes, assign register numbers.
462
+ """
463
+ for i, node in enumerate(order):
464
+ node.reg.n = start + i
465
+ return start + len(order)
466
+
467
+
468
+ def setRegisterNumbersForTemporaries(ast, start):
469
+ """
470
+ Assign register numbers for temporary registers, keeping track of
471
+ aliases and handling immediate operands.
472
+ """
473
+ seen = 0
474
+ signature = ''
475
+ aliases = []
476
+ for node in ast.postorderWalk():
477
+ if node.astType == 'alias':
478
+ aliases.append(node)
479
+ node = node.value
480
+ if node.reg.immediate:
481
+ node.reg.n = node.value
482
+ continue
483
+ reg = node.reg
484
+ if reg.n is None:
485
+ reg.n = start + seen
486
+ seen += 1
487
+ signature += reg.node.typecode()
488
+ for node in aliases:
489
+ node.reg = node.value.reg
490
+ return start + seen, signature
491
+
492
+
493
+ def convertASTtoThreeAddrForm(ast):
494
+ """
495
+ Convert an AST to a three address form.
496
+
497
+ Three address form is (op, reg1, reg2, reg3), where reg1 is the
498
+ destination of the result of the instruction.
499
+
500
+ I suppose this should be called three register form, but three
501
+ address form is found in compiler theory.
502
+ """
503
+ return [(node.value, node.reg) + tuple([c.reg for c in node.children])
504
+ for node in ast.allOf('op')]
505
+
506
+
507
+ def compileThreeAddrForm(program):
508
+ """
509
+ Given a three address form of the program, compile it a string that
510
+ the VM understands.
511
+ """
512
+
513
+ def nToChr(reg):
514
+ if reg is None:
515
+ return b'\xff'
516
+ elif reg.n < 0:
517
+ raise ValueError("negative value for register number %s" % reg.n)
518
+ else:
519
+ return bytes([reg.n])
520
+
521
+ def quadrupleToString(opcode, store, a1=None, a2=None):
522
+ cop = chr(interpreter.opcodes[opcode]).encode('ascii')
523
+ cs = nToChr(store)
524
+ ca1 = nToChr(a1)
525
+ ca2 = nToChr(a2)
526
+ return cop + cs + ca1 + ca2
527
+
528
+ def toString(args):
529
+ while len(args) < 4:
530
+ args += (None,)
531
+ opcode, store, a1, a2 = args[:4]
532
+ s = quadrupleToString(opcode, store, a1, a2)
533
+ l = [s]
534
+ args = args[4:]
535
+ while args:
536
+ s = quadrupleToString(b'noop', *args[:3])
537
+ l.append(s)
538
+ args = args[3:]
539
+ return b''.join(l)
540
+
541
+ prog_str = b''.join([toString(t) for t in program])
542
+ return prog_str
543
+
544
+
545
+ context_info = [
546
+ ('optimization', ('none', 'moderate', 'aggressive'), 'aggressive'),
547
+ ('truediv', (False, True, 'auto'), 'auto')
548
+ ]
549
+
550
+
551
+ def getContext(kwargs, _frame_depth=1):
552
+ d = kwargs.copy()
553
+ context = {}
554
+ for name, allowed, default in context_info:
555
+ value = d.pop(name, default)
556
+ if value in allowed:
557
+ context[name] = value
558
+ else:
559
+ raise ValueError("'%s' must be one of %s" % (name, allowed))
560
+
561
+ if d:
562
+ raise ValueError("Unknown keyword argument '%s'" % d.popitem()[0])
563
+ if context['truediv'] == 'auto':
564
+ caller_globals = sys._getframe(_frame_depth + 1).f_globals
565
+ context['truediv'] = caller_globals.get('division', None) == __future__.division
566
+
567
+ return context
568
+
569
+
570
+ def precompile(ex, signature=(), context={}, sanitize: bool=True):
571
+ """
572
+ Compile the expression to an intermediate form.
573
+ """
574
+ types = dict(signature)
575
+ input_order = [name for (name, type_) in signature]
576
+
577
+ if isinstance(ex, str):
578
+ ex = stringToExpression(ex, types, context, sanitize)
579
+
580
+ # the AST is like the expression, but the node objects don't have
581
+ # any odd interpretations
582
+
583
+ ast = expressionToAST(ex)
584
+
585
+ if ex.astType != 'op':
586
+ ast = ASTNode('op', value='copy', astKind=ex.astKind, children=(ast,))
587
+
588
+ ast = typeCompileAst(ast)
589
+
590
+ aliases = collapseDuplicateSubtrees(ast)
591
+
592
+ assignLeafRegisters(ast.allOf('raw'), Immediate)
593
+ assignLeafRegisters(ast.allOf('variable', 'constant'), Register)
594
+ assignBranchRegisters(ast.allOf('op'), Register)
595
+
596
+ # assign registers for aliases
597
+ for a in aliases:
598
+ a.reg = a.value.reg
599
+
600
+ input_order = getInputOrder(ast, input_order)
601
+ constants_order, constants = getConstants(ast)
602
+
603
+ if isReduction(ast):
604
+ ast.reg.temporary = False
605
+
606
+ optimizeTemporariesAllocation(ast)
607
+
608
+ ast.reg.temporary = False
609
+ r_output = 0
610
+ ast.reg.n = 0
611
+
612
+ r_inputs = r_output + 1
613
+ r_constants = setOrderedRegisterNumbers(input_order, r_inputs)
614
+ r_temps = setOrderedRegisterNumbers(constants_order, r_constants)
615
+ r_end, tempsig = setRegisterNumbersForTemporaries(ast, r_temps)
616
+
617
+ threeAddrProgram = convertASTtoThreeAddrForm(ast)
618
+ input_names = tuple([a.value for a in input_order])
619
+ signature = ''.join(type_to_typecode[types.get(x, default_type)]
620
+ for x in input_names)
621
+ return threeAddrProgram, signature, tempsig, constants, input_names
622
+
623
+
624
+ def NumExpr(ex, signature=(), sanitize: bool=True, **kwargs):
625
+ """
626
+ Compile an expression built using E.<variable> variables to a function.
627
+
628
+ ex can also be specified as a string "2*a+3*b".
629
+
630
+ The order of the input variables and their types can be specified using the
631
+ signature parameter, which is a list of (name, type) pairs.
632
+
633
+ Returns a `NumExpr` object containing the compiled function.
634
+ """
635
+
636
+ # In that case _frame_depth is wrong (it should be 2) but it doesn't matter
637
+ # since it will not be used (because truediv='auto' has already been
638
+ # translated to either True or False).
639
+ _frame_depth = 1
640
+ context = getContext(kwargs, _frame_depth=_frame_depth)
641
+ threeAddrProgram, inputsig, tempsig, constants, input_names = precompile(ex, signature, context, sanitize=sanitize)
642
+ program = compileThreeAddrForm(threeAddrProgram)
643
+ return interpreter.NumExpr(inputsig.encode('ascii'),
644
+ tempsig.encode('ascii'),
645
+ program, constants, input_names)
646
+
647
+
648
+ def disassemble(nex):
649
+ """
650
+ Given a NumExpr object, return a list which is the program disassembled.
651
+ """
652
+ rev_opcodes = {}
653
+ for op in interpreter.opcodes:
654
+ rev_opcodes[interpreter.opcodes[op]] = op
655
+ r_constants = 1 + len(nex.signature)
656
+ r_temps = r_constants + len(nex.constants)
657
+
658
+ def parseOp(op):
659
+ name, sig = [*op.rsplit(b'_', 1), ''][:2]
660
+ return name, sig
661
+
662
+ def getArg(pc, offset):
663
+ arg = nex.program[pc + (offset if offset < 4 else offset+1)]
664
+ _, sig = parseOp(rev_opcodes.get(nex.program[pc]))
665
+ try:
666
+ code = sig[offset - 1]
667
+ except IndexError:
668
+ return None
669
+
670
+ code = bytes([code])
671
+
672
+ if arg == 255:
673
+ return None
674
+ if code != b'n':
675
+ if arg == 0:
676
+ return b'r0'
677
+ elif arg < r_constants:
678
+ return ('r%d[%s]' % (arg, nex.input_names[arg - 1])).encode('ascii')
679
+ elif arg < r_temps:
680
+ return ('c%d[%s]' % (arg, nex.constants[arg - r_constants])).encode('ascii')
681
+ else:
682
+ return ('t%d' % (arg,)).encode('ascii')
683
+ else:
684
+ return arg
685
+
686
+ source = []
687
+ for pc in range(0, len(nex.program), 4):
688
+ op = rev_opcodes.get(nex.program[pc])
689
+ _, sig = parseOp(op)
690
+ parsed = [op]
691
+ for i in range(len(sig)):
692
+ parsed.append(getArg(pc, 1 + i))
693
+ while len(parsed) < 4:
694
+ parsed.append(None)
695
+ source.append(parsed)
696
+ return source
697
+
698
+
699
+ def getType(a):
700
+ kind = a.dtype.kind
701
+ if kind == 'b':
702
+ return bool
703
+ if kind in 'iu':
704
+ if a.dtype.itemsize > 4:
705
+ return long_ # ``long`` is for integers of more than 32 bits
706
+ if kind == 'u' and a.dtype.itemsize == 4:
707
+ return long_ # use ``long`` here as an ``int`` is not enough
708
+ return int_
709
+ if kind == 'f':
710
+ if a.dtype.itemsize > 4:
711
+ return double # ``double`` is for floats of more than 32 bits
712
+ return float
713
+ if kind == 'c':
714
+ return complex
715
+ if kind == 'S':
716
+ return bytes
717
+ if kind == 'U':
718
+ raise ValueError('NumExpr 2 does not support Unicode as a dtype.')
719
+ raise ValueError("unknown type %s" % a.dtype.name)
720
+
721
+
722
+ def getExprNames(text, context, sanitize: bool=True):
723
+ ex = stringToExpression(text, {}, context, sanitize)
724
+ ast = expressionToAST(ex)
725
+ input_order = getInputOrder(ast, None)
726
+ #try to figure out if vml operations are used by expression
727
+ if not use_vml:
728
+ ex_uses_vml = False
729
+ else:
730
+ for node in ast.postorderWalk():
731
+ if node.astType == 'op' and node.value in vml_functions:
732
+ ex_uses_vml = True
733
+ break
734
+ else:
735
+ ex_uses_vml = False
736
+
737
+ return [a.value for a in input_order], ex_uses_vml
738
+
739
+
740
+ def getArguments(names, local_dict=None, global_dict=None, _frame_depth: int=2):
741
+ """
742
+ Get the arguments based on the names.
743
+ """
744
+ call_frame = sys._getframe(_frame_depth)
745
+
746
+ clear_local_dict = False
747
+ if local_dict is None:
748
+ local_dict = call_frame.f_locals
749
+ clear_local_dict = True
750
+ try:
751
+ frame_globals = call_frame.f_globals
752
+ if global_dict is None:
753
+ global_dict = frame_globals
754
+
755
+ # If `call_frame` is the top frame of the interpreter we can't clear its
756
+ # `local_dict`, because it is actually the `global_dict`.
757
+ clear_local_dict = clear_local_dict and not frame_globals is local_dict
758
+
759
+ arguments = []
760
+ for name in names:
761
+ try:
762
+ a = local_dict[name]
763
+ except KeyError:
764
+ a = global_dict[name]
765
+ arguments.append(numpy.asarray(a))
766
+ finally:
767
+ # If we generated local_dict via an explicit reference to f_locals,
768
+ # clear the dict to prevent creating extra ref counts in the caller's scope
769
+ # See https://github.com/pydata/numexpr/issues/310
770
+ if clear_local_dict and hasattr(local_dict, 'clear'):
771
+ local_dict.clear()
772
+
773
+ return arguments
774
+
775
+
776
+ # Dictionaries for caching variable names and compiled expressions
777
+ _names_cache = CacheDict(256)
778
+ _numexpr_cache = CacheDict(256)
779
+ _numexpr_last = ContextDict()
780
+ evaluate_lock = threading.Lock()
781
+
782
+ def validate(ex: str,
783
+ local_dict: Optional[Dict] = None,
784
+ global_dict: Optional[Dict] = None,
785
+ out: numpy.ndarray = None,
786
+ order: str = 'K',
787
+ casting: str = 'safe',
788
+ _frame_depth: int = 2,
789
+ sanitize: Optional[bool] = None,
790
+ **kwargs) -> Optional[Exception]:
791
+ r"""
792
+ Validate a NumExpr expression with the given `local_dict` or `locals()`.
793
+ Returns `None` on success and the Exception object if one occurs. Note that
794
+ you can proceed directly to call `re_evaluate()` if you use `validate()`
795
+ to sanitize your expressions and variables in advance.
796
+
797
+ Parameters
798
+ ----------
799
+ ex: str
800
+ a string forming an expression, like "2*a+3*b". The values for "a"
801
+ and "b" will by default be taken from the calling function's frame
802
+ (through use of sys._getframe()). Alternatively, they can be specified
803
+ using the 'local_dict' or 'global_dict' arguments.
804
+
805
+ local_dict: dictionary, optional
806
+ A dictionary that replaces the local operands in current frame.
807
+
808
+ global_dict: dictionary, optional
809
+ A dictionary that replaces the global operands in current frame.
810
+
811
+ out: NumPy array, optional
812
+ An existing array where the outcome is going to be stored. Care is
813
+ required so that this array has the same shape and type than the
814
+ actual outcome of the computation. Useful for avoiding unnecessary
815
+ new array allocations.
816
+
817
+ order: {'C', 'F', 'A', or 'K'}, optional
818
+ Controls the iteration order for operands. 'C' means C order, 'F'
819
+ means Fortran order, 'A' means 'F' order if all the arrays are
820
+ Fortran contiguous, 'C' order otherwise, and 'K' means as close to
821
+ the order the array elements appear in memory as possible. For
822
+ efficient computations, typically 'K'eep order (the default) is
823
+ desired.
824
+
825
+ casting: {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional
826
+ Controls what kind of data casting may occur when making a copy or
827
+ buffering. Setting this to 'unsafe' is not recommended, as it can
828
+ adversely affect accumulations.
829
+
830
+ * 'no' means the data types should not be cast at all.
831
+ * 'equiv' means only byte-order changes are allowed.
832
+ * 'safe' means only casts which can preserve values are allowed.
833
+ * 'same_kind' means only safe casts or casts within a kind,
834
+ like float64 to float32, are allowed.
835
+ * 'unsafe' means any data conversions may be done.
836
+
837
+ sanitize: Optional[bool]
838
+ Both `validate` and by extension `evaluate` call `eval(ex)`, which is
839
+ potentially dangerous on unsanitized inputs. As such, NumExpr by default
840
+ performs simple sanitization, banning the character ':;[', the
841
+ dunder '__[\w+]__', and attribute access to all but '.real' and '.imag'.
842
+
843
+ Using `None` defaults to `True` unless the environment variable
844
+ `NUMEXPR_SANITIZE=0` is set, in which case the default is `False`.
845
+ Nominally this can be set via `os.environ` before `import numexpr`.
846
+
847
+ _frame_depth: int
848
+ The calling frame depth. Unless you are a NumExpr developer you should
849
+ not set this value.
850
+
851
+ Note
852
+ ----
853
+
854
+ """
855
+ global _numexpr_last
856
+
857
+ try:
858
+
859
+ if not isinstance(ex, str):
860
+ raise ValueError("must specify expression as a string")
861
+
862
+ if sanitize is None:
863
+ if 'NUMEXPR_SANITIZE' in os.environ:
864
+ sanitize = bool(int(os.environ['NUMEXPR_SANITIZE']))
865
+ else:
866
+ sanitize = True
867
+
868
+ # Get the names for this expression
869
+ context = getContext(kwargs)
870
+ expr_key = (ex, tuple(sorted(context.items())))
871
+ if expr_key not in _names_cache:
872
+ _names_cache[expr_key] = getExprNames(ex, context, sanitize=sanitize)
873
+ names, ex_uses_vml = _names_cache[expr_key]
874
+ arguments = getArguments(names, local_dict, global_dict, _frame_depth=_frame_depth)
875
+
876
+ # Create a signature
877
+ signature = [(name, getType(arg)) for (name, arg) in
878
+ zip(names, arguments)]
879
+
880
+ # Look up numexpr if possible.
881
+ numexpr_key = expr_key + (tuple(signature),)
882
+ try:
883
+ compiled_ex = _numexpr_cache[numexpr_key]
884
+ except KeyError:
885
+ compiled_ex = _numexpr_cache[numexpr_key] = NumExpr(ex, signature, sanitize=sanitize, **context)
886
+ kwargs = {'out': out, 'order': order, 'casting': casting,
887
+ 'ex_uses_vml': ex_uses_vml}
888
+ _numexpr_last.set(ex=compiled_ex, argnames=names, kwargs=kwargs)
889
+ except Exception as e:
890
+ return e
891
+ return None
892
+
893
+ def evaluate(ex: str,
894
+ local_dict: Optional[Dict] = None,
895
+ global_dict: Optional[Dict] = None,
896
+ out: numpy.ndarray = None,
897
+ order: str = 'K',
898
+ casting: str = 'safe',
899
+ sanitize: Optional[bool] = None,
900
+ _frame_depth: int = 3,
901
+ **kwargs) -> numpy.ndarray:
902
+ r"""
903
+ Evaluate a simple array expression element-wise using the virtual machine.
904
+
905
+ Parameters
906
+ ----------
907
+ ex: str
908
+ a string forming an expression, like "2*a+3*b". The values for "a"
909
+ and "b" will by default be taken from the calling function's frame
910
+ (through use of sys._getframe()). Alternatively, they can be specified
911
+ using the 'local_dict' or 'global_dict' arguments.
912
+
913
+ local_dict: dictionary, optional
914
+ A dictionary that replaces the local operands in current frame.
915
+
916
+ global_dict: dictionary, optional
917
+ A dictionary that replaces the global operands in current frame.
918
+
919
+ out: NumPy array, optional
920
+ An existing array where the outcome is going to be stored. Care is
921
+ required so that this array has the same shape and type than the
922
+ actual outcome of the computation. Useful for avoiding unnecessary
923
+ new array allocations.
924
+
925
+ order: {'C', 'F', 'A', or 'K'}, optional
926
+ Controls the iteration order for operands. 'C' means C order, 'F'
927
+ means Fortran order, 'A' means 'F' order if all the arrays are
928
+ Fortran contiguous, 'C' order otherwise, and 'K' means as close to
929
+ the order the array elements appear in memory as possible. For
930
+ efficient computations, typically 'K'eep order (the default) is
931
+ desired.
932
+
933
+ casting: {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional
934
+ Controls what kind of data casting may occur when making a copy or
935
+ buffering. Setting this to 'unsafe' is not recommended, as it can
936
+ adversely affect accumulations.
937
+
938
+ * 'no' means the data types should not be cast at all.
939
+ * 'equiv' means only byte-order changes are allowed.
940
+ * 'safe' means only casts which can preserve values are allowed.
941
+ * 'same_kind' means only safe casts or casts within a kind,
942
+ like float64 to float32, are allowed.
943
+ * 'unsafe' means any data conversions may be done.
944
+
945
+ sanitize: bool
946
+ Both `validate` and by extension `evaluate` call `eval(ex)`, which is
947
+ potentially dangerous on unsanitized inputs. As such, NumExpr by default
948
+ performs simple sanitization, banning the character ':;[', the
949
+ dunder '__[\w+]__', and attribute access to all but '.real' and '.imag'.
950
+
951
+ Using `None` defaults to `True` unless the environment variable
952
+ `NUMEXPR_SANITIZE=0` is set, in which case the default is `False`.
953
+ Nominally this can be set via `os.environ` before `import numexpr`.
954
+
955
+ _frame_depth: int
956
+ The calling frame depth. Unless you are a NumExpr developer you should
957
+ not set this value.
958
+
959
+ Note
960
+ ----
961
+ Both `validate` and by extension `evaluate` call `eval(ex)`, which is
962
+ potentially dangerous on unsanitized inputs. As such, NumExpr does some
963
+ sanitization, banning the character ':;[', the dunder '__', and attribute
964
+ access to all but '.r' for real and '.i' for imag access to complex numbers.
965
+ """
966
+ # We could avoid code duplication if we called validate and then re_evaluate
967
+ # here, but they we have difficulties with the `sys.getframe(2)` call in
968
+ # `getArguments`
969
+ e = validate(ex, local_dict=local_dict, global_dict=global_dict,
970
+ out=out, order=order, casting=casting,
971
+ _frame_depth=_frame_depth, sanitize=sanitize, **kwargs)
972
+ if e is None:
973
+ return re_evaluate(local_dict=local_dict, global_dict=global_dict, _frame_depth=_frame_depth)
974
+ else:
975
+ raise e
976
+
977
+ def re_evaluate(local_dict: Optional[Dict] = None,
978
+ global_dict: Optional[Dict] = None,
979
+ _frame_depth: int=2) -> numpy.ndarray:
980
+ """
981
+ Re-evaluate the previous executed array expression without any check.
982
+
983
+ This is meant for accelerating loops that are re-evaluating the same
984
+ expression repeatedly without changing anything else than the operands.
985
+ If unsure, use evaluate() which is safer.
986
+
987
+ Parameters
988
+ ----------
989
+ local_dict: dictionary, optional
990
+ A dictionary that replaces the local operands in current frame.
991
+ _frame_depth: int
992
+ The calling frame depth. Unless you are a NumExpr developer you should
993
+ not set this value.
994
+ """
995
+ global _numexpr_last
996
+
997
+ try:
998
+ compiled_ex = _numexpr_last['ex']
999
+ except KeyError:
1000
+ raise RuntimeError("A previous evaluate() execution was not found, please call `validate` or `evaluate` once before `re_evaluate`")
1001
+ argnames = _numexpr_last['argnames']
1002
+ args = getArguments(argnames, local_dict, global_dict, _frame_depth=_frame_depth)
1003
+ kwargs = _numexpr_last['kwargs']
1004
+ with evaluate_lock:
1005
+ return compiled_ex(*args, **kwargs)