def poly_sturm(f, *symbols): """Computes the Sturm sequence of a given polynomial. Given an univariate, square-free polynomial f(x) returns an associated Sturm sequence f_0(x), ..., f_n(x) defined by: f_0(x), f_1(x) = f(x), f'(x) f_n = -rem(f_{n-2}(x), f_{n-1}(x)) For more information on the implemented algorithm refer to: [1] J.H. Davenport, Y. Siret, E. Tournier, Computer Algebra Systems and Algorithms for Algebraic Computation, Academic Press, London, 1988, pp. 124-128 """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") if f.is_multivariate: raise MultivariatePolyError(f) else: f = f.as_squarefree() sturm = [f, f.diff()] while not sturm[-1].is_zero: sturm.append(-poly_div(sturm[-2], sturm[-1])[1]) return sturm[:-1]
def test_polys(): x = Symbol("x") f = Poly(x, x) g = lambda x: x for c in (IntegerPoly, IntegerPoly(x, x), Poly, Poly(x, x)): check(c) for c in (RootOf, RootOf(f, 0), RootsOf, RootsOf(x, x), RootSum, RootSum(g, f)): check(c)
def __new__(cls, f, x=None): if not isinstance(f, Poly): f = Poly(f, x) elif x is not None: raise SymbolsError("Redundant symbols were given") if f.is_multivariate: raise MultivariatePolyError(f) return Basic.__new__(cls, f)
def mv_int_div(f, g): q = Poly((), *symbols) r = Poly((), *symbols) while not f.is_zero: lc_f, lc_g = f.LC, g.LC dv = lc_f % lc_g cf = lc_f / lc_g monom = monomial_div(f.LM, g.LM) if dv == 0 and monom is not None: q = q.add_term(cf, monom) f -= g.mul_term(cf, monom) else: r = r.add_term(*f.LT) f = f.kill_lead_term() return q, r
def poly_factors(f, *symbols, **flags): """Factor polynomials over rationals. >>> from sympy.polys.factortools import poly_factors >>> from sympy.abc import x, y >>> poly_factors(x**2 - y**2, x, y) (1, [(Poly(x - y, x, y), 1), (Poly(x + y, x, y), 1)]) """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") symbols = list(f.symbols) try: denom, F = f.as_integer() except CoefficientError: other = set([]) for coeff in f.iter_coeffs(): other |= coeff.atoms(Symbol) symbols += sorted(other) F = Poly(f, *symbols) denom, F = F.as_integer() cont, factors = zzX_factor(zzX_from_poly(F)) for i, (h, k) in enumerate(factors): h = zzX_to_poly(h, *symbols) if f.symbols != symbols: h = h.as_poly(*f.symbols) factors[i] = (h, k) return Rational(cont, denom), factors
def poly_root_factors(f, *symbols, **flags): """Returns all factors of an univariate polynomial. >>> from sympy import * >>> x,y = symbols('xy') >>> factors = poly_root_factors(x**2-y, x) >>> set(f.as_basic() for f in factors) set([x + y**(1/2), x - y**(1/2)]) """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") if f.is_multivariate: raise MultivariatePolyError(f) else: x = f.symbols[0] if flags.has_key('multiple'): del flags['multiple'] zeros = roots(f, **flags) if not zeros: return [f] else: factors, N = [], 0 for r, n in zeros.iteritems(): h = Poly([(S.One, 1), (-r, 0)], x) factors, N = factors + [h] * n, N + n if N < f.degree: g = reduce(lambda p, q: p * q, factors) factors.append(poly_div(f, g)[0]) return factors
def poly_factors(f, *symbols, **flags): """Factor polynomials over rationals. >>> from sympy import * >>> x, y = symbols("x y") >>> poly_factors(x**2 - y**2, x, y) (1, [(Poly(x - y, x, y), 1), (Poly(x + y, x, y), 1)]) """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") denom, f = f.as_integer() if f.is_univariate: coeffs = map(int, f.iter_all_coeffs()) content, factors = zzx_factor(coeffs) for i in xrange(len(factors)): factor, k = factors[i] n = zzx_degree(factor) terms = {} for j, coeff in enumerate(factor): if coeff != 0: terms[(n - j, )] = Integer(coeff) factors[i] = Poly(terms, *f.symbols), k else: content, factors = kronecker_mv(f, **flags) return Rational(content, denom), factors
def poly_factors(f, *symbols, **flags): """Factor polynomials over rationals. >>> from sympy import * >>> x, y = symbols("x y") >>> poly_factors(x**2 - y**2, x, y) (1, [(Poly(x - y, x, y), 1), (Poly(x + y, x, y), 1)]) """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") denom, f = f.as_integer() if f.is_univariate: coeffs = map(int, f.iter_all_coeffs()) content, factors = zzx_factor(coeffs) for i in xrange(len(factors)): factor, k = factors[i] n = zzx_degree(factor) terms = {} for j, coeff in enumerate(factor): if coeff != 0: terms[(n-j,)] = Integer(coeff) factors[i] = Poly(terms, *f.symbols), k else: content, factors = kronecker_mv(f, **flags) return Rational(content, denom), factors
def poly_factors(f, *symbols, **flags): """Factor polynomials over rationals. >>> from sympy import * >>> x, y = symbols("x y") >>> poly_factors(x**2 - y**2, x, y) (1, [(Poly(x - y, x, y), 1), (Poly(x + y, x, y), 1)]) """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") symbols = list(f.symbols) try: denom, F = f.as_integer() except CoefficientError: other = set([]) for coeff in f.iter_coeffs(): other |= coeff.atoms(Symbol) symbols += sorted(other) F = Poly(f, *symbols) denom, F = F.as_integer() cont, factors = zzX_factor(zzX_from_poly(F)) for i, (h, k) in enumerate(factors): h = zzX_to_poly(h, *symbols) if f.symbols != symbols: h = h.as_poly(*f.symbols) factors[i] = (h, k) return Rational(cont, denom), factors
def roots(f, *symbols, **flags): """Computes symbolic roots of an univariate polynomial. Given an univariate polynomial f with symbolic 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 aren't used in the algorithm because of unreadable output. To enable them set cubics=True or quartics=True respectively. To get roots from a specific domain set the 'domain' flag with one of the following specifiers: Z, Q, R, I, C. By default all roots are returned (this is equivalent to setting domain='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. >>> from sympy import * >>> x,y = symbols('xy') >>> roots(x**2 - 1, x) {1: 1, -1: 1} >>> roots(x**2 - y, x) {y**(1/2): 1, -y**(1/2): 1} """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") if f.is_multivariate: raise MultivariatePolyError(f) def roots_trivial(g): if g.length == 1: if g.is_constant: return [] else: return [S.Zero] * g.degree (k, ), g = g.as_reduced() if k == 0: zeros = [] else: zeros = [S.Zero] * k if g.length == 2: zeros += roots_binomial(g) else: x = g.symbols[0] for i in [S.NegativeOne, S.One]: if g(i).expand() is S.Zero: g = poly_div(g, x - i)[0] zeros.append(i) break n = g.degree if n == 1: zeros += roots_linear(g) elif n == 2: zeros += roots_quadratic(g) elif n == 3: if flags.get('cubics', False): # TODO: now we can used factor() not only # for cubic polynomials. See #1158 try: _, factors = poly_factors(g) if len(factors) == 1: raise PolynomialError for factor, k in factors: zeros += roots(factor, multiple=True) * k except PolynomialError: zeros += roots_cubic(g) elif n == 4: if flags.get('quartics', False): zeros += roots_quartic(g) return zeros multiple = flags.get('multiple', False) if f.length == 1: if f.is_constant: if multiple: return [] else: return {} else: result = {S.Zero: f.degree} else: (k, ), f = f.as_reduced() if k == 0: result = {} else: result = {S.Zero: k} if f.degree == 1: result[roots_linear(f)[0]] = 1 else: factors = poly_decompose(f) zeros, g = {}, factors[0] for i, h in enumerate(poly_sqf(g)): for zero in roots_trivial(h): if zeros.has_key(zero): zeros[zero] += i + 1 else: zeros[zero] = i + 1 for factor in factors[1:]: previous, zeros = zeros.copy(), {} for zero, i in previous.iteritems(): g = factor.sub_term(zero, (0, )) for j, h in enumerate(poly_sqf(g)): for zero in roots_trivial(h): if zeros.has_key(zero): zeros[zero] += i * (j + 1) else: zeros[zero] = i * (j + 1) result.update(zeros) domain = flags.get('domain', None) if domain 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[domain] except KeyError: raise ValueError("Invalid domain: %s" % domain) for zero in dict(result).iterkeys(): if not query(zero): del result[zero] predicate = flags.get('predicate', None) if predicate is not None: for zero in dict(result).iterkeys(): if not predicate(zero): del result[zero] if not multiple: return result else: zeros = [] for zero, k in result.iteritems(): zeros.extend([zero] * k) return zeros
def kronecker_mv(f, **flags): """Kronecker method for Z[X] polynomials. NOTE: This function is very slow even on small input. Use debug=True flag to see its progress, if any. """ symbols = f.symbols def mv_int_div(f, g): q = Poly((), *symbols) r = Poly((), *symbols) while not f.is_zero: lc_f, lc_g = f.LC, g.LC dv = lc_f % lc_g cf = lc_f / lc_g monom = monomial_div(f.LM, g.LM) if dv == 0 and monom is not None: q = q.add_term(cf, monom) f -= g.mul_term(cf, monom) else: r = r.add_term(*f.LT) f = f.kill_lead_term() return q, r def combinations(lisp, m): def recursion(fa, lisp, m): if m == 0: yield fa else: for i, fa2 in enumerate(lisp[0 : len(lisp) + 1 - m]): for el in recursion(zzx_mul(fa2, fa), list(lisp[i + 1:]), m - 1): yield el for i, fa in enumerate(lisp[0 : len(lisp) + 1 - m]): for el in recursion(fa, list(lisp[i + 1:]), m - 1): yield el debug = flags.get('debug', False) cont, f = f.as_primitive() N = len(symbols) max_exp = {} for v in symbols: max_exp[v] = 0 for coeff, monom in f.iter_terms(): for v, exp in zip(symbols, monom): if exp > max_exp[v]: max_exp[v] = exp symbols = sorted(symbols, reverse=True, key=lambda v: max_exp[v]) f = Poly(f, *symbols) d = max_exp[symbols[0]] + 1 terms, exps = {}, [] for i in xrange(0, len(symbols)): exps.append(d**i) for coeff, monom in f.iter_terms(): exp = 0 for i, expi in enumerate(monom): exp += expi * exps[i] terms[exp] = int(coeff) g, factors = zzx_from_dict(terms), [] try: for ff, k in zzx_factor(g)[1]: for i in xrange(0, k): factors.append(ff) except OverflowError: raise PolynomialError("input too large for multivariate Kronecker method") const, result, tested = 1, [], [] if debug: print "KRONECKER-MV: Z[x] #factors = %i ..." % (len(factors)) for k in range(1, len(factors)//2 + 1): for h in combinations(factors, k): if h in tested: continue n = zzx_degree(h) terms = {} for coeff in h: if not coeff: n = n-1 continue else: coeff = Integer(coeff) y_deg, n = n, n-1 monom = [0] * N for i in xrange(N): v_deg = y_deg % d y_deg = (y_deg - v_deg) // d monom[i] = v_deg monom = tuple(monom) if terms.has_key(monom): terms[monom] += coeff else: terms[monom] = coeff cand = Poly(terms, *symbols) if cand.is_one: continue if cand.LC.is_negative: cand = -cand; q, r = mv_int_div(f, cand) if r.is_zero: if debug: print "KRONECKER-MV: Z[X] factor found %s" % cand result.append(cand) f = q else: tested.append(h) if f.is_constant: const, f = f.LC, Poly(1, *symbols) break if f.is_one: break if not f.is_one: if debug: print "KRONECKER-MV: Z[X] factor found %s" % f result.append(f) factors = {} for ff in result: if factors.has_key(ff): factors[ff] += 1 else: factors[ff] = 1 return cont*const, sorted(factors.items())
def roots(f, *symbols, **flags): """Computes symbolic roots of an univariate polynomial. Given an univariate polynomial f with symbolic 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 'domain' flag with one of the following specifiers: Z, Q, R, I, C. By default all roots are returned (this is equivalent to setting domain='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. >>> from sympy import * >>> x,y = symbols('xy') >>> roots(x**2 - 1, x) {1: 1, -1: 1} >>> roots(x**2 - y, x) {y**(1/2): 1, -y**(1/2): 1} """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") if f.is_multivariate: raise MultivariatePolyError(f) 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 = poly_decompose(f) result, g = {}, factors[0] for i, h in enumerate(poly_sqf(g)): for r in _try_heuristics(h): _update_dict(result, r, i + 1) for factor in factors[1:]: last, result = result.copy(), {} for last_r, i in last.iteritems(): g = factor.sub_term(last_r, (0, )) for j, h in enumerate(poly_sqf(g)): for r in _try_heuristics(h): _update_dict(result, r, i * (j + 1)) return result def _try_heuristics(f): """Find roots using formulas and some tricks. """ if f.length == 1: if f.is_constant: return [] else: return [S(0)] * f.degree if f.length == 2: if f.degree == 1: return roots_linear(f) else: return roots_binomial(f) x, result = f.symbols[0], [] for i in [S(-1), S(1)]: if f(i).expand().is_zero: f = poly_div(f, x - i)[0] result.append(i) break n = f.degree if n == 1: result += roots_linear(f) elif n == 2: result += roots_quadratic(f) elif n == 3 and flags.get('cubics', True): result += roots_cubic(f) elif n == 4 and flags.get('quartics', False): result += roots_quartic(f) return result multiple = flags.get('multiple', False) if f.length == 1: if f.is_constant: if multiple: return [] else: return {} else: result = {S(0): f.degree} else: (k, ), f = f.as_reduced() if k == 0: zeros = {} else: zeros = {S(0): k} result = {} if f.length == 2: if f.degree == 1: result[roots_linear(f)[0]] = 1 else: for r in roots_binomial(f): _update_dict(result, r, 1) elif f.degree == 2: for r in roots_quadratic(f): _update_dict(result, r, 1) else: try: _, factors = poly_factors(f) if len(factors) == 1 and factors[0][1] == 1: raise CoefficientError for factor, k in factors: for r in _try_heuristics(factor): _update_dict(result, r, k) except CoefficientError: result = _try_decompose(f) result.update(zeros) domain = flags.get('domain', None) if domain 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[domain] except KeyError: raise ValueError("Invalid domain: %s" % domain) for zero in dict(result).iterkeys(): if not query(zero): del result[zero] predicate = flags.get('predicate', None) if predicate is not None: for zero in dict(result).iterkeys(): if not predicate(zero): del result[zero] if not multiple: return result else: zeros = [] for zero, k in result.iteritems(): zeros.extend([zero] * k) return zeros
def roots(f, *symbols, **flags): """Computes symbolic roots of an univariate polynomial. Given an univariate polynomial f with symbolic 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 aren't used in the algorithm because of unreadable output. To enable them set cubics=True or quartics=True respectively. To get roots from a specific domain set the 'domain' flag with one of the following specifiers: Z, Q, R, I, C. By default all roots are returned (this is equivalent to setting domain='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. >>> from sympy import * >>> x,y = symbols('xy') >>> roots(x**2 - 1, x) {1: 1, -1: 1} >>> roots(x**2 - y, x) {y**(1/2): 1, -y**(1/2): 1} """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") if f.is_multivariate: raise MultivariatePolyError(f) def roots_trivial(g): if g.length == 1: if g.is_constant: return [] else: return [S.Zero] * g.degree (k,), g = g.as_reduced() if k == 0: zeros = [] else: zeros = [S.Zero] * k if g.length == 2: zeros += roots_binomial(g) else: x = g.symbols[0] for i in [S.NegativeOne, S.One]: if g(i).expand() is S.Zero: g = poly_div(g, x - i)[0] zeros.append(i) break n = g.degree if n == 1: zeros += roots_linear(g) elif n == 2: zeros += roots_quadratic(g) elif n == 3: if flags.get("cubics", False): # TODO: now we can used factor() not only # for cubic polynomials. See #1158 try: _, factors = poly_factors(g) if len(factors) == 1: raise PolynomialError for factor, k in factors: zeros += roots(factor, multiple=True) * k except PolynomialError: zeros += roots_cubic(g) elif n == 4: if flags.get("quartics", False): zeros += roots_quartic(g) return zeros multiple = flags.get("multiple", False) if f.length == 1: if f.is_constant: if multiple: return [] else: return {} else: result = {S.Zero: f.degree} else: (k,), f = f.as_reduced() if k == 0: result = {} else: result = {S.Zero: k} if f.degree == 1: result[roots_linear(f)[0]] = 1 else: factors = poly_decompose(f) zeros, g = {}, factors[0] for i, h in enumerate(poly_sqf(g)): for zero in roots_trivial(h): if zeros.has_key(zero): zeros[zero] += i + 1 else: zeros[zero] = i + 1 for factor in factors[1:]: previous, zeros = zeros.copy(), {} for zero, i in previous.iteritems(): g = factor.sub_term(zero, (0,)) for j, h in enumerate(poly_sqf(g)): for zero in roots_trivial(h): if zeros.has_key(zero): zeros[zero] += i * (j + 1) else: zeros[zero] = i * (j + 1) result.update(zeros) domain = flags.get("domain", None) if domain 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[domain] except KeyError: raise ValueError("Invalid domain: %s" % domain) for zero in dict(result).iterkeys(): if not query(zero): del result[zero] predicate = flags.get("predicate", None) if predicate is not None: for zero in dict(result).iterkeys(): if not predicate(zero): del result[zero] if not multiple: return result else: zeros = [] for zero, k in result.iteritems(): zeros.extend([zero] * k) return zeros
def number_of_real_roots(f, *symbols, **flags): """Returns the number of distinct real roots of f in (inf, sup]. >>> from sympy import * >>> x,y = symbols('xy') >>> f = Poly(x**2 - 1, x) Count real roots in the (-oo, oo) interval: >>> number_of_real_roots(f) 2 Count real roots in the (0, 2) interval: >>> number_of_real_roots(f, inf=0, sup=2) 1 Count real roots in the (sqrt(2), oo) interval: >>> number_of_real_roots(f, inf=sqrt(2)) 0 For more information on the implemented algorithm refer to: [1] J.H. Davenport, Y. Siret, E. Tournier, Computer Algebra Systems and Algorithms for Algebraic Computation, Academic Press, London, 1988, pp. 124-128 """ def sign_changes(seq): count = 0 for i in xrange(1, len(seq)): if (seq[i-1] < 0 and seq[i] >= 0) or \ (seq[i-1] > 0 and seq[i] <= 0): count += 1 return count if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") if f.is_multivariate: raise MultivariatePolyError(f) if f.degree < 1: return 0 inf = flags.get('inf', None) if inf is not None: inf = sympify(inf) if not inf.is_number: raise ValueError("Not a number: %s" % inf) elif abs(inf) is S.Infinity: inf = None sup = flags.get('sup', None) if sup is not None: sup = sympify(sup) if not sup.is_number: raise ValueError("Not a number: %s" % sup) elif abs(sup) is S.Infinity: sup = None sturm = poly_sturm(f) if inf is None: signs_inf = sign_changes([s.LC * (-1)**s.LM[0] for s in sturm]) else: signs_inf = sign_changes([s(inf) for s in sturm]) if sup is None: signs_sup = sign_changes([s.LC for s in sturm]) else: signs_sup = sign_changes([s(sup) for s in sturm]) return abs(signs_inf - signs_sup)
def test_Poly(): sT(Poly(7, x), "Poly([(Integer(7), (0,))], Symbol('x'), order='grlex')") sT(Poly(2*x*y + 7, x, y), "Poly([(Integer(2), (1, 1)), (Integer(7), (0, 0))], Symbol('x'), Symbol('y'), order='grlex')") sT(Poly(2*x*y - 7, x, y, order='grevlex'), "Poly([(Integer(2), (1, 1)), (Integer(-7), (0, 0))], Symbol('x'), Symbol('y'), order='grevlex')")
def test_Roots(): f = Poly(x**17 + 2 * x - 1, x) assert str(RootsOf(f)) == "RootsOf(x**17 + 2*x - 1, x)" assert str(RootOf(f, 0)) == "RootOf(x**17 + 2*x - 1, x, index=0)" assert str(RootSum(Lambda(z, z**2), f)) == "RootSum(Lambda(_z, _z**2), x**17 + 2*x - 1, x)"
def roots(f, *symbols, **flags): """Computes symbolic roots of an univariate polynomial. Given an univariate polynomial f with symbolic 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 'domain' flag with one of the following specifiers: Z, Q, R, I, C. By default all roots are returned (this is equivalent to setting domain='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. >>> from sympy import * >>> x,y = symbols('xy') >>> roots(x**2 - 1, x) {1: 1, -1: 1} >>> roots(x**2 - y, x) {y**(1/2): 1, -y**(1/2): 1} """ if not isinstance(f, Poly): f = Poly(f, *symbols) elif symbols: raise SymbolsError("Redundant symbols were given") if f.is_multivariate: raise MultivariatePolyError(f) def _update_dict(result, root, k): if result.has_key(root): result[root] += k else: result[root] = k def _try_decompose(f): """Find roots using functional decomposition. """ factors = poly_decompose(f) result, g = {}, factors[0] for i, h in enumerate(poly_sqf(g)): for r in _try_heuristics(h): _update_dict(result, r, i + 1) for factor in factors[1:]: last, result = result.copy(), {} for last_r, i in last.iteritems(): g = factor.sub_term(last_r, (0,)) for j, h in enumerate(poly_sqf(g)): for r in _try_heuristics(h): _update_dict(result, r, i * (j + 1)) return result def _try_heuristics(f): """Find roots using formulas and some tricks. """ if f.length == 1: if f.is_constant: return [] else: return [S(0)] * f.degree if f.length == 2: if f.degree == 1: return roots_linear(f) else: return roots_binomial(f) x, result = f.symbols[0], [] for i in [S(-1), S(1)]: if f(i).expand().is_zero: f = poly_div(f, x - i)[0] result.append(i) break n = f.degree if n == 1: result += roots_linear(f) elif n == 2: result += roots_quadratic(f) elif n == 3 and flags.get("cubics", True): result += roots_cubic(f) elif n == 4 and flags.get("quartics", False): result += roots_quartic(f) return result multiple = flags.get("multiple", False) if f.length == 1: if f.is_constant: if multiple: return [] else: return {} else: result = {S(0): f.degree} else: (k,), f = f.as_reduced() if k == 0: zeros = {} else: zeros = {S(0): k} result = {} if f.length == 2: if f.degree == 1: result[roots_linear(f)[0]] = 1 else: for r in roots_binomial(f): _update_dict(result, r, 1) elif f.degree == 2: for r in roots_quadratic(f): _update_dict(result, r, 1) else: try: _, factors = poly_factors(f) if len(factors) == 1 and factors[0][1] == 1: raise CoefficientError for factor, k in factors: for r in _try_heuristics(factor): _update_dict(result, r, k) except CoefficientError: result = _try_decompose(f) result.update(zeros) domain = flags.get("domain", None) if domain 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[domain] except KeyError: raise ValueError("Invalid domain: %s" % domain) for zero in dict(result).iterkeys(): if not query(zero): del result[zero] predicate = flags.get("predicate", None) if predicate is not None: for zero in dict(result).iterkeys(): if not predicate(zero): del result[zero] if not multiple: return result else: zeros = [] for zero, k in result.iteritems(): zeros.extend([zero] * k) return zeros
def kronecker_mv(f, **flags): """Kronecker method for Z[X] polynomials. NOTE: This function is very slow even on small input. Use debug=True flag to see its progress, if any. """ symbols = f.symbols def mv_int_div(f, g): q = Poly((), *symbols) r = Poly((), *symbols) while not f.is_zero: lc_f, lc_g = f.LC, g.LC dv = lc_f % lc_g cf = lc_f / lc_g monom = monomial_div(f.LM, g.LM) if dv == 0 and monom is not None: q = q.add_term(cf, monom) f -= g.mul_term(cf, monom) else: r = r.add_term(*f.LT) f = f.kill_lead_term() return q, r def combinations(lisp, m): def recursion(fa, lisp, m): if m == 0: yield fa else: for i, fa2 in enumerate(lisp[0:len(lisp) + 1 - m]): for el in recursion(zzx_mul(fa2, fa), list(lisp[i + 1:]), m - 1): yield el for i, fa in enumerate(lisp[0:len(lisp) + 1 - m]): for el in recursion(fa, list(lisp[i + 1:]), m - 1): yield el debug = flags.get('debug', False) cont, f = f.as_primitive() N = len(symbols) max_exp = {} for v in symbols: max_exp[v] = 0 for coeff, monom in f.iter_terms(): for v, exp in zip(symbols, monom): if exp > max_exp[v]: max_exp[v] = exp symbols = sorted(symbols, reverse=True, key=lambda v: max_exp[v]) f = Poly(f, *symbols) d = max_exp[symbols[0]] + 1 terms, exps = {}, [] for i in xrange(0, len(symbols)): exps.append(d**i) for coeff, monom in f.iter_terms(): exp = 0 for i, expi in enumerate(monom): exp += expi * exps[i] terms[exp] = int(coeff) g, factors = zzx_from_dict(terms), [] try: for ff, k in zzx_factor(g)[1]: for i in xrange(0, k): factors.append(ff) except OverflowError: raise PolynomialError( "input too large for multivariate Kronecker method") const, result, tested = 1, [], [] if debug: print "KRONECKER-MV: Z[x] #factors = %i ..." % (len(factors)) for k in range(1, len(factors) // 2 + 1): for h in combinations(factors, k): if h in tested: continue n = zzx_degree(h) terms = {} for coeff in h: if not coeff: n = n - 1 continue else: coeff = Integer(coeff) y_deg, n = n, n - 1 monom = [0] * N for i in xrange(N): v_deg = y_deg % d y_deg = (y_deg - v_deg) // d monom[i] = v_deg monom = tuple(monom) if terms.has_key(monom): terms[monom] += coeff else: terms[monom] = coeff cand = Poly(terms, *symbols) if cand.is_one: continue if cand.LC.is_negative: cand = -cand q, r = mv_int_div(f, cand) if r.is_zero: if debug: print "KRONECKER-MV: Z[X] factor found %s" % cand result.append(cand) f = q else: tested.append(h) if f.is_constant: const, f = f.LC, Poly(1, *symbols) break if f.is_one: break if not f.is_one: if debug: print "KRONECKER-MV: Z[X] factor found %s" % f result.append(f) factors = {} for ff in result: if factors.has_key(ff): factors[ff] += 1 else: factors[ff] = 1 return cont * const, sorted(factors.items())
def roots_quartic(f): """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. NOTE: There is not a single symbolic solution that is valid for all possible values of A, B, C and D. There are 4 sets of solutions possible based on the code below. These solutions (determined by the values of the reduced quartic coefficients, a = B/A, b = C/A, c = D/A, and d = E/A, are: 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 but a) p = -d + a*c/4 - b**2/12 = 0 and q = b*d/3 + a*b*c/24 - c**2/8 - d*a**2/8 - b**3/108 >= 0 then u (see code) will be zero, otherwise b) u != 0 Schematically, it looks like this: f = 0 g = 0 f != 0 and g != 0 | | / \ | | p=0 and q>=0 p!=0 or q<0 | | (i.e. u = 0 (i.e. u != 0) | | | | 4 solns 4 solns 4 solns 4 solns (default symbolic solution) These branches do not count the special cases that have a particularly simple form of the roots. Those special cases can often be solved by the general procedure. In the test suite, there are tests for those cases, and each of them marked with "general soln ok, too" in the code below still gave the same for the tests. Example:: >>> from sympy import var, Poly >>> from sympy.polys.rootfinding import roots_quartic >>> x = var('x') >>> r = roots_quartic(Poly(x**4 -6*x**3 +17*x**2 -26*x +20, x)) >>> # 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 + 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 #Other_particular_case:_Quasi-symmetric_equations """ # normalized coefficients one, a, b, c, d = f.as_monic().iter_all_coeffs() if d is S.Zero: # general soln ok, too return [S.Zero] + roots([1, a, b, c], multiple = True) elif (c/a)**2 == d: # general solution ok, too m = sqrt(d) z = Symbol('z', dummy=True) x = f.symbols[0] z1, z2 = roots_quadratic(Poly(z**2+a*z+b-2*m, z)) f = Poly(x**2 - z*x + m, x) return roots_quadratic(f.subs(z, z1)) + \ roots_quadratic(f.subs(z, z2)) 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 ans = [] if f.is_zero: # general solution not valid if f = 0. y1, y2 = [tmp ** S.Half for tmp in roots([1, e, g], multiple = True)] return [tmp - aon4 for tmp in [-y1, -y2, y1, y2]] if g.is_zero: # general solution not valid if g = 0 y = [S.Zero] + roots([1, 0, e, f], multiple = True) return [tmp - aon4 for tmp in y] else: TH = Rational(1, 3) p = -e**2/12 - g q = -e**3/108 + e*g/3 - f**2/8 root = sqrt(q**2/4 + p**3/27) rr = [-q/2 + s*root for s in [1]] # in [1,-1], either will do, so pick 1 for r in rr: uu = [r**TH] # solve(x**3-r,x), any one will do, so take primary for u in uu: if u.is_zero: y = -5*e/6 + u - q**TH else: y = -5*e/6 + u - p/u/3 w = sqrt(e + 2*y) # try to return the real solutions first if the # sign can be determined. The term tested is the # argument to `root` below. arg1 = 3*e + 2*y arg2 = 2*f/w if -(arg1 + arg2) > S.Zero: ss = [-1, 1] else: ss= [1, -1] for s in ss: root = sqrt(-(arg1 + s*arg2)) # return the more negative root first; # note that t and s have opposite signs # in the formula: s=+/-1 while t=-/+1. if aon4 > S.Zero: tt = [-1, 1] else: tt = [1, -1] for t in tt: ans.append((s*w - t*root)/2 - aon4) return ans
def test_Poly(): assert str(Poly(0, x)) == "Poly(0, x)" assert str(Poly(1, x)) == "Poly(1, x)" assert str(Poly(x, x)) == "Poly(x, x)" assert str(Poly(2 * x + 1, x)) == "Poly(2*x + 1, x)" assert str(Poly(2 * x - 1, x)) == "Poly(2*x - 1, x)" assert str(Poly(-1, x)) == "Poly(-1, x)" assert str(Poly(-x, x)) == "Poly(-x, x)" assert str(Poly(-2 * x + 1, x)) == "Poly(-2*x + 1, x)" assert str(Poly(-2 * x - 1, x)) == "Poly(-2*x - 1, x)" assert str(Poly(x - 1, x, order='lex')) == "Poly(x - 1, x)" assert str(Poly(x**2 + 1 + y, x)) == "Poly(x**2 + 1 + y, x)" assert str(Poly(x**2 - 1 + y, x)) == "Poly(x**2 - 1 + y, x)" assert str(Poly(-x * y * z + x * y - 1, x, y, z)) == "Poly(-x*y*z + x*y - 1, x, y, z)" assert str(Poly(-w*x**21*y**7*z + (1 + w)*z**3 - 2*x*z + 1, x, y, z)) == \ "Poly(-w*x**21*y**7*z + (1 + w)*z**3 - 2*x*z + 1, x, y, z)" assert str(Poly(x*y*z**2 - 27*x, x, y, z, order='lex')) == \ "Poly(x*y*z**2 - 27*x, x, y, z, order='lex')" assert str(Poly(x*y*z**2 - 27*x, x, y, z, order='grlex')) == \ "Poly(x*y*z**2 - 27*x, x, y, z)" assert str(Poly(x*y*z**2 - 27*x, x, y, z, order='grevlex')) == \ "Poly(x*y*z**2 - 27*x, x, y, z, order='grevlex')"