polysys.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. """Solvers of systems of polynomial equations. """
  2. from __future__ import annotations
  3. from typing import Any
  4. from collections.abc import Sequence, Iterable
  5. import itertools
  6. from sympy import Dummy
  7. from sympy.core import S
  8. from sympy.core.expr import Expr
  9. from sympy.core.exprtools import factor_terms
  10. from sympy.core.sorting import default_sort_key
  11. from sympy.logic.boolalg import Boolean
  12. from sympy.polys import Poly, groebner, roots
  13. from sympy.polys.domains import ZZ
  14. from sympy.polys.polyoptions import build_options
  15. from sympy.polys.polytools import parallel_poly_from_expr, sqf_part
  16. from sympy.polys.polyerrors import (
  17. ComputationFailed,
  18. PolificationFailed,
  19. CoercionFailed,
  20. GeneratorsNeeded,
  21. DomainError
  22. )
  23. from sympy.simplify import rcollect
  24. from sympy.utilities import postfixes
  25. from sympy.utilities.iterables import cartes
  26. from sympy.utilities.misc import filldedent
  27. from sympy.logic.boolalg import Or, And
  28. from sympy.core.relational import Eq
  29. class SolveFailed(Exception):
  30. """Raised when solver's conditions were not met. """
  31. def solve_poly_system(seq, *gens, strict=False, **args):
  32. """
  33. Return a list of solutions for the system of polynomial equations
  34. or else None.
  35. Parameters
  36. ==========
  37. seq: a list/tuple/set
  38. Listing all the equations that are needed to be solved
  39. gens: generators
  40. generators of the equations in seq for which we want the
  41. solutions
  42. strict: a boolean (default is False)
  43. if strict is True, NotImplementedError will be raised if
  44. the solution is known to be incomplete (which can occur if
  45. not all solutions are expressible in radicals)
  46. args: Keyword arguments
  47. Special options for solving the equations.
  48. Returns
  49. =======
  50. List[Tuple]
  51. a list of tuples with elements being solutions for the
  52. symbols in the order they were passed as gens
  53. None
  54. None is returned when the computed basis contains only the ground.
  55. Examples
  56. ========
  57. >>> from sympy import solve_poly_system
  58. >>> from sympy.abc import x, y
  59. >>> solve_poly_system([x*y - 2*y, 2*y**2 - x**2], x, y)
  60. [(0, 0), (2, -sqrt(2)), (2, sqrt(2))]
  61. >>> solve_poly_system([x**5 - x + y**3, y**2 - 1], x, y, strict=True)
  62. Traceback (most recent call last):
  63. ...
  64. UnsolvableFactorError
  65. """
  66. try:
  67. polys, opt = parallel_poly_from_expr(seq, *gens, **args)
  68. except PolificationFailed as exc:
  69. raise ComputationFailed('solve_poly_system', len(seq), exc)
  70. if len(polys) == len(opt.gens) == 2:
  71. f, g = polys
  72. if all(i <= 2 for i in f.degree_list() + g.degree_list()):
  73. try:
  74. return solve_biquadratic(f, g, opt)
  75. except SolveFailed:
  76. pass
  77. return solve_generic(polys, opt, strict=strict)
  78. def solve_biquadratic(f, g, opt):
  79. """Solve a system of two bivariate quadratic polynomial equations.
  80. Parameters
  81. ==========
  82. f: a single Expr or Poly
  83. First equation
  84. g: a single Expr or Poly
  85. Second Equation
  86. opt: an Options object
  87. For specifying keyword arguments and generators
  88. Returns
  89. =======
  90. List[Tuple]
  91. a list of tuples with elements being solutions for the
  92. symbols in the order they were passed as gens
  93. None
  94. None is returned when the computed basis contains only the ground.
  95. Examples
  96. ========
  97. >>> from sympy import Options, Poly
  98. >>> from sympy.abc import x, y
  99. >>> from sympy.solvers.polysys import solve_biquadratic
  100. >>> NewOption = Options((x, y), {'domain': 'ZZ'})
  101. >>> a = Poly(y**2 - 4 + x, y, x, domain='ZZ')
  102. >>> b = Poly(y*2 + 3*x - 7, y, x, domain='ZZ')
  103. >>> solve_biquadratic(a, b, NewOption)
  104. [(1/3, 3), (41/27, 11/9)]
  105. >>> a = Poly(y + x**2 - 3, y, x, domain='ZZ')
  106. >>> b = Poly(-y + x - 4, y, x, domain='ZZ')
  107. >>> solve_biquadratic(a, b, NewOption)
  108. [(7/2 - sqrt(29)/2, -sqrt(29)/2 - 1/2), (sqrt(29)/2 + 7/2, -1/2 + \
  109. sqrt(29)/2)]
  110. """
  111. G = groebner([f, g])
  112. if len(G) == 1 and G[0].is_ground:
  113. return None
  114. if len(G) != 2:
  115. raise SolveFailed
  116. x, y = opt.gens
  117. p, q = G
  118. if not p.gcd(q).is_ground:
  119. # not 0-dimensional
  120. raise SolveFailed
  121. p = Poly(p, x, expand=False)
  122. p_roots = [rcollect(expr, y) for expr in roots(p).keys()]
  123. q = q.ltrim(-1)
  124. q_roots = list(roots(q).keys())
  125. solutions = [(p_root.subs(y, q_root), q_root) for q_root, p_root in
  126. itertools.product(q_roots, p_roots)]
  127. return sorted(solutions, key=default_sort_key)
  128. def solve_generic(polys, opt, strict=False):
  129. """
  130. Solve a generic system of polynomial equations.
  131. Returns all possible solutions over C[x_1, x_2, ..., x_m] of a
  132. set F = { f_1, f_2, ..., f_n } of polynomial equations, using
  133. Groebner basis approach. For now only zero-dimensional systems
  134. are supported, which means F can have at most a finite number
  135. of solutions. If the basis contains only the ground, None is
  136. returned.
  137. The algorithm works by the fact that, supposing G is the basis
  138. of F with respect to an elimination order (here lexicographic
  139. order is used), G and F generate the same ideal, they have the
  140. same set of solutions. By the elimination property, if G is a
  141. reduced, zero-dimensional Groebner basis, then there exists an
  142. univariate polynomial in G (in its last variable). This can be
  143. solved by computing its roots. Substituting all computed roots
  144. for the last (eliminated) variable in other elements of G, new
  145. polynomial system is generated. Applying the above procedure
  146. recursively, a finite number of solutions can be found.
  147. The ability of finding all solutions by this procedure depends
  148. on the root finding algorithms. If no solutions were found, it
  149. means only that roots() failed, but the system is solvable. To
  150. overcome this difficulty use numerical algorithms instead.
  151. Parameters
  152. ==========
  153. polys: a list/tuple/set
  154. Listing all the polynomial equations that are needed to be solved
  155. opt: an Options object
  156. For specifying keyword arguments and generators
  157. strict: a boolean
  158. If strict is True, NotImplementedError will be raised if the solution
  159. is known to be incomplete
  160. Returns
  161. =======
  162. List[Tuple]
  163. a list of tuples with elements being solutions for the
  164. symbols in the order they were passed as gens
  165. None
  166. None is returned when the computed basis contains only the ground.
  167. References
  168. ==========
  169. .. [Buchberger01] B. Buchberger, Groebner Bases: A Short
  170. Introduction for Systems Theorists, In: R. Moreno-Diaz,
  171. B. Buchberger, J.L. Freire, Proceedings of EUROCAST'01,
  172. February, 2001
  173. .. [Cox97] D. Cox, J. Little, D. O'Shea, Ideals, Varieties
  174. and Algorithms, Springer, Second Edition, 1997, pp. 112
  175. Raises
  176. ========
  177. NotImplementedError
  178. If the system is not zero-dimensional (does not have a finite
  179. number of solutions)
  180. UnsolvableFactorError
  181. If ``strict`` is True and not all solution components are
  182. expressible in radicals
  183. Examples
  184. ========
  185. >>> from sympy import Poly, Options
  186. >>> from sympy.solvers.polysys import solve_generic
  187. >>> from sympy.abc import x, y
  188. >>> NewOption = Options((x, y), {'domain': 'ZZ'})
  189. >>> a = Poly(x - y + 5, x, y, domain='ZZ')
  190. >>> b = Poly(x + y - 3, x, y, domain='ZZ')
  191. >>> solve_generic([a, b], NewOption)
  192. [(-1, 4)]
  193. >>> a = Poly(x - 2*y + 5, x, y, domain='ZZ')
  194. >>> b = Poly(2*x - y - 3, x, y, domain='ZZ')
  195. >>> solve_generic([a, b], NewOption)
  196. [(11/3, 13/3)]
  197. >>> a = Poly(x**2 + y, x, y, domain='ZZ')
  198. >>> b = Poly(x + y*4, x, y, domain='ZZ')
  199. >>> solve_generic([a, b], NewOption)
  200. [(0, 0), (1/4, -1/16)]
  201. >>> a = Poly(x**5 - x + y**3, x, y, domain='ZZ')
  202. >>> b = Poly(y**2 - 1, x, y, domain='ZZ')
  203. >>> solve_generic([a, b], NewOption, strict=True)
  204. Traceback (most recent call last):
  205. ...
  206. UnsolvableFactorError
  207. """
  208. def _is_univariate(f):
  209. """Returns True if 'f' is univariate in its last variable. """
  210. for monom in f.monoms():
  211. if any(monom[:-1]):
  212. return False
  213. return True
  214. def _subs_root(f, gen, zero):
  215. """Replace generator with a root so that the result is nice. """
  216. p = f.as_expr({gen: zero})
  217. if f.degree(gen) >= 2:
  218. p = p.expand(deep=False)
  219. return p
  220. def _solve_reduced_system(system, gens, entry=False):
  221. """Recursively solves reduced polynomial systems. """
  222. if len(system) == len(gens) == 1:
  223. # the below line will produce UnsolvableFactorError if
  224. # strict=True and the solution from `roots` is incomplete
  225. zeros = list(roots(system[0], gens[-1], strict=strict).keys())
  226. return [(zero,) for zero in zeros]
  227. basis = groebner(system, gens, polys=True)
  228. if len(basis) == 1 and basis[0].is_ground:
  229. if not entry:
  230. return []
  231. else:
  232. return None
  233. univariate = list(filter(_is_univariate, basis))
  234. if len(basis) < len(gens):
  235. raise NotImplementedError(filldedent('''
  236. only zero-dimensional systems supported
  237. (finite number of solutions)
  238. '''))
  239. if len(univariate) == 1:
  240. f = univariate.pop()
  241. else:
  242. raise NotImplementedError(filldedent('''
  243. only zero-dimensional systems supported
  244. (finite number of solutions)
  245. '''))
  246. gens = f.gens
  247. gen = gens[-1]
  248. # the below line will produce UnsolvableFactorError if
  249. # strict=True and the solution from `roots` is incomplete
  250. zeros = list(roots(f.ltrim(gen), strict=strict).keys())
  251. if not zeros:
  252. return []
  253. if len(basis) == 1:
  254. return [(zero,) for zero in zeros]
  255. solutions = []
  256. for zero in zeros:
  257. new_system = []
  258. new_gens = gens[:-1]
  259. for b in basis[:-1]:
  260. eq = _subs_root(b, gen, zero)
  261. if eq is not S.Zero:
  262. new_system.append(eq)
  263. for solution in _solve_reduced_system(new_system, new_gens):
  264. solutions.append(solution + (zero,))
  265. if solutions and len(solutions[0]) != len(gens):
  266. raise NotImplementedError(filldedent('''
  267. only zero-dimensional systems supported
  268. (finite number of solutions)
  269. '''))
  270. return solutions
  271. try:
  272. result = _solve_reduced_system(polys, opt.gens, entry=True)
  273. except CoercionFailed:
  274. raise NotImplementedError
  275. if result is not None:
  276. return sorted(result, key=default_sort_key)
  277. def solve_triangulated(polys, *gens, **args):
  278. """
  279. Solve a polynomial system using Gianni-Kalkbrenner algorithm.
  280. The algorithm proceeds by computing one Groebner basis in the ground
  281. domain and then by iteratively computing polynomial factorizations in
  282. appropriately constructed algebraic extensions of the ground domain.
  283. Parameters
  284. ==========
  285. polys: a list/tuple/set
  286. Listing all the equations that are needed to be solved
  287. gens: generators
  288. generators of the equations in polys for which we want the
  289. solutions
  290. args: Keyword arguments
  291. Special options for solving the equations
  292. Returns
  293. =======
  294. List[Tuple]
  295. A List of tuples. Solutions for symbols that satisfy the
  296. equations listed in polys
  297. Examples
  298. ========
  299. >>> from sympy import solve_triangulated
  300. >>> from sympy.abc import x, y, z
  301. >>> F = [x**2 + y + z - 1, x + y**2 + z - 1, x + y + z**2 - 1]
  302. >>> solve_triangulated(F, x, y, z)
  303. [(0, 0, 1), (0, 1, 0), (1, 0, 0)]
  304. Using extension for algebraic solutions.
  305. >>> solve_triangulated(F, x, y, z, extension=True) #doctest: +NORMALIZE_WHITESPACE
  306. [(0, 0, 1), (0, 1, 0), (1, 0, 0),
  307. (CRootOf(x**2 + 2*x - 1, 0), CRootOf(x**2 + 2*x - 1, 0), CRootOf(x**2 + 2*x - 1, 0)),
  308. (CRootOf(x**2 + 2*x - 1, 1), CRootOf(x**2 + 2*x - 1, 1), CRootOf(x**2 + 2*x - 1, 1))]
  309. References
  310. ==========
  311. 1. Patrizia Gianni, Teo Mora, Algebraic Solution of System of
  312. Polynomial Equations using Groebner Bases, AAECC-5 on Applied Algebra,
  313. Algebraic Algorithms and Error-Correcting Codes, LNCS 356 247--257, 1989
  314. """
  315. opt = build_options(gens, args)
  316. G = groebner(polys, gens, polys=True)
  317. G = list(reversed(G))
  318. extension = opt.get('extension', False)
  319. if extension:
  320. def _solve_univariate(f):
  321. return [r for r, _ in f.all_roots(multiple=False, radicals=False)]
  322. else:
  323. domain = opt.get('domain')
  324. if domain is not None:
  325. for i, g in enumerate(G):
  326. G[i] = g.set_domain(domain)
  327. def _solve_univariate(f):
  328. return list(f.ground_roots().keys())
  329. f, G = G[0].ltrim(-1), G[1:]
  330. dom = f.get_domain()
  331. zeros = _solve_univariate(f)
  332. if extension:
  333. solutions = {((zero,), dom.algebraic_field(zero)) for zero in zeros}
  334. else:
  335. solutions = {((zero,), dom) for zero in zeros}
  336. var_seq = reversed(gens[:-1])
  337. vars_seq = postfixes(gens[1:])
  338. for var, vars in zip(var_seq, vars_seq):
  339. _solutions = set()
  340. for values, dom in solutions:
  341. H, mapping = [], list(zip(vars, values))
  342. for g in G:
  343. _vars = (var,) + vars
  344. if g.has_only_gens(*_vars) and g.degree(var) != 0:
  345. if extension:
  346. g = g.set_domain(g.domain.unify(dom))
  347. h = g.ltrim(var).eval(dict(mapping))
  348. if g.degree(var) == h.degree():
  349. H.append(h)
  350. p = min(H, key=lambda h: h.degree())
  351. zeros = _solve_univariate(p)
  352. for zero in zeros:
  353. if not (zero in dom):
  354. dom_zero = dom.algebraic_field(zero)
  355. else:
  356. dom_zero = dom
  357. _solutions.add(((zero,) + values, dom_zero))
  358. solutions = _solutions
  359. return sorted((s for s, _ in solutions), key=default_sort_key)
  360. def factor_system(eqs: Sequence[Expr | complex], gens: Sequence[Expr] = (), **kwargs: Any) -> list[list[Expr]]:
  361. """
  362. Factorizes a system of polynomial equations into
  363. irreducible subsystems.
  364. Parameters
  365. ==========
  366. eqs : list
  367. List of expressions to be factored.
  368. Each expression is assumed to be equal to zero.
  369. gens : list, optional
  370. Generator(s) of the polynomial ring.
  371. If not provided, all free symbols will be used.
  372. **kwargs : dict, optional
  373. Same optional arguments taken by ``factor``
  374. Returns
  375. =======
  376. list[list[Expr]]
  377. A list of lists of expressions, where each sublist represents
  378. an irreducible subsystem. When solved, each subsystem gives
  379. one component of the solution. Only generic solutions are
  380. returned (cases not requiring parameters to be zero).
  381. Examples
  382. ========
  383. >>> from sympy.solvers.polysys import factor_system, factor_system_cond
  384. >>> from sympy.abc import x, y, a, b, c
  385. A simple system with multiple solutions:
  386. >>> factor_system([x**2 - 1, y - 1])
  387. [[x + 1, y - 1], [x - 1, y - 1]]
  388. A system with no solution:
  389. >>> factor_system([x, 1])
  390. []
  391. A system where any value of the symbol(s) is a solution:
  392. >>> factor_system([x - x, (x + 1)**2 - (x**2 + 2*x + 1)])
  393. [[]]
  394. A system with no generic solution:
  395. >>> factor_system([a*x*(x-1), b*y, c], [x, y])
  396. []
  397. If c is added to the unknowns then the system has a generic solution:
  398. >>> factor_system([a*x*(x-1), b*y, c], [x, y, c])
  399. [[x - 1, y, c], [x, y, c]]
  400. Alternatively :func:`factor_system_cond` can be used to get degenerate
  401. cases as well:
  402. >>> factor_system_cond([a*x*(x-1), b*y, c], [x, y])
  403. [[x - 1, y, c], [x, y, c], [x - 1, b, c], [x, b, c], [y, a, c], [a, b, c]]
  404. Each of the above cases is only satisfiable in the degenerate case `c = 0`.
  405. The solution set of the original system represented
  406. by eqs is the union of the solution sets of the
  407. factorized systems.
  408. An empty list [] means no generic solution exists.
  409. A list containing an empty list [[]] means any value of
  410. the symbol(s) is a solution.
  411. See Also
  412. ========
  413. factor_system_cond : Returns both generic and degenerate solutions
  414. factor_system_bool : Returns a Boolean combination representing all solutions
  415. sympy.polys.polytools.factor : Factors a polynomial into irreducible factors
  416. over the rational numbers
  417. """
  418. systems = _factor_system_poly_from_expr(eqs, gens, **kwargs)
  419. systems_generic = [sys for sys in systems if not _is_degenerate(sys)]
  420. systems_expr = [[p.as_expr() for p in system] for system in systems_generic]
  421. return systems_expr
  422. def _is_degenerate(system: list[Poly]) -> bool:
  423. """Helper function to check if a system is degenerate"""
  424. return any(p.is_ground for p in system)
  425. def factor_system_bool(eqs: Sequence[Expr | complex], gens: Sequence[Expr] = (), **kwargs: Any) -> Boolean:
  426. """
  427. Factorizes a system of polynomial equations into irreducible DNF.
  428. The system of expressions(eqs) is taken and a Boolean combination
  429. of equations is returned that represents the same solution set.
  430. The result is in disjunctive normal form (OR of ANDs).
  431. Parameters
  432. ==========
  433. eqs : list
  434. List of expressions to be factored.
  435. Each expression is assumed to be equal to zero.
  436. gens : list, optional
  437. Generator(s) of the polynomial ring.
  438. If not provided, all free symbols will be used.
  439. **kwargs : dict, optional
  440. Optional keyword arguments
  441. Returns
  442. =======
  443. Boolean:
  444. A Boolean combination of equations. The result is typically in
  445. the form of a conjunction (AND) of a disjunctive normal form
  446. with additional conditions.
  447. Examples
  448. ========
  449. >>> from sympy.solvers.polysys import factor_system_bool
  450. >>> from sympy.abc import x, y, a, b, c
  451. >>> factor_system_bool([x**2 - 1])
  452. Eq(x - 1, 0) | Eq(x + 1, 0)
  453. >>> factor_system_bool([x**2 - 1, y - 1])
  454. (Eq(x - 1, 0) & Eq(y - 1, 0)) | (Eq(x + 1, 0) & Eq(y - 1, 0))
  455. >>> eqs = [a * (x - 1), b]
  456. >>> factor_system_bool([a*(x - 1), b])
  457. (Eq(a, 0) & Eq(b, 0)) | (Eq(b, 0) & Eq(x - 1, 0))
  458. >>> factor_system_bool([a*x**2 - a, b*(x + 1), c], [x])
  459. (Eq(c, 0) & Eq(x + 1, 0)) | (Eq(a, 0) & Eq(b, 0) & Eq(c, 0)) | (Eq(b, 0) & Eq(c, 0) & Eq(x - 1, 0))
  460. >>> factor_system_bool([x**2 + 2*x + 1 - (x + 1)**2])
  461. True
  462. The result is logically equivalent to the system of equations
  463. i.e. eqs. The function returns ``True`` when all values of
  464. the symbol(s) is a solution and ``False`` when the system
  465. cannot be solved.
  466. See Also
  467. ========
  468. factor_system : Returns factors and solvability condition separately
  469. factor_system_cond : Returns both factors and conditions
  470. """
  471. systems = factor_system_cond(eqs, gens, **kwargs)
  472. return Or(*[And(*[Eq(eq, 0) for eq in sys]) for sys in systems])
  473. def factor_system_cond(eqs: Sequence[Expr | complex], gens: Sequence[Expr] = (), **kwargs: Any) -> list[list[Expr]]:
  474. """
  475. Factorizes a polynomial system into irreducible components and returns
  476. both generic and degenerate solutions.
  477. Parameters
  478. ==========
  479. eqs : list
  480. List of expressions to be factored.
  481. Each expression is assumed to be equal to zero.
  482. gens : list, optional
  483. Generator(s) of the polynomial ring.
  484. If not provided, all free symbols will be used.
  485. **kwargs : dict, optional
  486. Optional keyword arguments.
  487. Returns
  488. =======
  489. list[list[Expr]]
  490. A list of lists of expressions, where each sublist represents
  491. an irreducible subsystem. Includes both generic solutions and
  492. degenerate cases requiring equality conditions on parameters.
  493. Examples
  494. ========
  495. >>> from sympy.solvers.polysys import factor_system_cond
  496. >>> from sympy.abc import x, y, a, b, c
  497. >>> factor_system_cond([x**2 - 4, a*y, b], [x, y])
  498. [[x + 2, y, b], [x - 2, y, b], [x + 2, a, b], [x - 2, a, b]]
  499. >>> factor_system_cond([a*x*(x-1), b*y, c], [x, y])
  500. [[x - 1, y, c], [x, y, c], [x - 1, b, c], [x, b, c], [y, a, c], [a, b, c]]
  501. An empty list [] means no solution exists.
  502. A list containing an empty list [[]] means any value of
  503. the symbol(s) is a solution.
  504. See Also
  505. ========
  506. factor_system : Returns only generic solutions
  507. factor_system_bool : Returns a Boolean combination representing all solutions
  508. sympy.polys.polytools.factor : Factors a polynomial into irreducible factors
  509. over the rational numbers
  510. """
  511. systems_poly = _factor_system_poly_from_expr(eqs, gens, **kwargs)
  512. systems = [[p.as_expr() for p in system] for system in systems_poly]
  513. return systems
  514. def _factor_system_poly_from_expr(
  515. eqs: Sequence[Expr | complex], gens: Sequence[Expr], **kwargs: Any
  516. ) -> list[list[Poly]]:
  517. """
  518. Convert expressions to polynomials and factor the system.
  519. Takes a sequence of expressions, converts them to
  520. polynomials, and factors the resulting system. Handles both regular
  521. polynomial systems and purely numerical cases.
  522. """
  523. try:
  524. polys, opts = parallel_poly_from_expr(eqs, *gens, **kwargs)
  525. only_numbers = False
  526. except (GeneratorsNeeded, PolificationFailed):
  527. _u = Dummy('u')
  528. polys, opts = parallel_poly_from_expr(eqs, [_u], **kwargs)
  529. assert opts['domain'].is_Numerical
  530. only_numbers = True
  531. if only_numbers:
  532. return [[]] if all(p == 0 for p in polys) else []
  533. return factor_system_poly(polys)
  534. def factor_system_poly(polys: list[Poly]) -> list[list[Poly]]:
  535. """
  536. Factors a system of polynomial equations into irreducible subsystems
  537. Core implementation that works directly with Poly instances.
  538. Parameters
  539. ==========
  540. polys : list[Poly]
  541. A list of Poly instances to be factored.
  542. Returns
  543. =======
  544. list[list[Poly]]
  545. A list of lists of polynomials, where each sublist represents
  546. an irreducible component of the solution. Includes both
  547. generic and degenerate cases.
  548. Examples
  549. ========
  550. >>> from sympy import symbols, Poly, ZZ
  551. >>> from sympy.solvers.polysys import factor_system_poly
  552. >>> a, b, c, x = symbols('a b c x')
  553. >>> p1 = Poly((a - 1)*(x - 2), x, domain=ZZ[a,b,c])
  554. >>> p2 = Poly((b - 3)*(x - 2), x, domain=ZZ[a,b,c])
  555. >>> p3 = Poly(c, x, domain=ZZ[a,b,c])
  556. The equation to be solved for x is ``x - 2 = 0`` provided either
  557. of the two conditions on the parameters ``a`` and ``b`` is nonzero
  558. and the constant parameter ``c`` should be zero.
  559. >>> sys1, sys2 = factor_system_poly([p1, p2, p3])
  560. >>> sys1
  561. [Poly(x - 2, x, domain='ZZ[a,b,c]'),
  562. Poly(c, x, domain='ZZ[a,b,c]')]
  563. >>> sys2
  564. [Poly(a - 1, x, domain='ZZ[a,b,c]'),
  565. Poly(b - 3, x, domain='ZZ[a,b,c]'),
  566. Poly(c, x, domain='ZZ[a,b,c]')]
  567. An empty list [] when returned means no solution exists.
  568. Whereas a list containing an empty list [[]] means any value is a solution.
  569. See Also
  570. ========
  571. factor_system : Returns only generic solutions
  572. factor_system_bool : Returns a Boolean combination representing the solutions
  573. factor_system_cond : Returns both generic and degenerate solutions
  574. sympy.polys.polytools.factor : Factors a polynomial into irreducible factors
  575. over the rational numbers
  576. """
  577. if not all(isinstance(poly, Poly) for poly in polys):
  578. raise TypeError("polys should be a list of Poly instances")
  579. if not polys:
  580. return [[]]
  581. base_domain = polys[0].domain
  582. base_gens = polys[0].gens
  583. if not all(poly.domain == base_domain and poly.gens == base_gens for poly in polys[1:]):
  584. raise DomainError("All polynomials must have the same domain and generators")
  585. factor_sets = []
  586. for poly in polys:
  587. constant, factors_mult = poly.factor_list()
  588. if constant.is_zero is True:
  589. continue
  590. elif constant.is_zero is False:
  591. if not factors_mult:
  592. return []
  593. factor_sets.append([f for f, _ in factors_mult])
  594. else:
  595. constant = sqf_part(factor_terms(constant).as_coeff_Mul()[1])
  596. constp = Poly(constant, base_gens, domain=base_domain)
  597. factors = [f for f, _ in factors_mult]
  598. factors.append(constp)
  599. factor_sets.append(factors)
  600. if not factor_sets:
  601. return [[]]
  602. result = _factor_sets(factor_sets)
  603. return _sort_systems(result)
  604. def _factor_sets_slow(eqs: list[list]) -> set[frozenset]:
  605. """
  606. Helper to find the minimal set of factorised subsystems that is
  607. equivalent to the original system.
  608. The result is in DNF.
  609. """
  610. if not eqs:
  611. return {frozenset()}
  612. systems_set = {frozenset(sys) for sys in cartes(*eqs)}
  613. return {s1 for s1 in systems_set if not any(s1 > s2 for s2 in systems_set)}
  614. def _factor_sets(eqs: list[list]) -> set[frozenset]:
  615. """
  616. Helper that builds factor combinations.
  617. """
  618. if not eqs:
  619. return {frozenset()}
  620. current_set = min(eqs, key=len)
  621. other_sets = [s for s in eqs if s is not current_set]
  622. stack = [(factor, [s for s in other_sets if factor not in s], {factor})
  623. for factor in current_set]
  624. result = set()
  625. while stack:
  626. factor, remaining_sets, current_solution = stack.pop()
  627. if not remaining_sets:
  628. result.add(frozenset(current_solution))
  629. continue
  630. next_set = min(remaining_sets, key=len)
  631. next_remaining = [s for s in remaining_sets if s is not next_set]
  632. for next_factor in next_set:
  633. valid_remaining = [s for s in next_remaining if next_factor not in s]
  634. new_solution = current_solution | {next_factor}
  635. stack.append((next_factor, valid_remaining, new_solution))
  636. return {s1 for s1 in result if not any(s1 > s2 for s2 in result)}
  637. def _sort_systems(systems: Iterable[Iterable[Poly]]) -> list[list[Poly]]:
  638. """Sorts a list of lists of polynomials"""
  639. systems_list = [sorted(s, key=_poly_sort_key, reverse=True) for s in systems]
  640. return sorted(systems_list, key=_sys_sort_key, reverse=True)
  641. def _poly_sort_key(poly):
  642. """Sort key for polynomials"""
  643. if poly.domain.is_FF:
  644. poly = poly.set_domain(ZZ)
  645. return poly.degree_list(), poly.rep.to_list()
  646. def _sys_sort_key(sys):
  647. """Sort key for lists of polynomials"""
  648. return list(zip(*map(_poly_sort_key, sys)))