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

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(object): 

39 def __init__(self): 

40 object.__init__(self) 

41 

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) 

50 

51 

52E = Expression() 

53 

54 

55class Context(threading.local): 

56 

57 def get(self, value, default): 

58 return self.__dict__.get(value, default) 

59 

60 def get_current_context(self): 

61 return self.__dict__ 

62 

63 def set_new_context(self, dict_): 

64 self.__dict__.update(dict_) 

65 

66# This will be called each time the local object is used in a separate thread 

67_context = Context() 

68 

69 

70def get_optimization(): 

71 return _context.get('optimization', 'none') 

72 

73 

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) 

84 

85 func.__name__ = f.__name__ 

86 func.__doc__ = f.__doc__ 

87 func.__dict__.update(f.__dict__) 

88 return func 

89 

90 

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 

97 

98 

99def isConstant(ex): 

100 "Returns True if ex is a constant scalar of an allowed type." 

101 return isinstance(ex, scalar_constant_types) 

102 

103 

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] 

115 

116 

117max_int32 = 2147483647 

118min_int32 = -max_int32 - 1 

119 

120 

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 

159 

160 

161def getKind(x): 

162 converter = bestConstantType(x) 

163 return type_to_kind[converter] 

164 

165 

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) 

170 

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) 

179 

180 return operation 

181 

182 

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) 

202 

203 return function 

204 

205 

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

213 

214 

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) 

226 

227 

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 

237 

238 

239@ophelper 

240def contains_func(a, b): 

241 return FuncNode('contains', [a, b], kind='bool') 

242 

243 

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

252 

253 

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) 

265 

266 

267@ophelper 

268def rtruediv_op(a, b): 

269 return truediv_op(b, a) 

270 

271 

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

277 

278 raise ValueError( 

279 'Integers to negative integer powers are not allowed.') 

280 

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 

292 

293 def multiply(x, y): 

294 if x is None: return y 

295 return OpNode('mul', [x, y]) 

296 

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

331 

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'), 

338 

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'), 

345 

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'), 

352 

353 'fmod': func(numpy.fmod, 'float'), 

354 'arctan2': func(numpy.arctan2, 'float'), 

355 

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'), 

361 

362 'abs': func(numpy.absolute, 'float'), 

363 'ceil': func(numpy.ceil, 'float', 'double'), 

364 'floor': func(numpy.floor, 'float', 'double'), 

365 

366 'where': where_func, 

367 

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'), 

372 

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} 

379 

380 

381class ExpressionNode(object): 

382 """ 

383 An object that represents a generic number object. 

384 

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' 

389 

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) 

400 

401 def get_real(self): 

402 if self.astType == 'constant': 

403 return ConstantNode(complex(self.value).real) 

404 return OpNode('real', (self,), 'double') 

405 

406 real = property(get_real) 

407 

408 def get_imag(self): 

409 if self.astType == 'constant': 

410 return ConstantNode(complex(self.value).imag) 

411 return OpNode('imag', (self,), 'double') 

412 

413 imag = property(get_imag) 

414 

415 def __str__(self): 

416 return '%s(%s, %s, %s)' % (self.__class__.__name__, self.value, 

417 self.astKind, self.children) 

418 

419 def __repr__(self): 

420 return self.__str__() 

421 

422 def __neg__(self): 

423 return OpNode('neg', (self,)) 

424 

425 def __invert__(self): 

426 return OpNode('invert', (self,)) 

427 

428 def __pos__(self): 

429 return self 

430 

431 # The next check is commented out. See #24 for more info. 

432 

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

438 

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) 

449 

450 __lshift__ = binop('lshift') 

451 __rlshift__ = binop('lshift', reversed=True) 

452 __rshift__ = binop('rshift') 

453 __rrshift__ = binop('rshift', reversed=True) 

454 

455 # boolean operations 

456 

457 __and__ = binop('and', kind='bool') 

458 __or__ = binop('or', kind='bool') 

459 

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

466 

467 

468class LeafNode(ExpressionNode): 

469 leafNode = True 

470 

471 

472class VariableNode(LeafNode): 

473 astType = 'variable' 

474 

475 def __init__(self, value=None, kind=None, children=None): 

476 LeafNode.__init__(self, value=value, kind=kind) 

477 

478 

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' 

488 

489 def __init__(self, value): 

490 self.value = value 

491 self.children = () 

492 

493 def __str__(self): 

494 return 'RawNode(%s)' % (self.value,) 

495 

496 __repr__ = __str__ 

497 

498 

499class ConstantNode(LeafNode): 

500 astType = 'constant' 

501 

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) 

508 

509 def __neg__(self): 

510 return ConstantNode(-self.value) 

511 

512 def __invert__(self): 

513 return ConstantNode(~self.value) 

514 

515 

516class OpNode(ExpressionNode): 

517 astType = 'op' 

518 

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) 

523 

524 

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)