diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5bee278c67a67967565d94de5cc374234e21c5c5
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/cnodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/cnodes.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..62c54cd05c96d37ce16da2ef7190ca3e9427b790
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/cnodes.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/numpy_nodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/numpy_nodes.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a87a620052364d930756defcb7b46883a0ccedff
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/numpy_nodes.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/scipy_nodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/scipy_nodes.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..adb43d4a524b0f8f4533be218a37498d982379df
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/scipy_nodes.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/crypto/__init__.py b/lib/python3.12/site-packages/sympy/crypto/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b27b4b036e5f2ed93a1ea88cd7d7144eb5615d4
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/crypto/__init__.py
@@ -0,0 +1,35 @@
+from sympy.crypto.crypto import (cycle_list,
+ encipher_shift, encipher_affine, encipher_substitution,
+ check_and_join, encipher_vigenere, decipher_vigenere, bifid5_square,
+ bifid6_square, encipher_hill, decipher_hill,
+ encipher_bifid5, encipher_bifid6, decipher_bifid5,
+ decipher_bifid6, encipher_kid_rsa, decipher_kid_rsa,
+ kid_rsa_private_key, kid_rsa_public_key, decipher_rsa, rsa_private_key,
+ rsa_public_key, encipher_rsa, lfsr_connection_polynomial,
+ lfsr_autocorrelation, lfsr_sequence, encode_morse, decode_morse,
+ elgamal_private_key, elgamal_public_key, decipher_elgamal,
+ encipher_elgamal, dh_private_key, dh_public_key, dh_shared_key,
+ padded_key, encipher_bifid, decipher_bifid, bifid_square, bifid5,
+ bifid6, bifid10, decipher_gm, encipher_gm, gm_public_key,
+ gm_private_key, bg_private_key, bg_public_key, encipher_bg, decipher_bg,
+ encipher_rot13, decipher_rot13, encipher_atbash, decipher_atbash,
+ encipher_railfence, decipher_railfence)
+
+__all__ = [
+ 'cycle_list', 'encipher_shift', 'encipher_affine',
+ 'encipher_substitution', 'check_and_join', 'encipher_vigenere',
+ 'decipher_vigenere', 'bifid5_square', 'bifid6_square', 'encipher_hill',
+ 'decipher_hill', 'encipher_bifid5', 'encipher_bifid6', 'decipher_bifid5',
+ 'decipher_bifid6', 'encipher_kid_rsa', 'decipher_kid_rsa',
+ 'kid_rsa_private_key', 'kid_rsa_public_key', 'decipher_rsa',
+ 'rsa_private_key', 'rsa_public_key', 'encipher_rsa',
+ 'lfsr_connection_polynomial', 'lfsr_autocorrelation', 'lfsr_sequence',
+ 'encode_morse', 'decode_morse', 'elgamal_private_key',
+ 'elgamal_public_key', 'decipher_elgamal', 'encipher_elgamal',
+ 'dh_private_key', 'dh_public_key', 'dh_shared_key', 'padded_key',
+ 'encipher_bifid', 'decipher_bifid', 'bifid_square', 'bifid5', 'bifid6',
+ 'bifid10', 'decipher_gm', 'encipher_gm', 'gm_public_key',
+ 'gm_private_key', 'bg_private_key', 'bg_public_key', 'encipher_bg',
+ 'decipher_bg', 'encipher_rot13', 'decipher_rot13', 'encipher_atbash',
+ 'decipher_atbash', 'encipher_railfence', 'decipher_railfence',
+]
diff --git a/lib/python3.12/site-packages/sympy/crypto/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/crypto/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9fa2451050417d968fac95e21e067cffe53ee21c
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/crypto/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/crypto/crypto.py b/lib/python3.12/site-packages/sympy/crypto/crypto.py
new file mode 100644
index 0000000000000000000000000000000000000000..62c50072ca1aa36123de87700fb9d5ebae0a9f27
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/crypto/crypto.py
@@ -0,0 +1,3367 @@
+"""
+This file contains some classical ciphers and routines
+implementing a linear-feedback shift register (LFSR)
+and the Diffie-Hellman key exchange.
+
+.. warning::
+
+ This module is intended for educational purposes only. Do not use the
+ functions in this module for real cryptographic applications. If you wish
+ to encrypt real data, we recommend using something like the `cryptography
+ `_ module.
+
+"""
+
+from string import whitespace, ascii_uppercase as uppercase, printable
+from functools import reduce
+import warnings
+
+from itertools import cycle
+
+from sympy.external.gmpy import GROUND_TYPES
+from sympy.core import Symbol
+from sympy.core.numbers import Rational
+from sympy.core.random import _randrange, _randint
+from sympy.external.gmpy import gcd, invert
+from sympy.functions.combinatorial.numbers import (totient as _euler,
+ reduced_totient as _carmichael)
+from sympy.matrices import Matrix
+from sympy.ntheory import isprime, primitive_root, factorint
+from sympy.ntheory.generate import nextprime
+from sympy.ntheory.modular import crt
+from sympy.polys.domains import FF
+from sympy.polys.polytools import Poly
+from sympy.utilities.misc import as_int, filldedent, translate
+from sympy.utilities.iterables import uniq, multiset
+from sympy.utilities.decorator import doctest_depends_on
+
+
+if GROUND_TYPES == 'flint':
+ __doctest_skip__ = ['lfsr_sequence']
+
+
+class NonInvertibleCipherWarning(RuntimeWarning):
+ """A warning raised if the cipher is not invertible."""
+ def __init__(self, msg):
+ self.fullMessage = msg
+
+ def __str__(self):
+ return '\n\t' + self.fullMessage
+
+ def warn(self, stacklevel=3):
+ warnings.warn(self, stacklevel=stacklevel)
+
+
+def AZ(s=None):
+ """Return the letters of ``s`` in uppercase. In case more than
+ one string is passed, each of them will be processed and a list
+ of upper case strings will be returned.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import AZ
+ >>> AZ('Hello, world!')
+ 'HELLOWORLD'
+ >>> AZ('Hello, world!'.split())
+ ['HELLO', 'WORLD']
+
+ See Also
+ ========
+
+ check_and_join
+
+ """
+ if not s:
+ return uppercase
+ t = isinstance(s, str)
+ if t:
+ s = [s]
+ rv = [check_and_join(i.upper().split(), uppercase, filter=True)
+ for i in s]
+ if t:
+ return rv[0]
+ return rv
+
+bifid5 = AZ().replace('J', '')
+bifid6 = AZ() + '0123456789'
+bifid10 = printable
+
+
+def padded_key(key, symbols):
+ """Return a string of the distinct characters of ``symbols`` with
+ those of ``key`` appearing first. A ValueError is raised if
+ a) there are duplicate characters in ``symbols`` or
+ b) there are characters in ``key`` that are not in ``symbols``.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import padded_key
+ >>> padded_key('PUPPY', 'OPQRSTUVWXY')
+ 'PUYOQRSTVWX'
+ >>> padded_key('RSA', 'ARTIST')
+ Traceback (most recent call last):
+ ...
+ ValueError: duplicate characters in symbols: T
+
+ """
+ syms = list(uniq(symbols))
+ if len(syms) != len(symbols):
+ extra = ''.join(sorted({
+ i for i in symbols if symbols.count(i) > 1}))
+ raise ValueError('duplicate characters in symbols: %s' % extra)
+ extra = set(key) - set(syms)
+ if extra:
+ raise ValueError(
+ 'characters in key but not symbols: %s' % ''.join(
+ sorted(extra)))
+ key0 = ''.join(list(uniq(key)))
+ # remove from syms characters in key0
+ return key0 + translate(''.join(syms), None, key0)
+
+
+def check_and_join(phrase, symbols=None, filter=None):
+ """
+ Joins characters of ``phrase`` and if ``symbols`` is given, raises
+ an error if any character in ``phrase`` is not in ``symbols``.
+
+ Parameters
+ ==========
+
+ phrase
+ String or list of strings to be returned as a string.
+
+ symbols
+ Iterable of characters allowed in ``phrase``.
+
+ If ``symbols`` is ``None``, no checking is performed.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import check_and_join
+ >>> check_and_join('a phrase')
+ 'a phrase'
+ >>> check_and_join('a phrase'.upper().split())
+ 'APHRASE'
+ >>> check_and_join('a phrase!'.upper().split(), 'ARE', filter=True)
+ 'ARAE'
+ >>> check_and_join('a phrase!'.upper().split(), 'ARE')
+ Traceback (most recent call last):
+ ...
+ ValueError: characters in phrase but not symbols: "!HPS"
+
+ """
+ rv = ''.join(''.join(phrase))
+ if symbols is not None:
+ symbols = check_and_join(symbols)
+ missing = ''.join(sorted(set(rv) - set(symbols)))
+ if missing:
+ if not filter:
+ raise ValueError(
+ 'characters in phrase but not symbols: "%s"' % missing)
+ rv = translate(rv, None, missing)
+ return rv
+
+
+def _prep(msg, key, alp, default=None):
+ if not alp:
+ if not default:
+ alp = AZ()
+ msg = AZ(msg)
+ key = AZ(key)
+ else:
+ alp = default
+ else:
+ alp = ''.join(alp)
+ key = check_and_join(key, alp, filter=True)
+ msg = check_and_join(msg, alp, filter=True)
+ return msg, key, alp
+
+
+def cycle_list(k, n):
+ """
+ Returns the elements of the list ``range(n)`` shifted to the
+ left by ``k`` (so the list starts with ``k`` (mod ``n``)).
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import cycle_list
+ >>> cycle_list(3, 10)
+ [3, 4, 5, 6, 7, 8, 9, 0, 1, 2]
+
+ """
+ k = k % n
+ return list(range(k, n)) + list(range(k))
+
+
+######## shift cipher examples ############
+
+
+def encipher_shift(msg, key, symbols=None):
+ """
+ Performs shift cipher encryption on plaintext msg, and returns the
+ ciphertext.
+
+ Parameters
+ ==========
+
+ key : int
+ The secret key.
+
+ msg : str
+ Plaintext of upper-case letters.
+
+ Returns
+ =======
+
+ str
+ Ciphertext of upper-case letters.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_shift, decipher_shift
+ >>> msg = "GONAVYBEATARMY"
+ >>> ct = encipher_shift(msg, 1); ct
+ 'HPOBWZCFBUBSNZ'
+
+ To decipher the shifted text, change the sign of the key:
+
+ >>> encipher_shift(ct, -1)
+ 'GONAVYBEATARMY'
+
+ There is also a convenience function that does this with the
+ original key:
+
+ >>> decipher_shift(ct, 1)
+ 'GONAVYBEATARMY'
+
+ Notes
+ =====
+
+ ALGORITHM:
+
+ STEPS:
+ 0. Number the letters of the alphabet from 0, ..., N
+ 1. Compute from the string ``msg`` a list ``L1`` of
+ corresponding integers.
+ 2. Compute from the list ``L1`` a new list ``L2``, given by
+ adding ``(k mod 26)`` to each element in ``L1``.
+ 3. Compute from the list ``L2`` a string ``ct`` of
+ corresponding letters.
+
+ The shift cipher is also called the Caesar cipher, after
+ Julius Caesar, who, according to Suetonius, used it with a
+ shift of three to protect messages of military significance.
+ Caesar's nephew Augustus reportedly used a similar cipher, but
+ with a right shift of 1.
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Caesar_cipher
+ .. [2] https://mathworld.wolfram.com/CaesarsMethod.html
+
+ See Also
+ ========
+
+ decipher_shift
+
+ """
+ msg, _, A = _prep(msg, '', symbols)
+ shift = len(A) - key % len(A)
+ key = A[shift:] + A[:shift]
+ return translate(msg, key, A)
+
+
+def decipher_shift(msg, key, symbols=None):
+ """
+ Return the text by shifting the characters of ``msg`` to the
+ left by the amount given by ``key``.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_shift, decipher_shift
+ >>> msg = "GONAVYBEATARMY"
+ >>> ct = encipher_shift(msg, 1); ct
+ 'HPOBWZCFBUBSNZ'
+
+ To decipher the shifted text, change the sign of the key:
+
+ >>> encipher_shift(ct, -1)
+ 'GONAVYBEATARMY'
+
+ Or use this function with the original key:
+
+ >>> decipher_shift(ct, 1)
+ 'GONAVYBEATARMY'
+
+ """
+ return encipher_shift(msg, -key, symbols)
+
+def encipher_rot13(msg, symbols=None):
+ """
+ Performs the ROT13 encryption on a given plaintext ``msg``.
+
+ Explanation
+ ===========
+
+ ROT13 is a substitution cipher which substitutes each letter
+ in the plaintext message for the letter furthest away from it
+ in the English alphabet.
+
+ Equivalently, it is just a Caeser (shift) cipher with a shift
+ key of 13 (midway point of the alphabet).
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/ROT13
+
+ See Also
+ ========
+
+ decipher_rot13
+ encipher_shift
+
+ """
+ return encipher_shift(msg, 13, symbols)
+
+def decipher_rot13(msg, symbols=None):
+ """
+ Performs the ROT13 decryption on a given plaintext ``msg``.
+
+ Explanation
+ ============
+
+ ``decipher_rot13`` is equivalent to ``encipher_rot13`` as both
+ ``decipher_shift`` with a key of 13 and ``encipher_shift`` key with a
+ key of 13 will return the same results. Nonetheless,
+ ``decipher_rot13`` has nonetheless been explicitly defined here for
+ consistency.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_rot13, decipher_rot13
+ >>> msg = 'GONAVYBEATARMY'
+ >>> ciphertext = encipher_rot13(msg);ciphertext
+ 'TBANILORNGNEZL'
+ >>> decipher_rot13(ciphertext)
+ 'GONAVYBEATARMY'
+ >>> encipher_rot13(msg) == decipher_rot13(msg)
+ True
+ >>> msg == decipher_rot13(ciphertext)
+ True
+
+ """
+ return decipher_shift(msg, 13, symbols)
+
+######## affine cipher examples ############
+
+
+def encipher_affine(msg, key, symbols=None, _inverse=False):
+ r"""
+ Performs the affine cipher encryption on plaintext ``msg``, and
+ returns the ciphertext.
+
+ Explanation
+ ===========
+
+ Encryption is based on the map `x \rightarrow ax+b` (mod `N`)
+ where ``N`` is the number of characters in the alphabet.
+ Decryption is based on the map `x \rightarrow cx+d` (mod `N`),
+ where `c = a^{-1}` (mod `N`) and `d = -a^{-1}b` (mod `N`).
+ In particular, for the map to be invertible, we need
+ `\mathrm{gcd}(a, N) = 1` and an error will be raised if this is
+ not true.
+
+ Parameters
+ ==========
+
+ msg : str
+ Characters that appear in ``symbols``.
+
+ a, b : int, int
+ A pair integers, with ``gcd(a, N) = 1`` (the secret key).
+
+ symbols
+ String of characters (default = uppercase letters).
+
+ When no symbols are given, ``msg`` is converted to upper case
+ letters and all other characters are ignored.
+
+ Returns
+ =======
+
+ ct
+ String of characters (the ciphertext message)
+
+ Notes
+ =====
+
+ ALGORITHM:
+
+ STEPS:
+ 0. Number the letters of the alphabet from 0, ..., N
+ 1. Compute from the string ``msg`` a list ``L1`` of
+ corresponding integers.
+ 2. Compute from the list ``L1`` a new list ``L2``, given by
+ replacing ``x`` by ``a*x + b (mod N)``, for each element
+ ``x`` in ``L1``.
+ 3. Compute from the list ``L2`` a string ``ct`` of
+ corresponding letters.
+
+ This is a straightforward generalization of the shift cipher with
+ the added complexity of requiring 2 characters to be deciphered in
+ order to recover the key.
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Affine_cipher
+
+ See Also
+ ========
+
+ decipher_affine
+
+ """
+ msg, _, A = _prep(msg, '', symbols)
+ N = len(A)
+ a, b = key
+ assert gcd(a, N) == 1
+ if _inverse:
+ c = invert(a, N)
+ d = -b*c
+ a, b = c, d
+ B = ''.join([A[(a*i + b) % N] for i in range(N)])
+ return translate(msg, A, B)
+
+
+def decipher_affine(msg, key, symbols=None):
+ r"""
+ Return the deciphered text that was made from the mapping,
+ `x \rightarrow ax+b` (mod `N`), where ``N`` is the
+ number of characters in the alphabet. Deciphering is done by
+ reciphering with a new key: `x \rightarrow cx+d` (mod `N`),
+ where `c = a^{-1}` (mod `N`) and `d = -a^{-1}b` (mod `N`).
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_affine, decipher_affine
+ >>> msg = "GO NAVY BEAT ARMY"
+ >>> key = (3, 1)
+ >>> encipher_affine(msg, key)
+ 'TROBMVENBGBALV'
+ >>> decipher_affine(_, key)
+ 'GONAVYBEATARMY'
+
+ See Also
+ ========
+
+ encipher_affine
+
+ """
+ return encipher_affine(msg, key, symbols, _inverse=True)
+
+
+def encipher_atbash(msg, symbols=None):
+ r"""
+ Enciphers a given ``msg`` into its Atbash ciphertext and returns it.
+
+ Explanation
+ ===========
+
+ Atbash is a substitution cipher originally used to encrypt the Hebrew
+ alphabet. Atbash works on the principle of mapping each alphabet to its
+ reverse / counterpart (i.e. a would map to z, b to y etc.)
+
+ Atbash is functionally equivalent to the affine cipher with ``a = 25``
+ and ``b = 25``
+
+ See Also
+ ========
+
+ decipher_atbash
+
+ """
+ return encipher_affine(msg, (25, 25), symbols)
+
+
+def decipher_atbash(msg, symbols=None):
+ r"""
+ Deciphers a given ``msg`` using Atbash cipher and returns it.
+
+ Explanation
+ ===========
+
+ ``decipher_atbash`` is functionally equivalent to ``encipher_atbash``.
+ However, it has still been added as a separate function to maintain
+ consistency.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_atbash, decipher_atbash
+ >>> msg = 'GONAVYBEATARMY'
+ >>> encipher_atbash(msg)
+ 'TLMZEBYVZGZINB'
+ >>> decipher_atbash(msg)
+ 'TLMZEBYVZGZINB'
+ >>> encipher_atbash(msg) == decipher_atbash(msg)
+ True
+ >>> msg == encipher_atbash(encipher_atbash(msg))
+ True
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Atbash
+
+ See Also
+ ========
+
+ encipher_atbash
+
+ """
+ return decipher_affine(msg, (25, 25), symbols)
+
+#################### substitution cipher ###########################
+
+
+def encipher_substitution(msg, old, new=None):
+ r"""
+ Returns the ciphertext obtained by replacing each character that
+ appears in ``old`` with the corresponding character in ``new``.
+ If ``old`` is a mapping, then new is ignored and the replacements
+ defined by ``old`` are used.
+
+ Explanation
+ ===========
+
+ This is a more general than the affine cipher in that the key can
+ only be recovered by determining the mapping for each symbol.
+ Though in practice, once a few symbols are recognized the mappings
+ for other characters can be quickly guessed.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_substitution, AZ
+ >>> old = 'OEYAG'
+ >>> new = '034^6'
+ >>> msg = AZ("go navy! beat army!")
+ >>> ct = encipher_substitution(msg, old, new); ct
+ '60N^V4B3^T^RM4'
+
+ To decrypt a substitution, reverse the last two arguments:
+
+ >>> encipher_substitution(ct, new, old)
+ 'GONAVYBEATARMY'
+
+ In the special case where ``old`` and ``new`` are a permutation of
+ order 2 (representing a transposition of characters) their order
+ is immaterial:
+
+ >>> old = 'NAVY'
+ >>> new = 'ANYV'
+ >>> encipher = lambda x: encipher_substitution(x, old, new)
+ >>> encipher('NAVY')
+ 'ANYV'
+ >>> encipher(_)
+ 'NAVY'
+
+ The substitution cipher, in general, is a method
+ whereby "units" (not necessarily single characters) of plaintext
+ are replaced with ciphertext according to a regular system.
+
+ >>> ords = dict(zip('abc', ['\\%i' % ord(i) for i in 'abc']))
+ >>> print(encipher_substitution('abc', ords))
+ \97\98\99
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Substitution_cipher
+
+ """
+ return translate(msg, old, new)
+
+
+######################################################################
+#################### Vigenere cipher examples ########################
+######################################################################
+
+def encipher_vigenere(msg, key, symbols=None):
+ """
+ Performs the Vigenere cipher encryption on plaintext ``msg``, and
+ returns the ciphertext.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_vigenere, AZ
+ >>> key = "encrypt"
+ >>> msg = "meet me on monday"
+ >>> encipher_vigenere(msg, key)
+ 'QRGKKTHRZQEBPR'
+
+ Section 1 of the Kryptos sculpture at the CIA headquarters
+ uses this cipher and also changes the order of the
+ alphabet [2]_. Here is the first line of that section of
+ the sculpture:
+
+ >>> from sympy.crypto.crypto import decipher_vigenere, padded_key
+ >>> alp = padded_key('KRYPTOS', AZ())
+ >>> key = 'PALIMPSEST'
+ >>> msg = 'EMUFPHZLRFAXYUSDJKZLDKRNSHGNFIVJ'
+ >>> decipher_vigenere(msg, key, alp)
+ 'BETWEENSUBTLESHADINGANDTHEABSENC'
+
+ Explanation
+ ===========
+
+ The Vigenere cipher is named after Blaise de Vigenere, a sixteenth
+ century diplomat and cryptographer, by a historical accident.
+ Vigenere actually invented a different and more complicated cipher.
+ The so-called *Vigenere cipher* was actually invented
+ by Giovan Batista Belaso in 1553.
+
+ This cipher was used in the 1800's, for example, during the American
+ Civil War. The Confederacy used a brass cipher disk to implement the
+ Vigenere cipher (now on display in the NSA Museum in Fort
+ Meade) [1]_.
+
+ The Vigenere cipher is a generalization of the shift cipher.
+ Whereas the shift cipher shifts each letter by the same amount
+ (that amount being the key of the shift cipher) the Vigenere
+ cipher shifts a letter by an amount determined by the key (which is
+ a word or phrase known only to the sender and receiver).
+
+ For example, if the key was a single letter, such as "C", then the
+ so-called Vigenere cipher is actually a shift cipher with a
+ shift of `2` (since "C" is the 2nd letter of the alphabet, if
+ you start counting at `0`). If the key was a word with two
+ letters, such as "CA", then the so-called Vigenere cipher will
+ shift letters in even positions by `2` and letters in odd positions
+ are left alone (shifted by `0`, since "A" is the 0th letter, if
+ you start counting at `0`).
+
+
+ ALGORITHM:
+
+ INPUT:
+
+ ``msg``: string of characters that appear in ``symbols``
+ (the plaintext)
+
+ ``key``: a string of characters that appear in ``symbols``
+ (the secret key)
+
+ ``symbols``: a string of letters defining the alphabet
+
+
+ OUTPUT:
+
+ ``ct``: string of characters (the ciphertext message)
+
+ STEPS:
+ 0. Number the letters of the alphabet from 0, ..., N
+ 1. Compute from the string ``key`` a list ``L1`` of
+ corresponding integers. Let ``n1 = len(L1)``.
+ 2. Compute from the string ``msg`` a list ``L2`` of
+ corresponding integers. Let ``n2 = len(L2)``.
+ 3. Break ``L2`` up sequentially into sublists of size
+ ``n1``; the last sublist may be smaller than ``n1``
+ 4. For each of these sublists ``L`` of ``L2``, compute a
+ new list ``C`` given by ``C[i] = L[i] + L1[i] (mod N)``
+ to the ``i``-th element in the sublist, for each ``i``.
+ 5. Assemble these lists ``C`` by concatenation into a new
+ list of length ``n2``.
+ 6. Compute from the new list a string ``ct`` of
+ corresponding letters.
+
+ Once it is known that the key is, say, `n` characters long,
+ frequency analysis can be applied to every `n`-th letter of
+ the ciphertext to determine the plaintext. This method is
+ called *Kasiski examination* (although it was first discovered
+ by Babbage). If they key is as long as the message and is
+ comprised of randomly selected characters -- a one-time pad -- the
+ message is theoretically unbreakable.
+
+ The cipher Vigenere actually discovered is an "auto-key" cipher
+ described as follows.
+
+ ALGORITHM:
+
+ INPUT:
+
+ ``key``: a string of letters (the secret key)
+
+ ``msg``: string of letters (the plaintext message)
+
+ OUTPUT:
+
+ ``ct``: string of upper-case letters (the ciphertext message)
+
+ STEPS:
+ 0. Number the letters of the alphabet from 0, ..., N
+ 1. Compute from the string ``msg`` a list ``L2`` of
+ corresponding integers. Let ``n2 = len(L2)``.
+ 2. Let ``n1`` be the length of the key. Append to the
+ string ``key`` the first ``n2 - n1`` characters of
+ the plaintext message. Compute from this string (also of
+ length ``n2``) a list ``L1`` of integers corresponding
+ to the letter numbers in the first step.
+ 3. Compute a new list ``C`` given by
+ ``C[i] = L1[i] + L2[i] (mod N)``.
+ 4. Compute from the new list a string ``ct`` of letters
+ corresponding to the new integers.
+
+ To decipher the auto-key ciphertext, the key is used to decipher
+ the first ``n1`` characters and then those characters become the
+ key to decipher the next ``n1`` characters, etc...:
+
+ >>> m = AZ('go navy, beat army! yes you can'); m
+ 'GONAVYBEATARMYYESYOUCAN'
+ >>> key = AZ('gold bug'); n1 = len(key); n2 = len(m)
+ >>> auto_key = key + m[:n2 - n1]; auto_key
+ 'GOLDBUGGONAVYBEATARMYYE'
+ >>> ct = encipher_vigenere(m, auto_key); ct
+ 'MCYDWSHKOGAMKZCELYFGAYR'
+ >>> n1 = len(key)
+ >>> pt = []
+ >>> while ct:
+ ... part, ct = ct[:n1], ct[n1:]
+ ... pt.append(decipher_vigenere(part, key))
+ ... key = pt[-1]
+ ...
+ >>> ''.join(pt) == m
+ True
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Vigenere_cipher
+ .. [2] https://web.archive.org/web/20071116100808/https://filebox.vt.edu/users/batman/kryptos.html
+ (short URL: https://goo.gl/ijr22d)
+
+ """
+ msg, key, A = _prep(msg, key, symbols)
+ map = {c: i for i, c in enumerate(A)}
+ key = [map[c] for c in key]
+ N = len(map)
+ k = len(key)
+ rv = []
+ for i, m in enumerate(msg):
+ rv.append(A[(map[m] + key[i % k]) % N])
+ rv = ''.join(rv)
+ return rv
+
+
+def decipher_vigenere(msg, key, symbols=None):
+ """
+ Decode using the Vigenere cipher.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import decipher_vigenere
+ >>> key = "encrypt"
+ >>> ct = "QRGK kt HRZQE BPR"
+ >>> decipher_vigenere(ct, key)
+ 'MEETMEONMONDAY'
+
+ """
+ msg, key, A = _prep(msg, key, symbols)
+ map = {c: i for i, c in enumerate(A)}
+ N = len(A) # normally, 26
+ K = [map[c] for c in key]
+ n = len(K)
+ C = [map[c] for c in msg]
+ rv = ''.join([A[(-K[i % n] + c) % N] for i, c in enumerate(C)])
+ return rv
+
+
+#################### Hill cipher ########################
+
+
+def encipher_hill(msg, key, symbols=None, pad="Q"):
+ r"""
+ Return the Hill cipher encryption of ``msg``.
+
+ Explanation
+ ===========
+
+ The Hill cipher [1]_, invented by Lester S. Hill in the 1920's [2]_,
+ was the first polygraphic cipher in which it was practical
+ (though barely) to operate on more than three symbols at once.
+ The following discussion assumes an elementary knowledge of
+ matrices.
+
+ First, each letter is first encoded as a number starting with 0.
+ Suppose your message `msg` consists of `n` capital letters, with no
+ spaces. This may be regarded an `n`-tuple M of elements of
+ `Z_{26}` (if the letters are those of the English alphabet). A key
+ in the Hill cipher is a `k x k` matrix `K`, all of whose entries
+ are in `Z_{26}`, such that the matrix `K` is invertible (i.e., the
+ linear transformation `K: Z_{N}^k \rightarrow Z_{N}^k`
+ is one-to-one).
+
+
+ Parameters
+ ==========
+
+ msg
+ Plaintext message of `n` upper-case letters.
+
+ key
+ A `k \times k` invertible matrix `K`, all of whose entries are
+ in `Z_{26}` (or whatever number of symbols are being used).
+
+ pad
+ Character (default "Q") to use to make length of text be a
+ multiple of ``k``.
+
+ Returns
+ =======
+
+ ct
+ Ciphertext of upper-case letters.
+
+ Notes
+ =====
+
+ ALGORITHM:
+
+ STEPS:
+ 0. Number the letters of the alphabet from 0, ..., N
+ 1. Compute from the string ``msg`` a list ``L`` of
+ corresponding integers. Let ``n = len(L)``.
+ 2. Break the list ``L`` up into ``t = ceiling(n/k)``
+ sublists ``L_1``, ..., ``L_t`` of size ``k`` (with
+ the last list "padded" to ensure its size is
+ ``k``).
+ 3. Compute new list ``C_1``, ..., ``C_t`` given by
+ ``C[i] = K*L_i`` (arithmetic is done mod N), for each
+ ``i``.
+ 4. Concatenate these into a list ``C = C_1 + ... + C_t``.
+ 5. Compute from ``C`` a string ``ct`` of corresponding
+ letters. This has length ``k*t``.
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Hill_cipher
+ .. [2] Lester S. Hill, Cryptography in an Algebraic Alphabet,
+ The American Mathematical Monthly Vol.36, June-July 1929,
+ pp.306-312.
+
+ See Also
+ ========
+
+ decipher_hill
+
+ """
+ assert key.is_square
+ assert len(pad) == 1
+ msg, pad, A = _prep(msg, pad, symbols)
+ map = {c: i for i, c in enumerate(A)}
+ P = [map[c] for c in msg]
+ N = len(A)
+ k = key.cols
+ n = len(P)
+ m, r = divmod(n, k)
+ if r:
+ P = P + [map[pad]]*(k - r)
+ m += 1
+ rv = ''.join([A[c % N] for j in range(m) for c in
+ list(key*Matrix(k, 1, [P[i]
+ for i in range(k*j, k*(j + 1))]))])
+ return rv
+
+
+def decipher_hill(msg, key, symbols=None):
+ """
+ Deciphering is the same as enciphering but using the inverse of the
+ key matrix.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_hill, decipher_hill
+ >>> from sympy import Matrix
+
+ >>> key = Matrix([[1, 2], [3, 5]])
+ >>> encipher_hill("meet me on monday", key)
+ 'UEQDUEODOCTCWQ'
+ >>> decipher_hill(_, key)
+ 'MEETMEONMONDAY'
+
+ When the length of the plaintext (stripped of invalid characters)
+ is not a multiple of the key dimension, extra characters will
+ appear at the end of the enciphered and deciphered text. In order to
+ decipher the text, those characters must be included in the text to
+ be deciphered. In the following, the key has a dimension of 4 but
+ the text is 2 short of being a multiple of 4 so two characters will
+ be added.
+
+ >>> key = Matrix([[1, 1, 1, 2], [0, 1, 1, 0],
+ ... [2, 2, 3, 4], [1, 1, 0, 1]])
+ >>> msg = "ST"
+ >>> encipher_hill(msg, key)
+ 'HJEB'
+ >>> decipher_hill(_, key)
+ 'STQQ'
+ >>> encipher_hill(msg, key, pad="Z")
+ 'ISPK'
+ >>> decipher_hill(_, key)
+ 'STZZ'
+
+ If the last two characters of the ciphertext were ignored in
+ either case, the wrong plaintext would be recovered:
+
+ >>> decipher_hill("HD", key)
+ 'ORMV'
+ >>> decipher_hill("IS", key)
+ 'UIKY'
+
+ See Also
+ ========
+
+ encipher_hill
+
+ """
+ assert key.is_square
+ msg, _, A = _prep(msg, '', symbols)
+ map = {c: i for i, c in enumerate(A)}
+ C = [map[c] for c in msg]
+ N = len(A)
+ k = key.cols
+ n = len(C)
+ m, r = divmod(n, k)
+ if r:
+ C = C + [0]*(k - r)
+ m += 1
+ key_inv = key.inv_mod(N)
+ rv = ''.join([A[p % N] for j in range(m) for p in
+ list(key_inv*Matrix(
+ k, 1, [C[i] for i in range(k*j, k*(j + 1))]))])
+ return rv
+
+
+#################### Bifid cipher ########################
+
+
+def encipher_bifid(msg, key, symbols=None):
+ r"""
+ Performs the Bifid cipher encryption on plaintext ``msg``, and
+ returns the ciphertext.
+
+ This is the version of the Bifid cipher that uses an `n \times n`
+ Polybius square.
+
+ Parameters
+ ==========
+
+ msg
+ Plaintext string.
+
+ key
+ Short string for key.
+
+ Duplicate characters are ignored and then it is padded with the
+ characters in ``symbols`` that were not in the short key.
+
+ symbols
+ `n \times n` characters defining the alphabet.
+
+ (default is string.printable)
+
+ Returns
+ =======
+
+ ciphertext
+ Ciphertext using Bifid5 cipher without spaces.
+
+ See Also
+ ========
+
+ decipher_bifid, encipher_bifid5, encipher_bifid6
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Bifid_cipher
+
+ """
+ msg, key, A = _prep(msg, key, symbols, bifid10)
+ long_key = ''.join(uniq(key)) or A
+
+ n = len(A)**.5
+ if n != int(n):
+ raise ValueError(
+ 'Length of alphabet (%s) is not a square number.' % len(A))
+ N = int(n)
+ if len(long_key) < N**2:
+ long_key = list(long_key) + [x for x in A if x not in long_key]
+
+ # the fractionalization
+ row_col = {ch: divmod(i, N) for i, ch in enumerate(long_key)}
+ r, c = zip(*[row_col[x] for x in msg])
+ rc = r + c
+ ch = {i: ch for ch, i in row_col.items()}
+ rv = ''.join(ch[i] for i in zip(rc[::2], rc[1::2]))
+ return rv
+
+
+def decipher_bifid(msg, key, symbols=None):
+ r"""
+ Performs the Bifid cipher decryption on ciphertext ``msg``, and
+ returns the plaintext.
+
+ This is the version of the Bifid cipher that uses the `n \times n`
+ Polybius square.
+
+ Parameters
+ ==========
+
+ msg
+ Ciphertext string.
+
+ key
+ Short string for key.
+
+ Duplicate characters are ignored and then it is padded with the
+ characters in symbols that were not in the short key.
+
+ symbols
+ `n \times n` characters defining the alphabet.
+
+ (default=string.printable, a `10 \times 10` matrix)
+
+ Returns
+ =======
+
+ deciphered
+ Deciphered text.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import (
+ ... encipher_bifid, decipher_bifid, AZ)
+
+ Do an encryption using the bifid5 alphabet:
+
+ >>> alp = AZ().replace('J', '')
+ >>> ct = AZ("meet me on monday!")
+ >>> key = AZ("gold bug")
+ >>> encipher_bifid(ct, key, alp)
+ 'IEILHHFSTSFQYE'
+
+ When entering the text or ciphertext, spaces are ignored so it
+ can be formatted as desired. Re-entering the ciphertext from the
+ preceding, putting 4 characters per line and padding with an extra
+ J, does not cause problems for the deciphering:
+
+ >>> decipher_bifid('''
+ ... IEILH
+ ... HFSTS
+ ... FQYEJ''', key, alp)
+ 'MEETMEONMONDAY'
+
+ When no alphabet is given, all 100 printable characters will be
+ used:
+
+ >>> key = ''
+ >>> encipher_bifid('hello world!', key)
+ 'bmtwmg-bIo*w'
+ >>> decipher_bifid(_, key)
+ 'hello world!'
+
+ If the key is changed, a different encryption is obtained:
+
+ >>> key = 'gold bug'
+ >>> encipher_bifid('hello world!', 'gold_bug')
+ 'hg2sfuei7t}w'
+
+ And if the key used to decrypt the message is not exact, the
+ original text will not be perfectly obtained:
+
+ >>> decipher_bifid(_, 'gold pug')
+ 'heldo~wor6d!'
+
+ """
+ msg, _, A = _prep(msg, '', symbols, bifid10)
+ long_key = ''.join(uniq(key)) or A
+
+ n = len(A)**.5
+ if n != int(n):
+ raise ValueError(
+ 'Length of alphabet (%s) is not a square number.' % len(A))
+ N = int(n)
+ if len(long_key) < N**2:
+ long_key = list(long_key) + [x for x in A if x not in long_key]
+
+ # the reverse fractionalization
+ row_col = {
+ ch: divmod(i, N) for i, ch in enumerate(long_key)}
+ rc = [i for c in msg for i in row_col[c]]
+ n = len(msg)
+ rc = zip(*(rc[:n], rc[n:]))
+ ch = {i: ch for ch, i in row_col.items()}
+ rv = ''.join(ch[i] for i in rc)
+ return rv
+
+
+def bifid_square(key):
+ """Return characters of ``key`` arranged in a square.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import (
+ ... bifid_square, AZ, padded_key, bifid5)
+ >>> bifid_square(AZ().replace('J', ''))
+ Matrix([
+ [A, B, C, D, E],
+ [F, G, H, I, K],
+ [L, M, N, O, P],
+ [Q, R, S, T, U],
+ [V, W, X, Y, Z]])
+
+ >>> bifid_square(padded_key(AZ('gold bug!'), bifid5))
+ Matrix([
+ [G, O, L, D, B],
+ [U, A, C, E, F],
+ [H, I, K, M, N],
+ [P, Q, R, S, T],
+ [V, W, X, Y, Z]])
+
+ See Also
+ ========
+
+ padded_key
+
+ """
+ A = ''.join(uniq(''.join(key)))
+ n = len(A)**.5
+ if n != int(n):
+ raise ValueError(
+ 'Length of alphabet (%s) is not a square number.' % len(A))
+ n = int(n)
+ f = lambda i, j: Symbol(A[n*i + j])
+ rv = Matrix(n, n, f)
+ return rv
+
+
+def encipher_bifid5(msg, key):
+ r"""
+ Performs the Bifid cipher encryption on plaintext ``msg``, and
+ returns the ciphertext.
+
+ Explanation
+ ===========
+
+ This is the version of the Bifid cipher that uses the `5 \times 5`
+ Polybius square. The letter "J" is ignored so it must be replaced
+ with something else (traditionally an "I") before encryption.
+
+ ALGORITHM: (5x5 case)
+
+ STEPS:
+ 0. Create the `5 \times 5` Polybius square ``S`` associated
+ to ``key`` as follows:
+
+ a) moving from left-to-right, top-to-bottom,
+ place the letters of the key into a `5 \times 5`
+ matrix,
+ b) if the key has less than 25 letters, add the
+ letters of the alphabet not in the key until the
+ `5 \times 5` square is filled.
+
+ 1. Create a list ``P`` of pairs of numbers which are the
+ coordinates in the Polybius square of the letters in
+ ``msg``.
+ 2. Let ``L1`` be the list of all first coordinates of ``P``
+ (length of ``L1 = n``), let ``L2`` be the list of all
+ second coordinates of ``P`` (so the length of ``L2``
+ is also ``n``).
+ 3. Let ``L`` be the concatenation of ``L1`` and ``L2``
+ (length ``L = 2*n``), except that consecutive numbers
+ are paired ``(L[2*i], L[2*i + 1])``. You can regard
+ ``L`` as a list of pairs of length ``n``.
+ 4. Let ``C`` be the list of all letters which are of the
+ form ``S[i, j]``, for all ``(i, j)`` in ``L``. As a
+ string, this is the ciphertext of ``msg``.
+
+ Parameters
+ ==========
+
+ msg : str
+ Plaintext string.
+
+ Converted to upper case and filtered of anything but all letters
+ except J.
+
+ key
+ Short string for key; non-alphabetic letters, J and duplicated
+ characters are ignored and then, if the length is less than 25
+ characters, it is padded with other letters of the alphabet
+ (in alphabetical order).
+
+ Returns
+ =======
+
+ ct
+ Ciphertext (all caps, no spaces).
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import (
+ ... encipher_bifid5, decipher_bifid5)
+
+ "J" will be omitted unless it is replaced with something else:
+
+ >>> round_trip = lambda m, k: \
+ ... decipher_bifid5(encipher_bifid5(m, k), k)
+ >>> key = 'a'
+ >>> msg = "JOSIE"
+ >>> round_trip(msg, key)
+ 'OSIE'
+ >>> round_trip(msg.replace("J", "I"), key)
+ 'IOSIE'
+ >>> j = "QIQ"
+ >>> round_trip(msg.replace("J", j), key).replace(j, "J")
+ 'JOSIE'
+
+
+ Notes
+ =====
+
+ The Bifid cipher was invented around 1901 by Felix Delastelle.
+ It is a *fractional substitution* cipher, where letters are
+ replaced by pairs of symbols from a smaller alphabet. The
+ cipher uses a `5 \times 5` square filled with some ordering of the
+ alphabet, except that "J" is replaced with "I" (this is a so-called
+ Polybius square; there is a `6 \times 6` analog if you add back in
+ "J" and also append onto the usual 26 letter alphabet, the digits
+ 0, 1, ..., 9).
+ According to Helen Gaines' book *Cryptanalysis*, this type of cipher
+ was used in the field by the German Army during World War I.
+
+ See Also
+ ========
+
+ decipher_bifid5, encipher_bifid
+
+ """
+ msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid5)
+ key = padded_key(key, bifid5)
+ return encipher_bifid(msg, '', key)
+
+
+def decipher_bifid5(msg, key):
+ r"""
+ Return the Bifid cipher decryption of ``msg``.
+
+ Explanation
+ ===========
+
+ This is the version of the Bifid cipher that uses the `5 \times 5`
+ Polybius square; the letter "J" is ignored unless a ``key`` of
+ length 25 is used.
+
+ Parameters
+ ==========
+
+ msg
+ Ciphertext string.
+
+ key
+ Short string for key; duplicated characters are ignored and if
+ the length is less then 25 characters, it will be padded with
+ other letters from the alphabet omitting "J".
+ Non-alphabetic characters are ignored.
+
+ Returns
+ =======
+
+ plaintext
+ Plaintext from Bifid5 cipher (all caps, no spaces).
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_bifid5, decipher_bifid5
+ >>> key = "gold bug"
+ >>> encipher_bifid5('meet me on friday', key)
+ 'IEILEHFSTSFXEE'
+ >>> encipher_bifid5('meet me on monday', key)
+ 'IEILHHFSTSFQYE'
+ >>> decipher_bifid5(_, key)
+ 'MEETMEONMONDAY'
+
+ """
+ msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid5)
+ key = padded_key(key, bifid5)
+ return decipher_bifid(msg, '', key)
+
+
+def bifid5_square(key=None):
+ r"""
+ 5x5 Polybius square.
+
+ Produce the Polybius square for the `5 \times 5` Bifid cipher.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import bifid5_square
+ >>> bifid5_square("gold bug")
+ Matrix([
+ [G, O, L, D, B],
+ [U, A, C, E, F],
+ [H, I, K, M, N],
+ [P, Q, R, S, T],
+ [V, W, X, Y, Z]])
+
+ """
+ if not key:
+ key = bifid5
+ else:
+ _, key, _ = _prep('', key.upper(), None, bifid5)
+ key = padded_key(key, bifid5)
+ return bifid_square(key)
+
+
+def encipher_bifid6(msg, key):
+ r"""
+ Performs the Bifid cipher encryption on plaintext ``msg``, and
+ returns the ciphertext.
+
+ This is the version of the Bifid cipher that uses the `6 \times 6`
+ Polybius square.
+
+ Parameters
+ ==========
+
+ msg
+ Plaintext string (digits okay).
+
+ key
+ Short string for key (digits okay).
+
+ If ``key`` is less than 36 characters long, the square will be
+ filled with letters A through Z and digits 0 through 9.
+
+ Returns
+ =======
+
+ ciphertext
+ Ciphertext from Bifid cipher (all caps, no spaces).
+
+ See Also
+ ========
+
+ decipher_bifid6, encipher_bifid
+
+ """
+ msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid6)
+ key = padded_key(key, bifid6)
+ return encipher_bifid(msg, '', key)
+
+
+def decipher_bifid6(msg, key):
+ r"""
+ Performs the Bifid cipher decryption on ciphertext ``msg``, and
+ returns the plaintext.
+
+ This is the version of the Bifid cipher that uses the `6 \times 6`
+ Polybius square.
+
+ Parameters
+ ==========
+
+ msg
+ Ciphertext string (digits okay); converted to upper case
+
+ key
+ Short string for key (digits okay).
+
+ If ``key`` is less than 36 characters long, the square will be
+ filled with letters A through Z and digits 0 through 9.
+ All letters are converted to uppercase.
+
+ Returns
+ =======
+
+ plaintext
+ Plaintext from Bifid cipher (all caps, no spaces).
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_bifid6, decipher_bifid6
+ >>> key = "gold bug"
+ >>> encipher_bifid6('meet me on monday at 8am', key)
+ 'KFKLJJHF5MMMKTFRGPL'
+ >>> decipher_bifid6(_, key)
+ 'MEETMEONMONDAYAT8AM'
+
+ """
+ msg, key, _ = _prep(msg.upper(), key.upper(), None, bifid6)
+ key = padded_key(key, bifid6)
+ return decipher_bifid(msg, '', key)
+
+
+def bifid6_square(key=None):
+ r"""
+ 6x6 Polybius square.
+
+ Produces the Polybius square for the `6 \times 6` Bifid cipher.
+ Assumes alphabet of symbols is "A", ..., "Z", "0", ..., "9".
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import bifid6_square
+ >>> key = "gold bug"
+ >>> bifid6_square(key)
+ Matrix([
+ [G, O, L, D, B, U],
+ [A, C, E, F, H, I],
+ [J, K, M, N, P, Q],
+ [R, S, T, V, W, X],
+ [Y, Z, 0, 1, 2, 3],
+ [4, 5, 6, 7, 8, 9]])
+
+ """
+ if not key:
+ key = bifid6
+ else:
+ _, key, _ = _prep('', key.upper(), None, bifid6)
+ key = padded_key(key, bifid6)
+ return bifid_square(key)
+
+
+#################### RSA #############################
+
+def _decipher_rsa_crt(i, d, factors):
+ """Decipher RSA using chinese remainder theorem from the information
+ of the relatively-prime factors of the modulus.
+
+ Parameters
+ ==========
+
+ i : integer
+ Ciphertext
+
+ d : integer
+ The exponent component.
+
+ factors : list of relatively-prime integers
+ The integers given must be coprime and the product must equal
+ the modulus component of the original RSA key.
+
+ Examples
+ ========
+
+ How to decrypt RSA with CRT:
+
+ >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key
+ >>> primes = [61, 53]
+ >>> e = 17
+ >>> args = primes + [e]
+ >>> puk = rsa_public_key(*args)
+ >>> prk = rsa_private_key(*args)
+
+ >>> from sympy.crypto.crypto import encipher_rsa, _decipher_rsa_crt
+ >>> msg = 65
+ >>> crt_primes = primes
+ >>> encrypted = encipher_rsa(msg, puk)
+ >>> decrypted = _decipher_rsa_crt(encrypted, prk[1], primes)
+ >>> decrypted
+ 65
+ """
+ moduluses = [pow(i, d, p) for p in factors]
+
+ result = crt(factors, moduluses)
+ if not result:
+ raise ValueError("CRT failed")
+ return result[0]
+
+
+def _rsa_key(*args, public=True, private=True, totient='Euler', index=None, multipower=None):
+ r"""A private subroutine to generate RSA key
+
+ Parameters
+ ==========
+
+ public, private : bool, optional
+ Flag to generate either a public key, a private key.
+
+ totient : 'Euler' or 'Carmichael'
+ Different notation used for totient.
+
+ multipower : bool, optional
+ Flag to bypass warning for multipower RSA.
+ """
+
+ if len(args) < 2:
+ return False
+
+ if totient not in ('Euler', 'Carmichael'):
+ raise ValueError(
+ "The argument totient={} should either be " \
+ "'Euler', 'Carmichalel'." \
+ .format(totient))
+
+ if totient == 'Euler':
+ _totient = _euler
+ else:
+ _totient = _carmichael
+
+ if index is not None:
+ index = as_int(index)
+ if totient != 'Carmichael':
+ raise ValueError(
+ "Setting the 'index' keyword argument requires totient"
+ "notation to be specified as 'Carmichael'.")
+
+ primes, e = args[:-1], args[-1]
+
+ if not all(isprime(p) for p in primes):
+ new_primes = []
+ for i in primes:
+ new_primes.extend(factorint(i, multiple=True))
+ primes = new_primes
+
+ n = reduce(lambda i, j: i*j, primes)
+
+ tally = multiset(primes)
+ if all(v == 1 for v in tally.values()):
+ phi = int(_totient(tally))
+
+ else:
+ if not multipower:
+ NonInvertibleCipherWarning(
+ 'Non-distinctive primes found in the factors {}. '
+ 'The cipher may not be decryptable for some numbers '
+ 'in the complete residue system Z[{}], but the cipher '
+ 'can still be valid if you restrict the domain to be '
+ 'the reduced residue system Z*[{}]. You can pass '
+ 'the flag multipower=True if you want to suppress this '
+ 'warning.'
+ .format(primes, n, n)
+ # stacklevel=4 because most users will call a function that
+ # calls this function
+ ).warn(stacklevel=4)
+ phi = int(_totient(tally))
+
+ if gcd(e, phi) == 1:
+ if public and not private:
+ if isinstance(index, int):
+ e = e % phi
+ e += index * phi
+ return n, e
+
+ if private and not public:
+ d = invert(e, phi)
+ if isinstance(index, int):
+ d += index * phi
+ return n, d
+
+ return False
+
+
+def rsa_public_key(*args, **kwargs):
+ r"""Return the RSA *public key* pair, `(n, e)`
+
+ Parameters
+ ==========
+
+ args : naturals
+ If specified as `p, q, e` where `p` and `q` are distinct primes
+ and `e` is a desired public exponent of the RSA, `n = p q` and
+ `e` will be verified against the totient
+ `\phi(n)` (Euler totient) or `\lambda(n)` (Carmichael totient)
+ to be `\gcd(e, \phi(n)) = 1` or `\gcd(e, \lambda(n)) = 1`.
+
+ If specified as `p_1, p_2, \dots, p_n, e` where
+ `p_1, p_2, \dots, p_n` are specified as primes,
+ and `e` is specified as a desired public exponent of the RSA,
+ it will be able to form a multi-prime RSA, which is a more
+ generalized form of the popular 2-prime RSA.
+
+ It can also be possible to form a single-prime RSA by specifying
+ the argument as `p, e`, which can be considered a trivial case
+ of a multiprime RSA.
+
+ Furthermore, it can be possible to form a multi-power RSA by
+ specifying two or more pairs of the primes to be same.
+ However, unlike the two-distinct prime RSA or multi-prime
+ RSA, not every numbers in the complete residue system
+ (`\mathbb{Z}_n`) will be decryptable since the mapping
+ `\mathbb{Z}_{n} \rightarrow \mathbb{Z}_{n}`
+ will not be bijective.
+ (Only except for the trivial case when
+ `e = 1`
+ or more generally,
+
+ .. math::
+ e \in \left \{ 1 + k \lambda(n)
+ \mid k \in \mathbb{Z} \land k \geq 0 \right \}
+
+ when RSA reduces to the identity.)
+ However, the RSA can still be decryptable for the numbers in the
+ reduced residue system (`\mathbb{Z}_n^{\times}`), since the
+ mapping
+ `\mathbb{Z}_{n}^{\times} \rightarrow \mathbb{Z}_{n}^{\times}`
+ can still be bijective.
+
+ If you pass a non-prime integer to the arguments
+ `p_1, p_2, \dots, p_n`, the particular number will be
+ prime-factored and it will become either a multi-prime RSA or a
+ multi-power RSA in its canonical form, depending on whether the
+ product equals its radical or not.
+ `p_1 p_2 \dots p_n = \text{rad}(p_1 p_2 \dots p_n)`
+
+ totient : bool, optional
+ If ``'Euler'``, it uses Euler's totient `\phi(n)` which is
+ :meth:`sympy.functions.combinatorial.numbers.totient` in SymPy.
+
+ If ``'Carmichael'``, it uses Carmichael's totient `\lambda(n)`
+ which is :meth:`sympy.functions.combinatorial.numbers.reduced_totient` in SymPy.
+
+ Unlike private key generation, this is a trivial keyword for
+ public key generation because
+ `\gcd(e, \phi(n)) = 1 \iff \gcd(e, \lambda(n)) = 1`.
+
+ index : nonnegative integer, optional
+ Returns an arbitrary solution of a RSA public key at the index
+ specified at `0, 1, 2, \dots`. This parameter needs to be
+ specified along with ``totient='Carmichael'``.
+
+ Similarly to the non-uniquenss of a RSA private key as described
+ in the ``index`` parameter documentation in
+ :meth:`rsa_private_key`, RSA public key is also not unique and
+ there is an infinite number of RSA public exponents which
+ can behave in the same manner.
+
+ From any given RSA public exponent `e`, there are can be an
+ another RSA public exponent `e + k \lambda(n)` where `k` is an
+ integer, `\lambda` is a Carmichael's totient function.
+
+ However, considering only the positive cases, there can be
+ a principal solution of a RSA public exponent `e_0` in
+ `0 < e_0 < \lambda(n)`, and all the other solutions
+ can be canonicalzed in a form of `e_0 + k \lambda(n)`.
+
+ ``index`` specifies the `k` notation to yield any possible value
+ an RSA public key can have.
+
+ An example of computing any arbitrary RSA public key:
+
+ >>> from sympy.crypto.crypto import rsa_public_key
+ >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=0)
+ (3233, 17)
+ >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=1)
+ (3233, 797)
+ >>> rsa_public_key(61, 53, 17, totient='Carmichael', index=2)
+ (3233, 1577)
+
+ multipower : bool, optional
+ Any pair of non-distinct primes found in the RSA specification
+ will restrict the domain of the cryptosystem, as noted in the
+ explanation of the parameter ``args``.
+
+ SymPy RSA key generator may give a warning before dispatching it
+ as a multi-power RSA, however, you can disable the warning if
+ you pass ``True`` to this keyword.
+
+ Returns
+ =======
+
+ (n, e) : int, int
+ `n` is a product of any arbitrary number of primes given as
+ the argument.
+
+ `e` is relatively prime (coprime) to the Euler totient
+ `\phi(n)`.
+
+ False
+ Returned if less than two arguments are given, or `e` is
+ not relatively prime to the modulus.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import rsa_public_key
+
+ A public key of a two-prime RSA:
+
+ >>> p, q, e = 3, 5, 7
+ >>> rsa_public_key(p, q, e)
+ (15, 7)
+ >>> rsa_public_key(p, q, 30)
+ False
+
+ A public key of a multiprime RSA:
+
+ >>> primes = [2, 3, 5, 7, 11, 13]
+ >>> e = 7
+ >>> args = primes + [e]
+ >>> rsa_public_key(*args)
+ (30030, 7)
+
+ Notes
+ =====
+
+ Although the RSA can be generalized over any modulus `n`, using
+ two large primes had became the most popular specification because a
+ product of two large primes is usually the hardest to factor
+ relatively to the digits of `n` can have.
+
+ However, it may need further understanding of the time complexities
+ of each prime-factoring algorithms to verify the claim.
+
+ See Also
+ ========
+
+ rsa_private_key
+ encipher_rsa
+ decipher_rsa
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29
+
+ .. [2] https://cacr.uwaterloo.ca/techreports/2006/cacr2006-16.pdf
+
+ .. [3] https://link.springer.com/content/pdf/10.1007/BFb0055738.pdf
+
+ .. [4] https://www.itiis.org/digital-library/manuscript/1381
+ """
+ return _rsa_key(*args, public=True, private=False, **kwargs)
+
+
+def rsa_private_key(*args, **kwargs):
+ r"""Return the RSA *private key* pair, `(n, d)`
+
+ Parameters
+ ==========
+
+ args : naturals
+ The keyword is identical to the ``args`` in
+ :meth:`rsa_public_key`.
+
+ totient : bool, optional
+ If ``'Euler'``, it uses Euler's totient convention `\phi(n)`
+ which is :meth:`sympy.functions.combinatorial.numbers.totient` in SymPy.
+
+ If ``'Carmichael'``, it uses Carmichael's totient convention
+ `\lambda(n)` which is
+ :meth:`sympy.functions.combinatorial.numbers.reduced_totient` in SymPy.
+
+ There can be some output differences for private key generation
+ as examples below.
+
+ Example using Euler's totient:
+
+ >>> from sympy.crypto.crypto import rsa_private_key
+ >>> rsa_private_key(61, 53, 17, totient='Euler')
+ (3233, 2753)
+
+ Example using Carmichael's totient:
+
+ >>> from sympy.crypto.crypto import rsa_private_key
+ >>> rsa_private_key(61, 53, 17, totient='Carmichael')
+ (3233, 413)
+
+ index : nonnegative integer, optional
+ Returns an arbitrary solution of a RSA private key at the index
+ specified at `0, 1, 2, \dots`. This parameter needs to be
+ specified along with ``totient='Carmichael'``.
+
+ RSA private exponent is a non-unique solution of
+ `e d \mod \lambda(n) = 1` and it is possible in any form of
+ `d + k \lambda(n)`, where `d` is an another
+ already-computed private exponent, and `\lambda` is a
+ Carmichael's totient function, and `k` is any integer.
+
+ However, considering only the positive cases, there can be
+ a principal solution of a RSA private exponent `d_0` in
+ `0 < d_0 < \lambda(n)`, and all the other solutions
+ can be canonicalzed in a form of `d_0 + k \lambda(n)`.
+
+ ``index`` specifies the `k` notation to yield any possible value
+ an RSA private key can have.
+
+ An example of computing any arbitrary RSA private key:
+
+ >>> from sympy.crypto.crypto import rsa_private_key
+ >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=0)
+ (3233, 413)
+ >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=1)
+ (3233, 1193)
+ >>> rsa_private_key(61, 53, 17, totient='Carmichael', index=2)
+ (3233, 1973)
+
+ multipower : bool, optional
+ The keyword is identical to the ``multipower`` in
+ :meth:`rsa_public_key`.
+
+ Returns
+ =======
+
+ (n, d) : int, int
+ `n` is a product of any arbitrary number of primes given as
+ the argument.
+
+ `d` is the inverse of `e` (mod `\phi(n)`) where `e` is the
+ exponent given, and `\phi` is a Euler totient.
+
+ False
+ Returned if less than two arguments are given, or `e` is
+ not relatively prime to the totient of the modulus.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import rsa_private_key
+
+ A private key of a two-prime RSA:
+
+ >>> p, q, e = 3, 5, 7
+ >>> rsa_private_key(p, q, e)
+ (15, 7)
+ >>> rsa_private_key(p, q, 30)
+ False
+
+ A private key of a multiprime RSA:
+
+ >>> primes = [2, 3, 5, 7, 11, 13]
+ >>> e = 7
+ >>> args = primes + [e]
+ >>> rsa_private_key(*args)
+ (30030, 823)
+
+ See Also
+ ========
+
+ rsa_public_key
+ encipher_rsa
+ decipher_rsa
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29
+
+ .. [2] https://cacr.uwaterloo.ca/techreports/2006/cacr2006-16.pdf
+
+ .. [3] https://link.springer.com/content/pdf/10.1007/BFb0055738.pdf
+
+ .. [4] https://www.itiis.org/digital-library/manuscript/1381
+ """
+ return _rsa_key(*args, public=False, private=True, **kwargs)
+
+
+def _encipher_decipher_rsa(i, key, factors=None):
+ n, d = key
+ if not factors:
+ return pow(i, d, n)
+
+ def _is_coprime_set(l):
+ is_coprime_set = True
+ for i in range(len(l)):
+ for j in range(i+1, len(l)):
+ if gcd(l[i], l[j]) != 1:
+ is_coprime_set = False
+ break
+ return is_coprime_set
+
+ prod = reduce(lambda i, j: i*j, factors)
+ if prod == n and _is_coprime_set(factors):
+ return _decipher_rsa_crt(i, d, factors)
+ return _encipher_decipher_rsa(i, key, factors=None)
+
+
+def encipher_rsa(i, key, factors=None):
+ r"""Encrypt the plaintext with RSA.
+
+ Parameters
+ ==========
+
+ i : integer
+ The plaintext to be encrypted for.
+
+ key : (n, e) where n, e are integers
+ `n` is the modulus of the key and `e` is the exponent of the
+ key. The encryption is computed by `i^e \bmod n`.
+
+ The key can either be a public key or a private key, however,
+ the message encrypted by a public key can only be decrypted by
+ a private key, and vice versa, as RSA is an asymmetric
+ cryptography system.
+
+ factors : list of coprime integers
+ This is identical to the keyword ``factors`` in
+ :meth:`decipher_rsa`.
+
+ Notes
+ =====
+
+ Some specifications may make the RSA not cryptographically
+ meaningful.
+
+ For example, `0`, `1` will remain always same after taking any
+ number of exponentiation, thus, should be avoided.
+
+ Furthermore, if `i^e < n`, `i` may easily be figured out by taking
+ `e` th root.
+
+ And also, specifying the exponent as `1` or in more generalized form
+ as `1 + k \lambda(n)` where `k` is an nonnegative integer,
+ `\lambda` is a carmichael totient, the RSA becomes an identity
+ mapping.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_rsa
+ >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key
+
+ Public Key Encryption:
+
+ >>> p, q, e = 3, 5, 7
+ >>> puk = rsa_public_key(p, q, e)
+ >>> msg = 12
+ >>> encipher_rsa(msg, puk)
+ 3
+
+ Private Key Encryption:
+
+ >>> p, q, e = 3, 5, 7
+ >>> prk = rsa_private_key(p, q, e)
+ >>> msg = 12
+ >>> encipher_rsa(msg, prk)
+ 3
+
+ Encryption using chinese remainder theorem:
+
+ >>> encipher_rsa(msg, prk, factors=[p, q])
+ 3
+ """
+ return _encipher_decipher_rsa(i, key, factors=factors)
+
+
+def decipher_rsa(i, key, factors=None):
+ r"""Decrypt the ciphertext with RSA.
+
+ Parameters
+ ==========
+
+ i : integer
+ The ciphertext to be decrypted for.
+
+ key : (n, d) where n, d are integers
+ `n` is the modulus of the key and `d` is the exponent of the
+ key. The decryption is computed by `i^d \bmod n`.
+
+ The key can either be a public key or a private key, however,
+ the message encrypted by a public key can only be decrypted by
+ a private key, and vice versa, as RSA is an asymmetric
+ cryptography system.
+
+ factors : list of coprime integers
+ As the modulus `n` created from RSA key generation is composed
+ of arbitrary prime factors
+ `n = {p_1}^{k_1}{p_2}^{k_2}\dots{p_n}^{k_n}` where
+ `p_1, p_2, \dots, p_n` are distinct primes and
+ `k_1, k_2, \dots, k_n` are positive integers, chinese remainder
+ theorem can be used to compute `i^d \bmod n` from the
+ fragmented modulo operations like
+
+ .. math::
+ i^d \bmod {p_1}^{k_1}, i^d \bmod {p_2}^{k_2}, \dots,
+ i^d \bmod {p_n}^{k_n}
+
+ or like
+
+ .. math::
+ i^d \bmod {p_1}^{k_1}{p_2}^{k_2},
+ i^d \bmod {p_3}^{k_3}, \dots ,
+ i^d \bmod {p_n}^{k_n}
+
+ as long as every moduli does not share any common divisor each
+ other.
+
+ The raw primes used in generating the RSA key pair can be a good
+ option.
+
+ Note that the speed advantage of using this is only viable for
+ very large cases (Like 2048-bit RSA keys) since the
+ overhead of using pure Python implementation of
+ :meth:`sympy.ntheory.modular.crt` may overcompensate the
+ theoretical speed advantage.
+
+ Notes
+ =====
+
+ See the ``Notes`` section in the documentation of
+ :meth:`encipher_rsa`
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import decipher_rsa, encipher_rsa
+ >>> from sympy.crypto.crypto import rsa_public_key, rsa_private_key
+
+ Public Key Encryption and Decryption:
+
+ >>> p, q, e = 3, 5, 7
+ >>> prk = rsa_private_key(p, q, e)
+ >>> puk = rsa_public_key(p, q, e)
+ >>> msg = 12
+ >>> new_msg = encipher_rsa(msg, prk)
+ >>> new_msg
+ 3
+ >>> decipher_rsa(new_msg, puk)
+ 12
+
+ Private Key Encryption and Decryption:
+
+ >>> p, q, e = 3, 5, 7
+ >>> prk = rsa_private_key(p, q, e)
+ >>> puk = rsa_public_key(p, q, e)
+ >>> msg = 12
+ >>> new_msg = encipher_rsa(msg, puk)
+ >>> new_msg
+ 3
+ >>> decipher_rsa(new_msg, prk)
+ 12
+
+ Decryption using chinese remainder theorem:
+
+ >>> decipher_rsa(new_msg, prk, factors=[p, q])
+ 12
+
+ See Also
+ ========
+
+ encipher_rsa
+ """
+ return _encipher_decipher_rsa(i, key, factors=factors)
+
+
+#################### kid krypto (kid RSA) #############################
+
+
+def kid_rsa_public_key(a, b, A, B):
+ r"""
+ Kid RSA is a version of RSA useful to teach grade school children
+ since it does not involve exponentiation.
+
+ Explanation
+ ===========
+
+ Alice wants to talk to Bob. Bob generates keys as follows.
+ Key generation:
+
+ * Select positive integers `a, b, A, B` at random.
+ * Compute `M = a b - 1`, `e = A M + a`, `d = B M + b`,
+ `n = (e d - 1)//M`.
+ * The *public key* is `(n, e)`. Bob sends these to Alice.
+ * The *private key* is `(n, d)`, which Bob keeps secret.
+
+ Encryption: If `p` is the plaintext message then the
+ ciphertext is `c = p e \pmod n`.
+
+ Decryption: If `c` is the ciphertext message then the
+ plaintext is `p = c d \pmod n`.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import kid_rsa_public_key
+ >>> a, b, A, B = 3, 4, 5, 6
+ >>> kid_rsa_public_key(a, b, A, B)
+ (369, 58)
+
+ """
+ M = a*b - 1
+ e = A*M + a
+ d = B*M + b
+ n = (e*d - 1)//M
+ return n, e
+
+
+def kid_rsa_private_key(a, b, A, B):
+ """
+ Compute `M = a b - 1`, `e = A M + a`, `d = B M + b`,
+ `n = (e d - 1) / M`. The *private key* is `d`, which Bob
+ keeps secret.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import kid_rsa_private_key
+ >>> a, b, A, B = 3, 4, 5, 6
+ >>> kid_rsa_private_key(a, b, A, B)
+ (369, 70)
+
+ """
+ M = a*b - 1
+ e = A*M + a
+ d = B*M + b
+ n = (e*d - 1)//M
+ return n, d
+
+
+def encipher_kid_rsa(msg, key):
+ """
+ Here ``msg`` is the plaintext and ``key`` is the public key.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import (
+ ... encipher_kid_rsa, kid_rsa_public_key)
+ >>> msg = 200
+ >>> a, b, A, B = 3, 4, 5, 6
+ >>> key = kid_rsa_public_key(a, b, A, B)
+ >>> encipher_kid_rsa(msg, key)
+ 161
+
+ """
+ n, e = key
+ return (msg*e) % n
+
+
+def decipher_kid_rsa(msg, key):
+ """
+ Here ``msg`` is the plaintext and ``key`` is the private key.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import (
+ ... kid_rsa_public_key, kid_rsa_private_key,
+ ... decipher_kid_rsa, encipher_kid_rsa)
+ >>> a, b, A, B = 3, 4, 5, 6
+ >>> d = kid_rsa_private_key(a, b, A, B)
+ >>> msg = 200
+ >>> pub = kid_rsa_public_key(a, b, A, B)
+ >>> pri = kid_rsa_private_key(a, b, A, B)
+ >>> ct = encipher_kid_rsa(msg, pub)
+ >>> decipher_kid_rsa(ct, pri)
+ 200
+
+ """
+ n, d = key
+ return (msg*d) % n
+
+
+#################### Morse Code ######################################
+
+morse_char = {
+ ".-": "A", "-...": "B",
+ "-.-.": "C", "-..": "D",
+ ".": "E", "..-.": "F",
+ "--.": "G", "....": "H",
+ "..": "I", ".---": "J",
+ "-.-": "K", ".-..": "L",
+ "--": "M", "-.": "N",
+ "---": "O", ".--.": "P",
+ "--.-": "Q", ".-.": "R",
+ "...": "S", "-": "T",
+ "..-": "U", "...-": "V",
+ ".--": "W", "-..-": "X",
+ "-.--": "Y", "--..": "Z",
+ "-----": "0", ".----": "1",
+ "..---": "2", "...--": "3",
+ "....-": "4", ".....": "5",
+ "-....": "6", "--...": "7",
+ "---..": "8", "----.": "9",
+ ".-.-.-": ".", "--..--": ",",
+ "---...": ":", "-.-.-.": ";",
+ "..--..": "?", "-....-": "-",
+ "..--.-": "_", "-.--.": "(",
+ "-.--.-": ")", ".----.": "'",
+ "-...-": "=", ".-.-.": "+",
+ "-..-.": "/", ".--.-.": "@",
+ "...-..-": "$", "-.-.--": "!"}
+char_morse = {v: k for k, v in morse_char.items()}
+
+
+def encode_morse(msg, sep='|', mapping=None):
+ """
+ Encodes a plaintext into popular Morse Code with letters
+ separated by ``sep`` and words by a double ``sep``.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encode_morse
+ >>> msg = 'ATTACK RIGHT FLANK'
+ >>> encode_morse(msg)
+ '.-|-|-|.-|-.-.|-.-||.-.|..|--.|....|-||..-.|.-..|.-|-.|-.-'
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Morse_code
+
+ """
+
+ mapping = mapping or char_morse
+ assert sep not in mapping
+ word_sep = 2*sep
+ mapping[" "] = word_sep
+ suffix = msg and msg[-1] in whitespace
+
+ # normalize whitespace
+ msg = (' ' if word_sep else '').join(msg.split())
+ # omit unmapped chars
+ chars = set(''.join(msg.split()))
+ ok = set(mapping.keys())
+ msg = translate(msg, None, ''.join(chars - ok))
+
+ morsestring = []
+ words = msg.split()
+ for word in words:
+ morseword = []
+ for letter in word:
+ morseletter = mapping[letter]
+ morseword.append(morseletter)
+
+ word = sep.join(morseword)
+ morsestring.append(word)
+
+ return word_sep.join(morsestring) + (word_sep if suffix else '')
+
+
+def decode_morse(msg, sep='|', mapping=None):
+ """
+ Decodes a Morse Code with letters separated by ``sep``
+ (default is '|') and words by `word_sep` (default is '||)
+ into plaintext.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import decode_morse
+ >>> mc = '--|---|...-|.||.|.-|...|-'
+ >>> decode_morse(mc)
+ 'MOVE EAST'
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Morse_code
+
+ """
+
+ mapping = mapping or morse_char
+ word_sep = 2*sep
+ characterstring = []
+ words = msg.strip(word_sep).split(word_sep)
+ for word in words:
+ letters = word.split(sep)
+ chars = [mapping[c] for c in letters]
+ word = ''.join(chars)
+ characterstring.append(word)
+ rv = " ".join(characterstring)
+ return rv
+
+
+#################### LFSRs ##########################################
+
+
+@doctest_depends_on(ground_types=['python', 'gmpy'])
+def lfsr_sequence(key, fill, n):
+ r"""
+ This function creates an LFSR sequence.
+
+ Parameters
+ ==========
+
+ key : list
+ A list of finite field elements, `[c_0, c_1, \ldots, c_k].`
+
+ fill : list
+ The list of the initial terms of the LFSR sequence,
+ `[x_0, x_1, \ldots, x_k].`
+
+ n
+ Number of terms of the sequence that the function returns.
+
+ Returns
+ =======
+
+ L
+ The LFSR sequence defined by
+ `x_{n+1} = c_k x_n + \ldots + c_0 x_{n-k}`, for
+ `n \leq k`.
+
+ Notes
+ =====
+
+ S. Golomb [G]_ gives a list of three statistical properties a
+ sequence of numbers `a = \{a_n\}_{n=1}^\infty`,
+ `a_n \in \{0,1\}`, should display to be considered
+ "random". Define the autocorrelation of `a` to be
+
+ .. math::
+
+ C(k) = C(k,a) = \lim_{N\rightarrow \infty} {1\over N}\sum_{n=1}^N (-1)^{a_n + a_{n+k}}.
+
+ In the case where `a` is periodic with period
+ `P` then this reduces to
+
+ .. math::
+
+ C(k) = {1\over P}\sum_{n=1}^P (-1)^{a_n + a_{n+k}}.
+
+ Assume `a` is periodic with period `P`.
+
+ - balance:
+
+ .. math::
+
+ \left|\sum_{n=1}^P(-1)^{a_n}\right| \leq 1.
+
+ - low autocorrelation:
+
+ .. math::
+
+ C(k) = \left\{ \begin{array}{cc} 1,& k = 0,\\ \epsilon, & k \ne 0. \end{array} \right.
+
+ (For sequences satisfying these first two properties, it is known
+ that `\epsilon = -1/P` must hold.)
+
+ - proportional runs property: In each period, half the runs have
+ length `1`, one-fourth have length `2`, etc.
+ Moreover, there are as many runs of `1`'s as there are of
+ `0`'s.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import lfsr_sequence
+ >>> from sympy.polys.domains import FF
+ >>> F = FF(2)
+ >>> fill = [F(1), F(1), F(0), F(1)]
+ >>> key = [F(1), F(0), F(0), F(1)]
+ >>> lfsr_sequence(key, fill, 10)
+ [1 mod 2, 1 mod 2, 0 mod 2, 1 mod 2, 0 mod 2,
+ 1 mod 2, 1 mod 2, 0 mod 2, 0 mod 2, 1 mod 2]
+
+ References
+ ==========
+
+ .. [G] Solomon Golomb, Shift register sequences, Aegean Park Press,
+ Laguna Hills, Ca, 1967
+
+ """
+ if not isinstance(key, list):
+ raise TypeError("key must be a list")
+ if not isinstance(fill, list):
+ raise TypeError("fill must be a list")
+ p = key[0].modulus()
+ F = FF(p)
+ s = fill
+ k = len(fill)
+ L = []
+ for i in range(n):
+ s0 = s[:]
+ L.append(s[0])
+ s = s[1:k]
+ x = sum(int(key[i]*s0[i]) for i in range(k))
+ s.append(F(x))
+ return L # use [int(x) for x in L] for int version
+
+
+def lfsr_autocorrelation(L, P, k):
+ """
+ This function computes the LFSR autocorrelation function.
+
+ Parameters
+ ==========
+
+ L
+ A periodic sequence of elements of `GF(2)`.
+ L must have length larger than P.
+
+ P
+ The period of L.
+
+ k : int
+ An integer `k` (`0 < k < P`).
+
+ Returns
+ =======
+
+ autocorrelation
+ The k-th value of the autocorrelation of the LFSR L.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import (
+ ... lfsr_sequence, lfsr_autocorrelation)
+ >>> from sympy.polys.domains import FF
+ >>> F = FF(2)
+ >>> fill = [F(1), F(1), F(0), F(1)]
+ >>> key = [F(1), F(0), F(0), F(1)]
+ >>> s = lfsr_sequence(key, fill, 20)
+ >>> lfsr_autocorrelation(s, 15, 7)
+ -1/15
+ >>> lfsr_autocorrelation(s, 15, 0)
+ 1
+
+ """
+ if not isinstance(L, list):
+ raise TypeError("L (=%s) must be a list" % L)
+ P = int(P)
+ k = int(k)
+ L0 = L[:P] # slices makes a copy
+ L1 = L0 + L0[:k]
+ L2 = [(-1)**(int(L1[i]) + int(L1[i + k])) for i in range(P)]
+ tot = sum(L2)
+ return Rational(tot, P)
+
+
+def lfsr_connection_polynomial(s):
+ """
+ This function computes the LFSR connection polynomial.
+
+ Parameters
+ ==========
+
+ s
+ A sequence of elements of even length, with entries in a finite
+ field.
+
+ Returns
+ =======
+
+ C(x)
+ The connection polynomial of a minimal LFSR yielding s.
+
+ This implements the algorithm in section 3 of J. L. Massey's
+ article [M]_.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import (
+ ... lfsr_sequence, lfsr_connection_polynomial)
+ >>> from sympy.polys.domains import FF
+ >>> F = FF(2)
+ >>> fill = [F(1), F(1), F(0), F(1)]
+ >>> key = [F(1), F(0), F(0), F(1)]
+ >>> s = lfsr_sequence(key, fill, 20)
+ >>> lfsr_connection_polynomial(s)
+ x**4 + x + 1
+ >>> fill = [F(1), F(0), F(0), F(1)]
+ >>> key = [F(1), F(1), F(0), F(1)]
+ >>> s = lfsr_sequence(key, fill, 20)
+ >>> lfsr_connection_polynomial(s)
+ x**3 + 1
+ >>> fill = [F(1), F(0), F(1)]
+ >>> key = [F(1), F(1), F(0)]
+ >>> s = lfsr_sequence(key, fill, 20)
+ >>> lfsr_connection_polynomial(s)
+ x**3 + x**2 + 1
+ >>> fill = [F(1), F(0), F(1)]
+ >>> key = [F(1), F(0), F(1)]
+ >>> s = lfsr_sequence(key, fill, 20)
+ >>> lfsr_connection_polynomial(s)
+ x**3 + x + 1
+
+ References
+ ==========
+
+ .. [M] James L. Massey, "Shift-Register Synthesis and BCH Decoding."
+ IEEE Trans. on Information Theory, vol. 15(1), pp. 122-127,
+ Jan 1969.
+
+ """
+ # Initialization:
+ p = s[0].modulus()
+ x = Symbol("x")
+ C = 1*x**0
+ B = 1*x**0
+ m = 1
+ b = 1*x**0
+ L = 0
+ N = 0
+ while N < len(s):
+ if L > 0:
+ dC = Poly(C).degree()
+ r = min(L + 1, dC + 1)
+ coeffsC = [C.subs(x, 0)] + [C.coeff(x**i)
+ for i in range(1, dC + 1)]
+ d = (int(s[N]) + sum(coeffsC[i]*int(s[N - i])
+ for i in range(1, r))) % p
+ if L == 0:
+ d = int(s[N])*x**0
+ if d == 0:
+ m += 1
+ N += 1
+ if d > 0:
+ if 2*L > N:
+ C = (C - d*((b**(p - 2)) % p)*x**m*B).expand()
+ m += 1
+ N += 1
+ else:
+ T = C
+ C = (C - d*((b**(p - 2)) % p)*x**m*B).expand()
+ L = N + 1 - L
+ m = 1
+ b = d
+ B = T
+ N += 1
+ dC = Poly(C).degree()
+ coeffsC = [C.subs(x, 0)] + [C.coeff(x**i) for i in range(1, dC + 1)]
+ return sum(coeffsC[i] % p*x**i for i in range(dC + 1)
+ if coeffsC[i] is not None)
+
+
+#################### ElGamal #############################
+
+
+def elgamal_private_key(digit=10, seed=None):
+ r"""
+ Return three number tuple as private key.
+
+ Explanation
+ ===========
+
+ Elgamal encryption is based on the mathematical problem
+ called the Discrete Logarithm Problem (DLP). For example,
+
+ `a^{b} \equiv c \pmod p`
+
+ In general, if ``a`` and ``b`` are known, ``ct`` is easily
+ calculated. If ``b`` is unknown, it is hard to use
+ ``a`` and ``ct`` to get ``b``.
+
+ Parameters
+ ==========
+
+ digit : int
+ Minimum number of binary digits for key.
+
+ Returns
+ =======
+
+ tuple : (p, r, d)
+ p = prime number.
+
+ r = primitive root.
+
+ d = random number.
+
+ Notes
+ =====
+
+ For testing purposes, the ``seed`` parameter may be set to control
+ the output of this routine. See sympy.core.random._randrange.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import elgamal_private_key
+ >>> from sympy.ntheory import is_primitive_root, isprime
+ >>> a, b, _ = elgamal_private_key()
+ >>> isprime(a)
+ True
+ >>> is_primitive_root(b, a)
+ True
+
+ """
+ randrange = _randrange(seed)
+ p = nextprime(2**digit)
+ return p, primitive_root(p), randrange(2, p)
+
+
+def elgamal_public_key(key):
+ r"""
+ Return three number tuple as public key.
+
+ Parameters
+ ==========
+
+ key : (p, r, e)
+ Tuple generated by ``elgamal_private_key``.
+
+ Returns
+ =======
+
+ tuple : (p, r, e)
+ `e = r**d \bmod p`
+
+ `d` is a random number in private key.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import elgamal_public_key
+ >>> elgamal_public_key((1031, 14, 636))
+ (1031, 14, 212)
+
+ """
+ p, r, e = key
+ return p, r, pow(r, e, p)
+
+
+def encipher_elgamal(i, key, seed=None):
+ r"""
+ Encrypt message with public key.
+
+ Explanation
+ ===========
+
+ ``i`` is a plaintext message expressed as an integer.
+ ``key`` is public key (p, r, e). In order to encrypt
+ a message, a random number ``a`` in ``range(2, p)``
+ is generated and the encryped message is returned as
+ `c_{1}` and `c_{2}` where:
+
+ `c_{1} \equiv r^{a} \pmod p`
+
+ `c_{2} \equiv m e^{a} \pmod p`
+
+ Parameters
+ ==========
+
+ msg
+ int of encoded message.
+
+ key
+ Public key.
+
+ Returns
+ =======
+
+ tuple : (c1, c2)
+ Encipher into two number.
+
+ Notes
+ =====
+
+ For testing purposes, the ``seed`` parameter may be set to control
+ the output of this routine. See sympy.core.random._randrange.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_elgamal, elgamal_private_key, elgamal_public_key
+ >>> pri = elgamal_private_key(5, seed=[3]); pri
+ (37, 2, 3)
+ >>> pub = elgamal_public_key(pri); pub
+ (37, 2, 8)
+ >>> msg = 36
+ >>> encipher_elgamal(msg, pub, seed=[3])
+ (8, 6)
+
+ """
+ p, r, e = key
+ if i < 0 or i >= p:
+ raise ValueError(
+ 'Message (%s) should be in range(%s)' % (i, p))
+ randrange = _randrange(seed)
+ a = randrange(2, p)
+ return pow(r, a, p), i*pow(e, a, p) % p
+
+
+def decipher_elgamal(msg, key):
+ r"""
+ Decrypt message with private key.
+
+ `msg = (c_{1}, c_{2})`
+
+ `key = (p, r, d)`
+
+ According to extended Eucliden theorem,
+ `u c_{1}^{d} + p n = 1`
+
+ `u \equiv 1/{{c_{1}}^d} \pmod p`
+
+ `u c_{2} \equiv \frac{1}{c_{1}^d} c_{2} \equiv \frac{1}{r^{ad}} c_{2} \pmod p`
+
+ `\frac{1}{r^{ad}} m e^a \equiv \frac{1}{r^{ad}} m {r^{d a}} \equiv m \pmod p`
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import decipher_elgamal
+ >>> from sympy.crypto.crypto import encipher_elgamal
+ >>> from sympy.crypto.crypto import elgamal_private_key
+ >>> from sympy.crypto.crypto import elgamal_public_key
+
+ >>> pri = elgamal_private_key(5, seed=[3])
+ >>> pub = elgamal_public_key(pri); pub
+ (37, 2, 8)
+ >>> msg = 17
+ >>> decipher_elgamal(encipher_elgamal(msg, pub), pri) == msg
+ True
+
+ """
+ p, _, d = key
+ c1, c2 = msg
+ u = pow(c1, -d, p)
+ return u * c2 % p
+
+
+################ Diffie-Hellman Key Exchange #########################
+
+def dh_private_key(digit=10, seed=None):
+ r"""
+ Return three integer tuple as private key.
+
+ Explanation
+ ===========
+
+ Diffie-Hellman key exchange is based on the mathematical problem
+ called the Discrete Logarithm Problem (see ElGamal).
+
+ Diffie-Hellman key exchange is divided into the following steps:
+
+ * Alice and Bob agree on a base that consist of a prime ``p``
+ and a primitive root of ``p`` called ``g``
+ * Alice choses a number ``a`` and Bob choses a number ``b`` where
+ ``a`` and ``b`` are random numbers in range `[2, p)`. These are
+ their private keys.
+ * Alice then publicly sends Bob `g^{a} \pmod p` while Bob sends
+ Alice `g^{b} \pmod p`
+ * They both raise the received value to their secretly chosen
+ number (``a`` or ``b``) and now have both as their shared key
+ `g^{ab} \pmod p`
+
+ Parameters
+ ==========
+
+ digit
+ Minimum number of binary digits required in key.
+
+ Returns
+ =======
+
+ tuple : (p, g, a)
+ p = prime number.
+
+ g = primitive root of p.
+
+ a = random number from 2 through p - 1.
+
+ Notes
+ =====
+
+ For testing purposes, the ``seed`` parameter may be set to control
+ the output of this routine. See sympy.core.random._randrange.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import dh_private_key
+ >>> from sympy.ntheory import isprime, is_primitive_root
+ >>> p, g, _ = dh_private_key()
+ >>> isprime(p)
+ True
+ >>> is_primitive_root(g, p)
+ True
+ >>> p, g, _ = dh_private_key(5)
+ >>> isprime(p)
+ True
+ >>> is_primitive_root(g, p)
+ True
+
+ """
+ p = nextprime(2**digit)
+ g = primitive_root(p)
+ randrange = _randrange(seed)
+ a = randrange(2, p)
+ return p, g, a
+
+
+def dh_public_key(key):
+ r"""
+ Return three number tuple as public key.
+
+ This is the tuple that Alice sends to Bob.
+
+ Parameters
+ ==========
+
+ key : (p, g, a)
+ A tuple generated by ``dh_private_key``.
+
+ Returns
+ =======
+
+ tuple : int, int, int
+ A tuple of `(p, g, g^a \mod p)` with `p`, `g` and `a` given as
+ parameters.s
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import dh_private_key, dh_public_key
+ >>> p, g, a = dh_private_key();
+ >>> _p, _g, x = dh_public_key((p, g, a))
+ >>> p == _p and g == _g
+ True
+ >>> x == pow(g, a, p)
+ True
+
+ """
+ p, g, a = key
+ return p, g, pow(g, a, p)
+
+
+def dh_shared_key(key, b):
+ """
+ Return an integer that is the shared key.
+
+ This is what Bob and Alice can both calculate using the public
+ keys they received from each other and their private keys.
+
+ Parameters
+ ==========
+
+ key : (p, g, x)
+ Tuple `(p, g, x)` generated by ``dh_public_key``.
+
+ b
+ Random number in the range of `2` to `p - 1`
+ (Chosen by second key exchange member (Bob)).
+
+ Returns
+ =======
+
+ int
+ A shared key.
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import (
+ ... dh_private_key, dh_public_key, dh_shared_key)
+ >>> prk = dh_private_key();
+ >>> p, g, x = dh_public_key(prk);
+ >>> sk = dh_shared_key((p, g, x), 1000)
+ >>> sk == pow(x, 1000, p)
+ True
+
+ """
+ p, _, x = key
+ if 1 >= b or b >= p:
+ raise ValueError(filldedent('''
+ Value of b should be greater 1 and less
+ than prime %s.''' % p))
+
+ return pow(x, b, p)
+
+
+################ Goldwasser-Micali Encryption #########################
+
+
+def _legendre(a, p):
+ """
+ Returns the legendre symbol of a and p
+ assuming that p is a prime.
+
+ i.e. 1 if a is a quadratic residue mod p
+ -1 if a is not a quadratic residue mod p
+ 0 if a is divisible by p
+
+ Parameters
+ ==========
+
+ a : int
+ The number to test.
+
+ p : prime
+ The prime to test ``a`` against.
+
+ Returns
+ =======
+
+ int
+ Legendre symbol (a / p).
+
+ """
+ sig = pow(a, (p - 1)//2, p)
+ if sig == 1:
+ return 1
+ elif sig == 0:
+ return 0
+ else:
+ return -1
+
+
+def _random_coprime_stream(n, seed=None):
+ randrange = _randrange(seed)
+ while True:
+ y = randrange(n)
+ if gcd(y, n) == 1:
+ yield y
+
+
+def gm_private_key(p, q, a=None):
+ r"""
+ Check if ``p`` and ``q`` can be used as private keys for
+ the Goldwasser-Micali encryption. The method works
+ roughly as follows.
+
+ Explanation
+ ===========
+
+ #. Pick two large primes $p$ and $q$.
+ #. Call their product $N$.
+ #. Given a message as an integer $i$, write $i$ in its bit representation $b_0, \dots, b_n$.
+ #. For each $k$,
+
+ if $b_k = 0$:
+ let $a_k$ be a random square
+ (quadratic residue) modulo $p q$
+ such that ``jacobi_symbol(a, p*q) = 1``
+ if $b_k = 1$:
+ let $a_k$ be a random non-square
+ (non-quadratic residue) modulo $p q$
+ such that ``jacobi_symbol(a, p*q) = 1``
+
+ returns $\left[a_1, a_2, \dots\right]$
+
+ $b_k$ can be recovered by checking whether or not
+ $a_k$ is a residue. And from the $b_k$'s, the message
+ can be reconstructed.
+
+ The idea is that, while ``jacobi_symbol(a, p*q)``
+ can be easily computed (and when it is equal to $-1$ will
+ tell you that $a$ is not a square mod $p q$), quadratic
+ residuosity modulo a composite number is hard to compute
+ without knowing its factorization.
+
+ Moreover, approximately half the numbers coprime to $p q$ have
+ :func:`~.jacobi_symbol` equal to $1$ . And among those, approximately half
+ are residues and approximately half are not. This maximizes the
+ entropy of the code.
+
+ Parameters
+ ==========
+
+ p, q, a
+ Initialization variables.
+
+ Returns
+ =======
+
+ tuple : (p, q)
+ The input value ``p`` and ``q``.
+
+ Raises
+ ======
+
+ ValueError
+ If ``p`` and ``q`` are not distinct odd primes.
+
+ """
+ if p == q:
+ raise ValueError("expected distinct primes, "
+ "got two copies of %i" % p)
+ elif not isprime(p) or not isprime(q):
+ raise ValueError("first two arguments must be prime, "
+ "got %i of %i" % (p, q))
+ elif p == 2 or q == 2:
+ raise ValueError("first two arguments must not be even, "
+ "got %i of %i" % (p, q))
+ return p, q
+
+
+def gm_public_key(p, q, a=None, seed=None):
+ """
+ Compute public keys for ``p`` and ``q``.
+ Note that in Goldwasser-Micali Encryption,
+ public keys are randomly selected.
+
+ Parameters
+ ==========
+
+ p, q, a : int, int, int
+ Initialization variables.
+
+ Returns
+ =======
+
+ tuple : (a, N)
+ ``a`` is the input ``a`` if it is not ``None`` otherwise
+ some random integer coprime to ``p`` and ``q``.
+
+ ``N`` is the product of ``p`` and ``q``.
+
+ """
+
+ p, q = gm_private_key(p, q)
+ N = p * q
+
+ if a is None:
+ randrange = _randrange(seed)
+ while True:
+ a = randrange(N)
+ if _legendre(a, p) == _legendre(a, q) == -1:
+ break
+ else:
+ if _legendre(a, p) != -1 or _legendre(a, q) != -1:
+ return False
+ return (a, N)
+
+
+def encipher_gm(i, key, seed=None):
+ """
+ Encrypt integer 'i' using public_key 'key'
+ Note that gm uses random encryption.
+
+ Parameters
+ ==========
+
+ i : int
+ The message to encrypt.
+
+ key : (a, N)
+ The public key.
+
+ Returns
+ =======
+
+ list : list of int
+ The randomized encrypted message.
+
+ """
+ if i < 0:
+ raise ValueError(
+ "message must be a non-negative "
+ "integer: got %d instead" % i)
+ a, N = key
+ bits = []
+ while i > 0:
+ bits.append(i % 2)
+ i //= 2
+
+ gen = _random_coprime_stream(N, seed)
+ rev = reversed(bits)
+ encode = lambda b: next(gen)**2*pow(a, b) % N
+ return [ encode(b) for b in rev ]
+
+
+
+def decipher_gm(message, key):
+ """
+ Decrypt message 'message' using public_key 'key'.
+
+ Parameters
+ ==========
+
+ message : list of int
+ The randomized encrypted message.
+
+ key : (p, q)
+ The private key.
+
+ Returns
+ =======
+
+ int
+ The encrypted message.
+
+ """
+ p, q = key
+ res = lambda m, p: _legendre(m, p) > 0
+ bits = [res(m, p) * res(m, q) for m in message]
+ m = 0
+ for b in bits:
+ m <<= 1
+ m += not b
+ return m
+
+
+
+########### RailFence Cipher #############
+
+def encipher_railfence(message,rails):
+ """
+ Performs Railfence Encryption on plaintext and returns ciphertext
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import encipher_railfence
+ >>> message = "hello world"
+ >>> encipher_railfence(message,3)
+ 'horel ollwd'
+
+ Parameters
+ ==========
+
+ message : string, the message to encrypt.
+ rails : int, the number of rails.
+
+ Returns
+ =======
+
+ The Encrypted string message.
+
+ References
+ ==========
+ .. [1] https://en.wikipedia.org/wiki/Rail_fence_cipher
+
+ """
+ r = list(range(rails))
+ p = cycle(r + r[-2:0:-1])
+ return ''.join(sorted(message, key=lambda i: next(p)))
+
+
+def decipher_railfence(ciphertext,rails):
+ """
+ Decrypt the message using the given rails
+
+ Examples
+ ========
+
+ >>> from sympy.crypto.crypto import decipher_railfence
+ >>> decipher_railfence("horel ollwd",3)
+ 'hello world'
+
+ Parameters
+ ==========
+
+ message : string, the message to encrypt.
+ rails : int, the number of rails.
+
+ Returns
+ =======
+
+ The Decrypted string message.
+
+ """
+ r = list(range(rails))
+ p = cycle(r + r[-2:0:-1])
+
+ idx = sorted(range(len(ciphertext)), key=lambda i: next(p))
+ res = [''] * len(ciphertext)
+ for i, c in zip(idx, ciphertext):
+ res[i] = c
+ return ''.join(res)
+
+
+################ Blum-Goldwasser cryptosystem #########################
+
+def bg_private_key(p, q):
+ """
+ Check if p and q can be used as private keys for
+ the Blum-Goldwasser cryptosystem.
+
+ Explanation
+ ===========
+
+ The three necessary checks for p and q to pass
+ so that they can be used as private keys:
+
+ 1. p and q must both be prime
+ 2. p and q must be distinct
+ 3. p and q must be congruent to 3 mod 4
+
+ Parameters
+ ==========
+
+ p, q
+ The keys to be checked.
+
+ Returns
+ =======
+
+ p, q
+ Input values.
+
+ Raises
+ ======
+
+ ValueError
+ If p and q do not pass the above conditions.
+
+ """
+
+ if not isprime(p) or not isprime(q):
+ raise ValueError("the two arguments must be prime, "
+ "got %i and %i" %(p, q))
+ elif p == q:
+ raise ValueError("the two arguments must be distinct, "
+ "got two copies of %i. " %p)
+ elif (p - 3) % 4 != 0 or (q - 3) % 4 != 0:
+ raise ValueError("the two arguments must be congruent to 3 mod 4, "
+ "got %i and %i" %(p, q))
+ return p, q
+
+def bg_public_key(p, q):
+ """
+ Calculates public keys from private keys.
+
+ Explanation
+ ===========
+
+ The function first checks the validity of
+ private keys passed as arguments and
+ then returns their product.
+
+ Parameters
+ ==========
+
+ p, q
+ The private keys.
+
+ Returns
+ =======
+
+ N
+ The public key.
+
+ """
+ p, q = bg_private_key(p, q)
+ N = p * q
+ return N
+
+def encipher_bg(i, key, seed=None):
+ """
+ Encrypts the message using public key and seed.
+
+ Explanation
+ ===========
+
+ ALGORITHM:
+ 1. Encodes i as a string of L bits, m.
+ 2. Select a random element r, where 1 < r < key, and computes
+ x = r^2 mod key.
+ 3. Use BBS pseudo-random number generator to generate L random bits, b,
+ using the initial seed as x.
+ 4. Encrypted message, c_i = m_i XOR b_i, 1 <= i <= L.
+ 5. x_L = x^(2^L) mod key.
+ 6. Return (c, x_L)
+
+ Parameters
+ ==========
+
+ i
+ Message, a non-negative integer
+
+ key
+ The public key
+
+ Returns
+ =======
+
+ Tuple
+ (encrypted_message, x_L)
+
+ Raises
+ ======
+
+ ValueError
+ If i is negative.
+
+ """
+
+ if i < 0:
+ raise ValueError(
+ "message must be a non-negative "
+ "integer: got %d instead" % i)
+
+ enc_msg = []
+ while i > 0:
+ enc_msg.append(i % 2)
+ i //= 2
+ enc_msg.reverse()
+ L = len(enc_msg)
+
+ r = _randint(seed)(2, key - 1)
+ x = r**2 % key
+ x_L = pow(int(x), int(2**L), int(key))
+
+ rand_bits = []
+ for _ in range(L):
+ rand_bits.append(x % 2)
+ x = x**2 % key
+
+ encrypt_msg = [m ^ b for (m, b) in zip(enc_msg, rand_bits)]
+
+ return (encrypt_msg, x_L)
+
+def decipher_bg(message, key):
+ """
+ Decrypts the message using private keys.
+
+ Explanation
+ ===========
+
+ ALGORITHM:
+ 1. Let, c be the encrypted message, y the second number received,
+ and p and q be the private keys.
+ 2. Compute, r_p = y^((p+1)/4 ^ L) mod p and
+ r_q = y^((q+1)/4 ^ L) mod q.
+ 3. Compute x_0 = (q(q^-1 mod p)r_p + p(p^-1 mod q)r_q) mod N.
+ 4. From, recompute the bits using the BBS generator, as in the
+ encryption algorithm.
+ 5. Compute original message by XORing c and b.
+
+ Parameters
+ ==========
+
+ message
+ Tuple of encrypted message and a non-negative integer.
+
+ key
+ Tuple of private keys.
+
+ Returns
+ =======
+
+ orig_msg
+ The original message
+
+ """
+
+ p, q = key
+ encrypt_msg, y = message
+ public_key = p * q
+ L = len(encrypt_msg)
+ p_t = ((p + 1)/4)**L
+ q_t = ((q + 1)/4)**L
+ r_p = pow(int(y), int(p_t), int(p))
+ r_q = pow(int(y), int(q_t), int(q))
+
+ x = (q * invert(q, p) * r_p + p * invert(p, q) * r_q) % public_key
+
+ orig_bits = []
+ for _ in range(L):
+ orig_bits.append(x % 2)
+ x = x**2 % public_key
+
+ orig_msg = 0
+ for (m, b) in zip(encrypt_msg, orig_bits):
+ orig_msg = orig_msg * 2
+ orig_msg += (m ^ b)
+
+ return orig_msg
diff --git a/lib/python3.12/site-packages/sympy/crypto/tests/__init__.py b/lib/python3.12/site-packages/sympy/crypto/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/sympy/crypto/tests/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/crypto/tests/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..076e28f1a261787c5809ecfaec077b9b22f47a48
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/crypto/tests/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/crypto/tests/__pycache__/test_crypto.cpython-312.pyc b/lib/python3.12/site-packages/sympy/crypto/tests/__pycache__/test_crypto.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d397e18d3b9e00eae8fdbbe9cd6be9ae6690d9ef
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/crypto/tests/__pycache__/test_crypto.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/crypto/tests/test_crypto.py b/lib/python3.12/site-packages/sympy/crypto/tests/test_crypto.py
new file mode 100644
index 0000000000000000000000000000000000000000..c671138f9a61325f6e65cc7cafddc7cd46f19229
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/crypto/tests/test_crypto.py
@@ -0,0 +1,562 @@
+from sympy.core import symbols
+from sympy.crypto.crypto import (cycle_list,
+ encipher_shift, encipher_affine, encipher_substitution,
+ check_and_join, encipher_vigenere, decipher_vigenere,
+ encipher_hill, decipher_hill, encipher_bifid5, encipher_bifid6,
+ bifid5_square, bifid6_square, bifid5, bifid6,
+ decipher_bifid5, decipher_bifid6, encipher_kid_rsa,
+ decipher_kid_rsa, kid_rsa_private_key, kid_rsa_public_key,
+ decipher_rsa, rsa_private_key, rsa_public_key, encipher_rsa,
+ lfsr_connection_polynomial, lfsr_autocorrelation, lfsr_sequence,
+ encode_morse, decode_morse, elgamal_private_key, elgamal_public_key,
+ encipher_elgamal, decipher_elgamal, dh_private_key, dh_public_key,
+ dh_shared_key, decipher_shift, decipher_affine, encipher_bifid,
+ decipher_bifid, bifid_square, padded_key, uniq, decipher_gm,
+ encipher_gm, gm_public_key, gm_private_key, encipher_bg, decipher_bg,
+ bg_private_key, bg_public_key, encipher_rot13, decipher_rot13,
+ encipher_atbash, decipher_atbash, NonInvertibleCipherWarning,
+ encipher_railfence, decipher_railfence)
+from sympy.external.gmpy import gcd
+from sympy.matrices import Matrix
+from sympy.ntheory import isprime, is_primitive_root
+from sympy.polys.domains import FF
+
+from sympy.testing.pytest import raises, warns
+
+from sympy.core.random import randrange
+
+def test_encipher_railfence():
+ assert encipher_railfence("hello world",2) == "hlowrdel ol"
+ assert encipher_railfence("hello world",3) == "horel ollwd"
+ assert encipher_railfence("hello world",4) == "hwe olordll"
+
+def test_decipher_railfence():
+ assert decipher_railfence("hlowrdel ol",2) == "hello world"
+ assert decipher_railfence("horel ollwd",3) == "hello world"
+ assert decipher_railfence("hwe olordll",4) == "hello world"
+
+
+def test_cycle_list():
+ assert cycle_list(3, 4) == [3, 0, 1, 2]
+ assert cycle_list(-1, 4) == [3, 0, 1, 2]
+ assert cycle_list(1, 4) == [1, 2, 3, 0]
+
+
+def test_encipher_shift():
+ assert encipher_shift("ABC", 0) == "ABC"
+ assert encipher_shift("ABC", 1) == "BCD"
+ assert encipher_shift("ABC", -1) == "ZAB"
+ assert decipher_shift("ZAB", -1) == "ABC"
+
+def test_encipher_rot13():
+ assert encipher_rot13("ABC") == "NOP"
+ assert encipher_rot13("NOP") == "ABC"
+ assert decipher_rot13("ABC") == "NOP"
+ assert decipher_rot13("NOP") == "ABC"
+
+
+def test_encipher_affine():
+ assert encipher_affine("ABC", (1, 0)) == "ABC"
+ assert encipher_affine("ABC", (1, 1)) == "BCD"
+ assert encipher_affine("ABC", (-1, 0)) == "AZY"
+ assert encipher_affine("ABC", (-1, 1), symbols="ABCD") == "BAD"
+ assert encipher_affine("123", (-1, 1), symbols="1234") == "214"
+ assert encipher_affine("ABC", (3, 16)) == "QTW"
+ assert decipher_affine("QTW", (3, 16)) == "ABC"
+
+def test_encipher_atbash():
+ assert encipher_atbash("ABC") == "ZYX"
+ assert encipher_atbash("ZYX") == "ABC"
+ assert decipher_atbash("ABC") == "ZYX"
+ assert decipher_atbash("ZYX") == "ABC"
+
+def test_encipher_substitution():
+ assert encipher_substitution("ABC", "BAC", "ABC") == "BAC"
+ assert encipher_substitution("123", "1243", "1234") == "124"
+
+
+def test_check_and_join():
+ assert check_and_join("abc") == "abc"
+ assert check_and_join(uniq("aaabc")) == "abc"
+ assert check_and_join("ab c".split()) == "abc"
+ assert check_and_join("abc", "a", filter=True) == "a"
+ raises(ValueError, lambda: check_and_join('ab', 'a'))
+
+
+def test_encipher_vigenere():
+ assert encipher_vigenere("ABC", "ABC") == "ACE"
+ assert encipher_vigenere("ABC", "ABC", symbols="ABCD") == "ACA"
+ assert encipher_vigenere("ABC", "AB", symbols="ABCD") == "ACC"
+ assert encipher_vigenere("AB", "ABC", symbols="ABCD") == "AC"
+ assert encipher_vigenere("A", "ABC", symbols="ABCD") == "A"
+
+
+def test_decipher_vigenere():
+ assert decipher_vigenere("ABC", "ABC") == "AAA"
+ assert decipher_vigenere("ABC", "ABC", symbols="ABCD") == "AAA"
+ assert decipher_vigenere("ABC", "AB", symbols="ABCD") == "AAC"
+ assert decipher_vigenere("AB", "ABC", symbols="ABCD") == "AA"
+ assert decipher_vigenere("A", "ABC", symbols="ABCD") == "A"
+
+
+def test_encipher_hill():
+ A = Matrix(2, 2, [1, 2, 3, 5])
+ assert encipher_hill("ABCD", A) == "CFIV"
+ A = Matrix(2, 2, [1, 0, 0, 1])
+ assert encipher_hill("ABCD", A) == "ABCD"
+ assert encipher_hill("ABCD", A, symbols="ABCD") == "ABCD"
+ A = Matrix(2, 2, [1, 2, 3, 5])
+ assert encipher_hill("ABCD", A, symbols="ABCD") == "CBAB"
+ assert encipher_hill("AB", A, symbols="ABCD") == "CB"
+ # message length, n, does not need to be a multiple of k;
+ # it is padded
+ assert encipher_hill("ABA", A) == "CFGC"
+ assert encipher_hill("ABA", A, pad="Z") == "CFYV"
+
+
+def test_decipher_hill():
+ A = Matrix(2, 2, [1, 2, 3, 5])
+ assert decipher_hill("CFIV", A) == "ABCD"
+ A = Matrix(2, 2, [1, 0, 0, 1])
+ assert decipher_hill("ABCD", A) == "ABCD"
+ assert decipher_hill("ABCD", A, symbols="ABCD") == "ABCD"
+ A = Matrix(2, 2, [1, 2, 3, 5])
+ assert decipher_hill("CBAB", A, symbols="ABCD") == "ABCD"
+ assert decipher_hill("CB", A, symbols="ABCD") == "AB"
+ # n does not need to be a multiple of k
+ assert decipher_hill("CFA", A) == "ABAA"
+
+
+def test_encipher_bifid5():
+ assert encipher_bifid5("AB", "AB") == "AB"
+ assert encipher_bifid5("AB", "CD") == "CO"
+ assert encipher_bifid5("ab", "c") == "CH"
+ assert encipher_bifid5("a bc", "b") == "BAC"
+
+
+def test_bifid5_square():
+ A = bifid5
+ f = lambda i, j: symbols(A[5*i + j])
+ M = Matrix(5, 5, f)
+ assert bifid5_square("") == M
+
+
+def test_decipher_bifid5():
+ assert decipher_bifid5("AB", "AB") == "AB"
+ assert decipher_bifid5("CO", "CD") == "AB"
+ assert decipher_bifid5("ch", "c") == "AB"
+ assert decipher_bifid5("b ac", "b") == "ABC"
+
+
+def test_encipher_bifid6():
+ assert encipher_bifid6("AB", "AB") == "AB"
+ assert encipher_bifid6("AB", "CD") == "CP"
+ assert encipher_bifid6("ab", "c") == "CI"
+ assert encipher_bifid6("a bc", "b") == "BAC"
+
+
+def test_decipher_bifid6():
+ assert decipher_bifid6("AB", "AB") == "AB"
+ assert decipher_bifid6("CP", "CD") == "AB"
+ assert decipher_bifid6("ci", "c") == "AB"
+ assert decipher_bifid6("b ac", "b") == "ABC"
+
+
+def test_bifid6_square():
+ A = bifid6
+ f = lambda i, j: symbols(A[6*i + j])
+ M = Matrix(6, 6, f)
+ assert bifid6_square("") == M
+
+
+def test_rsa_public_key():
+ assert rsa_public_key(2, 3, 1) == (6, 1)
+ assert rsa_public_key(5, 3, 3) == (15, 3)
+
+ with warns(NonInvertibleCipherWarning):
+ assert rsa_public_key(2, 2, 1) == (4, 1)
+ assert rsa_public_key(8, 8, 8) is False
+
+
+def test_rsa_private_key():
+ assert rsa_private_key(2, 3, 1) == (6, 1)
+ assert rsa_private_key(5, 3, 3) == (15, 3)
+ assert rsa_private_key(23,29,5) == (667,493)
+
+ with warns(NonInvertibleCipherWarning):
+ assert rsa_private_key(2, 2, 1) == (4, 1)
+ assert rsa_private_key(8, 8, 8) is False
+
+
+def test_rsa_large_key():
+ # Sample from
+ # http://www.herongyang.com/Cryptography/JCE-Public-Key-RSA-Private-Public-Key-Pair-Sample.html
+ p = int('101565610013301240713207239558950144682174355406589305284428666'\
+ '903702505233009')
+ q = int('894687191887545488935455605955948413812376003053143521429242133'\
+ '12069293984003')
+ e = int('65537')
+ d = int('893650581832704239530398858744759129594796235440844479456143566'\
+ '6999402846577625762582824202269399672579058991442587406384754958587'\
+ '400493169361356902030209')
+ assert rsa_public_key(p, q, e) == (p*q, e)
+ assert rsa_private_key(p, q, e) == (p*q, d)
+
+
+def test_encipher_rsa():
+ puk = rsa_public_key(2, 3, 1)
+ assert encipher_rsa(2, puk) == 2
+ puk = rsa_public_key(5, 3, 3)
+ assert encipher_rsa(2, puk) == 8
+
+ with warns(NonInvertibleCipherWarning):
+ puk = rsa_public_key(2, 2, 1)
+ assert encipher_rsa(2, puk) == 2
+
+
+def test_decipher_rsa():
+ prk = rsa_private_key(2, 3, 1)
+ assert decipher_rsa(2, prk) == 2
+ prk = rsa_private_key(5, 3, 3)
+ assert decipher_rsa(8, prk) == 2
+
+ with warns(NonInvertibleCipherWarning):
+ prk = rsa_private_key(2, 2, 1)
+ assert decipher_rsa(2, prk) == 2
+
+
+def test_mutltiprime_rsa_full_example():
+ # Test example from
+ # https://iopscience.iop.org/article/10.1088/1742-6596/995/1/012030
+ puk = rsa_public_key(2, 3, 5, 7, 11, 13, 7)
+ prk = rsa_private_key(2, 3, 5, 7, 11, 13, 7)
+ assert puk == (30030, 7)
+ assert prk == (30030, 823)
+
+ msg = 10
+ encrypted = encipher_rsa(2 * msg - 15, puk)
+ assert encrypted == 18065
+ decrypted = (decipher_rsa(encrypted, prk) + 15) / 2
+ assert decrypted == msg
+
+ # Test example from
+ # https://www.scirp.org/pdf/JCC_2018032215502008.pdf
+ puk1 = rsa_public_key(53, 41, 43, 47, 41)
+ prk1 = rsa_private_key(53, 41, 43, 47, 41)
+ puk2 = rsa_public_key(53, 41, 43, 47, 97)
+ prk2 = rsa_private_key(53, 41, 43, 47, 97)
+
+ assert puk1 == (4391633, 41)
+ assert prk1 == (4391633, 294041)
+ assert puk2 == (4391633, 97)
+ assert prk2 == (4391633, 455713)
+
+ msg = 12321
+ encrypted = encipher_rsa(encipher_rsa(msg, puk1), puk2)
+ assert encrypted == 1081588
+ decrypted = decipher_rsa(decipher_rsa(encrypted, prk2), prk1)
+ assert decrypted == msg
+
+
+def test_rsa_crt_extreme():
+ p = int(
+ '10177157607154245068023861503693082120906487143725062283406501' \
+ '54082258226204046999838297167140821364638180697194879500245557' \
+ '65445186962893346463841419427008800341257468600224049986260471' \
+ '92257248163014468841725476918639415726709736077813632961290911' \
+ '0256421232977833028677441206049309220354796014376698325101693')
+
+ q = int(
+ '28752342353095132872290181526607275886182793241660805077850801' \
+ '75689512797754286972952273553128181861830576836289738668745250' \
+ '34028199691128870676414118458442900035778874482624765513861643' \
+ '27966696316822188398336199002306588703902894100476186823849595' \
+ '103239410527279605442148285816149368667083114802852804976893')
+
+ r = int(
+ '17698229259868825776879500736350186838850961935956310134378261' \
+ '89771862186717463067541369694816245225291921138038800171125596' \
+ '07315449521981157084370187887650624061033066022458512942411841' \
+ '18747893789972315277160085086164119879536041875335384844820566' \
+ '0287479617671726408053319619892052000850883994343378882717849')
+
+ s = int(
+ '68925428438585431029269182233502611027091755064643742383515623' \
+ '64321310582896893395529367074942808353187138794422745718419645' \
+ '28291231865157212604266903677599180789896916456120289112752835' \
+ '98502265889669730331688206825220074713977607415178738015831030' \
+ '364290585369150502819743827343552098197095520550865360159439'
+ )
+
+ t = int(
+ '69035483433453632820551311892368908779778144568711455301541094' \
+ '31487047642322695357696860925747923189635033183069823820910521' \
+ '71172909106797748883261493224162414050106920442445896819806600' \
+ '15448444826108008217972129130625571421904893252804729877353352' \
+ '739420480574842850202181462656251626522910618936534699566291'
+ )
+
+ e = 65537
+ puk = rsa_public_key(p, q, r, s, t, e)
+ prk = rsa_private_key(p, q, r, s, t, e)
+
+ plaintext = 1000
+ ciphertext_1 = encipher_rsa(plaintext, puk)
+ ciphertext_2 = encipher_rsa(plaintext, puk, [p, q, r, s, t])
+ assert ciphertext_1 == ciphertext_2
+ assert decipher_rsa(ciphertext_1, prk) == \
+ decipher_rsa(ciphertext_1, prk, [p, q, r, s, t])
+
+
+def test_rsa_exhaustive():
+ p, q = 61, 53
+ e = 17
+ puk = rsa_public_key(p, q, e, totient='Carmichael')
+ prk = rsa_private_key(p, q, e, totient='Carmichael')
+
+ for msg in range(puk[0]):
+ encrypted = encipher_rsa(msg, puk)
+ decrypted = decipher_rsa(encrypted, prk)
+ try:
+ assert decrypted == msg
+ except AssertionError:
+ raise AssertionError(
+ "The RSA is not correctly decrypted " \
+ "(Original : {}, Encrypted : {}, Decrypted : {})" \
+ .format(msg, encrypted, decrypted)
+ )
+
+
+def test_rsa_multiprime_exhanstive():
+ primes = [3, 5, 7, 11]
+ e = 7
+ args = primes + [e]
+ puk = rsa_public_key(*args, totient='Carmichael')
+ prk = rsa_private_key(*args, totient='Carmichael')
+ n = puk[0]
+
+ for msg in range(n):
+ encrypted = encipher_rsa(msg, puk)
+ decrypted = decipher_rsa(encrypted, prk)
+ try:
+ assert decrypted == msg
+ except AssertionError:
+ raise AssertionError(
+ "The RSA is not correctly decrypted " \
+ "(Original : {}, Encrypted : {}, Decrypted : {})" \
+ .format(msg, encrypted, decrypted)
+ )
+
+
+def test_rsa_multipower_exhanstive():
+ primes = [5, 5, 7]
+ e = 7
+ args = primes + [e]
+ puk = rsa_public_key(*args, multipower=True)
+ prk = rsa_private_key(*args, multipower=True)
+ n = puk[0]
+
+ for msg in range(n):
+ if gcd(msg, n) != 1:
+ continue
+
+ encrypted = encipher_rsa(msg, puk)
+ decrypted = decipher_rsa(encrypted, prk)
+ try:
+ assert decrypted == msg
+ except AssertionError:
+ raise AssertionError(
+ "The RSA is not correctly decrypted " \
+ "(Original : {}, Encrypted : {}, Decrypted : {})" \
+ .format(msg, encrypted, decrypted)
+ )
+
+
+def test_kid_rsa_public_key():
+ assert kid_rsa_public_key(1, 2, 1, 1) == (5, 2)
+ assert kid_rsa_public_key(1, 2, 2, 1) == (8, 3)
+ assert kid_rsa_public_key(1, 2, 1, 2) == (7, 2)
+
+
+def test_kid_rsa_private_key():
+ assert kid_rsa_private_key(1, 2, 1, 1) == (5, 3)
+ assert kid_rsa_private_key(1, 2, 2, 1) == (8, 3)
+ assert kid_rsa_private_key(1, 2, 1, 2) == (7, 4)
+
+
+def test_encipher_kid_rsa():
+ assert encipher_kid_rsa(1, (5, 2)) == 2
+ assert encipher_kid_rsa(1, (8, 3)) == 3
+ assert encipher_kid_rsa(1, (7, 2)) == 2
+
+
+def test_decipher_kid_rsa():
+ assert decipher_kid_rsa(2, (5, 3)) == 1
+ assert decipher_kid_rsa(3, (8, 3)) == 1
+ assert decipher_kid_rsa(2, (7, 4)) == 1
+
+
+def test_encode_morse():
+ assert encode_morse('ABC') == '.-|-...|-.-.'
+ assert encode_morse('SMS ') == '...|--|...||'
+ assert encode_morse('SMS\n') == '...|--|...||'
+ assert encode_morse('') == ''
+ assert encode_morse(' ') == '||'
+ assert encode_morse(' ', sep='`') == '``'
+ assert encode_morse(' ', sep='``') == '````'
+ assert encode_morse('!@#$%^&*()_+') == '-.-.--|.--.-.|...-..-|-.--.|-.--.-|..--.-|.-.-.'
+ assert encode_morse('12345') == '.----|..---|...--|....-|.....'
+ assert encode_morse('67890') == '-....|--...|---..|----.|-----'
+
+
+def test_decode_morse():
+ assert decode_morse('-.-|.|-.--') == 'KEY'
+ assert decode_morse('.-.|..-|-.||') == 'RUN'
+ raises(KeyError, lambda: decode_morse('.....----'))
+
+
+def test_lfsr_sequence():
+ raises(TypeError, lambda: lfsr_sequence(1, [1], 1))
+ raises(TypeError, lambda: lfsr_sequence([1], 1, 1))
+ F = FF(2)
+ assert lfsr_sequence([F(1)], [F(1)], 2) == [F(1), F(1)]
+ assert lfsr_sequence([F(0)], [F(1)], 2) == [F(1), F(0)]
+ F = FF(3)
+ assert lfsr_sequence([F(1)], [F(1)], 2) == [F(1), F(1)]
+ assert lfsr_sequence([F(0)], [F(2)], 2) == [F(2), F(0)]
+ assert lfsr_sequence([F(1)], [F(2)], 2) == [F(2), F(2)]
+
+
+def test_lfsr_autocorrelation():
+ raises(TypeError, lambda: lfsr_autocorrelation(1, 2, 3))
+ F = FF(2)
+ s = lfsr_sequence([F(1), F(0)], [F(0), F(1)], 5)
+ assert lfsr_autocorrelation(s, 2, 0) == 1
+ assert lfsr_autocorrelation(s, 2, 1) == -1
+
+
+def test_lfsr_connection_polynomial():
+ F = FF(2)
+ x = symbols("x")
+ s = lfsr_sequence([F(1), F(0)], [F(0), F(1)], 5)
+ assert lfsr_connection_polynomial(s) == x**2 + 1
+ s = lfsr_sequence([F(1), F(1)], [F(0), F(1)], 5)
+ assert lfsr_connection_polynomial(s) == x**2 + x + 1
+
+
+def test_elgamal_private_key():
+ a, b, _ = elgamal_private_key(digit=100)
+ assert isprime(a)
+ assert is_primitive_root(b, a)
+ assert len(bin(a)) >= 102
+
+
+def test_elgamal():
+ dk = elgamal_private_key(5)
+ ek = elgamal_public_key(dk)
+ P = ek[0]
+ assert P - 1 == decipher_elgamal(encipher_elgamal(P - 1, ek), dk)
+ raises(ValueError, lambda: encipher_elgamal(P, dk))
+ raises(ValueError, lambda: encipher_elgamal(-1, dk))
+
+
+def test_dh_private_key():
+ p, g, _ = dh_private_key(digit = 100)
+ assert isprime(p)
+ assert is_primitive_root(g, p)
+ assert len(bin(p)) >= 102
+
+
+def test_dh_public_key():
+ p1, g1, a = dh_private_key(digit = 100)
+ p2, g2, ga = dh_public_key((p1, g1, a))
+ assert p1 == p2
+ assert g1 == g2
+ assert ga == pow(g1, a, p1)
+
+
+def test_dh_shared_key():
+ prk = dh_private_key(digit = 100)
+ p, _, ga = dh_public_key(prk)
+ b = randrange(2, p)
+ sk = dh_shared_key((p, _, ga), b)
+ assert sk == pow(ga, b, p)
+ raises(ValueError, lambda: dh_shared_key((1031, 14, 565), 2000))
+
+
+def test_padded_key():
+ assert padded_key('b', 'ab') == 'ba'
+ raises(ValueError, lambda: padded_key('ab', 'ace'))
+ raises(ValueError, lambda: padded_key('ab', 'abba'))
+
+
+def test_bifid():
+ raises(ValueError, lambda: encipher_bifid('abc', 'b', 'abcde'))
+ assert encipher_bifid('abc', 'b', 'abcd') == 'bdb'
+ raises(ValueError, lambda: decipher_bifid('bdb', 'b', 'abcde'))
+ assert encipher_bifid('bdb', 'b', 'abcd') == 'abc'
+ raises(ValueError, lambda: bifid_square('abcde'))
+ assert bifid5_square("B") == \
+ bifid5_square('BACDEFGHIKLMNOPQRSTUVWXYZ')
+ assert bifid6_square('B0') == \
+ bifid6_square('B0ACDEFGHIJKLMNOPQRSTUVWXYZ123456789')
+
+
+def test_encipher_decipher_gm():
+ ps = [131, 137, 139, 149, 151, 157, 163, 167,
+ 173, 179, 181, 191, 193, 197, 199]
+ qs = [89, 97, 101, 103, 107, 109, 113, 127,
+ 131, 137, 139, 149, 151, 157, 47]
+ messages = [
+ 0, 32855, 34303, 14805, 1280, 75859, 38368,
+ 724, 60356, 51675, 76697, 61854, 18661,
+ ]
+ for p, q in zip(ps, qs):
+ pri = gm_private_key(p, q)
+ for msg in messages:
+ pub = gm_public_key(p, q)
+ enc = encipher_gm(msg, pub)
+ dec = decipher_gm(enc, pri)
+ assert dec == msg
+
+
+def test_gm_private_key():
+ raises(ValueError, lambda: gm_public_key(13, 15))
+ raises(ValueError, lambda: gm_public_key(0, 0))
+ raises(ValueError, lambda: gm_public_key(0, 5))
+ assert 17, 19 == gm_public_key(17, 19)
+
+
+def test_gm_public_key():
+ assert 323 == gm_public_key(17, 19)[1]
+ assert 15 == gm_public_key(3, 5)[1]
+ raises(ValueError, lambda: gm_public_key(15, 19))
+
+def test_encipher_decipher_bg():
+ ps = [67, 7, 71, 103, 11, 43, 107, 47,
+ 79, 19, 83, 23, 59, 127, 31]
+ qs = qs = [7, 71, 103, 11, 43, 107, 47,
+ 79, 19, 83, 23, 59, 127, 31, 67]
+ messages = [
+ 0, 328, 343, 148, 1280, 758, 383,
+ 724, 603, 516, 766, 618, 186,
+ ]
+
+ for p, q in zip(ps, qs):
+ pri = bg_private_key(p, q)
+ for msg in messages:
+ pub = bg_public_key(p, q)
+ enc = encipher_bg(msg, pub)
+ dec = decipher_bg(enc, pri)
+ assert dec == msg
+
+def test_bg_private_key():
+ raises(ValueError, lambda: bg_private_key(8, 16))
+ raises(ValueError, lambda: bg_private_key(8, 8))
+ raises(ValueError, lambda: bg_private_key(13, 17))
+ assert 23, 31 == bg_private_key(23, 31)
+
+def test_bg_public_key():
+ assert 5293 == bg_public_key(67, 79)
+ assert 713 == bg_public_key(23, 31)
+ raises(ValueError, lambda: bg_private_key(13, 17))
diff --git a/lib/python3.12/site-packages/sympy/external/__init__.py b/lib/python3.12/site-packages/sympy/external/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..549b4b96cdce0ee4d31960e89cb9dc26af0e105d
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/__init__.py
@@ -0,0 +1,20 @@
+"""
+Unified place for determining if external dependencies are installed or not.
+
+You should import all external modules using the import_module() function.
+
+For example
+
+>>> from sympy.external import import_module
+>>> numpy = import_module('numpy')
+
+If the resulting library is not installed, or if the installed version
+is less than a given minimum version, the function will return None.
+Otherwise, it will return the library. See the docstring of
+import_module() for more information.
+
+"""
+
+from sympy.external.importtools import import_module
+
+__all__ = ['import_module']
diff --git a/lib/python3.12/site-packages/sympy/external/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5bb3a01474e86f87d6e84b436395c0151d47e957
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/__pycache__/gmpy.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/__pycache__/gmpy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..99095a374031f92cf45c039fb67973bd4577fe3c
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/__pycache__/gmpy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/__pycache__/importtools.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/__pycache__/importtools.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bca03d7c2100e24d18f4d21460452fa99d3a1447
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/__pycache__/importtools.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/__pycache__/ntheory.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/__pycache__/ntheory.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1afa2122de8c6364c78482f716f21a1891523cd6
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/__pycache__/ntheory.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/__pycache__/pythonmpq.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/__pycache__/pythonmpq.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b9dc581760d9540ce75b843e46f6d26d160c988d
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/__pycache__/pythonmpq.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/gmpy.py b/lib/python3.12/site-packages/sympy/external/gmpy.py
new file mode 100644
index 0000000000000000000000000000000000000000..b28da521a620b6c04e5302b5e7f360e00b3099cc
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/gmpy.py
@@ -0,0 +1,341 @@
+import os
+from ctypes import c_long, sizeof
+from functools import reduce
+from typing import Tuple as tTuple, Type
+from warnings import warn
+
+from sympy.external import import_module
+
+from .pythonmpq import PythonMPQ
+
+from .ntheory import (
+ bit_scan1 as python_bit_scan1,
+ bit_scan0 as python_bit_scan0,
+ remove as python_remove,
+ factorial as python_factorial,
+ sqrt as python_sqrt,
+ sqrtrem as python_sqrtrem,
+ gcd as python_gcd,
+ lcm as python_lcm,
+ gcdext as python_gcdext,
+ is_square as python_is_square,
+ invert as python_invert,
+ legendre as python_legendre,
+ jacobi as python_jacobi,
+ kronecker as python_kronecker,
+ iroot as python_iroot,
+ is_fermat_prp as python_is_fermat_prp,
+ is_euler_prp as python_is_euler_prp,
+ is_strong_prp as python_is_strong_prp,
+ is_fibonacci_prp as python_is_fibonacci_prp,
+ is_lucas_prp as python_is_lucas_prp,
+ is_selfridge_prp as python_is_selfridge_prp,
+ is_strong_lucas_prp as python_is_strong_lucas_prp,
+ is_strong_selfridge_prp as python_is_strong_selfridge_prp,
+ is_bpsw_prp as python_is_bpsw_prp,
+ is_strong_bpsw_prp as python_is_strong_bpsw_prp,
+)
+
+
+__all__ = [
+ # GROUND_TYPES is either 'gmpy' or 'python' depending on which is used. If
+ # gmpy is installed then it will be used unless the environment variable
+ # SYMPY_GROUND_TYPES is set to something other than 'auto', 'gmpy', or
+ # 'gmpy2'.
+ 'GROUND_TYPES',
+
+ # If HAS_GMPY is 0, no supported version of gmpy is available. Otherwise,
+ # HAS_GMPY will be 2 for gmpy2 if GROUND_TYPES is 'gmpy'. It used to be
+ # possible for HAS_GMPY to be 1 for gmpy but gmpy is no longer supported.
+ 'HAS_GMPY',
+
+ # SYMPY_INTS is a tuple containing the base types for valid integer types.
+ # This is either (int,) or (int, type(mpz(0))) depending on GROUND_TYPES.
+ 'SYMPY_INTS',
+
+ # MPQ is either gmpy.mpq or the Python equivalent from
+ # sympy.external.pythonmpq
+ 'MPQ',
+
+ # MPZ is either gmpy.mpz or int.
+ 'MPZ',
+
+ 'bit_scan1',
+ 'bit_scan0',
+ 'remove',
+ 'factorial',
+ 'sqrt',
+ 'is_square',
+ 'sqrtrem',
+ 'gcd',
+ 'lcm',
+ 'gcdext',
+ 'invert',
+ 'legendre',
+ 'jacobi',
+ 'kronecker',
+ 'iroot',
+ 'is_fermat_prp',
+ 'is_euler_prp',
+ 'is_strong_prp',
+ 'is_fibonacci_prp',
+ 'is_lucas_prp',
+ 'is_selfridge_prp',
+ 'is_strong_lucas_prp',
+ 'is_strong_selfridge_prp',
+ 'is_bpsw_prp',
+ 'is_strong_bpsw_prp',
+]
+
+
+#
+# Tested python-flint version. Future versions might work but we will only use
+# them if explicitly requested by SYMPY_GROUND_TYPES=flint.
+#
+_PYTHON_FLINT_VERSION_NEEDED = ["0.6", "0.7", "0.8", "0.9"]
+
+
+def _flint_version_okay(flint_version):
+ major, minor = flint_version.split('.')[:2]
+ flint_ver = f'{major}.{minor}'
+ return flint_ver in _PYTHON_FLINT_VERSION_NEEDED
+
+#
+# We will only use gmpy2 >= 2.0.0
+#
+_GMPY2_MIN_VERSION = '2.0.0'
+
+
+def _get_flint(sympy_ground_types):
+ if sympy_ground_types not in ('auto', 'flint'):
+ return None
+
+ try:
+ import flint
+ # Earlier versions of python-flint may not have __version__.
+ from flint import __version__ as _flint_version
+ except ImportError:
+ if sympy_ground_types == 'flint':
+ warn("SYMPY_GROUND_TYPES was set to flint but python-flint is not "
+ "installed. Falling back to other ground types.")
+ return None
+
+ if _flint_version_okay(_flint_version):
+ return flint
+ elif sympy_ground_types == 'auto':
+ return None
+ else:
+ warn(f"Using python-flint {_flint_version} because SYMPY_GROUND_TYPES "
+ f"is set to flint but this version of SymPy is only tested "
+ f"with python-flint versions {_PYTHON_FLINT_VERSION_NEEDED}.")
+ return flint
+
+
+def _get_gmpy2(sympy_ground_types):
+ if sympy_ground_types not in ('auto', 'gmpy', 'gmpy2'):
+ return None
+
+ gmpy = import_module('gmpy2', min_module_version=_GMPY2_MIN_VERSION,
+ module_version_attr='version', module_version_attr_call_args=())
+
+ if sympy_ground_types != 'auto' and gmpy is None:
+ warn("gmpy2 library is not installed, switching to 'python' ground types")
+
+ return gmpy
+
+
+#
+# SYMPY_GROUND_TYPES can be flint, gmpy, gmpy2, python or auto (default)
+#
+_SYMPY_GROUND_TYPES = os.environ.get('SYMPY_GROUND_TYPES', 'auto').lower()
+_flint = None
+_gmpy = None
+
+#
+# First handle auto-detection of flint/gmpy2. We will prefer flint if available
+# or otherwise gmpy2 if available and then lastly the python types.
+#
+if _SYMPY_GROUND_TYPES in ('auto', 'flint'):
+ _flint = _get_flint(_SYMPY_GROUND_TYPES)
+ if _flint is not None:
+ _SYMPY_GROUND_TYPES = 'flint'
+ else:
+ _SYMPY_GROUND_TYPES = 'auto'
+
+if _SYMPY_GROUND_TYPES in ('auto', 'gmpy', 'gmpy2'):
+ _gmpy = _get_gmpy2(_SYMPY_GROUND_TYPES)
+ if _gmpy is not None:
+ _SYMPY_GROUND_TYPES = 'gmpy'
+ else:
+ _SYMPY_GROUND_TYPES = 'python'
+
+if _SYMPY_GROUND_TYPES not in ('flint', 'gmpy', 'python'):
+ warn("SYMPY_GROUND_TYPES environment variable unrecognised. "
+ "Should be 'auto', 'flint', 'gmpy', 'gmpy2' or 'python'.")
+ _SYMPY_GROUND_TYPES = 'python'
+
+#
+# At this point _SYMPY_GROUND_TYPES is either flint, gmpy or python. The blocks
+# below define the values exported by this module in each case.
+#
+
+#
+# In gmpy2 and flint, there are functions that take a long (or unsigned long)
+# argument. That is, it is not possible to input a value larger than that.
+#
+LONG_MAX = (1 << (8*sizeof(c_long) - 1)) - 1
+
+#
+# Type checkers are confused by what SYMPY_INTS is. There may be a better type
+# hint for this like Type[Integral] or something.
+#
+SYMPY_INTS: tTuple[Type, ...]
+
+if _SYMPY_GROUND_TYPES == 'gmpy':
+
+ assert _gmpy is not None
+
+ flint = None
+ gmpy = _gmpy
+
+ HAS_GMPY = 2
+ GROUND_TYPES = 'gmpy'
+ SYMPY_INTS = (int, type(gmpy.mpz(0)))
+ MPZ = gmpy.mpz
+ MPQ = gmpy.mpq
+
+ bit_scan1 = gmpy.bit_scan1
+ bit_scan0 = gmpy.bit_scan0
+ remove = gmpy.remove
+ factorial = gmpy.fac
+ sqrt = gmpy.isqrt
+ is_square = gmpy.is_square
+ sqrtrem = gmpy.isqrt_rem
+ gcd = gmpy.gcd
+ lcm = gmpy.lcm
+ gcdext = gmpy.gcdext
+ invert = gmpy.invert
+ legendre = gmpy.legendre
+ jacobi = gmpy.jacobi
+ kronecker = gmpy.kronecker
+
+ def iroot(x, n):
+ # In the latest gmpy2, the threshold for n is ULONG_MAX,
+ # but adjust to the older one.
+ if n <= LONG_MAX:
+ return gmpy.iroot(x, n)
+ return python_iroot(x, n)
+
+ is_fermat_prp = gmpy.is_fermat_prp
+ is_euler_prp = gmpy.is_euler_prp
+ is_strong_prp = gmpy.is_strong_prp
+ is_fibonacci_prp = gmpy.is_fibonacci_prp
+ is_lucas_prp = gmpy.is_lucas_prp
+ is_selfridge_prp = gmpy.is_selfridge_prp
+ is_strong_lucas_prp = gmpy.is_strong_lucas_prp
+ is_strong_selfridge_prp = gmpy.is_strong_selfridge_prp
+ is_bpsw_prp = gmpy.is_bpsw_prp
+ is_strong_bpsw_prp = gmpy.is_strong_bpsw_prp
+
+elif _SYMPY_GROUND_TYPES == 'flint':
+
+ assert _flint is not None
+
+ flint = _flint
+ gmpy = None
+
+ HAS_GMPY = 0
+ GROUND_TYPES = 'flint'
+ SYMPY_INTS = (int, flint.fmpz) # type: ignore
+ MPZ = flint.fmpz # type: ignore
+ MPQ = flint.fmpq # type: ignore
+
+ bit_scan1 = python_bit_scan1
+ bit_scan0 = python_bit_scan0
+ remove = python_remove
+ factorial = python_factorial
+
+ def sqrt(x):
+ return flint.fmpz(x).isqrt()
+
+ def is_square(x):
+ if x < 0:
+ return False
+ return flint.fmpz(x).sqrtrem()[1] == 0
+
+ def sqrtrem(x):
+ return flint.fmpz(x).sqrtrem()
+
+ def gcd(*args):
+ return reduce(flint.fmpz.gcd, args, flint.fmpz(0))
+
+ def lcm(*args):
+ return reduce(flint.fmpz.lcm, args, flint.fmpz(1))
+
+ gcdext = python_gcdext
+ invert = python_invert
+ legendre = python_legendre
+
+ def jacobi(x, y):
+ if y <= 0 or not y % 2:
+ raise ValueError("y should be an odd positive integer")
+ return flint.fmpz(x).jacobi(y)
+
+ kronecker = python_kronecker
+
+ def iroot(x, n):
+ if n <= LONG_MAX:
+ y = flint.fmpz(x).root(n)
+ return y, y**n == x
+ return python_iroot(x, n)
+
+ is_fermat_prp = python_is_fermat_prp
+ is_euler_prp = python_is_euler_prp
+ is_strong_prp = python_is_strong_prp
+ is_fibonacci_prp = python_is_fibonacci_prp
+ is_lucas_prp = python_is_lucas_prp
+ is_selfridge_prp = python_is_selfridge_prp
+ is_strong_lucas_prp = python_is_strong_lucas_prp
+ is_strong_selfridge_prp = python_is_strong_selfridge_prp
+ is_bpsw_prp = python_is_bpsw_prp
+ is_strong_bpsw_prp = python_is_strong_bpsw_prp
+
+elif _SYMPY_GROUND_TYPES == 'python':
+
+ flint = None
+ gmpy = None
+
+ HAS_GMPY = 0
+ GROUND_TYPES = 'python'
+ SYMPY_INTS = (int,)
+ MPZ = int
+ MPQ = PythonMPQ
+
+ bit_scan1 = python_bit_scan1
+ bit_scan0 = python_bit_scan0
+ remove = python_remove
+ factorial = python_factorial
+ sqrt = python_sqrt
+ is_square = python_is_square
+ sqrtrem = python_sqrtrem
+ gcd = python_gcd
+ lcm = python_lcm
+ gcdext = python_gcdext
+ invert = python_invert
+ legendre = python_legendre
+ jacobi = python_jacobi
+ kronecker = python_kronecker
+ iroot = python_iroot
+ is_fermat_prp = python_is_fermat_prp
+ is_euler_prp = python_is_euler_prp
+ is_strong_prp = python_is_strong_prp
+ is_fibonacci_prp = python_is_fibonacci_prp
+ is_lucas_prp = python_is_lucas_prp
+ is_selfridge_prp = python_is_selfridge_prp
+ is_strong_lucas_prp = python_is_strong_lucas_prp
+ is_strong_selfridge_prp = python_is_strong_selfridge_prp
+ is_bpsw_prp = python_is_bpsw_prp
+ is_strong_bpsw_prp = python_is_strong_bpsw_prp
+
+else:
+ assert False
diff --git a/lib/python3.12/site-packages/sympy/external/importtools.py b/lib/python3.12/site-packages/sympy/external/importtools.py
new file mode 100644
index 0000000000000000000000000000000000000000..5008b3dd4634d3cee10744a0a92b1204051f07cc
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/importtools.py
@@ -0,0 +1,187 @@
+"""Tools to assist importing optional external modules."""
+
+import sys
+import re
+
+# Override these in the module to change the default warning behavior.
+# For example, you might set both to False before running the tests so that
+# warnings are not printed to the console, or set both to True for debugging.
+
+WARN_NOT_INSTALLED = None # Default is False
+WARN_OLD_VERSION = None # Default is True
+
+
+def __sympy_debug():
+ # helper function from sympy/__init__.py
+ # We don't just import SYMPY_DEBUG from that file because we don't want to
+ # import all of SymPy just to use this module.
+ import os
+ debug_str = os.getenv('SYMPY_DEBUG', 'False')
+ if debug_str in ('True', 'False'):
+ return eval(debug_str)
+ else:
+ raise RuntimeError("unrecognized value for SYMPY_DEBUG: %s" %
+ debug_str)
+
+if __sympy_debug():
+ WARN_OLD_VERSION = True
+ WARN_NOT_INSTALLED = True
+
+
+_component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
+
+def version_tuple(vstring):
+ # Parse a version string to a tuple e.g. '1.2' -> (1, 2)
+ # Simplified from distutils.version.LooseVersion which was deprecated in
+ # Python 3.10.
+ components = []
+ for x in _component_re.split(vstring):
+ if x and x != '.':
+ try:
+ x = int(x)
+ except ValueError:
+ pass
+ components.append(x)
+ return tuple(components)
+
+
+def import_module(module, min_module_version=None, min_python_version=None,
+ warn_not_installed=None, warn_old_version=None,
+ module_version_attr='__version__', module_version_attr_call_args=None,
+ import_kwargs={}, catch=()):
+ """
+ Import and return a module if it is installed.
+
+ If the module is not installed, it returns None.
+
+ A minimum version for the module can be given as the keyword argument
+ min_module_version. This should be comparable against the module version.
+ By default, module.__version__ is used to get the module version. To
+ override this, set the module_version_attr keyword argument. If the
+ attribute of the module to get the version should be called (e.g.,
+ module.version()), then set module_version_attr_call_args to the args such
+ that module.module_version_attr(*module_version_attr_call_args) returns the
+ module's version.
+
+ If the module version is less than min_module_version using the Python <
+ comparison, None will be returned, even if the module is installed. You can
+ use this to keep from importing an incompatible older version of a module.
+
+ You can also specify a minimum Python version by using the
+ min_python_version keyword argument. This should be comparable against
+ sys.version_info.
+
+ If the keyword argument warn_not_installed is set to True, the function will
+ emit a UserWarning when the module is not installed.
+
+ If the keyword argument warn_old_version is set to True, the function will
+ emit a UserWarning when the library is installed, but cannot be imported
+ because of the min_module_version or min_python_version options.
+
+ Note that because of the way warnings are handled, a warning will be
+ emitted for each module only once. You can change the default warning
+ behavior by overriding the values of WARN_NOT_INSTALLED and WARN_OLD_VERSION
+ in sympy.external.importtools. By default, WARN_NOT_INSTALLED is False and
+ WARN_OLD_VERSION is True.
+
+ This function uses __import__() to import the module. To pass additional
+ options to __import__(), use the import_kwargs keyword argument. For
+ example, to import a submodule A.B, you must pass a nonempty fromlist option
+ to __import__. See the docstring of __import__().
+
+ This catches ImportError to determine if the module is not installed. To
+ catch additional errors, pass them as a tuple to the catch keyword
+ argument.
+
+ Examples
+ ========
+
+ >>> from sympy.external import import_module
+
+ >>> numpy = import_module('numpy')
+
+ >>> numpy = import_module('numpy', min_python_version=(2, 7),
+ ... warn_old_version=False)
+
+ >>> numpy = import_module('numpy', min_module_version='1.5',
+ ... warn_old_version=False) # numpy.__version__ is a string
+
+ >>> # gmpy does not have __version__, but it does have gmpy.version()
+
+ >>> gmpy = import_module('gmpy', min_module_version='1.14',
+ ... module_version_attr='version', module_version_attr_call_args=(),
+ ... warn_old_version=False)
+
+ >>> # To import a submodule, you must pass a nonempty fromlist to
+ >>> # __import__(). The values do not matter.
+ >>> p3 = import_module('mpl_toolkits.mplot3d',
+ ... import_kwargs={'fromlist':['something']})
+
+ >>> # matplotlib.pyplot can raise RuntimeError when the display cannot be opened
+ >>> matplotlib = import_module('matplotlib',
+ ... import_kwargs={'fromlist':['pyplot']}, catch=(RuntimeError,))
+
+ """
+ # keyword argument overrides default, and global variable overrides
+ # keyword argument.
+ warn_old_version = (WARN_OLD_VERSION if WARN_OLD_VERSION is not None
+ else warn_old_version or True)
+ warn_not_installed = (WARN_NOT_INSTALLED if WARN_NOT_INSTALLED is not None
+ else warn_not_installed or False)
+
+ import warnings
+
+ # Check Python first so we don't waste time importing a module we can't use
+ if min_python_version:
+ if sys.version_info < min_python_version:
+ if warn_old_version:
+ warnings.warn("Python version is too old to use %s "
+ "(%s or newer required)" % (
+ module, '.'.join(map(str, min_python_version))),
+ UserWarning, stacklevel=2)
+ return
+
+ try:
+ mod = __import__(module, **import_kwargs)
+
+ ## there's something funny about imports with matplotlib and py3k. doing
+ ## from matplotlib import collections
+ ## gives python's stdlib collections module. explicitly re-importing
+ ## the module fixes this.
+ from_list = import_kwargs.get('fromlist', ())
+ for submod in from_list:
+ if submod == 'collections' and mod.__name__ == 'matplotlib':
+ __import__(module + '.' + submod)
+ except ImportError:
+ if warn_not_installed:
+ warnings.warn("%s module is not installed" % module, UserWarning,
+ stacklevel=2)
+ return
+ except catch as e:
+ if warn_not_installed:
+ warnings.warn(
+ "%s module could not be used (%s)" % (module, repr(e)),
+ stacklevel=2)
+ return
+
+ if min_module_version:
+ modversion = getattr(mod, module_version_attr)
+ if module_version_attr_call_args is not None:
+ modversion = modversion(*module_version_attr_call_args)
+ if version_tuple(modversion) < version_tuple(min_module_version):
+ if warn_old_version:
+ # Attempt to create a pretty string version of the version
+ if isinstance(min_module_version, str):
+ verstr = min_module_version
+ elif isinstance(min_module_version, (tuple, list)):
+ verstr = '.'.join(map(str, min_module_version))
+ else:
+ # Either don't know what this is. Hopefully
+ # it's something that has a nice str version, like an int.
+ verstr = str(min_module_version)
+ warnings.warn("%s version is too old to use "
+ "(%s or newer required)" % (module, verstr),
+ UserWarning, stacklevel=2)
+ return
+
+ return mod
diff --git a/lib/python3.12/site-packages/sympy/external/ntheory.py b/lib/python3.12/site-packages/sympy/external/ntheory.py
new file mode 100644
index 0000000000000000000000000000000000000000..296b1331a9e05cd7df386f8e0bfecb1a103aba79
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/ntheory.py
@@ -0,0 +1,637 @@
+# sympy.external.ntheory
+#
+# This module provides pure Python implementations of some number theory
+# functions that are alternately used from gmpy2 if it is installed.
+
+import sys
+import math
+
+import mpmath.libmp as mlib
+
+
+_small_trailing = [0] * 256
+for j in range(1, 8):
+ _small_trailing[1 << j :: 1 << (j + 1)] = [j] * (1 << (7 - j))
+
+
+def bit_scan1(x, n=0):
+ if not x:
+ return
+ x = abs(x >> n)
+ low_byte = x & 0xFF
+ if low_byte:
+ return _small_trailing[low_byte] + n
+
+ t = 8 + n
+ x >>= 8
+ # 2**m is quick for z up through 2**30
+ z = x.bit_length() - 1
+ if x == 1 << z:
+ return z + t
+
+ if z < 300:
+ # fixed 8-byte reduction
+ while not x & 0xFF:
+ x >>= 8
+ t += 8
+ else:
+ # binary reduction important when there might be a large
+ # number of trailing 0s
+ p = z >> 1
+ while not x & 0xFF:
+ while x & ((1 << p) - 1):
+ p >>= 1
+ x >>= p
+ t += p
+ return t + _small_trailing[x & 0xFF]
+
+
+def bit_scan0(x, n=0):
+ return bit_scan1(x + (1 << n), n)
+
+
+def remove(x, f):
+ if f < 2:
+ raise ValueError("factor must be > 1")
+ if x == 0:
+ return 0, 0
+ if f == 2:
+ b = bit_scan1(x)
+ return x >> b, b
+ m = 0
+ y, rem = divmod(x, f)
+ while not rem:
+ x = y
+ m += 1
+ if m > 5:
+ pow_list = [f**2]
+ while pow_list:
+ _f = pow_list[-1]
+ y, rem = divmod(x, _f)
+ if not rem:
+ m += 1 << len(pow_list)
+ x = y
+ pow_list.append(_f**2)
+ else:
+ pow_list.pop()
+ y, rem = divmod(x, f)
+ return x, m
+
+
+def factorial(x):
+ """Return x!."""
+ return int(mlib.ifac(int(x)))
+
+
+def sqrt(x):
+ """Integer square root of x."""
+ return int(mlib.isqrt(int(x)))
+
+
+def sqrtrem(x):
+ """Integer square root of x and remainder."""
+ s, r = mlib.sqrtrem(int(x))
+ return (int(s), int(r))
+
+
+if sys.version_info[:2] >= (3, 9):
+ # As of Python 3.9 these can take multiple arguments
+ gcd = math.gcd
+ lcm = math.lcm
+
+else:
+ # Until python 3.8 is no longer supported
+ from functools import reduce
+
+
+ def gcd(*args):
+ """gcd of multiple integers."""
+ return reduce(math.gcd, args, 0)
+
+
+ def lcm(*args):
+ """lcm of multiple integers."""
+ if 0 in args:
+ return 0
+ return reduce(lambda x, y: x*y//math.gcd(x, y), args, 1)
+
+
+def _sign(n):
+ if n < 0:
+ return -1, -n
+ return 1, n
+
+
+def gcdext(a, b):
+ if not a or not b:
+ g = abs(a) or abs(b)
+ if not g:
+ return (0, 0, 0)
+ return (g, a // g, b // g)
+
+ x_sign, a = _sign(a)
+ y_sign, b = _sign(b)
+ x, r = 1, 0
+ y, s = 0, 1
+
+ while b:
+ q, c = divmod(a, b)
+ a, b = b, c
+ x, r = r, x - q*r
+ y, s = s, y - q*s
+
+ return (a, x * x_sign, y * y_sign)
+
+
+def is_square(x):
+ """Return True if x is a square number."""
+ if x < 0:
+ return False
+
+ # Note that the possible values of y**2 % n for a given n are limited.
+ # For example, when n=4, y**2 % n can only take 0 or 1.
+ # In other words, if x % 4 is 2 or 3, then x is not a square number.
+ # Mathematically, it determines if it belongs to the set {y**2 % n},
+ # but implementationally, it can be realized as a logical conjunction
+ # with an n-bit integer.
+ # see https://mersenneforum.org/showpost.php?p=110896
+ # def magic(n):
+ # s = {y**2 % n for y in range(n)}
+ # s = set(range(n)) - s
+ # return sum(1 << bit for bit in s)
+ # >>> print(hex(magic(128)))
+ # 0xfdfdfdedfdfdfdecfdfdfdedfdfcfdec
+ # >>> print(hex(magic(99)))
+ # 0x5f6f9ffb6fb7ddfcb75befdec
+ # >>> print(hex(magic(91)))
+ # 0x6fd1bfcfed5f3679d3ebdec
+ # >>> print(hex(magic(85)))
+ # 0xdef9ae771ffe3b9d67dec
+ if 0xfdfdfdedfdfdfdecfdfdfdedfdfcfdec & (1 << (x & 127)):
+ return False # e.g. 2, 3
+ m = x % 765765 # 765765 = 99 * 91 * 85
+ if 0x5f6f9ffb6fb7ddfcb75befdec & (1 << (m % 99)):
+ return False # e.g. 17, 68
+ if 0x6fd1bfcfed5f3679d3ebdec & (1 << (m % 91)):
+ return False # e.g. 97, 388
+ if 0xdef9ae771ffe3b9d67dec & (1 << (m % 85)):
+ return False # e.g. 793, 1408
+ return mlib.sqrtrem(int(x))[1] == 0
+
+
+def invert(x, m):
+ """Modular inverse of x modulo m.
+
+ Returns y such that x*y == 1 mod m.
+
+ Uses ``math.pow`` but reproduces the behaviour of ``gmpy2.invert``
+ which raises ZeroDivisionError if no inverse exists.
+ """
+ try:
+ return pow(x, -1, m)
+ except ValueError:
+ raise ZeroDivisionError("invert() no inverse exists")
+
+
+def legendre(x, y):
+ """Legendre symbol (x / y).
+
+ Following the implementation of gmpy2,
+ the error is raised only when y is an even number.
+ """
+ if y <= 0 or not y % 2:
+ raise ValueError("y should be an odd prime")
+ x %= y
+ if not x:
+ return 0
+ if pow(x, (y - 1) // 2, y) == 1:
+ return 1
+ return -1
+
+
+def jacobi(x, y):
+ """Jacobi symbol (x / y)."""
+ if y <= 0 or not y % 2:
+ raise ValueError("y should be an odd positive integer")
+ x %= y
+ if not x:
+ return int(y == 1)
+ if y == 1 or x == 1:
+ return 1
+ if gcd(x, y) != 1:
+ return 0
+ j = 1
+ while x != 0:
+ while x % 2 == 0 and x > 0:
+ x >>= 1
+ if y % 8 in [3, 5]:
+ j = -j
+ x, y = y, x
+ if x % 4 == y % 4 == 3:
+ j = -j
+ x %= y
+ return j
+
+
+def kronecker(x, y):
+ """Kronecker symbol (x / y)."""
+ if gcd(x, y) != 1:
+ return 0
+ if y == 0:
+ return 1
+ sign = -1 if y < 0 and x < 0 else 1
+ y = abs(y)
+ s = bit_scan1(y)
+ y >>= s
+ if s % 2 and x % 8 in [3, 5]:
+ sign = -sign
+ return sign * jacobi(x, y)
+
+
+def iroot(y, n):
+ if y < 0:
+ raise ValueError("y must be nonnegative")
+ if n < 1:
+ raise ValueError("n must be positive")
+ if y in (0, 1):
+ return y, True
+ if n == 1:
+ return y, True
+ if n == 2:
+ x, rem = mlib.sqrtrem(y)
+ return int(x), not rem
+ if n >= y.bit_length():
+ return 1, False
+ # Get initial estimate for Newton's method. Care must be taken to
+ # avoid overflow
+ try:
+ guess = int(y**(1./n) + 0.5)
+ except OverflowError:
+ exp = math.log2(y)/n
+ if exp > 53:
+ shift = int(exp - 53)
+ guess = int(2.0**(exp - shift) + 1) << shift
+ else:
+ guess = int(2.0**exp)
+ if guess > 2**50:
+ # Newton iteration
+ xprev, x = -1, guess
+ while 1:
+ t = x**(n - 1)
+ xprev, x = x, ((n - 1)*x + y//t)//n
+ if abs(x - xprev) < 2:
+ break
+ else:
+ x = guess
+ # Compensate
+ t = x**n
+ while t < y:
+ x += 1
+ t = x**n
+ while t > y:
+ x -= 1
+ t = x**n
+ return x, t == y
+
+
+def is_fermat_prp(n, a):
+ if a < 2:
+ raise ValueError("is_fermat_prp() requires 'a' greater than or equal to 2")
+ if n < 1:
+ raise ValueError("is_fermat_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ a %= n
+ if gcd(n, a) != 1:
+ raise ValueError("is_fermat_prp() requires gcd(n,a) == 1")
+ return pow(a, n - 1, n) == 1
+
+
+def is_euler_prp(n, a):
+ if a < 2:
+ raise ValueError("is_euler_prp() requires 'a' greater than or equal to 2")
+ if n < 1:
+ raise ValueError("is_euler_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ a %= n
+ if gcd(n, a) != 1:
+ raise ValueError("is_euler_prp() requires gcd(n,a) == 1")
+ return pow(a, n >> 1, n) == jacobi(a, n) % n
+
+
+def _is_strong_prp(n, a):
+ s = bit_scan1(n - 1)
+ a = pow(a, n >> s, n)
+ if a == 1 or a == n - 1:
+ return True
+ for _ in range(s - 1):
+ a = pow(a, 2, n)
+ if a == n - 1:
+ return True
+ if a == 1:
+ return False
+ return False
+
+
+def is_strong_prp(n, a):
+ if a < 2:
+ raise ValueError("is_strong_prp() requires 'a' greater than or equal to 2")
+ if n < 1:
+ raise ValueError("is_strong_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ a %= n
+ if gcd(n, a) != 1:
+ raise ValueError("is_strong_prp() requires gcd(n,a) == 1")
+ return _is_strong_prp(n, a)
+
+
+def _lucas_sequence(n, P, Q, k):
+ r"""Return the modular Lucas sequence (U_k, V_k, Q_k).
+
+ Explanation
+ ===========
+
+ Given a Lucas sequence defined by P, Q, returns the kth values for
+ U and V, along with Q^k, all modulo n. This is intended for use with
+ possibly very large values of n and k, where the combinatorial functions
+ would be completely unusable.
+
+ .. math ::
+ U_k = \begin{cases}
+ 0 & \text{if } k = 0\\
+ 1 & \text{if } k = 1\\
+ PU_{k-1} - QU_{k-2} & \text{if } k > 1
+ \end{cases}\\
+ V_k = \begin{cases}
+ 2 & \text{if } k = 0\\
+ P & \text{if } k = 1\\
+ PV_{k-1} - QV_{k-2} & \text{if } k > 1
+ \end{cases}
+
+ The modular Lucas sequences are used in numerous places in number theory,
+ especially in the Lucas compositeness tests and the various n + 1 proofs.
+
+ Parameters
+ ==========
+
+ n : int
+ n is an odd number greater than or equal to 3
+ P : int
+ Q : int
+ D determined by D = P**2 - 4*Q is non-zero
+ k : int
+ k is a nonnegative integer
+
+ Returns
+ =======
+
+ U, V, Qk : (int, int, int)
+ `(U_k \bmod{n}, V_k \bmod{n}, Q^k \bmod{n})`
+
+ Examples
+ ========
+
+ >>> from sympy.external.ntheory import _lucas_sequence
+ >>> N = 10**2000 + 4561
+ >>> sol = U, V, Qk = _lucas_sequence(N, 3, 1, N//2); sol
+ (0, 2, 1)
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Lucas_sequence
+
+ """
+ if k == 0:
+ return (0, 2, 1)
+ D = P**2 - 4*Q
+ U = 1
+ V = P
+ Qk = Q % n
+ if Q == 1:
+ # Optimization for extra strong tests.
+ for b in bin(k)[3:]:
+ U = (U*V) % n
+ V = (V*V - 2) % n
+ if b == "1":
+ U, V = U*P + V, V*P + U*D
+ if U & 1:
+ U += n
+ if V & 1:
+ V += n
+ U, V = U >> 1, V >> 1
+ elif P == 1 and Q == -1:
+ # Small optimization for 50% of Selfridge parameters.
+ for b in bin(k)[3:]:
+ U = (U*V) % n
+ if Qk == 1:
+ V = (V*V - 2) % n
+ else:
+ V = (V*V + 2) % n
+ Qk = 1
+ if b == "1":
+ # new_U = (U + V) // 2
+ # new_V = (5*U + V) // 2 = 2*U + new_U
+ U, V = U + V, U << 1
+ if U & 1:
+ U += n
+ U >>= 1
+ V += U
+ Qk = -1
+ Qk %= n
+ elif P == 1:
+ for b in bin(k)[3:]:
+ U = (U*V) % n
+ V = (V*V - 2*Qk) % n
+ Qk *= Qk
+ if b == "1":
+ # new_U = (U + V) // 2
+ # new_V = new_U - 2*Q*U
+ U, V = U + V, (Q*U) << 1
+ if U & 1:
+ U += n
+ U >>= 1
+ V = U - V
+ Qk *= Q
+ Qk %= n
+ else:
+ # The general case with any P and Q.
+ for b in bin(k)[3:]:
+ U = (U*V) % n
+ V = (V*V - 2*Qk) % n
+ Qk *= Qk
+ if b == "1":
+ U, V = U*P + V, V*P + U*D
+ if U & 1:
+ U += n
+ if V & 1:
+ V += n
+ U, V = U >> 1, V >> 1
+ Qk *= Q
+ Qk %= n
+ return (U % n, V % n, Qk)
+
+
+def is_fibonacci_prp(n, p, q):
+ d = p**2 - 4*q
+ if d == 0 or p <= 0 or q not in [1, -1]:
+ raise ValueError("invalid values for p,q in is_fibonacci_prp()")
+ if n < 1:
+ raise ValueError("is_fibonacci_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ return _lucas_sequence(n, p, q, n)[1] == p % n
+
+
+def is_lucas_prp(n, p, q):
+ d = p**2 - 4*q
+ if d == 0:
+ raise ValueError("invalid values for p,q in is_lucas_prp()")
+ if n < 1:
+ raise ValueError("is_lucas_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ if gcd(n, q*d) not in [1, n]:
+ raise ValueError("is_lucas_prp() requires gcd(n,2*q*D) == 1")
+ return _lucas_sequence(n, p, q, n - jacobi(d, n))[0] == 0
+
+
+def _is_selfridge_prp(n):
+ """Lucas compositeness test with the Selfridge parameters for n.
+
+ Explanation
+ ===========
+
+ The Lucas compositeness test checks whether n is a prime number.
+ The test can be run with arbitrary parameters ``P`` and ``Q``, which also change the performance of the test.
+ So, which parameters are most effective for running the Lucas compositeness test?
+ As an algorithm for determining ``P`` and ``Q``, Selfridge proposed method A [1]_ page 1401
+ (Since two methods were proposed, referred to simply as A and B in the paper,
+ we will refer to one of them as "method A").
+
+ method A fixes ``P = 1``. Then, ``D`` defined by ``D = P**2 - 4Q`` is varied from 5, -7, 9, -11, 13, and so on,
+ with the first ``D`` being ``jacobi(D, n) == -1``. Once ``D`` is determined,
+ ``Q`` is determined to be ``(P**2 - D)//4``.
+
+ References
+ ==========
+
+ .. [1] Robert Baillie, Samuel S. Wagstaff, Lucas Pseudoprimes,
+ Math. Comp. Vol 35, Number 152 (1980), pp. 1391-1417,
+ https://doi.org/10.1090%2FS0025-5718-1980-0583518-6
+ http://mpqs.free.fr/LucasPseudoprimes.pdf
+
+ """
+ for D in range(5, 1_000_000, 2):
+ if D & 2: # if D % 4 == 3
+ D = -D
+ j = jacobi(D, n)
+ if j == -1:
+ return _lucas_sequence(n, 1, (1-D) // 4, n + 1)[0] == 0
+ if j == 0 and D % n:
+ return False
+ # When j == -1 is hard to find, suspect a square number
+ if D == 13 and is_square(n):
+ return False
+ raise ValueError("appropriate value for D cannot be found in is_selfridge_prp()")
+
+
+def is_selfridge_prp(n):
+ if n < 1:
+ raise ValueError("is_selfridge_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ return _is_selfridge_prp(n)
+
+
+def is_strong_lucas_prp(n, p, q):
+ D = p**2 - 4*q
+ if D == 0:
+ raise ValueError("invalid values for p,q in is_strong_lucas_prp()")
+ if n < 1:
+ raise ValueError("is_selfridge_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ if gcd(n, q*D) not in [1, n]:
+ raise ValueError("is_strong_lucas_prp() requires gcd(n,2*q*D) == 1")
+ j = jacobi(D, n)
+ s = bit_scan1(n - j)
+ U, V, Qk = _lucas_sequence(n, p, q, (n - j) >> s)
+ if U == 0 or V == 0:
+ return True
+ for _ in range(s - 1):
+ V = (V*V - 2*Qk) % n
+ if V == 0:
+ return True
+ Qk = pow(Qk, 2, n)
+ return False
+
+
+def _is_strong_selfridge_prp(n):
+ for D in range(5, 1_000_000, 2):
+ if D & 2: # if D % 4 == 3
+ D = -D
+ j = jacobi(D, n)
+ if j == -1:
+ s = bit_scan1(n + 1)
+ U, V, Qk = _lucas_sequence(n, 1, (1-D) // 4, (n + 1) >> s)
+ if U == 0 or V == 0:
+ return True
+ for _ in range(s - 1):
+ V = (V*V - 2*Qk) % n
+ if V == 0:
+ return True
+ Qk = pow(Qk, 2, n)
+ return False
+ if j == 0 and D % n:
+ return False
+ # When j == -1 is hard to find, suspect a square number
+ if D == 13 and is_square(n):
+ return False
+ raise ValueError("appropriate value for D cannot be found in is_strong_selfridge_prp()")
+
+
+def is_strong_selfridge_prp(n):
+ if n < 1:
+ raise ValueError("is_strong_selfridge_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ return _is_strong_selfridge_prp(n)
+
+
+def is_bpsw_prp(n):
+ if n < 1:
+ raise ValueError("is_bpsw_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ return _is_strong_prp(n, 2) and _is_selfridge_prp(n)
+
+
+def is_strong_bpsw_prp(n):
+ if n < 1:
+ raise ValueError("is_strong_bpsw_prp() requires 'n' be greater than 0")
+ if n == 1:
+ return False
+ if n % 2 == 0:
+ return n == 2
+ return _is_strong_prp(n, 2) and _is_strong_selfridge_prp(n)
diff --git a/lib/python3.12/site-packages/sympy/external/pythonmpq.py b/lib/python3.12/site-packages/sympy/external/pythonmpq.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8efd18a40a75b8a4f4de444b0c10d6347dbca6a
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/pythonmpq.py
@@ -0,0 +1,341 @@
+"""
+PythonMPQ: Rational number type based on Python integers.
+
+This class is intended as a pure Python fallback for when gmpy2 is not
+installed. If gmpy2 is installed then its mpq type will be used instead. The
+mpq type is around 20x faster. We could just use the stdlib Fraction class
+here but that is slower:
+
+ from fractions import Fraction
+ from sympy.external.pythonmpq import PythonMPQ
+ nums = range(1000)
+ dens = range(5, 1005)
+ rats = [Fraction(n, d) for n, d in zip(nums, dens)]
+ sum(rats) # <--- 24 milliseconds
+ rats = [PythonMPQ(n, d) for n, d in zip(nums, dens)]
+ sum(rats) # <--- 7 milliseconds
+
+Both mpq and Fraction have some awkward features like the behaviour of
+division with // and %:
+
+ >>> from fractions import Fraction
+ >>> Fraction(2, 3) % Fraction(1, 4)
+ 1/6
+
+For the QQ domain we do not want this behaviour because there should be no
+remainder when dividing rational numbers. SymPy does not make use of this
+aspect of mpq when gmpy2 is installed. Since this class is a fallback for that
+case we do not bother implementing e.g. __mod__ so that we can be sure we
+are not using it when gmpy2 is installed either.
+"""
+
+
+import operator
+from math import gcd
+from decimal import Decimal
+from fractions import Fraction
+import sys
+from typing import Tuple as tTuple, Type
+
+
+# Used for __hash__
+_PyHASH_MODULUS = sys.hash_info.modulus
+_PyHASH_INF = sys.hash_info.inf
+
+
+class PythonMPQ:
+ """Rational number implementation that is intended to be compatible with
+ gmpy2's mpq.
+
+ Also slightly faster than fractions.Fraction.
+
+ PythonMPQ should be treated as immutable although no effort is made to
+ prevent mutation (since that might slow down calculations).
+ """
+ __slots__ = ('numerator', 'denominator')
+
+ def __new__(cls, numerator, denominator=None):
+ """Construct PythonMPQ with gcd computation and checks"""
+ if denominator is not None:
+ #
+ # PythonMPQ(n, d): require n and d to be int and d != 0
+ #
+ if isinstance(numerator, int) and isinstance(denominator, int):
+ # This is the slow part:
+ divisor = gcd(numerator, denominator)
+ numerator //= divisor
+ denominator //= divisor
+ return cls._new_check(numerator, denominator)
+ else:
+ #
+ # PythonMPQ(q)
+ #
+ # Here q can be PythonMPQ, int, Decimal, float, Fraction or str
+ #
+ if isinstance(numerator, int):
+ return cls._new(numerator, 1)
+ elif isinstance(numerator, PythonMPQ):
+ return cls._new(numerator.numerator, numerator.denominator)
+
+ # Let Fraction handle Decimal/float conversion and str parsing
+ if isinstance(numerator, (Decimal, float, str)):
+ numerator = Fraction(numerator)
+ if isinstance(numerator, Fraction):
+ return cls._new(numerator.numerator, numerator.denominator)
+ #
+ # Reject everything else. This is more strict than mpq which allows
+ # things like mpq(Fraction, Fraction) or mpq(Decimal, any). The mpq
+ # behaviour is somewhat inconsistent so we choose to accept only a
+ # more strict subset of what mpq allows.
+ #
+ raise TypeError("PythonMPQ() requires numeric or string argument")
+
+ @classmethod
+ def _new_check(cls, numerator, denominator):
+ """Construct PythonMPQ, check divide by zero and canonicalize signs"""
+ if not denominator:
+ raise ZeroDivisionError(f'Zero divisor {numerator}/{denominator}')
+ elif denominator < 0:
+ numerator = -numerator
+ denominator = -denominator
+ return cls._new(numerator, denominator)
+
+ @classmethod
+ def _new(cls, numerator, denominator):
+ """Construct PythonMPQ efficiently (no checks)"""
+ obj = super().__new__(cls)
+ obj.numerator = numerator
+ obj.denominator = denominator
+ return obj
+
+ def __int__(self):
+ """Convert to int (truncates towards zero)"""
+ p, q = self.numerator, self.denominator
+ if p < 0:
+ return -(-p//q)
+ return p//q
+
+ def __float__(self):
+ """Convert to float (approximately)"""
+ return self.numerator / self.denominator
+
+ def __bool__(self):
+ """True/False if nonzero/zero"""
+ return bool(self.numerator)
+
+ def __eq__(self, other):
+ """Compare equal with PythonMPQ, int, float, Decimal or Fraction"""
+ if isinstance(other, PythonMPQ):
+ return (self.numerator == other.numerator
+ and self.denominator == other.denominator)
+ elif isinstance(other, self._compatible_types):
+ return self.__eq__(PythonMPQ(other))
+ else:
+ return NotImplemented
+
+ def __hash__(self):
+ """hash - same as mpq/Fraction"""
+ try:
+ dinv = pow(self.denominator, -1, _PyHASH_MODULUS)
+ except ValueError:
+ hash_ = _PyHASH_INF
+ else:
+ hash_ = hash(hash(abs(self.numerator)) * dinv)
+ result = hash_ if self.numerator >= 0 else -hash_
+ return -2 if result == -1 else result
+
+ def __reduce__(self):
+ """Deconstruct for pickling"""
+ return type(self), (self.numerator, self.denominator)
+
+ def __str__(self):
+ """Convert to string"""
+ if self.denominator != 1:
+ return f"{self.numerator}/{self.denominator}"
+ else:
+ return f"{self.numerator}"
+
+ def __repr__(self):
+ """Convert to string"""
+ return f"MPQ({self.numerator},{self.denominator})"
+
+ def _cmp(self, other, op):
+ """Helper for lt/le/gt/ge"""
+ if not isinstance(other, self._compatible_types):
+ return NotImplemented
+ lhs = self.numerator * other.denominator
+ rhs = other.numerator * self.denominator
+ return op(lhs, rhs)
+
+ def __lt__(self, other):
+ """self < other"""
+ return self._cmp(other, operator.lt)
+
+ def __le__(self, other):
+ """self <= other"""
+ return self._cmp(other, operator.le)
+
+ def __gt__(self, other):
+ """self > other"""
+ return self._cmp(other, operator.gt)
+
+ def __ge__(self, other):
+ """self >= other"""
+ return self._cmp(other, operator.ge)
+
+ def __abs__(self):
+ """abs(q)"""
+ return self._new(abs(self.numerator), self.denominator)
+
+ def __pos__(self):
+ """+q"""
+ return self
+
+ def __neg__(self):
+ """-q"""
+ return self._new(-self.numerator, self.denominator)
+
+ def __add__(self, other):
+ """q1 + q2"""
+ if isinstance(other, PythonMPQ):
+ #
+ # This is much faster than the naive method used in the stdlib
+ # fractions module. Not sure where this method comes from
+ # though...
+ #
+ # Compare timings for something like:
+ # nums = range(1000)
+ # rats = [PythonMPQ(n, d) for n, d in zip(nums[:-5], nums[5:])]
+ # sum(rats) # <-- time this
+ #
+ ap, aq = self.numerator, self.denominator
+ bp, bq = other.numerator, other.denominator
+ g = gcd(aq, bq)
+ if g == 1:
+ p = ap*bq + aq*bp
+ q = bq*aq
+ else:
+ q1, q2 = aq//g, bq//g
+ p, q = ap*q2 + bp*q1, q1*q2
+ g2 = gcd(p, g)
+ p, q = (p // g2), q * (g // g2)
+
+ elif isinstance(other, int):
+ p = self.numerator + self.denominator * other
+ q = self.denominator
+ else:
+ return NotImplemented
+
+ return self._new(p, q)
+
+ def __radd__(self, other):
+ """z1 + q2"""
+ if isinstance(other, int):
+ p = self.numerator + self.denominator * other
+ q = self.denominator
+ return self._new(p, q)
+ else:
+ return NotImplemented
+
+ def __sub__(self ,other):
+ """q1 - q2"""
+ if isinstance(other, PythonMPQ):
+ ap, aq = self.numerator, self.denominator
+ bp, bq = other.numerator, other.denominator
+ g = gcd(aq, bq)
+ if g == 1:
+ p = ap*bq - aq*bp
+ q = bq*aq
+ else:
+ q1, q2 = aq//g, bq//g
+ p, q = ap*q2 - bp*q1, q1*q2
+ g2 = gcd(p, g)
+ p, q = (p // g2), q * (g // g2)
+ elif isinstance(other, int):
+ p = self.numerator - self.denominator*other
+ q = self.denominator
+ else:
+ return NotImplemented
+
+ return self._new(p, q)
+
+ def __rsub__(self, other):
+ """z1 - q2"""
+ if isinstance(other, int):
+ p = self.denominator * other - self.numerator
+ q = self.denominator
+ return self._new(p, q)
+ else:
+ return NotImplemented
+
+ def __mul__(self, other):
+ """q1 * q2"""
+ if isinstance(other, PythonMPQ):
+ ap, aq = self.numerator, self.denominator
+ bp, bq = other.numerator, other.denominator
+ x1 = gcd(ap, bq)
+ x2 = gcd(bp, aq)
+ p, q = ((ap//x1)*(bp//x2), (aq//x2)*(bq//x1))
+ elif isinstance(other, int):
+ x = gcd(other, self.denominator)
+ p = self.numerator*(other//x)
+ q = self.denominator//x
+ else:
+ return NotImplemented
+
+ return self._new(p, q)
+
+ def __rmul__(self, other):
+ """z1 * q2"""
+ if isinstance(other, int):
+ x = gcd(self.denominator, other)
+ p = self.numerator*(other//x)
+ q = self.denominator//x
+ return self._new(p, q)
+ else:
+ return NotImplemented
+
+ def __pow__(self, exp):
+ """q ** z"""
+ p, q = self.numerator, self.denominator
+
+ if exp < 0:
+ p, q, exp = q, p, -exp
+
+ return self._new_check(p**exp, q**exp)
+
+ def __truediv__(self, other):
+ """q1 / q2"""
+ if isinstance(other, PythonMPQ):
+ ap, aq = self.numerator, self.denominator
+ bp, bq = other.numerator, other.denominator
+ x1 = gcd(ap, bp)
+ x2 = gcd(bq, aq)
+ p, q = ((ap//x1)*(bq//x2), (aq//x2)*(bp//x1))
+ elif isinstance(other, int):
+ x = gcd(other, self.numerator)
+ p = self.numerator//x
+ q = self.denominator*(other//x)
+ else:
+ return NotImplemented
+
+ return self._new_check(p, q)
+
+ def __rtruediv__(self, other):
+ """z / q"""
+ if isinstance(other, int):
+ x = gcd(self.numerator, other)
+ p = self.denominator*(other//x)
+ q = self.numerator//x
+ return self._new_check(p, q)
+ else:
+ return NotImplemented
+
+ _compatible_types: tTuple[Type, ...] = ()
+
+#
+# These are the types that PythonMPQ will interoperate with for operations
+# and comparisons such as ==, + etc. We define this down here so that we can
+# include PythonMPQ in the list as well.
+#
+PythonMPQ._compatible_types = (PythonMPQ, int, Decimal, Fraction)
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__init__.py b/lib/python3.12/site-packages/sympy/external/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..80fcfa8593191861a70e0fbea00e4219fadeb689
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_autowrap.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_autowrap.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..10c1ab82b29bc43124a3705ccfa081faa286c5aa
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_autowrap.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_codegen.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_codegen.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..89876ccd534231b3e4cd5feaddd3d5ef3bca243b
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_codegen.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_gmpy.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_gmpy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5a550eb759a1d53a8f3ee0d4817228071ffb0fad
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_gmpy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_importtools.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_importtools.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fb9172922bfcc434c8b4b14e383fed091c5e4bf4
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_importtools.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_ntheory.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_ntheory.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ab34f83beea8f2b9d041509eaf5e11671be73326
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_ntheory.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_numpy.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_numpy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a562b2f3d722b4cc907623a0f53160068b489bc5
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_numpy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_pythonmpq.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_pythonmpq.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7cb3d987cdcf0779880fb399c7ce2466f06b9137
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_pythonmpq.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_scipy.cpython-312.pyc b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_scipy.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f03c1ef70c95d9604053008d19aefc5ff7707d04
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/external/tests/__pycache__/test_scipy.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/external/tests/test_autowrap.py b/lib/python3.12/site-packages/sympy/external/tests/test_autowrap.py
new file mode 100644
index 0000000000000000000000000000000000000000..50ba10f9de911a8f9532af2c3e0cd1979cad1aa4
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/tests/test_autowrap.py
@@ -0,0 +1,313 @@
+import sympy
+import tempfile
+import os
+from sympy.core.mod import Mod
+from sympy.core.relational import Eq
+from sympy.core.symbol import symbols
+from sympy.external import import_module
+from sympy.tensor import IndexedBase, Idx
+from sympy.utilities.autowrap import autowrap, ufuncify, CodeWrapError
+from sympy.testing.pytest import skip
+
+numpy = import_module('numpy', min_module_version='1.6.1')
+Cython = import_module('Cython', min_module_version='0.15.1')
+f2py = import_module('numpy.f2py', import_kwargs={'fromlist': ['f2py']})
+
+f2pyworks = False
+if f2py:
+ try:
+ autowrap(symbols('x'), 'f95', 'f2py')
+ except (CodeWrapError, ImportError, OSError):
+ f2pyworks = False
+ else:
+ f2pyworks = True
+
+a, b, c = symbols('a b c')
+n, m, d = symbols('n m d', integer=True)
+A, B, C = symbols('A B C', cls=IndexedBase)
+i = Idx('i', m)
+j = Idx('j', n)
+k = Idx('k', d)
+
+
+def has_module(module):
+ """
+ Return True if module exists, otherwise run skip().
+
+ module should be a string.
+ """
+ # To give a string of the module name to skip(), this function takes a
+ # string. So we don't waste time running import_module() more than once,
+ # just map the three modules tested here in this dict.
+ modnames = {'numpy': numpy, 'Cython': Cython, 'f2py': f2py}
+
+ if modnames[module]:
+ if module == 'f2py' and not f2pyworks:
+ skip("Couldn't run f2py.")
+ return True
+ skip("Couldn't import %s." % module)
+
+#
+# test runners used by several language-backend combinations
+#
+
+def runtest_autowrap_twice(language, backend):
+ f = autowrap((((a + b)/c)**5).expand(), language, backend)
+ g = autowrap((((a + b)/c)**4).expand(), language, backend)
+
+ # check that autowrap updates the module name. Else, g gives the same as f
+ assert f(1, -2, 1) == -1.0
+ assert g(1, -2, 1) == 1.0
+
+
+def runtest_autowrap_trace(language, backend):
+ has_module('numpy')
+ trace = autowrap(A[i, i], language, backend)
+ assert trace(numpy.eye(100)) == 100
+
+
+def runtest_autowrap_matrix_vector(language, backend):
+ has_module('numpy')
+ x, y = symbols('x y', cls=IndexedBase)
+ expr = Eq(y[i], A[i, j]*x[j])
+ mv = autowrap(expr, language, backend)
+
+ # compare with numpy's dot product
+ M = numpy.random.rand(10, 20)
+ x = numpy.random.rand(20)
+ y = numpy.dot(M, x)
+ assert numpy.sum(numpy.abs(y - mv(M, x))) < 1e-13
+
+
+def runtest_autowrap_matrix_matrix(language, backend):
+ has_module('numpy')
+ expr = Eq(C[i, j], A[i, k]*B[k, j])
+ matmat = autowrap(expr, language, backend)
+
+ # compare with numpy's dot product
+ M1 = numpy.random.rand(10, 20)
+ M2 = numpy.random.rand(20, 15)
+ M3 = numpy.dot(M1, M2)
+ assert numpy.sum(numpy.abs(M3 - matmat(M1, M2))) < 1e-13
+
+
+def runtest_ufuncify(language, backend):
+ has_module('numpy')
+ a, b, c = symbols('a b c')
+ fabc = ufuncify([a, b, c], a*b + c, backend=backend)
+ facb = ufuncify([a, c, b], a*b + c, backend=backend)
+ grid = numpy.linspace(-2, 2, 50)
+ b = numpy.linspace(-5, 4, 50)
+ c = numpy.linspace(-1, 1, 50)
+ expected = grid*b + c
+ numpy.testing.assert_allclose(fabc(grid, b, c), expected)
+ numpy.testing.assert_allclose(facb(grid, c, b), expected)
+
+
+def runtest_issue_10274(language, backend):
+ expr = (a - b + c)**(13)
+ tmp = tempfile.mkdtemp()
+ f = autowrap(expr, language, backend, tempdir=tmp,
+ helpers=('helper', a - b + c, (a, b, c)))
+ assert f(1, 1, 1) == 1
+
+ for file in os.listdir(tmp):
+ if not (file.startswith("wrapped_code_") and file.endswith(".c")):
+ continue
+
+ with open(tmp + '/' + file) as fil:
+ lines = fil.readlines()
+ assert lines[0] == "/******************************************************************************\n"
+ assert "Code generated with SymPy " + sympy.__version__ in lines[1]
+ assert lines[2:] == [
+ " * *\n",
+ " * See http://www.sympy.org/ for more information. *\n",
+ " * *\n",
+ " * This file is part of 'autowrap' *\n",
+ " ******************************************************************************/\n",
+ "#include " + '"' + file[:-1]+ 'h"' + "\n",
+ "#include \n",
+ "\n",
+ "double helper(double a, double b, double c) {\n",
+ "\n",
+ " double helper_result;\n",
+ " helper_result = a - b + c;\n",
+ " return helper_result;\n",
+ "\n",
+ "}\n",
+ "\n",
+ "double autofunc(double a, double b, double c) {\n",
+ "\n",
+ " double autofunc_result;\n",
+ " autofunc_result = pow(helper(a, b, c), 13);\n",
+ " return autofunc_result;\n",
+ "\n",
+ "}\n",
+ ]
+
+
+def runtest_issue_15337(language, backend):
+ has_module('numpy')
+ # NOTE : autowrap was originally designed to only accept an iterable for
+ # the kwarg "helpers", but in issue 10274 the user mistakenly thought that
+ # if there was only a single helper it did not need to be passed via an
+ # iterable that wrapped the helper tuple. There were no tests for this
+ # behavior so when the code was changed to accept a single tuple it broke
+ # the original behavior. These tests below ensure that both now work.
+ a, b, c, d, e = symbols('a, b, c, d, e')
+ expr = (a - b + c - d + e)**13
+ exp_res = (1. - 2. + 3. - 4. + 5.)**13
+
+ f = autowrap(expr, language, backend, args=(a, b, c, d, e),
+ helpers=('f1', a - b + c, (a, b, c)))
+ numpy.testing.assert_allclose(f(1, 2, 3, 4, 5), exp_res)
+
+ f = autowrap(expr, language, backend, args=(a, b, c, d, e),
+ helpers=(('f1', a - b, (a, b)), ('f2', c - d, (c, d))))
+ numpy.testing.assert_allclose(f(1, 2, 3, 4, 5), exp_res)
+
+
+def test_issue_15230():
+ has_module('f2py')
+
+ x, y = symbols('x, y')
+ expr = Mod(x, 3.0) - Mod(y, -2.0)
+ f = autowrap(expr, args=[x, y], language='F95')
+ exp_res = float(expr.xreplace({x: 3.5, y: 2.7}).evalf())
+ assert abs(f(3.5, 2.7) - exp_res) < 1e-14
+
+ x, y = symbols('x, y', integer=True)
+ expr = Mod(x, 3) - Mod(y, -2)
+ f = autowrap(expr, args=[x, y], language='F95')
+ assert f(3, 2) == expr.xreplace({x: 3, y: 2})
+
+#
+# tests of language-backend combinations
+#
+
+# f2py
+
+
+def test_wrap_twice_f95_f2py():
+ has_module('f2py')
+ runtest_autowrap_twice('f95', 'f2py')
+
+
+def test_autowrap_trace_f95_f2py():
+ has_module('f2py')
+ runtest_autowrap_trace('f95', 'f2py')
+
+
+def test_autowrap_matrix_vector_f95_f2py():
+ has_module('f2py')
+ runtest_autowrap_matrix_vector('f95', 'f2py')
+
+
+def test_autowrap_matrix_matrix_f95_f2py():
+ has_module('f2py')
+ runtest_autowrap_matrix_matrix('f95', 'f2py')
+
+
+def test_ufuncify_f95_f2py():
+ has_module('f2py')
+ runtest_ufuncify('f95', 'f2py')
+
+
+def test_issue_15337_f95_f2py():
+ has_module('f2py')
+ runtest_issue_15337('f95', 'f2py')
+
+# Cython
+
+
+def test_wrap_twice_c_cython():
+ has_module('Cython')
+ runtest_autowrap_twice('C', 'cython')
+
+
+def test_autowrap_trace_C_Cython():
+ has_module('Cython')
+ runtest_autowrap_trace('C99', 'cython')
+
+
+def test_autowrap_matrix_vector_C_cython():
+ has_module('Cython')
+ runtest_autowrap_matrix_vector('C99', 'cython')
+
+
+def test_autowrap_matrix_matrix_C_cython():
+ has_module('Cython')
+ runtest_autowrap_matrix_matrix('C99', 'cython')
+
+
+def test_ufuncify_C_Cython():
+ has_module('Cython')
+ runtest_ufuncify('C99', 'cython')
+
+
+def test_issue_10274_C_cython():
+ has_module('Cython')
+ runtest_issue_10274('C89', 'cython')
+
+
+def test_issue_15337_C_cython():
+ has_module('Cython')
+ runtest_issue_15337('C89', 'cython')
+
+
+def test_autowrap_custom_printer():
+ has_module('Cython')
+
+ from sympy.core.numbers import pi
+ from sympy.utilities.codegen import C99CodeGen
+ from sympy.printing.c import C99CodePrinter
+
+ class PiPrinter(C99CodePrinter):
+ def _print_Pi(self, expr):
+ return "S_PI"
+
+ printer = PiPrinter()
+ gen = C99CodeGen(printer=printer)
+ gen.preprocessor_statements.append('#include "shortpi.h"')
+
+ expr = pi * a
+
+ expected = (
+ '#include "%s"\n'
+ '#include \n'
+ '#include "shortpi.h"\n'
+ '\n'
+ 'double autofunc(double a) {\n'
+ '\n'
+ ' double autofunc_result;\n'
+ ' autofunc_result = S_PI*a;\n'
+ ' return autofunc_result;\n'
+ '\n'
+ '}\n'
+ )
+
+ tmpdir = tempfile.mkdtemp()
+ # write a trivial header file to use in the generated code
+ with open(os.path.join(tmpdir, 'shortpi.h'), 'w') as f:
+ f.write('#define S_PI 3.14')
+
+ func = autowrap(expr, backend='cython', tempdir=tmpdir, code_gen=gen)
+
+ assert func(4.2) == 3.14 * 4.2
+
+ # check that the generated code is correct
+ for filename in os.listdir(tmpdir):
+ if filename.startswith('wrapped_code') and filename.endswith('.c'):
+ with open(os.path.join(tmpdir, filename)) as f:
+ lines = f.readlines()
+ expected = expected % filename.replace('.c', '.h')
+ assert ''.join(lines[7:]) == expected
+
+
+# Numpy
+
+def test_ufuncify_numpy():
+ # This test doesn't use Cython, but if Cython works, then there is a valid
+ # C compiler, which is needed.
+ has_module('Cython')
+ runtest_ufuncify('C99', 'numpy')
diff --git a/lib/python3.12/site-packages/sympy/external/tests/test_codegen.py b/lib/python3.12/site-packages/sympy/external/tests/test_codegen.py
new file mode 100644
index 0000000000000000000000000000000000000000..5449531a1217753c3c9d68cfec4d38852aeb0f8c
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/tests/test_codegen.py
@@ -0,0 +1,379 @@
+# This tests the compilation and execution of the source code generated with
+# utilities.codegen. The compilation takes place in a temporary directory that
+# is removed after the test. By default the test directory is always removed,
+# but this behavior can be changed by setting the environment variable
+# SYMPY_TEST_CLEAN_TEMP to:
+# export SYMPY_TEST_CLEAN_TEMP=always : the default behavior.
+# export SYMPY_TEST_CLEAN_TEMP=success : only remove the directories of working tests.
+# export SYMPY_TEST_CLEAN_TEMP=never : never remove the directories with the test code.
+# When a directory is not removed, the necessary information is printed on
+# screen to find the files that belong to the (failed) tests. If a test does
+# not fail, py.test captures all the output and you will not see the directories
+# corresponding to the successful tests. Use the --nocapture option to see all
+# the output.
+
+# All tests below have a counterpart in utilities/test/test_codegen.py. In the
+# latter file, the resulting code is compared with predefined strings, without
+# compilation or execution.
+
+# All the generated Fortran code should conform with the Fortran 95 standard,
+# and all the generated C code should be ANSI C, which facilitates the
+# incorporation in various projects. The tests below assume that the binary cc
+# is somewhere in the path and that it can compile ANSI C code.
+
+from sympy.abc import x, y, z
+from sympy.external import import_module
+from sympy.testing.pytest import skip
+from sympy.utilities.codegen import codegen, make_routine, get_code_generator
+import sys
+import os
+import tempfile
+import subprocess
+
+
+pyodide_js = import_module('pyodide_js')
+
+# templates for the main program that will test the generated code.
+
+main_template = {}
+main_template['F95'] = """
+program main
+ include "codegen.h"
+ integer :: result;
+ result = 0
+
+ %(statements)s
+
+ call exit(result)
+end program
+"""
+
+main_template['C89'] = """
+#include "codegen.h"
+#include
+#include
+
+int main() {
+ int result = 0;
+
+ %(statements)s
+
+ return result;
+}
+"""
+main_template['C99'] = main_template['C89']
+# templates for the numerical tests
+
+numerical_test_template = {}
+numerical_test_template['C89'] = """
+ if (fabs(%(call)s)>%(threshold)s) {
+ printf("Numerical validation failed: %(call)s=%%e threshold=%(threshold)s\\n", %(call)s);
+ result = -1;
+ }
+"""
+numerical_test_template['C99'] = numerical_test_template['C89']
+
+numerical_test_template['F95'] = """
+ if (abs(%(call)s)>%(threshold)s) then
+ write(6,"('Numerical validation failed:')")
+ write(6,"('%(call)s=',e15.5,'threshold=',e15.5)") %(call)s, %(threshold)s
+ result = -1;
+ end if
+"""
+# command sequences for supported compilers
+
+compile_commands = {}
+compile_commands['cc'] = [
+ "cc -c codegen.c -o codegen.o",
+ "cc -c main.c -o main.o",
+ "cc main.o codegen.o -lm -o test.exe"
+]
+
+compile_commands['gfortran'] = [
+ "gfortran -c codegen.f90 -o codegen.o",
+ "gfortran -ffree-line-length-none -c main.f90 -o main.o",
+ "gfortran main.o codegen.o -o test.exe"
+]
+
+compile_commands['g95'] = [
+ "g95 -c codegen.f90 -o codegen.o",
+ "g95 -ffree-line-length-huge -c main.f90 -o main.o",
+ "g95 main.o codegen.o -o test.exe"
+]
+
+compile_commands['ifort'] = [
+ "ifort -c codegen.f90 -o codegen.o",
+ "ifort -c main.f90 -o main.o",
+ "ifort main.o codegen.o -o test.exe"
+]
+
+combinations_lang_compiler = [
+ ('C89', 'cc'),
+ ('C99', 'cc'),
+ ('F95', 'ifort'),
+ ('F95', 'gfortran'),
+ ('F95', 'g95')
+]
+
+
+def try_run(commands):
+ """Run a series of commands and only return True if all ran fine."""
+ if pyodide_js:
+ return False
+ with open(os.devnull, 'w') as null:
+ for command in commands:
+ retcode = subprocess.call(command, stdout=null, shell=True,
+ stderr=subprocess.STDOUT)
+ if retcode != 0:
+ return False
+ return True
+
+
+def run_test(label, routines, numerical_tests, language, commands, friendly=True):
+ """A driver for the codegen tests.
+
+ This driver assumes that a compiler ifort is present in the PATH and that
+ ifort is (at least) a Fortran 90 compiler. The generated code is written in
+ a temporary directory, together with a main program that validates the
+ generated code. The test passes when the compilation and the validation
+ run correctly.
+ """
+
+ # Check input arguments before touching the file system
+ language = language.upper()
+ assert language in main_template
+ assert language in numerical_test_template
+
+ # Check that environment variable makes sense
+ clean = os.getenv('SYMPY_TEST_CLEAN_TEMP', 'always').lower()
+ if clean not in ('always', 'success', 'never'):
+ raise ValueError("SYMPY_TEST_CLEAN_TEMP must be one of the following: 'always', 'success' or 'never'.")
+
+ # Do all the magic to compile, run and validate the test code
+ # 1) prepare the temporary working directory, switch to that dir
+ work = tempfile.mkdtemp("_sympy_%s_test" % language, "%s_" % label)
+ oldwork = os.getcwd()
+ os.chdir(work)
+
+ # 2) write the generated code
+ if friendly:
+ # interpret the routines as a name_expr list and call the friendly
+ # function codegen
+ codegen(routines, language, "codegen", to_files=True)
+ else:
+ code_gen = get_code_generator(language, "codegen")
+ code_gen.write(routines, "codegen", to_files=True)
+
+ # 3) write a simple main program that links to the generated code, and that
+ # includes the numerical tests
+ test_strings = []
+ for fn_name, args, expected, threshold in numerical_tests:
+ call_string = "%s(%s)-(%s)" % (
+ fn_name, ",".join(str(arg) for arg in args), expected)
+ if language == "F95":
+ call_string = fortranize_double_constants(call_string)
+ threshold = fortranize_double_constants(str(threshold))
+ test_strings.append(numerical_test_template[language] % {
+ "call": call_string,
+ "threshold": threshold,
+ })
+
+ if language == "F95":
+ f_name = "main.f90"
+ elif language.startswith("C"):
+ f_name = "main.c"
+ else:
+ raise NotImplementedError(
+ "FIXME: filename extension unknown for language: %s" % language)
+
+ with open(f_name, "w") as f:
+ f.write(
+ main_template[language] % {'statements': "".join(test_strings)})
+
+ # 4) Compile and link
+ compiled = try_run(commands)
+
+ # 5) Run if compiled
+ if compiled:
+ executed = try_run(["./test.exe"])
+ else:
+ executed = False
+
+ # 6) Clean up stuff
+ if clean == 'always' or (clean == 'success' and compiled and executed):
+ def safe_remove(filename):
+ if os.path.isfile(filename):
+ os.remove(filename)
+ safe_remove("codegen.f90")
+ safe_remove("codegen.c")
+ safe_remove("codegen.h")
+ safe_remove("codegen.o")
+ safe_remove("main.f90")
+ safe_remove("main.c")
+ safe_remove("main.o")
+ safe_remove("test.exe")
+ os.chdir(oldwork)
+ os.rmdir(work)
+ else:
+ print("TEST NOT REMOVED: %s" % work, file=sys.stderr)
+ os.chdir(oldwork)
+
+ # 7) Do the assertions in the end
+ assert compiled, "failed to compile %s code with:\n%s" % (
+ language, "\n".join(commands))
+ assert executed, "failed to execute %s code from:\n%s" % (
+ language, "\n".join(commands))
+
+
+def fortranize_double_constants(code_string):
+ """
+ Replaces every literal float with literal doubles
+ """
+ import re
+ pattern_exp = re.compile(r'\d+(\.)?\d*[eE]-?\d+')
+ pattern_float = re.compile(r'\d+\.\d*(?!\d*d)')
+
+ def subs_exp(matchobj):
+ return re.sub('[eE]', 'd', matchobj.group(0))
+
+ def subs_float(matchobj):
+ return "%sd0" % matchobj.group(0)
+
+ code_string = pattern_exp.sub(subs_exp, code_string)
+ code_string = pattern_float.sub(subs_float, code_string)
+
+ return code_string
+
+
+def is_feasible(language, commands):
+ # This test should always work, otherwise the compiler is not present.
+ routine = make_routine("test", x)
+ numerical_tests = [
+ ("test", ( 1.0,), 1.0, 1e-15),
+ ("test", (-1.0,), -1.0, 1e-15),
+ ]
+ try:
+ run_test("is_feasible", [routine], numerical_tests, language, commands,
+ friendly=False)
+ return True
+ except AssertionError:
+ return False
+
+valid_lang_commands = []
+invalid_lang_compilers = []
+for lang, compiler in combinations_lang_compiler:
+ commands = compile_commands[compiler]
+ if is_feasible(lang, commands):
+ valid_lang_commands.append((lang, commands))
+ else:
+ invalid_lang_compilers.append((lang, compiler))
+
+# We test all language-compiler combinations, just to report what is skipped
+
+def test_C89_cc():
+ if ("C89", 'cc') in invalid_lang_compilers:
+ skip("`cc' command didn't work as expected (C89)")
+
+
+def test_C99_cc():
+ if ("C99", 'cc') in invalid_lang_compilers:
+ skip("`cc' command didn't work as expected (C99)")
+
+
+def test_F95_ifort():
+ if ("F95", 'ifort') in invalid_lang_compilers:
+ skip("`ifort' command didn't work as expected")
+
+
+def test_F95_gfortran():
+ if ("F95", 'gfortran') in invalid_lang_compilers:
+ skip("`gfortran' command didn't work as expected")
+
+
+def test_F95_g95():
+ if ("F95", 'g95') in invalid_lang_compilers:
+ skip("`g95' command didn't work as expected")
+
+# Here comes the actual tests
+
+
+def test_basic_codegen():
+ numerical_tests = [
+ ("test", (1.0, 6.0, 3.0), 21.0, 1e-15),
+ ("test", (-1.0, 2.0, -2.5), -2.5, 1e-15),
+ ]
+ name_expr = [("test", (x + y)*z)]
+ for lang, commands in valid_lang_commands:
+ run_test("basic_codegen", name_expr, numerical_tests, lang, commands)
+
+
+def test_intrinsic_math1_codegen():
+ # not included: log10
+ from sympy.core.evalf import N
+ from sympy.functions import ln
+ from sympy.functions.elementary.exponential import log
+ from sympy.functions.elementary.hyperbolic import (cosh, sinh, tanh)
+ from sympy.functions.elementary.integers import (ceiling, floor)
+ from sympy.functions.elementary.miscellaneous import sqrt
+ from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sin, tan)
+ name_expr = [
+ ("test_fabs", abs(x)),
+ ("test_acos", acos(x)),
+ ("test_asin", asin(x)),
+ ("test_atan", atan(x)),
+ ("test_cos", cos(x)),
+ ("test_cosh", cosh(x)),
+ ("test_log", log(x)),
+ ("test_ln", ln(x)),
+ ("test_sin", sin(x)),
+ ("test_sinh", sinh(x)),
+ ("test_sqrt", sqrt(x)),
+ ("test_tan", tan(x)),
+ ("test_tanh", tanh(x)),
+ ]
+ numerical_tests = []
+ for name, expr in name_expr:
+ for xval in 0.2, 0.5, 0.8:
+ expected = N(expr.subs(x, xval))
+ numerical_tests.append((name, (xval,), expected, 1e-14))
+ for lang, commands in valid_lang_commands:
+ if lang.startswith("C"):
+ name_expr_C = [("test_floor", floor(x)), ("test_ceil", ceiling(x))]
+ else:
+ name_expr_C = []
+ run_test("intrinsic_math1", name_expr + name_expr_C,
+ numerical_tests, lang, commands)
+
+
+def test_instrinsic_math2_codegen():
+ # not included: frexp, ldexp, modf, fmod
+ from sympy.core.evalf import N
+ from sympy.functions.elementary.trigonometric import atan2
+ name_expr = [
+ ("test_atan2", atan2(x, y)),
+ ("test_pow", x**y),
+ ]
+ numerical_tests = []
+ for name, expr in name_expr:
+ for xval, yval in (0.2, 1.3), (0.5, -0.2), (0.8, 0.8):
+ expected = N(expr.subs(x, xval).subs(y, yval))
+ numerical_tests.append((name, (xval, yval), expected, 1e-14))
+ for lang, commands in valid_lang_commands:
+ run_test("intrinsic_math2", name_expr, numerical_tests, lang, commands)
+
+
+def test_complicated_codegen():
+ from sympy.core.evalf import N
+ from sympy.functions.elementary.trigonometric import (cos, sin, tan)
+ name_expr = [
+ ("test1", ((sin(x) + cos(y) + tan(z))**7).expand()),
+ ("test2", cos(cos(cos(cos(cos(cos(cos(cos(x + y + z))))))))),
+ ]
+ numerical_tests = []
+ for name, expr in name_expr:
+ for xval, yval, zval in (0.2, 1.3, -0.3), (0.5, -0.2, 0.0), (0.8, 2.1, 0.8):
+ expected = N(expr.subs(x, xval).subs(y, yval).subs(z, zval))
+ numerical_tests.append((name, (xval, yval, zval), expected, 1e-12))
+ for lang, commands in valid_lang_commands:
+ run_test(
+ "complicated_codegen", name_expr, numerical_tests, lang, commands)
diff --git a/lib/python3.12/site-packages/sympy/external/tests/test_gmpy.py b/lib/python3.12/site-packages/sympy/external/tests/test_gmpy.py
new file mode 100644
index 0000000000000000000000000000000000000000..d88f9da0c6c26c15f529ce485fff5b72342170ea
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/tests/test_gmpy.py
@@ -0,0 +1,12 @@
+from sympy.external.gmpy import LONG_MAX, iroot
+from sympy.testing.pytest import raises
+
+
+def test_iroot():
+ assert iroot(2, LONG_MAX) == (1, False)
+ assert iroot(2, LONG_MAX + 1) == (1, False)
+ for x in range(3):
+ assert iroot(x, 1) == (x, True)
+ raises(ValueError, lambda: iroot(-1, 1))
+ raises(ValueError, lambda: iroot(0, 0))
+ raises(ValueError, lambda: iroot(0, -1))
diff --git a/lib/python3.12/site-packages/sympy/external/tests/test_importtools.py b/lib/python3.12/site-packages/sympy/external/tests/test_importtools.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b954070c179282ed2bcf5735d802c5f22a3a261
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/tests/test_importtools.py
@@ -0,0 +1,40 @@
+from sympy.external import import_module
+from sympy.testing.pytest import warns
+
+# fixes issue that arose in addressing issue 6533
+def test_no_stdlib_collections():
+ '''
+ make sure we get the right collections when it is not part of a
+ larger list
+ '''
+ import collections
+ matplotlib = import_module('matplotlib',
+ import_kwargs={'fromlist': ['cm', 'collections']},
+ min_module_version='1.1.0', catch=(RuntimeError,))
+ if matplotlib:
+ assert collections != matplotlib.collections
+
+def test_no_stdlib_collections2():
+ '''
+ make sure we get the right collections when it is not part of a
+ larger list
+ '''
+ import collections
+ matplotlib = import_module('matplotlib',
+ import_kwargs={'fromlist': ['collections']},
+ min_module_version='1.1.0', catch=(RuntimeError,))
+ if matplotlib:
+ assert collections != matplotlib.collections
+
+def test_no_stdlib_collections3():
+ '''make sure we get the right collections with no catch'''
+ import collections
+ matplotlib = import_module('matplotlib',
+ import_kwargs={'fromlist': ['cm', 'collections']},
+ min_module_version='1.1.0')
+ if matplotlib:
+ assert collections != matplotlib.collections
+
+def test_min_module_version_python3_basestring_error():
+ with warns(UserWarning):
+ import_module('mpmath', min_module_version='1000.0.1')
diff --git a/lib/python3.12/site-packages/sympy/external/tests/test_ntheory.py b/lib/python3.12/site-packages/sympy/external/tests/test_ntheory.py
new file mode 100644
index 0000000000000000000000000000000000000000..00824481ad27aa9071ea5801fb3bde75cacbc3c8
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/tests/test_ntheory.py
@@ -0,0 +1,307 @@
+from itertools import permutations
+
+from sympy.external.ntheory import (bit_scan1, remove, bit_scan0, is_fermat_prp,
+ is_euler_prp, is_strong_prp, gcdext, _lucas_sequence,
+ is_fibonacci_prp, is_lucas_prp, is_selfridge_prp,
+ is_strong_lucas_prp, is_strong_selfridge_prp,
+ is_bpsw_prp, is_strong_bpsw_prp)
+from sympy.testing.pytest import raises
+
+
+def test_bit_scan1():
+ assert bit_scan1(0) is None
+ assert bit_scan1(1) == 0
+ assert bit_scan1(-1) == 0
+ assert bit_scan1(2) == 1
+ assert bit_scan1(7) == 0
+ assert bit_scan1(-7) == 0
+ for i in range(100):
+ assert bit_scan1(1 << i) == i
+ assert bit_scan1((1 << i) * 31337) == i
+ for i in range(500):
+ n = (1 << 500) + (1 << i)
+ assert bit_scan1(n) == i
+ assert bit_scan1(1 << 1000001) == 1000001
+ assert bit_scan1((1 << 273956)*7**37) == 273956
+ # issue 12709
+ for i in range(1, 10):
+ big = 1 << i
+ assert bit_scan1(-big) == bit_scan1(big)
+
+
+def test_bit_scan0():
+ assert bit_scan0(-1) is None
+ assert bit_scan0(0) == 0
+ assert bit_scan0(1) == 1
+ assert bit_scan0(-2) == 0
+
+
+def test_remove():
+ raises(ValueError, lambda: remove(1, 1))
+ assert remove(0, 3) == (0, 0)
+ for f in range(2, 10):
+ for y in range(2, 1000):
+ for z in [1, 17, 101, 1009]:
+ assert remove(z*f**y, f) == (z, y)
+
+
+def test_gcdext():
+ assert gcdext(0, 0) == (0, 0, 0)
+ assert gcdext(3, 0) == (3, 1, 0)
+ assert gcdext(0, 4) == (4, 0, 1)
+
+ for n in range(1, 10):
+ assert gcdext(n, 1) == gcdext(-n, 1) == (1, 0, 1)
+ assert gcdext(n, -1) == gcdext(-n, -1) == (1, 0, -1)
+ assert gcdext(n, n) == gcdext(-n, n) == (n, 0, 1)
+ assert gcdext(n, -n) == gcdext(-n, -n) == (n, 0, -1)
+
+ for n in range(2, 10):
+ assert gcdext(1, n) == gcdext(1, -n) == (1, 1, 0)
+ assert gcdext(-1, n) == gcdext(-1, -n) == (1, -1, 0)
+
+ for a, b in permutations([2**5, 3, 5, 7**2, 11], 2):
+ g, x, y = gcdext(a, b)
+ assert g == a*x + b*y == 1
+
+
+def test_is_fermat_prp():
+ # invalid input
+ raises(ValueError, lambda: is_fermat_prp(0, 10))
+ raises(ValueError, lambda: is_fermat_prp(5, 1))
+
+ # n = 1
+ assert not is_fermat_prp(1, 3)
+
+ # n is prime
+ assert is_fermat_prp(2, 4)
+ assert is_fermat_prp(3, 2)
+ assert is_fermat_prp(11, 3)
+ assert is_fermat_prp(2**31-1, 5)
+
+ # A001567
+ pseudorpime = [341, 561, 645, 1105, 1387, 1729, 1905, 2047,
+ 2465, 2701, 2821, 3277, 4033, 4369, 4371, 4681]
+ for n in pseudorpime:
+ assert is_fermat_prp(n, 2)
+
+ # A020136
+ pseudorpime = [15, 85, 91, 341, 435, 451, 561, 645, 703, 1105,
+ 1247, 1271, 1387, 1581, 1695, 1729, 1891, 1905]
+ for n in pseudorpime:
+ assert is_fermat_prp(n, 4)
+
+
+def test_is_euler_prp():
+ # invalid input
+ raises(ValueError, lambda: is_euler_prp(0, 10))
+ raises(ValueError, lambda: is_euler_prp(5, 1))
+
+ # n = 1
+ assert not is_euler_prp(1, 3)
+
+ # n is prime
+ assert is_euler_prp(2, 4)
+ assert is_euler_prp(3, 2)
+ assert is_euler_prp(11, 3)
+ assert is_euler_prp(2**31-1, 5)
+
+ # A047713
+ pseudorpime = [561, 1105, 1729, 1905, 2047, 2465, 3277, 4033,
+ 4681, 6601, 8321, 8481, 10585, 12801, 15841]
+ for n in pseudorpime:
+ assert is_euler_prp(n, 2)
+
+ # A048950
+ pseudorpime = [121, 703, 1729, 1891, 2821, 3281, 7381, 8401,
+ 8911, 10585, 12403, 15457, 15841, 16531, 18721]
+ for n in pseudorpime:
+ assert is_euler_prp(n, 3)
+
+
+def test_is_strong_prp():
+ # invalid input
+ raises(ValueError, lambda: is_strong_prp(0, 10))
+ raises(ValueError, lambda: is_strong_prp(5, 1))
+
+ # n = 1
+ assert not is_strong_prp(1, 3)
+
+ # n is prime
+ assert is_strong_prp(2, 4)
+ assert is_strong_prp(3, 2)
+ assert is_strong_prp(11, 3)
+ assert is_strong_prp(2**31-1, 5)
+
+ # A001262
+ pseudorpime = [2047, 3277, 4033, 4681, 8321, 15841, 29341,
+ 42799, 49141, 52633, 65281, 74665, 80581]
+ for n in pseudorpime:
+ assert is_strong_prp(n, 2)
+
+ # A020229
+ pseudorpime = [121, 703, 1891, 3281, 8401, 8911, 10585, 12403,
+ 16531, 18721, 19345, 23521, 31621, 44287, 47197]
+ for n in pseudorpime:
+ assert is_strong_prp(n, 3)
+
+
+def test_lucas_sequence():
+ def lucas_u(P, Q, length):
+ array = [0] * length
+ array[1] = 1
+ for k in range(2, length):
+ array[k] = P * array[k - 1] - Q * array[k - 2]
+ return array
+
+ def lucas_v(P, Q, length):
+ array = [0] * length
+ array[0] = 2
+ array[1] = P
+ for k in range(2, length):
+ array[k] = P * array[k - 1] - Q * array[k - 2]
+ return array
+
+ length = 20
+ for P in range(-10, 10):
+ for Q in range(-10, 10):
+ D = P**2 - 4*Q
+ if D == 0:
+ continue
+ us = lucas_u(P, Q, length)
+ vs = lucas_v(P, Q, length)
+ for n in range(3, 100, 2):
+ for k in range(length):
+ U, V, Qk = _lucas_sequence(n, P, Q, k)
+ assert U == us[k] % n
+ assert V == vs[k] % n
+ assert pow(Q, k, n) == Qk
+
+
+def test_is_fibonacci_prp():
+ # invalid input
+ raises(ValueError, lambda: is_fibonacci_prp(3, 2, 1))
+ raises(ValueError, lambda: is_fibonacci_prp(3, -5, 1))
+ raises(ValueError, lambda: is_fibonacci_prp(3, 5, 2))
+ raises(ValueError, lambda: is_fibonacci_prp(0, 5, -1))
+
+ # n = 1
+ assert not is_fibonacci_prp(1, 3, 1)
+
+ # n is prime
+ assert is_fibonacci_prp(2, 5, 1)
+ assert is_fibonacci_prp(3, 6, -1)
+ assert is_fibonacci_prp(11, 7, 1)
+ assert is_fibonacci_prp(2**31-1, 8, -1)
+
+ # A005845
+ pseudorpime = [705, 2465, 2737, 3745, 4181, 5777, 6721,
+ 10877, 13201, 15251, 24465, 29281, 34561]
+ for n in pseudorpime:
+ assert is_fibonacci_prp(n, 1, -1)
+
+
+def test_is_lucas_prp():
+ # invalid input
+ raises(ValueError, lambda: is_lucas_prp(3, 2, 1))
+ raises(ValueError, lambda: is_lucas_prp(0, 5, -1))
+ raises(ValueError, lambda: is_lucas_prp(15, 3, 1))
+
+ # n = 1
+ assert not is_lucas_prp(1, 3, 1)
+
+ # n is prime
+ assert is_lucas_prp(2, 5, 2)
+ assert is_lucas_prp(3, 6, -1)
+ assert is_lucas_prp(11, 7, 5)
+ assert is_lucas_prp(2**31-1, 8, -3)
+
+ # A081264
+ pseudorpime = [323, 377, 1891, 3827, 4181, 5777, 6601, 6721,
+ 8149, 10877, 11663, 13201, 13981, 15251, 17119]
+ for n in pseudorpime:
+ assert is_lucas_prp(n, 1, -1)
+
+
+def test_is_selfridge_prp():
+ # invalid input
+ raises(ValueError, lambda: is_selfridge_prp(0))
+
+ # n = 1
+ assert not is_selfridge_prp(1)
+
+ # n is prime
+ assert is_selfridge_prp(2)
+ assert is_selfridge_prp(3)
+ assert is_selfridge_prp(11)
+ assert is_selfridge_prp(2**31-1)
+
+ # A217120
+ pseudorpime = [323, 377, 1159, 1829, 3827, 5459, 5777, 9071,
+ 9179, 10877, 11419, 11663, 13919, 14839, 16109]
+ for n in pseudorpime:
+ assert is_selfridge_prp(n)
+
+
+def test_is_strong_lucas_prp():
+ # invalid input
+ raises(ValueError, lambda: is_strong_lucas_prp(3, 2, 1))
+ raises(ValueError, lambda: is_strong_lucas_prp(0, 5, -1))
+ raises(ValueError, lambda: is_strong_lucas_prp(15, 3, 1))
+
+ # n = 1
+ assert not is_strong_lucas_prp(1, 3, 1)
+
+ # n is prime
+ assert is_strong_lucas_prp(2, 5, 2)
+ assert is_strong_lucas_prp(3, 6, -1)
+ assert is_strong_lucas_prp(11, 7, 5)
+ assert is_strong_lucas_prp(2**31-1, 8, -3)
+
+
+def test_is_strong_selfridge_prp():
+ # invalid input
+ raises(ValueError, lambda: is_strong_selfridge_prp(0))
+
+ # n = 1
+ assert not is_strong_selfridge_prp(1)
+
+ # n is prime
+ assert is_strong_selfridge_prp(2)
+ assert is_strong_selfridge_prp(3)
+ assert is_strong_selfridge_prp(11)
+ assert is_strong_selfridge_prp(2**31-1)
+
+ # A217255
+ pseudorpime = [5459, 5777, 10877, 16109, 18971, 22499, 24569,
+ 25199, 40309, 58519, 75077, 97439, 100127, 113573]
+ for n in pseudorpime:
+ assert is_strong_selfridge_prp(n)
+
+
+def test_is_bpsw_prp():
+ # invalid input
+ raises(ValueError, lambda: is_bpsw_prp(0))
+
+ # n = 1
+ assert not is_bpsw_prp(1)
+
+ # n is prime
+ assert is_bpsw_prp(2)
+ assert is_bpsw_prp(3)
+ assert is_bpsw_prp(11)
+ assert is_bpsw_prp(2**31-1)
+
+
+def test_is_strong_bpsw_prp():
+ # invalid input
+ raises(ValueError, lambda: is_strong_bpsw_prp(0))
+
+ # n = 1
+ assert not is_strong_bpsw_prp(1)
+
+ # n is prime
+ assert is_strong_bpsw_prp(2)
+ assert is_strong_bpsw_prp(3)
+ assert is_strong_bpsw_prp(11)
+ assert is_strong_bpsw_prp(2**31-1)
diff --git a/lib/python3.12/site-packages/sympy/external/tests/test_numpy.py b/lib/python3.12/site-packages/sympy/external/tests/test_numpy.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd456d0d6cc49138c29d7ab28ee02694448d578f
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/tests/test_numpy.py
@@ -0,0 +1,335 @@
+# This testfile tests SymPy <-> NumPy compatibility
+
+# Don't test any SymPy features here. Just pure interaction with NumPy.
+# Always write regular SymPy tests for anything, that can be tested in pure
+# Python (without numpy). Here we test everything, that a user may need when
+# using SymPy with NumPy
+from sympy.external.importtools import version_tuple
+from sympy.external import import_module
+
+numpy = import_module('numpy')
+if numpy:
+ array, matrix, ndarray = numpy.array, numpy.matrix, numpy.ndarray
+else:
+ #bin/test will not execute any tests now
+ disabled = True
+
+
+from sympy.core.numbers import (Float, Integer, Rational)
+from sympy.core.symbol import (Symbol, symbols)
+from sympy.functions.elementary.trigonometric import sin
+from sympy.matrices.dense import (Matrix, list2numpy, matrix2numpy, symarray)
+from sympy.utilities.lambdify import lambdify
+import sympy
+
+import mpmath
+from sympy.abc import x, y, z
+from sympy.utilities.decorator import conserve_mpmath_dps
+from sympy.utilities.exceptions import ignore_warnings
+from sympy.testing.pytest import raises
+
+
+# first, systematically check, that all operations are implemented and don't
+# raise an exception
+
+
+def test_systematic_basic():
+ def s(sympy_object, numpy_array):
+ _ = [sympy_object + numpy_array,
+ numpy_array + sympy_object,
+ sympy_object - numpy_array,
+ numpy_array - sympy_object,
+ sympy_object * numpy_array,
+ numpy_array * sympy_object,
+ sympy_object / numpy_array,
+ numpy_array / sympy_object,
+ sympy_object ** numpy_array,
+ numpy_array ** sympy_object]
+ x = Symbol("x")
+ y = Symbol("y")
+ sympy_objs = [
+ Rational(2, 3),
+ Float("1.3"),
+ x,
+ y,
+ pow(x, y)*y,
+ Integer(5),
+ Float(5.5),
+ ]
+ numpy_objs = [
+ array([1]),
+ array([3, 8, -1]),
+ array([x, x**2, Rational(5)]),
+ array([x/y*sin(y), 5, Rational(5)]),
+ ]
+ for x in sympy_objs:
+ for y in numpy_objs:
+ s(x, y)
+
+
+# now some random tests, that test particular problems and that also
+# check that the results of the operations are correct
+
+def test_basics():
+ one = Rational(1)
+ zero = Rational(0)
+ assert array(1) == array(one)
+ assert array([one]) == array([one])
+ assert array([x]) == array([x])
+ assert array(x) == array(Symbol("x"))
+ assert array(one + x) == array(1 + x)
+
+ X = array([one, zero, zero])
+ assert (X == array([one, zero, zero])).all()
+ assert (X == array([one, 0, 0])).all()
+
+
+def test_arrays():
+ one = Rational(1)
+ zero = Rational(0)
+ X = array([one, zero, zero])
+ Y = one*X
+ X = array([Symbol("a") + Rational(1, 2)])
+ Y = X + X
+ assert Y == array([1 + 2*Symbol("a")])
+ Y = Y + 1
+ assert Y == array([2 + 2*Symbol("a")])
+ Y = X - X
+ assert Y == array([0])
+
+
+def test_conversion1():
+ a = list2numpy([x**2, x])
+ #looks like an array?
+ assert isinstance(a, ndarray)
+ assert a[0] == x**2
+ assert a[1] == x
+ assert len(a) == 2
+ #yes, it's the array
+
+
+def test_conversion2():
+ a = 2*list2numpy([x**2, x])
+ b = list2numpy([2*x**2, 2*x])
+ assert (a == b).all()
+
+ one = Rational(1)
+ zero = Rational(0)
+ X = list2numpy([one, zero, zero])
+ Y = one*X
+ X = list2numpy([Symbol("a") + Rational(1, 2)])
+ Y = X + X
+ assert Y == array([1 + 2*Symbol("a")])
+ Y = Y + 1
+ assert Y == array([2 + 2*Symbol("a")])
+ Y = X - X
+ assert Y == array([0])
+
+
+def test_list2numpy():
+ assert (array([x**2, x]) == list2numpy([x**2, x])).all()
+
+
+def test_Matrix1():
+ m = Matrix([[x, x**2], [5, 2/x]])
+ assert (array(m.subs(x, 2)) == array([[2, 4], [5, 1]])).all()
+ m = Matrix([[sin(x), x**2], [5, 2/x]])
+ assert (array(m.subs(x, 2)) == array([[sin(2), 4], [5, 1]])).all()
+
+
+def test_Matrix2():
+ m = Matrix([[x, x**2], [5, 2/x]])
+ with ignore_warnings(PendingDeprecationWarning):
+ assert (matrix(m.subs(x, 2)) == matrix([[2, 4], [5, 1]])).all()
+ m = Matrix([[sin(x), x**2], [5, 2/x]])
+ with ignore_warnings(PendingDeprecationWarning):
+ assert (matrix(m.subs(x, 2)) == matrix([[sin(2), 4], [5, 1]])).all()
+
+
+def test_Matrix3():
+ a = array([[2, 4], [5, 1]])
+ assert Matrix(a) == Matrix([[2, 4], [5, 1]])
+ assert Matrix(a) != Matrix([[2, 4], [5, 2]])
+ a = array([[sin(2), 4], [5, 1]])
+ assert Matrix(a) == Matrix([[sin(2), 4], [5, 1]])
+ assert Matrix(a) != Matrix([[sin(0), 4], [5, 1]])
+
+
+def test_Matrix4():
+ with ignore_warnings(PendingDeprecationWarning):
+ a = matrix([[2, 4], [5, 1]])
+ assert Matrix(a) == Matrix([[2, 4], [5, 1]])
+ assert Matrix(a) != Matrix([[2, 4], [5, 2]])
+ with ignore_warnings(PendingDeprecationWarning):
+ a = matrix([[sin(2), 4], [5, 1]])
+ assert Matrix(a) == Matrix([[sin(2), 4], [5, 1]])
+ assert Matrix(a) != Matrix([[sin(0), 4], [5, 1]])
+
+
+def test_Matrix_sum():
+ M = Matrix([[1, 2, 3], [x, y, x], [2*y, -50, z*x]])
+ with ignore_warnings(PendingDeprecationWarning):
+ m = matrix([[2, 3, 4], [x, 5, 6], [x, y, z**2]])
+ assert M + m == Matrix([[3, 5, 7], [2*x, y + 5, x + 6], [2*y + x, y - 50, z*x + z**2]])
+ assert m + M == Matrix([[3, 5, 7], [2*x, y + 5, x + 6], [2*y + x, y - 50, z*x + z**2]])
+ assert M + m == M.add(m)
+
+
+def test_Matrix_mul():
+ M = Matrix([[1, 2, 3], [x, y, x]])
+ with ignore_warnings(PendingDeprecationWarning):
+ m = matrix([[2, 4], [x, 6], [x, z**2]])
+ assert M*m == Matrix([
+ [ 2 + 5*x, 16 + 3*z**2],
+ [2*x + x*y + x**2, 4*x + 6*y + x*z**2],
+ ])
+
+ assert m*M == Matrix([
+ [ 2 + 4*x, 4 + 4*y, 6 + 4*x],
+ [ 7*x, 2*x + 6*y, 9*x],
+ [x + x*z**2, 2*x + y*z**2, 3*x + x*z**2],
+ ])
+ a = array([2])
+ assert a[0] * M == 2 * M
+ assert M * a[0] == 2 * M
+
+
+def test_Matrix_array():
+ class matarray:
+ def __array__(self, dtype=object, copy=None):
+ if copy is not None and not copy:
+ raise TypeError("Cannot implement copy=False when converting Matrix to ndarray")
+ from numpy import array
+ return array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+ matarr = matarray()
+ assert Matrix(matarr) == Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+
+
+def test_matrix2numpy():
+ a = matrix2numpy(Matrix([[1, x**2], [3*sin(x), 0]]))
+ assert isinstance(a, ndarray)
+ assert a.shape == (2, 2)
+ assert a[0, 0] == 1
+ assert a[0, 1] == x**2
+ assert a[1, 0] == 3*sin(x)
+ assert a[1, 1] == 0
+
+
+def test_matrix2numpy_conversion():
+ a = Matrix([[1, 2, sin(x)], [x**2, x, Rational(1, 2)]])
+ b = array([[1, 2, sin(x)], [x**2, x, Rational(1, 2)]])
+ assert (matrix2numpy(a) == b).all()
+ assert matrix2numpy(a).dtype == numpy.dtype('object')
+
+ c = matrix2numpy(Matrix([[1, 2], [10, 20]]), dtype='int8')
+ d = matrix2numpy(Matrix([[1, 2], [10, 20]]), dtype='float64')
+ assert c.dtype == numpy.dtype('int8')
+ assert d.dtype == numpy.dtype('float64')
+
+
+def test_issue_3728():
+ assert (Rational(1, 2)*array([2*x, 0]) == array([x, 0])).all()
+ assert (Rational(1, 2) + array(
+ [2*x, 0]) == array([2*x + Rational(1, 2), Rational(1, 2)])).all()
+ assert (Float("0.5")*array([2*x, 0]) == array([Float("1.0")*x, 0])).all()
+ assert (Float("0.5") + array(
+ [2*x, 0]) == array([2*x + Float("0.5"), Float("0.5")])).all()
+
+
+@conserve_mpmath_dps
+def test_lambdify():
+ mpmath.mp.dps = 16
+ sin02 = mpmath.mpf("0.198669330795061215459412627")
+ f = lambdify(x, sin(x), "numpy")
+ prec = 1e-15
+ assert -prec < f(0.2) - sin02 < prec
+
+ # if this succeeds, it can't be a numpy function
+
+ if version_tuple(numpy.__version__) >= version_tuple('1.17'):
+ with raises(TypeError):
+ f(x)
+ else:
+ with raises(AttributeError):
+ f(x)
+
+
+def test_lambdify_matrix():
+ f = lambdify(x, Matrix([[x, 2*x], [1, 2]]), [{'ImmutableMatrix': numpy.array}, "numpy"])
+ assert (f(1) == array([[1, 2], [1, 2]])).all()
+
+
+def test_lambdify_matrix_multi_input():
+ M = sympy.Matrix([[x**2, x*y, x*z],
+ [y*x, y**2, y*z],
+ [z*x, z*y, z**2]])
+ f = lambdify((x, y, z), M, [{'ImmutableMatrix': numpy.array}, "numpy"])
+
+ xh, yh, zh = 1.0, 2.0, 3.0
+ expected = array([[xh**2, xh*yh, xh*zh],
+ [yh*xh, yh**2, yh*zh],
+ [zh*xh, zh*yh, zh**2]])
+ actual = f(xh, yh, zh)
+ assert numpy.allclose(actual, expected)
+
+
+def test_lambdify_matrix_vec_input():
+ X = sympy.DeferredVector('X')
+ M = Matrix([
+ [X[0]**2, X[0]*X[1], X[0]*X[2]],
+ [X[1]*X[0], X[1]**2, X[1]*X[2]],
+ [X[2]*X[0], X[2]*X[1], X[2]**2]])
+ f = lambdify(X, M, [{'ImmutableMatrix': numpy.array}, "numpy"])
+
+ Xh = array([1.0, 2.0, 3.0])
+ expected = array([[Xh[0]**2, Xh[0]*Xh[1], Xh[0]*Xh[2]],
+ [Xh[1]*Xh[0], Xh[1]**2, Xh[1]*Xh[2]],
+ [Xh[2]*Xh[0], Xh[2]*Xh[1], Xh[2]**2]])
+ actual = f(Xh)
+ assert numpy.allclose(actual, expected)
+
+
+def test_lambdify_transl():
+ from sympy.utilities.lambdify import NUMPY_TRANSLATIONS
+ for sym, mat in NUMPY_TRANSLATIONS.items():
+ assert sym in sympy.__dict__
+ assert mat in numpy.__dict__
+
+
+def test_symarray():
+ """Test creation of numpy arrays of SymPy symbols."""
+
+ import numpy as np
+ import numpy.testing as npt
+
+ syms = symbols('_0,_1,_2')
+ s1 = symarray("", 3)
+ s2 = symarray("", 3)
+ npt.assert_array_equal(s1, np.array(syms, dtype=object))
+ assert s1[0] == s2[0]
+
+ a = symarray('a', 3)
+ b = symarray('b', 3)
+ assert not(a[0] == b[0])
+
+ asyms = symbols('a_0,a_1,a_2')
+ npt.assert_array_equal(a, np.array(asyms, dtype=object))
+
+ # Multidimensional checks
+ a2d = symarray('a', (2, 3))
+ assert a2d.shape == (2, 3)
+ a00, a12 = symbols('a_0_0,a_1_2')
+ assert a2d[0, 0] == a00
+ assert a2d[1, 2] == a12
+
+ a3d = symarray('a', (2, 3, 2))
+ assert a3d.shape == (2, 3, 2)
+ a000, a120, a121 = symbols('a_0_0_0,a_1_2_0,a_1_2_1')
+ assert a3d[0, 0, 0] == a000
+ assert a3d[1, 2, 0] == a120
+ assert a3d[1, 2, 1] == a121
+
+
+def test_vectorize():
+ assert (numpy.vectorize(
+ sin)([1, 2, 3]) == numpy.array([sin(1), sin(2), sin(3)])).all()
diff --git a/lib/python3.12/site-packages/sympy/external/tests/test_pythonmpq.py b/lib/python3.12/site-packages/sympy/external/tests/test_pythonmpq.py
new file mode 100644
index 0000000000000000000000000000000000000000..137cfdf5c858544f0811ae666f000cfb368787a0
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/tests/test_pythonmpq.py
@@ -0,0 +1,176 @@
+"""
+test_pythonmpq.py
+
+Test the PythonMPQ class for consistency with gmpy2's mpq type. If gmpy2 is
+installed run the same tests for both.
+"""
+from fractions import Fraction
+from decimal import Decimal
+import pickle
+from typing import Callable, List, Tuple, Type
+
+from sympy.testing.pytest import raises
+
+from sympy.external.pythonmpq import PythonMPQ
+
+#
+# If gmpy2 is installed then run the tests for both mpq and PythonMPQ.
+# That should ensure consistency between the implementation here and mpq.
+#
+rational_types: List[Tuple[Callable, Type, Callable, Type]]
+rational_types = [(PythonMPQ, PythonMPQ, int, int)]
+try:
+ from gmpy2 import mpq, mpz
+ rational_types.append((mpq, type(mpq(1)), mpz, type(mpz(1))))
+except ImportError:
+ pass
+
+
+def test_PythonMPQ():
+ #
+ # Test PythonMPQ and also mpq if gmpy/gmpy2 is installed.
+ #
+ for Q, TQ, Z, TZ in rational_types:
+
+ def check_Q(q):
+ assert isinstance(q, TQ)
+ assert isinstance(q.numerator, TZ)
+ assert isinstance(q.denominator, TZ)
+ return q.numerator, q.denominator
+
+ # Check construction from different types
+ assert check_Q(Q(3)) == (3, 1)
+ assert check_Q(Q(3, 5)) == (3, 5)
+ assert check_Q(Q(Q(3, 5))) == (3, 5)
+ assert check_Q(Q(0.5)) == (1, 2)
+ assert check_Q(Q('0.5')) == (1, 2)
+ assert check_Q(Q(Fraction(3, 5))) == (3, 5)
+
+ # https://github.com/aleaxit/gmpy/issues/327
+ if Q is PythonMPQ:
+ assert check_Q(Q(Decimal('0.6'))) == (3, 5)
+
+ # Invalid types
+ raises(TypeError, lambda: Q([]))
+ raises(TypeError, lambda: Q([], []))
+
+ # Check normalisation of signs
+ assert check_Q(Q(2, 3)) == (2, 3)
+ assert check_Q(Q(-2, 3)) == (-2, 3)
+ assert check_Q(Q(2, -3)) == (-2, 3)
+ assert check_Q(Q(-2, -3)) == (2, 3)
+
+ # Check gcd calculation
+ assert check_Q(Q(12, 8)) == (3, 2)
+
+ # __int__/__float__
+ assert int(Q(5, 3)) == 1
+ assert int(Q(-5, 3)) == -1
+ assert float(Q(5, 2)) == 2.5
+ assert float(Q(-5, 2)) == -2.5
+
+ # __str__/__repr__
+ assert str(Q(2, 1)) == "2"
+ assert str(Q(1, 2)) == "1/2"
+ if Q is PythonMPQ:
+ assert repr(Q(2, 1)) == "MPQ(2,1)"
+ assert repr(Q(1, 2)) == "MPQ(1,2)"
+ else:
+ assert repr(Q(2, 1)) == "mpq(2,1)"
+ assert repr(Q(1, 2)) == "mpq(1,2)"
+
+ # __bool__
+ assert bool(Q(1, 2)) is True
+ assert bool(Q(0)) is False
+
+ # __eq__/__ne__
+ assert (Q(2, 3) == Q(2, 3)) is True
+ assert (Q(2, 3) == Q(2, 5)) is False
+ assert (Q(2, 3) != Q(2, 3)) is False
+ assert (Q(2, 3) != Q(2, 5)) is True
+
+ # __hash__
+ assert hash(Q(3, 5)) == hash(Fraction(3, 5))
+
+ # __reduce__
+ q = Q(2, 3)
+ assert pickle.loads(pickle.dumps(q)) == q
+
+ # __ge__/__gt__/__le__/__lt__
+ assert (Q(1, 3) < Q(2, 3)) is True
+ assert (Q(2, 3) < Q(2, 3)) is False
+ assert (Q(2, 3) < Q(1, 3)) is False
+ assert (Q(-2, 3) < Q(1, 3)) is True
+ assert (Q(1, 3) < Q(-2, 3)) is False
+
+ assert (Q(1, 3) <= Q(2, 3)) is True
+ assert (Q(2, 3) <= Q(2, 3)) is True
+ assert (Q(2, 3) <= Q(1, 3)) is False
+ assert (Q(-2, 3) <= Q(1, 3)) is True
+ assert (Q(1, 3) <= Q(-2, 3)) is False
+
+ assert (Q(1, 3) > Q(2, 3)) is False
+ assert (Q(2, 3) > Q(2, 3)) is False
+ assert (Q(2, 3) > Q(1, 3)) is True
+ assert (Q(-2, 3) > Q(1, 3)) is False
+ assert (Q(1, 3) > Q(-2, 3)) is True
+
+ assert (Q(1, 3) >= Q(2, 3)) is False
+ assert (Q(2, 3) >= Q(2, 3)) is True
+ assert (Q(2, 3) >= Q(1, 3)) is True
+ assert (Q(-2, 3) >= Q(1, 3)) is False
+ assert (Q(1, 3) >= Q(-2, 3)) is True
+
+ # __abs__/__pos__/__neg__
+ assert abs(Q(2, 3)) == abs(Q(-2, 3)) == Q(2, 3)
+ assert +Q(2, 3) == Q(2, 3)
+ assert -Q(2, 3) == Q(-2, 3)
+
+ # __add__/__radd__
+ assert Q(2, 3) + Q(5, 7) == Q(29, 21)
+ assert Q(2, 3) + 1 == Q(5, 3)
+ assert 1 + Q(2, 3) == Q(5, 3)
+ raises(TypeError, lambda: [] + Q(1))
+ raises(TypeError, lambda: Q(1) + [])
+
+ # __sub__/__rsub__
+ assert Q(2, 3) - Q(5, 7) == Q(-1, 21)
+ assert Q(2, 3) - 1 == Q(-1, 3)
+ assert 1 - Q(2, 3) == Q(1, 3)
+ raises(TypeError, lambda: [] - Q(1))
+ raises(TypeError, lambda: Q(1) - [])
+
+ # __mul__/__rmul__
+ assert Q(2, 3) * Q(5, 7) == Q(10, 21)
+ assert Q(2, 3) * 1 == Q(2, 3)
+ assert 1 * Q(2, 3) == Q(2, 3)
+ raises(TypeError, lambda: [] * Q(1))
+ raises(TypeError, lambda: Q(1) * [])
+
+ # __pow__/__rpow__
+ assert Q(2, 3) ** 2 == Q(4, 9)
+ assert Q(2, 3) ** 1 == Q(2, 3)
+ assert Q(-2, 3) ** 2 == Q(4, 9)
+ assert Q(-2, 3) ** -1 == Q(-3, 2)
+ if Q is PythonMPQ:
+ raises(TypeError, lambda: 1 ** Q(2, 3))
+ raises(TypeError, lambda: Q(1, 4) ** Q(1, 2))
+ raises(TypeError, lambda: [] ** Q(1))
+ raises(TypeError, lambda: Q(1) ** [])
+
+ # __div__/__rdiv__
+ assert Q(2, 3) / Q(5, 7) == Q(14, 15)
+ assert Q(2, 3) / 1 == Q(2, 3)
+ assert 1 / Q(2, 3) == Q(3, 2)
+ raises(TypeError, lambda: [] / Q(1))
+ raises(TypeError, lambda: Q(1) / [])
+ raises(ZeroDivisionError, lambda: Q(1, 2) / Q(0))
+
+ # __divmod__
+ if Q is PythonMPQ:
+ raises(TypeError, lambda: Q(2, 3) // Q(1, 3))
+ raises(TypeError, lambda: Q(2, 3) % Q(1, 3))
+ raises(TypeError, lambda: 1 // Q(1, 3))
+ raises(TypeError, lambda: 1 % Q(1, 3))
+ raises(TypeError, lambda: Q(2, 3) // 1)
+ raises(TypeError, lambda: Q(2, 3) % 1)
diff --git a/lib/python3.12/site-packages/sympy/external/tests/test_scipy.py b/lib/python3.12/site-packages/sympy/external/tests/test_scipy.py
new file mode 100644
index 0000000000000000000000000000000000000000..3746d1a311eb68bb1af16e18ab152c7236b42bb5
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/external/tests/test_scipy.py
@@ -0,0 +1,35 @@
+# This testfile tests SymPy <-> SciPy compatibility
+
+# Don't test any SymPy features here. Just pure interaction with SciPy.
+# Always write regular SymPy tests for anything, that can be tested in pure
+# Python (without scipy). Here we test everything, that a user may need when
+# using SymPy with SciPy
+
+from sympy.external import import_module
+
+scipy = import_module('scipy')
+if not scipy:
+ #bin/test will not execute any tests now
+ disabled = True
+
+from sympy.functions.special.bessel import jn_zeros
+
+
+def eq(a, b, tol=1e-6):
+ for x, y in zip(a, b):
+ if not (abs(x - y) < tol):
+ return False
+ return True
+
+
+def test_jn_zeros():
+ assert eq(jn_zeros(0, 4, method="scipy"),
+ [3.141592, 6.283185, 9.424777, 12.566370])
+ assert eq(jn_zeros(1, 4, method="scipy"),
+ [4.493409, 7.725251, 10.904121, 14.066193])
+ assert eq(jn_zeros(2, 4, method="scipy"),
+ [5.763459, 9.095011, 12.322940, 15.514603])
+ assert eq(jn_zeros(3, 4, method="scipy"),
+ [6.987932, 10.417118, 13.698023, 16.923621])
+ assert eq(jn_zeros(4, 4, method="scipy"),
+ [8.182561, 11.704907, 15.039664, 18.301255])
diff --git a/lib/python3.12/site-packages/sympy/integrals/__pycache__/integrals.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/__pycache__/integrals.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9fbbdece6180c2bf96aef6a31b5ebb698d61c598
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/__pycache__/integrals.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/__pycache__/meijerint_doc.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/__pycache__/meijerint_doc.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7c493dd7d441aae2a0dc66025c03e956eb0bb795
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/__pycache__/meijerint_doc.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/__pycache__/rationaltools.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/__pycache__/rationaltools.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4c4b8b04faff86fd34713f7689ef4123da4359df
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/__pycache__/rationaltools.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/__pycache__/rde.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/__pycache__/rde.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0bb2ce33aa26ac025d942576de376b9eb049ff4c
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/__pycache__/rde.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/__pycache__/risch.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/__pycache__/risch.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4275752c667cbe36c5276e6dbec49e029f51093a
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/__pycache__/risch.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/__pycache__/singularityfunctions.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/__pycache__/singularityfunctions.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..01c894be97466d99c84fc112c610eb68e1f61b72
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/__pycache__/singularityfunctions.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/benchmarks/__init__.py b/lib/python3.12/site-packages/sympy/integrals/benchmarks/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5f36c45497067ed357569ca7da79e1153a91c1cc
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/bench_integrate.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/bench_integrate.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6bb61540bc11909d17a95299dfe4c4968f23f435
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/bench_integrate.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/bench_trigintegrate.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/bench_trigintegrate.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9bd7713a26bb11ce570b8c02fe34183f1f0f9c0b
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/benchmarks/__pycache__/bench_trigintegrate.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/benchmarks/bench_integrate.py b/lib/python3.12/site-packages/sympy/integrals/benchmarks/bench_integrate.py
new file mode 100644
index 0000000000000000000000000000000000000000..833bc57403b34df1e75c798084ffc4d8afe9eae6
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/benchmarks/bench_integrate.py
@@ -0,0 +1,21 @@
+from sympy.core.symbol import Symbol
+from sympy.functions.elementary.trigonometric import sin
+from sympy.integrals.integrals import integrate
+
+x = Symbol('x')
+
+
+def bench_integrate_sin():
+ integrate(sin(x), x)
+
+
+def bench_integrate_x1sin():
+ integrate(x**1*sin(x), x)
+
+
+def bench_integrate_x2sin():
+ integrate(x**2*sin(x), x)
+
+
+def bench_integrate_x3sin():
+ integrate(x**3*sin(x), x)
diff --git a/lib/python3.12/site-packages/sympy/integrals/benchmarks/bench_trigintegrate.py b/lib/python3.12/site-packages/sympy/integrals/benchmarks/bench_trigintegrate.py
new file mode 100644
index 0000000000000000000000000000000000000000..403c5471b8048ff2aa97bf2f837b9ea05f0fd904
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/benchmarks/bench_trigintegrate.py
@@ -0,0 +1,13 @@
+from sympy.core.symbol import Symbol
+from sympy.functions.elementary.trigonometric import sin
+from sympy.integrals.trigonometry import trigintegrate
+
+x = Symbol('x')
+
+
+def timeit_trigintegrate_sin3x():
+ trigintegrate(sin(x)**3, x)
+
+
+def timeit_trigintegrate_x2():
+ trigintegrate(x**2, x) # -> None
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__init__.py b/lib/python3.12/site-packages/sympy/integrals/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d6d5058f3f7884aa15a0181772fa451fba0dd280
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_deltafunctions.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_deltafunctions.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9e633b838a8910d08e8d03a8ccfd12ac0dbbb0c4
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_deltafunctions.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_failing_integrals.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_failing_integrals.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e78694e542215bb8cea3689fabbffdc790934652
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_failing_integrals.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_heurisch.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_heurisch.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..df201bd503464c7634ab681b830833882726f291
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_heurisch.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_intpoly.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_intpoly.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..72c2dc9c0e565d2a3fdadc031d3180c592776ede
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_intpoly.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_laplace.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_laplace.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1a07258b7362e92c0b1e373974b73682bdc6f812
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_laplace.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_lineintegrals.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_lineintegrals.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e2f729996c281dc522529a3d10d22103485d3b06
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_lineintegrals.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_manual.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_manual.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9154af01dd7f33dfbba0fdea6dab33fab9bba5c7
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_manual.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_meijerint.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_meijerint.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e308f949df10ba8a38bb2a9ca67434538a1b69f9
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_meijerint.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_prde.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_prde.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..38e869e97fb42106c4ea049600e2daae964f7a6b
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_prde.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_quadrature.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_quadrature.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..89d4b1809b319884bde8bf191bd65e4117b70047
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_quadrature.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_rationaltools.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_rationaltools.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8acd16d33972eabd75e09db824a8c09a0448e24d
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_rationaltools.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_rde.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_rde.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..83a44a92c8f55e8bf5fc157099335daa411a9269
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_rde.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_risch.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_risch.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a1f90fd25fcf1f355113cdf4141251788cd4d0da
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_risch.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_singularityfunctions.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_singularityfunctions.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ddfcbf2c51f1b5e011eba08c7e5822b84d938922
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_singularityfunctions.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_transforms.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_transforms.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1a5d6a61ea698ad80a42cb5b16b04b8ac848a177
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_transforms.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_trigonometry.cpython-312.pyc b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_trigonometry.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2d52c86c5fb508d57a75de53beb40944de0ac303
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/integrals/tests/__pycache__/test_trigonometry.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_deltafunctions.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_deltafunctions.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4fd567349b50f795e08d583fd08db67b1596577
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_deltafunctions.py
@@ -0,0 +1,79 @@
+from sympy.core.function import Function
+from sympy.core.numbers import (Rational, pi)
+from sympy.core.singleton import S
+from sympy.core.symbol import symbols
+from sympy.functions.elementary.trigonometric import (cos, sin)
+from sympy.functions.special.delta_functions import (DiracDelta, Heaviside)
+from sympy.integrals.deltafunctions import change_mul, deltaintegrate
+
+f = Function("f")
+x_1, x_2, x, y, z = symbols("x_1 x_2 x y z")
+
+
+def test_change_mul():
+ assert change_mul(x, x) == (None, None)
+ assert change_mul(x*y, x) == (None, None)
+ assert change_mul(x*y*DiracDelta(x), x) == (DiracDelta(x), x*y)
+ assert change_mul(x*y*DiracDelta(x)*DiracDelta(y), x) == \
+ (DiracDelta(x), x*y*DiracDelta(y))
+ assert change_mul(DiracDelta(x)**2, x) == \
+ (DiracDelta(x), DiracDelta(x))
+ assert change_mul(y*DiracDelta(x)**2, x) == \
+ (DiracDelta(x), y*DiracDelta(x))
+
+
+def test_deltaintegrate():
+ assert deltaintegrate(x, x) is None
+ assert deltaintegrate(x + DiracDelta(x), x) is None
+ assert deltaintegrate(DiracDelta(x, 0), x) == Heaviside(x)
+ for n in range(10):
+ assert deltaintegrate(DiracDelta(x, n + 1), x) == DiracDelta(x, n)
+ assert deltaintegrate(DiracDelta(x), x) == Heaviside(x)
+ assert deltaintegrate(DiracDelta(-x), x) == Heaviside(x)
+ assert deltaintegrate(DiracDelta(x - y), x) == Heaviside(x - y)
+ assert deltaintegrate(DiracDelta(y - x), x) == Heaviside(x - y)
+
+ assert deltaintegrate(x*DiracDelta(x), x) == 0
+ assert deltaintegrate((x - y)*DiracDelta(x - y), x) == 0
+
+ assert deltaintegrate(DiracDelta(x)**2, x) == DiracDelta(0)*Heaviside(x)
+ assert deltaintegrate(y*DiracDelta(x)**2, x) == \
+ y*DiracDelta(0)*Heaviside(x)
+ assert deltaintegrate(DiracDelta(x, 1), x) == DiracDelta(x, 0)
+ assert deltaintegrate(y*DiracDelta(x, 1), x) == y*DiracDelta(x, 0)
+ assert deltaintegrate(DiracDelta(x, 1)**2, x) == -DiracDelta(0, 2)*Heaviside(x)
+ assert deltaintegrate(y*DiracDelta(x, 1)**2, x) == -y*DiracDelta(0, 2)*Heaviside(x)
+
+
+ assert deltaintegrate(DiracDelta(x) * f(x), x) == f(0) * Heaviside(x)
+ assert deltaintegrate(DiracDelta(-x) * f(x), x) == f(0) * Heaviside(x)
+ assert deltaintegrate(DiracDelta(x - 1) * f(x), x) == f(1) * Heaviside(x - 1)
+ assert deltaintegrate(DiracDelta(1 - x) * f(x), x) == f(1) * Heaviside(x - 1)
+ assert deltaintegrate(DiracDelta(x**2 + x - 2), x) == \
+ Heaviside(x - 1)/3 + Heaviside(x + 2)/3
+
+ p = cos(x)*(DiracDelta(x) + DiracDelta(x**2 - 1))*sin(x)*(x - pi)
+ assert deltaintegrate(p, x) - (-pi*(cos(1)*Heaviside(-1 + x)*sin(1)/2 - \
+ cos(1)*Heaviside(1 + x)*sin(1)/2) + \
+ cos(1)*Heaviside(1 + x)*sin(1)/2 + \
+ cos(1)*Heaviside(-1 + x)*sin(1)/2) == 0
+
+ p = x_2*DiracDelta(x - x_2)*DiracDelta(x_2 - x_1)
+ assert deltaintegrate(p, x_2) == x*DiracDelta(x - x_1)*Heaviside(x_2 - x)
+
+ p = x*y**2*z*DiracDelta(y - x)*DiracDelta(y - z)*DiracDelta(x - z)
+ assert deltaintegrate(p, y) == x**3*z*DiracDelta(x - z)**2*Heaviside(y - x)
+ assert deltaintegrate((x + 1)*DiracDelta(2*x), x) == S.Half * Heaviside(x)
+ assert deltaintegrate((x + 1)*DiracDelta(x*Rational(2, 3) + Rational(4, 9)), x) == \
+ S.Half * Heaviside(x + Rational(2, 3))
+
+ a, b, c = symbols('a b c', commutative=False)
+ assert deltaintegrate(DiracDelta(x - y)*f(x - b)*f(x - a), x) == \
+ f(y - b)*f(y - a)*Heaviside(x - y)
+
+ p = f(x - a)*DiracDelta(x - y)*f(x - c)*f(x - b)
+ assert deltaintegrate(p, x) == f(y - a)*f(y - c)*f(y - b)*Heaviside(x - y)
+
+ p = DiracDelta(x - z)*f(x - b)*f(x - a)*DiracDelta(x - y)
+ assert deltaintegrate(p, x) == DiracDelta(y - z)*f(y - b)*f(y - a) * \
+ Heaviside(x - y)
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_failing_integrals.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_failing_integrals.py
new file mode 100644
index 0000000000000000000000000000000000000000..9bb434cf0009cd96ad2b7882d109b7fbe23193c2
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_failing_integrals.py
@@ -0,0 +1,277 @@
+# A collection of failing integrals from the issues.
+
+from sympy.core.numbers import (I, Rational, oo, pi)
+from sympy.core.singleton import S
+from sympy.core.symbol import symbols
+from sympy.functions.elementary.complexes import sign
+from sympy.functions.elementary.exponential import (exp, log)
+from sympy.functions.elementary.hyperbolic import (sech, sinh)
+from sympy.functions.elementary.miscellaneous import sqrt
+from sympy.functions.elementary.piecewise import Piecewise
+from sympy.functions.elementary.trigonometric import (acos, atan, cos, sin, tan)
+from sympy.functions.special.delta_functions import DiracDelta
+from sympy.functions.special.gamma_functions import gamma
+from sympy.integrals.integrals import (Integral, integrate)
+from sympy.simplify.fu import fu
+
+
+from sympy.testing.pytest import XFAIL, slow, tooslow
+
+from sympy.abc import x, k, c, y, b, h, a, m, z, n, t
+
+
+@tooslow
+@XFAIL
+def test_issue_3880():
+ # integrate_hyperexponential(Poly(t*2*(1 - t0**2)*t0*(x**3 + x**2), t), Poly((1 + t0**2)**2*2*(x**2 + x + 1), t), [Poly(1, x), Poly(1 + t0**2, t0), Poly(t, t)], [x, t0, t], [exp, tan])
+ assert not integrate(exp(x)*cos(2*x)*sin(2*x) * (x**3 + x**2)/(2*(x**2 + x + 1)), x).has(Integral)
+
+
+def test_issue_4212_real():
+ xr = symbols('xr', real=True)
+ negabsx = Piecewise((-xr, xr < 0), (xr, True))
+ assert integrate(sign(xr), xr) == negabsx
+
+
+@XFAIL
+def test_issue_4212():
+ # XXX: Maybe this should be expected to fail without real assumptions on x.
+ # As a complex function sign(x) is not analytic and so there is no complex
+ # function whose complex derivative is sign(x). With real assumptions this
+ # works (see test_issue_4212_real above).
+ assert not integrate(sign(x), x).has(Integral)
+
+
+def test_issue_4511():
+ # This works, but gives a slightly over-complicated answer.
+ f = integrate(cos(x)**2 / (1 - sin(x)), x)
+ assert fu(f) == x - cos(x) - 1
+ assert f == ((x*tan(x/2)**2 + x - 2)/(tan(x/2)**2 + 1)).expand()
+
+
+def test_integrate_DiracDelta_no_meijerg():
+ assert integrate(integrate(integrate(
+ DiracDelta(x - y - z), (z, 0, oo)), (y, 0, 1), meijerg=False), (x, 0, 1)) == S.Half
+
+
+@XFAIL
+def test_integrate_DiracDelta_fails():
+ # issue 6427
+ # works without meijerg. See test_integrate_DiracDelta_no_meijerg above.
+ assert integrate(integrate(integrate(
+ DiracDelta(x - y - z), (z, 0, oo)), (y, 0, 1)), (x, 0, 1)) == S.Half
+
+
+@XFAIL
+@slow
+def test_issue_4525():
+ # Warning: takes a long time
+ assert not integrate((x**m * (1 - x)**n * (a + b*x + c*x**2))/(1 + x**2), (x, 0, 1)).has(Integral)
+
+
+@XFAIL
+@tooslow
+def test_issue_4540():
+ # Note, this integral is probably nonelementary
+ assert not integrate(
+ (sin(1/x) - x*exp(x)) /
+ ((-sin(1/x) + x*exp(x))*x + x*sin(1/x)), x).has(Integral)
+
+
+@XFAIL
+@slow
+def test_issue_4891():
+ # Requires the hypergeometric function.
+ assert not integrate(cos(x)**y, x).has(Integral)
+
+
+@XFAIL
+@slow
+def test_issue_1796a():
+ assert not integrate(exp(2*b*x)*exp(-a*x**2), x).has(Integral)
+
+
+@XFAIL
+def test_issue_4895b():
+ assert not integrate(exp(2*b*x)*exp(-a*x**2), (x, -oo, 0)).has(Integral)
+
+
+@XFAIL
+def test_issue_4895c():
+ assert not integrate(exp(2*b*x)*exp(-a*x**2), (x, -oo, oo)).has(Integral)
+
+
+@XFAIL
+def test_issue_4895d():
+ assert not integrate(exp(2*b*x)*exp(-a*x**2), (x, 0, oo)).has(Integral)
+
+
+@XFAIL
+@slow
+def test_issue_4941():
+ assert not integrate(sqrt(1 + sinh(x/20)**2), (x, -25, 25)).has(Integral)
+
+
+@XFAIL
+def test_issue_4992():
+ # Nonelementary integral. Requires hypergeometric/Meijer-G handling.
+ assert not integrate(log(x) * x**(k - 1) * exp(-x) / gamma(k), (x, 0, oo)).has(Integral)
+
+
+@XFAIL
+def test_issue_16396a():
+ i = integrate(1/(1+sqrt(tan(x))), (x, pi/3, pi/6))
+ assert not i.has(Integral)
+
+
+@XFAIL
+def test_issue_16396b():
+ i = integrate(x*sin(x)/(1+cos(x)**2), (x, 0, pi))
+ assert not i.has(Integral)
+
+
+@XFAIL
+def test_issue_16046():
+ assert integrate(exp(exp(I*x)), [x, 0, 2*pi]) == 2*pi
+
+
+@XFAIL
+def test_issue_15925a():
+ assert not integrate(sqrt((1+sin(x))**2+(cos(x))**2), (x, -pi/2, pi/2)).has(Integral)
+
+
+def test_issue_15925b():
+ f = sqrt((-12*cos(x)**2*sin(x))**2+(12*cos(x)*sin(x)**2)**2)
+ assert integrate(f, (x, 0, pi/6)) == Rational(3, 2)
+
+
+@XFAIL
+def test_issue_15925b_manual():
+ assert not integrate(sqrt((-12*cos(x)**2*sin(x))**2+(12*cos(x)*sin(x)**2)**2),
+ (x, 0, pi/6), manual=True).has(Integral)
+
+
+@XFAIL
+@tooslow
+def test_issue_15227():
+ i = integrate(log(1-x)*log((1+x)**2)/x, (x, 0, 1))
+ assert not i.has(Integral)
+ # assert i == -5*zeta(3)/4
+
+
+@XFAIL
+@slow
+def test_issue_14716():
+ i = integrate(log(x + 5)*cos(pi*x),(x, S.Half, 1))
+ assert not i.has(Integral)
+ # Mathematica can not solve it either, but
+ # integrate(log(x + 5)*cos(pi*x),(x, S.Half, 1)).transform(x, y - 5).doit()
+ # works
+ # assert i == -log(Rational(11, 2))/pi - Si(pi*Rational(11, 2))/pi + Si(6*pi)/pi
+
+
+@XFAIL
+def test_issue_14709a():
+ i = integrate(x*acos(1 - 2*x/h), (x, 0, h))
+ assert not i.has(Integral)
+ # assert i == 5*h**2*pi/16
+
+
+@slow
+@XFAIL
+def test_issue_14398():
+ assert not integrate(exp(x**2)*cos(x), x).has(Integral)
+
+
+@XFAIL
+def test_issue_14074():
+ i = integrate(log(sin(x)), (x, 0, pi/2))
+ assert not i.has(Integral)
+ # assert i == -pi*log(2)/2
+
+
+@XFAIL
+@slow
+def test_issue_14078b():
+ i = integrate((atan(4*x)-atan(2*x))/x, (x, 0, oo))
+ assert not i.has(Integral)
+ # assert i == pi*log(2)/2
+
+
+@XFAIL
+def test_issue_13792():
+ i = integrate(log(1/x) / (1 - x), (x, 0, 1))
+ assert not i.has(Integral)
+ # assert i in [polylog(2, -exp_polar(I*pi)), pi**2/6]
+
+
+@XFAIL
+def test_issue_11845a():
+ assert not integrate(exp(y - x**3), (x, 0, 1)).has(Integral)
+
+
+@XFAIL
+def test_issue_11845b():
+ assert not integrate(exp(-y - x**3), (x, 0, 1)).has(Integral)
+
+
+@XFAIL
+def test_issue_11813():
+ assert not integrate((a - x)**Rational(-1, 2)*x, (x, 0, a)).has(Integral)
+
+
+@XFAIL
+def test_issue_11254c():
+ assert not integrate(sech(x)**2, (x, 0, 1)).has(Integral)
+
+
+@XFAIL
+def test_issue_10584():
+ assert not integrate(sqrt(x**2 + 1/x**2), x).has(Integral)
+
+
+@XFAIL
+def test_issue_9101():
+ assert not integrate(log(x + sqrt(x**2 + y**2 + z**2)), z).has(Integral)
+
+
+@XFAIL
+def test_issue_7147():
+ assert not integrate(x/sqrt(a*x**2 + b*x + c)**3, x).has(Integral)
+
+
+@XFAIL
+def test_issue_7109():
+ assert not integrate(sqrt(a**2/(a**2 - x**2)), x).has(Integral)
+
+
+@XFAIL
+def test_integrate_Piecewise_rational_over_reals():
+ f = Piecewise(
+ (0, t - 478.515625*pi < 0),
+ (13.2075145209219*pi/(0.000871222*t + 0.995)**2, t - 478.515625*pi >= 0))
+
+ assert abs((integrate(f, (t, 0, oo)) - 15235.9375*pi).evalf()) <= 1e-7
+
+
+@XFAIL
+def test_issue_4311_slow():
+ # Not slow when bypassing heurish
+ assert not integrate(x*abs(9-x**2), x).has(Integral)
+
+@XFAIL
+def test_issue_20370():
+ a = symbols('a', positive=True)
+ assert integrate((1 + a * cos(x))**-1, (x, 0, 2 * pi)) == (2 * pi / sqrt(1 - a**2))
+
+
+@XFAIL
+def test_polylog():
+ # log(1/x)*log(x+1)-polylog(2, -x)
+ assert not integrate(log(1/x)/(x + 1), x).has(Integral)
+
+
+@XFAIL
+def test_polylog_manual():
+ # Make sure _parts_rule does not go into an infinite loop here
+ assert not integrate(log(1/x)/(x + 1), x, manual=True).has(Integral)
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_integrals.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_integrals.py
new file mode 100644
index 0000000000000000000000000000000000000000..8436d6127c97809dd3a59cadb75d44fb4800ee68
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_integrals.py
@@ -0,0 +1,2141 @@
+import math
+from sympy.concrete.summations import (Sum, summation)
+from sympy.core.add import Add
+from sympy.core.containers import Tuple
+from sympy.core.expr import Expr
+from sympy.core.function import (Derivative, Function, Lambda, diff)
+from sympy.core import EulerGamma
+from sympy.core.numbers import (E, I, Rational, nan, oo, pi, zoo, all_close)
+from sympy.core.relational import (Eq, Ne)
+from sympy.core.singleton import S
+from sympy.core.symbol import (Symbol, symbols)
+from sympy.core.sympify import sympify
+from sympy.functions.elementary.complexes import (Abs, im, polar_lift, re, sign)
+from sympy.functions.elementary.exponential import (LambertW, exp, exp_polar, log)
+from sympy.functions.elementary.hyperbolic import (acosh, asinh, cosh, coth, csch, sinh, tanh, sech)
+from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt)
+from sympy.functions.elementary.piecewise import Piecewise
+from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sin, sinc, tan, sec)
+from sympy.functions.special.delta_functions import DiracDelta, Heaviside
+from sympy.functions.special.error_functions import (Ci, Ei, Si, erf, erfc, erfi, fresnelc, li)
+from sympy.functions.special.gamma_functions import (gamma, polygamma)
+from sympy.functions.special.hyper import (hyper, meijerg)
+from sympy.functions.special.singularity_functions import SingularityFunction
+from sympy.functions.special.zeta_functions import lerchphi
+from sympy.integrals.integrals import integrate
+from sympy.logic.boolalg import And
+from sympy.matrices.dense import Matrix
+from sympy.polys.polytools import (Poly, factor)
+from sympy.printing.str import sstr
+from sympy.series.order import O
+from sympy.sets.sets import Interval
+from sympy.simplify.gammasimp import gammasimp
+from sympy.simplify.simplify import simplify
+from sympy.simplify.trigsimp import trigsimp
+from sympy.tensor.indexed import (Idx, IndexedBase)
+from sympy.core.expr import unchanged
+from sympy.functions.elementary.integers import floor
+from sympy.integrals.integrals import Integral
+from sympy.integrals.risch import NonElementaryIntegral
+from sympy.physics import units
+from sympy.testing.pytest import raises, slow, warns_deprecated_sympy, warns
+from sympy.utilities.exceptions import SymPyDeprecationWarning
+from sympy.core.random import verify_numerically
+
+
+x, y, z, a, b, c, d, e, s, t, x_1, x_2 = symbols('x y z a b c d e s t x_1 x_2')
+n = Symbol('n', integer=True)
+f = Function('f')
+
+
+def NS(e, n=15, **options):
+ return sstr(sympify(e).evalf(n, **options), full_prec=True)
+
+
+def test_poly_deprecated():
+ p = Poly(2*x, x)
+ assert p.integrate(x) == Poly(x**2, x, domain='QQ')
+ # The stacklevel is based on Integral(Poly)
+ with warns(SymPyDeprecationWarning, test_stacklevel=False):
+ integrate(p, x)
+ with warns(SymPyDeprecationWarning, test_stacklevel=False):
+ Integral(p, (x,))
+
+
+@slow
+def test_principal_value():
+ g = 1 / x
+ assert Integral(g, (x, -oo, oo)).principal_value() == 0
+ assert Integral(g, (y, -oo, oo)).principal_value() == oo * sign(1 / x)
+ raises(ValueError, lambda: Integral(g, (x)).principal_value())
+ raises(ValueError, lambda: Integral(g).principal_value())
+
+ l = 1 / ((x ** 3) - 1)
+ assert Integral(l, (x, -oo, oo)).principal_value().together() == -sqrt(3)*pi/3
+ raises(ValueError, lambda: Integral(l, (x, -oo, 1)).principal_value())
+
+ d = 1 / (x ** 2 - 1)
+ assert Integral(d, (x, -oo, oo)).principal_value() == 0
+ assert Integral(d, (x, -2, 2)).principal_value() == -log(3)
+
+ v = x / (x ** 2 - 1)
+ assert Integral(v, (x, -oo, oo)).principal_value() == 0
+ assert Integral(v, (x, -2, 2)).principal_value() == 0
+
+ s = x ** 2 / (x ** 2 - 1)
+ assert Integral(s, (x, -oo, oo)).principal_value() is oo
+ assert Integral(s, (x, -2, 2)).principal_value() == -log(3) + 4
+
+ f = 1 / ((x ** 2 - 1) * (1 + x ** 2))
+ assert Integral(f, (x, -oo, oo)).principal_value() == -pi / 2
+ assert Integral(f, (x, -2, 2)).principal_value() == -atan(2) - log(3) / 2
+
+
+def diff_test(i):
+ """Return the set of symbols, s, which were used in testing that
+ i.diff(s) agrees with i.doit().diff(s). If there is an error then
+ the assertion will fail, causing the test to fail."""
+ syms = i.free_symbols
+ for s in syms:
+ assert (i.diff(s).doit() - i.doit().diff(s)).expand() == 0
+ return syms
+
+
+def test_improper_integral():
+ assert integrate(log(x), (x, 0, 1)) == -1
+ assert integrate(x**(-2), (x, 1, oo)) == 1
+ assert integrate(1/(1 + exp(x)), (x, 0, oo)) == log(2)
+
+
+def test_constructor():
+ # this is shared by Sum, so testing Integral's constructor
+ # is equivalent to testing Sum's
+ s1 = Integral(n, n)
+ assert s1.limits == (Tuple(n),)
+ s2 = Integral(n, (n,))
+ assert s2.limits == (Tuple(n),)
+ s3 = Integral(Sum(x, (x, 1, y)))
+ assert s3.limits == (Tuple(y),)
+ s4 = Integral(n, Tuple(n,))
+ assert s4.limits == (Tuple(n),)
+
+ s5 = Integral(n, (n, Interval(1, 2)))
+ assert s5.limits == (Tuple(n, 1, 2),)
+
+ # Testing constructor with inequalities:
+ s6 = Integral(n, n > 10)
+ assert s6.limits == (Tuple(n, 10, oo),)
+ s7 = Integral(n, (n > 2) & (n < 5))
+ assert s7.limits == (Tuple(n, 2, 5),)
+
+
+def test_basics():
+
+ assert Integral(0, x) != 0
+ assert Integral(x, (x, 1, 1)) != 0
+ assert Integral(oo, x) != oo
+ assert Integral(S.NaN, x) is S.NaN
+
+ assert diff(Integral(y, y), x) == 0
+ assert diff(Integral(x, (x, 0, 1)), x) == 0
+ assert diff(Integral(x, x), x) == x
+ assert diff(Integral(t, (t, 0, x)), x) == x
+
+ e = (t + 1)**2
+ assert diff(integrate(e, (t, 0, x)), x) == \
+ diff(Integral(e, (t, 0, x)), x).doit().expand() == \
+ ((1 + x)**2).expand()
+ assert diff(integrate(e, (t, 0, x)), t) == \
+ diff(Integral(e, (t, 0, x)), t) == 0
+ assert diff(integrate(e, (t, 0, x)), a) == \
+ diff(Integral(e, (t, 0, x)), a) == 0
+ assert diff(integrate(e, t), a) == diff(Integral(e, t), a) == 0
+
+ assert integrate(e, (t, a, x)).diff(x) == \
+ Integral(e, (t, a, x)).diff(x).doit().expand()
+ assert Integral(e, (t, a, x)).diff(x).doit() == ((1 + x)**2)
+ assert integrate(e, (t, x, a)).diff(x).doit() == (-(1 + x)**2).expand()
+
+ assert integrate(t**2, (t, x, 2*x)).diff(x) == 7*x**2
+
+ assert Integral(x, x).atoms() == {x}
+ assert Integral(f(x), (x, 0, 1)).atoms() == {S.Zero, S.One, x}
+
+ assert diff_test(Integral(x, (x, 3*y))) == {y}
+ assert diff_test(Integral(x, (a, 3*y))) == {x, y}
+
+ assert integrate(x, (x, oo, oo)) == 0 #issue 8171
+ assert integrate(x, (x, -oo, -oo)) == 0
+
+ # sum integral of terms
+ assert integrate(y + x + exp(x), x) == x*y + x**2/2 + exp(x)
+
+ assert Integral(x).is_commutative
+ n = Symbol('n', commutative=False)
+ assert Integral(n + x, x).is_commutative is False
+
+
+def test_diff_wrt():
+ class Test(Expr):
+ _diff_wrt = True
+ is_commutative = True
+
+ t = Test()
+ assert integrate(t + 1, t) == t**2/2 + t
+ assert integrate(t + 1, (t, 0, 1)) == Rational(3, 2)
+
+ raises(ValueError, lambda: integrate(x + 1, x + 1))
+ raises(ValueError, lambda: integrate(x + 1, (x + 1, 0, 1)))
+
+
+def test_basics_multiple():
+ assert diff_test(Integral(x, (x, 3*x, 5*y), (y, x, 2*x))) == {x}
+ assert diff_test(Integral(x, (x, 5*y), (y, x, 2*x))) == {x}
+ assert diff_test(Integral(x, (x, 5*y), (y, y, 2*x))) == {x, y}
+ assert diff_test(Integral(y, y, x)) == {x, y}
+ assert diff_test(Integral(y*x, x, y)) == {x, y}
+ assert diff_test(Integral(x + y, y, (y, 1, x))) == {x}
+ assert diff_test(Integral(x + y, (x, x, y), (y, y, x))) == {x, y}
+
+
+def test_conjugate_transpose():
+ A, B = symbols("A B", commutative=False)
+
+ x = Symbol("x", complex=True)
+ p = Integral(A*B, (x,))
+ assert p.adjoint().doit() == p.doit().adjoint()
+ assert p.conjugate().doit() == p.doit().conjugate()
+ assert p.transpose().doit() == p.doit().transpose()
+
+ x = Symbol("x", real=True)
+ p = Integral(A*B, (x,))
+ assert p.adjoint().doit() == p.doit().adjoint()
+ assert p.conjugate().doit() == p.doit().conjugate()
+ assert p.transpose().doit() == p.doit().transpose()
+
+
+def test_integration():
+ assert integrate(0, (t, 0, x)) == 0
+ assert integrate(3, (t, 0, x)) == 3*x
+ assert integrate(t, (t, 0, x)) == x**2/2
+ assert integrate(3*t, (t, 0, x)) == 3*x**2/2
+ assert integrate(3*t**2, (t, 0, x)) == x**3
+ assert integrate(1/t, (t, 1, x)) == log(x)
+ assert integrate(-1/t**2, (t, 1, x)) == 1/x - 1
+ assert integrate(t**2 + 5*t - 8, (t, 0, x)) == x**3/3 + 5*x**2/2 - 8*x
+ assert integrate(x**2, x) == x**3/3
+ assert integrate((3*t*x)**5, x) == (3*t)**5 * x**6 / 6
+
+ b = Symbol("b")
+ c = Symbol("c")
+ assert integrate(a*t, (t, 0, x)) == a*x**2/2
+ assert integrate(a*t**4, (t, 0, x)) == a*x**5/5
+ assert integrate(a*t**2 + b*t + c, (t, 0, x)) == a*x**3/3 + b*x**2/2 + c*x
+
+
+def test_multiple_integration():
+ assert integrate((x**2)*(y**2), (x, 0, 1), (y, -1, 2)) == Rational(1)
+ assert integrate((y**2)*(x**2), x, y) == Rational(1, 9)*(x**3)*(y**3)
+ assert integrate(1/(x + 3)/(1 + x)**3, x) == \
+ log(3 + x)*Rational(-1, 8) + log(1 + x)*Rational(1, 8) + x/(4 + 8*x + 4*x**2)
+ assert integrate(sin(x*y)*y, (x, 0, 1), (y, 0, 1)) == -sin(1) + 1
+
+
+def test_issue_3532():
+ assert integrate(exp(-x), (x, 0, oo)) == 1
+
+
+def test_issue_3560():
+ assert integrate(sqrt(x)**3, x) == 2*sqrt(x)**5/5
+ assert integrate(sqrt(x), x) == 2*sqrt(x)**3/3
+ assert integrate(1/sqrt(x)**3, x) == -2/sqrt(x)
+
+
+def test_issue_18038():
+ raises(AttributeError, lambda: integrate((x, x)))
+
+
+def test_integrate_poly():
+ p = Poly(x + x**2*y + y**3, x, y)
+
+ # The stacklevel is based on Integral(Poly)
+ with warns_deprecated_sympy():
+ qx = Integral(p, x)
+ with warns(SymPyDeprecationWarning, test_stacklevel=False):
+ qx = integrate(p, x)
+ with warns(SymPyDeprecationWarning, test_stacklevel=False):
+ qy = integrate(p, y)
+
+ assert isinstance(qx, Poly) is True
+ assert isinstance(qy, Poly) is True
+
+ assert qx.gens == (x, y)
+ assert qy.gens == (x, y)
+
+ assert qx.as_expr() == x**2/2 + x**3*y/3 + x*y**3
+ assert qy.as_expr() == x*y + x**2*y**2/2 + y**4/4
+
+
+def test_integrate_poly_definite():
+ p = Poly(x + x**2*y + y**3, x, y)
+
+ with warns_deprecated_sympy():
+ Qx = Integral(p, (x, 0, 1))
+ with warns(SymPyDeprecationWarning, test_stacklevel=False):
+ Qx = integrate(p, (x, 0, 1))
+ with warns(SymPyDeprecationWarning, test_stacklevel=False):
+ Qy = integrate(p, (y, 0, pi))
+
+ assert isinstance(Qx, Poly) is True
+ assert isinstance(Qy, Poly) is True
+
+ assert Qx.gens == (y,)
+ assert Qy.gens == (x,)
+
+ assert Qx.as_expr() == S.Half + y/3 + y**3
+ assert Qy.as_expr() == pi**4/4 + pi*x + pi**2*x**2/2
+
+
+def test_integrate_omit_var():
+ y = Symbol('y')
+
+ assert integrate(x) == x**2/2
+
+ raises(ValueError, lambda: integrate(2))
+ raises(ValueError, lambda: integrate(x*y))
+
+
+def test_integrate_poly_accurately():
+ y = Symbol('y')
+ assert integrate(x*sin(y), x) == x**2*sin(y)/2
+
+ # when passed to risch_norman, this will be a CPU hog, so this really
+ # checks, that integrated function is recognized as polynomial
+ assert integrate(x**1000*sin(y), x) == x**1001*sin(y)/1001
+
+
+def test_issue_3635():
+ y = Symbol('y')
+ assert integrate(x**2, y) == x**2*y
+ assert integrate(x**2, (y, -1, 1)) == 2*x**2
+
+# works in SymPy and py.test but hangs in `setup.py test`
+
+
+def test_integrate_linearterm_pow():
+ # check integrate((a*x+b)^c, x) -- issue 3499
+ y = Symbol('y', positive=True)
+ # TODO: Remove conds='none' below, let the assumption take care of it.
+ assert integrate(x**y, x, conds='none') == x**(y + 1)/(y + 1)
+ assert integrate((exp(y)*x + 1/y)**(1 + sin(y)), x, conds='none') == \
+ exp(-y)*(exp(y)*x + 1/y)**(2 + sin(y)) / (2 + sin(y))
+
+
+def test_issue_3618():
+ assert integrate(pi*sqrt(x), x) == 2*pi*sqrt(x)**3/3
+ assert integrate(pi*sqrt(x) + E*sqrt(x)**3, x) == \
+ 2*pi*sqrt(x)**3/3 + 2*E *sqrt(x)**5/5
+
+
+def test_issue_3623():
+ assert integrate(cos((n + 1)*x), x) == Piecewise(
+ (sin(x*(n + 1))/(n + 1), Ne(n + 1, 0)), (x, True))
+ assert integrate(cos((n - 1)*x), x) == Piecewise(
+ (sin(x*(n - 1))/(n - 1), Ne(n - 1, 0)), (x, True))
+ assert integrate(cos((n + 1)*x) + cos((n - 1)*x), x) == \
+ Piecewise((sin(x*(n - 1))/(n - 1), Ne(n - 1, 0)), (x, True)) + \
+ Piecewise((sin(x*(n + 1))/(n + 1), Ne(n + 1, 0)), (x, True))
+
+
+def test_issue_3664():
+ n = Symbol('n', integer=True, nonzero=True)
+ assert integrate(-1./2 * x * sin(n * pi * x/2), [x, -2, 0]) == \
+ 2.0*cos(pi*n)/(pi*n)
+ assert integrate(x * sin(n * pi * x/2) * Rational(-1, 2), [x, -2, 0]) == \
+ 2*cos(pi*n)/(pi*n)
+
+
+def test_issue_3679():
+ # definite integration of rational functions gives wrong answers
+ assert NS(Integral(1/(x**2 - 8*x + 17), (x, 2, 4))) == '1.10714871779409'
+
+
+def test_issue_3686(): # remove this when fresnel integrals are implemented
+ from sympy.core.function import expand_func
+ from sympy.functions.special.error_functions import fresnels
+ assert expand_func(integrate(sin(x**2), x)) == \
+ sqrt(2)*sqrt(pi)*fresnels(sqrt(2)*x/sqrt(pi))/2
+
+
+def test_integrate_units():
+ m = units.m
+ s = units.s
+ assert integrate(x * m/s, (x, 1*s, 5*s)) == 12*m*s
+
+
+def test_transcendental_functions():
+ assert integrate(LambertW(2*x), x) == \
+ -x + x*LambertW(2*x) + x/LambertW(2*x)
+
+
+def test_log_polylog():
+ assert integrate(log(1 - x)/x, (x, 0, 1)) == -pi**2/6
+ assert integrate(log(x)*(1 - x)**(-1), (x, 0, 1)) == -pi**2/6
+
+
+def test_issue_3740():
+ f = 4*log(x) - 2*log(x)**2
+ fid = diff(integrate(f, x), x)
+ assert abs(f.subs(x, 42).evalf() - fid.subs(x, 42).evalf()) < 1e-10
+
+
+def test_issue_3788():
+ assert integrate(1/(1 + x**2), x) == atan(x)
+
+
+def test_issue_3952():
+ f = sin(x)
+ assert integrate(f, x) == -cos(x)
+ raises(ValueError, lambda: integrate(f, 2*x))
+
+
+def test_issue_4516():
+ assert integrate(2**x - 2*x, x) == 2**x/log(2) - x**2
+
+
+def test_issue_7450():
+ ans = integrate(exp(-(1 + I)*x), (x, 0, oo))
+ assert re(ans) == S.Half and im(ans) == Rational(-1, 2)
+
+
+def test_issue_8623():
+ assert integrate((1 + cos(2*x)) / (3 - 2*cos(2*x)), (x, 0, pi)) == -pi/2 + sqrt(5)*pi/2
+ assert integrate((1 + cos(2*x))/(3 - 2*cos(2*x))) == -x/2 + sqrt(5)*(atan(sqrt(5)*tan(x)) + \
+ pi*floor((x - pi/2)/pi))/2
+
+
+def test_issue_9569():
+ assert integrate(1 / (2 - cos(x)), (x, 0, pi)) == pi/sqrt(3)
+ assert integrate(1/(2 - cos(x))) == 2*sqrt(3)*(atan(sqrt(3)*tan(x/2)) + pi*floor((x/2 - pi/2)/pi))/3
+
+
+def test_issue_13733():
+ s = Symbol('s', positive=True)
+ pz = exp(-(z - y)**2/(2*s*s))/sqrt(2*pi*s*s)
+ pzgx = integrate(pz, (z, x, oo))
+ assert integrate(pzgx, (x, 0, oo)) == sqrt(2)*s*exp(-y**2/(2*s**2))/(2*sqrt(pi)) + \
+ y*erf(sqrt(2)*y/(2*s))/2 + y/2
+
+
+def test_issue_13749():
+ assert integrate(1 / (2 + cos(x)), (x, 0, pi)) == pi/sqrt(3)
+ assert integrate(1/(2 + cos(x))) == 2*sqrt(3)*(atan(sqrt(3)*tan(x/2)/3) + pi*floor((x/2 - pi/2)/pi))/3
+
+
+def test_issue_18133():
+ assert integrate(exp(x)/(1 + x)**2, x) == NonElementaryIntegral(exp(x)/(x + 1)**2, x)
+
+
+def test_issue_21741():
+ a = 4e6
+ b = 2.5e-7
+ r = Piecewise((b*I*exp(-a*I*pi*t*y)*exp(-a*I*pi*x*z)/(pi*x), Ne(x, 0)),
+ (z*exp(-a*I*pi*t*y), True))
+ fun = E**((-2*I*pi*(z*x+t*y))/(500*10**(-9)))
+ assert all_close(integrate(fun, z), r)
+
+
+def test_matrices():
+ M = Matrix(2, 2, lambda i, j: (i + j + 1)*sin((i + j + 1)*x))
+
+ assert integrate(M, x) == Matrix([
+ [-cos(x), -cos(2*x)],
+ [-cos(2*x), -cos(3*x)],
+ ])
+
+
+def test_integrate_functions():
+ # issue 4111
+ assert integrate(f(x), x) == Integral(f(x), x)
+ assert integrate(f(x), (x, 0, 1)) == Integral(f(x), (x, 0, 1))
+ assert integrate(f(x)*diff(f(x), x), x) == f(x)**2/2
+ assert integrate(diff(f(x), x) / f(x), x) == log(f(x))
+
+
+def test_integrate_derivatives():
+ assert integrate(Derivative(f(x), x), x) == f(x)
+ assert integrate(Derivative(f(y), y), x) == x*Derivative(f(y), y)
+ assert integrate(Derivative(f(x), x)**2, x) == \
+ Integral(Derivative(f(x), x)**2, x)
+
+
+def test_transform():
+ a = Integral(x**2 + 1, (x, -1, 2))
+ fx = x
+ fy = 3*y + 1
+ assert a.doit() == a.transform(fx, fy).doit()
+ assert a.transform(fx, fy).transform(fy, fx) == a
+ fx = 3*x + 1
+ fy = y
+ assert a.transform(fx, fy).transform(fy, fx) == a
+ a = Integral(sin(1/x), (x, 0, 1))
+ assert a.transform(x, 1/y) == Integral(sin(y)/y**2, (y, 1, oo))
+ assert a.transform(x, 1/y).transform(y, 1/x) == a
+ a = Integral(exp(-x**2), (x, -oo, oo))
+ assert a.transform(x, 2*y) == Integral(2*exp(-4*y**2), (y, -oo, oo))
+ # < 3 arg limit handled properly
+ assert Integral(x, x).transform(x, a*y).doit() == \
+ Integral(y*a**2, y).doit()
+ _3 = S(3)
+ assert Integral(x, (x, 0, -_3)).transform(x, 1/y).doit() == \
+ Integral(-1/x**3, (x, -oo, -1/_3)).doit()
+ assert Integral(x, (x, 0, _3)).transform(x, 1/y) == \
+ Integral(y**(-3), (y, 1/_3, oo))
+ # issue 8400
+ i = Integral(x + y, (x, 1, 2), (y, 1, 2))
+ assert i.transform(x, (x + 2*y, x)).doit() == \
+ i.transform(x, (x + 2*z, x)).doit() == 3
+
+ i = Integral(x, (x, a, b))
+ assert i.transform(x, 2*s) == Integral(4*s, (s, a/2, b/2))
+ raises(ValueError, lambda: i.transform(x, 1))
+ raises(ValueError, lambda: i.transform(x, s*t))
+ raises(ValueError, lambda: i.transform(x, -s))
+ raises(ValueError, lambda: i.transform(x, (s, t)))
+ raises(ValueError, lambda: i.transform(2*x, 2*s))
+
+ i = Integral(x**2, (x, 1, 2))
+ raises(ValueError, lambda: i.transform(x**2, s))
+
+ am = Symbol('a', negative=True)
+ bp = Symbol('b', positive=True)
+ i = Integral(x, (x, bp, am))
+ i.transform(x, 2*s)
+ assert i.transform(x, 2*s) == Integral(-4*s, (s, am/2, bp/2))
+
+ i = Integral(x, (x, a))
+ assert i.transform(x, 2*s) == Integral(4*s, (s, a/2))
+
+
+def test_issue_4052():
+ f = S.Half*asin(x) + x*sqrt(1 - x**2)/2
+
+ assert integrate(cos(asin(x)), x) == f
+ assert integrate(sin(acos(x)), x) == f
+
+
+@slow
+def test_evalf_integrals():
+ assert NS(Integral(x, (x, 2, 5)), 15) == '10.5000000000000'
+ gauss = Integral(exp(-x**2), (x, -oo, oo))
+ assert NS(gauss, 15) == '1.77245385090552'
+ assert NS(gauss**2 - pi + E*Rational(
+ 1, 10**20), 15) in ('2.71828182845904e-20', '2.71828182845905e-20')
+ # A monster of an integral from http://mathworld.wolfram.com/DefiniteIntegral.html
+ t = Symbol('t')
+ a = 8*sqrt(3)/(1 + 3*t**2)
+ b = 16*sqrt(2)*(3*t + 1)*sqrt(4*t**2 + t + 1)**3
+ c = (3*t**2 + 1)*(11*t**2 + 2*t + 3)**2
+ d = sqrt(2)*(249*t**2 + 54*t + 65)/(11*t**2 + 2*t + 3)**2
+ f = a - b/c - d
+ assert NS(Integral(f, (t, 0, 1)), 50) == \
+ NS((3*sqrt(2) - 49*pi + 162*atan(sqrt(2)))/12, 50)
+ # http://mathworld.wolfram.com/VardisIntegral.html
+ assert NS(Integral(log(log(1/x))/(1 + x + x**2), (x, 0, 1)), 15) == \
+ NS('pi/sqrt(3) * log(2*pi**(5/6) / gamma(1/6))', 15)
+ # http://mathworld.wolfram.com/AhmedsIntegral.html
+ assert NS(Integral(atan(sqrt(x**2 + 2))/(sqrt(x**2 + 2)*(x**2 + 1)), (x,
+ 0, 1)), 15) == NS(5*pi**2/96, 15)
+ # http://mathworld.wolfram.com/AbelsIntegral.html
+ assert NS(Integral(x/((exp(pi*x) - exp(
+ -pi*x))*(x**2 + 1)), (x, 0, oo)), 15) == NS('log(2)/2-1/4', 15)
+ # Complex part trimming
+ # http://mathworld.wolfram.com/VardisIntegral.html
+ assert NS(Integral(log(log(sin(x)/cos(x))), (x, pi/4, pi/2)), 15, chop=True) == \
+ NS('pi/4*log(4*pi**3/gamma(1/4)**4)', 15)
+ #
+ # Endpoints causing trouble (rounding error in integration points -> complex log)
+ assert NS(
+ 2 + Integral(log(2*cos(x/2)), (x, -pi, pi)), 17, chop=True) == NS(2, 17)
+ assert NS(
+ 2 + Integral(log(2*cos(x/2)), (x, -pi, pi)), 20, chop=True) == NS(2, 20)
+ assert NS(
+ 2 + Integral(log(2*cos(x/2)), (x, -pi, pi)), 22, chop=True) == NS(2, 22)
+ # Needs zero handling
+ assert NS(pi - 4*Integral(
+ 'sqrt(1-x**2)', (x, 0, 1)), 15, maxn=30, chop=True) in ('0.0', '0')
+ # Oscillatory quadrature
+ a = Integral(sin(x)/x**2, (x, 1, oo)).evalf(maxn=15)
+ assert 0.49 < a < 0.51
+ assert NS(
+ Integral(sin(x)/x**2, (x, 1, oo)), quad='osc') == '0.504067061906928'
+ assert NS(Integral(
+ cos(pi*x + 1)/x, (x, -oo, -1)), quad='osc') == '0.276374705640365'
+ # indefinite integrals aren't evaluated
+ assert NS(Integral(x, x)) == 'Integral(x, x)'
+ assert NS(Integral(x, (x, y))) == 'Integral(x, (x, y))'
+
+
+def test_evalf_issue_939():
+ # https://github.com/sympy/sympy/issues/4038
+
+ # The output form of an integral may differ by a step function between
+ # revisions, making this test a bit useless. This can't be said about
+ # other two tests. For now, all values of this evaluation are used here,
+ # but in future this should be reconsidered.
+ assert NS(integrate(1/(x**5 + 1), x).subs(x, 4), chop=True) in \
+ ['-0.000976138910649103', '0.965906660135753', '1.93278945918216']
+
+ assert NS(Integral(1/(x**5 + 1), (x, 2, 4))) == '0.0144361088886740'
+ assert NS(
+ integrate(1/(x**5 + 1), (x, 2, 4)), chop=True) == '0.0144361088886740'
+
+
+def test_double_previously_failing_integrals():
+ # Double integrals not implemented <- Sure it is!
+ res = integrate(sqrt(x) + x*y, (x, 1, 2), (y, -1, 1))
+ # Old numerical test
+ assert NS(res, 15) == '2.43790283299492'
+ # Symbolic test
+ assert res == Rational(-4, 3) + 8*sqrt(2)/3
+ # double integral + zero detection
+ assert integrate(sin(x + x*y), (x, -1, 1), (y, -1, 1)) is S.Zero
+
+
+def test_integrate_SingularityFunction():
+ in_1 = SingularityFunction(x, a, 3) + SingularityFunction(x, 5, -1)
+ out_1 = SingularityFunction(x, a, 4)/4 + SingularityFunction(x, 5, 0)
+ assert integrate(in_1, x) == out_1
+
+ in_2 = 10*SingularityFunction(x, 4, 0) - 5*SingularityFunction(x, -6, -2)
+ out_2 = 10*SingularityFunction(x, 4, 1) - 5*SingularityFunction(x, -6, -1)
+ assert integrate(in_2, x) == out_2
+
+ in_3 = 2*x**2*y -10*SingularityFunction(x, -4, 7) - 2*SingularityFunction(y, 10, -2)
+ out_3_1 = 2*x**3*y/3 - 2*x*SingularityFunction(y, 10, -2) - 5*SingularityFunction(x, -4, 8)/4
+ out_3_2 = x**2*y**2 - 10*y*SingularityFunction(x, -4, 7) - 2*SingularityFunction(y, 10, -1)
+ assert integrate(in_3, x) == out_3_1
+ assert integrate(in_3, y) == out_3_2
+
+ assert unchanged(Integral, in_3, (x,))
+ assert Integral(in_3, x) == Integral(in_3, (x,))
+ assert Integral(in_3, x).doit() == out_3_1
+
+ in_4 = 10*SingularityFunction(x, -4, 7) - 2*SingularityFunction(x, 10, -2)
+ out_4 = 5*SingularityFunction(x, -4, 8)/4 - 2*SingularityFunction(x, 10, -1)
+ assert integrate(in_4, (x, -oo, x)) == out_4
+
+ assert integrate(SingularityFunction(x, 5, -1), x) == SingularityFunction(x, 5, 0)
+ assert integrate(SingularityFunction(x, 0, -1), (x, -oo, oo)) == 1
+ assert integrate(5*SingularityFunction(x, 5, -1), (x, -oo, oo)) == 5
+ assert integrate(SingularityFunction(x, 5, -1) * f(x), (x, -oo, oo)) == f(5)
+
+
+def test_integrate_DiracDelta():
+ # This is here to check that deltaintegrate is being called, but also
+ # to test definite integrals. More tests are in test_deltafunctions.py
+ assert integrate(DiracDelta(x) * f(x), (x, -oo, oo)) == f(0)
+ assert integrate(DiracDelta(x)**2, (x, -oo, oo)) == DiracDelta(0)
+ # issue 4522
+ assert integrate(integrate((4 - 4*x + x*y - 4*y) * \
+ DiracDelta(x)*DiracDelta(y - 1), (x, 0, 1)), (y, 0, 1)) == 0
+ # issue 5729
+ p = exp(-(x**2 + y**2))/pi
+ assert integrate(p*DiracDelta(x - 10*y), (x, -oo, oo), (y, -oo, oo)) == \
+ integrate(p*DiracDelta(x - 10*y), (y, -oo, oo), (x, -oo, oo)) == \
+ integrate(p*DiracDelta(10*x - y), (x, -oo, oo), (y, -oo, oo)) == \
+ integrate(p*DiracDelta(10*x - y), (y, -oo, oo), (x, -oo, oo)) == \
+ 1/sqrt(101*pi)
+
+
+def test_integrate_returns_piecewise():
+ assert integrate(x**y, x) == Piecewise(
+ (x**(y + 1)/(y + 1), Ne(y, -1)), (log(x), True))
+ assert integrate(x**y, y) == Piecewise(
+ (x**y/log(x), Ne(log(x), 0)), (y, True))
+ assert integrate(exp(n*x), x) == Piecewise(
+ (exp(n*x)/n, Ne(n, 0)), (x, True))
+ assert integrate(x*exp(n*x), x) == Piecewise(
+ ((n*x - 1)*exp(n*x)/n**2, Ne(n**2, 0)), (x**2/2, True))
+ assert integrate(x**(n*y), x) == Piecewise(
+ (x**(n*y + 1)/(n*y + 1), Ne(n*y, -1)), (log(x), True))
+ assert integrate(x**(n*y), y) == Piecewise(
+ (x**(n*y)/(n*log(x)), Ne(n*log(x), 0)), (y, True))
+ assert integrate(cos(n*x), x) == Piecewise(
+ (sin(n*x)/n, Ne(n, 0)), (x, True))
+ assert integrate(cos(n*x)**2, x) == Piecewise(
+ ((n*x/2 + sin(n*x)*cos(n*x)/2)/n, Ne(n, 0)), (x, True))
+ assert integrate(x*cos(n*x), x) == Piecewise(
+ (x*sin(n*x)/n + cos(n*x)/n**2, Ne(n, 0)), (x**2/2, True))
+ assert integrate(sin(n*x), x) == Piecewise(
+ (-cos(n*x)/n, Ne(n, 0)), (0, True))
+ assert integrate(sin(n*x)**2, x) == Piecewise(
+ ((n*x/2 - sin(n*x)*cos(n*x)/2)/n, Ne(n, 0)), (0, True))
+ assert integrate(x*sin(n*x), x) == Piecewise(
+ (-x*cos(n*x)/n + sin(n*x)/n**2, Ne(n, 0)), (0, True))
+ assert integrate(exp(x*y), (x, 0, z)) == Piecewise(
+ (exp(y*z)/y - 1/y, (y > -oo) & (y < oo) & Ne(y, 0)), (z, True))
+ # https://github.com/sympy/sympy/issues/23707
+ assert integrate(exp(t)*exp(-t*sqrt(x - y)), t) == Piecewise(
+ (-exp(t)/(sqrt(x - y)*exp(t*sqrt(x - y)) - exp(t*sqrt(x - y))),
+ Ne(x, y + 1)), (t, True))
+
+
+def test_integrate_max_min():
+ x = symbols('x', real=True)
+ assert integrate(Min(x, 2), (x, 0, 3)) == 4
+ assert integrate(Max(x**2, x**3), (x, 0, 2)) == Rational(49, 12)
+ assert integrate(Min(exp(x), exp(-x))**2, x) == Piecewise( \
+ (exp(2*x)/2, x <= 0), (1 - exp(-2*x)/2, True))
+ # issue 7907
+ c = symbols('c', extended_real=True)
+ int1 = integrate(Max(c, x)*exp(-x**2), (x, -oo, oo))
+ int2 = integrate(c*exp(-x**2), (x, -oo, c))
+ int3 = integrate(x*exp(-x**2), (x, c, oo))
+ assert int1 == int2 + int3 == sqrt(pi)*c*erf(c)/2 + \
+ sqrt(pi)*c/2 + exp(-c**2)/2
+
+
+def test_integrate_Abs_sign():
+ assert integrate(Abs(x), (x, -2, 1)) == Rational(5, 2)
+ assert integrate(Abs(x), (x, 0, 1)) == S.Half
+ assert integrate(Abs(x + 1), (x, 0, 1)) == Rational(3, 2)
+ assert integrate(Abs(x**2 - 1), (x, -2, 2)) == 4
+ assert integrate(Abs(x**2 - 3*x), (x, -15, 15)) == 2259
+ assert integrate(sign(x), (x, -1, 2)) == 1
+ assert integrate(sign(x)*sin(x), (x, -pi, pi)) == 4
+ assert integrate(sign(x - 2) * x**2, (x, 0, 3)) == Rational(11, 3)
+
+ t, s = symbols('t s', real=True)
+ assert integrate(Abs(t), t) == Piecewise(
+ (-t**2/2, t <= 0), (t**2/2, True))
+ assert integrate(Abs(2*t - 6), t) == Piecewise(
+ (-t**2 + 6*t, t <= 3), (t**2 - 6*t + 18, True))
+ assert (integrate(abs(t - s**2), (t, 0, 2)) ==
+ 2*s**2*Min(2, s**2) - 2*s**2 - Min(2, s**2)**2 + 2)
+ assert integrate(exp(-Abs(t)), t) == Piecewise(
+ (exp(t), t <= 0), (2 - exp(-t), True))
+ assert integrate(sign(2*t - 6), t) == Piecewise(
+ (-t, t < 3), (t - 6, True))
+ assert integrate(2*t*sign(t**2 - 1), t) == Piecewise(
+ (t**2, t < -1), (-t**2 + 2, t < 1), (t**2, True))
+ assert integrate(sign(t), (t, s + 1)) == Piecewise(
+ (s + 1, s + 1 > 0), (-s - 1, s + 1 < 0), (0, True))
+
+
+def test_subs1():
+ e = Integral(exp(x - y), x)
+ assert e.subs(y, 3) == Integral(exp(x - 3), x)
+ e = Integral(exp(x - y), (x, 0, 1))
+ assert e.subs(y, 3) == Integral(exp(x - 3), (x, 0, 1))
+ f = Lambda(x, exp(-x**2))
+ conv = Integral(f(x - y)*f(y), (y, -oo, oo))
+ assert conv.subs({x: 0}) == Integral(exp(-2*y**2), (y, -oo, oo))
+
+
+def test_subs2():
+ e = Integral(exp(x - y), x, t)
+ assert e.subs(y, 3) == Integral(exp(x - 3), x, t)
+ e = Integral(exp(x - y), (x, 0, 1), (t, 0, 1))
+ assert e.subs(y, 3) == Integral(exp(x - 3), (x, 0, 1), (t, 0, 1))
+ f = Lambda(x, exp(-x**2))
+ conv = Integral(f(x - y)*f(y), (y, -oo, oo), (t, 0, 1))
+ assert conv.subs({x: 0}) == Integral(exp(-2*y**2), (y, -oo, oo), (t, 0, 1))
+
+
+def test_subs3():
+ e = Integral(exp(x - y), (x, 0, y), (t, y, 1))
+ assert e.subs(y, 3) == Integral(exp(x - 3), (x, 0, 3), (t, 3, 1))
+ f = Lambda(x, exp(-x**2))
+ conv = Integral(f(x - y)*f(y), (y, -oo, oo), (t, x, 1))
+ assert conv.subs({x: 0}) == Integral(exp(-2*y**2), (y, -oo, oo), (t, 0, 1))
+
+
+def test_subs4():
+ e = Integral(exp(x), (x, 0, y), (t, y, 1))
+ assert e.subs(y, 3) == Integral(exp(x), (x, 0, 3), (t, 3, 1))
+ f = Lambda(x, exp(-x**2))
+ conv = Integral(f(y)*f(y), (y, -oo, oo), (t, x, 1))
+ assert conv.subs({x: 0}) == Integral(exp(-2*y**2), (y, -oo, oo), (t, 0, 1))
+
+
+def test_subs5():
+ e = Integral(exp(-x**2), (x, -oo, oo))
+ assert e.subs(x, 5) == e
+ e = Integral(exp(-x**2 + y), x)
+ assert e.subs(y, 5) == Integral(exp(-x**2 + 5), x)
+ e = Integral(exp(-x**2 + y), (x, x))
+ assert e.subs(x, 5) == Integral(exp(y - x**2), (x, 5))
+ assert e.subs(y, 5) == Integral(exp(-x**2 + 5), x)
+ e = Integral(exp(-x**2 + y), (y, -oo, oo), (x, -oo, oo))
+ assert e.subs(x, 5) == e
+ assert e.subs(y, 5) == e
+ # Test evaluation of antiderivatives
+ e = Integral(exp(-x**2), (x, x))
+ assert e.subs(x, 5) == Integral(exp(-x**2), (x, 5))
+ e = Integral(exp(x), x)
+ assert (e.subs(x,1) - e.subs(x,0) - Integral(exp(x), (x, 0, 1))
+ ).doit().is_zero
+
+
+def test_subs6():
+ a, b = symbols('a b')
+ e = Integral(x*y, (x, f(x), f(y)))
+ assert e.subs(x, 1) == Integral(x*y, (x, f(1), f(y)))
+ assert e.subs(y, 1) == Integral(x, (x, f(x), f(1)))
+ e = Integral(x*y, (x, f(x), f(y)), (y, f(x), f(y)))
+ assert e.subs(x, 1) == Integral(x*y, (x, f(1), f(y)), (y, f(1), f(y)))
+ assert e.subs(y, 1) == Integral(x*y, (x, f(x), f(y)), (y, f(x), f(1)))
+ e = Integral(x*y, (x, f(x), f(a)), (y, f(x), f(a)))
+ assert e.subs(a, 1) == Integral(x*y, (x, f(x), f(1)), (y, f(x), f(1)))
+
+
+def test_subs7():
+ e = Integral(x, (x, 1, y), (y, 1, 2))
+ assert e.subs({x: 1, y: 2}) == e
+ e = Integral(sin(x) + sin(y), (x, sin(x), sin(y)),
+ (y, 1, 2))
+ assert e.subs(sin(y), 1) == e
+ assert e.subs(sin(x), 1) == Integral(sin(x) + sin(y), (x, 1, sin(y)),
+ (y, 1, 2))
+
+def test_expand():
+ e = Integral(f(x)+f(x**2), (x, 1, y))
+ assert e.expand() == Integral(f(x), (x, 1, y)) + Integral(f(x**2), (x, 1, y))
+ e = Integral(f(x)+f(x**2), (x, 1, oo))
+ assert e.expand() == e
+ assert e.expand(force=True) == Integral(f(x), (x, 1, oo)) + \
+ Integral(f(x**2), (x, 1, oo))
+
+
+def test_integration_variable():
+ raises(ValueError, lambda: Integral(exp(-x**2), 3))
+ raises(ValueError, lambda: Integral(exp(-x**2), (3, -oo, oo)))
+
+
+def test_expand_integral():
+ assert Integral(cos(x**2)*(sin(x**2) + 1), (x, 0, 1)).expand() == \
+ Integral(cos(x**2)*sin(x**2), (x, 0, 1)) + \
+ Integral(cos(x**2), (x, 0, 1))
+ assert Integral(cos(x**2)*(sin(x**2) + 1), x).expand() == \
+ Integral(cos(x**2)*sin(x**2), x) + \
+ Integral(cos(x**2), x)
+
+
+def test_as_sum_midpoint1():
+ e = Integral(sqrt(x**3 + 1), (x, 2, 10))
+ assert e.as_sum(1, method="midpoint") == 8*sqrt(217)
+ assert e.as_sum(2, method="midpoint") == 4*sqrt(65) + 12*sqrt(57)
+ assert e.as_sum(3, method="midpoint") == 8*sqrt(217)/3 + \
+ 8*sqrt(3081)/27 + 8*sqrt(52809)/27
+ assert e.as_sum(4, method="midpoint") == 2*sqrt(730) + \
+ 4*sqrt(7) + 4*sqrt(86) + 6*sqrt(14)
+ assert abs(e.as_sum(4, method="midpoint").n() - e.n()) < 0.5
+
+ e = Integral(sqrt(x**3 + y**3), (x, 2, 10), (y, 0, 10))
+ raises(NotImplementedError, lambda: e.as_sum(4))
+
+
+def test_as_sum_midpoint2():
+ e = Integral((x + y)**2, (x, 0, 1))
+ n = Symbol('n', positive=True, integer=True)
+ assert e.as_sum(1, method="midpoint").expand() == Rational(1, 4) + y + y**2
+ assert e.as_sum(2, method="midpoint").expand() == Rational(5, 16) + y + y**2
+ assert e.as_sum(3, method="midpoint").expand() == Rational(35, 108) + y + y**2
+ assert e.as_sum(4, method="midpoint").expand() == Rational(21, 64) + y + y**2
+ assert e.as_sum(n, method="midpoint").expand() == \
+ y**2 + y + Rational(1, 3) - 1/(12*n**2)
+
+
+def test_as_sum_left():
+ e = Integral((x + y)**2, (x, 0, 1))
+ assert e.as_sum(1, method="left").expand() == y**2
+ assert e.as_sum(2, method="left").expand() == Rational(1, 8) + y/2 + y**2
+ assert e.as_sum(3, method="left").expand() == Rational(5, 27) + y*Rational(2, 3) + y**2
+ assert e.as_sum(4, method="left").expand() == Rational(7, 32) + y*Rational(3, 4) + y**2
+ assert e.as_sum(n, method="left").expand() == \
+ y**2 + y + Rational(1, 3) - y/n - 1/(2*n) + 1/(6*n**2)
+ assert e.as_sum(10, method="left", evaluate=False).has(Sum)
+
+
+def test_as_sum_right():
+ e = Integral((x + y)**2, (x, 0, 1))
+ assert e.as_sum(1, method="right").expand() == 1 + 2*y + y**2
+ assert e.as_sum(2, method="right").expand() == Rational(5, 8) + y*Rational(3, 2) + y**2
+ assert e.as_sum(3, method="right").expand() == Rational(14, 27) + y*Rational(4, 3) + y**2
+ assert e.as_sum(4, method="right").expand() == Rational(15, 32) + y*Rational(5, 4) + y**2
+ assert e.as_sum(n, method="right").expand() == \
+ y**2 + y + Rational(1, 3) + y/n + 1/(2*n) + 1/(6*n**2)
+
+
+def test_as_sum_trapezoid():
+ e = Integral((x + y)**2, (x, 0, 1))
+ assert e.as_sum(1, method="trapezoid").expand() == y**2 + y + S.Half
+ assert e.as_sum(2, method="trapezoid").expand() == y**2 + y + Rational(3, 8)
+ assert e.as_sum(3, method="trapezoid").expand() == y**2 + y + Rational(19, 54)
+ assert e.as_sum(4, method="trapezoid").expand() == y**2 + y + Rational(11, 32)
+ assert e.as_sum(n, method="trapezoid").expand() == \
+ y**2 + y + Rational(1, 3) + 1/(6*n**2)
+ assert Integral(sign(x), (x, 0, 1)).as_sum(1, 'trapezoid') == S.Half
+
+
+def test_as_sum_raises():
+ e = Integral((x + y)**2, (x, 0, 1))
+ raises(ValueError, lambda: e.as_sum(-1))
+ raises(ValueError, lambda: e.as_sum(0))
+ raises(ValueError, lambda: Integral(x).as_sum(3))
+ raises(ValueError, lambda: e.as_sum(oo))
+ raises(ValueError, lambda: e.as_sum(3, method='xxxx2'))
+
+
+def test_nested_doit():
+ e = Integral(Integral(x, x), x)
+ f = Integral(x, x, x)
+ assert e.doit() == f.doit()
+
+
+def test_issue_4665():
+ # Allow only upper or lower limit evaluation
+ e = Integral(x**2, (x, None, 1))
+ f = Integral(x**2, (x, 1, None))
+ assert e.doit() == Rational(1, 3)
+ assert f.doit() == Rational(-1, 3)
+ assert Integral(x*y, (x, None, y)).subs(y, t) == Integral(x*t, (x, None, t))
+ assert Integral(x*y, (x, y, None)).subs(y, t) == Integral(x*t, (x, t, None))
+ assert integrate(x**2, (x, None, 1)) == Rational(1, 3)
+ assert integrate(x**2, (x, 1, None)) == Rational(-1, 3)
+ assert integrate("x**2", ("x", "1", None)) == Rational(-1, 3)
+
+
+def test_integral_reconstruct():
+ e = Integral(x**2, (x, -1, 1))
+ assert e == Integral(*e.args)
+
+
+def test_doit_integrals():
+ e = Integral(Integral(2*x), (x, 0, 1))
+ assert e.doit() == Rational(1, 3)
+ assert e.doit(deep=False) == Rational(1, 3)
+ f = Function('f')
+ # doesn't matter if the integral can't be performed
+ assert Integral(f(x), (x, 1, 1)).doit() == 0
+ # doesn't matter if the limits can't be evaluated
+ assert Integral(0, (x, 1, Integral(f(x), x))).doit() == 0
+ assert Integral(x, (a, 0)).doit() == 0
+ limits = ((a, 1, exp(x)), (x, 0))
+ assert Integral(a, *limits).doit() == Rational(1, 4)
+ assert Integral(a, *list(reversed(limits))).doit() == 0
+
+
+def test_issue_4884():
+ assert integrate(sqrt(x)*(1 + x)) == \
+ Piecewise(
+ (2*sqrt(x)*(x + 1)**2/5 - 2*sqrt(x)*(x + 1)/15 - 4*sqrt(x)/15,
+ Abs(x + 1) > 1),
+ (2*I*sqrt(-x)*(x + 1)**2/5 - 2*I*sqrt(-x)*(x + 1)/15 -
+ 4*I*sqrt(-x)/15, True))
+ assert integrate(x**x*(1 + log(x))) == x**x
+
+def test_issue_18153():
+ assert integrate(x**n*log(x),x) == \
+ Piecewise(
+ (n*x*x**n*log(x)/(n**2 + 2*n + 1) +
+ x*x**n*log(x)/(n**2 + 2*n + 1) - x*x**n/(n**2 + 2*n + 1)
+ , Ne(n, -1)), (log(x)**2/2, True)
+ )
+
+
+def test_is_number():
+ from sympy.abc import x, y, z
+ assert Integral(x).is_number is False
+ assert Integral(1, x).is_number is False
+ assert Integral(1, (x, 1)).is_number is True
+ assert Integral(1, (x, 1, 2)).is_number is True
+ assert Integral(1, (x, 1, y)).is_number is False
+ assert Integral(1, (x, y)).is_number is False
+ assert Integral(x, y).is_number is False
+ assert Integral(x, (y, 1, x)).is_number is False
+ assert Integral(x, (y, 1, 2)).is_number is False
+ assert Integral(x, (x, 1, 2)).is_number is True
+ # `foo.is_number` should always be equivalent to `not foo.free_symbols`
+ # in each of these cases, there are pseudo-free symbols
+ i = Integral(x, (y, 1, 1))
+ assert i.is_number is False and i.n() == 0
+ i = Integral(x, (y, z, z))
+ assert i.is_number is False and i.n() == 0
+ i = Integral(1, (y, z, z + 2))
+ assert i.is_number is False and i.n() == 2.0
+
+ assert Integral(x*y, (x, 1, 2), (y, 1, 3)).is_number is True
+ assert Integral(x*y, (x, 1, 2), (y, 1, z)).is_number is False
+ assert Integral(x, (x, 1)).is_number is True
+ assert Integral(x, (x, 1, Integral(y, (y, 1, 2)))).is_number is True
+ assert Integral(Sum(z, (z, 1, 2)), (x, 1, 2)).is_number is True
+ # it is possible to get a false negative if the integrand is
+ # actually an unsimplified zero, but this is true of is_number in general.
+ assert Integral(sin(x)**2 + cos(x)**2 - 1, x).is_number is False
+ assert Integral(f(x), (x, 0, 1)).is_number is True
+
+
+def test_free_symbols():
+ from sympy.abc import x, y, z
+ assert Integral(0, x).free_symbols == {x}
+ assert Integral(x).free_symbols == {x}
+ assert Integral(x, (x, None, y)).free_symbols == {y}
+ assert Integral(x, (x, y, None)).free_symbols == {y}
+ assert Integral(x, (x, 1, y)).free_symbols == {y}
+ assert Integral(x, (x, y, 1)).free_symbols == {y}
+ assert Integral(x, (x, x, y)).free_symbols == {x, y}
+ assert Integral(x, x, y).free_symbols == {x, y}
+ assert Integral(x, (x, 1, 2)).free_symbols == set()
+ assert Integral(x, (y, 1, 2)).free_symbols == {x}
+ # pseudo-free in this case
+ assert Integral(x, (y, z, z)).free_symbols == {x, z}
+ assert Integral(x, (y, 1, 2), (y, None, None)
+ ).free_symbols == {x, y}
+ assert Integral(x, (y, 1, 2), (x, 1, y)
+ ).free_symbols == {y}
+ assert Integral(2, (y, 1, 2), (y, 1, x), (x, 1, 2)
+ ).free_symbols == set()
+ assert Integral(2, (y, x, 2), (y, 1, x), (x, 1, 2)
+ ).free_symbols == set()
+ assert Integral(2, (x, 1, 2), (y, x, 2), (y, 1, 2)
+ ).free_symbols == {x}
+ assert Integral(f(x), (f(x), 1, y)).free_symbols == {y}
+ assert Integral(f(x), (f(x), 1, x)).free_symbols == {x}
+
+
+def test_is_zero():
+ from sympy.abc import x, m
+ assert Integral(0, (x, 1, x)).is_zero
+ assert Integral(1, (x, 1, 1)).is_zero
+ assert Integral(1, (x, 1, 2), (y, 2)).is_zero is False
+ assert Integral(x, (m, 0)).is_zero
+ assert Integral(x + m, (m, 0)).is_zero is None
+ i = Integral(m, (m, 1, exp(x)), (x, 0))
+ assert i.is_zero is None
+ assert Integral(m, (x, 0), (m, 1, exp(x))).is_zero is True
+
+ assert Integral(x, (x, oo, oo)).is_zero # issue 8171
+ assert Integral(x, (x, -oo, -oo)).is_zero
+
+ # this is zero but is beyond the scope of what is_zero
+ # should be doing
+ assert Integral(sin(x), (x, 0, 2*pi)).is_zero is None
+
+
+def test_series():
+ from sympy.abc import x
+ i = Integral(cos(x), (x, x))
+ e = i.lseries(x)
+ assert i.nseries(x, n=8).removeO() == Add(*[next(e) for j in range(4)])
+
+
+def test_trig_nonelementary_integrals():
+ x = Symbol('x')
+ assert integrate((1 + sin(x))/x, x) == log(x) + Si(x)
+ # next one comes out as log(x) + log(x**2)/2 + Ci(x)
+ # so not hardcoding this log ugliness
+ assert integrate((cos(x) + 2)/x, x).has(Ci)
+
+
+def test_issue_4403():
+ x = Symbol('x')
+ y = Symbol('y')
+ z = Symbol('z', positive=True)
+ assert integrate(sqrt(x**2 + z**2), x) == \
+ z**2*asinh(x/z)/2 + x*sqrt(x**2 + z**2)/2
+ assert integrate(sqrt(x**2 - z**2), x) == \
+ x*sqrt(x**2 - z**2)/2 - z**2*log(x + sqrt(x**2 - z**2))/2
+
+ x = Symbol('x', real=True)
+ y = Symbol('y', positive=True)
+ assert integrate(1/(x**2 + y**2)**S('3/2'), x) == \
+ x/(y**2*sqrt(x**2 + y**2))
+ # If y is real and nonzero, we get x*Abs(y)/(y**3*sqrt(x**2 + y**2)),
+ # which results from sqrt(1 + x**2/y**2) = sqrt(x**2 + y**2)/|y|.
+
+
+def test_issue_4403_2():
+ assert integrate(sqrt(-x**2 - 4), x) == \
+ -2*atan(x/sqrt(-4 - x**2)) + x*sqrt(-4 - x**2)/2
+
+
+def test_issue_4100():
+ R = Symbol('R', positive=True)
+ assert integrate(sqrt(R**2 - x**2), (x, 0, R)) == pi*R**2/4
+
+
+def test_issue_5167():
+ from sympy.abc import w, x, y, z
+ f = Function('f')
+ assert Integral(Integral(f(x), x), x) == Integral(f(x), x, x)
+ assert Integral(f(x)).args == (f(x), Tuple(x))
+ assert Integral(Integral(f(x))).args == (f(x), Tuple(x), Tuple(x))
+ assert Integral(Integral(f(x)), y).args == (f(x), Tuple(x), Tuple(y))
+ assert Integral(Integral(f(x), z), y).args == (f(x), Tuple(z), Tuple(y))
+ assert Integral(Integral(Integral(f(x), x), y), z).args == \
+ (f(x), Tuple(x), Tuple(y), Tuple(z))
+ assert integrate(Integral(f(x), x), x) == Integral(f(x), x, x)
+ assert integrate(Integral(f(x), y), x) == y*Integral(f(x), x)
+ assert integrate(Integral(f(x), x), y) in [Integral(y*f(x), x), y*Integral(f(x), x)]
+ assert integrate(Integral(2, x), x) == x**2
+ assert integrate(Integral(2, x), y) == 2*x*y
+ # don't re-order given limits
+ assert Integral(1, x, y).args != Integral(1, y, x).args
+ # do as many as possible
+ assert Integral(f(x), y, x, y, x).doit() == y**2*Integral(f(x), x, x)/2
+ assert Integral(f(x), (x, 1, 2), (w, 1, x), (z, 1, y)).doit() == \
+ y*(x - 1)*Integral(f(x), (x, 1, 2)) - (x - 1)*Integral(f(x), (x, 1, 2))
+
+
+def test_issue_4890():
+ z = Symbol('z', positive=True)
+ assert integrate(exp(-log(x)**2), x) == \
+ sqrt(pi)*exp(Rational(1, 4))*erf(log(x) - S.Half)/2
+ assert integrate(exp(log(x)**2), x) == \
+ sqrt(pi)*exp(Rational(-1, 4))*erfi(log(x)+S.Half)/2
+ assert integrate(exp(-z*log(x)**2), x) == \
+ sqrt(pi)*exp(1/(4*z))*erf(sqrt(z)*log(x) - 1/(2*sqrt(z)))/(2*sqrt(z))
+
+
+def test_issue_4551():
+ assert not integrate(1/(x*sqrt(1 - x**2)), x).has(Integral)
+
+
+def test_issue_4376():
+ n = Symbol('n', integer=True, positive=True)
+ assert simplify(integrate(n*(x**(1/n) - 1), (x, 0, S.Half)) -
+ (n**2 - 2**(1/n)*n**2 - n*2**(1/n))/(2**(1 + 1/n) + n*2**(1 + 1/n))) == 0
+
+
+def test_issue_4517():
+ assert integrate((sqrt(x) - x**3)/x**Rational(1, 3), x) == \
+ 6*x**Rational(7, 6)/7 - 3*x**Rational(11, 3)/11
+
+
+def test_issue_4527():
+ k, m = symbols('k m', integer=True)
+ assert integrate(sin(k*x)*sin(m*x), (x, 0, pi)).simplify() == \
+ Piecewise((0, Eq(k, 0) | Eq(m, 0)),
+ (-pi/2, Eq(k, -m) | (Eq(k, 0) & Eq(m, 0))),
+ (pi/2, Eq(k, m) | (Eq(k, 0) & Eq(m, 0))),
+ (0, True))
+ # Should be possible to further simplify to:
+ # Piecewise(
+ # (0, Eq(k, 0) | Eq(m, 0)),
+ # (-pi/2, Eq(k, -m)),
+ # (pi/2, Eq(k, m)),
+ # (0, True))
+ assert integrate(sin(k*x)*sin(m*x), (x,)) == Piecewise(
+ (0, And(Eq(k, 0), Eq(m, 0))),
+ (-x*sin(m*x)**2/2 - x*cos(m*x)**2/2 + sin(m*x)*cos(m*x)/(2*m), Eq(k, -m)),
+ (x*sin(m*x)**2/2 + x*cos(m*x)**2/2 - sin(m*x)*cos(m*x)/(2*m), Eq(k, m)),
+ (m*sin(k*x)*cos(m*x)/(k**2 - m**2) -
+ k*sin(m*x)*cos(k*x)/(k**2 - m**2), True))
+
+
+def test_issue_4199():
+ ypos = Symbol('y', positive=True)
+ # TODO: Remove conds='none' below, let the assumption take care of it.
+ assert integrate(exp(-I*2*pi*ypos*x)*x, (x, -oo, oo), conds='none') == \
+ Integral(exp(-I*2*pi*ypos*x)*x, (x, -oo, oo))
+
+
+def test_issue_3940():
+ a, b, c, d = symbols('a:d', positive=True)
+ assert integrate(exp(-x**2 + I*c*x), x) == \
+ -sqrt(pi)*exp(-c**2/4)*erf(I*c/2 - x)/2
+ assert integrate(exp(a*x**2 + b*x + c), x) == \
+ sqrt(pi)*exp(c - b**2/(4*a))*erfi((2*a*x + b)/(2*sqrt(a)))/(2*sqrt(a))
+
+ from sympy.core.function import expand_mul
+ from sympy.abc import k
+ assert expand_mul(integrate(exp(-x**2)*exp(I*k*x), (x, -oo, oo))) == \
+ sqrt(pi)*exp(-k**2/4)
+ a, d = symbols('a d', positive=True)
+ assert expand_mul(integrate(exp(-a*x**2 + 2*d*x), (x, -oo, oo))) == \
+ sqrt(pi)*exp(d**2/a)/sqrt(a)
+
+
+def test_issue_5413():
+ # Note that this is not the same as testing ratint() because integrate()
+ # pulls out the coefficient.
+ assert integrate(-a/(a**2 + x**2), x) == I*log(-I*a + x)/2 - I*log(I*a + x)/2
+
+
+def test_issue_4892a():
+ A, z = symbols('A z')
+ c = Symbol('c', nonzero=True)
+ P1 = -A*exp(-z)
+ P2 = -A/(c*t)*(sin(x)**2 + cos(y)**2)
+
+ h1 = -sin(x)**2 - cos(y)**2
+ h2 = -sin(x)**2 + sin(y)**2 - 1
+
+ # there is still some non-deterministic behavior in integrate
+ # or trigsimp which permits one of the following
+ assert integrate(c*(P2 - P1), t) in [
+ c*(-A*(-h1)*log(c*t)/c + A*t*exp(-z)),
+ c*(-A*(-h2)*log(c*t)/c + A*t*exp(-z)),
+ c*( A* h1 *log(c*t)/c + A*t*exp(-z)),
+ c*( A* h2 *log(c*t)/c + A*t*exp(-z)),
+ (A*c*t - A*(-h1)*log(t)*exp(z))*exp(-z),
+ (A*c*t - A*(-h2)*log(t)*exp(z))*exp(-z),
+ ]
+
+
+def test_issue_4892b():
+ # Issues relating to issue 4596 are making the actual result of this hard
+ # to test. The answer should be something like
+ #
+ # (-sin(y) + sqrt(-72 + 48*cos(y) - 8*cos(y)**2)/2)*log(x + sqrt(-72 +
+ # 48*cos(y) - 8*cos(y)**2)/(2*(3 - cos(y)))) + (-sin(y) - sqrt(-72 +
+ # 48*cos(y) - 8*cos(y)**2)/2)*log(x - sqrt(-72 + 48*cos(y) -
+ # 8*cos(y)**2)/(2*(3 - cos(y)))) + x**2*sin(y)/2 + 2*x*cos(y)
+
+ expr = (sin(y)*x**3 + 2*cos(y)*x**2 + 12)/(x**2 + 2)
+ assert trigsimp(factor(integrate(expr, x).diff(x) - expr)) == 0
+
+
+def test_issue_5178():
+ assert integrate(sin(x)*f(y, z), (x, 0, pi), (y, 0, pi), (z, 0, pi)) == \
+ 2*Integral(f(y, z), (y, 0, pi), (z, 0, pi))
+
+
+def test_integrate_series():
+ f = sin(x).series(x, 0, 10)
+ g = x**2/2 - x**4/24 + x**6/720 - x**8/40320 + x**10/3628800 + O(x**11)
+
+ assert integrate(f, x) == g
+ assert diff(integrate(f, x), x) == f
+
+ assert integrate(O(x**5), x) == O(x**6)
+
+
+def test_atom_bug():
+ from sympy.integrals.heurisch import heurisch
+ assert heurisch(meijerg([], [], [1], [], x), x) is None
+
+
+def test_limit_bug():
+ z = Symbol('z', zero=False)
+ assert integrate(sin(x*y*z), (x, 0, pi), (y, 0, pi)).together() == \
+ (log(z) - Ci(pi**2*z) + EulerGamma + 2*log(pi))/z
+
+
+def test_issue_4703():
+ g = Function('g')
+ assert integrate(exp(x)*g(x), x).has(Integral)
+
+
+def test_issue_1888():
+ f = Function('f')
+ assert integrate(f(x).diff(x)**2, x).has(Integral)
+
+# The following tests work using meijerint.
+
+
+def test_issue_3558():
+ assert integrate(cos(x*y), (x, -pi/2, pi/2), (y, 0, pi)) == 2*Si(pi**2/2)
+
+
+def test_issue_4422():
+ assert integrate(1/sqrt(16 + 4*x**2), x) == asinh(x/2) / 2
+
+
+def test_issue_4493():
+ assert simplify(integrate(x*sqrt(1 + 2*x), x)) == \
+ sqrt(2*x + 1)*(6*x**2 + x - 1)/15
+
+
+def test_issue_4737():
+ assert integrate(sin(x)/x, (x, -oo, oo)) == pi
+ assert integrate(sin(x)/x, (x, 0, oo)) == pi/2
+ assert integrate(sin(x)/x, x) == Si(x)
+
+
+def test_issue_4992():
+ # Note: psi in _check_antecedents becomes NaN.
+ from sympy.core.function import expand_func
+ a = Symbol('a', positive=True)
+ assert simplify(expand_func(integrate(exp(-x)*log(x)*x**a, (x, 0, oo)))) == \
+ (a*polygamma(0, a) + 1)*gamma(a)
+
+
+def test_issue_4487():
+ from sympy.functions.special.gamma_functions import lowergamma
+ assert simplify(integrate(exp(-x)*x**y, x)) == lowergamma(y + 1, x)
+
+
+def test_issue_4215():
+ x = Symbol("x")
+ assert integrate(1/(x**2), (x, -1, 1)) is oo
+
+
+def test_issue_4400():
+ n = Symbol('n', integer=True, positive=True)
+ assert integrate((x**n)*log(x), x) == \
+ n*x*x**n*log(x)/(n**2 + 2*n + 1) + x*x**n*log(x)/(n**2 + 2*n + 1) - \
+ x*x**n/(n**2 + 2*n + 1)
+
+
+def test_issue_6253():
+ # Note: this used to raise NotImplementedError
+ # Note: psi in _check_antecedents becomes NaN.
+ assert integrate((sqrt(1 - x) + sqrt(1 + x))**2/x, x, meijerg=True) == \
+ Integral((sqrt(-x + 1) + sqrt(x + 1))**2/x, x)
+
+
+def test_issue_4153():
+ assert integrate(1/(1 + x + y + z), (x, 0, 1), (y, 0, 1), (z, 0, 1)) in [
+ -12*log(3) - 3*log(6)/2 + 3*log(8)/2 + 5*log(2) + 7*log(4),
+ 6*log(2) + 8*log(4) - 27*log(3)/2, 22*log(2) - 27*log(3)/2,
+ -12*log(3) - 3*log(6)/2 + 47*log(2)/2]
+
+
+def test_issue_4326():
+ R, b, h = symbols('R b h')
+ # It doesn't matter if we can do the integral. Just make sure the result
+ # doesn't contain nan. This is really a test against _eval_interval.
+ e = integrate(((h*(x - R + b))/b)*sqrt(R**2 - x**2), (x, R - b, R))
+ assert not e.has(nan)
+ # See that it evaluates
+ assert not e.has(Integral)
+
+
+def test_powers():
+ assert integrate(2**x + 3**x, x) == 2**x/log(2) + 3**x/log(3)
+
+
+def test_manual_option():
+ raises(ValueError, lambda: integrate(1/x, x, manual=True, meijerg=True))
+ # an example of a function that manual integration cannot handle
+ assert integrate(log(1+x)/x, (x, 0, 1), manual=True).has(Integral)
+
+
+def test_meijerg_option():
+ raises(ValueError, lambda: integrate(1/x, x, meijerg=True, risch=True))
+ # an example of a function that meijerg integration cannot handle
+ assert integrate(tan(x), x, meijerg=True) == Integral(tan(x), x)
+
+
+def test_risch_option():
+ # risch=True only allowed on indefinite integrals
+ raises(ValueError, lambda: integrate(1/log(x), (x, 0, oo), risch=True))
+ assert integrate(exp(-x**2), x, risch=True) == NonElementaryIntegral(exp(-x**2), x)
+ assert integrate(log(1/x)*y, x, y, risch=True) == y**2*(x*log(1/x)/2 + x/2)
+ assert integrate(erf(x), x, risch=True) == Integral(erf(x), x)
+ # TODO: How to test risch=False?
+
+
+@slow
+def test_heurisch_option():
+ raises(ValueError, lambda: integrate(1/x, x, risch=True, heurisch=True))
+ # an integral that heurisch can handle
+ assert integrate(exp(x**2), x, heurisch=True) == sqrt(pi)*erfi(x)/2
+ # an integral that heurisch currently cannot handle
+ assert integrate(exp(x)/x, x, heurisch=True) == Integral(exp(x)/x, x)
+ # an integral where heurisch currently hangs, issue 15471
+ assert integrate(log(x)*cos(log(x))/x**Rational(3, 4), x, heurisch=False) == (
+ -128*x**Rational(1, 4)*sin(log(x))/289 + 240*x**Rational(1, 4)*cos(log(x))/289 +
+ (16*x**Rational(1, 4)*sin(log(x))/17 + 4*x**Rational(1, 4)*cos(log(x))/17)*log(x))
+
+
+def test_issue_6828():
+ f = 1/(1.08*x**2 - 4.3)
+ g = integrate(f, x).diff(x)
+ assert verify_numerically(f, g, tol=1e-12)
+
+
+def test_issue_4803():
+ x_max = Symbol("x_max")
+ assert integrate(y/pi*exp(-(x_max - x)/cos(a)), x) == \
+ y*exp((x - x_max)/cos(a))*cos(a)/pi
+
+
+def test_issue_4234():
+ assert integrate(1/sqrt(1 + tan(x)**2)) == tan(x)/sqrt(1 + tan(x)**2)
+
+
+def test_issue_4492():
+ assert simplify(integrate(x**2 * sqrt(5 - x**2), x)).factor(
+ deep=True) == Piecewise(
+ (I*(2*x**5 - 15*x**3 + 25*x - 25*sqrt(x**2 - 5)*acosh(sqrt(5)*x/5)) /
+ (8*sqrt(x**2 - 5)), (x > sqrt(5)) | (x < -sqrt(5))),
+ ((2*x**5 - 15*x**3 + 25*x - 25*sqrt(5 - x**2)*asin(sqrt(5)*x/5)) /
+ (-8*sqrt(-x**2 + 5)), True))
+
+
+def test_issue_2708():
+ # This test needs to use an integration function that can
+ # not be evaluated in closed form. Update as needed.
+ f = 1/(a + z + log(z))
+ integral_f = NonElementaryIntegral(f, (z, 2, 3))
+ assert Integral(f, (z, 2, 3)).doit() == integral_f
+ assert integrate(f + exp(z), (z, 2, 3)) == integral_f - exp(2) + exp(3)
+ assert integrate(2*f + exp(z), (z, 2, 3)) == \
+ 2*integral_f - exp(2) + exp(3)
+ assert integrate(exp(1.2*n*s*z*(-t + z)/t), (z, 0, x)) == \
+ NonElementaryIntegral(exp(-1.2*n*s*z)*exp(1.2*n*s*z**2/t),
+ (z, 0, x))
+
+
+def test_issue_2884():
+ f = (4.000002016020*x + 4.000002016020*y + 4.000006024032)*exp(10.0*x)
+ e = integrate(f, (x, 0.1, 0.2))
+ assert str(e) == '1.86831064982608*y + 2.16387491480008'
+
+
+def test_issue_8368i():
+ from sympy.functions.elementary.complexes import arg, Abs
+ assert integrate(exp(-s*x)*cosh(x), (x, 0, oo)) == \
+ Piecewise(
+ ( pi*Piecewise(
+ ( -s/(pi*(-s**2 + 1)),
+ Abs(s**2) < 1),
+ ( 1/(pi*s*(1 - 1/s**2)),
+ Abs(s**(-2)) < 1),
+ ( meijerg(
+ ((S.Half,), (0, 0)),
+ ((0, S.Half), (0,)),
+ polar_lift(s)**2),
+ True)
+ ),
+ s**2 > 1
+ ),
+ (
+ Integral(exp(-s*x)*cosh(x), (x, 0, oo)),
+ True))
+ assert integrate(exp(-s*x)*sinh(x), (x, 0, oo)) == \
+ Piecewise(
+ ( -1/(s + 1)/2 - 1/(-s + 1)/2,
+ And(
+ Abs(s) > 1,
+ Abs(arg(s)) < pi/2,
+ Abs(arg(s)) <= pi/2
+ )),
+ ( Integral(exp(-s*x)*sinh(x), (x, 0, oo)),
+ True))
+
+
+def test_issue_8901():
+ assert integrate(sinh(1.0*x)) == 1.0*cosh(1.0*x)
+ assert integrate(tanh(1.0*x)) == 1.0*x - 1.0*log(tanh(1.0*x) + 1)
+ assert integrate(tanh(x)) == x - log(tanh(x) + 1)
+
+
+@slow
+def test_issue_8945():
+ assert integrate(sin(x)**3/x, (x, 0, 1)) == -Si(3)/4 + 3*Si(1)/4
+ assert integrate(sin(x)**3/x, (x, 0, oo)) == pi/4
+ assert integrate(cos(x)**2/x**2, x) == -Si(2*x) - cos(2*x)/(2*x) - 1/(2*x)
+
+
+@slow
+def test_issue_7130():
+ i, L, a, b = symbols('i L a b')
+ integrand = (cos(pi*i*x/L)**2 / (a + b*x)).rewrite(exp)
+ assert x not in integrate(integrand, (x, 0, L)).free_symbols
+
+
+def test_issue_10567():
+ a, b, c, t = symbols('a b c t')
+ vt = Matrix([a*t, b, c])
+ assert integrate(vt, t) == Integral(vt, t).doit()
+ assert integrate(vt, t) == Matrix([[a*t**2/2], [b*t], [c*t]])
+
+
+def test_issue_11742():
+ assert integrate(sqrt(-x**2 + 8*x + 48), (x, 4, 12)) == 16*pi
+
+
+def test_issue_11856():
+ t = symbols('t')
+ assert integrate(sinc(pi*t), t) == Si(pi*t)/pi
+
+
+@slow
+def test_issue_11876():
+ assert integrate(sqrt(log(1/x)), (x, 0, 1)) == sqrt(pi)/2
+
+
+def test_issue_4950():
+ assert integrate((-60*exp(x) - 19.2*exp(4*x))*exp(4*x), x) ==\
+ -2.4*exp(8*x) - 12.0*exp(5*x)
+
+
+def test_issue_4968():
+ assert integrate(sin(log(x**2))) == x*sin(log(x**2))/5 - 2*x*cos(log(x**2))/5
+
+
+def test_singularities():
+ assert integrate(1/x**2, (x, -oo, oo)) is oo
+ assert integrate(1/x**2, (x, -1, 1)) is oo
+ assert integrate(1/(x - 1)**2, (x, -2, 2)) is oo
+
+ assert integrate(1/x**2, (x, 1, -1)) is -oo
+ assert integrate(1/(x - 1)**2, (x, 2, -2)) is -oo
+
+
+def test_issue_12645():
+ x, y = symbols('x y', real=True)
+ assert (integrate(sin(x*x*x + y*y),
+ (x, -sqrt(pi - y*y), sqrt(pi - y*y)),
+ (y, -sqrt(pi), sqrt(pi)))
+ == Integral(sin(x**3 + y**2),
+ (x, -sqrt(-y**2 + pi), sqrt(-y**2 + pi)),
+ (y, -sqrt(pi), sqrt(pi))))
+
+
+def test_issue_12677():
+ assert integrate(sin(x) / (cos(x)**3), (x, 0, pi/6)) == Rational(1, 6)
+
+
+def test_issue_14078():
+ assert integrate((cos(3*x)-cos(x))/x, (x, 0, oo)) == -log(3)
+
+
+def test_issue_14064():
+ assert integrate(1/cosh(x), (x, 0, oo)) == pi/2
+
+
+def test_issue_14027():
+ assert integrate(1/(1 + exp(x - S.Half)/(1 + exp(x))), x) == \
+ x - exp(S.Half)*log(exp(x) + exp(S.Half)/(1 + exp(S.Half)))/(exp(S.Half) + E)
+
+
+def test_issue_8170():
+ assert integrate(tan(x), (x, 0, pi/2)) is S.Infinity
+
+
+def test_issue_8440_14040():
+ assert integrate(1/x, (x, -1, 1)) is S.NaN
+ assert integrate(1/(x + 1), (x, -2, 3)) is S.NaN
+
+
+def test_issue_14096():
+ assert integrate(1/(x + y)**2, (x, 0, 1)) == -1/(y + 1) + 1/y
+ assert integrate(1/(1 + x + y + z)**2, (x, 0, 1), (y, 0, 1), (z, 0, 1)) == \
+ -4*log(4) - 6*log(2) + 9*log(3)
+
+
+def test_issue_14144():
+ assert Abs(integrate(1/sqrt(1 - x**3), (x, 0, 1)).n() - 1.402182) < 1e-6
+ assert Abs(integrate(sqrt(1 - x**3), (x, 0, 1)).n() - 0.841309) < 1e-6
+
+
+def test_issue_14375():
+ # This raised a TypeError. The antiderivative has exp_polar, which
+ # may be possible to unpolarify, so the exact output is not asserted here.
+ assert integrate(exp(I*x)*log(x), x).has(Ei)
+
+
+def test_issue_14437():
+ f = Function('f')(x, y, z)
+ assert integrate(f, (x, 0, 1), (y, 0, 2), (z, 0, 3)) == \
+ Integral(f, (x, 0, 1), (y, 0, 2), (z, 0, 3))
+
+
+def test_issue_14470():
+ assert integrate(1/sqrt(exp(x) + 1), x) == log(sqrt(exp(x) + 1) - 1) - log(sqrt(exp(x) + 1) + 1)
+
+
+def test_issue_14877():
+ f = exp(1 - exp(x**2)*x + 2*x**2)*(2*x**3 + x)/(1 - exp(x**2)*x)**2
+ assert integrate(f, x) == \
+ -exp(2*x**2 - x*exp(x**2) + 1)/(x*exp(3*x**2) - exp(2*x**2))
+
+
+def test_issue_14782():
+ f = sqrt(-x**2 + 1)*(-x**2 + x)
+ assert integrate(f, [x, -1, 1]) == - pi / 8
+
+
+@slow
+def test_issue_14782_slow():
+ f = sqrt(-x**2 + 1)*(-x**2 + x)
+ assert integrate(f, [x, 0, 1]) == S.One / 3 - pi / 16
+
+
+def test_issue_12081():
+ f = x**(Rational(-3, 2))*exp(-x)
+ assert integrate(f, [x, 0, oo]) is oo
+
+
+def test_issue_15285():
+ y = 1/x - 1
+ f = 4*y*exp(-2*y)/x**2
+ assert integrate(f, [x, 0, 1]) == 1
+
+
+def test_issue_15432():
+ assert integrate(x**n * exp(-x) * log(x), (x, 0, oo)).gammasimp() == Piecewise(
+ (gamma(n + 1)*polygamma(0, n) + gamma(n + 1)/n, re(n) + 1 > 0),
+ (Integral(x**n*exp(-x)*log(x), (x, 0, oo)), True))
+
+
+def test_issue_15124():
+ omega = IndexedBase('omega')
+ m, p = symbols('m p', cls=Idx)
+ assert integrate(exp(x*I*(omega[m] + omega[p])), x, conds='none') == \
+ -I*exp(I*x*omega[m])*exp(I*x*omega[p])/(omega[m] + omega[p])
+
+
+def test_issue_15218():
+ with warns_deprecated_sympy():
+ Integral(Eq(x, y))
+ with warns_deprecated_sympy():
+ assert Integral(Eq(x, y), x) == Eq(Integral(x, x), Integral(y, x))
+ with warns_deprecated_sympy():
+ assert Integral(Eq(x, y), x).doit() == Eq(x**2/2, x*y)
+ with warns(SymPyDeprecationWarning, test_stacklevel=False):
+ # The warning is made in the ExprWithLimits superclass. The stacklevel
+ # is correct for integrate(Eq) but not Eq.integrate
+ assert Eq(x, y).integrate(x) == Eq(x**2/2, x*y)
+
+ # These are not deprecated because they are definite integrals
+ assert integrate(Eq(x, y), (x, 0, 1)) == Eq(S.Half, y)
+ assert Eq(x, y).integrate((x, 0, 1)) == Eq(S.Half, y)
+
+
+def test_issue_15292():
+ res = integrate(exp(-x**2*cos(2*t)) * cos(x**2*sin(2*t)), (x, 0, oo))
+ assert isinstance(res, Piecewise)
+ assert gammasimp((res - sqrt(pi)/2 * cos(t)).subs(t, pi/6)) == 0
+
+
+def test_issue_4514():
+ assert integrate(sin(2*x)/sin(x), x) == 2*sin(x)
+
+
+def test_issue_15457():
+ x, a, b = symbols('x a b', real=True)
+ definite = integrate(exp(Abs(x-2)), (x, a, b))
+ indefinite = integrate(exp(Abs(x-2)), x)
+ assert definite.subs({a: 1, b: 3}) == -2 + 2*E
+ assert indefinite.subs(x, 3) - indefinite.subs(x, 1) == -2 + 2*E
+ assert definite.subs({a: -3, b: -1}) == -exp(3) + exp(5)
+ assert indefinite.subs(x, -1) - indefinite.subs(x, -3) == -exp(3) + exp(5)
+
+
+def test_issue_15431():
+ assert integrate(x*exp(x)*log(x), x) == \
+ (x*exp(x) - exp(x))*log(x) - exp(x) + Ei(x)
+
+
+def test_issue_15640_log_substitutions():
+ f = x/log(x)
+ F = Ei(2*log(x))
+ assert integrate(f, x) == F and F.diff(x) == f
+ f = x**3/log(x)**2
+ F = -x**4/log(x) + 4*Ei(4*log(x))
+ assert integrate(f, x) == F and F.diff(x) == f
+ f = sqrt(log(x))/x**2
+ F = -sqrt(pi)*erfc(sqrt(log(x)))/2 - sqrt(log(x))/x
+ assert integrate(f, x) == F and F.diff(x) == f
+
+
+def test_issue_15509():
+ from sympy.vector import CoordSys3D
+ N = CoordSys3D('N')
+ x = N.x
+ assert integrate(cos(a*x + b), (x, x_1, x_2), heurisch=True) == Piecewise(
+ (-sin(a*x_1 + b)/a + sin(a*x_2 + b)/a, (a > -oo) & (a < oo) & Ne(a, 0)), \
+ (-x_1*cos(b) + x_2*cos(b), True))
+
+
+def test_issue_4311_fast():
+ x = symbols('x', real=True)
+ assert integrate(x*abs(9-x**2), x) == Piecewise(
+ (x**4/4 - 9*x**2/2, x <= -3),
+ (-x**4/4 + 9*x**2/2 - Rational(81, 2), x <= 3),
+ (x**4/4 - 9*x**2/2, True))
+
+
+def test_integrate_with_complex_constants():
+ K = Symbol('K', positive=True)
+ x = Symbol('x', real=True)
+ m = Symbol('m', real=True)
+ t = Symbol('t', real=True)
+ assert integrate(exp(-I*K*x**2+m*x), x) == sqrt(pi)*exp(-I*m**2
+ /(4*K))*erfi((-2*I*K*x + m)/(2*sqrt(K)*sqrt(-I)))/(2*sqrt(K)*sqrt(-I))
+ assert integrate(1/(1 + I*x**2), x) == (-I*(sqrt(-I)*log(x - I*sqrt(-I))/2
+ - sqrt(-I)*log(x + I*sqrt(-I))/2))
+ assert integrate(exp(-I*x**2), x) == sqrt(pi)*erf(sqrt(I)*x)/(2*sqrt(I))
+
+ assert integrate((1/(exp(I*t)-2)), t) == -t/2 - I*log(exp(I*t) - 2)/2
+ assert integrate((1/(exp(I*t)-2)), (t, 0, 2*pi)) == -pi
+
+
+def test_issue_14241():
+ x = Symbol('x')
+ n = Symbol('n', positive=True, integer=True)
+ assert integrate(n * x ** (n - 1) / (x + 1), x) == \
+ n**2*x**n*lerchphi(x*exp_polar(I*pi), 1, n)*gamma(n)/gamma(n + 1)
+
+
+def test_issue_13112():
+ assert integrate(sin(t)**2 / (5 - 4*cos(t)), [t, 0, 2*pi]) == pi / 4
+
+
+def test_issue_14709b():
+ h = Symbol('h', positive=True)
+ i = integrate(x*acos(1 - 2*x/h), (x, 0, h))
+ assert i == 5*h**2*pi/16
+
+
+def test_issue_8614():
+ x = Symbol('x')
+ t = Symbol('t')
+ assert integrate(exp(t)/t, (t, -oo, x)) == Ei(x)
+ assert integrate((exp(-x) - exp(-2*x))/x, (x, 0, oo)) == log(2)
+
+
+@slow
+def test_issue_15494():
+ s = symbols('s', positive=True)
+
+ integrand = (exp(s/2) - 2*exp(1.6*s) + exp(s))*exp(s)
+ solution = integrate(integrand, s)
+ assert solution != S.NaN
+ # Not sure how to test this properly as it is a symbolic expression with floats
+ # assert str(solution) == '0.666666666666667*exp(1.5*s) + 0.5*exp(2.0*s) - 0.769230769230769*exp(2.6*s)'
+ # Maybe
+ assert abs(solution.subs(s, 1) - (-3.67440080236188)) <= 1e-8
+
+ integrand = (exp(s/2) - 2*exp(S(8)/5*s) + exp(s))*exp(s)
+ assert integrate(integrand, s) == -10*exp(13*s/5)/13 + 2*exp(3*s/2)/3 + exp(2*s)/2
+
+
+def test_li_integral():
+ y = Symbol('y')
+ assert Integral(li(y*x**2), x).doit() == Piecewise((x*li(x**2*y) - \
+ x*Ei(3*log(x**2*y)/2)/sqrt(x**2*y),
+ Ne(y, 0)), (0, True))
+
+
+def test_issue_17473():
+ x = Symbol('x')
+ n = Symbol('n')
+ h = S.Half
+ ans = x**(n + 1)*gamma(h + h/n)*hyper((h + h/n,),
+ (3*h, 3*h + h/n), -x**(2*n)/4)/(2*n*gamma(3*h + h/n))
+ got = integrate(sin(x**n), x)
+ assert got == ans
+ _x = Symbol('x', zero=False)
+ reps = {x: _x}
+ assert integrate(sin(_x**n), _x) == ans.xreplace(reps).expand()
+
+
+def test_issue_17671():
+ assert integrate(log(log(x)) / x**2, [x, 1, oo]) == -EulerGamma
+ assert integrate(log(log(x)) / x**3, [x, 1, oo]) == -log(2)/2 - EulerGamma/2
+ assert integrate(log(log(x)) / x**10, [x, 1, oo]) == -log(9)/9 - EulerGamma/9
+
+
+def test_issue_2975():
+ w = Symbol('w')
+ C = Symbol('C')
+ y = Symbol('y')
+ assert integrate(1/(y**2+C)**(S(3)/2), (y, -w/2, w/2)) == w/(C**(S(3)/2)*sqrt(1 + w**2/(4*C)))
+
+
+def test_issue_7827():
+ x, n, M = symbols('x n M')
+ N = Symbol('N', integer=True)
+ assert integrate(summation(x*n, (n, 1, N)), x) == x**2*(N**2/4 + N/4)
+ assert integrate(summation(x*sin(n), (n,1,N)), x) == \
+ Sum(x**2*sin(n)/2, (n, 1, N))
+ assert integrate(summation(sin(n*x), (n,1,N)), x) == \
+ Sum(Piecewise((-cos(n*x)/n, Ne(n, 0)), (0, True)), (n, 1, N))
+ assert integrate(integrate(summation(sin(n*x), (n,1,N)), x), x) == \
+ Piecewise((Sum(Piecewise((-sin(n*x)/n**2, Ne(n, 0)), (-x/n, True)),
+ (n, 1, N)), (n > -oo) & (n < oo) & Ne(n, 0)), (0, True))
+ assert integrate(Sum(x, (n, 1, M)), x) == M*x**2/2
+ raises(ValueError, lambda: integrate(Sum(x, (x, y, n)), y))
+ raises(ValueError, lambda: integrate(Sum(x, (x, 1, n)), n))
+ raises(ValueError, lambda: integrate(Sum(x, (x, 1, y)), x))
+
+
+def test_issue_4231():
+ f = (1 + 2*x + sqrt(x + log(x))*(1 + 3*x) + x**2)/(x*(x + sqrt(x + log(x)))*sqrt(x + log(x)))
+ assert integrate(f, x) == 2*sqrt(x + log(x)) + 2*log(x + sqrt(x + log(x)))
+
+
+def test_issue_17841():
+ f = diff(1/(x**2+x+I), x)
+ assert integrate(f, x) == 1/(x**2 + x + I)
+
+
+def test_issue_21034():
+ x = Symbol('x', real=True, nonzero=True)
+ f1 = x*(-x**4/asin(5)**4 - x*sinh(x + log(asin(5))) + 5)
+ f2 = (x + cosh(cos(4)))/(x*(x + 1/(12*x)))
+
+ assert integrate(f1, x) == \
+ -x**6/(6*asin(5)**4) - x**2*cosh(x + log(asin(5))) + 5*x**2/2 + 2*x*sinh(x + log(asin(5))) - 2*cosh(x + log(asin(5)))
+
+ assert integrate(f2, x) == \
+ log(x**2 + S(1)/12)/2 + 2*sqrt(3)*cosh(cos(4))*atan(2*sqrt(3)*x)
+
+
+def test_issue_4187():
+ assert integrate(log(x)*exp(-x), x) == Ei(-x) - exp(-x)*log(x)
+ assert integrate(log(x)*exp(-x), (x, 0, oo)) == -EulerGamma
+
+
+def test_issue_5547():
+ L = Symbol('L')
+ z = Symbol('z')
+ r0 = Symbol('r0')
+ R0 = Symbol('R0')
+
+ assert integrate(r0**2*cos(z)**2, (z, -L/2, L/2)) == -r0**2*(-L/4 -
+ sin(L/2)*cos(L/2)/2) + r0**2*(L/4 + sin(L/2)*cos(L/2)/2)
+
+ assert integrate(r0**2*cos(R0*z)**2, (z, -L/2, L/2)) == Piecewise(
+ (-r0**2*(-L*R0/4 - sin(L*R0/2)*cos(L*R0/2)/2)/R0 +
+ r0**2*(L*R0/4 + sin(L*R0/2)*cos(L*R0/2)/2)/R0, (R0 > -oo) & (R0 < oo) & Ne(R0, 0)),
+ (L*r0**2, True))
+
+ w = 2*pi*z/L
+
+ sol = sqrt(2)*sqrt(L)*r0**2*fresnelc(sqrt(2)*sqrt(L))*gamma(S.One/4)/(16*gamma(S(5)/4)) + L*r0**2/2
+
+ assert integrate(r0**2*cos(w*z)**2, (z, -L/2, L/2)) == sol
+
+
+def test_issue_15810():
+ assert integrate(1/(2**(2*x/3) + 1), (x, 0, oo)) == Rational(3, 2)
+
+
+def test_issue_21024():
+ x = Symbol('x', real=True, nonzero=True)
+ f = log(x)*log(4*x) + log(3*x + exp(2))
+ F = x*log(x)**2 + x*(1 - 2*log(2)) + (-2*x + 2*x*log(2))*log(x) + \
+ (x + exp(2)/6)*log(3*x + exp(2)) + exp(2)*log(3*x + exp(2))/6
+ assert F == integrate(f, x)
+
+ f = (x + exp(3))/x**2
+ F = log(x) - exp(3)/x
+ assert F == integrate(f, x)
+
+ f = (x**2 + exp(5))/x
+ F = x**2/2 + exp(5)*log(x)
+ assert F == integrate(f, x)
+
+ f = x/(2*x + tanh(1))
+ F = x/2 - log(2*x + tanh(1))*tanh(1)/4
+ assert F == integrate(f, x)
+
+ f = x - sinh(4)/x
+ F = x**2/2 - log(x)*sinh(4)
+ assert F == integrate(f, x)
+
+ f = log(x + exp(5)/x)
+ F = x*log(x + exp(5)/x) - x + 2*exp(Rational(5, 2))*atan(x*exp(Rational(-5, 2)))
+ assert F == integrate(f, x)
+
+ f = x**5/(x + E)
+ F = x**5/5 - E*x**4/4 + x**3*exp(2)/3 - x**2*exp(3)/2 + x*exp(4) - exp(5)*log(x + E)
+ assert F == integrate(f, x)
+
+ f = 4*x/(x + sinh(5))
+ F = 4*x - 4*log(x + sinh(5))*sinh(5)
+ assert F == integrate(f, x)
+
+ f = x**2/(2*x + sinh(2))
+ F = x**2/4 - x*sinh(2)/4 + log(2*x + sinh(2))*sinh(2)**2/8
+ assert F == integrate(f, x)
+
+ f = -x**2/(x + E)
+ F = -x**2/2 + E*x - exp(2)*log(x + E)
+ assert F == integrate(f, x)
+
+ f = (2*x + 3)*exp(5)/x
+ F = 2*x*exp(5) + 3*exp(5)*log(x)
+ assert F == integrate(f, x)
+
+ f = x + 2 + cosh(3)/x
+ F = x**2/2 + 2*x + log(x)*cosh(3)
+ assert F == integrate(f, x)
+
+ f = x - tanh(1)/x**3
+ F = x**2/2 + tanh(1)/(2*x**2)
+ assert F == integrate(f, x)
+
+ f = (3*x - exp(6))/x
+ F = 3*x - exp(6)*log(x)
+ assert F == integrate(f, x)
+
+ f = x**4/(x + exp(5))**2 + x
+ F = x**3/3 + x**2*(Rational(1, 2) - exp(5)) + 3*x*exp(10) - 4*exp(15)*log(x + exp(5)) - exp(20)/(x + exp(5))
+ assert F == integrate(f, x)
+
+ f = x*(x + exp(10)/x**2) + x
+ F = x**3/3 + x**2/2 + exp(10)*log(x)
+ assert F == integrate(f, x)
+
+ f = x + x/(5*x + sinh(3))
+ F = x**2/2 + x/5 - log(5*x + sinh(3))*sinh(3)/25
+ assert F == integrate(f, x)
+
+ f = (x + exp(3))/(2*x**2 + 2*x)
+ F = exp(3)*log(x)/2 - exp(3)*log(x + 1)/2 + log(x + 1)/2
+ assert F == integrate(f, x).expand()
+
+ f = log(x + 4*sinh(4))
+ F = x*log(x + 4*sinh(4)) - x + 4*log(x + 4*sinh(4))*sinh(4)
+ assert F == integrate(f, x)
+
+ f = -x + 20*(exp(-5) - atan(4)/x)**3*sin(4)/x
+ F = (-x**2*exp(15)/2 + 20*log(x)*sin(4) - (-180*x**2*exp(5)*sin(4)*atan(4) + 90*x*exp(10)*sin(4)*atan(4)**2 - \
+ 20*exp(15)*sin(4)*atan(4)**3)/(3*x**3))*exp(-15)
+ assert F == integrate(f, x)
+
+ f = 2*x**2*exp(-4) + 6/x
+ F_true = (2*x**3/3 + 6*exp(4)*log(x))*exp(-4)
+ assert F_true == integrate(f, x)
+
+
+def test_issue_21721():
+ a = Symbol('a')
+ assert integrate(1/(pi*(1+(x-a)**2)),(x,-oo,oo)).expand() == \
+ -Heaviside(im(a) - 1, 0) + Heaviside(im(a) + 1, 0)
+
+
+def test_issue_21831():
+ theta = symbols('theta')
+ assert integrate(cos(3*theta)/(5-4*cos(theta)), (theta, 0, 2*pi)) == pi/12
+ integrand = cos(2*theta)/(5 - 4*cos(theta))
+ assert integrate(integrand, (theta, 0, 2*pi)) == pi/6
+
+
+@slow
+def test_issue_22033_integral():
+ assert integrate((x**2 - Rational(1, 4))**2 * sqrt(1 - x**2), (x, -1, 1)) == pi/32
+
+
+@slow
+def test_issue_21671():
+ assert integrate(1,(z,x**2+y**2,2-x**2-y**2),(y,-sqrt(1-x**2),sqrt(1-x**2)),(x,-1,1)) == pi
+ assert integrate(-4*(1 - x**2)**(S(3)/2)/3 + 2*sqrt(1 - x**2)*(2 - 2*x**2), (x, -1, 1)) == pi
+
+
+def test_issue_18527():
+ # The manual integrator can not currently solve this. Assert that it does
+ # not give an incorrect result involving Abs when x has real assumptions.
+ xr = symbols('xr', real=True)
+ expr = (cos(x)/(4+(sin(x))**2))
+ res_real = integrate(expr.subs(x, xr), xr, manual=True).subs(xr, x)
+ assert integrate(expr, x, manual=True) == res_real == Integral(expr, x)
+
+
+def test_issue_23718():
+ f = 1/(b*cos(x) + a*sin(x))
+ Fpos = (-log(-a/b + tan(x/2) - sqrt(a**2 + b**2)/b)/sqrt(a**2 + b**2)
+ +log(-a/b + tan(x/2) + sqrt(a**2 + b**2)/b)/sqrt(a**2 + b**2))
+ F = Piecewise(
+ # XXX: The zoo case here is for a=b=0 so it should just be zoo or maybe
+ # it doesn't really need to be included at all given that the original
+ # integrand is really undefined in that case anyway.
+ (zoo*(-log(tan(x/2) - 1) + log(tan(x/2) + 1)), Eq(a, 0) & Eq(b, 0)),
+ (log(tan(x/2))/a, Eq(b, 0)),
+ (-I/(-I*b*sin(x) + b*cos(x)), Eq(a, -I*b)),
+ (I/(I*b*sin(x) + b*cos(x)), Eq(a, I*b)),
+ (Fpos, True),
+ )
+ assert integrate(f, x) == F
+
+ ap, bp = symbols('a, b', positive=True)
+ rep = {a: ap, b: bp}
+ assert integrate(f.subs(rep), x) == Fpos.subs(rep)
+
+
+def test_issue_23566():
+ i = integrate(1/sqrt(x**2-1), (x, -2, -1))
+ assert i == -log(2 - sqrt(3))
+ assert math.isclose(i.n(), 1.31695789692482)
+
+
+def test_pr_23583():
+ # This result from meijerg is wrong. Check whether new result is correct when this test fail.
+ assert integrate(1/sqrt((x - I)**2-1)) == Piecewise((acosh(x - I), Abs((x - I)**2) > 1), (-I*asin(x - I), True))
+
+
+def test_issue_7264():
+ assert integrate(exp(x)*sqrt(1 + exp(2*x))) == sqrt(exp(2*x) + 1)*exp(x)/2 + asinh(exp(x))/2
+
+
+def test_issue_11254a():
+ assert integrate(sech(x), (x, 0, 1)) == 2*atan(tanh(S.Half))
+
+
+def test_issue_11254b():
+ assert integrate(csch(x), x) == log(tanh(x/2))
+ assert integrate(csch(x), (x, 0, 1)) == oo
+
+
+def test_issue_11254d():
+ # (sech(x)**2).rewrite(sinh)
+ assert integrate(-1/sinh(x + I*pi/2, evaluate=False)**2, x) == -2/(exp(2*x) + 1)
+ assert integrate(cosh(x)**(-2), x) == 2*tanh(x/2)/(tanh(x/2)**2 + 1)
+
+
+def test_issue_22863():
+ i = integrate((3*x**3-x**2+2*x-4)/sqrt(x**2-3*x+2), (x, 0, 1))
+ assert i == -101*sqrt(2)/8 - 135*log(3 - 2*sqrt(2))/16
+ assert math.isclose(i.n(), -2.98126694400554)
+
+
+def test_issue_9723():
+ assert integrate(sqrt(x + sqrt(x))) == \
+ 2*sqrt(sqrt(x) + x)*(sqrt(x)/12 + x/3 - S(1)/8) + log(2*sqrt(x) + 2*sqrt(sqrt(x) + x) + 1)/8
+ assert integrate(sqrt(2*x+3+sqrt(4*x+5))**3) == \
+ sqrt(2*x + sqrt(4*x + 5) + 3) * \
+ (9*x/10 + 11*(4*x + 5)**(S(3)/2)/40 + sqrt(4*x + 5)/40 + (4*x + 5)**2/10 + S(11)/10)/2
+
+
+def test_issue_23704():
+ # XXX: This is testing that an exception is not raised in risch Ideally
+ # manualintegrate (manual=True) would be able to compute this but
+ # manualintegrate is very slow for this example so we don't test that here.
+ assert (integrate(log(x)/x**2/(c*x**2+b*x+a),x, risch=True)
+ == NonElementaryIntegral(log(x)/(a*x**2 + b*x**3 + c*x**4), x))
+
+
+def test_exp_substitution():
+ assert integrate(1/sqrt(1-exp(2*x))) == log(sqrt(1 - exp(2*x)) - 1)/2 - log(sqrt(1 - exp(2*x)) + 1)/2
+
+
+def test_hyperbolic():
+ assert integrate(coth(x)) == x - log(tanh(x) + 1) + log(tanh(x))
+ assert integrate(sech(x)) == 2*atan(tanh(x/2))
+ assert integrate(csch(x)) == log(tanh(x/2))
+
+
+def test_nested_pow():
+ assert integrate(sqrt(x**2)) == x*sqrt(x**2)/2
+ assert integrate(sqrt(x**(S(5)/3))) == 6*x*sqrt(x**(S(5)/3))/11
+ assert integrate(1/sqrt(x**2)) == x*log(x)/sqrt(x**2)
+ assert integrate(x*sqrt(x**(-4))) == x**2*sqrt(x**-4)*log(x)
+
+
+def test_sqrt_quadratic():
+ assert integrate(1/sqrt(3*x**2+4*x+5)) == sqrt(3)*asinh(3*sqrt(11)*(x + S(2)/3)/11)/3
+ assert integrate(1/sqrt(-3*x**2+4*x+5)) == sqrt(3)*asin(3*sqrt(19)*(x - S(2)/3)/19)/3
+ assert integrate(1/sqrt(3*x**2+4*x-5)) == sqrt(3)*log(6*x + 2*sqrt(3)*sqrt(3*x**2 + 4*x - 5) + 4)/3
+ assert integrate(1/sqrt(4*x**2-4*x+1)) == (x - S.Half)*log(x - S.Half)/(2*sqrt((x - S.Half)**2))
+ assert integrate(1/sqrt(a+b*x+c*x**2), x) == \
+ Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(c, 0) & Ne(a - b**2/(4*c), 0)),
+ ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), Ne(c, 0)),
+ (2*sqrt(a + b*x)/b, Ne(b, 0)), (x/sqrt(a), True))
+
+ assert integrate((7*x+6)/sqrt(3*x**2+4*x+5)) == \
+ 7*sqrt(3*x**2 + 4*x + 5)/3 + 4*sqrt(3)*asinh(3*sqrt(11)*(x + S(2)/3)/11)/9
+ assert integrate((7*x+6)/sqrt(-3*x**2+4*x+5)) == \
+ -7*sqrt(-3*x**2 + 4*x + 5)/3 + 32*sqrt(3)*asin(3*sqrt(19)*(x - S(2)/3)/19)/9
+ assert integrate((7*x+6)/sqrt(3*x**2+4*x-5)) == \
+ 7*sqrt(3*x**2 + 4*x - 5)/3 + 4*sqrt(3)*log(6*x + 2*sqrt(3)*sqrt(3*x**2 + 4*x - 5) + 4)/9
+ assert integrate((d+e*x)/sqrt(a+b*x+c*x**2), x) == \
+ Piecewise(((-b*e/(2*c) + d) *
+ Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(a - b**2/(4*c), 0)),
+ ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), True)) +
+ e*sqrt(a + b*x + c*x**2)/c, Ne(c, 0)),
+ ((2*d*sqrt(a + b*x) + 2*e*(-a*sqrt(a + b*x) + (a + b*x)**(S(3)/2)/3)/b)/b, Ne(b, 0)),
+ ((d*x + e*x**2/2)/sqrt(a), True))
+
+ assert integrate((3*x**3-x**2+2*x-4)/sqrt(x**2-3*x+2)) == \
+ sqrt(x**2 - 3*x + 2)*(x**2 + 13*x/4 + S(101)/8) + 135*log(2*x + 2*sqrt(x**2 - 3*x + 2) - 3)/16
+
+ assert integrate(sqrt(53225*x**2-66732*x+23013)) == \
+ (x/2 - S(16683)/53225)*sqrt(53225*x**2 - 66732*x + 23013) + \
+ 111576969*sqrt(2129)*asinh(53225*x/10563 - S(11122)/3521)/1133160250
+ assert integrate(sqrt(a+b*x+c*x**2), x) == \
+ Piecewise(((a/2 - b**2/(8*c)) *
+ Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(a - b**2/(4*c), 0)),
+ ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), True)) +
+ (b/(4*c) + x/2)*sqrt(a + b*x + c*x**2), Ne(c, 0)),
+ (2*(a + b*x)**(S(3)/2)/(3*b), Ne(b, 0)),
+ (sqrt(a)*x, True))
+
+ assert integrate(x*sqrt(x**2+2*x+4)) == \
+ (x**2/3 + x/6 + S(5)/6)*sqrt(x**2 + 2*x + 4) - 3*asinh(sqrt(3)*(x + 1)/3)/2
+
+
+def test_mul_pow_derivative():
+ assert integrate(x*sec(x)*tan(x)) == x*sec(x) - log(tan(x) + sec(x))
+ assert integrate(x*sec(x)**2, x) == x*tan(x) + log(cos(x))
+ assert integrate(x**3*Derivative(f(x), (x, 4))) == \
+ x**3*Derivative(f(x), (x, 3)) - 3*x**2*Derivative(f(x), (x, 2)) + 6*x*Derivative(f(x), x) - 6*f(x)
+
+
+def test_issue_20782():
+ fun1 = Piecewise((0, x < 0.0), (1, True))
+ fun2 = -Piecewise((0, x < 1.0), (1, True))
+ fun_sum = fun1 + fun2
+ L = (x, -float('Inf'), 1)
+
+ assert integrate(fun1, L) == 1
+ assert integrate(fun2, L) == 0
+ assert integrate(-fun1, L) == -1
+ assert integrate(-fun2, L) == 0.
+ assert integrate(fun_sum, L) == 1.
+ assert integrate(-fun_sum, L) == -1.
+
+
+def test_issue_20781():
+ P = lambda a: Piecewise((0, x < a), (1, x >= a))
+ f = lambda a: P(int(a)) + P(float(a))
+ L = (x, -float('Inf'), x)
+ f1 = integrate(f(1), L)
+ assert f1 == 2*x - Min(1.0, x) - Min(x, Max(1.0, 1, evaluate=False))
+ # XXX is_zero is True for S(0) and Float(0) and this is baked into
+ # the code more deeply than the issue of Float(0) != S(0)
+ assert integrate(f(0), (x, -float('Inf'), x)
+ ) == 2*x - 2*Min(0, x)
+
+
+@slow
+def test_issue_19427():
+ #
+ x = Symbol("x")
+
+ # Have always been okay:
+ assert integrate((x ** 4) * sqrt(1 - x ** 2), (x, -1, 1)) == pi / 16
+ assert integrate((-2 * x ** 2) * sqrt(1 - x ** 2), (x, -1, 1)) == -pi / 4
+ assert integrate((1) * sqrt(1 - x ** 2), (x, -1, 1)) == pi / 2
+
+ # Sum of the above, used to incorrectly return 0 for a while:
+ assert integrate((x ** 4 - 2 * x ** 2 + 1) * sqrt(1 - x ** 2), (x, -1, 1)) == 5 * pi / 16
+
+
+def test_issue_23942():
+ I1 = Integral(1/sqrt(a*(1 + x)**3 + (1 + x)**2), (x, 0, z))
+ assert I1.series(a, 1, n=1) == Integral(1/sqrt(x**3 + 4*x**2 + 5*x + 2), (x, 0, z)) + O(a - 1, (a, 1))
+ I2 = Integral(1/sqrt(a*(4 - x)**4 + (5 + x)**2), (x, 0, z))
+ assert I2.series(a, 2, n=1) == Integral(1/sqrt(2*x**4 - 32*x**3 + 193*x**2 - 502*x + 537), (x, 0, z)) + O(a - 2, (a, 2))
+
+
+def test_issue_25886():
+ # https://github.com/sympy/sympy/issues/25886
+ f = (1-x)*exp(0.937098661j*x)
+ F_exp = (1.0*(-1.0671234968289*I*y
+ + 1.13875255748434
+ + 1.0671234968289*I)*exp(0.937098661*I*y)
+ - 1.13875255748434*exp(0.937098661*I))
+ F = integrate(f, (x, y, 1.0))
+ assert F.is_same(F_exp, math.isclose)
+
+
+def test_old_issues():
+ # https://github.com/sympy/sympy/issues/5212
+ I1 = integrate(cos(log(x**2))/x)
+ assert I1 == sin(log(x**2))/2
+ # https://github.com/sympy/sympy/issues/5462
+ I2 = integrate(1/(x**2+y**2)**(Rational(3,2)),x)
+ assert I2 == x/(y**3*sqrt(x**2/y**2 + 1))
+ # https://github.com/sympy/sympy/issues/6278
+ I3 = integrate(1/(cos(x)+2),(x,0,2*pi))
+ assert I3 == 2*sqrt(3)*pi/3
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_intpoly.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_intpoly.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddbaad1fbdeca53ccab8e8b22758a6ad2d89836e
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_intpoly.py
@@ -0,0 +1,627 @@
+from sympy.functions.elementary.complexes import Abs
+from sympy.functions.elementary.miscellaneous import sqrt
+
+from sympy.core import S, Rational
+
+from sympy.integrals.intpoly import (decompose, best_origin, distance_to_side,
+ polytope_integrate, point_sort,
+ hyperplane_parameters, main_integrate3d,
+ main_integrate, polygon_integrate,
+ lineseg_integrate, integration_reduction,
+ integration_reduction_dynamic, is_vertex)
+
+from sympy.geometry.line import Segment2D
+from sympy.geometry.polygon import Polygon
+from sympy.geometry.point import Point, Point2D
+from sympy.abc import x, y, z
+
+from sympy.testing.pytest import slow
+
+
+def test_decompose():
+ assert decompose(x) == {1: x}
+ assert decompose(x**2) == {2: x**2}
+ assert decompose(x*y) == {2: x*y}
+ assert decompose(x + y) == {1: x + y}
+ assert decompose(x**2 + y) == {1: y, 2: x**2}
+ assert decompose(8*x**2 + 4*y + 7) == {0: 7, 1: 4*y, 2: 8*x**2}
+ assert decompose(x**2 + 3*y*x) == {2: x**2 + 3*x*y}
+ assert decompose(9*x**2 + y + 4*x + x**3 + y**2*x + 3) ==\
+ {0: 3, 1: 4*x + y, 2: 9*x**2, 3: x**3 + x*y**2}
+
+ assert decompose(x, True) == {x}
+ assert decompose(x ** 2, True) == {x**2}
+ assert decompose(x * y, True) == {x * y}
+ assert decompose(x + y, True) == {x, y}
+ assert decompose(x ** 2 + y, True) == {y, x ** 2}
+ assert decompose(8 * x ** 2 + 4 * y + 7, True) == {7, 4*y, 8*x**2}
+ assert decompose(x ** 2 + 3 * y * x, True) == {x ** 2, 3 * x * y}
+ assert decompose(9 * x ** 2 + y + 4 * x + x ** 3 + y ** 2 * x + 3, True) == \
+ {3, y, 4*x, 9*x**2, x*y**2, x**3}
+
+
+def test_best_origin():
+ expr1 = y ** 2 * x ** 5 + y ** 5 * x ** 7 + 7 * x + x ** 12 + y ** 7 * x
+
+ l1 = Segment2D(Point(0, 3), Point(1, 1))
+ l2 = Segment2D(Point(S(3) / 2, 0), Point(S(3) / 2, 3))
+ l3 = Segment2D(Point(0, S(3) / 2), Point(3, S(3) / 2))
+ l4 = Segment2D(Point(0, 2), Point(2, 0))
+ l5 = Segment2D(Point(0, 2), Point(1, 1))
+ l6 = Segment2D(Point(2, 0), Point(1, 1))
+
+ assert best_origin((2, 1), 3, l1, expr1) == (0, 3)
+ # XXX: Should these return exact Rational output? Maybe best_origin should
+ # sympify its arguments...
+ assert best_origin((2, 0), 3, l2, x ** 7) == (1.5, 0)
+ assert best_origin((0, 2), 3, l3, x ** 7) == (0, 1.5)
+ assert best_origin((1, 1), 2, l4, x ** 7 * y ** 3) == (0, 2)
+ assert best_origin((1, 1), 2, l4, x ** 3 * y ** 7) == (2, 0)
+ assert best_origin((1, 1), 2, l5, x ** 2 * y ** 9) == (0, 2)
+ assert best_origin((1, 1), 2, l6, x ** 9 * y ** 2) == (2, 0)
+
+
+@slow
+def test_polytope_integrate():
+ # Convex 2-Polytopes
+ # Vertex representation
+ assert polytope_integrate(Polygon(Point(0, 0), Point(0, 2),
+ Point(4, 0)), 1) == 4
+ assert polytope_integrate(Polygon(Point(0, 0), Point(0, 1),
+ Point(1, 1), Point(1, 0)), x * y) ==\
+ Rational(1, 4)
+ assert polytope_integrate(Polygon(Point(0, 3), Point(5, 3), Point(1, 1)),
+ 6*x**2 - 40*y) == Rational(-935, 3)
+
+ assert polytope_integrate(Polygon(Point(0, 0), Point(0, sqrt(3)),
+ Point(sqrt(3), sqrt(3)),
+ Point(sqrt(3), 0)), 1) == 3
+
+ hexagon = Polygon(Point(0, 0), Point(-sqrt(3) / 2, S.Half),
+ Point(-sqrt(3) / 2, S(3) / 2), Point(0, 2),
+ Point(sqrt(3) / 2, S(3) / 2), Point(sqrt(3) / 2, S.Half))
+
+ assert polytope_integrate(hexagon, 1) == S(3*sqrt(3)) / 2
+
+ # Hyperplane representation
+ assert polytope_integrate([((-1, 0), 0), ((1, 2), 4),
+ ((0, -1), 0)], 1) == 4
+ assert polytope_integrate([((-1, 0), 0), ((0, 1), 1),
+ ((1, 0), 1), ((0, -1), 0)], x * y) == Rational(1, 4)
+ assert polytope_integrate([((0, 1), 3), ((1, -2), -1),
+ ((-2, -1), -3)], 6*x**2 - 40*y) == Rational(-935, 3)
+ assert polytope_integrate([((-1, 0), 0), ((0, sqrt(3)), 3),
+ ((sqrt(3), 0), 3), ((0, -1), 0)], 1) == 3
+
+ hexagon = [((Rational(-1, 2), -sqrt(3) / 2), 0),
+ ((-1, 0), sqrt(3) / 2),
+ ((Rational(-1, 2), sqrt(3) / 2), sqrt(3)),
+ ((S.Half, sqrt(3) / 2), sqrt(3)),
+ ((1, 0), sqrt(3) / 2),
+ ((S.Half, -sqrt(3) / 2), 0)]
+ assert polytope_integrate(hexagon, 1) == S(3*sqrt(3)) / 2
+
+ # Non-convex polytopes
+ # Vertex representation
+ assert polytope_integrate(Polygon(Point(-1, -1), Point(-1, 1),
+ Point(1, 1), Point(0, 0),
+ Point(1, -1)), 1) == 3
+ assert polytope_integrate(Polygon(Point(-1, -1), Point(-1, 1),
+ Point(0, 0), Point(1, 1),
+ Point(1, -1), Point(0, 0)), 1) == 2
+ # Hyperplane representation
+ assert polytope_integrate([((-1, 0), 1), ((0, 1), 1), ((1, -1), 0),
+ ((1, 1), 0), ((0, -1), 1)], 1) == 3
+ assert polytope_integrate([((-1, 0), 1), ((1, 1), 0), ((-1, 1), 0),
+ ((1, 0), 1), ((-1, -1), 0),
+ ((1, -1), 0)], 1) == 2
+
+ # Tests for 2D polytopes mentioned in Chin et al(Page 10):
+ # http://dilbert.engr.ucdavis.edu/~suku/quadrature/cls-integration.pdf
+ fig1 = Polygon(Point(1.220, -0.827), Point(-1.490, -4.503),
+ Point(-3.766, -1.622), Point(-4.240, -0.091),
+ Point(-3.160, 4), Point(-0.981, 4.447),
+ Point(0.132, 4.027))
+ assert polytope_integrate(fig1, x**2 + x*y + y**2) ==\
+ S(2031627344735367)/(8*10**12)
+
+ fig2 = Polygon(Point(4.561, 2.317), Point(1.491, -1.315),
+ Point(-3.310, -3.164), Point(-4.845, -3.110),
+ Point(-4.569, 1.867))
+ assert polytope_integrate(fig2, x**2 + x*y + y**2) ==\
+ S(517091313866043)/(16*10**11)
+
+ fig3 = Polygon(Point(-2.740, -1.888), Point(-3.292, 4.233),
+ Point(-2.723, -0.697), Point(-0.643, -3.151))
+ assert polytope_integrate(fig3, x**2 + x*y + y**2) ==\
+ S(147449361647041)/(8*10**12)
+
+ fig4 = Polygon(Point(0.211, -4.622), Point(-2.684, 3.851),
+ Point(0.468, 4.879), Point(4.630, -1.325),
+ Point(-0.411, -1.044))
+ assert polytope_integrate(fig4, x**2 + x*y + y**2) ==\
+ S(180742845225803)/(10**12)
+
+ # Tests for many polynomials with maximum degree given(2D case).
+ tri = Polygon(Point(0, 3), Point(5, 3), Point(1, 1))
+ polys = []
+ expr1 = x**9*y + x**7*y**3 + 2*x**2*y**8
+ expr2 = x**6*y**4 + x**5*y**5 + 2*y**10
+ expr3 = x**10 + x**9*y + x**8*y**2 + x**5*y**5
+ polys.extend((expr1, expr2, expr3))
+ result_dict = polytope_integrate(tri, polys, max_degree=10)
+ assert result_dict[expr1] == Rational(615780107, 594)
+ assert result_dict[expr2] == Rational(13062161, 27)
+ assert result_dict[expr3] == Rational(1946257153, 924)
+
+ tri = Polygon(Point(0, 3), Point(5, 3), Point(1, 1))
+ expr1 = x**7*y**1 + 2*x**2*y**6
+ expr2 = x**6*y**4 + x**5*y**5 + 2*y**10
+ expr3 = x**10 + x**9*y + x**8*y**2 + x**5*y**5
+ polys.extend((expr1, expr2, expr3))
+ assert polytope_integrate(tri, polys, max_degree=9) == \
+ {x**7*y + 2*x**2*y**6: Rational(489262, 9)}
+
+ # Tests when all integral of all monomials up to a max_degree is to be
+ # calculated.
+ assert polytope_integrate(Polygon(Point(0, 0), Point(0, 1),
+ Point(1, 1), Point(1, 0)),
+ max_degree=4) == {0: 0, 1: 1, x: S.Half,
+ x ** 2 * y ** 2: S.One / 9,
+ x ** 4: S.One / 5,
+ y ** 4: S.One / 5,
+ y: S.Half,
+ x * y ** 2: S.One / 6,
+ y ** 2: S.One / 3,
+ x ** 3: S.One / 4,
+ x ** 2 * y: S.One / 6,
+ x ** 3 * y: S.One / 8,
+ x * y: S.One / 4,
+ y ** 3: S.One / 4,
+ x ** 2: S.One / 3,
+ x * y ** 3: S.One / 8}
+
+ # Tests for 3D polytopes
+ cube1 = [[(0, 0, 0), (0, 6, 6), (6, 6, 6), (3, 6, 0),
+ (0, 6, 0), (6, 0, 6), (3, 0, 0), (0, 0, 6)],
+ [1, 2, 3, 4], [3, 2, 5, 6], [1, 7, 5, 2], [0, 6, 5, 7],
+ [1, 4, 0, 7], [0, 4, 3, 6]]
+ assert polytope_integrate(cube1, 1) == S(162)
+
+ # 3D Test cases in Chin et al(2015)
+ cube2 = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),
+ (5, 0, 5), (5, 5, 0), (5, 5, 5)],
+ [3, 7, 6, 2], [1, 5, 7, 3], [5, 4, 6, 7], [0, 4, 5, 1],
+ [2, 0, 1, 3], [2, 6, 4, 0]]
+
+ cube3 = [[(0, 0, 0), (5, 0, 0), (5, 4, 0), (3, 2, 0), (3, 5, 0),
+ (0, 5, 0), (0, 0, 5), (5, 0, 5), (5, 4, 5), (3, 2, 5),
+ (3, 5, 5), (0, 5, 5)],
+ [6, 11, 5, 0], [1, 7, 6, 0], [5, 4, 3, 2, 1, 0], [11, 10, 4, 5],
+ [10, 9, 3, 4], [9, 8, 2, 3], [8, 7, 1, 2], [7, 8, 9, 10, 11, 6]]
+
+ cube4 = [[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1),
+ (S.One / 4, S.One / 4, S.One / 4)],
+ [0, 2, 1], [1, 3, 0], [4, 2, 3], [4, 3, 1],
+ [0, 1, 2], [2, 4, 1], [0, 3, 2]]
+
+ assert polytope_integrate(cube2, x ** 2 + y ** 2 + x * y + z ** 2) ==\
+ Rational(15625, 4)
+ assert polytope_integrate(cube3, x ** 2 + y ** 2 + x * y + z ** 2) ==\
+ S(33835) / 12
+ assert polytope_integrate(cube4, x ** 2 + y ** 2 + x * y + z ** 2) ==\
+ S(37) / 960
+
+ # Test cases from Mathematica's PolyhedronData library
+ octahedron = [[(S.NegativeOne / sqrt(2), 0, 0), (0, S.One / sqrt(2), 0),
+ (0, 0, S.NegativeOne / sqrt(2)), (0, 0, S.One / sqrt(2)),
+ (0, S.NegativeOne / sqrt(2), 0), (S.One / sqrt(2), 0, 0)],
+ [3, 4, 5], [3, 5, 1], [3, 1, 0], [3, 0, 4], [4, 0, 2],
+ [4, 2, 5], [2, 0, 1], [5, 2, 1]]
+
+ assert polytope_integrate(octahedron, 1) == sqrt(2) / 3
+
+ great_stellated_dodecahedron =\
+ [[(-0.32491969623290634095, 0, 0.42532540417601993887),
+ (0.32491969623290634095, 0, -0.42532540417601993887),
+ (-0.52573111211913359231, 0, 0.10040570794311363956),
+ (0.52573111211913359231, 0, -0.10040570794311363956),
+ (-0.10040570794311363956, -0.3090169943749474241, 0.42532540417601993887),
+ (-0.10040570794311363956, 0.30901699437494742410, 0.42532540417601993887),
+ (0.10040570794311363956, -0.3090169943749474241, -0.42532540417601993887),
+ (0.10040570794311363956, 0.30901699437494742410, -0.42532540417601993887),
+ (-0.16245984811645317047, -0.5, 0.10040570794311363956),
+ (-0.16245984811645317047, 0.5, 0.10040570794311363956),
+ (0.16245984811645317047, -0.5, -0.10040570794311363956),
+ (0.16245984811645317047, 0.5, -0.10040570794311363956),
+ (-0.42532540417601993887, -0.3090169943749474241, -0.10040570794311363956),
+ (-0.42532540417601993887, 0.30901699437494742410, -0.10040570794311363956),
+ (-0.26286555605956679615, 0.1909830056250525759, -0.42532540417601993887),
+ (-0.26286555605956679615, -0.1909830056250525759, -0.42532540417601993887),
+ (0.26286555605956679615, 0.1909830056250525759, 0.42532540417601993887),
+ (0.26286555605956679615, -0.1909830056250525759, 0.42532540417601993887),
+ (0.42532540417601993887, -0.3090169943749474241, 0.10040570794311363956),
+ (0.42532540417601993887, 0.30901699437494742410, 0.10040570794311363956)],
+ [12, 3, 0, 6, 16], [17, 7, 0, 3, 13],
+ [9, 6, 0, 7, 8], [18, 2, 1, 4, 14],
+ [15, 5, 1, 2, 19], [11, 4, 1, 5, 10],
+ [8, 19, 2, 18, 9], [10, 13, 3, 12, 11],
+ [16, 14, 4, 11, 12], [13, 10, 5, 15, 17],
+ [14, 16, 6, 9, 18], [19, 8, 7, 17, 15]]
+ # Actual volume is : 0.163118960624632
+ assert Abs(polytope_integrate(great_stellated_dodecahedron, 1) -\
+ 0.163118960624632) < 1e-12
+
+ expr = x **2 + y ** 2 + z ** 2
+ octahedron_five_compound = [[(0, -0.7071067811865475244, 0),
+ (0, 0.70710678118654752440, 0),
+ (0.1148764602736805918,
+ -0.35355339059327376220, -0.60150095500754567366),
+ (0.1148764602736805918, 0.35355339059327376220,
+ -0.60150095500754567366),
+ (0.18587401723009224507,
+ -0.57206140281768429760, 0.37174803446018449013),
+ (0.18587401723009224507, 0.57206140281768429760,
+ 0.37174803446018449013),
+ (0.30075047750377283683, -0.21850801222441053540,
+ 0.60150095500754567366),
+ (0.30075047750377283683, 0.21850801222441053540,
+ 0.60150095500754567366),
+ (0.48662449473386508189, -0.35355339059327376220,
+ -0.37174803446018449013),
+ (0.48662449473386508189, 0.35355339059327376220,
+ -0.37174803446018449013),
+ (-0.60150095500754567366, 0, -0.37174803446018449013),
+ (-0.30075047750377283683, -0.21850801222441053540,
+ -0.60150095500754567366),
+ (-0.30075047750377283683, 0.21850801222441053540,
+ -0.60150095500754567366),
+ (0.60150095500754567366, 0, 0.37174803446018449013),
+ (0.4156269377774534286, -0.57206140281768429760, 0),
+ (0.4156269377774534286, 0.57206140281768429760, 0),
+ (0.37174803446018449013, 0, -0.60150095500754567366),
+ (-0.4156269377774534286, -0.57206140281768429760, 0),
+ (-0.4156269377774534286, 0.57206140281768429760, 0),
+ (-0.67249851196395732696, -0.21850801222441053540, 0),
+ (-0.67249851196395732696, 0.21850801222441053540, 0),
+ (0.67249851196395732696, -0.21850801222441053540, 0),
+ (0.67249851196395732696, 0.21850801222441053540, 0),
+ (-0.37174803446018449013, 0, 0.60150095500754567366),
+ (-0.48662449473386508189, -0.35355339059327376220,
+ 0.37174803446018449013),
+ (-0.48662449473386508189, 0.35355339059327376220,
+ 0.37174803446018449013),
+ (-0.18587401723009224507, -0.57206140281768429760,
+ -0.37174803446018449013),
+ (-0.18587401723009224507, 0.57206140281768429760,
+ -0.37174803446018449013),
+ (-0.11487646027368059176, -0.35355339059327376220,
+ 0.60150095500754567366),
+ (-0.11487646027368059176, 0.35355339059327376220,
+ 0.60150095500754567366)],
+ [0, 10, 16], [23, 10, 0], [16, 13, 0],
+ [0, 13, 23], [16, 10, 1], [1, 10, 23],
+ [1, 13, 16], [23, 13, 1], [2, 4, 19],
+ [22, 4, 2], [2, 19, 27], [27, 22, 2],
+ [20, 5, 3], [3, 5, 21], [26, 20, 3],
+ [3, 21, 26], [29, 19, 4], [4, 22, 29],
+ [5, 20, 28], [28, 21, 5], [6, 8, 15],
+ [17, 8, 6], [6, 15, 25], [25, 17, 6],
+ [14, 9, 7], [7, 9, 18], [24, 14, 7],
+ [7, 18, 24], [8, 12, 15], [17, 12, 8],
+ [14, 11, 9], [9, 11, 18], [11, 14, 24],
+ [24, 18, 11], [25, 15, 12], [12, 17, 25],
+ [29, 27, 19], [20, 26, 28], [28, 26, 21],
+ [22, 27, 29]]
+ assert Abs(polytope_integrate(octahedron_five_compound, expr)) - 0.353553\
+ < 1e-6
+
+ cube_five_compound = [[(-0.1624598481164531631, -0.5, -0.6881909602355867691),
+ (-0.1624598481164531631, 0.5, -0.6881909602355867691),
+ (0.1624598481164531631, -0.5, 0.68819096023558676910),
+ (0.1624598481164531631, 0.5, 0.68819096023558676910),
+ (-0.52573111211913359231, 0, -0.6881909602355867691),
+ (0.52573111211913359231, 0, 0.68819096023558676910),
+ (-0.26286555605956679615, -0.8090169943749474241,
+ -0.1624598481164531631),
+ (-0.26286555605956679615, 0.8090169943749474241,
+ -0.1624598481164531631),
+ (0.26286555605956680301, -0.8090169943749474241,
+ 0.1624598481164531631),
+ (0.26286555605956680301, 0.8090169943749474241,
+ 0.1624598481164531631),
+ (-0.42532540417601993887, -0.3090169943749474241,
+ 0.68819096023558676910),
+ (-0.42532540417601993887, 0.30901699437494742410,
+ 0.68819096023558676910),
+ (0.42532540417601996609, -0.3090169943749474241,
+ -0.6881909602355867691),
+ (0.42532540417601996609, 0.30901699437494742410,
+ -0.6881909602355867691),
+ (-0.6881909602355867691, -0.5, 0.1624598481164531631),
+ (-0.6881909602355867691, 0.5, 0.1624598481164531631),
+ (0.68819096023558676910, -0.5, -0.1624598481164531631),
+ (0.68819096023558676910, 0.5, -0.1624598481164531631),
+ (-0.85065080835203998877, 0, -0.1624598481164531631),
+ (0.85065080835203993218, 0, 0.1624598481164531631)],
+ [18, 10, 3, 7], [13, 19, 8, 0], [18, 0, 8, 10],
+ [3, 19, 13, 7], [18, 7, 13, 0], [8, 19, 3, 10],
+ [6, 2, 11, 18], [1, 9, 19, 12], [11, 9, 1, 18],
+ [6, 12, 19, 2], [1, 12, 6, 18], [11, 2, 19, 9],
+ [4, 14, 11, 7], [17, 5, 8, 12], [4, 12, 8, 14],
+ [11, 5, 17, 7], [4, 7, 17, 12], [8, 5, 11, 14],
+ [6, 10, 15, 4], [13, 9, 5, 16], [15, 9, 13, 4],
+ [6, 16, 5, 10], [13, 16, 6, 4], [15, 10, 5, 9],
+ [14, 15, 1, 0], [16, 17, 3, 2], [14, 2, 3, 15],
+ [1, 17, 16, 0], [14, 0, 16, 2], [3, 17, 1, 15]]
+ assert Abs(polytope_integrate(cube_five_compound, expr) - 1.25) < 1e-12
+
+ echidnahedron = [[(0, 0, -2.4898982848827801995),
+ (0, 0, 2.4898982848827802734),
+ (0, -4.2360679774997896964, -2.4898982848827801995),
+ (0, -4.2360679774997896964, 2.4898982848827802734),
+ (0, 4.2360679774997896964, -2.4898982848827801995),
+ (0, 4.2360679774997896964, 2.4898982848827802734),
+ (-4.0287400534704067567, -1.3090169943749474241, -2.4898982848827801995),
+ (-4.0287400534704067567, -1.3090169943749474241, 2.4898982848827802734),
+ (-4.0287400534704067567, 1.3090169943749474241, -2.4898982848827801995),
+ (-4.0287400534704067567, 1.3090169943749474241, 2.4898982848827802734),
+ (4.0287400534704069747, -1.3090169943749474241, -2.4898982848827801995),
+ (4.0287400534704069747, -1.3090169943749474241, 2.4898982848827802734),
+ (4.0287400534704069747, 1.3090169943749474241, -2.4898982848827801995),
+ (4.0287400534704069747, 1.3090169943749474241, 2.4898982848827802734),
+ (-2.4898982848827801995, -3.4270509831248422723, -2.4898982848827801995),
+ (-2.4898982848827801995, -3.4270509831248422723, 2.4898982848827802734),
+ (-2.4898982848827801995, 3.4270509831248422723, -2.4898982848827801995),
+ (-2.4898982848827801995, 3.4270509831248422723, 2.4898982848827802734),
+ (2.4898982848827802734, -3.4270509831248422723, -2.4898982848827801995),
+ (2.4898982848827802734, -3.4270509831248422723, 2.4898982848827802734),
+ (2.4898982848827802734, 3.4270509831248422723, -2.4898982848827801995),
+ (2.4898982848827802734, 3.4270509831248422723, 2.4898982848827802734),
+ (-4.7169310137059934362, -0.8090169943749474241, -1.1135163644116066184),
+ (-4.7169310137059934362, 0.8090169943749474241, -1.1135163644116066184),
+ (4.7169310137059937438, -0.8090169943749474241, 1.11351636441160673519),
+ (4.7169310137059937438, 0.8090169943749474241, 1.11351636441160673519),
+ (-4.2916056095299737777, -2.1180339887498948482, 1.11351636441160673519),
+ (-4.2916056095299737777, 2.1180339887498948482, 1.11351636441160673519),
+ (4.2916056095299737777, -2.1180339887498948482, -1.1135163644116066184),
+ (4.2916056095299737777, 2.1180339887498948482, -1.1135163644116066184),
+ (-3.6034146492943870399, 0, -3.3405490932348205213),
+ (3.6034146492943870399, 0, 3.3405490932348202056),
+ (-3.3405490932348205213, -3.4270509831248422723, 1.11351636441160673519),
+ (-3.3405490932348205213, 3.4270509831248422723, 1.11351636441160673519),
+ (3.3405490932348202056, -3.4270509831248422723, -1.1135163644116066184),
+ (3.3405490932348202056, 3.4270509831248422723, -1.1135163644116066184),
+ (-2.9152236890588002395, -2.1180339887498948482, 3.3405490932348202056),
+ (-2.9152236890588002395, 2.1180339887498948482, 3.3405490932348202056),
+ (2.9152236890588002395, -2.1180339887498948482, -3.3405490932348205213),
+ (2.9152236890588002395, 2.1180339887498948482, -3.3405490932348205213),
+ (-2.2270327288232132368, 0, -1.1135163644116066184),
+ (-2.2270327288232132368, -4.2360679774997896964, -1.1135163644116066184),
+ (-2.2270327288232132368, 4.2360679774997896964, -1.1135163644116066184),
+ (2.2270327288232134704, 0, 1.11351636441160673519),
+ (2.2270327288232134704, -4.2360679774997896964, 1.11351636441160673519),
+ (2.2270327288232134704, 4.2360679774997896964, 1.11351636441160673519),
+ (-1.8017073246471935200, -1.3090169943749474241, 1.11351636441160673519),
+ (-1.8017073246471935200, 1.3090169943749474241, 1.11351636441160673519),
+ (1.8017073246471935043, -1.3090169943749474241, -1.1135163644116066184),
+ (1.8017073246471935043, 1.3090169943749474241, -1.1135163644116066184),
+ (-1.3763819204711735382, 0, -4.7169310137059934362),
+ (-1.3763819204711735382, 0, 0.26286555605956679615),
+ (1.37638192047117353821, 0, 4.7169310137059937438),
+ (1.37638192047117353821, 0, -0.26286555605956679615),
+ (-1.1135163644116066184, -3.4270509831248422723, -3.3405490932348205213),
+ (-1.1135163644116066184, -0.8090169943749474241, 4.7169310137059937438),
+ (-1.1135163644116066184, -0.8090169943749474241, -0.26286555605956679615),
+ (-1.1135163644116066184, 0.8090169943749474241, 4.7169310137059937438),
+ (-1.1135163644116066184, 0.8090169943749474241, -0.26286555605956679615),
+ (-1.1135163644116066184, 3.4270509831248422723, -3.3405490932348205213),
+ (1.11351636441160673519, -3.4270509831248422723, 3.3405490932348202056),
+ (1.11351636441160673519, -0.8090169943749474241, -4.7169310137059934362),
+ (1.11351636441160673519, -0.8090169943749474241, 0.26286555605956679615),
+ (1.11351636441160673519, 0.8090169943749474241, -4.7169310137059934362),
+ (1.11351636441160673519, 0.8090169943749474241, 0.26286555605956679615),
+ (1.11351636441160673519, 3.4270509831248422723, 3.3405490932348202056),
+ (-0.85065080835203998877, 0, 1.11351636441160673519),
+ (0.85065080835203993218, 0, -1.1135163644116066184),
+ (-0.6881909602355867691, -0.5, -1.1135163644116066184),
+ (-0.6881909602355867691, 0.5, -1.1135163644116066184),
+ (-0.6881909602355867691, -4.7360679774997896964, -1.1135163644116066184),
+ (-0.6881909602355867691, -2.1180339887498948482, -1.1135163644116066184),
+ (-0.6881909602355867691, 2.1180339887498948482, -1.1135163644116066184),
+ (-0.6881909602355867691, 4.7360679774997896964, -1.1135163644116066184),
+ (0.68819096023558676910, -0.5, 1.11351636441160673519),
+ (0.68819096023558676910, 0.5, 1.11351636441160673519),
+ (0.68819096023558676910, -4.7360679774997896964, 1.11351636441160673519),
+ (0.68819096023558676910, -2.1180339887498948482, 1.11351636441160673519),
+ (0.68819096023558676910, 2.1180339887498948482, 1.11351636441160673519),
+ (0.68819096023558676910, 4.7360679774997896964, 1.11351636441160673519),
+ (-0.42532540417601993887, -1.3090169943749474241, -4.7169310137059934362),
+ (-0.42532540417601993887, -1.3090169943749474241, 0.26286555605956679615),
+ (-0.42532540417601993887, 1.3090169943749474241, -4.7169310137059934362),
+ (-0.42532540417601993887, 1.3090169943749474241, 0.26286555605956679615),
+ (-0.26286555605956679615, -0.8090169943749474241, 1.11351636441160673519),
+ (-0.26286555605956679615, 0.8090169943749474241, 1.11351636441160673519),
+ (0.26286555605956679615, -0.8090169943749474241, -1.1135163644116066184),
+ (0.26286555605956679615, 0.8090169943749474241, -1.1135163644116066184),
+ (0.42532540417601996609, -1.3090169943749474241, 4.7169310137059937438),
+ (0.42532540417601996609, -1.3090169943749474241, -0.26286555605956679615),
+ (0.42532540417601996609, 1.3090169943749474241, 4.7169310137059937438),
+ (0.42532540417601996609, 1.3090169943749474241, -0.26286555605956679615)],
+ [9, 66, 47], [44, 62, 77], [20, 91, 49], [33, 47, 83],
+ [3, 77, 84], [12, 49, 53], [36, 84, 66], [28, 53, 62],
+ [73, 83, 91], [15, 84, 46], [25, 64, 43], [16, 58, 72],
+ [26, 46, 51], [11, 43, 74], [4, 72, 91], [60, 74, 84],
+ [35, 91, 64], [23, 51, 58], [19, 74, 77], [79, 83, 78],
+ [6, 56, 40], [76, 77, 81], [21, 78, 75], [8, 40, 58],
+ [31, 75, 74], [42, 58, 83], [41, 81, 56], [13, 75, 43],
+ [27, 51, 47], [2, 89, 71], [24, 43, 62], [17, 47, 85],
+ [14, 71, 56], [65, 85, 75], [22, 56, 51], [34, 62, 89],
+ [5, 85, 78], [32, 81, 46], [10, 53, 48], [45, 78, 64],
+ [7, 46, 66], [18, 48, 89], [37, 66, 85], [70, 89, 81],
+ [29, 64, 53], [88, 74, 1], [38, 67, 48], [42, 83, 72],
+ [57, 1, 85], [34, 48, 62], [59, 72, 87], [19, 62, 74],
+ [63, 87, 67], [17, 85, 83], [52, 75, 1], [39, 87, 49],
+ [22, 51, 40], [55, 1, 66], [29, 49, 64], [30, 40, 69],
+ [13, 64, 75], [82, 69, 87], [7, 66, 51], [90, 85, 1],
+ [59, 69, 72], [70, 81, 71], [88, 1, 84], [73, 72, 83],
+ [54, 71, 68], [5, 83, 85], [50, 68, 69], [3, 84, 81],
+ [57, 66, 1], [30, 68, 40], [28, 62, 48], [52, 1, 74],
+ [23, 40, 51], [38, 48, 86], [9, 51, 66], [80, 86, 68],
+ [11, 74, 62], [55, 84, 1], [54, 86, 71], [35, 64, 49],
+ [90, 1, 75], [41, 71, 81], [39, 49, 67], [15, 81, 84],
+ [61, 67, 86], [21, 75, 64], [24, 53, 43], [50, 69, 0],
+ [37, 85, 47], [31, 43, 75], [61, 0, 67], [27, 47, 58],
+ [10, 67, 53], [8, 58, 69], [90, 75, 85], [45, 91, 78],
+ [80, 68, 0], [36, 66, 46], [65, 78, 85], [63, 0, 87],
+ [32, 46, 56], [20, 87, 91], [14, 56, 68], [57, 85, 66],
+ [33, 58, 47], [61, 86, 0], [60, 84, 77], [37, 47, 66],
+ [82, 0, 69], [44, 77, 89], [16, 69, 58], [18, 89, 86],
+ [55, 66, 84], [26, 56, 46], [63, 67, 0], [31, 74, 43],
+ [36, 46, 84], [50, 0, 68], [25, 43, 53], [6, 68, 56],
+ [12, 53, 67], [88, 84, 74], [76, 89, 77], [82, 87, 0],
+ [65, 75, 78], [60, 77, 74], [80, 0, 86], [79, 78, 91],
+ [2, 86, 89], [4, 91, 87], [52, 74, 75], [21, 64, 78],
+ [18, 86, 48], [23, 58, 40], [5, 78, 83], [28, 48, 53],
+ [6, 40, 68], [25, 53, 64], [54, 68, 86], [33, 83, 58],
+ [17, 83, 47], [12, 67, 49], [41, 56, 71], [9, 47, 51],
+ [35, 49, 91], [2, 71, 86], [79, 91, 83], [38, 86, 67],
+ [26, 51, 56], [7, 51, 46], [4, 87, 72], [34, 89, 48],
+ [15, 46, 81], [42, 72, 58], [10, 48, 67], [27, 58, 51],
+ [39, 67, 87], [76, 81, 89], [3, 81, 77], [8, 69, 40],
+ [29, 53, 49], [19, 77, 62], [22, 40, 56], [20, 49, 87],
+ [32, 56, 81], [59, 87, 69], [24, 62, 53], [11, 62, 43],
+ [14, 68, 71], [73, 91, 72], [13, 43, 64], [70, 71, 89],
+ [16, 72, 69], [44, 89, 62], [30, 69, 68], [45, 64, 91]]
+ # Actual volume is : 51.405764746872634
+ assert Abs(polytope_integrate(echidnahedron, 1) - 51.4057647468726) < 1e-12
+ assert Abs(polytope_integrate(echidnahedron, expr) - 253.569603474519) <\
+ 1e-12
+
+ # Tests for many polynomials with maximum degree given(2D case).
+ assert polytope_integrate(cube2, [x**2, y*z], max_degree=2) == \
+ {y * z: 3125 / S(4), x ** 2: 3125 / S(3)}
+
+ assert polytope_integrate(cube2, max_degree=2) == \
+ {1: 125, x: 625 / S(2), x * z: 3125 / S(4), y: 625 / S(2),
+ y * z: 3125 / S(4), z ** 2: 3125 / S(3), y ** 2: 3125 / S(3),
+ z: 625 / S(2), x * y: 3125 / S(4), x ** 2: 3125 / S(3)}
+
+def test_point_sort():
+ assert point_sort([Point(0, 0), Point(1, 0), Point(1, 1)]) == \
+ [Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)]
+
+ fig6 = Polygon((0, 0), (1, 0), (1, 1))
+ assert polytope_integrate(fig6, x*y) == Rational(-1, 8)
+ assert polytope_integrate(fig6, x*y, clockwise = True) == Rational(1, 8)
+
+
+def test_polytopes_intersecting_sides():
+ fig5 = Polygon(Point(-4.165, -0.832), Point(-3.668, 1.568),
+ Point(-3.266, 1.279), Point(-1.090, -2.080),
+ Point(3.313, -0.683), Point(3.033, -4.845),
+ Point(-4.395, 4.840), Point(-1.007, -3.328))
+ assert polytope_integrate(fig5, x**2 + x*y + y**2) ==\
+ S(1633405224899363)/(24*10**12)
+
+ fig6 = Polygon(Point(-3.018, -4.473), Point(-0.103, 2.378),
+ Point(-1.605, -2.308), Point(4.516, -0.771),
+ Point(4.203, 0.478))
+ assert polytope_integrate(fig6, x**2 + x*y + y**2) ==\
+ S(88161333955921)/(3*10**12)
+
+
+def test_max_degree():
+ polygon = Polygon((0, 0), (0, 1), (1, 1), (1, 0))
+ polys = [1, x, y, x*y, x**2*y, x*y**2]
+ assert polytope_integrate(polygon, polys, max_degree=3) == \
+ {1: 1, x: S.Half, y: S.Half, x*y: Rational(1, 4), x**2*y: Rational(1, 6), x*y**2: Rational(1, 6)}
+ assert polytope_integrate(polygon, polys, max_degree=2) == \
+ {1: 1, x: S.Half, y: S.Half, x*y: Rational(1, 4)}
+ assert polytope_integrate(polygon, polys, max_degree=1) == \
+ {1: 1, x: S.Half, y: S.Half}
+
+
+def test_main_integrate3d():
+ cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\
+ (5, 0, 5), (5, 5, 0), (5, 5, 5)],\
+ [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\
+ [3, 1, 0, 2], [0, 4, 6, 2]]
+ vertices = cube[0]
+ faces = cube[1:]
+ hp_params = hyperplane_parameters(faces, vertices)
+ assert main_integrate3d(1, faces, vertices, hp_params) == -125
+ assert main_integrate3d(1, faces, vertices, hp_params, max_degree=1) == \
+ {1: -125, y: Rational(-625, 2), z: Rational(-625, 2), x: Rational(-625, 2)}
+
+
+def test_main_integrate():
+ triangle = Polygon((0, 3), (5, 3), (1, 1))
+ facets = triangle.sides
+ hp_params = hyperplane_parameters(triangle)
+ assert main_integrate(x**2 + y**2, facets, hp_params) == Rational(325, 6)
+ assert main_integrate(x**2 + y**2, facets, hp_params, max_degree=1) == \
+ {0: 0, 1: 5, y: Rational(35, 3), x: 10}
+
+
+def test_polygon_integrate():
+ cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\
+ (5, 0, 5), (5, 5, 0), (5, 5, 5)],\
+ [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\
+ [3, 1, 0, 2], [0, 4, 6, 2]]
+ facet = cube[1]
+ facets = cube[1:]
+ vertices = cube[0]
+ assert polygon_integrate(facet, [(0, 1, 0), 5], 0, facets, vertices, 1, 0) == -25
+
+
+def test_distance_to_side():
+ point = (0, 0, 0)
+ assert distance_to_side(point, [(0, 0, 1), (0, 1, 0)], (1, 0, 0)) == -sqrt(2)/2
+
+
+def test_lineseg_integrate():
+ polygon = [(0, 5, 0), (5, 5, 0), (5, 5, 5), (0, 5, 5)]
+ line_seg = [(0, 5, 0), (5, 5, 0)]
+ assert lineseg_integrate(polygon, 0, line_seg, 1, 0) == 5
+ assert lineseg_integrate(polygon, 0, line_seg, 0, 0) == 0
+
+
+def test_integration_reduction():
+ triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1))
+ facets = triangle.sides
+ a, b = hyperplane_parameters(triangle)[0]
+ assert integration_reduction(facets, 0, a, b, 1, (x, y), 0) == 5
+ assert integration_reduction(facets, 0, a, b, 0, (x, y), 0) == 0
+
+
+def test_integration_reduction_dynamic():
+ triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1))
+ facets = triangle.sides
+ a, b = hyperplane_parameters(triangle)[0]
+ x0 = facets[0].points[0]
+ monomial_values = [[0, 0, 0, 0], [1, 0, 0, 5],\
+ [y, 0, 1, 15], [x, 1, 0, None]]
+
+ assert integration_reduction_dynamic(facets, 0, a, b, x, 1, (x, y), 1,\
+ 0, 1, x0, monomial_values, 3) == Rational(25, 2)
+ assert integration_reduction_dynamic(facets, 0, a, b, 0, 1, (x, y), 1,\
+ 0, 1, x0, monomial_values, 3) == 0
+
+
+def test_is_vertex():
+ assert is_vertex(2) is False
+ assert is_vertex((2, 3)) is True
+ assert is_vertex(Point(2, 3)) is True
+ assert is_vertex((2, 3, 4)) is True
+ assert is_vertex((2, 3, 4, 5)) is False
+
+
+def test_issue_19234():
+ polygon = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0))
+ polys = [ 1, x, y, x*y, x**2*y, x*y**2]
+ assert polytope_integrate(polygon, polys) == \
+ {1: 1, x: S.Half, y: S.Half, x*y: Rational(1, 4), x**2*y: Rational(1, 6), x*y**2: Rational(1, 6)}
+ polys = [ 1, x, y, x*y, 3 + x**2*y, x + x*y**2]
+ assert polytope_integrate(polygon, polys) == \
+ {1: 1, x: S.Half, y: S.Half, x*y: Rational(1, 4), x**2*y + 3: Rational(19, 6), x*y**2 + x: Rational(2, 3)}
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_laplace.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_laplace.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c42490b91f7999dd78a9c0a105aae1e7f3e47f5
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_laplace.py
@@ -0,0 +1,769 @@
+from sympy.integrals.laplace import (
+ laplace_transform, inverse_laplace_transform,
+ LaplaceTransform, InverseLaplaceTransform,
+ _laplace_deep_collect, laplace_correspondence,
+ laplace_initial_conds)
+from sympy.core.function import Function, expand_mul
+from sympy.core import EulerGamma, Subs, Derivative, diff
+from sympy.core.exprtools import factor_terms
+from sympy.core.numbers import I, oo, pi
+from sympy.core.relational import Eq
+from sympy.core.singleton import S
+from sympy.core.symbol import Symbol, symbols
+from sympy.simplify.simplify import simplify
+from sympy.functions.elementary.complexes import Abs, re
+from sympy.functions.elementary.exponential import exp, log, exp_polar
+from sympy.functions.elementary.hyperbolic import cosh, sinh, coth, asinh
+from sympy.functions.elementary.miscellaneous import sqrt
+from sympy.functions.elementary.piecewise import Piecewise
+from sympy.functions.elementary.trigonometric import atan, cos, sin
+from sympy.logic.boolalg import And
+from sympy.functions.special.gamma_functions import (
+ lowergamma, gamma, uppergamma)
+from sympy.functions.special.delta_functions import DiracDelta, Heaviside
+from sympy.functions.special.singularity_functions import SingularityFunction
+from sympy.functions.special.zeta_functions import lerchphi
+from sympy.functions.special.error_functions import (
+ fresnelc, fresnels, erf, erfc, Ei, Ci, expint, E1)
+from sympy.functions.special.bessel import besseli, besselj, besselk, bessely
+from sympy.testing.pytest import slow, warns_deprecated_sympy
+from sympy.matrices import Matrix, eye
+from sympy.abc import s
+
+
+@slow
+def test_laplace_transform():
+ LT = laplace_transform
+ ILT = inverse_laplace_transform
+ a, b, c = symbols('a, b, c', positive=True)
+ np = symbols('np', integer=True, positive=True)
+ t, w, x = symbols('t, w, x')
+ f = Function('f')
+ F = Function('F')
+ g = Function('g')
+ y = Function('y')
+ Y = Function('Y')
+
+ # Test helper functions
+ assert (
+ _laplace_deep_collect(exp((t+a)*(t+b)) +
+ besselj(2, exp((t+a)*(t+b)-t**2)), t) ==
+ exp(a*b + t**2 + t*(a + b)) + besselj(2, exp(a*b + t*(a + b))))
+ L = laplace_transform(diff(y(t), t, 3), t, s, noconds=True)
+ L = laplace_correspondence(L, {y: Y})
+ L = laplace_initial_conds(L, t, {y: [2, 4, 8, 16, 32]})
+ assert L == s**3*Y(s) - 2*s**2 - 4*s - 8
+ # Test whether `noconds=True` in `doit`:
+ assert (2*LaplaceTransform(exp(t), t, s) - 1).doit() == -1 + 2/(s - 1)
+ assert (LT(a*t+t**2+t**(S(5)/2), t, s) ==
+ (a/s**2 + 2/s**3 + 15*sqrt(pi)/(8*s**(S(7)/2)), 0, True))
+ assert LT(b/(t+a), t, s) == (-b*exp(-a*s)*Ei(-a*s), 0, True)
+ assert (LT(1/sqrt(t+a), t, s) ==
+ (sqrt(pi)*sqrt(1/s)*exp(a*s)*erfc(sqrt(a)*sqrt(s)), 0, True))
+ assert (LT(sqrt(t)/(t+a), t, s) ==
+ (-pi*sqrt(a)*exp(a*s)*erfc(sqrt(a)*sqrt(s)) + sqrt(pi)*sqrt(1/s),
+ 0, True))
+ assert (LT((t+a)**(-S(3)/2), t, s) ==
+ (-2*sqrt(pi)*sqrt(s)*exp(a*s)*erfc(sqrt(a)*sqrt(s)) + 2/sqrt(a),
+ 0, True))
+ assert (LT(t**(S(1)/2)*(t+a)**(-1), t, s) ==
+ (-pi*sqrt(a)*exp(a*s)*erfc(sqrt(a)*sqrt(s)) + sqrt(pi)*sqrt(1/s),
+ 0, True))
+ assert (LT(1/(a*sqrt(t) + t**(3/2)), t, s) ==
+ (pi*sqrt(a)*exp(a*s)*erfc(sqrt(a)*sqrt(s)), 0, True))
+ assert (LT((t+a)**b, t, s) ==
+ (s**(-b - 1)*exp(-a*s)*uppergamma(b + 1, a*s), 0, True))
+ assert LT(t**5/(t+a), t, s) == (120*a**5*uppergamma(-5, a*s), 0, True)
+ assert LT(exp(t), t, s) == (1/(s - 1), 1, True)
+ assert LT(exp(2*t), t, s) == (1/(s - 2), 2, True)
+ assert LT(exp(a*t), t, s) == (1/(s - a), a, True)
+ assert LT(exp(a*(t-b)), t, s) == (exp(-a*b)/(-a + s), a, True)
+ assert LT(t*exp(-a*(t)), t, s) == ((a + s)**(-2), -a, True)
+ assert LT(t*exp(-a*(t-b)), t, s) == (exp(a*b)/(a + s)**2, -a, True)
+ assert LT(b*t*exp(-a*t), t, s) == (b/(a + s)**2, -a, True)
+ assert LT(exp(-a*exp(-t)), t, s) == (lowergamma(s, a)/a**s, 0, True)
+ assert LT(exp(-a*exp(t)), t, s) == (a**s*uppergamma(-s, a), 0, True)
+ assert (LT(t**(S(7)/4)*exp(-8*t)/gamma(S(11)/4), t, s) ==
+ ((s + 8)**(-S(11)/4), -8, True))
+ assert (LT(t**(S(3)/2)*exp(-8*t), t, s) ==
+ (3*sqrt(pi)/(4*(s + 8)**(S(5)/2)), -8, True))
+ assert LT(t**a*exp(-a*t), t, s) == ((a+s)**(-a-1)*gamma(a+1), -a, True)
+ assert (LT(b*exp(-a*t**2), t, s) ==
+ (sqrt(pi)*b*exp(s**2/(4*a))*erfc(s/(2*sqrt(a)))/(2*sqrt(a)),
+ 0, True))
+ assert (LT(exp(-2*t**2), t, s) ==
+ (sqrt(2)*sqrt(pi)*exp(s**2/8)*erfc(sqrt(2)*s/4)/4, 0, True))
+ assert (LT(b*exp(2*t**2), t, s) ==
+ (b*LaplaceTransform(exp(2*t**2), t, s), -oo, True))
+ assert (LT(t*exp(-a*t**2), t, s) ==
+ (1/(2*a) - s*erfc(s/(2*sqrt(a)))/(4*sqrt(pi)*a**(S(3)/2)),
+ 0, True))
+ assert (LT(exp(-a/t), t, s) ==
+ (2*sqrt(a)*sqrt(1/s)*besselk(1, 2*sqrt(a)*sqrt(s)), 0, True))
+ assert LT(sqrt(t)*exp(-a/t), t, s, simplify=True) == (
+ sqrt(pi)*(sqrt(a)*sqrt(s) + 1/S(2))*sqrt(s**(-3)) *
+ exp(-2*sqrt(a)*sqrt(s)), 0, True)
+ assert (LT(exp(-a/t)/sqrt(t), t, s) ==
+ (sqrt(pi)*sqrt(1/s)*exp(-2*sqrt(a)*sqrt(s)), 0, True))
+ assert (LT(exp(-a/t)/(t*sqrt(t)), t, s) ==
+ (sqrt(pi)*sqrt(1/a)*exp(-2*sqrt(a)*sqrt(s)), 0, True))
+ assert (
+ LT(exp(-2*sqrt(a*t)), t, s) ==
+ (1/s - sqrt(pi)*sqrt(a) * exp(a/s)*erfc(sqrt(a)*sqrt(1/s)) /
+ s**(S(3)/2), 0, True))
+ assert LT(exp(-2*sqrt(a*t))/sqrt(t), t, s) == (
+ exp(a/s)*erfc(sqrt(a) * sqrt(1/s))*(sqrt(pi)*sqrt(1/s)), 0, True)
+ assert (LT(t**4*exp(-2/t), t, s) ==
+ (8*sqrt(2)*(1/s)**(S(5)/2)*besselk(5, 2*sqrt(2)*sqrt(s)),
+ 0, True))
+ assert LT(sinh(a*t), t, s) == (a/(-a**2 + s**2), a, True)
+ assert (LT(b*sinh(a*t)**2, t, s) ==
+ (2*a**2*b/(-4*a**2*s + s**3), 2*a, True))
+ assert (LT(b*sinh(a*t)**2, t, s, simplify=True) ==
+ (2*a**2*b/(s*(-4*a**2 + s**2)), 2*a, True))
+ # The following line confirms that issue #21202 is solved
+ assert LT(cosh(2*t), t, s) == (s/(-4 + s**2), 2, True)
+ assert LT(cosh(a*t), t, s) == (s/(-a**2 + s**2), a, True)
+ assert (LT(cosh(a*t)**2, t, s, simplify=True) ==
+ ((2*a**2 - s**2)/(s*(4*a**2 - s**2)), 2*a, True))
+ assert (LT(sinh(x+3), x, s, simplify=True) ==
+ ((s*sinh(3) + cosh(3))/(s**2 - 1), 1, True))
+ L, _, _ = LT(42*sin(w*t+x)**2, t, s)
+ assert (
+ L -
+ 21*(s**2 + s*(-s*cos(2*x) + 2*w*sin(2*x)) +
+ 4*w**2)/(s*(s**2 + 4*w**2))).simplify() == 0
+ # The following line replaces the old test test_issue_7173()
+ assert LT(sinh(a*t)*cosh(a*t), t, s, simplify=True) == (a/(-4*a**2 + s**2),
+ 2*a, True)
+ assert LT(sinh(a*t)/t, t, s) == (log((a + s)/(-a + s))/2, a, True)
+ assert (LT(t**(-S(3)/2)*sinh(a*t), t, s) ==
+ (-sqrt(pi)*(sqrt(-a + s) - sqrt(a + s)), a, True))
+ assert (LT(sinh(2*sqrt(a*t)), t, s) ==
+ (sqrt(pi)*sqrt(a)*exp(a/s)/s**(S(3)/2), 0, True))
+ assert (LT(sqrt(t)*sinh(2*sqrt(a*t)), t, s, simplify=True) ==
+ ((-sqrt(a)*s**(S(5)/2) + sqrt(pi)*s**2*(2*a + s)*exp(a/s) *
+ erf(sqrt(a)*sqrt(1/s))/2)/s**(S(9)/2), 0, True))
+ assert (LT(sinh(2*sqrt(a*t))/sqrt(t), t, s) ==
+ (sqrt(pi)*exp(a/s)*erf(sqrt(a)*sqrt(1/s))/sqrt(s), 0, True))
+ assert (LT(sinh(sqrt(a*t))**2/sqrt(t), t, s) ==
+ (sqrt(pi)*(exp(a/s) - 1)/(2*sqrt(s)), 0, True))
+ assert (LT(t**(S(3)/7)*cosh(a*t), t, s) ==
+ (((a + s)**(-S(10)/7) + (-a+s)**(-S(10)/7))*gamma(S(10)/7)/2,
+ a, True))
+ assert (LT(cosh(2*sqrt(a*t)), t, s) ==
+ (sqrt(pi)*sqrt(a)*exp(a/s)*erf(sqrt(a)*sqrt(1/s))/s**(S(3)/2) +
+ 1/s, 0, True))
+ assert (LT(sqrt(t)*cosh(2*sqrt(a*t)), t, s) ==
+ (sqrt(pi)*(a + s/2)*exp(a/s)/s**(S(5)/2), 0, True))
+ assert (LT(cosh(2*sqrt(a*t))/sqrt(t), t, s) ==
+ (sqrt(pi)*exp(a/s)/sqrt(s), 0, True))
+ assert (LT(cosh(sqrt(a*t))**2/sqrt(t), t, s) ==
+ (sqrt(pi)*(exp(a/s) + 1)/(2*sqrt(s)), 0, True))
+ assert LT(log(t), t, s, simplify=True) == (
+ (-log(s) - EulerGamma)/s, 0, True)
+ assert (LT(-log(t/a), t, s, simplify=True) ==
+ ((log(a) + log(s) + EulerGamma)/s, 0, True))
+ assert LT(log(1+a*t), t, s) == (-exp(s/a)*Ei(-s/a)/s, 0, True)
+ assert (LT(log(t+a), t, s, simplify=True) ==
+ ((s*log(a) - exp(s/a)*Ei(-s/a))/s**2, 0, True))
+ assert (LT(log(t)/sqrt(t), t, s, simplify=True) ==
+ (sqrt(pi)*(-log(s) - log(4) - EulerGamma)/sqrt(s), 0, True))
+ assert (LT(t**(S(5)/2)*log(t), t, s, simplify=True) ==
+ (sqrt(pi)*(-15*log(s) - log(1073741824) - 15*EulerGamma + 46) /
+ (8*s**(S(7)/2)), 0, True))
+ assert (LT(t**3*log(t), t, s, noconds=True, simplify=True) -
+ 6*(-log(s) - S.EulerGamma + S(11)/6)/s**4).simplify() == S.Zero
+ assert (LT(log(t)**2, t, s, simplify=True) ==
+ (((log(s) + EulerGamma)**2 + pi**2/6)/s, 0, True))
+ assert (LT(exp(-a*t)*log(t), t, s, simplify=True) ==
+ ((-log(a + s) - EulerGamma)/(a + s), -a, True))
+ assert LT(sin(a*t), t, s) == (a/(a**2 + s**2), 0, True)
+ assert (LT(Abs(sin(a*t)), t, s) ==
+ (a*coth(pi*s/(2*a))/(a**2 + s**2), 0, True))
+ assert LT(sin(a*t)/t, t, s) == (atan(a/s), 0, True)
+ assert LT(sin(a*t)**2/t, t, s) == (log(4*a**2/s**2 + 1)/4, 0, True)
+ assert (LT(sin(a*t)**2/t**2, t, s) ==
+ (a*atan(2*a/s) - s*log(4*a**2/s**2 + 1)/4, 0, True))
+ assert (LT(sin(2*sqrt(a*t)), t, s) ==
+ (sqrt(pi)*sqrt(a)*exp(-a/s)/s**(S(3)/2), 0, True))
+ assert LT(sin(2*sqrt(a*t))/t, t, s) == (pi*erf(sqrt(a)*sqrt(1/s)), 0, True)
+ assert LT(cos(a*t), t, s) == (s/(a**2 + s**2), 0, True)
+ assert (LT(cos(a*t)**2, t, s) ==
+ ((2*a**2 + s**2)/(s*(4*a**2 + s**2)), 0, True))
+ assert (LT(sqrt(t)*cos(2*sqrt(a*t)), t, s, simplify=True) ==
+ (sqrt(pi)*(-a + s/2)*exp(-a/s)/s**(S(5)/2), 0, True))
+ assert (LT(cos(2*sqrt(a*t))/sqrt(t), t, s) ==
+ (sqrt(pi)*sqrt(1/s)*exp(-a/s), 0, True))
+ assert (LT(sin(a*t)*sin(b*t), t, s) ==
+ (2*a*b*s/((s**2 + (a - b)**2)*(s**2 + (a + b)**2)), 0, True))
+ assert (LT(cos(a*t)*sin(b*t), t, s) ==
+ (b*(-a**2 + b**2 + s**2)/((s**2 + (a - b)**2)*(s**2 + (a + b)**2)),
+ 0, True))
+ assert (LT(cos(a*t)*cos(b*t), t, s) ==
+ (s*(a**2 + b**2 + s**2)/((s**2 + (a - b)**2)*(s**2 + (a + b)**2)),
+ 0, True))
+ assert (LT(-a*t*cos(a*t) + sin(a*t), t, s, simplify=True) ==
+ (2*a**3/(a**4 + 2*a**2*s**2 + s**4), 0, True))
+ assert LT(c*exp(-b*t)*sin(a*t), t, s) == (a *
+ c/(a**2 + (b + s)**2), -b, True)
+ assert LT(c*exp(-b*t)*cos(a*t), t, s) == (c*(b + s)/(a**2 + (b + s)**2),
+ -b, True)
+ L, plane, cond = LT(cos(x + 3), x, s, simplify=True)
+ assert plane == 0
+ assert L - (s*cos(3) - sin(3))/(s**2 + 1) == 0
+ # Error functions (laplace7.pdf)
+ assert LT(erf(a*t), t, s) == (exp(s**2/(4*a**2))*erfc(s/(2*a))/s, 0, True)
+ assert LT(erf(sqrt(a*t)), t, s) == (sqrt(a)/(s*sqrt(a + s)), 0, True)
+ assert (LT(exp(a*t)*erf(sqrt(a*t)), t, s, simplify=True) ==
+ (-sqrt(a)/(sqrt(s)*(a - s)), a, True))
+ assert (LT(erf(sqrt(a/t)/2), t, s, simplify=True) ==
+ (1/s - exp(-sqrt(a)*sqrt(s))/s, 0, True))
+ assert (LT(erfc(sqrt(a*t)), t, s, simplify=True) ==
+ (-sqrt(a)/(s*sqrt(a + s)) + 1/s, -a, True))
+ assert (LT(exp(a*t)*erfc(sqrt(a*t)), t, s) ==
+ (1/(sqrt(a)*sqrt(s) + s), 0, True))
+ assert LT(erfc(sqrt(a/t)/2), t, s) == (exp(-sqrt(a)*sqrt(s))/s, 0, True)
+ # Bessel functions (laplace8.pdf)
+ assert LT(besselj(0, a*t), t, s) == (1/sqrt(a**2 + s**2), 0, True)
+ assert (LT(besselj(1, a*t), t, s, simplify=True) ==
+ (a/(a**2 + s**2 + s*sqrt(a**2 + s**2)), 0, True))
+ assert (LT(besselj(2, a*t), t, s, simplify=True) ==
+ (a**2/(sqrt(a**2 + s**2)*(s + sqrt(a**2 + s**2))**2), 0, True))
+ assert (LT(t*besselj(0, a*t), t, s) ==
+ (s/(a**2 + s**2)**(S(3)/2), 0, True))
+ assert (LT(t*besselj(1, a*t), t, s) ==
+ (a/(a**2 + s**2)**(S(3)/2), 0, True))
+ assert (LT(t**2*besselj(2, a*t), t, s) ==
+ (3*a**2/(a**2 + s**2)**(S(5)/2), 0, True))
+ assert LT(besselj(0, 2*sqrt(a*t)), t, s) == (exp(-a/s)/s, 0, True)
+ assert (LT(t**(S(3)/2)*besselj(3, 2*sqrt(a*t)), t, s) ==
+ (a**(S(3)/2)*exp(-a/s)/s**4, 0, True))
+ assert (LT(besselj(0, a*sqrt(t**2+b*t)), t, s, simplify=True) ==
+ (exp(b*(s - sqrt(a**2 + s**2)))/sqrt(a**2 + s**2), 0, True))
+ assert LT(besseli(0, a*t), t, s) == (1/sqrt(-a**2 + s**2), a, True)
+ assert (LT(besseli(1, a*t), t, s, simplify=True) ==
+ (a/(-a**2 + s**2 + s*sqrt(-a**2 + s**2)), a, True))
+ assert (LT(besseli(2, a*t), t, s, simplify=True) ==
+ (a**2/(sqrt(-a**2 + s**2)*(s + sqrt(-a**2 + s**2))**2), a, True))
+ assert LT(t*besseli(0, a*t), t, s) == (s/(-a**2 + s**2)**(S(3)/2), a, True)
+ assert LT(t*besseli(1, a*t), t, s) == (a/(-a**2 + s**2)**(S(3)/2), a, True)
+ assert (LT(t**2*besseli(2, a*t), t, s) ==
+ (3*a**2/(-a**2 + s**2)**(S(5)/2), a, True))
+ assert (LT(t**(S(3)/2)*besseli(3, 2*sqrt(a*t)), t, s) ==
+ (a**(S(3)/2)*exp(a/s)/s**4, 0, True))
+ assert (LT(bessely(0, a*t), t, s) ==
+ (-2*asinh(s/a)/(pi*sqrt(a**2 + s**2)), 0, True))
+ assert (LT(besselk(0, a*t), t, s) ==
+ (log((s + sqrt(-a**2 + s**2))/a)/sqrt(-a**2 + s**2), -a, True))
+ assert (LT(sin(a*t)**4, t, s, simplify=True) ==
+ (24*a**4/(s*(64*a**4 + 20*a**2*s**2 + s**4)), 0, True))
+ # Test general rules and unevaluated forms
+ # These all also test whether issue #7219 is solved.
+ assert LT(Heaviside(t-1)*cos(t-1), t, s) == (s*exp(-s)/(s**2 + 1), 0, True)
+ assert LT(a*f(t), t, w) == (a*LaplaceTransform(f(t), t, w), -oo, True)
+ assert (LT(a*Heaviside(t+1)*f(t+1), t, s) ==
+ (a*LaplaceTransform(f(t + 1), t, s), -oo, True))
+ assert (LT(a*Heaviside(t-1)*f(t-1), t, s) ==
+ (a*LaplaceTransform(f(t), t, s)*exp(-s), -oo, True))
+ assert (LT(b*f(t/a), t, s) ==
+ (a*b*LaplaceTransform(f(t), t, a*s), -oo, True))
+ assert LT(exp(-f(x)*t), t, s) == (1/(s + f(x)), -re(f(x)), True)
+ assert (LT(exp(-a*t)*f(t), t, s) ==
+ (LaplaceTransform(f(t), t, a + s), -oo, True))
+ assert (LT(exp(-a*t)*erfc(sqrt(b/t)/2), t, s) ==
+ (exp(-sqrt(b)*sqrt(a + s))/(a + s), -a, True))
+ assert (LT(sinh(a*t)*f(t), t, s) ==
+ (LaplaceTransform(f(t), t, -a + s)/2 -
+ LaplaceTransform(f(t), t, a + s)/2, -oo, True))
+ assert (LT(sinh(a*t)*t, t, s, simplify=True) ==
+ (2*a*s/(a**4 - 2*a**2*s**2 + s**4), a, True))
+ assert (LT(cosh(a*t)*f(t), t, s) ==
+ (LaplaceTransform(f(t), t, -a + s)/2 +
+ LaplaceTransform(f(t), t, a + s)/2, -oo, True))
+ assert (LT(cosh(a*t)*t, t, s, simplify=True) ==
+ (1/(2*(a + s)**2) + 1/(2*(a - s)**2), a, True))
+ assert (LT(sin(a*t)*f(t), t, s, simplify=True) ==
+ (I*(-LaplaceTransform(f(t), t, -I*a + s) +
+ LaplaceTransform(f(t), t, I*a + s))/2, -oo, True))
+ assert (LT(sin(f(t)), t, s) ==
+ (LaplaceTransform(sin(f(t)), t, s), -oo, True))
+ assert (LT(sin(a*t)*t, t, s, simplify=True) ==
+ (2*a*s/(a**4 + 2*a**2*s**2 + s**4), 0, True))
+ assert (LT(cos(a*t)*f(t), t, s) ==
+ (LaplaceTransform(f(t), t, -I*a + s)/2 +
+ LaplaceTransform(f(t), t, I*a + s)/2, -oo, True))
+ assert (LT(cos(a*t)*t, t, s, simplify=True) ==
+ ((-a**2 + s**2)/(a**4 + 2*a**2*s**2 + s**4), 0, True))
+ L, plane, _ = LT(sin(a*t+b)**2*f(t), t, s)
+ assert plane == -oo
+ assert (
+ -L + (
+ LaplaceTransform(f(t), t, s)/2 -
+ LaplaceTransform(f(t), t, -2*I*a + s)*exp(2*I*b)/4 -
+ LaplaceTransform(f(t), t, 2*I*a + s)*exp(-2*I*b)/4)) == 0
+ L = LT(sin(a*t+b)**2*f(t), t, s, noconds=True)
+ assert (
+ laplace_correspondence(L, {f: F}) ==
+ F(s)/2 - F(-2*I*a + s)*exp(2*I*b)/4 -
+ F(2*I*a + s)*exp(-2*I*b)/4)
+ L, plane, _ = LT(sin(a*t)**3*cosh(b*t), t, s)
+ assert plane == b
+ assert (
+ -L - 3*a/(8*(9*a**2 + b**2 + 2*b*s + s**2)) -
+ 3*a/(8*(9*a**2 + b**2 - 2*b*s + s**2)) +
+ 3*a/(8*(a**2 + b**2 + 2*b*s + s**2)) +
+ 3*a/(8*(a**2 + b**2 - 2*b*s + s**2))).simplify() == 0
+ assert (LT(t**2*exp(-t**2), t, s) ==
+ (sqrt(pi)*s**2*exp(s**2/4)*erfc(s/2)/8 - s/4 +
+ sqrt(pi)*exp(s**2/4)*erfc(s/2)/4, 0, True))
+ assert (LT((a*t**2 + b*t + c)*f(t), t, s) ==
+ (a*Derivative(LaplaceTransform(f(t), t, s), (s, 2)) -
+ b*Derivative(LaplaceTransform(f(t), t, s), s) +
+ c*LaplaceTransform(f(t), t, s), -oo, True))
+ assert (LT(t**np*g(t), t, s) ==
+ ((-1)**np*Derivative(LaplaceTransform(g(t), t, s), (s, np)),
+ -oo, True))
+ # The following tests check whether _piecewise_to_heaviside works:
+ x1 = Piecewise((0, t <= 0), (1, t <= 1), (0, True))
+ X1 = LT(x1, t, s)[0]
+ assert X1 == 1/s - exp(-s)/s
+ y1 = ILT(X1, s, t)
+ assert y1 == Heaviside(t) - Heaviside(t - 1)
+ x1 = Piecewise((0, t <= 0), (t, t <= 1), (2-t, t <= 2), (0, True))
+ X1 = LT(x1, t, s)[0].simplify()
+ assert X1 == (exp(2*s) - 2*exp(s) + 1)*exp(-2*s)/s**2
+ y1 = ILT(X1, s, t)
+ assert (
+ -y1 + t*Heaviside(t) + (t - 2)*Heaviside(t - 2) -
+ 2*(t - 1)*Heaviside(t - 1)).simplify() == 0
+ x1 = Piecewise((exp(t), t <= 0), (1, t <= 1), (exp(-(t)), True))
+ X1 = LT(x1, t, s)[0]
+ assert X1 == exp(-1)*exp(-s)/(s + 1) + 1/s - exp(-s)/s
+ y1 = ILT(X1, s, t)
+ assert y1 == (
+ exp(-1)*exp(1 - t)*Heaviside(t - 1) + Heaviside(t) - Heaviside(t - 1))
+ x1 = Piecewise((0, x <= 0), (1, x <= 1), (0, True))
+ X1 = LT(x1, t, s)[0]
+ assert X1 == Piecewise((0, x <= 0), (1, x <= 1), (0, True))/s
+ x1 = [
+ a*Piecewise((1, And(t > 1, t <= 3)), (2, True)),
+ a*Piecewise((1, And(t >= 1, t <= 3)), (2, True)),
+ a*Piecewise((1, And(t >= 1, t < 3)), (2, True)),
+ a*Piecewise((1, And(t > 1, t < 3)), (2, True))]
+ for x2 in x1:
+ assert LT(x2, t, s)[0].expand() == 2*a/s - a*exp(-s)/s + a*exp(-3*s)/s
+ assert (
+ LT(Piecewise((1, Eq(t, 1)), (2, True)), t, s)[0] ==
+ LaplaceTransform(Piecewise((1, Eq(t, 1)), (2, True)), t, s))
+ # The following lines test whether _laplace_transform successfully
+ # removes Heaviside(1) before processing espressions. It fails if
+ # Heaviside(t) remains because then meijerg functions will appear.
+ X1 = 1/sqrt(a*s**2-b)
+ x1 = ILT(X1, s, t)
+ Y1 = LT(x1, t, s)[0]
+ Z1 = (Y1**2/X1**2).simplify()
+ assert Z1 == 1
+ # The following two lines test whether issues #5813 and #7176 are solved.
+ assert (LT(diff(f(t), (t, 1)), t, s, noconds=True) ==
+ s*LaplaceTransform(f(t), t, s) - f(0))
+ assert (LT(diff(f(t), (t, 3)), t, s, noconds=True) ==
+ s**3*LaplaceTransform(f(t), t, s) - s**2*f(0) -
+ s*Subs(Derivative(f(t), t), t, 0) -
+ Subs(Derivative(f(t), (t, 2)), t, 0))
+ # Issue #7219
+ assert (LT(diff(f(x, t, w), t, 2), t, s) ==
+ (s**2*LaplaceTransform(f(x, t, w), t, s) - s*f(x, 0, w) -
+ Subs(Derivative(f(x, t, w), t), t, 0), -oo, True))
+ # Issue #23307
+ assert (LT(10*diff(f(t), (t, 1)), t, s, noconds=True) ==
+ 10*s*LaplaceTransform(f(t), t, s) - 10*f(0))
+ assert (LT(a*f(b*t)+g(c*t), t, s, noconds=True) ==
+ a*LaplaceTransform(f(t), t, s/b)/b +
+ LaplaceTransform(g(t), t, s/c)/c)
+ assert inverse_laplace_transform(
+ f(w), w, t, plane=0) == InverseLaplaceTransform(f(w), w, t, 0)
+ assert (LT(f(t)*g(t), t, s, noconds=True) ==
+ LaplaceTransform(f(t)*g(t), t, s))
+ # Issue #24294
+ assert (LT(b*f(a*t), t, s, noconds=True) ==
+ b*LaplaceTransform(f(t), t, s/a)/a)
+ assert LT(3*exp(t)*Heaviside(t), t, s) == (3/(s - 1), 1, True)
+ assert (LT(2*sin(t)*Heaviside(t), t, s, simplify=True) ==
+ (2/(s**2 + 1), 0, True))
+ # Issue #25293
+ assert (
+ LT((1/(t-1))*sin(4*pi*(t-1))*DiracDelta(t-1) *
+ (Heaviside(t-1/4) - Heaviside(t-2)), t, s)[0] == 4*pi*exp(-s))
+ # additional basic tests from wikipedia
+ assert (LT((t - a)**b*exp(-c*(t - a))*Heaviside(t - a), t, s) ==
+ ((c + s)**(-b - 1)*exp(-a*s)*gamma(b + 1), -c, True))
+ assert (
+ LT((exp(2*t)-1)*exp(-b-t)*Heaviside(t)/2, t, s, noconds=True,
+ simplify=True) ==
+ exp(-b)/(s**2 - 1))
+ # DiracDelta function: standard cases
+ assert LT(DiracDelta(t), t, s) == (1, -oo, True)
+ assert LT(DiracDelta(a*t), t, s) == (1/a, -oo, True)
+ assert LT(DiracDelta(t/42), t, s) == (42, -oo, True)
+ assert LT(DiracDelta(t+42), t, s) == (0, -oo, True)
+ assert (LT(DiracDelta(t)+DiracDelta(t-42), t, s) ==
+ (1 + exp(-42*s), -oo, True))
+ assert (LT(DiracDelta(t)-a*exp(-a*t), t, s, simplify=True) ==
+ (s/(a + s), -a, True))
+ assert (
+ LT(exp(-t)*(DiracDelta(t)+DiracDelta(t-42)), t, s, simplify=True) ==
+ (exp(-42*s - 42) + 1, -oo, True))
+ assert LT(f(t)*DiracDelta(t-42), t, s) == (f(42)*exp(-42*s), -oo, True)
+ assert LT(f(t)*DiracDelta(b*t-a), t, s) == (f(a/b)*exp(-a*s/b)/b,
+ -oo, True)
+ assert LT(f(t)*DiracDelta(b*t+a), t, s) == (0, -oo, True)
+ # SingularityFunction
+ assert LT(SingularityFunction(t, a, -1), t, s)[0] == exp(-a*s)
+ assert LT(SingularityFunction(t, a, 1), t, s)[0] == exp(-a*s)/s**2
+ assert LT(SingularityFunction(t, a, x), t, s)[0] == (
+ LaplaceTransform(SingularityFunction(t, a, x), t, s))
+ # Collection of cases that cannot be fully evaluated and/or would catch
+ # some common implementation errors
+ assert (LT(DiracDelta(t**2), t, s, noconds=True) ==
+ LaplaceTransform(DiracDelta(t**2), t, s))
+ assert LT(DiracDelta(t**2 - 1), t, s) == (exp(-s)/2, -oo, True)
+ assert LT(DiracDelta(t*(1 - t)), t, s) == (1 - exp(-s), -oo, True)
+ assert (LT((DiracDelta(t) + 1)*(DiracDelta(t - 1) + 1), t, s) ==
+ (LaplaceTransform(DiracDelta(t)*DiracDelta(t - 1), t, s) +
+ 1 + exp(-s) + 1/s, 0, True))
+ assert LT(DiracDelta(2*t-2*exp(a)), t, s) == (exp(-s*exp(a))/2, -oo, True)
+ assert LT(DiracDelta(-2*t+2*exp(a)), t, s) == (exp(-s*exp(a))/2, -oo, True)
+ # Heaviside tests
+ assert LT(Heaviside(t), t, s) == (1/s, 0, True)
+ assert LT(Heaviside(t - a), t, s) == (exp(-a*s)/s, 0, True)
+ assert LT(Heaviside(t-1), t, s) == (exp(-s)/s, 0, True)
+ assert LT(Heaviside(2*t-4), t, s) == (exp(-2*s)/s, 0, True)
+ assert LT(Heaviside(2*t+4), t, s) == (1/s, 0, True)
+ assert (LT(Heaviside(-2*t+4), t, s, simplify=True) ==
+ (1/s - exp(-2*s)/s, 0, True))
+ assert (LT(g(t)*Heaviside(t - w), t, s) ==
+ (LaplaceTransform(g(t)*Heaviside(t - w), t, s), -oo, True))
+ assert (
+ LT(Heaviside(t-a)*g(t), t, s) ==
+ (LaplaceTransform(g(a + t), t, s)*exp(-a*s), -oo, True))
+ assert (
+ LT(Heaviside(t+a)*g(t), t, s) ==
+ (LaplaceTransform(g(t), t, s), -oo, True))
+ assert (
+ LT(Heaviside(-t+a)*g(t), t, s) ==
+ (LaplaceTransform(g(t), t, s) -
+ LaplaceTransform(g(a + t), t, s)*exp(-a*s), -oo, True))
+ assert (
+ LT(Heaviside(-t-a)*g(t), t, s) == (0, 0, True))
+ # Fresnel functions
+ assert (laplace_transform(fresnels(t), t, s, simplify=True) ==
+ ((-sin(s**2/(2*pi))*fresnels(s/pi) +
+ sqrt(2)*sin(s**2/(2*pi) + pi/4)/2 -
+ cos(s**2/(2*pi))*fresnelc(s/pi))/s, 0, True))
+ assert (laplace_transform(fresnelc(t), t, s, simplify=True) ==
+ ((sin(s**2/(2*pi))*fresnelc(s/pi) -
+ cos(s**2/(2*pi))*fresnels(s/pi) +
+ sqrt(2)*cos(s**2/(2*pi) + pi/4)/2)/s, 0, True))
+ # Matrix tests
+ Mt = Matrix([[exp(t), t*exp(-t)], [t*exp(-t), exp(t)]])
+ Ms = Matrix([[1/(s - 1), (s + 1)**(-2)],
+ [(s + 1)**(-2), 1/(s - 1)]])
+ # The default behaviour for Laplace transform of a Matrix returns a Matrix
+ # of Tuples and is deprecated:
+ with warns_deprecated_sympy():
+ Ms_conds = Matrix(
+ [[(1/(s - 1), 1, True), ((s + 1)**(-2), -1, True)],
+ [((s + 1)**(-2), -1, True), (1/(s - 1), 1, True)]])
+ with warns_deprecated_sympy():
+ assert LT(Mt, t, s) == Ms_conds
+ # The new behavior is to return a tuple of a Matrix and the convergence
+ # conditions for the matrix as a whole:
+ assert LT(Mt, t, s, legacy_matrix=False) == (Ms, 1, True)
+ # With noconds=True the transformed matrix is returned without conditions
+ # either way:
+ assert LT(Mt, t, s, noconds=True) == Ms
+ assert LT(Mt, t, s, legacy_matrix=False, noconds=True) == Ms
+
+
+@slow
+def test_inverse_laplace_transform():
+ s = symbols('s')
+ k, n, t = symbols('k, n, t', real=True)
+ a, b, c, d = symbols('a, b, c, d', positive=True)
+ f = Function('f')
+ F = Function('F')
+
+ def ILT(g):
+ return inverse_laplace_transform(g, s, t)
+
+ def ILTS(g):
+ return inverse_laplace_transform(g, s, t, simplify=True)
+
+ def ILTF(g):
+ return laplace_correspondence(
+ inverse_laplace_transform(g, s, t), {f: F})
+
+ # Tests for the rules in Bateman54.
+
+ # Section 4.1: Some of the Laplace transform rules can also be used well
+ # in the inverse transform.
+ assert ILTF(exp(-a*s)*F(s)) == f(-a + t)
+ assert ILTF(k*F(s-a)) == k*f(t)*exp(-a*t)
+ assert ILTF(diff(F(s), s, 3)) == -t**3*f(t)
+ assert ILTF(diff(F(s), s, 4)) == t**4*f(t)
+
+ # Section 5.1: Most rules are impractical for a computer algebra system.
+
+ # Section 5.2: Rational functions
+ assert ILT(2) == 2*DiracDelta(t)
+ assert ILT(1/s) == Heaviside(t)
+ assert ILT(1/s**2) == t*Heaviside(t)
+ assert ILT(1/s**5) == t**4*Heaviside(t)/24
+ assert ILT(1/s**n) == t**(n - 1)*Heaviside(t)/gamma(n)
+ assert ILT(a/(a + s)) == a*exp(-a*t)*Heaviside(t)
+ assert ILT(s/(a + s)) == -a*exp(-a*t)*Heaviside(t) + DiracDelta(t)
+ assert (ILT(b*s/(s+a)**2) ==
+ b*(-a*t*exp(-a*t)*Heaviside(t) + exp(-a*t)*Heaviside(t)))
+ assert (ILTS(c/((s+a)*(s+b))) ==
+ c*(exp(a*t) - exp(b*t))*exp(-t*(a + b))*Heaviside(t)/(a - b))
+ assert (ILTS(c*s/((s+a)*(s+b))) ==
+ c*(a*exp(b*t) - b*exp(a*t))*exp(-t*(a + b))*Heaviside(t)/(a - b))
+ assert ILTS(s/(a + s)**3) == t*(-a*t + 2)*exp(-a*t)*Heaviside(t)/2
+ assert ILTS(1/(s*(a + s)**3)) == (
+ -a**2*t**2 - 2*a*t + 2*exp(a*t) - 2)*exp(-a*t)*Heaviside(t)/(2*a**3)
+ assert ILT(1/(s*(a + s)**n)) == (
+ Heaviside(t)*lowergamma(n, a*t)/(a**n*gamma(n)))
+ assert ILT((s-a)**(-b)) == t**(b - 1)*exp(a*t)*Heaviside(t)/gamma(b)
+ assert ILT((a + s)**(-2)) == t*exp(-a*t)*Heaviside(t)
+ assert ILT((a + s)**(-5)) == t**4*exp(-a*t)*Heaviside(t)/24
+ assert ILT(s**2/(s**2 + 1)) == -sin(t)*Heaviside(t) + DiracDelta(t)
+ assert ILT(1 - 1/(s**2 + 1)) == -sin(t)*Heaviside(t) + DiracDelta(t)
+ assert ILT(a/(a**2 + s**2)) == sin(a*t)*Heaviside(t)
+ assert ILT(s/(s**2 + a**2)) == cos(a*t)*Heaviside(t)
+ assert ILT(b/(b**2 + (a + s)**2)) == exp(-a*t)*sin(b*t)*Heaviside(t)
+ assert (ILT(b*s/(b**2 + (a + s)**2)) ==
+ b*(-a*exp(-a*t)*sin(b*t)/b + exp(-a*t)*cos(b*t))*Heaviside(t))
+ assert ILT(1/(s**2*(s**2 + 1))) == t*Heaviside(t) - sin(t)*Heaviside(t)
+ assert (ILTS(c*s/(d**2*(s+a)**2+b**2)) ==
+ c*(-a*d*sin(b*t/d) + b*cos(b*t/d))*exp(-a*t)*Heaviside(t)/(b*d**2))
+ assert ILTS((b*s**2 + d)/(a**2 + s**2)**2) == (
+ 2*a**2*b*sin(a*t) + (a**2*b - d)*(a*t*cos(a*t) -
+ sin(a*t)))*Heaviside(t)/(2*a**3)
+ assert ILTS(b/(s**2-a**2)) == b*sinh(a*t)*Heaviside(t)/a
+ assert (ILT(b/(s**2-a**2)) ==
+ b*(exp(a*t)*Heaviside(t)/(2*a) - exp(-a*t)*Heaviside(t)/(2*a)))
+ assert ILTS(b*s/(s**2-a**2)) == b*cosh(a*t)*Heaviside(t)
+ assert (ILT(b/(s*(s+a))) ==
+ b*(Heaviside(t)/a - exp(-a*t)*Heaviside(t)/a))
+ # Issue #24424
+ assert (ILTS((s + 8)/((s + 2)*(s**2 + 2*s + 10))) ==
+ ((8*sin(3*t) - 9*cos(3*t))*exp(t) + 9)*exp(-2*t)*Heaviside(t)/15)
+ # Issue #8514; this is not important anymore, since this function
+ # is not solved by integration anymore
+ assert (ILT(1/(a*s**2+b*s+c)) ==
+ 2*exp(-b*t/(2*a))*sin(t*sqrt(4*a*c - b**2)/(2*a)) *
+ Heaviside(t)/sqrt(4*a*c - b**2))
+
+ # Section 5.3: Irrational algebraic functions
+ assert ( # (1)
+ ILT(1/sqrt(s)/(b*s-a)) ==
+ exp(a*t/b)*Heaviside(t)*erf(sqrt(a)*sqrt(t)/sqrt(b))/(sqrt(a)*sqrt(b)))
+ assert ( # (2)
+ ILT(1/sqrt(k*s)/(c*s-a)/s) ==
+ (-2*c*sqrt(t)/(sqrt(pi)*a) +
+ c**(S(3)/2)*exp(a*t/c)*erf(sqrt(a)*sqrt(t)/sqrt(c))/a**(S(3)/2)) *
+ Heaviside(t)/(c*sqrt(k)))
+ assert ( # (4)
+ ILT(1/(sqrt(c*s)+a)) == (-a*exp(a**2*t/c)*erfc(a*sqrt(t)/sqrt(c))/c +
+ 1/(sqrt(pi)*sqrt(c)*sqrt(t)))*Heaviside(t))
+ assert ( # (5)
+ ILT(a/s/(b*sqrt(s)+a)) ==
+ (-exp(a**2*t/b**2)*erfc(a*sqrt(t)/b) + 1)*Heaviside(t))
+ assert ( # (6)
+ ILT((a-b)*sqrt(s)/(sqrt(s)+sqrt(a))/(s-b)) ==
+ (sqrt(a)*sqrt(b)*exp(b*t)*erfc(sqrt(b)*sqrt(t)) +
+ a*exp(a*t)*erfc(sqrt(a)*sqrt(t)) - b*exp(b*t))*Heaviside(t))
+ assert ( # (7)
+ ILT(1/sqrt(s)/(sqrt(b*s)+a)) ==
+ exp(a**2*t/b)*Heaviside(t)*erfc(a*sqrt(t)/sqrt(b))/sqrt(b))
+ assert ( # (8)
+ ILT(a**2/(sqrt(s)+a)/s**(S(3)/2)) ==
+ (2*a*sqrt(t)/sqrt(pi) + exp(a**2*t)*erfc(a*sqrt(t)) - 1) *
+ Heaviside(t))
+ assert ( # (9)
+ ILT((a-b)*sqrt(b)/(s-b)/sqrt(s)/(sqrt(s)+sqrt(a))) ==
+ (sqrt(a)*exp(b*t)*erf(sqrt(b)*sqrt(t)) +
+ sqrt(b)*exp(a*t)*erfc(sqrt(a)*sqrt(t)) -
+ sqrt(b)*exp(b*t))*Heaviside(t))
+ assert ( # (10)
+ ILT(1/(sqrt(s)+sqrt(a))**2) ==
+ (-2*sqrt(a)*sqrt(t)/sqrt(pi) +
+ (-2*a*t + 1)*(erf(sqrt(a)*sqrt(t)) -
+ 1)*exp(a*t) + 1)*Heaviside(t))
+ assert ( # (11)
+ ILT(1/(sqrt(s)+sqrt(a))**2/s) ==
+ ((2*t - 1/a)*exp(a*t)*erfc(sqrt(a)*sqrt(t)) + 1/a -
+ 2*sqrt(t)/(sqrt(pi)*sqrt(a)))*Heaviside(t))
+ assert ( # (12)
+ ILT(1/(sqrt(s)+a)**2/sqrt(s)) ==
+ (-2*a*t*exp(a**2*t)*erfc(a*sqrt(t)) +
+ 2*sqrt(t)/sqrt(pi))*Heaviside(t))
+ assert ( # (13)
+ ILT(1/(sqrt(s)+a)**3) ==
+ (-a*t*(2*a**2*t + 3)*exp(a**2*t)*erfc(a*sqrt(t)) +
+ 2*sqrt(t)*(a**2*t + 1)/sqrt(pi))*Heaviside(t))
+ x = (
+ - ILT(sqrt(s)/(sqrt(s)+a)**3) +
+ 2*(sqrt(pi)*a**2*t*(-2*sqrt(pi)*erfc(a*sqrt(t)) +
+ 2*exp(-a**2*t)/(a*sqrt(t))) *
+ (-a**4*t**2 - 5*a**2*t/2 - S.Half) * exp(a**2*t)/2 +
+ sqrt(pi)*a*sqrt(t)*(a**2*t + 1)/2) *
+ Heaviside(t)/(pi*a**2*t)).simplify()
+ assert ( # (14)
+ x == 0)
+ x = (
+ - ILT(1/sqrt(s)/(sqrt(s)+a)**3) +
+ Heaviside(t)*(sqrt(t)*((2*a**2*t + 1) *
+ (sqrt(pi)*a*sqrt(t)*exp(a**2*t) *
+ erfc(a*sqrt(t)) - 1) + 1) /
+ (sqrt(pi)*a))).simplify()
+ assert ( # (15)
+ x == 0)
+ assert ( # (16)
+ factor_terms(ILT(3/(sqrt(s)+a)**4)) ==
+ 3*(-2*a**3*t**(S(5)/2)*(2*a**2*t + 5)/(3*sqrt(pi)) +
+ t*(4*a**4*t**2 + 12*a**2*t + 3)*exp(a**2*t) *
+ erfc(a*sqrt(t))/3)*Heaviside(t))
+ assert ( # (17)
+ ILT((sqrt(s)-a)/(s*(sqrt(s)+a))) ==
+ (2*exp(a**2*t)*erfc(a*sqrt(t))-1)*Heaviside(t))
+ assert ( # (18)
+ ILT((sqrt(s)-a)**2/(s*(sqrt(s)+a)**2)) == (
+ 1 + 8*a**2*t*exp(a**2*t)*erfc(a*sqrt(t)) -
+ 8/sqrt(pi)*a*sqrt(t))*Heaviside(t))
+ assert ( # (19)
+ ILT((sqrt(s)-a)**3/(s*(sqrt(s)+a)**3)) == Heaviside(t)*(
+ 2*(8*a**4*t**2+8*a**2*t+1)*exp(a**2*t) *
+ erfc(a*sqrt(t))-8/sqrt(pi)*a*sqrt(t)*(2*a**2*t+1)-1))
+ assert ( # (22)
+ ILT(sqrt(s+a)/(s+b)) == Heaviside(t)*(
+ exp(-a*t)/sqrt(t)/sqrt(pi) +
+ sqrt(a-b)*exp(-b*t)*erf(sqrt(a-b)*sqrt(t))))
+ assert ( # (23)
+ ILT(1/sqrt(s+b)/(s+a)) == Heaviside(t)*(
+ 1/sqrt(b-a)*exp(-a*t)*erf(sqrt(b-a)*sqrt(t))))
+ assert ( # (35)
+ ILT(1/sqrt(s**2+a**2)) == Heaviside(t)*(
+ besselj(0, a*t)))
+ assert ( # (44)
+ ILT(1/sqrt(s**2-a**2)) == Heaviside(t)*(
+ besseli(0, a*t)))
+
+ # Miscellaneous tests
+ # Can _inverse_laplace_time_shift deal with positive exponents?
+ assert (
+ - ILT((s**2*exp(2*s) + 4*exp(s) - 4)*exp(-2*s)/(s*(s**2 + 1))) +
+ cos(t)*Heaviside(t) + 4*cos(t - 2)*Heaviside(t - 2) -
+ 4*cos(t - 1)*Heaviside(t - 1) - 4*Heaviside(t - 2) +
+ 4*Heaviside(t - 1)).simplify() == 0
+
+
+@slow
+def test_inverse_laplace_transform_old():
+ from sympy.functions.special.delta_functions import DiracDelta
+ ILT = inverse_laplace_transform
+ a, b, c, d = symbols('a b c d', positive=True)
+ n, r = symbols('n, r', real=True)
+ t, z = symbols('t z')
+ f = Function('f')
+ F = Function('F')
+
+ def simp_hyp(expr):
+ return factor_terms(expand_mul(expr)).rewrite(sin)
+
+ L = ILT(F(s), s, t)
+ assert laplace_correspondence(L, {f: F}) == f(t)
+ assert ILT(exp(-a*s)/s, s, t) == Heaviside(-a + t)
+ assert ILT(exp(-a*s)/(b + s), s, t) == exp(-b*(-a + t))*Heaviside(-a + t)
+ assert (ILT((b + s)/(a**2 + (b + s)**2), s, t) ==
+ exp(-b*t)*cos(a*t)*Heaviside(t))
+ assert (ILT(exp(-a*s)/s**b, s, t) ==
+ (-a + t)**(b - 1)*Heaviside(-a + t)/gamma(b))
+ assert (ILT(exp(-a*s)/sqrt(s**2 + 1), s, t) ==
+ Heaviside(-a + t)*besselj(0, a - t))
+ assert ILT(1/(s*sqrt(s + 1)), s, t) == Heaviside(t)*erf(sqrt(t))
+ # TODO sinh/cosh shifted come out a mess. also delayed trig is a mess
+ # TODO should this simplify further?
+ assert (ILT(exp(-a*s)/s**b, s, t) ==
+ (t - a)**(b - 1)*Heaviside(t - a)/gamma(b))
+ assert (ILT(exp(-a*s)/sqrt(1 + s**2), s, t) ==
+ Heaviside(t - a)*besselj(0, a - t)) # note: besselj(0, x) is even
+ # XXX ILT turns these branch factor into trig functions ...
+ assert (
+ simplify(ILT(a**b*(s + sqrt(s**2 - a**2))**(-b)/sqrt(s**2 - a**2),
+ s, t).rewrite(exp)) ==
+ Heaviside(t)*besseli(b, a*t))
+ assert (
+ ILT(a**b*(s + sqrt(s**2 + a**2))**(-b)/sqrt(s**2 + a**2),
+ s, t, simplify=True).rewrite(exp) ==
+ Heaviside(t)*besselj(b, a*t))
+ assert ILT(1/(s*sqrt(s + 1)), s, t) == Heaviside(t)*erf(sqrt(t))
+ # TODO can we make erf(t) work?
+ assert (ILT((s * eye(2) - Matrix([[1, 0], [0, 2]])).inv(), s, t) ==
+ Matrix([[exp(t)*Heaviside(t), 0], [0, exp(2*t)*Heaviside(t)]]))
+ # Test time_diff rule
+ assert (ILT(s**42*f(s), s, t) ==
+ Derivative(InverseLaplaceTransform(f(s), s, t, None), (t, 42)))
+ assert ILT(cos(s), s, t) == InverseLaplaceTransform(cos(s), s, t, None)
+ # Rules for testing different DiracDelta cases
+ assert (ILT(2*exp(3*s) - 5*exp(-7*s), s, t) ==
+ 2*InverseLaplaceTransform(exp(3*s), s, t, None) -
+ 5*DiracDelta(t - 7))
+ a = cos(sin(7)/2)
+ assert ILT(a*exp(-3*s), s, t) == a*DiracDelta(t - 3)
+ assert ILT(exp(2*s), s, t) == InverseLaplaceTransform(exp(2*s), s, t, None)
+ r = Symbol('r', real=True)
+ assert ILT(exp(r*s), s, t) == InverseLaplaceTransform(exp(r*s), s, t, None)
+ # Rules for testing whether Heaviside(t) is treated properly in diff rule
+ assert ILT(s**2/(a**2 + s**2), s, t) == (
+ -a*sin(a*t)*Heaviside(t) + DiracDelta(t))
+ assert ILT(s**2*(f(s) + 1/(a**2 + s**2)), s, t) == (
+ -a*sin(a*t)*Heaviside(t) + DiracDelta(t) +
+ Derivative(InverseLaplaceTransform(f(s), s, t, None), (t, 2)))
+ # Rules from the previous test_inverse_laplace_transform_delta_cond():
+ assert (ILT(exp(r*s), s, t, noconds=False) ==
+ (InverseLaplaceTransform(exp(r*s), s, t, None), True))
+ # inversion does not exist: verify it doesn't evaluate to DiracDelta
+ for z in (Symbol('z', extended_real=False),
+ Symbol('z', imaginary=True, zero=False)):
+ f = ILT(exp(z*s), s, t, noconds=False)
+ f = f[0] if isinstance(f, tuple) else f
+ assert f.func != DiracDelta
+
+
+@slow
+def test_expint():
+ x = Symbol('x')
+ a = Symbol('a')
+ u = Symbol('u', polar=True)
+
+ # TODO LT of Si, Shi, Chi is a mess ...
+ assert laplace_transform(Ci(x), x, s) == (-log(1 + s**2)/2/s, 0, True)
+ assert (laplace_transform(expint(a, x), x, s, simplify=True) ==
+ (lerchphi(s*exp_polar(I*pi), 1, a), 0, re(a) > S.Zero))
+ assert (laplace_transform(expint(1, x), x, s, simplify=True) ==
+ (log(s + 1)/s, 0, True))
+ assert (laplace_transform(expint(2, x), x, s, simplify=True) ==
+ ((s - log(s + 1))/s**2, 0, True))
+ assert (inverse_laplace_transform(-log(1 + s**2)/2/s, s, u).expand() ==
+ Heaviside(u)*Ci(u))
+ assert (
+ inverse_laplace_transform(log(s + 1)/s, s, x,
+ simplify=True).rewrite(expint) ==
+ Heaviside(x)*E1(x))
+ assert (
+ inverse_laplace_transform(
+ (s - log(s + 1))/s**2, s, x,
+ simplify=True).rewrite(expint).expand() ==
+ (expint(2, x)*Heaviside(x)).rewrite(Ei).rewrite(expint).expand())
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_lineintegrals.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_lineintegrals.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0af146b52406a153d033286f3fcfa79334d2a73
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_lineintegrals.py
@@ -0,0 +1,13 @@
+from sympy.core.numbers import E
+from sympy.core.symbol import symbols
+from sympy.functions.elementary.exponential import log
+from sympy.functions.elementary.miscellaneous import sqrt
+from sympy.geometry.curve import Curve
+from sympy.integrals.integrals import line_integrate
+
+s, t, x, y, z = symbols('s,t,x,y,z')
+
+
+def test_lineintegral():
+ c = Curve([E**t + 1, E**t - 1], (t, 0, log(2)))
+ assert line_integrate(x + y, c, [x, y]) == 3*sqrt(2)
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_meijerint.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_meijerint.py
new file mode 100644
index 0000000000000000000000000000000000000000..899bc96e63d6dd5bccd52856f15d3085e87d5807
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_meijerint.py
@@ -0,0 +1,774 @@
+from sympy.core.function import expand_func
+from sympy.core.numbers import (I, Rational, oo, pi)
+from sympy.core.singleton import S
+from sympy.core.sorting import default_sort_key
+from sympy.functions.elementary.complexes import Abs, arg, re, unpolarify
+from sympy.functions.elementary.exponential import (exp, exp_polar, log)
+from sympy.functions.elementary.hyperbolic import cosh, acosh, sinh
+from sympy.functions.elementary.miscellaneous import sqrt
+from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold
+from sympy.functions.elementary.trigonometric import (cos, sin, sinc, asin)
+from sympy.functions.special.error_functions import (erf, erfc)
+from sympy.functions.special.gamma_functions import (gamma, polygamma)
+from sympy.functions.special.hyper import (hyper, meijerg)
+from sympy.integrals.integrals import (Integral, integrate)
+from sympy.simplify.hyperexpand import hyperexpand
+from sympy.simplify.simplify import simplify
+from sympy.integrals.meijerint import (_rewrite_single, _rewrite1,
+ meijerint_indefinite, _inflate_g, _create_lookup_table,
+ meijerint_definite, meijerint_inversion)
+from sympy.testing.pytest import slow
+from sympy.core.random import (verify_numerically,
+ random_complex_number as randcplx)
+from sympy.abc import x, y, a, b, c, d, s, t, z
+
+
+def test_rewrite_single():
+ def t(expr, c, m):
+ e = _rewrite_single(meijerg([a], [b], [c], [d], expr), x)
+ assert e is not None
+ assert isinstance(e[0][0][2], meijerg)
+ assert e[0][0][2].argument.as_coeff_mul(x) == (c, (m,))
+
+ def tn(expr):
+ assert _rewrite_single(meijerg([a], [b], [c], [d], expr), x) is None
+
+ t(x, 1, x)
+ t(x**2, 1, x**2)
+ t(x**2 + y*x**2, y + 1, x**2)
+ tn(x**2 + x)
+ tn(x**y)
+
+ def u(expr, x):
+ from sympy.core.add import Add
+ r = _rewrite_single(expr, x)
+ e = Add(*[res[0]*res[2] for res in r[0]]).replace(
+ exp_polar, exp) # XXX Hack?
+ assert verify_numerically(e, expr, x)
+
+ u(exp(-x)*sin(x), x)
+
+ # The following has stopped working because hyperexpand changed slightly.
+ # It is probably not worth fixing
+ #u(exp(-x)*sin(x)*cos(x), x)
+
+ # This one cannot be done numerically, since it comes out as a g-function
+ # of argument 4*pi
+ # NOTE This also tests a bug in inverse mellin transform (which used to
+ # turn exp(4*pi*I*t) into a factor of exp(4*pi*I)**t instead of
+ # exp_polar).
+ #u(exp(x)*sin(x), x)
+ assert _rewrite_single(exp(x)*sin(x), x) == \
+ ([(-sqrt(2)/(2*sqrt(pi)), 0,
+ meijerg(((Rational(-1, 2), 0, Rational(1, 4), S.Half, Rational(3, 4)), (1,)),
+ ((), (Rational(-1, 2), 0)), 64*exp_polar(-4*I*pi)/x**4))], True)
+
+
+def test_rewrite1():
+ assert _rewrite1(x**3*meijerg([a], [b], [c], [d], x**2 + y*x**2)*5, x) == \
+ (5, x**3, [(1, 0, meijerg([a], [b], [c], [d], x**2*(y + 1)))], True)
+
+
+def test_meijerint_indefinite_numerically():
+ def t(fac, arg):
+ g = meijerg([a], [b], [c], [d], arg)*fac
+ subs = {a: randcplx()/10, b: randcplx()/10 + I,
+ c: randcplx(), d: randcplx()}
+ integral = meijerint_indefinite(g, x)
+ assert integral is not None
+ assert verify_numerically(g.subs(subs), integral.diff(x).subs(subs), x)
+ t(1, x)
+ t(2, x)
+ t(1, 2*x)
+ t(1, x**2)
+ t(5, x**S('3/2'))
+ t(x**3, x)
+ t(3*x**S('3/2'), 4*x**S('7/3'))
+
+
+def test_meijerint_definite():
+ v, b = meijerint_definite(x, x, 0, 0)
+ assert v.is_zero and b is True
+ v, b = meijerint_definite(x, x, oo, oo)
+ assert v.is_zero and b is True
+
+
+def test_inflate():
+ subs = {a: randcplx()/10, b: randcplx()/10 + I, c: randcplx(),
+ d: randcplx(), y: randcplx()/10}
+
+ def t(a, b, arg, n):
+ from sympy.core.mul import Mul
+ m1 = meijerg(a, b, arg)
+ m2 = Mul(*_inflate_g(m1, n))
+ # NOTE: (the random number)**9 must still be on the principal sheet.
+ # Thus make b&d small to create random numbers of small imaginary part.
+ return verify_numerically(m1.subs(subs), m2.subs(subs), x, b=0.1, d=-0.1)
+ assert t([[a], [b]], [[c], [d]], x, 3)
+ assert t([[a, y], [b]], [[c], [d]], x, 3)
+ assert t([[a], [b]], [[c, y], [d]], 2*x**3, 3)
+
+
+def test_recursive():
+ from sympy.core.symbol import symbols
+ a, b, c = symbols('a b c', positive=True)
+ r = exp(-(x - a)**2)*exp(-(x - b)**2)
+ e = integrate(r, (x, 0, oo), meijerg=True)
+ assert simplify(e.expand()) == (
+ sqrt(2)*sqrt(pi)*(
+ (erf(sqrt(2)*(a + b)/2) + 1)*exp(-a**2/2 + a*b - b**2/2))/4)
+ e = integrate(exp(-(x - a)**2)*exp(-(x - b)**2)*exp(c*x), (x, 0, oo), meijerg=True)
+ assert simplify(e) == (
+ sqrt(2)*sqrt(pi)*(erf(sqrt(2)*(2*a + 2*b + c)/4) + 1)*exp(-a**2 - b**2
+ + (2*a + 2*b + c)**2/8)/4)
+ assert simplify(integrate(exp(-(x - a - b - c)**2), (x, 0, oo), meijerg=True)) == \
+ sqrt(pi)/2*(1 + erf(a + b + c))
+ assert simplify(integrate(exp(-(x + a + b + c)**2), (x, 0, oo), meijerg=True)) == \
+ sqrt(pi)/2*(1 - erf(a + b + c))
+
+
+@slow
+def test_meijerint():
+ from sympy.core.function import expand
+ from sympy.core.symbol import symbols
+ s, t, mu = symbols('s t mu', real=True)
+ assert integrate(meijerg([], [], [0], [], s*t)
+ *meijerg([], [], [mu/2], [-mu/2], t**2/4),
+ (t, 0, oo)).is_Piecewise
+ s = symbols('s', positive=True)
+ assert integrate(x**s*meijerg([[], []], [[0], []], x), (x, 0, oo)) == \
+ gamma(s + 1)
+ assert integrate(x**s*meijerg([[], []], [[0], []], x), (x, 0, oo),
+ meijerg=True) == gamma(s + 1)
+ assert isinstance(integrate(x**s*meijerg([[], []], [[0], []], x),
+ (x, 0, oo), meijerg=False),
+ Integral)
+
+ assert meijerint_indefinite(exp(x), x) == exp(x)
+
+ # TODO what simplifications should be done automatically?
+ # This tests "extra case" for antecedents_1.
+ a, b = symbols('a b', positive=True)
+ assert simplify(meijerint_definite(x**a, x, 0, b)[0]) == \
+ b**(a + 1)/(a + 1)
+
+ # This tests various conditions and expansions:
+ assert meijerint_definite((x + 1)**3*exp(-x), x, 0, oo) == (16, True)
+
+ # Again, how about simplifications?
+ sigma, mu = symbols('sigma mu', positive=True)
+ i, c = meijerint_definite(exp(-((x - mu)/(2*sigma))**2), x, 0, oo)
+ assert simplify(i) == sqrt(pi)*sigma*(2 - erfc(mu/(2*sigma)))
+ assert c == True
+
+ i, _ = meijerint_definite(exp(-mu*x)*exp(sigma*x), x, 0, oo)
+ # TODO it would be nice to test the condition
+ assert simplify(i) == 1/(mu - sigma)
+
+ # Test substitutions to change limits
+ assert meijerint_definite(exp(x), x, -oo, 2) == (exp(2), True)
+ # Note: causes a NaN in _check_antecedents
+ assert expand(meijerint_definite(exp(x), x, 0, I)[0]) == exp(I) - 1
+ assert expand(meijerint_definite(exp(-x), x, 0, x)[0]) == \
+ 1 - exp(-exp(I*arg(x))*abs(x))
+
+ # Test -oo to oo
+ assert meijerint_definite(exp(-x**2), x, -oo, oo) == (sqrt(pi), True)
+ assert meijerint_definite(exp(-abs(x)), x, -oo, oo) == (2, True)
+ assert meijerint_definite(exp(-(2*x - 3)**2), x, -oo, oo) == \
+ (sqrt(pi)/2, True)
+ assert meijerint_definite(exp(-abs(2*x - 3)), x, -oo, oo) == (1, True)
+ assert meijerint_definite(exp(-((x - mu)/sigma)**2/2)/sqrt(2*pi*sigma**2),
+ x, -oo, oo) == (1, True)
+ assert meijerint_definite(sinc(x)**2, x, -oo, oo) == (pi, True)
+
+ # Test one of the extra conditions for 2 g-functinos
+ assert meijerint_definite(exp(-x)*sin(x), x, 0, oo) == (S.Half, True)
+
+ # Test a bug
+ def res(n):
+ return (1/(1 + x**2)).diff(x, n).subs(x, 1)*(-1)**n
+ for n in range(6):
+ assert integrate(exp(-x)*sin(x)*x**n, (x, 0, oo), meijerg=True) == \
+ res(n)
+
+ # This used to test trigexpand... now it is done by linear substitution
+ assert simplify(integrate(exp(-x)*sin(x + a), (x, 0, oo), meijerg=True)
+ ) == sqrt(2)*sin(a + pi/4)/2
+
+ # Test the condition 14 from prudnikov.
+ # (This is besselj*besselj in disguise, to stop the product from being
+ # recognised in the tables.)
+ a, b, s = symbols('a b s')
+ assert meijerint_definite(meijerg([], [], [a/2], [-a/2], x/4)
+ *meijerg([], [], [b/2], [-b/2], x/4)*x**(s - 1), x, 0, oo
+ ) == (
+ (4*2**(2*s - 2)*gamma(-2*s + 1)*gamma(a/2 + b/2 + s)
+ /(gamma(-a/2 + b/2 - s + 1)*gamma(a/2 - b/2 - s + 1)
+ *gamma(a/2 + b/2 - s + 1)),
+ (re(s) < 1) & (re(s) < S(1)/2) & (re(a)/2 + re(b)/2 + re(s) > 0)))
+
+ # test a bug
+ assert integrate(sin(x**a)*sin(x**b), (x, 0, oo), meijerg=True) == \
+ Integral(sin(x**a)*sin(x**b), (x, 0, oo))
+
+ # test better hyperexpand
+ assert integrate(exp(-x**2)*log(x), (x, 0, oo), meijerg=True) == \
+ (sqrt(pi)*polygamma(0, S.Half)/4).expand()
+
+ # Test hyperexpand bug.
+ from sympy.functions.special.gamma_functions import lowergamma
+ n = symbols('n', integer=True)
+ assert simplify(integrate(exp(-x)*x**n, x, meijerg=True)) == \
+ lowergamma(n + 1, x)
+
+ # Test a bug with argument 1/x
+ alpha = symbols('alpha', positive=True)
+ assert meijerint_definite((2 - x)**alpha*sin(alpha/x), x, 0, 2) == \
+ (sqrt(pi)*alpha*gamma(alpha + 1)*meijerg(((), (alpha/2 + S.Half,
+ alpha/2 + 1)), ((0, 0, S.Half), (Rational(-1, 2),)), alpha**2/16)/4, True)
+
+ # test a bug related to 3016
+ a, s = symbols('a s', positive=True)
+ assert simplify(integrate(x**s*exp(-a*x**2), (x, -oo, oo))) == \
+ a**(-s/2 - S.Half)*((-1)**s + 1)*gamma(s/2 + S.Half)/2
+
+
+def test_bessel():
+ from sympy.functions.special.bessel import (besseli, besselj)
+ assert simplify(integrate(besselj(a, z)*besselj(b, z)/z, (z, 0, oo),
+ meijerg=True, conds='none')) == \
+ 2*sin(pi*(a/2 - b/2))/(pi*(a - b)*(a + b))
+ assert simplify(integrate(besselj(a, z)*besselj(a, z)/z, (z, 0, oo),
+ meijerg=True, conds='none')) == 1/(2*a)
+
+ # TODO more orthogonality integrals
+
+ assert simplify(integrate(sin(z*x)*(x**2 - 1)**(-(y + S.Half)),
+ (x, 1, oo), meijerg=True, conds='none')
+ *2/((z/2)**y*sqrt(pi)*gamma(S.Half - y))) == \
+ besselj(y, z)
+
+ # Werner Rosenheinrich
+ # SOME INDEFINITE INTEGRALS OF BESSEL FUNCTIONS
+
+ assert integrate(x*besselj(0, x), x, meijerg=True) == x*besselj(1, x)
+ assert integrate(x*besseli(0, x), x, meijerg=True) == x*besseli(1, x)
+ # TODO can do higher powers, but come out as high order ... should they be
+ # reduced to order 0, 1?
+ assert integrate(besselj(1, x), x, meijerg=True) == -besselj(0, x)
+ assert integrate(besselj(1, x)**2/x, x, meijerg=True) == \
+ -(besselj(0, x)**2 + besselj(1, x)**2)/2
+ # TODO more besseli when tables are extended or recursive mellin works
+ assert integrate(besselj(0, x)**2/x**2, x, meijerg=True) == \
+ -2*x*besselj(0, x)**2 - 2*x*besselj(1, x)**2 \
+ + 2*besselj(0, x)*besselj(1, x) - besselj(0, x)**2/x
+ assert integrate(besselj(0, x)*besselj(1, x), x, meijerg=True) == \
+ -besselj(0, x)**2/2
+ assert integrate(x**2*besselj(0, x)*besselj(1, x), x, meijerg=True) == \
+ x**2*besselj(1, x)**2/2
+ assert integrate(besselj(0, x)*besselj(1, x)/x, x, meijerg=True) == \
+ (x*besselj(0, x)**2 + x*besselj(1, x)**2 -
+ besselj(0, x)*besselj(1, x))
+ # TODO how does besselj(0, a*x)*besselj(0, b*x) work?
+ # TODO how does besselj(0, x)**2*besselj(1, x)**2 work?
+ # TODO sin(x)*besselj(0, x) etc come out a mess
+ # TODO can x*log(x)*besselj(0, x) be done?
+ # TODO how does besselj(1, x)*besselj(0, x+a) work?
+ # TODO more indefinite integrals when struve functions etc are implemented
+
+ # test a substitution
+ assert integrate(besselj(1, x**2)*x, x, meijerg=True) == \
+ -besselj(0, x**2)/2
+
+
+def test_inversion():
+ from sympy.functions.special.bessel import besselj
+ from sympy.functions.special.delta_functions import Heaviside
+
+ def inv(f):
+ return piecewise_fold(meijerint_inversion(f, s, t))
+ assert inv(1/(s**2 + 1)) == sin(t)*Heaviside(t)
+ assert inv(s/(s**2 + 1)) == cos(t)*Heaviside(t)
+ assert inv(exp(-s)/s) == Heaviside(t - 1)
+ assert inv(1/sqrt(1 + s**2)) == besselj(0, t)*Heaviside(t)
+
+ # Test some antcedents checking.
+ assert meijerint_inversion(sqrt(s)/sqrt(1 + s**2), s, t) is None
+ assert inv(exp(s**2)) is None
+ assert meijerint_inversion(exp(-s**2), s, t) is None
+
+
+def test_inversion_conditional_output():
+ from sympy.core.symbol import Symbol
+ from sympy.integrals.transforms import InverseLaplaceTransform
+
+ a = Symbol('a', positive=True)
+ F = sqrt(pi/a)*exp(-2*sqrt(a)*sqrt(s))
+ f = meijerint_inversion(F, s, t)
+ assert not f.is_Piecewise
+
+ b = Symbol('b', real=True)
+ F = F.subs(a, b)
+ f2 = meijerint_inversion(F, s, t)
+ assert f2.is_Piecewise
+ # first piece is same as f
+ assert f2.args[0][0] == f.subs(a, b)
+ # last piece is an unevaluated transform
+ assert f2.args[-1][1]
+ ILT = InverseLaplaceTransform(F, s, t, None)
+ assert f2.args[-1][0] == ILT or f2.args[-1][0] == ILT.as_integral
+
+
+def test_inversion_exp_real_nonreal_shift():
+ from sympy.core.symbol import Symbol
+ from sympy.functions.special.delta_functions import DiracDelta
+ r = Symbol('r', real=True)
+ c = Symbol('c', extended_real=False)
+ a = 1 + 2*I
+ z = Symbol('z')
+ assert not meijerint_inversion(exp(r*s), s, t).is_Piecewise
+ assert meijerint_inversion(exp(a*s), s, t) is None
+ assert meijerint_inversion(exp(c*s), s, t) is None
+ f = meijerint_inversion(exp(z*s), s, t)
+ assert f.is_Piecewise
+ assert isinstance(f.args[0][0], DiracDelta)
+
+
+@slow
+def test_lookup_table():
+ from sympy.core.random import uniform, randrange
+ from sympy.core.add import Add
+ from sympy.integrals.meijerint import z as z_dummy
+ table = {}
+ _create_lookup_table(table)
+ for l in table.values():
+ for formula, terms, cond, hint in sorted(l, key=default_sort_key):
+ subs = {}
+ for ai in list(formula.free_symbols) + [z_dummy]:
+ if hasattr(ai, 'properties') and ai.properties:
+ # these Wilds match positive integers
+ subs[ai] = randrange(1, 10)
+ else:
+ subs[ai] = uniform(1.5, 2.0)
+ if not isinstance(terms, list):
+ terms = terms(subs)
+
+ # First test that hyperexpand can do this.
+ expanded = [hyperexpand(g) for (_, g) in terms]
+ assert all(x.is_Piecewise or not x.has(meijerg) for x in expanded)
+
+ # Now test that the meijer g-function is indeed as advertised.
+ expanded = Add(*[f*x for (f, x) in terms])
+ a, b = formula.n(subs=subs), expanded.n(subs=subs)
+ r = min(abs(a), abs(b))
+ if r < 1:
+ assert abs(a - b).n() <= 1e-10
+ else:
+ assert (abs(a - b)/r).n() <= 1e-10
+
+
+def test_branch_bug():
+ from sympy.functions.special.gamma_functions import lowergamma
+ from sympy.simplify.powsimp import powdenest
+ # TODO gammasimp cannot prove that the factor is unity
+ assert powdenest(integrate(erf(x**3), x, meijerg=True).diff(x),
+ polar=True) == 2*erf(x**3)*gamma(Rational(2, 3))/3/gamma(Rational(5, 3))
+ assert integrate(erf(x**3), x, meijerg=True) == \
+ 2*x*erf(x**3)*gamma(Rational(2, 3))/(3*gamma(Rational(5, 3))) \
+ - 2*gamma(Rational(2, 3))*lowergamma(Rational(2, 3), x**6)/(3*sqrt(pi)*gamma(Rational(5, 3)))
+
+
+def test_linear_subs():
+ from sympy.functions.special.bessel import besselj
+ assert integrate(sin(x - 1), x, meijerg=True) == -cos(1 - x)
+ assert integrate(besselj(1, x - 1), x, meijerg=True) == -besselj(0, 1 - x)
+
+
+@slow
+def test_probability():
+ # various integrals from probability theory
+ from sympy.core.function import expand_mul
+ from sympy.core.symbol import (Symbol, symbols)
+ from sympy.simplify.gammasimp import gammasimp
+ from sympy.simplify.powsimp import powsimp
+ mu1, mu2 = symbols('mu1 mu2', nonzero=True)
+ sigma1, sigma2 = symbols('sigma1 sigma2', positive=True)
+ rate = Symbol('lambda', positive=True)
+
+ def normal(x, mu, sigma):
+ return 1/sqrt(2*pi*sigma**2)*exp(-(x - mu)**2/2/sigma**2)
+
+ def exponential(x, rate):
+ return rate*exp(-rate*x)
+
+ assert integrate(normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True) == 1
+ assert integrate(x*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True) == \
+ mu1
+ assert integrate(x**2*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True) \
+ == mu1**2 + sigma1**2
+ assert integrate(x**3*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True) \
+ == mu1**3 + 3*mu1*sigma1**2
+ assert integrate(normal(x, mu1, sigma1)*normal(y, mu2, sigma2),
+ (x, -oo, oo), (y, -oo, oo), meijerg=True) == 1
+ assert integrate(x*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),
+ (x, -oo, oo), (y, -oo, oo), meijerg=True) == mu1
+ assert integrate(y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),
+ (x, -oo, oo), (y, -oo, oo), meijerg=True) == mu2
+ assert integrate(x*y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),
+ (x, -oo, oo), (y, -oo, oo), meijerg=True) == mu1*mu2
+ assert integrate((x + y + 1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),
+ (x, -oo, oo), (y, -oo, oo), meijerg=True) == 1 + mu1 + mu2
+ assert integrate((x + y - 1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),
+ (x, -oo, oo), (y, -oo, oo), meijerg=True) == \
+ -1 + mu1 + mu2
+
+ i = integrate(x**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),
+ (x, -oo, oo), (y, -oo, oo), meijerg=True)
+ assert not i.has(Abs)
+ assert simplify(i) == mu1**2 + sigma1**2
+ assert integrate(y**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),
+ (x, -oo, oo), (y, -oo, oo), meijerg=True) == \
+ sigma2**2 + mu2**2
+
+ assert integrate(exponential(x, rate), (x, 0, oo), meijerg=True) == 1
+ assert integrate(x*exponential(x, rate), (x, 0, oo), meijerg=True) == \
+ 1/rate
+ assert integrate(x**2*exponential(x, rate), (x, 0, oo), meijerg=True) == \
+ 2/rate**2
+
+ def E(expr):
+ res1 = integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
+ (x, 0, oo), (y, -oo, oo), meijerg=True)
+ res2 = integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
+ (y, -oo, oo), (x, 0, oo), meijerg=True)
+ assert expand_mul(res1) == expand_mul(res2)
+ return res1
+
+ assert E(1) == 1
+ assert E(x*y) == mu1/rate
+ assert E(x*y**2) == mu1**2/rate + sigma1**2/rate
+ ans = sigma1**2 + 1/rate**2
+ assert simplify(E((x + y + 1)**2) - E(x + y + 1)**2) == ans
+ assert simplify(E((x + y - 1)**2) - E(x + y - 1)**2) == ans
+ assert simplify(E((x + y)**2) - E(x + y)**2) == ans
+
+ # Beta' distribution
+ alpha, beta = symbols('alpha beta', positive=True)
+ betadist = x**(alpha - 1)*(1 + x)**(-alpha - beta)*gamma(alpha + beta) \
+ /gamma(alpha)/gamma(beta)
+ assert integrate(betadist, (x, 0, oo), meijerg=True) == 1
+ i = integrate(x*betadist, (x, 0, oo), meijerg=True, conds='separate')
+ assert (gammasimp(i[0]), i[1]) == (alpha/(beta - 1), 1 < beta)
+ j = integrate(x**2*betadist, (x, 0, oo), meijerg=True, conds='separate')
+ assert j[1] == (beta > 2)
+ assert gammasimp(j[0] - i[0]**2) == (alpha + beta - 1)*alpha \
+ /(beta - 2)/(beta - 1)**2
+
+ # Beta distribution
+ # NOTE: this is evaluated using antiderivatives. It also tests that
+ # meijerint_indefinite returns the simplest possible answer.
+ a, b = symbols('a b', positive=True)
+ betadist = x**(a - 1)*(-x + 1)**(b - 1)*gamma(a + b)/(gamma(a)*gamma(b))
+ assert simplify(integrate(betadist, (x, 0, 1), meijerg=True)) == 1
+ assert simplify(integrate(x*betadist, (x, 0, 1), meijerg=True)) == \
+ a/(a + b)
+ assert simplify(integrate(x**2*betadist, (x, 0, 1), meijerg=True)) == \
+ a*(a + 1)/(a + b)/(a + b + 1)
+ assert simplify(integrate(x**y*betadist, (x, 0, 1), meijerg=True)) == \
+ gamma(a + b)*gamma(a + y)/gamma(a)/gamma(a + b + y)
+
+ # Chi distribution
+ k = Symbol('k', integer=True, positive=True)
+ chi = 2**(1 - k/2)*x**(k - 1)*exp(-x**2/2)/gamma(k/2)
+ assert powsimp(integrate(chi, (x, 0, oo), meijerg=True)) == 1
+ assert simplify(integrate(x*chi, (x, 0, oo), meijerg=True)) == \
+ sqrt(2)*gamma((k + 1)/2)/gamma(k/2)
+ assert simplify(integrate(x**2*chi, (x, 0, oo), meijerg=True)) == k
+
+ # Chi^2 distribution
+ chisquared = 2**(-k/2)/gamma(k/2)*x**(k/2 - 1)*exp(-x/2)
+ assert powsimp(integrate(chisquared, (x, 0, oo), meijerg=True)) == 1
+ assert simplify(integrate(x*chisquared, (x, 0, oo), meijerg=True)) == k
+ assert simplify(integrate(x**2*chisquared, (x, 0, oo), meijerg=True)) == \
+ k*(k + 2)
+ assert gammasimp(integrate(((x - k)/sqrt(2*k))**3*chisquared, (x, 0, oo),
+ meijerg=True)) == 2*sqrt(2)/sqrt(k)
+
+ # Dagum distribution
+ a, b, p = symbols('a b p', positive=True)
+ # XXX (x/b)**a does not work
+ dagum = a*p/x*(x/b)**(a*p)/(1 + x**a/b**a)**(p + 1)
+ assert simplify(integrate(dagum, (x, 0, oo), meijerg=True)) == 1
+ # XXX conditions are a mess
+ arg = x*dagum
+ assert simplify(integrate(arg, (x, 0, oo), meijerg=True, conds='none')
+ ) == a*b*gamma(1 - 1/a)*gamma(p + 1 + 1/a)/(
+ (a*p + 1)*gamma(p))
+ assert simplify(integrate(x*arg, (x, 0, oo), meijerg=True, conds='none')
+ ) == a*b**2*gamma(1 - 2/a)*gamma(p + 1 + 2/a)/(
+ (a*p + 2)*gamma(p))
+
+ # F-distribution
+ d1, d2 = symbols('d1 d2', positive=True)
+ f = sqrt(((d1*x)**d1 * d2**d2)/(d1*x + d2)**(d1 + d2))/x \
+ /gamma(d1/2)/gamma(d2/2)*gamma((d1 + d2)/2)
+ assert simplify(integrate(f, (x, 0, oo), meijerg=True)) == 1
+ # TODO conditions are a mess
+ assert simplify(integrate(x*f, (x, 0, oo), meijerg=True, conds='none')
+ ) == d2/(d2 - 2)
+ assert simplify(integrate(x**2*f, (x, 0, oo), meijerg=True, conds='none')
+ ) == d2**2*(d1 + 2)/d1/(d2 - 4)/(d2 - 2)
+
+ # TODO gamma, rayleigh
+
+ # inverse gaussian
+ lamda, mu = symbols('lamda mu', positive=True)
+ dist = sqrt(lamda/2/pi)*x**(Rational(-3, 2))*exp(-lamda*(x - mu)**2/x/2/mu**2)
+ mysimp = lambda expr: simplify(expr.rewrite(exp))
+ assert mysimp(integrate(dist, (x, 0, oo))) == 1
+ assert mysimp(integrate(x*dist, (x, 0, oo))) == mu
+ assert mysimp(integrate((x - mu)**2*dist, (x, 0, oo))) == mu**3/lamda
+ assert mysimp(integrate((x - mu)**3*dist, (x, 0, oo))) == 3*mu**5/lamda**2
+
+ # Levi
+ c = Symbol('c', positive=True)
+ assert integrate(sqrt(c/2/pi)*exp(-c/2/(x - mu))/(x - mu)**S('3/2'),
+ (x, mu, oo)) == 1
+ # higher moments oo
+
+ # log-logistic
+ alpha, beta = symbols('alpha beta', positive=True)
+ distn = (beta/alpha)*x**(beta - 1)/alpha**(beta - 1)/ \
+ (1 + x**beta/alpha**beta)**2
+ # FIXME: If alpha, beta are not declared as finite the line below hangs
+ # after the changes in:
+ # https://github.com/sympy/sympy/pull/16603
+ assert simplify(integrate(distn, (x, 0, oo))) == 1
+ # NOTE the conditions are a mess, but correctly state beta > 1
+ assert simplify(integrate(x*distn, (x, 0, oo), conds='none')) == \
+ pi*alpha/beta/sin(pi/beta)
+ # (similar comment for conditions applies)
+ assert simplify(integrate(x**y*distn, (x, 0, oo), conds='none')) == \
+ pi*alpha**y*y/beta/sin(pi*y/beta)
+
+ # weibull
+ k = Symbol('k', positive=True)
+ n = Symbol('n', positive=True)
+ distn = k/lamda*(x/lamda)**(k - 1)*exp(-(x/lamda)**k)
+ assert simplify(integrate(distn, (x, 0, oo))) == 1
+ assert simplify(integrate(x**n*distn, (x, 0, oo))) == \
+ lamda**n*gamma(1 + n/k)
+
+ # rice distribution
+ from sympy.functions.special.bessel import besseli
+ nu, sigma = symbols('nu sigma', positive=True)
+ rice = x/sigma**2*exp(-(x**2 + nu**2)/2/sigma**2)*besseli(0, x*nu/sigma**2)
+ assert integrate(rice, (x, 0, oo), meijerg=True) == 1
+ # can someone verify higher moments?
+
+ # Laplace distribution
+ mu = Symbol('mu', real=True)
+ b = Symbol('b', positive=True)
+ laplace = exp(-abs(x - mu)/b)/2/b
+ assert integrate(laplace, (x, -oo, oo), meijerg=True) == 1
+ assert integrate(x*laplace, (x, -oo, oo), meijerg=True) == mu
+ assert integrate(x**2*laplace, (x, -oo, oo), meijerg=True) == \
+ 2*b**2 + mu**2
+
+ # TODO are there other distributions supported on (-oo, oo) that we can do?
+
+ # misc tests
+ k = Symbol('k', positive=True)
+ assert gammasimp(expand_mul(integrate(log(x)*x**(k - 1)*exp(-x)/gamma(k),
+ (x, 0, oo)))) == polygamma(0, k)
+
+
+@slow
+def test_expint():
+ """ Test various exponential integrals. """
+ from sympy.core.symbol import Symbol
+ from sympy.functions.elementary.hyperbolic import sinh
+ from sympy.functions.special.error_functions import (Chi, Ci, Ei, Shi, Si, expint)
+ assert simplify(unpolarify(integrate(exp(-z*x)/x**y, (x, 1, oo),
+ meijerg=True, conds='none'
+ ).rewrite(expint).expand(func=True))) == expint(y, z)
+
+ assert integrate(exp(-z*x)/x, (x, 1, oo), meijerg=True,
+ conds='none').rewrite(expint).expand() == \
+ expint(1, z)
+ assert integrate(exp(-z*x)/x**2, (x, 1, oo), meijerg=True,
+ conds='none').rewrite(expint).expand() == \
+ expint(2, z).rewrite(Ei).rewrite(expint)
+ assert integrate(exp(-z*x)/x**3, (x, 1, oo), meijerg=True,
+ conds='none').rewrite(expint).expand() == \
+ expint(3, z).rewrite(Ei).rewrite(expint).expand()
+
+ t = Symbol('t', positive=True)
+ assert integrate(-cos(x)/x, (x, t, oo), meijerg=True).expand() == Ci(t)
+ assert integrate(-sin(x)/x, (x, t, oo), meijerg=True).expand() == \
+ Si(t) - pi/2
+ assert integrate(sin(x)/x, (x, 0, z), meijerg=True) == Si(z)
+ assert integrate(sinh(x)/x, (x, 0, z), meijerg=True) == Shi(z)
+ assert integrate(exp(-x)/x, x, meijerg=True).expand().rewrite(expint) == \
+ I*pi - expint(1, x)
+ assert integrate(exp(-x)/x**2, x, meijerg=True).rewrite(expint).expand() \
+ == expint(1, x) - exp(-x)/x - I*pi
+
+ u = Symbol('u', polar=True)
+ assert integrate(cos(u)/u, u, meijerg=True).expand().as_independent(u)[1] \
+ == Ci(u)
+ assert integrate(cosh(u)/u, u, meijerg=True).expand().as_independent(u)[1] \
+ == Chi(u)
+
+ assert integrate(expint(1, x), x, meijerg=True
+ ).rewrite(expint).expand() == x*expint(1, x) - exp(-x)
+ assert integrate(expint(2, x), x, meijerg=True
+ ).rewrite(expint).expand() == \
+ -x**2*expint(1, x)/2 + x*exp(-x)/2 - exp(-x)/2
+ assert simplify(unpolarify(integrate(expint(y, x), x,
+ meijerg=True).rewrite(expint).expand(func=True))) == \
+ -expint(y + 1, x)
+
+ assert integrate(Si(x), x, meijerg=True) == x*Si(x) + cos(x)
+ assert integrate(Ci(u), u, meijerg=True).expand() == u*Ci(u) - sin(u)
+ assert integrate(Shi(x), x, meijerg=True) == x*Shi(x) - cosh(x)
+ assert integrate(Chi(u), u, meijerg=True).expand() == u*Chi(u) - sinh(u)
+
+ assert integrate(Si(x)*exp(-x), (x, 0, oo), meijerg=True) == pi/4
+ assert integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True) == log(2)/2
+
+
+def test_messy():
+ from sympy.functions.elementary.hyperbolic import (acosh, acoth)
+ from sympy.functions.elementary.trigonometric import (asin, atan)
+ from sympy.functions.special.bessel import besselj
+ from sympy.functions.special.error_functions import (Chi, E1, Shi, Si)
+ from sympy.integrals.transforms import (fourier_transform, laplace_transform)
+ assert (laplace_transform(Si(x), x, s, simplify=True) ==
+ ((-atan(s) + pi/2)/s, 0, True))
+
+ assert laplace_transform(Shi(x), x, s, simplify=True) == (
+ acoth(s)/s, -oo, s**2 > 1)
+
+ # where should the logs be simplified?
+ assert laplace_transform(Chi(x), x, s, simplify=True) == (
+ (log(s**(-2)) - log(1 - 1/s**2))/(2*s), -oo, s**2 > 1)
+
+ # TODO maybe simplify the inequalities? when the simplification
+ # allows for generators instead of symbols this will work
+ assert laplace_transform(besselj(a, x), x, s)[1:] == \
+ (0, (re(a) > -2) & (re(a) > -1))
+
+ # NOTE s < 0 can be done, but argument reduction is not good enough yet
+ ans = fourier_transform(besselj(1, x)/x, x, s, noconds=False)
+ assert (ans[0].factor(deep=True).expand(), ans[1]) == \
+ (Piecewise((0, (s > 1/(2*pi)) | (s < -1/(2*pi))),
+ (2*sqrt(-4*pi**2*s**2 + 1), True)), s > 0)
+ # TODO FT(besselj(0,x)) - conditions are messy (but for acceptable reasons)
+ # - folding could be better
+
+ assert integrate(E1(x)*besselj(0, x), (x, 0, oo), meijerg=True) == \
+ log(1 + sqrt(2))
+ assert integrate(E1(x)*besselj(1, x), (x, 0, oo), meijerg=True) == \
+ log(S.Half + sqrt(2)/2)
+
+ assert integrate(1/x/sqrt(1 - x**2), x, meijerg=True) == \
+ Piecewise((-acosh(1/x), abs(x**(-2)) > 1), (I*asin(1/x), True))
+
+
+def test_issue_6122():
+ assert integrate(exp(-I*x**2), (x, -oo, oo), meijerg=True) == \
+ -I*sqrt(pi)*exp(I*pi/4)
+
+
+def test_issue_6252():
+ expr = 1/x/(a + b*x)**Rational(1, 3)
+ anti = integrate(expr, x, meijerg=True)
+ assert not anti.has(hyper)
+ # XXX the expression is a mess, but actually upon differentiation and
+ # putting in numerical values seems to work...
+
+
+def test_issue_6348():
+ assert integrate(exp(I*x)/(1 + x**2), (x, -oo, oo)).simplify().rewrite(exp) \
+ == pi*exp(-1)
+
+
+def test_fresnel():
+ from sympy.functions.special.error_functions import (fresnelc, fresnels)
+
+ assert expand_func(integrate(sin(pi*x**2/2), x)) == fresnels(x)
+ assert expand_func(integrate(cos(pi*x**2/2), x)) == fresnelc(x)
+
+
+def test_issue_6860():
+ assert meijerint_indefinite(x**x**x, x) is None
+
+
+def test_issue_7337():
+ f = meijerint_indefinite(x*sqrt(2*x + 3), x).together()
+ assert f == sqrt(2*x + 3)*(2*x**2 + x - 3)/5
+ assert f._eval_interval(x, S.NegativeOne, S.One) == Rational(2, 5)
+
+
+def test_issue_8368():
+ assert meijerint_indefinite(cosh(x)*exp(-x*t), x) == (
+ (-t - 1)*exp(x) + (-t + 1)*exp(-x))*exp(-t*x)/2/(t**2 - 1)
+
+
+def test_issue_10211():
+ from sympy.abc import h, w
+ assert integrate((1/sqrt((y-x)**2 + h**2)**3), (x,0,w), (y,0,w)) == \
+ 2*sqrt(1 + w**2/h**2)/h - 2/h
+
+
+def test_issue_11806():
+ from sympy.core.symbol import symbols
+ y, L = symbols('y L', positive=True)
+ assert integrate(1/sqrt(x**2 + y**2)**3, (x, -L, L)) == \
+ 2*L/(y**2*sqrt(L**2 + y**2))
+
+def test_issue_10681():
+ from sympy.polys.domains.realfield import RR
+ from sympy.abc import R, r
+ f = integrate(r**2*(R**2-r**2)**0.5, r, meijerg=True)
+ g = (1.0/3)*R**1.0*r**3*hyper((-0.5, Rational(3, 2)), (Rational(5, 2),),
+ r**2*exp_polar(2*I*pi)/R**2)
+ assert RR.almosteq((f/g).n(), 1.0, 1e-12)
+
+def test_issue_13536():
+ from sympy.core.symbol import Symbol
+ a = Symbol('a', positive=True)
+ assert integrate(1/x**2, (x, oo, a)) == -1/a
+
+
+def test_issue_6462():
+ from sympy.core.symbol import Symbol
+ x = Symbol('x')
+ n = Symbol('n')
+ # Not the actual issue, still wrong answer for n = 1, but that there is no
+ # exception
+ assert integrate(cos(x**n)/x**n, x, meijerg=True).subs(n, 2).equals(
+ integrate(cos(x**2)/x**2, x, meijerg=True))
+
+
+def test_indefinite_1_bug():
+ assert integrate((b + t)**(-a), t, meijerg=True) == -b*(1 + t/b)**(1 - a)/(a*b**a - b**a)
+
+
+def test_pr_23583():
+ # This result is wrong. Check whether new result is correct when this test fail.
+ assert integrate(1/sqrt((x - I)**2-1), meijerg=True) == \
+ Piecewise((acosh(x - I), Abs((x - I)**2) > 1), (-I*asin(x - I), True))
+
+
+# 25786
+def test_integrate_function_of_square_over_negatives():
+ assert integrate(exp(-x**2), (x,-5,0), meijerg=True) == sqrt(pi)/2 * erf(5)
+
+
+def test_issue_25949():
+ from sympy.core.symbol import symbols
+ y = symbols("y", nonzero=True)
+ assert integrate(cosh(y*(x + 1)), (x, -1, -0.25), meijerg=True) == sinh(0.75*y)/y
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_quadrature.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_quadrature.py
new file mode 100644
index 0000000000000000000000000000000000000000..97471dbdbc13fda0bce7a8823ff2cefac4ab8802
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_quadrature.py
@@ -0,0 +1,601 @@
+from sympy.core import S, Rational
+from sympy.integrals.quadrature import (gauss_legendre, gauss_laguerre,
+ gauss_hermite, gauss_gen_laguerre,
+ gauss_chebyshev_t, gauss_chebyshev_u,
+ gauss_jacobi, gauss_lobatto)
+
+
+def test_legendre():
+ x, w = gauss_legendre(1, 17)
+ assert [str(r) for r in x] == ['0']
+ assert [str(r) for r in w] == ['2.0000000000000000']
+
+ x, w = gauss_legendre(2, 17)
+ assert [str(r) for r in x] == [
+ '-0.57735026918962576',
+ '0.57735026918962576']
+ assert [str(r) for r in w] == [
+ '1.0000000000000000',
+ '1.0000000000000000']
+
+ x, w = gauss_legendre(3, 17)
+ assert [str(r) for r in x] == [
+ '-0.77459666924148338',
+ '0',
+ '0.77459666924148338']
+ assert [str(r) for r in w] == [
+ '0.55555555555555556',
+ '0.88888888888888889',
+ '0.55555555555555556']
+
+ x, w = gauss_legendre(4, 17)
+ assert [str(r) for r in x] == [
+ '-0.86113631159405258',
+ '-0.33998104358485626',
+ '0.33998104358485626',
+ '0.86113631159405258']
+ assert [str(r) for r in w] == [
+ '0.34785484513745386',
+ '0.65214515486254614',
+ '0.65214515486254614',
+ '0.34785484513745386']
+
+
+def test_legendre_precise():
+ x, w = gauss_legendre(3, 40)
+ assert [str(r) for r in x] == [
+ '-0.7745966692414833770358530799564799221666',
+ '0',
+ '0.7745966692414833770358530799564799221666']
+ assert [str(r) for r in w] == [
+ '0.5555555555555555555555555555555555555556',
+ '0.8888888888888888888888888888888888888889',
+ '0.5555555555555555555555555555555555555556']
+
+
+def test_laguerre():
+ x, w = gauss_laguerre(1, 17)
+ assert [str(r) for r in x] == ['1.0000000000000000']
+ assert [str(r) for r in w] == ['1.0000000000000000']
+
+ x, w = gauss_laguerre(2, 17)
+ assert [str(r) for r in x] == [
+ '0.58578643762690495',
+ '3.4142135623730950']
+ assert [str(r) for r in w] == [
+ '0.85355339059327376',
+ '0.14644660940672624']
+
+ x, w = gauss_laguerre(3, 17)
+ assert [str(r) for r in x] == [
+ '0.41577455678347908',
+ '2.2942803602790417',
+ '6.2899450829374792',
+ ]
+ assert [str(r) for r in w] == [
+ '0.71109300992917302',
+ '0.27851773356924085',
+ '0.010389256501586136',
+ ]
+
+ x, w = gauss_laguerre(4, 17)
+ assert [str(r) for r in x] == [
+ '0.32254768961939231',
+ '1.7457611011583466',
+ '4.5366202969211280',
+ '9.3950709123011331']
+ assert [str(r) for r in w] == [
+ '0.60315410434163360',
+ '0.35741869243779969',
+ '0.038887908515005384',
+ '0.00053929470556132745']
+
+ x, w = gauss_laguerre(5, 17)
+ assert [str(r) for r in x] == [
+ '0.26356031971814091',
+ '1.4134030591065168',
+ '3.5964257710407221',
+ '7.0858100058588376',
+ '12.640800844275783']
+ assert [str(r) for r in w] == [
+ '0.52175561058280865',
+ '0.39866681108317593',
+ '0.075942449681707595',
+ '0.0036117586799220485',
+ '2.3369972385776228e-5']
+
+
+def test_laguerre_precise():
+ x, w = gauss_laguerre(3, 40)
+ assert [str(r) for r in x] == [
+ '0.4157745567834790833115338731282744735466',
+ '2.294280360279041719822050361359593868960',
+ '6.289945082937479196866415765512131657493']
+ assert [str(r) for r in w] == [
+ '0.7110930099291730154495901911425944313094',
+ '0.2785177335692408488014448884567264810349',
+ '0.01038925650158613574896492040067908765572']
+
+
+def test_hermite():
+ x, w = gauss_hermite(1, 17)
+ assert [str(r) for r in x] == ['0']
+ assert [str(r) for r in w] == ['1.7724538509055160']
+
+ x, w = gauss_hermite(2, 17)
+ assert [str(r) for r in x] == [
+ '-0.70710678118654752',
+ '0.70710678118654752']
+ assert [str(r) for r in w] == [
+ '0.88622692545275801',
+ '0.88622692545275801']
+
+ x, w = gauss_hermite(3, 17)
+ assert [str(r) for r in x] == [
+ '-1.2247448713915890',
+ '0',
+ '1.2247448713915890']
+ assert [str(r) for r in w] == [
+ '0.29540897515091934',
+ '1.1816359006036774',
+ '0.29540897515091934']
+
+ x, w = gauss_hermite(4, 17)
+ assert [str(r) for r in x] == [
+ '-1.6506801238857846',
+ '-0.52464762327529032',
+ '0.52464762327529032',
+ '1.6506801238857846']
+ assert [str(r) for r in w] == [
+ '0.081312835447245177',
+ '0.80491409000551284',
+ '0.80491409000551284',
+ '0.081312835447245177']
+
+ x, w = gauss_hermite(5, 17)
+ assert [str(r) for r in x] == [
+ '-2.0201828704560856',
+ '-0.95857246461381851',
+ '0',
+ '0.95857246461381851',
+ '2.0201828704560856']
+ assert [str(r) for r in w] == [
+ '0.019953242059045913',
+ '0.39361932315224116',
+ '0.94530872048294188',
+ '0.39361932315224116',
+ '0.019953242059045913']
+
+
+def test_hermite_precise():
+ x, w = gauss_hermite(3, 40)
+ assert [str(r) for r in x] == [
+ '-1.224744871391589049098642037352945695983',
+ '0',
+ '1.224744871391589049098642037352945695983']
+ assert [str(r) for r in w] == [
+ '0.2954089751509193378830279138901908637996',
+ '1.181635900603677351532111655560763455198',
+ '0.2954089751509193378830279138901908637996']
+
+
+def test_gen_laguerre():
+ x, w = gauss_gen_laguerre(1, Rational(-1, 2), 17)
+ assert [str(r) for r in x] == ['0.50000000000000000']
+ assert [str(r) for r in w] == ['1.7724538509055160']
+
+ x, w = gauss_gen_laguerre(2, Rational(-1, 2), 17)
+ assert [str(r) for r in x] == [
+ '0.27525512860841095',
+ '2.7247448713915890']
+ assert [str(r) for r in w] == [
+ '1.6098281800110257',
+ '0.16262567089449035']
+
+ x, w = gauss_gen_laguerre(3, Rational(-1, 2), 17)
+ assert [str(r) for r in x] == [
+ '0.19016350919348813',
+ '1.7844927485432516',
+ '5.5253437422632603']
+ assert [str(r) for r in w] == [
+ '1.4492591904487850',
+ '0.31413464064571329',
+ '0.0090600198110176913']
+
+ x, w = gauss_gen_laguerre(4, Rational(-1, 2), 17)
+ assert [str(r) for r in x] == [
+ '0.14530352150331709',
+ '1.3390972881263614',
+ '3.9269635013582872',
+ '8.5886356890120343']
+ assert [str(r) for r in w] == [
+ '1.3222940251164826',
+ '0.41560465162978376',
+ '0.034155966014826951',
+ '0.00039920814442273524']
+
+ x, w = gauss_gen_laguerre(5, Rational(-1, 2), 17)
+ assert [str(r) for r in x] == [
+ '0.11758132021177814',
+ '1.0745620124369040',
+ '3.0859374437175500',
+ '6.4147297336620305',
+ '11.807189489971737']
+ assert [str(r) for r in w] == [
+ '1.2217252674706516',
+ '0.48027722216462937',
+ '0.067748788910962126',
+ '0.0026872914935624654',
+ '1.5280865710465241e-5']
+
+ x, w = gauss_gen_laguerre(1, 2, 17)
+ assert [str(r) for r in x] == ['3.0000000000000000']
+ assert [str(r) for r in w] == ['2.0000000000000000']
+
+ x, w = gauss_gen_laguerre(2, 2, 17)
+ assert [str(r) for r in x] == [
+ '2.0000000000000000',
+ '6.0000000000000000']
+ assert [str(r) for r in w] == [
+ '1.5000000000000000',
+ '0.50000000000000000']
+
+ x, w = gauss_gen_laguerre(3, 2, 17)
+ assert [str(r) for r in x] == [
+ '1.5173870806774125',
+ '4.3115831337195203',
+ '9.1710297856030672']
+ assert [str(r) for r in w] == [
+ '1.0374949614904253',
+ '0.90575000470306537',
+ '0.056755033806509347']
+
+ x, w = gauss_gen_laguerre(4, 2, 17)
+ assert [str(r) for r in x] == [
+ '1.2267632635003021',
+ '3.4125073586969460',
+ '6.9026926058516134',
+ '12.458036771951139']
+ assert [str(r) for r in w] == [
+ '0.72552499769865438',
+ '1.0634242919791946',
+ '0.20669613102835355',
+ '0.0043545792937974889']
+
+ x, w = gauss_gen_laguerre(5, 2, 17)
+ assert [str(r) for r in x] == [
+ '1.0311091440933816',
+ '2.8372128239538217',
+ '5.6202942725987079',
+ '9.6829098376640271',
+ '15.828473921690062']
+ assert [str(r) for r in w] == [
+ '0.52091739683509184',
+ '1.0667059331592211',
+ '0.38354972366693113',
+ '0.028564233532974658',
+ '0.00026271280578124935']
+
+
+def test_gen_laguerre_precise():
+ x, w = gauss_gen_laguerre(3, Rational(-1, 2), 40)
+ assert [str(r) for r in x] == [
+ '0.1901635091934881328718554276203028970878',
+ '1.784492748543251591186722461957367638500',
+ '5.525343742263260275941422110422329464413']
+ assert [str(r) for r in w] == [
+ '1.449259190448785048183829411195134343108',
+ '0.3141346406457132878326231270167565378246',
+ '0.009060019811017691281714945129254301865020']
+
+ x, w = gauss_gen_laguerre(3, 2, 40)
+ assert [str(r) for r in x] == [
+ '1.517387080677412495020323111016672547482',
+ '4.311583133719520302881184669723530562299',
+ '9.171029785603067202098492219259796890218']
+ assert [str(r) for r in w] == [
+ '1.037494961490425285817554606541269153041',
+ '0.9057500047030653669269785048806009945254',
+ '0.05675503380650934725546688857812985243312']
+
+
+def test_chebyshev_t():
+ x, w = gauss_chebyshev_t(1, 17)
+ assert [str(r) for r in x] == ['0']
+ assert [str(r) for r in w] == ['3.1415926535897932']
+
+ x, w = gauss_chebyshev_t(2, 17)
+ assert [str(r) for r in x] == [
+ '0.70710678118654752',
+ '-0.70710678118654752']
+ assert [str(r) for r in w] == [
+ '1.5707963267948966',
+ '1.5707963267948966']
+
+ x, w = gauss_chebyshev_t(3, 17)
+ assert [str(r) for r in x] == [
+ '0.86602540378443865',
+ '0',
+ '-0.86602540378443865']
+ assert [str(r) for r in w] == [
+ '1.0471975511965977',
+ '1.0471975511965977',
+ '1.0471975511965977']
+
+ x, w = gauss_chebyshev_t(4, 17)
+ assert [str(r) for r in x] == [
+ '0.92387953251128676',
+ '0.38268343236508977',
+ '-0.38268343236508977',
+ '-0.92387953251128676']
+ assert [str(r) for r in w] == [
+ '0.78539816339744831',
+ '0.78539816339744831',
+ '0.78539816339744831',
+ '0.78539816339744831']
+
+ x, w = gauss_chebyshev_t(5, 17)
+ assert [str(r) for r in x] == [
+ '0.95105651629515357',
+ '0.58778525229247313',
+ '0',
+ '-0.58778525229247313',
+ '-0.95105651629515357']
+ assert [str(r) for r in w] == [
+ '0.62831853071795865',
+ '0.62831853071795865',
+ '0.62831853071795865',
+ '0.62831853071795865',
+ '0.62831853071795865']
+
+
+def test_chebyshev_t_precise():
+ x, w = gauss_chebyshev_t(3, 40)
+ assert [str(r) for r in x] == [
+ '0.8660254037844386467637231707529361834714',
+ '0',
+ '-0.8660254037844386467637231707529361834714']
+ assert [str(r) for r in w] == [
+ '1.047197551196597746154214461093167628066',
+ '1.047197551196597746154214461093167628066',
+ '1.047197551196597746154214461093167628066']
+
+
+def test_chebyshev_u():
+ x, w = gauss_chebyshev_u(1, 17)
+ assert [str(r) for r in x] == ['0']
+ assert [str(r) for r in w] == ['1.5707963267948966']
+
+ x, w = gauss_chebyshev_u(2, 17)
+ assert [str(r) for r in x] == [
+ '0.50000000000000000',
+ '-0.50000000000000000']
+ assert [str(r) for r in w] == [
+ '0.78539816339744831',
+ '0.78539816339744831']
+
+ x, w = gauss_chebyshev_u(3, 17)
+ assert [str(r) for r in x] == [
+ '0.70710678118654752',
+ '0',
+ '-0.70710678118654752']
+ assert [str(r) for r in w] == [
+ '0.39269908169872415',
+ '0.78539816339744831',
+ '0.39269908169872415']
+
+ x, w = gauss_chebyshev_u(4, 17)
+ assert [str(r) for r in x] == [
+ '0.80901699437494742',
+ '0.30901699437494742',
+ '-0.30901699437494742',
+ '-0.80901699437494742']
+ assert [str(r) for r in w] == [
+ '0.21707871342270599',
+ '0.56831944997474231',
+ '0.56831944997474231',
+ '0.21707871342270599']
+
+ x, w = gauss_chebyshev_u(5, 17)
+ assert [str(r) for r in x] == [
+ '0.86602540378443865',
+ '0.50000000000000000',
+ '0',
+ '-0.50000000000000000',
+ '-0.86602540378443865']
+ assert [str(r) for r in w] == [
+ '0.13089969389957472',
+ '0.39269908169872415',
+ '0.52359877559829887',
+ '0.39269908169872415',
+ '0.13089969389957472']
+
+
+def test_chebyshev_u_precise():
+ x, w = gauss_chebyshev_u(3, 40)
+ assert [str(r) for r in x] == [
+ '0.7071067811865475244008443621048490392848',
+ '0',
+ '-0.7071067811865475244008443621048490392848']
+ assert [str(r) for r in w] == [
+ '0.3926990816987241548078304229099378605246',
+ '0.7853981633974483096156608458198757210493',
+ '0.3926990816987241548078304229099378605246']
+
+
+def test_jacobi():
+ x, w = gauss_jacobi(1, Rational(-1, 2), S.Half, 17)
+ assert [str(r) for r in x] == ['0.50000000000000000']
+ assert [str(r) for r in w] == ['3.1415926535897932']
+
+ x, w = gauss_jacobi(2, Rational(-1, 2), S.Half, 17)
+ assert [str(r) for r in x] == [
+ '-0.30901699437494742',
+ '0.80901699437494742']
+ assert [str(r) for r in w] == [
+ '0.86831485369082398',
+ '2.2732777998989693']
+
+ x, w = gauss_jacobi(3, Rational(-1, 2), S.Half, 17)
+ assert [str(r) for r in x] == [
+ '-0.62348980185873353',
+ '0.22252093395631440',
+ '0.90096886790241913']
+ assert [str(r) for r in w] == [
+ '0.33795476356635433',
+ '1.0973322242791115',
+ '1.7063056657443274']
+
+ x, w = gauss_jacobi(4, Rational(-1, 2), S.Half, 17)
+ assert [str(r) for r in x] == [
+ '-0.76604444311897804',
+ '-0.17364817766693035',
+ '0.50000000000000000',
+ '0.93969262078590838']
+ assert [str(r) for r in w] == [
+ '0.16333179083642836',
+ '0.57690240318269103',
+ '1.0471975511965977',
+ '1.3541609083740761']
+
+ x, w = gauss_jacobi(5, Rational(-1, 2), S.Half, 17)
+ assert [str(r) for r in x] == [
+ '-0.84125353283118117',
+ '-0.41541501300188643',
+ '0.14231483827328514',
+ '0.65486073394528506',
+ '0.95949297361449739']
+ assert [str(r) for r in w] == [
+ '0.090675770007435372',
+ '0.33391416373675607',
+ '0.65248870981926643',
+ '0.94525424081394926',
+ '1.1192597692123861']
+
+ x, w = gauss_jacobi(1, 2, 3, 17)
+ assert [str(r) for r in x] == ['0.14285714285714286']
+ assert [str(r) for r in w] == ['1.0666666666666667']
+
+ x, w = gauss_jacobi(2, 2, 3, 17)
+ assert [str(r) for r in x] == [
+ '-0.24025307335204215',
+ '0.46247529557426437']
+ assert [str(r) for r in w] == [
+ '0.48514624517838660',
+ '0.58152042148828007']
+
+ x, w = gauss_jacobi(3, 2, 3, 17)
+ assert [str(r) for r in x] == [
+ '-0.46115870378089762',
+ '0.10438533038323902',
+ '0.62950064612493132']
+ assert [str(r) for r in w] == [
+ '0.17937613502213266',
+ '0.61595640991147154',
+ '0.27133412173306246']
+
+ x, w = gauss_jacobi(4, 2, 3, 17)
+ assert [str(r) for r in x] == [
+ '-0.59903470850824782',
+ '-0.14761105199952565',
+ '0.32554377081188859',
+ '0.72879429738819258']
+ assert [str(r) for r in w] == [
+ '0.067809641836772187',
+ '0.38956404952032481',
+ '0.47995970868024150',
+ '0.12933326662932816']
+
+ x, w = gauss_jacobi(5, 2, 3, 17)
+ assert [str(r) for r in x] == [
+ '-0.69045775012676106',
+ '-0.32651993134900065',
+ '0.082337849552034905',
+ '0.47517887061283164',
+ '0.79279429464422850']
+ assert [str(r) for r in w] == [
+ '0.027410178066337099',
+ '0.21291786060364828',
+ '0.43908437944395081',
+ '0.32220656547221822',
+ '0.065047683080512268']
+
+
+def test_jacobi_precise():
+ x, w = gauss_jacobi(3, Rational(-1, 2), S.Half, 40)
+ assert [str(r) for r in x] == [
+ '-0.6234898018587335305250048840042398106323',
+ '0.2225209339563144042889025644967947594664',
+ '0.9009688679024191262361023195074450511659']
+ assert [str(r) for r in w] == [
+ '0.3379547635663543330553835737094171534907',
+ '1.097332224279111467485302294320899710461',
+ '1.706305665744327437921957515249186020246']
+
+ x, w = gauss_jacobi(3, 2, 3, 40)
+ assert [str(r) for r in x] == [
+ '-0.4611587037808976179121958105554375981274',
+ '0.1043853303832390210914918407615869143233',
+ '0.6295006461249313240934312425211234110769']
+ assert [str(r) for r in w] == [
+ '0.1793761350221326596137764371503859752628',
+ '0.6159564099114715430909548532229749439714',
+ '0.2713341217330624639619353762933057474325']
+
+
+def test_lobatto():
+ x, w = gauss_lobatto(2, 17)
+ assert [str(r) for r in x] == [
+ '-1',
+ '1']
+ assert [str(r) for r in w] == [
+ '1.0000000000000000',
+ '1.0000000000000000']
+
+ x, w = gauss_lobatto(3, 17)
+ assert [str(r) for r in x] == [
+ '-1',
+ '0',
+ '1']
+ assert [str(r) for r in w] == [
+ '0.33333333333333333',
+ '1.3333333333333333',
+ '0.33333333333333333']
+
+ x, w = gauss_lobatto(4, 17)
+ assert [str(r) for r in x] == [
+ '-1',
+ '-0.44721359549995794',
+ '0.44721359549995794',
+ '1']
+ assert [str(r) for r in w] == [
+ '0.16666666666666667',
+ '0.83333333333333333',
+ '0.83333333333333333',
+ '0.16666666666666667']
+
+ x, w = gauss_lobatto(5, 17)
+ assert [str(r) for r in x] == [
+ '-1',
+ '-0.65465367070797714',
+ '0',
+ '0.65465367070797714',
+ '1']
+ assert [str(r) for r in w] == [
+ '0.10000000000000000',
+ '0.54444444444444444',
+ '0.71111111111111111',
+ '0.54444444444444444',
+ '0.10000000000000000']
+
+
+def test_lobatto_precise():
+ x, w = gauss_lobatto(3, 40)
+ assert [str(r) for r in x] == [
+ '-1',
+ '0',
+ '1']
+ assert [str(r) for r in w] == [
+ '0.3333333333333333333333333333333333333333',
+ '1.333333333333333333333333333333333333333',
+ '0.3333333333333333333333333333333333333333']
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_rationaltools.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_rationaltools.py
new file mode 100644
index 0000000000000000000000000000000000000000..809bf30c1c35f80e2a0b15c1e639603eab28a250
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_rationaltools.py
@@ -0,0 +1,183 @@
+from sympy.core.numbers import (I, Rational)
+from sympy.core.singleton import S
+from sympy.core.symbol import (Dummy, symbols)
+from sympy.functions.elementary.exponential import log
+from sympy.functions.elementary.miscellaneous import sqrt
+from sympy.functions.elementary.trigonometric import atan
+from sympy.integrals.integrals import integrate
+from sympy.polys.polytools import Poly
+from sympy.simplify.simplify import simplify
+
+from sympy.integrals.rationaltools import ratint, ratint_logpart, log_to_atan
+
+from sympy.abc import a, b, x, t
+
+half = S.Half
+
+
+def test_ratint():
+ assert ratint(S.Zero, x) == 0
+ assert ratint(S(7), x) == 7*x
+
+ assert ratint(x, x) == x**2/2
+ assert ratint(2*x, x) == x**2
+ assert ratint(-2*x, x) == -x**2
+
+ assert ratint(8*x**7 + 2*x + 1, x) == x**8 + x**2 + x
+
+ f = S.One
+ g = x + 1
+
+ assert ratint(f / g, x) == log(x + 1)
+ assert ratint((f, g), x) == log(x + 1)
+
+ f = x**3 - x
+ g = x - 1
+
+ assert ratint(f/g, x) == x**3/3 + x**2/2
+
+ f = x
+ g = (x - a)*(x + a)
+
+ assert ratint(f/g, x) == log(x**2 - a**2)/2
+
+ f = S.One
+ g = x**2 + 1
+
+ assert ratint(f/g, x, real=None) == atan(x)
+ assert ratint(f/g, x, real=True) == atan(x)
+
+ assert ratint(f/g, x, real=False) == I*log(x + I)/2 - I*log(x - I)/2
+
+ f = S(36)
+ g = x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2
+
+ assert ratint(f/g, x) == \
+ -4*log(x + 1) + 4*log(x - 2) + (12*x + 6)/(x**2 - 1)
+
+ f = x**4 - 3*x**2 + 6
+ g = x**6 - 5*x**4 + 5*x**2 + 4
+
+ assert ratint(f/g, x) == \
+ atan(x) + atan(x**3) + atan(x/2 - Rational(3, 2)*x**3 + S.Half*x**5)
+
+ f = x**7 - 24*x**4 - 4*x**2 + 8*x - 8
+ g = x**8 + 6*x**6 + 12*x**4 + 8*x**2
+
+ assert ratint(f/g, x) == \
+ (4 + 6*x + 8*x**2 + 3*x**3)/(4*x + 4*x**3 + x**5) + log(x)
+
+ assert ratint((x**3*f)/(x*g), x) == \
+ -(12 - 16*x + 6*x**2 - 14*x**3)/(4 + 4*x**2 + x**4) - \
+ 5*sqrt(2)*atan(x*sqrt(2)/2) + S.Half*x**2 - 3*log(2 + x**2)
+
+ f = x**5 - x**4 + 4*x**3 + x**2 - x + 5
+ g = x**4 - 2*x**3 + 5*x**2 - 4*x + 4
+
+ assert ratint(f/g, x) == \
+ x + S.Half*x**2 + S.Half*log(2 - x + x**2) + (9 - 4*x)/(7*x**2 - 7*x + 14) + \
+ 13*sqrt(7)*atan(Rational(-1, 7)*sqrt(7) + 2*x*sqrt(7)/7)/49
+
+ assert ratint(1/(x**2 + x + 1), x) == \
+ 2*sqrt(3)*atan(sqrt(3)/3 + 2*x*sqrt(3)/3)/3
+
+ assert ratint(1/(x**3 + 1), x) == \
+ -log(1 - x + x**2)/6 + log(1 + x)/3 + sqrt(3)*atan(-sqrt(3)
+ /3 + 2*x*sqrt(3)/3)/3
+
+ assert ratint(1/(x**2 + x + 1), x, real=False) == \
+ -I*3**half*log(half + x - half*I*3**half)/3 + \
+ I*3**half*log(half + x + half*I*3**half)/3
+
+ assert ratint(1/(x**3 + 1), x, real=False) == log(1 + x)/3 + \
+ (Rational(-1, 6) + I*3**half/6)*log(-half + x + I*3**half/2) + \
+ (Rational(-1, 6) - I*3**half/6)*log(-half + x - I*3**half/2)
+
+ # issue 4991
+ assert ratint(1/(x*(a + b*x)**3), x) == \
+ (3*a + 2*b*x)/(2*a**4 + 4*a**3*b*x + 2*a**2*b**2*x**2) + (
+ log(x) - log(a/b + x))/a**3
+
+ assert ratint(x/(1 - x**2), x) == -log(x**2 - 1)/2
+ assert ratint(-x/(1 - x**2), x) == log(x**2 - 1)/2
+
+ assert ratint((x/4 - 4/(1 - x)).diff(x), x) == x/4 + 4/(x - 1)
+
+ ans = atan(x)
+ assert ratint(1/(x**2 + 1), x, symbol=x) == ans
+ assert ratint(1/(x**2 + 1), x, symbol='x') == ans
+ assert ratint(1/(x**2 + 1), x, symbol=a) == ans
+ # this asserts that as_dummy must return a unique symbol
+ # even if the symbol is already a Dummy
+ d = Dummy()
+ assert ratint(1/(d**2 + 1), d, symbol=d) == atan(d)
+
+
+def test_ratint_logpart():
+ assert ratint_logpart(x, x**2 - 9, x, t) == \
+ [(Poly(x**2 - 9, x), Poly(-2*t + 1, t))]
+ assert ratint_logpart(x**2, x**3 - 5, x, t) == \
+ [(Poly(x**3 - 5, x), Poly(-3*t + 1, t))]
+
+
+def test_issue_5414():
+ assert ratint(1/(x**2 + 16), x) == atan(x/4)/4
+
+
+def test_issue_5249():
+ assert ratint(
+ 1/(x**2 + a**2), x) == (-I*log(-I*a + x)/2 + I*log(I*a + x)/2)/a
+
+
+def test_issue_5817():
+ a, b, c = symbols('a,b,c', positive=True)
+
+ assert simplify(ratint(a/(b*c*x**2 + a**2 + b*a), x)) == \
+ sqrt(a)*atan(sqrt(
+ b)*sqrt(c)*x/(sqrt(a)*sqrt(a + b)))/(sqrt(b)*sqrt(c)*sqrt(a + b))
+
+
+def test_issue_5981():
+ u = symbols('u')
+ assert integrate(1/(u**2 + 1)) == atan(u)
+
+def test_issue_10488():
+ a,b,c,x = symbols('a b c x', positive=True)
+ assert integrate(x/(a*x+b),x) == x/a - b*log(a*x + b)/a**2
+
+
+def test_issues_8246_12050_13501_14080():
+ a = symbols('a', nonzero=True)
+ assert integrate(a/(x**2 + a**2), x) == atan(x/a)
+ assert integrate(1/(x**2 + a**2), x) == atan(x/a)/a
+ assert integrate(1/(1 + a**2*x**2), x) == atan(a*x)/a
+
+
+def test_issue_6308():
+ k, a0 = symbols('k a0', real=True)
+ assert integrate((x**2 + 1 - k**2)/(x**2 + 1 + a0**2), x) == \
+ x - (a0**2 + k**2)*atan(x/sqrt(a0**2 + 1))/sqrt(a0**2 + 1)
+
+
+def test_issue_5907():
+ a = symbols('a', nonzero=True)
+ assert integrate(1/(x**2 + a**2)**2, x) == \
+ x/(2*a**4 + 2*a**2*x**2) + atan(x/a)/(2*a**3)
+
+
+def test_log_to_atan():
+ f, g = (Poly(x + S.Half, x, domain='QQ'), Poly(sqrt(3)/2, x, domain='EX'))
+ fg_ans = 2*atan(2*sqrt(3)*x/3 + sqrt(3)/3)
+ assert log_to_atan(f, g) == fg_ans
+ assert log_to_atan(g, f) == -fg_ans
+
+
+def test_issue_25896():
+ # for both tests, C = 0 in log_to_real
+ # but this only has a log result
+ e = (2*x + 1)/(x**2 + x + 1) + 1/x
+ assert ratint(e, x) == log(x**3 + x**2 + x)
+ # while this has more
+ assert ratint((4*x + 7)/(x**2 + 4*x + 6) + 2/x, x) == (
+ 2*log(x) + 2*log(x**2 + 4*x + 6) - sqrt(2)*atan(
+ sqrt(2)*x/2 + sqrt(2))/2)
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_rde.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_rde.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c7df5ce05846dc270756cd878870bbff78ff976
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_rde.py
@@ -0,0 +1,202 @@
+"""Most of these tests come from the examples in Bronstein's book."""
+from sympy.core.numbers import (I, Rational, oo)
+from sympy.core.symbol import symbols
+from sympy.polys.polytools import Poly
+from sympy.integrals.risch import (DifferentialExtension,
+ NonElementaryIntegralException)
+from sympy.integrals.rde import (order_at, order_at_oo, weak_normalizer,
+ normal_denom, special_denom, bound_degree, spde, solve_poly_rde,
+ no_cancel_equal, cancel_primitive, cancel_exp, rischDE)
+
+from sympy.testing.pytest import raises
+from sympy.abc import x, t, z, n
+
+t0, t1, t2, k = symbols('t:3 k')
+
+
+def test_order_at():
+ a = Poly(t**4, t)
+ b = Poly((t**2 + 1)**3*t, t)
+ c = Poly((t**2 + 1)**6*t, t)
+ d = Poly((t**2 + 1)**10*t**10, t)
+ e = Poly((t**2 + 1)**100*t**37, t)
+ p1 = Poly(t, t)
+ p2 = Poly(1 + t**2, t)
+ assert order_at(a, p1, t) == 4
+ assert order_at(b, p1, t) == 1
+ assert order_at(c, p1, t) == 1
+ assert order_at(d, p1, t) == 10
+ assert order_at(e, p1, t) == 37
+ assert order_at(a, p2, t) == 0
+ assert order_at(b, p2, t) == 3
+ assert order_at(c, p2, t) == 6
+ assert order_at(d, p1, t) == 10
+ assert order_at(e, p2, t) == 100
+ assert order_at(Poly(0, t), Poly(t, t), t) is oo
+ assert order_at_oo(Poly(t**2 - 1, t), Poly(t + 1), t) == \
+ order_at_oo(Poly(t - 1, t), Poly(1, t), t) == -1
+ assert order_at_oo(Poly(0, t), Poly(1, t), t) is oo
+
+def test_weak_normalizer():
+ a = Poly((1 + x)*t**5 + 4*t**4 + (-1 - 3*x)*t**3 - 4*t**2 + (-2 + 2*x)*t, t)
+ d = Poly(t**4 - 3*t**2 + 2, t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ r = weak_normalizer(a, d, DE, z)
+ assert r == (Poly(t**5 - t**4 - 4*t**3 + 4*t**2 + 4*t - 4, t, domain='ZZ[x]'),
+ (Poly((1 + x)*t**2 + x*t, t, domain='ZZ[x]'),
+ Poly(t + 1, t, domain='ZZ[x]')))
+ assert weak_normalizer(r[1][0], r[1][1], DE) == (Poly(1, t), r[1])
+ r = weak_normalizer(Poly(1 + t**2), Poly(t**2 - 1, t), DE, z)
+ assert r == (Poly(t**4 - 2*t**2 + 1, t), (Poly(-3*t**2 + 1, t), Poly(t**2 - 1, t)))
+ assert weak_normalizer(r[1][0], r[1][1], DE, z) == (Poly(1, t), r[1])
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2)]})
+ r = weak_normalizer(Poly(1 + t**2), Poly(t, t), DE, z)
+ assert r == (Poly(t, t), (Poly(0, t), Poly(1, t)))
+ assert weak_normalizer(r[1][0], r[1][1], DE, z) == (Poly(1, t), r[1])
+
+
+def test_normal_denom():
+ DE = DifferentialExtension(extension={'D': [Poly(1, x)]})
+ raises(NonElementaryIntegralException, lambda: normal_denom(Poly(1, x), Poly(1, x),
+ Poly(1, x), Poly(x, x), DE))
+ fa, fd = Poly(t**2 + 1, t), Poly(1, t)
+ ga, gd = Poly(1, t), Poly(t**2, t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ assert normal_denom(fa, fd, ga, gd, DE) == \
+ (Poly(t, t), (Poly(t**3 - t**2 + t - 1, t), Poly(1, t)), (Poly(1, t),
+ Poly(1, t)), Poly(t, t))
+
+
+def test_special_denom():
+ # TODO: add more tests here
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ assert special_denom(Poly(1, t), Poly(t**2, t), Poly(1, t), Poly(t**2 - 1, t),
+ Poly(t, t), DE) == \
+ (Poly(1, t), Poly(t**2 - 1, t), Poly(t**2 - 1, t), Poly(t, t))
+# assert special_denom(Poly(1, t), Poly(2*x, t), Poly((1 + 2*x)*t, t), DE) == 1
+
+ # issue 3940
+ # Note, this isn't a very good test, because the denominator is just 1,
+ # but at least it tests the exp cancellation case
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-2*x*t0, t0),
+ Poly(I*k*t1, t1)]})
+ DE.decrement_level()
+ assert special_denom(Poly(1, t0), Poly(I*k, t0), Poly(1, t0), Poly(t0, t0),
+ Poly(1, t0), DE) == \
+ (Poly(1, t0, domain='ZZ'), Poly(I*k, t0, domain='ZZ_I[k,x]'),
+ Poly(t0, t0, domain='ZZ'), Poly(1, t0, domain='ZZ'))
+
+
+ assert special_denom(Poly(1, t), Poly(t**2, t), Poly(1, t), Poly(t**2 - 1, t),
+ Poly(t, t), DE, case='tan') == \
+ (Poly(1, t, t0, domain='ZZ'), Poly(t**2, t0, t, domain='ZZ[x]'),
+ Poly(t, t, t0, domain='ZZ'), Poly(1, t0, domain='ZZ'))
+
+ raises(ValueError, lambda: special_denom(Poly(1, t), Poly(t**2, t), Poly(1, t), Poly(t**2 - 1, t),
+ Poly(t, t), DE, case='unrecognized_case'))
+
+
+def test_bound_degree_fail():
+ # Primitive
+ DE = DifferentialExtension(extension={'D': [Poly(1, x),
+ Poly(t0/x**2, t0), Poly(1/x, t)]})
+ assert bound_degree(Poly(t**2, t), Poly(-(1/x**2*t**2 + 1/x), t),
+ Poly((2*x - 1)*t**4 + (t0 + x)/x*t**3 - (t0 + 4*x**2)/2*x*t**2 + x*t,
+ t), DE) == 3
+
+
+def test_bound_degree():
+ # Base
+ DE = DifferentialExtension(extension={'D': [Poly(1, x)]})
+ assert bound_degree(Poly(1, x), Poly(-2*x, x), Poly(1, x), DE) == 0
+
+ # Primitive (see above test_bound_degree_fail)
+ # TODO: Add test for when the degree bound becomes larger after limited_integrate
+ # TODO: Add test for db == da - 1 case
+
+ # Exp
+ # TODO: Add tests
+ # TODO: Add test for when the degree becomes larger after parametric_log_deriv()
+
+ # Nonlinear
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ assert bound_degree(Poly(t, t), Poly((t - 1)*(t**2 + 1), t), Poly(1, t), DE) == 0
+
+
+def test_spde():
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ raises(NonElementaryIntegralException, lambda: spde(Poly(t, t), Poly((t - 1)*(t**2 + 1), t), Poly(1, t), 0, DE))
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ assert spde(Poly(t**2 + x*t*2 + x**2, t), Poly(t**2/x**2 + (2/x - 1)*t, t),
+ Poly(t**2/x**2 + (2/x - 1)*t, t), 0, DE) == \
+ (Poly(0, t), Poly(0, t), 0, Poly(0, t), Poly(1, t, domain='ZZ(x)'))
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t0/x**2, t0), Poly(1/x, t)]})
+ assert spde(Poly(t**2, t), Poly(-t**2/x**2 - 1/x, t),
+ Poly((2*x - 1)*t**4 + (t0 + x)/x*t**3 - (t0 + 4*x**2)/(2*x)*t**2 + x*t, t), 3, DE) == \
+ (Poly(0, t), Poly(0, t), 0, Poly(0, t),
+ Poly(t0*t**2/2 + x**2*t**2 - x**2*t, t, domain='ZZ(x,t0)'))
+ DE = DifferentialExtension(extension={'D': [Poly(1, x)]})
+ assert spde(Poly(x**2 + x + 1, x), Poly(-2*x - 1, x), Poly(x**5/2 +
+ 3*x**4/4 + x**3 - x**2 + 1, x), 4, DE) == \
+ (Poly(0, x, domain='QQ'), Poly(x/2 - Rational(1, 4), x), 2, Poly(x**2 + x + 1, x), Poly(x*Rational(5, 4), x))
+ assert spde(Poly(x**2 + x + 1, x), Poly(-2*x - 1, x), Poly(x**5/2 +
+ 3*x**4/4 + x**3 - x**2 + 1, x), n, DE) == \
+ (Poly(0, x, domain='QQ'), Poly(x/2 - Rational(1, 4), x), -2 + n, Poly(x**2 + x + 1, x), Poly(x*Rational(5, 4), x))
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1, t)]})
+ raises(NonElementaryIntegralException, lambda: spde(Poly((t - 1)*(t**2 + 1)**2, t), Poly((t - 1)*(t**2 + 1), t), Poly(1, t), 0, DE))
+ DE = DifferentialExtension(extension={'D': [Poly(1, x)]})
+ assert spde(Poly(x**2 - x, x), Poly(1, x), Poly(9*x**4 - 10*x**3 + 2*x**2, x), 4, DE) == \
+ (Poly(0, x, domain='ZZ'), Poly(0, x), 0, Poly(0, x), Poly(3*x**3 - 2*x**2, x, domain='QQ'))
+ assert spde(Poly(x**2 - x, x), Poly(x**2 - 5*x + 3, x), Poly(x**7 - x**6 - 2*x**4 + 3*x**3 - x**2, x), 5, DE) == \
+ (Poly(1, x, domain='QQ'), Poly(x + 1, x, domain='QQ'), 1, Poly(x**4 - x**3, x), Poly(x**3 - x**2, x, domain='QQ'))
+
+def test_solve_poly_rde_no_cancel():
+ # deg(b) large
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]})
+ assert solve_poly_rde(Poly(t**2 + 1, t), Poly(t**3 + (x + 1)*t**2 + t + x + 2, t),
+ oo, DE) == Poly(t + x, t)
+ # deg(b) small
+ DE = DifferentialExtension(extension={'D': [Poly(1, x)]})
+ assert solve_poly_rde(Poly(0, x), Poly(x/2 - Rational(1, 4), x), oo, DE) == \
+ Poly(x**2/4 - x/4, x)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ assert solve_poly_rde(Poly(2, t), Poly(t**2 + 2*t + 3, t), 1, DE) == \
+ Poly(t + 1, t, x)
+ # deg(b) == deg(D) - 1
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ assert no_cancel_equal(Poly(1 - t, t),
+ Poly(t**3 + t**2 - 2*x*t - 2*x, t), oo, DE) == \
+ (Poly(t**2, t), 1, Poly((-2 - 2*x)*t - 2*x, t))
+
+
+def test_solve_poly_rde_cancel():
+ # exp
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ assert cancel_exp(Poly(2*x, t), Poly(2*x, t), 0, DE) == \
+ Poly(1, t)
+ assert cancel_exp(Poly(2*x, t), Poly((1 + 2*x)*t, t), 1, DE) == \
+ Poly(t, t)
+ # TODO: Add more exp tests, including tests that require is_deriv_in_field()
+
+ # primitive
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]})
+
+ # If the DecrementLevel context manager is working correctly, this shouldn't
+ # cause any problems with the further tests.
+ raises(NonElementaryIntegralException, lambda: cancel_primitive(Poly(1, t), Poly(t, t), oo, DE))
+
+ assert cancel_primitive(Poly(1, t), Poly(t + 1/x, t), 2, DE) == \
+ Poly(t, t)
+ assert cancel_primitive(Poly(4*x, t), Poly(4*x*t**2 + 2*t/x, t), 3, DE) == \
+ Poly(t**2, t)
+
+ # TODO: Add more primitive tests, including tests that require is_deriv_in_field()
+
+
+def test_rischDE():
+ # TODO: Add more tests for rischDE, including ones from the text
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ DE.decrement_level()
+ assert rischDE(Poly(-2*x, x), Poly(1, x), Poly(1 - 2*x - 2*x**2, x),
+ Poly(1, x), DE) == \
+ (Poly(x + 1, x), Poly(1, x))
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_risch.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_risch.py
new file mode 100644
index 0000000000000000000000000000000000000000..68be260e1f3d42fb14c790afe6bda1768e777666
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_risch.py
@@ -0,0 +1,763 @@
+"""Most of these tests come from the examples in Bronstein's book."""
+from sympy.core.function import (Function, Lambda, diff, expand_log)
+from sympy.core.numbers import (I, Rational, pi)
+from sympy.core.relational import Ne
+from sympy.core.singleton import S
+from sympy.core.symbol import (Symbol, symbols)
+from sympy.functions.elementary.exponential import (exp, log)
+from sympy.functions.elementary.miscellaneous import sqrt
+from sympy.functions.elementary.piecewise import Piecewise
+from sympy.functions.elementary.trigonometric import (atan, sin, tan)
+from sympy.polys.polytools import (Poly, cancel, factor)
+from sympy.integrals.risch import (gcdex_diophantine, frac_in, as_poly_1t,
+ derivation, splitfactor, splitfactor_sqf, canonical_representation,
+ hermite_reduce, polynomial_reduce, residue_reduce, residue_reduce_to_basic,
+ integrate_primitive, integrate_hyperexponential_polynomial,
+ integrate_hyperexponential, integrate_hypertangent_polynomial,
+ integrate_nonlinear_no_specials, integer_powers, DifferentialExtension,
+ risch_integrate, DecrementLevel, NonElementaryIntegral, recognize_log_derivative,
+ recognize_derivative, laurent_series)
+from sympy.testing.pytest import raises
+
+from sympy.abc import x, t, nu, z, a, y
+t0, t1, t2 = symbols('t:3')
+i = Symbol('i')
+
+def test_gcdex_diophantine():
+ assert gcdex_diophantine(Poly(x**4 - 2*x**3 - 6*x**2 + 12*x + 15),
+ Poly(x**3 + x**2 - 4*x - 4), Poly(x**2 - 1)) == \
+ (Poly((-x**2 + 4*x - 3)/5), Poly((x**3 - 7*x**2 + 16*x - 10)/5))
+ assert gcdex_diophantine(Poly(x**3 + 6*x + 7), Poly(x**2 + 3*x + 2), Poly(x + 1)) == \
+ (Poly(1/13, x, domain='QQ'), Poly(-1/13*x + 3/13, x, domain='QQ'))
+
+
+def test_frac_in():
+ assert frac_in(Poly((x + 1)/x*t, t), x) == \
+ (Poly(t*x + t, x), Poly(x, x))
+ assert frac_in((x + 1)/x*t, x) == \
+ (Poly(t*x + t, x), Poly(x, x))
+ assert frac_in((Poly((x + 1)/x*t, t), Poly(t + 1, t)), x) == \
+ (Poly(t*x + t, x), Poly((1 + t)*x, x))
+ raises(ValueError, lambda: frac_in((x + 1)/log(x)*t, x))
+ assert frac_in(Poly((2 + 2*x + x*(1 + x))/(1 + x)**2, t), x, cancel=True) == \
+ (Poly(x + 2, x), Poly(x + 1, x))
+
+
+def test_as_poly_1t():
+ assert as_poly_1t(2/t + t, t, z) in [
+ Poly(t + 2*z, t, z), Poly(t + 2*z, z, t)]
+ assert as_poly_1t(2/t + 3/t**2, t, z) in [
+ Poly(2*z + 3*z**2, t, z), Poly(2*z + 3*z**2, z, t)]
+ assert as_poly_1t(2/((exp(2) + 1)*t), t, z) in [
+ Poly(2/(exp(2) + 1)*z, t, z), Poly(2/(exp(2) + 1)*z, z, t)]
+ assert as_poly_1t(2/((exp(2) + 1)*t) + t, t, z) in [
+ Poly(t + 2/(exp(2) + 1)*z, t, z), Poly(t + 2/(exp(2) + 1)*z, z, t)]
+ assert as_poly_1t(S.Zero, t, z) == Poly(0, t, z)
+
+
+def test_derivation():
+ p = Poly(4*x**4*t**5 + (-4*x**3 - 4*x**4)*t**4 + (-3*x**2 + 2*x**3)*t**3 +
+ (2*x + 7*x**2 + 2*x**3)*t**2 + (1 - 4*x - 4*x**2)*t - 1 + 2*x, t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - 3/(2*x)*t + 1/(2*x), t)]})
+ assert derivation(p, DE) == Poly(-20*x**4*t**6 + (2*x**3 + 16*x**4)*t**5 +
+ (21*x**2 + 12*x**3)*t**4 + (x*Rational(7, 2) - 25*x**2 - 12*x**3)*t**3 +
+ (-5 - x*Rational(15, 2) + 7*x**2)*t**2 - (3 - 8*x - 10*x**2 - 4*x**3)/(2*x)*t +
+ (1 - 4*x**2)/(2*x), t)
+ assert derivation(Poly(1, t), DE) == Poly(0, t)
+ assert derivation(Poly(t, t), DE) == DE.d
+ assert derivation(Poly(t**2 + 1/x*t + (1 - 2*x)/(4*x**2), t), DE) == \
+ Poly(-2*t**3 - 4/x*t**2 - (5 - 2*x)/(2*x**2)*t - (1 - 2*x)/(2*x**3), t, domain='ZZ(x)')
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(t, t)]})
+ assert derivation(Poly(x*t*t1, t), DE) == Poly(t*t1 + x*t*t1 + t, t)
+ assert derivation(Poly(x*t*t1, t), DE, coefficientD=True) == \
+ Poly((1 + t1)*t, t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x)]})
+ assert derivation(Poly(x, x), DE) == Poly(1, x)
+ # Test basic option
+ assert derivation((x + 1)/(x - 1), DE, basic=True) == -2/(1 - 2*x + x**2)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ assert derivation((t + 1)/(t - 1), DE, basic=True) == -2*t/(1 - 2*t + t**2)
+ assert derivation(t + 1, DE, basic=True) == t
+
+
+def test_splitfactor():
+ p = Poly(4*x**4*t**5 + (-4*x**3 - 4*x**4)*t**4 + (-3*x**2 + 2*x**3)*t**3 +
+ (2*x + 7*x**2 + 2*x**3)*t**2 + (1 - 4*x - 4*x**2)*t - 1 + 2*x, t, field=True)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - 3/(2*x)*t + 1/(2*x), t)]})
+ assert splitfactor(p, DE) == (Poly(4*x**4*t**3 + (-8*x**3 - 4*x**4)*t**2 +
+ (4*x**2 + 8*x**3)*t - 4*x**2, t, domain='ZZ(x)'),
+ Poly(t**2 + 1/x*t + (1 - 2*x)/(4*x**2), t, domain='ZZ(x)'))
+ assert splitfactor(Poly(x, t), DE) == (Poly(x, t), Poly(1, t))
+ r = Poly(-4*x**4*z**2 + 4*x**6*z**2 - z*x**3 - 4*x**5*z**3 + 4*x**3*z**3 + x**4 + z*x**5 - x**6, t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]})
+ assert splitfactor(r, DE, coefficientD=True) == \
+ (Poly(x*z - x**2 - z*x**3 + x**4, t), Poly(-x**2 + 4*x**2*z**2, t))
+ assert splitfactor_sqf(r, DE, coefficientD=True) == \
+ (((Poly(x*z - x**2 - z*x**3 + x**4, t), 1),), ((Poly(-x**2 + 4*x**2*z**2, t), 1),))
+ assert splitfactor(Poly(0, t), DE) == (Poly(0, t), Poly(1, t))
+ assert splitfactor_sqf(Poly(0, t), DE) == (((Poly(0, t), 1),), ())
+
+
+def test_canonical_representation():
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]})
+ assert canonical_representation(Poly(x - t, t), Poly(t**2, t), DE) == \
+ (Poly(0, t, domain='ZZ[x]'), (Poly(0, t, domain='QQ[x]'),
+ Poly(1, t, domain='ZZ')), (Poly(-t + x, t, domain='QQ[x]'),
+ Poly(t**2, t)))
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ assert canonical_representation(Poly(t**5 + t**3 + x**2*t + 1, t),
+ Poly((t**2 + 1)**3, t), DE) == \
+ (Poly(0, t, domain='ZZ[x]'), (Poly(t**5 + t**3 + x**2*t + 1, t, domain='QQ[x]'),
+ Poly(t**6 + 3*t**4 + 3*t**2 + 1, t, domain='QQ')),
+ (Poly(0, t, domain='QQ[x]'), Poly(1, t, domain='QQ')))
+
+
+def test_hermite_reduce():
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+
+ assert hermite_reduce(Poly(x - t, t), Poly(t**2, t), DE) == \
+ ((Poly(-x, t, domain='QQ[x]'), Poly(t, t, domain='QQ[x]')),
+ (Poly(0, t, domain='QQ[x]'), Poly(1, t, domain='QQ[x]')),
+ (Poly(-x, t, domain='QQ[x]'), Poly(1, t, domain='QQ[x]')))
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - t/x - (1 - nu**2/x**2), t)]})
+
+ assert hermite_reduce(
+ Poly(x**2*t**5 + x*t**4 - nu**2*t**3 - x*(x**2 + 1)*t**2 - (x**2 - nu**2)*t - x**5/4, t),
+ Poly(x**2*t**4 + x**2*(x**2 + 2)*t**2 + x**2 + x**4 + x**6/4, t), DE) == \
+ ((Poly(-x**2 - 4, t, domain='ZZ(x,nu)'), Poly(4*t**2 + 2*x**2 + 4, t, domain='ZZ(x,nu)')),
+ (Poly((-2*nu**2 - x**4)*t - (2*x**3 + 2*x), t, domain='ZZ(x,nu)'),
+ Poly(2*x**2*t**2 + x**4 + 2*x**2, t, domain='ZZ(x,nu)')),
+ (Poly(x*t + 1, t, domain='ZZ(x,nu)'), Poly(x, t, domain='ZZ(x,nu)')))
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]})
+
+ a = Poly((-2 + 3*x)*t**3 + (-1 + x)*t**2 + (-4*x + 2*x**2)*t + x**2, t)
+ d = Poly(x*t**6 - 4*x**2*t**5 + 6*x**3*t**4 - 4*x**4*t**3 + x**5*t**2, t)
+
+ assert hermite_reduce(a, d, DE) == \
+ ((Poly(3*t**2 + t + 3*x, t, domain='ZZ(x)'),
+ Poly(3*t**4 - 9*x*t**3 + 9*x**2*t**2 - 3*x**3*t, t, domain='ZZ(x)')),
+ (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')),
+ (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')))
+
+ assert hermite_reduce(
+ Poly(-t**2 + 2*t + 2, t, domain='ZZ(x)'),
+ Poly(-x*t**2 + 2*x*t - x, t, domain='ZZ(x)'), DE) == \
+ ((Poly(3, t, domain='ZZ(x)'), Poly(t - 1, t, domain='ZZ(x)')),
+ (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')),
+ (Poly(1, t, domain='ZZ(x)'), Poly(x, t, domain='ZZ(x)')))
+
+ assert hermite_reduce(
+ Poly(-x**2*t**6 + (-1 - 2*x**3 + x**4)*t**3 + (-3 - 3*x**4)*t**2 -
+ 2*x*t - x - 3*x**2, t, domain='ZZ(x)'),
+ Poly(x**4*t**6 - 2*x**2*t**3 + 1, t, domain='ZZ(x)'), DE) == \
+ ((Poly(x**3*t + x**4 + 1, t, domain='ZZ(x)'), Poly(x**3*t**3 - x, t, domain='ZZ(x)')),
+ (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')),
+ (Poly(-1, t, domain='ZZ(x)'), Poly(x**2, t, domain='ZZ(x)')))
+
+ assert hermite_reduce(
+ Poly((-2 + 3*x)*t**3 + (-1 + x)*t**2 + (-4*x + 2*x**2)*t + x**2, t),
+ Poly(x*t**6 - 4*x**2*t**5 + 6*x**3*t**4 - 4*x**4*t**3 + x**5*t**2, t), DE) == \
+ ((Poly(3*t**2 + t + 3*x, t, domain='ZZ(x)'),
+ Poly(3*t**4 - 9*x*t**3 + 9*x**2*t**2 - 3*x**3*t, t, domain='ZZ(x)')),
+ (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')),
+ (Poly(0, t, domain='ZZ(x)'), Poly(1, t, domain='ZZ(x)')))
+
+
+def test_polynomial_reduce():
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]})
+ assert polynomial_reduce(Poly(1 + x*t + t**2, t), DE) == \
+ (Poly(t, t), Poly(x*t, t))
+ assert polynomial_reduce(Poly(0, t), DE) == \
+ (Poly(0, t), Poly(0, t))
+
+
+def test_laurent_series():
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1, t)]})
+ a = Poly(36, t)
+ d = Poly((t - 2)*(t**2 - 1)**2, t)
+ F = Poly(t**2 - 1, t)
+ n = 2
+ assert laurent_series(a, d, F, n, DE) == \
+ (Poly(-3*t**3 + 3*t**2 - 6*t - 8, t), Poly(t**5 + t**4 - 2*t**3 - 2*t**2 + t + 1, t),
+ [Poly(-3*t**3 - 6*t**2, t, domain='QQ'), Poly(2*t**6 + 6*t**5 - 8*t**3, t, domain='QQ')])
+
+
+def test_recognize_derivative():
+ DE = DifferentialExtension(extension={'D': [Poly(1, t)]})
+ a = Poly(36, t)
+ d = Poly((t - 2)*(t**2 - 1)**2, t)
+ assert recognize_derivative(a, d, DE) == False
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]})
+ a = Poly(2, t)
+ d = Poly(t**2 - 1, t)
+ assert recognize_derivative(a, d, DE) == False
+ assert recognize_derivative(Poly(x*t, t), Poly(1, t), DE) == True
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ assert recognize_derivative(Poly(t, t), Poly(1, t), DE) == True
+
+
+def test_recognize_log_derivative():
+
+ a = Poly(2*x**2 + 4*x*t - 2*t - x**2*t, t)
+ d = Poly((2*x + t)*(t + x**2), t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ assert recognize_log_derivative(a, d, DE, z) == True
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]})
+ assert recognize_log_derivative(Poly(t + 1, t), Poly(t + x, t), DE) == True
+ assert recognize_log_derivative(Poly(2, t), Poly(t**2 - 1, t), DE) == True
+ DE = DifferentialExtension(extension={'D': [Poly(1, x)]})
+ assert recognize_log_derivative(Poly(1, x), Poly(x**2 - 2, x), DE) == False
+ assert recognize_log_derivative(Poly(1, x), Poly(x**2 + x, x), DE) == True
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ assert recognize_log_derivative(Poly(1, t), Poly(t**2 - 2, t), DE) == False
+ assert recognize_log_derivative(Poly(1, t), Poly(t**2 + t, t), DE) == False
+
+
+def test_residue_reduce():
+ a = Poly(2*t**2 - t - x**2, t)
+ d = Poly(t**3 - x**2*t, t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)], 'Tfuncs': [log]})
+ assert residue_reduce(a, d, DE, z, invert=False) == \
+ ([(Poly(z**2 - Rational(1, 4), z, domain='ZZ(x)'),
+ Poly((1 + 3*x*z - 6*z**2 - 2*x**2 + 4*x**2*z**2)*t - x*z + x**2 +
+ 2*x**2*z**2 - 2*z*x**3, t, domain='ZZ(z, x)'))], False)
+ assert residue_reduce(a, d, DE, z, invert=True) == \
+ ([(Poly(z**2 - Rational(1, 4), z, domain='ZZ(x)'), Poly(t + 2*x*z, t))], False)
+ assert residue_reduce(Poly(-2/x, t), Poly(t**2 - 1, t,), DE, z, invert=False) == \
+ ([(Poly(z**2 - 1, z, domain='QQ'), Poly(-2*z*t/x - 2/x, t, domain='ZZ(z,x)'))], True)
+ ans = residue_reduce(Poly(-2/x, t), Poly(t**2 - 1, t), DE, z, invert=True)
+ assert ans == ([(Poly(z**2 - 1, z, domain='QQ'), Poly(t + z, t))], True)
+ assert residue_reduce_to_basic(ans[0], DE, z) == -log(-1 + log(x)) + log(1 + log(x))
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - t/x - (1 - nu**2/x**2), t)]})
+ # TODO: Skip or make faster
+ assert residue_reduce(Poly((-2*nu**2 - x**4)/(2*x**2)*t - (1 + x**2)/x, t),
+ Poly(t**2 + 1 + x**2/2, t), DE, z) == \
+ ([(Poly(z + S.Half, z, domain='QQ'), Poly(t**2 + 1 + x**2/2, t,
+ domain='ZZ(x,nu)'))], True)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]})
+ assert residue_reduce(Poly(-2*x*t + 1 - x**2, t),
+ Poly(t**2 + 2*x*t + 1 + x**2, t), DE, z) == \
+ ([(Poly(z**2 + Rational(1, 4), z), Poly(t + x + 2*z, t))], True)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ assert residue_reduce(Poly(t, t), Poly(t + sqrt(2), t), DE, z) == \
+ ([(Poly(z - 1, z, domain='QQ'), Poly(t + sqrt(2), t))], True)
+
+
+def test_integrate_hyperexponential():
+ # TODO: Add tests for integrate_hyperexponential() from the book
+ a = Poly((1 + 2*t1 + t1**2 + 2*t1**3)*t**2 + (1 + t1**2)*t + 1 + t1**2, t)
+ d = Poly(1, t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t1**2, t1),
+ Poly(t*(1 + t1**2), t)], 'Tfuncs': [tan, Lambda(i, exp(tan(i)))]})
+ assert integrate_hyperexponential(a, d, DE) == \
+ (exp(2*tan(x))*tan(x) + exp(tan(x)), 1 + t1**2, True)
+ a = Poly((t1**3 + (x + 1)*t1**2 + t1 + x + 2)*t, t)
+ assert integrate_hyperexponential(a, d, DE) == \
+ ((x + tan(x))*exp(tan(x)), 0, True)
+
+ a = Poly(t, t)
+ d = Poly(1, t)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(2*x*t, t)],
+ 'Tfuncs': [Lambda(i, exp(x**2))]})
+
+ assert integrate_hyperexponential(a, d, DE) == \
+ (0, NonElementaryIntegral(exp(x**2), x), False)
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)], 'Tfuncs': [exp]})
+ assert integrate_hyperexponential(a, d, DE) == (exp(x), 0, True)
+
+ a = Poly(25*t**6 - 10*t**5 + 7*t**4 - 8*t**3 + 13*t**2 + 2*t - 1, t)
+ d = Poly(25*t**6 + 35*t**4 + 11*t**2 + 1, t)
+ assert integrate_hyperexponential(a, d, DE) == \
+ (-(11 - 10*exp(x))/(5 + 25*exp(2*x)) + log(1 + exp(2*x)), -1, True)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t0, t0), Poly(t0*t, t)],
+ 'Tfuncs': [exp, Lambda(i, exp(exp(i)))]})
+ assert integrate_hyperexponential(Poly(2*t0*t**2, t), Poly(1, t), DE) == (exp(2*exp(x)), 0, True)
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t0, t0), Poly(-t0*t, t)],
+ 'Tfuncs': [exp, Lambda(i, exp(-exp(i)))]})
+ assert integrate_hyperexponential(Poly(-27*exp(9) - 162*t0*exp(9) +
+ 27*x*t0*exp(9), t), Poly((36*exp(18) + x**2*exp(18) - 12*x*exp(18))*t, t), DE) == \
+ (27*exp(exp(x))/(-6*exp(9) + x*exp(9)), 0, True)
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)], 'Tfuncs': [exp]})
+ assert integrate_hyperexponential(Poly(x**2/2*t, t), Poly(1, t), DE) == \
+ ((2 - 2*x + x**2)*exp(x)/2, 0, True)
+ assert integrate_hyperexponential(Poly(1 + t, t), Poly(t, t), DE) == \
+ (-exp(-x), 1, True) # x - exp(-x)
+ assert integrate_hyperexponential(Poly(x, t), Poly(t + 1, t), DE) == \
+ (0, NonElementaryIntegral(x/(1 + exp(x)), x), False)
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t0), Poly(2*x*t1, t1)],
+ 'Tfuncs': [log, Lambda(i, exp(i**2))]})
+
+ elem, nonelem, b = integrate_hyperexponential(Poly((8*x**7 - 12*x**5 + 6*x**3 - x)*t1**4 +
+ (8*t0*x**7 - 8*t0*x**6 - 4*t0*x**5 + 2*t0*x**3 + 2*t0*x**2 - t0*x +
+ 24*x**8 - 36*x**6 - 4*x**5 + 22*x**4 + 4*x**3 - 7*x**2 - x + 1)*t1**3
+ + (8*t0*x**8 - 4*t0*x**6 - 16*t0*x**5 - 2*t0*x**4 + 12*t0*x**3 +
+ t0*x**2 - 2*t0*x + 24*x**9 - 36*x**7 - 8*x**6 + 22*x**5 + 12*x**4 -
+ 7*x**3 - 6*x**2 + x + 1)*t1**2 + (8*t0*x**8 - 8*t0*x**6 - 16*t0*x**5 +
+ 6*t0*x**4 + 10*t0*x**3 - 2*t0*x**2 - t0*x + 8*x**10 - 12*x**8 - 4*x**7
+ + 2*x**6 + 12*x**5 + 3*x**4 - 9*x**3 - x**2 + 2*x)*t1 + 8*t0*x**7 -
+ 12*t0*x**6 - 4*t0*x**5 + 8*t0*x**4 - t0*x**2 - 4*x**7 + 4*x**6 +
+ 4*x**5 - 4*x**4 - x**3 + x**2, t1), Poly((8*x**7 - 12*x**5 + 6*x**3 -
+ x)*t1**4 + (24*x**8 + 8*x**7 - 36*x**6 - 12*x**5 + 18*x**4 + 6*x**3 -
+ 3*x**2 - x)*t1**3 + (24*x**9 + 24*x**8 - 36*x**7 - 36*x**6 + 18*x**5 +
+ 18*x**4 - 3*x**3 - 3*x**2)*t1**2 + (8*x**10 + 24*x**9 - 12*x**8 -
+ 36*x**7 + 6*x**6 + 18*x**5 - x**4 - 3*x**3)*t1 + 8*x**10 - 12*x**8 +
+ 6*x**6 - x**4, t1), DE)
+
+ assert factor(elem) == -((x - 1)*log(x)/((x + exp(x**2))*(2*x**2 - 1)))
+ assert (nonelem, b) == (NonElementaryIntegral(exp(x**2)/(exp(x**2) + 1), x), False)
+
+def test_integrate_hyperexponential_polynomial():
+ # Without proper cancellation within integrate_hyperexponential_polynomial(),
+ # this will take a long time to complete, and will return a complicated
+ # expression
+ p = Poly((-28*x**11*t0 - 6*x**8*t0 + 6*x**9*t0 - 15*x**8*t0**2 +
+ 15*x**7*t0**2 + 84*x**10*t0**2 - 140*x**9*t0**3 - 20*x**6*t0**3 +
+ 20*x**7*t0**3 - 15*x**6*t0**4 + 15*x**5*t0**4 + 140*x**8*t0**4 -
+ 84*x**7*t0**5 - 6*x**4*t0**5 + 6*x**5*t0**5 + x**3*t0**6 - x**4*t0**6 +
+ 28*x**6*t0**6 - 4*x**5*t0**7 + x**9 - x**10 + 4*x**12)/(-8*x**11*t0 +
+ 28*x**10*t0**2 - 56*x**9*t0**3 + 70*x**8*t0**4 - 56*x**7*t0**5 +
+ 28*x**6*t0**6 - 8*x**5*t0**7 + x**4*t0**8 + x**12)*t1**2 +
+ (-28*x**11*t0 - 12*x**8*t0 + 12*x**9*t0 - 30*x**8*t0**2 +
+ 30*x**7*t0**2 + 84*x**10*t0**2 - 140*x**9*t0**3 - 40*x**6*t0**3 +
+ 40*x**7*t0**3 - 30*x**6*t0**4 + 30*x**5*t0**4 + 140*x**8*t0**4 -
+ 84*x**7*t0**5 - 12*x**4*t0**5 + 12*x**5*t0**5 - 2*x**4*t0**6 +
+ 2*x**3*t0**6 + 28*x**6*t0**6 - 4*x**5*t0**7 + 2*x**9 - 2*x**10 +
+ 4*x**12)/(-8*x**11*t0 + 28*x**10*t0**2 - 56*x**9*t0**3 +
+ 70*x**8*t0**4 - 56*x**7*t0**5 + 28*x**6*t0**6 - 8*x**5*t0**7 +
+ x**4*t0**8 + x**12)*t1 + (-2*x**2*t0 + 2*x**3*t0 + x*t0**2 -
+ x**2*t0**2 + x**3 - x**4)/(-4*x**5*t0 + 6*x**4*t0**2 - 4*x**3*t0**3 +
+ x**2*t0**4 + x**6), t1, z, expand=False)
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t0), Poly(2*x*t1, t1)]})
+ assert integrate_hyperexponential_polynomial(p, DE, z) == (
+ Poly((x - t0)*t1**2 + (-2*t0 + 2*x)*t1, t1), Poly(-2*x*t0 + x**2 +
+ t0**2, t1), True)
+
+ DE = DifferentialExtension(extension={'D':[Poly(1, x), Poly(t0, t0)]})
+ assert integrate_hyperexponential_polynomial(Poly(0, t0), DE, z) == (
+ Poly(0, t0), Poly(1, t0), True)
+
+
+def test_integrate_hyperexponential_returns_piecewise():
+ a, b = symbols('a b')
+ DE = DifferentialExtension(a**x, x)
+ assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise(
+ (exp(x*log(a))/log(a), Ne(log(a), 0)), (x, True)), 0, True)
+ DE = DifferentialExtension(a**(b*x), x)
+ assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise(
+ (exp(b*x*log(a))/(b*log(a)), Ne(b*log(a), 0)), (x, True)), 0, True)
+ DE = DifferentialExtension(exp(a*x), x)
+ assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise(
+ (exp(a*x)/a, Ne(a, 0)), (x, True)), 0, True)
+ DE = DifferentialExtension(x*exp(a*x), x)
+ assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise(
+ ((a*x - 1)*exp(a*x)/a**2, Ne(a**2, 0)), (x**2/2, True)), 0, True)
+ DE = DifferentialExtension(x**2*exp(a*x), x)
+ assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise(
+ ((x**2*a**2 - 2*a*x + 2)*exp(a*x)/a**3, Ne(a**3, 0)),
+ (x**3/3, True)), 0, True)
+ DE = DifferentialExtension(x**y + z, y)
+ assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise(
+ (exp(log(x)*y)/log(x), Ne(log(x), 0)), (y, True)), z, True)
+ DE = DifferentialExtension(x**y + z + x**(2*y), y)
+ assert integrate_hyperexponential(DE.fa, DE.fd, DE) == (Piecewise(
+ ((exp(2*log(x)*y)*log(x) +
+ 2*exp(log(x)*y)*log(x))/(2*log(x)**2), Ne(2*log(x)**2, 0)),
+ (2*y, True),
+ ), z, True)
+ # TODO: Add a test where two different parts of the extension use a
+ # Piecewise, like y**x + z**x.
+
+
+def test_issue_13947():
+ a, t, s = symbols('a t s')
+ assert risch_integrate(2**(-pi)/(2**t + 1), t) == \
+ 2**(-pi)*t - 2**(-pi)*log(2**t + 1)/log(2)
+ assert risch_integrate(a**(t - s)/(a**t + 1), t) == \
+ exp(-s*log(a))*log(a**t + 1)/log(a)
+
+
+def test_integrate_primitive():
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)],
+ 'Tfuncs': [log]})
+ assert integrate_primitive(Poly(t, t), Poly(1, t), DE) == (x*log(x), -1, True)
+ assert integrate_primitive(Poly(x, t), Poly(t, t), DE) == (0, NonElementaryIntegral(x/log(x), x), False)
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(1/(x + 1), t2)],
+ 'Tfuncs': [log, Lambda(i, log(i + 1))]})
+ assert integrate_primitive(Poly(t1, t2), Poly(t2, t2), DE) == \
+ (0, NonElementaryIntegral(log(x)/log(1 + x), x), False)
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(1/(x*t1), t2)],
+ 'Tfuncs': [log, Lambda(i, log(log(i)))]})
+ assert integrate_primitive(Poly(t2, t2), Poly(t1, t2), DE) == \
+ (0, NonElementaryIntegral(log(log(x))/log(x), x), False)
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t0)],
+ 'Tfuncs': [log]})
+ assert integrate_primitive(Poly(x**2*t0**3 + (3*x**2 + x)*t0**2 + (3*x**2
+ + 2*x)*t0 + x**2 + x, t0), Poly(x**2*t0**4 + 4*x**2*t0**3 + 6*x**2*t0**2 +
+ 4*x**2*t0 + x**2, t0), DE) == \
+ (-1/(log(x) + 1), NonElementaryIntegral(1/(log(x) + 1), x), False)
+
+def test_integrate_hypertangent_polynomial():
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**2 + 1, t)]})
+ assert integrate_hypertangent_polynomial(Poly(t**2 + x*t + 1, t), DE) == \
+ (Poly(t, t), Poly(x/2, t))
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(a*(t**2 + 1), t)]})
+ assert integrate_hypertangent_polynomial(Poly(t**5, t), DE) == \
+ (Poly(1/(4*a)*t**4 - 1/(2*a)*t**2, t), Poly(1/(2*a), t))
+
+
+def test_integrate_nonlinear_no_specials():
+ a, d, = Poly(x**2*t**5 + x*t**4 - nu**2*t**3 - x*(x**2 + 1)*t**2 - (x**2 -
+ nu**2)*t - x**5/4, t), Poly(x**2*t**4 + x**2*(x**2 + 2)*t**2 + x**2 + x**4 + x**6/4, t)
+ # f(x) == phi_nu(x), the logarithmic derivative of J_v, the Bessel function,
+ # which has no specials (see Chapter 5, note 4 of Bronstein's book).
+ f = Function('phi_nu')
+ DE = DifferentialExtension(extension={'D': [Poly(1, x),
+ Poly(-t**2 - t/x - (1 - nu**2/x**2), t)], 'Tfuncs': [f]})
+ assert integrate_nonlinear_no_specials(a, d, DE) == \
+ (-log(1 + f(x)**2 + x**2/2)/2 + (- 4 - x**2)/(4 + 2*x**2 + 4*f(x)**2), True)
+ assert integrate_nonlinear_no_specials(Poly(t, t), Poly(1, t), DE) == \
+ (0, False)
+
+
+def test_integer_powers():
+ assert integer_powers([x, x/2, x**2 + 1, x*Rational(2, 3)]) == [
+ (x/6, [(x, 6), (x/2, 3), (x*Rational(2, 3), 4)]),
+ (1 + x**2, [(1 + x**2, 1)])]
+
+
+def test_DifferentialExtension_exp():
+ assert DifferentialExtension(exp(x) + exp(x**2), x)._important_attrs == \
+ (Poly(t1 + t0, t1), Poly(1, t1), [Poly(1, x,), Poly(t0, t0),
+ Poly(2*x*t1, t1)], [x, t0, t1], [Lambda(i, exp(i)),
+ Lambda(i, exp(i**2))], [], [None, 'exp', 'exp'], [None, x, x**2])
+ assert DifferentialExtension(exp(x) + exp(2*x), x)._important_attrs == \
+ (Poly(t0**2 + t0, t0), Poly(1, t0), [Poly(1, x), Poly(t0, t0)], [x, t0],
+ [Lambda(i, exp(i))], [], [None, 'exp'], [None, x])
+ assert DifferentialExtension(exp(x) + exp(x/2), x)._important_attrs == \
+ (Poly(t0**2 + t0, t0), Poly(1, t0), [Poly(1, x), Poly(t0/2, t0)],
+ [x, t0], [Lambda(i, exp(i/2))], [], [None, 'exp'], [None, x/2])
+ assert DifferentialExtension(exp(x) + exp(x**2) + exp(x + x**2), x)._important_attrs == \
+ (Poly((1 + t0)*t1 + t0, t1), Poly(1, t1), [Poly(1, x), Poly(t0, t0),
+ Poly(2*x*t1, t1)], [x, t0, t1], [Lambda(i, exp(i)),
+ Lambda(i, exp(i**2))], [], [None, 'exp', 'exp'], [None, x, x**2])
+ assert DifferentialExtension(exp(x) + exp(x**2) + exp(x + x**2 + 1), x)._important_attrs == \
+ (Poly((1 + S.Exp1*t0)*t1 + t0, t1), Poly(1, t1), [Poly(1, x),
+ Poly(t0, t0), Poly(2*x*t1, t1)], [x, t0, t1], [Lambda(i, exp(i)),
+ Lambda(i, exp(i**2))], [], [None, 'exp', 'exp'], [None, x, x**2])
+ assert DifferentialExtension(exp(x) + exp(x**2) + exp(x/2 + x**2), x)._important_attrs == \
+ (Poly((t0 + 1)*t1 + t0**2, t1), Poly(1, t1), [Poly(1, x),
+ Poly(t0/2, t0), Poly(2*x*t1, t1)], [x, t0, t1],
+ [Lambda(i, exp(i/2)), Lambda(i, exp(i**2))],
+ [(exp(x/2), sqrt(exp(x)))], [None, 'exp', 'exp'], [None, x/2, x**2])
+ assert DifferentialExtension(exp(x) + exp(x**2) + exp(x/2 + x**2 + 3), x)._important_attrs == \
+ (Poly((t0*exp(3) + 1)*t1 + t0**2, t1), Poly(1, t1), [Poly(1, x),
+ Poly(t0/2, t0), Poly(2*x*t1, t1)], [x, t0, t1], [Lambda(i, exp(i/2)),
+ Lambda(i, exp(i**2))], [(exp(x/2), sqrt(exp(x)))], [None, 'exp', 'exp'],
+ [None, x/2, x**2])
+ assert DifferentialExtension(sqrt(exp(x)), x)._important_attrs == \
+ (Poly(t0, t0), Poly(1, t0), [Poly(1, x), Poly(t0/2, t0)], [x, t0],
+ [Lambda(i, exp(i/2))], [(exp(x/2), sqrt(exp(x)))], [None, 'exp'], [None, x/2])
+
+ assert DifferentialExtension(exp(x/2), x)._important_attrs == \
+ (Poly(t0, t0), Poly(1, t0), [Poly(1, x), Poly(t0/2, t0)], [x, t0],
+ [Lambda(i, exp(i/2))], [], [None, 'exp'], [None, x/2])
+
+
+def test_DifferentialExtension_log():
+ assert DifferentialExtension(log(x)*log(x + 1)*log(2*x**2 + 2*x), x)._important_attrs == \
+ (Poly(t0*t1**2 + (t0*log(2) + t0**2)*t1, t1), Poly(1, t1),
+ [Poly(1, x), Poly(1/x, t0),
+ Poly(1/(x + 1), t1, expand=False)], [x, t0, t1],
+ [Lambda(i, log(i)), Lambda(i, log(i + 1))], [], [None, 'log', 'log'],
+ [None, x, x + 1])
+ assert DifferentialExtension(x**x*log(x), x)._important_attrs == \
+ (Poly(t0*t1, t1), Poly(1, t1), [Poly(1, x), Poly(1/x, t0),
+ Poly((1 + t0)*t1, t1)], [x, t0, t1], [Lambda(i, log(i)),
+ Lambda(i, exp(t0*i))], [(exp(x*log(x)), x**x)], [None, 'log', 'exp'],
+ [None, x, t0*x])
+
+
+def test_DifferentialExtension_symlog():
+ # See comment on test_risch_integrate below
+ assert DifferentialExtension(log(x**x), x)._important_attrs == \
+ (Poly(t0*x, t1), Poly(1, t1), [Poly(1, x), Poly(1/x, t0), Poly((t0 +
+ 1)*t1, t1)], [x, t0, t1], [Lambda(i, log(i)), Lambda(i, exp(i*t0))],
+ [(exp(x*log(x)), x**x)], [None, 'log', 'exp'], [None, x, t0*x])
+ assert DifferentialExtension(log(x**y), x)._important_attrs == \
+ (Poly(y*t0, t0), Poly(1, t0), [Poly(1, x), Poly(1/x, t0)], [x, t0],
+ [Lambda(i, log(i))], [(y*log(x), log(x**y))], [None, 'log'],
+ [None, x])
+ assert DifferentialExtension(log(sqrt(x)), x)._important_attrs == \
+ (Poly(t0, t0), Poly(2, t0), [Poly(1, x), Poly(1/x, t0)], [x, t0],
+ [Lambda(i, log(i))], [(log(x)/2, log(sqrt(x)))], [None, 'log'],
+ [None, x])
+
+
+def test_DifferentialExtension_handle_first():
+ assert DifferentialExtension(exp(x)*log(x), x, handle_first='log')._important_attrs == \
+ (Poly(t0*t1, t1), Poly(1, t1), [Poly(1, x), Poly(1/x, t0),
+ Poly(t1, t1)], [x, t0, t1], [Lambda(i, log(i)), Lambda(i, exp(i))],
+ [], [None, 'log', 'exp'], [None, x, x])
+ assert DifferentialExtension(exp(x)*log(x), x, handle_first='exp')._important_attrs == \
+ (Poly(t0*t1, t1), Poly(1, t1), [Poly(1, x), Poly(t0, t0),
+ Poly(1/x, t1)], [x, t0, t1], [Lambda(i, exp(i)), Lambda(i, log(i))],
+ [], [None, 'exp', 'log'], [None, x, x])
+
+ # This one must have the log first, regardless of what we set it to
+ # (because the log is inside of the exponential: x**x == exp(x*log(x)))
+ assert DifferentialExtension(-x**x*log(x)**2 + x**x - x**x/x, x,
+ handle_first='exp')._important_attrs == \
+ DifferentialExtension(-x**x*log(x)**2 + x**x - x**x/x, x,
+ handle_first='log')._important_attrs == \
+ (Poly((-1 + x - x*t0**2)*t1, t1), Poly(x, t1),
+ [Poly(1, x), Poly(1/x, t0), Poly((1 + t0)*t1, t1)], [x, t0, t1],
+ [Lambda(i, log(i)), Lambda(i, exp(t0*i))], [(exp(x*log(x)), x**x)],
+ [None, 'log', 'exp'], [None, x, t0*x])
+
+
+def test_DifferentialExtension_all_attrs():
+ # Test 'unimportant' attributes
+ DE = DifferentialExtension(exp(x)*log(x), x, handle_first='exp')
+ assert DE.f == exp(x)*log(x)
+ assert DE.newf == t0*t1
+ assert DE.x == x
+ assert DE.cases == ['base', 'exp', 'primitive']
+ assert DE.case == 'primitive'
+
+ assert DE.level == -1
+ assert DE.t == t1 == DE.T[DE.level]
+ assert DE.d == Poly(1/x, t1) == DE.D[DE.level]
+ raises(ValueError, lambda: DE.increment_level())
+ DE.decrement_level()
+ assert DE.level == -2
+ assert DE.t == t0 == DE.T[DE.level]
+ assert DE.d == Poly(t0, t0) == DE.D[DE.level]
+ assert DE.case == 'exp'
+ DE.decrement_level()
+ assert DE.level == -3
+ assert DE.t == x == DE.T[DE.level] == DE.x
+ assert DE.d == Poly(1, x) == DE.D[DE.level]
+ assert DE.case == 'base'
+ raises(ValueError, lambda: DE.decrement_level())
+ DE.increment_level()
+ DE.increment_level()
+ assert DE.level == -1
+ assert DE.t == t1 == DE.T[DE.level]
+ assert DE.d == Poly(1/x, t1) == DE.D[DE.level]
+ assert DE.case == 'primitive'
+
+ # Test methods
+ assert DE.indices('log') == [2]
+ assert DE.indices('exp') == [1]
+
+
+def test_DifferentialExtension_extension_flag():
+ raises(ValueError, lambda: DifferentialExtension(extension={'T': [x, t]}))
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]})
+ assert DE._important_attrs == (None, None, [Poly(1, x), Poly(t, t)], [x, t],
+ None, None, None, None)
+ assert DE.d == Poly(t, t)
+ assert DE.t == t
+ assert DE.level == -1
+ assert DE.cases == ['base', 'exp']
+ assert DE.x == x
+ assert DE.case == 'exp'
+
+ DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)],
+ 'exts': [None, 'exp'], 'extargs': [None, x]})
+ assert DE._important_attrs == (None, None, [Poly(1, x), Poly(t, t)], [x, t],
+ None, None, [None, 'exp'], [None, x])
+ raises(ValueError, lambda: DifferentialExtension())
+
+
+def test_DifferentialExtension_misc():
+ # Odd ends
+ assert DifferentialExtension(sin(y)*exp(x), x)._important_attrs == \
+ (Poly(sin(y)*t0, t0, domain='ZZ[sin(y)]'), Poly(1, t0, domain='ZZ'),
+ [Poly(1, x, domain='ZZ'), Poly(t0, t0, domain='ZZ')], [x, t0],
+ [Lambda(i, exp(i))], [], [None, 'exp'], [None, x])
+ raises(NotImplementedError, lambda: DifferentialExtension(sin(x), x))
+ assert DifferentialExtension(10**x, x)._important_attrs == \
+ (Poly(t0, t0), Poly(1, t0), [Poly(1, x), Poly(log(10)*t0, t0)], [x, t0],
+ [Lambda(i, exp(i*log(10)))], [(exp(x*log(10)), 10**x)], [None, 'exp'],
+ [None, x*log(10)])
+ assert DifferentialExtension(log(x) + log(x**2), x)._important_attrs in [
+ (Poly(3*t0, t0), Poly(2, t0), [Poly(1, x), Poly(2/x, t0)], [x, t0],
+ [Lambda(i, log(i**2))], [], [None, ], [], [1], [x**2]),
+ (Poly(3*t0, t0), Poly(1, t0), [Poly(1, x), Poly(1/x, t0)], [x, t0],
+ [Lambda(i, log(i))], [], [None, 'log'], [None, x])]
+ assert DifferentialExtension(S.Zero, x)._important_attrs == \
+ (Poly(0, x), Poly(1, x), [Poly(1, x)], [x], [], [], [None], [None])
+ assert DifferentialExtension(tan(atan(x).rewrite(log)), x)._important_attrs == \
+ (Poly(x, x), Poly(1, x), [Poly(1, x)], [x], [], [], [None], [None])
+
+
+def test_DifferentialExtension_Rothstein():
+ # Rothstein's integral
+ f = (2581284541*exp(x) + 1757211400)/(39916800*exp(3*x) +
+ 119750400*exp(x)**2 + 119750400*exp(x) + 39916800)*exp(1/(exp(x) + 1) - 10*x)
+ assert DifferentialExtension(f, x)._important_attrs == \
+ (Poly((1757211400 + 2581284541*t0)*t1, t1), Poly(39916800 +
+ 119750400*t0 + 119750400*t0**2 + 39916800*t0**3, t1),
+ [Poly(1, x), Poly(t0, t0), Poly(-(10 + 21*t0 + 10*t0**2)/(1 + 2*t0 +
+ t0**2)*t1, t1, domain='ZZ(t0)')], [x, t0, t1],
+ [Lambda(i, exp(i)), Lambda(i, exp(1/(t0 + 1) - 10*i))], [],
+ [None, 'exp', 'exp'], [None, x, 1/(t0 + 1) - 10*x])
+
+
+class _TestingException(Exception):
+ """Dummy Exception class for testing."""
+ pass
+
+
+def test_DecrementLevel():
+ DE = DifferentialExtension(x*log(exp(x) + 1), x)
+ assert DE.level == -1
+ assert DE.t == t1
+ assert DE.d == Poly(t0/(t0 + 1), t1)
+ assert DE.case == 'primitive'
+
+ with DecrementLevel(DE):
+ assert DE.level == -2
+ assert DE.t == t0
+ assert DE.d == Poly(t0, t0)
+ assert DE.case == 'exp'
+
+ with DecrementLevel(DE):
+ assert DE.level == -3
+ assert DE.t == x
+ assert DE.d == Poly(1, x)
+ assert DE.case == 'base'
+
+ assert DE.level == -2
+ assert DE.t == t0
+ assert DE.d == Poly(t0, t0)
+ assert DE.case == 'exp'
+
+ assert DE.level == -1
+ assert DE.t == t1
+ assert DE.d == Poly(t0/(t0 + 1), t1)
+ assert DE.case == 'primitive'
+
+ # Test that __exit__ is called after an exception correctly
+ try:
+ with DecrementLevel(DE):
+ raise _TestingException
+ except _TestingException:
+ pass
+ else:
+ raise AssertionError("Did not raise.")
+
+ assert DE.level == -1
+ assert DE.t == t1
+ assert DE.d == Poly(t0/(t0 + 1), t1)
+ assert DE.case == 'primitive'
+
+
+def test_risch_integrate():
+ assert risch_integrate(t0*exp(x), x) == t0*exp(x)
+ assert risch_integrate(sin(x), x, rewrite_complex=True) == -exp(I*x)/2 - exp(-I*x)/2
+
+ # From my GSoC writeup
+ assert risch_integrate((1 + 2*x**2 + x**4 + 2*x**3*exp(2*x**2))/
+ (x**4*exp(x**2) + 2*x**2*exp(x**2) + exp(x**2)), x) == \
+ NonElementaryIntegral(exp(-x**2), x) + exp(x**2)/(1 + x**2)
+
+
+ assert risch_integrate(0, x) == 0
+
+ # also tests prde_cancel()
+ e1 = log(x/exp(x) + 1)
+ ans1 = risch_integrate(e1, x)
+ assert ans1 == (x*log(x*exp(-x) + 1) + NonElementaryIntegral((x**2 - x)/(x + exp(x)), x))
+ assert cancel(diff(ans1, x) - e1) == 0
+
+ # also tests issue #10798
+ e2 = (log(-1/y)/2 - log(1/y)/2)/y - (log(1 - 1/y)/2 - log(1 + 1/y)/2)/y
+ ans2 = risch_integrate(e2, y)
+ assert ans2 == log(1/y)*log(1 - 1/y)/2 - log(1/y)*log(1 + 1/y)/2 + \
+ NonElementaryIntegral((I*pi*y**2 - 2*y*log(1/y) - I*pi)/(2*y**3 - 2*y), y)
+ assert expand_log(cancel(diff(ans2, y) - e2), force=True) == 0
+
+ # These are tested here in addition to in test_DifferentialExtension above
+ # (symlogs) to test that backsubs works correctly. The integrals should be
+ # written in terms of the original logarithms in the integrands.
+
+ # XXX: Unfortunately, making backsubs work on this one is a little
+ # trickier, because x**x is converted to exp(x*log(x)), and so log(x**x)
+ # is converted to x*log(x). (x**2*log(x)).subs(x*log(x), log(x**x)) is
+ # smart enough, the issue is that these splits happen at different places
+ # in the algorithm. Maybe a heuristic is in order
+ assert risch_integrate(log(x**x), x) == x**2*log(x)/2 - x**2/4
+
+ assert risch_integrate(log(x**y), x) == x*log(x**y) - x*y
+ assert risch_integrate(log(sqrt(x)), x) == x*log(sqrt(x)) - x/2
+
+
+def test_risch_integrate_float():
+ assert risch_integrate((-60*exp(x) - 19.2*exp(4*x))*exp(4*x), x) == -2.4*exp(8*x) - 12.0*exp(5*x)
+
+
+def test_NonElementaryIntegral():
+ assert isinstance(risch_integrate(exp(x**2), x), NonElementaryIntegral)
+ assert isinstance(risch_integrate(x**x*log(x), x), NonElementaryIntegral)
+ # Make sure methods of Integral still give back a NonElementaryIntegral
+ assert isinstance(NonElementaryIntegral(x**x*t0, x).subs(t0, log(x)), NonElementaryIntegral)
+
+
+def test_xtothex():
+ a = risch_integrate(x**x, x)
+ assert a == NonElementaryIntegral(x**x, x)
+ assert isinstance(a, NonElementaryIntegral)
+
+
+def test_DifferentialExtension_equality():
+ DE1 = DE2 = DifferentialExtension(log(x), x)
+ assert DE1 == DE2
+
+
+def test_DifferentialExtension_printing():
+ DE = DifferentialExtension(exp(2*x**2) + log(exp(x**2) + 1), x)
+ assert repr(DE) == ("DifferentialExtension(dict([('f', exp(2*x**2) + log(exp(x**2) + 1)), "
+ "('x', x), ('T', [x, t0, t1]), ('D', [Poly(1, x, domain='ZZ'), Poly(2*x*t0, t0, domain='ZZ[x]'), "
+ "Poly(2*t0*x/(t0 + 1), t1, domain='ZZ(x,t0)')]), ('fa', Poly(t1 + t0**2, t1, domain='ZZ[t0]')), "
+ "('fd', Poly(1, t1, domain='ZZ')), ('Tfuncs', [Lambda(i, exp(i**2)), Lambda(i, log(t0 + 1))]), "
+ "('backsubs', []), ('exts', [None, 'exp', 'log']), ('extargs', [None, x**2, t0 + 1]), "
+ "('cases', ['base', 'exp', 'primitive']), ('case', 'primitive'), ('t', t1), "
+ "('d', Poly(2*t0*x/(t0 + 1), t1, domain='ZZ(x,t0)')), ('newf', t0**2 + t1), ('level', -1), "
+ "('dummy', False)]))")
+
+ assert str(DE) == ("DifferentialExtension({fa=Poly(t1 + t0**2, t1, domain='ZZ[t0]'), "
+ "fd=Poly(1, t1, domain='ZZ'), D=[Poly(1, x, domain='ZZ'), Poly(2*x*t0, t0, domain='ZZ[x]'), "
+ "Poly(2*t0*x/(t0 + 1), t1, domain='ZZ(x,t0)')]})")
+
+
+def test_issue_23948():
+ f = (
+ ( (-2*x**5 + 28*x**4 - 144*x**3 + 324*x**2 - 270*x)*log(x)**2
+ +(-4*x**6 + 56*x**5 - 288*x**4 + 648*x**3 - 540*x**2)*log(x)
+ +(2*x**5 - 28*x**4 + 144*x**3 - 324*x**2 + 270*x)*exp(x)
+ +(2*x**5 - 28*x**4 + 144*x**3 - 324*x**2 + 270*x)*log(5)
+ -2*x**7 + 26*x**6 - 116*x**5 + 180*x**4 + 54*x**3 - 270*x**2
+ )*log(-log(x)**2 - 2*x*log(x) + exp(x) + log(5) - x**2 - x)**2
+ +( (4*x**5 - 44*x**4 + 168*x**3 - 216*x**2 - 108*x + 324)*log(x)
+ +(-2*x**5 + 24*x**4 - 108*x**3 + 216*x**2 - 162*x)*exp(x)
+ +4*x**6 - 42*x**5 + 144*x**4 - 108*x**3 - 324*x**2 + 486*x
+ )*log(-log(x)**2 - 2*x*log(x) + exp(x) + log(5) - x**2 - x)
+ )/(x*exp(x)**2*log(x)**2 + 2*x**2*exp(x)**2*log(x) - x*exp(x)**3
+ +(-x*log(5) + x**3 + x**2)*exp(x)**2)
+
+ F = ((x**4 - 12*x**3 + 54*x**2 - 108*x + 81)*exp(-2*x)
+ *log(-x**2 - 2*x*log(x) - x + exp(x) - log(x)**2 + log(5))**2)
+
+ assert risch_integrate(f, x) == F
diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_singularityfunctions.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_singularityfunctions.py
new file mode 100644
index 0000000000000000000000000000000000000000..587e5f104cbf095f851ec538601ca146377b51ae
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_singularityfunctions.py
@@ -0,0 +1,22 @@
+from sympy.integrals.singularityfunctions import singularityintegrate
+from sympy.core.function import Function
+from sympy.core.symbol import symbols
+from sympy.functions.special.singularity_functions import SingularityFunction
+
+x, a, n, y = symbols('x a n y')
+f = Function('f')
+
+
+def test_singularityintegrate():
+ assert singularityintegrate(x, x) is None
+ assert singularityintegrate(x + SingularityFunction(x, 9, 1), x) is None
+
+ assert 4*singularityintegrate(SingularityFunction(x, a, 3), x) == 4*SingularityFunction(x, a, 4)/4
+ assert singularityintegrate(5*SingularityFunction(x, 5, -2), x) == 5*SingularityFunction(x, 5, -1)
+ assert singularityintegrate(6*SingularityFunction(x, 5, -1), x) == 6*SingularityFunction(x, 5, 0)
+ assert singularityintegrate(x*SingularityFunction(x, 0, -1), x) == 0
+ assert singularityintegrate((x - 5)*SingularityFunction(x, 5, -1), x) == 0
+ assert singularityintegrate(SingularityFunction(x, 0, -1) * f(x), x) == f(0) * SingularityFunction(x, 0, 0)
+ assert singularityintegrate(SingularityFunction(x, 1, -1) * f(x), x) == f(1) * SingularityFunction(x, 1, 0)
+ assert singularityintegrate(y*SingularityFunction(x, 0, -1)**2, x) == \
+ y*SingularityFunction(0, 0, -1)*SingularityFunction(x, 0, 0)
diff --git a/lib/python3.12/site-packages/sympy/polys/__init__.py b/lib/python3.12/site-packages/sympy/polys/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8055ed12d213de3ebc7a1f17100607fb1e3b89b8
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/__init__.py
@@ -0,0 +1,130 @@
+"""Polynomial manipulation algorithms and algebraic objects. """
+
+__all__ = [
+ 'Poly', 'PurePoly', 'poly_from_expr', 'parallel_poly_from_expr', 'degree',
+ 'total_degree', 'degree_list', 'LC', 'LM', 'LT', 'pdiv', 'prem', 'pquo',
+ 'pexquo', 'div', 'rem', 'quo', 'exquo', 'half_gcdex', 'gcdex', 'invert',
+ 'subresultants', 'resultant', 'discriminant', 'cofactors', 'gcd_list',
+ 'gcd', 'lcm_list', 'lcm', 'terms_gcd', 'trunc', 'monic', 'content',
+ 'primitive', 'compose', 'decompose', 'sturm', 'gff_list', 'gff',
+ 'sqf_norm', 'sqf_part', 'sqf_list', 'sqf', 'factor_list', 'factor',
+ 'intervals', 'refine_root', 'count_roots', 'all_roots', 'real_roots',
+ 'nroots', 'ground_roots', 'nth_power_roots_poly', 'cancel', 'reduced',
+ 'groebner', 'is_zero_dimensional', 'GroebnerBasis', 'poly',
+
+ 'symmetrize', 'horner', 'interpolate', 'rational_interpolate', 'viete',
+
+ 'together',
+
+ 'BasePolynomialError', 'ExactQuotientFailed', 'PolynomialDivisionFailed',
+ 'OperationNotSupported', 'HeuristicGCDFailed', 'HomomorphismFailed',
+ 'IsomorphismFailed', 'ExtraneousFactors', 'EvaluationFailed',
+ 'RefinementFailed', 'CoercionFailed', 'NotInvertible', 'NotReversible',
+ 'NotAlgebraic', 'DomainError', 'PolynomialError', 'UnificationFailed',
+ 'GeneratorsError', 'GeneratorsNeeded', 'ComputationFailed',
+ 'UnivariatePolynomialError', 'MultivariatePolynomialError',
+ 'PolificationFailed', 'OptionError', 'FlagError',
+
+ 'minpoly', 'minimal_polynomial', 'primitive_element', 'field_isomorphism',
+ 'to_number_field', 'isolate', 'round_two', 'prime_decomp',
+ 'prime_valuation', 'galois_group',
+
+ 'itermonomials', 'Monomial',
+
+ 'lex', 'grlex', 'grevlex', 'ilex', 'igrlex', 'igrevlex',
+
+ 'CRootOf', 'rootof', 'RootOf', 'ComplexRootOf', 'RootSum',
+
+ 'roots',
+
+ 'Domain', 'FiniteField', 'IntegerRing', 'RationalField', 'RealField',
+ 'ComplexField', 'PythonFiniteField', 'GMPYFiniteField',
+ 'PythonIntegerRing', 'GMPYIntegerRing', 'PythonRational',
+ 'GMPYRationalField', 'AlgebraicField', 'PolynomialRing', 'FractionField',
+ 'ExpressionDomain', 'FF_python', 'FF_gmpy', 'ZZ_python', 'ZZ_gmpy',
+ 'QQ_python', 'QQ_gmpy', 'GF', 'FF', 'ZZ', 'QQ', 'ZZ_I', 'QQ_I', 'RR',
+ 'CC', 'EX', 'EXRAW',
+
+ 'construct_domain',
+
+ 'swinnerton_dyer_poly', 'cyclotomic_poly', 'symmetric_poly',
+ 'random_poly', 'interpolating_poly',
+
+ 'jacobi_poly', 'chebyshevt_poly', 'chebyshevu_poly', 'hermite_poly',
+ 'hermite_prob_poly', 'legendre_poly', 'laguerre_poly',
+
+ 'bernoulli_poly', 'bernoulli_c_poly', 'genocchi_poly', 'euler_poly',
+ 'andre_poly',
+
+ 'apart', 'apart_list', 'assemble_partfrac_list',
+
+ 'Options',
+
+ 'ring', 'xring', 'vring', 'sring',
+
+ 'field', 'xfield', 'vfield', 'sfield'
+]
+
+from .polytools import (Poly, PurePoly, poly_from_expr,
+ parallel_poly_from_expr, degree, total_degree, degree_list, LC, LM,
+ LT, pdiv, prem, pquo, pexquo, div, rem, quo, exquo, half_gcdex, gcdex,
+ invert, subresultants, resultant, discriminant, cofactors, gcd_list,
+ gcd, lcm_list, lcm, terms_gcd, trunc, monic, content, primitive,
+ compose, decompose, sturm, gff_list, gff, sqf_norm, sqf_part,
+ sqf_list, sqf, factor_list, factor, intervals, refine_root,
+ count_roots, all_roots, real_roots, nroots, ground_roots,
+ nth_power_roots_poly, cancel, reduced, groebner, is_zero_dimensional,
+ GroebnerBasis, poly)
+
+from .polyfuncs import (symmetrize, horner, interpolate,
+ rational_interpolate, viete)
+
+from .rationaltools import together
+
+from .polyerrors import (BasePolynomialError, ExactQuotientFailed,
+ PolynomialDivisionFailed, OperationNotSupported, HeuristicGCDFailed,
+ HomomorphismFailed, IsomorphismFailed, ExtraneousFactors,
+ EvaluationFailed, RefinementFailed, CoercionFailed, NotInvertible,
+ NotReversible, NotAlgebraic, DomainError, PolynomialError,
+ UnificationFailed, GeneratorsError, GeneratorsNeeded,
+ ComputationFailed, UnivariatePolynomialError,
+ MultivariatePolynomialError, PolificationFailed, OptionError,
+ FlagError)
+
+from .numberfields import (minpoly, minimal_polynomial, primitive_element,
+ field_isomorphism, to_number_field, isolate, round_two, prime_decomp,
+ prime_valuation, galois_group)
+
+from .monomials import itermonomials, Monomial
+
+from .orderings import lex, grlex, grevlex, ilex, igrlex, igrevlex
+
+from .rootoftools import CRootOf, rootof, RootOf, ComplexRootOf, RootSum
+
+from .polyroots import roots
+
+from .domains import (Domain, FiniteField, IntegerRing, RationalField,
+ RealField, ComplexField, PythonFiniteField, GMPYFiniteField,
+ PythonIntegerRing, GMPYIntegerRing, PythonRational, GMPYRationalField,
+ AlgebraicField, PolynomialRing, FractionField, ExpressionDomain,
+ FF_python, FF_gmpy, ZZ_python, ZZ_gmpy, QQ_python, QQ_gmpy, GF, FF,
+ ZZ, QQ, ZZ_I, QQ_I, RR, CC, EX, EXRAW)
+
+from .constructor import construct_domain
+
+from .specialpolys import (swinnerton_dyer_poly, cyclotomic_poly,
+ symmetric_poly, random_poly, interpolating_poly)
+
+from .orthopolys import (jacobi_poly, chebyshevt_poly, chebyshevu_poly,
+ hermite_poly, hermite_prob_poly, legendre_poly, laguerre_poly)
+
+from .appellseqs import (bernoulli_poly, bernoulli_c_poly, genocchi_poly,
+ euler_poly, andre_poly)
+
+from .partfrac import apart, apart_list, assemble_partfrac_list
+
+from .polyoptions import Options
+
+from .rings import ring, xring, vring, sring
+
+from .fields import field, xfield, vfield, sfield
diff --git a/lib/python3.12/site-packages/sympy/polys/appellseqs.py b/lib/python3.12/site-packages/sympy/polys/appellseqs.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac10fe3d1f1e60ccdf46cdae4eb5b8a969500a3e
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/appellseqs.py
@@ -0,0 +1,269 @@
+r"""
+Efficient functions for generating Appell sequences.
+
+An Appell sequence is a zero-indexed sequence of polynomials `p_i(x)`
+satisfying `p_{i+1}'(x)=(i+1)p_i(x)` for all `i`. This definition leads
+to the following iterative algorithm:
+
+.. math :: p_0(x) = c_0,\ p_i(x) = i \int_0^x p_{i-1}(t)\,dt + c_i
+
+The constant coefficients `c_i` are usually determined from the
+just-evaluated integral and `i`.
+
+Appell sequences satisfy the following identity from umbral calculus:
+
+.. math :: p_n(x+y) = \sum_{k=0}^n \binom{n}{k} p_k(x) y^{n-k}
+
+References
+==========
+
+.. [1] https://en.wikipedia.org/wiki/Appell_sequence
+.. [2] Peter Luschny, "An introduction to the Bernoulli function",
+ https://arxiv.org/abs/2009.06743
+"""
+from sympy.polys.densearith import dup_mul_ground, dup_sub_ground, dup_quo_ground
+from sympy.polys.densetools import dup_eval, dup_integrate
+from sympy.polys.domains import ZZ, QQ
+from sympy.polys.polytools import named_poly
+from sympy.utilities import public
+
+def dup_bernoulli(n, K):
+ """Low-level implementation of Bernoulli polynomials."""
+ if n < 1:
+ return [K.one]
+ p = [K.one, K(-1,2)]
+ for i in range(2, n+1):
+ p = dup_integrate(dup_mul_ground(p, K(i), K), 1, K)
+ if i % 2 == 0:
+ p = dup_sub_ground(p, dup_eval(p, K(1,2), K) * K(1<<(i-1), (1<>> from sympy import summation
+ >>> from sympy.abc import x
+ >>> from sympy.polys import bernoulli_poly
+ >>> bernoulli_poly(5, x)
+ x**5 - 5*x**4/2 + 5*x**3/3 - x/6
+
+ >>> def psum(p, a, b):
+ ... return (bernoulli_poly(p+1,b+1) - bernoulli_poly(p+1,a)) / (p+1)
+ >>> psum(4, -6, 27)
+ 3144337
+ >>> summation(x**4, (x, -6, 27))
+ 3144337
+
+ >>> psum(1, 1, x).factor()
+ x*(x + 1)/2
+ >>> psum(2, 1, x).factor()
+ x*(x + 1)*(2*x + 1)/6
+ >>> psum(3, 1, x).factor()
+ x**2*(x + 1)**2/4
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+
+ See Also
+ ========
+
+ sympy.functions.combinatorial.numbers.bernoulli
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Bernoulli_polynomials
+ """
+ return named_poly(n, dup_bernoulli, QQ, "Bernoulli polynomial", (x,), polys)
+
+def dup_bernoulli_c(n, K):
+ """Low-level implementation of central Bernoulli polynomials."""
+ p = [K.one]
+ for i in range(1, n+1):
+ p = dup_integrate(dup_mul_ground(p, K(i), K), 1, K)
+ if i % 2 == 0:
+ p = dup_sub_ground(p, dup_eval(p, K.one, K) * K((1<<(i-1))-1, (1<>> from sympy import bernoulli, euler, genocchi
+ >>> from sympy.abc import x
+ >>> from sympy.polys import andre_poly
+ >>> andre_poly(9, x)
+ x**9 - 36*x**7 + 630*x**5 - 5124*x**3 + 12465*x
+
+ >>> [andre_poly(n, 0) for n in range(11)]
+ [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521]
+ >>> [euler(n) for n in range(11)]
+ [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521]
+ >>> [andre_poly(n-1, 1) * n / (4**n - 2**n) for n in range(1, 11)]
+ [1/2, 1/6, 0, -1/30, 0, 1/42, 0, -1/30, 0, 5/66]
+ >>> [bernoulli(n) for n in range(1, 11)]
+ [1/2, 1/6, 0, -1/30, 0, 1/42, 0, -1/30, 0, 5/66]
+ >>> [-andre_poly(n-1, -1) * n / (-2)**(n-1) for n in range(1, 11)]
+ [-1, -1, 0, 1, 0, -3, 0, 17, 0, -155]
+ >>> [genocchi(n) for n in range(1, 11)]
+ [-1, -1, 0, 1, 0, -3, 0, 17, 0, -155]
+
+ >>> [abs(andre_poly(n, n%2)) for n in range(11)]
+ [1, 1, 1, 2, 5, 16, 61, 272, 1385, 7936, 50521]
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+
+ See Also
+ ========
+
+ sympy.functions.combinatorial.numbers.andre
+
+ References
+ ==========
+
+ .. [1] Peter Luschny, "An introduction to the Bernoulli function",
+ https://arxiv.org/abs/2009.06743
+ """
+ return named_poly(n, dup_andre, ZZ, "Andre polynomial", (x,), polys)
diff --git a/lib/python3.12/site-packages/sympy/polys/constructor.py b/lib/python3.12/site-packages/sympy/polys/constructor.py
new file mode 100644
index 0000000000000000000000000000000000000000..49ce4782b987419ee8b736974f8755301380bdda
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/constructor.py
@@ -0,0 +1,387 @@
+"""Tools for constructing domains for expressions. """
+from math import prod
+
+from sympy.core import sympify
+from sympy.core.evalf import pure_complex
+from sympy.core.sorting import ordered
+from sympy.polys.domains import ZZ, QQ, ZZ_I, QQ_I, EX
+from sympy.polys.domains.complexfield import ComplexField
+from sympy.polys.domains.realfield import RealField
+from sympy.polys.polyoptions import build_options
+from sympy.polys.polyutils import parallel_dict_from_basic
+from sympy.utilities import public
+
+
+def _construct_simple(coeffs, opt):
+ """Handle simple domains, e.g.: ZZ, QQ, RR and algebraic domains. """
+ rationals = floats = complexes = algebraics = False
+ float_numbers = []
+
+ if opt.extension is True:
+ is_algebraic = lambda coeff: coeff.is_number and coeff.is_algebraic
+ else:
+ is_algebraic = lambda coeff: False
+
+ for coeff in coeffs:
+ if coeff.is_Rational:
+ if not coeff.is_Integer:
+ rationals = True
+ elif coeff.is_Float:
+ if algebraics:
+ # there are both reals and algebraics -> EX
+ return False
+ else:
+ floats = True
+ float_numbers.append(coeff)
+ else:
+ is_complex = pure_complex(coeff)
+ if is_complex:
+ complexes = True
+ x, y = is_complex
+ if x.is_Rational and y.is_Rational:
+ if not (x.is_Integer and y.is_Integer):
+ rationals = True
+ continue
+ else:
+ floats = True
+ if x.is_Float:
+ float_numbers.append(x)
+ if y.is_Float:
+ float_numbers.append(y)
+ elif is_algebraic(coeff):
+ if floats:
+ # there are both algebraics and reals -> EX
+ return False
+ algebraics = True
+ else:
+ # this is a composite domain, e.g. ZZ[X], EX
+ return None
+
+ # Use the maximum precision of all coefficients for the RR or CC
+ # precision
+ max_prec = max(c._prec for c in float_numbers) if float_numbers else 53
+
+ if algebraics:
+ domain, result = _construct_algebraic(coeffs, opt)
+ else:
+ if floats and complexes:
+ domain = ComplexField(prec=max_prec)
+ elif floats:
+ domain = RealField(prec=max_prec)
+ elif rationals or opt.field:
+ domain = QQ_I if complexes else QQ
+ else:
+ domain = ZZ_I if complexes else ZZ
+
+ result = [domain.from_sympy(coeff) for coeff in coeffs]
+
+ return domain, result
+
+
+def _construct_algebraic(coeffs, opt):
+ """We know that coefficients are algebraic so construct the extension. """
+ from sympy.polys.numberfields import primitive_element
+
+ exts = set()
+
+ def build_trees(args):
+ trees = []
+ for a in args:
+ if a.is_Rational:
+ tree = ('Q', QQ.from_sympy(a))
+ elif a.is_Add:
+ tree = ('+', build_trees(a.args))
+ elif a.is_Mul:
+ tree = ('*', build_trees(a.args))
+ else:
+ tree = ('e', a)
+ exts.add(a)
+ trees.append(tree)
+ return trees
+
+ trees = build_trees(coeffs)
+ exts = list(ordered(exts))
+
+ g, span, H = primitive_element(exts, ex=True, polys=True)
+ root = sum(s*ext for s, ext in zip(span, exts))
+
+ domain, g = QQ.algebraic_field((g, root)), g.rep.to_list()
+
+ exts_dom = [domain.dtype.from_list(h, g, QQ) for h in H]
+ exts_map = dict(zip(exts, exts_dom))
+
+ def convert_tree(tree):
+ op, args = tree
+ if op == 'Q':
+ return domain.dtype.from_list([args], g, QQ)
+ elif op == '+':
+ return sum((convert_tree(a) for a in args), domain.zero)
+ elif op == '*':
+ return prod(convert_tree(a) for a in args)
+ elif op == 'e':
+ return exts_map[args]
+ else:
+ raise RuntimeError
+
+ result = [convert_tree(tree) for tree in trees]
+
+ return domain, result
+
+
+def _construct_composite(coeffs, opt):
+ """Handle composite domains, e.g.: ZZ[X], QQ[X], ZZ(X), QQ(X). """
+ numers, denoms = [], []
+
+ for coeff in coeffs:
+ numer, denom = coeff.as_numer_denom()
+
+ numers.append(numer)
+ denoms.append(denom)
+
+ polys, gens = parallel_dict_from_basic(numers + denoms) # XXX: sorting
+ if not gens:
+ return None
+
+ if opt.composite is None:
+ if any(gen.is_number and gen.is_algebraic for gen in gens):
+ return None # generators are number-like so lets better use EX
+
+ all_symbols = set()
+
+ for gen in gens:
+ symbols = gen.free_symbols
+
+ if all_symbols & symbols:
+ return None # there could be algebraic relations between generators
+ else:
+ all_symbols |= symbols
+
+ n = len(gens)
+ k = len(polys)//2
+
+ numers = polys[:k]
+ denoms = polys[k:]
+
+ if opt.field:
+ fractions = True
+ else:
+ fractions, zeros = False, (0,)*n
+
+ for denom in denoms:
+ if len(denom) > 1 or zeros not in denom:
+ fractions = True
+ break
+
+ coeffs = set()
+
+ if not fractions:
+ for numer, denom in zip(numers, denoms):
+ denom = denom[zeros]
+
+ for monom, coeff in numer.items():
+ coeff /= denom
+ coeffs.add(coeff)
+ numer[monom] = coeff
+ else:
+ for numer, denom in zip(numers, denoms):
+ coeffs.update(list(numer.values()))
+ coeffs.update(list(denom.values()))
+
+ rationals = floats = complexes = False
+ float_numbers = []
+
+ for coeff in coeffs:
+ if coeff.is_Rational:
+ if not coeff.is_Integer:
+ rationals = True
+ elif coeff.is_Float:
+ floats = True
+ float_numbers.append(coeff)
+ else:
+ is_complex = pure_complex(coeff)
+ if is_complex is not None:
+ complexes = True
+ x, y = is_complex
+ if x.is_Rational and y.is_Rational:
+ if not (x.is_Integer and y.is_Integer):
+ rationals = True
+ else:
+ floats = True
+ if x.is_Float:
+ float_numbers.append(x)
+ if y.is_Float:
+ float_numbers.append(y)
+
+ max_prec = max(c._prec for c in float_numbers) if float_numbers else 53
+
+ if floats and complexes:
+ ground = ComplexField(prec=max_prec)
+ elif floats:
+ ground = RealField(prec=max_prec)
+ elif complexes:
+ if rationals:
+ ground = QQ_I
+ else:
+ ground = ZZ_I
+ elif rationals:
+ ground = QQ
+ else:
+ ground = ZZ
+
+ result = []
+
+ if not fractions:
+ domain = ground.poly_ring(*gens)
+
+ for numer in numers:
+ for monom, coeff in numer.items():
+ numer[monom] = ground.from_sympy(coeff)
+
+ result.append(domain(numer))
+ else:
+ domain = ground.frac_field(*gens)
+
+ for numer, denom in zip(numers, denoms):
+ for monom, coeff in numer.items():
+ numer[monom] = ground.from_sympy(coeff)
+
+ for monom, coeff in denom.items():
+ denom[monom] = ground.from_sympy(coeff)
+
+ result.append(domain((numer, denom)))
+
+ return domain, result
+
+
+def _construct_expression(coeffs, opt):
+ """The last resort case, i.e. use the expression domain. """
+ domain, result = EX, []
+
+ for coeff in coeffs:
+ result.append(domain.from_sympy(coeff))
+
+ return domain, result
+
+
+@public
+def construct_domain(obj, **args):
+ """Construct a minimal domain for a list of expressions.
+
+ Explanation
+ ===========
+
+ Given a list of normal SymPy expressions (of type :py:class:`~.Expr`)
+ ``construct_domain`` will find a minimal :py:class:`~.Domain` that can
+ represent those expressions. The expressions will be converted to elements
+ of the domain and both the domain and the domain elements are returned.
+
+ Parameters
+ ==========
+
+ obj: list or dict
+ The expressions to build a domain for.
+
+ **args: keyword arguments
+ Options that affect the choice of domain.
+
+ Returns
+ =======
+
+ (K, elements): Domain and list of domain elements
+ The domain K that can represent the expressions and the list or dict
+ of domain elements representing the same expressions as elements of K.
+
+ Examples
+ ========
+
+ Given a list of :py:class:`~.Integer` ``construct_domain`` will return the
+ domain :ref:`ZZ` and a list of integers as elements of :ref:`ZZ`.
+
+ >>> from sympy import construct_domain, S
+ >>> expressions = [S(2), S(3), S(4)]
+ >>> K, elements = construct_domain(expressions)
+ >>> K
+ ZZ
+ >>> elements
+ [2, 3, 4]
+ >>> type(elements[0]) # doctest: +SKIP
+
+ >>> type(expressions[0])
+
+
+ If there are any :py:class:`~.Rational` then :ref:`QQ` is returned
+ instead.
+
+ >>> construct_domain([S(1)/2, S(3)/4])
+ (QQ, [1/2, 3/4])
+
+ If there are symbols then a polynomial ring :ref:`K[x]` is returned.
+
+ >>> from sympy import symbols
+ >>> x, y = symbols('x, y')
+ >>> construct_domain([2*x + 1, S(3)/4])
+ (QQ[x], [2*x + 1, 3/4])
+ >>> construct_domain([2*x + 1, y])
+ (ZZ[x,y], [2*x + 1, y])
+
+ If any symbols appear with negative powers then a rational function field
+ :ref:`K(x)` will be returned.
+
+ >>> construct_domain([y/x, x/(1 - y)])
+ (ZZ(x,y), [y/x, -x/(y - 1)])
+
+ Irrational algebraic numbers will result in the :ref:`EX` domain by
+ default. The keyword argument ``extension=True`` leads to the construction
+ of an algebraic number field :ref:`QQ(a)`.
+
+ >>> from sympy import sqrt
+ >>> construct_domain([sqrt(2)])
+ (EX, [EX(sqrt(2))])
+ >>> construct_domain([sqrt(2)], extension=True) # doctest: +SKIP
+ (QQ, [ANP([1, 0], [1, 0, -2], QQ)])
+
+ See also
+ ========
+
+ Domain
+ Expr
+ """
+ opt = build_options(args)
+
+ if hasattr(obj, '__iter__'):
+ if isinstance(obj, dict):
+ if not obj:
+ monoms, coeffs = [], []
+ else:
+ monoms, coeffs = list(zip(*list(obj.items())))
+ else:
+ coeffs = obj
+ else:
+ coeffs = [obj]
+
+ coeffs = list(map(sympify, coeffs))
+ result = _construct_simple(coeffs, opt)
+
+ if result is not None:
+ if result is not False:
+ domain, coeffs = result
+ else:
+ domain, coeffs = _construct_expression(coeffs, opt)
+ else:
+ if opt.composite is False:
+ result = None
+ else:
+ result = _construct_composite(coeffs, opt)
+
+ if result is not None:
+ domain, coeffs = result
+ else:
+ domain, coeffs = _construct_expression(coeffs, opt)
+
+ if hasattr(obj, '__iter__'):
+ if isinstance(obj, dict):
+ return domain, dict(list(zip(monoms, coeffs)))
+ else:
+ return domain, coeffs
+ else:
+ return domain, coeffs[0]
diff --git a/lib/python3.12/site-packages/sympy/polys/densearith.py b/lib/python3.12/site-packages/sympy/polys/densearith.py
new file mode 100644
index 0000000000000000000000000000000000000000..30bf9553b05e9dc53c95df25c6047ba785995b4f
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/densearith.py
@@ -0,0 +1,1875 @@
+"""Arithmetics for dense recursive polynomials in ``K[x]`` or ``K[X]``. """
+
+
+from sympy.polys.densebasic import (
+ dup_slice,
+ dup_LC, dmp_LC,
+ dup_degree, dmp_degree,
+ dup_strip, dmp_strip,
+ dmp_zero_p, dmp_zero,
+ dmp_one_p, dmp_one,
+ dmp_ground, dmp_zeros)
+from sympy.polys.polyerrors import (ExactQuotientFailed, PolynomialDivisionFailed)
+
+def dup_add_term(f, c, i, K):
+ """
+ Add ``c*x**i`` to ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_add_term(x**2 - 1, ZZ(2), 4)
+ 2*x**4 + x**2 - 1
+
+ """
+ if not c:
+ return f
+
+ n = len(f)
+ m = n - i - 1
+
+ if i == n - 1:
+ return dup_strip([f[0] + c] + f[1:])
+ else:
+ if i >= n:
+ return [c] + [K.zero]*(i - n) + f
+ else:
+ return f[:m] + [f[m] + c] + f[m + 1:]
+
+
+def dmp_add_term(f, c, i, u, K):
+ """
+ Add ``c(x_2..x_u)*x_0**i`` to ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_add_term(x*y + 1, 2, 2)
+ 2*x**2 + x*y + 1
+
+ """
+ if not u:
+ return dup_add_term(f, c, i, K)
+
+ v = u - 1
+
+ if dmp_zero_p(c, v):
+ return f
+
+ n = len(f)
+ m = n - i - 1
+
+ if i == n - 1:
+ return dmp_strip([dmp_add(f[0], c, v, K)] + f[1:], u)
+ else:
+ if i >= n:
+ return [c] + dmp_zeros(i - n, v, K) + f
+ else:
+ return f[:m] + [dmp_add(f[m], c, v, K)] + f[m + 1:]
+
+
+def dup_sub_term(f, c, i, K):
+ """
+ Subtract ``c*x**i`` from ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_sub_term(2*x**4 + x**2 - 1, ZZ(2), 4)
+ x**2 - 1
+
+ """
+ if not c:
+ return f
+
+ n = len(f)
+ m = n - i - 1
+
+ if i == n - 1:
+ return dup_strip([f[0] - c] + f[1:])
+ else:
+ if i >= n:
+ return [-c] + [K.zero]*(i - n) + f
+ else:
+ return f[:m] + [f[m] - c] + f[m + 1:]
+
+
+def dmp_sub_term(f, c, i, u, K):
+ """
+ Subtract ``c(x_2..x_u)*x_0**i`` from ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_sub_term(2*x**2 + x*y + 1, 2, 2)
+ x*y + 1
+
+ """
+ if not u:
+ return dup_add_term(f, -c, i, K)
+
+ v = u - 1
+
+ if dmp_zero_p(c, v):
+ return f
+
+ n = len(f)
+ m = n - i - 1
+
+ if i == n - 1:
+ return dmp_strip([dmp_sub(f[0], c, v, K)] + f[1:], u)
+ else:
+ if i >= n:
+ return [dmp_neg(c, v, K)] + dmp_zeros(i - n, v, K) + f
+ else:
+ return f[:m] + [dmp_sub(f[m], c, v, K)] + f[m + 1:]
+
+
+def dup_mul_term(f, c, i, K):
+ """
+ Multiply ``f`` by ``c*x**i`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_mul_term(x**2 - 1, ZZ(3), 2)
+ 3*x**4 - 3*x**2
+
+ """
+ if not c or not f:
+ return []
+ else:
+ return [ cf * c for cf in f ] + [K.zero]*i
+
+
+def dmp_mul_term(f, c, i, u, K):
+ """
+ Multiply ``f`` by ``c(x_2..x_u)*x_0**i`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_mul_term(x**2*y + x, 3*y, 2)
+ 3*x**4*y**2 + 3*x**3*y
+
+ """
+ if not u:
+ return dup_mul_term(f, c, i, K)
+
+ v = u - 1
+
+ if dmp_zero_p(f, u):
+ return f
+ if dmp_zero_p(c, v):
+ return dmp_zero(u)
+ else:
+ return [ dmp_mul(cf, c, v, K) for cf in f ] + dmp_zeros(i, v, K)
+
+
+def dup_add_ground(f, c, K):
+ """
+ Add an element of the ground domain to ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_add_ground(x**3 + 2*x**2 + 3*x + 4, ZZ(4))
+ x**3 + 2*x**2 + 3*x + 8
+
+ """
+ return dup_add_term(f, c, 0, K)
+
+
+def dmp_add_ground(f, c, u, K):
+ """
+ Add an element of the ground domain to ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_add_ground(x**3 + 2*x**2 + 3*x + 4, ZZ(4))
+ x**3 + 2*x**2 + 3*x + 8
+
+ """
+ return dmp_add_term(f, dmp_ground(c, u - 1), 0, u, K)
+
+
+def dup_sub_ground(f, c, K):
+ """
+ Subtract an element of the ground domain from ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_sub_ground(x**3 + 2*x**2 + 3*x + 4, ZZ(4))
+ x**3 + 2*x**2 + 3*x
+
+ """
+ return dup_sub_term(f, c, 0, K)
+
+
+def dmp_sub_ground(f, c, u, K):
+ """
+ Subtract an element of the ground domain from ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_sub_ground(x**3 + 2*x**2 + 3*x + 4, ZZ(4))
+ x**3 + 2*x**2 + 3*x
+
+ """
+ return dmp_sub_term(f, dmp_ground(c, u - 1), 0, u, K)
+
+
+def dup_mul_ground(f, c, K):
+ """
+ Multiply ``f`` by a constant value in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_mul_ground(x**2 + 2*x - 1, ZZ(3))
+ 3*x**2 + 6*x - 3
+
+ """
+ if not c or not f:
+ return []
+ else:
+ return [ cf * c for cf in f ]
+
+
+def dmp_mul_ground(f, c, u, K):
+ """
+ Multiply ``f`` by a constant value in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_mul_ground(2*x + 2*y, ZZ(3))
+ 6*x + 6*y
+
+ """
+ if not u:
+ return dup_mul_ground(f, c, K)
+
+ v = u - 1
+
+ return [ dmp_mul_ground(cf, c, v, K) for cf in f ]
+
+
+def dup_quo_ground(f, c, K):
+ """
+ Quotient by a constant in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x = ring("x", ZZ)
+ >>> R.dup_quo_ground(3*x**2 + 2, ZZ(2))
+ x**2 + 1
+
+ >>> R, x = ring("x", QQ)
+ >>> R.dup_quo_ground(3*x**2 + 2, QQ(2))
+ 3/2*x**2 + 1
+
+ """
+ if not c:
+ raise ZeroDivisionError('polynomial division')
+ if not f:
+ return f
+
+ if K.is_Field:
+ return [ K.quo(cf, c) for cf in f ]
+ else:
+ return [ cf // c for cf in f ]
+
+
+def dmp_quo_ground(f, c, u, K):
+ """
+ Quotient by a constant in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x,y = ring("x,y", ZZ)
+ >>> R.dmp_quo_ground(2*x**2*y + 3*x, ZZ(2))
+ x**2*y + x
+
+ >>> R, x,y = ring("x,y", QQ)
+ >>> R.dmp_quo_ground(2*x**2*y + 3*x, QQ(2))
+ x**2*y + 3/2*x
+
+ """
+ if not u:
+ return dup_quo_ground(f, c, K)
+
+ v = u - 1
+
+ return [ dmp_quo_ground(cf, c, v, K) for cf in f ]
+
+
+def dup_exquo_ground(f, c, K):
+ """
+ Exact quotient by a constant in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x = ring("x", QQ)
+
+ >>> R.dup_exquo_ground(x**2 + 2, QQ(2))
+ 1/2*x**2 + 1
+
+ """
+ if not c:
+ raise ZeroDivisionError('polynomial division')
+ if not f:
+ return f
+
+ return [ K.exquo(cf, c) for cf in f ]
+
+
+def dmp_exquo_ground(f, c, u, K):
+ """
+ Exact quotient by a constant in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x,y = ring("x,y", QQ)
+
+ >>> R.dmp_exquo_ground(x**2*y + 2*x, QQ(2))
+ 1/2*x**2*y + x
+
+ """
+ if not u:
+ return dup_exquo_ground(f, c, K)
+
+ v = u - 1
+
+ return [ dmp_exquo_ground(cf, c, v, K) for cf in f ]
+
+
+def dup_lshift(f, n, K):
+ """
+ Efficiently multiply ``f`` by ``x**n`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_lshift(x**2 + 1, 2)
+ x**4 + x**2
+
+ """
+ if not f:
+ return f
+ else:
+ return f + [K.zero]*n
+
+
+def dup_rshift(f, n, K):
+ """
+ Efficiently divide ``f`` by ``x**n`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_rshift(x**4 + x**2, 2)
+ x**2 + 1
+ >>> R.dup_rshift(x**4 + x**2 + 2, 2)
+ x**2 + 1
+
+ """
+ return f[:-n]
+
+
+def dup_abs(f, K):
+ """
+ Make all coefficients positive in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_abs(x**2 - 1)
+ x**2 + 1
+
+ """
+ return [ K.abs(coeff) for coeff in f ]
+
+
+def dmp_abs(f, u, K):
+ """
+ Make all coefficients positive in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_abs(x**2*y - x)
+ x**2*y + x
+
+ """
+ if not u:
+ return dup_abs(f, K)
+
+ v = u - 1
+
+ return [ dmp_abs(cf, v, K) for cf in f ]
+
+
+def dup_neg(f, K):
+ """
+ Negate a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_neg(x**2 - 1)
+ -x**2 + 1
+
+ """
+ return [ -coeff for coeff in f ]
+
+
+def dmp_neg(f, u, K):
+ """
+ Negate a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_neg(x**2*y - x)
+ -x**2*y + x
+
+ """
+ if not u:
+ return dup_neg(f, K)
+
+ v = u - 1
+
+ return [ dmp_neg(cf, v, K) for cf in f ]
+
+
+def dup_add(f, g, K):
+ """
+ Add dense polynomials in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_add(x**2 - 1, x - 2)
+ x**2 + x - 3
+
+ """
+ if not f:
+ return g
+ if not g:
+ return f
+
+ df = dup_degree(f)
+ dg = dup_degree(g)
+
+ if df == dg:
+ return dup_strip([ a + b for a, b in zip(f, g) ])
+ else:
+ k = abs(df - dg)
+
+ if df > dg:
+ h, f = f[:k], f[k:]
+ else:
+ h, g = g[:k], g[k:]
+
+ return h + [ a + b for a, b in zip(f, g) ]
+
+
+def dmp_add(f, g, u, K):
+ """
+ Add dense polynomials in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_add(x**2 + y, x**2*y + x)
+ x**2*y + x**2 + x + y
+
+ """
+ if not u:
+ return dup_add(f, g, K)
+
+ df = dmp_degree(f, u)
+
+ if df < 0:
+ return g
+
+ dg = dmp_degree(g, u)
+
+ if dg < 0:
+ return f
+
+ v = u - 1
+
+ if df == dg:
+ return dmp_strip([ dmp_add(a, b, v, K) for a, b in zip(f, g) ], u)
+ else:
+ k = abs(df - dg)
+
+ if df > dg:
+ h, f = f[:k], f[k:]
+ else:
+ h, g = g[:k], g[k:]
+
+ return h + [ dmp_add(a, b, v, K) for a, b in zip(f, g) ]
+
+
+def dup_sub(f, g, K):
+ """
+ Subtract dense polynomials in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_sub(x**2 - 1, x - 2)
+ x**2 - x + 1
+
+ """
+ if not f:
+ return dup_neg(g, K)
+ if not g:
+ return f
+
+ df = dup_degree(f)
+ dg = dup_degree(g)
+
+ if df == dg:
+ return dup_strip([ a - b for a, b in zip(f, g) ])
+ else:
+ k = abs(df - dg)
+
+ if df > dg:
+ h, f = f[:k], f[k:]
+ else:
+ h, g = dup_neg(g[:k], K), g[k:]
+
+ return h + [ a - b for a, b in zip(f, g) ]
+
+
+def dmp_sub(f, g, u, K):
+ """
+ Subtract dense polynomials in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_sub(x**2 + y, x**2*y + x)
+ -x**2*y + x**2 - x + y
+
+ """
+ if not u:
+ return dup_sub(f, g, K)
+
+ df = dmp_degree(f, u)
+
+ if df < 0:
+ return dmp_neg(g, u, K)
+
+ dg = dmp_degree(g, u)
+
+ if dg < 0:
+ return f
+
+ v = u - 1
+
+ if df == dg:
+ return dmp_strip([ dmp_sub(a, b, v, K) for a, b in zip(f, g) ], u)
+ else:
+ k = abs(df - dg)
+
+ if df > dg:
+ h, f = f[:k], f[k:]
+ else:
+ h, g = dmp_neg(g[:k], u, K), g[k:]
+
+ return h + [ dmp_sub(a, b, v, K) for a, b in zip(f, g) ]
+
+
+def dup_add_mul(f, g, h, K):
+ """
+ Returns ``f + g*h`` where ``f, g, h`` are in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_add_mul(x**2 - 1, x - 2, x + 2)
+ 2*x**2 - 5
+
+ """
+ return dup_add(f, dup_mul(g, h, K), K)
+
+
+def dmp_add_mul(f, g, h, u, K):
+ """
+ Returns ``f + g*h`` where ``f, g, h`` are in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_add_mul(x**2 + y, x, x + 2)
+ 2*x**2 + 2*x + y
+
+ """
+ return dmp_add(f, dmp_mul(g, h, u, K), u, K)
+
+
+def dup_sub_mul(f, g, h, K):
+ """
+ Returns ``f - g*h`` where ``f, g, h`` are in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_sub_mul(x**2 - 1, x - 2, x + 2)
+ 3
+
+ """
+ return dup_sub(f, dup_mul(g, h, K), K)
+
+
+def dmp_sub_mul(f, g, h, u, K):
+ """
+ Returns ``f - g*h`` where ``f, g, h`` are in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_sub_mul(x**2 + y, x, x + 2)
+ -2*x + y
+
+ """
+ return dmp_sub(f, dmp_mul(g, h, u, K), u, K)
+
+
+def dup_mul(f, g, K):
+ """
+ Multiply dense polynomials in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_mul(x - 2, x + 2)
+ x**2 - 4
+
+ """
+ if f == g:
+ return dup_sqr(f, K)
+
+ if not (f and g):
+ return []
+
+ df = dup_degree(f)
+ dg = dup_degree(g)
+
+ n = max(df, dg) + 1
+
+ if n < 100:
+ h = []
+
+ for i in range(0, df + dg + 1):
+ coeff = K.zero
+
+ for j in range(max(0, i - dg), min(df, i) + 1):
+ coeff += f[j]*g[i - j]
+
+ h.append(coeff)
+
+ return dup_strip(h)
+ else:
+ # Use Karatsuba's algorithm (divide and conquer), see e.g.:
+ # Joris van der Hoeven, Relax But Don't Be Too Lazy,
+ # J. Symbolic Computation, 11 (2002), section 3.1.1.
+ n2 = n//2
+
+ fl, gl = dup_slice(f, 0, n2, K), dup_slice(g, 0, n2, K)
+
+ fh = dup_rshift(dup_slice(f, n2, n, K), n2, K)
+ gh = dup_rshift(dup_slice(g, n2, n, K), n2, K)
+
+ lo, hi = dup_mul(fl, gl, K), dup_mul(fh, gh, K)
+
+ mid = dup_mul(dup_add(fl, fh, K), dup_add(gl, gh, K), K)
+ mid = dup_sub(mid, dup_add(lo, hi, K), K)
+
+ return dup_add(dup_add(lo, dup_lshift(mid, n2, K), K),
+ dup_lshift(hi, 2*n2, K), K)
+
+
+def dmp_mul(f, g, u, K):
+ """
+ Multiply dense polynomials in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_mul(x*y + 1, x)
+ x**2*y + x
+
+ """
+ if not u:
+ return dup_mul(f, g, K)
+
+ if f == g:
+ return dmp_sqr(f, u, K)
+
+ df = dmp_degree(f, u)
+
+ if df < 0:
+ return f
+
+ dg = dmp_degree(g, u)
+
+ if dg < 0:
+ return g
+
+ h, v = [], u - 1
+
+ for i in range(0, df + dg + 1):
+ coeff = dmp_zero(v)
+
+ for j in range(max(0, i - dg), min(df, i) + 1):
+ coeff = dmp_add(coeff, dmp_mul(f[j], g[i - j], v, K), v, K)
+
+ h.append(coeff)
+
+ return dmp_strip(h, u)
+
+
+def dup_sqr(f, K):
+ """
+ Square dense polynomials in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_sqr(x**2 + 1)
+ x**4 + 2*x**2 + 1
+
+ """
+ df, h = len(f) - 1, []
+
+ for i in range(0, 2*df + 1):
+ c = K.zero
+
+ jmin = max(0, i - df)
+ jmax = min(i, df)
+
+ n = jmax - jmin + 1
+
+ jmax = jmin + n // 2 - 1
+
+ for j in range(jmin, jmax + 1):
+ c += f[j]*f[i - j]
+
+ c += c
+
+ if n & 1:
+ elem = f[jmax + 1]
+ c += elem**2
+
+ h.append(c)
+
+ return dup_strip(h)
+
+
+def dmp_sqr(f, u, K):
+ """
+ Square dense polynomials in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_sqr(x**2 + x*y + y**2)
+ x**4 + 2*x**3*y + 3*x**2*y**2 + 2*x*y**3 + y**4
+
+ """
+ if not u:
+ return dup_sqr(f, K)
+
+ df = dmp_degree(f, u)
+
+ if df < 0:
+ return f
+
+ h, v = [], u - 1
+
+ for i in range(0, 2*df + 1):
+ c = dmp_zero(v)
+
+ jmin = max(0, i - df)
+ jmax = min(i, df)
+
+ n = jmax - jmin + 1
+
+ jmax = jmin + n // 2 - 1
+
+ for j in range(jmin, jmax + 1):
+ c = dmp_add(c, dmp_mul(f[j], f[i - j], v, K), v, K)
+
+ c = dmp_mul_ground(c, K(2), v, K)
+
+ if n & 1:
+ elem = dmp_sqr(f[jmax + 1], v, K)
+ c = dmp_add(c, elem, v, K)
+
+ h.append(c)
+
+ return dmp_strip(h, u)
+
+
+def dup_pow(f, n, K):
+ """
+ Raise ``f`` to the ``n``-th power in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_pow(x - 2, 3)
+ x**3 - 6*x**2 + 12*x - 8
+
+ """
+ if not n:
+ return [K.one]
+ if n < 0:
+ raise ValueError("Cannot raise polynomial to a negative power")
+ if n == 1 or not f or f == [K.one]:
+ return f
+
+ g = [K.one]
+
+ while True:
+ n, m = n//2, n
+
+ if m % 2:
+ g = dup_mul(g, f, K)
+
+ if not n:
+ break
+
+ f = dup_sqr(f, K)
+
+ return g
+
+
+def dmp_pow(f, n, u, K):
+ """
+ Raise ``f`` to the ``n``-th power in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_pow(x*y + 1, 3)
+ x**3*y**3 + 3*x**2*y**2 + 3*x*y + 1
+
+ """
+ if not u:
+ return dup_pow(f, n, K)
+
+ if not n:
+ return dmp_one(u, K)
+ if n < 0:
+ raise ValueError("Cannot raise polynomial to a negative power")
+ if n == 1 or dmp_zero_p(f, u) or dmp_one_p(f, u, K):
+ return f
+
+ g = dmp_one(u, K)
+
+ while True:
+ n, m = n//2, n
+
+ if m & 1:
+ g = dmp_mul(g, f, u, K)
+
+ if not n:
+ break
+
+ f = dmp_sqr(f, u, K)
+
+ return g
+
+
+def dup_pdiv(f, g, K):
+ """
+ Polynomial pseudo-division in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_pdiv(x**2 + 1, 2*x - 4)
+ (2*x + 4, 20)
+
+ """
+ df = dup_degree(f)
+ dg = dup_degree(g)
+
+ q, r, dr = [], f, df
+
+ if not g:
+ raise ZeroDivisionError("polynomial division")
+ elif df < dg:
+ return q, r
+
+ N = df - dg + 1
+ lc_g = dup_LC(g, K)
+
+ while True:
+ lc_r = dup_LC(r, K)
+ j, N = dr - dg, N - 1
+
+ Q = dup_mul_ground(q, lc_g, K)
+ q = dup_add_term(Q, lc_r, j, K)
+
+ R = dup_mul_ground(r, lc_g, K)
+ G = dup_mul_term(g, lc_r, j, K)
+ r = dup_sub(R, G, K)
+
+ _dr, dr = dr, dup_degree(r)
+
+ if dr < dg:
+ break
+ elif not (dr < _dr):
+ raise PolynomialDivisionFailed(f, g, K)
+
+ c = lc_g**N
+
+ q = dup_mul_ground(q, c, K)
+ r = dup_mul_ground(r, c, K)
+
+ return q, r
+
+
+def dup_prem(f, g, K):
+ """
+ Polynomial pseudo-remainder in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_prem(x**2 + 1, 2*x - 4)
+ 20
+
+ """
+ df = dup_degree(f)
+ dg = dup_degree(g)
+
+ r, dr = f, df
+
+ if not g:
+ raise ZeroDivisionError("polynomial division")
+ elif df < dg:
+ return r
+
+ N = df - dg + 1
+ lc_g = dup_LC(g, K)
+
+ while True:
+ lc_r = dup_LC(r, K)
+ j, N = dr - dg, N - 1
+
+ R = dup_mul_ground(r, lc_g, K)
+ G = dup_mul_term(g, lc_r, j, K)
+ r = dup_sub(R, G, K)
+
+ _dr, dr = dr, dup_degree(r)
+
+ if dr < dg:
+ break
+ elif not (dr < _dr):
+ raise PolynomialDivisionFailed(f, g, K)
+
+ return dup_mul_ground(r, lc_g**N, K)
+
+
+def dup_pquo(f, g, K):
+ """
+ Polynomial exact pseudo-quotient in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_pquo(x**2 - 1, 2*x - 2)
+ 2*x + 2
+
+ >>> R.dup_pquo(x**2 + 1, 2*x - 4)
+ 2*x + 4
+
+ """
+ return dup_pdiv(f, g, K)[0]
+
+
+def dup_pexquo(f, g, K):
+ """
+ Polynomial pseudo-quotient in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_pexquo(x**2 - 1, 2*x - 2)
+ 2*x + 2
+
+ >>> R.dup_pexquo(x**2 + 1, 2*x - 4)
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: [2, -4] does not divide [1, 0, 1]
+
+ """
+ q, r = dup_pdiv(f, g, K)
+
+ if not r:
+ return q
+ else:
+ raise ExactQuotientFailed(f, g)
+
+
+def dmp_pdiv(f, g, u, K):
+ """
+ Polynomial pseudo-division in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_pdiv(x**2 + x*y, 2*x + 2)
+ (2*x + 2*y - 2, -4*y + 4)
+
+ """
+ if not u:
+ return dup_pdiv(f, g, K)
+
+ df = dmp_degree(f, u)
+ dg = dmp_degree(g, u)
+
+ if dg < 0:
+ raise ZeroDivisionError("polynomial division")
+
+ q, r, dr = dmp_zero(u), f, df
+
+ if df < dg:
+ return q, r
+
+ N = df - dg + 1
+ lc_g = dmp_LC(g, K)
+
+ while True:
+ lc_r = dmp_LC(r, K)
+ j, N = dr - dg, N - 1
+
+ Q = dmp_mul_term(q, lc_g, 0, u, K)
+ q = dmp_add_term(Q, lc_r, j, u, K)
+
+ R = dmp_mul_term(r, lc_g, 0, u, K)
+ G = dmp_mul_term(g, lc_r, j, u, K)
+ r = dmp_sub(R, G, u, K)
+
+ _dr, dr = dr, dmp_degree(r, u)
+
+ if dr < dg:
+ break
+ elif not (dr < _dr):
+ raise PolynomialDivisionFailed(f, g, K)
+
+ c = dmp_pow(lc_g, N, u - 1, K)
+
+ q = dmp_mul_term(q, c, 0, u, K)
+ r = dmp_mul_term(r, c, 0, u, K)
+
+ return q, r
+
+
+def dmp_prem(f, g, u, K):
+ """
+ Polynomial pseudo-remainder in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_prem(x**2 + x*y, 2*x + 2)
+ -4*y + 4
+
+ """
+ if not u:
+ return dup_prem(f, g, K)
+
+ df = dmp_degree(f, u)
+ dg = dmp_degree(g, u)
+
+ if dg < 0:
+ raise ZeroDivisionError("polynomial division")
+
+ r, dr = f, df
+
+ if df < dg:
+ return r
+
+ N = df - dg + 1
+ lc_g = dmp_LC(g, K)
+
+ while True:
+ lc_r = dmp_LC(r, K)
+ j, N = dr - dg, N - 1
+
+ R = dmp_mul_term(r, lc_g, 0, u, K)
+ G = dmp_mul_term(g, lc_r, j, u, K)
+ r = dmp_sub(R, G, u, K)
+
+ _dr, dr = dr, dmp_degree(r, u)
+
+ if dr < dg:
+ break
+ elif not (dr < _dr):
+ raise PolynomialDivisionFailed(f, g, K)
+
+ c = dmp_pow(lc_g, N, u - 1, K)
+
+ return dmp_mul_term(r, c, 0, u, K)
+
+
+def dmp_pquo(f, g, u, K):
+ """
+ Polynomial exact pseudo-quotient in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x**2 + x*y
+ >>> g = 2*x + 2*y
+ >>> h = 2*x + 2
+
+ >>> R.dmp_pquo(f, g)
+ 2*x
+
+ >>> R.dmp_pquo(f, h)
+ 2*x + 2*y - 2
+
+ """
+ return dmp_pdiv(f, g, u, K)[0]
+
+
+def dmp_pexquo(f, g, u, K):
+ """
+ Polynomial pseudo-quotient in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x**2 + x*y
+ >>> g = 2*x + 2*y
+ >>> h = 2*x + 2
+
+ >>> R.dmp_pexquo(f, g)
+ 2*x
+
+ >>> R.dmp_pexquo(f, h)
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: [[2], [2]] does not divide [[1], [1, 0], []]
+
+ """
+ q, r = dmp_pdiv(f, g, u, K)
+
+ if dmp_zero_p(r, u):
+ return q
+ else:
+ raise ExactQuotientFailed(f, g)
+
+
+def dup_rr_div(f, g, K):
+ """
+ Univariate division with remainder over a ring.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_rr_div(x**2 + 1, 2*x - 4)
+ (0, x**2 + 1)
+
+ """
+ df = dup_degree(f)
+ dg = dup_degree(g)
+
+ q, r, dr = [], f, df
+
+ if not g:
+ raise ZeroDivisionError("polynomial division")
+ elif df < dg:
+ return q, r
+
+ lc_g = dup_LC(g, K)
+
+ while True:
+ lc_r = dup_LC(r, K)
+
+ if lc_r % lc_g:
+ break
+
+ c = K.exquo(lc_r, lc_g)
+ j = dr - dg
+
+ q = dup_add_term(q, c, j, K)
+ h = dup_mul_term(g, c, j, K)
+ r = dup_sub(r, h, K)
+
+ _dr, dr = dr, dup_degree(r)
+
+ if dr < dg:
+ break
+ elif not (dr < _dr):
+ raise PolynomialDivisionFailed(f, g, K)
+
+ return q, r
+
+
+def dmp_rr_div(f, g, u, K):
+ """
+ Multivariate division with remainder over a ring.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_rr_div(x**2 + x*y, 2*x + 2)
+ (0, x**2 + x*y)
+
+ """
+ if not u:
+ return dup_rr_div(f, g, K)
+
+ df = dmp_degree(f, u)
+ dg = dmp_degree(g, u)
+
+ if dg < 0:
+ raise ZeroDivisionError("polynomial division")
+
+ q, r, dr = dmp_zero(u), f, df
+
+ if df < dg:
+ return q, r
+
+ lc_g, v = dmp_LC(g, K), u - 1
+
+ while True:
+ lc_r = dmp_LC(r, K)
+ c, R = dmp_rr_div(lc_r, lc_g, v, K)
+
+ if not dmp_zero_p(R, v):
+ break
+
+ j = dr - dg
+
+ q = dmp_add_term(q, c, j, u, K)
+ h = dmp_mul_term(g, c, j, u, K)
+ r = dmp_sub(r, h, u, K)
+
+ _dr, dr = dr, dmp_degree(r, u)
+
+ if dr < dg:
+ break
+ elif not (dr < _dr):
+ raise PolynomialDivisionFailed(f, g, K)
+
+ return q, r
+
+
+def dup_ff_div(f, g, K):
+ """
+ Polynomial division with remainder over a field.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x = ring("x", QQ)
+
+ >>> R.dup_ff_div(x**2 + 1, 2*x - 4)
+ (1/2*x + 1, 5)
+
+ """
+ df = dup_degree(f)
+ dg = dup_degree(g)
+
+ q, r, dr = [], f, df
+
+ if not g:
+ raise ZeroDivisionError("polynomial division")
+ elif df < dg:
+ return q, r
+
+ lc_g = dup_LC(g, K)
+
+ while True:
+ lc_r = dup_LC(r, K)
+
+ c = K.exquo(lc_r, lc_g)
+ j = dr - dg
+
+ q = dup_add_term(q, c, j, K)
+ h = dup_mul_term(g, c, j, K)
+ r = dup_sub(r, h, K)
+
+ _dr, dr = dr, dup_degree(r)
+
+ if dr < dg:
+ break
+ elif dr == _dr and not K.is_Exact:
+ # remove leading term created by rounding error
+ r = dup_strip(r[1:])
+ dr = dup_degree(r)
+ if dr < dg:
+ break
+ elif not (dr < _dr):
+ raise PolynomialDivisionFailed(f, g, K)
+
+ return q, r
+
+
+def dmp_ff_div(f, g, u, K):
+ """
+ Polynomial division with remainder over a field.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x,y = ring("x,y", QQ)
+
+ >>> R.dmp_ff_div(x**2 + x*y, 2*x + 2)
+ (1/2*x + 1/2*y - 1/2, -y + 1)
+
+ """
+ if not u:
+ return dup_ff_div(f, g, K)
+
+ df = dmp_degree(f, u)
+ dg = dmp_degree(g, u)
+
+ if dg < 0:
+ raise ZeroDivisionError("polynomial division")
+
+ q, r, dr = dmp_zero(u), f, df
+
+ if df < dg:
+ return q, r
+
+ lc_g, v = dmp_LC(g, K), u - 1
+
+ while True:
+ lc_r = dmp_LC(r, K)
+ c, R = dmp_ff_div(lc_r, lc_g, v, K)
+
+ if not dmp_zero_p(R, v):
+ break
+
+ j = dr - dg
+
+ q = dmp_add_term(q, c, j, u, K)
+ h = dmp_mul_term(g, c, j, u, K)
+ r = dmp_sub(r, h, u, K)
+
+ _dr, dr = dr, dmp_degree(r, u)
+
+ if dr < dg:
+ break
+ elif not (dr < _dr):
+ raise PolynomialDivisionFailed(f, g, K)
+
+ return q, r
+
+
+def dup_div(f, g, K):
+ """
+ Polynomial division with remainder in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x = ring("x", ZZ)
+ >>> R.dup_div(x**2 + 1, 2*x - 4)
+ (0, x**2 + 1)
+
+ >>> R, x = ring("x", QQ)
+ >>> R.dup_div(x**2 + 1, 2*x - 4)
+ (1/2*x + 1, 5)
+
+ """
+ if K.is_Field:
+ return dup_ff_div(f, g, K)
+ else:
+ return dup_rr_div(f, g, K)
+
+
+def dup_rem(f, g, K):
+ """
+ Returns polynomial remainder in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x = ring("x", ZZ)
+ >>> R.dup_rem(x**2 + 1, 2*x - 4)
+ x**2 + 1
+
+ >>> R, x = ring("x", QQ)
+ >>> R.dup_rem(x**2 + 1, 2*x - 4)
+ 5
+
+ """
+ return dup_div(f, g, K)[1]
+
+
+def dup_quo(f, g, K):
+ """
+ Returns exact polynomial quotient in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x = ring("x", ZZ)
+ >>> R.dup_quo(x**2 + 1, 2*x - 4)
+ 0
+
+ >>> R, x = ring("x", QQ)
+ >>> R.dup_quo(x**2 + 1, 2*x - 4)
+ 1/2*x + 1
+
+ """
+ return dup_div(f, g, K)[0]
+
+
+def dup_exquo(f, g, K):
+ """
+ Returns polynomial quotient in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_exquo(x**2 - 1, x - 1)
+ x + 1
+
+ >>> R.dup_exquo(x**2 + 1, 2*x - 4)
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: [2, -4] does not divide [1, 0, 1]
+
+ """
+ q, r = dup_div(f, g, K)
+
+ if not r:
+ return q
+ else:
+ raise ExactQuotientFailed(f, g)
+
+
+def dmp_div(f, g, u, K):
+ """
+ Polynomial division with remainder in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x,y = ring("x,y", ZZ)
+ >>> R.dmp_div(x**2 + x*y, 2*x + 2)
+ (0, x**2 + x*y)
+
+ >>> R, x,y = ring("x,y", QQ)
+ >>> R.dmp_div(x**2 + x*y, 2*x + 2)
+ (1/2*x + 1/2*y - 1/2, -y + 1)
+
+ """
+ if K.is_Field:
+ return dmp_ff_div(f, g, u, K)
+ else:
+ return dmp_rr_div(f, g, u, K)
+
+
+def dmp_rem(f, g, u, K):
+ """
+ Returns polynomial remainder in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x,y = ring("x,y", ZZ)
+ >>> R.dmp_rem(x**2 + x*y, 2*x + 2)
+ x**2 + x*y
+
+ >>> R, x,y = ring("x,y", QQ)
+ >>> R.dmp_rem(x**2 + x*y, 2*x + 2)
+ -y + 1
+
+ """
+ return dmp_div(f, g, u, K)[1]
+
+
+def dmp_quo(f, g, u, K):
+ """
+ Returns exact polynomial quotient in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x,y = ring("x,y", ZZ)
+ >>> R.dmp_quo(x**2 + x*y, 2*x + 2)
+ 0
+
+ >>> R, x,y = ring("x,y", QQ)
+ >>> R.dmp_quo(x**2 + x*y, 2*x + 2)
+ 1/2*x + 1/2*y - 1/2
+
+ """
+ return dmp_div(f, g, u, K)[0]
+
+
+def dmp_exquo(f, g, u, K):
+ """
+ Returns polynomial quotient in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x**2 + x*y
+ >>> g = x + y
+ >>> h = 2*x + 2
+
+ >>> R.dmp_exquo(f, g)
+ x
+
+ >>> R.dmp_exquo(f, h)
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: [[2], [2]] does not divide [[1], [1, 0], []]
+
+ """
+ q, r = dmp_div(f, g, u, K)
+
+ if dmp_zero_p(r, u):
+ return q
+ else:
+ raise ExactQuotientFailed(f, g)
+
+
+def dup_max_norm(f, K):
+ """
+ Returns maximum norm of a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_max_norm(-x**2 + 2*x - 3)
+ 3
+
+ """
+ if not f:
+ return K.zero
+ else:
+ return max(dup_abs(f, K))
+
+
+def dmp_max_norm(f, u, K):
+ """
+ Returns maximum norm of a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_max_norm(2*x*y - x - 3)
+ 3
+
+ """
+ if not u:
+ return dup_max_norm(f, K)
+
+ v = u - 1
+
+ return max(dmp_max_norm(c, v, K) for c in f)
+
+
+def dup_l1_norm(f, K):
+ """
+ Returns l1 norm of a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_l1_norm(2*x**3 - 3*x**2 + 1)
+ 6
+
+ """
+ if not f:
+ return K.zero
+ else:
+ return sum(dup_abs(f, K))
+
+
+def dmp_l1_norm(f, u, K):
+ """
+ Returns l1 norm of a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_l1_norm(2*x*y - x - 3)
+ 6
+
+ """
+ if not u:
+ return dup_l1_norm(f, K)
+
+ v = u - 1
+
+ return sum(dmp_l1_norm(c, v, K) for c in f)
+
+
+def dup_l2_norm_squared(f, K):
+ """
+ Returns squared l2 norm of a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_l2_norm_squared(2*x**3 - 3*x**2 + 1)
+ 14
+
+ """
+ return sum([coeff**2 for coeff in f], K.zero)
+
+
+def dmp_l2_norm_squared(f, u, K):
+ """
+ Returns squared l2 norm of a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_l2_norm_squared(2*x*y - x - 3)
+ 14
+
+ """
+ if not u:
+ return dup_l2_norm_squared(f, K)
+
+ v = u - 1
+
+ return sum(dmp_l2_norm_squared(c, v, K) for c in f)
+
+
+def dup_expand(polys, K):
+ """
+ Multiply together several polynomials in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_expand([x**2 - 1, x, 2])
+ 2*x**3 - 2*x
+
+ """
+ if not polys:
+ return [K.one]
+
+ f = polys[0]
+
+ for g in polys[1:]:
+ f = dup_mul(f, g, K)
+
+ return f
+
+
+def dmp_expand(polys, u, K):
+ """
+ Multiply together several polynomials in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_expand([x**2 + y**2, x + 1])
+ x**3 + x**2 + x*y**2 + y**2
+
+ """
+ if not polys:
+ return dmp_one(u, K)
+
+ f = polys[0]
+
+ for g in polys[1:]:
+ f = dmp_mul(f, g, u, K)
+
+ return f
diff --git a/lib/python3.12/site-packages/sympy/polys/densebasic.py b/lib/python3.12/site-packages/sympy/polys/densebasic.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3a8a9497302b1af5bca20de100b7ae41e96b439
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/densebasic.py
@@ -0,0 +1,1887 @@
+"""Basic tools for dense recursive polynomials in ``K[x]`` or ``K[X]``. """
+
+
+from sympy.core import igcd
+from sympy.polys.monomials import monomial_min, monomial_div
+from sympy.polys.orderings import monomial_key
+
+import random
+
+
+ninf = float('-inf')
+
+
+def poly_LC(f, K):
+ """
+ Return leading coefficient of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import poly_LC
+
+ >>> poly_LC([], ZZ)
+ 0
+ >>> poly_LC([ZZ(1), ZZ(2), ZZ(3)], ZZ)
+ 1
+
+ """
+ if not f:
+ return K.zero
+ else:
+ return f[0]
+
+
+def poly_TC(f, K):
+ """
+ Return trailing coefficient of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import poly_TC
+
+ >>> poly_TC([], ZZ)
+ 0
+ >>> poly_TC([ZZ(1), ZZ(2), ZZ(3)], ZZ)
+ 3
+
+ """
+ if not f:
+ return K.zero
+ else:
+ return f[-1]
+
+dup_LC = dmp_LC = poly_LC
+dup_TC = dmp_TC = poly_TC
+
+
+def dmp_ground_LC(f, u, K):
+ """
+ Return the ground leading coefficient.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_ground_LC
+
+ >>> f = ZZ.map([[[1], [2, 3]]])
+
+ >>> dmp_ground_LC(f, 2, ZZ)
+ 1
+
+ """
+ while u:
+ f = dmp_LC(f, K)
+ u -= 1
+
+ return dup_LC(f, K)
+
+
+def dmp_ground_TC(f, u, K):
+ """
+ Return the ground trailing coefficient.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_ground_TC
+
+ >>> f = ZZ.map([[[1], [2, 3]]])
+
+ >>> dmp_ground_TC(f, 2, ZZ)
+ 3
+
+ """
+ while u:
+ f = dmp_TC(f, K)
+ u -= 1
+
+ return dup_TC(f, K)
+
+
+def dmp_true_LT(f, u, K):
+ """
+ Return the leading term ``c * x_1**n_1 ... x_k**n_k``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_true_LT
+
+ >>> f = ZZ.map([[4], [2, 0], [3, 0, 0]])
+
+ >>> dmp_true_LT(f, 1, ZZ)
+ ((2, 0), 4)
+
+ """
+ monom = []
+
+ while u:
+ monom.append(len(f) - 1)
+ f, u = f[0], u - 1
+
+ if not f:
+ monom.append(0)
+ else:
+ monom.append(len(f) - 1)
+
+ return tuple(monom), dup_LC(f, K)
+
+
+def dup_degree(f):
+ """
+ Return the leading degree of ``f`` in ``K[x]``.
+
+ Note that the degree of 0 is negative infinity (``float('-inf')``).
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_degree
+
+ >>> f = ZZ.map([1, 2, 0, 3])
+
+ >>> dup_degree(f)
+ 3
+
+ """
+ if not f:
+ return ninf
+ return len(f) - 1
+
+
+def dmp_degree(f, u):
+ """
+ Return the leading degree of ``f`` in ``x_0`` in ``K[X]``.
+
+ Note that the degree of 0 is negative infinity (``float('-inf')``).
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_degree
+
+ >>> dmp_degree([[[]]], 2)
+ -inf
+
+ >>> f = ZZ.map([[2], [1, 2, 3]])
+
+ >>> dmp_degree(f, 1)
+ 1
+
+ """
+ if dmp_zero_p(f, u):
+ return ninf
+ else:
+ return len(f) - 1
+
+
+def _rec_degree_in(g, v, i, j):
+ """Recursive helper function for :func:`dmp_degree_in`."""
+ if i == j:
+ return dmp_degree(g, v)
+
+ v, i = v - 1, i + 1
+
+ return max(_rec_degree_in(c, v, i, j) for c in g)
+
+
+def dmp_degree_in(f, j, u):
+ """
+ Return the leading degree of ``f`` in ``x_j`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_degree_in
+
+ >>> f = ZZ.map([[2], [1, 2, 3]])
+
+ >>> dmp_degree_in(f, 0, 1)
+ 1
+ >>> dmp_degree_in(f, 1, 1)
+ 2
+
+ """
+ if not j:
+ return dmp_degree(f, u)
+ if j < 0 or j > u:
+ raise IndexError("0 <= j <= %s expected, got %s" % (u, j))
+
+ return _rec_degree_in(f, u, 0, j)
+
+
+def _rec_degree_list(g, v, i, degs):
+ """Recursive helper for :func:`dmp_degree_list`."""
+ degs[i] = max(degs[i], dmp_degree(g, v))
+
+ if v > 0:
+ v, i = v - 1, i + 1
+
+ for c in g:
+ _rec_degree_list(c, v, i, degs)
+
+
+def dmp_degree_list(f, u):
+ """
+ Return a list of degrees of ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_degree_list
+
+ >>> f = ZZ.map([[1], [1, 2, 3]])
+
+ >>> dmp_degree_list(f, 1)
+ (1, 2)
+
+ """
+ degs = [ninf]*(u + 1)
+ _rec_degree_list(f, u, 0, degs)
+ return tuple(degs)
+
+
+def dup_strip(f):
+ """
+ Remove leading zeros from ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dup_strip
+
+ >>> dup_strip([0, 0, 1, 2, 3, 0])
+ [1, 2, 3, 0]
+
+ """
+ if not f or f[0]:
+ return f
+
+ i = 0
+
+ for cf in f:
+ if cf:
+ break
+ else:
+ i += 1
+
+ return f[i:]
+
+
+def dmp_strip(f, u):
+ """
+ Remove leading zeros from ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dmp_strip
+
+ >>> dmp_strip([[], [0, 1, 2], [1]], 1)
+ [[0, 1, 2], [1]]
+
+ """
+ if not u:
+ return dup_strip(f)
+
+ if dmp_zero_p(f, u):
+ return f
+
+ i, v = 0, u - 1
+
+ for c in f:
+ if not dmp_zero_p(c, v):
+ break
+ else:
+ i += 1
+
+ if i == len(f):
+ return dmp_zero(u)
+ else:
+ return f[i:]
+
+
+def _rec_validate(f, g, i, K):
+ """Recursive helper for :func:`dmp_validate`."""
+ if not isinstance(g, list):
+ if K is not None and not K.of_type(g):
+ raise TypeError("%s in %s in not of type %s" % (g, f, K.dtype))
+
+ return {i - 1}
+ elif not g:
+ return {i}
+ else:
+ levels = set()
+
+ for c in g:
+ levels |= _rec_validate(f, c, i + 1, K)
+
+ return levels
+
+
+def _rec_strip(g, v):
+ """Recursive helper for :func:`_rec_strip`."""
+ if not v:
+ return dup_strip(g)
+
+ w = v - 1
+
+ return dmp_strip([ _rec_strip(c, w) for c in g ], v)
+
+
+def dmp_validate(f, K=None):
+ """
+ Return the number of levels in ``f`` and recursively strip it.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dmp_validate
+
+ >>> dmp_validate([[], [0, 1, 2], [1]])
+ ([[1, 2], [1]], 1)
+
+ >>> dmp_validate([[1], 1])
+ Traceback (most recent call last):
+ ...
+ ValueError: invalid data structure for a multivariate polynomial
+
+ """
+ levels = _rec_validate(f, f, 0, K)
+
+ u = levels.pop()
+
+ if not levels:
+ return _rec_strip(f, u), u
+ else:
+ raise ValueError(
+ "invalid data structure for a multivariate polynomial")
+
+
+def dup_reverse(f):
+ """
+ Compute ``x**n * f(1/x)``, i.e.: reverse ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_reverse
+
+ >>> f = ZZ.map([1, 2, 3, 0])
+
+ >>> dup_reverse(f)
+ [3, 2, 1]
+
+ """
+ return dup_strip(list(reversed(f)))
+
+
+def dup_copy(f):
+ """
+ Create a new copy of a polynomial ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_copy
+
+ >>> f = ZZ.map([1, 2, 3, 0])
+
+ >>> dup_copy([1, 2, 3, 0])
+ [1, 2, 3, 0]
+
+ """
+ return list(f)
+
+
+def dmp_copy(f, u):
+ """
+ Create a new copy of a polynomial ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_copy
+
+ >>> f = ZZ.map([[1], [1, 2]])
+
+ >>> dmp_copy(f, 1)
+ [[1], [1, 2]]
+
+ """
+ if not u:
+ return list(f)
+
+ v = u - 1
+
+ return [ dmp_copy(c, v) for c in f ]
+
+
+def dup_to_tuple(f):
+ """
+ Convert `f` into a tuple.
+
+ This is needed for hashing. This is similar to dup_copy().
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_copy
+
+ >>> f = ZZ.map([1, 2, 3, 0])
+
+ >>> dup_copy([1, 2, 3, 0])
+ [1, 2, 3, 0]
+
+ """
+ return tuple(f)
+
+
+def dmp_to_tuple(f, u):
+ """
+ Convert `f` into a nested tuple of tuples.
+
+ This is needed for hashing. This is similar to dmp_copy().
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_to_tuple
+
+ >>> f = ZZ.map([[1], [1, 2]])
+
+ >>> dmp_to_tuple(f, 1)
+ ((1,), (1, 2))
+
+ """
+ if not u:
+ return tuple(f)
+ v = u - 1
+
+ return tuple(dmp_to_tuple(c, v) for c in f)
+
+
+def dup_normal(f, K):
+ """
+ Normalize univariate polynomial in the given domain.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_normal
+
+ >>> dup_normal([0, 1, 2, 3], ZZ)
+ [1, 2, 3]
+
+ """
+ return dup_strip([ K.normal(c) for c in f ])
+
+
+def dmp_normal(f, u, K):
+ """
+ Normalize a multivariate polynomial in the given domain.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_normal
+
+ >>> dmp_normal([[], [0, 1, 2]], 1, ZZ)
+ [[1, 2]]
+
+ """
+ if not u:
+ return dup_normal(f, K)
+
+ v = u - 1
+
+ return dmp_strip([ dmp_normal(c, v, K) for c in f ], u)
+
+
+def dup_convert(f, K0, K1):
+ """
+ Convert the ground domain of ``f`` from ``K0`` to ``K1``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_convert
+
+ >>> R, x = ring("x", ZZ)
+
+ >>> dup_convert([R(1), R(2)], R.to_domain(), ZZ)
+ [1, 2]
+ >>> dup_convert([ZZ(1), ZZ(2)], ZZ, R.to_domain())
+ [1, 2]
+
+ """
+ if K0 is not None and K0 == K1:
+ return f
+ else:
+ return dup_strip([ K1.convert(c, K0) for c in f ])
+
+
+def dmp_convert(f, u, K0, K1):
+ """
+ Convert the ground domain of ``f`` from ``K0`` to ``K1``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_convert
+
+ >>> R, x = ring("x", ZZ)
+
+ >>> dmp_convert([[R(1)], [R(2)]], 1, R.to_domain(), ZZ)
+ [[1], [2]]
+ >>> dmp_convert([[ZZ(1)], [ZZ(2)]], 1, ZZ, R.to_domain())
+ [[1], [2]]
+
+ """
+ if not u:
+ return dup_convert(f, K0, K1)
+ if K0 is not None and K0 == K1:
+ return f
+
+ v = u - 1
+
+ return dmp_strip([ dmp_convert(c, v, K0, K1) for c in f ], u)
+
+
+def dup_from_sympy(f, K):
+ """
+ Convert the ground domain of ``f`` from SymPy to ``K``.
+
+ Examples
+ ========
+
+ >>> from sympy import S
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_from_sympy
+
+ >>> dup_from_sympy([S(1), S(2)], ZZ) == [ZZ(1), ZZ(2)]
+ True
+
+ """
+ return dup_strip([ K.from_sympy(c) for c in f ])
+
+
+def dmp_from_sympy(f, u, K):
+ """
+ Convert the ground domain of ``f`` from SymPy to ``K``.
+
+ Examples
+ ========
+
+ >>> from sympy import S
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_from_sympy
+
+ >>> dmp_from_sympy([[S(1)], [S(2)]], 1, ZZ) == [[ZZ(1)], [ZZ(2)]]
+ True
+
+ """
+ if not u:
+ return dup_from_sympy(f, K)
+
+ v = u - 1
+
+ return dmp_strip([ dmp_from_sympy(c, v, K) for c in f ], u)
+
+
+def dup_nth(f, n, K):
+ """
+ Return the ``n``-th coefficient of ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_nth
+
+ >>> f = ZZ.map([1, 2, 3])
+
+ >>> dup_nth(f, 0, ZZ)
+ 3
+ >>> dup_nth(f, 4, ZZ)
+ 0
+
+ """
+ if n < 0:
+ raise IndexError("'n' must be non-negative, got %i" % n)
+ elif n >= len(f):
+ return K.zero
+ else:
+ return f[dup_degree(f) - n]
+
+
+def dmp_nth(f, n, u, K):
+ """
+ Return the ``n``-th coefficient of ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_nth
+
+ >>> f = ZZ.map([[1], [2], [3]])
+
+ >>> dmp_nth(f, 0, 1, ZZ)
+ [3]
+ >>> dmp_nth(f, 4, 1, ZZ)
+ []
+
+ """
+ if n < 0:
+ raise IndexError("'n' must be non-negative, got %i" % n)
+ elif n >= len(f):
+ return dmp_zero(u - 1)
+ else:
+ return f[dmp_degree(f, u) - n]
+
+
+def dmp_ground_nth(f, N, u, K):
+ """
+ Return the ground ``n``-th coefficient of ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_ground_nth
+
+ >>> f = ZZ.map([[1], [2, 3]])
+
+ >>> dmp_ground_nth(f, (0, 1), 1, ZZ)
+ 2
+
+ """
+ v = u
+
+ for n in N:
+ if n < 0:
+ raise IndexError("`n` must be non-negative, got %i" % n)
+ elif n >= len(f):
+ return K.zero
+ else:
+ d = dmp_degree(f, v)
+ if d == ninf:
+ d = -1
+ f, v = f[d - n], v - 1
+
+ return f
+
+
+def dmp_zero_p(f, u):
+ """
+ Return ``True`` if ``f`` is zero in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dmp_zero_p
+
+ >>> dmp_zero_p([[[[[]]]]], 4)
+ True
+ >>> dmp_zero_p([[[[[1]]]]], 4)
+ False
+
+ """
+ while u:
+ if len(f) != 1:
+ return False
+
+ f = f[0]
+ u -= 1
+
+ return not f
+
+
+def dmp_zero(u):
+ """
+ Return a multivariate zero.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dmp_zero
+
+ >>> dmp_zero(4)
+ [[[[[]]]]]
+
+ """
+ r = []
+
+ for i in range(u):
+ r = [r]
+
+ return r
+
+
+def dmp_one_p(f, u, K):
+ """
+ Return ``True`` if ``f`` is one in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_one_p
+
+ >>> dmp_one_p([[[ZZ(1)]]], 2, ZZ)
+ True
+
+ """
+ return dmp_ground_p(f, K.one, u)
+
+
+def dmp_one(u, K):
+ """
+ Return a multivariate one over ``K``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_one
+
+ >>> dmp_one(2, ZZ)
+ [[[1]]]
+
+ """
+ return dmp_ground(K.one, u)
+
+
+def dmp_ground_p(f, c, u):
+ """
+ Return True if ``f`` is constant in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dmp_ground_p
+
+ >>> dmp_ground_p([[[3]]], 3, 2)
+ True
+ >>> dmp_ground_p([[[4]]], None, 2)
+ True
+
+ """
+ if c is not None and not c:
+ return dmp_zero_p(f, u)
+
+ while u:
+ if len(f) != 1:
+ return False
+ f = f[0]
+ u -= 1
+
+ if c is None:
+ return len(f) <= 1
+ else:
+ return f == [c]
+
+
+def dmp_ground(c, u):
+ """
+ Return a multivariate constant.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dmp_ground
+
+ >>> dmp_ground(3, 5)
+ [[[[[[3]]]]]]
+ >>> dmp_ground(1, -1)
+ 1
+
+ """
+ if not c:
+ return dmp_zero(u)
+
+ for i in range(u + 1):
+ c = [c]
+
+ return c
+
+
+def dmp_zeros(n, u, K):
+ """
+ Return a list of multivariate zeros.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_zeros
+
+ >>> dmp_zeros(3, 2, ZZ)
+ [[[[]]], [[[]]], [[[]]]]
+ >>> dmp_zeros(3, -1, ZZ)
+ [0, 0, 0]
+
+ """
+ if not n:
+ return []
+
+ if u < 0:
+ return [K.zero]*n
+ else:
+ return [ dmp_zero(u) for i in range(n) ]
+
+
+def dmp_grounds(c, n, u):
+ """
+ Return a list of multivariate constants.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_grounds
+
+ >>> dmp_grounds(ZZ(4), 3, 2)
+ [[[[4]]], [[[4]]], [[[4]]]]
+ >>> dmp_grounds(ZZ(4), 3, -1)
+ [4, 4, 4]
+
+ """
+ if not n:
+ return []
+
+ if u < 0:
+ return [c]*n
+ else:
+ return [ dmp_ground(c, u) for i in range(n) ]
+
+
+def dmp_negative_p(f, u, K):
+ """
+ Return ``True`` if ``LC(f)`` is negative.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_negative_p
+
+ >>> dmp_negative_p([[ZZ(1)], [-ZZ(1)]], 1, ZZ)
+ False
+ >>> dmp_negative_p([[-ZZ(1)], [ZZ(1)]], 1, ZZ)
+ True
+
+ """
+ return K.is_negative(dmp_ground_LC(f, u, K))
+
+
+def dmp_positive_p(f, u, K):
+ """
+ Return ``True`` if ``LC(f)`` is positive.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_positive_p
+
+ >>> dmp_positive_p([[ZZ(1)], [-ZZ(1)]], 1, ZZ)
+ True
+ >>> dmp_positive_p([[-ZZ(1)], [ZZ(1)]], 1, ZZ)
+ False
+
+ """
+ return K.is_positive(dmp_ground_LC(f, u, K))
+
+
+def dup_from_dict(f, K):
+ """
+ Create a ``K[x]`` polynomial from a ``dict``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_from_dict
+
+ >>> dup_from_dict({(0,): ZZ(7), (2,): ZZ(5), (4,): ZZ(1)}, ZZ)
+ [1, 0, 5, 0, 7]
+ >>> dup_from_dict({}, ZZ)
+ []
+
+ """
+ if not f:
+ return []
+
+ n, h = max(f.keys()), []
+
+ if isinstance(n, int):
+ for k in range(n, -1, -1):
+ h.append(f.get(k, K.zero))
+ else:
+ (n,) = n
+
+ for k in range(n, -1, -1):
+ h.append(f.get((k,), K.zero))
+
+ return dup_strip(h)
+
+
+def dup_from_raw_dict(f, K):
+ """
+ Create a ``K[x]`` polynomial from a raw ``dict``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_from_raw_dict
+
+ >>> dup_from_raw_dict({0: ZZ(7), 2: ZZ(5), 4: ZZ(1)}, ZZ)
+ [1, 0, 5, 0, 7]
+
+ """
+ if not f:
+ return []
+
+ n, h = max(f.keys()), []
+
+ for k in range(n, -1, -1):
+ h.append(f.get(k, K.zero))
+
+ return dup_strip(h)
+
+
+def dmp_from_dict(f, u, K):
+ """
+ Create a ``K[X]`` polynomial from a ``dict``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_from_dict
+
+ >>> dmp_from_dict({(0, 0): ZZ(3), (0, 1): ZZ(2), (2, 1): ZZ(1)}, 1, ZZ)
+ [[1, 0], [], [2, 3]]
+ >>> dmp_from_dict({}, 0, ZZ)
+ []
+
+ """
+ if not u:
+ return dup_from_dict(f, K)
+ if not f:
+ return dmp_zero(u)
+
+ coeffs = {}
+
+ for monom, coeff in f.items():
+ head, tail = monom[0], monom[1:]
+
+ if head in coeffs:
+ coeffs[head][tail] = coeff
+ else:
+ coeffs[head] = { tail: coeff }
+
+ n, v, h = max(coeffs.keys()), u - 1, []
+
+ for k in range(n, -1, -1):
+ coeff = coeffs.get(k)
+
+ if coeff is not None:
+ h.append(dmp_from_dict(coeff, v, K))
+ else:
+ h.append(dmp_zero(v))
+
+ return dmp_strip(h, u)
+
+
+def dup_to_dict(f, K=None, zero=False):
+ """
+ Convert ``K[x]`` polynomial to a ``dict``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dup_to_dict
+
+ >>> dup_to_dict([1, 0, 5, 0, 7])
+ {(0,): 7, (2,): 5, (4,): 1}
+ >>> dup_to_dict([])
+ {}
+
+ """
+ if not f and zero:
+ return {(0,): K.zero}
+
+ n, result = len(f) - 1, {}
+
+ for k in range(0, n + 1):
+ if f[n - k]:
+ result[(k,)] = f[n - k]
+
+ return result
+
+
+def dup_to_raw_dict(f, K=None, zero=False):
+ """
+ Convert a ``K[x]`` polynomial to a raw ``dict``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dup_to_raw_dict
+
+ >>> dup_to_raw_dict([1, 0, 5, 0, 7])
+ {0: 7, 2: 5, 4: 1}
+
+ """
+ if not f and zero:
+ return {0: K.zero}
+
+ n, result = len(f) - 1, {}
+
+ for k in range(0, n + 1):
+ if f[n - k]:
+ result[k] = f[n - k]
+
+ return result
+
+
+def dmp_to_dict(f, u, K=None, zero=False):
+ """
+ Convert a ``K[X]`` polynomial to a ``dict````.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.densebasic import dmp_to_dict
+
+ >>> dmp_to_dict([[1, 0], [], [2, 3]], 1)
+ {(0, 0): 3, (0, 1): 2, (2, 1): 1}
+ >>> dmp_to_dict([], 0)
+ {}
+
+ """
+ if not u:
+ return dup_to_dict(f, K, zero=zero)
+
+ if dmp_zero_p(f, u) and zero:
+ return {(0,)*(u + 1): K.zero}
+
+ n, v, result = dmp_degree(f, u), u - 1, {}
+
+ if n == ninf:
+ n = -1
+
+ for k in range(0, n + 1):
+ h = dmp_to_dict(f[n - k], v)
+
+ for exp, coeff in h.items():
+ result[(k,) + exp] = coeff
+
+ return result
+
+
+def dmp_swap(f, i, j, u, K):
+ """
+ Transform ``K[..x_i..x_j..]`` to ``K[..x_j..x_i..]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_swap
+
+ >>> f = ZZ.map([[[2], [1, 0]], []])
+
+ >>> dmp_swap(f, 0, 1, 2, ZZ)
+ [[[2], []], [[1, 0], []]]
+ >>> dmp_swap(f, 1, 2, 2, ZZ)
+ [[[1], [2, 0]], [[]]]
+ >>> dmp_swap(f, 0, 2, 2, ZZ)
+ [[[1, 0]], [[2, 0], []]]
+
+ """
+ if i < 0 or j < 0 or i > u or j > u:
+ raise IndexError("0 <= i < j <= %s expected" % u)
+ elif i == j:
+ return f
+
+ F, H = dmp_to_dict(f, u), {}
+
+ for exp, coeff in F.items():
+ H[exp[:i] + (exp[j],) +
+ exp[i + 1:j] +
+ (exp[i],) + exp[j + 1:]] = coeff
+
+ return dmp_from_dict(H, u, K)
+
+
+def dmp_permute(f, P, u, K):
+ """
+ Return a polynomial in ``K[x_{P(1)},..,x_{P(n)}]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_permute
+
+ >>> f = ZZ.map([[[2], [1, 0]], []])
+
+ >>> dmp_permute(f, [1, 0, 2], 2, ZZ)
+ [[[2], []], [[1, 0], []]]
+ >>> dmp_permute(f, [1, 2, 0], 2, ZZ)
+ [[[1], []], [[2, 0], []]]
+
+ """
+ F, H = dmp_to_dict(f, u), {}
+
+ for exp, coeff in F.items():
+ new_exp = [0]*len(exp)
+
+ for e, p in zip(exp, P):
+ new_exp[p] = e
+
+ H[tuple(new_exp)] = coeff
+
+ return dmp_from_dict(H, u, K)
+
+
+def dmp_nest(f, l, K):
+ """
+ Return a multivariate value nested ``l``-levels.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_nest
+
+ >>> dmp_nest([[ZZ(1)]], 2, ZZ)
+ [[[[1]]]]
+
+ """
+ if not isinstance(f, list):
+ return dmp_ground(f, l)
+
+ for i in range(l):
+ f = [f]
+
+ return f
+
+
+def dmp_raise(f, l, u, K):
+ """
+ Return a multivariate polynomial raised ``l``-levels.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_raise
+
+ >>> f = ZZ.map([[], [1, 2]])
+
+ >>> dmp_raise(f, 2, 1, ZZ)
+ [[[[]]], [[[1]], [[2]]]]
+
+ """
+ if not l:
+ return f
+
+ if not u:
+ if not f:
+ return dmp_zero(l)
+
+ k = l - 1
+
+ return [ dmp_ground(c, k) for c in f ]
+
+ v = u - 1
+
+ return [ dmp_raise(c, l, v, K) for c in f ]
+
+
+def dup_deflate(f, K):
+ """
+ Map ``x**m`` to ``y`` in a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_deflate
+
+ >>> f = ZZ.map([1, 0, 0, 1, 0, 0, 1])
+
+ >>> dup_deflate(f, ZZ)
+ (3, [1, 1, 1])
+
+ """
+ if dup_degree(f) <= 0:
+ return 1, f
+
+ g = 0
+
+ for i in range(len(f)):
+ if not f[-i - 1]:
+ continue
+
+ g = igcd(g, i)
+
+ if g == 1:
+ return 1, f
+
+ return g, f[::g]
+
+
+def dmp_deflate(f, u, K):
+ """
+ Map ``x_i**m_i`` to ``y_i`` in a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_deflate
+
+ >>> f = ZZ.map([[1, 0, 0, 2], [], [3, 0, 0, 4]])
+
+ >>> dmp_deflate(f, 1, ZZ)
+ ((2, 3), [[1, 2], [3, 4]])
+
+ """
+ if dmp_zero_p(f, u):
+ return (1,)*(u + 1), f
+
+ F = dmp_to_dict(f, u)
+ B = [0]*(u + 1)
+
+ for M in F.keys():
+ for i, m in enumerate(M):
+ B[i] = igcd(B[i], m)
+
+ for i, b in enumerate(B):
+ if not b:
+ B[i] = 1
+
+ B = tuple(B)
+
+ if all(b == 1 for b in B):
+ return B, f
+
+ H = {}
+
+ for A, coeff in F.items():
+ N = [ a // b for a, b in zip(A, B) ]
+ H[tuple(N)] = coeff
+
+ return B, dmp_from_dict(H, u, K)
+
+
+def dup_multi_deflate(polys, K):
+ """
+ Map ``x**m`` to ``y`` in a set of polynomials in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_multi_deflate
+
+ >>> f = ZZ.map([1, 0, 2, 0, 3])
+ >>> g = ZZ.map([4, 0, 0])
+
+ >>> dup_multi_deflate((f, g), ZZ)
+ (2, ([1, 2, 3], [4, 0]))
+
+ """
+ G = 0
+
+ for p in polys:
+ if dup_degree(p) <= 0:
+ return 1, polys
+
+ g = 0
+
+ for i in range(len(p)):
+ if not p[-i - 1]:
+ continue
+
+ g = igcd(g, i)
+
+ if g == 1:
+ return 1, polys
+
+ G = igcd(G, g)
+
+ return G, tuple([ p[::G] for p in polys ])
+
+
+def dmp_multi_deflate(polys, u, K):
+ """
+ Map ``x_i**m_i`` to ``y_i`` in a set of polynomials in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_multi_deflate
+
+ >>> f = ZZ.map([[1, 0, 0, 2], [], [3, 0, 0, 4]])
+ >>> g = ZZ.map([[1, 0, 2], [], [3, 0, 4]])
+
+ >>> dmp_multi_deflate((f, g), 1, ZZ)
+ ((2, 1), ([[1, 0, 0, 2], [3, 0, 0, 4]], [[1, 0, 2], [3, 0, 4]]))
+
+ """
+ if not u:
+ M, H = dup_multi_deflate(polys, K)
+ return (M,), H
+
+ F, B = [], [0]*(u + 1)
+
+ for p in polys:
+ f = dmp_to_dict(p, u)
+
+ if not dmp_zero_p(p, u):
+ for M in f.keys():
+ for i, m in enumerate(M):
+ B[i] = igcd(B[i], m)
+
+ F.append(f)
+
+ for i, b in enumerate(B):
+ if not b:
+ B[i] = 1
+
+ B = tuple(B)
+
+ if all(b == 1 for b in B):
+ return B, polys
+
+ H = []
+
+ for f in F:
+ h = {}
+
+ for A, coeff in f.items():
+ N = [ a // b for a, b in zip(A, B) ]
+ h[tuple(N)] = coeff
+
+ H.append(dmp_from_dict(h, u, K))
+
+ return B, tuple(H)
+
+
+def dup_inflate(f, m, K):
+ """
+ Map ``y`` to ``x**m`` in a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_inflate
+
+ >>> f = ZZ.map([1, 1, 1])
+
+ >>> dup_inflate(f, 3, ZZ)
+ [1, 0, 0, 1, 0, 0, 1]
+
+ """
+ if m <= 0:
+ raise IndexError("'m' must be positive, got %s" % m)
+ if m == 1 or not f:
+ return f
+
+ result = [f[0]]
+
+ for coeff in f[1:]:
+ result.extend([K.zero]*(m - 1))
+ result.append(coeff)
+
+ return result
+
+
+def _rec_inflate(g, M, v, i, K):
+ """Recursive helper for :func:`dmp_inflate`."""
+ if not v:
+ return dup_inflate(g, M[i], K)
+ if M[i] <= 0:
+ raise IndexError("all M[i] must be positive, got %s" % M[i])
+
+ w, j = v - 1, i + 1
+
+ g = [ _rec_inflate(c, M, w, j, K) for c in g ]
+
+ result = [g[0]]
+
+ for coeff in g[1:]:
+ for _ in range(1, M[i]):
+ result.append(dmp_zero(w))
+
+ result.append(coeff)
+
+ return result
+
+
+def dmp_inflate(f, M, u, K):
+ """
+ Map ``y_i`` to ``x_i**k_i`` in a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_inflate
+
+ >>> f = ZZ.map([[1, 2], [3, 4]])
+
+ >>> dmp_inflate(f, (2, 3), 1, ZZ)
+ [[1, 0, 0, 2], [], [3, 0, 0, 4]]
+
+ """
+ if not u:
+ return dup_inflate(f, M[0], K)
+
+ if all(m == 1 for m in M):
+ return f
+ else:
+ return _rec_inflate(f, M, u, 0, K)
+
+
+def dmp_exclude(f, u, K):
+ """
+ Exclude useless levels from ``f``.
+
+ Return the levels excluded, the new excluded ``f``, and the new ``u``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_exclude
+
+ >>> f = ZZ.map([[[1]], [[1], [2]]])
+
+ >>> dmp_exclude(f, 2, ZZ)
+ ([2], [[1], [1, 2]], 1)
+
+ """
+ if not u or dmp_ground_p(f, None, u):
+ return [], f, u
+
+ J, F = [], dmp_to_dict(f, u)
+
+ for j in range(0, u + 1):
+ for monom in F.keys():
+ if monom[j]:
+ break
+ else:
+ J.append(j)
+
+ if not J:
+ return [], f, u
+
+ f = {}
+
+ for monom, coeff in F.items():
+ monom = list(monom)
+
+ for j in reversed(J):
+ del monom[j]
+
+ f[tuple(monom)] = coeff
+
+ u -= len(J)
+
+ return J, dmp_from_dict(f, u, K), u
+
+
+def dmp_include(f, J, u, K):
+ """
+ Include useless levels in ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_include
+
+ >>> f = ZZ.map([[1], [1, 2]])
+
+ >>> dmp_include(f, [2], 1, ZZ)
+ [[[1]], [[1], [2]]]
+
+ """
+ if not J:
+ return f
+
+ F, f = dmp_to_dict(f, u), {}
+
+ for monom, coeff in F.items():
+ monom = list(monom)
+
+ for j in J:
+ monom.insert(j, 0)
+
+ f[tuple(monom)] = coeff
+
+ u += len(J)
+
+ return dmp_from_dict(f, u, K)
+
+
+def dmp_inject(f, u, K, front=False):
+ """
+ Convert ``f`` from ``K[X][Y]`` to ``K[X,Y]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_inject
+
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> dmp_inject([R(1), x + 2], 0, R.to_domain())
+ ([[[1]], [[1], [2]]], 2)
+ >>> dmp_inject([R(1), x + 2], 0, R.to_domain(), front=True)
+ ([[[1]], [[1, 2]]], 2)
+
+ """
+ f, h = dmp_to_dict(f, u), {}
+
+ v = K.ngens - 1
+
+ for f_monom, g in f.items():
+ g = g.to_dict()
+
+ for g_monom, c in g.items():
+ if front:
+ h[g_monom + f_monom] = c
+ else:
+ h[f_monom + g_monom] = c
+
+ w = u + v + 1
+
+ return dmp_from_dict(h, w, K.dom), w
+
+
+def dmp_eject(f, u, K, front=False):
+ """
+ Convert ``f`` from ``K[X,Y]`` to ``K[X][Y]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_eject
+
+ >>> dmp_eject([[[1]], [[1], [2]]], 2, ZZ['x', 'y'])
+ [1, x + 2]
+
+ """
+ f, h = dmp_to_dict(f, u), {}
+
+ n = K.ngens
+ v = u - K.ngens + 1
+
+ for monom, c in f.items():
+ if front:
+ g_monom, f_monom = monom[:n], monom[n:]
+ else:
+ g_monom, f_monom = monom[-n:], monom[:-n]
+
+ if f_monom in h:
+ h[f_monom][g_monom] = c
+ else:
+ h[f_monom] = {g_monom: c}
+
+ for monom, c in h.items():
+ h[monom] = K(c)
+
+ return dmp_from_dict(h, v - 1, K)
+
+
+def dup_terms_gcd(f, K):
+ """
+ Remove GCD of terms from ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_terms_gcd
+
+ >>> f = ZZ.map([1, 0, 1, 0, 0])
+
+ >>> dup_terms_gcd(f, ZZ)
+ (2, [1, 0, 1])
+
+ """
+ if dup_TC(f, K) or not f:
+ return 0, f
+
+ i = 0
+
+ for c in reversed(f):
+ if not c:
+ i += 1
+ else:
+ break
+
+ return i, f[:-i]
+
+
+def dmp_terms_gcd(f, u, K):
+ """
+ Remove GCD of terms from ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_terms_gcd
+
+ >>> f = ZZ.map([[1, 0], [1, 0, 0], [], []])
+
+ >>> dmp_terms_gcd(f, 1, ZZ)
+ ((2, 1), [[1], [1, 0]])
+
+ """
+ if dmp_ground_TC(f, u, K) or dmp_zero_p(f, u):
+ return (0,)*(u + 1), f
+
+ F = dmp_to_dict(f, u)
+ G = monomial_min(*list(F.keys()))
+
+ if all(g == 0 for g in G):
+ return G, f
+
+ f = {}
+
+ for monom, coeff in F.items():
+ f[monomial_div(monom, G)] = coeff
+
+ return G, dmp_from_dict(f, u, K)
+
+
+def _rec_list_terms(g, v, monom):
+ """Recursive helper for :func:`dmp_list_terms`."""
+ d, terms = dmp_degree(g, v), []
+
+ if not v:
+ for i, c in enumerate(g):
+ if not c:
+ continue
+
+ terms.append((monom + (d - i,), c))
+ else:
+ w = v - 1
+
+ for i, c in enumerate(g):
+ terms.extend(_rec_list_terms(c, w, monom + (d - i,)))
+
+ return terms
+
+
+def dmp_list_terms(f, u, K, order=None):
+ """
+ List all non-zero terms from ``f`` in the given order ``order``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_list_terms
+
+ >>> f = ZZ.map([[1, 1], [2, 3]])
+
+ >>> dmp_list_terms(f, 1, ZZ)
+ [((1, 1), 1), ((1, 0), 1), ((0, 1), 2), ((0, 0), 3)]
+ >>> dmp_list_terms(f, 1, ZZ, order='grevlex')
+ [((1, 1), 1), ((1, 0), 1), ((0, 1), 2), ((0, 0), 3)]
+
+ """
+ def sort(terms, O):
+ return sorted(terms, key=lambda term: O(term[0]), reverse=True)
+
+ terms = _rec_list_terms(f, u, ())
+
+ if not terms:
+ return [((0,)*(u + 1), K.zero)]
+
+ if order is None:
+ return terms
+ else:
+ return sort(terms, monomial_key(order))
+
+
+def dup_apply_pairs(f, g, h, args, K):
+ """
+ Apply ``h`` to pairs of coefficients of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_apply_pairs
+
+ >>> h = lambda x, y, z: 2*x + y - z
+
+ >>> dup_apply_pairs([1, 2, 3], [3, 2, 1], h, (1,), ZZ)
+ [4, 5, 6]
+
+ """
+ n, m = len(f), len(g)
+
+ if n != m:
+ if n > m:
+ g = [K.zero]*(n - m) + g
+ else:
+ f = [K.zero]*(m - n) + f
+
+ result = []
+
+ for a, b in zip(f, g):
+ result.append(h(a, b, *args))
+
+ return dup_strip(result)
+
+
+def dmp_apply_pairs(f, g, h, args, u, K):
+ """
+ Apply ``h`` to pairs of coefficients of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dmp_apply_pairs
+
+ >>> h = lambda x, y, z: 2*x + y - z
+
+ >>> dmp_apply_pairs([[1], [2, 3]], [[3], [2, 1]], h, (1,), 1, ZZ)
+ [[4], [5, 6]]
+
+ """
+ if not u:
+ return dup_apply_pairs(f, g, h, args, K)
+
+ n, m, v = len(f), len(g), u - 1
+
+ if n != m:
+ if n > m:
+ g = dmp_zeros(n - m, v, K) + g
+ else:
+ f = dmp_zeros(m - n, v, K) + f
+
+ result = []
+
+ for a, b in zip(f, g):
+ result.append(dmp_apply_pairs(a, b, h, args, v, K))
+
+ return dmp_strip(result, u)
+
+
+def dup_slice(f, m, n, K):
+ """Take a continuous subsequence of terms of ``f`` in ``K[x]``. """
+ k = len(f)
+
+ if k >= m:
+ M = k - m
+ else:
+ M = 0
+ if k >= n:
+ N = k - n
+ else:
+ N = 0
+
+ f = f[N:M]
+
+ while f and f[0] == K.zero:
+ f.pop(0)
+
+ if not f:
+ return []
+ else:
+ return f + [K.zero]*m
+
+
+def dmp_slice(f, m, n, u, K):
+ """Take a continuous subsequence of terms of ``f`` in ``K[X]``. """
+ return dmp_slice_in(f, m, n, 0, u, K)
+
+
+def dmp_slice_in(f, m, n, j, u, K):
+ """Take a continuous subsequence of terms of ``f`` in ``x_j`` in ``K[X]``. """
+ if j < 0 or j > u:
+ raise IndexError("-%s <= j < %s expected, got %s" % (u, u, j))
+
+ if not u:
+ return dup_slice(f, m, n, K)
+
+ f, g = dmp_to_dict(f, u), {}
+
+ for monom, coeff in f.items():
+ k = monom[j]
+
+ if k < m or k >= n:
+ monom = monom[:j] + (0,) + monom[j + 1:]
+
+ if monom in g:
+ g[monom] += coeff
+ else:
+ g[monom] = coeff
+
+ return dmp_from_dict(g, u, K)
+
+
+def dup_random(n, a, b, K):
+ """
+ Return a polynomial of degree ``n`` with coefficients in ``[a, b]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.densebasic import dup_random
+
+ >>> dup_random(3, -10, 10, ZZ) #doctest: +SKIP
+ [-2, -8, 9, -4]
+
+ """
+ f = [ K.convert(random.randint(a, b)) for _ in range(0, n + 1) ]
+
+ while not f[0]:
+ f[0] = K.convert(random.randint(a, b))
+
+ return f
diff --git a/lib/python3.12/site-packages/sympy/polys/densetools.py b/lib/python3.12/site-packages/sympy/polys/densetools.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c9db99e9e68efe31d8c4ddb132f2408978dd249
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/densetools.py
@@ -0,0 +1,1363 @@
+"""Advanced tools for dense recursive polynomials in ``K[x]`` or ``K[X]``. """
+
+
+from sympy.polys.densearith import (
+ dup_add_term, dmp_add_term,
+ dup_lshift,
+ dup_add, dmp_add,
+ dup_sub, dmp_sub,
+ dup_mul, dmp_mul,
+ dup_sqr,
+ dup_div,
+ dup_rem, dmp_rem,
+ dmp_expand,
+ dup_mul_ground, dmp_mul_ground,
+ dup_quo_ground, dmp_quo_ground,
+ dup_exquo_ground, dmp_exquo_ground,
+)
+from sympy.polys.densebasic import (
+ dup_strip, dmp_strip,
+ dup_convert, dmp_convert,
+ dup_degree, dmp_degree,
+ dmp_to_dict,
+ dmp_from_dict,
+ dup_LC, dmp_LC, dmp_ground_LC,
+ dup_TC, dmp_TC,
+ dmp_zero, dmp_ground,
+ dmp_zero_p,
+ dup_to_raw_dict, dup_from_raw_dict,
+ dmp_zeros
+)
+from sympy.polys.polyerrors import (
+ MultivariatePolynomialError,
+ DomainError
+)
+from sympy.utilities import variations
+
+from math import ceil as _ceil, log2 as _log2
+
+def dup_integrate(f, m, K):
+ """
+ Computes the indefinite integral of ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x = ring("x", QQ)
+
+ >>> R.dup_integrate(x**2 + 2*x, 1)
+ 1/3*x**3 + x**2
+ >>> R.dup_integrate(x**2 + 2*x, 2)
+ 1/12*x**4 + 1/3*x**3
+
+ """
+ if m <= 0 or not f:
+ return f
+
+ g = [K.zero]*m
+
+ for i, c in enumerate(reversed(f)):
+ n = i + 1
+
+ for j in range(1, m):
+ n *= i + j + 1
+
+ g.insert(0, K.exquo(c, K(n)))
+
+ return g
+
+
+def dmp_integrate(f, m, u, K):
+ """
+ Computes the indefinite integral of ``f`` in ``x_0`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x,y = ring("x,y", QQ)
+
+ >>> R.dmp_integrate(x + 2*y, 1)
+ 1/2*x**2 + 2*x*y
+ >>> R.dmp_integrate(x + 2*y, 2)
+ 1/6*x**3 + x**2*y
+
+ """
+ if not u:
+ return dup_integrate(f, m, K)
+
+ if m <= 0 or dmp_zero_p(f, u):
+ return f
+
+ g, v = dmp_zeros(m, u - 1, K), u - 1
+
+ for i, c in enumerate(reversed(f)):
+ n = i + 1
+
+ for j in range(1, m):
+ n *= i + j + 1
+
+ g.insert(0, dmp_quo_ground(c, K(n), v, K))
+
+ return g
+
+
+def _rec_integrate_in(g, m, v, i, j, K):
+ """Recursive helper for :func:`dmp_integrate_in`."""
+ if i == j:
+ return dmp_integrate(g, m, v, K)
+
+ w, i = v - 1, i + 1
+
+ return dmp_strip([ _rec_integrate_in(c, m, w, i, j, K) for c in g ], v)
+
+
+def dmp_integrate_in(f, m, j, u, K):
+ """
+ Computes the indefinite integral of ``f`` in ``x_j`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x,y = ring("x,y", QQ)
+
+ >>> R.dmp_integrate_in(x + 2*y, 1, 0)
+ 1/2*x**2 + 2*x*y
+ >>> R.dmp_integrate_in(x + 2*y, 1, 1)
+ x*y + y**2
+
+ """
+ if j < 0 or j > u:
+ raise IndexError("0 <= j <= u expected, got u = %d, j = %d" % (u, j))
+
+ return _rec_integrate_in(f, m, u, 0, j, K)
+
+
+def dup_diff(f, m, K):
+ """
+ ``m``-th order derivative of a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_diff(x**3 + 2*x**2 + 3*x + 4, 1)
+ 3*x**2 + 4*x + 3
+ >>> R.dup_diff(x**3 + 2*x**2 + 3*x + 4, 2)
+ 6*x + 4
+
+ """
+ if m <= 0:
+ return f
+
+ n = dup_degree(f)
+
+ if n < m:
+ return []
+
+ deriv = []
+
+ if m == 1:
+ for coeff in f[:-m]:
+ deriv.append(K(n)*coeff)
+ n -= 1
+ else:
+ for coeff in f[:-m]:
+ k = n
+
+ for i in range(n - 1, n - m, -1):
+ k *= i
+
+ deriv.append(K(k)*coeff)
+ n -= 1
+
+ return dup_strip(deriv)
+
+
+def dmp_diff(f, m, u, K):
+ """
+ ``m``-th order derivative in ``x_0`` of a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x*y**2 + 2*x*y + 3*x + 2*y**2 + 3*y + 1
+
+ >>> R.dmp_diff(f, 1)
+ y**2 + 2*y + 3
+ >>> R.dmp_diff(f, 2)
+ 0
+
+ """
+ if not u:
+ return dup_diff(f, m, K)
+ if m <= 0:
+ return f
+
+ n = dmp_degree(f, u)
+
+ if n < m:
+ return dmp_zero(u)
+
+ deriv, v = [], u - 1
+
+ if m == 1:
+ for coeff in f[:-m]:
+ deriv.append(dmp_mul_ground(coeff, K(n), v, K))
+ n -= 1
+ else:
+ for coeff in f[:-m]:
+ k = n
+
+ for i in range(n - 1, n - m, -1):
+ k *= i
+
+ deriv.append(dmp_mul_ground(coeff, K(k), v, K))
+ n -= 1
+
+ return dmp_strip(deriv, u)
+
+
+def _rec_diff_in(g, m, v, i, j, K):
+ """Recursive helper for :func:`dmp_diff_in`."""
+ if i == j:
+ return dmp_diff(g, m, v, K)
+
+ w, i = v - 1, i + 1
+
+ return dmp_strip([ _rec_diff_in(c, m, w, i, j, K) for c in g ], v)
+
+
+def dmp_diff_in(f, m, j, u, K):
+ """
+ ``m``-th order derivative in ``x_j`` of a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x*y**2 + 2*x*y + 3*x + 2*y**2 + 3*y + 1
+
+ >>> R.dmp_diff_in(f, 1, 0)
+ y**2 + 2*y + 3
+ >>> R.dmp_diff_in(f, 1, 1)
+ 2*x*y + 2*x + 4*y + 3
+
+ """
+ if j < 0 or j > u:
+ raise IndexError("0 <= j <= %s expected, got %s" % (u, j))
+
+ return _rec_diff_in(f, m, u, 0, j, K)
+
+
+def dup_eval(f, a, K):
+ """
+ Evaluate a polynomial at ``x = a`` in ``K[x]`` using Horner scheme.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_eval(x**2 + 2*x + 3, 2)
+ 11
+
+ """
+ if not a:
+ return K.convert(dup_TC(f, K))
+
+ result = K.zero
+
+ for c in f:
+ result *= a
+ result += c
+
+ return result
+
+
+def dmp_eval(f, a, u, K):
+ """
+ Evaluate a polynomial at ``x_0 = a`` in ``K[X]`` using the Horner scheme.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_eval(2*x*y + 3*x + y + 2, 2)
+ 5*y + 8
+
+ """
+ if not u:
+ return dup_eval(f, a, K)
+
+ if not a:
+ return dmp_TC(f, K)
+
+ result, v = dmp_LC(f, K), u - 1
+
+ for coeff in f[1:]:
+ result = dmp_mul_ground(result, a, v, K)
+ result = dmp_add(result, coeff, v, K)
+
+ return result
+
+
+def _rec_eval_in(g, a, v, i, j, K):
+ """Recursive helper for :func:`dmp_eval_in`."""
+ if i == j:
+ return dmp_eval(g, a, v, K)
+
+ v, i = v - 1, i + 1
+
+ return dmp_strip([ _rec_eval_in(c, a, v, i, j, K) for c in g ], v)
+
+
+def dmp_eval_in(f, a, j, u, K):
+ """
+ Evaluate a polynomial at ``x_j = a`` in ``K[X]`` using the Horner scheme.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = 2*x*y + 3*x + y + 2
+
+ >>> R.dmp_eval_in(f, 2, 0)
+ 5*y + 8
+ >>> R.dmp_eval_in(f, 2, 1)
+ 7*x + 4
+
+ """
+ if j < 0 or j > u:
+ raise IndexError("0 <= j <= %s expected, got %s" % (u, j))
+
+ return _rec_eval_in(f, a, u, 0, j, K)
+
+
+def _rec_eval_tail(g, i, A, u, K):
+ """Recursive helper for :func:`dmp_eval_tail`."""
+ if i == u:
+ return dup_eval(g, A[-1], K)
+ else:
+ h = [ _rec_eval_tail(c, i + 1, A, u, K) for c in g ]
+
+ if i < u - len(A) + 1:
+ return h
+ else:
+ return dup_eval(h, A[-u + i - 1], K)
+
+
+def dmp_eval_tail(f, A, u, K):
+ """
+ Evaluate a polynomial at ``x_j = a_j, ...`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = 2*x*y + 3*x + y + 2
+
+ >>> R.dmp_eval_tail(f, [2])
+ 7*x + 4
+ >>> R.dmp_eval_tail(f, [2, 2])
+ 18
+
+ """
+ if not A:
+ return f
+
+ if dmp_zero_p(f, u):
+ return dmp_zero(u - len(A))
+
+ e = _rec_eval_tail(f, 0, A, u, K)
+
+ if u == len(A) - 1:
+ return e
+ else:
+ return dmp_strip(e, u - len(A))
+
+
+def _rec_diff_eval(g, m, a, v, i, j, K):
+ """Recursive helper for :func:`dmp_diff_eval`."""
+ if i == j:
+ return dmp_eval(dmp_diff(g, m, v, K), a, v, K)
+
+ v, i = v - 1, i + 1
+
+ return dmp_strip([ _rec_diff_eval(c, m, a, v, i, j, K) for c in g ], v)
+
+
+def dmp_diff_eval_in(f, m, a, j, u, K):
+ """
+ Differentiate and evaluate a polynomial in ``x_j`` at ``a`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x*y**2 + 2*x*y + 3*x + 2*y**2 + 3*y + 1
+
+ >>> R.dmp_diff_eval_in(f, 1, 2, 0)
+ y**2 + 2*y + 3
+ >>> R.dmp_diff_eval_in(f, 1, 2, 1)
+ 6*x + 11
+
+ """
+ if j > u:
+ raise IndexError("-%s <= j < %s expected, got %s" % (u, u, j))
+ if not j:
+ return dmp_eval(dmp_diff(f, m, u, K), a, u, K)
+
+ return _rec_diff_eval(f, m, a, u, 0, j, K)
+
+
+def dup_trunc(f, p, K):
+ """
+ Reduce a ``K[x]`` polynomial modulo a constant ``p`` in ``K``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_trunc(2*x**3 + 3*x**2 + 5*x + 7, ZZ(3))
+ -x**3 - x + 1
+
+ """
+ if K.is_ZZ:
+ g = []
+
+ for c in f:
+ c = c % p
+
+ if c > p // 2:
+ g.append(c - p)
+ else:
+ g.append(c)
+ elif K.is_FiniteField:
+ # XXX: python-flint's nmod does not support %
+ pi = int(p)
+ g = [ K(int(c) % pi) for c in f ]
+ else:
+ g = [ c % p for c in f ]
+
+ return dup_strip(g)
+
+
+def dmp_trunc(f, p, u, K):
+ """
+ Reduce a ``K[X]`` polynomial modulo a polynomial ``p`` in ``K[Y]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = 3*x**2*y + 8*x**2 + 5*x*y + 6*x + 2*y + 3
+ >>> g = (y - 1).drop(x)
+
+ >>> R.dmp_trunc(f, g)
+ 11*x**2 + 11*x + 5
+
+ """
+ return dmp_strip([ dmp_rem(c, p, u - 1, K) for c in f ], u)
+
+
+def dmp_ground_trunc(f, p, u, K):
+ """
+ Reduce a ``K[X]`` polynomial modulo a constant ``p`` in ``K``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = 3*x**2*y + 8*x**2 + 5*x*y + 6*x + 2*y + 3
+
+ >>> R.dmp_ground_trunc(f, ZZ(3))
+ -x**2 - x*y - y
+
+ """
+ if not u:
+ return dup_trunc(f, p, K)
+
+ v = u - 1
+
+ return dmp_strip([ dmp_ground_trunc(c, p, v, K) for c in f ], u)
+
+
+def dup_monic(f, K):
+ """
+ Divide all coefficients by ``LC(f)`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x = ring("x", ZZ)
+ >>> R.dup_monic(3*x**2 + 6*x + 9)
+ x**2 + 2*x + 3
+
+ >>> R, x = ring("x", QQ)
+ >>> R.dup_monic(3*x**2 + 4*x + 2)
+ x**2 + 4/3*x + 2/3
+
+ """
+ if not f:
+ return f
+
+ lc = dup_LC(f, K)
+
+ if K.is_one(lc):
+ return f
+ else:
+ return dup_exquo_ground(f, lc, K)
+
+
+def dmp_ground_monic(f, u, K):
+ """
+ Divide all coefficients by ``LC(f)`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x,y = ring("x,y", ZZ)
+ >>> f = 3*x**2*y + 6*x**2 + 3*x*y + 9*y + 3
+
+ >>> R.dmp_ground_monic(f)
+ x**2*y + 2*x**2 + x*y + 3*y + 1
+
+ >>> R, x,y = ring("x,y", QQ)
+ >>> f = 3*x**2*y + 8*x**2 + 5*x*y + 6*x + 2*y + 3
+
+ >>> R.dmp_ground_monic(f)
+ x**2*y + 8/3*x**2 + 5/3*x*y + 2*x + 2/3*y + 1
+
+ """
+ if not u:
+ return dup_monic(f, K)
+
+ if dmp_zero_p(f, u):
+ return f
+
+ lc = dmp_ground_LC(f, u, K)
+
+ if K.is_one(lc):
+ return f
+ else:
+ return dmp_exquo_ground(f, lc, u, K)
+
+
+def dup_content(f, K):
+ """
+ Compute the GCD of coefficients of ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x = ring("x", ZZ)
+ >>> f = 6*x**2 + 8*x + 12
+
+ >>> R.dup_content(f)
+ 2
+
+ >>> R, x = ring("x", QQ)
+ >>> f = 6*x**2 + 8*x + 12
+
+ >>> R.dup_content(f)
+ 2
+
+ """
+ from sympy.polys.domains import QQ
+
+ if not f:
+ return K.zero
+
+ cont = K.zero
+
+ if K == QQ:
+ for c in f:
+ cont = K.gcd(cont, c)
+ else:
+ for c in f:
+ cont = K.gcd(cont, c)
+
+ if K.is_one(cont):
+ break
+
+ return cont
+
+
+def dmp_ground_content(f, u, K):
+ """
+ Compute the GCD of coefficients of ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x,y = ring("x,y", ZZ)
+ >>> f = 2*x*y + 6*x + 4*y + 12
+
+ >>> R.dmp_ground_content(f)
+ 2
+
+ >>> R, x,y = ring("x,y", QQ)
+ >>> f = 2*x*y + 6*x + 4*y + 12
+
+ >>> R.dmp_ground_content(f)
+ 2
+
+ """
+ from sympy.polys.domains import QQ
+
+ if not u:
+ return dup_content(f, K)
+
+ if dmp_zero_p(f, u):
+ return K.zero
+
+ cont, v = K.zero, u - 1
+
+ if K == QQ:
+ for c in f:
+ cont = K.gcd(cont, dmp_ground_content(c, v, K))
+ else:
+ for c in f:
+ cont = K.gcd(cont, dmp_ground_content(c, v, K))
+
+ if K.is_one(cont):
+ break
+
+ return cont
+
+
+def dup_primitive(f, K):
+ """
+ Compute content and the primitive form of ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x = ring("x", ZZ)
+ >>> f = 6*x**2 + 8*x + 12
+
+ >>> R.dup_primitive(f)
+ (2, 3*x**2 + 4*x + 6)
+
+ >>> R, x = ring("x", QQ)
+ >>> f = 6*x**2 + 8*x + 12
+
+ >>> R.dup_primitive(f)
+ (2, 3*x**2 + 4*x + 6)
+
+ """
+ if not f:
+ return K.zero, f
+
+ cont = dup_content(f, K)
+
+ if K.is_one(cont):
+ return cont, f
+ else:
+ return cont, dup_quo_ground(f, cont, K)
+
+
+def dmp_ground_primitive(f, u, K):
+ """
+ Compute content and the primitive form of ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ, QQ
+
+ >>> R, x,y = ring("x,y", ZZ)
+ >>> f = 2*x*y + 6*x + 4*y + 12
+
+ >>> R.dmp_ground_primitive(f)
+ (2, x*y + 3*x + 2*y + 6)
+
+ >>> R, x,y = ring("x,y", QQ)
+ >>> f = 2*x*y + 6*x + 4*y + 12
+
+ >>> R.dmp_ground_primitive(f)
+ (2, x*y + 3*x + 2*y + 6)
+
+ """
+ if not u:
+ return dup_primitive(f, K)
+
+ if dmp_zero_p(f, u):
+ return K.zero, f
+
+ cont = dmp_ground_content(f, u, K)
+
+ if K.is_one(cont):
+ return cont, f
+ else:
+ return cont, dmp_quo_ground(f, cont, u, K)
+
+
+def dup_extract(f, g, K):
+ """
+ Extract common content from a pair of polynomials in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_extract(6*x**2 + 12*x + 18, 4*x**2 + 8*x + 12)
+ (2, 3*x**2 + 6*x + 9, 2*x**2 + 4*x + 6)
+
+ """
+ fc = dup_content(f, K)
+ gc = dup_content(g, K)
+
+ gcd = K.gcd(fc, gc)
+
+ if not K.is_one(gcd):
+ f = dup_quo_ground(f, gcd, K)
+ g = dup_quo_ground(g, gcd, K)
+
+ return gcd, f, g
+
+
+def dmp_ground_extract(f, g, u, K):
+ """
+ Extract common content from a pair of polynomials in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_ground_extract(6*x*y + 12*x + 18, 4*x*y + 8*x + 12)
+ (2, 3*x*y + 6*x + 9, 2*x*y + 4*x + 6)
+
+ """
+ fc = dmp_ground_content(f, u, K)
+ gc = dmp_ground_content(g, u, K)
+
+ gcd = K.gcd(fc, gc)
+
+ if not K.is_one(gcd):
+ f = dmp_quo_ground(f, gcd, u, K)
+ g = dmp_quo_ground(g, gcd, u, K)
+
+ return gcd, f, g
+
+
+def dup_real_imag(f, K):
+ """
+ Find ``f1`` and ``f2``, such that ``f(x+I*y) = f1(x,y) + f2(x,y)*I``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dup_real_imag(x**3 + x**2 + x + 1)
+ (x**3 + x**2 - 3*x*y**2 + x - y**2 + 1, 3*x**2*y + 2*x*y - y**3 + y)
+
+ >>> from sympy.abc import x, y, z
+ >>> from sympy import I
+ >>> (z**3 + z**2 + z + 1).subs(z, x+I*y).expand().collect(I)
+ x**3 + x**2 - 3*x*y**2 + x - y**2 + I*(3*x**2*y + 2*x*y - y**3 + y) + 1
+
+ """
+ if not K.is_ZZ and not K.is_QQ:
+ raise DomainError("computing real and imaginary parts is not supported over %s" % K)
+
+ f1 = dmp_zero(1)
+ f2 = dmp_zero(1)
+
+ if not f:
+ return f1, f2
+
+ g = [[[K.one, K.zero]], [[K.one], []]]
+ h = dmp_ground(f[0], 2)
+
+ for c in f[1:]:
+ h = dmp_mul(h, g, 2, K)
+ h = dmp_add_term(h, dmp_ground(c, 1), 0, 2, K)
+
+ H = dup_to_raw_dict(h)
+
+ for k, h in H.items():
+ m = k % 4
+
+ if not m:
+ f1 = dmp_add(f1, h, 1, K)
+ elif m == 1:
+ f2 = dmp_add(f2, h, 1, K)
+ elif m == 2:
+ f1 = dmp_sub(f1, h, 1, K)
+ else:
+ f2 = dmp_sub(f2, h, 1, K)
+
+ return f1, f2
+
+
+def dup_mirror(f, K):
+ """
+ Evaluate efficiently the composition ``f(-x)`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_mirror(x**3 + 2*x**2 - 4*x + 2)
+ -x**3 + 2*x**2 + 4*x + 2
+
+ """
+ f = list(f)
+
+ for i in range(len(f) - 2, -1, -2):
+ f[i] = -f[i]
+
+ return f
+
+
+def dup_scale(f, a, K):
+ """
+ Evaluate efficiently composition ``f(a*x)`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_scale(x**2 - 2*x + 1, ZZ(2))
+ 4*x**2 - 4*x + 1
+
+ """
+ f, n, b = list(f), len(f) - 1, a
+
+ for i in range(n - 1, -1, -1):
+ f[i], b = b*f[i], b*a
+
+ return f
+
+
+def dup_shift(f, a, K):
+ """
+ Evaluate efficiently Taylor shift ``f(x + a)`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_shift(x**2 - 2*x + 1, ZZ(2))
+ x**2 + 2*x + 1
+
+ """
+ f, n = list(f), len(f) - 1
+
+ for i in range(n, 0, -1):
+ for j in range(0, i):
+ f[j + 1] += a*f[j]
+
+ return f
+
+
+def dmp_shift(f, a, u, K):
+ """
+ Evaluate efficiently Taylor shift ``f(X + A)`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy import symbols, ring, ZZ
+ >>> x, y = symbols('x y')
+ >>> R, _, _ = ring([x, y], ZZ)
+
+ >>> p = x**2*y + 2*x*y + 3*x + 4*y + 5
+
+ >>> R.dmp_shift(R(p), [ZZ(1), ZZ(2)])
+ x**2*y + 2*x**2 + 4*x*y + 11*x + 7*y + 22
+
+ >>> p.subs({x: x + 1, y: y + 2}).expand()
+ x**2*y + 2*x**2 + 4*x*y + 11*x + 7*y + 22
+ """
+ if not u:
+ return dup_shift(f, a[0], K)
+
+ if dmp_zero_p(f, u):
+ return f
+
+ a0, a1 = a[0], a[1:]
+
+ f = [ dmp_shift(c, a1, u-1, K) for c in f ]
+ n = len(f) - 1
+
+ for i in range(n, 0, -1):
+ for j in range(0, i):
+ afj = dmp_mul_ground(f[j], a0, u-1, K)
+ f[j + 1] = dmp_add(f[j + 1], afj, u-1, K)
+
+ return f
+
+
+def dup_transform(f, p, q, K):
+ """
+ Evaluate functional transformation ``q**n * f(p/q)`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_transform(x**2 - 2*x + 1, x**2 + 1, x - 1)
+ x**4 - 2*x**3 + 5*x**2 - 4*x + 4
+
+ """
+ if not f:
+ return []
+
+ n = len(f) - 1
+ h, Q = [f[0]], [[K.one]]
+
+ for i in range(0, n):
+ Q.append(dup_mul(Q[-1], q, K))
+
+ for c, q in zip(f[1:], Q[1:]):
+ h = dup_mul(h, p, K)
+ q = dup_mul_ground(q, c, K)
+ h = dup_add(h, q, K)
+
+ return h
+
+
+def dup_compose(f, g, K):
+ """
+ Evaluate functional composition ``f(g)`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_compose(x**2 + x, x - 1)
+ x**2 - x
+
+ """
+ if len(g) <= 1:
+ return dup_strip([dup_eval(f, dup_LC(g, K), K)])
+
+ if not f:
+ return []
+
+ h = [f[0]]
+
+ for c in f[1:]:
+ h = dup_mul(h, g, K)
+ h = dup_add_term(h, c, 0, K)
+
+ return h
+
+
+def dmp_compose(f, g, u, K):
+ """
+ Evaluate functional composition ``f(g)`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_compose(x*y + 2*x + y, y)
+ y**2 + 3*y
+
+ """
+ if not u:
+ return dup_compose(f, g, K)
+
+ if dmp_zero_p(f, u):
+ return f
+
+ h = [f[0]]
+
+ for c in f[1:]:
+ h = dmp_mul(h, g, u, K)
+ h = dmp_add_term(h, c, 0, u, K)
+
+ return h
+
+
+def _dup_right_decompose(f, s, K):
+ """Helper function for :func:`_dup_decompose`."""
+ n = len(f) - 1
+ lc = dup_LC(f, K)
+
+ f = dup_to_raw_dict(f)
+ g = { s: K.one }
+
+ r = n // s
+
+ for i in range(1, s):
+ coeff = K.zero
+
+ for j in range(0, i):
+ if not n + j - i in f:
+ continue
+
+ if not s - j in g:
+ continue
+
+ fc, gc = f[n + j - i], g[s - j]
+ coeff += (i - r*j)*fc*gc
+
+ g[s - i] = K.quo(coeff, i*r*lc)
+
+ return dup_from_raw_dict(g, K)
+
+
+def _dup_left_decompose(f, h, K):
+ """Helper function for :func:`_dup_decompose`."""
+ g, i = {}, 0
+
+ while f:
+ q, r = dup_div(f, h, K)
+
+ if dup_degree(r) > 0:
+ return None
+ else:
+ g[i] = dup_LC(r, K)
+ f, i = q, i + 1
+
+ return dup_from_raw_dict(g, K)
+
+
+def _dup_decompose(f, K):
+ """Helper function for :func:`dup_decompose`."""
+ df = len(f) - 1
+
+ for s in range(2, df):
+ if df % s != 0:
+ continue
+
+ h = _dup_right_decompose(f, s, K)
+
+ if h is not None:
+ g = _dup_left_decompose(f, h, K)
+
+ if g is not None:
+ return g, h
+
+ return None
+
+
+def dup_decompose(f, K):
+ """
+ Computes functional decomposition of ``f`` in ``K[x]``.
+
+ Given a univariate polynomial ``f`` with coefficients in a field of
+ characteristic zero, returns list ``[f_1, f_2, ..., f_n]``, where::
+
+ f = f_1 o f_2 o ... f_n = f_1(f_2(... f_n))
+
+ and ``f_2, ..., f_n`` are monic and homogeneous polynomials of at
+ least second degree.
+
+ Unlike factorization, complete functional decompositions of
+ polynomials are not unique, consider examples:
+
+ 1. ``f o g = f(x + b) o (g - b)``
+ 2. ``x**n o x**m = x**m o x**n``
+ 3. ``T_n o T_m = T_m o T_n``
+
+ where ``T_n`` and ``T_m`` are Chebyshev polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_decompose(x**4 - 2*x**3 + x**2)
+ [x**2, x**2 - x]
+
+ References
+ ==========
+
+ .. [1] [Kozen89]_
+
+ """
+ F = []
+
+ while True:
+ result = _dup_decompose(f, K)
+
+ if result is not None:
+ f, h = result
+ F = [h] + F
+ else:
+ break
+
+ return [f] + F
+
+
+def dmp_lift(f, u, K):
+ """
+ Convert algebraic coefficients to integers in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> from sympy import I
+
+ >>> K = QQ.algebraic_field(I)
+ >>> R, x = ring("x", K)
+
+ >>> f = x**2 + K([QQ(1), QQ(0)])*x + K([QQ(2), QQ(0)])
+
+ >>> R.dmp_lift(f)
+ x**8 + 2*x**6 + 9*x**4 - 8*x**2 + 16
+
+ """
+ if K.is_GaussianField:
+ K1 = K.as_AlgebraicField()
+ f = dmp_convert(f, u, K, K1)
+ K = K1
+
+ if not K.is_Algebraic:
+ raise DomainError(
+ 'computation can be done only in an algebraic domain')
+
+ F, monoms, polys = dmp_to_dict(f, u), [], []
+
+ for monom, coeff in F.items():
+ if not coeff.is_ground:
+ monoms.append(monom)
+
+ perms = variations([-1, 1], len(monoms), repetition=True)
+
+ for perm in perms:
+ G = dict(F)
+
+ for sign, monom in zip(perm, monoms):
+ if sign == -1:
+ G[monom] = -G[monom]
+
+ polys.append(dmp_from_dict(G, u, K))
+
+ return dmp_convert(dmp_expand(polys, u, K), u, K, K.dom)
+
+
+def dup_sign_variations(f, K):
+ """
+ Compute the number of sign variations of ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_sign_variations(x**4 - x**2 - x + 1)
+ 2
+
+ """
+ prev, k = K.zero, 0
+
+ for coeff in f:
+ if K.is_negative(coeff*prev):
+ k += 1
+
+ if coeff:
+ prev = coeff
+
+ return k
+
+
+def dup_clear_denoms(f, K0, K1=None, convert=False):
+ """
+ Clear denominators, i.e. transform ``K_0`` to ``K_1``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x = ring("x", QQ)
+
+ >>> f = QQ(1,2)*x + QQ(1,3)
+
+ >>> R.dup_clear_denoms(f, convert=False)
+ (6, 3*x + 2)
+ >>> R.dup_clear_denoms(f, convert=True)
+ (6, 3*x + 2)
+
+ """
+ if K1 is None:
+ if K0.has_assoc_Ring:
+ K1 = K0.get_ring()
+ else:
+ K1 = K0
+
+ common = K1.one
+
+ for c in f:
+ common = K1.lcm(common, K0.denom(c))
+
+ if K1.is_one(common):
+ if not convert:
+ return common, f
+ else:
+ return common, dup_convert(f, K0, K1)
+
+ # Use quo rather than exquo to handle inexact domains by discarding the
+ # remainder.
+ f = [K0.numer(c)*K1.quo(common, K0.denom(c)) for c in f]
+
+ if not convert:
+ return common, dup_convert(f, K1, K0)
+ else:
+ return common, f
+
+
+def _rec_clear_denoms(g, v, K0, K1):
+ """Recursive helper for :func:`dmp_clear_denoms`."""
+ common = K1.one
+
+ if not v:
+ for c in g:
+ common = K1.lcm(common, K0.denom(c))
+ else:
+ w = v - 1
+
+ for c in g:
+ common = K1.lcm(common, _rec_clear_denoms(c, w, K0, K1))
+
+ return common
+
+
+def dmp_clear_denoms(f, u, K0, K1=None, convert=False):
+ """
+ Clear denominators, i.e. transform ``K_0`` to ``K_1``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x,y = ring("x,y", QQ)
+
+ >>> f = QQ(1,2)*x + QQ(1,3)*y + 1
+
+ >>> R.dmp_clear_denoms(f, convert=False)
+ (6, 3*x + 2*y + 6)
+ >>> R.dmp_clear_denoms(f, convert=True)
+ (6, 3*x + 2*y + 6)
+
+ """
+ if not u:
+ return dup_clear_denoms(f, K0, K1, convert=convert)
+
+ if K1 is None:
+ if K0.has_assoc_Ring:
+ K1 = K0.get_ring()
+ else:
+ K1 = K0
+
+ common = _rec_clear_denoms(f, u, K0, K1)
+
+ if not K1.is_one(common):
+ f = dmp_mul_ground(f, common, u, K0)
+
+ if not convert:
+ return common, f
+ else:
+ return common, dmp_convert(f, u, K0, K1)
+
+
+def dup_revert(f, n, K):
+ """
+ Compute ``f**(-1)`` mod ``x**n`` using Newton iteration.
+
+ This function computes first ``2**n`` terms of a polynomial that
+ is a result of inversion of a polynomial modulo ``x**n``. This is
+ useful to efficiently compute series expansion of ``1/f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x = ring("x", QQ)
+
+ >>> f = -QQ(1,720)*x**6 + QQ(1,24)*x**4 - QQ(1,2)*x**2 + 1
+
+ >>> R.dup_revert(f, 8)
+ 61/720*x**6 + 5/24*x**4 + 1/2*x**2 + 1
+
+ """
+ g = [K.revert(dup_TC(f, K))]
+ h = [K.one, K.zero, K.zero]
+
+ N = int(_ceil(_log2(n)))
+
+ for i in range(1, N + 1):
+ a = dup_mul_ground(g, K(2), K)
+ b = dup_mul(f, dup_sqr(g, K), K)
+ g = dup_rem(dup_sub(a, b, K), h, K)
+ h = dup_lshift(h, dup_degree(h), K)
+
+ return g
+
+
+def dmp_revert(f, g, u, K):
+ """
+ Compute ``f**(-1)`` mod ``x**n`` using Newton iteration.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x,y = ring("x,y", QQ)
+
+ """
+ if not u:
+ return dup_revert(f, g, K)
+ else:
+ raise MultivariatePolynomialError(f, g)
diff --git a/lib/python3.12/site-packages/sympy/polys/dispersion.py b/lib/python3.12/site-packages/sympy/polys/dispersion.py
new file mode 100644
index 0000000000000000000000000000000000000000..699277d221f24b9bff42c55c3bb34fe5783ae7a1
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/dispersion.py
@@ -0,0 +1,212 @@
+from sympy.core import S
+from sympy.polys import Poly
+
+
+def dispersionset(p, q=None, *gens, **args):
+ r"""Compute the *dispersion set* of two polynomials.
+
+ For two polynomials `f(x)` and `g(x)` with `\deg f > 0`
+ and `\deg g > 0` the dispersion set `\operatorname{J}(f, g)` is defined as:
+
+ .. math::
+ \operatorname{J}(f, g)
+ & := \{a \in \mathbb{N}_0 | \gcd(f(x), g(x+a)) \neq 1\} \\
+ & = \{a \in \mathbb{N}_0 | \deg \gcd(f(x), g(x+a)) \geq 1\}
+
+ For a single polynomial one defines `\operatorname{J}(f) := \operatorname{J}(f, f)`.
+
+ Examples
+ ========
+
+ >>> from sympy import poly
+ >>> from sympy.polys.dispersion import dispersion, dispersionset
+ >>> from sympy.abc import x
+
+ Dispersion set and dispersion of a simple polynomial:
+
+ >>> fp = poly((x - 3)*(x + 3), x)
+ >>> sorted(dispersionset(fp))
+ [0, 6]
+ >>> dispersion(fp)
+ 6
+
+ Note that the definition of the dispersion is not symmetric:
+
+ >>> fp = poly(x**4 - 3*x**2 + 1, x)
+ >>> gp = fp.shift(-3)
+ >>> sorted(dispersionset(fp, gp))
+ [2, 3, 4]
+ >>> dispersion(fp, gp)
+ 4
+ >>> sorted(dispersionset(gp, fp))
+ []
+ >>> dispersion(gp, fp)
+ -oo
+
+ Computing the dispersion also works over field extensions:
+
+ >>> from sympy import sqrt
+ >>> fp = poly(x**2 + sqrt(5)*x - 1, x, domain='QQ')
+ >>> gp = poly(x**2 + (2 + sqrt(5))*x + sqrt(5), x, domain='QQ')
+ >>> sorted(dispersionset(fp, gp))
+ [2]
+ >>> sorted(dispersionset(gp, fp))
+ [1, 4]
+
+ We can even perform the computations for polynomials
+ having symbolic coefficients:
+
+ >>> from sympy.abc import a
+ >>> fp = poly(4*x**4 + (4*a + 8)*x**3 + (a**2 + 6*a + 4)*x**2 + (a**2 + 2*a)*x, x)
+ >>> sorted(dispersionset(fp))
+ [0, 1]
+
+ See Also
+ ========
+
+ dispersion
+
+ References
+ ==========
+
+ .. [1] [ManWright94]_
+ .. [2] [Koepf98]_
+ .. [3] [Abramov71]_
+ .. [4] [Man93]_
+ """
+ # Check for valid input
+ same = False if q is not None else True
+ if same:
+ q = p
+
+ p = Poly(p, *gens, **args)
+ q = Poly(q, *gens, **args)
+
+ if not p.is_univariate or not q.is_univariate:
+ raise ValueError("Polynomials need to be univariate")
+
+ # The generator
+ if not p.gen == q.gen:
+ raise ValueError("Polynomials must have the same generator")
+ gen = p.gen
+
+ # We define the dispersion of constant polynomials to be zero
+ if p.degree() < 1 or q.degree() < 1:
+ return {0}
+
+ # Factor p and q over the rationals
+ fp = p.factor_list()
+ fq = q.factor_list() if not same else fp
+
+ # Iterate over all pairs of factors
+ J = set()
+ for s, unused in fp[1]:
+ for t, unused in fq[1]:
+ m = s.degree()
+ n = t.degree()
+ if n != m:
+ continue
+ an = s.LC()
+ bn = t.LC()
+ if not (an - bn).is_zero:
+ continue
+ # Note that the roles of `s` and `t` below are switched
+ # w.r.t. the original paper. This is for consistency
+ # with the description in the book of W. Koepf.
+ anm1 = s.coeff_monomial(gen**(m-1))
+ bnm1 = t.coeff_monomial(gen**(n-1))
+ alpha = (anm1 - bnm1) / S(n*bn)
+ if not alpha.is_integer:
+ continue
+ if alpha < 0 or alpha in J:
+ continue
+ if n > 1 and not (s - t.shift(alpha)).is_zero:
+ continue
+ J.add(alpha)
+
+ return J
+
+
+def dispersion(p, q=None, *gens, **args):
+ r"""Compute the *dispersion* of polynomials.
+
+ For two polynomials `f(x)` and `g(x)` with `\deg f > 0`
+ and `\deg g > 0` the dispersion `\operatorname{dis}(f, g)` is defined as:
+
+ .. math::
+ \operatorname{dis}(f, g)
+ & := \max\{ J(f,g) \cup \{0\} \} \\
+ & = \max\{ \{a \in \mathbb{N} | \gcd(f(x), g(x+a)) \neq 1\} \cup \{0\} \}
+
+ and for a single polynomial `\operatorname{dis}(f) := \operatorname{dis}(f, f)`.
+ Note that we make the definition `\max\{\} := -\infty`.
+
+ Examples
+ ========
+
+ >>> from sympy import poly
+ >>> from sympy.polys.dispersion import dispersion, dispersionset
+ >>> from sympy.abc import x
+
+ Dispersion set and dispersion of a simple polynomial:
+
+ >>> fp = poly((x - 3)*(x + 3), x)
+ >>> sorted(dispersionset(fp))
+ [0, 6]
+ >>> dispersion(fp)
+ 6
+
+ Note that the definition of the dispersion is not symmetric:
+
+ >>> fp = poly(x**4 - 3*x**2 + 1, x)
+ >>> gp = fp.shift(-3)
+ >>> sorted(dispersionset(fp, gp))
+ [2, 3, 4]
+ >>> dispersion(fp, gp)
+ 4
+ >>> sorted(dispersionset(gp, fp))
+ []
+ >>> dispersion(gp, fp)
+ -oo
+
+ The maximum of an empty set is defined to be `-\infty`
+ as seen in this example.
+
+ Computing the dispersion also works over field extensions:
+
+ >>> from sympy import sqrt
+ >>> fp = poly(x**2 + sqrt(5)*x - 1, x, domain='QQ')
+ >>> gp = poly(x**2 + (2 + sqrt(5))*x + sqrt(5), x, domain='QQ')
+ >>> sorted(dispersionset(fp, gp))
+ [2]
+ >>> sorted(dispersionset(gp, fp))
+ [1, 4]
+
+ We can even perform the computations for polynomials
+ having symbolic coefficients:
+
+ >>> from sympy.abc import a
+ >>> fp = poly(4*x**4 + (4*a + 8)*x**3 + (a**2 + 6*a + 4)*x**2 + (a**2 + 2*a)*x, x)
+ >>> sorted(dispersionset(fp))
+ [0, 1]
+
+ See Also
+ ========
+
+ dispersionset
+
+ References
+ ==========
+
+ .. [1] [ManWright94]_
+ .. [2] [Koepf98]_
+ .. [3] [Abramov71]_
+ .. [4] [Man93]_
+ """
+ J = dispersionset(p, q, *gens, **args)
+ if not J:
+ # Definition for maximum of empty set
+ j = S.NegativeInfinity
+ else:
+ j = max(J)
+ return j
diff --git a/lib/python3.12/site-packages/sympy/polys/distributedmodules.py b/lib/python3.12/site-packages/sympy/polys/distributedmodules.py
new file mode 100644
index 0000000000000000000000000000000000000000..df4581e58951a9c29b9e5b085311f5e6cb00f381
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/distributedmodules.py
@@ -0,0 +1,739 @@
+r"""
+Sparse distributed elements of free modules over multivariate (generalized)
+polynomial rings.
+
+This code and its data structures are very much like the distributed
+polynomials, except that the first "exponent" of the monomial is
+a module generator index. That is, the multi-exponent ``(i, e_1, ..., e_n)``
+represents the "monomial" `x_1^{e_1} \cdots x_n^{e_n} f_i` of the free module
+`F` generated by `f_1, \ldots, f_r` over (a localization of) the ring
+`K[x_1, \ldots, x_n]`. A module element is simply stored as a list of terms
+ordered by the monomial order. Here a term is a pair of a multi-exponent and a
+coefficient. In general, this coefficient should never be zero (since it can
+then be omitted). The zero module element is stored as an empty list.
+
+The main routines are ``sdm_nf_mora`` and ``sdm_groebner`` which can be used
+to compute, respectively, weak normal forms and standard bases. They work with
+arbitrary (not necessarily global) monomial orders.
+
+In general, product orders have to be used to construct valid monomial orders
+for modules. However, ``lex`` can be used as-is.
+
+Note that the "level" (number of variables, i.e. parameter u+1 in
+distributedpolys.py) is never needed in this code.
+
+The main reference for this file is [SCA],
+"A Singular Introduction to Commutative Algebra".
+"""
+
+
+from itertools import permutations
+
+from sympy.polys.monomials import (
+ monomial_mul, monomial_lcm, monomial_div, monomial_deg
+)
+
+from sympy.polys.polytools import Poly
+from sympy.polys.polyutils import parallel_dict_from_expr
+from sympy.core.singleton import S
+from sympy.core.sympify import sympify
+
+# Additional monomial tools.
+
+
+def sdm_monomial_mul(M, X):
+ """
+ Multiply tuple ``X`` representing a monomial of `K[X]` into the tuple
+ ``M`` representing a monomial of `F`.
+
+ Examples
+ ========
+
+ Multiplying `xy^3` into `x f_1` yields `x^2 y^3 f_1`:
+
+ >>> from sympy.polys.distributedmodules import sdm_monomial_mul
+ >>> sdm_monomial_mul((1, 1, 0), (1, 3))
+ (1, 2, 3)
+ """
+ return (M[0],) + monomial_mul(X, M[1:])
+
+
+def sdm_monomial_deg(M):
+ """
+ Return the total degree of ``M``.
+
+ Examples
+ ========
+
+ For example, the total degree of `x^2 y f_5` is 3:
+
+ >>> from sympy.polys.distributedmodules import sdm_monomial_deg
+ >>> sdm_monomial_deg((5, 2, 1))
+ 3
+ """
+ return monomial_deg(M[1:])
+
+
+def sdm_monomial_lcm(A, B):
+ r"""
+ Return the "least common multiple" of ``A`` and ``B``.
+
+ IF `A = M e_j` and `B = N e_j`, where `M` and `N` are polynomial monomials,
+ this returns `\lcm(M, N) e_j`. Note that ``A`` and ``B`` involve distinct
+ monomials.
+
+ Otherwise the result is undefined.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_monomial_lcm
+ >>> sdm_monomial_lcm((1, 2, 3), (1, 0, 5))
+ (1, 2, 5)
+ """
+ return (A[0],) + monomial_lcm(A[1:], B[1:])
+
+
+def sdm_monomial_divides(A, B):
+ """
+ Does there exist a (polynomial) monomial X such that XA = B?
+
+ Examples
+ ========
+
+ Positive examples:
+
+ In the following examples, the monomial is given in terms of x, y and the
+ generator(s), f_1, f_2 etc. The tuple form of that monomial is used in
+ the call to sdm_monomial_divides.
+ Note: the generator appears last in the expression but first in the tuple
+ and other factors appear in the same order that they appear in the monomial
+ expression.
+
+ `A = f_1` divides `B = f_1`
+
+ >>> from sympy.polys.distributedmodules import sdm_monomial_divides
+ >>> sdm_monomial_divides((1, 0, 0), (1, 0, 0))
+ True
+
+ `A = f_1` divides `B = x^2 y f_1`
+
+ >>> sdm_monomial_divides((1, 0, 0), (1, 2, 1))
+ True
+
+ `A = xy f_5` divides `B = x^2 y f_5`
+
+ >>> sdm_monomial_divides((5, 1, 1), (5, 2, 1))
+ True
+
+ Negative examples:
+
+ `A = f_1` does not divide `B = f_2`
+
+ >>> sdm_monomial_divides((1, 0, 0), (2, 0, 0))
+ False
+
+ `A = x f_1` does not divide `B = f_1`
+
+ >>> sdm_monomial_divides((1, 1, 0), (1, 0, 0))
+ False
+
+ `A = xy^2 f_5` does not divide `B = y f_5`
+
+ >>> sdm_monomial_divides((5, 1, 2), (5, 0, 1))
+ False
+ """
+ return A[0] == B[0] and all(a <= b for a, b in zip(A[1:], B[1:]))
+
+
+# The actual distributed modules code.
+
+def sdm_LC(f, K):
+ """Returns the leading coefficient of ``f``. """
+ if not f:
+ return K.zero
+ else:
+ return f[0][1]
+
+
+def sdm_to_dict(f):
+ """Make a dictionary from a distributed polynomial. """
+ return dict(f)
+
+
+def sdm_from_dict(d, O):
+ """
+ Create an sdm from a dictionary.
+
+ Here ``O`` is the monomial order to use.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_from_dict
+ >>> from sympy.polys import QQ, lex
+ >>> dic = {(1, 1, 0): QQ(1), (1, 0, 0): QQ(2), (0, 1, 0): QQ(0)}
+ >>> sdm_from_dict(dic, lex)
+ [((1, 1, 0), 1), ((1, 0, 0), 2)]
+ """
+ return sdm_strip(sdm_sort(list(d.items()), O))
+
+
+def sdm_sort(f, O):
+ """Sort terms in ``f`` using the given monomial order ``O``. """
+ return sorted(f, key=lambda term: O(term[0]), reverse=True)
+
+
+def sdm_strip(f):
+ """Remove terms with zero coefficients from ``f`` in ``K[X]``. """
+ return [ (monom, coeff) for monom, coeff in f if coeff ]
+
+
+def sdm_add(f, g, O, K):
+ """
+ Add two module elements ``f``, ``g``.
+
+ Addition is done over the ground field ``K``, monomials are ordered
+ according to ``O``.
+
+ Examples
+ ========
+
+ All examples use lexicographic order.
+
+ `(xy f_1) + (f_2) = f_2 + xy f_1`
+
+ >>> from sympy.polys.distributedmodules import sdm_add
+ >>> from sympy.polys import lex, QQ
+ >>> sdm_add([((1, 1, 1), QQ(1))], [((2, 0, 0), QQ(1))], lex, QQ)
+ [((2, 0, 0), 1), ((1, 1, 1), 1)]
+
+ `(xy f_1) + (-xy f_1)` = 0`
+
+ >>> sdm_add([((1, 1, 1), QQ(1))], [((1, 1, 1), QQ(-1))], lex, QQ)
+ []
+
+ `(f_1) + (2f_1) = 3f_1`
+
+ >>> sdm_add([((1, 0, 0), QQ(1))], [((1, 0, 0), QQ(2))], lex, QQ)
+ [((1, 0, 0), 3)]
+
+ `(yf_1) + (xf_1) = xf_1 + yf_1`
+
+ >>> sdm_add([((1, 0, 1), QQ(1))], [((1, 1, 0), QQ(1))], lex, QQ)
+ [((1, 1, 0), 1), ((1, 0, 1), 1)]
+ """
+ h = dict(f)
+
+ for monom, c in g:
+ if monom in h:
+ coeff = h[monom] + c
+
+ if not coeff:
+ del h[monom]
+ else:
+ h[monom] = coeff
+ else:
+ h[monom] = c
+
+ return sdm_from_dict(h, O)
+
+
+def sdm_LM(f):
+ r"""
+ Returns the leading monomial of ``f``.
+
+ Only valid if `f \ne 0`.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_LM, sdm_from_dict
+ >>> from sympy.polys import QQ, lex
+ >>> dic = {(1, 2, 3): QQ(1), (4, 0, 0): QQ(1), (4, 0, 1): QQ(1)}
+ >>> sdm_LM(sdm_from_dict(dic, lex))
+ (4, 0, 1)
+ """
+ return f[0][0]
+
+
+def sdm_LT(f):
+ r"""
+ Returns the leading term of ``f``.
+
+ Only valid if `f \ne 0`.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_LT, sdm_from_dict
+ >>> from sympy.polys import QQ, lex
+ >>> dic = {(1, 2, 3): QQ(1), (4, 0, 0): QQ(2), (4, 0, 1): QQ(3)}
+ >>> sdm_LT(sdm_from_dict(dic, lex))
+ ((4, 0, 1), 3)
+ """
+ return f[0]
+
+
+def sdm_mul_term(f, term, O, K):
+ """
+ Multiply a distributed module element ``f`` by a (polynomial) term ``term``.
+
+ Multiplication of coefficients is done over the ground field ``K``, and
+ monomials are ordered according to ``O``.
+
+ Examples
+ ========
+
+ `0 f_1 = 0`
+
+ >>> from sympy.polys.distributedmodules import sdm_mul_term
+ >>> from sympy.polys import lex, QQ
+ >>> sdm_mul_term([((1, 0, 0), QQ(1))], ((0, 0), QQ(0)), lex, QQ)
+ []
+
+ `x 0 = 0`
+
+ >>> sdm_mul_term([], ((1, 0), QQ(1)), lex, QQ)
+ []
+
+ `(x) (f_1) = xf_1`
+
+ >>> sdm_mul_term([((1, 0, 0), QQ(1))], ((1, 0), QQ(1)), lex, QQ)
+ [((1, 1, 0), 1)]
+
+ `(2xy) (3x f_1 + 4y f_2) = 8xy^2 f_2 + 6x^2y f_1`
+
+ >>> f = [((2, 0, 1), QQ(4)), ((1, 1, 0), QQ(3))]
+ >>> sdm_mul_term(f, ((1, 1), QQ(2)), lex, QQ)
+ [((2, 1, 2), 8), ((1, 2, 1), 6)]
+ """
+ X, c = term
+
+ if not f or not c:
+ return []
+ else:
+ if K.is_one(c):
+ return [ (sdm_monomial_mul(f_M, X), f_c) for f_M, f_c in f ]
+ else:
+ return [ (sdm_monomial_mul(f_M, X), f_c * c) for f_M, f_c in f ]
+
+
+def sdm_zero():
+ """Return the zero module element."""
+ return []
+
+
+def sdm_deg(f):
+ """
+ Degree of ``f``.
+
+ This is the maximum of the degrees of all its monomials.
+ Invalid if ``f`` is zero.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_deg
+ >>> sdm_deg([((1, 2, 3), 1), ((10, 0, 1), 1), ((2, 3, 4), 4)])
+ 7
+ """
+ return max(sdm_monomial_deg(M[0]) for M in f)
+
+
+# Conversion
+
+def sdm_from_vector(vec, O, K, **opts):
+ """
+ Create an sdm from an iterable of expressions.
+
+ Coefficients are created in the ground field ``K``, and terms are ordered
+ according to monomial order ``O``. Named arguments are passed on to the
+ polys conversion code and can be used to specify for example generators.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_from_vector
+ >>> from sympy.abc import x, y, z
+ >>> from sympy.polys import QQ, lex
+ >>> sdm_from_vector([x**2+y**2, 2*z], lex, QQ)
+ [((1, 0, 0, 1), 2), ((0, 2, 0, 0), 1), ((0, 0, 2, 0), 1)]
+ """
+ dics, gens = parallel_dict_from_expr(sympify(vec), **opts)
+ dic = {}
+ for i, d in enumerate(dics):
+ for k, v in d.items():
+ dic[(i,) + k] = K.convert(v)
+ return sdm_from_dict(dic, O)
+
+
+def sdm_to_vector(f, gens, K, n=None):
+ """
+ Convert sdm ``f`` into a list of polynomial expressions.
+
+ The generators for the polynomial ring are specified via ``gens``. The rank
+ of the module is guessed, or passed via ``n``. The ground field is assumed
+ to be ``K``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_to_vector
+ >>> from sympy.abc import x, y, z
+ >>> from sympy.polys import QQ
+ >>> f = [((1, 0, 0, 1), QQ(2)), ((0, 2, 0, 0), QQ(1)), ((0, 0, 2, 0), QQ(1))]
+ >>> sdm_to_vector(f, [x, y, z], QQ)
+ [x**2 + y**2, 2*z]
+ """
+ dic = sdm_to_dict(f)
+ dics = {}
+ for k, v in dic.items():
+ dics.setdefault(k[0], []).append((k[1:], v))
+ n = n or len(dics)
+ res = []
+ for k in range(n):
+ if k in dics:
+ res.append(Poly(dict(dics[k]), gens=gens, domain=K).as_expr())
+ else:
+ res.append(S.Zero)
+ return res
+
+# Algorithms.
+
+
+def sdm_spoly(f, g, O, K, phantom=None):
+ """
+ Compute the generalized s-polynomial of ``f`` and ``g``.
+
+ The ground field is assumed to be ``K``, and monomials ordered according to
+ ``O``.
+
+ This is invalid if either of ``f`` or ``g`` is zero.
+
+ If the leading terms of `f` and `g` involve different basis elements of
+ `F`, their s-poly is defined to be zero. Otherwise it is a certain linear
+ combination of `f` and `g` in which the leading terms cancel.
+ See [SCA, defn 2.3.6] for details.
+
+ If ``phantom`` is not ``None``, it should be a pair of module elements on
+ which to perform the same operation(s) as on ``f`` and ``g``. The in this
+ case both results are returned.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_spoly
+ >>> from sympy.polys import QQ, lex
+ >>> f = [((2, 1, 1), QQ(1)), ((1, 0, 1), QQ(1))]
+ >>> g = [((2, 3, 0), QQ(1))]
+ >>> h = [((1, 2, 3), QQ(1))]
+ >>> sdm_spoly(f, h, lex, QQ)
+ []
+ >>> sdm_spoly(f, g, lex, QQ)
+ [((1, 2, 1), 1)]
+ """
+ if not f or not g:
+ return sdm_zero()
+ LM1 = sdm_LM(f)
+ LM2 = sdm_LM(g)
+ if LM1[0] != LM2[0]:
+ return sdm_zero()
+ LM1 = LM1[1:]
+ LM2 = LM2[1:]
+ lcm = monomial_lcm(LM1, LM2)
+ m1 = monomial_div(lcm, LM1)
+ m2 = monomial_div(lcm, LM2)
+ c = K.quo(-sdm_LC(f, K), sdm_LC(g, K))
+ r1 = sdm_add(sdm_mul_term(f, (m1, K.one), O, K),
+ sdm_mul_term(g, (m2, c), O, K), O, K)
+ if phantom is None:
+ return r1
+ r2 = sdm_add(sdm_mul_term(phantom[0], (m1, K.one), O, K),
+ sdm_mul_term(phantom[1], (m2, c), O, K), O, K)
+ return r1, r2
+
+
+def sdm_ecart(f):
+ """
+ Compute the ecart of ``f``.
+
+ This is defined to be the difference of the total degree of `f` and the
+ total degree of the leading monomial of `f` [SCA, defn 2.3.7].
+
+ Invalid if f is zero.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.distributedmodules import sdm_ecart
+ >>> sdm_ecart([((1, 2, 3), 1), ((1, 0, 1), 1)])
+ 0
+ >>> sdm_ecart([((2, 2, 1), 1), ((1, 5, 1), 1)])
+ 3
+ """
+ return sdm_deg(f) - sdm_monomial_deg(sdm_LM(f))
+
+
+def sdm_nf_mora(f, G, O, K, phantom=None):
+ r"""
+ Compute a weak normal form of ``f`` with respect to ``G`` and order ``O``.
+
+ The ground field is assumed to be ``K``, and monomials ordered according to
+ ``O``.
+
+ Weak normal forms are defined in [SCA, defn 2.3.3]. They are not unique.
+ This function deterministically computes a weak normal form, depending on
+ the order of `G`.
+
+ The most important property of a weak normal form is the following: if
+ `R` is the ring associated with the monomial ordering (if the ordering is
+ global, we just have `R = K[x_1, \ldots, x_n]`, otherwise it is a certain
+ localization thereof), `I` any ideal of `R` and `G` a standard basis for
+ `I`, then for any `f \in R`, we have `f \in I` if and only if
+ `NF(f | G) = 0`.
+
+ This is the generalized Mora algorithm for computing weak normal forms with
+ respect to arbitrary monomial orders [SCA, algorithm 2.3.9].
+
+ If ``phantom`` is not ``None``, it should be a pair of "phantom" arguments
+ on which to perform the same computations as on ``f``, ``G``, both results
+ are then returned.
+ """
+ from itertools import repeat
+ h = f
+ T = list(G)
+ if phantom is not None:
+ # "phantom" variables with suffix p
+ hp = phantom[0]
+ Tp = list(phantom[1])
+ phantom = True
+ else:
+ Tp = repeat([])
+ phantom = False
+ while h:
+ # TODO better data structure!!!
+ Th = [(g, sdm_ecart(g), gp) for g, gp in zip(T, Tp)
+ if sdm_monomial_divides(sdm_LM(g), sdm_LM(h))]
+ if not Th:
+ break
+ g, _, gp = min(Th, key=lambda x: x[1])
+ if sdm_ecart(g) > sdm_ecart(h):
+ T.append(h)
+ if phantom:
+ Tp.append(hp)
+ if phantom:
+ h, hp = sdm_spoly(h, g, O, K, phantom=(hp, gp))
+ else:
+ h = sdm_spoly(h, g, O, K)
+ if phantom:
+ return h, hp
+ return h
+
+
+def sdm_nf_buchberger(f, G, O, K, phantom=None):
+ r"""
+ Compute a weak normal form of ``f`` with respect to ``G`` and order ``O``.
+
+ The ground field is assumed to be ``K``, and monomials ordered according to
+ ``O``.
+
+ This is the standard Buchberger algorithm for computing weak normal forms with
+ respect to *global* monomial orders [SCA, algorithm 1.6.10].
+
+ If ``phantom`` is not ``None``, it should be a pair of "phantom" arguments
+ on which to perform the same computations as on ``f``, ``G``, both results
+ are then returned.
+ """
+ from itertools import repeat
+ h = f
+ T = list(G)
+ if phantom is not None:
+ # "phantom" variables with suffix p
+ hp = phantom[0]
+ Tp = list(phantom[1])
+ phantom = True
+ else:
+ Tp = repeat([])
+ phantom = False
+ while h:
+ try:
+ g, gp = next((g, gp) for g, gp in zip(T, Tp)
+ if sdm_monomial_divides(sdm_LM(g), sdm_LM(h)))
+ except StopIteration:
+ break
+ if phantom:
+ h, hp = sdm_spoly(h, g, O, K, phantom=(hp, gp))
+ else:
+ h = sdm_spoly(h, g, O, K)
+ if phantom:
+ return h, hp
+ return h
+
+
+def sdm_nf_buchberger_reduced(f, G, O, K):
+ r"""
+ Compute a reduced normal form of ``f`` with respect to ``G`` and order ``O``.
+
+ The ground field is assumed to be ``K``, and monomials ordered according to
+ ``O``.
+
+ In contrast to weak normal forms, reduced normal forms *are* unique, but
+ their computation is more expensive.
+
+ This is the standard Buchberger algorithm for computing reduced normal forms
+ with respect to *global* monomial orders [SCA, algorithm 1.6.11].
+
+ The ``pantom`` option is not supported, so this normal form cannot be used
+ as a normal form for the "extended" groebner algorithm.
+ """
+ h = sdm_zero()
+ g = f
+ while g:
+ g = sdm_nf_buchberger(g, G, O, K)
+ if g:
+ h = sdm_add(h, [sdm_LT(g)], O, K)
+ g = g[1:]
+ return h
+
+
+def sdm_groebner(G, NF, O, K, extended=False):
+ """
+ Compute a minimal standard basis of ``G`` with respect to order ``O``.
+
+ The algorithm uses a normal form ``NF``, for example ``sdm_nf_mora``.
+ The ground field is assumed to be ``K``, and monomials ordered according
+ to ``O``.
+
+ Let `N` denote the submodule generated by elements of `G`. A standard
+ basis for `N` is a subset `S` of `N`, such that `in(S) = in(N)`, where for
+ any subset `X` of `F`, `in(X)` denotes the submodule generated by the
+ initial forms of elements of `X`. [SCA, defn 2.3.2]
+
+ A standard basis is called minimal if no subset of it is a standard basis.
+
+ One may show that standard bases are always generating sets.
+
+ Minimal standard bases are not unique. This algorithm computes a
+ deterministic result, depending on the particular order of `G`.
+
+ If ``extended=True``, also compute the transition matrix from the initial
+ generators to the groebner basis. That is, return a list of coefficient
+ vectors, expressing the elements of the groebner basis in terms of the
+ elements of ``G``.
+
+ This functions implements the "sugar" strategy, see
+
+ Giovini et al: "One sugar cube, please" OR Selection strategies in
+ Buchberger algorithm.
+ """
+
+ # The critical pair set.
+ # A critical pair is stored as (i, j, s, t) where (i, j) defines the pair
+ # (by indexing S), s is the sugar of the pair, and t is the lcm of their
+ # leading monomials.
+ P = []
+
+ # The eventual standard basis.
+ S = []
+ Sugars = []
+
+ def Ssugar(i, j):
+ """Compute the sugar of the S-poly corresponding to (i, j)."""
+ LMi = sdm_LM(S[i])
+ LMj = sdm_LM(S[j])
+ return max(Sugars[i] - sdm_monomial_deg(LMi),
+ Sugars[j] - sdm_monomial_deg(LMj)) \
+ + sdm_monomial_deg(sdm_monomial_lcm(LMi, LMj))
+
+ ourkey = lambda p: (p[2], O(p[3]), p[1])
+
+ def update(f, sugar, P):
+ """Add f with sugar ``sugar`` to S, update P."""
+ if not f:
+ return P
+ k = len(S)
+ S.append(f)
+ Sugars.append(sugar)
+
+ LMf = sdm_LM(f)
+
+ def removethis(pair):
+ i, j, s, t = pair
+ if LMf[0] != t[0]:
+ return False
+ tik = sdm_monomial_lcm(LMf, sdm_LM(S[i]))
+ tjk = sdm_monomial_lcm(LMf, sdm_LM(S[j]))
+ return tik != t and tjk != t and sdm_monomial_divides(tik, t) and \
+ sdm_monomial_divides(tjk, t)
+ # apply the chain criterion
+ P = [p for p in P if not removethis(p)]
+
+ # new-pair set
+ N = [(i, k, Ssugar(i, k), sdm_monomial_lcm(LMf, sdm_LM(S[i])))
+ for i in range(k) if LMf[0] == sdm_LM(S[i])[0]]
+ # TODO apply the product criterion?
+ N.sort(key=ourkey)
+ remove = set()
+ for i, p in enumerate(N):
+ for j in range(i + 1, len(N)):
+ if sdm_monomial_divides(p[3], N[j][3]):
+ remove.add(j)
+
+ # TODO mergesort?
+ P.extend(reversed([p for i, p in enumerate(N) if i not in remove]))
+ P.sort(key=ourkey, reverse=True)
+ # NOTE reverse-sort, because we want to pop from the end
+ return P
+
+ # Figure out the number of generators in the ground ring.
+ try:
+ # NOTE: we look for the first non-zero vector, take its first monomial
+ # the number of generators in the ring is one less than the length
+ # (since the zeroth entry is for the module generators)
+ numgens = len(next(x[0] for x in G if x)[0]) - 1
+ except StopIteration:
+ # No non-zero elements in G ...
+ if extended:
+ return [], []
+ return []
+
+ # This list will store expressions of the elements of S in terms of the
+ # initial generators
+ coefficients = []
+
+ # First add all the elements of G to S
+ for i, f in enumerate(G):
+ P = update(f, sdm_deg(f), P)
+ if extended and f:
+ coefficients.append(sdm_from_dict({(i,) + (0,)*numgens: K(1)}, O))
+
+ # Now carry out the buchberger algorithm.
+ while P:
+ i, j, s, t = P.pop()
+ f, g = S[i], S[j]
+ if extended:
+ sp, coeff = sdm_spoly(f, g, O, K,
+ phantom=(coefficients[i], coefficients[j]))
+ h, hcoeff = NF(sp, S, O, K, phantom=(coeff, coefficients))
+ if h:
+ coefficients.append(hcoeff)
+ else:
+ h = NF(sdm_spoly(f, g, O, K), S, O, K)
+ P = update(h, Ssugar(i, j), P)
+
+ # Finally interreduce the standard basis.
+ # (TODO again, better data structures)
+ S = {(tuple(f), i) for i, f in enumerate(S)}
+ for (a, ai), (b, bi) in permutations(S, 2):
+ A = sdm_LM(a)
+ B = sdm_LM(b)
+ if sdm_monomial_divides(A, B) and (b, bi) in S and (a, ai) in S:
+ S.remove((b, bi))
+
+ L = sorted(((list(f), i) for f, i in S), key=lambda p: O(sdm_LM(p[0])),
+ reverse=True)
+ res = [x[0] for x in L]
+ if extended:
+ return res, [coefficients[i] for _, i in L]
+ return res
diff --git a/lib/python3.12/site-packages/sympy/polys/domainmatrix.py b/lib/python3.12/site-packages/sympy/polys/domainmatrix.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0ccaaa4cb96e0c49da58d8e9128c1b6fa551ade
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/domainmatrix.py
@@ -0,0 +1,12 @@
+"""
+Stub module to expose DomainMatrix which has now moved to
+sympy.polys.matrices package. It should now be imported as:
+
+ >>> from sympy.polys.matrices import DomainMatrix
+
+This module might be removed in future.
+"""
+
+from sympy.polys.matrices.domainmatrix import DomainMatrix
+
+__all__ = ['DomainMatrix']
diff --git a/lib/python3.12/site-packages/sympy/polys/factortools.py b/lib/python3.12/site-packages/sympy/polys/factortools.py
new file mode 100644
index 0000000000000000000000000000000000000000..021a6b06cb8802748deef6c69448ebc50503269b
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/factortools.py
@@ -0,0 +1,1648 @@
+"""Polynomial factorization routines in characteristic zero. """
+
+from sympy.external.gmpy import GROUND_TYPES
+
+from sympy.core.random import _randint
+
+from sympy.polys.galoistools import (
+ gf_from_int_poly, gf_to_int_poly,
+ gf_lshift, gf_add_mul, gf_mul,
+ gf_div, gf_rem,
+ gf_gcdex,
+ gf_sqf_p,
+ gf_factor_sqf, gf_factor)
+
+from sympy.polys.densebasic import (
+ dup_LC, dmp_LC, dmp_ground_LC,
+ dup_TC,
+ dup_convert, dmp_convert,
+ dup_degree, dmp_degree,
+ dmp_degree_in, dmp_degree_list,
+ dmp_from_dict,
+ dmp_zero_p,
+ dmp_one,
+ dmp_nest, dmp_raise,
+ dup_strip,
+ dmp_ground,
+ dup_inflate,
+ dmp_exclude, dmp_include,
+ dmp_inject, dmp_eject,
+ dup_terms_gcd, dmp_terms_gcd)
+
+from sympy.polys.densearith import (
+ dup_neg, dmp_neg,
+ dup_add, dmp_add,
+ dup_sub, dmp_sub,
+ dup_mul, dmp_mul,
+ dup_sqr,
+ dmp_pow,
+ dup_div, dmp_div,
+ dup_quo, dmp_quo,
+ dmp_expand,
+ dmp_add_mul,
+ dup_sub_mul, dmp_sub_mul,
+ dup_lshift,
+ dup_max_norm, dmp_max_norm,
+ dup_l1_norm,
+ dup_mul_ground, dmp_mul_ground,
+ dup_quo_ground, dmp_quo_ground)
+
+from sympy.polys.densetools import (
+ dup_clear_denoms, dmp_clear_denoms,
+ dup_trunc, dmp_ground_trunc,
+ dup_content,
+ dup_monic, dmp_ground_monic,
+ dup_primitive, dmp_ground_primitive,
+ dmp_eval_tail,
+ dmp_eval_in, dmp_diff_eval_in,
+ dup_shift, dmp_shift, dup_mirror)
+
+from sympy.polys.euclidtools import (
+ dmp_primitive,
+ dup_inner_gcd, dmp_inner_gcd)
+
+from sympy.polys.sqfreetools import (
+ dup_sqf_p,
+ dup_sqf_norm, dmp_sqf_norm,
+ dup_sqf_part, dmp_sqf_part,
+ _dup_check_degrees, _dmp_check_degrees,
+ )
+
+from sympy.polys.polyutils import _sort_factors
+from sympy.polys.polyconfig import query
+
+from sympy.polys.polyerrors import (
+ ExtraneousFactors, DomainError, CoercionFailed, EvaluationFailed)
+
+from sympy.utilities import subsets
+
+from math import ceil as _ceil, log as _log, log2 as _log2
+
+
+if GROUND_TYPES == 'flint':
+ from flint import fmpz_poly
+else:
+ fmpz_poly = None
+
+
+def dup_trial_division(f, factors, K):
+ """
+ Determine multiplicities of factors for a univariate polynomial
+ using trial division.
+
+ An error will be raised if any factor does not divide ``f``.
+ """
+ result = []
+
+ for factor in factors:
+ k = 0
+
+ while True:
+ q, r = dup_div(f, factor, K)
+
+ if not r:
+ f, k = q, k + 1
+ else:
+ break
+
+ if k == 0:
+ raise RuntimeError("trial division failed")
+
+ result.append((factor, k))
+
+ return _sort_factors(result)
+
+
+def dmp_trial_division(f, factors, u, K):
+ """
+ Determine multiplicities of factors for a multivariate polynomial
+ using trial division.
+
+ An error will be raised if any factor does not divide ``f``.
+ """
+ result = []
+
+ for factor in factors:
+ k = 0
+
+ while True:
+ q, r = dmp_div(f, factor, u, K)
+
+ if dmp_zero_p(r, u):
+ f, k = q, k + 1
+ else:
+ break
+
+ if k == 0:
+ raise RuntimeError("trial division failed")
+
+ result.append((factor, k))
+
+ return _sort_factors(result)
+
+
+def dup_zz_mignotte_bound(f, K):
+ """
+ The Knuth-Cohen variant of Mignotte bound for
+ univariate polynomials in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> f = x**3 + 14*x**2 + 56*x + 64
+ >>> R.dup_zz_mignotte_bound(f)
+ 152
+
+ By checking ``factor(f)`` we can see that max coeff is 8
+
+ Also consider a case that ``f`` is irreducible for example
+ ``f = 2*x**2 + 3*x + 4``. To avoid a bug for these cases, we return the
+ bound plus the max coefficient of ``f``
+
+ >>> f = 2*x**2 + 3*x + 4
+ >>> R.dup_zz_mignotte_bound(f)
+ 6
+
+ Lastly, to see the difference between the new and the old Mignotte bound
+ consider the irreducible polynomial:
+
+ >>> f = 87*x**7 + 4*x**6 + 80*x**5 + 17*x**4 + 9*x**3 + 12*x**2 + 49*x + 26
+ >>> R.dup_zz_mignotte_bound(f)
+ 744
+
+ The new Mignotte bound is 744 whereas the old one (SymPy 1.5.1) is 1937664.
+
+
+ References
+ ==========
+
+ ..[1] [Abbott13]_
+
+ """
+ from sympy.functions.combinatorial.factorials import binomial
+ d = dup_degree(f)
+ delta = _ceil(d / 2)
+ delta2 = _ceil(delta / 2)
+
+ # euclidean-norm
+ eucl_norm = K.sqrt( sum( cf**2 for cf in f ) )
+
+ # biggest values of binomial coefficients (p. 538 of reference)
+ t1 = binomial(delta - 1, delta2)
+ t2 = binomial(delta - 1, delta2 - 1)
+
+ lc = K.abs(dup_LC(f, K)) # leading coefficient
+ bound = t1 * eucl_norm + t2 * lc # (p. 538 of reference)
+ bound += dup_max_norm(f, K) # add max coeff for irreducible polys
+ bound = _ceil(bound / 2) * 2 # round up to even integer
+
+ return bound
+
+def dmp_zz_mignotte_bound(f, u, K):
+ """Mignotte bound for multivariate polynomials in `K[X]`. """
+ a = dmp_max_norm(f, u, K)
+ b = abs(dmp_ground_LC(f, u, K))
+ n = sum(dmp_degree_list(f, u))
+
+ return K.sqrt(K(n + 1))*2**n*a*b
+
+
+def dup_zz_hensel_step(m, f, g, h, s, t, K):
+ """
+ One step in Hensel lifting in `Z[x]`.
+
+ Given positive integer `m` and `Z[x]` polynomials `f`, `g`, `h`, `s`
+ and `t` such that::
+
+ f = g*h (mod m)
+ s*g + t*h = 1 (mod m)
+
+ lc(f) is not a zero divisor (mod m)
+ lc(h) = 1
+
+ deg(f) = deg(g) + deg(h)
+ deg(s) < deg(h)
+ deg(t) < deg(g)
+
+ returns polynomials `G`, `H`, `S` and `T`, such that::
+
+ f = G*H (mod m**2)
+ S*G + T*H = 1 (mod m**2)
+
+ References
+ ==========
+
+ .. [1] [Gathen99]_
+
+ """
+ M = m**2
+
+ e = dup_sub_mul(f, g, h, K)
+ e = dup_trunc(e, M, K)
+
+ q, r = dup_div(dup_mul(s, e, K), h, K)
+
+ q = dup_trunc(q, M, K)
+ r = dup_trunc(r, M, K)
+
+ u = dup_add(dup_mul(t, e, K), dup_mul(q, g, K), K)
+ G = dup_trunc(dup_add(g, u, K), M, K)
+ H = dup_trunc(dup_add(h, r, K), M, K)
+
+ u = dup_add(dup_mul(s, G, K), dup_mul(t, H, K), K)
+ b = dup_trunc(dup_sub(u, [K.one], K), M, K)
+
+ c, d = dup_div(dup_mul(s, b, K), H, K)
+
+ c = dup_trunc(c, M, K)
+ d = dup_trunc(d, M, K)
+
+ u = dup_add(dup_mul(t, b, K), dup_mul(c, G, K), K)
+ S = dup_trunc(dup_sub(s, d, K), M, K)
+ T = dup_trunc(dup_sub(t, u, K), M, K)
+
+ return G, H, S, T
+
+
+def dup_zz_hensel_lift(p, f, f_list, l, K):
+ r"""
+ Multifactor Hensel lifting in `Z[x]`.
+
+ Given a prime `p`, polynomial `f` over `Z[x]` such that `lc(f)`
+ is a unit modulo `p`, monic pair-wise coprime polynomials `f_i`
+ over `Z[x]` satisfying::
+
+ f = lc(f) f_1 ... f_r (mod p)
+
+ and a positive integer `l`, returns a list of monic polynomials
+ `F_1,\ F_2,\ \dots,\ F_r` satisfying::
+
+ f = lc(f) F_1 ... F_r (mod p**l)
+
+ F_i = f_i (mod p), i = 1..r
+
+ References
+ ==========
+
+ .. [1] [Gathen99]_
+
+ """
+ r = len(f_list)
+ lc = dup_LC(f, K)
+
+ if r == 1:
+ F = dup_mul_ground(f, K.gcdex(lc, p**l)[0], K)
+ return [ dup_trunc(F, p**l, K) ]
+
+ m = p
+ k = r // 2
+ d = int(_ceil(_log2(l)))
+
+ g = gf_from_int_poly([lc], p)
+
+ for f_i in f_list[:k]:
+ g = gf_mul(g, gf_from_int_poly(f_i, p), p, K)
+
+ h = gf_from_int_poly(f_list[k], p)
+
+ for f_i in f_list[k + 1:]:
+ h = gf_mul(h, gf_from_int_poly(f_i, p), p, K)
+
+ s, t, _ = gf_gcdex(g, h, p, K)
+
+ g = gf_to_int_poly(g, p)
+ h = gf_to_int_poly(h, p)
+ s = gf_to_int_poly(s, p)
+ t = gf_to_int_poly(t, p)
+
+ for _ in range(1, d + 1):
+ (g, h, s, t), m = dup_zz_hensel_step(m, f, g, h, s, t, K), m**2
+
+ return dup_zz_hensel_lift(p, g, f_list[:k], l, K) \
+ + dup_zz_hensel_lift(p, h, f_list[k:], l, K)
+
+def _test_pl(fc, q, pl):
+ if q > pl // 2:
+ q = q - pl
+ if not q:
+ return True
+ return fc % q == 0
+
+def dup_zz_zassenhaus(f, K):
+ """Factor primitive square-free polynomials in `Z[x]`. """
+ n = dup_degree(f)
+
+ if n == 1:
+ return [f]
+
+ from sympy.ntheory import isprime
+
+ fc = f[-1]
+ A = dup_max_norm(f, K)
+ b = dup_LC(f, K)
+ B = int(abs(K.sqrt(K(n + 1))*2**n*A*b))
+ C = int((n + 1)**(2*n)*A**(2*n - 1))
+ gamma = int(_ceil(2*_log2(C)))
+ bound = int(2*gamma*_log(gamma))
+ a = []
+ # choose a prime number `p` such that `f` be square free in Z_p
+ # if there are many factors in Z_p, choose among a few different `p`
+ # the one with fewer factors
+ for px in range(3, bound + 1):
+ if not isprime(px) or b % px == 0:
+ continue
+
+ px = K.convert(px)
+
+ F = gf_from_int_poly(f, px)
+
+ if not gf_sqf_p(F, px, K):
+ continue
+ fsqfx = gf_factor_sqf(F, px, K)[1]
+ a.append((px, fsqfx))
+ if len(fsqfx) < 15 or len(a) > 4:
+ break
+ p, fsqf = min(a, key=lambda x: len(x[1]))
+
+ l = int(_ceil(_log(2*B + 1, p)))
+
+ modular = [gf_to_int_poly(ff, p) for ff in fsqf]
+
+ g = dup_zz_hensel_lift(p, f, modular, l, K)
+
+ sorted_T = range(len(g))
+ T = set(sorted_T)
+ factors, s = [], 1
+ pl = p**l
+
+ while 2*s <= len(T):
+ for S in subsets(sorted_T, s):
+ # lift the constant coefficient of the product `G` of the factors
+ # in the subset `S`; if it is does not divide `fc`, `G` does
+ # not divide the input polynomial
+
+ if b == 1:
+ q = 1
+ for i in S:
+ q = q*g[i][-1]
+ q = q % pl
+ if not _test_pl(fc, q, pl):
+ continue
+ else:
+ G = [b]
+ for i in S:
+ G = dup_mul(G, g[i], K)
+ G = dup_trunc(G, pl, K)
+ G = dup_primitive(G, K)[1]
+ q = G[-1]
+ if q and fc % q != 0:
+ continue
+
+ H = [b]
+ S = set(S)
+ T_S = T - S
+
+ if b == 1:
+ G = [b]
+ for i in S:
+ G = dup_mul(G, g[i], K)
+ G = dup_trunc(G, pl, K)
+
+ for i in T_S:
+ H = dup_mul(H, g[i], K)
+
+ H = dup_trunc(H, pl, K)
+
+ G_norm = dup_l1_norm(G, K)
+ H_norm = dup_l1_norm(H, K)
+
+ if G_norm*H_norm <= B:
+ T = T_S
+ sorted_T = [i for i in sorted_T if i not in S]
+
+ G = dup_primitive(G, K)[1]
+ f = dup_primitive(H, K)[1]
+
+ factors.append(G)
+ b = dup_LC(f, K)
+
+ break
+ else:
+ s += 1
+
+ return factors + [f]
+
+
+def dup_zz_irreducible_p(f, K):
+ """Test irreducibility using Eisenstein's criterion. """
+ lc = dup_LC(f, K)
+ tc = dup_TC(f, K)
+
+ e_fc = dup_content(f[1:], K)
+
+ if e_fc:
+ from sympy.ntheory import factorint
+ e_ff = factorint(int(e_fc))
+
+ for p in e_ff.keys():
+ if (lc % p) and (tc % p**2):
+ return True
+
+
+def dup_cyclotomic_p(f, K, irreducible=False):
+ """
+ Efficiently test if ``f`` is a cyclotomic polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> f = x**16 + x**14 - x**10 + x**8 - x**6 + x**2 + 1
+ >>> R.dup_cyclotomic_p(f)
+ False
+
+ >>> g = x**16 + x**14 - x**10 - x**8 - x**6 + x**2 + 1
+ >>> R.dup_cyclotomic_p(g)
+ True
+
+ References
+ ==========
+
+ Bradford, Russell J., and James H. Davenport. "Effective tests for
+ cyclotomic polynomials." In International Symposium on Symbolic and
+ Algebraic Computation, pp. 244-251. Springer, Berlin, Heidelberg, 1988.
+
+ """
+ if K.is_QQ:
+ try:
+ K0, K = K, K.get_ring()
+ f = dup_convert(f, K0, K)
+ except CoercionFailed:
+ return False
+ elif not K.is_ZZ:
+ return False
+
+ lc = dup_LC(f, K)
+ tc = dup_TC(f, K)
+
+ if lc != 1 or (tc != -1 and tc != 1):
+ return False
+
+ if not irreducible:
+ coeff, factors = dup_factor_list(f, K)
+
+ if coeff != K.one or factors != [(f, 1)]:
+ return False
+
+ n = dup_degree(f)
+ g, h = [], []
+
+ for i in range(n, -1, -2):
+ g.insert(0, f[i])
+
+ for i in range(n - 1, -1, -2):
+ h.insert(0, f[i])
+
+ g = dup_sqr(dup_strip(g), K)
+ h = dup_sqr(dup_strip(h), K)
+
+ F = dup_sub(g, dup_lshift(h, 1, K), K)
+
+ if K.is_negative(dup_LC(F, K)):
+ F = dup_neg(F, K)
+
+ if F == f:
+ return True
+
+ g = dup_mirror(f, K)
+
+ if K.is_negative(dup_LC(g, K)):
+ g = dup_neg(g, K)
+
+ if F == g and dup_cyclotomic_p(g, K):
+ return True
+
+ G = dup_sqf_part(F, K)
+
+ if dup_sqr(G, K) == F and dup_cyclotomic_p(G, K):
+ return True
+
+ return False
+
+
+def dup_zz_cyclotomic_poly(n, K):
+ """Efficiently generate n-th cyclotomic polynomial. """
+ from sympy.ntheory import factorint
+ h = [K.one, -K.one]
+
+ for p, k in factorint(n).items():
+ h = dup_quo(dup_inflate(h, p, K), h, K)
+ h = dup_inflate(h, p**(k - 1), K)
+
+ return h
+
+
+def _dup_cyclotomic_decompose(n, K):
+ from sympy.ntheory import factorint
+
+ H = [[K.one, -K.one]]
+
+ for p, k in factorint(n).items():
+ Q = [ dup_quo(dup_inflate(h, p, K), h, K) for h in H ]
+ H.extend(Q)
+
+ for i in range(1, k):
+ Q = [ dup_inflate(q, p, K) for q in Q ]
+ H.extend(Q)
+
+ return H
+
+
+def dup_zz_cyclotomic_factor(f, K):
+ """
+ Efficiently factor polynomials `x**n - 1` and `x**n + 1` in `Z[x]`.
+
+ Given a univariate polynomial `f` in `Z[x]` returns a list of factors
+ of `f`, provided that `f` is in the form `x**n - 1` or `x**n + 1` for
+ `n >= 1`. Otherwise returns None.
+
+ Factorization is performed using cyclotomic decomposition of `f`,
+ which makes this method much faster that any other direct factorization
+ approach (e.g. Zassenhaus's).
+
+ References
+ ==========
+
+ .. [1] [Weisstein09]_
+
+ """
+ lc_f, tc_f = dup_LC(f, K), dup_TC(f, K)
+
+ if dup_degree(f) <= 0:
+ return None
+
+ if lc_f != 1 or tc_f not in [-1, 1]:
+ return None
+
+ if any(bool(cf) for cf in f[1:-1]):
+ return None
+
+ n = dup_degree(f)
+ F = _dup_cyclotomic_decompose(n, K)
+
+ if not K.is_one(tc_f):
+ return F
+ else:
+ H = []
+
+ for h in _dup_cyclotomic_decompose(2*n, K):
+ if h not in F:
+ H.append(h)
+
+ return H
+
+
+def dup_zz_factor_sqf(f, K):
+ """Factor square-free (non-primitive) polynomials in `Z[x]`. """
+ cont, g = dup_primitive(f, K)
+
+ n = dup_degree(g)
+
+ if dup_LC(g, K) < 0:
+ cont, g = -cont, dup_neg(g, K)
+
+ if n <= 0:
+ return cont, []
+ elif n == 1:
+ return cont, [g]
+
+ if query('USE_IRREDUCIBLE_IN_FACTOR'):
+ if dup_zz_irreducible_p(g, K):
+ return cont, [g]
+
+ factors = None
+
+ if query('USE_CYCLOTOMIC_FACTOR'):
+ factors = dup_zz_cyclotomic_factor(g, K)
+
+ if factors is None:
+ factors = dup_zz_zassenhaus(g, K)
+
+ return cont, _sort_factors(factors, multiple=False)
+
+
+def dup_zz_factor(f, K):
+ """
+ Factor (non square-free) polynomials in `Z[x]`.
+
+ Given a univariate polynomial `f` in `Z[x]` computes its complete
+ factorization `f_1, ..., f_n` into irreducibles over integers::
+
+ f = content(f) f_1**k_1 ... f_n**k_n
+
+ The factorization is computed by reducing the input polynomial
+ into a primitive square-free polynomial and factoring it using
+ Zassenhaus algorithm. Trial division is used to recover the
+ multiplicities of factors.
+
+ The result is returned as a tuple consisting of::
+
+ (content(f), [(f_1, k_1), ..., (f_n, k_n))
+
+ Examples
+ ========
+
+ Consider the polynomial `f = 2*x**4 - 2`::
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_zz_factor(2*x**4 - 2)
+ (2, [(x - 1, 1), (x + 1, 1), (x**2 + 1, 1)])
+
+ In result we got the following factorization::
+
+ f = 2 (x - 1) (x + 1) (x**2 + 1)
+
+ Note that this is a complete factorization over integers,
+ however over Gaussian integers we can factor the last term.
+
+ By default, polynomials `x**n - 1` and `x**n + 1` are factored
+ using cyclotomic decomposition to speedup computations. To
+ disable this behaviour set cyclotomic=False.
+
+ References
+ ==========
+
+ .. [1] [Gathen99]_
+
+ """
+ if GROUND_TYPES == 'flint':
+ f_flint = fmpz_poly(f[::-1])
+ cont, factors = f_flint.factor()
+ factors = [(fac.coeffs()[::-1], exp) for fac, exp in factors]
+ return cont, _sort_factors(factors)
+
+ cont, g = dup_primitive(f, K)
+
+ n = dup_degree(g)
+
+ if dup_LC(g, K) < 0:
+ cont, g = -cont, dup_neg(g, K)
+
+ if n <= 0:
+ return cont, []
+ elif n == 1:
+ return cont, [(g, 1)]
+
+ if query('USE_IRREDUCIBLE_IN_FACTOR'):
+ if dup_zz_irreducible_p(g, K):
+ return cont, [(g, 1)]
+
+ g = dup_sqf_part(g, K)
+ H = None
+
+ if query('USE_CYCLOTOMIC_FACTOR'):
+ H = dup_zz_cyclotomic_factor(g, K)
+
+ if H is None:
+ H = dup_zz_zassenhaus(g, K)
+
+ factors = dup_trial_division(f, H, K)
+
+ _dup_check_degrees(f, factors)
+
+ return cont, factors
+
+
+def dmp_zz_wang_non_divisors(E, cs, ct, K):
+ """Wang/EEZ: Compute a set of valid divisors. """
+ result = [ cs*ct ]
+
+ for q in E:
+ q = abs(q)
+
+ for r in reversed(result):
+ while r != 1:
+ r = K.gcd(r, q)
+ q = q // r
+
+ if K.is_one(q):
+ return None
+
+ result.append(q)
+
+ return result[1:]
+
+
+def dmp_zz_wang_test_points(f, T, ct, A, u, K):
+ """Wang/EEZ: Test evaluation points for suitability. """
+ if not dmp_eval_tail(dmp_LC(f, K), A, u - 1, K):
+ raise EvaluationFailed('no luck')
+
+ g = dmp_eval_tail(f, A, u, K)
+
+ if not dup_sqf_p(g, K):
+ raise EvaluationFailed('no luck')
+
+ c, h = dup_primitive(g, K)
+
+ if K.is_negative(dup_LC(h, K)):
+ c, h = -c, dup_neg(h, K)
+
+ v = u - 1
+
+ E = [ dmp_eval_tail(t, A, v, K) for t, _ in T ]
+ D = dmp_zz_wang_non_divisors(E, c, ct, K)
+
+ if D is not None:
+ return c, h, E
+ else:
+ raise EvaluationFailed('no luck')
+
+
+def dmp_zz_wang_lead_coeffs(f, T, cs, E, H, A, u, K):
+ """Wang/EEZ: Compute correct leading coefficients. """
+ C, J, v = [], [0]*len(E), u - 1
+
+ for h in H:
+ c = dmp_one(v, K)
+ d = dup_LC(h, K)*cs
+
+ for i in reversed(range(len(E))):
+ k, e, (t, _) = 0, E[i], T[i]
+
+ while not (d % e):
+ d, k = d//e, k + 1
+
+ if k != 0:
+ c, J[i] = dmp_mul(c, dmp_pow(t, k, v, K), v, K), 1
+
+ C.append(c)
+
+ if not all(J):
+ raise ExtraneousFactors # pragma: no cover
+
+ CC, HH = [], []
+
+ for c, h in zip(C, H):
+ d = dmp_eval_tail(c, A, v, K)
+ lc = dup_LC(h, K)
+
+ if K.is_one(cs):
+ cc = lc//d
+ else:
+ g = K.gcd(lc, d)
+ d, cc = d//g, lc//g
+ h, cs = dup_mul_ground(h, d, K), cs//d
+
+ c = dmp_mul_ground(c, cc, v, K)
+
+ CC.append(c)
+ HH.append(h)
+
+ if K.is_one(cs):
+ return f, HH, CC
+
+ CCC, HHH = [], []
+
+ for c, h in zip(CC, HH):
+ CCC.append(dmp_mul_ground(c, cs, v, K))
+ HHH.append(dmp_mul_ground(h, cs, 0, K))
+
+ f = dmp_mul_ground(f, cs**(len(H) - 1), u, K)
+
+ return f, HHH, CCC
+
+
+def dup_zz_diophantine(F, m, p, K):
+ """Wang/EEZ: Solve univariate Diophantine equations. """
+ if len(F) == 2:
+ a, b = F
+
+ f = gf_from_int_poly(a, p)
+ g = gf_from_int_poly(b, p)
+
+ s, t, G = gf_gcdex(g, f, p, K)
+
+ s = gf_lshift(s, m, K)
+ t = gf_lshift(t, m, K)
+
+ q, s = gf_div(s, f, p, K)
+
+ t = gf_add_mul(t, q, g, p, K)
+
+ s = gf_to_int_poly(s, p)
+ t = gf_to_int_poly(t, p)
+
+ result = [s, t]
+ else:
+ G = [F[-1]]
+
+ for f in reversed(F[1:-1]):
+ G.insert(0, dup_mul(f, G[0], K))
+
+ S, T = [], [[1]]
+
+ for f, g in zip(F, G):
+ t, s = dmp_zz_diophantine([g, f], T[-1], [], 0, p, 1, K)
+ T.append(t)
+ S.append(s)
+
+ result, S = [], S + [T[-1]]
+
+ for s, f in zip(S, F):
+ s = gf_from_int_poly(s, p)
+ f = gf_from_int_poly(f, p)
+
+ r = gf_rem(gf_lshift(s, m, K), f, p, K)
+ s = gf_to_int_poly(r, p)
+
+ result.append(s)
+
+ return result
+
+
+def dmp_zz_diophantine(F, c, A, d, p, u, K):
+ """Wang/EEZ: Solve multivariate Diophantine equations. """
+ if not A:
+ S = [ [] for _ in F ]
+ n = dup_degree(c)
+
+ for i, coeff in enumerate(c):
+ if not coeff:
+ continue
+
+ T = dup_zz_diophantine(F, n - i, p, K)
+
+ for j, (s, t) in enumerate(zip(S, T)):
+ t = dup_mul_ground(t, coeff, K)
+ S[j] = dup_trunc(dup_add(s, t, K), p, K)
+ else:
+ n = len(A)
+ e = dmp_expand(F, u, K)
+
+ a, A = A[-1], A[:-1]
+ B, G = [], []
+
+ for f in F:
+ B.append(dmp_quo(e, f, u, K))
+ G.append(dmp_eval_in(f, a, n, u, K))
+
+ C = dmp_eval_in(c, a, n, u, K)
+
+ v = u - 1
+
+ S = dmp_zz_diophantine(G, C, A, d, p, v, K)
+ S = [ dmp_raise(s, 1, v, K) for s in S ]
+
+ for s, b in zip(S, B):
+ c = dmp_sub_mul(c, s, b, u, K)
+
+ c = dmp_ground_trunc(c, p, u, K)
+
+ m = dmp_nest([K.one, -a], n, K)
+ M = dmp_one(n, K)
+
+ for k in range(0, d):
+ if dmp_zero_p(c, u):
+ break
+
+ M = dmp_mul(M, m, u, K)
+ C = dmp_diff_eval_in(c, k + 1, a, n, u, K)
+
+ if not dmp_zero_p(C, v):
+ C = dmp_quo_ground(C, K.factorial(K(k) + 1), v, K)
+ T = dmp_zz_diophantine(G, C, A, d, p, v, K)
+
+ for i, t in enumerate(T):
+ T[i] = dmp_mul(dmp_raise(t, 1, v, K), M, u, K)
+
+ for i, (s, t) in enumerate(zip(S, T)):
+ S[i] = dmp_add(s, t, u, K)
+
+ for t, b in zip(T, B):
+ c = dmp_sub_mul(c, t, b, u, K)
+
+ c = dmp_ground_trunc(c, p, u, K)
+
+ S = [ dmp_ground_trunc(s, p, u, K) for s in S ]
+
+ return S
+
+
+def dmp_zz_wang_hensel_lifting(f, H, LC, A, p, u, K):
+ """Wang/EEZ: Parallel Hensel lifting algorithm. """
+ S, n, v = [f], len(A), u - 1
+
+ H = list(H)
+
+ for i, a in enumerate(reversed(A[1:])):
+ s = dmp_eval_in(S[0], a, n - i, u - i, K)
+ S.insert(0, dmp_ground_trunc(s, p, v - i, K))
+
+ d = max(dmp_degree_list(f, u)[1:])
+
+ for j, s, a in zip(range(2, n + 2), S, A):
+ G, w = list(H), j - 1
+
+ I, J = A[:j - 2], A[j - 1:]
+
+ for i, (h, lc) in enumerate(zip(H, LC)):
+ lc = dmp_ground_trunc(dmp_eval_tail(lc, J, v, K), p, w - 1, K)
+ H[i] = [lc] + dmp_raise(h[1:], 1, w - 1, K)
+
+ m = dmp_nest([K.one, -a], w, K)
+ M = dmp_one(w, K)
+
+ c = dmp_sub(s, dmp_expand(H, w, K), w, K)
+
+ dj = dmp_degree_in(s, w, w)
+
+ for k in range(0, dj):
+ if dmp_zero_p(c, w):
+ break
+
+ M = dmp_mul(M, m, w, K)
+ C = dmp_diff_eval_in(c, k + 1, a, w, w, K)
+
+ if not dmp_zero_p(C, w - 1):
+ C = dmp_quo_ground(C, K.factorial(K(k) + 1), w - 1, K)
+ T = dmp_zz_diophantine(G, C, I, d, p, w - 1, K)
+
+ for i, (h, t) in enumerate(zip(H, T)):
+ h = dmp_add_mul(h, dmp_raise(t, 1, w - 1, K), M, w, K)
+ H[i] = dmp_ground_trunc(h, p, w, K)
+
+ h = dmp_sub(s, dmp_expand(H, w, K), w, K)
+ c = dmp_ground_trunc(h, p, w, K)
+
+ if dmp_expand(H, u, K) != f:
+ raise ExtraneousFactors # pragma: no cover
+ else:
+ return H
+
+
+def dmp_zz_wang(f, u, K, mod=None, seed=None):
+ r"""
+ Factor primitive square-free polynomials in `Z[X]`.
+
+ Given a multivariate polynomial `f` in `Z[x_1,...,x_n]`, which is
+ primitive and square-free in `x_1`, computes factorization of `f` into
+ irreducibles over integers.
+
+ The procedure is based on Wang's Enhanced Extended Zassenhaus
+ algorithm. The algorithm works by viewing `f` as a univariate polynomial
+ in `Z[x_2,...,x_n][x_1]`, for which an evaluation mapping is computed::
+
+ x_2 -> a_2, ..., x_n -> a_n
+
+ where `a_i`, for `i = 2, \dots, n`, are carefully chosen integers. The
+ mapping is used to transform `f` into a univariate polynomial in `Z[x_1]`,
+ which can be factored efficiently using Zassenhaus algorithm. The last
+ step is to lift univariate factors to obtain true multivariate
+ factors. For this purpose a parallel Hensel lifting procedure is used.
+
+ The parameter ``seed`` is passed to _randint and can be used to seed randint
+ (when an integer) or (for testing purposes) can be a sequence of numbers.
+
+ References
+ ==========
+
+ .. [1] [Wang78]_
+ .. [2] [Geddes92]_
+
+ """
+ from sympy.ntheory import nextprime
+
+ randint = _randint(seed)
+
+ ct, T = dmp_zz_factor(dmp_LC(f, K), u - 1, K)
+
+ b = dmp_zz_mignotte_bound(f, u, K)
+ p = K(nextprime(b))
+
+ if mod is None:
+ if u == 1:
+ mod = 2
+ else:
+ mod = 1
+
+ history, configs, A, r = set(), [], [K.zero]*u, None
+
+ try:
+ cs, s, E = dmp_zz_wang_test_points(f, T, ct, A, u, K)
+
+ _, H = dup_zz_factor_sqf(s, K)
+
+ r = len(H)
+
+ if r == 1:
+ return [f]
+
+ configs = [(s, cs, E, H, A)]
+ except EvaluationFailed:
+ pass
+
+ eez_num_configs = query('EEZ_NUMBER_OF_CONFIGS')
+ eez_num_tries = query('EEZ_NUMBER_OF_TRIES')
+ eez_mod_step = query('EEZ_MODULUS_STEP')
+
+ while len(configs) < eez_num_configs:
+ for _ in range(eez_num_tries):
+ A = [ K(randint(-mod, mod)) for _ in range(u) ]
+
+ if tuple(A) not in history:
+ history.add(tuple(A))
+ else:
+ continue
+
+ try:
+ cs, s, E = dmp_zz_wang_test_points(f, T, ct, A, u, K)
+ except EvaluationFailed:
+ continue
+
+ _, H = dup_zz_factor_sqf(s, K)
+
+ rr = len(H)
+
+ if r is not None:
+ if rr != r: # pragma: no cover
+ if rr < r:
+ configs, r = [], rr
+ else:
+ continue
+ else:
+ r = rr
+
+ if r == 1:
+ return [f]
+
+ configs.append((s, cs, E, H, A))
+
+ if len(configs) == eez_num_configs:
+ break
+ else:
+ mod += eez_mod_step
+
+ s_norm, s_arg, i = None, 0, 0
+
+ for s, _, _, _, _ in configs:
+ _s_norm = dup_max_norm(s, K)
+
+ if s_norm is not None:
+ if _s_norm < s_norm:
+ s_norm = _s_norm
+ s_arg = i
+ else:
+ s_norm = _s_norm
+
+ i += 1
+
+ _, cs, E, H, A = configs[s_arg]
+ orig_f = f
+
+ try:
+ f, H, LC = dmp_zz_wang_lead_coeffs(f, T, cs, E, H, A, u, K)
+ factors = dmp_zz_wang_hensel_lifting(f, H, LC, A, p, u, K)
+ except ExtraneousFactors: # pragma: no cover
+ if query('EEZ_RESTART_IF_NEEDED'):
+ return dmp_zz_wang(orig_f, u, K, mod + 1)
+ else:
+ raise ExtraneousFactors(
+ "we need to restart algorithm with better parameters")
+
+ result = []
+
+ for f in factors:
+ _, f = dmp_ground_primitive(f, u, K)
+
+ if K.is_negative(dmp_ground_LC(f, u, K)):
+ f = dmp_neg(f, u, K)
+
+ result.append(f)
+
+ return result
+
+
+def dmp_zz_factor(f, u, K):
+ r"""
+ Factor (non square-free) polynomials in `Z[X]`.
+
+ Given a multivariate polynomial `f` in `Z[x]` computes its complete
+ factorization `f_1, \dots, f_n` into irreducibles over integers::
+
+ f = content(f) f_1**k_1 ... f_n**k_n
+
+ The factorization is computed by reducing the input polynomial
+ into a primitive square-free polynomial and factoring it using
+ Enhanced Extended Zassenhaus (EEZ) algorithm. Trial division
+ is used to recover the multiplicities of factors.
+
+ The result is returned as a tuple consisting of::
+
+ (content(f), [(f_1, k_1), ..., (f_n, k_n))
+
+ Consider polynomial `f = 2*(x**2 - y**2)`::
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_zz_factor(2*x**2 - 2*y**2)
+ (2, [(x - y, 1), (x + y, 1)])
+
+ In result we got the following factorization::
+
+ f = 2 (x - y) (x + y)
+
+ References
+ ==========
+
+ .. [1] [Gathen99]_
+
+ """
+ if not u:
+ return dup_zz_factor(f, K)
+
+ if dmp_zero_p(f, u):
+ return K.zero, []
+
+ cont, g = dmp_ground_primitive(f, u, K)
+
+ if dmp_ground_LC(g, u, K) < 0:
+ cont, g = -cont, dmp_neg(g, u, K)
+
+ if all(d <= 0 for d in dmp_degree_list(g, u)):
+ return cont, []
+
+ G, g = dmp_primitive(g, u, K)
+
+ factors = []
+
+ if dmp_degree(g, u) > 0:
+ g = dmp_sqf_part(g, u, K)
+ H = dmp_zz_wang(g, u, K)
+ factors = dmp_trial_division(f, H, u, K)
+
+ for g, k in dmp_zz_factor(G, u - 1, K)[1]:
+ factors.insert(0, ([g], k))
+
+ _dmp_check_degrees(f, u, factors)
+
+ return cont, _sort_factors(factors)
+
+
+def dup_qq_i_factor(f, K0):
+ """Factor univariate polynomials into irreducibles in `QQ_I[x]`. """
+ # Factor in QQ
+ K1 = K0.as_AlgebraicField()
+ f = dup_convert(f, K0, K1)
+ coeff, factors = dup_factor_list(f, K1)
+ factors = [(dup_convert(fac, K1, K0), i) for fac, i in factors]
+ coeff = K0.convert(coeff, K1)
+ return coeff, factors
+
+
+def dup_zz_i_factor(f, K0):
+ """Factor univariate polynomials into irreducibles in `ZZ_I[x]`. """
+ # First factor in QQ_I
+ K1 = K0.get_field()
+ f = dup_convert(f, K0, K1)
+ coeff, factors = dup_qq_i_factor(f, K1)
+
+ new_factors = []
+ for fac, i in factors:
+ # Extract content
+ fac_denom, fac_num = dup_clear_denoms(fac, K1)
+ fac_num_ZZ_I = dup_convert(fac_num, K1, K0)
+ content, fac_prim = dmp_ground_primitive(fac_num_ZZ_I, 0, K0)
+
+ coeff = (coeff * content ** i) // fac_denom ** i
+ new_factors.append((fac_prim, i))
+
+ factors = new_factors
+ coeff = K0.convert(coeff, K1)
+ return coeff, factors
+
+
+def dmp_qq_i_factor(f, u, K0):
+ """Factor multivariate polynomials into irreducibles in `QQ_I[X]`. """
+ # Factor in QQ
+ K1 = K0.as_AlgebraicField()
+ f = dmp_convert(f, u, K0, K1)
+ coeff, factors = dmp_factor_list(f, u, K1)
+ factors = [(dmp_convert(fac, u, K1, K0), i) for fac, i in factors]
+ coeff = K0.convert(coeff, K1)
+ return coeff, factors
+
+
+def dmp_zz_i_factor(f, u, K0):
+ """Factor multivariate polynomials into irreducibles in `ZZ_I[X]`. """
+ # First factor in QQ_I
+ K1 = K0.get_field()
+ f = dmp_convert(f, u, K0, K1)
+ coeff, factors = dmp_qq_i_factor(f, u, K1)
+
+ new_factors = []
+ for fac, i in factors:
+ # Extract content
+ fac_denom, fac_num = dmp_clear_denoms(fac, u, K1)
+ fac_num_ZZ_I = dmp_convert(fac_num, u, K1, K0)
+ content, fac_prim = dmp_ground_primitive(fac_num_ZZ_I, u, K0)
+
+ coeff = (coeff * content ** i) // fac_denom ** i
+ new_factors.append((fac_prim, i))
+
+ factors = new_factors
+ coeff = K0.convert(coeff, K1)
+ return coeff, factors
+
+
+def dup_ext_factor(f, K):
+ r"""Factor univariate polynomials over algebraic number fields.
+
+ The domain `K` must be an algebraic number field `k(a)` (see :ref:`QQ(a)`).
+
+ Examples
+ ========
+
+ First define the algebraic number field `K = \mathbb{Q}(\sqrt{2})`:
+
+ >>> from sympy import QQ, sqrt
+ >>> from sympy.polys.factortools import dup_ext_factor
+ >>> K = QQ.algebraic_field(sqrt(2))
+
+ We can now factorise the polynomial `x^2 - 2` over `K`:
+
+ >>> p = [K(1), K(0), K(-2)] # x^2 - 2
+ >>> p1 = [K(1), -K.unit] # x - sqrt(2)
+ >>> p2 = [K(1), +K.unit] # x + sqrt(2)
+ >>> dup_ext_factor(p, K) == (K.one, [(p1, 1), (p2, 1)])
+ True
+
+ Usually this would be done at a higher level:
+
+ >>> from sympy import factor
+ >>> from sympy.abc import x
+ >>> factor(x**2 - 2, extension=sqrt(2))
+ (x - sqrt(2))*(x + sqrt(2))
+
+ Explanation
+ ===========
+
+ Uses Trager's algorithm. In particular this function is algorithm
+ ``alg_factor`` from [Trager76]_.
+
+ If `f` is a polynomial in `k(a)[x]` then its norm `g(x)` is a polynomial in
+ `k[x]`. If `g(x)` is square-free and has irreducible factors `g_1(x)`,
+ `g_2(x)`, `\cdots` then the irreducible factors of `f` in `k(a)[x]` are
+ given by `f_i(x) = \gcd(f(x), g_i(x))` where the GCD is computed in
+ `k(a)[x]`.
+
+ The first step in Trager's algorithm is to find an integer shift `s` so
+ that `f(x-sa)` has square-free norm. Then the norm is factorized in `k[x]`
+ and the GCD of (shifted) `f` with each factor gives the shifted factors of
+ `f`. At the end the shift is undone to recover the unshifted factors of `f`
+ in `k(a)[x]`.
+
+ The algorithm reduces the problem of factorization in `k(a)[x]` to
+ factorization in `k[x]` with the main additional steps being to compute the
+ norm (a resultant calculation in `k[x,y]`) and some polynomial GCDs in
+ `k(a)[x]`.
+
+ In practice in SymPy the base field `k` will be the rationals :ref:`QQ` and
+ this function factorizes a polynomial with coefficients in an algebraic
+ number field like `\mathbb{Q}(\sqrt{2})`.
+
+ See Also
+ ========
+
+ dmp_ext_factor:
+ Analogous function for multivariate polynomials over ``k(a)``.
+ dup_sqf_norm:
+ Subroutine ``sqfr_norm`` also from [Trager76]_.
+ sympy.polys.polytools.factor:
+ The high-level function that ultimately uses this function as needed.
+ """
+ n, lc = dup_degree(f), dup_LC(f, K)
+
+ f = dup_monic(f, K)
+
+ if n <= 0:
+ return lc, []
+ if n == 1:
+ return lc, [(f, 1)]
+
+ f, F = dup_sqf_part(f, K), f
+ s, g, r = dup_sqf_norm(f, K)
+
+ factors = dup_factor_list_include(r, K.dom)
+
+ if len(factors) == 1:
+ return lc, [(f, n//dup_degree(f))]
+
+ H = s*K.unit
+
+ for i, (factor, _) in enumerate(factors):
+ h = dup_convert(factor, K.dom, K)
+ h, _, g = dup_inner_gcd(h, g, K)
+ h = dup_shift(h, H, K)
+ factors[i] = h
+
+ factors = dup_trial_division(F, factors, K)
+
+ _dup_check_degrees(F, factors)
+
+ return lc, factors
+
+
+def dmp_ext_factor(f, u, K):
+ r"""Factor multivariate polynomials over algebraic number fields.
+
+ The domain `K` must be an algebraic number field `k(a)` (see :ref:`QQ(a)`).
+
+ Examples
+ ========
+
+ First define the algebraic number field `K = \mathbb{Q}(\sqrt{2})`:
+
+ >>> from sympy import QQ, sqrt
+ >>> from sympy.polys.factortools import dmp_ext_factor
+ >>> K = QQ.algebraic_field(sqrt(2))
+
+ We can now factorise the polynomial `x^2 y^2 - 2` over `K`:
+
+ >>> p = [[K(1),K(0),K(0)], [], [K(-2)]] # x**2*y**2 - 2
+ >>> p1 = [[K(1),K(0)], [-K.unit]] # x*y - sqrt(2)
+ >>> p2 = [[K(1),K(0)], [+K.unit]] # x*y + sqrt(2)
+ >>> dmp_ext_factor(p, 1, K) == (K.one, [(p1, 1), (p2, 1)])
+ True
+
+ Usually this would be done at a higher level:
+
+ >>> from sympy import factor
+ >>> from sympy.abc import x, y
+ >>> factor(x**2*y**2 - 2, extension=sqrt(2))
+ (x*y - sqrt(2))*(x*y + sqrt(2))
+
+ Explanation
+ ===========
+
+ This is Trager's algorithm for multivariate polynomials. In particular this
+ function is algorithm ``alg_factor`` from [Trager76]_.
+
+ See :func:`dup_ext_factor` for explanation.
+
+ See Also
+ ========
+
+ dup_ext_factor:
+ Analogous function for univariate polynomials over ``k(a)``.
+ dmp_sqf_norm:
+ Multivariate version of subroutine ``sqfr_norm`` also from [Trager76]_.
+ sympy.polys.polytools.factor:
+ The high-level function that ultimately uses this function as needed.
+ """
+ if not u:
+ return dup_ext_factor(f, K)
+
+ lc = dmp_ground_LC(f, u, K)
+ f = dmp_ground_monic(f, u, K)
+
+ if all(d <= 0 for d in dmp_degree_list(f, u)):
+ return lc, []
+
+ f, F = dmp_sqf_part(f, u, K), f
+ s, g, r = dmp_sqf_norm(f, u, K)
+
+ factors = dmp_factor_list_include(r, u, K.dom)
+
+ if len(factors) == 1:
+ factors = [f]
+ else:
+ for i, (factor, _) in enumerate(factors):
+ h = dmp_convert(factor, u, K.dom, K)
+ h, _, g = dmp_inner_gcd(h, g, u, K)
+ a = [si*K.unit for si in s]
+ h = dmp_shift(h, a, u, K)
+ factors[i] = h
+
+ result = dmp_trial_division(F, factors, u, K)
+
+ _dmp_check_degrees(F, u, result)
+
+ return lc, result
+
+
+def dup_gf_factor(f, K):
+ """Factor univariate polynomials over finite fields. """
+ f = dup_convert(f, K, K.dom)
+
+ coeff, factors = gf_factor(f, K.mod, K.dom)
+
+ for i, (f, k) in enumerate(factors):
+ factors[i] = (dup_convert(f, K.dom, K), k)
+
+ return K.convert(coeff, K.dom), factors
+
+
+def dmp_gf_factor(f, u, K):
+ """Factor multivariate polynomials over finite fields. """
+ raise NotImplementedError('multivariate polynomials over finite fields')
+
+
+def dup_factor_list(f, K0):
+ """Factor univariate polynomials into irreducibles in `K[x]`. """
+ j, f = dup_terms_gcd(f, K0)
+ cont, f = dup_primitive(f, K0)
+
+ if K0.is_FiniteField:
+ coeff, factors = dup_gf_factor(f, K0)
+ elif K0.is_Algebraic:
+ coeff, factors = dup_ext_factor(f, K0)
+ elif K0.is_GaussianRing:
+ coeff, factors = dup_zz_i_factor(f, K0)
+ elif K0.is_GaussianField:
+ coeff, factors = dup_qq_i_factor(f, K0)
+ else:
+ if not K0.is_Exact:
+ K0_inexact, K0 = K0, K0.get_exact()
+ f = dup_convert(f, K0_inexact, K0)
+ else:
+ K0_inexact = None
+
+ if K0.is_Field:
+ K = K0.get_ring()
+
+ denom, f = dup_clear_denoms(f, K0, K)
+ f = dup_convert(f, K0, K)
+ else:
+ K = K0
+
+ if K.is_ZZ:
+ coeff, factors = dup_zz_factor(f, K)
+ elif K.is_Poly:
+ f, u = dmp_inject(f, 0, K)
+
+ coeff, factors = dmp_factor_list(f, u, K.dom)
+
+ for i, (f, k) in enumerate(factors):
+ factors[i] = (dmp_eject(f, u, K), k)
+
+ coeff = K.convert(coeff, K.dom)
+ else: # pragma: no cover
+ raise DomainError('factorization not supported over %s' % K0)
+
+ if K0.is_Field:
+ for i, (f, k) in enumerate(factors):
+ factors[i] = (dup_convert(f, K, K0), k)
+
+ coeff = K0.convert(coeff, K)
+ coeff = K0.quo(coeff, denom)
+
+ if K0_inexact:
+ for i, (f, k) in enumerate(factors):
+ max_norm = dup_max_norm(f, K0)
+ f = dup_quo_ground(f, max_norm, K0)
+ f = dup_convert(f, K0, K0_inexact)
+ factors[i] = (f, k)
+ coeff = K0.mul(coeff, K0.pow(max_norm, k))
+
+ coeff = K0_inexact.convert(coeff, K0)
+ K0 = K0_inexact
+
+ if j:
+ factors.insert(0, ([K0.one, K0.zero], j))
+
+ return coeff*cont, _sort_factors(factors)
+
+
+def dup_factor_list_include(f, K):
+ """Factor univariate polynomials into irreducibles in `K[x]`. """
+ coeff, factors = dup_factor_list(f, K)
+
+ if not factors:
+ return [(dup_strip([coeff]), 1)]
+ else:
+ g = dup_mul_ground(factors[0][0], coeff, K)
+ return [(g, factors[0][1])] + factors[1:]
+
+
+def dmp_factor_list(f, u, K0):
+ """Factor multivariate polynomials into irreducibles in `K[X]`. """
+ if not u:
+ return dup_factor_list(f, K0)
+
+ J, f = dmp_terms_gcd(f, u, K0)
+ cont, f = dmp_ground_primitive(f, u, K0)
+
+ if K0.is_FiniteField: # pragma: no cover
+ coeff, factors = dmp_gf_factor(f, u, K0)
+ elif K0.is_Algebraic:
+ coeff, factors = dmp_ext_factor(f, u, K0)
+ elif K0.is_GaussianRing:
+ coeff, factors = dmp_zz_i_factor(f, u, K0)
+ elif K0.is_GaussianField:
+ coeff, factors = dmp_qq_i_factor(f, u, K0)
+ else:
+ if not K0.is_Exact:
+ K0_inexact, K0 = K0, K0.get_exact()
+ f = dmp_convert(f, u, K0_inexact, K0)
+ else:
+ K0_inexact = None
+
+ if K0.is_Field:
+ K = K0.get_ring()
+
+ denom, f = dmp_clear_denoms(f, u, K0, K)
+ f = dmp_convert(f, u, K0, K)
+ else:
+ K = K0
+
+ if K.is_ZZ:
+ levels, f, v = dmp_exclude(f, u, K)
+ coeff, factors = dmp_zz_factor(f, v, K)
+
+ for i, (f, k) in enumerate(factors):
+ factors[i] = (dmp_include(f, levels, v, K), k)
+ elif K.is_Poly:
+ f, v = dmp_inject(f, u, K)
+
+ coeff, factors = dmp_factor_list(f, v, K.dom)
+
+ for i, (f, k) in enumerate(factors):
+ factors[i] = (dmp_eject(f, v, K), k)
+
+ coeff = K.convert(coeff, K.dom)
+ else: # pragma: no cover
+ raise DomainError('factorization not supported over %s' % K0)
+
+ if K0.is_Field:
+ for i, (f, k) in enumerate(factors):
+ factors[i] = (dmp_convert(f, u, K, K0), k)
+
+ coeff = K0.convert(coeff, K)
+ coeff = K0.quo(coeff, denom)
+
+ if K0_inexact:
+ for i, (f, k) in enumerate(factors):
+ max_norm = dmp_max_norm(f, u, K0)
+ f = dmp_quo_ground(f, max_norm, u, K0)
+ f = dmp_convert(f, u, K0, K0_inexact)
+ factors[i] = (f, k)
+ coeff = K0.mul(coeff, K0.pow(max_norm, k))
+
+ coeff = K0_inexact.convert(coeff, K0)
+ K0 = K0_inexact
+
+ for i, j in enumerate(reversed(J)):
+ if not j:
+ continue
+
+ term = {(0,)*(u - i) + (1,) + (0,)*i: K0.one}
+ factors.insert(0, (dmp_from_dict(term, u, K0), j))
+
+ return coeff*cont, _sort_factors(factors)
+
+
+def dmp_factor_list_include(f, u, K):
+ """Factor multivariate polynomials into irreducibles in `K[X]`. """
+ if not u:
+ return dup_factor_list_include(f, K)
+
+ coeff, factors = dmp_factor_list(f, u, K)
+
+ if not factors:
+ return [(dmp_ground(coeff, u), 1)]
+ else:
+ g = dmp_mul_ground(factors[0][0], coeff, u, K)
+ return [(g, factors[0][1])] + factors[1:]
+
+
+def dup_irreducible_p(f, K):
+ """
+ Returns ``True`` if a univariate polynomial ``f`` has no factors
+ over its domain.
+ """
+ return dmp_irreducible_p(f, 0, K)
+
+
+def dmp_irreducible_p(f, u, K):
+ """
+ Returns ``True`` if a multivariate polynomial ``f`` has no factors
+ over its domain.
+ """
+ _, factors = dmp_factor_list(f, u, K)
+
+ if not factors:
+ return True
+ elif len(factors) > 1:
+ return False
+ else:
+ _, k = factors[0]
+ return k == 1
diff --git a/lib/python3.12/site-packages/sympy/polys/fglmtools.py b/lib/python3.12/site-packages/sympy/polys/fglmtools.py
new file mode 100644
index 0000000000000000000000000000000000000000..d68fe5bc2a40741e39b89163d393f7b57e6b1c49
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/fglmtools.py
@@ -0,0 +1,153 @@
+"""Implementation of matrix FGLM Groebner basis conversion algorithm. """
+
+
+from sympy.polys.monomials import monomial_mul, monomial_div
+
+def matrix_fglm(F, ring, O_to):
+ """
+ Converts the reduced Groebner basis ``F`` of a zero-dimensional
+ ideal w.r.t. ``O_from`` to a reduced Groebner basis
+ w.r.t. ``O_to``.
+
+ References
+ ==========
+
+ .. [1] J.C. Faugere, P. Gianni, D. Lazard, T. Mora (1994). Efficient
+ Computation of Zero-dimensional Groebner Bases by Change of
+ Ordering
+ """
+ domain = ring.domain
+ ngens = ring.ngens
+
+ ring_to = ring.clone(order=O_to)
+
+ old_basis = _basis(F, ring)
+ M = _representing_matrices(old_basis, F, ring)
+
+ # V contains the normalforms (wrt O_from) of S
+ S = [ring.zero_monom]
+ V = [[domain.one] + [domain.zero] * (len(old_basis) - 1)]
+ G = []
+
+ L = [(i, 0) for i in range(ngens)] # (i, j) corresponds to x_i * S[j]
+ L.sort(key=lambda k_l: O_to(_incr_k(S[k_l[1]], k_l[0])), reverse=True)
+ t = L.pop()
+
+ P = _identity_matrix(len(old_basis), domain)
+
+ while True:
+ s = len(S)
+ v = _matrix_mul(M[t[0]], V[t[1]])
+ _lambda = _matrix_mul(P, v)
+
+ if all(_lambda[i] == domain.zero for i in range(s, len(old_basis))):
+ # there is a linear combination of v by V
+ lt = ring.term_new(_incr_k(S[t[1]], t[0]), domain.one)
+ rest = ring.from_dict({S[i]: _lambda[i] for i in range(s)})
+
+ g = (lt - rest).set_ring(ring_to)
+ if g:
+ G.append(g)
+ else:
+ # v is linearly independent from V
+ P = _update(s, _lambda, P)
+ S.append(_incr_k(S[t[1]], t[0]))
+ V.append(v)
+
+ L.extend([(i, s) for i in range(ngens)])
+ L = list(set(L))
+ L.sort(key=lambda k_l: O_to(_incr_k(S[k_l[1]], k_l[0])), reverse=True)
+
+ L = [(k, l) for (k, l) in L if all(monomial_div(_incr_k(S[l], k), g.LM) is None for g in G)]
+
+ if not L:
+ G = [ g.monic() for g in G ]
+ return sorted(G, key=lambda g: O_to(g.LM), reverse=True)
+
+ t = L.pop()
+
+
+def _incr_k(m, k):
+ return tuple(list(m[:k]) + [m[k] + 1] + list(m[k + 1:]))
+
+
+def _identity_matrix(n, domain):
+ M = [[domain.zero]*n for _ in range(n)]
+
+ for i in range(n):
+ M[i][i] = domain.one
+
+ return M
+
+
+def _matrix_mul(M, v):
+ return [sum(row[i] * v[i] for i in range(len(v))) for row in M]
+
+
+def _update(s, _lambda, P):
+ """
+ Update ``P`` such that for the updated `P'` `P' v = e_{s}`.
+ """
+ k = min(j for j in range(s, len(_lambda)) if _lambda[j] != 0)
+
+ for r in range(len(_lambda)):
+ if r != k:
+ P[r] = [P[r][j] - (P[k][j] * _lambda[r]) / _lambda[k] for j in range(len(P[r]))]
+
+ P[k] = [P[k][j] / _lambda[k] for j in range(len(P[k]))]
+ P[k], P[s] = P[s], P[k]
+
+ return P
+
+
+def _representing_matrices(basis, G, ring):
+ r"""
+ Compute the matrices corresponding to the linear maps `m \mapsto
+ x_i m` for all variables `x_i`.
+ """
+ domain = ring.domain
+ u = ring.ngens-1
+
+ def var(i):
+ return tuple([0] * i + [1] + [0] * (u - i))
+
+ def representing_matrix(m):
+ M = [[domain.zero] * len(basis) for _ in range(len(basis))]
+
+ for i, v in enumerate(basis):
+ r = ring.term_new(monomial_mul(m, v), domain.one).rem(G)
+
+ for monom, coeff in r.terms():
+ j = basis.index(monom)
+ M[j][i] = coeff
+
+ return M
+
+ return [representing_matrix(var(i)) for i in range(u + 1)]
+
+
+def _basis(G, ring):
+ r"""
+ Computes a list of monomials which are not divisible by the leading
+ monomials wrt to ``O`` of ``G``. These monomials are a basis of
+ `K[X_1, \ldots, X_n]/(G)`.
+ """
+ order = ring.order
+
+ leading_monomials = [g.LM for g in G]
+ candidates = [ring.zero_monom]
+ basis = []
+
+ while candidates:
+ t = candidates.pop()
+ basis.append(t)
+
+ new_candidates = [_incr_k(t, k) for k in range(ring.ngens)
+ if all(monomial_div(_incr_k(t, k), lmg) is None
+ for lmg in leading_monomials)]
+ candidates.extend(new_candidates)
+ candidates.sort(key=order, reverse=True)
+
+ basis = list(set(basis))
+
+ return sorted(basis, key=order)
diff --git a/lib/python3.12/site-packages/sympy/polys/fields.py b/lib/python3.12/site-packages/sympy/polys/fields.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3f239c4ed10dc769def957c520738e45bc1de8f
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/fields.py
@@ -0,0 +1,631 @@
+"""Sparse rational function fields. """
+
+from __future__ import annotations
+from typing import Any
+from functools import reduce
+
+from operator import add, mul, lt, le, gt, ge
+
+from sympy.core.expr import Expr
+from sympy.core.mod import Mod
+from sympy.core.numbers import Exp1
+from sympy.core.singleton import S
+from sympy.core.symbol import Symbol
+from sympy.core.sympify import CantSympify, sympify
+from sympy.functions.elementary.exponential import ExpBase
+from sympy.polys.domains.domainelement import DomainElement
+from sympy.polys.domains.fractionfield import FractionField
+from sympy.polys.domains.polynomialring import PolynomialRing
+from sympy.polys.constructor import construct_domain
+from sympy.polys.orderings import lex
+from sympy.polys.polyerrors import CoercionFailed
+from sympy.polys.polyoptions import build_options
+from sympy.polys.polyutils import _parallel_dict_from_expr
+from sympy.polys.rings import PolyElement
+from sympy.printing.defaults import DefaultPrinting
+from sympy.utilities import public
+from sympy.utilities.iterables import is_sequence
+from sympy.utilities.magic import pollute
+
+@public
+def field(symbols, domain, order=lex):
+ """Construct new rational function field returning (field, x1, ..., xn). """
+ _field = FracField(symbols, domain, order)
+ return (_field,) + _field.gens
+
+@public
+def xfield(symbols, domain, order=lex):
+ """Construct new rational function field returning (field, (x1, ..., xn)). """
+ _field = FracField(symbols, domain, order)
+ return (_field, _field.gens)
+
+@public
+def vfield(symbols, domain, order=lex):
+ """Construct new rational function field and inject generators into global namespace. """
+ _field = FracField(symbols, domain, order)
+ pollute([ sym.name for sym in _field.symbols ], _field.gens)
+ return _field
+
+@public
+def sfield(exprs, *symbols, **options):
+ """Construct a field deriving generators and domain
+ from options and input expressions.
+
+ Parameters
+ ==========
+
+ exprs : py:class:`~.Expr` or sequence of :py:class:`~.Expr` (sympifiable)
+
+ symbols : sequence of :py:class:`~.Symbol`/:py:class:`~.Expr`
+
+ options : keyword arguments understood by :py:class:`~.Options`
+
+ Examples
+ ========
+
+ >>> from sympy import exp, log, symbols, sfield
+
+ >>> x = symbols("x")
+ >>> K, f = sfield((x*log(x) + 4*x**2)*exp(1/x + log(x)/3)/x**2)
+ >>> K
+ Rational function field in x, exp(1/x), log(x), x**(1/3) over ZZ with lex order
+ >>> f
+ (4*x**2*(exp(1/x)) + x*(exp(1/x))*(log(x)))/((x**(1/3))**5)
+ """
+ single = False
+ if not is_sequence(exprs):
+ exprs, single = [exprs], True
+
+ exprs = list(map(sympify, exprs))
+ opt = build_options(symbols, options)
+ numdens = []
+ for expr in exprs:
+ numdens.extend(expr.as_numer_denom())
+ reps, opt = _parallel_dict_from_expr(numdens, opt)
+
+ if opt.domain is None:
+ # NOTE: this is inefficient because construct_domain() automatically
+ # performs conversion to the target domain. It shouldn't do this.
+ coeffs = sum([list(rep.values()) for rep in reps], [])
+ opt.domain, _ = construct_domain(coeffs, opt=opt)
+
+ _field = FracField(opt.gens, opt.domain, opt.order)
+ fracs = []
+ for i in range(0, len(reps), 2):
+ fracs.append(_field(tuple(reps[i:i+2])))
+
+ if single:
+ return (_field, fracs[0])
+ else:
+ return (_field, fracs)
+
+_field_cache: dict[Any, Any] = {}
+
+class FracField(DefaultPrinting):
+ """Multivariate distributed rational function field. """
+
+ def __new__(cls, symbols, domain, order=lex):
+ from sympy.polys.rings import PolyRing
+ ring = PolyRing(symbols, domain, order)
+ symbols = ring.symbols
+ ngens = ring.ngens
+ domain = ring.domain
+ order = ring.order
+
+ _hash_tuple = (cls.__name__, symbols, ngens, domain, order)
+ obj = _field_cache.get(_hash_tuple)
+
+ if obj is None:
+ obj = object.__new__(cls)
+ obj._hash_tuple = _hash_tuple
+ obj._hash = hash(_hash_tuple)
+ obj.ring = ring
+ obj.dtype = type("FracElement", (FracElement,), {"field": obj})
+ obj.symbols = symbols
+ obj.ngens = ngens
+ obj.domain = domain
+ obj.order = order
+
+ obj.zero = obj.dtype(ring.zero)
+ obj.one = obj.dtype(ring.one)
+
+ obj.gens = obj._gens()
+
+ for symbol, generator in zip(obj.symbols, obj.gens):
+ if isinstance(symbol, Symbol):
+ name = symbol.name
+
+ if not hasattr(obj, name):
+ setattr(obj, name, generator)
+
+ _field_cache[_hash_tuple] = obj
+
+ return obj
+
+ def _gens(self):
+ """Return a list of polynomial generators. """
+ return tuple([ self.dtype(gen) for gen in self.ring.gens ])
+
+ def __getnewargs__(self):
+ return (self.symbols, self.domain, self.order)
+
+ def __hash__(self):
+ return self._hash
+
+ def index(self, gen):
+ if isinstance(gen, self.dtype):
+ return self.ring.index(gen.to_poly())
+ else:
+ raise ValueError("expected a %s, got %s instead" % (self.dtype,gen))
+
+ def __eq__(self, other):
+ return isinstance(other, FracField) and \
+ (self.symbols, self.ngens, self.domain, self.order) == \
+ (other.symbols, other.ngens, other.domain, other.order)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def raw_new(self, numer, denom=None):
+ return self.dtype(numer, denom)
+ def new(self, numer, denom=None):
+ if denom is None: denom = self.ring.one
+ numer, denom = numer.cancel(denom)
+ return self.raw_new(numer, denom)
+
+ def domain_new(self, element):
+ return self.domain.convert(element)
+
+ def ground_new(self, element):
+ try:
+ return self.new(self.ring.ground_new(element))
+ except CoercionFailed:
+ domain = self.domain
+
+ if not domain.is_Field and domain.has_assoc_Field:
+ ring = self.ring
+ ground_field = domain.get_field()
+ element = ground_field.convert(element)
+ numer = ring.ground_new(ground_field.numer(element))
+ denom = ring.ground_new(ground_field.denom(element))
+ return self.raw_new(numer, denom)
+ else:
+ raise
+
+ def field_new(self, element):
+ if isinstance(element, FracElement):
+ if self == element.field:
+ return element
+
+ if isinstance(self.domain, FractionField) and \
+ self.domain.field == element.field:
+ return self.ground_new(element)
+ elif isinstance(self.domain, PolynomialRing) and \
+ self.domain.ring.to_field() == element.field:
+ return self.ground_new(element)
+ else:
+ raise NotImplementedError("conversion")
+ elif isinstance(element, PolyElement):
+ denom, numer = element.clear_denoms()
+
+ if isinstance(self.domain, PolynomialRing) and \
+ numer.ring == self.domain.ring:
+ numer = self.ring.ground_new(numer)
+ elif isinstance(self.domain, FractionField) and \
+ numer.ring == self.domain.field.to_ring():
+ numer = self.ring.ground_new(numer)
+ else:
+ numer = numer.set_ring(self.ring)
+
+ denom = self.ring.ground_new(denom)
+ return self.raw_new(numer, denom)
+ elif isinstance(element, tuple) and len(element) == 2:
+ numer, denom = list(map(self.ring.ring_new, element))
+ return self.new(numer, denom)
+ elif isinstance(element, str):
+ raise NotImplementedError("parsing")
+ elif isinstance(element, Expr):
+ return self.from_expr(element)
+ else:
+ return self.ground_new(element)
+
+ __call__ = field_new
+
+ def _rebuild_expr(self, expr, mapping):
+ domain = self.domain
+ powers = tuple((gen, gen.as_base_exp()) for gen in mapping.keys()
+ if gen.is_Pow or isinstance(gen, ExpBase))
+
+ def _rebuild(expr):
+ generator = mapping.get(expr)
+
+ if generator is not None:
+ return generator
+ elif expr.is_Add:
+ return reduce(add, list(map(_rebuild, expr.args)))
+ elif expr.is_Mul:
+ return reduce(mul, list(map(_rebuild, expr.args)))
+ elif expr.is_Pow or isinstance(expr, (ExpBase, Exp1)):
+ b, e = expr.as_base_exp()
+ # look for bg**eg whose integer power may be b**e
+ for gen, (bg, eg) in powers:
+ if bg == b and Mod(e, eg) == 0:
+ return mapping.get(gen)**int(e/eg)
+ if e.is_Integer and e is not S.One:
+ return _rebuild(b)**int(e)
+ elif mapping.get(1/expr) is not None:
+ return 1/mapping.get(1/expr)
+
+ try:
+ return domain.convert(expr)
+ except CoercionFailed:
+ if not domain.is_Field and domain.has_assoc_Field:
+ return domain.get_field().convert(expr)
+ else:
+ raise
+
+ return _rebuild(expr)
+
+ def from_expr(self, expr):
+ mapping = dict(list(zip(self.symbols, self.gens)))
+
+ try:
+ frac = self._rebuild_expr(sympify(expr), mapping)
+ except CoercionFailed:
+ raise ValueError("expected an expression convertible to a rational function in %s, got %s" % (self, expr))
+ else:
+ return self.field_new(frac)
+
+ def to_domain(self):
+ return FractionField(self)
+
+ def to_ring(self):
+ from sympy.polys.rings import PolyRing
+ return PolyRing(self.symbols, self.domain, self.order)
+
+class FracElement(DomainElement, DefaultPrinting, CantSympify):
+ """Element of multivariate distributed rational function field. """
+
+ def __init__(self, numer, denom=None):
+ if denom is None:
+ denom = self.field.ring.one
+ elif not denom:
+ raise ZeroDivisionError("zero denominator")
+
+ self.numer = numer
+ self.denom = denom
+
+ def raw_new(f, numer, denom):
+ return f.__class__(numer, denom)
+ def new(f, numer, denom):
+ return f.raw_new(*numer.cancel(denom))
+
+ def to_poly(f):
+ if f.denom != 1:
+ raise ValueError("f.denom should be 1")
+ return f.numer
+
+ def parent(self):
+ return self.field.to_domain()
+
+ def __getnewargs__(self):
+ return (self.field, self.numer, self.denom)
+
+ _hash = None
+
+ def __hash__(self):
+ _hash = self._hash
+ if _hash is None:
+ self._hash = _hash = hash((self.field, self.numer, self.denom))
+ return _hash
+
+ def copy(self):
+ return self.raw_new(self.numer.copy(), self.denom.copy())
+
+ def set_field(self, new_field):
+ if self.field == new_field:
+ return self
+ else:
+ new_ring = new_field.ring
+ numer = self.numer.set_ring(new_ring)
+ denom = self.denom.set_ring(new_ring)
+ return new_field.new(numer, denom)
+
+ def as_expr(self, *symbols):
+ return self.numer.as_expr(*symbols)/self.denom.as_expr(*symbols)
+
+ def __eq__(f, g):
+ if isinstance(g, FracElement) and f.field == g.field:
+ return f.numer == g.numer and f.denom == g.denom
+ else:
+ return f.numer == g and f.denom == f.field.ring.one
+
+ def __ne__(f, g):
+ return not f == g
+
+ def __bool__(f):
+ return bool(f.numer)
+
+ def sort_key(self):
+ return (self.denom.sort_key(), self.numer.sort_key())
+
+ def _cmp(f1, f2, op):
+ if isinstance(f2, f1.field.dtype):
+ return op(f1.sort_key(), f2.sort_key())
+ else:
+ return NotImplemented
+
+ def __lt__(f1, f2):
+ return f1._cmp(f2, lt)
+ def __le__(f1, f2):
+ return f1._cmp(f2, le)
+ def __gt__(f1, f2):
+ return f1._cmp(f2, gt)
+ def __ge__(f1, f2):
+ return f1._cmp(f2, ge)
+
+ def __pos__(f):
+ """Negate all coefficients in ``f``. """
+ return f.raw_new(f.numer, f.denom)
+
+ def __neg__(f):
+ """Negate all coefficients in ``f``. """
+ return f.raw_new(-f.numer, f.denom)
+
+ def _extract_ground(self, element):
+ domain = self.field.domain
+
+ try:
+ element = domain.convert(element)
+ except CoercionFailed:
+ if not domain.is_Field and domain.has_assoc_Field:
+ ground_field = domain.get_field()
+
+ try:
+ element = ground_field.convert(element)
+ except CoercionFailed:
+ pass
+ else:
+ return -1, ground_field.numer(element), ground_field.denom(element)
+
+ return 0, None, None
+ else:
+ return 1, element, None
+
+ def __add__(f, g):
+ """Add rational functions ``f`` and ``g``. """
+ field = f.field
+
+ if not g:
+ return f
+ elif not f:
+ return g
+ elif isinstance(g, field.dtype):
+ if f.denom == g.denom:
+ return f.new(f.numer + g.numer, f.denom)
+ else:
+ return f.new(f.numer*g.denom + f.denom*g.numer, f.denom*g.denom)
+ elif isinstance(g, field.ring.dtype):
+ return f.new(f.numer + f.denom*g, f.denom)
+ else:
+ if isinstance(g, FracElement):
+ if isinstance(field.domain, FractionField) and field.domain.field == g.field:
+ pass
+ elif isinstance(g.field.domain, FractionField) and g.field.domain.field == field:
+ return g.__radd__(f)
+ else:
+ return NotImplemented
+ elif isinstance(g, PolyElement):
+ if isinstance(field.domain, PolynomialRing) and field.domain.ring == g.ring:
+ pass
+ else:
+ return g.__radd__(f)
+
+ return f.__radd__(g)
+
+ def __radd__(f, c):
+ if isinstance(c, f.field.ring.dtype):
+ return f.new(f.numer + f.denom*c, f.denom)
+
+ op, g_numer, g_denom = f._extract_ground(c)
+
+ if op == 1:
+ return f.new(f.numer + f.denom*g_numer, f.denom)
+ elif not op:
+ return NotImplemented
+ else:
+ return f.new(f.numer*g_denom + f.denom*g_numer, f.denom*g_denom)
+
+ def __sub__(f, g):
+ """Subtract rational functions ``f`` and ``g``. """
+ field = f.field
+
+ if not g:
+ return f
+ elif not f:
+ return -g
+ elif isinstance(g, field.dtype):
+ if f.denom == g.denom:
+ return f.new(f.numer - g.numer, f.denom)
+ else:
+ return f.new(f.numer*g.denom - f.denom*g.numer, f.denom*g.denom)
+ elif isinstance(g, field.ring.dtype):
+ return f.new(f.numer - f.denom*g, f.denom)
+ else:
+ if isinstance(g, FracElement):
+ if isinstance(field.domain, FractionField) and field.domain.field == g.field:
+ pass
+ elif isinstance(g.field.domain, FractionField) and g.field.domain.field == field:
+ return g.__rsub__(f)
+ else:
+ return NotImplemented
+ elif isinstance(g, PolyElement):
+ if isinstance(field.domain, PolynomialRing) and field.domain.ring == g.ring:
+ pass
+ else:
+ return g.__rsub__(f)
+
+ op, g_numer, g_denom = f._extract_ground(g)
+
+ if op == 1:
+ return f.new(f.numer - f.denom*g_numer, f.denom)
+ elif not op:
+ return NotImplemented
+ else:
+ return f.new(f.numer*g_denom - f.denom*g_numer, f.denom*g_denom)
+
+ def __rsub__(f, c):
+ if isinstance(c, f.field.ring.dtype):
+ return f.new(-f.numer + f.denom*c, f.denom)
+
+ op, g_numer, g_denom = f._extract_ground(c)
+
+ if op == 1:
+ return f.new(-f.numer + f.denom*g_numer, f.denom)
+ elif not op:
+ return NotImplemented
+ else:
+ return f.new(-f.numer*g_denom + f.denom*g_numer, f.denom*g_denom)
+
+ def __mul__(f, g):
+ """Multiply rational functions ``f`` and ``g``. """
+ field = f.field
+
+ if not f or not g:
+ return field.zero
+ elif isinstance(g, field.dtype):
+ return f.new(f.numer*g.numer, f.denom*g.denom)
+ elif isinstance(g, field.ring.dtype):
+ return f.new(f.numer*g, f.denom)
+ else:
+ if isinstance(g, FracElement):
+ if isinstance(field.domain, FractionField) and field.domain.field == g.field:
+ pass
+ elif isinstance(g.field.domain, FractionField) and g.field.domain.field == field:
+ return g.__rmul__(f)
+ else:
+ return NotImplemented
+ elif isinstance(g, PolyElement):
+ if isinstance(field.domain, PolynomialRing) and field.domain.ring == g.ring:
+ pass
+ else:
+ return g.__rmul__(f)
+
+ return f.__rmul__(g)
+
+ def __rmul__(f, c):
+ if isinstance(c, f.field.ring.dtype):
+ return f.new(f.numer*c, f.denom)
+
+ op, g_numer, g_denom = f._extract_ground(c)
+
+ if op == 1:
+ return f.new(f.numer*g_numer, f.denom)
+ elif not op:
+ return NotImplemented
+ else:
+ return f.new(f.numer*g_numer, f.denom*g_denom)
+
+ def __truediv__(f, g):
+ """Computes quotient of fractions ``f`` and ``g``. """
+ field = f.field
+
+ if not g:
+ raise ZeroDivisionError
+ elif isinstance(g, field.dtype):
+ return f.new(f.numer*g.denom, f.denom*g.numer)
+ elif isinstance(g, field.ring.dtype):
+ return f.new(f.numer, f.denom*g)
+ else:
+ if isinstance(g, FracElement):
+ if isinstance(field.domain, FractionField) and field.domain.field == g.field:
+ pass
+ elif isinstance(g.field.domain, FractionField) and g.field.domain.field == field:
+ return g.__rtruediv__(f)
+ else:
+ return NotImplemented
+ elif isinstance(g, PolyElement):
+ if isinstance(field.domain, PolynomialRing) and field.domain.ring == g.ring:
+ pass
+ else:
+ return g.__rtruediv__(f)
+
+ op, g_numer, g_denom = f._extract_ground(g)
+
+ if op == 1:
+ return f.new(f.numer, f.denom*g_numer)
+ elif not op:
+ return NotImplemented
+ else:
+ return f.new(f.numer*g_denom, f.denom*g_numer)
+
+ def __rtruediv__(f, c):
+ if not f:
+ raise ZeroDivisionError
+ elif isinstance(c, f.field.ring.dtype):
+ return f.new(f.denom*c, f.numer)
+
+ op, g_numer, g_denom = f._extract_ground(c)
+
+ if op == 1:
+ return f.new(f.denom*g_numer, f.numer)
+ elif not op:
+ return NotImplemented
+ else:
+ return f.new(f.denom*g_numer, f.numer*g_denom)
+
+ def __pow__(f, n):
+ """Raise ``f`` to a non-negative power ``n``. """
+ if n >= 0:
+ return f.raw_new(f.numer**n, f.denom**n)
+ elif not f:
+ raise ZeroDivisionError
+ else:
+ return f.raw_new(f.denom**-n, f.numer**-n)
+
+ def diff(f, x):
+ """Computes partial derivative in ``x``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.fields import field
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y, z = field("x,y,z", ZZ)
+ >>> ((x**2 + y)/(z + 1)).diff(x)
+ 2*x/(z + 1)
+
+ """
+ x = x.to_poly()
+ return f.new(f.numer.diff(x)*f.denom - f.numer*f.denom.diff(x), f.denom**2)
+
+ def __call__(f, *values):
+ if 0 < len(values) <= f.field.ngens:
+ return f.evaluate(list(zip(f.field.gens, values)))
+ else:
+ raise ValueError("expected at least 1 and at most %s values, got %s" % (f.field.ngens, len(values)))
+
+ def evaluate(f, x, a=None):
+ if isinstance(x, list) and a is None:
+ x = [ (X.to_poly(), a) for X, a in x ]
+ numer, denom = f.numer.evaluate(x), f.denom.evaluate(x)
+ else:
+ x = x.to_poly()
+ numer, denom = f.numer.evaluate(x, a), f.denom.evaluate(x, a)
+
+ field = numer.ring.to_field()
+ return field.new(numer, denom)
+
+ def subs(f, x, a=None):
+ if isinstance(x, list) and a is None:
+ x = [ (X.to_poly(), a) for X, a in x ]
+ numer, denom = f.numer.subs(x), f.denom.subs(x)
+ else:
+ x = x.to_poly()
+ numer, denom = f.numer.subs(x, a), f.denom.subs(x, a)
+
+ return f.new(numer, denom)
+
+ def compose(f, x, a=None):
+ raise NotImplementedError
diff --git a/lib/python3.12/site-packages/sympy/polys/groebnertools.py b/lib/python3.12/site-packages/sympy/polys/groebnertools.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc5c2f228ab4f4182e4c8fff68d974aa25c9d531
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/groebnertools.py
@@ -0,0 +1,862 @@
+"""Groebner bases algorithms. """
+
+
+from sympy.core.symbol import Dummy
+from sympy.polys.monomials import monomial_mul, monomial_lcm, monomial_divides, term_div
+from sympy.polys.orderings import lex
+from sympy.polys.polyerrors import DomainError
+from sympy.polys.polyconfig import query
+
+def groebner(seq, ring, method=None):
+ """
+ Computes Groebner basis for a set of polynomials in `K[X]`.
+
+ Wrapper around the (default) improved Buchberger and the other algorithms
+ for computing Groebner bases. The choice of algorithm can be changed via
+ ``method`` argument or :func:`sympy.polys.polyconfig.setup`, where
+ ``method`` can be either ``buchberger`` or ``f5b``.
+
+ """
+ if method is None:
+ method = query('groebner')
+
+ _groebner_methods = {
+ 'buchberger': _buchberger,
+ 'f5b': _f5b,
+ }
+
+ try:
+ _groebner = _groebner_methods[method]
+ except KeyError:
+ raise ValueError("'%s' is not a valid Groebner bases algorithm (valid are 'buchberger' and 'f5b')" % method)
+
+ domain, orig = ring.domain, None
+
+ if not domain.is_Field or not domain.has_assoc_Field:
+ try:
+ orig, ring = ring, ring.clone(domain=domain.get_field())
+ except DomainError:
+ raise DomainError("Cannot compute a Groebner basis over %s" % domain)
+ else:
+ seq = [ s.set_ring(ring) for s in seq ]
+
+ G = _groebner(seq, ring)
+
+ if orig is not None:
+ G = [ g.clear_denoms()[1].set_ring(orig) for g in G ]
+
+ return G
+
+def _buchberger(f, ring):
+ """
+ Computes Groebner basis for a set of polynomials in `K[X]`.
+
+ Given a set of multivariate polynomials `F`, finds another
+ set `G`, such that Ideal `F = Ideal G` and `G` is a reduced
+ Groebner basis.
+
+ The resulting basis is unique and has monic generators if the
+ ground domains is a field. Otherwise the result is non-unique
+ but Groebner bases over e.g. integers can be computed (if the
+ input polynomials are monic).
+
+ Groebner bases can be used to choose specific generators for a
+ polynomial ideal. Because these bases are unique you can check
+ for ideal equality by comparing the Groebner bases. To see if
+ one polynomial lies in an ideal, divide by the elements in the
+ base and see if the remainder vanishes.
+
+ They can also be used to solve systems of polynomial equations
+ as, by choosing lexicographic ordering, you can eliminate one
+ variable at a time, provided that the ideal is zero-dimensional
+ (finite number of solutions).
+
+ Notes
+ =====
+
+ Algorithm used: an improved version of Buchberger's algorithm
+ as presented in T. Becker, V. Weispfenning, Groebner Bases: A
+ Computational Approach to Commutative Algebra, Springer, 1993,
+ page 232.
+
+ References
+ ==========
+
+ .. [1] [Bose03]_
+ .. [2] [Giovini91]_
+ .. [3] [Ajwa95]_
+ .. [4] [Cox97]_
+
+ """
+ order = ring.order
+
+ monomial_mul = ring.monomial_mul
+ monomial_div = ring.monomial_div
+ monomial_lcm = ring.monomial_lcm
+
+ def select(P):
+ # normal selection strategy
+ # select the pair with minimum LCM(LM(f), LM(g))
+ pr = min(P, key=lambda pair: order(monomial_lcm(f[pair[0]].LM, f[pair[1]].LM)))
+ return pr
+
+ def normal(g, J):
+ h = g.rem([ f[j] for j in J ])
+
+ if not h:
+ return None
+ else:
+ h = h.monic()
+
+ if h not in I:
+ I[h] = len(f)
+ f.append(h)
+
+ return h.LM, I[h]
+
+ def update(G, B, ih):
+ # update G using the set of critical pairs B and h
+ # [BW] page 230
+ h = f[ih]
+ mh = h.LM
+
+ # filter new pairs (h, g), g in G
+ C = G.copy()
+ D = set()
+
+ while C:
+ # select a pair (h, g) by popping an element from C
+ ig = C.pop()
+ g = f[ig]
+ mg = g.LM
+ LCMhg = monomial_lcm(mh, mg)
+
+ def lcm_divides(ip):
+ # LCM(LM(h), LM(p)) divides LCM(LM(h), LM(g))
+ m = monomial_lcm(mh, f[ip].LM)
+ return monomial_div(LCMhg, m)
+
+ # HT(h) and HT(g) disjoint: mh*mg == LCMhg
+ if monomial_mul(mh, mg) == LCMhg or (
+ not any(lcm_divides(ipx) for ipx in C) and
+ not any(lcm_divides(pr[1]) for pr in D)):
+ D.add((ih, ig))
+
+ E = set()
+
+ while D:
+ # select h, g from D (h the same as above)
+ ih, ig = D.pop()
+ mg = f[ig].LM
+ LCMhg = monomial_lcm(mh, mg)
+
+ if not monomial_mul(mh, mg) == LCMhg:
+ E.add((ih, ig))
+
+ # filter old pairs
+ B_new = set()
+
+ while B:
+ # select g1, g2 from B (-> CP)
+ ig1, ig2 = B.pop()
+ mg1 = f[ig1].LM
+ mg2 = f[ig2].LM
+ LCM12 = monomial_lcm(mg1, mg2)
+
+ # if HT(h) does not divide lcm(HT(g1), HT(g2))
+ if not monomial_div(LCM12, mh) or \
+ monomial_lcm(mg1, mh) == LCM12 or \
+ monomial_lcm(mg2, mh) == LCM12:
+ B_new.add((ig1, ig2))
+
+ B_new |= E
+
+ # filter polynomials
+ G_new = set()
+
+ while G:
+ ig = G.pop()
+ mg = f[ig].LM
+
+ if not monomial_div(mg, mh):
+ G_new.add(ig)
+
+ G_new.add(ih)
+
+ return G_new, B_new
+ # end of update ################################
+
+ if not f:
+ return []
+
+ # replace f with a reduced list of initial polynomials; see [BW] page 203
+ f1 = f[:]
+
+ while True:
+ f = f1[:]
+ f1 = []
+
+ for i in range(len(f)):
+ p = f[i]
+ r = p.rem(f[:i])
+
+ if r:
+ f1.append(r.monic())
+
+ if f == f1:
+ break
+
+ I = {} # ip = I[p]; p = f[ip]
+ F = set() # set of indices of polynomials
+ G = set() # set of indices of intermediate would-be Groebner basis
+ CP = set() # set of pairs of indices of critical pairs
+
+ for i, h in enumerate(f):
+ I[h] = i
+ F.add(i)
+
+ #####################################
+ # algorithm GROEBNERNEWS2 in [BW] page 232
+
+ while F:
+ # select p with minimum monomial according to the monomial ordering
+ h = min([f[x] for x in F], key=lambda f: order(f.LM))
+ ih = I[h]
+ F.remove(ih)
+ G, CP = update(G, CP, ih)
+
+ # count the number of critical pairs which reduce to zero
+ reductions_to_zero = 0
+
+ while CP:
+ ig1, ig2 = select(CP)
+ CP.remove((ig1, ig2))
+
+ h = spoly(f[ig1], f[ig2], ring)
+ # ordering divisors is on average more efficient [Cox] page 111
+ G1 = sorted(G, key=lambda g: order(f[g].LM))
+ ht = normal(h, G1)
+
+ if ht:
+ G, CP = update(G, CP, ht[1])
+ else:
+ reductions_to_zero += 1
+
+ ######################################
+ # now G is a Groebner basis; reduce it
+ Gr = set()
+
+ for ig in G:
+ ht = normal(f[ig], G - {ig})
+
+ if ht:
+ Gr.add(ht[1])
+
+ Gr = [f[ig] for ig in Gr]
+
+ # order according to the monomial ordering
+ Gr = sorted(Gr, key=lambda f: order(f.LM), reverse=True)
+
+ return Gr
+
+def spoly(p1, p2, ring):
+ """
+ Compute LCM(LM(p1), LM(p2))/LM(p1)*p1 - LCM(LM(p1), LM(p2))/LM(p2)*p2
+ This is the S-poly provided p1 and p2 are monic
+ """
+ LM1 = p1.LM
+ LM2 = p2.LM
+ LCM12 = ring.monomial_lcm(LM1, LM2)
+ m1 = ring.monomial_div(LCM12, LM1)
+ m2 = ring.monomial_div(LCM12, LM2)
+ s1 = p1.mul_monom(m1)
+ s2 = p2.mul_monom(m2)
+ s = s1 - s2
+ return s
+
+# F5B
+
+# convenience functions
+
+
+def Sign(f):
+ return f[0]
+
+
+def Polyn(f):
+ return f[1]
+
+
+def Num(f):
+ return f[2]
+
+
+def sig(monomial, index):
+ return (monomial, index)
+
+
+def lbp(signature, polynomial, number):
+ return (signature, polynomial, number)
+
+# signature functions
+
+
+def sig_cmp(u, v, order):
+ """
+ Compare two signatures by extending the term order to K[X]^n.
+
+ u < v iff
+ - the index of v is greater than the index of u
+ or
+ - the index of v is equal to the index of u and u[0] < v[0] w.r.t. order
+
+ u > v otherwise
+ """
+ if u[1] > v[1]:
+ return -1
+ if u[1] == v[1]:
+ #if u[0] == v[0]:
+ # return 0
+ if order(u[0]) < order(v[0]):
+ return -1
+ return 1
+
+
+def sig_key(s, order):
+ """
+ Key for comparing two signatures.
+
+ s = (m, k), t = (n, l)
+
+ s < t iff [k > l] or [k == l and m < n]
+ s > t otherwise
+ """
+ return (-s[1], order(s[0]))
+
+
+def sig_mult(s, m):
+ """
+ Multiply a signature by a monomial.
+
+ The product of a signature (m, i) and a monomial n is defined as
+ (m * t, i).
+ """
+ return sig(monomial_mul(s[0], m), s[1])
+
+# labeled polynomial functions
+
+
+def lbp_sub(f, g):
+ """
+ Subtract labeled polynomial g from f.
+
+ The signature and number of the difference of f and g are signature
+ and number of the maximum of f and g, w.r.t. lbp_cmp.
+ """
+ if sig_cmp(Sign(f), Sign(g), Polyn(f).ring.order) < 0:
+ max_poly = g
+ else:
+ max_poly = f
+
+ ret = Polyn(f) - Polyn(g)
+
+ return lbp(Sign(max_poly), ret, Num(max_poly))
+
+
+def lbp_mul_term(f, cx):
+ """
+ Multiply a labeled polynomial with a term.
+
+ The product of a labeled polynomial (s, p, k) by a monomial is
+ defined as (m * s, m * p, k).
+ """
+ return lbp(sig_mult(Sign(f), cx[0]), Polyn(f).mul_term(cx), Num(f))
+
+
+def lbp_cmp(f, g):
+ """
+ Compare two labeled polynomials.
+
+ f < g iff
+ - Sign(f) < Sign(g)
+ or
+ - Sign(f) == Sign(g) and Num(f) > Num(g)
+
+ f > g otherwise
+ """
+ if sig_cmp(Sign(f), Sign(g), Polyn(f).ring.order) == -1:
+ return -1
+ if Sign(f) == Sign(g):
+ if Num(f) > Num(g):
+ return -1
+ #if Num(f) == Num(g):
+ # return 0
+ return 1
+
+
+def lbp_key(f):
+ """
+ Key for comparing two labeled polynomials.
+ """
+ return (sig_key(Sign(f), Polyn(f).ring.order), -Num(f))
+
+# algorithm and helper functions
+
+
+def critical_pair(f, g, ring):
+ """
+ Compute the critical pair corresponding to two labeled polynomials.
+
+ A critical pair is a tuple (um, f, vm, g), where um and vm are
+ terms such that um * f - vm * g is the S-polynomial of f and g (so,
+ wlog assume um * f > vm * g).
+ For performance sake, a critical pair is represented as a tuple
+ (Sign(um * f), um, f, Sign(vm * g), vm, g), since um * f creates
+ a new, relatively expensive object in memory, whereas Sign(um *
+ f) and um are lightweight and f (in the tuple) is a reference to
+ an already existing object in memory.
+ """
+ domain = ring.domain
+
+ ltf = Polyn(f).LT
+ ltg = Polyn(g).LT
+ lt = (monomial_lcm(ltf[0], ltg[0]), domain.one)
+
+ um = term_div(lt, ltf, domain)
+ vm = term_div(lt, ltg, domain)
+
+ # The full information is not needed (now), so only the product
+ # with the leading term is considered:
+ fr = lbp_mul_term(lbp(Sign(f), Polyn(f).leading_term(), Num(f)), um)
+ gr = lbp_mul_term(lbp(Sign(g), Polyn(g).leading_term(), Num(g)), vm)
+
+ # return in proper order, such that the S-polynomial is just
+ # u_first * f_first - u_second * f_second:
+ if lbp_cmp(fr, gr) == -1:
+ return (Sign(gr), vm, g, Sign(fr), um, f)
+ else:
+ return (Sign(fr), um, f, Sign(gr), vm, g)
+
+
+def cp_cmp(c, d):
+ """
+ Compare two critical pairs c and d.
+
+ c < d iff
+ - lbp(c[0], _, Num(c[2]) < lbp(d[0], _, Num(d[2])) (this
+ corresponds to um_c * f_c and um_d * f_d)
+ or
+ - lbp(c[0], _, Num(c[2]) >< lbp(d[0], _, Num(d[2])) and
+ lbp(c[3], _, Num(c[5])) < lbp(d[3], _, Num(d[5])) (this
+ corresponds to vm_c * g_c and vm_d * g_d)
+
+ c > d otherwise
+ """
+ zero = Polyn(c[2]).ring.zero
+
+ c0 = lbp(c[0], zero, Num(c[2]))
+ d0 = lbp(d[0], zero, Num(d[2]))
+
+ r = lbp_cmp(c0, d0)
+
+ if r == -1:
+ return -1
+ if r == 0:
+ c1 = lbp(c[3], zero, Num(c[5]))
+ d1 = lbp(d[3], zero, Num(d[5]))
+
+ r = lbp_cmp(c1, d1)
+
+ if r == -1:
+ return -1
+ #if r == 0:
+ # return 0
+ return 1
+
+
+def cp_key(c, ring):
+ """
+ Key for comparing critical pairs.
+ """
+ return (lbp_key(lbp(c[0], ring.zero, Num(c[2]))), lbp_key(lbp(c[3], ring.zero, Num(c[5]))))
+
+
+def s_poly(cp):
+ """
+ Compute the S-polynomial of a critical pair.
+
+ The S-polynomial of a critical pair cp is cp[1] * cp[2] - cp[4] * cp[5].
+ """
+ return lbp_sub(lbp_mul_term(cp[2], cp[1]), lbp_mul_term(cp[5], cp[4]))
+
+
+def is_rewritable_or_comparable(sign, num, B):
+ """
+ Check if a labeled polynomial is redundant by checking if its
+ signature and number imply rewritability or comparability.
+
+ (sign, num) is comparable if there exists a labeled polynomial
+ h in B, such that sign[1] (the index) is less than Sign(h)[1]
+ and sign[0] is divisible by the leading monomial of h.
+
+ (sign, num) is rewritable if there exists a labeled polynomial
+ h in B, such thatsign[1] is equal to Sign(h)[1], num < Num(h)
+ and sign[0] is divisible by Sign(h)[0].
+ """
+ for h in B:
+ # comparable
+ if sign[1] < Sign(h)[1]:
+ if monomial_divides(Polyn(h).LM, sign[0]):
+ return True
+
+ # rewritable
+ if sign[1] == Sign(h)[1]:
+ if num < Num(h):
+ if monomial_divides(Sign(h)[0], sign[0]):
+ return True
+ return False
+
+
+def f5_reduce(f, B):
+ """
+ F5-reduce a labeled polynomial f by B.
+
+ Continuously searches for non-zero labeled polynomial h in B, such
+ that the leading term lt_h of h divides the leading term lt_f of
+ f and Sign(lt_h * h) < Sign(f). If such a labeled polynomial h is
+ found, f gets replaced by f - lt_f / lt_h * h. If no such h can be
+ found or f is 0, f is no further F5-reducible and f gets returned.
+
+ A polynomial that is reducible in the usual sense need not be
+ F5-reducible, e.g.:
+
+ >>> from sympy.polys.groebnertools import lbp, sig, f5_reduce, Polyn
+ >>> from sympy.polys import ring, QQ, lex
+
+ >>> R, x,y,z = ring("x,y,z", QQ, lex)
+
+ >>> f = lbp(sig((1, 1, 1), 4), x, 3)
+ >>> g = lbp(sig((0, 0, 0), 2), x, 2)
+
+ >>> Polyn(f).rem([Polyn(g)])
+ 0
+ >>> f5_reduce(f, [g])
+ (((1, 1, 1), 4), x, 3)
+
+ """
+ order = Polyn(f).ring.order
+ domain = Polyn(f).ring.domain
+
+ if not Polyn(f):
+ return f
+
+ while True:
+ g = f
+
+ for h in B:
+ if Polyn(h):
+ if monomial_divides(Polyn(h).LM, Polyn(f).LM):
+ t = term_div(Polyn(f).LT, Polyn(h).LT, domain)
+ if sig_cmp(sig_mult(Sign(h), t[0]), Sign(f), order) < 0:
+ # The following check need not be done and is in general slower than without.
+ #if not is_rewritable_or_comparable(Sign(gp), Num(gp), B):
+ hp = lbp_mul_term(h, t)
+ f = lbp_sub(f, hp)
+ break
+
+ if g == f or not Polyn(f):
+ return f
+
+
+def _f5b(F, ring):
+ """
+ Computes a reduced Groebner basis for the ideal generated by F.
+
+ f5b is an implementation of the F5B algorithm by Yao Sun and
+ Dingkang Wang. Similarly to Buchberger's algorithm, the algorithm
+ proceeds by computing critical pairs, computing the S-polynomial,
+ reducing it and adjoining the reduced S-polynomial if it is not 0.
+
+ Unlike Buchberger's algorithm, each polynomial contains additional
+ information, namely a signature and a number. The signature
+ specifies the path of computation (i.e. from which polynomial in
+ the original basis was it derived and how), the number says when
+ the polynomial was added to the basis. With this information it
+ is (often) possible to decide if an S-polynomial will reduce to
+ 0 and can be discarded.
+
+ Optimizations include: Reducing the generators before computing
+ a Groebner basis, removing redundant critical pairs when a new
+ polynomial enters the basis and sorting the critical pairs and
+ the current basis.
+
+ Once a Groebner basis has been found, it gets reduced.
+
+ References
+ ==========
+
+ .. [1] Yao Sun, Dingkang Wang: "A New Proof for the Correctness of F5
+ (F5-Like) Algorithm", https://arxiv.org/abs/1004.0084 (specifically
+ v4)
+
+ .. [2] Thomas Becker, Volker Weispfenning, Groebner bases: A computational
+ approach to commutative algebra, 1993, p. 203, 216
+ """
+ order = ring.order
+
+ # reduce polynomials (like in Mario Pernici's implementation) (Becker, Weispfenning, p. 203)
+ B = F
+ while True:
+ F = B
+ B = []
+
+ for i in range(len(F)):
+ p = F[i]
+ r = p.rem(F[:i])
+
+ if r:
+ B.append(r)
+
+ if F == B:
+ break
+
+ # basis
+ B = [lbp(sig(ring.zero_monom, i + 1), F[i], i + 1) for i in range(len(F))]
+ B.sort(key=lambda f: order(Polyn(f).LM), reverse=True)
+
+ # critical pairs
+ CP = [critical_pair(B[i], B[j], ring) for i in range(len(B)) for j in range(i + 1, len(B))]
+ CP.sort(key=lambda cp: cp_key(cp, ring), reverse=True)
+
+ k = len(B)
+
+ reductions_to_zero = 0
+
+ while len(CP):
+ cp = CP.pop()
+
+ # discard redundant critical pairs:
+ if is_rewritable_or_comparable(cp[0], Num(cp[2]), B):
+ continue
+ if is_rewritable_or_comparable(cp[3], Num(cp[5]), B):
+ continue
+
+ s = s_poly(cp)
+
+ p = f5_reduce(s, B)
+
+ p = lbp(Sign(p), Polyn(p).monic(), k + 1)
+
+ if Polyn(p):
+ # remove old critical pairs, that become redundant when adding p:
+ indices = []
+ for i, cp in enumerate(CP):
+ if is_rewritable_or_comparable(cp[0], Num(cp[2]), [p]):
+ indices.append(i)
+ elif is_rewritable_or_comparable(cp[3], Num(cp[5]), [p]):
+ indices.append(i)
+
+ for i in reversed(indices):
+ del CP[i]
+
+ # only add new critical pairs that are not made redundant by p:
+ for g in B:
+ if Polyn(g):
+ cp = critical_pair(p, g, ring)
+ if is_rewritable_or_comparable(cp[0], Num(cp[2]), [p]):
+ continue
+ elif is_rewritable_or_comparable(cp[3], Num(cp[5]), [p]):
+ continue
+
+ CP.append(cp)
+
+ # sort (other sorting methods/selection strategies were not as successful)
+ CP.sort(key=lambda cp: cp_key(cp, ring), reverse=True)
+
+ # insert p into B:
+ m = Polyn(p).LM
+ if order(m) <= order(Polyn(B[-1]).LM):
+ B.append(p)
+ else:
+ for i, q in enumerate(B):
+ if order(m) > order(Polyn(q).LM):
+ B.insert(i, p)
+ break
+
+ k += 1
+
+ #print(len(B), len(CP), "%d critical pairs removed" % len(indices))
+ else:
+ reductions_to_zero += 1
+
+ # reduce Groebner basis:
+ H = [Polyn(g).monic() for g in B]
+ H = red_groebner(H, ring)
+
+ return sorted(H, key=lambda f: order(f.LM), reverse=True)
+
+
+def red_groebner(G, ring):
+ """
+ Compute reduced Groebner basis, from BeckerWeispfenning93, p. 216
+
+ Selects a subset of generators, that already generate the ideal
+ and computes a reduced Groebner basis for them.
+ """
+ def reduction(P):
+ """
+ The actual reduction algorithm.
+ """
+ Q = []
+ for i, p in enumerate(P):
+ h = p.rem(P[:i] + P[i + 1:])
+ if h:
+ Q.append(h)
+
+ return [p.monic() for p in Q]
+
+ F = G
+ H = []
+
+ while F:
+ f0 = F.pop()
+
+ if not any(monomial_divides(f.LM, f0.LM) for f in F + H):
+ H.append(f0)
+
+ # Becker, Weispfenning, p. 217: H is Groebner basis of the ideal generated by G.
+ return reduction(H)
+
+
+def is_groebner(G, ring):
+ """
+ Check if G is a Groebner basis.
+ """
+ for i in range(len(G)):
+ for j in range(i + 1, len(G)):
+ s = spoly(G[i], G[j], ring)
+ s = s.rem(G)
+ if s:
+ return False
+
+ return True
+
+
+def is_minimal(G, ring):
+ """
+ Checks if G is a minimal Groebner basis.
+ """
+ order = ring.order
+ domain = ring.domain
+
+ G.sort(key=lambda g: order(g.LM))
+
+ for i, g in enumerate(G):
+ if g.LC != domain.one:
+ return False
+
+ for h in G[:i] + G[i + 1:]:
+ if monomial_divides(h.LM, g.LM):
+ return False
+
+ return True
+
+
+def is_reduced(G, ring):
+ """
+ Checks if G is a reduced Groebner basis.
+ """
+ order = ring.order
+ domain = ring.domain
+
+ G.sort(key=lambda g: order(g.LM))
+
+ for i, g in enumerate(G):
+ if g.LC != domain.one:
+ return False
+
+ for term in g.terms():
+ for h in G[:i] + G[i + 1:]:
+ if monomial_divides(h.LM, term[0]):
+ return False
+
+ return True
+
+def groebner_lcm(f, g):
+ """
+ Computes LCM of two polynomials using Groebner bases.
+
+ The LCM is computed as the unique generator of the intersection
+ of the two ideals generated by `f` and `g`. The approach is to
+ compute a Groebner basis with respect to lexicographic ordering
+ of `t*f` and `(1 - t)*g`, where `t` is an unrelated variable and
+ then filtering out the solution that does not contain `t`.
+
+ References
+ ==========
+
+ .. [1] [Cox97]_
+
+ """
+ if f.ring != g.ring:
+ raise ValueError("Values should be equal")
+
+ ring = f.ring
+ domain = ring.domain
+
+ if not f or not g:
+ return ring.zero
+
+ if len(f) <= 1 and len(g) <= 1:
+ monom = monomial_lcm(f.LM, g.LM)
+ coeff = domain.lcm(f.LC, g.LC)
+ return ring.term_new(monom, coeff)
+
+ fc, f = f.primitive()
+ gc, g = g.primitive()
+
+ lcm = domain.lcm(fc, gc)
+
+ f_terms = [ ((1,) + monom, coeff) for monom, coeff in f.terms() ]
+ g_terms = [ ((0,) + monom, coeff) for monom, coeff in g.terms() ] \
+ + [ ((1,) + monom,-coeff) for monom, coeff in g.terms() ]
+
+ t = Dummy("t")
+ t_ring = ring.clone(symbols=(t,) + ring.symbols, order=lex)
+
+ F = t_ring.from_terms(f_terms)
+ G = t_ring.from_terms(g_terms)
+
+ basis = groebner([F, G], t_ring)
+
+ def is_independent(h, j):
+ return not any(monom[j] for monom in h.monoms())
+
+ H = [ h for h in basis if is_independent(h, 0) ]
+
+ h_terms = [ (monom[1:], coeff*lcm) for monom, coeff in H[0].terms() ]
+ h = ring.from_terms(h_terms)
+
+ return h
+
+def groebner_gcd(f, g):
+ """Computes GCD of two polynomials using Groebner bases. """
+ if f.ring != g.ring:
+ raise ValueError("Values should be equal")
+ domain = f.ring.domain
+
+ if not domain.is_Field:
+ fc, f = f.primitive()
+ gc, g = g.primitive()
+ gcd = domain.gcd(fc, gc)
+
+ H = (f*g).quo([groebner_lcm(f, g)])
+
+ if len(H) != 1:
+ raise ValueError("Length should be 1")
+ h = H[0]
+
+ if not domain.is_Field:
+ return gcd*h
+ else:
+ return h.monic()
diff --git a/lib/python3.12/site-packages/sympy/polys/heuristicgcd.py b/lib/python3.12/site-packages/sympy/polys/heuristicgcd.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea9eeac952e88552d729f0bd3073dee21b6ab68b
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/heuristicgcd.py
@@ -0,0 +1,149 @@
+"""Heuristic polynomial GCD algorithm (HEUGCD). """
+
+from .polyerrors import HeuristicGCDFailed
+
+HEU_GCD_MAX = 6
+
+def heugcd(f, g):
+ """
+ Heuristic polynomial GCD in ``Z[X]``.
+
+ Given univariate polynomials ``f`` and ``g`` in ``Z[X]``, returns
+ their GCD and cofactors, i.e. polynomials ``h``, ``cff`` and ``cfg``
+ such that::
+
+ h = gcd(f, g), cff = quo(f, h) and cfg = quo(g, h)
+
+ The algorithm is purely heuristic which means it may fail to compute
+ the GCD. This will be signaled by raising an exception. In this case
+ you will need to switch to another GCD method.
+
+ The algorithm computes the polynomial GCD by evaluating polynomials
+ ``f`` and ``g`` at certain points and computing (fast) integer GCD
+ of those evaluations. The polynomial GCD is recovered from the integer
+ image by interpolation. The evaluation process reduces f and g variable
+ by variable into a large integer. The final step is to verify if the
+ interpolated polynomial is the correct GCD. This gives cofactors of
+ the input polynomials as a side effect.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.heuristicgcd import heugcd
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x,y, = ring("x,y", ZZ)
+
+ >>> f = x**2 + 2*x*y + y**2
+ >>> g = x**2 + x*y
+
+ >>> h, cff, cfg = heugcd(f, g)
+ >>> h, cff, cfg
+ (x + y, x + y, x)
+
+ >>> cff*h == f
+ True
+ >>> cfg*h == g
+ True
+
+ References
+ ==========
+
+ .. [1] [Liao95]_
+
+ """
+ assert f.ring == g.ring and f.ring.domain.is_ZZ
+
+ ring = f.ring
+ x0 = ring.gens[0]
+ domain = ring.domain
+
+ gcd, f, g = f.extract_ground(g)
+
+ f_norm = f.max_norm()
+ g_norm = g.max_norm()
+
+ B = domain(2*min(f_norm, g_norm) + 29)
+
+ x = max(min(B, 99*domain.sqrt(B)),
+ 2*min(f_norm // abs(f.LC),
+ g_norm // abs(g.LC)) + 4)
+
+ for i in range(0, HEU_GCD_MAX):
+ ff = f.evaluate(x0, x)
+ gg = g.evaluate(x0, x)
+
+ if ff and gg:
+ if ring.ngens == 1:
+ h, cff, cfg = domain.cofactors(ff, gg)
+ else:
+ h, cff, cfg = heugcd(ff, gg)
+
+ h = _gcd_interpolate(h, x, ring)
+ h = h.primitive()[1]
+
+ cff_, r = f.div(h)
+
+ if not r:
+ cfg_, r = g.div(h)
+
+ if not r:
+ h = h.mul_ground(gcd)
+ return h, cff_, cfg_
+
+ cff = _gcd_interpolate(cff, x, ring)
+
+ h, r = f.div(cff)
+
+ if not r:
+ cfg_, r = g.div(h)
+
+ if not r:
+ h = h.mul_ground(gcd)
+ return h, cff, cfg_
+
+ cfg = _gcd_interpolate(cfg, x, ring)
+
+ h, r = g.div(cfg)
+
+ if not r:
+ cff_, r = f.div(h)
+
+ if not r:
+ h = h.mul_ground(gcd)
+ return h, cff_, cfg
+
+ x = 73794*x * domain.sqrt(domain.sqrt(x)) // 27011
+
+ raise HeuristicGCDFailed('no luck')
+
+def _gcd_interpolate(h, x, ring):
+ """Interpolate polynomial GCD from integer GCD. """
+ f, i = ring.zero, 0
+
+ # TODO: don't expose poly repr implementation details
+ if ring.ngens == 1:
+ while h:
+ g = h % x
+ if g > x // 2: g -= x
+ h = (h - g) // x
+
+ # f += X**i*g
+ if g:
+ f[(i,)] = g
+ i += 1
+ else:
+ while h:
+ g = h.trunc_ground(x)
+ h = (h - g).quo_ground(x)
+
+ # f += X**i*g
+ if g:
+ for monom, coeff in g.iterterms():
+ f[(i,) + monom] = coeff
+ i += 1
+
+ if f.LC < 0:
+ return -f
+ else:
+ return f
diff --git a/lib/python3.12/site-packages/sympy/polys/modulargcd.py b/lib/python3.12/site-packages/sympy/polys/modulargcd.py
new file mode 100644
index 0000000000000000000000000000000000000000..20dfd33d919703873e9fb5d3039abb351297df61
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/modulargcd.py
@@ -0,0 +1,2277 @@
+from sympy.core.symbol import Dummy
+from sympy.ntheory import nextprime
+from sympy.ntheory.modular import crt
+from sympy.polys.domains import PolynomialRing
+from sympy.polys.galoistools import (
+ gf_gcd, gf_from_dict, gf_gcdex, gf_div, gf_lcm)
+from sympy.polys.polyerrors import ModularGCDFailed
+
+from mpmath import sqrt
+import random
+
+
+def _trivial_gcd(f, g):
+ """
+ Compute the GCD of two polynomials in trivial cases, i.e. when one
+ or both polynomials are zero.
+ """
+ ring = f.ring
+
+ if not (f or g):
+ return ring.zero, ring.zero, ring.zero
+ elif not f:
+ if g.LC < ring.domain.zero:
+ return -g, ring.zero, -ring.one
+ else:
+ return g, ring.zero, ring.one
+ elif not g:
+ if f.LC < ring.domain.zero:
+ return -f, -ring.one, ring.zero
+ else:
+ return f, ring.one, ring.zero
+ return None
+
+
+def _gf_gcd(fp, gp, p):
+ r"""
+ Compute the GCD of two univariate polynomials in `\mathbb{Z}_p[x]`.
+ """
+ dom = fp.ring.domain
+
+ while gp:
+ rem = fp
+ deg = gp.degree()
+ lcinv = dom.invert(gp.LC, p)
+
+ while True:
+ degrem = rem.degree()
+ if degrem < deg:
+ break
+ rem = (rem - gp.mul_monom((degrem - deg,)).mul_ground(lcinv * rem.LC)).trunc_ground(p)
+
+ fp = gp
+ gp = rem
+
+ return fp.mul_ground(dom.invert(fp.LC, p)).trunc_ground(p)
+
+
+def _degree_bound_univariate(f, g):
+ r"""
+ Compute an upper bound for the degree of the GCD of two univariate
+ integer polynomials `f` and `g`.
+
+ The function chooses a suitable prime `p` and computes the GCD of
+ `f` and `g` in `\mathbb{Z}_p[x]`. The choice of `p` guarantees that
+ the degree in `\mathbb{Z}_p[x]` is greater than or equal to the degree
+ in `\mathbb{Z}[x]`.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ univariate integer polynomial
+ g : PolyElement
+ univariate integer polynomial
+
+ """
+ gamma = f.ring.domain.gcd(f.LC, g.LC)
+ p = 1
+
+ p = nextprime(p)
+ while gamma % p == 0:
+ p = nextprime(p)
+
+ fp = f.trunc_ground(p)
+ gp = g.trunc_ground(p)
+ hp = _gf_gcd(fp, gp, p)
+ deghp = hp.degree()
+ return deghp
+
+
+def _chinese_remainder_reconstruction_univariate(hp, hq, p, q):
+ r"""
+ Construct a polynomial `h_{pq}` in `\mathbb{Z}_{p q}[x]` such that
+
+ .. math ::
+
+ h_{pq} = h_p \; \mathrm{mod} \, p
+
+ h_{pq} = h_q \; \mathrm{mod} \, q
+
+ for relatively prime integers `p` and `q` and polynomials
+ `h_p` and `h_q` in `\mathbb{Z}_p[x]` and `\mathbb{Z}_q[x]`
+ respectively.
+
+ The coefficients of the polynomial `h_{pq}` are computed with the
+ Chinese Remainder Theorem. The symmetric representation in
+ `\mathbb{Z}_p[x]`, `\mathbb{Z}_q[x]` and `\mathbb{Z}_{p q}[x]` is used.
+ It is assumed that `h_p` and `h_q` have the same degree.
+
+ Parameters
+ ==========
+
+ hp : PolyElement
+ univariate integer polynomial with coefficients in `\mathbb{Z}_p`
+ hq : PolyElement
+ univariate integer polynomial with coefficients in `\mathbb{Z}_q`
+ p : Integer
+ modulus of `h_p`, relatively prime to `q`
+ q : Integer
+ modulus of `h_q`, relatively prime to `p`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import _chinese_remainder_reconstruction_univariate
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x = ring("x", ZZ)
+ >>> p = 3
+ >>> q = 5
+
+ >>> hp = -x**3 - 1
+ >>> hq = 2*x**3 - 2*x**2 + x
+
+ >>> hpq = _chinese_remainder_reconstruction_univariate(hp, hq, p, q)
+ >>> hpq
+ 2*x**3 + 3*x**2 + 6*x + 5
+
+ >>> hpq.trunc_ground(p) == hp
+ True
+ >>> hpq.trunc_ground(q) == hq
+ True
+
+ """
+ n = hp.degree()
+ x = hp.ring.gens[0]
+ hpq = hp.ring.zero
+
+ for i in range(n+1):
+ hpq[(i,)] = crt([p, q], [hp.coeff(x**i), hq.coeff(x**i)], symmetric=True)[0]
+
+ hpq.strip_zero()
+ return hpq
+
+
+def modgcd_univariate(f, g):
+ r"""
+ Computes the GCD of two polynomials in `\mathbb{Z}[x]` using a modular
+ algorithm.
+
+ The algorithm computes the GCD of two univariate integer polynomials
+ `f` and `g` by computing the GCD in `\mathbb{Z}_p[x]` for suitable
+ primes `p` and then reconstructing the coefficients with the Chinese
+ Remainder Theorem. Trial division is only made for candidates which
+ are very likely the desired GCD.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ univariate integer polynomial
+ g : PolyElement
+ univariate integer polynomial
+
+ Returns
+ =======
+
+ h : PolyElement
+ GCD of the polynomials `f` and `g`
+ cff : PolyElement
+ cofactor of `f`, i.e. `\frac{f}{h}`
+ cfg : PolyElement
+ cofactor of `g`, i.e. `\frac{g}{h}`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import modgcd_univariate
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x = ring("x", ZZ)
+
+ >>> f = x**5 - 1
+ >>> g = x - 1
+
+ >>> h, cff, cfg = modgcd_univariate(f, g)
+ >>> h, cff, cfg
+ (x - 1, x**4 + x**3 + x**2 + x + 1, 1)
+
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ >>> f = 6*x**2 - 6
+ >>> g = 2*x**2 + 4*x + 2
+
+ >>> h, cff, cfg = modgcd_univariate(f, g)
+ >>> h, cff, cfg
+ (2*x + 2, 3*x - 3, x + 1)
+
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ References
+ ==========
+
+ 1. [Monagan00]_
+
+ """
+ assert f.ring == g.ring and f.ring.domain.is_ZZ
+
+ result = _trivial_gcd(f, g)
+ if result is not None:
+ return result
+
+ ring = f.ring
+
+ cf, f = f.primitive()
+ cg, g = g.primitive()
+ ch = ring.domain.gcd(cf, cg)
+
+ bound = _degree_bound_univariate(f, g)
+ if bound == 0:
+ return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
+
+ gamma = ring.domain.gcd(f.LC, g.LC)
+ m = 1
+ p = 1
+
+ while True:
+ p = nextprime(p)
+ while gamma % p == 0:
+ p = nextprime(p)
+
+ fp = f.trunc_ground(p)
+ gp = g.trunc_ground(p)
+ hp = _gf_gcd(fp, gp, p)
+ deghp = hp.degree()
+
+ if deghp > bound:
+ continue
+ elif deghp < bound:
+ m = 1
+ bound = deghp
+ continue
+
+ hp = hp.mul_ground(gamma).trunc_ground(p)
+ if m == 1:
+ m = p
+ hlastm = hp
+ continue
+
+ hm = _chinese_remainder_reconstruction_univariate(hp, hlastm, p, m)
+ m *= p
+
+ if not hm == hlastm:
+ hlastm = hm
+ continue
+
+ h = hm.quo_ground(hm.content())
+ fquo, frem = f.div(h)
+ gquo, grem = g.div(h)
+ if not frem and not grem:
+ if h.LC < 0:
+ ch = -ch
+ h = h.mul_ground(ch)
+ cff = fquo.mul_ground(cf // ch)
+ cfg = gquo.mul_ground(cg // ch)
+ return h, cff, cfg
+
+
+def _primitive(f, p):
+ r"""
+ Compute the content and the primitive part of a polynomial in
+ `\mathbb{Z}_p[x_0, \ldots, x_{k-2}, y] \cong \mathbb{Z}_p[y][x_0, \ldots, x_{k-2}]`.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ integer polynomial in `\mathbb{Z}_p[x0, \ldots, x{k-2}, y]`
+ p : Integer
+ modulus of `f`
+
+ Returns
+ =======
+
+ contf : PolyElement
+ integer polynomial in `\mathbb{Z}_p[y]`, content of `f`
+ ppf : PolyElement
+ primitive part of `f`, i.e. `\frac{f}{contf}`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import _primitive
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x, y = ring("x, y", ZZ)
+ >>> p = 3
+
+ >>> f = x**2*y**2 + x**2*y - y**2 - y
+ >>> _primitive(f, p)
+ (y**2 + y, x**2 - 1)
+
+ >>> R, x, y, z = ring("x, y, z", ZZ)
+
+ >>> f = x*y*z - y**2*z**2
+ >>> _primitive(f, p)
+ (z, x*y - y**2*z)
+
+ """
+ ring = f.ring
+ dom = ring.domain
+ k = ring.ngens
+
+ coeffs = {}
+ for monom, coeff in f.iterterms():
+ if monom[:-1] not in coeffs:
+ coeffs[monom[:-1]] = {}
+ coeffs[monom[:-1]][monom[-1]] = coeff
+
+ cont = []
+ for coeff in iter(coeffs.values()):
+ cont = gf_gcd(cont, gf_from_dict(coeff, p, dom), p, dom)
+
+ yring = ring.clone(symbols=ring.symbols[k-1])
+ contf = yring.from_dense(cont).trunc_ground(p)
+
+ return contf, f.quo(contf.set_ring(ring))
+
+
+def _deg(f):
+ r"""
+ Compute the degree of a multivariate polynomial
+ `f \in K[x_0, \ldots, x_{k-2}, y] \cong K[y][x_0, \ldots, x_{k-2}]`.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ polynomial in `K[x_0, \ldots, x_{k-2}, y]`
+
+ Returns
+ =======
+
+ degf : Integer tuple
+ degree of `f` in `x_0, \ldots, x_{k-2}`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import _deg
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x, y = ring("x, y", ZZ)
+
+ >>> f = x**2*y**2 + x**2*y - 1
+ >>> _deg(f)
+ (2,)
+
+ >>> R, x, y, z = ring("x, y, z", ZZ)
+
+ >>> f = x**2*y**2 + x**2*y - 1
+ >>> _deg(f)
+ (2, 2)
+
+ >>> f = x*y*z - y**2*z**2
+ >>> _deg(f)
+ (1, 1)
+
+ """
+ k = f.ring.ngens
+ degf = (0,) * (k-1)
+ for monom in f.itermonoms():
+ if monom[:-1] > degf:
+ degf = monom[:-1]
+ return degf
+
+
+def _LC(f):
+ r"""
+ Compute the leading coefficient of a multivariate polynomial
+ `f \in K[x_0, \ldots, x_{k-2}, y] \cong K[y][x_0, \ldots, x_{k-2}]`.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ polynomial in `K[x_0, \ldots, x_{k-2}, y]`
+
+ Returns
+ =======
+
+ lcf : PolyElement
+ polynomial in `K[y]`, leading coefficient of `f`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import _LC
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x, y = ring("x, y", ZZ)
+
+ >>> f = x**2*y**2 + x**2*y - 1
+ >>> _LC(f)
+ y**2 + y
+
+ >>> R, x, y, z = ring("x, y, z", ZZ)
+
+ >>> f = x**2*y**2 + x**2*y - 1
+ >>> _LC(f)
+ 1
+
+ >>> f = x*y*z - y**2*z**2
+ >>> _LC(f)
+ z
+
+ """
+ ring = f.ring
+ k = ring.ngens
+ yring = ring.clone(symbols=ring.symbols[k-1])
+ y = yring.gens[0]
+ degf = _deg(f)
+
+ lcf = yring.zero
+ for monom, coeff in f.iterterms():
+ if monom[:-1] == degf:
+ lcf += coeff*y**monom[-1]
+ return lcf
+
+
+def _swap(f, i):
+ """
+ Make the variable `x_i` the leading one in a multivariate polynomial `f`.
+ """
+ ring = f.ring
+ fswap = ring.zero
+ for monom, coeff in f.iterterms():
+ monomswap = (monom[i],) + monom[:i] + monom[i+1:]
+ fswap[monomswap] = coeff
+ return fswap
+
+
+def _degree_bound_bivariate(f, g):
+ r"""
+ Compute upper degree bounds for the GCD of two bivariate
+ integer polynomials `f` and `g`.
+
+ The GCD is viewed as a polynomial in `\mathbb{Z}[y][x]` and the
+ function returns an upper bound for its degree and one for the degree
+ of its content. This is done by choosing a suitable prime `p` and
+ computing the GCD of the contents of `f \; \mathrm{mod} \, p` and
+ `g \; \mathrm{mod} \, p`. The choice of `p` guarantees that the degree
+ of the content in `\mathbb{Z}_p[y]` is greater than or equal to the
+ degree in `\mathbb{Z}[y]`. To obtain the degree bound in the variable
+ `x`, the polynomials are evaluated at `y = a` for a suitable
+ `a \in \mathbb{Z}_p` and then their GCD in `\mathbb{Z}_p[x]` is
+ computed. If no such `a` exists, i.e. the degree in `\mathbb{Z}_p[x]`
+ is always smaller than the one in `\mathbb{Z}[y][x]`, then the bound is
+ set to the minimum of the degrees of `f` and `g` in `x`.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ bivariate integer polynomial
+ g : PolyElement
+ bivariate integer polynomial
+
+ Returns
+ =======
+
+ xbound : Integer
+ upper bound for the degree of the GCD of the polynomials `f` and
+ `g` in the variable `x`
+ ycontbound : Integer
+ upper bound for the degree of the content of the GCD of the
+ polynomials `f` and `g` in the variable `y`
+
+ References
+ ==========
+
+ 1. [Monagan00]_
+
+ """
+ ring = f.ring
+
+ gamma1 = ring.domain.gcd(f.LC, g.LC)
+ gamma2 = ring.domain.gcd(_swap(f, 1).LC, _swap(g, 1).LC)
+ badprimes = gamma1 * gamma2
+ p = 1
+
+ p = nextprime(p)
+ while badprimes % p == 0:
+ p = nextprime(p)
+
+ fp = f.trunc_ground(p)
+ gp = g.trunc_ground(p)
+ contfp, fp = _primitive(fp, p)
+ contgp, gp = _primitive(gp, p)
+ conthp = _gf_gcd(contfp, contgp, p) # polynomial in Z_p[y]
+ ycontbound = conthp.degree()
+
+ # polynomial in Z_p[y]
+ delta = _gf_gcd(_LC(fp), _LC(gp), p)
+
+ for a in range(p):
+ if not delta.evaluate(0, a) % p:
+ continue
+ fpa = fp.evaluate(1, a).trunc_ground(p)
+ gpa = gp.evaluate(1, a).trunc_ground(p)
+ hpa = _gf_gcd(fpa, gpa, p)
+ xbound = hpa.degree()
+ return xbound, ycontbound
+
+ return min(fp.degree(), gp.degree()), ycontbound
+
+
+def _chinese_remainder_reconstruction_multivariate(hp, hq, p, q):
+ r"""
+ Construct a polynomial `h_{pq}` in
+ `\mathbb{Z}_{p q}[x_0, \ldots, x_{k-1}]` such that
+
+ .. math ::
+
+ h_{pq} = h_p \; \mathrm{mod} \, p
+
+ h_{pq} = h_q \; \mathrm{mod} \, q
+
+ for relatively prime integers `p` and `q` and polynomials
+ `h_p` and `h_q` in `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]` and
+ `\mathbb{Z}_q[x_0, \ldots, x_{k-1}]` respectively.
+
+ The coefficients of the polynomial `h_{pq}` are computed with the
+ Chinese Remainder Theorem. The symmetric representation in
+ `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`,
+ `\mathbb{Z}_q[x_0, \ldots, x_{k-1}]` and
+ `\mathbb{Z}_{p q}[x_0, \ldots, x_{k-1}]` is used.
+
+ Parameters
+ ==========
+
+ hp : PolyElement
+ multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
+ hq : PolyElement
+ multivariate integer polynomial with coefficients in `\mathbb{Z}_q`
+ p : Integer
+ modulus of `h_p`, relatively prime to `q`
+ q : Integer
+ modulus of `h_q`, relatively prime to `p`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import _chinese_remainder_reconstruction_multivariate
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x, y = ring("x, y", ZZ)
+ >>> p = 3
+ >>> q = 5
+
+ >>> hp = x**3*y - x**2 - 1
+ >>> hq = -x**3*y - 2*x*y**2 + 2
+
+ >>> hpq = _chinese_remainder_reconstruction_multivariate(hp, hq, p, q)
+ >>> hpq
+ 4*x**3*y + 5*x**2 + 3*x*y**2 + 2
+
+ >>> hpq.trunc_ground(p) == hp
+ True
+ >>> hpq.trunc_ground(q) == hq
+ True
+
+ >>> R, x, y, z = ring("x, y, z", ZZ)
+ >>> p = 6
+ >>> q = 5
+
+ >>> hp = 3*x**4 - y**3*z + z
+ >>> hq = -2*x**4 + z
+
+ >>> hpq = _chinese_remainder_reconstruction_multivariate(hp, hq, p, q)
+ >>> hpq
+ 3*x**4 + 5*y**3*z + z
+
+ >>> hpq.trunc_ground(p) == hp
+ True
+ >>> hpq.trunc_ground(q) == hq
+ True
+
+ """
+ hpmonoms = set(hp.monoms())
+ hqmonoms = set(hq.monoms())
+ monoms = hpmonoms.intersection(hqmonoms)
+ hpmonoms.difference_update(monoms)
+ hqmonoms.difference_update(monoms)
+
+ zero = hp.ring.domain.zero
+
+ hpq = hp.ring.zero
+
+ if isinstance(hp.ring.domain, PolynomialRing):
+ crt_ = _chinese_remainder_reconstruction_multivariate
+ else:
+ def crt_(cp, cq, p, q):
+ return crt([p, q], [cp, cq], symmetric=True)[0]
+
+ for monom in monoms:
+ hpq[monom] = crt_(hp[monom], hq[monom], p, q)
+ for monom in hpmonoms:
+ hpq[monom] = crt_(hp[monom], zero, p, q)
+ for monom in hqmonoms:
+ hpq[monom] = crt_(zero, hq[monom], p, q)
+
+ return hpq
+
+
+def _interpolate_multivariate(evalpoints, hpeval, ring, i, p, ground=False):
+ r"""
+ Reconstruct a polynomial `h_p` in `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`
+ from a list of evaluation points in `\mathbb{Z}_p` and a list of
+ polynomials in
+ `\mathbb{Z}_p[x_0, \ldots, x_{i-1}, x_{i+1}, \ldots, x_{k-1}]`, which
+ are the images of `h_p` evaluated in the variable `x_i`.
+
+ It is also possible to reconstruct a parameter of the ground domain,
+ i.e. if `h_p` is a polynomial over `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`.
+ In this case, one has to set ``ground=True``.
+
+ Parameters
+ ==========
+
+ evalpoints : list of Integer objects
+ list of evaluation points in `\mathbb{Z}_p`
+ hpeval : list of PolyElement objects
+ list of polynomials in (resp. over)
+ `\mathbb{Z}_p[x_0, \ldots, x_{i-1}, x_{i+1}, \ldots, x_{k-1}]`,
+ images of `h_p` evaluated in the variable `x_i`
+ ring : PolyRing
+ `h_p` will be an element of this ring
+ i : Integer
+ index of the variable which has to be reconstructed
+ p : Integer
+ prime number, modulus of `h_p`
+ ground : Boolean
+ indicates whether `x_i` is in the ground domain, default is
+ ``False``
+
+ Returns
+ =======
+
+ hp : PolyElement
+ interpolated polynomial in (resp. over)
+ `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`
+
+ """
+ hp = ring.zero
+
+ if ground:
+ domain = ring.domain.domain
+ y = ring.domain.gens[i]
+ else:
+ domain = ring.domain
+ y = ring.gens[i]
+
+ for a, hpa in zip(evalpoints, hpeval):
+ numer = ring.one
+ denom = domain.one
+ for b in evalpoints:
+ if b == a:
+ continue
+
+ numer *= y - b
+ denom *= a - b
+
+ denom = domain.invert(denom, p)
+ coeff = numer.mul_ground(denom)
+ hp += hpa.set_ring(ring) * coeff
+
+ return hp.trunc_ground(p)
+
+
+def modgcd_bivariate(f, g):
+ r"""
+ Computes the GCD of two polynomials in `\mathbb{Z}[x, y]` using a
+ modular algorithm.
+
+ The algorithm computes the GCD of two bivariate integer polynomials
+ `f` and `g` by calculating the GCD in `\mathbb{Z}_p[x, y]` for
+ suitable primes `p` and then reconstructing the coefficients with the
+ Chinese Remainder Theorem. To compute the bivariate GCD over
+ `\mathbb{Z}_p`, the polynomials `f \; \mathrm{mod} \, p` and
+ `g \; \mathrm{mod} \, p` are evaluated at `y = a` for certain
+ `a \in \mathbb{Z}_p` and then their univariate GCD in `\mathbb{Z}_p[x]`
+ is computed. Interpolating those yields the bivariate GCD in
+ `\mathbb{Z}_p[x, y]`. To verify the result in `\mathbb{Z}[x, y]`, trial
+ division is done, but only for candidates which are very likely the
+ desired GCD.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ bivariate integer polynomial
+ g : PolyElement
+ bivariate integer polynomial
+
+ Returns
+ =======
+
+ h : PolyElement
+ GCD of the polynomials `f` and `g`
+ cff : PolyElement
+ cofactor of `f`, i.e. `\frac{f}{h}`
+ cfg : PolyElement
+ cofactor of `g`, i.e. `\frac{g}{h}`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import modgcd_bivariate
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x, y = ring("x, y", ZZ)
+
+ >>> f = x**2 - y**2
+ >>> g = x**2 + 2*x*y + y**2
+
+ >>> h, cff, cfg = modgcd_bivariate(f, g)
+ >>> h, cff, cfg
+ (x + y, x - y, x + y)
+
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ >>> f = x**2*y - x**2 - 4*y + 4
+ >>> g = x + 2
+
+ >>> h, cff, cfg = modgcd_bivariate(f, g)
+ >>> h, cff, cfg
+ (x + 2, x*y - x - 2*y + 2, 1)
+
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ References
+ ==========
+
+ 1. [Monagan00]_
+
+ """
+ assert f.ring == g.ring and f.ring.domain.is_ZZ
+
+ result = _trivial_gcd(f, g)
+ if result is not None:
+ return result
+
+ ring = f.ring
+
+ cf, f = f.primitive()
+ cg, g = g.primitive()
+ ch = ring.domain.gcd(cf, cg)
+
+ xbound, ycontbound = _degree_bound_bivariate(f, g)
+ if xbound == ycontbound == 0:
+ return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
+
+ fswap = _swap(f, 1)
+ gswap = _swap(g, 1)
+ degyf = fswap.degree()
+ degyg = gswap.degree()
+
+ ybound, xcontbound = _degree_bound_bivariate(fswap, gswap)
+ if ybound == xcontbound == 0:
+ return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
+
+ # TODO: to improve performance, choose the main variable here
+
+ gamma1 = ring.domain.gcd(f.LC, g.LC)
+ gamma2 = ring.domain.gcd(fswap.LC, gswap.LC)
+ badprimes = gamma1 * gamma2
+ m = 1
+ p = 1
+
+ while True:
+ p = nextprime(p)
+ while badprimes % p == 0:
+ p = nextprime(p)
+
+ fp = f.trunc_ground(p)
+ gp = g.trunc_ground(p)
+ contfp, fp = _primitive(fp, p)
+ contgp, gp = _primitive(gp, p)
+ conthp = _gf_gcd(contfp, contgp, p) # monic polynomial in Z_p[y]
+ degconthp = conthp.degree()
+
+ if degconthp > ycontbound:
+ continue
+ elif degconthp < ycontbound:
+ m = 1
+ ycontbound = degconthp
+ continue
+
+ # polynomial in Z_p[y]
+ delta = _gf_gcd(_LC(fp), _LC(gp), p)
+
+ degcontfp = contfp.degree()
+ degcontgp = contgp.degree()
+ degdelta = delta.degree()
+
+ N = min(degyf - degcontfp, degyg - degcontgp,
+ ybound - ycontbound + degdelta) + 1
+
+ if p < N:
+ continue
+
+ n = 0
+ evalpoints = []
+ hpeval = []
+ unlucky = False
+
+ for a in range(p):
+ deltaa = delta.evaluate(0, a)
+ if not deltaa % p:
+ continue
+
+ fpa = fp.evaluate(1, a).trunc_ground(p)
+ gpa = gp.evaluate(1, a).trunc_ground(p)
+ hpa = _gf_gcd(fpa, gpa, p) # monic polynomial in Z_p[x]
+ deghpa = hpa.degree()
+
+ if deghpa > xbound:
+ continue
+ elif deghpa < xbound:
+ m = 1
+ xbound = deghpa
+ unlucky = True
+ break
+
+ hpa = hpa.mul_ground(deltaa).trunc_ground(p)
+ evalpoints.append(a)
+ hpeval.append(hpa)
+ n += 1
+
+ if n == N:
+ break
+
+ if unlucky:
+ continue
+ if n < N:
+ continue
+
+ hp = _interpolate_multivariate(evalpoints, hpeval, ring, 1, p)
+
+ hp = _primitive(hp, p)[1]
+ hp = hp * conthp.set_ring(ring)
+ degyhp = hp.degree(1)
+
+ if degyhp > ybound:
+ continue
+ if degyhp < ybound:
+ m = 1
+ ybound = degyhp
+ continue
+
+ hp = hp.mul_ground(gamma1).trunc_ground(p)
+ if m == 1:
+ m = p
+ hlastm = hp
+ continue
+
+ hm = _chinese_remainder_reconstruction_multivariate(hp, hlastm, p, m)
+ m *= p
+
+ if not hm == hlastm:
+ hlastm = hm
+ continue
+
+ h = hm.quo_ground(hm.content())
+ fquo, frem = f.div(h)
+ gquo, grem = g.div(h)
+ if not frem and not grem:
+ if h.LC < 0:
+ ch = -ch
+ h = h.mul_ground(ch)
+ cff = fquo.mul_ground(cf // ch)
+ cfg = gquo.mul_ground(cg // ch)
+ return h, cff, cfg
+
+
+def _modgcd_multivariate_p(f, g, p, degbound, contbound):
+ r"""
+ Compute the GCD of two polynomials in
+ `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`.
+
+ The algorithm reduces the problem step by step by evaluating the
+ polynomials `f` and `g` at `x_{k-1} = a` for suitable
+ `a \in \mathbb{Z}_p` and then calls itself recursively to compute the GCD
+ in `\mathbb{Z}_p[x_0, \ldots, x_{k-2}]`. If these recursive calls are
+ successful for enough evaluation points, the GCD in `k` variables is
+ interpolated, otherwise the algorithm returns ``None``. Every time a GCD
+ or a content is computed, their degrees are compared with the bounds. If
+ a degree greater then the bound is encountered, then the current call
+ returns ``None`` and a new evaluation point has to be chosen. If at some
+ point the degree is smaller, the correspondent bound is updated and the
+ algorithm fails.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
+ g : PolyElement
+ multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
+ p : Integer
+ prime number, modulus of `f` and `g`
+ degbound : list of Integer objects
+ ``degbound[i]`` is an upper bound for the degree of the GCD of `f`
+ and `g` in the variable `x_i`
+ contbound : list of Integer objects
+ ``contbound[i]`` is an upper bound for the degree of the content of
+ the GCD in `\mathbb{Z}_p[x_i][x_0, \ldots, x_{i-1}]`,
+ ``contbound[0]`` is not used can therefore be chosen
+ arbitrarily.
+
+ Returns
+ =======
+
+ h : PolyElement
+ GCD of the polynomials `f` and `g` or ``None``
+
+ References
+ ==========
+
+ 1. [Monagan00]_
+ 2. [Brown71]_
+
+ """
+ ring = f.ring
+ k = ring.ngens
+
+ if k == 1:
+ h = _gf_gcd(f, g, p).trunc_ground(p)
+ degh = h.degree()
+
+ if degh > degbound[0]:
+ return None
+ if degh < degbound[0]:
+ degbound[0] = degh
+ raise ModularGCDFailed
+
+ return h
+
+ degyf = f.degree(k-1)
+ degyg = g.degree(k-1)
+
+ contf, f = _primitive(f, p)
+ contg, g = _primitive(g, p)
+
+ conth = _gf_gcd(contf, contg, p) # polynomial in Z_p[y]
+
+ degcontf = contf.degree()
+ degcontg = contg.degree()
+ degconth = conth.degree()
+
+ if degconth > contbound[k-1]:
+ return None
+ if degconth < contbound[k-1]:
+ contbound[k-1] = degconth
+ raise ModularGCDFailed
+
+ lcf = _LC(f)
+ lcg = _LC(g)
+
+ delta = _gf_gcd(lcf, lcg, p) # polynomial in Z_p[y]
+
+ evaltest = delta
+
+ for i in range(k-1):
+ evaltest *= _gf_gcd(_LC(_swap(f, i)), _LC(_swap(g, i)), p)
+
+ degdelta = delta.degree()
+
+ N = min(degyf - degcontf, degyg - degcontg,
+ degbound[k-1] - contbound[k-1] + degdelta) + 1
+
+ if p < N:
+ return None
+
+ n = 0
+ d = 0
+ evalpoints = []
+ heval = []
+ points = list(range(p))
+
+ while points:
+ a = random.sample(points, 1)[0]
+ points.remove(a)
+
+ if not evaltest.evaluate(0, a) % p:
+ continue
+
+ deltaa = delta.evaluate(0, a) % p
+
+ fa = f.evaluate(k-1, a).trunc_ground(p)
+ ga = g.evaluate(k-1, a).trunc_ground(p)
+
+ # polynomials in Z_p[x_0, ..., x_{k-2}]
+ ha = _modgcd_multivariate_p(fa, ga, p, degbound, contbound)
+
+ if ha is None:
+ d += 1
+ if d > n:
+ return None
+ continue
+
+ if ha.is_ground:
+ h = conth.set_ring(ring).trunc_ground(p)
+ return h
+
+ ha = ha.mul_ground(deltaa).trunc_ground(p)
+
+ evalpoints.append(a)
+ heval.append(ha)
+ n += 1
+
+ if n == N:
+ h = _interpolate_multivariate(evalpoints, heval, ring, k-1, p)
+
+ h = _primitive(h, p)[1] * conth.set_ring(ring)
+ degyh = h.degree(k-1)
+
+ if degyh > degbound[k-1]:
+ return None
+ if degyh < degbound[k-1]:
+ degbound[k-1] = degyh
+ raise ModularGCDFailed
+
+ return h
+
+ return None
+
+
+def modgcd_multivariate(f, g):
+ r"""
+ Compute the GCD of two polynomials in `\mathbb{Z}[x_0, \ldots, x_{k-1}]`
+ using a modular algorithm.
+
+ The algorithm computes the GCD of two multivariate integer polynomials
+ `f` and `g` by calculating the GCD in
+ `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]` for suitable primes `p` and then
+ reconstructing the coefficients with the Chinese Remainder Theorem. To
+ compute the multivariate GCD over `\mathbb{Z}_p` the recursive
+ subroutine :func:`_modgcd_multivariate_p` is used. To verify the result in
+ `\mathbb{Z}[x_0, \ldots, x_{k-1}]`, trial division is done, but only for
+ candidates which are very likely the desired GCD.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ multivariate integer polynomial
+ g : PolyElement
+ multivariate integer polynomial
+
+ Returns
+ =======
+
+ h : PolyElement
+ GCD of the polynomials `f` and `g`
+ cff : PolyElement
+ cofactor of `f`, i.e. `\frac{f}{h}`
+ cfg : PolyElement
+ cofactor of `g`, i.e. `\frac{g}{h}`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import modgcd_multivariate
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x, y = ring("x, y", ZZ)
+
+ >>> f = x**2 - y**2
+ >>> g = x**2 + 2*x*y + y**2
+
+ >>> h, cff, cfg = modgcd_multivariate(f, g)
+ >>> h, cff, cfg
+ (x + y, x - y, x + y)
+
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ >>> R, x, y, z = ring("x, y, z", ZZ)
+
+ >>> f = x*z**2 - y*z**2
+ >>> g = x**2*z + z
+
+ >>> h, cff, cfg = modgcd_multivariate(f, g)
+ >>> h, cff, cfg
+ (z, x*z - y*z, x**2 + 1)
+
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ References
+ ==========
+
+ 1. [Monagan00]_
+ 2. [Brown71]_
+
+ See also
+ ========
+
+ _modgcd_multivariate_p
+
+ """
+ assert f.ring == g.ring and f.ring.domain.is_ZZ
+
+ result = _trivial_gcd(f, g)
+ if result is not None:
+ return result
+
+ ring = f.ring
+ k = ring.ngens
+
+ # divide out integer content
+ cf, f = f.primitive()
+ cg, g = g.primitive()
+ ch = ring.domain.gcd(cf, cg)
+
+ gamma = ring.domain.gcd(f.LC, g.LC)
+
+ badprimes = ring.domain.one
+ for i in range(k):
+ badprimes *= ring.domain.gcd(_swap(f, i).LC, _swap(g, i).LC)
+
+ degbound = [min(fdeg, gdeg) for fdeg, gdeg in zip(f.degrees(), g.degrees())]
+ contbound = list(degbound)
+
+ m = 1
+ p = 1
+
+ while True:
+ p = nextprime(p)
+ while badprimes % p == 0:
+ p = nextprime(p)
+
+ fp = f.trunc_ground(p)
+ gp = g.trunc_ground(p)
+
+ try:
+ # monic GCD of fp, gp in Z_p[x_0, ..., x_{k-2}, y]
+ hp = _modgcd_multivariate_p(fp, gp, p, degbound, contbound)
+ except ModularGCDFailed:
+ m = 1
+ continue
+
+ if hp is None:
+ continue
+
+ hp = hp.mul_ground(gamma).trunc_ground(p)
+ if m == 1:
+ m = p
+ hlastm = hp
+ continue
+
+ hm = _chinese_remainder_reconstruction_multivariate(hp, hlastm, p, m)
+ m *= p
+
+ if not hm == hlastm:
+ hlastm = hm
+ continue
+
+ h = hm.primitive()[1]
+ fquo, frem = f.div(h)
+ gquo, grem = g.div(h)
+ if not frem and not grem:
+ if h.LC < 0:
+ ch = -ch
+ h = h.mul_ground(ch)
+ cff = fquo.mul_ground(cf // ch)
+ cfg = gquo.mul_ground(cg // ch)
+ return h, cff, cfg
+
+
+def _gf_div(f, g, p):
+ r"""
+ Compute `\frac f g` modulo `p` for two univariate polynomials over
+ `\mathbb Z_p`.
+ """
+ ring = f.ring
+ densequo, denserem = gf_div(f.to_dense(), g.to_dense(), p, ring.domain)
+ return ring.from_dense(densequo), ring.from_dense(denserem)
+
+
+def _rational_function_reconstruction(c, p, m):
+ r"""
+ Reconstruct a rational function `\frac a b` in `\mathbb Z_p(t)` from
+
+ .. math::
+
+ c = \frac a b \; \mathrm{mod} \, m,
+
+ where `c` and `m` are polynomials in `\mathbb Z_p[t]` and `m` has
+ positive degree.
+
+ The algorithm is based on the Euclidean Algorithm. In general, `m` is
+ not irreducible, so it is possible that `b` is not invertible modulo
+ `m`. In that case ``None`` is returned.
+
+ Parameters
+ ==========
+
+ c : PolyElement
+ univariate polynomial in `\mathbb Z[t]`
+ p : Integer
+ prime number
+ m : PolyElement
+ modulus, not necessarily irreducible
+
+ Returns
+ =======
+
+ frac : FracElement
+ either `\frac a b` in `\mathbb Z(t)` or ``None``
+
+ References
+ ==========
+
+ 1. [Hoeij04]_
+
+ """
+ ring = c.ring
+ domain = ring.domain
+ M = m.degree()
+ N = M // 2
+ D = M - N - 1
+
+ r0, s0 = m, ring.zero
+ r1, s1 = c, ring.one
+
+ while r1.degree() > N:
+ quo = _gf_div(r0, r1, p)[0]
+ r0, r1 = r1, (r0 - quo*r1).trunc_ground(p)
+ s0, s1 = s1, (s0 - quo*s1).trunc_ground(p)
+
+ a, b = r1, s1
+ if b.degree() > D or _gf_gcd(b, m, p) != 1:
+ return None
+
+ lc = b.LC
+ if lc != 1:
+ lcinv = domain.invert(lc, p)
+ a = a.mul_ground(lcinv).trunc_ground(p)
+ b = b.mul_ground(lcinv).trunc_ground(p)
+
+ field = ring.to_field()
+
+ return field(a) / field(b)
+
+
+def _rational_reconstruction_func_coeffs(hm, p, m, ring, k):
+ r"""
+ Reconstruct every coefficient `c_h` of a polynomial `h` in
+ `\mathbb Z_p(t_k)[t_1, \ldots, t_{k-1}][x, z]` from the corresponding
+ coefficient `c_{h_m}` of a polynomial `h_m` in
+ `\mathbb Z_p[t_1, \ldots, t_k][x, z] \cong \mathbb Z_p[t_k][t_1, \ldots, t_{k-1}][x, z]`
+ such that
+
+ .. math::
+
+ c_{h_m} = c_h \; \mathrm{mod} \, m,
+
+ where `m \in \mathbb Z_p[t]`.
+
+ The reconstruction is based on the Euclidean Algorithm. In general, `m`
+ is not irreducible, so it is possible that this fails for some
+ coefficient. In that case ``None`` is returned.
+
+ Parameters
+ ==========
+
+ hm : PolyElement
+ polynomial in `\mathbb Z[t_1, \ldots, t_k][x, z]`
+ p : Integer
+ prime number, modulus of `\mathbb Z_p`
+ m : PolyElement
+ modulus, polynomial in `\mathbb Z[t]`, not necessarily irreducible
+ ring : PolyRing
+ `\mathbb Z(t_k)[t_1, \ldots, t_{k-1}][x, z]`, `h` will be an
+ element of this ring
+ k : Integer
+ index of the parameter `t_k` which will be reconstructed
+
+ Returns
+ =======
+
+ h : PolyElement
+ reconstructed polynomial in
+ `\mathbb Z(t_k)[t_1, \ldots, t_{k-1}][x, z]` or ``None``
+
+ See also
+ ========
+
+ _rational_function_reconstruction
+
+ """
+ h = ring.zero
+
+ for monom, coeff in hm.iterterms():
+ if k == 0:
+ coeffh = _rational_function_reconstruction(coeff, p, m)
+
+ if not coeffh:
+ return None
+
+ else:
+ coeffh = ring.domain.zero
+ for mon, c in coeff.drop_to_ground(k).iterterms():
+ ch = _rational_function_reconstruction(c, p, m)
+
+ if not ch:
+ return None
+
+ coeffh[mon] = ch
+
+ h[monom] = coeffh
+
+ return h
+
+
+def _gf_gcdex(f, g, p):
+ r"""
+ Extended Euclidean Algorithm for two univariate polynomials over
+ `\mathbb Z_p`.
+
+ Returns polynomials `s, t` and `h`, such that `h` is the GCD of `f` and
+ `g` and `sf + tg = h \; \mathrm{mod} \, p`.
+
+ """
+ ring = f.ring
+ s, t, h = gf_gcdex(f.to_dense(), g.to_dense(), p, ring.domain)
+ return ring.from_dense(s), ring.from_dense(t), ring.from_dense(h)
+
+
+def _trunc(f, minpoly, p):
+ r"""
+ Compute the reduced representation of a polynomial `f` in
+ `\mathbb Z_p[z] / (\check m_{\alpha}(z))[x]`
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ polynomial in `\mathbb Z[x, z]`
+ minpoly : PolyElement
+ polynomial `\check m_{\alpha} \in \mathbb Z[z]`, not necessarily
+ irreducible
+ p : Integer
+ prime number, modulus of `\mathbb Z_p`
+
+ Returns
+ =======
+
+ ftrunc : PolyElement
+ polynomial in `\mathbb Z[x, z]`, reduced modulo
+ `\check m_{\alpha}(z)` and `p`
+
+ """
+ ring = f.ring
+ minpoly = minpoly.set_ring(ring)
+ p_ = ring.ground_new(p)
+
+ return f.trunc_ground(p).rem([minpoly, p_]).trunc_ground(p)
+
+
+def _euclidean_algorithm(f, g, minpoly, p):
+ r"""
+ Compute the monic GCD of two univariate polynomials in
+ `\mathbb{Z}_p[z]/(\check m_{\alpha}(z))[x]` with the Euclidean
+ Algorithm.
+
+ In general, `\check m_{\alpha}(z)` is not irreducible, so it is possible
+ that some leading coefficient is not invertible modulo
+ `\check m_{\alpha}(z)`. In that case ``None`` is returned.
+
+ Parameters
+ ==========
+
+ f, g : PolyElement
+ polynomials in `\mathbb Z[x, z]`
+ minpoly : PolyElement
+ polynomial in `\mathbb Z[z]`, not necessarily irreducible
+ p : Integer
+ prime number, modulus of `\mathbb Z_p`
+
+ Returns
+ =======
+
+ h : PolyElement
+ GCD of `f` and `g` in `\mathbb Z[z, x]` or ``None``, coefficients
+ are in `\left[ -\frac{p-1} 2, \frac{p-1} 2 \right]`
+
+ """
+ ring = f.ring
+
+ f = _trunc(f, minpoly, p)
+ g = _trunc(g, minpoly, p)
+
+ while g:
+ rem = f
+ deg = g.degree(0) # degree in x
+ lcinv, _, gcd = _gf_gcdex(ring.dmp_LC(g), minpoly, p)
+
+ if not gcd == 1:
+ return None
+
+ while True:
+ degrem = rem.degree(0) # degree in x
+ if degrem < deg:
+ break
+ quo = (lcinv * ring.dmp_LC(rem)).set_ring(ring)
+ rem = _trunc(rem - g.mul_monom((degrem - deg, 0))*quo, minpoly, p)
+
+ f = g
+ g = rem
+
+ lcfinv = _gf_gcdex(ring.dmp_LC(f), minpoly, p)[0].set_ring(ring)
+
+ return _trunc(f * lcfinv, minpoly, p)
+
+
+def _trial_division(f, h, minpoly, p=None):
+ r"""
+ Check if `h` divides `f` in
+ `\mathbb K[t_1, \ldots, t_k][z]/(m_{\alpha}(z))`, where `\mathbb K` is
+ either `\mathbb Q` or `\mathbb Z_p`.
+
+ This algorithm is based on pseudo division and does not use any
+ fractions. By default `\mathbb K` is `\mathbb Q`, if a prime number `p`
+ is given, `\mathbb Z_p` is chosen instead.
+
+ Parameters
+ ==========
+
+ f, h : PolyElement
+ polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
+ minpoly : PolyElement
+ polynomial `m_{\alpha}(z)` in `\mathbb Z[t_1, \ldots, t_k][z]`
+ p : Integer or None
+ if `p` is given, `\mathbb K` is set to `\mathbb Z_p` instead of
+ `\mathbb Q`, default is ``None``
+
+ Returns
+ =======
+
+ rem : PolyElement
+ remainder of `\frac f h`
+
+ References
+ ==========
+
+ .. [1] [Hoeij02]_
+
+ """
+ ring = f.ring
+
+ zxring = ring.clone(symbols=(ring.symbols[1], ring.symbols[0]))
+
+ minpoly = minpoly.set_ring(ring)
+
+ rem = f
+
+ degrem = rem.degree()
+ degh = h.degree()
+ degm = minpoly.degree(1)
+
+ lch = _LC(h).set_ring(ring)
+ lcm = minpoly.LC
+
+ while rem and degrem >= degh:
+ # polynomial in Z[t_1, ..., t_k][z]
+ lcrem = _LC(rem).set_ring(ring)
+ rem = rem*lch - h.mul_monom((degrem - degh, 0))*lcrem
+ if p:
+ rem = rem.trunc_ground(p)
+ degrem = rem.degree(1)
+
+ while rem and degrem >= degm:
+ # polynomial in Z[t_1, ..., t_k][x]
+ lcrem = _LC(rem.set_ring(zxring)).set_ring(ring)
+ rem = rem.mul_ground(lcm) - minpoly.mul_monom((0, degrem - degm))*lcrem
+ if p:
+ rem = rem.trunc_ground(p)
+ degrem = rem.degree(1)
+
+ degrem = rem.degree()
+
+ return rem
+
+
+def _evaluate_ground(f, i, a):
+ r"""
+ Evaluate a polynomial `f` at `a` in the `i`-th variable of the ground
+ domain.
+ """
+ ring = f.ring.clone(domain=f.ring.domain.ring.drop(i))
+ fa = ring.zero
+
+ for monom, coeff in f.iterterms():
+ fa[monom] = coeff.evaluate(i, a)
+
+ return fa
+
+
+def _func_field_modgcd_p(f, g, minpoly, p):
+ r"""
+ Compute the GCD of two polynomials `f` and `g` in
+ `\mathbb Z_p(t_1, \ldots, t_k)[z]/(\check m_\alpha(z))[x]`.
+
+ The algorithm reduces the problem step by step by evaluating the
+ polynomials `f` and `g` at `t_k = a` for suitable `a \in \mathbb Z_p`
+ and then calls itself recursively to compute the GCD in
+ `\mathbb Z_p(t_1, \ldots, t_{k-1})[z]/(\check m_\alpha(z))[x]`. If these
+ recursive calls are successful, the GCD over `k` variables is
+ interpolated, otherwise the algorithm returns ``None``. After
+ interpolation, Rational Function Reconstruction is used to obtain the
+ correct coefficients. If this fails, a new evaluation point has to be
+ chosen, otherwise the desired polynomial is obtained by clearing
+ denominators. The result is verified with a fraction free trial
+ division.
+
+ Parameters
+ ==========
+
+ f, g : PolyElement
+ polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
+ minpoly : PolyElement
+ polynomial in `\mathbb Z[t_1, \ldots, t_k][z]`, not necessarily
+ irreducible
+ p : Integer
+ prime number, modulus of `\mathbb Z_p`
+
+ Returns
+ =======
+
+ h : PolyElement
+ primitive associate in `\mathbb Z[t_1, \ldots, t_k][x, z]` of the
+ GCD of the polynomials `f` and `g` or ``None``, coefficients are
+ in `\left[ -\frac{p-1} 2, \frac{p-1} 2 \right]`
+
+ References
+ ==========
+
+ 1. [Hoeij04]_
+
+ """
+ ring = f.ring
+ domain = ring.domain # Z[t_1, ..., t_k]
+
+ if isinstance(domain, PolynomialRing):
+ k = domain.ngens
+ else:
+ return _euclidean_algorithm(f, g, minpoly, p)
+
+ if k == 1:
+ qdomain = domain.ring.to_field()
+ else:
+ qdomain = domain.ring.drop_to_ground(k - 1)
+ qdomain = qdomain.clone(domain=qdomain.domain.ring.to_field())
+
+ qring = ring.clone(domain=qdomain) # = Z(t_k)[t_1, ..., t_{k-1}][x, z]
+
+ n = 1
+ d = 1
+
+ # polynomial in Z_p[t_1, ..., t_k][z]
+ gamma = ring.dmp_LC(f) * ring.dmp_LC(g)
+ # polynomial in Z_p[t_1, ..., t_k]
+ delta = minpoly.LC
+
+ evalpoints = []
+ heval = []
+ LMlist = []
+ points = list(range(p))
+
+ while points:
+ a = random.sample(points, 1)[0]
+ points.remove(a)
+
+ if k == 1:
+ test = delta.evaluate(k-1, a) % p == 0
+ else:
+ test = delta.evaluate(k-1, a).trunc_ground(p) == 0
+
+ if test:
+ continue
+
+ gammaa = _evaluate_ground(gamma, k-1, a)
+ minpolya = _evaluate_ground(minpoly, k-1, a)
+
+ if gammaa.rem([minpolya, gammaa.ring(p)]) == 0:
+ continue
+
+ fa = _evaluate_ground(f, k-1, a)
+ ga = _evaluate_ground(g, k-1, a)
+
+ # polynomial in Z_p[x, t_1, ..., t_{k-1}, z]/(minpoly)
+ ha = _func_field_modgcd_p(fa, ga, minpolya, p)
+
+ if ha is None:
+ d += 1
+ if d > n:
+ return None
+ continue
+
+ if ha == 1:
+ return ha
+
+ LM = [ha.degree()] + [0]*(k-1)
+ if k > 1:
+ for monom, coeff in ha.iterterms():
+ if monom[0] == LM[0] and coeff.LM > tuple(LM[1:]):
+ LM[1:] = coeff.LM
+
+ evalpoints_a = [a]
+ heval_a = [ha]
+ if k == 1:
+ m = qring.domain.get_ring().one
+ else:
+ m = qring.domain.domain.get_ring().one
+
+ t = m.ring.gens[0]
+
+ for b, hb, LMhb in zip(evalpoints, heval, LMlist):
+ if LMhb == LM:
+ evalpoints_a.append(b)
+ heval_a.append(hb)
+ m *= (t - b)
+
+ m = m.trunc_ground(p)
+ evalpoints.append(a)
+ heval.append(ha)
+ LMlist.append(LM)
+ n += 1
+
+ # polynomial in Z_p[t_1, ..., t_k][x, z]
+ h = _interpolate_multivariate(evalpoints_a, heval_a, ring, k-1, p, ground=True)
+
+ # polynomial in Z_p(t_k)[t_1, ..., t_{k-1}][x, z]
+ h = _rational_reconstruction_func_coeffs(h, p, m, qring, k-1)
+
+ if h is None:
+ continue
+
+ if k == 1:
+ dom = qring.domain.field
+ den = dom.ring.one
+
+ for coeff in h.itercoeffs():
+ den = dom.ring.from_dense(gf_lcm(den.to_dense(), coeff.denom.to_dense(),
+ p, dom.domain))
+
+ else:
+ dom = qring.domain.domain.field
+ den = dom.ring.one
+
+ for coeff in h.itercoeffs():
+ for c in coeff.itercoeffs():
+ den = dom.ring.from_dense(gf_lcm(den.to_dense(), c.denom.to_dense(),
+ p, dom.domain))
+
+ den = qring.domain_new(den.trunc_ground(p))
+ h = ring(h.mul_ground(den).as_expr()).trunc_ground(p)
+
+ if not _trial_division(f, h, minpoly, p) and not _trial_division(g, h, minpoly, p):
+ return h
+
+ return None
+
+
+def _integer_rational_reconstruction(c, m, domain):
+ r"""
+ Reconstruct a rational number `\frac a b` from
+
+ .. math::
+
+ c = \frac a b \; \mathrm{mod} \, m,
+
+ where `c` and `m` are integers.
+
+ The algorithm is based on the Euclidean Algorithm. In general, `m` is
+ not a prime number, so it is possible that `b` is not invertible modulo
+ `m`. In that case ``None`` is returned.
+
+ Parameters
+ ==========
+
+ c : Integer
+ `c = \frac a b \; \mathrm{mod} \, m`
+ m : Integer
+ modulus, not necessarily prime
+ domain : IntegerRing
+ `a, b, c` are elements of ``domain``
+
+ Returns
+ =======
+
+ frac : Rational
+ either `\frac a b` in `\mathbb Q` or ``None``
+
+ References
+ ==========
+
+ 1. [Wang81]_
+
+ """
+ if c < 0:
+ c += m
+
+ r0, s0 = m, domain.zero
+ r1, s1 = c, domain.one
+
+ bound = sqrt(m / 2) # still correct if replaced by ZZ.sqrt(m // 2) ?
+
+ while int(r1) >= bound:
+ quo = r0 // r1
+ r0, r1 = r1, r0 - quo*r1
+ s0, s1 = s1, s0 - quo*s1
+
+ if abs(int(s1)) >= bound:
+ return None
+
+ if s1 < 0:
+ a, b = -r1, -s1
+ elif s1 > 0:
+ a, b = r1, s1
+ else:
+ return None
+
+ field = domain.get_field()
+
+ return field(a) / field(b)
+
+
+def _rational_reconstruction_int_coeffs(hm, m, ring):
+ r"""
+ Reconstruct every rational coefficient `c_h` of a polynomial `h` in
+ `\mathbb Q[t_1, \ldots, t_k][x, z]` from the corresponding integer
+ coefficient `c_{h_m}` of a polynomial `h_m` in
+ `\mathbb Z[t_1, \ldots, t_k][x, z]` such that
+
+ .. math::
+
+ c_{h_m} = c_h \; \mathrm{mod} \, m,
+
+ where `m \in \mathbb Z`.
+
+ The reconstruction is based on the Euclidean Algorithm. In general,
+ `m` is not a prime number, so it is possible that this fails for some
+ coefficient. In that case ``None`` is returned.
+
+ Parameters
+ ==========
+
+ hm : PolyElement
+ polynomial in `\mathbb Z[t_1, \ldots, t_k][x, z]`
+ m : Integer
+ modulus, not necessarily prime
+ ring : PolyRing
+ `\mathbb Q[t_1, \ldots, t_k][x, z]`, `h` will be an element of this
+ ring
+
+ Returns
+ =======
+
+ h : PolyElement
+ reconstructed polynomial in `\mathbb Q[t_1, \ldots, t_k][x, z]` or
+ ``None``
+
+ See also
+ ========
+
+ _integer_rational_reconstruction
+
+ """
+ h = ring.zero
+
+ if isinstance(ring.domain, PolynomialRing):
+ reconstruction = _rational_reconstruction_int_coeffs
+ domain = ring.domain.ring
+ else:
+ reconstruction = _integer_rational_reconstruction
+ domain = hm.ring.domain
+
+ for monom, coeff in hm.iterterms():
+ coeffh = reconstruction(coeff, m, domain)
+
+ if not coeffh:
+ return None
+
+ h[monom] = coeffh
+
+ return h
+
+
+def _func_field_modgcd_m(f, g, minpoly):
+ r"""
+ Compute the GCD of two polynomials in
+ `\mathbb Q(t_1, \ldots, t_k)[z]/(m_{\alpha}(z))[x]` using a modular
+ algorithm.
+
+ The algorithm computes the GCD of two polynomials `f` and `g` by
+ calculating the GCD in
+ `\mathbb Z_p(t_1, \ldots, t_k)[z] / (\check m_{\alpha}(z))[x]` for
+ suitable primes `p` and the primitive associate `\check m_{\alpha}(z)`
+ of `m_{\alpha}(z)`. Then the coefficients are reconstructed with the
+ Chinese Remainder Theorem and Rational Reconstruction. To compute the
+ GCD over `\mathbb Z_p(t_1, \ldots, t_k)[z] / (\check m_{\alpha})[x]`,
+ the recursive subroutine ``_func_field_modgcd_p`` is used. To verify the
+ result in `\mathbb Q(t_1, \ldots, t_k)[z] / (m_{\alpha}(z))[x]`, a
+ fraction free trial division is used.
+
+ Parameters
+ ==========
+
+ f, g : PolyElement
+ polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
+ minpoly : PolyElement
+ irreducible polynomial in `\mathbb Z[t_1, \ldots, t_k][z]`
+
+ Returns
+ =======
+
+ h : PolyElement
+ the primitive associate in `\mathbb Z[t_1, \ldots, t_k][x, z]` of
+ the GCD of `f` and `g`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import _func_field_modgcd_m
+ >>> from sympy.polys import ring, ZZ
+
+ >>> R, x, z = ring('x, z', ZZ)
+ >>> minpoly = (z**2 - 2).drop(0)
+
+ >>> f = x**2 + 2*x*z + 2
+ >>> g = x + z
+ >>> _func_field_modgcd_m(f, g, minpoly)
+ x + z
+
+ >>> D, t = ring('t', ZZ)
+ >>> R, x, z = ring('x, z', D)
+ >>> minpoly = (z**2-3).drop(0)
+
+ >>> f = x**2 + (t + 1)*x*z + 3*t
+ >>> g = x*z + 3*t
+ >>> _func_field_modgcd_m(f, g, minpoly)
+ x + t*z
+
+ References
+ ==========
+
+ 1. [Hoeij04]_
+
+ See also
+ ========
+
+ _func_field_modgcd_p
+
+ """
+ ring = f.ring
+ domain = ring.domain
+
+ if isinstance(domain, PolynomialRing):
+ k = domain.ngens
+ QQdomain = domain.ring.clone(domain=domain.domain.get_field())
+ QQring = ring.clone(domain=QQdomain)
+ else:
+ k = 0
+ QQring = ring.clone(domain=ring.domain.get_field())
+
+ cf, f = f.primitive()
+ cg, g = g.primitive()
+
+ # polynomial in Z[t_1, ..., t_k][z]
+ gamma = ring.dmp_LC(f) * ring.dmp_LC(g)
+ # polynomial in Z[t_1, ..., t_k]
+ delta = minpoly.LC
+
+ p = 1
+ primes = []
+ hplist = []
+ LMlist = []
+
+ while True:
+ p = nextprime(p)
+
+ if gamma.trunc_ground(p) == 0:
+ continue
+
+ if k == 0:
+ test = (delta % p == 0)
+ else:
+ test = (delta.trunc_ground(p) == 0)
+
+ if test:
+ continue
+
+ fp = f.trunc_ground(p)
+ gp = g.trunc_ground(p)
+ minpolyp = minpoly.trunc_ground(p)
+
+ hp = _func_field_modgcd_p(fp, gp, minpolyp, p)
+
+ if hp is None:
+ continue
+
+ if hp == 1:
+ return ring.one
+
+ LM = [hp.degree()] + [0]*k
+ if k > 0:
+ for monom, coeff in hp.iterterms():
+ if monom[0] == LM[0] and coeff.LM > tuple(LM[1:]):
+ LM[1:] = coeff.LM
+
+ hm = hp
+ m = p
+
+ for q, hq, LMhq in zip(primes, hplist, LMlist):
+ if LMhq == LM:
+ hm = _chinese_remainder_reconstruction_multivariate(hq, hm, q, m)
+ m *= q
+
+ primes.append(p)
+ hplist.append(hp)
+ LMlist.append(LM)
+
+ hm = _rational_reconstruction_int_coeffs(hm, m, QQring)
+
+ if hm is None:
+ continue
+
+ if k == 0:
+ h = hm.clear_denoms()[1]
+ else:
+ den = domain.domain.one
+ for coeff in hm.itercoeffs():
+ den = domain.domain.lcm(den, coeff.clear_denoms()[0])
+ h = hm.mul_ground(den)
+
+ # convert back to Z[t_1, ..., t_k][x, z] from Q[t_1, ..., t_k][x, z]
+ h = h.set_ring(ring)
+ h = h.primitive()[1]
+
+ if not (_trial_division(f.mul_ground(cf), h, minpoly) or
+ _trial_division(g.mul_ground(cg), h, minpoly)):
+ return h
+
+
+def _to_ZZ_poly(f, ring):
+ r"""
+ Compute an associate of a polynomial
+ `f \in \mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]` in
+ `\mathbb Z[x_1, \ldots, x_{n-1}][z] / (\check m_{\alpha}(z))[x_0]`,
+ where `\check m_{\alpha}(z) \in \mathbb Z[z]` is the primitive associate
+ of the minimal polynomial `m_{\alpha}(z)` of `\alpha` over
+ `\mathbb Q`.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
+ ring : PolyRing
+ `\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
+
+ Returns
+ =======
+
+ f_ : PolyElement
+ associate of `f` in
+ `\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
+
+ """
+ f_ = ring.zero
+
+ if isinstance(ring.domain, PolynomialRing):
+ domain = ring.domain.domain
+ else:
+ domain = ring.domain
+
+ den = domain.one
+
+ for coeff in f.itercoeffs():
+ for c in coeff.to_list():
+ if c:
+ den = domain.lcm(den, c.denominator)
+
+ for monom, coeff in f.iterterms():
+ coeff = coeff.to_list()
+ m = ring.domain.one
+ if isinstance(ring.domain, PolynomialRing):
+ m = m.mul_monom(monom[1:])
+ n = len(coeff)
+
+ for i in range(n):
+ if coeff[i]:
+ c = domain.convert(coeff[i] * den) * m
+
+ if (monom[0], n-i-1) not in f_:
+ f_[(monom[0], n-i-1)] = c
+ else:
+ f_[(monom[0], n-i-1)] += c
+
+ return f_
+
+
+def _to_ANP_poly(f, ring):
+ r"""
+ Convert a polynomial
+ `f \in \mathbb Z[x_1, \ldots, x_{n-1}][z]/(\check m_{\alpha}(z))[x_0]`
+ to a polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`,
+ where `\check m_{\alpha}(z) \in \mathbb Z[z]` is the primitive associate
+ of the minimal polynomial `m_{\alpha}(z)` of `\alpha` over
+ `\mathbb Q`.
+
+ Parameters
+ ==========
+
+ f : PolyElement
+ polynomial in `\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
+ ring : PolyRing
+ `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
+
+ Returns
+ =======
+
+ f_ : PolyElement
+ polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
+
+ """
+ domain = ring.domain
+ f_ = ring.zero
+
+ if isinstance(f.ring.domain, PolynomialRing):
+ for monom, coeff in f.iterterms():
+ for mon, coef in coeff.iterterms():
+ m = (monom[0],) + mon
+ c = domain([domain.domain(coef)] + [0]*monom[1])
+
+ if m not in f_:
+ f_[m] = c
+ else:
+ f_[m] += c
+
+ else:
+ for monom, coeff in f.iterterms():
+ m = (monom[0],)
+ c = domain([domain.domain(coeff)] + [0]*monom[1])
+
+ if m not in f_:
+ f_[m] = c
+ else:
+ f_[m] += c
+
+ return f_
+
+
+def _minpoly_from_dense(minpoly, ring):
+ r"""
+ Change representation of the minimal polynomial from ``DMP`` to
+ ``PolyElement`` for a given ring.
+ """
+ minpoly_ = ring.zero
+
+ for monom, coeff in minpoly.terms():
+ minpoly_[monom] = ring.domain(coeff)
+
+ return minpoly_
+
+
+def _primitive_in_x0(f):
+ r"""
+ Compute the content in `x_0` and the primitive part of a polynomial `f`
+ in
+ `\mathbb Q(\alpha)[x_0, x_1, \ldots, x_{n-1}] \cong \mathbb Q(\alpha)[x_1, \ldots, x_{n-1}][x_0]`.
+ """
+ fring = f.ring
+ ring = fring.drop_to_ground(*range(1, fring.ngens))
+ dom = ring.domain.ring
+ f_ = ring(f.as_expr())
+ cont = dom.zero
+
+ for coeff in f_.itercoeffs():
+ cont = func_field_modgcd(cont, coeff)[0]
+ if cont == dom.one:
+ return cont, f
+
+ return cont, f.quo(cont.set_ring(fring))
+
+
+# TODO: add support for algebraic function fields
+def func_field_modgcd(f, g):
+ r"""
+ Compute the GCD of two polynomials `f` and `g` in
+ `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]` using a modular algorithm.
+
+ The algorithm first computes the primitive associate
+ `\check m_{\alpha}(z)` of the minimal polynomial `m_{\alpha}` in
+ `\mathbb{Z}[z]` and the primitive associates of `f` and `g` in
+ `\mathbb{Z}[x_1, \ldots, x_{n-1}][z]/(\check m_{\alpha})[x_0]`. Then it
+ computes the GCD in
+ `\mathbb Q(x_1, \ldots, x_{n-1})[z]/(m_{\alpha}(z))[x_0]`.
+ This is done by calculating the GCD in
+ `\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]` for
+ suitable primes `p` and then reconstructing the coefficients with the
+ Chinese Remainder Theorem and Rational Reconstuction. The GCD over
+ `\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]` is
+ computed with a recursive subroutine, which evaluates the polynomials at
+ `x_{n-1} = a` for suitable evaluation points `a \in \mathbb Z_p` and
+ then calls itself recursively until the ground domain does no longer
+ contain any parameters. For
+ `\mathbb{Z}_p[z]/(\check m_{\alpha}(z))[x_0]` the Euclidean Algorithm is
+ used. The results of those recursive calls are then interpolated and
+ Rational Function Reconstruction is used to obtain the correct
+ coefficients. The results, both in
+ `\mathbb Q(x_1, \ldots, x_{n-1})[z]/(m_{\alpha}(z))[x_0]` and
+ `\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]`, are
+ verified by a fraction free trial division.
+
+ Apart from the above GCD computation some GCDs in
+ `\mathbb Q(\alpha)[x_1, \ldots, x_{n-1}]` have to be calculated,
+ because treating the polynomials as univariate ones can result in
+ a spurious content of the GCD. For this ``func_field_modgcd`` is
+ called recursively.
+
+ Parameters
+ ==========
+
+ f, g : PolyElement
+ polynomials in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
+
+ Returns
+ =======
+
+ h : PolyElement
+ monic GCD of the polynomials `f` and `g`
+ cff : PolyElement
+ cofactor of `f`, i.e. `\frac f h`
+ cfg : PolyElement
+ cofactor of `g`, i.e. `\frac g h`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.modulargcd import func_field_modgcd
+ >>> from sympy.polys import AlgebraicField, QQ, ring
+ >>> from sympy import sqrt
+
+ >>> A = AlgebraicField(QQ, sqrt(2))
+ >>> R, x = ring('x', A)
+
+ >>> f = x**2 - 2
+ >>> g = x + sqrt(2)
+
+ >>> h, cff, cfg = func_field_modgcd(f, g)
+
+ >>> h == x + sqrt(2)
+ True
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ >>> R, x, y = ring('x, y', A)
+
+ >>> f = x**2 + 2*sqrt(2)*x*y + 2*y**2
+ >>> g = x + sqrt(2)*y
+
+ >>> h, cff, cfg = func_field_modgcd(f, g)
+
+ >>> h == x + sqrt(2)*y
+ True
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ >>> f = x + sqrt(2)*y
+ >>> g = x + y
+
+ >>> h, cff, cfg = func_field_modgcd(f, g)
+
+ >>> h == R.one
+ True
+ >>> cff * h == f
+ True
+ >>> cfg * h == g
+ True
+
+ References
+ ==========
+
+ 1. [Hoeij04]_
+
+ """
+ ring = f.ring
+ domain = ring.domain
+ n = ring.ngens
+
+ assert ring == g.ring and domain.is_Algebraic
+
+ result = _trivial_gcd(f, g)
+ if result is not None:
+ return result
+
+ z = Dummy('z')
+
+ ZZring = ring.clone(symbols=ring.symbols + (z,), domain=domain.domain.get_ring())
+
+ if n == 1:
+ f_ = _to_ZZ_poly(f, ZZring)
+ g_ = _to_ZZ_poly(g, ZZring)
+ minpoly = ZZring.drop(0).from_dense(domain.mod.to_list())
+
+ h = _func_field_modgcd_m(f_, g_, minpoly)
+ h = _to_ANP_poly(h, ring)
+
+ else:
+ # contx0f in Q(a)[x_1, ..., x_{n-1}], f in Q(a)[x_0, ..., x_{n-1}]
+ contx0f, f = _primitive_in_x0(f)
+ contx0g, g = _primitive_in_x0(g)
+ contx0h = func_field_modgcd(contx0f, contx0g)[0]
+
+ ZZring_ = ZZring.drop_to_ground(*range(1, n))
+
+ f_ = _to_ZZ_poly(f, ZZring_)
+ g_ = _to_ZZ_poly(g, ZZring_)
+ minpoly = _minpoly_from_dense(domain.mod, ZZring_.drop(0))
+
+ h = _func_field_modgcd_m(f_, g_, minpoly)
+ h = _to_ANP_poly(h, ring)
+
+ contx0h_, h = _primitive_in_x0(h)
+ h *= contx0h.set_ring(ring)
+ f *= contx0f.set_ring(ring)
+ g *= contx0g.set_ring(ring)
+
+ h = h.quo_ground(h.LC)
+
+ return h, f.quo(h), g.quo(h)
diff --git a/lib/python3.12/site-packages/sympy/polys/monomials.py b/lib/python3.12/site-packages/sympy/polys/monomials.py
new file mode 100644
index 0000000000000000000000000000000000000000..f464ba97f137931c29154b1bdcb8ec4f364919f8
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/monomials.py
@@ -0,0 +1,622 @@
+"""Tools and arithmetics for monomials of distributed polynomials. """
+
+
+from itertools import combinations_with_replacement, product
+from textwrap import dedent
+
+from sympy.core import Mul, S, Tuple, sympify
+from sympy.polys.polyerrors import ExactQuotientFailed
+from sympy.polys.polyutils import PicklableWithSlots, dict_from_expr
+from sympy.utilities import public
+from sympy.utilities.iterables import is_sequence, iterable
+
+@public
+def itermonomials(variables, max_degrees, min_degrees=None):
+ r"""
+ ``max_degrees`` and ``min_degrees`` are either both integers or both lists.
+ Unless otherwise specified, ``min_degrees`` is either ``0`` or
+ ``[0, ..., 0]``.
+
+ A generator of all monomials ``monom`` is returned, such that
+ either
+ ``min_degree <= total_degree(monom) <= max_degree``,
+ or
+ ``min_degrees[i] <= degree_list(monom)[i] <= max_degrees[i]``,
+ for all ``i``.
+
+ Case I. ``max_degrees`` and ``min_degrees`` are both integers
+ =============================================================
+
+ Given a set of variables $V$ and a min_degree $N$ and a max_degree $M$
+ generate a set of monomials of degree less than or equal to $N$ and greater
+ than or equal to $M$. The total number of monomials in commutative
+ variables is huge and is given by the following formula if $M = 0$:
+
+ .. math::
+ \frac{(\#V + N)!}{\#V! N!}
+
+ For example if we would like to generate a dense polynomial of
+ a total degree $N = 50$ and $M = 0$, which is the worst case, in 5
+ variables, assuming that exponents and all of coefficients are 32-bit long
+ and stored in an array we would need almost 80 GiB of memory! Fortunately
+ most polynomials, that we will encounter, are sparse.
+
+ Consider monomials in commutative variables $x$ and $y$
+ and non-commutative variables $a$ and $b$::
+
+ >>> from sympy import symbols
+ >>> from sympy.polys.monomials import itermonomials
+ >>> from sympy.polys.orderings import monomial_key
+ >>> from sympy.abc import x, y
+
+ >>> sorted(itermonomials([x, y], 2), key=monomial_key('grlex', [y, x]))
+ [1, x, y, x**2, x*y, y**2]
+
+ >>> sorted(itermonomials([x, y], 3), key=monomial_key('grlex', [y, x]))
+ [1, x, y, x**2, x*y, y**2, x**3, x**2*y, x*y**2, y**3]
+
+ >>> a, b = symbols('a, b', commutative=False)
+ >>> set(itermonomials([a, b, x], 2))
+ {1, a, a**2, b, b**2, x, x**2, a*b, b*a, x*a, x*b}
+
+ >>> sorted(itermonomials([x, y], 2, 1), key=monomial_key('grlex', [y, x]))
+ [x, y, x**2, x*y, y**2]
+
+ Case II. ``max_degrees`` and ``min_degrees`` are both lists
+ ===========================================================
+
+ If ``max_degrees = [d_1, ..., d_n]`` and
+ ``min_degrees = [e_1, ..., e_n]``, the number of monomials generated
+ is:
+
+ .. math::
+ (d_1 - e_1 + 1) (d_2 - e_2 + 1) \cdots (d_n - e_n + 1)
+
+ Let us generate all monomials ``monom`` in variables $x$ and $y$
+ such that ``[1, 2][i] <= degree_list(monom)[i] <= [2, 4][i]``,
+ ``i = 0, 1`` ::
+
+ >>> from sympy import symbols
+ >>> from sympy.polys.monomials import itermonomials
+ >>> from sympy.polys.orderings import monomial_key
+ >>> from sympy.abc import x, y
+
+ >>> sorted(itermonomials([x, y], [2, 4], [1, 2]), reverse=True, key=monomial_key('lex', [x, y]))
+ [x**2*y**4, x**2*y**3, x**2*y**2, x*y**4, x*y**3, x*y**2]
+ """
+ n = len(variables)
+ if is_sequence(max_degrees):
+ if len(max_degrees) != n:
+ raise ValueError('Argument sizes do not match')
+ if min_degrees is None:
+ min_degrees = [0]*n
+ elif not is_sequence(min_degrees):
+ raise ValueError('min_degrees is not a list')
+ else:
+ if len(min_degrees) != n:
+ raise ValueError('Argument sizes do not match')
+ if any(i < 0 for i in min_degrees):
+ raise ValueError("min_degrees cannot contain negative numbers")
+ total_degree = False
+ else:
+ max_degree = max_degrees
+ if max_degree < 0:
+ raise ValueError("max_degrees cannot be negative")
+ if min_degrees is None:
+ min_degree = 0
+ else:
+ if min_degrees < 0:
+ raise ValueError("min_degrees cannot be negative")
+ min_degree = min_degrees
+ total_degree = True
+ if total_degree:
+ if min_degree > max_degree:
+ return
+ if not variables or max_degree == 0:
+ yield S.One
+ return
+ # Force to list in case of passed tuple or other incompatible collection
+ variables = list(variables) + [S.One]
+ if all(variable.is_commutative for variable in variables):
+ monomials_list_comm = []
+ for item in combinations_with_replacement(variables, max_degree):
+ powers = dict.fromkeys(variables, 0)
+ for variable in item:
+ if variable != 1:
+ powers[variable] += 1
+ if sum(powers.values()) >= min_degree:
+ monomials_list_comm.append(Mul(*item))
+ yield from set(monomials_list_comm)
+ else:
+ monomials_list_non_comm = []
+ for item in product(variables, repeat=max_degree):
+ powers = dict.fromkeys(variables, 0)
+ for variable in item:
+ if variable != 1:
+ powers[variable] += 1
+ if sum(powers.values()) >= min_degree:
+ monomials_list_non_comm.append(Mul(*item))
+ yield from set(monomials_list_non_comm)
+ else:
+ if any(min_degrees[i] > max_degrees[i] for i in range(n)):
+ raise ValueError('min_degrees[i] must be <= max_degrees[i] for all i')
+ power_lists = []
+ for var, min_d, max_d in zip(variables, min_degrees, max_degrees):
+ power_lists.append([var**i for i in range(min_d, max_d + 1)])
+ for powers in product(*power_lists):
+ yield Mul(*powers)
+
+def monomial_count(V, N):
+ r"""
+ Computes the number of monomials.
+
+ The number of monomials is given by the following formula:
+
+ .. math::
+
+ \frac{(\#V + N)!}{\#V! N!}
+
+ where `N` is a total degree and `V` is a set of variables.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.monomials import itermonomials, monomial_count
+ >>> from sympy.polys.orderings import monomial_key
+ >>> from sympy.abc import x, y
+
+ >>> monomial_count(2, 2)
+ 6
+
+ >>> M = list(itermonomials([x, y], 2))
+
+ >>> sorted(M, key=monomial_key('grlex', [y, x]))
+ [1, x, y, x**2, x*y, y**2]
+ >>> len(M)
+ 6
+
+ """
+ from sympy.functions.combinatorial.factorials import factorial
+ return factorial(V + N) / factorial(V) / factorial(N)
+
+def monomial_mul(A, B):
+ """
+ Multiplication of tuples representing monomials.
+
+ Examples
+ ========
+
+ Lets multiply `x**3*y**4*z` with `x*y**2`::
+
+ >>> from sympy.polys.monomials import monomial_mul
+
+ >>> monomial_mul((3, 4, 1), (1, 2, 0))
+ (4, 6, 1)
+
+ which gives `x**4*y**5*z`.
+
+ """
+ return tuple([ a + b for a, b in zip(A, B) ])
+
+def monomial_div(A, B):
+ """
+ Division of tuples representing monomials.
+
+ Examples
+ ========
+
+ Lets divide `x**3*y**4*z` by `x*y**2`::
+
+ >>> from sympy.polys.monomials import monomial_div
+
+ >>> monomial_div((3, 4, 1), (1, 2, 0))
+ (2, 2, 1)
+
+ which gives `x**2*y**2*z`. However::
+
+ >>> monomial_div((3, 4, 1), (1, 2, 2)) is None
+ True
+
+ `x*y**2*z**2` does not divide `x**3*y**4*z`.
+
+ """
+ C = monomial_ldiv(A, B)
+
+ if all(c >= 0 for c in C):
+ return tuple(C)
+ else:
+ return None
+
+def monomial_ldiv(A, B):
+ """
+ Division of tuples representing monomials.
+
+ Examples
+ ========
+
+ Lets divide `x**3*y**4*z` by `x*y**2`::
+
+ >>> from sympy.polys.monomials import monomial_ldiv
+
+ >>> monomial_ldiv((3, 4, 1), (1, 2, 0))
+ (2, 2, 1)
+
+ which gives `x**2*y**2*z`.
+
+ >>> monomial_ldiv((3, 4, 1), (1, 2, 2))
+ (2, 2, -1)
+
+ which gives `x**2*y**2*z**-1`.
+
+ """
+ return tuple([ a - b for a, b in zip(A, B) ])
+
+def monomial_pow(A, n):
+ """Return the n-th pow of the monomial. """
+ return tuple([ a*n for a in A ])
+
+def monomial_gcd(A, B):
+ """
+ Greatest common divisor of tuples representing monomials.
+
+ Examples
+ ========
+
+ Lets compute GCD of `x*y**4*z` and `x**3*y**2`::
+
+ >>> from sympy.polys.monomials import monomial_gcd
+
+ >>> monomial_gcd((1, 4, 1), (3, 2, 0))
+ (1, 2, 0)
+
+ which gives `x*y**2`.
+
+ """
+ return tuple([ min(a, b) for a, b in zip(A, B) ])
+
+def monomial_lcm(A, B):
+ """
+ Least common multiple of tuples representing monomials.
+
+ Examples
+ ========
+
+ Lets compute LCM of `x*y**4*z` and `x**3*y**2`::
+
+ >>> from sympy.polys.monomials import monomial_lcm
+
+ >>> monomial_lcm((1, 4, 1), (3, 2, 0))
+ (3, 4, 1)
+
+ which gives `x**3*y**4*z`.
+
+ """
+ return tuple([ max(a, b) for a, b in zip(A, B) ])
+
+def monomial_divides(A, B):
+ """
+ Does there exist a monomial X such that XA == B?
+
+ Examples
+ ========
+
+ >>> from sympy.polys.monomials import monomial_divides
+ >>> monomial_divides((1, 2), (3, 4))
+ True
+ >>> monomial_divides((1, 2), (0, 2))
+ False
+ """
+ return all(a <= b for a, b in zip(A, B))
+
+def monomial_max(*monoms):
+ """
+ Returns maximal degree for each variable in a set of monomials.
+
+ Examples
+ ========
+
+ Consider monomials `x**3*y**4*z**5`, `y**5*z` and `x**6*y**3*z**9`.
+ We wish to find out what is the maximal degree for each of `x`, `y`
+ and `z` variables::
+
+ >>> from sympy.polys.monomials import monomial_max
+
+ >>> monomial_max((3,4,5), (0,5,1), (6,3,9))
+ (6, 5, 9)
+
+ """
+ M = list(monoms[0])
+
+ for N in monoms[1:]:
+ for i, n in enumerate(N):
+ M[i] = max(M[i], n)
+
+ return tuple(M)
+
+def monomial_min(*monoms):
+ """
+ Returns minimal degree for each variable in a set of monomials.
+
+ Examples
+ ========
+
+ Consider monomials `x**3*y**4*z**5`, `y**5*z` and `x**6*y**3*z**9`.
+ We wish to find out what is the minimal degree for each of `x`, `y`
+ and `z` variables::
+
+ >>> from sympy.polys.monomials import monomial_min
+
+ >>> monomial_min((3,4,5), (0,5,1), (6,3,9))
+ (0, 3, 1)
+
+ """
+ M = list(monoms[0])
+
+ for N in monoms[1:]:
+ for i, n in enumerate(N):
+ M[i] = min(M[i], n)
+
+ return tuple(M)
+
+def monomial_deg(M):
+ """
+ Returns the total degree of a monomial.
+
+ Examples
+ ========
+
+ The total degree of `xy^2` is 3:
+
+ >>> from sympy.polys.monomials import monomial_deg
+ >>> monomial_deg((1, 2))
+ 3
+ """
+ return sum(M)
+
+def term_div(a, b, domain):
+ """Division of two terms in over a ring/field. """
+ a_lm, a_lc = a
+ b_lm, b_lc = b
+
+ monom = monomial_div(a_lm, b_lm)
+
+ if domain.is_Field:
+ if monom is not None:
+ return monom, domain.quo(a_lc, b_lc)
+ else:
+ return None
+ else:
+ if not (monom is None or a_lc % b_lc):
+ return monom, domain.quo(a_lc, b_lc)
+ else:
+ return None
+
+class MonomialOps:
+ """Code generator of fast monomial arithmetic functions. """
+
+ def __init__(self, ngens):
+ self.ngens = ngens
+
+ def _build(self, code, name):
+ ns = {}
+ exec(code, ns)
+ return ns[name]
+
+ def _vars(self, name):
+ return [ "%s%s" % (name, i) for i in range(self.ngens) ]
+
+ def mul(self):
+ name = "monomial_mul"
+ template = dedent("""\
+ def %(name)s(A, B):
+ (%(A)s,) = A
+ (%(B)s,) = B
+ return (%(AB)s,)
+ """)
+ A = self._vars("a")
+ B = self._vars("b")
+ AB = [ "%s + %s" % (a, b) for a, b in zip(A, B) ]
+ code = template % {"name": name, "A": ", ".join(A), "B": ", ".join(B), "AB": ", ".join(AB)}
+ return self._build(code, name)
+
+ def pow(self):
+ name = "monomial_pow"
+ template = dedent("""\
+ def %(name)s(A, k):
+ (%(A)s,) = A
+ return (%(Ak)s,)
+ """)
+ A = self._vars("a")
+ Ak = [ "%s*k" % a for a in A ]
+ code = template % {"name": name, "A": ", ".join(A), "Ak": ", ".join(Ak)}
+ return self._build(code, name)
+
+ def mulpow(self):
+ name = "monomial_mulpow"
+ template = dedent("""\
+ def %(name)s(A, B, k):
+ (%(A)s,) = A
+ (%(B)s,) = B
+ return (%(ABk)s,)
+ """)
+ A = self._vars("a")
+ B = self._vars("b")
+ ABk = [ "%s + %s*k" % (a, b) for a, b in zip(A, B) ]
+ code = template % {"name": name, "A": ", ".join(A), "B": ", ".join(B), "ABk": ", ".join(ABk)}
+ return self._build(code, name)
+
+ def ldiv(self):
+ name = "monomial_ldiv"
+ template = dedent("""\
+ def %(name)s(A, B):
+ (%(A)s,) = A
+ (%(B)s,) = B
+ return (%(AB)s,)
+ """)
+ A = self._vars("a")
+ B = self._vars("b")
+ AB = [ "%s - %s" % (a, b) for a, b in zip(A, B) ]
+ code = template % {"name": name, "A": ", ".join(A), "B": ", ".join(B), "AB": ", ".join(AB)}
+ return self._build(code, name)
+
+ def div(self):
+ name = "monomial_div"
+ template = dedent("""\
+ def %(name)s(A, B):
+ (%(A)s,) = A
+ (%(B)s,) = B
+ %(RAB)s
+ return (%(R)s,)
+ """)
+ A = self._vars("a")
+ B = self._vars("b")
+ RAB = [ "r%(i)s = a%(i)s - b%(i)s\n if r%(i)s < 0: return None" % {"i": i} for i in range(self.ngens) ]
+ R = self._vars("r")
+ code = template % {"name": name, "A": ", ".join(A), "B": ", ".join(B), "RAB": "\n ".join(RAB), "R": ", ".join(R)}
+ return self._build(code, name)
+
+ def lcm(self):
+ name = "monomial_lcm"
+ template = dedent("""\
+ def %(name)s(A, B):
+ (%(A)s,) = A
+ (%(B)s,) = B
+ return (%(AB)s,)
+ """)
+ A = self._vars("a")
+ B = self._vars("b")
+ AB = [ "%s if %s >= %s else %s" % (a, a, b, b) for a, b in zip(A, B) ]
+ code = template % {"name": name, "A": ", ".join(A), "B": ", ".join(B), "AB": ", ".join(AB)}
+ return self._build(code, name)
+
+ def gcd(self):
+ name = "monomial_gcd"
+ template = dedent("""\
+ def %(name)s(A, B):
+ (%(A)s,) = A
+ (%(B)s,) = B
+ return (%(AB)s,)
+ """)
+ A = self._vars("a")
+ B = self._vars("b")
+ AB = [ "%s if %s <= %s else %s" % (a, a, b, b) for a, b in zip(A, B) ]
+ code = template % {"name": name, "A": ", ".join(A), "B": ", ".join(B), "AB": ", ".join(AB)}
+ return self._build(code, name)
+
+@public
+class Monomial(PicklableWithSlots):
+ """Class representing a monomial, i.e. a product of powers. """
+
+ __slots__ = ('exponents', 'gens')
+
+ def __init__(self, monom, gens=None):
+ if not iterable(monom):
+ rep, gens = dict_from_expr(sympify(monom), gens=gens)
+ if len(rep) == 1 and list(rep.values())[0] == 1:
+ monom = list(rep.keys())[0]
+ else:
+ raise ValueError("Expected a monomial got {}".format(monom))
+
+ self.exponents = tuple(map(int, monom))
+ self.gens = gens
+
+ def rebuild(self, exponents, gens=None):
+ return self.__class__(exponents, gens or self.gens)
+
+ def __len__(self):
+ return len(self.exponents)
+
+ def __iter__(self):
+ return iter(self.exponents)
+
+ def __getitem__(self, item):
+ return self.exponents[item]
+
+ def __hash__(self):
+ return hash((self.__class__.__name__, self.exponents, self.gens))
+
+ def __str__(self):
+ if self.gens:
+ return "*".join([ "%s**%s" % (gen, exp) for gen, exp in zip(self.gens, self.exponents) ])
+ else:
+ return "%s(%s)" % (self.__class__.__name__, self.exponents)
+
+ def as_expr(self, *gens):
+ """Convert a monomial instance to a SymPy expression. """
+ gens = gens or self.gens
+
+ if not gens:
+ raise ValueError(
+ "Cannot convert %s to an expression without generators" % self)
+
+ return Mul(*[ gen**exp for gen, exp in zip(gens, self.exponents) ])
+
+ def __eq__(self, other):
+ if isinstance(other, Monomial):
+ exponents = other.exponents
+ elif isinstance(other, (tuple, Tuple)):
+ exponents = other
+ else:
+ return False
+
+ return self.exponents == exponents
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __mul__(self, other):
+ if isinstance(other, Monomial):
+ exponents = other.exponents
+ elif isinstance(other, (tuple, Tuple)):
+ exponents = other
+ else:
+ raise NotImplementedError
+
+ return self.rebuild(monomial_mul(self.exponents, exponents))
+
+ def __truediv__(self, other):
+ if isinstance(other, Monomial):
+ exponents = other.exponents
+ elif isinstance(other, (tuple, Tuple)):
+ exponents = other
+ else:
+ raise NotImplementedError
+
+ result = monomial_div(self.exponents, exponents)
+
+ if result is not None:
+ return self.rebuild(result)
+ else:
+ raise ExactQuotientFailed(self, Monomial(other))
+
+ __floordiv__ = __truediv__
+
+ def __pow__(self, other):
+ n = int(other)
+ if n < 0:
+ raise ValueError("a non-negative integer expected, got %s" % other)
+ return self.rebuild(monomial_pow(self.exponents, n))
+
+ def gcd(self, other):
+ """Greatest common divisor of monomials. """
+ if isinstance(other, Monomial):
+ exponents = other.exponents
+ elif isinstance(other, (tuple, Tuple)):
+ exponents = other
+ else:
+ raise TypeError(
+ "an instance of Monomial class expected, got %s" % other)
+
+ return self.rebuild(monomial_gcd(self.exponents, exponents))
+
+ def lcm(self, other):
+ """Least common multiple of monomials. """
+ if isinstance(other, Monomial):
+ exponents = other.exponents
+ elif isinstance(other, (tuple, Tuple)):
+ exponents = other
+ else:
+ raise TypeError(
+ "an instance of Monomial class expected, got %s" % other)
+
+ return self.rebuild(monomial_lcm(self.exponents, exponents))
diff --git a/lib/python3.12/site-packages/sympy/polys/multivariate_resultants.py b/lib/python3.12/site-packages/sympy/polys/multivariate_resultants.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6c967a8b981e25e8e26745804a658ff7b90e9af
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/multivariate_resultants.py
@@ -0,0 +1,473 @@
+"""
+This module contains functions for two multivariate resultants. These
+are:
+
+- Dixon's resultant.
+- Macaulay's resultant.
+
+Multivariate resultants are used to identify whether a multivariate
+system has common roots. That is when the resultant is equal to zero.
+"""
+from math import prod
+
+from sympy.core.mul import Mul
+from sympy.matrices.dense import (Matrix, diag)
+from sympy.polys.polytools import (Poly, degree_list, rem)
+from sympy.simplify.simplify import simplify
+from sympy.tensor.indexed import IndexedBase
+from sympy.polys.monomials import itermonomials, monomial_deg
+from sympy.polys.orderings import monomial_key
+from sympy.polys.polytools import poly_from_expr, total_degree
+from sympy.functions.combinatorial.factorials import binomial
+from itertools import combinations_with_replacement
+from sympy.utilities.exceptions import sympy_deprecation_warning
+
+class DixonResultant():
+ """
+ A class for retrieving the Dixon's resultant of a multivariate
+ system.
+
+ Examples
+ ========
+
+ >>> from sympy import symbols
+
+ >>> from sympy.polys.multivariate_resultants import DixonResultant
+ >>> x, y = symbols('x, y')
+
+ >>> p = x + y
+ >>> q = x ** 2 + y ** 3
+ >>> h = x ** 2 + y
+
+ >>> dixon = DixonResultant(variables=[x, y], polynomials=[p, q, h])
+ >>> poly = dixon.get_dixon_polynomial()
+ >>> matrix = dixon.get_dixon_matrix(polynomial=poly)
+ >>> matrix
+ Matrix([
+ [ 0, 0, -1, 0, -1],
+ [ 0, -1, 0, -1, 0],
+ [-1, 0, 1, 0, 0],
+ [ 0, -1, 0, 0, 1],
+ [-1, 0, 0, 1, 0]])
+ >>> matrix.det()
+ 0
+
+ See Also
+ ========
+
+ Notebook in examples: sympy/example/notebooks.
+
+ References
+ ==========
+
+ .. [1] [Kapur1994]_
+ .. [2] [Palancz08]_
+
+ """
+
+ def __init__(self, polynomials, variables):
+ """
+ A class that takes two lists, a list of polynomials and list of
+ variables. Returns the Dixon matrix of the multivariate system.
+
+ Parameters
+ ----------
+ polynomials : list of polynomials
+ A list of m n-degree polynomials
+ variables: list
+ A list of all n variables
+ """
+ self.polynomials = polynomials
+ self.variables = variables
+
+ self.n = len(self.variables)
+ self.m = len(self.polynomials)
+
+ a = IndexedBase("alpha")
+ # A list of n alpha variables (the replacing variables)
+ self.dummy_variables = [a[i] for i in range(self.n)]
+
+ # A list of the d_max of each variable.
+ self._max_degrees = [max(degree_list(poly)[i] for poly in self.polynomials)
+ for i in range(self.n)]
+
+ @property
+ def max_degrees(self):
+ sympy_deprecation_warning(
+ """
+ The max_degrees property of DixonResultant is deprecated.
+ """,
+ deprecated_since_version="1.5",
+ active_deprecations_target="deprecated-dixonresultant-properties",
+ )
+ return self._max_degrees
+
+ def get_dixon_polynomial(self):
+ r"""
+ Returns
+ =======
+
+ dixon_polynomial: polynomial
+ Dixon's polynomial is calculated as:
+
+ delta = Delta(A) / ((x_1 - a_1) ... (x_n - a_n)) where,
+
+ A = |p_1(x_1,... x_n), ..., p_n(x_1,... x_n)|
+ |p_1(a_1,... x_n), ..., p_n(a_1,... x_n)|
+ |... , ..., ...|
+ |p_1(a_1,... a_n), ..., p_n(a_1,... a_n)|
+ """
+ if self.m != (self.n + 1):
+ raise ValueError('Method invalid for given combination.')
+
+ # First row
+ rows = [self.polynomials]
+
+ temp = list(self.variables)
+
+ for idx in range(self.n):
+ temp[idx] = self.dummy_variables[idx]
+ substitution = dict(zip(self.variables, temp))
+ rows.append([f.subs(substitution) for f in self.polynomials])
+
+ A = Matrix(rows)
+
+ terms = zip(self.variables, self.dummy_variables)
+ product_of_differences = Mul(*[a - b for a, b in terms])
+ dixon_polynomial = (A.det() / product_of_differences).factor()
+
+ return poly_from_expr(dixon_polynomial, self.dummy_variables)[0]
+
+ def get_upper_degree(self):
+ sympy_deprecation_warning(
+ """
+ The get_upper_degree() method of DixonResultant is deprecated. Use
+ get_max_degrees() instead.
+ """,
+ deprecated_since_version="1.5",
+ active_deprecations_target="deprecated-dixonresultant-properties"
+ )
+ list_of_products = [self.variables[i] ** self._max_degrees[i]
+ for i in range(self.n)]
+ product = prod(list_of_products)
+ product = Poly(product).monoms()
+
+ return monomial_deg(*product)
+
+ def get_max_degrees(self, polynomial):
+ r"""
+ Returns a list of the maximum degree of each variable appearing
+ in the coefficients of the Dixon polynomial. The coefficients are
+ viewed as polys in $x_1, x_2, \dots, x_n$.
+ """
+ deg_lists = [degree_list(Poly(poly, self.variables))
+ for poly in polynomial.coeffs()]
+
+ max_degrees = [max(degs) for degs in zip(*deg_lists)]
+
+ return max_degrees
+
+ def get_dixon_matrix(self, polynomial):
+ r"""
+ Construct the Dixon matrix from the coefficients of polynomial
+ \alpha. Each coefficient is viewed as a polynomial of x_1, ...,
+ x_n.
+ """
+
+ max_degrees = self.get_max_degrees(polynomial)
+
+ # list of column headers of the Dixon matrix.
+ monomials = itermonomials(self.variables, max_degrees)
+ monomials = sorted(monomials, reverse=True,
+ key=monomial_key('lex', self.variables))
+
+ dixon_matrix = Matrix([[Poly(c, *self.variables).coeff_monomial(m)
+ for m in monomials]
+ for c in polynomial.coeffs()])
+
+ # remove columns if needed
+ if dixon_matrix.shape[0] != dixon_matrix.shape[1]:
+ keep = [column for column in range(dixon_matrix.shape[-1])
+ if any(element != 0 for element
+ in dixon_matrix[:, column])]
+
+ dixon_matrix = dixon_matrix[:, keep]
+
+ return dixon_matrix
+
+ def KSY_precondition(self, matrix):
+ """
+ Test for the validity of the Kapur-Saxena-Yang precondition.
+
+ The precondition requires that the column corresponding to the
+ monomial 1 = x_1 ^ 0 * x_2 ^ 0 * ... * x_n ^ 0 is not a linear
+ combination of the remaining ones. In SymPy notation this is
+ the last column. For the precondition to hold the last non-zero
+ row of the rref matrix should be of the form [0, 0, ..., 1].
+ """
+ if matrix.is_zero_matrix:
+ return False
+
+ m, n = matrix.shape
+
+ # simplify the matrix and keep only its non-zero rows
+ matrix = simplify(matrix.rref()[0])
+ rows = [i for i in range(m) if any(matrix[i, j] != 0 for j in range(n))]
+ matrix = matrix[rows,:]
+
+ condition = Matrix([[0]*(n-1) + [1]])
+
+ if matrix[-1,:] == condition:
+ return True
+ else:
+ return False
+
+ def delete_zero_rows_and_columns(self, matrix):
+ """Remove the zero rows and columns of the matrix."""
+ rows = [
+ i for i in range(matrix.rows) if not matrix.row(i).is_zero_matrix]
+ cols = [
+ j for j in range(matrix.cols) if not matrix.col(j).is_zero_matrix]
+
+ return matrix[rows, cols]
+
+ def product_leading_entries(self, matrix):
+ """Calculate the product of the leading entries of the matrix."""
+ res = 1
+ for row in range(matrix.rows):
+ for el in matrix.row(row):
+ if el != 0:
+ res = res * el
+ break
+ return res
+
+ def get_KSY_Dixon_resultant(self, matrix):
+ """Calculate the Kapur-Saxena-Yang approach to the Dixon Resultant."""
+ matrix = self.delete_zero_rows_and_columns(matrix)
+ _, U, _ = matrix.LUdecomposition()
+ matrix = self.delete_zero_rows_and_columns(simplify(U))
+
+ return self.product_leading_entries(matrix)
+
+class MacaulayResultant():
+ """
+ A class for calculating the Macaulay resultant. Note that the
+ polynomials must be homogenized and their coefficients must be
+ given as symbols.
+
+ Examples
+ ========
+
+ >>> from sympy import symbols
+
+ >>> from sympy.polys.multivariate_resultants import MacaulayResultant
+ >>> x, y, z = symbols('x, y, z')
+
+ >>> a_0, a_1, a_2 = symbols('a_0, a_1, a_2')
+ >>> b_0, b_1, b_2 = symbols('b_0, b_1, b_2')
+ >>> c_0, c_1, c_2,c_3, c_4 = symbols('c_0, c_1, c_2, c_3, c_4')
+
+ >>> f = a_0 * y - a_1 * x + a_2 * z
+ >>> g = b_1 * x ** 2 + b_0 * y ** 2 - b_2 * z ** 2
+ >>> h = c_0 * y * z ** 2 - c_1 * x ** 3 + c_2 * x ** 2 * z - c_3 * x * z ** 2 + c_4 * z ** 3
+
+ >>> mac = MacaulayResultant(polynomials=[f, g, h], variables=[x, y, z])
+ >>> mac.monomial_set
+ [x**4, x**3*y, x**3*z, x**2*y**2, x**2*y*z, x**2*z**2, x*y**3,
+ x*y**2*z, x*y*z**2, x*z**3, y**4, y**3*z, y**2*z**2, y*z**3, z**4]
+ >>> matrix = mac.get_matrix()
+ >>> submatrix = mac.get_submatrix(matrix)
+ >>> submatrix
+ Matrix([
+ [-a_1, a_0, a_2, 0],
+ [ 0, -a_1, 0, 0],
+ [ 0, 0, -a_1, 0],
+ [ 0, 0, 0, -a_1]])
+
+ See Also
+ ========
+
+ Notebook in examples: sympy/example/notebooks.
+
+ References
+ ==========
+
+ .. [1] [Bruce97]_
+ .. [2] [Stiller96]_
+
+ """
+ def __init__(self, polynomials, variables):
+ """
+ Parameters
+ ==========
+
+ variables: list
+ A list of all n variables
+ polynomials : list of SymPy polynomials
+ A list of m n-degree polynomials
+ """
+ self.polynomials = polynomials
+ self.variables = variables
+ self.n = len(variables)
+
+ # A list of the d_max of each polynomial.
+ self.degrees = [total_degree(poly, *self.variables) for poly
+ in self.polynomials]
+
+ self.degree_m = self._get_degree_m()
+ self.monomials_size = self.get_size()
+
+ # The set T of all possible monomials of degree degree_m
+ self.monomial_set = self.get_monomials_of_certain_degree(self.degree_m)
+
+ def _get_degree_m(self):
+ r"""
+ Returns
+ =======
+
+ degree_m: int
+ The degree_m is calculated as 1 + \sum_1 ^ n (d_i - 1),
+ where d_i is the degree of the i polynomial
+ """
+ return 1 + sum(d - 1 for d in self.degrees)
+
+ def get_size(self):
+ r"""
+ Returns
+ =======
+
+ size: int
+ The size of set T. Set T is the set of all possible
+ monomials of the n variables for degree equal to the
+ degree_m
+ """
+ return binomial(self.degree_m + self.n - 1, self.n - 1)
+
+ def get_monomials_of_certain_degree(self, degree):
+ """
+ Returns
+ =======
+
+ monomials: list
+ A list of monomials of a certain degree.
+ """
+ monomials = [Mul(*monomial) for monomial
+ in combinations_with_replacement(self.variables,
+ degree)]
+
+ return sorted(monomials, reverse=True,
+ key=monomial_key('lex', self.variables))
+
+ def get_row_coefficients(self):
+ """
+ Returns
+ =======
+
+ row_coefficients: list
+ The row coefficients of Macaulay's matrix
+ """
+ row_coefficients = []
+ divisible = []
+ for i in range(self.n):
+ if i == 0:
+ degree = self.degree_m - self.degrees[i]
+ monomial = self.get_monomials_of_certain_degree(degree)
+ row_coefficients.append(monomial)
+ else:
+ divisible.append(self.variables[i - 1] **
+ self.degrees[i - 1])
+ degree = self.degree_m - self.degrees[i]
+ poss_rows = self.get_monomials_of_certain_degree(degree)
+ for div in divisible:
+ for p in poss_rows:
+ if rem(p, div) == 0:
+ poss_rows = [item for item in poss_rows
+ if item != p]
+ row_coefficients.append(poss_rows)
+ return row_coefficients
+
+ def get_matrix(self):
+ """
+ Returns
+ =======
+
+ macaulay_matrix: Matrix
+ The Macaulay numerator matrix
+ """
+ rows = []
+ row_coefficients = self.get_row_coefficients()
+ for i in range(self.n):
+ for multiplier in row_coefficients[i]:
+ coefficients = []
+ poly = Poly(self.polynomials[i] * multiplier,
+ *self.variables)
+
+ for mono in self.monomial_set:
+ coefficients.append(poly.coeff_monomial(mono))
+ rows.append(coefficients)
+
+ macaulay_matrix = Matrix(rows)
+ return macaulay_matrix
+
+ def get_reduced_nonreduced(self):
+ r"""
+ Returns
+ =======
+
+ reduced: list
+ A list of the reduced monomials
+ non_reduced: list
+ A list of the monomials that are not reduced
+
+ Definition
+ ==========
+
+ A polynomial is said to be reduced in x_i, if its degree (the
+ maximum degree of its monomials) in x_i is less than d_i. A
+ polynomial that is reduced in all variables but one is said
+ simply to be reduced.
+ """
+ divisible = []
+ for m in self.monomial_set:
+ temp = []
+ for i, v in enumerate(self.variables):
+ temp.append(bool(total_degree(m, v) >= self.degrees[i]))
+ divisible.append(temp)
+ reduced = [i for i, r in enumerate(divisible)
+ if sum(r) < self.n - 1]
+ non_reduced = [i for i, r in enumerate(divisible)
+ if sum(r) >= self.n -1]
+
+ return reduced, non_reduced
+
+ def get_submatrix(self, matrix):
+ r"""
+ Returns
+ =======
+
+ macaulay_submatrix: Matrix
+ The Macaulay denominator matrix. Columns that are non reduced are kept.
+ The row which contains one of the a_{i}s is dropped. a_{i}s
+ are the coefficients of x_i ^ {d_i}.
+ """
+ reduced, non_reduced = self.get_reduced_nonreduced()
+
+ # if reduced == [], then det(matrix) should be 1
+ if reduced == []:
+ return diag([1])
+
+ # reduced != []
+ reduction_set = [v ** self.degrees[i] for i, v
+ in enumerate(self.variables)]
+
+ ais = [self.polynomials[i].coeff(reduction_set[i])
+ for i in range(self.n)]
+
+ reduced_matrix = matrix[:, reduced]
+ keep = []
+ for row in range(reduced_matrix.rows):
+ check = [ai in reduced_matrix[row, :] for ai in ais]
+ if True not in check:
+ keep.append(row)
+
+ return matrix[keep, non_reduced]
diff --git a/lib/python3.12/site-packages/sympy/polys/orthopolys.py b/lib/python3.12/site-packages/sympy/polys/orthopolys.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee82457703a2be172951ee38e3cd67f221a438a0
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/orthopolys.py
@@ -0,0 +1,343 @@
+"""Efficient functions for generating orthogonal polynomials."""
+from sympy.core.symbol import Dummy
+from sympy.polys.densearith import (dup_mul, dup_mul_ground,
+ dup_lshift, dup_sub, dup_add, dup_sub_term, dup_sub_ground, dup_sqr)
+from sympy.polys.domains import ZZ, QQ
+from sympy.polys.polytools import named_poly
+from sympy.utilities import public
+
+def dup_jacobi(n, a, b, K):
+ """Low-level implementation of Jacobi polynomials."""
+ if n < 1:
+ return [K.one]
+ m2, m1 = [K.one], [(a+b)/K(2) + K.one, (a-b)/K(2)]
+ for i in range(2, n+1):
+ den = K(i)*(a + b + i)*(a + b + K(2)*i - K(2))
+ f0 = (a + b + K(2)*i - K.one) * (a*a - b*b) / (K(2)*den)
+ f1 = (a + b + K(2)*i - K.one) * (a + b + K(2)*i - K(2)) * (a + b + K(2)*i) / (K(2)*den)
+ f2 = (a + i - K.one)*(b + i - K.one)*(a + b + K(2)*i) / den
+ p0 = dup_mul_ground(m1, f0, K)
+ p1 = dup_mul_ground(dup_lshift(m1, 1, K), f1, K)
+ p2 = dup_mul_ground(m2, f2, K)
+ m2, m1 = m1, dup_sub(dup_add(p0, p1, K), p2, K)
+ return m1
+
+@public
+def jacobi_poly(n, a, b, x=None, polys=False):
+ r"""Generates the Jacobi polynomial `P_n^{(a,b)}(x)`.
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ a
+ Lower limit of minimal domain for the list of coefficients.
+ b
+ Upper limit of minimal domain for the list of coefficients.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ return named_poly(n, dup_jacobi, None, "Jacobi polynomial", (x, a, b), polys)
+
+def dup_gegenbauer(n, a, K):
+ """Low-level implementation of Gegenbauer polynomials."""
+ if n < 1:
+ return [K.one]
+ m2, m1 = [K.one], [K(2)*a, K.zero]
+ for i in range(2, n+1):
+ p1 = dup_mul_ground(dup_lshift(m1, 1, K), K(2)*(a-K.one)/K(i) + K(2), K)
+ p2 = dup_mul_ground(m2, K(2)*(a-K.one)/K(i) + K.one, K)
+ m2, m1 = m1, dup_sub(p1, p2, K)
+ return m1
+
+def gegenbauer_poly(n, a, x=None, polys=False):
+ r"""Generates the Gegenbauer polynomial `C_n^{(a)}(x)`.
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ a
+ Decides minimal domain for the list of coefficients.
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ return named_poly(n, dup_gegenbauer, None, "Gegenbauer polynomial", (x, a), polys)
+
+def dup_chebyshevt(n, K):
+ """Low-level implementation of Chebyshev polynomials of the first kind."""
+ if n < 1:
+ return [K.one]
+ # When n is small, it is faster to directly calculate the recurrence relation.
+ if n < 64: # The threshold serves as a heuristic
+ return _dup_chebyshevt_rec(n, K)
+ return _dup_chebyshevt_prod(n, K)
+
+def _dup_chebyshevt_rec(n, K):
+ r""" Chebyshev polynomials of the first kind using recurrence.
+
+ Explanation
+ ===========
+
+ Chebyshev polynomials of the first kind are defined by the recurrence
+ relation:
+
+ .. math::
+ T_0(x) &= 1\\
+ T_1(x) &= x\\
+ T_n(x) &= 2xT_{n-1}(x) - T_{n-2}(x)
+
+ This function calculates the Chebyshev polynomial of the first kind using
+ the above recurrence relation.
+
+ Parameters
+ ==========
+
+ n : int
+ n is a nonnegative integer.
+ K : domain
+
+ """
+ m2, m1 = [K.one], [K.one, K.zero]
+ for _ in range(n - 1):
+ m2, m1 = m1, dup_sub(dup_mul_ground(dup_lshift(m1, 1, K), K(2), K), m2, K)
+ return m1
+
+def _dup_chebyshevt_prod(n, K):
+ r""" Chebyshev polynomials of the first kind using recursive products.
+
+ Explanation
+ ===========
+
+ Computes Chebyshev polynomials of the first kind using
+
+ .. math::
+ T_{2n}(x) &= 2T_n^2(x) - 1\\
+ T_{2n+1}(x) &= 2T_{n+1}(x)T_n(x) - x
+
+ This is faster than ``_dup_chebyshevt_rec`` for large ``n``.
+
+ Parameters
+ ==========
+
+ n : int
+ n is a nonnegative integer.
+ K : domain
+
+ """
+ m2, m1 = [K.one, K.zero], [K(2), K.zero, -K.one]
+ for i in bin(n)[3:]:
+ c = dup_sub_term(dup_mul_ground(dup_mul(m1, m2, K), K(2), K), K.one, 1, K)
+ if i == '1':
+ m2, m1 = c, dup_sub_ground(dup_mul_ground(dup_sqr(m1, K), K(2), K), K.one, K)
+ else:
+ m2, m1 = dup_sub_ground(dup_mul_ground(dup_sqr(m2, K), K(2), K), K.one, K), c
+ return m2
+
+def dup_chebyshevu(n, K):
+ """Low-level implementation of Chebyshev polynomials of the second kind."""
+ if n < 1:
+ return [K.one]
+ m2, m1 = [K.one], [K(2), K.zero]
+ for i in range(2, n+1):
+ m2, m1 = m1, dup_sub(dup_mul_ground(dup_lshift(m1, 1, K), K(2), K), m2, K)
+ return m1
+
+@public
+def chebyshevt_poly(n, x=None, polys=False):
+ r"""Generates the Chebyshev polynomial of the first kind `T_n(x)`.
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ return named_poly(n, dup_chebyshevt, ZZ,
+ "Chebyshev polynomial of the first kind", (x,), polys)
+
+@public
+def chebyshevu_poly(n, x=None, polys=False):
+ r"""Generates the Chebyshev polynomial of the second kind `U_n(x)`.
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ return named_poly(n, dup_chebyshevu, ZZ,
+ "Chebyshev polynomial of the second kind", (x,), polys)
+
+def dup_hermite(n, K):
+ """Low-level implementation of Hermite polynomials."""
+ if n < 1:
+ return [K.one]
+ m2, m1 = [K.one], [K(2), K.zero]
+ for i in range(2, n+1):
+ a = dup_lshift(m1, 1, K)
+ b = dup_mul_ground(m2, K(i-1), K)
+ m2, m1 = m1, dup_mul_ground(dup_sub(a, b, K), K(2), K)
+ return m1
+
+def dup_hermite_prob(n, K):
+ """Low-level implementation of probabilist's Hermite polynomials."""
+ if n < 1:
+ return [K.one]
+ m2, m1 = [K.one], [K.one, K.zero]
+ for i in range(2, n+1):
+ a = dup_lshift(m1, 1, K)
+ b = dup_mul_ground(m2, K(i-1), K)
+ m2, m1 = m1, dup_sub(a, b, K)
+ return m1
+
+@public
+def hermite_poly(n, x=None, polys=False):
+ r"""Generates the Hermite polynomial `H_n(x)`.
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ return named_poly(n, dup_hermite, ZZ, "Hermite polynomial", (x,), polys)
+
+@public
+def hermite_prob_poly(n, x=None, polys=False):
+ r"""Generates the probabilist's Hermite polynomial `He_n(x)`.
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ return named_poly(n, dup_hermite_prob, ZZ,
+ "probabilist's Hermite polynomial", (x,), polys)
+
+def dup_legendre(n, K):
+ """Low-level implementation of Legendre polynomials."""
+ if n < 1:
+ return [K.one]
+ m2, m1 = [K.one], [K.one, K.zero]
+ for i in range(2, n+1):
+ a = dup_mul_ground(dup_lshift(m1, 1, K), K(2*i-1, i), K)
+ b = dup_mul_ground(m2, K(i-1, i), K)
+ m2, m1 = m1, dup_sub(a, b, K)
+ return m1
+
+@public
+def legendre_poly(n, x=None, polys=False):
+ r"""Generates the Legendre polynomial `P_n(x)`.
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ return named_poly(n, dup_legendre, QQ, "Legendre polynomial", (x,), polys)
+
+def dup_laguerre(n, alpha, K):
+ """Low-level implementation of Laguerre polynomials."""
+ m2, m1 = [K.zero], [K.one]
+ for i in range(1, n+1):
+ a = dup_mul(m1, [-K.one/K(i), (alpha-K.one)/K(i) + K(2)], K)
+ b = dup_mul_ground(m2, (alpha-K.one)/K(i) + K.one, K)
+ m2, m1 = m1, dup_sub(a, b, K)
+ return m1
+
+@public
+def laguerre_poly(n, x=None, alpha=0, polys=False):
+ r"""Generates the Laguerre polynomial `L_n^{(\alpha)}(x)`.
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ alpha : optional
+ Decides minimal domain for the list of coefficients.
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ return named_poly(n, dup_laguerre, None, "Laguerre polynomial", (x, alpha), polys)
+
+def dup_spherical_bessel_fn(n, K):
+ """Low-level implementation of fn(n, x)."""
+ if n < 1:
+ return [K.one, K.zero]
+ m2, m1 = [K.one], [K.one, K.zero]
+ for i in range(2, n+1):
+ m2, m1 = m1, dup_sub(dup_mul_ground(dup_lshift(m1, 1, K), K(2*i-1), K), m2, K)
+ return dup_lshift(m1, 1, K)
+
+def dup_spherical_bessel_fn_minus(n, K):
+ """Low-level implementation of fn(-n, x)."""
+ m2, m1 = [K.one, K.zero], [K.zero]
+ for i in range(2, n+1):
+ m2, m1 = m1, dup_sub(dup_mul_ground(dup_lshift(m1, 1, K), K(3-2*i), K), m2, K)
+ return m1
+
+def spherical_bessel_fn(n, x=None, polys=False):
+ """
+ Coefficients for the spherical Bessel functions.
+
+ These are only needed in the jn() function.
+
+ The coefficients are calculated from:
+
+ fn(0, z) = 1/z
+ fn(1, z) = 1/z**2
+ fn(n-1, z) + fn(n+1, z) == (2*n+1)/z * fn(n, z)
+
+ Parameters
+ ==========
+
+ n : int
+ Degree of the polynomial.
+ x : optional
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.orthopolys import spherical_bessel_fn as fn
+ >>> from sympy import Symbol
+ >>> z = Symbol("z")
+ >>> fn(1, z)
+ z**(-2)
+ >>> fn(2, z)
+ -1/z + 3/z**3
+ >>> fn(3, z)
+ -6/z**2 + 15/z**4
+ >>> fn(4, z)
+ 1/z - 45/z**3 + 105/z**5
+
+ """
+ if x is None:
+ x = Dummy("x")
+ f = dup_spherical_bessel_fn_minus if n < 0 else dup_spherical_bessel_fn
+ return named_poly(abs(n), f, ZZ, "", (QQ(1)/x,), polys)
diff --git a/lib/python3.12/site-packages/sympy/polys/partfrac.py b/lib/python3.12/site-packages/sympy/polys/partfrac.py
new file mode 100644
index 0000000000000000000000000000000000000000..a04e5de9b78a73521032942e98905550048a3960
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/partfrac.py
@@ -0,0 +1,496 @@
+"""Algorithms for partial fraction decomposition of rational functions. """
+
+
+from sympy.core import S, Add, sympify, Function, Lambda, Dummy
+from sympy.core.traversal import preorder_traversal
+from sympy.polys import Poly, RootSum, cancel, factor
+from sympy.polys.polyerrors import PolynomialError
+from sympy.polys.polyoptions import allowed_flags, set_defaults
+from sympy.polys.polytools import parallel_poly_from_expr
+from sympy.utilities import numbered_symbols, take, xthreaded, public
+
+
+@xthreaded
+@public
+def apart(f, x=None, full=False, **options):
+ """
+ Compute partial fraction decomposition of a rational function.
+
+ Given a rational function ``f``, computes the partial fraction
+ decomposition of ``f``. Two algorithms are available: One is based on the
+ undertermined coefficients method, the other is Bronstein's full partial
+ fraction decomposition algorithm.
+
+ The undetermined coefficients method (selected by ``full=False``) uses
+ polynomial factorization (and therefore accepts the same options as
+ factor) for the denominator. Per default it works over the rational
+ numbers, therefore decomposition of denominators with non-rational roots
+ (e.g. irrational, complex roots) is not supported by default (see options
+ of factor).
+
+ Bronstein's algorithm can be selected by using ``full=True`` and allows a
+ decomposition of denominators with non-rational roots. A human-readable
+ result can be obtained via ``doit()`` (see examples below).
+
+ Examples
+ ========
+
+ >>> from sympy.polys.partfrac import apart
+ >>> from sympy.abc import x, y
+
+ By default, using the undetermined coefficients method:
+
+ >>> apart(y/(x + 2)/(x + 1), x)
+ -y/(x + 2) + y/(x + 1)
+
+ The undetermined coefficients method does not provide a result when the
+ denominators roots are not rational:
+
+ >>> apart(y/(x**2 + x + 1), x)
+ y/(x**2 + x + 1)
+
+ You can choose Bronstein's algorithm by setting ``full=True``:
+
+ >>> apart(y/(x**2 + x + 1), x, full=True)
+ RootSum(_w**2 + _w + 1, Lambda(_a, (-2*_a*y/3 - y/3)/(-_a + x)))
+
+ Calling ``doit()`` yields a human-readable result:
+
+ >>> apart(y/(x**2 + x + 1), x, full=True).doit()
+ (-y/3 - 2*y*(-1/2 - sqrt(3)*I/2)/3)/(x + 1/2 + sqrt(3)*I/2) + (-y/3 -
+ 2*y*(-1/2 + sqrt(3)*I/2)/3)/(x + 1/2 - sqrt(3)*I/2)
+
+
+ See Also
+ ========
+
+ apart_list, assemble_partfrac_list
+ """
+ allowed_flags(options, [])
+
+ f = sympify(f)
+
+ if f.is_Atom:
+ return f
+ else:
+ P, Q = f.as_numer_denom()
+
+ _options = options.copy()
+ options = set_defaults(options, extension=True)
+ try:
+ (P, Q), opt = parallel_poly_from_expr((P, Q), x, **options)
+ except PolynomialError as msg:
+ if f.is_commutative:
+ raise PolynomialError(msg)
+ # non-commutative
+ if f.is_Mul:
+ c, nc = f.args_cnc(split_1=False)
+ nc = f.func(*nc)
+ if c:
+ c = apart(f.func._from_args(c), x=x, full=full, **_options)
+ return c*nc
+ else:
+ return nc
+ elif f.is_Add:
+ c = []
+ nc = []
+ for i in f.args:
+ if i.is_commutative:
+ c.append(i)
+ else:
+ try:
+ nc.append(apart(i, x=x, full=full, **_options))
+ except NotImplementedError:
+ nc.append(i)
+ return apart(f.func(*c), x=x, full=full, **_options) + f.func(*nc)
+ else:
+ reps = []
+ pot = preorder_traversal(f)
+ next(pot)
+ for e in pot:
+ try:
+ reps.append((e, apart(e, x=x, full=full, **_options)))
+ pot.skip() # this was handled successfully
+ except NotImplementedError:
+ pass
+ return f.xreplace(dict(reps))
+
+ if P.is_multivariate:
+ fc = f.cancel()
+ if fc != f:
+ return apart(fc, x=x, full=full, **_options)
+
+ raise NotImplementedError(
+ "multivariate partial fraction decomposition")
+
+ common, P, Q = P.cancel(Q)
+
+ poly, P = P.div(Q, auto=True)
+ P, Q = P.rat_clear_denoms(Q)
+
+ if Q.degree() <= 1:
+ partial = P/Q
+ else:
+ if not full:
+ partial = apart_undetermined_coeffs(P, Q)
+ else:
+ partial = apart_full_decomposition(P, Q)
+
+ terms = S.Zero
+
+ for term in Add.make_args(partial):
+ if term.has(RootSum):
+ terms += term
+ else:
+ terms += factor(term)
+
+ return common*(poly.as_expr() + terms)
+
+
+def apart_undetermined_coeffs(P, Q):
+ """Partial fractions via method of undetermined coefficients. """
+ X = numbered_symbols(cls=Dummy)
+ partial, symbols = [], []
+
+ _, factors = Q.factor_list()
+
+ for f, k in factors:
+ n, q = f.degree(), Q
+
+ for i in range(1, k + 1):
+ coeffs, q = take(X, n), q.quo(f)
+ partial.append((coeffs, q, f, i))
+ symbols.extend(coeffs)
+
+ dom = Q.get_domain().inject(*symbols)
+ F = Poly(0, Q.gen, domain=dom)
+
+ for i, (coeffs, q, f, k) in enumerate(partial):
+ h = Poly(coeffs, Q.gen, domain=dom)
+ partial[i] = (h, f, k)
+ q = q.set_domain(dom)
+ F += h*q
+
+ system, result = [], S.Zero
+
+ for (k,), coeff in F.terms():
+ system.append(coeff - P.nth(k))
+
+ from sympy.solvers import solve
+ solution = solve(system, symbols)
+
+ for h, f, k in partial:
+ h = h.as_expr().subs(solution)
+ result += h/f.as_expr()**k
+
+ return result
+
+
+def apart_full_decomposition(P, Q):
+ """
+ Bronstein's full partial fraction decomposition algorithm.
+
+ Given a univariate rational function ``f``, performing only GCD
+ operations over the algebraic closure of the initial ground domain
+ of definition, compute full partial fraction decomposition with
+ fractions having linear denominators.
+
+ Note that no factorization of the initial denominator of ``f`` is
+ performed. The final decomposition is formed in terms of a sum of
+ :class:`RootSum` instances.
+
+ References
+ ==========
+
+ .. [1] [Bronstein93]_
+
+ """
+ return assemble_partfrac_list(apart_list(P/Q, P.gens[0]))
+
+
+@public
+def apart_list(f, x=None, dummies=None, **options):
+ """
+ Compute partial fraction decomposition of a rational function
+ and return the result in structured form.
+
+ Given a rational function ``f`` compute the partial fraction decomposition
+ of ``f``. Only Bronstein's full partial fraction decomposition algorithm
+ is supported by this method. The return value is highly structured and
+ perfectly suited for further algorithmic treatment rather than being
+ human-readable. The function returns a tuple holding three elements:
+
+ * The first item is the common coefficient, free of the variable `x` used
+ for decomposition. (It is an element of the base field `K`.)
+
+ * The second item is the polynomial part of the decomposition. This can be
+ the zero polynomial. (It is an element of `K[x]`.)
+
+ * The third part itself is a list of quadruples. Each quadruple
+ has the following elements in this order:
+
+ - The (not necessarily irreducible) polynomial `D` whose roots `w_i` appear
+ in the linear denominator of a bunch of related fraction terms. (This item
+ can also be a list of explicit roots. However, at the moment ``apart_list``
+ never returns a result this way, but the related ``assemble_partfrac_list``
+ function accepts this format as input.)
+
+ - The numerator of the fraction, written as a function of the root `w`
+
+ - The linear denominator of the fraction *excluding its power exponent*,
+ written as a function of the root `w`.
+
+ - The power to which the denominator has to be raised.
+
+ On can always rebuild a plain expression by using the function ``assemble_partfrac_list``.
+
+ Examples
+ ========
+
+ A first example:
+
+ >>> from sympy.polys.partfrac import apart_list, assemble_partfrac_list
+ >>> from sympy.abc import x, t
+
+ >>> f = (2*x**3 - 2*x) / (x**2 - 2*x + 1)
+ >>> pfd = apart_list(f)
+ >>> pfd
+ (1,
+ Poly(2*x + 4, x, domain='ZZ'),
+ [(Poly(_w - 1, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1)])
+
+ >>> assemble_partfrac_list(pfd)
+ 2*x + 4 + 4/(x - 1)
+
+ Second example:
+
+ >>> f = (-2*x - 2*x**2) / (3*x**2 - 6*x)
+ >>> pfd = apart_list(f)
+ >>> pfd
+ (-1,
+ Poly(2/3, x, domain='QQ'),
+ [(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 2), Lambda(_a, -_a + x), 1)])
+
+ >>> assemble_partfrac_list(pfd)
+ -2/3 - 2/(x - 2)
+
+ Another example, showing symbolic parameters:
+
+ >>> pfd = apart_list(t/(x**2 + x + t), x)
+ >>> pfd
+ (1,
+ Poly(0, x, domain='ZZ[t]'),
+ [(Poly(_w**2 + _w + t, _w, domain='ZZ[t]'),
+ Lambda(_a, -2*_a*t/(4*t - 1) - t/(4*t - 1)),
+ Lambda(_a, -_a + x),
+ 1)])
+
+ >>> assemble_partfrac_list(pfd)
+ RootSum(_w**2 + _w + t, Lambda(_a, (-2*_a*t/(4*t - 1) - t/(4*t - 1))/(-_a + x)))
+
+ This example is taken from Bronstein's original paper:
+
+ >>> f = 36 / (x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2)
+ >>> pfd = apart_list(f)
+ >>> pfd
+ (1,
+ Poly(0, x, domain='ZZ'),
+ [(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1),
+ (Poly(_w**2 - 1, _w, domain='ZZ'), Lambda(_a, -3*_a - 6), Lambda(_a, -_a + x), 2),
+ (Poly(_w + 1, _w, domain='ZZ'), Lambda(_a, -4), Lambda(_a, -_a + x), 1)])
+
+ >>> assemble_partfrac_list(pfd)
+ -4/(x + 1) - 3/(x + 1)**2 - 9/(x - 1)**2 + 4/(x - 2)
+
+ See also
+ ========
+
+ apart, assemble_partfrac_list
+
+ References
+ ==========
+
+ .. [1] [Bronstein93]_
+
+ """
+ allowed_flags(options, [])
+
+ f = sympify(f)
+
+ if f.is_Atom:
+ return f
+ else:
+ P, Q = f.as_numer_denom()
+
+ options = set_defaults(options, extension=True)
+ (P, Q), opt = parallel_poly_from_expr((P, Q), x, **options)
+
+ if P.is_multivariate:
+ raise NotImplementedError(
+ "multivariate partial fraction decomposition")
+
+ common, P, Q = P.cancel(Q)
+
+ poly, P = P.div(Q, auto=True)
+ P, Q = P.rat_clear_denoms(Q)
+
+ polypart = poly
+
+ if dummies is None:
+ def dummies(name):
+ d = Dummy(name)
+ while True:
+ yield d
+
+ dummies = dummies("w")
+
+ rationalpart = apart_list_full_decomposition(P, Q, dummies)
+
+ return (common, polypart, rationalpart)
+
+
+def apart_list_full_decomposition(P, Q, dummygen):
+ """
+ Bronstein's full partial fraction decomposition algorithm.
+
+ Given a univariate rational function ``f``, performing only GCD
+ operations over the algebraic closure of the initial ground domain
+ of definition, compute full partial fraction decomposition with
+ fractions having linear denominators.
+
+ Note that no factorization of the initial denominator of ``f`` is
+ performed. The final decomposition is formed in terms of a sum of
+ :class:`RootSum` instances.
+
+ References
+ ==========
+
+ .. [1] [Bronstein93]_
+
+ """
+ P_orig, Q_orig, x, U = P, Q, P.gen, []
+
+ u = Function('u')(x)
+ a = Dummy('a')
+
+ partial = []
+
+ for d, n in Q.sqf_list_include(all=True):
+ b = d.as_expr()
+ U += [ u.diff(x, n - 1) ]
+
+ h = cancel(P_orig/Q_orig.quo(d**n)) / u**n
+
+ H, subs = [h], []
+
+ for j in range(1, n):
+ H += [ H[-1].diff(x) / j ]
+
+ for j in range(1, n + 1):
+ subs += [ (U[j - 1], b.diff(x, j) / j) ]
+
+ for j in range(0, n):
+ P, Q = cancel(H[j]).as_numer_denom()
+
+ for i in range(0, j + 1):
+ P = P.subs(*subs[j - i])
+
+ Q = Q.subs(*subs[0])
+
+ P = Poly(P, x)
+ Q = Poly(Q, x)
+
+ G = P.gcd(d)
+ D = d.quo(G)
+
+ B, g = Q.half_gcdex(D)
+ b = (P * B.quo(g)).rem(D)
+
+ Dw = D.subs(x, next(dummygen))
+ numer = Lambda(a, b.as_expr().subs(x, a))
+ denom = Lambda(a, (x - a))
+ exponent = n-j
+
+ partial.append((Dw, numer, denom, exponent))
+
+ return partial
+
+
+@public
+def assemble_partfrac_list(partial_list):
+ r"""Reassemble a full partial fraction decomposition
+ from a structured result obtained by the function ``apart_list``.
+
+ Examples
+ ========
+
+ This example is taken from Bronstein's original paper:
+
+ >>> from sympy.polys.partfrac import apart_list, assemble_partfrac_list
+ >>> from sympy.abc import x
+
+ >>> f = 36 / (x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2)
+ >>> pfd = apart_list(f)
+ >>> pfd
+ (1,
+ Poly(0, x, domain='ZZ'),
+ [(Poly(_w - 2, _w, domain='ZZ'), Lambda(_a, 4), Lambda(_a, -_a + x), 1),
+ (Poly(_w**2 - 1, _w, domain='ZZ'), Lambda(_a, -3*_a - 6), Lambda(_a, -_a + x), 2),
+ (Poly(_w + 1, _w, domain='ZZ'), Lambda(_a, -4), Lambda(_a, -_a + x), 1)])
+
+ >>> assemble_partfrac_list(pfd)
+ -4/(x + 1) - 3/(x + 1)**2 - 9/(x - 1)**2 + 4/(x - 2)
+
+ If we happen to know some roots we can provide them easily inside the structure:
+
+ >>> pfd = apart_list(2/(x**2-2))
+ >>> pfd
+ (1,
+ Poly(0, x, domain='ZZ'),
+ [(Poly(_w**2 - 2, _w, domain='ZZ'),
+ Lambda(_a, _a/2),
+ Lambda(_a, -_a + x),
+ 1)])
+
+ >>> pfda = assemble_partfrac_list(pfd)
+ >>> pfda
+ RootSum(_w**2 - 2, Lambda(_a, _a/(-_a + x)))/2
+
+ >>> pfda.doit()
+ -sqrt(2)/(2*(x + sqrt(2))) + sqrt(2)/(2*(x - sqrt(2)))
+
+ >>> from sympy import Dummy, Poly, Lambda, sqrt
+ >>> a = Dummy("a")
+ >>> pfd = (1, Poly(0, x, domain='ZZ'), [([sqrt(2),-sqrt(2)], Lambda(a, a/2), Lambda(a, -a + x), 1)])
+
+ >>> assemble_partfrac_list(pfd)
+ -sqrt(2)/(2*(x + sqrt(2))) + sqrt(2)/(2*(x - sqrt(2)))
+
+ See Also
+ ========
+
+ apart, apart_list
+ """
+ # Common factor
+ common = partial_list[0]
+
+ # Polynomial part
+ polypart = partial_list[1]
+ pfd = polypart.as_expr()
+
+ # Rational parts
+ for r, nf, df, ex in partial_list[2]:
+ if isinstance(r, Poly):
+ # Assemble in case the roots are given implicitly by a polynomials
+ an, nu = nf.variables, nf.expr
+ ad, de = df.variables, df.expr
+ # Hack to make dummies equal because Lambda created new Dummies
+ de = de.subs(ad[0], an[0])
+ func = Lambda(tuple(an), nu/de**ex)
+ pfd += RootSum(r, func, auto=False, quadratic=False)
+ else:
+ # Assemble in case the roots are given explicitly by a list of algebraic numbers
+ for root in r:
+ pfd += nf(root)/df(root)**ex
+
+ return common*pfd
diff --git a/lib/python3.12/site-packages/sympy/polys/polyclasses.py b/lib/python3.12/site-packages/sympy/polys/polyclasses.py
new file mode 100644
index 0000000000000000000000000000000000000000..c19f72a227f861f19d9936bd440bf271c0d04ba2
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/polyclasses.py
@@ -0,0 +1,3126 @@
+"""OO layer for several polynomial representations. """
+
+from __future__ import annotations
+
+from sympy.external.gmpy import GROUND_TYPES
+
+from sympy.utilities.exceptions import sympy_deprecation_warning
+
+from sympy.core.numbers import oo
+from sympy.core.sympify import CantSympify
+from sympy.polys.polyutils import PicklableWithSlots, _sort_factors
+from sympy.polys.domains import Domain, ZZ, QQ
+
+from sympy.polys.polyerrors import (
+ CoercionFailed,
+ ExactQuotientFailed,
+ DomainError,
+ NotInvertible,
+)
+
+from sympy.polys.densebasic import (
+ ninf,
+ dmp_validate,
+ dup_normal, dmp_normal,
+ dup_convert, dmp_convert,
+ dmp_from_sympy,
+ dup_strip,
+ dmp_degree_in,
+ dmp_degree_list,
+ dmp_negative_p,
+ dmp_ground_LC,
+ dmp_ground_TC,
+ dmp_ground_nth,
+ dmp_one, dmp_ground,
+ dmp_zero, dmp_zero_p, dmp_one_p, dmp_ground_p,
+ dup_from_dict, dmp_from_dict,
+ dmp_to_dict,
+ dmp_deflate,
+ dmp_inject, dmp_eject,
+ dmp_terms_gcd,
+ dmp_list_terms, dmp_exclude,
+ dup_slice, dmp_slice_in, dmp_permute,
+ dmp_to_tuple,)
+
+from sympy.polys.densearith import (
+ dmp_add_ground,
+ dmp_sub_ground,
+ dmp_mul_ground,
+ dmp_quo_ground,
+ dmp_exquo_ground,
+ dmp_abs,
+ dmp_neg,
+ dmp_add,
+ dmp_sub,
+ dmp_mul,
+ dmp_sqr,
+ dmp_pow,
+ dmp_pdiv,
+ dmp_prem,
+ dmp_pquo,
+ dmp_pexquo,
+ dmp_div,
+ dmp_rem,
+ dmp_quo,
+ dmp_exquo,
+ dmp_add_mul, dmp_sub_mul,
+ dmp_max_norm,
+ dmp_l1_norm,
+ dmp_l2_norm_squared)
+
+from sympy.polys.densetools import (
+ dmp_clear_denoms,
+ dmp_integrate_in,
+ dmp_diff_in,
+ dmp_eval_in,
+ dup_revert,
+ dmp_ground_trunc,
+ dmp_ground_content,
+ dmp_ground_primitive,
+ dmp_ground_monic,
+ dmp_compose,
+ dup_decompose,
+ dup_shift,
+ dmp_shift,
+ dup_transform,
+ dmp_lift)
+
+from sympy.polys.euclidtools import (
+ dup_half_gcdex, dup_gcdex, dup_invert,
+ dmp_subresultants,
+ dmp_resultant,
+ dmp_discriminant,
+ dmp_inner_gcd,
+ dmp_gcd,
+ dmp_lcm,
+ dmp_cancel)
+
+from sympy.polys.sqfreetools import (
+ dup_gff_list,
+ dmp_norm,
+ dmp_sqf_p,
+ dmp_sqf_norm,
+ dmp_sqf_part,
+ dmp_sqf_list, dmp_sqf_list_include)
+
+from sympy.polys.factortools import (
+ dup_cyclotomic_p, dmp_irreducible_p,
+ dmp_factor_list, dmp_factor_list_include)
+
+from sympy.polys.rootisolation import (
+ dup_isolate_real_roots_sqf,
+ dup_isolate_real_roots,
+ dup_isolate_all_roots_sqf,
+ dup_isolate_all_roots,
+ dup_refine_real_root,
+ dup_count_real_roots,
+ dup_count_complex_roots,
+ dup_sturm,
+ dup_cauchy_upper_bound,
+ dup_cauchy_lower_bound,
+ dup_mignotte_sep_bound_squared)
+
+from sympy.polys.polyerrors import (
+ UnificationFailed,
+ PolynomialError)
+
+
+_flint_domains: tuple[Domain, ...]
+
+if GROUND_TYPES == 'flint':
+ import flint
+ _flint_domains = (ZZ, QQ)
+else:
+ flint = None
+ _flint_domains = ()
+
+
+class DMP(CantSympify):
+ """Dense Multivariate Polynomials over `K`. """
+
+ __slots__ = ()
+
+ def __new__(cls, rep, dom, lev=None):
+
+ if lev is None:
+ rep, lev = dmp_validate(rep)
+ elif not isinstance(rep, list):
+ raise CoercionFailed("expected list, got %s" % type(rep))
+
+ return cls.new(rep, dom, lev)
+
+ @classmethod
+ def new(cls, rep, dom, lev):
+ # It would be too slow to call _validate_args always at runtime.
+ # Ideally this checking would be handled by a static type checker.
+ #
+ #cls._validate_args(rep, dom, lev)
+ if flint is not None:
+ if lev == 0 and dom in _flint_domains:
+ return DUP_Flint._new(rep, dom, lev)
+
+ return DMP_Python._new(rep, dom, lev)
+
+ @property
+ def rep(f):
+ """Get the representation of ``f``. """
+
+ sympy_deprecation_warning("""
+ Accessing the ``DMP.rep`` attribute is deprecated. The internal
+ representation of ``DMP`` instances can now be ``DUP_Flint`` when the
+ ground types are ``flint``. In this case the ``DMP`` instance does not
+ have a ``rep`` attribute. Use ``DMP.to_list()`` instead. Using
+ ``DMP.to_list()`` also works in previous versions of SymPy.
+ """,
+ deprecated_since_version="1.13",
+ active_deprecations_target="dmp-rep",
+ )
+
+ return f.to_list()
+
+ def to_best(f):
+ """Convert to DUP_Flint if possible.
+
+ This method should be used when the domain or level is changed and it
+ potentially becomes possible to convert from DMP_Python to DUP_Flint.
+ """
+ if flint is not None:
+ if isinstance(f, DMP_Python) and f.lev == 0 and f.dom in _flint_domains:
+ return DUP_Flint.new(f._rep, f.dom, f.lev)
+
+ return f
+
+ @classmethod
+ def _validate_args(cls, rep, dom, lev):
+ assert isinstance(dom, Domain)
+ assert isinstance(lev, int) and lev >= 0
+
+ def validate_rep(rep, lev):
+ assert isinstance(rep, list)
+ if lev == 0:
+ assert all(dom.of_type(c) for c in rep)
+ else:
+ for r in rep:
+ validate_rep(r, lev - 1)
+
+ validate_rep(rep, lev)
+
+ @classmethod
+ def from_dict(cls, rep, lev, dom):
+ rep = dmp_from_dict(rep, lev, dom)
+ return cls.new(rep, dom, lev)
+
+ @classmethod
+ def from_list(cls, rep, lev, dom):
+ """Create an instance of ``cls`` given a list of native coefficients. """
+ return cls.new(dmp_convert(rep, lev, None, dom), dom, lev)
+
+ @classmethod
+ def from_sympy_list(cls, rep, lev, dom):
+ """Create an instance of ``cls`` given a list of SymPy coefficients. """
+ return cls.new(dmp_from_sympy(rep, lev, dom), dom, lev)
+
+ @classmethod
+ def from_monoms_coeffs(cls, monoms, coeffs, lev, dom):
+ return cls(dict(list(zip(monoms, coeffs))), dom, lev)
+
+ def convert(f, dom):
+ """Convert ``f`` to a ``DMP`` over the new domain. """
+ if f.dom == dom:
+ return f
+ elif f.lev or flint is None:
+ return f._convert(dom)
+ elif isinstance(f, DUP_Flint):
+ if dom in _flint_domains:
+ return f._convert(dom)
+ else:
+ return f.to_DMP_Python()._convert(dom)
+ elif isinstance(f, DMP_Python):
+ if dom in _flint_domains:
+ return f._convert(dom).to_DUP_Flint()
+ else:
+ return f._convert(dom)
+ else:
+ raise RuntimeError("unreachable code")
+
+ def _convert(f, dom):
+ raise NotImplementedError
+
+ @classmethod
+ def zero(cls, lev, dom):
+ return DMP(dmp_zero(lev), dom, lev)
+
+ @classmethod
+ def one(cls, lev, dom):
+ return DMP(dmp_one(lev, dom), dom, lev)
+
+ def _one(f):
+ raise NotImplementedError
+
+ def __repr__(f):
+ return "%s(%s, %s)" % (f.__class__.__name__, f.to_list(), f.dom)
+
+ def __hash__(f):
+ return hash((f.__class__.__name__, f.to_tuple(), f.lev, f.dom))
+
+ def __getnewargs__(self):
+ return self.to_list(), self.dom, self.lev
+
+ def ground_new(f, coeff):
+ """Construct a new ground instance of ``f``. """
+ raise NotImplementedError
+
+ def unify_DMP(f, g):
+ """Unify and return ``DMP`` instances of ``f`` and ``g``. """
+ if not isinstance(g, DMP) or f.lev != g.lev:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ if f.dom != g.dom:
+ dom = f.dom.unify(g.dom)
+ f = f.convert(dom)
+ g = g.convert(dom)
+
+ return f, g
+
+ def to_dict(f, zero=False):
+ """Convert ``f`` to a dict representation with native coefficients. """
+ return dmp_to_dict(f.to_list(), f.lev, f.dom, zero=zero)
+
+ def to_sympy_dict(f, zero=False):
+ """Convert ``f`` to a dict representation with SymPy coefficients. """
+ rep = f.to_dict(zero=zero)
+
+ for k, v in rep.items():
+ rep[k] = f.dom.to_sympy(v)
+
+ return rep
+
+ def to_sympy_list(f):
+ """Convert ``f`` to a list representation with SymPy coefficients. """
+ def sympify_nested_list(rep):
+ out = []
+ for val in rep:
+ if isinstance(val, list):
+ out.append(sympify_nested_list(val))
+ else:
+ out.append(f.dom.to_sympy(val))
+ return out
+
+ return sympify_nested_list(f.to_list())
+
+ def to_list(f):
+ """Convert ``f`` to a list representation with native coefficients. """
+ raise NotImplementedError
+
+ def to_tuple(f):
+ """
+ Convert ``f`` to a tuple representation with native coefficients.
+
+ This is needed for hashing.
+ """
+ raise NotImplementedError
+
+ def to_ring(f):
+ """Make the ground domain a ring. """
+ return f.convert(f.dom.get_ring())
+
+ def to_field(f):
+ """Make the ground domain a field. """
+ return f.convert(f.dom.get_field())
+
+ def to_exact(f):
+ """Make the ground domain exact. """
+ return f.convert(f.dom.get_exact())
+
+ def slice(f, m, n, j=0):
+ """Take a continuous subsequence of terms of ``f``. """
+ if not f.lev and not j:
+ return f._slice(m, n)
+ else:
+ return f._slice_lev(m, n, j)
+
+ def _slice(f, m, n):
+ raise NotImplementedError
+
+ def _slice_lev(f, m, n, j):
+ raise NotImplementedError
+
+ def coeffs(f, order=None):
+ """Returns all non-zero coefficients from ``f`` in lex order. """
+ return [ c for _, c in f.terms(order=order) ]
+
+ def monoms(f, order=None):
+ """Returns all non-zero monomials from ``f`` in lex order. """
+ return [ m for m, _ in f.terms(order=order) ]
+
+ def terms(f, order=None):
+ """Returns all non-zero terms from ``f`` in lex order. """
+ if f.is_zero:
+ zero_monom = (0,)*(f.lev + 1)
+ return [(zero_monom, f.dom.zero)]
+ else:
+ return f._terms(order=order)
+
+ def _terms(f, order=None):
+ raise NotImplementedError
+
+ def all_coeffs(f):
+ """Returns all coefficients from ``f``. """
+ if f.lev:
+ raise PolynomialError('multivariate polynomials not supported')
+
+ if not f:
+ return [f.dom.zero]
+ else:
+ return list(f.to_list())
+
+ def all_monoms(f):
+ """Returns all monomials from ``f``. """
+ if f.lev:
+ raise PolynomialError('multivariate polynomials not supported')
+
+ n = f.degree()
+
+ if n < 0:
+ return [(0,)]
+ else:
+ return [ (n - i,) for i, c in enumerate(f.to_list()) ]
+
+ def all_terms(f):
+ """Returns all terms from a ``f``. """
+ if f.lev:
+ raise PolynomialError('multivariate polynomials not supported')
+
+ n = f.degree()
+
+ if n < 0:
+ return [((0,), f.dom.zero)]
+ else:
+ return [ ((n - i,), c) for i, c in enumerate(f.to_list()) ]
+
+ def lift(f):
+ """Convert algebraic coefficients to rationals. """
+ return f._lift().to_best()
+
+ def _lift(f):
+ raise NotImplementedError
+
+ def deflate(f):
+ """Reduce degree of `f` by mapping `x_i^m` to `y_i`. """
+ raise NotImplementedError
+
+ def inject(f, front=False):
+ """Inject ground domain generators into ``f``. """
+ raise NotImplementedError
+
+ def eject(f, dom, front=False):
+ """Eject selected generators into the ground domain. """
+ raise NotImplementedError
+
+ def exclude(f):
+ r"""
+ Remove useless generators from ``f``.
+
+ Returns the removed generators and the new excluded ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polyclasses import DMP
+ >>> from sympy.polys.domains import ZZ
+
+ >>> DMP([[[ZZ(1)]], [[ZZ(1)], [ZZ(2)]]], ZZ).exclude()
+ ([2], DMP_Python([[1], [1, 2]], ZZ))
+
+ """
+ J, F = f._exclude()
+ return J, F.to_best()
+
+ def _exclude(f):
+ raise NotImplementedError
+
+ def permute(f, P):
+ r"""
+ Returns a polynomial in `K[x_{P(1)}, ..., x_{P(n)}]`.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polyclasses import DMP
+ >>> from sympy.polys.domains import ZZ
+
+ >>> DMP([[[ZZ(2)], [ZZ(1), ZZ(0)]], [[]]], ZZ).permute([1, 0, 2])
+ DMP_Python([[[2], []], [[1, 0], []]], ZZ)
+
+ >>> DMP([[[ZZ(2)], [ZZ(1), ZZ(0)]], [[]]], ZZ).permute([1, 2, 0])
+ DMP_Python([[[1], []], [[2, 0], []]], ZZ)
+
+ """
+ return f._permute(P)
+
+ def _permute(f, P):
+ raise NotImplementedError
+
+ def terms_gcd(f):
+ """Remove GCD of terms from the polynomial ``f``. """
+ raise NotImplementedError
+
+ def abs(f):
+ """Make all coefficients in ``f`` positive. """
+ raise NotImplementedError
+
+ def neg(f):
+ """Negate all coefficients in ``f``. """
+ raise NotImplementedError
+
+ def add_ground(f, c):
+ """Add an element of the ground domain to ``f``. """
+ return f._add_ground(f.dom.convert(c))
+
+ def sub_ground(f, c):
+ """Subtract an element of the ground domain from ``f``. """
+ return f._sub_ground(f.dom.convert(c))
+
+ def mul_ground(f, c):
+ """Multiply ``f`` by a an element of the ground domain. """
+ return f._mul_ground(f.dom.convert(c))
+
+ def quo_ground(f, c):
+ """Quotient of ``f`` by a an element of the ground domain. """
+ return f._quo_ground(f.dom.convert(c))
+
+ def exquo_ground(f, c):
+ """Exact quotient of ``f`` by a an element of the ground domain. """
+ return f._exquo_ground(f.dom.convert(c))
+
+ def add(f, g):
+ """Add two multivariate polynomials ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._add(G)
+
+ def sub(f, g):
+ """Subtract two multivariate polynomials ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._sub(G)
+
+ def mul(f, g):
+ """Multiply two multivariate polynomials ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._mul(G)
+
+ def sqr(f):
+ """Square a multivariate polynomial ``f``. """
+ return f._sqr()
+
+ def pow(f, n):
+ """Raise ``f`` to a non-negative power ``n``. """
+ if not isinstance(n, int):
+ raise TypeError("``int`` expected, got %s" % type(n))
+ return f._pow(n)
+
+ def pdiv(f, g):
+ """Polynomial pseudo-division of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._pdiv(G)
+
+ def prem(f, g):
+ """Polynomial pseudo-remainder of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._prem(G)
+
+ def pquo(f, g):
+ """Polynomial pseudo-quotient of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._pquo(G)
+
+ def pexquo(f, g):
+ """Polynomial exact pseudo-quotient of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._pexquo(G)
+
+ def div(f, g):
+ """Polynomial division with remainder of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._div(G)
+
+ def rem(f, g):
+ """Computes polynomial remainder of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._rem(G)
+
+ def quo(f, g):
+ """Computes polynomial quotient of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._quo(G)
+
+ def exquo(f, g):
+ """Computes polynomial exact quotient of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._exquo(G)
+
+ def _add_ground(f, c):
+ raise NotImplementedError
+
+ def _sub_ground(f, c):
+ raise NotImplementedError
+
+ def _mul_ground(f, c):
+ raise NotImplementedError
+
+ def _quo_ground(f, c):
+ raise NotImplementedError
+
+ def _exquo_ground(f, c):
+ raise NotImplementedError
+
+ def _add(f, g):
+ raise NotImplementedError
+
+ def _sub(f, g):
+ raise NotImplementedError
+
+ def _mul(f, g):
+ raise NotImplementedError
+
+ def _sqr(f):
+ raise NotImplementedError
+
+ def _pow(f, n):
+ raise NotImplementedError
+
+ def _pdiv(f, g):
+ raise NotImplementedError
+
+ def _prem(f, g):
+ raise NotImplementedError
+
+ def _pquo(f, g):
+ raise NotImplementedError
+
+ def _pexquo(f, g):
+ raise NotImplementedError
+
+ def _div(f, g):
+ raise NotImplementedError
+
+ def _rem(f, g):
+ raise NotImplementedError
+
+ def _quo(f, g):
+ raise NotImplementedError
+
+ def _exquo(f, g):
+ raise NotImplementedError
+
+ def degree(f, j=0):
+ """Returns the leading degree of ``f`` in ``x_j``. """
+ if not isinstance(j, int):
+ raise TypeError("``int`` expected, got %s" % type(j))
+
+ return f._degree(j)
+
+ def _degree(f, j):
+ raise NotImplementedError
+
+ def degree_list(f):
+ """Returns a list of degrees of ``f``. """
+ raise NotImplementedError
+
+ def total_degree(f):
+ """Returns the total degree of ``f``. """
+ raise NotImplementedError
+
+ def homogenize(f, s):
+ """Return homogeneous polynomial of ``f``"""
+ td = f.total_degree()
+ result = {}
+ new_symbol = (s == len(f.terms()[0][0]))
+ for term in f.terms():
+ d = sum(term[0])
+ if d < td:
+ i = td - d
+ else:
+ i = 0
+ if new_symbol:
+ result[term[0] + (i,)] = term[1]
+ else:
+ l = list(term[0])
+ l[s] += i
+ result[tuple(l)] = term[1]
+ return DMP.from_dict(result, f.lev + int(new_symbol), f.dom)
+
+ def homogeneous_order(f):
+ """Returns the homogeneous order of ``f``. """
+ if f.is_zero:
+ return -oo
+
+ monoms = f.monoms()
+ tdeg = sum(monoms[0])
+
+ for monom in monoms:
+ _tdeg = sum(monom)
+
+ if _tdeg != tdeg:
+ return None
+
+ return tdeg
+
+ def LC(f):
+ """Returns the leading coefficient of ``f``. """
+ raise NotImplementedError
+
+ def TC(f):
+ """Returns the trailing coefficient of ``f``. """
+ raise NotImplementedError
+
+ def nth(f, *N):
+ """Returns the ``n``-th coefficient of ``f``. """
+ if all(isinstance(n, int) for n in N):
+ return f._nth(N)
+ else:
+ raise TypeError("a sequence of integers expected")
+
+ def _nth(f, N):
+ raise NotImplementedError
+
+ def max_norm(f):
+ """Returns maximum norm of ``f``. """
+ raise NotImplementedError
+
+ def l1_norm(f):
+ """Returns l1 norm of ``f``. """
+ raise NotImplementedError
+
+ def l2_norm_squared(f):
+ """Return squared l2 norm of ``f``. """
+ raise NotImplementedError
+
+ def clear_denoms(f):
+ """Clear denominators, but keep the ground domain. """
+ raise NotImplementedError
+
+ def integrate(f, m=1, j=0):
+ """Computes the ``m``-th order indefinite integral of ``f`` in ``x_j``. """
+ if not isinstance(m, int):
+ raise TypeError("``int`` expected, got %s" % type(m))
+
+ if not isinstance(j, int):
+ raise TypeError("``int`` expected, got %s" % type(j))
+
+ return f._integrate(m, j)
+
+ def _integrate(f, m, j):
+ raise NotImplementedError
+
+ def diff(f, m=1, j=0):
+ """Computes the ``m``-th order derivative of ``f`` in ``x_j``. """
+ if not isinstance(m, int):
+ raise TypeError("``int`` expected, got %s" % type(m))
+
+ if not isinstance(j, int):
+ raise TypeError("``int`` expected, got %s" % type(j))
+
+ return f._diff(m, j)
+
+ def _diff(f, m, j):
+ raise NotImplementedError
+
+ def eval(f, a, j=0):
+ """Evaluates ``f`` at the given point ``a`` in ``x_j``. """
+ if not isinstance(j, int):
+ raise TypeError("``int`` expected, got %s" % type(j))
+ elif not (0 <= j <= f.lev):
+ raise ValueError("invalid variable index %s" % j)
+
+ if f.lev:
+ return f._eval_lev(a, j)
+ else:
+ return f._eval(a)
+
+ def _eval(f, a):
+ raise NotImplementedError
+
+ def _eval_lev(f, a, j):
+ raise NotImplementedError
+
+ def half_gcdex(f, g):
+ """Half extended Euclidean algorithm, if univariate. """
+ F, G = f.unify_DMP(g)
+
+ if F.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return F._half_gcdex(G)
+
+ def _half_gcdex(f, g):
+ raise NotImplementedError
+
+ def gcdex(f, g):
+ """Extended Euclidean algorithm, if univariate. """
+ F, G = f.unify_DMP(g)
+
+ if F.lev:
+ raise ValueError('univariate polynomial expected')
+
+ if not F.dom.is_Field:
+ raise DomainError('ground domain must be a field')
+
+ return F._gcdex(G)
+
+ def _gcdex(f, g):
+ raise NotImplementedError
+
+ def invert(f, g):
+ """Invert ``f`` modulo ``g``, if possible. """
+ F, G = f.unify_DMP(g)
+
+ if F.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return F._invert(G)
+
+ def _invert(f, g):
+ raise NotImplementedError
+
+ def revert(f, n):
+ """Compute ``f**(-1)`` mod ``x**n``. """
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return f._revert(n)
+
+ def _revert(f, n):
+ raise NotImplementedError
+
+ def subresultants(f, g):
+ """Computes subresultant PRS sequence of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._subresultants(G)
+
+ def _subresultants(f, g):
+ raise NotImplementedError
+
+ def resultant(f, g, includePRS=False):
+ """Computes resultant of ``f`` and ``g`` via PRS. """
+ F, G = f.unify_DMP(g)
+ if includePRS:
+ return F._resultant_includePRS(G)
+ else:
+ return F._resultant(G)
+
+ def _resultant(f, g, includePRS=False):
+ raise NotImplementedError
+
+ def discriminant(f):
+ """Computes discriminant of ``f``. """
+ raise NotImplementedError
+
+ def cofactors(f, g):
+ """Returns GCD of ``f`` and ``g`` and their cofactors. """
+ F, G = f.unify_DMP(g)
+ return F._cofactors(G)
+
+ def _cofactors(f, g):
+ raise NotImplementedError
+
+ def gcd(f, g):
+ """Returns polynomial GCD of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._gcd(G)
+
+ def _gcd(f, g):
+ raise NotImplementedError
+
+ def lcm(f, g):
+ """Returns polynomial LCM of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._lcm(G)
+
+ def _lcm(f, g):
+ raise NotImplementedError
+
+ def cancel(f, g, include=True):
+ """Cancel common factors in a rational function ``f/g``. """
+ F, G = f.unify_DMP(g)
+
+ if include:
+ return F._cancel_include(G)
+ else:
+ return F._cancel(G)
+
+ def _cancel(f, g):
+ raise NotImplementedError
+
+ def _cancel_include(f, g):
+ raise NotImplementedError
+
+ def trunc(f, p):
+ """Reduce ``f`` modulo a constant ``p``. """
+ return f._trunc(f.dom.convert(p))
+
+ def _trunc(f, p):
+ raise NotImplementedError
+
+ def monic(f):
+ """Divides all coefficients by ``LC(f)``. """
+ raise NotImplementedError
+
+ def content(f):
+ """Returns GCD of polynomial coefficients. """
+ raise NotImplementedError
+
+ def primitive(f):
+ """Returns content and a primitive form of ``f``. """
+ raise NotImplementedError
+
+ def compose(f, g):
+ """Computes functional composition of ``f`` and ``g``. """
+ F, G = f.unify_DMP(g)
+ return F._compose(G)
+
+ def _compose(f, g):
+ raise NotImplementedError
+
+ def decompose(f):
+ """Computes functional decomposition of ``f``. """
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return f._decompose()
+
+ def _decompose(f):
+ raise NotImplementedError
+
+ def shift(f, a):
+ """Efficiently compute Taylor shift ``f(x + a)``. """
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return f._shift(f.dom.convert(a))
+
+ def shift_list(f, a):
+ """Efficiently compute Taylor shift ``f(X + A)``. """
+ a = [f.dom.convert(ai) for ai in a]
+ return f._shift_list(a)
+
+ def _shift(f, a):
+ raise NotImplementedError
+
+ def transform(f, p, q):
+ """Evaluate functional transformation ``q**n * f(p/q)``."""
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ P, Q = p.unify_DMP(q)
+ F, P = f.unify_DMP(P)
+ F, Q = F.unify_DMP(Q)
+
+ return F._transform(P, Q)
+
+ def _transform(f, p, q):
+ raise NotImplementedError
+
+ def sturm(f):
+ """Computes the Sturm sequence of ``f``. """
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return f._sturm()
+
+ def _sturm(f):
+ raise NotImplementedError
+
+ def cauchy_upper_bound(f):
+ """Computes the Cauchy upper bound on the roots of ``f``. """
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return f._cauchy_upper_bound()
+
+ def _cauchy_upper_bound(f):
+ raise NotImplementedError
+
+ def cauchy_lower_bound(f):
+ """Computes the Cauchy lower bound on the nonzero roots of ``f``. """
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return f._cauchy_lower_bound()
+
+ def _cauchy_lower_bound(f):
+ raise NotImplementedError
+
+ def mignotte_sep_bound_squared(f):
+ """Computes the squared Mignotte bound on root separations of ``f``. """
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return f._mignotte_sep_bound_squared()
+
+ def _mignotte_sep_bound_squared(f):
+ raise NotImplementedError
+
+ def gff_list(f):
+ """Computes greatest factorial factorization of ``f``. """
+ if f.lev:
+ raise ValueError('univariate polynomial expected')
+
+ return f._gff_list()
+
+ def _gff_list(f):
+ raise NotImplementedError
+
+ def norm(f):
+ """Computes ``Norm(f)``."""
+ raise NotImplementedError
+
+ def sqf_norm(f):
+ """Computes square-free norm of ``f``. """
+ raise NotImplementedError
+
+ def sqf_part(f):
+ """Computes square-free part of ``f``. """
+ raise NotImplementedError
+
+ def sqf_list(f, all=False):
+ """Returns a list of square-free factors of ``f``. """
+ raise NotImplementedError
+
+ def sqf_list_include(f, all=False):
+ """Returns a list of square-free factors of ``f``. """
+ raise NotImplementedError
+
+ def factor_list(f):
+ """Returns a list of irreducible factors of ``f``. """
+ raise NotImplementedError
+
+ def factor_list_include(f):
+ """Returns a list of irreducible factors of ``f``. """
+ raise NotImplementedError
+
+ def intervals(f, all=False, eps=None, inf=None, sup=None, fast=False, sqf=False):
+ """Compute isolating intervals for roots of ``f``. """
+ if f.lev:
+ raise PolynomialError("Cannot isolate roots of a multivariate polynomial")
+
+ if all and sqf:
+ return f._isolate_all_roots_sqf(eps=eps, inf=inf, sup=sup, fast=fast)
+ elif all and not sqf:
+ return f._isolate_all_roots(eps=eps, inf=inf, sup=sup, fast=fast)
+ elif not all and sqf:
+ return f._isolate_real_roots_sqf(eps=eps, inf=inf, sup=sup, fast=fast)
+ else:
+ return f._isolate_real_roots(eps=eps, inf=inf, sup=sup, fast=fast)
+
+ def _isolate_all_roots(f, eps, inf, sup, fast):
+ raise NotImplementedError
+
+ def _isolate_all_roots_sqf(f, eps, inf, sup, fast):
+ raise NotImplementedError
+
+ def _isolate_real_roots(f, eps, inf, sup, fast):
+ raise NotImplementedError
+
+ def _isolate_real_roots_sqf(f, eps, inf, sup, fast):
+ raise NotImplementedError
+
+ def refine_root(f, s, t, eps=None, steps=None, fast=False):
+ """
+ Refine an isolating interval to the given precision.
+
+ ``eps`` should be a rational number.
+
+ """
+ if f.lev:
+ raise PolynomialError(
+ "Cannot refine a root of a multivariate polynomial")
+
+ return f._refine_real_root(s, t, eps=eps, steps=steps, fast=fast)
+
+ def _refine_real_root(f, s, t, eps, steps, fast):
+ raise NotImplementedError
+
+ def count_real_roots(f, inf=None, sup=None):
+ """Return the number of real roots of ``f`` in ``[inf, sup]``. """
+ raise NotImplementedError
+
+ def count_complex_roots(f, inf=None, sup=None):
+ """Return the number of complex roots of ``f`` in ``[inf, sup]``. """
+ raise NotImplementedError
+
+ @property
+ def is_zero(f):
+ """Returns ``True`` if ``f`` is a zero polynomial. """
+ raise NotImplementedError
+
+ @property
+ def is_one(f):
+ """Returns ``True`` if ``f`` is a unit polynomial. """
+ raise NotImplementedError
+
+ @property
+ def is_ground(f):
+ """Returns ``True`` if ``f`` is an element of the ground domain. """
+ raise NotImplementedError
+
+ @property
+ def is_sqf(f):
+ """Returns ``True`` if ``f`` is a square-free polynomial. """
+ raise NotImplementedError
+
+ @property
+ def is_monic(f):
+ """Returns ``True`` if the leading coefficient of ``f`` is one. """
+ raise NotImplementedError
+
+ @property
+ def is_primitive(f):
+ """Returns ``True`` if the GCD of the coefficients of ``f`` is one. """
+ raise NotImplementedError
+
+ @property
+ def is_linear(f):
+ """Returns ``True`` if ``f`` is linear in all its variables. """
+ raise NotImplementedError
+
+ @property
+ def is_quadratic(f):
+ """Returns ``True`` if ``f`` is quadratic in all its variables. """
+ raise NotImplementedError
+
+ @property
+ def is_monomial(f):
+ """Returns ``True`` if ``f`` is zero or has only one term. """
+ raise NotImplementedError
+
+ @property
+ def is_homogeneous(f):
+ """Returns ``True`` if ``f`` is a homogeneous polynomial. """
+ raise NotImplementedError
+
+ @property
+ def is_irreducible(f):
+ """Returns ``True`` if ``f`` has no factors over its domain. """
+ raise NotImplementedError
+
+ @property
+ def is_cyclotomic(f):
+ """Returns ``True`` if ``f`` is a cyclotomic polynomial. """
+ raise NotImplementedError
+
+ def __abs__(f):
+ return f.abs()
+
+ def __neg__(f):
+ return f.neg()
+
+ def __add__(f, g):
+ if isinstance(g, DMP):
+ return f.add(g)
+ else:
+ try:
+ return f.add_ground(g)
+ except CoercionFailed:
+ return NotImplemented
+
+ def __radd__(f, g):
+ return f.__add__(g)
+
+ def __sub__(f, g):
+ if isinstance(g, DMP):
+ return f.sub(g)
+ else:
+ try:
+ return f.sub_ground(g)
+ except CoercionFailed:
+ return NotImplemented
+
+ def __rsub__(f, g):
+ return (-f).__add__(g)
+
+ def __mul__(f, g):
+ if isinstance(g, DMP):
+ return f.mul(g)
+ else:
+ try:
+ return f.mul_ground(g)
+ except CoercionFailed:
+ return NotImplemented
+
+ def __rmul__(f, g):
+ return f.__mul__(g)
+
+ def __truediv__(f, g):
+ if isinstance(g, DMP):
+ return f.exquo(g)
+ else:
+ try:
+ return f.mul_ground(g)
+ except CoercionFailed:
+ return NotImplemented
+
+ def __rtruediv__(f, g):
+ if isinstance(g, DMP):
+ return g.exquo(f)
+ else:
+ try:
+ return f._one().mul_ground(g).exquo(f)
+ except CoercionFailed:
+ return NotImplemented
+
+ def __pow__(f, n):
+ return f.pow(n)
+
+ def __divmod__(f, g):
+ return f.div(g)
+
+ def __mod__(f, g):
+ return f.rem(g)
+
+ def __floordiv__(f, g):
+ if isinstance(g, DMP):
+ return f.quo(g)
+ else:
+ try:
+ return f.quo_ground(g)
+ except TypeError:
+ return NotImplemented
+
+ def __eq__(f, g):
+ if f is g:
+ return True
+ if not isinstance(g, DMP):
+ return NotImplemented
+ try:
+ F, G = f.unify_DMP(g)
+ except UnificationFailed:
+ return False
+ else:
+ return F._strict_eq(G)
+
+ def _strict_eq(f, g):
+ raise NotImplementedError
+
+ def eq(f, g, strict=False):
+ if not strict:
+ return f == g
+ else:
+ return f._strict_eq(g)
+
+ def ne(f, g, strict=False):
+ return not f.eq(g, strict=strict)
+
+ def __lt__(f, g):
+ F, G = f.unify_DMP(g)
+ return F.to_list() < G.to_list()
+
+ def __le__(f, g):
+ F, G = f.unify_DMP(g)
+ return F.to_list() <= G.to_list()
+
+ def __gt__(f, g):
+ F, G = f.unify_DMP(g)
+ return F.to_list() > G.to_list()
+
+ def __ge__(f, g):
+ F, G = f.unify_DMP(g)
+ return F.to_list() >= G.to_list()
+
+ def __bool__(f):
+ return not f.is_zero
+
+
+class DMP_Python(DMP):
+ """Dense Multivariate Polynomials over `K`. """
+
+ __slots__ = ('_rep', 'dom', 'lev')
+
+ @classmethod
+ def _new(cls, rep, dom, lev):
+ obj = object.__new__(cls)
+ obj._rep = rep
+ obj.lev = lev
+ obj.dom = dom
+ return obj
+
+ def _strict_eq(f, g):
+ if type(f) != type(g):
+ return False
+ return f.lev == g.lev and f.dom == g.dom and f._rep == g._rep
+
+ def per(f, rep):
+ """Create a DMP out of the given representation. """
+ return f._new(rep, f.dom, f.lev)
+
+ def ground_new(f, coeff):
+ """Construct a new ground instance of ``f``. """
+ return f._new(dmp_ground(coeff, f.lev), f.dom, f.lev)
+
+ def _one(f):
+ return f.one(f.lev, f.dom)
+
+ def unify(f, g):
+ """Unify representations of two multivariate polynomials. """
+ # XXX: This function is not really used any more since there is
+ # unify_DMP now.
+ if not isinstance(g, DMP) or f.lev != g.lev:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ if f.dom == g.dom:
+ return f.lev, f.dom, f.per, f._rep, g._rep
+ else:
+ lev, dom = f.lev, f.dom.unify(g.dom)
+
+ F = dmp_convert(f._rep, lev, f.dom, dom)
+ G = dmp_convert(g._rep, lev, g.dom, dom)
+
+ def per(rep):
+ return f._new(rep, dom, lev)
+
+ return lev, dom, per, F, G
+
+ def to_DUP_Flint(f):
+ """Convert ``f`` to a Flint representation. """
+ return DUP_Flint._new(f._rep, f.dom, f.lev)
+
+ def to_list(f):
+ """Convert ``f`` to a list representation with native coefficients. """
+ return list(f._rep)
+
+ def to_tuple(f):
+ """Convert ``f`` to a tuple representation with native coefficients. """
+ return dmp_to_tuple(f._rep, f.lev)
+
+ def _convert(f, dom):
+ """Convert the ground domain of ``f``. """
+ return f._new(dmp_convert(f._rep, f.lev, f.dom, dom), dom, f.lev)
+
+ def _slice(f, m, n):
+ """Take a continuous subsequence of terms of ``f``. """
+ rep = dup_slice(f._rep, m, n, f.dom)
+ return f._new(rep, f.dom, f.lev)
+
+ def _slice_lev(f, m, n, j):
+ """Take a continuous subsequence of terms of ``f``. """
+ rep = dmp_slice_in(f._rep, m, n, j, f.lev, f.dom)
+ return f._new(rep, f.dom, f.lev)
+
+ def _terms(f, order=None):
+ """Returns all non-zero terms from ``f`` in lex order. """
+ return dmp_list_terms(f._rep, f.lev, f.dom, order=order)
+
+ def _lift(f):
+ """Convert algebraic coefficients to rationals. """
+ r = dmp_lift(f._rep, f.lev, f.dom)
+ return f._new(r, f.dom.dom, f.lev)
+
+ def deflate(f):
+ """Reduce degree of `f` by mapping `x_i^m` to `y_i`. """
+ J, F = dmp_deflate(f._rep, f.lev, f.dom)
+ return J, f.per(F)
+
+ def inject(f, front=False):
+ """Inject ground domain generators into ``f``. """
+ F, lev = dmp_inject(f._rep, f.lev, f.dom, front=front)
+ # XXX: domain and level changed here
+ return f._new(F, f.dom.dom, lev)
+
+ def eject(f, dom, front=False):
+ """Eject selected generators into the ground domain. """
+ F = dmp_eject(f._rep, f.lev, dom, front=front)
+ # XXX: domain and level changed here
+ return f._new(F, dom, f.lev - len(dom.symbols))
+
+ def _exclude(f):
+ """Remove useless generators from ``f``. """
+ J, F, u = dmp_exclude(f._rep, f.lev, f.dom)
+ # XXX: level changed here
+ return J, f._new(F, f.dom, u)
+
+ def _permute(f, P):
+ """Returns a polynomial in `K[x_{P(1)}, ..., x_{P(n)}]`. """
+ return f.per(dmp_permute(f._rep, P, f.lev, f.dom))
+
+ def terms_gcd(f):
+ """Remove GCD of terms from the polynomial ``f``. """
+ J, F = dmp_terms_gcd(f._rep, f.lev, f.dom)
+ return J, f.per(F)
+
+ def _add_ground(f, c):
+ """Add an element of the ground domain to ``f``. """
+ return f.per(dmp_add_ground(f._rep, c, f.lev, f.dom))
+
+ def _sub_ground(f, c):
+ """Subtract an element of the ground domain from ``f``. """
+ return f.per(dmp_sub_ground(f._rep, c, f.lev, f.dom))
+
+ def _mul_ground(f, c):
+ """Multiply ``f`` by a an element of the ground domain. """
+ return f.per(dmp_mul_ground(f._rep, c, f.lev, f.dom))
+
+ def _quo_ground(f, c):
+ """Quotient of ``f`` by a an element of the ground domain. """
+ return f.per(dmp_quo_ground(f._rep, c, f.lev, f.dom))
+
+ def _exquo_ground(f, c):
+ """Exact quotient of ``f`` by a an element of the ground domain. """
+ return f.per(dmp_exquo_ground(f._rep, c, f.lev, f.dom))
+
+ def abs(f):
+ """Make all coefficients in ``f`` positive. """
+ return f.per(dmp_abs(f._rep, f.lev, f.dom))
+
+ def neg(f):
+ """Negate all coefficients in ``f``. """
+ return f.per(dmp_neg(f._rep, f.lev, f.dom))
+
+ def _add(f, g):
+ """Add two multivariate polynomials ``f`` and ``g``. """
+ return f.per(dmp_add(f._rep, g._rep, f.lev, f.dom))
+
+ def _sub(f, g):
+ """Subtract two multivariate polynomials ``f`` and ``g``. """
+ return f.per(dmp_sub(f._rep, g._rep, f.lev, f.dom))
+
+ def _mul(f, g):
+ """Multiply two multivariate polynomials ``f`` and ``g``. """
+ return f.per(dmp_mul(f._rep, g._rep, f.lev, f.dom))
+
+ def sqr(f):
+ """Square a multivariate polynomial ``f``. """
+ return f.per(dmp_sqr(f._rep, f.lev, f.dom))
+
+ def _pow(f, n):
+ """Raise ``f`` to a non-negative power ``n``. """
+ return f.per(dmp_pow(f._rep, n, f.lev, f.dom))
+
+ def _pdiv(f, g):
+ """Polynomial pseudo-division of ``f`` and ``g``. """
+ q, r = dmp_pdiv(f._rep, g._rep, f.lev, f.dom)
+ return f.per(q), f.per(r)
+
+ def _prem(f, g):
+ """Polynomial pseudo-remainder of ``f`` and ``g``. """
+ return f.per(dmp_prem(f._rep, g._rep, f.lev, f.dom))
+
+ def _pquo(f, g):
+ """Polynomial pseudo-quotient of ``f`` and ``g``. """
+ return f.per(dmp_pquo(f._rep, g._rep, f.lev, f.dom))
+
+ def _pexquo(f, g):
+ """Polynomial exact pseudo-quotient of ``f`` and ``g``. """
+ return f.per(dmp_pexquo(f._rep, g._rep, f.lev, f.dom))
+
+ def _div(f, g):
+ """Polynomial division with remainder of ``f`` and ``g``. """
+ q, r = dmp_div(f._rep, g._rep, f.lev, f.dom)
+ return f.per(q), f.per(r)
+
+ def _rem(f, g):
+ """Computes polynomial remainder of ``f`` and ``g``. """
+ return f.per(dmp_rem(f._rep, g._rep, f.lev, f.dom))
+
+ def _quo(f, g):
+ """Computes polynomial quotient of ``f`` and ``g``. """
+ return f.per(dmp_quo(f._rep, g._rep, f.lev, f.dom))
+
+ def _exquo(f, g):
+ """Computes polynomial exact quotient of ``f`` and ``g``. """
+ return f.per(dmp_exquo(f._rep, g._rep, f.lev, f.dom))
+
+ def _degree(f, j=0):
+ """Returns the leading degree of ``f`` in ``x_j``. """
+ return dmp_degree_in(f._rep, j, f.lev)
+
+ def degree_list(f):
+ """Returns a list of degrees of ``f``. """
+ return dmp_degree_list(f._rep, f.lev)
+
+ def total_degree(f):
+ """Returns the total degree of ``f``. """
+ return max(sum(m) for m in f.monoms())
+
+ def LC(f):
+ """Returns the leading coefficient of ``f``. """
+ return dmp_ground_LC(f._rep, f.lev, f.dom)
+
+ def TC(f):
+ """Returns the trailing coefficient of ``f``. """
+ return dmp_ground_TC(f._rep, f.lev, f.dom)
+
+ def _nth(f, N):
+ """Returns the ``n``-th coefficient of ``f``. """
+ return dmp_ground_nth(f._rep, N, f.lev, f.dom)
+
+ def max_norm(f):
+ """Returns maximum norm of ``f``. """
+ return dmp_max_norm(f._rep, f.lev, f.dom)
+
+ def l1_norm(f):
+ """Returns l1 norm of ``f``. """
+ return dmp_l1_norm(f._rep, f.lev, f.dom)
+
+ def l2_norm_squared(f):
+ """Return squared l2 norm of ``f``. """
+ return dmp_l2_norm_squared(f._rep, f.lev, f.dom)
+
+ def clear_denoms(f):
+ """Clear denominators, but keep the ground domain. """
+ coeff, F = dmp_clear_denoms(f._rep, f.lev, f.dom)
+ return coeff, f.per(F)
+
+ def _integrate(f, m=1, j=0):
+ """Computes the ``m``-th order indefinite integral of ``f`` in ``x_j``. """
+ return f.per(dmp_integrate_in(f._rep, m, j, f.lev, f.dom))
+
+ def _diff(f, m=1, j=0):
+ """Computes the ``m``-th order derivative of ``f`` in ``x_j``. """
+ return f.per(dmp_diff_in(f._rep, m, j, f.lev, f.dom))
+
+ def _eval(f, a):
+ return dmp_eval_in(f._rep, f.dom.convert(a), 0, f.lev, f.dom)
+
+ def _eval_lev(f, a, j):
+ rep = dmp_eval_in(f._rep, f.dom.convert(a), j, f.lev, f.dom)
+ return f.new(rep, f.dom, f.lev - 1)
+
+ def _half_gcdex(f, g):
+ """Half extended Euclidean algorithm, if univariate. """
+ s, h = dup_half_gcdex(f._rep, g._rep, f.dom)
+ return f.per(s), f.per(h)
+
+ def _gcdex(f, g):
+ """Extended Euclidean algorithm, if univariate. """
+ s, t, h = dup_gcdex(f._rep, g._rep, f.dom)
+ return f.per(s), f.per(t), f.per(h)
+
+ def _invert(f, g):
+ """Invert ``f`` modulo ``g``, if possible. """
+ s = dup_invert(f._rep, g._rep, f.dom)
+ return f.per(s)
+
+ def _revert(f, n):
+ """Compute ``f**(-1)`` mod ``x**n``. """
+ return f.per(dup_revert(f._rep, n, f.dom))
+
+ def _subresultants(f, g):
+ """Computes subresultant PRS sequence of ``f`` and ``g``. """
+ R = dmp_subresultants(f._rep, g._rep, f.lev, f.dom)
+ return list(map(f.per, R))
+
+ def _resultant_includePRS(f, g):
+ """Computes resultant of ``f`` and ``g`` via PRS. """
+ res, R = dmp_resultant(f._rep, g._rep, f.lev, f.dom, includePRS=True)
+ if f.lev:
+ res = f.new(res, f.dom, f.lev - 1)
+ return res, list(map(f.per, R))
+
+ def _resultant(f, g):
+ res = dmp_resultant(f._rep, g._rep, f.lev, f.dom)
+ if f.lev:
+ res = f.new(res, f.dom, f.lev - 1)
+ return res
+
+ def discriminant(f):
+ """Computes discriminant of ``f``. """
+ res = dmp_discriminant(f._rep, f.lev, f.dom)
+ if f.lev:
+ res = f.new(res, f.dom, f.lev - 1)
+ return res
+
+ def _cofactors(f, g):
+ """Returns GCD of ``f`` and ``g`` and their cofactors. """
+ h, cff, cfg = dmp_inner_gcd(f._rep, g._rep, f.lev, f.dom)
+ return f.per(h), f.per(cff), f.per(cfg)
+
+ def _gcd(f, g):
+ """Returns polynomial GCD of ``f`` and ``g``. """
+ return f.per(dmp_gcd(f._rep, g._rep, f.lev, f.dom))
+
+ def _lcm(f, g):
+ """Returns polynomial LCM of ``f`` and ``g``. """
+ return f.per(dmp_lcm(f._rep, g._rep, f.lev, f.dom))
+
+ def _cancel(f, g):
+ """Cancel common factors in a rational function ``f/g``. """
+ cF, cG, F, G = dmp_cancel(f._rep, g._rep, f.lev, f.dom, include=False)
+ return cF, cG, f.per(F), f.per(G)
+
+ def _cancel_include(f, g):
+ """Cancel common factors in a rational function ``f/g``. """
+ F, G = dmp_cancel(f._rep, g._rep, f.lev, f.dom, include=True)
+ return f.per(F), f.per(G)
+
+ def _trunc(f, p):
+ """Reduce ``f`` modulo a constant ``p``. """
+ return f.per(dmp_ground_trunc(f._rep, p, f.lev, f.dom))
+
+ def monic(f):
+ """Divides all coefficients by ``LC(f)``. """
+ return f.per(dmp_ground_monic(f._rep, f.lev, f.dom))
+
+ def content(f):
+ """Returns GCD of polynomial coefficients. """
+ return dmp_ground_content(f._rep, f.lev, f.dom)
+
+ def primitive(f):
+ """Returns content and a primitive form of ``f``. """
+ cont, F = dmp_ground_primitive(f._rep, f.lev, f.dom)
+ return cont, f.per(F)
+
+ def _compose(f, g):
+ """Computes functional composition of ``f`` and ``g``. """
+ return f.per(dmp_compose(f._rep, g._rep, f.lev, f.dom))
+
+ def _decompose(f):
+ """Computes functional decomposition of ``f``. """
+ return list(map(f.per, dup_decompose(f._rep, f.dom)))
+
+ def _shift(f, a):
+ """Efficiently compute Taylor shift ``f(x + a)``. """
+ return f.per(dup_shift(f._rep, a, f.dom))
+
+ def _shift_list(f, a):
+ """Efficiently compute Taylor shift ``f(X + A)``. """
+ return f.per(dmp_shift(f._rep, a, f.lev, f.dom))
+
+ def _transform(f, p, q):
+ """Evaluate functional transformation ``q**n * f(p/q)``."""
+ return f.per(dup_transform(f._rep, p._rep, q._rep, f.dom))
+
+ def _sturm(f):
+ """Computes the Sturm sequence of ``f``. """
+ return list(map(f.per, dup_sturm(f._rep, f.dom)))
+
+ def _cauchy_upper_bound(f):
+ """Computes the Cauchy upper bound on the roots of ``f``. """
+ return dup_cauchy_upper_bound(f._rep, f.dom)
+
+ def _cauchy_lower_bound(f):
+ """Computes the Cauchy lower bound on the nonzero roots of ``f``. """
+ return dup_cauchy_lower_bound(f._rep, f.dom)
+
+ def _mignotte_sep_bound_squared(f):
+ """Computes the squared Mignotte bound on root separations of ``f``. """
+ return dup_mignotte_sep_bound_squared(f._rep, f.dom)
+
+ def _gff_list(f):
+ """Computes greatest factorial factorization of ``f``. """
+ return [ (f.per(g), k) for g, k in dup_gff_list(f._rep, f.dom) ]
+
+ def norm(f):
+ """Computes ``Norm(f)``."""
+ r = dmp_norm(f._rep, f.lev, f.dom)
+ return f.new(r, f.dom.dom, f.lev)
+
+ def sqf_norm(f):
+ """Computes square-free norm of ``f``. """
+ s, g, r = dmp_sqf_norm(f._rep, f.lev, f.dom)
+ return s, f.per(g), f.new(r, f.dom.dom, f.lev)
+
+ def sqf_part(f):
+ """Computes square-free part of ``f``. """
+ return f.per(dmp_sqf_part(f._rep, f.lev, f.dom))
+
+ def sqf_list(f, all=False):
+ """Returns a list of square-free factors of ``f``. """
+ coeff, factors = dmp_sqf_list(f._rep, f.lev, f.dom, all)
+ return coeff, [ (f.per(g), k) for g, k in factors ]
+
+ def sqf_list_include(f, all=False):
+ """Returns a list of square-free factors of ``f``. """
+ factors = dmp_sqf_list_include(f._rep, f.lev, f.dom, all)
+ return [ (f.per(g), k) for g, k in factors ]
+
+ def factor_list(f):
+ """Returns a list of irreducible factors of ``f``. """
+ coeff, factors = dmp_factor_list(f._rep, f.lev, f.dom)
+ return coeff, [ (f.per(g), k) for g, k in factors ]
+
+ def factor_list_include(f):
+ """Returns a list of irreducible factors of ``f``. """
+ factors = dmp_factor_list_include(f._rep, f.lev, f.dom)
+ return [ (f.per(g), k) for g, k in factors ]
+
+ def _isolate_real_roots(f, eps, inf, sup, fast):
+ return dup_isolate_real_roots(f._rep, f.dom, eps=eps, inf=inf, sup=sup, fast=fast)
+
+ def _isolate_real_roots_sqf(f, eps, inf, sup, fast):
+ return dup_isolate_real_roots_sqf(f._rep, f.dom, eps=eps, inf=inf, sup=sup, fast=fast)
+
+ def _isolate_all_roots(f, eps, inf, sup, fast):
+ return dup_isolate_all_roots(f._rep, f.dom, eps=eps, inf=inf, sup=sup, fast=fast)
+
+ def _isolate_all_roots_sqf(f, eps, inf, sup, fast):
+ return dup_isolate_all_roots_sqf(f._rep, f.dom, eps=eps, inf=inf, sup=sup, fast=fast)
+
+ def _refine_real_root(f, s, t, eps, steps, fast):
+ return dup_refine_real_root(f._rep, s, t, f.dom, eps=eps, steps=steps, fast=fast)
+
+ def count_real_roots(f, inf=None, sup=None):
+ """Return the number of real roots of ``f`` in ``[inf, sup]``. """
+ return dup_count_real_roots(f._rep, f.dom, inf=inf, sup=sup)
+
+ def count_complex_roots(f, inf=None, sup=None):
+ """Return the number of complex roots of ``f`` in ``[inf, sup]``. """
+ return dup_count_complex_roots(f._rep, f.dom, inf=inf, sup=sup)
+
+ @property
+ def is_zero(f):
+ """Returns ``True`` if ``f`` is a zero polynomial. """
+ return dmp_zero_p(f._rep, f.lev)
+
+ @property
+ def is_one(f):
+ """Returns ``True`` if ``f`` is a unit polynomial. """
+ return dmp_one_p(f._rep, f.lev, f.dom)
+
+ @property
+ def is_ground(f):
+ """Returns ``True`` if ``f`` is an element of the ground domain. """
+ return dmp_ground_p(f._rep, None, f.lev)
+
+ @property
+ def is_sqf(f):
+ """Returns ``True`` if ``f`` is a square-free polynomial. """
+ return dmp_sqf_p(f._rep, f.lev, f.dom)
+
+ @property
+ def is_monic(f):
+ """Returns ``True`` if the leading coefficient of ``f`` is one. """
+ return f.dom.is_one(dmp_ground_LC(f._rep, f.lev, f.dom))
+
+ @property
+ def is_primitive(f):
+ """Returns ``True`` if the GCD of the coefficients of ``f`` is one. """
+ return f.dom.is_one(dmp_ground_content(f._rep, f.lev, f.dom))
+
+ @property
+ def is_linear(f):
+ """Returns ``True`` if ``f`` is linear in all its variables. """
+ return all(sum(monom) <= 1 for monom in dmp_to_dict(f._rep, f.lev, f.dom).keys())
+
+ @property
+ def is_quadratic(f):
+ """Returns ``True`` if ``f`` is quadratic in all its variables. """
+ return all(sum(monom) <= 2 for monom in dmp_to_dict(f._rep, f.lev, f.dom).keys())
+
+ @property
+ def is_monomial(f):
+ """Returns ``True`` if ``f`` is zero or has only one term. """
+ return len(f.to_dict()) <= 1
+
+ @property
+ def is_homogeneous(f):
+ """Returns ``True`` if ``f`` is a homogeneous polynomial. """
+ return f.homogeneous_order() is not None
+
+ @property
+ def is_irreducible(f):
+ """Returns ``True`` if ``f`` has no factors over its domain. """
+ return dmp_irreducible_p(f._rep, f.lev, f.dom)
+
+ @property
+ def is_cyclotomic(f):
+ """Returns ``True`` if ``f`` is a cyclotomic polynomial. """
+ if not f.lev:
+ return dup_cyclotomic_p(f._rep, f.dom)
+ else:
+ return False
+
+
+class DUP_Flint(DMP):
+ """Dense Multivariate Polynomials over `K`. """
+
+ lev = 0
+
+ __slots__ = ('_rep', 'dom', '_cls')
+
+ def __reduce__(self):
+ return self.__class__, (self.to_list(), self.dom, self.lev)
+
+ @classmethod
+ def _new(cls, rep, dom, lev):
+ rep = cls._flint_poly(rep[::-1], dom, lev)
+ return cls.from_rep(rep, dom)
+
+ def to_list(f):
+ """Convert ``f`` to a list representation with native coefficients. """
+ return f._rep.coeffs()[::-1]
+
+ @classmethod
+ def _flint_poly(cls, rep, dom, lev):
+ assert dom in _flint_domains
+ assert lev == 0
+ flint_cls = cls._get_flint_poly_cls(dom)
+ return flint_cls(rep)
+
+ @classmethod
+ def _get_flint_poly_cls(cls, dom):
+ if dom.is_ZZ:
+ return flint.fmpz_poly
+ elif dom.is_QQ:
+ return flint.fmpq_poly
+ else:
+ raise RuntimeError("Domain %s is not supported with flint" % dom)
+
+ @classmethod
+ def from_rep(cls, rep, dom):
+ """Create a DMP from the given representation. """
+
+ if dom.is_ZZ:
+ assert isinstance(rep, flint.fmpz_poly)
+ _cls = flint.fmpz_poly
+ elif dom.is_QQ:
+ assert isinstance(rep, flint.fmpq_poly)
+ _cls = flint.fmpq_poly
+ else:
+ raise RuntimeError("Domain %s is not supported with flint" % dom)
+
+ obj = object.__new__(cls)
+ obj.dom = dom
+ obj._rep = rep
+ obj._cls = _cls
+
+ return obj
+
+ def _strict_eq(f, g):
+ if type(f) != type(g):
+ return False
+ return f.dom == g.dom and f._rep == g._rep
+
+ def ground_new(f, coeff):
+ """Construct a new ground instance of ``f``. """
+ return f.from_rep(f._cls([coeff]), f.dom)
+
+ def _one(f):
+ return f.ground_new(f.dom.one)
+
+ def unify(f, g):
+ """Unify representations of two polynomials. """
+ raise RuntimeError
+
+ def to_DMP_Python(f):
+ """Convert ``f`` to a Python native representation. """
+ return DMP_Python._new(f.to_list(), f.dom, f.lev)
+
+ def to_tuple(f):
+ """Convert ``f`` to a tuple representation with native coefficients. """
+ return tuple(f.to_list())
+
+ def _convert(f, dom):
+ """Convert the ground domain of ``f``. """
+ if dom == QQ and f.dom == ZZ:
+ return f.from_rep(flint.fmpq_poly(f._rep), dom)
+ elif dom == ZZ and f.dom == QQ:
+ # XXX: python-flint should provide a faster way to do this.
+ return f.to_DMP_Python()._convert(dom).to_DUP_Flint()
+ else:
+ raise RuntimeError(f"DUP_Flint: Cannot convert {f.dom} to {dom}")
+
+ def _slice(f, m, n):
+ """Take a continuous subsequence of terms of ``f``. """
+ coeffs = f._rep.coeffs()[m:n]
+ return f.from_rep(f._cls(coeffs), f.dom)
+
+ def _slice_lev(f, m, n, j):
+ """Take a continuous subsequence of terms of ``f``. """
+ # Only makes sense for multivariate polynomials
+ raise NotImplementedError
+
+ def _terms(f, order=None):
+ """Returns all non-zero terms from ``f`` in lex order. """
+ if order is None or order.alias == 'lex':
+ terms = [ ((n,), c) for n, c in enumerate(f._rep.coeffs()) if c ]
+ return terms[::-1]
+ else:
+ # XXX: InverseOrder (ilex) comes here. We could handle that case
+ # efficiently by reversing the coefficients but it is not clear
+ # how to test if the order is InverseOrder.
+ #
+ # Otherwise why would the order ever be different for univariate
+ # polynomials?
+ return f.to_DMP_Python()._terms(order=order)
+
+ def _lift(f):
+ """Convert algebraic coefficients to rationals. """
+ # This is for algebraic number fields which DUP_Flint does not support
+ raise NotImplementedError
+
+ def deflate(f):
+ """Reduce degree of `f` by mapping `x_i^m` to `y_i`. """
+ # XXX: Check because otherwise this segfaults with python-flint:
+ #
+ # >>> flint.fmpz_poly([]).deflation()
+ # Exception (fmpz_poly_deflate). Division by zero.
+ # Aborted (core dumped
+ #
+ if f.is_zero:
+ return (1,), f
+ g, n = f._rep.deflation()
+ return (n,), f.from_rep(g, f.dom)
+
+ def inject(f, front=False):
+ """Inject ground domain generators into ``f``. """
+ # Ground domain would need to be a poly ring
+ raise NotImplementedError
+
+ def eject(f, dom, front=False):
+ """Eject selected generators into the ground domain. """
+ # Only makes sense for multivariate polynomials
+ raise NotImplementedError
+
+ def _exclude(f):
+ """Remove useless generators from ``f``. """
+ # Only makes sense for multivariate polynomials
+ raise NotImplementedError
+
+ def _permute(f, P):
+ """Returns a polynomial in `K[x_{P(1)}, ..., x_{P(n)}]`. """
+ # Only makes sense for multivariate polynomials
+ raise NotImplementedError
+
+ def terms_gcd(f):
+ """Remove GCD of terms from the polynomial ``f``. """
+ # XXX: python-flint should have primitive, content, etc methods.
+ J, F = f.to_DMP_Python().terms_gcd()
+ return J, F.to_DUP_Flint()
+
+ def _add_ground(f, c):
+ """Add an element of the ground domain to ``f``. """
+ return f.from_rep(f._rep + c, f.dom)
+
+ def _sub_ground(f, c):
+ """Subtract an element of the ground domain from ``f``. """
+ return f.from_rep(f._rep - c, f.dom)
+
+ def _mul_ground(f, c):
+ """Multiply ``f`` by a an element of the ground domain. """
+ return f.from_rep(f._rep * c, f.dom)
+
+ def _quo_ground(f, c):
+ """Quotient of ``f`` by a an element of the ground domain. """
+ return f.from_rep(f._rep // c, f.dom)
+
+ def _exquo_ground(f, c):
+ """Exact quotient of ``f`` by a an element of the ground domain. """
+ q, r = divmod(f._rep, c)
+ if r:
+ raise ExactQuotientFailed(f, c)
+ return f.from_rep(q, f.dom)
+
+ def abs(f):
+ """Make all coefficients in ``f`` positive. """
+ return f.to_DMP_Python().abs().to_DUP_Flint()
+
+ def neg(f):
+ """Negate all coefficients in ``f``. """
+ return f.from_rep(-f._rep, f.dom)
+
+ def _add(f, g):
+ """Add two multivariate polynomials ``f`` and ``g``. """
+ return f.from_rep(f._rep + g._rep, f.dom)
+
+ def _sub(f, g):
+ """Subtract two multivariate polynomials ``f`` and ``g``. """
+ return f.from_rep(f._rep - g._rep, f.dom)
+
+ def _mul(f, g):
+ """Multiply two multivariate polynomials ``f`` and ``g``. """
+ return f.from_rep(f._rep * g._rep, f.dom)
+
+ def sqr(f):
+ """Square a multivariate polynomial ``f``. """
+ return f.from_rep(f._rep ** 2, f.dom)
+
+ def _pow(f, n):
+ """Raise ``f`` to a non-negative power ``n``. """
+ return f.from_rep(f._rep ** n, f.dom)
+
+ def _pdiv(f, g):
+ """Polynomial pseudo-division of ``f`` and ``g``. """
+ d = f.degree() - g.degree() + 1
+ q, r = divmod(g.LC()**d * f._rep, g._rep)
+ return f.from_rep(q, f.dom), f.from_rep(r, f.dom)
+
+ def _prem(f, g):
+ """Polynomial pseudo-remainder of ``f`` and ``g``. """
+ d = f.degree() - g.degree() + 1
+ q = (g.LC()**d * f._rep) % g._rep
+ return f.from_rep(q, f.dom)
+
+ def _pquo(f, g):
+ """Polynomial pseudo-quotient of ``f`` and ``g``. """
+ d = f.degree() - g.degree() + 1
+ r = (g.LC()**d * f._rep) // g._rep
+ return f.from_rep(r, f.dom)
+
+ def _pexquo(f, g):
+ """Polynomial exact pseudo-quotient of ``f`` and ``g``. """
+ d = f.degree() - g.degree() + 1
+ q, r = divmod(g.LC()**d * f._rep, g._rep)
+ if r:
+ raise ExactQuotientFailed(f, g)
+ return f.from_rep(q, f.dom)
+
+ def _div(f, g):
+ """Polynomial division with remainder of ``f`` and ``g``. """
+ if f.dom.is_Field:
+ q, r = divmod(f._rep, g._rep)
+ return f.from_rep(q, f.dom), f.from_rep(r, f.dom)
+ else:
+ # XXX: python-flint defines division in ZZ[x] differently
+ q, r = f.to_DMP_Python()._div(g.to_DMP_Python())
+ return q.to_DUP_Flint(), r.to_DUP_Flint()
+
+ def _rem(f, g):
+ """Computes polynomial remainder of ``f`` and ``g``. """
+ return f.from_rep(f._rep % g._rep, f.dom)
+
+ def _quo(f, g):
+ """Computes polynomial quotient of ``f`` and ``g``. """
+ return f.from_rep(f._rep // g._rep, f.dom)
+
+ def _exquo(f, g):
+ """Computes polynomial exact quotient of ``f`` and ``g``. """
+ q, r = f._div(g)
+ if r:
+ raise ExactQuotientFailed(f, g)
+ return q
+
+ def _degree(f, j=0):
+ """Returns the leading degree of ``f`` in ``x_j``. """
+ d = f._rep.degree()
+ if d == -1:
+ d = ninf
+ return d
+
+ def degree_list(f):
+ """Returns a list of degrees of ``f``. """
+ return ( f._degree() ,)
+
+ def total_degree(f):
+ """Returns the total degree of ``f``. """
+ return f._degree()
+
+ def LC(f):
+ """Returns the leading coefficient of ``f``. """
+ return f._rep[f._rep.degree()]
+
+ def TC(f):
+ """Returns the trailing coefficient of ``f``. """
+ return f._rep[0]
+
+ def _nth(f, N):
+ """Returns the ``n``-th coefficient of ``f``. """
+ [n] = N
+ return f._rep[n]
+
+ def max_norm(f):
+ """Returns maximum norm of ``f``. """
+ return f.to_DMP_Python().max_norm()
+
+ def l1_norm(f):
+ """Returns l1 norm of ``f``. """
+ return f.to_DMP_Python().l1_norm()
+
+ def l2_norm_squared(f):
+ """Return squared l2 norm of ``f``. """
+ return f.to_DMP_Python().l2_norm_squared()
+
+ def clear_denoms(f):
+ """Clear denominators, but keep the ground domain. """
+ denom = f._rep.denom()
+ numer = f.from_rep(f._cls(f._rep.numer()), f.dom)
+ return denom, numer
+
+ def _integrate(f, m=1, j=0):
+ """Computes the ``m``-th order indefinite integral of ``f`` in ``x_j``. """
+ assert j == 0
+ if f.dom.is_QQ:
+ rep = f._rep
+ for i in range(m):
+ rep = rep.integral()
+ return f.from_rep(rep, f.dom)
+ else:
+ return f.to_DMP_Python()._integrate(m=m, j=j).to_DUP_Flint()
+
+ def _diff(f, m=1, j=0):
+ """Computes the ``m``-th order derivative of ``f``. """
+ assert j == 0
+ rep = f._rep
+ for i in range(m):
+ rep = rep.derivative()
+ return f.from_rep(rep, f.dom)
+
+ def _eval(f, a):
+ return f.to_DMP_Python()._eval(a)
+
+ def _eval_lev(f, a, j):
+ # Only makes sense for multivariate polynomials
+ raise NotImplementedError
+
+ def _half_gcdex(f, g):
+ """Half extended Euclidean algorithm. """
+ s, h = f.to_DMP_Python()._half_gcdex(g.to_DMP_Python())
+ return s.to_DUP_Flint(), h.to_DUP_Flint()
+
+ def _gcdex(f, g):
+ """Extended Euclidean algorithm. """
+ h, s, t = f._rep.xgcd(g._rep)
+ return f.from_rep(s, f.dom), f.from_rep(t, f.dom), f.from_rep(h, f.dom)
+
+ def _invert(f, g):
+ """Invert ``f`` modulo ``g``, if possible. """
+ if f.dom.is_QQ:
+ gcd, F_inv, _ = f._rep.xgcd(g._rep)
+ if gcd != 1:
+ raise NotInvertible("zero divisor")
+ return f.from_rep(F_inv, f.dom)
+ else:
+ return f.to_DMP_Python()._invert(g.to_DMP_Python()).to_DUP_Flint()
+
+ def _revert(f, n):
+ """Compute ``f**(-1)`` mod ``x**n``. """
+ return f.to_DMP_Python()._revert(n).to_DUP_Flint()
+
+ def _subresultants(f, g):
+ """Computes subresultant PRS sequence of ``f`` and ``g``. """
+ R = f.to_DMP_Python()._subresultants(g.to_DMP_Python())
+ return [ g.to_DUP_Flint() for g in R ]
+
+ def _resultant_includePRS(f, g):
+ """Computes resultant of ``f`` and ``g`` via PRS. """
+ res, R = f.to_DMP_Python()._resultant_includePRS(g.to_DMP_Python())
+ return res, [ g.to_DUP_Flint() for g in R ]
+
+ def _resultant(f, g):
+ """Computes resultant of ``f`` and ``g``. """
+ return f.to_DMP_Python()._resultant(g.to_DMP_Python())
+
+ def discriminant(f):
+ """Computes discriminant of ``f``. """
+ return f.to_DMP_Python().discriminant()
+
+ def _cofactors(f, g):
+ """Returns GCD of ``f`` and ``g`` and their cofactors. """
+ h = f.gcd(g)
+ return h, f.exquo(h), g.exquo(h)
+
+ def _gcd(f, g):
+ """Returns polynomial GCD of ``f`` and ``g``. """
+ return f.from_rep(f._rep.gcd(g._rep), f.dom)
+
+ def _lcm(f, g):
+ """Returns polynomial LCM of ``f`` and ``g``. """
+ # XXX: python-flint should have a lcm method
+ if not (f and g):
+ return f.ground_new(f.dom.zero)
+
+ l = f._mul(g)._exquo(f._gcd(g))
+
+ if l.dom.is_Field:
+ l = l.monic()
+ elif l.LC() < 0:
+ l = l.neg()
+
+ return l
+
+ def _cancel(f, g):
+ """Cancel common factors in a rational function ``f/g``. """
+ # Think carefully about how to handle denominators and coefficient
+ # canonicalisation if more domains are permitted...
+ assert f.dom == g.dom in (ZZ, QQ)
+
+ if f.dom.is_QQ:
+ cG, F = f.clear_denoms()
+ cF, G = g.clear_denoms()
+ else:
+ cG, F = f.dom.one, f
+ cF, G = g.dom.one, g
+
+ cH = cF.gcd(cG)
+ cF, cG = cF // cH, cG // cH
+
+ H = F._gcd(G)
+ F, G = F.exquo(H), G.exquo(H)
+
+ f_neg = F.LC() < 0
+ g_neg = G.LC() < 0
+
+ if f_neg and g_neg:
+ F, G = F.neg(), G.neg()
+ elif f_neg:
+ cF, F = -cF, F.neg()
+ elif g_neg:
+ cF, G = -cF, G.neg()
+
+ return cF, cG, F, G
+
+ def _cancel_include(f, g):
+ """Cancel common factors in a rational function ``f/g``. """
+ cF, cG, F, G = f._cancel(g)
+ return F._mul_ground(cF), G._mul_ground(cG)
+
+ def _trunc(f, p):
+ """Reduce ``f`` modulo a constant ``p``. """
+ return f.to_DMP_Python()._trunc(p).to_DUP_Flint()
+
+ def monic(f):
+ """Divides all coefficients by ``LC(f)``. """
+ return f._exquo_ground(f.LC())
+
+ def content(f):
+ """Returns GCD of polynomial coefficients. """
+ # XXX: python-flint should have a content method
+ return f.to_DMP_Python().content()
+
+ def primitive(f):
+ """Returns content and a primitive form of ``f``. """
+ cont = f.content()
+ prim = f._exquo_ground(cont)
+ return cont, prim
+
+ def _compose(f, g):
+ """Computes functional composition of ``f`` and ``g``. """
+ return f.from_rep(f._rep(g._rep), f.dom)
+
+ def _decompose(f):
+ """Computes functional decomposition of ``f``. """
+ return [ g.to_DUP_Flint() for g in f.to_DMP_Python()._decompose() ]
+
+ def _shift(f, a):
+ """Efficiently compute Taylor shift ``f(x + a)``. """
+ x_plus_a = f._cls([a, f.dom.one])
+ return f.from_rep(f._rep(x_plus_a), f.dom)
+
+ def _transform(f, p, q):
+ """Evaluate functional transformation ``q**n * f(p/q)``."""
+ F, P, Q = f.to_DMP_Python(), p.to_DMP_Python(), q.to_DMP_Python()
+ return F.transform(P, Q).to_DUP_Flint()
+
+ def _sturm(f):
+ """Computes the Sturm sequence of ``f``. """
+ return [ g.to_DUP_Flint() for g in f.to_DMP_Python()._sturm() ]
+
+ def _cauchy_upper_bound(f):
+ """Computes the Cauchy upper bound on the roots of ``f``. """
+ return f.to_DMP_Python()._cauchy_upper_bound()
+
+ def _cauchy_lower_bound(f):
+ """Computes the Cauchy lower bound on the nonzero roots of ``f``. """
+ return f.to_DMP_Python()._cauchy_lower_bound()
+
+ def _mignotte_sep_bound_squared(f):
+ """Computes the squared Mignotte bound on root separations of ``f``. """
+ return f.to_DMP_Python()._mignotte_sep_bound_squared()
+
+ def _gff_list(f):
+ """Computes greatest factorial factorization of ``f``. """
+ F = f.to_DMP_Python()
+ return [ (g.to_DUP_Flint(), k) for g, k in F.gff_list() ]
+
+ def norm(f):
+ """Computes ``Norm(f)``."""
+ # This is for algebraic number fields which DUP_Flint does not support
+ raise NotImplementedError
+
+ def sqf_norm(f):
+ """Computes square-free norm of ``f``. """
+ # This is for algebraic number fields which DUP_Flint does not support
+ raise NotImplementedError
+
+ def sqf_part(f):
+ """Computes square-free part of ``f``. """
+ return f._exquo(f._gcd(f._diff()))
+
+ def sqf_list(f, all=False):
+ """Returns a list of square-free factors of ``f``. """
+ coeff, factors = f.to_DMP_Python().sqf_list(all=all)
+ return coeff, [ (g.to_DUP_Flint(), k) for g, k in factors ]
+
+ def sqf_list_include(f, all=False):
+ """Returns a list of square-free factors of ``f``. """
+ factors = f.to_DMP_Python().sqf_list_include(all=all)
+ return [ (g.to_DUP_Flint(), k) for g, k in factors ]
+
+ def factor_list(f):
+ """Returns a list of irreducible factors of ``f``. """
+
+ if f.dom.is_ZZ:
+ # python-flint matches polys here
+ coeff, factors = f._rep.factor()
+ factors = [ (f.from_rep(g, f.dom), k) for g, k in factors ]
+
+ elif f.dom.is_QQ:
+ # python-flint returns monic factors over QQ whereas polys returns
+ # denominator free factors.
+ coeff, factors = f._rep.factor()
+ factors_monic = [ (f.from_rep(g, f.dom), k) for g, k in factors ]
+
+ # Absorb the denominators into coeff
+ factors = []
+ for g, k in factors_monic:
+ d, g = g.clear_denoms()
+ coeff /= d**k
+ factors.append((g, k))
+
+ else:
+ # Check carefully when adding more domains here...
+ raise RuntimeError("Domain %s is not supported with flint" % f.dom)
+
+ # We need to match the way that polys orders the factors
+ factors = f._sort_factors(factors)
+
+ return coeff, factors
+
+ def factor_list_include(f):
+ """Returns a list of irreducible factors of ``f``. """
+ # XXX: factor_list_include seems to be broken in general:
+ #
+ # >>> Poly(2*(x - 1)**3, x).factor_list_include()
+ # [(Poly(2*x - 2, x, domain='ZZ'), 3)]
+ #
+ # Let's not try to implement it here.
+ factors = f.to_DMP_Python().factor_list_include()
+ return [ (g.to_DUP_Flint(), k) for g, k in factors ]
+
+ def _sort_factors(f, factors):
+ """Sort a list of factors to canonical order. """
+ # Convert the factors to lists and use _sort_factors from polys
+ factors = [ (g.to_list(), k) for g, k in factors ]
+ factors = _sort_factors(factors, multiple=True)
+ to_dup_flint = lambda g: f.from_rep(f._cls(g[::-1]), f.dom)
+ return [ (to_dup_flint(g), k) for g, k in factors ]
+
+ def _isolate_real_roots(f, eps, inf, sup, fast):
+ return f.to_DMP_Python()._isolate_real_roots(eps, inf, sup, fast)
+
+ def _isolate_real_roots_sqf(f, eps, inf, sup, fast):
+ return f.to_DMP_Python()._isolate_real_roots_sqf(eps, inf, sup, fast)
+
+ def _isolate_all_roots(f, eps, inf, sup, fast):
+ return f.to_DMP_Python()._isolate_all_roots(eps, inf, sup, fast)
+
+ def _isolate_all_roots_sqf(f, eps, inf, sup, fast):
+ return f.to_DMP_Python()._isolate_all_roots_sqf(eps, inf, sup, fast)
+
+ def _refine_real_root(f, s, t, eps, steps, fast):
+ return f.to_DMP_Python()._refine_real_root(s, t, eps, steps, fast)
+
+ def count_real_roots(f, inf=None, sup=None):
+ """Return the number of real roots of ``f`` in ``[inf, sup]``. """
+ return f.to_DMP_Python().count_real_roots(inf=inf, sup=sup)
+
+ def count_complex_roots(f, inf=None, sup=None):
+ """Return the number of complex roots of ``f`` in ``[inf, sup]``. """
+ return f.to_DMP_Python().count_complex_roots(inf=inf, sup=sup)
+
+ @property
+ def is_zero(f):
+ """Returns ``True`` if ``f`` is a zero polynomial. """
+ return not f._rep
+
+ @property
+ def is_one(f):
+ """Returns ``True`` if ``f`` is a unit polynomial. """
+ return f._rep == f.dom.one
+
+ @property
+ def is_ground(f):
+ """Returns ``True`` if ``f`` is an element of the ground domain. """
+ return f._rep.degree() <= 0
+
+ @property
+ def is_linear(f):
+ """Returns ``True`` if ``f`` is linear in all its variables. """
+ return f._rep.degree() <= 1
+
+ @property
+ def is_quadratic(f):
+ """Returns ``True`` if ``f`` is quadratic in all its variables. """
+ return f._rep.degree() <= 2
+
+ @property
+ def is_monomial(f):
+ """Returns ``True`` if ``f`` is zero or has only one term. """
+ return f.to_DMP_Python().is_monomial
+
+ @property
+ def is_monic(f):
+ """Returns ``True`` if the leading coefficient of ``f`` is one. """
+ return f.LC() == f.dom.one
+
+ @property
+ def is_primitive(f):
+ """Returns ``True`` if the GCD of the coefficients of ``f`` is one. """
+ return f.to_DMP_Python().is_primitive
+
+ @property
+ def is_homogeneous(f):
+ """Returns ``True`` if ``f`` is a homogeneous polynomial. """
+ return f.to_DMP_Python().is_homogeneous
+
+ @property
+ def is_sqf(f):
+ """Returns ``True`` if ``f`` is a square-free polynomial. """
+ return f.to_DMP_Python().is_sqf
+
+ @property
+ def is_irreducible(f):
+ """Returns ``True`` if ``f`` has no factors over its domain. """
+ return f.to_DMP_Python().is_irreducible
+
+ @property
+ def is_cyclotomic(f):
+ """Returns ``True`` if ``f`` is a cyclotomic polynomial. """
+ if f.dom.is_ZZ:
+ return bool(f._rep.is_cyclotomic())
+ else:
+ return f.to_DMP_Python().is_cyclotomic
+
+
+def init_normal_DMF(num, den, lev, dom):
+ return DMF(dmp_normal(num, lev, dom),
+ dmp_normal(den, lev, dom), dom, lev)
+
+
+class DMF(PicklableWithSlots, CantSympify):
+ """Dense Multivariate Fractions over `K`. """
+
+ __slots__ = ('num', 'den', 'lev', 'dom')
+
+ def __init__(self, rep, dom, lev=None):
+ num, den, lev = self._parse(rep, dom, lev)
+ num, den = dmp_cancel(num, den, lev, dom)
+
+ self.num = num
+ self.den = den
+ self.lev = lev
+ self.dom = dom
+
+ @classmethod
+ def new(cls, rep, dom, lev=None):
+ num, den, lev = cls._parse(rep, dom, lev)
+
+ obj = object.__new__(cls)
+
+ obj.num = num
+ obj.den = den
+ obj.lev = lev
+ obj.dom = dom
+
+ return obj
+
+ def ground_new(self, rep):
+ return self.new(rep, self.dom, self.lev)
+
+ @classmethod
+ def _parse(cls, rep, dom, lev=None):
+ if isinstance(rep, tuple):
+ num, den = rep
+
+ if lev is not None:
+ if isinstance(num, dict):
+ num = dmp_from_dict(num, lev, dom)
+
+ if isinstance(den, dict):
+ den = dmp_from_dict(den, lev, dom)
+ else:
+ num, num_lev = dmp_validate(num)
+ den, den_lev = dmp_validate(den)
+
+ if num_lev == den_lev:
+ lev = num_lev
+ else:
+ raise ValueError('inconsistent number of levels')
+
+ if dmp_zero_p(den, lev):
+ raise ZeroDivisionError('fraction denominator')
+
+ if dmp_zero_p(num, lev):
+ den = dmp_one(lev, dom)
+ else:
+ if dmp_negative_p(den, lev, dom):
+ num = dmp_neg(num, lev, dom)
+ den = dmp_neg(den, lev, dom)
+ else:
+ num = rep
+
+ if lev is not None:
+ if isinstance(num, dict):
+ num = dmp_from_dict(num, lev, dom)
+ elif not isinstance(num, list):
+ num = dmp_ground(dom.convert(num), lev)
+ else:
+ num, lev = dmp_validate(num)
+
+ den = dmp_one(lev, dom)
+
+ return num, den, lev
+
+ def __repr__(f):
+ return "%s((%s, %s), %s)" % (f.__class__.__name__, f.num, f.den, f.dom)
+
+ def __hash__(f):
+ return hash((f.__class__.__name__, dmp_to_tuple(f.num, f.lev),
+ dmp_to_tuple(f.den, f.lev), f.lev, f.dom))
+
+ def poly_unify(f, g):
+ """Unify a multivariate fraction and a polynomial. """
+ if not isinstance(g, DMP) or f.lev != g.lev:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ if f.dom == g.dom:
+ return (f.lev, f.dom, f.per, (f.num, f.den), g._rep)
+ else:
+ lev, dom = f.lev, f.dom.unify(g.dom)
+
+ F = (dmp_convert(f.num, lev, f.dom, dom),
+ dmp_convert(f.den, lev, f.dom, dom))
+
+ G = dmp_convert(g._rep, lev, g.dom, dom)
+
+ def per(num, den, cancel=True, kill=False, lev=lev):
+ if kill:
+ if not lev:
+ return num/den
+ else:
+ lev = lev - 1
+
+ if cancel:
+ num, den = dmp_cancel(num, den, lev, dom)
+
+ return f.__class__.new((num, den), dom, lev)
+
+ return lev, dom, per, F, G
+
+ def frac_unify(f, g):
+ """Unify representations of two multivariate fractions. """
+ if not isinstance(g, DMF) or f.lev != g.lev:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ if f.dom == g.dom:
+ return (f.lev, f.dom, f.per, (f.num, f.den),
+ (g.num, g.den))
+ else:
+ lev, dom = f.lev, f.dom.unify(g.dom)
+
+ F = (dmp_convert(f.num, lev, f.dom, dom),
+ dmp_convert(f.den, lev, f.dom, dom))
+
+ G = (dmp_convert(g.num, lev, g.dom, dom),
+ dmp_convert(g.den, lev, g.dom, dom))
+
+ def per(num, den, cancel=True, kill=False, lev=lev):
+ if kill:
+ if not lev:
+ return num/den
+ else:
+ lev = lev - 1
+
+ if cancel:
+ num, den = dmp_cancel(num, den, lev, dom)
+
+ return f.__class__.new((num, den), dom, lev)
+
+ return lev, dom, per, F, G
+
+ def per(f, num, den, cancel=True, kill=False):
+ """Create a DMF out of the given representation. """
+ lev, dom = f.lev, f.dom
+
+ if kill:
+ if not lev:
+ return num/den
+ else:
+ lev -= 1
+
+ if cancel:
+ num, den = dmp_cancel(num, den, lev, dom)
+
+ return f.__class__.new((num, den), dom, lev)
+
+ def half_per(f, rep, kill=False):
+ """Create a DMP out of the given representation. """
+ lev = f.lev
+
+ if kill:
+ if not lev:
+ return rep
+ else:
+ lev -= 1
+
+ return DMP(rep, f.dom, lev)
+
+ @classmethod
+ def zero(cls, lev, dom):
+ return cls.new(0, dom, lev)
+
+ @classmethod
+ def one(cls, lev, dom):
+ return cls.new(1, dom, lev)
+
+ def numer(f):
+ """Returns the numerator of ``f``. """
+ return f.half_per(f.num)
+
+ def denom(f):
+ """Returns the denominator of ``f``. """
+ return f.half_per(f.den)
+
+ def cancel(f):
+ """Remove common factors from ``f.num`` and ``f.den``. """
+ return f.per(f.num, f.den)
+
+ def neg(f):
+ """Negate all coefficients in ``f``. """
+ return f.per(dmp_neg(f.num, f.lev, f.dom), f.den, cancel=False)
+
+ def add_ground(f, c):
+ """Add an element of the ground domain to ``f``. """
+ return f + f.ground_new(c)
+
+ def add(f, g):
+ """Add two multivariate fractions ``f`` and ``g``. """
+ if isinstance(g, DMP):
+ lev, dom, per, (F_num, F_den), G = f.poly_unify(g)
+ num, den = dmp_add_mul(F_num, F_den, G, lev, dom), F_den
+ else:
+ lev, dom, per, F, G = f.frac_unify(g)
+ (F_num, F_den), (G_num, G_den) = F, G
+
+ num = dmp_add(dmp_mul(F_num, G_den, lev, dom),
+ dmp_mul(F_den, G_num, lev, dom), lev, dom)
+ den = dmp_mul(F_den, G_den, lev, dom)
+
+ return per(num, den)
+
+ def sub(f, g):
+ """Subtract two multivariate fractions ``f`` and ``g``. """
+ if isinstance(g, DMP):
+ lev, dom, per, (F_num, F_den), G = f.poly_unify(g)
+ num, den = dmp_sub_mul(F_num, F_den, G, lev, dom), F_den
+ else:
+ lev, dom, per, F, G = f.frac_unify(g)
+ (F_num, F_den), (G_num, G_den) = F, G
+
+ num = dmp_sub(dmp_mul(F_num, G_den, lev, dom),
+ dmp_mul(F_den, G_num, lev, dom), lev, dom)
+ den = dmp_mul(F_den, G_den, lev, dom)
+
+ return per(num, den)
+
+ def mul(f, g):
+ """Multiply two multivariate fractions ``f`` and ``g``. """
+ if isinstance(g, DMP):
+ lev, dom, per, (F_num, F_den), G = f.poly_unify(g)
+ num, den = dmp_mul(F_num, G, lev, dom), F_den
+ else:
+ lev, dom, per, F, G = f.frac_unify(g)
+ (F_num, F_den), (G_num, G_den) = F, G
+
+ num = dmp_mul(F_num, G_num, lev, dom)
+ den = dmp_mul(F_den, G_den, lev, dom)
+
+ return per(num, den)
+
+ def pow(f, n):
+ """Raise ``f`` to a non-negative power ``n``. """
+ if isinstance(n, int):
+ num, den = f.num, f.den
+ if n < 0:
+ num, den, n = den, num, -n
+ return f.per(dmp_pow(num, n, f.lev, f.dom),
+ dmp_pow(den, n, f.lev, f.dom), cancel=False)
+ else:
+ raise TypeError("``int`` expected, got %s" % type(n))
+
+ def quo(f, g):
+ """Computes quotient of fractions ``f`` and ``g``. """
+ if isinstance(g, DMP):
+ lev, dom, per, (F_num, F_den), G = f.poly_unify(g)
+ num, den = F_num, dmp_mul(F_den, G, lev, dom)
+ else:
+ lev, dom, per, F, G = f.frac_unify(g)
+ (F_num, F_den), (G_num, G_den) = F, G
+
+ num = dmp_mul(F_num, G_den, lev, dom)
+ den = dmp_mul(F_den, G_num, lev, dom)
+
+ return per(num, den)
+
+ exquo = quo
+
+ def invert(f, check=True):
+ """Computes inverse of a fraction ``f``. """
+ return f.per(f.den, f.num, cancel=False)
+
+ @property
+ def is_zero(f):
+ """Returns ``True`` if ``f`` is a zero fraction. """
+ return dmp_zero_p(f.num, f.lev)
+
+ @property
+ def is_one(f):
+ """Returns ``True`` if ``f`` is a unit fraction. """
+ return dmp_one_p(f.num, f.lev, f.dom) and \
+ dmp_one_p(f.den, f.lev, f.dom)
+
+ def __neg__(f):
+ return f.neg()
+
+ def __add__(f, g):
+ if isinstance(g, (DMP, DMF)):
+ return f.add(g)
+ elif g in f.dom:
+ return f.add_ground(f.dom.convert(g))
+
+ try:
+ return f.add(f.half_per(g))
+ except (TypeError, CoercionFailed, NotImplementedError):
+ return NotImplemented
+
+ def __radd__(f, g):
+ return f.__add__(g)
+
+ def __sub__(f, g):
+ if isinstance(g, (DMP, DMF)):
+ return f.sub(g)
+
+ try:
+ return f.sub(f.half_per(g))
+ except (TypeError, CoercionFailed, NotImplementedError):
+ return NotImplemented
+
+ def __rsub__(f, g):
+ return (-f).__add__(g)
+
+ def __mul__(f, g):
+ if isinstance(g, (DMP, DMF)):
+ return f.mul(g)
+
+ try:
+ return f.mul(f.half_per(g))
+ except (TypeError, CoercionFailed, NotImplementedError):
+ return NotImplemented
+
+ def __rmul__(f, g):
+ return f.__mul__(g)
+
+ def __pow__(f, n):
+ return f.pow(n)
+
+ def __truediv__(f, g):
+ if isinstance(g, (DMP, DMF)):
+ return f.quo(g)
+
+ try:
+ return f.quo(f.half_per(g))
+ except (TypeError, CoercionFailed, NotImplementedError):
+ return NotImplemented
+
+ def __rtruediv__(self, g):
+ return self.invert(check=False)*g
+
+ def __eq__(f, g):
+ try:
+ if isinstance(g, DMP):
+ _, _, _, (F_num, F_den), G = f.poly_unify(g)
+
+ if f.lev == g.lev:
+ return dmp_one_p(F_den, f.lev, f.dom) and F_num == G
+ else:
+ _, _, _, F, G = f.frac_unify(g)
+
+ if f.lev == g.lev:
+ return F == G
+ except UnificationFailed:
+ pass
+
+ return False
+
+ def __ne__(f, g):
+ try:
+ if isinstance(g, DMP):
+ _, _, _, (F_num, F_den), G = f.poly_unify(g)
+
+ if f.lev == g.lev:
+ return not (dmp_one_p(F_den, f.lev, f.dom) and F_num == G)
+ else:
+ _, _, _, F, G = f.frac_unify(g)
+
+ if f.lev == g.lev:
+ return F != G
+ except UnificationFailed:
+ pass
+
+ return True
+
+ def __lt__(f, g):
+ _, _, _, F, G = f.frac_unify(g)
+ return F < G
+
+ def __le__(f, g):
+ _, _, _, F, G = f.frac_unify(g)
+ return F <= G
+
+ def __gt__(f, g):
+ _, _, _, F, G = f.frac_unify(g)
+ return F > G
+
+ def __ge__(f, g):
+ _, _, _, F, G = f.frac_unify(g)
+ return F >= G
+
+ def __bool__(f):
+ return not dmp_zero_p(f.num, f.lev)
+
+
+def init_normal_ANP(rep, mod, dom):
+ return ANP(dup_normal(rep, dom),
+ dup_normal(mod, dom), dom)
+
+
+class ANP(CantSympify):
+ """Dense Algebraic Number Polynomials over a field. """
+
+ __slots__ = ('_rep', '_mod', 'dom')
+
+ def __new__(cls, rep, mod, dom):
+ if isinstance(rep, DMP):
+ pass
+ elif type(rep) is dict: # don't use isinstance
+ rep = DMP(dup_from_dict(rep, dom), dom, 0)
+ else:
+ if isinstance(rep, list):
+ rep = [dom.convert(a) for a in rep]
+ else:
+ rep = [dom.convert(rep)]
+ rep = DMP(dup_strip(rep), dom, 0)
+
+ if isinstance(mod, DMP):
+ pass
+ elif isinstance(mod, dict):
+ mod = DMP(dup_from_dict(mod, dom), dom, 0)
+ else:
+ mod = DMP(dup_strip(mod), dom, 0)
+
+ return cls.new(rep, mod, dom)
+
+ @classmethod
+ def new(cls, rep, mod, dom):
+ if not (rep.dom == mod.dom == dom):
+ raise RuntimeError("Inconsistent domain")
+ obj = super().__new__(cls)
+ obj._rep = rep
+ obj._mod = mod
+ obj.dom = dom
+ return obj
+
+ # XXX: It should be possible to use __getnewargs__ rather than __reduce__
+ # but it doesn't work for some reason. Probably this would be easier if
+ # python-flint supported pickling for polynomial types.
+ def __reduce__(self):
+ return ANP, (self.rep, self.mod, self.dom)
+
+ @property
+ def rep(self):
+ return self._rep.to_list()
+
+ @property
+ def mod(self):
+ return self.mod_to_list()
+
+ def to_DMP(self):
+ return self._rep
+
+ def mod_to_DMP(self):
+ return self._mod
+
+ def per(f, rep):
+ return f.new(rep, f._mod, f.dom)
+
+ def __repr__(f):
+ return "%s(%s, %s, %s)" % (f.__class__.__name__, f._rep.to_list(), f._mod.to_list(), f.dom)
+
+ def __hash__(f):
+ return hash((f.__class__.__name__, f.to_tuple(), f._mod.to_tuple(), f.dom))
+
+ def convert(f, dom):
+ """Convert ``f`` to a ``ANP`` over a new domain. """
+ if f.dom == dom:
+ return f
+ else:
+ return f.new(f._rep.convert(dom), f._mod.convert(dom), dom)
+
+ def unify(f, g):
+ """Unify representations of two algebraic numbers. """
+
+ # XXX: This unify method is not used any more because unify_ANP is used
+ # instead.
+
+ if not isinstance(g, ANP) or f.mod != g.mod:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ if f.dom == g.dom:
+ return f.dom, f.per, f.rep, g.rep, f.mod
+ else:
+ dom = f.dom.unify(g.dom)
+
+ F = dup_convert(f.rep, f.dom, dom)
+ G = dup_convert(g.rep, g.dom, dom)
+
+ if dom != f.dom and dom != g.dom:
+ mod = dup_convert(f.mod, f.dom, dom)
+ else:
+ if dom == f.dom:
+ mod = f.mod
+ else:
+ mod = g.mod
+
+ per = lambda rep: ANP(rep, mod, dom)
+
+ return dom, per, F, G, mod
+
+ def unify_ANP(f, g):
+ """Unify and return ``DMP`` instances of ``f`` and ``g``. """
+ if not isinstance(g, ANP) or f._mod != g._mod:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ # The domain is almost always QQ but there are some tests involving ZZ
+ if f.dom != g.dom:
+ dom = f.dom.unify(g.dom)
+ f = f.convert(dom)
+ g = g.convert(dom)
+
+ return f._rep, g._rep, f._mod, f.dom
+
+ @classmethod
+ def zero(cls, mod, dom):
+ return ANP(0, mod, dom)
+
+ @classmethod
+ def one(cls, mod, dom):
+ return ANP(1, mod, dom)
+
+ def to_dict(f):
+ """Convert ``f`` to a dict representation with native coefficients. """
+ return f._rep.to_dict()
+
+ def to_sympy_dict(f):
+ """Convert ``f`` to a dict representation with SymPy coefficients. """
+ rep = dmp_to_dict(f.rep, 0, f.dom)
+
+ for k, v in rep.items():
+ rep[k] = f.dom.to_sympy(v)
+
+ return rep
+
+ def to_list(f):
+ """Convert ``f`` to a list representation with native coefficients. """
+ return f._rep.to_list()
+
+ def mod_to_list(f):
+ """Return ``f.mod`` as a list with native coefficients. """
+ return f._mod.to_list()
+
+ def to_sympy_list(f):
+ """Convert ``f`` to a list representation with SymPy coefficients. """
+ return [ f.dom.to_sympy(c) for c in f.to_list() ]
+
+ def to_tuple(f):
+ """
+ Convert ``f`` to a tuple representation with native coefficients.
+
+ This is needed for hashing.
+ """
+ return f._rep.to_tuple()
+
+ @classmethod
+ def from_list(cls, rep, mod, dom):
+ return ANP(dup_strip(list(map(dom.convert, rep))), mod, dom)
+
+ def add_ground(f, c):
+ """Add an element of the ground domain to ``f``. """
+ return f.per(f._rep.add_ground(c))
+
+ def sub_ground(f, c):
+ """Subtract an element of the ground domain from ``f``. """
+ return f.per(f._rep.sub_ground(c))
+
+ def mul_ground(f, c):
+ """Multiply ``f`` by an element of the ground domain. """
+ return f.per(f._rep.mul_ground(c))
+
+ def quo_ground(f, c):
+ """Quotient of ``f`` by an element of the ground domain. """
+ return f.per(f._rep.quo_ground(c))
+
+ def neg(f):
+ return f.per(f._rep.neg())
+
+ def add(f, g):
+ F, G, mod, dom = f.unify_ANP(g)
+ return f.new(F.add(G), mod, dom)
+
+ def sub(f, g):
+ F, G, mod, dom = f.unify_ANP(g)
+ return f.new(F.sub(G), mod, dom)
+
+ def mul(f, g):
+ F, G, mod, dom = f.unify_ANP(g)
+ return f.new(F.mul(G).rem(mod), mod, dom)
+
+ def pow(f, n):
+ """Raise ``f`` to a non-negative power ``n``. """
+ if not isinstance(n, int):
+ raise TypeError("``int`` expected, got %s" % type(n))
+
+ mod = f._mod
+ F = f._rep
+
+ if n < 0:
+ F, n = F.invert(mod), -n
+
+ # XXX: Need a pow_mod method for DMP
+ return f.new(F.pow(n).rem(f._mod), mod, f.dom)
+
+ def exquo(f, g):
+ F, G, mod, dom = f.unify_ANP(g)
+ return f.new(F.mul(G.invert(mod)).rem(mod), mod, dom)
+
+ def div(f, g):
+ return f.exquo(g), f.zero(f._mod, f.dom)
+
+ def quo(f, g):
+ return f.exquo(g)
+
+ def rem(f, g):
+ F, G, mod, dom = f.unify_ANP(g)
+ s, h = F.half_gcdex(G)
+
+ if h.is_one:
+ return f.zero(mod, dom)
+ else:
+ raise NotInvertible("zero divisor")
+
+ def LC(f):
+ """Returns the leading coefficient of ``f``. """
+ return f._rep.LC()
+
+ def TC(f):
+ """Returns the trailing coefficient of ``f``. """
+ return f._rep.TC()
+
+ @property
+ def is_zero(f):
+ """Returns ``True`` if ``f`` is a zero algebraic number. """
+ return f._rep.is_zero
+
+ @property
+ def is_one(f):
+ """Returns ``True`` if ``f`` is a unit algebraic number. """
+ return f._rep.is_one
+
+ @property
+ def is_ground(f):
+ """Returns ``True`` if ``f`` is an element of the ground domain. """
+ return f._rep.is_ground
+
+ def __pos__(f):
+ return f
+
+ def __neg__(f):
+ return f.neg()
+
+ def __add__(f, g):
+ if isinstance(g, ANP):
+ return f.add(g)
+ try:
+ g = f.dom.convert(g)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ return f.add_ground(g)
+
+ def __radd__(f, g):
+ return f.__add__(g)
+
+ def __sub__(f, g):
+ if isinstance(g, ANP):
+ return f.sub(g)
+ try:
+ g = f.dom.convert(g)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ return f.sub_ground(g)
+
+ def __rsub__(f, g):
+ return (-f).__add__(g)
+
+ def __mul__(f, g):
+ if isinstance(g, ANP):
+ return f.mul(g)
+ try:
+ g = f.dom.convert(g)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ return f.mul_ground(g)
+
+ def __rmul__(f, g):
+ return f.__mul__(g)
+
+ def __pow__(f, n):
+ return f.pow(n)
+
+ def __divmod__(f, g):
+ return f.div(g)
+
+ def __mod__(f, g):
+ return f.rem(g)
+
+ def __truediv__(f, g):
+ if isinstance(g, ANP):
+ return f.quo(g)
+ try:
+ g = f.dom.convert(g)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ return f.quo_ground(g)
+
+ def __eq__(f, g):
+ try:
+ F, G, _, _ = f.unify_ANP(g)
+ except UnificationFailed:
+ return NotImplemented
+ return F == G
+
+ def __ne__(f, g):
+ try:
+ F, G, _, _ = f.unify_ANP(g)
+ except UnificationFailed:
+ return NotImplemented
+ return F != G
+
+ def __lt__(f, g):
+ F, G, _, _ = f.unify_ANP(g)
+ return F < G
+
+ def __le__(f, g):
+ F, G, _, _ = f.unify_ANP(g)
+ return F <= G
+
+ def __gt__(f, g):
+ F, G, _, _ = f.unify_ANP(g)
+ return F > G
+
+ def __ge__(f, g):
+ F, G, _, _ = f.unify_ANP(g)
+ return F >= G
+
+ def __bool__(f):
+ return bool(f._rep)
diff --git a/lib/python3.12/site-packages/sympy/polys/polyconfig.py b/lib/python3.12/site-packages/sympy/polys/polyconfig.py
new file mode 100644
index 0000000000000000000000000000000000000000..75731f7ac4e4f8784ff8f999cc3537bfa3c6659a
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/polyconfig.py
@@ -0,0 +1,67 @@
+"""Configuration utilities for polynomial manipulation algorithms. """
+
+
+from contextlib import contextmanager
+
+_default_config = {
+ 'USE_COLLINS_RESULTANT': False,
+ 'USE_SIMPLIFY_GCD': True,
+ 'USE_HEU_GCD': True,
+
+ 'USE_IRREDUCIBLE_IN_FACTOR': False,
+ 'USE_CYCLOTOMIC_FACTOR': True,
+
+ 'EEZ_RESTART_IF_NEEDED': True,
+ 'EEZ_NUMBER_OF_CONFIGS': 3,
+ 'EEZ_NUMBER_OF_TRIES': 5,
+ 'EEZ_MODULUS_STEP': 2,
+
+ 'GF_IRRED_METHOD': 'rabin',
+ 'GF_FACTOR_METHOD': 'zassenhaus',
+
+ 'GROEBNER': 'buchberger',
+}
+
+_current_config = {}
+
+@contextmanager
+def using(**kwargs):
+ for k, v in kwargs.items():
+ setup(k, v)
+
+ yield
+
+ for k in kwargs.keys():
+ setup(k)
+
+def setup(key, value=None):
+ """Assign a value to (or reset) a configuration item. """
+ key = key.upper()
+
+ if value is not None:
+ _current_config[key] = value
+ else:
+ _current_config[key] = _default_config[key]
+
+
+def query(key):
+ """Ask for a value of the given configuration item. """
+ return _current_config.get(key.upper(), None)
+
+
+def configure():
+ """Initialized configuration of polys module. """
+ from os import getenv
+
+ for key, default in _default_config.items():
+ value = getenv('SYMPY_' + key)
+
+ if value is not None:
+ try:
+ _current_config[key] = eval(value)
+ except NameError:
+ _current_config[key] = value
+ else:
+ _current_config[key] = default
+
+configure()
diff --git a/lib/python3.12/site-packages/sympy/polys/polyfuncs.py b/lib/python3.12/site-packages/sympy/polys/polyfuncs.py
new file mode 100644
index 0000000000000000000000000000000000000000..b412123f7383c68177a88df8817e921d96f6d5af
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/polyfuncs.py
@@ -0,0 +1,321 @@
+"""High-level polynomials manipulation functions. """
+
+
+from sympy.core import S, Basic, symbols, Dummy
+from sympy.polys.polyerrors import (
+ PolificationFailed, ComputationFailed,
+ MultivariatePolynomialError, OptionError)
+from sympy.polys.polyoptions import allowed_flags, build_options
+from sympy.polys.polytools import poly_from_expr, Poly
+from sympy.polys.specialpolys import (
+ symmetric_poly, interpolating_poly)
+from sympy.polys.rings import sring
+from sympy.utilities import numbered_symbols, take, public
+
+@public
+def symmetrize(F, *gens, **args):
+ r"""
+ Rewrite a polynomial in terms of elementary symmetric polynomials.
+
+ A symmetric polynomial is a multivariate polynomial that remains invariant
+ under any variable permutation, i.e., if `f = f(x_1, x_2, \dots, x_n)`,
+ then `f = f(x_{i_1}, x_{i_2}, \dots, x_{i_n})`, where
+ `(i_1, i_2, \dots, i_n)` is a permutation of `(1, 2, \dots, n)` (an
+ element of the group `S_n`).
+
+ Returns a tuple of symmetric polynomials ``(f1, f2, ..., fn)`` such that
+ ``f = f1 + f2 + ... + fn``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polyfuncs import symmetrize
+ >>> from sympy.abc import x, y
+
+ >>> symmetrize(x**2 + y**2)
+ (-2*x*y + (x + y)**2, 0)
+
+ >>> symmetrize(x**2 + y**2, formal=True)
+ (s1**2 - 2*s2, 0, [(s1, x + y), (s2, x*y)])
+
+ >>> symmetrize(x**2 - y**2)
+ (-2*x*y + (x + y)**2, -2*y**2)
+
+ >>> symmetrize(x**2 - y**2, formal=True)
+ (s1**2 - 2*s2, -2*y**2, [(s1, x + y), (s2, x*y)])
+
+ """
+ allowed_flags(args, ['formal', 'symbols'])
+
+ iterable = True
+
+ if not hasattr(F, '__iter__'):
+ iterable = False
+ F = [F]
+
+ R, F = sring(F, *gens, **args)
+ gens = R.symbols
+
+ opt = build_options(gens, args)
+ symbols = opt.symbols
+ symbols = [next(symbols) for i in range(len(gens))]
+
+ result = []
+
+ for f in F:
+ p, r, m = f.symmetrize()
+ result.append((p.as_expr(*symbols), r.as_expr(*gens)))
+
+ polys = [(s, g.as_expr()) for s, (_, g) in zip(symbols, m)]
+
+ if not opt.formal:
+ for i, (sym, non_sym) in enumerate(result):
+ result[i] = (sym.subs(polys), non_sym)
+
+ if not iterable:
+ result, = result
+
+ if not opt.formal:
+ return result
+ else:
+ if iterable:
+ return result, polys
+ else:
+ return result + (polys,)
+
+
+@public
+def horner(f, *gens, **args):
+ """
+ Rewrite a polynomial in Horner form.
+
+ Among other applications, evaluation of a polynomial at a point is optimal
+ when it is applied using the Horner scheme ([1]).
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polyfuncs import horner
+ >>> from sympy.abc import x, y, a, b, c, d, e
+
+ >>> horner(9*x**4 + 8*x**3 + 7*x**2 + 6*x + 5)
+ x*(x*(x*(9*x + 8) + 7) + 6) + 5
+
+ >>> horner(a*x**4 + b*x**3 + c*x**2 + d*x + e)
+ e + x*(d + x*(c + x*(a*x + b)))
+
+ >>> f = 4*x**2*y**2 + 2*x**2*y + 2*x*y**2 + x*y
+
+ >>> horner(f, wrt=x)
+ x*(x*y*(4*y + 2) + y*(2*y + 1))
+
+ >>> horner(f, wrt=y)
+ y*(x*y*(4*x + 2) + x*(2*x + 1))
+
+ References
+ ==========
+ [1] - https://en.wikipedia.org/wiki/Horner_scheme
+
+ """
+ allowed_flags(args, [])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ return exc.expr
+
+ form, gen = S.Zero, F.gen
+
+ if F.is_univariate:
+ for coeff in F.all_coeffs():
+ form = form*gen + coeff
+ else:
+ F, gens = Poly(F, gen), gens[1:]
+
+ for coeff in F.all_coeffs():
+ form = form*gen + horner(coeff, *gens, **args)
+
+ return form
+
+
+@public
+def interpolate(data, x):
+ """
+ Construct an interpolating polynomial for the data points
+ evaluated at point x (which can be symbolic or numeric).
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polyfuncs import interpolate
+ >>> from sympy.abc import a, b, x
+
+ A list is interpreted as though it were paired with a range starting
+ from 1:
+
+ >>> interpolate([1, 4, 9, 16], x)
+ x**2
+
+ This can be made explicit by giving a list of coordinates:
+
+ >>> interpolate([(1, 1), (2, 4), (3, 9)], x)
+ x**2
+
+ The (x, y) coordinates can also be given as keys and values of a
+ dictionary (and the points need not be equispaced):
+
+ >>> interpolate([(-1, 2), (1, 2), (2, 5)], x)
+ x**2 + 1
+ >>> interpolate({-1: 2, 1: 2, 2: 5}, x)
+ x**2 + 1
+
+ If the interpolation is going to be used only once then the
+ value of interest can be passed instead of passing a symbol:
+
+ >>> interpolate([1, 4, 9], 5)
+ 25
+
+ Symbolic coordinates are also supported:
+
+ >>> [(i,interpolate((a, b), i)) for i in range(1, 4)]
+ [(1, a), (2, b), (3, -a + 2*b)]
+ """
+ n = len(data)
+
+ if isinstance(data, dict):
+ if x in data:
+ return S(data[x])
+ X, Y = list(zip(*data.items()))
+ else:
+ if isinstance(data[0], tuple):
+ X, Y = list(zip(*data))
+ if x in X:
+ return S(Y[X.index(x)])
+ else:
+ if x in range(1, n + 1):
+ return S(data[x - 1])
+ Y = list(data)
+ X = list(range(1, n + 1))
+
+ try:
+ return interpolating_poly(n, x, X, Y).expand()
+ except ValueError:
+ d = Dummy()
+ return interpolating_poly(n, d, X, Y).expand().subs(d, x)
+
+
+@public
+def rational_interpolate(data, degnum, X=symbols('x')):
+ """
+ Returns a rational interpolation, where the data points are element of
+ any integral domain.
+
+ The first argument contains the data (as a list of coordinates). The
+ ``degnum`` argument is the degree in the numerator of the rational
+ function. Setting it too high will decrease the maximal degree in the
+ denominator for the same amount of data.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polyfuncs import rational_interpolate
+
+ >>> data = [(1, -210), (2, -35), (3, 105), (4, 231), (5, 350), (6, 465)]
+ >>> rational_interpolate(data, 2)
+ (105*x**2 - 525)/(x + 1)
+
+ Values do not need to be integers:
+
+ >>> from sympy import sympify
+ >>> x = [1, 2, 3, 4, 5, 6]
+ >>> y = sympify("[-1, 0, 2, 22/5, 7, 68/7]")
+ >>> rational_interpolate(zip(x, y), 2)
+ (3*x**2 - 7*x + 2)/(x + 1)
+
+ The symbol for the variable can be changed if needed:
+ >>> from sympy import symbols
+ >>> z = symbols('z')
+ >>> rational_interpolate(data, 2, X=z)
+ (105*z**2 - 525)/(z + 1)
+
+ References
+ ==========
+
+ .. [1] Algorithm is adapted from:
+ http://axiom-wiki.newsynthesis.org/RationalInterpolation
+
+ """
+ from sympy.matrices.dense import ones
+
+ xdata, ydata = list(zip(*data))
+
+ k = len(xdata) - degnum - 1
+ if k < 0:
+ raise OptionError("Too few values for the required degree.")
+ c = ones(degnum + k + 1, degnum + k + 2)
+ for j in range(max(degnum, k)):
+ for i in range(degnum + k + 1):
+ c[i, j + 1] = c[i, j]*xdata[i]
+ for j in range(k + 1):
+ for i in range(degnum + k + 1):
+ c[i, degnum + k + 1 - j] = -c[i, k - j]*ydata[i]
+ r = c.nullspace()[0]
+ return (sum(r[i] * X**i for i in range(degnum + 1))
+ / sum(r[i + degnum + 1] * X**i for i in range(k + 1)))
+
+
+@public
+def viete(f, roots=None, *gens, **args):
+ """
+ Generate Viete's formulas for ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polyfuncs import viete
+ >>> from sympy import symbols
+
+ >>> x, a, b, c, r1, r2 = symbols('x,a:c,r1:3')
+
+ >>> viete(a*x**2 + b*x + c, [r1, r2], x)
+ [(r1 + r2, -b/a), (r1*r2, c/a)]
+
+ """
+ allowed_flags(args, [])
+
+ if isinstance(roots, Basic):
+ gens, roots = (roots,) + gens, None
+
+ try:
+ f, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('viete', 1, exc)
+
+ if f.is_multivariate:
+ raise MultivariatePolynomialError(
+ "multivariate polynomials are not allowed")
+
+ n = f.degree()
+
+ if n < 1:
+ raise ValueError(
+ "Cannot derive Viete's formulas for a constant polynomial")
+
+ if roots is None:
+ roots = numbered_symbols('r', start=1)
+
+ roots = take(roots, n)
+
+ if n != len(roots):
+ raise ValueError("required %s roots, got %s" % (n, len(roots)))
+
+ lc, coeffs = f.LC(), f.all_coeffs()
+ result, sign = [], -1
+
+ for i, coeff in enumerate(coeffs[1:]):
+ poly = symmetric_poly(i + 1, roots)
+ coeff = sign*(coeff/lc)
+ result.append((poly, coeff))
+ sign = -sign
+
+ return result
diff --git a/lib/python3.12/site-packages/sympy/polys/polymatrix.py b/lib/python3.12/site-packages/sympy/polys/polymatrix.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb2a58efc3ebfd85507ac2b0cfd31230e55ded66
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/polymatrix.py
@@ -0,0 +1,292 @@
+from sympy.core.expr import Expr
+from sympy.core.symbol import Dummy
+from sympy.core.sympify import _sympify
+
+from sympy.polys.polyerrors import CoercionFailed
+from sympy.polys.polytools import Poly, parallel_poly_from_expr
+from sympy.polys.domains import QQ
+
+from sympy.polys.matrices import DomainMatrix
+from sympy.polys.matrices.domainscalar import DomainScalar
+
+
+class MutablePolyDenseMatrix:
+ """
+ A mutable matrix of objects from poly module or to operate with them.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polymatrix import PolyMatrix
+ >>> from sympy import Symbol, Poly
+ >>> x = Symbol('x')
+ >>> pm1 = PolyMatrix([[Poly(x**2, x), Poly(-x, x)], [Poly(x**3, x), Poly(-1 + x, x)]])
+ >>> v1 = PolyMatrix([[1, 0], [-1, 0]], x)
+ >>> pm1*v1
+ PolyMatrix([
+ [ x**2 + x, 0],
+ [x**3 - x + 1, 0]], ring=QQ[x])
+
+ >>> pm1.ring
+ ZZ[x]
+
+ >>> v1*pm1
+ PolyMatrix([
+ [ x**2, -x],
+ [-x**2, x]], ring=QQ[x])
+
+ >>> pm2 = PolyMatrix([[Poly(x**2, x, domain='QQ'), Poly(0, x, domain='QQ'), Poly(1, x, domain='QQ'), \
+ Poly(x**3, x, domain='QQ'), Poly(0, x, domain='QQ'), Poly(-x**3, x, domain='QQ')]])
+ >>> v2 = PolyMatrix([1, 0, 0, 0, 0, 0], x)
+ >>> v2.ring
+ QQ[x]
+ >>> pm2*v2
+ PolyMatrix([[x**2]], ring=QQ[x])
+
+ """
+
+ def __new__(cls, *args, ring=None):
+
+ if not args:
+ # PolyMatrix(ring=QQ[x])
+ if ring is None:
+ raise TypeError("The ring needs to be specified for an empty PolyMatrix")
+ rows, cols, items, gens = 0, 0, [], ()
+ elif isinstance(args[0], list):
+ elements, gens = args[0], args[1:]
+ if not elements:
+ # PolyMatrix([])
+ rows, cols, items = 0, 0, []
+ elif isinstance(elements[0], (list, tuple)):
+ # PolyMatrix([[1, 2]], x)
+ rows, cols = len(elements), len(elements[0])
+ items = [e for row in elements for e in row]
+ else:
+ # PolyMatrix([1, 2], x)
+ rows, cols = len(elements), 1
+ items = elements
+ elif [type(a) for a in args[:3]] == [int, int, list]:
+ # PolyMatrix(2, 2, [1, 2, 3, 4], x)
+ rows, cols, items, gens = args[0], args[1], args[2], args[3:]
+ elif [type(a) for a in args[:3]] == [int, int, type(lambda: 0)]:
+ # PolyMatrix(2, 2, lambda i, j: i+j, x)
+ rows, cols, func, gens = args[0], args[1], args[2], args[3:]
+ items = [func(i, j) for i in range(rows) for j in range(cols)]
+ else:
+ raise TypeError("Invalid arguments")
+
+ # PolyMatrix([[1]], x, y) vs PolyMatrix([[1]], (x, y))
+ if len(gens) == 1 and isinstance(gens[0], tuple):
+ gens = gens[0]
+ # gens is now a tuple (x, y)
+
+ return cls.from_list(rows, cols, items, gens, ring)
+
+ @classmethod
+ def from_list(cls, rows, cols, items, gens, ring):
+
+ # items can be Expr, Poly, or a mix of Expr and Poly
+ items = [_sympify(item) for item in items]
+ if items and all(isinstance(item, Poly) for item in items):
+ polys = True
+ else:
+ polys = False
+
+ # Identify the ring for the polys
+ if ring is not None:
+ # Parse a domain string like 'QQ[x]'
+ if isinstance(ring, str):
+ ring = Poly(0, Dummy(), domain=ring).domain
+ elif polys:
+ p = items[0]
+ for p2 in items[1:]:
+ p, _ = p.unify(p2)
+ ring = p.domain[p.gens]
+ else:
+ items, info = parallel_poly_from_expr(items, gens, field=True)
+ ring = info['domain'][info['gens']]
+ polys = True
+
+ # Efficiently convert when all elements are Poly
+ if polys:
+ p_ring = Poly(0, ring.symbols, domain=ring.domain)
+ to_ring = ring.ring.from_list
+ convert_poly = lambda p: to_ring(p.unify(p_ring)[0].rep.to_list())
+ elements = [convert_poly(p) for p in items]
+ else:
+ convert_expr = ring.from_sympy
+ elements = [convert_expr(e.as_expr()) for e in items]
+
+ # Convert to domain elements and construct DomainMatrix
+ elements_lol = [[elements[i*cols + j] for j in range(cols)] for i in range(rows)]
+ dm = DomainMatrix(elements_lol, (rows, cols), ring)
+ return cls.from_dm(dm)
+
+ @classmethod
+ def from_dm(cls, dm):
+ obj = super().__new__(cls)
+ dm = dm.to_sparse()
+ R = dm.domain
+ obj._dm = dm
+ obj.ring = R
+ obj.domain = R.domain
+ obj.gens = R.symbols
+ return obj
+
+ def to_Matrix(self):
+ return self._dm.to_Matrix()
+
+ @classmethod
+ def from_Matrix(cls, other, *gens, ring=None):
+ return cls(*other.shape, other.flat(), *gens, ring=ring)
+
+ def set_gens(self, gens):
+ return self.from_Matrix(self.to_Matrix(), gens)
+
+ def __repr__(self):
+ if self.rows * self.cols:
+ return 'Poly' + repr(self.to_Matrix())[:-1] + f', ring={self.ring})'
+ else:
+ return f'PolyMatrix({self.rows}, {self.cols}, [], ring={self.ring})'
+
+ @property
+ def shape(self):
+ return self._dm.shape
+
+ @property
+ def rows(self):
+ return self.shape[0]
+
+ @property
+ def cols(self):
+ return self.shape[1]
+
+ def __len__(self):
+ return self.rows * self.cols
+
+ def __getitem__(self, key):
+
+ def to_poly(v):
+ ground = self._dm.domain.domain
+ gens = self._dm.domain.symbols
+ return Poly(v.to_dict(), gens, domain=ground)
+
+ dm = self._dm
+
+ if isinstance(key, slice):
+ items = dm.flat()[key]
+ return [to_poly(item) for item in items]
+ elif isinstance(key, int):
+ i, j = divmod(key, self.cols)
+ e = dm[i,j]
+ return to_poly(e.element)
+
+ i, j = key
+ if isinstance(i, int) and isinstance(j, int):
+ return to_poly(dm[i, j].element)
+ else:
+ return self.from_dm(dm[i, j])
+
+ def __eq__(self, other):
+ if not isinstance(self, type(other)):
+ return NotImplemented
+ return self._dm == other._dm
+
+ def __add__(self, other):
+ if isinstance(other, type(self)):
+ return self.from_dm(self._dm + other._dm)
+ return NotImplemented
+
+ def __sub__(self, other):
+ if isinstance(other, type(self)):
+ return self.from_dm(self._dm - other._dm)
+ return NotImplemented
+
+ def __mul__(self, other):
+ if isinstance(other, type(self)):
+ return self.from_dm(self._dm * other._dm)
+ elif isinstance(other, int):
+ other = _sympify(other)
+ if isinstance(other, Expr):
+ Kx = self.ring
+ try:
+ other_ds = DomainScalar(Kx.from_sympy(other), Kx)
+ except (CoercionFailed, ValueError):
+ other_ds = DomainScalar.from_sympy(other)
+ return self.from_dm(self._dm * other_ds)
+ return NotImplemented
+
+ def __rmul__(self, other):
+ if isinstance(other, int):
+ other = _sympify(other)
+ if isinstance(other, Expr):
+ other_ds = DomainScalar.from_sympy(other)
+ return self.from_dm(other_ds * self._dm)
+ return NotImplemented
+
+ def __truediv__(self, other):
+
+ if isinstance(other, Poly):
+ other = other.as_expr()
+ elif isinstance(other, int):
+ other = _sympify(other)
+ if not isinstance(other, Expr):
+ return NotImplemented
+
+ other = self.domain.from_sympy(other)
+ inverse = self.ring.convert_from(1/other, self.domain)
+ inverse = DomainScalar(inverse, self.ring)
+ dm = self._dm * inverse
+ return self.from_dm(dm)
+
+ def __neg__(self):
+ return self.from_dm(-self._dm)
+
+ def transpose(self):
+ return self.from_dm(self._dm.transpose())
+
+ def row_join(self, other):
+ dm = DomainMatrix.hstack(self._dm, other._dm)
+ return self.from_dm(dm)
+
+ def col_join(self, other):
+ dm = DomainMatrix.vstack(self._dm, other._dm)
+ return self.from_dm(dm)
+
+ def applyfunc(self, func):
+ M = self.to_Matrix().applyfunc(func)
+ return self.from_Matrix(M, self.gens)
+
+ @classmethod
+ def eye(cls, n, gens):
+ return cls.from_dm(DomainMatrix.eye(n, QQ[gens]))
+
+ @classmethod
+ def zeros(cls, m, n, gens):
+ return cls.from_dm(DomainMatrix.zeros((m, n), QQ[gens]))
+
+ def rref(self, simplify='ignore', normalize_last='ignore'):
+ # If this is K[x] then computes RREF in ground field K.
+ if not (self.domain.is_Field and all(p.is_ground for p in self)):
+ raise ValueError("PolyMatrix rref is only for ground field elements")
+ dm = self._dm
+ dm_ground = dm.convert_to(dm.domain.domain)
+ dm_rref, pivots = dm_ground.rref()
+ dm_rref = dm_rref.convert_to(dm.domain)
+ return self.from_dm(dm_rref), pivots
+
+ def nullspace(self):
+ # If this is K[x] then computes nullspace in ground field K.
+ if not (self.domain.is_Field and all(p.is_ground for p in self)):
+ raise ValueError("PolyMatrix nullspace is only for ground field elements")
+ dm = self._dm
+ K, Kx = self.domain, self.ring
+ dm_null_rows = dm.convert_to(K).nullspace(divide_last=True).convert_to(Kx)
+ dm_null = dm_null_rows.transpose()
+ dm_basis = [dm_null[:,i] for i in range(dm_null.shape[1])]
+ return [self.from_dm(dmvec) for dmvec in dm_basis]
+
+ def rank(self):
+ return self.cols - len(self.nullspace())
+
+MutablePolyMatrix = PolyMatrix = MutablePolyDenseMatrix
diff --git a/lib/python3.12/site-packages/sympy/polys/polyquinticconst.py b/lib/python3.12/site-packages/sympy/polys/polyquinticconst.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b17096fd2cf3b205c3b819eb11ffc2012ea125b
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/polyquinticconst.py
@@ -0,0 +1,187 @@
+"""
+Solving solvable quintics - An implementation of DS Dummit's paper
+
+Paper :
+https://www.ams.org/journals/mcom/1991-57-195/S0025-5718-1991-1079014-X/S0025-5718-1991-1079014-X.pdf
+
+Mathematica notebook:
+http://www.emba.uvm.edu/~ddummit/quintics/quintics.nb
+
+"""
+
+
+from sympy.core import Symbol
+from sympy.core.evalf import N
+from sympy.core.numbers import I, Rational
+from sympy.functions import sqrt
+from sympy.polys.polytools import Poly
+from sympy.utilities import public
+
+x = Symbol('x')
+
+@public
+class PolyQuintic:
+ """Special functions for solvable quintics"""
+ def __init__(self, poly):
+ _, _, self.p, self.q, self.r, self.s = poly.all_coeffs()
+ self.zeta1 = Rational(-1, 4) + (sqrt(5)/4) + I*sqrt((sqrt(5)/8) + Rational(5, 8))
+ self.zeta2 = (-sqrt(5)/4) - Rational(1, 4) + I*sqrt((-sqrt(5)/8) + Rational(5, 8))
+ self.zeta3 = (-sqrt(5)/4) - Rational(1, 4) - I*sqrt((-sqrt(5)/8) + Rational(5, 8))
+ self.zeta4 = Rational(-1, 4) + (sqrt(5)/4) - I*sqrt((sqrt(5)/8) + Rational(5, 8))
+
+ @property
+ def f20(self):
+ p, q, r, s = self.p, self.q, self.r, self.s
+ f20 = q**8 - 13*p*q**6*r + p**5*q**2*r**2 + 65*p**2*q**4*r**2 - 4*p**6*r**3 - 128*p**3*q**2*r**3 + 17*q**4*r**3 + 48*p**4*r**4 - 16*p*q**2*r**4 - 192*p**2*r**5 + 256*r**6 - 4*p**5*q**3*s - 12*p**2*q**5*s + 18*p**6*q*r*s + 12*p**3*q**3*r*s - 124*q**5*r*s + 196*p**4*q*r**2*s + 590*p*q**3*r**2*s - 160*p**2*q*r**3*s - 1600*q*r**4*s - 27*p**7*s**2 - 150*p**4*q**2*s**2 - 125*p*q**4*s**2 - 99*p**5*r*s**2 - 725*p**2*q**2*r*s**2 + 1200*p**3*r**2*s**2 + 3250*q**2*r**2*s**2 - 2000*p*r**3*s**2 - 1250*p*q*r*s**3 + 3125*p**2*s**4 - 9375*r*s**4-(2*p*q**6 - 19*p**2*q**4*r + 51*p**3*q**2*r**2 - 3*q**4*r**2 - 32*p**4*r**3 - 76*p*q**2*r**3 + 256*p**2*r**4 - 512*r**5 + 31*p**3*q**3*s + 58*q**5*s - 117*p**4*q*r*s - 105*p*q**3*r*s - 260*p**2*q*r**2*s + 2400*q*r**3*s + 108*p**5*s**2 + 325*p**2*q**2*s**2 - 525*p**3*r*s**2 - 2750*q**2*r*s**2 + 500*p*r**2*s**2 - 625*p*q*s**3 + 3125*s**4)*x+(p**2*q**4 - 6*p**3*q**2*r - 8*q**4*r + 9*p**4*r**2 + 76*p*q**2*r**2 - 136*p**2*r**3 + 400*r**4 - 50*p*q**3*s + 90*p**2*q*r*s - 1400*q*r**2*s + 625*q**2*s**2 + 500*p*r*s**2)*x**2-(2*q**4 - 21*p*q**2*r + 40*p**2*r**2 - 160*r**3 + 15*p**2*q*s + 400*q*r*s - 125*p*s**2)*x**3+(2*p*q**2 - 6*p**2*r + 40*r**2 - 50*q*s)*x**4 + 8*r*x**5 + x**6
+ return Poly(f20, x)
+
+ @property
+ def b(self):
+ p, q, r, s = self.p, self.q, self.r, self.s
+ b = ( [], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0], [0,0,0,0,0,0],)
+
+ b[1][5] = 100*p**7*q**7 + 2175*p**4*q**9 + 10500*p*q**11 - 1100*p**8*q**5*r - 27975*p**5*q**7*r - 152950*p**2*q**9*r + 4125*p**9*q**3*r**2 + 128875*p**6*q**5*r**2 + 830525*p**3*q**7*r**2 - 59450*q**9*r**2 - 5400*p**10*q*r**3 - 243800*p**7*q**3*r**3 - 2082650*p**4*q**5*r**3 + 333925*p*q**7*r**3 + 139200*p**8*q*r**4 + 2406000*p**5*q**3*r**4 + 122600*p**2*q**5*r**4 - 1254400*p**6*q*r**5 - 3776000*p**3*q**3*r**5 - 1832000*q**5*r**5 + 4736000*p**4*q*r**6 + 6720000*p*q**3*r**6 - 6400000*p**2*q*r**7 + 900*p**9*q**4*s + 37400*p**6*q**6*s + 281625*p**3*q**8*s + 435000*q**10*s - 6750*p**10*q**2*r*s - 322300*p**7*q**4*r*s - 2718575*p**4*q**6*r*s - 4214250*p*q**8*r*s + 16200*p**11*r**2*s + 859275*p**8*q**2*r**2*s + 8925475*p**5*q**4*r**2*s + 14427875*p**2*q**6*r**2*s - 453600*p**9*r**3*s - 10038400*p**6*q**2*r**3*s - 17397500*p**3*q**4*r**3*s + 11333125*q**6*r**3*s + 4451200*p**7*r**4*s + 15850000*p**4*q**2*r**4*s - 34000000*p*q**4*r**4*s - 17984000*p**5*r**5*s + 10000000*p**2*q**2*r**5*s + 25600000*p**3*r**6*s + 8000000*q**2*r**6*s - 6075*p**11*q*s**2 + 83250*p**8*q**3*s**2 + 1282500*p**5*q**5*s**2 + 2862500*p**2*q**7*s**2 - 724275*p**9*q*r*s**2 - 9807250*p**6*q**3*r*s**2 - 28374375*p**3*q**5*r*s**2 - 22212500*q**7*r*s**2 + 8982000*p**7*q*r**2*s**2 + 39600000*p**4*q**3*r**2*s**2 + 61746875*p*q**5*r**2*s**2 + 1010000*p**5*q*r**3*s**2 + 1000000*p**2*q**3*r**3*s**2 - 78000000*p**3*q*r**4*s**2 - 30000000*q**3*r**4*s**2 - 80000000*p*q*r**5*s**2 + 759375*p**10*s**3 + 9787500*p**7*q**2*s**3 + 39062500*p**4*q**4*s**3 + 52343750*p*q**6*s**3 - 12301875*p**8*r*s**3 - 98175000*p**5*q**2*r*s**3 - 225078125*p**2*q**4*r*s**3 + 54900000*p**6*r**2*s**3 + 310000000*p**3*q**2*r**2*s**3 + 7890625*q**4*r**2*s**3 - 51250000*p**4*r**3*s**3 + 420000000*p*q**2*r**3*s**3 - 110000000*p**2*r**4*s**3 + 200000000*r**5*s**3 - 2109375*p**6*q*s**4 + 21093750*p**3*q**3*s**4 + 89843750*q**5*s**4 - 182343750*p**4*q*r*s**4 - 733203125*p*q**3*r*s**4 + 196875000*p**2*q*r**2*s**4 - 1125000000*q*r**3*s**4 + 158203125*p**5*s**5 + 566406250*p**2*q**2*s**5 - 101562500*p**3*r*s**5 + 1669921875*q**2*r*s**5 - 1250000000*p*r**2*s**5 + 1220703125*p*q*s**6 - 6103515625*s**7
+
+ b[1][4] = -1000*p**5*q**7 - 7250*p**2*q**9 + 10800*p**6*q**5*r + 96900*p**3*q**7*r + 52500*q**9*r - 37400*p**7*q**3*r**2 - 470850*p**4*q**5*r**2 - 640600*p*q**7*r**2 + 39600*p**8*q*r**3 + 983600*p**5*q**3*r**3 + 2848100*p**2*q**5*r**3 - 814400*p**6*q*r**4 - 6076000*p**3*q**3*r**4 - 2308000*q**5*r**4 + 5024000*p**4*q*r**5 + 9680000*p*q**3*r**5 - 9600000*p**2*q*r**6 - 13800*p**7*q**4*s - 94650*p**4*q**6*s + 26500*p*q**8*s + 86400*p**8*q**2*r*s + 816500*p**5*q**4*r*s + 257500*p**2*q**6*r*s - 91800*p**9*r**2*s - 1853700*p**6*q**2*r**2*s - 630000*p**3*q**4*r**2*s + 8971250*q**6*r**2*s + 2071200*p**7*r**3*s + 7240000*p**4*q**2*r**3*s - 29375000*p*q**4*r**3*s - 14416000*p**5*r**4*s + 5200000*p**2*q**2*r**4*s + 30400000*p**3*r**5*s + 12000000*q**2*r**5*s - 64800*p**9*q*s**2 - 567000*p**6*q**3*s**2 - 1655000*p**3*q**5*s**2 - 6987500*q**7*s**2 - 337500*p**7*q*r*s**2 - 8462500*p**4*q**3*r*s**2 + 5812500*p*q**5*r*s**2 + 24930000*p**5*q*r**2*s**2 + 69125000*p**2*q**3*r**2*s**2 - 103500000*p**3*q*r**3*s**2 - 30000000*q**3*r**3*s**2 - 90000000*p*q*r**4*s**2 + 708750*p**8*s**3 + 5400000*p**5*q**2*s**3 - 8906250*p**2*q**4*s**3 - 18562500*p**6*r*s**3 + 625000*p**3*q**2*r*s**3 - 29687500*q**4*r*s**3 + 75000000*p**4*r**2*s**3 + 416250000*p*q**2*r**2*s**3 - 60000000*p**2*r**3*s**3 + 300000000*r**4*s**3 - 71718750*p**4*q*s**4 - 189062500*p*q**3*s**4 - 210937500*p**2*q*r*s**4 - 1187500000*q*r**2*s**4 + 187500000*p**3*s**5 + 800781250*q**2*s**5 + 390625000*p*r*s**5
+
+ b[1][3] = 500*p**6*q**5 + 6350*p**3*q**7 + 19800*q**9 - 3750*p**7*q**3*r - 65100*p**4*q**5*r - 264950*p*q**7*r + 6750*p**8*q*r**2 + 209050*p**5*q**3*r**2 + 1217250*p**2*q**5*r**2 - 219000*p**6*q*r**3 - 2510000*p**3*q**3*r**3 - 1098500*q**5*r**3 + 2068000*p**4*q*r**4 + 5060000*p*q**3*r**4 - 5200000*p**2*q*r**5 + 6750*p**8*q**2*s + 96350*p**5*q**4*s + 346000*p**2*q**6*s - 20250*p**9*r*s - 459900*p**6*q**2*r*s - 1828750*p**3*q**4*r*s + 2930000*q**6*r*s + 594000*p**7*r**2*s + 4301250*p**4*q**2*r**2*s - 10906250*p*q**4*r**2*s - 5252000*p**5*r**3*s + 1450000*p**2*q**2*r**3*s + 12800000*p**3*r**4*s + 6500000*q**2*r**4*s - 74250*p**7*q*s**2 - 1418750*p**4*q**3*s**2 - 5956250*p*q**5*s**2 + 4297500*p**5*q*r*s**2 + 29906250*p**2*q**3*r*s**2 - 31500000*p**3*q*r**2*s**2 - 12500000*q**3*r**2*s**2 - 35000000*p*q*r**3*s**2 - 1350000*p**6*s**3 - 6093750*p**3*q**2*s**3 - 17500000*q**4*s**3 + 7031250*p**4*r*s**3 + 127812500*p*q**2*r*s**3 - 18750000*p**2*r**2*s**3 + 162500000*r**3*s**3 - 107812500*p**2*q*s**4 - 460937500*q*r*s**4 + 214843750*p*s**5
+
+ b[1][2] = -1950*p**4*q**5 - 14100*p*q**7 + 14350*p**5*q**3*r + 125600*p**2*q**5*r - 27900*p**6*q*r**2 - 402250*p**3*q**3*r**2 - 288250*q**5*r**2 + 436000*p**4*q*r**3 + 1345000*p*q**3*r**3 - 1400000*p**2*q*r**4 - 9450*p**6*q**2*s + 1250*p**3*q**4*s + 465000*q**6*s + 49950*p**7*r*s + 302500*p**4*q**2*r*s - 1718750*p*q**4*r*s - 834000*p**5*r**2*s - 437500*p**2*q**2*r**2*s + 3100000*p**3*r**3*s + 1750000*q**2*r**3*s + 292500*p**5*q*s**2 + 1937500*p**2*q**3*s**2 - 3343750*p**3*q*r*s**2 - 1875000*q**3*r*s**2 - 8125000*p*q*r**2*s**2 + 1406250*p**4*s**3 + 12343750*p*q**2*s**3 - 5312500*p**2*r*s**3 + 43750000*r**2*s**3 - 74218750*q*s**4
+
+ b[1][1] = 300*p**5*q**3 + 2150*p**2*q**5 - 1350*p**6*q*r - 21500*p**3*q**3*r - 61500*q**5*r + 42000*p**4*q*r**2 + 290000*p*q**3*r**2 - 300000*p**2*q*r**3 + 4050*p**7*s + 45000*p**4*q**2*s + 125000*p*q**4*s - 108000*p**5*r*s - 643750*p**2*q**2*r*s + 700000*p**3*r**2*s + 375000*q**2*r**2*s + 93750*p**3*q*s**2 + 312500*q**3*s**2 - 1875000*p*q*r*s**2 + 1406250*p**2*s**3 + 9375000*r*s**3
+
+ b[1][0] = -1250*p**3*q**3 - 9000*q**5 + 4500*p**4*q*r + 46250*p*q**3*r - 50000*p**2*q*r**2 - 6750*p**5*s - 43750*p**2*q**2*s + 75000*p**3*r*s + 62500*q**2*r*s - 156250*p*q*s**2 + 1562500*s**3
+
+ b[2][5] = 200*p**6*q**11 - 250*p**3*q**13 - 10800*q**15 - 3900*p**7*q**9*r - 3325*p**4*q**11*r + 181800*p*q**13*r + 26950*p**8*q**7*r**2 + 69625*p**5*q**9*r**2 - 1214450*p**2*q**11*r**2 - 78725*p**9*q**5*r**3 - 368675*p**6*q**7*r**3 + 4166325*p**3*q**9*r**3 + 1131100*q**11*r**3 + 73400*p**10*q**3*r**4 + 661950*p**7*q**5*r**4 - 9151950*p**4*q**7*r**4 - 16633075*p*q**9*r**4 + 36000*p**11*q*r**5 + 135600*p**8*q**3*r**5 + 17321400*p**5*q**5*r**5 + 85338300*p**2*q**7*r**5 - 832000*p**9*q*r**6 - 21379200*p**6*q**3*r**6 - 176044000*p**3*q**5*r**6 - 1410000*q**7*r**6 + 6528000*p**7*q*r**7 + 129664000*p**4*q**3*r**7 + 47344000*p*q**5*r**7 - 21504000*p**5*q*r**8 - 115200000*p**2*q**3*r**8 + 25600000*p**3*q*r**9 + 64000000*q**3*r**9 + 15700*p**8*q**8*s + 120525*p**5*q**10*s + 113250*p**2*q**12*s - 196900*p**9*q**6*r*s - 1776925*p**6*q**8*r*s - 3062475*p**3*q**10*r*s - 4153500*q**12*r*s + 857925*p**10*q**4*r**2*s + 10562775*p**7*q**6*r**2*s + 34866250*p**4*q**8*r**2*s + 73486750*p*q**10*r**2*s - 1333800*p**11*q**2*r**3*s - 29212625*p**8*q**4*r**3*s - 168729675*p**5*q**6*r**3*s - 427230750*p**2*q**8*r**3*s + 108000*p**12*r**4*s + 30384200*p**9*q**2*r**4*s + 324535100*p**6*q**4*r**4*s + 952666750*p**3*q**6*r**4*s - 38076875*q**8*r**4*s - 4296000*p**10*r**5*s - 213606400*p**7*q**2*r**5*s - 842060000*p**4*q**4*r**5*s - 95285000*p*q**6*r**5*s + 61184000*p**8*r**6*s + 567520000*p**5*q**2*r**6*s + 547000000*p**2*q**4*r**6*s - 390912000*p**6*r**7*s - 812800000*p**3*q**2*r**7*s - 924000000*q**4*r**7*s + 1152000000*p**4*r**8*s + 800000000*p*q**2*r**8*s - 1280000000*p**2*r**9*s + 141750*p**10*q**5*s**2 - 31500*p**7*q**7*s**2 - 11325000*p**4*q**9*s**2 - 31687500*p*q**11*s**2 - 1293975*p**11*q**3*r*s**2 - 4803800*p**8*q**5*r*s**2 + 71398250*p**5*q**7*r*s**2 + 227625000*p**2*q**9*r*s**2 + 3256200*p**12*q*r**2*s**2 + 43870125*p**9*q**3*r**2*s**2 + 64581500*p**6*q**5*r**2*s**2 + 56090625*p**3*q**7*r**2*s**2 + 260218750*q**9*r**2*s**2 - 74610000*p**10*q*r**3*s**2 - 662186500*p**7*q**3*r**3*s**2 - 1987747500*p**4*q**5*r**3*s**2 - 811928125*p*q**7*r**3*s**2 + 471286000*p**8*q*r**4*s**2 + 2106040000*p**5*q**3*r**4*s**2 + 792687500*p**2*q**5*r**4*s**2 - 135120000*p**6*q*r**5*s**2 + 2479000000*p**3*q**3*r**5*s**2 + 5242250000*q**5*r**5*s**2 - 6400000000*p**4*q*r**6*s**2 - 8620000000*p*q**3*r**6*s**2 + 13280000000*p**2*q*r**7*s**2 + 1600000000*q*r**8*s**2 + 273375*p**12*q**2*s**3 - 13612500*p**9*q**4*s**3 - 177250000*p**6*q**6*s**3 - 511015625*p**3*q**8*s**3 - 320937500*q**10*s**3 - 2770200*p**13*r*s**3 + 12595500*p**10*q**2*r*s**3 + 543950000*p**7*q**4*r*s**3 + 1612281250*p**4*q**6*r*s**3 + 968125000*p*q**8*r*s**3 + 77031000*p**11*r**2*s**3 + 373218750*p**8*q**2*r**2*s**3 + 1839765625*p**5*q**4*r**2*s**3 + 1818515625*p**2*q**6*r**2*s**3 - 776745000*p**9*r**3*s**3 - 6861075000*p**6*q**2*r**3*s**3 - 20014531250*p**3*q**4*r**3*s**3 - 13747812500*q**6*r**3*s**3 + 3768000000*p**7*r**4*s**3 + 35365000000*p**4*q**2*r**4*s**3 + 34441875000*p*q**4*r**4*s**3 - 9628000000*p**5*r**5*s**3 - 63230000000*p**2*q**2*r**5*s**3 + 13600000000*p**3*r**6*s**3 - 15000000000*q**2*r**6*s**3 - 10400000000*p*r**7*s**3 - 45562500*p**11*q*s**4 - 525937500*p**8*q**3*s**4 - 1364218750*p**5*q**5*s**4 - 1382812500*p**2*q**7*s**4 + 572062500*p**9*q*r*s**4 + 2473515625*p**6*q**3*r*s**4 + 13192187500*p**3*q**5*r*s**4 + 12703125000*q**7*r*s**4 - 451406250*p**7*q*r**2*s**4 - 18153906250*p**4*q**3*r**2*s**4 - 36908203125*p*q**5*r**2*s**4 - 9069375000*p**5*q*r**3*s**4 + 79957812500*p**2*q**3*r**3*s**4 + 5512500000*p**3*q*r**4*s**4 + 50656250000*q**3*r**4*s**4 + 74750000000*p*q*r**5*s**4 + 56953125*p**10*s**5 + 1381640625*p**7*q**2*s**5 - 781250000*p**4*q**4*s**5 + 878906250*p*q**6*s**5 - 2655703125*p**8*r*s**5 - 3223046875*p**5*q**2*r*s**5 - 35117187500*p**2*q**4*r*s**5 + 26573437500*p**6*r**2*s**5 + 14785156250*p**3*q**2*r**2*s**5 - 52050781250*q**4*r**2*s**5 - 103062500000*p**4*r**3*s**5 - 281796875000*p*q**2*r**3*s**5 + 146875000000*p**2*r**4*s**5 - 37500000000*r**5*s**5 - 8789062500*p**6*q*s**6 - 3906250000*p**3*q**3*s**6 + 1464843750*q**5*s**6 + 102929687500*p**4*q*r*s**6 + 297119140625*p*q**3*r*s**6 - 217773437500*p**2*q*r**2*s**6 + 167968750000*q*r**3*s**6 + 10986328125*p**5*s**7 + 98876953125*p**2*q**2*s**7 - 188964843750*p**3*r*s**7 - 278320312500*q**2*r*s**7 + 517578125000*p*r**2*s**7 - 610351562500*p*q*s**8 + 762939453125*s**9
+
+ b[2][4] = -200*p**7*q**9 + 1850*p**4*q**11 + 21600*p*q**13 + 3200*p**8*q**7*r - 19200*p**5*q**9*r - 316350*p**2*q**11*r - 19050*p**9*q**5*r**2 + 37400*p**6*q**7*r**2 + 1759250*p**3*q**9*r**2 + 440100*q**11*r**2 + 48750*p**10*q**3*r**3 + 190200*p**7*q**5*r**3 - 4604200*p**4*q**7*r**3 - 6072800*p*q**9*r**3 - 43200*p**11*q*r**4 - 834500*p**8*q**3*r**4 + 4916000*p**5*q**5*r**4 + 27926850*p**2*q**7*r**4 + 969600*p**9*q*r**5 + 2467200*p**6*q**3*r**5 - 45393200*p**3*q**5*r**5 - 5399500*q**7*r**5 - 7283200*p**7*q*r**6 + 10536000*p**4*q**3*r**6 + 41656000*p*q**5*r**6 + 22784000*p**5*q*r**7 - 35200000*p**2*q**3*r**7 - 25600000*p**3*q*r**8 + 96000000*q**3*r**8 - 3000*p**9*q**6*s + 40400*p**6*q**8*s + 136550*p**3*q**10*s - 1647000*q**12*s + 40500*p**10*q**4*r*s - 173600*p**7*q**6*r*s - 126500*p**4*q**8*r*s + 23969250*p*q**10*r*s - 153900*p**11*q**2*r**2*s - 486150*p**8*q**4*r**2*s - 4115800*p**5*q**6*r**2*s - 112653250*p**2*q**8*r**2*s + 129600*p**12*r**3*s + 2683350*p**9*q**2*r**3*s + 10906650*p**6*q**4*r**3*s + 187289500*p**3*q**6*r**3*s + 44098750*q**8*r**3*s - 4384800*p**10*r**4*s - 35660800*p**7*q**2*r**4*s - 175420000*p**4*q**4*r**4*s - 426538750*p*q**6*r**4*s + 60857600*p**8*r**5*s + 349436000*p**5*q**2*r**5*s + 900600000*p**2*q**4*r**5*s - 429568000*p**6*r**6*s - 1511200000*p**3*q**2*r**6*s - 1286000000*q**4*r**6*s + 1472000000*p**4*r**7*s + 1440000000*p*q**2*r**7*s - 1920000000*p**2*r**8*s - 36450*p**11*q**3*s**2 - 188100*p**8*q**5*s**2 - 5504750*p**5*q**7*s**2 - 37968750*p**2*q**9*s**2 + 255150*p**12*q*r*s**2 + 2754000*p**9*q**3*r*s**2 + 49196500*p**6*q**5*r*s**2 + 323587500*p**3*q**7*r*s**2 - 83250000*q**9*r*s**2 - 465750*p**10*q*r**2*s**2 - 31881500*p**7*q**3*r**2*s**2 - 415585000*p**4*q**5*r**2*s**2 + 1054775000*p*q**7*r**2*s**2 - 96823500*p**8*q*r**3*s**2 - 701490000*p**5*q**3*r**3*s**2 - 2953531250*p**2*q**5*r**3*s**2 + 1454560000*p**6*q*r**4*s**2 + 7670500000*p**3*q**3*r**4*s**2 + 5661062500*q**5*r**4*s**2 - 7785000000*p**4*q*r**5*s**2 - 9450000000*p*q**3*r**5*s**2 + 14000000000*p**2*q*r**6*s**2 + 2400000000*q*r**7*s**2 - 437400*p**13*s**3 - 10145250*p**10*q**2*s**3 - 121912500*p**7*q**4*s**3 - 576531250*p**4*q**6*s**3 - 528593750*p*q**8*s**3 + 12939750*p**11*r*s**3 + 313368750*p**8*q**2*r*s**3 + 2171812500*p**5*q**4*r*s**3 + 2381718750*p**2*q**6*r*s**3 - 124638750*p**9*r**2*s**3 - 3001575000*p**6*q**2*r**2*s**3 - 12259375000*p**3*q**4*r**2*s**3 - 9985312500*q**6*r**2*s**3 + 384000000*p**7*r**3*s**3 + 13997500000*p**4*q**2*r**3*s**3 + 20749531250*p*q**4*r**3*s**3 - 553500000*p**5*r**4*s**3 - 41835000000*p**2*q**2*r**4*s**3 + 5420000000*p**3*r**5*s**3 - 16300000000*q**2*r**5*s**3 - 17600000000*p*r**6*s**3 - 7593750*p**9*q*s**4 + 289218750*p**6*q**3*s**4 + 3591406250*p**3*q**5*s**4 + 5992187500*q**7*s**4 + 658125000*p**7*q*r*s**4 - 269531250*p**4*q**3*r*s**4 - 15882812500*p*q**5*r*s**4 - 4785000000*p**5*q*r**2*s**4 + 54375781250*p**2*q**3*r**2*s**4 - 5668750000*p**3*q*r**3*s**4 + 35867187500*q**3*r**3*s**4 + 113875000000*p*q*r**4*s**4 - 544218750*p**8*s**5 - 5407031250*p**5*q**2*s**5 - 14277343750*p**2*q**4*s**5 + 5421093750*p**6*r*s**5 - 24941406250*p**3*q**2*r*s**5 - 25488281250*q**4*r*s**5 - 11500000000*p**4*r**2*s**5 - 231894531250*p*q**2*r**2*s**5 - 6250000000*p**2*r**3*s**5 - 43750000000*r**4*s**5 + 35449218750*p**4*q*s**6 + 137695312500*p*q**3*s**6 + 34667968750*p**2*q*r*s**6 + 202148437500*q*r**2*s**6 - 33691406250*p**3*s**7 - 214843750000*q**2*s**7 - 31738281250*p*r*s**7
+
+ b[2][3] = -800*p**5*q**9 - 5400*p**2*q**11 + 5800*p**6*q**7*r + 48750*p**3*q**9*r + 16200*q**11*r - 3000*p**7*q**5*r**2 - 108350*p**4*q**7*r**2 - 263250*p*q**9*r**2 - 60700*p**8*q**3*r**3 - 386250*p**5*q**5*r**3 + 253100*p**2*q**7*r**3 + 127800*p**9*q*r**4 + 2326700*p**6*q**3*r**4 + 6565550*p**3*q**5*r**4 - 705750*q**7*r**4 - 2903200*p**7*q*r**5 - 21218000*p**4*q**3*r**5 + 1057000*p*q**5*r**5 + 20368000*p**5*q*r**6 + 33000000*p**2*q**3*r**6 - 43200000*p**3*q*r**7 + 52000000*q**3*r**7 + 6200*p**7*q**6*s + 188250*p**4*q**8*s + 931500*p*q**10*s - 73800*p**8*q**4*r*s - 1466850*p**5*q**6*r*s - 6894000*p**2*q**8*r*s + 315900*p**9*q**2*r**2*s + 4547000*p**6*q**4*r**2*s + 20362500*p**3*q**6*r**2*s + 15018750*q**8*r**2*s - 653400*p**10*r**3*s - 13897550*p**7*q**2*r**3*s - 76757500*p**4*q**4*r**3*s - 124207500*p*q**6*r**3*s + 18567600*p**8*r**4*s + 175911000*p**5*q**2*r**4*s + 253787500*p**2*q**4*r**4*s - 183816000*p**6*r**5*s - 706900000*p**3*q**2*r**5*s - 665750000*q**4*r**5*s + 740000000*p**4*r**6*s + 890000000*p*q**2*r**6*s - 1040000000*p**2*r**7*s - 763000*p**6*q**5*s**2 - 12375000*p**3*q**7*s**2 - 40500000*q**9*s**2 + 364500*p**10*q*r*s**2 + 15537000*p**7*q**3*r*s**2 + 154392500*p**4*q**5*r*s**2 + 372206250*p*q**7*r*s**2 - 25481250*p**8*q*r**2*s**2 - 386300000*p**5*q**3*r**2*s**2 - 996343750*p**2*q**5*r**2*s**2 + 459872500*p**6*q*r**3*s**2 + 2943937500*p**3*q**3*r**3*s**2 + 2437781250*q**5*r**3*s**2 - 2883750000*p**4*q*r**4*s**2 - 4343750000*p*q**3*r**4*s**2 + 5495000000*p**2*q*r**5*s**2 + 1300000000*q*r**6*s**2 - 364500*p**11*s**3 - 13668750*p**8*q**2*s**3 - 113406250*p**5*q**4*s**3 - 159062500*p**2*q**6*s**3 + 13972500*p**9*r*s**3 + 61537500*p**6*q**2*r*s**3 - 1622656250*p**3*q**4*r*s**3 - 2720625000*q**6*r*s**3 - 201656250*p**7*r**2*s**3 + 1949687500*p**4*q**2*r**2*s**3 + 4979687500*p*q**4*r**2*s**3 + 497125000*p**5*r**3*s**3 - 11150625000*p**2*q**2*r**3*s**3 + 2982500000*p**3*r**4*s**3 - 6612500000*q**2*r**4*s**3 - 10450000000*p*r**5*s**3 + 126562500*p**7*q*s**4 + 1443750000*p**4*q**3*s**4 + 281250000*p*q**5*s**4 - 1648125000*p**5*q*r*s**4 + 11271093750*p**2*q**3*r*s**4 - 4785156250*p**3*q*r**2*s**4 + 8808593750*q**3*r**2*s**4 + 52390625000*p*q*r**3*s**4 - 611718750*p**6*s**5 - 13027343750*p**3*q**2*s**5 - 1464843750*q**4*s**5 + 6492187500*p**4*r*s**5 - 65351562500*p*q**2*r*s**5 - 13476562500*p**2*r**2*s**5 - 24218750000*r**3*s**5 + 41992187500*p**2*q*s**6 + 69824218750*q*r*s**6 - 34179687500*p*s**7
+
+ b[2][2] = -1000*p**6*q**7 - 5150*p**3*q**9 + 10800*q**11 + 11000*p**7*q**5*r + 66450*p**4*q**7*r - 127800*p*q**9*r - 41250*p**8*q**3*r**2 - 368400*p**5*q**5*r**2 + 204200*p**2*q**7*r**2 + 54000*p**9*q*r**3 + 1040950*p**6*q**3*r**3 + 2096500*p**3*q**5*r**3 + 200000*q**7*r**3 - 1140000*p**7*q*r**4 - 7691000*p**4*q**3*r**4 - 2281000*p*q**5*r**4 + 7296000*p**5*q*r**5 + 13300000*p**2*q**3*r**5 - 14400000*p**3*q*r**6 + 14000000*q**3*r**6 - 9000*p**8*q**4*s + 52100*p**5*q**6*s + 710250*p**2*q**8*s + 67500*p**9*q**2*r*s - 256100*p**6*q**4*r*s - 5753000*p**3*q**6*r*s + 292500*q**8*r*s - 162000*p**10*r**2*s - 1432350*p**7*q**2*r**2*s + 5410000*p**4*q**4*r**2*s - 7408750*p*q**6*r**2*s + 4401000*p**8*r**3*s + 24185000*p**5*q**2*r**3*s + 20781250*p**2*q**4*r**3*s - 43012000*p**6*r**4*s - 146300000*p**3*q**2*r**4*s - 165875000*q**4*r**4*s + 182000000*p**4*r**5*s + 250000000*p*q**2*r**5*s - 280000000*p**2*r**6*s + 60750*p**10*q*s**2 + 2414250*p**7*q**3*s**2 + 15770000*p**4*q**5*s**2 + 15825000*p*q**7*s**2 - 6021000*p**8*q*r*s**2 - 62252500*p**5*q**3*r*s**2 - 74718750*p**2*q**5*r*s**2 + 90888750*p**6*q*r**2*s**2 + 471312500*p**3*q**3*r**2*s**2 + 525875000*q**5*r**2*s**2 - 539375000*p**4*q*r**3*s**2 - 1030000000*p*q**3*r**3*s**2 + 1142500000*p**2*q*r**4*s**2 + 350000000*q*r**5*s**2 - 303750*p**9*s**3 - 35943750*p**6*q**2*s**3 - 331875000*p**3*q**4*s**3 - 505937500*q**6*s**3 + 8437500*p**7*r*s**3 + 530781250*p**4*q**2*r*s**3 + 1150312500*p*q**4*r*s**3 - 154500000*p**5*r**2*s**3 - 2059062500*p**2*q**2*r**2*s**3 + 1150000000*p**3*r**3*s**3 - 1343750000*q**2*r**3*s**3 - 2900000000*p*r**4*s**3 + 30937500*p**5*q*s**4 + 1166406250*p**2*q**3*s**4 - 1496875000*p**3*q*r*s**4 + 1296875000*q**3*r*s**4 + 10640625000*p*q*r**2*s**4 - 281250000*p**4*s**5 - 9746093750*p*q**2*s**5 + 1269531250*p**2*r*s**5 - 7421875000*r**2*s**5 + 15625000000*q*s**6
+
+ b[2][1] = -1600*p**4*q**7 - 10800*p*q**9 + 9800*p**5*q**5*r + 80550*p**2*q**7*r - 4600*p**6*q**3*r**2 - 112700*p**3*q**5*r**2 + 40500*q**7*r**2 - 34200*p**7*q*r**3 - 279500*p**4*q**3*r**3 - 665750*p*q**5*r**3 + 632000*p**5*q*r**4 + 3200000*p**2*q**3*r**4 - 2800000*p**3*q*r**5 + 3000000*q**3*r**5 - 18600*p**6*q**4*s - 51750*p**3*q**6*s + 405000*q**8*s + 21600*p**7*q**2*r*s - 122500*p**4*q**4*r*s - 2891250*p*q**6*r*s + 156600*p**8*r**2*s + 1569750*p**5*q**2*r**2*s + 6943750*p**2*q**4*r**2*s - 3774000*p**6*r**3*s - 27100000*p**3*q**2*r**3*s - 30187500*q**4*r**3*s + 28000000*p**4*r**4*s + 52500000*p*q**2*r**4*s - 60000000*p**2*r**5*s - 81000*p**8*q*s**2 - 240000*p**5*q**3*s**2 + 937500*p**2*q**5*s**2 + 3273750*p**6*q*r*s**2 + 30406250*p**3*q**3*r*s**2 + 55687500*q**5*r*s**2 - 42187500*p**4*q*r**2*s**2 - 112812500*p*q**3*r**2*s**2 + 152500000*p**2*q*r**3*s**2 + 75000000*q*r**4*s**2 - 4218750*p**4*q**2*s**3 + 15156250*p*q**4*s**3 + 5906250*p**5*r*s**3 - 206562500*p**2*q**2*r*s**3 + 107500000*p**3*r**2*s**3 - 159375000*q**2*r**2*s**3 - 612500000*p*r**3*s**3 + 135937500*p**3*q*s**4 + 46875000*q**3*s**4 + 1175781250*p*q*r*s**4 - 292968750*p**2*s**5 - 1367187500*r*s**5
+
+ b[2][0] = -800*p**5*q**5 - 5400*p**2*q**7 + 6000*p**6*q**3*r + 51700*p**3*q**5*r + 27000*q**7*r - 10800*p**7*q*r**2 - 163250*p**4*q**3*r**2 - 285750*p*q**5*r**2 + 192000*p**5*q*r**3 + 1000000*p**2*q**3*r**3 - 800000*p**3*q*r**4 + 500000*q**3*r**4 - 10800*p**7*q**2*s - 57500*p**4*q**4*s + 67500*p*q**6*s + 32400*p**8*r*s + 279000*p**5*q**2*r*s - 131250*p**2*q**4*r*s - 729000*p**6*r**2*s - 4100000*p**3*q**2*r**2*s - 5343750*q**4*r**2*s + 5000000*p**4*r**3*s + 10000000*p*q**2*r**3*s - 10000000*p**2*r**4*s + 641250*p**6*q*s**2 + 5812500*p**3*q**3*s**2 + 10125000*q**5*s**2 - 7031250*p**4*q*r*s**2 - 20625000*p*q**3*r*s**2 + 17500000*p**2*q*r**2*s**2 + 12500000*q*r**3*s**2 - 843750*p**5*s**3 - 19375000*p**2*q**2*s**3 + 30000000*p**3*r*s**3 - 20312500*q**2*r*s**3 - 112500000*p*r**2*s**3 + 183593750*p*q*s**4 - 292968750*s**5
+
+ b[3][5] = 500*p**11*q**6 + 9875*p**8*q**8 + 42625*p**5*q**10 - 35000*p**2*q**12 - 4500*p**12*q**4*r - 108375*p**9*q**6*r - 516750*p**6*q**8*r + 1110500*p**3*q**10*r + 2730000*q**12*r + 10125*p**13*q**2*r**2 + 358250*p**10*q**4*r**2 + 1908625*p**7*q**6*r**2 - 11744250*p**4*q**8*r**2 - 43383250*p*q**10*r**2 - 313875*p**11*q**2*r**3 - 2074875*p**8*q**4*r**3 + 52094750*p**5*q**6*r**3 + 264567500*p**2*q**8*r**3 + 796125*p**9*q**2*r**4 - 92486250*p**6*q**4*r**4 - 757957500*p**3*q**6*r**4 - 29354375*q**8*r**4 + 60970000*p**7*q**2*r**5 + 1112462500*p**4*q**4*r**5 + 571094375*p*q**6*r**5 - 685290000*p**5*q**2*r**6 - 2037800000*p**2*q**4*r**6 + 2279600000*p**3*q**2*r**7 + 849000000*q**4*r**7 - 1480000000*p*q**2*r**8 + 13500*p**13*q**3*s + 363000*p**10*q**5*s + 2861250*p**7*q**7*s + 8493750*p**4*q**9*s + 17031250*p*q**11*s - 60750*p**14*q*r*s - 2319750*p**11*q**3*r*s - 22674250*p**8*q**5*r*s - 74368750*p**5*q**7*r*s - 170578125*p**2*q**9*r*s + 2760750*p**12*q*r**2*s + 46719000*p**9*q**3*r**2*s + 163356375*p**6*q**5*r**2*s + 360295625*p**3*q**7*r**2*s - 195990625*q**9*r**2*s - 37341750*p**10*q*r**3*s - 194739375*p**7*q**3*r**3*s - 105463125*p**4*q**5*r**3*s - 415825000*p*q**7*r**3*s + 90180000*p**8*q*r**4*s - 990552500*p**5*q**3*r**4*s + 3519212500*p**2*q**5*r**4*s + 1112220000*p**6*q*r**5*s - 4508750000*p**3*q**3*r**5*s - 8159500000*q**5*r**5*s - 4356000000*p**4*q*r**6*s + 14615000000*p*q**3*r**6*s - 2160000000*p**2*q*r**7*s + 91125*p**15*s**2 + 3290625*p**12*q**2*s**2 + 35100000*p**9*q**4*s**2 + 175406250*p**6*q**6*s**2 + 629062500*p**3*q**8*s**2 + 910937500*q**10*s**2 - 5710500*p**13*r*s**2 - 100423125*p**10*q**2*r*s**2 - 604743750*p**7*q**4*r*s**2 - 2954843750*p**4*q**6*r*s**2 - 4587578125*p*q**8*r*s**2 + 116194500*p**11*r**2*s**2 + 1280716250*p**8*q**2*r**2*s**2 + 7401190625*p**5*q**4*r**2*s**2 + 11619937500*p**2*q**6*r**2*s**2 - 952173125*p**9*r**3*s**2 - 6519712500*p**6*q**2*r**3*s**2 - 10238593750*p**3*q**4*r**3*s**2 + 29984609375*q**6*r**3*s**2 + 2558300000*p**7*r**4*s**2 + 16225000000*p**4*q**2*r**4*s**2 - 64994140625*p*q**4*r**4*s**2 + 4202250000*p**5*r**5*s**2 + 46925000000*p**2*q**2*r**5*s**2 - 28950000000*p**3*r**6*s**2 - 1000000000*q**2*r**6*s**2 + 37000000000*p*r**7*s**2 - 48093750*p**11*q*s**3 - 673359375*p**8*q**3*s**3 - 2170312500*p**5*q**5*s**3 - 2466796875*p**2*q**7*s**3 + 647578125*p**9*q*r*s**3 + 597031250*p**6*q**3*r*s**3 - 7542578125*p**3*q**5*r*s**3 - 41125000000*q**7*r*s**3 - 2175828125*p**7*q*r**2*s**3 - 7101562500*p**4*q**3*r**2*s**3 + 100596875000*p*q**5*r**2*s**3 - 8984687500*p**5*q*r**3*s**3 - 120070312500*p**2*q**3*r**3*s**3 + 57343750000*p**3*q*r**4*s**3 + 9500000000*q**3*r**4*s**3 - 342875000000*p*q*r**5*s**3 + 400781250*p**10*s**4 + 8531250000*p**7*q**2*s**4 + 34033203125*p**4*q**4*s**4 + 42724609375*p*q**6*s**4 - 6289453125*p**8*r*s**4 - 24037109375*p**5*q**2*r*s**4 - 62626953125*p**2*q**4*r*s**4 + 17299218750*p**6*r**2*s**4 + 108357421875*p**3*q**2*r**2*s**4 - 55380859375*q**4*r**2*s**4 + 105648437500*p**4*r**3*s**4 + 1204228515625*p*q**2*r**3*s**4 - 365000000000*p**2*r**4*s**4 + 184375000000*r**5*s**4 - 32080078125*p**6*q*s**5 - 98144531250*p**3*q**3*s**5 + 93994140625*q**5*s**5 - 178955078125*p**4*q*r*s**5 - 1299804687500*p*q**3*r*s**5 + 332421875000*p**2*q*r**2*s**5 - 1195312500000*q*r**3*s**5 + 72021484375*p**5*s**6 + 323486328125*p**2*q**2*s**6 + 682373046875*p**3*r*s**6 + 2447509765625*q**2*r*s**6 - 3011474609375*p*r**2*s**6 + 3051757812500*p*q*s**7 - 7629394531250*s**8
+
+ b[3][4] = 1500*p**9*q**6 + 69625*p**6*q**8 + 590375*p**3*q**10 + 1035000*q**12 - 13500*p**10*q**4*r - 760625*p**7*q**6*r - 7904500*p**4*q**8*r - 18169250*p*q**10*r + 30375*p**11*q**2*r**2 + 2628625*p**8*q**4*r**2 + 37879000*p**5*q**6*r**2 + 121367500*p**2*q**8*r**2 - 2699250*p**9*q**2*r**3 - 76776875*p**6*q**4*r**3 - 403583125*p**3*q**6*r**3 - 78865625*q**8*r**3 + 60907500*p**7*q**2*r**4 + 735291250*p**4*q**4*r**4 + 781142500*p*q**6*r**4 - 558270000*p**5*q**2*r**5 - 2150725000*p**2*q**4*r**5 + 2015400000*p**3*q**2*r**6 + 1181000000*q**4*r**6 - 2220000000*p*q**2*r**7 + 40500*p**11*q**3*s + 1376500*p**8*q**5*s + 9953125*p**5*q**7*s + 9765625*p**2*q**9*s - 182250*p**12*q*r*s - 8859000*p**9*q**3*r*s - 82854500*p**6*q**5*r*s - 71511250*p**3*q**7*r*s + 273631250*q**9*r*s + 10233000*p**10*q*r**2*s + 179627500*p**7*q**3*r**2*s + 25164375*p**4*q**5*r**2*s - 2927290625*p*q**7*r**2*s - 171305000*p**8*q*r**3*s - 544768750*p**5*q**3*r**3*s + 7583437500*p**2*q**5*r**3*s + 1139860000*p**6*q*r**4*s - 6489375000*p**3*q**3*r**4*s - 9625375000*q**5*r**4*s - 1838000000*p**4*q*r**5*s + 19835000000*p*q**3*r**5*s - 3240000000*p**2*q*r**6*s + 273375*p**13*s**2 + 9753750*p**10*q**2*s**2 + 82575000*p**7*q**4*s**2 + 202265625*p**4*q**6*s**2 + 556093750*p*q**8*s**2 - 11552625*p**11*r*s**2 - 115813125*p**8*q**2*r*s**2 + 630590625*p**5*q**4*r*s**2 + 1347015625*p**2*q**6*r*s**2 + 157578750*p**9*r**2*s**2 - 689206250*p**6*q**2*r**2*s**2 - 4299609375*p**3*q**4*r**2*s**2 + 23896171875*q**6*r**2*s**2 - 1022437500*p**7*r**3*s**2 + 6648125000*p**4*q**2*r**3*s**2 - 52895312500*p*q**4*r**3*s**2 + 4401750000*p**5*r**4*s**2 + 26500000000*p**2*q**2*r**4*s**2 - 22125000000*p**3*r**5*s**2 - 1500000000*q**2*r**5*s**2 + 55500000000*p*r**6*s**2 - 137109375*p**9*q*s**3 - 1955937500*p**6*q**3*s**3 - 6790234375*p**3*q**5*s**3 - 16996093750*q**7*s**3 + 2146218750*p**7*q*r*s**3 + 6570312500*p**4*q**3*r*s**3 + 39918750000*p*q**5*r*s**3 - 7673281250*p**5*q*r**2*s**3 - 52000000000*p**2*q**3*r**2*s**3 + 50796875000*p**3*q*r**3*s**3 + 18750000000*q**3*r**3*s**3 - 399875000000*p*q*r**4*s**3 + 780468750*p**8*s**4 + 14455078125*p**5*q**2*s**4 + 10048828125*p**2*q**4*s**4 - 15113671875*p**6*r*s**4 + 39298828125*p**3*q**2*r*s**4 - 52138671875*q**4*r*s**4 + 45964843750*p**4*r**2*s**4 + 914414062500*p*q**2*r**2*s**4 + 1953125000*p**2*r**3*s**4 + 334375000000*r**4*s**4 - 149169921875*p**4*q*s**5 - 459716796875*p*q**3*s**5 - 325585937500*p**2*q*r*s**5 - 1462890625000*q*r**2*s**5 + 296630859375*p**3*s**6 + 1324462890625*q**2*s**6 + 307617187500*p*r*s**6
+
+ b[3][3] = -20750*p**7*q**6 - 290125*p**4*q**8 - 993000*p*q**10 + 146125*p**8*q**4*r + 2721500*p**5*q**6*r + 11833750*p**2*q**8*r - 237375*p**9*q**2*r**2 - 8167500*p**6*q**4*r**2 - 54605625*p**3*q**6*r**2 - 23802500*q**8*r**2 + 8927500*p**7*q**2*r**3 + 131184375*p**4*q**4*r**3 + 254695000*p*q**6*r**3 - 121561250*p**5*q**2*r**4 - 728003125*p**2*q**4*r**4 + 702550000*p**3*q**2*r**5 + 597312500*q**4*r**5 - 1202500000*p*q**2*r**6 - 194625*p**9*q**3*s - 1568875*p**6*q**5*s + 9685625*p**3*q**7*s + 74662500*q**9*s + 327375*p**10*q*r*s + 1280000*p**7*q**3*r*s - 123703750*p**4*q**5*r*s - 850121875*p*q**7*r*s - 7436250*p**8*q*r**2*s + 164820000*p**5*q**3*r**2*s + 2336659375*p**2*q**5*r**2*s + 32202500*p**6*q*r**3*s - 2429765625*p**3*q**3*r**3*s - 4318609375*q**5*r**3*s + 148000000*p**4*q*r**4*s + 9902812500*p*q**3*r**4*s - 1755000000*p**2*q*r**5*s + 1154250*p**11*s**2 + 36821250*p**8*q**2*s**2 + 372825000*p**5*q**4*s**2 + 1170921875*p**2*q**6*s**2 - 38913750*p**9*r*s**2 - 797071875*p**6*q**2*r*s**2 - 2848984375*p**3*q**4*r*s**2 + 7651406250*q**6*r*s**2 + 415068750*p**7*r**2*s**2 + 3151328125*p**4*q**2*r**2*s**2 - 17696875000*p*q**4*r**2*s**2 - 725968750*p**5*r**3*s**2 + 5295312500*p**2*q**2*r**3*s**2 - 8581250000*p**3*r**4*s**2 - 812500000*q**2*r**4*s**2 + 30062500000*p*r**5*s**2 - 110109375*p**7*q*s**3 - 1976562500*p**4*q**3*s**3 - 6329296875*p*q**5*s**3 + 2256328125*p**5*q*r*s**3 + 8554687500*p**2*q**3*r*s**3 + 12947265625*p**3*q*r**2*s**3 + 7984375000*q**3*r**2*s**3 - 167039062500*p*q*r**3*s**3 + 1181250000*p**6*s**4 + 17873046875*p**3*q**2*s**4 - 20449218750*q**4*s**4 - 16265625000*p**4*r*s**4 + 260869140625*p*q**2*r*s**4 + 21025390625*p**2*r**2*s**4 + 207617187500*r**3*s**4 - 207177734375*p**2*q*s**5 - 615478515625*q*r*s**5 + 301513671875*p*s**6
+
+ b[3][2] = 53125*p**5*q**6 + 425000*p**2*q**8 - 394375*p**6*q**4*r - 4301875*p**3*q**6*r - 3225000*q**8*r + 851250*p**7*q**2*r**2 + 16910625*p**4*q**4*r**2 + 44210000*p*q**6*r**2 - 20474375*p**5*q**2*r**3 - 147190625*p**2*q**4*r**3 + 163975000*p**3*q**2*r**4 + 156812500*q**4*r**4 - 323750000*p*q**2*r**5 - 99375*p**7*q**3*s - 6395000*p**4*q**5*s - 49243750*p*q**7*s - 1164375*p**8*q*r*s + 4465625*p**5*q**3*r*s + 205546875*p**2*q**5*r*s + 12163750*p**6*q*r**2*s - 315546875*p**3*q**3*r**2*s - 946453125*q**5*r**2*s - 23500000*p**4*q*r**3*s + 2313437500*p*q**3*r**3*s - 472500000*p**2*q*r**4*s + 1316250*p**9*s**2 + 22715625*p**6*q**2*s**2 + 206953125*p**3*q**4*s**2 + 1220000000*q**6*s**2 - 20953125*p**7*r*s**2 - 277656250*p**4*q**2*r*s**2 - 3317187500*p*q**4*r*s**2 + 293734375*p**5*r**2*s**2 + 1351562500*p**2*q**2*r**2*s**2 - 2278125000*p**3*r**3*s**2 - 218750000*q**2*r**3*s**2 + 8093750000*p*r**4*s**2 - 9609375*p**5*q*s**3 + 240234375*p**2*q**3*s**3 + 2310546875*p**3*q*r*s**3 + 1171875000*q**3*r*s**3 - 33460937500*p*q*r**2*s**3 + 2185546875*p**4*s**4 + 32578125000*p*q**2*s**4 - 8544921875*p**2*r*s**4 + 58398437500*r**2*s**4 - 114013671875*q*s**5
+
+ b[3][1] = -16250*p**6*q**4 - 191875*p**3*q**6 - 495000*q**8 + 73125*p**7*q**2*r + 1437500*p**4*q**4*r + 5866250*p*q**6*r - 2043125*p**5*q**2*r**2 - 17218750*p**2*q**4*r**2 + 19106250*p**3*q**2*r**3 + 34015625*q**4*r**3 - 69375000*p*q**2*r**4 - 219375*p**8*q*s - 2846250*p**5*q**3*s - 8021875*p**2*q**5*s + 3420000*p**6*q*r*s - 1640625*p**3*q**3*r*s - 152468750*q**5*r*s + 3062500*p**4*q*r**2*s + 381171875*p*q**3*r**2*s - 101250000*p**2*q*r**3*s + 2784375*p**7*s**2 + 43515625*p**4*q**2*s**2 + 115625000*p*q**4*s**2 - 48140625*p**5*r*s**2 - 307421875*p**2*q**2*r*s**2 - 25781250*p**3*r**2*s**2 - 46875000*q**2*r**2*s**2 + 1734375000*p*r**3*s**2 - 128906250*p**3*q*s**3 + 339843750*q**3*s**3 - 4583984375*p*q*r*s**3 + 2236328125*p**2*s**4 + 12255859375*r*s**4
+
+ b[3][0] = 31875*p**4*q**4 + 255000*p*q**6 - 82500*p**5*q**2*r - 1106250*p**2*q**4*r + 1653125*p**3*q**2*r**2 + 5187500*q**4*r**2 - 11562500*p*q**2*r**3 - 118125*p**6*q*s - 3593750*p**3*q**3*s - 23812500*q**5*s + 4656250*p**4*q*r*s + 67109375*p*q**3*r*s - 16875000*p**2*q*r**2*s - 984375*p**5*s**2 - 19531250*p**2*q**2*s**2 - 37890625*p**3*r*s**2 - 7812500*q**2*r*s**2 + 289062500*p*r**2*s**2 - 529296875*p*q*s**3 + 2343750000*s**4
+
+ b[4][5] = 600*p**10*q**10 + 13850*p**7*q**12 + 106150*p**4*q**14 + 270000*p*q**16 - 9300*p**11*q**8*r - 234075*p**8*q**10*r - 1942825*p**5*q**12*r - 5319900*p**2*q**14*r + 52050*p**12*q**6*r**2 + 1481025*p**9*q**8*r**2 + 13594450*p**6*q**10*r**2 + 40062750*p**3*q**12*r**2 - 3569400*q**14*r**2 - 122175*p**13*q**4*r**3 - 4260350*p**10*q**6*r**3 - 45052375*p**7*q**8*r**3 - 142634900*p**4*q**10*r**3 + 54186350*p*q**12*r**3 + 97200*p**14*q**2*r**4 + 5284225*p**11*q**4*r**4 + 70389525*p**8*q**6*r**4 + 232732850*p**5*q**8*r**4 - 318849400*p**2*q**10*r**4 - 2046000*p**12*q**2*r**5 - 43874125*p**9*q**4*r**5 - 107411850*p**6*q**6*r**5 + 948310700*p**3*q**8*r**5 - 34763575*q**10*r**5 + 5915600*p**10*q**2*r**6 - 115887800*p**7*q**4*r**6 - 1649542400*p**4*q**6*r**6 + 224468875*p*q**8*r**6 + 120252800*p**8*q**2*r**7 + 1779902000*p**5*q**4*r**7 - 288250000*p**2*q**6*r**7 - 915200000*p**6*q**2*r**8 - 1164000000*p**3*q**4*r**8 - 444200000*q**6*r**8 + 2502400000*p**4*q**2*r**9 + 1984000000*p*q**4*r**9 - 2880000000*p**2*q**2*r**10 + 20700*p**12*q**7*s + 551475*p**9*q**9*s + 5194875*p**6*q**11*s + 18985000*p**3*q**13*s + 16875000*q**15*s - 218700*p**13*q**5*r*s - 6606475*p**10*q**7*r*s - 69770850*p**7*q**9*r*s - 285325500*p**4*q**11*r*s - 292005000*p*q**13*r*s + 694575*p**14*q**3*r**2*s + 26187750*p**11*q**5*r**2*s + 328992825*p**8*q**7*r**2*s + 1573292400*p**5*q**9*r**2*s + 1930043875*p**2*q**11*r**2*s - 583200*p**15*q*r**3*s - 37263225*p**12*q**3*r**3*s - 638579425*p**9*q**5*r**3*s - 3920212225*p**6*q**7*r**3*s - 6327336875*p**3*q**9*r**3*s + 440969375*q**11*r**3*s + 13446000*p**13*q*r**4*s + 462330325*p**10*q**3*r**4*s + 4509088275*p**7*q**5*r**4*s + 11709795625*p**4*q**7*r**4*s - 3579565625*p*q**9*r**4*s - 85033600*p**11*q*r**5*s - 2136801600*p**8*q**3*r**5*s - 12221575800*p**5*q**5*r**5*s + 9431044375*p**2*q**7*r**5*s + 10643200*p**9*q*r**6*s + 4565594000*p**6*q**3*r**6*s - 1778590000*p**3*q**5*r**6*s + 4842175000*q**7*r**6*s + 712320000*p**7*q*r**7*s - 16182000000*p**4*q**3*r**7*s - 21918000000*p*q**5*r**7*s - 742400000*p**5*q*r**8*s + 31040000000*p**2*q**3*r**8*s + 1280000000*p**3*q*r**9*s + 4800000000*q**3*r**9*s + 230850*p**14*q**4*s**2 + 7373250*p**11*q**6*s**2 + 85045625*p**8*q**8*s**2 + 399140625*p**5*q**10*s**2 + 565031250*p**2*q**12*s**2 - 1257525*p**15*q**2*r*s**2 - 52728975*p**12*q**4*r*s**2 - 743466375*p**9*q**6*r*s**2 - 4144915000*p**6*q**8*r*s**2 - 7102690625*p**3*q**10*r*s**2 - 1389937500*q**12*r*s**2 + 874800*p**16*r**2*s**2 + 89851275*p**13*q**2*r**2*s**2 + 1897236775*p**10*q**4*r**2*s**2 + 14144163000*p**7*q**6*r**2*s**2 + 31942921875*p**4*q**8*r**2*s**2 + 13305118750*p*q**10*r**2*s**2 - 23004000*p**14*r**3*s**2 - 1450715475*p**11*q**2*r**3*s**2 - 19427105000*p**8*q**4*r**3*s**2 - 70634028750*p**5*q**6*r**3*s**2 - 47854218750*p**2*q**8*r**3*s**2 + 204710400*p**12*r**4*s**2 + 10875135000*p**9*q**2*r**4*s**2 + 83618806250*p**6*q**4*r**4*s**2 + 62744500000*p**3*q**6*r**4*s**2 - 19806718750*q**8*r**4*s**2 - 757094800*p**10*r**5*s**2 - 37718030000*p**7*q**2*r**5*s**2 - 22479500000*p**4*q**4*r**5*s**2 + 91556093750*p*q**6*r**5*s**2 + 2306320000*p**8*r**6*s**2 + 55539600000*p**5*q**2*r**6*s**2 - 112851250000*p**2*q**4*r**6*s**2 - 10720000000*p**6*r**7*s**2 - 64720000000*p**3*q**2*r**7*s**2 - 59925000000*q**4*r**7*s**2 + 28000000000*p**4*r**8*s**2 + 28000000000*p*q**2*r**8*s**2 - 24000000000*p**2*r**9*s**2 + 820125*p**16*q*s**3 + 36804375*p**13*q**3*s**3 + 552225000*p**10*q**5*s**3 + 3357593750*p**7*q**7*s**3 + 7146562500*p**4*q**9*s**3 + 3851562500*p*q**11*s**3 - 92400750*p**14*q*r*s**3 - 2350175625*p**11*q**3*r*s**3 - 19470640625*p**8*q**5*r*s**3 - 52820593750*p**5*q**7*r*s**3 - 45447734375*p**2*q**9*r*s**3 + 1824363000*p**12*q*r**2*s**3 + 31435234375*p**9*q**3*r**2*s**3 + 141717537500*p**6*q**5*r**2*s**3 + 228370781250*p**3*q**7*r**2*s**3 + 34610078125*q**9*r**2*s**3 - 17591825625*p**10*q*r**3*s**3 - 188927187500*p**7*q**3*r**3*s**3 - 502088984375*p**4*q**5*r**3*s**3 - 187849296875*p*q**7*r**3*s**3 + 75577750000*p**8*q*r**4*s**3 + 342800000000*p**5*q**3*r**4*s**3 + 295384296875*p**2*q**5*r**4*s**3 - 107681250000*p**6*q*r**5*s**3 + 53330000000*p**3*q**3*r**5*s**3 + 271586875000*q**5*r**5*s**3 - 26410000000*p**4*q*r**6*s**3 - 188200000000*p*q**3*r**6*s**3 + 92000000000*p**2*q*r**7*s**3 + 120000000000*q*r**8*s**3 + 47840625*p**15*s**4 + 1150453125*p**12*q**2*s**4 + 9229453125*p**9*q**4*s**4 + 24954687500*p**6*q**6*s**4 + 22978515625*p**3*q**8*s**4 + 1367187500*q**10*s**4 - 1193737500*p**13*r*s**4 - 20817843750*p**10*q**2*r*s**4 - 98640000000*p**7*q**4*r*s**4 - 225767187500*p**4*q**6*r*s**4 - 74707031250*p*q**8*r*s**4 + 13431318750*p**11*r**2*s**4 + 188709843750*p**8*q**2*r**2*s**4 + 875157656250*p**5*q**4*r**2*s**4 + 593812890625*p**2*q**6*r**2*s**4 - 69869296875*p**9*r**3*s**4 - 854811093750*p**6*q**2*r**3*s**4 - 1730658203125*p**3*q**4*r**3*s**4 - 570867187500*q**6*r**3*s**4 + 162075625000*p**7*r**4*s**4 + 1536375000000*p**4*q**2*r**4*s**4 + 765156250000*p*q**4*r**4*s**4 - 165988750000*p**5*r**5*s**4 - 728968750000*p**2*q**2*r**5*s**4 + 121500000000*p**3*r**6*s**4 - 1039375000000*q**2*r**6*s**4 - 100000000000*p*r**7*s**4 - 379687500*p**11*q*s**5 - 11607421875*p**8*q**3*s**5 - 20830078125*p**5*q**5*s**5 - 33691406250*p**2*q**7*s**5 - 41491406250*p**9*q*r*s**5 - 419054687500*p**6*q**3*r*s**5 - 129511718750*p**3*q**5*r*s**5 + 311767578125*q**7*r*s**5 + 620116015625*p**7*q*r**2*s**5 + 1154687500000*p**4*q**3*r**2*s**5 + 36455078125*p*q**5*r**2*s**5 - 2265953125000*p**5*q*r**3*s**5 - 1509521484375*p**2*q**3*r**3*s**5 + 2530468750000*p**3*q*r**4*s**5 + 3259765625000*q**3*r**4*s**5 + 93750000000*p*q*r**5*s**5 + 23730468750*p**10*s**6 + 243603515625*p**7*q**2*s**6 + 341552734375*p**4*q**4*s**6 - 12207031250*p*q**6*s**6 - 357099609375*p**8*r*s**6 - 298193359375*p**5*q**2*r*s**6 + 406738281250*p**2*q**4*r*s**6 + 1615683593750*p**6*r**2*s**6 + 558593750000*p**3*q**2*r**2*s**6 - 2811035156250*q**4*r**2*s**6 - 2960937500000*p**4*r**3*s**6 - 3802246093750*p*q**2*r**3*s**6 + 2347656250000*p**2*r**4*s**6 - 671875000000*r**5*s**6 - 651855468750*p**6*q*s**7 - 1458740234375*p**3*q**3*s**7 - 152587890625*q**5*s**7 + 1628417968750*p**4*q*r*s**7 + 3948974609375*p*q**3*r*s**7 - 916748046875*p**2*q*r**2*s**7 + 1611328125000*q*r**3*s**7 + 640869140625*p**5*s**8 + 1068115234375*p**2*q**2*s**8 - 2044677734375*p**3*r*s**8 - 3204345703125*q**2*r*s**8 + 1739501953125*p*r**2*s**8
+
+ b[4][4] = -600*p**11*q**8 - 14050*p**8*q**10 - 109100*p**5*q**12 - 280800*p**2*q**14 + 7200*p**12*q**6*r + 188700*p**9*q**8*r + 1621725*p**6*q**10*r + 4577075*p**3*q**12*r + 5400*q**14*r - 28350*p**13*q**4*r**2 - 910600*p**10*q**6*r**2 - 9237975*p**7*q**8*r**2 - 30718900*p**4*q**10*r**2 - 5575950*p*q**12*r**2 + 36450*p**14*q**2*r**3 + 1848125*p**11*q**4*r**3 + 25137775*p**8*q**6*r**3 + 109591450*p**5*q**8*r**3 + 70627650*p**2*q**10*r**3 - 1317150*p**12*q**2*r**4 - 32857100*p**9*q**4*r**4 - 219125575*p**6*q**6*r**4 - 327565875*p**3*q**8*r**4 - 13011875*q**10*r**4 + 16484150*p**10*q**2*r**5 + 222242250*p**7*q**4*r**5 + 642173750*p**4*q**6*r**5 + 101263750*p*q**8*r**5 - 79345000*p**8*q**2*r**6 - 433180000*p**5*q**4*r**6 - 93731250*p**2*q**6*r**6 - 74300000*p**6*q**2*r**7 - 1057900000*p**3*q**4*r**7 - 591175000*q**6*r**7 + 1891600000*p**4*q**2*r**8 + 2796000000*p*q**4*r**8 - 4320000000*p**2*q**2*r**9 - 16200*p**13*q**5*s - 359500*p**10*q**7*s - 2603825*p**7*q**9*s - 4590375*p**4*q**11*s + 12352500*p*q**13*s + 121500*p**14*q**3*r*s + 3227400*p**11*q**5*r*s + 27301725*p**8*q**7*r*s + 59480975*p**5*q**9*r*s - 137308875*p**2*q**11*r*s - 218700*p**15*q*r**2*s - 8903925*p**12*q**3*r**2*s - 100918225*p**9*q**5*r**2*s - 325291300*p**6*q**7*r**2*s + 365705000*p**3*q**9*r**2*s + 94342500*q**11*r**2*s + 7632900*p**13*q*r**3*s + 162995400*p**10*q**3*r**3*s + 974558975*p**7*q**5*r**3*s + 930991250*p**4*q**7*r**3*s - 495368750*p*q**9*r**3*s - 97344900*p**11*q*r**4*s - 1406739250*p**8*q**3*r**4*s - 5572526250*p**5*q**5*r**4*s - 1903987500*p**2*q**7*r**4*s + 678550000*p**9*q*r**5*s + 8176215000*p**6*q**3*r**5*s + 18082050000*p**3*q**5*r**5*s + 5435843750*q**7*r**5*s - 2979800000*p**7*q*r**6*s - 29163500000*p**4*q**3*r**6*s - 27417500000*p*q**5*r**6*s + 6282400000*p**5*q*r**7*s + 48690000000*p**2*q**3*r**7*s - 2880000000*p**3*q*r**8*s + 7200000000*q**3*r**8*s - 109350*p**15*q**2*s**2 - 2405700*p**12*q**4*s**2 - 16125250*p**9*q**6*s**2 - 4930000*p**6*q**8*s**2 + 201150000*p**3*q**10*s**2 - 243000000*q**12*s**2 + 328050*p**16*r*s**2 + 10552275*p**13*q**2*r*s**2 + 88019100*p**10*q**4*r*s**2 - 4208625*p**7*q**6*r*s**2 - 1920390625*p**4*q**8*r*s**2 + 1759537500*p*q**10*r*s**2 - 11955600*p**14*r**2*s**2 - 196375050*p**11*q**2*r**2*s**2 - 555196250*p**8*q**4*r**2*s**2 + 4213270000*p**5*q**6*r**2*s**2 - 157468750*p**2*q**8*r**2*s**2 + 162656100*p**12*r**3*s**2 + 1880870000*p**9*q**2*r**3*s**2 + 753684375*p**6*q**4*r**3*s**2 - 25423062500*p**3*q**6*r**3*s**2 - 14142031250*q**8*r**3*s**2 - 1251948750*p**10*r**4*s**2 - 12524475000*p**7*q**2*r**4*s**2 + 18067656250*p**4*q**4*r**4*s**2 + 60531875000*p*q**6*r**4*s**2 + 6827725000*p**8*r**5*s**2 + 57157000000*p**5*q**2*r**5*s**2 - 75844531250*p**2*q**4*r**5*s**2 - 24452500000*p**6*r**6*s**2 - 144950000000*p**3*q**2*r**6*s**2 - 82109375000*q**4*r**6*s**2 + 46950000000*p**4*r**7*s**2 + 60000000000*p*q**2*r**7*s**2 - 36000000000*p**2*r**8*s**2 + 1549125*p**14*q*s**3 + 51873750*p**11*q**3*s**3 + 599781250*p**8*q**5*s**3 + 2421156250*p**5*q**7*s**3 - 1693515625*p**2*q**9*s**3 - 104884875*p**12*q*r*s**3 - 1937437500*p**9*q**3*r*s**3 - 11461053125*p**6*q**5*r*s**3 + 10299375000*p**3*q**7*r*s**3 + 10551250000*q**9*r*s**3 + 1336263750*p**10*q*r**2*s**3 + 23737250000*p**7*q**3*r**2*s**3 + 57136718750*p**4*q**5*r**2*s**3 - 8288906250*p*q**7*r**2*s**3 - 10907218750*p**8*q*r**3*s**3 - 160615000000*p**5*q**3*r**3*s**3 - 111134687500*p**2*q**5*r**3*s**3 + 46743125000*p**6*q*r**4*s**3 + 570509375000*p**3*q**3*r**4*s**3 + 274839843750*q**5*r**4*s**3 - 73312500000*p**4*q*r**5*s**3 - 145437500000*p*q**3*r**5*s**3 + 8750000000*p**2*q*r**6*s**3 + 180000000000*q*r**7*s**3 + 15946875*p**13*s**4 + 1265625*p**10*q**2*s**4 - 3282343750*p**7*q**4*s**4 - 38241406250*p**4*q**6*s**4 - 40136718750*p*q**8*s**4 - 113146875*p**11*r*s**4 - 2302734375*p**8*q**2*r*s**4 + 68450156250*p**5*q**4*r*s**4 + 177376562500*p**2*q**6*r*s**4 + 3164062500*p**9*r**2*s**4 + 14392890625*p**6*q**2*r**2*s**4 - 543781250000*p**3*q**4*r**2*s**4 - 319769531250*q**6*r**2*s**4 - 21048281250*p**7*r**3*s**4 - 240687500000*p**4*q**2*r**3*s**4 - 228164062500*p*q**4*r**3*s**4 + 23062500000*p**5*r**4*s**4 + 300410156250*p**2*q**2*r**4*s**4 + 93437500000*p**3*r**5*s**4 - 1141015625000*q**2*r**5*s**4 - 187500000000*p*r**6*s**4 + 1761328125*p**9*q*s**5 - 3177734375*p**6*q**3*s**5 + 60019531250*p**3*q**5*s**5 + 108398437500*q**7*s**5 + 24106640625*p**7*q*r*s**5 + 429589843750*p**4*q**3*r*s**5 + 410371093750*p*q**5*r*s**5 - 23582031250*p**5*q*r**2*s**5 + 202441406250*p**2*q**3*r**2*s**5 - 383203125000*p**3*q*r**3*s**5 + 2232910156250*q**3*r**3*s**5 + 1500000000000*p*q*r**4*s**5 - 13710937500*p**8*s**6 - 202832031250*p**5*q**2*s**6 - 531738281250*p**2*q**4*s**6 + 73330078125*p**6*r*s**6 - 3906250000*p**3*q**2*r*s**6 - 1275878906250*q**4*r*s**6 - 121093750000*p**4*r**2*s**6 - 3308593750000*p*q**2*r**2*s**6 + 18066406250*p**2*r**3*s**6 - 244140625000*r**4*s**6 + 327148437500*p**4*q*s**7 + 1672363281250*p*q**3*s**7 + 446777343750*p**2*q*r*s**7 + 1232910156250*q*r**2*s**7 - 274658203125*p**3*s**8 - 1068115234375*q**2*s**8 - 61035156250*p*r*s**8
+
+ b[4][3] = 200*p**9*q**8 + 7550*p**6*q**10 + 78650*p**3*q**12 + 248400*q**14 - 4800*p**10*q**6*r - 164300*p**7*q**8*r - 1709575*p**4*q**10*r - 5566500*p*q**12*r + 31050*p**11*q**4*r**2 + 1116175*p**8*q**6*r**2 + 12674650*p**5*q**8*r**2 + 45333850*p**2*q**10*r**2 - 60750*p**12*q**2*r**3 - 2872725*p**9*q**4*r**3 - 40403050*p**6*q**6*r**3 - 173564375*p**3*q**8*r**3 - 11242250*q**10*r**3 + 2174100*p**10*q**2*r**4 + 54010000*p**7*q**4*r**4 + 331074875*p**4*q**6*r**4 + 114173750*p*q**8*r**4 - 24858500*p**8*q**2*r**5 - 300875000*p**5*q**4*r**5 - 319430625*p**2*q**6*r**5 + 69810000*p**6*q**2*r**6 - 23900000*p**3*q**4*r**6 - 294662500*q**6*r**6 + 524200000*p**4*q**2*r**7 + 1432000000*p*q**4*r**7 - 2340000000*p**2*q**2*r**8 + 5400*p**11*q**5*s + 310400*p**8*q**7*s + 3591725*p**5*q**9*s + 11556750*p**2*q**11*s - 105300*p**12*q**3*r*s - 4234650*p**9*q**5*r*s - 49928875*p**6*q**7*r*s - 174078125*p**3*q**9*r*s + 18000000*q**11*r*s + 364500*p**13*q*r**2*s + 15763050*p**10*q**3*r**2*s + 220187400*p**7*q**5*r**2*s + 929609375*p**4*q**7*r**2*s - 43653125*p*q**9*r**2*s - 13427100*p**11*q*r**3*s - 346066250*p**8*q**3*r**3*s - 2287673375*p**5*q**5*r**3*s - 1403903125*p**2*q**7*r**3*s + 184586000*p**9*q*r**4*s + 2983460000*p**6*q**3*r**4*s + 8725818750*p**3*q**5*r**4*s + 2527734375*q**7*r**4*s - 1284480000*p**7*q*r**5*s - 13138250000*p**4*q**3*r**5*s - 14001625000*p*q**5*r**5*s + 4224800000*p**5*q*r**6*s + 27460000000*p**2*q**3*r**6*s - 3760000000*p**3*q*r**7*s + 3900000000*q**3*r**7*s + 36450*p**13*q**2*s**2 + 2765475*p**10*q**4*s**2 + 34027625*p**7*q**6*s**2 + 97375000*p**4*q**8*s**2 - 88275000*p*q**10*s**2 - 546750*p**14*r*s**2 - 21961125*p**11*q**2*r*s**2 - 273059375*p**8*q**4*r*s**2 - 761562500*p**5*q**6*r*s**2 + 1869656250*p**2*q**8*r*s**2 + 20545650*p**12*r**2*s**2 + 473934375*p**9*q**2*r**2*s**2 + 1758053125*p**6*q**4*r**2*s**2 - 8743359375*p**3*q**6*r**2*s**2 - 4154375000*q**8*r**2*s**2 - 296559000*p**10*r**3*s**2 - 4065056250*p**7*q**2*r**3*s**2 - 186328125*p**4*q**4*r**3*s**2 + 19419453125*p*q**6*r**3*s**2 + 2326262500*p**8*r**4*s**2 + 21189375000*p**5*q**2*r**4*s**2 - 26301953125*p**2*q**4*r**4*s**2 - 10513250000*p**6*r**5*s**2 - 69937500000*p**3*q**2*r**5*s**2 - 42257812500*q**4*r**5*s**2 + 23375000000*p**4*r**6*s**2 + 40750000000*p*q**2*r**6*s**2 - 19500000000*p**2*r**7*s**2 + 4009500*p**12*q*s**3 + 36140625*p**9*q**3*s**3 - 335459375*p**6*q**5*s**3 - 2695312500*p**3*q**7*s**3 - 1486250000*q**9*s**3 + 102515625*p**10*q*r*s**3 + 4006812500*p**7*q**3*r*s**3 + 27589609375*p**4*q**5*r*s**3 + 20195312500*p*q**7*r*s**3 - 2792812500*p**8*q*r**2*s**3 - 44115156250*p**5*q**3*r**2*s**3 - 72609453125*p**2*q**5*r**2*s**3 + 18752500000*p**6*q*r**3*s**3 + 218140625000*p**3*q**3*r**3*s**3 + 109940234375*q**5*r**3*s**3 - 21893750000*p**4*q*r**4*s**3 - 65187500000*p*q**3*r**4*s**3 - 31000000000*p**2*q*r**5*s**3 + 97500000000*q*r**6*s**3 - 86568750*p**11*s**4 - 1955390625*p**8*q**2*s**4 - 8960781250*p**5*q**4*s**4 - 1357812500*p**2*q**6*s**4 + 1657968750*p**9*r*s**4 + 10467187500*p**6*q**2*r*s**4 - 55292968750*p**3*q**4*r*s**4 - 60683593750*q**6*r*s**4 - 11473593750*p**7*r**2*s**4 - 123281250000*p**4*q**2*r**2*s**4 - 164912109375*p*q**4*r**2*s**4 + 13150000000*p**5*r**3*s**4 + 190751953125*p**2*q**2*r**3*s**4 + 61875000000*p**3*r**4*s**4 - 467773437500*q**2*r**4*s**4 - 118750000000*p*r**5*s**4 + 7583203125*p**7*q*s**5 + 54638671875*p**4*q**3*s**5 + 39423828125*p*q**5*s**5 + 32392578125*p**5*q*r*s**5 + 278515625000*p**2*q**3*r*s**5 - 298339843750*p**3*q*r**2*s**5 + 560791015625*q**3*r**2*s**5 + 720703125000*p*q*r**3*s**5 - 19687500000*p**6*s**6 - 159667968750*p**3*q**2*s**6 - 72265625000*q**4*s**6 + 116699218750*p**4*r*s**6 - 924072265625*p*q**2*r*s**6 - 156005859375*p**2*r**2*s**6 - 112304687500*r**3*s**6 + 349121093750*p**2*q*s**7 + 396728515625*q*r*s**7 - 213623046875*p*s**8
+
+ b[4][2] = -600*p**10*q**6 - 18450*p**7*q**8 - 174000*p**4*q**10 - 518400*p*q**12 + 5400*p**11*q**4*r + 197550*p**8*q**6*r + 2147775*p**5*q**8*r + 7219800*p**2*q**10*r - 12150*p**12*q**2*r**2 - 662200*p**9*q**4*r**2 - 9274775*p**6*q**6*r**2 - 38330625*p**3*q**8*r**2 - 5508000*q**10*r**2 + 656550*p**10*q**2*r**3 + 16233750*p**7*q**4*r**3 + 97335875*p**4*q**6*r**3 + 58271250*p*q**8*r**3 - 9845500*p**8*q**2*r**4 - 119464375*p**5*q**4*r**4 - 194431875*p**2*q**6*r**4 + 49465000*p**6*q**2*r**5 + 166000000*p**3*q**4*r**5 - 80793750*q**6*r**5 + 54400000*p**4*q**2*r**6 + 377750000*p*q**4*r**6 - 630000000*p**2*q**2*r**7 - 16200*p**12*q**3*s - 459300*p**9*q**5*s - 4207225*p**6*q**7*s - 10827500*p**3*q**9*s + 13635000*q**11*s + 72900*p**13*q*r*s + 2877300*p**10*q**3*r*s + 33239700*p**7*q**5*r*s + 107080625*p**4*q**7*r*s - 114975000*p*q**9*r*s - 3601800*p**11*q*r**2*s - 75214375*p**8*q**3*r**2*s - 387073250*p**5*q**5*r**2*s + 55540625*p**2*q**7*r**2*s + 53793000*p**9*q*r**3*s + 687176875*p**6*q**3*r**3*s + 1670018750*p**3*q**5*r**3*s + 665234375*q**7*r**3*s - 391570000*p**7*q*r**4*s - 3420125000*p**4*q**3*r**4*s - 3609625000*p*q**5*r**4*s + 1365600000*p**5*q*r**5*s + 7236250000*p**2*q**3*r**5*s - 1220000000*p**3*q*r**6*s + 1050000000*q**3*r**6*s - 109350*p**14*s**2 - 3065850*p**11*q**2*s**2 - 26908125*p**8*q**4*s**2 - 44606875*p**5*q**6*s**2 + 269812500*p**2*q**8*s**2 + 5200200*p**12*r*s**2 + 81826875*p**9*q**2*r*s**2 + 155378125*p**6*q**4*r*s**2 - 1936203125*p**3*q**6*r*s**2 - 998437500*q**8*r*s**2 - 77145750*p**10*r**2*s**2 - 745528125*p**7*q**2*r**2*s**2 + 683437500*p**4*q**4*r**2*s**2 + 4083359375*p*q**6*r**2*s**2 + 593287500*p**8*r**3*s**2 + 4799375000*p**5*q**2*r**3*s**2 - 4167578125*p**2*q**4*r**3*s**2 - 2731125000*p**6*r**4*s**2 - 18668750000*p**3*q**2*r**4*s**2 - 10480468750*q**4*r**4*s**2 + 6200000000*p**4*r**5*s**2 + 11750000000*p*q**2*r**5*s**2 - 5250000000*p**2*r**6*s**2 + 26527500*p**10*q*s**3 + 526031250*p**7*q**3*s**3 + 3160703125*p**4*q**5*s**3 + 2650312500*p*q**7*s**3 - 448031250*p**8*q*r*s**3 - 6682968750*p**5*q**3*r*s**3 - 11642812500*p**2*q**5*r*s**3 + 2553203125*p**6*q*r**2*s**3 + 37234375000*p**3*q**3*r**2*s**3 + 21871484375*q**5*r**2*s**3 + 2803125000*p**4*q*r**3*s**3 - 10796875000*p*q**3*r**3*s**3 - 16656250000*p**2*q*r**4*s**3 + 26250000000*q*r**5*s**3 - 75937500*p**9*s**4 - 704062500*p**6*q**2*s**4 - 8363281250*p**3*q**4*s**4 - 10398437500*q**6*s**4 + 197578125*p**7*r*s**4 - 16441406250*p**4*q**2*r*s**4 - 24277343750*p*q**4*r*s**4 - 5716015625*p**5*r**2*s**4 + 31728515625*p**2*q**2*r**2*s**4 + 27031250000*p**3*r**3*s**4 - 92285156250*q**2*r**3*s**4 - 33593750000*p*r**4*s**4 + 10394531250*p**5*q*s**5 + 38037109375*p**2*q**3*s**5 - 48144531250*p**3*q*r*s**5 + 74462890625*q**3*r*s**5 + 121093750000*p*q*r**2*s**5 - 2197265625*p**4*s**6 - 92529296875*p*q**2*s**6 + 15380859375*p**2*r*s**6 - 31738281250*r**2*s**6 + 54931640625*q*s**7
+
+ b[4][1] = 200*p**8*q**6 + 2950*p**5*q**8 + 10800*p**2*q**10 - 1800*p**9*q**4*r - 49650*p**6*q**6*r - 403375*p**3*q**8*r - 999000*q**10*r + 4050*p**10*q**2*r**2 + 236625*p**7*q**4*r**2 + 3109500*p**4*q**6*r**2 + 11463750*p*q**8*r**2 - 331500*p**8*q**2*r**3 - 7818125*p**5*q**4*r**3 - 41411250*p**2*q**6*r**3 + 4782500*p**6*q**2*r**4 + 47475000*p**3*q**4*r**4 - 16728125*q**6*r**4 - 8700000*p**4*q**2*r**5 + 81750000*p*q**4*r**5 - 135000000*p**2*q**2*r**6 + 5400*p**10*q**3*s + 144200*p**7*q**5*s + 939375*p**4*q**7*s + 1012500*p*q**9*s - 24300*p**11*q*r*s - 1169250*p**8*q**3*r*s - 14027250*p**5*q**5*r*s - 44446875*p**2*q**7*r*s + 2011500*p**9*q*r**2*s + 49330625*p**6*q**3*r**2*s + 272009375*p**3*q**5*r**2*s + 104062500*q**7*r**2*s - 34660000*p**7*q*r**3*s - 455062500*p**4*q**3*r**3*s - 625906250*p*q**5*r**3*s + 210200000*p**5*q*r**4*s + 1298750000*p**2*q**3*r**4*s - 240000000*p**3*q*r**5*s + 225000000*q**3*r**5*s + 36450*p**12*s**2 + 1231875*p**9*q**2*s**2 + 10712500*p**6*q**4*s**2 + 21718750*p**3*q**6*s**2 + 16875000*q**8*s**2 - 2814750*p**10*r*s**2 - 67612500*p**7*q**2*r*s**2 - 345156250*p**4*q**4*r*s**2 - 283125000*p*q**6*r*s**2 + 51300000*p**8*r**2*s**2 + 734531250*p**5*q**2*r**2*s**2 + 1267187500*p**2*q**4*r**2*s**2 - 384312500*p**6*r**3*s**2 - 3912500000*p**3*q**2*r**3*s**2 - 1822265625*q**4*r**3*s**2 + 1112500000*p**4*r**4*s**2 + 2437500000*p*q**2*r**4*s**2 - 1125000000*p**2*r**5*s**2 - 72578125*p**5*q**3*s**3 - 189296875*p**2*q**5*s**3 + 127265625*p**6*q*r*s**3 + 1415625000*p**3*q**3*r*s**3 + 1229687500*q**5*r*s**3 + 1448437500*p**4*q*r**2*s**3 + 2218750000*p*q**3*r**2*s**3 - 4031250000*p**2*q*r**3*s**3 + 5625000000*q*r**4*s**3 - 132890625*p**7*s**4 - 529296875*p**4*q**2*s**4 - 175781250*p*q**4*s**4 - 401953125*p**5*r*s**4 - 4482421875*p**2*q**2*r*s**4 + 4140625000*p**3*r**2*s**4 - 10498046875*q**2*r**2*s**4 - 7031250000*p*r**3*s**4 + 1220703125*p**3*q*s**5 + 1953125000*q**3*s**5 + 14160156250*p*q*r*s**5 - 1708984375*p**2*s**6 - 3662109375*r*s**6
+
+ b[4][0] = -4600*p**6*q**6 - 67850*p**3*q**8 - 248400*q**10 + 38900*p**7*q**4*r + 679575*p**4*q**6*r + 2866500*p*q**8*r - 81900*p**8*q**2*r**2 - 2009750*p**5*q**4*r**2 - 10783750*p**2*q**6*r**2 + 1478750*p**6*q**2*r**3 + 14165625*p**3*q**4*r**3 - 2743750*q**6*r**3 - 5450000*p**4*q**2*r**4 + 12687500*p*q**4*r**4 - 22500000*p**2*q**2*r**5 - 101700*p**8*q**3*s - 1700975*p**5*q**5*s - 7061250*p**2*q**7*s + 423900*p**9*q*r*s + 9292375*p**6*q**3*r*s + 50438750*p**3*q**5*r*s + 20475000*q**7*r*s - 7852500*p**7*q*r**2*s - 87765625*p**4*q**3*r**2*s - 121609375*p*q**5*r**2*s + 47700000*p**5*q*r**3*s + 264687500*p**2*q**3*r**3*s - 65000000*p**3*q*r**4*s + 37500000*q**3*r**4*s - 534600*p**10*s**2 - 10344375*p**7*q**2*s**2 - 54859375*p**4*q**4*s**2 - 40312500*p*q**6*s**2 + 10158750*p**8*r*s**2 + 117778125*p**5*q**2*r*s**2 + 192421875*p**2*q**4*r*s**2 - 70593750*p**6*r**2*s**2 - 685312500*p**3*q**2*r**2*s**2 - 334375000*q**4*r**2*s**2 + 193750000*p**4*r**3*s**2 + 500000000*p*q**2*r**3*s**2 - 187500000*p**2*r**4*s**2 + 8437500*p**6*q*s**3 + 159218750*p**3*q**3*s**3 + 220625000*q**5*s**3 + 353828125*p**4*q*r*s**3 + 412500000*p*q**3*r*s**3 - 1023437500*p**2*q*r**2*s**3 + 937500000*q*r**3*s**3 - 206015625*p**5*s**4 - 701171875*p**2*q**2*s**4 + 998046875*p**3*r*s**4 - 1308593750*q**2*r*s**4 - 1367187500*p*r**2*s**4 + 1708984375*p*q*s**5 - 976562500*s**6
+
+ return b
+
+ @property
+ def o(self):
+ p, q, r, s = self.p, self.q, self.r, self.s
+ o = [0]*6
+
+ o[5] = -1600*p**10*q**10 - 23600*p**7*q**12 - 86400*p**4*q**14 + 24800*p**11*q**8*r + 419200*p**8*q**10*r + 1850450*p**5*q**12*r + 896400*p**2*q**14*r - 138800*p**12*q**6*r**2 - 2921900*p**9*q**8*r**2 - 17295200*p**6*q**10*r**2 - 27127750*p**3*q**12*r**2 - 26076600*q**14*r**2 + 325800*p**13*q**4*r**3 + 9993850*p**10*q**6*r**3 + 88010500*p**7*q**8*r**3 + 274047650*p**4*q**10*r**3 + 410171400*p*q**12*r**3 - 259200*p**14*q**2*r**4 - 17147100*p**11*q**4*r**4 - 254289150*p**8*q**6*r**4 - 1318548225*p**5*q**8*r**4 - 2633598475*p**2*q**10*r**4 + 12636000*p**12*q**2*r**5 + 388911000*p**9*q**4*r**5 + 3269704725*p**6*q**6*r**5 + 8791192300*p**3*q**8*r**5 + 93560575*q**10*r**5 - 228361600*p**10*q**2*r**6 - 3951199200*p**7*q**4*r**6 - 16276981100*p**4*q**6*r**6 - 1597227000*p*q**8*r**6 + 1947899200*p**8*q**2*r**7 + 17037648000*p**5*q**4*r**7 + 8919740000*p**2*q**6*r**7 - 7672160000*p**6*q**2*r**8 - 15496000000*p**3*q**4*r**8 + 4224000000*q**6*r**8 + 9968000000*p**4*q**2*r**9 - 8640000000*p*q**4*r**9 + 4800000000*p**2*q**2*r**10 - 55200*p**12*q**7*s - 685600*p**9*q**9*s + 1028250*p**6*q**11*s + 37650000*p**3*q**13*s + 111375000*q**15*s + 583200*p**13*q**5*r*s + 9075600*p**10*q**7*r*s - 883150*p**7*q**9*r*s - 506830750*p**4*q**11*r*s - 1793137500*p*q**13*r*s - 1852200*p**14*q**3*r**2*s - 41435250*p**11*q**5*r**2*s - 80566700*p**8*q**7*r**2*s + 2485673600*p**5*q**9*r**2*s + 11442286125*p**2*q**11*r**2*s + 1555200*p**15*q*r**3*s + 80846100*p**12*q**3*r**3*s + 564906800*p**9*q**5*r**3*s - 4493012400*p**6*q**7*r**3*s - 35492391250*p**3*q**9*r**3*s - 789931875*q**11*r**3*s - 71766000*p**13*q*r**4*s - 1551149200*p**10*q**3*r**4*s - 1773437900*p**7*q**5*r**4*s + 51957593125*p**4*q**7*r**4*s + 14964765625*p*q**9*r**4*s + 1231569600*p**11*q*r**5*s + 12042977600*p**8*q**3*r**5*s - 27151011200*p**5*q**5*r**5*s - 88080610000*p**2*q**7*r**5*s - 9912995200*p**9*q*r**6*s - 29448104000*p**6*q**3*r**6*s + 144954840000*p**3*q**5*r**6*s - 44601300000*q**7*r**6*s + 35453760000*p**7*q*r**7*s - 63264000000*p**4*q**3*r**7*s + 60544000000*p*q**5*r**7*s - 30048000000*p**5*q*r**8*s + 37040000000*p**2*q**3*r**8*s - 60800000000*p**3*q*r**9*s - 48000000000*q**3*r**9*s - 615600*p**14*q**4*s**2 - 10524500*p**11*q**6*s**2 - 33831250*p**8*q**8*s**2 + 222806250*p**5*q**10*s**2 + 1099687500*p**2*q**12*s**2 + 3353400*p**15*q**2*r*s**2 + 74269350*p**12*q**4*r*s**2 + 276445750*p**9*q**6*r*s**2 - 2618600000*p**6*q**8*r*s**2 - 14473243750*p**3*q**10*r*s**2 + 1383750000*q**12*r*s**2 - 2332800*p**16*r**2*s**2 - 132750900*p**13*q**2*r**2*s**2 - 900775150*p**10*q**4*r**2*s**2 + 8249244500*p**7*q**6*r**2*s**2 + 59525796875*p**4*q**8*r**2*s**2 - 40292868750*p*q**10*r**2*s**2 + 128304000*p**14*r**3*s**2 + 3160232100*p**11*q**2*r**3*s**2 + 8329580000*p**8*q**4*r**3*s**2 - 45558458750*p**5*q**6*r**3*s**2 + 297252890625*p**2*q**8*r**3*s**2 - 2769854400*p**12*r**4*s**2 - 37065970000*p**9*q**2*r**4*s**2 - 90812546875*p**6*q**4*r**4*s**2 - 627902000000*p**3*q**6*r**4*s**2 + 181347421875*q**8*r**4*s**2 + 30946932800*p**10*r**5*s**2 + 249954680000*p**7*q**2*r**5*s**2 + 802954812500*p**4*q**4*r**5*s**2 - 80900000000*p*q**6*r**5*s**2 - 192137320000*p**8*r**6*s**2 - 932641600000*p**5*q**2*r**6*s**2 - 943242500000*p**2*q**4*r**6*s**2 + 658412000000*p**6*r**7*s**2 + 1930720000000*p**3*q**2*r**7*s**2 + 593800000000*q**4*r**7*s**2 - 1162800000000*p**4*r**8*s**2 - 280000000000*p*q**2*r**8*s**2 + 840000000000*p**2*r**9*s**2 - 2187000*p**16*q*s**3 - 47418750*p**13*q**3*s**3 - 180618750*p**10*q**5*s**3 + 2231250000*p**7*q**7*s**3 + 17857734375*p**4*q**9*s**3 + 29882812500*p*q**11*s**3 + 24664500*p**14*q*r*s**3 - 853368750*p**11*q**3*r*s**3 - 25939693750*p**8*q**5*r*s**3 - 177541562500*p**5*q**7*r*s**3 - 297978828125*p**2*q**9*r*s**3 - 153468000*p**12*q*r**2*s**3 + 30188125000*p**9*q**3*r**2*s**3 + 344049821875*p**6*q**5*r**2*s**3 + 534026875000*p**3*q**7*r**2*s**3 - 340726484375*q**9*r**2*s**3 - 9056190000*p**10*q*r**3*s**3 - 322314687500*p**7*q**3*r**3*s**3 - 769632109375*p**4*q**5*r**3*s**3 - 83276875000*p*q**7*r**3*s**3 + 164061000000*p**8*q*r**4*s**3 + 1381358750000*p**5*q**3*r**4*s**3 + 3088020000000*p**2*q**5*r**4*s**3 - 1267655000000*p**6*q*r**5*s**3 - 7642630000000*p**3*q**3*r**5*s**3 - 2759877500000*q**5*r**5*s**3 + 4597760000000*p**4*q*r**6*s**3 + 1846200000000*p*q**3*r**6*s**3 - 7006000000000*p**2*q*r**7*s**3 - 1200000000000*q*r**8*s**3 + 18225000*p**15*s**4 + 1328906250*p**12*q**2*s**4 + 24729140625*p**9*q**4*s**4 + 169467187500*p**6*q**6*s**4 + 413281250000*p**3*q**8*s**4 + 223828125000*q**10*s**4 + 710775000*p**13*r*s**4 - 18611015625*p**10*q**2*r*s**4 - 314344375000*p**7*q**4*r*s**4 - 828439843750*p**4*q**6*r*s**4 + 460937500000*p*q**8*r*s**4 - 25674975000*p**11*r**2*s**4 - 52223515625*p**8*q**2*r**2*s**4 - 387160000000*p**5*q**4*r**2*s**4 - 4733680078125*p**2*q**6*r**2*s**4 + 343911875000*p**9*r**3*s**4 + 3328658359375*p**6*q**2*r**3*s**4 + 16532406250000*p**3*q**4*r**3*s**4 + 5980613281250*q**6*r**3*s**4 - 2295497500000*p**7*r**4*s**4 - 14809820312500*p**4*q**2*r**4*s**4 - 6491406250000*p*q**4*r**4*s**4 + 7768470000000*p**5*r**5*s**4 + 34192562500000*p**2*q**2*r**5*s**4 - 11859000000000*p**3*r**6*s**4 + 10530000000000*q**2*r**6*s**4 + 6000000000000*p*r**7*s**4 + 11453906250*p**11*q*s**5 + 149765625000*p**8*q**3*s**5 + 545537109375*p**5*q**5*s**5 + 527343750000*p**2*q**7*s**5 - 371313281250*p**9*q*r*s**5 - 3461455078125*p**6*q**3*r*s**5 - 7920878906250*p**3*q**5*r*s**5 - 4747314453125*q**7*r*s**5 + 2417815625000*p**7*q*r**2*s**5 + 5465576171875*p**4*q**3*r**2*s**5 + 5937128906250*p*q**5*r**2*s**5 - 10661156250000*p**5*q*r**3*s**5 - 63574218750000*p**2*q**3*r**3*s**5 + 24059375000000*p**3*q*r**4*s**5 - 33023437500000*q**3*r**4*s**5 - 43125000000000*p*q*r**5*s**5 + 94394531250*p**10*s**6 + 1097167968750*p**7*q**2*s**6 + 2829833984375*p**4*q**4*s**6 - 1525878906250*p*q**6*s**6 + 2724609375*p**8*r*s**6 + 13998535156250*p**5*q**2*r*s**6 + 57094482421875*p**2*q**4*r*s**6 - 8512509765625*p**6*r**2*s**6 - 37941406250000*p**3*q**2*r**2*s**6 + 33191894531250*q**4*r**2*s**6 + 50534179687500*p**4*r**3*s**6 + 156656250000000*p*q**2*r**3*s**6 - 85023437500000*p**2*r**4*s**6 + 10125000000000*r**5*s**6 - 2717285156250*p**6*q*s**7 - 11352539062500*p**3*q**3*s**7 - 2593994140625*q**5*s**7 - 47154541015625*p**4*q*r*s**7 - 160644531250000*p*q**3*r*s**7 + 142500000000000*p**2*q*r**2*s**7 - 26757812500000*q*r**3*s**7 - 4364013671875*p**5*s**8 - 94604492187500*p**2*q**2*s**8 + 114379882812500*p**3*r*s**8 + 51116943359375*q**2*r*s**8 - 346435546875000*p*r**2*s**8 + 476837158203125*p*q*s**9 - 476837158203125*s**10
+
+ o[4] = 1600*p**11*q**8 + 20800*p**8*q**10 + 45100*p**5*q**12 - 151200*p**2*q**14 - 19200*p**12*q**6*r - 293200*p**9*q**8*r - 794600*p**6*q**10*r + 2634675*p**3*q**12*r + 2640600*q**14*r + 75600*p**13*q**4*r**2 + 1529100*p**10*q**6*r**2 + 6233350*p**7*q**8*r**2 - 12013350*p**4*q**10*r**2 - 29069550*p*q**12*r**2 - 97200*p**14*q**2*r**3 - 3562500*p**11*q**4*r**3 - 26984900*p**8*q**6*r**3 - 15900325*p**5*q**8*r**3 + 76267100*p**2*q**10*r**3 + 3272400*p**12*q**2*r**4 + 59486850*p**9*q**4*r**4 + 221270075*p**6*q**6*r**4 + 74065250*p**3*q**8*r**4 - 300564375*q**10*r**4 - 45569400*p**10*q**2*r**5 - 438666000*p**7*q**4*r**5 - 444821250*p**4*q**6*r**5 + 2448256250*p*q**8*r**5 + 290640000*p**8*q**2*r**6 + 855850000*p**5*q**4*r**6 - 5741875000*p**2*q**6*r**6 - 644000000*p**6*q**2*r**7 + 5574000000*p**3*q**4*r**7 + 4643000000*q**6*r**7 - 1696000000*p**4*q**2*r**8 - 12660000000*p*q**4*r**8 + 7200000000*p**2*q**2*r**9 + 43200*p**13*q**5*s + 572000*p**10*q**7*s - 59800*p**7*q**9*s - 24174625*p**4*q**11*s - 74587500*p*q**13*s - 324000*p**14*q**3*r*s - 5531400*p**11*q**5*r*s - 3712100*p**8*q**7*r*s + 293009275*p**5*q**9*r*s + 1115548875*p**2*q**11*r*s + 583200*p**15*q*r**2*s + 18343800*p**12*q**3*r**2*s + 77911100*p**9*q**5*r**2*s - 957488825*p**6*q**7*r**2*s - 5449661250*p**3*q**9*r**2*s + 960120000*q**11*r**2*s - 23684400*p**13*q*r**3*s - 373761900*p**10*q**3*r**3*s - 27944975*p**7*q**5*r**3*s + 10375740625*p**4*q**7*r**3*s - 4649093750*p*q**9*r**3*s + 395816400*p**11*q*r**4*s + 2910968000*p**8*q**3*r**4*s - 9126162500*p**5*q**5*r**4*s - 11696118750*p**2*q**7*r**4*s - 3028640000*p**9*q*r**5*s - 3251550000*p**6*q**3*r**5*s + 47914250000*p**3*q**5*r**5*s - 30255625000*q**7*r**5*s + 9304000000*p**7*q*r**6*s - 42970000000*p**4*q**3*r**6*s + 31475000000*p*q**5*r**6*s + 2176000000*p**5*q*r**7*s + 62100000000*p**2*q**3*r**7*s - 43200000000*p**3*q*r**8*s - 72000000000*q**3*r**8*s + 291600*p**15*q**2*s**2 + 2702700*p**12*q**4*s**2 - 38692250*p**9*q**6*s**2 - 538903125*p**6*q**8*s**2 - 1613112500*p**3*q**10*s**2 + 320625000*q**12*s**2 - 874800*p**16*r*s**2 - 14166900*p**13*q**2*r*s**2 + 193284900*p**10*q**4*r*s**2 + 3688520500*p**7*q**6*r*s**2 + 11613390625*p**4*q**8*r*s**2 - 15609881250*p*q**10*r*s**2 + 44031600*p**14*r**2*s**2 + 482345550*p**11*q**2*r**2*s**2 - 2020881875*p**8*q**4*r**2*s**2 - 7407026250*p**5*q**6*r**2*s**2 + 136175750000*p**2*q**8*r**2*s**2 - 1000884600*p**12*r**3*s**2 - 8888950000*p**9*q**2*r**3*s**2 - 30101703125*p**6*q**4*r**3*s**2 - 319761000000*p**3*q**6*r**3*s**2 + 51519218750*q**8*r**3*s**2 + 12622395000*p**10*r**4*s**2 + 97032450000*p**7*q**2*r**4*s**2 + 469929218750*p**4*q**4*r**4*s**2 + 291342187500*p*q**6*r**4*s**2 - 96382000000*p**8*r**5*s**2 - 598070000000*p**5*q**2*r**5*s**2 - 1165021875000*p**2*q**4*r**5*s**2 + 446500000000*p**6*r**6*s**2 + 1651500000000*p**3*q**2*r**6*s**2 + 789375000000*q**4*r**6*s**2 - 1152000000000*p**4*r**7*s**2 - 600000000000*p*q**2*r**7*s**2 + 1260000000000*p**2*r**8*s**2 - 24786000*p**14*q*s**3 - 660487500*p**11*q**3*s**3 - 5886356250*p**8*q**5*s**3 - 18137187500*p**5*q**7*s**3 - 5120546875*p**2*q**9*s**3 + 827658000*p**12*q*r*s**3 + 13343062500*p**9*q**3*r*s**3 + 39782068750*p**6*q**5*r*s**3 - 111288437500*p**3*q**7*r*s**3 - 15438750000*q**9*r*s**3 - 14540782500*p**10*q*r**2*s**3 - 135889750000*p**7*q**3*r**2*s**3 - 176892578125*p**4*q**5*r**2*s**3 - 934462656250*p*q**7*r**2*s**3 + 171669250000*p**8*q*r**3*s**3 + 1164538125000*p**5*q**3*r**3*s**3 + 3192346406250*p**2*q**5*r**3*s**3 - 1295476250000*p**6*q*r**4*s**3 - 6540712500000*p**3*q**3*r**4*s**3 - 2957828125000*q**5*r**4*s**3 + 5366750000000*p**4*q*r**5*s**3 + 3165000000000*p*q**3*r**5*s**3 - 8862500000000*p**2*q*r**6*s**3 - 1800000000000*q*r**7*s**3 + 236925000*p**13*s**4 + 8895234375*p**10*q**2*s**4 + 106180781250*p**7*q**4*s**4 + 474221875000*p**4*q**6*s**4 + 616210937500*p*q**8*s**4 - 6995868750*p**11*r*s**4 - 184190625000*p**8*q**2*r*s**4 - 1299254453125*p**5*q**4*r*s**4 - 2475458593750*p**2*q**6*r*s**4 + 63049218750*p**9*r**2*s**4 + 1646791484375*p**6*q**2*r**2*s**4 + 9086886718750*p**3*q**4*r**2*s**4 + 4673421875000*q**6*r**2*s**4 - 215665000000*p**7*r**3*s**4 - 7864589843750*p**4*q**2*r**3*s**4 - 5987890625000*p*q**4*r**3*s**4 + 594843750000*p**5*r**4*s**4 + 27791171875000*p**2*q**2*r**4*s**4 - 3881250000000*p**3*r**5*s**4 + 12203125000000*q**2*r**5*s**4 + 10312500000000*p*r**6*s**4 - 34720312500*p**9*q*s**5 - 545126953125*p**6*q**3*s**5 - 2176425781250*p**3*q**5*s**5 - 2792968750000*q**7*s**5 - 1395703125*p**7*q*r*s**5 - 1957568359375*p**4*q**3*r*s**5 + 5122636718750*p*q**5*r*s**5 + 858210937500*p**5*q*r**2*s**5 - 42050097656250*p**2*q**3*r**2*s**5 + 7088281250000*p**3*q*r**3*s**5 - 25974609375000*q**3*r**3*s**5 - 69296875000000*p*q*r**4*s**5 + 384697265625*p**8*s**6 + 6403320312500*p**5*q**2*s**6 + 16742675781250*p**2*q**4*s**6 - 3467080078125*p**6*r*s**6 + 11009765625000*p**3*q**2*r*s**6 + 16451660156250*q**4*r*s**6 + 6979003906250*p**4*r**2*s**6 + 145403320312500*p*q**2*r**2*s**6 + 4076171875000*p**2*r**3*s**6 + 22265625000000*r**4*s**6 - 21915283203125*p**4*q*s**7 - 86608886718750*p*q**3*s**7 - 22785644531250*p**2*q*r*s**7 - 103466796875000*q*r**2*s**7 + 18798828125000*p**3*s**8 + 106048583984375*q**2*s**8 + 17761230468750*p*r*s**8
+
+ o[3] = 2800*p**9*q**8 + 55700*p**6*q**10 + 363600*p**3*q**12 + 777600*q**14 - 27200*p**10*q**6*r - 700200*p**7*q**8*r - 5726550*p**4*q**10*r - 15066000*p*q**12*r + 74700*p**11*q**4*r**2 + 2859575*p**8*q**6*r**2 + 31175725*p**5*q**8*r**2 + 103147650*p**2*q**10*r**2 - 40500*p**12*q**2*r**3 - 4274400*p**9*q**4*r**3 - 76065825*p**6*q**6*r**3 - 365623750*p**3*q**8*r**3 - 132264000*q**10*r**3 + 2192400*p**10*q**2*r**4 + 92562500*p**7*q**4*r**4 + 799193875*p**4*q**6*r**4 + 1188193125*p*q**8*r**4 - 41231500*p**8*q**2*r**5 - 914210000*p**5*q**4*r**5 - 3318853125*p**2*q**6*r**5 + 398850000*p**6*q**2*r**6 + 3944000000*p**3*q**4*r**6 + 2211312500*q**6*r**6 - 1817000000*p**4*q**2*r**7 - 6720000000*p*q**4*r**7 + 3900000000*p**2*q**2*r**8 + 75600*p**11*q**5*s + 1823100*p**8*q**7*s + 14534150*p**5*q**9*s + 38265750*p**2*q**11*s - 394200*p**12*q**3*r*s - 11453850*p**9*q**5*r*s - 101213000*p**6*q**7*r*s - 223565625*p**3*q**9*r*s + 415125000*q**11*r*s + 243000*p**13*q*r**2*s + 13654575*p**10*q**3*r**2*s + 163811725*p**7*q**5*r**2*s + 173461250*p**4*q**7*r**2*s - 3008671875*p*q**9*r**2*s - 2016900*p**11*q*r**3*s - 86576250*p**8*q**3*r**3*s - 324146625*p**5*q**5*r**3*s + 3378506250*p**2*q**7*r**3*s - 89211000*p**9*q*r**4*s - 55207500*p**6*q**3*r**4*s + 1493950000*p**3*q**5*r**4*s - 12573609375*q**7*r**4*s + 1140100000*p**7*q*r**5*s + 42500000*p**4*q**3*r**5*s + 21511250000*p*q**5*r**5*s - 4058000000*p**5*q*r**6*s + 6725000000*p**2*q**3*r**6*s - 1400000000*p**3*q*r**7*s - 39000000000*q**3*r**7*s + 510300*p**13*q**2*s**2 + 4814775*p**10*q**4*s**2 - 70265125*p**7*q**6*s**2 - 1016484375*p**4*q**8*s**2 - 3221100000*p*q**10*s**2 - 364500*p**14*r*s**2 + 30314250*p**11*q**2*r*s**2 + 1106765625*p**8*q**4*r*s**2 + 10984203125*p**5*q**6*r*s**2 + 33905812500*p**2*q**8*r*s**2 - 37980900*p**12*r**2*s**2 - 2142905625*p**9*q**2*r**2*s**2 - 26896125000*p**6*q**4*r**2*s**2 - 95551328125*p**3*q**6*r**2*s**2 + 11320312500*q**8*r**2*s**2 + 1743781500*p**10*r**3*s**2 + 35432262500*p**7*q**2*r**3*s**2 + 177855859375*p**4*q**4*r**3*s**2 + 121260546875*p*q**6*r**3*s**2 - 25943162500*p**8*r**4*s**2 - 249165500000*p**5*q**2*r**4*s**2 - 461739453125*p**2*q**4*r**4*s**2 + 177823750000*p**6*r**5*s**2 + 726225000000*p**3*q**2*r**5*s**2 + 404195312500*q**4*r**5*s**2 - 565875000000*p**4*r**6*s**2 - 407500000000*p*q**2*r**6*s**2 + 682500000000*p**2*r**7*s**2 - 59140125*p**12*q*s**3 - 1290515625*p**9*q**3*s**3 - 8785071875*p**6*q**5*s**3 - 15588281250*p**3*q**7*s**3 + 17505000000*q**9*s**3 + 896062500*p**10*q*r*s**3 + 2589750000*p**7*q**3*r*s**3 - 82700156250*p**4*q**5*r*s**3 - 347683593750*p*q**7*r*s**3 + 17022656250*p**8*q*r**2*s**3 + 320923593750*p**5*q**3*r**2*s**3 + 1042116875000*p**2*q**5*r**2*s**3 - 353262812500*p**6*q*r**3*s**3 - 2212664062500*p**3*q**3*r**3*s**3 - 1252408984375*q**5*r**3*s**3 + 1967362500000*p**4*q*r**4*s**3 + 1583343750000*p*q**3*r**4*s**3 - 3560625000000*p**2*q*r**5*s**3 - 975000000000*q*r**6*s**3 + 462459375*p**11*s**4 + 14210859375*p**8*q**2*s**4 + 99521718750*p**5*q**4*s**4 + 114955468750*p**2*q**6*s**4 - 17720859375*p**9*r*s**4 - 100320703125*p**6*q**2*r*s**4 + 1021943359375*p**3*q**4*r*s**4 + 1193203125000*q**6*r*s**4 + 171371250000*p**7*r**2*s**4 - 1113390625000*p**4*q**2*r**2*s**4 - 1211474609375*p*q**4*r**2*s**4 - 274056250000*p**5*r**3*s**4 + 8285166015625*p**2*q**2*r**3*s**4 - 2079375000000*p**3*r**4*s**4 + 5137304687500*q**2*r**4*s**4 + 6187500000000*p*r**5*s**4 - 135675000000*p**7*q*s**5 - 1275244140625*p**4*q**3*s**5 - 28388671875*p*q**5*s**5 + 1015166015625*p**5*q*r*s**5 - 10584423828125*p**2*q**3*r*s**5 + 3559570312500*p**3*q*r**2*s**5 - 6929931640625*q**3*r**2*s**5 - 32304687500000*p*q*r**3*s**5 + 430576171875*p**6*s**6 + 9397949218750*p**3*q**2*s**6 + 575195312500*q**4*s**6 - 4086425781250*p**4*r*s**6 + 42183837890625*p*q**2*r*s**6 + 8156494140625*p**2*r**2*s**6 + 12612304687500*r**3*s**6 - 25513916015625*p**2*q*s**7 - 37017822265625*q*r*s**7 + 18981933593750*p*s**8
+
+ o[2] = 1600*p**10*q**6 + 9200*p**7*q**8 - 126000*p**4*q**10 - 777600*p*q**12 - 14400*p**11*q**4*r - 119300*p**8*q**6*r + 1203225*p**5*q**8*r + 9412200*p**2*q**10*r + 32400*p**12*q**2*r**2 + 417950*p**9*q**4*r**2 - 4543725*p**6*q**6*r**2 - 49008125*p**3*q**8*r**2 - 24192000*q**10*r**2 - 292050*p**10*q**2*r**3 + 8760000*p**7*q**4*r**3 + 137506625*p**4*q**6*r**3 + 225438750*p*q**8*r**3 - 4213250*p**8*q**2*r**4 - 173595625*p**5*q**4*r**4 - 653003125*p**2*q**6*r**4 + 82575000*p**6*q**2*r**5 + 838125000*p**3*q**4*r**5 + 578562500*q**6*r**5 - 421500000*p**4*q**2*r**6 - 1796250000*p*q**4*r**6 + 1050000000*p**2*q**2*r**7 + 43200*p**12*q**3*s + 807300*p**9*q**5*s + 5328225*p**6*q**7*s + 16946250*p**3*q**9*s + 29565000*q**11*s - 194400*p**13*q*r*s - 5505300*p**10*q**3*r*s - 49886700*p**7*q**5*r*s - 178821875*p**4*q**7*r*s - 222750000*p*q**9*r*s + 6814800*p**11*q*r**2*s + 120525625*p**8*q**3*r**2*s + 526694500*p**5*q**5*r**2*s + 84065625*p**2*q**7*r**2*s - 123670500*p**9*q*r**3*s - 1106731875*p**6*q**3*r**3*s - 669556250*p**3*q**5*r**3*s - 2869265625*q**7*r**3*s + 1004350000*p**7*q*r**4*s + 3384375000*p**4*q**3*r**4*s + 5665625000*p*q**5*r**4*s - 3411000000*p**5*q*r**5*s - 418750000*p**2*q**3*r**5*s + 1700000000*p**3*q*r**6*s - 10500000000*q**3*r**6*s + 291600*p**14*s**2 + 9829350*p**11*q**2*s**2 + 114151875*p**8*q**4*s**2 + 522169375*p**5*q**6*s**2 + 716906250*p**2*q**8*s**2 - 18625950*p**12*r*s**2 - 387703125*p**9*q**2*r*s**2 - 2056109375*p**6*q**4*r*s**2 - 760203125*p**3*q**6*r*s**2 + 3071250000*q**8*r*s**2 + 512419500*p**10*r**2*s**2 + 5859053125*p**7*q**2*r**2*s**2 + 12154062500*p**4*q**4*r**2*s**2 + 15931640625*p*q**6*r**2*s**2 - 6598393750*p**8*r**3*s**2 - 43549625000*p**5*q**2*r**3*s**2 - 82011328125*p**2*q**4*r**3*s**2 + 43538125000*p**6*r**4*s**2 + 160831250000*p**3*q**2*r**4*s**2 + 99070312500*q**4*r**4*s**2 - 141812500000*p**4*r**5*s**2 - 117500000000*p*q**2*r**5*s**2 + 183750000000*p**2*r**6*s**2 - 154608750*p**10*q*s**3 - 3309468750*p**7*q**3*s**3 - 20834140625*p**4*q**5*s**3 - 34731562500*p*q**7*s**3 + 5970375000*p**8*q*r*s**3 + 68533281250*p**5*q**3*r*s**3 + 142698281250*p**2*q**5*r*s**3 - 74509140625*p**6*q*r**2*s**3 - 389148437500*p**3*q**3*r**2*s**3 - 270937890625*q**5*r**2*s**3 + 366696875000*p**4*q*r**3*s**3 + 400031250000*p*q**3*r**3*s**3 - 735156250000*p**2*q*r**4*s**3 - 262500000000*q*r**5*s**3 + 371250000*p**9*s**4 + 21315000000*p**6*q**2*s**4 + 179515625000*p**3*q**4*s**4 + 238406250000*q**6*s**4 - 9071015625*p**7*r*s**4 - 268945312500*p**4*q**2*r*s**4 - 379785156250*p*q**4*r*s**4 + 140262890625*p**5*r**2*s**4 + 1486259765625*p**2*q**2*r**2*s**4 - 806484375000*p**3*r**3*s**4 + 1066210937500*q**2*r**3*s**4 + 1722656250000*p*r**4*s**4 - 125648437500*p**5*q*s**5 - 1236279296875*p**2*q**3*s**5 + 1267871093750*p**3*q*r*s**5 - 1044677734375*q**3*r*s**5 - 6630859375000*p*q*r**2*s**5 + 160888671875*p**4*s**6 + 6352294921875*p*q**2*s**6 - 708740234375*p**2*r*s**6 + 3901367187500*r**2*s**6 - 8050537109375*q*s**7
+
+ o[1] = 2800*p**8*q**6 + 41300*p**5*q**8 + 151200*p**2*q**10 - 25200*p**9*q**4*r - 542600*p**6*q**6*r - 3397875*p**3*q**8*r - 5751000*q**10*r + 56700*p**10*q**2*r**2 + 1972125*p**7*q**4*r**2 + 18624250*p**4*q**6*r**2 + 50253750*p*q**8*r**2 - 1701000*p**8*q**2*r**3 - 32630625*p**5*q**4*r**3 - 139868750*p**2*q**6*r**3 + 18162500*p**6*q**2*r**4 + 177125000*p**3*q**4*r**4 + 121734375*q**6*r**4 - 100500000*p**4*q**2*r**5 - 386250000*p*q**4*r**5 + 225000000*p**2*q**2*r**6 + 75600*p**10*q**3*s + 1708800*p**7*q**5*s + 12836875*p**4*q**7*s + 32062500*p*q**9*s - 340200*p**11*q*r*s - 10185750*p**8*q**3*r*s - 97502750*p**5*q**5*r*s - 301640625*p**2*q**7*r*s + 7168500*p**9*q*r**2*s + 135960625*p**6*q**3*r**2*s + 587471875*p**3*q**5*r**2*s - 384750000*q**7*r**2*s - 29325000*p**7*q*r**3*s - 320625000*p**4*q**3*r**3*s + 523437500*p*q**5*r**3*s - 42000000*p**5*q*r**4*s + 343750000*p**2*q**3*r**4*s + 150000000*p**3*q*r**5*s - 2250000000*q**3*r**5*s + 510300*p**12*s**2 + 12808125*p**9*q**2*s**2 + 107062500*p**6*q**4*s**2 + 270312500*p**3*q**6*s**2 - 168750000*q**8*s**2 - 2551500*p**10*r*s**2 - 5062500*p**7*q**2*r*s**2 + 712343750*p**4*q**4*r*s**2 + 4788281250*p*q**6*r*s**2 - 256837500*p**8*r**2*s**2 - 3574812500*p**5*q**2*r**2*s**2 - 14967968750*p**2*q**4*r**2*s**2 + 4040937500*p**6*r**3*s**2 + 26400000000*p**3*q**2*r**3*s**2 + 17083984375*q**4*r**3*s**2 - 21812500000*p**4*r**4*s**2 - 24375000000*p*q**2*r**4*s**2 + 39375000000*p**2*r**5*s**2 - 127265625*p**5*q**3*s**3 - 680234375*p**2*q**5*s**3 - 2048203125*p**6*q*r*s**3 - 18794531250*p**3*q**3*r*s**3 - 25050000000*q**5*r*s**3 + 26621875000*p**4*q*r**2*s**3 + 37007812500*p*q**3*r**2*s**3 - 105468750000*p**2*q*r**3*s**3 - 56250000000*q*r**4*s**3 + 1124296875*p**7*s**4 + 9251953125*p**4*q**2*s**4 - 8007812500*p*q**4*s**4 - 4004296875*p**5*r*s**4 + 179931640625*p**2*q**2*r*s**4 - 75703125000*p**3*r**2*s**4 + 133447265625*q**2*r**2*s**4 + 363281250000*p*r**3*s**4 - 91552734375*p**3*q*s**5 - 19531250000*q**3*s**5 - 751953125000*p*q*r*s**5 + 157958984375*p**2*s**6 + 748291015625*r*s**6
+
+ o[0] = -14400*p**6*q**6 - 212400*p**3*q**8 - 777600*q**10 + 92100*p**7*q**4*r + 1689675*p**4*q**6*r + 7371000*p*q**8*r - 122850*p**8*q**2*r**2 - 3735250*p**5*q**4*r**2 - 22432500*p**2*q**6*r**2 + 2298750*p**6*q**2*r**3 + 29390625*p**3*q**4*r**3 + 18000000*q**6*r**3 - 17750000*p**4*q**2*r**4 - 62812500*p*q**4*r**4 + 37500000*p**2*q**2*r**5 - 51300*p**8*q**3*s - 768025*p**5*q**5*s - 2801250*p**2*q**7*s - 275400*p**9*q*r*s - 5479875*p**6*q**3*r*s - 35538750*p**3*q**5*r*s - 68850000*q**7*r*s + 12757500*p**7*q*r**2*s + 133640625*p**4*q**3*r**2*s + 222609375*p*q**5*r**2*s - 108500000*p**5*q*r**3*s - 290312500*p**2*q**3*r**3*s + 275000000*p**3*q*r**4*s - 375000000*q**3*r**4*s + 1931850*p**10*s**2 + 40213125*p**7*q**2*s**2 + 253921875*p**4*q**4*s**2 + 464062500*p*q**6*s**2 - 71077500*p**8*r*s**2 - 818746875*p**5*q**2*r*s**2 - 1882265625*p**2*q**4*r*s**2 + 826031250*p**6*r**2*s**2 + 4369687500*p**3*q**2*r**2*s**2 + 3107812500*q**4*r**2*s**2 - 3943750000*p**4*r**3*s**2 - 5000000000*p*q**2*r**3*s**2 + 6562500000*p**2*r**4*s**2 - 295312500*p**6*q*s**3 - 2938906250*p**3*q**3*s**3 - 4848750000*q**5*s**3 + 3791484375*p**4*q*r*s**3 + 7556250000*p*q**3*r*s**3 - 11960937500*p**2*q*r**2*s**3 - 9375000000*q*r**3*s**3 + 1668515625*p**5*s**4 + 20447265625*p**2*q**2*s**4 - 21955078125*p**3*r*s**4 + 18984375000*q**2*r*s**4 + 67382812500*p*r**2*s**4 - 120849609375*p*q*s**5 + 157226562500*s**6
+
+ return o
+
+ @property
+ def a(self):
+ p, q, r, s = self.p, self.q, self.r, self.s
+ a = [0]*6
+
+ a[5] = -100*p**7*q**7 - 2175*p**4*q**9 - 10500*p*q**11 + 1100*p**8*q**5*r + 27975*p**5*q**7*r + 152950*p**2*q**9*r - 4125*p**9*q**3*r**2 - 128875*p**6*q**5*r**2 - 830525*p**3*q**7*r**2 + 59450*q**9*r**2 + 5400*p**10*q*r**3 + 243800*p**7*q**3*r**3 + 2082650*p**4*q**5*r**3 - 333925*p*q**7*r**3 - 139200*p**8*q*r**4 - 2406000*p**5*q**3*r**4 - 122600*p**2*q**5*r**4 + 1254400*p**6*q*r**5 + 3776000*p**3*q**3*r**5 + 1832000*q**5*r**5 - 4736000*p**4*q*r**6 - 6720000*p*q**3*r**6 + 6400000*p**2*q*r**7 - 900*p**9*q**4*s - 37400*p**6*q**6*s - 281625*p**3*q**8*s - 435000*q**10*s + 6750*p**10*q**2*r*s + 322300*p**7*q**4*r*s + 2718575*p**4*q**6*r*s + 4214250*p*q**8*r*s - 16200*p**11*r**2*s - 859275*p**8*q**2*r**2*s - 8925475*p**5*q**4*r**2*s - 14427875*p**2*q**6*r**2*s + 453600*p**9*r**3*s + 10038400*p**6*q**2*r**3*s + 17397500*p**3*q**4*r**3*s - 11333125*q**6*r**3*s - 4451200*p**7*r**4*s - 15850000*p**4*q**2*r**4*s + 34000000*p*q**4*r**4*s + 17984000*p**5*r**5*s - 10000000*p**2*q**2*r**5*s - 25600000*p**3*r**6*s - 8000000*q**2*r**6*s + 6075*p**11*q*s**2 - 83250*p**8*q**3*s**2 - 1282500*p**5*q**5*s**2 - 2862500*p**2*q**7*s**2 + 724275*p**9*q*r*s**2 + 9807250*p**6*q**3*r*s**2 + 28374375*p**3*q**5*r*s**2 + 22212500*q**7*r*s**2 - 8982000*p**7*q*r**2*s**2 - 39600000*p**4*q**3*r**2*s**2 - 61746875*p*q**5*r**2*s**2 - 1010000*p**5*q*r**3*s**2 - 1000000*p**2*q**3*r**3*s**2 + 78000000*p**3*q*r**4*s**2 + 30000000*q**3*r**4*s**2 + 80000000*p*q*r**5*s**2 - 759375*p**10*s**3 - 9787500*p**7*q**2*s**3 - 39062500*p**4*q**4*s**3 - 52343750*p*q**6*s**3 + 12301875*p**8*r*s**3 + 98175000*p**5*q**2*r*s**3 + 225078125*p**2*q**4*r*s**3 - 54900000*p**6*r**2*s**3 - 310000000*p**3*q**2*r**2*s**3 - 7890625*q**4*r**2*s**3 + 51250000*p**4*r**3*s**3 - 420000000*p*q**2*r**3*s**3 + 110000000*p**2*r**4*s**3 - 200000000*r**5*s**3 + 2109375*p**6*q*s**4 - 21093750*p**3*q**3*s**4 - 89843750*q**5*s**4 + 182343750*p**4*q*r*s**4 + 733203125*p*q**3*r*s**4 - 196875000*p**2*q*r**2*s**4 + 1125000000*q*r**3*s**4 - 158203125*p**5*s**5 - 566406250*p**2*q**2*s**5 + 101562500*p**3*r*s**5 - 1669921875*q**2*r*s**5 + 1250000000*p*r**2*s**5 - 1220703125*p*q*s**6 + 6103515625*s**7
+
+ a[4] = 1000*p**5*q**7 + 7250*p**2*q**9 - 10800*p**6*q**5*r - 96900*p**3*q**7*r - 52500*q**9*r + 37400*p**7*q**3*r**2 + 470850*p**4*q**5*r**2 + 640600*p*q**7*r**2 - 39600*p**8*q*r**3 - 983600*p**5*q**3*r**3 - 2848100*p**2*q**5*r**3 + 814400*p**6*q*r**4 + 6076000*p**3*q**3*r**4 + 2308000*q**5*r**4 - 5024000*p**4*q*r**5 - 9680000*p*q**3*r**5 + 9600000*p**2*q*r**6 + 13800*p**7*q**4*s + 94650*p**4*q**6*s - 26500*p*q**8*s - 86400*p**8*q**2*r*s - 816500*p**5*q**4*r*s - 257500*p**2*q**6*r*s + 91800*p**9*r**2*s + 1853700*p**6*q**2*r**2*s + 630000*p**3*q**4*r**2*s - 8971250*q**6*r**2*s - 2071200*p**7*r**3*s - 7240000*p**4*q**2*r**3*s + 29375000*p*q**4*r**3*s + 14416000*p**5*r**4*s - 5200000*p**2*q**2*r**4*s - 30400000*p**3*r**5*s - 12000000*q**2*r**5*s + 64800*p**9*q*s**2 + 567000*p**6*q**3*s**2 + 1655000*p**3*q**5*s**2 + 6987500*q**7*s**2 + 337500*p**7*q*r*s**2 + 8462500*p**4*q**3*r*s**2 - 5812500*p*q**5*r*s**2 - 24930000*p**5*q*r**2*s**2 - 69125000*p**2*q**3*r**2*s**2 + 103500000*p**3*q*r**3*s**2 + 30000000*q**3*r**3*s**2 + 90000000*p*q*r**4*s**2 - 708750*p**8*s**3 - 5400000*p**5*q**2*s**3 + 8906250*p**2*q**4*s**3 + 18562500*p**6*r*s**3 - 625000*p**3*q**2*r*s**3 + 29687500*q**4*r*s**3 - 75000000*p**4*r**2*s**3 - 416250000*p*q**2*r**2*s**3 + 60000000*p**2*r**3*s**3 - 300000000*r**4*s**3 + 71718750*p**4*q*s**4 + 189062500*p*q**3*s**4 + 210937500*p**2*q*r*s**4 + 1187500000*q*r**2*s**4 - 187500000*p**3*s**5 - 800781250*q**2*s**5 - 390625000*p*r*s**5
+
+ a[3] = -500*p**6*q**5 - 6350*p**3*q**7 - 19800*q**9 + 3750*p**7*q**3*r + 65100*p**4*q**5*r + 264950*p*q**7*r - 6750*p**8*q*r**2 - 209050*p**5*q**3*r**2 - 1217250*p**2*q**5*r**2 + 219000*p**6*q*r**3 + 2510000*p**3*q**3*r**3 + 1098500*q**5*r**3 - 2068000*p**4*q*r**4 - 5060000*p*q**3*r**4 + 5200000*p**2*q*r**5 - 6750*p**8*q**2*s - 96350*p**5*q**4*s - 346000*p**2*q**6*s + 20250*p**9*r*s + 459900*p**6*q**2*r*s + 1828750*p**3*q**4*r*s - 2930000*q**6*r*s - 594000*p**7*r**2*s - 4301250*p**4*q**2*r**2*s + 10906250*p*q**4*r**2*s + 5252000*p**5*r**3*s - 1450000*p**2*q**2*r**3*s - 12800000*p**3*r**4*s - 6500000*q**2*r**4*s + 74250*p**7*q*s**2 + 1418750*p**4*q**3*s**2 + 5956250*p*q**5*s**2 - 4297500*p**5*q*r*s**2 - 29906250*p**2*q**3*r*s**2 + 31500000*p**3*q*r**2*s**2 + 12500000*q**3*r**2*s**2 + 35000000*p*q*r**3*s**2 + 1350000*p**6*s**3 + 6093750*p**3*q**2*s**3 + 17500000*q**4*s**3 - 7031250*p**4*r*s**3 - 127812500*p*q**2*r*s**3 + 18750000*p**2*r**2*s**3 - 162500000*r**3*s**3 + 107812500*p**2*q*s**4 + 460937500*q*r*s**4 - 214843750*p*s**5
+
+ a[2] = 1950*p**4*q**5 + 14100*p*q**7 - 14350*p**5*q**3*r - 125600*p**2*q**5*r + 27900*p**6*q*r**2 + 402250*p**3*q**3*r**2 + 288250*q**5*r**2 - 436000*p**4*q*r**3 - 1345000*p*q**3*r**3 + 1400000*p**2*q*r**4 + 9450*p**6*q**2*s - 1250*p**3*q**4*s - 465000*q**6*s - 49950*p**7*r*s - 302500*p**4*q**2*r*s + 1718750*p*q**4*r*s + 834000*p**5*r**2*s + 437500*p**2*q**2*r**2*s - 3100000*p**3*r**3*s - 1750000*q**2*r**3*s - 292500*p**5*q*s**2 - 1937500*p**2*q**3*s**2 + 3343750*p**3*q*r*s**2 + 1875000*q**3*r*s**2 + 8125000*p*q*r**2*s**2 - 1406250*p**4*s**3 - 12343750*p*q**2*s**3 + 5312500*p**2*r*s**3 - 43750000*r**2*s**3 + 74218750*q*s**4
+
+ a[1] = -300*p**5*q**3 - 2150*p**2*q**5 + 1350*p**6*q*r + 21500*p**3*q**3*r + 61500*q**5*r - 42000*p**4*q*r**2 - 290000*p*q**3*r**2 + 300000*p**2*q*r**3 - 4050*p**7*s - 45000*p**4*q**2*s - 125000*p*q**4*s + 108000*p**5*r*s + 643750*p**2*q**2*r*s - 700000*p**3*r**2*s - 375000*q**2*r**2*s - 93750*p**3*q*s**2 - 312500*q**3*s**2 + 1875000*p*q*r*s**2 - 1406250*p**2*s**3 - 9375000*r*s**3
+
+ a[0] = 1250*p**3*q**3 + 9000*q**5 - 4500*p**4*q*r - 46250*p*q**3*r + 50000*p**2*q*r**2 + 6750*p**5*s + 43750*p**2*q**2*s - 75000*p**3*r*s - 62500*q**2*r*s + 156250*p*q*s**2 - 1562500*s**3
+
+ return a
+
+ @property
+ def c(self):
+ p, q, r, s = self.p, self.q, self.r, self.s
+ c = [0]*6
+
+ c[5] = -40*p**5*q**11 - 270*p**2*q**13 + 700*p**6*q**9*r + 5165*p**3*q**11*r + 540*q**13*r - 4230*p**7*q**7*r**2 - 31845*p**4*q**9*r**2 + 20880*p*q**11*r**2 + 9645*p**8*q**5*r**3 + 57615*p**5*q**7*r**3 - 358255*p**2*q**9*r**3 - 1880*p**9*q**3*r**4 + 114020*p**6*q**5*r**4 + 2012190*p**3*q**7*r**4 - 26855*q**9*r**4 - 14400*p**10*q*r**5 - 470400*p**7*q**3*r**5 - 5088640*p**4*q**5*r**5 + 920*p*q**7*r**5 + 332800*p**8*q*r**6 + 5797120*p**5*q**3*r**6 + 1608000*p**2*q**5*r**6 - 2611200*p**6*q*r**7 - 7424000*p**3*q**3*r**7 - 2323200*q**5*r**7 + 8601600*p**4*q*r**8 + 9472000*p*q**3*r**8 - 10240000*p**2*q*r**9 - 3060*p**7*q**8*s - 39085*p**4*q**10*s - 132300*p*q**12*s + 36580*p**8*q**6*r*s + 520185*p**5*q**8*r*s + 1969860*p**2*q**10*r*s - 144045*p**9*q**4*r**2*s - 2438425*p**6*q**6*r**2*s - 10809475*p**3*q**8*r**2*s + 518850*q**10*r**2*s + 182520*p**10*q**2*r**3*s + 4533930*p**7*q**4*r**3*s + 26196770*p**4*q**6*r**3*s - 4542325*p*q**8*r**3*s + 21600*p**11*r**4*s - 2208080*p**8*q**2*r**4*s - 24787960*p**5*q**4*r**4*s + 10813900*p**2*q**6*r**4*s - 499200*p**9*r**5*s + 3827840*p**6*q**2*r**5*s + 9596000*p**3*q**4*r**5*s + 22662000*q**6*r**5*s + 3916800*p**7*r**6*s - 29952000*p**4*q**2*r**6*s - 90800000*p*q**4*r**6*s - 12902400*p**5*r**7*s + 87040000*p**2*q**2*r**7*s + 15360000*p**3*r**8*s + 12800000*q**2*r**8*s - 38070*p**9*q**5*s**2 - 566700*p**6*q**7*s**2 - 2574375*p**3*q**9*s**2 - 1822500*q**11*s**2 + 292815*p**10*q**3*r*s**2 + 5170280*p**7*q**5*r*s**2 + 27918125*p**4*q**7*r*s**2 + 21997500*p*q**9*r*s**2 - 573480*p**11*q*r**2*s**2 - 14566350*p**8*q**3*r**2*s**2 - 104851575*p**5*q**5*r**2*s**2 - 96448750*p**2*q**7*r**2*s**2 + 11001240*p**9*q*r**3*s**2 + 147798600*p**6*q**3*r**3*s**2 + 158632750*p**3*q**5*r**3*s**2 - 78222500*q**7*r**3*s**2 - 62819200*p**7*q*r**4*s**2 - 136160000*p**4*q**3*r**4*s**2 + 317555000*p*q**5*r**4*s**2 + 160224000*p**5*q*r**5*s**2 - 267600000*p**2*q**3*r**5*s**2 - 153600000*p**3*q*r**6*s**2 - 120000000*q**3*r**6*s**2 - 32000000*p*q*r**7*s**2 - 127575*p**11*q**2*s**3 - 2148750*p**8*q**4*s**3 - 13652500*p**5*q**6*s**3 - 19531250*p**2*q**8*s**3 + 495720*p**12*r*s**3 + 11856375*p**9*q**2*r*s**3 + 107807500*p**6*q**4*r*s**3 + 222334375*p**3*q**6*r*s**3 + 105062500*q**8*r*s**3 - 11566800*p**10*r**2*s**3 - 216787500*p**7*q**2*r**2*s**3 - 633437500*p**4*q**4*r**2*s**3 - 504484375*p*q**6*r**2*s**3 + 90918000*p**8*r**3*s**3 + 567080000*p**5*q**2*r**3*s**3 + 692937500*p**2*q**4*r**3*s**3 - 326640000*p**6*r**4*s**3 - 339000000*p**3*q**2*r**4*s**3 + 369250000*q**4*r**4*s**3 + 560000000*p**4*r**5*s**3 + 508000000*p*q**2*r**5*s**3 - 480000000*p**2*r**6*s**3 + 320000000*r**7*s**3 - 455625*p**10*q*s**4 - 27562500*p**7*q**3*s**4 - 120593750*p**4*q**5*s**4 - 60312500*p*q**7*s**4 + 110615625*p**8*q*r*s**4 + 662984375*p**5*q**3*r*s**4 + 528515625*p**2*q**5*r*s**4 - 541687500*p**6*q*r**2*s**4 - 1262343750*p**3*q**3*r**2*s**4 - 466406250*q**5*r**2*s**4 + 633000000*p**4*q*r**3*s**4 - 1264375000*p*q**3*r**3*s**4 + 1085000000*p**2*q*r**4*s**4 - 2700000000*q*r**5*s**4 - 68343750*p**9*s**5 - 478828125*p**6*q**2*s**5 - 355468750*p**3*q**4*s**5 - 11718750*q**6*s**5 + 718031250*p**7*r*s**5 + 1658593750*p**4*q**2*r*s**5 + 2212890625*p*q**4*r*s**5 - 2855625000*p**5*r**2*s**5 - 4273437500*p**2*q**2*r**2*s**5 + 4537500000*p**3*r**3*s**5 + 8031250000*q**2*r**3*s**5 - 1750000000*p*r**4*s**5 + 1353515625*p**5*q*s**6 + 1562500000*p**2*q**3*s**6 - 3964843750*p**3*q*r*s**6 - 7226562500*q**3*r*s**6 + 1953125000*p*q*r**2*s**6 - 1757812500*p**4*s**7 - 3173828125*p*q**2*s**7 + 6445312500*p**2*r*s**7 - 3906250000*r**2*s**7 + 6103515625*q*s**8
+
+ c[4] = 40*p**6*q**9 + 110*p**3*q**11 - 1080*q**13 - 560*p**7*q**7*r - 1780*p**4*q**9*r + 17370*p*q**11*r + 2850*p**8*q**5*r**2 + 10520*p**5*q**7*r**2 - 115910*p**2*q**9*r**2 - 6090*p**9*q**3*r**3 - 25330*p**6*q**5*r**3 + 448740*p**3*q**7*r**3 + 128230*q**9*r**3 + 4320*p**10*q*r**4 + 16960*p**7*q**3*r**4 - 1143600*p**4*q**5*r**4 - 1410310*p*q**7*r**4 + 3840*p**8*q*r**5 + 1744480*p**5*q**3*r**5 + 5619520*p**2*q**5*r**5 - 1198080*p**6*q*r**6 - 10579200*p**3*q**3*r**6 - 2940800*q**5*r**6 + 8294400*p**4*q*r**7 + 13568000*p*q**3*r**7 - 15360000*p**2*q*r**8 + 840*p**8*q**6*s + 7580*p**5*q**8*s + 24420*p**2*q**10*s - 8100*p**9*q**4*r*s - 94100*p**6*q**6*r*s - 473000*p**3*q**8*r*s - 473400*q**10*r*s + 22680*p**10*q**2*r**2*s + 374370*p**7*q**4*r**2*s + 2888020*p**4*q**6*r**2*s + 5561050*p*q**8*r**2*s - 12960*p**11*r**3*s - 485820*p**8*q**2*r**3*s - 6723440*p**5*q**4*r**3*s - 23561400*p**2*q**6*r**3*s + 190080*p**9*r**4*s + 5894880*p**6*q**2*r**4*s + 50882000*p**3*q**4*r**4*s + 22411500*q**6*r**4*s - 258560*p**7*r**5*s - 46248000*p**4*q**2*r**5*s - 103800000*p*q**4*r**5*s - 3737600*p**5*r**6*s + 119680000*p**2*q**2*r**6*s + 10240000*p**3*r**7*s + 19200000*q**2*r**7*s + 7290*p**10*q**3*s**2 + 117360*p**7*q**5*s**2 + 691250*p**4*q**7*s**2 - 198750*p*q**9*s**2 - 36450*p**11*q*r*s**2 - 854550*p**8*q**3*r*s**2 - 7340700*p**5*q**5*r*s**2 - 2028750*p**2*q**7*r*s**2 + 995490*p**9*q*r**2*s**2 + 18896600*p**6*q**3*r**2*s**2 + 5026500*p**3*q**5*r**2*s**2 - 52272500*q**7*r**2*s**2 - 16636800*p**7*q*r**3*s**2 - 43200000*p**4*q**3*r**3*s**2 + 223426250*p*q**5*r**3*s**2 + 112068000*p**5*q*r**4*s**2 - 177000000*p**2*q**3*r**4*s**2 - 244000000*p**3*q*r**5*s**2 - 156000000*q**3*r**5*s**2 + 43740*p**12*s**3 + 1032750*p**9*q**2*s**3 + 8602500*p**6*q**4*s**3 + 15606250*p**3*q**6*s**3 + 39625000*q**8*s**3 - 1603800*p**10*r*s**3 - 26932500*p**7*q**2*r*s**3 - 19562500*p**4*q**4*r*s**3 - 152000000*p*q**6*r*s**3 + 25555500*p**8*r**2*s**3 + 16230000*p**5*q**2*r**2*s**3 + 42187500*p**2*q**4*r**2*s**3 - 165660000*p**6*r**3*s**3 + 373500000*p**3*q**2*r**3*s**3 + 332937500*q**4*r**3*s**3 + 465000000*p**4*r**4*s**3 + 586000000*p*q**2*r**4*s**3 - 592000000*p**2*r**5*s**3 + 480000000*r**6*s**3 - 1518750*p**8*q*s**4 - 62531250*p**5*q**3*s**4 + 7656250*p**2*q**5*s**4 + 184781250*p**6*q*r*s**4 - 15781250*p**3*q**3*r*s**4 - 135156250*q**5*r*s**4 - 1148250000*p**4*q*r**2*s**4 - 2121406250*p*q**3*r**2*s**4 + 1990000000*p**2*q*r**3*s**4 - 3150000000*q*r**4*s**4 - 2531250*p**7*s**5 + 660937500*p**4*q**2*s**5 + 1339843750*p*q**4*s**5 - 33750000*p**5*r*s**5 - 679687500*p**2*q**2*r*s**5 + 6250000*p**3*r**2*s**5 + 6195312500*q**2*r**2*s**5 + 1125000000*p*r**3*s**5 - 996093750*p**3*q*s**6 - 3125000000*q**3*s**6 - 3222656250*p*q*r*s**6 + 1171875000*p**2*s**7 + 976562500*r*s**7
+
+ c[3] = 80*p**4*q**9 + 540*p*q**11 - 600*p**5*q**7*r - 4770*p**2*q**9*r + 1230*p**6*q**5*r**2 + 20900*p**3*q**7*r**2 + 47250*q**9*r**2 - 710*p**7*q**3*r**3 - 84950*p**4*q**5*r**3 - 526310*p*q**7*r**3 + 720*p**8*q*r**4 + 216280*p**5*q**3*r**4 + 2068020*p**2*q**5*r**4 - 198080*p**6*q*r**5 - 3703200*p**3*q**3*r**5 - 1423600*q**5*r**5 + 2860800*p**4*q*r**6 + 7056000*p*q**3*r**6 - 8320000*p**2*q*r**7 - 2720*p**6*q**6*s - 46350*p**3*q**8*s - 178200*q**10*s + 25740*p**7*q**4*r*s + 489490*p**4*q**6*r*s + 2152350*p*q**8*r*s - 61560*p**8*q**2*r**2*s - 1568150*p**5*q**4*r**2*s - 9060500*p**2*q**6*r**2*s + 24840*p**9*r**3*s + 1692380*p**6*q**2*r**3*s + 18098250*p**3*q**4*r**3*s + 9387750*q**6*r**3*s - 382560*p**7*r**4*s - 16818000*p**4*q**2*r**4*s - 49325000*p*q**4*r**4*s + 1212800*p**5*r**5*s + 64840000*p**2*q**2*r**5*s - 320000*p**3*r**6*s + 10400000*q**2*r**6*s - 36450*p**8*q**3*s**2 - 588350*p**5*q**5*s**2 - 2156250*p**2*q**7*s**2 + 123930*p**9*q*r*s**2 + 2879700*p**6*q**3*r*s**2 + 12548000*p**3*q**5*r*s**2 - 14445000*q**7*r*s**2 - 3233250*p**7*q*r**2*s**2 - 28485000*p**4*q**3*r**2*s**2 + 72231250*p*q**5*r**2*s**2 + 32093000*p**5*q*r**3*s**2 - 61275000*p**2*q**3*r**3*s**2 - 107500000*p**3*q*r**4*s**2 - 78500000*q**3*r**4*s**2 + 22000000*p*q*r**5*s**2 - 72900*p**10*s**3 - 1215000*p**7*q**2*s**3 - 2937500*p**4*q**4*s**3 + 9156250*p*q**6*s**3 + 2612250*p**8*r*s**3 + 16560000*p**5*q**2*r*s**3 - 75468750*p**2*q**4*r*s**3 - 32737500*p**6*r**2*s**3 + 169062500*p**3*q**2*r**2*s**3 + 121718750*q**4*r**2*s**3 + 160250000*p**4*r**3*s**3 + 219750000*p*q**2*r**3*s**3 - 317000000*p**2*r**4*s**3 + 260000000*r**5*s**3 + 2531250*p**6*q*s**4 + 22500000*p**3*q**3*s**4 + 39843750*q**5*s**4 - 266343750*p**4*q*r*s**4 - 776406250*p*q**3*r*s**4 + 789062500*p**2*q*r**2*s**4 - 1368750000*q*r**3*s**4 + 67500000*p**5*s**5 + 441406250*p**2*q**2*s**5 - 311718750*p**3*r*s**5 + 1785156250*q**2*r*s**5 + 546875000*p*r**2*s**5 - 1269531250*p*q*s**6 + 488281250*s**7
+
+ c[2] = 120*p**5*q**7 + 810*p**2*q**9 - 1280*p**6*q**5*r - 9160*p**3*q**7*r + 3780*q**9*r + 4530*p**7*q**3*r**2 + 36640*p**4*q**5*r**2 - 45270*p*q**7*r**2 - 5400*p**8*q*r**3 - 60920*p**5*q**3*r**3 + 200050*p**2*q**5*r**3 + 31200*p**6*q*r**4 - 476000*p**3*q**3*r**4 - 378200*q**5*r**4 + 521600*p**4*q*r**5 + 1872000*p*q**3*r**5 - 2240000*p**2*q*r**6 + 1440*p**7*q**4*s + 15310*p**4*q**6*s + 59400*p*q**8*s - 9180*p**8*q**2*r*s - 115240*p**5*q**4*r*s - 589650*p**2*q**6*r*s + 16200*p**9*r**2*s + 316710*p**6*q**2*r**2*s + 2547750*p**3*q**4*r**2*s + 2178000*q**6*r**2*s - 259200*p**7*r**3*s - 4123000*p**4*q**2*r**3*s - 11700000*p*q**4*r**3*s + 937600*p**5*r**4*s + 16340000*p**2*q**2*r**4*s - 640000*p**3*r**5*s + 2800000*q**2*r**5*s - 2430*p**9*q*s**2 - 54450*p**6*q**3*s**2 - 285500*p**3*q**5*s**2 - 2767500*q**7*s**2 + 43200*p**7*q*r*s**2 - 916250*p**4*q**3*r*s**2 + 14482500*p*q**5*r*s**2 + 4806000*p**5*q*r**2*s**2 - 13212500*p**2*q**3*r**2*s**2 - 25400000*p**3*q*r**3*s**2 - 18750000*q**3*r**3*s**2 + 8000000*p*q*r**4*s**2 + 121500*p**8*s**3 + 2058750*p**5*q**2*s**3 - 6656250*p**2*q**4*s**3 - 6716250*p**6*r*s**3 + 24125000*p**3*q**2*r*s**3 + 23875000*q**4*r*s**3 + 43125000*p**4*r**2*s**3 + 45750000*p*q**2*r**2*s**3 - 87500000*p**2*r**3*s**3 + 70000000*r**4*s**3 - 44437500*p**4*q*s**4 - 107968750*p*q**3*s**4 + 159531250*p**2*q*r*s**4 - 284375000*q*r**2*s**4 + 7031250*p**3*s**5 + 265625000*q**2*s**5 + 31250000*p*r*s**5
+
+ c[1] = 160*p**3*q**7 + 1080*q**9 - 1080*p**4*q**5*r - 8730*p*q**7*r + 1510*p**5*q**3*r**2 + 20420*p**2*q**5*r**2 + 720*p**6*q*r**3 - 23200*p**3*q**3*r**3 - 79900*q**5*r**3 + 35200*p**4*q*r**4 + 404000*p*q**3*r**4 - 480000*p**2*q*r**5 + 960*p**5*q**4*s + 2850*p**2*q**6*s + 540*p**6*q**2*r*s + 63500*p**3*q**4*r*s + 319500*q**6*r*s - 7560*p**7*r**2*s - 253500*p**4*q**2*r**2*s - 1806250*p*q**4*r**2*s + 91200*p**5*r**3*s + 2600000*p**2*q**2*r**3*s - 80000*p**3*r**4*s + 600000*q**2*r**4*s - 4050*p**7*q*s**2 - 120000*p**4*q**3*s**2 - 273750*p*q**5*s**2 + 425250*p**5*q*r*s**2 + 2325000*p**2*q**3*r*s**2 - 5400000*p**3*q*r**2*s**2 - 2875000*q**3*r**2*s**2 + 1500000*p*q*r**3*s**2 - 303750*p**6*s**3 - 843750*p**3*q**2*s**3 - 812500*q**4*s**3 + 5062500*p**4*r*s**3 + 13312500*p*q**2*r*s**3 - 14500000*p**2*r**2*s**3 + 15000000*r**3*s**3 - 3750000*p**2*q*s**4 - 35937500*q*r*s**4 + 11718750*p*s**5
+
+ c[0] = 80*p**4*q**5 + 540*p*q**7 - 600*p**5*q**3*r - 4770*p**2*q**5*r + 1080*p**6*q*r**2 + 11200*p**3*q**3*r**2 - 12150*q**5*r**2 - 4800*p**4*q*r**3 + 64000*p*q**3*r**3 - 80000*p**2*q*r**4 + 1080*p**6*q**2*s + 13250*p**3*q**4*s + 54000*q**6*s - 3240*p**7*r*s - 56250*p**4*q**2*r*s - 337500*p*q**4*r*s + 43200*p**5*r**2*s + 560000*p**2*q**2*r**2*s - 80000*p**3*r**3*s + 100000*q**2*r**3*s + 6750*p**5*q*s**2 + 225000*p**2*q**3*s**2 - 900000*p**3*q*r*s**2 - 562500*q**3*r*s**2 + 500000*p*q*r**2*s**2 + 843750*p**4*s**3 + 1937500*p*q**2*s**3 - 3000000*p**2*r*s**3 + 2500000*r**2*s**3 - 5468750*q*s**4
+
+ return c
+
+ @property
+ def F(self):
+ p, q, r, s = self.p, self.q, self.r, self.s
+ F = 4*p**6*q**6 + 59*p**3*q**8 + 216*q**10 - 36*p**7*q**4*r - 623*p**4*q**6*r - 2610*p*q**8*r + 81*p**8*q**2*r**2 + 2015*p**5*q**4*r**2 + 10825*p**2*q**6*r**2 - 1800*p**6*q**2*r**3 - 17500*p**3*q**4*r**3 + 625*q**6*r**3 + 10000*p**4*q**2*r**4 + 108*p**8*q**3*s + 1584*p**5*q**5*s + 5700*p**2*q**7*s - 486*p**9*q*r*s - 9720*p**6*q**3*r*s - 45050*p**3*q**5*r*s - 9000*q**7*r*s + 10800*p**7*q*r**2*s + 92500*p**4*q**3*r**2*s + 32500*p*q**5*r**2*s - 60000*p**5*q*r**3*s - 50000*p**2*q**3*r**3*s + 729*p**10*s**2 + 12150*p**7*q**2*s**2 + 60000*p**4*q**4*s**2 + 93750*p*q**6*s**2 - 18225*p**8*r*s**2 - 175500*p**5*q**2*r*s**2 - 478125*p**2*q**4*r*s**2 + 135000*p**6*r**2*s**2 + 850000*p**3*q**2*r**2*s**2 + 15625*q**4*r**2*s**2 - 250000*p**4*r**3*s**2 + 225000*p**3*q**3*s**3 + 175000*q**5*s**3 - 1012500*p**4*q*r*s**3 - 1187500*p*q**3*r*s**3 + 1250000*p**2*q*r**2*s**3 + 928125*p**5*s**4 + 1875000*p**2*q**2*s**4 - 2812500*p**3*r*s**4 - 390625*q**2*r*s**4 - 9765625*s**6
+ return F
+
+ def l0(self, theta):
+ F = self.F
+ a = self.a
+ l0 = Poly(a, x).eval(theta)/F
+ return l0
+
+ def T(self, theta, d):
+ F = self.F
+ T = [0]*5
+ b = self.b
+ # Note that the order of sublists of the b's has been reversed compared to the paper
+ T[1] = -Poly(b[1], x).eval(theta)/(2*F)
+ T[2] = Poly(b[2], x).eval(theta)/(2*d*F)
+ T[3] = Poly(b[3], x).eval(theta)/(2*F)
+ T[4] = Poly(b[4], x).eval(theta)/(2*d*F)
+ return T
+
+ def order(self, theta, d):
+ F = self.F
+ o = self.o
+ order = Poly(o, x).eval(theta)/(d*F)
+ return N(order)
+
+ def uv(self, theta, d):
+ c = self.c
+ u = self.q*Rational(-25, 2)
+ v = Poly(c, x).eval(theta)/(2*d*self.F)
+ return N(u), N(v)
+
+ @property
+ def zeta(self):
+ return [self.zeta1, self.zeta2, self.zeta3, self.zeta4]
diff --git a/lib/python3.12/site-packages/sympy/polys/polyroots.py b/lib/python3.12/site-packages/sympy/polys/polyroots.py
new file mode 100644
index 0000000000000000000000000000000000000000..4def1312eb5b94a13e511d2d4f9b15f1d51fd63f
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/polyroots.py
@@ -0,0 +1,1227 @@
+"""Algorithms for computing symbolic roots of polynomials. """
+
+
+import math
+from functools import reduce
+
+from sympy.core import S, I, pi
+from sympy.core.exprtools import factor_terms
+from sympy.core.function import _mexpand
+from sympy.core.logic import fuzzy_not
+from sympy.core.mul import expand_2arg, Mul
+from sympy.core.intfunc import igcd
+from sympy.core.numbers import Rational, comp
+from sympy.core.power import Pow
+from sympy.core.relational import Eq
+from sympy.core.sorting import ordered
+from sympy.core.symbol import Dummy, Symbol, symbols
+from sympy.core.sympify import sympify
+from sympy.functions import exp, im, cos, acos, Piecewise
+from sympy.functions.elementary.miscellaneous import root, sqrt
+from sympy.ntheory import divisors, isprime, nextprime
+from sympy.polys.domains import EX
+from sympy.polys.polyerrors import (PolynomialError, GeneratorsNeeded,
+ DomainError, UnsolvableFactorError)
+from sympy.polys.polyquinticconst import PolyQuintic
+from sympy.polys.polytools import Poly, cancel, factor, gcd_list, discriminant
+from sympy.polys.rationaltools import together
+from sympy.polys.specialpolys import cyclotomic_poly
+from sympy.utilities import public
+from sympy.utilities.misc import filldedent
+
+
+
+z = Symbol('z') # importing from abc cause O to be lost as clashing symbol
+
+
+def roots_linear(f):
+ """Returns a list of roots of a linear polynomial."""
+ r = -f.nth(0)/f.nth(1)
+ dom = f.get_domain()
+
+ if not dom.is_Numerical:
+ if dom.is_Composite:
+ r = factor(r)
+ else:
+ from sympy.simplify.simplify import simplify
+ r = simplify(r)
+
+ return [r]
+
+
+def roots_quadratic(f):
+ """Returns a list of roots of a quadratic polynomial. If the domain is ZZ
+ then the roots will be sorted with negatives coming before positives.
+ The ordering will be the same for any numerical coefficients as long as
+ the assumptions tested are correct, otherwise the ordering will not be
+ sorted (but will be canonical).
+ """
+
+ a, b, c = f.all_coeffs()
+ dom = f.get_domain()
+
+ def _sqrt(d):
+ # remove squares from square root since both will be represented
+ # in the results; a similar thing is happening in roots() but
+ # must be duplicated here because not all quadratics are binomials
+ co = []
+ other = []
+ for di in Mul.make_args(d):
+ if di.is_Pow and di.exp.is_Integer and di.exp % 2 == 0:
+ co.append(Pow(di.base, di.exp//2))
+ else:
+ other.append(di)
+ if co:
+ d = Mul(*other)
+ co = Mul(*co)
+ return co*sqrt(d)
+ return sqrt(d)
+
+ def _simplify(expr):
+ if dom.is_Composite:
+ return factor(expr)
+ else:
+ from sympy.simplify.simplify import simplify
+ return simplify(expr)
+
+ if c is S.Zero:
+ r0, r1 = S.Zero, -b/a
+
+ if not dom.is_Numerical:
+ r1 = _simplify(r1)
+ elif r1.is_negative:
+ r0, r1 = r1, r0
+ elif b is S.Zero:
+ r = -c/a
+ if not dom.is_Numerical:
+ r = _simplify(r)
+
+ R = _sqrt(r)
+ r0 = -R
+ r1 = R
+ else:
+ d = b**2 - 4*a*c
+ A = 2*a
+ B = -b/A
+
+ if not dom.is_Numerical:
+ d = _simplify(d)
+ B = _simplify(B)
+
+ D = factor_terms(_sqrt(d)/A)
+ r0 = B - D
+ r1 = B + D
+ if a.is_negative:
+ r0, r1 = r1, r0
+ elif not dom.is_Numerical:
+ r0, r1 = [expand_2arg(i) for i in (r0, r1)]
+
+ return [r0, r1]
+
+
+def roots_cubic(f, trig=False):
+ """Returns a list of roots of a cubic polynomial.
+
+ References
+ ==========
+ [1] https://en.wikipedia.org/wiki/Cubic_function, General formula for roots,
+ (accessed November 17, 2014).
+ """
+ if trig:
+ a, b, c, d = f.all_coeffs()
+ p = (3*a*c - b**2)/(3*a**2)
+ q = (2*b**3 - 9*a*b*c + 27*a**2*d)/(27*a**3)
+ D = 18*a*b*c*d - 4*b**3*d + b**2*c**2 - 4*a*c**3 - 27*a**2*d**2
+ if (D > 0) == True:
+ rv = []
+ for k in range(3):
+ rv.append(2*sqrt(-p/3)*cos(acos(q/p*sqrt(-3/p)*Rational(3, 2))/3 - k*pi*Rational(2, 3)))
+ return [i - b/3/a for i in rv]
+
+ # a*x**3 + b*x**2 + c*x + d -> x**3 + a*x**2 + b*x + c
+ _, a, b, c = f.monic().all_coeffs()
+
+ if c is S.Zero:
+ x1, x2 = roots([1, a, b], multiple=True)
+ return [x1, S.Zero, x2]
+
+ # x**3 + a*x**2 + b*x + c -> u**3 + p*u + q
+ p = b - a**2/3
+ q = c - a*b/3 + 2*a**3/27
+
+ pon3 = p/3
+ aon3 = a/3
+
+ u1 = None
+ if p is S.Zero:
+ if q is S.Zero:
+ return [-aon3]*3
+ u1 = -root(q, 3) if q.is_positive else root(-q, 3)
+ elif q is S.Zero:
+ y1, y2 = roots([1, 0, p], multiple=True)
+ return [tmp - aon3 for tmp in [y1, S.Zero, y2]]
+ elif q.is_real and q.is_negative:
+ u1 = -root(-q/2 + sqrt(q**2/4 + pon3**3), 3)
+
+ coeff = I*sqrt(3)/2
+ if u1 is None:
+ u1 = S.One
+ u2 = Rational(-1, 2) + coeff
+ u3 = Rational(-1, 2) - coeff
+ b, c, d = a, b, c # a, b, c, d = S.One, a, b, c
+ D0 = b**2 - 3*c # b**2 - 3*a*c
+ D1 = 2*b**3 - 9*b*c + 27*d # 2*b**3 - 9*a*b*c + 27*a**2*d
+ C = root((D1 + sqrt(D1**2 - 4*D0**3))/2, 3)
+ return [-(b + uk*C + D0/C/uk)/3 for uk in [u1, u2, u3]] # -(b + uk*C + D0/C/uk)/3/a
+
+ u2 = u1*(Rational(-1, 2) + coeff)
+ u3 = u1*(Rational(-1, 2) - coeff)
+
+ if p is S.Zero:
+ return [u1 - aon3, u2 - aon3, u3 - aon3]
+
+ soln = [
+ -u1 + pon3/u1 - aon3,
+ -u2 + pon3/u2 - aon3,
+ -u3 + pon3/u3 - aon3
+ ]
+
+ return soln
+
+def _roots_quartic_euler(p, q, r, a):
+ """
+ Descartes-Euler solution of the quartic equation
+
+ Parameters
+ ==========
+
+ p, q, r: coefficients of ``x**4 + p*x**2 + q*x + r``
+ a: shift of the roots
+
+ Notes
+ =====
+
+ This is a helper function for ``roots_quartic``.
+
+ Look for solutions of the form ::
+
+ ``x1 = sqrt(R) - sqrt(A + B*sqrt(R))``
+ ``x2 = -sqrt(R) - sqrt(A - B*sqrt(R))``
+ ``x3 = -sqrt(R) + sqrt(A - B*sqrt(R))``
+ ``x4 = sqrt(R) + sqrt(A + B*sqrt(R))``
+
+ To satisfy the quartic equation one must have
+ ``p = -2*(R + A); q = -4*B*R; r = (R - A)**2 - B**2*R``
+ so that ``R`` must satisfy the Descartes-Euler resolvent equation
+ ``64*R**3 + 32*p*R**2 + (4*p**2 - 16*r)*R - q**2 = 0``
+
+ If the resolvent does not have a rational solution, return None;
+ in that case it is likely that the Ferrari method gives a simpler
+ solution.
+
+ Examples
+ ========
+
+ >>> from sympy import S
+ >>> from sympy.polys.polyroots import _roots_quartic_euler
+ >>> p, q, r = -S(64)/5, -S(512)/125, -S(1024)/3125
+ >>> _roots_quartic_euler(p, q, r, S(0))[0]
+ -sqrt(32*sqrt(5)/125 + 16/5) + 4*sqrt(5)/5
+ """
+ # solve the resolvent equation
+ x = Dummy('x')
+ eq = 64*x**3 + 32*p*x**2 + (4*p**2 - 16*r)*x - q**2
+ xsols = list(roots(Poly(eq, x), cubics=False).keys())
+ xsols = [sol for sol in xsols if sol.is_rational and sol.is_nonzero]
+ if not xsols:
+ return None
+ R = max(xsols)
+ c1 = sqrt(R)
+ B = -q*c1/(4*R)
+ A = -R - p/2
+ c2 = sqrt(A + B)
+ c3 = sqrt(A - B)
+ return [c1 - c2 - a, -c1 - c3 - a, -c1 + c3 - a, c1 + c2 - a]
+
+
+def roots_quartic(f):
+ r"""
+ Returns a list of roots of a quartic polynomial.
+
+ There are many references for solving quartic expressions available [1-5].
+ This reviewer has found that many of them require one to select from among
+ 2 or more possible sets of solutions and that some solutions work when one
+ is searching for real roots but do not work when searching for complex roots
+ (though this is not always stated clearly). The following routine has been
+ tested and found to be correct for 0, 2 or 4 complex roots.
+
+ The quasisymmetric case solution [6] looks for quartics that have the form
+ `x**4 + A*x**3 + B*x**2 + C*x + D = 0` where `(C/A)**2 = D`.
+
+ Although no general solution that is always applicable for all
+ coefficients is known to this reviewer, certain conditions are tested
+ to determine the simplest 4 expressions that can be returned:
+
+ 1) `f = c + a*(a**2/8 - b/2) == 0`
+ 2) `g = d - a*(a*(3*a**2/256 - b/16) + c/4) = 0`
+ 3) if `f != 0` and `g != 0` and `p = -d + a*c/4 - b**2/12` then
+ a) `p == 0`
+ b) `p != 0`
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.polys.polyroots import roots_quartic
+
+ >>> r = roots_quartic(Poly('x**4-6*x**3+17*x**2-26*x+20'))
+
+ >>> # 4 complex roots: 1+-I*sqrt(3), 2+-I
+ >>> sorted(str(tmp.evalf(n=2)) for tmp in r)
+ ['1.0 + 1.7*I', '1.0 - 1.7*I', '2.0 + 1.0*I', '2.0 - 1.0*I']
+
+ References
+ ==========
+
+ 1. http://mathforum.org/dr.math/faq/faq.cubic.equations.html
+ 2. https://en.wikipedia.org/wiki/Quartic_function#Summary_of_Ferrari.27s_method
+ 3. https://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html
+ 4. https://people.bath.ac.uk/masjhd/JHD-CA.pdf
+ 5. http://www.albmath.org/files/Math_5713.pdf
+ 6. https://web.archive.org/web/20171002081448/http://www.statemaster.com/encyclopedia/Quartic-equation
+ 7. https://eqworld.ipmnet.ru/en/solutions/ae/ae0108.pdf
+ """
+ _, a, b, c, d = f.monic().all_coeffs()
+
+ if not d:
+ return [S.Zero] + roots([1, a, b, c], multiple=True)
+ elif (c/a)**2 == d:
+ x, m = f.gen, c/a
+
+ g = Poly(x**2 + a*x + b - 2*m, x)
+
+ z1, z2 = roots_quadratic(g)
+
+ h1 = Poly(x**2 - z1*x + m, x)
+ h2 = Poly(x**2 - z2*x + m, x)
+
+ r1 = roots_quadratic(h1)
+ r2 = roots_quadratic(h2)
+
+ return r1 + r2
+ else:
+ a2 = a**2
+ e = b - 3*a2/8
+ f = _mexpand(c + a*(a2/8 - b/2))
+ aon4 = a/4
+ g = _mexpand(d - aon4*(a*(3*a2/64 - b/4) + c))
+
+ if f.is_zero:
+ y1, y2 = [sqrt(tmp) for tmp in
+ roots([1, e, g], multiple=True)]
+ return [tmp - aon4 for tmp in [-y1, -y2, y1, y2]]
+ if g.is_zero:
+ y = [S.Zero] + roots([1, 0, e, f], multiple=True)
+ return [tmp - aon4 for tmp in y]
+ else:
+ # Descartes-Euler method, see [7]
+ sols = _roots_quartic_euler(e, f, g, aon4)
+ if sols:
+ return sols
+ # Ferrari method, see [1, 2]
+ p = -e**2/12 - g
+ q = -e**3/108 + e*g/3 - f**2/8
+ TH = Rational(1, 3)
+
+ def _ans(y):
+ w = sqrt(e + 2*y)
+ arg1 = 3*e + 2*y
+ arg2 = 2*f/w
+ ans = []
+ for s in [-1, 1]:
+ root = sqrt(-(arg1 + s*arg2))
+ for t in [-1, 1]:
+ ans.append((s*w - t*root)/2 - aon4)
+ return ans
+
+ # whether a Piecewise is returned or not
+ # depends on knowing p, so try to put
+ # in a simple form
+ p = _mexpand(p)
+
+
+ # p == 0 case
+ y1 = e*Rational(-5, 6) - q**TH
+ if p.is_zero:
+ return _ans(y1)
+
+ # if p != 0 then u below is not 0
+ root = sqrt(q**2/4 + p**3/27)
+ r = -q/2 + root # or -q/2 - root
+ u = r**TH # primary root of solve(x**3 - r, x)
+ y2 = e*Rational(-5, 6) + u - p/u/3
+ if fuzzy_not(p.is_zero):
+ return _ans(y2)
+
+ # sort it out once they know the values of the coefficients
+ return [Piecewise((a1, Eq(p, 0)), (a2, True))
+ for a1, a2 in zip(_ans(y1), _ans(y2))]
+
+
+def roots_binomial(f):
+ """Returns a list of roots of a binomial polynomial. If the domain is ZZ
+ then the roots will be sorted with negatives coming before positives.
+ The ordering will be the same for any numerical coefficients as long as
+ the assumptions tested are correct, otherwise the ordering will not be
+ sorted (but will be canonical).
+ """
+ n = f.degree()
+
+ a, b = f.nth(n), f.nth(0)
+ base = -cancel(b/a)
+ alpha = root(base, n)
+
+ if alpha.is_number:
+ alpha = alpha.expand(complex=True)
+
+ # define some parameters that will allow us to order the roots.
+ # If the domain is ZZ this is guaranteed to return roots sorted
+ # with reals before non-real roots and non-real sorted according
+ # to real part and imaginary part, e.g. -1, 1, -1 + I, 2 - I
+ neg = base.is_negative
+ even = n % 2 == 0
+ if neg:
+ if even == True and (base + 1).is_positive:
+ big = True
+ else:
+ big = False
+
+ # get the indices in the right order so the computed
+ # roots will be sorted when the domain is ZZ
+ ks = []
+ imax = n//2
+ if even:
+ ks.append(imax)
+ imax -= 1
+ if not neg:
+ ks.append(0)
+ for i in range(imax, 0, -1):
+ if neg:
+ ks.extend([i, -i])
+ else:
+ ks.extend([-i, i])
+ if neg:
+ ks.append(0)
+ if big:
+ for i in range(0, len(ks), 2):
+ pair = ks[i: i + 2]
+ pair = list(reversed(pair))
+
+ # compute the roots
+ roots, d = [], 2*I*pi/n
+ for k in ks:
+ zeta = exp(k*d).expand(complex=True)
+ roots.append((alpha*zeta).expand(power_base=False))
+
+ return roots
+
+
+def _inv_totient_estimate(m):
+ """
+ Find ``(L, U)`` such that ``L <= phi^-1(m) <= U``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polyroots import _inv_totient_estimate
+
+ >>> _inv_totient_estimate(192)
+ (192, 840)
+ >>> _inv_totient_estimate(400)
+ (400, 1750)
+
+ """
+ primes = [ d + 1 for d in divisors(m) if isprime(d + 1) ]
+
+ a, b = 1, 1
+
+ for p in primes:
+ a *= p
+ b *= p - 1
+
+ L = m
+ U = int(math.ceil(m*(float(a)/b)))
+
+ P = p = 2
+ primes = []
+
+ while P <= U:
+ p = nextprime(p)
+ primes.append(p)
+ P *= p
+
+ P //= p
+ b = 1
+
+ for p in primes[:-1]:
+ b *= p - 1
+
+ U = int(math.ceil(m*(float(P)/b)))
+
+ return L, U
+
+
+def roots_cyclotomic(f, factor=False):
+ """Compute roots of cyclotomic polynomials. """
+ L, U = _inv_totient_estimate(f.degree())
+
+ for n in range(L, U + 1):
+ g = cyclotomic_poly(n, f.gen, polys=True)
+
+ if f.expr == g.expr:
+ break
+ else: # pragma: no cover
+ raise RuntimeError("failed to find index of a cyclotomic polynomial")
+
+ roots = []
+
+ if not factor:
+ # get the indices in the right order so the computed
+ # roots will be sorted
+ h = n//2
+ ks = [i for i in range(1, n + 1) if igcd(i, n) == 1]
+ ks.sort(key=lambda x: (x, -1) if x <= h else (abs(x - n), 1))
+ d = 2*I*pi/n
+ for k in reversed(ks):
+ roots.append(exp(k*d).expand(complex=True))
+ else:
+ g = Poly(f, extension=root(-1, n))
+
+ for h, _ in ordered(g.factor_list()[1]):
+ roots.append(-h.TC())
+
+ return roots
+
+
+def roots_quintic(f):
+ """
+ Calculate exact roots of a solvable irreducible quintic with rational coefficients.
+ Return an empty list if the quintic is reducible or not solvable.
+ """
+ result = []
+
+ coeff_5, coeff_4, p_, q_, r_, s_ = f.all_coeffs()
+
+ if not all(coeff.is_Rational for coeff in (coeff_5, coeff_4, p_, q_, r_, s_)):
+ return result
+
+ if coeff_5 != 1:
+ f = Poly(f / coeff_5)
+ _, coeff_4, p_, q_, r_, s_ = f.all_coeffs()
+
+ # Cancel coeff_4 to form x^5 + px^3 + qx^2 + rx + s
+ if coeff_4:
+ p = p_ - 2*coeff_4*coeff_4/5
+ q = q_ - 3*coeff_4*p_/5 + 4*coeff_4**3/25
+ r = r_ - 2*coeff_4*q_/5 + 3*coeff_4**2*p_/25 - 3*coeff_4**4/125
+ s = s_ - coeff_4*r_/5 + coeff_4**2*q_/25 - coeff_4**3*p_/125 + 4*coeff_4**5/3125
+ x = f.gen
+ f = Poly(x**5 + p*x**3 + q*x**2 + r*x + s)
+ else:
+ p, q, r, s = p_, q_, r_, s_
+
+ quintic = PolyQuintic(f)
+
+ # Eqn standardized. Algo for solving starts here
+ if not f.is_irreducible:
+ return result
+ f20 = quintic.f20
+ # Check if f20 has linear factors over domain Z
+ if f20.is_irreducible:
+ return result
+ # Now, we know that f is solvable
+ for _factor in f20.factor_list()[1]:
+ if _factor[0].is_linear:
+ theta = _factor[0].root(0)
+ break
+ d = discriminant(f)
+ delta = sqrt(d)
+ # zeta = a fifth root of unity
+ zeta1, zeta2, zeta3, zeta4 = quintic.zeta
+ T = quintic.T(theta, d)
+ tol = S(1e-10)
+ alpha = T[1] + T[2]*delta
+ alpha_bar = T[1] - T[2]*delta
+ beta = T[3] + T[4]*delta
+ beta_bar = T[3] - T[4]*delta
+
+ disc = alpha**2 - 4*beta
+ disc_bar = alpha_bar**2 - 4*beta_bar
+
+ l0 = quintic.l0(theta)
+ Stwo = S(2)
+ l1 = _quintic_simplify((-alpha + sqrt(disc)) / Stwo)
+ l4 = _quintic_simplify((-alpha - sqrt(disc)) / Stwo)
+
+ l2 = _quintic_simplify((-alpha_bar + sqrt(disc_bar)) / Stwo)
+ l3 = _quintic_simplify((-alpha_bar - sqrt(disc_bar)) / Stwo)
+
+ order = quintic.order(theta, d)
+ test = (order*delta.n()) - ( (l1.n() - l4.n())*(l2.n() - l3.n()) )
+ # Comparing floats
+ if not comp(test, 0, tol):
+ l2, l3 = l3, l2
+
+ # Now we have correct order of l's
+ R1 = l0 + l1*zeta1 + l2*zeta2 + l3*zeta3 + l4*zeta4
+ R2 = l0 + l3*zeta1 + l1*zeta2 + l4*zeta3 + l2*zeta4
+ R3 = l0 + l2*zeta1 + l4*zeta2 + l1*zeta3 + l3*zeta4
+ R4 = l0 + l4*zeta1 + l3*zeta2 + l2*zeta3 + l1*zeta4
+
+ Res = [None, [None]*5, [None]*5, [None]*5, [None]*5]
+ Res_n = [None, [None]*5, [None]*5, [None]*5, [None]*5]
+
+ # Simplifying improves performance a lot for exact expressions
+ R1 = _quintic_simplify(R1)
+ R2 = _quintic_simplify(R2)
+ R3 = _quintic_simplify(R3)
+ R4 = _quintic_simplify(R4)
+
+ # hard-coded results for [factor(i) for i in _vsolve(x**5 - a - I*b, x)]
+ x0 = z**(S(1)/5)
+ x1 = sqrt(2)
+ x2 = sqrt(5)
+ x3 = sqrt(5 - x2)
+ x4 = I*x2
+ x5 = x4 + I
+ x6 = I*x0/4
+ x7 = x1*sqrt(x2 + 5)
+ sol = [x0, -x6*(x1*x3 - x5), x6*(x1*x3 + x5), -x6*(x4 + x7 - I), x6*(-x4 + x7 + I)]
+
+ R1 = R1.as_real_imag()
+ R2 = R2.as_real_imag()
+ R3 = R3.as_real_imag()
+ R4 = R4.as_real_imag()
+
+ for i, s in enumerate(sol):
+ Res[1][i] = _quintic_simplify(s.xreplace({z: R1[0] + I*R1[1]}))
+ Res[2][i] = _quintic_simplify(s.xreplace({z: R2[0] + I*R2[1]}))
+ Res[3][i] = _quintic_simplify(s.xreplace({z: R3[0] + I*R3[1]}))
+ Res[4][i] = _quintic_simplify(s.xreplace({z: R4[0] + I*R4[1]}))
+
+ for i in range(1, 5):
+ for j in range(5):
+ Res_n[i][j] = Res[i][j].n()
+ Res[i][j] = _quintic_simplify(Res[i][j])
+ r1 = Res[1][0]
+ r1_n = Res_n[1][0]
+
+ for i in range(5):
+ if comp(im(r1_n*Res_n[4][i]), 0, tol):
+ r4 = Res[4][i]
+ break
+
+ # Now we have various Res values. Each will be a list of five
+ # values. We have to pick one r value from those five for each Res
+ u, v = quintic.uv(theta, d)
+ testplus = (u + v*delta*sqrt(5)).n()
+ testminus = (u - v*delta*sqrt(5)).n()
+
+ # Evaluated numbers suffixed with _n
+ # We will use evaluated numbers for calculation. Much faster.
+ r4_n = r4.n()
+ r2 = r3 = None
+
+ for i in range(5):
+ r2temp_n = Res_n[2][i]
+ for j in range(5):
+ # Again storing away the exact number and using
+ # evaluated numbers in computations
+ r3temp_n = Res_n[3][j]
+ if (comp((r1_n*r2temp_n**2 + r4_n*r3temp_n**2 - testplus).n(), 0, tol) and
+ comp((r3temp_n*r1_n**2 + r2temp_n*r4_n**2 - testminus).n(), 0, tol)):
+ r2 = Res[2][i]
+ r3 = Res[3][j]
+ break
+ if r2 is not None:
+ break
+ else:
+ return [] # fall back to normal solve
+
+ # Now, we have r's so we can get roots
+ x1 = (r1 + r2 + r3 + r4)/5
+ x2 = (r1*zeta4 + r2*zeta3 + r3*zeta2 + r4*zeta1)/5
+ x3 = (r1*zeta3 + r2*zeta1 + r3*zeta4 + r4*zeta2)/5
+ x4 = (r1*zeta2 + r2*zeta4 + r3*zeta1 + r4*zeta3)/5
+ x5 = (r1*zeta1 + r2*zeta2 + r3*zeta3 + r4*zeta4)/5
+ result = [x1, x2, x3, x4, x5]
+
+ # Now check if solutions are distinct
+
+ saw = set()
+ for r in result:
+ r = r.n(2)
+ if r in saw:
+ # Roots were identical. Abort, return []
+ # and fall back to usual solve
+ return []
+ saw.add(r)
+
+ # Restore to original equation where coeff_4 is nonzero
+ if coeff_4:
+ result = [x - coeff_4 / 5 for x in result]
+ return result
+
+
+def _quintic_simplify(expr):
+ from sympy.simplify.simplify import powsimp
+ expr = powsimp(expr)
+ expr = cancel(expr)
+ return together(expr)
+
+
+def _integer_basis(poly):
+ """Compute coefficient basis for a polynomial over integers.
+
+ Returns the integer ``div`` such that substituting ``x = div*y``
+ ``p(x) = m*q(y)`` where the coefficients of ``q`` are smaller
+ than those of ``p``.
+
+ For example ``x**5 + 512*x + 1024 = 0``
+ with ``div = 4`` becomes ``y**5 + 2*y + 1 = 0``
+
+ Returns the integer ``div`` or ``None`` if there is no possible scaling.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import Poly
+ >>> from sympy.abc import x
+ >>> from sympy.polys.polyroots import _integer_basis
+ >>> p = Poly(x**5 + 512*x + 1024, x, domain='ZZ')
+ >>> _integer_basis(p)
+ 4
+ """
+ monoms, coeffs = list(zip(*poly.terms()))
+
+ monoms, = list(zip(*monoms))
+ coeffs = list(map(abs, coeffs))
+
+ if coeffs[0] < coeffs[-1]:
+ coeffs = list(reversed(coeffs))
+ n = monoms[0]
+ monoms = [n - i for i in reversed(monoms)]
+ else:
+ return None
+
+ monoms = monoms[:-1]
+ coeffs = coeffs[:-1]
+
+ # Special case for two-term polynominals
+ if len(monoms) == 1:
+ r = Pow(coeffs[0], S.One/monoms[0])
+ if r.is_Integer:
+ return int(r)
+ else:
+ return None
+
+ divs = reversed(divisors(gcd_list(coeffs))[1:])
+
+ try:
+ div = next(divs)
+ except StopIteration:
+ return None
+
+ while True:
+ for monom, coeff in zip(monoms, coeffs):
+ if coeff % div**monom != 0:
+ try:
+ div = next(divs)
+ except StopIteration:
+ return None
+ else:
+ break
+ else:
+ return div
+
+
+def preprocess_roots(poly):
+ """Try to get rid of symbolic coefficients from ``poly``. """
+ coeff = S.One
+
+ poly_func = poly.func
+ try:
+ _, poly = poly.clear_denoms(convert=True)
+ except DomainError:
+ return coeff, poly
+
+ poly = poly.primitive()[1]
+ poly = poly.retract()
+
+ # TODO: This is fragile. Figure out how to make this independent of construct_domain().
+ if poly.get_domain().is_Poly and all(c.is_term for c in poly.rep.coeffs()):
+ poly = poly.inject()
+
+ strips = list(zip(*poly.monoms()))
+ gens = list(poly.gens[1:])
+
+ base, strips = strips[0], strips[1:]
+
+ for gen, strip in zip(list(gens), strips):
+ reverse = False
+
+ if strip[0] < strip[-1]:
+ strip = reversed(strip)
+ reverse = True
+
+ ratio = None
+
+ for a, b in zip(base, strip):
+ if not a and not b:
+ continue
+ elif not a or not b:
+ break
+ elif b % a != 0:
+ break
+ else:
+ _ratio = b // a
+
+ if ratio is None:
+ ratio = _ratio
+ elif ratio != _ratio:
+ break
+ else:
+ if reverse:
+ ratio = -ratio
+
+ poly = poly.eval(gen, 1)
+ coeff *= gen**(-ratio)
+ gens.remove(gen)
+
+ if gens:
+ poly = poly.eject(*gens)
+
+ if poly.is_univariate and poly.get_domain().is_ZZ:
+ basis = _integer_basis(poly)
+
+ if basis is not None:
+ n = poly.degree()
+
+ def func(k, coeff):
+ return coeff//basis**(n - k[0])
+
+ poly = poly.termwise(func)
+ coeff *= basis
+
+ if not isinstance(poly, poly_func):
+ poly = poly_func(poly)
+ return coeff, poly
+
+
+@public
+def roots(f, *gens,
+ auto=True,
+ cubics=True,
+ trig=False,
+ quartics=True,
+ quintics=False,
+ multiple=False,
+ filter=None,
+ predicate=None,
+ strict=False,
+ **flags):
+ """
+ Computes symbolic roots of a univariate polynomial.
+
+ Given a univariate polynomial f with symbolic coefficients (or
+ a list of the polynomial's coefficients), returns a dictionary
+ with its roots and their multiplicities.
+
+ Only roots expressible via radicals will be returned. To get
+ a complete set of roots use RootOf class or numerical methods
+ instead. By default cubic and quartic formulas are used in
+ the algorithm. To disable them because of unreadable output
+ set ``cubics=False`` or ``quartics=False`` respectively. If cubic
+ roots are real but are expressed in terms of complex numbers
+ (casus irreducibilis [1]) the ``trig`` flag can be set to True to
+ have the solutions returned in terms of cosine and inverse cosine
+ functions.
+
+ To get roots from a specific domain set the ``filter`` flag with
+ one of the following specifiers: Z, Q, R, I, C. By default all
+ roots are returned (this is equivalent to setting ``filter='C'``).
+
+ By default a dictionary is returned giving a compact result in
+ case of multiple roots. However to get a list containing all
+ those roots set the ``multiple`` flag to True; the list will
+ have identical roots appearing next to each other in the result.
+ (For a given Poly, the all_roots method will give the roots in
+ sorted numerical order.)
+
+ If the ``strict`` flag is True, ``UnsolvableFactorError`` will be
+ raised if the roots found are known to be incomplete (because
+ some roots are not expressible in radicals).
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, roots, degree
+ >>> from sympy.abc import x, y
+
+ >>> roots(x**2 - 1, x)
+ {-1: 1, 1: 1}
+
+ >>> p = Poly(x**2-1, x)
+ >>> roots(p)
+ {-1: 1, 1: 1}
+
+ >>> p = Poly(x**2-y, x, y)
+
+ >>> roots(Poly(p, x))
+ {-sqrt(y): 1, sqrt(y): 1}
+
+ >>> roots(x**2 - y, x)
+ {-sqrt(y): 1, sqrt(y): 1}
+
+ >>> roots([1, 0, -1])
+ {-1: 1, 1: 1}
+
+ ``roots`` will only return roots expressible in radicals. If
+ the given polynomial has some or all of its roots inexpressible in
+ radicals, the result of ``roots`` will be incomplete or empty
+ respectively.
+
+ Example where result is incomplete:
+
+ >>> roots((x-1)*(x**5-x+1), x)
+ {1: 1}
+
+ In this case, the polynomial has an unsolvable quintic factor
+ whose roots cannot be expressed by radicals. The polynomial has a
+ rational root (due to the factor `(x-1)`), which is returned since
+ ``roots`` always finds all rational roots.
+
+ Example where result is empty:
+
+ >>> roots(x**7-3*x**2+1, x)
+ {}
+
+ Here, the polynomial has no roots expressible in radicals, so
+ ``roots`` returns an empty dictionary.
+
+ The result produced by ``roots`` is complete if and only if the
+ sum of the multiplicity of each root is equal to the degree of
+ the polynomial. If strict=True, UnsolvableFactorError will be
+ raised if the result is incomplete.
+
+ The result can be be checked for completeness as follows:
+
+ >>> f = x**3-2*x**2+1
+ >>> sum(roots(f, x).values()) == degree(f, x)
+ True
+ >>> f = (x-1)*(x**5-x+1)
+ >>> sum(roots(f, x).values()) == degree(f, x)
+ False
+
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Cubic_equation#Trigonometric_and_hyperbolic_solutions
+
+ """
+ from sympy.polys.polytools import to_rational_coeffs
+ flags = dict(flags)
+
+ if isinstance(f, list):
+ if gens:
+ raise ValueError('redundant generators given')
+
+ x = Dummy('x')
+
+ poly, i = {}, len(f) - 1
+
+ for coeff in f:
+ poly[i], i = sympify(coeff), i - 1
+
+ f = Poly(poly, x, field=True)
+ else:
+ try:
+ F = Poly(f, *gens, **flags)
+ if not isinstance(f, Poly) and not F.gen.is_Symbol:
+ raise PolynomialError("generator must be a Symbol")
+ f = F
+ except GeneratorsNeeded:
+ if multiple:
+ return []
+ else:
+ return {}
+ else:
+ n = f.degree()
+ if f.length() == 2 and n > 2:
+ # check for foo**n in constant if dep is c*gen**m
+ con, dep = f.as_expr().as_independent(*f.gens)
+ fcon = -(-con).factor()
+ if fcon != con:
+ con = fcon
+ bases = []
+ for i in Mul.make_args(con):
+ if i.is_Pow:
+ b, e = i.as_base_exp()
+ if e.is_Integer and b.is_Add:
+ bases.append((b, Dummy(positive=True)))
+ if bases:
+ rv = roots(Poly((dep + con).xreplace(dict(bases)),
+ *f.gens), *F.gens,
+ auto=auto,
+ cubics=cubics,
+ trig=trig,
+ quartics=quartics,
+ quintics=quintics,
+ multiple=multiple,
+ filter=filter,
+ predicate=predicate,
+ **flags)
+ return {factor_terms(k.xreplace(
+ {v: k for k, v in bases})
+ ): v for k, v in rv.items()}
+
+ if f.is_multivariate:
+ raise PolynomialError('multivariate polynomials are not supported')
+
+ def _update_dict(result, zeros, currentroot, k):
+ if currentroot == S.Zero:
+ if S.Zero in zeros:
+ zeros[S.Zero] += k
+ else:
+ zeros[S.Zero] = k
+ if currentroot in result:
+ result[currentroot] += k
+ else:
+ result[currentroot] = k
+
+ def _try_decompose(f):
+ """Find roots using functional decomposition. """
+ factors, roots = f.decompose(), []
+
+ for currentroot in _try_heuristics(factors[0]):
+ roots.append(currentroot)
+
+ for currentfactor in factors[1:]:
+ previous, roots = list(roots), []
+
+ for currentroot in previous:
+ g = currentfactor - Poly(currentroot, f.gen)
+
+ for currentroot in _try_heuristics(g):
+ roots.append(currentroot)
+
+ return roots
+
+ def _try_heuristics(f):
+ """Find roots using formulas and some tricks. """
+ if f.is_ground:
+ return []
+ if f.is_monomial:
+ return [S.Zero]*f.degree()
+
+ if f.length() == 2:
+ if f.degree() == 1:
+ return list(map(cancel, roots_linear(f)))
+ else:
+ return roots_binomial(f)
+
+ result = []
+
+ for i in [-1, 1]:
+ if not f.eval(i):
+ f = f.quo(Poly(f.gen - i, f.gen))
+ result.append(i)
+ break
+
+ n = f.degree()
+
+ if n == 1:
+ result += list(map(cancel, roots_linear(f)))
+ elif n == 2:
+ result += list(map(cancel, roots_quadratic(f)))
+ elif f.is_cyclotomic:
+ result += roots_cyclotomic(f)
+ elif n == 3 and cubics:
+ result += roots_cubic(f, trig=trig)
+ elif n == 4 and quartics:
+ result += roots_quartic(f)
+ elif n == 5 and quintics:
+ result += roots_quintic(f)
+
+ return result
+
+ # Convert the generators to symbols
+ dumgens = symbols('x:%d' % len(f.gens), cls=Dummy)
+ f = f.per(f.rep, dumgens)
+
+ (k,), f = f.terms_gcd()
+
+ if not k:
+ zeros = {}
+ else:
+ zeros = {S.Zero: k}
+
+ coeff, f = preprocess_roots(f)
+
+ if auto and f.get_domain().is_Ring:
+ f = f.to_field()
+
+ # Use EX instead of ZZ_I or QQ_I
+ if f.get_domain().is_QQ_I:
+ f = f.per(f.rep.convert(EX))
+
+ rescale_x = None
+ translate_x = None
+
+ result = {}
+
+ if not f.is_ground:
+ dom = f.get_domain()
+ if not dom.is_Exact and dom.is_Numerical:
+ for r in f.nroots():
+ _update_dict(result, zeros, r, 1)
+ elif f.degree() == 1:
+ _update_dict(result, zeros, roots_linear(f)[0], 1)
+ elif f.length() == 2:
+ roots_fun = roots_quadratic if f.degree() == 2 else roots_binomial
+ for r in roots_fun(f):
+ _update_dict(result, zeros, r, 1)
+ else:
+ _, factors = Poly(f.as_expr()).factor_list()
+ if len(factors) == 1 and f.degree() == 2:
+ for r in roots_quadratic(f):
+ _update_dict(result, zeros, r, 1)
+ else:
+ if len(factors) == 1 and factors[0][1] == 1:
+ if f.get_domain().is_EX:
+ res = to_rational_coeffs(f)
+ if res:
+ if res[0] is None:
+ translate_x, f = res[2:]
+ else:
+ rescale_x, f = res[1], res[-1]
+ result = roots(f)
+ if not result:
+ for currentroot in _try_decompose(f):
+ _update_dict(result, zeros, currentroot, 1)
+ else:
+ for r in _try_heuristics(f):
+ _update_dict(result, zeros, r, 1)
+ else:
+ for currentroot in _try_decompose(f):
+ _update_dict(result, zeros, currentroot, 1)
+ else:
+ for currentfactor, k in factors:
+ for r in _try_heuristics(Poly(currentfactor, f.gen, field=True)):
+ _update_dict(result, zeros, r, k)
+
+ if coeff is not S.One:
+ _result, result, = result, {}
+
+ for currentroot, k in _result.items():
+ result[coeff*currentroot] = k
+
+ if filter not in [None, 'C']:
+ handlers = {
+ 'Z': lambda r: r.is_Integer,
+ 'Q': lambda r: r.is_Rational,
+ 'R': lambda r: all(a.is_real for a in r.as_numer_denom()),
+ 'I': lambda r: r.is_imaginary,
+ }
+
+ try:
+ query = handlers[filter]
+ except KeyError:
+ raise ValueError("Invalid filter: %s" % filter)
+
+ for zero in dict(result).keys():
+ if not query(zero):
+ del result[zero]
+
+ if predicate is not None:
+ for zero in dict(result).keys():
+ if not predicate(zero):
+ del result[zero]
+ if rescale_x:
+ result1 = {}
+ for k, v in result.items():
+ result1[k*rescale_x] = v
+ result = result1
+ if translate_x:
+ result1 = {}
+ for k, v in result.items():
+ result1[k + translate_x] = v
+ result = result1
+
+ # adding zero roots after non-trivial roots have been translated
+ result.update(zeros)
+
+ if strict and sum(result.values()) < f.degree():
+ raise UnsolvableFactorError(filldedent('''
+ Strict mode: some factors cannot be solved in radicals, so
+ a complete list of solutions cannot be returned. Call
+ roots with strict=False to get solutions expressible in
+ radicals (if there are any).
+ '''))
+
+ if not multiple:
+ return result
+ else:
+ zeros = []
+
+ for zero in ordered(result):
+ zeros.extend([zero]*result[zero])
+
+ return zeros
+
+
+def root_factors(f, *gens, filter=None, **args):
+ """
+ Returns all factors of a univariate polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy.abc import x, y
+ >>> from sympy.polys.polyroots import root_factors
+
+ >>> root_factors(x**2 - y, x)
+ [x - sqrt(y), x + sqrt(y)]
+
+ """
+ args = dict(args)
+
+ F = Poly(f, *gens, **args)
+
+ if not F.is_Poly:
+ return [f]
+
+ if F.is_multivariate:
+ raise ValueError('multivariate polynomials are not supported')
+
+ x = F.gens[0]
+
+ zeros = roots(F, filter=filter)
+
+ if not zeros:
+ factors = [F]
+ else:
+ factors, N = [], 0
+
+ for r, n in ordered(zeros.items()):
+ factors, N = factors + [Poly(x - r, x)]*n, N + n
+
+ if N < F.degree():
+ G = reduce(lambda p, q: p*q, factors)
+ factors.append(F.quo(G))
+
+ if not isinstance(f, Poly):
+ factors = [ f.as_expr() for f in factors ]
+
+ return factors
diff --git a/lib/python3.12/site-packages/sympy/polys/polytools.py b/lib/python3.12/site-packages/sympy/polys/polytools.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8f537c20ddd08d576e50ae0dfb5a5271f365800
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/polytools.py
@@ -0,0 +1,7748 @@
+"""User-friendly public interface to polynomial functions. """
+
+
+from functools import wraps, reduce
+from operator import mul
+from typing import Optional
+
+from sympy.core import (
+ S, Expr, Add, Tuple
+)
+from sympy.core.basic import Basic
+from sympy.core.decorators import _sympifyit
+from sympy.core.exprtools import Factors, factor_nc, factor_terms
+from sympy.core.evalf import (
+ pure_complex, evalf, fastlog, _evalf_with_bounded_error, quad_to_mpmath)
+from sympy.core.function import Derivative
+from sympy.core.mul import Mul, _keep_coeff
+from sympy.core.intfunc import ilcm
+from sympy.core.numbers import I, Integer, equal_valued
+from sympy.core.relational import Relational, Equality
+from sympy.core.sorting import ordered
+from sympy.core.symbol import Dummy, Symbol
+from sympy.core.sympify import sympify, _sympify
+from sympy.core.traversal import preorder_traversal, bottom_up
+from sympy.logic.boolalg import BooleanAtom
+from sympy.polys import polyoptions as options
+from sympy.polys.constructor import construct_domain
+from sympy.polys.domains import FF, QQ, ZZ
+from sympy.polys.domains.domainelement import DomainElement
+from sympy.polys.fglmtools import matrix_fglm
+from sympy.polys.groebnertools import groebner as _groebner
+from sympy.polys.monomials import Monomial
+from sympy.polys.orderings import monomial_key
+from sympy.polys.polyclasses import DMP, DMF, ANP
+from sympy.polys.polyerrors import (
+ OperationNotSupported, DomainError,
+ CoercionFailed, UnificationFailed,
+ GeneratorsNeeded, PolynomialError,
+ MultivariatePolynomialError,
+ ExactQuotientFailed,
+ PolificationFailed,
+ ComputationFailed,
+ GeneratorsError,
+)
+from sympy.polys.polyutils import (
+ basic_from_dict,
+ _sort_gens,
+ _unify_gens,
+ _dict_reorder,
+ _dict_from_expr,
+ _parallel_dict_from_expr,
+)
+from sympy.polys.rationaltools import together
+from sympy.polys.rootisolation import dup_isolate_real_roots_list
+from sympy.utilities import group, public, filldedent
+from sympy.utilities.exceptions import sympy_deprecation_warning
+from sympy.utilities.iterables import iterable, sift
+
+
+# Required to avoid errors
+import sympy.polys
+
+import mpmath
+from mpmath.libmp.libhyper import NoConvergence
+
+
+
+def _polifyit(func):
+ @wraps(func)
+ def wrapper(f, g):
+ g = _sympify(g)
+ if isinstance(g, Poly):
+ return func(f, g)
+ elif isinstance(g, Integer):
+ g = f.from_expr(g, *f.gens, domain=f.domain)
+ return func(f, g)
+ elif isinstance(g, Expr):
+ try:
+ g = f.from_expr(g, *f.gens)
+ except PolynomialError:
+ if g.is_Matrix:
+ return NotImplemented
+ expr_method = getattr(f.as_expr(), func.__name__)
+ result = expr_method(g)
+ if result is not NotImplemented:
+ sympy_deprecation_warning(
+ """
+ Mixing Poly with non-polynomial expressions in binary
+ operations is deprecated. Either explicitly convert
+ the non-Poly operand to a Poly with as_poly() or
+ convert the Poly to an Expr with as_expr().
+ """,
+ deprecated_since_version="1.6",
+ active_deprecations_target="deprecated-poly-nonpoly-binary-operations",
+ )
+ return result
+ else:
+ return func(f, g)
+ else:
+ return NotImplemented
+ return wrapper
+
+
+
+@public
+class Poly(Basic):
+ """
+ Generic class for representing and operating on polynomial expressions.
+
+ See :ref:`polys-docs` for general documentation.
+
+ Poly is a subclass of Basic rather than Expr but instances can be
+ converted to Expr with the :py:meth:`~.Poly.as_expr` method.
+
+ .. deprecated:: 1.6
+
+ Combining Poly with non-Poly objects in binary operations is
+ deprecated. Explicitly convert both objects to either Poly or Expr
+ first. See :ref:`deprecated-poly-nonpoly-binary-operations`.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ Create a univariate polynomial:
+
+ >>> Poly(x*(x**2 + x - 1)**2)
+ Poly(x**5 + 2*x**4 - x**3 - 2*x**2 + x, x, domain='ZZ')
+
+ Create a univariate polynomial with specific domain:
+
+ >>> from sympy import sqrt
+ >>> Poly(x**2 + 2*x + sqrt(3), domain='R')
+ Poly(1.0*x**2 + 2.0*x + 1.73205080756888, x, domain='RR')
+
+ Create a multivariate polynomial:
+
+ >>> Poly(y*x**2 + x*y + 1)
+ Poly(x**2*y + x*y + 1, x, y, domain='ZZ')
+
+ Create a univariate polynomial, where y is a constant:
+
+ >>> Poly(y*x**2 + x*y + 1,x)
+ Poly(y*x**2 + y*x + 1, x, domain='ZZ[y]')
+
+ You can evaluate the above polynomial as a function of y:
+
+ >>> Poly(y*x**2 + x*y + 1,x).eval(2)
+ 6*y + 1
+
+ See Also
+ ========
+
+ sympy.core.expr.Expr
+
+ """
+
+ __slots__ = ('rep', 'gens')
+
+ is_commutative = True
+ is_Poly = True
+ _op_priority = 10.001
+
+ def __new__(cls, rep, *gens, **args):
+ """Create a new polynomial instance out of something useful. """
+ opt = options.build_options(gens, args)
+
+ if 'order' in opt:
+ raise NotImplementedError("'order' keyword is not implemented yet")
+
+ if isinstance(rep, (DMP, DMF, ANP, DomainElement)):
+ return cls._from_domain_element(rep, opt)
+ elif iterable(rep, exclude=str):
+ if isinstance(rep, dict):
+ return cls._from_dict(rep, opt)
+ else:
+ return cls._from_list(list(rep), opt)
+ else:
+ rep = sympify(rep, evaluate=type(rep) is not str)
+
+ if rep.is_Poly:
+ return cls._from_poly(rep, opt)
+ else:
+ return cls._from_expr(rep, opt)
+
+ # Poly does not pass its args to Basic.__new__ to be stored in _args so we
+ # have to emulate them here with an args property that derives from rep
+ # and gens which are instance attributes. This also means we need to
+ # define _hashable_content. The _hashable_content is rep and gens but args
+ # uses expr instead of rep (expr is the Basic version of rep). Passing
+ # expr in args means that Basic methods like subs should work. Using rep
+ # otherwise means that Poly can remain more efficient than Basic by
+ # avoiding creating a Basic instance just to be hashable.
+
+ @classmethod
+ def new(cls, rep, *gens):
+ """Construct :class:`Poly` instance from raw representation. """
+ if not isinstance(rep, DMP):
+ raise PolynomialError(
+ "invalid polynomial representation: %s" % rep)
+ elif rep.lev != len(gens) - 1:
+ raise PolynomialError("invalid arguments: %s, %s" % (rep, gens))
+
+ obj = Basic.__new__(cls)
+ obj.rep = rep
+ obj.gens = gens
+
+ return obj
+
+ @property
+ def expr(self):
+ return basic_from_dict(self.rep.to_sympy_dict(), *self.gens)
+
+ @property
+ def args(self):
+ return (self.expr,) + self.gens
+
+ def _hashable_content(self):
+ return (self.rep,) + self.gens
+
+ @classmethod
+ def from_dict(cls, rep, *gens, **args):
+ """Construct a polynomial from a ``dict``. """
+ opt = options.build_options(gens, args)
+ return cls._from_dict(rep, opt)
+
+ @classmethod
+ def from_list(cls, rep, *gens, **args):
+ """Construct a polynomial from a ``list``. """
+ opt = options.build_options(gens, args)
+ return cls._from_list(rep, opt)
+
+ @classmethod
+ def from_poly(cls, rep, *gens, **args):
+ """Construct a polynomial from a polynomial. """
+ opt = options.build_options(gens, args)
+ return cls._from_poly(rep, opt)
+
+ @classmethod
+ def from_expr(cls, rep, *gens, **args):
+ """Construct a polynomial from an expression. """
+ opt = options.build_options(gens, args)
+ return cls._from_expr(rep, opt)
+
+ @classmethod
+ def _from_dict(cls, rep, opt):
+ """Construct a polynomial from a ``dict``. """
+ gens = opt.gens
+
+ if not gens:
+ raise GeneratorsNeeded(
+ "Cannot initialize from 'dict' without generators")
+
+ level = len(gens) - 1
+ domain = opt.domain
+
+ if domain is None:
+ domain, rep = construct_domain(rep, opt=opt)
+ else:
+ for monom, coeff in rep.items():
+ rep[monom] = domain.convert(coeff)
+
+ return cls.new(DMP.from_dict(rep, level, domain), *gens)
+
+ @classmethod
+ def _from_list(cls, rep, opt):
+ """Construct a polynomial from a ``list``. """
+ gens = opt.gens
+
+ if not gens:
+ raise GeneratorsNeeded(
+ "Cannot initialize from 'list' without generators")
+ elif len(gens) != 1:
+ raise MultivariatePolynomialError(
+ "'list' representation not supported")
+
+ level = len(gens) - 1
+ domain = opt.domain
+
+ if domain is None:
+ domain, rep = construct_domain(rep, opt=opt)
+ else:
+ rep = list(map(domain.convert, rep))
+
+ return cls.new(DMP.from_list(rep, level, domain), *gens)
+
+ @classmethod
+ def _from_poly(cls, rep, opt):
+ """Construct a polynomial from a polynomial. """
+ if cls != rep.__class__:
+ rep = cls.new(rep.rep, *rep.gens)
+
+ gens = opt.gens
+ field = opt.field
+ domain = opt.domain
+
+ if gens and rep.gens != gens:
+ if set(rep.gens) != set(gens):
+ return cls._from_expr(rep.as_expr(), opt)
+ else:
+ rep = rep.reorder(*gens)
+
+ if 'domain' in opt and domain:
+ rep = rep.set_domain(domain)
+ elif field is True:
+ rep = rep.to_field()
+
+ return rep
+
+ @classmethod
+ def _from_expr(cls, rep, opt):
+ """Construct a polynomial from an expression. """
+ rep, opt = _dict_from_expr(rep, opt)
+ return cls._from_dict(rep, opt)
+
+ @classmethod
+ def _from_domain_element(cls, rep, opt):
+ gens = opt.gens
+ domain = opt.domain
+
+ level = len(gens) - 1
+ rep = [domain.convert(rep)]
+
+ return cls.new(DMP.from_list(rep, level, domain), *gens)
+
+ def __hash__(self):
+ return super().__hash__()
+
+ @property
+ def free_symbols(self):
+ """
+ Free symbols of a polynomial expression.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y, z
+
+ >>> Poly(x**2 + 1).free_symbols
+ {x}
+ >>> Poly(x**2 + y).free_symbols
+ {x, y}
+ >>> Poly(x**2 + y, x).free_symbols
+ {x, y}
+ >>> Poly(x**2 + y, x, z).free_symbols
+ {x, y}
+
+ """
+ symbols = set()
+ gens = self.gens
+ for i in range(len(gens)):
+ for monom in self.monoms():
+ if monom[i]:
+ symbols |= gens[i].free_symbols
+ break
+
+ return symbols | self.free_symbols_in_domain
+
+ @property
+ def free_symbols_in_domain(self):
+ """
+ Free symbols of the domain of ``self``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + 1).free_symbols_in_domain
+ set()
+ >>> Poly(x**2 + y).free_symbols_in_domain
+ set()
+ >>> Poly(x**2 + y, x).free_symbols_in_domain
+ {y}
+
+ """
+ domain, symbols = self.rep.dom, set()
+
+ if domain.is_Composite:
+ for gen in domain.symbols:
+ symbols |= gen.free_symbols
+ elif domain.is_EX:
+ for coeff in self.coeffs():
+ symbols |= coeff.free_symbols
+
+ return symbols
+
+ @property
+ def gen(self):
+ """
+ Return the principal generator.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).gen
+ x
+
+ """
+ return self.gens[0]
+
+ @property
+ def domain(self):
+ """Get the ground domain of a :py:class:`~.Poly`
+
+ Returns
+ =======
+
+ :py:class:`~.Domain`:
+ Ground domain of the :py:class:`~.Poly`.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, Symbol
+ >>> x = Symbol('x')
+ >>> p = Poly(x**2 + x)
+ >>> p
+ Poly(x**2 + x, x, domain='ZZ')
+ >>> p.domain
+ ZZ
+ """
+ return self.get_domain()
+
+ @property
+ def zero(self):
+ """Return zero polynomial with ``self``'s properties. """
+ return self.new(self.rep.zero(self.rep.lev, self.rep.dom), *self.gens)
+
+ @property
+ def one(self):
+ """Return one polynomial with ``self``'s properties. """
+ return self.new(self.rep.one(self.rep.lev, self.rep.dom), *self.gens)
+
+ @property
+ def unit(self):
+ """Return unit polynomial with ``self``'s properties. """
+ return self.new(self.rep.unit(self.rep.lev, self.rep.dom), *self.gens)
+
+ def unify(f, g):
+ """
+ Make ``f`` and ``g`` belong to the same domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f, g = Poly(x/2 + 1), Poly(2*x + 1)
+
+ >>> f
+ Poly(1/2*x + 1, x, domain='QQ')
+ >>> g
+ Poly(2*x + 1, x, domain='ZZ')
+
+ >>> F, G = f.unify(g)
+
+ >>> F
+ Poly(1/2*x + 1, x, domain='QQ')
+ >>> G
+ Poly(2*x + 1, x, domain='QQ')
+
+ """
+ _, per, F, G = f._unify(g)
+ return per(F), per(G)
+
+ def _unify(f, g):
+ g = sympify(g)
+
+ if not g.is_Poly:
+ try:
+ g_coeff = f.rep.dom.from_sympy(g)
+ except CoercionFailed:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+ else:
+ return f.rep.dom, f.per, f.rep, f.rep.ground_new(g_coeff)
+
+ if isinstance(f.rep, DMP) and isinstance(g.rep, DMP):
+ gens = _unify_gens(f.gens, g.gens)
+
+ dom, lev = f.rep.dom.unify(g.rep.dom, gens), len(gens) - 1
+
+ if f.gens != gens:
+ f_monoms, f_coeffs = _dict_reorder(
+ f.rep.to_dict(), f.gens, gens)
+
+ if f.rep.dom != dom:
+ f_coeffs = [dom.convert(c, f.rep.dom) for c in f_coeffs]
+
+ F = DMP.from_dict(dict(list(zip(f_monoms, f_coeffs))), lev, dom)
+ else:
+ F = f.rep.convert(dom)
+
+ if g.gens != gens:
+ g_monoms, g_coeffs = _dict_reorder(
+ g.rep.to_dict(), g.gens, gens)
+
+ if g.rep.dom != dom:
+ g_coeffs = [dom.convert(c, g.rep.dom) for c in g_coeffs]
+
+ G = DMP.from_dict(dict(list(zip(g_monoms, g_coeffs))), lev, dom)
+ else:
+ G = g.rep.convert(dom)
+ else:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ cls = f.__class__
+
+ def per(rep, dom=dom, gens=gens, remove=None):
+ if remove is not None:
+ gens = gens[:remove] + gens[remove + 1:]
+
+ if not gens:
+ return dom.to_sympy(rep)
+
+ return cls.new(rep, *gens)
+
+ return dom, per, F, G
+
+ def per(f, rep, gens=None, remove=None):
+ """
+ Create a Poly out of the given representation.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, ZZ
+ >>> from sympy.abc import x, y
+
+ >>> from sympy.polys.polyclasses import DMP
+
+ >>> a = Poly(x**2 + 1)
+
+ >>> a.per(DMP([ZZ(1), ZZ(1)], ZZ), gens=[y])
+ Poly(y + 1, y, domain='ZZ')
+
+ """
+ if gens is None:
+ gens = f.gens
+
+ if remove is not None:
+ gens = gens[:remove] + gens[remove + 1:]
+
+ if not gens:
+ return f.rep.dom.to_sympy(rep)
+
+ return f.__class__.new(rep, *gens)
+
+ def set_domain(f, domain):
+ """Set the ground domain of ``f``. """
+ opt = options.build_options(f.gens, {'domain': domain})
+ return f.per(f.rep.convert(opt.domain))
+
+ def get_domain(f):
+ """Get the ground domain of ``f``. """
+ return f.rep.dom
+
+ def set_modulus(f, modulus):
+ """
+ Set the modulus of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(5*x**2 + 2*x - 1, x).set_modulus(2)
+ Poly(x**2 + 1, x, modulus=2)
+
+ """
+ modulus = options.Modulus.preprocess(modulus)
+ return f.set_domain(FF(modulus))
+
+ def get_modulus(f):
+ """
+ Get the modulus of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, modulus=2).get_modulus()
+ 2
+
+ """
+ domain = f.get_domain()
+
+ if domain.is_FiniteField:
+ return Integer(domain.characteristic())
+ else:
+ raise PolynomialError("not a polynomial over a Galois field")
+
+ def _eval_subs(f, old, new):
+ """Internal implementation of :func:`subs`. """
+ if old in f.gens:
+ if new.is_number:
+ return f.eval(old, new)
+ else:
+ try:
+ return f.replace(old, new)
+ except PolynomialError:
+ pass
+
+ return f.as_expr().subs(old, new)
+
+ def exclude(f):
+ """
+ Remove unnecessary generators from ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import a, b, c, d, x
+
+ >>> Poly(a + x, a, b, c, d, x).exclude()
+ Poly(a + x, a, x, domain='ZZ')
+
+ """
+ J, new = f.rep.exclude()
+ gens = [gen for j, gen in enumerate(f.gens) if j not in J]
+
+ return f.per(new, gens=gens)
+
+ def replace(f, x, y=None, **_ignore):
+ # XXX this does not match Basic's signature
+ """
+ Replace ``x`` with ``y`` in generators list.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + 1, x).replace(x, y)
+ Poly(y**2 + 1, y, domain='ZZ')
+
+ """
+ if y is None:
+ if f.is_univariate:
+ x, y = f.gen, x
+ else:
+ raise PolynomialError(
+ "syntax supported only in univariate case")
+
+ if x == y or x not in f.gens:
+ return f
+
+ if x in f.gens and y not in f.gens:
+ dom = f.get_domain()
+
+ if not dom.is_Composite or y not in dom.symbols:
+ gens = list(f.gens)
+ gens[gens.index(x)] = y
+ return f.per(f.rep, gens=gens)
+
+ raise PolynomialError("Cannot replace %s with %s in %s" % (x, y, f))
+
+ def match(f, *args, **kwargs):
+ """Match expression from Poly. See Basic.match()"""
+ return f.as_expr().match(*args, **kwargs)
+
+ def reorder(f, *gens, **args):
+ """
+ Efficiently apply new order of generators.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + x*y**2, x, y).reorder(y, x)
+ Poly(y**2*x + x**2, y, x, domain='ZZ')
+
+ """
+ opt = options.Options((), args)
+
+ if not gens:
+ gens = _sort_gens(f.gens, opt=opt)
+ elif set(f.gens) != set(gens):
+ raise PolynomialError(
+ "generators list can differ only up to order of elements")
+
+ rep = dict(list(zip(*_dict_reorder(f.rep.to_dict(), f.gens, gens))))
+
+ return f.per(DMP.from_dict(rep, len(gens) - 1, f.rep.dom), gens=gens)
+
+ def ltrim(f, gen):
+ """
+ Remove dummy generators from ``f`` that are to the left of
+ specified ``gen`` in the generators as ordered. When ``gen``
+ is an integer, it refers to the generator located at that
+ position within the tuple of generators of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y, z
+
+ >>> Poly(y**2 + y*z**2, x, y, z).ltrim(y)
+ Poly(y**2 + y*z**2, y, z, domain='ZZ')
+ >>> Poly(z, x, y, z).ltrim(-1)
+ Poly(z, z, domain='ZZ')
+
+ """
+ rep = f.as_dict(native=True)
+ j = f._gen_to_level(gen)
+
+ terms = {}
+
+ for monom, coeff in rep.items():
+
+ if any(monom[:j]):
+ # some generator is used in the portion to be trimmed
+ raise PolynomialError("Cannot left trim %s" % f)
+
+ terms[monom[j:]] = coeff
+
+ gens = f.gens[j:]
+
+ return f.new(DMP.from_dict(terms, len(gens) - 1, f.rep.dom), *gens)
+
+ def has_only_gens(f, *gens):
+ """
+ Return ``True`` if ``Poly(f, *gens)`` retains ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y, z
+
+ >>> Poly(x*y + 1, x, y, z).has_only_gens(x, y)
+ True
+ >>> Poly(x*y + z, x, y, z).has_only_gens(x, y)
+ False
+
+ """
+ indices = set()
+
+ for gen in gens:
+ try:
+ index = f.gens.index(gen)
+ except ValueError:
+ raise GeneratorsError(
+ "%s doesn't have %s as generator" % (f, gen))
+ else:
+ indices.add(index)
+
+ for monom in f.monoms():
+ for i, elt in enumerate(monom):
+ if i not in indices and elt:
+ return False
+
+ return True
+
+ def to_ring(f):
+ """
+ Make the ground domain a ring.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, QQ
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, domain=QQ).to_ring()
+ Poly(x**2 + 1, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'to_ring'):
+ result = f.rep.to_ring()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'to_ring')
+
+ return f.per(result)
+
+ def to_field(f):
+ """
+ Make the ground domain a field.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, ZZ
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x, domain=ZZ).to_field()
+ Poly(x**2 + 1, x, domain='QQ')
+
+ """
+ if hasattr(f.rep, 'to_field'):
+ result = f.rep.to_field()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'to_field')
+
+ return f.per(result)
+
+ def to_exact(f):
+ """
+ Make the ground domain exact.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, RR
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1.0, x, domain=RR).to_exact()
+ Poly(x**2 + 1, x, domain='QQ')
+
+ """
+ if hasattr(f.rep, 'to_exact'):
+ result = f.rep.to_exact()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'to_exact')
+
+ return f.per(result)
+
+ def retract(f, field=None):
+ """
+ Recalculate the ground domain of a polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = Poly(x**2 + 1, x, domain='QQ[y]')
+ >>> f
+ Poly(x**2 + 1, x, domain='QQ[y]')
+
+ >>> f.retract()
+ Poly(x**2 + 1, x, domain='ZZ')
+ >>> f.retract(field=True)
+ Poly(x**2 + 1, x, domain='QQ')
+
+ """
+ dom, rep = construct_domain(f.as_dict(zero=True),
+ field=field, composite=f.domain.is_Composite or None)
+ return f.from_dict(rep, f.gens, domain=dom)
+
+ def slice(f, x, m, n=None):
+ """Take a continuous subsequence of terms of ``f``. """
+ if n is None:
+ j, m, n = 0, x, m
+ else:
+ j = f._gen_to_level(x)
+
+ m, n = int(m), int(n)
+
+ if hasattr(f.rep, 'slice'):
+ result = f.rep.slice(m, n, j)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'slice')
+
+ return f.per(result)
+
+ def coeffs(f, order=None):
+ """
+ Returns all non-zero coefficients from ``f`` in lex order.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**3 + 2*x + 3, x).coeffs()
+ [1, 2, 3]
+
+ See Also
+ ========
+ all_coeffs
+ coeff_monomial
+ nth
+
+ """
+ return [f.rep.dom.to_sympy(c) for c in f.rep.coeffs(order=order)]
+
+ def monoms(f, order=None):
+ """
+ Returns all non-zero monomials from ``f`` in lex order.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + 2*x*y**2 + x*y + 3*y, x, y).monoms()
+ [(2, 0), (1, 2), (1, 1), (0, 1)]
+
+ See Also
+ ========
+ all_monoms
+
+ """
+ return f.rep.monoms(order=order)
+
+ def terms(f, order=None):
+ """
+ Returns all non-zero terms from ``f`` in lex order.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + 2*x*y**2 + x*y + 3*y, x, y).terms()
+ [((2, 0), 1), ((1, 2), 2), ((1, 1), 1), ((0, 1), 3)]
+
+ See Also
+ ========
+ all_terms
+
+ """
+ return [(m, f.rep.dom.to_sympy(c)) for m, c in f.rep.terms(order=order)]
+
+ def all_coeffs(f):
+ """
+ Returns all coefficients from a univariate polynomial ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**3 + 2*x - 1, x).all_coeffs()
+ [1, 0, 2, -1]
+
+ """
+ return [f.rep.dom.to_sympy(c) for c in f.rep.all_coeffs()]
+
+ def all_monoms(f):
+ """
+ Returns all monomials from a univariate polynomial ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**3 + 2*x - 1, x).all_monoms()
+ [(3,), (2,), (1,), (0,)]
+
+ See Also
+ ========
+ all_terms
+
+ """
+ return f.rep.all_monoms()
+
+ def all_terms(f):
+ """
+ Returns all terms from a univariate polynomial ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**3 + 2*x - 1, x).all_terms()
+ [((3,), 1), ((2,), 0), ((1,), 2), ((0,), -1)]
+
+ """
+ return [(m, f.rep.dom.to_sympy(c)) for m, c in f.rep.all_terms()]
+
+ def termwise(f, func, *gens, **args):
+ """
+ Apply a function to all terms of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> def func(k, coeff):
+ ... k = k[0]
+ ... return coeff//10**(2-k)
+
+ >>> Poly(x**2 + 20*x + 400).termwise(func)
+ Poly(x**2 + 2*x + 4, x, domain='ZZ')
+
+ """
+ terms = {}
+
+ for monom, coeff in f.terms():
+ result = func(monom, coeff)
+
+ if isinstance(result, tuple):
+ monom, coeff = result
+ else:
+ coeff = result
+
+ if coeff:
+ if monom not in terms:
+ terms[monom] = coeff
+ else:
+ raise PolynomialError(
+ "%s monomial was generated twice" % monom)
+
+ return f.from_dict(terms, *(gens or f.gens), **args)
+
+ def length(f):
+ """
+ Returns the number of non-zero terms in ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 2*x - 1).length()
+ 3
+
+ """
+ return len(f.as_dict())
+
+ def as_dict(f, native=False, zero=False):
+ """
+ Switch to a ``dict`` representation.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + 2*x*y**2 - y, x, y).as_dict()
+ {(0, 1): -1, (1, 2): 2, (2, 0): 1}
+
+ """
+ if native:
+ return f.rep.to_dict(zero=zero)
+ else:
+ return f.rep.to_sympy_dict(zero=zero)
+
+ def as_list(f, native=False):
+ """Switch to a ``list`` representation. """
+ if native:
+ return f.rep.to_list()
+ else:
+ return f.rep.to_sympy_list()
+
+ def as_expr(f, *gens):
+ """
+ Convert a Poly instance to an Expr instance.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> f = Poly(x**2 + 2*x*y**2 - y, x, y)
+
+ >>> f.as_expr()
+ x**2 + 2*x*y**2 - y
+ >>> f.as_expr({x: 5})
+ 10*y**2 - y + 25
+ >>> f.as_expr(5, 6)
+ 379
+
+ """
+ if not gens:
+ return f.expr
+
+ if len(gens) == 1 and isinstance(gens[0], dict):
+ mapping = gens[0]
+ gens = list(f.gens)
+
+ for gen, value in mapping.items():
+ try:
+ index = gens.index(gen)
+ except ValueError:
+ raise GeneratorsError(
+ "%s doesn't have %s as generator" % (f, gen))
+ else:
+ gens[index] = value
+
+ return basic_from_dict(f.rep.to_sympy_dict(), *gens)
+
+ def as_poly(self, *gens, **args):
+ """Converts ``self`` to a polynomial or returns ``None``.
+
+ >>> from sympy import sin
+ >>> from sympy.abc import x, y
+
+ >>> print((x**2 + x*y).as_poly())
+ Poly(x**2 + x*y, x, y, domain='ZZ')
+
+ >>> print((x**2 + x*y).as_poly(x, y))
+ Poly(x**2 + x*y, x, y, domain='ZZ')
+
+ >>> print((x**2 + sin(y)).as_poly(x, y))
+ None
+
+ """
+ try:
+ poly = Poly(self, *gens, **args)
+
+ if not poly.is_Poly:
+ return None
+ else:
+ return poly
+ except PolynomialError:
+ return None
+
+ def lift(f):
+ """
+ Convert algebraic coefficients to rationals.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, I
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + I*x + 1, x, extension=I).lift()
+ Poly(x**4 + 3*x**2 + 1, x, domain='QQ')
+
+ """
+ if hasattr(f.rep, 'lift'):
+ result = f.rep.lift()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'lift')
+
+ return f.per(result)
+
+ def deflate(f):
+ """
+ Reduce degree of ``f`` by mapping ``x_i**m`` to ``y_i``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**6*y**2 + x**3 + 1, x, y).deflate()
+ ((3, 2), Poly(x**2*y + x + 1, x, y, domain='ZZ'))
+
+ """
+ if hasattr(f.rep, 'deflate'):
+ J, result = f.rep.deflate()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'deflate')
+
+ return J, f.per(result)
+
+ def inject(f, front=False):
+ """
+ Inject ground domain generators into ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> f = Poly(x**2*y + x*y**3 + x*y + 1, x)
+
+ >>> f.inject()
+ Poly(x**2*y + x*y**3 + x*y + 1, x, y, domain='ZZ')
+ >>> f.inject(front=True)
+ Poly(y**3*x + y*x**2 + y*x + 1, y, x, domain='ZZ')
+
+ """
+ dom = f.rep.dom
+
+ if dom.is_Numerical:
+ return f
+ elif not dom.is_Poly:
+ raise DomainError("Cannot inject generators over %s" % dom)
+
+ if hasattr(f.rep, 'inject'):
+ result = f.rep.inject(front=front)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'inject')
+
+ if front:
+ gens = dom.symbols + f.gens
+ else:
+ gens = f.gens + dom.symbols
+
+ return f.new(result, *gens)
+
+ def eject(f, *gens):
+ """
+ Eject selected generators into the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> f = Poly(x**2*y + x*y**3 + x*y + 1, x, y)
+
+ >>> f.eject(x)
+ Poly(x*y**3 + (x**2 + x)*y + 1, y, domain='ZZ[x]')
+ >>> f.eject(y)
+ Poly(y*x**2 + (y**3 + y)*x + 1, x, domain='ZZ[y]')
+
+ """
+ dom = f.rep.dom
+
+ if not dom.is_Numerical:
+ raise DomainError("Cannot eject generators over %s" % dom)
+
+ k = len(gens)
+
+ if f.gens[:k] == gens:
+ _gens, front = f.gens[k:], True
+ elif f.gens[-k:] == gens:
+ _gens, front = f.gens[:-k], False
+ else:
+ raise NotImplementedError(
+ "can only eject front or back generators")
+
+ dom = dom.inject(*gens)
+
+ if hasattr(f.rep, 'eject'):
+ result = f.rep.eject(dom, front=front)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'eject')
+
+ return f.new(result, *_gens)
+
+ def terms_gcd(f):
+ """
+ Remove GCD of terms from the polynomial ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**6*y**2 + x**3*y, x, y).terms_gcd()
+ ((3, 1), Poly(x**3*y + 1, x, y, domain='ZZ'))
+
+ """
+ if hasattr(f.rep, 'terms_gcd'):
+ J, result = f.rep.terms_gcd()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'terms_gcd')
+
+ return J, f.per(result)
+
+ def add_ground(f, coeff):
+ """
+ Add an element of the ground domain to ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x + 1).add_ground(2)
+ Poly(x + 3, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'add_ground'):
+ result = f.rep.add_ground(coeff)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'add_ground')
+
+ return f.per(result)
+
+ def sub_ground(f, coeff):
+ """
+ Subtract an element of the ground domain from ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x + 1).sub_ground(2)
+ Poly(x - 1, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'sub_ground'):
+ result = f.rep.sub_ground(coeff)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'sub_ground')
+
+ return f.per(result)
+
+ def mul_ground(f, coeff):
+ """
+ Multiply ``f`` by a an element of the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x + 1).mul_ground(2)
+ Poly(2*x + 2, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'mul_ground'):
+ result = f.rep.mul_ground(coeff)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'mul_ground')
+
+ return f.per(result)
+
+ def quo_ground(f, coeff):
+ """
+ Quotient of ``f`` by a an element of the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(2*x + 4).quo_ground(2)
+ Poly(x + 2, x, domain='ZZ')
+
+ >>> Poly(2*x + 3).quo_ground(2)
+ Poly(x + 1, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'quo_ground'):
+ result = f.rep.quo_ground(coeff)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'quo_ground')
+
+ return f.per(result)
+
+ def exquo_ground(f, coeff):
+ """
+ Exact quotient of ``f`` by a an element of the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(2*x + 4).exquo_ground(2)
+ Poly(x + 2, x, domain='ZZ')
+
+ >>> Poly(2*x + 3).exquo_ground(2)
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: 2 does not divide 3 in ZZ
+
+ """
+ if hasattr(f.rep, 'exquo_ground'):
+ result = f.rep.exquo_ground(coeff)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'exquo_ground')
+
+ return f.per(result)
+
+ def abs(f):
+ """
+ Make all coefficients in ``f`` positive.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 1, x).abs()
+ Poly(x**2 + 1, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'abs'):
+ result = f.rep.abs()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'abs')
+
+ return f.per(result)
+
+ def neg(f):
+ """
+ Negate all coefficients in ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 1, x).neg()
+ Poly(-x**2 + 1, x, domain='ZZ')
+
+ >>> -Poly(x**2 - 1, x)
+ Poly(-x**2 + 1, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'neg'):
+ result = f.rep.neg()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'neg')
+
+ return f.per(result)
+
+ def add(f, g):
+ """
+ Add two polynomials ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).add(Poly(x - 2, x))
+ Poly(x**2 + x - 1, x, domain='ZZ')
+
+ >>> Poly(x**2 + 1, x) + Poly(x - 2, x)
+ Poly(x**2 + x - 1, x, domain='ZZ')
+
+ """
+ g = sympify(g)
+
+ if not g.is_Poly:
+ return f.add_ground(g)
+
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'add'):
+ result = F.add(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'add')
+
+ return per(result)
+
+ def sub(f, g):
+ """
+ Subtract two polynomials ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).sub(Poly(x - 2, x))
+ Poly(x**2 - x + 3, x, domain='ZZ')
+
+ >>> Poly(x**2 + 1, x) - Poly(x - 2, x)
+ Poly(x**2 - x + 3, x, domain='ZZ')
+
+ """
+ g = sympify(g)
+
+ if not g.is_Poly:
+ return f.sub_ground(g)
+
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'sub'):
+ result = F.sub(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'sub')
+
+ return per(result)
+
+ def mul(f, g):
+ """
+ Multiply two polynomials ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).mul(Poly(x - 2, x))
+ Poly(x**3 - 2*x**2 + x - 2, x, domain='ZZ')
+
+ >>> Poly(x**2 + 1, x)*Poly(x - 2, x)
+ Poly(x**3 - 2*x**2 + x - 2, x, domain='ZZ')
+
+ """
+ g = sympify(g)
+
+ if not g.is_Poly:
+ return f.mul_ground(g)
+
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'mul'):
+ result = F.mul(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'mul')
+
+ return per(result)
+
+ def sqr(f):
+ """
+ Square a polynomial ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x - 2, x).sqr()
+ Poly(x**2 - 4*x + 4, x, domain='ZZ')
+
+ >>> Poly(x - 2, x)**2
+ Poly(x**2 - 4*x + 4, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'sqr'):
+ result = f.rep.sqr()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'sqr')
+
+ return f.per(result)
+
+ def pow(f, n):
+ """
+ Raise ``f`` to a non-negative power ``n``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x - 2, x).pow(3)
+ Poly(x**3 - 6*x**2 + 12*x - 8, x, domain='ZZ')
+
+ >>> Poly(x - 2, x)**3
+ Poly(x**3 - 6*x**2 + 12*x - 8, x, domain='ZZ')
+
+ """
+ n = int(n)
+
+ if hasattr(f.rep, 'pow'):
+ result = f.rep.pow(n)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'pow')
+
+ return f.per(result)
+
+ def pdiv(f, g):
+ """
+ Polynomial pseudo-division of ``f`` by ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).pdiv(Poly(2*x - 4, x))
+ (Poly(2*x + 4, x, domain='ZZ'), Poly(20, x, domain='ZZ'))
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'pdiv'):
+ q, r = F.pdiv(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'pdiv')
+
+ return per(q), per(r)
+
+ def prem(f, g):
+ """
+ Polynomial pseudo-remainder of ``f`` by ``g``.
+
+ Caveat: The function prem(f, g, x) can be safely used to compute
+ in Z[x] _only_ subresultant polynomial remainder sequences (prs's).
+
+ To safely compute Euclidean and Sturmian prs's in Z[x]
+ employ anyone of the corresponding functions found in
+ the module sympy.polys.subresultants_qq_zz. The functions
+ in the module with suffix _pg compute prs's in Z[x] employing
+ rem(f, g, x), whereas the functions with suffix _amv
+ compute prs's in Z[x] employing rem_z(f, g, x).
+
+ The function rem_z(f, g, x) differs from prem(f, g, x) in that
+ to compute the remainder polynomials in Z[x] it premultiplies
+ the divident times the absolute value of the leading coefficient
+ of the divisor raised to the power degree(f, x) - degree(g, x) + 1.
+
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).prem(Poly(2*x - 4, x))
+ Poly(20, x, domain='ZZ')
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'prem'):
+ result = F.prem(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'prem')
+
+ return per(result)
+
+ def pquo(f, g):
+ """
+ Polynomial pseudo-quotient of ``f`` by ``g``.
+
+ See the Caveat note in the function prem(f, g).
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).pquo(Poly(2*x - 4, x))
+ Poly(2*x + 4, x, domain='ZZ')
+
+ >>> Poly(x**2 - 1, x).pquo(Poly(2*x - 2, x))
+ Poly(2*x + 2, x, domain='ZZ')
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'pquo'):
+ result = F.pquo(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'pquo')
+
+ return per(result)
+
+ def pexquo(f, g):
+ """
+ Polynomial exact pseudo-quotient of ``f`` by ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 1, x).pexquo(Poly(2*x - 2, x))
+ Poly(2*x + 2, x, domain='ZZ')
+
+ >>> Poly(x**2 + 1, x).pexquo(Poly(2*x - 4, x))
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: 2*x - 4 does not divide x**2 + 1
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'pexquo'):
+ try:
+ result = F.pexquo(G)
+ except ExactQuotientFailed as exc:
+ raise exc.new(f.as_expr(), g.as_expr())
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'pexquo')
+
+ return per(result)
+
+ def div(f, g, auto=True):
+ """
+ Polynomial division with remainder of ``f`` by ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).div(Poly(2*x - 4, x))
+ (Poly(1/2*x + 1, x, domain='QQ'), Poly(5, x, domain='QQ'))
+
+ >>> Poly(x**2 + 1, x).div(Poly(2*x - 4, x), auto=False)
+ (Poly(0, x, domain='ZZ'), Poly(x**2 + 1, x, domain='ZZ'))
+
+ """
+ dom, per, F, G = f._unify(g)
+ retract = False
+
+ if auto and dom.is_Ring and not dom.is_Field:
+ F, G = F.to_field(), G.to_field()
+ retract = True
+
+ if hasattr(f.rep, 'div'):
+ q, r = F.div(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'div')
+
+ if retract:
+ try:
+ Q, R = q.to_ring(), r.to_ring()
+ except CoercionFailed:
+ pass
+ else:
+ q, r = Q, R
+
+ return per(q), per(r)
+
+ def rem(f, g, auto=True):
+ """
+ Computes the polynomial remainder of ``f`` by ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).rem(Poly(2*x - 4, x))
+ Poly(5, x, domain='ZZ')
+
+ >>> Poly(x**2 + 1, x).rem(Poly(2*x - 4, x), auto=False)
+ Poly(x**2 + 1, x, domain='ZZ')
+
+ """
+ dom, per, F, G = f._unify(g)
+ retract = False
+
+ if auto and dom.is_Ring and not dom.is_Field:
+ F, G = F.to_field(), G.to_field()
+ retract = True
+
+ if hasattr(f.rep, 'rem'):
+ r = F.rem(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'rem')
+
+ if retract:
+ try:
+ r = r.to_ring()
+ except CoercionFailed:
+ pass
+
+ return per(r)
+
+ def quo(f, g, auto=True):
+ """
+ Computes polynomial quotient of ``f`` by ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).quo(Poly(2*x - 4, x))
+ Poly(1/2*x + 1, x, domain='QQ')
+
+ >>> Poly(x**2 - 1, x).quo(Poly(x - 1, x))
+ Poly(x + 1, x, domain='ZZ')
+
+ """
+ dom, per, F, G = f._unify(g)
+ retract = False
+
+ if auto and dom.is_Ring and not dom.is_Field:
+ F, G = F.to_field(), G.to_field()
+ retract = True
+
+ if hasattr(f.rep, 'quo'):
+ q = F.quo(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'quo')
+
+ if retract:
+ try:
+ q = q.to_ring()
+ except CoercionFailed:
+ pass
+
+ return per(q)
+
+ def exquo(f, g, auto=True):
+ """
+ Computes polynomial exact quotient of ``f`` by ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 1, x).exquo(Poly(x - 1, x))
+ Poly(x + 1, x, domain='ZZ')
+
+ >>> Poly(x**2 + 1, x).exquo(Poly(2*x - 4, x))
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: 2*x - 4 does not divide x**2 + 1
+
+ """
+ dom, per, F, G = f._unify(g)
+ retract = False
+
+ if auto and dom.is_Ring and not dom.is_Field:
+ F, G = F.to_field(), G.to_field()
+ retract = True
+
+ if hasattr(f.rep, 'exquo'):
+ try:
+ q = F.exquo(G)
+ except ExactQuotientFailed as exc:
+ raise exc.new(f.as_expr(), g.as_expr())
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'exquo')
+
+ if retract:
+ try:
+ q = q.to_ring()
+ except CoercionFailed:
+ pass
+
+ return per(q)
+
+ def _gen_to_level(f, gen):
+ """Returns level associated with the given generator. """
+ if isinstance(gen, int):
+ length = len(f.gens)
+
+ if -length <= gen < length:
+ if gen < 0:
+ return length + gen
+ else:
+ return gen
+ else:
+ raise PolynomialError("-%s <= gen < %s expected, got %s" %
+ (length, length, gen))
+ else:
+ try:
+ return f.gens.index(sympify(gen))
+ except ValueError:
+ raise PolynomialError(
+ "a valid generator expected, got %s" % gen)
+
+ def degree(f, gen=0):
+ """
+ Returns degree of ``f`` in ``x_j``.
+
+ The degree of 0 is negative infinity.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + y*x + 1, x, y).degree()
+ 2
+ >>> Poly(x**2 + y*x + y, x, y).degree(y)
+ 1
+ >>> Poly(0, x).degree()
+ -oo
+
+ """
+ j = f._gen_to_level(gen)
+
+ if hasattr(f.rep, 'degree'):
+ d = f.rep.degree(j)
+ if d < 0:
+ d = S.NegativeInfinity
+ return d
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'degree')
+
+ def degree_list(f):
+ """
+ Returns a list of degrees of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + y*x + 1, x, y).degree_list()
+ (2, 1)
+
+ """
+ if hasattr(f.rep, 'degree_list'):
+ return f.rep.degree_list()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'degree_list')
+
+ def total_degree(f):
+ """
+ Returns the total degree of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + y*x + 1, x, y).total_degree()
+ 2
+ >>> Poly(x + y**5, x, y).total_degree()
+ 5
+
+ """
+ if hasattr(f.rep, 'total_degree'):
+ return f.rep.total_degree()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'total_degree')
+
+ def homogenize(f, s):
+ """
+ Returns the homogeneous polynomial of ``f``.
+
+ A homogeneous polynomial is a polynomial whose all monomials with
+ non-zero coefficients have the same total degree. If you only
+ want to check if a polynomial is homogeneous, then use
+ :func:`Poly.is_homogeneous`. If you want not only to check if a
+ polynomial is homogeneous but also compute its homogeneous order,
+ then use :func:`Poly.homogeneous_order`.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y, z
+
+ >>> f = Poly(x**5 + 2*x**2*y**2 + 9*x*y**3)
+ >>> f.homogenize(z)
+ Poly(x**5 + 2*x**2*y**2*z + 9*x*y**3*z, x, y, z, domain='ZZ')
+
+ """
+ if not isinstance(s, Symbol):
+ raise TypeError("``Symbol`` expected, got %s" % type(s))
+ if s in f.gens:
+ i = f.gens.index(s)
+ gens = f.gens
+ else:
+ i = len(f.gens)
+ gens = f.gens + (s,)
+ if hasattr(f.rep, 'homogenize'):
+ return f.per(f.rep.homogenize(i), gens=gens)
+ raise OperationNotSupported(f, 'homogeneous_order')
+
+ def homogeneous_order(f):
+ """
+ Returns the homogeneous order of ``f``.
+
+ A homogeneous polynomial is a polynomial whose all monomials with
+ non-zero coefficients have the same total degree. This degree is
+ the homogeneous order of ``f``. If you only want to check if a
+ polynomial is homogeneous, then use :func:`Poly.is_homogeneous`.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> f = Poly(x**5 + 2*x**3*y**2 + 9*x*y**4)
+ >>> f.homogeneous_order()
+ 5
+
+ """
+ if hasattr(f.rep, 'homogeneous_order'):
+ return f.rep.homogeneous_order()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'homogeneous_order')
+
+ def LC(f, order=None):
+ """
+ Returns the leading coefficient of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(4*x**3 + 2*x**2 + 3*x, x).LC()
+ 4
+
+ """
+ if order is not None:
+ return f.coeffs(order)[0]
+
+ if hasattr(f.rep, 'LC'):
+ result = f.rep.LC()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'LC')
+
+ return f.rep.dom.to_sympy(result)
+
+ def TC(f):
+ """
+ Returns the trailing coefficient of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**3 + 2*x**2 + 3*x, x).TC()
+ 0
+
+ """
+ if hasattr(f.rep, 'TC'):
+ result = f.rep.TC()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'TC')
+
+ return f.rep.dom.to_sympy(result)
+
+ def EC(f, order=None):
+ """
+ Returns the last non-zero coefficient of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**3 + 2*x**2 + 3*x, x).EC()
+ 3
+
+ """
+ if hasattr(f.rep, 'coeffs'):
+ return f.coeffs(order)[-1]
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'EC')
+
+ def coeff_monomial(f, monom):
+ """
+ Returns the coefficient of ``monom`` in ``f`` if there, else None.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, exp
+ >>> from sympy.abc import x, y
+
+ >>> p = Poly(24*x*y*exp(8) + 23*x, x, y)
+
+ >>> p.coeff_monomial(x)
+ 23
+ >>> p.coeff_monomial(y)
+ 0
+ >>> p.coeff_monomial(x*y)
+ 24*exp(8)
+
+ Note that ``Expr.coeff()`` behaves differently, collecting terms
+ if possible; the Poly must be converted to an Expr to use that
+ method, however:
+
+ >>> p.as_expr().coeff(x)
+ 24*y*exp(8) + 23
+ >>> p.as_expr().coeff(y)
+ 24*x*exp(8)
+ >>> p.as_expr().coeff(x*y)
+ 24*exp(8)
+
+ See Also
+ ========
+ nth: more efficient query using exponents of the monomial's generators
+
+ """
+ return f.nth(*Monomial(monom, f.gens).exponents)
+
+ def nth(f, *N):
+ """
+ Returns the ``n``-th coefficient of ``f`` where ``N`` are the
+ exponents of the generators in the term of interest.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, sqrt
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**3 + 2*x**2 + 3*x, x).nth(2)
+ 2
+ >>> Poly(x**3 + 2*x*y**2 + y**2, x, y).nth(1, 2)
+ 2
+ >>> Poly(4*sqrt(x)*y)
+ Poly(4*y*(sqrt(x)), y, sqrt(x), domain='ZZ')
+ >>> _.nth(1, 1)
+ 4
+
+ See Also
+ ========
+ coeff_monomial
+
+ """
+ if hasattr(f.rep, 'nth'):
+ if len(N) != len(f.gens):
+ raise ValueError('exponent of each generator must be specified')
+ result = f.rep.nth(*list(map(int, N)))
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'nth')
+
+ return f.rep.dom.to_sympy(result)
+
+ def coeff(f, x, n=1, right=False):
+ # the semantics of coeff_monomial and Expr.coeff are different;
+ # if someone is working with a Poly, they should be aware of the
+ # differences and chose the method best suited for the query.
+ # Alternatively, a pure-polys method could be written here but
+ # at this time the ``right`` keyword would be ignored because Poly
+ # doesn't work with non-commutatives.
+ raise NotImplementedError(
+ 'Either convert to Expr with `as_expr` method '
+ 'to use Expr\'s coeff method or else use the '
+ '`coeff_monomial` method of Polys.')
+
+ def LM(f, order=None):
+ """
+ Returns the leading monomial of ``f``.
+
+ The Leading monomial signifies the monomial having
+ the highest power of the principal generator in the
+ expression f.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(4*x**2 + 2*x*y**2 + x*y + 3*y, x, y).LM()
+ x**2*y**0
+
+ """
+ return Monomial(f.monoms(order)[0], f.gens)
+
+ def EM(f, order=None):
+ """
+ Returns the last non-zero monomial of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(4*x**2 + 2*x*y**2 + x*y + 3*y, x, y).EM()
+ x**0*y**1
+
+ """
+ return Monomial(f.monoms(order)[-1], f.gens)
+
+ def LT(f, order=None):
+ """
+ Returns the leading term of ``f``.
+
+ The Leading term signifies the term having
+ the highest power of the principal generator in the
+ expression f along with its coefficient.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(4*x**2 + 2*x*y**2 + x*y + 3*y, x, y).LT()
+ (x**2*y**0, 4)
+
+ """
+ monom, coeff = f.terms(order)[0]
+ return Monomial(monom, f.gens), coeff
+
+ def ET(f, order=None):
+ """
+ Returns the last non-zero term of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(4*x**2 + 2*x*y**2 + x*y + 3*y, x, y).ET()
+ (x**0*y**1, 3)
+
+ """
+ monom, coeff = f.terms(order)[-1]
+ return Monomial(monom, f.gens), coeff
+
+ def max_norm(f):
+ """
+ Returns maximum norm of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(-x**2 + 2*x - 3, x).max_norm()
+ 3
+
+ """
+ if hasattr(f.rep, 'max_norm'):
+ result = f.rep.max_norm()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'max_norm')
+
+ return f.rep.dom.to_sympy(result)
+
+ def l1_norm(f):
+ """
+ Returns l1 norm of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(-x**2 + 2*x - 3, x).l1_norm()
+ 6
+
+ """
+ if hasattr(f.rep, 'l1_norm'):
+ result = f.rep.l1_norm()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'l1_norm')
+
+ return f.rep.dom.to_sympy(result)
+
+ def clear_denoms(self, convert=False):
+ """
+ Clear denominators, but keep the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, S, QQ
+ >>> from sympy.abc import x
+
+ >>> f = Poly(x/2 + S(1)/3, x, domain=QQ)
+
+ >>> f.clear_denoms()
+ (6, Poly(3*x + 2, x, domain='QQ'))
+ >>> f.clear_denoms(convert=True)
+ (6, Poly(3*x + 2, x, domain='ZZ'))
+
+ """
+ f = self
+
+ if not f.rep.dom.is_Field:
+ return S.One, f
+
+ dom = f.get_domain()
+ if dom.has_assoc_Ring:
+ dom = f.rep.dom.get_ring()
+
+ if hasattr(f.rep, 'clear_denoms'):
+ coeff, result = f.rep.clear_denoms()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'clear_denoms')
+
+ coeff, f = dom.to_sympy(coeff), f.per(result)
+
+ if not convert or not dom.has_assoc_Ring:
+ return coeff, f
+ else:
+ return coeff, f.to_ring()
+
+ def rat_clear_denoms(self, g):
+ """
+ Clear denominators in a rational function ``f/g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> f = Poly(x**2/y + 1, x)
+ >>> g = Poly(x**3 + y, x)
+
+ >>> p, q = f.rat_clear_denoms(g)
+
+ >>> p
+ Poly(x**2 + y, x, domain='ZZ[y]')
+ >>> q
+ Poly(y*x**3 + y**2, x, domain='ZZ[y]')
+
+ """
+ f = self
+
+ dom, per, f, g = f._unify(g)
+
+ f = per(f)
+ g = per(g)
+
+ if not (dom.is_Field and dom.has_assoc_Ring):
+ return f, g
+
+ a, f = f.clear_denoms(convert=True)
+ b, g = g.clear_denoms(convert=True)
+
+ f = f.mul_ground(b)
+ g = g.mul_ground(a)
+
+ return f, g
+
+ def integrate(self, *specs, **args):
+ """
+ Computes indefinite integral of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + 2*x + 1, x).integrate()
+ Poly(1/3*x**3 + x**2 + x, x, domain='QQ')
+
+ >>> Poly(x*y**2 + x, x, y).integrate((0, 1), (1, 0))
+ Poly(1/2*x**2*y**2 + 1/2*x**2, x, y, domain='QQ')
+
+ """
+ f = self
+
+ if args.get('auto', True) and f.rep.dom.is_Ring:
+ f = f.to_field()
+
+ if hasattr(f.rep, 'integrate'):
+ if not specs:
+ return f.per(f.rep.integrate(m=1))
+
+ rep = f.rep
+
+ for spec in specs:
+ if isinstance(spec, tuple):
+ gen, m = spec
+ else:
+ gen, m = spec, 1
+
+ rep = rep.integrate(int(m), f._gen_to_level(gen))
+
+ return f.per(rep)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'integrate')
+
+ def diff(f, *specs, **kwargs):
+ """
+ Computes partial derivative of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + 2*x + 1, x).diff()
+ Poly(2*x + 2, x, domain='ZZ')
+
+ >>> Poly(x*y**2 + x, x, y).diff((0, 0), (1, 1))
+ Poly(2*x*y, x, y, domain='ZZ')
+
+ """
+ if not kwargs.get('evaluate', True):
+ return Derivative(f, *specs, **kwargs)
+
+ if hasattr(f.rep, 'diff'):
+ if not specs:
+ return f.per(f.rep.diff(m=1))
+
+ rep = f.rep
+
+ for spec in specs:
+ if isinstance(spec, tuple):
+ gen, m = spec
+ else:
+ gen, m = spec, 1
+
+ rep = rep.diff(int(m), f._gen_to_level(gen))
+
+ return f.per(rep)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'diff')
+
+ _eval_derivative = diff
+
+ def eval(self, x, a=None, auto=True):
+ """
+ Evaluate ``f`` at ``a`` in the given variable.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y, z
+
+ >>> Poly(x**2 + 2*x + 3, x).eval(2)
+ 11
+
+ >>> Poly(2*x*y + 3*x + y + 2, x, y).eval(x, 2)
+ Poly(5*y + 8, y, domain='ZZ')
+
+ >>> f = Poly(2*x*y + 3*x + y + 2*z, x, y, z)
+
+ >>> f.eval({x: 2})
+ Poly(5*y + 2*z + 6, y, z, domain='ZZ')
+ >>> f.eval({x: 2, y: 5})
+ Poly(2*z + 31, z, domain='ZZ')
+ >>> f.eval({x: 2, y: 5, z: 7})
+ 45
+
+ >>> f.eval((2, 5))
+ Poly(2*z + 31, z, domain='ZZ')
+ >>> f(2, 5)
+ Poly(2*z + 31, z, domain='ZZ')
+
+ """
+ f = self
+
+ if a is None:
+ if isinstance(x, dict):
+ mapping = x
+
+ for gen, value in mapping.items():
+ f = f.eval(gen, value)
+
+ return f
+ elif isinstance(x, (tuple, list)):
+ values = x
+
+ if len(values) > len(f.gens):
+ raise ValueError("too many values provided")
+
+ for gen, value in zip(f.gens, values):
+ f = f.eval(gen, value)
+
+ return f
+ else:
+ j, a = 0, x
+ else:
+ j = f._gen_to_level(x)
+
+ if not hasattr(f.rep, 'eval'): # pragma: no cover
+ raise OperationNotSupported(f, 'eval')
+
+ try:
+ result = f.rep.eval(a, j)
+ except CoercionFailed:
+ if not auto:
+ raise DomainError("Cannot evaluate at %s in %s" % (a, f.rep.dom))
+ else:
+ a_domain, [a] = construct_domain([a])
+ new_domain = f.get_domain().unify_with_symbols(a_domain, f.gens)
+
+ f = f.set_domain(new_domain)
+ a = new_domain.convert(a, a_domain)
+
+ result = f.rep.eval(a, j)
+
+ return f.per(result, remove=j)
+
+ def __call__(f, *values):
+ """
+ Evaluate ``f`` at the give values.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y, z
+
+ >>> f = Poly(2*x*y + 3*x + y + 2*z, x, y, z)
+
+ >>> f(2)
+ Poly(5*y + 2*z + 6, y, z, domain='ZZ')
+ >>> f(2, 5)
+ Poly(2*z + 31, z, domain='ZZ')
+ >>> f(2, 5, 7)
+ 45
+
+ """
+ return f.eval(values)
+
+ def half_gcdex(f, g, auto=True):
+ """
+ Half extended Euclidean algorithm of ``f`` and ``g``.
+
+ Returns ``(s, h)`` such that ``h = gcd(f, g)`` and ``s*f = h (mod g)``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = x**4 - 2*x**3 - 6*x**2 + 12*x + 15
+ >>> g = x**3 + x**2 - 4*x - 4
+
+ >>> Poly(f).half_gcdex(Poly(g))
+ (Poly(-1/5*x + 3/5, x, domain='QQ'), Poly(x + 1, x, domain='QQ'))
+
+ """
+ dom, per, F, G = f._unify(g)
+
+ if auto and dom.is_Ring:
+ F, G = F.to_field(), G.to_field()
+
+ if hasattr(f.rep, 'half_gcdex'):
+ s, h = F.half_gcdex(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'half_gcdex')
+
+ return per(s), per(h)
+
+ def gcdex(f, g, auto=True):
+ """
+ Extended Euclidean algorithm of ``f`` and ``g``.
+
+ Returns ``(s, t, h)`` such that ``h = gcd(f, g)`` and ``s*f + t*g = h``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = x**4 - 2*x**3 - 6*x**2 + 12*x + 15
+ >>> g = x**3 + x**2 - 4*x - 4
+
+ >>> Poly(f).gcdex(Poly(g))
+ (Poly(-1/5*x + 3/5, x, domain='QQ'),
+ Poly(1/5*x**2 - 6/5*x + 2, x, domain='QQ'),
+ Poly(x + 1, x, domain='QQ'))
+
+ """
+ dom, per, F, G = f._unify(g)
+
+ if auto and dom.is_Ring:
+ F, G = F.to_field(), G.to_field()
+
+ if hasattr(f.rep, 'gcdex'):
+ s, t, h = F.gcdex(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'gcdex')
+
+ return per(s), per(t), per(h)
+
+ def invert(f, g, auto=True):
+ """
+ Invert ``f`` modulo ``g`` when possible.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 1, x).invert(Poly(2*x - 1, x))
+ Poly(-4/3, x, domain='QQ')
+
+ >>> Poly(x**2 - 1, x).invert(Poly(x - 1, x))
+ Traceback (most recent call last):
+ ...
+ NotInvertible: zero divisor
+
+ """
+ dom, per, F, G = f._unify(g)
+
+ if auto and dom.is_Ring:
+ F, G = F.to_field(), G.to_field()
+
+ if hasattr(f.rep, 'invert'):
+ result = F.invert(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'invert')
+
+ return per(result)
+
+ def revert(f, n):
+ """
+ Compute ``f**(-1)`` mod ``x**n``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(1, x).revert(2)
+ Poly(1, x, domain='ZZ')
+
+ >>> Poly(1 + x, x).revert(1)
+ Poly(1, x, domain='ZZ')
+
+ >>> Poly(x**2 - 2, x).revert(2)
+ Traceback (most recent call last):
+ ...
+ NotReversible: only units are reversible in a ring
+
+ >>> Poly(1/x, x).revert(1)
+ Traceback (most recent call last):
+ ...
+ PolynomialError: 1/x contains an element of the generators set
+
+ """
+ if hasattr(f.rep, 'revert'):
+ result = f.rep.revert(int(n))
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'revert')
+
+ return f.per(result)
+
+ def subresultants(f, g):
+ """
+ Computes the subresultant PRS of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 1, x).subresultants(Poly(x**2 - 1, x))
+ [Poly(x**2 + 1, x, domain='ZZ'),
+ Poly(x**2 - 1, x, domain='ZZ'),
+ Poly(-2, x, domain='ZZ')]
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'subresultants'):
+ result = F.subresultants(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'subresultants')
+
+ return list(map(per, result))
+
+ def resultant(f, g, includePRS=False):
+ """
+ Computes the resultant of ``f`` and ``g`` via PRS.
+
+ If includePRS=True, it includes the subresultant PRS in the result.
+ Because the PRS is used to calculate the resultant, this is more
+ efficient than calling :func:`subresultants` separately.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = Poly(x**2 + 1, x)
+
+ >>> f.resultant(Poly(x**2 - 1, x))
+ 4
+ >>> f.resultant(Poly(x**2 - 1, x), includePRS=True)
+ (4, [Poly(x**2 + 1, x, domain='ZZ'), Poly(x**2 - 1, x, domain='ZZ'),
+ Poly(-2, x, domain='ZZ')])
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'resultant'):
+ if includePRS:
+ result, R = F.resultant(G, includePRS=includePRS)
+ else:
+ result = F.resultant(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'resultant')
+
+ if includePRS:
+ return (per(result, remove=0), list(map(per, R)))
+ return per(result, remove=0)
+
+ def discriminant(f):
+ """
+ Computes the discriminant of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + 2*x + 3, x).discriminant()
+ -8
+
+ """
+ if hasattr(f.rep, 'discriminant'):
+ result = f.rep.discriminant()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'discriminant')
+
+ return f.per(result, remove=0)
+
+ def dispersionset(f, g=None):
+ r"""Compute the *dispersion set* of two polynomials.
+
+ For two polynomials `f(x)` and `g(x)` with `\deg f > 0`
+ and `\deg g > 0` the dispersion set `\operatorname{J}(f, g)` is defined as:
+
+ .. math::
+ \operatorname{J}(f, g)
+ & := \{a \in \mathbb{N}_0 | \gcd(f(x), g(x+a)) \neq 1\} \\
+ & = \{a \in \mathbb{N}_0 | \deg \gcd(f(x), g(x+a)) \geq 1\}
+
+ For a single polynomial one defines `\operatorname{J}(f) := \operatorname{J}(f, f)`.
+
+ Examples
+ ========
+
+ >>> from sympy import poly
+ >>> from sympy.polys.dispersion import dispersion, dispersionset
+ >>> from sympy.abc import x
+
+ Dispersion set and dispersion of a simple polynomial:
+
+ >>> fp = poly((x - 3)*(x + 3), x)
+ >>> sorted(dispersionset(fp))
+ [0, 6]
+ >>> dispersion(fp)
+ 6
+
+ Note that the definition of the dispersion is not symmetric:
+
+ >>> fp = poly(x**4 - 3*x**2 + 1, x)
+ >>> gp = fp.shift(-3)
+ >>> sorted(dispersionset(fp, gp))
+ [2, 3, 4]
+ >>> dispersion(fp, gp)
+ 4
+ >>> sorted(dispersionset(gp, fp))
+ []
+ >>> dispersion(gp, fp)
+ -oo
+
+ Computing the dispersion also works over field extensions:
+
+ >>> from sympy import sqrt
+ >>> fp = poly(x**2 + sqrt(5)*x - 1, x, domain='QQ')
+ >>> gp = poly(x**2 + (2 + sqrt(5))*x + sqrt(5), x, domain='QQ')
+ >>> sorted(dispersionset(fp, gp))
+ [2]
+ >>> sorted(dispersionset(gp, fp))
+ [1, 4]
+
+ We can even perform the computations for polynomials
+ having symbolic coefficients:
+
+ >>> from sympy.abc import a
+ >>> fp = poly(4*x**4 + (4*a + 8)*x**3 + (a**2 + 6*a + 4)*x**2 + (a**2 + 2*a)*x, x)
+ >>> sorted(dispersionset(fp))
+ [0, 1]
+
+ See Also
+ ========
+
+ dispersion
+
+ References
+ ==========
+
+ 1. [ManWright94]_
+ 2. [Koepf98]_
+ 3. [Abramov71]_
+ 4. [Man93]_
+ """
+ from sympy.polys.dispersion import dispersionset
+ return dispersionset(f, g)
+
+ def dispersion(f, g=None):
+ r"""Compute the *dispersion* of polynomials.
+
+ For two polynomials `f(x)` and `g(x)` with `\deg f > 0`
+ and `\deg g > 0` the dispersion `\operatorname{dis}(f, g)` is defined as:
+
+ .. math::
+ \operatorname{dis}(f, g)
+ & := \max\{ J(f,g) \cup \{0\} \} \\
+ & = \max\{ \{a \in \mathbb{N} | \gcd(f(x), g(x+a)) \neq 1\} \cup \{0\} \}
+
+ and for a single polynomial `\operatorname{dis}(f) := \operatorname{dis}(f, f)`.
+
+ Examples
+ ========
+
+ >>> from sympy import poly
+ >>> from sympy.polys.dispersion import dispersion, dispersionset
+ >>> from sympy.abc import x
+
+ Dispersion set and dispersion of a simple polynomial:
+
+ >>> fp = poly((x - 3)*(x + 3), x)
+ >>> sorted(dispersionset(fp))
+ [0, 6]
+ >>> dispersion(fp)
+ 6
+
+ Note that the definition of the dispersion is not symmetric:
+
+ >>> fp = poly(x**4 - 3*x**2 + 1, x)
+ >>> gp = fp.shift(-3)
+ >>> sorted(dispersionset(fp, gp))
+ [2, 3, 4]
+ >>> dispersion(fp, gp)
+ 4
+ >>> sorted(dispersionset(gp, fp))
+ []
+ >>> dispersion(gp, fp)
+ -oo
+
+ Computing the dispersion also works over field extensions:
+
+ >>> from sympy import sqrt
+ >>> fp = poly(x**2 + sqrt(5)*x - 1, x, domain='QQ')
+ >>> gp = poly(x**2 + (2 + sqrt(5))*x + sqrt(5), x, domain='QQ')
+ >>> sorted(dispersionset(fp, gp))
+ [2]
+ >>> sorted(dispersionset(gp, fp))
+ [1, 4]
+
+ We can even perform the computations for polynomials
+ having symbolic coefficients:
+
+ >>> from sympy.abc import a
+ >>> fp = poly(4*x**4 + (4*a + 8)*x**3 + (a**2 + 6*a + 4)*x**2 + (a**2 + 2*a)*x, x)
+ >>> sorted(dispersionset(fp))
+ [0, 1]
+
+ See Also
+ ========
+
+ dispersionset
+
+ References
+ ==========
+
+ 1. [ManWright94]_
+ 2. [Koepf98]_
+ 3. [Abramov71]_
+ 4. [Man93]_
+ """
+ from sympy.polys.dispersion import dispersion
+ return dispersion(f, g)
+
+ def cofactors(f, g):
+ """
+ Returns the GCD of ``f`` and ``g`` and their cofactors.
+
+ Returns polynomials ``(h, cff, cfg)`` such that ``h = gcd(f, g)``, and
+ ``cff = quo(f, h)`` and ``cfg = quo(g, h)`` are, so called, cofactors
+ of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 1, x).cofactors(Poly(x**2 - 3*x + 2, x))
+ (Poly(x - 1, x, domain='ZZ'),
+ Poly(x + 1, x, domain='ZZ'),
+ Poly(x - 2, x, domain='ZZ'))
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'cofactors'):
+ h, cff, cfg = F.cofactors(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'cofactors')
+
+ return per(h), per(cff), per(cfg)
+
+ def gcd(f, g):
+ """
+ Returns the polynomial GCD of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 1, x).gcd(Poly(x**2 - 3*x + 2, x))
+ Poly(x - 1, x, domain='ZZ')
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'gcd'):
+ result = F.gcd(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'gcd')
+
+ return per(result)
+
+ def lcm(f, g):
+ """
+ Returns polynomial LCM of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 1, x).lcm(Poly(x**2 - 3*x + 2, x))
+ Poly(x**3 - 2*x**2 - x + 2, x, domain='ZZ')
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'lcm'):
+ result = F.lcm(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'lcm')
+
+ return per(result)
+
+ def trunc(f, p):
+ """
+ Reduce ``f`` modulo a constant ``p``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(2*x**3 + 3*x**2 + 5*x + 7, x).trunc(3)
+ Poly(-x**3 - x + 1, x, domain='ZZ')
+
+ """
+ p = f.rep.dom.convert(p)
+
+ if hasattr(f.rep, 'trunc'):
+ result = f.rep.trunc(p)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'trunc')
+
+ return f.per(result)
+
+ def monic(self, auto=True):
+ """
+ Divides all coefficients by ``LC(f)``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, ZZ
+ >>> from sympy.abc import x
+
+ >>> Poly(3*x**2 + 6*x + 9, x, domain=ZZ).monic()
+ Poly(x**2 + 2*x + 3, x, domain='QQ')
+
+ >>> Poly(3*x**2 + 4*x + 2, x, domain=ZZ).monic()
+ Poly(x**2 + 4/3*x + 2/3, x, domain='QQ')
+
+ """
+ f = self
+
+ if auto and f.rep.dom.is_Ring:
+ f = f.to_field()
+
+ if hasattr(f.rep, 'monic'):
+ result = f.rep.monic()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'monic')
+
+ return f.per(result)
+
+ def content(f):
+ """
+ Returns the GCD of polynomial coefficients.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(6*x**2 + 8*x + 12, x).content()
+ 2
+
+ """
+ if hasattr(f.rep, 'content'):
+ result = f.rep.content()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'content')
+
+ return f.rep.dom.to_sympy(result)
+
+ def primitive(f):
+ """
+ Returns the content and a primitive form of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(2*x**2 + 8*x + 12, x).primitive()
+ (2, Poly(x**2 + 4*x + 6, x, domain='ZZ'))
+
+ """
+ if hasattr(f.rep, 'primitive'):
+ cont, result = f.rep.primitive()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'primitive')
+
+ return f.rep.dom.to_sympy(cont), f.per(result)
+
+ def compose(f, g):
+ """
+ Computes the functional composition of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + x, x).compose(Poly(x - 1, x))
+ Poly(x**2 - x, x, domain='ZZ')
+
+ """
+ _, per, F, G = f._unify(g)
+
+ if hasattr(f.rep, 'compose'):
+ result = F.compose(G)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'compose')
+
+ return per(result)
+
+ def decompose(f):
+ """
+ Computes a functional decomposition of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**4 + 2*x**3 - x - 1, x, domain='ZZ').decompose()
+ [Poly(x**2 - x - 1, x, domain='ZZ'), Poly(x**2 + x, x, domain='ZZ')]
+
+ """
+ if hasattr(f.rep, 'decompose'):
+ result = f.rep.decompose()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'decompose')
+
+ return list(map(f.per, result))
+
+ def shift(f, a):
+ """
+ Efficiently compute Taylor shift ``f(x + a)``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 2*x + 1, x).shift(2)
+ Poly(x**2 + 2*x + 1, x, domain='ZZ')
+
+ See Also
+ ========
+
+ shift_list: Analogous method for multivariate polynomials.
+ """
+ return f.per(f.rep.shift(a))
+
+ def shift_list(f, a):
+ """
+ Efficiently compute Taylor shift ``f(X + A)``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x*y, [x,y]).shift_list([1, 2]) == Poly((x+1)*(y+2), [x,y])
+ True
+
+ See Also
+ ========
+
+ shift: Analogous method for univariate polynomials.
+ """
+ return f.per(f.rep.shift_list(a))
+
+ def transform(f, p, q):
+ """
+ Efficiently evaluate the functional transformation ``q**n * f(p/q)``.
+
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 2*x + 1, x).transform(Poly(x + 1, x), Poly(x - 1, x))
+ Poly(4, x, domain='ZZ')
+
+ """
+ P, Q = p.unify(q)
+ F, P = f.unify(P)
+ F, Q = F.unify(Q)
+
+ if hasattr(F.rep, 'transform'):
+ result = F.rep.transform(P.rep, Q.rep)
+ else: # pragma: no cover
+ raise OperationNotSupported(F, 'transform')
+
+ return F.per(result)
+
+ def sturm(self, auto=True):
+ """
+ Computes the Sturm sequence of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**3 - 2*x**2 + x - 3, x).sturm()
+ [Poly(x**3 - 2*x**2 + x - 3, x, domain='QQ'),
+ Poly(3*x**2 - 4*x + 1, x, domain='QQ'),
+ Poly(2/9*x + 25/9, x, domain='QQ'),
+ Poly(-2079/4, x, domain='QQ')]
+
+ """
+ f = self
+
+ if auto and f.rep.dom.is_Ring:
+ f = f.to_field()
+
+ if hasattr(f.rep, 'sturm'):
+ result = f.rep.sturm()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'sturm')
+
+ return list(map(f.per, result))
+
+ def gff_list(f):
+ """
+ Computes greatest factorial factorization of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = x**5 + 2*x**4 - x**3 - 2*x**2
+
+ >>> Poly(f).gff_list()
+ [(Poly(x, x, domain='ZZ'), 1), (Poly(x + 2, x, domain='ZZ'), 4)]
+
+ """
+ if hasattr(f.rep, 'gff_list'):
+ result = f.rep.gff_list()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'gff_list')
+
+ return [(f.per(g), k) for g, k in result]
+
+ def norm(f):
+ """
+ Computes the product, ``Norm(f)``, of the conjugates of
+ a polynomial ``f`` defined over a number field ``K``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, sqrt
+ >>> from sympy.abc import x
+
+ >>> a, b = sqrt(2), sqrt(3)
+
+ A polynomial over a quadratic extension.
+ Two conjugates x - a and x + a.
+
+ >>> f = Poly(x - a, x, extension=a)
+ >>> f.norm()
+ Poly(x**2 - 2, x, domain='QQ')
+
+ A polynomial over a quartic extension.
+ Four conjugates x - a, x - a, x + a and x + a.
+
+ >>> f = Poly(x - a, x, extension=(a, b))
+ >>> f.norm()
+ Poly(x**4 - 4*x**2 + 4, x, domain='QQ')
+
+ """
+ if hasattr(f.rep, 'norm'):
+ r = f.rep.norm()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'norm')
+
+ return f.per(r)
+
+ def sqf_norm(f):
+ """
+ Computes square-free norm of ``f``.
+
+ Returns ``s``, ``f``, ``r``, such that ``g(x) = f(x-sa)`` and
+ ``r(x) = Norm(g(x))`` is a square-free polynomial over ``K``,
+ where ``a`` is the algebraic extension of the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, sqrt
+ >>> from sympy.abc import x
+
+ >>> s, f, r = Poly(x**2 + 1, x, extension=[sqrt(3)]).sqf_norm()
+
+ >>> s
+ [1]
+ >>> f
+ Poly(x**2 - 2*sqrt(3)*x + 4, x, domain='QQ')
+ >>> r
+ Poly(x**4 - 4*x**2 + 16, x, domain='QQ')
+
+ """
+ if hasattr(f.rep, 'sqf_norm'):
+ s, g, r = f.rep.sqf_norm()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'sqf_norm')
+
+ return s, f.per(g), f.per(r)
+
+ def sqf_part(f):
+ """
+ Computes square-free part of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**3 - 3*x - 2, x).sqf_part()
+ Poly(x**2 - x - 2, x, domain='ZZ')
+
+ """
+ if hasattr(f.rep, 'sqf_part'):
+ result = f.rep.sqf_part()
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'sqf_part')
+
+ return f.per(result)
+
+ def sqf_list(f, all=False):
+ """
+ Returns a list of square-free factors of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = 2*x**5 + 16*x**4 + 50*x**3 + 76*x**2 + 56*x + 16
+
+ >>> Poly(f).sqf_list()
+ (2, [(Poly(x + 1, x, domain='ZZ'), 2),
+ (Poly(x + 2, x, domain='ZZ'), 3)])
+
+ >>> Poly(f).sqf_list(all=True)
+ (2, [(Poly(1, x, domain='ZZ'), 1),
+ (Poly(x + 1, x, domain='ZZ'), 2),
+ (Poly(x + 2, x, domain='ZZ'), 3)])
+
+ """
+ if hasattr(f.rep, 'sqf_list'):
+ coeff, factors = f.rep.sqf_list(all)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'sqf_list')
+
+ return f.rep.dom.to_sympy(coeff), [(f.per(g), k) for g, k in factors]
+
+ def sqf_list_include(f, all=False):
+ """
+ Returns a list of square-free factors of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, expand
+ >>> from sympy.abc import x
+
+ >>> f = expand(2*(x + 1)**3*x**4)
+ >>> f
+ 2*x**7 + 6*x**6 + 6*x**5 + 2*x**4
+
+ >>> Poly(f).sqf_list_include()
+ [(Poly(2, x, domain='ZZ'), 1),
+ (Poly(x + 1, x, domain='ZZ'), 3),
+ (Poly(x, x, domain='ZZ'), 4)]
+
+ >>> Poly(f).sqf_list_include(all=True)
+ [(Poly(2, x, domain='ZZ'), 1),
+ (Poly(1, x, domain='ZZ'), 2),
+ (Poly(x + 1, x, domain='ZZ'), 3),
+ (Poly(x, x, domain='ZZ'), 4)]
+
+ """
+ if hasattr(f.rep, 'sqf_list_include'):
+ factors = f.rep.sqf_list_include(all)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'sqf_list_include')
+
+ return [(f.per(g), k) for g, k in factors]
+
+ def factor_list(f):
+ """
+ Returns a list of irreducible factors of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> f = 2*x**5 + 2*x**4*y + 4*x**3 + 4*x**2*y + 2*x + 2*y
+
+ >>> Poly(f).factor_list()
+ (2, [(Poly(x + y, x, y, domain='ZZ'), 1),
+ (Poly(x**2 + 1, x, y, domain='ZZ'), 2)])
+
+ """
+ if hasattr(f.rep, 'factor_list'):
+ try:
+ coeff, factors = f.rep.factor_list()
+ except DomainError:
+ if f.degree() == 0:
+ return f.as_expr(), []
+ else:
+ return S.One, [(f, 1)]
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'factor_list')
+
+ return f.rep.dom.to_sympy(coeff), [(f.per(g), k) for g, k in factors]
+
+ def factor_list_include(f):
+ """
+ Returns a list of irreducible factors of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> f = 2*x**5 + 2*x**4*y + 4*x**3 + 4*x**2*y + 2*x + 2*y
+
+ >>> Poly(f).factor_list_include()
+ [(Poly(2*x + 2*y, x, y, domain='ZZ'), 1),
+ (Poly(x**2 + 1, x, y, domain='ZZ'), 2)]
+
+ """
+ if hasattr(f.rep, 'factor_list_include'):
+ try:
+ factors = f.rep.factor_list_include()
+ except DomainError:
+ return [(f, 1)]
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'factor_list_include')
+
+ return [(f.per(g), k) for g, k in factors]
+
+ def intervals(f, all=False, eps=None, inf=None, sup=None, fast=False, sqf=False):
+ """
+ Compute isolating intervals for roots of ``f``.
+
+ For real roots the Vincent-Akritas-Strzebonski (VAS) continued fractions method is used.
+
+ References
+ ==========
+ .. [#] Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative Study of Two Real Root
+ Isolation Methods . Nonlinear Analysis: Modelling and Control, Vol. 10, No. 4, 297-304, 2005.
+ .. [#] Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S. Vigklas: Improving the
+ Performance of the Continued Fractions Method Using new Bounds of Positive Roots. Nonlinear
+ Analysis: Modelling and Control, Vol. 13, No. 3, 265-279, 2008.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 3, x).intervals()
+ [((-2, -1), 1), ((1, 2), 1)]
+ >>> Poly(x**2 - 3, x).intervals(eps=1e-2)
+ [((-26/15, -19/11), 1), ((19/11, 26/15), 1)]
+
+ """
+ if eps is not None:
+ eps = QQ.convert(eps)
+
+ if eps <= 0:
+ raise ValueError("'eps' must be a positive rational")
+
+ if inf is not None:
+ inf = QQ.convert(inf)
+ if sup is not None:
+ sup = QQ.convert(sup)
+
+ if hasattr(f.rep, 'intervals'):
+ result = f.rep.intervals(
+ all=all, eps=eps, inf=inf, sup=sup, fast=fast, sqf=sqf)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'intervals')
+
+ if sqf:
+ def _real(interval):
+ s, t = interval
+ return (QQ.to_sympy(s), QQ.to_sympy(t))
+
+ if not all:
+ return list(map(_real, result))
+
+ def _complex(rectangle):
+ (u, v), (s, t) = rectangle
+ return (QQ.to_sympy(u) + I*QQ.to_sympy(v),
+ QQ.to_sympy(s) + I*QQ.to_sympy(t))
+
+ real_part, complex_part = result
+
+ return list(map(_real, real_part)), list(map(_complex, complex_part))
+ else:
+ def _real(interval):
+ (s, t), k = interval
+ return ((QQ.to_sympy(s), QQ.to_sympy(t)), k)
+
+ if not all:
+ return list(map(_real, result))
+
+ def _complex(rectangle):
+ ((u, v), (s, t)), k = rectangle
+ return ((QQ.to_sympy(u) + I*QQ.to_sympy(v),
+ QQ.to_sympy(s) + I*QQ.to_sympy(t)), k)
+
+ real_part, complex_part = result
+
+ return list(map(_real, real_part)), list(map(_complex, complex_part))
+
+ def refine_root(f, s, t, eps=None, steps=None, fast=False, check_sqf=False):
+ """
+ Refine an isolating interval of a root to the given precision.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 3, x).refine_root(1, 2, eps=1e-2)
+ (19/11, 26/15)
+
+ """
+ if check_sqf and not f.is_sqf:
+ raise PolynomialError("only square-free polynomials supported")
+
+ s, t = QQ.convert(s), QQ.convert(t)
+
+ if eps is not None:
+ eps = QQ.convert(eps)
+
+ if eps <= 0:
+ raise ValueError("'eps' must be a positive rational")
+
+ if steps is not None:
+ steps = int(steps)
+ elif eps is None:
+ steps = 1
+
+ if hasattr(f.rep, 'refine_root'):
+ S, T = f.rep.refine_root(s, t, eps=eps, steps=steps, fast=fast)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'refine_root')
+
+ return QQ.to_sympy(S), QQ.to_sympy(T)
+
+ def count_roots(f, inf=None, sup=None):
+ """
+ Return the number of roots of ``f`` in ``[inf, sup]`` interval.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, I
+ >>> from sympy.abc import x
+
+ >>> Poly(x**4 - 4, x).count_roots(-3, 3)
+ 2
+ >>> Poly(x**4 - 4, x).count_roots(0, 1 + 3*I)
+ 1
+
+ """
+ inf_real, sup_real = True, True
+
+ if inf is not None:
+ inf = sympify(inf)
+
+ if inf is S.NegativeInfinity:
+ inf = None
+ else:
+ re, im = inf.as_real_imag()
+
+ if not im:
+ inf = QQ.convert(inf)
+ else:
+ inf, inf_real = list(map(QQ.convert, (re, im))), False
+
+ if sup is not None:
+ sup = sympify(sup)
+
+ if sup is S.Infinity:
+ sup = None
+ else:
+ re, im = sup.as_real_imag()
+
+ if not im:
+ sup = QQ.convert(sup)
+ else:
+ sup, sup_real = list(map(QQ.convert, (re, im))), False
+
+ if inf_real and sup_real:
+ if hasattr(f.rep, 'count_real_roots'):
+ count = f.rep.count_real_roots(inf=inf, sup=sup)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'count_real_roots')
+ else:
+ if inf_real and inf is not None:
+ inf = (inf, QQ.zero)
+
+ if sup_real and sup is not None:
+ sup = (sup, QQ.zero)
+
+ if hasattr(f.rep, 'count_complex_roots'):
+ count = f.rep.count_complex_roots(inf=inf, sup=sup)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'count_complex_roots')
+
+ return Integer(count)
+
+ def root(f, index, radicals=True):
+ """
+ Get an indexed root of a polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = Poly(2*x**3 - 7*x**2 + 4*x + 4)
+
+ >>> f.root(0)
+ -1/2
+ >>> f.root(1)
+ 2
+ >>> f.root(2)
+ 2
+ >>> f.root(3)
+ Traceback (most recent call last):
+ ...
+ IndexError: root index out of [-3, 2] range, got 3
+
+ >>> Poly(x**5 + x + 1).root(0)
+ CRootOf(x**3 - x**2 + 1, 0)
+
+ """
+ return sympy.polys.rootoftools.rootof(f, index, radicals=radicals)
+
+ def real_roots(f, multiple=True, radicals=True):
+ """
+ Return a list of real roots with multiplicities.
+
+ See :func:`real_roots` for more explanation.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(2*x**3 - 7*x**2 + 4*x + 4).real_roots()
+ [-1/2, 2, 2]
+ >>> Poly(x**3 + x + 1).real_roots()
+ [CRootOf(x**3 + x + 1, 0)]
+ """
+ reals = sympy.polys.rootoftools.CRootOf.real_roots(f, radicals=radicals)
+
+ if multiple:
+ return reals
+ else:
+ return group(reals, multiple=False)
+
+ def all_roots(f, multiple=True, radicals=True):
+ """
+ Return a list of real and complex roots with multiplicities.
+
+ See :func:`all_roots` for more explanation.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(2*x**3 - 7*x**2 + 4*x + 4).all_roots()
+ [-1/2, 2, 2]
+ >>> Poly(x**3 + x + 1).all_roots()
+ [CRootOf(x**3 + x + 1, 0),
+ CRootOf(x**3 + x + 1, 1),
+ CRootOf(x**3 + x + 1, 2)]
+
+ """
+ roots = sympy.polys.rootoftools.CRootOf.all_roots(f, radicals=radicals)
+
+ if multiple:
+ return roots
+ else:
+ return group(roots, multiple=False)
+
+ def nroots(f, n=15, maxsteps=50, cleanup=True):
+ """
+ Compute numerical approximations of roots of ``f``.
+
+ Parameters
+ ==========
+
+ n ... the number of digits to calculate
+ maxsteps ... the maximum number of iterations to do
+
+ If the accuracy `n` cannot be reached in `maxsteps`, it will raise an
+ exception. You need to rerun with higher maxsteps.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 3).nroots(n=15)
+ [-1.73205080756888, 1.73205080756888]
+ >>> Poly(x**2 - 3).nroots(n=30)
+ [-1.73205080756887729352744634151, 1.73205080756887729352744634151]
+
+ """
+ if f.is_multivariate:
+ raise MultivariatePolynomialError(
+ "Cannot compute numerical roots of %s" % f)
+
+ if f.degree() <= 0:
+ return []
+
+ # For integer and rational coefficients, convert them to integers only
+ # (for accuracy). Otherwise just try to convert the coefficients to
+ # mpmath.mpc and raise an exception if the conversion fails.
+ if f.rep.dom is ZZ:
+ coeffs = [int(coeff) for coeff in f.all_coeffs()]
+ elif f.rep.dom is QQ:
+ denoms = [coeff.q for coeff in f.all_coeffs()]
+ fac = ilcm(*denoms)
+ coeffs = [int(coeff*fac) for coeff in f.all_coeffs()]
+ else:
+ coeffs = [coeff.evalf(n=n).as_real_imag()
+ for coeff in f.all_coeffs()]
+ try:
+ coeffs = [mpmath.mpc(*coeff) for coeff in coeffs]
+ except TypeError:
+ raise DomainError("Numerical domain expected, got %s" % \
+ f.rep.dom)
+
+ dps = mpmath.mp.dps
+ mpmath.mp.dps = n
+
+ from sympy.functions.elementary.complexes import sign
+ try:
+ # We need to add extra precision to guard against losing accuracy.
+ # 10 times the degree of the polynomial seems to work well.
+ roots = mpmath.polyroots(coeffs, maxsteps=maxsteps,
+ cleanup=cleanup, error=False, extraprec=f.degree()*10)
+
+ # Mpmath puts real roots first, then complex ones (as does all_roots)
+ # so we make sure this convention holds here, too.
+ roots = list(map(sympify,
+ sorted(roots, key=lambda r: (1 if r.imag else 0, r.real, abs(r.imag), sign(r.imag)))))
+ except NoConvergence:
+ try:
+ # If roots did not converge try again with more extra precision.
+ roots = mpmath.polyroots(coeffs, maxsteps=maxsteps,
+ cleanup=cleanup, error=False, extraprec=f.degree()*15)
+ roots = list(map(sympify,
+ sorted(roots, key=lambda r: (1 if r.imag else 0, r.real, abs(r.imag), sign(r.imag)))))
+ except NoConvergence:
+ raise NoConvergence(
+ 'convergence to root failed; try n < %s or maxsteps > %s' % (
+ n, maxsteps))
+ finally:
+ mpmath.mp.dps = dps
+
+ return roots
+
+ def ground_roots(f):
+ """
+ Compute roots of ``f`` by factorization in the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**6 - 4*x**4 + 4*x**3 - x**2).ground_roots()
+ {0: 2, 1: 2}
+
+ """
+ if f.is_multivariate:
+ raise MultivariatePolynomialError(
+ "Cannot compute ground roots of %s" % f)
+
+ roots = {}
+
+ for factor, k in f.factor_list()[1]:
+ if factor.is_linear:
+ a, b = factor.all_coeffs()
+ roots[-b/a] = k
+
+ return roots
+
+ def nth_power_roots_poly(f, n):
+ """
+ Construct a polynomial with n-th powers of roots of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = Poly(x**4 - x**2 + 1)
+
+ >>> f.nth_power_roots_poly(2)
+ Poly(x**4 - 2*x**3 + 3*x**2 - 2*x + 1, x, domain='ZZ')
+ >>> f.nth_power_roots_poly(3)
+ Poly(x**4 + 2*x**2 + 1, x, domain='ZZ')
+ >>> f.nth_power_roots_poly(4)
+ Poly(x**4 + 2*x**3 + 3*x**2 + 2*x + 1, x, domain='ZZ')
+ >>> f.nth_power_roots_poly(12)
+ Poly(x**4 - 4*x**3 + 6*x**2 - 4*x + 1, x, domain='ZZ')
+
+ """
+ if f.is_multivariate:
+ raise MultivariatePolynomialError(
+ "must be a univariate polynomial")
+
+ N = sympify(n)
+
+ if N.is_Integer and N >= 1:
+ n = int(N)
+ else:
+ raise ValueError("'n' must an integer and n >= 1, got %s" % n)
+
+ x = f.gen
+ t = Dummy('t')
+
+ r = f.resultant(f.__class__.from_expr(x**n - t, x, t))
+
+ return r.replace(t, x)
+
+ def same_root(f, a, b):
+ """
+ Decide whether two roots of this polynomial are equal.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, cyclotomic_poly, exp, I, pi
+ >>> f = Poly(cyclotomic_poly(5))
+ >>> r0 = exp(2*I*pi/5)
+ >>> indices = [i for i, r in enumerate(f.all_roots()) if f.same_root(r, r0)]
+ >>> print(indices)
+ [3]
+
+ Raises
+ ======
+
+ DomainError
+ If the domain of the polynomial is not :ref:`ZZ`, :ref:`QQ`,
+ :ref:`RR`, or :ref:`CC`.
+ MultivariatePolynomialError
+ If the polynomial is not univariate.
+ PolynomialError
+ If the polynomial is of degree < 2.
+
+ """
+ if f.is_multivariate:
+ raise MultivariatePolynomialError(
+ "Must be a univariate polynomial")
+
+ dom_delta_sq = f.rep.mignotte_sep_bound_squared()
+ delta_sq = f.domain.get_field().to_sympy(dom_delta_sq)
+ # We have delta_sq = delta**2, where delta is a lower bound on the
+ # minimum separation between any two roots of this polynomial.
+ # Let eps = delta/3, and define eps_sq = eps**2 = delta**2/9.
+ eps_sq = delta_sq / 9
+
+ r, _, _, _ = evalf(1/eps_sq, 1, {})
+ n = fastlog(r)
+ # Then 2^n > 1/eps**2.
+ m = (n // 2) + (n % 2)
+ # Then 2^(-m) < eps.
+ ev = lambda x: quad_to_mpmath(_evalf_with_bounded_error(x, m=m))
+
+ # Then for any complex numbers a, b we will have
+ # |a - ev(a)| < eps and |b - ev(b)| < eps.
+ # So if |ev(a) - ev(b)|**2 < eps**2, then
+ # |ev(a) - ev(b)| < eps, hence |a - b| < 3*eps = delta.
+ A, B = ev(a), ev(b)
+ return (A.real - B.real)**2 + (A.imag - B.imag)**2 < eps_sq
+
+ def cancel(f, g, include=False):
+ """
+ Cancel common factors in a rational function ``f/g``.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(2*x**2 - 2, x).cancel(Poly(x**2 - 2*x + 1, x))
+ (1, Poly(2*x + 2, x, domain='ZZ'), Poly(x - 1, x, domain='ZZ'))
+
+ >>> Poly(2*x**2 - 2, x).cancel(Poly(x**2 - 2*x + 1, x), include=True)
+ (Poly(2*x + 2, x, domain='ZZ'), Poly(x - 1, x, domain='ZZ'))
+
+ """
+ dom, per, F, G = f._unify(g)
+
+ if hasattr(F, 'cancel'):
+ result = F.cancel(G, include=include)
+ else: # pragma: no cover
+ raise OperationNotSupported(f, 'cancel')
+
+ if not include:
+ if dom.has_assoc_Ring:
+ dom = dom.get_ring()
+
+ cp, cq, p, q = result
+
+ cp = dom.to_sympy(cp)
+ cq = dom.to_sympy(cq)
+
+ return cp/cq, per(p), per(q)
+ else:
+ return tuple(map(per, result))
+
+ def make_monic_over_integers_by_scaling_roots(f):
+ """
+ Turn any univariate polynomial over :ref:`QQ` or :ref:`ZZ` into a monic
+ polynomial over :ref:`ZZ`, by scaling the roots as necessary.
+
+ Explanation
+ ===========
+
+ This operation can be performed whether or not *f* is irreducible; when
+ it is, this can be understood as determining an algebraic integer
+ generating the same field as a root of *f*.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly, S
+ >>> from sympy.abc import x
+ >>> f = Poly(x**2/2 + S(1)/4 * x + S(1)/8, x, domain='QQ')
+ >>> f.make_monic_over_integers_by_scaling_roots()
+ (Poly(x**2 + 2*x + 4, x, domain='ZZ'), 4)
+
+ Returns
+ =======
+
+ Pair ``(g, c)``
+ g is the polynomial
+
+ c is the integer by which the roots had to be scaled
+
+ """
+ if not f.is_univariate or f.domain not in [ZZ, QQ]:
+ raise ValueError('Polynomial must be univariate over ZZ or QQ.')
+ if f.is_monic and f.domain == ZZ:
+ return f, ZZ.one
+ else:
+ fm = f.monic()
+ c, _ = fm.clear_denoms()
+ return fm.transform(Poly(fm.gen), c).to_ring(), c
+
+ def galois_group(f, by_name=False, max_tries=30, randomize=False):
+ """
+ Compute the Galois group of this polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+ >>> f = Poly(x**4 - 2)
+ >>> G, _ = f.galois_group(by_name=True)
+ >>> print(G)
+ S4TransitiveSubgroups.D4
+
+ See Also
+ ========
+
+ sympy.polys.numberfields.galoisgroups.galois_group
+
+ """
+ from sympy.polys.numberfields.galoisgroups import (
+ _galois_group_degree_3, _galois_group_degree_4_lookup,
+ _galois_group_degree_5_lookup_ext_factor,
+ _galois_group_degree_6_lookup,
+ )
+ if (not f.is_univariate
+ or not f.is_irreducible
+ or f.domain not in [ZZ, QQ]
+ ):
+ raise ValueError('Polynomial must be irreducible and univariate over ZZ or QQ.')
+ gg = {
+ 3: _galois_group_degree_3,
+ 4: _galois_group_degree_4_lookup,
+ 5: _galois_group_degree_5_lookup_ext_factor,
+ 6: _galois_group_degree_6_lookup,
+ }
+ max_supported = max(gg.keys())
+ n = f.degree()
+ if n > max_supported:
+ raise ValueError(f"Only polynomials up to degree {max_supported} are supported.")
+ elif n < 1:
+ raise ValueError("Constant polynomial has no Galois group.")
+ elif n == 1:
+ from sympy.combinatorics.galois import S1TransitiveSubgroups
+ name, alt = S1TransitiveSubgroups.S1, True
+ elif n == 2:
+ from sympy.combinatorics.galois import S2TransitiveSubgroups
+ name, alt = S2TransitiveSubgroups.S2, False
+ else:
+ g, _ = f.make_monic_over_integers_by_scaling_roots()
+ name, alt = gg[n](g, max_tries=max_tries, randomize=randomize)
+ G = name if by_name else name.get_perm_group()
+ return G, alt
+
+ @property
+ def is_zero(f):
+ """
+ Returns ``True`` if ``f`` is a zero polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(0, x).is_zero
+ True
+ >>> Poly(1, x).is_zero
+ False
+
+ """
+ return f.rep.is_zero
+
+ @property
+ def is_one(f):
+ """
+ Returns ``True`` if ``f`` is a unit polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(0, x).is_one
+ False
+ >>> Poly(1, x).is_one
+ True
+
+ """
+ return f.rep.is_one
+
+ @property
+ def is_sqf(f):
+ """
+ Returns ``True`` if ``f`` is a square-free polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 - 2*x + 1, x).is_sqf
+ False
+ >>> Poly(x**2 - 1, x).is_sqf
+ True
+
+ """
+ return f.rep.is_sqf
+
+ @property
+ def is_monic(f):
+ """
+ Returns ``True`` if the leading coefficient of ``f`` is one.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x + 2, x).is_monic
+ True
+ >>> Poly(2*x + 2, x).is_monic
+ False
+
+ """
+ return f.rep.is_monic
+
+ @property
+ def is_primitive(f):
+ """
+ Returns ``True`` if GCD of the coefficients of ``f`` is one.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(2*x**2 + 6*x + 12, x).is_primitive
+ False
+ >>> Poly(x**2 + 3*x + 6, x).is_primitive
+ True
+
+ """
+ return f.rep.is_primitive
+
+ @property
+ def is_ground(f):
+ """
+ Returns ``True`` if ``f`` is an element of the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x, x).is_ground
+ False
+ >>> Poly(2, x).is_ground
+ True
+ >>> Poly(y, x).is_ground
+ True
+
+ """
+ return f.rep.is_ground
+
+ @property
+ def is_linear(f):
+ """
+ Returns ``True`` if ``f`` is linear in all its variables.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x + y + 2, x, y).is_linear
+ True
+ >>> Poly(x*y + 2, x, y).is_linear
+ False
+
+ """
+ return f.rep.is_linear
+
+ @property
+ def is_quadratic(f):
+ """
+ Returns ``True`` if ``f`` is quadratic in all its variables.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x*y + 2, x, y).is_quadratic
+ True
+ >>> Poly(x*y**2 + 2, x, y).is_quadratic
+ False
+
+ """
+ return f.rep.is_quadratic
+
+ @property
+ def is_monomial(f):
+ """
+ Returns ``True`` if ``f`` is zero or has only one term.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(3*x**2, x).is_monomial
+ True
+ >>> Poly(3*x**2 + 1, x).is_monomial
+ False
+
+ """
+ return f.rep.is_monomial
+
+ @property
+ def is_homogeneous(f):
+ """
+ Returns ``True`` if ``f`` is a homogeneous polynomial.
+
+ A homogeneous polynomial is a polynomial whose all monomials with
+ non-zero coefficients have the same total degree. If you want not
+ only to check if a polynomial is homogeneous but also compute its
+ homogeneous order, then use :func:`Poly.homogeneous_order`.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + x*y, x, y).is_homogeneous
+ True
+ >>> Poly(x**3 + x*y, x, y).is_homogeneous
+ False
+
+ """
+ return f.rep.is_homogeneous
+
+ @property
+ def is_irreducible(f):
+ """
+ Returns ``True`` if ``f`` has no factors over its domain.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> Poly(x**2 + x + 1, x, modulus=2).is_irreducible
+ True
+ >>> Poly(x**2 + 1, x, modulus=2).is_irreducible
+ False
+
+ """
+ return f.rep.is_irreducible
+
+ @property
+ def is_univariate(f):
+ """
+ Returns ``True`` if ``f`` is a univariate polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + x + 1, x).is_univariate
+ True
+ >>> Poly(x*y**2 + x*y + 1, x, y).is_univariate
+ False
+ >>> Poly(x*y**2 + x*y + 1, x).is_univariate
+ True
+ >>> Poly(x**2 + x + 1, x, y).is_univariate
+ False
+
+ """
+ return len(f.gens) == 1
+
+ @property
+ def is_multivariate(f):
+ """
+ Returns ``True`` if ``f`` is a multivariate polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x, y
+
+ >>> Poly(x**2 + x + 1, x).is_multivariate
+ False
+ >>> Poly(x*y**2 + x*y + 1, x, y).is_multivariate
+ True
+ >>> Poly(x*y**2 + x*y + 1, x).is_multivariate
+ False
+ >>> Poly(x**2 + x + 1, x, y).is_multivariate
+ True
+
+ """
+ return len(f.gens) != 1
+
+ @property
+ def is_cyclotomic(f):
+ """
+ Returns ``True`` if ``f`` is a cyclotomic polnomial.
+
+ Examples
+ ========
+
+ >>> from sympy import Poly
+ >>> from sympy.abc import x
+
+ >>> f = x**16 + x**14 - x**10 + x**8 - x**6 + x**2 + 1
+
+ >>> Poly(f).is_cyclotomic
+ False
+
+ >>> g = x**16 + x**14 - x**10 - x**8 - x**6 + x**2 + 1
+
+ >>> Poly(g).is_cyclotomic
+ True
+
+ """
+ return f.rep.is_cyclotomic
+
+ def __abs__(f):
+ return f.abs()
+
+ def __neg__(f):
+ return f.neg()
+
+ @_polifyit
+ def __add__(f, g):
+ return f.add(g)
+
+ @_polifyit
+ def __radd__(f, g):
+ return g.add(f)
+
+ @_polifyit
+ def __sub__(f, g):
+ return f.sub(g)
+
+ @_polifyit
+ def __rsub__(f, g):
+ return g.sub(f)
+
+ @_polifyit
+ def __mul__(f, g):
+ return f.mul(g)
+
+ @_polifyit
+ def __rmul__(f, g):
+ return g.mul(f)
+
+ @_sympifyit('n', NotImplemented)
+ def __pow__(f, n):
+ if n.is_Integer and n >= 0:
+ return f.pow(n)
+ else:
+ return NotImplemented
+
+ @_polifyit
+ def __divmod__(f, g):
+ return f.div(g)
+
+ @_polifyit
+ def __rdivmod__(f, g):
+ return g.div(f)
+
+ @_polifyit
+ def __mod__(f, g):
+ return f.rem(g)
+
+ @_polifyit
+ def __rmod__(f, g):
+ return g.rem(f)
+
+ @_polifyit
+ def __floordiv__(f, g):
+ return f.quo(g)
+
+ @_polifyit
+ def __rfloordiv__(f, g):
+ return g.quo(f)
+
+ @_sympifyit('g', NotImplemented)
+ def __truediv__(f, g):
+ return f.as_expr()/g.as_expr()
+
+ @_sympifyit('g', NotImplemented)
+ def __rtruediv__(f, g):
+ return g.as_expr()/f.as_expr()
+
+ @_sympifyit('other', NotImplemented)
+ def __eq__(self, other):
+ f, g = self, other
+
+ if not g.is_Poly:
+ try:
+ g = f.__class__(g, f.gens, domain=f.get_domain())
+ except (PolynomialError, DomainError, CoercionFailed):
+ return False
+
+ if f.gens != g.gens:
+ return False
+
+ if f.rep.dom != g.rep.dom:
+ return False
+
+ return f.rep == g.rep
+
+ @_sympifyit('g', NotImplemented)
+ def __ne__(f, g):
+ return not f == g
+
+ def __bool__(f):
+ return not f.is_zero
+
+ def eq(f, g, strict=False):
+ if not strict:
+ return f == g
+ else:
+ return f._strict_eq(sympify(g))
+
+ def ne(f, g, strict=False):
+ return not f.eq(g, strict=strict)
+
+ def _strict_eq(f, g):
+ return isinstance(g, f.__class__) and f.gens == g.gens and f.rep.eq(g.rep, strict=True)
+
+
+@public
+class PurePoly(Poly):
+ """Class for representing pure polynomials. """
+
+ def _hashable_content(self):
+ """Allow SymPy to hash Poly instances. """
+ return (self.rep,)
+
+ def __hash__(self):
+ return super().__hash__()
+
+ @property
+ def free_symbols(self):
+ """
+ Free symbols of a polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import PurePoly
+ >>> from sympy.abc import x, y
+
+ >>> PurePoly(x**2 + 1).free_symbols
+ set()
+ >>> PurePoly(x**2 + y).free_symbols
+ set()
+ >>> PurePoly(x**2 + y, x).free_symbols
+ {y}
+
+ """
+ return self.free_symbols_in_domain
+
+ @_sympifyit('other', NotImplemented)
+ def __eq__(self, other):
+ f, g = self, other
+
+ if not g.is_Poly:
+ try:
+ g = f.__class__(g, f.gens, domain=f.get_domain())
+ except (PolynomialError, DomainError, CoercionFailed):
+ return False
+
+ if len(f.gens) != len(g.gens):
+ return False
+
+ if f.rep.dom != g.rep.dom:
+ try:
+ dom = f.rep.dom.unify(g.rep.dom, f.gens)
+ except UnificationFailed:
+ return False
+
+ f = f.set_domain(dom)
+ g = g.set_domain(dom)
+
+ return f.rep == g.rep
+
+ def _strict_eq(f, g):
+ return isinstance(g, f.__class__) and f.rep.eq(g.rep, strict=True)
+
+ def _unify(f, g):
+ g = sympify(g)
+
+ if not g.is_Poly:
+ try:
+ return f.rep.dom, f.per, f.rep, f.rep.per(f.rep.dom.from_sympy(g))
+ except CoercionFailed:
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ if len(f.gens) != len(g.gens):
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ if not (isinstance(f.rep, DMP) and isinstance(g.rep, DMP)):
+ raise UnificationFailed("Cannot unify %s with %s" % (f, g))
+
+ cls = f.__class__
+ gens = f.gens
+
+ dom = f.rep.dom.unify(g.rep.dom, gens)
+
+ F = f.rep.convert(dom)
+ G = g.rep.convert(dom)
+
+ def per(rep, dom=dom, gens=gens, remove=None):
+ if remove is not None:
+ gens = gens[:remove] + gens[remove + 1:]
+
+ if not gens:
+ return dom.to_sympy(rep)
+
+ return cls.new(rep, *gens)
+
+ return dom, per, F, G
+
+
+@public
+def poly_from_expr(expr, *gens, **args):
+ """Construct a polynomial from an expression. """
+ opt = options.build_options(gens, args)
+ return _poly_from_expr(expr, opt)
+
+
+def _poly_from_expr(expr, opt):
+ """Construct a polynomial from an expression. """
+ orig, expr = expr, sympify(expr)
+
+ if not isinstance(expr, Basic):
+ raise PolificationFailed(opt, orig, expr)
+ elif expr.is_Poly:
+ poly = expr.__class__._from_poly(expr, opt)
+
+ opt.gens = poly.gens
+ opt.domain = poly.domain
+
+ if opt.polys is None:
+ opt.polys = True
+
+ return poly, opt
+ elif opt.expand:
+ expr = expr.expand()
+
+ rep, opt = _dict_from_expr(expr, opt)
+ if not opt.gens:
+ raise PolificationFailed(opt, orig, expr)
+
+ monoms, coeffs = list(zip(*list(rep.items())))
+ domain = opt.domain
+
+ if domain is None:
+ opt.domain, coeffs = construct_domain(coeffs, opt=opt)
+ else:
+ coeffs = list(map(domain.from_sympy, coeffs))
+
+ rep = dict(list(zip(monoms, coeffs)))
+ poly = Poly._from_dict(rep, opt)
+
+ if opt.polys is None:
+ opt.polys = False
+
+ return poly, opt
+
+
+@public
+def parallel_poly_from_expr(exprs, *gens, **args):
+ """Construct polynomials from expressions. """
+ opt = options.build_options(gens, args)
+ return _parallel_poly_from_expr(exprs, opt)
+
+
+def _parallel_poly_from_expr(exprs, opt):
+ """Construct polynomials from expressions. """
+ if len(exprs) == 2:
+ f, g = exprs
+
+ if isinstance(f, Poly) and isinstance(g, Poly):
+ f = f.__class__._from_poly(f, opt)
+ g = g.__class__._from_poly(g, opt)
+
+ f, g = f.unify(g)
+
+ opt.gens = f.gens
+ opt.domain = f.domain
+
+ if opt.polys is None:
+ opt.polys = True
+
+ return [f, g], opt
+
+ origs, exprs = list(exprs), []
+ _exprs, _polys = [], []
+
+ failed = False
+
+ for i, expr in enumerate(origs):
+ expr = sympify(expr)
+
+ if isinstance(expr, Basic):
+ if expr.is_Poly:
+ _polys.append(i)
+ else:
+ _exprs.append(i)
+
+ if opt.expand:
+ expr = expr.expand()
+ else:
+ failed = True
+
+ exprs.append(expr)
+
+ if failed:
+ raise PolificationFailed(opt, origs, exprs, True)
+
+ if _polys:
+ # XXX: this is a temporary solution
+ for i in _polys:
+ exprs[i] = exprs[i].as_expr()
+
+ reps, opt = _parallel_dict_from_expr(exprs, opt)
+ if not opt.gens:
+ raise PolificationFailed(opt, origs, exprs, True)
+
+ from sympy.functions.elementary.piecewise import Piecewise
+ for k in opt.gens:
+ if isinstance(k, Piecewise):
+ raise PolynomialError("Piecewise generators do not make sense")
+
+ coeffs_list, lengths = [], []
+
+ all_monoms = []
+ all_coeffs = []
+
+ for rep in reps:
+ monoms, coeffs = list(zip(*list(rep.items())))
+
+ coeffs_list.extend(coeffs)
+ all_monoms.append(monoms)
+
+ lengths.append(len(coeffs))
+
+ domain = opt.domain
+
+ if domain is None:
+ opt.domain, coeffs_list = construct_domain(coeffs_list, opt=opt)
+ else:
+ coeffs_list = list(map(domain.from_sympy, coeffs_list))
+
+ for k in lengths:
+ all_coeffs.append(coeffs_list[:k])
+ coeffs_list = coeffs_list[k:]
+
+ polys = []
+
+ for monoms, coeffs in zip(all_monoms, all_coeffs):
+ rep = dict(list(zip(monoms, coeffs)))
+ poly = Poly._from_dict(rep, opt)
+ polys.append(poly)
+
+ if opt.polys is None:
+ opt.polys = bool(_polys)
+
+ return polys, opt
+
+
+def _update_args(args, key, value):
+ """Add a new ``(key, value)`` pair to arguments ``dict``. """
+ args = dict(args)
+
+ if key not in args:
+ args[key] = value
+
+ return args
+
+
+@public
+def degree(f, gen=0):
+ """
+ Return the degree of ``f`` in the given variable.
+
+ The degree of 0 is negative infinity.
+
+ Examples
+ ========
+
+ >>> from sympy import degree
+ >>> from sympy.abc import x, y
+
+ >>> degree(x**2 + y*x + 1, gen=x)
+ 2
+ >>> degree(x**2 + y*x + 1, gen=y)
+ 1
+ >>> degree(0, x)
+ -oo
+
+ See also
+ ========
+
+ sympy.polys.polytools.Poly.total_degree
+ degree_list
+ """
+
+ f = sympify(f, strict=True)
+ gen_is_Num = sympify(gen, strict=True).is_Number
+ if f.is_Poly:
+ p = f
+ isNum = p.as_expr().is_Number
+ else:
+ isNum = f.is_Number
+ if not isNum:
+ if gen_is_Num:
+ p, _ = poly_from_expr(f)
+ else:
+ p, _ = poly_from_expr(f, gen)
+
+ if isNum:
+ return S.Zero if f else S.NegativeInfinity
+
+ if not gen_is_Num:
+ if f.is_Poly and gen not in p.gens:
+ # try recast without explicit gens
+ p, _ = poly_from_expr(f.as_expr())
+ if gen not in p.gens:
+ return S.Zero
+ elif not f.is_Poly and len(f.free_symbols) > 1:
+ raise TypeError(filldedent('''
+ A symbolic generator of interest is required for a multivariate
+ expression like func = %s, e.g. degree(func, gen = %s) instead of
+ degree(func, gen = %s).
+ ''' % (f, next(ordered(f.free_symbols)), gen)))
+ result = p.degree(gen)
+ return Integer(result) if isinstance(result, int) else S.NegativeInfinity
+
+
+@public
+def total_degree(f, *gens):
+ """
+ Return the total_degree of ``f`` in the given variables.
+
+ Examples
+ ========
+ >>> from sympy import total_degree, Poly
+ >>> from sympy.abc import x, y
+
+ >>> total_degree(1)
+ 0
+ >>> total_degree(x + x*y)
+ 2
+ >>> total_degree(x + x*y, x)
+ 1
+
+ If the expression is a Poly and no variables are given
+ then the generators of the Poly will be used:
+
+ >>> p = Poly(x + x*y, y)
+ >>> total_degree(p)
+ 1
+
+ To deal with the underlying expression of the Poly, convert
+ it to an Expr:
+
+ >>> total_degree(p.as_expr())
+ 2
+
+ This is done automatically if any variables are given:
+
+ >>> total_degree(p, x)
+ 1
+
+ See also
+ ========
+ degree
+ """
+
+ p = sympify(f)
+ if p.is_Poly:
+ p = p.as_expr()
+ if p.is_Number:
+ rv = 0
+ else:
+ if f.is_Poly:
+ gens = gens or f.gens
+ rv = Poly(p, gens).total_degree()
+
+ return Integer(rv)
+
+
+@public
+def degree_list(f, *gens, **args):
+ """
+ Return a list of degrees of ``f`` in all variables.
+
+ Examples
+ ========
+
+ >>> from sympy import degree_list
+ >>> from sympy.abc import x, y
+
+ >>> degree_list(x**2 + y*x + 1)
+ (2, 1)
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('degree_list', 1, exc)
+
+ degrees = F.degree_list()
+
+ return tuple(map(Integer, degrees))
+
+
+@public
+def LC(f, *gens, **args):
+ """
+ Return the leading coefficient of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import LC
+ >>> from sympy.abc import x, y
+
+ >>> LC(4*x**2 + 2*x*y**2 + x*y + 3*y)
+ 4
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('LC', 1, exc)
+
+ return F.LC(order=opt.order)
+
+
+@public
+def LM(f, *gens, **args):
+ """
+ Return the leading monomial of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import LM
+ >>> from sympy.abc import x, y
+
+ >>> LM(4*x**2 + 2*x*y**2 + x*y + 3*y)
+ x**2
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('LM', 1, exc)
+
+ monom = F.LM(order=opt.order)
+ return monom.as_expr()
+
+
+@public
+def LT(f, *gens, **args):
+ """
+ Return the leading term of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import LT
+ >>> from sympy.abc import x, y
+
+ >>> LT(4*x**2 + 2*x*y**2 + x*y + 3*y)
+ 4*x**2
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('LT', 1, exc)
+
+ monom, coeff = F.LT(order=opt.order)
+ return coeff*monom.as_expr()
+
+
+@public
+def pdiv(f, g, *gens, **args):
+ """
+ Compute polynomial pseudo-division of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import pdiv
+ >>> from sympy.abc import x
+
+ >>> pdiv(x**2 + 1, 2*x - 4)
+ (2*x + 4, 20)
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('pdiv', 2, exc)
+
+ q, r = F.pdiv(G)
+
+ if not opt.polys:
+ return q.as_expr(), r.as_expr()
+ else:
+ return q, r
+
+
+@public
+def prem(f, g, *gens, **args):
+ """
+ Compute polynomial pseudo-remainder of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import prem
+ >>> from sympy.abc import x
+
+ >>> prem(x**2 + 1, 2*x - 4)
+ 20
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('prem', 2, exc)
+
+ r = F.prem(G)
+
+ if not opt.polys:
+ return r.as_expr()
+ else:
+ return r
+
+
+@public
+def pquo(f, g, *gens, **args):
+ """
+ Compute polynomial pseudo-quotient of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import pquo
+ >>> from sympy.abc import x
+
+ >>> pquo(x**2 + 1, 2*x - 4)
+ 2*x + 4
+ >>> pquo(x**2 - 1, 2*x - 1)
+ 2*x + 1
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('pquo', 2, exc)
+
+ try:
+ q = F.pquo(G)
+ except ExactQuotientFailed:
+ raise ExactQuotientFailed(f, g)
+
+ if not opt.polys:
+ return q.as_expr()
+ else:
+ return q
+
+
+@public
+def pexquo(f, g, *gens, **args):
+ """
+ Compute polynomial exact pseudo-quotient of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import pexquo
+ >>> from sympy.abc import x
+
+ >>> pexquo(x**2 - 1, 2*x - 2)
+ 2*x + 2
+
+ >>> pexquo(x**2 + 1, 2*x - 4)
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: 2*x - 4 does not divide x**2 + 1
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('pexquo', 2, exc)
+
+ q = F.pexquo(G)
+
+ if not opt.polys:
+ return q.as_expr()
+ else:
+ return q
+
+
+@public
+def div(f, g, *gens, **args):
+ """
+ Compute polynomial division of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import div, ZZ, QQ
+ >>> from sympy.abc import x
+
+ >>> div(x**2 + 1, 2*x - 4, domain=ZZ)
+ (0, x**2 + 1)
+ >>> div(x**2 + 1, 2*x - 4, domain=QQ)
+ (x/2 + 1, 5)
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('div', 2, exc)
+
+ q, r = F.div(G, auto=opt.auto)
+
+ if not opt.polys:
+ return q.as_expr(), r.as_expr()
+ else:
+ return q, r
+
+
+@public
+def rem(f, g, *gens, **args):
+ """
+ Compute polynomial remainder of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import rem, ZZ, QQ
+ >>> from sympy.abc import x
+
+ >>> rem(x**2 + 1, 2*x - 4, domain=ZZ)
+ x**2 + 1
+ >>> rem(x**2 + 1, 2*x - 4, domain=QQ)
+ 5
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('rem', 2, exc)
+
+ r = F.rem(G, auto=opt.auto)
+
+ if not opt.polys:
+ return r.as_expr()
+ else:
+ return r
+
+
+@public
+def quo(f, g, *gens, **args):
+ """
+ Compute polynomial quotient of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import quo
+ >>> from sympy.abc import x
+
+ >>> quo(x**2 + 1, 2*x - 4)
+ x/2 + 1
+ >>> quo(x**2 - 1, x - 1)
+ x + 1
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('quo', 2, exc)
+
+ q = F.quo(G, auto=opt.auto)
+
+ if not opt.polys:
+ return q.as_expr()
+ else:
+ return q
+
+
+@public
+def exquo(f, g, *gens, **args):
+ """
+ Compute polynomial exact quotient of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import exquo
+ >>> from sympy.abc import x
+
+ >>> exquo(x**2 - 1, x - 1)
+ x + 1
+
+ >>> exquo(x**2 + 1, 2*x - 4)
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: 2*x - 4 does not divide x**2 + 1
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('exquo', 2, exc)
+
+ q = F.exquo(G, auto=opt.auto)
+
+ if not opt.polys:
+ return q.as_expr()
+ else:
+ return q
+
+
+@public
+def half_gcdex(f, g, *gens, **args):
+ """
+ Half extended Euclidean algorithm of ``f`` and ``g``.
+
+ Returns ``(s, h)`` such that ``h = gcd(f, g)`` and ``s*f = h (mod g)``.
+
+ Examples
+ ========
+
+ >>> from sympy import half_gcdex
+ >>> from sympy.abc import x
+
+ >>> half_gcdex(x**4 - 2*x**3 - 6*x**2 + 12*x + 15, x**3 + x**2 - 4*x - 4)
+ (3/5 - x/5, x + 1)
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ domain, (a, b) = construct_domain(exc.exprs)
+
+ try:
+ s, h = domain.half_gcdex(a, b)
+ except NotImplementedError:
+ raise ComputationFailed('half_gcdex', 2, exc)
+ else:
+ return domain.to_sympy(s), domain.to_sympy(h)
+
+ s, h = F.half_gcdex(G, auto=opt.auto)
+
+ if not opt.polys:
+ return s.as_expr(), h.as_expr()
+ else:
+ return s, h
+
+
+@public
+def gcdex(f, g, *gens, **args):
+ """
+ Extended Euclidean algorithm of ``f`` and ``g``.
+
+ Returns ``(s, t, h)`` such that ``h = gcd(f, g)`` and ``s*f + t*g = h``.
+
+ Examples
+ ========
+
+ >>> from sympy import gcdex
+ >>> from sympy.abc import x
+
+ >>> gcdex(x**4 - 2*x**3 - 6*x**2 + 12*x + 15, x**3 + x**2 - 4*x - 4)
+ (3/5 - x/5, x**2/5 - 6*x/5 + 2, x + 1)
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ domain, (a, b) = construct_domain(exc.exprs)
+
+ try:
+ s, t, h = domain.gcdex(a, b)
+ except NotImplementedError:
+ raise ComputationFailed('gcdex', 2, exc)
+ else:
+ return domain.to_sympy(s), domain.to_sympy(t), domain.to_sympy(h)
+
+ s, t, h = F.gcdex(G, auto=opt.auto)
+
+ if not opt.polys:
+ return s.as_expr(), t.as_expr(), h.as_expr()
+ else:
+ return s, t, h
+
+
+@public
+def invert(f, g, *gens, **args):
+ """
+ Invert ``f`` modulo ``g`` when possible.
+
+ Examples
+ ========
+
+ >>> from sympy import invert, S, mod_inverse
+ >>> from sympy.abc import x
+
+ >>> invert(x**2 - 1, 2*x - 1)
+ -4/3
+
+ >>> invert(x**2 - 1, x - 1)
+ Traceback (most recent call last):
+ ...
+ NotInvertible: zero divisor
+
+ For more efficient inversion of Rationals,
+ use the :obj:`sympy.core.intfunc.mod_inverse` function:
+
+ >>> mod_inverse(3, 5)
+ 2
+ >>> (S(2)/5).invert(S(7)/3)
+ 5/2
+
+ See Also
+ ========
+ sympy.core.intfunc.mod_inverse
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ domain, (a, b) = construct_domain(exc.exprs)
+
+ try:
+ return domain.to_sympy(domain.invert(a, b))
+ except NotImplementedError:
+ raise ComputationFailed('invert', 2, exc)
+
+ h = F.invert(G, auto=opt.auto)
+
+ if not opt.polys:
+ return h.as_expr()
+ else:
+ return h
+
+
+@public
+def subresultants(f, g, *gens, **args):
+ """
+ Compute subresultant PRS of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import subresultants
+ >>> from sympy.abc import x
+
+ >>> subresultants(x**2 + 1, x**2 - 1)
+ [x**2 + 1, x**2 - 1, -2]
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('subresultants', 2, exc)
+
+ result = F.subresultants(G)
+
+ if not opt.polys:
+ return [r.as_expr() for r in result]
+ else:
+ return result
+
+
+@public
+def resultant(f, g, *gens, includePRS=False, **args):
+ """
+ Compute resultant of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import resultant
+ >>> from sympy.abc import x
+
+ >>> resultant(x**2 + 1, x**2 - 1)
+ 4
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('resultant', 2, exc)
+
+ if includePRS:
+ result, R = F.resultant(G, includePRS=includePRS)
+ else:
+ result = F.resultant(G)
+
+ if not opt.polys:
+ if includePRS:
+ return result.as_expr(), [r.as_expr() for r in R]
+ return result.as_expr()
+ else:
+ if includePRS:
+ return result, R
+ return result
+
+
+@public
+def discriminant(f, *gens, **args):
+ """
+ Compute discriminant of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import discriminant
+ >>> from sympy.abc import x
+
+ >>> discriminant(x**2 + 2*x + 3)
+ -8
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('discriminant', 1, exc)
+
+ result = F.discriminant()
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def cofactors(f, g, *gens, **args):
+ """
+ Compute GCD and cofactors of ``f`` and ``g``.
+
+ Returns polynomials ``(h, cff, cfg)`` such that ``h = gcd(f, g)``, and
+ ``cff = quo(f, h)`` and ``cfg = quo(g, h)`` are, so called, cofactors
+ of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import cofactors
+ >>> from sympy.abc import x
+
+ >>> cofactors(x**2 - 1, x**2 - 3*x + 2)
+ (x - 1, x + 1, x - 2)
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ domain, (a, b) = construct_domain(exc.exprs)
+
+ try:
+ h, cff, cfg = domain.cofactors(a, b)
+ except NotImplementedError:
+ raise ComputationFailed('cofactors', 2, exc)
+ else:
+ return domain.to_sympy(h), domain.to_sympy(cff), domain.to_sympy(cfg)
+
+ h, cff, cfg = F.cofactors(G)
+
+ if not opt.polys:
+ return h.as_expr(), cff.as_expr(), cfg.as_expr()
+ else:
+ return h, cff, cfg
+
+
+@public
+def gcd_list(seq, *gens, **args):
+ """
+ Compute GCD of a list of polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy import gcd_list
+ >>> from sympy.abc import x
+
+ >>> gcd_list([x**3 - 1, x**2 - 1, x**2 - 3*x + 2])
+ x - 1
+
+ """
+ seq = sympify(seq)
+
+ def try_non_polynomial_gcd(seq):
+ if not gens and not args:
+ domain, numbers = construct_domain(seq)
+
+ if not numbers:
+ return domain.zero
+ elif domain.is_Numerical:
+ result, numbers = numbers[0], numbers[1:]
+
+ for number in numbers:
+ result = domain.gcd(result, number)
+
+ if domain.is_one(result):
+ break
+
+ return domain.to_sympy(result)
+
+ return None
+
+ result = try_non_polynomial_gcd(seq)
+
+ if result is not None:
+ return result
+
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ polys, opt = parallel_poly_from_expr(seq, *gens, **args)
+
+ # gcd for domain Q[irrational] (purely algebraic irrational)
+ if len(seq) > 1 and all(elt.is_algebraic and elt.is_irrational for elt in seq):
+ a = seq[-1]
+ lst = [ (a/elt).ratsimp() for elt in seq[:-1] ]
+ if all(frc.is_rational for frc in lst):
+ lc = 1
+ for frc in lst:
+ lc = lcm(lc, frc.as_numer_denom()[0])
+ # abs ensures that the gcd is always non-negative
+ return abs(a/lc)
+
+ except PolificationFailed as exc:
+ result = try_non_polynomial_gcd(exc.exprs)
+
+ if result is not None:
+ return result
+ else:
+ raise ComputationFailed('gcd_list', len(seq), exc)
+
+ if not polys:
+ if not opt.polys:
+ return S.Zero
+ else:
+ return Poly(0, opt=opt)
+
+ result, polys = polys[0], polys[1:]
+
+ for poly in polys:
+ result = result.gcd(poly)
+
+ if result.is_one:
+ break
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def gcd(f, g=None, *gens, **args):
+ """
+ Compute GCD of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import gcd
+ >>> from sympy.abc import x
+
+ >>> gcd(x**2 - 1, x**2 - 3*x + 2)
+ x - 1
+
+ """
+ if hasattr(f, '__iter__'):
+ if g is not None:
+ gens = (g,) + gens
+
+ return gcd_list(f, *gens, **args)
+ elif g is None:
+ raise TypeError("gcd() takes 2 arguments or a sequence of arguments")
+
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+
+ # gcd for domain Q[irrational] (purely algebraic irrational)
+ a, b = map(sympify, (f, g))
+ if a.is_algebraic and a.is_irrational and b.is_algebraic and b.is_irrational:
+ frc = (a/b).ratsimp()
+ if frc.is_rational:
+ # abs ensures that the returned gcd is always non-negative
+ return abs(a/frc.as_numer_denom()[0])
+
+ except PolificationFailed as exc:
+ domain, (a, b) = construct_domain(exc.exprs)
+
+ try:
+ return domain.to_sympy(domain.gcd(a, b))
+ except NotImplementedError:
+ raise ComputationFailed('gcd', 2, exc)
+
+ result = F.gcd(G)
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def lcm_list(seq, *gens, **args):
+ """
+ Compute LCM of a list of polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy import lcm_list
+ >>> from sympy.abc import x
+
+ >>> lcm_list([x**3 - 1, x**2 - 1, x**2 - 3*x + 2])
+ x**5 - x**4 - 2*x**3 - x**2 + x + 2
+
+ """
+ seq = sympify(seq)
+
+ def try_non_polynomial_lcm(seq) -> Optional[Expr]:
+ if not gens and not args:
+ domain, numbers = construct_domain(seq)
+
+ if not numbers:
+ return domain.to_sympy(domain.one)
+ elif domain.is_Numerical:
+ result, numbers = numbers[0], numbers[1:]
+
+ for number in numbers:
+ result = domain.lcm(result, number)
+
+ return domain.to_sympy(result)
+
+ return None
+
+ result = try_non_polynomial_lcm(seq)
+
+ if result is not None:
+ return result
+
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ polys, opt = parallel_poly_from_expr(seq, *gens, **args)
+
+ # lcm for domain Q[irrational] (purely algebraic irrational)
+ if len(seq) > 1 and all(elt.is_algebraic and elt.is_irrational for elt in seq):
+ a = seq[-1]
+ lst = [ (a/elt).ratsimp() for elt in seq[:-1] ]
+ if all(frc.is_rational for frc in lst):
+ lc = 1
+ for frc in lst:
+ lc = lcm(lc, frc.as_numer_denom()[1])
+ return a*lc
+
+ except PolificationFailed as exc:
+ result = try_non_polynomial_lcm(exc.exprs)
+
+ if result is not None:
+ return result
+ else:
+ raise ComputationFailed('lcm_list', len(seq), exc)
+
+ if not polys:
+ if not opt.polys:
+ return S.One
+ else:
+ return Poly(1, opt=opt)
+
+ result, polys = polys[0], polys[1:]
+
+ for poly in polys:
+ result = result.lcm(poly)
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def lcm(f, g=None, *gens, **args):
+ """
+ Compute LCM of ``f`` and ``g``.
+
+ Examples
+ ========
+
+ >>> from sympy import lcm
+ >>> from sympy.abc import x
+
+ >>> lcm(x**2 - 1, x**2 - 3*x + 2)
+ x**3 - 2*x**2 - x + 2
+
+ """
+ if hasattr(f, '__iter__'):
+ if g is not None:
+ gens = (g,) + gens
+
+ return lcm_list(f, *gens, **args)
+ elif g is None:
+ raise TypeError("lcm() takes 2 arguments or a sequence of arguments")
+
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+
+ # lcm for domain Q[irrational] (purely algebraic irrational)
+ a, b = map(sympify, (f, g))
+ if a.is_algebraic and a.is_irrational and b.is_algebraic and b.is_irrational:
+ frc = (a/b).ratsimp()
+ if frc.is_rational:
+ return a*frc.as_numer_denom()[1]
+
+ except PolificationFailed as exc:
+ domain, (a, b) = construct_domain(exc.exprs)
+
+ try:
+ return domain.to_sympy(domain.lcm(a, b))
+ except NotImplementedError:
+ raise ComputationFailed('lcm', 2, exc)
+
+ result = F.lcm(G)
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def terms_gcd(f, *gens, **args):
+ """
+ Remove GCD of terms from ``f``.
+
+ If the ``deep`` flag is True, then the arguments of ``f`` will have
+ terms_gcd applied to them.
+
+ If a fraction is factored out of ``f`` and ``f`` is an Add, then
+ an unevaluated Mul will be returned so that automatic simplification
+ does not redistribute it. The hint ``clear``, when set to False, can be
+ used to prevent such factoring when all coefficients are not fractions.
+
+ Examples
+ ========
+
+ >>> from sympy import terms_gcd, cos
+ >>> from sympy.abc import x, y
+ >>> terms_gcd(x**6*y**2 + x**3*y, x, y)
+ x**3*y*(x**3*y + 1)
+
+ The default action of polys routines is to expand the expression
+ given to them. terms_gcd follows this behavior:
+
+ >>> terms_gcd((3+3*x)*(x+x*y))
+ 3*x*(x*y + x + y + 1)
+
+ If this is not desired then the hint ``expand`` can be set to False.
+ In this case the expression will be treated as though it were comprised
+ of one or more terms:
+
+ >>> terms_gcd((3+3*x)*(x+x*y), expand=False)
+ (3*x + 3)*(x*y + x)
+
+ In order to traverse factors of a Mul or the arguments of other
+ functions, the ``deep`` hint can be used:
+
+ >>> terms_gcd((3 + 3*x)*(x + x*y), expand=False, deep=True)
+ 3*x*(x + 1)*(y + 1)
+ >>> terms_gcd(cos(x + x*y), deep=True)
+ cos(x*(y + 1))
+
+ Rationals are factored out by default:
+
+ >>> terms_gcd(x + y/2)
+ (2*x + y)/2
+
+ Only the y-term had a coefficient that was a fraction; if one
+ does not want to factor out the 1/2 in cases like this, the
+ flag ``clear`` can be set to False:
+
+ >>> terms_gcd(x + y/2, clear=False)
+ x + y/2
+ >>> terms_gcd(x*y/2 + y**2, clear=False)
+ y*(x/2 + y)
+
+ The ``clear`` flag is ignored if all coefficients are fractions:
+
+ >>> terms_gcd(x/3 + y/2, clear=False)
+ (2*x + 3*y)/6
+
+ See Also
+ ========
+ sympy.core.exprtools.gcd_terms, sympy.core.exprtools.factor_terms
+
+ """
+
+ orig = sympify(f)
+
+ if isinstance(f, Equality):
+ return Equality(*(terms_gcd(s, *gens, **args) for s in [f.lhs, f.rhs]))
+ elif isinstance(f, Relational):
+ raise TypeError("Inequalities cannot be used with terms_gcd. Found: %s" %(f,))
+
+ if not isinstance(f, Expr) or f.is_Atom:
+ return orig
+
+ if args.get('deep', False):
+ new = f.func(*[terms_gcd(a, *gens, **args) for a in f.args])
+ args.pop('deep')
+ args['expand'] = False
+ return terms_gcd(new, *gens, **args)
+
+ clear = args.pop('clear', True)
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ return exc.expr
+
+ J, f = F.terms_gcd()
+
+ if opt.domain.is_Ring:
+ if opt.domain.is_Field:
+ denom, f = f.clear_denoms(convert=True)
+
+ coeff, f = f.primitive()
+
+ if opt.domain.is_Field:
+ coeff /= denom
+ else:
+ coeff = S.One
+
+ term = Mul(*[x**j for x, j in zip(f.gens, J)])
+ if equal_valued(coeff, 1):
+ coeff = S.One
+ if term == 1:
+ return orig
+
+ if clear:
+ return _keep_coeff(coeff, term*f.as_expr())
+ # base the clearing on the form of the original expression, not
+ # the (perhaps) Mul that we have now
+ coeff, f = _keep_coeff(coeff, f.as_expr(), clear=False).as_coeff_Mul()
+ return _keep_coeff(coeff, term*f, clear=False)
+
+
+@public
+def trunc(f, p, *gens, **args):
+ """
+ Reduce ``f`` modulo a constant ``p``.
+
+ Examples
+ ========
+
+ >>> from sympy import trunc
+ >>> from sympy.abc import x
+
+ >>> trunc(2*x**3 + 3*x**2 + 5*x + 7, 3)
+ -x**3 - x + 1
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('trunc', 1, exc)
+
+ result = F.trunc(sympify(p))
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def monic(f, *gens, **args):
+ """
+ Divide all coefficients of ``f`` by ``LC(f)``.
+
+ Examples
+ ========
+
+ >>> from sympy import monic
+ >>> from sympy.abc import x
+
+ >>> monic(3*x**2 + 4*x + 2)
+ x**2 + 4*x/3 + 2/3
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('monic', 1, exc)
+
+ result = F.monic(auto=opt.auto)
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def content(f, *gens, **args):
+ """
+ Compute GCD of coefficients of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import content
+ >>> from sympy.abc import x
+
+ >>> content(6*x**2 + 8*x + 12)
+ 2
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('content', 1, exc)
+
+ return F.content()
+
+
+@public
+def primitive(f, *gens, **args):
+ """
+ Compute content and the primitive form of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polytools import primitive
+ >>> from sympy.abc import x
+
+ >>> primitive(6*x**2 + 8*x + 12)
+ (2, 3*x**2 + 4*x + 6)
+
+ >>> eq = (2 + 2*x)*x + 2
+
+ Expansion is performed by default:
+
+ >>> primitive(eq)
+ (2, x**2 + x + 1)
+
+ Set ``expand`` to False to shut this off. Note that the
+ extraction will not be recursive; use the as_content_primitive method
+ for recursive, non-destructive Rational extraction.
+
+ >>> primitive(eq, expand=False)
+ (1, x*(2*x + 2) + 2)
+
+ >>> eq.as_content_primitive()
+ (2, x*(x + 1) + 1)
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('primitive', 1, exc)
+
+ cont, result = F.primitive()
+ if not opt.polys:
+ return cont, result.as_expr()
+ else:
+ return cont, result
+
+
+@public
+def compose(f, g, *gens, **args):
+ """
+ Compute functional composition ``f(g)``.
+
+ Examples
+ ========
+
+ >>> from sympy import compose
+ >>> from sympy.abc import x
+
+ >>> compose(x**2 + x, x - 1)
+ x**2 - x
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ (F, G), opt = parallel_poly_from_expr((f, g), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('compose', 2, exc)
+
+ result = F.compose(G)
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def decompose(f, *gens, **args):
+ """
+ Compute functional decomposition of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import decompose
+ >>> from sympy.abc import x
+
+ >>> decompose(x**4 + 2*x**3 - x - 1)
+ [x**2 - x - 1, x**2 + x]
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('decompose', 1, exc)
+
+ result = F.decompose()
+
+ if not opt.polys:
+ return [r.as_expr() for r in result]
+ else:
+ return result
+
+
+@public
+def sturm(f, *gens, **args):
+ """
+ Compute Sturm sequence of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import sturm
+ >>> from sympy.abc import x
+
+ >>> sturm(x**3 - 2*x**2 + x - 3)
+ [x**3 - 2*x**2 + x - 3, 3*x**2 - 4*x + 1, 2*x/9 + 25/9, -2079/4]
+
+ """
+ options.allowed_flags(args, ['auto', 'polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('sturm', 1, exc)
+
+ result = F.sturm(auto=opt.auto)
+
+ if not opt.polys:
+ return [r.as_expr() for r in result]
+ else:
+ return result
+
+
+@public
+def gff_list(f, *gens, **args):
+ """
+ Compute a list of greatest factorial factors of ``f``.
+
+ Note that the input to ff() and rf() should be Poly instances to use the
+ definitions here.
+
+ Examples
+ ========
+
+ >>> from sympy import gff_list, ff, Poly
+ >>> from sympy.abc import x
+
+ >>> f = Poly(x**5 + 2*x**4 - x**3 - 2*x**2, x)
+
+ >>> gff_list(f)
+ [(Poly(x, x, domain='ZZ'), 1), (Poly(x + 2, x, domain='ZZ'), 4)]
+
+ >>> (ff(Poly(x), 1)*ff(Poly(x + 2), 4)) == f
+ True
+
+ >>> f = Poly(x**12 + 6*x**11 - 11*x**10 - 56*x**9 + 220*x**8 + 208*x**7 - \
+ 1401*x**6 + 1090*x**5 + 2715*x**4 - 6720*x**3 - 1092*x**2 + 5040*x, x)
+
+ >>> gff_list(f)
+ [(Poly(x**3 + 7, x, domain='ZZ'), 2), (Poly(x**2 + 5*x, x, domain='ZZ'), 3)]
+
+ >>> ff(Poly(x**3 + 7, x), 2)*ff(Poly(x**2 + 5*x, x), 3) == f
+ True
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('gff_list', 1, exc)
+
+ factors = F.gff_list()
+
+ if not opt.polys:
+ return [(g.as_expr(), k) for g, k in factors]
+ else:
+ return factors
+
+
+@public
+def gff(f, *gens, **args):
+ """Compute greatest factorial factorization of ``f``. """
+ raise NotImplementedError('symbolic falling factorial')
+
+
+@public
+def sqf_norm(f, *gens, **args):
+ """
+ Compute square-free norm of ``f``.
+
+ Returns ``s``, ``f``, ``r``, such that ``g(x) = f(x-sa)`` and
+ ``r(x) = Norm(g(x))`` is a square-free polynomial over ``K``,
+ where ``a`` is the algebraic extension of the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import sqf_norm, sqrt
+ >>> from sympy.abc import x
+
+ >>> sqf_norm(x**2 + 1, extension=[sqrt(3)])
+ ([1], x**2 - 2*sqrt(3)*x + 4, x**4 - 4*x**2 + 16)
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('sqf_norm', 1, exc)
+
+ s, g, r = F.sqf_norm()
+
+ s_expr = [Integer(si) for si in s]
+
+ if not opt.polys:
+ return s_expr, g.as_expr(), r.as_expr()
+ else:
+ return s_expr, g, r
+
+
+@public
+def sqf_part(f, *gens, **args):
+ """
+ Compute square-free part of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import sqf_part
+ >>> from sympy.abc import x
+
+ >>> sqf_part(x**3 - 3*x - 2)
+ x**2 - x - 2
+
+ """
+ options.allowed_flags(args, ['polys'])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('sqf_part', 1, exc)
+
+ result = F.sqf_part()
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+def _sorted_factors(factors, method):
+ """Sort a list of ``(expr, exp)`` pairs. """
+ if method == 'sqf':
+ def key(obj):
+ poly, exp = obj
+ rep = poly.rep.to_list()
+ return (exp, len(rep), len(poly.gens), str(poly.domain), rep)
+ else:
+ def key(obj):
+ poly, exp = obj
+ rep = poly.rep.to_list()
+ return (len(rep), len(poly.gens), exp, str(poly.domain), rep)
+
+ return sorted(factors, key=key)
+
+
+def _factors_product(factors):
+ """Multiply a list of ``(expr, exp)`` pairs. """
+ return Mul(*[f.as_expr()**k for f, k in factors])
+
+
+def _symbolic_factor_list(expr, opt, method):
+ """Helper function for :func:`_symbolic_factor`. """
+ coeff, factors = S.One, []
+
+ args = [i._eval_factor() if hasattr(i, '_eval_factor') else i
+ for i in Mul.make_args(expr)]
+ for arg in args:
+ if arg.is_Number or (isinstance(arg, Expr) and pure_complex(arg)):
+ coeff *= arg
+ continue
+ elif arg.is_Pow and arg.base != S.Exp1:
+ base, exp = arg.args
+ if base.is_Number and exp.is_Number:
+ coeff *= arg
+ continue
+ if base.is_Number:
+ factors.append((base, exp))
+ continue
+ else:
+ base, exp = arg, S.One
+
+ try:
+ poly, _ = _poly_from_expr(base, opt)
+ except PolificationFailed as exc:
+ factors.append((exc.expr, exp))
+ else:
+ func = getattr(poly, method + '_list')
+
+ _coeff, _factors = func()
+ if _coeff is not S.One:
+ if exp.is_Integer:
+ coeff *= _coeff**exp
+ elif _coeff.is_positive:
+ factors.append((_coeff, exp))
+ else:
+ _factors.append((_coeff, S.One))
+
+ if exp is S.One:
+ factors.extend(_factors)
+ elif exp.is_integer:
+ factors.extend([(f, k*exp) for f, k in _factors])
+ else:
+ other = []
+
+ for f, k in _factors:
+ if f.as_expr().is_positive:
+ factors.append((f, k*exp))
+ else:
+ other.append((f, k))
+
+ factors.append((_factors_product(other), exp))
+ if method == 'sqf':
+ factors = [(reduce(mul, (f for f, _ in factors if _ == k)), k)
+ for k in {i for _, i in factors}]
+
+ return coeff, factors
+
+
+def _symbolic_factor(expr, opt, method):
+ """Helper function for :func:`_factor`. """
+ if isinstance(expr, Expr):
+ if hasattr(expr,'_eval_factor'):
+ return expr._eval_factor()
+ coeff, factors = _symbolic_factor_list(together(expr, fraction=opt['fraction']), opt, method)
+ return _keep_coeff(coeff, _factors_product(factors))
+ elif hasattr(expr, 'args'):
+ return expr.func(*[_symbolic_factor(arg, opt, method) for arg in expr.args])
+ elif hasattr(expr, '__iter__'):
+ return expr.__class__([_symbolic_factor(arg, opt, method) for arg in expr])
+ else:
+ return expr
+
+
+def _generic_factor_list(expr, gens, args, method):
+ """Helper function for :func:`sqf_list` and :func:`factor_list`. """
+ options.allowed_flags(args, ['frac', 'polys'])
+ opt = options.build_options(gens, args)
+
+ expr = sympify(expr)
+
+ if isinstance(expr, (Expr, Poly)):
+ if isinstance(expr, Poly):
+ numer, denom = expr, 1
+ else:
+ numer, denom = together(expr).as_numer_denom()
+
+ cp, fp = _symbolic_factor_list(numer, opt, method)
+ cq, fq = _symbolic_factor_list(denom, opt, method)
+
+ if fq and not opt.frac:
+ raise PolynomialError("a polynomial expected, got %s" % expr)
+
+ _opt = opt.clone({"expand": True})
+
+ for factors in (fp, fq):
+ for i, (f, k) in enumerate(factors):
+ if not f.is_Poly:
+ f, _ = _poly_from_expr(f, _opt)
+ factors[i] = (f, k)
+
+ fp = _sorted_factors(fp, method)
+ fq = _sorted_factors(fq, method)
+
+ if not opt.polys:
+ fp = [(f.as_expr(), k) for f, k in fp]
+ fq = [(f.as_expr(), k) for f, k in fq]
+
+ coeff = cp/cq
+
+ if not opt.frac:
+ return coeff, fp
+ else:
+ return coeff, fp, fq
+ else:
+ raise PolynomialError("a polynomial expected, got %s" % expr)
+
+
+def _generic_factor(expr, gens, args, method):
+ """Helper function for :func:`sqf` and :func:`factor`. """
+ fraction = args.pop('fraction', True)
+ options.allowed_flags(args, [])
+ opt = options.build_options(gens, args)
+ opt['fraction'] = fraction
+ return _symbolic_factor(sympify(expr), opt, method)
+
+
+def to_rational_coeffs(f):
+ """
+ try to transform a polynomial to have rational coefficients
+
+ try to find a transformation ``x = alpha*y``
+
+ ``f(x) = lc*alpha**n * g(y)`` where ``g`` is a polynomial with
+ rational coefficients, ``lc`` the leading coefficient.
+
+ If this fails, try ``x = y + beta``
+ ``f(x) = g(y)``
+
+ Returns ``None`` if ``g`` not found;
+ ``(lc, alpha, None, g)`` in case of rescaling
+ ``(None, None, beta, g)`` in case of translation
+
+ Notes
+ =====
+
+ Currently it transforms only polynomials without roots larger than 2.
+
+ Examples
+ ========
+
+ >>> from sympy import sqrt, Poly, simplify
+ >>> from sympy.polys.polytools import to_rational_coeffs
+ >>> from sympy.abc import x
+ >>> p = Poly(((x**2-1)*(x-2)).subs({x:x*(1 + sqrt(2))}), x, domain='EX')
+ >>> lc, r, _, g = to_rational_coeffs(p)
+ >>> lc, r
+ (7 + 5*sqrt(2), 2 - 2*sqrt(2))
+ >>> g
+ Poly(x**3 + x**2 - 1/4*x - 1/4, x, domain='QQ')
+ >>> r1 = simplify(1/r)
+ >>> Poly(lc*r**3*(g.as_expr()).subs({x:x*r1}), x, domain='EX') == p
+ True
+
+ """
+ from sympy.simplify.simplify import simplify
+
+ def _try_rescale(f, f1=None):
+ """
+ try rescaling ``x -> alpha*x`` to convert f to a polynomial
+ with rational coefficients.
+ Returns ``alpha, f``; if the rescaling is successful,
+ ``alpha`` is the rescaling factor, and ``f`` is the rescaled
+ polynomial; else ``alpha`` is ``None``.
+ """
+ if not len(f.gens) == 1 or not (f.gens[0]).is_Atom:
+ return None, f
+ n = f.degree()
+ lc = f.LC()
+ f1 = f1 or f1.monic()
+ coeffs = f1.all_coeffs()[1:]
+ coeffs = [simplify(coeffx) for coeffx in coeffs]
+ if len(coeffs) > 1 and coeffs[-2]:
+ rescale1_x = simplify(coeffs[-2]/coeffs[-1])
+ coeffs1 = []
+ for i in range(len(coeffs)):
+ coeffx = simplify(coeffs[i]*rescale1_x**(i + 1))
+ if not coeffx.is_rational:
+ break
+ coeffs1.append(coeffx)
+ else:
+ rescale_x = simplify(1/rescale1_x)
+ x = f.gens[0]
+ v = [x**n]
+ for i in range(1, n + 1):
+ v.append(coeffs1[i - 1]*x**(n - i))
+ f = Add(*v)
+ f = Poly(f)
+ return lc, rescale_x, f
+ return None
+
+ def _try_translate(f, f1=None):
+ """
+ try translating ``x -> x + alpha`` to convert f to a polynomial
+ with rational coefficients.
+ Returns ``alpha, f``; if the translating is successful,
+ ``alpha`` is the translating factor, and ``f`` is the shifted
+ polynomial; else ``alpha`` is ``None``.
+ """
+ if not len(f.gens) == 1 or not (f.gens[0]).is_Atom:
+ return None, f
+ n = f.degree()
+ f1 = f1 or f1.monic()
+ coeffs = f1.all_coeffs()[1:]
+ c = simplify(coeffs[0])
+ if c.is_Add and not c.is_rational:
+ rat, nonrat = sift(c.args,
+ lambda z: z.is_rational is True, binary=True)
+ alpha = -c.func(*nonrat)/n
+ f2 = f1.shift(alpha)
+ return alpha, f2
+ return None
+
+ def _has_square_roots(p):
+ """
+ Return True if ``f`` is a sum with square roots but no other root
+ """
+ coeffs = p.coeffs()
+ has_sq = False
+ for y in coeffs:
+ for x in Add.make_args(y):
+ f = Factors(x).factors
+ r = [wx.q for b, wx in f.items() if
+ b.is_number and wx.is_Rational and wx.q >= 2]
+ if not r:
+ continue
+ if min(r) == 2:
+ has_sq = True
+ if max(r) > 2:
+ return False
+ return has_sq
+
+ if f.get_domain().is_EX and _has_square_roots(f):
+ f1 = f.monic()
+ r = _try_rescale(f, f1)
+ if r:
+ return r[0], r[1], None, r[2]
+ else:
+ r = _try_translate(f, f1)
+ if r:
+ return None, None, r[0], r[1]
+ return None
+
+
+def _torational_factor_list(p, x):
+ """
+ helper function to factor polynomial using to_rational_coeffs
+
+ Examples
+ ========
+
+ >>> from sympy.polys.polytools import _torational_factor_list
+ >>> from sympy.abc import x
+ >>> from sympy import sqrt, expand, Mul
+ >>> p = expand(((x**2-1)*(x-2)).subs({x:x*(1 + sqrt(2))}))
+ >>> factors = _torational_factor_list(p, x); factors
+ (-2, [(-x*(1 + sqrt(2))/2 + 1, 1), (-x*(1 + sqrt(2)) - 1, 1), (-x*(1 + sqrt(2)) + 1, 1)])
+ >>> expand(factors[0]*Mul(*[z[0] for z in factors[1]])) == p
+ True
+ >>> p = expand(((x**2-1)*(x-2)).subs({x:x + sqrt(2)}))
+ >>> factors = _torational_factor_list(p, x); factors
+ (1, [(x - 2 + sqrt(2), 1), (x - 1 + sqrt(2), 1), (x + 1 + sqrt(2), 1)])
+ >>> expand(factors[0]*Mul(*[z[0] for z in factors[1]])) == p
+ True
+
+ """
+ from sympy.simplify.simplify import simplify
+ p1 = Poly(p, x, domain='EX')
+ n = p1.degree()
+ res = to_rational_coeffs(p1)
+ if not res:
+ return None
+ lc, r, t, g = res
+ factors = factor_list(g.as_expr())
+ if lc:
+ c = simplify(factors[0]*lc*r**n)
+ r1 = simplify(1/r)
+ a = []
+ for z in factors[1:][0]:
+ a.append((simplify(z[0].subs({x: x*r1})), z[1]))
+ else:
+ c = factors[0]
+ a = []
+ for z in factors[1:][0]:
+ a.append((z[0].subs({x: x - t}), z[1]))
+ return (c, a)
+
+
+@public
+def sqf_list(f, *gens, **args):
+ """
+ Compute a list of square-free factors of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import sqf_list
+ >>> from sympy.abc import x
+
+ >>> sqf_list(2*x**5 + 16*x**4 + 50*x**3 + 76*x**2 + 56*x + 16)
+ (2, [(x + 1, 2), (x + 2, 3)])
+
+ """
+ return _generic_factor_list(f, gens, args, method='sqf')
+
+
+@public
+def sqf(f, *gens, **args):
+ """
+ Compute square-free factorization of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import sqf
+ >>> from sympy.abc import x
+
+ >>> sqf(2*x**5 + 16*x**4 + 50*x**3 + 76*x**2 + 56*x + 16)
+ 2*(x + 1)**2*(x + 2)**3
+
+ """
+ return _generic_factor(f, gens, args, method='sqf')
+
+
+@public
+def factor_list(f, *gens, **args):
+ """
+ Compute a list of irreducible factors of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import factor_list
+ >>> from sympy.abc import x, y
+
+ >>> factor_list(2*x**5 + 2*x**4*y + 4*x**3 + 4*x**2*y + 2*x + 2*y)
+ (2, [(x + y, 1), (x**2 + 1, 2)])
+
+ """
+ return _generic_factor_list(f, gens, args, method='factor')
+
+
+@public
+def factor(f, *gens, deep=False, **args):
+ """
+ Compute the factorization of expression, ``f``, into irreducibles. (To
+ factor an integer into primes, use ``factorint``.)
+
+ There two modes implemented: symbolic and formal. If ``f`` is not an
+ instance of :class:`Poly` and generators are not specified, then the
+ former mode is used. Otherwise, the formal mode is used.
+
+ In symbolic mode, :func:`factor` will traverse the expression tree and
+ factor its components without any prior expansion, unless an instance
+ of :class:`~.Add` is encountered (in this case formal factorization is
+ used). This way :func:`factor` can handle large or symbolic exponents.
+
+ By default, the factorization is computed over the rationals. To factor
+ over other domain, e.g. an algebraic or finite field, use appropriate
+ options: ``extension``, ``modulus`` or ``domain``.
+
+ Examples
+ ========
+
+ >>> from sympy import factor, sqrt, exp
+ >>> from sympy.abc import x, y
+
+ >>> factor(2*x**5 + 2*x**4*y + 4*x**3 + 4*x**2*y + 2*x + 2*y)
+ 2*(x + y)*(x**2 + 1)**2
+
+ >>> factor(x**2 + 1)
+ x**2 + 1
+ >>> factor(x**2 + 1, modulus=2)
+ (x + 1)**2
+ >>> factor(x**2 + 1, gaussian=True)
+ (x - I)*(x + I)
+
+ >>> factor(x**2 - 2, extension=sqrt(2))
+ (x - sqrt(2))*(x + sqrt(2))
+
+ >>> factor((x**2 - 1)/(x**2 + 4*x + 4))
+ (x - 1)*(x + 1)/(x + 2)**2
+ >>> factor((x**2 + 4*x + 4)**10000000*(x**2 + 1))
+ (x + 2)**20000000*(x**2 + 1)
+
+ By default, factor deals with an expression as a whole:
+
+ >>> eq = 2**(x**2 + 2*x + 1)
+ >>> factor(eq)
+ 2**(x**2 + 2*x + 1)
+
+ If the ``deep`` flag is True then subexpressions will
+ be factored:
+
+ >>> factor(eq, deep=True)
+ 2**((x + 1)**2)
+
+ If the ``fraction`` flag is False then rational expressions
+ will not be combined. By default it is True.
+
+ >>> factor(5*x + 3*exp(2 - 7*x), deep=True)
+ (5*x*exp(7*x) + 3*exp(2))*exp(-7*x)
+ >>> factor(5*x + 3*exp(2 - 7*x), deep=True, fraction=False)
+ 5*x + 3*exp(2)*exp(-7*x)
+
+ See Also
+ ========
+ sympy.ntheory.factor_.factorint
+
+ """
+ f = sympify(f)
+ if deep:
+ def _try_factor(expr):
+ """
+ Factor, but avoid changing the expression when unable to.
+ """
+ fac = factor(expr, *gens, **args)
+ if fac.is_Mul or fac.is_Pow:
+ return fac
+ return expr
+
+ f = bottom_up(f, _try_factor)
+ # clean up any subexpressions that may have been expanded
+ # while factoring out a larger expression
+ partials = {}
+ muladd = f.atoms(Mul, Add)
+ for p in muladd:
+ fac = factor(p, *gens, **args)
+ if (fac.is_Mul or fac.is_Pow) and fac != p:
+ partials[p] = fac
+ return f.xreplace(partials)
+
+ try:
+ return _generic_factor(f, gens, args, method='factor')
+ except PolynomialError:
+ if not f.is_commutative:
+ return factor_nc(f)
+ else:
+ raise
+
+
+@public
+def intervals(F, all=False, eps=None, inf=None, sup=None, strict=False, fast=False, sqf=False):
+ """
+ Compute isolating intervals for roots of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import intervals
+ >>> from sympy.abc import x
+
+ >>> intervals(x**2 - 3)
+ [((-2, -1), 1), ((1, 2), 1)]
+ >>> intervals(x**2 - 3, eps=1e-2)
+ [((-26/15, -19/11), 1), ((19/11, 26/15), 1)]
+
+ """
+ if not hasattr(F, '__iter__'):
+ try:
+ F = Poly(F)
+ except GeneratorsNeeded:
+ return []
+
+ return F.intervals(all=all, eps=eps, inf=inf, sup=sup, fast=fast, sqf=sqf)
+ else:
+ polys, opt = parallel_poly_from_expr(F, domain='QQ')
+
+ if len(opt.gens) > 1:
+ raise MultivariatePolynomialError
+
+ for i, poly in enumerate(polys):
+ polys[i] = poly.rep.to_list()
+
+ if eps is not None:
+ eps = opt.domain.convert(eps)
+
+ if eps <= 0:
+ raise ValueError("'eps' must be a positive rational")
+
+ if inf is not None:
+ inf = opt.domain.convert(inf)
+ if sup is not None:
+ sup = opt.domain.convert(sup)
+
+ intervals = dup_isolate_real_roots_list(polys, opt.domain,
+ eps=eps, inf=inf, sup=sup, strict=strict, fast=fast)
+
+ result = []
+
+ for (s, t), indices in intervals:
+ s, t = opt.domain.to_sympy(s), opt.domain.to_sympy(t)
+ result.append(((s, t), indices))
+
+ return result
+
+
+@public
+def refine_root(f, s, t, eps=None, steps=None, fast=False, check_sqf=False):
+ """
+ Refine an isolating interval of a root to the given precision.
+
+ Examples
+ ========
+
+ >>> from sympy import refine_root
+ >>> from sympy.abc import x
+
+ >>> refine_root(x**2 - 3, 1, 2, eps=1e-2)
+ (19/11, 26/15)
+
+ """
+ try:
+ F = Poly(f)
+ if not isinstance(f, Poly) and not F.gen.is_Symbol:
+ # root of sin(x) + 1 is -1 but when someone
+ # passes an Expr instead of Poly they may not expect
+ # that the generator will be sin(x), not x
+ raise PolynomialError("generator must be a Symbol")
+ except GeneratorsNeeded:
+ raise PolynomialError(
+ "Cannot refine a root of %s, not a polynomial" % f)
+
+ return F.refine_root(s, t, eps=eps, steps=steps, fast=fast, check_sqf=check_sqf)
+
+
+@public
+def count_roots(f, inf=None, sup=None):
+ """
+ Return the number of roots of ``f`` in ``[inf, sup]`` interval.
+
+ If one of ``inf`` or ``sup`` is complex, it will return the number of roots
+ in the complex rectangle with corners at ``inf`` and ``sup``.
+
+ Examples
+ ========
+
+ >>> from sympy import count_roots, I
+ >>> from sympy.abc import x
+
+ >>> count_roots(x**4 - 4, -3, 3)
+ 2
+ >>> count_roots(x**4 - 4, 0, 1 + 3*I)
+ 1
+
+ """
+ try:
+ F = Poly(f, greedy=False)
+ if not isinstance(f, Poly) and not F.gen.is_Symbol:
+ # root of sin(x) + 1 is -1 but when someone
+ # passes an Expr instead of Poly they may not expect
+ # that the generator will be sin(x), not x
+ raise PolynomialError("generator must be a Symbol")
+ except GeneratorsNeeded:
+ raise PolynomialError("Cannot count roots of %s, not a polynomial" % f)
+
+ return F.count_roots(inf=inf, sup=sup)
+
+
+@public
+def all_roots(f, multiple=True, radicals=True):
+ """
+ Returns the real and complex roots of ``f`` with multiplicities.
+
+ Explanation
+ ===========
+
+ Finds all real and complex roots of a univariate polynomial with rational
+ coefficients of any degree exactly. The roots are represented in the form
+ given by :func:`~.rootof`. This is equivalent to using :func:`~.rootof` to
+ find each of the indexed roots.
+
+ Examples
+ ========
+
+ >>> from sympy import all_roots
+ >>> from sympy.abc import x, y
+
+ >>> print(all_roots(x**3 + 1))
+ [-1, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2]
+
+ Simple radical formulae are used in some cases but the cubic and quartic
+ formulae are avoided. Instead most non-rational roots will be represented
+ as :class:`~.ComplexRootOf`:
+
+ >>> print(all_roots(x**3 + x + 1))
+ [CRootOf(x**3 + x + 1, 0), CRootOf(x**3 + x + 1, 1), CRootOf(x**3 + x + 1, 2)]
+
+ All roots of any polynomial with rational coefficients of any degree can be
+ represented using :py:class:`~.ComplexRootOf`. The use of
+ :py:class:`~.ComplexRootOf` bypasses limitations on the availability of
+ radical formulae for quintic and higher degree polynomials _[1]:
+
+ >>> p = x**5 - x - 1
+ >>> for r in all_roots(p): print(r)
+ CRootOf(x**5 - x - 1, 0)
+ CRootOf(x**5 - x - 1, 1)
+ CRootOf(x**5 - x - 1, 2)
+ CRootOf(x**5 - x - 1, 3)
+ CRootOf(x**5 - x - 1, 4)
+ >>> [r.evalf(3) for r in all_roots(p)]
+ [1.17, -0.765 - 0.352*I, -0.765 + 0.352*I, 0.181 - 1.08*I, 0.181 + 1.08*I]
+
+ Irrational algebraic or transcendental coefficients cannot currently be
+ handled by :func:`all_roots` (or :func:`~.rootof` more generally):
+
+ >>> from sympy import sqrt, expand
+ >>> p = expand((x - sqrt(2))*(x - sqrt(3)))
+ >>> print(p)
+ x**2 - sqrt(3)*x - sqrt(2)*x + sqrt(6)
+ >>> all_roots(p)
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: sorted roots not supported over EX
+
+ In the case of algebraic or transcendental coefficients
+ :func:`~.ground_roots` might be able to find some roots by factorisation:
+
+ >>> from sympy import ground_roots
+ >>> ground_roots(p, x, extension=True)
+ {sqrt(2): 1, sqrt(3): 1}
+
+ If the coefficients are numeric then :func:`~.nroots` can be used to find
+ all roots approximately:
+
+ >>> from sympy import nroots
+ >>> nroots(p, 5)
+ [1.4142, 1.732]
+
+ If the coefficients are symbolic then :func:`sympy.polys.polyroots.roots`
+ or :func:`~.ground_roots` should be used instead:
+
+ >>> from sympy import roots, ground_roots
+ >>> p = x**2 - 3*x*y + 2*y**2
+ >>> roots(p, x)
+ {y: 1, 2*y: 1}
+ >>> ground_roots(p, x)
+ {y: 1, 2*y: 1}
+
+ Parameters
+ ==========
+
+ f : :class:`~.Expr` or :class:`~.Poly`
+ A univariate polynomial with rational (or ``Float``) coefficients.
+ multiple : ``bool`` (default ``True``).
+ Whether to return a ``list`` of roots or a list of root/multiplicity
+ pairs.
+ radicals : ``bool`` (default ``True``)
+ Use simple radical formulae rather than :py:class:`~.ComplexRootOf` for
+ some irrational roots.
+
+ Returns
+ =======
+
+ A list of :class:`~.Expr` (usually :class:`~.ComplexRootOf`) representing
+ the roots is returned with each root repeated according to its multiplicity
+ as a root of ``f``. The roots are always uniquely ordered with real roots
+ coming before complex roots. The real roots are in increasing order.
+ Complex roots are ordered by increasing real part and then increasing
+ imaginary part.
+
+ If ``multiple=False`` is passed then a list of root/multiplicity pairs is
+ returned instead.
+
+ If ``radicals=False`` is passed then all roots will be represented as
+ either rational numbers or :class:`~.ComplexRootOf`.
+
+ See also
+ ========
+
+ Poly.all_roots:
+ The underlying :class:`Poly` method used by :func:`~.all_roots`.
+ rootof:
+ Compute a single numbered root of a univariate polynomial.
+ real_roots:
+ Compute all the real roots using :func:`~.rootof`.
+ ground_roots:
+ Compute some roots in the ground domain by factorisation.
+ nroots:
+ Compute all roots using approximate numerical techniques.
+ sympy.polys.polyroots.roots:
+ Compute symbolic expressions for roots using radical formulae.
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem
+ """
+ try:
+ F = Poly(f, greedy=False)
+ if not isinstance(f, Poly) and not F.gen.is_Symbol:
+ # root of sin(x) + 1 is -1 but when someone
+ # passes an Expr instead of Poly they may not expect
+ # that the generator will be sin(x), not x
+ raise PolynomialError("generator must be a Symbol")
+ except GeneratorsNeeded:
+ raise PolynomialError(
+ "Cannot compute real roots of %s, not a polynomial" % f)
+
+ return F.all_roots(multiple=multiple, radicals=radicals)
+
+
+@public
+def real_roots(f, multiple=True, radicals=True):
+ """
+ Returns the real roots of ``f`` with multiplicities.
+
+ Explanation
+ ===========
+
+ Finds all real roots of a univariate polynomial with rational coefficients
+ of any degree exactly. The roots are represented in the form given by
+ :func:`~.rootof`. This is equivalent to using :func:`~.rootof` or
+ :func:`~.all_roots` and filtering out only the real roots. However if only
+ the real roots are needed then :func:`real_roots` is more efficient than
+ :func:`~.all_roots` because it computes only the real roots and avoids
+ costly complex root isolation routines.
+
+ Examples
+ ========
+
+ >>> from sympy import real_roots
+ >>> from sympy.abc import x, y
+
+ >>> real_roots(2*x**3 - 7*x**2 + 4*x + 4)
+ [-1/2, 2, 2]
+ >>> real_roots(2*x**3 - 7*x**2 + 4*x + 4, multiple=False)
+ [(-1/2, 1), (2, 2)]
+
+ Real roots of any polynomial with rational coefficients of any degree can
+ be represented using :py:class:`~.ComplexRootOf`:
+
+ >>> p = x**9 + 2*x + 2
+ >>> print(real_roots(p))
+ [CRootOf(x**9 + 2*x + 2, 0)]
+ >>> [r.evalf(3) for r in real_roots(p)]
+ [-0.865]
+
+ All rational roots will be returned as rational numbers. Roots of some
+ simple factors will be expressed using radical or other formulae (unless
+ ``radicals=False`` is passed). All other roots will be expressed as
+ :class:`~.ComplexRootOf`.
+
+ >>> p = (x + 7)*(x**2 - 2)*(x**3 + x + 1)
+ >>> print(real_roots(p))
+ [-7, -sqrt(2), CRootOf(x**3 + x + 1, 0), sqrt(2)]
+ >>> print(real_roots(p, radicals=False))
+ [-7, CRootOf(x**2 - 2, 0), CRootOf(x**3 + x + 1, 0), CRootOf(x**2 - 2, 1)]
+
+ All returned root expressions will numerically evaluate to real numbers
+ with no imaginary part. This is in contrast to the expressions generated by
+ the cubic or quartic formulae as used by :func:`~.roots` which suffer from
+ casus irreducibilis [1]_:
+
+ >>> from sympy import roots
+ >>> p = 2*x**3 - 9*x**2 - 6*x + 3
+ >>> [r.evalf(5) for r in roots(p, multiple=True)]
+ [5.0365 - 0.e-11*I, 0.33984 + 0.e-13*I, -0.87636 + 0.e-10*I]
+ >>> [r.evalf(5) for r in real_roots(p, x)]
+ [-0.87636, 0.33984, 5.0365]
+ >>> [r.is_real for r in roots(p, multiple=True)]
+ [None, None, None]
+ >>> [r.is_real for r in real_roots(p)]
+ [True, True, True]
+
+ Using :func:`real_roots` is equivalent to using :func:`~.all_roots` (or
+ :func:`~.rootof`) and filtering out only the real roots:
+
+ >>> from sympy import all_roots
+ >>> r = [r for r in all_roots(p) if r.is_real]
+ >>> real_roots(p) == r
+ True
+
+ If only the real roots are wanted then using :func:`real_roots` is faster
+ than using :func:`~.all_roots`. Using :func:`real_roots` avoids complex root
+ isolation which can be a lot slower than real root isolation especially for
+ polynomials of high degree which typically have many more complex roots
+ than real roots.
+
+ Irrational algebraic or transcendental coefficients cannot be handled by
+ :func:`real_roots` (or :func:`~.rootof` more generally):
+
+ >>> from sympy import sqrt, expand
+ >>> p = expand((x - sqrt(2))*(x - sqrt(3)))
+ >>> print(p)
+ x**2 - sqrt(3)*x - sqrt(2)*x + sqrt(6)
+ >>> real_roots(p)
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: sorted roots not supported over EX
+
+ In the case of algebraic or transcendental coefficients
+ :func:`~.ground_roots` might be able to find some roots by factorisation:
+
+ >>> from sympy import ground_roots
+ >>> ground_roots(p, x, extension=True)
+ {sqrt(2): 1, sqrt(3): 1}
+
+ If the coefficients are numeric then :func:`~.nroots` can be used to find
+ all roots approximately:
+
+ >>> from sympy import nroots
+ >>> nroots(p, 5)
+ [1.4142, 1.732]
+
+ If the coefficients are symbolic then :func:`sympy.polys.polyroots.roots`
+ or :func:`~.ground_roots` should be used instead.
+
+ >>> from sympy import roots, ground_roots
+ >>> p = x**2 - 3*x*y + 2*y**2
+ >>> roots(p, x)
+ {y: 1, 2*y: 1}
+ >>> ground_roots(p, x)
+ {y: 1, 2*y: 1}
+
+ Parameters
+ ==========
+
+ f : :class:`~.Expr` or :class:`~.Poly`
+ A univariate polynomial with rational (or ``Float``) coefficients.
+ multiple : ``bool`` (default ``True``).
+ Whether to return a ``list`` of roots or a list of root/multiplicity
+ pairs.
+ radicals : ``bool`` (default ``True``)
+ Use simple radical formulae rather than :py:class:`~.ComplexRootOf` for
+ some irrational roots.
+
+ Returns
+ =======
+
+ A list of :class:`~.Expr` (usually :class:`~.ComplexRootOf`) representing
+ the real roots is returned. The roots are arranged in increasing order and
+ are repeated according to their multiplicities as roots of ``f``.
+
+ If ``multiple=False`` is passed then a list of root/multiplicity pairs is
+ returned instead.
+
+ If ``radicals=False`` is passed then all roots will be represented as
+ either rational numbers or :class:`~.ComplexRootOf`.
+
+ See also
+ ========
+
+ Poly.real_roots:
+ The underlying :class:`Poly` method used by :func:`real_roots`.
+ rootof:
+ Compute a single numbered root of a univariate polynomial.
+ all_roots:
+ Compute all real and non-real roots using :func:`~.rootof`.
+ ground_roots:
+ Compute some roots in the ground domain by factorisation.
+ nroots:
+ Compute all roots using approximate numerical techniques.
+ sympy.polys.polyroots.roots:
+ Compute symbolic expressions for roots using radical formulae.
+
+ References
+ ==========
+
+ .. [1] https://en.wikipedia.org/wiki/Casus_irreducibilis
+ """
+ try:
+ F = Poly(f, greedy=False)
+ if not isinstance(f, Poly) and not F.gen.is_Symbol:
+ # root of sin(x) + 1 is -1 but when someone
+ # passes an Expr instead of Poly they may not expect
+ # that the generator will be sin(x), not x
+ raise PolynomialError("generator must be a Symbol")
+ except GeneratorsNeeded:
+ raise PolynomialError(
+ "Cannot compute real roots of %s, not a polynomial" % f)
+
+ return F.real_roots(multiple=multiple, radicals=radicals)
+
+
+@public
+def nroots(f, n=15, maxsteps=50, cleanup=True):
+ """
+ Compute numerical approximations of roots of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import nroots
+ >>> from sympy.abc import x
+
+ >>> nroots(x**2 - 3, n=15)
+ [-1.73205080756888, 1.73205080756888]
+ >>> nroots(x**2 - 3, n=30)
+ [-1.73205080756887729352744634151, 1.73205080756887729352744634151]
+
+ """
+ try:
+ F = Poly(f, greedy=False)
+ if not isinstance(f, Poly) and not F.gen.is_Symbol:
+ # root of sin(x) + 1 is -1 but when someone
+ # passes an Expr instead of Poly they may not expect
+ # that the generator will be sin(x), not x
+ raise PolynomialError("generator must be a Symbol")
+ except GeneratorsNeeded:
+ raise PolynomialError(
+ "Cannot compute numerical roots of %s, not a polynomial" % f)
+
+ return F.nroots(n=n, maxsteps=maxsteps, cleanup=cleanup)
+
+
+@public
+def ground_roots(f, *gens, **args):
+ """
+ Compute roots of ``f`` by factorization in the ground domain.
+
+ Examples
+ ========
+
+ >>> from sympy import ground_roots
+ >>> from sympy.abc import x
+
+ >>> ground_roots(x**6 - 4*x**4 + 4*x**3 - x**2)
+ {0: 2, 1: 2}
+
+ """
+ options.allowed_flags(args, [])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ if not isinstance(f, Poly) and not F.gen.is_Symbol:
+ # root of sin(x) + 1 is -1 but when someone
+ # passes an Expr instead of Poly they may not expect
+ # that the generator will be sin(x), not x
+ raise PolynomialError("generator must be a Symbol")
+ except PolificationFailed as exc:
+ raise ComputationFailed('ground_roots', 1, exc)
+
+ return F.ground_roots()
+
+
+@public
+def nth_power_roots_poly(f, n, *gens, **args):
+ """
+ Construct a polynomial with n-th powers of roots of ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import nth_power_roots_poly, factor, roots
+ >>> from sympy.abc import x
+
+ >>> f = x**4 - x**2 + 1
+ >>> g = factor(nth_power_roots_poly(f, 2))
+
+ >>> g
+ (x**2 - x + 1)**2
+
+ >>> R_f = [ (r**2).expand() for r in roots(f) ]
+ >>> R_g = roots(g).keys()
+
+ >>> set(R_f) == set(R_g)
+ True
+
+ """
+ options.allowed_flags(args, [])
+
+ try:
+ F, opt = poly_from_expr(f, *gens, **args)
+ if not isinstance(f, Poly) and not F.gen.is_Symbol:
+ # root of sin(x) + 1 is -1 but when someone
+ # passes an Expr instead of Poly they may not expect
+ # that the generator will be sin(x), not x
+ raise PolynomialError("generator must be a Symbol")
+ except PolificationFailed as exc:
+ raise ComputationFailed('nth_power_roots_poly', 1, exc)
+
+ result = F.nth_power_roots_poly(n)
+
+ if not opt.polys:
+ return result.as_expr()
+ else:
+ return result
+
+
+@public
+def cancel(f, *gens, _signsimp=True, **args):
+ """
+ Cancel common factors in a rational function ``f``.
+
+ Examples
+ ========
+
+ >>> from sympy import cancel, sqrt, Symbol, together
+ >>> from sympy.abc import x
+ >>> A = Symbol('A', commutative=False)
+
+ >>> cancel((2*x**2 - 2)/(x**2 - 2*x + 1))
+ (2*x + 2)/(x - 1)
+ >>> cancel((sqrt(3) + sqrt(15)*A)/(sqrt(2) + sqrt(10)*A))
+ sqrt(6)/2
+
+ Note: due to automatic distribution of Rationals, a sum divided by an integer
+ will appear as a sum. To recover a rational form use `together` on the result:
+
+ >>> cancel(x/2 + 1)
+ x/2 + 1
+ >>> together(_)
+ (x + 2)/2
+ """
+ from sympy.simplify.simplify import signsimp
+ from sympy.polys.rings import sring
+ options.allowed_flags(args, ['polys'])
+
+ f = sympify(f)
+ if _signsimp:
+ f = signsimp(f)
+ opt = {}
+ if 'polys' in args:
+ opt['polys'] = args['polys']
+
+ if not isinstance(f, (tuple, Tuple)):
+ if f.is_Number or isinstance(f, Relational) or not isinstance(f, Expr):
+ return f
+ f = factor_terms(f, radical=True)
+ p, q = f.as_numer_denom()
+
+ elif len(f) == 2:
+ p, q = f
+ if isinstance(p, Poly) and isinstance(q, Poly):
+ opt['gens'] = p.gens
+ opt['domain'] = p.domain
+ opt['polys'] = opt.get('polys', True)
+ p, q = p.as_expr(), q.as_expr()
+ elif isinstance(f, Tuple):
+ return factor_terms(f)
+ else:
+ raise ValueError('unexpected argument: %s' % f)
+
+ from sympy.functions.elementary.piecewise import Piecewise
+ try:
+ if f.has(Piecewise):
+ raise PolynomialError()
+ R, (F, G) = sring((p, q), *gens, **args)
+ if not R.ngens:
+ if not isinstance(f, (tuple, Tuple)):
+ return f.expand()
+ else:
+ return S.One, p, q
+ except PolynomialError as msg:
+ if f.is_commutative and not f.has(Piecewise):
+ raise PolynomialError(msg)
+ # Handling of noncommutative and/or piecewise expressions
+ if f.is_Add or f.is_Mul:
+ c, nc = sift(f.args, lambda x:
+ x.is_commutative is True and not x.has(Piecewise),
+ binary=True)
+ nc = [cancel(i) for i in nc]
+ return f.func(cancel(f.func(*c)), *nc)
+ else:
+ reps = []
+ pot = preorder_traversal(f)
+ next(pot)
+ for e in pot:
+ # XXX: This should really skip anything that's not Expr.
+ if isinstance(e, (tuple, Tuple, BooleanAtom)):
+ continue
+ try:
+ reps.append((e, cancel(e)))
+ pot.skip() # this was handled successfully
+ except NotImplementedError:
+ pass
+ return f.xreplace(dict(reps))
+
+ c, (P, Q) = 1, F.cancel(G)
+ if opt.get('polys', False) and 'gens' not in opt:
+ opt['gens'] = R.symbols
+
+ if not isinstance(f, (tuple, Tuple)):
+ return c*(P.as_expr()/Q.as_expr())
+ else:
+ P, Q = P.as_expr(), Q.as_expr()
+ if not opt.get('polys', False):
+ return c, P, Q
+ else:
+ return c, Poly(P, *gens, **opt), Poly(Q, *gens, **opt)
+
+
+@public
+def reduced(f, G, *gens, **args):
+ """
+ Reduces a polynomial ``f`` modulo a set of polynomials ``G``.
+
+ Given a polynomial ``f`` and a set of polynomials ``G = (g_1, ..., g_n)``,
+ computes a set of quotients ``q = (q_1, ..., q_n)`` and the remainder ``r``
+ such that ``f = q_1*g_1 + ... + q_n*g_n + r``, where ``r`` vanishes or ``r``
+ is a completely reduced polynomial with respect to ``G``.
+
+ Examples
+ ========
+
+ >>> from sympy import reduced
+ >>> from sympy.abc import x, y
+
+ >>> reduced(2*x**4 + y**2 - x**2 + y**3, [x**3 - x, y**3 - y])
+ ([2*x, 1], x**2 + y**2 + y)
+
+ """
+ options.allowed_flags(args, ['polys', 'auto'])
+
+ try:
+ polys, opt = parallel_poly_from_expr([f] + list(G), *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('reduced', 0, exc)
+
+ domain = opt.domain
+ retract = False
+
+ if opt.auto and domain.is_Ring and not domain.is_Field:
+ opt = opt.clone({"domain": domain.get_field()})
+ retract = True
+
+ from sympy.polys.rings import xring
+ _ring, _ = xring(opt.gens, opt.domain, opt.order)
+
+ for i, poly in enumerate(polys):
+ poly = poly.set_domain(opt.domain).rep.to_dict()
+ polys[i] = _ring.from_dict(poly)
+
+ Q, r = polys[0].div(polys[1:])
+
+ Q = [Poly._from_dict(dict(q), opt) for q in Q]
+ r = Poly._from_dict(dict(r), opt)
+
+ if retract:
+ try:
+ _Q, _r = [q.to_ring() for q in Q], r.to_ring()
+ except CoercionFailed:
+ pass
+ else:
+ Q, r = _Q, _r
+
+ if not opt.polys:
+ return [q.as_expr() for q in Q], r.as_expr()
+ else:
+ return Q, r
+
+
+@public
+def groebner(F, *gens, **args):
+ """
+ Computes the reduced Groebner basis for a set of polynomials.
+
+ Use the ``order`` argument to set the monomial ordering that will be
+ used to compute the basis. Allowed orders are ``lex``, ``grlex`` and
+ ``grevlex``. If no order is specified, it defaults to ``lex``.
+
+ For more information on Groebner bases, see the references and the docstring
+ of :func:`~.solve_poly_system`.
+
+ Examples
+ ========
+
+ Example taken from [1].
+
+ >>> from sympy import groebner
+ >>> from sympy.abc import x, y
+
+ >>> F = [x*y - 2*y, 2*y**2 - x**2]
+
+ >>> groebner(F, x, y, order='lex')
+ GroebnerBasis([x**2 - 2*y**2, x*y - 2*y, y**3 - 2*y], x, y,
+ domain='ZZ', order='lex')
+ >>> groebner(F, x, y, order='grlex')
+ GroebnerBasis([y**3 - 2*y, x**2 - 2*y**2, x*y - 2*y], x, y,
+ domain='ZZ', order='grlex')
+ >>> groebner(F, x, y, order='grevlex')
+ GroebnerBasis([y**3 - 2*y, x**2 - 2*y**2, x*y - 2*y], x, y,
+ domain='ZZ', order='grevlex')
+
+ By default, an improved implementation of the Buchberger algorithm is
+ used. Optionally, an implementation of the F5B algorithm can be used. The
+ algorithm can be set using the ``method`` flag or with the
+ :func:`sympy.polys.polyconfig.setup` function.
+
+ >>> F = [x**2 - x - 1, (2*x - 1) * y - (x**10 - (1 - x)**10)]
+
+ >>> groebner(F, x, y, method='buchberger')
+ GroebnerBasis([x**2 - x - 1, y - 55], x, y, domain='ZZ', order='lex')
+ >>> groebner(F, x, y, method='f5b')
+ GroebnerBasis([x**2 - x - 1, y - 55], x, y, domain='ZZ', order='lex')
+
+ References
+ ==========
+
+ 1. [Buchberger01]_
+ 2. [Cox97]_
+
+ """
+ return GroebnerBasis(F, *gens, **args)
+
+
+@public
+def is_zero_dimensional(F, *gens, **args):
+ """
+ Checks if the ideal generated by a Groebner basis is zero-dimensional.
+
+ The algorithm checks if the set of monomials not divisible by the
+ leading monomial of any element of ``F`` is bounded.
+
+ References
+ ==========
+
+ David A. Cox, John B. Little, Donal O'Shea. Ideals, Varieties and
+ Algorithms, 3rd edition, p. 230
+
+ """
+ return GroebnerBasis(F, *gens, **args).is_zero_dimensional
+
+
+@public
+class GroebnerBasis(Basic):
+ """Represents a reduced Groebner basis. """
+
+ def __new__(cls, F, *gens, **args):
+ """Compute a reduced Groebner basis for a system of polynomials. """
+ options.allowed_flags(args, ['polys', 'method'])
+
+ try:
+ polys, opt = parallel_poly_from_expr(F, *gens, **args)
+ except PolificationFailed as exc:
+ raise ComputationFailed('groebner', len(F), exc)
+
+ from sympy.polys.rings import PolyRing
+ ring = PolyRing(opt.gens, opt.domain, opt.order)
+
+ polys = [ring.from_dict(poly.rep.to_dict()) for poly in polys if poly]
+
+ G = _groebner(polys, ring, method=opt.method)
+ G = [Poly._from_dict(g, opt) for g in G]
+
+ return cls._new(G, opt)
+
+ @classmethod
+ def _new(cls, basis, options):
+ obj = Basic.__new__(cls)
+
+ obj._basis = tuple(basis)
+ obj._options = options
+
+ return obj
+
+ @property
+ def args(self):
+ basis = (p.as_expr() for p in self._basis)
+ return (Tuple(*basis), Tuple(*self._options.gens))
+
+ @property
+ def exprs(self):
+ return [poly.as_expr() for poly in self._basis]
+
+ @property
+ def polys(self):
+ return list(self._basis)
+
+ @property
+ def gens(self):
+ return self._options.gens
+
+ @property
+ def domain(self):
+ return self._options.domain
+
+ @property
+ def order(self):
+ return self._options.order
+
+ def __len__(self):
+ return len(self._basis)
+
+ def __iter__(self):
+ if self._options.polys:
+ return iter(self.polys)
+ else:
+ return iter(self.exprs)
+
+ def __getitem__(self, item):
+ if self._options.polys:
+ basis = self.polys
+ else:
+ basis = self.exprs
+
+ return basis[item]
+
+ def __hash__(self):
+ return hash((self._basis, tuple(self._options.items())))
+
+ def __eq__(self, other):
+ if isinstance(other, self.__class__):
+ return self._basis == other._basis and self._options == other._options
+ elif iterable(other):
+ return self.polys == list(other) or self.exprs == list(other)
+ else:
+ return False
+
+ def __ne__(self, other):
+ return not self == other
+
+ @property
+ def is_zero_dimensional(self):
+ """
+ Checks if the ideal generated by a Groebner basis is zero-dimensional.
+
+ The algorithm checks if the set of monomials not divisible by the
+ leading monomial of any element of ``F`` is bounded.
+
+ References
+ ==========
+
+ David A. Cox, John B. Little, Donal O'Shea. Ideals, Varieties and
+ Algorithms, 3rd edition, p. 230
+
+ """
+ def single_var(monomial):
+ return sum(map(bool, monomial)) == 1
+
+ exponents = Monomial([0]*len(self.gens))
+ order = self._options.order
+
+ for poly in self.polys:
+ monomial = poly.LM(order=order)
+
+ if single_var(monomial):
+ exponents *= monomial
+
+ # If any element of the exponents vector is zero, then there's
+ # a variable for which there's no degree bound and the ideal
+ # generated by this Groebner basis isn't zero-dimensional.
+ return all(exponents)
+
+ def fglm(self, order):
+ """
+ Convert a Groebner basis from one ordering to another.
+
+ The FGLM algorithm converts reduced Groebner bases of zero-dimensional
+ ideals from one ordering to another. This method is often used when it
+ is infeasible to compute a Groebner basis with respect to a particular
+ ordering directly.
+
+ Examples
+ ========
+
+ >>> from sympy.abc import x, y
+ >>> from sympy import groebner
+
+ >>> F = [x**2 - 3*y - x + 1, y**2 - 2*x + y - 1]
+ >>> G = groebner(F, x, y, order='grlex')
+
+ >>> list(G.fglm('lex'))
+ [2*x - y**2 - y + 1, y**4 + 2*y**3 - 3*y**2 - 16*y + 7]
+ >>> list(groebner(F, x, y, order='lex'))
+ [2*x - y**2 - y + 1, y**4 + 2*y**3 - 3*y**2 - 16*y + 7]
+
+ References
+ ==========
+
+ .. [1] J.C. Faugere, P. Gianni, D. Lazard, T. Mora (1994). Efficient
+ Computation of Zero-dimensional Groebner Bases by Change of
+ Ordering
+
+ """
+ opt = self._options
+
+ src_order = opt.order
+ dst_order = monomial_key(order)
+
+ if src_order == dst_order:
+ return self
+
+ if not self.is_zero_dimensional:
+ raise NotImplementedError("Cannot convert Groebner bases of ideals with positive dimension")
+
+ polys = list(self._basis)
+ domain = opt.domain
+
+ opt = opt.clone({
+ "domain": domain.get_field(),
+ "order": dst_order,
+ })
+
+ from sympy.polys.rings import xring
+ _ring, _ = xring(opt.gens, opt.domain, src_order)
+
+ for i, poly in enumerate(polys):
+ poly = poly.set_domain(opt.domain).rep.to_dict()
+ polys[i] = _ring.from_dict(poly)
+
+ G = matrix_fglm(polys, _ring, dst_order)
+ G = [Poly._from_dict(dict(g), opt) for g in G]
+
+ if not domain.is_Field:
+ G = [g.clear_denoms(convert=True)[1] for g in G]
+ opt.domain = domain
+
+ return self._new(G, opt)
+
+ def reduce(self, expr, auto=True):
+ """
+ Reduces a polynomial modulo a Groebner basis.
+
+ Given a polynomial ``f`` and a set of polynomials ``G = (g_1, ..., g_n)``,
+ computes a set of quotients ``q = (q_1, ..., q_n)`` and the remainder ``r``
+ such that ``f = q_1*f_1 + ... + q_n*f_n + r``, where ``r`` vanishes or ``r``
+ is a completely reduced polynomial with respect to ``G``.
+
+ Examples
+ ========
+
+ >>> from sympy import groebner, expand
+ >>> from sympy.abc import x, y
+
+ >>> f = 2*x**4 - x**2 + y**3 + y**2
+ >>> G = groebner([x**3 - x, y**3 - y])
+
+ >>> G.reduce(f)
+ ([2*x, 1], x**2 + y**2 + y)
+ >>> Q, r = _
+
+ >>> expand(sum(q*g for q, g in zip(Q, G)) + r)
+ 2*x**4 - x**2 + y**3 + y**2
+ >>> _ == f
+ True
+
+ """
+ poly = Poly._from_expr(expr, self._options)
+ polys = [poly] + list(self._basis)
+
+ opt = self._options
+ domain = opt.domain
+
+ retract = False
+
+ if auto and domain.is_Ring and not domain.is_Field:
+ opt = opt.clone({"domain": domain.get_field()})
+ retract = True
+
+ from sympy.polys.rings import xring
+ _ring, _ = xring(opt.gens, opt.domain, opt.order)
+
+ for i, poly in enumerate(polys):
+ poly = poly.set_domain(opt.domain).rep.to_dict()
+ polys[i] = _ring.from_dict(poly)
+
+ Q, r = polys[0].div(polys[1:])
+
+ Q = [Poly._from_dict(dict(q), opt) for q in Q]
+ r = Poly._from_dict(dict(r), opt)
+
+ if retract:
+ try:
+ _Q, _r = [q.to_ring() for q in Q], r.to_ring()
+ except CoercionFailed:
+ pass
+ else:
+ Q, r = _Q, _r
+
+ if not opt.polys:
+ return [q.as_expr() for q in Q], r.as_expr()
+ else:
+ return Q, r
+
+ def contains(self, poly):
+ """
+ Check if ``poly`` belongs the ideal generated by ``self``.
+
+ Examples
+ ========
+
+ >>> from sympy import groebner
+ >>> from sympy.abc import x, y
+
+ >>> f = 2*x**3 + y**3 + 3*y
+ >>> G = groebner([x**2 + y**2 - 1, x*y - 2])
+
+ >>> G.contains(f)
+ True
+ >>> G.contains(f + 1)
+ False
+
+ """
+ return self.reduce(poly)[1] == 0
+
+
+@public
+def poly(expr, *gens, **args):
+ """
+ Efficiently transform an expression into a polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy import poly
+ >>> from sympy.abc import x
+
+ >>> poly(x*(x**2 + x - 1)**2)
+ Poly(x**5 + 2*x**4 - x**3 - 2*x**2 + x, x, domain='ZZ')
+
+ """
+ options.allowed_flags(args, [])
+
+ def _poly(expr, opt):
+ terms, poly_terms = [], []
+
+ for term in Add.make_args(expr):
+ factors, poly_factors = [], []
+
+ for factor in Mul.make_args(term):
+ if factor.is_Add:
+ poly_factors.append(_poly(factor, opt))
+ elif factor.is_Pow and factor.base.is_Add and \
+ factor.exp.is_Integer and factor.exp >= 0:
+ poly_factors.append(
+ _poly(factor.base, opt).pow(factor.exp))
+ else:
+ factors.append(factor)
+
+ if not poly_factors:
+ terms.append(term)
+ else:
+ product = poly_factors[0]
+
+ for factor in poly_factors[1:]:
+ product = product.mul(factor)
+
+ if factors:
+ factor = Mul(*factors)
+
+ if factor.is_Number:
+ product *= factor
+ else:
+ product = product.mul(Poly._from_expr(factor, opt))
+
+ poly_terms.append(product)
+
+ if not poly_terms:
+ result = Poly._from_expr(expr, opt)
+ else:
+ result = poly_terms[0]
+
+ for term in poly_terms[1:]:
+ result = result.add(term)
+
+ if terms:
+ term = Add(*terms)
+
+ if term.is_Number:
+ result += term
+ else:
+ result = result.add(Poly._from_expr(term, opt))
+
+ return result.reorder(*opt.get('gens', ()), **args)
+
+ expr = sympify(expr)
+
+ if expr.is_Poly:
+ return Poly(expr, *gens, **args)
+
+ if 'expand' not in args:
+ args['expand'] = False
+
+ opt = options.build_options(gens, args)
+
+ return _poly(expr, opt)
+
+
+def named_poly(n, f, K, name, x, polys):
+ r"""Common interface to the low-level polynomial generating functions
+ in orthopolys and appellseqs.
+
+ Parameters
+ ==========
+
+ n : int
+ Index of the polynomial, which may or may not equal its degree.
+ f : callable
+ Low-level generating function to use.
+ K : Domain or None
+ Domain in which to perform the computations. If None, use the smallest
+ field containing the rationals and the extra parameters of x (see below).
+ name : str
+ Name of an arbitrary individual polynomial in the sequence generated
+ by f, only used in the error message for invalid n.
+ x : seq
+ The first element of this argument is the main variable of all
+ polynomials in this sequence. Any further elements are extra
+ parameters required by f.
+ polys : bool, optional
+ If True, return a Poly, otherwise (default) return an expression.
+ """
+ if n < 0:
+ raise ValueError("Cannot generate %s of index %s" % (name, n))
+ head, tail = x[0], x[1:]
+ if K is None:
+ K, tail = construct_domain(tail, field=True)
+ poly = DMP(f(int(n), *tail, K), K)
+ if head is None:
+ poly = PurePoly.new(poly, Dummy('x'))
+ else:
+ poly = Poly.new(poly, head)
+ return poly if polys else poly.as_expr()
diff --git a/lib/python3.12/site-packages/sympy/polys/polyutils.py b/lib/python3.12/site-packages/sympy/polys/polyutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5c49fcc9d81b335b790f2f8a1bf9a4078e89005
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/polyutils.py
@@ -0,0 +1,582 @@
+"""Useful utilities for higher level polynomial classes. """
+
+from __future__ import annotations
+
+from sympy.external.gmpy import GROUND_TYPES
+
+from sympy.core import (S, Add, Mul, Pow, Eq, Expr,
+ expand_mul, expand_multinomial)
+from sympy.core.exprtools import decompose_power, decompose_power_rat
+from sympy.core.numbers import _illegal
+from sympy.polys.polyerrors import PolynomialError, GeneratorsError
+from sympy.polys.polyoptions import build_options
+
+import re
+
+
+_gens_order = {
+ 'a': 301, 'b': 302, 'c': 303, 'd': 304,
+ 'e': 305, 'f': 306, 'g': 307, 'h': 308,
+ 'i': 309, 'j': 310, 'k': 311, 'l': 312,
+ 'm': 313, 'n': 314, 'o': 315, 'p': 216,
+ 'q': 217, 'r': 218, 's': 219, 't': 220,
+ 'u': 221, 'v': 222, 'w': 223, 'x': 124,
+ 'y': 125, 'z': 126,
+}
+
+_max_order = 1000
+_re_gen = re.compile(r"^(.*?)(\d*)$", re.MULTILINE)
+
+
+def _nsort(roots, separated=False):
+ """Sort the numerical roots putting the real roots first, then sorting
+ according to real and imaginary parts. If ``separated`` is True, then
+ the real and imaginary roots will be returned in two lists, respectively.
+
+ This routine tries to avoid issue 6137 by separating the roots into real
+ and imaginary parts before evaluation. In addition, the sorting will raise
+ an error if any computation cannot be done with precision.
+ """
+ if not all(r.is_number for r in roots):
+ raise NotImplementedError
+ # see issue 6137:
+ # get the real part of the evaluated real and imaginary parts of each root
+ key = [[i.n(2).as_real_imag()[0] for i in r.as_real_imag()] for r in roots]
+ # make sure the parts were computed with precision
+ if len(roots) > 1 and any(i._prec == 1 for k in key for i in k):
+ raise NotImplementedError("could not compute root with precision")
+ # insert a key to indicate if the root has an imaginary part
+ key = [(1 if i else 0, r, i) for r, i in key]
+ key = sorted(zip(key, roots))
+ # return the real and imaginary roots separately if desired
+ if separated:
+ r = []
+ i = []
+ for (im, _, _), v in key:
+ if im:
+ i.append(v)
+ else:
+ r.append(v)
+ return r, i
+ _, roots = zip(*key)
+ return list(roots)
+
+
+def _sort_gens(gens, **args):
+ """Sort generators in a reasonably intelligent way. """
+ opt = build_options(args)
+
+ gens_order, wrt = {}, None
+
+ if opt is not None:
+ gens_order, wrt = {}, opt.wrt
+
+ for i, gen in enumerate(opt.sort):
+ gens_order[gen] = i + 1
+
+ def order_key(gen):
+ gen = str(gen)
+
+ if wrt is not None:
+ try:
+ return (-len(wrt) + wrt.index(gen), gen, 0)
+ except ValueError:
+ pass
+
+ name, index = _re_gen.match(gen).groups()
+
+ if index:
+ index = int(index)
+ else:
+ index = 0
+
+ try:
+ return ( gens_order[name], name, index)
+ except KeyError:
+ pass
+
+ try:
+ return (_gens_order[name], name, index)
+ except KeyError:
+ pass
+
+ return (_max_order, name, index)
+
+ try:
+ gens = sorted(gens, key=order_key)
+ except TypeError: # pragma: no cover
+ pass
+
+ return tuple(gens)
+
+
+def _unify_gens(f_gens, g_gens):
+ """Unify generators in a reasonably intelligent way. """
+ f_gens = list(f_gens)
+ g_gens = list(g_gens)
+
+ if f_gens == g_gens:
+ return tuple(f_gens)
+
+ gens, common, k = [], [], 0
+
+ for gen in f_gens:
+ if gen in g_gens:
+ common.append(gen)
+
+ for i, gen in enumerate(g_gens):
+ if gen in common:
+ g_gens[i], k = common[k], k + 1
+
+ for gen in common:
+ i = f_gens.index(gen)
+
+ gens.extend(f_gens[:i])
+ f_gens = f_gens[i + 1:]
+
+ i = g_gens.index(gen)
+
+ gens.extend(g_gens[:i])
+ g_gens = g_gens[i + 1:]
+
+ gens.append(gen)
+
+ gens.extend(f_gens)
+ gens.extend(g_gens)
+
+ return tuple(gens)
+
+
+def _analyze_gens(gens):
+ """Support for passing generators as `*gens` and `[gens]`. """
+ if len(gens) == 1 and hasattr(gens[0], '__iter__'):
+ return tuple(gens[0])
+ else:
+ return tuple(gens)
+
+
+def _sort_factors(factors, **args):
+ """Sort low-level factors in increasing 'complexity' order. """
+
+ # XXX: GF(p) does not support comparisons so we need a key function to sort
+ # the factors if python-flint is being used. A better solution might be to
+ # add a sort key method to each domain.
+ def order_key(factor):
+ if isinstance(factor, _GF_types):
+ return int(factor)
+ elif isinstance(factor, list):
+ return [order_key(f) for f in factor]
+ else:
+ return factor
+
+ def order_if_multiple_key(factor):
+ (f, n) = factor
+ return (len(f), n, order_key(f))
+
+ def order_no_multiple_key(f):
+ return (len(f), order_key(f))
+
+ if args.get('multiple', True):
+ return sorted(factors, key=order_if_multiple_key)
+ else:
+ return sorted(factors, key=order_no_multiple_key)
+
+
+illegal_types = [type(obj) for obj in _illegal]
+finf = [float(i) for i in _illegal[1:3]]
+
+
+def _not_a_coeff(expr):
+ """Do not treat NaN and infinities as valid polynomial coefficients. """
+ if type(expr) in illegal_types or expr in finf:
+ return True
+ if isinstance(expr, float) and float(expr) != expr:
+ return True # nan
+ return # could be
+
+
+def _parallel_dict_from_expr_if_gens(exprs, opt):
+ """Transform expressions into a multinomial form given generators. """
+ k, indices = len(opt.gens), {}
+
+ for i, g in enumerate(opt.gens):
+ indices[g] = i
+
+ polys = []
+
+ for expr in exprs:
+ poly = {}
+
+ if expr.is_Equality:
+ expr = expr.lhs - expr.rhs
+
+ for term in Add.make_args(expr):
+ coeff, monom = [], [0]*k
+
+ for factor in Mul.make_args(term):
+ if not _not_a_coeff(factor) and factor.is_Number:
+ coeff.append(factor)
+ else:
+ try:
+ if opt.series is False:
+ base, exp = decompose_power(factor)
+
+ if exp < 0:
+ exp, base = -exp, Pow(base, -S.One)
+ else:
+ base, exp = decompose_power_rat(factor)
+
+ monom[indices[base]] = exp
+ except KeyError:
+ if not factor.has_free(*opt.gens):
+ coeff.append(factor)
+ else:
+ raise PolynomialError("%s contains an element of "
+ "the set of generators." % factor)
+
+ monom = tuple(monom)
+
+ if monom in poly:
+ poly[monom] += Mul(*coeff)
+ else:
+ poly[monom] = Mul(*coeff)
+
+ polys.append(poly)
+
+ return polys, opt.gens
+
+
+def _parallel_dict_from_expr_no_gens(exprs, opt):
+ """Transform expressions into a multinomial form and figure out generators. """
+ if opt.domain is not None:
+ def _is_coeff(factor):
+ return factor in opt.domain
+ elif opt.extension is True:
+ def _is_coeff(factor):
+ return factor.is_algebraic
+ elif opt.greedy is not False:
+ def _is_coeff(factor):
+ return factor is S.ImaginaryUnit
+ else:
+ def _is_coeff(factor):
+ return factor.is_number
+
+ gens, reprs = set(), []
+
+ for expr in exprs:
+ terms = []
+
+ if expr.is_Equality:
+ expr = expr.lhs - expr.rhs
+
+ for term in Add.make_args(expr):
+ coeff, elements = [], {}
+
+ for factor in Mul.make_args(term):
+ if not _not_a_coeff(factor) and (factor.is_Number or _is_coeff(factor)):
+ coeff.append(factor)
+ else:
+ if opt.series is False:
+ base, exp = decompose_power(factor)
+
+ if exp < 0:
+ exp, base = -exp, Pow(base, -S.One)
+ else:
+ base, exp = decompose_power_rat(factor)
+
+ elements[base] = elements.setdefault(base, 0) + exp
+ gens.add(base)
+
+ terms.append((coeff, elements))
+
+ reprs.append(terms)
+
+ gens = _sort_gens(gens, opt=opt)
+ k, indices = len(gens), {}
+
+ for i, g in enumerate(gens):
+ indices[g] = i
+
+ polys = []
+
+ for terms in reprs:
+ poly = {}
+
+ for coeff, term in terms:
+ monom = [0]*k
+
+ for base, exp in term.items():
+ monom[indices[base]] = exp
+
+ monom = tuple(monom)
+
+ if monom in poly:
+ poly[monom] += Mul(*coeff)
+ else:
+ poly[monom] = Mul(*coeff)
+
+ polys.append(poly)
+
+ return polys, tuple(gens)
+
+
+def _dict_from_expr_if_gens(expr, opt):
+ """Transform an expression into a multinomial form given generators. """
+ (poly,), gens = _parallel_dict_from_expr_if_gens((expr,), opt)
+ return poly, gens
+
+
+def _dict_from_expr_no_gens(expr, opt):
+ """Transform an expression into a multinomial form and figure out generators. """
+ (poly,), gens = _parallel_dict_from_expr_no_gens((expr,), opt)
+ return poly, gens
+
+
+def parallel_dict_from_expr(exprs, **args):
+ """Transform expressions into a multinomial form. """
+ reps, opt = _parallel_dict_from_expr(exprs, build_options(args))
+ return reps, opt.gens
+
+
+def _parallel_dict_from_expr(exprs, opt):
+ """Transform expressions into a multinomial form. """
+ if opt.expand is not False:
+ exprs = [ expr.expand() for expr in exprs ]
+
+ if any(expr.is_commutative is False for expr in exprs):
+ raise PolynomialError('non-commutative expressions are not supported')
+
+ if opt.gens:
+ reps, gens = _parallel_dict_from_expr_if_gens(exprs, opt)
+ else:
+ reps, gens = _parallel_dict_from_expr_no_gens(exprs, opt)
+
+ return reps, opt.clone({'gens': gens})
+
+
+def dict_from_expr(expr, **args):
+ """Transform an expression into a multinomial form. """
+ rep, opt = _dict_from_expr(expr, build_options(args))
+ return rep, opt.gens
+
+
+def _dict_from_expr(expr, opt):
+ """Transform an expression into a multinomial form. """
+ if expr.is_commutative is False:
+ raise PolynomialError('non-commutative expressions are not supported')
+
+ def _is_expandable_pow(expr):
+ return (expr.is_Pow and expr.exp.is_positive and expr.exp.is_Integer
+ and expr.base.is_Add)
+
+ if opt.expand is not False:
+ if not isinstance(expr, (Expr, Eq)):
+ raise PolynomialError('expression must be of type Expr')
+ expr = expr.expand()
+ # TODO: Integrate this into expand() itself
+ while any(_is_expandable_pow(i) or i.is_Mul and
+ any(_is_expandable_pow(j) for j in i.args) for i in
+ Add.make_args(expr)):
+
+ expr = expand_multinomial(expr)
+ while any(i.is_Mul and any(j.is_Add for j in i.args) for i in Add.make_args(expr)):
+ expr = expand_mul(expr)
+
+ if opt.gens:
+ rep, gens = _dict_from_expr_if_gens(expr, opt)
+ else:
+ rep, gens = _dict_from_expr_no_gens(expr, opt)
+
+ return rep, opt.clone({'gens': gens})
+
+
+def expr_from_dict(rep, *gens):
+ """Convert a multinomial form into an expression. """
+ result = []
+
+ for monom, coeff in rep.items():
+ term = [coeff]
+ for g, m in zip(gens, monom):
+ if m:
+ term.append(Pow(g, m))
+
+ result.append(Mul(*term))
+
+ return Add(*result)
+
+parallel_dict_from_basic = parallel_dict_from_expr
+dict_from_basic = dict_from_expr
+basic_from_dict = expr_from_dict
+
+
+def _dict_reorder(rep, gens, new_gens):
+ """Reorder levels using dict representation. """
+ gens = list(gens)
+
+ monoms = rep.keys()
+ coeffs = rep.values()
+
+ new_monoms = [ [] for _ in range(len(rep)) ]
+ used_indices = set()
+
+ for gen in new_gens:
+ try:
+ j = gens.index(gen)
+ used_indices.add(j)
+
+ for M, new_M in zip(monoms, new_monoms):
+ new_M.append(M[j])
+ except ValueError:
+ for new_M in new_monoms:
+ new_M.append(0)
+
+ for i, _ in enumerate(gens):
+ if i not in used_indices:
+ for monom in monoms:
+ if monom[i]:
+ raise GeneratorsError("unable to drop generators")
+
+ return map(tuple, new_monoms), coeffs
+
+
+class PicklableWithSlots:
+ """
+ Mixin class that allows to pickle objects with ``__slots__``.
+
+ Examples
+ ========
+
+ First define a class that mixes :class:`PicklableWithSlots` in::
+
+ >>> from sympy.polys.polyutils import PicklableWithSlots
+ >>> class Some(PicklableWithSlots):
+ ... __slots__ = ('foo', 'bar')
+ ...
+ ... def __init__(self, foo, bar):
+ ... self.foo = foo
+ ... self.bar = bar
+
+ To make :mod:`pickle` happy in doctest we have to use these hacks::
+
+ >>> import builtins
+ >>> builtins.Some = Some
+ >>> from sympy.polys import polyutils
+ >>> polyutils.Some = Some
+
+ Next lets see if we can create an instance, pickle it and unpickle::
+
+ >>> some = Some('abc', 10)
+ >>> some.foo, some.bar
+ ('abc', 10)
+
+ >>> from pickle import dumps, loads
+ >>> some2 = loads(dumps(some))
+
+ >>> some2.foo, some2.bar
+ ('abc', 10)
+
+ """
+
+ __slots__ = ()
+
+ def __getstate__(self, cls=None):
+ if cls is None:
+ # This is the case for the instance that gets pickled
+ cls = self.__class__
+
+ d = {}
+
+ # Get all data that should be stored from super classes
+ for c in cls.__bases__:
+ # XXX: Python 3.11 defines object.__getstate__ and it does not
+ # accept any arguments so we need to make sure not to call it with
+ # an argument here. To be compatible with Python < 3.11 we need to
+ # be careful not to assume that c or object has a __getstate__
+ # method though.
+ getstate = getattr(c, "__getstate__", None)
+ objstate = getattr(object, "__getstate__", None)
+ if getstate is not None and getstate is not objstate:
+ d.update(getstate(self, c))
+
+ # Get all information that should be stored from cls and return the dict
+ for name in cls.__slots__:
+ if hasattr(self, name):
+ d[name] = getattr(self, name)
+
+ return d
+
+ def __setstate__(self, d):
+ # All values that were pickled are now assigned to a fresh instance
+ for name, value in d.items():
+ setattr(self, name, value)
+
+
+class IntegerPowerable:
+ r"""
+ Mixin class for classes that define a `__mul__` method, and want to be
+ raised to integer powers in the natural way that follows. Implements
+ powering via binary expansion, for efficiency.
+
+ By default, only integer powers $\geq 2$ are supported. To support the
+ first, zeroth, or negative powers, override the corresponding methods,
+ `_first_power`, `_zeroth_power`, `_negative_power`, below.
+ """
+
+ def __pow__(self, e, modulo=None):
+ if e < 2:
+ try:
+ if e == 1:
+ return self._first_power()
+ elif e == 0:
+ return self._zeroth_power()
+ else:
+ return self._negative_power(e, modulo=modulo)
+ except NotImplementedError:
+ return NotImplemented
+ else:
+ bits = [int(d) for d in reversed(bin(e)[2:])]
+ n = len(bits)
+ p = self
+ first = True
+ for i in range(n):
+ if bits[i]:
+ if first:
+ r = p
+ first = False
+ else:
+ r *= p
+ if modulo is not None:
+ r %= modulo
+ if i < n - 1:
+ p *= p
+ if modulo is not None:
+ p %= modulo
+ return r
+
+ def _negative_power(self, e, modulo=None):
+ """
+ Compute inverse of self, then raise that to the abs(e) power.
+ For example, if the class has an `inv()` method,
+ return self.inv() ** abs(e) % modulo
+ """
+ raise NotImplementedError
+
+ def _zeroth_power(self):
+ """Return unity element of algebraic struct to which self belongs."""
+ raise NotImplementedError
+
+ def _first_power(self):
+ """Return a copy of self."""
+ raise NotImplementedError
+
+
+_GF_types: tuple[type, ...]
+
+
+if GROUND_TYPES == 'flint':
+ import flint
+ _GF_types = (flint.nmod, flint.fmpz_mod)
+else:
+ from sympy.polys.domains.modularinteger import ModularInteger
+ flint = None
+ _GF_types = (ModularInteger,)
diff --git a/lib/python3.12/site-packages/sympy/polys/rationaltools.py b/lib/python3.12/site-packages/sympy/polys/rationaltools.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ca513ff2d4af96baaaf1c82caf501750b1524da
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/rationaltools.py
@@ -0,0 +1,85 @@
+"""Tools for manipulation of rational expressions. """
+
+
+from sympy.core import Basic, Add, sympify
+from sympy.core.exprtools import gcd_terms
+from sympy.utilities import public
+from sympy.utilities.iterables import iterable
+
+
+@public
+def together(expr, deep=False, fraction=True):
+ """
+ Denest and combine rational expressions using symbolic methods.
+
+ This function takes an expression or a container of expressions
+ and puts it (them) together by denesting and combining rational
+ subexpressions. No heroic measures are taken to minimize degree
+ of the resulting numerator and denominator. To obtain completely
+ reduced expression use :func:`~.cancel`. However, :func:`~.together`
+ can preserve as much as possible of the structure of the input
+ expression in the output (no expansion is performed).
+
+ A wide variety of objects can be put together including lists,
+ tuples, sets, relational objects, integrals and others. It is
+ also possible to transform interior of function applications,
+ by setting ``deep`` flag to ``True``.
+
+ By definition, :func:`~.together` is a complement to :func:`~.apart`,
+ so ``apart(together(expr))`` should return expr unchanged. Note
+ however, that :func:`~.together` uses only symbolic methods, so
+ it might be necessary to use :func:`~.cancel` to perform algebraic
+ simplification and minimize degree of the numerator and denominator.
+
+ Examples
+ ========
+
+ >>> from sympy import together, exp
+ >>> from sympy.abc import x, y, z
+
+ >>> together(1/x + 1/y)
+ (x + y)/(x*y)
+ >>> together(1/x + 1/y + 1/z)
+ (x*y + x*z + y*z)/(x*y*z)
+
+ >>> together(1/(x*y) + 1/y**2)
+ (x + y)/(x*y**2)
+
+ >>> together(1/(1 + 1/x) + 1/(1 + 1/y))
+ (x*(y + 1) + y*(x + 1))/((x + 1)*(y + 1))
+
+ >>> together(exp(1/x + 1/y))
+ exp(1/y + 1/x)
+ >>> together(exp(1/x + 1/y), deep=True)
+ exp((x + y)/(x*y))
+
+ >>> together(1/exp(x) + 1/(x*exp(x)))
+ (x + 1)*exp(-x)/x
+
+ >>> together(1/exp(2*x) + 1/(x*exp(3*x)))
+ (x*exp(x) + 1)*exp(-3*x)/x
+
+ """
+ def _together(expr):
+ if isinstance(expr, Basic):
+ if expr.is_Atom or (expr.is_Function and not deep):
+ return expr
+ elif expr.is_Add:
+ return gcd_terms(list(map(_together, Add.make_args(expr))), fraction=fraction)
+ elif expr.is_Pow:
+ base = _together(expr.base)
+
+ if deep:
+ exp = _together(expr.exp)
+ else:
+ exp = expr.exp
+
+ return expr.func(base, exp)
+ else:
+ return expr.func(*[ _together(arg) for arg in expr.args ])
+ elif iterable(expr):
+ return expr.__class__([ _together(ex) for ex in expr ])
+
+ return expr
+
+ return _together(sympify(expr))
diff --git a/lib/python3.12/site-packages/sympy/polys/ring_series.py b/lib/python3.12/site-packages/sympy/polys/ring_series.py
new file mode 100644
index 0000000000000000000000000000000000000000..25ae6c732f44edf1617286867de7f4c0829f1b9b
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/ring_series.py
@@ -0,0 +1,2026 @@
+"""Power series evaluation and manipulation using sparse Polynomials
+
+Implementing a new function
+---------------------------
+
+There are a few things to be kept in mind when adding a new function here::
+
+ - The implementation should work on all possible input domains/rings.
+ Special cases include the ``EX`` ring and a constant term in the series
+ to be expanded. There can be two types of constant terms in the series:
+
+ + A constant value or symbol.
+ + A term of a multivariate series not involving the generator, with
+ respect to which the series is to expanded.
+
+ Strictly speaking, a generator of a ring should not be considered a
+ constant. However, for series expansion both the cases need similar
+ treatment (as the user does not care about inner details), i.e, use an
+ addition formula to separate the constant part and the variable part (see
+ rs_sin for reference).
+
+ - All the algorithms used here are primarily designed to work for Taylor
+ series (number of iterations in the algo equals the required order).
+ Hence, it becomes tricky to get the series of the right order if a
+ Puiseux series is input. Use rs_puiseux? in your function if your
+ algorithm is not designed to handle fractional powers.
+
+Extending rs_series
+-------------------
+
+To make a function work with rs_series you need to do two things::
+
+ - Many sure it works with a constant term (as explained above).
+ - If the series contains constant terms, you might need to extend its ring.
+ You do so by adding the new terms to the rings as generators.
+ ``PolyRing.compose`` and ``PolyRing.add_gens`` are two functions that do
+ so and need to be called every time you expand a series containing a
+ constant term.
+
+Look at rs_sin and rs_series for further reference.
+
+"""
+
+from sympy.polys.domains import QQ, EX
+from sympy.polys.rings import PolyElement, ring, sring
+from sympy.polys.polyerrors import DomainError
+from sympy.polys.monomials import (monomial_min, monomial_mul, monomial_div,
+ monomial_ldiv)
+from mpmath.libmp.libintmath import ifac
+from sympy.core import PoleError, Function, Expr
+from sympy.core.numbers import Rational
+from sympy.core.intfunc import igcd
+from sympy.functions import sin, cos, tan, atan, exp, atanh, tanh, log, ceiling
+from sympy.utilities.misc import as_int
+from mpmath.libmp.libintmath import giant_steps
+import math
+
+
+def _invert_monoms(p1):
+ """
+ Compute ``x**n * p1(1/x)`` for a univariate polynomial ``p1`` in ``x``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import _invert_monoms
+ >>> R, x = ring('x', ZZ)
+ >>> p = x**2 + 2*x + 3
+ >>> _invert_monoms(p)
+ 3*x**2 + 2*x + 1
+
+ See Also
+ ========
+
+ sympy.polys.densebasic.dup_reverse
+ """
+ terms = list(p1.items())
+ terms.sort()
+ deg = p1.degree()
+ R = p1.ring
+ p = R.zero
+ cv = p1.listcoeffs()
+ mv = p1.listmonoms()
+ for mvi, cvi in zip(mv, cv):
+ p[(deg - mvi[0],)] = cvi
+ return p
+
+def _giant_steps(target):
+ """Return a list of precision steps for the Newton's method"""
+ res = giant_steps(2, target)
+ if res[0] != 2:
+ res = [2] + res
+ return res
+
+def rs_trunc(p1, x, prec):
+ """
+ Truncate the series in the ``x`` variable with precision ``prec``,
+ that is, modulo ``O(x**prec)``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_trunc
+ >>> R, x = ring('x', QQ)
+ >>> p = x**10 + x**5 + x + 1
+ >>> rs_trunc(p, x, 12)
+ x**10 + x**5 + x + 1
+ >>> rs_trunc(p, x, 10)
+ x**5 + x + 1
+ """
+ R = p1.ring
+ p = R.zero
+ i = R.gens.index(x)
+ for exp1 in p1:
+ if exp1[i] >= prec:
+ continue
+ p[exp1] = p1[exp1]
+ return p
+
+def rs_is_puiseux(p, x):
+ """
+ Test if ``p`` is Puiseux series in ``x``.
+
+ Raise an exception if it has a negative power in ``x``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_is_puiseux
+ >>> R, x = ring('x', QQ)
+ >>> p = x**QQ(2,5) + x**QQ(2,3) + x
+ >>> rs_is_puiseux(p, x)
+ True
+ """
+ index = p.ring.gens.index(x)
+ for k in p:
+ if k[index] != int(k[index]):
+ return True
+ if k[index] < 0:
+ raise ValueError('The series is not regular in %s' % x)
+ return False
+
+def rs_puiseux(f, p, x, prec):
+ """
+ Return the puiseux series for `f(p, x, prec)`.
+
+ To be used when function ``f`` is implemented only for regular series.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_puiseux, rs_exp
+ >>> R, x = ring('x', QQ)
+ >>> p = x**QQ(2,5) + x**QQ(2,3) + x
+ >>> rs_puiseux(rs_exp,p, x, 1)
+ 1/2*x**(4/5) + x**(2/3) + x**(2/5) + 1
+ """
+ index = p.ring.gens.index(x)
+ n = 1
+ for k in p:
+ power = k[index]
+ if isinstance(power, Rational):
+ num, den = power.as_numer_denom()
+ n = int(n*den // igcd(n, den))
+ elif power != int(power):
+ den = power.denominator
+ n = int(n*den // igcd(n, den))
+ if n != 1:
+ p1 = pow_xin(p, index, n)
+ r = f(p1, x, prec*n)
+ n1 = QQ(1, n)
+ if isinstance(r, tuple):
+ r = tuple([pow_xin(rx, index, n1) for rx in r])
+ else:
+ r = pow_xin(r, index, n1)
+ else:
+ r = f(p, x, prec)
+ return r
+
+def rs_puiseux2(f, p, q, x, prec):
+ """
+ Return the puiseux series for `f(p, q, x, prec)`.
+
+ To be used when function ``f`` is implemented only for regular series.
+ """
+ index = p.ring.gens.index(x)
+ n = 1
+ for k in p:
+ power = k[index]
+ if isinstance(power, Rational):
+ num, den = power.as_numer_denom()
+ n = n*den // igcd(n, den)
+ elif power != int(power):
+ den = power.denominator
+ n = n*den // igcd(n, den)
+ if n != 1:
+ p1 = pow_xin(p, index, n)
+ r = f(p1, q, x, prec*n)
+ n1 = QQ(1, n)
+ r = pow_xin(r, index, n1)
+ else:
+ r = f(p, q, x, prec)
+ return r
+
+def rs_mul(p1, p2, x, prec):
+ """
+ Return the product of the given two series, modulo ``O(x**prec)``.
+
+ ``x`` is the series variable or its position in the generators.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_mul
+ >>> R, x = ring('x', QQ)
+ >>> p1 = x**2 + 2*x + 1
+ >>> p2 = x + 1
+ >>> rs_mul(p1, p2, x, 3)
+ 3*x**2 + 3*x + 1
+ """
+ R = p1.ring
+ p = R.zero
+ if R.__class__ != p2.ring.__class__ or R != p2.ring:
+ raise ValueError('p1 and p2 must have the same ring')
+ iv = R.gens.index(x)
+ if not isinstance(p2, PolyElement):
+ raise ValueError('p2 must be a polynomial')
+ if R == p2.ring:
+ get = p.get
+ items2 = list(p2.items())
+ items2.sort(key=lambda e: e[0][iv])
+ if R.ngens == 1:
+ for exp1, v1 in p1.items():
+ for exp2, v2 in items2:
+ exp = exp1[0] + exp2[0]
+ if exp < prec:
+ exp = (exp, )
+ p[exp] = get(exp, 0) + v1*v2
+ else:
+ break
+ else:
+ monomial_mul = R.monomial_mul
+ for exp1, v1 in p1.items():
+ for exp2, v2 in items2:
+ if exp1[iv] + exp2[iv] < prec:
+ exp = monomial_mul(exp1, exp2)
+ p[exp] = get(exp, 0) + v1*v2
+ else:
+ break
+
+ p.strip_zero()
+ return p
+
+def rs_square(p1, x, prec):
+ """
+ Square the series modulo ``O(x**prec)``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_square
+ >>> R, x = ring('x', QQ)
+ >>> p = x**2 + 2*x + 1
+ >>> rs_square(p, x, 3)
+ 6*x**2 + 4*x + 1
+ """
+ R = p1.ring
+ p = R.zero
+ iv = R.gens.index(x)
+ get = p.get
+ items = list(p1.items())
+ items.sort(key=lambda e: e[0][iv])
+ monomial_mul = R.monomial_mul
+ for i in range(len(items)):
+ exp1, v1 = items[i]
+ for j in range(i):
+ exp2, v2 = items[j]
+ if exp1[iv] + exp2[iv] < prec:
+ exp = monomial_mul(exp1, exp2)
+ p[exp] = get(exp, 0) + v1*v2
+ else:
+ break
+ p = p.imul_num(2)
+ get = p.get
+ for expv, v in p1.items():
+ if 2*expv[iv] < prec:
+ e2 = monomial_mul(expv, expv)
+ p[e2] = get(e2, 0) + v**2
+ p.strip_zero()
+ return p
+
+def rs_pow(p1, n, x, prec):
+ """
+ Return ``p1**n`` modulo ``O(x**prec)``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_pow
+ >>> R, x = ring('x', QQ)
+ >>> p = x + 1
+ >>> rs_pow(p, 4, x, 3)
+ 6*x**2 + 4*x + 1
+ """
+ R = p1.ring
+ if isinstance(n, Rational):
+ np = int(n.p)
+ nq = int(n.q)
+ if nq != 1:
+ res = rs_nth_root(p1, nq, x, prec)
+ if np != 1:
+ res = rs_pow(res, np, x, prec)
+ else:
+ res = rs_pow(p1, np, x, prec)
+ return res
+
+ n = as_int(n)
+ if n == 0:
+ if p1:
+ return R(1)
+ else:
+ raise ValueError('0**0 is undefined')
+ if n < 0:
+ p1 = rs_pow(p1, -n, x, prec)
+ return rs_series_inversion(p1, x, prec)
+ if n == 1:
+ return rs_trunc(p1, x, prec)
+ if n == 2:
+ return rs_square(p1, x, prec)
+ if n == 3:
+ p2 = rs_square(p1, x, prec)
+ return rs_mul(p1, p2, x, prec)
+ p = R(1)
+ while 1:
+ if n & 1:
+ p = rs_mul(p1, p, x, prec)
+ n -= 1
+ if not n:
+ break
+ p1 = rs_square(p1, x, prec)
+ n = n // 2
+ return p
+
+def rs_subs(p, rules, x, prec):
+ """
+ Substitution with truncation according to the mapping in ``rules``.
+
+ Return a series with precision ``prec`` in the generator ``x``
+
+ Note that substitutions are not done one after the other
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_subs
+ >>> R, x, y = ring('x, y', QQ)
+ >>> p = x**2 + y**2
+ >>> rs_subs(p, {x: x+ y, y: x+ 2*y}, x, 3)
+ 2*x**2 + 6*x*y + 5*y**2
+ >>> (x + y)**2 + (x + 2*y)**2
+ 2*x**2 + 6*x*y + 5*y**2
+
+ which differs from
+
+ >>> rs_subs(rs_subs(p, {x: x+ y}, x, 3), {y: x+ 2*y}, x, 3)
+ 5*x**2 + 12*x*y + 8*y**2
+
+ Parameters
+ ----------
+ p : :class:`~.PolyElement` Input series.
+ rules : ``dict`` with substitution mappings.
+ x : :class:`~.PolyElement` in which the series truncation is to be done.
+ prec : :class:`~.Integer` order of the series after truncation.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_subs
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_subs(x**2+y**2, {y: (x+y)**2}, x, 3)
+ 6*x**2*y**2 + x**2 + 4*x*y**3 + y**4
+ """
+ R = p.ring
+ ngens = R.ngens
+ d = R(0)
+ for i in range(ngens):
+ d[(i, 1)] = R.gens[i]
+ for var in rules:
+ d[(R.index(var), 1)] = rules[var]
+ p1 = R(0)
+ p_keys = sorted(p.keys())
+ for expv in p_keys:
+ p2 = R(1)
+ for i in range(ngens):
+ power = expv[i]
+ if power == 0:
+ continue
+ if (i, power) not in d:
+ q, r = divmod(power, 2)
+ if r == 0 and (i, q) in d:
+ d[(i, power)] = rs_square(d[(i, q)], x, prec)
+ elif (i, power - 1) in d:
+ d[(i, power)] = rs_mul(d[(i, power - 1)], d[(i, 1)],
+ x, prec)
+ else:
+ d[(i, power)] = rs_pow(d[(i, 1)], power, x, prec)
+ p2 = rs_mul(p2, d[(i, power)], x, prec)
+ p1 += p2*p[expv]
+ return p1
+
+def _has_constant_term(p, x):
+ """
+ Check if ``p`` has a constant term in ``x``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import _has_constant_term
+ >>> R, x = ring('x', QQ)
+ >>> p = x**2 + x + 1
+ >>> _has_constant_term(p, x)
+ True
+ """
+ R = p.ring
+ iv = R.gens.index(x)
+ zm = R.zero_monom
+ a = [0]*R.ngens
+ a[iv] = 1
+ miv = tuple(a)
+ for expv in p:
+ if monomial_min(expv, miv) == zm:
+ return True
+ return False
+
+def _get_constant_term(p, x):
+ """Return constant term in p with respect to x
+
+ Note that it is not simply `p[R.zero_monom]` as there might be multiple
+ generators in the ring R. We want the `x`-free term which can contain other
+ generators.
+ """
+ R = p.ring
+ i = R.gens.index(x)
+ zm = R.zero_monom
+ a = [0]*R.ngens
+ a[i] = 1
+ miv = tuple(a)
+ c = 0
+ for expv in p:
+ if monomial_min(expv, miv) == zm:
+ c += R({expv: p[expv]})
+ return c
+
+def _check_series_var(p, x, name):
+ index = p.ring.gens.index(x)
+ m = min(p, key=lambda k: k[index])[index]
+ if m < 0:
+ raise PoleError("Asymptotic expansion of %s around [oo] not "
+ "implemented." % name)
+ return index, m
+
+def _series_inversion1(p, x, prec):
+ """
+ Univariate series inversion ``1/p`` modulo ``O(x**prec)``.
+
+ The Newton method is used.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import _series_inversion1
+ >>> R, x = ring('x', QQ)
+ >>> p = x + 1
+ >>> _series_inversion1(p, x, 4)
+ -x**3 + x**2 - x + 1
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(_series_inversion1, p, x, prec)
+ R = p.ring
+ zm = R.zero_monom
+ c = p[zm]
+
+ # giant_steps does not seem to work with PythonRational numbers with 1 as
+ # denominator. This makes sure such a number is converted to integer.
+ if prec == int(prec):
+ prec = int(prec)
+
+ if zm not in p:
+ raise ValueError("No constant term in series")
+ if _has_constant_term(p - c, x):
+ raise ValueError("p cannot contain a constant term depending on "
+ "parameters")
+ one = R(1)
+ if R.domain is EX:
+ one = 1
+ if c != one:
+ # TODO add check that it is a unit
+ p1 = R(1)/c
+ else:
+ p1 = R(1)
+ for precx in _giant_steps(prec):
+ t = 1 - rs_mul(p1, p, x, precx)
+ p1 = p1 + rs_mul(p1, t, x, precx)
+ return p1
+
+def rs_series_inversion(p, x, prec):
+ """
+ Multivariate series inversion ``1/p`` modulo ``O(x**prec)``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_series_inversion
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_series_inversion(1 + x*y**2, x, 4)
+ -x**3*y**6 + x**2*y**4 - x*y**2 + 1
+ >>> rs_series_inversion(1 + x*y**2, y, 4)
+ -x*y**2 + 1
+ >>> rs_series_inversion(x + x**2, x, 4)
+ x**3 - x**2 + x - 1 + x**(-1)
+ """
+ R = p.ring
+ if p == R.zero:
+ raise ZeroDivisionError
+ zm = R.zero_monom
+ index = R.gens.index(x)
+ m = min(p, key=lambda k: k[index])[index]
+ if m:
+ p = mul_xin(p, index, -m)
+ prec = prec + m
+ if zm not in p:
+ raise NotImplementedError("No constant term in series")
+
+ if _has_constant_term(p - p[zm], x):
+ raise NotImplementedError("p - p[0] must not have a constant term in "
+ "the series variables")
+ r = _series_inversion1(p, x, prec)
+ if m != 0:
+ r = mul_xin(r, index, -m)
+ return r
+
+def _coefficient_t(p, t):
+ r"""Coefficient of `x_i**j` in p, where ``t`` = (i, j)"""
+ i, j = t
+ R = p.ring
+ expv1 = [0]*R.ngens
+ expv1[i] = j
+ expv1 = tuple(expv1)
+ p1 = R(0)
+ for expv in p:
+ if expv[i] == j:
+ p1[monomial_div(expv, expv1)] = p[expv]
+ return p1
+
+def rs_series_reversion(p, x, n, y):
+ r"""
+ Reversion of a series.
+
+ ``p`` is a series with ``O(x**n)`` of the form $p = ax + f(x)$
+ where $a$ is a number different from 0.
+
+ $f(x) = \sum_{k=2}^{n-1} a_kx_k$
+
+ Parameters
+ ==========
+
+ a_k : Can depend polynomially on other variables, not indicated.
+ x : Variable with name x.
+ y : Variable with name y.
+
+ Returns
+ =======
+
+ Solve $p = y$, that is, given $ax + f(x) - y = 0$,
+ find the solution $x = r(y)$ up to $O(y^n)$.
+
+ Algorithm
+ =========
+
+ If $r_i$ is the solution at order $i$, then:
+ $ar_i + f(r_i) - y = O\left(y^{i + 1}\right)$
+
+ and if $r_{i + 1}$ is the solution at order $i + 1$, then:
+ $ar_{i + 1} + f(r_{i + 1}) - y = O\left(y^{i + 2}\right)$
+
+ We have, $r_{i + 1} = r_i + e$, such that,
+ $ae + f(r_i) = O\left(y^{i + 2}\right)$
+ or $e = -f(r_i)/a$
+
+ So we use the recursion relation:
+ $r_{i + 1} = r_i - f(r_i)/a$
+ with the boundary condition: $r_1 = y$
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_series_reversion, rs_trunc
+ >>> R, x, y, a, b = ring('x, y, a, b', QQ)
+ >>> p = x - x**2 - 2*b*x**2 + 2*a*b*x**2
+ >>> p1 = rs_series_reversion(p, x, 3, y); p1
+ -2*y**2*a*b + 2*y**2*b + y**2 + y
+ >>> rs_trunc(p.compose(x, p1), y, 3)
+ y
+ """
+ if rs_is_puiseux(p, x):
+ raise NotImplementedError
+ R = p.ring
+ nx = R.gens.index(x)
+ y = R(y)
+ ny = R.gens.index(y)
+ if _has_constant_term(p, x):
+ raise ValueError("p must not contain a constant term in the series "
+ "variable")
+ a = _coefficient_t(p, (nx, 1))
+ zm = R.zero_monom
+ assert zm in a and len(a) == 1
+ a = a[zm]
+ r = y/a
+ for i in range(2, n):
+ sp = rs_subs(p, {x: r}, y, i + 1)
+ sp = _coefficient_t(sp, (ny, i))*y**i
+ r -= sp/a
+ return r
+
+def rs_series_from_list(p, c, x, prec, concur=1):
+ """
+ Return a series `sum c[n]*p**n` modulo `O(x**prec)`.
+
+ It reduces the number of multiplications by summing concurrently.
+
+ `ax = [1, p, p**2, .., p**(J - 1)]`
+ `s = sum(c[i]*ax[i]` for i in `range(r, (r + 1)*J))*p**((K - 1)*J)`
+ with `K >= (n + 1)/J`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_series_from_list, rs_trunc
+ >>> R, x = ring('x', QQ)
+ >>> p = x**2 + x + 1
+ >>> c = [1, 2, 3]
+ >>> rs_series_from_list(p, c, x, 4)
+ 6*x**3 + 11*x**2 + 8*x + 6
+ >>> rs_trunc(1 + 2*p + 3*p**2, x, 4)
+ 6*x**3 + 11*x**2 + 8*x + 6
+ >>> pc = R.from_list(list(reversed(c)))
+ >>> rs_trunc(pc.compose(x, p), x, 4)
+ 6*x**3 + 11*x**2 + 8*x + 6
+
+ """
+
+ # TODO: Add this when it is documented in Sphinx
+ """
+ See Also
+ ========
+
+ sympy.polys.rings.PolyRing.compose
+
+ """
+ R = p.ring
+ n = len(c)
+ if not concur:
+ q = R(1)
+ s = c[0]*q
+ for i in range(1, n):
+ q = rs_mul(q, p, x, prec)
+ s += c[i]*q
+ return s
+ J = int(math.sqrt(n) + 1)
+ K, r = divmod(n, J)
+ if r:
+ K += 1
+ ax = [R(1)]
+ q = R(1)
+ if len(p) < 20:
+ for i in range(1, J):
+ q = rs_mul(q, p, x, prec)
+ ax.append(q)
+ else:
+ for i in range(1, J):
+ if i % 2 == 0:
+ q = rs_square(ax[i//2], x, prec)
+ else:
+ q = rs_mul(q, p, x, prec)
+ ax.append(q)
+ # optimize using rs_square
+ pj = rs_mul(ax[-1], p, x, prec)
+ b = R(1)
+ s = R(0)
+ for k in range(K - 1):
+ r = J*k
+ s1 = c[r]
+ for j in range(1, J):
+ s1 += c[r + j]*ax[j]
+ s1 = rs_mul(s1, b, x, prec)
+ s += s1
+ b = rs_mul(b, pj, x, prec)
+ if not b:
+ break
+ k = K - 1
+ r = J*k
+ if r < n:
+ s1 = c[r]*R(1)
+ for j in range(1, J):
+ if r + j >= n:
+ break
+ s1 += c[r + j]*ax[j]
+ s1 = rs_mul(s1, b, x, prec)
+ s += s1
+ return s
+
+def rs_diff(p, x):
+ """
+ Return partial derivative of ``p`` with respect to ``x``.
+
+ Parameters
+ ==========
+
+ x : :class:`~.PolyElement` with respect to which ``p`` is differentiated.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_diff
+ >>> R, x, y = ring('x, y', QQ)
+ >>> p = x + x**2*y**3
+ >>> rs_diff(p, x)
+ 2*x*y**3 + 1
+ """
+ R = p.ring
+ n = R.gens.index(x)
+ p1 = R.zero
+ mn = [0]*R.ngens
+ mn[n] = 1
+ mn = tuple(mn)
+ for expv in p:
+ if expv[n]:
+ e = monomial_ldiv(expv, mn)
+ p1[e] = R.domain_new(p[expv]*expv[n])
+ return p1
+
+def rs_integrate(p, x):
+ """
+ Integrate ``p`` with respect to ``x``.
+
+ Parameters
+ ==========
+
+ x : :class:`~.PolyElement` with respect to which ``p`` is integrated.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_integrate
+ >>> R, x, y = ring('x, y', QQ)
+ >>> p = x + x**2*y**3
+ >>> rs_integrate(p, x)
+ 1/3*x**3*y**3 + 1/2*x**2
+ """
+ R = p.ring
+ p1 = R.zero
+ n = R.gens.index(x)
+ mn = [0]*R.ngens
+ mn[n] = 1
+ mn = tuple(mn)
+
+ for expv in p:
+ e = monomial_mul(expv, mn)
+ p1[e] = R.domain_new(p[expv]/(expv[n] + 1))
+ return p1
+
+def rs_fun(p, f, *args):
+ r"""
+ Function of a multivariate series computed by substitution.
+
+ The case with f method name is used to compute `rs\_tan` and `rs\_nth\_root`
+ of a multivariate series:
+
+ `rs\_fun(p, tan, iv, prec)`
+
+ tan series is first computed for a dummy variable _x,
+ i.e, `rs\_tan(\_x, iv, prec)`. Then we substitute _x with p to get the
+ desired series
+
+ Parameters
+ ==========
+
+ p : :class:`~.PolyElement` The multivariate series to be expanded.
+ f : `ring\_series` function to be applied on `p`.
+ args[-2] : :class:`~.PolyElement` with respect to which, the series is to be expanded.
+ args[-1] : Required order of the expanded series.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_fun, _tan1
+ >>> R, x, y = ring('x, y', QQ)
+ >>> p = x + x*y + x**2*y + x**3*y**2
+ >>> rs_fun(p, _tan1, x, 4)
+ 1/3*x**3*y**3 + 2*x**3*y**2 + x**3*y + 1/3*x**3 + x**2*y + x*y + x
+ """
+ _R = p.ring
+ R1, _x = ring('_x', _R.domain)
+ h = int(args[-1])
+ args1 = args[:-2] + (_x, h)
+ zm = _R.zero_monom
+ # separate the constant term of the series
+ # compute the univariate series f(_x, .., 'x', sum(nv))
+ if zm in p:
+ x1 = _x + p[zm]
+ p1 = p - p[zm]
+ else:
+ x1 = _x
+ p1 = p
+ if isinstance(f, str):
+ q = getattr(x1, f)(*args1)
+ else:
+ q = f(x1, *args1)
+ a = sorted(q.items())
+ c = [0]*h
+ for x in a:
+ c[x[0][0]] = x[1]
+ p1 = rs_series_from_list(p1, c, args[-2], args[-1])
+ return p1
+
+def mul_xin(p, i, n):
+ r"""
+ Return `p*x_i**n`.
+
+ `x\_i` is the ith variable in ``p``.
+ """
+ R = p.ring
+ q = R(0)
+ for k, v in p.items():
+ k1 = list(k)
+ k1[i] += n
+ q[tuple(k1)] = v
+ return q
+
+def pow_xin(p, i, n):
+ """
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import pow_xin
+ >>> R, x, y = ring('x, y', QQ)
+ >>> p = x**QQ(2,5) + x + x**QQ(2,3)
+ >>> index = p.ring.gens.index(x)
+ >>> pow_xin(p, index, 15)
+ x**15 + x**10 + x**6
+ """
+ R = p.ring
+ q = R(0)
+ for k, v in p.items():
+ k1 = list(k)
+ k1[i] *= n
+ q[tuple(k1)] = v
+ return q
+
+def _nth_root1(p, n, x, prec):
+ """
+ Univariate series expansion of the nth root of ``p``.
+
+ The Newton method is used.
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux2(_nth_root1, p, n, x, prec)
+ R = p.ring
+ zm = R.zero_monom
+ if zm not in p:
+ raise NotImplementedError('No constant term in series')
+ n = as_int(n)
+ assert p[zm] == 1
+ p1 = R(1)
+ if p == 1:
+ return p
+ if n == 0:
+ return R(1)
+ if n == 1:
+ return p
+ if n < 0:
+ n = -n
+ sign = 1
+ else:
+ sign = 0
+ for precx in _giant_steps(prec):
+ tmp = rs_pow(p1, n + 1, x, precx)
+ tmp = rs_mul(tmp, p, x, precx)
+ p1 += p1/n - tmp/n
+ if sign:
+ return p1
+ else:
+ return _series_inversion1(p1, x, prec)
+
+def rs_nth_root(p, n, x, prec):
+ """
+ Multivariate series expansion of the nth root of ``p``.
+
+ Parameters
+ ==========
+
+ p : Expr
+ The polynomial to computer the root of.
+ n : integer
+ The order of the root to be computed.
+ x : :class:`~.PolyElement`
+ prec : integer
+ Order of the expanded series.
+
+ Notes
+ =====
+
+ The result of this function is dependent on the ring over which the
+ polynomial has been defined. If the answer involves a root of a constant,
+ make sure that the polynomial is over a real field. It cannot yet handle
+ roots of symbols.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ, RR
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_nth_root
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_nth_root(1 + x + x*y, -3, x, 3)
+ 2/9*x**2*y**2 + 4/9*x**2*y + 2/9*x**2 - 1/3*x*y - 1/3*x + 1
+ >>> R, x, y = ring('x, y', RR)
+ >>> rs_nth_root(3 + x + x*y, 3, x, 2)
+ 0.160249952256379*x*y + 0.160249952256379*x + 1.44224957030741
+ """
+ if n == 0:
+ if p == 0:
+ raise ValueError('0**0 expression')
+ else:
+ return p.ring(1)
+ if n == 1:
+ return rs_trunc(p, x, prec)
+ R = p.ring
+ index = R.gens.index(x)
+ m = min(p, key=lambda k: k[index])[index]
+ p = mul_xin(p, index, -m)
+ prec -= m
+
+ if _has_constant_term(p - 1, x):
+ zm = R.zero_monom
+ c = p[zm]
+ if R.domain is EX:
+ c_expr = c.as_expr()
+ const = c_expr**QQ(1, n)
+ elif isinstance(c, PolyElement):
+ try:
+ c_expr = c.as_expr()
+ const = R(c_expr**(QQ(1, n)))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ else:
+ try: # RealElement doesn't support
+ const = R(c**Rational(1, n)) # exponentiation with mpq object
+ except ValueError: # as exponent
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ res = rs_nth_root(p/c, n, x, prec)*const
+ else:
+ res = _nth_root1(p, n, x, prec)
+ if m:
+ m = QQ(m, n)
+ res = mul_xin(res, index, m)
+ return res
+
+def rs_log(p, x, prec):
+ """
+ The Logarithm of ``p`` modulo ``O(x**prec)``.
+
+ Notes
+ =====
+
+ Truncation of ``integral dx p**-1*d p/dx`` is used.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_log
+ >>> R, x = ring('x', QQ)
+ >>> rs_log(1 + x, x, 8)
+ 1/7*x**7 - 1/6*x**6 + 1/5*x**5 - 1/4*x**4 + 1/3*x**3 - 1/2*x**2 + x
+ >>> rs_log(x**QQ(3, 2) + 1, x, 5)
+ 1/3*x**(9/2) - 1/2*x**3 + x**(3/2)
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_log, p, x, prec)
+ R = p.ring
+ if p == 1:
+ return R.zero
+ c = _get_constant_term(p, x)
+ if c:
+ const = 0
+ if c == 1:
+ pass
+ else:
+ c_expr = c.as_expr()
+ if R.domain is EX:
+ const = log(c_expr)
+ elif isinstance(c, PolyElement):
+ try:
+ const = R(log(c_expr))
+ except ValueError:
+ R = R.add_gens([log(c_expr)])
+ p = p.set_ring(R)
+ x = x.set_ring(R)
+ c = c.set_ring(R)
+ const = R(log(c_expr))
+ else:
+ try:
+ const = R(log(c))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+
+ dlog = p.diff(x)
+ dlog = rs_mul(dlog, _series_inversion1(p, x, prec), x, prec - 1)
+ return rs_integrate(dlog, x) + const
+ else:
+ raise NotImplementedError
+
+def rs_LambertW(p, x, prec):
+ """
+ Calculate the series expansion of the principal branch of the Lambert W
+ function.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_LambertW
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_LambertW(x + x*y, x, 3)
+ -x**2*y**2 - 2*x**2*y - x**2 + x*y + x
+
+ See Also
+ ========
+
+ LambertW
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_LambertW, p, x, prec)
+ R = p.ring
+ p1 = R(0)
+ if _has_constant_term(p, x):
+ raise NotImplementedError("Polynomial must not have constant term in "
+ "the series variables")
+ if x in R.gens:
+ for precx in _giant_steps(prec):
+ e = rs_exp(p1, x, precx)
+ p2 = rs_mul(e, p1, x, precx) - p
+ p3 = rs_mul(e, p1 + 1, x, precx)
+ p3 = rs_series_inversion(p3, x, precx)
+ tmp = rs_mul(p2, p3, x, precx)
+ p1 -= tmp
+ return p1
+ else:
+ raise NotImplementedError
+
+def _exp1(p, x, prec):
+ r"""Helper function for `rs\_exp`. """
+ R = p.ring
+ p1 = R(1)
+ for precx in _giant_steps(prec):
+ pt = p - rs_log(p1, x, precx)
+ tmp = rs_mul(pt, p1, x, precx)
+ p1 += tmp
+ return p1
+
+def rs_exp(p, x, prec):
+ """
+ Exponentiation of a series modulo ``O(x**prec)``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_exp
+ >>> R, x = ring('x', QQ)
+ >>> rs_exp(x**2, x, 7)
+ 1/6*x**6 + 1/2*x**4 + x**2 + 1
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_exp, p, x, prec)
+ R = p.ring
+ c = _get_constant_term(p, x)
+ if c:
+ if R.domain is EX:
+ c_expr = c.as_expr()
+ const = exp(c_expr)
+ elif isinstance(c, PolyElement):
+ try:
+ c_expr = c.as_expr()
+ const = R(exp(c_expr))
+ except ValueError:
+ R = R.add_gens([exp(c_expr)])
+ p = p.set_ring(R)
+ x = x.set_ring(R)
+ c = c.set_ring(R)
+ const = R(exp(c_expr))
+ else:
+ try:
+ const = R(exp(c))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ p1 = p - c
+
+ # Makes use of SymPy functions to evaluate the values of the cos/sin
+ # of the constant term.
+ return const*rs_exp(p1, x, prec)
+
+ if len(p) > 20:
+ return _exp1(p, x, prec)
+ one = R(1)
+ n = 1
+ c = []
+ for k in range(prec):
+ c.append(one/n)
+ k += 1
+ n *= k
+
+ r = rs_series_from_list(p, c, x, prec)
+ return r
+
+def _atan(p, iv, prec):
+ """
+ Expansion using formula.
+
+ Faster on very small and univariate series.
+ """
+ R = p.ring
+ mo = R(-1)
+ c = [-mo]
+ p2 = rs_square(p, iv, prec)
+ for k in range(1, prec):
+ c.append(mo**k/(2*k + 1))
+ s = rs_series_from_list(p2, c, iv, prec)
+ s = rs_mul(s, p, iv, prec)
+ return s
+
+def rs_atan(p, x, prec):
+ """
+ The arctangent of a series
+
+ Return the series expansion of the atan of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_atan
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_atan(x + x*y, x, 4)
+ -1/3*x**3*y**3 - x**3*y**2 - x**3*y - 1/3*x**3 + x*y + x
+
+ See Also
+ ========
+
+ atan
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_atan, p, x, prec)
+ R = p.ring
+ const = 0
+ if _has_constant_term(p, x):
+ zm = R.zero_monom
+ c = p[zm]
+ if R.domain is EX:
+ c_expr = c.as_expr()
+ const = atan(c_expr)
+ elif isinstance(c, PolyElement):
+ try:
+ c_expr = c.as_expr()
+ const = R(atan(c_expr))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ else:
+ try:
+ const = R(atan(c))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+
+ # Instead of using a closed form formula, we differentiate atan(p) to get
+ # `1/(1+p**2) * dp`, whose series expansion is much easier to calculate.
+ # Finally we integrate to get back atan
+ dp = p.diff(x)
+ p1 = rs_square(p, x, prec) + R(1)
+ p1 = rs_series_inversion(p1, x, prec - 1)
+ p1 = rs_mul(dp, p1, x, prec - 1)
+ return rs_integrate(p1, x) + const
+
+def rs_asin(p, x, prec):
+ """
+ Arcsine of a series
+
+ Return the series expansion of the asin of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_asin
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_asin(x, x, 8)
+ 5/112*x**7 + 3/40*x**5 + 1/6*x**3 + x
+
+ See Also
+ ========
+
+ asin
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_asin, p, x, prec)
+ if _has_constant_term(p, x):
+ raise NotImplementedError("Polynomial must not have constant term in "
+ "series variables")
+ R = p.ring
+ if x in R.gens:
+ # get a good value
+ if len(p) > 20:
+ dp = rs_diff(p, x)
+ p1 = 1 - rs_square(p, x, prec - 1)
+ p1 = rs_nth_root(p1, -2, x, prec - 1)
+ p1 = rs_mul(dp, p1, x, prec - 1)
+ return rs_integrate(p1, x)
+ one = R(1)
+ c = [0, one, 0]
+ for k in range(3, prec, 2):
+ c.append((k - 2)**2*c[-2]/(k*(k - 1)))
+ c.append(0)
+ return rs_series_from_list(p, c, x, prec)
+
+ else:
+ raise NotImplementedError
+
+def _tan1(p, x, prec):
+ r"""
+ Helper function of :func:`rs_tan`.
+
+ Return the series expansion of tan of a univariate series using Newton's
+ method. It takes advantage of the fact that series expansion of atan is
+ easier than that of tan.
+
+ Consider `f(x) = y - \arctan(x)`
+ Let r be a root of f(x) found using Newton's method.
+ Then `f(r) = 0`
+ Or `y = \arctan(x)` where `x = \tan(y)` as required.
+ """
+ R = p.ring
+ p1 = R(0)
+ for precx in _giant_steps(prec):
+ tmp = p - rs_atan(p1, x, precx)
+ tmp = rs_mul(tmp, 1 + rs_square(p1, x, precx), x, precx)
+ p1 += tmp
+ return p1
+
+def rs_tan(p, x, prec):
+ """
+ Tangent of a series.
+
+ Return the series expansion of the tan of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_tan
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_tan(x + x*y, x, 4)
+ 1/3*x**3*y**3 + x**3*y**2 + x**3*y + 1/3*x**3 + x*y + x
+
+ See Also
+ ========
+
+ _tan1, tan
+ """
+ if rs_is_puiseux(p, x):
+ r = rs_puiseux(rs_tan, p, x, prec)
+ return r
+ R = p.ring
+ const = 0
+ c = _get_constant_term(p, x)
+ if c:
+ if R.domain is EX:
+ c_expr = c.as_expr()
+ const = tan(c_expr)
+ elif isinstance(c, PolyElement):
+ try:
+ c_expr = c.as_expr()
+ const = R(tan(c_expr))
+ except ValueError:
+ R = R.add_gens([tan(c_expr, )])
+ p = p.set_ring(R)
+ x = x.set_ring(R)
+ c = c.set_ring(R)
+ const = R(tan(c_expr))
+ else:
+ try:
+ const = R(tan(c))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ p1 = p - c
+
+ # Makes use of SymPy functions to evaluate the values of the cos/sin
+ # of the constant term.
+ t2 = rs_tan(p1, x, prec)
+ t = rs_series_inversion(1 - const*t2, x, prec)
+ return rs_mul(const + t2, t, x, prec)
+
+ if R.ngens == 1:
+ return _tan1(p, x, prec)
+ else:
+ return rs_fun(p, rs_tan, x, prec)
+
+def rs_cot(p, x, prec):
+ """
+ Cotangent of a series
+
+ Return the series expansion of the cot of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_cot
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_cot(x, x, 6)
+ -2/945*x**5 - 1/45*x**3 - 1/3*x + x**(-1)
+
+ See Also
+ ========
+
+ cot
+ """
+ # It can not handle series like `p = x + x*y` where the coefficient of the
+ # linear term in the series variable is symbolic.
+ if rs_is_puiseux(p, x):
+ r = rs_puiseux(rs_cot, p, x, prec)
+ return r
+ i, m = _check_series_var(p, x, 'cot')
+ prec1 = prec + 2*m
+ c, s = rs_cos_sin(p, x, prec1)
+ s = mul_xin(s, i, -m)
+ s = rs_series_inversion(s, x, prec1)
+ res = rs_mul(c, s, x, prec1)
+ res = mul_xin(res, i, -m)
+ res = rs_trunc(res, x, prec)
+ return res
+
+def rs_sin(p, x, prec):
+ """
+ Sine of a series
+
+ Return the series expansion of the sin of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_sin
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_sin(x + x*y, x, 4)
+ -1/6*x**3*y**3 - 1/2*x**3*y**2 - 1/2*x**3*y - 1/6*x**3 + x*y + x
+ >>> rs_sin(x**QQ(3, 2) + x*y**QQ(7, 5), x, 4)
+ -1/2*x**(7/2)*y**(14/5) - 1/6*x**3*y**(21/5) + x**(3/2) + x*y**(7/5)
+
+ See Also
+ ========
+
+ sin
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_sin, p, x, prec)
+ R = x.ring
+ if not p:
+ return R(0)
+ c = _get_constant_term(p, x)
+ if c:
+ if R.domain is EX:
+ c_expr = c.as_expr()
+ t1, t2 = sin(c_expr), cos(c_expr)
+ elif isinstance(c, PolyElement):
+ try:
+ c_expr = c.as_expr()
+ t1, t2 = R(sin(c_expr)), R(cos(c_expr))
+ except ValueError:
+ R = R.add_gens([sin(c_expr), cos(c_expr)])
+ p = p.set_ring(R)
+ x = x.set_ring(R)
+ c = c.set_ring(R)
+ t1, t2 = R(sin(c_expr)), R(cos(c_expr))
+ else:
+ try:
+ t1, t2 = R(sin(c)), R(cos(c))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ p1 = p - c
+
+ # Makes use of SymPy cos, sin functions to evaluate the values of the
+ # cos/sin of the constant term.
+ return rs_sin(p1, x, prec)*t2 + rs_cos(p1, x, prec)*t1
+
+ # Series is calculated in terms of tan as its evaluation is fast.
+ if len(p) > 20 and R.ngens == 1:
+ t = rs_tan(p/2, x, prec)
+ t2 = rs_square(t, x, prec)
+ p1 = rs_series_inversion(1 + t2, x, prec)
+ return rs_mul(p1, 2*t, x, prec)
+ one = R(1)
+ n = 1
+ c = [0]
+ for k in range(2, prec + 2, 2):
+ c.append(one/n)
+ c.append(0)
+ n *= -k*(k + 1)
+ return rs_series_from_list(p, c, x, prec)
+
+def rs_cos(p, x, prec):
+ """
+ Cosine of a series
+
+ Return the series expansion of the cos of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_cos
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_cos(x + x*y, x, 4)
+ -1/2*x**2*y**2 - x**2*y - 1/2*x**2 + 1
+ >>> rs_cos(x + x*y, x, 4)/x**QQ(7, 5)
+ -1/2*x**(3/5)*y**2 - x**(3/5)*y - 1/2*x**(3/5) + x**(-7/5)
+
+ See Also
+ ========
+
+ cos
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_cos, p, x, prec)
+ R = p.ring
+ c = _get_constant_term(p, x)
+ if c:
+ if R.domain is EX:
+ c_expr = c.as_expr()
+ _, _ = sin(c_expr), cos(c_expr)
+ elif isinstance(c, PolyElement):
+ try:
+ c_expr = c.as_expr()
+ _, _ = R(sin(c_expr)), R(cos(c_expr))
+ except ValueError:
+ R = R.add_gens([sin(c_expr), cos(c_expr)])
+ p = p.set_ring(R)
+ x = x.set_ring(R)
+ c = c.set_ring(R)
+ else:
+ try:
+ _, _ = R(sin(c)), R(cos(c))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ p1 = p - c
+
+ # Makes use of SymPy cos, sin functions to evaluate the values of the
+ # cos/sin of the constant term.
+ p_cos = rs_cos(p1, x, prec)
+ p_sin = rs_sin(p1, x, prec)
+ R = R.compose(p_cos.ring).compose(p_sin.ring)
+ p_cos.set_ring(R)
+ p_sin.set_ring(R)
+ t1, t2 = R(sin(c_expr)), R(cos(c_expr))
+ return p_cos*t2 - p_sin*t1
+
+ # Series is calculated in terms of tan as its evaluation is fast.
+ if len(p) > 20 and R.ngens == 1:
+ t = rs_tan(p/2, x, prec)
+ t2 = rs_square(t, x, prec)
+ p1 = rs_series_inversion(1+t2, x, prec)
+ return rs_mul(p1, 1 - t2, x, prec)
+ one = R(1)
+ n = 1
+ c = []
+ for k in range(2, prec + 2, 2):
+ c.append(one/n)
+ c.append(0)
+ n *= -k*(k - 1)
+ return rs_series_from_list(p, c, x, prec)
+
+def rs_cos_sin(p, x, prec):
+ r"""
+ Return the tuple ``(rs_cos(p, x, prec)`, `rs_sin(p, x, prec))``.
+
+ Is faster than calling rs_cos and rs_sin separately
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_cos_sin, p, x, prec)
+ t = rs_tan(p/2, x, prec)
+ t2 = rs_square(t, x, prec)
+ p1 = rs_series_inversion(1 + t2, x, prec)
+ return (rs_mul(p1, 1 - t2, x, prec), rs_mul(p1, 2*t, x, prec))
+
+def _atanh(p, x, prec):
+ """
+ Expansion using formula
+
+ Faster for very small and univariate series
+ """
+ R = p.ring
+ one = R(1)
+ c = [one]
+ p2 = rs_square(p, x, prec)
+ for k in range(1, prec):
+ c.append(one/(2*k + 1))
+ s = rs_series_from_list(p2, c, x, prec)
+ s = rs_mul(s, p, x, prec)
+ return s
+
+def rs_atanh(p, x, prec):
+ """
+ Hyperbolic arctangent of a series
+
+ Return the series expansion of the atanh of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_atanh
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_atanh(x + x*y, x, 4)
+ 1/3*x**3*y**3 + x**3*y**2 + x**3*y + 1/3*x**3 + x*y + x
+
+ See Also
+ ========
+
+ atanh
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_atanh, p, x, prec)
+ R = p.ring
+ const = 0
+ if _has_constant_term(p, x):
+ zm = R.zero_monom
+ c = p[zm]
+ if R.domain is EX:
+ c_expr = c.as_expr()
+ const = atanh(c_expr)
+ elif isinstance(c, PolyElement):
+ try:
+ c_expr = c.as_expr()
+ const = R(atanh(c_expr))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ else:
+ try:
+ const = R(atanh(c))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+
+ # Instead of using a closed form formula, we differentiate atanh(p) to get
+ # `1/(1-p**2) * dp`, whose series expansion is much easier to calculate.
+ # Finally we integrate to get back atanh
+ dp = rs_diff(p, x)
+ p1 = - rs_square(p, x, prec) + 1
+ p1 = rs_series_inversion(p1, x, prec - 1)
+ p1 = rs_mul(dp, p1, x, prec - 1)
+ return rs_integrate(p1, x) + const
+
+def rs_sinh(p, x, prec):
+ """
+ Hyperbolic sine of a series
+
+ Return the series expansion of the sinh of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_sinh
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_sinh(x + x*y, x, 4)
+ 1/6*x**3*y**3 + 1/2*x**3*y**2 + 1/2*x**3*y + 1/6*x**3 + x*y + x
+
+ See Also
+ ========
+
+ sinh
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_sinh, p, x, prec)
+ t = rs_exp(p, x, prec)
+ t1 = rs_series_inversion(t, x, prec)
+ return (t - t1)/2
+
+def rs_cosh(p, x, prec):
+ """
+ Hyperbolic cosine of a series
+
+ Return the series expansion of the cosh of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_cosh
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_cosh(x + x*y, x, 4)
+ 1/2*x**2*y**2 + x**2*y + 1/2*x**2 + 1
+
+ See Also
+ ========
+
+ cosh
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_cosh, p, x, prec)
+ t = rs_exp(p, x, prec)
+ t1 = rs_series_inversion(t, x, prec)
+ return (t + t1)/2
+
+def _tanh(p, x, prec):
+ r"""
+ Helper function of :func:`rs_tanh`
+
+ Return the series expansion of tanh of a univariate series using Newton's
+ method. It takes advantage of the fact that series expansion of atanh is
+ easier than that of tanh.
+
+ See Also
+ ========
+
+ _tanh
+ """
+ R = p.ring
+ p1 = R(0)
+ for precx in _giant_steps(prec):
+ tmp = p - rs_atanh(p1, x, precx)
+ tmp = rs_mul(tmp, 1 - rs_square(p1, x, prec), x, precx)
+ p1 += tmp
+ return p1
+
+def rs_tanh(p, x, prec):
+ """
+ Hyperbolic tangent of a series
+
+ Return the series expansion of the tanh of ``p``, about 0.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_tanh
+ >>> R, x, y = ring('x, y', QQ)
+ >>> rs_tanh(x + x*y, x, 4)
+ -1/3*x**3*y**3 - x**3*y**2 - x**3*y - 1/3*x**3 + x*y + x
+
+ See Also
+ ========
+
+ tanh
+ """
+ if rs_is_puiseux(p, x):
+ return rs_puiseux(rs_tanh, p, x, prec)
+ R = p.ring
+ const = 0
+ if _has_constant_term(p, x):
+ zm = R.zero_monom
+ c = p[zm]
+ if R.domain is EX:
+ c_expr = c.as_expr()
+ const = tanh(c_expr)
+ elif isinstance(c, PolyElement):
+ try:
+ c_expr = c.as_expr()
+ const = R(tanh(c_expr))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ else:
+ try:
+ const = R(tanh(c))
+ except ValueError:
+ raise DomainError("The given series cannot be expanded in "
+ "this domain.")
+ p1 = p - c
+ t1 = rs_tanh(p1, x, prec)
+ t = rs_series_inversion(1 + const*t1, x, prec)
+ return rs_mul(const + t1, t, x, prec)
+
+ if R.ngens == 1:
+ return _tanh(p, x, prec)
+ else:
+ return rs_fun(p, _tanh, x, prec)
+
+def rs_newton(p, x, prec):
+ """
+ Compute the truncated Newton sum of the polynomial ``p``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_newton
+ >>> R, x = ring('x', QQ)
+ >>> p = x**2 - 2
+ >>> rs_newton(p, x, 5)
+ 8*x**4 + 4*x**2 + 2
+ """
+ deg = p.degree()
+ p1 = _invert_monoms(p)
+ p2 = rs_series_inversion(p1, x, prec)
+ p3 = rs_mul(p1.diff(x), p2, x, prec)
+ res = deg - p3*x
+ return res
+
+def rs_hadamard_exp(p1, inverse=False):
+ """
+ Return ``sum f_i/i!*x**i`` from ``sum f_i*x**i``,
+ where ``x`` is the first variable.
+
+ If ``invers=True`` return ``sum f_i*i!*x**i``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_hadamard_exp
+ >>> R, x = ring('x', QQ)
+ >>> p = 1 + x + x**2 + x**3
+ >>> rs_hadamard_exp(p)
+ 1/6*x**3 + 1/2*x**2 + x + 1
+ """
+ R = p1.ring
+ if R.domain != QQ:
+ raise NotImplementedError
+ p = R.zero
+ if not inverse:
+ for exp1, v1 in p1.items():
+ p[exp1] = v1/int(ifac(exp1[0]))
+ else:
+ for exp1, v1 in p1.items():
+ p[exp1] = v1*int(ifac(exp1[0]))
+ return p
+
+def rs_compose_add(p1, p2):
+ """
+ compute the composed sum ``prod(p2(x - beta) for beta root of p1)``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.ring_series import rs_compose_add
+ >>> R, x = ring('x', QQ)
+ >>> f = x**2 - 2
+ >>> g = x**2 - 3
+ >>> rs_compose_add(f, g)
+ x**4 - 10*x**2 + 1
+
+ References
+ ==========
+
+ .. [1] A. Bostan, P. Flajolet, B. Salvy and E. Schost
+ "Fast Computation with Two Algebraic Numbers",
+ (2002) Research Report 4579, Institut
+ National de Recherche en Informatique et en Automatique
+ """
+ R = p1.ring
+ x = R.gens[0]
+ prec = p1.degree()*p2.degree() + 1
+ np1 = rs_newton(p1, x, prec)
+ np1e = rs_hadamard_exp(np1)
+ np2 = rs_newton(p2, x, prec)
+ np2e = rs_hadamard_exp(np2)
+ np3e = rs_mul(np1e, np2e, x, prec)
+ np3 = rs_hadamard_exp(np3e, True)
+ np3a = (np3[(0,)] - np3)/x
+ q = rs_integrate(np3a, x)
+ q = rs_exp(q, x, prec)
+ q = _invert_monoms(q)
+ q = q.primitive()[1]
+ dp = p1.degree()*p2.degree() - q.degree()
+ # `dp` is the multiplicity of the zeroes of the resultant;
+ # these zeroes are missed in this computation so they are put here.
+ # if p1 and p2 are monic irreducible polynomials,
+ # there are zeroes in the resultant
+ # if and only if p1 = p2 ; in fact in that case p1 and p2 have a
+ # root in common, so gcd(p1, p2) != 1; being p1 and p2 irreducible
+ # this means p1 = p2
+ if dp:
+ q = q*x**dp
+ return q
+
+
+_convert_func = {
+ 'sin': 'rs_sin',
+ 'cos': 'rs_cos',
+ 'exp': 'rs_exp',
+ 'tan': 'rs_tan',
+ 'log': 'rs_log'
+ }
+
+def rs_min_pow(expr, series_rs, a):
+ """Find the minimum power of `a` in the series expansion of expr"""
+ series = 0
+ n = 2
+ while series == 0:
+ series = _rs_series(expr, series_rs, a, n)
+ n *= 2
+ R = series.ring
+ a = R(a)
+ i = R.gens.index(a)
+ return min(series, key=lambda t: t[i])[i]
+
+
+def _rs_series(expr, series_rs, a, prec):
+ # TODO Use _parallel_dict_from_expr instead of sring as sring is
+ # inefficient. For details, read the todo in sring.
+ args = expr.args
+ R = series_rs.ring
+
+ # expr does not contain any function to be expanded
+ if not any(arg.has(Function) for arg in args) and not expr.is_Function:
+ return series_rs
+
+ if not expr.has(a):
+ return series_rs
+
+ elif expr.is_Function:
+ arg = args[0]
+ if len(args) > 1:
+ raise NotImplementedError
+ R1, series = sring(arg, domain=QQ, expand=False, series=True)
+ series_inner = _rs_series(arg, series, a, prec)
+
+ # Why do we need to compose these three rings?
+ #
+ # We want to use a simple domain (like ``QQ`` or ``RR``) but they don't
+ # support symbolic coefficients. We need a ring that for example lets
+ # us have `sin(1)` and `cos(1)` as coefficients if we are expanding
+ # `sin(x + 1)`. The ``EX`` domain allows all symbolic coefficients, but
+ # that makes it very complex and hence slow.
+ #
+ # To solve this problem, we add only those symbolic elements as
+ # generators to our ring, that we need. Here, series_inner might
+ # involve terms like `sin(4)`, `exp(a)`, etc, which are not there in
+ # R1 or R. Hence, we compose these three rings to create one that has
+ # the generators of all three.
+ R = R.compose(R1).compose(series_inner.ring)
+ series_inner = series_inner.set_ring(R)
+ series = eval(_convert_func[str(expr.func)])(series_inner,
+ R(a), prec)
+ return series
+
+ elif expr.is_Mul:
+ n = len(args)
+ for arg in args: # XXX Looks redundant
+ if not arg.is_Number:
+ R1, _ = sring(arg, expand=False, series=True)
+ R = R.compose(R1)
+ min_pows = list(map(rs_min_pow, args, [R(arg) for arg in args],
+ [a]*len(args)))
+ sum_pows = sum(min_pows)
+ series = R(1)
+
+ for i in range(n):
+ _series = _rs_series(args[i], R(args[i]), a, prec - sum_pows +
+ min_pows[i])
+ R = R.compose(_series.ring)
+ _series = _series.set_ring(R)
+ series = series.set_ring(R)
+ series *= _series
+ series = rs_trunc(series, R(a), prec)
+ return series
+
+ elif expr.is_Add:
+ n = len(args)
+ series = R(0)
+ for i in range(n):
+ _series = _rs_series(args[i], R(args[i]), a, prec)
+ R = R.compose(_series.ring)
+ _series = _series.set_ring(R)
+ series = series.set_ring(R)
+ series += _series
+ return series
+
+ elif expr.is_Pow:
+ R1, _ = sring(expr.base, domain=QQ, expand=False, series=True)
+ R = R.compose(R1)
+ series_inner = _rs_series(expr.base, R(expr.base), a, prec)
+ return rs_pow(series_inner, expr.exp, series_inner.ring(a), prec)
+
+ # The `is_constant` method is buggy hence we check it at the end.
+ # See issue #9786 for details.
+ elif isinstance(expr, Expr) and expr.is_constant():
+ return sring(expr, domain=QQ, expand=False, series=True)[1]
+
+ else:
+ raise NotImplementedError
+
+def rs_series(expr, a, prec):
+ """Return the series expansion of an expression about 0.
+
+ Parameters
+ ==========
+
+ expr : :class:`Expr`
+ a : :class:`Symbol` with respect to which expr is to be expanded
+ prec : order of the series expansion
+
+ Currently supports multivariate Taylor series expansion. This is much
+ faster that SymPy's series method as it uses sparse polynomial operations.
+
+ It automatically creates the simplest ring required to represent the series
+ expansion through repeated calls to sring.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.ring_series import rs_series
+ >>> from sympy import sin, cos, exp, tan, symbols, QQ
+ >>> a, b, c = symbols('a, b, c')
+ >>> rs_series(sin(a) + exp(a), a, 5)
+ 1/24*a**4 + 1/2*a**2 + 2*a + 1
+ >>> series = rs_series(tan(a + b)*cos(a + c), a, 2)
+ >>> series.as_expr()
+ -a*sin(c)*tan(b) + a*cos(c)*tan(b)**2 + a*cos(c) + cos(c)*tan(b)
+ >>> series = rs_series(exp(a**QQ(1,3) + a**QQ(2, 5)), a, 1)
+ >>> series.as_expr()
+ a**(11/15) + a**(4/5)/2 + a**(2/5) + a**(2/3)/2 + a**(1/3) + 1
+
+ """
+ R, series = sring(expr, domain=QQ, expand=False, series=True)
+ if a not in R.symbols:
+ R = R.add_gens([a, ])
+ series = series.set_ring(R)
+ series = _rs_series(expr, series, a, prec)
+ R = series.ring
+ gen = R(a)
+ prec_got = series.degree(gen) + 1
+
+ if prec_got >= prec:
+ return rs_trunc(series, gen, prec)
+ else:
+ # increase the requested number of terms to get the desired
+ # number keep increasing (up to 9) until the received order
+ # is different than the original order and then predict how
+ # many additional terms are needed
+ for more in range(1, 9):
+ p1 = _rs_series(expr, series, a, prec=prec + more)
+ gen = gen.set_ring(p1.ring)
+ new_prec = p1.degree(gen) + 1
+ if new_prec != prec_got:
+ prec_do = ceiling(prec + (prec - prec_got)*more/(new_prec -
+ prec_got))
+ p1 = _rs_series(expr, series, a, prec=prec_do)
+ while p1.degree(gen) + 1 < prec:
+ p1 = _rs_series(expr, series, a, prec=prec_do)
+ gen = gen.set_ring(p1.ring)
+ prec_do *= 2
+ break
+ else:
+ break
+ else:
+ raise ValueError('Could not calculate %s terms for %s'
+ % (str(prec), expr))
+ return rs_trunc(p1, gen, prec)
diff --git a/lib/python3.12/site-packages/sympy/polys/rings.py b/lib/python3.12/site-packages/sympy/polys/rings.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e3db5d3997b10735395b3d130246eaaddd918b6
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/rings.py
@@ -0,0 +1,3013 @@
+"""Sparse polynomial rings. """
+
+from __future__ import annotations
+from typing import Any
+
+from operator import add, mul, lt, le, gt, ge
+from functools import reduce
+from types import GeneratorType
+
+from sympy.core.expr import Expr
+from sympy.core.intfunc import igcd
+from sympy.core.symbol import Symbol, symbols as _symbols
+from sympy.core.sympify import CantSympify, sympify
+from sympy.ntheory.multinomial import multinomial_coefficients
+from sympy.polys.compatibility import IPolys
+from sympy.polys.constructor import construct_domain
+from sympy.polys.densebasic import ninf, dmp_to_dict, dmp_from_dict
+from sympy.polys.domains.domainelement import DomainElement
+from sympy.polys.domains.polynomialring import PolynomialRing
+from sympy.polys.heuristicgcd import heugcd
+from sympy.polys.monomials import MonomialOps
+from sympy.polys.orderings import lex
+from sympy.polys.polyerrors import (
+ CoercionFailed, GeneratorsError,
+ ExactQuotientFailed, MultivariatePolynomialError)
+from sympy.polys.polyoptions import (Domain as DomainOpt,
+ Order as OrderOpt, build_options)
+from sympy.polys.polyutils import (expr_from_dict, _dict_reorder,
+ _parallel_dict_from_expr)
+from sympy.printing.defaults import DefaultPrinting
+from sympy.utilities import public, subsets
+from sympy.utilities.iterables import is_sequence
+from sympy.utilities.magic import pollute
+
+@public
+def ring(symbols, domain, order=lex):
+ """Construct a polynomial ring returning ``(ring, x_1, ..., x_n)``.
+
+ Parameters
+ ==========
+
+ symbols : str
+ Symbol/Expr or sequence of str, Symbol/Expr (non-empty)
+ domain : :class:`~.Domain` or coercible
+ order : :class:`~.MonomialOrder` or coercible, optional, defaults to ``lex``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.orderings import lex
+
+ >>> R, x, y, z = ring("x,y,z", ZZ, lex)
+ >>> R
+ Polynomial ring in x, y, z over ZZ with lex order
+ >>> x + y + z
+ x + y + z
+ >>> type(_)
+
+
+ """
+ _ring = PolyRing(symbols, domain, order)
+ return (_ring,) + _ring.gens
+
+@public
+def xring(symbols, domain, order=lex):
+ """Construct a polynomial ring returning ``(ring, (x_1, ..., x_n))``.
+
+ Parameters
+ ==========
+
+ symbols : str
+ Symbol/Expr or sequence of str, Symbol/Expr (non-empty)
+ domain : :class:`~.Domain` or coercible
+ order : :class:`~.MonomialOrder` or coercible, optional, defaults to ``lex``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import xring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.orderings import lex
+
+ >>> R, (x, y, z) = xring("x,y,z", ZZ, lex)
+ >>> R
+ Polynomial ring in x, y, z over ZZ with lex order
+ >>> x + y + z
+ x + y + z
+ >>> type(_)
+
+
+ """
+ _ring = PolyRing(symbols, domain, order)
+ return (_ring, _ring.gens)
+
+@public
+def vring(symbols, domain, order=lex):
+ """Construct a polynomial ring and inject ``x_1, ..., x_n`` into the global namespace.
+
+ Parameters
+ ==========
+
+ symbols : str
+ Symbol/Expr or sequence of str, Symbol/Expr (non-empty)
+ domain : :class:`~.Domain` or coercible
+ order : :class:`~.MonomialOrder` or coercible, optional, defaults to ``lex``
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import vring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.orderings import lex
+
+ >>> vring("x,y,z", ZZ, lex)
+ Polynomial ring in x, y, z over ZZ with lex order
+ >>> x + y + z # noqa:
+ x + y + z
+ >>> type(_)
+
+
+ """
+ _ring = PolyRing(symbols, domain, order)
+ pollute([ sym.name for sym in _ring.symbols ], _ring.gens)
+ return _ring
+
+@public
+def sring(exprs, *symbols, **options):
+ """Construct a ring deriving generators and domain from options and input expressions.
+
+ Parameters
+ ==========
+
+ exprs : :class:`~.Expr` or sequence of :class:`~.Expr` (sympifiable)
+ symbols : sequence of :class:`~.Symbol`/:class:`~.Expr`
+ options : keyword arguments understood by :class:`~.Options`
+
+ Examples
+ ========
+
+ >>> from sympy import sring, symbols
+
+ >>> x, y, z = symbols("x,y,z")
+ >>> R, f = sring(x + 2*y + 3*z)
+ >>> R
+ Polynomial ring in x, y, z over ZZ with lex order
+ >>> f
+ x + 2*y + 3*z
+ >>> type(_)
+
+
+ """
+ single = False
+
+ if not is_sequence(exprs):
+ exprs, single = [exprs], True
+
+ exprs = list(map(sympify, exprs))
+ opt = build_options(symbols, options)
+
+ # TODO: rewrite this so that it doesn't use expand() (see poly()).
+ reps, opt = _parallel_dict_from_expr(exprs, opt)
+
+ if opt.domain is None:
+ coeffs = sum([ list(rep.values()) for rep in reps ], [])
+
+ opt.domain, coeffs_dom = construct_domain(coeffs, opt=opt)
+
+ coeff_map = dict(zip(coeffs, coeffs_dom))
+ reps = [{m: coeff_map[c] for m, c in rep.items()} for rep in reps]
+
+ _ring = PolyRing(opt.gens, opt.domain, opt.order)
+ polys = list(map(_ring.from_dict, reps))
+
+ if single:
+ return (_ring, polys[0])
+ else:
+ return (_ring, polys)
+
+def _parse_symbols(symbols):
+ if isinstance(symbols, str):
+ return _symbols(symbols, seq=True) if symbols else ()
+ elif isinstance(symbols, Expr):
+ return (symbols,)
+ elif is_sequence(symbols):
+ if all(isinstance(s, str) for s in symbols):
+ return _symbols(symbols)
+ elif all(isinstance(s, Expr) for s in symbols):
+ return symbols
+
+ raise GeneratorsError("expected a string, Symbol or expression or a non-empty sequence of strings, Symbols or expressions")
+
+_ring_cache: dict[Any, Any] = {}
+
+class PolyRing(DefaultPrinting, IPolys):
+ """Multivariate distributed polynomial ring. """
+
+ def __new__(cls, symbols, domain, order=lex):
+ symbols = tuple(_parse_symbols(symbols))
+ ngens = len(symbols)
+ domain = DomainOpt.preprocess(domain)
+ order = OrderOpt.preprocess(order)
+
+ _hash_tuple = (cls.__name__, symbols, ngens, domain, order)
+ obj = _ring_cache.get(_hash_tuple)
+
+ if obj is None:
+ if domain.is_Composite and set(symbols) & set(domain.symbols):
+ raise GeneratorsError("polynomial ring and it's ground domain share generators")
+
+ obj = object.__new__(cls)
+ obj._hash_tuple = _hash_tuple
+ obj._hash = hash(_hash_tuple)
+ obj.dtype = type("PolyElement", (PolyElement,), {"ring": obj})
+ obj.symbols = symbols
+ obj.ngens = ngens
+ obj.domain = domain
+ obj.order = order
+
+ obj.zero_monom = (0,)*ngens
+ obj.gens = obj._gens()
+ obj._gens_set = set(obj.gens)
+
+ obj._one = [(obj.zero_monom, domain.one)]
+
+ if ngens:
+ # These expect monomials in at least one variable
+ codegen = MonomialOps(ngens)
+ obj.monomial_mul = codegen.mul()
+ obj.monomial_pow = codegen.pow()
+ obj.monomial_mulpow = codegen.mulpow()
+ obj.monomial_ldiv = codegen.ldiv()
+ obj.monomial_div = codegen.div()
+ obj.monomial_lcm = codegen.lcm()
+ obj.monomial_gcd = codegen.gcd()
+ else:
+ monunit = lambda a, b: ()
+ obj.monomial_mul = monunit
+ obj.monomial_pow = monunit
+ obj.monomial_mulpow = lambda a, b, c: ()
+ obj.monomial_ldiv = monunit
+ obj.monomial_div = monunit
+ obj.monomial_lcm = monunit
+ obj.monomial_gcd = monunit
+
+
+ if order is lex:
+ obj.leading_expv = max
+ else:
+ obj.leading_expv = lambda f: max(f, key=order)
+
+ for symbol, generator in zip(obj.symbols, obj.gens):
+ if isinstance(symbol, Symbol):
+ name = symbol.name
+
+ if not hasattr(obj, name):
+ setattr(obj, name, generator)
+
+ _ring_cache[_hash_tuple] = obj
+
+ return obj
+
+ def _gens(self):
+ """Return a list of polynomial generators. """
+ one = self.domain.one
+ _gens = []
+ for i in range(self.ngens):
+ expv = self.monomial_basis(i)
+ poly = self.zero
+ poly[expv] = one
+ _gens.append(poly)
+ return tuple(_gens)
+
+ def __getnewargs__(self):
+ return (self.symbols, self.domain, self.order)
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ del state["leading_expv"]
+
+ for key in state:
+ if key.startswith("monomial_"):
+ del state[key]
+
+ return state
+
+ def __hash__(self):
+ return self._hash
+
+ def __eq__(self, other):
+ return isinstance(other, PolyRing) and \
+ (self.symbols, self.domain, self.ngens, self.order) == \
+ (other.symbols, other.domain, other.ngens, other.order)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def clone(self, symbols=None, domain=None, order=None):
+ return self.__class__(symbols or self.symbols, domain or self.domain, order or self.order)
+
+ def monomial_basis(self, i):
+ """Return the ith-basis element. """
+ basis = [0]*self.ngens
+ basis[i] = 1
+ return tuple(basis)
+
+ @property
+ def zero(self):
+ return self.dtype()
+
+ @property
+ def one(self):
+ return self.dtype(self._one)
+
+ def domain_new(self, element, orig_domain=None):
+ return self.domain.convert(element, orig_domain)
+
+ def ground_new(self, coeff):
+ return self.term_new(self.zero_monom, coeff)
+
+ def term_new(self, monom, coeff):
+ coeff = self.domain_new(coeff)
+ poly = self.zero
+ if coeff:
+ poly[monom] = coeff
+ return poly
+
+ def ring_new(self, element):
+ if isinstance(element, PolyElement):
+ if self == element.ring:
+ return element
+ elif isinstance(self.domain, PolynomialRing) and self.domain.ring == element.ring:
+ return self.ground_new(element)
+ else:
+ raise NotImplementedError("conversion")
+ elif isinstance(element, str):
+ raise NotImplementedError("parsing")
+ elif isinstance(element, dict):
+ return self.from_dict(element)
+ elif isinstance(element, list):
+ try:
+ return self.from_terms(element)
+ except ValueError:
+ return self.from_list(element)
+ elif isinstance(element, Expr):
+ return self.from_expr(element)
+ else:
+ return self.ground_new(element)
+
+ __call__ = ring_new
+
+ def from_dict(self, element, orig_domain=None):
+ domain_new = self.domain_new
+ poly = self.zero
+
+ for monom, coeff in element.items():
+ coeff = domain_new(coeff, orig_domain)
+ if coeff:
+ poly[monom] = coeff
+
+ return poly
+
+ def from_terms(self, element, orig_domain=None):
+ return self.from_dict(dict(element), orig_domain)
+
+ def from_list(self, element):
+ return self.from_dict(dmp_to_dict(element, self.ngens-1, self.domain))
+
+ def _rebuild_expr(self, expr, mapping):
+ domain = self.domain
+
+ def _rebuild(expr):
+ generator = mapping.get(expr)
+
+ if generator is not None:
+ return generator
+ elif expr.is_Add:
+ return reduce(add, list(map(_rebuild, expr.args)))
+ elif expr.is_Mul:
+ return reduce(mul, list(map(_rebuild, expr.args)))
+ else:
+ # XXX: Use as_base_exp() to handle Pow(x, n) and also exp(n)
+ # XXX: E can be a generator e.g. sring([exp(2)]) -> ZZ[E]
+ base, exp = expr.as_base_exp()
+ if exp.is_Integer and exp > 1:
+ return _rebuild(base)**int(exp)
+ else:
+ return self.ground_new(domain.convert(expr))
+
+ return _rebuild(sympify(expr))
+
+ def from_expr(self, expr):
+ mapping = dict(list(zip(self.symbols, self.gens)))
+
+ try:
+ poly = self._rebuild_expr(expr, mapping)
+ except CoercionFailed:
+ raise ValueError("expected an expression convertible to a polynomial in %s, got %s" % (self, expr))
+ else:
+ return self.ring_new(poly)
+
+ def index(self, gen):
+ """Compute index of ``gen`` in ``self.gens``. """
+ if gen is None:
+ if self.ngens:
+ i = 0
+ else:
+ i = -1 # indicate impossible choice
+ elif isinstance(gen, int):
+ i = gen
+
+ if 0 <= i and i < self.ngens:
+ pass
+ elif -self.ngens <= i and i <= -1:
+ i = -i - 1
+ else:
+ raise ValueError("invalid generator index: %s" % gen)
+ elif isinstance(gen, self.dtype):
+ try:
+ i = self.gens.index(gen)
+ except ValueError:
+ raise ValueError("invalid generator: %s" % gen)
+ elif isinstance(gen, str):
+ try:
+ i = self.symbols.index(gen)
+ except ValueError:
+ raise ValueError("invalid generator: %s" % gen)
+ else:
+ raise ValueError("expected a polynomial generator, an integer, a string or None, got %s" % gen)
+
+ return i
+
+ def drop(self, *gens):
+ """Remove specified generators from this ring. """
+ indices = set(map(self.index, gens))
+ symbols = [ s for i, s in enumerate(self.symbols) if i not in indices ]
+
+ if not symbols:
+ return self.domain
+ else:
+ return self.clone(symbols=symbols)
+
+ def __getitem__(self, key):
+ symbols = self.symbols[key]
+
+ if not symbols:
+ return self.domain
+ else:
+ return self.clone(symbols=symbols)
+
+ def to_ground(self):
+ # TODO: should AlgebraicField be a Composite domain?
+ if self.domain.is_Composite or hasattr(self.domain, 'domain'):
+ return self.clone(domain=self.domain.domain)
+ else:
+ raise ValueError("%s is not a composite domain" % self.domain)
+
+ def to_domain(self):
+ return PolynomialRing(self)
+
+ def to_field(self):
+ from sympy.polys.fields import FracField
+ return FracField(self.symbols, self.domain, self.order)
+
+ @property
+ def is_univariate(self):
+ return len(self.gens) == 1
+
+ @property
+ def is_multivariate(self):
+ return len(self.gens) > 1
+
+ def add(self, *objs):
+ """
+ Add a sequence of polynomials or containers of polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> R, x = ring("x", ZZ)
+ >>> R.add([ x**2 + 2*i + 3 for i in range(4) ])
+ 4*x**2 + 24
+ >>> _.factor_list()
+ (4, [(x**2 + 6, 1)])
+
+ """
+ p = self.zero
+
+ for obj in objs:
+ if is_sequence(obj, include=GeneratorType):
+ p += self.add(*obj)
+ else:
+ p += obj
+
+ return p
+
+ def mul(self, *objs):
+ """
+ Multiply a sequence of polynomials or containers of polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> R, x = ring("x", ZZ)
+ >>> R.mul([ x**2 + 2*i + 3 for i in range(4) ])
+ x**8 + 24*x**6 + 206*x**4 + 744*x**2 + 945
+ >>> _.factor_list()
+ (1, [(x**2 + 3, 1), (x**2 + 5, 1), (x**2 + 7, 1), (x**2 + 9, 1)])
+
+ """
+ p = self.one
+
+ for obj in objs:
+ if is_sequence(obj, include=GeneratorType):
+ p *= self.mul(*obj)
+ else:
+ p *= obj
+
+ return p
+
+ def drop_to_ground(self, *gens):
+ r"""
+ Remove specified generators from the ring and inject them into
+ its domain.
+ """
+ indices = set(map(self.index, gens))
+ symbols = [s for i, s in enumerate(self.symbols) if i not in indices]
+ gens = [gen for i, gen in enumerate(self.gens) if i not in indices]
+
+ if not symbols:
+ return self
+ else:
+ return self.clone(symbols=symbols, domain=self.drop(*gens))
+
+ def compose(self, other):
+ """Add the generators of ``other`` to ``self``"""
+ if self != other:
+ syms = set(self.symbols).union(set(other.symbols))
+ return self.clone(symbols=list(syms))
+ else:
+ return self
+
+ def add_gens(self, symbols):
+ """Add the elements of ``symbols`` as generators to ``self``"""
+ syms = set(self.symbols).union(set(symbols))
+ return self.clone(symbols=list(syms))
+
+ def symmetric_poly(self, n):
+ """
+ Return the elementary symmetric polynomial of degree *n* over
+ this ring's generators.
+ """
+ if n < 0 or n > self.ngens:
+ raise ValueError("Cannot generate symmetric polynomial of order %s for %s" % (n, self.gens))
+ elif not n:
+ return self.one
+ else:
+ poly = self.zero
+ for s in subsets(range(self.ngens), int(n)):
+ monom = tuple(int(i in s) for i in range(self.ngens))
+ poly += self.term_new(monom, self.domain.one)
+ return poly
+
+
+class PolyElement(DomainElement, DefaultPrinting, CantSympify, dict):
+ """Element of multivariate distributed polynomial ring. """
+
+ def new(self, init):
+ return self.__class__(init)
+
+ def parent(self):
+ return self.ring.to_domain()
+
+ def __getnewargs__(self):
+ return (self.ring, list(self.iterterms()))
+
+ _hash = None
+
+ def __hash__(self):
+ # XXX: This computes a hash of a dictionary, but currently we don't
+ # protect dictionary from being changed so any use site modifications
+ # will make hashing go wrong. Use this feature with caution until we
+ # figure out how to make a safe API without compromising speed of this
+ # low-level class.
+ _hash = self._hash
+ if _hash is None:
+ self._hash = _hash = hash((self.ring, frozenset(self.items())))
+ return _hash
+
+ def copy(self):
+ """Return a copy of polynomial self.
+
+ Polynomials are mutable; if one is interested in preserving
+ a polynomial, and one plans to use inplace operations, one
+ can copy the polynomial. This method makes a shallow copy.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.rings import ring
+
+ >>> R, x, y = ring('x, y', ZZ)
+ >>> p = (x + y)**2
+ >>> p1 = p.copy()
+ >>> p2 = p
+ >>> p[R.zero_monom] = 3
+ >>> p
+ x**2 + 2*x*y + y**2 + 3
+ >>> p1
+ x**2 + 2*x*y + y**2
+ >>> p2
+ x**2 + 2*x*y + y**2 + 3
+
+ """
+ return self.new(self)
+
+ def set_ring(self, new_ring):
+ if self.ring == new_ring:
+ return self
+ elif self.ring.symbols != new_ring.symbols:
+ terms = list(zip(*_dict_reorder(self, self.ring.symbols, new_ring.symbols)))
+ return new_ring.from_terms(terms, self.ring.domain)
+ else:
+ return new_ring.from_dict(self, self.ring.domain)
+
+ def as_expr(self, *symbols):
+ if not symbols:
+ symbols = self.ring.symbols
+ elif len(symbols) != self.ring.ngens:
+ raise ValueError(
+ "Wrong number of symbols, expected %s got %s" %
+ (self.ring.ngens, len(symbols))
+ )
+
+ return expr_from_dict(self.as_expr_dict(), *symbols)
+
+ def as_expr_dict(self):
+ to_sympy = self.ring.domain.to_sympy
+ return {monom: to_sympy(coeff) for monom, coeff in self.iterterms()}
+
+ def clear_denoms(self):
+ domain = self.ring.domain
+
+ if not domain.is_Field or not domain.has_assoc_Ring:
+ return domain.one, self
+
+ ground_ring = domain.get_ring()
+ common = ground_ring.one
+ lcm = ground_ring.lcm
+ denom = domain.denom
+
+ for coeff in self.values():
+ common = lcm(common, denom(coeff))
+
+ poly = self.new([ (k, v*common) for k, v in self.items() ])
+ return common, poly
+
+ def strip_zero(self):
+ """Eliminate monomials with zero coefficient. """
+ for k, v in list(self.items()):
+ if not v:
+ del self[k]
+
+ def __eq__(p1, p2):
+ """Equality test for polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.rings import ring
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> p1 = (x + y)**2 + (x - y)**2
+ >>> p1 == 4*x*y
+ False
+ >>> p1 == 2*(x**2 + y**2)
+ True
+
+ """
+ if not p2:
+ return not p1
+ elif isinstance(p2, PolyElement) and p2.ring == p1.ring:
+ return dict.__eq__(p1, p2)
+ elif len(p1) > 1:
+ return False
+ else:
+ return p1.get(p1.ring.zero_monom) == p2
+
+ def __ne__(p1, p2):
+ return not p1 == p2
+
+ def almosteq(p1, p2, tolerance=None):
+ """Approximate equality test for polynomials. """
+ ring = p1.ring
+
+ if isinstance(p2, ring.dtype):
+ if set(p1.keys()) != set(p2.keys()):
+ return False
+
+ almosteq = ring.domain.almosteq
+
+ for k in p1.keys():
+ if not almosteq(p1[k], p2[k], tolerance):
+ return False
+ return True
+ elif len(p1) > 1:
+ return False
+ else:
+ try:
+ p2 = ring.domain.convert(p2)
+ except CoercionFailed:
+ return False
+ else:
+ return ring.domain.almosteq(p1.const(), p2, tolerance)
+
+ def sort_key(self):
+ return (len(self), self.terms())
+
+ def _cmp(p1, p2, op):
+ if isinstance(p2, p1.ring.dtype):
+ return op(p1.sort_key(), p2.sort_key())
+ else:
+ return NotImplemented
+
+ def __lt__(p1, p2):
+ return p1._cmp(p2, lt)
+ def __le__(p1, p2):
+ return p1._cmp(p2, le)
+ def __gt__(p1, p2):
+ return p1._cmp(p2, gt)
+ def __ge__(p1, p2):
+ return p1._cmp(p2, ge)
+
+ def _drop(self, gen):
+ ring = self.ring
+ i = ring.index(gen)
+
+ if ring.ngens == 1:
+ return i, ring.domain
+ else:
+ symbols = list(ring.symbols)
+ del symbols[i]
+ return i, ring.clone(symbols=symbols)
+
+ def drop(self, gen):
+ i, ring = self._drop(gen)
+
+ if self.ring.ngens == 1:
+ if self.is_ground:
+ return self.coeff(1)
+ else:
+ raise ValueError("Cannot drop %s" % gen)
+ else:
+ poly = ring.zero
+
+ for k, v in self.items():
+ if k[i] == 0:
+ K = list(k)
+ del K[i]
+ poly[tuple(K)] = v
+ else:
+ raise ValueError("Cannot drop %s" % gen)
+
+ return poly
+
+ def _drop_to_ground(self, gen):
+ ring = self.ring
+ i = ring.index(gen)
+
+ symbols = list(ring.symbols)
+ del symbols[i]
+ return i, ring.clone(symbols=symbols, domain=ring[i])
+
+ def drop_to_ground(self, gen):
+ if self.ring.ngens == 1:
+ raise ValueError("Cannot drop only generator to ground")
+
+ i, ring = self._drop_to_ground(gen)
+ poly = ring.zero
+ gen = ring.domain.gens[0]
+
+ for monom, coeff in self.iterterms():
+ mon = monom[:i] + monom[i+1:]
+ if mon not in poly:
+ poly[mon] = (gen**monom[i]).mul_ground(coeff)
+ else:
+ poly[mon] += (gen**monom[i]).mul_ground(coeff)
+
+ return poly
+
+ def to_dense(self):
+ return dmp_from_dict(self, self.ring.ngens-1, self.ring.domain)
+
+ def to_dict(self):
+ return dict(self)
+
+ def str(self, printer, precedence, exp_pattern, mul_symbol):
+ if not self:
+ return printer._print(self.ring.domain.zero)
+ prec_mul = precedence["Mul"]
+ prec_atom = precedence["Atom"]
+ ring = self.ring
+ symbols = ring.symbols
+ ngens = ring.ngens
+ zm = ring.zero_monom
+ sexpvs = []
+ for expv, coeff in self.terms():
+ negative = ring.domain.is_negative(coeff)
+ sign = " - " if negative else " + "
+ sexpvs.append(sign)
+ if expv == zm:
+ scoeff = printer._print(coeff)
+ if negative and scoeff.startswith("-"):
+ scoeff = scoeff[1:]
+ else:
+ if negative:
+ coeff = -coeff
+ if coeff != self.ring.domain.one:
+ scoeff = printer.parenthesize(coeff, prec_mul, strict=True)
+ else:
+ scoeff = ''
+ sexpv = []
+ for i in range(ngens):
+ exp = expv[i]
+ if not exp:
+ continue
+ symbol = printer.parenthesize(symbols[i], prec_atom, strict=True)
+ if exp != 1:
+ if exp != int(exp) or exp < 0:
+ sexp = printer.parenthesize(exp, prec_atom, strict=False)
+ else:
+ sexp = exp
+ sexpv.append(exp_pattern % (symbol, sexp))
+ else:
+ sexpv.append('%s' % symbol)
+ if scoeff:
+ sexpv = [scoeff] + sexpv
+ sexpvs.append(mul_symbol.join(sexpv))
+ if sexpvs[0] in [" + ", " - "]:
+ head = sexpvs.pop(0)
+ if head == " - ":
+ sexpvs.insert(0, "-")
+ return "".join(sexpvs)
+
+ @property
+ def is_generator(self):
+ return self in self.ring._gens_set
+
+ @property
+ def is_ground(self):
+ return not self or (len(self) == 1 and self.ring.zero_monom in self)
+
+ @property
+ def is_monomial(self):
+ return not self or (len(self) == 1 and self.LC == 1)
+
+ @property
+ def is_term(self):
+ return len(self) <= 1
+
+ @property
+ def is_negative(self):
+ return self.ring.domain.is_negative(self.LC)
+
+ @property
+ def is_positive(self):
+ return self.ring.domain.is_positive(self.LC)
+
+ @property
+ def is_nonnegative(self):
+ return self.ring.domain.is_nonnegative(self.LC)
+
+ @property
+ def is_nonpositive(self):
+ return self.ring.domain.is_nonpositive(self.LC)
+
+ @property
+ def is_zero(f):
+ return not f
+
+ @property
+ def is_one(f):
+ return f == f.ring.one
+
+ @property
+ def is_monic(f):
+ return f.ring.domain.is_one(f.LC)
+
+ @property
+ def is_primitive(f):
+ return f.ring.domain.is_one(f.content())
+
+ @property
+ def is_linear(f):
+ return all(sum(monom) <= 1 for monom in f.itermonoms())
+
+ @property
+ def is_quadratic(f):
+ return all(sum(monom) <= 2 for monom in f.itermonoms())
+
+ @property
+ def is_squarefree(f):
+ if not f.ring.ngens:
+ return True
+ return f.ring.dmp_sqf_p(f)
+
+ @property
+ def is_irreducible(f):
+ if not f.ring.ngens:
+ return True
+ return f.ring.dmp_irreducible_p(f)
+
+ @property
+ def is_cyclotomic(f):
+ if f.ring.is_univariate:
+ return f.ring.dup_cyclotomic_p(f)
+ else:
+ raise MultivariatePolynomialError("cyclotomic polynomial")
+
+ def __neg__(self):
+ return self.new([ (monom, -coeff) for monom, coeff in self.iterterms() ])
+
+ def __pos__(self):
+ return self
+
+ def __add__(p1, p2):
+ """Add two polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.rings import ring
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> (x + y)**2 + (x - y)**2
+ 2*x**2 + 2*y**2
+
+ """
+ if not p2:
+ return p1.copy()
+ ring = p1.ring
+ if isinstance(p2, ring.dtype):
+ p = p1.copy()
+ get = p.get
+ zero = ring.domain.zero
+ for k, v in p2.items():
+ v = get(k, zero) + v
+ if v:
+ p[k] = v
+ else:
+ del p[k]
+ return p
+ elif isinstance(p2, PolyElement):
+ if isinstance(ring.domain, PolynomialRing) and ring.domain.ring == p2.ring:
+ pass
+ elif isinstance(p2.ring.domain, PolynomialRing) and p2.ring.domain.ring == ring:
+ return p2.__radd__(p1)
+ else:
+ return NotImplemented
+
+ try:
+ cp2 = ring.domain_new(p2)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ p = p1.copy()
+ if not cp2:
+ return p
+ zm = ring.zero_monom
+ if zm not in p1.keys():
+ p[zm] = cp2
+ else:
+ if p2 == -p[zm]:
+ del p[zm]
+ else:
+ p[zm] += cp2
+ return p
+
+ def __radd__(p1, n):
+ p = p1.copy()
+ if not n:
+ return p
+ ring = p1.ring
+ try:
+ n = ring.domain_new(n)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ zm = ring.zero_monom
+ if zm not in p1.keys():
+ p[zm] = n
+ else:
+ if n == -p[zm]:
+ del p[zm]
+ else:
+ p[zm] += n
+ return p
+
+ def __sub__(p1, p2):
+ """Subtract polynomial p2 from p1.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.rings import ring
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> p1 = x + y**2
+ >>> p2 = x*y + y**2
+ >>> p1 - p2
+ -x*y + x
+
+ """
+ if not p2:
+ return p1.copy()
+ ring = p1.ring
+ if isinstance(p2, ring.dtype):
+ p = p1.copy()
+ get = p.get
+ zero = ring.domain.zero
+ for k, v in p2.items():
+ v = get(k, zero) - v
+ if v:
+ p[k] = v
+ else:
+ del p[k]
+ return p
+ elif isinstance(p2, PolyElement):
+ if isinstance(ring.domain, PolynomialRing) and ring.domain.ring == p2.ring:
+ pass
+ elif isinstance(p2.ring.domain, PolynomialRing) and p2.ring.domain.ring == ring:
+ return p2.__rsub__(p1)
+ else:
+ return NotImplemented
+
+ try:
+ p2 = ring.domain_new(p2)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ p = p1.copy()
+ zm = ring.zero_monom
+ if zm not in p1.keys():
+ p[zm] = -p2
+ else:
+ if p2 == p[zm]:
+ del p[zm]
+ else:
+ p[zm] -= p2
+ return p
+
+ def __rsub__(p1, n):
+ """n - p1 with n convertible to the coefficient domain.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.rings import ring
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> p = x + y
+ >>> 4 - p
+ -x - y + 4
+
+ """
+ ring = p1.ring
+ try:
+ n = ring.domain_new(n)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ p = ring.zero
+ for expv in p1:
+ p[expv] = -p1[expv]
+ p += n
+ return p
+
+ def __mul__(p1, p2):
+ """Multiply two polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import QQ
+ >>> from sympy.polys.rings import ring
+
+ >>> _, x, y = ring('x, y', QQ)
+ >>> p1 = x + y
+ >>> p2 = x - y
+ >>> p1*p2
+ x**2 - y**2
+
+ """
+ ring = p1.ring
+ p = ring.zero
+ if not p1 or not p2:
+ return p
+ elif isinstance(p2, ring.dtype):
+ get = p.get
+ zero = ring.domain.zero
+ monomial_mul = ring.monomial_mul
+ p2it = list(p2.items())
+ for exp1, v1 in p1.items():
+ for exp2, v2 in p2it:
+ exp = monomial_mul(exp1, exp2)
+ p[exp] = get(exp, zero) + v1*v2
+ p.strip_zero()
+ return p
+ elif isinstance(p2, PolyElement):
+ if isinstance(ring.domain, PolynomialRing) and ring.domain.ring == p2.ring:
+ pass
+ elif isinstance(p2.ring.domain, PolynomialRing) and p2.ring.domain.ring == ring:
+ return p2.__rmul__(p1)
+ else:
+ return NotImplemented
+
+ try:
+ p2 = ring.domain_new(p2)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ for exp1, v1 in p1.items():
+ v = v1*p2
+ if v:
+ p[exp1] = v
+ return p
+
+ def __rmul__(p1, p2):
+ """p2 * p1 with p2 in the coefficient domain of p1.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.rings import ring
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> p = x + y
+ >>> 4 * p
+ 4*x + 4*y
+
+ """
+ p = p1.ring.zero
+ if not p2:
+ return p
+ try:
+ p2 = p.ring.domain_new(p2)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ for exp1, v1 in p1.items():
+ v = p2*v1
+ if v:
+ p[exp1] = v
+ return p
+
+ def __pow__(self, n):
+ """raise polynomial to power `n`
+
+ Examples
+ ========
+
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.rings import ring
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> p = x + y**2
+ >>> p**3
+ x**3 + 3*x**2*y**2 + 3*x*y**4 + y**6
+
+ """
+ ring = self.ring
+
+ if not n:
+ if self:
+ return ring.one
+ else:
+ raise ValueError("0**0")
+ elif len(self) == 1:
+ monom, coeff = list(self.items())[0]
+ p = ring.zero
+ if coeff == ring.domain.one:
+ p[ring.monomial_pow(monom, n)] = coeff
+ else:
+ p[ring.monomial_pow(monom, n)] = coeff**n
+ return p
+
+ # For ring series, we need negative and rational exponent support only
+ # with monomials.
+ n = int(n)
+ if n < 0:
+ raise ValueError("Negative exponent")
+
+ elif n == 1:
+ return self.copy()
+ elif n == 2:
+ return self.square()
+ elif n == 3:
+ return self*self.square()
+ elif len(self) <= 5: # TODO: use an actual density measure
+ return self._pow_multinomial(n)
+ else:
+ return self._pow_generic(n)
+
+ def _pow_generic(self, n):
+ p = self.ring.one
+ c = self
+
+ while True:
+ if n & 1:
+ p = p*c
+ n -= 1
+ if not n:
+ break
+
+ c = c.square()
+ n = n // 2
+
+ return p
+
+ def _pow_multinomial(self, n):
+ multinomials = multinomial_coefficients(len(self), n).items()
+ monomial_mulpow = self.ring.monomial_mulpow
+ zero_monom = self.ring.zero_monom
+ terms = self.items()
+ zero = self.ring.domain.zero
+ poly = self.ring.zero
+
+ for multinomial, multinomial_coeff in multinomials:
+ product_monom = zero_monom
+ product_coeff = multinomial_coeff
+
+ for exp, (monom, coeff) in zip(multinomial, terms):
+ if exp:
+ product_monom = monomial_mulpow(product_monom, monom, exp)
+ product_coeff *= coeff**exp
+
+ monom = tuple(product_monom)
+ coeff = product_coeff
+
+ coeff = poly.get(monom, zero) + coeff
+
+ if coeff:
+ poly[monom] = coeff
+ elif monom in poly:
+ del poly[monom]
+
+ return poly
+
+ def square(self):
+ """square of a polynomial
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> p = x + y**2
+ >>> p.square()
+ x**2 + 2*x*y**2 + y**4
+
+ """
+ ring = self.ring
+ p = ring.zero
+ get = p.get
+ keys = list(self.keys())
+ zero = ring.domain.zero
+ monomial_mul = ring.monomial_mul
+ for i in range(len(keys)):
+ k1 = keys[i]
+ pk = self[k1]
+ for j in range(i):
+ k2 = keys[j]
+ exp = monomial_mul(k1, k2)
+ p[exp] = get(exp, zero) + pk*self[k2]
+ p = p.imul_num(2)
+ get = p.get
+ for k, v in self.items():
+ k2 = monomial_mul(k, k)
+ p[k2] = get(k2, zero) + v**2
+ p.strip_zero()
+ return p
+
+ def __divmod__(p1, p2):
+ ring = p1.ring
+
+ if not p2:
+ raise ZeroDivisionError("polynomial division")
+ elif isinstance(p2, ring.dtype):
+ return p1.div(p2)
+ elif isinstance(p2, PolyElement):
+ if isinstance(ring.domain, PolynomialRing) and ring.domain.ring == p2.ring:
+ pass
+ elif isinstance(p2.ring.domain, PolynomialRing) and p2.ring.domain.ring == ring:
+ return p2.__rdivmod__(p1)
+ else:
+ return NotImplemented
+
+ try:
+ p2 = ring.domain_new(p2)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ return (p1.quo_ground(p2), p1.rem_ground(p2))
+
+ def __rdivmod__(p1, p2):
+ return NotImplemented
+
+ def __mod__(p1, p2):
+ ring = p1.ring
+
+ if not p2:
+ raise ZeroDivisionError("polynomial division")
+ elif isinstance(p2, ring.dtype):
+ return p1.rem(p2)
+ elif isinstance(p2, PolyElement):
+ if isinstance(ring.domain, PolynomialRing) and ring.domain.ring == p2.ring:
+ pass
+ elif isinstance(p2.ring.domain, PolynomialRing) and p2.ring.domain.ring == ring:
+ return p2.__rmod__(p1)
+ else:
+ return NotImplemented
+
+ try:
+ p2 = ring.domain_new(p2)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ return p1.rem_ground(p2)
+
+ def __rmod__(p1, p2):
+ return NotImplemented
+
+ def __truediv__(p1, p2):
+ ring = p1.ring
+
+ if not p2:
+ raise ZeroDivisionError("polynomial division")
+ elif isinstance(p2, ring.dtype):
+ if p2.is_monomial:
+ return p1*(p2**(-1))
+ else:
+ return p1.quo(p2)
+ elif isinstance(p2, PolyElement):
+ if isinstance(ring.domain, PolynomialRing) and ring.domain.ring == p2.ring:
+ pass
+ elif isinstance(p2.ring.domain, PolynomialRing) and p2.ring.domain.ring == ring:
+ return p2.__rtruediv__(p1)
+ else:
+ return NotImplemented
+
+ try:
+ p2 = ring.domain_new(p2)
+ except CoercionFailed:
+ return NotImplemented
+ else:
+ return p1.quo_ground(p2)
+
+ def __rtruediv__(p1, p2):
+ return NotImplemented
+
+ __floordiv__ = __truediv__
+ __rfloordiv__ = __rtruediv__
+
+ # TODO: use // (__floordiv__) for exquo()?
+
+ def _term_div(self):
+ zm = self.ring.zero_monom
+ domain = self.ring.domain
+ domain_quo = domain.quo
+ monomial_div = self.ring.monomial_div
+
+ if domain.is_Field:
+ def term_div(a_lm_a_lc, b_lm_b_lc):
+ a_lm, a_lc = a_lm_a_lc
+ b_lm, b_lc = b_lm_b_lc
+ if b_lm == zm: # apparently this is a very common case
+ monom = a_lm
+ else:
+ monom = monomial_div(a_lm, b_lm)
+ if monom is not None:
+ return monom, domain_quo(a_lc, b_lc)
+ else:
+ return None
+ else:
+ def term_div(a_lm_a_lc, b_lm_b_lc):
+ a_lm, a_lc = a_lm_a_lc
+ b_lm, b_lc = b_lm_b_lc
+ if b_lm == zm: # apparently this is a very common case
+ monom = a_lm
+ else:
+ monom = monomial_div(a_lm, b_lm)
+ if not (monom is None or a_lc % b_lc):
+ return monom, domain_quo(a_lc, b_lc)
+ else:
+ return None
+
+ return term_div
+
+ def div(self, fv):
+ """Division algorithm, see [CLO] p64.
+
+ fv array of polynomials
+ return qv, r such that
+ self = sum(fv[i]*qv[i]) + r
+
+ All polynomials are required not to be Laurent polynomials.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> f = x**3
+ >>> f0 = x - y**2
+ >>> f1 = x - y
+ >>> qv, r = f.div((f0, f1))
+ >>> qv[0]
+ x**2 + x*y**2 + y**4
+ >>> qv[1]
+ 0
+ >>> r
+ y**6
+
+ """
+ ring = self.ring
+ ret_single = False
+ if isinstance(fv, PolyElement):
+ ret_single = True
+ fv = [fv]
+ if not all(fv):
+ raise ZeroDivisionError("polynomial division")
+ if not self:
+ if ret_single:
+ return ring.zero, ring.zero
+ else:
+ return [], ring.zero
+ for f in fv:
+ if f.ring != ring:
+ raise ValueError('self and f must have the same ring')
+ s = len(fv)
+ qv = [ring.zero for i in range(s)]
+ p = self.copy()
+ r = ring.zero
+ term_div = self._term_div()
+ expvs = [fx.leading_expv() for fx in fv]
+ while p:
+ i = 0
+ divoccurred = 0
+ while i < s and divoccurred == 0:
+ expv = p.leading_expv()
+ term = term_div((expv, p[expv]), (expvs[i], fv[i][expvs[i]]))
+ if term is not None:
+ expv1, c = term
+ qv[i] = qv[i]._iadd_monom((expv1, c))
+ p = p._iadd_poly_monom(fv[i], (expv1, -c))
+ divoccurred = 1
+ else:
+ i += 1
+ if not divoccurred:
+ expv = p.leading_expv()
+ r = r._iadd_monom((expv, p[expv]))
+ del p[expv]
+ if expv == ring.zero_monom:
+ r += p
+ if ret_single:
+ if not qv:
+ return ring.zero, r
+ else:
+ return qv[0], r
+ else:
+ return qv, r
+
+ def rem(self, G):
+ f = self
+ if isinstance(G, PolyElement):
+ G = [G]
+ if not all(G):
+ raise ZeroDivisionError("polynomial division")
+ ring = f.ring
+ domain = ring.domain
+ zero = domain.zero
+ monomial_mul = ring.monomial_mul
+ r = ring.zero
+ term_div = f._term_div()
+ ltf = f.LT
+ f = f.copy()
+ get = f.get
+ while f:
+ for g in G:
+ tq = term_div(ltf, g.LT)
+ if tq is not None:
+ m, c = tq
+ for mg, cg in g.iterterms():
+ m1 = monomial_mul(mg, m)
+ c1 = get(m1, zero) - c*cg
+ if not c1:
+ del f[m1]
+ else:
+ f[m1] = c1
+ ltm = f.leading_expv()
+ if ltm is not None:
+ ltf = ltm, f[ltm]
+
+ break
+ else:
+ ltm, ltc = ltf
+ if ltm in r:
+ r[ltm] += ltc
+ else:
+ r[ltm] = ltc
+ del f[ltm]
+ ltm = f.leading_expv()
+ if ltm is not None:
+ ltf = ltm, f[ltm]
+
+ return r
+
+ def quo(f, G):
+ return f.div(G)[0]
+
+ def exquo(f, G):
+ q, r = f.div(G)
+
+ if not r:
+ return q
+ else:
+ raise ExactQuotientFailed(f, G)
+
+ def _iadd_monom(self, mc):
+ """add to self the monomial coeff*x0**i0*x1**i1*...
+ unless self is a generator -- then just return the sum of the two.
+
+ mc is a tuple, (monom, coeff), where monomial is (i0, i1, ...)
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> p = x**4 + 2*y
+ >>> m = (1, 2)
+ >>> p1 = p._iadd_monom((m, 5))
+ >>> p1
+ x**4 + 5*x*y**2 + 2*y
+ >>> p1 is p
+ True
+ >>> p = x
+ >>> p1 = p._iadd_monom((m, 5))
+ >>> p1
+ 5*x*y**2 + x
+ >>> p1 is p
+ False
+
+ """
+ if self in self.ring._gens_set:
+ cpself = self.copy()
+ else:
+ cpself = self
+ expv, coeff = mc
+ c = cpself.get(expv)
+ if c is None:
+ cpself[expv] = coeff
+ else:
+ c += coeff
+ if c:
+ cpself[expv] = c
+ else:
+ del cpself[expv]
+ return cpself
+
+ def _iadd_poly_monom(self, p2, mc):
+ """add to self the product of (p)*(coeff*x0**i0*x1**i1*...)
+ unless self is a generator -- then just return the sum of the two.
+
+ mc is a tuple, (monom, coeff), where monomial is (i0, i1, ...)
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y, z = ring('x, y, z', ZZ)
+ >>> p1 = x**4 + 2*y
+ >>> p2 = y + z
+ >>> m = (1, 2, 3)
+ >>> p1 = p1._iadd_poly_monom(p2, (m, 3))
+ >>> p1
+ x**4 + 3*x*y**3*z**3 + 3*x*y**2*z**4 + 2*y
+
+ """
+ p1 = self
+ if p1 in p1.ring._gens_set:
+ p1 = p1.copy()
+ (m, c) = mc
+ get = p1.get
+ zero = p1.ring.domain.zero
+ monomial_mul = p1.ring.monomial_mul
+ for k, v in p2.items():
+ ka = monomial_mul(k, m)
+ coeff = get(ka, zero) + v*c
+ if coeff:
+ p1[ka] = coeff
+ else:
+ del p1[ka]
+ return p1
+
+ def degree(f, x=None):
+ """
+ The leading degree in ``x`` or the main variable.
+
+ Note that the degree of 0 is negative infinity (``float('-inf')``)
+
+ """
+ i = f.ring.index(x)
+
+ if not f:
+ return ninf
+ elif i < 0:
+ return 0
+ else:
+ return max(monom[i] for monom in f.itermonoms())
+
+ def degrees(f):
+ """
+ A tuple containing leading degrees in all variables.
+
+ Note that the degree of 0 is negative infinity (``float('-inf')``)
+
+ """
+ if not f:
+ return (ninf,)*f.ring.ngens
+ else:
+ return tuple(map(max, list(zip(*f.itermonoms()))))
+
+ def tail_degree(f, x=None):
+ """
+ The tail degree in ``x`` or the main variable.
+
+ Note that the degree of 0 is negative infinity (``float('-inf')``)
+
+ """
+ i = f.ring.index(x)
+
+ if not f:
+ return ninf
+ elif i < 0:
+ return 0
+ else:
+ return min(monom[i] for monom in f.itermonoms())
+
+ def tail_degrees(f):
+ """
+ A tuple containing tail degrees in all variables.
+
+ Note that the degree of 0 is negative infinity (``float('-inf')``)
+
+ """
+ if not f:
+ return (ninf,)*f.ring.ngens
+ else:
+ return tuple(map(min, list(zip(*f.itermonoms()))))
+
+ def leading_expv(self):
+ """Leading monomial tuple according to the monomial ordering.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y, z = ring('x, y, z', ZZ)
+ >>> p = x**4 + x**3*y + x**2*z**2 + z**7
+ >>> p.leading_expv()
+ (4, 0, 0)
+
+ """
+ if self:
+ return self.ring.leading_expv(self)
+ else:
+ return None
+
+ def _get_coeff(self, expv):
+ return self.get(expv, self.ring.domain.zero)
+
+ def coeff(self, element):
+ """
+ Returns the coefficient that stands next to the given monomial.
+
+ Parameters
+ ==========
+
+ element : PolyElement (with ``is_monomial = True``) or 1
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y, z = ring("x,y,z", ZZ)
+ >>> f = 3*x**2*y - x*y*z + 7*z**3 + 23
+
+ >>> f.coeff(x**2*y)
+ 3
+ >>> f.coeff(x*y)
+ 0
+ >>> f.coeff(1)
+ 23
+
+ """
+ if element == 1:
+ return self._get_coeff(self.ring.zero_monom)
+ elif isinstance(element, self.ring.dtype):
+ terms = list(element.iterterms())
+ if len(terms) == 1:
+ monom, coeff = terms[0]
+ if coeff == self.ring.domain.one:
+ return self._get_coeff(monom)
+
+ raise ValueError("expected a monomial, got %s" % element)
+
+ def const(self):
+ """Returns the constant coefficient. """
+ return self._get_coeff(self.ring.zero_monom)
+
+ @property
+ def LC(self):
+ return self._get_coeff(self.leading_expv())
+
+ @property
+ def LM(self):
+ expv = self.leading_expv()
+ if expv is None:
+ return self.ring.zero_monom
+ else:
+ return expv
+
+ def leading_monom(self):
+ """
+ Leading monomial as a polynomial element.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> (3*x*y + y**2).leading_monom()
+ x*y
+
+ """
+ p = self.ring.zero
+ expv = self.leading_expv()
+ if expv:
+ p[expv] = self.ring.domain.one
+ return p
+
+ @property
+ def LT(self):
+ expv = self.leading_expv()
+ if expv is None:
+ return (self.ring.zero_monom, self.ring.domain.zero)
+ else:
+ return (expv, self._get_coeff(expv))
+
+ def leading_term(self):
+ """Leading term as a polynomial element.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> (3*x*y + y**2).leading_term()
+ 3*x*y
+
+ """
+ p = self.ring.zero
+ expv = self.leading_expv()
+ if expv is not None:
+ p[expv] = self[expv]
+ return p
+
+ def _sorted(self, seq, order):
+ if order is None:
+ order = self.ring.order
+ else:
+ order = OrderOpt.preprocess(order)
+
+ if order is lex:
+ return sorted(seq, key=lambda monom: monom[0], reverse=True)
+ else:
+ return sorted(seq, key=lambda monom: order(monom[0]), reverse=True)
+
+ def coeffs(self, order=None):
+ """Ordered list of polynomial coefficients.
+
+ Parameters
+ ==========
+
+ order : :class:`~.MonomialOrder` or coercible, optional
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.orderings import lex, grlex
+
+ >>> _, x, y = ring("x, y", ZZ, lex)
+ >>> f = x*y**7 + 2*x**2*y**3
+
+ >>> f.coeffs()
+ [2, 1]
+ >>> f.coeffs(grlex)
+ [1, 2]
+
+ """
+ return [ coeff for _, coeff in self.terms(order) ]
+
+ def monoms(self, order=None):
+ """Ordered list of polynomial monomials.
+
+ Parameters
+ ==========
+
+ order : :class:`~.MonomialOrder` or coercible, optional
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.orderings import lex, grlex
+
+ >>> _, x, y = ring("x, y", ZZ, lex)
+ >>> f = x*y**7 + 2*x**2*y**3
+
+ >>> f.monoms()
+ [(2, 3), (1, 7)]
+ >>> f.monoms(grlex)
+ [(1, 7), (2, 3)]
+
+ """
+ return [ monom for monom, _ in self.terms(order) ]
+
+ def terms(self, order=None):
+ """Ordered list of polynomial terms.
+
+ Parameters
+ ==========
+
+ order : :class:`~.MonomialOrder` or coercible, optional
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+ >>> from sympy.polys.orderings import lex, grlex
+
+ >>> _, x, y = ring("x, y", ZZ, lex)
+ >>> f = x*y**7 + 2*x**2*y**3
+
+ >>> f.terms()
+ [((2, 3), 2), ((1, 7), 1)]
+ >>> f.terms(grlex)
+ [((1, 7), 1), ((2, 3), 2)]
+
+ """
+ return self._sorted(list(self.items()), order)
+
+ def itercoeffs(self):
+ """Iterator over coefficients of a polynomial. """
+ return iter(self.values())
+
+ def itermonoms(self):
+ """Iterator over monomials of a polynomial. """
+ return iter(self.keys())
+
+ def iterterms(self):
+ """Iterator over terms of a polynomial. """
+ return iter(self.items())
+
+ def listcoeffs(self):
+ """Unordered list of polynomial coefficients. """
+ return list(self.values())
+
+ def listmonoms(self):
+ """Unordered list of polynomial monomials. """
+ return list(self.keys())
+
+ def listterms(self):
+ """Unordered list of polynomial terms. """
+ return list(self.items())
+
+ def imul_num(p, c):
+ """multiply inplace the polynomial p by an element in the
+ coefficient ring, provided p is not one of the generators;
+ else multiply not inplace
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y = ring('x, y', ZZ)
+ >>> p = x + y**2
+ >>> p1 = p.imul_num(3)
+ >>> p1
+ 3*x + 3*y**2
+ >>> p1 is p
+ True
+ >>> p = x
+ >>> p1 = p.imul_num(3)
+ >>> p1
+ 3*x
+ >>> p1 is p
+ False
+
+ """
+ if p in p.ring._gens_set:
+ return p*c
+ if not c:
+ p.clear()
+ return
+ for exp in p:
+ p[exp] *= c
+ return p
+
+ def content(f):
+ """Returns GCD of polynomial's coefficients. """
+ domain = f.ring.domain
+ cont = domain.zero
+ gcd = domain.gcd
+
+ for coeff in f.itercoeffs():
+ cont = gcd(cont, coeff)
+
+ return cont
+
+ def primitive(f):
+ """Returns content and a primitive polynomial. """
+ cont = f.content()
+ return cont, f.quo_ground(cont)
+
+ def monic(f):
+ """Divides all coefficients by the leading coefficient. """
+ if not f:
+ return f
+ else:
+ return f.quo_ground(f.LC)
+
+ def mul_ground(f, x):
+ if not x:
+ return f.ring.zero
+
+ terms = [ (monom, coeff*x) for monom, coeff in f.iterterms() ]
+ return f.new(terms)
+
+ def mul_monom(f, monom):
+ monomial_mul = f.ring.monomial_mul
+ terms = [ (monomial_mul(f_monom, monom), f_coeff) for f_monom, f_coeff in f.items() ]
+ return f.new(terms)
+
+ def mul_term(f, term):
+ monom, coeff = term
+
+ if not f or not coeff:
+ return f.ring.zero
+ elif monom == f.ring.zero_monom:
+ return f.mul_ground(coeff)
+
+ monomial_mul = f.ring.monomial_mul
+ terms = [ (monomial_mul(f_monom, monom), f_coeff*coeff) for f_monom, f_coeff in f.items() ]
+ return f.new(terms)
+
+ def quo_ground(f, x):
+ domain = f.ring.domain
+
+ if not x:
+ raise ZeroDivisionError('polynomial division')
+ if not f or x == domain.one:
+ return f
+
+ if domain.is_Field:
+ quo = domain.quo
+ terms = [ (monom, quo(coeff, x)) for monom, coeff in f.iterterms() ]
+ else:
+ terms = [ (monom, coeff // x) for monom, coeff in f.iterterms() if not (coeff % x) ]
+
+ return f.new(terms)
+
+ def quo_term(f, term):
+ monom, coeff = term
+
+ if not coeff:
+ raise ZeroDivisionError("polynomial division")
+ elif not f:
+ return f.ring.zero
+ elif monom == f.ring.zero_monom:
+ return f.quo_ground(coeff)
+
+ term_div = f._term_div()
+
+ terms = [ term_div(t, term) for t in f.iterterms() ]
+ return f.new([ t for t in terms if t is not None ])
+
+ def trunc_ground(f, p):
+ if f.ring.domain.is_ZZ:
+ terms = []
+
+ for monom, coeff in f.iterterms():
+ coeff = coeff % p
+
+ if coeff > p // 2:
+ coeff = coeff - p
+
+ terms.append((monom, coeff))
+ else:
+ terms = [ (monom, coeff % p) for monom, coeff in f.iterterms() ]
+
+ poly = f.new(terms)
+ poly.strip_zero()
+ return poly
+
+ rem_ground = trunc_ground
+
+ def extract_ground(self, g):
+ f = self
+ fc = f.content()
+ gc = g.content()
+
+ gcd = f.ring.domain.gcd(fc, gc)
+
+ f = f.quo_ground(gcd)
+ g = g.quo_ground(gcd)
+
+ return gcd, f, g
+
+ def _norm(f, norm_func):
+ if not f:
+ return f.ring.domain.zero
+ else:
+ ground_abs = f.ring.domain.abs
+ return norm_func([ ground_abs(coeff) for coeff in f.itercoeffs() ])
+
+ def max_norm(f):
+ return f._norm(max)
+
+ def l1_norm(f):
+ return f._norm(sum)
+
+ def deflate(f, *G):
+ ring = f.ring
+ polys = [f] + list(G)
+
+ J = [0]*ring.ngens
+
+ for p in polys:
+ for monom in p.itermonoms():
+ for i, m in enumerate(monom):
+ J[i] = igcd(J[i], m)
+
+ for i, b in enumerate(J):
+ if not b:
+ J[i] = 1
+
+ J = tuple(J)
+
+ if all(b == 1 for b in J):
+ return J, polys
+
+ H = []
+
+ for p in polys:
+ h = ring.zero
+
+ for I, coeff in p.iterterms():
+ N = [ i // j for i, j in zip(I, J) ]
+ h[tuple(N)] = coeff
+
+ H.append(h)
+
+ return J, H
+
+ def inflate(f, J):
+ poly = f.ring.zero
+
+ for I, coeff in f.iterterms():
+ N = [ i*j for i, j in zip(I, J) ]
+ poly[tuple(N)] = coeff
+
+ return poly
+
+ def lcm(self, g):
+ f = self
+ domain = f.ring.domain
+
+ if not domain.is_Field:
+ fc, f = f.primitive()
+ gc, g = g.primitive()
+ c = domain.lcm(fc, gc)
+
+ h = (f*g).quo(f.gcd(g))
+
+ if not domain.is_Field:
+ return h.mul_ground(c)
+ else:
+ return h.monic()
+
+ def gcd(f, g):
+ return f.cofactors(g)[0]
+
+ def cofactors(f, g):
+ if not f and not g:
+ zero = f.ring.zero
+ return zero, zero, zero
+ elif not f:
+ h, cff, cfg = f._gcd_zero(g)
+ return h, cff, cfg
+ elif not g:
+ h, cfg, cff = g._gcd_zero(f)
+ return h, cff, cfg
+ elif len(f) == 1:
+ h, cff, cfg = f._gcd_monom(g)
+ return h, cff, cfg
+ elif len(g) == 1:
+ h, cfg, cff = g._gcd_monom(f)
+ return h, cff, cfg
+
+ J, (f, g) = f.deflate(g)
+ h, cff, cfg = f._gcd(g)
+
+ return (h.inflate(J), cff.inflate(J), cfg.inflate(J))
+
+ def _gcd_zero(f, g):
+ one, zero = f.ring.one, f.ring.zero
+ if g.is_nonnegative:
+ return g, zero, one
+ else:
+ return -g, zero, -one
+
+ def _gcd_monom(f, g):
+ ring = f.ring
+ ground_gcd = ring.domain.gcd
+ ground_quo = ring.domain.quo
+ monomial_gcd = ring.monomial_gcd
+ monomial_ldiv = ring.monomial_ldiv
+ mf, cf = list(f.iterterms())[0]
+ _mgcd, _cgcd = mf, cf
+ for mg, cg in g.iterterms():
+ _mgcd = monomial_gcd(_mgcd, mg)
+ _cgcd = ground_gcd(_cgcd, cg)
+ h = f.new([(_mgcd, _cgcd)])
+ cff = f.new([(monomial_ldiv(mf, _mgcd), ground_quo(cf, _cgcd))])
+ cfg = f.new([(monomial_ldiv(mg, _mgcd), ground_quo(cg, _cgcd)) for mg, cg in g.iterterms()])
+ return h, cff, cfg
+
+ def _gcd(f, g):
+ ring = f.ring
+
+ if ring.domain.is_QQ:
+ return f._gcd_QQ(g)
+ elif ring.domain.is_ZZ:
+ return f._gcd_ZZ(g)
+ else: # TODO: don't use dense representation (port PRS algorithms)
+ return ring.dmp_inner_gcd(f, g)
+
+ def _gcd_ZZ(f, g):
+ return heugcd(f, g)
+
+ def _gcd_QQ(self, g):
+ f = self
+ ring = f.ring
+ new_ring = ring.clone(domain=ring.domain.get_ring())
+
+ cf, f = f.clear_denoms()
+ cg, g = g.clear_denoms()
+
+ f = f.set_ring(new_ring)
+ g = g.set_ring(new_ring)
+
+ h, cff, cfg = f._gcd_ZZ(g)
+
+ h = h.set_ring(ring)
+ c, h = h.LC, h.monic()
+
+ cff = cff.set_ring(ring).mul_ground(ring.domain.quo(c, cf))
+ cfg = cfg.set_ring(ring).mul_ground(ring.domain.quo(c, cg))
+
+ return h, cff, cfg
+
+ def cancel(self, g):
+ """
+ Cancel common factors in a rational function ``f/g``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> (2*x**2 - 2).cancel(x**2 - 2*x + 1)
+ (2*x + 2, x - 1)
+
+ """
+ f = self
+ ring = f.ring
+
+ if not f:
+ return f, ring.one
+
+ domain = ring.domain
+
+ if not (domain.is_Field and domain.has_assoc_Ring):
+ _, p, q = f.cofactors(g)
+ else:
+ new_ring = ring.clone(domain=domain.get_ring())
+
+ cq, f = f.clear_denoms()
+ cp, g = g.clear_denoms()
+
+ f = f.set_ring(new_ring)
+ g = g.set_ring(new_ring)
+
+ _, p, q = f.cofactors(g)
+ _, cp, cq = new_ring.domain.cofactors(cp, cq)
+
+ p = p.set_ring(ring)
+ q = q.set_ring(ring)
+
+ p = p.mul_ground(cp)
+ q = q.mul_ground(cq)
+
+ # Make canonical with respect to sign or quadrant in the case of ZZ_I
+ # or QQ_I. This ensures that the LC of the denominator is canonical by
+ # multiplying top and bottom by a unit of the ring.
+ u = q.canonical_unit()
+ if u == domain.one:
+ p, q = p, q
+ elif u == -domain.one:
+ p, q = -p, -q
+ else:
+ p = p.mul_ground(u)
+ q = q.mul_ground(u)
+
+ return p, q
+
+ def canonical_unit(f):
+ domain = f.ring.domain
+ return domain.canonical_unit(f.LC)
+
+ def diff(f, x):
+ """Computes partial derivative in ``x``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+
+ >>> _, x, y = ring("x,y", ZZ)
+ >>> p = x + x**2*y**3
+ >>> p.diff(x)
+ 2*x*y**3 + 1
+
+ """
+ ring = f.ring
+ i = ring.index(x)
+ m = ring.monomial_basis(i)
+ g = ring.zero
+ for expv, coeff in f.iterterms():
+ if expv[i]:
+ e = ring.monomial_ldiv(expv, m)
+ g[e] = ring.domain_new(coeff*expv[i])
+ return g
+
+ def __call__(f, *values):
+ if 0 < len(values) <= f.ring.ngens:
+ return f.evaluate(list(zip(f.ring.gens, values)))
+ else:
+ raise ValueError("expected at least 1 and at most %s values, got %s" % (f.ring.ngens, len(values)))
+
+ def evaluate(self, x, a=None):
+ f = self
+
+ if isinstance(x, list) and a is None:
+ (X, a), x = x[0], x[1:]
+ f = f.evaluate(X, a)
+
+ if not x:
+ return f
+ else:
+ x = [ (Y.drop(X), a) for (Y, a) in x ]
+ return f.evaluate(x)
+
+ ring = f.ring
+ i = ring.index(x)
+ a = ring.domain.convert(a)
+
+ if ring.ngens == 1:
+ result = ring.domain.zero
+
+ for (n,), coeff in f.iterterms():
+ result += coeff*a**n
+
+ return result
+ else:
+ poly = ring.drop(x).zero
+
+ for monom, coeff in f.iterterms():
+ n, monom = monom[i], monom[:i] + monom[i+1:]
+ coeff = coeff*a**n
+
+ if monom in poly:
+ coeff = coeff + poly[monom]
+
+ if coeff:
+ poly[monom] = coeff
+ else:
+ del poly[monom]
+ else:
+ if coeff:
+ poly[monom] = coeff
+
+ return poly
+
+ def subs(self, x, a=None):
+ f = self
+
+ if isinstance(x, list) and a is None:
+ for X, a in x:
+ f = f.subs(X, a)
+ return f
+
+ ring = f.ring
+ i = ring.index(x)
+ a = ring.domain.convert(a)
+
+ if ring.ngens == 1:
+ result = ring.domain.zero
+
+ for (n,), coeff in f.iterterms():
+ result += coeff*a**n
+
+ return ring.ground_new(result)
+ else:
+ poly = ring.zero
+
+ for monom, coeff in f.iterterms():
+ n, monom = monom[i], monom[:i] + (0,) + monom[i+1:]
+ coeff = coeff*a**n
+
+ if monom in poly:
+ coeff = coeff + poly[monom]
+
+ if coeff:
+ poly[monom] = coeff
+ else:
+ del poly[monom]
+ else:
+ if coeff:
+ poly[monom] = coeff
+
+ return poly
+
+ def symmetrize(self):
+ r"""
+ Rewrite *self* in terms of elementary symmetric polynomials.
+
+ Explanation
+ ===========
+
+ If this :py:class:`~.PolyElement` belongs to a ring of $n$ variables,
+ we can try to write it as a function of the elementary symmetric
+ polynomials on $n$ variables. We compute a symmetric part, and a
+ remainder for any part we were not able to symmetrize.
+
+ Examples
+ ========
+
+ >>> from sympy.polys.rings import ring
+ >>> from sympy.polys.domains import ZZ
+ >>> R, x, y = ring("x,y", ZZ)
+
+ >>> f = x**2 + y**2
+ >>> f.symmetrize()
+ (x**2 - 2*y, 0, [(x, x + y), (y, x*y)])
+
+ >>> f = x**2 - y**2
+ >>> f.symmetrize()
+ (x**2 - 2*y, -2*y**2, [(x, x + y), (y, x*y)])
+
+ Returns
+ =======
+
+ Triple ``(p, r, m)``
+ ``p`` is a :py:class:`~.PolyElement` that represents our attempt
+ to express *self* as a function of elementary symmetric
+ polynomials. Each variable in ``p`` stands for one of the
+ elementary symmetric polynomials. The correspondence is given
+ by ``m``.
+
+ ``r`` is the remainder.
+
+ ``m`` is a list of pairs, giving the mapping from variables in
+ ``p`` to elementary symmetric polynomials.
+
+ The triple satisfies the equation ``p.compose(m) + r == self``.
+ If the remainder ``r`` is zero, *self* is symmetric. If it is
+ nonzero, we were not able to represent *self* as symmetric.
+
+ See Also
+ ========
+
+ sympy.polys.polyfuncs.symmetrize
+
+ References
+ ==========
+
+ .. [1] Lauer, E. Algorithms for symmetrical polynomials, Proc. 1976
+ ACM Symp. on Symbolic and Algebraic Computing, NY 242-247.
+ https://dl.acm.org/doi/pdf/10.1145/800205.806342
+
+ """
+ f = self.copy()
+ ring = f.ring
+ n = ring.ngens
+
+ if not n:
+ return f, ring.zero, []
+
+ polys = [ring.symmetric_poly(i+1) for i in range(n)]
+
+ poly_powers = {}
+ def get_poly_power(i, n):
+ if (i, n) not in poly_powers:
+ poly_powers[(i, n)] = polys[i]**n
+ return poly_powers[(i, n)]
+
+ indices = list(range(n - 1))
+ weights = list(range(n, 0, -1))
+
+ symmetric = ring.zero
+
+ while f:
+ _height, _monom, _coeff = -1, None, None
+
+ for i, (monom, coeff) in enumerate(f.terms()):
+ if all(monom[i] >= monom[i + 1] for i in indices):
+ height = max(n*m for n, m in zip(weights, monom))
+
+ if height > _height:
+ _height, _monom, _coeff = height, monom, coeff
+
+ if _height != -1:
+ monom, coeff = _monom, _coeff
+ else:
+ break
+
+ exponents = []
+ for m1, m2 in zip(monom, monom[1:] + (0,)):
+ exponents.append(m1 - m2)
+
+ symmetric += ring.term_new(tuple(exponents), coeff)
+
+ product = coeff
+ for i, n in enumerate(exponents):
+ product *= get_poly_power(i, n)
+ f -= product
+
+ mapping = list(zip(ring.gens, polys))
+
+ return symmetric, f, mapping
+
+ def compose(f, x, a=None):
+ ring = f.ring
+ poly = ring.zero
+ gens_map = dict(zip(ring.gens, range(ring.ngens)))
+
+ if a is not None:
+ replacements = [(x, a)]
+ else:
+ if isinstance(x, list):
+ replacements = list(x)
+ elif isinstance(x, dict):
+ replacements = sorted(x.items(), key=lambda k: gens_map[k[0]])
+ else:
+ raise ValueError("expected a generator, value pair a sequence of such pairs")
+
+ for k, (x, g) in enumerate(replacements):
+ replacements[k] = (gens_map[x], ring.ring_new(g))
+
+ for monom, coeff in f.iterterms():
+ monom = list(monom)
+ subpoly = ring.one
+
+ for i, g in replacements:
+ n, monom[i] = monom[i], 0
+ if n:
+ subpoly *= g**n
+
+ subpoly = subpoly.mul_term((tuple(monom), coeff))
+ poly += subpoly
+
+ return poly
+
+ def coeff_wrt(self, x, deg):
+ """
+ Coefficient of ``self`` with respect to ``x**deg``.
+
+ Treating ``self`` as a univariate polynomial in ``x`` this finds the
+ coefficient of ``x**deg`` as a polynomial in the other generators.
+
+ Parameters
+ ==========
+
+ x : generator or generator index
+ The generator or generator index to compute the expression for.
+ deg : int
+ The degree of the monomial to compute the expression for.
+
+ Returns
+ =======
+
+ :py:class:`~.PolyElement`
+ The coefficient of ``x**deg`` as a polynomial in the same ring.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x, y, z = ring("x, y, z", ZZ)
+
+ >>> p = 2*x**4 + 3*y**4 + 10*z**2 + 10*x*z**2
+ >>> deg = 2
+ >>> p.coeff_wrt(2, deg) # Using the generator index
+ 10*x + 10
+ >>> p.coeff_wrt(z, deg) # Using the generator
+ 10*x + 10
+ >>> p.coeff(z**2) # shows the difference between coeff and coeff_wrt
+ 10
+
+ See Also
+ ========
+
+ coeff, coeffs
+
+ """
+ p = self
+ i = p.ring.index(x)
+ terms = [(m, c) for m, c in p.iterterms() if m[i] == deg]
+
+ if not terms:
+ return p.ring.zero
+
+ monoms, coeffs = zip(*terms)
+ monoms = [m[:i] + (0,) + m[i + 1:] for m in monoms]
+ return p.ring.from_dict(dict(zip(monoms, coeffs)))
+
+ def prem(self, g, x=None):
+ """
+ Pseudo-remainder of the polynomial ``self`` with respect to ``g``.
+
+ The pseudo-quotient ``q`` and pseudo-remainder ``r`` with respect to
+ ``z`` when dividing ``f`` by ``g`` satisfy ``m*f = g*q + r``,
+ where ``deg(r,z) < deg(g,z)`` and
+ ``m = LC(g,z)**(deg(f,z) - deg(g,z)+1)``.
+
+ See :meth:`pdiv` for explanation of pseudo-division.
+
+
+ Parameters
+ ==========
+
+ g : :py:class:`~.PolyElement`
+ The polynomial to divide ``self`` by.
+ x : generator or generator index, optional
+ The main variable of the polynomials and default is first generator.
+
+ Returns
+ =======
+
+ :py:class:`~.PolyElement`
+ The pseudo-remainder polynomial.
+
+ Raises
+ ======
+
+ ZeroDivisionError : If ``g`` is the zero polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x, y = ring("x, y", ZZ)
+
+ >>> f = x**2 + x*y
+ >>> g = 2*x + 2
+ >>> f.prem(g) # first generator is chosen by default if it is not given
+ -4*y + 4
+ >>> f.rem(g) # shows the differnce between prem and rem
+ x**2 + x*y
+ >>> f.prem(g, y) # generator is given
+ 0
+ >>> f.prem(g, 1) # generator index is given
+ 0
+
+ See Also
+ ========
+
+ pdiv, pquo, pexquo, sympy.polys.domains.ring.Ring.rem
+
+ """
+ f = self
+ x = f.ring.index(x)
+ df = f.degree(x)
+ dg = g.degree(x)
+
+ if dg < 0:
+ raise ZeroDivisionError('polynomial division')
+
+ r, dr = f, df
+
+ if df < dg:
+ return r
+
+ N = df - dg + 1
+
+ lc_g = g.coeff_wrt(x, dg)
+
+ xp = f.ring.gens[x]
+
+ while True:
+
+ lc_r = r.coeff_wrt(x, dr)
+ j, N = dr - dg, N - 1
+
+ R = r * lc_g
+ G = g * lc_r * xp**j
+ r = R - G
+
+ dr = r.degree(x)
+
+ if dr < dg:
+ break
+
+ c = lc_g ** N
+
+ return r * c
+
+ def pdiv(self, g, x=None):
+ """
+ Computes the pseudo-division of the polynomial ``self`` with respect to ``g``.
+
+ The pseudo-division algorithm is used to find the pseudo-quotient ``q``
+ and pseudo-remainder ``r`` such that ``m*f = g*q + r``, where ``m``
+ represents the multiplier and ``f`` is the dividend polynomial.
+
+ The pseudo-quotient ``q`` and pseudo-remainder ``r`` are polynomials in
+ the variable ``x``, with the degree of ``r`` with respect to ``x``
+ being strictly less than the degree of ``g`` with respect to ``x``.
+
+ The multiplier ``m`` is defined as
+ ``LC(g, x) ^ (deg(f, x) - deg(g, x) + 1)``,
+ where ``LC(g, x)`` represents the leading coefficient of ``g``.
+
+ It is important to note that in the context of the ``prem`` method,
+ multivariate polynomials in a ring, such as ``R[x,y,z]``, are treated
+ as univariate polynomials with coefficients that are polynomials,
+ such as ``R[x,y][z]``. When dividing ``f`` by ``g`` with respect to the
+ variable ``z``, the pseudo-quotient ``q`` and pseudo-remainder ``r``
+ satisfy ``m*f = g*q + r``, where ``deg(r, z) < deg(g, z)``
+ and ``m = LC(g, z)^(deg(f, z) - deg(g, z) + 1)``.
+
+ In this function, the pseudo-remainder ``r`` can be obtained using the
+ ``prem`` method, the pseudo-quotient ``q`` can
+ be obtained using the ``pquo`` method, and
+ the function ``pdiv`` itself returns a tuple ``(q, r)``.
+
+
+ Parameters
+ ==========
+
+ g : :py:class:`~.PolyElement`
+ The polynomial to divide ``self`` by.
+ x : generator or generator index, optional
+ The main variable of the polynomials and default is first generator.
+
+ Returns
+ =======
+
+ :py:class:`~.PolyElement`
+ The pseudo-division polynomial (tuple of ``q`` and ``r``).
+
+ Raises
+ ======
+
+ ZeroDivisionError : If ``g`` is the zero polynomial.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x, y = ring("x, y", ZZ)
+
+ >>> f = x**2 + x*y
+ >>> g = 2*x + 2
+ >>> f.pdiv(g) # first generator is chosen by default if it is not given
+ (2*x + 2*y - 2, -4*y + 4)
+ >>> f.div(g) # shows the difference between pdiv and div
+ (0, x**2 + x*y)
+ >>> f.pdiv(g, y) # generator is given
+ (2*x**3 + 2*x**2*y + 6*x**2 + 2*x*y + 8*x + 4, 0)
+ >>> f.pdiv(g, 1) # generator index is given
+ (2*x**3 + 2*x**2*y + 6*x**2 + 2*x*y + 8*x + 4, 0)
+
+ See Also
+ ========
+
+ prem
+ Computes only the pseudo-remainder more efficiently than
+ `f.pdiv(g)[1]`.
+ pquo
+ Returns only the pseudo-quotient.
+ pexquo
+ Returns only an exact pseudo-quotient having no remainder.
+ div
+ Returns quotient and remainder of f and g polynomials.
+
+ """
+ f = self
+ x = f.ring.index(x)
+
+ df = f.degree(x)
+ dg = g.degree(x)
+
+ if dg < 0:
+ raise ZeroDivisionError("polynomial division")
+
+ q, r, dr = x, f, df
+
+ if df < dg:
+ return q, r
+
+ N = df - dg + 1
+ lc_g = g.coeff_wrt(x, dg)
+
+ xp = f.ring.gens[x]
+
+ while True:
+
+ lc_r = r.coeff_wrt(x, dr)
+ j, N = dr - dg, N - 1
+
+ Q = q * lc_g
+
+ q = Q + (lc_r)*xp**j
+
+ R = r * lc_g
+
+ G = g * lc_r * xp**j
+
+ r = R - G
+
+ dr = r.degree(x)
+
+ if dr < dg:
+ break
+
+ c = lc_g**N
+
+ q = q * c
+ r = r * c
+
+ return q, r
+
+ def pquo(self, g, x=None):
+ """
+ Polynomial pseudo-quotient in multivariate polynomial ring.
+
+ Examples
+ ========
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x**2 + x*y
+ >>> g = 2*x + 2*y
+ >>> h = 2*x + 2
+ >>> f.pquo(g)
+ 2*x
+ >>> f.quo(g) # shows the difference between pquo and quo
+ 0
+ >>> f.pquo(h)
+ 2*x + 2*y - 2
+ >>> f.quo(h) # shows the difference between pquo and quo
+ 0
+
+ See Also
+ ========
+
+ prem, pdiv, pexquo, sympy.polys.domains.ring.Ring.quo
+
+ """
+ f = self
+ return f.pdiv(g, x)[0]
+
+ def pexquo(self, g, x=None):
+ """
+ Polynomial exact pseudo-quotient in multivariate polynomial ring.
+
+ Examples
+ ========
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x**2 + x*y
+ >>> g = 2*x + 2*y
+ >>> h = 2*x + 2
+ >>> f.pexquo(g)
+ 2*x
+ >>> f.exquo(g) # shows the differnce between pexquo and exquo
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: 2*x + 2*y does not divide x**2 + x*y
+ >>> f.pexquo(h)
+ Traceback (most recent call last):
+ ...
+ ExactQuotientFailed: 2*x + 2 does not divide x**2 + x*y
+
+ See Also
+ ========
+
+ prem, pdiv, pquo, sympy.polys.domains.ring.Ring.exquo
+
+ """
+ f = self
+ q, r = f.pdiv(g, x)
+
+ if r.is_zero:
+ return q
+ else:
+ raise ExactQuotientFailed(f, g)
+
+ def subresultants(self, g, x=None):
+ """
+ Computes the subresultant PRS of two polynomials ``self`` and ``g``.
+
+ Parameters
+ ==========
+
+ g : :py:class:`~.PolyElement`
+ The second polynomial.
+ x : generator or generator index
+ The variable with respect to which the subresultant sequence is computed.
+
+ Returns
+ =======
+
+ R : list
+ Returns a list polynomials representing the subresultant PRS.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x, y = ring("x, y", ZZ)
+
+ >>> f = x**2*y + x*y
+ >>> g = x + y
+ >>> f.subresultants(g) # first generator is chosen by default if not given
+ [x**2*y + x*y, x + y, y**3 - y**2]
+ >>> f.subresultants(g, 0) # generator index is given
+ [x**2*y + x*y, x + y, y**3 - y**2]
+ >>> f.subresultants(g, y) # generator is given
+ [x**2*y + x*y, x + y, x**3 + x**2]
+
+ """
+ f = self
+ x = f.ring.index(x)
+ n = f.degree(x)
+ m = g.degree(x)
+
+ if n < m:
+ f, g = g, f
+ n, m = m, n
+
+ if f == 0:
+ return [0, 0]
+
+ if g == 0:
+ return [f, 1]
+
+ R = [f, g]
+
+ d = n - m
+ b = (-1) ** (d + 1)
+
+ # Compute the pseudo-remainder for f and g
+ h = f.prem(g, x)
+ h = h * b
+
+ # Compute the coefficient of g with respect to x**m
+ lc = g.coeff_wrt(x, m)
+
+ c = lc ** d
+
+ S = [1, c]
+
+ c = -c
+
+ while h:
+ k = h.degree(x)
+
+ R.append(h)
+ f, g, m, d = g, h, k, m - k
+
+ b = -lc * c ** d
+ h = f.prem(g, x)
+ h = h.exquo(b)
+
+ lc = g.coeff_wrt(x, k)
+
+ if d > 1:
+ p = (-lc) ** d
+ q = c ** (d - 1)
+ c = p.exquo(q)
+ else:
+ c = -lc
+
+ S.append(-c)
+
+ return R
+
+ # TODO: following methods should point to polynomial
+ # representation independent algorithm implementations.
+
+ def half_gcdex(f, g):
+ return f.ring.dmp_half_gcdex(f, g)
+
+ def gcdex(f, g):
+ return f.ring.dmp_gcdex(f, g)
+
+ def resultant(f, g):
+ return f.ring.dmp_resultant(f, g)
+
+ def discriminant(f):
+ return f.ring.dmp_discriminant(f)
+
+ def decompose(f):
+ if f.ring.is_univariate:
+ return f.ring.dup_decompose(f)
+ else:
+ raise MultivariatePolynomialError("polynomial decomposition")
+
+ def shift(f, a):
+ if f.ring.is_univariate:
+ return f.ring.dup_shift(f, a)
+ else:
+ raise MultivariatePolynomialError("shift: use shift_list instead")
+
+ def shift_list(f, a):
+ return f.ring.dmp_shift(f, a)
+
+ def sturm(f):
+ if f.ring.is_univariate:
+ return f.ring.dup_sturm(f)
+ else:
+ raise MultivariatePolynomialError("sturm sequence")
+
+ def gff_list(f):
+ return f.ring.dmp_gff_list(f)
+
+ def norm(f):
+ return f.ring.dmp_norm(f)
+
+ def sqf_norm(f):
+ return f.ring.dmp_sqf_norm(f)
+
+ def sqf_part(f):
+ return f.ring.dmp_sqf_part(f)
+
+ def sqf_list(f, all=False):
+ return f.ring.dmp_sqf_list(f, all=all)
+
+ def factor_list(f):
+ return f.ring.dmp_factor_list(f)
diff --git a/lib/python3.12/site-packages/sympy/polys/rootisolation.py b/lib/python3.12/site-packages/sympy/polys/rootisolation.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb50044fc301b55079abc98e5811d4596515ead2
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/rootisolation.py
@@ -0,0 +1,2196 @@
+"""Real and complex root isolation and refinement algorithms. """
+
+
+from sympy.polys.densearith import (
+ dup_neg, dup_rshift, dup_rem,
+ dup_l2_norm_squared)
+from sympy.polys.densebasic import (
+ dup_LC, dup_TC, dup_degree,
+ dup_strip, dup_reverse,
+ dup_convert,
+ dup_terms_gcd)
+from sympy.polys.densetools import (
+ dup_clear_denoms,
+ dup_mirror, dup_scale, dup_shift,
+ dup_transform,
+ dup_diff,
+ dup_eval, dmp_eval_in,
+ dup_sign_variations,
+ dup_real_imag)
+from sympy.polys.euclidtools import (
+ dup_discriminant)
+from sympy.polys.factortools import (
+ dup_factor_list)
+from sympy.polys.polyerrors import (
+ RefinementFailed,
+ DomainError,
+ PolynomialError)
+from sympy.polys.sqfreetools import (
+ dup_sqf_part, dup_sqf_list)
+
+
+def dup_sturm(f, K):
+ """
+ Computes the Sturm sequence of ``f`` in ``F[x]``.
+
+ Given a univariate, square-free polynomial ``f(x)`` returns the
+ associated Sturm sequence ``f_0(x), ..., f_n(x)`` defined by::
+
+ f_0(x), f_1(x) = f(x), f'(x)
+ f_n = -rem(f_{n-2}(x), f_{n-1}(x))
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, QQ
+ >>> R, x = ring("x", QQ)
+
+ >>> R.dup_sturm(x**3 - 2*x**2 + x - 3)
+ [x**3 - 2*x**2 + x - 3, 3*x**2 - 4*x + 1, 2/9*x + 25/9, -2079/4]
+
+ References
+ ==========
+
+ .. [1] [Davenport88]_
+
+ """
+ if not K.is_Field:
+ raise DomainError("Cannot compute Sturm sequence over %s" % K)
+
+ f = dup_sqf_part(f, K)
+
+ sturm = [f, dup_diff(f, 1, K)]
+
+ while sturm[-1]:
+ s = dup_rem(sturm[-2], sturm[-1], K)
+ sturm.append(dup_neg(s, K))
+
+ return sturm[:-1]
+
+def dup_root_upper_bound(f, K):
+ """Compute the LMQ upper bound for the positive roots of `f`;
+ LMQ (Local Max Quadratic) was developed by Akritas-Strzebonski-Vigklas.
+
+ References
+ ==========
+ .. [1] Alkiviadis G. Akritas: "Linear and Quadratic Complexity Bounds on the
+ Values of the Positive Roots of Polynomials"
+ Journal of Universal Computer Science, Vol. 15, No. 3, 523-537, 2009.
+ """
+ n, P = len(f), []
+ t = n * [K.one]
+ if dup_LC(f, K) < 0:
+ f = dup_neg(f, K)
+ f = list(reversed(f))
+
+ for i in range(0, n):
+ if f[i] >= 0:
+ continue
+
+ a, QL = K.log(-f[i], 2), []
+
+ for j in range(i + 1, n):
+
+ if f[j] <= 0:
+ continue
+
+ q = t[j] + a - K.log(f[j], 2)
+ QL.append([q // (j - i), j])
+
+ if not QL:
+ continue
+
+ q = min(QL)
+
+ t[q[1]] = t[q[1]] + 1
+
+ P.append(q[0])
+
+ if not P:
+ return None
+ else:
+ return K.get_field()(2)**(max(P) + 1)
+
+def dup_root_lower_bound(f, K):
+ """Compute the LMQ lower bound for the positive roots of `f`;
+ LMQ (Local Max Quadratic) was developed by Akritas-Strzebonski-Vigklas.
+
+ References
+ ==========
+ .. [1] Alkiviadis G. Akritas: "Linear and Quadratic Complexity Bounds on the
+ Values of the Positive Roots of Polynomials"
+ Journal of Universal Computer Science, Vol. 15, No. 3, 523-537, 2009.
+ """
+ bound = dup_root_upper_bound(dup_reverse(f), K)
+
+ if bound is not None:
+ return 1/bound
+ else:
+ return None
+
+def dup_cauchy_upper_bound(f, K):
+ """
+ Compute the Cauchy upper bound on the absolute value of all roots of f,
+ real or complex.
+
+ References
+ ==========
+ .. [1] https://en.wikipedia.org/wiki/Geometrical_properties_of_polynomial_roots#Lagrange's_and_Cauchy's_bounds
+ """
+ n = dup_degree(f)
+ if n < 1:
+ raise PolynomialError('Polynomial has no roots.')
+
+ if K.is_ZZ:
+ L = K.get_field()
+ f, K = dup_convert(f, K, L), L
+ elif not K.is_QQ or K.is_RR or K.is_CC:
+ # We need to compute absolute value, and we are not supporting cases
+ # where this would take us outside the domain (or its quotient field).
+ raise DomainError('Cauchy bound not supported over %s' % K)
+ else:
+ f = f[:]
+
+ while K.is_zero(f[-1]):
+ f.pop()
+ if len(f) == 1:
+ # Monomial. All roots are zero.
+ return K.zero
+
+ lc = f[0]
+ return K.one + max(abs(n / lc) for n in f[1:])
+
+def dup_cauchy_lower_bound(f, K):
+ """Compute the Cauchy lower bound on the absolute value of all non-zero
+ roots of f, real or complex."""
+ g = dup_reverse(f)
+ if len(g) < 2:
+ raise PolynomialError('Polynomial has no non-zero roots.')
+ if K.is_ZZ:
+ K = K.get_field()
+ b = dup_cauchy_upper_bound(g, K)
+ return K.one / b
+
+def dup_mignotte_sep_bound_squared(f, K):
+ """
+ Return the square of the Mignotte lower bound on separation between
+ distinct roots of f. The square is returned so that the bound lies in
+ K or its quotient field.
+
+ References
+ ==========
+
+ .. [1] Mignotte, Maurice. "Some useful bounds." Computer algebra.
+ Springer, Vienna, 1982. 259-263.
+ https://people.dm.unipi.it/gianni/AC-EAG/Mignotte.pdf
+ """
+ n = dup_degree(f)
+ if n < 2:
+ raise PolynomialError('Polynomials of degree < 2 have no distinct roots.')
+
+ if K.is_ZZ:
+ L = K.get_field()
+ f, K = dup_convert(f, K, L), L
+ elif not K.is_QQ or K.is_RR or K.is_CC:
+ # We need to compute absolute value, and we are not supporting cases
+ # where this would take us outside the domain (or its quotient field).
+ raise DomainError('Mignotte bound not supported over %s' % K)
+
+ D = dup_discriminant(f, K)
+ l2sq = dup_l2_norm_squared(f, K)
+ return K(3)*K.abs(D) / ( K(n)**(n+1) * l2sq**(n-1) )
+
+def _mobius_from_interval(I, field):
+ """Convert an open interval to a Mobius transform. """
+ s, t = I
+
+ a, c = field.numer(s), field.denom(s)
+ b, d = field.numer(t), field.denom(t)
+
+ return a, b, c, d
+
+def _mobius_to_interval(M, field):
+ """Convert a Mobius transform to an open interval. """
+ a, b, c, d = M
+
+ s, t = field(a, c), field(b, d)
+
+ if s <= t:
+ return (s, t)
+ else:
+ return (t, s)
+
+def dup_step_refine_real_root(f, M, K, fast=False):
+ """One step of positive real root refinement algorithm. """
+ a, b, c, d = M
+
+ if a == b and c == d:
+ return f, (a, b, c, d)
+
+ A = dup_root_lower_bound(f, K)
+
+ if A is not None:
+ A = K(int(A))
+ else:
+ A = K.zero
+
+ if fast and A > 16:
+ f = dup_scale(f, A, K)
+ a, c, A = A*a, A*c, K.one
+
+ if A >= K.one:
+ f = dup_shift(f, A, K)
+ b, d = A*a + b, A*c + d
+
+ if not dup_eval(f, K.zero, K):
+ return f, (b, b, d, d)
+
+ f, g = dup_shift(f, K.one, K), f
+
+ a1, b1, c1, d1 = a, a + b, c, c + d
+
+ if not dup_eval(f, K.zero, K):
+ return f, (b1, b1, d1, d1)
+
+ k = dup_sign_variations(f, K)
+
+ if k == 1:
+ a, b, c, d = a1, b1, c1, d1
+ else:
+ f = dup_shift(dup_reverse(g), K.one, K)
+
+ if not dup_eval(f, K.zero, K):
+ f = dup_rshift(f, 1, K)
+
+ a, b, c, d = b, a + b, d, c + d
+
+ return f, (a, b, c, d)
+
+def dup_inner_refine_real_root(f, M, K, eps=None, steps=None, disjoint=None, fast=False, mobius=False):
+ """Refine a positive root of `f` given a Mobius transform or an interval. """
+ F = K.get_field()
+
+ if len(M) == 2:
+ a, b, c, d = _mobius_from_interval(M, F)
+ else:
+ a, b, c, d = M
+
+ while not c:
+ f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c,
+ d), K, fast=fast)
+
+ if eps is not None and steps is not None:
+ for i in range(0, steps):
+ if abs(F(a, c) - F(b, d)) >= eps:
+ f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c, d), K, fast=fast)
+ else:
+ break
+ else:
+ if eps is not None:
+ while abs(F(a, c) - F(b, d)) >= eps:
+ f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c, d), K, fast=fast)
+
+ if steps is not None:
+ for i in range(0, steps):
+ f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c, d), K, fast=fast)
+
+ if disjoint is not None:
+ while True:
+ u, v = _mobius_to_interval((a, b, c, d), F)
+
+ if v <= disjoint or disjoint <= u:
+ break
+ else:
+ f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c, d), K, fast=fast)
+
+ if not mobius:
+ return _mobius_to_interval((a, b, c, d), F)
+ else:
+ return f, (a, b, c, d)
+
+def dup_outer_refine_real_root(f, s, t, K, eps=None, steps=None, disjoint=None, fast=False):
+ """Refine a positive root of `f` given an interval `(s, t)`. """
+ a, b, c, d = _mobius_from_interval((s, t), K.get_field())
+
+ f = dup_transform(f, dup_strip([a, b]),
+ dup_strip([c, d]), K)
+
+ if dup_sign_variations(f, K) != 1:
+ raise RefinementFailed("there should be exactly one root in (%s, %s) interval" % (s, t))
+
+ return dup_inner_refine_real_root(f, (a, b, c, d), K, eps=eps, steps=steps, disjoint=disjoint, fast=fast)
+
+def dup_refine_real_root(f, s, t, K, eps=None, steps=None, disjoint=None, fast=False):
+ """Refine real root's approximating interval to the given precision. """
+ if K.is_QQ:
+ (_, f), K = dup_clear_denoms(f, K, convert=True), K.get_ring()
+ elif not K.is_ZZ:
+ raise DomainError("real root refinement not supported over %s" % K)
+
+ if s == t:
+ return (s, t)
+
+ if s > t:
+ s, t = t, s
+
+ negative = False
+
+ if s < 0:
+ if t <= 0:
+ f, s, t, negative = dup_mirror(f, K), -t, -s, True
+ else:
+ raise ValueError("Cannot refine a real root in (%s, %s)" % (s, t))
+
+ if negative and disjoint is not None:
+ if disjoint < 0:
+ disjoint = -disjoint
+ else:
+ disjoint = None
+
+ s, t = dup_outer_refine_real_root(
+ f, s, t, K, eps=eps, steps=steps, disjoint=disjoint, fast=fast)
+
+ if negative:
+ return (-t, -s)
+ else:
+ return ( s, t)
+
+def dup_inner_isolate_real_roots(f, K, eps=None, fast=False):
+ """Internal function for isolation positive roots up to given precision.
+
+ References
+ ==========
+ 1. Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative Study of Two Real Root
+ Isolation Methods . Nonlinear Analysis: Modelling and Control, Vol. 10, No. 4, 297-304, 2005.
+ 2. Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S. Vigklas: Improving the
+ Performance of the Continued Fractions Method Using new Bounds of Positive Roots. Nonlinear
+ Analysis: Modelling and Control, Vol. 13, No. 3, 265-279, 2008.
+ """
+ a, b, c, d = K.one, K.zero, K.zero, K.one
+
+ k = dup_sign_variations(f, K)
+
+ if k == 0:
+ return []
+ if k == 1:
+ roots = [dup_inner_refine_real_root(
+ f, (a, b, c, d), K, eps=eps, fast=fast, mobius=True)]
+ else:
+ roots, stack = [], [(a, b, c, d, f, k)]
+
+ while stack:
+ a, b, c, d, f, k = stack.pop()
+
+ A = dup_root_lower_bound(f, K)
+
+ if A is not None:
+ A = K(int(A))
+ else:
+ A = K.zero
+
+ if fast and A > 16:
+ f = dup_scale(f, A, K)
+ a, c, A = A*a, A*c, K.one
+
+ if A >= K.one:
+ f = dup_shift(f, A, K)
+ b, d = A*a + b, A*c + d
+
+ if not dup_TC(f, K):
+ roots.append((f, (b, b, d, d)))
+ f = dup_rshift(f, 1, K)
+
+ k = dup_sign_variations(f, K)
+
+ if k == 0:
+ continue
+ if k == 1:
+ roots.append(dup_inner_refine_real_root(
+ f, (a, b, c, d), K, eps=eps, fast=fast, mobius=True))
+ continue
+
+ f1 = dup_shift(f, K.one, K)
+
+ a1, b1, c1, d1, r = a, a + b, c, c + d, 0
+
+ if not dup_TC(f1, K):
+ roots.append((f1, (b1, b1, d1, d1)))
+ f1, r = dup_rshift(f1, 1, K), 1
+
+ k1 = dup_sign_variations(f1, K)
+ k2 = k - k1 - r
+
+ a2, b2, c2, d2 = b, a + b, d, c + d
+
+ if k2 > 1:
+ f2 = dup_shift(dup_reverse(f), K.one, K)
+
+ if not dup_TC(f2, K):
+ f2 = dup_rshift(f2, 1, K)
+
+ k2 = dup_sign_variations(f2, K)
+ else:
+ f2 = None
+
+ if k1 < k2:
+ a1, a2, b1, b2 = a2, a1, b2, b1
+ c1, c2, d1, d2 = c2, c1, d2, d1
+ f1, f2, k1, k2 = f2, f1, k2, k1
+
+ if not k1:
+ continue
+
+ if f1 is None:
+ f1 = dup_shift(dup_reverse(f), K.one, K)
+
+ if not dup_TC(f1, K):
+ f1 = dup_rshift(f1, 1, K)
+
+ if k1 == 1:
+ roots.append(dup_inner_refine_real_root(
+ f1, (a1, b1, c1, d1), K, eps=eps, fast=fast, mobius=True))
+ else:
+ stack.append((a1, b1, c1, d1, f1, k1))
+
+ if not k2:
+ continue
+
+ if f2 is None:
+ f2 = dup_shift(dup_reverse(f), K.one, K)
+
+ if not dup_TC(f2, K):
+ f2 = dup_rshift(f2, 1, K)
+
+ if k2 == 1:
+ roots.append(dup_inner_refine_real_root(
+ f2, (a2, b2, c2, d2), K, eps=eps, fast=fast, mobius=True))
+ else:
+ stack.append((a2, b2, c2, d2, f2, k2))
+
+ return roots
+
+def _discard_if_outside_interval(f, M, inf, sup, K, negative, fast, mobius):
+ """Discard an isolating interval if outside ``(inf, sup)``. """
+ F = K.get_field()
+
+ while True:
+ u, v = _mobius_to_interval(M, F)
+
+ if negative:
+ u, v = -v, -u
+
+ if (inf is None or u >= inf) and (sup is None or v <= sup):
+ if not mobius:
+ return u, v
+ else:
+ return f, M
+ elif (sup is not None and u > sup) or (inf is not None and v < inf):
+ return None
+ else:
+ f, M = dup_step_refine_real_root(f, M, K, fast=fast)
+
+def dup_inner_isolate_positive_roots(f, K, eps=None, inf=None, sup=None, fast=False, mobius=False):
+ """Iteratively compute disjoint positive root isolation intervals. """
+ if sup is not None and sup < 0:
+ return []
+
+ roots = dup_inner_isolate_real_roots(f, K, eps=eps, fast=fast)
+
+ F, results = K.get_field(), []
+
+ if inf is not None or sup is not None:
+ for f, M in roots:
+ result = _discard_if_outside_interval(f, M, inf, sup, K, False, fast, mobius)
+
+ if result is not None:
+ results.append(result)
+ elif not mobius:
+ for f, M in roots:
+ u, v = _mobius_to_interval(M, F)
+ results.append((u, v))
+ else:
+ results = roots
+
+ return results
+
+def dup_inner_isolate_negative_roots(f, K, inf=None, sup=None, eps=None, fast=False, mobius=False):
+ """Iteratively compute disjoint negative root isolation intervals. """
+ if inf is not None and inf >= 0:
+ return []
+
+ roots = dup_inner_isolate_real_roots(dup_mirror(f, K), K, eps=eps, fast=fast)
+
+ F, results = K.get_field(), []
+
+ if inf is not None or sup is not None:
+ for f, M in roots:
+ result = _discard_if_outside_interval(f, M, inf, sup, K, True, fast, mobius)
+
+ if result is not None:
+ results.append(result)
+ elif not mobius:
+ for f, M in roots:
+ u, v = _mobius_to_interval(M, F)
+ results.append((-v, -u))
+ else:
+ results = roots
+
+ return results
+
+def _isolate_zero(f, K, inf, sup, basis=False, sqf=False):
+ """Handle special case of CF algorithm when ``f`` is homogeneous. """
+ j, f = dup_terms_gcd(f, K)
+
+ if j > 0:
+ F = K.get_field()
+
+ if (inf is None or inf <= 0) and (sup is None or 0 <= sup):
+ if not sqf:
+ if not basis:
+ return [((F.zero, F.zero), j)], f
+ else:
+ return [((F.zero, F.zero), j, [K.one, K.zero])], f
+ else:
+ return [(F.zero, F.zero)], f
+
+ return [], f
+
+def dup_isolate_real_roots_sqf(f, K, eps=None, inf=None, sup=None, fast=False, blackbox=False):
+ """Isolate real roots of a square-free polynomial using the Vincent-Akritas-Strzebonski (VAS) CF approach.
+
+ References
+ ==========
+ .. [1] Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative
+ Study of Two Real Root Isolation Methods. Nonlinear Analysis:
+ Modelling and Control, Vol. 10, No. 4, 297-304, 2005.
+ .. [2] Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S.
+ Vigklas: Improving the Performance of the Continued Fractions
+ Method Using New Bounds of Positive Roots. Nonlinear Analysis:
+ Modelling and Control, Vol. 13, No. 3, 265-279, 2008.
+
+ """
+ if K.is_QQ:
+ (_, f), K = dup_clear_denoms(f, K, convert=True), K.get_ring()
+ elif not K.is_ZZ:
+ raise DomainError("isolation of real roots not supported over %s" % K)
+
+ if dup_degree(f) <= 0:
+ return []
+
+ I_zero, f = _isolate_zero(f, K, inf, sup, basis=False, sqf=True)
+
+ I_neg = dup_inner_isolate_negative_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast)
+ I_pos = dup_inner_isolate_positive_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast)
+
+ roots = sorted(I_neg + I_zero + I_pos)
+
+ if not blackbox:
+ return roots
+ else:
+ return [ RealInterval((a, b), f, K) for (a, b) in roots ]
+
+def dup_isolate_real_roots(f, K, eps=None, inf=None, sup=None, basis=False, fast=False):
+ """Isolate real roots using Vincent-Akritas-Strzebonski (VAS) continued fractions approach.
+
+ References
+ ==========
+
+ .. [1] Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative
+ Study of Two Real Root Isolation Methods. Nonlinear Analysis:
+ Modelling and Control, Vol. 10, No. 4, 297-304, 2005.
+ .. [2] Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S.
+ Vigklas: Improving the Performance of the Continued Fractions
+ Method Using New Bounds of Positive Roots.
+ Nonlinear Analysis: Modelling and Control, Vol. 13, No. 3, 265-279, 2008.
+
+ """
+ if K.is_QQ:
+ (_, f), K = dup_clear_denoms(f, K, convert=True), K.get_ring()
+ elif not K.is_ZZ:
+ raise DomainError("isolation of real roots not supported over %s" % K)
+
+ if dup_degree(f) <= 0:
+ return []
+
+ I_zero, f = _isolate_zero(f, K, inf, sup, basis=basis, sqf=False)
+
+ _, factors = dup_sqf_list(f, K)
+
+ if len(factors) == 1:
+ ((f, k),) = factors
+
+ I_neg = dup_inner_isolate_negative_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast)
+ I_pos = dup_inner_isolate_positive_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast)
+
+ I_neg = [ ((u, v), k) for u, v in I_neg ]
+ I_pos = [ ((u, v), k) for u, v in I_pos ]
+ else:
+ I_neg, I_pos = _real_isolate_and_disjoin(factors, K,
+ eps=eps, inf=inf, sup=sup, basis=basis, fast=fast)
+
+ return sorted(I_neg + I_zero + I_pos)
+
+def dup_isolate_real_roots_list(polys, K, eps=None, inf=None, sup=None, strict=False, basis=False, fast=False):
+ """Isolate real roots of a list of square-free polynomial using Vincent-Akritas-Strzebonski (VAS) CF approach.
+
+ References
+ ==========
+
+ .. [1] Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative
+ Study of Two Real Root Isolation Methods. Nonlinear Analysis:
+ Modelling and Control, Vol. 10, No. 4, 297-304, 2005.
+ .. [2] Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S.
+ Vigklas: Improving the Performance of the Continued Fractions
+ Method Using New Bounds of Positive Roots.
+ Nonlinear Analysis: Modelling and Control, Vol. 13, No. 3, 265-279, 2008.
+
+ """
+ if K.is_QQ:
+ K, F, polys = K.get_ring(), K, polys[:]
+
+ for i, p in enumerate(polys):
+ polys[i] = dup_clear_denoms(p, F, K, convert=True)[1]
+ elif not K.is_ZZ:
+ raise DomainError("isolation of real roots not supported over %s" % K)
+
+ zeros, factors_dict = False, {}
+
+ if (inf is None or inf <= 0) and (sup is None or 0 <= sup):
+ zeros, zero_indices = True, {}
+
+ for i, p in enumerate(polys):
+ j, p = dup_terms_gcd(p, K)
+
+ if zeros and j > 0:
+ zero_indices[i] = j
+
+ for f, k in dup_factor_list(p, K)[1]:
+ f = tuple(f)
+
+ if f not in factors_dict:
+ factors_dict[f] = {i: k}
+ else:
+ factors_dict[f][i] = k
+
+ factors_list = []
+
+ for f, indices in factors_dict.items():
+ factors_list.append((list(f), indices))
+
+ I_neg, I_pos = _real_isolate_and_disjoin(factors_list, K, eps=eps,
+ inf=inf, sup=sup, strict=strict, basis=basis, fast=fast)
+
+ F = K.get_field()
+
+ if not zeros or not zero_indices:
+ I_zero = []
+ else:
+ if not basis:
+ I_zero = [((F.zero, F.zero), zero_indices)]
+ else:
+ I_zero = [((F.zero, F.zero), zero_indices, [K.one, K.zero])]
+
+ return sorted(I_neg + I_zero + I_pos)
+
+def _disjoint_p(M, N, strict=False):
+ """Check if Mobius transforms define disjoint intervals. """
+ a1, b1, c1, d1 = M
+ a2, b2, c2, d2 = N
+
+ a1d1, b1c1 = a1*d1, b1*c1
+ a2d2, b2c2 = a2*d2, b2*c2
+
+ if a1d1 == b1c1 and a2d2 == b2c2:
+ return True
+
+ if a1d1 > b1c1:
+ a1, c1, b1, d1 = b1, d1, a1, c1
+
+ if a2d2 > b2c2:
+ a2, c2, b2, d2 = b2, d2, a2, c2
+
+ if not strict:
+ return a2*d1 >= c2*b1 or b2*c1 <= d2*a1
+ else:
+ return a2*d1 > c2*b1 or b2*c1 < d2*a1
+
+def _real_isolate_and_disjoin(factors, K, eps=None, inf=None, sup=None, strict=False, basis=False, fast=False):
+ """Isolate real roots of a list of polynomials and disjoin intervals. """
+ I_pos, I_neg = [], []
+
+ for i, (f, k) in enumerate(factors):
+ for F, M in dup_inner_isolate_positive_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast, mobius=True):
+ I_pos.append((F, M, k, f))
+
+ for G, N in dup_inner_isolate_negative_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast, mobius=True):
+ I_neg.append((G, N, k, f))
+
+ for i, (f, M, k, F) in enumerate(I_pos):
+ for j, (g, N, m, G) in enumerate(I_pos[i + 1:]):
+ while not _disjoint_p(M, N, strict=strict):
+ f, M = dup_inner_refine_real_root(f, M, K, steps=1, fast=fast, mobius=True)
+ g, N = dup_inner_refine_real_root(g, N, K, steps=1, fast=fast, mobius=True)
+
+ I_pos[i + j + 1] = (g, N, m, G)
+
+ I_pos[i] = (f, M, k, F)
+
+ for i, (f, M, k, F) in enumerate(I_neg):
+ for j, (g, N, m, G) in enumerate(I_neg[i + 1:]):
+ while not _disjoint_p(M, N, strict=strict):
+ f, M = dup_inner_refine_real_root(f, M, K, steps=1, fast=fast, mobius=True)
+ g, N = dup_inner_refine_real_root(g, N, K, steps=1, fast=fast, mobius=True)
+
+ I_neg[i + j + 1] = (g, N, m, G)
+
+ I_neg[i] = (f, M, k, F)
+
+ if strict:
+ for i, (f, M, k, F) in enumerate(I_neg):
+ if not M[0]:
+ while not M[0]:
+ f, M = dup_inner_refine_real_root(f, M, K, steps=1, fast=fast, mobius=True)
+
+ I_neg[i] = (f, M, k, F)
+ break
+
+ for j, (g, N, m, G) in enumerate(I_pos):
+ if not N[0]:
+ while not N[0]:
+ g, N = dup_inner_refine_real_root(g, N, K, steps=1, fast=fast, mobius=True)
+
+ I_pos[j] = (g, N, m, G)
+ break
+
+ field = K.get_field()
+
+ I_neg = [ (_mobius_to_interval(M, field), k, f) for (_, M, k, f) in I_neg ]
+ I_pos = [ (_mobius_to_interval(M, field), k, f) for (_, M, k, f) in I_pos ]
+
+ I_neg = [((-v, -u), k, f) for ((u, v), k, f) in I_neg]
+
+ if not basis:
+ I_neg = [((u, v), k) for ((u, v), k, _) in I_neg]
+ I_pos = [((u, v), k) for ((u, v), k, _) in I_pos]
+
+ return I_neg, I_pos
+
+def dup_count_real_roots(f, K, inf=None, sup=None):
+ """Returns the number of distinct real roots of ``f`` in ``[inf, sup]``. """
+ if dup_degree(f) <= 0:
+ return 0
+
+ if not K.is_Field:
+ R, K = K, K.get_field()
+ f = dup_convert(f, R, K)
+
+ sturm = dup_sturm(f, K)
+
+ if inf is None:
+ signs_inf = dup_sign_variations([ dup_LC(s, K)*(-1)**dup_degree(s) for s in sturm ], K)
+ else:
+ signs_inf = dup_sign_variations([ dup_eval(s, inf, K) for s in sturm ], K)
+
+ if sup is None:
+ signs_sup = dup_sign_variations([ dup_LC(s, K) for s in sturm ], K)
+ else:
+ signs_sup = dup_sign_variations([ dup_eval(s, sup, K) for s in sturm ], K)
+
+ count = abs(signs_inf - signs_sup)
+
+ if inf is not None and not dup_eval(f, inf, K):
+ count += 1
+
+ return count
+
+OO = 'OO' # Origin of (re, im) coordinate system
+
+Q1 = 'Q1' # Quadrant #1 (++): re > 0 and im > 0
+Q2 = 'Q2' # Quadrant #2 (-+): re < 0 and im > 0
+Q3 = 'Q3' # Quadrant #3 (--): re < 0 and im < 0
+Q4 = 'Q4' # Quadrant #4 (+-): re > 0 and im < 0
+
+A1 = 'A1' # Axis #1 (+0): re > 0 and im = 0
+A2 = 'A2' # Axis #2 (0+): re = 0 and im > 0
+A3 = 'A3' # Axis #3 (-0): re < 0 and im = 0
+A4 = 'A4' # Axis #4 (0-): re = 0 and im < 0
+
+_rules_simple = {
+ # Q --> Q (same) => no change
+ (Q1, Q1): 0,
+ (Q2, Q2): 0,
+ (Q3, Q3): 0,
+ (Q4, Q4): 0,
+
+ # A -- CCW --> Q => +1/4 (CCW)
+ (A1, Q1): 1,
+ (A2, Q2): 1,
+ (A3, Q3): 1,
+ (A4, Q4): 1,
+
+ # A -- CW --> Q => -1/4 (CCW)
+ (A1, Q4): 2,
+ (A2, Q1): 2,
+ (A3, Q2): 2,
+ (A4, Q3): 2,
+
+ # Q -- CCW --> A => +1/4 (CCW)
+ (Q1, A2): 3,
+ (Q2, A3): 3,
+ (Q3, A4): 3,
+ (Q4, A1): 3,
+
+ # Q -- CW --> A => -1/4 (CCW)
+ (Q1, A1): 4,
+ (Q2, A2): 4,
+ (Q3, A3): 4,
+ (Q4, A4): 4,
+
+ # Q -- CCW --> Q => +1/2 (CCW)
+ (Q1, Q2): +5,
+ (Q2, Q3): +5,
+ (Q3, Q4): +5,
+ (Q4, Q1): +5,
+
+ # Q -- CW --> Q => -1/2 (CW)
+ (Q1, Q4): -5,
+ (Q2, Q1): -5,
+ (Q3, Q2): -5,
+ (Q4, Q3): -5,
+}
+
+_rules_ambiguous = {
+ # A -- CCW --> Q => { +1/4 (CCW), -9/4 (CW) }
+ (A1, OO, Q1): -1,
+ (A2, OO, Q2): -1,
+ (A3, OO, Q3): -1,
+ (A4, OO, Q4): -1,
+
+ # A -- CW --> Q => { -1/4 (CCW), +7/4 (CW) }
+ (A1, OO, Q4): -2,
+ (A2, OO, Q1): -2,
+ (A3, OO, Q2): -2,
+ (A4, OO, Q3): -2,
+
+ # Q -- CCW --> A => { +1/4 (CCW), -9/4 (CW) }
+ (Q1, OO, A2): -3,
+ (Q2, OO, A3): -3,
+ (Q3, OO, A4): -3,
+ (Q4, OO, A1): -3,
+
+ # Q -- CW --> A => { -1/4 (CCW), +7/4 (CW) }
+ (Q1, OO, A1): -4,
+ (Q2, OO, A2): -4,
+ (Q3, OO, A3): -4,
+ (Q4, OO, A4): -4,
+
+ # A -- OO --> A => { +1 (CCW), -1 (CW) }
+ (A1, A3): 7,
+ (A2, A4): 7,
+ (A3, A1): 7,
+ (A4, A2): 7,
+
+ (A1, OO, A3): 7,
+ (A2, OO, A4): 7,
+ (A3, OO, A1): 7,
+ (A4, OO, A2): 7,
+
+ # Q -- DIA --> Q => { +1 (CCW), -1 (CW) }
+ (Q1, Q3): 8,
+ (Q2, Q4): 8,
+ (Q3, Q1): 8,
+ (Q4, Q2): 8,
+
+ (Q1, OO, Q3): 8,
+ (Q2, OO, Q4): 8,
+ (Q3, OO, Q1): 8,
+ (Q4, OO, Q2): 8,
+
+ # A --- R ---> A => { +1/2 (CCW), -3/2 (CW) }
+ (A1, A2): 9,
+ (A2, A3): 9,
+ (A3, A4): 9,
+ (A4, A1): 9,
+
+ (A1, OO, A2): 9,
+ (A2, OO, A3): 9,
+ (A3, OO, A4): 9,
+ (A4, OO, A1): 9,
+
+ # A --- L ---> A => { +3/2 (CCW), -1/2 (CW) }
+ (A1, A4): 10,
+ (A2, A1): 10,
+ (A3, A2): 10,
+ (A4, A3): 10,
+
+ (A1, OO, A4): 10,
+ (A2, OO, A1): 10,
+ (A3, OO, A2): 10,
+ (A4, OO, A3): 10,
+
+ # Q --- 1 ---> A => { +3/4 (CCW), -5/4 (CW) }
+ (Q1, A3): 11,
+ (Q2, A4): 11,
+ (Q3, A1): 11,
+ (Q4, A2): 11,
+
+ (Q1, OO, A3): 11,
+ (Q2, OO, A4): 11,
+ (Q3, OO, A1): 11,
+ (Q4, OO, A2): 11,
+
+ # Q --- 2 ---> A => { +5/4 (CCW), -3/4 (CW) }
+ (Q1, A4): 12,
+ (Q2, A1): 12,
+ (Q3, A2): 12,
+ (Q4, A3): 12,
+
+ (Q1, OO, A4): 12,
+ (Q2, OO, A1): 12,
+ (Q3, OO, A2): 12,
+ (Q4, OO, A3): 12,
+
+ # A --- 1 ---> Q => { +5/4 (CCW), -3/4 (CW) }
+ (A1, Q3): 13,
+ (A2, Q4): 13,
+ (A3, Q1): 13,
+ (A4, Q2): 13,
+
+ (A1, OO, Q3): 13,
+ (A2, OO, Q4): 13,
+ (A3, OO, Q1): 13,
+ (A4, OO, Q2): 13,
+
+ # A --- 2 ---> Q => { +3/4 (CCW), -5/4 (CW) }
+ (A1, Q2): 14,
+ (A2, Q3): 14,
+ (A3, Q4): 14,
+ (A4, Q1): 14,
+
+ (A1, OO, Q2): 14,
+ (A2, OO, Q3): 14,
+ (A3, OO, Q4): 14,
+ (A4, OO, Q1): 14,
+
+ # Q --> OO --> Q => { +1/2 (CCW), -3/2 (CW) }
+ (Q1, OO, Q2): 15,
+ (Q2, OO, Q3): 15,
+ (Q3, OO, Q4): 15,
+ (Q4, OO, Q1): 15,
+
+ # Q --> OO --> Q => { +3/2 (CCW), -1/2 (CW) }
+ (Q1, OO, Q4): 16,
+ (Q2, OO, Q1): 16,
+ (Q3, OO, Q2): 16,
+ (Q4, OO, Q3): 16,
+
+ # A --> OO --> A => { +2 (CCW), 0 (CW) }
+ (A1, OO, A1): 17,
+ (A2, OO, A2): 17,
+ (A3, OO, A3): 17,
+ (A4, OO, A4): 17,
+
+ # Q --> OO --> Q => { +2 (CCW), 0 (CW) }
+ (Q1, OO, Q1): 18,
+ (Q2, OO, Q2): 18,
+ (Q3, OO, Q3): 18,
+ (Q4, OO, Q4): 18,
+}
+
+_values = {
+ 0: [( 0, 1)],
+ 1: [(+1, 4)],
+ 2: [(-1, 4)],
+ 3: [(+1, 4)],
+ 4: [(-1, 4)],
+ -1: [(+9, 4), (+1, 4)],
+ -2: [(+7, 4), (-1, 4)],
+ -3: [(+9, 4), (+1, 4)],
+ -4: [(+7, 4), (-1, 4)],
+ +5: [(+1, 2)],
+ -5: [(-1, 2)],
+ 7: [(+1, 1), (-1, 1)],
+ 8: [(+1, 1), (-1, 1)],
+ 9: [(+1, 2), (-3, 2)],
+ 10: [(+3, 2), (-1, 2)],
+ 11: [(+3, 4), (-5, 4)],
+ 12: [(+5, 4), (-3, 4)],
+ 13: [(+5, 4), (-3, 4)],
+ 14: [(+3, 4), (-5, 4)],
+ 15: [(+1, 2), (-3, 2)],
+ 16: [(+3, 2), (-1, 2)],
+ 17: [(+2, 1), ( 0, 1)],
+ 18: [(+2, 1), ( 0, 1)],
+}
+
+def _classify_point(re, im):
+ """Return the half-axis (or origin) on which (re, im) point is located. """
+ if not re and not im:
+ return OO
+
+ if not re:
+ if im > 0:
+ return A2
+ else:
+ return A4
+ elif not im:
+ if re > 0:
+ return A1
+ else:
+ return A3
+
+def _intervals_to_quadrants(intervals, f1, f2, s, t, F):
+ """Generate a sequence of extended quadrants from a list of critical points. """
+ if not intervals:
+ return []
+
+ Q = []
+
+ if not f1:
+ (a, b), _, _ = intervals[0]
+
+ if a == b == s:
+ if len(intervals) == 1:
+ if dup_eval(f2, t, F) > 0:
+ return [OO, A2]
+ else:
+ return [OO, A4]
+ else:
+ (a, _), _, _ = intervals[1]
+
+ if dup_eval(f2, (s + a)/2, F) > 0:
+ Q.extend([OO, A2])
+ f2_sgn = +1
+ else:
+ Q.extend([OO, A4])
+ f2_sgn = -1
+
+ intervals = intervals[1:]
+ else:
+ if dup_eval(f2, s, F) > 0:
+ Q.append(A2)
+ f2_sgn = +1
+ else:
+ Q.append(A4)
+ f2_sgn = -1
+
+ for (a, _), indices, _ in intervals:
+ Q.append(OO)
+
+ if indices[1] % 2 == 1:
+ f2_sgn = -f2_sgn
+
+ if a != t:
+ if f2_sgn > 0:
+ Q.append(A2)
+ else:
+ Q.append(A4)
+
+ return Q
+
+ if not f2:
+ (a, b), _, _ = intervals[0]
+
+ if a == b == s:
+ if len(intervals) == 1:
+ if dup_eval(f1, t, F) > 0:
+ return [OO, A1]
+ else:
+ return [OO, A3]
+ else:
+ (a, _), _, _ = intervals[1]
+
+ if dup_eval(f1, (s + a)/2, F) > 0:
+ Q.extend([OO, A1])
+ f1_sgn = +1
+ else:
+ Q.extend([OO, A3])
+ f1_sgn = -1
+
+ intervals = intervals[1:]
+ else:
+ if dup_eval(f1, s, F) > 0:
+ Q.append(A1)
+ f1_sgn = +1
+ else:
+ Q.append(A3)
+ f1_sgn = -1
+
+ for (a, _), indices, _ in intervals:
+ Q.append(OO)
+
+ if indices[0] % 2 == 1:
+ f1_sgn = -f1_sgn
+
+ if a != t:
+ if f1_sgn > 0:
+ Q.append(A1)
+ else:
+ Q.append(A3)
+
+ return Q
+
+ re = dup_eval(f1, s, F)
+ im = dup_eval(f2, s, F)
+
+ if not re or not im:
+ Q.append(_classify_point(re, im))
+
+ if len(intervals) == 1:
+ re = dup_eval(f1, t, F)
+ im = dup_eval(f2, t, F)
+ else:
+ (a, _), _, _ = intervals[1]
+
+ re = dup_eval(f1, (s + a)/2, F)
+ im = dup_eval(f2, (s + a)/2, F)
+
+ intervals = intervals[1:]
+
+ if re > 0:
+ f1_sgn = +1
+ else:
+ f1_sgn = -1
+
+ if im > 0:
+ f2_sgn = +1
+ else:
+ f2_sgn = -1
+
+ sgn = {
+ (+1, +1): Q1,
+ (-1, +1): Q2,
+ (-1, -1): Q3,
+ (+1, -1): Q4,
+ }
+
+ Q.append(sgn[(f1_sgn, f2_sgn)])
+
+ for (a, b), indices, _ in intervals:
+ if a == b:
+ re = dup_eval(f1, a, F)
+ im = dup_eval(f2, a, F)
+
+ cls = _classify_point(re, im)
+
+ if cls is not None:
+ Q.append(cls)
+
+ if 0 in indices:
+ if indices[0] % 2 == 1:
+ f1_sgn = -f1_sgn
+
+ if 1 in indices:
+ if indices[1] % 2 == 1:
+ f2_sgn = -f2_sgn
+
+ if not (a == b and b == t):
+ Q.append(sgn[(f1_sgn, f2_sgn)])
+
+ return Q
+
+def _traverse_quadrants(Q_L1, Q_L2, Q_L3, Q_L4, exclude=None):
+ """Transform sequences of quadrants to a sequence of rules. """
+ if exclude is True:
+ edges = [1, 1, 0, 0]
+
+ corners = {
+ (0, 1): 1,
+ (1, 2): 1,
+ (2, 3): 0,
+ (3, 0): 1,
+ }
+ else:
+ edges = [0, 0, 0, 0]
+
+ corners = {
+ (0, 1): 0,
+ (1, 2): 0,
+ (2, 3): 0,
+ (3, 0): 0,
+ }
+
+ if exclude is not None and exclude is not True:
+ exclude = set(exclude)
+
+ for i, edge in enumerate(['S', 'E', 'N', 'W']):
+ if edge in exclude:
+ edges[i] = 1
+
+ for i, corner in enumerate(['SW', 'SE', 'NE', 'NW']):
+ if corner in exclude:
+ corners[((i - 1) % 4, i)] = 1
+
+ QQ, rules = [Q_L1, Q_L2, Q_L3, Q_L4], []
+
+ for i, Q in enumerate(QQ):
+ if not Q:
+ continue
+
+ if Q[-1] == OO:
+ Q = Q[:-1]
+
+ if Q[0] == OO:
+ j, Q = (i - 1) % 4, Q[1:]
+ qq = (QQ[j][-2], OO, Q[0])
+
+ if qq in _rules_ambiguous:
+ rules.append((_rules_ambiguous[qq], corners[(j, i)]))
+ else:
+ raise NotImplementedError("3 element rule (corner): " + str(qq))
+
+ q1, k = Q[0], 1
+
+ while k < len(Q):
+ q2, k = Q[k], k + 1
+
+ if q2 != OO:
+ qq = (q1, q2)
+
+ if qq in _rules_simple:
+ rules.append((_rules_simple[qq], 0))
+ elif qq in _rules_ambiguous:
+ rules.append((_rules_ambiguous[qq], edges[i]))
+ else:
+ raise NotImplementedError("2 element rule (inside): " + str(qq))
+ else:
+ qq, k = (q1, q2, Q[k]), k + 1
+
+ if qq in _rules_ambiguous:
+ rules.append((_rules_ambiguous[qq], edges[i]))
+ else:
+ raise NotImplementedError("3 element rule (edge): " + str(qq))
+
+ q1 = qq[-1]
+
+ return rules
+
+def _reverse_intervals(intervals):
+ """Reverse intervals for traversal from right to left and from top to bottom. """
+ return [ ((b, a), indices, f) for (a, b), indices, f in reversed(intervals) ]
+
+def _winding_number(T, field):
+ """Compute the winding number of the input polynomial, i.e. the number of roots. """
+ return int(sum(field(*_values[t][i]) for t, i in T) / field(2))
+
+def dup_count_complex_roots(f, K, inf=None, sup=None, exclude=None):
+ """Count all roots in [u + v*I, s + t*I] rectangle using Collins-Krandick algorithm. """
+ if not K.is_ZZ and not K.is_QQ:
+ raise DomainError("complex root counting is not supported over %s" % K)
+
+ if K.is_ZZ:
+ R, F = K, K.get_field()
+ else:
+ R, F = K.get_ring(), K
+
+ f = dup_convert(f, K, F)
+
+ if inf is None or sup is None:
+ _, lc = dup_degree(f), abs(dup_LC(f, F))
+ B = 2*max(F.quo(abs(c), lc) for c in f)
+
+ if inf is None:
+ (u, v) = (-B, -B)
+ else:
+ (u, v) = inf
+
+ if sup is None:
+ (s, t) = (+B, +B)
+ else:
+ (s, t) = sup
+
+ f1, f2 = dup_real_imag(f, F)
+
+ f1L1F = dmp_eval_in(f1, v, 1, 1, F)
+ f2L1F = dmp_eval_in(f2, v, 1, 1, F)
+
+ _, f1L1R = dup_clear_denoms(f1L1F, F, R, convert=True)
+ _, f2L1R = dup_clear_denoms(f2L1F, F, R, convert=True)
+
+ f1L2F = dmp_eval_in(f1, s, 0, 1, F)
+ f2L2F = dmp_eval_in(f2, s, 0, 1, F)
+
+ _, f1L2R = dup_clear_denoms(f1L2F, F, R, convert=True)
+ _, f2L2R = dup_clear_denoms(f2L2F, F, R, convert=True)
+
+ f1L3F = dmp_eval_in(f1, t, 1, 1, F)
+ f2L3F = dmp_eval_in(f2, t, 1, 1, F)
+
+ _, f1L3R = dup_clear_denoms(f1L3F, F, R, convert=True)
+ _, f2L3R = dup_clear_denoms(f2L3F, F, R, convert=True)
+
+ f1L4F = dmp_eval_in(f1, u, 0, 1, F)
+ f2L4F = dmp_eval_in(f2, u, 0, 1, F)
+
+ _, f1L4R = dup_clear_denoms(f1L4F, F, R, convert=True)
+ _, f2L4R = dup_clear_denoms(f2L4F, F, R, convert=True)
+
+ S_L1 = [f1L1R, f2L1R]
+ S_L2 = [f1L2R, f2L2R]
+ S_L3 = [f1L3R, f2L3R]
+ S_L4 = [f1L4R, f2L4R]
+
+ I_L1 = dup_isolate_real_roots_list(S_L1, R, inf=u, sup=s, fast=True, basis=True, strict=True)
+ I_L2 = dup_isolate_real_roots_list(S_L2, R, inf=v, sup=t, fast=True, basis=True, strict=True)
+ I_L3 = dup_isolate_real_roots_list(S_L3, R, inf=u, sup=s, fast=True, basis=True, strict=True)
+ I_L4 = dup_isolate_real_roots_list(S_L4, R, inf=v, sup=t, fast=True, basis=True, strict=True)
+
+ I_L3 = _reverse_intervals(I_L3)
+ I_L4 = _reverse_intervals(I_L4)
+
+ Q_L1 = _intervals_to_quadrants(I_L1, f1L1F, f2L1F, u, s, F)
+ Q_L2 = _intervals_to_quadrants(I_L2, f1L2F, f2L2F, v, t, F)
+ Q_L3 = _intervals_to_quadrants(I_L3, f1L3F, f2L3F, s, u, F)
+ Q_L4 = _intervals_to_quadrants(I_L4, f1L4F, f2L4F, t, v, F)
+
+ T = _traverse_quadrants(Q_L1, Q_L2, Q_L3, Q_L4, exclude=exclude)
+
+ return _winding_number(T, F)
+
+def _vertical_bisection(N, a, b, I, Q, F1, F2, f1, f2, F):
+ """Vertical bisection step in Collins-Krandick root isolation algorithm. """
+ (u, v), (s, t) = a, b
+
+ I_L1, I_L2, I_L3, I_L4 = I
+ Q_L1, Q_L2, Q_L3, Q_L4 = Q
+
+ f1L1F, f1L2F, f1L3F, f1L4F = F1
+ f2L1F, f2L2F, f2L3F, f2L4F = F2
+
+ x = (u + s) / 2
+
+ f1V = dmp_eval_in(f1, x, 0, 1, F)
+ f2V = dmp_eval_in(f2, x, 0, 1, F)
+
+ I_V = dup_isolate_real_roots_list([f1V, f2V], F, inf=v, sup=t, fast=True, strict=True, basis=True)
+
+ I_L1_L, I_L1_R = [], []
+ I_L2_L, I_L2_R = I_V, I_L2
+ I_L3_L, I_L3_R = [], []
+ I_L4_L, I_L4_R = I_L4, _reverse_intervals(I_V)
+
+ for I in I_L1:
+ (a, b), indices, h = I
+
+ if a == b:
+ if a == x:
+ I_L1_L.append(I)
+ I_L1_R.append(I)
+ elif a < x:
+ I_L1_L.append(I)
+ else:
+ I_L1_R.append(I)
+ else:
+ if b <= x:
+ I_L1_L.append(I)
+ elif a >= x:
+ I_L1_R.append(I)
+ else:
+ a, b = dup_refine_real_root(h, a, b, F.get_ring(), disjoint=x, fast=True)
+
+ if b <= x:
+ I_L1_L.append(((a, b), indices, h))
+ if a >= x:
+ I_L1_R.append(((a, b), indices, h))
+
+ for I in I_L3:
+ (b, a), indices, h = I
+
+ if a == b:
+ if a == x:
+ I_L3_L.append(I)
+ I_L3_R.append(I)
+ elif a < x:
+ I_L3_L.append(I)
+ else:
+ I_L3_R.append(I)
+ else:
+ if b <= x:
+ I_L3_L.append(I)
+ elif a >= x:
+ I_L3_R.append(I)
+ else:
+ a, b = dup_refine_real_root(h, a, b, F.get_ring(), disjoint=x, fast=True)
+
+ if b <= x:
+ I_L3_L.append(((b, a), indices, h))
+ if a >= x:
+ I_L3_R.append(((b, a), indices, h))
+
+ Q_L1_L = _intervals_to_quadrants(I_L1_L, f1L1F, f2L1F, u, x, F)
+ Q_L2_L = _intervals_to_quadrants(I_L2_L, f1V, f2V, v, t, F)
+ Q_L3_L = _intervals_to_quadrants(I_L3_L, f1L3F, f2L3F, x, u, F)
+ Q_L4_L = Q_L4
+
+ Q_L1_R = _intervals_to_quadrants(I_L1_R, f1L1F, f2L1F, x, s, F)
+ Q_L2_R = Q_L2
+ Q_L3_R = _intervals_to_quadrants(I_L3_R, f1L3F, f2L3F, s, x, F)
+ Q_L4_R = _intervals_to_quadrants(I_L4_R, f1V, f2V, t, v, F)
+
+ T_L = _traverse_quadrants(Q_L1_L, Q_L2_L, Q_L3_L, Q_L4_L, exclude=True)
+ T_R = _traverse_quadrants(Q_L1_R, Q_L2_R, Q_L3_R, Q_L4_R, exclude=True)
+
+ N_L = _winding_number(T_L, F)
+ N_R = _winding_number(T_R, F)
+
+ I_L = (I_L1_L, I_L2_L, I_L3_L, I_L4_L)
+ Q_L = (Q_L1_L, Q_L2_L, Q_L3_L, Q_L4_L)
+
+ I_R = (I_L1_R, I_L2_R, I_L3_R, I_L4_R)
+ Q_R = (Q_L1_R, Q_L2_R, Q_L3_R, Q_L4_R)
+
+ F1_L = (f1L1F, f1V, f1L3F, f1L4F)
+ F2_L = (f2L1F, f2V, f2L3F, f2L4F)
+
+ F1_R = (f1L1F, f1L2F, f1L3F, f1V)
+ F2_R = (f2L1F, f2L2F, f2L3F, f2V)
+
+ a, b = (u, v), (x, t)
+ c, d = (x, v), (s, t)
+
+ D_L = (N_L, a, b, I_L, Q_L, F1_L, F2_L)
+ D_R = (N_R, c, d, I_R, Q_R, F1_R, F2_R)
+
+ return D_L, D_R
+
+def _horizontal_bisection(N, a, b, I, Q, F1, F2, f1, f2, F):
+ """Horizontal bisection step in Collins-Krandick root isolation algorithm. """
+ (u, v), (s, t) = a, b
+
+ I_L1, I_L2, I_L3, I_L4 = I
+ Q_L1, Q_L2, Q_L3, Q_L4 = Q
+
+ f1L1F, f1L2F, f1L3F, f1L4F = F1
+ f2L1F, f2L2F, f2L3F, f2L4F = F2
+
+ y = (v + t) / 2
+
+ f1H = dmp_eval_in(f1, y, 1, 1, F)
+ f2H = dmp_eval_in(f2, y, 1, 1, F)
+
+ I_H = dup_isolate_real_roots_list([f1H, f2H], F, inf=u, sup=s, fast=True, strict=True, basis=True)
+
+ I_L1_B, I_L1_U = I_L1, I_H
+ I_L2_B, I_L2_U = [], []
+ I_L3_B, I_L3_U = _reverse_intervals(I_H), I_L3
+ I_L4_B, I_L4_U = [], []
+
+ for I in I_L2:
+ (a, b), indices, h = I
+
+ if a == b:
+ if a == y:
+ I_L2_B.append(I)
+ I_L2_U.append(I)
+ elif a < y:
+ I_L2_B.append(I)
+ else:
+ I_L2_U.append(I)
+ else:
+ if b <= y:
+ I_L2_B.append(I)
+ elif a >= y:
+ I_L2_U.append(I)
+ else:
+ a, b = dup_refine_real_root(h, a, b, F.get_ring(), disjoint=y, fast=True)
+
+ if b <= y:
+ I_L2_B.append(((a, b), indices, h))
+ if a >= y:
+ I_L2_U.append(((a, b), indices, h))
+
+ for I in I_L4:
+ (b, a), indices, h = I
+
+ if a == b:
+ if a == y:
+ I_L4_B.append(I)
+ I_L4_U.append(I)
+ elif a < y:
+ I_L4_B.append(I)
+ else:
+ I_L4_U.append(I)
+ else:
+ if b <= y:
+ I_L4_B.append(I)
+ elif a >= y:
+ I_L4_U.append(I)
+ else:
+ a, b = dup_refine_real_root(h, a, b, F.get_ring(), disjoint=y, fast=True)
+
+ if b <= y:
+ I_L4_B.append(((b, a), indices, h))
+ if a >= y:
+ I_L4_U.append(((b, a), indices, h))
+
+ Q_L1_B = Q_L1
+ Q_L2_B = _intervals_to_quadrants(I_L2_B, f1L2F, f2L2F, v, y, F)
+ Q_L3_B = _intervals_to_quadrants(I_L3_B, f1H, f2H, s, u, F)
+ Q_L4_B = _intervals_to_quadrants(I_L4_B, f1L4F, f2L4F, y, v, F)
+
+ Q_L1_U = _intervals_to_quadrants(I_L1_U, f1H, f2H, u, s, F)
+ Q_L2_U = _intervals_to_quadrants(I_L2_U, f1L2F, f2L2F, y, t, F)
+ Q_L3_U = Q_L3
+ Q_L4_U = _intervals_to_quadrants(I_L4_U, f1L4F, f2L4F, t, y, F)
+
+ T_B = _traverse_quadrants(Q_L1_B, Q_L2_B, Q_L3_B, Q_L4_B, exclude=True)
+ T_U = _traverse_quadrants(Q_L1_U, Q_L2_U, Q_L3_U, Q_L4_U, exclude=True)
+
+ N_B = _winding_number(T_B, F)
+ N_U = _winding_number(T_U, F)
+
+ I_B = (I_L1_B, I_L2_B, I_L3_B, I_L4_B)
+ Q_B = (Q_L1_B, Q_L2_B, Q_L3_B, Q_L4_B)
+
+ I_U = (I_L1_U, I_L2_U, I_L3_U, I_L4_U)
+ Q_U = (Q_L1_U, Q_L2_U, Q_L3_U, Q_L4_U)
+
+ F1_B = (f1L1F, f1L2F, f1H, f1L4F)
+ F2_B = (f2L1F, f2L2F, f2H, f2L4F)
+
+ F1_U = (f1H, f1L2F, f1L3F, f1L4F)
+ F2_U = (f2H, f2L2F, f2L3F, f2L4F)
+
+ a, b = (u, v), (s, y)
+ c, d = (u, y), (s, t)
+
+ D_B = (N_B, a, b, I_B, Q_B, F1_B, F2_B)
+ D_U = (N_U, c, d, I_U, Q_U, F1_U, F2_U)
+
+ return D_B, D_U
+
+def _depth_first_select(rectangles):
+ """Find a rectangle of minimum area for bisection. """
+ min_area, j = None, None
+
+ for i, (_, (u, v), (s, t), _, _, _, _) in enumerate(rectangles):
+ area = (s - u)*(t - v)
+
+ if min_area is None or area < min_area:
+ min_area, j = area, i
+
+ return rectangles.pop(j)
+
+def _rectangle_small_p(a, b, eps):
+ """Return ``True`` if the given rectangle is small enough. """
+ (u, v), (s, t) = a, b
+
+ if eps is not None:
+ return s - u < eps and t - v < eps
+ else:
+ return True
+
+def dup_isolate_complex_roots_sqf(f, K, eps=None, inf=None, sup=None, blackbox=False):
+ """Isolate complex roots of a square-free polynomial using Collins-Krandick algorithm. """
+ if not K.is_ZZ and not K.is_QQ:
+ raise DomainError("isolation of complex roots is not supported over %s" % K)
+
+ if dup_degree(f) <= 0:
+ return []
+
+ if K.is_ZZ:
+ F = K.get_field()
+ else:
+ F = K
+
+ f = dup_convert(f, K, F)
+
+ lc = abs(dup_LC(f, F))
+ B = 2*max(F.quo(abs(c), lc) for c in f)
+
+ (u, v), (s, t) = (-B, F.zero), (B, B)
+
+ if inf is not None:
+ u = inf
+
+ if sup is not None:
+ s = sup
+
+ if v < 0 or t <= v or s <= u:
+ raise ValueError("not a valid complex isolation rectangle")
+
+ f1, f2 = dup_real_imag(f, F)
+
+ f1L1 = dmp_eval_in(f1, v, 1, 1, F)
+ f2L1 = dmp_eval_in(f2, v, 1, 1, F)
+
+ f1L2 = dmp_eval_in(f1, s, 0, 1, F)
+ f2L2 = dmp_eval_in(f2, s, 0, 1, F)
+
+ f1L3 = dmp_eval_in(f1, t, 1, 1, F)
+ f2L3 = dmp_eval_in(f2, t, 1, 1, F)
+
+ f1L4 = dmp_eval_in(f1, u, 0, 1, F)
+ f2L4 = dmp_eval_in(f2, u, 0, 1, F)
+
+ S_L1 = [f1L1, f2L1]
+ S_L2 = [f1L2, f2L2]
+ S_L3 = [f1L3, f2L3]
+ S_L4 = [f1L4, f2L4]
+
+ I_L1 = dup_isolate_real_roots_list(S_L1, F, inf=u, sup=s, fast=True, strict=True, basis=True)
+ I_L2 = dup_isolate_real_roots_list(S_L2, F, inf=v, sup=t, fast=True, strict=True, basis=True)
+ I_L3 = dup_isolate_real_roots_list(S_L3, F, inf=u, sup=s, fast=True, strict=True, basis=True)
+ I_L4 = dup_isolate_real_roots_list(S_L4, F, inf=v, sup=t, fast=True, strict=True, basis=True)
+
+ I_L3 = _reverse_intervals(I_L3)
+ I_L4 = _reverse_intervals(I_L4)
+
+ Q_L1 = _intervals_to_quadrants(I_L1, f1L1, f2L1, u, s, F)
+ Q_L2 = _intervals_to_quadrants(I_L2, f1L2, f2L2, v, t, F)
+ Q_L3 = _intervals_to_quadrants(I_L3, f1L3, f2L3, s, u, F)
+ Q_L4 = _intervals_to_quadrants(I_L4, f1L4, f2L4, t, v, F)
+
+ T = _traverse_quadrants(Q_L1, Q_L2, Q_L3, Q_L4)
+ N = _winding_number(T, F)
+
+ if not N:
+ return []
+
+ I = (I_L1, I_L2, I_L3, I_L4)
+ Q = (Q_L1, Q_L2, Q_L3, Q_L4)
+
+ F1 = (f1L1, f1L2, f1L3, f1L4)
+ F2 = (f2L1, f2L2, f2L3, f2L4)
+
+ rectangles, roots = [(N, (u, v), (s, t), I, Q, F1, F2)], []
+
+ while rectangles:
+ N, (u, v), (s, t), I, Q, F1, F2 = _depth_first_select(rectangles)
+
+ if s - u > t - v:
+ D_L, D_R = _vertical_bisection(N, (u, v), (s, t), I, Q, F1, F2, f1, f2, F)
+
+ N_L, a, b, I_L, Q_L, F1_L, F2_L = D_L
+ N_R, c, d, I_R, Q_R, F1_R, F2_R = D_R
+
+ if N_L >= 1:
+ if N_L == 1 and _rectangle_small_p(a, b, eps):
+ roots.append(ComplexInterval(a, b, I_L, Q_L, F1_L, F2_L, f1, f2, F))
+ else:
+ rectangles.append(D_L)
+
+ if N_R >= 1:
+ if N_R == 1 and _rectangle_small_p(c, d, eps):
+ roots.append(ComplexInterval(c, d, I_R, Q_R, F1_R, F2_R, f1, f2, F))
+ else:
+ rectangles.append(D_R)
+ else:
+ D_B, D_U = _horizontal_bisection(N, (u, v), (s, t), I, Q, F1, F2, f1, f2, F)
+
+ N_B, a, b, I_B, Q_B, F1_B, F2_B = D_B
+ N_U, c, d, I_U, Q_U, F1_U, F2_U = D_U
+
+ if N_B >= 1:
+ if N_B == 1 and _rectangle_small_p(a, b, eps):
+ roots.append(ComplexInterval(
+ a, b, I_B, Q_B, F1_B, F2_B, f1, f2, F))
+ else:
+ rectangles.append(D_B)
+
+ if N_U >= 1:
+ if N_U == 1 and _rectangle_small_p(c, d, eps):
+ roots.append(ComplexInterval(
+ c, d, I_U, Q_U, F1_U, F2_U, f1, f2, F))
+ else:
+ rectangles.append(D_U)
+
+ _roots, roots = sorted(roots, key=lambda r: (r.ax, r.ay)), []
+
+ for root in _roots:
+ roots.extend([root.conjugate(), root])
+
+ if blackbox:
+ return roots
+ else:
+ return [ r.as_tuple() for r in roots ]
+
+def dup_isolate_all_roots_sqf(f, K, eps=None, inf=None, sup=None, fast=False, blackbox=False):
+ """Isolate real and complex roots of a square-free polynomial ``f``. """
+ return (
+ dup_isolate_real_roots_sqf( f, K, eps=eps, inf=inf, sup=sup, fast=fast, blackbox=blackbox),
+ dup_isolate_complex_roots_sqf(f, K, eps=eps, inf=inf, sup=sup, blackbox=blackbox))
+
+def dup_isolate_all_roots(f, K, eps=None, inf=None, sup=None, fast=False):
+ """Isolate real and complex roots of a non-square-free polynomial ``f``. """
+ if not K.is_ZZ and not K.is_QQ:
+ raise DomainError("isolation of real and complex roots is not supported over %s" % K)
+
+ _, factors = dup_sqf_list(f, K)
+
+ if len(factors) == 1:
+ ((f, k),) = factors
+
+ real_part, complex_part = dup_isolate_all_roots_sqf(
+ f, K, eps=eps, inf=inf, sup=sup, fast=fast)
+
+ real_part = [ ((a, b), k) for (a, b) in real_part ]
+ complex_part = [ ((a, b), k) for (a, b) in complex_part ]
+
+ return real_part, complex_part
+ else:
+ raise NotImplementedError( "only trivial square-free polynomials are supported")
+
+class RealInterval:
+ """A fully qualified representation of a real isolation interval. """
+
+ def __init__(self, data, f, dom):
+ """Initialize new real interval with complete information. """
+ if len(data) == 2:
+ s, t = data
+
+ self.neg = False
+
+ if s < 0:
+ if t <= 0:
+ f, s, t, self.neg = dup_mirror(f, dom), -t, -s, True
+ else:
+ raise ValueError("Cannot refine a real root in (%s, %s)" % (s, t))
+
+ a, b, c, d = _mobius_from_interval((s, t), dom.get_field())
+
+ f = dup_transform(f, dup_strip([a, b]),
+ dup_strip([c, d]), dom)
+
+ self.mobius = a, b, c, d
+ else:
+ self.mobius = data[:-1]
+ self.neg = data[-1]
+
+ self.f, self.dom = f, dom
+
+ @property
+ def func(self):
+ return RealInterval
+
+ @property
+ def args(self):
+ i = self
+ return (i.mobius + (i.neg,), i.f, i.dom)
+
+ def __eq__(self, other):
+ if type(other) is not type(self):
+ return False
+ return self.args == other.args
+
+ @property
+ def a(self):
+ """Return the position of the left end. """
+ field = self.dom.get_field()
+ a, b, c, d = self.mobius
+
+ if not self.neg:
+ if a*d < b*c:
+ return field(a, c)
+ return field(b, d)
+ else:
+ if a*d > b*c:
+ return -field(a, c)
+ return -field(b, d)
+
+ @property
+ def b(self):
+ """Return the position of the right end. """
+ was = self.neg
+ self.neg = not was
+ rv = -self.a
+ self.neg = was
+ return rv
+
+ @property
+ def dx(self):
+ """Return width of the real isolating interval. """
+ return self.b - self.a
+
+ @property
+ def center(self):
+ """Return the center of the real isolating interval. """
+ return (self.a + self.b)/2
+
+ @property
+ def max_denom(self):
+ """Return the largest denominator occurring in either endpoint. """
+ return max(self.a.denominator, self.b.denominator)
+
+ def as_tuple(self):
+ """Return tuple representation of real isolating interval. """
+ return (self.a, self.b)
+
+ def __repr__(self):
+ return "(%s, %s)" % (self.a, self.b)
+
+ def __contains__(self, item):
+ """
+ Say whether a complex number belongs to this real interval.
+
+ Parameters
+ ==========
+
+ item : pair (re, im) or number re
+ Either a pair giving the real and imaginary parts of the number,
+ or else a real number.
+
+ """
+ if isinstance(item, tuple):
+ re, im = item
+ else:
+ re, im = item, 0
+ return im == 0 and self.a <= re <= self.b
+
+ def is_disjoint(self, other):
+ """Return ``True`` if two isolation intervals are disjoint. """
+ if isinstance(other, RealInterval):
+ return (self.b < other.a or other.b < self.a)
+ assert isinstance(other, ComplexInterval)
+ return (self.b < other.ax or other.bx < self.a
+ or other.ay*other.by > 0)
+
+ def _inner_refine(self):
+ """Internal one step real root refinement procedure. """
+ if self.mobius is None:
+ return self
+
+ f, mobius = dup_inner_refine_real_root(
+ self.f, self.mobius, self.dom, steps=1, mobius=True)
+
+ return RealInterval(mobius + (self.neg,), f, self.dom)
+
+ def refine_disjoint(self, other):
+ """Refine an isolating interval until it is disjoint with another one. """
+ expr = self
+ while not expr.is_disjoint(other):
+ expr, other = expr._inner_refine(), other._inner_refine()
+
+ return expr, other
+
+ def refine_size(self, dx):
+ """Refine an isolating interval until it is of sufficiently small size. """
+ expr = self
+ while not (expr.dx < dx):
+ expr = expr._inner_refine()
+
+ return expr
+
+ def refine_step(self, steps=1):
+ """Perform several steps of real root refinement algorithm. """
+ expr = self
+ for _ in range(steps):
+ expr = expr._inner_refine()
+
+ return expr
+
+ def refine(self):
+ """Perform one step of real root refinement algorithm. """
+ return self._inner_refine()
+
+
+class ComplexInterval:
+ """A fully qualified representation of a complex isolation interval.
+ The printed form is shown as (ax, bx) x (ay, by) where (ax, ay)
+ and (bx, by) are the coordinates of the southwest and northeast
+ corners of the interval's rectangle, respectively.
+
+ Examples
+ ========
+
+ >>> from sympy import CRootOf, S
+ >>> from sympy.abc import x
+ >>> CRootOf.clear_cache() # for doctest reproducibility
+ >>> root = CRootOf(x**10 - 2*x + 3, 9)
+ >>> i = root._get_interval(); i
+ (3/64, 3/32) x (9/8, 75/64)
+
+ The real part of the root lies within the range [0, 3/4] while
+ the imaginary part lies within the range [9/8, 3/2]:
+
+ >>> root.n(3)
+ 0.0766 + 1.14*I
+
+ The width of the ranges in the x and y directions on the complex
+ plane are:
+
+ >>> i.dx, i.dy
+ (3/64, 3/64)
+
+ The center of the range is
+
+ >>> i.center
+ (9/128, 147/128)
+
+ The northeast coordinate of the rectangle bounding the root in the
+ complex plane is given by attribute b and the x and y components
+ are accessed by bx and by:
+
+ >>> i.b, i.bx, i.by
+ ((3/32, 75/64), 3/32, 75/64)
+
+ The southwest coordinate is similarly given by i.a
+
+ >>> i.a, i.ax, i.ay
+ ((3/64, 9/8), 3/64, 9/8)
+
+ Although the interval prints to show only the real and imaginary
+ range of the root, all the information of the underlying root
+ is contained as properties of the interval.
+
+ For example, an interval with a nonpositive imaginary range is
+ considered to be the conjugate. Since the y values of y are in the
+ range [0, 1/4] it is not the conjugate:
+
+ >>> i.conj
+ False
+
+ The conjugate's interval is
+
+ >>> ic = i.conjugate(); ic
+ (3/64, 3/32) x (-75/64, -9/8)
+
+ NOTE: the values printed still represent the x and y range
+ in which the root -- conjugate, in this case -- is located,
+ but the underlying a and b values of a root and its conjugate
+ are the same:
+
+ >>> assert i.a == ic.a and i.b == ic.b
+
+ What changes are the reported coordinates of the bounding rectangle:
+
+ >>> (i.ax, i.ay), (i.bx, i.by)
+ ((3/64, 9/8), (3/32, 75/64))
+ >>> (ic.ax, ic.ay), (ic.bx, ic.by)
+ ((3/64, -75/64), (3/32, -9/8))
+
+ The interval can be refined once:
+
+ >>> i # for reference, this is the current interval
+ (3/64, 3/32) x (9/8, 75/64)
+
+ >>> i.refine()
+ (3/64, 3/32) x (9/8, 147/128)
+
+ Several refinement steps can be taken:
+
+ >>> i.refine_step(2) # 2 steps
+ (9/128, 3/32) x (9/8, 147/128)
+
+ It is also possible to refine to a given tolerance:
+
+ >>> tol = min(i.dx, i.dy)/2
+ >>> i.refine_size(tol)
+ (9/128, 21/256) x (9/8, 291/256)
+
+ A disjoint interval is one whose bounding rectangle does not
+ overlap with another. An interval, necessarily, is not disjoint with
+ itself, but any interval is disjoint with a conjugate since the
+ conjugate rectangle will always be in the lower half of the complex
+ plane and the non-conjugate in the upper half:
+
+ >>> i.is_disjoint(i), i.is_disjoint(i.conjugate())
+ (False, True)
+
+ The following interval j is not disjoint from i:
+
+ >>> close = CRootOf(x**10 - 2*x + 300/S(101), 9)
+ >>> j = close._get_interval(); j
+ (75/1616, 75/808) x (225/202, 1875/1616)
+ >>> i.is_disjoint(j)
+ False
+
+ The two can be made disjoint, however:
+
+ >>> newi, newj = i.refine_disjoint(j)
+ >>> newi
+ (39/512, 159/2048) x (2325/2048, 4653/4096)
+ >>> newj
+ (3975/51712, 2025/25856) x (29325/25856, 117375/103424)
+
+ Even though the real ranges overlap, the imaginary do not, so
+ the roots have been resolved as distinct. Intervals are disjoint
+ when either the real or imaginary component of the intervals is
+ distinct. In the case above, the real components have not been
+ resolved (so we do not know, yet, which root has the smaller real
+ part) but the imaginary part of ``close`` is larger than ``root``:
+
+ >>> close.n(3)
+ 0.0771 + 1.13*I
+ >>> root.n(3)
+ 0.0766 + 1.14*I
+ """
+
+ def __init__(self, a, b, I, Q, F1, F2, f1, f2, dom, conj=False):
+ """Initialize new complex interval with complete information. """
+ # a and b are the SW and NE corner of the bounding interval,
+ # (ax, ay) and (bx, by), respectively, for the NON-CONJUGATE
+ # root (the one with the positive imaginary part); when working
+ # with the conjugate, the a and b value are still non-negative
+ # but the ay, by are reversed and have oppositite sign
+ self.a, self.b = a, b
+ self.I, self.Q = I, Q
+
+ self.f1, self.F1 = f1, F1
+ self.f2, self.F2 = f2, F2
+
+ self.dom = dom
+ self.conj = conj
+
+ @property
+ def func(self):
+ return ComplexInterval
+
+ @property
+ def args(self):
+ i = self
+ return (i.a, i.b, i.I, i.Q, i.F1, i.F2, i.f1, i.f2, i.dom, i.conj)
+
+ def __eq__(self, other):
+ if type(other) is not type(self):
+ return False
+ return self.args == other.args
+
+ @property
+ def ax(self):
+ """Return ``x`` coordinate of south-western corner. """
+ return self.a[0]
+
+ @property
+ def ay(self):
+ """Return ``y`` coordinate of south-western corner. """
+ if not self.conj:
+ return self.a[1]
+ else:
+ return -self.b[1]
+
+ @property
+ def bx(self):
+ """Return ``x`` coordinate of north-eastern corner. """
+ return self.b[0]
+
+ @property
+ def by(self):
+ """Return ``y`` coordinate of north-eastern corner. """
+ if not self.conj:
+ return self.b[1]
+ else:
+ return -self.a[1]
+
+ @property
+ def dx(self):
+ """Return width of the complex isolating interval. """
+ return self.b[0] - self.a[0]
+
+ @property
+ def dy(self):
+ """Return height of the complex isolating interval. """
+ return self.b[1] - self.a[1]
+
+ @property
+ def center(self):
+ """Return the center of the complex isolating interval. """
+ return ((self.ax + self.bx)/2, (self.ay + self.by)/2)
+
+ @property
+ def max_denom(self):
+ """Return the largest denominator occurring in either endpoint. """
+ return max(self.ax.denominator, self.bx.denominator,
+ self.ay.denominator, self.by.denominator)
+
+ def as_tuple(self):
+ """Return tuple representation of the complex isolating
+ interval's SW and NE corners, respectively. """
+ return ((self.ax, self.ay), (self.bx, self.by))
+
+ def __repr__(self):
+ return "(%s, %s) x (%s, %s)" % (self.ax, self.bx, self.ay, self.by)
+
+ def conjugate(self):
+ """This complex interval really is located in lower half-plane. """
+ return ComplexInterval(self.a, self.b, self.I, self.Q,
+ self.F1, self.F2, self.f1, self.f2, self.dom, conj=True)
+
+ def __contains__(self, item):
+ """
+ Say whether a complex number belongs to this complex rectangular
+ region.
+
+ Parameters
+ ==========
+
+ item : pair (re, im) or number re
+ Either a pair giving the real and imaginary parts of the number,
+ or else a real number.
+
+ """
+ if isinstance(item, tuple):
+ re, im = item
+ else:
+ re, im = item, 0
+ return self.ax <= re <= self.bx and self.ay <= im <= self.by
+
+ def is_disjoint(self, other):
+ """Return ``True`` if two isolation intervals are disjoint. """
+ if isinstance(other, RealInterval):
+ return other.is_disjoint(self)
+ if self.conj != other.conj: # above and below real axis
+ return True
+ re_distinct = (self.bx < other.ax or other.bx < self.ax)
+ if re_distinct:
+ return True
+ im_distinct = (self.by < other.ay or other.by < self.ay)
+ return im_distinct
+
+ def _inner_refine(self):
+ """Internal one step complex root refinement procedure. """
+ (u, v), (s, t) = self.a, self.b
+
+ I, Q = self.I, self.Q
+
+ f1, F1 = self.f1, self.F1
+ f2, F2 = self.f2, self.F2
+
+ dom = self.dom
+
+ if s - u > t - v:
+ D_L, D_R = _vertical_bisection(1, (u, v), (s, t), I, Q, F1, F2, f1, f2, dom)
+
+ if D_L[0] == 1:
+ _, a, b, I, Q, F1, F2 = D_L
+ else:
+ _, a, b, I, Q, F1, F2 = D_R
+ else:
+ D_B, D_U = _horizontal_bisection(1, (u, v), (s, t), I, Q, F1, F2, f1, f2, dom)
+
+ if D_B[0] == 1:
+ _, a, b, I, Q, F1, F2 = D_B
+ else:
+ _, a, b, I, Q, F1, F2 = D_U
+
+ return ComplexInterval(a, b, I, Q, F1, F2, f1, f2, dom, self.conj)
+
+ def refine_disjoint(self, other):
+ """Refine an isolating interval until it is disjoint with another one. """
+ expr = self
+ while not expr.is_disjoint(other):
+ expr, other = expr._inner_refine(), other._inner_refine()
+
+ return expr, other
+
+ def refine_size(self, dx, dy=None):
+ """Refine an isolating interval until it is of sufficiently small size. """
+ if dy is None:
+ dy = dx
+ expr = self
+ while not (expr.dx < dx and expr.dy < dy):
+ expr = expr._inner_refine()
+
+ return expr
+
+ def refine_step(self, steps=1):
+ """Perform several steps of complex root refinement algorithm. """
+ expr = self
+ for _ in range(steps):
+ expr = expr._inner_refine()
+
+ return expr
+
+ def refine(self):
+ """Perform one step of complex root refinement algorithm. """
+ return self._inner_refine()
diff --git a/lib/python3.12/site-packages/sympy/polys/rootoftools.py b/lib/python3.12/site-packages/sympy/polys/rootoftools.py
new file mode 100644
index 0000000000000000000000000000000000000000..cfd0af01cb5dd1414b40849431e4cd8a872eaa10
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/rootoftools.py
@@ -0,0 +1,1245 @@
+"""Implementation of RootOf class and related tools. """
+
+
+from sympy.core.basic import Basic
+from sympy.core import (S, Expr, Integer, Float, I, oo, Add, Lambda,
+ symbols, sympify, Rational, Dummy)
+from sympy.core.cache import cacheit
+from sympy.core.relational import is_le
+from sympy.core.sorting import ordered
+from sympy.polys.domains import QQ
+from sympy.polys.polyerrors import (
+ MultivariatePolynomialError,
+ GeneratorsNeeded,
+ PolynomialError,
+ DomainError)
+from sympy.polys.polyfuncs import symmetrize, viete
+from sympy.polys.polyroots import (
+ roots_linear, roots_quadratic, roots_binomial,
+ preprocess_roots, roots)
+from sympy.polys.polytools import Poly, PurePoly, factor
+from sympy.polys.rationaltools import together
+from sympy.polys.rootisolation import (
+ dup_isolate_complex_roots_sqf,
+ dup_isolate_real_roots_sqf)
+from sympy.utilities import lambdify, public, sift, numbered_symbols
+
+from mpmath import mpf, mpc, findroot, workprec
+from mpmath.libmp.libmpf import dps_to_prec, prec_to_dps
+from sympy.multipledispatch import dispatch
+from itertools import chain
+
+
+__all__ = ['CRootOf']
+
+
+
+class _pure_key_dict:
+ """A minimal dictionary that makes sure that the key is a
+ univariate PurePoly instance.
+
+ Examples
+ ========
+
+ Only the following actions are guaranteed:
+
+ >>> from sympy.polys.rootoftools import _pure_key_dict
+ >>> from sympy import PurePoly
+ >>> from sympy.abc import x, y
+
+ 1) creation
+
+ >>> P = _pure_key_dict()
+
+ 2) assignment for a PurePoly or univariate polynomial
+
+ >>> P[x] = 1
+ >>> P[PurePoly(x - y, x)] = 2
+
+ 3) retrieval based on PurePoly key comparison (use this
+ instead of the get method)
+
+ >>> P[y]
+ 1
+
+ 4) KeyError when trying to retrieve a nonexisting key
+
+ >>> P[y + 1]
+ Traceback (most recent call last):
+ ...
+ KeyError: PurePoly(y + 1, y, domain='ZZ')
+
+ 5) ability to query with ``in``
+
+ >>> x + 1 in P
+ False
+
+ NOTE: this is a *not* a dictionary. It is a very basic object
+ for internal use that makes sure to always address its cache
+ via PurePoly instances. It does not, for example, implement
+ ``get`` or ``setdefault``.
+ """
+ def __init__(self):
+ self._dict = {}
+
+ def __getitem__(self, k):
+ if not isinstance(k, PurePoly):
+ if not (isinstance(k, Expr) and len(k.free_symbols) == 1):
+ raise KeyError
+ k = PurePoly(k, expand=False)
+ return self._dict[k]
+
+ def __setitem__(self, k, v):
+ if not isinstance(k, PurePoly):
+ if not (isinstance(k, Expr) and len(k.free_symbols) == 1):
+ raise ValueError('expecting univariate expression')
+ k = PurePoly(k, expand=False)
+ self._dict[k] = v
+
+ def __contains__(self, k):
+ try:
+ self[k]
+ return True
+ except KeyError:
+ return False
+
+_reals_cache = _pure_key_dict()
+_complexes_cache = _pure_key_dict()
+
+
+def _pure_factors(poly):
+ _, factors = poly.factor_list()
+ return [(PurePoly(f, expand=False), m) for f, m in factors]
+
+
+def _imag_count_of_factor(f):
+ """Return the number of imaginary roots for irreducible
+ univariate polynomial ``f``.
+ """
+ terms = [(i, j) for (i,), j in f.terms()]
+ if any(i % 2 for i, j in terms):
+ return 0
+ # update signs
+ even = [(i, I**i*j) for i, j in terms]
+ even = Poly.from_dict(dict(even), Dummy('x'))
+ return int(even.count_roots(-oo, oo))
+
+
+@public
+def rootof(f, x, index=None, radicals=True, expand=True):
+ """An indexed root of a univariate polynomial.
+
+ Returns either a :obj:`ComplexRootOf` object or an explicit
+ expression involving radicals.
+
+ Parameters
+ ==========
+
+ f : Expr
+ Univariate polynomial.
+ x : Symbol, optional
+ Generator for ``f``.
+ index : int or Integer
+ radicals : bool
+ Return a radical expression if possible.
+ expand : bool
+ Expand ``f``.
+ """
+ return CRootOf(f, x, index=index, radicals=radicals, expand=expand)
+
+
+@public
+class RootOf(Expr):
+ """Represents a root of a univariate polynomial.
+
+ Base class for roots of different kinds of polynomials.
+ Only complex roots are currently supported.
+ """
+
+ __slots__ = ('poly',)
+
+ def __new__(cls, f, x, index=None, radicals=True, expand=True):
+ """Construct a new ``CRootOf`` object for ``k``-th root of ``f``."""
+ return rootof(f, x, index=index, radicals=radicals, expand=expand)
+
+@public
+class ComplexRootOf(RootOf):
+ """Represents an indexed complex root of a polynomial.
+
+ Roots of a univariate polynomial separated into disjoint
+ real or complex intervals and indexed in a fixed order:
+
+ * real roots come first and are sorted in increasing order;
+ * complex roots come next and are sorted primarily by increasing
+ real part, secondarily by increasing imaginary part.
+
+ Currently only rational coefficients are allowed.
+ Can be imported as ``CRootOf``. To avoid confusion, the
+ generator must be a Symbol.
+
+
+ Examples
+ ========
+
+ >>> from sympy import CRootOf, rootof
+ >>> from sympy.abc import x
+
+ CRootOf is a way to reference a particular root of a
+ polynomial. If there is a rational root, it will be returned:
+
+ >>> CRootOf.clear_cache() # for doctest reproducibility
+ >>> CRootOf(x**2 - 4, 0)
+ -2
+
+ Whether roots involving radicals are returned or not
+ depends on whether the ``radicals`` flag is true (which is
+ set to True with rootof):
+
+ >>> CRootOf(x**2 - 3, 0)
+ CRootOf(x**2 - 3, 0)
+ >>> CRootOf(x**2 - 3, 0, radicals=True)
+ -sqrt(3)
+ >>> rootof(x**2 - 3, 0)
+ -sqrt(3)
+
+ The following cannot be expressed in terms of radicals:
+
+ >>> r = rootof(4*x**5 + 16*x**3 + 12*x**2 + 7, 0); r
+ CRootOf(4*x**5 + 16*x**3 + 12*x**2 + 7, 0)
+
+ The root bounds can be seen, however, and they are used by the
+ evaluation methods to get numerical approximations for the root.
+
+ >>> interval = r._get_interval(); interval
+ (-1, 0)
+ >>> r.evalf(2)
+ -0.98
+
+ The evalf method refines the width of the root bounds until it
+ guarantees that any decimal approximation within those bounds
+ will satisfy the desired precision. It then stores the refined
+ interval so subsequent requests at or below the requested
+ precision will not have to recompute the root bounds and will
+ return very quickly.
+
+ Before evaluation above, the interval was
+
+ >>> interval
+ (-1, 0)
+
+ After evaluation it is now
+
+ >>> r._get_interval() # doctest: +SKIP
+ (-165/169, -206/211)
+
+ To reset all intervals for a given polynomial, the :meth:`_reset` method
+ can be called from any CRootOf instance of the polynomial:
+
+ >>> r._reset()
+ >>> r._get_interval()
+ (-1, 0)
+
+ The :meth:`eval_approx` method will also find the root to a given
+ precision but the interval is not modified unless the search
+ for the root fails to converge within the root bounds. And
+ the secant method is used to find the root. (The ``evalf``
+ method uses bisection and will always update the interval.)
+
+ >>> r.eval_approx(2)
+ -0.98
+
+ The interval needed to be slightly updated to find that root:
+
+ >>> r._get_interval()
+ (-1, -1/2)
+
+ The ``evalf_rational`` will compute a rational approximation
+ of the root to the desired accuracy or precision.
+
+ >>> r.eval_rational(n=2)
+ -69629/71318
+
+ >>> t = CRootOf(x**3 + 10*x + 1, 1)
+ >>> t.eval_rational(1e-1)
+ 15/256 - 805*I/256
+ >>> t.eval_rational(1e-1, 1e-4)
+ 3275/65536 - 414645*I/131072
+ >>> t.eval_rational(1e-4, 1e-4)
+ 6545/131072 - 414645*I/131072
+ >>> t.eval_rational(n=2)
+ 104755/2097152 - 6634255*I/2097152
+
+ Notes
+ =====
+
+ Although a PurePoly can be constructed from a non-symbol generator
+ RootOf instances of non-symbols are disallowed to avoid confusion
+ over what root is being represented.
+
+ >>> from sympy import exp, PurePoly
+ >>> PurePoly(x) == PurePoly(exp(x))
+ True
+ >>> CRootOf(x - 1, 0)
+ 1
+ >>> CRootOf(exp(x) - 1, 0) # would correspond to x == 0
+ Traceback (most recent call last):
+ ...
+ sympy.polys.polyerrors.PolynomialError: generator must be a Symbol
+
+ See Also
+ ========
+
+ eval_approx
+ eval_rational
+
+ """
+
+ __slots__ = ('index',)
+ is_complex = True
+ is_number = True
+ is_finite = True
+
+ def __new__(cls, f, x, index=None, radicals=False, expand=True):
+ """ Construct an indexed complex root of a polynomial.
+
+ See ``rootof`` for the parameters.
+
+ The default value of ``radicals`` is ``False`` to satisfy
+ ``eval(srepr(expr) == expr``.
+ """
+ x = sympify(x)
+
+ if index is None and x.is_Integer:
+ x, index = None, x
+ else:
+ index = sympify(index)
+
+ if index is not None and index.is_Integer:
+ index = int(index)
+ else:
+ raise ValueError("expected an integer root index, got %s" % index)
+
+ poly = PurePoly(f, x, greedy=False, expand=expand)
+
+ if not poly.is_univariate:
+ raise PolynomialError("only univariate polynomials are allowed")
+
+ if not poly.gen.is_Symbol:
+ # PurePoly(sin(x) + 1) == PurePoly(x + 1) but the roots of
+ # x for each are not the same: issue 8617
+ raise PolynomialError("generator must be a Symbol")
+
+ degree = poly.degree()
+
+ if degree <= 0:
+ raise PolynomialError("Cannot construct CRootOf object for %s" % f)
+
+ if index < -degree or index >= degree:
+ raise IndexError("root index out of [%d, %d] range, got %d" %
+ (-degree, degree - 1, index))
+ elif index < 0:
+ index += degree
+
+ dom = poly.get_domain()
+
+ if not dom.is_Exact:
+ poly = poly.to_exact()
+
+ roots = cls._roots_trivial(poly, radicals)
+
+ if roots is not None:
+ return roots[index]
+
+ coeff, poly = preprocess_roots(poly)
+ dom = poly.get_domain()
+
+ if not dom.is_ZZ:
+ raise NotImplementedError("CRootOf is not supported over %s" % dom)
+
+ root = cls._indexed_root(poly, index, lazy=True)
+ return coeff * cls._postprocess_root(root, radicals)
+
+ @classmethod
+ def _new(cls, poly, index):
+ """Construct new ``CRootOf`` object from raw data. """
+ obj = Expr.__new__(cls)
+
+ obj.poly = PurePoly(poly)
+ obj.index = index
+
+ try:
+ _reals_cache[obj.poly] = _reals_cache[poly]
+ _complexes_cache[obj.poly] = _complexes_cache[poly]
+ except KeyError:
+ pass
+
+ return obj
+
+ def _hashable_content(self):
+ return (self.poly, self.index)
+
+ @property
+ def expr(self):
+ return self.poly.as_expr()
+
+ @property
+ def args(self):
+ return (self.expr, Integer(self.index))
+
+ @property
+ def free_symbols(self):
+ # CRootOf currently only works with univariate expressions
+ # whose poly attribute should be a PurePoly with no free
+ # symbols
+ return set()
+
+ def _eval_is_real(self):
+ """Return ``True`` if the root is real. """
+ self._ensure_reals_init()
+ return self.index < len(_reals_cache[self.poly])
+
+ def _eval_is_imaginary(self):
+ """Return ``True`` if the root is imaginary. """
+ self._ensure_reals_init()
+ if self.index >= len(_reals_cache[self.poly]):
+ ivl = self._get_interval()
+ return ivl.ax*ivl.bx <= 0 # all others are on one side or the other
+ return False # XXX is this necessary?
+
+ @classmethod
+ def real_roots(cls, poly, radicals=True):
+ """Get real roots of a polynomial. """
+ return cls._get_roots("_real_roots", poly, radicals)
+
+ @classmethod
+ def all_roots(cls, poly, radicals=True):
+ """Get real and complex roots of a polynomial. """
+ return cls._get_roots("_all_roots", poly, radicals)
+
+ @classmethod
+ def _get_reals_sqf(cls, currentfactor, use_cache=True):
+ """Get real root isolating intervals for a square-free factor."""
+ if use_cache and currentfactor in _reals_cache:
+ real_part = _reals_cache[currentfactor]
+ else:
+ _reals_cache[currentfactor] = real_part = \
+ dup_isolate_real_roots_sqf(
+ currentfactor.rep.to_list(), currentfactor.rep.dom, blackbox=True)
+
+ return real_part
+
+ @classmethod
+ def _get_complexes_sqf(cls, currentfactor, use_cache=True):
+ """Get complex root isolating intervals for a square-free factor."""
+ if use_cache and currentfactor in _complexes_cache:
+ complex_part = _complexes_cache[currentfactor]
+ else:
+ _complexes_cache[currentfactor] = complex_part = \
+ dup_isolate_complex_roots_sqf(
+ currentfactor.rep.to_list(), currentfactor.rep.dom, blackbox=True)
+ return complex_part
+
+ @classmethod
+ def _get_reals(cls, factors, use_cache=True):
+ """Compute real root isolating intervals for a list of factors. """
+ reals = []
+
+ for currentfactor, k in factors:
+ try:
+ if not use_cache:
+ raise KeyError
+ r = _reals_cache[currentfactor]
+ reals.extend([(i, currentfactor, k) for i in r])
+ except KeyError:
+ real_part = cls._get_reals_sqf(currentfactor, use_cache)
+ new = [(root, currentfactor, k) for root in real_part]
+ reals.extend(new)
+
+ reals = cls._reals_sorted(reals)
+ return reals
+
+ @classmethod
+ def _get_complexes(cls, factors, use_cache=True):
+ """Compute complex root isolating intervals for a list of factors. """
+ complexes = []
+
+ for currentfactor, k in ordered(factors):
+ try:
+ if not use_cache:
+ raise KeyError
+ c = _complexes_cache[currentfactor]
+ complexes.extend([(i, currentfactor, k) for i in c])
+ except KeyError:
+ complex_part = cls._get_complexes_sqf(currentfactor, use_cache)
+ new = [(root, currentfactor, k) for root in complex_part]
+ complexes.extend(new)
+
+ complexes = cls._complexes_sorted(complexes)
+ return complexes
+
+ @classmethod
+ def _reals_sorted(cls, reals):
+ """Make real isolating intervals disjoint and sort roots. """
+ cache = {}
+
+ for i, (u, f, k) in enumerate(reals):
+ for j, (v, g, m) in enumerate(reals[i + 1:]):
+ u, v = u.refine_disjoint(v)
+ reals[i + j + 1] = (v, g, m)
+
+ reals[i] = (u, f, k)
+
+ reals = sorted(reals, key=lambda r: r[0].a)
+
+ for root, currentfactor, _ in reals:
+ if currentfactor in cache:
+ cache[currentfactor].append(root)
+ else:
+ cache[currentfactor] = [root]
+
+ for currentfactor, root in cache.items():
+ _reals_cache[currentfactor] = root
+
+ return reals
+
+ @classmethod
+ def _refine_imaginary(cls, complexes):
+ sifted = sift(complexes, lambda c: c[1])
+ complexes = []
+ for f in ordered(sifted):
+ nimag = _imag_count_of_factor(f)
+ if nimag == 0:
+ # refine until xbounds are neg or pos
+ for u, f, k in sifted[f]:
+ while u.ax*u.bx <= 0:
+ u = u._inner_refine()
+ complexes.append((u, f, k))
+ else:
+ # refine until all but nimag xbounds are neg or pos
+ potential_imag = list(range(len(sifted[f])))
+ while True:
+ assert len(potential_imag) > 1
+ for i in list(potential_imag):
+ u, f, k = sifted[f][i]
+ if u.ax*u.bx > 0:
+ potential_imag.remove(i)
+ elif u.ax != u.bx:
+ u = u._inner_refine()
+ sifted[f][i] = u, f, k
+ if len(potential_imag) == nimag:
+ break
+ complexes.extend(sifted[f])
+ return complexes
+
+ @classmethod
+ def _refine_complexes(cls, complexes):
+ """return complexes such that no bounding rectangles of non-conjugate
+ roots would intersect. In addition, assure that neither ay nor by is
+ 0 to guarantee that non-real roots are distinct from real roots in
+ terms of the y-bounds.
+ """
+ # get the intervals pairwise-disjoint.
+ # If rectangles were drawn around the coordinates of the bounding
+ # rectangles, no rectangles would intersect after this procedure.
+ for i, (u, f, k) in enumerate(complexes):
+ for j, (v, g, m) in enumerate(complexes[i + 1:]):
+ u, v = u.refine_disjoint(v)
+ complexes[i + j + 1] = (v, g, m)
+
+ complexes[i] = (u, f, k)
+
+ # refine until the x-bounds are unambiguously positive or negative
+ # for non-imaginary roots
+ complexes = cls._refine_imaginary(complexes)
+
+ # make sure that all y bounds are off the real axis
+ # and on the same side of the axis
+ for i, (u, f, k) in enumerate(complexes):
+ while u.ay*u.by <= 0:
+ u = u.refine()
+ complexes[i] = u, f, k
+ return complexes
+
+ @classmethod
+ def _complexes_sorted(cls, complexes):
+ """Make complex isolating intervals disjoint and sort roots. """
+ complexes = cls._refine_complexes(complexes)
+ # XXX don't sort until you are sure that it is compatible
+ # with the indexing method but assert that the desired state
+ # is not broken
+ C, F = 0, 1 # location of ComplexInterval and factor
+ fs = {i[F] for i in complexes}
+ for i in range(1, len(complexes)):
+ if complexes[i][F] != complexes[i - 1][F]:
+ # if this fails the factors of a root were not
+ # contiguous because a discontinuity should only
+ # happen once
+ fs.remove(complexes[i - 1][F])
+ for i, cmplx in enumerate(complexes):
+ # negative im part (conj=True) comes before
+ # positive im part (conj=False)
+ assert cmplx[C].conj is (i % 2 == 0)
+
+ # update cache
+ cache = {}
+ # -- collate
+ for root, currentfactor, _ in complexes:
+ cache.setdefault(currentfactor, []).append(root)
+ # -- store
+ for currentfactor, root in cache.items():
+ _complexes_cache[currentfactor] = root
+
+ return complexes
+
+ @classmethod
+ def _reals_index(cls, reals, index):
+ """
+ Map initial real root index to an index in a factor where
+ the root belongs.
+ """
+ i = 0
+
+ for j, (_, currentfactor, k) in enumerate(reals):
+ if index < i + k:
+ poly, index = currentfactor, 0
+
+ for _, currentfactor, _ in reals[:j]:
+ if currentfactor == poly:
+ index += 1
+
+ return poly, index
+ else:
+ i += k
+
+ @classmethod
+ def _complexes_index(cls, complexes, index):
+ """
+ Map initial complex root index to an index in a factor where
+ the root belongs.
+ """
+ i = 0
+ for j, (_, currentfactor, k) in enumerate(complexes):
+ if index < i + k:
+ poly, index = currentfactor, 0
+
+ for _, currentfactor, _ in complexes[:j]:
+ if currentfactor == poly:
+ index += 1
+
+ index += len(_reals_cache[poly])
+
+ return poly, index
+ else:
+ i += k
+
+ @classmethod
+ def _count_roots(cls, roots):
+ """Count the number of real or complex roots with multiplicities."""
+ return sum(k for _, _, k in roots)
+
+ @classmethod
+ def _indexed_root(cls, poly, index, lazy=False):
+ """Get a root of a composite polynomial by index. """
+ factors = _pure_factors(poly)
+
+ # If the given poly is already irreducible, then the index does not
+ # need to be adjusted, and we can postpone the heavy lifting of
+ # computing and refining isolating intervals until that is needed.
+ # Note, however, that `_pure_factors()` extracts a negative leading
+ # coeff if present, so `factors[0][0]` may differ from `poly`, and
+ # is the "normalized" version of `poly` that we must return.
+ if lazy and len(factors) == 1 and factors[0][1] == 1:
+ return factors[0][0], index
+
+ reals = cls._get_reals(factors)
+ reals_count = cls._count_roots(reals)
+
+ if index < reals_count:
+ return cls._reals_index(reals, index)
+ else:
+ complexes = cls._get_complexes(factors)
+ return cls._complexes_index(complexes, index - reals_count)
+
+ def _ensure_reals_init(self):
+ """Ensure that our poly has entries in the reals cache. """
+ if self.poly not in _reals_cache:
+ self._indexed_root(self.poly, self.index)
+
+ def _ensure_complexes_init(self):
+ """Ensure that our poly has entries in the complexes cache. """
+ if self.poly not in _complexes_cache:
+ self._indexed_root(self.poly, self.index)
+
+ @classmethod
+ def _real_roots(cls, poly):
+ """Get real roots of a composite polynomial. """
+ factors = _pure_factors(poly)
+
+ reals = cls._get_reals(factors)
+ reals_count = cls._count_roots(reals)
+
+ roots = []
+
+ for index in range(0, reals_count):
+ roots.append(cls._reals_index(reals, index))
+
+ return roots
+
+ def _reset(self):
+ """
+ Reset all intervals
+ """
+ self._all_roots(self.poly, use_cache=False)
+
+ @classmethod
+ def _all_roots(cls, poly, use_cache=True):
+ """Get real and complex roots of a composite polynomial. """
+ factors = _pure_factors(poly)
+
+ reals = cls._get_reals(factors, use_cache=use_cache)
+ reals_count = cls._count_roots(reals)
+
+ roots = []
+
+ for index in range(0, reals_count):
+ roots.append(cls._reals_index(reals, index))
+
+ complexes = cls._get_complexes(factors, use_cache=use_cache)
+ complexes_count = cls._count_roots(complexes)
+
+ for index in range(0, complexes_count):
+ roots.append(cls._complexes_index(complexes, index))
+
+ return roots
+
+ @classmethod
+ @cacheit
+ def _roots_trivial(cls, poly, radicals):
+ """Compute roots in linear, quadratic and binomial cases. """
+ if poly.degree() == 1:
+ return roots_linear(poly)
+
+ if not radicals:
+ return None
+
+ if poly.degree() == 2:
+ return roots_quadratic(poly)
+ elif poly.length() == 2 and poly.TC():
+ return roots_binomial(poly)
+ else:
+ return None
+
+ @classmethod
+ def _preprocess_roots(cls, poly):
+ """Take heroic measures to make ``poly`` compatible with ``CRootOf``."""
+ dom = poly.get_domain()
+
+ if not dom.is_Exact:
+ poly = poly.to_exact()
+
+ coeff, poly = preprocess_roots(poly)
+ dom = poly.get_domain()
+
+ if not dom.is_ZZ:
+ raise NotImplementedError(
+ "sorted roots not supported over %s" % dom)
+
+ return coeff, poly
+
+ @classmethod
+ def _postprocess_root(cls, root, radicals):
+ """Return the root if it is trivial or a ``CRootOf`` object. """
+ poly, index = root
+ roots = cls._roots_trivial(poly, radicals)
+
+ if roots is not None:
+ return roots[index]
+ else:
+ return cls._new(poly, index)
+
+ @classmethod
+ def _get_roots(cls, method, poly, radicals):
+ """Return postprocessed roots of specified kind. """
+ if not poly.is_univariate:
+ raise PolynomialError("only univariate polynomials are allowed")
+ # get rid of gen and it's free symbol
+ d = Dummy()
+ poly = poly.subs(poly.gen, d)
+ x = symbols('x')
+ # see what others are left and select x or a numbered x
+ # that doesn't clash
+ free_names = {str(i) for i in poly.free_symbols}
+ for x in chain((symbols('x'),), numbered_symbols('x')):
+ if x.name not in free_names:
+ poly = poly.xreplace({d: x})
+ break
+ coeff, poly = cls._preprocess_roots(poly)
+ roots = []
+
+ for root in getattr(cls, method)(poly):
+ roots.append(coeff*cls._postprocess_root(root, radicals))
+ return roots
+
+ @classmethod
+ def clear_cache(cls):
+ """Reset cache for reals and complexes.
+
+ The intervals used to approximate a root instance are updated
+ as needed. When a request is made to see the intervals, the
+ most current values are shown. `clear_cache` will reset all
+ CRootOf instances back to their original state.
+
+ See Also
+ ========
+
+ _reset
+ """
+ global _reals_cache, _complexes_cache
+ _reals_cache = _pure_key_dict()
+ _complexes_cache = _pure_key_dict()
+
+ def _get_interval(self):
+ """Internal function for retrieving isolation interval from cache. """
+ self._ensure_reals_init()
+ if self.is_real:
+ return _reals_cache[self.poly][self.index]
+ else:
+ reals_count = len(_reals_cache[self.poly])
+ self._ensure_complexes_init()
+ return _complexes_cache[self.poly][self.index - reals_count]
+
+ def _set_interval(self, interval):
+ """Internal function for updating isolation interval in cache. """
+ self._ensure_reals_init()
+ if self.is_real:
+ _reals_cache[self.poly][self.index] = interval
+ else:
+ reals_count = len(_reals_cache[self.poly])
+ self._ensure_complexes_init()
+ _complexes_cache[self.poly][self.index - reals_count] = interval
+
+ def _eval_subs(self, old, new):
+ # don't allow subs to change anything
+ return self
+
+ def _eval_conjugate(self):
+ if self.is_real:
+ return self
+ expr, i = self.args
+ return self.func(expr, i + (1 if self._get_interval().conj else -1))
+
+ def eval_approx(self, n, return_mpmath=False):
+ """Evaluate this complex root to the given precision.
+
+ This uses secant method and root bounds are used to both
+ generate an initial guess and to check that the root
+ returned is valid. If ever the method converges outside the
+ root bounds, the bounds will be made smaller and updated.
+ """
+ prec = dps_to_prec(n)
+ with workprec(prec):
+ g = self.poly.gen
+ if not g.is_Symbol:
+ d = Dummy('x')
+ if self.is_imaginary:
+ d *= I
+ func = lambdify(d, self.expr.subs(g, d))
+ else:
+ expr = self.expr
+ if self.is_imaginary:
+ expr = self.expr.subs(g, I*g)
+ func = lambdify(g, expr)
+
+ interval = self._get_interval()
+ while True:
+ if self.is_real:
+ a = mpf(str(interval.a))
+ b = mpf(str(interval.b))
+ if a == b:
+ root = a
+ break
+ x0 = mpf(str(interval.center))
+ x1 = x0 + mpf(str(interval.dx))/4
+ elif self.is_imaginary:
+ a = mpf(str(interval.ay))
+ b = mpf(str(interval.by))
+ if a == b:
+ root = mpc(mpf('0'), a)
+ break
+ x0 = mpf(str(interval.center[1]))
+ x1 = x0 + mpf(str(interval.dy))/4
+ else:
+ ax = mpf(str(interval.ax))
+ bx = mpf(str(interval.bx))
+ ay = mpf(str(interval.ay))
+ by = mpf(str(interval.by))
+ if ax == bx and ay == by:
+ root = mpc(ax, ay)
+ break
+ x0 = mpc(*map(str, interval.center))
+ x1 = x0 + mpc(*map(str, (interval.dx, interval.dy)))/4
+ try:
+ # without a tolerance, this will return when (to within
+ # the given precision) x_i == x_{i-1}
+ root = findroot(func, (x0, x1))
+ # If the (real or complex) root is not in the 'interval',
+ # then keep refining the interval. This happens if findroot
+ # accidentally finds a different root outside of this
+ # interval because our initial estimate 'x0' was not close
+ # enough. It is also possible that the secant method will
+ # get trapped by a max/min in the interval; the root
+ # verification by findroot will raise a ValueError in this
+ # case and the interval will then be tightened -- and
+ # eventually the root will be found.
+ #
+ # It is also possible that findroot will not have any
+ # successful iterations to process (in which case it
+ # will fail to initialize a variable that is tested
+ # after the iterations and raise an UnboundLocalError).
+ if self.is_real or self.is_imaginary:
+ if not bool(root.imag) == self.is_real and (
+ a <= root <= b):
+ if self.is_imaginary:
+ root = mpc(mpf('0'), root.real)
+ break
+ elif (ax <= root.real <= bx and ay <= root.imag <= by):
+ break
+ except (UnboundLocalError, ValueError):
+ pass
+ interval = interval.refine()
+
+ # update the interval so we at least (for this precision or
+ # less) don't have much work to do to recompute the root
+ self._set_interval(interval)
+ if return_mpmath:
+ return root
+ return (Float._new(root.real._mpf_, prec) +
+ I*Float._new(root.imag._mpf_, prec))
+
+ def _eval_evalf(self, prec, **kwargs):
+ """Evaluate this complex root to the given precision."""
+ # all kwargs are ignored
+ return self.eval_rational(n=prec_to_dps(prec))._evalf(prec)
+
+ def eval_rational(self, dx=None, dy=None, n=15):
+ """
+ Return a Rational approximation of ``self`` that has real
+ and imaginary component approximations that are within ``dx``
+ and ``dy`` of the true values, respectively. Alternatively,
+ ``n`` digits of precision can be specified.
+
+ The interval is refined with bisection and is sure to
+ converge. The root bounds are updated when the refinement
+ is complete so recalculation at the same or lesser precision
+ will not have to repeat the refinement and should be much
+ faster.
+
+ The following example first obtains Rational approximation to
+ 1e-8 accuracy for all roots of the 4-th order Legendre
+ polynomial. Since the roots are all less than 1, this will
+ ensure the decimal representation of the approximation will be
+ correct (including rounding) to 6 digits:
+
+ >>> from sympy import legendre_poly, Symbol
+ >>> x = Symbol("x")
+ >>> p = legendre_poly(4, x, polys=True)
+ >>> r = p.real_roots()[-1]
+ >>> r.eval_rational(10**-8).n(6)
+ 0.861136
+
+ It is not necessary to a two-step calculation, however: the
+ decimal representation can be computed directly:
+
+ >>> r.evalf(17)
+ 0.86113631159405258
+
+ """
+ dy = dy or dx
+ if dx:
+ rtol = None
+ dx = dx if isinstance(dx, Rational) else Rational(str(dx))
+ dy = dy if isinstance(dy, Rational) else Rational(str(dy))
+ else:
+ # 5 binary (or 2 decimal) digits are needed to ensure that
+ # a given digit is correctly rounded
+ # prec_to_dps(dps_to_prec(n) + 5) - n <= 2 (tested for
+ # n in range(1000000)
+ rtol = S(10)**-(n + 2) # +2 for guard digits
+ interval = self._get_interval()
+ while True:
+ if self.is_real:
+ if rtol:
+ dx = abs(interval.center*rtol)
+ interval = interval.refine_size(dx=dx)
+ c = interval.center
+ real = Rational(c)
+ imag = S.Zero
+ if not rtol or interval.dx < abs(c*rtol):
+ break
+ elif self.is_imaginary:
+ if rtol:
+ dy = abs(interval.center[1]*rtol)
+ dx = 1
+ interval = interval.refine_size(dx=dx, dy=dy)
+ c = interval.center[1]
+ imag = Rational(c)
+ real = S.Zero
+ if not rtol or interval.dy < abs(c*rtol):
+ break
+ else:
+ if rtol:
+ dx = abs(interval.center[0]*rtol)
+ dy = abs(interval.center[1]*rtol)
+ interval = interval.refine_size(dx, dy)
+ c = interval.center
+ real, imag = map(Rational, c)
+ if not rtol or (
+ interval.dx < abs(c[0]*rtol) and
+ interval.dy < abs(c[1]*rtol)):
+ break
+
+ # update the interval so we at least (for this precision or
+ # less) don't have much work to do to recompute the root
+ self._set_interval(interval)
+ return real + I*imag
+
+
+CRootOf = ComplexRootOf
+
+
+@dispatch(ComplexRootOf, ComplexRootOf)
+def _eval_is_eq(lhs, rhs): # noqa:F811
+ # if we use is_eq to check here, we get infinite recurion
+ return lhs == rhs
+
+
+@dispatch(ComplexRootOf, Basic) # type:ignore
+def _eval_is_eq(lhs, rhs): # noqa:F811
+ # CRootOf represents a Root, so if rhs is that root, it should set
+ # the expression to zero *and* it should be in the interval of the
+ # CRootOf instance. It must also be a number that agrees with the
+ # is_real value of the CRootOf instance.
+ if not rhs.is_number:
+ return None
+ if not rhs.is_finite:
+ return False
+ z = lhs.expr.subs(lhs.expr.free_symbols.pop(), rhs).is_zero
+ if z is False: # all roots will make z True but we don't know
+ # whether this is the right root if z is True
+ return False
+ o = rhs.is_real, rhs.is_imaginary
+ s = lhs.is_real, lhs.is_imaginary
+ assert None not in s # this is part of initial refinement
+ if o != s and None not in o:
+ return False
+ re, im = rhs.as_real_imag()
+ if lhs.is_real:
+ if im:
+ return False
+ i = lhs._get_interval()
+ a, b = [Rational(str(_)) for _ in (i.a, i.b)]
+ return sympify(a <= rhs and rhs <= b)
+ i = lhs._get_interval()
+ r1, r2, i1, i2 = [Rational(str(j)) for j in (
+ i.ax, i.bx, i.ay, i.by)]
+ return is_le(r1, re) and is_le(re,r2) and is_le(i1,im) and is_le(im,i2)
+
+
+@public
+class RootSum(Expr):
+ """Represents a sum of all roots of a univariate polynomial. """
+
+ __slots__ = ('poly', 'fun', 'auto')
+
+ def __new__(cls, expr, func=None, x=None, auto=True, quadratic=False):
+ """Construct a new ``RootSum`` instance of roots of a polynomial."""
+ coeff, poly = cls._transform(expr, x)
+
+ if not poly.is_univariate:
+ raise MultivariatePolynomialError(
+ "only univariate polynomials are allowed")
+
+ if func is None:
+ func = Lambda(poly.gen, poly.gen)
+ else:
+ is_func = getattr(func, 'is_Function', False)
+
+ if is_func and 1 in func.nargs:
+ if not isinstance(func, Lambda):
+ func = Lambda(poly.gen, func(poly.gen))
+ else:
+ raise ValueError(
+ "expected a univariate function, got %s" % func)
+
+ var, expr = func.variables[0], func.expr
+
+ if coeff is not S.One:
+ expr = expr.subs(var, coeff*var)
+
+ deg = poly.degree()
+
+ if not expr.has(var):
+ return deg*expr
+
+ if expr.is_Add:
+ add_const, expr = expr.as_independent(var)
+ else:
+ add_const = S.Zero
+
+ if expr.is_Mul:
+ mul_const, expr = expr.as_independent(var)
+ else:
+ mul_const = S.One
+
+ func = Lambda(var, expr)
+
+ rational = cls._is_func_rational(poly, func)
+ factors, terms = _pure_factors(poly), []
+
+ for poly, k in factors:
+ if poly.is_linear:
+ term = func(roots_linear(poly)[0])
+ elif quadratic and poly.is_quadratic:
+ term = sum(map(func, roots_quadratic(poly)))
+ else:
+ if not rational or not auto:
+ term = cls._new(poly, func, auto)
+ else:
+ term = cls._rational_case(poly, func)
+
+ terms.append(k*term)
+
+ return mul_const*Add(*terms) + deg*add_const
+
+ @classmethod
+ def _new(cls, poly, func, auto=True):
+ """Construct new raw ``RootSum`` instance. """
+ obj = Expr.__new__(cls)
+
+ obj.poly = poly
+ obj.fun = func
+ obj.auto = auto
+
+ return obj
+
+ @classmethod
+ def new(cls, poly, func, auto=True):
+ """Construct new ``RootSum`` instance. """
+ if not func.expr.has(*func.variables):
+ return func.expr
+
+ rational = cls._is_func_rational(poly, func)
+
+ if not rational or not auto:
+ return cls._new(poly, func, auto)
+ else:
+ return cls._rational_case(poly, func)
+
+ @classmethod
+ def _transform(cls, expr, x):
+ """Transform an expression to a polynomial. """
+ poly = PurePoly(expr, x, greedy=False)
+ return preprocess_roots(poly)
+
+ @classmethod
+ def _is_func_rational(cls, poly, func):
+ """Check if a lambda is a rational function. """
+ var, expr = func.variables[0], func.expr
+ return expr.is_rational_function(var)
+
+ @classmethod
+ def _rational_case(cls, poly, func):
+ """Handle the rational function case. """
+ roots = symbols('r:%d' % poly.degree())
+ var, expr = func.variables[0], func.expr
+
+ f = sum(expr.subs(var, r) for r in roots)
+ p, q = together(f).as_numer_denom()
+
+ domain = QQ[roots]
+
+ p = p.expand()
+ q = q.expand()
+
+ try:
+ p = Poly(p, domain=domain, expand=False)
+ except GeneratorsNeeded:
+ p, p_coeff = None, (p,)
+ else:
+ p_monom, p_coeff = zip(*p.terms())
+
+ try:
+ q = Poly(q, domain=domain, expand=False)
+ except GeneratorsNeeded:
+ q, q_coeff = None, (q,)
+ else:
+ q_monom, q_coeff = zip(*q.terms())
+
+ coeffs, mapping = symmetrize(p_coeff + q_coeff, formal=True)
+ formulas, values = viete(poly, roots), []
+
+ for (sym, _), (_, val) in zip(mapping, formulas):
+ values.append((sym, val))
+
+ for i, (coeff, _) in enumerate(coeffs):
+ coeffs[i] = coeff.subs(values)
+
+ n = len(p_coeff)
+
+ p_coeff = coeffs[:n]
+ q_coeff = coeffs[n:]
+
+ if p is not None:
+ p = Poly(dict(zip(p_monom, p_coeff)), *p.gens).as_expr()
+ else:
+ (p,) = p_coeff
+
+ if q is not None:
+ q = Poly(dict(zip(q_monom, q_coeff)), *q.gens).as_expr()
+ else:
+ (q,) = q_coeff
+
+ return factor(p/q)
+
+ def _hashable_content(self):
+ return (self.poly, self.fun)
+
+ @property
+ def expr(self):
+ return self.poly.as_expr()
+
+ @property
+ def args(self):
+ return (self.expr, self.fun, self.poly.gen)
+
+ @property
+ def free_symbols(self):
+ return self.poly.free_symbols | self.fun.free_symbols
+
+ @property
+ def is_commutative(self):
+ return True
+
+ def doit(self, **hints):
+ if not hints.get('roots', True):
+ return self
+
+ _roots = roots(self.poly, multiple=True)
+
+ if len(_roots) < self.poly.degree():
+ return self
+ else:
+ return Add(*[self.fun(r) for r in _roots])
+
+ def _eval_evalf(self, prec):
+ try:
+ _roots = self.poly.nroots(n=prec_to_dps(prec))
+ except (DomainError, PolynomialError):
+ return self
+ else:
+ return Add(*[self.fun(r) for r in _roots])
+
+ def _eval_derivative(self, x):
+ var, expr = self.fun.args
+ func = Lambda(var, expr.diff(x))
+ return self.new(self.poly, func, self.auto)
diff --git a/lib/python3.12/site-packages/sympy/polys/solvers.py b/lib/python3.12/site-packages/sympy/polys/solvers.py
new file mode 100644
index 0000000000000000000000000000000000000000..b333e81d975a8cd71e7eb683c2b943d8538f6ac5
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/solvers.py
@@ -0,0 +1,435 @@
+"""Low-level linear systems solver. """
+
+
+from sympy.utilities.exceptions import sympy_deprecation_warning
+from sympy.utilities.iterables import connected_components
+
+from sympy.core.sympify import sympify
+from sympy.core.numbers import Integer, Rational
+from sympy.matrices.dense import MutableDenseMatrix
+from sympy.polys.domains import ZZ, QQ
+
+from sympy.polys.domains import EX
+from sympy.polys.rings import sring
+from sympy.polys.polyerrors import NotInvertible
+from sympy.polys.domainmatrix import DomainMatrix
+
+
+class PolyNonlinearError(Exception):
+ """Raised by solve_lin_sys for nonlinear equations"""
+ pass
+
+
+class RawMatrix(MutableDenseMatrix):
+ """
+ .. deprecated:: 1.9
+
+ This class fundamentally is broken by design. Use ``DomainMatrix`` if
+ you want a matrix over the polys domains or ``Matrix`` for a matrix
+ with ``Expr`` elements. The ``RawMatrix`` class will be removed/broken
+ in future in order to reestablish the invariant that the elements of a
+ Matrix should be of type ``Expr``.
+
+ """
+ _sympify = staticmethod(lambda x, *args, **kwargs: x)
+
+ def __init__(self, *args, **kwargs):
+ sympy_deprecation_warning(
+ """
+ The RawMatrix class is deprecated. Use either DomainMatrix or
+ Matrix instead.
+ """,
+ deprecated_since_version="1.9",
+ active_deprecations_target="deprecated-rawmatrix",
+ )
+
+ domain = ZZ
+ for i in range(self.rows):
+ for j in range(self.cols):
+ val = self[i,j]
+ if getattr(val, 'is_Poly', False):
+ K = val.domain[val.gens]
+ val_sympy = val.as_expr()
+ elif hasattr(val, 'parent'):
+ K = val.parent()
+ val_sympy = K.to_sympy(val)
+ elif isinstance(val, (int, Integer)):
+ K = ZZ
+ val_sympy = sympify(val)
+ elif isinstance(val, Rational):
+ K = QQ
+ val_sympy = val
+ else:
+ for K in ZZ, QQ:
+ if K.of_type(val):
+ val_sympy = K.to_sympy(val)
+ break
+ else:
+ raise TypeError
+ domain = domain.unify(K)
+ self[i,j] = val_sympy
+ self.ring = domain
+
+
+def eqs_to_matrix(eqs_coeffs, eqs_rhs, gens, domain):
+ """Get matrix from linear equations in dict format.
+
+ Explanation
+ ===========
+
+ Get the matrix representation of a system of linear equations represented
+ as dicts with low-level DomainElement coefficients. This is an
+ *internal* function that is used by solve_lin_sys.
+
+ Parameters
+ ==========
+
+ eqs_coeffs: list[dict[Symbol, DomainElement]]
+ The left hand sides of the equations as dicts mapping from symbols to
+ coefficients where the coefficients are instances of
+ DomainElement.
+ eqs_rhs: list[DomainElements]
+ The right hand sides of the equations as instances of
+ DomainElement.
+ gens: list[Symbol]
+ The unknowns in the system of equations.
+ domain: Domain
+ The domain for coefficients of both lhs and rhs.
+
+ Returns
+ =======
+
+ The augmented matrix representation of the system as a DomainMatrix.
+
+ Examples
+ ========
+
+ >>> from sympy import symbols, ZZ
+ >>> from sympy.polys.solvers import eqs_to_matrix
+ >>> x, y = symbols('x, y')
+ >>> eqs_coeff = [{x:ZZ(1), y:ZZ(1)}, {x:ZZ(1), y:ZZ(-1)}]
+ >>> eqs_rhs = [ZZ(0), ZZ(-1)]
+ >>> eqs_to_matrix(eqs_coeff, eqs_rhs, [x, y], ZZ)
+ DomainMatrix([[1, 1, 0], [1, -1, 1]], (2, 3), ZZ)
+
+ See also
+ ========
+
+ solve_lin_sys: Uses :func:`~eqs_to_matrix` internally
+ """
+ sym2index = {x: n for n, x in enumerate(gens)}
+ nrows = len(eqs_coeffs)
+ ncols = len(gens) + 1
+ rows = [[domain.zero] * ncols for _ in range(nrows)]
+ for row, eq_coeff, eq_rhs in zip(rows, eqs_coeffs, eqs_rhs):
+ for sym, coeff in eq_coeff.items():
+ row[sym2index[sym]] = domain.convert(coeff)
+ row[-1] = -domain.convert(eq_rhs)
+
+ return DomainMatrix(rows, (nrows, ncols), domain)
+
+
+def sympy_eqs_to_ring(eqs, symbols):
+ """Convert a system of equations from Expr to a PolyRing
+
+ Explanation
+ ===========
+
+ High-level functions like ``solve`` expect Expr as inputs but can use
+ ``solve_lin_sys`` internally. This function converts equations from
+ ``Expr`` to the low-level poly types used by the ``solve_lin_sys``
+ function.
+
+ Parameters
+ ==========
+
+ eqs: List of Expr
+ A list of equations as Expr instances
+ symbols: List of Symbol
+ A list of the symbols that are the unknowns in the system of
+ equations.
+
+ Returns
+ =======
+
+ Tuple[List[PolyElement], Ring]: The equations as PolyElement instances
+ and the ring of polynomials within which each equation is represented.
+
+ Examples
+ ========
+
+ >>> from sympy import symbols
+ >>> from sympy.polys.solvers import sympy_eqs_to_ring
+ >>> a, x, y = symbols('a, x, y')
+ >>> eqs = [x-y, x+a*y]
+ >>> eqs_ring, ring = sympy_eqs_to_ring(eqs, [x, y])
+ >>> eqs_ring
+ [x - y, x + a*y]
+ >>> type(eqs_ring[0])
+
+ >>> ring
+ ZZ(a)[x,y]
+
+ With the equations in this form they can be passed to ``solve_lin_sys``:
+
+ >>> from sympy.polys.solvers import solve_lin_sys
+ >>> solve_lin_sys(eqs_ring, ring)
+ {y: 0, x: 0}
+ """
+ try:
+ K, eqs_K = sring(eqs, symbols, field=True, extension=True)
+ except NotInvertible:
+ # https://github.com/sympy/sympy/issues/18874
+ K, eqs_K = sring(eqs, symbols, domain=EX)
+ return eqs_K, K.to_domain()
+
+
+def solve_lin_sys(eqs, ring, _raw=True):
+ """Solve a system of linear equations from a PolynomialRing
+
+ Explanation
+ ===========
+
+ Solves a system of linear equations given as PolyElement instances of a
+ PolynomialRing. The basic arithmetic is carried out using instance of
+ DomainElement which is more efficient than :class:`~sympy.core.expr.Expr`
+ for the most common inputs.
+
+ While this is a public function it is intended primarily for internal use
+ so its interface is not necessarily convenient. Users are suggested to use
+ the :func:`sympy.solvers.solveset.linsolve` function (which uses this
+ function internally) instead.
+
+ Parameters
+ ==========
+
+ eqs: list[PolyElement]
+ The linear equations to be solved as elements of a
+ PolynomialRing (assumed equal to zero).
+ ring: PolynomialRing
+ The polynomial ring from which eqs are drawn. The generators of this
+ ring are the unknowns to be solved for and the domain of the ring is
+ the domain of the coefficients of the system of equations.
+ _raw: bool
+ If *_raw* is False, the keys and values in the returned dictionary
+ will be of type Expr (and the unit of the field will be removed from
+ the keys) otherwise the low-level polys types will be returned, e.g.
+ PolyElement: PythonRational.
+
+ Returns
+ =======
+
+ ``None`` if the system has no solution.
+
+ dict[Symbol, Expr] if _raw=False
+
+ dict[Symbol, DomainElement] if _raw=True.
+
+ Examples
+ ========
+
+ >>> from sympy import symbols
+ >>> from sympy.polys.solvers import solve_lin_sys, sympy_eqs_to_ring
+ >>> x, y = symbols('x, y')
+ >>> eqs = [x - y, x + y - 2]
+ >>> eqs_ring, ring = sympy_eqs_to_ring(eqs, [x, y])
+ >>> solve_lin_sys(eqs_ring, ring)
+ {y: 1, x: 1}
+
+ Passing ``_raw=False`` returns the same result except that the keys are
+ ``Expr`` rather than low-level poly types.
+
+ >>> solve_lin_sys(eqs_ring, ring, _raw=False)
+ {x: 1, y: 1}
+
+ See also
+ ========
+
+ sympy_eqs_to_ring: prepares the inputs to ``solve_lin_sys``.
+ linsolve: ``linsolve`` uses ``solve_lin_sys`` internally.
+ sympy.solvers.solvers.solve: ``solve`` uses ``solve_lin_sys`` internally.
+ """
+ as_expr = not _raw
+
+ assert ring.domain.is_Field
+
+ eqs_dict = [dict(eq) for eq in eqs]
+
+ one_monom = ring.one.monoms()[0]
+ zero = ring.domain.zero
+
+ eqs_rhs = []
+ eqs_coeffs = []
+ for eq_dict in eqs_dict:
+ eq_rhs = eq_dict.pop(one_monom, zero)
+ eq_coeffs = {}
+ for monom, coeff in eq_dict.items():
+ if sum(monom) != 1:
+ msg = "Nonlinear term encountered in solve_lin_sys"
+ raise PolyNonlinearError(msg)
+ eq_coeffs[ring.gens[monom.index(1)]] = coeff
+ if not eq_coeffs:
+ if not eq_rhs:
+ continue
+ else:
+ return None
+ eqs_rhs.append(eq_rhs)
+ eqs_coeffs.append(eq_coeffs)
+
+ result = _solve_lin_sys(eqs_coeffs, eqs_rhs, ring)
+
+ if result is not None and as_expr:
+
+ def to_sympy(x):
+ as_expr = getattr(x, 'as_expr', None)
+ if as_expr:
+ return as_expr()
+ else:
+ return ring.domain.to_sympy(x)
+
+ tresult = {to_sympy(sym): to_sympy(val) for sym, val in result.items()}
+
+ # Remove 1.0x
+ result = {}
+ for k, v in tresult.items():
+ if k.is_Mul:
+ c, s = k.as_coeff_Mul()
+ result[s] = v/c
+ else:
+ result[k] = v
+
+ return result
+
+
+def _solve_lin_sys(eqs_coeffs, eqs_rhs, ring):
+ """Solve a linear system from dict of PolynomialRing coefficients
+
+ Explanation
+ ===========
+
+ This is an **internal** function used by :func:`solve_lin_sys` after the
+ equations have been preprocessed. The role of this function is to split
+ the system into connected components and pass those to
+ :func:`_solve_lin_sys_component`.
+
+ Examples
+ ========
+
+ Setup a system for $x-y=0$ and $x+y=2$ and solve:
+
+ >>> from sympy import symbols, sring
+ >>> from sympy.polys.solvers import _solve_lin_sys
+ >>> x, y = symbols('x, y')
+ >>> R, (xr, yr) = sring([x, y], [x, y])
+ >>> eqs = [{xr:R.one, yr:-R.one}, {xr:R.one, yr:R.one}]
+ >>> eqs_rhs = [R.zero, -2*R.one]
+ >>> _solve_lin_sys(eqs, eqs_rhs, R)
+ {y: 1, x: 1}
+
+ See also
+ ========
+
+ solve_lin_sys: This function is used internally by :func:`solve_lin_sys`.
+ """
+ V = ring.gens
+ E = []
+ for eq_coeffs in eqs_coeffs:
+ syms = list(eq_coeffs)
+ E.extend(zip(syms[:-1], syms[1:]))
+ G = V, E
+
+ components = connected_components(G)
+
+ sym2comp = {}
+ for n, component in enumerate(components):
+ for sym in component:
+ sym2comp[sym] = n
+
+ subsystems = [([], []) for _ in range(len(components))]
+ for eq_coeff, eq_rhs in zip(eqs_coeffs, eqs_rhs):
+ sym = next(iter(eq_coeff), None)
+ sub_coeff, sub_rhs = subsystems[sym2comp[sym]]
+ sub_coeff.append(eq_coeff)
+ sub_rhs.append(eq_rhs)
+
+ sol = {}
+ for subsystem in subsystems:
+ subsol = _solve_lin_sys_component(subsystem[0], subsystem[1], ring)
+ if subsol is None:
+ return None
+ sol.update(subsol)
+
+ return sol
+
+
+def _solve_lin_sys_component(eqs_coeffs, eqs_rhs, ring):
+ """Solve a linear system from dict of PolynomialRing coefficients
+
+ Explanation
+ ===========
+
+ This is an **internal** function used by :func:`solve_lin_sys` after the
+ equations have been preprocessed. After :func:`_solve_lin_sys` splits the
+ system into connected components this function is called for each
+ component. The system of equations is solved using Gauss-Jordan
+ elimination with division followed by back-substitution.
+
+ Examples
+ ========
+
+ Setup a system for $x-y=0$ and $x+y=2$ and solve:
+
+ >>> from sympy import symbols, sring
+ >>> from sympy.polys.solvers import _solve_lin_sys_component
+ >>> x, y = symbols('x, y')
+ >>> R, (xr, yr) = sring([x, y], [x, y])
+ >>> eqs = [{xr:R.one, yr:-R.one}, {xr:R.one, yr:R.one}]
+ >>> eqs_rhs = [R.zero, -2*R.one]
+ >>> _solve_lin_sys_component(eqs, eqs_rhs, R)
+ {y: 1, x: 1}
+
+ See also
+ ========
+
+ solve_lin_sys: This function is used internally by :func:`solve_lin_sys`.
+ """
+
+ # transform from equations to matrix form
+ matrix = eqs_to_matrix(eqs_coeffs, eqs_rhs, ring.gens, ring.domain)
+
+ # convert to a field for rref
+ if not matrix.domain.is_Field:
+ matrix = matrix.to_field()
+
+ # solve by row-reduction
+ echelon, pivots = matrix.rref()
+
+ # construct the returnable form of the solutions
+ keys = ring.gens
+
+ if pivots and pivots[-1] == len(keys):
+ return None
+
+ if len(pivots) == len(keys):
+ sol = []
+ for s in [row[-1] for row in echelon.rep.to_ddm()]:
+ a = s
+ sol.append(a)
+ sols = dict(zip(keys, sol))
+ else:
+ sols = {}
+ g = ring.gens
+ # Extract ground domain coefficients and convert to the ring:
+ if hasattr(ring, 'ring'):
+ convert = ring.ring.ground_new
+ else:
+ convert = ring.ground_new
+ echelon = echelon.rep.to_ddm()
+ vals_set = {v for row in echelon for v in row}
+ vals_map = {v: convert(v) for v in vals_set}
+ echelon = [[vals_map[eij] for eij in ei] for ei in echelon]
+ for i, p in enumerate(pivots):
+ v = echelon[i][-1] - sum(echelon[i][j]*g[j] for j in range(p+1, len(g)) if echelon[i][j])
+ sols[keys[p]] = v
+
+ return sols
diff --git a/lib/python3.12/site-packages/sympy/polys/specialpolys.py b/lib/python3.12/site-packages/sympy/polys/specialpolys.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e85de8679cda3084f1c263a045f4d8f817bed98
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/specialpolys.py
@@ -0,0 +1,340 @@
+"""Functions for generating interesting polynomials, e.g. for benchmarking. """
+
+
+from sympy.core import Add, Mul, Symbol, sympify, Dummy, symbols
+from sympy.core.containers import Tuple
+from sympy.core.singleton import S
+from sympy.ntheory import nextprime
+from sympy.polys.densearith import (
+ dmp_add_term, dmp_neg, dmp_mul, dmp_sqr
+)
+from sympy.polys.densebasic import (
+ dmp_zero, dmp_one, dmp_ground,
+ dup_from_raw_dict, dmp_raise, dup_random
+)
+from sympy.polys.domains import ZZ
+from sympy.polys.factortools import dup_zz_cyclotomic_poly
+from sympy.polys.polyclasses import DMP
+from sympy.polys.polytools import Poly, PurePoly
+from sympy.polys.polyutils import _analyze_gens
+from sympy.utilities import subsets, public, filldedent
+
+
+@public
+def swinnerton_dyer_poly(n, x=None, polys=False):
+ """Generates n-th Swinnerton-Dyer polynomial in `x`.
+
+ Parameters
+ ----------
+ n : int
+ `n` decides the order of polynomial
+ x : optional
+ polys : bool, optional
+ ``polys=True`` returns an expression, otherwise
+ (default) returns an expression.
+ """
+ if n <= 0:
+ raise ValueError(
+ "Cannot generate Swinnerton-Dyer polynomial of order %s" % n)
+
+ if x is not None:
+ sympify(x)
+ else:
+ x = Dummy('x')
+
+ if n > 3:
+ from sympy.functions.elementary.miscellaneous import sqrt
+ from .numberfields import minimal_polynomial
+ p = 2
+ a = [sqrt(2)]
+ for i in range(2, n + 1):
+ p = nextprime(p)
+ a.append(sqrt(p))
+ return minimal_polynomial(Add(*a), x, polys=polys)
+
+ if n == 1:
+ ex = x**2 - 2
+ elif n == 2:
+ ex = x**4 - 10*x**2 + 1
+ elif n == 3:
+ ex = x**8 - 40*x**6 + 352*x**4 - 960*x**2 + 576
+
+ return PurePoly(ex, x) if polys else ex
+
+
+@public
+def cyclotomic_poly(n, x=None, polys=False):
+ """Generates cyclotomic polynomial of order `n` in `x`.
+
+ Parameters
+ ----------
+ n : int
+ `n` decides the order of polynomial
+ x : optional
+ polys : bool, optional
+ ``polys=True`` returns an expression, otherwise
+ (default) returns an expression.
+ """
+ if n <= 0:
+ raise ValueError(
+ "Cannot generate cyclotomic polynomial of order %s" % n)
+
+ poly = DMP(dup_zz_cyclotomic_poly(int(n), ZZ), ZZ)
+
+ if x is not None:
+ poly = Poly.new(poly, x)
+ else:
+ poly = PurePoly.new(poly, Dummy('x'))
+
+ return poly if polys else poly.as_expr()
+
+
+@public
+def symmetric_poly(n, *gens, polys=False):
+ """
+ Generates symmetric polynomial of order `n`.
+
+ Parameters
+ ==========
+
+ polys: bool, optional (default: False)
+ Returns a Poly object when ``polys=True``, otherwise
+ (default) returns an expression.
+ """
+ gens = _analyze_gens(gens)
+
+ if n < 0 or n > len(gens) or not gens:
+ raise ValueError("Cannot generate symmetric polynomial of order %s for %s" % (n, gens))
+ elif not n:
+ poly = S.One
+ else:
+ poly = Add(*[Mul(*s) for s in subsets(gens, int(n))])
+
+ return Poly(poly, *gens) if polys else poly
+
+
+@public
+def random_poly(x, n, inf, sup, domain=ZZ, polys=False):
+ """Generates a polynomial of degree ``n`` with coefficients in
+ ``[inf, sup]``.
+
+ Parameters
+ ----------
+ x
+ `x` is the independent term of polynomial
+ n : int
+ `n` decides the order of polynomial
+ inf
+ Lower limit of range in which coefficients lie
+ sup
+ Upper limit of range in which coefficients lie
+ domain : optional
+ Decides what ring the coefficients are supposed
+ to belong. Default is set to Integers.
+ polys : bool, optional
+ ``polys=True`` returns an expression, otherwise
+ (default) returns an expression.
+ """
+ poly = Poly(dup_random(n, inf, sup, domain), x, domain=domain)
+
+ return poly if polys else poly.as_expr()
+
+
+@public
+def interpolating_poly(n, x, X='x', Y='y'):
+ """Construct Lagrange interpolating polynomial for ``n``
+ data points. If a sequence of values are given for ``X`` and ``Y``
+ then the first ``n`` values will be used.
+ """
+ ok = getattr(x, 'free_symbols', None)
+
+ if isinstance(X, str):
+ X = symbols("%s:%s" % (X, n))
+ elif ok and ok & Tuple(*X).free_symbols:
+ ok = False
+
+ if isinstance(Y, str):
+ Y = symbols("%s:%s" % (Y, n))
+ elif ok and ok & Tuple(*Y).free_symbols:
+ ok = False
+
+ if not ok:
+ raise ValueError(filldedent('''
+ Expecting symbol for x that does not appear in X or Y.
+ Use `interpolate(list(zip(X, Y)), x)` instead.'''))
+
+ coeffs = []
+ numert = Mul(*[x - X[i] for i in range(n)])
+
+ for i in range(n):
+ numer = numert/(x - X[i])
+ denom = Mul(*[(X[i] - X[j]) for j in range(n) if i != j])
+ coeffs.append(numer/denom)
+
+ return Add(*[coeff*y for coeff, y in zip(coeffs, Y)])
+
+
+def fateman_poly_F_1(n):
+ """Fateman's GCD benchmark: trivial GCD """
+ Y = [Symbol('y_' + str(i)) for i in range(n + 1)]
+
+ y_0, y_1 = Y[0], Y[1]
+
+ u = y_0 + Add(*Y[1:])
+ v = y_0**2 + Add(*[y**2 for y in Y[1:]])
+
+ F = ((u + 1)*(u + 2)).as_poly(*Y)
+ G = ((v + 1)*(-3*y_1*y_0**2 + y_1**2 - 1)).as_poly(*Y)
+
+ H = Poly(1, *Y)
+
+ return F, G, H
+
+
+def dmp_fateman_poly_F_1(n, K):
+ """Fateman's GCD benchmark: trivial GCD """
+ u = [K(1), K(0)]
+
+ for i in range(n):
+ u = [dmp_one(i, K), u]
+
+ v = [K(1), K(0), K(0)]
+
+ for i in range(0, n):
+ v = [dmp_one(i, K), dmp_zero(i), v]
+
+ m = n - 1
+
+ U = dmp_add_term(u, dmp_ground(K(1), m), 0, n, K)
+ V = dmp_add_term(u, dmp_ground(K(2), m), 0, n, K)
+
+ f = [[-K(3), K(0)], [], [K(1), K(0), -K(1)]]
+
+ W = dmp_add_term(v, dmp_ground(K(1), m), 0, n, K)
+ Y = dmp_raise(f, m, 1, K)
+
+ F = dmp_mul(U, V, n, K)
+ G = dmp_mul(W, Y, n, K)
+
+ H = dmp_one(n, K)
+
+ return F, G, H
+
+
+def fateman_poly_F_2(n):
+ """Fateman's GCD benchmark: linearly dense quartic inputs """
+ Y = [Symbol('y_' + str(i)) for i in range(n + 1)]
+
+ y_0 = Y[0]
+
+ u = Add(*Y[1:])
+
+ H = Poly((y_0 + u + 1)**2, *Y)
+
+ F = Poly((y_0 - u - 2)**2, *Y)
+ G = Poly((y_0 + u + 2)**2, *Y)
+
+ return H*F, H*G, H
+
+
+def dmp_fateman_poly_F_2(n, K):
+ """Fateman's GCD benchmark: linearly dense quartic inputs """
+ u = [K(1), K(0)]
+
+ for i in range(n - 1):
+ u = [dmp_one(i, K), u]
+
+ m = n - 1
+
+ v = dmp_add_term(u, dmp_ground(K(2), m - 1), 0, n, K)
+
+ f = dmp_sqr([dmp_one(m, K), dmp_neg(v, m, K)], n, K)
+ g = dmp_sqr([dmp_one(m, K), v], n, K)
+
+ v = dmp_add_term(u, dmp_one(m - 1, K), 0, n, K)
+
+ h = dmp_sqr([dmp_one(m, K), v], n, K)
+
+ return dmp_mul(f, h, n, K), dmp_mul(g, h, n, K), h
+
+
+def fateman_poly_F_3(n):
+ """Fateman's GCD benchmark: sparse inputs (deg f ~ vars f) """
+ Y = [Symbol('y_' + str(i)) for i in range(n + 1)]
+
+ y_0 = Y[0]
+
+ u = Add(*[y**(n + 1) for y in Y[1:]])
+
+ H = Poly((y_0**(n + 1) + u + 1)**2, *Y)
+
+ F = Poly((y_0**(n + 1) - u - 2)**2, *Y)
+ G = Poly((y_0**(n + 1) + u + 2)**2, *Y)
+
+ return H*F, H*G, H
+
+
+def dmp_fateman_poly_F_3(n, K):
+ """Fateman's GCD benchmark: sparse inputs (deg f ~ vars f) """
+ u = dup_from_raw_dict({n + 1: K.one}, K)
+
+ for i in range(0, n - 1):
+ u = dmp_add_term([u], dmp_one(i, K), n + 1, i + 1, K)
+
+ v = dmp_add_term(u, dmp_ground(K(2), n - 2), 0, n, K)
+
+ f = dmp_sqr(
+ dmp_add_term([dmp_neg(v, n - 1, K)], dmp_one(n - 1, K), n + 1, n, K), n, K)
+ g = dmp_sqr(dmp_add_term([v], dmp_one(n - 1, K), n + 1, n, K), n, K)
+
+ v = dmp_add_term(u, dmp_one(n - 2, K), 0, n - 1, K)
+
+ h = dmp_sqr(dmp_add_term([v], dmp_one(n - 1, K), n + 1, n, K), n, K)
+
+ return dmp_mul(f, h, n, K), dmp_mul(g, h, n, K), h
+
+# A few useful polynomials from Wang's paper ('78).
+
+from sympy.polys.rings import ring
+
+def _f_0():
+ R, x, y, z = ring("x,y,z", ZZ)
+ return x**2*y*z**2 + 2*x**2*y*z + 3*x**2*y + 2*x**2 + 3*x + 4*y**2*z**2 + 5*y**2*z + 6*y**2 + y*z**2 + 2*y*z + y + 1
+
+def _f_1():
+ R, x, y, z = ring("x,y,z", ZZ)
+ return x**3*y*z + x**2*y**2*z**2 + x**2*y**2 + 20*x**2*y*z + 30*x**2*y + x**2*z**2 + 10*x**2*z + x*y**3*z + 30*x*y**2*z + 20*x*y**2 + x*y*z**3 + 10*x*y*z**2 + x*y*z + 610*x*y + 20*x*z**2 + 230*x*z + 300*x + y**2*z**2 + 10*y**2*z + 30*y*z**2 + 320*y*z + 200*y + 600*z + 6000
+
+def _f_2():
+ R, x, y, z = ring("x,y,z", ZZ)
+ return x**5*y**3 + x**5*y**2*z + x**5*y*z**2 + x**5*z**3 + x**3*y**2 + x**3*y*z + 90*x**3*y + 90*x**3*z + x**2*y**2*z - 11*x**2*y**2 + x**2*z**3 - 11*x**2*z**2 + y*z - 11*y + 90*z - 990
+
+def _f_3():
+ R, x, y, z = ring("x,y,z", ZZ)
+ return x**5*y**2 + x**4*z**4 + x**4 + x**3*y**3*z + x**3*z + x**2*y**4 + x**2*y**3*z**3 + x**2*y*z**5 + x**2*y*z + x*y**2*z**4 + x*y**2 + x*y*z**7 + x*y*z**3 + x*y*z**2 + y**2*z + y*z**4
+
+def _f_4():
+ R, x, y, z = ring("x,y,z", ZZ)
+ return -x**9*y**8*z - x**8*y**5*z**3 - x**7*y**12*z**2 - 5*x**7*y**8 - x**6*y**9*z**4 + x**6*y**7*z**3 + 3*x**6*y**7*z - 5*x**6*y**5*z**2 - x**6*y**4*z**3 + x**5*y**4*z**5 + 3*x**5*y**4*z**3 - x**5*y*z**5 + x**4*y**11*z**4 + 3*x**4*y**11*z**2 - x**4*y**8*z**4 + 5*x**4*y**7*z**2 + 15*x**4*y**7 - 5*x**4*y**4*z**2 + x**3*y**8*z**6 + 3*x**3*y**8*z**4 - x**3*y**5*z**6 + 5*x**3*y**4*z**4 + 15*x**3*y**4*z**2 + x**3*y**3*z**5 + 3*x**3*y**3*z**3 - 5*x**3*y*z**4 + x**2*z**7 + 3*x**2*z**5 + x*y**7*z**6 + 3*x*y**7*z**4 + 5*x*y**3*z**4 + 15*x*y**3*z**2 + y**4*z**8 + 3*y**4*z**6 + 5*z**6 + 15*z**4
+
+def _f_5():
+ R, x, y, z = ring("x,y,z", ZZ)
+ return -x**3 - 3*x**2*y + 3*x**2*z - 3*x*y**2 + 6*x*y*z - 3*x*z**2 - y**3 + 3*y**2*z - 3*y*z**2 + z**3
+
+def _f_6():
+ R, x, y, z, t = ring("x,y,z,t", ZZ)
+ return 2115*x**4*y + 45*x**3*z**3*t**2 - 45*x**3*t**2 - 423*x*y**4 - 47*x*y**3 + 141*x*y*z**3 + 94*x*y*z*t - 9*y**3*z**3*t**2 + 9*y**3*t**2 - y**2*z**3*t**2 + y**2*t**2 + 3*z**6*t**2 + 2*z**4*t**3 - 3*z**3*t**2 - 2*z*t**3
+
+def _w_1():
+ R, x, y, z = ring("x,y,z", ZZ)
+ return 4*x**6*y**4*z**2 + 4*x**6*y**3*z**3 - 4*x**6*y**2*z**4 - 4*x**6*y*z**5 + x**5*y**4*z**3 + 12*x**5*y**3*z - x**5*y**2*z**5 + 12*x**5*y**2*z**2 - 12*x**5*y*z**3 - 12*x**5*z**4 + 8*x**4*y**4 + 6*x**4*y**3*z**2 + 8*x**4*y**3*z - 4*x**4*y**2*z**4 + 4*x**4*y**2*z**3 - 8*x**4*y**2*z**2 - 4*x**4*y*z**5 - 2*x**4*y*z**4 - 8*x**4*y*z**3 + 2*x**3*y**4*z + x**3*y**3*z**3 - x**3*y**2*z**5 - 2*x**3*y**2*z**3 + 9*x**3*y**2*z - 12*x**3*y*z**3 + 12*x**3*y*z**2 - 12*x**3*z**4 + 3*x**3*z**3 + 6*x**2*y**3 - 6*x**2*y**2*z**2 + 8*x**2*y**2*z - 2*x**2*y*z**4 - 8*x**2*y*z**3 + 2*x**2*y*z**2 + 2*x*y**3*z - 2*x*y**2*z**3 - 3*x*y*z + 3*x*z**3 - 2*y**2 + 2*y*z**2
+
+def _w_2():
+ R, x, y = ring("x,y", ZZ)
+ return 24*x**8*y**3 + 48*x**8*y**2 + 24*x**7*y**5 - 72*x**7*y**2 + 25*x**6*y**4 + 2*x**6*y**3 + 4*x**6*y + 8*x**6 + x**5*y**6 + x**5*y**3 - 12*x**5 + x**4*y**5 - x**4*y**4 - 2*x**4*y**3 + 292*x**4*y**2 - x**3*y**6 + 3*x**3*y**3 - x**2*y**5 + 12*x**2*y**3 + 48*x**2 - 12*y**3
+
+def f_polys():
+ return _f_0(), _f_1(), _f_2(), _f_3(), _f_4(), _f_5(), _f_6()
+
+def w_polys():
+ return _w_1(), _w_2()
diff --git a/lib/python3.12/site-packages/sympy/polys/sqfreetools.py b/lib/python3.12/site-packages/sympy/polys/sqfreetools.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e773e9a1b6b40e0238bbac68543e574bbb37353
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/sqfreetools.py
@@ -0,0 +1,795 @@
+"""Square-free decomposition algorithms and related tools. """
+
+
+from sympy.polys.densearith import (
+ dup_neg, dmp_neg,
+ dup_sub, dmp_sub,
+ dup_mul, dmp_mul,
+ dup_quo, dmp_quo,
+ dup_mul_ground, dmp_mul_ground)
+from sympy.polys.densebasic import (
+ dup_strip,
+ dup_LC, dmp_ground_LC,
+ dmp_zero_p,
+ dmp_ground,
+ dup_degree, dmp_degree, dmp_degree_in, dmp_degree_list,
+ dmp_raise, dmp_inject,
+ dup_convert)
+from sympy.polys.densetools import (
+ dup_diff, dmp_diff, dmp_diff_in,
+ dup_shift, dmp_shift,
+ dup_monic, dmp_ground_monic,
+ dup_primitive, dmp_ground_primitive)
+from sympy.polys.euclidtools import (
+ dup_inner_gcd, dmp_inner_gcd,
+ dup_gcd, dmp_gcd,
+ dmp_resultant, dmp_primitive)
+from sympy.polys.galoistools import (
+ gf_sqf_list, gf_sqf_part)
+from sympy.polys.polyerrors import (
+ MultivariatePolynomialError,
+ DomainError)
+
+
+def _dup_check_degrees(f, result):
+ """Sanity check the degrees of a computed factorization in K[x]."""
+ deg = sum(k * dup_degree(fac) for (fac, k) in result)
+ assert deg == dup_degree(f)
+
+
+def _dmp_check_degrees(f, u, result):
+ """Sanity check the degrees of a computed factorization in K[X]."""
+ degs = [0] * (u + 1)
+ for fac, k in result:
+ degs_fac = dmp_degree_list(fac, u)
+ degs = [d1 + k * d2 for d1, d2 in zip(degs, degs_fac)]
+ assert tuple(degs) == dmp_degree_list(f, u)
+
+
+def dup_sqf_p(f, K):
+ """
+ Return ``True`` if ``f`` is a square-free polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_sqf_p(x**2 - 2*x + 1)
+ False
+ >>> R.dup_sqf_p(x**2 - 1)
+ True
+
+ """
+ if not f:
+ return True
+ else:
+ return not dup_degree(dup_gcd(f, dup_diff(f, 1, K), K))
+
+
+def dmp_sqf_p(f, u, K):
+ """
+ Return ``True`` if ``f`` is a square-free polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_sqf_p(x**2 + 2*x*y + y**2)
+ False
+ >>> R.dmp_sqf_p(x**2 + y**2)
+ True
+
+ """
+ if dmp_zero_p(f, u):
+ return True
+
+ for i in range(u+1):
+
+ fp = dmp_diff_in(f, 1, i, u, K)
+
+ if dmp_zero_p(fp, u):
+ continue
+
+ gcd = dmp_gcd(f, fp, u, K)
+
+ if dmp_degree_in(gcd, i, u) != 0:
+ return False
+
+ return True
+
+
+def dup_sqf_norm(f, K):
+ r"""
+ Find a shift of `f` in `K[x]` that has square-free norm.
+
+ The domain `K` must be an algebraic number field `k(a)` (see :ref:`QQ(a)`).
+
+ Returns `(s,g,r)`, such that `g(x)=f(x-sa)`, `r(x)=\text{Norm}(g(x))` and
+ `r` is a square-free polynomial over `k`.
+
+ Examples
+ ========
+
+ We first create the algebraic number field `K=k(a)=\mathbb{Q}(\sqrt{3})`
+ and rings `K[x]` and `k[x]`:
+
+ >>> from sympy.polys import ring, QQ
+ >>> from sympy import sqrt
+
+ >>> K = QQ.algebraic_field(sqrt(3))
+ >>> R, x = ring("x", K)
+ >>> _, X = ring("x", QQ)
+
+ We can now find a square free norm for a shift of `f`:
+
+ >>> f = x**2 - 1
+ >>> s, g, r = R.dup_sqf_norm(f)
+
+ The choice of shift `s` is arbitrary and the particular values returned for
+ `g` and `r` are determined by `s`.
+
+ >>> s == 1
+ True
+ >>> g == x**2 - 2*sqrt(3)*x + 2
+ True
+ >>> r == X**4 - 8*X**2 + 4
+ True
+
+ The invariants are:
+
+ >>> g == f.shift(-s*K.unit)
+ True
+ >>> g.norm() == r
+ True
+ >>> r.is_squarefree
+ True
+
+ Explanation
+ ===========
+
+ This is part of Trager's algorithm for factorizing polynomials over
+ algebraic number fields. In particular this function is algorithm
+ ``sqfr_norm`` from [Trager76]_.
+
+ See Also
+ ========
+
+ dmp_sqf_norm:
+ Analogous function for multivariate polynomials over ``k(a)``.
+ dmp_norm:
+ Computes the norm of `f` directly without any shift.
+ dup_ext_factor:
+ Function implementing Trager's algorithm that uses this.
+ sympy.polys.polytools.sqf_norm:
+ High-level interface for using this function.
+ """
+ if not K.is_Algebraic:
+ raise DomainError("ground domain must be algebraic")
+
+ s, g = 0, dmp_raise(K.mod.to_list(), 1, 0, K.dom)
+
+ while True:
+ h, _ = dmp_inject(f, 0, K, front=True)
+ r = dmp_resultant(g, h, 1, K.dom)
+
+ if dup_sqf_p(r, K.dom):
+ break
+ else:
+ f, s = dup_shift(f, -K.unit, K), s + 1
+
+ return s, f, r
+
+
+def _dmp_sqf_norm_shifts(f, u, K):
+ """Generate a sequence of candidate shifts for dmp_sqf_norm."""
+ #
+ # We want to find a minimal shift if possible because shifting high degree
+ # variables can be expensive e.g. x**10 -> (x + 1)**10. We try a few easy
+ # cases first before the final infinite loop that is guaranteed to give
+ # only finitely many bad shifts (see Trager76 for proof of this in the
+ # univariate case).
+ #
+
+ # First the trivial shift [0, 0, ...]
+ n = u + 1
+ s0 = [0] * n
+ yield s0, f
+
+ # Shift in multiples of the generator of the extension field K
+ a = K.unit
+
+ # Variables of degree > 0 ordered by increasing degree
+ d = dmp_degree_list(f, u)
+ var_indices = [i for di, i in sorted(zip(d, range(u+1))) if di > 0]
+
+ # Now try [1, 0, 0, ...], [0, 1, 0, ...]
+ for i in var_indices:
+ s1 = s0.copy()
+ s1[i] = 1
+ a1 = [-a*s1i for s1i in s1]
+ f1 = dmp_shift(f, a1, u, K)
+ yield s1, f1
+
+ # Now try [1, 1, 1, ...], [2, 2, 2, ...]
+ j = 0
+ while True:
+ j += 1
+ sj = [j] * n
+ aj = [-a*j] * n
+ fj = dmp_shift(f, aj, u, K)
+ yield sj, fj
+
+
+def dmp_sqf_norm(f, u, K):
+ r"""
+ Find a shift of ``f`` in ``K[X]`` that has square-free norm.
+
+ The domain `K` must be an algebraic number field `k(a)` (see :ref:`QQ(a)`).
+
+ Returns `(s,g,r)`, such that `g(x_1,x_2,\cdots)=f(x_1-s_1 a, x_2 - s_2 a,
+ \cdots)`, `r(x)=\text{Norm}(g(x))` and `r` is a square-free polynomial over
+ `k`.
+
+ Examples
+ ========
+
+ We first create the algebraic number field `K=k(a)=\mathbb{Q}(i)` and rings
+ `K[x,y]` and `k[x,y]`:
+
+ >>> from sympy.polys import ring, QQ
+ >>> from sympy import I
+
+ >>> K = QQ.algebraic_field(I)
+ >>> R, x, y = ring("x,y", K)
+ >>> _, X, Y = ring("x,y", QQ)
+
+ We can now find a square free norm for a shift of `f`:
+
+ >>> f = x*y + y**2
+ >>> s, g, r = R.dmp_sqf_norm(f)
+
+ The choice of shifts ``s`` is arbitrary and the particular values returned
+ for ``g`` and ``r`` are determined by ``s``.
+
+ >>> s
+ [0, 1]
+ >>> g == x*y - I*x + y**2 - 2*I*y - 1
+ True
+ >>> r == X**2*Y**2 + X**2 + 2*X*Y**3 + 2*X*Y + Y**4 + 2*Y**2 + 1
+ True
+
+ The required invariants are:
+
+ >>> g == f.shift_list([-si*K.unit for si in s])
+ True
+ >>> g.norm() == r
+ True
+ >>> r.is_squarefree
+ True
+
+ Explanation
+ ===========
+
+ This is part of Trager's algorithm for factorizing polynomials over
+ algebraic number fields. In particular this function is a multivariate
+ generalization of algorithm ``sqfr_norm`` from [Trager76]_.
+
+ See Also
+ ========
+
+ dup_sqf_norm:
+ Analogous function for univariate polynomials over ``k(a)``.
+ dmp_norm:
+ Computes the norm of `f` directly without any shift.
+ dmp_ext_factor:
+ Function implementing Trager's algorithm that uses this.
+ sympy.polys.polytools.sqf_norm:
+ High-level interface for using this function.
+ """
+ if not u:
+ s, g, r = dup_sqf_norm(f, K)
+ return [s], g, r
+
+ if not K.is_Algebraic:
+ raise DomainError("ground domain must be algebraic")
+
+ g = dmp_raise(K.mod.to_list(), u + 1, 0, K.dom)
+
+ for s, f in _dmp_sqf_norm_shifts(f, u, K):
+
+ h, _ = dmp_inject(f, u, K, front=True)
+ r = dmp_resultant(g, h, u + 1, K.dom)
+
+ if dmp_sqf_p(r, u, K.dom):
+ break
+
+ return s, f, r
+
+
+def dmp_norm(f, u, K):
+ r"""
+ Norm of ``f`` in ``K[X]``, often not square-free.
+
+ The domain `K` must be an algebraic number field `k(a)` (see :ref:`QQ(a)`).
+
+ Examples
+ ========
+
+ We first define the algebraic number field `K = k(a) = \mathbb{Q}(\sqrt{2})`:
+
+ >>> from sympy import QQ, sqrt
+ >>> from sympy.polys.sqfreetools import dmp_norm
+ >>> k = QQ
+ >>> K = k.algebraic_field(sqrt(2))
+
+ We can now compute the norm of a polynomial `p` in `K[x,y]`:
+
+ >>> p = [[K(1)], [K(1),K.unit]] # x + y + sqrt(2)
+ >>> N = [[k(1)], [k(2),k(0)], [k(1),k(0),k(-2)]] # x**2 + 2*x*y + y**2 - 2
+ >>> dmp_norm(p, 1, K) == N
+ True
+
+ In higher level functions that is:
+
+ >>> from sympy import expand, roots, minpoly
+ >>> from sympy.abc import x, y
+ >>> from math import prod
+ >>> a = sqrt(2)
+ >>> e = (x + y + a)
+ >>> e.as_poly([x, y], extension=a).norm()
+ Poly(x**2 + 2*x*y + y**2 - 2, x, y, domain='QQ')
+
+ This is equal to the product of the expressions `x + y + a_i` where the
+ `a_i` are the conjugates of `a`:
+
+ >>> pa = minpoly(a)
+ >>> pa
+ _x**2 - 2
+ >>> rs = roots(pa, multiple=True)
+ >>> rs
+ [sqrt(2), -sqrt(2)]
+ >>> n = prod(e.subs(a, r) for r in rs)
+ >>> n
+ (x + y - sqrt(2))*(x + y + sqrt(2))
+ >>> expand(n)
+ x**2 + 2*x*y + y**2 - 2
+
+ Explanation
+ ===========
+
+ Given an algebraic number field `K = k(a)` any element `b` of `K` can be
+ represented as polynomial function `b=g(a)` where `g` is in `k[x]`. If the
+ minimal polynomial of `a` over `k` is `p_a` then the roots `a_1`, `a_2`,
+ `\cdots` of `p_a(x)` are the conjugates of `a`. The norm of `b` is the
+ product `g(a1) \times g(a2) \times \cdots` and is an element of `k`.
+
+ As in [Trager76]_ we extend this norm to multivariate polynomials over `K`.
+ If `b(x)` is a polynomial in `k(a)[X]` then we can think of `b` as being
+ alternately a function `g_X(a)` where `g_X` is an element of `k[X][y]` i.e.
+ a polynomial function with coefficients that are elements of `k[X]`. Then
+ the norm of `b` is the product `g_X(a1) \times g_X(a2) \times \cdots` and
+ will be an element of `k[X]`.
+
+ See Also
+ ========
+
+ dmp_sqf_norm:
+ Compute a shift of `f` so that the `\text{Norm}(f)` is square-free.
+ sympy.polys.polytools.Poly.norm:
+ Higher-level function that calls this.
+ """
+ if not K.is_Algebraic:
+ raise DomainError("ground domain must be algebraic")
+
+ g = dmp_raise(K.mod.to_list(), u + 1, 0, K.dom)
+ h, _ = dmp_inject(f, u, K, front=True)
+
+ return dmp_resultant(g, h, u + 1, K.dom)
+
+
+def dup_gf_sqf_part(f, K):
+ """Compute square-free part of ``f`` in ``GF(p)[x]``. """
+ f = dup_convert(f, K, K.dom)
+ g = gf_sqf_part(f, K.mod, K.dom)
+ return dup_convert(g, K.dom, K)
+
+
+def dmp_gf_sqf_part(f, u, K):
+ """Compute square-free part of ``f`` in ``GF(p)[X]``. """
+ raise NotImplementedError('multivariate polynomials over finite fields')
+
+
+def dup_sqf_part(f, K):
+ """
+ Returns square-free part of a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_sqf_part(x**3 - 3*x - 2)
+ x**2 - x - 2
+
+ See Also
+ ========
+
+ sympy.polys.polytools.Poly.sqf_part
+ """
+ if K.is_FiniteField:
+ return dup_gf_sqf_part(f, K)
+
+ if not f:
+ return f
+
+ if K.is_negative(dup_LC(f, K)):
+ f = dup_neg(f, K)
+
+ gcd = dup_gcd(f, dup_diff(f, 1, K), K)
+ sqf = dup_quo(f, gcd, K)
+
+ if K.is_Field:
+ return dup_monic(sqf, K)
+ else:
+ return dup_primitive(sqf, K)[1]
+
+
+def dmp_sqf_part(f, u, K):
+ """
+ Returns square-free part of a polynomial in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> R.dmp_sqf_part(x**3 + 2*x**2*y + x*y**2)
+ x**2 + x*y
+
+ """
+ if not u:
+ return dup_sqf_part(f, K)
+
+ if K.is_FiniteField:
+ return dmp_gf_sqf_part(f, u, K)
+
+ if dmp_zero_p(f, u):
+ return f
+
+ if K.is_negative(dmp_ground_LC(f, u, K)):
+ f = dmp_neg(f, u, K)
+
+ gcd = f
+ for i in range(u+1):
+ gcd = dmp_gcd(gcd, dmp_diff_in(f, 1, i, u, K), u, K)
+ sqf = dmp_quo(f, gcd, u, K)
+
+ if K.is_Field:
+ return dmp_ground_monic(sqf, u, K)
+ else:
+ return dmp_ground_primitive(sqf, u, K)[1]
+
+
+def dup_gf_sqf_list(f, K, all=False):
+ """Compute square-free decomposition of ``f`` in ``GF(p)[x]``. """
+ f_orig = f
+
+ f = dup_convert(f, K, K.dom)
+
+ coeff, factors = gf_sqf_list(f, K.mod, K.dom, all=all)
+
+ for i, (f, k) in enumerate(factors):
+ factors[i] = (dup_convert(f, K.dom, K), k)
+
+ _dup_check_degrees(f_orig, factors)
+
+ return K.convert(coeff, K.dom), factors
+
+
+def dmp_gf_sqf_list(f, u, K, all=False):
+ """Compute square-free decomposition of ``f`` in ``GF(p)[X]``. """
+ raise NotImplementedError('multivariate polynomials over finite fields')
+
+
+def dup_sqf_list(f, K, all=False):
+ """
+ Return square-free decomposition of a polynomial in ``K[x]``.
+
+ Uses Yun's algorithm from [Yun76]_.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> f = 2*x**5 + 16*x**4 + 50*x**3 + 76*x**2 + 56*x + 16
+
+ >>> R.dup_sqf_list(f)
+ (2, [(x + 1, 2), (x + 2, 3)])
+ >>> R.dup_sqf_list(f, all=True)
+ (2, [(1, 1), (x + 1, 2), (x + 2, 3)])
+
+ See Also
+ ========
+
+ dmp_sqf_list:
+ Corresponding function for multivariate polynomials.
+ sympy.polys.polytools.sqf_list:
+ High-level function for square-free factorization of expressions.
+ sympy.polys.polytools.Poly.sqf_list:
+ Analogous method on :class:`~.Poly`.
+
+ References
+ ==========
+
+ [Yun76]_
+ """
+ if K.is_FiniteField:
+ return dup_gf_sqf_list(f, K, all=all)
+
+ f_orig = f
+
+ if K.is_Field:
+ coeff = dup_LC(f, K)
+ f = dup_monic(f, K)
+ else:
+ coeff, f = dup_primitive(f, K)
+
+ if K.is_negative(dup_LC(f, K)):
+ f = dup_neg(f, K)
+ coeff = -coeff
+
+ if dup_degree(f) <= 0:
+ return coeff, []
+
+ result, i = [], 1
+
+ h = dup_diff(f, 1, K)
+ g, p, q = dup_inner_gcd(f, h, K)
+
+ while True:
+ d = dup_diff(p, 1, K)
+ h = dup_sub(q, d, K)
+
+ if not h:
+ result.append((p, i))
+ break
+
+ g, p, q = dup_inner_gcd(p, h, K)
+
+ if all or dup_degree(g) > 0:
+ result.append((g, i))
+
+ i += 1
+
+ _dup_check_degrees(f_orig, result)
+
+ return coeff, result
+
+
+def dup_sqf_list_include(f, K, all=False):
+ """
+ Return square-free decomposition of a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> f = 2*x**5 + 16*x**4 + 50*x**3 + 76*x**2 + 56*x + 16
+
+ >>> R.dup_sqf_list_include(f)
+ [(2, 1), (x + 1, 2), (x + 2, 3)]
+ >>> R.dup_sqf_list_include(f, all=True)
+ [(2, 1), (x + 1, 2), (x + 2, 3)]
+
+ """
+ coeff, factors = dup_sqf_list(f, K, all=all)
+
+ if factors and factors[0][1] == 1:
+ g = dup_mul_ground(factors[0][0], coeff, K)
+ return [(g, 1)] + factors[1:]
+ else:
+ g = dup_strip([coeff])
+ return [(g, 1)] + factors
+
+
+def dmp_sqf_list(f, u, K, all=False):
+ """
+ Return square-free decomposition of a polynomial in `K[X]`.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x**5 + 2*x**4*y + x**3*y**2
+
+ >>> R.dmp_sqf_list(f)
+ (1, [(x + y, 2), (x, 3)])
+ >>> R.dmp_sqf_list(f, all=True)
+ (1, [(1, 1), (x + y, 2), (x, 3)])
+
+ Explanation
+ ===========
+
+ Uses Yun's algorithm for univariate polynomials from [Yun76]_ recrusively.
+ The multivariate polynomial is treated as a univariate polynomial in its
+ leading variable. Then Yun's algorithm computes the square-free
+ factorization of the primitive and the content is factored recursively.
+
+ It would be better to use a dedicated algorithm for multivariate
+ polynomials instead.
+
+ See Also
+ ========
+
+ dup_sqf_list:
+ Corresponding function for univariate polynomials.
+ sympy.polys.polytools.sqf_list:
+ High-level function for square-free factorization of expressions.
+ sympy.polys.polytools.Poly.sqf_list:
+ Analogous method on :class:`~.Poly`.
+ """
+ if not u:
+ return dup_sqf_list(f, K, all=all)
+
+ if K.is_FiniteField:
+ return dmp_gf_sqf_list(f, u, K, all=all)
+
+ f_orig = f
+
+ if K.is_Field:
+ coeff = dmp_ground_LC(f, u, K)
+ f = dmp_ground_monic(f, u, K)
+ else:
+ coeff, f = dmp_ground_primitive(f, u, K)
+
+ if K.is_negative(dmp_ground_LC(f, u, K)):
+ f = dmp_neg(f, u, K)
+ coeff = -coeff
+
+ deg = dmp_degree(f, u)
+ if deg < 0:
+ return coeff, []
+
+ # Yun's algorithm requires the polynomial to be primitive as a univariate
+ # polynomial in its main variable.
+ content, f = dmp_primitive(f, u, K)
+
+ result = {}
+
+ if deg != 0:
+
+ h = dmp_diff(f, 1, u, K)
+ g, p, q = dmp_inner_gcd(f, h, u, K)
+
+ i = 1
+
+ while True:
+ d = dmp_diff(p, 1, u, K)
+ h = dmp_sub(q, d, u, K)
+
+ if dmp_zero_p(h, u):
+ result[i] = p
+ break
+
+ g, p, q = dmp_inner_gcd(p, h, u, K)
+
+ if all or dmp_degree(g, u) > 0:
+ result[i] = g
+
+ i += 1
+
+ coeff_content, result_content = dmp_sqf_list(content, u-1, K, all=all)
+
+ coeff *= coeff_content
+
+ # Combine factors of the content and primitive part that have the same
+ # multiplicity to produce a list in ascending order of multiplicity.
+ for fac, i in result_content:
+ fac = [fac]
+ if i in result:
+ result[i] = dmp_mul(result[i], fac, u, K)
+ else:
+ result[i] = fac
+
+ result = [(result[i], i) for i in sorted(result)]
+
+ _dmp_check_degrees(f_orig, u, result)
+
+ return coeff, result
+
+
+def dmp_sqf_list_include(f, u, K, all=False):
+ """
+ Return square-free decomposition of a polynomial in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ >>> f = x**5 + 2*x**4*y + x**3*y**2
+
+ >>> R.dmp_sqf_list_include(f)
+ [(1, 1), (x + y, 2), (x, 3)]
+ >>> R.dmp_sqf_list_include(f, all=True)
+ [(1, 1), (x + y, 2), (x, 3)]
+
+ """
+ if not u:
+ return dup_sqf_list_include(f, K, all=all)
+
+ coeff, factors = dmp_sqf_list(f, u, K, all=all)
+
+ if factors and factors[0][1] == 1:
+ g = dmp_mul_ground(factors[0][0], coeff, u, K)
+ return [(g, 1)] + factors[1:]
+ else:
+ g = dmp_ground(coeff, u)
+ return [(g, 1)] + factors
+
+
+def dup_gff_list(f, K):
+ """
+ Compute greatest factorial factorization of ``f`` in ``K[x]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x = ring("x", ZZ)
+
+ >>> R.dup_gff_list(x**5 + 2*x**4 - x**3 - 2*x**2)
+ [(x, 1), (x + 2, 4)]
+
+ """
+ if not f:
+ raise ValueError("greatest factorial factorization doesn't exist for a zero polynomial")
+
+ f = dup_monic(f, K)
+
+ if not dup_degree(f):
+ return []
+ else:
+ g = dup_gcd(f, dup_shift(f, K.one, K), K)
+ H = dup_gff_list(g, K)
+
+ for i, (h, k) in enumerate(H):
+ g = dup_mul(g, dup_shift(h, -K(k), K), K)
+ H[i] = (h, k + 1)
+
+ f = dup_quo(f, g, K)
+
+ if not dup_degree(f):
+ return H
+ else:
+ return [(f, 1)] + H
+
+
+def dmp_gff_list(f, u, K):
+ """
+ Compute greatest factorial factorization of ``f`` in ``K[X]``.
+
+ Examples
+ ========
+
+ >>> from sympy.polys import ring, ZZ
+ >>> R, x,y = ring("x,y", ZZ)
+
+ """
+ if not u:
+ return dup_gff_list(f, K)
+ else:
+ raise MultivariatePolynomialError(f)
diff --git a/lib/python3.12/site-packages/sympy/polys/subresultants_qq_zz.py b/lib/python3.12/site-packages/sympy/polys/subresultants_qq_zz.py
new file mode 100644
index 0000000000000000000000000000000000000000..d681d44efeb06f5e3381f8d03840e7a925dd67d4
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/polys/subresultants_qq_zz.py
@@ -0,0 +1,2558 @@
+"""
+This module contains functions for the computation
+of Euclidean, (generalized) Sturmian, (modified) subresultant
+polynomial remainder sequences (prs's) of two polynomials;
+included are also three functions for the computation of the
+resultant of two polynomials.
+
+Except for the function res_z(), which computes the resultant
+of two polynomials, the pseudo-remainder function prem()
+of sympy is _not_ used by any of the functions in the module.
+
+Instead of prem() we use the function
+
+rem_z().
+
+Included is also the function quo_z().
+
+An explanation of why we avoid prem() can be found in the
+references stated in the docstring of rem_z().
+
+1. Theoretical background:
+==========================
+Consider the polynomials f, g in Z[x] of degrees deg(f) = n and
+deg(g) = m with n >= m.
+
+Definition 1:
+=============
+The sign sequence of a polynomial remainder sequence (prs) is the
+sequence of signs of the leading coefficients of its polynomials.
+
+Sign sequences can be computed with the function:
+
+sign_seq(poly_seq, x)
+
+Definition 2:
+=============
+A polynomial remainder sequence (prs) is called complete if the
+degree difference between any two consecutive polynomials is 1;
+otherwise, it called incomplete.
+
+It is understood that f, g belong to the sequences mentioned in
+the two definitions above.
+
+1A. Euclidean and subresultant prs's:
+=====================================
+The subresultant prs of f, g is a sequence of polynomials in Z[x]
+analogous to the Euclidean prs, the sequence obtained by applying
+on f, g Euclid's algorithm for polynomial greatest common divisors
+(gcd) in Q[x].
+
+The subresultant prs differs from the Euclidean prs in that the
+coefficients of each polynomial in the former sequence are determinants
+--- also referred to as subresultants --- of appropriately selected
+sub-matrices of sylvester1(f, g, x), Sylvester's matrix of 1840 of
+dimensions (n + m) * (n + m).
+
+Recall that the determinant of sylvester1(f, g, x) itself is
+called the resultant of f, g and serves as a criterion of whether
+the two polynomials have common roots or not.
+
+In SymPy the resultant is computed with the function
+resultant(f, g, x). This function does _not_ evaluate the
+determinant of sylvester(f, g, x, 1); instead, it returns
+the last member of the subresultant prs of f, g, multiplied
+(if needed) by an appropriate power of -1; see the caveat below.
+
+In this module we use three functions to compute the
+resultant of f, g:
+a) res(f, g, x) computes the resultant by evaluating
+the determinant of sylvester(f, g, x, 1);
+b) res_q(f, g, x) computes the resultant recursively, by
+performing polynomial divisions in Q[x] with the function rem();
+c) res_z(f, g, x) computes the resultant recursively, by
+performing polynomial divisions in Z[x] with the function prem().
+
+Caveat: If Df = degree(f, x) and Dg = degree(g, x), then:
+
+resultant(f, g, x) = (-1)**(Df*Dg) * resultant(g, f, x).
+
+For complete prs's the sign sequence of the Euclidean prs of f, g
+is identical to the sign sequence of the subresultant prs of f, g
+and the coefficients of one sequence are easily computed from the
+coefficients of the other.
+
+For incomplete prs's the polynomials in the subresultant prs, generally
+differ in sign from those of the Euclidean prs, and --- unlike the
+case of complete prs's --- it is not at all obvious how to compute
+the coefficients of one sequence from the coefficients of the other.
+
+1B. Sturmian and modified subresultant prs's:
+=============================================
+For the same polynomials f, g in Z[x] mentioned above, their ``modified''
+subresultant prs is a sequence of polynomials similar to the Sturmian
+prs, the sequence obtained by applying in Q[x] Sturm's algorithm on f, g.
+
+The two sequences differ in that the coefficients of each polynomial
+in the modified subresultant prs are the determinants --- also referred
+to as modified subresultants --- of appropriately selected sub-matrices
+of sylvester2(f, g, x), Sylvester's matrix of 1853 of dimensions 2n x 2n.
+
+The determinant of sylvester2 itself is called the modified resultant
+of f, g and it also can serve as a criterion of whether the two
+polynomials have common roots or not.
+
+For complete prs's the sign sequence of the Sturmian prs of f, g is
+identical to the sign sequence of the modified subresultant prs of
+f, g and the coefficients of one sequence are easily computed from
+the coefficients of the other.
+
+For incomplete prs's the polynomials in the modified subresultant prs,
+generally differ in sign from those of the Sturmian prs, and --- unlike
+the case of complete prs's --- it is not at all obvious how to compute
+the coefficients of one sequence from the coefficients of the other.
+
+As Sylvester pointed out, the coefficients of the polynomial remainders
+obtained as (modified) subresultants are the smallest possible without
+introducing rationals and without computing (integer) greatest common
+divisors.
+
+1C. On terminology:
+===================
+Whence the terminology? Well generalized Sturmian prs's are
+``modifications'' of Euclidean prs's; the hint came from the title
+of the Pell-Gordon paper of 1917.
+
+In the literature one also encounters the name ``non signed'' and
+``signed'' prs for Euclidean and Sturmian prs respectively.
+
+Likewise ``non signed'' and ``signed'' subresultant prs for
+subresultant and modified subresultant prs respectively.
+
+2. Functions in the module:
+===========================
+No function utilizes SymPy's function prem().
+
+2A. Matrices:
+=============
+The functions sylvester(f, g, x, method=1) and
+sylvester(f, g, x, method=2) compute either Sylvester matrix.
+They can be used to compute (modified) subresultant prs's by
+direct determinant evaluation.
+
+The function bezout(f, g, x, method='prs') provides a matrix of
+smaller dimensions than either Sylvester matrix. It is the function
+of choice for computing (modified) subresultant prs's by direct
+determinant evaluation.
+
+sylvester(f, g, x, method=1)
+sylvester(f, g, x, method=2)
+bezout(f, g, x, method='prs')
+
+The following identity holds:
+
+bezout(f, g, x, method='prs') =
+backward_eye(deg(f))*bezout(f, g, x, method='bz')*backward_eye(deg(f))
+
+2B. Subresultant and modified subresultant prs's by
+===================================================
+determinant evaluations:
+=======================
+We use the Sylvester matrices of 1840 and 1853 to
+compute, respectively, subresultant and modified
+subresultant polynomial remainder sequences. However,
+for large matrices this approach takes a lot of time.
+
+Instead of utilizing the Sylvester matrices, we can
+employ the Bezout matrix which is of smaller dimensions.
+
+subresultants_sylv(f, g, x)
+modified_subresultants_sylv(f, g, x)
+subresultants_bezout(f, g, x)
+modified_subresultants_bezout(f, g, x)
+
+2C. Subresultant prs's by ONE determinant evaluation:
+=====================================================
+All three functions in this section evaluate one determinant
+per remainder polynomial; this is the determinant of an
+appropriately selected sub-matrix of sylvester1(f, g, x),
+Sylvester's matrix of 1840.
+
+To compute the remainder polynomials the function
+subresultants_rem(f, g, x) employs rem(f, g, x).
+By contrast, the other two functions implement Van Vleck's ideas
+of 1900 and compute the remainder polynomials by trinagularizing
+sylvester2(f, g, x), Sylvester's matrix of 1853.
+
+
+subresultants_rem(f, g, x)
+subresultants_vv(f, g, x)
+subresultants_vv_2(f, g, x).
+
+2E. Euclidean, Sturmian prs's in Q[x]:
+======================================
+euclid_q(f, g, x)
+sturm_q(f, g, x)
+
+2F. Euclidean, Sturmian and (modified) subresultant prs's P-G:
+==============================================================
+All functions in this section are based on the Pell-Gordon (P-G)
+theorem of 1917.
+Computations are done in Q[x], employing the function rem(f, g, x)
+for the computation of the remainder polynomials.
+
+euclid_pg(f, g, x)
+sturm pg(f, g, x)
+subresultants_pg(f, g, x)
+modified_subresultants_pg(f, g, x)
+
+2G. Euclidean, Sturmian and (modified) subresultant prs's A-M-V:
+================================================================
+All functions in this section are based on the Akritas-Malaschonok-
+Vigklas (A-M-V) theorem of 2015.
+Computations are done in Z[x], employing the function rem_z(f, g, x)
+for the computation of the remainder polynomials.
+
+euclid_amv(f, g, x)
+sturm_amv(f, g, x)
+subresultants_amv(f, g, x)
+modified_subresultants_amv(f, g, x)
+
+2Ga. Exception:
+===============
+subresultants_amv_q(f, g, x)
+
+This function employs rem(f, g, x) for the computation of
+the remainder polynomials, despite the fact that it implements
+the A-M-V Theorem.
+
+It is included in our module in order to show that theorems P-G
+and A-M-V can be implemented utilizing either the function
+rem(f, g, x) or the function rem_z(f, g, x).
+
+For clearly historical reasons --- since the Collins-Brown-Traub
+coefficients-reduction factor beta_i was not available in 1917 ---
+we have implemented the Pell-Gordon theorem with the function
+rem(f, g, x) and the A-M-V Theorem with the function rem_z(f, g, x).
+
+2H. Resultants:
+===============
+res(f, g, x)
+res_q(f, g, x)
+res_z(f, g, x)
+"""
+
+
+from sympy.concrete.summations import summation
+from sympy.core.function import expand
+from sympy.core.numbers import nan
+from sympy.core.singleton import S
+from sympy.core.symbol import Dummy as var
+from sympy.functions.elementary.complexes import Abs, sign
+from sympy.functions.elementary.integers import floor
+from sympy.matrices.dense import eye, Matrix, zeros
+from sympy.printing.pretty.pretty import pretty_print as pprint
+from sympy.simplify.simplify import simplify
+from sympy.polys.domains import QQ
+from sympy.polys.polytools import degree, LC, Poly, pquo, quo, prem, rem
+from sympy.polys.polyerrors import PolynomialError
+
+
+def sylvester(f, g, x, method = 1):
+ '''
+ The input polynomials f, g are in Z[x] or in Q[x]. Let m = degree(f, x),
+ n = degree(g, x) and mx = max(m, n).
+
+ a. If method = 1 (default), computes sylvester1, Sylvester's matrix of 1840
+ of dimension (m + n) x (m + n). The determinants of properly chosen
+ submatrices of this matrix (a.k.a. subresultants) can be
+ used to compute the coefficients of the Euclidean PRS of f, g.
+
+ b. If method = 2, computes sylvester2, Sylvester's matrix of 1853
+ of dimension (2*mx) x (2*mx). The determinants of properly chosen
+ submatrices of this matrix (a.k.a. ``modified'' subresultants) can be
+ used to compute the coefficients of the Sturmian PRS of f, g.
+
+ Applications of these Matrices can be found in the references below.
+ Especially, for applications of sylvester2, see the first reference!!
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem
+ by Van Vleck Regarding Sturm Sequences. Serdica Journal of Computing,
+ Vol. 7, No 4, 101-134, 2013.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
+ and Modified Subresultant Polynomial Remainder Sequences.''
+ Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
+
+ '''
+ # obtain degrees of polys
+ m, n = degree( Poly(f, x), x), degree( Poly(g, x), x)
+
+ # Special cases:
+ # A:: case m = n < 0 (i.e. both polys are 0)
+ if m == n and n < 0:
+ return Matrix([])
+
+ # B:: case m = n = 0 (i.e. both polys are constants)
+ if m == n and n == 0:
+ return Matrix([])
+
+ # C:: m == 0 and n < 0 or m < 0 and n == 0
+ # (i.e. one poly is constant and the other is 0)
+ if m == 0 and n < 0:
+ return Matrix([])
+ elif m < 0 and n == 0:
+ return Matrix([])
+
+ # D:: m >= 1 and n < 0 or m < 0 and n >=1
+ # (i.e. one poly is of degree >=1 and the other is 0)
+ if m >= 1 and n < 0:
+ return Matrix([0])
+ elif m < 0 and n >= 1:
+ return Matrix([0])
+
+ fp = Poly(f, x).all_coeffs()
+ gp = Poly(g, x).all_coeffs()
+
+ # Sylvester's matrix of 1840 (default; a.k.a. sylvester1)
+ if method <= 1:
+ M = zeros(m + n)
+ k = 0
+ for i in range(n):
+ j = k
+ for coeff in fp:
+ M[i, j] = coeff
+ j = j + 1
+ k = k + 1
+ k = 0
+ for i in range(n, m + n):
+ j = k
+ for coeff in gp:
+ M[i, j] = coeff
+ j = j + 1
+ k = k + 1
+ return M
+
+ # Sylvester's matrix of 1853 (a.k.a sylvester2)
+ if method >= 2:
+ if len(fp) < len(gp):
+ h = []
+ for i in range(len(gp) - len(fp)):
+ h.append(0)
+ fp[ : 0] = h
+ else:
+ h = []
+ for i in range(len(fp) - len(gp)):
+ h.append(0)
+ gp[ : 0] = h
+ mx = max(m, n)
+ dim = 2*mx
+ M = zeros( dim )
+ k = 0
+ for i in range( mx ):
+ j = k
+ for coeff in fp:
+ M[2*i, j] = coeff
+ j = j + 1
+ j = k
+ for coeff in gp:
+ M[2*i + 1, j] = coeff
+ j = j + 1
+ k = k + 1
+ return M
+
+def process_matrix_output(poly_seq, x):
+ """
+ poly_seq is a polynomial remainder sequence computed either by
+ (modified_)subresultants_bezout or by (modified_)subresultants_sylv.
+
+ This function removes from poly_seq all zero polynomials as well
+ as all those whose degree is equal to the degree of a preceding
+ polynomial in poly_seq, as we scan it from left to right.
+
+ """
+ L = poly_seq[:] # get a copy of the input sequence
+ d = degree(L[1], x)
+ i = 2
+ while i < len(L):
+ d_i = degree(L[i], x)
+ if d_i < 0: # zero poly
+ L.remove(L[i])
+ i = i - 1
+ if d == d_i: # poly degree equals degree of previous poly
+ L.remove(L[i])
+ i = i - 1
+ if d_i >= 0:
+ d = d_i
+ i = i + 1
+
+ return L
+
+def subresultants_sylv(f, g, x):
+ """
+ The input polynomials f, g are in Z[x] or in Q[x]. It is assumed
+ that deg(f) >= deg(g).
+
+ Computes the subresultant polynomial remainder sequence (prs)
+ of f, g by evaluating determinants of appropriately selected
+ submatrices of sylvester(f, g, x, 1). The dimensions of the
+ latter are (deg(f) + deg(g)) x (deg(f) + deg(g)).
+
+ Each coefficient is computed by evaluating the determinant of the
+ corresponding submatrix of sylvester(f, g, x, 1).
+
+ If the subresultant prs is complete, then the output coincides
+ with the Euclidean sequence of the polynomials f, g.
+
+ References:
+ ===========
+ 1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants
+ and Their Applications. Appl. Algebra in Engin., Communic. and Comp.,
+ Vol. 15, 233-266, 2004.
+
+ """
+
+ # make sure neither f nor g is 0
+ if f == 0 or g == 0:
+ return [f, g]
+
+ n = degF = degree(f, x)
+ m = degG = degree(g, x)
+
+ # make sure proper degrees
+ if n == 0 and m == 0:
+ return [f, g]
+ if n < m:
+ n, m, degF, degG, f, g = m, n, degG, degF, g, f
+ if n > 0 and m == 0:
+ return [f, g]
+
+ SR_L = [f, g] # subresultant list
+
+ # form matrix sylvester(f, g, x, 1)
+ S = sylvester(f, g, x, 1)
+
+ # pick appropriate submatrices of S
+ # and form subresultant polys
+ j = m - 1
+
+ while j > 0:
+ Sp = S[:, :] # copy of S
+ # delete last j rows of coeffs of g
+ for ind in range(m + n - j, m + n):
+ Sp.row_del(m + n - j)
+ # delete last j rows of coeffs of f
+ for ind in range(m - j, m):
+ Sp.row_del(m - j)
+
+ # evaluate determinants and form coefficients list
+ coeff_L, k, l = [], Sp.rows, 0
+ while l <= j:
+ coeff_L.append(Sp[:, 0:k].det())
+ Sp.col_swap(k - 1, k + l)
+ l += 1
+
+ # form poly and append to SP_L
+ SR_L.append(Poly(coeff_L, x).as_expr())
+ j -= 1
+
+ # j = 0
+ SR_L.append(S.det())
+
+ return process_matrix_output(SR_L, x)
+
+def modified_subresultants_sylv(f, g, x):
+ """
+ The input polynomials f, g are in Z[x] or in Q[x]. It is assumed
+ that deg(f) >= deg(g).
+
+ Computes the modified subresultant polynomial remainder sequence (prs)
+ of f, g by evaluating determinants of appropriately selected
+ submatrices of sylvester(f, g, x, 2). The dimensions of the
+ latter are (2*deg(f)) x (2*deg(f)).
+
+ Each coefficient is computed by evaluating the determinant of the
+ corresponding submatrix of sylvester(f, g, x, 2).
+
+ If the modified subresultant prs is complete, then the output coincides
+ with the Sturmian sequence of the polynomials f, g.
+
+ References:
+ ===========
+ 1. A. G. Akritas,G.I. Malaschonok and P.S. Vigklas:
+ Sturm Sequences and Modified Subresultant Polynomial Remainder
+ Sequences. Serdica Journal of Computing, Vol. 8, No 1, 29--46, 2014.
+
+ """
+
+ # make sure neither f nor g is 0
+ if f == 0 or g == 0:
+ return [f, g]
+
+ n = degF = degree(f, x)
+ m = degG = degree(g, x)
+
+ # make sure proper degrees
+ if n == 0 and m == 0:
+ return [f, g]
+ if n < m:
+ n, m, degF, degG, f, g = m, n, degG, degF, g, f
+ if n > 0 and m == 0:
+ return [f, g]
+
+ SR_L = [f, g] # modified subresultant list
+
+ # form matrix sylvester(f, g, x, 2)
+ S = sylvester(f, g, x, 2)
+
+ # pick appropriate submatrices of S
+ # and form modified subresultant polys
+ j = m - 1
+
+ while j > 0:
+ # delete last 2*j rows of pairs of coeffs of f, g
+ Sp = S[0:2*n - 2*j, :] # copy of first 2*n - 2*j rows of S
+
+ # evaluate determinants and form coefficients list
+ coeff_L, k, l = [], Sp.rows, 0
+ while l <= j:
+ coeff_L.append(Sp[:, 0:k].det())
+ Sp.col_swap(k - 1, k + l)
+ l += 1
+
+ # form poly and append to SP_L
+ SR_L.append(Poly(coeff_L, x).as_expr())
+ j -= 1
+
+ # j = 0
+ SR_L.append(S.det())
+
+ return process_matrix_output(SR_L, x)
+
+def res(f, g, x):
+ """
+ The input polynomials f, g are in Z[x] or in Q[x].
+
+ The output is the resultant of f, g computed by evaluating
+ the determinant of the matrix sylvester(f, g, x, 1).
+
+ References:
+ ===========
+ 1. J. S. Cohen: Computer Algebra and Symbolic Computation
+ - Mathematical Methods. A. K. Peters, 2003.
+
+ """
+ if f == 0 or g == 0:
+ raise PolynomialError("The resultant of %s and %s is not defined" % (f, g))
+ else:
+ return sylvester(f, g, x, 1).det()
+
+def res_q(f, g, x):
+ """
+ The input polynomials f, g are in Z[x] or in Q[x].
+
+ The output is the resultant of f, g computed recursively
+ by polynomial divisions in Q[x], using the function rem.
+ See Cohen's book p. 281.
+
+ References:
+ ===========
+ 1. J. S. Cohen: Computer Algebra and Symbolic Computation
+ - Mathematical Methods. A. K. Peters, 2003.
+ """
+ m = degree(f, x)
+ n = degree(g, x)
+ if m < n:
+ return (-1)**(m*n) * res_q(g, f, x)
+ elif n == 0: # g is a constant
+ return g**m
+ else:
+ r = rem(f, g, x)
+ if r == 0:
+ return 0
+ else:
+ s = degree(r, x)
+ l = LC(g, x)
+ return (-1)**(m*n) * l**(m-s)*res_q(g, r, x)
+
+def res_z(f, g, x):
+ """
+ The input polynomials f, g are in Z[x] or in Q[x].
+
+ The output is the resultant of f, g computed recursively
+ by polynomial divisions in Z[x], using the function prem().
+ See Cohen's book p. 283.
+
+ References:
+ ===========
+ 1. J. S. Cohen: Computer Algebra and Symbolic Computation
+ - Mathematical Methods. A. K. Peters, 2003.
+ """
+ m = degree(f, x)
+ n = degree(g, x)
+ if m < n:
+ return (-1)**(m*n) * res_z(g, f, x)
+ elif n == 0: # g is a constant
+ return g**m
+ else:
+ r = prem(f, g, x)
+ if r == 0:
+ return 0
+ else:
+ delta = m - n + 1
+ w = (-1)**(m*n) * res_z(g, r, x)
+ s = degree(r, x)
+ l = LC(g, x)
+ k = delta * n - m + s
+ return quo(w, l**k, x)
+
+def sign_seq(poly_seq, x):
+ """
+ Given a sequence of polynomials poly_seq, it returns
+ the sequence of signs of the leading coefficients of
+ the polynomials in poly_seq.
+
+ """
+ return [sign(LC(poly_seq[i], x)) for i in range(len(poly_seq))]
+
+def bezout(p, q, x, method='bz'):
+ """
+ The input polynomials p, q are in Z[x] or in Q[x]. Let
+ mx = max(degree(p, x), degree(q, x)).
+
+ The default option bezout(p, q, x, method='bz') returns Bezout's
+ symmetric matrix of p and q, of dimensions (mx) x (mx). The
+ determinant of this matrix is equal to the determinant of sylvester2,
+ Sylvester's matrix of 1853, whose dimensions are (2*mx) x (2*mx);
+ however the subresultants of these two matrices may differ.
+
+ The other option, bezout(p, q, x, 'prs'), is of interest to us
+ in this module because it returns a matrix equivalent to sylvester2.
+ In this case all subresultants of the two matrices are identical.
+
+ Both the subresultant polynomial remainder sequence (prs) and
+ the modified subresultant prs of p and q can be computed by
+ evaluating determinants of appropriately selected submatrices of
+ bezout(p, q, x, 'prs') --- one determinant per coefficient of the
+ remainder polynomials.
+
+ The matrices bezout(p, q, x, 'bz') and bezout(p, q, x, 'prs')
+ are related by the formula
+
+ bezout(p, q, x, 'prs') =
+ backward_eye(deg(p)) * bezout(p, q, x, 'bz') * backward_eye(deg(p)),
+
+ where backward_eye() is the backward identity function.
+
+ References
+ ==========
+ 1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants
+ and Their Applications. Appl. Algebra in Engin., Communic. and Comp.,
+ Vol. 15, 233-266, 2004.
+
+ """
+ # obtain degrees of polys
+ m, n = degree( Poly(p, x), x), degree( Poly(q, x), x)
+
+ # Special cases:
+ # A:: case m = n < 0 (i.e. both polys are 0)
+ if m == n and n < 0:
+ return Matrix([])
+
+ # B:: case m = n = 0 (i.e. both polys are constants)
+ if m == n and n == 0:
+ return Matrix([])
+
+ # C:: m == 0 and n < 0 or m < 0 and n == 0
+ # (i.e. one poly is constant and the other is 0)
+ if m == 0 and n < 0:
+ return Matrix([])
+ elif m < 0 and n == 0:
+ return Matrix([])
+
+ # D:: m >= 1 and n < 0 or m < 0 and n >=1
+ # (i.e. one poly is of degree >=1 and the other is 0)
+ if m >= 1 and n < 0:
+ return Matrix([0])
+ elif m < 0 and n >= 1:
+ return Matrix([0])
+
+ y = var('y')
+
+ # expr is 0 when x = y
+ expr = p * q.subs({x:y}) - p.subs({x:y}) * q
+
+ # hence expr is exactly divisible by x - y
+ poly = Poly( quo(expr, x-y), x, y)
+
+ # form Bezout matrix and store them in B as indicated to get
+ # the LC coefficient of each poly either in the first position
+ # of each row (method='prs') or in the last (method='bz').
+ mx = max(m, n)
+ B = zeros(mx)
+ for i in range(mx):
+ for j in range(mx):
+ if method == 'prs':
+ B[mx - 1 - i, mx - 1 - j] = poly.nth(i, j)
+ else:
+ B[i, j] = poly.nth(i, j)
+ return B
+
+def backward_eye(n):
+ '''
+ Returns the backward identity matrix of dimensions n x n.
+
+ Needed to "turn" the Bezout matrices
+ so that the leading coefficients are first.
+ See docstring of the function bezout(p, q, x, method='bz').
+ '''
+ M = eye(n) # identity matrix of order n
+
+ for i in range(int(M.rows / 2)):
+ M.row_swap(0 + i, M.rows - 1 - i)
+
+ return M
+
+def subresultants_bezout(p, q, x):
+ """
+ The input polynomials p, q are in Z[x] or in Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the subresultant polynomial remainder sequence
+ of p, q by evaluating determinants of appropriately selected
+ submatrices of bezout(p, q, x, 'prs'). The dimensions of the
+ latter are deg(p) x deg(p).
+
+ Each coefficient is computed by evaluating the determinant of the
+ corresponding submatrix of bezout(p, q, x, 'prs').
+
+ bezout(p, q, x, 'prs) is used instead of sylvester(p, q, x, 1),
+ Sylvester's matrix of 1840, because the dimensions of the latter
+ are (deg(p) + deg(q)) x (deg(p) + deg(q)).
+
+ If the subresultant prs is complete, then the output coincides
+ with the Euclidean sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants
+ and Their Applications. Appl. Algebra in Engin., Communic. and Comp.,
+ Vol. 15, 233-266, 2004.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ f, g = p, q
+ n = degF = degree(f, x)
+ m = degG = degree(g, x)
+
+ # make sure proper degrees
+ if n == 0 and m == 0:
+ return [f, g]
+ if n < m:
+ n, m, degF, degG, f, g = m, n, degG, degF, g, f
+ if n > 0 and m == 0:
+ return [f, g]
+
+ SR_L = [f, g] # subresultant list
+ F = LC(f, x)**(degF - degG)
+
+ # form the bezout matrix
+ B = bezout(f, g, x, 'prs')
+
+ # pick appropriate submatrices of B
+ # and form subresultant polys
+ if degF > degG:
+ j = 2
+ if degF == degG:
+ j = 1
+ while j <= degF:
+ M = B[0:j, :]
+ k, coeff_L = j - 1, []
+ while k <= degF - 1:
+ coeff_L.append(M[:, 0:j].det())
+ if k < degF - 1:
+ M.col_swap(j - 1, k + 1)
+ k = k + 1
+
+ # apply Theorem 2.1 in the paper by Toca & Vega 2004
+ # to get correct signs
+ SR_L.append(int((-1)**(j*(j-1)/2)) * (Poly(coeff_L, x) / F).as_expr())
+ j = j + 1
+
+ return process_matrix_output(SR_L, x)
+
+def modified_subresultants_bezout(p, q, x):
+ """
+ The input polynomials p, q are in Z[x] or in Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the modified subresultant polynomial remainder sequence
+ of p, q by evaluating determinants of appropriately selected
+ submatrices of bezout(p, q, x, 'prs'). The dimensions of the
+ latter are deg(p) x deg(p).
+
+ Each coefficient is computed by evaluating the determinant of the
+ corresponding submatrix of bezout(p, q, x, 'prs').
+
+ bezout(p, q, x, 'prs') is used instead of sylvester(p, q, x, 2),
+ Sylvester's matrix of 1853, because the dimensions of the latter
+ are 2*deg(p) x 2*deg(p).
+
+ If the modified subresultant prs is complete, and LC( p ) > 0, the output
+ coincides with the (generalized) Sturm's sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
+ and Modified Subresultant Polynomial Remainder Sequences.''
+ Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
+
+ 2. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants
+ and Their Applications. Appl. Algebra in Engin., Communic. and Comp.,
+ Vol. 15, 233-266, 2004.
+
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ f, g = p, q
+ n = degF = degree(f, x)
+ m = degG = degree(g, x)
+
+ # make sure proper degrees
+ if n == 0 and m == 0:
+ return [f, g]
+ if n < m:
+ n, m, degF, degG, f, g = m, n, degG, degF, g, f
+ if n > 0 and m == 0:
+ return [f, g]
+
+ SR_L = [f, g] # subresultant list
+
+ # form the bezout matrix
+ B = bezout(f, g, x, 'prs')
+
+ # pick appropriate submatrices of B
+ # and form subresultant polys
+ if degF > degG:
+ j = 2
+ if degF == degG:
+ j = 1
+ while j <= degF:
+ M = B[0:j, :]
+ k, coeff_L = j - 1, []
+ while k <= degF - 1:
+ coeff_L.append(M[:, 0:j].det())
+ if k < degF - 1:
+ M.col_swap(j - 1, k + 1)
+ k = k + 1
+
+ ## Theorem 2.1 in the paper by Toca & Vega 2004 is _not needed_
+ ## in this case since
+ ## the bezout matrix is equivalent to sylvester2
+ SR_L.append(( Poly(coeff_L, x)).as_expr())
+ j = j + 1
+
+ return process_matrix_output(SR_L, x)
+
+def sturm_pg(p, q, x, method=0):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the (generalized) Sturm sequence of p and q in Z[x] or Q[x].
+ If q = diff(p, x, 1) it is the usual Sturm sequence.
+
+ A. If method == 0, default, the remainder coefficients of the sequence
+ are (in absolute value) ``modified'' subresultants, which for non-monic
+ polynomials are greater than the coefficients of the corresponding
+ subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))).
+
+ B. If method == 1, the remainder coefficients of the sequence are (in
+ absolute value) subresultants, which for non-monic polynomials are
+ smaller than the coefficients of the corresponding ``modified''
+ subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))).
+
+ If the Sturm sequence is complete, method=0 and LC( p ) > 0, the coefficients
+ of the polynomials in the sequence are ``modified'' subresultants.
+ That is, they are determinants of appropriately selected submatrices of
+ sylvester2, Sylvester's matrix of 1853. In this case the Sturm sequence
+ coincides with the ``modified'' subresultant prs, of the polynomials
+ p, q.
+
+ If the Sturm sequence is incomplete and method=0 then the signs of the
+ coefficients of the polynomials in the sequence may differ from the signs
+ of the coefficients of the corresponding polynomials in the ``modified''
+ subresultant prs; however, the absolute values are the same.
+
+ To compute the coefficients, no determinant evaluation takes place. Instead,
+ polynomial divisions in Q[x] are performed, using the function rem(p, q, x);
+ the coefficients of the remainders computed this way become (``modified'')
+ subresultants with the help of the Pell-Gordon Theorem of 1917.
+ See also the function euclid_pg(p, q, x).
+
+ References
+ ==========
+ 1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding
+ the Highest Common Factor of Two Polynomials. Annals of MatheMatics,
+ Second Series, 18 (1917), No. 4, 188-193.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
+ and Modified Subresultant Polynomial Remainder Sequences.''
+ Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ # make sure proper degrees
+ d0 = degree(p, x)
+ d1 = degree(q, x)
+ if d0 == 0 and d1 == 0:
+ return [p, q]
+ if d1 > d0:
+ d0, d1 = d1, d0
+ p, q = q, p
+ if d0 > 0 and d1 == 0:
+ return [p,q]
+
+ # make sure LC(p) > 0
+ flag = 0
+ if LC(p,x) < 0:
+ flag = 1
+ p = -p
+ q = -q
+
+ # initialize
+ lcf = LC(p, x)**(d0 - d1) # lcf * subr = modified subr
+ a0, a1 = p, q # the input polys
+ sturm_seq = [a0, a1] # the output list
+ del0 = d0 - d1 # degree difference
+ rho1 = LC(a1, x) # leading coeff of a1
+ exp_deg = d1 - 1 # expected degree of a2
+ a2 = - rem(a0, a1, domain=QQ) # first remainder
+ rho2 = LC(a2,x) # leading coeff of a2
+ d2 = degree(a2, x) # actual degree of a2
+ deg_diff_new = exp_deg - d2 # expected - actual degree
+ del1 = d1 - d2 # degree difference
+
+ # mul_fac is the factor by which a2 is multiplied to
+ # get integer coefficients
+ mul_fac_old = rho1**(del0 + del1 - deg_diff_new)
+
+ # append accordingly
+ if method == 0:
+ sturm_seq.append( simplify(lcf * a2 * Abs(mul_fac_old)))
+ else:
+ sturm_seq.append( simplify( a2 * Abs(mul_fac_old)))
+
+ # main loop
+ deg_diff_old = deg_diff_new
+ while d2 > 0:
+ a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
+ del0 = del1 # update degree difference
+ exp_deg = d1 - 1 # new expected degree
+ a2 = - rem(a0, a1, domain=QQ) # new remainder
+ rho3 = LC(a2, x) # leading coeff of a2
+ d2 = degree(a2, x) # actual degree of a2
+ deg_diff_new = exp_deg - d2 # expected - actual degree
+ del1 = d1 - d2 # degree difference
+
+ # take into consideration the power
+ # rho1**deg_diff_old that was "left out"
+ expo_old = deg_diff_old # rho1 raised to this power
+ expo_new = del0 + del1 - deg_diff_new # rho2 raised to this power
+
+ # update variables and append
+ mul_fac_new = rho2**(expo_new) * rho1**(expo_old) * mul_fac_old
+ deg_diff_old, mul_fac_old = deg_diff_new, mul_fac_new
+ rho1, rho2 = rho2, rho3
+ if method == 0:
+ sturm_seq.append( simplify(lcf * a2 * Abs(mul_fac_old)))
+ else:
+ sturm_seq.append( simplify( a2 * Abs(mul_fac_old)))
+
+ if flag: # change the sign of the sequence
+ sturm_seq = [-i for i in sturm_seq]
+
+ # gcd is of degree > 0 ?
+ m = len(sturm_seq)
+ if sturm_seq[m - 1] == nan or sturm_seq[m - 1] == 0:
+ sturm_seq.pop(m - 1)
+
+ return sturm_seq
+
+def sturm_q(p, q, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the (generalized) Sturm sequence of p and q in Q[x].
+ Polynomial divisions in Q[x] are performed, using the function rem(p, q, x).
+
+ The coefficients of the polynomials in the Sturm sequence can be uniquely
+ determined from the corresponding coefficients of the polynomials found
+ either in:
+
+ (a) the ``modified'' subresultant prs, (references 1, 2)
+
+ or in
+
+ (b) the subresultant prs (reference 3).
+
+ References
+ ==========
+ 1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding
+ the Highest Common Factor of Two Polynomials. Annals of MatheMatics,
+ Second Series, 18 (1917), No. 4, 188-193.
+
+ 2 Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
+ and Modified Subresultant Polynomial Remainder Sequences.''
+ Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
+
+ 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
+ on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ # make sure proper degrees
+ d0 = degree(p, x)
+ d1 = degree(q, x)
+ if d0 == 0 and d1 == 0:
+ return [p, q]
+ if d1 > d0:
+ d0, d1 = d1, d0
+ p, q = q, p
+ if d0 > 0 and d1 == 0:
+ return [p,q]
+
+ # make sure LC(p) > 0
+ flag = 0
+ if LC(p,x) < 0:
+ flag = 1
+ p = -p
+ q = -q
+
+ # initialize
+ a0, a1 = p, q # the input polys
+ sturm_seq = [a0, a1] # the output list
+ a2 = -rem(a0, a1, domain=QQ) # first remainder
+ d2 = degree(a2, x) # degree of a2
+ sturm_seq.append( a2 )
+
+ # main loop
+ while d2 > 0:
+ a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
+ a2 = -rem(a0, a1, domain=QQ) # new remainder
+ d2 = degree(a2, x) # actual degree of a2
+ sturm_seq.append( a2 )
+
+ if flag: # change the sign of the sequence
+ sturm_seq = [-i for i in sturm_seq]
+
+ # gcd is of degree > 0 ?
+ m = len(sturm_seq)
+ if sturm_seq[m - 1] == nan or sturm_seq[m - 1] == 0:
+ sturm_seq.pop(m - 1)
+
+ return sturm_seq
+
+def sturm_amv(p, q, x, method=0):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the (generalized) Sturm sequence of p and q in Z[x] or Q[x].
+ If q = diff(p, x, 1) it is the usual Sturm sequence.
+
+ A. If method == 0, default, the remainder coefficients of the
+ sequence are (in absolute value) ``modified'' subresultants, which
+ for non-monic polynomials are greater than the coefficients of the
+ corresponding subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))).
+
+ B. If method == 1, the remainder coefficients of the sequence are (in
+ absolute value) subresultants, which for non-monic polynomials are
+ smaller than the coefficients of the corresponding ``modified''
+ subresultants by the factor Abs( LC(p)**( deg(p)- deg(q)) ).
+
+ If the Sturm sequence is complete, method=0 and LC( p ) > 0, then the
+ coefficients of the polynomials in the sequence are ``modified'' subresultants.
+ That is, they are determinants of appropriately selected submatrices of
+ sylvester2, Sylvester's matrix of 1853. In this case the Sturm sequence
+ coincides with the ``modified'' subresultant prs, of the polynomials
+ p, q.
+
+ If the Sturm sequence is incomplete and method=0 then the signs of the
+ coefficients of the polynomials in the sequence may differ from the signs
+ of the coefficients of the corresponding polynomials in the ``modified''
+ subresultant prs; however, the absolute values are the same.
+
+ To compute the coefficients, no determinant evaluation takes place.
+ Instead, we first compute the euclidean sequence of p and q using
+ euclid_amv(p, q, x) and then: (a) change the signs of the remainders in the
+ Euclidean sequence according to the pattern "-, -, +, +, -, -, +, +,..."
+ (see Lemma 1 in the 1st reference or Theorem 3 in the 2nd reference)
+ and (b) if method=0, assuming deg(p) > deg(q), we multiply the remainder
+ coefficients of the Euclidean sequence times the factor
+ Abs( LC(p)**( deg(p)- deg(q)) ) to make them modified subresultants.
+ See also the function sturm_pg(p, q, x).
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
+ on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders
+ Obtained in Finding the Greatest Common Divisor of Two Polynomials.'' Serdica
+ Journal of Computing 9(2) (2015), 123-138.
+
+ 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
+ Remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
+ Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
+
+ """
+ # compute the euclidean sequence
+ prs = euclid_amv(p, q, x)
+
+ # defensive
+ if prs == [] or len(prs) == 2:
+ return prs
+
+ # the coefficients in prs are subresultants and hence are smaller
+ # than the corresponding subresultants by the factor
+ # Abs( LC(prs[0])**( deg(prs[0]) - deg(prs[1])) ); Theorem 2, 2nd reference.
+ lcf = Abs( LC(prs[0])**( degree(prs[0], x) - degree(prs[1], x) ) )
+
+ # the signs of the first two polys in the sequence stay the same
+ sturm_seq = [prs[0], prs[1]]
+
+ # change the signs according to "-, -, +, +, -, -, +, +,..."
+ # and multiply times lcf if needed
+ flag = 0
+ m = len(prs)
+ i = 2
+ while i <= m-1:
+ if flag == 0:
+ sturm_seq.append( - prs[i] )
+ i = i + 1
+ if i == m:
+ break
+ sturm_seq.append( - prs[i] )
+ i = i + 1
+ flag = 1
+ elif flag == 1:
+ sturm_seq.append( prs[i] )
+ i = i + 1
+ if i == m:
+ break
+ sturm_seq.append( prs[i] )
+ i = i + 1
+ flag = 0
+
+ # subresultants or modified subresultants?
+ if method == 0 and lcf > 1:
+ aux_seq = [sturm_seq[0], sturm_seq[1]]
+ for i in range(2, m):
+ aux_seq.append(simplify(sturm_seq[i] * lcf ))
+ sturm_seq = aux_seq
+
+ return sturm_seq
+
+def euclid_pg(p, q, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the Euclidean sequence of p and q in Z[x] or Q[x].
+
+ If the Euclidean sequence is complete the coefficients of the polynomials
+ in the sequence are subresultants. That is, they are determinants of
+ appropriately selected submatrices of sylvester1, Sylvester's matrix of 1840.
+ In this case the Euclidean sequence coincides with the subresultant prs
+ of the polynomials p, q.
+
+ If the Euclidean sequence is incomplete the signs of the coefficients of the
+ polynomials in the sequence may differ from the signs of the coefficients of
+ the corresponding polynomials in the subresultant prs; however, the absolute
+ values are the same.
+
+ To compute the Euclidean sequence, no determinant evaluation takes place.
+ We first compute the (generalized) Sturm sequence of p and q using
+ sturm_pg(p, q, x, 1), in which case the coefficients are (in absolute value)
+ equal to subresultants. Then we change the signs of the remainders in the
+ Sturm sequence according to the pattern "-, -, +, +, -, -, +, +,..." ;
+ see Lemma 1 in the 1st reference or Theorem 3 in the 2nd reference as well as
+ the function sturm_pg(p, q, x).
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
+ on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders
+ Obtained in Finding the Greatest Common Divisor of Two Polynomials.'' Serdica
+ Journal of Computing 9(2) (2015), 123-138.
+
+ 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
+ Remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
+ Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
+ """
+ # compute the sturmian sequence using the Pell-Gordon (or AMV) theorem
+ # with the coefficients in the prs being (in absolute value) subresultants
+ prs = sturm_pg(p, q, x, 1) ## any other method would do
+
+ # defensive
+ if prs == [] or len(prs) == 2:
+ return prs
+
+ # the signs of the first two polys in the sequence stay the same
+ euclid_seq = [prs[0], prs[1]]
+
+ # change the signs according to "-, -, +, +, -, -, +, +,..."
+ flag = 0
+ m = len(prs)
+ i = 2
+ while i <= m-1:
+ if flag == 0:
+ euclid_seq.append(- prs[i] )
+ i = i + 1
+ if i == m:
+ break
+ euclid_seq.append(- prs[i] )
+ i = i + 1
+ flag = 1
+ elif flag == 1:
+ euclid_seq.append(prs[i] )
+ i = i + 1
+ if i == m:
+ break
+ euclid_seq.append(prs[i] )
+ i = i + 1
+ flag = 0
+
+ return euclid_seq
+
+def euclid_q(p, q, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the Euclidean sequence of p and q in Q[x].
+ Polynomial divisions in Q[x] are performed, using the function rem(p, q, x).
+
+ The coefficients of the polynomials in the Euclidean sequence can be uniquely
+ determined from the corresponding coefficients of the polynomials found
+ either in:
+
+ (a) the ``modified'' subresultant polynomial remainder sequence,
+ (references 1, 2)
+
+ or in
+
+ (b) the subresultant polynomial remainder sequence (references 3).
+
+ References
+ ==========
+ 1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding
+ the Highest Common Factor of Two Polynomials. Annals of MatheMatics,
+ Second Series, 18 (1917), No. 4, 188-193.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
+ and Modified Subresultant Polynomial Remainder Sequences.''
+ Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
+
+ 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
+ on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ # make sure proper degrees
+ d0 = degree(p, x)
+ d1 = degree(q, x)
+ if d0 == 0 and d1 == 0:
+ return [p, q]
+ if d1 > d0:
+ d0, d1 = d1, d0
+ p, q = q, p
+ if d0 > 0 and d1 == 0:
+ return [p,q]
+
+ # make sure LC(p) > 0
+ flag = 0
+ if LC(p,x) < 0:
+ flag = 1
+ p = -p
+ q = -q
+
+ # initialize
+ a0, a1 = p, q # the input polys
+ euclid_seq = [a0, a1] # the output list
+ a2 = rem(a0, a1, domain=QQ) # first remainder
+ d2 = degree(a2, x) # degree of a2
+ euclid_seq.append( a2 )
+
+ # main loop
+ while d2 > 0:
+ a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
+ a2 = rem(a0, a1, domain=QQ) # new remainder
+ d2 = degree(a2, x) # actual degree of a2
+ euclid_seq.append( a2 )
+
+ if flag: # change the sign of the sequence
+ euclid_seq = [-i for i in euclid_seq]
+
+ # gcd is of degree > 0 ?
+ m = len(euclid_seq)
+ if euclid_seq[m - 1] == nan or euclid_seq[m - 1] == 0:
+ euclid_seq.pop(m - 1)
+
+ return euclid_seq
+
+def euclid_amv(f, g, x):
+ """
+ f, g are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(f, x) >= degree(g, x).
+
+ Computes the Euclidean sequence of p and q in Z[x] or Q[x].
+
+ If the Euclidean sequence is complete the coefficients of the polynomials
+ in the sequence are subresultants. That is, they are determinants of
+ appropriately selected submatrices of sylvester1, Sylvester's matrix of 1840.
+ In this case the Euclidean sequence coincides with the subresultant prs,
+ of the polynomials p, q.
+
+ If the Euclidean sequence is incomplete the signs of the coefficients of the
+ polynomials in the sequence may differ from the signs of the coefficients of
+ the corresponding polynomials in the subresultant prs; however, the absolute
+ values are the same.
+
+ To compute the coefficients, no determinant evaluation takes place.
+ Instead, polynomial divisions in Z[x] or Q[x] are performed, using
+ the function rem_z(f, g, x); the coefficients of the remainders
+ computed this way become subresultants with the help of the
+ Collins-Brown-Traub formula for coefficient reduction.
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
+ on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
+ remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
+ Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
+
+ """
+ # make sure neither f nor g is 0
+ if f == 0 or g == 0:
+ return [f, g]
+
+ # make sure proper degrees
+ d0 = degree(f, x)
+ d1 = degree(g, x)
+ if d0 == 0 and d1 == 0:
+ return [f, g]
+ if d1 > d0:
+ d0, d1 = d1, d0
+ f, g = g, f
+ if d0 > 0 and d1 == 0:
+ return [f, g]
+
+ # initialize
+ a0 = f
+ a1 = g
+ euclid_seq = [a0, a1]
+ deg_dif_p1, c = degree(a0, x) - degree(a1, x) + 1, -1
+
+ # compute the first polynomial of the prs
+ i = 1
+ a2 = rem_z(a0, a1, x) / Abs( (-1)**deg_dif_p1 ) # first remainder
+ euclid_seq.append( a2 )
+ d2 = degree(a2, x) # actual degree of a2
+
+ # main loop
+ while d2 >= 1:
+ a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
+ i += 1
+ sigma0 = -LC(a0)
+ c = (sigma0**(deg_dif_p1 - 1)) / (c**(deg_dif_p1 - 2))
+ deg_dif_p1 = degree(a0, x) - d2 + 1
+ a2 = rem_z(a0, a1, x) / Abs( (c**(deg_dif_p1 - 1)) * sigma0 )
+ euclid_seq.append( a2 )
+ d2 = degree(a2, x) # actual degree of a2
+
+ # gcd is of degree > 0 ?
+ m = len(euclid_seq)
+ if euclid_seq[m - 1] == nan or euclid_seq[m - 1] == 0:
+ euclid_seq.pop(m - 1)
+
+ return euclid_seq
+
+def modified_subresultants_pg(p, q, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the ``modified'' subresultant prs of p and q in Z[x] or Q[x];
+ the coefficients of the polynomials in the sequence are
+ ``modified'' subresultants. That is, they are determinants of appropriately
+ selected submatrices of sylvester2, Sylvester's matrix of 1853.
+
+ To compute the coefficients, no determinant evaluation takes place. Instead,
+ polynomial divisions in Q[x] are performed, using the function rem(p, q, x);
+ the coefficients of the remainders computed this way become ``modified''
+ subresultants with the help of the Pell-Gordon Theorem of 1917.
+
+ If the ``modified'' subresultant prs is complete, and LC( p ) > 0, it coincides
+ with the (generalized) Sturm sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding
+ the Highest Common Factor of Two Polynomials. Annals of MatheMatics,
+ Second Series, 18 (1917), No. 4, 188-193.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
+ and Modified Subresultant Polynomial Remainder Sequences.''
+ Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ # make sure proper degrees
+ d0 = degree(p,x)
+ d1 = degree(q,x)
+ if d0 == 0 and d1 == 0:
+ return [p, q]
+ if d1 > d0:
+ d0, d1 = d1, d0
+ p, q = q, p
+ if d0 > 0 and d1 == 0:
+ return [p,q]
+
+ # initialize
+ k = var('k') # index in summation formula
+ u_list = [] # of elements (-1)**u_i
+ subres_l = [p, q] # mod. subr. prs output list
+ a0, a1 = p, q # the input polys
+ del0 = d0 - d1 # degree difference
+ degdif = del0 # save it
+ rho_1 = LC(a0) # lead. coeff (a0)
+
+ # Initialize Pell-Gordon variables
+ rho_list_minus_1 = sign( LC(a0, x)) # sign of LC(a0)
+ rho1 = LC(a1, x) # leading coeff of a1
+ rho_list = [ sign(rho1)] # of signs
+ p_list = [del0] # of degree differences
+ u = summation(k, (k, 1, p_list[0])) # value of u
+ u_list.append(u) # of u values
+ v = sum(p_list) # v value
+
+ # first remainder
+ exp_deg = d1 - 1 # expected degree of a2
+ a2 = - rem(a0, a1, domain=QQ) # first remainder
+ rho2 = LC(a2, x) # leading coeff of a2
+ d2 = degree(a2, x) # actual degree of a2
+ deg_diff_new = exp_deg - d2 # expected - actual degree
+ del1 = d1 - d2 # degree difference
+
+ # mul_fac is the factor by which a2 is multiplied to
+ # get integer coefficients
+ mul_fac_old = rho1**(del0 + del1 - deg_diff_new)
+
+ # update Pell-Gordon variables
+ p_list.append(1 + deg_diff_new) # deg_diff_new is 0 for complete seq
+
+ # apply Pell-Gordon formula (7) in second reference
+ num = 1 # numerator of fraction
+ for u in u_list:
+ num *= (-1)**u
+ num = num * (-1)**v
+
+ # denominator depends on complete / incomplete seq
+ if deg_diff_new == 0: # complete seq
+ den = 1
+ for k in range(len(rho_list)):
+ den *= rho_list[k]**(p_list[k] + p_list[k + 1])
+ den = den * rho_list_minus_1
+ else: # incomplete seq
+ den = 1
+ for k in range(len(rho_list)-1):
+ den *= rho_list[k]**(p_list[k] + p_list[k + 1])
+ den = den * rho_list_minus_1
+ expo = (p_list[len(rho_list) - 1] + p_list[len(rho_list)] - deg_diff_new)
+ den = den * rho_list[len(rho_list) - 1]**expo
+
+ # the sign of the determinant depends on sg(num / den)
+ if sign(num / den) > 0:
+ subres_l.append( simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) )
+ else:
+ subres_l.append(- simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) )
+
+ # update Pell-Gordon variables
+ k = var('k')
+ rho_list.append( sign(rho2))
+ u = summation(k, (k, 1, p_list[len(p_list) - 1]))
+ u_list.append(u)
+ v = sum(p_list)
+ deg_diff_old=deg_diff_new
+
+ # main loop
+ while d2 > 0:
+ a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
+ del0 = del1 # update degree difference
+ exp_deg = d1 - 1 # new expected degree
+ a2 = - rem(a0, a1, domain=QQ) # new remainder
+ rho3 = LC(a2, x) # leading coeff of a2
+ d2 = degree(a2, x) # actual degree of a2
+ deg_diff_new = exp_deg - d2 # expected - actual degree
+ del1 = d1 - d2 # degree difference
+
+ # take into consideration the power
+ # rho1**deg_diff_old that was "left out"
+ expo_old = deg_diff_old # rho1 raised to this power
+ expo_new = del0 + del1 - deg_diff_new # rho2 raised to this power
+
+ mul_fac_new = rho2**(expo_new) * rho1**(expo_old) * mul_fac_old
+
+ # update variables
+ deg_diff_old, mul_fac_old = deg_diff_new, mul_fac_new
+ rho1, rho2 = rho2, rho3
+
+ # update Pell-Gordon variables
+ p_list.append(1 + deg_diff_new) # deg_diff_new is 0 for complete seq
+
+ # apply Pell-Gordon formula (7) in second reference
+ num = 1 # numerator
+ for u in u_list:
+ num *= (-1)**u
+ num = num * (-1)**v
+
+ # denominator depends on complete / incomplete seq
+ if deg_diff_new == 0: # complete seq
+ den = 1
+ for k in range(len(rho_list)):
+ den *= rho_list[k]**(p_list[k] + p_list[k + 1])
+ den = den * rho_list_minus_1
+ else: # incomplete seq
+ den = 1
+ for k in range(len(rho_list)-1):
+ den *= rho_list[k]**(p_list[k] + p_list[k + 1])
+ den = den * rho_list_minus_1
+ expo = (p_list[len(rho_list) - 1] + p_list[len(rho_list)] - deg_diff_new)
+ den = den * rho_list[len(rho_list) - 1]**expo
+
+ # the sign of the determinant depends on sg(num / den)
+ if sign(num / den) > 0:
+ subres_l.append( simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) )
+ else:
+ subres_l.append(- simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) )
+
+ # update Pell-Gordon variables
+ k = var('k')
+ rho_list.append( sign(rho2))
+ u = summation(k, (k, 1, p_list[len(p_list) - 1]))
+ u_list.append(u)
+ v = sum(p_list)
+
+ # gcd is of degree > 0 ?
+ m = len(subres_l)
+ if subres_l[m - 1] == nan or subres_l[m - 1] == 0:
+ subres_l.pop(m - 1)
+
+ # LC( p ) < 0
+ m = len(subres_l) # list may be shorter now due to deg(gcd ) > 0
+ if LC( p ) < 0:
+ aux_seq = [subres_l[0], subres_l[1]]
+ for i in range(2, m):
+ aux_seq.append(simplify(subres_l[i] * (-1) ))
+ subres_l = aux_seq
+
+ return subres_l
+
+def subresultants_pg(p, q, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the subresultant prs of p and q in Z[x] or Q[x], from
+ the modified subresultant prs of p and q.
+
+ The coefficients of the polynomials in these two sequences differ only
+ in sign and the factor LC(p)**( deg(p)- deg(q)) as stated in
+ Theorem 2 of the reference.
+
+ The coefficients of the polynomials in the output sequence are
+ subresultants. That is, they are determinants of appropriately
+ selected submatrices of sylvester1, Sylvester's matrix of 1840.
+
+ If the subresultant prs is complete, then it coincides with the
+ Euclidean sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: "On the Remainders
+ Obtained in Finding the Greatest Common Divisor of Two Polynomials."
+ Serdica Journal of Computing 9(2) (2015), 123-138.
+
+ """
+ # compute the modified subresultant prs
+ lst = modified_subresultants_pg(p,q,x) ## any other method would do
+
+ # defensive
+ if lst == [] or len(lst) == 2:
+ return lst
+
+ # the coefficients in lst are modified subresultants and, hence, are
+ # greater than those of the corresponding subresultants by the factor
+ # LC(lst[0])**( deg(lst[0]) - deg(lst[1])); see Theorem 2 in reference.
+ lcf = LC(lst[0])**( degree(lst[0], x) - degree(lst[1], x) )
+
+ # Initialize the subresultant prs list
+ subr_seq = [lst[0], lst[1]]
+
+ # compute the degree sequences m_i and j_i of Theorem 2 in reference.
+ deg_seq = [degree(Poly(poly, x), x) for poly in lst]
+ deg = deg_seq[0]
+ deg_seq_s = deg_seq[1:-1]
+ m_seq = [m-1 for m in deg_seq_s]
+ j_seq = [deg - m for m in m_seq]
+
+ # compute the AMV factors of Theorem 2 in reference.
+ fact = [(-1)**( j*(j-1)/S(2) ) for j in j_seq]
+
+ # shortened list without the first two polys
+ lst_s = lst[2:]
+
+ # poly lst_s[k] is multiplied times fact[k], divided by lcf
+ # and appended to the subresultant prs list
+ m = len(fact)
+ for k in range(m):
+ if sign(fact[k]) == -1:
+ subr_seq.append(-lst_s[k] / lcf)
+ else:
+ subr_seq.append(lst_s[k] / lcf)
+
+ return subr_seq
+
+def subresultants_amv_q(p, q, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the subresultant prs of p and q in Q[x];
+ the coefficients of the polynomials in the sequence are
+ subresultants. That is, they are determinants of appropriately
+ selected submatrices of sylvester1, Sylvester's matrix of 1840.
+
+ To compute the coefficients, no determinant evaluation takes place.
+ Instead, polynomial divisions in Q[x] are performed, using the
+ function rem(p, q, x); the coefficients of the remainders
+ computed this way become subresultants with the help of the
+ Akritas-Malaschonok-Vigklas Theorem of 2015.
+
+ If the subresultant prs is complete, then it coincides with the
+ Euclidean sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
+ on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
+ remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
+ Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ # make sure proper degrees
+ d0 = degree(p, x)
+ d1 = degree(q, x)
+ if d0 == 0 and d1 == 0:
+ return [p, q]
+ if d1 > d0:
+ d0, d1 = d1, d0
+ p, q = q, p
+ if d0 > 0 and d1 == 0:
+ return [p, q]
+
+ # initialize
+ i, s = 0, 0 # counters for remainders & odd elements
+ p_odd_index_sum = 0 # contains the sum of p_1, p_3, etc
+ subres_l = [p, q] # subresultant prs output list
+ a0, a1 = p, q # the input polys
+ sigma1 = LC(a1, x) # leading coeff of a1
+ p0 = d0 - d1 # degree difference
+ if p0 % 2 == 1:
+ s += 1
+ phi = floor( (s + 1) / 2 )
+ mul_fac = 1
+ d2 = d1
+
+ # main loop
+ while d2 > 0:
+ i += 1
+ a2 = rem(a0, a1, domain= QQ) # new remainder
+ if i == 1:
+ sigma2 = LC(a2, x)
+ else:
+ sigma3 = LC(a2, x)
+ sigma1, sigma2 = sigma2, sigma3
+ d2 = degree(a2, x)
+ p1 = d1 - d2
+ psi = i + phi + p_odd_index_sum
+
+ # new mul_fac
+ mul_fac = sigma1**(p0 + 1) * mul_fac
+
+ ## compute the sign of the first fraction in formula (9) of the paper
+ # numerator
+ num = (-1)**psi
+ # denominator
+ den = sign(mul_fac)
+
+ # the sign of the determinant depends on sign( num / den ) != 0
+ if sign(num / den) > 0:
+ subres_l.append( simplify(expand(a2* Abs(mul_fac))))
+ else:
+ subres_l.append(- simplify(expand(a2* Abs(mul_fac))))
+
+ ## bring into mul_fac the missing power of sigma if there was a degree gap
+ if p1 - 1 > 0:
+ mul_fac = mul_fac * sigma1**(p1 - 1)
+
+ # update AMV variables
+ a0, a1, d0, d1 = a1, a2, d1, d2
+ p0 = p1
+ if p0 % 2 ==1:
+ s += 1
+ phi = floor( (s + 1) / 2 )
+ if i%2 == 1:
+ p_odd_index_sum += p0 # p_i has odd index
+
+ # gcd is of degree > 0 ?
+ m = len(subres_l)
+ if subres_l[m - 1] == nan or subres_l[m - 1] == 0:
+ subres_l.pop(m - 1)
+
+ return subres_l
+
+def compute_sign(base, expo):
+ '''
+ base != 0 and expo >= 0 are integers;
+
+ returns the sign of base**expo without
+ evaluating the power itself!
+ '''
+ sb = sign(base)
+ if sb == 1:
+ return 1
+ pe = expo % 2
+ if pe == 0:
+ return -sb
+ else:
+ return sb
+
+def rem_z(p, q, x):
+ '''
+ Intended mainly for p, q polynomials in Z[x] so that,
+ on dividing p by q, the remainder will also be in Z[x]. (However,
+ it also works fine for polynomials in Q[x].) It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ It premultiplies p by the _absolute_ value of the leading coefficient
+ of q, raised to the power deg(p) - deg(q) + 1 and then performs
+ polynomial division in Q[x], using the function rem(p, q, x).
+
+ By contrast the function prem(p, q, x) does _not_ use the absolute
+ value of the leading coefficient of q.
+ This results not only in ``messing up the signs'' of the Euclidean and
+ Sturmian prs's as mentioned in the second reference,
+ but also in violation of the main results of the first and third
+ references --- Theorem 4 and Theorem 1 respectively. Theorems 4 and 1
+ establish a one-to-one correspondence between the Euclidean and the
+ Sturmian prs of p, q, on one hand, and the subresultant prs of p, q,
+ on the other.
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders
+ Obtained in Finding the Greatest Common Divisor of Two Polynomials.''
+ Serdica Journal of Computing, 9(2) (2015), 123-138.
+
+ 2. https://planetMath.org/sturmstheorem
+
+ 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result on
+ the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
+
+ '''
+ if (p.as_poly().is_univariate and q.as_poly().is_univariate and
+ p.as_poly().gens == q.as_poly().gens):
+ delta = (degree(p, x) - degree(q, x) + 1)
+ return rem(Abs(LC(q, x))**delta * p, q, x)
+ else:
+ return prem(p, q, x)
+
+def quo_z(p, q, x):
+ """
+ Intended mainly for p, q polynomials in Z[x] so that,
+ on dividing p by q, the quotient will also be in Z[x]. (However,
+ it also works fine for polynomials in Q[x].) It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ It premultiplies p by the _absolute_ value of the leading coefficient
+ of q, raised to the power deg(p) - deg(q) + 1 and then performs
+ polynomial division in Q[x], using the function quo(p, q, x).
+
+ By contrast the function pquo(p, q, x) does _not_ use the absolute
+ value of the leading coefficient of q.
+
+ See also function rem_z(p, q, x) for additional comments and references.
+
+ """
+ if (p.as_poly().is_univariate and q.as_poly().is_univariate and
+ p.as_poly().gens == q.as_poly().gens):
+ delta = (degree(p, x) - degree(q, x) + 1)
+ return quo(Abs(LC(q, x))**delta * p, q, x)
+ else:
+ return pquo(p, q, x)
+
+def subresultants_amv(f, g, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(f, x) >= degree(g, x).
+
+ Computes the subresultant prs of p and q in Z[x] or Q[x];
+ the coefficients of the polynomials in the sequence are
+ subresultants. That is, they are determinants of appropriately
+ selected submatrices of sylvester1, Sylvester's matrix of 1840.
+
+ To compute the coefficients, no determinant evaluation takes place.
+ Instead, polynomial divisions in Z[x] or Q[x] are performed, using
+ the function rem_z(p, q, x); the coefficients of the remainders
+ computed this way become subresultants with the help of the
+ Akritas-Malaschonok-Vigklas Theorem of 2015 and the Collins-Brown-
+ Traub formula for coefficient reduction.
+
+ If the subresultant prs is complete, then it coincides with the
+ Euclidean sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
+ on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
+ remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
+ Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
+
+ """
+ # make sure neither f nor g is 0
+ if f == 0 or g == 0:
+ return [f, g]
+
+ # make sure proper degrees
+ d0 = degree(f, x)
+ d1 = degree(g, x)
+ if d0 == 0 and d1 == 0:
+ return [f, g]
+ if d1 > d0:
+ d0, d1 = d1, d0
+ f, g = g, f
+ if d0 > 0 and d1 == 0:
+ return [f, g]
+
+ # initialize
+ a0 = f
+ a1 = g
+ subres_l = [a0, a1]
+ deg_dif_p1, c = degree(a0, x) - degree(a1, x) + 1, -1
+
+ # initialize AMV variables
+ sigma1 = LC(a1, x) # leading coeff of a1
+ i, s = 0, 0 # counters for remainders & odd elements
+ p_odd_index_sum = 0 # contains the sum of p_1, p_3, etc
+ p0 = deg_dif_p1 - 1
+ if p0 % 2 == 1:
+ s += 1
+ phi = floor( (s + 1) / 2 )
+
+ # compute the first polynomial of the prs
+ i += 1
+ a2 = rem_z(a0, a1, x) / Abs( (-1)**deg_dif_p1 ) # first remainder
+ sigma2 = LC(a2, x) # leading coeff of a2
+ d2 = degree(a2, x) # actual degree of a2
+ p1 = d1 - d2 # degree difference
+
+ # sgn_den is the factor, the denominator 1st fraction of (9),
+ # by which a2 is multiplied to get integer coefficients
+ sgn_den = compute_sign( sigma1, p0 + 1 )
+
+ ## compute sign of the 1st fraction in formula (9) of the paper
+ # numerator
+ psi = i + phi + p_odd_index_sum
+ num = (-1)**psi
+ # denominator
+ den = sgn_den
+
+ # the sign of the determinant depends on sign(num / den) != 0
+ if sign(num / den) > 0:
+ subres_l.append( a2 )
+ else:
+ subres_l.append( -a2 )
+
+ # update AMV variable
+ if p1 % 2 == 1:
+ s += 1
+
+ # bring in the missing power of sigma if there was gap
+ if p1 - 1 > 0:
+ sgn_den = sgn_den * compute_sign( sigma1, p1 - 1 )
+
+ # main loop
+ while d2 >= 1:
+ phi = floor( (s + 1) / 2 )
+ if i%2 == 1:
+ p_odd_index_sum += p1 # p_i has odd index
+ a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
+ p0 = p1 # update degree difference
+ i += 1
+ sigma0 = -LC(a0)
+ c = (sigma0**(deg_dif_p1 - 1)) / (c**(deg_dif_p1 - 2))
+ deg_dif_p1 = degree(a0, x) - d2 + 1
+ a2 = rem_z(a0, a1, x) / Abs( (c**(deg_dif_p1 - 1)) * sigma0 )
+ sigma3 = LC(a2, x) # leading coeff of a2
+ d2 = degree(a2, x) # actual degree of a2
+ p1 = d1 - d2 # degree difference
+ psi = i + phi + p_odd_index_sum
+
+ # update variables
+ sigma1, sigma2 = sigma2, sigma3
+
+ # new sgn_den
+ sgn_den = compute_sign( sigma1, p0 + 1 ) * sgn_den
+
+ # compute the sign of the first fraction in formula (9) of the paper
+ # numerator
+ num = (-1)**psi
+ # denominator
+ den = sgn_den
+
+ # the sign of the determinant depends on sign( num / den ) != 0
+ if sign(num / den) > 0:
+ subres_l.append( a2 )
+ else:
+ subres_l.append( -a2 )
+
+ # update AMV variable
+ if p1 % 2 ==1:
+ s += 1
+
+ # bring in the missing power of sigma if there was gap
+ if p1 - 1 > 0:
+ sgn_den = sgn_den * compute_sign( sigma1, p1 - 1 )
+
+ # gcd is of degree > 0 ?
+ m = len(subres_l)
+ if subres_l[m - 1] == nan or subres_l[m - 1] == 0:
+ subres_l.pop(m - 1)
+
+ return subres_l
+
+def modified_subresultants_amv(p, q, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the modified subresultant prs of p and q in Z[x] or Q[x],
+ from the subresultant prs of p and q.
+ The coefficients of the polynomials in the two sequences differ only
+ in sign and the factor LC(p)**( deg(p)- deg(q)) as stated in
+ Theorem 2 of the reference.
+
+ The coefficients of the polynomials in the output sequence are
+ modified subresultants. That is, they are determinants of appropriately
+ selected submatrices of sylvester2, Sylvester's matrix of 1853.
+
+ If the modified subresultant prs is complete, and LC( p ) > 0, it coincides
+ with the (generalized) Sturm's sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: "On the Remainders
+ Obtained in Finding the Greatest Common Divisor of Two Polynomials."
+ Serdica Journal of Computing, Serdica Journal of Computing, 9(2) (2015), 123-138.
+
+ """
+ # compute the subresultant prs
+ lst = subresultants_amv(p,q,x) ## any other method would do
+
+ # defensive
+ if lst == [] or len(lst) == 2:
+ return lst
+
+ # the coefficients in lst are subresultants and, hence, smaller than those
+ # of the corresponding modified subresultants by the factor
+ # LC(lst[0])**( deg(lst[0]) - deg(lst[1])); see Theorem 2.
+ lcf = LC(lst[0])**( degree(lst[0], x) - degree(lst[1], x) )
+
+ # Initialize the modified subresultant prs list
+ subr_seq = [lst[0], lst[1]]
+
+ # compute the degree sequences m_i and j_i of Theorem 2
+ deg_seq = [degree(Poly(poly, x), x) for poly in lst]
+ deg = deg_seq[0]
+ deg_seq_s = deg_seq[1:-1]
+ m_seq = [m-1 for m in deg_seq_s]
+ j_seq = [deg - m for m in m_seq]
+
+ # compute the AMV factors of Theorem 2
+ fact = [(-1)**( j*(j-1)/S(2) ) for j in j_seq]
+
+ # shortened list without the first two polys
+ lst_s = lst[2:]
+
+ # poly lst_s[k] is multiplied times fact[k] and times lcf
+ # and appended to the subresultant prs list
+ m = len(fact)
+ for k in range(m):
+ if sign(fact[k]) == -1:
+ subr_seq.append( simplify(-lst_s[k] * lcf) )
+ else:
+ subr_seq.append( simplify(lst_s[k] * lcf) )
+
+ return subr_seq
+
+def correct_sign(deg_f, deg_g, s1, rdel, cdel):
+ """
+ Used in various subresultant prs algorithms.
+
+ Evaluates the determinant, (a.k.a. subresultant) of a properly selected
+ submatrix of s1, Sylvester's matrix of 1840, to get the correct sign
+ and value of the leading coefficient of a given polynomial remainder.
+
+ deg_f, deg_g are the degrees of the original polynomials p, q for which the
+ matrix s1 = sylvester(p, q, x, 1) was constructed.
+
+ rdel denotes the expected degree of the remainder; it is the number of
+ rows to be deleted from each group of rows in s1 as described in the
+ reference below.
+
+ cdel denotes the expected degree minus the actual degree of the remainder;
+ it is the number of columns to be deleted --- starting with the last column
+ forming the square matrix --- from the matrix resulting after the row deletions.
+
+ References
+ ==========
+ Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
+ and Modified Subresultant Polynomial Remainder Sequences.''
+ Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
+
+ """
+ M = s1[:, :] # copy of matrix s1
+
+ # eliminate rdel rows from the first deg_g rows
+ for i in range(M.rows - deg_f - 1, M.rows - deg_f - rdel - 1, -1):
+ M.row_del(i)
+
+ # eliminate rdel rows from the last deg_f rows
+ for i in range(M.rows - 1, M.rows - rdel - 1, -1):
+ M.row_del(i)
+
+ # eliminate cdel columns
+ for i in range(cdel):
+ M.col_del(M.rows - 1)
+
+ # define submatrix
+ Md = M[:, 0: M.rows]
+
+ return Md.det()
+
+def subresultants_rem(p, q, x):
+ """
+ p, q are polynomials in Z[x] or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the subresultant prs of p and q in Z[x] or Q[x];
+ the coefficients of the polynomials in the sequence are
+ subresultants. That is, they are determinants of appropriately
+ selected submatrices of sylvester1, Sylvester's matrix of 1840.
+
+ To compute the coefficients polynomial divisions in Q[x] are
+ performed, using the function rem(p, q, x). The coefficients
+ of the remainders computed this way become subresultants by evaluating
+ one subresultant per remainder --- that of the leading coefficient.
+ This way we obtain the correct sign and value of the leading coefficient
+ of the remainder and we easily ``force'' the rest of the coefficients
+ to become subresultants.
+
+ If the subresultant prs is complete, then it coincides with the
+ Euclidean sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. Akritas, A. G.:``Three New Methods for Computing Subresultant
+ Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ # make sure proper degrees
+ f, g = p, q
+ n = deg_f = degree(f, x)
+ m = deg_g = degree(g, x)
+ if n == 0 and m == 0:
+ return [f, g]
+ if n < m:
+ n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f
+ if n > 0 and m == 0:
+ return [f, g]
+
+ # initialize
+ s1 = sylvester(f, g, x, 1)
+ sr_list = [f, g] # subresultant list
+
+ # main loop
+ while deg_g > 0:
+ r = rem(p, q, x)
+ d = degree(r, x)
+ if d < 0:
+ return sr_list
+
+ # make coefficients subresultants evaluating ONE determinant
+ exp_deg = deg_g - 1 # expected degree
+ sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d)
+ r = simplify((r / LC(r, x)) * sign_value)
+
+ # append poly with subresultant coeffs
+ sr_list.append(r)
+
+ # update degrees and polys
+ deg_f, deg_g = deg_g, d
+ p, q = q, r
+
+ # gcd is of degree > 0 ?
+ m = len(sr_list)
+ if sr_list[m - 1] == nan or sr_list[m - 1] == 0:
+ sr_list.pop(m - 1)
+
+ return sr_list
+
+def pivot(M, i, j):
+ '''
+ M is a matrix, and M[i, j] specifies the pivot element.
+
+ All elements below M[i, j], in the j-th column, will
+ be zeroed, if they are not already 0, according to
+ Dodgson-Bareiss' integer preserving transformations.
+
+ References
+ ==========
+ 1. Akritas, A. G.: ``A new method for computing polynomial greatest
+ common divisors and polynomial remainder sequences.''
+ Numerische MatheMatik 52, 119-127, 1988.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem
+ by Van Vleck Regarding Sturm Sequences.''
+ Serdica Journal of Computing, 7, No 4, 101-134, 2013.
+
+ '''
+ ma = M[:, :] # copy of matrix M
+ rs = ma.rows # No. of rows
+ cs = ma.cols # No. of cols
+ for r in range(i+1, rs):
+ if ma[r, j] != 0:
+ for c in range(j + 1, cs):
+ ma[r, c] = ma[i, j] * ma[r, c] - ma[i, c] * ma[r, j]
+ ma[r, j] = 0
+ return ma
+
+def rotate_r(L, k):
+ '''
+ Rotates right by k. L is a row of a matrix or a list.
+
+ '''
+ ll = list(L)
+ if ll == []:
+ return []
+ for i in range(k):
+ el = ll.pop(len(ll) - 1)
+ ll.insert(0, el)
+ return ll if isinstance(L, list) else Matrix([ll])
+
+def rotate_l(L, k):
+ '''
+ Rotates left by k. L is a row of a matrix or a list.
+
+ '''
+ ll = list(L)
+ if ll == []:
+ return []
+ for i in range(k):
+ el = ll.pop(0)
+ ll.insert(len(ll) - 1, el)
+ return ll if isinstance(L, list) else Matrix([ll])
+
+def row2poly(row, deg, x):
+ '''
+ Converts the row of a matrix to a poly of degree deg and variable x.
+ Some entries at the beginning and/or at the end of the row may be zero.
+
+ '''
+ k = 0
+ poly = []
+ leng = len(row)
+
+ # find the beginning of the poly ; i.e. the first
+ # non-zero element of the row
+ while row[k] == 0:
+ k = k + 1
+
+ # append the next deg + 1 elements to poly
+ for j in range( deg + 1):
+ if k + j <= leng:
+ poly.append(row[k + j])
+
+ return Poly(poly, x)
+
+def create_ma(deg_f, deg_g, row1, row2, col_num):
+ '''
+ Creates a ``small'' matrix M to be triangularized.
+
+ deg_f, deg_g are the degrees of the divident and of the
+ divisor polynomials respectively, deg_g > deg_f.
+
+ The coefficients of the divident poly are the elements
+ in row2 and those of the divisor poly are the elements
+ in row1.
+
+ col_num defines the number of columns of the matrix M.
+
+ '''
+ if deg_g - deg_f >= 1:
+ print('Reverse degrees')
+ return
+
+ m = zeros(deg_f - deg_g + 2, col_num)
+
+ for i in range(deg_f - deg_g + 1):
+ m[i, :] = rotate_r(row1, i)
+ m[deg_f - deg_g + 1, :] = row2
+
+ return m
+
+def find_degree(M, deg_f):
+ '''
+ Finds the degree of the poly corresponding (after triangularization)
+ to the _last_ row of the ``small'' matrix M, created by create_ma().
+
+ deg_f is the degree of the divident poly.
+ If _last_ row is all 0's returns None.
+
+ '''
+ j = deg_f
+ for i in range(0, M.cols):
+ if M[M.rows - 1, i] == 0:
+ j = j - 1
+ else:
+ return j if j >= 0 else 0
+
+def final_touches(s2, r, deg_g):
+ """
+ s2 is sylvester2, r is the row pointer in s2,
+ deg_g is the degree of the poly last inserted in s2.
+
+ After a gcd of degree > 0 has been found with Van Vleck's
+ method, and was inserted into s2, if its last term is not
+ in the last column of s2, then it is inserted as many
+ times as needed, rotated right by one each time, until
+ the condition is met.
+
+ """
+ R = s2.row(r-1)
+
+ # find the first non zero term
+ for i in range(s2.cols):
+ if R[0,i] == 0:
+ continue
+ else:
+ break
+
+ # missing rows until last term is in last column
+ mr = s2.cols - (i + deg_g + 1)
+
+ # insert them by replacing the existing entries in the row
+ i = 0
+ while mr != 0 and r + i < s2.rows :
+ s2[r + i, : ] = rotate_r(R, i + 1)
+ i += 1
+ mr -= 1
+
+ return s2
+
+def subresultants_vv(p, q, x, method = 0):
+ """
+ p, q are polynomials in Z[x] (intended) or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the subresultant prs of p, q by triangularizing,
+ in Z[x] or in Q[x], all the smaller matrices encountered in the
+ process of triangularizing sylvester2, Sylvester's matrix of 1853;
+ see references 1 and 2 for Van Vleck's method. With each remainder,
+ sylvester2 gets updated and is prepared to be printed if requested.
+
+ If sylvester2 has small dimensions and you want to see the final,
+ triangularized matrix use this version with method=1; otherwise,
+ use either this version with method=0 (default) or the faster version,
+ subresultants_vv_2(p, q, x), where sylvester2 is used implicitly.
+
+ Sylvester's matrix sylvester1 is also used to compute one
+ subresultant per remainder; namely, that of the leading
+ coefficient, in order to obtain the correct sign and to
+ force the remainder coefficients to become subresultants.
+
+ If the subresultant prs is complete, then it coincides with the
+ Euclidean sequence of the polynomials p, q.
+
+ If the final, triangularized matrix s2 is printed, then:
+ (a) if deg(p) - deg(q) > 1 or deg( gcd(p, q) ) > 0, several
+ of the last rows in s2 will remain unprocessed;
+ (b) if deg(p) - deg(q) == 0, p will not appear in the final matrix.
+
+ References
+ ==========
+ 1. Akritas, A. G.: ``A new method for computing polynomial greatest
+ common divisors and polynomial remainder sequences.''
+ Numerische MatheMatik 52, 119-127, 1988.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem
+ by Van Vleck Regarding Sturm Sequences.''
+ Serdica Journal of Computing, 7, No 4, 101-134, 2013.
+
+ 3. Akritas, A. G.:``Three New Methods for Computing Subresultant
+ Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ # make sure proper degrees
+ f, g = p, q
+ n = deg_f = degree(f, x)
+ m = deg_g = degree(g, x)
+ if n == 0 and m == 0:
+ return [f, g]
+ if n < m:
+ n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f
+ if n > 0 and m == 0:
+ return [f, g]
+
+ # initialize
+ s1 = sylvester(f, g, x, 1)
+ s2 = sylvester(f, g, x, 2)
+ sr_list = [f, g]
+ col_num = 2 * n # columns in s2
+
+ # make two rows (row0, row1) of poly coefficients
+ row0 = Poly(f, x, domain = QQ).all_coeffs()
+ leng0 = len(row0)
+ for i in range(col_num - leng0):
+ row0.append(0)
+ row0 = Matrix([row0])
+ row1 = Poly(g,x, domain = QQ).all_coeffs()
+ leng1 = len(row1)
+ for i in range(col_num - leng1):
+ row1.append(0)
+ row1 = Matrix([row1])
+
+ # row pointer for deg_f - deg_g == 1; may be reset below
+ r = 2
+
+ # modify first rows of s2 matrix depending on poly degrees
+ if deg_f - deg_g > 1:
+ r = 1
+ # replacing the existing entries in the rows of s2,
+ # insert row0 (deg_f - deg_g - 1) times, rotated each time
+ for i in range(deg_f - deg_g - 1):
+ s2[r + i, : ] = rotate_r(row0, i + 1)
+ r = r + deg_f - deg_g - 1
+ # insert row1 (deg_f - deg_g) times, rotated each time
+ for i in range(deg_f - deg_g):
+ s2[r + i, : ] = rotate_r(row1, r + i)
+ r = r + deg_f - deg_g
+
+ if deg_f - deg_g == 0:
+ r = 0
+
+ # main loop
+ while deg_g > 0:
+ # create a small matrix M, and triangularize it;
+ M = create_ma(deg_f, deg_g, row1, row0, col_num)
+ # will need only the first and last rows of M
+ for i in range(deg_f - deg_g + 1):
+ M1 = pivot(M, i, i)
+ M = M1[:, :]
+
+ # treat last row of M as poly; find its degree
+ d = find_degree(M, deg_f)
+ if d is None:
+ break
+ exp_deg = deg_g - 1
+
+ # evaluate one determinant & make coefficients subresultants
+ sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d)
+ poly = row2poly(M[M.rows - 1, :], d, x)
+ temp2 = LC(poly, x)
+ poly = simplify((poly / temp2) * sign_value)
+
+ # update s2 by inserting first row of M as needed
+ row0 = M[0, :]
+ for i in range(deg_g - d):
+ s2[r + i, :] = rotate_r(row0, r + i)
+ r = r + deg_g - d
+
+ # update s2 by inserting last row of M as needed
+ row1 = rotate_l(M[M.rows - 1, :], deg_f - d)
+ row1 = (row1 / temp2) * sign_value
+ for i in range(deg_g - d):
+ s2[r + i, :] = rotate_r(row1, r + i)
+ r = r + deg_g - d
+
+ # update degrees
+ deg_f, deg_g = deg_g, d
+
+ # append poly with subresultant coeffs
+ sr_list.append(poly)
+
+ # final touches to print the s2 matrix
+ if method != 0 and s2.rows > 2:
+ s2 = final_touches(s2, r, deg_g)
+ pprint(s2)
+ elif method != 0 and s2.rows == 2:
+ s2[1, :] = rotate_r(s2.row(1), 1)
+ pprint(s2)
+
+ return sr_list
+
+def subresultants_vv_2(p, q, x):
+ """
+ p, q are polynomials in Z[x] (intended) or Q[x]. It is assumed
+ that degree(p, x) >= degree(q, x).
+
+ Computes the subresultant prs of p, q by triangularizing,
+ in Z[x] or in Q[x], all the smaller matrices encountered in the
+ process of triangularizing sylvester2, Sylvester's matrix of 1853;
+ see references 1 and 2 for Van Vleck's method.
+
+ If the sylvester2 matrix has big dimensions use this version,
+ where sylvester2 is used implicitly. If you want to see the final,
+ triangularized matrix sylvester2, then use the first version,
+ subresultants_vv(p, q, x, 1).
+
+ sylvester1, Sylvester's matrix of 1840, is also used to compute
+ one subresultant per remainder; namely, that of the leading
+ coefficient, in order to obtain the correct sign and to
+ ``force'' the remainder coefficients to become subresultants.
+
+ If the subresultant prs is complete, then it coincides with the
+ Euclidean sequence of the polynomials p, q.
+
+ References
+ ==========
+ 1. Akritas, A. G.: ``A new method for computing polynomial greatest
+ common divisors and polynomial remainder sequences.''
+ Numerische MatheMatik 52, 119-127, 1988.
+
+ 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem
+ by Van Vleck Regarding Sturm Sequences.''
+ Serdica Journal of Computing, 7, No 4, 101-134, 2013.
+
+ 3. Akritas, A. G.:``Three New Methods for Computing Subresultant
+ Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26.
+
+ """
+ # make sure neither p nor q is 0
+ if p == 0 or q == 0:
+ return [p, q]
+
+ # make sure proper degrees
+ f, g = p, q
+ n = deg_f = degree(f, x)
+ m = deg_g = degree(g, x)
+ if n == 0 and m == 0:
+ return [f, g]
+ if n < m:
+ n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f
+ if n > 0 and m == 0:
+ return [f, g]
+
+ # initialize
+ s1 = sylvester(f, g, x, 1)
+ sr_list = [f, g] # subresultant list
+ col_num = 2 * n # columns in sylvester2
+
+ # make two rows (row0, row1) of poly coefficients
+ row0 = Poly(f, x, domain = QQ).all_coeffs()
+ leng0 = len(row0)
+ for i in range(col_num - leng0):
+ row0.append(0)
+ row0 = Matrix([row0])
+ row1 = Poly(g,x, domain = QQ).all_coeffs()
+ leng1 = len(row1)
+ for i in range(col_num - leng1):
+ row1.append(0)
+ row1 = Matrix([row1])
+
+ # main loop
+ while deg_g > 0:
+ # create a small matrix M, and triangularize it
+ M = create_ma(deg_f, deg_g, row1, row0, col_num)
+ for i in range(deg_f - deg_g + 1):
+ M1 = pivot(M, i, i)
+ M = M1[:, :]
+
+ # treat last row of M as poly; find its degree
+ d = find_degree(M, deg_f)
+ if d is None:
+ return sr_list
+ exp_deg = deg_g - 1
+
+ # evaluate one determinant & make coefficients subresultants
+ sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d)
+ poly = row2poly(M[M.rows - 1, :], d, x)
+ poly = simplify((poly / LC(poly, x)) * sign_value)
+
+ # append poly with subresultant coeffs
+ sr_list.append(poly)
+
+ # update degrees and rows
+ deg_f, deg_g = deg_g, d
+ row0 = row1
+ row1 = Poly(poly, x, domain = QQ).all_coeffs()
+ leng1 = len(row1)
+ for i in range(col_num - leng1):
+ row1.append(0)
+ row1 = Matrix([row1])
+
+ return sr_list
diff --git a/lib/python3.12/site-packages/sympy/sandbox/__init__.py b/lib/python3.12/site-packages/sympy/sandbox/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a84b7517819bb2fc9886274e09d955a74cabca1
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/sandbox/__init__.py
@@ -0,0 +1,8 @@
+"""
+Sandbox module of SymPy.
+
+This module contains experimental code, use at your own risk!
+
+There is no warranty that this code will still be located here in future
+versions of SymPy.
+"""
diff --git a/lib/python3.12/site-packages/sympy/sandbox/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/sandbox/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1c059add0c243caedfee5417044e926f649a9535
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/sandbox/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/sandbox/__pycache__/indexed_integrals.cpython-312.pyc b/lib/python3.12/site-packages/sympy/sandbox/__pycache__/indexed_integrals.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e0a7cf427342daa807250080ad9c3ffb807e4e8a
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/sandbox/__pycache__/indexed_integrals.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/sandbox/indexed_integrals.py b/lib/python3.12/site-packages/sympy/sandbox/indexed_integrals.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0c17d141448b5a71cb814bff76a710a5bd43f88
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/sandbox/indexed_integrals.py
@@ -0,0 +1,72 @@
+from sympy.tensor import Indexed
+from sympy.core.containers import Tuple
+from sympy.core.symbol import Dummy
+from sympy.core.sympify import sympify
+from sympy.integrals.integrals import Integral
+
+
+class IndexedIntegral(Integral):
+ """
+ Experimental class to test integration by indexed variables.
+
+ Usage is analogue to ``Integral``, it simply adds awareness of
+ integration over indices.
+
+ Contraction of non-identical index symbols referring to the same
+ ``IndexedBase`` is not yet supported.
+
+ Examples
+ ========
+
+ >>> from sympy.sandbox.indexed_integrals import IndexedIntegral
+ >>> from sympy import IndexedBase, symbols
+ >>> A = IndexedBase('A')
+ >>> i, j = symbols('i j', integer=True)
+ >>> ii = IndexedIntegral(A[i], A[i])
+ >>> ii
+ Integral(_A[i], _A[i])
+ >>> ii.doit()
+ A[i]**2/2
+
+ If the indices are different, indexed objects are considered to be
+ different variables:
+
+ >>> i2 = IndexedIntegral(A[j], A[i])
+ >>> i2
+ Integral(A[j], _A[i])
+ >>> i2.doit()
+ A[i]*A[j]
+ """
+
+ def __new__(cls, function, *limits, **assumptions):
+ repl, limits = IndexedIntegral._indexed_process_limits(limits)
+ function = sympify(function)
+ function = function.xreplace(repl)
+ obj = Integral.__new__(cls, function, *limits, **assumptions)
+ obj._indexed_repl = repl
+ obj._indexed_reverse_repl = {val: key for key, val in repl.items()}
+ return obj
+
+ def doit(self):
+ res = super().doit()
+ return res.xreplace(self._indexed_reverse_repl)
+
+ @staticmethod
+ def _indexed_process_limits(limits):
+ repl = {}
+ newlimits = []
+ for i in limits:
+ if isinstance(i, (tuple, list, Tuple)):
+ v = i[0]
+ vrest = i[1:]
+ else:
+ v = i
+ vrest = ()
+ if isinstance(v, Indexed):
+ if v not in repl:
+ r = Dummy(str(v))
+ repl[v] = r
+ newlimits.append((r,)+vrest)
+ else:
+ newlimits.append(i)
+ return repl, newlimits
diff --git a/lib/python3.12/site-packages/sympy/sandbox/tests/__init__.py b/lib/python3.12/site-packages/sympy/sandbox/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/sympy/sandbox/tests/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/sandbox/tests/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ec1f5355d210f7fd570e564fd09cccd8fa470979
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/sandbox/tests/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/sandbox/tests/__pycache__/test_indexed_integrals.cpython-312.pyc b/lib/python3.12/site-packages/sympy/sandbox/tests/__pycache__/test_indexed_integrals.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..87be1a1ee847457ea2fd26f2e778c944733157b4
Binary files /dev/null and b/lib/python3.12/site-packages/sympy/sandbox/tests/__pycache__/test_indexed_integrals.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/sympy/sandbox/tests/test_indexed_integrals.py b/lib/python3.12/site-packages/sympy/sandbox/tests/test_indexed_integrals.py
new file mode 100644
index 0000000000000000000000000000000000000000..61b98f0ffec29e026f6dfe8e16fde8b5818b0b09
--- /dev/null
+++ b/lib/python3.12/site-packages/sympy/sandbox/tests/test_indexed_integrals.py
@@ -0,0 +1,25 @@
+from sympy.sandbox.indexed_integrals import IndexedIntegral
+from sympy.core.symbol import symbols
+from sympy.functions.elementary.trigonometric import (cos, sin)
+from sympy.tensor.indexed import (Idx, IndexedBase)
+
+
+def test_indexed_integrals():
+ A = IndexedBase('A')
+ i, j = symbols('i j', integer=True)
+ a1, a2 = symbols('a1:3', cls=Idx)
+ assert isinstance(a1, Idx)
+
+ assert IndexedIntegral(1, A[i]).doit() == A[i]
+ assert IndexedIntegral(A[i], A[i]).doit() == A[i] ** 2 / 2
+ assert IndexedIntegral(A[j], A[i]).doit() == A[i] * A[j]
+ assert IndexedIntegral(A[i] * A[j], A[i]).doit() == A[i] ** 2 * A[j] / 2
+ assert IndexedIntegral(sin(A[i]), A[i]).doit() == -cos(A[i])
+ assert IndexedIntegral(sin(A[j]), A[i]).doit() == sin(A[j]) * A[i]
+
+ assert IndexedIntegral(1, A[a1]).doit() == A[a1]
+ assert IndexedIntegral(A[a1], A[a1]).doit() == A[a1] ** 2 / 2
+ assert IndexedIntegral(A[a2], A[a1]).doit() == A[a1] * A[a2]
+ assert IndexedIntegral(A[a1] * A[a2], A[a1]).doit() == A[a1] ** 2 * A[a2] / 2
+ assert IndexedIntegral(sin(A[a1]), A[a1]).doit() == -cos(A[a1])
+ assert IndexedIntegral(sin(A[a2]), A[a1]).doit() == sin(A[a2]) * A[a1]