123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- """Low-level linear systems solver. """
- from sympy.utilities.exceptions import sympy_deprecation_warning
- from sympy.utilities.iterables import connected_components
- from sympy.core.sympify import sympify
- from sympy.core.numbers import Integer, Rational
- from sympy.matrices.dense import MutableDenseMatrix
- from sympy.polys.domains import ZZ, QQ
- from sympy.polys.domains import EX
- from sympy.polys.rings import sring
- from sympy.polys.polyerrors import NotInvertible
- from sympy.polys.domainmatrix import DomainMatrix
- class PolyNonlinearError(Exception):
- """Raised by solve_lin_sys for nonlinear equations"""
- pass
- class RawMatrix(MutableDenseMatrix):
- """
- .. deprecated:: 1.9
- This class fundamentally is broken by design. Use ``DomainMatrix`` if
- you want a matrix over the polys domains or ``Matrix`` for a matrix
- with ``Expr`` elements. The ``RawMatrix`` class will be removed/broken
- in future in order to reestablish the invariant that the elements of a
- Matrix should be of type ``Expr``.
- """
- _sympify = staticmethod(lambda x: x)
- def __init__(self, *args, **kwargs):
- sympy_deprecation_warning(
- """
- The RawMatrix class is deprecated. Use either DomainMatrix or
- Matrix instead.
- """,
- deprecated_since_version="1.9",
- active_deprecations_target="deprecated-rawmatrix",
- )
- domain = ZZ
- for i in range(self.rows):
- for j in range(self.cols):
- val = self[i,j]
- if getattr(val, 'is_Poly', False):
- K = val.domain[val.gens]
- val_sympy = val.as_expr()
- elif hasattr(val, 'parent'):
- K = val.parent()
- val_sympy = K.to_sympy(val)
- elif isinstance(val, (int, Integer)):
- K = ZZ
- val_sympy = sympify(val)
- elif isinstance(val, Rational):
- K = QQ
- val_sympy = val
- else:
- for K in ZZ, QQ:
- if K.of_type(val):
- val_sympy = K.to_sympy(val)
- break
- else:
- raise TypeError
- domain = domain.unify(K)
- self[i,j] = val_sympy
- self.ring = domain
- def eqs_to_matrix(eqs_coeffs, eqs_rhs, gens, domain):
- """Get matrix from linear equations in dict format.
- Explanation
- ===========
- Get the matrix representation of a system of linear equations represented
- as dicts with low-level DomainElement coefficients. This is an
- *internal* function that is used by solve_lin_sys.
- Parameters
- ==========
- eqs_coeffs: list[dict[Symbol, DomainElement]]
- The left hand sides of the equations as dicts mapping from symbols to
- coefficients where the coefficients are instances of
- DomainElement.
- eqs_rhs: list[DomainElements]
- The right hand sides of the equations as instances of
- DomainElement.
- gens: list[Symbol]
- The unknowns in the system of equations.
- domain: Domain
- The domain for coefficients of both lhs and rhs.
- Returns
- =======
- The augmented matrix representation of the system as a DomainMatrix.
- Examples
- ========
- >>> from sympy import symbols, ZZ
- >>> from sympy.polys.solvers import eqs_to_matrix
- >>> x, y = symbols('x, y')
- >>> eqs_coeff = [{x:ZZ(1), y:ZZ(1)}, {x:ZZ(1), y:ZZ(-1)}]
- >>> eqs_rhs = [ZZ(0), ZZ(-1)]
- >>> eqs_to_matrix(eqs_coeff, eqs_rhs, [x, y], ZZ)
- DomainMatrix([[1, 1, 0], [1, -1, 1]], (2, 3), ZZ)
- See also
- ========
- solve_lin_sys: Uses :func:`~eqs_to_matrix` internally
- """
- sym2index = {x: n for n, x in enumerate(gens)}
- nrows = len(eqs_coeffs)
- ncols = len(gens) + 1
- rows = [[domain.zero] * ncols for _ in range(nrows)]
- for row, eq_coeff, eq_rhs in zip(rows, eqs_coeffs, eqs_rhs):
- for sym, coeff in eq_coeff.items():
- row[sym2index[sym]] = domain.convert(coeff)
- row[-1] = -domain.convert(eq_rhs)
- return DomainMatrix(rows, (nrows, ncols), domain)
- def sympy_eqs_to_ring(eqs, symbols):
- """Convert a system of equations from Expr to a PolyRing
- Explanation
- ===========
- High-level functions like ``solve`` expect Expr as inputs but can use
- ``solve_lin_sys`` internally. This function converts equations from
- ``Expr`` to the low-level poly types used by the ``solve_lin_sys``
- function.
- Parameters
- ==========
- eqs: List of Expr
- A list of equations as Expr instances
- symbols: List of Symbol
- A list of the symbols that are the unknowns in the system of
- equations.
- Returns
- =======
- Tuple[List[PolyElement], Ring]: The equations as PolyElement instances
- and the ring of polynomials within which each equation is represented.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.polys.solvers import sympy_eqs_to_ring
- >>> a, x, y = symbols('a, x, y')
- >>> eqs = [x-y, x+a*y]
- >>> eqs_ring, ring = sympy_eqs_to_ring(eqs, [x, y])
- >>> eqs_ring
- [x - y, x + a*y]
- >>> type(eqs_ring[0])
- <class 'sympy.polys.rings.PolyElement'>
- >>> ring
- ZZ(a)[x,y]
- With the equations in this form they can be passed to ``solve_lin_sys``:
- >>> from sympy.polys.solvers import solve_lin_sys
- >>> solve_lin_sys(eqs_ring, ring)
- {y: 0, x: 0}
- """
- try:
- K, eqs_K = sring(eqs, symbols, field=True, extension=True)
- except NotInvertible:
- # https://github.com/sympy/sympy/issues/18874
- K, eqs_K = sring(eqs, symbols, domain=EX)
- return eqs_K, K.to_domain()
- def solve_lin_sys(eqs, ring, _raw=True):
- """Solve a system of linear equations from a PolynomialRing
- Explanation
- ===========
- Solves a system of linear equations given as PolyElement instances of a
- PolynomialRing. The basic arithmetic is carried out using instance of
- DomainElement which is more efficient than :class:`~sympy.core.expr.Expr`
- for the most common inputs.
- While this is a public function it is intended primarily for internal use
- so its interface is not necessarily convenient. Users are suggested to use
- the :func:`sympy.solvers.solveset.linsolve` function (which uses this
- function internally) instead.
- Parameters
- ==========
- eqs: list[PolyElement]
- The linear equations to be solved as elements of a
- PolynomialRing (assumed equal to zero).
- ring: PolynomialRing
- The polynomial ring from which eqs are drawn. The generators of this
- ring are the unkowns to be solved for and the domain of the ring is
- the domain of the coefficients of the system of equations.
- _raw: bool
- If *_raw* is False, the keys and values in the returned dictionary
- will be of type Expr (and the unit of the field will be removed from
- the keys) otherwise the low-level polys types will be returned, e.g.
- PolyElement: PythonRational.
- Returns
- =======
- ``None`` if the system has no solution.
- dict[Symbol, Expr] if _raw=False
- dict[Symbol, DomainElement] if _raw=True.
- Examples
- ========
- >>> from sympy import symbols
- >>> from sympy.polys.solvers import solve_lin_sys, sympy_eqs_to_ring
- >>> x, y = symbols('x, y')
- >>> eqs = [x - y, x + y - 2]
- >>> eqs_ring, ring = sympy_eqs_to_ring(eqs, [x, y])
- >>> solve_lin_sys(eqs_ring, ring)
- {y: 1, x: 1}
- Passing ``_raw=False`` returns the same result except that the keys are
- ``Expr`` rather than low-level poly types.
- >>> solve_lin_sys(eqs_ring, ring, _raw=False)
- {x: 1, y: 1}
- See also
- ========
- sympy_eqs_to_ring: prepares the inputs to ``solve_lin_sys``.
- linsolve: ``linsolve`` uses ``solve_lin_sys`` internally.
- sympy.solvers.solvers.solve: ``solve`` uses ``solve_lin_sys`` internally.
- """
- as_expr = not _raw
- assert ring.domain.is_Field
- eqs_dict = [dict(eq) for eq in eqs]
- one_monom = ring.one.monoms()[0]
- zero = ring.domain.zero
- eqs_rhs = []
- eqs_coeffs = []
- for eq_dict in eqs_dict:
- eq_rhs = eq_dict.pop(one_monom, zero)
- eq_coeffs = {}
- for monom, coeff in eq_dict.items():
- if sum(monom) != 1:
- msg = "Nonlinear term encountered in solve_lin_sys"
- raise PolyNonlinearError(msg)
- eq_coeffs[ring.gens[monom.index(1)]] = coeff
- if not eq_coeffs:
- if not eq_rhs:
- continue
- else:
- return None
- eqs_rhs.append(eq_rhs)
- eqs_coeffs.append(eq_coeffs)
- result = _solve_lin_sys(eqs_coeffs, eqs_rhs, ring)
- if result is not None and as_expr:
- def to_sympy(x):
- as_expr = getattr(x, 'as_expr', None)
- if as_expr:
- return as_expr()
- else:
- return ring.domain.to_sympy(x)
- tresult = {to_sympy(sym): to_sympy(val) for sym, val in result.items()}
- # Remove 1.0x
- result = {}
- for k, v in tresult.items():
- if k.is_Mul:
- c, s = k.as_coeff_Mul()
- result[s] = v/c
- else:
- result[k] = v
- return result
- def _solve_lin_sys(eqs_coeffs, eqs_rhs, ring):
- """Solve a linear system from dict of PolynomialRing coefficients
- Explanation
- ===========
- This is an **internal** function used by :func:`solve_lin_sys` after the
- equations have been preprocessed. The role of this function is to split
- the system into connected components and pass those to
- :func:`_solve_lin_sys_component`.
- Examples
- ========
- Setup a system for $x-y=0$ and $x+y=2$ and solve:
- >>> from sympy import symbols, sring
- >>> from sympy.polys.solvers import _solve_lin_sys
- >>> x, y = symbols('x, y')
- >>> R, (xr, yr) = sring([x, y], [x, y])
- >>> eqs = [{xr:R.one, yr:-R.one}, {xr:R.one, yr:R.one}]
- >>> eqs_rhs = [R.zero, -2*R.one]
- >>> _solve_lin_sys(eqs, eqs_rhs, R)
- {y: 1, x: 1}
- See also
- ========
- solve_lin_sys: This function is used internally by :func:`solve_lin_sys`.
- """
- V = ring.gens
- E = []
- for eq_coeffs in eqs_coeffs:
- syms = list(eq_coeffs)
- E.extend(zip(syms[:-1], syms[1:]))
- G = V, E
- components = connected_components(G)
- sym2comp = {}
- for n, component in enumerate(components):
- for sym in component:
- sym2comp[sym] = n
- subsystems = [([], []) for _ in range(len(components))]
- for eq_coeff, eq_rhs in zip(eqs_coeffs, eqs_rhs):
- sym = next(iter(eq_coeff), None)
- sub_coeff, sub_rhs = subsystems[sym2comp[sym]]
- sub_coeff.append(eq_coeff)
- sub_rhs.append(eq_rhs)
- sol = {}
- for subsystem in subsystems:
- subsol = _solve_lin_sys_component(subsystem[0], subsystem[1], ring)
- if subsol is None:
- return None
- sol.update(subsol)
- return sol
- def _solve_lin_sys_component(eqs_coeffs, eqs_rhs, ring):
- """Solve a linear system from dict of PolynomialRing coefficients
- Explanation
- ===========
- This is an **internal** function used by :func:`solve_lin_sys` after the
- equations have been preprocessed. After :func:`_solve_lin_sys` splits the
- system into connected components this function is called for each
- component. The system of equations is solved using Gauss-Jordan
- elimination with division followed by back-substitution.
- Examples
- ========
- Setup a system for $x-y=0$ and $x+y=2$ and solve:
- >>> from sympy import symbols, sring
- >>> from sympy.polys.solvers import _solve_lin_sys_component
- >>> x, y = symbols('x, y')
- >>> R, (xr, yr) = sring([x, y], [x, y])
- >>> eqs = [{xr:R.one, yr:-R.one}, {xr:R.one, yr:R.one}]
- >>> eqs_rhs = [R.zero, -2*R.one]
- >>> _solve_lin_sys_component(eqs, eqs_rhs, R)
- {y: 1, x: 1}
- See also
- ========
- solve_lin_sys: This function is used internally by :func:`solve_lin_sys`.
- """
- # transform from equations to matrix form
- matrix = eqs_to_matrix(eqs_coeffs, eqs_rhs, ring.gens, ring.domain)
- # convert to a field for rref
- if not matrix.domain.is_Field:
- matrix = matrix.to_field()
- # solve by row-reduction
- echelon, pivots = matrix.rref()
- # construct the returnable form of the solutions
- keys = ring.gens
- if pivots and pivots[-1] == len(keys):
- return None
- if len(pivots) == len(keys):
- sol = []
- for s in [row[-1] for row in echelon.rep.to_ddm()]:
- a = s
- sol.append(a)
- sols = dict(zip(keys, sol))
- else:
- sols = {}
- g = ring.gens
- # Extract ground domain coefficients and convert to the ring:
- if hasattr(ring, 'ring'):
- convert = ring.ring.ground_new
- else:
- convert = ring.ground_new
- echelon = echelon.rep.to_ddm()
- vals_set = {v for row in echelon for v in row}
- vals_map = {v: convert(v) for v in vals_set}
- echelon = [[vals_map[eij] for eij in ei] for ei in echelon]
- for i, p in enumerate(pivots):
- v = echelon[i][-1] - sum(echelon[i][j]*g[j] for j in range(p+1, len(g)) if echelon[i][j])
- sols[keys[p]] = v
- return sols
|