Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/numexpr-2.8.7.dev1-py3.8-linux-x86_64.egg/numexpr/expressions.py: 87%

303 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-19 06:05 +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#################################################################### 

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)