Repository: sympy/sympy
File: sympy/polys/polytools.py (Python)

Code before:

        # 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()]
            with local_workdps(n) as ctx:
                try:
                    coeffs = [ctx.mpc(*coeff) for coeff in coeffs]
                except TypeError:
                    raise DomainError("Numerical domain expected, got %s" % f.rep.dom)

        from sympy.functions.elementary.complexes import sign
        opts = {'maxsteps': maxsteps, 'cleanup': cleanup, 'error': False}
        for prec in [f.degree()*10, f.degree()*15]:
            try:
                with local_workdps(n) as ctx:
                    roots = ctx.polyroots(coeffs, **opts, extraprec=prec)
                    # Mpmath puts real roots first, then complex ones (as does
                    # all_roots) so we make sure this convention holds here,
                    # too.
                    key = lambda r: (1 if r.imag else 0, r.real, abs(r.imag), sign(r.imag))
                    roots = [sympify(r) for r in sorted(roots, key=key)]
                    break
            except NoConvergence:
                continue
        else:
            msg = 'convergence to root failed; try n < %s or maxsteps > %s'
            raise NoConvergence(msg % (n, maxsteps))


        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}

Review comment (performance):
I don't know if this will be run in a tight loop, but perhaps it should be taken care of at import time instead of at run time. Unless we don't want to import mpmath at import time.

Code after:

        # 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()]
            with local_workdps(n) as ctx:
                try:
                    coeffs = [ctx.mpc(*coeff) for coeff in coeffs]
                except TypeError:
                    raise DomainError("Numerical domain expected, got %s" % f.rep.dom)

        from sympy.functions.elementary.complexes import sign
        opts = {'maxsteps': maxsteps, 'cleanup': cleanup, 'error': False}

        # mpmath 1.4 deprecates calling polyroots without the 'asc' argument
        # and apparently prefers asc=True which reverses the order compared to
        # default behaviour for mpmath < 1.4.
        from mpmath import __version__ as mpver
        if not any(mpver.startswith(prefix) for prefix in ('0.', '1.0.', '1.1.', '1.2.', '1.3.')):
            # This should be the code when mpmath 1.4.0 is the minimum version:
            opts['asc'] = True
            coeffs = coeffs[::-1]

        for prec in [f.degree()*10, f.degree()*15]:
            try:
                with local_workdps(n) as ctx:
                    roots = ctx.polyroots(coeffs, **opts, extraprec=prec)
                    # Mpmath puts real roots first, then complex ones (as does
                    # all_roots) so we make sure this convention holds here,
                    # too.
                    key = lambda r: (1 if r.imag else 0, r.real, abs(r.imag), sign(r.imag))
                    roots = [sympify(r) for r in sorted(roots, key=key)]
                    break
            except NoConvergence:
                continue
        else:
            msg = 'convergence to root failed; try n < %s or maxsteps > %s'
            raise NoConvergence(msg % (n, maxsteps))


        return roots

    def ground_roots(f):
        """