diff --git a/.gitattributes b/.gitattributes index 4b01532bbfe13cbbc43a064a6413fd033e34bfd1..1943cbaac98cbd9242194c83ee511d0b167494a7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -263,3 +263,4 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/torch/_inductor/_ .venv/lib/python3.11/site-packages/setuptools/gui-arm64.exe filter=lfs diff=lfs merge=lfs -text .venv/lib/python3.11/site-packages/numpy.libs/libquadmath-96973f99.so.0.0.0 filter=lfs diff=lfs merge=lfs -text .venv/lib/python3.11/site-packages/numpy.libs/libgfortran-040039e1.so.5.0.0 filter=lfs diff=lfs merge=lfs -text +tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/stats/__pycache__/stochastic_process_types.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/matrices.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/matrices.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02e08018a69f6753ebf5328d825891cca6ec5c72 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/matrices.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/paulialgebra.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/paulialgebra.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ff94db8bb0eb6cc63822b9bd62a4e6b80de711f Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/paulialgebra.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/sho.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/sho.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c71118da0a3454d6f9ef158ae83c9068072b030 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/__pycache__/sho.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/curve.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/curve.py new file mode 100644 index 0000000000000000000000000000000000000000..6474dc1517cc34876da833cac524e8b148ab90cc --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/curve.py @@ -0,0 +1,1763 @@ +"""Implementations of characteristic curves for musculotendon models.""" + +from dataclasses import dataclass + +from sympy.core.expr import UnevaluatedExpr +from sympy.core.function import ArgumentIndexError, Function +from sympy.core.numbers import Float, Integer +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.hyperbolic import cosh, sinh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.printing.precedence import PRECEDENCE + + +__all__ = [ + 'CharacteristicCurveCollection', + 'CharacteristicCurveFunction', + 'FiberForceLengthActiveDeGroote2016', + 'FiberForceLengthPassiveDeGroote2016', + 'FiberForceLengthPassiveInverseDeGroote2016', + 'FiberForceVelocityDeGroote2016', + 'FiberForceVelocityInverseDeGroote2016', + 'TendonForceLengthDeGroote2016', + 'TendonForceLengthInverseDeGroote2016', +] + + +class CharacteristicCurveFunction(Function): + """Base class for all musculotendon characteristic curve functions.""" + + @classmethod + def eval(cls): + msg = ( + f'Cannot directly instantiate {cls.__name__!r}, instances of ' + f'characteristic curves must be of a concrete subclass.' + + ) + raise TypeError(msg) + + def _print_code(self, printer): + """Print code for the function defining the curve using a printer. + + Explanation + =========== + + The order of operations may need to be controlled as constant folding + the numeric terms within the equations of a musculotendon + characteristic curve can sometimes results in a numerically-unstable + expression. + + Parameters + ========== + + printer : Printer + The printer to be used to print a string representation of the + characteristic curve as valid code in the target language. + + """ + return printer._print(printer.parenthesize( + self.doit(deep=False, evaluate=False), PRECEDENCE['Atom'], + )) + + _ccode = _print_code + _cupycode = _print_code + _cxxcode = _print_code + _fcode = _print_code + _jaxcode = _print_code + _lambdacode = _print_code + _mpmathcode = _print_code + _octave = _print_code + _pythoncode = _print_code + _numpycode = _print_code + _scipycode = _print_code + + +class TendonForceLengthDeGroote2016(CharacteristicCurveFunction): + r"""Tendon force-length curve based on De Groote et al., 2016 [1]_. + + Explanation + =========== + + Gives the normalized tendon force produced as a function of normalized + tendon length. + + The function is defined by the equation: + + $fl^T = c_0 \exp{c_3 \left( \tilde{l}^T - c_1 \right)} - c_2$ + + with constant values of $c_0 = 0.2$, $c_1 = 0.995$, $c_2 = 0.25$, and + $c_3 = 33.93669377311689$. + + While it is possible to change the constant values, these were carefully + selected in the original publication to give the characteristic curve + specific and required properties. For example, the function produces no + force when the tendon is in an unstrained state. It also produces a force + of 1 normalized unit when the tendon is under a 5% strain. + + Examples + ======== + + The preferred way to instantiate :class:`TendonForceLengthDeGroote2016` is using + the :meth:`~.with_defaults` constructor because this will automatically + populate the constants within the characteristic curve equation with the + floating point values from the original publication. This constructor takes + a single argument corresponding to normalized tendon length. We'll create a + :class:`~.Symbol` called ``l_T_tilde`` to represent this. + + >>> from sympy import Symbol + >>> from sympy.physics.biomechanics import TendonForceLengthDeGroote2016 + >>> l_T_tilde = Symbol('l_T_tilde') + >>> fl_T = TendonForceLengthDeGroote2016.with_defaults(l_T_tilde) + >>> fl_T + TendonForceLengthDeGroote2016(l_T_tilde, 0.2, 0.995, 0.25, + 33.93669377311689) + + It's also possible to populate the four constants with your own values too. + + >>> from sympy import symbols + >>> c0, c1, c2, c3 = symbols('c0 c1 c2 c3') + >>> fl_T = TendonForceLengthDeGroote2016(l_T_tilde, c0, c1, c2, c3) + >>> fl_T + TendonForceLengthDeGroote2016(l_T_tilde, c0, c1, c2, c3) + + You don't just have to use symbols as the arguments, it's also possible to + use expressions. Let's create a new pair of symbols, ``l_T`` and + ``l_T_slack``, representing tendon length and tendon slack length + respectively. We can then represent ``l_T_tilde`` as an expression, the + ratio of these. + + >>> l_T, l_T_slack = symbols('l_T l_T_slack') + >>> l_T_tilde = l_T/l_T_slack + >>> fl_T = TendonForceLengthDeGroote2016.with_defaults(l_T_tilde) + >>> fl_T + TendonForceLengthDeGroote2016(l_T/l_T_slack, 0.2, 0.995, 0.25, + 33.93669377311689) + + To inspect the actual symbolic expression that this function represents, + we can call the :meth:`~.doit` method on an instance. We'll use the keyword + argument ``evaluate=False`` as this will keep the expression in its + canonical form and won't simplify any constants. + + >>> fl_T.doit(evaluate=False) + -0.25 + 0.2*exp(33.93669377311689*(l_T/l_T_slack - 0.995)) + + The function can also be differentiated. We'll differentiate with respect + to l_T using the ``diff`` method on an instance with the single positional + argument ``l_T``. + + >>> fl_T.diff(l_T) + 6.787338754623378*exp(33.93669377311689*(l_T/l_T_slack - 0.995))/l_T_slack + + References + ========== + + .. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation + of direct collocation optimal control problem formulations for + solving the muscle redundancy problem, Annals of biomedical + engineering, 44(10), (2016) pp. 2922-2936 + + """ + + @classmethod + def with_defaults(cls, l_T_tilde): + r"""Recommended constructor that will use the published constants. + + Explanation + =========== + + Returns a new instance of the tendon force-length function using the + four constant values specified in the original publication. + + These have the values: + + $c_0 = 0.2$ + $c_1 = 0.995$ + $c_2 = 0.25$ + $c_3 = 33.93669377311689$ + + Parameters + ========== + + l_T_tilde : Any (sympifiable) + Normalized tendon length. + + """ + c0 = Float('0.2') + c1 = Float('0.995') + c2 = Float('0.25') + c3 = Float('33.93669377311689') + return cls(l_T_tilde, c0, c1, c2, c3) + + @classmethod + def eval(cls, l_T_tilde, c0, c1, c2, c3): + """Evaluation of basic inputs. + + Parameters + ========== + + l_T_tilde : Any (sympifiable) + Normalized tendon length. + c0 : Any (sympifiable) + The first constant in the characteristic equation. The published + value is ``0.2``. + c1 : Any (sympifiable) + The second constant in the characteristic equation. The published + value is ``0.995``. + c2 : Any (sympifiable) + The third constant in the characteristic equation. The published + value is ``0.25``. + c3 : Any (sympifiable) + The fourth constant in the characteristic equation. The published + value is ``33.93669377311689``. + + """ + pass + + def _eval_evalf(self, prec): + """Evaluate the expression numerically using ``evalf``.""" + return self.doit(deep=False, evaluate=False)._eval_evalf(prec) + + def doit(self, deep=True, evaluate=True, **hints): + """Evaluate the expression defining the function. + + Parameters + ========== + + deep : bool + Whether ``doit`` should be recursively called. Default is ``True``. + evaluate : bool. + Whether the SymPy expression should be evaluated as it is + constructed. If ``False``, then no constant folding will be + conducted which will leave the expression in a more numerically- + stable for values of ``l_T_tilde`` that correspond to a sensible + operating range for a musculotendon. Default is ``True``. + **kwargs : dict[str, Any] + Additional keyword argument pairs to be recursively passed to + ``doit``. + + """ + l_T_tilde, *constants = self.args + if deep: + hints['evaluate'] = evaluate + l_T_tilde = l_T_tilde.doit(deep=deep, **hints) + c0, c1, c2, c3 = [c.doit(deep=deep, **hints) for c in constants] + else: + c0, c1, c2, c3 = constants + + if evaluate: + return c0*exp(c3*(l_T_tilde - c1)) - c2 + + return c0*exp(c3*UnevaluatedExpr(l_T_tilde - c1)) - c2 + + def fdiff(self, argindex=1): + """Derivative of the function with respect to a single argument. + + Parameters + ========== + + argindex : int + The index of the function's arguments with respect to which the + derivative should be taken. Argument indexes start at ``1``. + Default is ``1``. + + """ + l_T_tilde, c0, c1, c2, c3 = self.args + if argindex == 1: + return c0*c3*exp(c3*UnevaluatedExpr(l_T_tilde - c1)) + elif argindex == 2: + return exp(c3*UnevaluatedExpr(l_T_tilde - c1)) + elif argindex == 3: + return -c0*c3*exp(c3*UnevaluatedExpr(l_T_tilde - c1)) + elif argindex == 4: + return Integer(-1) + elif argindex == 5: + return c0*(l_T_tilde - c1)*exp(c3*UnevaluatedExpr(l_T_tilde - c1)) + + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """Inverse function. + + Parameters + ========== + + argindex : int + Value to start indexing the arguments at. Default is ``1``. + + """ + return TendonForceLengthInverseDeGroote2016 + + def _latex(self, printer): + """Print a LaTeX representation of the function defining the curve. + + Parameters + ========== + + printer : Printer + The printer to be used to print the LaTeX string representation. + + """ + l_T_tilde = self.args[0] + _l_T_tilde = printer._print(l_T_tilde) + return r'\operatorname{fl}^T \left( %s \right)' % _l_T_tilde + + +class TendonForceLengthInverseDeGroote2016(CharacteristicCurveFunction): + r"""Inverse tendon force-length curve based on De Groote et al., 2016 [1]_. + + Explanation + =========== + + Gives the normalized tendon length that produces a specific normalized + tendon force. + + The function is defined by the equation: + + ${fl^T}^{-1} = frac{\log{\frac{fl^T + c_2}{c_0}}}{c_3} + c_1$ + + with constant values of $c_0 = 0.2$, $c_1 = 0.995$, $c_2 = 0.25$, and + $c_3 = 33.93669377311689$. This function is the exact analytical inverse + of the related tendon force-length curve ``TendonForceLengthDeGroote2016``. + + While it is possible to change the constant values, these were carefully + selected in the original publication to give the characteristic curve + specific and required properties. For example, the function produces no + force when the tendon is in an unstrained state. It also produces a force + of 1 normalized unit when the tendon is under a 5% strain. + + Examples + ======== + + The preferred way to instantiate :class:`TendonForceLengthInverseDeGroote2016` is + using the :meth:`~.with_defaults` constructor because this will automatically + populate the constants within the characteristic curve equation with the + floating point values from the original publication. This constructor takes + a single argument corresponding to normalized tendon force-length, which is + equal to the tendon force. We'll create a :class:`~.Symbol` called ``fl_T`` to + represent this. + + >>> from sympy import Symbol + >>> from sympy.physics.biomechanics import TendonForceLengthInverseDeGroote2016 + >>> fl_T = Symbol('fl_T') + >>> l_T_tilde = TendonForceLengthInverseDeGroote2016.with_defaults(fl_T) + >>> l_T_tilde + TendonForceLengthInverseDeGroote2016(fl_T, 0.2, 0.995, 0.25, + 33.93669377311689) + + It's also possible to populate the four constants with your own values too. + + >>> from sympy import symbols + >>> c0, c1, c2, c3 = symbols('c0 c1 c2 c3') + >>> l_T_tilde = TendonForceLengthInverseDeGroote2016(fl_T, c0, c1, c2, c3) + >>> l_T_tilde + TendonForceLengthInverseDeGroote2016(fl_T, c0, c1, c2, c3) + + To inspect the actual symbolic expression that this function represents, + we can call the :meth:`~.doit` method on an instance. We'll use the keyword + argument ``evaluate=False`` as this will keep the expression in its + canonical form and won't simplify any constants. + + >>> l_T_tilde.doit(evaluate=False) + c1 + log((c2 + fl_T)/c0)/c3 + + The function can also be differentiated. We'll differentiate with respect + to l_T using the ``diff`` method on an instance with the single positional + argument ``l_T``. + + >>> l_T_tilde.diff(fl_T) + 1/(c3*(c2 + fl_T)) + + References + ========== + + .. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation + of direct collocation optimal control problem formulations for + solving the muscle redundancy problem, Annals of biomedical + engineering, 44(10), (2016) pp. 2922-2936 + + """ + + @classmethod + def with_defaults(cls, fl_T): + r"""Recommended constructor that will use the published constants. + + Explanation + =========== + + Returns a new instance of the inverse tendon force-length function + using the four constant values specified in the original publication. + + These have the values: + + $c_0 = 0.2$ + $c_1 = 0.995$ + $c_2 = 0.25$ + $c_3 = 33.93669377311689$ + + Parameters + ========== + + fl_T : Any (sympifiable) + Normalized tendon force as a function of tendon length. + + """ + c0 = Float('0.2') + c1 = Float('0.995') + c2 = Float('0.25') + c3 = Float('33.93669377311689') + return cls(fl_T, c0, c1, c2, c3) + + @classmethod + def eval(cls, fl_T, c0, c1, c2, c3): + """Evaluation of basic inputs. + + Parameters + ========== + + fl_T : Any (sympifiable) + Normalized tendon force as a function of tendon length. + c0 : Any (sympifiable) + The first constant in the characteristic equation. The published + value is ``0.2``. + c1 : Any (sympifiable) + The second constant in the characteristic equation. The published + value is ``0.995``. + c2 : Any (sympifiable) + The third constant in the characteristic equation. The published + value is ``0.25``. + c3 : Any (sympifiable) + The fourth constant in the characteristic equation. The published + value is ``33.93669377311689``. + + """ + pass + + def _eval_evalf(self, prec): + """Evaluate the expression numerically using ``evalf``.""" + return self.doit(deep=False, evaluate=False)._eval_evalf(prec) + + def doit(self, deep=True, evaluate=True, **hints): + """Evaluate the expression defining the function. + + Parameters + ========== + + deep : bool + Whether ``doit`` should be recursively called. Default is ``True``. + evaluate : bool. + Whether the SymPy expression should be evaluated as it is + constructed. If ``False``, then no constant folding will be + conducted which will leave the expression in a more numerically- + stable for values of ``l_T_tilde`` that correspond to a sensible + operating range for a musculotendon. Default is ``True``. + **kwargs : dict[str, Any] + Additional keyword argument pairs to be recursively passed to + ``doit``. + + """ + fl_T, *constants = self.args + if deep: + hints['evaluate'] = evaluate + fl_T = fl_T.doit(deep=deep, **hints) + c0, c1, c2, c3 = [c.doit(deep=deep, **hints) for c in constants] + else: + c0, c1, c2, c3 = constants + + if evaluate: + return log((fl_T + c2)/c0)/c3 + c1 + + return log(UnevaluatedExpr((fl_T + c2)/c0))/c3 + c1 + + def fdiff(self, argindex=1): + """Derivative of the function with respect to a single argument. + + Parameters + ========== + + argindex : int + The index of the function's arguments with respect to which the + derivative should be taken. Argument indexes start at ``1``. + Default is ``1``. + + """ + fl_T, c0, c1, c2, c3 = self.args + if argindex == 1: + return 1/(c3*(fl_T + c2)) + elif argindex == 2: + return -1/(c0*c3) + elif argindex == 3: + return Integer(1) + elif argindex == 4: + return 1/(c3*(fl_T + c2)) + elif argindex == 5: + return -log(UnevaluatedExpr((fl_T + c2)/c0))/c3**2 + + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """Inverse function. + + Parameters + ========== + + argindex : int + Value to start indexing the arguments at. Default is ``1``. + + """ + return TendonForceLengthDeGroote2016 + + def _latex(self, printer): + """Print a LaTeX representation of the function defining the curve. + + Parameters + ========== + + printer : Printer + The printer to be used to print the LaTeX string representation. + + """ + fl_T = self.args[0] + _fl_T = printer._print(fl_T) + return r'\left( \operatorname{fl}^T \right)^{-1} \left( %s \right)' % _fl_T + + +class FiberForceLengthPassiveDeGroote2016(CharacteristicCurveFunction): + r"""Passive muscle fiber force-length curve based on De Groote et al., 2016 + [1]_. + + Explanation + =========== + + The function is defined by the equation: + + $fl^M_{pas} = \frac{\frac{\exp{c_1 \left(\tilde{l^M} - 1\right)}}{c_0} - 1}{\exp{c_1} - 1}$ + + with constant values of $c_0 = 0.6$ and $c_1 = 4.0$. + + While it is possible to change the constant values, these were carefully + selected in the original publication to give the characteristic curve + specific and required properties. For example, the function produces a + passive fiber force very close to 0 for all normalized fiber lengths + between 0 and 1. + + Examples + ======== + + The preferred way to instantiate :class:`FiberForceLengthPassiveDeGroote2016` is + using the :meth:`~.with_defaults` constructor because this will automatically + populate the constants within the characteristic curve equation with the + floating point values from the original publication. This constructor takes + a single argument corresponding to normalized muscle fiber length. We'll + create a :class:`~.Symbol` called ``l_M_tilde`` to represent this. + + >>> from sympy import Symbol + >>> from sympy.physics.biomechanics import FiberForceLengthPassiveDeGroote2016 + >>> l_M_tilde = Symbol('l_M_tilde') + >>> fl_M = FiberForceLengthPassiveDeGroote2016.with_defaults(l_M_tilde) + >>> fl_M + FiberForceLengthPassiveDeGroote2016(l_M_tilde, 0.6, 4.0) + + It's also possible to populate the two constants with your own values too. + + >>> from sympy import symbols + >>> c0, c1 = symbols('c0 c1') + >>> fl_M = FiberForceLengthPassiveDeGroote2016(l_M_tilde, c0, c1) + >>> fl_M + FiberForceLengthPassiveDeGroote2016(l_M_tilde, c0, c1) + + You don't just have to use symbols as the arguments, it's also possible to + use expressions. Let's create a new pair of symbols, ``l_M`` and + ``l_M_opt``, representing muscle fiber length and optimal muscle fiber + length respectively. We can then represent ``l_M_tilde`` as an expression, + the ratio of these. + + >>> l_M, l_M_opt = symbols('l_M l_M_opt') + >>> l_M_tilde = l_M/l_M_opt + >>> fl_M = FiberForceLengthPassiveDeGroote2016.with_defaults(l_M_tilde) + >>> fl_M + FiberForceLengthPassiveDeGroote2016(l_M/l_M_opt, 0.6, 4.0) + + To inspect the actual symbolic expression that this function represents, + we can call the :meth:`~.doit` method on an instance. We'll use the keyword + argument ``evaluate=False`` as this will keep the expression in its + canonical form and won't simplify any constants. + + >>> fl_M.doit(evaluate=False) + 0.0186573603637741*(-1 + exp(6.66666666666667*(l_M/l_M_opt - 1))) + + The function can also be differentiated. We'll differentiate with respect + to l_M using the ``diff`` method on an instance with the single positional + argument ``l_M``. + + >>> fl_M.diff(l_M) + 0.12438240242516*exp(6.66666666666667*(l_M/l_M_opt - 1))/l_M_opt + + References + ========== + + .. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation + of direct collocation optimal control problem formulations for + solving the muscle redundancy problem, Annals of biomedical + engineering, 44(10), (2016) pp. 2922-2936 + + """ + + @classmethod + def with_defaults(cls, l_M_tilde): + r"""Recommended constructor that will use the published constants. + + Explanation + =========== + + Returns a new instance of the muscle fiber passive force-length + function using the four constant values specified in the original + publication. + + These have the values: + + $c_0 = 0.6$ + $c_1 = 4.0$ + + Parameters + ========== + + l_M_tilde : Any (sympifiable) + Normalized muscle fiber length. + + """ + c0 = Float('0.6') + c1 = Float('4.0') + return cls(l_M_tilde, c0, c1) + + @classmethod + def eval(cls, l_M_tilde, c0, c1): + """Evaluation of basic inputs. + + Parameters + ========== + + l_M_tilde : Any (sympifiable) + Normalized muscle fiber length. + c0 : Any (sympifiable) + The first constant in the characteristic equation. The published + value is ``0.6``. + c1 : Any (sympifiable) + The second constant in the characteristic equation. The published + value is ``4.0``. + + """ + pass + + def _eval_evalf(self, prec): + """Evaluate the expression numerically using ``evalf``.""" + return self.doit(deep=False, evaluate=False)._eval_evalf(prec) + + def doit(self, deep=True, evaluate=True, **hints): + """Evaluate the expression defining the function. + + Parameters + ========== + + deep : bool + Whether ``doit`` should be recursively called. Default is ``True``. + evaluate : bool. + Whether the SymPy expression should be evaluated as it is + constructed. If ``False``, then no constant folding will be + conducted which will leave the expression in a more numerically- + stable for values of ``l_T_tilde`` that correspond to a sensible + operating range for a musculotendon. Default is ``True``. + **kwargs : dict[str, Any] + Additional keyword argument pairs to be recursively passed to + ``doit``. + + """ + l_M_tilde, *constants = self.args + if deep: + hints['evaluate'] = evaluate + l_M_tilde = l_M_tilde.doit(deep=deep, **hints) + c0, c1 = [c.doit(deep=deep, **hints) for c in constants] + else: + c0, c1 = constants + + if evaluate: + return (exp((c1*(l_M_tilde - 1))/c0) - 1)/(exp(c1) - 1) + + return (exp((c1*UnevaluatedExpr(l_M_tilde - 1))/c0) - 1)/(exp(c1) - 1) + + def fdiff(self, argindex=1): + """Derivative of the function with respect to a single argument. + + Parameters + ========== + + argindex : int + The index of the function's arguments with respect to which the + derivative should be taken. Argument indexes start at ``1``. + Default is ``1``. + + """ + l_M_tilde, c0, c1 = self.args + if argindex == 1: + return c1*exp(c1*UnevaluatedExpr(l_M_tilde - 1)/c0)/(c0*(exp(c1) - 1)) + elif argindex == 2: + return ( + -c1*exp(c1*UnevaluatedExpr(l_M_tilde - 1)/c0) + *UnevaluatedExpr(l_M_tilde - 1)/(c0**2*(exp(c1) - 1)) + ) + elif argindex == 3: + return ( + -exp(c1)*(-1 + exp(c1*UnevaluatedExpr(l_M_tilde - 1)/c0))/(exp(c1) - 1)**2 + + exp(c1*UnevaluatedExpr(l_M_tilde - 1)/c0)*(l_M_tilde - 1)/(c0*(exp(c1) - 1)) + ) + + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """Inverse function. + + Parameters + ========== + + argindex : int + Value to start indexing the arguments at. Default is ``1``. + + """ + return FiberForceLengthPassiveInverseDeGroote2016 + + def _latex(self, printer): + """Print a LaTeX representation of the function defining the curve. + + Parameters + ========== + + printer : Printer + The printer to be used to print the LaTeX string representation. + + """ + l_M_tilde = self.args[0] + _l_M_tilde = printer._print(l_M_tilde) + return r'\operatorname{fl}^M_{pas} \left( %s \right)' % _l_M_tilde + + +class FiberForceLengthPassiveInverseDeGroote2016(CharacteristicCurveFunction): + r"""Inverse passive muscle fiber force-length curve based on De Groote et + al., 2016 [1]_. + + Explanation + =========== + + Gives the normalized muscle fiber length that produces a specific normalized + passive muscle fiber force. + + The function is defined by the equation: + + ${fl^M_{pas}}^{-1} = \frac{c_0 \log{\left(\exp{c_1} - 1\right)fl^M_pas + 1}}{c_1} + 1$ + + with constant values of $c_0 = 0.6$ and $c_1 = 4.0$. This function is the + exact analytical inverse of the related tendon force-length curve + ``FiberForceLengthPassiveDeGroote2016``. + + While it is possible to change the constant values, these were carefully + selected in the original publication to give the characteristic curve + specific and required properties. For example, the function produces a + passive fiber force very close to 0 for all normalized fiber lengths + between 0 and 1. + + Examples + ======== + + The preferred way to instantiate + :class:`FiberForceLengthPassiveInverseDeGroote2016` is using the + :meth:`~.with_defaults` constructor because this will automatically populate the + constants within the characteristic curve equation with the floating point + values from the original publication. This constructor takes a single + argument corresponding to the normalized passive muscle fiber length-force + component of the muscle fiber force. We'll create a :class:`~.Symbol` called + ``fl_M_pas`` to represent this. + + >>> from sympy import Symbol + >>> from sympy.physics.biomechanics import FiberForceLengthPassiveInverseDeGroote2016 + >>> fl_M_pas = Symbol('fl_M_pas') + >>> l_M_tilde = FiberForceLengthPassiveInverseDeGroote2016.with_defaults(fl_M_pas) + >>> l_M_tilde + FiberForceLengthPassiveInverseDeGroote2016(fl_M_pas, 0.6, 4.0) + + It's also possible to populate the two constants with your own values too. + + >>> from sympy import symbols + >>> c0, c1 = symbols('c0 c1') + >>> l_M_tilde = FiberForceLengthPassiveInverseDeGroote2016(fl_M_pas, c0, c1) + >>> l_M_tilde + FiberForceLengthPassiveInverseDeGroote2016(fl_M_pas, c0, c1) + + To inspect the actual symbolic expression that this function represents, + we can call the :meth:`~.doit` method on an instance. We'll use the keyword + argument ``evaluate=False`` as this will keep the expression in its + canonical form and won't simplify any constants. + + >>> l_M_tilde.doit(evaluate=False) + c0*log(1 + fl_M_pas*(exp(c1) - 1))/c1 + 1 + + The function can also be differentiated. We'll differentiate with respect + to fl_M_pas using the ``diff`` method on an instance with the single positional + argument ``fl_M_pas``. + + >>> l_M_tilde.diff(fl_M_pas) + c0*(exp(c1) - 1)/(c1*(fl_M_pas*(exp(c1) - 1) + 1)) + + References + ========== + + .. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation + of direct collocation optimal control problem formulations for + solving the muscle redundancy problem, Annals of biomedical + engineering, 44(10), (2016) pp. 2922-2936 + + """ + + @classmethod + def with_defaults(cls, fl_M_pas): + r"""Recommended constructor that will use the published constants. + + Explanation + =========== + + Returns a new instance of the inverse muscle fiber passive force-length + function using the four constant values specified in the original + publication. + + These have the values: + + $c_0 = 0.6$ + $c_1 = 4.0$ + + Parameters + ========== + + fl_M_pas : Any (sympifiable) + Normalized passive muscle fiber force as a function of muscle fiber + length. + + """ + c0 = Float('0.6') + c1 = Float('4.0') + return cls(fl_M_pas, c0, c1) + + @classmethod + def eval(cls, fl_M_pas, c0, c1): + """Evaluation of basic inputs. + + Parameters + ========== + + fl_M_pas : Any (sympifiable) + Normalized passive muscle fiber force. + c0 : Any (sympifiable) + The first constant in the characteristic equation. The published + value is ``0.6``. + c1 : Any (sympifiable) + The second constant in the characteristic equation. The published + value is ``4.0``. + + """ + pass + + def _eval_evalf(self, prec): + """Evaluate the expression numerically using ``evalf``.""" + return self.doit(deep=False, evaluate=False)._eval_evalf(prec) + + def doit(self, deep=True, evaluate=True, **hints): + """Evaluate the expression defining the function. + + Parameters + ========== + + deep : bool + Whether ``doit`` should be recursively called. Default is ``True``. + evaluate : bool. + Whether the SymPy expression should be evaluated as it is + constructed. If ``False``, then no constant folding will be + conducted which will leave the expression in a more numerically- + stable for values of ``l_T_tilde`` that correspond to a sensible + operating range for a musculotendon. Default is ``True``. + **kwargs : dict[str, Any] + Additional keyword argument pairs to be recursively passed to + ``doit``. + + """ + fl_M_pas, *constants = self.args + if deep: + hints['evaluate'] = evaluate + fl_M_pas = fl_M_pas.doit(deep=deep, **hints) + c0, c1 = [c.doit(deep=deep, **hints) for c in constants] + else: + c0, c1 = constants + + if evaluate: + return c0*log(fl_M_pas*(exp(c1) - 1) + 1)/c1 + 1 + + return c0*log(UnevaluatedExpr(fl_M_pas*(exp(c1) - 1)) + 1)/c1 + 1 + + def fdiff(self, argindex=1): + """Derivative of the function with respect to a single argument. + + Parameters + ========== + + argindex : int + The index of the function's arguments with respect to which the + derivative should be taken. Argument indexes start at ``1``. + Default is ``1``. + + """ + fl_M_pas, c0, c1 = self.args + if argindex == 1: + return c0*(exp(c1) - 1)/(c1*(fl_M_pas*(exp(c1) - 1) + 1)) + elif argindex == 2: + return log(fl_M_pas*(exp(c1) - 1) + 1)/c1 + elif argindex == 3: + return ( + c0*fl_M_pas*exp(c1)/(c1*(fl_M_pas*(exp(c1) - 1) + 1)) + - c0*log(fl_M_pas*(exp(c1) - 1) + 1)/c1**2 + ) + + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """Inverse function. + + Parameters + ========== + + argindex : int + Value to start indexing the arguments at. Default is ``1``. + + """ + return FiberForceLengthPassiveDeGroote2016 + + def _latex(self, printer): + """Print a LaTeX representation of the function defining the curve. + + Parameters + ========== + + printer : Printer + The printer to be used to print the LaTeX string representation. + + """ + fl_M_pas = self.args[0] + _fl_M_pas = printer._print(fl_M_pas) + return r'\left( \operatorname{fl}^M_{pas} \right)^{-1} \left( %s \right)' % _fl_M_pas + + +class FiberForceLengthActiveDeGroote2016(CharacteristicCurveFunction): + r"""Active muscle fiber force-length curve based on De Groote et al., 2016 + [1]_. + + Explanation + =========== + + The function is defined by the equation: + + $fl_{\text{act}}^M = c_0 \exp\left(-\frac{1}{2}\left(\frac{\tilde{l}^M - c_1}{c_2 + c_3 \tilde{l}^M}\right)^2\right) + + c_4 \exp\left(-\frac{1}{2}\left(\frac{\tilde{l}^M - c_5}{c_6 + c_7 \tilde{l}^M}\right)^2\right) + + c_8 \exp\left(-\frac{1}{2}\left(\frac{\tilde{l}^M - c_9}{c_{10} + c_{11} \tilde{l}^M}\right)^2\right)$ + + with constant values of $c0 = 0.814$, $c1 = 1.06$, $c2 = 0.162$, + $c3 = 0.0633$, $c4 = 0.433$, $c5 = 0.717$, $c6 = -0.0299$, $c7 = 0.2$, + $c8 = 0.1$, $c9 = 1.0$, $c10 = 0.354$, and $c11 = 0.0$. + + While it is possible to change the constant values, these were carefully + selected in the original publication to give the characteristic curve + specific and required properties. For example, the function produces a + active fiber force of 1 at a normalized fiber length of 1, and an active + fiber force of 0 at normalized fiber lengths of 0 and 2. + + Examples + ======== + + The preferred way to instantiate :class:`FiberForceLengthActiveDeGroote2016` is + using the :meth:`~.with_defaults` constructor because this will automatically + populate the constants within the characteristic curve equation with the + floating point values from the original publication. This constructor takes + a single argument corresponding to normalized muscle fiber length. We'll + create a :class:`~.Symbol` called ``l_M_tilde`` to represent this. + + >>> from sympy import Symbol + >>> from sympy.physics.biomechanics import FiberForceLengthActiveDeGroote2016 + >>> l_M_tilde = Symbol('l_M_tilde') + >>> fl_M = FiberForceLengthActiveDeGroote2016.with_defaults(l_M_tilde) + >>> fl_M + FiberForceLengthActiveDeGroote2016(l_M_tilde, 0.814, 1.06, 0.162, 0.0633, + 0.433, 0.717, -0.0299, 0.2, 0.1, 1.0, 0.354, 0.0) + + It's also possible to populate the two constants with your own values too. + + >>> from sympy import symbols + >>> c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = symbols('c0:12') + >>> fl_M = FiberForceLengthActiveDeGroote2016(l_M_tilde, c0, c1, c2, c3, + ... c4, c5, c6, c7, c8, c9, c10, c11) + >>> fl_M + FiberForceLengthActiveDeGroote2016(l_M_tilde, c0, c1, c2, c3, c4, c5, c6, + c7, c8, c9, c10, c11) + + You don't just have to use symbols as the arguments, it's also possible to + use expressions. Let's create a new pair of symbols, ``l_M`` and + ``l_M_opt``, representing muscle fiber length and optimal muscle fiber + length respectively. We can then represent ``l_M_tilde`` as an expression, + the ratio of these. + + >>> l_M, l_M_opt = symbols('l_M l_M_opt') + >>> l_M_tilde = l_M/l_M_opt + >>> fl_M = FiberForceLengthActiveDeGroote2016.with_defaults(l_M_tilde) + >>> fl_M + FiberForceLengthActiveDeGroote2016(l_M/l_M_opt, 0.814, 1.06, 0.162, 0.0633, + 0.433, 0.717, -0.0299, 0.2, 0.1, 1.0, 0.354, 0.0) + + To inspect the actual symbolic expression that this function represents, + we can call the :meth:`~.doit` method on an instance. We'll use the keyword + argument ``evaluate=False`` as this will keep the expression in its + canonical form and won't simplify any constants. + + >>> fl_M.doit(evaluate=False) + 0.814*exp(-19.0519737844841*(l_M/l_M_opt + - 1.06)**2/(0.390740740740741*l_M/l_M_opt + 1)**2) + + 0.433*exp(-12.5*(l_M/l_M_opt - 0.717)**2/(l_M/l_M_opt - 0.1495)**2) + + 0.1*exp(-3.98991349867535*(l_M/l_M_opt - 1.0)**2) + + The function can also be differentiated. We'll differentiate with respect + to l_M using the ``diff`` method on an instance with the single positional + argument ``l_M``. + + >>> fl_M.diff(l_M) + ((-0.79798269973507*l_M/l_M_opt + + 0.79798269973507)*exp(-3.98991349867535*(l_M/l_M_opt - 1.0)**2) + + (10.825*(-l_M/l_M_opt + 0.717)/(l_M/l_M_opt - 0.1495)**2 + + 10.825*(l_M/l_M_opt - 0.717)**2/(l_M/l_M_opt + - 0.1495)**3)*exp(-12.5*(l_M/l_M_opt - 0.717)**2/(l_M/l_M_opt - 0.1495)**2) + + (31.0166133211401*(-l_M/l_M_opt + 1.06)/(0.390740740740741*l_M/l_M_opt + + 1)**2 + 13.6174190361677*(0.943396226415094*l_M/l_M_opt + - 1)**2/(0.390740740740741*l_M/l_M_opt + + 1)**3)*exp(-21.4067977442463*(0.943396226415094*l_M/l_M_opt + - 1)**2/(0.390740740740741*l_M/l_M_opt + 1)**2))/l_M_opt + + References + ========== + + .. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation + of direct collocation optimal control problem formulations for + solving the muscle redundancy problem, Annals of biomedical + engineering, 44(10), (2016) pp. 2922-2936 + + """ + + @classmethod + def with_defaults(cls, l_M_tilde): + r"""Recommended constructor that will use the published constants. + + Explanation + =========== + + Returns a new instance of the inverse muscle fiber act force-length + function using the four constant values specified in the original + publication. + + These have the values: + + $c0 = 0.814$ + $c1 = 1.06$ + $c2 = 0.162$ + $c3 = 0.0633$ + $c4 = 0.433$ + $c5 = 0.717$ + $c6 = -0.0299$ + $c7 = 0.2$ + $c8 = 0.1$ + $c9 = 1.0$ + $c10 = 0.354$ + $c11 = 0.0$ + + Parameters + ========== + + fl_M_act : Any (sympifiable) + Normalized passive muscle fiber force as a function of muscle fiber + length. + + """ + c0 = Float('0.814') + c1 = Float('1.06') + c2 = Float('0.162') + c3 = Float('0.0633') + c4 = Float('0.433') + c5 = Float('0.717') + c6 = Float('-0.0299') + c7 = Float('0.2') + c8 = Float('0.1') + c9 = Float('1.0') + c10 = Float('0.354') + c11 = Float('0.0') + return cls(l_M_tilde, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) + + @classmethod + def eval(cls, l_M_tilde, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11): + """Evaluation of basic inputs. + + Parameters + ========== + + l_M_tilde : Any (sympifiable) + Normalized muscle fiber length. + c0 : Any (sympifiable) + The first constant in the characteristic equation. The published + value is ``0.814``. + c1 : Any (sympifiable) + The second constant in the characteristic equation. The published + value is ``1.06``. + c2 : Any (sympifiable) + The third constant in the characteristic equation. The published + value is ``0.162``. + c3 : Any (sympifiable) + The fourth constant in the characteristic equation. The published + value is ``0.0633``. + c4 : Any (sympifiable) + The fifth constant in the characteristic equation. The published + value is ``0.433``. + c5 : Any (sympifiable) + The sixth constant in the characteristic equation. The published + value is ``0.717``. + c6 : Any (sympifiable) + The seventh constant in the characteristic equation. The published + value is ``-0.0299``. + c7 : Any (sympifiable) + The eighth constant in the characteristic equation. The published + value is ``0.2``. + c8 : Any (sympifiable) + The ninth constant in the characteristic equation. The published + value is ``0.1``. + c9 : Any (sympifiable) + The tenth constant in the characteristic equation. The published + value is ``1.0``. + c10 : Any (sympifiable) + The eleventh constant in the characteristic equation. The published + value is ``0.354``. + c11 : Any (sympifiable) + The tweflth constant in the characteristic equation. The published + value is ``0.0``. + + """ + pass + + def _eval_evalf(self, prec): + """Evaluate the expression numerically using ``evalf``.""" + return self.doit(deep=False, evaluate=False)._eval_evalf(prec) + + def doit(self, deep=True, evaluate=True, **hints): + """Evaluate the expression defining the function. + + Parameters + ========== + + deep : bool + Whether ``doit`` should be recursively called. Default is ``True``. + evaluate : bool. + Whether the SymPy expression should be evaluated as it is + constructed. If ``False``, then no constant folding will be + conducted which will leave the expression in a more numerically- + stable for values of ``l_M_tilde`` that correspond to a sensible + operating range for a musculotendon. Default is ``True``. + **kwargs : dict[str, Any] + Additional keyword argument pairs to be recursively passed to + ``doit``. + + """ + l_M_tilde, *constants = self.args + if deep: + hints['evaluate'] = evaluate + l_M_tilde = l_M_tilde.doit(deep=deep, **hints) + constants = [c.doit(deep=deep, **hints) for c in constants] + c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = constants + + if evaluate: + return ( + c0*exp(-(((l_M_tilde - c1)/(c2 + c3*l_M_tilde))**2)/2) + + c4*exp(-(((l_M_tilde - c5)/(c6 + c7*l_M_tilde))**2)/2) + + c8*exp(-(((l_M_tilde - c9)/(c10 + c11*l_M_tilde))**2)/2) + ) + + return ( + c0*exp(-((UnevaluatedExpr(l_M_tilde - c1)/(c2 + c3*l_M_tilde))**2)/2) + + c4*exp(-((UnevaluatedExpr(l_M_tilde - c5)/(c6 + c7*l_M_tilde))**2)/2) + + c8*exp(-((UnevaluatedExpr(l_M_tilde - c9)/(c10 + c11*l_M_tilde))**2)/2) + ) + + def fdiff(self, argindex=1): + """Derivative of the function with respect to a single argument. + + Parameters + ========== + + argindex : int + The index of the function's arguments with respect to which the + derivative should be taken. Argument indexes start at ``1``. + Default is ``1``. + + """ + l_M_tilde, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = self.args + if argindex == 1: + return ( + c0*( + c3*(l_M_tilde - c1)**2/(c2 + c3*l_M_tilde)**3 + + (c1 - l_M_tilde)/((c2 + c3*l_M_tilde)**2) + )*exp(-(l_M_tilde - c1)**2/(2*(c2 + c3*l_M_tilde)**2)) + + c4*( + c7*(l_M_tilde - c5)**2/(c6 + c7*l_M_tilde)**3 + + (c5 - l_M_tilde)/((c6 + c7*l_M_tilde)**2) + )*exp(-(l_M_tilde - c5)**2/(2*(c6 + c7*l_M_tilde)**2)) + + c8*( + c11*(l_M_tilde - c9)**2/(c10 + c11*l_M_tilde)**3 + + (c9 - l_M_tilde)/((c10 + c11*l_M_tilde)**2) + )*exp(-(l_M_tilde - c9)**2/(2*(c10 + c11*l_M_tilde)**2)) + ) + elif argindex == 2: + return exp(-(l_M_tilde - c1)**2/(2*(c2 + c3*l_M_tilde)**2)) + elif argindex == 3: + return ( + c0*(l_M_tilde - c1)/(c2 + c3*l_M_tilde)**2 + *exp(-(l_M_tilde - c1)**2 /(2*(c2 + c3*l_M_tilde)**2)) + ) + elif argindex == 4: + return ( + c0*(l_M_tilde - c1)**2/(c2 + c3*l_M_tilde)**3 + *exp(-(l_M_tilde - c1)**2/(2*(c2 + c3*l_M_tilde)**2)) + ) + elif argindex == 5: + return ( + c0*l_M_tilde*(l_M_tilde - c1)**2/(c2 + c3*l_M_tilde)**3 + *exp(-(l_M_tilde - c1)**2/(2*(c2 + c3*l_M_tilde)**2)) + ) + elif argindex == 6: + return exp(-(l_M_tilde - c5)**2/(2*(c6 + c7*l_M_tilde)**2)) + elif argindex == 7: + return ( + c4*(l_M_tilde - c5)/(c6 + c7*l_M_tilde)**2 + *exp(-(l_M_tilde - c5)**2 /(2*(c6 + c7*l_M_tilde)**2)) + ) + elif argindex == 8: + return ( + c4*(l_M_tilde - c5)**2/(c6 + c7*l_M_tilde)**3 + *exp(-(l_M_tilde - c5)**2/(2*(c6 + c7*l_M_tilde)**2)) + ) + elif argindex == 9: + return ( + c4*l_M_tilde*(l_M_tilde - c5)**2/(c6 + c7*l_M_tilde)**3 + *exp(-(l_M_tilde - c5)**2/(2*(c6 + c7*l_M_tilde)**2)) + ) + elif argindex == 10: + return exp(-(l_M_tilde - c9)**2/(2*(c10 + c11*l_M_tilde)**2)) + elif argindex == 11: + return ( + c8*(l_M_tilde - c9)/(c10 + c11*l_M_tilde)**2 + *exp(-(l_M_tilde - c9)**2 /(2*(c10 + c11*l_M_tilde)**2)) + ) + elif argindex == 12: + return ( + c8*(l_M_tilde - c9)**2/(c10 + c11*l_M_tilde)**3 + *exp(-(l_M_tilde - c9)**2/(2*(c10 + c11*l_M_tilde)**2)) + ) + elif argindex == 13: + return ( + c8*l_M_tilde*(l_M_tilde - c9)**2/(c10 + c11*l_M_tilde)**3 + *exp(-(l_M_tilde - c9)**2/(2*(c10 + c11*l_M_tilde)**2)) + ) + + raise ArgumentIndexError(self, argindex) + + def _latex(self, printer): + """Print a LaTeX representation of the function defining the curve. + + Parameters + ========== + + printer : Printer + The printer to be used to print the LaTeX string representation. + + """ + l_M_tilde = self.args[0] + _l_M_tilde = printer._print(l_M_tilde) + return r'\operatorname{fl}^M_{act} \left( %s \right)' % _l_M_tilde + + +class FiberForceVelocityDeGroote2016(CharacteristicCurveFunction): + r"""Muscle fiber force-velocity curve based on De Groote et al., 2016 [1]_. + + Explanation + =========== + + Gives the normalized muscle fiber force produced as a function of + normalized tendon velocity. + + The function is defined by the equation: + + $fv^M = c_0 \log{\left(c_1 \tilde{v}_m + c_2\right) + \sqrt{\left(c_1 \tilde{v}_m + c_2\right)^2 + 1}} + c_3$ + + with constant values of $c_0 = -0.318$, $c_1 = -8.149$, $c_2 = -0.374$, and + $c_3 = 0.886$. + + While it is possible to change the constant values, these were carefully + selected in the original publication to give the characteristic curve + specific and required properties. For example, the function produces a + normalized muscle fiber force of 1 when the muscle fibers are contracting + isometrically (they have an extension rate of 0). + + Examples + ======== + + The preferred way to instantiate :class:`FiberForceVelocityDeGroote2016` is using + the :meth:`~.with_defaults` constructor because this will automatically populate + the constants within the characteristic curve equation with the floating + point values from the original publication. This constructor takes a single + argument corresponding to normalized muscle fiber extension velocity. We'll + create a :class:`~.Symbol` called ``v_M_tilde`` to represent this. + + >>> from sympy import Symbol + >>> from sympy.physics.biomechanics import FiberForceVelocityDeGroote2016 + >>> v_M_tilde = Symbol('v_M_tilde') + >>> fv_M = FiberForceVelocityDeGroote2016.with_defaults(v_M_tilde) + >>> fv_M + FiberForceVelocityDeGroote2016(v_M_tilde, -0.318, -8.149, -0.374, 0.886) + + It's also possible to populate the four constants with your own values too. + + >>> from sympy import symbols + >>> c0, c1, c2, c3 = symbols('c0 c1 c2 c3') + >>> fv_M = FiberForceVelocityDeGroote2016(v_M_tilde, c0, c1, c2, c3) + >>> fv_M + FiberForceVelocityDeGroote2016(v_M_tilde, c0, c1, c2, c3) + + You don't just have to use symbols as the arguments, it's also possible to + use expressions. Let's create a new pair of symbols, ``v_M`` and + ``v_M_max``, representing muscle fiber extension velocity and maximum + muscle fiber extension velocity respectively. We can then represent + ``v_M_tilde`` as an expression, the ratio of these. + + >>> v_M, v_M_max = symbols('v_M v_M_max') + >>> v_M_tilde = v_M/v_M_max + >>> fv_M = FiberForceVelocityDeGroote2016.with_defaults(v_M_tilde) + >>> fv_M + FiberForceVelocityDeGroote2016(v_M/v_M_max, -0.318, -8.149, -0.374, 0.886) + + To inspect the actual symbolic expression that this function represents, + we can call the :meth:`~.doit` method on an instance. We'll use the keyword + argument ``evaluate=False`` as this will keep the expression in its + canonical form and won't simplify any constants. + + >>> fv_M.doit(evaluate=False) + 0.886 - 0.318*log(-8.149*v_M/v_M_max - 0.374 + sqrt(1 + (-8.149*v_M/v_M_max + - 0.374)**2)) + + The function can also be differentiated. We'll differentiate with respect + to v_M using the ``diff`` method on an instance with the single positional + argument ``v_M``. + + >>> fv_M.diff(v_M) + 2.591382*(1 + (-8.149*v_M/v_M_max - 0.374)**2)**(-1/2)/v_M_max + + References + ========== + + .. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation + of direct collocation optimal control problem formulations for + solving the muscle redundancy problem, Annals of biomedical + engineering, 44(10), (2016) pp. 2922-2936 + + """ + + @classmethod + def with_defaults(cls, v_M_tilde): + r"""Recommended constructor that will use the published constants. + + Explanation + =========== + + Returns a new instance of the muscle fiber force-velocity function + using the four constant values specified in the original publication. + + These have the values: + + $c_0 = -0.318$ + $c_1 = -8.149$ + $c_2 = -0.374$ + $c_3 = 0.886$ + + Parameters + ========== + + v_M_tilde : Any (sympifiable) + Normalized muscle fiber extension velocity. + + """ + c0 = Float('-0.318') + c1 = Float('-8.149') + c2 = Float('-0.374') + c3 = Float('0.886') + return cls(v_M_tilde, c0, c1, c2, c3) + + @classmethod + def eval(cls, v_M_tilde, c0, c1, c2, c3): + """Evaluation of basic inputs. + + Parameters + ========== + + v_M_tilde : Any (sympifiable) + Normalized muscle fiber extension velocity. + c0 : Any (sympifiable) + The first constant in the characteristic equation. The published + value is ``-0.318``. + c1 : Any (sympifiable) + The second constant in the characteristic equation. The published + value is ``-8.149``. + c2 : Any (sympifiable) + The third constant in the characteristic equation. The published + value is ``-0.374``. + c3 : Any (sympifiable) + The fourth constant in the characteristic equation. The published + value is ``0.886``. + + """ + pass + + def _eval_evalf(self, prec): + """Evaluate the expression numerically using ``evalf``.""" + return self.doit(deep=False, evaluate=False)._eval_evalf(prec) + + def doit(self, deep=True, evaluate=True, **hints): + """Evaluate the expression defining the function. + + Parameters + ========== + + deep : bool + Whether ``doit`` should be recursively called. Default is ``True``. + evaluate : bool. + Whether the SymPy expression should be evaluated as it is + constructed. If ``False``, then no constant folding will be + conducted which will leave the expression in a more numerically- + stable for values of ``v_M_tilde`` that correspond to a sensible + operating range for a musculotendon. Default is ``True``. + **kwargs : dict[str, Any] + Additional keyword argument pairs to be recursively passed to + ``doit``. + + """ + v_M_tilde, *constants = self.args + if deep: + hints['evaluate'] = evaluate + v_M_tilde = v_M_tilde.doit(deep=deep, **hints) + c0, c1, c2, c3 = [c.doit(deep=deep, **hints) for c in constants] + else: + c0, c1, c2, c3 = constants + + if evaluate: + return c0*log(c1*v_M_tilde + c2 + sqrt((c1*v_M_tilde + c2)**2 + 1)) + c3 + + return c0*log(c1*v_M_tilde + c2 + sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1)) + c3 + + def fdiff(self, argindex=1): + """Derivative of the function with respect to a single argument. + + Parameters + ========== + + argindex : int + The index of the function's arguments with respect to which the + derivative should be taken. Argument indexes start at ``1``. + Default is ``1``. + + """ + v_M_tilde, c0, c1, c2, c3 = self.args + if argindex == 1: + return c0*c1/sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1) + elif argindex == 2: + return log( + c1*v_M_tilde + c2 + + sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1) + ) + elif argindex == 3: + return c0*v_M_tilde/sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1) + elif argindex == 4: + return c0/sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1) + elif argindex == 5: + return Integer(1) + + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """Inverse function. + + Parameters + ========== + + argindex : int + Value to start indexing the arguments at. Default is ``1``. + + """ + return FiberForceVelocityInverseDeGroote2016 + + def _latex(self, printer): + """Print a LaTeX representation of the function defining the curve. + + Parameters + ========== + + printer : Printer + The printer to be used to print the LaTeX string representation. + + """ + v_M_tilde = self.args[0] + _v_M_tilde = printer._print(v_M_tilde) + return r'\operatorname{fv}^M \left( %s \right)' % _v_M_tilde + + +class FiberForceVelocityInverseDeGroote2016(CharacteristicCurveFunction): + r"""Inverse muscle fiber force-velocity curve based on De Groote et al., + 2016 [1]_. + + Explanation + =========== + + Gives the normalized muscle fiber velocity that produces a specific + normalized muscle fiber force. + + The function is defined by the equation: + + ${fv^M}^{-1} = \frac{\sinh{\frac{fv^M - c_3}{c_0}} - c_2}{c_1}$ + + with constant values of $c_0 = -0.318$, $c_1 = -8.149$, $c_2 = -0.374$, and + $c_3 = 0.886$. This function is the exact analytical inverse of the related + muscle fiber force-velocity curve ``FiberForceVelocityDeGroote2016``. + + While it is possible to change the constant values, these were carefully + selected in the original publication to give the characteristic curve + specific and required properties. For example, the function produces a + normalized muscle fiber force of 1 when the muscle fibers are contracting + isometrically (they have an extension rate of 0). + + Examples + ======== + + The preferred way to instantiate :class:`FiberForceVelocityInverseDeGroote2016` + is using the :meth:`~.with_defaults` constructor because this will automatically + populate the constants within the characteristic curve equation with the + floating point values from the original publication. This constructor takes + a single argument corresponding to normalized muscle fiber force-velocity + component of the muscle fiber force. We'll create a :class:`~.Symbol` called + ``fv_M`` to represent this. + + >>> from sympy import Symbol + >>> from sympy.physics.biomechanics import FiberForceVelocityInverseDeGroote2016 + >>> fv_M = Symbol('fv_M') + >>> v_M_tilde = FiberForceVelocityInverseDeGroote2016.with_defaults(fv_M) + >>> v_M_tilde + FiberForceVelocityInverseDeGroote2016(fv_M, -0.318, -8.149, -0.374, 0.886) + + It's also possible to populate the four constants with your own values too. + + >>> from sympy import symbols + >>> c0, c1, c2, c3 = symbols('c0 c1 c2 c3') + >>> v_M_tilde = FiberForceVelocityInverseDeGroote2016(fv_M, c0, c1, c2, c3) + >>> v_M_tilde + FiberForceVelocityInverseDeGroote2016(fv_M, c0, c1, c2, c3) + + To inspect the actual symbolic expression that this function represents, + we can call the :meth:`~.doit` method on an instance. We'll use the keyword + argument ``evaluate=False`` as this will keep the expression in its + canonical form and won't simplify any constants. + + >>> v_M_tilde.doit(evaluate=False) + (-c2 + sinh((-c3 + fv_M)/c0))/c1 + + The function can also be differentiated. We'll differentiate with respect + to fv_M using the ``diff`` method on an instance with the single positional + argument ``fv_M``. + + >>> v_M_tilde.diff(fv_M) + cosh((-c3 + fv_M)/c0)/(c0*c1) + + References + ========== + + .. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation + of direct collocation optimal control problem formulations for + solving the muscle redundancy problem, Annals of biomedical + engineering, 44(10), (2016) pp. 2922-2936 + + """ + + @classmethod + def with_defaults(cls, fv_M): + r"""Recommended constructor that will use the published constants. + + Explanation + =========== + + Returns a new instance of the inverse muscle fiber force-velocity + function using the four constant values specified in the original + publication. + + These have the values: + + $c_0 = -0.318$ + $c_1 = -8.149$ + $c_2 = -0.374$ + $c_3 = 0.886$ + + Parameters + ========== + + fv_M : Any (sympifiable) + Normalized muscle fiber extension velocity. + + """ + c0 = Float('-0.318') + c1 = Float('-8.149') + c2 = Float('-0.374') + c3 = Float('0.886') + return cls(fv_M, c0, c1, c2, c3) + + @classmethod + def eval(cls, fv_M, c0, c1, c2, c3): + """Evaluation of basic inputs. + + Parameters + ========== + + fv_M : Any (sympifiable) + Normalized muscle fiber force as a function of muscle fiber + extension velocity. + c0 : Any (sympifiable) + The first constant in the characteristic equation. The published + value is ``-0.318``. + c1 : Any (sympifiable) + The second constant in the characteristic equation. The published + value is ``-8.149``. + c2 : Any (sympifiable) + The third constant in the characteristic equation. The published + value is ``-0.374``. + c3 : Any (sympifiable) + The fourth constant in the characteristic equation. The published + value is ``0.886``. + + """ + pass + + def _eval_evalf(self, prec): + """Evaluate the expression numerically using ``evalf``.""" + return self.doit(deep=False, evaluate=False)._eval_evalf(prec) + + def doit(self, deep=True, evaluate=True, **hints): + """Evaluate the expression defining the function. + + Parameters + ========== + + deep : bool + Whether ``doit`` should be recursively called. Default is ``True``. + evaluate : bool. + Whether the SymPy expression should be evaluated as it is + constructed. If ``False``, then no constant folding will be + conducted which will leave the expression in a more numerically- + stable for values of ``fv_M`` that correspond to a sensible + operating range for a musculotendon. Default is ``True``. + **kwargs : dict[str, Any] + Additional keyword argument pairs to be recursively passed to + ``doit``. + + """ + fv_M, *constants = self.args + if deep: + hints['evaluate'] = evaluate + fv_M = fv_M.doit(deep=deep, **hints) + c0, c1, c2, c3 = [c.doit(deep=deep, **hints) for c in constants] + else: + c0, c1, c2, c3 = constants + + if evaluate: + return (sinh((fv_M - c3)/c0) - c2)/c1 + + return (sinh(UnevaluatedExpr(fv_M - c3)/c0) - c2)/c1 + + def fdiff(self, argindex=1): + """Derivative of the function with respect to a single argument. + + Parameters + ========== + + argindex : int + The index of the function's arguments with respect to which the + derivative should be taken. Argument indexes start at ``1``. + Default is ``1``. + + """ + fv_M, c0, c1, c2, c3 = self.args + if argindex == 1: + return cosh((fv_M - c3)/c0)/(c0*c1) + elif argindex == 2: + return (c3 - fv_M)*cosh((fv_M - c3)/c0)/(c0**2*c1) + elif argindex == 3: + return (c2 - sinh((fv_M - c3)/c0))/c1**2 + elif argindex == 4: + return -1/c1 + elif argindex == 5: + return -cosh((fv_M - c3)/c0)/(c0*c1) + + raise ArgumentIndexError(self, argindex) + + def inverse(self, argindex=1): + """Inverse function. + + Parameters + ========== + + argindex : int + Value to start indexing the arguments at. Default is ``1``. + + """ + return FiberForceVelocityDeGroote2016 + + def _latex(self, printer): + """Print a LaTeX representation of the function defining the curve. + + Parameters + ========== + + printer : Printer + The printer to be used to print the LaTeX string representation. + + """ + fv_M = self.args[0] + _fv_M = printer._print(fv_M) + return r'\left( \operatorname{fv}^M \right)^{-1} \left( %s \right)' % _fv_M + + +@dataclass(frozen=True) +class CharacteristicCurveCollection: + """Simple data container to group together related characteristic curves.""" + tendon_force_length: CharacteristicCurveFunction + tendon_force_length_inverse: CharacteristicCurveFunction + fiber_force_length_passive: CharacteristicCurveFunction + fiber_force_length_passive_inverse: CharacteristicCurveFunction + fiber_force_length_active: CharacteristicCurveFunction + fiber_force_velocity: CharacteristicCurveFunction + fiber_force_velocity_inverse: CharacteristicCurveFunction + + def __iter__(self): + """Iterator support for ``CharacteristicCurveCollection``.""" + yield self.tendon_force_length + yield self.tendon_force_length_inverse + yield self.fiber_force_length_passive + yield self.fiber_force_length_passive_inverse + yield self.fiber_force_length_active + yield self.fiber_force_velocity + yield self.fiber_force_velocity_inverse diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/__pycache__/__init__.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3f23c92b62959cb022892de8950c890d83e3d13 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/__pycache__/__init__.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/__pycache__/test_mixin.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/__pycache__/test_mixin.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3dc90ac3a2274a904c4af8bec98f4ee4435314c7 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/__pycache__/test_mixin.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/test_activation.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/test_activation.py new file mode 100644 index 0000000000000000000000000000000000000000..a38742f0d42af48dff95295eae869b2c5ef269de --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/biomechanics/tests/test_activation.py @@ -0,0 +1,348 @@ +"""Tests for the ``sympy.physics.biomechanics.activation.py`` module.""" + +import pytest + +from sympy import Symbol +from sympy.core.numbers import Float, Integer, Rational +from sympy.functions.elementary.hyperbolic import tanh +from sympy.matrices import Matrix +from sympy.matrices.dense import zeros +from sympy.physics.mechanics import dynamicsymbols +from sympy.physics.biomechanics import ( + ActivationBase, + FirstOrderActivationDeGroote2016, + ZerothOrderActivation, +) +from sympy.physics.biomechanics._mixin import _NamedMixin +from sympy.simplify.simplify import simplify + + +class TestZerothOrderActivation: + + @staticmethod + def test_class(): + assert issubclass(ZerothOrderActivation, ActivationBase) + assert issubclass(ZerothOrderActivation, _NamedMixin) + assert ZerothOrderActivation.__name__ == 'ZerothOrderActivation' + + @pytest.fixture(autouse=True) + def _zeroth_order_activation_fixture(self): + self.name = 'name' + self.e = dynamicsymbols('e_name') + self.instance = ZerothOrderActivation(self.name) + + def test_instance(self): + instance = ZerothOrderActivation(self.name) + assert isinstance(instance, ZerothOrderActivation) + + def test_with_defaults(self): + instance = ZerothOrderActivation.with_defaults(self.name) + assert isinstance(instance, ZerothOrderActivation) + assert instance == ZerothOrderActivation(self.name) + + def test_name(self): + assert hasattr(self.instance, 'name') + assert self.instance.name == self.name + + def test_order(self): + assert hasattr(self.instance, 'order') + assert self.instance.order == 0 + + def test_excitation_attribute(self): + assert hasattr(self.instance, 'e') + assert hasattr(self.instance, 'excitation') + e_expected = dynamicsymbols('e_name') + assert self.instance.e == e_expected + assert self.instance.excitation == e_expected + assert self.instance.e is self.instance.excitation + + def test_activation_attribute(self): + assert hasattr(self.instance, 'a') + assert hasattr(self.instance, 'activation') + a_expected = dynamicsymbols('e_name') + assert self.instance.a == a_expected + assert self.instance.activation == a_expected + assert self.instance.a is self.instance.activation is self.instance.e + + def test_state_vars_attribute(self): + assert hasattr(self.instance, 'x') + assert hasattr(self.instance, 'state_vars') + assert self.instance.x == self.instance.state_vars + x_expected = zeros(0, 1) + assert self.instance.x == x_expected + assert self.instance.state_vars == x_expected + assert isinstance(self.instance.x, Matrix) + assert isinstance(self.instance.state_vars, Matrix) + assert self.instance.x.shape == (0, 1) + assert self.instance.state_vars.shape == (0, 1) + + def test_input_vars_attribute(self): + assert hasattr(self.instance, 'r') + assert hasattr(self.instance, 'input_vars') + assert self.instance.r == self.instance.input_vars + r_expected = Matrix([self.e]) + assert self.instance.r == r_expected + assert self.instance.input_vars == r_expected + assert isinstance(self.instance.r, Matrix) + assert isinstance(self.instance.input_vars, Matrix) + assert self.instance.r.shape == (1, 1) + assert self.instance.input_vars.shape == (1, 1) + + def test_constants_attribute(self): + assert hasattr(self.instance, 'p') + assert hasattr(self.instance, 'constants') + assert self.instance.p == self.instance.constants + p_expected = zeros(0, 1) + assert self.instance.p == p_expected + assert self.instance.constants == p_expected + assert isinstance(self.instance.p, Matrix) + assert isinstance(self.instance.constants, Matrix) + assert self.instance.p.shape == (0, 1) + assert self.instance.constants.shape == (0, 1) + + def test_M_attribute(self): + assert hasattr(self.instance, 'M') + M_expected = Matrix([]) + assert self.instance.M == M_expected + assert isinstance(self.instance.M, Matrix) + assert self.instance.M.shape == (0, 0) + + def test_F(self): + assert hasattr(self.instance, 'F') + F_expected = zeros(0, 1) + assert self.instance.F == F_expected + assert isinstance(self.instance.F, Matrix) + assert self.instance.F.shape == (0, 1) + + def test_rhs(self): + assert hasattr(self.instance, 'rhs') + rhs_expected = zeros(0, 1) + rhs = self.instance.rhs() + assert rhs == rhs_expected + assert isinstance(rhs, Matrix) + assert rhs.shape == (0, 1) + + def test_repr(self): + expected = 'ZerothOrderActivation(\'name\')' + assert repr(self.instance) == expected + + +class TestFirstOrderActivationDeGroote2016: + + @staticmethod + def test_class(): + assert issubclass(FirstOrderActivationDeGroote2016, ActivationBase) + assert issubclass(FirstOrderActivationDeGroote2016, _NamedMixin) + assert FirstOrderActivationDeGroote2016.__name__ == 'FirstOrderActivationDeGroote2016' + + @pytest.fixture(autouse=True) + def _first_order_activation_de_groote_2016_fixture(self): + self.name = 'name' + self.e = dynamicsymbols('e_name') + self.a = dynamicsymbols('a_name') + self.tau_a = Symbol('tau_a') + self.tau_d = Symbol('tau_d') + self.b = Symbol('b') + self.instance = FirstOrderActivationDeGroote2016( + self.name, + self.tau_a, + self.tau_d, + self.b, + ) + + def test_instance(self): + instance = FirstOrderActivationDeGroote2016(self.name) + assert isinstance(instance, FirstOrderActivationDeGroote2016) + + def test_with_defaults(self): + instance = FirstOrderActivationDeGroote2016.with_defaults(self.name) + assert isinstance(instance, FirstOrderActivationDeGroote2016) + assert instance.tau_a == Float('0.015') + assert instance.activation_time_constant == Float('0.015') + assert instance.tau_d == Float('0.060') + assert instance.deactivation_time_constant == Float('0.060') + assert instance.b == Float('10.0') + assert instance.smoothing_rate == Float('10.0') + + def test_name(self): + assert hasattr(self.instance, 'name') + assert self.instance.name == self.name + + def test_order(self): + assert hasattr(self.instance, 'order') + assert self.instance.order == 1 + + def test_excitation(self): + assert hasattr(self.instance, 'e') + assert hasattr(self.instance, 'excitation') + e_expected = dynamicsymbols('e_name') + assert self.instance.e == e_expected + assert self.instance.excitation == e_expected + assert self.instance.e is self.instance.excitation + + def test_excitation_is_immutable(self): + with pytest.raises(AttributeError): + self.instance.e = None + with pytest.raises(AttributeError): + self.instance.excitation = None + + def test_activation(self): + assert hasattr(self.instance, 'a') + assert hasattr(self.instance, 'activation') + a_expected = dynamicsymbols('a_name') + assert self.instance.a == a_expected + assert self.instance.activation == a_expected + + def test_activation_is_immutable(self): + with pytest.raises(AttributeError): + self.instance.a = None + with pytest.raises(AttributeError): + self.instance.activation = None + + @pytest.mark.parametrize( + 'tau_a, expected', + [ + (None, Symbol('tau_a_name')), + (Symbol('tau_a'), Symbol('tau_a')), + (Float('0.015'), Float('0.015')), + ] + ) + def test_activation_time_constant(self, tau_a, expected): + instance = FirstOrderActivationDeGroote2016( + 'name', activation_time_constant=tau_a, + ) + assert instance.tau_a == expected + assert instance.activation_time_constant == expected + assert instance.tau_a is instance.activation_time_constant + + def test_activation_time_constant_is_immutable(self): + with pytest.raises(AttributeError): + self.instance.tau_a = None + with pytest.raises(AttributeError): + self.instance.activation_time_constant = None + + @pytest.mark.parametrize( + 'tau_d, expected', + [ + (None, Symbol('tau_d_name')), + (Symbol('tau_d'), Symbol('tau_d')), + (Float('0.060'), Float('0.060')), + ] + ) + def test_deactivation_time_constant(self, tau_d, expected): + instance = FirstOrderActivationDeGroote2016( + 'name', deactivation_time_constant=tau_d, + ) + assert instance.tau_d == expected + assert instance.deactivation_time_constant == expected + assert instance.tau_d is instance.deactivation_time_constant + + def test_deactivation_time_constant_is_immutable(self): + with pytest.raises(AttributeError): + self.instance.tau_d = None + with pytest.raises(AttributeError): + self.instance.deactivation_time_constant = None + + @pytest.mark.parametrize( + 'b, expected', + [ + (None, Symbol('b_name')), + (Symbol('b'), Symbol('b')), + (Integer('10'), Integer('10')), + ] + ) + def test_smoothing_rate(self, b, expected): + instance = FirstOrderActivationDeGroote2016( + 'name', smoothing_rate=b, + ) + assert instance.b == expected + assert instance.smoothing_rate == expected + assert instance.b is instance.smoothing_rate + + def test_smoothing_rate_is_immutable(self): + with pytest.raises(AttributeError): + self.instance.b = None + with pytest.raises(AttributeError): + self.instance.smoothing_rate = None + + def test_state_vars(self): + assert hasattr(self.instance, 'x') + assert hasattr(self.instance, 'state_vars') + assert self.instance.x == self.instance.state_vars + x_expected = Matrix([self.a]) + assert self.instance.x == x_expected + assert self.instance.state_vars == x_expected + assert isinstance(self.instance.x, Matrix) + assert isinstance(self.instance.state_vars, Matrix) + assert self.instance.x.shape == (1, 1) + assert self.instance.state_vars.shape == (1, 1) + + def test_input_vars(self): + assert hasattr(self.instance, 'r') + assert hasattr(self.instance, 'input_vars') + assert self.instance.r == self.instance.input_vars + r_expected = Matrix([self.e]) + assert self.instance.r == r_expected + assert self.instance.input_vars == r_expected + assert isinstance(self.instance.r, Matrix) + assert isinstance(self.instance.input_vars, Matrix) + assert self.instance.r.shape == (1, 1) + assert self.instance.input_vars.shape == (1, 1) + + def test_constants(self): + assert hasattr(self.instance, 'p') + assert hasattr(self.instance, 'constants') + assert self.instance.p == self.instance.constants + p_expected = Matrix([self.tau_a, self.tau_d, self.b]) + assert self.instance.p == p_expected + assert self.instance.constants == p_expected + assert isinstance(self.instance.p, Matrix) + assert isinstance(self.instance.constants, Matrix) + assert self.instance.p.shape == (3, 1) + assert self.instance.constants.shape == (3, 1) + + def test_M(self): + assert hasattr(self.instance, 'M') + M_expected = Matrix([1]) + assert self.instance.M == M_expected + assert isinstance(self.instance.M, Matrix) + assert self.instance.M.shape == (1, 1) + + def test_F(self): + assert hasattr(self.instance, 'F') + da_expr = ( + ((1/(self.tau_a*(Rational(1, 2) + Rational(3, 2)*self.a))) + *(Rational(1, 2) + Rational(1, 2)*tanh(self.b*(self.e - self.a))) + + ((Rational(1, 2) + Rational(3, 2)*self.a)/self.tau_d) + *(Rational(1, 2) - Rational(1, 2)*tanh(self.b*(self.e - self.a)))) + *(self.e - self.a) + ) + F_expected = Matrix([da_expr]) + assert self.instance.F == F_expected + assert isinstance(self.instance.F, Matrix) + assert self.instance.F.shape == (1, 1) + + def test_rhs(self): + assert hasattr(self.instance, 'rhs') + da_expr = ( + ((1/(self.tau_a*(Rational(1, 2) + Rational(3, 2)*self.a))) + *(Rational(1, 2) + Rational(1, 2)*tanh(self.b*(self.e - self.a))) + + ((Rational(1, 2) + Rational(3, 2)*self.a)/self.tau_d) + *(Rational(1, 2) - Rational(1, 2)*tanh(self.b*(self.e - self.a)))) + *(self.e - self.a) + ) + rhs_expected = Matrix([da_expr]) + rhs = self.instance.rhs() + assert rhs == rhs_expected + assert isinstance(rhs, Matrix) + assert rhs.shape == (1, 1) + assert simplify(self.instance.M.solve(self.instance.F) - rhs) == zeros(1) + + def test_repr(self): + expected = ( + 'FirstOrderActivationDeGroote2016(\'name\', ' + 'activation_time_constant=tau_a, ' + 'deactivation_time_constant=tau_d, ' + 'smoothing_rate=b)' + ) + assert repr(self.instance) == expected diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/control/__init__.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/control/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fb8c13ff147b3603466c8c4b2d9c8c0b25e3b360 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/control/__init__.py @@ -0,0 +1,16 @@ +from .lti import (TransferFunction, Series, MIMOSeries, Parallel, MIMOParallel, + Feedback, MIMOFeedback, TransferFunctionMatrix, StateSpace, gbt, bilinear, forward_diff, + backward_diff, phase_margin, gain_margin) +from .control_plots import (pole_zero_numerical_data, pole_zero_plot, step_response_numerical_data, + step_response_plot, impulse_response_numerical_data, impulse_response_plot, ramp_response_numerical_data, + ramp_response_plot, bode_magnitude_numerical_data, bode_phase_numerical_data, bode_magnitude_plot, + bode_phase_plot, bode_plot) + +__all__ = ['TransferFunction', 'Series', 'MIMOSeries', 'Parallel', + 'MIMOParallel', 'Feedback', 'MIMOFeedback', 'TransferFunctionMatrix', 'StateSpace', + 'gbt', 'bilinear', 'forward_diff', 'backward_diff', 'phase_margin', 'gain_margin', + 'pole_zero_numerical_data', 'pole_zero_plot', 'step_response_numerical_data', + 'step_response_plot', 'impulse_response_numerical_data', 'impulse_response_plot', + 'ramp_response_numerical_data', 'ramp_response_plot', + 'bode_magnitude_numerical_data', 'bode_phase_numerical_data', + 'bode_magnitude_plot', 'bode_phase_plot', 'bode_plot'] diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/control/control_plots.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/control/control_plots.py new file mode 100644 index 0000000000000000000000000000000000000000..3742de329e61a84ff604accaced369261bc4befe --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/control/control_plots.py @@ -0,0 +1,978 @@ +from sympy.core.numbers import I, pi +from sympy.functions.elementary.exponential import (exp, log) +from sympy.polys.partfrac import apart +from sympy.core.symbol import Dummy +from sympy.external import import_module +from sympy.functions import arg, Abs +from sympy.integrals.laplace import _fast_inverse_laplace +from sympy.physics.control.lti import SISOLinearTimeInvariant +from sympy.plotting.series import LineOver1DRangeSeries +from sympy.polys.polytools import Poly +from sympy.printing.latex import latex + +__all__ = ['pole_zero_numerical_data', 'pole_zero_plot', + 'step_response_numerical_data', 'step_response_plot', + 'impulse_response_numerical_data', 'impulse_response_plot', + 'ramp_response_numerical_data', 'ramp_response_plot', + 'bode_magnitude_numerical_data', 'bode_phase_numerical_data', + 'bode_magnitude_plot', 'bode_phase_plot', 'bode_plot'] + +matplotlib = import_module( + 'matplotlib', import_kwargs={'fromlist': ['pyplot']}, + catch=(RuntimeError,)) + +numpy = import_module('numpy') + +if matplotlib: + plt = matplotlib.pyplot + +if numpy: + np = numpy # Matplotlib already has numpy as a compulsory dependency. No need to install it separately. + + +def _check_system(system): + """Function to check whether the dynamical system passed for plots is + compatible or not.""" + if not isinstance(system, SISOLinearTimeInvariant): + raise NotImplementedError("Only SISO LTI systems are currently supported.") + sys = system.to_expr() + len_free_symbols = len(sys.free_symbols) + if len_free_symbols > 1: + raise ValueError("Extra degree of freedom found. Make sure" + " that there are no free symbols in the dynamical system other" + " than the variable of Laplace transform.") + if sys.has(exp): + # Should test that exp is not part of a constant, in which case + # no exception is required, compare exp(s) with s*exp(1) + raise NotImplementedError("Time delay terms are not supported.") + + +def pole_zero_numerical_data(system): + """ + Returns the numerical data of poles and zeros of the system. + It is internally used by ``pole_zero_plot`` to get the data + for plotting poles and zeros. Users can use this data to further + analyse the dynamics of the system or plot using a different + backend/plotting-module. + + Parameters + ========== + + system : SISOLinearTimeInvariant + The system for which the pole-zero data is to be computed. + + Returns + ======= + + tuple : (zeros, poles) + zeros = Zeros of the system. NumPy array of complex numbers. + poles = Poles of the system. NumPy array of complex numbers. + + Raises + ====== + + NotImplementedError + When a SISO LTI system is not passed. + + When time delay terms are present in the system. + + ValueError + When more than one free symbol is present in the system. + The only variable in the transfer function should be + the variable of the Laplace transform. + + Examples + ======== + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import pole_zero_numerical_data + >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s) + >>> pole_zero_numerical_data(tf1) # doctest: +SKIP + ([-0.+1.j 0.-1.j], [-2. +0.j -0.5+0.8660254j -0.5-0.8660254j -1. +0.j ]) + + See Also + ======== + + pole_zero_plot + + """ + _check_system(system) + system = system.doit() # Get the equivalent TransferFunction object. + + num_poly = Poly(system.num, system.var).all_coeffs() + den_poly = Poly(system.den, system.var).all_coeffs() + + num_poly = np.array(num_poly, dtype=np.complex128) + den_poly = np.array(den_poly, dtype=np.complex128) + + zeros = np.roots(num_poly) + poles = np.roots(den_poly) + + return zeros, poles + + +def pole_zero_plot(system, pole_color='blue', pole_markersize=10, + zero_color='orange', zero_markersize=7, grid=True, show_axes=True, + show=True, **kwargs): + r""" + Returns the Pole-Zero plot (also known as PZ Plot or PZ Map) of a system. + + A Pole-Zero plot is a graphical representation of a system's poles and + zeros. It is plotted on a complex plane, with circular markers representing + the system's zeros and 'x' shaped markers representing the system's poles. + + Parameters + ========== + + system : SISOLinearTimeInvariant type systems + The system for which the pole-zero plot is to be computed. + pole_color : str, tuple, optional + The color of the pole points on the plot. Default color + is blue. The color can be provided as a matplotlib color string, + or a 3-tuple of floats each in the 0-1 range. + pole_markersize : Number, optional + The size of the markers used to mark the poles in the plot. + Default pole markersize is 10. + zero_color : str, tuple, optional + The color of the zero points on the plot. Default color + is orange. The color can be provided as a matplotlib color string, + or a 3-tuple of floats each in the 0-1 range. + zero_markersize : Number, optional + The size of the markers used to mark the zeros in the plot. + Default zero markersize is 7. + grid : boolean, optional + If ``True``, the plot will have a grid. Defaults to True. + show_axes : boolean, optional + If ``True``, the coordinate axes will be shown. Defaults to False. + show : boolean, optional + If ``True``, the plot will be displayed otherwise + the equivalent matplotlib ``plot`` object will be returned. + Defaults to True. + + Examples + ======== + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import pole_zero_plot + >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s) + >>> pole_zero_plot(tf1) # doctest: +SKIP + + See Also + ======== + + pole_zero_numerical_data + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Pole%E2%80%93zero_plot + + """ + zeros, poles = pole_zero_numerical_data(system) + + zero_real = np.real(zeros) + zero_imag = np.imag(zeros) + + pole_real = np.real(poles) + pole_imag = np.imag(poles) + + plt.plot(pole_real, pole_imag, 'x', mfc='none', + markersize=pole_markersize, color=pole_color) + plt.plot(zero_real, zero_imag, 'o', markersize=zero_markersize, + color=zero_color) + plt.xlabel('Real Axis') + plt.ylabel('Imaginary Axis') + plt.title(f'Poles and Zeros of ${latex(system)}$', pad=20) + + if grid: + plt.grid() + if show_axes: + plt.axhline(0, color='black') + plt.axvline(0, color='black') + if show: + plt.show() + return + + return plt + + +def step_response_numerical_data(system, prec=8, lower_limit=0, + upper_limit=10, **kwargs): + """ + Returns the numerical values of the points in the step response plot + of a SISO continuous-time system. By default, adaptive sampling + is used. If the user wants to instead get an uniformly + sampled response, then ``adaptive`` kwarg should be passed ``False`` + and ``n`` must be passed as additional kwargs. + Refer to the parameters of class :class:`sympy.plotting.series.LineOver1DRangeSeries` + for more details. + + Parameters + ========== + + system : SISOLinearTimeInvariant + The system for which the unit step response data is to be computed. + prec : int, optional + The decimal point precision for the point coordinate values. + Defaults to 8. + lower_limit : Number, optional + The lower limit of the plot range. Defaults to 0. + upper_limit : Number, optional + The upper limit of the plot range. Defaults to 10. + kwargs : + Additional keyword arguments are passed to the underlying + :class:`sympy.plotting.series.LineOver1DRangeSeries` class. + + Returns + ======= + + tuple : (x, y) + x = Time-axis values of the points in the step response. NumPy array. + y = Amplitude-axis values of the points in the step response. NumPy array. + + Raises + ====== + + NotImplementedError + When a SISO LTI system is not passed. + + When time delay terms are present in the system. + + ValueError + When more than one free symbol is present in the system. + The only variable in the transfer function should be + the variable of the Laplace transform. + + When ``lower_limit`` parameter is less than 0. + + Examples + ======== + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import step_response_numerical_data + >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s) + >>> step_response_numerical_data(tf1) # doctest: +SKIP + ([0.0, 0.025413462339411542, 0.0484508722725343, ... , 9.670250533855183, 9.844291913708725, 10.0], + [0.0, 0.023844582399907256, 0.042894276802320226, ..., 6.828770759094287e-12, 6.456457160755703e-12]) + + See Also + ======== + + step_response_plot + + """ + if lower_limit < 0: + raise ValueError("Lower limit of time must be greater " + "than or equal to zero.") + _check_system(system) + _x = Dummy("x") + expr = system.to_expr()/(system.var) + expr = apart(expr, system.var, full=True) + _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec) + return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit), + **kwargs).get_points() + + +def step_response_plot(system, color='b', prec=8, lower_limit=0, + upper_limit=10, show_axes=False, grid=True, show=True, **kwargs): + r""" + Returns the unit step response of a continuous-time system. It is + the response of the system when the input signal is a step function. + + Parameters + ========== + + system : SISOLinearTimeInvariant type + The LTI SISO system for which the Step Response is to be computed. + color : str, tuple, optional + The color of the line. Default is Blue. + show : boolean, optional + If ``True``, the plot will be displayed otherwise + the equivalent matplotlib ``plot`` object will be returned. + Defaults to True. + lower_limit : Number, optional + The lower limit of the plot range. Defaults to 0. + upper_limit : Number, optional + The upper limit of the plot range. Defaults to 10. + prec : int, optional + The decimal point precision for the point coordinate values. + Defaults to 8. + show_axes : boolean, optional + If ``True``, the coordinate axes will be shown. Defaults to False. + grid : boolean, optional + If ``True``, the plot will have a grid. Defaults to True. + + Examples + ======== + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import step_response_plot + >>> tf1 = TransferFunction(8*s**2 + 18*s + 32, s**3 + 6*s**2 + 14*s + 24, s) + >>> step_response_plot(tf1) # doctest: +SKIP + + See Also + ======== + + impulse_response_plot, ramp_response_plot + + References + ========== + + .. [1] https://www.mathworks.com/help/control/ref/lti.step.html + + """ + x, y = step_response_numerical_data(system, prec=prec, + lower_limit=lower_limit, upper_limit=upper_limit, **kwargs) + plt.plot(x, y, color=color) + plt.xlabel('Time (s)') + plt.ylabel('Amplitude') + plt.title(f'Unit Step Response of ${latex(system)}$', pad=20) + + if grid: + plt.grid() + if show_axes: + plt.axhline(0, color='black') + plt.axvline(0, color='black') + if show: + plt.show() + return + + return plt + + +def impulse_response_numerical_data(system, prec=8, lower_limit=0, + upper_limit=10, **kwargs): + """ + Returns the numerical values of the points in the impulse response plot + of a SISO continuous-time system. By default, adaptive sampling + is used. If the user wants to instead get an uniformly + sampled response, then ``adaptive`` kwarg should be passed ``False`` + and ``n`` must be passed as additional kwargs. + Refer to the parameters of class :class:`sympy.plotting.series.LineOver1DRangeSeries` + for more details. + + Parameters + ========== + + system : SISOLinearTimeInvariant + The system for which the impulse response data is to be computed. + prec : int, optional + The decimal point precision for the point coordinate values. + Defaults to 8. + lower_limit : Number, optional + The lower limit of the plot range. Defaults to 0. + upper_limit : Number, optional + The upper limit of the plot range. Defaults to 10. + kwargs : + Additional keyword arguments are passed to the underlying + :class:`sympy.plotting.series.LineOver1DRangeSeries` class. + + Returns + ======= + + tuple : (x, y) + x = Time-axis values of the points in the impulse response. NumPy array. + y = Amplitude-axis values of the points in the impulse response. NumPy array. + + Raises + ====== + + NotImplementedError + When a SISO LTI system is not passed. + + When time delay terms are present in the system. + + ValueError + When more than one free symbol is present in the system. + The only variable in the transfer function should be + the variable of the Laplace transform. + + When ``lower_limit`` parameter is less than 0. + + Examples + ======== + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import impulse_response_numerical_data + >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s) + >>> impulse_response_numerical_data(tf1) # doctest: +SKIP + ([0.0, 0.06616480200395854,... , 9.854500743565858, 10.0], + [0.9999999799999999, 0.7042848373025861,...,7.170748906965121e-13, -5.1901263495547205e-12]) + + See Also + ======== + + impulse_response_plot + + """ + if lower_limit < 0: + raise ValueError("Lower limit of time must be greater " + "than or equal to zero.") + _check_system(system) + _x = Dummy("x") + expr = system.to_expr() + expr = apart(expr, system.var, full=True) + _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec) + return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit), + **kwargs).get_points() + + +def impulse_response_plot(system, color='b', prec=8, lower_limit=0, + upper_limit=10, show_axes=False, grid=True, show=True, **kwargs): + r""" + Returns the unit impulse response (Input is the Dirac-Delta Function) of a + continuous-time system. + + Parameters + ========== + + system : SISOLinearTimeInvariant type + The LTI SISO system for which the Impulse Response is to be computed. + color : str, tuple, optional + The color of the line. Default is Blue. + show : boolean, optional + If ``True``, the plot will be displayed otherwise + the equivalent matplotlib ``plot`` object will be returned. + Defaults to True. + lower_limit : Number, optional + The lower limit of the plot range. Defaults to 0. + upper_limit : Number, optional + The upper limit of the plot range. Defaults to 10. + prec : int, optional + The decimal point precision for the point coordinate values. + Defaults to 8. + show_axes : boolean, optional + If ``True``, the coordinate axes will be shown. Defaults to False. + grid : boolean, optional + If ``True``, the plot will have a grid. Defaults to True. + + Examples + ======== + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import impulse_response_plot + >>> tf1 = TransferFunction(8*s**2 + 18*s + 32, s**3 + 6*s**2 + 14*s + 24, s) + >>> impulse_response_plot(tf1) # doctest: +SKIP + + See Also + ======== + + step_response_plot, ramp_response_plot + + References + ========== + + .. [1] https://www.mathworks.com/help/control/ref/dynamicsystem.impulse.html + + """ + x, y = impulse_response_numerical_data(system, prec=prec, + lower_limit=lower_limit, upper_limit=upper_limit, **kwargs) + plt.plot(x, y, color=color) + plt.xlabel('Time (s)') + plt.ylabel('Amplitude') + plt.title(f'Impulse Response of ${latex(system)}$', pad=20) + + if grid: + plt.grid() + if show_axes: + plt.axhline(0, color='black') + plt.axvline(0, color='black') + if show: + plt.show() + return + + return plt + + +def ramp_response_numerical_data(system, slope=1, prec=8, + lower_limit=0, upper_limit=10, **kwargs): + """ + Returns the numerical values of the points in the ramp response plot + of a SISO continuous-time system. By default, adaptive sampling + is used. If the user wants to instead get an uniformly + sampled response, then ``adaptive`` kwarg should be passed ``False`` + and ``n`` must be passed as additional kwargs. + Refer to the parameters of class :class:`sympy.plotting.series.LineOver1DRangeSeries` + for more details. + + Parameters + ========== + + system : SISOLinearTimeInvariant + The system for which the ramp response data is to be computed. + slope : Number, optional + The slope of the input ramp function. Defaults to 1. + prec : int, optional + The decimal point precision for the point coordinate values. + Defaults to 8. + lower_limit : Number, optional + The lower limit of the plot range. Defaults to 0. + upper_limit : Number, optional + The upper limit of the plot range. Defaults to 10. + kwargs : + Additional keyword arguments are passed to the underlying + :class:`sympy.plotting.series.LineOver1DRangeSeries` class. + + Returns + ======= + + tuple : (x, y) + x = Time-axis values of the points in the ramp response plot. NumPy array. + y = Amplitude-axis values of the points in the ramp response plot. NumPy array. + + Raises + ====== + + NotImplementedError + When a SISO LTI system is not passed. + + When time delay terms are present in the system. + + ValueError + When more than one free symbol is present in the system. + The only variable in the transfer function should be + the variable of the Laplace transform. + + When ``lower_limit`` parameter is less than 0. + + When ``slope`` is negative. + + Examples + ======== + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import ramp_response_numerical_data + >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s) + >>> ramp_response_numerical_data(tf1) # doctest: +SKIP + (([0.0, 0.12166980856813935,..., 9.861246379582118, 10.0], + [1.4504508011325967e-09, 0.006046440489058766,..., 0.12499999999568202, 0.12499999999661349])) + + See Also + ======== + + ramp_response_plot + + """ + if slope < 0: + raise ValueError("Slope must be greater than or equal" + " to zero.") + if lower_limit < 0: + raise ValueError("Lower limit of time must be greater " + "than or equal to zero.") + _check_system(system) + _x = Dummy("x") + expr = (slope*system.to_expr())/((system.var)**2) + expr = apart(expr, system.var, full=True) + _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec) + return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit), + **kwargs).get_points() + + +def ramp_response_plot(system, slope=1, color='b', prec=8, lower_limit=0, + upper_limit=10, show_axes=False, grid=True, show=True, **kwargs): + r""" + Returns the ramp response of a continuous-time system. + + Ramp function is defined as the straight line + passing through origin ($f(x) = mx$). The slope of + the ramp function can be varied by the user and + the default value is 1. + + Parameters + ========== + + system : SISOLinearTimeInvariant type + The LTI SISO system for which the Ramp Response is to be computed. + slope : Number, optional + The slope of the input ramp function. Defaults to 1. + color : str, tuple, optional + The color of the line. Default is Blue. + show : boolean, optional + If ``True``, the plot will be displayed otherwise + the equivalent matplotlib ``plot`` object will be returned. + Defaults to True. + lower_limit : Number, optional + The lower limit of the plot range. Defaults to 0. + upper_limit : Number, optional + The upper limit of the plot range. Defaults to 10. + prec : int, optional + The decimal point precision for the point coordinate values. + Defaults to 8. + show_axes : boolean, optional + If ``True``, the coordinate axes will be shown. Defaults to False. + grid : boolean, optional + If ``True``, the plot will have a grid. Defaults to True. + + Examples + ======== + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import ramp_response_plot + >>> tf1 = TransferFunction(s, (s+4)*(s+8), s) + >>> ramp_response_plot(tf1, upper_limit=2) # doctest: +SKIP + + See Also + ======== + + step_response_plot, impulse_response_plot + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Ramp_function + + """ + x, y = ramp_response_numerical_data(system, slope=slope, prec=prec, + lower_limit=lower_limit, upper_limit=upper_limit, **kwargs) + plt.plot(x, y, color=color) + plt.xlabel('Time (s)') + plt.ylabel('Amplitude') + plt.title(f'Ramp Response of ${latex(system)}$ [Slope = {slope}]', pad=20) + + if grid: + plt.grid() + if show_axes: + plt.axhline(0, color='black') + plt.axvline(0, color='black') + if show: + plt.show() + return + + return plt + + +def bode_magnitude_numerical_data(system, initial_exp=-5, final_exp=5, freq_unit='rad/sec', **kwargs): + """ + Returns the numerical data of the Bode magnitude plot of the system. + It is internally used by ``bode_magnitude_plot`` to get the data + for plotting Bode magnitude plot. Users can use this data to further + analyse the dynamics of the system or plot using a different + backend/plotting-module. + + Parameters + ========== + + system : SISOLinearTimeInvariant + The system for which the data is to be computed. + initial_exp : Number, optional + The initial exponent of 10 of the semilog plot. Defaults to -5. + final_exp : Number, optional + The final exponent of 10 of the semilog plot. Defaults to 5. + freq_unit : string, optional + User can choose between ``'rad/sec'`` (radians/second) and ``'Hz'`` (Hertz) as frequency units. + + Returns + ======= + + tuple : (x, y) + x = x-axis values of the Bode magnitude plot. + y = y-axis values of the Bode magnitude plot. + + Raises + ====== + + NotImplementedError + When a SISO LTI system is not passed. + + When time delay terms are present in the system. + + ValueError + When more than one free symbol is present in the system. + The only variable in the transfer function should be + the variable of the Laplace transform. + + When incorrect frequency units are given as input. + + Examples + ======== + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import bode_magnitude_numerical_data + >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s) + >>> bode_magnitude_numerical_data(tf1) # doctest: +SKIP + ([1e-05, 1.5148378120533502e-05,..., 68437.36188804005, 100000.0], + [-6.020599914256786, -6.0205999155219505,..., -193.4117304087953, -200.00000000260573]) + + See Also + ======== + + bode_magnitude_plot, bode_phase_numerical_data + + """ + _check_system(system) + expr = system.to_expr() + freq_units = ('rad/sec', 'Hz') + if freq_unit not in freq_units: + raise ValueError('Only "rad/sec" and "Hz" are accepted frequency units.') + + _w = Dummy("w", real=True) + if freq_unit == 'Hz': + repl = I*_w*2*pi + else: + repl = I*_w + w_expr = expr.subs({system.var: repl}) + + mag = 20*log(Abs(w_expr), 10) + + x, y = LineOver1DRangeSeries(mag, + (_w, 10**initial_exp, 10**final_exp), xscale='log', **kwargs).get_points() + + return x, y + + +def bode_magnitude_plot(system, initial_exp=-5, final_exp=5, + color='b', show_axes=False, grid=True, show=True, freq_unit='rad/sec', **kwargs): + r""" + Returns the Bode magnitude plot of a continuous-time system. + + See ``bode_plot`` for all the parameters. + """ + x, y = bode_magnitude_numerical_data(system, initial_exp=initial_exp, + final_exp=final_exp, freq_unit=freq_unit) + plt.plot(x, y, color=color, **kwargs) + plt.xscale('log') + + + plt.xlabel('Frequency (%s) [Log Scale]' % freq_unit) + plt.ylabel('Magnitude (dB)') + plt.title(f'Bode Plot (Magnitude) of ${latex(system)}$', pad=20) + + if grid: + plt.grid(True) + if show_axes: + plt.axhline(0, color='black') + plt.axvline(0, color='black') + if show: + plt.show() + return + + return plt + + +def bode_phase_numerical_data(system, initial_exp=-5, final_exp=5, freq_unit='rad/sec', phase_unit='rad', phase_unwrap = True, **kwargs): + """ + Returns the numerical data of the Bode phase plot of the system. + It is internally used by ``bode_phase_plot`` to get the data + for plotting Bode phase plot. Users can use this data to further + analyse the dynamics of the system or plot using a different + backend/plotting-module. + + Parameters + ========== + + system : SISOLinearTimeInvariant + The system for which the Bode phase plot data is to be computed. + initial_exp : Number, optional + The initial exponent of 10 of the semilog plot. Defaults to -5. + final_exp : Number, optional + The final exponent of 10 of the semilog plot. Defaults to 5. + freq_unit : string, optional + User can choose between ``'rad/sec'`` (radians/second) and '``'Hz'`` (Hertz) as frequency units. + phase_unit : string, optional + User can choose between ``'rad'`` (radians) and ``'deg'`` (degree) as phase units. + phase_unwrap : bool, optional + Set to ``True`` by default. + + Returns + ======= + + tuple : (x, y) + x = x-axis values of the Bode phase plot. + y = y-axis values of the Bode phase plot. + + Raises + ====== + + NotImplementedError + When a SISO LTI system is not passed. + + When time delay terms are present in the system. + + ValueError + When more than one free symbol is present in the system. + The only variable in the transfer function should be + the variable of the Laplace transform. + + When incorrect frequency or phase units are given as input. + + Examples + ======== + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import bode_phase_numerical_data + >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s) + >>> bode_phase_numerical_data(tf1) # doctest: +SKIP + ([1e-05, 1.4472354033813751e-05, 2.035581932165858e-05,..., 47577.3248186011, 67884.09326036123, 100000.0], + [-2.5000000000291665e-05, -3.6180885085e-05, -5.08895483066e-05,...,-3.1415085799262523, -3.14155265358979]) + + See Also + ======== + + bode_magnitude_plot, bode_phase_numerical_data + + """ + _check_system(system) + expr = system.to_expr() + freq_units = ('rad/sec', 'Hz') + phase_units = ('rad', 'deg') + if freq_unit not in freq_units: + raise ValueError('Only "rad/sec" and "Hz" are accepted frequency units.') + if phase_unit not in phase_units: + raise ValueError('Only "rad" and "deg" are accepted phase units.') + + _w = Dummy("w", real=True) + if freq_unit == 'Hz': + repl = I*_w*2*pi + else: + repl = I*_w + w_expr = expr.subs({system.var: repl}) + + if phase_unit == 'deg': + phase = arg(w_expr)*180/pi + else: + phase = arg(w_expr) + + x, y = LineOver1DRangeSeries(phase, + (_w, 10**initial_exp, 10**final_exp), xscale='log', **kwargs).get_points() + + half = None + if phase_unwrap: + if(phase_unit == 'rad'): + half = pi + elif(phase_unit == 'deg'): + half = 180 + if half: + unit = 2*half + for i in range(1, len(y)): + diff = y[i] - y[i - 1] + if diff > half: # Jump from -half to half + y[i] = (y[i] - unit) + elif diff < -half: # Jump from half to -half + y[i] = (y[i] + unit) + + return x, y + + +def bode_phase_plot(system, initial_exp=-5, final_exp=5, + color='b', show_axes=False, grid=True, show=True, freq_unit='rad/sec', phase_unit='rad', phase_unwrap=True, **kwargs): + r""" + Returns the Bode phase plot of a continuous-time system. + + See ``bode_plot`` for all the parameters. + """ + x, y = bode_phase_numerical_data(system, initial_exp=initial_exp, + final_exp=final_exp, freq_unit=freq_unit, phase_unit=phase_unit, phase_unwrap=phase_unwrap) + plt.plot(x, y, color=color, **kwargs) + plt.xscale('log') + + plt.xlabel('Frequency (%s) [Log Scale]' % freq_unit) + plt.ylabel('Phase (%s)' % phase_unit) + plt.title(f'Bode Plot (Phase) of ${latex(system)}$', pad=20) + + if grid: + plt.grid(True) + if show_axes: + plt.axhline(0, color='black') + plt.axvline(0, color='black') + if show: + plt.show() + return + + return plt + + +def bode_plot(system, initial_exp=-5, final_exp=5, + grid=True, show_axes=False, show=True, freq_unit='rad/sec', phase_unit='rad', phase_unwrap=True, **kwargs): + r""" + Returns the Bode phase and magnitude plots of a continuous-time system. + + Parameters + ========== + + system : SISOLinearTimeInvariant type + The LTI SISO system for which the Bode Plot is to be computed. + initial_exp : Number, optional + The initial exponent of 10 of the semilog plot. Defaults to -5. + final_exp : Number, optional + The final exponent of 10 of the semilog plot. Defaults to 5. + show : boolean, optional + If ``True``, the plot will be displayed otherwise + the equivalent matplotlib ``plot`` object will be returned. + Defaults to True. + prec : int, optional + The decimal point precision for the point coordinate values. + Defaults to 8. + grid : boolean, optional + If ``True``, the plot will have a grid. Defaults to True. + show_axes : boolean, optional + If ``True``, the coordinate axes will be shown. Defaults to False. + freq_unit : string, optional + User can choose between ``'rad/sec'`` (radians/second) and ``'Hz'`` (Hertz) as frequency units. + phase_unit : string, optional + User can choose between ``'rad'`` (radians) and ``'deg'`` (degree) as phase units. + + Examples + ======== + + .. plot:: + :context: close-figs + :format: doctest + :include-source: True + + >>> from sympy.abc import s + >>> from sympy.physics.control.lti import TransferFunction + >>> from sympy.physics.control.control_plots import bode_plot + >>> tf1 = TransferFunction(1*s**2 + 0.1*s + 7.5, 1*s**4 + 0.12*s**3 + 9*s**2, s) + >>> bode_plot(tf1, initial_exp=0.2, final_exp=0.7) # doctest: +SKIP + + See Also + ======== + + bode_magnitude_plot, bode_phase_plot + + """ + plt.subplot(211) + mag = bode_magnitude_plot(system, initial_exp=initial_exp, final_exp=final_exp, + show=False, grid=grid, show_axes=show_axes, + freq_unit=freq_unit, **kwargs) + mag.title(f'Bode Plot of ${latex(system)}$', pad=20) + mag.xlabel(None) + plt.subplot(212) + bode_phase_plot(system, initial_exp=initial_exp, final_exp=final_exp, + show=False, grid=grid, show_axes=show_axes, freq_unit=freq_unit, phase_unit=phase_unit, phase_unwrap=phase_unwrap, **kwargs).title(None) + + if show: + plt.show() + return + + return plt diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/tests/test_physics_matrices.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/tests/test_physics_matrices.py new file mode 100644 index 0000000000000000000000000000000000000000..14fa47668d0760826e0354c8cafae787a24256eb --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/tests/test_physics_matrices.py @@ -0,0 +1,84 @@ +from sympy.physics.matrices import msigma, mgamma, minkowski_tensor, pat_matrix, mdft +from sympy.core.numbers import (I, Rational) +from sympy.core.singleton import S +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.matrices.dense import (Matrix, eye, zeros) +from sympy.testing.pytest import warns_deprecated_sympy + + +def test_parallel_axis_theorem(): + # This tests the parallel axis theorem matrix by comparing to test + # matrices. + + # First case, 1 in all directions. + mat1 = Matrix(((2, -1, -1), (-1, 2, -1), (-1, -1, 2))) + assert pat_matrix(1, 1, 1, 1) == mat1 + assert pat_matrix(2, 1, 1, 1) == 2*mat1 + + # Second case, 1 in x, 0 in all others + mat2 = Matrix(((0, 0, 0), (0, 1, 0), (0, 0, 1))) + assert pat_matrix(1, 1, 0, 0) == mat2 + assert pat_matrix(2, 1, 0, 0) == 2*mat2 + + # Third case, 1 in y, 0 in all others + mat3 = Matrix(((1, 0, 0), (0, 0, 0), (0, 0, 1))) + assert pat_matrix(1, 0, 1, 0) == mat3 + assert pat_matrix(2, 0, 1, 0) == 2*mat3 + + # Fourth case, 1 in z, 0 in all others + mat4 = Matrix(((1, 0, 0), (0, 1, 0), (0, 0, 0))) + assert pat_matrix(1, 0, 0, 1) == mat4 + assert pat_matrix(2, 0, 0, 1) == 2*mat4 + + +def test_Pauli(): + #this and the following test are testing both Pauli and Dirac matrices + #and also that the general Matrix class works correctly in a real world + #situation + sigma1 = msigma(1) + sigma2 = msigma(2) + sigma3 = msigma(3) + + assert sigma1 == sigma1 + assert sigma1 != sigma2 + + # sigma*I -> I*sigma (see #354) + assert sigma1*sigma2 == sigma3*I + assert sigma3*sigma1 == sigma2*I + assert sigma2*sigma3 == sigma1*I + + assert sigma1*sigma1 == eye(2) + assert sigma2*sigma2 == eye(2) + assert sigma3*sigma3 == eye(2) + + assert sigma1*2*sigma1 == 2*eye(2) + assert sigma1*sigma3*sigma1 == -sigma3 + + +def test_Dirac(): + gamma0 = mgamma(0) + gamma1 = mgamma(1) + gamma2 = mgamma(2) + gamma3 = mgamma(3) + gamma5 = mgamma(5) + + # gamma*I -> I*gamma (see #354) + assert gamma5 == gamma0 * gamma1 * gamma2 * gamma3 * I + assert gamma1 * gamma2 + gamma2 * gamma1 == zeros(4) + assert gamma0 * gamma0 == eye(4) * minkowski_tensor[0, 0] + assert gamma2 * gamma2 != eye(4) * minkowski_tensor[0, 0] + assert gamma2 * gamma2 == eye(4) * minkowski_tensor[2, 2] + + assert mgamma(5, True) == \ + mgamma(0, True)*mgamma(1, True)*mgamma(2, True)*mgamma(3, True)*I + +def test_mdft(): + with warns_deprecated_sympy(): + assert mdft(1) == Matrix([[1]]) + with warns_deprecated_sympy(): + assert mdft(2) == 1/sqrt(2)*Matrix([[1,1],[1,-1]]) + with warns_deprecated_sympy(): + assert mdft(4) == Matrix([[S.Half, S.Half, S.Half, S.Half], + [S.Half, -I/2, Rational(-1,2), I/2], + [S.Half, Rational(-1,2), S.Half, Rational(-1,2)], + [S.Half, I/2, Rational(-1,2), -I/2]]) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__init__.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bf17c7f3051b03d9c0fc794d9d79885c94cc878e --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__init__.py @@ -0,0 +1,453 @@ +# isort:skip_file +""" +Dimensional analysis and unit systems. + +This module defines dimension/unit systems and physical quantities. It is +based on a group-theoretical construction where dimensions are represented as +vectors (coefficients being the exponents), and units are defined as a dimension +to which we added a scale. + +Quantities are built from a factor and a unit, and are the basic objects that +one will use when doing computations. + +All objects except systems and prefixes can be used in SymPy expressions. +Note that as part of a CAS, various objects do not combine automatically +under operations. + +Details about the implementation can be found in the documentation, and we +will not repeat all the explanations we gave there concerning our approach. +Ideas about future developments can be found on the `Github wiki +`_, and you should consult +this page if you are willing to help. + +Useful functions: + +- ``find_unit``: easily lookup pre-defined units. +- ``convert_to(expr, newunit)``: converts an expression into the same + expression expressed in another unit. + +""" + +from .dimensions import Dimension, DimensionSystem +from .unitsystem import UnitSystem +from .util import convert_to +from .quantities import Quantity + +from .definitions.dimension_definitions import ( + amount_of_substance, acceleration, action, area, + capacitance, charge, conductance, current, energy, + force, frequency, impedance, inductance, length, + luminous_intensity, magnetic_density, + magnetic_flux, mass, momentum, power, pressure, temperature, time, + velocity, voltage, volume +) + +Unit = Quantity + +speed = velocity +luminosity = luminous_intensity +magnetic_flux_density = magnetic_density +amount = amount_of_substance + +from .prefixes import ( + # 10-power based: + yotta, + zetta, + exa, + peta, + tera, + giga, + mega, + kilo, + hecto, + deca, + deci, + centi, + milli, + micro, + nano, + pico, + femto, + atto, + zepto, + yocto, + # 2-power based: + kibi, + mebi, + gibi, + tebi, + pebi, + exbi, +) + +from .definitions import ( + percent, percents, + permille, + rad, radian, radians, + deg, degree, degrees, + sr, steradian, steradians, + mil, angular_mil, angular_mils, + m, meter, meters, + kg, kilogram, kilograms, + s, second, seconds, + A, ampere, amperes, + K, kelvin, kelvins, + mol, mole, moles, + cd, candela, candelas, + g, gram, grams, + mg, milligram, milligrams, + ug, microgram, micrograms, + t, tonne, metric_ton, + newton, newtons, N, + joule, joules, J, + watt, watts, W, + pascal, pascals, Pa, pa, + hertz, hz, Hz, + coulomb, coulombs, C, + volt, volts, v, V, + ohm, ohms, + siemens, S, mho, mhos, + farad, farads, F, + henry, henrys, H, + tesla, teslas, T, + weber, webers, Wb, wb, + optical_power, dioptre, D, + lux, lx, + katal, kat, + gray, Gy, + becquerel, Bq, + km, kilometer, kilometers, + dm, decimeter, decimeters, + cm, centimeter, centimeters, + mm, millimeter, millimeters, + um, micrometer, micrometers, micron, microns, + nm, nanometer, nanometers, + pm, picometer, picometers, + ft, foot, feet, + inch, inches, + yd, yard, yards, + mi, mile, miles, + nmi, nautical_mile, nautical_miles, + angstrom, angstroms, + ha, hectare, + l, L, liter, liters, + dl, dL, deciliter, deciliters, + cl, cL, centiliter, centiliters, + ml, mL, milliliter, milliliters, + ms, millisecond, milliseconds, + us, microsecond, microseconds, + ns, nanosecond, nanoseconds, + ps, picosecond, picoseconds, + minute, minutes, + h, hour, hours, + day, days, + anomalistic_year, anomalistic_years, + sidereal_year, sidereal_years, + tropical_year, tropical_years, + common_year, common_years, + julian_year, julian_years, + draconic_year, draconic_years, + gaussian_year, gaussian_years, + full_moon_cycle, full_moon_cycles, + year, years, + G, gravitational_constant, + c, speed_of_light, + elementary_charge, + hbar, + planck, + eV, electronvolt, electronvolts, + avogadro_number, + avogadro, avogadro_constant, + boltzmann, boltzmann_constant, + stefan, stefan_boltzmann_constant, + R, molar_gas_constant, + faraday_constant, + josephson_constant, + von_klitzing_constant, + Da, dalton, amu, amus, atomic_mass_unit, atomic_mass_constant, + me, electron_rest_mass, + gee, gees, acceleration_due_to_gravity, + u0, magnetic_constant, vacuum_permeability, + e0, electric_constant, vacuum_permittivity, + Z0, vacuum_impedance, + coulomb_constant, electric_force_constant, + atmosphere, atmospheres, atm, + kPa, + bar, bars, + pound, pounds, + psi, + dHg0, + mmHg, torr, + mmu, mmus, milli_mass_unit, + quart, quarts, + ly, lightyear, lightyears, + au, astronomical_unit, astronomical_units, + planck_mass, + planck_time, + planck_temperature, + planck_length, + planck_charge, + planck_area, + planck_volume, + planck_momentum, + planck_energy, + planck_force, + planck_power, + planck_density, + planck_energy_density, + planck_intensity, + planck_angular_frequency, + planck_pressure, + planck_current, + planck_voltage, + planck_impedance, + planck_acceleration, + bit, bits, + byte, + kibibyte, kibibytes, + mebibyte, mebibytes, + gibibyte, gibibytes, + tebibyte, tebibytes, + pebibyte, pebibytes, + exbibyte, exbibytes, +) + +from .systems import ( + mks, mksa, si +) + + +def find_unit(quantity, unit_system="SI"): + """ + Return a list of matching units or dimension names. + + - If ``quantity`` is a string -- units/dimensions containing the string + `quantity`. + - If ``quantity`` is a unit or dimension -- units having matching base + units or dimensions. + + Examples + ======== + + >>> from sympy.physics import units as u + >>> u.find_unit('charge') + ['C', 'coulomb', 'coulombs', 'planck_charge', 'elementary_charge'] + >>> u.find_unit(u.charge) + ['C', 'coulomb', 'coulombs', 'planck_charge', 'elementary_charge'] + >>> u.find_unit("ampere") + ['ampere', 'amperes'] + >>> u.find_unit('angstrom') + ['angstrom', 'angstroms'] + >>> u.find_unit('volt') + ['volt', 'volts', 'electronvolt', 'electronvolts', 'planck_voltage'] + >>> u.find_unit(u.inch**3)[:9] + ['L', 'l', 'cL', 'cl', 'dL', 'dl', 'mL', 'ml', 'liter'] + """ + unit_system = UnitSystem.get_unit_system(unit_system) + + import sympy.physics.units as u + rv = [] + if isinstance(quantity, str): + rv = [i for i in dir(u) if quantity in i and isinstance(getattr(u, i), Quantity)] + dim = getattr(u, quantity) + if isinstance(dim, Dimension): + rv.extend(find_unit(dim)) + else: + for i in sorted(dir(u)): + other = getattr(u, i) + if not isinstance(other, Quantity): + continue + if isinstance(quantity, Quantity): + if quantity.dimension == other.dimension: + rv.append(str(i)) + elif isinstance(quantity, Dimension): + if other.dimension == quantity: + rv.append(str(i)) + elif other.dimension == Dimension(unit_system.get_dimensional_expr(quantity)): + rv.append(str(i)) + return sorted(set(rv), key=lambda x: (len(x), x)) + +# NOTE: the old units module had additional variables: +# 'density', 'illuminance', 'resistance'. +# They were not dimensions, but units (old Unit class). + +__all__ = [ + 'Dimension', 'DimensionSystem', + 'UnitSystem', + 'convert_to', + 'Quantity', + + 'amount_of_substance', 'acceleration', 'action', 'area', + 'capacitance', 'charge', 'conductance', 'current', 'energy', + 'force', 'frequency', 'impedance', 'inductance', 'length', + 'luminous_intensity', 'magnetic_density', + 'magnetic_flux', 'mass', 'momentum', 'power', 'pressure', 'temperature', 'time', + 'velocity', 'voltage', 'volume', + + 'Unit', + + 'speed', + 'luminosity', + 'magnetic_flux_density', + 'amount', + + 'yotta', + 'zetta', + 'exa', + 'peta', + 'tera', + 'giga', + 'mega', + 'kilo', + 'hecto', + 'deca', + 'deci', + 'centi', + 'milli', + 'micro', + 'nano', + 'pico', + 'femto', + 'atto', + 'zepto', + 'yocto', + + 'kibi', + 'mebi', + 'gibi', + 'tebi', + 'pebi', + 'exbi', + + 'percent', 'percents', + 'permille', + 'rad', 'radian', 'radians', + 'deg', 'degree', 'degrees', + 'sr', 'steradian', 'steradians', + 'mil', 'angular_mil', 'angular_mils', + 'm', 'meter', 'meters', + 'kg', 'kilogram', 'kilograms', + 's', 'second', 'seconds', + 'A', 'ampere', 'amperes', + 'K', 'kelvin', 'kelvins', + 'mol', 'mole', 'moles', + 'cd', 'candela', 'candelas', + 'g', 'gram', 'grams', + 'mg', 'milligram', 'milligrams', + 'ug', 'microgram', 'micrograms', + 't', 'tonne', 'metric_ton', + 'newton', 'newtons', 'N', + 'joule', 'joules', 'J', + 'watt', 'watts', 'W', + 'pascal', 'pascals', 'Pa', 'pa', + 'hertz', 'hz', 'Hz', + 'coulomb', 'coulombs', 'C', + 'volt', 'volts', 'v', 'V', + 'ohm', 'ohms', + 'siemens', 'S', 'mho', 'mhos', + 'farad', 'farads', 'F', + 'henry', 'henrys', 'H', + 'tesla', 'teslas', 'T', + 'weber', 'webers', 'Wb', 'wb', + 'optical_power', 'dioptre', 'D', + 'lux', 'lx', + 'katal', 'kat', + 'gray', 'Gy', + 'becquerel', 'Bq', + 'km', 'kilometer', 'kilometers', + 'dm', 'decimeter', 'decimeters', + 'cm', 'centimeter', 'centimeters', + 'mm', 'millimeter', 'millimeters', + 'um', 'micrometer', 'micrometers', 'micron', 'microns', + 'nm', 'nanometer', 'nanometers', + 'pm', 'picometer', 'picometers', + 'ft', 'foot', 'feet', + 'inch', 'inches', + 'yd', 'yard', 'yards', + 'mi', 'mile', 'miles', + 'nmi', 'nautical_mile', 'nautical_miles', + 'angstrom', 'angstroms', + 'ha', 'hectare', + 'l', 'L', 'liter', 'liters', + 'dl', 'dL', 'deciliter', 'deciliters', + 'cl', 'cL', 'centiliter', 'centiliters', + 'ml', 'mL', 'milliliter', 'milliliters', + 'ms', 'millisecond', 'milliseconds', + 'us', 'microsecond', 'microseconds', + 'ns', 'nanosecond', 'nanoseconds', + 'ps', 'picosecond', 'picoseconds', + 'minute', 'minutes', + 'h', 'hour', 'hours', + 'day', 'days', + 'anomalistic_year', 'anomalistic_years', + 'sidereal_year', 'sidereal_years', + 'tropical_year', 'tropical_years', + 'common_year', 'common_years', + 'julian_year', 'julian_years', + 'draconic_year', 'draconic_years', + 'gaussian_year', 'gaussian_years', + 'full_moon_cycle', 'full_moon_cycles', + 'year', 'years', + 'G', 'gravitational_constant', + 'c', 'speed_of_light', + 'elementary_charge', + 'hbar', + 'planck', + 'eV', 'electronvolt', 'electronvolts', + 'avogadro_number', + 'avogadro', 'avogadro_constant', + 'boltzmann', 'boltzmann_constant', + 'stefan', 'stefan_boltzmann_constant', + 'R', 'molar_gas_constant', + 'faraday_constant', + 'josephson_constant', + 'von_klitzing_constant', + 'Da', 'dalton', 'amu', 'amus', 'atomic_mass_unit', 'atomic_mass_constant', + 'me', 'electron_rest_mass', + 'gee', 'gees', 'acceleration_due_to_gravity', + 'u0', 'magnetic_constant', 'vacuum_permeability', + 'e0', 'electric_constant', 'vacuum_permittivity', + 'Z0', 'vacuum_impedance', + 'coulomb_constant', 'electric_force_constant', + 'atmosphere', 'atmospheres', 'atm', + 'kPa', + 'bar', 'bars', + 'pound', 'pounds', + 'psi', + 'dHg0', + 'mmHg', 'torr', + 'mmu', 'mmus', 'milli_mass_unit', + 'quart', 'quarts', + 'ly', 'lightyear', 'lightyears', + 'au', 'astronomical_unit', 'astronomical_units', + 'planck_mass', + 'planck_time', + 'planck_temperature', + 'planck_length', + 'planck_charge', + 'planck_area', + 'planck_volume', + 'planck_momentum', + 'planck_energy', + 'planck_force', + 'planck_power', + 'planck_density', + 'planck_energy_density', + 'planck_intensity', + 'planck_angular_frequency', + 'planck_pressure', + 'planck_current', + 'planck_voltage', + 'planck_impedance', + 'planck_acceleration', + 'bit', 'bits', + 'byte', + 'kibibyte', 'kibibytes', + 'mebibyte', 'mebibytes', + 'gibibyte', 'gibibytes', + 'tebibyte', 'tebibytes', + 'pebibyte', 'pebibytes', + 'exbibyte', 'exbibytes', + + 'mks', 'mksa', 'si', +] diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/__init__.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72320497edd4932d854b11e0f0496efb14a8d15e Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/__init__.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/prefixes.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/prefixes.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..014c5331d787425b0ad009d96e7d80d37e8f1c27 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/prefixes.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/quantities.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/quantities.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d9e626fe9c7c5f9c72c3bd536509549e974a1ce Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/__pycache__/quantities.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/definitions/__init__.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/definitions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..759889695d38c6e78237cc64974da3ecca6425cd --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/definitions/__init__.py @@ -0,0 +1,265 @@ +from .unit_definitions import ( + percent, percents, + permille, + rad, radian, radians, + deg, degree, degrees, + sr, steradian, steradians, + mil, angular_mil, angular_mils, + m, meter, meters, + kg, kilogram, kilograms, + s, second, seconds, + A, ampere, amperes, + K, kelvin, kelvins, + mol, mole, moles, + cd, candela, candelas, + g, gram, grams, + mg, milligram, milligrams, + ug, microgram, micrograms, + t, tonne, metric_ton, + newton, newtons, N, + joule, joules, J, + watt, watts, W, + pascal, pascals, Pa, pa, + hertz, hz, Hz, + coulomb, coulombs, C, + volt, volts, v, V, + ohm, ohms, + siemens, S, mho, mhos, + farad, farads, F, + henry, henrys, H, + tesla, teslas, T, + weber, webers, Wb, wb, + optical_power, dioptre, D, + lux, lx, + katal, kat, + gray, Gy, + becquerel, Bq, + km, kilometer, kilometers, + dm, decimeter, decimeters, + cm, centimeter, centimeters, + mm, millimeter, millimeters, + um, micrometer, micrometers, micron, microns, + nm, nanometer, nanometers, + pm, picometer, picometers, + ft, foot, feet, + inch, inches, + yd, yard, yards, + mi, mile, miles, + nmi, nautical_mile, nautical_miles, + ha, hectare, + l, L, liter, liters, + dl, dL, deciliter, deciliters, + cl, cL, centiliter, centiliters, + ml, mL, milliliter, milliliters, + ms, millisecond, milliseconds, + us, microsecond, microseconds, + ns, nanosecond, nanoseconds, + ps, picosecond, picoseconds, + minute, minutes, + h, hour, hours, + day, days, + anomalistic_year, anomalistic_years, + sidereal_year, sidereal_years, + tropical_year, tropical_years, + common_year, common_years, + julian_year, julian_years, + draconic_year, draconic_years, + gaussian_year, gaussian_years, + full_moon_cycle, full_moon_cycles, + year, years, + G, gravitational_constant, + c, speed_of_light, + elementary_charge, + hbar, + planck, + eV, electronvolt, electronvolts, + avogadro_number, + avogadro, avogadro_constant, + boltzmann, boltzmann_constant, + stefan, stefan_boltzmann_constant, + R, molar_gas_constant, + faraday_constant, + josephson_constant, + von_klitzing_constant, + Da, dalton, amu, amus, atomic_mass_unit, atomic_mass_constant, + me, electron_rest_mass, + gee, gees, acceleration_due_to_gravity, + u0, magnetic_constant, vacuum_permeability, + e0, electric_constant, vacuum_permittivity, + Z0, vacuum_impedance, + coulomb_constant, coulombs_constant, electric_force_constant, + atmosphere, atmospheres, atm, + kPa, kilopascal, + bar, bars, + pound, pounds, + psi, + dHg0, + mmHg, torr, + mmu, mmus, milli_mass_unit, + quart, quarts, + angstrom, angstroms, + ly, lightyear, lightyears, + au, astronomical_unit, astronomical_units, + planck_mass, + planck_time, + planck_temperature, + planck_length, + planck_charge, + planck_area, + planck_volume, + planck_momentum, + planck_energy, + planck_force, + planck_power, + planck_density, + planck_energy_density, + planck_intensity, + planck_angular_frequency, + planck_pressure, + planck_current, + planck_voltage, + planck_impedance, + planck_acceleration, + bit, bits, + byte, + kibibyte, kibibytes, + mebibyte, mebibytes, + gibibyte, gibibytes, + tebibyte, tebibytes, + pebibyte, pebibytes, + exbibyte, exbibytes, + curie, rutherford +) + +__all__ = [ + 'percent', 'percents', + 'permille', + 'rad', 'radian', 'radians', + 'deg', 'degree', 'degrees', + 'sr', 'steradian', 'steradians', + 'mil', 'angular_mil', 'angular_mils', + 'm', 'meter', 'meters', + 'kg', 'kilogram', 'kilograms', + 's', 'second', 'seconds', + 'A', 'ampere', 'amperes', + 'K', 'kelvin', 'kelvins', + 'mol', 'mole', 'moles', + 'cd', 'candela', 'candelas', + 'g', 'gram', 'grams', + 'mg', 'milligram', 'milligrams', + 'ug', 'microgram', 'micrograms', + 't', 'tonne', 'metric_ton', + 'newton', 'newtons', 'N', + 'joule', 'joules', 'J', + 'watt', 'watts', 'W', + 'pascal', 'pascals', 'Pa', 'pa', + 'hertz', 'hz', 'Hz', + 'coulomb', 'coulombs', 'C', + 'volt', 'volts', 'v', 'V', + 'ohm', 'ohms', + 'siemens', 'S', 'mho', 'mhos', + 'farad', 'farads', 'F', + 'henry', 'henrys', 'H', + 'tesla', 'teslas', 'T', + 'weber', 'webers', 'Wb', 'wb', + 'optical_power', 'dioptre', 'D', + 'lux', 'lx', + 'katal', 'kat', + 'gray', 'Gy', + 'becquerel', 'Bq', + 'km', 'kilometer', 'kilometers', + 'dm', 'decimeter', 'decimeters', + 'cm', 'centimeter', 'centimeters', + 'mm', 'millimeter', 'millimeters', + 'um', 'micrometer', 'micrometers', 'micron', 'microns', + 'nm', 'nanometer', 'nanometers', + 'pm', 'picometer', 'picometers', + 'ft', 'foot', 'feet', + 'inch', 'inches', + 'yd', 'yard', 'yards', + 'mi', 'mile', 'miles', + 'nmi', 'nautical_mile', 'nautical_miles', + 'ha', 'hectare', + 'l', 'L', 'liter', 'liters', + 'dl', 'dL', 'deciliter', 'deciliters', + 'cl', 'cL', 'centiliter', 'centiliters', + 'ml', 'mL', 'milliliter', 'milliliters', + 'ms', 'millisecond', 'milliseconds', + 'us', 'microsecond', 'microseconds', + 'ns', 'nanosecond', 'nanoseconds', + 'ps', 'picosecond', 'picoseconds', + 'minute', 'minutes', + 'h', 'hour', 'hours', + 'day', 'days', + 'anomalistic_year', 'anomalistic_years', + 'sidereal_year', 'sidereal_years', + 'tropical_year', 'tropical_years', + 'common_year', 'common_years', + 'julian_year', 'julian_years', + 'draconic_year', 'draconic_years', + 'gaussian_year', 'gaussian_years', + 'full_moon_cycle', 'full_moon_cycles', + 'year', 'years', + 'G', 'gravitational_constant', + 'c', 'speed_of_light', + 'elementary_charge', + 'hbar', + 'planck', + 'eV', 'electronvolt', 'electronvolts', + 'avogadro_number', + 'avogadro', 'avogadro_constant', + 'boltzmann', 'boltzmann_constant', + 'stefan', 'stefan_boltzmann_constant', + 'R', 'molar_gas_constant', + 'faraday_constant', + 'josephson_constant', + 'von_klitzing_constant', + 'Da', 'dalton', 'amu', 'amus', 'atomic_mass_unit', 'atomic_mass_constant', + 'me', 'electron_rest_mass', + 'gee', 'gees', 'acceleration_due_to_gravity', + 'u0', 'magnetic_constant', 'vacuum_permeability', + 'e0', 'electric_constant', 'vacuum_permittivity', + 'Z0', 'vacuum_impedance', + 'coulomb_constant', 'coulombs_constant', 'electric_force_constant', + 'atmosphere', 'atmospheres', 'atm', + 'kPa', 'kilopascal', + 'bar', 'bars', + 'pound', 'pounds', + 'psi', + 'dHg0', + 'mmHg', 'torr', + 'mmu', 'mmus', 'milli_mass_unit', + 'quart', 'quarts', + 'angstrom', 'angstroms', + 'ly', 'lightyear', 'lightyears', + 'au', 'astronomical_unit', 'astronomical_units', + 'planck_mass', + 'planck_time', + 'planck_temperature', + 'planck_length', + 'planck_charge', + 'planck_area', + 'planck_volume', + 'planck_momentum', + 'planck_energy', + 'planck_force', + 'planck_power', + 'planck_density', + 'planck_energy_density', + 'planck_intensity', + 'planck_angular_frequency', + 'planck_pressure', + 'planck_current', + 'planck_voltage', + 'planck_impedance', + 'planck_acceleration', + 'bit', 'bits', + 'byte', + 'kibibyte', 'kibibytes', + 'mebibyte', 'mebibytes', + 'gibibyte', 'gibibytes', + 'tebibyte', 'tebibytes', + 'pebibyte', 'pebibytes', + 'exbibyte', 'exbibytes', + 'curie', 'rutherford', +] diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/dimensions.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/dimensions.py new file mode 100644 index 0000000000000000000000000000000000000000..951f8ab17a58606e74f1c14ffa312fedbeebb4b6 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/dimensions.py @@ -0,0 +1,590 @@ +""" +Definition of physical dimensions. + +Unit systems will be constructed on top of these dimensions. + +Most of the examples in the doc use MKS system and are presented from the +computer point of view: from a human point, adding length to time is not legal +in MKS but it is in natural system; for a computer in natural system there is +no time dimension (but a velocity dimension instead) - in the basis - so the +question of adding time to length has no meaning. +""" + +from __future__ import annotations + +import collections +from functools import reduce + +from sympy.core.basic import Basic +from sympy.core.containers import (Dict, Tuple) +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.matrices.dense import Matrix +from sympy.functions.elementary.trigonometric import TrigonometricFunction +from sympy.core.expr import Expr +from sympy.core.power import Pow + + +class _QuantityMapper: + + _quantity_scale_factors_global: dict[Expr, Expr] = {} + _quantity_dimensional_equivalence_map_global: dict[Expr, Expr] = {} + _quantity_dimension_global: dict[Expr, Expr] = {} + + def __init__(self, *args, **kwargs): + self._quantity_dimension_map = {} + self._quantity_scale_factors = {} + + def set_quantity_dimension(self, quantity, dimension): + """ + Set the dimension for the quantity in a unit system. + + If this relation is valid in every unit system, use + ``quantity.set_global_dimension(dimension)`` instead. + """ + from sympy.physics.units import Quantity + dimension = sympify(dimension) + if not isinstance(dimension, Dimension): + if dimension == 1: + dimension = Dimension(1) + else: + raise ValueError("expected dimension or 1") + elif isinstance(dimension, Quantity): + dimension = self.get_quantity_dimension(dimension) + self._quantity_dimension_map[quantity] = dimension + + def set_quantity_scale_factor(self, quantity, scale_factor): + """ + Set the scale factor of a quantity relative to another quantity. + + It should be used only once per quantity to just one other quantity, + the algorithm will then be able to compute the scale factors to all + other quantities. + + In case the scale factor is valid in every unit system, please use + ``quantity.set_global_relative_scale_factor(scale_factor)`` instead. + """ + from sympy.physics.units import Quantity + from sympy.physics.units.prefixes import Prefix + scale_factor = sympify(scale_factor) + # replace all prefixes by their ratio to canonical units: + scale_factor = scale_factor.replace( + lambda x: isinstance(x, Prefix), + lambda x: x.scale_factor + ) + # replace all quantities by their ratio to canonical units: + scale_factor = scale_factor.replace( + lambda x: isinstance(x, Quantity), + lambda x: self.get_quantity_scale_factor(x) + ) + self._quantity_scale_factors[quantity] = scale_factor + + def get_quantity_dimension(self, unit): + from sympy.physics.units import Quantity + # First look-up the local dimension map, then the global one: + if unit in self._quantity_dimension_map: + return self._quantity_dimension_map[unit] + if unit in self._quantity_dimension_global: + return self._quantity_dimension_global[unit] + if unit in self._quantity_dimensional_equivalence_map_global: + dep_unit = self._quantity_dimensional_equivalence_map_global[unit] + if isinstance(dep_unit, Quantity): + return self.get_quantity_dimension(dep_unit) + else: + return Dimension(self.get_dimensional_expr(dep_unit)) + if isinstance(unit, Quantity): + return Dimension(unit.name) + else: + return Dimension(1) + + def get_quantity_scale_factor(self, unit): + if unit in self._quantity_scale_factors: + return self._quantity_scale_factors[unit] + if unit in self._quantity_scale_factors_global: + mul_factor, other_unit = self._quantity_scale_factors_global[unit] + return mul_factor*self.get_quantity_scale_factor(other_unit) + return S.One + + +class Dimension(Expr): + """ + This class represent the dimension of a physical quantities. + + The ``Dimension`` constructor takes as parameters a name and an optional + symbol. + + For example, in classical mechanics we know that time is different from + temperature and dimensions make this difference (but they do not provide + any measure of these quantites. + + >>> from sympy.physics.units import Dimension + >>> length = Dimension('length') + >>> length + Dimension(length) + >>> time = Dimension('time') + >>> time + Dimension(time) + + Dimensions can be composed using multiplication, division and + exponentiation (by a number) to give new dimensions. Addition and + subtraction is defined only when the two objects are the same dimension. + + >>> velocity = length / time + >>> velocity + Dimension(length/time) + + It is possible to use a dimension system object to get the dimensionsal + dependencies of a dimension, for example the dimension system used by the + SI units convention can be used: + + >>> from sympy.physics.units.systems.si import dimsys_SI + >>> dimsys_SI.get_dimensional_dependencies(velocity) + {Dimension(length, L): 1, Dimension(time, T): -1} + >>> length + length + Dimension(length) + >>> l2 = length**2 + >>> l2 + Dimension(length**2) + >>> dimsys_SI.get_dimensional_dependencies(l2) + {Dimension(length, L): 2} + + """ + + _op_priority = 13.0 + + # XXX: This doesn't seem to be used anywhere... + _dimensional_dependencies = {} # type: ignore + + is_commutative = True + is_number = False + # make sqrt(M**2) --> M + is_positive = True + is_real = True + + def __new__(cls, name, symbol=None): + + if isinstance(name, str): + name = Symbol(name) + else: + name = sympify(name) + + if not isinstance(name, Expr): + raise TypeError("Dimension name needs to be a valid math expression") + + if isinstance(symbol, str): + symbol = Symbol(symbol) + elif symbol is not None: + assert isinstance(symbol, Symbol) + + obj = Expr.__new__(cls, name) + + obj._name = name + obj._symbol = symbol + return obj + + @property + def name(self): + return self._name + + @property + def symbol(self): + return self._symbol + + def __str__(self): + """ + Display the string representation of the dimension. + """ + if self.symbol is None: + return "Dimension(%s)" % (self.name) + else: + return "Dimension(%s, %s)" % (self.name, self.symbol) + + def __repr__(self): + return self.__str__() + + def __neg__(self): + return self + + def __add__(self, other): + from sympy.physics.units.quantities import Quantity + other = sympify(other) + if isinstance(other, Basic): + if other.has(Quantity): + raise TypeError("cannot sum dimension and quantity") + if isinstance(other, Dimension) and self == other: + return self + return super().__add__(other) + return self + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + # there is no notion of ordering (or magnitude) among dimension, + # subtraction is equivalent to addition when the operation is legal + return self + other + + def __rsub__(self, other): + # there is no notion of ordering (or magnitude) among dimension, + # subtraction is equivalent to addition when the operation is legal + return self + other + + def __pow__(self, other): + return self._eval_power(other) + + def _eval_power(self, other): + other = sympify(other) + return Dimension(self.name**other) + + def __mul__(self, other): + from sympy.physics.units.quantities import Quantity + if isinstance(other, Basic): + if other.has(Quantity): + raise TypeError("cannot sum dimension and quantity") + if isinstance(other, Dimension): + return Dimension(self.name*other.name) + if not other.free_symbols: # other.is_number cannot be used + return self + return super().__mul__(other) + return self + + def __rmul__(self, other): + return self.__mul__(other) + + def __truediv__(self, other): + return self*Pow(other, -1) + + def __rtruediv__(self, other): + return other * pow(self, -1) + + @classmethod + def _from_dimensional_dependencies(cls, dependencies): + return reduce(lambda x, y: x * y, ( + d**e for d, e in dependencies.items() + ), 1) + + def has_integer_powers(self, dim_sys): + """ + Check if the dimension object has only integer powers. + + All the dimension powers should be integers, but rational powers may + appear in intermediate steps. This method may be used to check that the + final result is well-defined. + """ + + return all(dpow.is_Integer for dpow in dim_sys.get_dimensional_dependencies(self).values()) + + +# Create dimensions according to the base units in MKSA. +# For other unit systems, they can be derived by transforming the base +# dimensional dependency dictionary. + + +class DimensionSystem(Basic, _QuantityMapper): + r""" + DimensionSystem represents a coherent set of dimensions. + + The constructor takes three parameters: + + - base dimensions; + - derived dimensions: these are defined in terms of the base dimensions + (for example velocity is defined from the division of length by time); + - dependency of dimensions: how the derived dimensions depend + on the base dimensions. + + Optionally either the ``derived_dims`` or the ``dimensional_dependencies`` + may be omitted. + """ + + def __new__(cls, base_dims, derived_dims=(), dimensional_dependencies={}): + dimensional_dependencies = dict(dimensional_dependencies) + + def parse_dim(dim): + if isinstance(dim, str): + dim = Dimension(Symbol(dim)) + elif isinstance(dim, Dimension): + pass + elif isinstance(dim, Symbol): + dim = Dimension(dim) + else: + raise TypeError("%s wrong type" % dim) + return dim + + base_dims = [parse_dim(i) for i in base_dims] + derived_dims = [parse_dim(i) for i in derived_dims] + + for dim in base_dims: + if (dim in dimensional_dependencies + and (len(dimensional_dependencies[dim]) != 1 or + dimensional_dependencies[dim].get(dim, None) != 1)): + raise IndexError("Repeated value in base dimensions") + dimensional_dependencies[dim] = Dict({dim: 1}) + + def parse_dim_name(dim): + if isinstance(dim, Dimension): + return dim + elif isinstance(dim, str): + return Dimension(Symbol(dim)) + elif isinstance(dim, Symbol): + return Dimension(dim) + else: + raise TypeError("unrecognized type %s for %s" % (type(dim), dim)) + + for dim in dimensional_dependencies.keys(): + dim = parse_dim(dim) + if (dim not in derived_dims) and (dim not in base_dims): + derived_dims.append(dim) + + def parse_dict(d): + return Dict({parse_dim_name(i): j for i, j in d.items()}) + + # Make sure everything is a SymPy type: + dimensional_dependencies = {parse_dim_name(i): parse_dict(j) for i, j in + dimensional_dependencies.items()} + + for dim in derived_dims: + if dim in base_dims: + raise ValueError("Dimension %s both in base and derived" % dim) + if dim not in dimensional_dependencies: + # TODO: should this raise a warning? + dimensional_dependencies[dim] = Dict({dim: 1}) + + base_dims.sort(key=default_sort_key) + derived_dims.sort(key=default_sort_key) + + base_dims = Tuple(*base_dims) + derived_dims = Tuple(*derived_dims) + dimensional_dependencies = Dict({i: Dict(j) for i, j in dimensional_dependencies.items()}) + obj = Basic.__new__(cls, base_dims, derived_dims, dimensional_dependencies) + return obj + + @property + def base_dims(self): + return self.args[0] + + @property + def derived_dims(self): + return self.args[1] + + @property + def dimensional_dependencies(self): + return self.args[2] + + def _get_dimensional_dependencies_for_name(self, dimension): + if isinstance(dimension, str): + dimension = Dimension(Symbol(dimension)) + elif not isinstance(dimension, Dimension): + dimension = Dimension(dimension) + + if dimension.name.is_Symbol: + # Dimensions not included in the dependencies are considered + # as base dimensions: + return dict(self.dimensional_dependencies.get(dimension, {dimension: 1})) + + if dimension.name.is_number or dimension.name.is_NumberSymbol: + return {} + + get_for_name = self._get_dimensional_dependencies_for_name + + if dimension.name.is_Mul: + ret = collections.defaultdict(int) + dicts = [get_for_name(i) for i in dimension.name.args] + for d in dicts: + for k, v in d.items(): + ret[k] += v + return {k: v for (k, v) in ret.items() if v != 0} + + if dimension.name.is_Add: + dicts = [get_for_name(i) for i in dimension.name.args] + if all(d == dicts[0] for d in dicts[1:]): + return dicts[0] + raise TypeError("Only equivalent dimensions can be added or subtracted.") + + if dimension.name.is_Pow: + dim_base = get_for_name(dimension.name.base) + dim_exp = get_for_name(dimension.name.exp) + if dim_exp == {} or dimension.name.exp.is_Symbol: + return {k: v * dimension.name.exp for (k, v) in dim_base.items()} + else: + raise TypeError("The exponent for the power operator must be a Symbol or dimensionless.") + + if dimension.name.is_Function: + args = (Dimension._from_dimensional_dependencies( + get_for_name(arg)) for arg in dimension.name.args) + result = dimension.name.func(*args) + + dicts = [get_for_name(i) for i in dimension.name.args] + + if isinstance(result, Dimension): + return self.get_dimensional_dependencies(result) + elif result.func == dimension.name.func: + if isinstance(dimension.name, TrigonometricFunction): + if dicts[0] in ({}, {Dimension('angle'): 1}): + return {} + else: + raise TypeError("The input argument for the function {} must be dimensionless or have dimensions of angle.".format(dimension.func)) + else: + if all(item == {} for item in dicts): + return {} + else: + raise TypeError("The input arguments for the function {} must be dimensionless.".format(dimension.func)) + else: + return get_for_name(result) + + raise TypeError("Type {} not implemented for get_dimensional_dependencies".format(type(dimension.name))) + + def get_dimensional_dependencies(self, name, mark_dimensionless=False): + dimdep = self._get_dimensional_dependencies_for_name(name) + if mark_dimensionless and dimdep == {}: + return {Dimension(1): 1} + return dict(dimdep.items()) + + def equivalent_dims(self, dim1, dim2): + deps1 = self.get_dimensional_dependencies(dim1) + deps2 = self.get_dimensional_dependencies(dim2) + return deps1 == deps2 + + def extend(self, new_base_dims, new_derived_dims=(), new_dim_deps=None): + deps = dict(self.dimensional_dependencies) + if new_dim_deps: + deps.update(new_dim_deps) + + new_dim_sys = DimensionSystem( + tuple(self.base_dims) + tuple(new_base_dims), + tuple(self.derived_dims) + tuple(new_derived_dims), + deps + ) + new_dim_sys._quantity_dimension_map.update(self._quantity_dimension_map) + new_dim_sys._quantity_scale_factors.update(self._quantity_scale_factors) + return new_dim_sys + + def is_dimensionless(self, dimension): + """ + Check if the dimension object really has a dimension. + + A dimension should have at least one component with non-zero power. + """ + if dimension.name == 1: + return True + return self.get_dimensional_dependencies(dimension) == {} + + @property + def list_can_dims(self): + """ + Useless method, kept for compatibility with previous versions. + + DO NOT USE. + + List all canonical dimension names. + """ + dimset = set() + for i in self.base_dims: + dimset.update(set(self.get_dimensional_dependencies(i).keys())) + return tuple(sorted(dimset, key=str)) + + @property + def inv_can_transf_matrix(self): + """ + Useless method, kept for compatibility with previous versions. + + DO NOT USE. + + Compute the inverse transformation matrix from the base to the + canonical dimension basis. + + It corresponds to the matrix where columns are the vector of base + dimensions in canonical basis. + + This matrix will almost never be used because dimensions are always + defined with respect to the canonical basis, so no work has to be done + to get them in this basis. Nonetheless if this matrix is not square + (or not invertible) it means that we have chosen a bad basis. + """ + matrix = reduce(lambda x, y: x.row_join(y), + [self.dim_can_vector(d) for d in self.base_dims]) + return matrix + + @property + def can_transf_matrix(self): + """ + Useless method, kept for compatibility with previous versions. + + DO NOT USE. + + Return the canonical transformation matrix from the canonical to the + base dimension basis. + + It is the inverse of the matrix computed with inv_can_transf_matrix(). + """ + + #TODO: the inversion will fail if the system is inconsistent, for + # example if the matrix is not a square + return reduce(lambda x, y: x.row_join(y), + [self.dim_can_vector(d) for d in sorted(self.base_dims, key=str)] + ).inv() + + def dim_can_vector(self, dim): + """ + Useless method, kept for compatibility with previous versions. + + DO NOT USE. + + Dimensional representation in terms of the canonical base dimensions. + """ + + vec = [] + for d in self.list_can_dims: + vec.append(self.get_dimensional_dependencies(dim).get(d, 0)) + return Matrix(vec) + + def dim_vector(self, dim): + """ + Useless method, kept for compatibility with previous versions. + + DO NOT USE. + + + Vector representation in terms of the base dimensions. + """ + return self.can_transf_matrix * Matrix(self.dim_can_vector(dim)) + + def print_dim_base(self, dim): + """ + Give the string expression of a dimension in term of the basis symbols. + """ + dims = self.dim_vector(dim) + symbols = [i.symbol if i.symbol is not None else i.name for i in self.base_dims] + res = S.One + for (s, p) in zip(symbols, dims): + res *= s**p + return res + + @property + def dim(self): + """ + Useless method, kept for compatibility with previous versions. + + DO NOT USE. + + Give the dimension of the system. + + That is return the number of dimensions forming the basis. + """ + return len(self.base_dims) + + @property + def is_consistent(self): + """ + Useless method, kept for compatibility with previous versions. + + DO NOT USE. + + Check if the system is well defined. + """ + + # not enough or too many base dimensions compared to independent + # dimensions + # in vector language: the set of vectors do not form a basis + return self.inv_can_transf_matrix.is_square diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/quantities.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/quantities.py new file mode 100644 index 0000000000000000000000000000000000000000..cc19e72aea83b5bd8ae7cf2f63dd49388a3815ee --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/quantities.py @@ -0,0 +1,152 @@ +""" +Physical quantities. +""" + +from sympy.core.expr import AtomicExpr +from sympy.core.symbol import Symbol +from sympy.core.sympify import sympify +from sympy.physics.units.dimensions import _QuantityMapper +from sympy.physics.units.prefixes import Prefix + + +class Quantity(AtomicExpr): + """ + Physical quantity: can be a unit of measure, a constant or a generic quantity. + """ + + is_commutative = True + is_real = True + is_number = False + is_nonzero = True + is_physical_constant = False + _diff_wrt = True + + def __new__(cls, name, abbrev=None, + latex_repr=None, pretty_unicode_repr=None, + pretty_ascii_repr=None, mathml_presentation_repr=None, + is_prefixed=False, + **assumptions): + + if not isinstance(name, Symbol): + name = Symbol(name) + + if abbrev is None: + abbrev = name + elif isinstance(abbrev, str): + abbrev = Symbol(abbrev) + + # HACK: These are here purely for type checking. They actually get assigned below. + cls._is_prefixed = is_prefixed + + obj = AtomicExpr.__new__(cls, name, abbrev) + obj._name = name + obj._abbrev = abbrev + obj._latex_repr = latex_repr + obj._unicode_repr = pretty_unicode_repr + obj._ascii_repr = pretty_ascii_repr + obj._mathml_repr = mathml_presentation_repr + obj._is_prefixed = is_prefixed + return obj + + def set_global_dimension(self, dimension): + _QuantityMapper._quantity_dimension_global[self] = dimension + + def set_global_relative_scale_factor(self, scale_factor, reference_quantity): + """ + Setting a scale factor that is valid across all unit system. + """ + from sympy.physics.units import UnitSystem + scale_factor = sympify(scale_factor) + if isinstance(scale_factor, Prefix): + self._is_prefixed = True + # replace all prefixes by their ratio to canonical units: + scale_factor = scale_factor.replace( + lambda x: isinstance(x, Prefix), + lambda x: x.scale_factor + ) + scale_factor = sympify(scale_factor) + UnitSystem._quantity_scale_factors_global[self] = (scale_factor, reference_quantity) + UnitSystem._quantity_dimensional_equivalence_map_global[self] = reference_quantity + + @property + def name(self): + return self._name + + @property + def dimension(self): + from sympy.physics.units import UnitSystem + unit_system = UnitSystem.get_default_unit_system() + return unit_system.get_quantity_dimension(self) + + @property + def abbrev(self): + """ + Symbol representing the unit name. + + Prepend the abbreviation with the prefix symbol if it is defines. + """ + return self._abbrev + + @property + def scale_factor(self): + """ + Overall magnitude of the quantity as compared to the canonical units. + """ + from sympy.physics.units import UnitSystem + unit_system = UnitSystem.get_default_unit_system() + return unit_system.get_quantity_scale_factor(self) + + def _eval_is_positive(self): + return True + + def _eval_is_constant(self): + return True + + def _eval_Abs(self): + return self + + def _eval_subs(self, old, new): + if isinstance(new, Quantity) and self != old: + return self + + def _latex(self, printer): + if self._latex_repr: + return self._latex_repr + else: + return r'\text{{{}}}'.format(self.args[1] \ + if len(self.args) >= 2 else self.args[0]) + + def convert_to(self, other, unit_system="SI"): + """ + Convert the quantity to another quantity of same dimensions. + + Examples + ======== + + >>> from sympy.physics.units import speed_of_light, meter, second + >>> speed_of_light + speed_of_light + >>> speed_of_light.convert_to(meter/second) + 299792458*meter/second + + >>> from sympy.physics.units import liter + >>> liter.convert_to(meter**3) + meter**3/1000 + """ + from .util import convert_to + return convert_to(self, other, unit_system) + + @property + def free_symbols(self): + """Return free symbols from quantity.""" + return set() + + @property + def is_prefixed(self): + """Whether or not the quantity is prefixed. Eg. `kilogram` is prefixed, but `gram` is not.""" + return self._is_prefixed + +class PhysicalConstant(Quantity): + """Represents a physical constant, eg. `speed_of_light` or `avogadro_constant`.""" + + is_physical_constant = True diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/__init__.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cc99b676009dfa4a9422add294d79d94edc5f0c Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/__init__.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/cgs.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/cgs.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35b52c70f1cd66f5fbcc6fb94f1baf3b8dc6740d Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/cgs.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/length_weight_time.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/length_weight_time.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9cc10b8dbe7a48ba34406fb497f8f7ef355f0457 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/length_weight_time.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/mks.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/mks.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24027dbbe123f926d68bfeb7baec6d697f7f080e Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/mks.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/mksa.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/mksa.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b0924de585689b5776693bbf604e7ae4b7839da Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/mksa.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/natural.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/natural.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43c2cae7ec51b7c68a0fd6c39b6f0dcbb75d782b Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/natural.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/si.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/si.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63a9475357aa758933f5f9a7422439f1b36f19e0 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/__pycache__/si.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/cgs.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/cgs.py new file mode 100644 index 0000000000000000000000000000000000000000..1f5ee0b5454f1998672e1979ae4eaabe57a8edb4 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/cgs.py @@ -0,0 +1,82 @@ +from sympy.core.singleton import S +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.physics.units import UnitSystem, centimeter, gram, second, coulomb, charge, speed_of_light, current, mass, \ + length, voltage, magnetic_density, magnetic_flux +from sympy.physics.units.definitions import coulombs_constant +from sympy.physics.units.definitions.unit_definitions import statcoulomb, statampere, statvolt, volt, tesla, gauss, \ + weber, maxwell, debye, oersted, ohm, farad, henry, erg, ampere, coulomb_constant +from sympy.physics.units.systems.mks import dimsys_length_weight_time + +One = S.One + +dimsys_cgs = dimsys_length_weight_time.extend( + [], + new_dim_deps={ + # Dimensional dependencies for derived dimensions + "impedance": {"time": 1, "length": -1}, + "conductance": {"time": -1, "length": 1}, + "capacitance": {"length": 1}, + "inductance": {"time": 2, "length": -1}, + "charge": {"mass": S.Half, "length": S(3)/2, "time": -1}, + "current": {"mass": One/2, "length": 3*One/2, "time": -2}, + "voltage": {"length": -One/2, "mass": One/2, "time": -1}, + "magnetic_density": {"length": -One/2, "mass": One/2, "time": -1}, + "magnetic_flux": {"length": 3*One/2, "mass": One/2, "time": -1}, + } +) + +cgs_gauss = UnitSystem( + base_units=[centimeter, gram, second], + units=[], + name="cgs_gauss", + dimension_system=dimsys_cgs) + + +cgs_gauss.set_quantity_scale_factor(coulombs_constant, 1) + +cgs_gauss.set_quantity_dimension(statcoulomb, charge) +cgs_gauss.set_quantity_scale_factor(statcoulomb, centimeter**(S(3)/2)*gram**(S.Half)/second) + +cgs_gauss.set_quantity_dimension(coulomb, charge) + +cgs_gauss.set_quantity_dimension(statampere, current) +cgs_gauss.set_quantity_scale_factor(statampere, statcoulomb/second) + +cgs_gauss.set_quantity_dimension(statvolt, voltage) +cgs_gauss.set_quantity_scale_factor(statvolt, erg/statcoulomb) + +cgs_gauss.set_quantity_dimension(volt, voltage) + +cgs_gauss.set_quantity_dimension(gauss, magnetic_density) +cgs_gauss.set_quantity_scale_factor(gauss, sqrt(gram/centimeter)/second) + +cgs_gauss.set_quantity_dimension(tesla, magnetic_density) + +cgs_gauss.set_quantity_dimension(maxwell, magnetic_flux) +cgs_gauss.set_quantity_scale_factor(maxwell, sqrt(centimeter**3*gram)/second) + +# SI units expressed in CGS-gaussian units: +cgs_gauss.set_quantity_scale_factor(coulomb, 10*speed_of_light*statcoulomb) +cgs_gauss.set_quantity_scale_factor(ampere, 10*speed_of_light*statcoulomb/second) +cgs_gauss.set_quantity_scale_factor(volt, 10**6/speed_of_light*statvolt) +cgs_gauss.set_quantity_scale_factor(weber, 10**8*maxwell) +cgs_gauss.set_quantity_scale_factor(tesla, 10**4*gauss) +cgs_gauss.set_quantity_scale_factor(debye, One/10**18*statcoulomb*centimeter) +cgs_gauss.set_quantity_scale_factor(oersted, sqrt(gram/centimeter)/second) +cgs_gauss.set_quantity_scale_factor(ohm, 10**5/speed_of_light**2*second/centimeter) +cgs_gauss.set_quantity_scale_factor(farad, One/10**5*speed_of_light**2*centimeter) +cgs_gauss.set_quantity_scale_factor(henry, 10**5/speed_of_light**2/centimeter*second**2) + +# Coulomb's constant: +cgs_gauss.set_quantity_dimension(coulomb_constant, 1) +cgs_gauss.set_quantity_scale_factor(coulomb_constant, 1) + +__all__ = [ + 'ohm', 'tesla', 'maxwell', 'speed_of_light', 'volt', 'second', 'voltage', + 'debye', 'dimsys_length_weight_time', 'centimeter', 'coulomb_constant', + 'farad', 'sqrt', 'UnitSystem', 'current', 'charge', 'weber', 'gram', + 'statcoulomb', 'gauss', 'S', 'statvolt', 'oersted', 'statampere', + 'dimsys_cgs', 'coulomb', 'magnetic_density', 'magnetic_flux', 'One', + 'length', 'erg', 'mass', 'coulombs_constant', 'henry', 'ampere', + 'cgs_gauss', +] diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/length_weight_time.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/length_weight_time.py new file mode 100644 index 0000000000000000000000000000000000000000..dca4ded82afb8ff0e45f197e51c23850ca824737 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/length_weight_time.py @@ -0,0 +1,156 @@ +from sympy.core.singleton import S + +from sympy.core.numbers import pi + +from sympy.physics.units import DimensionSystem, hertz, kilogram +from sympy.physics.units.definitions import ( + G, Hz, J, N, Pa, W, c, g, kg, m, s, meter, gram, second, newton, + joule, watt, pascal) +from sympy.physics.units.definitions.dimension_definitions import ( + acceleration, action, energy, force, frequency, momentum, + power, pressure, velocity, length, mass, time) +from sympy.physics.units.prefixes import PREFIXES, prefix_unit +from sympy.physics.units.prefixes import ( + kibi, mebi, gibi, tebi, pebi, exbi +) +from sympy.physics.units.definitions import ( + cd, K, coulomb, volt, ohm, siemens, farad, henry, tesla, weber, dioptre, + lux, katal, gray, becquerel, inch, hectare, liter, julian_year, + gravitational_constant, speed_of_light, elementary_charge, planck, hbar, + electronvolt, avogadro_number, avogadro_constant, boltzmann_constant, + stefan_boltzmann_constant, atomic_mass_constant, molar_gas_constant, + faraday_constant, josephson_constant, von_klitzing_constant, + acceleration_due_to_gravity, magnetic_constant, vacuum_permittivity, + vacuum_impedance, coulomb_constant, atmosphere, bar, pound, psi, mmHg, + milli_mass_unit, quart, lightyear, astronomical_unit, planck_mass, + planck_time, planck_temperature, planck_length, planck_charge, + planck_area, planck_volume, planck_momentum, planck_energy, planck_force, + planck_power, planck_density, planck_energy_density, planck_intensity, + planck_angular_frequency, planck_pressure, planck_current, planck_voltage, + planck_impedance, planck_acceleration, bit, byte, kibibyte, mebibyte, + gibibyte, tebibyte, pebibyte, exbibyte, curie, rutherford, radian, degree, + steradian, angular_mil, atomic_mass_unit, gee, kPa, ampere, u0, kelvin, + mol, mole, candela, electric_constant, boltzmann, angstrom +) + + +dimsys_length_weight_time = DimensionSystem([ + # Dimensional dependencies for MKS base dimensions + length, + mass, + time, +], dimensional_dependencies={ + # Dimensional dependencies for derived dimensions + "velocity": {"length": 1, "time": -1}, + "acceleration": {"length": 1, "time": -2}, + "momentum": {"mass": 1, "length": 1, "time": -1}, + "force": {"mass": 1, "length": 1, "time": -2}, + "energy": {"mass": 1, "length": 2, "time": -2}, + "power": {"length": 2, "mass": 1, "time": -3}, + "pressure": {"mass": 1, "length": -1, "time": -2}, + "frequency": {"time": -1}, + "action": {"length": 2, "mass": 1, "time": -1}, + "area": {"length": 2}, + "volume": {"length": 3}, +}) + + +One = S.One + + +# Base units: +dimsys_length_weight_time.set_quantity_dimension(meter, length) +dimsys_length_weight_time.set_quantity_scale_factor(meter, One) + +# gram; used to define its prefixed units +dimsys_length_weight_time.set_quantity_dimension(gram, mass) +dimsys_length_weight_time.set_quantity_scale_factor(gram, One) + +dimsys_length_weight_time.set_quantity_dimension(second, time) +dimsys_length_weight_time.set_quantity_scale_factor(second, One) + +# derived units + +dimsys_length_weight_time.set_quantity_dimension(newton, force) +dimsys_length_weight_time.set_quantity_scale_factor(newton, kilogram*meter/second**2) + +dimsys_length_weight_time.set_quantity_dimension(joule, energy) +dimsys_length_weight_time.set_quantity_scale_factor(joule, newton*meter) + +dimsys_length_weight_time.set_quantity_dimension(watt, power) +dimsys_length_weight_time.set_quantity_scale_factor(watt, joule/second) + +dimsys_length_weight_time.set_quantity_dimension(pascal, pressure) +dimsys_length_weight_time.set_quantity_scale_factor(pascal, newton/meter**2) + +dimsys_length_weight_time.set_quantity_dimension(hertz, frequency) +dimsys_length_weight_time.set_quantity_scale_factor(hertz, One) + +# Other derived units: + +dimsys_length_weight_time.set_quantity_dimension(dioptre, 1 / length) +dimsys_length_weight_time.set_quantity_scale_factor(dioptre, 1/meter) + +# Common volume and area units + +dimsys_length_weight_time.set_quantity_dimension(hectare, length**2) +dimsys_length_weight_time.set_quantity_scale_factor(hectare, (meter**2)*(10000)) + +dimsys_length_weight_time.set_quantity_dimension(liter, length**3) +dimsys_length_weight_time.set_quantity_scale_factor(liter, meter**3/1000) + + +# Newton constant +# REF: NIST SP 959 (June 2019) + +dimsys_length_weight_time.set_quantity_dimension(gravitational_constant, length ** 3 * mass ** -1 * time ** -2) +dimsys_length_weight_time.set_quantity_scale_factor(gravitational_constant, 6.67430e-11*m**3/(kg*s**2)) + +# speed of light + +dimsys_length_weight_time.set_quantity_dimension(speed_of_light, velocity) +dimsys_length_weight_time.set_quantity_scale_factor(speed_of_light, 299792458*meter/second) + + +# Planck constant +# REF: NIST SP 959 (June 2019) + +dimsys_length_weight_time.set_quantity_dimension(planck, action) +dimsys_length_weight_time.set_quantity_scale_factor(planck, 6.62607015e-34*joule*second) + +# Reduced Planck constant +# REF: NIST SP 959 (June 2019) + +dimsys_length_weight_time.set_quantity_dimension(hbar, action) +dimsys_length_weight_time.set_quantity_scale_factor(hbar, planck / (2 * pi)) + + +__all__ = [ + 'mmHg', 'atmosphere', 'newton', 'meter', 'vacuum_permittivity', 'pascal', + 'magnetic_constant', 'angular_mil', 'julian_year', 'weber', 'exbibyte', + 'liter', 'molar_gas_constant', 'faraday_constant', 'avogadro_constant', + 'planck_momentum', 'planck_density', 'gee', 'mol', 'bit', 'gray', 'kibi', + 'bar', 'curie', 'prefix_unit', 'PREFIXES', 'planck_time', 'gram', + 'candela', 'force', 'planck_intensity', 'energy', 'becquerel', + 'planck_acceleration', 'speed_of_light', 'dioptre', 'second', 'frequency', + 'Hz', 'power', 'lux', 'planck_current', 'momentum', 'tebibyte', + 'planck_power', 'degree', 'mebi', 'K', 'planck_volume', + 'quart', 'pressure', 'W', 'joule', 'boltzmann_constant', 'c', 'g', + 'planck_force', 'exbi', 's', 'watt', 'action', 'hbar', 'gibibyte', + 'DimensionSystem', 'cd', 'volt', 'planck_charge', 'angstrom', + 'dimsys_length_weight_time', 'pebi', 'vacuum_impedance', 'planck', + 'farad', 'gravitational_constant', 'u0', 'hertz', 'tesla', 'steradian', + 'josephson_constant', 'planck_area', 'stefan_boltzmann_constant', + 'astronomical_unit', 'J', 'N', 'planck_voltage', 'planck_energy', + 'atomic_mass_constant', 'rutherford', 'elementary_charge', 'Pa', + 'planck_mass', 'henry', 'planck_angular_frequency', 'ohm', 'pound', + 'planck_pressure', 'G', 'avogadro_number', 'psi', 'von_klitzing_constant', + 'planck_length', 'radian', 'mole', 'acceleration', + 'planck_energy_density', 'mebibyte', 'length', + 'acceleration_due_to_gravity', 'planck_temperature', 'tebi', 'inch', + 'electronvolt', 'coulomb_constant', 'kelvin', 'kPa', 'boltzmann', + 'milli_mass_unit', 'gibi', 'planck_impedance', 'electric_constant', 'kg', + 'coulomb', 'siemens', 'byte', 'atomic_mass_unit', 'm', 'kibibyte', + 'kilogram', 'lightyear', 'mass', 'time', 'pebibyte', 'velocity', + 'ampere', 'katal', +] diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/mks.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/mks.py new file mode 100644 index 0000000000000000000000000000000000000000..18cc4b1be5e2cbf5773845e48a0cb552fb750fae --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/mks.py @@ -0,0 +1,46 @@ +""" +MKS unit system. + +MKS stands for "meter, kilogram, second". +""" + +from sympy.physics.units import UnitSystem +from sympy.physics.units.definitions import gravitational_constant, hertz, joule, newton, pascal, watt, speed_of_light, gram, kilogram, meter, second +from sympy.physics.units.definitions.dimension_definitions import ( + acceleration, action, energy, force, frequency, momentum, + power, pressure, velocity, length, mass, time) +from sympy.physics.units.prefixes import PREFIXES, prefix_unit +from sympy.physics.units.systems.length_weight_time import dimsys_length_weight_time + +dims = (velocity, acceleration, momentum, force, energy, power, pressure, + frequency, action) + +units = [meter, gram, second, joule, newton, watt, pascal, hertz] +all_units = [] + +# Prefixes of units like gram, joule, newton etc get added using `prefix_unit` +# in the for loop, but the actual units have to be added manually. +all_units.extend([gram, joule, newton, watt, pascal, hertz]) + +for u in units: + all_units.extend(prefix_unit(u, PREFIXES)) +all_units.extend([gravitational_constant, speed_of_light]) + +# unit system +MKS = UnitSystem(base_units=(meter, kilogram, second), units=all_units, name="MKS", dimension_system=dimsys_length_weight_time, derived_units={ + power: watt, + time: second, + pressure: pascal, + length: meter, + frequency: hertz, + mass: kilogram, + force: newton, + energy: joule, + velocity: meter/second, + acceleration: meter/(second**2), +}) + + +__all__ = [ + 'MKS', 'units', 'all_units', 'dims', +] diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/mksa.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/mksa.py new file mode 100644 index 0000000000000000000000000000000000000000..c18c0d6ae3801358d8828e2309d091cb9cb987d8 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/mksa.py @@ -0,0 +1,54 @@ +""" +MKS unit system. + +MKS stands for "meter, kilogram, second, ampere". +""" + +from __future__ import annotations + +from sympy.physics.units.definitions import Z0, ampere, coulomb, farad, henry, siemens, tesla, volt, weber, ohm +from sympy.physics.units.definitions.dimension_definitions import ( + capacitance, charge, conductance, current, impedance, inductance, + magnetic_density, magnetic_flux, voltage) +from sympy.physics.units.prefixes import PREFIXES, prefix_unit +from sympy.physics.units.systems.mks import MKS, dimsys_length_weight_time +from sympy.physics.units.quantities import Quantity + +dims = (voltage, impedance, conductance, current, capacitance, inductance, charge, + magnetic_density, magnetic_flux) + +units = [ampere, volt, ohm, siemens, farad, henry, coulomb, tesla, weber] + +all_units: list[Quantity] = [] +for u in units: + all_units.extend(prefix_unit(u, PREFIXES)) +all_units.extend(units) + +all_units.append(Z0) + +dimsys_MKSA = dimsys_length_weight_time.extend([ + # Dimensional dependencies for base dimensions (MKSA not in MKS) + current, +], new_dim_deps={ + # Dimensional dependencies for derived dimensions + "voltage": {"mass": 1, "length": 2, "current": -1, "time": -3}, + "impedance": {"mass": 1, "length": 2, "current": -2, "time": -3}, + "conductance": {"mass": -1, "length": -2, "current": 2, "time": 3}, + "capacitance": {"mass": -1, "length": -2, "current": 2, "time": 4}, + "inductance": {"mass": 1, "length": 2, "current": -2, "time": -2}, + "charge": {"current": 1, "time": 1}, + "magnetic_density": {"mass": 1, "current": -1, "time": -2}, + "magnetic_flux": {"length": 2, "mass": 1, "current": -1, "time": -2}, +}) + +MKSA = MKS.extend(base=(ampere,), units=all_units, name='MKSA', dimension_system=dimsys_MKSA, derived_units={ + magnetic_flux: weber, + impedance: ohm, + current: ampere, + voltage: volt, + inductance: henry, + conductance: siemens, + magnetic_density: tesla, + charge: coulomb, + capacitance: farad, +}) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/natural.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/natural.py new file mode 100644 index 0000000000000000000000000000000000000000..13eb2c19e982438fab4b1422ddc5a25b16204be8 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/natural.py @@ -0,0 +1,27 @@ +""" +Naturalunit system. + +The natural system comes from "setting c = 1, hbar = 1". From the computer +point of view it means that we use velocity and action instead of length and +time. Moreover instead of mass we use energy. +""" + +from sympy.physics.units import DimensionSystem +from sympy.physics.units.definitions import c, eV, hbar +from sympy.physics.units.definitions.dimension_definitions import ( + action, energy, force, frequency, length, mass, momentum, + power, time, velocity) +from sympy.physics.units.prefixes import PREFIXES, prefix_unit +from sympy.physics.units.unitsystem import UnitSystem + + +# dimension system +_natural_dim = DimensionSystem( + base_dims=(action, energy, velocity), + derived_dims=(length, mass, time, momentum, force, power, frequency) +) + +units = prefix_unit(eV, PREFIXES) + +# unit system +natural = UnitSystem(base_units=(hbar, eV, c), units=units, name="Natural system") diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/si.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/si.py new file mode 100644 index 0000000000000000000000000000000000000000..2bfa7805871b8663c70b8af7da9ca1dc9b4afab3 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/systems/si.py @@ -0,0 +1,377 @@ +""" +SI unit system. +Based on MKSA, which stands for "meter, kilogram, second, ampere". +Added kelvin, candela and mole. + +""" + +from __future__ import annotations + +from sympy.physics.units import DimensionSystem, Dimension, dHg0 + +from sympy.physics.units.quantities import Quantity + +from sympy.core.numbers import (Rational, pi) +from sympy.core.singleton import S +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.physics.units.definitions.dimension_definitions import ( + acceleration, action, current, impedance, length, mass, time, velocity, + amount_of_substance, temperature, information, frequency, force, pressure, + energy, power, charge, voltage, capacitance, conductance, magnetic_flux, + magnetic_density, inductance, luminous_intensity +) +from sympy.physics.units.definitions import ( + kilogram, newton, second, meter, gram, cd, K, joule, watt, pascal, hertz, + coulomb, volt, ohm, siemens, farad, henry, tesla, weber, dioptre, lux, + katal, gray, becquerel, inch, liter, julian_year, gravitational_constant, + speed_of_light, elementary_charge, planck, hbar, electronvolt, + avogadro_number, avogadro_constant, boltzmann_constant, electron_rest_mass, + stefan_boltzmann_constant, Da, atomic_mass_constant, molar_gas_constant, + faraday_constant, josephson_constant, von_klitzing_constant, + acceleration_due_to_gravity, magnetic_constant, vacuum_permittivity, + vacuum_impedance, coulomb_constant, atmosphere, bar, pound, psi, mmHg, + milli_mass_unit, quart, lightyear, astronomical_unit, planck_mass, + planck_time, planck_temperature, planck_length, planck_charge, planck_area, + planck_volume, planck_momentum, planck_energy, planck_force, planck_power, + planck_density, planck_energy_density, planck_intensity, + planck_angular_frequency, planck_pressure, planck_current, planck_voltage, + planck_impedance, planck_acceleration, bit, byte, kibibyte, mebibyte, + gibibyte, tebibyte, pebibyte, exbibyte, curie, rutherford, radian, degree, + steradian, angular_mil, atomic_mass_unit, gee, kPa, ampere, u0, c, kelvin, + mol, mole, candela, m, kg, s, electric_constant, G, boltzmann +) +from sympy.physics.units.prefixes import PREFIXES, prefix_unit +from sympy.physics.units.systems.mksa import MKSA, dimsys_MKSA + +derived_dims = (frequency, force, pressure, energy, power, charge, voltage, + capacitance, conductance, magnetic_flux, + magnetic_density, inductance, luminous_intensity) +base_dims = (amount_of_substance, luminous_intensity, temperature) + +units = [mol, cd, K, lux, hertz, newton, pascal, joule, watt, coulomb, volt, + farad, ohm, siemens, weber, tesla, henry, candela, lux, becquerel, + gray, katal] + +all_units: list[Quantity] = [] +for u in units: + all_units.extend(prefix_unit(u, PREFIXES)) + +all_units.extend(units) +all_units.extend([mol, cd, K, lux]) + + +dimsys_SI = dimsys_MKSA.extend( + [ + # Dimensional dependencies for other base dimensions: + temperature, + amount_of_substance, + luminous_intensity, + ]) + +dimsys_default = dimsys_SI.extend( + [information], +) + +SI = MKSA.extend(base=(mol, cd, K), units=all_units, name='SI', dimension_system=dimsys_SI, derived_units={ + power: watt, + magnetic_flux: weber, + time: second, + impedance: ohm, + pressure: pascal, + current: ampere, + voltage: volt, + length: meter, + frequency: hertz, + inductance: henry, + temperature: kelvin, + amount_of_substance: mole, + luminous_intensity: candela, + conductance: siemens, + mass: kilogram, + magnetic_density: tesla, + charge: coulomb, + force: newton, + capacitance: farad, + energy: joule, + velocity: meter/second, +}) + +One = S.One + +SI.set_quantity_dimension(radian, One) + +SI.set_quantity_scale_factor(ampere, One) + +SI.set_quantity_scale_factor(kelvin, One) + +SI.set_quantity_scale_factor(mole, One) + +SI.set_quantity_scale_factor(candela, One) + +# MKSA extension to MKS: derived units + +SI.set_quantity_scale_factor(coulomb, One) + +SI.set_quantity_scale_factor(volt, joule/coulomb) + +SI.set_quantity_scale_factor(ohm, volt/ampere) + +SI.set_quantity_scale_factor(siemens, ampere/volt) + +SI.set_quantity_scale_factor(farad, coulomb/volt) + +SI.set_quantity_scale_factor(henry, volt*second/ampere) + +SI.set_quantity_scale_factor(tesla, volt*second/meter**2) + +SI.set_quantity_scale_factor(weber, joule/ampere) + + +SI.set_quantity_dimension(lux, luminous_intensity / length ** 2) +SI.set_quantity_scale_factor(lux, steradian*candela/meter**2) + +# katal is the SI unit of catalytic activity + +SI.set_quantity_dimension(katal, amount_of_substance / time) +SI.set_quantity_scale_factor(katal, mol/second) + +# gray is the SI unit of absorbed dose + +SI.set_quantity_dimension(gray, energy / mass) +SI.set_quantity_scale_factor(gray, meter**2/second**2) + +# becquerel is the SI unit of radioactivity + +SI.set_quantity_dimension(becquerel, 1 / time) +SI.set_quantity_scale_factor(becquerel, 1/second) + +#### CONSTANTS #### + +# elementary charge +# REF: NIST SP 959 (June 2019) + +SI.set_quantity_dimension(elementary_charge, charge) +SI.set_quantity_scale_factor(elementary_charge, 1.602176634e-19*coulomb) + +# Electronvolt +# REF: NIST SP 959 (June 2019) + +SI.set_quantity_dimension(electronvolt, energy) +SI.set_quantity_scale_factor(electronvolt, 1.602176634e-19*joule) + +# Avogadro number +# REF: NIST SP 959 (June 2019) + +SI.set_quantity_dimension(avogadro_number, One) +SI.set_quantity_scale_factor(avogadro_number, 6.02214076e23) + +# Avogadro constant + +SI.set_quantity_dimension(avogadro_constant, amount_of_substance ** -1) +SI.set_quantity_scale_factor(avogadro_constant, avogadro_number / mol) + +# Boltzmann constant +# REF: NIST SP 959 (June 2019) + +SI.set_quantity_dimension(boltzmann_constant, energy / temperature) +SI.set_quantity_scale_factor(boltzmann_constant, 1.380649e-23*joule/kelvin) + +# Stefan-Boltzmann constant +# REF: NIST SP 959 (June 2019) + +SI.set_quantity_dimension(stefan_boltzmann_constant, energy * time ** -1 * length ** -2 * temperature ** -4) +SI.set_quantity_scale_factor(stefan_boltzmann_constant, pi**2 * boltzmann_constant**4 / (60 * hbar**3 * speed_of_light ** 2)) + +# Atomic mass +# REF: NIST SP 959 (June 2019) + +SI.set_quantity_dimension(atomic_mass_constant, mass) +SI.set_quantity_scale_factor(atomic_mass_constant, 1.66053906660e-24*gram) + +# Molar gas constant +# REF: NIST SP 959 (June 2019) + +SI.set_quantity_dimension(molar_gas_constant, energy / (temperature * amount_of_substance)) +SI.set_quantity_scale_factor(molar_gas_constant, boltzmann_constant * avogadro_constant) + +# Faraday constant + +SI.set_quantity_dimension(faraday_constant, charge / amount_of_substance) +SI.set_quantity_scale_factor(faraday_constant, elementary_charge * avogadro_constant) + +# Josephson constant + +SI.set_quantity_dimension(josephson_constant, frequency / voltage) +SI.set_quantity_scale_factor(josephson_constant, 0.5 * planck / elementary_charge) + +# Von Klitzing constant + +SI.set_quantity_dimension(von_klitzing_constant, voltage / current) +SI.set_quantity_scale_factor(von_klitzing_constant, hbar / elementary_charge ** 2) + +# Acceleration due to gravity (on the Earth surface) + +SI.set_quantity_dimension(acceleration_due_to_gravity, acceleration) +SI.set_quantity_scale_factor(acceleration_due_to_gravity, 9.80665*meter/second**2) + +# magnetic constant: + +SI.set_quantity_dimension(magnetic_constant, force / current ** 2) +SI.set_quantity_scale_factor(magnetic_constant, 4*pi/10**7 * newton/ampere**2) + +# electric constant: + +SI.set_quantity_dimension(vacuum_permittivity, capacitance / length) +SI.set_quantity_scale_factor(vacuum_permittivity, 1/(u0 * c**2)) + +# vacuum impedance: + +SI.set_quantity_dimension(vacuum_impedance, impedance) +SI.set_quantity_scale_factor(vacuum_impedance, u0 * c) + +# Electron rest mass +SI.set_quantity_dimension(electron_rest_mass, mass) +SI.set_quantity_scale_factor(electron_rest_mass, 9.1093837015e-31*kilogram) + +# Coulomb's constant: +SI.set_quantity_dimension(coulomb_constant, force * length ** 2 / charge ** 2) +SI.set_quantity_scale_factor(coulomb_constant, 1/(4*pi*vacuum_permittivity)) + +SI.set_quantity_dimension(psi, pressure) +SI.set_quantity_scale_factor(psi, pound * gee / inch ** 2) + +SI.set_quantity_dimension(mmHg, pressure) +SI.set_quantity_scale_factor(mmHg, dHg0 * acceleration_due_to_gravity * kilogram / meter**2) + +SI.set_quantity_dimension(milli_mass_unit, mass) +SI.set_quantity_scale_factor(milli_mass_unit, atomic_mass_unit/1000) + +SI.set_quantity_dimension(quart, length ** 3) +SI.set_quantity_scale_factor(quart, Rational(231, 4) * inch**3) + +# Other convenient units and magnitudes + +SI.set_quantity_dimension(lightyear, length) +SI.set_quantity_scale_factor(lightyear, speed_of_light*julian_year) + +SI.set_quantity_dimension(astronomical_unit, length) +SI.set_quantity_scale_factor(astronomical_unit, 149597870691*meter) + +# Fundamental Planck units: + +SI.set_quantity_dimension(planck_mass, mass) +SI.set_quantity_scale_factor(planck_mass, sqrt(hbar*speed_of_light/G)) + +SI.set_quantity_dimension(planck_time, time) +SI.set_quantity_scale_factor(planck_time, sqrt(hbar*G/speed_of_light**5)) + +SI.set_quantity_dimension(planck_temperature, temperature) +SI.set_quantity_scale_factor(planck_temperature, sqrt(hbar*speed_of_light**5/G/boltzmann**2)) + +SI.set_quantity_dimension(planck_length, length) +SI.set_quantity_scale_factor(planck_length, sqrt(hbar*G/speed_of_light**3)) + +SI.set_quantity_dimension(planck_charge, charge) +SI.set_quantity_scale_factor(planck_charge, sqrt(4*pi*electric_constant*hbar*speed_of_light)) + +# Derived Planck units: + +SI.set_quantity_dimension(planck_area, length ** 2) +SI.set_quantity_scale_factor(planck_area, planck_length**2) + +SI.set_quantity_dimension(planck_volume, length ** 3) +SI.set_quantity_scale_factor(planck_volume, planck_length**3) + +SI.set_quantity_dimension(planck_momentum, mass * velocity) +SI.set_quantity_scale_factor(planck_momentum, planck_mass * speed_of_light) + +SI.set_quantity_dimension(planck_energy, energy) +SI.set_quantity_scale_factor(planck_energy, planck_mass * speed_of_light**2) + +SI.set_quantity_dimension(planck_force, force) +SI.set_quantity_scale_factor(planck_force, planck_energy / planck_length) + +SI.set_quantity_dimension(planck_power, power) +SI.set_quantity_scale_factor(planck_power, planck_energy / planck_time) + +SI.set_quantity_dimension(planck_density, mass / length ** 3) +SI.set_quantity_scale_factor(planck_density, planck_mass / planck_length**3) + +SI.set_quantity_dimension(planck_energy_density, energy / length ** 3) +SI.set_quantity_scale_factor(planck_energy_density, planck_energy / planck_length**3) + +SI.set_quantity_dimension(planck_intensity, mass * time ** (-3)) +SI.set_quantity_scale_factor(planck_intensity, planck_energy_density * speed_of_light) + +SI.set_quantity_dimension(planck_angular_frequency, 1 / time) +SI.set_quantity_scale_factor(planck_angular_frequency, 1 / planck_time) + +SI.set_quantity_dimension(planck_pressure, pressure) +SI.set_quantity_scale_factor(planck_pressure, planck_force / planck_length**2) + +SI.set_quantity_dimension(planck_current, current) +SI.set_quantity_scale_factor(planck_current, planck_charge / planck_time) + +SI.set_quantity_dimension(planck_voltage, voltage) +SI.set_quantity_scale_factor(planck_voltage, planck_energy / planck_charge) + +SI.set_quantity_dimension(planck_impedance, impedance) +SI.set_quantity_scale_factor(planck_impedance, planck_voltage / planck_current) + +SI.set_quantity_dimension(planck_acceleration, acceleration) +SI.set_quantity_scale_factor(planck_acceleration, speed_of_light / planck_time) + +# Older units for radioactivity + +SI.set_quantity_dimension(curie, 1 / time) +SI.set_quantity_scale_factor(curie, 37000000000*becquerel) + +SI.set_quantity_dimension(rutherford, 1 / time) +SI.set_quantity_scale_factor(rutherford, 1000000*becquerel) + + +# check that scale factors are the right SI dimensions: +for _scale_factor, _dimension in zip( + SI._quantity_scale_factors.values(), + SI._quantity_dimension_map.values() +): + dimex = SI.get_dimensional_expr(_scale_factor) + if dimex != 1: + # XXX: equivalent_dims is an instance method taking two arguments in + # addition to self so this can not work: + if not DimensionSystem.equivalent_dims(_dimension, Dimension(dimex)): # type: ignore + raise ValueError("quantity value and dimension mismatch") +del _scale_factor, _dimension + +__all__ = [ + 'mmHg', 'atmosphere', 'inductance', 'newton', 'meter', + 'vacuum_permittivity', 'pascal', 'magnetic_constant', 'voltage', + 'angular_mil', 'luminous_intensity', 'all_units', + 'julian_year', 'weber', 'exbibyte', 'liter', + 'molar_gas_constant', 'faraday_constant', 'avogadro_constant', + 'lightyear', 'planck_density', 'gee', 'mol', 'bit', 'gray', + 'planck_momentum', 'bar', 'magnetic_density', 'prefix_unit', 'PREFIXES', + 'planck_time', 'dimex', 'gram', 'candela', 'force', 'planck_intensity', + 'energy', 'becquerel', 'planck_acceleration', 'speed_of_light', + 'conductance', 'frequency', 'coulomb_constant', 'degree', 'lux', 'planck', + 'current', 'planck_current', 'tebibyte', 'planck_power', 'MKSA', 'power', + 'K', 'planck_volume', 'quart', 'pressure', 'amount_of_substance', + 'joule', 'boltzmann_constant', 'Dimension', 'c', 'planck_force', 'length', + 'watt', 'action', 'hbar', 'gibibyte', 'DimensionSystem', 'cd', 'volt', + 'planck_charge', 'dioptre', 'vacuum_impedance', 'dimsys_default', 'farad', + 'charge', 'gravitational_constant', 'temperature', 'u0', 'hertz', + 'capacitance', 'tesla', 'steradian', 'planck_mass', 'josephson_constant', + 'planck_area', 'stefan_boltzmann_constant', 'base_dims', + 'astronomical_unit', 'radian', 'planck_voltage', 'impedance', + 'planck_energy', 'Da', 'atomic_mass_constant', 'rutherford', 'second', 'inch', + 'elementary_charge', 'SI', 'electronvolt', 'dimsys_SI', 'henry', + 'planck_angular_frequency', 'ohm', 'pound', 'planck_pressure', 'G', 'psi', + 'dHg0', 'von_klitzing_constant', 'planck_length', 'avogadro_number', + 'mole', 'acceleration', 'information', 'planck_energy_density', + 'mebibyte', 's', 'acceleration_due_to_gravity', 'electron_rest_mass', + 'planck_temperature', 'units', 'mass', 'dimsys_MKSA', 'kelvin', 'kPa', + 'boltzmann', 'milli_mass_unit', 'planck_impedance', 'electric_constant', + 'derived_dims', 'kg', 'coulomb', 'siemens', 'byte', 'magnetic_flux', + 'atomic_mass_unit', 'm', 'kibibyte', 'kilogram', 'One', 'curie', 'u', + 'time', 'pebibyte', 'velocity', 'ampere', 'katal', +] diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__init__.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/__init__.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b90a6dd9fd76ac92edfe8f589cccce01133a7d66 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/__init__.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_dimensions.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_dimensions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c72def6ca61cf88b7e859c635bd49ccf98a881e5 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_dimensions.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_dimensionsystem.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_dimensionsystem.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5d853ba63d468e92478d6c6c8eb92029d0ea212 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_dimensionsystem.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_prefixes.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_prefixes.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d52f043338982ee8ff9732c0c2fa9e9e1369c4e Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_prefixes.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_quantities.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_quantities.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..198e39d8e524e56ce2328f172bb493ae0f6f61af Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_quantities.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_unitsystem.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_unitsystem.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3943299b7348cc351d22de870fe976ff2afc01ad Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_unitsystem.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_util.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_util.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2581aecefbb03e228fc202cab111f36cf039b27f Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/__pycache__/test_util.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_dimensions.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_dimensions.py new file mode 100644 index 0000000000000000000000000000000000000000..6455df41068a07c966c5f3e782e561fec4d16a97 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_dimensions.py @@ -0,0 +1,150 @@ +from sympy.physics.units.systems.si import dimsys_SI + +from sympy.core.numbers import pi +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, atan2, cos) +from sympy.physics.units.dimensions import Dimension +from sympy.physics.units.definitions.dimension_definitions import ( + length, time, mass, force, pressure, angle +) +from sympy.physics.units import foot +from sympy.testing.pytest import raises + + +def test_Dimension_definition(): + assert dimsys_SI.get_dimensional_dependencies(length) == {length: 1} + assert length.name == Symbol("length") + assert length.symbol == Symbol("L") + + halflength = sqrt(length) + assert dimsys_SI.get_dimensional_dependencies(halflength) == {length: S.Half} + + +def test_Dimension_error_definition(): + # tuple with more or less than two entries + raises(TypeError, lambda: Dimension(("length", 1, 2))) + raises(TypeError, lambda: Dimension(["length"])) + + # non-number power + raises(TypeError, lambda: Dimension({"length": "a"})) + + # non-number with named argument + raises(TypeError, lambda: Dimension({"length": (1, 2)})) + + # symbol should by Symbol or str + raises(AssertionError, lambda: Dimension("length", symbol=1)) + + +def test_str(): + assert str(Dimension("length")) == "Dimension(length)" + assert str(Dimension("length", "L")) == "Dimension(length, L)" + + +def test_Dimension_properties(): + assert dimsys_SI.is_dimensionless(length) is False + assert dimsys_SI.is_dimensionless(length/length) is True + assert dimsys_SI.is_dimensionless(Dimension("undefined")) is False + + assert length.has_integer_powers(dimsys_SI) is True + assert (length**(-1)).has_integer_powers(dimsys_SI) is True + assert (length**1.5).has_integer_powers(dimsys_SI) is False + + +def test_Dimension_add_sub(): + assert length + length == length + assert length - length == length + assert -length == length + + raises(TypeError, lambda: length + foot) + raises(TypeError, lambda: foot + length) + raises(TypeError, lambda: length - foot) + raises(TypeError, lambda: foot - length) + + # issue 14547 - only raise error for dimensional args; allow + # others to pass + x = Symbol('x') + e = length + x + assert e == x + length and e.is_Add and set(e.args) == {length, x} + e = length + 1 + assert e == 1 + length == 1 - length and e.is_Add and set(e.args) == {length, 1} + + assert dimsys_SI.get_dimensional_dependencies(mass * length / time**2 + force) == \ + {length: 1, mass: 1, time: -2} + assert dimsys_SI.get_dimensional_dependencies(mass * length / time**2 + force - + pressure * length**2) == \ + {length: 1, mass: 1, time: -2} + + raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(mass * length / time**2 + pressure)) + +def test_Dimension_mul_div_exp(): + assert 2*length == length*2 == length/2 == length + assert 2/length == 1/length + x = Symbol('x') + m = x*length + assert m == length*x and m.is_Mul and set(m.args) == {x, length} + d = x/length + assert d == x*length**-1 and d.is_Mul and set(d.args) == {x, 1/length} + d = length/x + assert d == length*x**-1 and d.is_Mul and set(d.args) == {1/x, length} + + velo = length / time + + assert (length * length) == length ** 2 + + assert dimsys_SI.get_dimensional_dependencies(length * length) == {length: 2} + assert dimsys_SI.get_dimensional_dependencies(length ** 2) == {length: 2} + assert dimsys_SI.get_dimensional_dependencies(length * time) == {length: 1, time: 1} + assert dimsys_SI.get_dimensional_dependencies(velo) == {length: 1, time: -1} + assert dimsys_SI.get_dimensional_dependencies(velo ** 2) == {length: 2, time: -2} + + assert dimsys_SI.get_dimensional_dependencies(length / length) == {} + assert dimsys_SI.get_dimensional_dependencies(velo / length * time) == {} + assert dimsys_SI.get_dimensional_dependencies(length ** -1) == {length: -1} + assert dimsys_SI.get_dimensional_dependencies(velo ** -1.5) == {length: -1.5, time: 1.5} + + length_a = length**"a" + assert dimsys_SI.get_dimensional_dependencies(length_a) == {length: Symbol("a")} + + assert dimsys_SI.get_dimensional_dependencies(length**pi) == {length: pi} + assert dimsys_SI.get_dimensional_dependencies(length**(length/length)) == {length: Dimension(1)} + + raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(length**length)) + + assert length != 1 + assert length / length != 1 + + length_0 = length ** 0 + assert dimsys_SI.get_dimensional_dependencies(length_0) == {} + + # issue 18738 + a = Symbol('a') + b = Symbol('b') + c = sqrt(a**2 + b**2) + c_dim = c.subs({a: length, b: length}) + assert dimsys_SI.equivalent_dims(c_dim, length) + +def test_Dimension_functions(): + raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(cos(length))) + raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(acos(angle))) + raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(atan2(length, time))) + raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(log(length))) + raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(log(100, length))) + raises(TypeError, lambda: dimsys_SI.get_dimensional_dependencies(log(length, 10))) + + assert dimsys_SI.get_dimensional_dependencies(pi) == {} + + assert dimsys_SI.get_dimensional_dependencies(cos(1)) == {} + assert dimsys_SI.get_dimensional_dependencies(cos(angle)) == {} + + assert dimsys_SI.get_dimensional_dependencies(atan2(length, length)) == {} + + assert dimsys_SI.get_dimensional_dependencies(log(length / length, length / length)) == {} + + assert dimsys_SI.get_dimensional_dependencies(Abs(length)) == {length: 1} + assert dimsys_SI.get_dimensional_dependencies(Abs(length / length)) == {} + + assert dimsys_SI.get_dimensional_dependencies(sqrt(-1)) == {} diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_prefixes.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_prefixes.py new file mode 100644 index 0000000000000000000000000000000000000000..7b180102ecd00abf3ff5f8cb4c24aa82ae76ef77 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_prefixes.py @@ -0,0 +1,86 @@ +from sympy.core.mul import Mul +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.physics.units import Quantity, length, meter, W +from sympy.physics.units.prefixes import PREFIXES, Prefix, prefix_unit, kilo, \ + kibi +from sympy.physics.units.systems import SI + +x = Symbol('x') + + +def test_prefix_operations(): + m = PREFIXES['m'] + k = PREFIXES['k'] + M = PREFIXES['M'] + + dodeca = Prefix('dodeca', 'dd', 1, base=12) + + assert m * k is S.One + assert m * W == W / 1000 + assert k * k == M + assert 1 / m == k + assert k / m == M + + assert dodeca * dodeca == 144 + assert 1 / dodeca == S.One / 12 + assert k / dodeca == S(1000) / 12 + assert dodeca / dodeca is S.One + + m = Quantity("fake_meter") + SI.set_quantity_dimension(m, S.One) + SI.set_quantity_scale_factor(m, S.One) + + assert dodeca * m == 12 * m + assert dodeca / m == 12 / m + + expr1 = kilo * 3 + assert isinstance(expr1, Mul) + assert expr1.args == (3, kilo) + + expr2 = kilo * x + assert isinstance(expr2, Mul) + assert expr2.args == (x, kilo) + + expr3 = kilo / 3 + assert isinstance(expr3, Mul) + assert expr3.args == (Rational(1, 3), kilo) + assert expr3.args == (S.One/3, kilo) + + expr4 = kilo / x + assert isinstance(expr4, Mul) + assert expr4.args == (1/x, kilo) + + +def test_prefix_unit(): + m = Quantity("fake_meter", abbrev="m") + m.set_global_relative_scale_factor(1, meter) + + pref = {"m": PREFIXES["m"], "c": PREFIXES["c"], "d": PREFIXES["d"]} + + q1 = Quantity("millifake_meter", abbrev="mm") + q2 = Quantity("centifake_meter", abbrev="cm") + q3 = Quantity("decifake_meter", abbrev="dm") + + SI.set_quantity_dimension(q1, length) + + SI.set_quantity_scale_factor(q1, PREFIXES["m"]) + SI.set_quantity_scale_factor(q1, PREFIXES["c"]) + SI.set_quantity_scale_factor(q1, PREFIXES["d"]) + + res = [q1, q2, q3] + + prefs = prefix_unit(m, pref) + assert set(prefs) == set(res) + assert {v.abbrev for v in prefs} == set(symbols("mm,cm,dm")) + + +def test_bases(): + assert kilo.base == 10 + assert kibi.base == 2 + + +def test_repr(): + assert eval(repr(kilo)) == kilo + assert eval(repr(kibi)) == kibi diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_unit_system_cgs_gauss.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_unit_system_cgs_gauss.py new file mode 100644 index 0000000000000000000000000000000000000000..12629280785c94fa8be33bc97bdd714140a3e346 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_unit_system_cgs_gauss.py @@ -0,0 +1,55 @@ +from sympy.concrete.tests.test_sums_products import NS + +from sympy.core.singleton import S +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.physics.units import convert_to, coulomb_constant, elementary_charge, gravitational_constant, planck +from sympy.physics.units.definitions.unit_definitions import angstrom, statcoulomb, coulomb, second, gram, centimeter, erg, \ + newton, joule, dyne, speed_of_light, meter, farad, henry, statvolt, volt, ohm +from sympy.physics.units.systems import SI +from sympy.physics.units.systems.cgs import cgs_gauss + + +def test_conversion_to_from_si(): + assert convert_to(statcoulomb, coulomb, cgs_gauss) == coulomb/2997924580 + assert convert_to(coulomb, statcoulomb, cgs_gauss) == 2997924580*statcoulomb + assert convert_to(statcoulomb, sqrt(gram*centimeter**3)/second, cgs_gauss) == centimeter**(S(3)/2)*sqrt(gram)/second + assert convert_to(coulomb, sqrt(gram*centimeter**3)/second, cgs_gauss) == 2997924580*centimeter**(S(3)/2)*sqrt(gram)/second + + # SI units have an additional base unit, no conversion in case of electromagnetism: + assert convert_to(coulomb, statcoulomb, SI) == coulomb + assert convert_to(statcoulomb, coulomb, SI) == statcoulomb + + # SI without electromagnetism: + assert convert_to(erg, joule, SI) == joule/10**7 + assert convert_to(erg, joule, cgs_gauss) == joule/10**7 + assert convert_to(joule, erg, SI) == 10**7*erg + assert convert_to(joule, erg, cgs_gauss) == 10**7*erg + + + assert convert_to(dyne, newton, SI) == newton/10**5 + assert convert_to(dyne, newton, cgs_gauss) == newton/10**5 + assert convert_to(newton, dyne, SI) == 10**5*dyne + assert convert_to(newton, dyne, cgs_gauss) == 10**5*dyne + + +def test_cgs_gauss_convert_constants(): + + assert convert_to(speed_of_light, centimeter/second, cgs_gauss) == 29979245800*centimeter/second + + assert convert_to(coulomb_constant, 1, cgs_gauss) == 1 + assert convert_to(coulomb_constant, newton*meter**2/coulomb**2, cgs_gauss) == 22468879468420441*meter**2*newton/(2500000*coulomb**2) + assert convert_to(coulomb_constant, newton*meter**2/coulomb**2, SI) == 22468879468420441*meter**2*newton/(2500000*coulomb**2) + assert convert_to(coulomb_constant, dyne*centimeter**2/statcoulomb**2, cgs_gauss) == centimeter**2*dyne/statcoulomb**2 + assert convert_to(coulomb_constant, 1, SI) == coulomb_constant + assert NS(convert_to(coulomb_constant, newton*meter**2/coulomb**2, SI)) == '8987551787.36818*meter**2*newton/coulomb**2' + + assert convert_to(elementary_charge, statcoulomb, cgs_gauss) + assert convert_to(angstrom, centimeter, cgs_gauss) == 1*centimeter/10**8 + assert convert_to(gravitational_constant, dyne*centimeter**2/gram**2, cgs_gauss) + assert NS(convert_to(planck, erg*second, cgs_gauss)) == '6.62607015e-27*erg*second' + + spc = 25000*second/(22468879468420441*centimeter) + assert convert_to(ohm, second/centimeter, cgs_gauss) == spc + assert convert_to(henry, second**2/centimeter, cgs_gauss) == spc*second + assert convert_to(volt, statvolt, cgs_gauss) == 10**6*statvolt/299792458 + assert convert_to(farad, centimeter, cgs_gauss) == 299792458**2*centimeter/10**5 diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_unitsystem.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_unitsystem.py new file mode 100644 index 0000000000000000000000000000000000000000..a04f3aabb6274bed4f1b82ac0719fa618b55eed7 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_unitsystem.py @@ -0,0 +1,86 @@ +from sympy.physics.units import DimensionSystem, joule, second, ampere + +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.physics.units.definitions import c, kg, m, s +from sympy.physics.units.definitions.dimension_definitions import length, time +from sympy.physics.units.quantities import Quantity +from sympy.physics.units.unitsystem import UnitSystem +from sympy.physics.units.util import convert_to + + +def test_definition(): + # want to test if the system can have several units of the same dimension + dm = Quantity("dm") + base = (m, s) + # base_dim = (m.dimension, s.dimension) + ms = UnitSystem(base, (c, dm), "MS", "MS system") + ms.set_quantity_dimension(dm, length) + ms.set_quantity_scale_factor(dm, Rational(1, 10)) + + assert set(ms._base_units) == set(base) + assert set(ms._units) == {m, s, c, dm} + # assert ms._units == DimensionSystem._sort_dims(base + (velocity,)) + assert ms.name == "MS" + assert ms.descr == "MS system" + + +def test_str_repr(): + assert str(UnitSystem((m, s), name="MS")) == "MS" + assert str(UnitSystem((m, s))) == "UnitSystem((meter, second))" + + assert repr(UnitSystem((m, s))) == "" % (m, s) + + +def test_convert_to(): + A = Quantity("A") + A.set_global_relative_scale_factor(S.One, ampere) + + Js = Quantity("Js") + Js.set_global_relative_scale_factor(S.One, joule*second) + + mksa = UnitSystem((m, kg, s, A), (Js,)) + assert convert_to(Js, mksa._base_units) == m**2*kg*s**-1/1000 + + +def test_extend(): + ms = UnitSystem((m, s), (c,)) + Js = Quantity("Js") + Js.set_global_relative_scale_factor(1, joule*second) + mks = ms.extend((kg,), (Js,)) + + res = UnitSystem((m, s, kg), (c, Js)) + assert set(mks._base_units) == set(res._base_units) + assert set(mks._units) == set(res._units) + + +def test_dim(): + dimsys = UnitSystem((m, kg, s), (c,)) + assert dimsys.dim == 3 + + +def test_is_consistent(): + dimension_system = DimensionSystem([length, time]) + us = UnitSystem([m, s], dimension_system=dimension_system) + assert us.is_consistent == True + + +def test_get_units_non_prefixed(): + from sympy.physics.units import volt, ohm + unit_system = UnitSystem.get_unit_system("SI") + units = unit_system.get_units_non_prefixed() + for prefix in ["giga", "tera", "peta", "exa", "zetta", "yotta", "kilo", "hecto", "deca", "deci", "centi", "milli", "micro", "nano", "pico", "femto", "atto", "zepto", "yocto"]: + for unit in units: + assert isinstance(unit, Quantity), f"{unit} must be a Quantity, not {type(unit)}" + assert not unit.is_prefixed, f"{unit} is marked as prefixed" + assert not unit.is_physical_constant, f"{unit} is marked as physics constant" + assert not unit.name.name.startswith(prefix), f"Unit {unit.name} has prefix {prefix}" + assert volt in units + assert ohm in units + +def test_derived_units_must_exist_in_unit_system(): + for unit_system in UnitSystem._unit_systems.values(): + for preferred_unit in unit_system.derived_units.values(): + units = preferred_unit.atoms(Quantity) + for unit in units: + assert unit in unit_system._units, f"Unit {unit} is not in unit system {unit_system}" diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_util.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_util.py new file mode 100644 index 0000000000000000000000000000000000000000..3522af675d33275f322e2b731309e19bffde1e1d --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/tests/test_util.py @@ -0,0 +1,178 @@ +from sympy.core.containers import Tuple +from sympy.core.numbers import pi +from sympy.core.power import Pow +from sympy.core.symbol import symbols +from sympy.core.sympify import sympify +from sympy.printing.str import sstr +from sympy.physics.units import ( + G, centimeter, coulomb, day, degree, gram, hbar, hour, inch, joule, kelvin, + kilogram, kilometer, length, meter, mile, minute, newton, planck, + planck_length, planck_mass, planck_temperature, planck_time, radians, + second, speed_of_light, steradian, time, km) +from sympy.physics.units.util import convert_to, check_dimensions +from sympy.testing.pytest import raises +from sympy.functions.elementary.miscellaneous import sqrt + + +def NS(e, n=15, **options): + return sstr(sympify(e).evalf(n, **options), full_prec=True) + + +L = length +T = time + + +def test_dim_simplify_add(): + # assert Add(L, L) == L + assert L + L == L + + +def test_dim_simplify_mul(): + # assert Mul(L, T) == L*T + assert L*T == L*T + + +def test_dim_simplify_pow(): + assert Pow(L, 2) == L**2 + + +def test_dim_simplify_rec(): + # assert Mul(Add(L, L), T) == L*T + assert (L + L) * T == L*T + + +def test_convert_to_quantities(): + assert convert_to(3, meter) == 3 + + assert convert_to(mile, kilometer) == 25146*kilometer/15625 + assert convert_to(meter/second, speed_of_light) == speed_of_light/299792458 + assert convert_to(299792458*meter/second, speed_of_light) == speed_of_light + assert convert_to(2*299792458*meter/second, speed_of_light) == 2*speed_of_light + assert convert_to(speed_of_light, meter/second) == 299792458*meter/second + assert convert_to(2*speed_of_light, meter/second) == 599584916*meter/second + assert convert_to(day, second) == 86400*second + assert convert_to(2*hour, minute) == 120*minute + assert convert_to(mile, meter) == 201168*meter/125 + assert convert_to(mile/hour, kilometer/hour) == 25146*kilometer/(15625*hour) + assert convert_to(3*newton, meter/second) == 3*newton + assert convert_to(3*newton, kilogram*meter/second**2) == 3*meter*kilogram/second**2 + assert convert_to(kilometer + mile, meter) == 326168*meter/125 + assert convert_to(2*kilometer + 3*mile, meter) == 853504*meter/125 + assert convert_to(inch**2, meter**2) == 16129*meter**2/25000000 + assert convert_to(3*inch**2, meter) == 48387*meter**2/25000000 + assert convert_to(2*kilometer/hour + 3*mile/hour, meter/second) == 53344*meter/(28125*second) + assert convert_to(2*kilometer/hour + 3*mile/hour, centimeter/second) == 213376*centimeter/(1125*second) + assert convert_to(kilometer * (mile + kilometer), meter) == 2609344 * meter ** 2 + + assert convert_to(steradian, coulomb) == steradian + assert convert_to(radians, degree) == 180*degree/pi + assert convert_to(radians, [meter, degree]) == 180*degree/pi + assert convert_to(pi*radians, degree) == 180*degree + assert convert_to(pi, degree) == 180*degree + + # https://github.com/sympy/sympy/issues/26263 + assert convert_to(sqrt(meter**2 + meter**2.0), meter) == sqrt(meter**2 + meter**2.0) + assert convert_to((meter**2 + meter**2.0)**2, meter) == (meter**2 + meter**2.0)**2 + + +def test_convert_to_tuples_of_quantities(): + from sympy.core.symbol import symbols + + alpha, beta = symbols('alpha beta') + + assert convert_to(speed_of_light, [meter, second]) == 299792458 * meter / second + assert convert_to(speed_of_light, (meter, second)) == 299792458 * meter / second + assert convert_to(speed_of_light, Tuple(meter, second)) == 299792458 * meter / second + assert convert_to(joule, [meter, kilogram, second]) == kilogram*meter**2/second**2 + assert convert_to(joule, [centimeter, gram, second]) == 10000000*centimeter**2*gram/second**2 + assert convert_to(299792458*meter/second, [speed_of_light]) == speed_of_light + assert convert_to(speed_of_light / 2, [meter, second, kilogram]) == meter/second*299792458 / 2 + # This doesn't make physically sense, but let's keep it as a conversion test: + assert convert_to(2 * speed_of_light, [meter, second, kilogram]) == 2 * 299792458 * meter / second + assert convert_to(G, [G, speed_of_light, planck]) == 1.0*G + + assert NS(convert_to(meter, [G, speed_of_light, hbar]), n=7) == '6.187142e+34*gravitational_constant**0.5000000*hbar**0.5000000/speed_of_light**1.500000' + assert NS(convert_to(planck_mass, kilogram), n=7) == '2.176434e-8*kilogram' + assert NS(convert_to(planck_length, meter), n=7) == '1.616255e-35*meter' + assert NS(convert_to(planck_time, second), n=6) == '5.39125e-44*second' + assert NS(convert_to(planck_temperature, kelvin), n=7) == '1.416784e+32*kelvin' + assert NS(convert_to(convert_to(meter, [G, speed_of_light, planck]), meter), n=10) == '1.000000000*meter' + + # similar to https://github.com/sympy/sympy/issues/26263 + assert convert_to(sqrt(meter**2 + second**2.0), [meter, second]) == sqrt(meter**2 + second**2.0) + assert convert_to((meter**2 + second**2.0)**2, [meter, second]) == (meter**2 + second**2.0)**2 + + # similar to https://github.com/sympy/sympy/issues/21463 + assert convert_to(1/(beta*meter + meter), 1/meter) == 1/(beta*meter + meter) + assert convert_to(1/(beta*meter + alpha*meter), 1/kilometer) == (1/(kilometer*beta/1000 + alpha*kilometer/1000)) + +def test_eval_simplify(): + from sympy.physics.units import cm, mm, km, m, K, kilo + from sympy.core.symbol import symbols + + x, y = symbols('x y') + + assert (cm/mm).simplify() == 10 + assert (km/m).simplify() == 1000 + assert (km/cm).simplify() == 100000 + assert (10*x*K*km**2/m/cm).simplify() == 1000000000*x*kelvin + assert (cm/km/m).simplify() == 1/(10000000*centimeter) + + assert (3*kilo*meter).simplify() == 3000*meter + assert (4*kilo*meter/(2*kilometer)).simplify() == 2 + assert (4*kilometer**2/(kilo*meter)**2).simplify() == 4 + + +def test_quantity_simplify(): + from sympy.physics.units.util import quantity_simplify + from sympy.physics.units import kilo, foot + from sympy.core.symbol import symbols + + x, y = symbols('x y') + + assert quantity_simplify(x*(8*kilo*newton*meter + y)) == x*(8000*meter*newton + y) + assert quantity_simplify(foot*inch*(foot + inch)) == foot**2*(foot + foot/12)/12 + assert quantity_simplify(foot*inch*(foot*foot + inch*(foot + inch))) == foot**2*(foot**2 + foot/12*(foot + foot/12))/12 + assert quantity_simplify(2**(foot/inch*kilo/1000)*inch) == 4096*foot/12 + assert quantity_simplify(foot**2*inch + inch**2*foot) == 13*foot**3/144 + +def test_quantity_simplify_across_dimensions(): + from sympy.physics.units.util import quantity_simplify + from sympy.physics.units import ampere, ohm, volt, joule, pascal, farad, second, watt, siemens, henry, tesla, weber, hour, newton + + assert quantity_simplify(ampere*ohm, across_dimensions=True, unit_system="SI") == volt + assert quantity_simplify(6*ampere*ohm, across_dimensions=True, unit_system="SI") == 6*volt + assert quantity_simplify(volt/ampere, across_dimensions=True, unit_system="SI") == ohm + assert quantity_simplify(volt/ohm, across_dimensions=True, unit_system="SI") == ampere + assert quantity_simplify(joule/meter**3, across_dimensions=True, unit_system="SI") == pascal + assert quantity_simplify(farad*ohm, across_dimensions=True, unit_system="SI") == second + assert quantity_simplify(joule/second, across_dimensions=True, unit_system="SI") == watt + assert quantity_simplify(meter**3/second, across_dimensions=True, unit_system="SI") == meter**3/second + assert quantity_simplify(joule/second, across_dimensions=True, unit_system="SI") == watt + + assert quantity_simplify(joule/coulomb, across_dimensions=True, unit_system="SI") == volt + assert quantity_simplify(volt/ampere, across_dimensions=True, unit_system="SI") == ohm + assert quantity_simplify(ampere/volt, across_dimensions=True, unit_system="SI") == siemens + assert quantity_simplify(coulomb/volt, across_dimensions=True, unit_system="SI") == farad + assert quantity_simplify(volt*second/ampere, across_dimensions=True, unit_system="SI") == henry + assert quantity_simplify(volt*second/meter**2, across_dimensions=True, unit_system="SI") == tesla + assert quantity_simplify(joule/ampere, across_dimensions=True, unit_system="SI") == weber + + assert quantity_simplify(5*kilometer/hour, across_dimensions=True, unit_system="SI") == 25*meter/(18*second) + assert quantity_simplify(5*kilogram*meter/second**2, across_dimensions=True, unit_system="SI") == 5*newton + +def test_check_dimensions(): + x = symbols('x') + assert check_dimensions(inch + x) == inch + x + assert check_dimensions(length + x) == length + x + # after subs we get 2*length; check will clear the constant + assert check_dimensions((length + x).subs(x, length)) == length + assert check_dimensions(newton*meter + joule) == joule + meter*newton + raises(ValueError, lambda: check_dimensions(inch + 1)) + raises(ValueError, lambda: check_dimensions(length + 1)) + raises(ValueError, lambda: check_dimensions(length + time)) + raises(ValueError, lambda: check_dimensions(meter + second)) + raises(ValueError, lambda: check_dimensions(2 * meter + second)) + raises(ValueError, lambda: check_dimensions(2 * meter + 3 * second)) + raises(ValueError, lambda: check_dimensions(1 / second + 1 / meter)) + raises(ValueError, lambda: check_dimensions(2 * meter*(mile + centimeter) + km)) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/unitsystem.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/unitsystem.py new file mode 100644 index 0000000000000000000000000000000000000000..5705c821c217f781717f9dd5cad6f3c9c77b145f --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/unitsystem.py @@ -0,0 +1,205 @@ +""" +Unit system for physical quantities; include definition of constants. +""" + +from typing import Dict as tDict, Set as tSet + +from sympy.core.add import Add +from sympy.core.function import (Derivative, Function) +from sympy.core.mul import Mul +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.physics.units.dimensions import _QuantityMapper +from sympy.physics.units.quantities import Quantity + +from .dimensions import Dimension + + +class UnitSystem(_QuantityMapper): + """ + UnitSystem represents a coherent set of units. + + A unit system is basically a dimension system with notions of scales. Many + of the methods are defined in the same way. + + It is much better if all base units have a symbol. + """ + + _unit_systems = {} # type: tDict[str, UnitSystem] + + def __init__(self, base_units, units=(), name="", descr="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}): + + UnitSystem._unit_systems[name] = self + + self.name = name + self.descr = descr + + self._base_units = base_units + self._dimension_system = dimension_system + self._units = tuple(set(base_units) | set(units)) + self._base_units = tuple(base_units) + self._derived_units = derived_units + + super().__init__() + + def __str__(self): + """ + Return the name of the system. + + If it does not exist, then it makes a list of symbols (or names) of + the base dimensions. + """ + + if self.name != "": + return self.name + else: + return "UnitSystem((%s))" % ", ".join( + str(d) for d in self._base_units) + + def __repr__(self): + return '' % repr(self._base_units) + + def extend(self, base, units=(), name="", description="", dimension_system=None, derived_units: tDict[Dimension, Quantity]={}): + """Extend the current system into a new one. + + Take the base and normal units of the current system to merge + them to the base and normal units given in argument. + If not provided, name and description are overridden by empty strings. + """ + + base = self._base_units + tuple(base) + units = self._units + tuple(units) + + return UnitSystem(base, units, name, description, dimension_system, {**self._derived_units, **derived_units}) + + def get_dimension_system(self): + return self._dimension_system + + def get_quantity_dimension(self, unit): + qdm = self.get_dimension_system()._quantity_dimension_map + if unit in qdm: + return qdm[unit] + return super().get_quantity_dimension(unit) + + def get_quantity_scale_factor(self, unit): + qsfm = self.get_dimension_system()._quantity_scale_factors + if unit in qsfm: + return qsfm[unit] + return super().get_quantity_scale_factor(unit) + + @staticmethod + def get_unit_system(unit_system): + if isinstance(unit_system, UnitSystem): + return unit_system + + if unit_system not in UnitSystem._unit_systems: + raise ValueError( + "Unit system is not supported. Currently" + "supported unit systems are {}".format( + ", ".join(sorted(UnitSystem._unit_systems)) + ) + ) + + return UnitSystem._unit_systems[unit_system] + + @staticmethod + def get_default_unit_system(): + return UnitSystem._unit_systems["SI"] + + @property + def dim(self): + """ + Give the dimension of the system. + + That is return the number of units forming the basis. + """ + return len(self._base_units) + + @property + def is_consistent(self): + """ + Check if the underlying dimension system is consistent. + """ + # test is performed in DimensionSystem + return self.get_dimension_system().is_consistent + + @property + def derived_units(self) -> tDict[Dimension, Quantity]: + return self._derived_units + + def get_dimensional_expr(self, expr): + from sympy.physics.units import Quantity + if isinstance(expr, Mul): + return Mul(*[self.get_dimensional_expr(i) for i in expr.args]) + elif isinstance(expr, Pow): + return self.get_dimensional_expr(expr.base) ** expr.exp + elif isinstance(expr, Add): + return self.get_dimensional_expr(expr.args[0]) + elif isinstance(expr, Derivative): + dim = self.get_dimensional_expr(expr.expr) + for independent, count in expr.variable_count: + dim /= self.get_dimensional_expr(independent)**count + return dim + elif isinstance(expr, Function): + args = [self.get_dimensional_expr(arg) for arg in expr.args] + if all(i == 1 for i in args): + return S.One + return expr.func(*args) + elif isinstance(expr, Quantity): + return self.get_quantity_dimension(expr).name + return S.One + + def _collect_factor_and_dimension(self, expr): + """ + Return tuple with scale factor expression and dimension expression. + """ + from sympy.physics.units import Quantity + if isinstance(expr, Quantity): + return expr.scale_factor, expr.dimension + elif isinstance(expr, Mul): + factor = 1 + dimension = Dimension(1) + for arg in expr.args: + arg_factor, arg_dim = self._collect_factor_and_dimension(arg) + factor *= arg_factor + dimension *= arg_dim + return factor, dimension + elif isinstance(expr, Pow): + factor, dim = self._collect_factor_and_dimension(expr.base) + exp_factor, exp_dim = self._collect_factor_and_dimension(expr.exp) + if self.get_dimension_system().is_dimensionless(exp_dim): + exp_dim = 1 + return factor ** exp_factor, dim ** (exp_factor * exp_dim) + elif isinstance(expr, Add): + factor, dim = self._collect_factor_and_dimension(expr.args[0]) + for addend in expr.args[1:]: + addend_factor, addend_dim = \ + self._collect_factor_and_dimension(addend) + if not self.get_dimension_system().equivalent_dims(dim, addend_dim): + raise ValueError( + 'Dimension of "{}" is {}, ' + 'but it should be {}'.format( + addend, addend_dim, dim)) + factor += addend_factor + return factor, dim + elif isinstance(expr, Derivative): + factor, dim = self._collect_factor_and_dimension(expr.args[0]) + for independent, count in expr.variable_count: + ifactor, idim = self._collect_factor_and_dimension(independent) + factor /= ifactor**count + dim /= idim**count + return factor, dim + elif isinstance(expr, Function): + fds = [self._collect_factor_and_dimension(arg) for arg in expr.args] + dims = [Dimension(1) if self.get_dimension_system().is_dimensionless(d[1]) else d[1] for d in fds] + return (expr.func(*(f[0] for f in fds)), *dims) + elif isinstance(expr, Dimension): + return S.One, expr + else: + return expr, Dimension(1) + + def get_units_non_prefixed(self) -> tSet[Quantity]: + """ + Return the units of the system that do not have a prefix. + """ + return set(filter(lambda u: not u.is_prefixed and not u.is_physical_constant, self._units)) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/util.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/util.py new file mode 100644 index 0000000000000000000000000000000000000000..b3f5a004fe9fa93b11039c5a832829715a7d44c6 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/units/util.py @@ -0,0 +1,265 @@ +""" +Several methods to simplify expressions involving unit objects. +""" +from functools import reduce +from collections.abc import Iterable +from typing import Optional + +from sympy import default_sort_key +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.mul import Mul +from sympy.core.power import Pow +from sympy.core.sorting import ordered +from sympy.core.sympify import sympify +from sympy.core.function import Function +from sympy.matrices.exceptions import NonInvertibleMatrixError +from sympy.physics.units.dimensions import Dimension, DimensionSystem +from sympy.physics.units.prefixes import Prefix +from sympy.physics.units.quantities import Quantity +from sympy.physics.units.unitsystem import UnitSystem +from sympy.utilities.iterables import sift + + +def _get_conversion_matrix_for_expr(expr, target_units, unit_system): + from sympy.matrices.dense import Matrix + + dimension_system = unit_system.get_dimension_system() + + expr_dim = Dimension(unit_system.get_dimensional_expr(expr)) + dim_dependencies = dimension_system.get_dimensional_dependencies(expr_dim, mark_dimensionless=True) + target_dims = [Dimension(unit_system.get_dimensional_expr(x)) for x in target_units] + canon_dim_units = [i for x in target_dims for i in dimension_system.get_dimensional_dependencies(x, mark_dimensionless=True)] + canon_expr_units = set(dim_dependencies) + + if not canon_expr_units.issubset(set(canon_dim_units)): + return None + + seen = set() + canon_dim_units = [i for i in canon_dim_units if not (i in seen or seen.add(i))] + + camat = Matrix([[dimension_system.get_dimensional_dependencies(i, mark_dimensionless=True).get(j, 0) for i in target_dims] for j in canon_dim_units]) + exprmat = Matrix([dim_dependencies.get(k, 0) for k in canon_dim_units]) + + try: + res_exponents = camat.solve(exprmat) + except NonInvertibleMatrixError: + return None + + return res_exponents + + +def convert_to(expr, target_units, unit_system="SI"): + """ + Convert ``expr`` to the same expression with all of its units and quantities + represented as factors of ``target_units``, whenever the dimension is compatible. + + ``target_units`` may be a single unit/quantity, or a collection of + units/quantities. + + Examples + ======== + + >>> from sympy.physics.units import speed_of_light, meter, gram, second, day + >>> from sympy.physics.units import mile, newton, kilogram, atomic_mass_constant + >>> from sympy.physics.units import kilometer, centimeter + >>> from sympy.physics.units import gravitational_constant, hbar + >>> from sympy.physics.units import convert_to + >>> convert_to(mile, kilometer) + 25146*kilometer/15625 + >>> convert_to(mile, kilometer).n() + 1.609344*kilometer + >>> convert_to(speed_of_light, meter/second) + 299792458*meter/second + >>> convert_to(day, second) + 86400*second + >>> 3*newton + 3*newton + >>> convert_to(3*newton, kilogram*meter/second**2) + 3*kilogram*meter/second**2 + >>> convert_to(atomic_mass_constant, gram) + 1.660539060e-24*gram + + Conversion to multiple units: + + >>> convert_to(speed_of_light, [meter, second]) + 299792458*meter/second + >>> convert_to(3*newton, [centimeter, gram, second]) + 300000*centimeter*gram/second**2 + + Conversion to Planck units: + + >>> convert_to(atomic_mass_constant, [gravitational_constant, speed_of_light, hbar]).n() + 7.62963087839509e-20*hbar**0.5*speed_of_light**0.5/gravitational_constant**0.5 + + """ + from sympy.physics.units import UnitSystem + unit_system = UnitSystem.get_unit_system(unit_system) + + if not isinstance(target_units, (Iterable, Tuple)): + target_units = [target_units] + + def handle_Adds(expr): + return Add.fromiter(convert_to(i, target_units, unit_system) + for i in expr.args) + + if isinstance(expr, Add): + return handle_Adds(expr) + elif isinstance(expr, Pow) and isinstance(expr.base, Add): + return handle_Adds(expr.base) ** expr.exp + + expr = sympify(expr) + target_units = sympify(target_units) + + if isinstance(expr, Function): + expr = expr.together() + + if not isinstance(expr, Quantity) and expr.has(Quantity): + expr = expr.replace(lambda x: isinstance(x, Quantity), + lambda x: x.convert_to(target_units, unit_system)) + + def get_total_scale_factor(expr): + if isinstance(expr, Mul): + return reduce(lambda x, y: x * y, + [get_total_scale_factor(i) for i in expr.args]) + elif isinstance(expr, Pow): + return get_total_scale_factor(expr.base) ** expr.exp + elif isinstance(expr, Quantity): + return unit_system.get_quantity_scale_factor(expr) + return expr + + depmat = _get_conversion_matrix_for_expr(expr, target_units, unit_system) + if depmat is None: + return expr + + expr_scale_factor = get_total_scale_factor(expr) + return expr_scale_factor * Mul.fromiter( + (1/get_total_scale_factor(u)*u)**p for u, p in + zip(target_units, depmat)) + + +def quantity_simplify(expr, across_dimensions: bool=False, unit_system=None): + """Return an equivalent expression in which prefixes are replaced + with numerical values and all units of a given dimension are the + unified in a canonical manner by default. `across_dimensions` allows + for units of different dimensions to be simplified together. + + `unit_system` must be specified if `across_dimensions` is True. + + Examples + ======== + + >>> from sympy.physics.units.util import quantity_simplify + >>> from sympy.physics.units.prefixes import kilo + >>> from sympy.physics.units import foot, inch, joule, coulomb + >>> quantity_simplify(kilo*foot*inch) + 250*foot**2/3 + >>> quantity_simplify(foot - 6*inch) + foot/2 + >>> quantity_simplify(5*joule/coulomb, across_dimensions=True, unit_system="SI") + 5*volt + """ + + if expr.is_Atom or not expr.has(Prefix, Quantity): + return expr + + # replace all prefixes with numerical values + p = expr.atoms(Prefix) + expr = expr.xreplace({p: p.scale_factor for p in p}) + + # replace all quantities of given dimension with a canonical + # quantity, chosen from those in the expression + d = sift(expr.atoms(Quantity), lambda i: i.dimension) + for k in d: + if len(d[k]) == 1: + continue + v = list(ordered(d[k])) + ref = v[0]/v[0].scale_factor + expr = expr.xreplace({vi: ref*vi.scale_factor for vi in v[1:]}) + + if across_dimensions: + # combine quantities of different dimensions into a single + # quantity that is equivalent to the original expression + + if unit_system is None: + raise ValueError("unit_system must be specified if across_dimensions is True") + + unit_system = UnitSystem.get_unit_system(unit_system) + dimension_system: DimensionSystem = unit_system.get_dimension_system() + dim_expr = unit_system.get_dimensional_expr(expr) + dim_deps = dimension_system.get_dimensional_dependencies(dim_expr, mark_dimensionless=True) + + target_dimension: Optional[Dimension] = None + for ds_dim, ds_dim_deps in dimension_system.dimensional_dependencies.items(): + if ds_dim_deps == dim_deps: + target_dimension = ds_dim + break + + if target_dimension is None: + # if we can't find a target dimension, we can't do anything. unsure how to handle this case. + return expr + + target_unit = unit_system.derived_units.get(target_dimension) + if target_unit: + expr = convert_to(expr, target_unit, unit_system) + + return expr + + +def check_dimensions(expr, unit_system="SI"): + """Return expr if units in addends have the same + base dimensions, else raise a ValueError.""" + # the case of adding a number to a dimensional quantity + # is ignored for the sake of SymPy core routines, so this + # function will raise an error now if such an addend is + # found. + # Also, when doing substitutions, multiplicative constants + # might be introduced, so remove those now + + from sympy.physics.units import UnitSystem + unit_system = UnitSystem.get_unit_system(unit_system) + + def addDict(dict1, dict2): + """Merge dictionaries by adding values of common keys and + removing keys with value of 0.""" + dict3 = {**dict1, **dict2} + for key, value in dict3.items(): + if key in dict1 and key in dict2: + dict3[key] = value + dict1[key] + return {key:val for key, val in dict3.items() if val != 0} + + adds = expr.atoms(Add) + DIM_OF = unit_system.get_dimension_system().get_dimensional_dependencies + for a in adds: + deset = set() + for ai in a.args: + if ai.is_number: + deset.add(()) + continue + dims = [] + skip = False + dimdict = {} + for i in Mul.make_args(ai): + if i.has(Quantity): + i = Dimension(unit_system.get_dimensional_expr(i)) + if i.has(Dimension): + dimdict = addDict(dimdict, DIM_OF(i)) + elif i.free_symbols: + skip = True + break + dims.extend(dimdict.items()) + if not skip: + deset.add(tuple(sorted(dims, key=default_sort_key))) + if len(deset) > 1: + raise ValueError( + "addends have incompatible dimensions: {}".format(deset)) + + # clear multiplicative constants on Dimensions which may be + # left after substitution + reps = {} + for m in expr.atoms(Mul): + if any(isinstance(i, Dimension) for i in m.args): + reps[m] = m.func(*[ + i for i in m.args if not i.is_number]) + + return expr.xreplace(reps) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/__pycache__/test_functions.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/__pycache__/test_functions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f2f6e962dc5dad9e813a0c51009ead64d7f78dc Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/__pycache__/test_functions.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/__pycache__/test_printing.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/__pycache__/test_printing.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6503de0a12c270d3c69e3e4f830de1e6f2d6c9d3 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/__pycache__/test_printing.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/test_vector.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/test_vector.py new file mode 100644 index 0000000000000000000000000000000000000000..2b9c154e60be553228d37eec609dfc23120935ff --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/physics/vector/tests/test_vector.py @@ -0,0 +1,274 @@ +from sympy.core.numbers import (Float, pi) +from sympy.core.symbol import symbols +from sympy.core.sorting import ordered +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.matrices.immutable import ImmutableDenseMatrix as Matrix +from sympy.physics.vector import ReferenceFrame, Vector, dynamicsymbols, dot +from sympy.physics.vector.vector import VectorTypeError +from sympy.abc import x, y, z +from sympy.testing.pytest import raises + +A = ReferenceFrame('A') + + +def test_free_dynamicsymbols(): + A, B, C, D = symbols('A, B, C, D', cls=ReferenceFrame) + a, b, c, d, e, f = dynamicsymbols('a, b, c, d, e, f') + B.orient_axis(A, a, A.x) + C.orient_axis(B, b, B.y) + D.orient_axis(C, c, C.x) + + v = d*D.x + e*D.y + f*D.z + + assert set(ordered(v.free_dynamicsymbols(A))) == {a, b, c, d, e, f} + assert set(ordered(v.free_dynamicsymbols(B))) == {b, c, d, e, f} + assert set(ordered(v.free_dynamicsymbols(C))) == {c, d, e, f} + assert set(ordered(v.free_dynamicsymbols(D))) == {d, e, f} + + +def test_Vector(): + assert A.x != A.y + assert A.y != A.z + assert A.z != A.x + + assert A.x + 0 == A.x + + v1 = x*A.x + y*A.y + z*A.z + v2 = x**2*A.x + y**2*A.y + z**2*A.z + v3 = v1 + v2 + v4 = v1 - v2 + + assert isinstance(v1, Vector) + assert dot(v1, A.x) == x + assert dot(v1, A.y) == y + assert dot(v1, A.z) == z + + assert isinstance(v2, Vector) + assert dot(v2, A.x) == x**2 + assert dot(v2, A.y) == y**2 + assert dot(v2, A.z) == z**2 + + assert isinstance(v3, Vector) + # We probably shouldn't be using simplify in dot... + assert dot(v3, A.x) == x**2 + x + assert dot(v3, A.y) == y**2 + y + assert dot(v3, A.z) == z**2 + z + + assert isinstance(v4, Vector) + # We probably shouldn't be using simplify in dot... + assert dot(v4, A.x) == x - x**2 + assert dot(v4, A.y) == y - y**2 + assert dot(v4, A.z) == z - z**2 + + assert v1.to_matrix(A) == Matrix([[x], [y], [z]]) + q = symbols('q') + B = A.orientnew('B', 'Axis', (q, A.x)) + assert v1.to_matrix(B) == Matrix([[x], + [ y * cos(q) + z * sin(q)], + [-y * sin(q) + z * cos(q)]]) + + #Test the separate method + B = ReferenceFrame('B') + v5 = x*A.x + y*A.y + z*B.z + assert Vector(0).separate() == {} + assert v1.separate() == {A: v1} + assert v5.separate() == {A: x*A.x + y*A.y, B: z*B.z} + + #Test the free_symbols property + v6 = x*A.x + y*A.y + z*A.z + assert v6.free_symbols(A) == {x,y,z} + + raises(TypeError, lambda: v3.applyfunc(v1)) + + +def test_Vector_diffs(): + q1, q2, q3, q4 = dynamicsymbols('q1 q2 q3 q4') + q1d, q2d, q3d, q4d = dynamicsymbols('q1 q2 q3 q4', 1) + q1dd, q2dd, q3dd, q4dd = dynamicsymbols('q1 q2 q3 q4', 2) + N = ReferenceFrame('N') + A = N.orientnew('A', 'Axis', [q3, N.z]) + B = A.orientnew('B', 'Axis', [q2, A.x]) + v1 = q2 * A.x + q3 * N.y + v2 = q3 * B.x + v1 + v3 = v1.dt(B) + v4 = v2.dt(B) + v5 = q1*A.x + q2*A.y + q3*A.z + + assert v1.dt(N) == q2d * A.x + q2 * q3d * A.y + q3d * N.y + assert v1.dt(A) == q2d * A.x + q3 * q3d * N.x + q3d * N.y + assert v1.dt(B) == (q2d * A.x + q3 * q3d * N.x + q3d * + N.y - q3 * cos(q3) * q2d * N.z) + assert v2.dt(N) == (q2d * A.x + (q2 + q3) * q3d * A.y + q3d * B.x + q3d * + N.y) + assert v2.dt(A) == q2d * A.x + q3d * B.x + q3 * q3d * N.x + q3d * N.y + assert v2.dt(B) == (q2d * A.x + q3d * B.x + q3 * q3d * N.x + q3d * N.y - + q3 * cos(q3) * q2d * N.z) + assert v3.dt(N) == (q2dd * A.x + q2d * q3d * A.y + (q3d**2 + q3 * q3dd) * + N.x + q3dd * N.y + (q3 * sin(q3) * q2d * q3d - + cos(q3) * q2d * q3d - q3 * cos(q3) * q2dd) * N.z) + assert v3.dt(A) == (q2dd * A.x + (2 * q3d**2 + q3 * q3dd) * N.x + (q3dd - + q3 * q3d**2) * N.y + (q3 * sin(q3) * q2d * q3d - + cos(q3) * q2d * q3d - q3 * cos(q3) * q2dd) * N.z) + assert (v3.dt(B) - (q2dd*A.x - q3*cos(q3)*q2d**2*A.y + (2*q3d**2 + + q3*q3dd)*N.x + (q3dd - q3*q3d**2)*N.y + (2*q3*sin(q3)*q2d*q3d - + 2*cos(q3)*q2d*q3d - q3*cos(q3)*q2dd)*N.z)).express(B).simplify() == 0 + assert v4.dt(N) == (q2dd * A.x + q3d * (q2d + q3d) * A.y + q3dd * B.x + + (q3d**2 + q3 * q3dd) * N.x + q3dd * N.y + (q3 * + sin(q3) * q2d * q3d - cos(q3) * q2d * q3d - q3 * + cos(q3) * q2dd) * N.z) + assert v4.dt(A) == (q2dd * A.x + q3dd * B.x + (2 * q3d**2 + q3 * q3dd) * + N.x + (q3dd - q3 * q3d**2) * N.y + (q3 * sin(q3) * + q2d * q3d - cos(q3) * q2d * q3d - q3 * cos(q3) * + q2dd) * N.z) + assert (v4.dt(B) - (q2dd*A.x - q3*cos(q3)*q2d**2*A.y + q3dd*B.x + + (2*q3d**2 + q3*q3dd)*N.x + (q3dd - q3*q3d**2)*N.y + + (2*q3*sin(q3)*q2d*q3d - 2*cos(q3)*q2d*q3d - + q3*cos(q3)*q2dd)*N.z)).express(B).simplify() == 0 + assert v5.dt(B) == q1d*A.x + (q3*q2d + q2d)*A.y + (-q2*q2d + q3d)*A.z + assert v5.dt(A) == q1d*A.x + q2d*A.y + q3d*A.z + assert v5.dt(N) == (-q2*q3d + q1d)*A.x + (q1*q3d + q2d)*A.y + q3d*A.z + assert v3.diff(q1d, N) == 0 + assert v3.diff(q2d, N) == A.x - q3 * cos(q3) * N.z + assert v3.diff(q3d, N) == q3 * N.x + N.y + assert v3.diff(q1d, A) == 0 + assert v3.diff(q2d, A) == A.x - q3 * cos(q3) * N.z + assert v3.diff(q3d, A) == q3 * N.x + N.y + assert v3.diff(q1d, B) == 0 + assert v3.diff(q2d, B) == A.x - q3 * cos(q3) * N.z + assert v3.diff(q3d, B) == q3 * N.x + N.y + assert v4.diff(q1d, N) == 0 + assert v4.diff(q2d, N) == A.x - q3 * cos(q3) * N.z + assert v4.diff(q3d, N) == B.x + q3 * N.x + N.y + assert v4.diff(q1d, A) == 0 + assert v4.diff(q2d, A) == A.x - q3 * cos(q3) * N.z + assert v4.diff(q3d, A) == B.x + q3 * N.x + N.y + assert v4.diff(q1d, B) == 0 + assert v4.diff(q2d, B) == A.x - q3 * cos(q3) * N.z + assert v4.diff(q3d, B) == B.x + q3 * N.x + N.y + + # diff() should only express vector components in the derivative frame if + # the orientation of the component's frame depends on the variable + v6 = q2**2*N.y + q2**2*A.y + q2**2*B.y + # already expressed in N + n_measy = 2*q2 + # A_C_N does not depend on q2, so don't express in N + a_measy = 2*q2 + # B_C_N depends on q2, so express in N + b_measx = (q2**2*B.y).dot(N.x).diff(q2) + b_measy = (q2**2*B.y).dot(N.y).diff(q2) + b_measz = (q2**2*B.y).dot(N.z).diff(q2) + n_comp, a_comp = v6.diff(q2, N).args + assert len(v6.diff(q2, N).args) == 2 # only N and A parts + assert n_comp[1] == N + assert a_comp[1] == A + assert n_comp[0] == Matrix([b_measx, b_measy + n_measy, b_measz]) + assert a_comp[0] == Matrix([0, a_measy, 0]) + + +def test_vector_var_in_dcm(): + + N = ReferenceFrame('N') + A = ReferenceFrame('A') + B = ReferenceFrame('B') + u1, u2, u3, u4 = dynamicsymbols('u1 u2 u3 u4') + + v = u1 * u2 * A.x + u3 * N.y + u4**2 * N.z + + assert v.diff(u1, N, var_in_dcm=False) == u2 * A.x + assert v.diff(u1, A, var_in_dcm=False) == u2 * A.x + assert v.diff(u3, N, var_in_dcm=False) == N.y + assert v.diff(u3, A, var_in_dcm=False) == N.y + assert v.diff(u3, B, var_in_dcm=False) == N.y + assert v.diff(u4, N, var_in_dcm=False) == 2 * u4 * N.z + + raises(ValueError, lambda: v.diff(u1, N)) + + +def test_vector_simplify(): + x, y, z, k, n, m, w, f, s, A = symbols('x, y, z, k, n, m, w, f, s, A') + N = ReferenceFrame('N') + + test1 = (1 / x + 1 / y) * N.x + assert (test1 & N.x) != (x + y) / (x * y) + test1 = test1.simplify() + assert (test1 & N.x) == (x + y) / (x * y) + + test2 = (A**2 * s**4 / (4 * pi * k * m**3)) * N.x + test2 = test2.simplify() + assert (test2 & N.x) == (A**2 * s**4 / (4 * pi * k * m**3)) + + test3 = ((4 + 4 * x - 2 * (2 + 2 * x)) / (2 + 2 * x)) * N.x + test3 = test3.simplify() + assert (test3 & N.x) == 0 + + test4 = ((-4 * x * y**2 - 2 * y**3 - 2 * x**2 * y) / (x + y)**2) * N.x + test4 = test4.simplify() + assert (test4 & N.x) == -2 * y + + +def test_vector_evalf(): + a, b = symbols('a b') + v = pi * A.x + assert v.evalf(2) == Float('3.1416', 2) * A.x + v = pi * A.x + 5 * a * A.y - b * A.z + assert v.evalf(3) == Float('3.1416', 3) * A.x + Float('5', 3) * a * A.y - b * A.z + assert v.evalf(5, subs={a: 1.234, b:5.8973}) == Float('3.1415926536', 5) * A.x + Float('6.17', 5) * A.y - Float('5.8973', 5) * A.z + + +def test_vector_angle(): + A = ReferenceFrame('A') + v1 = A.x + A.y + v2 = A.z + assert v1.angle_between(v2) == pi/2 + B = ReferenceFrame('B') + B.orient_axis(A, A.x, pi) + v3 = A.x + v4 = B.x + assert v3.angle_between(v4) == 0 + + +def test_vector_xreplace(): + x, y, z = symbols('x y z') + v = x**2 * A.x + x*y * A.y + x*y*z * A.z + assert v.xreplace({x : cos(x)}) == cos(x)**2 * A.x + y*cos(x) * A.y + y*z*cos(x) * A.z + assert v.xreplace({x*y : pi}) == x**2 * A.x + pi * A.y + x*y*z * A.z + assert v.xreplace({x*y*z : 1}) == x**2*A.x + x*y*A.y + A.z + assert v.xreplace({x:1, z:0}) == A.x + y * A.y + raises(TypeError, lambda: v.xreplace()) + raises(TypeError, lambda: v.xreplace([x, y])) + +def test_issue_23366(): + u1 = dynamicsymbols('u1') + N = ReferenceFrame('N') + N_v_A = u1*N.x + raises(VectorTypeError, lambda: N_v_A.diff(N, u1)) + + +def test_vector_outer(): + a, b, c, d, e, f = symbols('a, b, c, d, e, f') + N = ReferenceFrame('N') + v1 = a*N.x + b*N.y + c*N.z + v2 = d*N.x + e*N.y + f*N.z + v1v2 = Matrix([[a*d, a*e, a*f], + [b*d, b*e, b*f], + [c*d, c*e, c*f]]) + assert v1.outer(v2).to_matrix(N) == v1v2 + assert (v1 | v2).to_matrix(N) == v1v2 + v2v1 = Matrix([[d*a, d*b, d*c], + [e*a, e*b, e*c], + [f*a, f*b, f*c]]) + assert v2.outer(v1).to_matrix(N) == v2v1 + assert (v2 | v1).to_matrix(N) == v2v1 + + +def test_overloaded_operators(): + a, b, c, d, e, f = symbols('a, b, c, d, e, f') + N = ReferenceFrame('N') + v1 = a*N.x + b*N.y + c*N.z + v2 = d*N.x + e*N.y + f*N.z + + assert v1 + v2 == v2 + v1 + assert v1 - v2 == -v2 + v1 + assert v1 & v2 == v2 & v1 + assert v1 ^ v2 == v1.cross(v2) + assert v2 ^ v1 == v2.cross(v1) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/__pycache__/appellseqs.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/__pycache__/appellseqs.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..544a12479bf78ac3a3e2ef4b6bf8e5b85786fe36 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/__pycache__/appellseqs.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/__pycache__/polymatrix.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/__pycache__/polymatrix.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d9ed5d0c503589a3c5879c198cf27e243b50c03 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/__pycache__/polymatrix.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/densearith.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/densearith.py new file mode 100644 index 0000000000000000000000000000000000000000..30bf9553b05e9dc53c95df25c6047ba785995b4f --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/densebasic.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/densebasic.py new file mode 100644 index 0000000000000000000000000000000000000000..b3a8a9497302b1af5bca20de100b7ae41e96b439 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/dispersion.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/dispersion.py new file mode 100644 index 0000000000000000000000000000000000000000..699277d221f24b9bff42c55c3bb34fe5783ae7a1 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/distributedmodules.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/distributedmodules.py new file mode 100644 index 0000000000000000000000000000000000000000..df4581e58951a9c29b9e5b085311f5e6cb00f381 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/domainmatrix.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/domainmatrix.py new file mode 100644 index 0000000000000000000000000000000000000000..c0ccaaa4cb96e0c49da58d8e9128c1b6fa551ade --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/factortools.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/factortools.py new file mode 100644 index 0000000000000000000000000000000000000000..021a6b06cb8802748deef6c69448ebc50503269b --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/fglmtools.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/fglmtools.py new file mode 100644 index 0000000000000000000000000000000000000000..d68fe5bc2a40741e39b89163d393f7b57e6b1c49 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/galoistools.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/galoistools.py new file mode 100644 index 0000000000000000000000000000000000000000..b09f85057eced59b8054c6007f2b291a35a2fafb --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/galoistools.py @@ -0,0 +1,2532 @@ +"""Dense univariate polynomials with coefficients in Galois fields. """ + +from math import ceil as _ceil, sqrt as _sqrt, prod + +from sympy.core.random import uniform, _randint +from sympy.external.gmpy import SYMPY_INTS, MPZ, invert +from sympy.polys.polyconfig import query +from sympy.polys.polyerrors import ExactQuotientFailed +from sympy.polys.polyutils import _sort_factors + + +def gf_crt(U, M, K=None): + """ + Chinese Remainder Theorem. + + Given a set of integer residues ``u_0,...,u_n`` and a set of + co-prime integer moduli ``m_0,...,m_n``, returns an integer + ``u``, such that ``u = u_i mod m_i`` for ``i = ``0,...,n``. + + Examples + ======== + + Consider a set of residues ``U = [49, 76, 65]`` + and a set of moduli ``M = [99, 97, 95]``. Then we have:: + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_crt + + >>> gf_crt([49, 76, 65], [99, 97, 95], ZZ) + 639985 + + This is the correct result because:: + + >>> [639985 % m for m in [99, 97, 95]] + [49, 76, 65] + + Note: this is a low-level routine with no error checking. + + See Also + ======== + + sympy.ntheory.modular.crt : a higher level crt routine + sympy.ntheory.modular.solve_congruence + + """ + p = prod(M, start=K.one) + v = K.zero + + for u, m in zip(U, M): + e = p // m + s, _, _ = K.gcdex(e, m) + v += e*(u*s % m) + + return v % p + + +def gf_crt1(M, K): + """ + First part of the Chinese Remainder Theorem. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_crt, gf_crt1, gf_crt2 + >>> U = [49, 76, 65] + >>> M = [99, 97, 95] + + The following two codes have the same result. + + >>> gf_crt(U, M, ZZ) + 639985 + + >>> p, E, S = gf_crt1(M, ZZ) + >>> gf_crt2(U, M, p, E, S, ZZ) + 639985 + + However, it is faster when we want to fix ``M`` and + compute for multiple U, i.e. the following cases: + + >>> p, E, S = gf_crt1(M, ZZ) + >>> Us = [[49, 76, 65], [23, 42, 67]] + >>> for U in Us: + ... print(gf_crt2(U, M, p, E, S, ZZ)) + 639985 + 236237 + + See Also + ======== + + sympy.ntheory.modular.crt1 : a higher level crt routine + sympy.polys.galoistools.gf_crt + sympy.polys.galoistools.gf_crt2 + + """ + E, S = [], [] + p = prod(M, start=K.one) + + for m in M: + E.append(p // m) + S.append(K.gcdex(E[-1], m)[0] % m) + + return p, E, S + + +def gf_crt2(U, M, p, E, S, K): + """ + Second part of the Chinese Remainder Theorem. + + See ``gf_crt1`` for usage. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_crt2 + + >>> U = [49, 76, 65] + >>> M = [99, 97, 95] + >>> p = 912285 + >>> E = [9215, 9405, 9603] + >>> S = [62, 24, 12] + + >>> gf_crt2(U, M, p, E, S, ZZ) + 639985 + + See Also + ======== + + sympy.ntheory.modular.crt2 : a higher level crt routine + sympy.polys.galoistools.gf_crt + sympy.polys.galoistools.gf_crt1 + + """ + v = K.zero + + for u, m, e, s in zip(U, M, E, S): + v += e*(u*s % m) + + return v % p + + +def gf_int(a, p): + """ + Coerce ``a mod p`` to an integer in the range ``[-p/2, p/2]``. + + Examples + ======== + + >>> from sympy.polys.galoistools import gf_int + + >>> gf_int(2, 7) + 2 + >>> gf_int(5, 7) + -2 + + """ + if a <= p // 2: + return a + else: + return a - p + + +def gf_degree(f): + """ + Return the leading degree of ``f``. + + Examples + ======== + + >>> from sympy.polys.galoistools import gf_degree + + >>> gf_degree([1, 1, 2, 0]) + 3 + >>> gf_degree([]) + -1 + + """ + return len(f) - 1 + + +def gf_LC(f, K): + """ + Return the leading coefficient of ``f``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_LC + + >>> gf_LC([3, 0, 1], ZZ) + 3 + + """ + if not f: + return K.zero + else: + return f[0] + + +def gf_TC(f, K): + """ + Return the trailing coefficient of ``f``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_TC + + >>> gf_TC([3, 0, 1], ZZ) + 1 + + """ + if not f: + return K.zero + else: + return f[-1] + + +def gf_strip(f): + """ + Remove leading zeros from ``f``. + + + Examples + ======== + + >>> from sympy.polys.galoistools import gf_strip + + >>> gf_strip([0, 0, 0, 3, 0, 1]) + [3, 0, 1] + + """ + if not f or f[0]: + return f + + k = 0 + + for coeff in f: + if coeff: + break + else: + k += 1 + + return f[k:] + + +def gf_trunc(f, p): + """ + Reduce all coefficients modulo ``p``. + + Examples + ======== + + >>> from sympy.polys.galoistools import gf_trunc + + >>> gf_trunc([7, -2, 3], 5) + [2, 3, 3] + + """ + return gf_strip([ a % p for a in f ]) + + +def gf_normal(f, p, K): + """ + Normalize all coefficients in ``K``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_normal + + >>> gf_normal([5, 10, 21, -3], 5, ZZ) + [1, 2] + + """ + return gf_trunc(list(map(K, f)), p) + + +def gf_from_dict(f, p, K): + """ + Create a ``GF(p)[x]`` polynomial from a dict. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_from_dict + + >>> gf_from_dict({10: ZZ(4), 4: ZZ(33), 0: ZZ(-1)}, 5, ZZ) + [4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4] + + """ + n, h = max(f.keys()), [] + + if isinstance(n, SYMPY_INTS): + for k in range(n, -1, -1): + h.append(f.get(k, K.zero) % p) + else: + (n,) = n + + for k in range(n, -1, -1): + h.append(f.get((k,), K.zero) % p) + + return gf_trunc(h, p) + + +def gf_to_dict(f, p, symmetric=True): + """ + Convert a ``GF(p)[x]`` polynomial to a dict. + + Examples + ======== + + >>> from sympy.polys.galoistools import gf_to_dict + + >>> gf_to_dict([4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4], 5) + {0: -1, 4: -2, 10: -1} + >>> gf_to_dict([4, 0, 0, 0, 0, 0, 3, 0, 0, 0, 4], 5, symmetric=False) + {0: 4, 4: 3, 10: 4} + + """ + n, result = gf_degree(f), {} + + for k in range(0, n + 1): + if symmetric: + a = gf_int(f[n - k], p) + else: + a = f[n - k] + + if a: + result[k] = a + + return result + + +def gf_from_int_poly(f, p): + """ + Create a ``GF(p)[x]`` polynomial from ``Z[x]``. + + Examples + ======== + + >>> from sympy.polys.galoistools import gf_from_int_poly + + >>> gf_from_int_poly([7, -2, 3], 5) + [2, 3, 3] + + """ + return gf_trunc(f, p) + + +def gf_to_int_poly(f, p, symmetric=True): + """ + Convert a ``GF(p)[x]`` polynomial to ``Z[x]``. + + + Examples + ======== + + >>> from sympy.polys.galoistools import gf_to_int_poly + + >>> gf_to_int_poly([2, 3, 3], 5) + [2, -2, -2] + >>> gf_to_int_poly([2, 3, 3], 5, symmetric=False) + [2, 3, 3] + + """ + if symmetric: + return [ gf_int(c, p) for c in f ] + else: + return f + + +def gf_neg(f, p, K): + """ + Negate a polynomial in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_neg + + >>> gf_neg([3, 2, 1, 0], 5, ZZ) + [2, 3, 4, 0] + + """ + return [ -coeff % p for coeff in f ] + + +def gf_add_ground(f, a, p, K): + """ + Compute ``f + a`` where ``f`` in ``GF(p)[x]`` and ``a`` in ``GF(p)``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_add_ground + + >>> gf_add_ground([3, 2, 4], 2, 5, ZZ) + [3, 2, 1] + + """ + if not f: + a = a % p + else: + a = (f[-1] + a) % p + + if len(f) > 1: + return f[:-1] + [a] + + if not a: + return [] + else: + return [a] + + +def gf_sub_ground(f, a, p, K): + """ + Compute ``f - a`` where ``f`` in ``GF(p)[x]`` and ``a`` in ``GF(p)``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_sub_ground + + >>> gf_sub_ground([3, 2, 4], 2, 5, ZZ) + [3, 2, 2] + + """ + if not f: + a = -a % p + else: + a = (f[-1] - a) % p + + if len(f) > 1: + return f[:-1] + [a] + + if not a: + return [] + else: + return [a] + + +def gf_mul_ground(f, a, p, K): + """ + Compute ``f * a`` where ``f`` in ``GF(p)[x]`` and ``a`` in ``GF(p)``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_mul_ground + + >>> gf_mul_ground([3, 2, 4], 2, 5, ZZ) + [1, 4, 3] + + """ + if not a: + return [] + else: + return [ (a*b) % p for b in f ] + + +def gf_quo_ground(f, a, p, K): + """ + Compute ``f/a`` where ``f`` in ``GF(p)[x]`` and ``a`` in ``GF(p)``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_quo_ground + + >>> gf_quo_ground(ZZ.map([3, 2, 4]), ZZ(2), 5, ZZ) + [4, 1, 2] + + """ + return gf_mul_ground(f, K.invert(a, p), p, K) + + +def gf_add(f, g, p, K): + """ + Add polynomials in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_add + + >>> gf_add([3, 2, 4], [2, 2, 2], 5, ZZ) + [4, 1] + + """ + if not f: + return g + if not g: + return f + + df = gf_degree(f) + dg = gf_degree(g) + + if df == dg: + return gf_strip([ (a + b) % p 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) % p for a, b in zip(f, g) ] + + +def gf_sub(f, g, p, K): + """ + Subtract polynomials in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_sub + + >>> gf_sub([3, 2, 4], [2, 2, 2], 5, ZZ) + [1, 0, 2] + + """ + if not g: + return f + if not f: + return gf_neg(g, p, K) + + df = gf_degree(f) + dg = gf_degree(g) + + if df == dg: + return gf_strip([ (a - b) % p for a, b in zip(f, g) ]) + else: + k = abs(df - dg) + + if df > dg: + h, f = f[:k], f[k:] + else: + h, g = gf_neg(g[:k], p, K), g[k:] + + return h + [ (a - b) % p for a, b in zip(f, g) ] + + +def gf_mul(f, g, p, K): + """ + Multiply polynomials in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_mul + + >>> gf_mul([3, 2, 4], [2, 2, 2], 5, ZZ) + [1, 0, 3, 2, 3] + + """ + df = gf_degree(f) + dg = gf_degree(g) + + dh = df + dg + h = [0]*(dh + 1) + + for i in range(0, dh + 1): + coeff = K.zero + + for j in range(max(0, i - dg), min(i, df) + 1): + coeff += f[j]*g[i - j] + + h[i] = coeff % p + + return gf_strip(h) + + +def gf_sqr(f, p, K): + """ + Square polynomials in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_sqr + + >>> gf_sqr([3, 2, 4], 5, ZZ) + [4, 2, 3, 1, 1] + + """ + df = gf_degree(f) + + dh = 2*df + h = [0]*(dh + 1) + + for i in range(0, dh + 1): + coeff = 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): + coeff += f[j]*f[i - j] + + coeff += coeff + + if n & 1: + elem = f[jmax + 1] + coeff += elem**2 + + h[i] = coeff % p + + return gf_strip(h) + + +def gf_add_mul(f, g, h, p, K): + """ + Returns ``f + g*h`` where ``f``, ``g``, ``h`` in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_add_mul + >>> gf_add_mul([3, 2, 4], [2, 2, 2], [1, 4], 5, ZZ) + [2, 3, 2, 2] + """ + return gf_add(f, gf_mul(g, h, p, K), p, K) + + +def gf_sub_mul(f, g, h, p, K): + """ + Compute ``f - g*h`` where ``f``, ``g``, ``h`` in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_sub_mul + + >>> gf_sub_mul([3, 2, 4], [2, 2, 2], [1, 4], 5, ZZ) + [3, 3, 2, 1] + + """ + return gf_sub(f, gf_mul(g, h, p, K), p, K) + + +def gf_expand(F, p, K): + """ + Expand results of :func:`~.factor` in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_expand + + >>> gf_expand([([3, 2, 4], 1), ([2, 2], 2), ([3, 1], 3)], 5, ZZ) + [4, 3, 0, 3, 0, 1, 4, 1] + + """ + if isinstance(F, tuple): + lc, F = F + else: + lc = K.one + + g = [lc] + + for f, k in F: + f = gf_pow(f, k, p, K) + g = gf_mul(g, f, p, K) + + return g + + +def gf_div(f, g, p, K): + """ + Division with remainder in ``GF(p)[x]``. + + Given univariate polynomials ``f`` and ``g`` with coefficients in a + finite field with ``p`` elements, returns polynomials ``q`` and ``r`` + (quotient and remainder) such that ``f = q*g + r``. + + Consider polynomials ``x**3 + x + 1`` and ``x**2 + x`` in GF(2):: + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_div, gf_add_mul + + >>> gf_div(ZZ.map([1, 0, 1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) + ([1, 1], [1]) + + As result we obtained quotient ``x + 1`` and remainder ``1``, thus:: + + >>> gf_add_mul(ZZ.map([1]), ZZ.map([1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) + [1, 0, 1, 1] + + References + ========== + + .. [1] [Monagan93]_ + .. [2] [Gathen99]_ + + """ + df = gf_degree(f) + dg = gf_degree(g) + + if not g: + raise ZeroDivisionError("polynomial division") + elif df < dg: + return [], f + + inv = K.invert(g[0], p) + + h, dq, dr = list(f), df - dg, dg - 1 + + for i in range(0, df + 1): + coeff = h[i] + + for j in range(max(0, dg - i), min(df - i, dr) + 1): + coeff -= h[i + j - dg] * g[dg - j] + + if i <= dq: + coeff *= inv + + h[i] = coeff % p + + return h[:dq + 1], gf_strip(h[dq + 1:]) + + +def gf_rem(f, g, p, K): + """ + Compute polynomial remainder in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_rem + + >>> gf_rem(ZZ.map([1, 0, 1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) + [1] + + """ + return gf_div(f, g, p, K)[1] + + +def gf_quo(f, g, p, K): + """ + Compute exact quotient in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_quo + + >>> gf_quo(ZZ.map([1, 0, 1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) + [1, 1] + >>> gf_quo(ZZ.map([1, 0, 3, 2, 3]), ZZ.map([2, 2, 2]), 5, ZZ) + [3, 2, 4] + + """ + df = gf_degree(f) + dg = gf_degree(g) + + if not g: + raise ZeroDivisionError("polynomial division") + elif df < dg: + return [] + + inv = K.invert(g[0], p) + + h, dq, dr = f[:], df - dg, dg - 1 + + for i in range(0, dq + 1): + coeff = h[i] + + for j in range(max(0, dg - i), min(df - i, dr) + 1): + coeff -= h[i + j - dg] * g[dg - j] + + h[i] = (coeff * inv) % p + + return h[:dq + 1] + + +def gf_exquo(f, g, p, K): + """ + Compute polynomial quotient in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_exquo + + >>> gf_exquo(ZZ.map([1, 0, 3, 2, 3]), ZZ.map([2, 2, 2]), 5, ZZ) + [3, 2, 4] + + >>> gf_exquo(ZZ.map([1, 0, 1, 1]), ZZ.map([1, 1, 0]), 2, ZZ) + Traceback (most recent call last): + ... + ExactQuotientFailed: [1, 1, 0] does not divide [1, 0, 1, 1] + + """ + q, r = gf_div(f, g, p, K) + + if not r: + return q + else: + raise ExactQuotientFailed(f, g) + + +def gf_lshift(f, n, K): + """ + Efficiently multiply ``f`` by ``x**n``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_lshift + + >>> gf_lshift([3, 2, 4], 4, ZZ) + [3, 2, 4, 0, 0, 0, 0] + + """ + if not f: + return f + else: + return f + [K.zero]*n + + +def gf_rshift(f, n, K): + """ + Efficiently divide ``f`` by ``x**n``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_rshift + + >>> gf_rshift([1, 2, 3, 4, 0], 3, ZZ) + ([1, 2], [3, 4, 0]) + + """ + if not n: + return f, [] + else: + return f[:-n], f[-n:] + + +def gf_pow(f, n, p, K): + """ + Compute ``f**n`` in ``GF(p)[x]`` using repeated squaring. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_pow + + >>> gf_pow([3, 2, 4], 3, 5, ZZ) + [2, 4, 4, 2, 2, 1, 4] + + """ + if not n: + return [K.one] + elif n == 1: + return f + elif n == 2: + return gf_sqr(f, p, K) + + h = [K.one] + + while True: + if n & 1: + h = gf_mul(h, f, p, K) + n -= 1 + + n >>= 1 + + if not n: + break + + f = gf_sqr(f, p, K) + + return h + +def gf_frobenius_monomial_base(g, p, K): + """ + return the list of ``x**(i*p) mod g in Z_p`` for ``i = 0, .., n - 1`` + where ``n = gf_degree(g)`` + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_frobenius_monomial_base + >>> g = ZZ.map([1, 0, 2, 1]) + >>> gf_frobenius_monomial_base(g, 5, ZZ) + [[1], [4, 4, 2], [1, 2]] + + """ + n = gf_degree(g) + if n == 0: + return [] + b = [0]*n + b[0] = [1] + if p < n: + for i in range(1, n): + mon = gf_lshift(b[i - 1], p, K) + b[i] = gf_rem(mon, g, p, K) + elif n > 1: + b[1] = gf_pow_mod([K.one, K.zero], p, g, p, K) + for i in range(2, n): + b[i] = gf_mul(b[i - 1], b[1], p, K) + b[i] = gf_rem(b[i], g, p, K) + + return b + +def gf_frobenius_map(f, g, b, p, K): + """ + compute gf_pow_mod(f, p, g, p, K) using the Frobenius map + + Parameters + ========== + + f, g : polynomials in ``GF(p)[x]`` + b : frobenius monomial base + p : prime number + K : domain + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_frobenius_monomial_base, gf_frobenius_map + >>> f = ZZ.map([2, 1, 0, 1]) + >>> g = ZZ.map([1, 0, 2, 1]) + >>> p = 5 + >>> b = gf_frobenius_monomial_base(g, p, ZZ) + >>> r = gf_frobenius_map(f, g, b, p, ZZ) + >>> gf_frobenius_map(f, g, b, p, ZZ) + [4, 0, 3] + """ + m = gf_degree(g) + if gf_degree(f) >= m: + f = gf_rem(f, g, p, K) + if not f: + return [] + n = gf_degree(f) + sf = [f[-1]] + for i in range(1, n + 1): + v = gf_mul_ground(b[i], f[n - i], p, K) + sf = gf_add(sf, v, p, K) + return sf + +def _gf_pow_pnm1d2(f, n, g, b, p, K): + """ + utility function for ``gf_edf_zassenhaus`` + Compute ``f**((p**n - 1) // 2)`` in ``GF(p)[x]/(g)`` + ``f**((p**n - 1) // 2) = (f*f**p*...*f**(p**n - 1))**((p - 1) // 2)`` + """ + f = gf_rem(f, g, p, K) + h = f + r = f + for i in range(1, n): + h = gf_frobenius_map(h, g, b, p, K) + r = gf_mul(r, h, p, K) + r = gf_rem(r, g, p, K) + + res = gf_pow_mod(r, (p - 1)//2, g, p, K) + return res + +def gf_pow_mod(f, n, g, p, K): + """ + Compute ``f**n`` in ``GF(p)[x]/(g)`` using repeated squaring. + + Given polynomials ``f`` and ``g`` in ``GF(p)[x]`` and a non-negative + integer ``n``, efficiently computes ``f**n (mod g)`` i.e. the remainder + of ``f**n`` from division by ``g``, using the repeated squaring algorithm. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_pow_mod + + >>> gf_pow_mod(ZZ.map([3, 2, 4]), 3, ZZ.map([1, 1]), 5, ZZ) + [] + + References + ========== + + .. [1] [Gathen99]_ + + """ + if not n: + return [K.one] + elif n == 1: + return gf_rem(f, g, p, K) + elif n == 2: + return gf_rem(gf_sqr(f, p, K), g, p, K) + + h = [K.one] + + while True: + if n & 1: + h = gf_mul(h, f, p, K) + h = gf_rem(h, g, p, K) + n -= 1 + + n >>= 1 + + if not n: + break + + f = gf_sqr(f, p, K) + f = gf_rem(f, g, p, K) + + return h + + +def gf_gcd(f, g, p, K): + """ + Euclidean Algorithm in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_gcd + + >>> gf_gcd(ZZ.map([3, 2, 4]), ZZ.map([2, 2, 3]), 5, ZZ) + [1, 3] + + """ + while g: + f, g = g, gf_rem(f, g, p, K) + + return gf_monic(f, p, K)[1] + + +def gf_lcm(f, g, p, K): + """ + Compute polynomial LCM in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_lcm + + >>> gf_lcm(ZZ.map([3, 2, 4]), ZZ.map([2, 2, 3]), 5, ZZ) + [1, 2, 0, 4] + + """ + if not f or not g: + return [] + + h = gf_quo(gf_mul(f, g, p, K), + gf_gcd(f, g, p, K), p, K) + + return gf_monic(h, p, K)[1] + + +def gf_cofactors(f, g, p, K): + """ + Compute polynomial GCD and cofactors in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_cofactors + + >>> gf_cofactors(ZZ.map([3, 2, 4]), ZZ.map([2, 2, 3]), 5, ZZ) + ([1, 3], [3, 3], [2, 1]) + + """ + if not f and not g: + return ([], [], []) + + h = gf_gcd(f, g, p, K) + + return (h, gf_quo(f, h, p, K), + gf_quo(g, h, p, K)) + + +def gf_gcdex(f, g, p, K): + """ + Extended Euclidean Algorithm in ``GF(p)[x]``. + + Given polynomials ``f`` and ``g`` in ``GF(p)[x]``, computes polynomials + ``s``, ``t`` and ``h``, such that ``h = gcd(f, g)`` and ``s*f + t*g = h``. + The typical application of EEA is solving polynomial diophantine equations. + + Consider polynomials ``f = (x + 7) (x + 1)``, ``g = (x + 7) (x**2 + 1)`` + in ``GF(11)[x]``. Application of Extended Euclidean Algorithm gives:: + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_gcdex, gf_mul, gf_add + + >>> s, t, g = gf_gcdex(ZZ.map([1, 8, 7]), ZZ.map([1, 7, 1, 7]), 11, ZZ) + >>> s, t, g + ([5, 6], [6], [1, 7]) + + As result we obtained polynomials ``s = 5*x + 6`` and ``t = 6``, and + additionally ``gcd(f, g) = x + 7``. This is correct because:: + + >>> S = gf_mul(s, ZZ.map([1, 8, 7]), 11, ZZ) + >>> T = gf_mul(t, ZZ.map([1, 7, 1, 7]), 11, ZZ) + + >>> gf_add(S, T, 11, ZZ) == [1, 7] + True + + References + ========== + + .. [1] [Gathen99]_ + + """ + if not (f or g): + return [K.one], [], [] + + p0, r0 = gf_monic(f, p, K) + p1, r1 = gf_monic(g, p, K) + + if not f: + return [], [K.invert(p1, p)], r1 + if not g: + return [K.invert(p0, p)], [], r0 + + s0, s1 = [K.invert(p0, p)], [] + t0, t1 = [], [K.invert(p1, p)] + + while True: + Q, R = gf_div(r0, r1, p, K) + + if not R: + break + + (lc, r1), r0 = gf_monic(R, p, K), r1 + + inv = K.invert(lc, p) + + s = gf_sub_mul(s0, s1, Q, p, K) + t = gf_sub_mul(t0, t1, Q, p, K) + + s1, s0 = gf_mul_ground(s, inv, p, K), s1 + t1, t0 = gf_mul_ground(t, inv, p, K), t1 + + return s1, t1, r1 + + +def gf_monic(f, p, K): + """ + Compute LC and a monic polynomial in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_monic + + >>> gf_monic(ZZ.map([3, 2, 4]), 5, ZZ) + (3, [1, 4, 3]) + + """ + if not f: + return K.zero, [] + else: + lc = f[0] + + if K.is_one(lc): + return lc, list(f) + else: + return lc, gf_quo_ground(f, lc, p, K) + + +def gf_diff(f, p, K): + """ + Differentiate polynomial in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_diff + + >>> gf_diff([3, 2, 4], 5, ZZ) + [1, 2] + + """ + df = gf_degree(f) + + h, n = [K.zero]*df, df + + for coeff in f[:-1]: + coeff *= K(n) + coeff %= p + + if coeff: + h[df - n] = coeff + + n -= 1 + + return gf_strip(h) + + +def gf_eval(f, a, p, K): + """ + Evaluate ``f(a)`` in ``GF(p)`` using Horner scheme. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_eval + + >>> gf_eval([3, 2, 4], 2, 5, ZZ) + 0 + + """ + result = K.zero + + for c in f: + result *= a + result += c + result %= p + + return result + + +def gf_multi_eval(f, A, p, K): + """ + Evaluate ``f(a)`` for ``a`` in ``[a_1, ..., a_n]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_multi_eval + + >>> gf_multi_eval([3, 2, 4], [0, 1, 2, 3, 4], 5, ZZ) + [4, 4, 0, 2, 0] + + """ + return [ gf_eval(f, a, p, K) for a in A ] + + +def gf_compose(f, g, p, K): + """ + Compute polynomial composition ``f(g)`` in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_compose + + >>> gf_compose([3, 2, 4], [2, 2, 2], 5, ZZ) + [2, 4, 0, 3, 0] + + """ + if len(g) <= 1: + return gf_strip([gf_eval(f, gf_LC(g, K), p, K)]) + + if not f: + return [] + + h = [f[0]] + + for c in f[1:]: + h = gf_mul(h, g, p, K) + h = gf_add_ground(h, c, p, K) + + return h + + +def gf_compose_mod(g, h, f, p, K): + """ + Compute polynomial composition ``g(h)`` in ``GF(p)[x]/(f)``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_compose_mod + + >>> gf_compose_mod(ZZ.map([3, 2, 4]), ZZ.map([2, 2, 2]), ZZ.map([4, 3]), 5, ZZ) + [4] + + """ + if not g: + return [] + + comp = [g[0]] + + for a in g[1:]: + comp = gf_mul(comp, h, p, K) + comp = gf_add_ground(comp, a, p, K) + comp = gf_rem(comp, f, p, K) + + return comp + + +def gf_trace_map(a, b, c, n, f, p, K): + """ + Compute polynomial trace map in ``GF(p)[x]/(f)``. + + Given a polynomial ``f`` in ``GF(p)[x]``, polynomials ``a``, ``b``, + ``c`` in the quotient ring ``GF(p)[x]/(f)`` such that ``b = c**t + (mod f)`` for some positive power ``t`` of ``p``, and a positive + integer ``n``, returns a mapping:: + + a -> a**t**n, a + a**t + a**t**2 + ... + a**t**n (mod f) + + In factorization context, ``b = x**p mod f`` and ``c = x mod f``. + This way we can efficiently compute trace polynomials in equal + degree factorization routine, much faster than with other methods, + like iterated Frobenius algorithm, for large degrees. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_trace_map + + >>> gf_trace_map([1, 2], [4, 4], [1, 1], 4, [3, 2, 4], 5, ZZ) + ([1, 3], [1, 3]) + + References + ========== + + .. [1] [Gathen92]_ + + """ + u = gf_compose_mod(a, b, f, p, K) + v = b + + if n & 1: + U = gf_add(a, u, p, K) + V = b + else: + U = a + V = c + + n >>= 1 + + while n: + u = gf_add(u, gf_compose_mod(u, v, f, p, K), p, K) + v = gf_compose_mod(v, v, f, p, K) + + if n & 1: + U = gf_add(U, gf_compose_mod(u, V, f, p, K), p, K) + V = gf_compose_mod(v, V, f, p, K) + + n >>= 1 + + return gf_compose_mod(a, V, f, p, K), U + +def _gf_trace_map(f, n, g, b, p, K): + """ + utility for ``gf_edf_shoup`` + """ + f = gf_rem(f, g, p, K) + h = f + r = f + for i in range(1, n): + h = gf_frobenius_map(h, g, b, p, K) + r = gf_add(r, h, p, K) + r = gf_rem(r, g, p, K) + return r + + +def gf_random(n, p, K): + """ + Generate a random polynomial in ``GF(p)[x]`` of degree ``n``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_random + >>> gf_random(10, 5, ZZ) #doctest: +SKIP + [1, 2, 3, 2, 1, 1, 1, 2, 0, 4, 2] + + """ + pi = int(p) + return [K.one] + [ K(int(uniform(0, pi))) for i in range(0, n) ] + + +def gf_irreducible(n, p, K): + """ + Generate random irreducible polynomial of degree ``n`` in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_irreducible + >>> gf_irreducible(10, 5, ZZ) #doctest: +SKIP + [1, 4, 2, 2, 3, 2, 4, 1, 4, 0, 4] + + """ + while True: + f = gf_random(n, p, K) + if gf_irreducible_p(f, p, K): + return f + + +def gf_irred_p_ben_or(f, p, K): + """ + Ben-Or's polynomial irreducibility test over finite fields. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_irred_p_ben_or + + >>> gf_irred_p_ben_or(ZZ.map([1, 4, 2, 2, 3, 2, 4, 1, 4, 0, 4]), 5, ZZ) + True + >>> gf_irred_p_ben_or(ZZ.map([3, 2, 4]), 5, ZZ) + False + + """ + n = gf_degree(f) + + if n <= 1: + return True + + _, f = gf_monic(f, p, K) + if n < 5: + H = h = gf_pow_mod([K.one, K.zero], p, f, p, K) + + for i in range(0, n//2): + g = gf_sub(h, [K.one, K.zero], p, K) + + if gf_gcd(f, g, p, K) == [K.one]: + h = gf_compose_mod(h, H, f, p, K) + else: + return False + else: + b = gf_frobenius_monomial_base(f, p, K) + H = h = gf_frobenius_map([K.one, K.zero], f, b, p, K) + for i in range(0, n//2): + g = gf_sub(h, [K.one, K.zero], p, K) + if gf_gcd(f, g, p, K) == [K.one]: + h = gf_frobenius_map(h, f, b, p, K) + else: + return False + + return True + + +def gf_irred_p_rabin(f, p, K): + """ + Rabin's polynomial irreducibility test over finite fields. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_irred_p_rabin + + >>> gf_irred_p_rabin(ZZ.map([1, 4, 2, 2, 3, 2, 4, 1, 4, 0, 4]), 5, ZZ) + True + >>> gf_irred_p_rabin(ZZ.map([3, 2, 4]), 5, ZZ) + False + + """ + n = gf_degree(f) + + if n <= 1: + return True + + _, f = gf_monic(f, p, K) + + x = [K.one, K.zero] + + from sympy.ntheory import factorint + + indices = { n//d for d in factorint(n) } + + b = gf_frobenius_monomial_base(f, p, K) + h = b[1] + + for i in range(1, n): + if i in indices: + g = gf_sub(h, x, p, K) + + if gf_gcd(f, g, p, K) != [K.one]: + return False + + h = gf_frobenius_map(h, f, b, p, K) + + return h == x + +_irred_methods = { + 'ben-or': gf_irred_p_ben_or, + 'rabin': gf_irred_p_rabin, +} + + +def gf_irreducible_p(f, p, K): + """ + Test irreducibility of a polynomial ``f`` in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_irreducible_p + + >>> gf_irreducible_p(ZZ.map([1, 4, 2, 2, 3, 2, 4, 1, 4, 0, 4]), 5, ZZ) + True + >>> gf_irreducible_p(ZZ.map([3, 2, 4]), 5, ZZ) + False + + """ + method = query('GF_IRRED_METHOD') + + if method is not None: + irred = _irred_methods[method](f, p, K) + else: + irred = gf_irred_p_rabin(f, p, K) + + return irred + + +def gf_sqf_p(f, p, K): + """ + Return ``True`` if ``f`` is square-free in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_sqf_p + + >>> gf_sqf_p(ZZ.map([3, 2, 4]), 5, ZZ) + True + >>> gf_sqf_p(ZZ.map([2, 4, 4, 2, 2, 1, 4]), 5, ZZ) + False + + """ + _, f = gf_monic(f, p, K) + + if not f: + return True + else: + return gf_gcd(f, gf_diff(f, p, K), p, K) == [K.one] + + +def gf_sqf_part(f, p, K): + """ + Return square-free part of a ``GF(p)[x]`` polynomial. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_sqf_part + + >>> gf_sqf_part(ZZ.map([1, 1, 3, 0, 1, 0, 2, 2, 1]), 5, ZZ) + [1, 4, 3] + + """ + _, sqf = gf_sqf_list(f, p, K) + + g = [K.one] + + for f, _ in sqf: + g = gf_mul(g, f, p, K) + + return g + + +def gf_sqf_list(f, p, K, all=False): + """ + Return the square-free decomposition of a ``GF(p)[x]`` polynomial. + + Given a polynomial ``f`` in ``GF(p)[x]``, returns the leading coefficient + of ``f`` and a square-free decomposition ``f_1**e_1 f_2**e_2 ... f_k**e_k`` + such that all ``f_i`` are monic polynomials and ``(f_i, f_j)`` for ``i != j`` + are co-prime and ``e_1 ... e_k`` are given in increasing order. All trivial + terms (i.e. ``f_i = 1``) are not included in the output. + + Consider polynomial ``f = x**11 + 1`` over ``GF(11)[x]``:: + + >>> from sympy.polys.domains import ZZ + + >>> from sympy.polys.galoistools import ( + ... gf_from_dict, gf_diff, gf_sqf_list, gf_pow, + ... ) + ... # doctest: +NORMALIZE_WHITESPACE + + >>> f = gf_from_dict({11: ZZ(1), 0: ZZ(1)}, 11, ZZ) + + Note that ``f'(x) = 0``:: + + >>> gf_diff(f, 11, ZZ) + [] + + This phenomenon does not happen in characteristic zero. However we can + still compute square-free decomposition of ``f`` using ``gf_sqf()``:: + + >>> gf_sqf_list(f, 11, ZZ) + (1, [([1, 1], 11)]) + + We obtained factorization ``f = (x + 1)**11``. This is correct because:: + + >>> gf_pow([1, 1], 11, 11, ZZ) == f + True + + References + ========== + + .. [1] [Geddes92]_ + + """ + n, sqf, factors, r = 1, False, [], int(p) + + lc, f = gf_monic(f, p, K) + + if gf_degree(f) < 1: + return lc, [] + + while True: + F = gf_diff(f, p, K) + + if F != []: + g = gf_gcd(f, F, p, K) + h = gf_quo(f, g, p, K) + + i = 1 + + while h != [K.one]: + G = gf_gcd(g, h, p, K) + H = gf_quo(h, G, p, K) + + if gf_degree(H) > 0: + factors.append((H, i*n)) + + g, h, i = gf_quo(g, G, p, K), G, i + 1 + + if g == [K.one]: + sqf = True + else: + f = g + + if not sqf: + d = gf_degree(f) // r + + for i in range(0, d + 1): + f[i] = f[i*r] + + f, n = f[:d + 1], n*r + else: + break + + if all: + raise ValueError("'all=True' is not supported yet") + + return lc, factors + + +def gf_Qmatrix(f, p, K): + """ + Calculate Berlekamp's ``Q`` matrix. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_Qmatrix + + >>> gf_Qmatrix([3, 2, 4], 5, ZZ) + [[1, 0], + [3, 4]] + + >>> gf_Qmatrix([1, 0, 0, 0, 1], 5, ZZ) + [[1, 0, 0, 0], + [0, 4, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 4]] + + """ + n, r = gf_degree(f), int(p) + + q = [K.one] + [K.zero]*(n - 1) + Q = [list(q)] + [[]]*(n - 1) + + for i in range(1, (n - 1)*r + 1): + qq, c = [(-q[-1]*f[-1]) % p], q[-1] + + for j in range(1, n): + qq.append((q[j - 1] - c*f[-j - 1]) % p) + + if not (i % r): + Q[i//r] = list(qq) + + q = qq + + return Q + + +def gf_Qbasis(Q, p, K): + """ + Compute a basis of the kernel of ``Q``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_Qmatrix, gf_Qbasis + + >>> gf_Qbasis(gf_Qmatrix([1, 0, 0, 0, 1], 5, ZZ), 5, ZZ) + [[1, 0, 0, 0], [0, 0, 1, 0]] + + >>> gf_Qbasis(gf_Qmatrix([3, 2, 4], 5, ZZ), 5, ZZ) + [[1, 0]] + + """ + Q, n = [ list(q) for q in Q ], len(Q) + + for k in range(0, n): + Q[k][k] = (Q[k][k] - K.one) % p + + for k in range(0, n): + for i in range(k, n): + if Q[k][i]: + break + else: + continue + + inv = K.invert(Q[k][i], p) + + for j in range(0, n): + Q[j][i] = (Q[j][i]*inv) % p + + for j in range(0, n): + t = Q[j][k] + Q[j][k] = Q[j][i] + Q[j][i] = t + + for i in range(0, n): + if i != k: + q = Q[k][i] + + for j in range(0, n): + Q[j][i] = (Q[j][i] - Q[j][k]*q) % p + + for i in range(0, n): + for j in range(0, n): + if i == j: + Q[i][j] = (K.one - Q[i][j]) % p + else: + Q[i][j] = (-Q[i][j]) % p + + basis = [] + + for q in Q: + if any(q): + basis.append(q) + + return basis + + +def gf_berlekamp(f, p, K): + """ + Factor a square-free ``f`` in ``GF(p)[x]`` for small ``p``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_berlekamp + + >>> gf_berlekamp([1, 0, 0, 0, 1], 5, ZZ) + [[1, 0, 2], [1, 0, 3]] + + """ + Q = gf_Qmatrix(f, p, K) + V = gf_Qbasis(Q, p, K) + + for i, v in enumerate(V): + V[i] = gf_strip(list(reversed(v))) + + factors = [f] + + for k in range(1, len(V)): + for f in list(factors): + s = K.zero + + while s < p: + g = gf_sub_ground(V[k], s, p, K) + h = gf_gcd(f, g, p, K) + + if h != [K.one] and h != f: + factors.remove(f) + + f = gf_quo(f, h, p, K) + factors.extend([f, h]) + + if len(factors) == len(V): + return _sort_factors(factors, multiple=False) + + s += K.one + + return _sort_factors(factors, multiple=False) + + +def gf_ddf_zassenhaus(f, p, K): + """ + Cantor-Zassenhaus: Deterministic Distinct Degree Factorization + + Given a monic square-free polynomial ``f`` in ``GF(p)[x]``, computes + partial distinct degree factorization ``f_1 ... f_d`` of ``f`` where + ``deg(f_i) != deg(f_j)`` for ``i != j``. The result is returned as a + list of pairs ``(f_i, e_i)`` where ``deg(f_i) > 0`` and ``e_i > 0`` + is an argument to the equal degree factorization routine. + + Consider the polynomial ``x**15 - 1`` in ``GF(11)[x]``:: + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_from_dict + + >>> f = gf_from_dict({15: ZZ(1), 0: ZZ(-1)}, 11, ZZ) + + Distinct degree factorization gives:: + + >>> from sympy.polys.galoistools import gf_ddf_zassenhaus + + >>> gf_ddf_zassenhaus(f, 11, ZZ) + [([1, 0, 0, 0, 0, 10], 1), ([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], 2)] + + which means ``x**15 - 1 = (x**5 - 1) (x**10 + x**5 + 1)``. To obtain + factorization into irreducibles, use equal degree factorization + procedure (EDF) with each of the factors. + + References + ========== + + .. [1] [Gathen99]_ + .. [2] [Geddes92]_ + + """ + i, g, factors = 1, [K.one, K.zero], [] + + b = gf_frobenius_monomial_base(f, p, K) + while 2*i <= gf_degree(f): + g = gf_frobenius_map(g, f, b, p, K) + h = gf_gcd(f, gf_sub(g, [K.one, K.zero], p, K), p, K) + + if h != [K.one]: + factors.append((h, i)) + + f = gf_quo(f, h, p, K) + g = gf_rem(g, f, p, K) + b = gf_frobenius_monomial_base(f, p, K) + + i += 1 + + if f != [K.one]: + return factors + [(f, gf_degree(f))] + else: + return factors + + +def gf_edf_zassenhaus(f, n, p, K): + """ + Cantor-Zassenhaus: Probabilistic Equal Degree Factorization + + Given a monic square-free polynomial ``f`` in ``GF(p)[x]`` and + an integer ``n``, such that ``n`` divides ``deg(f)``, returns all + irreducible factors ``f_1,...,f_d`` of ``f``, each of degree ``n``. + EDF procedure gives complete factorization over Galois fields. + + Consider the square-free polynomial ``f = x**3 + x**2 + x + 1`` in + ``GF(5)[x]``. Let's compute its irreducible factors of degree one:: + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_edf_zassenhaus + + >>> gf_edf_zassenhaus([1,1,1,1], 1, 5, ZZ) + [[1, 1], [1, 2], [1, 3]] + + Notes + ===== + + The case p == 2 is handled by Cohen's Algorithm 3.4.8. The case p odd is + as in Geddes Algorithm 8.9 (or Cohen's Algorithm 3.4.6). + + References + ========== + + .. [1] [Gathen99]_ + .. [2] [Geddes92]_ Algorithm 8.9 + .. [3] [Cohen93]_ Algorithm 3.4.8 + + """ + factors = [f] + + if gf_degree(f) <= n: + return factors + + N = gf_degree(f) // n + if p != 2: + b = gf_frobenius_monomial_base(f, p, K) + + t = [K.one, K.zero] + while len(factors) < N: + if p == 2: + h = r = t + + for i in range(n - 1): + r = gf_pow_mod(r, 2, f, p, K) + h = gf_add(h, r, p, K) + + g = gf_gcd(f, h, p, K) + t += [K.zero, K.zero] + else: + r = gf_random(2 * n - 1, p, K) + h = _gf_pow_pnm1d2(r, n, f, b, p, K) + g = gf_gcd(f, gf_sub_ground(h, K.one, p, K), p, K) + + if g != [K.one] and g != f: + factors = gf_edf_zassenhaus(g, n, p, K) \ + + gf_edf_zassenhaus(gf_quo(f, g, p, K), n, p, K) + + return _sort_factors(factors, multiple=False) + + +def gf_ddf_shoup(f, p, K): + """ + Kaltofen-Shoup: Deterministic Distinct Degree Factorization + + Given a monic square-free polynomial ``f`` in ``GF(p)[x]``, computes + partial distinct degree factorization ``f_1,...,f_d`` of ``f`` where + ``deg(f_i) != deg(f_j)`` for ``i != j``. The result is returned as a + list of pairs ``(f_i, e_i)`` where ``deg(f_i) > 0`` and ``e_i > 0`` + is an argument to the equal degree factorization routine. + + This algorithm is an improved version of Zassenhaus algorithm for + large ``deg(f)`` and modulus ``p`` (especially for ``deg(f) ~ lg(p)``). + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_ddf_shoup, gf_from_dict + + >>> f = gf_from_dict({6: ZZ(1), 5: ZZ(-1), 4: ZZ(1), 3: ZZ(1), 1: ZZ(-1)}, 3, ZZ) + + >>> gf_ddf_shoup(f, 3, ZZ) + [([1, 1, 0], 1), ([1, 1, 0, 1, 2], 2)] + + References + ========== + + .. [1] [Kaltofen98]_ + .. [2] [Shoup95]_ + .. [3] [Gathen92]_ + + """ + n = gf_degree(f) + k = int(_ceil(_sqrt(n//2))) + b = gf_frobenius_monomial_base(f, p, K) + h = gf_frobenius_map([K.one, K.zero], f, b, p, K) + # U[i] = x**(p**i) + U = [[K.one, K.zero], h] + [K.zero]*(k - 1) + + for i in range(2, k + 1): + U[i] = gf_frobenius_map(U[i-1], f, b, p, K) + + h, U = U[k], U[:k] + # V[i] = x**(p**(k*(i+1))) + V = [h] + [K.zero]*(k - 1) + + for i in range(1, k): + V[i] = gf_compose_mod(V[i - 1], h, f, p, K) + + factors = [] + + for i, v in enumerate(V): + h, j = [K.one], k - 1 + + for u in U: + g = gf_sub(v, u, p, K) + h = gf_mul(h, g, p, K) + h = gf_rem(h, f, p, K) + + g = gf_gcd(f, h, p, K) + f = gf_quo(f, g, p, K) + + for u in reversed(U): + h = gf_sub(v, u, p, K) + F = gf_gcd(g, h, p, K) + + if F != [K.one]: + factors.append((F, k*(i + 1) - j)) + + g, j = gf_quo(g, F, p, K), j - 1 + + if f != [K.one]: + factors.append((f, gf_degree(f))) + + return factors + +def gf_edf_shoup(f, n, p, K): + """ + Gathen-Shoup: Probabilistic Equal Degree Factorization + + Given a monic square-free polynomial ``f`` in ``GF(p)[x]`` and integer + ``n`` such that ``n`` divides ``deg(f)``, returns all irreducible factors + ``f_1,...,f_d`` of ``f``, each of degree ``n``. This is a complete + factorization over Galois fields. + + This algorithm is an improved version of Zassenhaus algorithm for + large ``deg(f)`` and modulus ``p`` (especially for ``deg(f) ~ lg(p)``). + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_edf_shoup + + >>> gf_edf_shoup(ZZ.map([1, 2837, 2277]), 1, 2917, ZZ) + [[1, 852], [1, 1985]] + + References + ========== + + .. [1] [Shoup91]_ + .. [2] [Gathen92]_ + + """ + N, q = gf_degree(f), int(p) + + if not N: + return [] + if N <= n: + return [f] + + factors, x = [f], [K.one, K.zero] + + r = gf_random(N - 1, p, K) + + if p == 2: + h = gf_pow_mod(x, q, f, p, K) + H = gf_trace_map(r, h, x, n - 1, f, p, K)[1] + h1 = gf_gcd(f, H, p, K) + h2 = gf_quo(f, h1, p, K) + + factors = gf_edf_shoup(h1, n, p, K) \ + + gf_edf_shoup(h2, n, p, K) + else: + b = gf_frobenius_monomial_base(f, p, K) + H = _gf_trace_map(r, n, f, b, p, K) + h = gf_pow_mod(H, (q - 1)//2, f, p, K) + + h1 = gf_gcd(f, h, p, K) + h2 = gf_gcd(f, gf_sub_ground(h, K.one, p, K), p, K) + h3 = gf_quo(f, gf_mul(h1, h2, p, K), p, K) + + factors = gf_edf_shoup(h1, n, p, K) \ + + gf_edf_shoup(h2, n, p, K) \ + + gf_edf_shoup(h3, n, p, K) + + return _sort_factors(factors, multiple=False) + + +def gf_zassenhaus(f, p, K): + """ + Factor a square-free ``f`` in ``GF(p)[x]`` for medium ``p``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_zassenhaus + + >>> gf_zassenhaus(ZZ.map([1, 4, 3]), 5, ZZ) + [[1, 1], [1, 3]] + + """ + factors = [] + + for factor, n in gf_ddf_zassenhaus(f, p, K): + factors += gf_edf_zassenhaus(factor, n, p, K) + + return _sort_factors(factors, multiple=False) + + +def gf_shoup(f, p, K): + """ + Factor a square-free ``f`` in ``GF(p)[x]`` for large ``p``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_shoup + + >>> gf_shoup(ZZ.map([1, 4, 3]), 5, ZZ) + [[1, 1], [1, 3]] + + """ + factors = [] + + for factor, n in gf_ddf_shoup(f, p, K): + factors += gf_edf_shoup(factor, n, p, K) + + return _sort_factors(factors, multiple=False) + +_factor_methods = { + 'berlekamp': gf_berlekamp, # ``p`` : small + 'zassenhaus': gf_zassenhaus, # ``p`` : medium + 'shoup': gf_shoup, # ``p`` : large +} + + +def gf_factor_sqf(f, p, K, method=None): + """ + Factor a square-free polynomial ``f`` in ``GF(p)[x]``. + + Examples + ======== + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_factor_sqf + + >>> gf_factor_sqf(ZZ.map([3, 2, 4]), 5, ZZ) + (3, [[1, 1], [1, 3]]) + + """ + lc, f = gf_monic(f, p, K) + + if gf_degree(f) < 1: + return lc, [] + + method = method or query('GF_FACTOR_METHOD') + + if method is not None: + factors = _factor_methods[method](f, p, K) + else: + factors = gf_zassenhaus(f, p, K) + + return lc, factors + + +def gf_factor(f, p, K): + """ + Factor (non square-free) polynomials in ``GF(p)[x]``. + + Given a possibly non square-free polynomial ``f`` in ``GF(p)[x]``, + returns its complete factorization into irreducibles:: + + f_1(x)**e_1 f_2(x)**e_2 ... f_d(x)**e_d + + where each ``f_i`` is a monic polynomial and ``gcd(f_i, f_j) == 1``, + for ``i != j``. The result is given as a tuple consisting of the + leading coefficient of ``f`` and a list of factors of ``f`` with + their multiplicities. + + The algorithm proceeds by first computing square-free decomposition + of ``f`` and then iteratively factoring each of square-free factors. + + Consider a non square-free polynomial ``f = (7*x + 1) (x + 2)**2`` in + ``GF(11)[x]``. We obtain its factorization into irreducibles as follows:: + + >>> from sympy.polys.domains import ZZ + >>> from sympy.polys.galoistools import gf_factor + + >>> gf_factor(ZZ.map([5, 2, 7, 2]), 11, ZZ) + (5, [([1, 2], 1), ([1, 8], 2)]) + + We arrived with factorization ``f = 5 (x + 2) (x + 8)**2``. We did not + recover the exact form of the input polynomial because we requested to + get monic factors of ``f`` and its leading coefficient separately. + + Square-free factors of ``f`` can be factored into irreducibles over + ``GF(p)`` using three very different methods: + + Berlekamp + efficient for very small values of ``p`` (usually ``p < 25``) + Cantor-Zassenhaus + efficient on average input and with "typical" ``p`` + Shoup-Kaltofen-Gathen + efficient with very large inputs and modulus + + If you want to use a specific factorization method, instead of the default + one, set ``GF_FACTOR_METHOD`` with one of ``berlekamp``, ``zassenhaus`` or + ``shoup`` values. + + References + ========== + + .. [1] [Gathen99]_ + + """ + lc, f = gf_monic(f, p, K) + + if gf_degree(f) < 1: + return lc, [] + + factors = [] + + for g, n in gf_sqf_list(f, p, K)[1]: + for h in gf_factor_sqf(g, p, K)[1]: + factors.append((h, n)) + + return lc, _sort_factors(factors) + + +def gf_value(f, a): + """ + Value of polynomial 'f' at 'a' in field R. + + Examples + ======== + + >>> from sympy.polys.galoistools import gf_value + + >>> gf_value([1, 7, 2, 4], 11) + 2204 + + """ + result = 0 + for c in f: + result *= a + result += c + return result + + +def linear_congruence(a, b, m): + """ + Returns the values of x satisfying a*x congruent b mod(m) + + Here m is positive integer and a, b are natural numbers. + This function returns only those values of x which are distinct mod(m). + + Examples + ======== + + >>> from sympy.polys.galoistools import linear_congruence + + >>> linear_congruence(3, 12, 15) + [4, 9, 14] + + There are 3 solutions distinct mod(15) since gcd(a, m) = gcd(3, 15) = 3. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Linear_congruence_theorem + + """ + from sympy.polys.polytools import gcdex + if a % m == 0: + if b % m == 0: + return list(range(m)) + else: + return [] + r, _, g = gcdex(a, m) + if b % g != 0: + return [] + return [(r * b // g + t * m // g) % m for t in range(g)] + + +def _raise_mod_power(x, s, p, f): + """ + Used in gf_csolve to generate solutions of f(x) cong 0 mod(p**(s + 1)) + from the solutions of f(x) cong 0 mod(p**s). + + Examples + ======== + + >>> from sympy.polys.galoistools import _raise_mod_power + >>> from sympy.polys.galoistools import csolve_prime + + These is the solutions of f(x) = x**2 + x + 7 cong 0 mod(3) + + >>> f = [1, 1, 7] + >>> csolve_prime(f, 3) + [1] + >>> [ i for i in range(3) if not (i**2 + i + 7) % 3] + [1] + + The solutions of f(x) cong 0 mod(9) are constructed from the + values returned from _raise_mod_power: + + >>> x, s, p = 1, 1, 3 + >>> V = _raise_mod_power(x, s, p, f) + >>> [x + v * p**s for v in V] + [1, 4, 7] + + And these are confirmed with the following: + + >>> [ i for i in range(3**2) if not (i**2 + i + 7) % 3**2] + [1, 4, 7] + + """ + from sympy.polys.domains import ZZ + f_f = gf_diff(f, p, ZZ) + alpha = gf_value(f_f, x) + beta = - gf_value(f, x) // p**s + return linear_congruence(alpha, beta, p) + + +def _csolve_prime_las_vegas(f, p, seed=None): + r""" Solutions of `f(x) \equiv 0 \pmod{p}`, `f(0) \not\equiv 0 \pmod{p}`. + + Explanation + =========== + + This algorithm is classified as the Las Vegas method. + That is, it always returns the correct answer and solves the problem + fast in many cases, but if it is unlucky, it does not answer forever. + + Suppose the polynomial f is not a zero polynomial. Assume further + that it is of degree at most p-1 and `f(0)\not\equiv 0 \pmod{p}`. + These assumptions are not an essential part of the algorithm, + only that it is more convenient for the function calling this + function to resolve them. + + Note that `x^{p-1} - 1 \equiv \prod_{a=1}^{p-1}(x - a) \pmod{p}`. + Thus, the greatest common divisor with f is `\prod_{s \in S}(x - s)`, + with S being the set of solutions to f. Furthermore, + when a is randomly determined, `(x+a)^{(p-1)/2}-1` is + a polynomial with (p-1)/2 randomly chosen solutions. + The greatest common divisor of f may be a nontrivial factor of f. + + When p is large and the degree of f is small, + it is faster than naive solution methods. + + Parameters + ========== + + f : polynomial + p : prime number + + Returns + ======= + + list[int] + a list of solutions, sorted in ascending order + by integers in the range [1, p). The same value + does not exist in the list even if there is + a multiple solution. If no solution exists, returns []. + + Examples + ======== + + >>> from sympy.polys.galoistools import _csolve_prime_las_vegas + >>> _csolve_prime_las_vegas([1, 4, 3], 7) # x^2 + 4x + 3 = 0 (mod 7) + [4, 6] + >>> _csolve_prime_las_vegas([5, 7, 1, 9], 11) # 5x^3 + 7x^2 + x + 9 = 0 (mod 11) + [1, 5, 8] + + References + ========== + + .. [1] R. Crandall and C. Pomerance "Prime Numbers", 2nd Ed., Algorithm 2.3.10 + + """ + from sympy.polys.domains import ZZ + from sympy.ntheory import sqrt_mod + randint = _randint(seed) + root = set() + g = gf_pow_mod([1, 0], p - 1, f, p, ZZ) + g = gf_sub_ground(g, 1, p, ZZ) + # We want to calculate gcd(x**(p-1) - 1, f(x)) + factors = [gf_gcd(f, g, p, ZZ)] + while factors: + f = factors.pop() + # If the degree is small, solve directly + if len(f) <= 1: + continue + if len(f) == 2: + root.add(-invert(f[0], p) * f[1] % p) + continue + if len(f) == 3: + inv = invert(f[0], p) + b = f[1] * inv % p + b = (b + p * (b % 2)) // 2 + root.update((r - b) % p for r in + sqrt_mod(b**2 - f[2] * inv, p, all_roots=True)) + continue + while True: + # Determine `a` randomly and + # compute gcd((x+a)**((p-1)//2)-1, f(x)) + a = randint(0, p - 1) + g = gf_pow_mod([1, a], (p - 1) // 2, f, p, ZZ) + g = gf_sub_ground(g, 1, p, ZZ) + g = gf_gcd(f, g, p, ZZ) + if 1 < len(g) < len(f): + factors.append(g) + factors.append(gf_div(f, g, p, ZZ)[0]) + break + return sorted(root) + + +def csolve_prime(f, p, e=1): + r""" Solutions of `f(x) \equiv 0 \pmod{p^e}`. + + Parameters + ========== + + f : polynomial + p : prime number + e : positive integer + + Returns + ======= + + list[int] + a list of solutions, sorted in ascending order + by integers in the range [1, p**e). The same value + does not exist in the list even if there is + a multiple solution. If no solution exists, returns []. + + Examples + ======== + + >>> from sympy.polys.galoistools import csolve_prime + >>> csolve_prime([1, 1, 7], 3, 1) + [1] + >>> csolve_prime([1, 1, 7], 3, 2) + [1, 4, 7] + + Solutions [7, 4, 1] (mod 3**2) are generated by ``_raise_mod_power()`` + from solution [1] (mod 3). + """ + from sympy.polys.domains import ZZ + g = [MPZ(int(c)) for c in f] + # Convert to polynomial of degree at most p-1 + for i in range(len(g) - p): + g[i + p - 1] += g[i] + g[i] = 0 + g = gf_trunc(g, p) + # Checks whether g(x) is divisible by x + k = 0 + while k < len(g) and g[len(g) - k - 1] == 0: + k += 1 + if k: + g = g[:-k] + root_zero = [0] + else: + root_zero = [] + if g == []: + X1 = list(range(p)) + elif len(g)**2 < p: + # The conditions under which `_csolve_prime_las_vegas` is faster than + # a naive solution are worth considering. + X1 = root_zero + _csolve_prime_las_vegas(g, p) + else: + X1 = root_zero + [i for i in range(p) if gf_eval(g, i, p, ZZ) == 0] + if e == 1: + return X1 + X = [] + S = list(zip(X1, [1]*len(X1))) + while S: + x, s = S.pop() + if s == e: + X.append(x) + else: + s1 = s + 1 + ps = p**s + S.extend([(x + v*ps, s1) for v in _raise_mod_power(x, s, p, f)]) + return sorted(X) + + +def gf_csolve(f, n): + """ + To solve f(x) congruent 0 mod(n). + + n is divided into canonical factors and f(x) cong 0 mod(p**e) will be + solved for each factor. Applying the Chinese Remainder Theorem to the + results returns the final answers. + + Examples + ======== + + Solve [1, 1, 7] congruent 0 mod(189): + + >>> from sympy.polys.galoistools import gf_csolve + >>> gf_csolve([1, 1, 7], 189) + [13, 49, 76, 112, 139, 175] + + See Also + ======== + + sympy.ntheory.residue_ntheory.polynomial_congruence : a higher level solving routine + + References + ========== + + .. [1] 'An introduction to the Theory of Numbers' 5th Edition by Ivan Niven, + Zuckerman and Montgomery. + + """ + from sympy.polys.domains import ZZ + from sympy.ntheory import factorint + P = factorint(n) + X = [csolve_prime(f, p, e) for p, e in P.items()] + pools = list(map(tuple, X)) + perms = [[]] + for pool in pools: + perms = [x + [y] for x in perms for y in pool] + dist_factors = [pow(p, e) for p, e in P.items()] + return sorted([gf_crt(per, dist_factors, ZZ) for per in perms]) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/groebnertools.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/groebnertools.py new file mode 100644 index 0000000000000000000000000000000000000000..fc5c2f228ab4f4182e4c8fff68d974aa25c9d531 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/modulargcd.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/modulargcd.py new file mode 100644 index 0000000000000000000000000000000000000000..20dfd33d919703873e9fb5d3039abb351297df61 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/orderings.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/orderings.py new file mode 100644 index 0000000000000000000000000000000000000000..b6ed575d5103440e1e8ebda4c53c4149d3badf11 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/orderings.py @@ -0,0 +1,286 @@ +"""Definitions of monomial orderings. """ + +from __future__ import annotations + +__all__ = ["lex", "grlex", "grevlex", "ilex", "igrlex", "igrevlex"] + +from sympy.core import Symbol +from sympy.utilities.iterables import iterable + +class MonomialOrder: + """Base class for monomial orderings. """ + + alias: str | None = None + is_global: bool | None = None + is_default = False + + def __repr__(self): + return self.__class__.__name__ + "()" + + def __str__(self): + return self.alias + + def __call__(self, monomial): + raise NotImplementedError + + def __eq__(self, other): + return self.__class__ == other.__class__ + + def __hash__(self): + return hash(self.__class__) + + def __ne__(self, other): + return not (self == other) + +class LexOrder(MonomialOrder): + """Lexicographic order of monomials. """ + + alias = 'lex' + is_global = True + is_default = True + + def __call__(self, monomial): + return monomial + +class GradedLexOrder(MonomialOrder): + """Graded lexicographic order of monomials. """ + + alias = 'grlex' + is_global = True + + def __call__(self, monomial): + return (sum(monomial), monomial) + +class ReversedGradedLexOrder(MonomialOrder): + """Reversed graded lexicographic order of monomials. """ + + alias = 'grevlex' + is_global = True + + def __call__(self, monomial): + return (sum(monomial), tuple(reversed([-m for m in monomial]))) + +class ProductOrder(MonomialOrder): + """ + A product order built from other monomial orders. + + Given (not necessarily total) orders O1, O2, ..., On, their product order + P is defined as M1 > M2 iff there exists i such that O1(M1) = O2(M2), + ..., Oi(M1) = Oi(M2), O{i+1}(M1) > O{i+1}(M2). + + Product orders are typically built from monomial orders on different sets + of variables. + + ProductOrder is constructed by passing a list of pairs + [(O1, L1), (O2, L2), ...] where Oi are MonomialOrders and Li are callables. + Upon comparison, the Li are passed the total monomial, and should filter + out the part of the monomial to pass to Oi. + + Examples + ======== + + We can use a lexicographic order on x_1, x_2 and also on + y_1, y_2, y_3, and their product on {x_i, y_i} as follows: + + >>> from sympy.polys.orderings import lex, grlex, ProductOrder + >>> P = ProductOrder( + ... (lex, lambda m: m[:2]), # lex order on x_1 and x_2 of monomial + ... (grlex, lambda m: m[2:]) # grlex on y_1, y_2, y_3 + ... ) + >>> P((2, 1, 1, 0, 0)) > P((1, 10, 0, 2, 0)) + True + + Here the exponent `2` of `x_1` in the first monomial + (`x_1^2 x_2 y_1`) is bigger than the exponent `1` of `x_1` in the + second monomial (`x_1 x_2^10 y_2^2`), so the first monomial is greater + in the product ordering. + + >>> P((2, 1, 1, 0, 0)) < P((2, 1, 0, 2, 0)) + True + + Here the exponents of `x_1` and `x_2` agree, so the grlex order on + `y_1, y_2, y_3` is used to decide the ordering. In this case the monomial + `y_2^2` is ordered larger than `y_1`, since for the grlex order the degree + of the monomial is most important. + """ + + def __init__(self, *args): + self.args = args + + def __call__(self, monomial): + return tuple(O(lamda(monomial)) for (O, lamda) in self.args) + + def __repr__(self): + contents = [repr(x[0]) for x in self.args] + return self.__class__.__name__ + '(' + ", ".join(contents) + ')' + + def __str__(self): + contents = [str(x[0]) for x in self.args] + return self.__class__.__name__ + '(' + ", ".join(contents) + ')' + + def __eq__(self, other): + if not isinstance(other, ProductOrder): + return False + return self.args == other.args + + def __hash__(self): + return hash((self.__class__, self.args)) + + @property + def is_global(self): + if all(o.is_global is True for o, _ in self.args): + return True + if all(o.is_global is False for o, _ in self.args): + return False + return None + +class InverseOrder(MonomialOrder): + """ + The "inverse" of another monomial order. + + If O is any monomial order, we can construct another monomial order iO + such that `A >_{iO} B` if and only if `B >_O A`. This is useful for + constructing local orders. + + Note that many algorithms only work with *global* orders. + + For example, in the inverse lexicographic order on a single variable `x`, + high powers of `x` count as small: + + >>> from sympy.polys.orderings import lex, InverseOrder + >>> ilex = InverseOrder(lex) + >>> ilex((5,)) < ilex((0,)) + True + """ + + def __init__(self, O): + self.O = O + + def __str__(self): + return "i" + str(self.O) + + def __call__(self, monomial): + def inv(l): + if iterable(l): + return tuple(inv(x) for x in l) + return -l + return inv(self.O(monomial)) + + @property + def is_global(self): + if self.O.is_global is True: + return False + if self.O.is_global is False: + return True + return None + + def __eq__(self, other): + return isinstance(other, InverseOrder) and other.O == self.O + + def __hash__(self): + return hash((self.__class__, self.O)) + +lex = LexOrder() +grlex = GradedLexOrder() +grevlex = ReversedGradedLexOrder() +ilex = InverseOrder(lex) +igrlex = InverseOrder(grlex) +igrevlex = InverseOrder(grevlex) + +_monomial_key = { + 'lex': lex, + 'grlex': grlex, + 'grevlex': grevlex, + 'ilex': ilex, + 'igrlex': igrlex, + 'igrevlex': igrevlex +} + +def monomial_key(order=None, gens=None): + """ + Return a function defining admissible order on monomials. + + The result of a call to :func:`monomial_key` is a function which should + be used as a key to :func:`sorted` built-in function, to provide order + in a set of monomials of the same length. + + Currently supported monomial orderings are: + + 1. lex - lexicographic order (default) + 2. grlex - graded lexicographic order + 3. grevlex - reversed graded lexicographic order + 4. ilex, igrlex, igrevlex - the corresponding inverse orders + + If the ``order`` input argument is not a string but has ``__call__`` + attribute, then it will pass through with an assumption that the + callable object defines an admissible order on monomials. + + If the ``gens`` input argument contains a list of generators, the + resulting key function can be used to sort SymPy ``Expr`` objects. + + """ + if order is None: + order = lex + + if isinstance(order, Symbol): + order = str(order) + + if isinstance(order, str): + try: + order = _monomial_key[order] + except KeyError: + raise ValueError("supported monomial orderings are 'lex', 'grlex' and 'grevlex', got %r" % order) + if hasattr(order, '__call__'): + if gens is not None: + def _order(expr): + return order(expr.as_poly(*gens).degree_list()) + return _order + return order + else: + raise ValueError("monomial ordering specification must be a string or a callable, got %s" % order) + +class _ItemGetter: + """Helper class to return a subsequence of values.""" + + def __init__(self, seq): + self.seq = tuple(seq) + + def __call__(self, m): + return tuple(m[idx] for idx in self.seq) + + def __eq__(self, other): + if not isinstance(other, _ItemGetter): + return False + return self.seq == other.seq + +def build_product_order(arg, gens): + """ + Build a monomial order on ``gens``. + + ``arg`` should be a tuple of iterables. The first element of each iterable + should be a string or monomial order (will be passed to monomial_key), + the others should be subsets of the generators. This function will build + the corresponding product order. + + For example, build a product of two grlex orders: + + >>> from sympy.polys.orderings import build_product_order + >>> from sympy.abc import x, y, z, t + + >>> O = build_product_order((("grlex", x, y), ("grlex", z, t)), [x, y, z, t]) + >>> O((1, 2, 3, 4)) + ((3, (1, 2)), (7, (3, 4))) + + """ + gens2idx = {} + for i, g in enumerate(gens): + gens2idx[g] = i + order = [] + for expr in arg: + name = expr[0] + var = expr[1:] + + def makelambda(var): + return _ItemGetter(gens2idx[g] for g in var) + order.append((monomial_key(name), makelambda(var))) + return ProductOrder(*order) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/orthopolys.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/orthopolys.py new file mode 100644 index 0000000000000000000000000000000000000000..ee82457703a2be172951ee38e3cd67f221a438a0 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polyerrors.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polyerrors.py new file mode 100644 index 0000000000000000000000000000000000000000..79385ffaf6746386f8f108c3e02992dcaf4a4f55 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polyerrors.py @@ -0,0 +1,183 @@ +"""Definitions of common exceptions for `polys` module. """ + + +from sympy.utilities import public + +@public +class BasePolynomialError(Exception): + """Base class for polynomial related exceptions. """ + + def new(self, *args): + raise NotImplementedError("abstract base class") + +@public +class ExactQuotientFailed(BasePolynomialError): + + def __init__(self, f, g, dom=None): + self.f, self.g, self.dom = f, g, dom + + def __str__(self): # pragma: no cover + from sympy.printing.str import sstr + + if self.dom is None: + return "%s does not divide %s" % (sstr(self.g), sstr(self.f)) + else: + return "%s does not divide %s in %s" % (sstr(self.g), sstr(self.f), sstr(self.dom)) + + def new(self, f, g): + return self.__class__(f, g, self.dom) + +@public +class PolynomialDivisionFailed(BasePolynomialError): + + def __init__(self, f, g, domain): + self.f = f + self.g = g + self.domain = domain + + def __str__(self): + if self.domain.is_EX: + msg = "You may want to use a different simplification algorithm. Note " \ + "that in general it's not possible to guarantee to detect zero " \ + "in this domain." + elif not self.domain.is_Exact: + msg = "Your working precision or tolerance of computations may be set " \ + "improperly. Adjust those parameters of the coefficient domain " \ + "and try again." + else: + msg = "Zero detection is guaranteed in this coefficient domain. This " \ + "may indicate a bug in SymPy or the domain is user defined and " \ + "doesn't implement zero detection properly." + + return "couldn't reduce degree in a polynomial division algorithm when " \ + "dividing %s by %s. This can happen when it's not possible to " \ + "detect zero in the coefficient domain. The domain of computation " \ + "is %s. %s" % (self.f, self.g, self.domain, msg) + +@public +class OperationNotSupported(BasePolynomialError): + + def __init__(self, poly, func): + self.poly = poly + self.func = func + + def __str__(self): # pragma: no cover + return "`%s` operation not supported by %s representation" % (self.func, self.poly.rep.__class__.__name__) + +@public +class HeuristicGCDFailed(BasePolynomialError): + pass + +class ModularGCDFailed(BasePolynomialError): + pass + +@public +class HomomorphismFailed(BasePolynomialError): + pass + +@public +class IsomorphismFailed(BasePolynomialError): + pass + +@public +class ExtraneousFactors(BasePolynomialError): + pass + +@public +class EvaluationFailed(BasePolynomialError): + pass + +@public +class RefinementFailed(BasePolynomialError): + pass + +@public +class CoercionFailed(BasePolynomialError): + pass + +@public +class NotInvertible(BasePolynomialError): + pass + +@public +class NotReversible(BasePolynomialError): + pass + +@public +class NotAlgebraic(BasePolynomialError): + pass + +@public +class DomainError(BasePolynomialError): + pass + +@public +class PolynomialError(BasePolynomialError): + pass + +@public +class UnificationFailed(BasePolynomialError): + pass + +@public +class UnsolvableFactorError(BasePolynomialError): + """Raised if ``roots`` is called with strict=True and a polynomial + having a factor whose solutions are not expressible in radicals + is encountered.""" + +@public +class GeneratorsError(BasePolynomialError): + pass + +@public +class GeneratorsNeeded(GeneratorsError): + pass + +@public +class ComputationFailed(BasePolynomialError): + + def __init__(self, func, nargs, exc): + self.func = func + self.nargs = nargs + self.exc = exc + + def __str__(self): + return "%s(%s) failed without generators" % (self.func, ', '.join(map(str, self.exc.exprs[:self.nargs]))) + +@public +class UnivariatePolynomialError(PolynomialError): + pass + +@public +class MultivariatePolynomialError(PolynomialError): + pass + +@public +class PolificationFailed(PolynomialError): + + def __init__(self, opt, origs, exprs, seq=False): + if not seq: + self.orig = origs + self.expr = exprs + self.origs = [origs] + self.exprs = [exprs] + else: + self.origs = origs + self.exprs = exprs + + self.opt = opt + self.seq = seq + + def __str__(self): # pragma: no cover + if not self.seq: + return "Cannot construct a polynomial from %s" % str(self.orig) + else: + return "Cannot construct polynomials from %s" % ', '.join(map(str, self.origs)) + +@public +class OptionError(BasePolynomialError): + pass + +@public +class FlagError(OptionError): + pass diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polyoptions.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polyoptions.py new file mode 100644 index 0000000000000000000000000000000000000000..739437ff50ba3586479b692bd1123b602dedd8b7 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polyoptions.py @@ -0,0 +1,786 @@ +"""Options manager for :class:`~.Poly` and public API functions. """ + +from __future__ import annotations + +__all__ = ["Options"] + +from sympy.core import Basic, sympify +from sympy.polys.polyerrors import GeneratorsError, OptionError, FlagError +from sympy.utilities import numbered_symbols, topological_sort, public +from sympy.utilities.iterables import has_dups, is_sequence + +import sympy.polys + +import re + +class Option: + """Base class for all kinds of options. """ + + option: str | None = None + + is_Flag = False + + requires: list[str] = [] + excludes: list[str] = [] + + after: list[str] = [] + before: list[str] = [] + + @classmethod + def default(cls): + return None + + @classmethod + def preprocess(cls, option): + return None + + @classmethod + def postprocess(cls, options): + pass + + +class Flag(Option): + """Base class for all kinds of flags. """ + + is_Flag = True + + +class BooleanOption(Option): + """An option that must have a boolean value or equivalent assigned. """ + + @classmethod + def preprocess(cls, value): + if value in [True, False]: + return bool(value) + else: + raise OptionError("'%s' must have a boolean value assigned, got %s" % (cls.option, value)) + + +class OptionType(type): + """Base type for all options that does registers options. """ + + def __init__(cls, *args, **kwargs): + @property + def getter(self): + try: + return self[cls.option] + except KeyError: + return cls.default() + + setattr(Options, cls.option, getter) + Options.__options__[cls.option] = cls + + +@public +class Options(dict): + """ + Options manager for polynomial manipulation module. + + Examples + ======== + + >>> from sympy.polys.polyoptions import Options + >>> from sympy.polys.polyoptions import build_options + + >>> from sympy.abc import x, y, z + + >>> Options((x, y, z), {'domain': 'ZZ'}) + {'auto': False, 'domain': ZZ, 'gens': (x, y, z)} + + >>> build_options((x, y, z), {'domain': 'ZZ'}) + {'auto': False, 'domain': ZZ, 'gens': (x, y, z)} + + **Options** + + * Expand --- boolean option + * Gens --- option + * Wrt --- option + * Sort --- option + * Order --- option + * Field --- boolean option + * Greedy --- boolean option + * Domain --- option + * Split --- boolean option + * Gaussian --- boolean option + * Extension --- option + * Modulus --- option + * Symmetric --- boolean option + * Strict --- boolean option + + **Flags** + + * Auto --- boolean flag + * Frac --- boolean flag + * Formal --- boolean flag + * Polys --- boolean flag + * Include --- boolean flag + * All --- boolean flag + * Gen --- flag + * Series --- boolean flag + + """ + + __order__ = None + __options__: dict[str, type[Option]] = {} + + def __init__(self, gens, args, flags=None, strict=False): + dict.__init__(self) + + if gens and args.get('gens', ()): + raise OptionError( + "both '*gens' and keyword argument 'gens' supplied") + elif gens: + args = dict(args) + args['gens'] = gens + + defaults = args.pop('defaults', {}) + + def preprocess_options(args): + for option, value in args.items(): + try: + cls = self.__options__[option] + except KeyError: + raise OptionError("'%s' is not a valid option" % option) + + if issubclass(cls, Flag): + if flags is None or option not in flags: + if strict: + raise OptionError("'%s' flag is not allowed in this context" % option) + + if value is not None: + self[option] = cls.preprocess(value) + + preprocess_options(args) + + for key in dict(defaults): + if key in self: + del defaults[key] + else: + for option in self.keys(): + cls = self.__options__[option] + + if key in cls.excludes: + del defaults[key] + break + + preprocess_options(defaults) + + for option in self.keys(): + cls = self.__options__[option] + + for require_option in cls.requires: + if self.get(require_option) is None: + raise OptionError("'%s' option is only allowed together with '%s'" % (option, require_option)) + + for exclude_option in cls.excludes: + if self.get(exclude_option) is not None: + raise OptionError("'%s' option is not allowed together with '%s'" % (option, exclude_option)) + + for option in self.__order__: + self.__options__[option].postprocess(self) + + @classmethod + def _init_dependencies_order(cls): + """Resolve the order of options' processing. """ + if cls.__order__ is None: + vertices, edges = [], set() + + for name, option in cls.__options__.items(): + vertices.append(name) + + edges.update((_name, name) for _name in option.after) + + edges.update((name, _name) for _name in option.before) + + try: + cls.__order__ = topological_sort((vertices, list(edges))) + except ValueError: + raise RuntimeError( + "cycle detected in sympy.polys options framework") + + def clone(self, updates={}): + """Clone ``self`` and update specified options. """ + obj = dict.__new__(self.__class__) + + for option, value in self.items(): + obj[option] = value + + for option, value in updates.items(): + obj[option] = value + + return obj + + def __setattr__(self, attr, value): + if attr in self.__options__: + self[attr] = value + else: + super().__setattr__(attr, value) + + @property + def args(self): + args = {} + + for option, value in self.items(): + if value is not None and option != 'gens': + cls = self.__options__[option] + + if not issubclass(cls, Flag): + args[option] = value + + return args + + @property + def options(self): + options = {} + + for option, cls in self.__options__.items(): + if not issubclass(cls, Flag): + options[option] = getattr(self, option) + + return options + + @property + def flags(self): + flags = {} + + for option, cls in self.__options__.items(): + if issubclass(cls, Flag): + flags[option] = getattr(self, option) + + return flags + + +class Expand(BooleanOption, metaclass=OptionType): + """``expand`` option to polynomial manipulation functions. """ + + option = 'expand' + + requires: list[str] = [] + excludes: list[str] = [] + + @classmethod + def default(cls): + return True + + +class Gens(Option, metaclass=OptionType): + """``gens`` option to polynomial manipulation functions. """ + + option = 'gens' + + requires: list[str] = [] + excludes: list[str] = [] + + @classmethod + def default(cls): + return () + + @classmethod + def preprocess(cls, gens): + if isinstance(gens, Basic): + gens = (gens,) + elif len(gens) == 1 and is_sequence(gens[0]): + gens = gens[0] + + if gens == (None,): + gens = () + elif has_dups(gens): + raise GeneratorsError("duplicated generators: %s" % str(gens)) + elif any(gen.is_commutative is False for gen in gens): + raise GeneratorsError("non-commutative generators: %s" % str(gens)) + + return tuple(gens) + + +class Wrt(Option, metaclass=OptionType): + """``wrt`` option to polynomial manipulation functions. """ + + option = 'wrt' + + requires: list[str] = [] + excludes: list[str] = [] + + _re_split = re.compile(r"\s*,\s*|\s+") + + @classmethod + def preprocess(cls, wrt): + if isinstance(wrt, Basic): + return [str(wrt)] + elif isinstance(wrt, str): + wrt = wrt.strip() + if wrt.endswith(','): + raise OptionError('Bad input: missing parameter.') + if not wrt: + return [] + return list(cls._re_split.split(wrt)) + elif hasattr(wrt, '__getitem__'): + return list(map(str, wrt)) + else: + raise OptionError("invalid argument for 'wrt' option") + + +class Sort(Option, metaclass=OptionType): + """``sort`` option to polynomial manipulation functions. """ + + option = 'sort' + + requires: list[str] = [] + excludes: list[str] = [] + + @classmethod + def default(cls): + return [] + + @classmethod + def preprocess(cls, sort): + if isinstance(sort, str): + return [ gen.strip() for gen in sort.split('>') ] + elif hasattr(sort, '__getitem__'): + return list(map(str, sort)) + else: + raise OptionError("invalid argument for 'sort' option") + + +class Order(Option, metaclass=OptionType): + """``order`` option to polynomial manipulation functions. """ + + option = 'order' + + requires: list[str] = [] + excludes: list[str] = [] + + @classmethod + def default(cls): + return sympy.polys.orderings.lex + + @classmethod + def preprocess(cls, order): + return sympy.polys.orderings.monomial_key(order) + + +class Field(BooleanOption, metaclass=OptionType): + """``field`` option to polynomial manipulation functions. """ + + option = 'field' + + requires: list[str] = [] + excludes = ['domain', 'split', 'gaussian'] + + +class Greedy(BooleanOption, metaclass=OptionType): + """``greedy`` option to polynomial manipulation functions. """ + + option = 'greedy' + + requires: list[str] = [] + excludes = ['domain', 'split', 'gaussian', 'extension', 'modulus', 'symmetric'] + + +class Composite(BooleanOption, metaclass=OptionType): + """``composite`` option to polynomial manipulation functions. """ + + option = 'composite' + + @classmethod + def default(cls): + return None + + requires: list[str] = [] + excludes = ['domain', 'split', 'gaussian', 'extension', 'modulus', 'symmetric'] + + +class Domain(Option, metaclass=OptionType): + """``domain`` option to polynomial manipulation functions. """ + + option = 'domain' + + requires: list[str] = [] + excludes = ['field', 'greedy', 'split', 'gaussian', 'extension'] + + after = ['gens'] + + _re_realfield = re.compile(r"^(R|RR)(_(\d+))?$") + _re_complexfield = re.compile(r"^(C|CC)(_(\d+))?$") + _re_finitefield = re.compile(r"^(FF|GF)\((\d+)\)$") + _re_polynomial = re.compile(r"^(Z|ZZ|Q|QQ|ZZ_I|QQ_I|R|RR|C|CC)\[(.+)\]$") + _re_fraction = re.compile(r"^(Z|ZZ|Q|QQ)\((.+)\)$") + _re_algebraic = re.compile(r"^(Q|QQ)\<(.+)\>$") + + @classmethod + def preprocess(cls, domain): + if isinstance(domain, sympy.polys.domains.Domain): + return domain + elif hasattr(domain, 'to_domain'): + return domain.to_domain() + elif isinstance(domain, str): + if domain in ['Z', 'ZZ']: + return sympy.polys.domains.ZZ + + if domain in ['Q', 'QQ']: + return sympy.polys.domains.QQ + + if domain == 'ZZ_I': + return sympy.polys.domains.ZZ_I + + if domain == 'QQ_I': + return sympy.polys.domains.QQ_I + + if domain == 'EX': + return sympy.polys.domains.EX + + r = cls._re_realfield.match(domain) + + if r is not None: + _, _, prec = r.groups() + + if prec is None: + return sympy.polys.domains.RR + else: + return sympy.polys.domains.RealField(int(prec)) + + r = cls._re_complexfield.match(domain) + + if r is not None: + _, _, prec = r.groups() + + if prec is None: + return sympy.polys.domains.CC + else: + return sympy.polys.domains.ComplexField(int(prec)) + + r = cls._re_finitefield.match(domain) + + if r is not None: + return sympy.polys.domains.FF(int(r.groups()[1])) + + r = cls._re_polynomial.match(domain) + + if r is not None: + ground, gens = r.groups() + + gens = list(map(sympify, gens.split(','))) + + if ground in ['Z', 'ZZ']: + return sympy.polys.domains.ZZ.poly_ring(*gens) + elif ground in ['Q', 'QQ']: + return sympy.polys.domains.QQ.poly_ring(*gens) + elif ground in ['R', 'RR']: + return sympy.polys.domains.RR.poly_ring(*gens) + elif ground == 'ZZ_I': + return sympy.polys.domains.ZZ_I.poly_ring(*gens) + elif ground == 'QQ_I': + return sympy.polys.domains.QQ_I.poly_ring(*gens) + else: + return sympy.polys.domains.CC.poly_ring(*gens) + + r = cls._re_fraction.match(domain) + + if r is not None: + ground, gens = r.groups() + + gens = list(map(sympify, gens.split(','))) + + if ground in ['Z', 'ZZ']: + return sympy.polys.domains.ZZ.frac_field(*gens) + else: + return sympy.polys.domains.QQ.frac_field(*gens) + + r = cls._re_algebraic.match(domain) + + if r is not None: + gens = list(map(sympify, r.groups()[1].split(','))) + return sympy.polys.domains.QQ.algebraic_field(*gens) + + raise OptionError('expected a valid domain specification, got %s' % domain) + + @classmethod + def postprocess(cls, options): + if 'gens' in options and 'domain' in options and options['domain'].is_Composite and \ + (set(options['domain'].symbols) & set(options['gens'])): + raise GeneratorsError( + "ground domain and generators interfere together") + elif ('gens' not in options or not options['gens']) and \ + 'domain' in options and options['domain'] == sympy.polys.domains.EX: + raise GeneratorsError("you have to provide generators because EX domain was requested") + + +class Split(BooleanOption, metaclass=OptionType): + """``split`` option to polynomial manipulation functions. """ + + option = 'split' + + requires: list[str] = [] + excludes = ['field', 'greedy', 'domain', 'gaussian', 'extension', + 'modulus', 'symmetric'] + + @classmethod + def postprocess(cls, options): + if 'split' in options: + raise NotImplementedError("'split' option is not implemented yet") + + +class Gaussian(BooleanOption, metaclass=OptionType): + """``gaussian`` option to polynomial manipulation functions. """ + + option = 'gaussian' + + requires: list[str] = [] + excludes = ['field', 'greedy', 'domain', 'split', 'extension', + 'modulus', 'symmetric'] + + @classmethod + def postprocess(cls, options): + if 'gaussian' in options and options['gaussian'] is True: + options['domain'] = sympy.polys.domains.QQ_I + Extension.postprocess(options) + + +class Extension(Option, metaclass=OptionType): + """``extension`` option to polynomial manipulation functions. """ + + option = 'extension' + + requires: list[str] = [] + excludes = ['greedy', 'domain', 'split', 'gaussian', 'modulus', + 'symmetric'] + + @classmethod + def preprocess(cls, extension): + if extension == 1: + return bool(extension) + elif extension == 0: + raise OptionError("'False' is an invalid argument for 'extension'") + else: + if not hasattr(extension, '__iter__'): + extension = {extension} + else: + if not extension: + extension = None + else: + extension = set(extension) + + return extension + + @classmethod + def postprocess(cls, options): + if 'extension' in options and options['extension'] is not True: + options['domain'] = sympy.polys.domains.QQ.algebraic_field( + *options['extension']) + + +class Modulus(Option, metaclass=OptionType): + """``modulus`` option to polynomial manipulation functions. """ + + option = 'modulus' + + requires: list[str] = [] + excludes = ['greedy', 'split', 'domain', 'gaussian', 'extension'] + + @classmethod + def preprocess(cls, modulus): + modulus = sympify(modulus) + + if modulus.is_Integer and modulus > 0: + return int(modulus) + else: + raise OptionError( + "'modulus' must a positive integer, got %s" % modulus) + + @classmethod + def postprocess(cls, options): + if 'modulus' in options: + modulus = options['modulus'] + symmetric = options.get('symmetric', True) + options['domain'] = sympy.polys.domains.FF(modulus, symmetric) + + +class Symmetric(BooleanOption, metaclass=OptionType): + """``symmetric`` option to polynomial manipulation functions. """ + + option = 'symmetric' + + requires = ['modulus'] + excludes = ['greedy', 'domain', 'split', 'gaussian', 'extension'] + + +class Strict(BooleanOption, metaclass=OptionType): + """``strict`` option to polynomial manipulation functions. """ + + option = 'strict' + + @classmethod + def default(cls): + return True + + +class Auto(BooleanOption, Flag, metaclass=OptionType): + """``auto`` flag to polynomial manipulation functions. """ + + option = 'auto' + + after = ['field', 'domain', 'extension', 'gaussian'] + + @classmethod + def default(cls): + return True + + @classmethod + def postprocess(cls, options): + if ('domain' in options or 'field' in options) and 'auto' not in options: + options['auto'] = False + + +class Frac(BooleanOption, Flag, metaclass=OptionType): + """``auto`` option to polynomial manipulation functions. """ + + option = 'frac' + + @classmethod + def default(cls): + return False + + +class Formal(BooleanOption, Flag, metaclass=OptionType): + """``formal`` flag to polynomial manipulation functions. """ + + option = 'formal' + + @classmethod + def default(cls): + return False + + +class Polys(BooleanOption, Flag, metaclass=OptionType): + """``polys`` flag to polynomial manipulation functions. """ + + option = 'polys' + + +class Include(BooleanOption, Flag, metaclass=OptionType): + """``include`` flag to polynomial manipulation functions. """ + + option = 'include' + + @classmethod + def default(cls): + return False + + +class All(BooleanOption, Flag, metaclass=OptionType): + """``all`` flag to polynomial manipulation functions. """ + + option = 'all' + + @classmethod + def default(cls): + return False + + +class Gen(Flag, metaclass=OptionType): + """``gen`` flag to polynomial manipulation functions. """ + + option = 'gen' + + @classmethod + def default(cls): + return 0 + + @classmethod + def preprocess(cls, gen): + if isinstance(gen, (Basic, int)): + return gen + else: + raise OptionError("invalid argument for 'gen' option") + + +class Series(BooleanOption, Flag, metaclass=OptionType): + """``series`` flag to polynomial manipulation functions. """ + + option = 'series' + + @classmethod + def default(cls): + return False + + +class Symbols(Flag, metaclass=OptionType): + """``symbols`` flag to polynomial manipulation functions. """ + + option = 'symbols' + + @classmethod + def default(cls): + return numbered_symbols('s', start=1) + + @classmethod + def preprocess(cls, symbols): + if hasattr(symbols, '__iter__'): + return iter(symbols) + else: + raise OptionError("expected an iterator or iterable container, got %s" % symbols) + + +class Method(Flag, metaclass=OptionType): + """``method`` flag to polynomial manipulation functions. """ + + option = 'method' + + @classmethod + def preprocess(cls, method): + if isinstance(method, str): + return method.lower() + else: + raise OptionError("expected a string, got %s" % method) + + +def build_options(gens, args=None): + """Construct options from keyword arguments or ... options. """ + if args is None: + gens, args = (), gens + + if len(args) != 1 or 'opt' not in args or gens: + return Options(gens, args) + else: + return args['opt'] + + +def allowed_flags(args, flags): + """ + Allow specified flags to be used in the given context. + + Examples + ======== + + >>> from sympy.polys.polyoptions import allowed_flags + >>> from sympy.polys.domains import ZZ + + >>> allowed_flags({'domain': ZZ}, []) + + >>> allowed_flags({'domain': ZZ, 'frac': True}, []) + Traceback (most recent call last): + ... + FlagError: 'frac' flag is not allowed in this context + + >>> allowed_flags({'domain': ZZ, 'frac': True}, ['frac']) + + """ + flags = set(flags) + + for arg in args.keys(): + try: + if Options.__options__[arg].is_Flag and arg not in flags: + raise FlagError( + "'%s' flag is not allowed in this context" % arg) + except KeyError: + raise OptionError("'%s' is not a valid option" % arg) + + +def set_defaults(options, **defaults): + """Update options with default values. """ + if 'defaults' not in options: + options = dict(options) + options['defaults'] = defaults + + return options + +Options._init_dependencies_order() diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polyroots.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polyroots.py new file mode 100644 index 0000000000000000000000000000000000000000..4def1312eb5b94a13e511d2d4f9b15f1d51fd63f --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polytools.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/polytools.py new file mode 100644 index 0000000000000000000000000000000000000000..a8f537c20ddd08d576e50ae0dfb5a5271f365800 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/rationaltools.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/rationaltools.py new file mode 100644 index 0000000000000000000000000000000000000000..0ca513ff2d4af96baaaf1c82caf501750b1524da --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/ring_series.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/ring_series.py new file mode 100644 index 0000000000000000000000000000000000000000..25ae6c732f44edf1617286867de7f4c0829f1b9b --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/rootoftools.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/rootoftools.py new file mode 100644 index 0000000000000000000000000000000000000000..cfd0af01cb5dd1414b40849431e4cd8a872eaa10 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/sqfreetools.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/polys/sqfreetools.py new file mode 100644 index 0000000000000000000000000000000000000000..1e773e9a1b6b40e0238bbac68543e574bbb37353 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/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/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/stats/__pycache__/stochastic_process_types.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/stats/__pycache__/stochastic_process_types.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfbe97306f82e2f5541297812de83092e93ab6c4 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/stats/__pycache__/stochastic_process_types.cpython-311.pyc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df1c246214dd74965c1c126d3e00591fe01fd6e715ed8fee444a40150576fb36 +size 124343 diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_coordsysrect.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_coordsysrect.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d3bef8fcb0f024d0b0a5bee39076faeda1360c9 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_coordsysrect.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_dyadic.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_dyadic.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ae371f358ae496f890200b2dbdaa30fb2222441 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_dyadic.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_functions.cpython-311.pyc b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_functions.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..152e24a51ba144c438028d4ca521b2cdc2d0be46 Binary files /dev/null and b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/__pycache__/test_functions.cpython-311.pyc differ diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/test_coordsysrect.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/test_coordsysrect.py new file mode 100644 index 0000000000000000000000000000000000000000..53eb8c89ec1643a71800efe3e370acff3cb6f9c0 --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/test_coordsysrect.py @@ -0,0 +1,464 @@ +from sympy.testing.pytest import raises +from sympy.vector.coordsysrect import CoordSys3D +from sympy.vector.scalar import BaseScalar +from sympy.core.function import expand +from sympy.core.numbers import pi +from sympy.core.symbol import symbols +from sympy.functions.elementary.hyperbolic import (cosh, sinh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, atan2, cos, sin) +from sympy.matrices.dense import zeros +from sympy.matrices.immutable import ImmutableDenseMatrix as Matrix +from sympy.simplify.simplify import simplify +from sympy.vector.functions import express +from sympy.vector.point import Point +from sympy.vector.vector import Vector +from sympy.vector.orienters import (AxisOrienter, BodyOrienter, + SpaceOrienter, QuaternionOrienter) + + +x, y, z = symbols('x y z') +a, b, c, q = symbols('a b c q') +q1, q2, q3, q4 = symbols('q1 q2 q3 q4') + + +def test_func_args(): + A = CoordSys3D('A') + assert A.x.func(*A.x.args) == A.x + expr = 3*A.x + 4*A.y + assert expr.func(*expr.args) == expr + assert A.i.func(*A.i.args) == A.i + v = A.x*A.i + A.y*A.j + A.z*A.k + assert v.func(*v.args) == v + assert A.origin.func(*A.origin.args) == A.origin + + +def test_coordsys3d_equivalence(): + A = CoordSys3D('A') + A1 = CoordSys3D('A') + assert A1 == A + B = CoordSys3D('B') + assert A != B + + +def test_orienters(): + A = CoordSys3D('A') + axis_orienter = AxisOrienter(a, A.k) + body_orienter = BodyOrienter(a, b, c, '123') + space_orienter = SpaceOrienter(a, b, c, '123') + q_orienter = QuaternionOrienter(q1, q2, q3, q4) + assert axis_orienter.rotation_matrix(A) == Matrix([ + [ cos(a), sin(a), 0], + [-sin(a), cos(a), 0], + [ 0, 0, 1]]) + assert body_orienter.rotation_matrix() == Matrix([ + [ cos(b)*cos(c), sin(a)*sin(b)*cos(c) + sin(c)*cos(a), + sin(a)*sin(c) - sin(b)*cos(a)*cos(c)], + [-sin(c)*cos(b), -sin(a)*sin(b)*sin(c) + cos(a)*cos(c), + sin(a)*cos(c) + sin(b)*sin(c)*cos(a)], + [ sin(b), -sin(a)*cos(b), + cos(a)*cos(b)]]) + assert space_orienter.rotation_matrix() == Matrix([ + [cos(b)*cos(c), sin(c)*cos(b), -sin(b)], + [sin(a)*sin(b)*cos(c) - sin(c)*cos(a), + sin(a)*sin(b)*sin(c) + cos(a)*cos(c), sin(a)*cos(b)], + [sin(a)*sin(c) + sin(b)*cos(a)*cos(c), -sin(a)*cos(c) + + sin(b)*sin(c)*cos(a), cos(a)*cos(b)]]) + assert q_orienter.rotation_matrix() == Matrix([ + [q1**2 + q2**2 - q3**2 - q4**2, 2*q1*q4 + 2*q2*q3, + -2*q1*q3 + 2*q2*q4], + [-2*q1*q4 + 2*q2*q3, q1**2 - q2**2 + q3**2 - q4**2, + 2*q1*q2 + 2*q3*q4], + [2*q1*q3 + 2*q2*q4, + -2*q1*q2 + 2*q3*q4, q1**2 - q2**2 - q3**2 + q4**2]]) + + +def test_coordinate_vars(): + """ + Tests the coordinate variables functionality with respect to + reorientation of coordinate systems. + """ + A = CoordSys3D('A') + # Note that the name given on the lhs is different from A.x._name + assert BaseScalar(0, A, 'A_x', r'\mathbf{{x}_{A}}') == A.x + assert BaseScalar(1, A, 'A_y', r'\mathbf{{y}_{A}}') == A.y + assert BaseScalar(2, A, 'A_z', r'\mathbf{{z}_{A}}') == A.z + assert BaseScalar(0, A, 'A_x', r'\mathbf{{x}_{A}}').__hash__() == A.x.__hash__() + assert isinstance(A.x, BaseScalar) and \ + isinstance(A.y, BaseScalar) and \ + isinstance(A.z, BaseScalar) + assert A.x*A.y == A.y*A.x + assert A.scalar_map(A) == {A.x: A.x, A.y: A.y, A.z: A.z} + assert A.x.system == A + assert A.x.diff(A.x) == 1 + B = A.orient_new_axis('B', q, A.k) + assert B.scalar_map(A) == {B.z: A.z, B.y: -A.x*sin(q) + A.y*cos(q), + B.x: A.x*cos(q) + A.y*sin(q)} + assert A.scalar_map(B) == {A.x: B.x*cos(q) - B.y*sin(q), + A.y: B.x*sin(q) + B.y*cos(q), A.z: B.z} + assert express(B.x, A, variables=True) == A.x*cos(q) + A.y*sin(q) + assert express(B.y, A, variables=True) == -A.x*sin(q) + A.y*cos(q) + assert express(B.z, A, variables=True) == A.z + assert expand(express(B.x*B.y*B.z, A, variables=True)) == \ + expand(A.z*(-A.x*sin(q) + A.y*cos(q))*(A.x*cos(q) + A.y*sin(q))) + assert express(B.x*B.i + B.y*B.j + B.z*B.k, A) == \ + (B.x*cos(q) - B.y*sin(q))*A.i + (B.x*sin(q) + \ + B.y*cos(q))*A.j + B.z*A.k + assert simplify(express(B.x*B.i + B.y*B.j + B.z*B.k, A, \ + variables=True)) == \ + A.x*A.i + A.y*A.j + A.z*A.k + assert express(A.x*A.i + A.y*A.j + A.z*A.k, B) == \ + (A.x*cos(q) + A.y*sin(q))*B.i + \ + (-A.x*sin(q) + A.y*cos(q))*B.j + A.z*B.k + assert simplify(express(A.x*A.i + A.y*A.j + A.z*A.k, B, \ + variables=True)) == \ + B.x*B.i + B.y*B.j + B.z*B.k + N = B.orient_new_axis('N', -q, B.k) + assert N.scalar_map(A) == \ + {N.x: A.x, N.z: A.z, N.y: A.y} + C = A.orient_new_axis('C', q, A.i + A.j + A.k) + mapping = A.scalar_map(C) + assert mapping[A.x].equals(C.x*(2*cos(q) + 1)/3 + + C.y*(-2*sin(q + pi/6) + 1)/3 + + C.z*(-2*cos(q + pi/3) + 1)/3) + assert mapping[A.y].equals(C.x*(-2*cos(q + pi/3) + 1)/3 + + C.y*(2*cos(q) + 1)/3 + + C.z*(-2*sin(q + pi/6) + 1)/3) + assert mapping[A.z].equals(C.x*(-2*sin(q + pi/6) + 1)/3 + + C.y*(-2*cos(q + pi/3) + 1)/3 + + C.z*(2*cos(q) + 1)/3) + D = A.locate_new('D', a*A.i + b*A.j + c*A.k) + assert D.scalar_map(A) == {D.z: A.z - c, D.x: A.x - a, D.y: A.y - b} + E = A.orient_new_axis('E', a, A.k, a*A.i + b*A.j + c*A.k) + assert A.scalar_map(E) == {A.z: E.z + c, + A.x: E.x*cos(a) - E.y*sin(a) + a, + A.y: E.x*sin(a) + E.y*cos(a) + b} + assert E.scalar_map(A) == {E.x: (A.x - a)*cos(a) + (A.y - b)*sin(a), + E.y: (-A.x + a)*sin(a) + (A.y - b)*cos(a), + E.z: A.z - c} + F = A.locate_new('F', Vector.zero) + assert A.scalar_map(F) == {A.z: F.z, A.x: F.x, A.y: F.y} + + +def test_rotation_matrix(): + N = CoordSys3D('N') + A = N.orient_new_axis('A', q1, N.k) + B = A.orient_new_axis('B', q2, A.i) + C = B.orient_new_axis('C', q3, B.j) + D = N.orient_new_axis('D', q4, N.j) + E = N.orient_new_space('E', q1, q2, q3, '123') + F = N.orient_new_quaternion('F', q1, q2, q3, q4) + G = N.orient_new_body('G', q1, q2, q3, '123') + assert N.rotation_matrix(C) == Matrix([ + [- sin(q1) * sin(q2) * sin(q3) + cos(q1) * cos(q3), - sin(q1) * + cos(q2), sin(q1) * sin(q2) * cos(q3) + sin(q3) * cos(q1)], \ + [sin(q1) * cos(q3) + sin(q2) * sin(q3) * cos(q1), \ + cos(q1) * cos(q2), sin(q1) * sin(q3) - sin(q2) * cos(q1) * \ + cos(q3)], [- sin(q3) * cos(q2), sin(q2), cos(q2) * cos(q3)]]) + test_mat = D.rotation_matrix(C) - Matrix( + [[cos(q1) * cos(q3) * cos(q4) - sin(q3) * (- sin(q4) * cos(q2) + + sin(q1) * sin(q2) * cos(q4)), - sin(q2) * sin(q4) - sin(q1) * + cos(q2) * cos(q4), sin(q3) * cos(q1) * cos(q4) + cos(q3) * \ + (- sin(q4) * cos(q2) + sin(q1) * sin(q2) * cos(q4))], \ + [sin(q1) * cos(q3) + sin(q2) * sin(q3) * cos(q1), cos(q1) * \ + cos(q2), sin(q1) * sin(q3) - sin(q2) * cos(q1) * cos(q3)], \ + [sin(q4) * cos(q1) * cos(q3) - sin(q3) * (cos(q2) * cos(q4) + \ + sin(q1) * sin(q2) * \ + sin(q4)), sin(q2) * + cos(q4) - sin(q1) * sin(q4) * cos(q2), sin(q3) * \ + sin(q4) * cos(q1) + cos(q3) * (cos(q2) * cos(q4) + \ + sin(q1) * sin(q2) * sin(q4))]]) + assert test_mat.expand() == zeros(3, 3) + assert E.rotation_matrix(N) == Matrix( + [[cos(q2)*cos(q3), sin(q3)*cos(q2), -sin(q2)], + [sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1), \ + sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), sin(q1)*cos(q2)], \ + [sin(q1)*sin(q3) + sin(q2)*cos(q1)*cos(q3), - \ + sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1), cos(q1)*cos(q2)]]) + assert F.rotation_matrix(N) == Matrix([[ + q1**2 + q2**2 - q3**2 - q4**2, + 2*q1*q4 + 2*q2*q3, -2*q1*q3 + 2*q2*q4],[ -2*q1*q4 + 2*q2*q3, + q1**2 - q2**2 + q3**2 - q4**2, 2*q1*q2 + 2*q3*q4], + [2*q1*q3 + 2*q2*q4, + -2*q1*q2 + 2*q3*q4, + q1**2 - q2**2 - q3**2 + q4**2]]) + assert G.rotation_matrix(N) == Matrix([[ + cos(q2)*cos(q3), sin(q1)*sin(q2)*cos(q3) + sin(q3)*cos(q1), + sin(q1)*sin(q3) - sin(q2)*cos(q1)*cos(q3)], [ + -sin(q3)*cos(q2), -sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), + sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1)],[ + sin(q2), -sin(q1)*cos(q2), cos(q1)*cos(q2)]]) + + +def test_vector_with_orientation(): + """ + Tests the effects of orientation of coordinate systems on + basic vector operations. + """ + N = CoordSys3D('N') + A = N.orient_new_axis('A', q1, N.k) + B = A.orient_new_axis('B', q2, A.i) + C = B.orient_new_axis('C', q3, B.j) + + # Test to_matrix + v1 = a*N.i + b*N.j + c*N.k + assert v1.to_matrix(A) == Matrix([[ a*cos(q1) + b*sin(q1)], + [-a*sin(q1) + b*cos(q1)], + [ c]]) + + # Test dot + assert N.i.dot(A.i) == cos(q1) + assert N.i.dot(A.j) == -sin(q1) + assert N.i.dot(A.k) == 0 + assert N.j.dot(A.i) == sin(q1) + assert N.j.dot(A.j) == cos(q1) + assert N.j.dot(A.k) == 0 + assert N.k.dot(A.i) == 0 + assert N.k.dot(A.j) == 0 + assert N.k.dot(A.k) == 1 + + assert N.i.dot(A.i + A.j) == -sin(q1) + cos(q1) == \ + (A.i + A.j).dot(N.i) + + assert A.i.dot(C.i) == cos(q3) + assert A.i.dot(C.j) == 0 + assert A.i.dot(C.k) == sin(q3) + assert A.j.dot(C.i) == sin(q2)*sin(q3) + assert A.j.dot(C.j) == cos(q2) + assert A.j.dot(C.k) == -sin(q2)*cos(q3) + assert A.k.dot(C.i) == -cos(q2)*sin(q3) + assert A.k.dot(C.j) == sin(q2) + assert A.k.dot(C.k) == cos(q2)*cos(q3) + + # Test cross + assert N.i.cross(A.i) == sin(q1)*A.k + assert N.i.cross(A.j) == cos(q1)*A.k + assert N.i.cross(A.k) == -sin(q1)*A.i - cos(q1)*A.j + assert N.j.cross(A.i) == -cos(q1)*A.k + assert N.j.cross(A.j) == sin(q1)*A.k + assert N.j.cross(A.k) == cos(q1)*A.i - sin(q1)*A.j + assert N.k.cross(A.i) == A.j + assert N.k.cross(A.j) == -A.i + assert N.k.cross(A.k) == Vector.zero + + assert N.i.cross(A.i) == sin(q1)*A.k + assert N.i.cross(A.j) == cos(q1)*A.k + assert N.i.cross(A.i + A.j) == sin(q1)*A.k + cos(q1)*A.k + assert (A.i + A.j).cross(N.i) == (-sin(q1) - cos(q1))*N.k + + assert A.i.cross(C.i) == sin(q3)*C.j + assert A.i.cross(C.j) == -sin(q3)*C.i + cos(q3)*C.k + assert A.i.cross(C.k) == -cos(q3)*C.j + assert C.i.cross(A.i) == (-sin(q3)*cos(q2))*A.j + \ + (-sin(q2)*sin(q3))*A.k + assert C.j.cross(A.i) == (sin(q2))*A.j + (-cos(q2))*A.k + assert express(C.k.cross(A.i), C).trigsimp() == cos(q3)*C.j + + +def test_orient_new_methods(): + N = CoordSys3D('N') + orienter1 = AxisOrienter(q4, N.j) + orienter2 = SpaceOrienter(q1, q2, q3, '123') + orienter3 = QuaternionOrienter(q1, q2, q3, q4) + orienter4 = BodyOrienter(q1, q2, q3, '123') + D = N.orient_new('D', (orienter1, )) + E = N.orient_new('E', (orienter2, )) + F = N.orient_new('F', (orienter3, )) + G = N.orient_new('G', (orienter4, )) + assert D == N.orient_new_axis('D', q4, N.j) + assert E == N.orient_new_space('E', q1, q2, q3, '123') + assert F == N.orient_new_quaternion('F', q1, q2, q3, q4) + assert G == N.orient_new_body('G', q1, q2, q3, '123') + + +def test_locatenew_point(): + """ + Tests Point class, and locate_new method in CoordSys3D. + """ + A = CoordSys3D('A') + assert isinstance(A.origin, Point) + v = a*A.i + b*A.j + c*A.k + C = A.locate_new('C', v) + assert C.origin.position_wrt(A) == \ + C.position_wrt(A) == \ + C.origin.position_wrt(A.origin) == v + assert A.origin.position_wrt(C) == \ + A.position_wrt(C) == \ + A.origin.position_wrt(C.origin) == -v + assert A.origin.express_coordinates(C) == (-a, -b, -c) + p = A.origin.locate_new('p', -v) + assert p.express_coordinates(A) == (-a, -b, -c) + assert p.position_wrt(C.origin) == p.position_wrt(C) == \ + -2 * v + p1 = p.locate_new('p1', 2*v) + assert p1.position_wrt(C.origin) == Vector.zero + assert p1.express_coordinates(C) == (0, 0, 0) + p2 = p.locate_new('p2', A.i) + assert p1.position_wrt(p2) == 2*v - A.i + assert p2.express_coordinates(C) == (-2*a + 1, -2*b, -2*c) + + +def test_create_new(): + a = CoordSys3D('a') + c = a.create_new('c', transformation='spherical') + assert c._parent == a + assert c.transformation_to_parent() == \ + (c.r*sin(c.theta)*cos(c.phi), c.r*sin(c.theta)*sin(c.phi), c.r*cos(c.theta)) + assert c.transformation_from_parent() == \ + (sqrt(a.x**2 + a.y**2 + a.z**2), acos(a.z/sqrt(a.x**2 + a.y**2 + a.z**2)), atan2(a.y, a.x)) + + +def test_evalf(): + A = CoordSys3D('A') + v = 3*A.i + 4*A.j + a*A.k + assert v.n() == v.evalf() + assert v.evalf(subs={a:1}) == v.subs(a, 1).evalf() + + +def test_lame_coefficients(): + a = CoordSys3D('a', 'spherical') + assert a.lame_coefficients() == (1, a.r, sin(a.theta)*a.r) + a = CoordSys3D('a') + assert a.lame_coefficients() == (1, 1, 1) + a = CoordSys3D('a', 'cartesian') + assert a.lame_coefficients() == (1, 1, 1) + a = CoordSys3D('a', 'cylindrical') + assert a.lame_coefficients() == (1, a.r, 1) + + +def test_transformation_equations(): + + x, y, z = symbols('x y z') + # Str + a = CoordSys3D('a', transformation='spherical', + variable_names=["r", "theta", "phi"]) + r, theta, phi = a.base_scalars() + + assert r == a.r + assert theta == a.theta + assert phi == a.phi + + raises(AttributeError, lambda: a.x) + raises(AttributeError, lambda: a.y) + raises(AttributeError, lambda: a.z) + + assert a.transformation_to_parent() == ( + r*sin(theta)*cos(phi), + r*sin(theta)*sin(phi), + r*cos(theta) + ) + assert a.lame_coefficients() == (1, r, r*sin(theta)) + assert a.transformation_from_parent_function()(x, y, z) == ( + sqrt(x ** 2 + y ** 2 + z ** 2), + acos((z) / sqrt(x**2 + y**2 + z**2)), + atan2(y, x) + ) + a = CoordSys3D('a', transformation='cylindrical', + variable_names=["r", "theta", "z"]) + r, theta, z = a.base_scalars() + assert a.transformation_to_parent() == ( + r*cos(theta), + r*sin(theta), + z + ) + assert a.lame_coefficients() == (1, a.r, 1) + assert a.transformation_from_parent_function()(x, y, z) == (sqrt(x**2 + y**2), + atan2(y, x), z) + + a = CoordSys3D('a', 'cartesian') + assert a.transformation_to_parent() == (a.x, a.y, a.z) + assert a.lame_coefficients() == (1, 1, 1) + assert a.transformation_from_parent_function()(x, y, z) == (x, y, z) + + # Variables and expressions + + # Cartesian with equation tuple: + x, y, z = symbols('x y z') + a = CoordSys3D('a', ((x, y, z), (x, y, z))) + a._calculate_inv_trans_equations() + assert a.transformation_to_parent() == (a.x1, a.x2, a.x3) + assert a.lame_coefficients() == (1, 1, 1) + assert a.transformation_from_parent_function()(x, y, z) == (x, y, z) + r, theta, z = symbols("r theta z") + + # Cylindrical with equation tuple: + a = CoordSys3D('a', [(r, theta, z), (r*cos(theta), r*sin(theta), z)], + variable_names=["r", "theta", "z"]) + r, theta, z = a.base_scalars() + assert a.transformation_to_parent() == ( + r*cos(theta), r*sin(theta), z + ) + assert a.lame_coefficients() == ( + sqrt(sin(theta)**2 + cos(theta)**2), + sqrt(r**2*sin(theta)**2 + r**2*cos(theta)**2), + 1 + ) # ==> this should simplify to (1, r, 1), tests are too slow with `simplify`. + + # Definitions with `lambda`: + + # Cartesian with `lambda` + a = CoordSys3D('a', lambda x, y, z: (x, y, z)) + assert a.transformation_to_parent() == (a.x1, a.x2, a.x3) + assert a.lame_coefficients() == (1, 1, 1) + a._calculate_inv_trans_equations() + assert a.transformation_from_parent_function()(x, y, z) == (x, y, z) + + # Spherical with `lambda` + a = CoordSys3D('a', lambda r, theta, phi: (r*sin(theta)*cos(phi), r*sin(theta)*sin(phi), r*cos(theta)), + variable_names=["r", "theta", "phi"]) + r, theta, phi = a.base_scalars() + assert a.transformation_to_parent() == ( + r*sin(theta)*cos(phi), r*sin(phi)*sin(theta), r*cos(theta) + ) + assert a.lame_coefficients() == ( + sqrt(sin(phi)**2*sin(theta)**2 + sin(theta)**2*cos(phi)**2 + cos(theta)**2), + sqrt(r**2*sin(phi)**2*cos(theta)**2 + r**2*sin(theta)**2 + r**2*cos(phi)**2*cos(theta)**2), + sqrt(r**2*sin(phi)**2*sin(theta)**2 + r**2*sin(theta)**2*cos(phi)**2) + ) # ==> this should simplify to (1, r, sin(theta)*r), `simplify` is too slow. + + # Cylindrical with `lambda` + a = CoordSys3D('a', lambda r, theta, z: + (r*cos(theta), r*sin(theta), z), + variable_names=["r", "theta", "z"] + ) + r, theta, z = a.base_scalars() + assert a.transformation_to_parent() == (r*cos(theta), r*sin(theta), z) + assert a.lame_coefficients() == ( + sqrt(sin(theta)**2 + cos(theta)**2), + sqrt(r**2*sin(theta)**2 + r**2*cos(theta)**2), + 1 + ) # ==> this should simplify to (1, a.x, 1) + + raises(TypeError, lambda: CoordSys3D('a', transformation={ + x: x*sin(y)*cos(z), y:x*sin(y)*sin(z), z: x*cos(y)})) + + +def test_check_orthogonality(): + x, y, z = symbols('x y z') + u,v = symbols('u, v') + a = CoordSys3D('a', transformation=((x, y, z), (x*sin(y)*cos(z), x*sin(y)*sin(z), x*cos(y)))) + assert a._check_orthogonality(a._transformation) is True + a = CoordSys3D('a', transformation=((x, y, z), (x * cos(y), x * sin(y), z))) + assert a._check_orthogonality(a._transformation) is True + a = CoordSys3D('a', transformation=((u, v, z), (cosh(u) * cos(v), sinh(u) * sin(v), z))) + assert a._check_orthogonality(a._transformation) is True + + raises(ValueError, lambda: CoordSys3D('a', transformation=((x, y, z), (x, x, z)))) + raises(ValueError, lambda: CoordSys3D('a', transformation=( + (x, y, z), (x*sin(y/2)*cos(z), x*sin(y)*sin(z), x*cos(y))))) + + +def test_rotation_trans_equations(): + a = CoordSys3D('a') + from sympy.core.symbol import symbols + q0 = symbols('q0') + assert a._rotation_trans_equations(a._parent_rotation_matrix, a.base_scalars()) == (a.x, a.y, a.z) + assert a._rotation_trans_equations(a._inverse_rotation_matrix(), a.base_scalars()) == (a.x, a.y, a.z) + b = a.orient_new_axis('b', 0, -a.k) + assert b._rotation_trans_equations(b._parent_rotation_matrix, b.base_scalars()) == (b.x, b.y, b.z) + assert b._rotation_trans_equations(b._inverse_rotation_matrix(), b.base_scalars()) == (b.x, b.y, b.z) + c = a.orient_new_axis('c', q0, -a.k) + assert c._rotation_trans_equations(c._parent_rotation_matrix, c.base_scalars()) == \ + (-sin(q0) * c.y + cos(q0) * c.x, sin(q0) * c.x + cos(q0) * c.y, c.z) + assert c._rotation_trans_equations(c._inverse_rotation_matrix(), c.base_scalars()) == \ + (sin(q0) * c.y + cos(q0) * c.x, -sin(q0) * c.x + cos(q0) * c.y, c.z) diff --git a/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/test_integrals.py b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/test_integrals.py new file mode 100644 index 0000000000000000000000000000000000000000..08e15562cacf088d469266ca33a3cb993584aa9a --- /dev/null +++ b/tuning-competition-baseline/.venv/lib/python3.11/site-packages/sympy/vector/tests/test_integrals.py @@ -0,0 +1,106 @@ +from sympy.core.numbers import pi +from sympy.core.singleton import S +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.testing.pytest import raises +from sympy.vector.coordsysrect import CoordSys3D +from sympy.vector.integrals import ParametricIntegral, vector_integrate +from sympy.vector.parametricregion import ParametricRegion +from sympy.vector.implicitregion import ImplicitRegion +from sympy.abc import x, y, z, u, v, r, t, theta, phi +from sympy.geometry import Point, Segment, Curve, Circle, Polygon, Plane + +C = CoordSys3D('C') + +def test_parametric_lineintegrals(): + halfcircle = ParametricRegion((4*cos(theta), 4*sin(theta)), (theta, -pi/2, pi/2)) + assert ParametricIntegral(C.x*C.y**4, halfcircle) == S(8192)/5 + + curve = ParametricRegion((t, t**2, t**3), (t, 0, 1)) + field1 = 8*C.x**2*C.y*C.z*C.i + 5*C.z*C.j - 4*C.x*C.y*C.k + assert ParametricIntegral(field1, curve) == 1 + line = ParametricRegion((4*t - 1, 2 - 2*t, t), (t, 0, 1)) + assert ParametricIntegral(C.x*C.z*C.i - C.y*C.z*C.k, line) == 3 + + assert ParametricIntegral(4*C.x**3, ParametricRegion((1, t), (t, 0, 2))) == 8 + + helix = ParametricRegion((cos(t), sin(t), 3*t), (t, 0, 4*pi)) + assert ParametricIntegral(C.x*C.y*C.z, helix) == -3*sqrt(10)*pi + + field2 = C.y*C.i + C.z*C.j + C.z*C.k + assert ParametricIntegral(field2, ParametricRegion((cos(t), sin(t), t**2), (t, 0, pi))) == -5*pi/2 + pi**4/2 + +def test_parametric_surfaceintegrals(): + + semisphere = ParametricRegion((2*sin(phi)*cos(theta), 2*sin(phi)*sin(theta), 2*cos(phi)),\ + (theta, 0, 2*pi), (phi, 0, pi/2)) + assert ParametricIntegral(C.z, semisphere) == 8*pi + + cylinder = ParametricRegion((sqrt(3)*cos(theta), sqrt(3)*sin(theta), z), (z, 0, 6), (theta, 0, 2*pi)) + assert ParametricIntegral(C.y, cylinder) == 0 + + cone = ParametricRegion((v*cos(u), v*sin(u), v), (u, 0, 2*pi), (v, 0, 1)) + assert ParametricIntegral(C.x*C.i + C.y*C.j + C.z**4*C.k, cone) == pi/3 + + triangle1 = ParametricRegion((x, y), (x, 0, 2), (y, 0, 10 - 5*x)) + triangle2 = ParametricRegion((x, y), (y, 0, 10 - 5*x), (x, 0, 2)) + assert ParametricIntegral(-15.6*C.y*C.k, triangle1) == ParametricIntegral(-15.6*C.y*C.k, triangle2) + assert ParametricIntegral(C.z, triangle1) == 10*C.z + +def test_parametric_volumeintegrals(): + + cube = ParametricRegion((x, y, z), (x, 0, 1), (y, 0, 1), (z, 0, 1)) + assert ParametricIntegral(1, cube) == 1 + + solidsphere1 = ParametricRegion((r*sin(phi)*cos(theta), r*sin(phi)*sin(theta), r*cos(phi)),\ + (r, 0, 2), (theta, 0, 2*pi), (phi, 0, pi)) + solidsphere2 = ParametricRegion((r*sin(phi)*cos(theta), r*sin(phi)*sin(theta), r*cos(phi)),\ + (r, 0, 2), (phi, 0, pi), (theta, 0, 2*pi)) + assert ParametricIntegral(C.x**2 + C.y**2, solidsphere1) == -256*pi/15 + assert ParametricIntegral(C.x**2 + C.y**2, solidsphere2) == 256*pi/15 + + region_under_plane1 = ParametricRegion((x, y, z), (x, 0, 3), (y, 0, -2*x/3 + 2),\ + (z, 0, 6 - 2*x - 3*y)) + region_under_plane2 = ParametricRegion((x, y, z), (x, 0, 3), (z, 0, 6 - 2*x - 3*y),\ + (y, 0, -2*x/3 + 2)) + + assert ParametricIntegral(C.x*C.i + C.j - 100*C.k, region_under_plane1) == \ + ParametricIntegral(C.x*C.i + C.j - 100*C.k, region_under_plane2) + assert ParametricIntegral(2*C.x, region_under_plane2) == -9 + +def test_vector_integrate(): + halfdisc = ParametricRegion((r*cos(theta), r* sin(theta)), (r, -2, 2), (theta, 0, pi)) + assert vector_integrate(C.x**2, halfdisc) == 4*pi + assert vector_integrate(C.x, ParametricRegion((t, t**2), (t, 2, 3))) == -17*sqrt(17)/12 + 37*sqrt(37)/12 + + assert vector_integrate(C.y**3*C.z, (C.x, 0, 3), (C.y, -1, 4)) == 765*C.z/4 + + s1 = Segment(Point(0, 0), Point(0, 1)) + assert vector_integrate(-15*C.y, s1) == S(-15)/2 + s2 = Segment(Point(4, 3, 9), Point(1, 1, 7)) + assert vector_integrate(C.y*C.i, s2) == -6 + + curve = Curve((sin(t), cos(t)), (t, 0, 2)) + assert vector_integrate(5*C.z, curve) == 10*C.z + + c1 = Circle(Point(2, 3), 6) + assert vector_integrate(C.x*C.y, c1) == 72*pi + c2 = Circle(Point(0, 0), Point(1, 1), Point(1, 0)) + assert vector_integrate(1, c2) == c2.circumference + + triangle = Polygon((0, 0), (1, 0), (1, 1)) + assert vector_integrate(C.x*C.i - 14*C.y*C.j, triangle) == 0 + p1, p2, p3, p4 = [(0, 0), (1, 0), (5, 1), (0, 1)] + poly = Polygon(p1, p2, p3, p4) + assert vector_integrate(-23*C.z, poly) == -161*C.z - 23*sqrt(17)*C.z + + point = Point(2, 3) + assert vector_integrate(C.i*C.y - C.z, point) == ParametricIntegral(C.y*C.i, ParametricRegion((2, 3))) + + c3 = ImplicitRegion((x, y), x**2 + y**2 - 4) + assert vector_integrate(45, c3) == 180*pi + c4 = ImplicitRegion((x, y), (x - 3)**2 + (y - 4)**2 - 9) + assert vector_integrate(1, c4) == 6*pi + + pl = Plane(Point(1, 1, 1), Point(2, 3, 4), Point(2, 2, 2)) + raises(ValueError, lambda: vector_integrate(C.x*C.z*C.i + C.k, pl))