misc.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  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
  161. global _debug_iter
  162. oldtmp = _debug_tmp
  163. _debug_tmp = []
  164. _debug_iter += 1
  165. def tree(subtrees):
  166. def indent(s, variant=1):
  167. x = s.split("\n")
  168. r = "+-%s\n" % x[0]
  169. for a in x[1:]:
  170. if a == "":
  171. continue
  172. if variant == 1:
  173. r += "| %s\n" % a
  174. else:
  175. r += " %s\n" % a
  176. return r
  177. if len(subtrees) == 0:
  178. return ""
  179. f = []
  180. for a in subtrees[:-1]:
  181. f.append(indent(a))
  182. f.append(indent(subtrees[-1], 2))
  183. return ''.join(f)
  184. # If there is a bug and the algorithm enters an infinite loop, enable the
  185. # following lines. It will print the names and parameters of all major functions
  186. # that are called, *before* they are called
  187. #from functools import reduce
  188. #print("%s%s %s%s" % (_debug_iter, reduce(lambda x, y: x + y, \
  189. # map(lambda x: '-', range(1, 2 + _debug_iter))), f.__name__, args))
  190. r = f(*args, **kw)
  191. _debug_iter -= 1
  192. s = "%s%s = %s\n" % (f.__name__, args, r)
  193. if _debug_tmp != []:
  194. s += tree(_debug_tmp)
  195. _debug_tmp = oldtmp
  196. _debug_tmp.append(s)
  197. if _debug_iter == 0:
  198. print(_debug_tmp[0])
  199. _debug_tmp = []
  200. return r
  201. def decorated(*args, **kwargs):
  202. return maketree(func, *args, **kwargs)
  203. return decorated
  204. def debug(*args):
  205. """
  206. Print ``*args`` if SYMPY_DEBUG is True, else do nothing.
  207. """
  208. from sympy import SYMPY_DEBUG
  209. if SYMPY_DEBUG:
  210. print(*args, file=sys.stderr)
  211. def debugf(string, args):
  212. """
  213. Print ``string%args`` if SYMPY_DEBUG is True, else do nothing. This is
  214. intended for debug messages using formatted strings.
  215. """
  216. from sympy import SYMPY_DEBUG
  217. if SYMPY_DEBUG:
  218. print(string%args, file=sys.stderr)
  219. def find_executable(executable, path=None):
  220. """Try to find 'executable' in the directories listed in 'path' (a
  221. string listing directories separated by 'os.pathsep'; defaults to
  222. os.environ['PATH']). Returns the complete filename or None if not
  223. found
  224. """
  225. from .exceptions import sympy_deprecation_warning
  226. sympy_deprecation_warning(
  227. """
  228. sympy.utilities.misc.find_executable() is deprecated. Use the standard
  229. library shutil.which() function instead.
  230. """,
  231. deprecated_since_version="1.7",
  232. active_deprecations_target="deprecated-find-executable",
  233. )
  234. if path is None:
  235. path = os.environ['PATH']
  236. paths = path.split(os.pathsep)
  237. extlist = ['']
  238. if os.name == 'os2':
  239. (base, ext) = os.path.splitext(executable)
  240. # executable files on OS/2 can have an arbitrary extension, but
  241. # .exe is automatically appended if no dot is present in the name
  242. if not ext:
  243. executable = executable + ".exe"
  244. elif sys.platform == 'win32':
  245. pathext = os.environ['PATHEXT'].lower().split(os.pathsep)
  246. (base, ext) = os.path.splitext(executable)
  247. if ext.lower() not in pathext:
  248. extlist = pathext
  249. for ext in extlist:
  250. execname = executable + ext
  251. if os.path.isfile(execname):
  252. return execname
  253. else:
  254. for p in paths:
  255. f = os.path.join(p, execname)
  256. if os.path.isfile(f):
  257. return f
  258. return None
  259. def func_name(x, short=False):
  260. """Return function name of `x` (if defined) else the `type(x)`.
  261. If short is True and there is a shorter alias for the result,
  262. return the alias.
  263. Examples
  264. ========
  265. >>> from sympy.utilities.misc import func_name
  266. >>> from sympy import Matrix
  267. >>> from sympy.abc import x
  268. >>> func_name(Matrix.eye(3))
  269. 'MutableDenseMatrix'
  270. >>> func_name(x < 1)
  271. 'StrictLessThan'
  272. >>> func_name(x < 1, short=True)
  273. 'Lt'
  274. """
  275. alias = {
  276. 'GreaterThan': 'Ge',
  277. 'StrictGreaterThan': 'Gt',
  278. 'LessThan': 'Le',
  279. 'StrictLessThan': 'Lt',
  280. 'Equality': 'Eq',
  281. 'Unequality': 'Ne',
  282. }
  283. typ = type(x)
  284. if str(typ).startswith("<type '"):
  285. typ = str(typ).split("'")[1].split("'")[0]
  286. elif str(typ).startswith("<class '"):
  287. typ = str(typ).split("'")[1].split("'")[0]
  288. rv = getattr(getattr(x, 'func', x), '__name__', typ)
  289. if '.' in rv:
  290. rv = rv.split('.')[-1]
  291. if short:
  292. rv = alias.get(rv, rv)
  293. return rv
  294. def _replace(reps):
  295. """Return a function that can make the replacements, given in
  296. ``reps``, on a string. The replacements should be given as mapping.
  297. Examples
  298. ========
  299. >>> from sympy.utilities.misc import _replace
  300. >>> f = _replace(dict(foo='bar', d='t'))
  301. >>> f('food')
  302. 'bart'
  303. >>> f = _replace({})
  304. >>> f('food')
  305. 'food'
  306. """
  307. if not reps:
  308. return lambda x: x
  309. D = lambda match: reps[match.group(0)]
  310. pattern = _re.compile("|".join(
  311. [_re.escape(k) for k, v in reps.items()]), _re.M)
  312. return lambda string: pattern.sub(D, string)
  313. def replace(string, *reps):
  314. """Return ``string`` with all keys in ``reps`` replaced with
  315. their corresponding values, longer strings first, irrespective
  316. of the order they are given. ``reps`` may be passed as tuples
  317. or a single mapping.
  318. Examples
  319. ========
  320. >>> from sympy.utilities.misc import replace
  321. >>> replace('foo', {'oo': 'ar', 'f': 'b'})
  322. 'bar'
  323. >>> replace("spamham sha", ("spam", "eggs"), ("sha","md5"))
  324. 'eggsham md5'
  325. There is no guarantee that a unique answer will be
  326. obtained if keys in a mapping overlap (i.e. are the same
  327. length and have some identical sequence at the
  328. beginning/end):
  329. >>> reps = [
  330. ... ('ab', 'x'),
  331. ... ('bc', 'y')]
  332. >>> replace('abc', *reps) in ('xc', 'ay')
  333. True
  334. References
  335. ==========
  336. .. [1] https://stackoverflow.com/questions/6116978/how-to-replace-multiple-substrings-of-a-string
  337. """
  338. if len(reps) == 1:
  339. kv = reps[0]
  340. if isinstance(kv, dict):
  341. reps = kv
  342. else:
  343. return string.replace(*kv)
  344. else:
  345. reps = dict(reps)
  346. return _replace(reps)(string)
  347. def translate(s, a, b=None, c=None):
  348. """Return ``s`` where characters have been replaced or deleted.
  349. SYNTAX
  350. ======
  351. translate(s, None, deletechars):
  352. all characters in ``deletechars`` are deleted
  353. translate(s, map [,deletechars]):
  354. all characters in ``deletechars`` (if provided) are deleted
  355. then the replacements defined by map are made; if the keys
  356. of map are strings then the longer ones are handled first.
  357. Multicharacter deletions should have a value of ''.
  358. translate(s, oldchars, newchars, deletechars)
  359. all characters in ``deletechars`` are deleted
  360. then each character in ``oldchars`` is replaced with the
  361. corresponding character in ``newchars``
  362. Examples
  363. ========
  364. >>> from sympy.utilities.misc import translate
  365. >>> abc = 'abc'
  366. >>> translate(abc, None, 'a')
  367. 'bc'
  368. >>> translate(abc, {'a': 'x'}, 'c')
  369. 'xb'
  370. >>> translate(abc, {'abc': 'x', 'a': 'y'})
  371. 'x'
  372. >>> translate('abcd', 'ac', 'AC', 'd')
  373. 'AbC'
  374. There is no guarantee that a unique answer will be
  375. obtained if keys in a mapping overlap are the same
  376. length and have some identical sequences at the
  377. beginning/end:
  378. >>> translate(abc, {'ab': 'x', 'bc': 'y'}) in ('xc', 'ay')
  379. True
  380. """
  381. mr = {}
  382. if a is None:
  383. if c is not None:
  384. raise ValueError('c should be None when a=None is passed, instead got %s' % c)
  385. if b is None:
  386. return s
  387. c = b
  388. a = b = ''
  389. else:
  390. if isinstance(a, dict):
  391. short = {}
  392. for k in list(a.keys()):
  393. if len(k) == 1 and len(a[k]) == 1:
  394. short[k] = a.pop(k)
  395. mr = a
  396. c = b
  397. if short:
  398. a, b = [''.join(i) for i in list(zip(*short.items()))]
  399. else:
  400. a = b = ''
  401. elif len(a) != len(b):
  402. raise ValueError('oldchars and newchars have different lengths')
  403. if c:
  404. val = str.maketrans('', '', c)
  405. s = s.translate(val)
  406. s = replace(s, mr)
  407. n = str.maketrans(a, b)
  408. return s.translate(n)
  409. def ordinal(num):
  410. """Return ordinal number string of num, e.g. 1 becomes 1st.
  411. """
  412. # modified from https://codereview.stackexchange.com/questions/41298/producing-ordinal-numbers
  413. n = as_int(num)
  414. k = abs(n) % 100
  415. if 11 <= k <= 13:
  416. suffix = 'th'
  417. elif k % 10 == 1:
  418. suffix = 'st'
  419. elif k % 10 == 2:
  420. suffix = 'nd'
  421. elif k % 10 == 3:
  422. suffix = 'rd'
  423. else:
  424. suffix = 'th'
  425. return str(n) + suffix
  426. def as_int(n, strict=True):
  427. """
  428. Convert the argument to a builtin integer.
  429. The return value is guaranteed to be equal to the input. ValueError is
  430. raised if the input has a non-integral value. When ``strict`` is True, this
  431. uses `__index__ <https://docs.python.org/3/reference/datamodel.html#object.__index__>`_
  432. and when it is False it uses ``int``.
  433. Examples
  434. ========
  435. >>> from sympy.utilities.misc import as_int
  436. >>> from sympy import sqrt, S
  437. The function is primarily concerned with sanitizing input for
  438. functions that need to work with builtin integers, so anything that
  439. is unambiguously an integer should be returned as an int:
  440. >>> as_int(S(3))
  441. 3
  442. Floats, being of limited precision, are not assumed to be exact and
  443. will raise an error unless the ``strict`` flag is False. This
  444. precision issue becomes apparent for large floating point numbers:
  445. >>> big = 1e23
  446. >>> type(big) is float
  447. True
  448. >>> big == int(big)
  449. True
  450. >>> as_int(big)
  451. Traceback (most recent call last):
  452. ...
  453. ValueError: ... is not an integer
  454. >>> as_int(big, strict=False)
  455. 99999999999999991611392
  456. Input that might be a complex representation of an integer value is
  457. also rejected by default:
  458. >>> one = sqrt(3 + 2*sqrt(2)) - sqrt(2)
  459. >>> int(one) == 1
  460. True
  461. >>> as_int(one)
  462. Traceback (most recent call last):
  463. ...
  464. ValueError: ... is not an integer
  465. """
  466. if strict:
  467. try:
  468. if isinstance(n, bool):
  469. raise TypeError
  470. return operator.index(n)
  471. except TypeError:
  472. raise ValueError('%s is not an integer' % (n,))
  473. else:
  474. try:
  475. result = int(n)
  476. except TypeError:
  477. raise ValueError('%s is not an integer' % (n,))
  478. if n != result:
  479. raise ValueError('%s is not an integer' % (n,))
  480. return result