polysys.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. """Solvers of systems of polynomial equations. """
  2. from sympy.core import S
  3. from sympy.core.sorting import default_sort_key
  4. from sympy.polys import Poly, groebner, roots
  5. from sympy.polys.polytools import parallel_poly_from_expr
  6. from sympy.polys.polyerrors import (ComputationFailed,
  7. PolificationFailed, CoercionFailed)
  8. from sympy.simplify import rcollect
  9. from sympy.utilities import postfixes
  10. from sympy.utilities.misc import filldedent
  11. class SolveFailed(Exception):
  12. """Raised when solver's conditions weren't met. """
  13. def solve_poly_system(seq, *gens, **args):
  14. """
  15. Solve a system of polynomial equations.
  16. Parameters
  17. ==========
  18. seq: a list/tuple/set
  19. Listing all the equations that are needed to be solved
  20. gens: generators
  21. generators of the equations in seq for which we want the
  22. solutions
  23. args: Keyword arguments
  24. Special options for solving the equations
  25. Returns
  26. =======
  27. List[Tuple]
  28. A List of tuples. Solutions for symbols that satisfy the
  29. equations listed in seq
  30. Examples
  31. ========
  32. >>> from sympy import solve_poly_system
  33. >>> from sympy.abc import x, y
  34. >>> solve_poly_system([x*y - 2*y, 2*y**2 - x**2], x, y)
  35. [(0, 0), (2, -sqrt(2)), (2, sqrt(2))]
  36. """
  37. try:
  38. polys, opt = parallel_poly_from_expr(seq, *gens, **args)
  39. except PolificationFailed as exc:
  40. raise ComputationFailed('solve_poly_system', len(seq), exc)
  41. if len(polys) == len(opt.gens) == 2:
  42. f, g = polys
  43. if all(i <= 2 for i in f.degree_list() + g.degree_list()):
  44. try:
  45. return solve_biquadratic(f, g, opt)
  46. except SolveFailed:
  47. pass
  48. return solve_generic(polys, opt)
  49. def solve_biquadratic(f, g, opt):
  50. """Solve a system of two bivariate quadratic polynomial equations.
  51. Parameters
  52. ==========
  53. f: a single Expr or Poly
  54. First equation
  55. g: a single Expr or Poly
  56. Second Equation
  57. opt: an Options object
  58. For specifying keyword arguments and generators
  59. Returns
  60. =======
  61. List[Tuple]
  62. A List of tuples. Solutions for symbols that satisfy the
  63. equations listed in seq.
  64. Examples
  65. ========
  66. >>> from sympy import Options, Poly
  67. >>> from sympy.abc import x, y
  68. >>> from sympy.solvers.polysys import solve_biquadratic
  69. >>> NewOption = Options((x, y), {'domain': 'ZZ'})
  70. >>> a = Poly(y**2 - 4 + x, y, x, domain='ZZ')
  71. >>> b = Poly(y*2 + 3*x - 7, y, x, domain='ZZ')
  72. >>> solve_biquadratic(a, b, NewOption)
  73. [(1/3, 3), (41/27, 11/9)]
  74. >>> a = Poly(y + x**2 - 3, y, x, domain='ZZ')
  75. >>> b = Poly(-y + x - 4, y, x, domain='ZZ')
  76. >>> solve_biquadratic(a, b, NewOption)
  77. [(7/2 - sqrt(29)/2, -sqrt(29)/2 - 1/2), (sqrt(29)/2 + 7/2, -1/2 + \
  78. sqrt(29)/2)]
  79. """
  80. G = groebner([f, g])
  81. if len(G) == 1 and G[0].is_ground:
  82. return None
  83. if len(G) != 2:
  84. raise SolveFailed
  85. x, y = opt.gens
  86. p, q = G
  87. if not p.gcd(q).is_ground:
  88. # not 0-dimensional
  89. raise SolveFailed
  90. p = Poly(p, x, expand=False)
  91. p_roots = [rcollect(expr, y) for expr in roots(p).keys()]
  92. q = q.ltrim(-1)
  93. q_roots = list(roots(q).keys())
  94. solutions = []
  95. for q_root in q_roots:
  96. for p_root in p_roots:
  97. solution = (p_root.subs(y, q_root), q_root)
  98. solutions.append(solution)
  99. return sorted(solutions, key=default_sort_key)
  100. def solve_generic(polys, opt):
  101. """
  102. Solve a generic system of polynomial equations.
  103. Returns all possible solutions over C[x_1, x_2, ..., x_m] of a
  104. set F = { f_1, f_2, ..., f_n } of polynomial equations, using
  105. Groebner basis approach. For now only zero-dimensional systems
  106. are supported, which means F can have at most a finite number
  107. of solutions.
  108. The algorithm works by the fact that, supposing G is the basis
  109. of F with respect to an elimination order (here lexicographic
  110. order is used), G and F generate the same ideal, they have the
  111. same set of solutions. By the elimination property, if G is a
  112. reduced, zero-dimensional Groebner basis, then there exists an
  113. univariate polynomial in G (in its last variable). This can be
  114. solved by computing its roots. Substituting all computed roots
  115. for the last (eliminated) variable in other elements of G, new
  116. polynomial system is generated. Applying the above procedure
  117. recursively, a finite number of solutions can be found.
  118. The ability of finding all solutions by this procedure depends
  119. on the root finding algorithms. If no solutions were found, it
  120. means only that roots() failed, but the system is solvable. To
  121. overcome this difficulty use numerical algorithms instead.
  122. Parameters
  123. ==========
  124. polys: a list/tuple/set
  125. Listing all the polynomial equations that are needed to be solved
  126. opt: an Options object
  127. For specifying keyword arguments and generators
  128. Returns
  129. =======
  130. List[Tuple]
  131. A List of tuples. Solutions for symbols that satisfy the
  132. equations listed in seq
  133. References
  134. ==========
  135. .. [Buchberger01] B. Buchberger, Groebner Bases: A Short
  136. Introduction for Systems Theorists, In: R. Moreno-Diaz,
  137. B. Buchberger, J.L. Freire, Proceedings of EUROCAST'01,
  138. February, 2001
  139. .. [Cox97] D. Cox, J. Little, D. O'Shea, Ideals, Varieties
  140. and Algorithms, Springer, Second Edition, 1997, pp. 112
  141. Examples
  142. ========
  143. >>> from sympy import Poly, Options
  144. >>> from sympy.solvers.polysys import solve_generic
  145. >>> from sympy.abc import x, y
  146. >>> NewOption = Options((x, y), {'domain': 'ZZ'})
  147. >>> a = Poly(x - y + 5, x, y, domain='ZZ')
  148. >>> b = Poly(x + y - 3, x, y, domain='ZZ')
  149. >>> solve_generic([a, b], NewOption)
  150. [(-1, 4)]
  151. >>> a = Poly(x - 2*y + 5, x, y, domain='ZZ')
  152. >>> b = Poly(2*x - y - 3, x, y, domain='ZZ')
  153. >>> solve_generic([a, b], NewOption)
  154. [(11/3, 13/3)]
  155. >>> a = Poly(x**2 + y, x, y, domain='ZZ')
  156. >>> b = Poly(x + y*4, x, y, domain='ZZ')
  157. >>> solve_generic([a, b], NewOption)
  158. [(0, 0), (1/4, -1/16)]
  159. """
  160. def _is_univariate(f):
  161. """Returns True if 'f' is univariate in its last variable. """
  162. for monom in f.monoms():
  163. if any(monom[:-1]):
  164. return False
  165. return True
  166. def _subs_root(f, gen, zero):
  167. """Replace generator with a root so that the result is nice. """
  168. p = f.as_expr({gen: zero})
  169. if f.degree(gen) >= 2:
  170. p = p.expand(deep=False)
  171. return p
  172. def _solve_reduced_system(system, gens, entry=False):
  173. """Recursively solves reduced polynomial systems. """
  174. if len(system) == len(gens) == 1:
  175. zeros = list(roots(system[0], gens[-1]).keys())
  176. return [(zero,) for zero in zeros]
  177. basis = groebner(system, gens, polys=True)
  178. if len(basis) == 1 and basis[0].is_ground:
  179. if not entry:
  180. return []
  181. else:
  182. return None
  183. univariate = list(filter(_is_univariate, basis))
  184. if len(basis) < len(gens):
  185. raise NotImplementedError(filldedent('''
  186. only zero-dimensional systems supported
  187. (finite number of solutions)
  188. '''))
  189. if len(univariate) == 1:
  190. f = univariate.pop()
  191. else:
  192. raise NotImplementedError(filldedent('''
  193. only zero-dimensional systems supported
  194. (finite number of solutions)
  195. '''))
  196. gens = f.gens
  197. gen = gens[-1]
  198. zeros = list(roots(f.ltrim(gen)).keys())
  199. if not zeros:
  200. return []
  201. if len(basis) == 1:
  202. return [(zero,) for zero in zeros]
  203. solutions = []
  204. for zero in zeros:
  205. new_system = []
  206. new_gens = gens[:-1]
  207. for b in basis[:-1]:
  208. eq = _subs_root(b, gen, zero)
  209. if eq is not S.Zero:
  210. new_system.append(eq)
  211. for solution in _solve_reduced_system(new_system, new_gens):
  212. solutions.append(solution + (zero,))
  213. if solutions and len(solutions[0]) != len(gens):
  214. raise NotImplementedError(filldedent('''
  215. only zero-dimensional systems supported
  216. (finite number of solutions)
  217. '''))
  218. return solutions
  219. try:
  220. result = _solve_reduced_system(polys, opt.gens, entry=True)
  221. except CoercionFailed:
  222. raise NotImplementedError
  223. if result is not None:
  224. return sorted(result, key=default_sort_key)
  225. else:
  226. return None
  227. def solve_triangulated(polys, *gens, **args):
  228. """
  229. Solve a polynomial system using Gianni-Kalkbrenner algorithm.
  230. The algorithm proceeds by computing one Groebner basis in the ground
  231. domain and then by iteratively computing polynomial factorizations in
  232. appropriately constructed algebraic extensions of the ground domain.
  233. Parameters
  234. ==========
  235. polys: a list/tuple/set
  236. Listing all the equations that are needed to be solved
  237. gens: generators
  238. generators of the equations in polys for which we want the
  239. solutions
  240. args: Keyword arguments
  241. Special options for solving the equations
  242. Returns
  243. =======
  244. List[Tuple]
  245. A List of tuples. Solutions for symbols that satisfy the
  246. equations listed in polys
  247. Examples
  248. ========
  249. >>> from sympy import solve_triangulated
  250. >>> from sympy.abc import x, y, z
  251. >>> F = [x**2 + y + z - 1, x + y**2 + z - 1, x + y + z**2 - 1]
  252. >>> solve_triangulated(F, x, y, z)
  253. [(0, 0, 1), (0, 1, 0), (1, 0, 0)]
  254. References
  255. ==========
  256. 1. Patrizia Gianni, Teo Mora, Algebraic Solution of System of
  257. Polynomial Equations using Groebner Bases, AAECC-5 on Applied Algebra,
  258. Algebraic Algorithms and Error-Correcting Codes, LNCS 356 247--257, 1989
  259. """
  260. G = groebner(polys, gens, polys=True)
  261. G = list(reversed(G))
  262. domain = args.get('domain')
  263. if domain is not None:
  264. for i, g in enumerate(G):
  265. G[i] = g.set_domain(domain)
  266. f, G = G[0].ltrim(-1), G[1:]
  267. dom = f.get_domain()
  268. zeros = f.ground_roots()
  269. solutions = set()
  270. for zero in zeros:
  271. solutions.add(((zero,), dom))
  272. var_seq = reversed(gens[:-1])
  273. vars_seq = postfixes(gens[1:])
  274. for var, vars in zip(var_seq, vars_seq):
  275. _solutions = set()
  276. for values, dom in solutions:
  277. H, mapping = [], list(zip(vars, values))
  278. for g in G:
  279. _vars = (var,) + vars
  280. if g.has_only_gens(*_vars) and g.degree(var) != 0:
  281. h = g.ltrim(var).eval(dict(mapping))
  282. if g.degree(var) == h.degree():
  283. H.append(h)
  284. p = min(H, key=lambda h: h.degree())
  285. zeros = p.ground_roots()
  286. for zero in zeros:
  287. if not zero.is_Rational:
  288. dom_zero = dom.algebraic_field(zero)
  289. else:
  290. dom_zero = dom
  291. _solutions.add(((zero,) + values, dom_zero))
  292. solutions = _solutions
  293. solutions = list(solutions)
  294. for i, (solution, _) in enumerate(solutions):
  295. solutions[i] = solution
  296. return sorted(solutions, key=default_sort_key)