Spaces:
Running
Running
"""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, *args, **kwargs: 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 unknowns 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 | |