Coverage for /usr/lib/python3/dist-packages/sympy/utilities/misc.py: 20%

209 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Miscellaneous stuff that does not really fit anywhere else.""" 

2 

3from __future__ import annotations 

4 

5import operator 

6import sys 

7import os 

8import re as _re 

9import struct 

10from textwrap import fill, dedent 

11 

12 

13class Undecidable(ValueError): 

14 # an error to be raised when a decision cannot be made definitively 

15 # where a definitive answer is needed 

16 pass 

17 

18 

19def filldedent(s, w=70, **kwargs): 

20 """ 

21 Strips leading and trailing empty lines from a copy of ``s``, then dedents, 

22 fills and returns it. 

23 

24 Empty line stripping serves to deal with docstrings like this one that 

25 start with a newline after the initial triple quote, inserting an empty 

26 line at the beginning of the string. 

27 

28 Additional keyword arguments will be passed to ``textwrap.fill()``. 

29 

30 See Also 

31 ======== 

32 strlines, rawlines 

33 

34 """ 

35 return '\n' + fill(dedent(str(s)).strip('\n'), width=w, **kwargs) 

36 

37 

38def strlines(s, c=64, short=False): 

39 """Return a cut-and-pastable string that, when printed, is 

40 equivalent to the input. The lines will be surrounded by 

41 parentheses and no line will be longer than c (default 64) 

42 characters. If the line contains newlines characters, the 

43 `rawlines` result will be returned. If ``short`` is True 

44 (default is False) then if there is one line it will be 

45 returned without bounding parentheses. 

46 

47 Examples 

48 ======== 

49 

50 >>> from sympy.utilities.misc import strlines 

51 >>> q = 'this is a long string that should be broken into shorter lines' 

52 >>> print(strlines(q, 40)) 

53 ( 

54 'this is a long string that should be b' 

55 'roken into shorter lines' 

56 ) 

57 >>> q == ( 

58 ... 'this is a long string that should be b' 

59 ... 'roken into shorter lines' 

60 ... ) 

61 True 

62 

63 See Also 

64 ======== 

65 filldedent, rawlines 

66 """ 

67 if not isinstance(s, str): 

68 raise ValueError('expecting string input') 

69 if '\n' in s: 

70 return rawlines(s) 

71 q = '"' if repr(s).startswith('"') else "'" 

72 q = (q,)*2 

73 if '\\' in s: # use r-string 

74 m = '(\nr%s%%s%s\n)' % q 

75 j = '%s\nr%s' % q 

76 c -= 3 

77 else: 

78 m = '(\n%s%%s%s\n)' % q 

79 j = '%s\n%s' % q 

80 c -= 2 

81 out = [] 

82 while s: 

83 out.append(s[:c]) 

84 s=s[c:] 

85 if short and len(out) == 1: 

86 return (m % out[0]).splitlines()[1] # strip bounding (\n...\n) 

87 return m % j.join(out) 

88 

89 

90def rawlines(s): 

91 """Return a cut-and-pastable string that, when printed, is equivalent 

92 to the input. Use this when there is more than one line in the 

93 string. The string returned is formatted so it can be indented 

94 nicely within tests; in some cases it is wrapped in the dedent 

95 function which has to be imported from textwrap. 

96 

97 Examples 

98 ======== 

99 

100 Note: because there are characters in the examples below that need 

101 to be escaped because they are themselves within a triple quoted 

102 docstring, expressions below look more complicated than they would 

103 be if they were printed in an interpreter window. 

104 

105 >>> from sympy.utilities.misc import rawlines 

106 >>> from sympy import TableForm 

107 >>> s = str(TableForm([[1, 10]], headings=(None, ['a', 'bee']))) 

108 >>> print(rawlines(s)) 

109 ( 

110 'a bee\\n' 

111 '-----\\n' 

112 '1 10 ' 

113 ) 

114 >>> print(rawlines('''this 

115 ... that''')) 

116 dedent('''\\ 

117 this 

118 that''') 

119 

120 >>> print(rawlines('''this 

121 ... that 

122 ... ''')) 

123 dedent('''\\ 

124 this 

125 that 

126 ''') 

127 

128 >>> s = \"\"\"this 

129 ... is a triple ''' 

130 ... \"\"\" 

131 >>> print(rawlines(s)) 

132 dedent(\"\"\"\\ 

133 this 

134 is a triple ''' 

135 \"\"\") 

136 

137 >>> print(rawlines('''this 

138 ... that 

139 ... ''')) 

140 ( 

141 'this\\n' 

142 'that\\n' 

143 ' ' 

144 ) 

145 

146 See Also 

147 ======== 

148 filldedent, strlines 

149 """ 

150 lines = s.split('\n') 

151 if len(lines) == 1: 

152 return repr(lines[0]) 

153 triple = ["'''" in s, '"""' in s] 

154 if any(li.endswith(' ') for li in lines) or '\\' in s or all(triple): 

155 rv = [] 

156 # add on the newlines 

157 trailing = s.endswith('\n') 

158 last = len(lines) - 1 

159 for i, li in enumerate(lines): 

160 if i != last or trailing: 

161 rv.append(repr(li + '\n')) 

162 else: 

163 rv.append(repr(li)) 

164 return '(\n %s\n)' % '\n '.join(rv) 

165 else: 

166 rv = '\n '.join(lines) 

167 if triple[0]: 

168 return 'dedent("""\\\n %s""")' % rv 

169 else: 

170 return "dedent('''\\\n %s''')" % rv 

171 

172ARCH = str(struct.calcsize('P') * 8) + "-bit" 

173 

174 

175# XXX: PyPy does not support hash randomization 

176HASH_RANDOMIZATION = getattr(sys.flags, 'hash_randomization', False) 

177 

178_debug_tmp: list[str] = [] 

179_debug_iter = 0 

180 

181def debug_decorator(func): 

182 """If SYMPY_DEBUG is True, it will print a nice execution tree with 

183 arguments and results of all decorated functions, else do nothing. 

184 """ 

185 from sympy import SYMPY_DEBUG 

186 

187 if not SYMPY_DEBUG: 

188 return func 

189 

190 def maketree(f, *args, **kw): 

191 global _debug_tmp 

192 global _debug_iter 

193 oldtmp = _debug_tmp 

194 _debug_tmp = [] 

195 _debug_iter += 1 

196 

197 def tree(subtrees): 

198 def indent(s, variant=1): 

199 x = s.split("\n") 

200 r = "+-%s\n" % x[0] 

201 for a in x[1:]: 

202 if a == "": 

203 continue 

204 if variant == 1: 

205 r += "| %s\n" % a 

206 else: 

207 r += " %s\n" % a 

208 return r 

209 if len(subtrees) == 0: 

210 return "" 

211 f = [] 

212 for a in subtrees[:-1]: 

213 f.append(indent(a)) 

214 f.append(indent(subtrees[-1], 2)) 

215 return ''.join(f) 

216 

217 # If there is a bug and the algorithm enters an infinite loop, enable the 

218 # following lines. It will print the names and parameters of all major functions 

219 # that are called, *before* they are called 

220 #from functools import reduce 

221 #print("%s%s %s%s" % (_debug_iter, reduce(lambda x, y: x + y, \ 

222 # map(lambda x: '-', range(1, 2 + _debug_iter))), f.__name__, args)) 

223 

224 r = f(*args, **kw) 

225 

226 _debug_iter -= 1 

227 s = "%s%s = %s\n" % (f.__name__, args, r) 

228 if _debug_tmp != []: 

229 s += tree(_debug_tmp) 

230 _debug_tmp = oldtmp 

231 _debug_tmp.append(s) 

232 if _debug_iter == 0: 

233 print(_debug_tmp[0]) 

234 _debug_tmp = [] 

235 return r 

236 

237 def decorated(*args, **kwargs): 

238 return maketree(func, *args, **kwargs) 

239 

240 return decorated 

241 

242 

243def debug(*args): 

244 """ 

245 Print ``*args`` if SYMPY_DEBUG is True, else do nothing. 

246 """ 

247 from sympy import SYMPY_DEBUG 

248 if SYMPY_DEBUG: 

249 print(*args, file=sys.stderr) 

250 

251 

252def debugf(string, args): 

253 """ 

254 Print ``string%args`` if SYMPY_DEBUG is True, else do nothing. This is 

255 intended for debug messages using formatted strings. 

256 """ 

257 from sympy import SYMPY_DEBUG 

258 if SYMPY_DEBUG: 

259 print(string%args, file=sys.stderr) 

260 

261 

262def find_executable(executable, path=None): 

263 """Try to find 'executable' in the directories listed in 'path' (a 

264 string listing directories separated by 'os.pathsep'; defaults to 

265 os.environ['PATH']). Returns the complete filename or None if not 

266 found 

267 """ 

268 from .exceptions import sympy_deprecation_warning 

269 sympy_deprecation_warning( 

270 """ 

271 sympy.utilities.misc.find_executable() is deprecated. Use the standard 

272 library shutil.which() function instead. 

273 """, 

274 deprecated_since_version="1.7", 

275 active_deprecations_target="deprecated-find-executable", 

276 ) 

277 if path is None: 

278 path = os.environ['PATH'] 

279 paths = path.split(os.pathsep) 

280 extlist = [''] 

281 if os.name == 'os2': 

282 (base, ext) = os.path.splitext(executable) 

283 # executable files on OS/2 can have an arbitrary extension, but 

284 # .exe is automatically appended if no dot is present in the name 

285 if not ext: 

286 executable = executable + ".exe" 

287 elif sys.platform == 'win32': 

288 pathext = os.environ['PATHEXT'].lower().split(os.pathsep) 

289 (base, ext) = os.path.splitext(executable) 

290 if ext.lower() not in pathext: 

291 extlist = pathext 

292 for ext in extlist: 

293 execname = executable + ext 

294 if os.path.isfile(execname): 

295 return execname 

296 else: 

297 for p in paths: 

298 f = os.path.join(p, execname) 

299 if os.path.isfile(f): 

300 return f 

301 

302 return None 

303 

304 

305def func_name(x, short=False): 

306 """Return function name of `x` (if defined) else the `type(x)`. 

307 If short is True and there is a shorter alias for the result, 

308 return the alias. 

309 

310 Examples 

311 ======== 

312 

313 >>> from sympy.utilities.misc import func_name 

314 >>> from sympy import Matrix 

315 >>> from sympy.abc import x 

316 >>> func_name(Matrix.eye(3)) 

317 'MutableDenseMatrix' 

318 >>> func_name(x < 1) 

319 'StrictLessThan' 

320 >>> func_name(x < 1, short=True) 

321 'Lt' 

322 """ 

323 alias = { 

324 'GreaterThan': 'Ge', 

325 'StrictGreaterThan': 'Gt', 

326 'LessThan': 'Le', 

327 'StrictLessThan': 'Lt', 

328 'Equality': 'Eq', 

329 'Unequality': 'Ne', 

330 } 

331 typ = type(x) 

332 if str(typ).startswith("<type '"): 

333 typ = str(typ).split("'")[1].split("'")[0] 

334 elif str(typ).startswith("<class '"): 

335 typ = str(typ).split("'")[1].split("'")[0] 

336 rv = getattr(getattr(x, 'func', x), '__name__', typ) 

337 if '.' in rv: 

338 rv = rv.split('.')[-1] 

339 if short: 

340 rv = alias.get(rv, rv) 

341 return rv 

342 

343 

344def _replace(reps): 

345 """Return a function that can make the replacements, given in 

346 ``reps``, on a string. The replacements should be given as mapping. 

347 

348 Examples 

349 ======== 

350 

351 >>> from sympy.utilities.misc import _replace 

352 >>> f = _replace(dict(foo='bar', d='t')) 

353 >>> f('food') 

354 'bart' 

355 >>> f = _replace({}) 

356 >>> f('food') 

357 'food' 

358 """ 

359 if not reps: 

360 return lambda x: x 

361 D = lambda match: reps[match.group(0)] 

362 pattern = _re.compile("|".join( 

363 [_re.escape(k) for k, v in reps.items()]), _re.M) 

364 return lambda string: pattern.sub(D, string) 

365 

366 

367def replace(string, *reps): 

368 """Return ``string`` with all keys in ``reps`` replaced with 

369 their corresponding values, longer strings first, irrespective 

370 of the order they are given. ``reps`` may be passed as tuples 

371 or a single mapping. 

372 

373 Examples 

374 ======== 

375 

376 >>> from sympy.utilities.misc import replace 

377 >>> replace('foo', {'oo': 'ar', 'f': 'b'}) 

378 'bar' 

379 >>> replace("spamham sha", ("spam", "eggs"), ("sha","md5")) 

380 'eggsham md5' 

381 

382 There is no guarantee that a unique answer will be 

383 obtained if keys in a mapping overlap (i.e. are the same 

384 length and have some identical sequence at the 

385 beginning/end): 

386 

387 >>> reps = [ 

388 ... ('ab', 'x'), 

389 ... ('bc', 'y')] 

390 >>> replace('abc', *reps) in ('xc', 'ay') 

391 True 

392 

393 References 

394 ========== 

395 

396 .. [1] https://stackoverflow.com/questions/6116978/how-to-replace-multiple-substrings-of-a-string 

397 """ 

398 if len(reps) == 1: 

399 kv = reps[0] 

400 if isinstance(kv, dict): 

401 reps = kv 

402 else: 

403 return string.replace(*kv) 

404 else: 

405 reps = dict(reps) 

406 return _replace(reps)(string) 

407 

408 

409def translate(s, a, b=None, c=None): 

410 """Return ``s`` where characters have been replaced or deleted. 

411 

412 SYNTAX 

413 ====== 

414 

415 translate(s, None, deletechars): 

416 all characters in ``deletechars`` are deleted 

417 translate(s, map [,deletechars]): 

418 all characters in ``deletechars`` (if provided) are deleted 

419 then the replacements defined by map are made; if the keys 

420 of map are strings then the longer ones are handled first. 

421 Multicharacter deletions should have a value of ''. 

422 translate(s, oldchars, newchars, deletechars) 

423 all characters in ``deletechars`` are deleted 

424 then each character in ``oldchars`` is replaced with the 

425 corresponding character in ``newchars`` 

426 

427 Examples 

428 ======== 

429 

430 >>> from sympy.utilities.misc import translate 

431 >>> abc = 'abc' 

432 >>> translate(abc, None, 'a') 

433 'bc' 

434 >>> translate(abc, {'a': 'x'}, 'c') 

435 'xb' 

436 >>> translate(abc, {'abc': 'x', 'a': 'y'}) 

437 'x' 

438 

439 >>> translate('abcd', 'ac', 'AC', 'd') 

440 'AbC' 

441 

442 There is no guarantee that a unique answer will be 

443 obtained if keys in a mapping overlap are the same 

444 length and have some identical sequences at the 

445 beginning/end: 

446 

447 >>> translate(abc, {'ab': 'x', 'bc': 'y'}) in ('xc', 'ay') 

448 True 

449 """ 

450 

451 mr = {} 

452 if a is None: 

453 if c is not None: 

454 raise ValueError('c should be None when a=None is passed, instead got %s' % c) 

455 if b is None: 

456 return s 

457 c = b 

458 a = b = '' 

459 else: 

460 if isinstance(a, dict): 

461 short = {} 

462 for k in list(a.keys()): 

463 if len(k) == 1 and len(a[k]) == 1: 

464 short[k] = a.pop(k) 

465 mr = a 

466 c = b 

467 if short: 

468 a, b = [''.join(i) for i in list(zip(*short.items()))] 

469 else: 

470 a = b = '' 

471 elif len(a) != len(b): 

472 raise ValueError('oldchars and newchars have different lengths') 

473 

474 if c: 

475 val = str.maketrans('', '', c) 

476 s = s.translate(val) 

477 s = replace(s, mr) 

478 n = str.maketrans(a, b) 

479 return s.translate(n) 

480 

481 

482def ordinal(num): 

483 """Return ordinal number string of num, e.g. 1 becomes 1st. 

484 """ 

485 # modified from https://codereview.stackexchange.com/questions/41298/producing-ordinal-numbers 

486 n = as_int(num) 

487 k = abs(n) % 100 

488 if 11 <= k <= 13: 

489 suffix = 'th' 

490 elif k % 10 == 1: 

491 suffix = 'st' 

492 elif k % 10 == 2: 

493 suffix = 'nd' 

494 elif k % 10 == 3: 

495 suffix = 'rd' 

496 else: 

497 suffix = 'th' 

498 return str(n) + suffix 

499 

500 

501def as_int(n, strict=True): 

502 """ 

503 Convert the argument to a builtin integer. 

504 

505 The return value is guaranteed to be equal to the input. ValueError is 

506 raised if the input has a non-integral value. When ``strict`` is True, this 

507 uses `__index__ <https://docs.python.org/3/reference/datamodel.html#object.__index__>`_ 

508 and when it is False it uses ``int``. 

509 

510 

511 Examples 

512 ======== 

513 

514 >>> from sympy.utilities.misc import as_int 

515 >>> from sympy import sqrt, S 

516 

517 The function is primarily concerned with sanitizing input for 

518 functions that need to work with builtin integers, so anything that 

519 is unambiguously an integer should be returned as an int: 

520 

521 >>> as_int(S(3)) 

522 3 

523 

524 Floats, being of limited precision, are not assumed to be exact and 

525 will raise an error unless the ``strict`` flag is False. This 

526 precision issue becomes apparent for large floating point numbers: 

527 

528 >>> big = 1e23 

529 >>> type(big) is float 

530 True 

531 >>> big == int(big) 

532 True 

533 >>> as_int(big) 

534 Traceback (most recent call last): 

535 ... 

536 ValueError: ... is not an integer 

537 >>> as_int(big, strict=False) 

538 99999999999999991611392 

539 

540 Input that might be a complex representation of an integer value is 

541 also rejected by default: 

542 

543 >>> one = sqrt(3 + 2*sqrt(2)) - sqrt(2) 

544 >>> int(one) == 1 

545 True 

546 >>> as_int(one) 

547 Traceback (most recent call last): 

548 ... 

549 ValueError: ... is not an integer 

550 """ 

551 if strict: 

552 try: 

553 if isinstance(n, bool): 

554 raise TypeError 

555 return operator.index(n) 

556 except TypeError: 

557 raise ValueError('%s is not an integer' % (n,)) 

558 else: 

559 try: 

560 result = int(n) 

561 except TypeError: 

562 raise ValueError('%s is not an integer' % (n,)) 

563 if n != result: 

564 raise ValueError('%s is not an integer' % (n,)) 

565 return result