misc.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. """Miscellaneous stuff that does not really fit anywhere else."""
  2. from __future__ import annotations
  3. import operator
  4. import sys
  5. import os
  6. import re as _re
  7. import struct
  8. from textwrap import fill, dedent
  9. class Undecidable(ValueError):
  10. # an error to be raised when a decision cannot be made definitively
  11. # where a definitive answer is needed
  12. pass
  13. def filldedent(s, w=70, **kwargs):
  14. """
  15. Strips leading and trailing empty lines from a copy of ``s``, then dedents,
  16. fills and returns it.
  17. Empty line stripping serves to deal with docstrings like this one that
  18. start with a newline after the initial triple quote, inserting an empty
  19. line at the beginning of the string.
  20. Additional keyword arguments will be passed to ``textwrap.fill()``.
  21. See Also
  22. ========
  23. strlines, rawlines
  24. """
  25. return '\n' + fill(dedent(str(s)).strip('\n'), width=w, **kwargs)
  26. def strlines(s, c=64, short=False):
  27. """Return a cut-and-pastable string that, when printed, is
  28. equivalent to the input. The lines will be surrounded by
  29. parentheses and no line will be longer than c (default 64)
  30. characters. If the line contains newlines characters, the
  31. `rawlines` result will be returned. If ``short`` is True
  32. (default is False) then if there is one line it will be
  33. returned without bounding parentheses.
  34. Examples
  35. ========
  36. >>> from sympy.utilities.misc import strlines
  37. >>> q = 'this is a long string that should be broken into shorter lines'
  38. >>> print(strlines(q, 40))
  39. (
  40. 'this is a long string that should be b'
  41. 'roken into shorter lines'
  42. )
  43. >>> q == (
  44. ... 'this is a long string that should be b'
  45. ... 'roken into shorter lines'
  46. ... )
  47. True
  48. See Also
  49. ========
  50. filldedent, rawlines
  51. """
  52. if not isinstance(s, str):
  53. raise ValueError('expecting string input')
  54. if '\n' in s:
  55. return rawlines(s)
  56. q = '"' if repr(s).startswith('"') else "'"
  57. q = (q,)*2
  58. if '\\' in s: # use r-string
  59. m = '(\nr%s%%s%s\n)' % q
  60. j = '%s\nr%s' % q
  61. c -= 3
  62. else:
  63. m = '(\n%s%%s%s\n)' % q
  64. j = '%s\n%s' % q
  65. c -= 2
  66. out = []
  67. while s:
  68. out.append(s[:c])
  69. s=s[c:]
  70. if short and len(out) == 1:
  71. return (m % out[0]).splitlines()[1] # strip bounding (\n...\n)
  72. return m % j.join(out)
  73. def rawlines(s):
  74. """Return a cut-and-pastable string that, when printed, is equivalent
  75. to the input. Use this when there is more than one line in the
  76. string. The string returned is formatted so it can be indented
  77. nicely within tests; in some cases it is wrapped in the dedent
  78. function which has to be imported from textwrap.
  79. Examples
  80. ========
  81. Note: because there are characters in the examples below that need
  82. to be escaped because they are themselves within a triple quoted
  83. docstring, expressions below look more complicated than they would
  84. be if they were printed in an interpreter window.
  85. >>> from sympy.utilities.misc import rawlines
  86. >>> from sympy import TableForm
  87. >>> s = str(TableForm([[1, 10]], headings=(None, ['a', 'bee'])))
  88. >>> print(rawlines(s))
  89. (
  90. 'a bee\\n'
  91. '-----\\n'
  92. '1 10 '
  93. )
  94. >>> print(rawlines('''this
  95. ... that'''))
  96. dedent('''\\
  97. this
  98. that''')
  99. >>> print(rawlines('''this
  100. ... that
  101. ... '''))
  102. dedent('''\\
  103. this
  104. that
  105. ''')
  106. >>> s = \"\"\"this
  107. ... is a triple '''
  108. ... \"\"\"
  109. >>> print(rawlines(s))
  110. dedent(\"\"\"\\
  111. this
  112. is a triple '''
  113. \"\"\")
  114. >>> print(rawlines('''this
  115. ... that
  116. ... '''))
  117. (
  118. 'this\\n'
  119. 'that\\n'
  120. ' '
  121. )
  122. See Also
  123. ========
  124. filldedent, strlines
  125. """
  126. lines = s.split('\n')
  127. if len(lines) == 1:
  128. return repr(lines[0])
  129. triple = ["'''" in s, '"""' in s]
  130. if any(li.endswith(' ') for li in lines) or '\\' in s or all(triple):
  131. rv = []
  132. # add on the newlines
  133. trailing = s.endswith('\n')
  134. last = len(lines) - 1
  135. for i, li in enumerate(lines):
  136. if i != last or trailing:
  137. rv.append(repr(li + '\n'))
  138. else:
  139. rv.append(repr(li))
  140. return '(\n %s\n)' % '\n '.join(rv)
  141. else:
  142. rv = '\n '.join(lines)
  143. if triple[0]:
  144. return 'dedent("""\\\n %s""")' % rv
  145. else:
  146. return "dedent('''\\\n %s''')" % rv
  147. ARCH = str(struct.calcsize('P') * 8) + "-bit"
  148. # XXX: PyPy does not support hash randomization
  149. HASH_RANDOMIZATION = getattr(sys.flags, 'hash_randomization', False)
  150. _debug_tmp: list[str] = []
  151. _debug_iter = 0
  152. def debug_decorator(func):
  153. """If SYMPY_DEBUG is True, it will print a nice execution tree with
  154. arguments and results of all decorated functions, else do nothing.
  155. """
  156. from sympy import SYMPY_DEBUG
  157. if not SYMPY_DEBUG:
  158. return func
  159. def maketree(f, *args, **kw):
  160. global _debug_tmp, _debug_iter
  161. oldtmp = _debug_tmp
  162. _debug_tmp = []
  163. _debug_iter += 1
  164. def tree(subtrees):
  165. def indent(s, variant=1):
  166. x = s.split("\n")
  167. r = "+-%s\n" % x[0]
  168. for a in x[1:]:
  169. if a == "":
  170. continue
  171. if variant == 1:
  172. r += "| %s\n" % a
  173. else:
  174. r += " %s\n" % a
  175. return r
  176. if len(subtrees) == 0:
  177. return ""
  178. f = []
  179. for a in subtrees[:-1]:
  180. f.append(indent(a))
  181. f.append(indent(subtrees[-1], 2))
  182. return ''.join(f)
  183. # If there is a bug and the algorithm enters an infinite loop, enable the
  184. # following lines. It will print the names and parameters of all major functions
  185. # that are called, *before* they are called
  186. #from functools import reduce
  187. #print("%s%s %s%s" % (_debug_iter, reduce(lambda x, y: x + y, \
  188. # map(lambda x: '-', range(1, 2 + _debug_iter))), f.__name__, args))
  189. r = f(*args, **kw)
  190. _debug_iter -= 1
  191. s = "%s%s = %s\n" % (f.__name__, args, r)
  192. if _debug_tmp != []:
  193. s += tree(_debug_tmp)
  194. _debug_tmp = oldtmp
  195. _debug_tmp.append(s)
  196. if _debug_iter == 0:
  197. print(_debug_tmp[0])
  198. _debug_tmp = []
  199. return r
  200. def decorated(*args, **kwargs):
  201. return maketree(func, *args, **kwargs)
  202. return decorated
  203. def debug(*args):
  204. """
  205. Print ``*args`` if SYMPY_DEBUG is True, else do nothing.
  206. """
  207. from sympy import SYMPY_DEBUG
  208. if SYMPY_DEBUG:
  209. print(*args, file=sys.stderr)
  210. def debugf(string, args):
  211. """
  212. Print ``string%args`` if SYMPY_DEBUG is True, else do nothing. This is
  213. intended for debug messages using formatted strings.
  214. """
  215. from sympy import SYMPY_DEBUG
  216. if SYMPY_DEBUG:
  217. print(string%args, file=sys.stderr)
  218. def find_executable(executable, path=None):
  219. """Try to find 'executable' in the directories listed in 'path' (a
  220. string listing directories separated by 'os.pathsep'; defaults to
  221. os.environ['PATH']). Returns the complete filename or None if not
  222. found
  223. """
  224. from .exceptions import sympy_deprecation_warning
  225. sympy_deprecation_warning(
  226. """
  227. sympy.utilities.misc.find_executable() is deprecated. Use the standard
  228. library shutil.which() function instead.
  229. """,
  230. deprecated_since_version="1.7",
  231. active_deprecations_target="deprecated-find-executable",
  232. )
  233. if path is None:
  234. path = os.environ['PATH']
  235. paths = path.split(os.pathsep)
  236. extlist = ['']
  237. if os.name == 'os2':
  238. (base, ext) = os.path.splitext(executable)
  239. # executable files on OS/2 can have an arbitrary extension, but
  240. # .exe is automatically appended if no dot is present in the name
  241. if not ext:
  242. executable = executable + ".exe"
  243. elif sys.platform == 'win32':
  244. pathext = os.environ['PATHEXT'].lower().split(os.pathsep)
  245. (base, ext) = os.path.splitext(executable)
  246. if ext.lower() not in pathext:
  247. extlist = pathext
  248. for ext in extlist:
  249. execname = executable + ext
  250. if os.path.isfile(execname):
  251. return execname
  252. else:
  253. for p in paths:
  254. f = os.path.join(p, execname)
  255. if os.path.isfile(f):
  256. return f
  257. return None
  258. def func_name(x, short=False):
  259. """Return function name of `x` (if defined) else the `type(x)`.
  260. If short is True and there is a shorter alias for the result,
  261. return the alias.
  262. Examples
  263. ========
  264. >>> from sympy.utilities.misc import func_name
  265. >>> from sympy import Matrix
  266. >>> from sympy.abc import x
  267. >>> func_name(Matrix.eye(3))
  268. 'MutableDenseMatrix'
  269. >>> func_name(x < 1)
  270. 'StrictLessThan'
  271. >>> func_name(x < 1, short=True)
  272. 'Lt'
  273. """
  274. alias = {
  275. 'GreaterThan': 'Ge',
  276. 'StrictGreaterThan': 'Gt',
  277. 'LessThan': 'Le',
  278. 'StrictLessThan': 'Lt',
  279. 'Equality': 'Eq',
  280. 'Unequality': 'Ne',
  281. }
  282. typ = type(x)
  283. if str(typ).startswith("<type '"):
  284. typ = str(typ).split("'")[1].split("'")[0]
  285. elif str(typ).startswith("<class '"):
  286. typ = str(typ).split("'")[1].split("'")[0]
  287. rv = getattr(getattr(x, 'func', x), '__name__', typ)
  288. if '.' in rv:
  289. rv = rv.split('.')[-1]
  290. if short:
  291. rv = alias.get(rv, rv)
  292. return rv
  293. def _replace(reps):
  294. """Return a function that can make the replacements, given in
  295. ``reps``, on a string. The replacements should be given as mapping.
  296. Examples
  297. ========
  298. >>> from sympy.utilities.misc import _replace
  299. >>> f = _replace(dict(foo='bar', d='t'))
  300. >>> f('food')
  301. 'bart'
  302. >>> f = _replace({})
  303. >>> f('food')
  304. 'food'
  305. """
  306. if not reps:
  307. return lambda x: x
  308. D = lambda match: reps[match.group(0)]
  309. pattern = _re.compile("|".join(
  310. [_re.escape(k) for k, v in reps.items()]), _re.MULTILINE)
  311. return lambda string: pattern.sub(D, string)
  312. def replace(string, *reps):
  313. """Return ``string`` with all keys in ``reps`` replaced with
  314. their corresponding values, longer strings first, irrespective
  315. of the order they are given. ``reps`` may be passed as tuples
  316. or a single mapping.
  317. Examples
  318. ========
  319. >>> from sympy.utilities.misc import replace
  320. >>> replace('foo', {'oo': 'ar', 'f': 'b'})
  321. 'bar'
  322. >>> replace("spamham sha", ("spam", "eggs"), ("sha","md5"))
  323. 'eggsham md5'
  324. There is no guarantee that a unique answer will be
  325. obtained if keys in a mapping overlap (i.e. are the same
  326. length and have some identical sequence at the
  327. beginning/end):
  328. >>> reps = [
  329. ... ('ab', 'x'),
  330. ... ('bc', 'y')]
  331. >>> replace('abc', *reps) in ('xc', 'ay')
  332. True
  333. References
  334. ==========
  335. .. [1] https://stackoverflow.com/questions/6116978/how-to-replace-multiple-substrings-of-a-string
  336. """
  337. if len(reps) == 1:
  338. kv = reps[0]
  339. if isinstance(kv, dict):
  340. reps = kv
  341. else:
  342. return string.replace(*kv)
  343. else:
  344. reps = dict(reps)
  345. return _replace(reps)(string)
  346. def translate(s, a, b=None, c=None):
  347. """Return ``s`` where characters have been replaced or deleted.
  348. SYNTAX
  349. ======
  350. translate(s, None, deletechars):
  351. all characters in ``deletechars`` are deleted
  352. translate(s, map [,deletechars]):
  353. all characters in ``deletechars`` (if provided) are deleted
  354. then the replacements defined by map are made; if the keys
  355. of map are strings then the longer ones are handled first.
  356. Multicharacter deletions should have a value of ''.
  357. translate(s, oldchars, newchars, deletechars)
  358. all characters in ``deletechars`` are deleted
  359. then each character in ``oldchars`` is replaced with the
  360. corresponding character in ``newchars``
  361. Examples
  362. ========
  363. >>> from sympy.utilities.misc import translate
  364. >>> abc = 'abc'
  365. >>> translate(abc, None, 'a')
  366. 'bc'
  367. >>> translate(abc, {'a': 'x'}, 'c')
  368. 'xb'
  369. >>> translate(abc, {'abc': 'x', 'a': 'y'})
  370. 'x'
  371. >>> translate('abcd', 'ac', 'AC', 'd')
  372. 'AbC'
  373. There is no guarantee that a unique answer will be
  374. obtained if keys in a mapping overlap are the same
  375. length and have some identical sequences at the
  376. beginning/end:
  377. >>> translate(abc, {'ab': 'x', 'bc': 'y'}) in ('xc', 'ay')
  378. True
  379. """
  380. mr = {}
  381. if a is None:
  382. if c is not None:
  383. raise ValueError('c should be None when a=None is passed, instead got %s' % c)
  384. if b is None:
  385. return s
  386. c = b
  387. a = b = ''
  388. else:
  389. if isinstance(a, dict):
  390. short = {}
  391. for k in list(a.keys()):
  392. if len(k) == 1 and len(a[k]) == 1:
  393. short[k] = a.pop(k)
  394. mr = a
  395. c = b
  396. if short:
  397. a, b = [''.join(i) for i in list(zip(*short.items()))]
  398. else:
  399. a = b = ''
  400. elif len(a) != len(b):
  401. raise ValueError('oldchars and newchars have different lengths')
  402. if c:
  403. val = str.maketrans('', '', c)
  404. s = s.translate(val)
  405. s = replace(s, mr)
  406. n = str.maketrans(a, b)
  407. return s.translate(n)
  408. def ordinal(num):
  409. """Return ordinal number string of num, e.g. 1 becomes 1st.
  410. """
  411. # modified from https://codereview.stackexchange.com/questions/41298/producing-ordinal-numbers
  412. n = as_int(num)
  413. k = abs(n) % 100
  414. if 11 <= k <= 13:
  415. suffix = 'th'
  416. elif k % 10 == 1:
  417. suffix = 'st'
  418. elif k % 10 == 2:
  419. suffix = 'nd'
  420. elif k % 10 == 3:
  421. suffix = 'rd'
  422. else:
  423. suffix = 'th'
  424. return str(n) + suffix
  425. def as_int(n, strict=True):
  426. """
  427. Convert the argument to a builtin integer.
  428. The return value is guaranteed to be equal to the input. ValueError is
  429. raised if the input has a non-integral value. When ``strict`` is True, this
  430. uses `__index__ <https://docs.python.org/3/reference/datamodel.html#object.__index__>`_
  431. and when it is False it uses ``int``.
  432. Examples
  433. ========
  434. >>> from sympy.utilities.misc import as_int
  435. >>> from sympy import sqrt, S
  436. The function is primarily concerned with sanitizing input for
  437. functions that need to work with builtin integers, so anything that
  438. is unambiguously an integer should be returned as an int:
  439. >>> as_int(S(3))
  440. 3
  441. Floats, being of limited precision, are not assumed to be exact and
  442. will raise an error unless the ``strict`` flag is False. This
  443. precision issue becomes apparent for large floating point numbers:
  444. >>> big = 1e23
  445. >>> type(big) is float
  446. True
  447. >>> big == int(big)
  448. True
  449. >>> as_int(big)
  450. Traceback (most recent call last):
  451. ...
  452. ValueError: ... is not an integer
  453. >>> as_int(big, strict=False)
  454. 99999999999999991611392
  455. Input that might be a complex representation of an integer value is
  456. also rejected by default:
  457. >>> one = sqrt(3 + 2*sqrt(2)) - sqrt(2)
  458. >>> int(one) == 1
  459. True
  460. >>> as_int(one)
  461. Traceback (most recent call last):
  462. ...
  463. ValueError: ... is not an integer
  464. """
  465. if strict:
  466. try:
  467. if isinstance(n, bool):
  468. raise TypeError
  469. return operator.index(n)
  470. except TypeError:
  471. raise ValueError('%s is not an integer' % (n,))
  472. else:
  473. try:
  474. result = int(n)
  475. except TypeError:
  476. raise ValueError('%s is not an integer' % (n,))
  477. if n - result:
  478. raise ValueError('%s is not an integer' % (n,))
  479. return result