def doit(self, **hints): """ Perform the integration using any hints given. Examples ======== >>> from sympy import Integral >>> from sympy.abc import x, i >>> Integral(x**i, (i, 1, 3)).doit() Piecewise((2, log(x) == 0), (x**3/log(x) - x/log(x), True)) See Also ======== sympy.integrals.trigonometry.trigintegrate sympy.integrals.risch.heurisch sympy.integrals.rationaltools.ratint as_sum : Approximate the integral using a sum """ if not hints.get('integrals', True): return self deep = hints.get('deep', True) meijerg = hints.get('meijerg', None) conds = hints.get('conds', 'piecewise') risch = hints.get('risch', None) manual = hints.get('manual', None) if conds not in ['separate', 'piecewise', 'none']: raise ValueError('conds must be one of "separate", "piecewise", ' '"none", got: %s' % conds) if risch and any(len(xab) > 1 for xab in self.limits): raise ValueError('risch=True is only allowed for indefinite integrals.') # check for the trivial case of equal upper and lower limits if self.is_zero: return S.Zero # now compute and check the function function = self.function if deep: function = function.doit(**hints) if function.is_zero: return S.Zero # There is no trivial answer, so continue undone_limits = [] # ulj = free symbols of any undone limits' upper and lower limits ulj = set() for xab in self.limits: # compute uli, the free symbols in the # Upper and Lower limits of limit I if len(xab) == 1: uli = set(xab[:1]) elif len(xab) == 2: uli = xab[1].free_symbols elif len(xab) == 3: uli = xab[1].free_symbols.union(xab[2].free_symbols) # this integral can be done as long as there is no blocking # limit that has been undone. An undone limit is blocking if # it contains an integration variable that is in this limit's # upper or lower free symbols or vice versa if xab[0] in ulj or any(v[0] in uli for v in undone_limits): undone_limits.append(xab) ulj.update(uli) continue # There are a number of tradeoffs in using the meijer g method. # It can sometimes be a lot faster than other methods, and # sometimes slower. And there are certain types of integrals for # which it is more likely to work than others. # These heuristics are incorporated in deciding what integration # methods to try, in what order. # See the integrate() docstring for details. def try_meijerg(function, xab): ret = None if len(xab) == 3 and meijerg is not False: x, a, b = xab try: res = meijerint_definite(function, x, a, b) except NotImplementedError: from sympy.integrals.meijerint import _debug _debug('NotImplementedError from meijerint_definite') res = None if res is not None: f, cond = res if conds == 'piecewise': ret = Piecewise((f, cond), (self.func(function, (x, a, b)), True)) elif conds == 'separate': if len(self.limits) != 1: raise ValueError('conds=separate not supported in ' 'multiple integrals') ret = f, cond else: ret = f return ret meijerg1 = meijerg if len(xab) == 3 and xab[1].is_real and xab[2].is_real \ and not function.is_Poly and \ (xab[1].has(oo, -oo) or xab[2].has(oo, -oo)): ret = try_meijerg(function, xab) if ret is not None: function = ret continue else: meijerg1 = False # If the special meijerg code did not succeed finding a definite # integral, then the code using meijerint_indefinite will not either # (it might find an antiderivative, but the answer is likely to be # nonsensical). # Thus if we are requested to only use meijer g-function methods, # we give up at this stage. Otherwise we just disable g-function # methods. if meijerg1 is False and meijerg is True: antideriv = None else: antideriv = self._eval_integral( function, xab[0], meijerg=meijerg1, risch=risch, manual=manual, conds=conds) if antideriv is None and meijerg1 is True: ret = try_meijerg(function, xab) if ret is not None: function = ret continue if antideriv is None: undone_limits.append(xab) else: if len(xab) == 1: function = antideriv else: if len(xab) == 3: x, a, b = xab if len(xab) == 2: x, b = xab a = None if deep: if isinstance(a, Basic): a = a.doit(**hints) if isinstance(b, Basic): b = b.doit(**hints) if antideriv.is_Poly: gens = list(antideriv.gens) gens.remove(x) antideriv = antideriv.as_expr() function = antideriv._eval_interval(x, a, b) function = Poly(function, *gens) else: try: function = antideriv._eval_interval(x, a, b) except NotImplementedError: # This can happen if _eval_interval depends in a # complicated way on limits that cannot be computed undone_limits.append(xab) if undone_limits: return self.func(*([function] + undone_limits)) return function
def _get_euler_characteristic_eq_sols(eq, func, match_obj): r""" Returns the solution of homogeneous part of the linear euler ODE and the list of roots of characteristic equation. The parameter ``match_obj`` is a dict of order:coeff terms, where order is the order of the derivative on each term, and coeff is the coefficient of that derivative. """ x = func.args[0] f = func.func # First, set up characteristic equation. chareq, symbol = S.Zero, Dummy('x') for i in match_obj: if i >= 0: chareq += (match_obj[i] * diff(x**symbol, x, i) * x**-symbol).expand() chareq = Poly(chareq, symbol) chareqroots = [rootof(chareq, k) for k in range(chareq.degree())] collectterms = [] # A generator of constants constants = list(get_numbered_constants(eq, num=chareq.degree() * 2)) constants.reverse() # Create a dict root: multiplicity or charroots charroots = defaultdict(int) for root in chareqroots: charroots[root] += 1 gsol = S.Zero ln = log for root, multiplicity in charroots.items(): for i in range(multiplicity): if isinstance(root, RootOf): gsol += (x**root) * constants.pop() if multiplicity != 1: raise ValueError("Value should be 1") collectterms = [(0, root, 0)] + collectterms elif root.is_real: gsol += ln(x)**i * (x**root) * constants.pop() collectterms = [(i, root, 0)] + collectterms else: reroot = re(root) imroot = im(root) gsol += ln(x)**i * ( x**reroot) * (constants.pop() * sin(abs(imroot) * ln(x)) + constants.pop() * cos(imroot * ln(x))) collectterms = [(i, reroot, imroot)] + collectterms gsol = Eq(f(x), gsol) gensols = [] # Keep track of when to use sin or cos for nonzero imroot for i, reroot, imroot in collectterms: if imroot == 0: gensols.append(ln(x)**i * x**reroot) else: sin_form = ln(x)**i * x**reroot * sin(abs(imroot) * ln(x)) if sin_form in gensols: cos_form = ln(x)**i * x**reroot * cos(imroot * ln(x)) gensols.append(cos_form) else: gensols.append(sin_form) return gsol, gensols
def apart_full_decomposition(P, Q): """ Bronstein's full partial fraction decomposition algorithm. Given a univariate rational function ``f``, performing only GCD operations over the algebraic closure of the initial ground domain of definition, compute full partial fraction decomposition with fractions having linear denominators. Note that no factorization of the initial denominator of ``f`` is performed. The final decomposition is formed in terms of a sum of :class:`RootSum` instances. References ========== 1. [Bronstein93]_ """ f, x, U = P/Q, P.gen, [] u = Function('u')(x) a = Dummy('a') partial = S(0) for d, n in Q.sqf_list_include(all=True): b = d.as_expr() U += [ u.diff(x, n - 1) ] h = cancel(f*b**n) / u**n H, subs = [h], [] for j in range(1, n): H += [ H[-1].diff(x) / j ] for j in range(1, n + 1): subs += [ (U[j - 1], b.diff(x, j) / j) ] for j in range(0, n): P, Q = cancel(H[j]).as_numer_denom() for i in range(0, j + 1): P = P.subs(*subs[j - i]) Q = Q.subs(*subs[0]) P = Poly(P, x) Q = Poly(Q, x) G = P.gcd(d) D = d.quo(G) B, g = Q.half_gcdex(D) b = (P * B.quo(g)).rem(D) numer = b.as_expr() denom = (x - a)**(n - j) func = Lambda(a, numer.subs(x, a)/denom) partial += RootSum(D, func, auto=False) return partial
def _solve_inequality(ie, s, linear=False): """Return the inequality with s isolated on the left, if possible. If the relationship is non-linear, a solution involving And or Or may be returned. False or True are returned if the relationship is never True or always True, respectively. If `linear` is True (default is False) an `s`-dependent expression will be isolated on the left, if possible but it will not be solved for `s` unless the expression is linear in `s`. Furthermore, only "safe" operations which don't change the sense of the relationship are applied: no division by an unsigned value is attempted unless the relationship involves Eq or Ne and no division by a value not known to be nonzero is ever attempted. Examples ======== >>> from sympy import Eq, Symbol >>> from sympy.solvers.inequalities import _solve_inequality as f >>> from sympy.abc import x, y For linear expressions, the symbol can be isolated: >>> f(x - 2 < 0, x) x < 2 >>> f(-x - 6 < x, x) x > -3 Sometimes nonlinear relationships will be False >>> f(x**2 + 4 < 0, x) False Or they may involve more than one region of values: >>> f(x**2 - 4 < 0, x) (-2 < x) & (x < 2) To restrict the solution to a relational, set linear=True and only the x-dependent portion will be isolated on the left: >>> f(x**2 - 4 < 0, x, linear=True) x**2 < 4 Division of only nonzero quantities is allowed, so x cannot be isolated by dividing by y: >>> y.is_nonzero is None # it is unknown whether it is 0 or not True >>> f(x*y < 1, x) x*y < 1 And while an equality (or inequality) still holds after dividing by a non-zero quantity >>> nz = Symbol('nz', nonzero=True) >>> f(Eq(x*nz, 1), x) Eq(x, 1/nz) the sign must be known for other inequalities involving > or <: >>> f(x*nz <= 1, x) nz*x <= 1 >>> p = Symbol('p', positive=True) >>> f(x*p <= 1, x) x <= 1/p When there are denominators in the original expression that are removed by expansion, conditions for them will be returned as part of the result: >>> f(x < x*(2/x - 1), x) (x < 1) & Ne(x, 0) """ from sympy.solvers.solvers import denoms if s not in ie.free_symbols: return ie if ie.rhs == s: ie = ie.reversed if ie.lhs == s and s not in ie.rhs.free_symbols: return ie def classify(ie, s, i): # return True or False if ie evaluates when substituting s with # i else None (if unevaluated) or NaN (when there is an error # in evaluating) try: v = ie.subs(s, i) if v is S.NaN: return v elif v not in (True, False): return return v except TypeError: return S.NaN rv = None oo = S.Infinity expr = ie.lhs - ie.rhs try: p = Poly(expr, s) if p.degree() == 0: rv = ie.func(p.as_expr(), 0) elif not linear and p.degree() > 1: # handle in except clause raise NotImplementedError except (PolynomialError, NotImplementedError): if not linear: try: rv = reduce_rational_inequalities([[ie]], s) except PolynomialError: rv = solve_univariate_inequality(ie, s) # remove restrictions wrt +/-oo that may have been # applied when using sets to simplify the relationship okoo = classify(ie, s, oo) if okoo is S.true and classify(rv, s, oo) is S.false: rv = rv.subs(s < oo, True) oknoo = classify(ie, s, -oo) if (oknoo is S.true and classify(rv, s, -oo) is S.false): rv = rv.subs(-oo < s, True) rv = rv.subs(s > -oo, True) if rv is S.true: rv = (s <= oo) if okoo is S.true else (s < oo) if oknoo is not S.true: rv = And(-oo < s, rv) else: p = Poly(expr) conds = [] if rv is None: e = p.as_expr() # this is in expanded form # Do a safe inversion of e, moving non-s terms # to the rhs and dividing by a nonzero factor if # the relational is Eq/Ne; for other relationals # the sign must also be positive or negative rhs = 0 b, ax = e.as_independent(s, as_Add=True) e -= b rhs -= b ef = factor_terms(e) a, e = ef.as_independent(s, as_Add=False) if (a.is_zero != False or # don't divide by potential 0 a.is_negative == a.is_positive is None and # if sign is not known then ie.rel_op not in ('!=', '==')): # reject if not Eq/Ne e = ef a = S.One rhs /= a if a.is_positive: rv = ie.func(e, rhs) else: rv = ie.reversed.func(e, rhs) # return conditions under which the value is # valid, too. beginning_denoms = denoms(ie.lhs) | denoms(ie.rhs) current_denoms = denoms(rv) for d in beginning_denoms - current_denoms: c = _solve_inequality(Eq(d, 0), s, linear=linear) if isinstance(c, Eq) and c.lhs == s: if classify(rv, s, c.rhs) is S.true: # rv is permitting this value but it shouldn't conds.append(~c) for i in (-oo, oo): if (classify(rv, s, i) is S.true and classify(ie, s, i) is not S.true): conds.append(s < i if i is oo else i < s) conds.append(rv) return And(*conds)
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<sqrt(5)>') >>> gp = poly(x**2 + (2 + sqrt(5))*x + sqrt(5), x, domain='QQ<sqrt(5)>') >>> 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 test_roots_composite(): assert len(roots(Poly(y**3 + y**2 * sqrt(x) + y + x, y, composite=True))) == 3
A, B, C, h in k[t] and for any solution q in k<t> of a*Dq + b*q == c, r = qh in k[t] satisfies A*Dr + B*r == C. For case == 'primitive', k<t> == k[t], so it returns (a, b, c, 1) in this case. This constitutes step 2 of the outline given in the rde.py docstring. """ from sympy.integrals.prde import parametric_log_deriv # TODO: finish writing this and write tests if case == 'auto': case = DE.case if case == 'exp': p = Poly(DE.t, DE.t) elif case == 'tan': p = Poly(DE.t**2 + 1, DE.t) elif case in ['primitive', 'base']: B = ba.to_field().quo(bd) C = ca.to_field().quo(cd) return (a, B, C, Poly(1, DE.t)) else: raise ValueError("case must be one of {'exp', 'tan', 'primitive', " "'base'}, not %s." % case) nb = order_at(ba, p, DE.t) - order_at(bd, p, DE.t) nc = order_at(ca, p, DE.t) - order_at(cd, p, DE.t) n = min(0, nc - min(0, nb)) if not nb:
def _ratsimpmodprime(a, b, allsol, N=0, D=0): r""" Computes a rational simplification of ``a/b`` which minimizes the sum of the total degrees of the numerator and the denominator. The algorithm proceeds by looking at ``a * d - b * c`` modulo the ideal generated by ``G`` for some ``c`` and ``d`` with degree less than ``a`` and ``b`` respectively. The coefficients of ``c`` and ``d`` are indeterminates and thus the coefficients of the normalform of ``a * d - b * c`` are linear polynomials in these indeterminates. If these linear polynomials, considered as system of equations, have a nontrivial solution, then `\frac{a}{b} \equiv \frac{c}{d}` modulo the ideal generated by ``G``. So, by construction, the degree of ``c`` and ``d`` is less than the degree of ``a`` and ``b``, so a simpler representation has been found. After a simpler representation has been found, the algorithm tries to reduce the degree of the numerator and denominator and returns the result afterwards. As an extension, if quick=False, we look at all possible degrees such that the total degree is less than *or equal to* the best current solution. We retain a list of all solutions of minimal degree, and try to find the best one at the end. """ c, d = a, b steps = 0 maxdeg = a.total_degree() + b.total_degree() if quick: bound = maxdeg - 1 else: bound = maxdeg while N + D <= bound: if (N, D) in tested: break tested.add((N, D)) M1 = staircase(N) M2 = staircase(D) debug('%s / %s: %s, %s' % (N, D, M1, M2)) Cs = symbols("c:%d" % len(M1), cls=Dummy) Ds = symbols("d:%d" % len(M2), cls=Dummy) ng = Cs + Ds c_hat = Poly(sum([Cs[i] * M1[i] for i in range(len(M1))]), opt.gens + ng) d_hat = Poly(sum([Ds[i] * M2[i] for i in range(len(M2))]), opt.gens + ng) r = reduced(a * d_hat - b * c_hat, G, opt.gens + ng, order=opt.order, polys=True)[1] S = Poly(r, gens=opt.gens).coeffs() sol = solve(S, Cs + Ds, particular=True, quick=True) if sol and not all([s == 0 for s in sol.values()]): c = c_hat.subs(sol) d = d_hat.subs(sol) # The "free" variables occurring before as parameters # might still be in the substituted c, d, so set them # to the value chosen before: c = c.subs(dict(list(zip(Cs + Ds, [1] * (len(Cs) + len(Ds)))))) d = d.subs(dict(list(zip(Cs + Ds, [1] * (len(Cs) + len(Ds)))))) c = Poly(c, opt.gens) d = Poly(d, opt.gens) if d == 0: raise ValueError('Ideal not prime?') allsol.append((c_hat, d_hat, S, Cs + Ds)) if N + D != maxdeg: allsol = [allsol[-1]] break steps += 1 N += 1 D += 1 if steps > 0: c, d, allsol = _ratsimpmodprime(c, d, allsol, N, D - steps) c, d, allsol = _ratsimpmodprime(c, d, allsol, N - steps, D) return c, d, allsol
def ratsimpmodprime(expr, G, *gens, quick=True, polynomial=False, **args): """ Simplifies a rational expression ``expr`` modulo the prime ideal generated by ``G``. ``G`` should be a Groebner basis of the ideal. >>> from sympy.simplify.ratsimp import ratsimpmodprime >>> from sympy.abc import x, y >>> eq = (x + y**5 + y)/(x - y) >>> ratsimpmodprime(eq, [x*y**5 - x - y], x, y, order='lex') (-x**2 - x*y - x - y)/(-x**2 + x*y) If ``polynomial`` is False, the algorithm computes a rational simplification which minimizes the sum of the total degrees of the numerator and the denominator. If ``polynomial`` is True, this function just brings numerator and denominator into a canonical form. This is much faster, but has potentially worse results. References ========== .. [1] M. Monagan, R. Pearce, Rational Simplification Modulo a Polynomial Ideal, http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.163.6984 (specifically, the second algorithm) """ from sympy import solve debug('ratsimpmodprime', expr) # usual preparation of polynomials: num, denom = cancel(expr).as_numer_denom() try: polys, opt = parallel_poly_from_expr([num, denom] + G, *gens, **args) except PolificationFailed: return expr domain = opt.domain if domain.has_assoc_Field: opt.domain = domain.get_field() else: raise DomainError("can't compute rational simplification over %s" % domain) # compute only once leading_monomials = [g.LM(opt.order) for g in polys[2:]] tested = set() def staircase(n): """ Compute all monomials with degree less than ``n`` that are not divisible by any element of ``leading_monomials``. """ if n == 0: return [1] S = [] for mi in combinations_with_replacement(range(len(opt.gens)), n): m = [0] * len(opt.gens) for i in mi: m[i] += 1 if all([monomial_div(m, lmg) is None for lmg in leading_monomials]): S.append(m) return [Monomial(s).as_expr(*opt.gens) for s in S] + staircase(n - 1) def _ratsimpmodprime(a, b, allsol, N=0, D=0): r""" Computes a rational simplification of ``a/b`` which minimizes the sum of the total degrees of the numerator and the denominator. The algorithm proceeds by looking at ``a * d - b * c`` modulo the ideal generated by ``G`` for some ``c`` and ``d`` with degree less than ``a`` and ``b`` respectively. The coefficients of ``c`` and ``d`` are indeterminates and thus the coefficients of the normalform of ``a * d - b * c`` are linear polynomials in these indeterminates. If these linear polynomials, considered as system of equations, have a nontrivial solution, then `\frac{a}{b} \equiv \frac{c}{d}` modulo the ideal generated by ``G``. So, by construction, the degree of ``c`` and ``d`` is less than the degree of ``a`` and ``b``, so a simpler representation has been found. After a simpler representation has been found, the algorithm tries to reduce the degree of the numerator and denominator and returns the result afterwards. As an extension, if quick=False, we look at all possible degrees such that the total degree is less than *or equal to* the best current solution. We retain a list of all solutions of minimal degree, and try to find the best one at the end. """ c, d = a, b steps = 0 maxdeg = a.total_degree() + b.total_degree() if quick: bound = maxdeg - 1 else: bound = maxdeg while N + D <= bound: if (N, D) in tested: break tested.add((N, D)) M1 = staircase(N) M2 = staircase(D) debug('%s / %s: %s, %s' % (N, D, M1, M2)) Cs = symbols("c:%d" % len(M1), cls=Dummy) Ds = symbols("d:%d" % len(M2), cls=Dummy) ng = Cs + Ds c_hat = Poly(sum([Cs[i] * M1[i] for i in range(len(M1))]), opt.gens + ng) d_hat = Poly(sum([Ds[i] * M2[i] for i in range(len(M2))]), opt.gens + ng) r = reduced(a * d_hat - b * c_hat, G, opt.gens + ng, order=opt.order, polys=True)[1] S = Poly(r, gens=opt.gens).coeffs() sol = solve(S, Cs + Ds, particular=True, quick=True) if sol and not all([s == 0 for s in sol.values()]): c = c_hat.subs(sol) d = d_hat.subs(sol) # The "free" variables occurring before as parameters # might still be in the substituted c, d, so set them # to the value chosen before: c = c.subs(dict(list(zip(Cs + Ds, [1] * (len(Cs) + len(Ds)))))) d = d.subs(dict(list(zip(Cs + Ds, [1] * (len(Cs) + len(Ds)))))) c = Poly(c, opt.gens) d = Poly(d, opt.gens) if d == 0: raise ValueError('Ideal not prime?') allsol.append((c_hat, d_hat, S, Cs + Ds)) if N + D != maxdeg: allsol = [allsol[-1]] break steps += 1 N += 1 D += 1 if steps > 0: c, d, allsol = _ratsimpmodprime(c, d, allsol, N, D - steps) c, d, allsol = _ratsimpmodprime(c, d, allsol, N - steps, D) return c, d, allsol # preprocessing. this improves performance a bit when deg(num) # and deg(denom) are large: num = reduced(num, G, opt.gens, order=opt.order)[1] denom = reduced(denom, G, opt.gens, order=opt.order)[1] if polynomial: return (num / denom).cancel() c, d, allsol = _ratsimpmodprime(Poly(num, opt.gens, domain=opt.domain), Poly(denom, opt.gens, domain=opt.domain), []) if not quick and allsol: debug('Looking for best minimal solution. Got: %s' % len(allsol)) newsol = [] for c_hat, d_hat, S, ng in allsol: sol = solve(S, ng, particular=True, quick=False) newsol.append((c_hat.subs(sol), d_hat.subs(sol))) c, d = min(newsol, key=lambda x: len(x[0].terms()) + len(x[1].terms())) if not domain.is_Field: cn, c = c.clear_denoms(convert=True) dn, d = d.clear_denoms(convert=True) r = Rational(cn, dn) else: r = Rational(1) return (c * r.q) / (d * r.p)
def ratint(f, x, **flags): """Performs indefinite integration of rational functions. Given a field :math:`K` and a rational function :math:`f = p/q`, where :math:`p` and :math:`q` are polynomials in :math:`K[x]`, returns a function :math:`g` such that :math:`f = g'`. >>> from sympy.integrals.rationaltools import ratint >>> from sympy.abc import x >>> ratint(36/(x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2), x) (12*x + 6)/(x**2 - 1) + 4*log(x - 2) - 4*log(x + 1) References ========== .. [Bro05] M. Bronstein, Symbolic Integration I: Transcendental Functions, Second Edition, Springer-Verlag, 2005, pp. 35-70 See Also ======== sympy.integrals.integrals.Integral.doit ratint_logpart, ratint_ratpart """ if type(f) is not tuple: p, q = f.as_numer_denom() else: p, q = f p, q = Poly(p, x, composite=False, field=True), Poly(q, x, composite=False, field=True) coeff, p, q = p.cancel(q) poly, p = p.div(q) result = poly.integrate(x).as_expr() if p.is_zero: return coeff * result g, h = ratint_ratpart(p, q, x) P, Q = h.as_numer_denom() P = Poly(P, x) Q = Poly(Q, x) q, r = P.div(Q) result += g + q.integrate(x).as_expr() if not r.is_zero: symbol = flags.get('symbol', 't') if not isinstance(symbol, Symbol): t = Dummy(symbol) else: t = symbol.as_dummy() L = ratint_logpart(r, Q, x, t) real = flags.get('real') if real is None: if type(f) is not tuple: atoms = f.atoms() else: p, q = f atoms = p.atoms() | q.atoms() for elt in atoms - set([x]): if not elt.is_real: real = False break else: real = True eps = S(0) if not real: for h, q in L: eps += RootSum(q, Lambda(t, t * log(h.as_expr())), quadratic=True) else: for h, q in L: R = log_to_real(h, q, x, t) if R is not None: eps += R else: eps += RootSum(q, Lambda(t, t * log(h.as_expr())), quadratic=True) result += eps return coeff * result
def test_roots_cubic(): assert roots_cubic(Poly(2 * x**3, x)) == [0, 0, 0] assert roots_cubic(Poly(x**3 - 3 * x**2 + 3 * x - 1, x)) == [1, 1, 1] assert roots_cubic(Poly(x**3 + 1, x)) == \ [-1, S.Half - I*sqrt(3)/2, S.Half + I*sqrt(3)/2]
def log_to_real(h, q, x, t): """ Convert complex logarithms to real functions. Given real field K and polynomials h in K[t,x] and q in K[t], returns real function f such that: ___ df d \ ` -- = -- ) a log(h(a, x)) dx dx /__, a | q(a) = 0 Examples ======== >>> from sympy.integrals.rationaltools import log_to_real >>> from sympy.abc import x, y >>> from sympy import Poly, sqrt, S >>> log_to_real(Poly(x + 3*y/2 + S(1)/2, x, domain='QQ[y]'), ... Poly(3*y**2 + 1, y, domain='ZZ'), x, y) 2*sqrt(3)*atan(2*sqrt(3)*x/3 + sqrt(3)/3)/3 >>> log_to_real(Poly(x**2 - 1, x, domain='ZZ'), ... Poly(-2*y + 1, y, domain='ZZ'), x, y) log(x**2 - 1)/2 See Also ======== log_to_atan """ u, v = symbols('u,v', cls=Dummy) H = h.as_expr().subs({t: u + I * v}).expand() Q = q.as_expr().subs({t: u + I * v}).expand() H_map = collect(H, I, evaluate=False) Q_map = collect(Q, I, evaluate=False) a, b = H_map.get(S(1), S(0)), H_map.get(I, S(0)) c, d = Q_map.get(S(1), S(0)), Q_map.get(I, S(0)) R = Poly(resultant(c, d, v), u) R_u = roots(R, filter='R') if len(R_u) != R.count_roots(): return None result = S(0) for r_u in R_u.iterkeys(): C = Poly(c.subs({u: r_u}), v) R_v = roots(C, filter='R') if len(R_v) != C.count_roots(): return None for r_v in R_v: if not r_v.is_positive: continue D = d.subs({u: r_u, v: r_v}) if D.evalf(chop=True) != 0: continue A = Poly(a.subs({u: r_u, v: r_v}), x) B = Poly(b.subs({u: r_u, v: r_v}), x) AB = (A**2 + B**2).as_expr() result += r_u * log(AB) + r_v * log_to_atan(A, B) R_q = roots(q, filter='R') if len(R_q) != q.count_roots(): return None for r in R_q.iterkeys(): result += r * log(h.as_expr().subs(t, r)) return result
def ratint_logpart(f, g, x, t=None): """ Lazard-Rioboo-Trager algorithm. Given a field K and polynomials f and g in K[x], such that f and g are coprime, deg(f) < deg(g) and g is square-free, returns a list of tuples (s_i, q_i) of polynomials, for i = 1..n, such that s_i in K[t, x] and q_i in K[t], and: ___ ___ d f d \ ` \ ` -- - = -- ) ) a log(s_i(a, x)) dx g dx /__, /__, i=1..n a | q_i(a) = 0 Examples ======== >>> from sympy.integrals.rationaltools import ratint_logpart >>> from sympy.abc import x >>> from sympy import Poly >>> ratint_logpart(Poly(1, x, domain='ZZ'), ... Poly(x**2 + x + 1, x, domain='ZZ'), x) [(Poly(x + 3*_t/2 + 1/2, x, domain='QQ[_t]'), ...Poly(3*_t**2 + 1, _t, domain='ZZ'))] >>> ratint_logpart(Poly(12, x, domain='ZZ'), ... Poly(x**2 - x - 2, x, domain='ZZ'), x) [(Poly(x - 3*_t/8 - 1/2, x, domain='QQ[_t]'), ...Poly(-_t**2 + 16, _t, domain='ZZ'))] See Also ======== ratint, ratint_ratpart """ f, g = Poly(f, x), Poly(g, x) t = t or Dummy('t') a, b = g, f - g.diff() * Poly(t, x) res, R = resultant(a, b, includePRS=True) res = Poly(res, t, composite=False) R_map, H = {}, [] for r in R: R_map[r.degree()] = r def _include_sign(c, sqf): if c < 0: h, k = sqf[0] sqf[0] = h * c, k C, res_sqf = res.sqf_list() _include_sign(C, res_sqf) for q, i in res_sqf: _, q = q.primitive() if g.degree() == i: H.append((g, q)) else: h = R_map[i] h_lc = Poly(h.LC(), t, field=True) c, h_lc_sqf = h_lc.sqf_list(all=True) _include_sign(c, h_lc_sqf) for a, j in h_lc_sqf: h = h.quo(Poly(a.gcd(q)**j, x)) inv, coeffs = h_lc.invert(q), [S(1)] for coeff in h.coeffs()[1:]: T = (inv * coeff).rem(q) coeffs.append(T.as_expr()) h = Poly(dict(zip(h.monoms(), coeffs)), x) H.append((h, q)) return H
def doit(self, **hints): if not hints.get('integrals', True): return self deep = hints.get('deep', True) # check for the trivial case of equal upper and lower limits if self.is_zero: return S.Zero # now compute and check the function function = self.function if deep: function = function.doit(**hints) if function.is_zero: return S.Zero # There is no trivial answer, so continue undone_limits = [] ulj = set( ) # free symbols of any undone limits' upper and lower limits for xab in self.limits: # compute uli, the free symbols in the # Upper and Lower limits of limit I if len(xab) == 1: uli = set(xab[:1]) elif len(xab) == 2: uli = xab[1].free_symbols elif len(xab) == 3: uli = xab[1].free_symbols.union(xab[2].free_symbols) # this integral can be done as long as there is no blocking # limit that has been undone. An undone limit is blocking if # it contains an integration variable that is in this limit's # upper or lower free symbols or vice versa if xab[0] in ulj or any(v[0] in uli for v in undone_limits): undone_limits.append(xab) ulj.update(uli) continue antideriv = self._eval_integral(function, xab[0]) if antideriv is None: undone_limits.append(xab) else: if len(xab) == 1: function = antideriv else: if len(xab) == 3: x, a, b = xab if len(xab) == 2: x, b = xab a = None if deep: if isinstance(a, Basic): a = a.doit(**hints) if isinstance(b, Basic): b = b.doit(**hints) if antideriv.is_Poly: gens = list(antideriv.gens) gens.remove(x) antideriv = antideriv.as_expr() function = antideriv._eval_interval(x, a, b) function = Poly(function, *gens) else: function = antideriv._eval_interval(x, a, b) if undone_limits: return self.func(*([function] + undone_limits)) return function
def test_roots_preprocessing(): f = a * y * x**2 + y - b coeff, poly = preprocess_roots(Poly(f, x)) assert coeff == 1 assert poly == Poly(a * y * x**2 + y - b, x) f = c**3 * x**3 + c**2 * x**2 + c * x + a coeff, poly = preprocess_roots(Poly(f, x)) assert coeff == 1 / c assert poly == Poly(x**3 + x**2 + x + a, x) f = c**3 * x**3 + c**2 * x**2 + a coeff, poly = preprocess_roots(Poly(f, x)) assert coeff == 1 / c assert poly == Poly(x**3 + x**2 + a, x) f = c**3 * x**3 + c * x + a coeff, poly = preprocess_roots(Poly(f, x)) assert coeff == 1 / c assert poly == Poly(x**3 + x + a, x) f = c**3 * x**3 + a coeff, poly = preprocess_roots(Poly(f, x)) assert coeff == 1 / c assert poly == Poly(x**3 + a, x) E, F, J, L = symbols("E,F,J,L") f = -21601054687500000000*E**8*J**8/L**16 + \ 508232812500000000*F*x*E**7*J**7/L**14 - \ 4269543750000000*E**6*F**2*J**6*x**2/L**12 + \ 16194716250000*E**5*F**3*J**5*x**3/L**10 - \ 27633173750*E**4*F**4*J**4*x**4/L**8 + \ 14840215*E**3*F**5*J**3*x**5/L**6 + \ 54794*E**2*F**6*J**2*x**6/(5*L**4) - \ 1153*E*J*F**7*x**7/(80*L**2) + \ 633*F**8*x**8/160000 coeff, poly = preprocess_roots(Poly(f, x)) assert coeff == 20 * E * J / (F * L**2) assert poly == 633*x**8 - 115300*x**7 + 4383520*x**6 + 296804300*x**5 - 27633173750*x**4 + \ 809735812500*x**3 - 10673859375000*x**2 + 63529101562500*x - 135006591796875 f = Poly(-y**2 + x**2 * exp(x), y, domain=ZZ[x, exp(x)]) g = Poly(-y**2 + exp(x), y, domain=ZZ[exp(x)]) assert preprocess_roots(f) == (x, g)
def normal_lines(self, p, prec=None): """Normal lines between `p` and the ellipse. Parameters ========== p : Point Returns ======= normal_lines : list with 1, 2 or 4 Lines Examples ======== >>> from sympy import Point, Ellipse >>> e = Ellipse((0, 0), 2, 3) >>> c = e.center >>> e.normal_lines(c + Point(1, 0)) [Line2D(Point2D(0, 0), Point2D(1, 0))] >>> e.normal_lines(c) [Line2D(Point2D(0, 0), Point2D(0, 1)), Line2D(Point2D(0, 0), Point2D(1, 0))] Off-axis points require the solution of a quartic equation. This often leads to very large expressions that may be of little practical use. An approximate solution of `prec` digits can be obtained by passing in the desired value: >>> e.normal_lines((3, 3), prec=2) [Line2D(Point2D(-0.81, -2.7), Point2D(0.19, -1.2)), Line2D(Point2D(1.5, -2.0), Point2D(2.5, -2.7))] Whereas the above solution has an operation count of 12, the exact solution has an operation count of 2020. """ p = Point(p, dim=2) # XXX change True to something like self.angle == 0 if the arbitrarily # rotated ellipse is introduced. # https://github.com/sympy/sympy/issues/2815) if True: rv = [] if p.x == self.center.x: rv.append(Line(self.center, slope=oo)) if p.y == self.center.y: rv.append(Line(self.center, slope=0)) if rv: # at these special orientations of p either 1 or 2 normals # exist and we are done return rv # find the 4 normal points and construct lines through them with # the corresponding slope x, y = Dummy('x', real=True), Dummy('y', real=True) eq = self.equation(x, y) dydx = idiff(eq, y, x) norm = -1/dydx slope = Line(p, (x, y)).slope seq = slope - norm # TODO: Replace solve with solveset, when this line is tested yis = solve(seq, y)[0] xeq = eq.subs(y, yis).as_numer_denom()[0].expand() if len(xeq.free_symbols) == 1: try: # this is so much faster, it's worth a try xsol = Poly(xeq, x).real_roots() except (DomainError, PolynomialError, NotImplementedError): # TODO: Replace solve with solveset, when these lines are tested xsol = _nsort(solve(xeq, x), separated=True)[0] points = [Point(i, solve(eq.subs(x, i), y)[0]) for i in xsol] else: raise NotImplementedError( 'intersections for the general ellipse are not supported') slopes = [norm.subs(zip((x, y), pt.args)) for pt in points] if prec is not None: points = [pt.n(prec) for pt in points] slopes = [i if _not_a_coeff(i) else i.n(prec) for i in slopes] return [Line(pt, slope=s) for pt, s in zip(points, slopes)]
def test_roots0(): assert roots(1, x) == {} assert roots(x, x) == {S.Zero: 1} assert roots(x**9, x) == {S.Zero: 9} assert roots(((x - 2) * (x + 3) * (x - 4)).expand(), x) == { -S(3): 1, S(2): 1, S(4): 1 } assert roots(2 * x + 1, x) == {-S.Half: 1} assert roots((2 * x + 1)**2, x) == {-S.Half: 2} assert roots((2 * x + 1)**5, x) == {-S.Half: 5} assert roots((2 * x + 1)**10, x) == {-S.Half: 10} assert roots(x**4 - 1, x) == {I: 1, S.One: 1, -S.One: 1, -I: 1} assert roots((x**4 - 1)**2, x) == {I: 2, S.One: 2, -S.One: 2, -I: 2} assert roots(((2 * x - 3)**2).expand(), x) == {Rational(3, 2): 2} assert roots(((2 * x + 3)**2).expand(), x) == {-Rational(3, 2): 2} assert roots(((2 * x - 3)**3).expand(), x) == {Rational(3, 2): 3} assert roots(((2 * x + 3)**3).expand(), x) == {-Rational(3, 2): 3} assert roots(((2 * x - 3)**5).expand(), x) == {Rational(3, 2): 5} assert roots(((2 * x + 3)**5).expand(), x) == {-Rational(3, 2): 5} assert roots(((a * x - b)**5).expand(), x) == {b / a: 5} assert roots(((a * x + b)**5).expand(), x) == {-b / a: 5} assert roots(x**2 + (-a - 1) * x + a, x) == {a: 1, S.One: 1} assert roots(x**4 - 2 * x**2 + 1, x) == {S.One: 2, -S.One: 2} assert roots(x**6 - 4*x**4 + 4*x**3 - x**2, x) == \ {S.One: 2, -1 - sqrt(2): 1, S.Zero: 2, -1 + sqrt(2): 1} assert roots(x**8 - 1, x) == { sqrt(2) / 2 + I * sqrt(2) / 2: 1, sqrt(2) / 2 - I * sqrt(2) / 2: 1, -sqrt(2) / 2 + I * sqrt(2) / 2: 1, -sqrt(2) / 2 - I * sqrt(2) / 2: 1, S.One: 1, -S.One: 1, I: 1, -I: 1 } f = -2016*x**2 - 5616*x**3 - 2056*x**4 + 3324*x**5 + 2176*x**6 - \ 224*x**7 - 384*x**8 - 64*x**9 assert roots(f) == { S(0): 2, -S(2): 2, S(2): 1, -S(7) / 2: 1, -S(3) / 2: 1, -S(1) / 2: 1, S(3) / 2: 1 } assert roots((a + b + c) * x - (a + b + c + d), x) == { (a + b + c + d) / (a + b + c): 1 } assert roots(x**3 + x**2 - x + 1, x, cubics=False) == {} assert roots(((x - 2) * (x + 3) * (x - 4)).expand(), x, cubics=False) == { -S(3): 1, S(2): 1, S(4): 1 } assert roots(((x - 2)*(x + 3)*(x - 4)*(x - 5)).expand(), x, cubics=False) == \ {-S(3): 1, S(2): 1, S(4): 1, S(5): 1} assert roots(x**3 + 2 * x**2 + 4 * x + 8, x) == { -S(2): 1, -2 * I: 1, 2 * I: 1 } assert roots(x**3 + 2*x**2 + 4*x + 8, x, cubics=True) == \ {-2*I: 1, 2*I: 1, -S(2): 1} assert roots((x**2 - x)*(x**3 + 2*x**2 + 4*x + 8), x ) == \ {S(1): 1, S(0): 1, -S(2): 1, -2*I: 1, 2*I: 1} r1_2, r1_3 = Rational(1, 2), Rational(1, 3) x0 = (3 * sqrt(33) + 19)**r1_3 x1 = 4 / x0 / 3 x2 = x0 / 3 x3 = sqrt(3) * I / 2 x4 = x3 - r1_2 x5 = -x3 - r1_2 assert roots(x**3 + x**2 - x + 1, x, cubics=True) == { -x1 - x2 - r1_3: 1, -x1 / x4 - x2 * x4 - r1_3: 1, -x1 / x5 - x2 * x5 - r1_3: 1, } f = (x**2 + 2 * x + 3).subs(x, 2 * x**2 + 3 * x).subs(x, 5 * x - 4) r13_20, r1_20 = [Rational(*r) for r in ((13, 20), (1, 20))] s2 = sqrt(2) assert roots(f, x) == { r13_20 + r1_20 * sqrt(1 - 8 * I * s2): 1, r13_20 - r1_20 * sqrt(1 - 8 * I * s2): 1, r13_20 + r1_20 * sqrt(1 + 8 * I * s2): 1, r13_20 - r1_20 * sqrt(1 + 8 * I * s2): 1, } f = x**4 + x**3 + x**2 + x + 1 r1_4, r1_8, r5_8 = [Rational(*r) for r in ((1, 4), (1, 8), (5, 8))] assert roots(f, x) == { -r1_4 + r1_4 * 5**r1_2 + I * (r5_8 + r1_8 * 5**r1_2)**r1_2: 1, -r1_4 + r1_4 * 5**r1_2 - I * (r5_8 + r1_8 * 5**r1_2)**r1_2: 1, -r1_4 - r1_4 * 5**r1_2 + I * (r5_8 - r1_8 * 5**r1_2)**r1_2: 1, -r1_4 - r1_4 * 5**r1_2 - I * (r5_8 - r1_8 * 5**r1_2)**r1_2: 1, } f = z**3 + (-2 - y) * z**2 + (1 + 2 * y - 2 * x**2) * z - y + 2 * x**2 assert roots(f, z) == { S.One: 1, S.Half + S.Half * y + S.Half * sqrt(1 - 2 * y + y**2 + 8 * x**2): 1, S.Half + S.Half * y - S.Half * sqrt(1 - 2 * y + y**2 + 8 * x**2): 1, } assert roots(a * b * c * x**3 + 2 * x**2 + 4 * x + 8, x, cubics=False) == {} assert roots(a * b * c * x**3 + 2 * x**2 + 4 * x + 8, x, cubics=True) != {} assert roots(x**4 - 1, x, filter='Z') == {S.One: 1, -S.One: 1} assert roots(x**4 - 1, x, filter='I') == {I: 1, -I: 1} assert roots((x - 1) * (x + 1), x) == {S.One: 1, -S.One: 1} assert roots((x - 1) * (x + 1), x, predicate=lambda r: r.is_positive) == { S.One: 1 } assert roots(x**4 - 1, x, filter='Z', multiple=True) == [-S.One, S.One] assert roots(x**4 - 1, x, filter='I', multiple=True) == [I, -I] assert roots(x**3, x, multiple=True) == [S.Zero, S.Zero, S.Zero] assert roots(1234, x, multiple=True) == [] f = x**6 - x**5 + x**4 - x**3 + x**2 - x + 1 assert roots(f) == { -I * sin(pi / 7) + cos(pi / 7): 1, -I * sin(2 * pi / 7) - cos(2 * pi / 7): 1, -I * sin(3 * pi / 7) + cos(3 * pi / 7): 1, I * sin(pi / 7) + cos(pi / 7): 1, I * sin(2 * pi / 7) - cos(2 * pi / 7): 1, I * sin(3 * pi / 7) + cos(3 * pi / 7): 1, } g = ((x**2 + 1) * f**2).expand() assert roots(g) == { -I * sin(pi / 7) + cos(pi / 7): 2, -I * sin(2 * pi / 7) - cos(2 * pi / 7): 2, -I * sin(3 * pi / 7) + cos(3 * pi / 7): 2, I * sin(pi / 7) + cos(pi / 7): 2, I * sin(2 * pi / 7) - cos(2 * pi / 7): 2, I * sin(3 * pi / 7) + cos(3 * pi / 7): 2, -I: 1, I: 1, } r = roots(x**3 + 40 * x + 64) real_root = [rx for rx in r if rx.is_real][0] cr = 108 + 6 * sqrt(1074) assert real_root == -2 * root(cr, 3) / 3 + 20 / root(cr, 3) eq = Poly((7 + 5 * sqrt(2)) * x**3 + (-6 - 4 * sqrt(2)) * x**2 + (-sqrt(2) - 1) * x + 2, x, domain='EX') assert roots(eq) == {-1 + sqrt(2): 1, -2 + 2 * sqrt(2): 1, -sqrt(2) + 1: 1} eq = Poly(41 * x**5 + 29 * sqrt(2) * x**5 - 153 * x**4 - 108 * sqrt(2) * x**4 + 175 * x**3 + 125 * sqrt(2) * x**3 - 45 * x**2 - 30 * sqrt(2) * x**2 - 26 * sqrt(2) * x - 26 * x + 24, x, domain='EX') assert roots(eq) == { -sqrt(2) + 1: 1, -2 + 2 * sqrt(2): 1, -1 + sqrt(2): 1, -4 + 4 * sqrt(2): 1, -3 + 3 * sqrt(2): 1 } eq = Poly(x**3 - 2 * x**2 + 6 * sqrt(2) * x**2 - 8 * sqrt(2) * x + 23 * x - 14 + 14 * sqrt(2), x, domain='EX') assert roots(eq) == { -2 * sqrt(2) + 2: 1, -2 * sqrt(2) + 1: 1, -2 * sqrt(2) - 1: 1 } assert roots(Poly((x + sqrt(2))**3 - 7, x, domain='EX')) == \ {-sqrt(2) - root(7, 3)/2 - sqrt(3)*root(7, 3)*I/2: 1, -sqrt(2) - root(7, 3)/2 + sqrt(3)*root(7, 3)*I/2: 1, -sqrt(2) + root(7, 3): 1}
b in k<t>, and g1, ..., gm in k(t) with Dt/t in k (resp. Dt/(t**2 + 1) in k, sqrt(-1) not in k), a != 0, and gcd(a, t) == 1 (resp. gcd(a, t**2 + 1) == 1), return the tuple (A, B, GG, h) such that A, B, h in k[t], GG = [gg1, ..., ggm] in k(t)^m, and for any solution c1, ..., cm in Const(k) and q in k<t> of a*Dq + b*q == Sum(ci*gi, (i, 1, m)), r == q*h in k[t] satisfies A*Dr + B*r == Sum(ci*ggi, (i, 1, m)). For case == 'primitive', k<t> == k[t], so it returns (a, b, G, 1) in this case. """ # TODO: Merge this with the very similar special_denom() in rde.py if case == 'auto': case = DE.case if case == 'exp': p = Poly(DE.t, DE.t) elif case == 'tan': p = Poly(DE.t**2 + 1, DE.t) elif case in ['primitive', 'base']: B = ba.quo(bd) return (a, B, G, Poly(1, DE.t)) else: raise ValueError("case must be one of {'exp', 'tan', 'primitive', " "'base'}, not %s." % case) nb = order_at(ba, p, DE.t) - order_at(bd, p, DE.t) nc = min([order_at(Ga, p, DE.t) - order_at(Gd, p, DE.t) for Ga, Gd in G]) n = min(0, nc - min(0, nb)) if not nb: # Possible cancellation. if case == 'exp':
def rsolve_poly(coeffs, f, n, **hints): r""" Given linear recurrence operator `\operatorname{L}` of order `k` with polynomial coefficients and inhomogeneous equation `\operatorname{L} y = f`, where `f` is a polynomial, we seek for all polynomial solutions over field `K` of characteristic zero. The algorithm performs two basic steps: (1) Compute degree `N` of the general polynomial solution. (2) Find all polynomials of degree `N` or less of `\operatorname{L} y = f`. There are two methods for computing the polynomial solutions. If the degree bound is relatively small, i.e. it's smaller than or equal to the order of the recurrence, then naive method of undetermined coefficients is being used. This gives system of algebraic equations with `N+1` unknowns. In the other case, the algorithm performs transformation of the initial equation to an equivalent one, for which the system of algebraic equations has only `r` indeterminates. This method is quite sophisticated (in comparison with the naive one) and was invented together by Abramov, Bronstein and Petkovsek. It is possible to generalize the algorithm implemented here to the case of linear q-difference and differential equations. Lets say that we would like to compute `m`-th Bernoulli polynomial up to a constant. For this we can use `b(n+1) - b(n) = m n^{m-1}` recurrence, which has solution `b(n) = B_m + C`. For example: >>> from sympy import Symbol, rsolve_poly >>> n = Symbol('n', integer=True) >>> rsolve_poly([-1, 1], 4*n**3, n) C0 + n**4 - 2*n**3 + n**2 References ========== .. [1] S. A. Abramov, M. Bronstein and M. Petkovsek, On polynomial solutions of linear operator equations, in: T. Levelt, ed., Proc. ISSAC '95, ACM Press, New York, 1995, 290-296. .. [2] M. Petkovsek, Hypergeometric solutions of linear recurrences with polynomial coefficients, J. Symbolic Computation, 14 (1992), 243-264. .. [3] M. Petkovsek, H. S. Wilf, D. Zeilberger, A = B, 1996. """ f = sympify(f) if not f.is_polynomial(n): return None homogeneous = f.is_zero r = len(coeffs) - 1 coeffs = [Poly(coeff, n) for coeff in coeffs] polys = [Poly(0, n)]*(r + 1) terms = [(S.Zero, S.NegativeInfinity)]*(r + 1) for i in range(r + 1): for j in range(i, r + 1): polys[i] += coeffs[j]*binomial(j, i) if not polys[i].is_zero: (exp,), coeff = polys[i].LT() terms[i] = (coeff, exp) d = b = terms[0][1] for i in range(1, r + 1): if terms[i][1] > d: d = terms[i][1] if terms[i][1] - i > b: b = terms[i][1] - i d, b = int(d), int(b) x = Dummy('x') degree_poly = S.Zero for i in range(r + 1): if terms[i][1] - i == b: degree_poly += terms[i][0]*FallingFactorial(x, i) nni_roots = list(roots(degree_poly, x, filter='Z', predicate=lambda r: r >= 0).keys()) if nni_roots: N = [max(nni_roots)] else: N = [] if homogeneous: N += [-b - 1] else: N += [f.as_poly(n).degree() - b, -b - 1] N = int(max(N)) if N < 0: if homogeneous: if hints.get('symbols', False): return (S.Zero, []) else: return S.Zero else: return None if N <= r: C = [] y = E = S.Zero for i in range(N + 1): C.append(Symbol('C' + str(i))) y += C[i] * n**i for i in range(r + 1): E += coeffs[i].as_expr()*y.subs(n, n + i) solutions = solve_undetermined_coeffs(E - f, C, n) if solutions is not None: C = [c for c in C if (c not in solutions)] result = y.subs(solutions) else: return None # TBD else: A = r U = N + A + b + 1 nni_roots = list(roots(polys[r], filter='Z', predicate=lambda r: r >= 0).keys()) if nni_roots != []: a = max(nni_roots) + 1 else: a = S.Zero def _zero_vector(k): return [S.Zero] * k def _one_vector(k): return [S.One] * k def _delta(p, k): B = S.One D = p.subs(n, a + k) for i in range(1, k + 1): B *= -Rational(k - i + 1, i) D += B * p.subs(n, a + k - i) return D alpha = {} for i in range(-A, d + 1): I = _one_vector(d + 1) for k in range(1, d + 1): I[k] = I[k - 1] * (x + i - k + 1)/k alpha[i] = S.Zero for j in range(A + 1): for k in range(d + 1): B = binomial(k, i + j) D = _delta(polys[j].as_expr(), k) alpha[i] += I[k]*B*D V = Matrix(U, A, lambda i, j: int(i == j)) if homogeneous: for i in range(A, U): v = _zero_vector(A) for k in range(1, A + b + 1): if i - k < 0: break B = alpha[k - A].subs(x, i - k) for j in range(A): v[j] += B * V[i - k, j] denom = alpha[-A].subs(x, i) for j in range(A): V[i, j] = -v[j] / denom else: G = _zero_vector(U) for i in range(A, U): v = _zero_vector(A) g = S.Zero for k in range(1, A + b + 1): if i - k < 0: break B = alpha[k - A].subs(x, i - k) for j in range(A): v[j] += B * V[i - k, j] g += B * G[i - k] denom = alpha[-A].subs(x, i) for j in range(A): V[i, j] = -v[j] / denom G[i] = (_delta(f, i - A) - g) / denom P, Q = _one_vector(U), _zero_vector(A) for i in range(1, U): P[i] = (P[i - 1] * (n - a - i + 1)/i).expand() for i in range(A): Q[i] = Add(*[(v*p).expand() for v, p in zip(V[:, i], P)]) if not homogeneous: h = Add(*[(g*p).expand() for g, p in zip(G, P)]) C = [Symbol('C' + str(i)) for i in range(A)] g = lambda i: Add(*[c*_delta(q, i) for c, q in zip(C, Q)]) if homogeneous: E = [g(i) for i in range(N + 1, U)] else: E = [g(i) + _delta(h, i) for i in range(N + 1, U)] if E != []: solutions = solve(E, *C) if not solutions: if homogeneous: if hints.get('symbols', False): return (S.Zero, []) else: return S.Zero else: return None else: solutions = {} if homogeneous: result = S.Zero else: result = h for c, q in list(zip(C, Q)): if c in solutions: s = solutions[c]*q C.remove(c) else: s = c*q result += s.expand() if hints.get('symbols', False): return (result, C) else: return result
def parametric_log_deriv_heu(fa, fd, wa, wd, DE, c1=None): """ Parametric logarithmic derivative heuristic. Given a derivation D on k[t], f in k(t), and a hyperexponential monomial theta over k(t), raises either NotImplementedError, in which case the heuristic failed, or returns None, in which case it has proven that no solution exists, or returns a solution (n, m, v) of the equation n*f == Dv/v + m*Dtheta/theta, with v in k(t)* and n, m in ZZ with n != 0. If this heuristic fails, the structure theorem approach will need to be used. The argument w == Dtheta/theta """ # TODO: finish writing this and write tests c1 = c1 or Dummy('c1') p, a = fa.div(fd) q, b = wa.div(wd) B = max(0, derivation(DE.t, DE).degree(DE.t) - 1) C = max(p.degree(DE.t), q.degree(DE.t)) if q.degree(DE.t) > B: eqs = [p.nth(i) - c1 * q.nth(i) for i in range(B + 1, C + 1)] s = solve(eqs, c1) if not s or not s[c1].is_Rational: # deg(q) > B, no solution for c. return None N, M = s[c1].as_numer_denom() # N and M are integers N, M = Poly(N, DE.t), Poly(M, DE.t) nfmwa = N * fa * wd - M * wa * fd nfmwd = fd * wd Qv = is_log_deriv_k_t_radical_in_field(N * fa * wd - M * wa * fd, fd * wd, DE, 'auto') if Qv is None: # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. return None Q, e, v = Qv if e != 1: return None if Q.is_zero or v.is_zero: return None return (Q * N, Q * M, v) if p.degree(DE.t) > B: return None c = lcm(fd.as_poly(DE.t).LC(), wd.as_poly(DE.t).LC()) l = fd.monic().lcm(wd.monic()) * Poly(c, DE.t) ln, ls = splitfactor(l, DE) z = ls * ln.gcd(ln.diff(DE.t)) if not z.has(DE.t): raise NotImplementedError("parametric_log_deriv_heu() " "heuristic failed: z in k.") u1, r1 = (fa * l.quo(fd)).div(z) # (l*f).div(z) u2, r2 = (wa * l.quo(wd)).div(z) # (l*w).div(z) eqs = [r1.nth(i) - c1 * r2.nth(i) for i in range(z.degree(DE.t))] s = solve(eqs, c1) if not s or not s[c1].is_Rational: # deg(q) <= B, no solution for c. return None M, N = s[c1].as_numer_denom() nfmwa = N.as_poly(DE.t) * fa * wd - M.as_poly(DE.t) * wa * fd nfmwd = fd * wd Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE) if Qv is None: # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. return None Q, v = Qv if Q.is_zero or v.is_zero: return None return (Q * N, Q * M, v)
def test_Poly(): assert str(Poly(0, x)) == "Poly(0, x, domain='ZZ')" assert str(Poly(1, x)) == "Poly(1, x, domain='ZZ')" assert str(Poly(x, x)) == "Poly(x, x, domain='ZZ')" assert str(Poly(2 * x + 1, x)) == "Poly(2*x + 1, x, domain='ZZ')" assert str(Poly(2 * x - 1, x)) == "Poly(2*x - 1, x, domain='ZZ')" assert str(Poly(-1, x)) == "Poly(-1, x, domain='ZZ')" assert str(Poly(-x, x)) == "Poly(-x, x, domain='ZZ')" assert str(Poly(-2 * x + 1, x)) == "Poly(-2*x + 1, x, domain='ZZ')" assert str(Poly(-2 * x - 1, x)) == "Poly(-2*x - 1, x, domain='ZZ')" assert str(Poly(x - 1, x)) == "Poly(x - 1, x, domain='ZZ')" assert str(Poly(2 * x + x**5, x)) == "Poly(x**5 + 2*x, x, domain='ZZ')" assert str(Poly(3**(2 * x), 3**x)) == "Poly((3**x)**2, 3**x, domain='ZZ')" assert str(Poly((x**2)**x)) == "Poly(((x**2)**x), (x**2)**x, domain='ZZ')" assert str(Poly((x + y)**3, (x + y), expand=False)) == "Poly((x + y)**3, x + y, domain='ZZ')" assert str(Poly((x - 1)**2, (x - 1), expand=False)) == "Poly((x - 1)**2, x - 1, domain='ZZ')" assert str(Poly(x**2 + 1 + y, x)) == "Poly(x**2 + y + 1, x, domain='ZZ[y]')" assert str(Poly(x**2 - 1 + y, x)) == "Poly(x**2 + y - 1, x, domain='ZZ[y]')" assert str(Poly(x**2 + I * x, x)) == "Poly(x**2 + I*x, x, domain='EX')" assert str(Poly(x**2 - I * x, x)) == "Poly(x**2 - I*x, x, domain='EX')" assert str(Poly(-x * y * z + x * y - 1, x, y, z)) == "Poly(-x*y*z + x*y - 1, x, y, z, domain='ZZ')" 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 - 2*x*z + (w + 1)*z**3 + 1, x, y, z, domain='ZZ[w]')" assert str(Poly(x**2 + 1, x, modulus=2)) == "Poly(x**2 + 1, x, modulus=2)" assert str(Poly(2 * x**2 + 3 * x + 4, x, modulus=17)) == "Poly(2*x**2 + 3*x + 4, x, modulus=17)"
def is_deriv_k(fa, fd, DE): """ Checks if Df/f is the derivative of an element of k(t). a in k(t) is the derivative of an element of k(t) if there exists b in k(t) such that a = Db. Either returns (ans, u), such that Df/f == Du, or None, which means that Df/f is not the derivative of an element of k(t). ans is a list of tuples such that Add(*[i*j for i, j in ans]) == u. This is useful for seeing exactly which elements of k(t) produce u. This function uses the structure theorem approach, which says that for any f in K, Df/f is the derivative of a element of K if and only if there are ri in QQ such that:: --- --- Dt \ r * Dt + \ r * i Df / i i / i --- = --. --- --- t f i in L i in E i K/C(x) K/C(x) Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of hyperexponential monomials of K over C(x)). If K is an elementary extension over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the transcendence degree of K over C(x). Furthermore, because Const_D(K) == Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x) and L_K/C(x) are disjoint. The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed recursively using this same function. Therefore, it is required to pass them as indices to D (or T). E_args are the arguments of the hyperexponentials indexed by E_K (i.e., if i is in E_K, then T[i] == exp(E_args[i])). This is needed to compute the final answer u such that Df/f == Du. log(f) will be the same as u up to a additive constant. This is because they will both behave the same as monomials. For example, both log(x) and log(2*x) == log(x) + log(2) satisfy Dt == 1/x, because log(2) is constant. Therefore, the term const is returned. const is such that log(const) + f == u. This is calculated by dividing the arguments of one logarithm from the other. Therefore, it is necessary to pass the arguments of the logarithmic terms in L_args. To handle the case where we are given Df/f, not f, use is_deriv_k_in_field(). """ # Compute Df/f dfa, dfd = fd * (fd * derivation(fa, DE) - fa * derivation(fd, DE)), fd**2 * fa dfa, dfd = dfa.cancel(dfd, include=True) # Our assumption here is that each monomial is recursively transcendental if len(DE.L_K) + len(DE.E_K) != len(DE.D) - 1: if [i for i in DE.cases if i == 'tan'] or \ set([i for i in DE.cases if i == 'primitive']) - set(DE.L_K): raise NotImplementedError( "Real version of the structure " "theorems with hypertangent support is not yet implemented.") # TODO: What should really be done in this case? raise NotImplementedError("Nonelementary extensions not supported " "in the structure theorems.") E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.E_K] L_part = [DE.D[i].as_expr() for i in DE.L_K] lhs = Matrix([E_part + L_part]) rhs = Matrix([dfa.as_expr() / dfd.as_expr()]) A, u = constant_system(lhs, rhs, DE) if not all(derivation(i, DE, basic=True).is_zero for i in u) or not A: # If the elements of u are not all constant # Note: See comment in constant_system # Also note: derivation(basic=True) calls cancel() return None else: if not all(i.is_Rational for i in u): raise NotImplementedError("Cannot work with non-rational " "coefficients in this case.") else: terms = DE.E_args + [DE.T[i] for i in DE.L_K] ans = list(zip(terms, u)) result = Add(*[Mul(i, j) for i, j in ans]) argterms = [DE.T[i] for i in DE.E_K] + DE.L_args l = [] for i, j in zip(argterms, u): # We need to get around things like sqrt(x**2) != x # and also sqrt(x**2 + 2*x + 1) != x + 1 icoeff, iterms = sqf_list(i) l.append( Mul(*([Pow(icoeff, j)] + [Pow(b, e * j) for b, e in iterms]))) const = cancel(fa.as_expr() / fd.as_expr() / Mul(*l)) return (ans, result, const)
def test_Poly(): assert str(Poly(0, x)) == "Poly(0, x, domain='ZZ')" assert str(Poly(1, x)) == "Poly(1, x, domain='ZZ')" assert str(Poly(x, x)) == "Poly(x, x, domain='ZZ')" assert str(Poly(2 * x + 1, x)) == "Poly(2*x + 1, x, domain='ZZ')" assert str(Poly(2 * x - 1, x)) == "Poly(2*x - 1, x, domain='ZZ')" assert str(Poly(-1, x)) == "Poly(-1, x, domain='ZZ')" assert str(Poly(-x, x)) == "Poly(-x, x, domain='ZZ')" assert str(Poly(-2 * x + 1, x)) == "Poly(-2*x + 1, x, domain='ZZ')" assert str(Poly(-2 * x - 1, x)) == "Poly(-2*x - 1, x, domain='ZZ')" assert str(Poly(x - 1, x)) == "Poly(x - 1, x, domain='ZZ')" assert str(Poly(x**2 + 1 + y, x)) == "Poly(x**2 + y + 1, x, domain='ZZ[y]')" assert str(Poly(x**2 - 1 + y, x)) == "Poly(x**2 + y - 1, x, domain='ZZ[y]')" assert str(Poly(x**2 + I * x, x)) == "Poly(x**2 + I*x, x, domain='EX')" assert str(Poly(x**2 - I * x, x)) == "Poly(x**2 - I*x, x, domain='EX')" assert str(Poly(-x * y * z + x * y - 1, x, y, z)) == "Poly(-x*y*z + x*y - 1, x, y, z, domain='ZZ')" 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 - 2*x*z + (w + 1)*z**3 + 1, x, y, z, domain='ZZ[w]')" assert str(Poly(x**2 + 1, x, modulus=2)) == "Poly(x**2 + 1, x, modulus=2)" assert str(Poly(2 * x**2 + 3 * x + 4, x, modulus=17)) == "Poly(2*x**2 + 3*x + 4, x, modulus=17)"
def is_log_deriv_k_t_radical(fa, fd, DE, Df=True): """ Checks if Df is the logarithmic derivative of a k(t)-radical. b in k(t) can be written as the logarithmic derivative of a k(t) radical if there exist n in ZZ and u in k(t) with n, u != 0 such that n*b == Du/u. Either returns (ans, u, n, const) or None, which means that Df cannot be written as the logarithmic derivative of a k(t)-radical. ans is a list of tuples such that Mul(*[i**j for i, j in ans]) == u. This is useful for seeing exactly what elements of k(t) produce u. This function uses the structure theorem approach, which says that for any f in K, Df is the logarithmic derivative of a K-radical if and only if there are ri in QQ such that:: --- --- Dt \ r * Dt + \ r * i / i i / i --- = Df. --- --- t i in L i in E i K/C(x) K/C(x) Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of hyperexponential monomials of K over C(x)). If K is an elementary extension over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the transcendence degree of K over C(x). Furthermore, because Const_D(K) == Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x) and L_K/C(x) are disjoint. The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed recursively using this same function. Therefore, it is required to pass them as indices to D (or T). L_args are the arguments of the logarithms indexed by L_K (i.e., if i is in L_K, then T[i] == log(L_args[i])). This is needed to compute the final answer u such that n*f == Du/u. exp(f) will be the same as u up to a multiplicative constant. This is because they will both behave the same as monomials. For example, both exp(x) and exp(x + 1) == E*exp(x) satisfy Dt == t. Therefore, the term const is returned. const is such that exp(const)*f == u. This is calculated by subtracting the arguments of one exponential from the other. Therefore, it is necessary to pass the arguments of the exponential terms in E_args. To handle the case where we are given Df, not f, use is_log_deriv_k_t_radical_in_field(). """ H = [] if Df: dfa, dfd = (fd * derivation(fa, DE) - fa * derivation(fd, DE)).cancel( fd**2, include=True) else: dfa, dfd = fa, fd # Our assumption here is that each monomial is recursively transcendental if len(DE.L_K) + len(DE.E_K) != len(DE.D) - 1: if [i for i in DE.cases if i == 'tan'] or \ set([i for i in DE.cases if i == 'primitive']) - set(DE.L_K): raise NotImplementedError( "Real version of the structure " "theorems with hypertangent support is not yet implemented.") # TODO: What should really be done in this case? raise NotImplementedError("Nonelementary extensions not supported " "in the structure theorems.") E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.E_K] L_part = [DE.D[i].as_expr() for i in DE.L_K] lhs = Matrix([E_part + L_part]) rhs = Matrix([dfa.as_expr() / dfd.as_expr()]) A, u = constant_system(lhs, rhs, DE) if not all(derivation(i, DE, basic=True).is_zero for i in u) or not A: # If the elements of u are not all constant # Note: See comment in constant_system # Also note: derivation(basic=True) calls cancel() return None else: if not all(i.is_Rational for i in u): # TODO: But maybe we can tell if they're not rational, like # log(2)/log(3). Also, there should be an option to continue # anyway, even if the result might potentially be wrong. raise NotImplementedError("Cannot work with non-rational " "coefficients in this case.") else: n = reduce(ilcm, [i.as_numer_denom()[1] for i in u]) u *= n terms = [DE.T[i] for i in DE.E_K] + DE.L_args ans = list(zip(terms, u)) result = Mul(*[Pow(i, j) for i, j in ans]) # exp(f) will be the same as result up to a multiplicative # constant. We now find the log of that constant. argterms = DE.E_args + [DE.T[i] for i in DE.L_K] const = cancel(fa.as_expr() / fd.as_expr() - Add(*[Mul(i, j / n) for i, j in zip(argterms, u)])) return (ans, result, n, const)
def _get_const_characteristic_eq_sols(r, func, order): r""" Returns the roots of characteristic equation of constant coefficient linear ODE and list of collectterms which is later on used by simplification to use collect on solution. The parameter `r` is a dict of order:coeff terms, where order is the order of the derivative on each term, and coeff is the coefficient of that derivative. """ x = func.args[0] # First, set up characteristic equation. chareq, symbol = S.Zero, Dummy('x') for i in r.keys(): if type(i) == str or i < 0: pass else: chareq += r[i] * symbol**i chareq = Poly(chareq, symbol) # Can't just call roots because it doesn't return rootof for unsolveable # polynomials. chareqroots = roots(chareq, multiple=True) if len(chareqroots) != order: chareqroots = [rootof(chareq, k) for k in range(chareq.degree())] chareq_is_complex = not all(i.is_real for i in chareq.all_coeffs()) # Create a dict root: multiplicity or charroots charroots = defaultdict(int) for root in chareqroots: charroots[root] += 1 # We need to keep track of terms so we can run collect() at the end. # This is necessary for constantsimp to work properly. collectterms = [] gensols = [] conjugate_roots = [] # used to prevent double-use of conjugate roots # Loop over roots in theorder provided by roots/rootof... for root in chareqroots: # but don't repoeat multiple roots. if root not in charroots: continue multiplicity = charroots.pop(root) for i in range(multiplicity): if chareq_is_complex: gensols.append(x**i * exp(root * x)) collectterms = [(i, root, 0)] + collectterms continue reroot = re(root) imroot = im(root) if imroot.has(atan2) and reroot.has(atan2): # Remove this condition when re and im stop returning # circular atan2 usages. gensols.append(x**i * exp(root * x)) collectterms = [(i, root, 0)] + collectterms else: if root in conjugate_roots: collectterms = [(i, reroot, imroot)] + collectterms continue if imroot == 0: gensols.append(x**i * exp(reroot * x)) collectterms = [(i, reroot, 0)] + collectterms continue conjugate_roots.append(conjugate(root)) gensols.append(x**i * exp(reroot * x) * sin(abs(imroot) * x)) gensols.append(x**i * exp(reroot * x) * cos(imroot * x)) # This ordering is important collectterms = [(i, reroot, imroot)] + collectterms return gensols, collectterms
def trigsimp_groebner(expr, hints=[], quick=False, order="grlex", polynomial=False): """ Simplify trigonometric expressions using a groebner basis algorithm. Explanation =========== This routine takes a fraction involving trigonometric or hyperbolic expressions, and tries to simplify it. The primary metric is the total degree. Some attempts are made to choose the simplest possible expression of the minimal degree, but this is non-rigorous, and also very slow (see the ``quick=True`` option). If ``polynomial`` is set to True, instead of simplifying numerator and denominator together, this function just brings numerator and denominator into a canonical form. This is much faster, but has potentially worse results. However, if the input is a polynomial, then the result is guaranteed to be an equivalent polynomial of minimal degree. The most important option is hints. Its entries can be any of the following: - a natural number - a function - an iterable of the form (func, var1, var2, ...) - anything else, interpreted as a generator A number is used to indicate that the search space should be increased. A function is used to indicate that said function is likely to occur in a simplified expression. An iterable is used indicate that func(var1 + var2 + ...) is likely to occur in a simplified . An additional generator also indicates that it is likely to occur. (See examples below). This routine carries out various computationally intensive algorithms. The option ``quick=True`` can be used to suppress one particularly slow step (at the expense of potentially more complicated results, but never at the expense of increased total degree). Examples ======== >>> from sympy.abc import x, y >>> from sympy import sin, tan, cos, sinh, cosh, tanh >>> from sympy.simplify.trigsimp import trigsimp_groebner Suppose you want to simplify ``sin(x)*cos(x)``. Naively, nothing happens: >>> ex = sin(x)*cos(x) >>> trigsimp_groebner(ex) sin(x)*cos(x) This is because ``trigsimp_groebner`` only looks for a simplification involving just ``sin(x)`` and ``cos(x)``. You can tell it to also try ``2*x`` by passing ``hints=[2]``: >>> trigsimp_groebner(ex, hints=[2]) sin(2*x)/2 >>> trigsimp_groebner(sin(x)**2 - cos(x)**2, hints=[2]) -cos(2*x) Increasing the search space this way can quickly become expensive. A much faster way is to give a specific expression that is likely to occur: >>> trigsimp_groebner(ex, hints=[sin(2*x)]) sin(2*x)/2 Hyperbolic expressions are similarly supported: >>> trigsimp_groebner(sinh(2*x)/sinh(x)) 2*cosh(x) Note how no hints had to be passed, since the expression already involved ``2*x``. The tangent function is also supported. You can either pass ``tan`` in the hints, to indicate that tan should be tried whenever cosine or sine are, or you can pass a specific generator: >>> trigsimp_groebner(sin(x)/cos(x), hints=[tan]) tan(x) >>> trigsimp_groebner(sinh(x)/cosh(x), hints=[tanh(x)]) tanh(x) Finally, you can use the iterable form to suggest that angle sum formulae should be tried: >>> ex = (tan(x) + tan(y))/(1 - tan(x)*tan(y)) >>> trigsimp_groebner(ex, hints=[(tan, x, y)]) tan(x + y) """ # TODO # - preprocess by replacing everything by funcs we can handle # - optionally use cot instead of tan # - more intelligent hinting. # For example, if the ideal is small, and we have sin(x), sin(y), # add sin(x + y) automatically... ? # - algebraic numbers ... # - expressions of lowest degree are not distinguished properly # e.g. 1 - sin(x)**2 # - we could try to order the generators intelligently, so as to influence # which monomials appear in the quotient basis # THEORY # ------ # Ratsimpmodprime above can be used to "simplify" a rational function # modulo a prime ideal. "Simplify" mainly means finding an equivalent # expression of lower total degree. # # We intend to use this to simplify trigonometric functions. To do that, # we need to decide (a) which ring to use, and (b) modulo which ideal to # simplify. In practice, (a) means settling on a list of "generators" # a, b, c, ..., such that the fraction we want to simplify is a rational # function in a, b, c, ..., with coefficients in ZZ (integers). # (2) means that we have to decide what relations to impose on the # generators. There are two practical problems: # (1) The ideal has to be *prime* (a technical term). # (2) The relations have to be polynomials in the generators. # # We typically have two kinds of generators: # - trigonometric expressions, like sin(x), cos(5*x), etc # - "everything else", like gamma(x), pi, etc. # # Since this function is trigsimp, we will concentrate on what to do with # trigonometric expressions. We can also simplify hyperbolic expressions, # but the extensions should be clear. # # One crucial point is that all *other* generators really should behave # like indeterminates. In particular if (say) "I" is one of them, then # in fact I**2 + 1 = 0 and we may and will compute non-sensical # expressions. However, we can work with a dummy and add the relation # I**2 + 1 = 0 to our ideal, then substitute back in the end. # # Now regarding trigonometric generators. We split them into groups, # according to the argument of the trigonometric functions. We want to # organise this in such a way that most trigonometric identities apply in # the same group. For example, given sin(x), cos(2*x) and cos(y), we would # group as [sin(x), cos(2*x)] and [cos(y)]. # # Our prime ideal will be built in three steps: # (1) For each group, compute a "geometrically prime" ideal of relations. # Geometrically prime means that it generates a prime ideal in # CC[gens], not just ZZ[gens]. # (2) Take the union of all the generators of the ideals for all groups. # By the geometric primality condition, this is still prime. # (3) Add further inter-group relations which preserve primality. # # Step (1) works as follows. We will isolate common factors in the # argument, so that all our generators are of the form sin(n*x), cos(n*x) # or tan(n*x), with n an integer. Suppose first there are no tan terms. # The ideal [sin(x)**2 + cos(x)**2 - 1] is geometrically prime, since # X**2 + Y**2 - 1 is irreducible over CC. # Now, if we have a generator sin(n*x), than we can, using trig identities, # express sin(n*x) as a polynomial in sin(x) and cos(x). We can add this # relation to the ideal, preserving geometric primality, since the quotient # ring is unchanged. # Thus we have treated all sin and cos terms. # For tan(n*x), we add a relation tan(n*x)*cos(n*x) - sin(n*x) = 0. # (This requires of course that we already have relations for cos(n*x) and # sin(n*x).) It is not obvious, but it seems that this preserves geometric # primality. # XXX A real proof would be nice. HELP! # Sketch that <S**2 + C**2 - 1, C*T - S> is a prime ideal of # CC[S, C, T]: # - it suffices to show that the projective closure in CP**3 is # irreducible # - using the half-angle substitutions, we can express sin(x), tan(x), # cos(x) as rational functions in tan(x/2) # - from this, we get a rational map from CP**1 to our curve # - this is a morphism, hence the curve is prime # # Step (2) is trivial. # # Step (3) works by adding selected relations of the form # sin(x + y) - sin(x)*cos(y) - sin(y)*cos(x), etc. Geometric primality is # preserved by the same argument as before. def parse_hints(hints): """Split hints into (n, funcs, iterables, gens).""" n = 1 funcs, iterables, gens = [], [], [] for e in hints: if isinstance(e, (SYMPY_INTS, Integer)): n = e elif isinstance(e, FunctionClass): funcs.append(e) elif iterable(e): iterables.append((e[0], e[1:])) # XXX sin(x+2y)? # Note: we go through polys so e.g. # sin(-x) -> -sin(x) -> sin(x) gens.extend( parallel_poly_from_expr([e[0](x) for x in e[1:]] + [e[0](Add(*e[1:]))])[1].gens) else: gens.append(e) return n, funcs, iterables, gens def build_ideal(x, terms): """ Build generators for our ideal. ``Terms`` is an iterable with elements of the form (fn, coeff), indicating that we have a generator fn(coeff*x). If any of the terms is trigonometric, sin(x) and cos(x) are guaranteed to appear in terms. Similarly for hyperbolic functions. For tan(n*x), sin(n*x) and cos(n*x) are guaranteed. """ I = [] y = Dummy('y') for fn, coeff in terms: for c, s, t, rel in ([cos, sin, tan, cos(x)**2 + sin(x)**2 - 1], [ cosh, sinh, tanh, cosh(x)**2 - sinh(x)**2 - 1 ]): if coeff == 1 and fn in [c, s]: I.append(rel) elif fn == t: I.append(t(coeff * x) * c(coeff * x) - s(coeff * x)) elif fn in [c, s]: cn = fn(coeff * y).expand(trig=True).subs(y, x) I.append(fn(coeff * x) - cn) return list(set(I)) def analyse_gens(gens, hints): """ Analyse the generators ``gens``, using the hints ``hints``. The meaning of ``hints`` is described in the main docstring. Return a new list of generators, and also the ideal we should work with. """ # First parse the hints n, funcs, iterables, extragens = parse_hints(hints) debug('n=%s' % n, 'funcs:', funcs, 'iterables:', iterables, 'extragens:', extragens) # We just add the extragens to gens and analyse them as before gens = list(gens) gens.extend(extragens) # remove duplicates funcs = list(set(funcs)) iterables = list(set(iterables)) gens = list(set(gens)) # all the functions we can do anything with allfuncs = {sin, cos, tan, sinh, cosh, tanh} # sin(3*x) -> ((3, x), sin) trigterms = [(g.args[0].as_coeff_mul(), g.func) for g in gens if g.func in allfuncs] # Our list of new generators - start with anything that we cannot # work with (i.e. is not a trigonometric term) freegens = [g for g in gens if g.func not in allfuncs] newgens = [] trigdict = {} for (coeff, var), fn in trigterms: trigdict.setdefault(var, []).append((coeff, fn)) res = [] # the ideal for key, val in trigdict.items(): # We have now assembeled a dictionary. Its keys are common # arguments in trigonometric expressions, and values are lists of # pairs (fn, coeff). x0, (fn, coeff) in trigdict means that we # need to deal with fn(coeff*x0). We take the rational gcd of the # coeffs, call it ``gcd``. We then use x = x0/gcd as "base symbol", # all other arguments are integral multiples thereof. # We will build an ideal which works with sin(x), cos(x). # If hint tan is provided, also work with tan(x). Moreover, if # n > 1, also work with sin(k*x) for k <= n, and similarly for cos # (and tan if the hint is provided). Finally, any generators which # the ideal does not work with but we need to accommodate (either # because it was in expr or because it was provided as a hint) # we also build into the ideal. # This selection process is expressed in the list ``terms``. # build_ideal then generates the actual relations in our ideal, # from this list. fns = [x[1] for x in val] val = [x[0] for x in val] gcd = reduce(igcd, val) terms = [(fn, v / gcd) for (fn, v) in zip(fns, val)] fs = set(funcs + fns) for c, s, t in ([cos, sin, tan], [cosh, sinh, tanh]): if any(x in fs for x in (c, s, t)): fs.add(c) fs.add(s) for fn in fs: for k in range(1, n + 1): terms.append((fn, k)) extra = [] for fn, v in terms: if fn == tan: extra.append((sin, v)) extra.append((cos, v)) if fn in [sin, cos] and tan in fs: extra.append((tan, v)) if fn == tanh: extra.append((sinh, v)) extra.append((cosh, v)) if fn in [sinh, cosh] and tanh in fs: extra.append((tanh, v)) terms.extend(extra) x = gcd * Mul(*key) r = build_ideal(x, terms) res.extend(r) newgens.extend({fn(v * x) for fn, v in terms}) # Add generators for compound expressions from iterables for fn, args in iterables: if fn == tan: # Tan expressions are recovered from sin and cos. iterables.extend([(sin, args), (cos, args)]) elif fn == tanh: # Tanh expressions are recovered from sihn and cosh. iterables.extend([(sinh, args), (cosh, args)]) else: dummys = symbols('d:%i' % len(args), cls=Dummy) expr = fn(Add(*dummys)).expand(trig=True).subs( list(zip(dummys, args))) res.append(fn(Add(*args)) - expr) if myI in gens: res.append(myI**2 + 1) freegens.remove(myI) newgens.append(myI) return res, freegens, newgens myI = Dummy('I') expr = expr.subs(S.ImaginaryUnit, myI) subs = [(myI, S.ImaginaryUnit)] num, denom = cancel(expr).as_numer_denom() try: (pnum, pdenom), opt = parallel_poly_from_expr([num, denom]) except PolificationFailed: return expr debug('initial gens:', opt.gens) ideal, freegens, gens = analyse_gens(opt.gens, hints) debug('ideal:', ideal) debug('new gens:', gens, " -- len", len(gens)) debug('free gens:', freegens, " -- len", len(gens)) # NOTE we force the domain to be ZZ to stop polys from injecting generators # (which is usually a sign of a bug in the way we build the ideal) if not gens: return expr G = groebner(ideal, order=order, gens=gens, domain=ZZ) debug('groebner basis:', list(G), " -- len", len(G)) # If our fraction is a polynomial in the free generators, simplify all # coefficients separately: from sympy.simplify.ratsimp import ratsimpmodprime if freegens and pdenom.has_only_gens(*set(gens).intersection(pdenom.gens)): num = Poly(num, gens=gens + freegens).eject(*gens) res = [] for monom, coeff in num.terms(): ourgens = set(parallel_poly_from_expr([coeff, denom])[1].gens) # We compute the transitive closure of all generators that can # be reached from our generators through relations in the ideal. changed = True while changed: changed = False for p in ideal: p = Poly(p) if not ourgens.issuperset(p.gens) and \ not p.has_only_gens(*set(p.gens).difference(ourgens)): changed = True ourgens.update(p.exclude().gens) # NOTE preserve order! realgens = [x for x in gens if x in ourgens] # The generators of the ideal have now been (implicitly) split # into two groups: those involving ourgens and those that don't. # Since we took the transitive closure above, these two groups # live in subgrings generated by a *disjoint* set of variables. # Any sensible groebner basis algorithm will preserve this disjoint # structure (i.e. the elements of the groebner basis can be split # similarly), and and the two subsets of the groebner basis then # form groebner bases by themselves. (For the smaller generating # sets, of course.) ourG = [ g.as_expr() for g in G.polys if g.has_only_gens(*ourgens.intersection(g.gens)) ] res.append(Mul(*[a**b for a, b in zip(freegens, monom)]) * \ ratsimpmodprime(coeff/denom, ourG, order=order, gens=realgens, quick=quick, domain=ZZ, polynomial=polynomial).subs(subs)) return Add(*res) # NOTE The following is simpler and has less assumptions on the # groebner basis algorithm. If the above turns out to be broken, # use this. return Add(*[Mul(*[a**b for a, b in zip(freegens, monom)]) * \ ratsimpmodprime(coeff/denom, list(G), order=order, gens=gens, quick=quick, domain=ZZ) for monom, coeff in num.terms()]) else: return ratsimpmodprime(expr, list(G), order=order, gens=freegens + gens, quick=quick, domain=ZZ, polynomial=polynomial).subs(subs)
def test_ExtensionElement(): A = FiniteExtension(Poly(x**2 + 1, x)) assert srepr(A.generator) == \ "ExtElem(DMP([1, 0], ZZ, ring=GlobalPolynomialRing(ZZ, Symbol('x'))), FiniteExtension(Poly(x**2 + 1, x, domain='ZZ')))"
def test_roots_linear(): assert roots_linear(Poly(2 * x + 1, x)) == [-Rational(1, 2)]
def test_FiniteExtension(): assert srepr(FiniteExtension(Poly(x**2 + 1, x))) == \ "FiniteExtension(Poly(x**2 + 1, x, domain='ZZ'))"
def gosper_normal(f, g, n, polys=True): r""" Compute the Gosper's normal form of ``f`` and ``g``. Given relatively prime univariate polynomials ``f`` and ``g``, rewrite their quotient to a normal form defined as follows:: .. math:: \frac{f(n)}{g(n)} = Z \cdot \frac{A(n) C(n+1)}{B(n) C(n)} where ``Z`` is an arbitrary constant and ``A``, ``B``, ``C`` are monic polynomials in ``n`` with the following properties: 1. `\gcd(A(n), B(n+h)) = 1 \forall h \in \mathbb{N}` 2. `\gcd(B(n), C(n+1)) = 1` 3. `\gcd(A(n), C(n)) = 1` This normal form, or rational factorization in other words, is a crucial step in Gosper's algorithm and in solving of difference equations. It can be also used to decide if two hypergeometric terms are similar or not. This procedure will return a tuple containing elements of this factorization in the form ``(Z*A, B, C)``. **Examples** >>> from sympy.concrete.gosper import gosper_normal >>> from sympy.abc import n >>> gosper_normal(4*n+5, 2*(4*n+1)*(2*n+3), n, polys=False) (1/4, n + 3/2, n + 1/4) """ (p, q), opt = parallel_poly_from_expr((f, g), n, field=True, extension=True) a, A = p.LC(), p.monic() b, B = q.LC(), q.monic() C, Z = A.one, a / b h = Dummy('h') D = Poly(n + h, n, h, domain=opt.domain) R = A.resultant(B.compose(D)) roots = set(R.ground_roots().keys()) for r in set(roots): if not r.is_Integer or r < 0: roots.remove(r) for i in sorted(roots): d = A.gcd(B.shift(+i)) A = A.quo(d) B = B.quo(d.shift(-i)) for j in xrange(1, i + 1): C *= d.shift(-j) A = A.mul_ground(Z) if not polys: A = A.as_expr() B = B.as_expr() C = C.as_expr() return A, B, C