def solve_biquadratic(f, g, opt): """Solve a system of two bivariate quadratic polynomial equations. """ G = groebner([f, g]) if len(G) == 1 and G[0].is_ground: return None 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 = 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)
def _solve_as_poly(f, symbol, solveset_solver, invert_func): """ Solve the equation using polynomial techniques if it already is a polynomial equation or, with a change of variables, can be made so. """ result = None if f.is_polynomial(symbol): solns = roots(f, symbol, cubics=True, quartics=True, quintics=True, domain='EX') num_roots = sum(solns.values()) if degree(f, symbol) <= num_roots: result = FiniteSet(*solns.keys()) else: poly = Poly(f, symbol) solns = poly.all_roots() if poly.degree() <= len(solns): result = FiniteSet(*solns) else: result = ConditionSet(symbol, Eq(f, 0), S.Complexes) else: poly = Poly(f) if poly is None: result = ConditionSet(symbol, Eq(f, 0), S.Complexes) gens = [g for g in poly.gens if g.has(symbol)] if len(gens) == 1: poly = Poly(poly, gens[0]) gen = poly.gen deg = poly.degree() poly = Poly(poly.as_expr(), poly.gen, composite=True) poly_solns = FiniteSet(*roots(poly, cubics=True, quartics=True, quintics=True).keys()) if len(poly_solns) < deg: result = ConditionSet(symbol, Eq(f, 0), S.Complexes) if gen != symbol: y = Dummy('y') lhs, rhs_s = invert_func(gen, y, symbol) if lhs is symbol: result = Union(*[rhs_s.subs(y, s) for s in poly_solns]) else: result = ConditionSet(symbol, Eq(f, 0), S.Complexes) else: result = ConditionSet(symbol, Eq(f, 0), S.Complexes) if result is not None: if isinstance(result, FiniteSet): # this is to simplify solutions like -sqrt(-I) to sqrt(2)/2 # - sqrt(2)*I/2. We are not expanding for solution with free # variables because that makes the solution more complicated. For # example expand_complex(a) returns re(a) + I*im(a) if all([s.free_symbols == set() and not isinstance(s, RootOf) for s in result]): s = Dummy('s') result = imageset(Lambda(s, expand_complex(s)), result) return result else: return ConditionSet(symbol, Eq(f, 0), S.Complexes)
def _compute_formula(f, x, P, Q, k, m, k_max): """Computes the formula for f.""" from sympy.polys import roots sol = [] for i in range(k_max + 1, k_max + m + 1): r = f.diff(x, i).limit(x, 0) / factorial(i) if r is S.Zero: continue kterm = m*k + i res = r p = P.subs(k, kterm) q = Q.subs(k, kterm) c1 = p.subs(k, 1/k).leadterm(k)[0] c2 = q.subs(k, 1/k).leadterm(k)[0] res *= (-c1 / c2)**k for r, mul in roots(p, k).items(): res *= rf(-r, k)**mul for r, mul in roots(q, k).items(): res /= rf(-r, k)**mul sol.append((res, kterm)) return sol
def _compute_formula(f, x, P, Q, k, m, k_max): """Computes the formula for f.""" from sympy.polys import roots sol = [] for i in range(k_max + 1, k_max + m + 1): if (i < 0) == True: continue r = f.diff(x, i).limit(x, 0) / factorial(i) if r is S.Zero: continue kterm = m * k + i res = r p = P.subs(k, kterm) q = Q.subs(k, kterm) c1 = p.subs(k, 1 / k).leadterm(k)[0] c2 = q.subs(k, 1 / k).leadterm(k)[0] res *= (-c1 / c2)**k for r, mul in roots(p, k).items(): res *= rf(-r, k)**mul for r, mul in roots(q, k).items(): res /= rf(-r, k)**mul sol.append((res, kterm)) return sol
def _solve_as_poly(f, symbol, solveset_solver, invert_func): """ Solve the equation using polynomial techniques if it already is a polynomial equation or, with a change of variables, can be made so. """ result = None if f.is_polynomial(symbol): solns = roots(f, symbol, cubics=True, quartics=True, quintics=True, domain='EX') num_roots = sum(solns.values()) if degree(f, symbol) <= num_roots: result = FiniteSet(*solns.keys()) else: poly = Poly(f, symbol) solns = poly.all_roots() if poly.degree() <= len(solns): result = FiniteSet(*solns) else: result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes) else: poly = Poly(f) if poly is None: result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes) gens = [g for g in poly.gens if g.has(symbol)] if len(gens) == 1: poly = Poly(poly, gens[0]) gen = poly.gen deg = poly.degree() poly = Poly(poly.as_expr(), poly.gen, composite=True) poly_solns = FiniteSet(*roots(poly, cubics=True, quartics=True, quintics=True).keys()) if len(poly_solns) < deg: result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes) if gen != symbol: y = Dummy('y') lhs, rhs_s = invert_func(gen, y, symbol) if lhs is symbol: result = Union(*[rhs_s.subs(y, s) for s in poly_solns]) else: result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes) else: result = ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes) if result is not None: if isinstance(result, FiniteSet): # this is to simplify solutions like -sqrt(-I) to sqrt(2)/2 # - sqrt(2)*I/2. We are not expanding for solution with free # variables because that makes the solution more complicated. For # example expand_complex(a) returns re(a) + I*im(a) if all([s.free_symbols == set() and not isinstance(s, RootOf) for s in result]): s = Dummy('s') result = imageset(Lambda(s, expand_complex(s)), result) return result else: return ConditionSet(Lambda(symbol, Eq(f, 0)), S.Complexes)
def _solve_reduced_system(system, gens, entry=False): """Recursively solves reduced polynomial systems. """ if len(system) == len(gens) == 1: zeros = list(roots(system[0], gens[-1]).keys()) return [(zero, ) for zero in zeros] basis = groebner(system, gens, polys=True) if len(basis) == 1 and basis[0].is_ground: if not entry: return [] else: return None univariate = list(filter(_is_univariate, basis)) if len(univariate) == 1: f = univariate.pop() else: raise NotImplementedError( filldedent(''' only zero-dimensional systems supported (finite number of solutions) ''')) gens = f.gens gen = gens[-1] zeros = list(roots(f.ltrim(gen)).keys()) if not zeros: return [] 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, )) if solutions and len(solutions[0]) != len(gens): raise NotImplementedError( filldedent(''' only zero-dimensional systems supported (finite number of solutions) ''')) return solutions
def _solve_reduced_system(system, gens, entry=False): """Recursively solves reduced polynomial systems. """ if len(system) == len(gens) == 1: zeros = list(roots(system[0], gens[-1]).keys()) return [(zero,) for zero in zeros] basis = groebner(system, gens, polys=True) if len(basis) == 1 and basis[0].is_ground: if not entry: return [] else: return None univariate = list(filter(_is_univariate, basis)) if len(univariate) == 1: f = univariate.pop() else: raise NotImplementedError(filldedent(''' only zero-dimensional systems supported (finite number of solutions) ''')) gens = f.gens gen = gens[-1] zeros = list(roots(f.ltrim(gen)).keys()) if not zeros: return [] 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,)) if solutions and len(solutions[0]) != len(gens): raise NotImplementedError(filldedent(''' only zero-dimensional systems supported (finite number of solutions) ''')) return solutions
def solve_biquadratic(f, g, opt): """Solve a system of two bivariate quadratic polynomial equations. Examples ======== >>> from sympy.polys import Options, Poly >>> from sympy.abc import x, y >>> from sympy.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 None if len(G) != 2: raise SolveFailed x, y = opt.gens p, q = G if not p.gcd(q).is_ground: # not 0-dimensional raise SolveFailed p = Poly(p, x, expand=False) p_roots = [rcollect(expr, y) for expr in roots(p).keys()] q = q.ltrim(-1) 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 solve_biquadratic(f, g, opt): """Solve a system of two bivariate quadratic polynomial equations. Examples ======== >>> from sympy.polys import Options, Poly >>> from sympy.abc import x, y >>> from sympy.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) [(7/2 - sqrt(29)/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 None if len(G) != 2: raise SolveFailed x, y = opt.gens p, q = G if not p.gcd(q).is_ground: # not 0-dimensional raise SolveFailed p = Poly(p, x, expand=False) p_roots = [rcollect(expr, y) for expr in roots(p).keys()] q = q.ltrim(-1) 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 _eigenvals_list(M, error_when_incomplete=True, simplify=False, **flags): iblocks = M.strongly_connected_components() all_eigs = [] for b in iblocks: block = M[b, b] if isinstance(simplify, FunctionType): charpoly = block.charpoly(simplify=simplify) else: charpoly = block.charpoly() eigs = roots(charpoly, multiple=True, **flags) if len(eigs) != block.rows: degree = int(charpoly.degree()) f = charpoly.as_expr() x = charpoly.gen try: eigs = [CRootOf(f, x, idx) for idx in range(degree)] except NotImplementedError: if error_when_incomplete: raise MatrixError(eigenvals_error_message) else: eigs = [] all_eigs += eigs if not simplify: return all_eigs if not isinstance(simplify, FunctionType): simplify = _simplify return [simplify(value) for value in all_eigs]
def _eigenvals_list( M, error_when_incomplete=True, simplify=False, **flags): iblocks = M.connected_components() all_eigs = [] for b in iblocks: block = M[b, b] if isinstance(simplify, FunctionType): charpoly = block.charpoly(simplify=simplify) else: charpoly = block.charpoly() eigs = roots(charpoly, multiple=True, **flags) if error_when_incomplete: if len(eigs) != block.rows: raise MatrixError( "Could not compute eigenvalues for {}. if you see this " "error, please report to SymPy issue tracker." .format(block)) all_eigs += eigs if not simplify: return all_eigs if not isinstance(simplify, FunctionType): simplify = _simplify return [simplify(value) for value in all_eigs]
def _eigenvals_dict( M, error_when_incomplete=True, simplify=False, **flags): iblocks = M.connected_components() all_eigs = {} for b in iblocks: block = M[b, b] if isinstance(simplify, FunctionType): charpoly = block.charpoly(simplify=simplify) else: charpoly = block.charpoly() eigs = roots(charpoly, multiple=False, **flags) if error_when_incomplete: if sum(eigs.values()) != block.rows: raise MatrixError( "Could not compute eigenvalues for {}. if you see this " "error, please report to SymPy issue tracker." .format(block)) for k, v in eigs.items(): if k in all_eigs: all_eigs[k] += v else: all_eigs[k] = v if not simplify: return all_eigs if not isinstance(simplify, FunctionType): simplify = _simplify return {simplify(key): value for key, value in all_eigs.items()}
def _eval_product(self, term=None): k = self.index a = self.lower n = self.upper if term is None: term = self.term if not term.has(k): return term**(n - a + 1) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One C_ = poly.LC all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a - r, n - a + 1) Q *= n - r if len(all_roots) < poly.degree: B = Product(quo(poly, Q.as_poly(k)), (k, a, n)) return poly.LC**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p) q = self._eval_product(q) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t) if p is not None: exclude.append(p) else: include.append(p) if not exclude: return None else: A, B = Mul(*exclude), Mul(*include) return A * Product(B, (k, a, n)) elif term.is_Pow: if not term.base.has(k): s = sum(term.exp, (k, a, n)) if not isinstance(s, Sum): return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base) if p is not None: return p**term.exp
def _eval_product(self, term=None): k = self.index a = self.lower n = self.upper if term is None: term = self.term if not term.has(k): return term**(n-a+1) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One C_= poly.LC all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a-r, n-a+1) Q *= n - r if len(all_roots) < poly.degree: B = Product(quo(poly, Q.as_poly(k)), (k, a, n)) return poly.LC**(n-a+1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p) q = self._eval_product(q) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t) if p is not None: exclude.append(p) else: include.append(p) if not exclude: return None else: A, B = Mul(*exclude), Mul(*include) return A * Product(B, (k, a, n)) elif term.is_Pow: if not term.base.has(k): s = sum(term.exp, (k, a, n)) if not isinstance(s, Sum): return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base) if p is not None: return p**term.exp
def _eval_product(self, a, n, term): from sympy import summation, Sum k = self.index if not term.has(k): return term**(n-a+1) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One C_= poly.LC() all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a-r, n-a+1) Q *= n - r if len(all_roots) < poly.degree(): B = Product(quo(poly, Q.as_poly(k)), (k, a, n)) return poly.LC()**(n-a+1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(a, n, p) q = self._eval_product(a, n, q) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(a, n, t) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: A, B = Mul(*exclude), term._new_rawargs(*include) return A * Product(B, (k, a, n)) elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) if not isinstance(s, Sum): return term.base**s elif not term.exp.has(k): p = self._eval_product(a, n, term.base) if p is not None: return p**term.exp
def _eval_product(self, a, n, term): from sympy import summation, Sum k = self.index if not term.has(k): return term**(n - a + 1) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One C_ = poly.LC() all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a - r, n - a + 1) Q *= n - r if len(all_roots) < poly.degree(): B = Product(quo(poly, Q.as_poly(k)), (k, a, n)) return poly.LC()**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(a, n, p) q = self._eval_product(a, n, q) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(a, n, t) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: A, B = Mul(*exclude), term._new_rawargs(*include) return A * Product(B, (k, a, n)) elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) if not isinstance(s, Sum): return term.base**s elif not term.exp.has(k): p = self._eval_product(a, n, term.base) if p is not None: return p**term.exp
def _eigenvals_dict( M, error_when_incomplete=True, simplify=False, **flags): iblocks = M.strongly_connected_components() all_eigs = {} is_dom = M._rep.domain in (ZZ, QQ) for b in iblocks: # Fast path for a 1x1 block: if is_dom and len(b) == 1: index = b[0] val = M[index, index] all_eigs[val] = all_eigs.get(val, 0) + 1 continue block = M[b, b] if isinstance(simplify, FunctionType): charpoly = block.charpoly(simplify=simplify) else: charpoly = block.charpoly() eigs = roots(charpoly, multiple=False, **flags) if sum(eigs.values()) != block.rows: degree = int(charpoly.degree()) f = charpoly.as_expr() x = charpoly.gen try: eigs = {CRootOf(f, x, idx): 1 for idx in range(degree)} except NotImplementedError: if error_when_incomplete: raise MatrixError(eigenvals_error_message) else: eigs = {} for k, v in eigs.items(): if k in all_eigs: all_eigs[k] += v else: all_eigs[k] = v if not simplify: return all_eigs if not isinstance(simplify, FunctionType): simplify = _simplify return {simplify(key): value for key, value in all_eigs.items()}
def solve_reduced_system(system, gens, entry=False): """Recursively solves reduced polynomial systems. """ basis = groebner(system, gens, polys=True) if len(basis) == 1 and basis[0].is_ground: if not entry: return [] else: return None univariate = filter(is_univariate, basis) basis = [ b.as_basic() for b in basis ] if len(univariate) == 1: f = univariate.pop() else: raise ValueError("only zero-dimensional systems supported") gens = f.gens f = f.as_basic() zeros = roots(f, gens[-1]).keys() if not zeros: return [] 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 = b.subs(gens[-1], zero).expand() if not eq.is_zero: new_system.append(eq) for solution in solve_reduced_system(new_system, new_gens): solutions.append(solution + [zero]) return solutions
def _eigenvals_list( M, error_when_incomplete=True, simplify=False, **flags): iblocks = M.strongly_connected_components() all_eigs = [] is_dom = M._rep.domain in (ZZ, QQ) for b in iblocks: # Fast path for a 1x1 block: if is_dom and len(b) == 1: index = b[0] val = M[index, index] all_eigs.append(val) continue block = M[b, b] if isinstance(simplify, FunctionType): charpoly = block.charpoly(simplify=simplify) else: charpoly = block.charpoly() eigs = roots(charpoly, multiple=True, **flags) if len(eigs) != block.rows: degree = int(charpoly.degree()) f = charpoly.as_expr() x = charpoly.gen try: eigs = [CRootOf(f, x, idx) for idx in range(degree)] except NotImplementedError: if error_when_incomplete: raise MatrixError(eigenvals_error_message) else: eigs = [] all_eigs += eigs if not simplify: return all_eigs if not isinstance(simplify, FunctionType): simplify = _simplify return [simplify(value) for value in all_eigs]
def solve_reduced_system(system, entry=False): """Recursively solves reduced polynomial systems. """ basis = poly_groebner(system) if len(basis) == 1 and basis[0].is_one: if not entry: return [] else: return None univariate = filter(is_univariate, basis) if len(univariate) == 1: f = univariate.pop() else: raise PolynomialError("Not a zero-dimensional system") zeros = roots(Poly(f, f.symbols[-1])).keys() if not zeros: return [] if len(basis) == 1: return [ [zero] for zero in zeros ] solutions = [] for zero in zeros: new_system = [] for poly in basis[:-1]: eq = poly.evaluate((poly.symbols[-1], zero)) if not eq.is_zero: new_system.append(eq) for solution in solve_reduced_system(new_system): solutions.append(solution + [zero]) return solutions
def solve_reduced_system(system, entry=False): """Recursively solves reduced polynomial systems. """ basis = poly_groebner(system) if len(basis) == 1 and basis[0].is_one: if not entry: return [] else: return None univariate = filter(is_univariate, basis) if len(univariate) == 1: f = univariate.pop() else: raise PolynomialError("Not a zero-dimensional system") zeros = roots(Poly(f, f.symbols[-1])).keys() if not zeros: return [] if len(basis) == 1: return [[zero] for zero in zeros] solutions = [] for zero in zeros: new_system = [] for poly in basis[:-1]: eq = poly.evaluate((poly.symbols[-1], zero)) if not eq.is_zero: new_system.append(eq) for solution in solve_reduced_system(new_system): solutions.append(solution + [zero]) return solutions
def _eigenvals_dict( M, error_when_incomplete=True, simplify=False, **flags): iblocks = M.connected_components() all_eigs = {} for b in iblocks: block = M[b, b] if isinstance(simplify, FunctionType): charpoly = block.charpoly(simplify=simplify) else: charpoly = block.charpoly() eigs = roots(charpoly, multiple=False, **flags) if sum(eigs.values()) != block.rows: degree = int(charpoly.degree()) f = charpoly.as_expr() x = charpoly.gen try: eigs = {CRootOf(f, x, idx): 1 for idx in range(degree)} except NotImplementedError: if error_when_incomplete: raise MatrixError(eigenvals_error_message) else: eigs = {} for k, v in eigs.items(): if k in all_eigs: all_eigs[k] += v else: all_eigs[k] = v if not simplify: return all_eigs if not isinstance(simplify, FunctionType): simplify = _simplify return {simplify(key): value for key, value in all_eigs.items()}
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 sympy.abc import x >>> from sympy.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 None coeffs = 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 = 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 xrange(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 xrange(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 xrange(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 None
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.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 None 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 solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial and transcendental equations and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior, eg. because of speed issues, set simplified=False in function arguments. To solve equations and systems of equations of other kind, eg. recurrence relations of differential equations use rsolve() or dsolve() functions respectively. >>> from sympy import * >>> x,y = symbols('xy') Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ if not symbols: raise ValueError('no symbols were given') if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] symbols = map(sympify, symbols) result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False if isinstance(symbols, (list, tuple)): symbols_passed = symbols[:] elif isinstance(symbols, set): symbols_passed = list(symbols) i = 0 for s in symbols: if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Symbol('F%d' % i, dummy=True) elif s.is_Derivative: symbol_swapped = True s_new = Symbol('D%d' % i, dummy=True) else: raise TypeError('not a Symbol or a Function') symbols_new.append(s_new) i += 1 if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if not isinstance(f, (tuple, list, set)): f = sympify(f) # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new if isinstance(f, Equality): f = f.lhs - f.rhs if len(symbols) != 1: raise NotImplementedError('multivariate equation') symbol = symbols[0] strategy = guess_solve_strategy(f, symbol) if strategy == GS_POLY: poly = f.as_poly(symbol) assert poly is not None result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, Q = f.as_numer_denom() #TODO: check for Q != 0 return solve(P, symbol, **flags) elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Add): # we must search for a suitable change of variable # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the GCD of the denominators m = ilcm(*exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Symbol('t', positive=True, dummy=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: raise TypeError( "Could not convert to a polynomial equation: %s" % f_) cv_sols = solve(f_, t) for sol in cv_sols: result.append(sol**m) elif isinstance(f, Mul): for mul_arg in args: result.extend(solve(mul_arg, symbol)) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) f1 = simplify(f * symbol**(-m)) result = solve(f1, symbol) # TODO: we might have introduced unwanted solutions # when multiplied by x**-m elif strategy == GS_TRANSCENDENTAL: #a, b = f.as_numer_denom() # Let's throw away the denominator for now. When we have robust # assumptions, it should be checked, that for the solution, # b!=0. result = tsolve(f, *symbols) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) else: raise NotImplementedError( "No algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True): return map(simplify, result) else: return result else: if not f: return {} else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: g = sympify(g) if isinstance(g, Equality): g = g.lhs - g.rhs poly = g.as_poly(*symbols) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for coeff, monom in poly.iter_terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff soln = solve_linear_system(matrix, *symbols, **flags) else: soln = solve_poly_system(polys) # Use swap_dict to ensure we return the same type as what was # passed if symbol_swapped: if isinstance(soln, dict): res = {} for k in soln.keys(): res.update({swap_back_dict[k]: soln[k]}) return res else: return soln else: return soln
def _solve(f, *symbols, **flags): """ Return a checked solution for f in terms of one or more of the symbols.""" check = flags.get('check', True) if not iterable(f): if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if not soln is None: return soln # find first successful solution failed = [] for s in symbols: n, d = solve_linear(f, symbols=[s]) if n.is_Symbol: return [{n: cancel(d)}] failed.append(s) for s in failed: try: soln = _solve(f, s, **flags) except NotImplementedError: continue if soln: return [{s: sol} for sol in soln] else: return soln else: msg = "No algorithms are implemented to solve equation %s" raise NotImplementedError(msg % f) symbol = symbols[0] # build up solutions if f is a Mul if f.is_Mul: result = set() dens = denoms(f, symbols) for m in f.args: soln = _solve(m, symbol, **flags) result.update(set(soln)) if check: result = [s for s in result if all(not checksol(den, {symbol: s}, **flags) for den in dens)] elif f.is_Piecewise: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) dens = set() # all checking has already been done else: # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, symbols=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] result = False # no solution was obtained msg = '' # there is no failure message dens = denoms(f, symbols) # store these for checking later # Poly is generally robust enough to convert anything to # a polynomial and tell us the different generators that it # contains, so we will inspect the generators identified by # polys to figure out what to do. poly = Poly(f_num) if poly is None: raise ValueError('could not convert %s to Poly' % f_num) gens = [g for g in poly.gens if g.has(symbol)] if len(gens) > 1: # If there is more than one generator, it could be that the # generators have the same base but different powers, e.g. # >>> Poly(exp(x)+1/exp(x)) # Poly(exp(-x) + exp(x), exp(-x), exp(x), domain='ZZ') # >>> Poly(sqrt(x)+sqrt(sqrt(x))) # Poly(sqrt(x) + x**(1/4), sqrt(x), x**(1/4), domain='ZZ') # If the exponents are Rational then a change of variables # will make this a polynomial equation in a single base. def as_base_q(x): """Return (b**e, q) for x = b**(p*e/q) where p/q is the leading Rational of the exponent of x, e.g. exp(-2*x/3) -> (exp(x), 3) """ b, e = x.as_base_exp() if e.is_Rational: return b, e.q if not e.is_Mul: return x, 1 c, ee = e.as_coeff_Mul() if c.is_Rational and not c is S.One: # c could be a Float return b**ee, c.q return x, 1 bases, qs = zip(*[as_base_q(g) for g in gens]) bases = set(bases) if len(bases) == 1 and any(q != 1 for q in qs): # e.g. for x**(1/2) + x**(1/4) a change of variables # can be made using p**4 to give p**2 + p base = bases.pop() m = reduce(ilcm, qs) p = Dummy('p', positive=True) cov = p**m fnew = f_num.subs(base, cov) poly = Poly(fnew, p) # we now have a single generator, p # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain critical length of the roots. if poly.degree() > 2: flags['simplify'] = flags.get('simplify', False) soln = roots(poly, cubics=True, quartics=True).keys() # We now know what the values of p are equal to. Now find out # how they are related to the original x, e.g. if p**2 = cos(x) then # x = acos(p**2) # inversion = _solve(cov - base, symbol, **flags) result = [i.subs(p, s) for i in inversion for s in soln] if check: result = [r for r in result if checksol(f_num, {symbol: r}, **flags) is not False] elif len(gens) == 1: # There is only one generator that we are interested in, but there may # have been more than one generator identified by polys (e.g. for symbols # other than the one we are interested in) so recast the poly in terms # of our generator of interest. if len(poly.gens) > 1: poly = Poly(poly, gens[0]) # if we haven't tried tsolve yet, do so now if not flags.pop('tsolve', False): # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain critical length of the roots. if poly.degree() > 2: flags['simplify'] = flags.get('simplify', False) soln = roots(poly, cubics=True, quartics=True).keys() gen = poly.gen if gen != symbol: u = Dummy() flags['tsolve'] = True inversion = _solve(gen - u, symbol, **flags) soln = list(set([i.subs(u, s) for i in inversion for s in soln])) result = soln else: msg = 'multiple generators %s' % gens # fallback if above fails if result is False: result = _tsolve(f_num, symbol, **flags) or False if result is False: raise NotImplementedError(msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplify', True): result = map(simplify, result) # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it if check: result = [s for s in result if all(not checksol(den, {symbol: s}, **flags) for den in dens)] return result else: if not f: return [] else: polys = [] for g in f: poly = g.as_poly(*symbols, **{'extension': True}) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros(n, m + 1) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None result = solve_linear_system(matrix, *symbols, **flags) return result else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] result = solve_poly_system(polys) return result
def normal(f, g, n=None): """Given relatively prime univariate polynomials 'f' and 'g', rewrite their quotient to a normal form defined as follows: f(n) A(n) C(n+1) ---- = Z ----------- g(n) B(n) C(n) where Z is arbitrary constant and A, B, C are monic polynomials in 'n' with following properties: (1) gcd(A(n), B(n+h)) = 1 for all 'h' in 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 crucial step in Gosper's algorithm and in difference equations solving. It can be also used to decide if two hypergeometric are similar or not. This procedure will return a tuple containing elements of this factorization in the form (Z*A, B, C). For example: >>> from sympy import Symbol, normal >>> n = Symbol('n', integer=True) >>> normal(4*n+5, 2*(4*n+1)*(2*n+3), n) (1/4, 3/2 + n, 1/4 + n) """ f, g = map(sympify, (f, g)) p = f.as_poly(n, field=True) q = g.as_poly(n, field=True) a, p = p.LC(), p.monic() b, q = q.LC(), q.monic() A = p.as_basic() B = q.as_basic() C, Z = S.One, a / b h = Symbol('h', dummy=True) res = resultant(A, B.subs(n, n + h), n) nni_roots = roots(res, h, filter='Z', predicate=lambda r: r >= 0).keys() if not nni_roots: return (f, g, S.One) else: for i in sorted(nni_roots): 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 xrange(1, i + 1)]) return (Z * A, B, C)
def normal(f, g, n=None): """Given relatively prime univariate polynomials 'f' and 'g', rewrite their quotient to a normal form defined as follows: f(n) A(n) C(n+1) ---- = Z ----------- g(n) B(n) C(n) where Z is arbitrary constant and A, B, C are monic polynomials in 'n' with following properties: (1) gcd(A(n), B(n+h)) = 1 for all 'h' in 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 crucial step in Gosper's algorithm and in difference equations solving. It can be also used to decide if two hypergeometric are similar or not. This procedure will return a tuple containing elements of this factorization in the form (Z*A, B, C). For example: >>> from sympy import Symbol, normal >>> n = Symbol('n', integer=True) >>> normal(4*n+5, 2*(4*n+1)*(2*n+3), n) (1/4, 3/2 + n, 1/4 + n) """ f, g = map(sympify, (f, g)) p = f.as_poly(n, field=True) q = g.as_poly(n, field=True) a, p = p.LC(), p.monic() b, q = q.LC(), q.monic() A = p.as_basic() B = q.as_basic() C, Z = S.One, a / b h = Dummy('h') res = resultant(A, B.subs(n, n+h), n) nni_roots = roots(res, h, filter='Z', predicate=lambda r: r >= 0).keys() if not nni_roots: return (f, g, S.One) else: for i in sorted(nni_roots): 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 xrange(1, i+1) ]) return (Z*A, B, C)
def _solve(f, *symbols, **flags): """ Return a checked solution for f in terms of one or more of the symbols.""" if not iterable(f): if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if not soln is None: return soln # find first successful solution failed = [] for s in symbols: n, d = solve_linear(f, x=[s]) if n.is_Symbol: soln = {n: cancel(d)} return soln failed.append(s) for s in failed: try: soln = _solve(f, s, **flags) return soln except NotImplementedError: pass else: msg = "No algorithms are implemented to solve equation %s" raise NotImplementedError(msg % f) symbol = symbols[0] # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, x=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] strategy = guess_solve_strategy(f, symbol) result = False # no solution was obtained if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: msg = "Cannot solve equation %s for %s" % (f, symbol) else: # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain critical length of the roots. if poly.degree() > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, _ = f.as_numer_denom() dens = denoms(f, x=symbols) try: soln = _solve(P, symbol, **flags) except NotImplementedError: msg = "Cannot solve equation %s for %s" % (P, symbol) result = [] else: if dens: # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it result = [s for s in soln if all(not checksol(den, {symbol: s}, **flags) for den in dens)] else: result = soln elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Pow): result = _solve(args[0], symbol, **flags) elif isinstance(f, Add): # we must search for a suitable change of variables # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: msg = "Could not convert to a polynomial equation: %s" % f_ result = [] else: soln = [s**m for s in _solve(f_, t)] # we might have introduced solutions from another branch # when changing variables; check and keep solutions # unless they definitely aren't a solution result = [s for s in soln if checksol(f, {symbol: s}, **flags) is not False] elif isinstance(f, Mul): result = [] for m in f.args: result.extend(_solve(m, symbol, **flags) or []) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) if m and m != 1: f_ = simplify(f*symbol**(-m)) try: sols = _solve(f_, symbol) except NotImplementedError: msg = 'Could not solve %s for %s' % (f_, symbol) else: # we might have introduced unwanted solutions # when multiplying by x**-m; check and keep solutions # unless they definitely aren't a solution if sols: result = [s for s in sols if checksol(f, {symbol: s}, **flags) is not False] else: msg = 'CV_2 calculated %d but it should have been other than 0 or 1' % m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) # this is the fallback for not getting any other solution if result is False or strategy == GS_TRANSCENDENTAL: soln = tsolve(f_num, symbol) dens = denoms(f, x=symbols) if not dens: result = soln else: # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it result = [s for s in soln if all(not checksol(den, {symbol: s}, **flags) for den in dens)] if result is False: raise NotImplementedError(msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True) and strategy != GS_RATIONAL: result = map(simplify, result) return result else: if not f: return [] else: polys = [] for g in f: poly = g.as_poly(*symbols, **{'extension': True}) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None result = solve_linear_system(matrix, *symbols, **flags) return result else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] result = solve_poly_system(polys) return result
def rsolve_poly(coeffs, f, n, **hints): """Given linear recurrence operator L of order 'k' with polynomial coefficients and inhomogeneous equation Ly = 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 Ly = 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**2 - 2*n**3 + n**4 For more information on implemented algorithms refer to: [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 xrange(0, r+1): for j in xrange(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 xrange(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 = Symbol('x', dummy=True) degree_poly = S.Zero for i in xrange(0, r+1): if terms[i][1] - i == b: degree_poly += terms[i][0]*FallingFactorial(x, i) nni_roots = 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 xrange(0, N+1): C.append(Symbol('C'+str(i))) y += C[i] * n**i for i in xrange(0, r+1): E += coeffs[i].as_basic()*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 = 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 xrange(1, k+1): B *= -Rational(k-i+1, i) D += B * p.subs(n, a+k-i) return D alpha = {} for i in xrange(-A, d+1): I = one_vector(d+1) for k in xrange(1, d+1): I[k] = I[k-1] * (x+i-k+1)/k alpha[i] = S.Zero for j in xrange(0, A+1): for k in xrange(0, d+1): B = Binomial(k, i+j) D = delta(polys[j].as_basic(), k) alpha[i] += I[k]*B*D V = Matrix(U, A, lambda i, j: int(i == j)) if homogeneous: for i in xrange(A, U): v = zero_vector(A) for k in xrange(1, A+b+1): if i - k < 0: break B = alpha[k-A].subs(x, i-k) for j in xrange(0, A): v[j] += B * V[i-k, j] denom = alpha[-A].subs(x, i) for j in xrange(0, A): V[i, j] = -v[j] / denom else: G = zero_vector(U) for i in xrange(A, U): v = zero_vector(A) g = S.Zero for k in xrange(1, A+b+1): if i - k < 0: break B = alpha[k-A].subs(x, i-k) for j in xrange(0, A): v[j] += B * V[i-k, j] g += B * G[i-k] denom = alpha[-A].subs(x, i) for j in xrange(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 xrange(1, U): P[i] = (P[i-1] * (n-a-i+1)/i).expand() for i in xrange(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 xrange(0, A) ] g = lambda i: Add(*[ c*delta(q, i) for c, q in zip(C, Q) ]) if homogeneous: E = [ g(i) for i in xrange(N+1, U) ] else: E = [ g(i) + delta(h, i) for i in xrange(N+1, U) ] if E != []: solutions = solve(E, *C) if solutions is None: 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 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_ratio(coeffs, f, n, **hints): """Given linear recurrence operator L of order 'k' with polynomial coefficients and inhomogeneous equation Ly = 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 Ly = 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. For more information on the implemented algorithm refer to: [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 """ f = sympify(f) if not f.is_polynomial(n): return None coeffs = map(sympify, coeffs) r = len(coeffs)-1 A, B = coeffs[r], coeffs[0] A = A.subs(n, n-r).expand() h = Symbol('h', dummy=True) res = resultant(A, B.subs(n, n+h), n) if not res.is_polynomial(h): p, q = res.as_numer_denom() res = exquo(p, q, h) nni_roots = 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 xrange(int(max(nni_roots)), -1, -1): d = gcd(A, B.subs(n, n+i), n) A = exquo(A, d, n) B = exquo(B, d.subs(n, n-i), n) C *= Mul(*[ d.subs(n, n-j) for j in xrange(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] = exquo(coeffs[i], g, n) denoms[i] = exquo(denoms[i], g, n) for i in xrange(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 None
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 sympy.solvers import rsolve_hyper >>> from sympy.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. Petkovsek, Hypergeometric solutions of linear recurrences with polynomial coefficients, J. Symbolic Computation, 14 (1992), 243-264. .. [2] M. Petkovsek, H. S. Wilf, D. Zeilberger, A = B, 1996. """ coeffs = 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 None for h in similar.iterkeys(): if hypersimilar(g, h, n): similar[h] += g break else: similar[g] = S.Zero inhomogeneous = [] for g, h in similar.iteritems(): inhomogeneous.append(g + h) elif f.is_hypergeometric(n): inhomogeneous = [f] else: return None for i, g in enumerate(inhomogeneous): coeff, polys = S.One, coeffs[:] denoms = [ S.One ] * (r + 1) s = hypersimp(g, n) for j in xrange(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 xrange(0, r + 1): polys[j] *= Mul(*(denoms[:j] + denoms[j + 1:])) R = rsolve_poly(polys, Mul(*denoms), n) if not (R is None or R is S.Zero): inhomogeneous[i] *= R else: return None result = Add(*inhomogeneous) 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).iterkeys() ] q_factors = [ z for z in roots(q, n).iterkeys() ] 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 xrange(0, r + 1): a = Mul(*[ A.subs(n, n + j) for j in xrange(0, i) ]) b = Mul(*[ B.subs(n, n + j) for j in xrange(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 xrange(0, r + 1): coeff = polys[i].nth(d) if coeff is not S.Zero: poly += coeff * Z**i for z in roots(poly, Z).iterkeys(): if z.is_zero: continue (C, s) = rsolve_poly([ polys[i]*z**i for i in xrange(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) # If there is a nonnegative root in the denominator of the ratio, # this indicates that the term y(n_root) is zero, and one should # start the product with the term y(n_root + 1). n0 = 0 for n_root in roots(ratio.as_numer_denom()[1], n).keys(): if (n0 < (n_root + 1)) is True: n0 = n_root + 1 K = product(ratio, (n, n0, 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 = zip(numbered_symbols('C'), kernel) if sk: for C, ker in sk: result += C * ker else: return None if hints.get('symbols', False): symbols |= set([s for s, k in sk]) return (result, list(symbols)) else: return result
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.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 definite = dif.is_Integer if definite and (dif < 100): return self._eval_product_direct(term, limits) 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: factored = factor_terms(term, fraction=True) if factored.is_Mul: return self._eval_product(factored, (k, a, n)) elif term.is_Mul: # Factor in part without the summation variable and part with without_k, with_k = term.as_coeff_mul(k) if len(with_k) >= 2: # More than one term including k, so still a multiplication exclude, include = [], [] for t in with_k: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = self.func(arg, (k, a, n)).doit() return without_k**(n - a + 1) * A * B else: # Just a single term p = self._eval_product(with_k[0], (k, a, n)) if p is None: p = self.func(with_k[0], (k, a, n)).doit() return without_k**(n - a + 1) * p 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 if definite: return self._eval_product_direct(term, limits)
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.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() q = self._eval_product(q, (k, a, n)) if q.is_Number: # There is expression, which couldn't change by # as_numer_denom(). E.g. n**(2/3) + 1 --> (n**(2/3) + 1, 1). # We have to catch this case. p = sum([self._eval_product(i, (k, a, n)) for i in p.as_coeff_Add()]) else: p = self._eval_product(p, (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 None 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 _eigenvals(M, error_when_incomplete=True, **flags): r"""Return eigenvalues using the Berkowitz agorithm to compute the characteristic polynomial. Parameters ========== error_when_incomplete : bool, optional If it is set to ``True``, it will raise an error if not all eigenvalues are computed. This is caused by ``roots`` not returning a full list of eigenvalues. simplify : bool or function, optional If it is set to ``True``, it attempts to return the most simplified form of expressions returned by applying default simplification method in every routine. If it is set to ``False``, it will skip simplification in this particular routine to save computation resources. If a function is passed to, it will attempt to apply the particular function as simplification method. rational : bool, optional If it is set to ``True``, every floating point numbers would be replaced with rationals before computation. It can solve some issues of ``roots`` routine not working well with floats. multiple : bool, optional If it is set to ``True``, the result will be in the form of a list. If it is set to ``False``, the result will be in the form of a dictionary. Returns ======= eigs : list or dict Eigenvalues of a matrix. The return format would be specified by the key ``multiple``. Raises ====== MatrixError If not enough roots had got computed. NonSquareMatrixError If attempted to compute eigenvalues from a non-square matrix. Examples ======== >>> from sympy.matrices import Matrix >>> M = Matrix(3, 3, [0, 1, 1, 1, 0, 0, 1, 1, 1]) >>> M.eigenvals() {-1: 1, 0: 1, 2: 1} See Also ======== MatrixDeterminant.charpoly eigenvects Notes ===== Eigenvalues of a matrix `A` can be computed by solving a matrix equation `\det(A - \lambda I) = 0` """ if not M: return {} if not M.is_square: raise NonSquareMatrixError("{} must be a square matrix.".format(M)) simplify = flags.pop('simplify', False) multiple = flags.get('multiple', False) rational = flags.pop('rational', True) if M.is_upper or M.is_lower: return _eigenvals_triangular(M, multiple=multiple) if all(x.is_number for x in M) and M.has(Float): return _eigenvals_mpmath(M, multiple=multiple) if rational: M = M.applyfunc(lambda x: nsimplify(x, rational=True) if x.has(Float) else x) if isinstance(simplify, FunctionType): eigs = roots(M.charpoly(simplify=simplify), **flags) else: eigs = roots(M.charpoly(), **flags) # make sure the algebraic multiplicity sums to the # size of the matrix if error_when_incomplete: if not multiple and sum(eigs.values()) != M.rows or \ multiple and len(eigs) != M.cols: raise MatrixError("Could not compute eigenvalues for {}".format(M)) # Since 'simplify' flag is unsupported in roots() # simplify() function will be applied once at the end of the routine. if not simplify: return eigs if not isinstance(simplify, FunctionType): simplify = _simplify # With 'multiple' flag set true, simplify() will be mapped for the list # Otherwise, simplify() will be mapped for the keys of the dictionary if not multiple: return {simplify(key): value for key, value in eigs.items()} else: return [simplify(value) for value in eigs]
def solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial, transcendental equations, piecewise combinations thereof and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior (e.g., because of speed issues) set simplified=False in function arguments. To solve equations and systems of equations like recurrence relations or differential equations, use rsolve() or dsolve(), respectively. >>> from sympy import I, solve >>> from sympy.abc import x, y Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ def sympit(w): return map(sympify, iff(isinstance(w, (list, tuple, set)), w, [w])) # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. bare_f = not isinstance(f, (list, tuple, set)) f, symbols = (sympit(w) for w in [f, symbols]) for i, fi in enumerate(f): if isinstance(fi, Equality): f[i] = fi.lhs - fi.rhs if not symbols: #get symbols from equations or supply dummy symbols since #solve(3,x) returns []...though it seems that it should raise some sort of error TODO symbols = set([]) for fi in f: symbols |= fi.atoms(Symbol) or set([Dummy('x')]) symbols = list(symbols) if bare_f: f = f[0] if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy('F%d' % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy('D%d' % i) else: raise TypeError('not a Symbol or a Function') symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if not isinstance(f, (tuple, list, set)): # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f = piecewise_fold(f) if len(symbols) != 1: result = {} for s in symbols: result[s] = solve(f, s, **flags) if flags.get('simplified', True): for s, r in result.items(): result[s] = map(simplify, r) return result symbol = symbols[0] strategy = guess_solve_strategy(f, symbol) if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: raise NotImplementedError("Cannot solve equation " + str(f) + " for " + str(symbol)) # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain crtical length of the roots. if poly.degree > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, Q = f.as_numer_denom() #TODO: check for Q != 0 result = solve(P, symbol, **flags) elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Add): # we must search for a suitable change of variable # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: raise NotImplementedError( "Could not convert to a polynomial equation: %s" % f_) cv_sols = solve(f_, t) for sol in cv_sols: result.append(sol**m) elif isinstance(f, Mul): for mul_arg in args: result.extend(solve(mul_arg, symbol)) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) f1 = simplify(f * symbol**(-m)) result = solve(f1, symbol) # TODO: we might have introduced unwanted solutions # when multiplied by x**-m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == GS_TRANSCENDENTAL: #a, b = f.as_numer_denom() # Let's throw away the denominator for now. When we have robust # assumptions, it should be checked, that for the solution, # b!=0. result = tsolve(f, *symbols) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) else: raise NotImplementedError( "No algorithms are implemented to solve equation %s" % f) # This symbol swap should not be necessary for the single symbol case: if you've # solved for the symbol the it will not appear in the solution. Right now, however # ode's are getting solutions for solve (even though they shouldn't be -- see the # swap_back test in test_solvers). if symbol_swapped: result = [ri.subs(swap_back_dict) for ri in result] if flags.get('simplified', True) and strategy != GS_RATIONAL: return map(simplify, result) else: return result else: if not f: return {} else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: poly = g.as_poly(*symbols) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff soln = solve_linear_system(matrix, *symbols, **flags) else: soln = solve_poly_system(polys) # Use swap_dict to ensure we return the same type as what was # passed if symbol_swapped: if isinstance(soln, dict): res = {} for k in soln.keys(): res.update({swap_back_dict[k]: soln[k]}) return res else: return soln else: return soln
def solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial and transcendental equations and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior, eg. because of speed issues, set simplified=False in function arguments. To solve equations and systems of equations of other kind, eg. recurrence relations of differential equations use rsolve() or dsolve() functions respectively. >>> from sympy import * >>> x,y = symbols('xy') Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ if not symbols: raise ValueError('no symbols were given') if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] symbols = map(sympify, symbols) if any(not s.is_Symbol for s in symbols): raise TypeError('not a Symbol') if not isinstance(f, (tuple, list, set)): f = sympify(f) if isinstance(f, Equality): f = f.lhs - f.rhs if len(symbols) != 1: raise NotImplementedError('multivariate equation') symbol = symbols[0] strategy = guess_solve_strategy(f, symbol) if strategy == GS_POLY: poly = f.as_poly( symbol ) assert poly is not None result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, Q = f.as_numer_denom() #TODO: check for Q != 0 return solve(P, symbol, **flags) elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Add): # we must search for a suitable change of variable # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the GCD of the denominators m = ilcm(*exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Symbol('t', positive=True, dummy=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: raise TypeError("Could not convert to a polynomial equation: %s" % f_) cv_sols = solve(f_, t) result = list() for sol in cv_sols: result.append(sol**m) elif isinstance(f, Mul): result = [] for mul_arg in args: result.extend(solve(mul_arg, symbol)) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) f1 = simplify(f*symbol**(-m)) result = solve(f1, symbol) # TODO: we might have introduced unwanted solutions # when multiplied by x**-m elif strategy == GS_TRASCENDENTAL: #a, b = f.as_numer_denom() # Let's throw away the denominator for now. When we have robust # assumptions, it should be checked, that for the solution, # b!=0. result = tsolve(f, *symbols) elif strategy == -1: raise Exception('Could not parse expression %s' % f) else: raise NotImplementedError("No algorithms where implemented to solve equation %s" % f) if flags.get('simplified', True): return map(simplify, result) else: return result else: if not f: return {} else: polys = [] for g in f: g = sympify(g) if isinstance(g, Equality): g = g.lhs - g.rhs poly = g.as_poly(*symbols) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for coeff, monom in poly.iter_terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff return solve_linear_system(matrix, *symbols, **flags) else: return solve_poly_system(polys)
def rsolve_hyper(coeffs, f, n, **hints): """Given linear recurrence operator L of order 'k' with polynomial coefficients and inhomogeneous equation Ly = 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 Ly = f, and find particular solution using Abramov's algorithm. (2) Compute generating set of 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 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 For more information on the implemented algorithm refer to: [1] M. Petkovsek, Hypergeometric solutions of linear recurrences with polynomial coefficients, J. Symbolic Computation, 14 (1992), 243-264. [2] M. Petkovsek, H. S. Wilf, D. Zeilberger, A = B, 1996. """ coeffs = map(sympify, coeffs) f = sympify(f) r, kernel = len(coeffs)-1, [] if not f.is_zero: if f.is_Add: similar = {} for g in f.expand().args: if not g.is_hypergeometric(n): return None for h in similar.iterkeys(): if hypersimilar(g, h, n): similar[h] += g break else: similar[g] = S.Zero inhomogeneous = [] for g, h in similar.iteritems(): inhomogeneous.append(g+h) elif f.is_hypergeometric(n): inhomogeneous = [f] else: return None for i, g in enumerate(inhomogeneous): coeff, polys = S.One, coeffs[:] denoms = [ S.One ] * (r+1) s = hypersimp(g, n) for j in xrange(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 xrange(0, r+1): polys[j] *= Mul(*(denoms[:j] + denoms[j+1:])) R = rsolve_poly(polys, Mul(*denoms), n) if not (R is None or R is S.Zero): inhomogeneous[i] *= R else: return None result = Add(*inhomogeneous) else: result = S.Zero Z = Symbol('Z', dummy=True) p, q = coeffs[0], coeffs[r].subs(n, n-r+1) p_factors = [ z for z in roots(p, n).iterkeys() ] q_factors = [ z for z in roots(q, n).iterkeys() ] 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 xrange(0, r+1): a = Mul(*[ A.subs(n, n+j) for j in xrange(0, i) ]) b = Mul(*[ B.subs(n, n+j) for j in xrange(i, r) ]) poly = exquo(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 xrange(0, r+1): coeff = polys[i].nth(d) if coeff is not S.Zero: poly += coeff * Z**i for z in roots(poly, Z).iterkeys(): if not z.is_real or z.is_zero: continue C = rsolve_poly([ polys[i]*z**i for i in xrange(r+1) ], 0, n) if C is not None and C is not S.Zero: ratio = z * A * C.subs(n, n + 1) / B / C K = product(simplify(ratio), (n, 0, n-1)) if casoratian(kernel+[K], n) != 0: kernel.append(K) symbols = [ Symbol('C'+str(i)) for i in xrange(len(kernel)) ] for C, ker in zip(symbols, kernel): result += C * ker if hints.get('symbols', False): return (result, symbols) else: return result
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.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: factored = factor_terms(term, fraction=True) if factored.is_Mul: return self._eval_product(factored, (k, a, n)) 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 None 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 solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial and transcendental equations and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior, eg. because of speed issues, set simplified=False in function arguments. To solve equations and systems of equations of other kind, eg. recurrence relations of differential equations use rsolve() or dsolve() functions respectively. >>> from sympy import * >>> x,y = symbols('xy') Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ if not symbols: raise ValueError('no symbols were given') if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] symbols = map(sympify, symbols) result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False if isinstance(symbols, (list, tuple)): symbols_passed = symbols[:] elif isinstance(symbols, set): symbols_passed = list(symbols) i = 0 for s in symbols: if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Symbol('F%d' % i, dummy=True) elif s.is_Derivative: symbol_swapped = True s_new = Symbol('D%d' % i, dummy=True) else: raise TypeError('not a Symbol or a Function') symbols_new.append(s_new) i += 1 if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if not isinstance(f, (tuple, list, set)): f = sympify(f) # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new if isinstance(f, Equality): f = f.lhs - f.rhs if len(symbols) != 1: raise NotImplementedError('multivariate equation') symbol = symbols[0] strategy = guess_solve_strategy(f, symbol) if strategy == GS_POLY: poly = f.as_poly( symbol ) assert poly is not None result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, Q = f.as_numer_denom() #TODO: check for Q != 0 return solve(P, symbol, **flags) elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Add): # we must search for a suitable change of variable # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the GCD of the denominators m = ilcm(*exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Symbol('t', positive=True, dummy=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: raise TypeError("Could not convert to a polynomial equation: %s" % f_) cv_sols = solve(f_, t) for sol in cv_sols: result.append(sol**m) elif isinstance(f, Mul): for mul_arg in args: result.extend(solve(mul_arg, symbol)) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) f1 = simplify(f*symbol**(-m)) result = solve(f1, symbol) # TODO: we might have introduced unwanted solutions # when multiplied by x**-m elif strategy == GS_TRANSCENDENTAL: #a, b = f.as_numer_denom() # Let's throw away the denominator for now. When we have robust # assumptions, it should be checked, that for the solution, # b!=0. result = tsolve(f, *symbols) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) else: raise NotImplementedError("No algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True): return map(simplify, result) else: return result else: if not f: return {} else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: g = sympify(g) if isinstance(g, Equality): g = g.lhs - g.rhs poly = g.as_poly(*symbols) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for coeff, monom in poly.iter_terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff soln = solve_linear_system(matrix, *symbols, **flags) else: soln = solve_poly_system(polys) # Use swap_dict to ensure we return the same type as what was # passed if symbol_swapped: if isinstance(soln, dict): res = {} for k in soln.keys(): res.update({swap_back_dict[k]: soln[k]}) return res else: return soln else: return soln
def _eval_product(self, term, limits): from sympy import summation (k, a, n) = limits if k not in term.free_symbols: return term**(n - a + 1) if a == n: return term.subs(k, a) dif = n - a if dif.is_Integer: return Mul(*[term.subs(k, a + i) for i in xrange(dif + 1)]) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly, multiple=True) for r in all_roots: A *= C.RisingFactorial(a - r, n - a + 1) Q *= n - r if len(all_roots) < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = Product(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 None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = Product(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 Product(evaluated, limits) else: return f
def solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial, transcendental equations, piecewise combinations thereof and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior (e.g., because of speed issues) set simplified=False in function arguments. To solve equations and systems of equations like recurrence relations or differential equations, use rsolve() or dsolve(), respectively. >>> from sympy import I, solve >>> from sympy.abc import x, y Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ def sympit(w): return map(sympify, iff(isinstance(w, (list, tuple, set)), w, [w])) # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. bare_f = not isinstance(f, (list, tuple, set)) f, symbols = (sympit(w) for w in [f, symbols]) if any(isinstance(fi, bool) or (fi.is_Relational and not fi.is_Equality) for fi in f): return reduce_inequalities(f, assume=flags.get("assume")) for i, fi in enumerate(f): if fi.is_Equality: f[i] = fi.lhs - fi.rhs if not symbols: # get symbols from equations or supply dummy symbols since # solve(3,x) returns []...though it seems that it should raise some sort of error TODO symbols = set([]) for fi in f: symbols |= fi.atoms(Symbol) or set([Dummy("x")]) symbols = list(symbols) if bare_f: f = f[0] if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy("F%d" % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy("D%d" % i) else: raise TypeError("not a Symbol or a Function") symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if not isinstance(f, (tuple, list, set)): # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f = piecewise_fold(f) if len(symbols) != 1: result = {} for s in symbols: result[s] = solve(f, s, **flags) if flags.get("simplified", True): for s, r in result.items(): result[s] = map(simplify, r) return result symbol = symbols[0] strategy = guess_solve_strategy(f, symbol) if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: raise NotImplementedError("Cannot solve equation " + str(f) + " for " + str(symbol)) # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain crtical length of the roots. if poly.degree > 2: flags["simplified"] = flags.get("simplified", False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, Q = f.as_numer_denom() # TODO: check for Q != 0 result = solve(P, symbol, **flags) elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Add): # we must search for a suitable change of variable # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy("t", positive=True) f_ = f.subs(symbol, t ** m) if guess_solve_strategy(f_, t) != GS_POLY: raise NotImplementedError("Could not convert to a polynomial equation: %s" % f_) cv_sols = solve(f_, t) for sol in cv_sols: result.append(sol ** m) elif isinstance(f, Mul): for mul_arg in args: result.extend(solve(mul_arg, symbol)) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) f1 = simplify(f * symbol ** (-m)) result = solve(f1, symbol) # TODO: we might have introduced unwanted solutions # when multiplied by x**-m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == GS_TRANSCENDENTAL: # a, b = f.as_numer_denom() # Let's throw away the denominator for now. When we have robust # assumptions, it should be checked, that for the solution, # b!=0. result = tsolve(f, *symbols) elif strategy == -1: raise ValueError("Could not parse expression %s" % f) else: raise NotImplementedError("No algorithms are implemented to solve equation %s" % f) # This symbol swap should not be necessary for the single symbol case: if you've # solved for the symbol the it will not appear in the solution. Right now, however # ode's are getting solutions for solve (even though they shouldn't be -- see the # swap_back test in test_solvers). if symbol_swapped: result = [ri.subs(swap_back_dict) for ri in result] if flags.get("simplified", True) and strategy != GS_RATIONAL: return map(simplify, result) else: return result else: if not f: return {} else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: poly = g.as_poly(*symbols) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff soln = solve_linear_system(matrix, *symbols, **flags) else: soln = solve_poly_system(polys) # Use swap_dict to ensure we return the same type as what was # passed if symbol_swapped: if isinstance(soln, dict): res = {} for k in soln.keys(): res.update({swap_back_dict[k]: soln[k]}) return res else: return soln else: return soln
def _solve(f, *symbols, **flags): """ Return a checked solution for f in terms of one or more of the symbols.""" if not iterable(f): if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if not soln is None: return soln # find first successful solution failed = [] for s in symbols: n, d = solve_linear(f, x=[s]) if n.is_Symbol: soln = {n: cancel(d)} return soln failed.append(s) for s in failed: try: soln = _solve(f, s, **flags) return soln except NotImplementedError: pass else: msg = "No algorithms are implemented to solve equation %s" raise NotImplementedError(msg % f) symbol = symbols[0] # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, x=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] strategy = guess_solve_strategy(f, symbol) result = False # no solution was obtained if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: msg = "Cannot solve equation %s for %s" % (f, symbol) else: # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain critical length of the roots. if poly.degree() > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, _ = f.as_numer_denom() dens = denoms(f, x=symbols) try: soln = _solve(P, symbol, **flags) except NotImplementedError: msg = "Cannot solve equation %s for %s" % (P, symbol) result = [] else: if dens: # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it result = [ s for s in soln if all(not checksol(den, {symbol: s}) for den in dens) ] else: result = soln elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Pow): result = _solve(args[0], symbol, **flags) elif isinstance(f, Add): # we must search for a suitable change of variables # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: msg = "Could not convert to a polynomial equation: %s" % f_ result = [] else: soln = [s**m for s in _solve(f_, t)] # we might have introduced solutions from another branch # when changing variables; check and keep solutions # unless they definitely aren't a solution result = [ s for s in soln if checksol(f, {symbol: s}) is not False ] elif isinstance(f, Mul): result = [] for m in f.args: result.extend(_solve(m, symbol, **flags) or []) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) if m and m != 1: f_ = simplify(f * symbol**(-m)) try: sols = _solve(f_, symbol) except NotImplementedError: msg = 'Could not solve %s for %s' % (f_, symbol) else: # we might have introduced unwanted solutions # when multiplying by x**-m; check and keep solutions # unless they definitely aren't a solution if sols: result = [ s for s in sols if checksol(f, {symbol: s}) is not False ] else: msg = 'CV_2 calculated %d but it should have been other than 0 or 1' % m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) # this is the fallback for not getting any other solution if result is False or strategy == GS_TRANSCENDENTAL: soln = tsolve(f_num, symbol) dens = denoms(f, x=symbols) if not dens: result = soln else: # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it result = [ s for s in soln if all(not checksol(den, {symbol: s}) for den in dens) ] if result is False: raise NotImplementedError( msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True) and strategy != GS_RATIONAL: result = map(simplify, result) return result else: if not f: return [] else: polys = [] for g in f: poly = g.as_poly(*symbols, **{'extension': True}) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None result = solve_linear_system(matrix, *symbols, **flags) return result else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] result = solve_poly_system(polys) return result
def _rsolve_hypergeometric(f, x, P, Q, k, m): """Recursive wrapper to rsolve_hypergeometric. Returns a Tuple of (formula, series independent terms, maximum power of x in independent terms) if successful otherwise ``None``. See :func:`rsolve_hypergeometric` for details. """ from sympy.polys import lcm, roots from sympy.integrals import integrate # tranformation - c proots, qroots = roots(P, k), roots(Q, k) all_roots = dict(proots) all_roots.update(qroots) scale = lcm([r.as_numer_denom()[1] for r, t in all_roots.items() if r.is_rational]) f, P, Q, m = _transformation_c(f, x, P, Q, k, m, scale) # transformation - a qroots = roots(Q, k) if qroots: k_min = Min(*qroots.keys()) else: k_min = S.Zero shift = k_min + m f, P, Q, m = _transformation_a(f, x, P, Q, k, m, shift) l = (x*f).limit(x, 0) if not isinstance(l, Limit) and l != 0: # Ideally should only be l != 0 return None qroots = roots(Q, k) if qroots: k_max = Max(*qroots.keys()) else: k_max = S.Zero ind, mp = S.Zero, -oo for i in range(k_max + m + 1): r = f.diff(x, i).limit(x, 0) / factorial(i) if r.is_finite is False: old_f = f f, P, Q, m = _transformation_a(f, x, P, Q, k, m, i) f, P, Q, m = _transformation_e(f, x, P, Q, k, m) sol, ind, mp = _rsolve_hypergeometric(f, x, P, Q, k, m) sol = _apply_integrate(sol, x, k) sol = _apply_shift(sol, i) ind = integrate(ind, x) ind += (old_f - ind).limit(x, 0) # constant of integration mp += 1 return sol, ind, mp elif r: ind += r*x**(i + shift) pow_x = Rational((i + shift), scale) if pow_x > mp: mp = pow_x # maximum power of x ind = ind.subs(x, x**(1/scale)) sol = _compute_formula(f, x, P, Q, k, m, k_max) sol = _apply_shift(sol, shift) sol = _apply_scale(sol, scale) return sol, ind, mp
def solve_biquadratic(f, g, opt): """Solve a system of two bivariate quadratic polynomial equations. Parameters ========== f: a single Expr or Poly First equation g: a single Expr or Poly Second Equation opt: an Options object For specifying keyword arguments and generators Returns ======= List[Tuple] A List of tuples. Solutions for symbols that satisfy the equations listed in seq. Examples ======== >>> from sympy import Options, Poly >>> from sympy.abc import x, y >>> from sympy.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) [(7/2 - sqrt(29)/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 None if len(G) != 2: raise SolveFailed x, y = opt.gens p, q = G if not p.gcd(q).is_ground: # not 0-dimensional raise SolveFailed p = Poly(p, x, expand=False) p_roots = [rcollect(expr, y) for expr in roots(p).keys()] q = q.ltrim(-1) 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 _solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial, transcendental equations, piecewise combinations thereof and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior (e.g., because of speed issues) set simplified=False in function arguments. To solve equations and systems of equations like recurrence relations or differential equations, use rsolve() or dsolve(), respectively. >>> from sympy import I, solve >>> from sympy.abc import x, y Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ def sympified_list(w): return map(sympify, iff(isinstance(w,(list, tuple, set)), w, [w])) # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. bare_f = not iterable(f) f, symbols = (sympified_list(w) for w in [f, symbols]) for i, fi in enumerate(f): if isinstance(fi, Equality): f[i] = fi.lhs - fi.rhs elif isinstance(fi, Poly): f[i] = fi.as_expr() elif isinstance(fi, bool) or fi.is_Relational: return reduce_inequalities(f, assume=flags.get('assume')) if not symbols: #get symbols from equations or supply dummy symbols since #solve(3,x) returns []...though it seems that it should raise some sort of error TODO symbols = set([]) for fi in f: symbols |= fi.free_symbols or set([Dummy('x')]) symbols = list(symbols) symbols.sort(key=Basic.sort_key) if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy('F%d' % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy('D%d' % i) else: raise TypeError('not a Symbol or a Function') symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if bare_f: f = f[0] # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f = piecewise_fold(f) if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if soln is None: n, d = solve_linear(f, x=symbols) if n.is_Symbol: soln = {n: cancel(d)} if soln: if symbol_swapped and isinstance(soln, dict): return dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in soln.iteritems()]) return soln symbol = symbols[0] # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, x=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] strategy = guess_solve_strategy(f, symbol) result = False # no solution was obtained if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: msg = "Cannot solve equation %s for %s" % (f, symbol) else: # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain crtical length of the roots. if poly.degree() > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, _ = f.as_numer_denom() dens = denoms(f, x=symbols) # reject any result that makes Q affirmatively 0; # if in doubt, keep it try: soln = _solve(P, symbol, **flags) except NotImplementedError: msg = "Cannot solve equation %s for %s" % (P, symbol) result = [] else: if dens: result = [s for s in soln if all(not checksol(den, {symbol: s}) for den in dens)] else: result = soln elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Pow): result = _solve(args[0], symbol, **flags) elif isinstance(f, Add): # we must search for a suitable change of variables # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: msg = "Could not convert to a polynomial equation: %s" % f_ result = [] else: soln = [s**m for s in _solve(f_, t)] # we might have introduced solutions from another branch # when changing variables; check and keep solutions # unless they definitely aren't a solution result = [s for s in soln if checksol(f, {symbol: s}) is not False] elif isinstance(f, Mul): result = [] for m in f.args: result.extend(_solve(m, symbol, **flags) or []) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) if m and m != 1: f_ = simplify(f*symbol**(-m)) try: sols = _solve(f_, symbol) except NotImplementedError: msg = 'Could not solve %s for %s' % (f_, symbol) else: # we might have introduced unwanted solutions # when multiplying by x**-m; check and keep solutions # unless they definitely aren't a solution if sols: result = [s for s in sols if checksol(f, {symbol: s}) is not False] else: msg = 'CV_2 calculated %d but it should have been other than 0 or 1' % m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) # this is the fallback for not getting any other solution if result is False or strategy == GS_TRANSCENDENTAL: # reject any result that makes any dens affirmatively 0, # if in doubt, keep it soln = tsolve(f_num, symbol) dens = denoms(f, x=symbols) if not dens: result = soln else: result = [s for s in soln if all(not checksol(den, {symbol: s}) for den in dens)] if result is False: raise NotImplementedError(msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True) and strategy != GS_RATIONAL: result = map(simplify, result) return result else: if not f: return [] else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: poly = g.as_poly(*symbols, extension=True) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None soln = solve_linear_system(matrix, *symbols, **flags) # Use swap_dict to ensure we return the same type as what was # passed; this is not necessary in the poly-system case which # only supports zero-dimensional systems if symbol_swapped and soln: soln = dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in soln.iteritems()]) return soln else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] return solve_poly_system(polys)
def solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial and transcendental equations and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior, eg. because of speed issues, set simplified=False in function arguments. To solve equations and systems of equations of other kind, eg. recurrence relations of differential equations use rsolve() or dsolve() functions respectively. >>> from sympy import * >>> x,y = symbols('xy') Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ if not symbols: raise ValueError('no symbols were given') if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] symbols = map(sympify, symbols) if any(not s.is_Symbol for s in symbols): raise TypeError('not a Symbol') if not isinstance(f, (tuple, list, set)): f = sympify(f) if isinstance(f, Equality): f = f.lhs - f.rhs if len(symbols) != 1: raise NotImplementedError('multivariate equation') symbol = symbols[0] strategy = guess_solve_strategy(f, symbol) if strategy == GS_POLY: poly = f.as_poly(symbol) assert poly is not None result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, Q = f.as_numer_denom() #TODO: check for Q != 0 return solve(P, symbol, **flags) elif strategy == GS_POLY_CV_1: # we must search for a suitable change of variable # collect exponents exponents_denom = list() args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the GCD of the denominators m = ilcm(*exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Symbol('t', positive=True, dummy=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: raise TypeError( "Could not convert to a polynomial equation: %s" % f_) cv_sols = solve(f_, t) result = list() for sol in cv_sols: result.append(sol**(S.One / m)) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) f1 = simplify(f * symbol**(-m)) result = solve(f1, symbol) # TODO: we might have introduced unwanted solutions # when multiplied by x**-m elif strategy == GS_TRASCENDENTAL: #a, b = f.as_numer_denom() # Let's throw away the denominator for now. When we have robust # assumptions, it should be checked, that for the solution, # b!=0. result = tsolve(f, *symbols) elif strategy == -1: raise Exception('Could not parse expression %s' % f) else: raise NotImplementedError( "No algorithms where implemented to solve equation %s" % f) if flags.get('simplified', True): return map(simplify, result) else: return result else: if not f: return {} else: polys = [] for g in f: g = sympify(g) if isinstance(g, Equality): g = g.lhs - g.rhs poly = g.as_poly(*symbols) if poly is not None: polys.append(poly) else: raise NotImplementedError if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for coeff, monom in poly.iter_terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff return solve_linear_system(matrix, *symbols, **flags) else: return solve_poly_system(polys)
def _rsolve_hypergeometric(f, x, P, Q, k, m): """Recursive wrapper to rsolve_hypergeometric. Returns a Tuple of (formula, series independent terms, maximum power of x in independent terms) if successful otherwise ``None``. See :func:`rsolve_hypergeometric` for details. """ from sympy.polys import lcm, roots from sympy.integrals import integrate # transformation - c proots, qroots = roots(P, k), roots(Q, k) all_roots = dict(proots) all_roots.update(qroots) scale = lcm( [r.as_numer_denom()[1] for r, t in all_roots.items() if r.is_rational]) f, P, Q, m = _transformation_c(f, x, P, Q, k, m, scale) # transformation - a qroots = roots(Q, k) if qroots: k_min = Min(*qroots.keys()) else: k_min = S.Zero shift = k_min + m f, P, Q, m = _transformation_a(f, x, P, Q, k, m, shift) l = (x * f).limit(x, 0) if not isinstance(l, Limit) and l != 0: # Ideally should only be l != 0 return None qroots = roots(Q, k) if qroots: k_max = Max(*qroots.keys()) else: k_max = S.Zero ind, mp = S.Zero, -oo for i in range(k_max + m + 1): r = f.diff(x, i).limit(x, 0) / factorial(i) if r.is_finite is False: old_f = f f, P, Q, m = _transformation_a(f, x, P, Q, k, m, i) f, P, Q, m = _transformation_e(f, x, P, Q, k, m) sol, ind, mp = _rsolve_hypergeometric(f, x, P, Q, k, m) sol = _apply_integrate(sol, x, k) sol = _apply_shift(sol, i) ind = integrate(ind, x) ind += (old_f - ind).limit(x, 0) # constant of integration mp += 1 return sol, ind, mp elif r: ind += r * x**(i + shift) pow_x = Rational((i + shift), scale) if pow_x > mp: mp = pow_x # maximum power of x ind = ind.subs(x, x**(1 / scale)) sol = _compute_formula(f, x, P, Q, k, m, k_max) sol = _apply_shift(sol, shift) sol = _apply_scale(sol, scale) return sol, ind, mp
def _solve(f, *symbols, **flags): """Solves equations and systems of equations. Currently supported are univariate polynomial, transcendental equations, piecewise combinations thereof and systems of linear and polynomial equations. Input is formed as a single expression or an equation, or an iterable container in case of an equation system. The type of output may vary and depends heavily on the input. For more details refer to more problem specific functions. By default all solutions are simplified to make the output more readable. If this is not the expected behavior (e.g., because of speed issues) set simplified=False in function arguments. To solve equations and systems of equations like recurrence relations or differential equations, use rsolve() or dsolve(), respectively. >>> from sympy import I, solve >>> from sympy.abc import x, y Solve a polynomial equation: >>> solve(x**4-1, x) [1, -1, -I, I] Solve a linear system: >>> solve((x+5*y-2, -3*x+6*y-15), x, y) {x: -3, y: 1} """ def sympified_list(w): return map(sympify, iff(isinstance(w, (list, tuple, set)), w, [w])) # make f and symbols into lists of sympified quantities # keeping track of how f was passed since if it is a list # a dictionary of results will be returned. bare_f = not iterable(f) f, symbols = (sympified_list(w) for w in [f, symbols]) for i, fi in enumerate(f): if isinstance(fi, Equality): f[i] = fi.lhs - fi.rhs elif isinstance(fi, Poly): f[i] = fi.as_expr() elif isinstance(fi, bool) or fi.is_Relational: return reduce_inequalities(f, assume=flags.get('assume')) if not symbols: #get symbols from equations or supply dummy symbols since #solve(3,x) returns []...though it seems that it should raise some sort of error TODO symbols = set([]) for fi in f: symbols |= fi.free_symbols or set([Dummy('x')]) symbols = list(symbols) symbols.sort(key=Basic.sort_key) if len(symbols) == 1: if isinstance(symbols[0], (list, tuple, set)): symbols = symbols[0] result = list() # Begin code handling for Function and Derivative instances # Basic idea: store all the passed symbols in symbols_passed, check to see # if any of them are Function or Derivative types, if so, use a dummy # symbol in their place, and set symbol_swapped = True so that other parts # of the code can be aware of the swap. Once all swapping is done, the # continue on with regular solving as usual, and swap back at the end of # the routine, so that whatever was passed in symbols is what is returned. symbols_new = [] symbol_swapped = False symbols_passed = list(symbols) for i, s in enumerate(symbols): if s.is_Symbol: s_new = s elif s.is_Function: symbol_swapped = True s_new = Dummy('F%d' % i) elif s.is_Derivative: symbol_swapped = True s_new = Dummy('D%d' % i) else: raise TypeError('not a Symbol or a Function') symbols_new.append(s_new) if symbol_swapped: swap_back_dict = dict(zip(symbols_new, symbols)) # End code for handling of Function and Derivative instances if bare_f: f = f[0] # Create a swap dictionary for storing the passed symbols to be solved # for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = f.subs(swap_dict) symbols = symbols_new # Any embedded piecewise functions need to be brought out to the # top level so that the appropriate strategy gets selected. f = piecewise_fold(f) if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if soln is None: n, d = solve_linear(f, x=symbols) if n.is_Symbol: soln = {n: cancel(d)} if soln: if symbol_swapped and isinstance(soln, dict): return dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in soln.iteritems()]) return soln symbol = symbols[0] # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, x=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] strategy = guess_solve_strategy(f, symbol) result = False # no solution was obtained if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: msg = "Cannot solve equation %s for %s" % (f, symbol) else: # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain crtical length of the roots. if poly.degree() > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, _ = f.as_numer_denom() dens = denoms(f, x=symbols) # reject any result that makes Q affirmatively 0; # if in doubt, keep it try: soln = _solve(P, symbol, **flags) except NotImplementedError: msg = "Cannot solve equation %s for %s" % (P, symbol) result = [] else: if dens: result = [ s for s in soln if all(not checksol(den, {symbol: s}) for den in dens) ] else: result = soln elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Pow): result = _solve(args[0], symbol, **flags) elif isinstance(f, Add): # we must search for a suitable change of variables # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: msg = "Could not convert to a polynomial equation: %s" % f_ result = [] else: soln = [s**m for s in _solve(f_, t)] # we might have introduced solutions from another branch # when changing variables; check and keep solutions # unless they definitely aren't a solution result = [ s for s in soln if checksol(f, {symbol: s}) is not False ] elif isinstance(f, Mul): result = [] for m in f.args: result.extend(_solve(m, symbol, **flags) or []) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) if m and m != 1: f_ = simplify(f * symbol**(-m)) try: sols = _solve(f_, symbol) except NotImplementedError: msg = 'Could not solve %s for %s' % (f_, symbol) else: # we might have introduced unwanted solutions # when multiplying by x**-m; check and keep solutions # unless they definitely aren't a solution if sols: result = [ s for s in sols if checksol(f, {symbol: s}) is not False ] else: msg = 'CV_2 calculated %d but it should have been other than 0 or 1' % m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) # this is the fallback for not getting any other solution if result is False or strategy == GS_TRANSCENDENTAL: # reject any result that makes any dens affirmatively 0, # if in doubt, keep it soln = tsolve(f_num, symbol) dens = denoms(f, x=symbols) if not dens: result = soln else: result = [ s for s in soln if all(not checksol(den, {symbol: s}) for den in dens) ] if result is False: raise NotImplementedError( msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True) and strategy != GS_RATIONAL: result = map(simplify, result) return result else: if not f: return [] else: # Create a swap dictionary for storing the passed symbols to be # solved for, so that they may be swapped back. if symbol_swapped: swap_dict = zip(symbols, symbols_new) f = [fi.subs(swap_dict) for fi in f] symbols = symbols_new polys = [] for g in f: poly = g.as_poly(*symbols, extension=True) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None soln = solve_linear_system(matrix, *symbols, **flags) # Use swap_dict to ensure we return the same type as what was # passed; this is not necessary in the poly-system case which # only supports zero-dimensional systems if symbol_swapped and soln: soln = dict([(swap_back_dict[k], v.subs(swap_back_dict)) for k, v in soln.iteritems()]) return soln else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] return solve_poly_system(polys)