| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872 |
- """Solvers of systems of polynomial equations. """
- from __future__ import annotations
- from typing import Any
- from collections.abc import Sequence, Iterable
- import itertools
- from sympy import Dummy
- from sympy.core import S
- from sympy.core.expr import Expr
- from sympy.core.exprtools import factor_terms
- from sympy.core.sorting import default_sort_key
- from sympy.logic.boolalg import Boolean
- from sympy.polys import Poly, groebner, roots
- from sympy.polys.domains import ZZ
- from sympy.polys.polyoptions import build_options
- from sympy.polys.polytools import parallel_poly_from_expr, sqf_part
- from sympy.polys.polyerrors import (
- ComputationFailed,
- PolificationFailed,
- CoercionFailed,
- GeneratorsNeeded,
- DomainError
- )
- from sympy.simplify import rcollect
- from sympy.utilities import postfixes
- from sympy.utilities.iterables import cartes
- from sympy.utilities.misc import filldedent
- from sympy.logic.boolalg import Or, And
- from sympy.core.relational import Eq
- class SolveFailed(Exception):
- """Raised when solver's conditions were not met. """
- def solve_poly_system(seq, *gens, strict=False, **args):
- """
- Return a list of solutions for the system of polynomial equations
- or else None.
- 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
- strict: a boolean (default is False)
- if strict is True, NotImplementedError will be raised if
- the solution is known to be incomplete (which can occur if
- not all solutions are expressible in radicals)
- args: Keyword arguments
- Special options for solving the equations.
- Returns
- =======
- List[Tuple]
- a list of tuples with elements being solutions for the
- symbols in the order they were passed as gens
- None
- None is returned when the computed basis contains only the ground.
- 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))]
- >>> solve_poly_system([x**5 - x + y**3, y**2 - 1], x, y, strict=True)
- Traceback (most recent call last):
- ...
- UnsolvableFactorError
- """
- 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, strict=strict)
- 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 with elements being solutions for the
- symbols in the order they were passed as gens
- None
- None is returned when the computed basis contains only the ground.
- 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 = [(p_root.subs(y, q_root), q_root) for q_root, p_root in
- itertools.product(q_roots, p_roots)]
- return sorted(solutions, key=default_sort_key)
- def solve_generic(polys, opt, strict=False):
- """
- 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. If the basis contains only the ground, None is
- returned.
- 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
- strict: a boolean
- If strict is True, NotImplementedError will be raised if the solution
- is known to be incomplete
- Returns
- =======
- List[Tuple]
- a list of tuples with elements being solutions for the
- symbols in the order they were passed as gens
- None
- None is returned when the computed basis contains only the ground.
- 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
- Raises
- ========
- NotImplementedError
- If the system is not zero-dimensional (does not have a finite
- number of solutions)
- UnsolvableFactorError
- If ``strict`` is True and not all solution components are
- expressible in radicals
- 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)]
- >>> a = Poly(x**5 - x + y**3, x, y, domain='ZZ')
- >>> b = Poly(y**2 - 1, x, y, domain='ZZ')
- >>> solve_generic([a, b], NewOption, strict=True)
- Traceback (most recent call last):
- ...
- UnsolvableFactorError
- """
- 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:
- # the below line will produce UnsolvableFactorError if
- # strict=True and the solution from `roots` is incomplete
- zeros = list(roots(system[0], gens[-1], strict=strict).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]
- # the below line will produce UnsolvableFactorError if
- # strict=True and the solution from `roots` is incomplete
- zeros = list(roots(f.ltrim(gen), strict=strict).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)
- 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)]
- Using extension for algebraic solutions.
- >>> solve_triangulated(F, x, y, z, extension=True) #doctest: +NORMALIZE_WHITESPACE
- [(0, 0, 1), (0, 1, 0), (1, 0, 0),
- (CRootOf(x**2 + 2*x - 1, 0), CRootOf(x**2 + 2*x - 1, 0), CRootOf(x**2 + 2*x - 1, 0)),
- (CRootOf(x**2 + 2*x - 1, 1), CRootOf(x**2 + 2*x - 1, 1), CRootOf(x**2 + 2*x - 1, 1))]
- 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
- """
- opt = build_options(gens, args)
- G = groebner(polys, gens, polys=True)
- G = list(reversed(G))
- extension = opt.get('extension', False)
- if extension:
- def _solve_univariate(f):
- return [r for r, _ in f.all_roots(multiple=False, radicals=False)]
- else:
- domain = opt.get('domain')
- if domain is not None:
- for i, g in enumerate(G):
- G[i] = g.set_domain(domain)
- def _solve_univariate(f):
- return list(f.ground_roots().keys())
- f, G = G[0].ltrim(-1), G[1:]
- dom = f.get_domain()
- zeros = _solve_univariate(f)
- if extension:
- solutions = {((zero,), dom.algebraic_field(zero)) for zero in zeros}
- else:
- solutions = {((zero,), dom) for zero in zeros}
- 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:
- if extension:
- g = g.set_domain(g.domain.unify(dom))
- 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 = _solve_univariate(p)
- for zero in zeros:
- if not (zero in dom):
- dom_zero = dom.algebraic_field(zero)
- else:
- dom_zero = dom
- _solutions.add(((zero,) + values, dom_zero))
- solutions = _solutions
- return sorted((s for s, _ in solutions), key=default_sort_key)
- def factor_system(eqs: Sequence[Expr | complex], gens: Sequence[Expr] = (), **kwargs: Any) -> list[list[Expr]]:
- """
- Factorizes a system of polynomial equations into
- irreducible subsystems.
- Parameters
- ==========
- eqs : list
- List of expressions to be factored.
- Each expression is assumed to be equal to zero.
- gens : list, optional
- Generator(s) of the polynomial ring.
- If not provided, all free symbols will be used.
- **kwargs : dict, optional
- Same optional arguments taken by ``factor``
- Returns
- =======
- list[list[Expr]]
- A list of lists of expressions, where each sublist represents
- an irreducible subsystem. When solved, each subsystem gives
- one component of the solution. Only generic solutions are
- returned (cases not requiring parameters to be zero).
- Examples
- ========
- >>> from sympy.solvers.polysys import factor_system, factor_system_cond
- >>> from sympy.abc import x, y, a, b, c
- A simple system with multiple solutions:
- >>> factor_system([x**2 - 1, y - 1])
- [[x + 1, y - 1], [x - 1, y - 1]]
- A system with no solution:
- >>> factor_system([x, 1])
- []
- A system where any value of the symbol(s) is a solution:
- >>> factor_system([x - x, (x + 1)**2 - (x**2 + 2*x + 1)])
- [[]]
- A system with no generic solution:
- >>> factor_system([a*x*(x-1), b*y, c], [x, y])
- []
- If c is added to the unknowns then the system has a generic solution:
- >>> factor_system([a*x*(x-1), b*y, c], [x, y, c])
- [[x - 1, y, c], [x, y, c]]
- Alternatively :func:`factor_system_cond` can be used to get degenerate
- cases as well:
- >>> factor_system_cond([a*x*(x-1), b*y, c], [x, y])
- [[x - 1, y, c], [x, y, c], [x - 1, b, c], [x, b, c], [y, a, c], [a, b, c]]
- Each of the above cases is only satisfiable in the degenerate case `c = 0`.
- The solution set of the original system represented
- by eqs is the union of the solution sets of the
- factorized systems.
- An empty list [] means no generic solution exists.
- A list containing an empty list [[]] means any value of
- the symbol(s) is a solution.
- See Also
- ========
- factor_system_cond : Returns both generic and degenerate solutions
- factor_system_bool : Returns a Boolean combination representing all solutions
- sympy.polys.polytools.factor : Factors a polynomial into irreducible factors
- over the rational numbers
- """
- systems = _factor_system_poly_from_expr(eqs, gens, **kwargs)
- systems_generic = [sys for sys in systems if not _is_degenerate(sys)]
- systems_expr = [[p.as_expr() for p in system] for system in systems_generic]
- return systems_expr
- def _is_degenerate(system: list[Poly]) -> bool:
- """Helper function to check if a system is degenerate"""
- return any(p.is_ground for p in system)
- def factor_system_bool(eqs: Sequence[Expr | complex], gens: Sequence[Expr] = (), **kwargs: Any) -> Boolean:
- """
- Factorizes a system of polynomial equations into irreducible DNF.
- The system of expressions(eqs) is taken and a Boolean combination
- of equations is returned that represents the same solution set.
- The result is in disjunctive normal form (OR of ANDs).
- Parameters
- ==========
- eqs : list
- List of expressions to be factored.
- Each expression is assumed to be equal to zero.
- gens : list, optional
- Generator(s) of the polynomial ring.
- If not provided, all free symbols will be used.
- **kwargs : dict, optional
- Optional keyword arguments
- Returns
- =======
- Boolean:
- A Boolean combination of equations. The result is typically in
- the form of a conjunction (AND) of a disjunctive normal form
- with additional conditions.
- Examples
- ========
- >>> from sympy.solvers.polysys import factor_system_bool
- >>> from sympy.abc import x, y, a, b, c
- >>> factor_system_bool([x**2 - 1])
- Eq(x - 1, 0) | Eq(x + 1, 0)
- >>> factor_system_bool([x**2 - 1, y - 1])
- (Eq(x - 1, 0) & Eq(y - 1, 0)) | (Eq(x + 1, 0) & Eq(y - 1, 0))
- >>> eqs = [a * (x - 1), b]
- >>> factor_system_bool([a*(x - 1), b])
- (Eq(a, 0) & Eq(b, 0)) | (Eq(b, 0) & Eq(x - 1, 0))
- >>> factor_system_bool([a*x**2 - a, b*(x + 1), c], [x])
- (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))
- >>> factor_system_bool([x**2 + 2*x + 1 - (x + 1)**2])
- True
- The result is logically equivalent to the system of equations
- i.e. eqs. The function returns ``True`` when all values of
- the symbol(s) is a solution and ``False`` when the system
- cannot be solved.
- See Also
- ========
- factor_system : Returns factors and solvability condition separately
- factor_system_cond : Returns both factors and conditions
- """
- systems = factor_system_cond(eqs, gens, **kwargs)
- return Or(*[And(*[Eq(eq, 0) for eq in sys]) for sys in systems])
- def factor_system_cond(eqs: Sequence[Expr | complex], gens: Sequence[Expr] = (), **kwargs: Any) -> list[list[Expr]]:
- """
- Factorizes a polynomial system into irreducible components and returns
- both generic and degenerate solutions.
- Parameters
- ==========
- eqs : list
- List of expressions to be factored.
- Each expression is assumed to be equal to zero.
- gens : list, optional
- Generator(s) of the polynomial ring.
- If not provided, all free symbols will be used.
- **kwargs : dict, optional
- Optional keyword arguments.
- Returns
- =======
- list[list[Expr]]
- A list of lists of expressions, where each sublist represents
- an irreducible subsystem. Includes both generic solutions and
- degenerate cases requiring equality conditions on parameters.
- Examples
- ========
- >>> from sympy.solvers.polysys import factor_system_cond
- >>> from sympy.abc import x, y, a, b, c
- >>> factor_system_cond([x**2 - 4, a*y, b], [x, y])
- [[x + 2, y, b], [x - 2, y, b], [x + 2, a, b], [x - 2, a, b]]
- >>> factor_system_cond([a*x*(x-1), b*y, c], [x, y])
- [[x - 1, y, c], [x, y, c], [x - 1, b, c], [x, b, c], [y, a, c], [a, b, c]]
- An empty list [] means no solution exists.
- A list containing an empty list [[]] means any value of
- the symbol(s) is a solution.
- See Also
- ========
- factor_system : Returns only generic solutions
- factor_system_bool : Returns a Boolean combination representing all solutions
- sympy.polys.polytools.factor : Factors a polynomial into irreducible factors
- over the rational numbers
- """
- systems_poly = _factor_system_poly_from_expr(eqs, gens, **kwargs)
- systems = [[p.as_expr() for p in system] for system in systems_poly]
- return systems
- def _factor_system_poly_from_expr(
- eqs: Sequence[Expr | complex], gens: Sequence[Expr], **kwargs: Any
- ) -> list[list[Poly]]:
- """
- Convert expressions to polynomials and factor the system.
- Takes a sequence of expressions, converts them to
- polynomials, and factors the resulting system. Handles both regular
- polynomial systems and purely numerical cases.
- """
- try:
- polys, opts = parallel_poly_from_expr(eqs, *gens, **kwargs)
- only_numbers = False
- except (GeneratorsNeeded, PolificationFailed):
- _u = Dummy('u')
- polys, opts = parallel_poly_from_expr(eqs, [_u], **kwargs)
- assert opts['domain'].is_Numerical
- only_numbers = True
- if only_numbers:
- return [[]] if all(p == 0 for p in polys) else []
- return factor_system_poly(polys)
- def factor_system_poly(polys: list[Poly]) -> list[list[Poly]]:
- """
- Factors a system of polynomial equations into irreducible subsystems
- Core implementation that works directly with Poly instances.
- Parameters
- ==========
- polys : list[Poly]
- A list of Poly instances to be factored.
- Returns
- =======
- list[list[Poly]]
- A list of lists of polynomials, where each sublist represents
- an irreducible component of the solution. Includes both
- generic and degenerate cases.
- Examples
- ========
- >>> from sympy import symbols, Poly, ZZ
- >>> from sympy.solvers.polysys import factor_system_poly
- >>> a, b, c, x = symbols('a b c x')
- >>> p1 = Poly((a - 1)*(x - 2), x, domain=ZZ[a,b,c])
- >>> p2 = Poly((b - 3)*(x - 2), x, domain=ZZ[a,b,c])
- >>> p3 = Poly(c, x, domain=ZZ[a,b,c])
- The equation to be solved for x is ``x - 2 = 0`` provided either
- of the two conditions on the parameters ``a`` and ``b`` is nonzero
- and the constant parameter ``c`` should be zero.
- >>> sys1, sys2 = factor_system_poly([p1, p2, p3])
- >>> sys1
- [Poly(x - 2, x, domain='ZZ[a,b,c]'),
- Poly(c, x, domain='ZZ[a,b,c]')]
- >>> sys2
- [Poly(a - 1, x, domain='ZZ[a,b,c]'),
- Poly(b - 3, x, domain='ZZ[a,b,c]'),
- Poly(c, x, domain='ZZ[a,b,c]')]
- An empty list [] when returned means no solution exists.
- Whereas a list containing an empty list [[]] means any value is a solution.
- See Also
- ========
- factor_system : Returns only generic solutions
- factor_system_bool : Returns a Boolean combination representing the solutions
- factor_system_cond : Returns both generic and degenerate solutions
- sympy.polys.polytools.factor : Factors a polynomial into irreducible factors
- over the rational numbers
- """
- if not all(isinstance(poly, Poly) for poly in polys):
- raise TypeError("polys should be a list of Poly instances")
- if not polys:
- return [[]]
- base_domain = polys[0].domain
- base_gens = polys[0].gens
- if not all(poly.domain == base_domain and poly.gens == base_gens for poly in polys[1:]):
- raise DomainError("All polynomials must have the same domain and generators")
- factor_sets = []
- for poly in polys:
- constant, factors_mult = poly.factor_list()
- if constant.is_zero is True:
- continue
- elif constant.is_zero is False:
- if not factors_mult:
- return []
- factor_sets.append([f for f, _ in factors_mult])
- else:
- constant = sqf_part(factor_terms(constant).as_coeff_Mul()[1])
- constp = Poly(constant, base_gens, domain=base_domain)
- factors = [f for f, _ in factors_mult]
- factors.append(constp)
- factor_sets.append(factors)
- if not factor_sets:
- return [[]]
- result = _factor_sets(factor_sets)
- return _sort_systems(result)
- def _factor_sets_slow(eqs: list[list]) -> set[frozenset]:
- """
- Helper to find the minimal set of factorised subsystems that is
- equivalent to the original system.
- The result is in DNF.
- """
- if not eqs:
- return {frozenset()}
- systems_set = {frozenset(sys) for sys in cartes(*eqs)}
- return {s1 for s1 in systems_set if not any(s1 > s2 for s2 in systems_set)}
- def _factor_sets(eqs: list[list]) -> set[frozenset]:
- """
- Helper that builds factor combinations.
- """
- if not eqs:
- return {frozenset()}
- current_set = min(eqs, key=len)
- other_sets = [s for s in eqs if s is not current_set]
- stack = [(factor, [s for s in other_sets if factor not in s], {factor})
- for factor in current_set]
- result = set()
- while stack:
- factor, remaining_sets, current_solution = stack.pop()
- if not remaining_sets:
- result.add(frozenset(current_solution))
- continue
- next_set = min(remaining_sets, key=len)
- next_remaining = [s for s in remaining_sets if s is not next_set]
- for next_factor in next_set:
- valid_remaining = [s for s in next_remaining if next_factor not in s]
- new_solution = current_solution | {next_factor}
- stack.append((next_factor, valid_remaining, new_solution))
- return {s1 for s1 in result if not any(s1 > s2 for s2 in result)}
- def _sort_systems(systems: Iterable[Iterable[Poly]]) -> list[list[Poly]]:
- """Sorts a list of lists of polynomials"""
- systems_list = [sorted(s, key=_poly_sort_key, reverse=True) for s in systems]
- return sorted(systems_list, key=_sys_sort_key, reverse=True)
- def _poly_sort_key(poly):
- """Sort key for polynomials"""
- if poly.domain.is_FF:
- poly = poly.set_domain(ZZ)
- return poly.degree_list(), poly.rep.to_list()
- def _sys_sort_key(sys):
- """Sort key for lists of polynomials"""
- return list(zip(*map(_poly_sort_key, sys)))
|