def solve_biquadratic(f, g, opt): """Solve a system of two bivariate quadratic polynomial equations. Examples ======== >>> from diofant.polys import Options, Poly >>> from diofant.abc import x, y >>> from diofant.solvers.polysys import solve_biquadratic >>> NewOption = Options((x, y), {'domain': 'ZZ'}) >>> a = Poly(y**2 - 4 + x, y, x, domain='ZZ') >>> b = Poly(y*2 + 3*x - 7, y, x, domain='ZZ') >>> solve_biquadratic(a, b, NewOption) [(1/3, 3), (41/27, 11/9)] >>> a = Poly(y + x**2 - 3, y, x, domain='ZZ') >>> b = Poly(-y + x - 4, y, x, domain='ZZ') >>> solve_biquadratic(a, b, NewOption) [(-sqrt(29)/2 + 7/2, -sqrt(29)/2 - 1/2), (sqrt(29)/2 + 7/2, -1/2 + \ sqrt(29)/2)] """ G = groebner([f, g]) if len(G) == 1 and G[0].is_ground: return if len(G) != 2: raise SolveFailed p, q = G x, y = opt.gens p = Poly(p, x, expand=False) q = q.ltrim(-1) p_roots = [ rcollect(expr, y) for expr in roots(p).keys() ] q_roots = list(roots(q).keys()) solutions = [] for q_root in q_roots: for p_root in p_roots: solution = (p_root.subs(y, q_root), q_root) solutions.append(solution) return sorted(solutions, key=default_sort_key)
def test_Domain__algebraic_field(): alg = ZZ.algebraic_field(sqrt(3)) assert alg.minpoly == Poly(x**2 - 3) assert alg.domain == QQ assert alg.from_expr(sqrt(3)).denominator == 1 assert alg.from_expr(2 * sqrt(3)).denominator == 1 assert alg.from_expr(sqrt(3) / 2).denominator == 2 assert alg([QQ(7, 38), QQ(3, 2)]).denominator == 38 alg = QQ.algebraic_field(sqrt(2)) assert alg.minpoly == Poly(x**2 - 2) assert alg.domain == QQ alg = QQ.algebraic_field(sqrt(2), sqrt(3)) assert alg.minpoly == Poly(x**4 - 10 * x**2 + 1) assert alg.domain == QQ assert alg.is_nonpositive(alg([-1, 1])) is True assert alg.is_nonnegative(alg([2, -1])) is True assert alg(1).numerator == alg(1) assert alg.from_expr(sqrt(3) / 2).numerator == alg.from_expr(2 * sqrt(3)) assert alg.from_expr(sqrt(3) / 2).denominator == 4 pytest.raises(DomainError, lambda: AlgebraicField(ZZ, sqrt(2))) assert alg.characteristic == 0 assert alg.is_RealAlgebraicField is True assert int(alg(2)) == 2 assert int(alg.from_expr(Rational(3, 2))) == 1 pytest.raises(TypeError, lambda: int(alg([1, 1]))) alg = QQ.algebraic_field(I) assert alg.algebraic_field(I) == alg assert alg.is_RealAlgebraicField is False alg = QQ.algebraic_field(sqrt(2)).algebraic_field(sqrt(3)) assert alg.minpoly == Poly(x**2 - 3, x, domain=QQ.algebraic_field(sqrt(2))) # issue sympy/sympy#14476 assert QQ.algebraic_field(Rational(1, 7)) is QQ alg = QQ.algebraic_field(sqrt(2)).algebraic_field(I) assert alg.from_expr(2 * sqrt(2) + I / 3) == alg( [alg.domain(1) / 3, alg.domain(2 * sqrt(2))]) alg2 = QQ.algebraic_field(sqrt(2)) assert alg2.from_expr(sqrt(2)) == alg2.convert(alg.from_expr(sqrt(2))) eq = -x**3 + 2 * x**2 + 3 * x - 2 rs = roots(eq, multiple=True) alg = QQ.algebraic_field(rs[0]) assert alg.ext_root == RootOf(eq, 2) alg1 = QQ.algebraic_field(I) alg2 = QQ.algebraic_field(sqrt(2)).algebraic_field(I) assert alg1 != alg2
def _solve_reduced_system(system, gens): """Recursively solves reduced polynomial systems. """ basis = groebner(system, gens, polys=True) if len(basis) == 1 and basis[0].is_ground: return univariate = list(filter(_is_univariate, basis)) if len(univariate) == 1: f = univariate.pop() else: raise NotImplementedError("only zero-dimensional systems " "supported (finite number of solutions)") gens = f.gens gen = gens[-1] zeros = list(roots(f.ltrim(gen)).keys()) if len(basis) == 1: return [ (zero,) for zero in zeros ] solutions = [] for zero in zeros: new_system = [] new_gens = gens[:-1] for b in basis[:-1]: eq = _subs_root(b, gen, zero) if eq is not S.Zero: new_system.append(eq) for solution in _solve_reduced_system(new_system, new_gens): solutions.append(solution + (zero,)) return solutions
def _eval_product(self, term, limits): from diofant.concrete.delta import deltaproduct, _has_simple_delta from diofant.concrete.summations import summation from diofant.functions import KroneckerDelta, RisingFactorial (k, a, n) = limits if k not in term.free_symbols: if (term - 1).is_zero: return S.One return term**(n - a + 1) if a == n: return term.subs(k, a) if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]): return deltaproduct(term, limits) dif = n - a if dif.is_Integer: return Mul(*[term.subs(k, a + i) for i in range(dif + 1)]) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly) M = 0 for r, m in all_roots.items(): M += m A *= RisingFactorial(a - r, n - a + 1)**m Q *= (n - r)**m if M < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = self.func(arg, (k, a, n)).doit() return poly.LC()**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p, (k, a, n)) q = self._eval_product(q, (k, a, n)) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = self.func(arg, (k, a, n)).doit() return A * B elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base, (k, a, n)) if p is not None: return p**term.exp elif isinstance(term, Product): evaluated = term.doit() f = self._eval_product(evaluated, limits) if f is None: return self.func(evaluated, limits) else: return f
def test_Domain__algebraic_field(): alg = ZZ.algebraic_field(sqrt(3)) assert alg.minpoly == Poly(x**2 - 3) assert alg.domain == QQ assert alg.from_expr(sqrt(3)).denominator == 1 assert alg.from_expr(2 * sqrt(3)).denominator == 1 assert alg.from_expr(sqrt(3) / 2).denominator == 2 assert alg([QQ(7, 38), QQ(3, 2)]).denominator == 38 alg = QQ.algebraic_field(sqrt(2)) assert alg.minpoly == Poly(x**2 - 2) assert alg.domain == QQ alg = QQ.algebraic_field(sqrt(2), sqrt(3)) assert alg.minpoly == Poly(x**4 - 10 * x**2 + 1) assert alg.domain == QQ assert alg(1).numerator == alg(1) assert alg.from_expr(sqrt(3) / 2).numerator == alg.from_expr(2 * sqrt(3)) assert alg.from_expr(sqrt(3) / 2).denominator == 4 pytest.raises(DomainError, lambda: AlgebraicField(ZZ, sqrt(2))) assert alg.characteristic == 0 assert alg.is_RealAlgebraicField is True assert int(alg(2)) == 2 assert int(alg.from_expr(Rational(3, 2))) == 1 alg = QQ.algebraic_field(I) assert alg.algebraic_field(I) == alg assert alg.is_RealAlgebraicField is False pytest.raises(TypeError, lambda: int(alg([1, 1]))) alg = QQ.algebraic_field(sqrt(2)).algebraic_field(sqrt(3)) assert alg.minpoly == Poly(x**2 - 3, x, domain=QQ.algebraic_field(sqrt(2))) # issue sympy/sympy#14476 assert QQ.algebraic_field(Rational(1, 7)) is QQ alg = QQ.algebraic_field(sqrt(2)).algebraic_field(I) assert alg.from_expr(2 * sqrt(2) + I / 3) == alg( [alg.domain([1]) / 3, alg.domain([2, 0])]) alg2 = QQ.algebraic_field(sqrt(2)) assert alg2.from_expr(sqrt(2)) == alg2.convert(alg.from_expr(sqrt(2))) eq = -x**3 + 2 * x**2 + 3 * x - 2 rs = roots(eq, multiple=True) alg = QQ.algebraic_field(rs[0]) assert alg.is_RealAlgebraicField alg1 = QQ.algebraic_field(I) alg2 = QQ.algebraic_field(sqrt(2)).algebraic_field(I) assert alg1 != alg2 alg3 = QQ.algebraic_field(RootOf(4 * x**7 + x - 1, 0)) assert alg3.is_RealAlgebraicField assert int(alg3.unit) == 2 assert 2.772 > alg3.unit > 2.771 assert int(alg3([3, 17, 11, -1, 2])) == 622 assert int( alg3([ 1, QQ(-11, 4), QQ(125326976730518, 44208605852241), QQ(-16742151878022, 12894796053515), QQ(2331359268715, 10459004949272) ])) == 18 alg4 = QQ.algebraic_field(sqrt(2) + I) assert alg4.convert(alg2.unit) == alg4.from_expr(I)
def test_Domain__algebraic_field(): alg = ZZ.algebraic_field(sqrt(3)) assert alg.minpoly == Poly(x**2 - 3) assert alg.domain == QQ assert alg.from_expr(sqrt(3)).denominator == 1 assert alg.from_expr(2*sqrt(3)).denominator == 1 assert alg.from_expr(sqrt(3)/2).denominator == 2 assert alg([QQ(7, 38), QQ(3, 2)]).denominator == 38 alg = QQ.algebraic_field(sqrt(2)) assert alg.minpoly == Poly(x**2 - 2) assert alg.domain == QQ alg = QQ.algebraic_field(sqrt(2), sqrt(3)) assert alg.minpoly == Poly(x**4 - 10*x**2 + 1) assert alg.domain == QQ assert alg.is_nonpositive(alg([-1, 1])) is True assert alg.is_nonnegative(alg([2, -1])) is True assert alg(1).numerator == alg(1) assert alg.from_expr(sqrt(3)/2).numerator == alg.from_expr(2*sqrt(3)) assert alg.from_expr(sqrt(3)/2).denominator == 4 pytest.raises(DomainError, lambda: AlgebraicField(ZZ, sqrt(2))) assert alg.characteristic == 0 assert alg.is_RealAlgebraicField is True assert int(alg(2)) == 2 assert int(alg.from_expr(Rational(3, 2))) == 1 pytest.raises(TypeError, lambda: int(alg([1, 1]))) alg = QQ.algebraic_field(I) assert alg.algebraic_field(I) == alg assert alg.is_RealAlgebraicField is False alg = QQ.algebraic_field(sqrt(2)).algebraic_field(sqrt(3)) assert alg.minpoly == Poly(x**2 - 3, x, domain=QQ.algebraic_field(sqrt(2))) # issue sympy/sympy#14476 assert QQ.algebraic_field(Rational(1, 7)) is QQ alg = QQ.algebraic_field(sqrt(2)).algebraic_field(I) assert alg.from_expr(2*sqrt(2) + I/3) == alg([alg.domain(1)/3, alg.domain(2*sqrt(2))]) alg2 = QQ.algebraic_field(sqrt(2)) assert alg2.from_expr(sqrt(2)) == alg2.convert(alg.from_expr(sqrt(2))) eq = -x**3 + 2*x**2 + 3*x - 2 rs = roots(eq, multiple=True) alg = QQ.algebraic_field(rs[0]) assert alg.is_RealAlgebraicField alg1 = QQ.algebraic_field(I) alg2 = QQ.algebraic_field(sqrt(2)).algebraic_field(I) assert alg1 != alg2 alg3 = QQ.algebraic_field(RootOf(4*x**7 + x - 1, 0)) assert alg3.is_RealAlgebraicField assert 2.772 > alg3.unit > 2.771 alg4 = QQ.algebraic_field(sqrt(2) + I) assert alg4.convert(alg2.unit) == alg4.from_expr(I)
def rsolve_poly(coeffs, f, n, **hints): """ 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 Petkovšek. 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 diofant 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. Petkovšek, On polynomial solutions of linear operator equations, in: T. Levelt, ed., Proc. ISSAC '95, ACM Press, New York, 1995, 290-296. .. [2] M. Petkovšek, Hypergeometric solutions of linear recurrences with polynomial coefficients, J. Symbolic Computation, 14 (1992), 243-264. .. [3] M. Petkovšek, H. S. Wilf, D. Zeilberger, A = B, 1996. """ f = sympify(f) if not f.is_polynomial(n): return homogeneous = f.is_zero r = len(coeffs) - 1 coeffs = [Poly(coeff, n) for coeff in coeffs] g = gcd_list(coeffs + [f], n, polys=True) if not g.is_ground: coeffs = [quo(c, g, n, polys=False) for c in coeffs] f = quo(f, g, n, polys=False) polys = [Poly(0, n)] * (r + 1) terms = [(S.Zero, S.NegativeInfinity)] * (r + 1) for i in range(0, 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(0, 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 if N <= r: C = [] y = E = S.Zero for i in range(0, N + 1): C.append(Symbol('C' + str(i))) y += C[i] * n**i for i in range(0, 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 # 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(0, A + 1): for k in range(0, 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(0, A): v[j] += B * V[i - k, j] denom = alpha[-A].subs(x, i) for j in range(0, 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(0, A): v[j] += B * V[i - k, j] g += B * G[i - k] denom = alpha[-A].subs(x, i) for j in range(0, 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(0, 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(0, A)] def g(i): return 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 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 rsolve_hyper(coeffs, f, n, **hints): """ Given linear recurrence operator `\operatorname{L}` of order `k` with polynomial coefficients and inhomogeneous equation `\operatorname{L} y = f` we seek for all hypergeometric solutions over field `K` of characteristic zero. The inhomogeneous part can be either hypergeometric or a sum of a fixed number of pairwise dissimilar hypergeometric terms. The algorithm performs three basic steps: (1) Group together similar hypergeometric terms in the inhomogeneous part of `\operatorname{L} y = f`, and find particular solution using Abramov's algorithm. (2) Compute generating set of `\operatorname{L}` and find basis in it, so that all solutions are linearly independent. (3) Form final solution with the number of arbitrary constants equal to dimension of basis of `\operatorname{L}`. Term `a(n)` is hypergeometric if it is annihilated by first order linear difference equations with polynomial coefficients or, in simpler words, if consecutive term ratio is a rational function. The output of this procedure is a linear combination of fixed number of hypergeometric terms. However the underlying method can generate larger class of solutions - D'Alembertian terms. Note also that this method not only computes the kernel of the inhomogeneous equation, but also reduces in to a basis so that solutions generated by this procedure are linearly independent Examples ======== >>> from diofant.solvers import rsolve_hyper >>> from diofant.abc import x >>> rsolve_hyper([-1, -1, 1], 0, x) C0*(1/2 + sqrt(5)/2)**x + C1*(-sqrt(5)/2 + 1/2)**x >>> rsolve_hyper([-1, 1], 1 + x, x) C0 + x*(x + 1)/2 References ========== .. [1] M. Petkovšek, Hypergeometric solutions of linear recurrences with polynomial coefficients, J. Symbolic Computation, 14 (1992), 243-264. .. [2] M. Petkovšek, H. S. Wilf, D. Zeilberger, A = B, 1996. """ coeffs = list(map(sympify, coeffs)) f = sympify(f) r, kernel, symbols = len(coeffs) - 1, [], set() if not f.is_zero: if f.is_Add: similar = {} for g in f.expand().args: if not g.is_hypergeometric(n): return for h in similar.keys(): if hypersimilar(g, h, n): similar[h] += g break else: similar[g] = S.Zero inhomogeneous = [] for g, h in similar.items(): inhomogeneous.append(g + h) elif f.is_hypergeometric(n): inhomogeneous = [f] else: return for i, g in enumerate(inhomogeneous): coeff, polys = S.One, coeffs[:] denoms = [S.One] * (r + 1) s = hypersimp(g, n) for j in range(1, r + 1): coeff *= s.subs(n, n + j - 1) p, q = coeff.as_numer_denom() polys[j] *= p denoms[j] = q for j in range(0, r + 1): polys[j] *= Mul(*(denoms[:j] + denoms[j + 1:])) R = rsolve_ratio(polys, Mul(*denoms), n, symbols=True) if R is not None: R, syms = R if syms: R = R.subs(zip(syms, [0] * len(syms))) if R: inhomogeneous[i] *= R else: return result = Add(*inhomogeneous) result = simplify(result) else: result = S.Zero Z = Dummy('Z') p, q = coeffs[0], coeffs[r].subs(n, n - r + 1) p_factors = [z for z in roots(p, n).keys()] q_factors = [z for z in roots(q, n).keys()] factors = [(S.One, S.One)] for p in p_factors: for q in q_factors: if p.is_integer and q.is_integer and p <= q: continue else: factors += [(n - p, n - q)] p = [(n - p, S.One) for p in p_factors] q = [(S.One, n - q) for q in q_factors] factors = p + factors + q for A, B in factors: polys, degrees = [], [] D = A * B.subs(n, n + r - 1) for i in range(0, r + 1): a = Mul(*[A.subs(n, n + j) for j in range(0, i)]) b = Mul(*[B.subs(n, n + j) for j in range(i, r)]) poly = quo(coeffs[i] * a * b, D, n) polys.append(poly.as_poly(n)) if not poly.is_zero: degrees.append(polys[i].degree()) d, poly = max(degrees), S.Zero for i in range(0, r + 1): coeff = polys[i].nth(d) if coeff is not S.Zero: poly += coeff * Z**i for z in roots(poly, Z).keys(): if z.is_zero: continue (C, s) = rsolve_poly([polys[i] * z**i for i in range(r + 1)], 0, n, symbols=True) if C is not None and C is not S.Zero: symbols |= set(s) ratio = z * A * C.subs(n, n + 1) / B / C ratio = simplify(ratio) skip = max([-1] + [ v for v in roots(Mul(*ratio.as_numer_denom()), n).keys() if v.is_Integer ]) + 1 K = product(ratio, (n, skip, n - 1)) if K.has(factorial, FallingFactorial, RisingFactorial): K = simplify(K) if casoratian(kernel + [K], n, zero=False) != 0: kernel.append(K) kernel.sort(key=default_sort_key) sk = list(zip(numbered_symbols('C'), kernel)) for C, ker in sk: result += C * ker if hints.get('symbols', False): symbols |= {s for s, k in sk} return result, list(symbols) else: return result
def rsolve_ratio(coeffs, f, n, **hints): """ 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 rational solutions over field `K` of characteristic zero. This procedure accepts only polynomials, however if you are interested in solving recurrence with rational coefficients then use ``rsolve`` which will pre-process the given equation and run this procedure with polynomial arguments. The algorithm performs two basic steps: (1) Compute polynomial `v(n)` which can be used as universal denominator of any rational solution of equation `\operatorname{L} y = f`. (2) Construct new linear difference equation by substitution `y(n) = u(n)/v(n)` and solve it for `u(n)` finding all its polynomial solutions. Return ``None`` if none were found. Algorithm implemented here is a revised version of the original Abramov's algorithm, developed in 1989. The new approach is much simpler to implement and has better overall efficiency. This method can be easily adapted to q-difference equations case. Besides finding rational solutions alone, this functions is an important part of Hyper algorithm were it is used to find particular solution of inhomogeneous part of a recurrence. Examples ======== >>> from diofant.abc import x >>> from diofant.solvers.recurr import rsolve_ratio >>> rsolve_ratio([-2*x**3 + x**2 + 2*x - 1, 2*x**3 + x**2 - 6*x, ... - 2*x**3 - 11*x**2 - 18*x - 9, 2*x**3 + 13*x**2 + 22*x + 8], 0, x) C2*(2*x - 3)/(2*(x**2 - 1)) References ========== .. [1] S. A. Abramov, Rational solutions of linear difference and q-difference equations with polynomial coefficients, in: T. Levelt, ed., Proc. ISSAC '95, ACM Press, New York, 1995, 285-289 See Also ======== rsolve_hyper """ f = sympify(f) if not f.is_polynomial(n): return coeffs = list(map(sympify, coeffs)) r = len(coeffs) - 1 A, B = coeffs[r], coeffs[0] A = A.subs(n, n - r).expand() h = Dummy('h') res = resultant(A, B.subs(n, n + h), n) if not res.is_polynomial(h): p, q = res.as_numer_denom() res = quo(p, q, h) nni_roots = list( roots(res, h, filter='Z', predicate=lambda r: r >= 0).keys()) if not nni_roots: return rsolve_poly(coeffs, f, n, **hints) else: C, numers = S.One, [S.Zero] * (r + 1) for i in range(int(max(nni_roots)), -1, -1): d = gcd(A, B.subs(n, n + i), n) A = quo(A, d, n) B = quo(B, d.subs(n, n - i), n) C *= Mul(*[d.subs(n, n - j) for j in range(0, i + 1)]) denoms = [C.subs(n, n + i) for i in range(0, r + 1)] for i in range(0, r + 1): g = gcd(coeffs[i], denoms[i], n) numers[i] = quo(coeffs[i], g, n) denoms[i] = quo(denoms[i], g, n) for i in range(0, r + 1): numers[i] *= Mul(*(denoms[:i] + denoms[i + 1:])) result = rsolve_poly(numers, f * Mul(*denoms), n, **hints) if result is not None: if hints.get('symbols', False): return simplify(result[0] / C), result[1] else: return simplify(result / C) else: return