polysys.py 13 KB

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