def _dict_from_expr(expr, opt): """Transform an expression into a multinomial form. """ if expr.is_commutative is False: raise PolynomialError('non-commutative expressions are not supported') def _is_expandable_pow(expr): return (expr.is_Pow and expr.exp.is_positive and expr.exp.is_Integer and expr.base.is_Add) if opt.expand is not False: if not isinstance(expr, (Expr, Eq)): raise PolynomialError('expression must be of type Expr') expr = expr.expand() # TODO: Integrate this into expand() itself while any( _is_expandable_pow(i) or i.is_Mul and any( _is_expandable_pow(j) for j in i.args) for i in Add.make_args(expr)): expr = expand_multinomial(expr) while any(i.is_Mul and any(j.is_Add for j in i.args) for i in Add.make_args(expr)): expr = expand_mul(expr) if opt.gens: rep, gens = _dict_from_expr_if_gens(expr, opt) else: rep, gens = _dict_from_expr_no_gens(expr, opt) return rep, opt.clone({'gens': gens})
def __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("can't 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) return coeff * cls._postprocess_root(root, radicals)
def __new__(cls, f, x, index=None, radicals=True, expand=True): """Construct a new ``RootOf`` object for ``k``-th root of ``f``. """ x = sympify(x) if index is None and x.is_Integer: x, index = None, x else: index = sympify(index) if index.is_Integer: index = int(index) else: raise ValueError("expected an integer root index, got %d" % index) poly = PurePoly(f, x, greedy=False, expand=expand) if not poly.is_univariate: raise PolynomialError("only univariate polynomials are allowed") degree = poly.degree() if degree <= 0: raise PolynomialError("can't construct RootOf 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("RootOf is not supported over %s" % dom) root = cls._indexed_root(poly, index) return coeff * cls._postprocess_root(root, radicals)
def _dict_from_basic_if_gens(ex, gens, **args): """Convert `ex` to a multinomial given a generators list. """ k, indices = len(gens), {} for i, g in enumerate(gens): indices[g] = i result = {} for term in ex.as_Add(): coeff, monom = [], [0] * k for factor in term.as_Mul(): if factor.is_Number: coeff.append(factor) else: try: base, exp = _analyze_power(*factor.as_Pow()) monom[indices[base]] = exp except KeyError: if not factor.has(*gens): coeff.append(factor) else: raise PolynomialError( "%s contains an element of the generators set" % factor) monom = tuple(monom) if result.has_key(monom): result[monom] += Mul(*coeff) else: result[monom] = Mul(*coeff) return result
def _analyze_power(base, exp): """Extract non-integer part of `exp` to the `base`. """ if exp.is_Number: if exp.is_Rational: if not exp.is_Integer: base = Pow(base, Rational(1, exp.q)) exp = exp.p else: base, exp = Pow(base, exp), 1 else: exp, tail = exp.as_coeff_terms() if exp.is_Number: if exp.is_Rational: if not exp.is_Integer: tail += (Rational(1, exp.q), ) exp = exp.p else: tail, exp = (exp, ) + tail, 1 else: # pragma: no cover raise PolynomialError("got invalid polynomial term") base = Pow(base, Mul(*tail)) if exp < 0: exp, base = -exp, Pow(base, -S.One) return base, exp
def all_coeffs(f): """Returns all coefficients from `f`. """ if not f.lev: if not f: return [f.dom.zero] else: return [ c for c in f.rep ] else: raise PolynomialError('multivariate polynomials not supported')
def all_terms(f): """Returns all terms from a `f`. """ if not f.lev: n = dup_degree(f.rep) if n < 0: return [((0,), f.dom.zero)] else: return [ ((n-i,), c) for i, c in enumerate(f.rep) ] else: raise PolynomialError('multivariate polynomials not supported')
def all_monoms(f): """Returns all monomials from `f`. """ if not f.lev: n = dup_degree(f.rep) if n < 0: return [(0,)] else: return [ (n-i,) for i, c in enumerate(f.rep) ] else: raise PolynomialError('multivariate polynomials not supported')
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") coeff, poly = cls._preprocess_roots(poly) roots = [] for root in getattr(cls, method)(poly): roots.append(coeff * cls._postprocess_root(root, radicals)) return roots
def refine_root(f, s, t, eps=None, steps=None, fast=False): """Refine an isolating interval to the given precision. """ if not f.lev: return dup_refine_real_root(f.rep, s, t, f.dom, eps=eps, steps=steps, fast=fast) else: raise PolynomialError( "can't refine a root of a multivariate polynomial")
def _dict_from_expr(expr, opt): """Transform an expression into a multinomial form. """ if opt.expand is not False: expr = expr.expand() if expr.is_commutative is False: raise PolynomialError('non-commutative expressions are not supported') if opt.gens: rep, gens = _dict_from_expr_if_gens(expr, opt) else: rep, gens = _dict_from_expr_no_gens(expr, opt) return rep, opt.clone({'gens': gens})
def _parallel_dict_from_expr(exprs, opt): """Transform expressions into a multinomial form. """ if opt.expand is not False: exprs = [expr.expand() for expr in exprs] if any(expr.is_commutative is False for expr in exprs): raise PolynomialError('non-commutative expressions are not supported') if opt.gens: reps, gens = _parallel_dict_from_expr_if_gens(exprs, opt) else: reps, gens = _parallel_dict_from_expr_no_gens(exprs, opt) return reps, opt.clone({'gens': gens})
def _parallel_dict_from_expr_if_gens(exprs, opt): """Transform expressions into a multinomial form given generators. """ k, indices = len(opt.gens), {} for i, g in enumerate(opt.gens): indices[g] = i polys = [] for expr in exprs: poly = {} if expr.is_Equality: expr = expr.lhs - expr.rhs for term in Add.make_args(expr): coeff, monom = [], [0] * k for factor in Mul.make_args(term): if not _not_a_coeff(factor) and factor.is_Number: coeff.append(factor) else: try: if opt.series is False: base, exp = decompose_power(factor) if exp < 0: exp, base = -exp, Pow(base, -S.One) else: base, exp = decompose_power_rat(factor) monom[indices[base]] = exp except KeyError: if not factor.free_symbols.intersection(opt.gens): coeff.append(factor) else: raise PolynomialError("%s contains an element of " "the set of generators." % factor) monom = tuple(monom) if monom in poly: poly[monom] += Mul(*coeff) else: poly[monom] = Mul(*coeff) polys.append(poly) return polys, opt.gens
def intervals(f, all=False, eps=None, inf=None, sup=None, fast=False, sqf=False): """Compute isolating intervals for roots of `f`. """ if not f.lev: if not all: if not sqf: return dup_isolate_real_roots(f.rep, f.dom, eps=eps, inf=inf, sup=sup, fast=fast) else: return dup_isolate_real_roots_sqf(f.rep, f.dom, eps=eps, inf=inf, sup=sup, fast=fast) else: if not sqf: return dup_isolate_all_roots(f.rep, f.dom, eps=eps, inf=inf, sup=sup, fast=fast) else: return dup_isolate_all_roots_sqf(f.rep, f.dom, eps=eps, inf=inf, sup=sup, fast=fast) else: raise PolynomialError("can't isolate roots of a multivariate polynomial")
def _parallel_dict_from_expr_if_gens(exprs, opt): """Transform expressions into a multinomial form given generators. """ k, indices = len(opt.gens), {} for i, g in enumerate(opt.gens): indices[g] = i polys = [] for expr in exprs: poly = {} for term in Add.make_args(expr): coeff, monom = [], [0] * k for factor in Mul.make_args(term): if factor.is_Number: coeff.append(factor) else: try: base, exp = decompose_power(factor) if exp < 0: exp, base = -exp, Pow(base, -S.One) monom[indices[base]] = exp except KeyError: if not factor.has(*opt.gens): coeff.append(factor) else: raise PolynomialError( "%s contains an element of the generators set" % factor) monom = tuple(monom) if monom in poly: poly[monom] += Mul(*coeff) else: poly[monom] = Mul(*coeff) polys.append(poly) return polys, opt.gens
def _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
def roots(f, *gens, **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. 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 tuple containing all those roots set the ``multiple`` flag to True. Examples ======== >>> from sympy import Poly, roots >>> 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} """ from sympy.polys.polytools import to_rational_coeffs flags = dict(flags) auto = flags.pop('auto', True) cubics = flags.pop('cubics', True) quartics = flags.pop('quartics', True) quintics = flags.pop('quintics', False) multiple = flags.pop('multiple', False) filter = flags.pop('filter', None) predicate = flags.pop('predicate', None) 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) except GeneratorsNeeded: if multiple: return [] else: return {} if f.is_multivariate: raise PolynomialError('multivariate polynomials are not supported') def _update_dict(result, root, k): if root in result: result[root] += k else: result[root] = k def _try_decompose(f): """Find roots using functional decomposition. """ factors, roots = f.decompose(), [] for root in _try_heuristics(factors[0]): roots.append(root) for factor in factors[1:]: previous, roots = list(roots), [] for root in previous: g = factor - Poly(root, f.gen) for root in _try_heuristics(g): roots.append(root) return roots def _try_heuristics(f): """Find roots using formulas and some tricks. """ if f.is_ground: return [] if f.is_monomial: return [S(0)] * 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) elif n == 4 and quartics: result += roots_quartic(f) elif n == 5 and quintics: result += roots_quintic(f) return result (k, ), f = f.terms_gcd() if not k: zeros = {} else: zeros = {S(0): k} coeff, f = preprocess_roots(f) if auto and f.get_domain().has_Ring: f = f.to_field() rescale_x = None translate_x = None result = {} if not f.is_ground: if not f.get_domain().is_Exact: for r in f.nroots(): _update_dict(result, r, 1) elif f.degree() == 1: result[roots_linear(f)[0]] = 1 elif f.degree() == 2: for r in roots_quadratic(f): _update_dict(result, r, 1) elif f.length() == 2: for r in roots_binomial(f): _update_dict(result, r, 1) else: _, factors = Poly(f.as_expr()).factor_list() 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 root in _try_decompose(f): _update_dict(result, root, 1) else: for root in _try_decompose(f): _update_dict(result, root, 1) else: for factor, k in factors: for r in _try_heuristics(Poly(factor, f.gen, field=True)): _update_dict(result, r, k) if coeff is not S.One: _result, result, = result, {} for root, k in _result.items(): result[coeff * root] = k result.update(zeros) if filter not in [None, 'C']: handlers = { 'Z': lambda r: r.is_Integer, 'Q': lambda r: r.is_Rational, 'R': lambda r: r.is_real, '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 if not multiple: return result else: zeros = [] for zero, k in result.items(): zeros.extend([zero] * k) return sorted(zeros, key=default_sort_key)
def test_pickling_polys_errors(): from sympy.polys.polyerrors import ( ExactQuotientFailed, OperationNotSupported, HeuristicGCDFailed, HomomorphismFailed, IsomorphismFailed, ExtraneousFactors, EvaluationFailed, RefinementFailed, CoercionFailed, NotInvertible, NotReversible, NotAlgebraic, DomainError, PolynomialError, UnificationFailed, GeneratorsError, GeneratorsNeeded, ComputationFailed, UnivariatePolynomialError, MultivariatePolynomialError, PolificationFailed, OptionError, FlagError) x = Symbol('x') # TODO: TypeError: __init__() takes at least 3 arguments (1 given) # for c in (ExactQuotientFailed, ExactQuotientFailed(x, 3*x, ZZ)): # check(c) # TODO: TypeError: can't pickle instancemethod objects # for c in (OperationNotSupported, OperationNotSupported(Poly(x), Poly.gcd)): # check(c) for c in (HeuristicGCDFailed, HeuristicGCDFailed()): check(c) for c in (HomomorphismFailed, HomomorphismFailed()): check(c) for c in (IsomorphismFailed, IsomorphismFailed()): check(c) for c in (ExtraneousFactors, ExtraneousFactors()): check(c) for c in (EvaluationFailed, EvaluationFailed()): check(c) for c in (RefinementFailed, RefinementFailed()): check(c) for c in (CoercionFailed, CoercionFailed()): check(c) for c in (NotInvertible, NotInvertible()): check(c) for c in (NotReversible, NotReversible()): check(c) for c in (NotAlgebraic, NotAlgebraic()): check(c) for c in (DomainError, DomainError()): check(c) for c in (PolynomialError, PolynomialError()): check(c) for c in (UnificationFailed, UnificationFailed()): check(c) for c in (GeneratorsError, GeneratorsError()): check(c) for c in (GeneratorsNeeded, GeneratorsNeeded()): check(c) # TODO: PicklingError: Can't pickle <function <lambda> at 0x38578c0>: it's not found as __main__.<lambda> # for c in (ComputationFailed, ComputationFailed(lambda t: t, 3, None)): # check(c) for c in (UnivariatePolynomialError, UnivariatePolynomialError()): check(c) for c in (MultivariatePolynomialError, MultivariatePolynomialError()): check(c) # TODO: TypeError: __init__() takes at least 3 arguments (1 given) # for c in (PolificationFailed, PolificationFailed({}, x, x, False)): # check(c) for c in (OptionError, OptionError()): check(c) for c in (FlagError, FlagError()): check(c)
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 don't 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 there is a general solution, simpler results can be obtained for certain values of the coefficients. In all cases, 4 roots are 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, symbols, I >>> 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. http://en.wikipedia.org/wiki/Quartic_function#Summary_of_Ferrari.27s_method 3. http://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html 4. http://staff.bath.ac.uk/masjhd/JHD-CA.pdf 5. http://www.albmath.org/files/Math_5713.pdf 6. http://www.statemaster.com/encyclopedia/Quartic-equation 7. 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 = c + a*(a2/8 - b/2) g = d - a*(a*(3*a2/256 - b/16) + c/4) aon4 = a/4 if f is S.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 S.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] a2 = a**2 e = b - 3*a2/8 f = c + a*(a2/8 - b/2) g = d - a*(a*(3*a2/256 - b/16) + c/4) p = -e**2/12 - g q = -e**3/108 + e*g/3 - f**2/8 TH = Rational(1, 3) if p.is_zero: y = -5*e/6 - q**TH elif p.is_number and p.is_comparable: # with 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) y = -5*e/6 + u - p/u/3 else: raise PolynomialError('cannot return general quartic solution') 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
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_function#Trigonometric_.28and_hyperbolic.29_method """ 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 apart(f, x=None, full=False, **options): """ Compute partial fraction decomposition of a rational function. Given a rational function ``f`` compute partial fraction decomposition of ``f``. Two algorithms are available: one is based on undetermined coefficients method and the other is Bronstein's full partial fraction decomposition algorithm. Examples ======== >>> from sympy.polys.partfrac import apart >>> from sympy.abc import x, y By default, using the undetermined coefficients method: >>> apart(y/(x + 2)/(x + 1), x) -y/(x + 2) + y/(x + 1) You can choose Bronstein's algorithm by setting ``full=True``: >>> apart(y/(x**2 + x + 1), x) y/(x**2 + x + 1) >>> apart(y/(x**2 + x + 1), x, full=True) RootSum(_w**2 + _w + 1, Lambda(_a, (-2*_a*y/3 - y/3)/(-_a + x))) See Also ======== apart_list, assemble_partfrac_list """ allowed_flags(options, []) f = sympify(f) if f.is_Atom: return f else: P, Q = f.as_numer_denom() _options = options.copy() options = set_defaults(options, extension=True) try: (P, Q), opt = parallel_poly_from_expr((P, Q), x, **options) except PolynomialError as msg: if f.is_commutative: raise PolynomialError(msg) # non-commutative if f.is_Mul: c, nc = f.args_cnc(split_1=False) nc = f.func(*[apart(i, x=x, full=full, **_options) for i in nc]) if c: c = apart(f.func._from_args(c), x=x, full=full, **_options) return c*nc else: return nc elif f.is_Add: c = [] nc = [] for i in f.args: if i.is_commutative: c.append(i) else: try: nc.append(apart(i, x=x, full=full, **_options)) except NotImplementedError: nc.append(i) return apart(f.func(*c), x=x, full=full, **_options) + f.func(*nc) else: reps = [] pot = preorder_traversal(f) next(pot) for e in pot: try: reps.append((e, apart(e, x=x, full=full, **_options))) pot.skip() # this was handled successfully except NotImplementedError: pass return f.xreplace(dict(reps)) if P.is_multivariate: fc = f.cancel() if fc != f: return apart(fc, x=x, full=full, **_options) raise NotImplementedError( "multivariate partial fraction decomposition") common, P, Q = P.cancel(Q) poly, P = P.div(Q, auto=True) P, Q = P.rat_clear_denoms(Q) if Q.degree() <= 1: partial = P/Q else: if not full: partial = apart_undetermined_coeffs(P, Q) else: partial = apart_full_decomposition(P, Q) terms = S.Zero for term in Add.make_args(partial): if term.has(RootSum): terms += term else: terms += factor(term) return common*(poly.as_expr() + terms)
def roots(f, *gens, **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.) Examples ======== >>> from sympy import Poly, roots >>> 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} References ========== .. [1] https://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method """ from sympy.polys.polytools import to_rational_coeffs flags = dict(flags) auto = flags.pop('auto', True) cubics = flags.pop('cubics', True) trig = flags.pop('trig', False) quartics = flags.pop('quartics', True) quintics = flags.pop('quintics', False) multiple = flags.pop('multiple', False) filter = flags.pop('filter', None) predicate = flags.pop('predicate', None) 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 f.length == 2 and f.degree() != 1: # check for foo**n factors in the constant n = f.degree() npow_bases = [] others = [] expr = f.as_expr() con = expr.as_independent(*gens)[0] for p in Mul.make_args(con): if p.is_Pow and not p.exp % n: npow_bases.append(p.base**(p.exp/n)) else: others.append(p) if npow_bases: b = Mul(*npow_bases) B = Dummy() d = roots(Poly(expr - con + B**n*Mul(*others), *gens, **flags), *gens, **flags) rv = {} for k, v in d.items(): rv[k.subs(B, b)] = v return rv except GeneratorsNeeded: if multiple: return [] else: return {} if f.is_multivariate: raise PolynomialError('multivariate polynomials are not supported') def _update_dict(result, currentroot, 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(0)]*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 (k,), f = f.terms_gcd() if not k: zeros = {} else: zeros = {S(0): k} coeff, f = preprocess_roots(f) if auto and f.get_domain().is_Ring: f = f.to_field() 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, r, 1) elif f.degree() == 1: result[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, 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, 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, currentroot, 1) else: for r in _try_heuristics(f): _update_dict(result, r, 1) else: for currentroot in _try_decompose(f): _update_dict(result, currentroot, 1) else: for currentfactor, k in factors: for r in _try_heuristics(Poly(currentfactor, f.gen, field=True)): _update_dict(result, r, k) if coeff is not S.One: _result, result, = result, {} for currentroot, k in _result.items(): result[coeff*currentroot] = k result.update(zeros) if filter not in [None, 'C']: handlers = { 'Z': lambda r: r.is_Integer, 'Q': lambda r: r.is_Rational, 'R': lambda r: r.is_real, '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 if not multiple: return result else: zeros = [] for zero in ordered(result): zeros.extend([zero]*result[zero]) return zeros
def apart(f, x=None, full=False, **options): """ Compute partial fraction decomposition of a rational function. Given a rational function ``f`` compute partial fraction decomposition of ``f``. Two algorithms are available: one is based on undetermined coefficients method and the other is Bronstein's full partial fraction decomposition algorithm. Examples ======== >>> from sympy.polys.partfrac import apart >>> from sympy.abc import x, y By default, using the undetermined coefficients method: >>> apart(y/(x + 2)/(x + 1), x) -y/(x + 2) + y/(x + 1) You can choose Bronstein's algorithm by setting ``full=True``: >>> apart(y/(x**2 + x + 1), x) y/(x**2 + x + 1) >>> apart(y/(x**2 + x + 1), x, full=True) RootSum(_w**2 + _w + 1, Lambda(_a, (-2*_a*y/3 - y/3)/(-_a + x))) See Also ======== apart_list, assemble_partfrac_list """ allowed_flags(options, []) f = sympify(f) if f.is_Atom: return f else: P, Q = f.as_numer_denom() _options = options.copy() options = set_defaults(options, extension=True) try: (P, Q), opt = parallel_poly_from_expr((P, Q), x, **options) except PolynomialError, msg: if f.is_commutative: raise PolynomialError(msg) # non-commutative if f.is_Mul: c, nc = f.args_cnc(split_1=False) nc = Mul(*[apart(i, x=x, full=full, **_options) for i in nc]) if c: c = apart(Mul._from_args(c), x=x, full=full, **_options) return c*nc else: return nc elif f.is_Add: c = [] nc = [] for i in f.args: if i.is_commutative: c.append(i) else: try: nc.append(apart(i, x=x, full=full, **_options)) except NotImplementedError: nc.append(i) return apart(Add(*c), x=x, full=full, **_options) + Add(*nc) else: reps = [] pot = preorder_traversal(f) pot.next() for e in pot: try: reps.append((e, apart(e, x=x, full=full, **_options))) pot.skip() # this was handled successfully except NotImplementedError: pass return f.xreplace(dict(reps))
def apart(f, x=None, full=False, **options): """ Compute partial fraction decomposition of a rational function. Given a rational function ``f``, computes the partial fraction decomposition of ``f``. Two algorithms are available: One is based on the undertermined coefficients method, the other is Bronstein's full partial fraction decomposition algorithm. The undetermined coefficients method (selected by ``full=False``) uses polynomial factorization (and therefore accepts the same options as factor) for the denominator. Per default it works over the rational numbers, therefore decomposition of denominators with non-rational roots (e.g. irrational, complex roots) is not supported by default (see options of factor). Bronstein's algorithm can be selected by using ``full=True`` and allows a decomposition of denominators with non-rational roots. A human-readable result can be obtained via ``doit()`` (see examples below). Examples ======== >>> from sympy.polys.partfrac import apart >>> from sympy.abc import x, y By default, using the undetermined coefficients method: >>> apart(y/(x + 2)/(x + 1), x) -y/(x + 2) + y/(x + 1) The undetermined coefficients method does not provide a result when the denominators roots are not rational: >>> apart(y/(x**2 + x + 1), x) y/(x**2 + x + 1) You can choose Bronstein's algorithm by setting ``full=True``: >>> apart(y/(x**2 + x + 1), x, full=True) RootSum(_w**2 + _w + 1, Lambda(_a, (-2*_a*y/3 - y/3)/(-_a + x))) Calling ``doit()`` yields a human-readable result: >>> apart(y/(x**2 + x + 1), x, full=True).doit() (-y/3 - 2*y*(-1/2 - sqrt(3)*I/2)/3)/(x + 1/2 + sqrt(3)*I/2) + (-y/3 - 2*y*(-1/2 + sqrt(3)*I/2)/3)/(x + 1/2 - sqrt(3)*I/2) See Also ======== apart_list, assemble_partfrac_list """ allowed_flags(options, []) f = sympify(f) if f.is_Atom: return f else: P, Q = f.as_numer_denom() _options = options.copy() options = set_defaults(options, extension=True) try: (P, Q), opt = parallel_poly_from_expr((P, Q), x, **options) except PolynomialError as msg: if f.is_commutative: raise PolynomialError(msg) # non-commutative if f.is_Mul: c, nc = f.args_cnc(split_1=False) nc = f.func(*nc) if c: c = apart(f.func._from_args(c), x=x, full=full, **_options) return c*nc else: return nc elif f.is_Add: c = [] nc = [] for i in f.args: if i.is_commutative: c.append(i) else: try: nc.append(apart(i, x=x, full=full, **_options)) except NotImplementedError: nc.append(i) return apart(f.func(*c), x=x, full=full, **_options) + f.func(*nc) else: reps = [] pot = preorder_traversal(f) next(pot) for e in pot: try: reps.append((e, apart(e, x=x, full=full, **_options))) pot.skip() # this was handled successfully except NotImplementedError: pass return f.xreplace(dict(reps)) if P.is_multivariate: fc = f.cancel() if fc != f: return apart(fc, x=x, full=full, **_options) raise NotImplementedError( "multivariate partial fraction decomposition") common, P, Q = P.cancel(Q) poly, P = P.div(Q, auto=True) P, Q = P.rat_clear_denoms(Q) if Q.degree() <= 1: partial = P/Q else: if not full: partial = apart_undetermined_coeffs(P, Q) else: partial = apart_full_decomposition(P, Q) terms = S.Zero for term in Add.make_args(partial): if term.has(RootSum): terms += term else: terms += factor(term) return common*(poly.as_expr() + terms)
def __new__(cls, f, x=None, indices=None, radicals=True, expand=True): """Construct a new ``RootOf`` object for ``k``-th root of ``f``. """ if indices is None and (not isinstance(x, Basic) or x.is_Integer): x, indices = None, x poly = Poly(f, x, greedy=False, expand=expand) if not poly.is_univariate: raise PolynomialError("only univariate polynomials are allowed") degree = poly.degree() if degree <= 0: raise PolynomialError("can't construct RootOf object for %s" % f) if indices is not None and indices is not True: if hasattr(indices, '__iter__'): indices, iterable = list(indices), True else: indices, iterable = [indices], False indices = map(int, indices) for i, index in enumerate(indices): if index < -degree or index >= degree: raise IndexError( "root index out of [%d, %d] range, got %d" % (-degree, degree - 1, index)) elif index < 0: indices[i] += degree else: iterable = True if indices is True: indices = range(degree) dom = poly.get_domain() if not dom.is_Exact: poly = poly.to_exact() roots = roots_trivial(poly, radicals) if roots is not None: if indices is not None: result = [roots[index] for index in indices] else: result = [root for root in roots if root.is_real] else: coeff, poly = _rootof_preprocess(poly) dom = poly.get_domain() if not dom.is_ZZ: raise NotImplementedError("RootOf is not supported over %s" % dom) result = [] for data in _rootof_data(poly, indices): poly, index, pointer, conjugate = data roots = roots_trivial(poly, radicals) if roots is not None: result.append(coeff * roots[index]) else: result.append(coeff * cls._new(poly, index, pointer, conjugate)) if not iterable: return result[0] else: return result