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]