123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- """Solvers of systems of polynomial equations. """
- from sympy.core import S
- from sympy.core.sorting import default_sort_key
- from sympy.polys import Poly, groebner, roots
- from sympy.polys.polytools import parallel_poly_from_expr
- from sympy.polys.polyerrors import (ComputationFailed,
- PolificationFailed, CoercionFailed)
- from sympy.simplify import rcollect
- from sympy.utilities import postfixes
- from sympy.utilities.misc import filldedent
- class SolveFailed(Exception):
- """Raised when solver's conditions weren't met. """
- def solve_poly_system(seq, *gens, **args):
- """
- Solve a system of polynomial equations.
- Parameters
- ==========
- seq: a list/tuple/set
- Listing all the equations that are needed to be solved
- gens: generators
- generators of the equations in seq for which we want the
- solutions
- args: Keyword arguments
- Special options for solving the equations
- Returns
- =======
- List[Tuple]
- A List of tuples. Solutions for symbols that satisfy the
- equations listed in seq
- Examples
- ========
- >>> from sympy import solve_poly_system
- >>> from sympy.abc import x, y
- >>> solve_poly_system([x*y - 2*y, 2*y**2 - x**2], x, y)
- [(0, 0), (2, -sqrt(2)), (2, sqrt(2))]
- """
- try:
- polys, opt = parallel_poly_from_expr(seq, *gens, **args)
- except PolificationFailed as exc:
- raise ComputationFailed('solve_poly_system', len(seq), exc)
- if len(polys) == len(opt.gens) == 2:
- f, g = polys
- if all(i <= 2 for i in f.degree_list() + g.degree_list()):
- try:
- return solve_biquadratic(f, g, opt)
- except SolveFailed:
- pass
- return solve_generic(polys, opt)
- 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_generic(polys, opt):
- """
- Solve a generic system of polynomial equations.
- Returns all possible solutions over C[x_1, x_2, ..., x_m] of a
- set F = { f_1, f_2, ..., f_n } of polynomial equations, using
- Groebner basis approach. For now only zero-dimensional systems
- are supported, which means F can have at most a finite number
- of solutions.
- The algorithm works by the fact that, supposing G is the basis
- of F with respect to an elimination order (here lexicographic
- order is used), G and F generate the same ideal, they have the
- same set of solutions. By the elimination property, if G is a
- reduced, zero-dimensional Groebner basis, then there exists an
- univariate polynomial in G (in its last variable). This can be
- solved by computing its roots. Substituting all computed roots
- for the last (eliminated) variable in other elements of G, new
- polynomial system is generated. Applying the above procedure
- recursively, a finite number of solutions can be found.
- The ability of finding all solutions by this procedure depends
- on the root finding algorithms. If no solutions were found, it
- means only that roots() failed, but the system is solvable. To
- overcome this difficulty use numerical algorithms instead.
- Parameters
- ==========
- polys: a list/tuple/set
- Listing all the polynomial equations that are needed to be solved
- 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
- References
- ==========
- .. [Buchberger01] B. Buchberger, Groebner Bases: A Short
- Introduction for Systems Theorists, In: R. Moreno-Diaz,
- B. Buchberger, J.L. Freire, Proceedings of EUROCAST'01,
- February, 2001
- .. [Cox97] D. Cox, J. Little, D. O'Shea, Ideals, Varieties
- and Algorithms, Springer, Second Edition, 1997, pp. 112
- Examples
- ========
- >>> from sympy import Poly, Options
- >>> from sympy.solvers.polysys import solve_generic
- >>> from sympy.abc import x, y
- >>> NewOption = Options((x, y), {'domain': 'ZZ'})
- >>> a = Poly(x - y + 5, x, y, domain='ZZ')
- >>> b = Poly(x + y - 3, x, y, domain='ZZ')
- >>> solve_generic([a, b], NewOption)
- [(-1, 4)]
- >>> a = Poly(x - 2*y + 5, x, y, domain='ZZ')
- >>> b = Poly(2*x - y - 3, x, y, domain='ZZ')
- >>> solve_generic([a, b], NewOption)
- [(11/3, 13/3)]
- >>> a = Poly(x**2 + y, x, y, domain='ZZ')
- >>> b = Poly(x + y*4, x, y, domain='ZZ')
- >>> solve_generic([a, b], NewOption)
- [(0, 0), (1/4, -1/16)]
- """
- def _is_univariate(f):
- """Returns True if 'f' is univariate in its last variable. """
- for monom in f.monoms():
- if any(monom[:-1]):
- return False
- return True
- def _subs_root(f, gen, zero):
- """Replace generator with a root so that the result is nice. """
- p = f.as_expr({gen: zero})
- if f.degree(gen) >= 2:
- p = p.expand(deep=False)
- return p
- 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(basis) < len(gens):
- raise NotImplementedError(filldedent('''
- only zero-dimensional systems supported
- (finite number of solutions)
- '''))
- 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
- try:
- result = _solve_reduced_system(polys, opt.gens, entry=True)
- except CoercionFailed:
- raise NotImplementedError
- if result is not None:
- return sorted(result, key=default_sort_key)
- else:
- return None
- def solve_triangulated(polys, *gens, **args):
- """
- Solve a polynomial system using Gianni-Kalkbrenner algorithm.
- The algorithm proceeds by computing one Groebner basis in the ground
- domain and then by iteratively computing polynomial factorizations in
- appropriately constructed algebraic extensions of the ground domain.
- Parameters
- ==========
- polys: a list/tuple/set
- Listing all the equations that are needed to be solved
- gens: generators
- generators of the equations in polys for which we want the
- solutions
- args: Keyword arguments
- Special options for solving the equations
- Returns
- =======
- List[Tuple]
- A List of tuples. Solutions for symbols that satisfy the
- equations listed in polys
- Examples
- ========
- >>> from sympy import solve_triangulated
- >>> from sympy.abc import x, y, z
- >>> F = [x**2 + y + z - 1, x + y**2 + z - 1, x + y + z**2 - 1]
- >>> solve_triangulated(F, x, y, z)
- [(0, 0, 1), (0, 1, 0), (1, 0, 0)]
- References
- ==========
- 1. Patrizia Gianni, Teo Mora, Algebraic Solution of System of
- Polynomial Equations using Groebner Bases, AAECC-5 on Applied Algebra,
- Algebraic Algorithms and Error-Correcting Codes, LNCS 356 247--257, 1989
- """
- G = groebner(polys, gens, polys=True)
- G = list(reversed(G))
- domain = args.get('domain')
- if domain is not None:
- for i, g in enumerate(G):
- G[i] = g.set_domain(domain)
- f, G = G[0].ltrim(-1), G[1:]
- dom = f.get_domain()
- zeros = f.ground_roots()
- solutions = set()
- for zero in zeros:
- solutions.add(((zero,), dom))
- var_seq = reversed(gens[:-1])
- vars_seq = postfixes(gens[1:])
- for var, vars in zip(var_seq, vars_seq):
- _solutions = set()
- for values, dom in solutions:
- H, mapping = [], list(zip(vars, values))
- for g in G:
- _vars = (var,) + vars
- if g.has_only_gens(*_vars) and g.degree(var) != 0:
- h = g.ltrim(var).eval(dict(mapping))
- if g.degree(var) == h.degree():
- H.append(h)
- p = min(H, key=lambda h: h.degree())
- zeros = p.ground_roots()
- for zero in zeros:
- if not zero.is_Rational:
- dom_zero = dom.algebraic_field(zero)
- else:
- dom_zero = dom
- _solutions.add(((zero,) + values, dom_zero))
- solutions = _solutions
- solutions = list(solutions)
- for i, (solution, _) in enumerate(solutions):
- solutions[i] = solution
- return sorted(solutions, key=default_sort_key)
|