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
« 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."""
3from __future__ import annotations
5import operator
6import sys
7import os
8import re as _re
9import struct
10from textwrap import fill, dedent
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
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.
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.
28 Additional keyword arguments will be passed to ``textwrap.fill()``.
30 See Also
31 ========
32 strlines, rawlines
34 """
35 return '\n' + fill(dedent(str(s)).strip('\n'), width=w, **kwargs)
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.
47 Examples
48 ========
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
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)
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.
97 Examples
98 ========
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.
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''')
120 >>> print(rawlines('''this
121 ... that
122 ... '''))
123 dedent('''\\
124 this
125 that
126 ''')
128 >>> s = \"\"\"this
129 ... is a triple '''
130 ... \"\"\"
131 >>> print(rawlines(s))
132 dedent(\"\"\"\\
133 this
134 is a triple '''
135 \"\"\")
137 >>> print(rawlines('''this
138 ... that
139 ... '''))
140 (
141 'this\\n'
142 'that\\n'
143 ' '
144 )
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
172ARCH = str(struct.calcsize('P') * 8) + "-bit"
175# XXX: PyPy does not support hash randomization
176HASH_RANDOMIZATION = getattr(sys.flags, 'hash_randomization', False)
178_debug_tmp: list[str] = []
179_debug_iter = 0
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
187 if not SYMPY_DEBUG:
188 return func
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
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)
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))
224 r = f(*args, **kw)
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
237 def decorated(*args, **kwargs):
238 return maketree(func, *args, **kwargs)
240 return decorated
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)
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)
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
302 return None
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.
310 Examples
311 ========
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
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.
348 Examples
349 ========
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)
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.
373 Examples
374 ========
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'
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):
387 >>> reps = [
388 ... ('ab', 'x'),
389 ... ('bc', 'y')]
390 >>> replace('abc', *reps) in ('xc', 'ay')
391 True
393 References
394 ==========
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)
409def translate(s, a, b=None, c=None):
410 """Return ``s`` where characters have been replaced or deleted.
412 SYNTAX
413 ======
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``
427 Examples
428 ========
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'
439 >>> translate('abcd', 'ac', 'AC', 'd')
440 'AbC'
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:
447 >>> translate(abc, {'ab': 'x', 'bc': 'y'}) in ('xc', 'ay')
448 True
449 """
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')
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)
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
501def as_int(n, strict=True):
502 """
503 Convert the argument to a builtin integer.
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``.
511 Examples
512 ========
514 >>> from sympy.utilities.misc import as_int
515 >>> from sympy import sqrt, S
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:
521 >>> as_int(S(3))
522 3
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:
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
540 Input that might be a complex representation of an integer value is
541 also rejected by default:
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