Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .venv/lib/python3.13/site-packages/sympy/core/tests/test_basic.py +343 -0
- .venv/lib/python3.13/site-packages/sympy/core/tests/test_kind.py +57 -0
- .venv/lib/python3.13/site-packages/sympy/core/tests/test_multidimensional.py +24 -0
- .venv/lib/python3.13/site-packages/sympy/core/tests/test_singleton.py +76 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/__init__.py +5 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/extensions.py +356 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/homomorphisms.py +691 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/ideals.py +395 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/modules.py +1488 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_extensions.py +196 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_homomorphisms.py +113 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_ideals.py +131 -0
- .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_modules.py +408 -0
- .venv/lib/python3.13/site-packages/sympy/polys/benchmarks/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_galoispolys.py +66 -0
- .venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_groebnertools.py +25 -0
- .venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_solvers.py +0 -0
- .venv/lib/python3.13/site-packages/sympy/polys/domains/tests/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_domains.py +1434 -0
- .venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_polynomialring.py +93 -0
- .venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_quotientring.py +52 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/__init__.py +15 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/_dfm.py +951 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/_typing.py +16 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/ddm.py +1176 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/dense.py +824 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/dfm.py +35 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/domainmatrix.py +0 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/domainscalar.py +122 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/eigen.py +90 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/exceptions.py +67 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/linsolve.py +230 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/lll.py +94 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/normalforms.py +540 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/rref.py +422 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/sdm.py +2197 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/__init__.py +0 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_ddm.py +558 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_dense.py +350 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_domainmatrix.py +1383 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_domainscalar.py +153 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_eigen.py +90 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_fflu.py +301 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_inverse.py +193 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_linsolve.py +112 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_lll.py +145 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_normalforms.py +156 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_nullspace.py +209 -0
- .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_rref.py +737 -0
.venv/lib/python3.13/site-packages/sympy/core/tests/test_basic.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""This tests sympy/core/basic.py with (ideally) no reference to subclasses
|
| 2 |
+
of Basic or Atom."""
|
| 3 |
+
import collections
|
| 4 |
+
from typing import TypeVar, Generic
|
| 5 |
+
|
| 6 |
+
from sympy.assumptions.ask import Q
|
| 7 |
+
from sympy.core.basic import (Basic, Atom, as_Basic,
|
| 8 |
+
_atomic, _aresame)
|
| 9 |
+
from sympy.core.containers import Tuple
|
| 10 |
+
from sympy.core.function import Function, Lambda
|
| 11 |
+
from sympy.core.numbers import I, pi, Float
|
| 12 |
+
from sympy.core.singleton import S
|
| 13 |
+
from sympy.core.symbol import symbols, Symbol, Dummy
|
| 14 |
+
from sympy.concrete.summations import Sum
|
| 15 |
+
from sympy.functions.elementary.trigonometric import (cos, sin)
|
| 16 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 17 |
+
from sympy.integrals.integrals import Integral
|
| 18 |
+
from sympy.functions.elementary.exponential import exp
|
| 19 |
+
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
| 20 |
+
from sympy.functions.elementary.complexes import Abs, sign
|
| 21 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 22 |
+
from sympy.core.relational import Eq
|
| 23 |
+
|
| 24 |
+
b1 = Basic()
|
| 25 |
+
b2 = Basic(b1)
|
| 26 |
+
b3 = Basic(b2)
|
| 27 |
+
b21 = Basic(b2, b1)
|
| 28 |
+
T = TypeVar('T')
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test__aresame():
|
| 32 |
+
assert not _aresame(Basic(Tuple()), Basic())
|
| 33 |
+
for i, j in [(S(2), S(2.)), (1., Float(1))]:
|
| 34 |
+
for do in range(2):
|
| 35 |
+
assert not _aresame(Basic(i), Basic(j))
|
| 36 |
+
assert not _aresame(i, j)
|
| 37 |
+
i, j = j, i
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def test_structure():
|
| 41 |
+
assert b21.args == (b2, b1)
|
| 42 |
+
assert b21.func(*b21.args) == b21
|
| 43 |
+
assert bool(b1)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def test_immutable():
|
| 47 |
+
assert not hasattr(b1, '__dict__')
|
| 48 |
+
with raises(AttributeError):
|
| 49 |
+
b1.x = 1
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def test_equality():
|
| 53 |
+
instances = [b1, b2, b3, b21, Basic(b1, b1, b1), Basic]
|
| 54 |
+
for i, b_i in enumerate(instances):
|
| 55 |
+
for j, b_j in enumerate(instances):
|
| 56 |
+
assert (b_i == b_j) == (i == j)
|
| 57 |
+
assert (b_i != b_j) == (i != j)
|
| 58 |
+
|
| 59 |
+
assert Basic() != []
|
| 60 |
+
assert not(Basic() == [])
|
| 61 |
+
assert Basic() != 0
|
| 62 |
+
assert not(Basic() == 0)
|
| 63 |
+
|
| 64 |
+
class Foo:
|
| 65 |
+
"""
|
| 66 |
+
Class that is unaware of Basic, and relies on both classes returning
|
| 67 |
+
the NotImplemented singleton for equivalence to evaluate to False.
|
| 68 |
+
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
b = Basic()
|
| 72 |
+
foo = Foo()
|
| 73 |
+
|
| 74 |
+
assert b != foo
|
| 75 |
+
assert foo != b
|
| 76 |
+
assert not b == foo
|
| 77 |
+
assert not foo == b
|
| 78 |
+
|
| 79 |
+
class Bar:
|
| 80 |
+
"""
|
| 81 |
+
Class that considers itself equal to any instance of Basic, and relies
|
| 82 |
+
on Basic returning the NotImplemented singleton in order to achieve
|
| 83 |
+
a symmetric equivalence relation.
|
| 84 |
+
|
| 85 |
+
"""
|
| 86 |
+
def __eq__(self, other):
|
| 87 |
+
if isinstance(other, Basic):
|
| 88 |
+
return True
|
| 89 |
+
return NotImplemented
|
| 90 |
+
|
| 91 |
+
def __ne__(self, other):
|
| 92 |
+
return not self == other
|
| 93 |
+
|
| 94 |
+
bar = Bar()
|
| 95 |
+
|
| 96 |
+
assert b == bar
|
| 97 |
+
assert bar == b
|
| 98 |
+
assert not b != bar
|
| 99 |
+
assert not bar != b
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def test_matches_basic():
|
| 103 |
+
instances = [Basic(b1, b1, b2), Basic(b1, b2, b1), Basic(b2, b1, b1),
|
| 104 |
+
Basic(b1, b2), Basic(b2, b1), b2, b1]
|
| 105 |
+
for i, b_i in enumerate(instances):
|
| 106 |
+
for j, b_j in enumerate(instances):
|
| 107 |
+
if i == j:
|
| 108 |
+
assert b_i.matches(b_j) == {}
|
| 109 |
+
else:
|
| 110 |
+
assert b_i.matches(b_j) is None
|
| 111 |
+
assert b1.match(b1) == {}
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def test_has():
|
| 115 |
+
assert b21.has(b1)
|
| 116 |
+
assert b21.has(b3, b1)
|
| 117 |
+
assert b21.has(Basic)
|
| 118 |
+
assert not b1.has(b21, b3)
|
| 119 |
+
assert not b21.has()
|
| 120 |
+
assert not b21.has(str)
|
| 121 |
+
assert not Symbol("x").has("x")
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def test_subs():
|
| 125 |
+
assert b21.subs(b2, b1) == Basic(b1, b1)
|
| 126 |
+
assert b21.subs(b2, b21) == Basic(b21, b1)
|
| 127 |
+
assert b3.subs(b2, b1) == b2
|
| 128 |
+
|
| 129 |
+
assert b21.subs([(b2, b1), (b1, b2)]) == Basic(b2, b2)
|
| 130 |
+
|
| 131 |
+
assert b21.subs({b1: b2, b2: b1}) == Basic(b2, b2)
|
| 132 |
+
assert b21.subs(collections.ChainMap({b1: b2}, {b2: b1})) == Basic(b2, b2)
|
| 133 |
+
assert b21.subs(collections.OrderedDict([(b2, b1), (b1, b2)])) == Basic(b2, b2)
|
| 134 |
+
|
| 135 |
+
raises(ValueError, lambda: b21.subs('bad arg'))
|
| 136 |
+
raises(TypeError, lambda: b21.subs(b1, b2, b3))
|
| 137 |
+
# dict(b1=foo) creates a string 'b1' but leaves foo unchanged; subs
|
| 138 |
+
# will convert the first to a symbol but will raise an error if foo
|
| 139 |
+
# cannot be sympified; sympification is strict if foo is not string
|
| 140 |
+
raises(TypeError, lambda: b21.subs(b1='bad arg'))
|
| 141 |
+
|
| 142 |
+
assert Symbol("text").subs({"text": b1}) == b1
|
| 143 |
+
assert Symbol("s").subs({"s": 1}) == 1
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def test_subs_with_unicode_symbols():
|
| 147 |
+
expr = Symbol('var1')
|
| 148 |
+
replaced = expr.subs('var1', 'x')
|
| 149 |
+
assert replaced.name == 'x'
|
| 150 |
+
|
| 151 |
+
replaced = expr.subs('var1', 'x')
|
| 152 |
+
assert replaced.name == 'x'
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def test_atoms():
|
| 156 |
+
assert b21.atoms() == {Basic()}
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def test_free_symbols_empty():
|
| 160 |
+
assert b21.free_symbols == set()
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def test_doit():
|
| 164 |
+
assert b21.doit() == b21
|
| 165 |
+
assert b21.doit(deep=False) == b21
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def test_S():
|
| 169 |
+
assert repr(S) == 'S'
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def test_xreplace():
|
| 173 |
+
assert b21.xreplace({b2: b1}) == Basic(b1, b1)
|
| 174 |
+
assert b21.xreplace({b2: b21}) == Basic(b21, b1)
|
| 175 |
+
assert b3.xreplace({b2: b1}) == b2
|
| 176 |
+
assert Basic(b1, b2).xreplace({b1: b2, b2: b1}) == Basic(b2, b1)
|
| 177 |
+
assert Atom(b1).xreplace({b1: b2}) == Atom(b1)
|
| 178 |
+
assert Atom(b1).xreplace({Atom(b1): b2}) == b2
|
| 179 |
+
raises(TypeError, lambda: b1.xreplace())
|
| 180 |
+
raises(TypeError, lambda: b1.xreplace([b1, b2]))
|
| 181 |
+
for f in (exp, Function('f')):
|
| 182 |
+
assert f.xreplace({}) == f
|
| 183 |
+
assert f.xreplace({}, hack2=True) == f
|
| 184 |
+
assert f.xreplace({f: b1}) == b1
|
| 185 |
+
assert f.xreplace({f: b1}, hack2=True) == b1
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def test_sorted_args():
|
| 189 |
+
x = symbols('x')
|
| 190 |
+
assert b21._sorted_args == b21.args
|
| 191 |
+
raises(AttributeError, lambda: x._sorted_args)
|
| 192 |
+
|
| 193 |
+
def test_call():
|
| 194 |
+
x, y = symbols('x y')
|
| 195 |
+
# See the long history of this in issues 5026 and 5105.
|
| 196 |
+
|
| 197 |
+
raises(TypeError, lambda: sin(x)({ x : 1, sin(x) : 2}))
|
| 198 |
+
raises(TypeError, lambda: sin(x)(1))
|
| 199 |
+
|
| 200 |
+
# No effect as there are no callables
|
| 201 |
+
assert sin(x).rcall(1) == sin(x)
|
| 202 |
+
assert (1 + sin(x)).rcall(1) == 1 + sin(x)
|
| 203 |
+
|
| 204 |
+
# Effect in the presence of callables
|
| 205 |
+
l = Lambda(x, 2*x)
|
| 206 |
+
assert (l + x).rcall(y) == 2*y + x
|
| 207 |
+
assert (x**l).rcall(2) == x**4
|
| 208 |
+
# TODO UndefinedFunction does not subclass Expr
|
| 209 |
+
#f = Function('f')
|
| 210 |
+
#assert (2*f)(x) == 2*f(x)
|
| 211 |
+
|
| 212 |
+
assert (Q.real & Q.positive).rcall(x) == Q.real(x) & Q.positive(x)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def test_rewrite():
|
| 216 |
+
x, y, z = symbols('x y z')
|
| 217 |
+
a, b = symbols('a b')
|
| 218 |
+
f1 = sin(x) + cos(x)
|
| 219 |
+
assert f1.rewrite(cos,exp) == exp(I*x)/2 + sin(x) + exp(-I*x)/2
|
| 220 |
+
assert f1.rewrite([cos],sin) == sin(x) + sin(x + pi/2, evaluate=False)
|
| 221 |
+
f2 = sin(x) + cos(y)/gamma(z)
|
| 222 |
+
assert f2.rewrite(sin,exp) == -I*(exp(I*x) - exp(-I*x))/2 + cos(y)/gamma(z)
|
| 223 |
+
|
| 224 |
+
assert f1.rewrite() == f1
|
| 225 |
+
|
| 226 |
+
def test_literal_evalf_is_number_is_zero_is_comparable():
|
| 227 |
+
x = symbols('x')
|
| 228 |
+
f = Function('f')
|
| 229 |
+
|
| 230 |
+
# issue 5033
|
| 231 |
+
assert f.is_number is False
|
| 232 |
+
# issue 6646
|
| 233 |
+
assert f(1).is_number is False
|
| 234 |
+
i = Integral(0, (x, x, x))
|
| 235 |
+
# expressions that are symbolically 0 can be difficult to prove
|
| 236 |
+
# so in case there is some easy way to know if something is 0
|
| 237 |
+
# it should appear in the is_zero property for that object;
|
| 238 |
+
# if is_zero is true evalf should always be able to compute that
|
| 239 |
+
# zero
|
| 240 |
+
assert i.n() == 0
|
| 241 |
+
assert i.is_zero
|
| 242 |
+
assert i.is_number is False
|
| 243 |
+
assert i.evalf(2, strict=False) == 0
|
| 244 |
+
|
| 245 |
+
# issue 10268
|
| 246 |
+
n = sin(1)**2 + cos(1)**2 - 1
|
| 247 |
+
assert n.is_comparable is False
|
| 248 |
+
assert n.n(2).is_comparable is False
|
| 249 |
+
assert n.n(2).n(2).is_comparable
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def test_as_Basic():
|
| 253 |
+
assert as_Basic(1) is S.One
|
| 254 |
+
assert as_Basic(()) == Tuple()
|
| 255 |
+
raises(TypeError, lambda: as_Basic([]))
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def test_atomic():
|
| 259 |
+
g, h = map(Function, 'gh')
|
| 260 |
+
x = symbols('x')
|
| 261 |
+
assert _atomic(g(x + h(x))) == {g(x + h(x))}
|
| 262 |
+
assert _atomic(g(x + h(x)), recursive=True) == {h(x), x, g(x + h(x))}
|
| 263 |
+
assert _atomic(1) == set()
|
| 264 |
+
assert _atomic(Basic(S(1), S(2))) == set()
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def test_as_dummy():
|
| 268 |
+
u, v, x, y, z, _0, _1 = symbols('u v x y z _0 _1')
|
| 269 |
+
assert Lambda(x, x + 1).as_dummy() == Lambda(_0, _0 + 1)
|
| 270 |
+
assert Lambda(x, x + _0).as_dummy() == Lambda(_1, _0 + _1)
|
| 271 |
+
eq = (1 + Sum(x, (x, 1, x)))
|
| 272 |
+
ans = 1 + Sum(_0, (_0, 1, x))
|
| 273 |
+
once = eq.as_dummy()
|
| 274 |
+
assert once == ans
|
| 275 |
+
twice = once.as_dummy()
|
| 276 |
+
assert twice == ans
|
| 277 |
+
assert Integral(x + _0, (x, x + 1), (_0, 1, 2)
|
| 278 |
+
).as_dummy() == Integral(_0 + _1, (_0, x + 1), (_1, 1, 2))
|
| 279 |
+
for T in (Symbol, Dummy):
|
| 280 |
+
d = T('x', real=True)
|
| 281 |
+
D = d.as_dummy()
|
| 282 |
+
assert D != d and D.func == Dummy and D.is_real is None
|
| 283 |
+
assert Dummy().as_dummy().is_commutative
|
| 284 |
+
assert Dummy(commutative=False).as_dummy().is_commutative is False
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def test_canonical_variables():
|
| 288 |
+
x, i0, i1 = symbols('x _:2')
|
| 289 |
+
assert Integral(x, (x, x + 1)).canonical_variables == {x: i0}
|
| 290 |
+
assert Integral(x, (x, x + 1), (i0, 1, 2)).canonical_variables == {
|
| 291 |
+
x: i0, i0: i1}
|
| 292 |
+
assert Integral(x, (x, x + i0)).canonical_variables == {x: i1}
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def test_replace_exceptions():
|
| 296 |
+
from sympy.core.symbol import Wild
|
| 297 |
+
x, y = symbols('x y')
|
| 298 |
+
e = (x**2 + x*y)
|
| 299 |
+
raises(TypeError, lambda: e.replace(sin, 2))
|
| 300 |
+
b = Wild('b')
|
| 301 |
+
c = Wild('c')
|
| 302 |
+
raises(TypeError, lambda: e.replace(b*c, c.is_real))
|
| 303 |
+
raises(TypeError, lambda: e.replace(b.is_real, 1))
|
| 304 |
+
raises(TypeError, lambda: e.replace(lambda d: d.is_Number, 1))
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def test_ManagedProperties():
|
| 308 |
+
# ManagedProperties is now deprecated. Here we do our best to check that if
|
| 309 |
+
# someone is using it then it does work in the way that it previously did
|
| 310 |
+
# but gives a deprecation warning.
|
| 311 |
+
from sympy.core.assumptions import ManagedProperties
|
| 312 |
+
|
| 313 |
+
myclasses = []
|
| 314 |
+
|
| 315 |
+
class MyMeta(ManagedProperties):
|
| 316 |
+
def __init__(cls, *args, **kwargs):
|
| 317 |
+
myclasses.append('executed')
|
| 318 |
+
super().__init__(*args, **kwargs)
|
| 319 |
+
|
| 320 |
+
code = """
|
| 321 |
+
class MySubclass(Basic, metaclass=MyMeta):
|
| 322 |
+
pass
|
| 323 |
+
"""
|
| 324 |
+
with warns_deprecated_sympy():
|
| 325 |
+
exec(code)
|
| 326 |
+
|
| 327 |
+
assert myclasses == ['executed']
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def test_generic():
|
| 331 |
+
# https://github.com/sympy/sympy/issues/25399
|
| 332 |
+
class A(Symbol, Generic[T]):
|
| 333 |
+
pass
|
| 334 |
+
|
| 335 |
+
class B(A[T]):
|
| 336 |
+
pass
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def test_rewrite_abs():
|
| 340 |
+
# https://github.com/sympy/sympy/issues/27323
|
| 341 |
+
x = Symbol('x')
|
| 342 |
+
assert sign(x).rewrite(abs) == sign(x).rewrite(Abs)
|
| 343 |
+
assert sign(x).rewrite(abs) == Piecewise((0, Eq(x, 0)), (x / Abs(x), True))
|
.venv/lib/python3.13/site-packages/sympy/core/tests/test_kind.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.add import Add
|
| 2 |
+
from sympy.core.kind import NumberKind, UndefinedKind
|
| 3 |
+
from sympy.core.mul import Mul
|
| 4 |
+
from sympy.core.numbers import pi, zoo, I, AlgebraicNumber
|
| 5 |
+
from sympy.core.singleton import S
|
| 6 |
+
from sympy.core.symbol import Symbol
|
| 7 |
+
from sympy.integrals.integrals import Integral
|
| 8 |
+
from sympy.core.function import Derivative
|
| 9 |
+
from sympy.matrices import (Matrix, SparseMatrix, ImmutableMatrix,
|
| 10 |
+
ImmutableSparseMatrix, MatrixSymbol, MatrixKind, MatMul)
|
| 11 |
+
|
| 12 |
+
comm_x = Symbol('x')
|
| 13 |
+
noncomm_x = Symbol('x', commutative=False)
|
| 14 |
+
|
| 15 |
+
def test_NumberKind():
|
| 16 |
+
assert S.One.kind is NumberKind
|
| 17 |
+
assert pi.kind is NumberKind
|
| 18 |
+
assert S.NaN.kind is NumberKind
|
| 19 |
+
assert zoo.kind is NumberKind
|
| 20 |
+
assert I.kind is NumberKind
|
| 21 |
+
assert AlgebraicNumber(1).kind is NumberKind
|
| 22 |
+
|
| 23 |
+
def test_Add_kind():
|
| 24 |
+
assert Add(2, 3, evaluate=False).kind is NumberKind
|
| 25 |
+
assert Add(2,comm_x).kind is NumberKind
|
| 26 |
+
assert Add(2,noncomm_x).kind is UndefinedKind
|
| 27 |
+
|
| 28 |
+
def test_mul_kind():
|
| 29 |
+
assert Mul(2,comm_x, evaluate=False).kind is NumberKind
|
| 30 |
+
assert Mul(2,3, evaluate=False).kind is NumberKind
|
| 31 |
+
assert Mul(noncomm_x,2, evaluate=False).kind is UndefinedKind
|
| 32 |
+
assert Mul(2,noncomm_x, evaluate=False).kind is UndefinedKind
|
| 33 |
+
|
| 34 |
+
def test_Symbol_kind():
|
| 35 |
+
assert comm_x.kind is NumberKind
|
| 36 |
+
assert noncomm_x.kind is UndefinedKind
|
| 37 |
+
|
| 38 |
+
def test_Integral_kind():
|
| 39 |
+
A = MatrixSymbol('A', 2,2)
|
| 40 |
+
assert Integral(comm_x, comm_x).kind is NumberKind
|
| 41 |
+
assert Integral(A, comm_x).kind is MatrixKind(NumberKind)
|
| 42 |
+
|
| 43 |
+
def test_Derivative_kind():
|
| 44 |
+
A = MatrixSymbol('A', 2,2)
|
| 45 |
+
assert Derivative(comm_x, comm_x).kind is NumberKind
|
| 46 |
+
assert Derivative(A, comm_x).kind is MatrixKind(NumberKind)
|
| 47 |
+
|
| 48 |
+
def test_Matrix_kind():
|
| 49 |
+
classes = (Matrix, SparseMatrix, ImmutableMatrix, ImmutableSparseMatrix)
|
| 50 |
+
for cls in classes:
|
| 51 |
+
m = cls.zeros(3, 2)
|
| 52 |
+
assert m.kind is MatrixKind(NumberKind)
|
| 53 |
+
|
| 54 |
+
def test_MatMul_kind():
|
| 55 |
+
M = Matrix([[1,2],[3,4]])
|
| 56 |
+
assert MatMul(2, M).kind is MatrixKind(NumberKind)
|
| 57 |
+
assert MatMul(comm_x, M).kind is MatrixKind(NumberKind)
|
.venv/lib/python3.13/site-packages/sympy/core/tests/test_multidimensional.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.function import (Derivative, Function, diff)
|
| 2 |
+
from sympy.core.symbol import symbols
|
| 3 |
+
from sympy.functions.elementary.trigonometric import sin
|
| 4 |
+
from sympy.core.multidimensional import vectorize
|
| 5 |
+
x, y, z = symbols('x y z')
|
| 6 |
+
f, g, h = list(map(Function, 'fgh'))
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_vectorize():
|
| 10 |
+
@vectorize(0)
|
| 11 |
+
def vsin(x):
|
| 12 |
+
return sin(x)
|
| 13 |
+
|
| 14 |
+
assert vsin([1, x, y]) == [sin(1), sin(x), sin(y)]
|
| 15 |
+
|
| 16 |
+
@vectorize(0, 1)
|
| 17 |
+
def vdiff(f, y):
|
| 18 |
+
return diff(f, y)
|
| 19 |
+
|
| 20 |
+
assert vdiff([f(x, y, z), g(x, y, z), h(x, y, z)], [x, y, z]) == \
|
| 21 |
+
[[Derivative(f(x, y, z), x), Derivative(f(x, y, z), y),
|
| 22 |
+
Derivative(f(x, y, z), z)], [Derivative(g(x, y, z), x),
|
| 23 |
+
Derivative(g(x, y, z), y), Derivative(g(x, y, z), z)],
|
| 24 |
+
[Derivative(h(x, y, z), x), Derivative(h(x, y, z), y), Derivative(h(x, y, z), z)]]
|
.venv/lib/python3.13/site-packages/sympy/core/tests/test_singleton.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.basic import Basic
|
| 2 |
+
from sympy.core.numbers import Rational
|
| 3 |
+
from sympy.core.singleton import S, Singleton
|
| 4 |
+
|
| 5 |
+
def test_Singleton():
|
| 6 |
+
|
| 7 |
+
class MySingleton(Basic, metaclass=Singleton):
|
| 8 |
+
pass
|
| 9 |
+
|
| 10 |
+
MySingleton() # force instantiation
|
| 11 |
+
assert MySingleton() is not Basic()
|
| 12 |
+
assert MySingleton() is MySingleton()
|
| 13 |
+
assert S.MySingleton is MySingleton()
|
| 14 |
+
|
| 15 |
+
class MySingleton_sub(MySingleton):
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
MySingleton_sub()
|
| 19 |
+
assert MySingleton_sub() is not MySingleton()
|
| 20 |
+
assert MySingleton_sub() is MySingleton_sub()
|
| 21 |
+
|
| 22 |
+
def test_singleton_redefinition():
|
| 23 |
+
class TestSingleton(Basic, metaclass=Singleton):
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
assert TestSingleton() is S.TestSingleton
|
| 27 |
+
|
| 28 |
+
class TestSingleton(Basic, metaclass=Singleton):
|
| 29 |
+
pass
|
| 30 |
+
|
| 31 |
+
assert TestSingleton() is S.TestSingleton
|
| 32 |
+
|
| 33 |
+
def test_names_in_namespace():
|
| 34 |
+
# Every singleton name should be accessible from the 'from sympy import *'
|
| 35 |
+
# namespace in addition to the S object. However, it does not need to be
|
| 36 |
+
# by the same name (e.g., oo instead of S.Infinity).
|
| 37 |
+
|
| 38 |
+
# As a general rule, things should only be added to the singleton registry
|
| 39 |
+
# if they are used often enough that code can benefit either from the
|
| 40 |
+
# performance benefit of being able to use 'is' (this only matters in very
|
| 41 |
+
# tight loops), or from the memory savings of having exactly one instance
|
| 42 |
+
# (this matters for the numbers singletons, but very little else). The
|
| 43 |
+
# singleton registry is already a bit overpopulated, and things cannot be
|
| 44 |
+
# removed from it without breaking backwards compatibility. So if you got
|
| 45 |
+
# here by adding something new to the singletons, ask yourself if it
|
| 46 |
+
# really needs to be singletonized. Note that SymPy classes compare to one
|
| 47 |
+
# another just fine, so Class() == Class() will give True even if each
|
| 48 |
+
# Class() returns a new instance. Having unique instances is only
|
| 49 |
+
# necessary for the above noted performance gains. It should not be needed
|
| 50 |
+
# for any behavioral purposes.
|
| 51 |
+
|
| 52 |
+
# If you determine that something really should be a singleton, it must be
|
| 53 |
+
# accessible to sympify() without using 'S' (hence this test). Also, its
|
| 54 |
+
# str printer should print a form that does not use S. This is because
|
| 55 |
+
# sympify() disables attribute lookups by default for safety purposes.
|
| 56 |
+
d = {}
|
| 57 |
+
exec('from sympy import *', d)
|
| 58 |
+
|
| 59 |
+
for name in dir(S) + list(S._classes_to_install):
|
| 60 |
+
if name.startswith('_'):
|
| 61 |
+
continue
|
| 62 |
+
if name == 'register':
|
| 63 |
+
continue
|
| 64 |
+
if isinstance(getattr(S, name), Rational):
|
| 65 |
+
continue
|
| 66 |
+
if getattr(S, name).__module__.startswith('sympy.physics'):
|
| 67 |
+
continue
|
| 68 |
+
if name in ['MySingleton', 'MySingleton_sub', 'TestSingleton']:
|
| 69 |
+
# From the tests above
|
| 70 |
+
continue
|
| 71 |
+
if name == 'NegativeInfinity':
|
| 72 |
+
# Accessible by -oo
|
| 73 |
+
continue
|
| 74 |
+
|
| 75 |
+
# Use is here to ensure it is the exact same object
|
| 76 |
+
assert any(getattr(S, name) is i for i in d.values()), name
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Module for algebraic geometry and commutative algebra."""
|
| 2 |
+
|
| 3 |
+
from .homomorphisms import homomorphism
|
| 4 |
+
|
| 5 |
+
__all__ = ['homomorphism']
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/extensions.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Finite extensions of ring domains."""
|
| 2 |
+
|
| 3 |
+
from sympy.polys.domains.domain import Domain
|
| 4 |
+
from sympy.polys.domains.domainelement import DomainElement
|
| 5 |
+
from sympy.polys.polyerrors import (CoercionFailed, NotInvertible,
|
| 6 |
+
GeneratorsError)
|
| 7 |
+
from sympy.polys.polytools import Poly
|
| 8 |
+
from sympy.printing.defaults import DefaultPrinting
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class ExtensionElement(DomainElement, DefaultPrinting):
|
| 12 |
+
"""
|
| 13 |
+
Element of a finite extension.
|
| 14 |
+
|
| 15 |
+
A class of univariate polynomials modulo the ``modulus``
|
| 16 |
+
of the extension ``ext``. It is represented by the
|
| 17 |
+
unique polynomial ``rep`` of lowest degree. Both
|
| 18 |
+
``rep`` and the representation ``mod`` of ``modulus``
|
| 19 |
+
are of class DMP.
|
| 20 |
+
|
| 21 |
+
"""
|
| 22 |
+
__slots__ = ('rep', 'ext')
|
| 23 |
+
|
| 24 |
+
def __init__(self, rep, ext):
|
| 25 |
+
self.rep = rep
|
| 26 |
+
self.ext = ext
|
| 27 |
+
|
| 28 |
+
def parent(f):
|
| 29 |
+
return f.ext
|
| 30 |
+
|
| 31 |
+
def as_expr(f):
|
| 32 |
+
return f.ext.to_sympy(f)
|
| 33 |
+
|
| 34 |
+
def __bool__(f):
|
| 35 |
+
return bool(f.rep)
|
| 36 |
+
|
| 37 |
+
def __pos__(f):
|
| 38 |
+
return f
|
| 39 |
+
|
| 40 |
+
def __neg__(f):
|
| 41 |
+
return ExtElem(-f.rep, f.ext)
|
| 42 |
+
|
| 43 |
+
def _get_rep(f, g):
|
| 44 |
+
if isinstance(g, ExtElem):
|
| 45 |
+
if g.ext == f.ext:
|
| 46 |
+
return g.rep
|
| 47 |
+
else:
|
| 48 |
+
return None
|
| 49 |
+
else:
|
| 50 |
+
try:
|
| 51 |
+
g = f.ext.convert(g)
|
| 52 |
+
return g.rep
|
| 53 |
+
except CoercionFailed:
|
| 54 |
+
return None
|
| 55 |
+
|
| 56 |
+
def __add__(f, g):
|
| 57 |
+
rep = f._get_rep(g)
|
| 58 |
+
if rep is not None:
|
| 59 |
+
return ExtElem(f.rep + rep, f.ext)
|
| 60 |
+
else:
|
| 61 |
+
return NotImplemented
|
| 62 |
+
|
| 63 |
+
__radd__ = __add__
|
| 64 |
+
|
| 65 |
+
def __sub__(f, g):
|
| 66 |
+
rep = f._get_rep(g)
|
| 67 |
+
if rep is not None:
|
| 68 |
+
return ExtElem(f.rep - rep, f.ext)
|
| 69 |
+
else:
|
| 70 |
+
return NotImplemented
|
| 71 |
+
|
| 72 |
+
def __rsub__(f, g):
|
| 73 |
+
rep = f._get_rep(g)
|
| 74 |
+
if rep is not None:
|
| 75 |
+
return ExtElem(rep - f.rep, f.ext)
|
| 76 |
+
else:
|
| 77 |
+
return NotImplemented
|
| 78 |
+
|
| 79 |
+
def __mul__(f, g):
|
| 80 |
+
rep = f._get_rep(g)
|
| 81 |
+
if rep is not None:
|
| 82 |
+
return ExtElem((f.rep * rep) % f.ext.mod, f.ext)
|
| 83 |
+
else:
|
| 84 |
+
return NotImplemented
|
| 85 |
+
|
| 86 |
+
__rmul__ = __mul__
|
| 87 |
+
|
| 88 |
+
def _divcheck(f):
|
| 89 |
+
"""Raise if division is not implemented for this divisor"""
|
| 90 |
+
if not f:
|
| 91 |
+
raise NotInvertible('Zero divisor')
|
| 92 |
+
elif f.ext.is_Field:
|
| 93 |
+
return True
|
| 94 |
+
elif f.rep.is_ground and f.ext.domain.is_unit(f.rep.LC()):
|
| 95 |
+
return True
|
| 96 |
+
else:
|
| 97 |
+
# Some cases like (2*x + 2)/2 over ZZ will fail here. It is
|
| 98 |
+
# unclear how to implement division in general if the ground
|
| 99 |
+
# domain is not a field so for now it was decided to restrict the
|
| 100 |
+
# implementation to division by invertible constants.
|
| 101 |
+
msg = (f"Can not invert {f} in {f.ext}. "
|
| 102 |
+
"Only division by invertible constants is implemented.")
|
| 103 |
+
raise NotImplementedError(msg)
|
| 104 |
+
|
| 105 |
+
def inverse(f):
|
| 106 |
+
"""Multiplicative inverse.
|
| 107 |
+
|
| 108 |
+
Raises
|
| 109 |
+
======
|
| 110 |
+
|
| 111 |
+
NotInvertible
|
| 112 |
+
If the element is a zero divisor.
|
| 113 |
+
|
| 114 |
+
"""
|
| 115 |
+
f._divcheck()
|
| 116 |
+
|
| 117 |
+
if f.ext.is_Field:
|
| 118 |
+
invrep = f.rep.invert(f.ext.mod)
|
| 119 |
+
else:
|
| 120 |
+
R = f.ext.ring
|
| 121 |
+
invrep = R.exquo(R.one, f.rep)
|
| 122 |
+
|
| 123 |
+
return ExtElem(invrep, f.ext)
|
| 124 |
+
|
| 125 |
+
def __truediv__(f, g):
|
| 126 |
+
rep = f._get_rep(g)
|
| 127 |
+
if rep is None:
|
| 128 |
+
return NotImplemented
|
| 129 |
+
g = ExtElem(rep, f.ext)
|
| 130 |
+
|
| 131 |
+
try:
|
| 132 |
+
ginv = g.inverse()
|
| 133 |
+
except NotInvertible:
|
| 134 |
+
raise ZeroDivisionError(f"{f} / {g}")
|
| 135 |
+
|
| 136 |
+
return f * ginv
|
| 137 |
+
|
| 138 |
+
__floordiv__ = __truediv__
|
| 139 |
+
|
| 140 |
+
def __rtruediv__(f, g):
|
| 141 |
+
try:
|
| 142 |
+
g = f.ext.convert(g)
|
| 143 |
+
except CoercionFailed:
|
| 144 |
+
return NotImplemented
|
| 145 |
+
return g / f
|
| 146 |
+
|
| 147 |
+
__rfloordiv__ = __rtruediv__
|
| 148 |
+
|
| 149 |
+
def __mod__(f, g):
|
| 150 |
+
rep = f._get_rep(g)
|
| 151 |
+
if rep is None:
|
| 152 |
+
return NotImplemented
|
| 153 |
+
g = ExtElem(rep, f.ext)
|
| 154 |
+
|
| 155 |
+
try:
|
| 156 |
+
g._divcheck()
|
| 157 |
+
except NotInvertible:
|
| 158 |
+
raise ZeroDivisionError(f"{f} % {g}")
|
| 159 |
+
|
| 160 |
+
# Division where defined is always exact so there is no remainder
|
| 161 |
+
return f.ext.zero
|
| 162 |
+
|
| 163 |
+
def __rmod__(f, g):
|
| 164 |
+
try:
|
| 165 |
+
g = f.ext.convert(g)
|
| 166 |
+
except CoercionFailed:
|
| 167 |
+
return NotImplemented
|
| 168 |
+
return g % f
|
| 169 |
+
|
| 170 |
+
def __pow__(f, n):
|
| 171 |
+
if not isinstance(n, int):
|
| 172 |
+
raise TypeError("exponent of type 'int' expected")
|
| 173 |
+
if n < 0:
|
| 174 |
+
try:
|
| 175 |
+
f, n = f.inverse(), -n
|
| 176 |
+
except NotImplementedError:
|
| 177 |
+
raise ValueError("negative powers are not defined")
|
| 178 |
+
|
| 179 |
+
b = f.rep
|
| 180 |
+
m = f.ext.mod
|
| 181 |
+
r = f.ext.one.rep
|
| 182 |
+
while n > 0:
|
| 183 |
+
if n % 2:
|
| 184 |
+
r = (r*b) % m
|
| 185 |
+
b = (b*b) % m
|
| 186 |
+
n //= 2
|
| 187 |
+
|
| 188 |
+
return ExtElem(r, f.ext)
|
| 189 |
+
|
| 190 |
+
def __eq__(f, g):
|
| 191 |
+
if isinstance(g, ExtElem):
|
| 192 |
+
return f.rep == g.rep and f.ext == g.ext
|
| 193 |
+
else:
|
| 194 |
+
return NotImplemented
|
| 195 |
+
|
| 196 |
+
def __ne__(f, g):
|
| 197 |
+
return not f == g
|
| 198 |
+
|
| 199 |
+
def __hash__(f):
|
| 200 |
+
return hash((f.rep, f.ext))
|
| 201 |
+
|
| 202 |
+
def __str__(f):
|
| 203 |
+
from sympy.printing.str import sstr
|
| 204 |
+
return sstr(f.as_expr())
|
| 205 |
+
|
| 206 |
+
__repr__ = __str__
|
| 207 |
+
|
| 208 |
+
@property
|
| 209 |
+
def is_ground(f):
|
| 210 |
+
return f.rep.is_ground
|
| 211 |
+
|
| 212 |
+
def to_ground(f):
|
| 213 |
+
[c] = f.rep.to_list()
|
| 214 |
+
return c
|
| 215 |
+
|
| 216 |
+
ExtElem = ExtensionElement
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
class MonogenicFiniteExtension(Domain):
|
| 220 |
+
r"""
|
| 221 |
+
Finite extension generated by an integral element.
|
| 222 |
+
|
| 223 |
+
The generator is defined by a monic univariate
|
| 224 |
+
polynomial derived from the argument ``mod``.
|
| 225 |
+
|
| 226 |
+
A shorter alias is ``FiniteExtension``.
|
| 227 |
+
|
| 228 |
+
Examples
|
| 229 |
+
========
|
| 230 |
+
|
| 231 |
+
Quadratic integer ring $\mathbb{Z}[\sqrt2]$:
|
| 232 |
+
|
| 233 |
+
>>> from sympy import Symbol, Poly
|
| 234 |
+
>>> from sympy.polys.agca.extensions import FiniteExtension
|
| 235 |
+
>>> x = Symbol('x')
|
| 236 |
+
>>> R = FiniteExtension(Poly(x**2 - 2)); R
|
| 237 |
+
ZZ[x]/(x**2 - 2)
|
| 238 |
+
>>> R.rank
|
| 239 |
+
2
|
| 240 |
+
>>> R(1 + x)*(3 - 2*x)
|
| 241 |
+
x - 1
|
| 242 |
+
|
| 243 |
+
Finite field $GF(5^3)$ defined by the primitive
|
| 244 |
+
polynomial $x^3 + x^2 + 2$ (over $\mathbb{Z}_5$).
|
| 245 |
+
|
| 246 |
+
>>> F = FiniteExtension(Poly(x**3 + x**2 + 2, modulus=5)); F
|
| 247 |
+
GF(5)[x]/(x**3 + x**2 + 2)
|
| 248 |
+
>>> F.basis
|
| 249 |
+
(1, x, x**2)
|
| 250 |
+
>>> F(x + 3)/(x**2 + 2)
|
| 251 |
+
-2*x**2 + x + 2
|
| 252 |
+
|
| 253 |
+
Function field of an elliptic curve:
|
| 254 |
+
|
| 255 |
+
>>> t = Symbol('t')
|
| 256 |
+
>>> FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True))
|
| 257 |
+
ZZ(x)[t]/(t**2 - x**3 - x + 1)
|
| 258 |
+
|
| 259 |
+
"""
|
| 260 |
+
is_FiniteExtension = True
|
| 261 |
+
|
| 262 |
+
dtype = ExtensionElement
|
| 263 |
+
|
| 264 |
+
def __init__(self, mod):
|
| 265 |
+
if not (isinstance(mod, Poly) and mod.is_univariate):
|
| 266 |
+
raise TypeError("modulus must be a univariate Poly")
|
| 267 |
+
|
| 268 |
+
# Using auto=True (default) potentially changes the ground domain to a
|
| 269 |
+
# field whereas auto=False raises if division is not exact. We'll let
|
| 270 |
+
# the caller decide whether or not they want to put the ground domain
|
| 271 |
+
# over a field. In most uses mod is already monic.
|
| 272 |
+
mod = mod.monic(auto=False)
|
| 273 |
+
|
| 274 |
+
self.rank = mod.degree()
|
| 275 |
+
self.modulus = mod
|
| 276 |
+
self.mod = mod.rep # DMP representation
|
| 277 |
+
|
| 278 |
+
self.domain = dom = mod.domain
|
| 279 |
+
self.ring = dom.old_poly_ring(*mod.gens)
|
| 280 |
+
|
| 281 |
+
self.zero = self.convert(self.ring.zero)
|
| 282 |
+
self.one = self.convert(self.ring.one)
|
| 283 |
+
|
| 284 |
+
gen = self.ring.gens[0]
|
| 285 |
+
self.symbol = self.ring.symbols[0]
|
| 286 |
+
self.generator = self.convert(gen)
|
| 287 |
+
self.basis = tuple(self.convert(gen**i) for i in range(self.rank))
|
| 288 |
+
|
| 289 |
+
# XXX: It might be necessary to check mod.is_irreducible here
|
| 290 |
+
self.is_Field = self.domain.is_Field
|
| 291 |
+
|
| 292 |
+
def new(self, arg):
|
| 293 |
+
rep = self.ring.convert(arg)
|
| 294 |
+
return ExtElem(rep % self.mod, self)
|
| 295 |
+
|
| 296 |
+
def __eq__(self, other):
|
| 297 |
+
if not isinstance(other, FiniteExtension):
|
| 298 |
+
return False
|
| 299 |
+
return self.modulus == other.modulus
|
| 300 |
+
|
| 301 |
+
def __hash__(self):
|
| 302 |
+
return hash((self.__class__.__name__, self.modulus))
|
| 303 |
+
|
| 304 |
+
def __str__(self):
|
| 305 |
+
return "%s/(%s)" % (self.ring, self.modulus.as_expr())
|
| 306 |
+
|
| 307 |
+
__repr__ = __str__
|
| 308 |
+
|
| 309 |
+
@property
|
| 310 |
+
def has_CharacteristicZero(self):
|
| 311 |
+
return self.domain.has_CharacteristicZero
|
| 312 |
+
|
| 313 |
+
def characteristic(self):
|
| 314 |
+
return self.domain.characteristic()
|
| 315 |
+
|
| 316 |
+
def convert(self, f, base=None):
|
| 317 |
+
rep = self.ring.convert(f, base)
|
| 318 |
+
return ExtElem(rep % self.mod, self)
|
| 319 |
+
|
| 320 |
+
def convert_from(self, f, base):
|
| 321 |
+
rep = self.ring.convert(f, base)
|
| 322 |
+
return ExtElem(rep % self.mod, self)
|
| 323 |
+
|
| 324 |
+
def to_sympy(self, f):
|
| 325 |
+
return self.ring.to_sympy(f.rep)
|
| 326 |
+
|
| 327 |
+
def from_sympy(self, f):
|
| 328 |
+
return self.convert(f)
|
| 329 |
+
|
| 330 |
+
def set_domain(self, K):
|
| 331 |
+
mod = self.modulus.set_domain(K)
|
| 332 |
+
return self.__class__(mod)
|
| 333 |
+
|
| 334 |
+
def drop(self, *symbols):
|
| 335 |
+
if self.symbol in symbols:
|
| 336 |
+
raise GeneratorsError('Can not drop generator from FiniteExtension')
|
| 337 |
+
K = self.domain.drop(*symbols)
|
| 338 |
+
return self.set_domain(K)
|
| 339 |
+
|
| 340 |
+
def quo(self, f, g):
|
| 341 |
+
return self.exquo(f, g)
|
| 342 |
+
|
| 343 |
+
def exquo(self, f, g):
|
| 344 |
+
rep = self.ring.exquo(f.rep, g.rep)
|
| 345 |
+
return ExtElem(rep % self.mod, self)
|
| 346 |
+
|
| 347 |
+
def is_negative(self, a):
|
| 348 |
+
return False
|
| 349 |
+
|
| 350 |
+
def is_unit(self, a):
|
| 351 |
+
if self.is_Field:
|
| 352 |
+
return bool(a)
|
| 353 |
+
elif a.is_ground:
|
| 354 |
+
return self.domain.is_unit(a.to_ground())
|
| 355 |
+
|
| 356 |
+
FiniteExtension = MonogenicFiniteExtension
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/homomorphisms.py
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Computations with homomorphisms of modules and rings.
|
| 3 |
+
|
| 4 |
+
This module implements classes for representing homomorphisms of rings and
|
| 5 |
+
their modules. Instead of instantiating the classes directly, you should use
|
| 6 |
+
the function ``homomorphism(from, to, matrix)`` to create homomorphism objects.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
from sympy.polys.agca.modules import (Module, FreeModule, QuotientModule,
|
| 11 |
+
SubModule, SubQuotientModule)
|
| 12 |
+
from sympy.polys.polyerrors import CoercionFailed
|
| 13 |
+
|
| 14 |
+
# The main computational task for module homomorphisms is kernels.
|
| 15 |
+
# For this reason, the concrete classes are organised by domain module type.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ModuleHomomorphism:
|
| 19 |
+
"""
|
| 20 |
+
Abstract base class for module homomoprhisms. Do not instantiate.
|
| 21 |
+
|
| 22 |
+
Instead, use the ``homomorphism`` function:
|
| 23 |
+
|
| 24 |
+
>>> from sympy import QQ
|
| 25 |
+
>>> from sympy.abc import x
|
| 26 |
+
>>> from sympy.polys.agca import homomorphism
|
| 27 |
+
|
| 28 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 29 |
+
>>> homomorphism(F, F, [[1, 0], [0, 1]])
|
| 30 |
+
Matrix([
|
| 31 |
+
[1, 0], : QQ[x]**2 -> QQ[x]**2
|
| 32 |
+
[0, 1]])
|
| 33 |
+
|
| 34 |
+
Attributes:
|
| 35 |
+
|
| 36 |
+
- ring - the ring over which we are considering modules
|
| 37 |
+
- domain - the domain module
|
| 38 |
+
- codomain - the codomain module
|
| 39 |
+
- _ker - cached kernel
|
| 40 |
+
- _img - cached image
|
| 41 |
+
|
| 42 |
+
Non-implemented methods:
|
| 43 |
+
|
| 44 |
+
- _kernel
|
| 45 |
+
- _image
|
| 46 |
+
- _restrict_domain
|
| 47 |
+
- _restrict_codomain
|
| 48 |
+
- _quotient_domain
|
| 49 |
+
- _quotient_codomain
|
| 50 |
+
- _apply
|
| 51 |
+
- _mul_scalar
|
| 52 |
+
- _compose
|
| 53 |
+
- _add
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
def __init__(self, domain, codomain):
|
| 57 |
+
if not isinstance(domain, Module):
|
| 58 |
+
raise TypeError('Source must be a module, got %s' % domain)
|
| 59 |
+
if not isinstance(codomain, Module):
|
| 60 |
+
raise TypeError('Target must be a module, got %s' % codomain)
|
| 61 |
+
if domain.ring != codomain.ring:
|
| 62 |
+
raise ValueError('Source and codomain must be over same ring, '
|
| 63 |
+
'got %s != %s' % (domain, codomain))
|
| 64 |
+
self.domain = domain
|
| 65 |
+
self.codomain = codomain
|
| 66 |
+
self.ring = domain.ring
|
| 67 |
+
self._ker = None
|
| 68 |
+
self._img = None
|
| 69 |
+
|
| 70 |
+
def kernel(self):
|
| 71 |
+
r"""
|
| 72 |
+
Compute the kernel of ``self``.
|
| 73 |
+
|
| 74 |
+
That is, if ``self`` is the homomorphism `\phi: M \to N`, then compute
|
| 75 |
+
`ker(\phi) = \{x \in M | \phi(x) = 0\}`. This is a submodule of `M`.
|
| 76 |
+
|
| 77 |
+
Examples
|
| 78 |
+
========
|
| 79 |
+
|
| 80 |
+
>>> from sympy import QQ
|
| 81 |
+
>>> from sympy.abc import x
|
| 82 |
+
>>> from sympy.polys.agca import homomorphism
|
| 83 |
+
|
| 84 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 85 |
+
>>> homomorphism(F, F, [[1, 0], [x, 0]]).kernel()
|
| 86 |
+
<[x, -1]>
|
| 87 |
+
"""
|
| 88 |
+
if self._ker is None:
|
| 89 |
+
self._ker = self._kernel()
|
| 90 |
+
return self._ker
|
| 91 |
+
|
| 92 |
+
def image(self):
|
| 93 |
+
r"""
|
| 94 |
+
Compute the image of ``self``.
|
| 95 |
+
|
| 96 |
+
That is, if ``self`` is the homomorphism `\phi: M \to N`, then compute
|
| 97 |
+
`im(\phi) = \{\phi(x) | x \in M \}`. This is a submodule of `N`.
|
| 98 |
+
|
| 99 |
+
Examples
|
| 100 |
+
========
|
| 101 |
+
|
| 102 |
+
>>> from sympy import QQ
|
| 103 |
+
>>> from sympy.abc import x
|
| 104 |
+
>>> from sympy.polys.agca import homomorphism
|
| 105 |
+
|
| 106 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 107 |
+
>>> homomorphism(F, F, [[1, 0], [x, 0]]).image() == F.submodule([1, 0])
|
| 108 |
+
True
|
| 109 |
+
"""
|
| 110 |
+
if self._img is None:
|
| 111 |
+
self._img = self._image()
|
| 112 |
+
return self._img
|
| 113 |
+
|
| 114 |
+
def _kernel(self):
|
| 115 |
+
"""Compute the kernel of ``self``."""
|
| 116 |
+
raise NotImplementedError
|
| 117 |
+
|
| 118 |
+
def _image(self):
|
| 119 |
+
"""Compute the image of ``self``."""
|
| 120 |
+
raise NotImplementedError
|
| 121 |
+
|
| 122 |
+
def _restrict_domain(self, sm):
|
| 123 |
+
"""Implementation of domain restriction."""
|
| 124 |
+
raise NotImplementedError
|
| 125 |
+
|
| 126 |
+
def _restrict_codomain(self, sm):
|
| 127 |
+
"""Implementation of codomain restriction."""
|
| 128 |
+
raise NotImplementedError
|
| 129 |
+
|
| 130 |
+
def _quotient_domain(self, sm):
|
| 131 |
+
"""Implementation of domain quotient."""
|
| 132 |
+
raise NotImplementedError
|
| 133 |
+
|
| 134 |
+
def _quotient_codomain(self, sm):
|
| 135 |
+
"""Implementation of codomain quotient."""
|
| 136 |
+
raise NotImplementedError
|
| 137 |
+
|
| 138 |
+
def restrict_domain(self, sm):
|
| 139 |
+
"""
|
| 140 |
+
Return ``self``, with the domain restricted to ``sm``.
|
| 141 |
+
|
| 142 |
+
Here ``sm`` has to be a submodule of ``self.domain``.
|
| 143 |
+
|
| 144 |
+
Examples
|
| 145 |
+
========
|
| 146 |
+
|
| 147 |
+
>>> from sympy import QQ
|
| 148 |
+
>>> from sympy.abc import x
|
| 149 |
+
>>> from sympy.polys.agca import homomorphism
|
| 150 |
+
|
| 151 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 152 |
+
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
|
| 153 |
+
>>> h
|
| 154 |
+
Matrix([
|
| 155 |
+
[1, x], : QQ[x]**2 -> QQ[x]**2
|
| 156 |
+
[0, 0]])
|
| 157 |
+
>>> h.restrict_domain(F.submodule([1, 0]))
|
| 158 |
+
Matrix([
|
| 159 |
+
[1, x], : <[1, 0]> -> QQ[x]**2
|
| 160 |
+
[0, 0]])
|
| 161 |
+
|
| 162 |
+
This is the same as just composing on the right with the submodule
|
| 163 |
+
inclusion:
|
| 164 |
+
|
| 165 |
+
>>> h * F.submodule([1, 0]).inclusion_hom()
|
| 166 |
+
Matrix([
|
| 167 |
+
[1, x], : <[1, 0]> -> QQ[x]**2
|
| 168 |
+
[0, 0]])
|
| 169 |
+
"""
|
| 170 |
+
if not self.domain.is_submodule(sm):
|
| 171 |
+
raise ValueError('sm must be a submodule of %s, got %s'
|
| 172 |
+
% (self.domain, sm))
|
| 173 |
+
if sm == self.domain:
|
| 174 |
+
return self
|
| 175 |
+
return self._restrict_domain(sm)
|
| 176 |
+
|
| 177 |
+
def restrict_codomain(self, sm):
|
| 178 |
+
"""
|
| 179 |
+
Return ``self``, with codomain restricted to to ``sm``.
|
| 180 |
+
|
| 181 |
+
Here ``sm`` has to be a submodule of ``self.codomain`` containing the
|
| 182 |
+
image.
|
| 183 |
+
|
| 184 |
+
Examples
|
| 185 |
+
========
|
| 186 |
+
|
| 187 |
+
>>> from sympy import QQ
|
| 188 |
+
>>> from sympy.abc import x
|
| 189 |
+
>>> from sympy.polys.agca import homomorphism
|
| 190 |
+
|
| 191 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 192 |
+
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
|
| 193 |
+
>>> h
|
| 194 |
+
Matrix([
|
| 195 |
+
[1, x], : QQ[x]**2 -> QQ[x]**2
|
| 196 |
+
[0, 0]])
|
| 197 |
+
>>> h.restrict_codomain(F.submodule([1, 0]))
|
| 198 |
+
Matrix([
|
| 199 |
+
[1, x], : QQ[x]**2 -> <[1, 0]>
|
| 200 |
+
[0, 0]])
|
| 201 |
+
"""
|
| 202 |
+
if not sm.is_submodule(self.image()):
|
| 203 |
+
raise ValueError('the image %s must contain sm, got %s'
|
| 204 |
+
% (self.image(), sm))
|
| 205 |
+
if sm == self.codomain:
|
| 206 |
+
return self
|
| 207 |
+
return self._restrict_codomain(sm)
|
| 208 |
+
|
| 209 |
+
def quotient_domain(self, sm):
|
| 210 |
+
"""
|
| 211 |
+
Return ``self`` with domain replaced by ``domain/sm``.
|
| 212 |
+
|
| 213 |
+
Here ``sm`` must be a submodule of ``self.kernel()``.
|
| 214 |
+
|
| 215 |
+
Examples
|
| 216 |
+
========
|
| 217 |
+
|
| 218 |
+
>>> from sympy import QQ
|
| 219 |
+
>>> from sympy.abc import x
|
| 220 |
+
>>> from sympy.polys.agca import homomorphism
|
| 221 |
+
|
| 222 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 223 |
+
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
|
| 224 |
+
>>> h
|
| 225 |
+
Matrix([
|
| 226 |
+
[1, x], : QQ[x]**2 -> QQ[x]**2
|
| 227 |
+
[0, 0]])
|
| 228 |
+
>>> h.quotient_domain(F.submodule([-x, 1]))
|
| 229 |
+
Matrix([
|
| 230 |
+
[1, x], : QQ[x]**2/<[-x, 1]> -> QQ[x]**2
|
| 231 |
+
[0, 0]])
|
| 232 |
+
"""
|
| 233 |
+
if not self.kernel().is_submodule(sm):
|
| 234 |
+
raise ValueError('kernel %s must contain sm, got %s' %
|
| 235 |
+
(self.kernel(), sm))
|
| 236 |
+
if sm.is_zero():
|
| 237 |
+
return self
|
| 238 |
+
return self._quotient_domain(sm)
|
| 239 |
+
|
| 240 |
+
def quotient_codomain(self, sm):
|
| 241 |
+
"""
|
| 242 |
+
Return ``self`` with codomain replaced by ``codomain/sm``.
|
| 243 |
+
|
| 244 |
+
Here ``sm`` must be a submodule of ``self.codomain``.
|
| 245 |
+
|
| 246 |
+
Examples
|
| 247 |
+
========
|
| 248 |
+
|
| 249 |
+
>>> from sympy import QQ
|
| 250 |
+
>>> from sympy.abc import x
|
| 251 |
+
>>> from sympy.polys.agca import homomorphism
|
| 252 |
+
|
| 253 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 254 |
+
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
|
| 255 |
+
>>> h
|
| 256 |
+
Matrix([
|
| 257 |
+
[1, x], : QQ[x]**2 -> QQ[x]**2
|
| 258 |
+
[0, 0]])
|
| 259 |
+
>>> h.quotient_codomain(F.submodule([1, 1]))
|
| 260 |
+
Matrix([
|
| 261 |
+
[1, x], : QQ[x]**2 -> QQ[x]**2/<[1, 1]>
|
| 262 |
+
[0, 0]])
|
| 263 |
+
|
| 264 |
+
This is the same as composing with the quotient map on the left:
|
| 265 |
+
|
| 266 |
+
>>> (F/[(1, 1)]).quotient_hom() * h
|
| 267 |
+
Matrix([
|
| 268 |
+
[1, x], : QQ[x]**2 -> QQ[x]**2/<[1, 1]>
|
| 269 |
+
[0, 0]])
|
| 270 |
+
"""
|
| 271 |
+
if not self.codomain.is_submodule(sm):
|
| 272 |
+
raise ValueError('sm must be a submodule of codomain %s, got %s'
|
| 273 |
+
% (self.codomain, sm))
|
| 274 |
+
if sm.is_zero():
|
| 275 |
+
return self
|
| 276 |
+
return self._quotient_codomain(sm)
|
| 277 |
+
|
| 278 |
+
def _apply(self, elem):
|
| 279 |
+
"""Apply ``self`` to ``elem``."""
|
| 280 |
+
raise NotImplementedError
|
| 281 |
+
|
| 282 |
+
def __call__(self, elem):
|
| 283 |
+
return self.codomain.convert(self._apply(self.domain.convert(elem)))
|
| 284 |
+
|
| 285 |
+
def _compose(self, oth):
|
| 286 |
+
"""
|
| 287 |
+
Compose ``self`` with ``oth``, that is, return the homomorphism
|
| 288 |
+
obtained by first applying then ``self``, then ``oth``.
|
| 289 |
+
|
| 290 |
+
(This method is private since in this syntax, it is non-obvious which
|
| 291 |
+
homomorphism is executed first.)
|
| 292 |
+
"""
|
| 293 |
+
raise NotImplementedError
|
| 294 |
+
|
| 295 |
+
def _mul_scalar(self, c):
|
| 296 |
+
"""Scalar multiplication. ``c`` is guaranteed in self.ring."""
|
| 297 |
+
raise NotImplementedError
|
| 298 |
+
|
| 299 |
+
def _add(self, oth):
|
| 300 |
+
"""
|
| 301 |
+
Homomorphism addition.
|
| 302 |
+
``oth`` is guaranteed to be a homomorphism with same domain/codomain.
|
| 303 |
+
"""
|
| 304 |
+
raise NotImplementedError
|
| 305 |
+
|
| 306 |
+
def _check_hom(self, oth):
|
| 307 |
+
"""Helper to check that oth is a homomorphism with same domain/codomain."""
|
| 308 |
+
if not isinstance(oth, ModuleHomomorphism):
|
| 309 |
+
return False
|
| 310 |
+
return oth.domain == self.domain and oth.codomain == self.codomain
|
| 311 |
+
|
| 312 |
+
def __mul__(self, oth):
|
| 313 |
+
if isinstance(oth, ModuleHomomorphism) and self.domain == oth.codomain:
|
| 314 |
+
return oth._compose(self)
|
| 315 |
+
try:
|
| 316 |
+
return self._mul_scalar(self.ring.convert(oth))
|
| 317 |
+
except CoercionFailed:
|
| 318 |
+
return NotImplemented
|
| 319 |
+
|
| 320 |
+
# NOTE: _compose will never be called from rmul
|
| 321 |
+
__rmul__ = __mul__
|
| 322 |
+
|
| 323 |
+
def __truediv__(self, oth):
|
| 324 |
+
try:
|
| 325 |
+
return self._mul_scalar(1/self.ring.convert(oth))
|
| 326 |
+
except CoercionFailed:
|
| 327 |
+
return NotImplemented
|
| 328 |
+
|
| 329 |
+
def __add__(self, oth):
|
| 330 |
+
if self._check_hom(oth):
|
| 331 |
+
return self._add(oth)
|
| 332 |
+
return NotImplemented
|
| 333 |
+
|
| 334 |
+
def __sub__(self, oth):
|
| 335 |
+
if self._check_hom(oth):
|
| 336 |
+
return self._add(oth._mul_scalar(self.ring.convert(-1)))
|
| 337 |
+
return NotImplemented
|
| 338 |
+
|
| 339 |
+
def is_injective(self):
|
| 340 |
+
"""
|
| 341 |
+
Return True if ``self`` is injective.
|
| 342 |
+
|
| 343 |
+
That is, check if the elements of the domain are mapped to the same
|
| 344 |
+
codomain element.
|
| 345 |
+
|
| 346 |
+
Examples
|
| 347 |
+
========
|
| 348 |
+
|
| 349 |
+
>>> from sympy import QQ
|
| 350 |
+
>>> from sympy.abc import x
|
| 351 |
+
>>> from sympy.polys.agca import homomorphism
|
| 352 |
+
|
| 353 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 354 |
+
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
|
| 355 |
+
>>> h.is_injective()
|
| 356 |
+
False
|
| 357 |
+
>>> h.quotient_domain(h.kernel()).is_injective()
|
| 358 |
+
True
|
| 359 |
+
"""
|
| 360 |
+
return self.kernel().is_zero()
|
| 361 |
+
|
| 362 |
+
def is_surjective(self):
|
| 363 |
+
"""
|
| 364 |
+
Return True if ``self`` is surjective.
|
| 365 |
+
|
| 366 |
+
That is, check if every element of the codomain has at least one
|
| 367 |
+
preimage.
|
| 368 |
+
|
| 369 |
+
Examples
|
| 370 |
+
========
|
| 371 |
+
|
| 372 |
+
>>> from sympy import QQ
|
| 373 |
+
>>> from sympy.abc import x
|
| 374 |
+
>>> from sympy.polys.agca import homomorphism
|
| 375 |
+
|
| 376 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 377 |
+
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
|
| 378 |
+
>>> h.is_surjective()
|
| 379 |
+
False
|
| 380 |
+
>>> h.restrict_codomain(h.image()).is_surjective()
|
| 381 |
+
True
|
| 382 |
+
"""
|
| 383 |
+
return self.image() == self.codomain
|
| 384 |
+
|
| 385 |
+
def is_isomorphism(self):
|
| 386 |
+
"""
|
| 387 |
+
Return True if ``self`` is an isomorphism.
|
| 388 |
+
|
| 389 |
+
That is, check if every element of the codomain has precisely one
|
| 390 |
+
preimage. Equivalently, ``self`` is both injective and surjective.
|
| 391 |
+
|
| 392 |
+
Examples
|
| 393 |
+
========
|
| 394 |
+
|
| 395 |
+
>>> from sympy import QQ
|
| 396 |
+
>>> from sympy.abc import x
|
| 397 |
+
>>> from sympy.polys.agca import homomorphism
|
| 398 |
+
|
| 399 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 400 |
+
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
|
| 401 |
+
>>> h = h.restrict_codomain(h.image())
|
| 402 |
+
>>> h.is_isomorphism()
|
| 403 |
+
False
|
| 404 |
+
>>> h.quotient_domain(h.kernel()).is_isomorphism()
|
| 405 |
+
True
|
| 406 |
+
"""
|
| 407 |
+
return self.is_injective() and self.is_surjective()
|
| 408 |
+
|
| 409 |
+
def is_zero(self):
|
| 410 |
+
"""
|
| 411 |
+
Return True if ``self`` is a zero morphism.
|
| 412 |
+
|
| 413 |
+
That is, check if every element of the domain is mapped to zero
|
| 414 |
+
under self.
|
| 415 |
+
|
| 416 |
+
Examples
|
| 417 |
+
========
|
| 418 |
+
|
| 419 |
+
>>> from sympy import QQ
|
| 420 |
+
>>> from sympy.abc import x
|
| 421 |
+
>>> from sympy.polys.agca import homomorphism
|
| 422 |
+
|
| 423 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 424 |
+
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
|
| 425 |
+
>>> h.is_zero()
|
| 426 |
+
False
|
| 427 |
+
>>> h.restrict_domain(F.submodule()).is_zero()
|
| 428 |
+
True
|
| 429 |
+
>>> h.quotient_codomain(h.image()).is_zero()
|
| 430 |
+
True
|
| 431 |
+
"""
|
| 432 |
+
return self.image().is_zero()
|
| 433 |
+
|
| 434 |
+
def __eq__(self, oth):
|
| 435 |
+
try:
|
| 436 |
+
return (self - oth).is_zero()
|
| 437 |
+
except TypeError:
|
| 438 |
+
return False
|
| 439 |
+
|
| 440 |
+
def __ne__(self, oth):
|
| 441 |
+
return not (self == oth)
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
class MatrixHomomorphism(ModuleHomomorphism):
|
| 445 |
+
r"""
|
| 446 |
+
Helper class for all homomoprhisms which are expressed via a matrix.
|
| 447 |
+
|
| 448 |
+
That is, for such homomorphisms ``domain`` is contained in a module
|
| 449 |
+
generated by finitely many elements `e_1, \ldots, e_n`, so that the
|
| 450 |
+
homomorphism is determined uniquely by its action on the `e_i`. It
|
| 451 |
+
can thus be represented as a vector of elements of the codomain module,
|
| 452 |
+
or potentially a supermodule of the codomain module
|
| 453 |
+
(and hence conventionally as a matrix, if there is a similar interpretation
|
| 454 |
+
for elements of the codomain module).
|
| 455 |
+
|
| 456 |
+
Note that this class does *not* assume that the `e_i` freely generate a
|
| 457 |
+
submodule, nor that ``domain`` is even all of this submodule. It exists
|
| 458 |
+
only to unify the interface.
|
| 459 |
+
|
| 460 |
+
Do not instantiate.
|
| 461 |
+
|
| 462 |
+
Attributes:
|
| 463 |
+
|
| 464 |
+
- matrix - the list of images determining the homomorphism.
|
| 465 |
+
NOTE: the elements of matrix belong to either self.codomain or
|
| 466 |
+
self.codomain.container
|
| 467 |
+
|
| 468 |
+
Still non-implemented methods:
|
| 469 |
+
|
| 470 |
+
- kernel
|
| 471 |
+
- _apply
|
| 472 |
+
"""
|
| 473 |
+
|
| 474 |
+
def __init__(self, domain, codomain, matrix):
|
| 475 |
+
ModuleHomomorphism.__init__(self, domain, codomain)
|
| 476 |
+
if len(matrix) != domain.rank:
|
| 477 |
+
raise ValueError('Need to provide %s elements, got %s'
|
| 478 |
+
% (domain.rank, len(matrix)))
|
| 479 |
+
|
| 480 |
+
converter = self.codomain.convert
|
| 481 |
+
if isinstance(self.codomain, (SubModule, SubQuotientModule)):
|
| 482 |
+
converter = self.codomain.container.convert
|
| 483 |
+
self.matrix = tuple(converter(x) for x in matrix)
|
| 484 |
+
|
| 485 |
+
def _sympy_matrix(self):
|
| 486 |
+
"""Helper function which returns a SymPy matrix ``self.matrix``."""
|
| 487 |
+
from sympy.matrices import Matrix
|
| 488 |
+
c = lambda x: x
|
| 489 |
+
if isinstance(self.codomain, (QuotientModule, SubQuotientModule)):
|
| 490 |
+
c = lambda x: x.data
|
| 491 |
+
return Matrix([[self.ring.to_sympy(y) for y in c(x)] for x in self.matrix]).T
|
| 492 |
+
|
| 493 |
+
def __repr__(self):
|
| 494 |
+
lines = repr(self._sympy_matrix()).split('\n')
|
| 495 |
+
t = " : %s -> %s" % (self.domain, self.codomain)
|
| 496 |
+
s = ' '*len(t)
|
| 497 |
+
n = len(lines)
|
| 498 |
+
for i in range(n // 2):
|
| 499 |
+
lines[i] += s
|
| 500 |
+
lines[n // 2] += t
|
| 501 |
+
for i in range(n//2 + 1, n):
|
| 502 |
+
lines[i] += s
|
| 503 |
+
return '\n'.join(lines)
|
| 504 |
+
|
| 505 |
+
def _restrict_domain(self, sm):
|
| 506 |
+
"""Implementation of domain restriction."""
|
| 507 |
+
return SubModuleHomomorphism(sm, self.codomain, self.matrix)
|
| 508 |
+
|
| 509 |
+
def _restrict_codomain(self, sm):
|
| 510 |
+
"""Implementation of codomain restriction."""
|
| 511 |
+
return self.__class__(self.domain, sm, self.matrix)
|
| 512 |
+
|
| 513 |
+
def _quotient_domain(self, sm):
|
| 514 |
+
"""Implementation of domain quotient."""
|
| 515 |
+
return self.__class__(self.domain/sm, self.codomain, self.matrix)
|
| 516 |
+
|
| 517 |
+
def _quotient_codomain(self, sm):
|
| 518 |
+
"""Implementation of codomain quotient."""
|
| 519 |
+
Q = self.codomain/sm
|
| 520 |
+
converter = Q.convert
|
| 521 |
+
if isinstance(self.codomain, SubModule):
|
| 522 |
+
converter = Q.container.convert
|
| 523 |
+
return self.__class__(self.domain, self.codomain/sm,
|
| 524 |
+
[converter(x) for x in self.matrix])
|
| 525 |
+
|
| 526 |
+
def _add(self, oth):
|
| 527 |
+
return self.__class__(self.domain, self.codomain,
|
| 528 |
+
[x + y for x, y in zip(self.matrix, oth.matrix)])
|
| 529 |
+
|
| 530 |
+
def _mul_scalar(self, c):
|
| 531 |
+
return self.__class__(self.domain, self.codomain, [c*x for x in self.matrix])
|
| 532 |
+
|
| 533 |
+
def _compose(self, oth):
|
| 534 |
+
return self.__class__(self.domain, oth.codomain, [oth(x) for x in self.matrix])
|
| 535 |
+
|
| 536 |
+
|
| 537 |
+
class FreeModuleHomomorphism(MatrixHomomorphism):
|
| 538 |
+
"""
|
| 539 |
+
Concrete class for homomorphisms with domain a free module or a quotient
|
| 540 |
+
thereof.
|
| 541 |
+
|
| 542 |
+
Do not instantiate; the constructor does not check that your data is well
|
| 543 |
+
defined. Use the ``homomorphism`` function instead:
|
| 544 |
+
|
| 545 |
+
>>> from sympy import QQ
|
| 546 |
+
>>> from sympy.abc import x
|
| 547 |
+
>>> from sympy.polys.agca import homomorphism
|
| 548 |
+
|
| 549 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 550 |
+
>>> homomorphism(F, F, [[1, 0], [0, 1]])
|
| 551 |
+
Matrix([
|
| 552 |
+
[1, 0], : QQ[x]**2 -> QQ[x]**2
|
| 553 |
+
[0, 1]])
|
| 554 |
+
"""
|
| 555 |
+
|
| 556 |
+
def _apply(self, elem):
|
| 557 |
+
if isinstance(self.domain, QuotientModule):
|
| 558 |
+
elem = elem.data
|
| 559 |
+
return sum(x * e for x, e in zip(elem, self.matrix))
|
| 560 |
+
|
| 561 |
+
def _image(self):
|
| 562 |
+
return self.codomain.submodule(*self.matrix)
|
| 563 |
+
|
| 564 |
+
def _kernel(self):
|
| 565 |
+
# The domain is either a free module or a quotient thereof.
|
| 566 |
+
# It does not matter if it is a quotient, because that won't increase
|
| 567 |
+
# the kernel.
|
| 568 |
+
# Our generators {e_i} are sent to the matrix entries {b_i}.
|
| 569 |
+
# The kernel is essentially the syzygy module of these {b_i}.
|
| 570 |
+
syz = self.image().syzygy_module()
|
| 571 |
+
return self.domain.submodule(*syz.gens)
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
class SubModuleHomomorphism(MatrixHomomorphism):
|
| 575 |
+
"""
|
| 576 |
+
Concrete class for homomorphism with domain a submodule of a free module
|
| 577 |
+
or a quotient thereof.
|
| 578 |
+
|
| 579 |
+
Do not instantiate; the constructor does not check that your data is well
|
| 580 |
+
defined. Use the ``homomorphism`` function instead:
|
| 581 |
+
|
| 582 |
+
>>> from sympy import QQ
|
| 583 |
+
>>> from sympy.abc import x
|
| 584 |
+
>>> from sympy.polys.agca import homomorphism
|
| 585 |
+
|
| 586 |
+
>>> M = QQ.old_poly_ring(x).free_module(2)*x
|
| 587 |
+
>>> homomorphism(M, M, [[1, 0], [0, 1]])
|
| 588 |
+
Matrix([
|
| 589 |
+
[1, 0], : <[x, 0], [0, x]> -> <[x, 0], [0, x]>
|
| 590 |
+
[0, 1]])
|
| 591 |
+
"""
|
| 592 |
+
|
| 593 |
+
def _apply(self, elem):
|
| 594 |
+
if isinstance(self.domain, SubQuotientModule):
|
| 595 |
+
elem = elem.data
|
| 596 |
+
return sum(x * e for x, e in zip(elem, self.matrix))
|
| 597 |
+
|
| 598 |
+
def _image(self):
|
| 599 |
+
return self.codomain.submodule(*[self(x) for x in self.domain.gens])
|
| 600 |
+
|
| 601 |
+
def _kernel(self):
|
| 602 |
+
syz = self.image().syzygy_module()
|
| 603 |
+
return self.domain.submodule(
|
| 604 |
+
*[sum(xi*gi for xi, gi in zip(s, self.domain.gens))
|
| 605 |
+
for s in syz.gens])
|
| 606 |
+
|
| 607 |
+
|
| 608 |
+
def homomorphism(domain, codomain, matrix):
|
| 609 |
+
r"""
|
| 610 |
+
Create a homomorphism object.
|
| 611 |
+
|
| 612 |
+
This function tries to build a homomorphism from ``domain`` to ``codomain``
|
| 613 |
+
via the matrix ``matrix``.
|
| 614 |
+
|
| 615 |
+
Examples
|
| 616 |
+
========
|
| 617 |
+
|
| 618 |
+
>>> from sympy import QQ
|
| 619 |
+
>>> from sympy.abc import x
|
| 620 |
+
>>> from sympy.polys.agca import homomorphism
|
| 621 |
+
|
| 622 |
+
>>> R = QQ.old_poly_ring(x)
|
| 623 |
+
>>> T = R.free_module(2)
|
| 624 |
+
|
| 625 |
+
If ``domain`` is a free module generated by `e_1, \ldots, e_n`, then
|
| 626 |
+
``matrix`` should be an n-element iterable `(b_1, \ldots, b_n)` where
|
| 627 |
+
the `b_i` are elements of ``codomain``. The constructed homomorphism is the
|
| 628 |
+
unique homomorphism sending `e_i` to `b_i`.
|
| 629 |
+
|
| 630 |
+
>>> F = R.free_module(2)
|
| 631 |
+
>>> h = homomorphism(F, T, [[1, x], [x**2, 0]])
|
| 632 |
+
>>> h
|
| 633 |
+
Matrix([
|
| 634 |
+
[1, x**2], : QQ[x]**2 -> QQ[x]**2
|
| 635 |
+
[x, 0]])
|
| 636 |
+
>>> h([1, 0])
|
| 637 |
+
[1, x]
|
| 638 |
+
>>> h([0, 1])
|
| 639 |
+
[x**2, 0]
|
| 640 |
+
>>> h([1, 1])
|
| 641 |
+
[x**2 + 1, x]
|
| 642 |
+
|
| 643 |
+
If ``domain`` is a submodule of a free module, them ``matrix`` determines
|
| 644 |
+
a homomoprhism from the containing free module to ``codomain``, and the
|
| 645 |
+
homomorphism returned is obtained by restriction to ``domain``.
|
| 646 |
+
|
| 647 |
+
>>> S = F.submodule([1, 0], [0, x])
|
| 648 |
+
>>> homomorphism(S, T, [[1, x], [x**2, 0]])
|
| 649 |
+
Matrix([
|
| 650 |
+
[1, x**2], : <[1, 0], [0, x]> -> QQ[x]**2
|
| 651 |
+
[x, 0]])
|
| 652 |
+
|
| 653 |
+
If ``domain`` is a (sub)quotient `N/K`, then ``matrix`` determines a
|
| 654 |
+
homomorphism from `N` to ``codomain``. If the kernel contains `K`, this
|
| 655 |
+
homomorphism descends to ``domain`` and is returned; otherwise an exception
|
| 656 |
+
is raised.
|
| 657 |
+
|
| 658 |
+
>>> homomorphism(S/[(1, 0)], T, [0, [x**2, 0]])
|
| 659 |
+
Matrix([
|
| 660 |
+
[0, x**2], : <[1, 0] + <[1, 0]>, [0, x] + <[1, 0]>, [1, 0] + <[1, 0]>> -> QQ[x]**2
|
| 661 |
+
[0, 0]])
|
| 662 |
+
>>> homomorphism(S/[(0, x)], T, [0, [x**2, 0]])
|
| 663 |
+
Traceback (most recent call last):
|
| 664 |
+
...
|
| 665 |
+
ValueError: kernel <[1, 0], [0, 0]> must contain sm, got <[0,x]>
|
| 666 |
+
|
| 667 |
+
"""
|
| 668 |
+
def freepres(module):
|
| 669 |
+
"""
|
| 670 |
+
Return a tuple ``(F, S, Q, c)`` where ``F`` is a free module, ``S`` is a
|
| 671 |
+
submodule of ``F``, and ``Q`` a submodule of ``S``, such that
|
| 672 |
+
``module = S/Q``, and ``c`` is a conversion function.
|
| 673 |
+
"""
|
| 674 |
+
if isinstance(module, FreeModule):
|
| 675 |
+
return module, module, module.submodule(), lambda x: module.convert(x)
|
| 676 |
+
if isinstance(module, QuotientModule):
|
| 677 |
+
return (module.base, module.base, module.killed_module,
|
| 678 |
+
lambda x: module.convert(x).data)
|
| 679 |
+
if isinstance(module, SubQuotientModule):
|
| 680 |
+
return (module.base.container, module.base, module.killed_module,
|
| 681 |
+
lambda x: module.container.convert(x).data)
|
| 682 |
+
# an ordinary submodule
|
| 683 |
+
return (module.container, module, module.submodule(),
|
| 684 |
+
lambda x: module.container.convert(x))
|
| 685 |
+
|
| 686 |
+
SF, SS, SQ, _ = freepres(domain)
|
| 687 |
+
TF, TS, TQ, c = freepres(codomain)
|
| 688 |
+
# NOTE this is probably a bit inefficient (redundant checks)
|
| 689 |
+
return FreeModuleHomomorphism(SF, TF, [c(x) for x in matrix]
|
| 690 |
+
).restrict_domain(SS).restrict_codomain(TS
|
| 691 |
+
).quotient_codomain(TQ).quotient_domain(SQ)
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/ideals.py
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Computations with ideals of polynomial rings."""
|
| 2 |
+
|
| 3 |
+
from sympy.polys.polyerrors import CoercionFailed
|
| 4 |
+
from sympy.polys.polyutils import IntegerPowerable
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class Ideal(IntegerPowerable):
|
| 8 |
+
"""
|
| 9 |
+
Abstract base class for ideals.
|
| 10 |
+
|
| 11 |
+
Do not instantiate - use explicit constructors in the ring class instead:
|
| 12 |
+
|
| 13 |
+
>>> from sympy import QQ
|
| 14 |
+
>>> from sympy.abc import x
|
| 15 |
+
>>> QQ.old_poly_ring(x).ideal(x+1)
|
| 16 |
+
<x + 1>
|
| 17 |
+
|
| 18 |
+
Attributes
|
| 19 |
+
|
| 20 |
+
- ring - the ring this ideal belongs to
|
| 21 |
+
|
| 22 |
+
Non-implemented methods:
|
| 23 |
+
|
| 24 |
+
- _contains_elem
|
| 25 |
+
- _contains_ideal
|
| 26 |
+
- _quotient
|
| 27 |
+
- _intersect
|
| 28 |
+
- _union
|
| 29 |
+
- _product
|
| 30 |
+
- is_whole_ring
|
| 31 |
+
- is_zero
|
| 32 |
+
- is_prime, is_maximal, is_primary, is_radical
|
| 33 |
+
- is_principal
|
| 34 |
+
- height, depth
|
| 35 |
+
- radical
|
| 36 |
+
|
| 37 |
+
Methods that likely should be overridden in subclasses:
|
| 38 |
+
|
| 39 |
+
- reduce_element
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def _contains_elem(self, x):
|
| 43 |
+
"""Implementation of element containment."""
|
| 44 |
+
raise NotImplementedError
|
| 45 |
+
|
| 46 |
+
def _contains_ideal(self, I):
|
| 47 |
+
"""Implementation of ideal containment."""
|
| 48 |
+
raise NotImplementedError
|
| 49 |
+
|
| 50 |
+
def _quotient(self, J):
|
| 51 |
+
"""Implementation of ideal quotient."""
|
| 52 |
+
raise NotImplementedError
|
| 53 |
+
|
| 54 |
+
def _intersect(self, J):
|
| 55 |
+
"""Implementation of ideal intersection."""
|
| 56 |
+
raise NotImplementedError
|
| 57 |
+
|
| 58 |
+
def is_whole_ring(self):
|
| 59 |
+
"""Return True if ``self`` is the whole ring."""
|
| 60 |
+
raise NotImplementedError
|
| 61 |
+
|
| 62 |
+
def is_zero(self):
|
| 63 |
+
"""Return True if ``self`` is the zero ideal."""
|
| 64 |
+
raise NotImplementedError
|
| 65 |
+
|
| 66 |
+
def _equals(self, J):
|
| 67 |
+
"""Implementation of ideal equality."""
|
| 68 |
+
return self._contains_ideal(J) and J._contains_ideal(self)
|
| 69 |
+
|
| 70 |
+
def is_prime(self):
|
| 71 |
+
"""Return True if ``self`` is a prime ideal."""
|
| 72 |
+
raise NotImplementedError
|
| 73 |
+
|
| 74 |
+
def is_maximal(self):
|
| 75 |
+
"""Return True if ``self`` is a maximal ideal."""
|
| 76 |
+
raise NotImplementedError
|
| 77 |
+
|
| 78 |
+
def is_radical(self):
|
| 79 |
+
"""Return True if ``self`` is a radical ideal."""
|
| 80 |
+
raise NotImplementedError
|
| 81 |
+
|
| 82 |
+
def is_primary(self):
|
| 83 |
+
"""Return True if ``self`` is a primary ideal."""
|
| 84 |
+
raise NotImplementedError
|
| 85 |
+
|
| 86 |
+
def is_principal(self):
|
| 87 |
+
"""Return True if ``self`` is a principal ideal."""
|
| 88 |
+
raise NotImplementedError
|
| 89 |
+
|
| 90 |
+
def radical(self):
|
| 91 |
+
"""Compute the radical of ``self``."""
|
| 92 |
+
raise NotImplementedError
|
| 93 |
+
|
| 94 |
+
def depth(self):
|
| 95 |
+
"""Compute the depth of ``self``."""
|
| 96 |
+
raise NotImplementedError
|
| 97 |
+
|
| 98 |
+
def height(self):
|
| 99 |
+
"""Compute the height of ``self``."""
|
| 100 |
+
raise NotImplementedError
|
| 101 |
+
|
| 102 |
+
# TODO more
|
| 103 |
+
|
| 104 |
+
# non-implemented methods end here
|
| 105 |
+
|
| 106 |
+
def __init__(self, ring):
|
| 107 |
+
self.ring = ring
|
| 108 |
+
|
| 109 |
+
def _check_ideal(self, J):
|
| 110 |
+
"""Helper to check ``J`` is an ideal of our ring."""
|
| 111 |
+
if not isinstance(J, Ideal) or J.ring != self.ring:
|
| 112 |
+
raise ValueError(
|
| 113 |
+
'J must be an ideal of %s, got %s' % (self.ring, J))
|
| 114 |
+
|
| 115 |
+
def contains(self, elem):
|
| 116 |
+
"""
|
| 117 |
+
Return True if ``elem`` is an element of this ideal.
|
| 118 |
+
|
| 119 |
+
Examples
|
| 120 |
+
========
|
| 121 |
+
|
| 122 |
+
>>> from sympy.abc import x
|
| 123 |
+
>>> from sympy import QQ
|
| 124 |
+
>>> QQ.old_poly_ring(x).ideal(x+1, x-1).contains(3)
|
| 125 |
+
True
|
| 126 |
+
>>> QQ.old_poly_ring(x).ideal(x**2, x**3).contains(x)
|
| 127 |
+
False
|
| 128 |
+
"""
|
| 129 |
+
return self._contains_elem(self.ring.convert(elem))
|
| 130 |
+
|
| 131 |
+
def subset(self, other):
|
| 132 |
+
"""
|
| 133 |
+
Returns True if ``other`` is is a subset of ``self``.
|
| 134 |
+
|
| 135 |
+
Here ``other`` may be an ideal.
|
| 136 |
+
|
| 137 |
+
Examples
|
| 138 |
+
========
|
| 139 |
+
|
| 140 |
+
>>> from sympy.abc import x
|
| 141 |
+
>>> from sympy import QQ
|
| 142 |
+
>>> I = QQ.old_poly_ring(x).ideal(x+1)
|
| 143 |
+
>>> I.subset([x**2 - 1, x**2 + 2*x + 1])
|
| 144 |
+
True
|
| 145 |
+
>>> I.subset([x**2 + 1, x + 1])
|
| 146 |
+
False
|
| 147 |
+
>>> I.subset(QQ.old_poly_ring(x).ideal(x**2 - 1))
|
| 148 |
+
True
|
| 149 |
+
"""
|
| 150 |
+
if isinstance(other, Ideal):
|
| 151 |
+
return self._contains_ideal(other)
|
| 152 |
+
return all(self._contains_elem(x) for x in other)
|
| 153 |
+
|
| 154 |
+
def quotient(self, J, **opts):
|
| 155 |
+
r"""
|
| 156 |
+
Compute the ideal quotient of ``self`` by ``J``.
|
| 157 |
+
|
| 158 |
+
That is, if ``self`` is the ideal `I`, compute the set
|
| 159 |
+
`I : J = \{x \in R | xJ \subset I \}`.
|
| 160 |
+
|
| 161 |
+
Examples
|
| 162 |
+
========
|
| 163 |
+
|
| 164 |
+
>>> from sympy.abc import x, y
|
| 165 |
+
>>> from sympy import QQ
|
| 166 |
+
>>> R = QQ.old_poly_ring(x, y)
|
| 167 |
+
>>> R.ideal(x*y).quotient(R.ideal(x))
|
| 168 |
+
<y>
|
| 169 |
+
"""
|
| 170 |
+
self._check_ideal(J)
|
| 171 |
+
return self._quotient(J, **opts)
|
| 172 |
+
|
| 173 |
+
def intersect(self, J):
|
| 174 |
+
"""
|
| 175 |
+
Compute the intersection of self with ideal J.
|
| 176 |
+
|
| 177 |
+
Examples
|
| 178 |
+
========
|
| 179 |
+
|
| 180 |
+
>>> from sympy.abc import x, y
|
| 181 |
+
>>> from sympy import QQ
|
| 182 |
+
>>> R = QQ.old_poly_ring(x, y)
|
| 183 |
+
>>> R.ideal(x).intersect(R.ideal(y))
|
| 184 |
+
<x*y>
|
| 185 |
+
"""
|
| 186 |
+
self._check_ideal(J)
|
| 187 |
+
return self._intersect(J)
|
| 188 |
+
|
| 189 |
+
def saturate(self, J):
|
| 190 |
+
r"""
|
| 191 |
+
Compute the ideal saturation of ``self`` by ``J``.
|
| 192 |
+
|
| 193 |
+
That is, if ``self`` is the ideal `I`, compute the set
|
| 194 |
+
`I : J^\infty = \{x \in R | xJ^n \subset I \text{ for some } n\}`.
|
| 195 |
+
"""
|
| 196 |
+
raise NotImplementedError
|
| 197 |
+
# Note this can be implemented using repeated quotient
|
| 198 |
+
|
| 199 |
+
def union(self, J):
|
| 200 |
+
"""
|
| 201 |
+
Compute the ideal generated by the union of ``self`` and ``J``.
|
| 202 |
+
|
| 203 |
+
Examples
|
| 204 |
+
========
|
| 205 |
+
|
| 206 |
+
>>> from sympy.abc import x
|
| 207 |
+
>>> from sympy import QQ
|
| 208 |
+
>>> QQ.old_poly_ring(x).ideal(x**2 - 1).union(QQ.old_poly_ring(x).ideal((x+1)**2)) == QQ.old_poly_ring(x).ideal(x+1)
|
| 209 |
+
True
|
| 210 |
+
"""
|
| 211 |
+
self._check_ideal(J)
|
| 212 |
+
return self._union(J)
|
| 213 |
+
|
| 214 |
+
def product(self, J):
|
| 215 |
+
r"""
|
| 216 |
+
Compute the ideal product of ``self`` and ``J``.
|
| 217 |
+
|
| 218 |
+
That is, compute the ideal generated by products `xy`, for `x` an element
|
| 219 |
+
of ``self`` and `y \in J`.
|
| 220 |
+
|
| 221 |
+
Examples
|
| 222 |
+
========
|
| 223 |
+
|
| 224 |
+
>>> from sympy.abc import x, y
|
| 225 |
+
>>> from sympy import QQ
|
| 226 |
+
>>> QQ.old_poly_ring(x, y).ideal(x).product(QQ.old_poly_ring(x, y).ideal(y))
|
| 227 |
+
<x*y>
|
| 228 |
+
"""
|
| 229 |
+
self._check_ideal(J)
|
| 230 |
+
return self._product(J)
|
| 231 |
+
|
| 232 |
+
def reduce_element(self, x):
|
| 233 |
+
"""
|
| 234 |
+
Reduce the element ``x`` of our ring modulo the ideal ``self``.
|
| 235 |
+
|
| 236 |
+
Here "reduce" has no specific meaning: it could return a unique normal
|
| 237 |
+
form, simplify the expression a bit, or just do nothing.
|
| 238 |
+
"""
|
| 239 |
+
return x
|
| 240 |
+
|
| 241 |
+
def __add__(self, e):
|
| 242 |
+
if not isinstance(e, Ideal):
|
| 243 |
+
R = self.ring.quotient_ring(self)
|
| 244 |
+
if isinstance(e, R.dtype):
|
| 245 |
+
return e
|
| 246 |
+
if isinstance(e, R.ring.dtype):
|
| 247 |
+
return R(e)
|
| 248 |
+
return R.convert(e)
|
| 249 |
+
self._check_ideal(e)
|
| 250 |
+
return self.union(e)
|
| 251 |
+
|
| 252 |
+
__radd__ = __add__
|
| 253 |
+
|
| 254 |
+
def __mul__(self, e):
|
| 255 |
+
if not isinstance(e, Ideal):
|
| 256 |
+
try:
|
| 257 |
+
e = self.ring.ideal(e)
|
| 258 |
+
except CoercionFailed:
|
| 259 |
+
return NotImplemented
|
| 260 |
+
self._check_ideal(e)
|
| 261 |
+
return self.product(e)
|
| 262 |
+
|
| 263 |
+
__rmul__ = __mul__
|
| 264 |
+
|
| 265 |
+
def _zeroth_power(self):
|
| 266 |
+
return self.ring.ideal(1)
|
| 267 |
+
|
| 268 |
+
def _first_power(self):
|
| 269 |
+
# Raising to any power but 1 returns a new instance. So we mult by 1
|
| 270 |
+
# here so that the first power is no exception.
|
| 271 |
+
return self * 1
|
| 272 |
+
|
| 273 |
+
def __eq__(self, e):
|
| 274 |
+
if not isinstance(e, Ideal) or e.ring != self.ring:
|
| 275 |
+
return False
|
| 276 |
+
return self._equals(e)
|
| 277 |
+
|
| 278 |
+
def __ne__(self, e):
|
| 279 |
+
return not (self == e)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
class ModuleImplementedIdeal(Ideal):
|
| 283 |
+
"""
|
| 284 |
+
Ideal implementation relying on the modules code.
|
| 285 |
+
|
| 286 |
+
Attributes:
|
| 287 |
+
|
| 288 |
+
- _module - the underlying module
|
| 289 |
+
"""
|
| 290 |
+
|
| 291 |
+
def __init__(self, ring, module):
|
| 292 |
+
Ideal.__init__(self, ring)
|
| 293 |
+
self._module = module
|
| 294 |
+
|
| 295 |
+
def _contains_elem(self, x):
|
| 296 |
+
return self._module.contains([x])
|
| 297 |
+
|
| 298 |
+
def _contains_ideal(self, J):
|
| 299 |
+
if not isinstance(J, ModuleImplementedIdeal):
|
| 300 |
+
raise NotImplementedError
|
| 301 |
+
return self._module.is_submodule(J._module)
|
| 302 |
+
|
| 303 |
+
def _intersect(self, J):
|
| 304 |
+
if not isinstance(J, ModuleImplementedIdeal):
|
| 305 |
+
raise NotImplementedError
|
| 306 |
+
return self.__class__(self.ring, self._module.intersect(J._module))
|
| 307 |
+
|
| 308 |
+
def _quotient(self, J, **opts):
|
| 309 |
+
if not isinstance(J, ModuleImplementedIdeal):
|
| 310 |
+
raise NotImplementedError
|
| 311 |
+
return self._module.module_quotient(J._module, **opts)
|
| 312 |
+
|
| 313 |
+
def _union(self, J):
|
| 314 |
+
if not isinstance(J, ModuleImplementedIdeal):
|
| 315 |
+
raise NotImplementedError
|
| 316 |
+
return self.__class__(self.ring, self._module.union(J._module))
|
| 317 |
+
|
| 318 |
+
@property
|
| 319 |
+
def gens(self):
|
| 320 |
+
"""
|
| 321 |
+
Return generators for ``self``.
|
| 322 |
+
|
| 323 |
+
Examples
|
| 324 |
+
========
|
| 325 |
+
|
| 326 |
+
>>> from sympy import QQ
|
| 327 |
+
>>> from sympy.abc import x, y
|
| 328 |
+
>>> list(QQ.old_poly_ring(x, y).ideal(x, y, x**2 + y).gens)
|
| 329 |
+
[DMP_Python([[1], []], QQ), DMP_Python([[1, 0]], QQ), DMP_Python([[1], [], [1, 0]], QQ)]
|
| 330 |
+
"""
|
| 331 |
+
return (x[0] for x in self._module.gens)
|
| 332 |
+
|
| 333 |
+
def is_zero(self):
|
| 334 |
+
"""
|
| 335 |
+
Return True if ``self`` is the zero ideal.
|
| 336 |
+
|
| 337 |
+
Examples
|
| 338 |
+
========
|
| 339 |
+
|
| 340 |
+
>>> from sympy.abc import x
|
| 341 |
+
>>> from sympy import QQ
|
| 342 |
+
>>> QQ.old_poly_ring(x).ideal(x).is_zero()
|
| 343 |
+
False
|
| 344 |
+
>>> QQ.old_poly_ring(x).ideal().is_zero()
|
| 345 |
+
True
|
| 346 |
+
"""
|
| 347 |
+
return self._module.is_zero()
|
| 348 |
+
|
| 349 |
+
def is_whole_ring(self):
|
| 350 |
+
"""
|
| 351 |
+
Return True if ``self`` is the whole ring, i.e. one generator is a unit.
|
| 352 |
+
|
| 353 |
+
Examples
|
| 354 |
+
========
|
| 355 |
+
|
| 356 |
+
>>> from sympy.abc import x
|
| 357 |
+
>>> from sympy import QQ, ilex
|
| 358 |
+
>>> QQ.old_poly_ring(x).ideal(x).is_whole_ring()
|
| 359 |
+
False
|
| 360 |
+
>>> QQ.old_poly_ring(x).ideal(3).is_whole_ring()
|
| 361 |
+
True
|
| 362 |
+
>>> QQ.old_poly_ring(x, order=ilex).ideal(2 + x).is_whole_ring()
|
| 363 |
+
True
|
| 364 |
+
"""
|
| 365 |
+
return self._module.is_full_module()
|
| 366 |
+
|
| 367 |
+
def __repr__(self):
|
| 368 |
+
from sympy.printing.str import sstr
|
| 369 |
+
gens = [self.ring.to_sympy(x) for [x] in self._module.gens]
|
| 370 |
+
return '<' + ','.join(sstr(g) for g in gens) + '>'
|
| 371 |
+
|
| 372 |
+
# NOTE this is the only method using the fact that the module is a SubModule
|
| 373 |
+
def _product(self, J):
|
| 374 |
+
if not isinstance(J, ModuleImplementedIdeal):
|
| 375 |
+
raise NotImplementedError
|
| 376 |
+
return self.__class__(self.ring, self._module.submodule(
|
| 377 |
+
*[[x*y] for [x] in self._module.gens for [y] in J._module.gens]))
|
| 378 |
+
|
| 379 |
+
def in_terms_of_generators(self, e):
|
| 380 |
+
"""
|
| 381 |
+
Express ``e`` in terms of the generators of ``self``.
|
| 382 |
+
|
| 383 |
+
Examples
|
| 384 |
+
========
|
| 385 |
+
|
| 386 |
+
>>> from sympy.abc import x
|
| 387 |
+
>>> from sympy import QQ
|
| 388 |
+
>>> I = QQ.old_poly_ring(x).ideal(x**2 + 1, x)
|
| 389 |
+
>>> I.in_terms_of_generators(1) # doctest: +SKIP
|
| 390 |
+
[DMP_Python([1], QQ), DMP_Python([-1, 0], QQ)]
|
| 391 |
+
"""
|
| 392 |
+
return self._module.in_terms_of_generators([e])
|
| 393 |
+
|
| 394 |
+
def reduce_element(self, x, **options):
|
| 395 |
+
return self._module.reduce_element([x], **options)[0]
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/modules.py
ADDED
|
@@ -0,0 +1,1488 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Computations with modules over polynomial rings.
|
| 3 |
+
|
| 4 |
+
This module implements various classes that encapsulate groebner basis
|
| 5 |
+
computations for modules. Most of them should not be instantiated by hand.
|
| 6 |
+
Instead, use the constructing routines on objects you already have.
|
| 7 |
+
|
| 8 |
+
For example, to construct a free module over ``QQ[x, y]``, call
|
| 9 |
+
``QQ[x, y].free_module(rank)`` instead of the ``FreeModule`` constructor.
|
| 10 |
+
In fact ``FreeModule`` is an abstract base class that should not be
|
| 11 |
+
instantiated, the ``free_module`` method instead returns the implementing class
|
| 12 |
+
``FreeModulePolyRing``.
|
| 13 |
+
|
| 14 |
+
In general, the abstract base classes implement most functionality in terms of
|
| 15 |
+
a few non-implemented methods. The concrete base classes supply only these
|
| 16 |
+
non-implemented methods. They may also supply new implementations of the
|
| 17 |
+
convenience methods, for example if there are faster algorithms available.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
from copy import copy
|
| 22 |
+
from functools import reduce
|
| 23 |
+
|
| 24 |
+
from sympy.polys.agca.ideals import Ideal
|
| 25 |
+
from sympy.polys.domains.field import Field
|
| 26 |
+
from sympy.polys.orderings import ProductOrder, monomial_key
|
| 27 |
+
from sympy.polys.polyclasses import DMP
|
| 28 |
+
from sympy.polys.polyerrors import CoercionFailed
|
| 29 |
+
from sympy.core.basic import _aresame
|
| 30 |
+
from sympy.utilities.iterables import iterable
|
| 31 |
+
|
| 32 |
+
# TODO
|
| 33 |
+
# - module saturation
|
| 34 |
+
# - module quotient/intersection for quotient rings
|
| 35 |
+
# - free resoltutions / syzygies
|
| 36 |
+
# - finding small/minimal generating sets
|
| 37 |
+
# - ...
|
| 38 |
+
|
| 39 |
+
##########################################################################
|
| 40 |
+
## Abstract base classes #################################################
|
| 41 |
+
##########################################################################
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class Module:
|
| 45 |
+
"""
|
| 46 |
+
Abstract base class for modules.
|
| 47 |
+
|
| 48 |
+
Do not instantiate - use ring explicit constructors instead:
|
| 49 |
+
|
| 50 |
+
>>> from sympy import QQ
|
| 51 |
+
>>> from sympy.abc import x
|
| 52 |
+
>>> QQ.old_poly_ring(x).free_module(2)
|
| 53 |
+
QQ[x]**2
|
| 54 |
+
|
| 55 |
+
Attributes:
|
| 56 |
+
|
| 57 |
+
- dtype - type of elements
|
| 58 |
+
- ring - containing ring
|
| 59 |
+
|
| 60 |
+
Non-implemented methods:
|
| 61 |
+
|
| 62 |
+
- submodule
|
| 63 |
+
- quotient_module
|
| 64 |
+
- is_zero
|
| 65 |
+
- is_submodule
|
| 66 |
+
- multiply_ideal
|
| 67 |
+
|
| 68 |
+
The method convert likely needs to be changed in subclasses.
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
def __init__(self, ring):
|
| 72 |
+
self.ring = ring
|
| 73 |
+
|
| 74 |
+
def convert(self, elem, M=None):
|
| 75 |
+
"""
|
| 76 |
+
Convert ``elem`` into internal representation of this module.
|
| 77 |
+
|
| 78 |
+
If ``M`` is not None, it should be a module containing it.
|
| 79 |
+
"""
|
| 80 |
+
if not isinstance(elem, self.dtype):
|
| 81 |
+
raise CoercionFailed
|
| 82 |
+
return elem
|
| 83 |
+
|
| 84 |
+
def submodule(self, *gens):
|
| 85 |
+
"""Generate a submodule."""
|
| 86 |
+
raise NotImplementedError
|
| 87 |
+
|
| 88 |
+
def quotient_module(self, other):
|
| 89 |
+
"""Generate a quotient module."""
|
| 90 |
+
raise NotImplementedError
|
| 91 |
+
|
| 92 |
+
def __truediv__(self, e):
|
| 93 |
+
if not isinstance(e, Module):
|
| 94 |
+
e = self.submodule(*e)
|
| 95 |
+
return self.quotient_module(e)
|
| 96 |
+
|
| 97 |
+
def contains(self, elem):
|
| 98 |
+
"""Return True if ``elem`` is an element of this module."""
|
| 99 |
+
try:
|
| 100 |
+
self.convert(elem)
|
| 101 |
+
return True
|
| 102 |
+
except CoercionFailed:
|
| 103 |
+
return False
|
| 104 |
+
|
| 105 |
+
def __contains__(self, elem):
|
| 106 |
+
return self.contains(elem)
|
| 107 |
+
|
| 108 |
+
def subset(self, other):
|
| 109 |
+
"""
|
| 110 |
+
Returns True if ``other`` is is a subset of ``self``.
|
| 111 |
+
|
| 112 |
+
Examples
|
| 113 |
+
========
|
| 114 |
+
|
| 115 |
+
>>> from sympy.abc import x
|
| 116 |
+
>>> from sympy import QQ
|
| 117 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 118 |
+
>>> F.subset([(1, x), (x, 2)])
|
| 119 |
+
True
|
| 120 |
+
>>> F.subset([(1/x, x), (x, 2)])
|
| 121 |
+
False
|
| 122 |
+
"""
|
| 123 |
+
return all(self.contains(x) for x in other)
|
| 124 |
+
|
| 125 |
+
def __eq__(self, other):
|
| 126 |
+
return self.is_submodule(other) and other.is_submodule(self)
|
| 127 |
+
|
| 128 |
+
def __ne__(self, other):
|
| 129 |
+
return not (self == other)
|
| 130 |
+
|
| 131 |
+
def is_zero(self):
|
| 132 |
+
"""Returns True if ``self`` is a zero module."""
|
| 133 |
+
raise NotImplementedError
|
| 134 |
+
|
| 135 |
+
def is_submodule(self, other):
|
| 136 |
+
"""Returns True if ``other`` is a submodule of ``self``."""
|
| 137 |
+
raise NotImplementedError
|
| 138 |
+
|
| 139 |
+
def multiply_ideal(self, other):
|
| 140 |
+
"""
|
| 141 |
+
Multiply ``self`` by the ideal ``other``.
|
| 142 |
+
"""
|
| 143 |
+
raise NotImplementedError
|
| 144 |
+
|
| 145 |
+
def __mul__(self, e):
|
| 146 |
+
if not isinstance(e, Ideal):
|
| 147 |
+
try:
|
| 148 |
+
e = self.ring.ideal(e)
|
| 149 |
+
except (CoercionFailed, NotImplementedError):
|
| 150 |
+
return NotImplemented
|
| 151 |
+
return self.multiply_ideal(e)
|
| 152 |
+
|
| 153 |
+
__rmul__ = __mul__
|
| 154 |
+
|
| 155 |
+
def identity_hom(self):
|
| 156 |
+
"""Return the identity homomorphism on ``self``."""
|
| 157 |
+
raise NotImplementedError
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class ModuleElement:
|
| 161 |
+
"""
|
| 162 |
+
Base class for module element wrappers.
|
| 163 |
+
|
| 164 |
+
Use this class to wrap primitive data types as module elements. It stores
|
| 165 |
+
a reference to the containing module, and implements all the arithmetic
|
| 166 |
+
operators.
|
| 167 |
+
|
| 168 |
+
Attributes:
|
| 169 |
+
|
| 170 |
+
- module - containing module
|
| 171 |
+
- data - internal data
|
| 172 |
+
|
| 173 |
+
Methods that likely need change in subclasses:
|
| 174 |
+
|
| 175 |
+
- add
|
| 176 |
+
- mul
|
| 177 |
+
- div
|
| 178 |
+
- eq
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
def __init__(self, module, data):
|
| 182 |
+
self.module = module
|
| 183 |
+
self.data = data
|
| 184 |
+
|
| 185 |
+
def add(self, d1, d2):
|
| 186 |
+
"""Add data ``d1`` and ``d2``."""
|
| 187 |
+
return d1 + d2
|
| 188 |
+
|
| 189 |
+
def mul(self, m, d):
|
| 190 |
+
"""Multiply module data ``m`` by coefficient d."""
|
| 191 |
+
return m * d
|
| 192 |
+
|
| 193 |
+
def div(self, m, d):
|
| 194 |
+
"""Divide module data ``m`` by coefficient d."""
|
| 195 |
+
return m / d
|
| 196 |
+
|
| 197 |
+
def eq(self, d1, d2):
|
| 198 |
+
"""Return true if d1 and d2 represent the same element."""
|
| 199 |
+
return d1 == d2
|
| 200 |
+
|
| 201 |
+
def __add__(self, om):
|
| 202 |
+
if not isinstance(om, self.__class__) or om.module != self.module:
|
| 203 |
+
try:
|
| 204 |
+
om = self.module.convert(om)
|
| 205 |
+
except CoercionFailed:
|
| 206 |
+
return NotImplemented
|
| 207 |
+
return self.__class__(self.module, self.add(self.data, om.data))
|
| 208 |
+
|
| 209 |
+
__radd__ = __add__
|
| 210 |
+
|
| 211 |
+
def __neg__(self):
|
| 212 |
+
return self.__class__(self.module, self.mul(self.data,
|
| 213 |
+
self.module.ring.convert(-1)))
|
| 214 |
+
|
| 215 |
+
def __sub__(self, om):
|
| 216 |
+
if not isinstance(om, self.__class__) or om.module != self.module:
|
| 217 |
+
try:
|
| 218 |
+
om = self.module.convert(om)
|
| 219 |
+
except CoercionFailed:
|
| 220 |
+
return NotImplemented
|
| 221 |
+
return self.__add__(-om)
|
| 222 |
+
|
| 223 |
+
def __rsub__(self, om):
|
| 224 |
+
return (-self).__add__(om)
|
| 225 |
+
|
| 226 |
+
def __mul__(self, o):
|
| 227 |
+
if not isinstance(o, self.module.ring.dtype):
|
| 228 |
+
try:
|
| 229 |
+
o = self.module.ring.convert(o)
|
| 230 |
+
except CoercionFailed:
|
| 231 |
+
return NotImplemented
|
| 232 |
+
return self.__class__(self.module, self.mul(self.data, o))
|
| 233 |
+
|
| 234 |
+
__rmul__ = __mul__
|
| 235 |
+
|
| 236 |
+
def __truediv__(self, o):
|
| 237 |
+
if not isinstance(o, self.module.ring.dtype):
|
| 238 |
+
try:
|
| 239 |
+
o = self.module.ring.convert(o)
|
| 240 |
+
except CoercionFailed:
|
| 241 |
+
return NotImplemented
|
| 242 |
+
return self.__class__(self.module, self.div(self.data, o))
|
| 243 |
+
|
| 244 |
+
def __eq__(self, om):
|
| 245 |
+
if not isinstance(om, self.__class__) or om.module != self.module:
|
| 246 |
+
try:
|
| 247 |
+
om = self.module.convert(om)
|
| 248 |
+
except CoercionFailed:
|
| 249 |
+
return False
|
| 250 |
+
return self.eq(self.data, om.data)
|
| 251 |
+
|
| 252 |
+
def __ne__(self, om):
|
| 253 |
+
return not self == om
|
| 254 |
+
|
| 255 |
+
##########################################################################
|
| 256 |
+
## Free Modules ##########################################################
|
| 257 |
+
##########################################################################
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
class FreeModuleElement(ModuleElement):
|
| 261 |
+
"""Element of a free module. Data stored as a tuple."""
|
| 262 |
+
|
| 263 |
+
def add(self, d1, d2):
|
| 264 |
+
return tuple(x + y for x, y in zip(d1, d2))
|
| 265 |
+
|
| 266 |
+
def mul(self, d, p):
|
| 267 |
+
return tuple(x * p for x in d)
|
| 268 |
+
|
| 269 |
+
def div(self, d, p):
|
| 270 |
+
return tuple(x / p for x in d)
|
| 271 |
+
|
| 272 |
+
def __repr__(self):
|
| 273 |
+
from sympy.printing.str import sstr
|
| 274 |
+
data = self.data
|
| 275 |
+
if any(isinstance(x, DMP) for x in data):
|
| 276 |
+
data = [self.module.ring.to_sympy(x) for x in data]
|
| 277 |
+
return '[' + ', '.join(sstr(x) for x in data) + ']'
|
| 278 |
+
|
| 279 |
+
def __iter__(self):
|
| 280 |
+
return self.data.__iter__()
|
| 281 |
+
|
| 282 |
+
def __getitem__(self, idx):
|
| 283 |
+
return self.data[idx]
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
class FreeModule(Module):
|
| 287 |
+
"""
|
| 288 |
+
Abstract base class for free modules.
|
| 289 |
+
|
| 290 |
+
Additional attributes:
|
| 291 |
+
|
| 292 |
+
- rank - rank of the free module
|
| 293 |
+
|
| 294 |
+
Non-implemented methods:
|
| 295 |
+
|
| 296 |
+
- submodule
|
| 297 |
+
"""
|
| 298 |
+
|
| 299 |
+
dtype = FreeModuleElement
|
| 300 |
+
|
| 301 |
+
def __init__(self, ring, rank):
|
| 302 |
+
Module.__init__(self, ring)
|
| 303 |
+
self.rank = rank
|
| 304 |
+
|
| 305 |
+
def __repr__(self):
|
| 306 |
+
return repr(self.ring) + "**" + repr(self.rank)
|
| 307 |
+
|
| 308 |
+
def is_submodule(self, other):
|
| 309 |
+
"""
|
| 310 |
+
Returns True if ``other`` is a submodule of ``self``.
|
| 311 |
+
|
| 312 |
+
Examples
|
| 313 |
+
========
|
| 314 |
+
|
| 315 |
+
>>> from sympy.abc import x
|
| 316 |
+
>>> from sympy import QQ
|
| 317 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 318 |
+
>>> M = F.submodule([2, x])
|
| 319 |
+
>>> F.is_submodule(F)
|
| 320 |
+
True
|
| 321 |
+
>>> F.is_submodule(M)
|
| 322 |
+
True
|
| 323 |
+
>>> M.is_submodule(F)
|
| 324 |
+
False
|
| 325 |
+
"""
|
| 326 |
+
if isinstance(other, SubModule):
|
| 327 |
+
return other.container == self
|
| 328 |
+
if isinstance(other, FreeModule):
|
| 329 |
+
return other.ring == self.ring and other.rank == self.rank
|
| 330 |
+
return False
|
| 331 |
+
|
| 332 |
+
def convert(self, elem, M=None):
|
| 333 |
+
"""
|
| 334 |
+
Convert ``elem`` into the internal representation.
|
| 335 |
+
|
| 336 |
+
This method is called implicitly whenever computations involve elements
|
| 337 |
+
not in the internal representation.
|
| 338 |
+
|
| 339 |
+
Examples
|
| 340 |
+
========
|
| 341 |
+
|
| 342 |
+
>>> from sympy.abc import x
|
| 343 |
+
>>> from sympy import QQ
|
| 344 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 345 |
+
>>> F.convert([1, 0])
|
| 346 |
+
[1, 0]
|
| 347 |
+
"""
|
| 348 |
+
if isinstance(elem, FreeModuleElement):
|
| 349 |
+
if elem.module is self:
|
| 350 |
+
return elem
|
| 351 |
+
if elem.module.rank != self.rank:
|
| 352 |
+
raise CoercionFailed
|
| 353 |
+
return FreeModuleElement(self,
|
| 354 |
+
tuple(self.ring.convert(x, elem.module.ring) for x in elem.data))
|
| 355 |
+
elif iterable(elem):
|
| 356 |
+
tpl = tuple(self.ring.convert(x) for x in elem)
|
| 357 |
+
if len(tpl) != self.rank:
|
| 358 |
+
raise CoercionFailed
|
| 359 |
+
return FreeModuleElement(self, tpl)
|
| 360 |
+
elif _aresame(elem, 0):
|
| 361 |
+
return FreeModuleElement(self, (self.ring.convert(0),)*self.rank)
|
| 362 |
+
else:
|
| 363 |
+
raise CoercionFailed
|
| 364 |
+
|
| 365 |
+
def is_zero(self):
|
| 366 |
+
"""
|
| 367 |
+
Returns True if ``self`` is a zero module.
|
| 368 |
+
|
| 369 |
+
(If, as this implementation assumes, the coefficient ring is not the
|
| 370 |
+
zero ring, then this is equivalent to the rank being zero.)
|
| 371 |
+
|
| 372 |
+
Examples
|
| 373 |
+
========
|
| 374 |
+
|
| 375 |
+
>>> from sympy.abc import x
|
| 376 |
+
>>> from sympy import QQ
|
| 377 |
+
>>> QQ.old_poly_ring(x).free_module(0).is_zero()
|
| 378 |
+
True
|
| 379 |
+
>>> QQ.old_poly_ring(x).free_module(1).is_zero()
|
| 380 |
+
False
|
| 381 |
+
"""
|
| 382 |
+
return self.rank == 0
|
| 383 |
+
|
| 384 |
+
def basis(self):
|
| 385 |
+
"""
|
| 386 |
+
Return a set of basis elements.
|
| 387 |
+
|
| 388 |
+
Examples
|
| 389 |
+
========
|
| 390 |
+
|
| 391 |
+
>>> from sympy.abc import x
|
| 392 |
+
>>> from sympy import QQ
|
| 393 |
+
>>> QQ.old_poly_ring(x).free_module(3).basis()
|
| 394 |
+
([1, 0, 0], [0, 1, 0], [0, 0, 1])
|
| 395 |
+
"""
|
| 396 |
+
from sympy.matrices import eye
|
| 397 |
+
M = eye(self.rank)
|
| 398 |
+
return tuple(self.convert(M.row(i)) for i in range(self.rank))
|
| 399 |
+
|
| 400 |
+
def quotient_module(self, submodule):
|
| 401 |
+
"""
|
| 402 |
+
Return a quotient module.
|
| 403 |
+
|
| 404 |
+
Examples
|
| 405 |
+
========
|
| 406 |
+
|
| 407 |
+
>>> from sympy.abc import x
|
| 408 |
+
>>> from sympy import QQ
|
| 409 |
+
>>> M = QQ.old_poly_ring(x).free_module(2)
|
| 410 |
+
>>> M.quotient_module(M.submodule([1, x], [x, 2]))
|
| 411 |
+
QQ[x]**2/<[1, x], [x, 2]>
|
| 412 |
+
|
| 413 |
+
Or more conicisely, using the overloaded division operator:
|
| 414 |
+
|
| 415 |
+
>>> QQ.old_poly_ring(x).free_module(2) / [[1, x], [x, 2]]
|
| 416 |
+
QQ[x]**2/<[1, x], [x, 2]>
|
| 417 |
+
"""
|
| 418 |
+
return QuotientModule(self.ring, self, submodule)
|
| 419 |
+
|
| 420 |
+
def multiply_ideal(self, other):
|
| 421 |
+
"""
|
| 422 |
+
Multiply ``self`` by the ideal ``other``.
|
| 423 |
+
|
| 424 |
+
Examples
|
| 425 |
+
========
|
| 426 |
+
|
| 427 |
+
>>> from sympy.abc import x
|
| 428 |
+
>>> from sympy import QQ
|
| 429 |
+
>>> I = QQ.old_poly_ring(x).ideal(x)
|
| 430 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 431 |
+
>>> F.multiply_ideal(I)
|
| 432 |
+
<[x, 0], [0, x]>
|
| 433 |
+
"""
|
| 434 |
+
return self.submodule(*self.basis()).multiply_ideal(other)
|
| 435 |
+
|
| 436 |
+
def identity_hom(self):
|
| 437 |
+
"""
|
| 438 |
+
Return the identity homomorphism on ``self``.
|
| 439 |
+
|
| 440 |
+
Examples
|
| 441 |
+
========
|
| 442 |
+
|
| 443 |
+
>>> from sympy.abc import x
|
| 444 |
+
>>> from sympy import QQ
|
| 445 |
+
>>> QQ.old_poly_ring(x).free_module(2).identity_hom()
|
| 446 |
+
Matrix([
|
| 447 |
+
[1, 0], : QQ[x]**2 -> QQ[x]**2
|
| 448 |
+
[0, 1]])
|
| 449 |
+
"""
|
| 450 |
+
from sympy.polys.agca.homomorphisms import homomorphism
|
| 451 |
+
return homomorphism(self, self, self.basis())
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
class FreeModulePolyRing(FreeModule):
|
| 455 |
+
"""
|
| 456 |
+
Free module over a generalized polynomial ring.
|
| 457 |
+
|
| 458 |
+
Do not instantiate this, use the constructor method of the ring instead:
|
| 459 |
+
|
| 460 |
+
Examples
|
| 461 |
+
========
|
| 462 |
+
|
| 463 |
+
>>> from sympy.abc import x
|
| 464 |
+
>>> from sympy import QQ
|
| 465 |
+
>>> F = QQ.old_poly_ring(x).free_module(3)
|
| 466 |
+
>>> F
|
| 467 |
+
QQ[x]**3
|
| 468 |
+
>>> F.contains([x, 1, 0])
|
| 469 |
+
True
|
| 470 |
+
>>> F.contains([1/x, 0, 1])
|
| 471 |
+
False
|
| 472 |
+
"""
|
| 473 |
+
|
| 474 |
+
def __init__(self, ring, rank):
|
| 475 |
+
from sympy.polys.domains.old_polynomialring import PolynomialRingBase
|
| 476 |
+
FreeModule.__init__(self, ring, rank)
|
| 477 |
+
if not isinstance(ring, PolynomialRingBase):
|
| 478 |
+
raise NotImplementedError('This implementation only works over '
|
| 479 |
+
+ 'polynomial rings, got %s' % ring)
|
| 480 |
+
if not isinstance(ring.dom, Field):
|
| 481 |
+
raise NotImplementedError('Ground domain must be a field, '
|
| 482 |
+
+ 'got %s' % ring.dom)
|
| 483 |
+
|
| 484 |
+
def submodule(self, *gens, **opts):
|
| 485 |
+
"""
|
| 486 |
+
Generate a submodule.
|
| 487 |
+
|
| 488 |
+
Examples
|
| 489 |
+
========
|
| 490 |
+
|
| 491 |
+
>>> from sympy.abc import x, y
|
| 492 |
+
>>> from sympy import QQ
|
| 493 |
+
>>> M = QQ.old_poly_ring(x, y).free_module(2).submodule([x, x + y])
|
| 494 |
+
>>> M
|
| 495 |
+
<[x, x + y]>
|
| 496 |
+
>>> M.contains([2*x, 2*x + 2*y])
|
| 497 |
+
True
|
| 498 |
+
>>> M.contains([x, y])
|
| 499 |
+
False
|
| 500 |
+
"""
|
| 501 |
+
return SubModulePolyRing(gens, self, **opts)
|
| 502 |
+
|
| 503 |
+
|
| 504 |
+
class FreeModuleQuotientRing(FreeModule):
|
| 505 |
+
"""
|
| 506 |
+
Free module over a quotient ring.
|
| 507 |
+
|
| 508 |
+
Do not instantiate this, use the constructor method of the ring instead:
|
| 509 |
+
|
| 510 |
+
Examples
|
| 511 |
+
========
|
| 512 |
+
|
| 513 |
+
>>> from sympy.abc import x
|
| 514 |
+
>>> from sympy import QQ
|
| 515 |
+
>>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(3)
|
| 516 |
+
>>> F
|
| 517 |
+
(QQ[x]/<x**2 + 1>)**3
|
| 518 |
+
|
| 519 |
+
Attributes
|
| 520 |
+
|
| 521 |
+
- quot - the quotient module `R^n / IR^n`, where `R/I` is our ring
|
| 522 |
+
"""
|
| 523 |
+
|
| 524 |
+
def __init__(self, ring, rank):
|
| 525 |
+
from sympy.polys.domains.quotientring import QuotientRing
|
| 526 |
+
FreeModule.__init__(self, ring, rank)
|
| 527 |
+
if not isinstance(ring, QuotientRing):
|
| 528 |
+
raise NotImplementedError('This implementation only works over '
|
| 529 |
+
+ 'quotient rings, got %s' % ring)
|
| 530 |
+
F = self.ring.ring.free_module(self.rank)
|
| 531 |
+
self.quot = F / (self.ring.base_ideal*F)
|
| 532 |
+
|
| 533 |
+
def __repr__(self):
|
| 534 |
+
return "(" + repr(self.ring) + ")" + "**" + repr(self.rank)
|
| 535 |
+
|
| 536 |
+
def submodule(self, *gens, **opts):
|
| 537 |
+
"""
|
| 538 |
+
Generate a submodule.
|
| 539 |
+
|
| 540 |
+
Examples
|
| 541 |
+
========
|
| 542 |
+
|
| 543 |
+
>>> from sympy.abc import x, y
|
| 544 |
+
>>> from sympy import QQ
|
| 545 |
+
>>> M = (QQ.old_poly_ring(x, y)/[x**2 - y**2]).free_module(2).submodule([x, x + y])
|
| 546 |
+
>>> M
|
| 547 |
+
<[x + <x**2 - y**2>, x + y + <x**2 - y**2>]>
|
| 548 |
+
>>> M.contains([y**2, x**2 + x*y])
|
| 549 |
+
True
|
| 550 |
+
>>> M.contains([x, y])
|
| 551 |
+
False
|
| 552 |
+
"""
|
| 553 |
+
return SubModuleQuotientRing(gens, self, **opts)
|
| 554 |
+
|
| 555 |
+
def lift(self, elem):
|
| 556 |
+
"""
|
| 557 |
+
Lift the element ``elem`` of self to the module self.quot.
|
| 558 |
+
|
| 559 |
+
Note that self.quot is the same set as self, just as an R-module
|
| 560 |
+
and not as an R/I-module, so this makes sense.
|
| 561 |
+
|
| 562 |
+
Examples
|
| 563 |
+
========
|
| 564 |
+
|
| 565 |
+
>>> from sympy.abc import x
|
| 566 |
+
>>> from sympy import QQ
|
| 567 |
+
>>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(2)
|
| 568 |
+
>>> e = F.convert([1, 0])
|
| 569 |
+
>>> e
|
| 570 |
+
[1 + <x**2 + 1>, 0 + <x**2 + 1>]
|
| 571 |
+
>>> L = F.quot
|
| 572 |
+
>>> l = F.lift(e)
|
| 573 |
+
>>> l
|
| 574 |
+
[1, 0] + <[x**2 + 1, 0], [0, x**2 + 1]>
|
| 575 |
+
>>> L.contains(l)
|
| 576 |
+
True
|
| 577 |
+
"""
|
| 578 |
+
return self.quot.convert([x.data for x in elem])
|
| 579 |
+
|
| 580 |
+
def unlift(self, elem):
|
| 581 |
+
"""
|
| 582 |
+
Push down an element of self.quot to self.
|
| 583 |
+
|
| 584 |
+
This undoes ``lift``.
|
| 585 |
+
|
| 586 |
+
Examples
|
| 587 |
+
========
|
| 588 |
+
|
| 589 |
+
>>> from sympy.abc import x
|
| 590 |
+
>>> from sympy import QQ
|
| 591 |
+
>>> F = (QQ.old_poly_ring(x)/[x**2 + 1]).free_module(2)
|
| 592 |
+
>>> e = F.convert([1, 0])
|
| 593 |
+
>>> l = F.lift(e)
|
| 594 |
+
>>> e == l
|
| 595 |
+
False
|
| 596 |
+
>>> e == F.unlift(l)
|
| 597 |
+
True
|
| 598 |
+
"""
|
| 599 |
+
return self.convert(elem.data)
|
| 600 |
+
|
| 601 |
+
##########################################################################
|
| 602 |
+
## Submodules and subquotients ###########################################
|
| 603 |
+
##########################################################################
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
class SubModule(Module):
|
| 607 |
+
"""
|
| 608 |
+
Base class for submodules.
|
| 609 |
+
|
| 610 |
+
Attributes:
|
| 611 |
+
|
| 612 |
+
- container - containing module
|
| 613 |
+
- gens - generators (subset of containing module)
|
| 614 |
+
- rank - rank of containing module
|
| 615 |
+
|
| 616 |
+
Non-implemented methods:
|
| 617 |
+
|
| 618 |
+
- _contains
|
| 619 |
+
- _syzygies
|
| 620 |
+
- _in_terms_of_generators
|
| 621 |
+
- _intersect
|
| 622 |
+
- _module_quotient
|
| 623 |
+
|
| 624 |
+
Methods that likely need change in subclasses:
|
| 625 |
+
|
| 626 |
+
- reduce_element
|
| 627 |
+
"""
|
| 628 |
+
|
| 629 |
+
def __init__(self, gens, container):
|
| 630 |
+
Module.__init__(self, container.ring)
|
| 631 |
+
self.gens = tuple(container.convert(x) for x in gens)
|
| 632 |
+
self.container = container
|
| 633 |
+
self.rank = container.rank
|
| 634 |
+
self.ring = container.ring
|
| 635 |
+
self.dtype = container.dtype
|
| 636 |
+
|
| 637 |
+
def __repr__(self):
|
| 638 |
+
return "<" + ", ".join(repr(x) for x in self.gens) + ">"
|
| 639 |
+
|
| 640 |
+
def _contains(self, other):
|
| 641 |
+
"""Implementation of containment.
|
| 642 |
+
Other is guaranteed to be FreeModuleElement."""
|
| 643 |
+
raise NotImplementedError
|
| 644 |
+
|
| 645 |
+
def _syzygies(self):
|
| 646 |
+
"""Implementation of syzygy computation wrt self generators."""
|
| 647 |
+
raise NotImplementedError
|
| 648 |
+
|
| 649 |
+
def _in_terms_of_generators(self, e):
|
| 650 |
+
"""Implementation of expression in terms of generators."""
|
| 651 |
+
raise NotImplementedError
|
| 652 |
+
|
| 653 |
+
def convert(self, elem, M=None):
|
| 654 |
+
"""
|
| 655 |
+
Convert ``elem`` into the internal represantition.
|
| 656 |
+
|
| 657 |
+
Mostly called implicitly.
|
| 658 |
+
|
| 659 |
+
Examples
|
| 660 |
+
========
|
| 661 |
+
|
| 662 |
+
>>> from sympy.abc import x
|
| 663 |
+
>>> from sympy import QQ
|
| 664 |
+
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([1, x])
|
| 665 |
+
>>> M.convert([2, 2*x])
|
| 666 |
+
[2, 2*x]
|
| 667 |
+
"""
|
| 668 |
+
if isinstance(elem, self.container.dtype) and elem.module is self:
|
| 669 |
+
return elem
|
| 670 |
+
r = copy(self.container.convert(elem, M))
|
| 671 |
+
r.module = self
|
| 672 |
+
if not self._contains(r):
|
| 673 |
+
raise CoercionFailed
|
| 674 |
+
return r
|
| 675 |
+
|
| 676 |
+
def _intersect(self, other):
|
| 677 |
+
"""Implementation of intersection.
|
| 678 |
+
Other is guaranteed to be a submodule of same free module."""
|
| 679 |
+
raise NotImplementedError
|
| 680 |
+
|
| 681 |
+
def _module_quotient(self, other):
|
| 682 |
+
"""Implementation of quotient.
|
| 683 |
+
Other is guaranteed to be a submodule of same free module."""
|
| 684 |
+
raise NotImplementedError
|
| 685 |
+
|
| 686 |
+
def intersect(self, other, **options):
|
| 687 |
+
"""
|
| 688 |
+
Returns the intersection of ``self`` with submodule ``other``.
|
| 689 |
+
|
| 690 |
+
Examples
|
| 691 |
+
========
|
| 692 |
+
|
| 693 |
+
>>> from sympy.abc import x, y
|
| 694 |
+
>>> from sympy import QQ
|
| 695 |
+
>>> F = QQ.old_poly_ring(x, y).free_module(2)
|
| 696 |
+
>>> F.submodule([x, x]).intersect(F.submodule([y, y]))
|
| 697 |
+
<[x*y, x*y]>
|
| 698 |
+
|
| 699 |
+
Some implementation allow further options to be passed. Currently, to
|
| 700 |
+
only one implemented is ``relations=True``, in which case the function
|
| 701 |
+
will return a triple ``(res, rela, relb)``, where ``res`` is the
|
| 702 |
+
intersection module, and ``rela`` and ``relb`` are lists of coefficient
|
| 703 |
+
vectors, expressing the generators of ``res`` in terms of the
|
| 704 |
+
generators of ``self`` (``rela``) and ``other`` (``relb``).
|
| 705 |
+
|
| 706 |
+
>>> F.submodule([x, x]).intersect(F.submodule([y, y]), relations=True)
|
| 707 |
+
(<[x*y, x*y]>, [(DMP_Python([[1, 0]], QQ),)], [(DMP_Python([[1], []], QQ),)])
|
| 708 |
+
|
| 709 |
+
The above result says: the intersection module is generated by the
|
| 710 |
+
single element `(-xy, -xy) = -y (x, x) = -x (y, y)`, where
|
| 711 |
+
`(x, x)` and `(y, y)` respectively are the unique generators of
|
| 712 |
+
the two modules being intersected.
|
| 713 |
+
"""
|
| 714 |
+
if not isinstance(other, SubModule):
|
| 715 |
+
raise TypeError('%s is not a SubModule' % other)
|
| 716 |
+
if other.container != self.container:
|
| 717 |
+
raise ValueError(
|
| 718 |
+
'%s is contained in a different free module' % other)
|
| 719 |
+
return self._intersect(other, **options)
|
| 720 |
+
|
| 721 |
+
def module_quotient(self, other, **options):
|
| 722 |
+
r"""
|
| 723 |
+
Returns the module quotient of ``self`` by submodule ``other``.
|
| 724 |
+
|
| 725 |
+
That is, if ``self`` is the module `M` and ``other`` is `N`, then
|
| 726 |
+
return the ideal `\{f \in R | fN \subset M\}`.
|
| 727 |
+
|
| 728 |
+
Examples
|
| 729 |
+
========
|
| 730 |
+
|
| 731 |
+
>>> from sympy import QQ
|
| 732 |
+
>>> from sympy.abc import x, y
|
| 733 |
+
>>> F = QQ.old_poly_ring(x, y).free_module(2)
|
| 734 |
+
>>> S = F.submodule([x*y, x*y])
|
| 735 |
+
>>> T = F.submodule([x, x])
|
| 736 |
+
>>> S.module_quotient(T)
|
| 737 |
+
<y>
|
| 738 |
+
|
| 739 |
+
Some implementations allow further options to be passed. Currently, the
|
| 740 |
+
only one implemented is ``relations=True``, which may only be passed
|
| 741 |
+
if ``other`` is principal. In this case the function
|
| 742 |
+
will return a pair ``(res, rel)`` where ``res`` is the ideal, and
|
| 743 |
+
``rel`` is a list of coefficient vectors, expressing the generators of
|
| 744 |
+
the ideal, multiplied by the generator of ``other`` in terms of
|
| 745 |
+
generators of ``self``.
|
| 746 |
+
|
| 747 |
+
>>> S.module_quotient(T, relations=True)
|
| 748 |
+
(<y>, [[DMP_Python([[1]], QQ)]])
|
| 749 |
+
|
| 750 |
+
This means that the quotient ideal is generated by the single element
|
| 751 |
+
`y`, and that `y (x, x) = 1 (xy, xy)`, `(x, x)` and `(xy, xy)` being
|
| 752 |
+
the generators of `T` and `S`, respectively.
|
| 753 |
+
"""
|
| 754 |
+
if not isinstance(other, SubModule):
|
| 755 |
+
raise TypeError('%s is not a SubModule' % other)
|
| 756 |
+
if other.container != self.container:
|
| 757 |
+
raise ValueError(
|
| 758 |
+
'%s is contained in a different free module' % other)
|
| 759 |
+
return self._module_quotient(other, **options)
|
| 760 |
+
|
| 761 |
+
def union(self, other):
|
| 762 |
+
"""
|
| 763 |
+
Returns the module generated by the union of ``self`` and ``other``.
|
| 764 |
+
|
| 765 |
+
Examples
|
| 766 |
+
========
|
| 767 |
+
|
| 768 |
+
>>> from sympy.abc import x
|
| 769 |
+
>>> from sympy import QQ
|
| 770 |
+
>>> F = QQ.old_poly_ring(x).free_module(1)
|
| 771 |
+
>>> M = F.submodule([x**2 + x]) # <x(x+1)>
|
| 772 |
+
>>> N = F.submodule([x**2 - 1]) # <(x-1)(x+1)>
|
| 773 |
+
>>> M.union(N) == F.submodule([x+1])
|
| 774 |
+
True
|
| 775 |
+
"""
|
| 776 |
+
if not isinstance(other, SubModule):
|
| 777 |
+
raise TypeError('%s is not a SubModule' % other)
|
| 778 |
+
if other.container != self.container:
|
| 779 |
+
raise ValueError(
|
| 780 |
+
'%s is contained in a different free module' % other)
|
| 781 |
+
return self.__class__(self.gens + other.gens, self.container)
|
| 782 |
+
|
| 783 |
+
def is_zero(self):
|
| 784 |
+
"""
|
| 785 |
+
Return True if ``self`` is a zero module.
|
| 786 |
+
|
| 787 |
+
Examples
|
| 788 |
+
========
|
| 789 |
+
|
| 790 |
+
>>> from sympy.abc import x
|
| 791 |
+
>>> from sympy import QQ
|
| 792 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 793 |
+
>>> F.submodule([x, 1]).is_zero()
|
| 794 |
+
False
|
| 795 |
+
>>> F.submodule([0, 0]).is_zero()
|
| 796 |
+
True
|
| 797 |
+
"""
|
| 798 |
+
return all(x == 0 for x in self.gens)
|
| 799 |
+
|
| 800 |
+
def submodule(self, *gens):
|
| 801 |
+
"""
|
| 802 |
+
Generate a submodule.
|
| 803 |
+
|
| 804 |
+
Examples
|
| 805 |
+
========
|
| 806 |
+
|
| 807 |
+
>>> from sympy.abc import x
|
| 808 |
+
>>> from sympy import QQ
|
| 809 |
+
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([x, 1])
|
| 810 |
+
>>> M.submodule([x**2, x])
|
| 811 |
+
<[x**2, x]>
|
| 812 |
+
"""
|
| 813 |
+
if not self.subset(gens):
|
| 814 |
+
raise ValueError('%s not a subset of %s' % (gens, self))
|
| 815 |
+
return self.__class__(gens, self.container)
|
| 816 |
+
|
| 817 |
+
def is_full_module(self):
|
| 818 |
+
"""
|
| 819 |
+
Return True if ``self`` is the entire free module.
|
| 820 |
+
|
| 821 |
+
Examples
|
| 822 |
+
========
|
| 823 |
+
|
| 824 |
+
>>> from sympy.abc import x
|
| 825 |
+
>>> from sympy import QQ
|
| 826 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 827 |
+
>>> F.submodule([x, 1]).is_full_module()
|
| 828 |
+
False
|
| 829 |
+
>>> F.submodule([1, 1], [1, 2]).is_full_module()
|
| 830 |
+
True
|
| 831 |
+
"""
|
| 832 |
+
return all(self.contains(x) for x in self.container.basis())
|
| 833 |
+
|
| 834 |
+
def is_submodule(self, other):
|
| 835 |
+
"""
|
| 836 |
+
Returns True if ``other`` is a submodule of ``self``.
|
| 837 |
+
|
| 838 |
+
>>> from sympy.abc import x
|
| 839 |
+
>>> from sympy import QQ
|
| 840 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 841 |
+
>>> M = F.submodule([2, x])
|
| 842 |
+
>>> N = M.submodule([2*x, x**2])
|
| 843 |
+
>>> M.is_submodule(M)
|
| 844 |
+
True
|
| 845 |
+
>>> M.is_submodule(N)
|
| 846 |
+
True
|
| 847 |
+
>>> N.is_submodule(M)
|
| 848 |
+
False
|
| 849 |
+
"""
|
| 850 |
+
if isinstance(other, SubModule):
|
| 851 |
+
return self.container == other.container and \
|
| 852 |
+
all(self.contains(x) for x in other.gens)
|
| 853 |
+
if isinstance(other, (FreeModule, QuotientModule)):
|
| 854 |
+
return self.container == other and self.is_full_module()
|
| 855 |
+
return False
|
| 856 |
+
|
| 857 |
+
def syzygy_module(self, **opts):
|
| 858 |
+
r"""
|
| 859 |
+
Compute the syzygy module of the generators of ``self``.
|
| 860 |
+
|
| 861 |
+
Suppose `M` is generated by `f_1, \ldots, f_n` over the ring
|
| 862 |
+
`R`. Consider the homomorphism `\phi: R^n \to M`, given by
|
| 863 |
+
sending `(r_1, \ldots, r_n) \to r_1 f_1 + \cdots + r_n f_n`.
|
| 864 |
+
The syzygy module is defined to be the kernel of `\phi`.
|
| 865 |
+
|
| 866 |
+
Examples
|
| 867 |
+
========
|
| 868 |
+
|
| 869 |
+
The syzygy module is zero iff the generators generate freely a free
|
| 870 |
+
submodule:
|
| 871 |
+
|
| 872 |
+
>>> from sympy.abc import x, y
|
| 873 |
+
>>> from sympy import QQ
|
| 874 |
+
>>> QQ.old_poly_ring(x).free_module(2).submodule([1, 0], [1, 1]).syzygy_module().is_zero()
|
| 875 |
+
True
|
| 876 |
+
|
| 877 |
+
A slightly more interesting example:
|
| 878 |
+
|
| 879 |
+
>>> M = QQ.old_poly_ring(x, y).free_module(2).submodule([x, 2*x], [y, 2*y])
|
| 880 |
+
>>> S = QQ.old_poly_ring(x, y).free_module(2).submodule([y, -x])
|
| 881 |
+
>>> M.syzygy_module() == S
|
| 882 |
+
True
|
| 883 |
+
"""
|
| 884 |
+
F = self.ring.free_module(len(self.gens))
|
| 885 |
+
# NOTE we filter out zero syzygies. This is for convenience of the
|
| 886 |
+
# _syzygies function and not meant to replace any real "generating set
|
| 887 |
+
# reduction" algorithm
|
| 888 |
+
return F.submodule(*[x for x in self._syzygies() if F.convert(x) != 0],
|
| 889 |
+
**opts)
|
| 890 |
+
|
| 891 |
+
def in_terms_of_generators(self, e):
|
| 892 |
+
"""
|
| 893 |
+
Express element ``e`` of ``self`` in terms of the generators.
|
| 894 |
+
|
| 895 |
+
Examples
|
| 896 |
+
========
|
| 897 |
+
|
| 898 |
+
>>> from sympy.abc import x
|
| 899 |
+
>>> from sympy import QQ
|
| 900 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 901 |
+
>>> M = F.submodule([1, 0], [1, 1])
|
| 902 |
+
>>> M.in_terms_of_generators([x, x**2]) # doctest: +SKIP
|
| 903 |
+
[DMP_Python([-1, 1, 0], QQ), DMP_Python([1, 0, 0], QQ)]
|
| 904 |
+
"""
|
| 905 |
+
try:
|
| 906 |
+
e = self.convert(e)
|
| 907 |
+
except CoercionFailed:
|
| 908 |
+
raise ValueError('%s is not an element of %s' % (e, self))
|
| 909 |
+
return self._in_terms_of_generators(e)
|
| 910 |
+
|
| 911 |
+
def reduce_element(self, x):
|
| 912 |
+
"""
|
| 913 |
+
Reduce the element ``x`` of our ring modulo the ideal ``self``.
|
| 914 |
+
|
| 915 |
+
Here "reduce" has no specific meaning, it could return a unique normal
|
| 916 |
+
form, simplify the expression a bit, or just do nothing.
|
| 917 |
+
"""
|
| 918 |
+
return x
|
| 919 |
+
|
| 920 |
+
def quotient_module(self, other, **opts):
|
| 921 |
+
"""
|
| 922 |
+
Return a quotient module.
|
| 923 |
+
|
| 924 |
+
This is the same as taking a submodule of a quotient of the containing
|
| 925 |
+
module.
|
| 926 |
+
|
| 927 |
+
Examples
|
| 928 |
+
========
|
| 929 |
+
|
| 930 |
+
>>> from sympy.abc import x
|
| 931 |
+
>>> from sympy import QQ
|
| 932 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 933 |
+
>>> S1 = F.submodule([x, 1])
|
| 934 |
+
>>> S2 = F.submodule([x**2, x])
|
| 935 |
+
>>> S1.quotient_module(S2)
|
| 936 |
+
<[x, 1] + <[x**2, x]>>
|
| 937 |
+
|
| 938 |
+
Or more coincisely, using the overloaded division operator:
|
| 939 |
+
|
| 940 |
+
>>> F.submodule([x, 1]) / [(x**2, x)]
|
| 941 |
+
<[x, 1] + <[x**2, x]>>
|
| 942 |
+
"""
|
| 943 |
+
if not self.is_submodule(other):
|
| 944 |
+
raise ValueError('%s not a submodule of %s' % (other, self))
|
| 945 |
+
return SubQuotientModule(self.gens,
|
| 946 |
+
self.container.quotient_module(other), **opts)
|
| 947 |
+
|
| 948 |
+
def __add__(self, oth):
|
| 949 |
+
return self.container.quotient_module(self).convert(oth)
|
| 950 |
+
|
| 951 |
+
__radd__ = __add__
|
| 952 |
+
|
| 953 |
+
def multiply_ideal(self, I):
|
| 954 |
+
"""
|
| 955 |
+
Multiply ``self`` by the ideal ``I``.
|
| 956 |
+
|
| 957 |
+
Examples
|
| 958 |
+
========
|
| 959 |
+
|
| 960 |
+
>>> from sympy.abc import x
|
| 961 |
+
>>> from sympy import QQ
|
| 962 |
+
>>> I = QQ.old_poly_ring(x).ideal(x**2)
|
| 963 |
+
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([1, 1])
|
| 964 |
+
>>> I*M
|
| 965 |
+
<[x**2, x**2]>
|
| 966 |
+
"""
|
| 967 |
+
return self.submodule(*[x*g for [x] in I._module.gens for g in self.gens])
|
| 968 |
+
|
| 969 |
+
def inclusion_hom(self):
|
| 970 |
+
"""
|
| 971 |
+
Return a homomorphism representing the inclusion map of ``self``.
|
| 972 |
+
|
| 973 |
+
That is, the natural map from ``self`` to ``self.container``.
|
| 974 |
+
|
| 975 |
+
Examples
|
| 976 |
+
========
|
| 977 |
+
|
| 978 |
+
>>> from sympy.abc import x
|
| 979 |
+
>>> from sympy import QQ
|
| 980 |
+
>>> QQ.old_poly_ring(x).free_module(2).submodule([x, x]).inclusion_hom()
|
| 981 |
+
Matrix([
|
| 982 |
+
[1, 0], : <[x, x]> -> QQ[x]**2
|
| 983 |
+
[0, 1]])
|
| 984 |
+
"""
|
| 985 |
+
return self.container.identity_hom().restrict_domain(self)
|
| 986 |
+
|
| 987 |
+
def identity_hom(self):
|
| 988 |
+
"""
|
| 989 |
+
Return the identity homomorphism on ``self``.
|
| 990 |
+
|
| 991 |
+
Examples
|
| 992 |
+
========
|
| 993 |
+
|
| 994 |
+
>>> from sympy.abc import x
|
| 995 |
+
>>> from sympy import QQ
|
| 996 |
+
>>> QQ.old_poly_ring(x).free_module(2).submodule([x, x]).identity_hom()
|
| 997 |
+
Matrix([
|
| 998 |
+
[1, 0], : <[x, x]> -> <[x, x]>
|
| 999 |
+
[0, 1]])
|
| 1000 |
+
"""
|
| 1001 |
+
return self.container.identity_hom().restrict_domain(
|
| 1002 |
+
self).restrict_codomain(self)
|
| 1003 |
+
|
| 1004 |
+
|
| 1005 |
+
class SubQuotientModule(SubModule):
|
| 1006 |
+
"""
|
| 1007 |
+
Submodule of a quotient module.
|
| 1008 |
+
|
| 1009 |
+
Equivalently, quotient module of a submodule.
|
| 1010 |
+
|
| 1011 |
+
Do not instantiate this, instead use the submodule or quotient_module
|
| 1012 |
+
constructing methods:
|
| 1013 |
+
|
| 1014 |
+
>>> from sympy.abc import x
|
| 1015 |
+
>>> from sympy import QQ
|
| 1016 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 1017 |
+
>>> S = F.submodule([1, 0], [1, x])
|
| 1018 |
+
>>> Q = F/[(1, 0)]
|
| 1019 |
+
>>> S/[(1, 0)] == Q.submodule([5, x])
|
| 1020 |
+
True
|
| 1021 |
+
|
| 1022 |
+
Attributes:
|
| 1023 |
+
|
| 1024 |
+
- base - base module we are quotient of
|
| 1025 |
+
- killed_module - submodule used to form the quotient
|
| 1026 |
+
"""
|
| 1027 |
+
def __init__(self, gens, container, **opts):
|
| 1028 |
+
SubModule.__init__(self, gens, container)
|
| 1029 |
+
self.killed_module = self.container.killed_module
|
| 1030 |
+
# XXX it is important for some code below that the generators of base
|
| 1031 |
+
# are in this particular order!
|
| 1032 |
+
self.base = self.container.base.submodule(
|
| 1033 |
+
*[x.data for x in self.gens], **opts).union(self.killed_module)
|
| 1034 |
+
|
| 1035 |
+
def _contains(self, elem):
|
| 1036 |
+
return self.base.contains(elem.data)
|
| 1037 |
+
|
| 1038 |
+
def _syzygies(self):
|
| 1039 |
+
# let N = self.killed_module be generated by e_1, ..., e_r
|
| 1040 |
+
# let F = self.base be generated by f_1, ..., f_s and e_1, ..., e_r
|
| 1041 |
+
# Then self = F/N.
|
| 1042 |
+
# Let phi: R**s --> self be the evident surjection.
|
| 1043 |
+
# Similarly psi: R**(s + r) --> F.
|
| 1044 |
+
# We need to find generators for ker(phi). Let chi: R**s --> F be the
|
| 1045 |
+
# evident lift of phi. For X in R**s, phi(X) = 0 iff chi(X) is
|
| 1046 |
+
# contained in N, iff there exists Y in R**r such that
|
| 1047 |
+
# psi(X, Y) = 0.
|
| 1048 |
+
# Hence if alpha: R**(s + r) --> R**s is the projection map, then
|
| 1049 |
+
# ker(phi) = alpha ker(psi).
|
| 1050 |
+
return [X[:len(self.gens)] for X in self.base._syzygies()]
|
| 1051 |
+
|
| 1052 |
+
def _in_terms_of_generators(self, e):
|
| 1053 |
+
return self.base._in_terms_of_generators(e.data)[:len(self.gens)]
|
| 1054 |
+
|
| 1055 |
+
def is_full_module(self):
|
| 1056 |
+
"""
|
| 1057 |
+
Return True if ``self`` is the entire free module.
|
| 1058 |
+
|
| 1059 |
+
Examples
|
| 1060 |
+
========
|
| 1061 |
+
|
| 1062 |
+
>>> from sympy.abc import x
|
| 1063 |
+
>>> from sympy import QQ
|
| 1064 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 1065 |
+
>>> F.submodule([x, 1]).is_full_module()
|
| 1066 |
+
False
|
| 1067 |
+
>>> F.submodule([1, 1], [1, 2]).is_full_module()
|
| 1068 |
+
True
|
| 1069 |
+
"""
|
| 1070 |
+
return self.base.is_full_module()
|
| 1071 |
+
|
| 1072 |
+
def quotient_hom(self):
|
| 1073 |
+
"""
|
| 1074 |
+
Return the quotient homomorphism to self.
|
| 1075 |
+
|
| 1076 |
+
That is, return the natural map from ``self.base`` to ``self``.
|
| 1077 |
+
|
| 1078 |
+
Examples
|
| 1079 |
+
========
|
| 1080 |
+
|
| 1081 |
+
>>> from sympy.abc import x
|
| 1082 |
+
>>> from sympy import QQ
|
| 1083 |
+
>>> M = (QQ.old_poly_ring(x).free_module(2) / [(1, x)]).submodule([1, 0])
|
| 1084 |
+
>>> M.quotient_hom()
|
| 1085 |
+
Matrix([
|
| 1086 |
+
[1, 0], : <[1, 0], [1, x]> -> <[1, 0] + <[1, x]>, [1, x] + <[1, x]>>
|
| 1087 |
+
[0, 1]])
|
| 1088 |
+
"""
|
| 1089 |
+
return self.base.identity_hom().quotient_codomain(self.killed_module)
|
| 1090 |
+
|
| 1091 |
+
|
| 1092 |
+
_subs0 = lambda x: x[0]
|
| 1093 |
+
_subs1 = lambda x: x[1:]
|
| 1094 |
+
|
| 1095 |
+
|
| 1096 |
+
class ModuleOrder(ProductOrder):
|
| 1097 |
+
"""A product monomial order with a zeroth term as module index."""
|
| 1098 |
+
|
| 1099 |
+
def __init__(self, o1, o2, TOP):
|
| 1100 |
+
if TOP:
|
| 1101 |
+
ProductOrder.__init__(self, (o2, _subs1), (o1, _subs0))
|
| 1102 |
+
else:
|
| 1103 |
+
ProductOrder.__init__(self, (o1, _subs0), (o2, _subs1))
|
| 1104 |
+
|
| 1105 |
+
|
| 1106 |
+
class SubModulePolyRing(SubModule):
|
| 1107 |
+
"""
|
| 1108 |
+
Submodule of a free module over a generalized polynomial ring.
|
| 1109 |
+
|
| 1110 |
+
Do not instantiate this, use the constructor method of FreeModule instead:
|
| 1111 |
+
|
| 1112 |
+
>>> from sympy.abc import x, y
|
| 1113 |
+
>>> from sympy import QQ
|
| 1114 |
+
>>> F = QQ.old_poly_ring(x, y).free_module(2)
|
| 1115 |
+
>>> F.submodule([x, y], [1, 0])
|
| 1116 |
+
<[x, y], [1, 0]>
|
| 1117 |
+
|
| 1118 |
+
Attributes:
|
| 1119 |
+
|
| 1120 |
+
- order - monomial order used
|
| 1121 |
+
"""
|
| 1122 |
+
|
| 1123 |
+
#self._gb - cached groebner basis
|
| 1124 |
+
#self._gbe - cached groebner basis relations
|
| 1125 |
+
|
| 1126 |
+
def __init__(self, gens, container, order="lex", TOP=True):
|
| 1127 |
+
SubModule.__init__(self, gens, container)
|
| 1128 |
+
if not isinstance(container, FreeModulePolyRing):
|
| 1129 |
+
raise NotImplementedError('This implementation is for submodules of '
|
| 1130 |
+
+ 'FreeModulePolyRing, got %s' % container)
|
| 1131 |
+
self.order = ModuleOrder(monomial_key(order), self.ring.order, TOP)
|
| 1132 |
+
self._gb = None
|
| 1133 |
+
self._gbe = None
|
| 1134 |
+
|
| 1135 |
+
def __eq__(self, other):
|
| 1136 |
+
if isinstance(other, SubModulePolyRing) and self.order != other.order:
|
| 1137 |
+
return False
|
| 1138 |
+
return SubModule.__eq__(self, other)
|
| 1139 |
+
|
| 1140 |
+
def _groebner(self, extended=False):
|
| 1141 |
+
"""Returns a standard basis in sdm form."""
|
| 1142 |
+
from sympy.polys.distributedmodules import sdm_groebner, sdm_nf_mora
|
| 1143 |
+
if self._gbe is None and extended:
|
| 1144 |
+
gb, gbe = sdm_groebner(
|
| 1145 |
+
[self.ring._vector_to_sdm(x, self.order) for x in self.gens],
|
| 1146 |
+
sdm_nf_mora, self.order, self.ring.dom, extended=True)
|
| 1147 |
+
self._gb, self._gbe = tuple(gb), tuple(gbe)
|
| 1148 |
+
if self._gb is None:
|
| 1149 |
+
self._gb = tuple(sdm_groebner(
|
| 1150 |
+
[self.ring._vector_to_sdm(x, self.order) for x in self.gens],
|
| 1151 |
+
sdm_nf_mora, self.order, self.ring.dom))
|
| 1152 |
+
if extended:
|
| 1153 |
+
return self._gb, self._gbe
|
| 1154 |
+
else:
|
| 1155 |
+
return self._gb
|
| 1156 |
+
|
| 1157 |
+
def _groebner_vec(self, extended=False):
|
| 1158 |
+
"""Returns a standard basis in element form."""
|
| 1159 |
+
if not extended:
|
| 1160 |
+
return [FreeModuleElement(self,
|
| 1161 |
+
tuple(self.ring._sdm_to_vector(x, self.rank)))
|
| 1162 |
+
for x in self._groebner()]
|
| 1163 |
+
gb, gbe = self._groebner(extended=True)
|
| 1164 |
+
return ([self.convert(self.ring._sdm_to_vector(x, self.rank))
|
| 1165 |
+
for x in gb],
|
| 1166 |
+
[self.ring._sdm_to_vector(x, len(self.gens)) for x in gbe])
|
| 1167 |
+
|
| 1168 |
+
def _contains(self, x):
|
| 1169 |
+
from sympy.polys.distributedmodules import sdm_zero, sdm_nf_mora
|
| 1170 |
+
return sdm_nf_mora(self.ring._vector_to_sdm(x, self.order),
|
| 1171 |
+
self._groebner(), self.order, self.ring.dom) == \
|
| 1172 |
+
sdm_zero()
|
| 1173 |
+
|
| 1174 |
+
def _syzygies(self):
|
| 1175 |
+
"""Compute syzygies. See [SCA, algorithm 2.5.4]."""
|
| 1176 |
+
# NOTE if self.gens is a standard basis, this can be done more
|
| 1177 |
+
# efficiently using Schreyer's theorem
|
| 1178 |
+
|
| 1179 |
+
# First bullet point
|
| 1180 |
+
k = len(self.gens)
|
| 1181 |
+
r = self.rank
|
| 1182 |
+
zero = self.ring.convert(0)
|
| 1183 |
+
one = self.ring.convert(1)
|
| 1184 |
+
Rkr = self.ring.free_module(r + k)
|
| 1185 |
+
newgens = []
|
| 1186 |
+
for j, f in enumerate(self.gens):
|
| 1187 |
+
m = [0]*(r + k)
|
| 1188 |
+
for i, v in enumerate(f):
|
| 1189 |
+
m[i] = v
|
| 1190 |
+
for i in range(k):
|
| 1191 |
+
m[r + i] = one if j == i else zero
|
| 1192 |
+
m = FreeModuleElement(Rkr, tuple(m))
|
| 1193 |
+
newgens.append(m)
|
| 1194 |
+
# Note: we need *descending* order on module index, and TOP=False to
|
| 1195 |
+
# get an elimination order
|
| 1196 |
+
F = Rkr.submodule(*newgens, order='ilex', TOP=False)
|
| 1197 |
+
|
| 1198 |
+
# Second bullet point: standard basis of F
|
| 1199 |
+
G = F._groebner_vec()
|
| 1200 |
+
|
| 1201 |
+
# Third bullet point: G0 = G intersect the new k components
|
| 1202 |
+
G0 = [x[r:] for x in G if all(y == zero for y in x[:r])]
|
| 1203 |
+
|
| 1204 |
+
# Fourth and fifth bullet points: we are done
|
| 1205 |
+
return G0
|
| 1206 |
+
|
| 1207 |
+
def _in_terms_of_generators(self, e):
|
| 1208 |
+
"""Expression in terms of generators. See [SCA, 2.8.1]."""
|
| 1209 |
+
# NOTE: if gens is a standard basis, this can be done more efficiently
|
| 1210 |
+
M = self.ring.free_module(self.rank).submodule(*((e,) + self.gens))
|
| 1211 |
+
S = M.syzygy_module(
|
| 1212 |
+
order="ilex", TOP=False) # We want decreasing order!
|
| 1213 |
+
G = S._groebner_vec()
|
| 1214 |
+
# This list cannot not be empty since e is an element
|
| 1215 |
+
e = [x for x in G if self.ring.is_unit(x[0])][0]
|
| 1216 |
+
return [-x/e[0] for x in e[1:]]
|
| 1217 |
+
|
| 1218 |
+
def reduce_element(self, x, NF=None):
|
| 1219 |
+
"""
|
| 1220 |
+
Reduce the element ``x`` of our container modulo ``self``.
|
| 1221 |
+
|
| 1222 |
+
This applies the normal form ``NF`` to ``x``. If ``NF`` is passed
|
| 1223 |
+
as none, the default Mora normal form is used (which is not unique!).
|
| 1224 |
+
"""
|
| 1225 |
+
from sympy.polys.distributedmodules import sdm_nf_mora
|
| 1226 |
+
if NF is None:
|
| 1227 |
+
NF = sdm_nf_mora
|
| 1228 |
+
return self.container.convert(self.ring._sdm_to_vector(NF(
|
| 1229 |
+
self.ring._vector_to_sdm(x, self.order), self._groebner(),
|
| 1230 |
+
self.order, self.ring.dom),
|
| 1231 |
+
self.rank))
|
| 1232 |
+
|
| 1233 |
+
def _intersect(self, other, relations=False):
|
| 1234 |
+
# See: [SCA, section 2.8.2]
|
| 1235 |
+
fi = self.gens
|
| 1236 |
+
hi = other.gens
|
| 1237 |
+
r = self.rank
|
| 1238 |
+
ci = [[0]*(2*r) for _ in range(r)]
|
| 1239 |
+
for k in range(r):
|
| 1240 |
+
ci[k][k] = 1
|
| 1241 |
+
ci[k][r + k] = 1
|
| 1242 |
+
di = [list(f) + [0]*r for f in fi]
|
| 1243 |
+
ei = [[0]*r + list(h) for h in hi]
|
| 1244 |
+
syz = self.ring.free_module(2*r).submodule(*(ci + di + ei))._syzygies()
|
| 1245 |
+
nonzero = [x for x in syz if any(y != self.ring.zero for y in x[:r])]
|
| 1246 |
+
res = self.container.submodule(*([-y for y in x[:r]] for x in nonzero))
|
| 1247 |
+
reln1 = [x[r:r + len(fi)] for x in nonzero]
|
| 1248 |
+
reln2 = [x[r + len(fi):] for x in nonzero]
|
| 1249 |
+
if relations:
|
| 1250 |
+
return res, reln1, reln2
|
| 1251 |
+
return res
|
| 1252 |
+
|
| 1253 |
+
def _module_quotient(self, other, relations=False):
|
| 1254 |
+
# See: [SCA, section 2.8.4]
|
| 1255 |
+
if relations and len(other.gens) != 1:
|
| 1256 |
+
raise NotImplementedError
|
| 1257 |
+
if len(other.gens) == 0:
|
| 1258 |
+
return self.ring.ideal(1)
|
| 1259 |
+
elif len(other.gens) == 1:
|
| 1260 |
+
# We do some trickery. Let f be the (vector!) generating ``other``
|
| 1261 |
+
# and f1, .., fn be the (vectors) generating self.
|
| 1262 |
+
# Consider the submodule of R^{r+1} generated by (f, 1) and
|
| 1263 |
+
# {(fi, 0) | i}. Then the intersection with the last module
|
| 1264 |
+
# component yields the quotient.
|
| 1265 |
+
g1 = list(other.gens[0]) + [1]
|
| 1266 |
+
gi = [list(x) + [0] for x in self.gens]
|
| 1267 |
+
# NOTE: We *need* to use an elimination order
|
| 1268 |
+
M = self.ring.free_module(self.rank + 1).submodule(*([g1] + gi),
|
| 1269 |
+
order='ilex', TOP=False)
|
| 1270 |
+
if not relations:
|
| 1271 |
+
return self.ring.ideal(*[x[-1] for x in M._groebner_vec() if
|
| 1272 |
+
all(y == self.ring.zero for y in x[:-1])])
|
| 1273 |
+
else:
|
| 1274 |
+
G, R = M._groebner_vec(extended=True)
|
| 1275 |
+
indices = [i for i, x in enumerate(G) if
|
| 1276 |
+
all(y == self.ring.zero for y in x[:-1])]
|
| 1277 |
+
return (self.ring.ideal(*[G[i][-1] for i in indices]),
|
| 1278 |
+
[[-x for x in R[i][1:]] for i in indices])
|
| 1279 |
+
# For more generators, we use I : <h1, .., hn> = intersection of
|
| 1280 |
+
# {I : <hi> | i}
|
| 1281 |
+
# TODO this can be done more efficiently
|
| 1282 |
+
return reduce(lambda x, y: x.intersect(y),
|
| 1283 |
+
(self._module_quotient(self.container.submodule(x)) for x in other.gens))
|
| 1284 |
+
|
| 1285 |
+
|
| 1286 |
+
class SubModuleQuotientRing(SubModule):
|
| 1287 |
+
"""
|
| 1288 |
+
Class for submodules of free modules over quotient rings.
|
| 1289 |
+
|
| 1290 |
+
Do not instantiate this. Instead use the submodule methods.
|
| 1291 |
+
|
| 1292 |
+
>>> from sympy.abc import x, y
|
| 1293 |
+
>>> from sympy import QQ
|
| 1294 |
+
>>> M = (QQ.old_poly_ring(x, y)/[x**2 - y**2]).free_module(2).submodule([x, x + y])
|
| 1295 |
+
>>> M
|
| 1296 |
+
<[x + <x**2 - y**2>, x + y + <x**2 - y**2>]>
|
| 1297 |
+
>>> M.contains([y**2, x**2 + x*y])
|
| 1298 |
+
True
|
| 1299 |
+
>>> M.contains([x, y])
|
| 1300 |
+
False
|
| 1301 |
+
|
| 1302 |
+
Attributes:
|
| 1303 |
+
|
| 1304 |
+
- quot - the subquotient of `R^n/IR^n` generated by lifts of our generators
|
| 1305 |
+
"""
|
| 1306 |
+
|
| 1307 |
+
def __init__(self, gens, container):
|
| 1308 |
+
SubModule.__init__(self, gens, container)
|
| 1309 |
+
self.quot = self.container.quot.submodule(
|
| 1310 |
+
*[self.container.lift(x) for x in self.gens])
|
| 1311 |
+
|
| 1312 |
+
def _contains(self, elem):
|
| 1313 |
+
return self.quot._contains(self.container.lift(elem))
|
| 1314 |
+
|
| 1315 |
+
def _syzygies(self):
|
| 1316 |
+
return [tuple(self.ring.convert(y, self.quot.ring) for y in x)
|
| 1317 |
+
for x in self.quot._syzygies()]
|
| 1318 |
+
|
| 1319 |
+
def _in_terms_of_generators(self, elem):
|
| 1320 |
+
return [self.ring.convert(x, self.quot.ring) for x in
|
| 1321 |
+
self.quot._in_terms_of_generators(self.container.lift(elem))]
|
| 1322 |
+
|
| 1323 |
+
##########################################################################
|
| 1324 |
+
## Quotient Modules ######################################################
|
| 1325 |
+
##########################################################################
|
| 1326 |
+
|
| 1327 |
+
|
| 1328 |
+
class QuotientModuleElement(ModuleElement):
|
| 1329 |
+
"""Element of a quotient module."""
|
| 1330 |
+
|
| 1331 |
+
def eq(self, d1, d2):
|
| 1332 |
+
"""Equality comparison."""
|
| 1333 |
+
return self.module.killed_module.contains(d1 - d2)
|
| 1334 |
+
|
| 1335 |
+
def __repr__(self):
|
| 1336 |
+
return repr(self.data) + " + " + repr(self.module.killed_module)
|
| 1337 |
+
|
| 1338 |
+
|
| 1339 |
+
class QuotientModule(Module):
|
| 1340 |
+
"""
|
| 1341 |
+
Class for quotient modules.
|
| 1342 |
+
|
| 1343 |
+
Do not instantiate this directly. For subquotients, see the
|
| 1344 |
+
SubQuotientModule class.
|
| 1345 |
+
|
| 1346 |
+
Attributes:
|
| 1347 |
+
|
| 1348 |
+
- base - the base module we are a quotient of
|
| 1349 |
+
- killed_module - the submodule used to form the quotient
|
| 1350 |
+
- rank of the base
|
| 1351 |
+
"""
|
| 1352 |
+
|
| 1353 |
+
dtype = QuotientModuleElement
|
| 1354 |
+
|
| 1355 |
+
def __init__(self, ring, base, submodule):
|
| 1356 |
+
Module.__init__(self, ring)
|
| 1357 |
+
if not base.is_submodule(submodule):
|
| 1358 |
+
raise ValueError('%s is not a submodule of %s' % (submodule, base))
|
| 1359 |
+
self.base = base
|
| 1360 |
+
self.killed_module = submodule
|
| 1361 |
+
self.rank = base.rank
|
| 1362 |
+
|
| 1363 |
+
def __repr__(self):
|
| 1364 |
+
return repr(self.base) + "/" + repr(self.killed_module)
|
| 1365 |
+
|
| 1366 |
+
def is_zero(self):
|
| 1367 |
+
"""
|
| 1368 |
+
Return True if ``self`` is a zero module.
|
| 1369 |
+
|
| 1370 |
+
This happens if and only if the base module is the same as the
|
| 1371 |
+
submodule being killed.
|
| 1372 |
+
|
| 1373 |
+
Examples
|
| 1374 |
+
========
|
| 1375 |
+
|
| 1376 |
+
>>> from sympy.abc import x
|
| 1377 |
+
>>> from sympy import QQ
|
| 1378 |
+
>>> F = QQ.old_poly_ring(x).free_module(2)
|
| 1379 |
+
>>> (F/[(1, 0)]).is_zero()
|
| 1380 |
+
False
|
| 1381 |
+
>>> (F/[(1, 0), (0, 1)]).is_zero()
|
| 1382 |
+
True
|
| 1383 |
+
"""
|
| 1384 |
+
return self.base == self.killed_module
|
| 1385 |
+
|
| 1386 |
+
def is_submodule(self, other):
|
| 1387 |
+
"""
|
| 1388 |
+
Return True if ``other`` is a submodule of ``self``.
|
| 1389 |
+
|
| 1390 |
+
Examples
|
| 1391 |
+
========
|
| 1392 |
+
|
| 1393 |
+
>>> from sympy.abc import x
|
| 1394 |
+
>>> from sympy import QQ
|
| 1395 |
+
>>> Q = QQ.old_poly_ring(x).free_module(2) / [(x, x)]
|
| 1396 |
+
>>> S = Q.submodule([1, 0])
|
| 1397 |
+
>>> Q.is_submodule(S)
|
| 1398 |
+
True
|
| 1399 |
+
>>> S.is_submodule(Q)
|
| 1400 |
+
False
|
| 1401 |
+
"""
|
| 1402 |
+
if isinstance(other, QuotientModule):
|
| 1403 |
+
return self.killed_module == other.killed_module and \
|
| 1404 |
+
self.base.is_submodule(other.base)
|
| 1405 |
+
if isinstance(other, SubQuotientModule):
|
| 1406 |
+
return other.container == self
|
| 1407 |
+
return False
|
| 1408 |
+
|
| 1409 |
+
def submodule(self, *gens, **opts):
|
| 1410 |
+
"""
|
| 1411 |
+
Generate a submodule.
|
| 1412 |
+
|
| 1413 |
+
This is the same as taking a quotient of a submodule of the base
|
| 1414 |
+
module.
|
| 1415 |
+
|
| 1416 |
+
Examples
|
| 1417 |
+
========
|
| 1418 |
+
|
| 1419 |
+
>>> from sympy.abc import x
|
| 1420 |
+
>>> from sympy import QQ
|
| 1421 |
+
>>> Q = QQ.old_poly_ring(x).free_module(2) / [(x, x)]
|
| 1422 |
+
>>> Q.submodule([x, 0])
|
| 1423 |
+
<[x, 0] + <[x, x]>>
|
| 1424 |
+
"""
|
| 1425 |
+
return SubQuotientModule(gens, self, **opts)
|
| 1426 |
+
|
| 1427 |
+
def convert(self, elem, M=None):
|
| 1428 |
+
"""
|
| 1429 |
+
Convert ``elem`` into the internal representation.
|
| 1430 |
+
|
| 1431 |
+
This method is called implicitly whenever computations involve elements
|
| 1432 |
+
not in the internal representation.
|
| 1433 |
+
|
| 1434 |
+
Examples
|
| 1435 |
+
========
|
| 1436 |
+
|
| 1437 |
+
>>> from sympy.abc import x
|
| 1438 |
+
>>> from sympy import QQ
|
| 1439 |
+
>>> F = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)]
|
| 1440 |
+
>>> F.convert([1, 0])
|
| 1441 |
+
[1, 0] + <[1, 2], [1, x]>
|
| 1442 |
+
"""
|
| 1443 |
+
if isinstance(elem, QuotientModuleElement):
|
| 1444 |
+
if elem.module is self:
|
| 1445 |
+
return elem
|
| 1446 |
+
if self.killed_module.is_submodule(elem.module.killed_module):
|
| 1447 |
+
return QuotientModuleElement(self, self.base.convert(elem.data))
|
| 1448 |
+
raise CoercionFailed
|
| 1449 |
+
return QuotientModuleElement(self, self.base.convert(elem))
|
| 1450 |
+
|
| 1451 |
+
def identity_hom(self):
|
| 1452 |
+
"""
|
| 1453 |
+
Return the identity homomorphism on ``self``.
|
| 1454 |
+
|
| 1455 |
+
Examples
|
| 1456 |
+
========
|
| 1457 |
+
|
| 1458 |
+
>>> from sympy.abc import x
|
| 1459 |
+
>>> from sympy import QQ
|
| 1460 |
+
>>> M = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)]
|
| 1461 |
+
>>> M.identity_hom()
|
| 1462 |
+
Matrix([
|
| 1463 |
+
[1, 0], : QQ[x]**2/<[1, 2], [1, x]> -> QQ[x]**2/<[1, 2], [1, x]>
|
| 1464 |
+
[0, 1]])
|
| 1465 |
+
"""
|
| 1466 |
+
return self.base.identity_hom().quotient_codomain(
|
| 1467 |
+
self.killed_module).quotient_domain(self.killed_module)
|
| 1468 |
+
|
| 1469 |
+
def quotient_hom(self):
|
| 1470 |
+
"""
|
| 1471 |
+
Return the quotient homomorphism to ``self``.
|
| 1472 |
+
|
| 1473 |
+
That is, return a homomorphism representing the natural map from
|
| 1474 |
+
``self.base`` to ``self``.
|
| 1475 |
+
|
| 1476 |
+
Examples
|
| 1477 |
+
========
|
| 1478 |
+
|
| 1479 |
+
>>> from sympy.abc import x
|
| 1480 |
+
>>> from sympy import QQ
|
| 1481 |
+
>>> M = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)]
|
| 1482 |
+
>>> M.quotient_hom()
|
| 1483 |
+
Matrix([
|
| 1484 |
+
[1, 0], : QQ[x]**2 -> QQ[x]**2/<[1, 2], [1, x]>
|
| 1485 |
+
[0, 1]])
|
| 1486 |
+
"""
|
| 1487 |
+
return self.base.identity_hom().quotient_codomain(
|
| 1488 |
+
self.killed_module)
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/tests/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_extensions.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.symbol import symbols
|
| 2 |
+
from sympy.functions.elementary.trigonometric import (cos, sin)
|
| 3 |
+
from sympy.polys import QQ, ZZ
|
| 4 |
+
from sympy.polys.polytools import Poly
|
| 5 |
+
from sympy.polys.polyerrors import NotInvertible
|
| 6 |
+
from sympy.polys.agca.extensions import FiniteExtension
|
| 7 |
+
from sympy.polys.domainmatrix import DomainMatrix
|
| 8 |
+
|
| 9 |
+
from sympy.testing.pytest import raises
|
| 10 |
+
|
| 11 |
+
from sympy.abc import x, y, t
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_FiniteExtension():
|
| 15 |
+
# Gaussian integers
|
| 16 |
+
A = FiniteExtension(Poly(x**2 + 1, x))
|
| 17 |
+
assert A.rank == 2
|
| 18 |
+
assert str(A) == 'ZZ[x]/(x**2 + 1)'
|
| 19 |
+
i = A.generator
|
| 20 |
+
assert i.parent() is A
|
| 21 |
+
|
| 22 |
+
assert i*i == A(-1)
|
| 23 |
+
raises(TypeError, lambda: i*())
|
| 24 |
+
|
| 25 |
+
assert A.basis == (A.one, i)
|
| 26 |
+
assert A(1) == A.one
|
| 27 |
+
assert i**2 == A(-1)
|
| 28 |
+
assert i**2 != -1 # no coercion
|
| 29 |
+
assert (2 + i)*(1 - i) == 3 - i
|
| 30 |
+
assert (1 + i)**8 == A(16)
|
| 31 |
+
assert A(1).inverse() == A(1)
|
| 32 |
+
raises(NotImplementedError, lambda: A(2).inverse())
|
| 33 |
+
|
| 34 |
+
# Finite field of order 27
|
| 35 |
+
F = FiniteExtension(Poly(x**3 - x + 1, x, modulus=3))
|
| 36 |
+
assert F.rank == 3
|
| 37 |
+
a = F.generator # also generates the cyclic group F - {0}
|
| 38 |
+
assert F.basis == (F(1), a, a**2)
|
| 39 |
+
assert a**27 == a
|
| 40 |
+
assert a**26 == F(1)
|
| 41 |
+
assert a**13 == F(-1)
|
| 42 |
+
assert a**9 == a + 1
|
| 43 |
+
assert a**3 == a - 1
|
| 44 |
+
assert a**6 == a**2 + a + 1
|
| 45 |
+
assert F(x**2 + x).inverse() == 1 - a
|
| 46 |
+
assert F(x + 2)**(-1) == F(x + 2).inverse()
|
| 47 |
+
assert a**19 * a**(-19) == F(1)
|
| 48 |
+
assert (a - 1) / (2*a**2 - 1) == a**2 + 1
|
| 49 |
+
assert (a - 1) // (2*a**2 - 1) == a**2 + 1
|
| 50 |
+
assert 2/(a**2 + 1) == a**2 - a + 1
|
| 51 |
+
assert (a**2 + 1)/2 == -a**2 - 1
|
| 52 |
+
raises(NotInvertible, lambda: F(0).inverse())
|
| 53 |
+
|
| 54 |
+
# Function field of an elliptic curve
|
| 55 |
+
K = FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True))
|
| 56 |
+
assert K.rank == 2
|
| 57 |
+
assert str(K) == 'ZZ(x)[t]/(t**2 - x**3 - x + 1)'
|
| 58 |
+
y = K.generator
|
| 59 |
+
c = 1/(x**3 - x**2 + x - 1)
|
| 60 |
+
assert ((y + x)*(y - x)).inverse() == K(c)
|
| 61 |
+
assert (y + x)*(y - x)*c == K(1) # explicit inverse of y + x
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def test_FiniteExtension_eq_hash():
|
| 65 |
+
# Test eq and hash
|
| 66 |
+
p1 = Poly(x**2 - 2, x, domain=ZZ)
|
| 67 |
+
p2 = Poly(x**2 - 2, x, domain=QQ)
|
| 68 |
+
K1 = FiniteExtension(p1)
|
| 69 |
+
K2 = FiniteExtension(p2)
|
| 70 |
+
assert K1 == FiniteExtension(Poly(x**2 - 2))
|
| 71 |
+
assert K2 != FiniteExtension(Poly(x**2 - 2))
|
| 72 |
+
assert len({K1, K2, FiniteExtension(p1)}) == 2
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def test_FiniteExtension_mod():
|
| 76 |
+
# Test mod
|
| 77 |
+
K = FiniteExtension(Poly(x**3 + 1, x, domain=QQ))
|
| 78 |
+
xf = K(x)
|
| 79 |
+
assert (xf**2 - 1) % 1 == K.zero
|
| 80 |
+
assert 1 % (xf**2 - 1) == K.zero
|
| 81 |
+
assert (xf**2 - 1) / (xf - 1) == xf + 1
|
| 82 |
+
assert (xf**2 - 1) // (xf - 1) == xf + 1
|
| 83 |
+
assert (xf**2 - 1) % (xf - 1) == K.zero
|
| 84 |
+
raises(ZeroDivisionError, lambda: (xf**2 - 1) % 0)
|
| 85 |
+
raises(TypeError, lambda: xf % [])
|
| 86 |
+
raises(TypeError, lambda: [] % xf)
|
| 87 |
+
|
| 88 |
+
# Test mod over ring
|
| 89 |
+
K = FiniteExtension(Poly(x**3 + 1, x, domain=ZZ))
|
| 90 |
+
xf = K(x)
|
| 91 |
+
assert (xf**2 - 1) % 1 == K.zero
|
| 92 |
+
raises(NotImplementedError, lambda: (xf**2 - 1) % (xf - 1))
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def test_FiniteExtension_from_sympy():
|
| 96 |
+
# Test to_sympy/from_sympy
|
| 97 |
+
K = FiniteExtension(Poly(x**3 + 1, x, domain=ZZ))
|
| 98 |
+
xf = K(x)
|
| 99 |
+
assert K.from_sympy(x) == xf
|
| 100 |
+
assert K.to_sympy(xf) == x
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def test_FiniteExtension_set_domain():
|
| 104 |
+
KZ = FiniteExtension(Poly(x**2 + 1, x, domain='ZZ'))
|
| 105 |
+
KQ = FiniteExtension(Poly(x**2 + 1, x, domain='QQ'))
|
| 106 |
+
assert KZ.set_domain(QQ) == KQ
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def test_FiniteExtension_exquo():
|
| 110 |
+
# Test exquo
|
| 111 |
+
K = FiniteExtension(Poly(x**4 + 1))
|
| 112 |
+
xf = K(x)
|
| 113 |
+
assert K.exquo(xf**2 - 1, xf - 1) == xf + 1
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def test_FiniteExtension_convert():
|
| 117 |
+
# Test from_MonogenicFiniteExtension
|
| 118 |
+
K1 = FiniteExtension(Poly(x**2 + 1))
|
| 119 |
+
K2 = QQ[x]
|
| 120 |
+
x1, x2 = K1(x), K2(x)
|
| 121 |
+
assert K1.convert(x2) == x1
|
| 122 |
+
assert K2.convert(x1) == x2
|
| 123 |
+
|
| 124 |
+
K = FiniteExtension(Poly(x**2 - 1, domain=QQ))
|
| 125 |
+
assert K.convert_from(QQ(1, 2), QQ) == K.one/2
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def test_FiniteExtension_division_ring():
|
| 129 |
+
# Test division in FiniteExtension over a ring
|
| 130 |
+
KQ = FiniteExtension(Poly(x**2 - 1, x, domain=QQ))
|
| 131 |
+
KZ = FiniteExtension(Poly(x**2 - 1, x, domain=ZZ))
|
| 132 |
+
KQt = FiniteExtension(Poly(x**2 - 1, x, domain=QQ[t]))
|
| 133 |
+
KQtf = FiniteExtension(Poly(x**2 - 1, x, domain=QQ.frac_field(t)))
|
| 134 |
+
assert KQ.is_Field is True
|
| 135 |
+
assert KZ.is_Field is False
|
| 136 |
+
assert KQt.is_Field is False
|
| 137 |
+
assert KQtf.is_Field is True
|
| 138 |
+
for K in KQ, KZ, KQt, KQtf:
|
| 139 |
+
xK = K.convert(x)
|
| 140 |
+
assert xK / K.one == xK
|
| 141 |
+
assert xK // K.one == xK
|
| 142 |
+
assert xK % K.one == K.zero
|
| 143 |
+
raises(ZeroDivisionError, lambda: xK / K.zero)
|
| 144 |
+
raises(ZeroDivisionError, lambda: xK // K.zero)
|
| 145 |
+
raises(ZeroDivisionError, lambda: xK % K.zero)
|
| 146 |
+
if K.is_Field:
|
| 147 |
+
assert xK / xK == K.one
|
| 148 |
+
assert xK // xK == K.one
|
| 149 |
+
assert xK % xK == K.zero
|
| 150 |
+
else:
|
| 151 |
+
raises(NotImplementedError, lambda: xK / xK)
|
| 152 |
+
raises(NotImplementedError, lambda: xK // xK)
|
| 153 |
+
raises(NotImplementedError, lambda: xK % xK)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def test_FiniteExtension_Poly():
|
| 157 |
+
K = FiniteExtension(Poly(x**2 - 2))
|
| 158 |
+
p = Poly(x, y, domain=K)
|
| 159 |
+
assert p.domain == K
|
| 160 |
+
assert p.as_expr() == x
|
| 161 |
+
assert (p**2).as_expr() == 2
|
| 162 |
+
|
| 163 |
+
K = FiniteExtension(Poly(x**2 - 2, x, domain=QQ))
|
| 164 |
+
K2 = FiniteExtension(Poly(t**2 - 2, t, domain=K))
|
| 165 |
+
assert str(K2) == 'QQ[x]/(x**2 - 2)[t]/(t**2 - 2)'
|
| 166 |
+
|
| 167 |
+
eK = K2.convert(x + t)
|
| 168 |
+
assert K2.to_sympy(eK) == x + t
|
| 169 |
+
assert K2.to_sympy(eK ** 2) == 4 + 2*x*t
|
| 170 |
+
p = Poly(x + t, y, domain=K2)
|
| 171 |
+
assert p**2 == Poly(4 + 2*x*t, y, domain=K2)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def test_FiniteExtension_sincos_jacobian():
|
| 175 |
+
# Use FiniteExtensino to compute the Jacobian of a matrix involving sin
|
| 176 |
+
# and cos of different symbols.
|
| 177 |
+
r, p, t = symbols('rho, phi, theta')
|
| 178 |
+
elements = [
|
| 179 |
+
[sin(p)*cos(t), r*cos(p)*cos(t), -r*sin(p)*sin(t)],
|
| 180 |
+
[sin(p)*sin(t), r*cos(p)*sin(t), r*sin(p)*cos(t)],
|
| 181 |
+
[ cos(p), -r*sin(p), 0],
|
| 182 |
+
]
|
| 183 |
+
|
| 184 |
+
def make_extension(K):
|
| 185 |
+
K = FiniteExtension(Poly(sin(p)**2+cos(p)**2-1, sin(p), domain=K[cos(p)]))
|
| 186 |
+
K = FiniteExtension(Poly(sin(t)**2+cos(t)**2-1, sin(t), domain=K[cos(t)]))
|
| 187 |
+
return K
|
| 188 |
+
|
| 189 |
+
Ksc1 = make_extension(ZZ[r])
|
| 190 |
+
Ksc2 = make_extension(ZZ)[r]
|
| 191 |
+
|
| 192 |
+
for K in [Ksc1, Ksc2]:
|
| 193 |
+
elements_K = [[K.convert(e) for e in row] for row in elements]
|
| 194 |
+
J = DomainMatrix(elements_K, (3, 3), K)
|
| 195 |
+
det = J.charpoly()[-1] * (-K.one)**3
|
| 196 |
+
assert det == K.convert(r**2*sin(p))
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_homomorphisms.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for homomorphisms."""
|
| 2 |
+
|
| 3 |
+
from sympy.core.singleton import S
|
| 4 |
+
from sympy.polys.domains.rationalfield import QQ
|
| 5 |
+
from sympy.abc import x, y
|
| 6 |
+
from sympy.polys.agca import homomorphism
|
| 7 |
+
from sympy.testing.pytest import raises
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def test_printing():
|
| 11 |
+
R = QQ.old_poly_ring(x)
|
| 12 |
+
|
| 13 |
+
assert str(homomorphism(R.free_module(1), R.free_module(1), [0])) == \
|
| 14 |
+
'Matrix([[0]]) : QQ[x]**1 -> QQ[x]**1'
|
| 15 |
+
assert str(homomorphism(R.free_module(2), R.free_module(2), [0, 0])) == \
|
| 16 |
+
'Matrix([ \n[0, 0], : QQ[x]**2 -> QQ[x]**2\n[0, 0]]) '
|
| 17 |
+
assert str(homomorphism(R.free_module(1), R.free_module(1) / [[x]], [0])) == \
|
| 18 |
+
'Matrix([[0]]) : QQ[x]**1 -> QQ[x]**1/<[x]>'
|
| 19 |
+
assert str(R.free_module(0).identity_hom()) == 'Matrix(0, 0, []) : QQ[x]**0 -> QQ[x]**0'
|
| 20 |
+
|
| 21 |
+
def test_operations():
|
| 22 |
+
F = QQ.old_poly_ring(x).free_module(2)
|
| 23 |
+
G = QQ.old_poly_ring(x).free_module(3)
|
| 24 |
+
f = F.identity_hom()
|
| 25 |
+
g = homomorphism(F, F, [0, [1, x]])
|
| 26 |
+
h = homomorphism(F, F, [[1, 0], 0])
|
| 27 |
+
i = homomorphism(F, G, [[1, 0, 0], [0, 1, 0]])
|
| 28 |
+
|
| 29 |
+
assert f == f
|
| 30 |
+
assert f != g
|
| 31 |
+
assert f != i
|
| 32 |
+
assert (f != F.identity_hom()) is False
|
| 33 |
+
assert 2*f == f*2 == homomorphism(F, F, [[2, 0], [0, 2]])
|
| 34 |
+
assert f/2 == homomorphism(F, F, [[S.Half, 0], [0, S.Half]])
|
| 35 |
+
assert f + g == homomorphism(F, F, [[1, 0], [1, x + 1]])
|
| 36 |
+
assert f - g == homomorphism(F, F, [[1, 0], [-1, 1 - x]])
|
| 37 |
+
assert f*g == g == g*f
|
| 38 |
+
assert h*g == homomorphism(F, F, [0, [1, 0]])
|
| 39 |
+
assert g*h == homomorphism(F, F, [0, 0])
|
| 40 |
+
assert i*f == i
|
| 41 |
+
assert f([1, 2]) == [1, 2]
|
| 42 |
+
assert g([1, 2]) == [2, 2*x]
|
| 43 |
+
|
| 44 |
+
assert i.restrict_domain(F.submodule([x, x]))([x, x]) == i([x, x])
|
| 45 |
+
h1 = h.quotient_domain(F.submodule([0, 1]))
|
| 46 |
+
assert h1([1, 0]) == h([1, 0])
|
| 47 |
+
assert h1.restrict_domain(h1.domain.submodule([x, 0]))([x, 0]) == h([x, 0])
|
| 48 |
+
|
| 49 |
+
raises(TypeError, lambda: f/g)
|
| 50 |
+
raises(TypeError, lambda: f + 1)
|
| 51 |
+
raises(TypeError, lambda: f + i)
|
| 52 |
+
raises(TypeError, lambda: f - 1)
|
| 53 |
+
raises(TypeError, lambda: f*i)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def test_creation():
|
| 57 |
+
F = QQ.old_poly_ring(x).free_module(3)
|
| 58 |
+
G = QQ.old_poly_ring(x).free_module(2)
|
| 59 |
+
SM = F.submodule([1, 1, 1])
|
| 60 |
+
Q = F / SM
|
| 61 |
+
SQ = Q.submodule([1, 0, 0])
|
| 62 |
+
|
| 63 |
+
matrix = [[1, 0], [0, 1], [-1, -1]]
|
| 64 |
+
h = homomorphism(F, G, matrix)
|
| 65 |
+
h2 = homomorphism(Q, G, matrix)
|
| 66 |
+
assert h.quotient_domain(SM) == h2
|
| 67 |
+
raises(ValueError, lambda: h.quotient_domain(F.submodule([1, 0, 0])))
|
| 68 |
+
assert h2.restrict_domain(SQ) == homomorphism(SQ, G, matrix)
|
| 69 |
+
raises(ValueError, lambda: h.restrict_domain(G))
|
| 70 |
+
raises(ValueError, lambda: h.restrict_codomain(G.submodule([1, 0])))
|
| 71 |
+
raises(ValueError, lambda: h.quotient_codomain(F))
|
| 72 |
+
|
| 73 |
+
im = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
|
| 74 |
+
for M in [F, SM, Q, SQ]:
|
| 75 |
+
assert M.identity_hom() == homomorphism(M, M, im)
|
| 76 |
+
assert SM.inclusion_hom() == homomorphism(SM, F, im)
|
| 77 |
+
assert SQ.inclusion_hom() == homomorphism(SQ, Q, im)
|
| 78 |
+
assert Q.quotient_hom() == homomorphism(F, Q, im)
|
| 79 |
+
assert SQ.quotient_hom() == homomorphism(SQ.base, SQ, im)
|
| 80 |
+
|
| 81 |
+
class conv:
|
| 82 |
+
def convert(x, y=None):
|
| 83 |
+
return x
|
| 84 |
+
|
| 85 |
+
class dummy:
|
| 86 |
+
container = conv()
|
| 87 |
+
|
| 88 |
+
def submodule(*args):
|
| 89 |
+
return None
|
| 90 |
+
raises(TypeError, lambda: homomorphism(dummy(), G, matrix))
|
| 91 |
+
raises(TypeError, lambda: homomorphism(F, dummy(), matrix))
|
| 92 |
+
raises(
|
| 93 |
+
ValueError, lambda: homomorphism(QQ.old_poly_ring(x, y).free_module(3), G, matrix))
|
| 94 |
+
raises(ValueError, lambda: homomorphism(F, G, [0, 0]))
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def test_properties():
|
| 98 |
+
R = QQ.old_poly_ring(x, y)
|
| 99 |
+
F = R.free_module(2)
|
| 100 |
+
h = homomorphism(F, F, [[x, 0], [y, 0]])
|
| 101 |
+
assert h.kernel() == F.submodule([-y, x])
|
| 102 |
+
assert h.image() == F.submodule([x, 0], [y, 0])
|
| 103 |
+
assert not h.is_injective()
|
| 104 |
+
assert not h.is_surjective()
|
| 105 |
+
assert h.restrict_codomain(h.image()).is_surjective()
|
| 106 |
+
assert h.restrict_domain(F.submodule([1, 0])).is_injective()
|
| 107 |
+
assert h.quotient_domain(
|
| 108 |
+
h.kernel()).restrict_codomain(h.image()).is_isomorphism()
|
| 109 |
+
|
| 110 |
+
R2 = QQ.old_poly_ring(x, y, order=(("lex", x), ("ilex", y))) / [x**2 + 1]
|
| 111 |
+
F = R2.free_module(2)
|
| 112 |
+
h = homomorphism(F, F, [[x, 0], [y, y + 1]])
|
| 113 |
+
assert h.is_isomorphism()
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_ideals.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Test ideals.py code."""
|
| 2 |
+
|
| 3 |
+
from sympy.polys import QQ, ilex
|
| 4 |
+
from sympy.abc import x, y, z
|
| 5 |
+
from sympy.testing.pytest import raises
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_ideal_operations():
|
| 9 |
+
R = QQ.old_poly_ring(x, y)
|
| 10 |
+
I = R.ideal(x)
|
| 11 |
+
J = R.ideal(y)
|
| 12 |
+
S = R.ideal(x*y)
|
| 13 |
+
T = R.ideal(x, y)
|
| 14 |
+
|
| 15 |
+
assert not (I == J)
|
| 16 |
+
assert I == I
|
| 17 |
+
|
| 18 |
+
assert I.union(J) == T
|
| 19 |
+
assert I + J == T
|
| 20 |
+
assert I + T == T
|
| 21 |
+
|
| 22 |
+
assert not I.subset(T)
|
| 23 |
+
assert T.subset(I)
|
| 24 |
+
|
| 25 |
+
assert I.product(J) == S
|
| 26 |
+
assert I*J == S
|
| 27 |
+
assert x*J == S
|
| 28 |
+
assert I*y == S
|
| 29 |
+
assert R.convert(x)*J == S
|
| 30 |
+
assert I*R.convert(y) == S
|
| 31 |
+
|
| 32 |
+
assert not I.is_zero()
|
| 33 |
+
assert not J.is_whole_ring()
|
| 34 |
+
|
| 35 |
+
assert R.ideal(x**2 + 1, x).is_whole_ring()
|
| 36 |
+
assert R.ideal() == R.ideal(0)
|
| 37 |
+
assert R.ideal().is_zero()
|
| 38 |
+
|
| 39 |
+
assert T.contains(x*y)
|
| 40 |
+
assert T.subset([x, y])
|
| 41 |
+
|
| 42 |
+
assert T.in_terms_of_generators(x) == [R(1), R(0)]
|
| 43 |
+
|
| 44 |
+
assert T**0 == R.ideal(1)
|
| 45 |
+
assert T**1 == T
|
| 46 |
+
assert T**2 == R.ideal(x**2, y**2, x*y)
|
| 47 |
+
assert I**5 == R.ideal(x**5)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_exceptions():
|
| 51 |
+
I = QQ.old_poly_ring(x).ideal(x)
|
| 52 |
+
J = QQ.old_poly_ring(y).ideal(1)
|
| 53 |
+
raises(ValueError, lambda: I.union(x))
|
| 54 |
+
raises(ValueError, lambda: I + J)
|
| 55 |
+
raises(ValueError, lambda: I * J)
|
| 56 |
+
raises(ValueError, lambda: I.union(J))
|
| 57 |
+
assert (I == J) is False
|
| 58 |
+
assert I != J
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def test_nontriv_global():
|
| 62 |
+
R = QQ.old_poly_ring(x, y, z)
|
| 63 |
+
|
| 64 |
+
def contains(I, f):
|
| 65 |
+
return R.ideal(*I).contains(f)
|
| 66 |
+
|
| 67 |
+
assert contains([x, y], x)
|
| 68 |
+
assert contains([x, y], x + y)
|
| 69 |
+
assert not contains([x, y], 1)
|
| 70 |
+
assert not contains([x, y], z)
|
| 71 |
+
assert contains([x**2 + y, x**2 + x], x - y)
|
| 72 |
+
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2)
|
| 73 |
+
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**3)
|
| 74 |
+
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4)
|
| 75 |
+
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y**2)
|
| 76 |
+
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4 + y**3 + 2*z*y*x)
|
| 77 |
+
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y*z)
|
| 78 |
+
assert contains([x, 1 + x + y, 5 - 7*y], 1)
|
| 79 |
+
assert contains(
|
| 80 |
+
[x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z],
|
| 81 |
+
x**3)
|
| 82 |
+
assert not contains(
|
| 83 |
+
[x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z],
|
| 84 |
+
x**2 + y**2)
|
| 85 |
+
|
| 86 |
+
# compare local order
|
| 87 |
+
assert not contains([x*(1 + x + y), y*(1 + z)], x)
|
| 88 |
+
assert not contains([x*(1 + x + y), y*(1 + z)], x + y)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def test_nontriv_local():
|
| 92 |
+
R = QQ.old_poly_ring(x, y, z, order=ilex)
|
| 93 |
+
|
| 94 |
+
def contains(I, f):
|
| 95 |
+
return R.ideal(*I).contains(f)
|
| 96 |
+
|
| 97 |
+
assert contains([x, y], x)
|
| 98 |
+
assert contains([x, y], x + y)
|
| 99 |
+
assert not contains([x, y], 1)
|
| 100 |
+
assert not contains([x, y], z)
|
| 101 |
+
assert contains([x**2 + y, x**2 + x], x - y)
|
| 102 |
+
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2)
|
| 103 |
+
assert contains([x*(1 + x + y), y*(1 + z)], x)
|
| 104 |
+
assert contains([x*(1 + x + y), y*(1 + z)], x + y)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def test_intersection():
|
| 108 |
+
R = QQ.old_poly_ring(x, y, z)
|
| 109 |
+
# SCA, example 1.8.11
|
| 110 |
+
assert R.ideal(x, y).intersect(R.ideal(y**2, z)) == R.ideal(y**2, y*z, x*z)
|
| 111 |
+
|
| 112 |
+
assert R.ideal(x, y).intersect(R.ideal()).is_zero()
|
| 113 |
+
|
| 114 |
+
R = QQ.old_poly_ring(x, y, z, order="ilex")
|
| 115 |
+
assert R.ideal(x, y).intersect(R.ideal(y**2 + y**2*z, z + z*x**3*y)) == \
|
| 116 |
+
R.ideal(y**2, y*z, x*z)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def test_quotient():
|
| 120 |
+
# SCA, example 1.8.13
|
| 121 |
+
R = QQ.old_poly_ring(x, y, z)
|
| 122 |
+
assert R.ideal(x, y).quotient(R.ideal(y**2, z)) == R.ideal(x, y)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def test_reduction():
|
| 126 |
+
from sympy.polys.distributedmodules import sdm_nf_buchberger_reduced
|
| 127 |
+
R = QQ.old_poly_ring(x, y)
|
| 128 |
+
I = R.ideal(x**5, y)
|
| 129 |
+
e = R.convert(x**3 + y**2)
|
| 130 |
+
assert I.reduce_element(e) == e
|
| 131 |
+
assert I.reduce_element(e, NF=sdm_nf_buchberger_reduced) == R.convert(x**3)
|
.venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_modules.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Test modules.py code."""
|
| 2 |
+
|
| 3 |
+
from sympy.polys.agca.modules import FreeModule, ModuleOrder, FreeModulePolyRing
|
| 4 |
+
from sympy.polys import CoercionFailed, QQ, lex, grlex, ilex, ZZ
|
| 5 |
+
from sympy.abc import x, y, z
|
| 6 |
+
from sympy.testing.pytest import raises
|
| 7 |
+
from sympy.core.numbers import Rational
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def test_FreeModuleElement():
|
| 11 |
+
M = QQ.old_poly_ring(x).free_module(3)
|
| 12 |
+
e = M.convert([1, x, x**2])
|
| 13 |
+
f = [QQ.old_poly_ring(x).convert(1), QQ.old_poly_ring(x).convert(x), QQ.old_poly_ring(x).convert(x**2)]
|
| 14 |
+
assert list(e) == f
|
| 15 |
+
assert f[0] == e[0]
|
| 16 |
+
assert f[1] == e[1]
|
| 17 |
+
assert f[2] == e[2]
|
| 18 |
+
raises(IndexError, lambda: e[3])
|
| 19 |
+
|
| 20 |
+
g = M.convert([x, 0, 0])
|
| 21 |
+
assert e + g == M.convert([x + 1, x, x**2])
|
| 22 |
+
assert f + g == M.convert([x + 1, x, x**2])
|
| 23 |
+
assert -e == M.convert([-1, -x, -x**2])
|
| 24 |
+
assert e - g == M.convert([1 - x, x, x**2])
|
| 25 |
+
assert e != g
|
| 26 |
+
|
| 27 |
+
assert M.convert([x, x, x]) / QQ.old_poly_ring(x).convert(x) == [1, 1, 1]
|
| 28 |
+
R = QQ.old_poly_ring(x, order="ilex")
|
| 29 |
+
assert R.free_module(1).convert([x]) / R.convert(x) == [1]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def test_FreeModule():
|
| 33 |
+
M1 = FreeModule(QQ.old_poly_ring(x), 2)
|
| 34 |
+
assert M1 == FreeModule(QQ.old_poly_ring(x), 2)
|
| 35 |
+
assert M1 != FreeModule(QQ.old_poly_ring(y), 2)
|
| 36 |
+
assert M1 != FreeModule(QQ.old_poly_ring(x), 3)
|
| 37 |
+
M2 = FreeModule(QQ.old_poly_ring(x, order="ilex"), 2)
|
| 38 |
+
|
| 39 |
+
assert [x, 1] in M1
|
| 40 |
+
assert [x] not in M1
|
| 41 |
+
assert [2, y] not in M1
|
| 42 |
+
assert [1/(x + 1), 2] not in M1
|
| 43 |
+
|
| 44 |
+
e = M1.convert([x, x**2 + 1])
|
| 45 |
+
X = QQ.old_poly_ring(x).convert(x)
|
| 46 |
+
assert e == [X, X**2 + 1]
|
| 47 |
+
assert e == [x, x**2 + 1]
|
| 48 |
+
assert 2*e == [2*x, 2*x**2 + 2]
|
| 49 |
+
assert e*2 == [2*x, 2*x**2 + 2]
|
| 50 |
+
assert e/2 == [x/2, (x**2 + 1)/2]
|
| 51 |
+
assert x*e == [x**2, x**3 + x]
|
| 52 |
+
assert e*x == [x**2, x**3 + x]
|
| 53 |
+
assert X*e == [x**2, x**3 + x]
|
| 54 |
+
assert e*X == [x**2, x**3 + x]
|
| 55 |
+
|
| 56 |
+
assert [x, 1] in M2
|
| 57 |
+
assert [x] not in M2
|
| 58 |
+
assert [2, y] not in M2
|
| 59 |
+
assert [1/(x + 1), 2] in M2
|
| 60 |
+
|
| 61 |
+
e = M2.convert([x, x**2 + 1])
|
| 62 |
+
X = QQ.old_poly_ring(x, order="ilex").convert(x)
|
| 63 |
+
assert e == [X, X**2 + 1]
|
| 64 |
+
assert e == [x, x**2 + 1]
|
| 65 |
+
assert 2*e == [2*x, 2*x**2 + 2]
|
| 66 |
+
assert e*2 == [2*x, 2*x**2 + 2]
|
| 67 |
+
assert e/2 == [x/2, (x**2 + 1)/2]
|
| 68 |
+
assert x*e == [x**2, x**3 + x]
|
| 69 |
+
assert e*x == [x**2, x**3 + x]
|
| 70 |
+
assert e/(1 + x) == [x/(1 + x), (x**2 + 1)/(1 + x)]
|
| 71 |
+
assert X*e == [x**2, x**3 + x]
|
| 72 |
+
assert e*X == [x**2, x**3 + x]
|
| 73 |
+
|
| 74 |
+
M3 = FreeModule(QQ.old_poly_ring(x, y), 2)
|
| 75 |
+
assert M3.convert(e) == M3.convert([x, x**2 + 1])
|
| 76 |
+
|
| 77 |
+
assert not M3.is_submodule(0)
|
| 78 |
+
assert not M3.is_zero()
|
| 79 |
+
|
| 80 |
+
raises(NotImplementedError, lambda: ZZ.old_poly_ring(x).free_module(2))
|
| 81 |
+
raises(NotImplementedError, lambda: FreeModulePolyRing(ZZ, 2))
|
| 82 |
+
raises(CoercionFailed, lambda: M1.convert(QQ.old_poly_ring(x).free_module(3)
|
| 83 |
+
.convert([1, 2, 3])))
|
| 84 |
+
raises(CoercionFailed, lambda: M3.convert(1))
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def test_ModuleOrder():
|
| 88 |
+
o1 = ModuleOrder(lex, grlex, False)
|
| 89 |
+
o2 = ModuleOrder(ilex, lex, False)
|
| 90 |
+
|
| 91 |
+
assert o1 == ModuleOrder(lex, grlex, False)
|
| 92 |
+
assert (o1 != ModuleOrder(lex, grlex, False)) is False
|
| 93 |
+
assert o1 != o2
|
| 94 |
+
|
| 95 |
+
assert o1((1, 2, 3)) == (1, (5, (2, 3)))
|
| 96 |
+
assert o2((1, 2, 3)) == (-1, (2, 3))
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def test_SubModulePolyRing_global():
|
| 100 |
+
R = QQ.old_poly_ring(x, y)
|
| 101 |
+
F = R.free_module(3)
|
| 102 |
+
Fd = F.submodule([1, 0, 0], [1, 2, 0], [1, 2, 3])
|
| 103 |
+
M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1])
|
| 104 |
+
|
| 105 |
+
assert F == Fd
|
| 106 |
+
assert Fd == F
|
| 107 |
+
assert F != M
|
| 108 |
+
assert M != F
|
| 109 |
+
assert Fd != M
|
| 110 |
+
assert M != Fd
|
| 111 |
+
assert Fd == F.submodule(*F.basis())
|
| 112 |
+
|
| 113 |
+
assert Fd.is_full_module()
|
| 114 |
+
assert not M.is_full_module()
|
| 115 |
+
assert not Fd.is_zero()
|
| 116 |
+
assert not M.is_zero()
|
| 117 |
+
assert Fd.submodule().is_zero()
|
| 118 |
+
|
| 119 |
+
assert M.contains([x**2 + y**2 + x, 1 + y, 1])
|
| 120 |
+
assert not M.contains([x**2 + y**2 + x, 1 + y, 2])
|
| 121 |
+
assert M.contains([y**2, 1 - x*y, -x])
|
| 122 |
+
|
| 123 |
+
assert not F.submodule([1 + x, 0, 0]) == F.submodule([1, 0, 0])
|
| 124 |
+
assert F.submodule([1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1])) == F
|
| 125 |
+
assert not M.is_submodule(0)
|
| 126 |
+
|
| 127 |
+
m = F.convert([x**2 + y**2, 1, 0])
|
| 128 |
+
n = M.convert(m)
|
| 129 |
+
assert m.module is F
|
| 130 |
+
assert n.module is M
|
| 131 |
+
|
| 132 |
+
raises(ValueError, lambda: M.submodule([1, 0, 0]))
|
| 133 |
+
raises(TypeError, lambda: M.union(1))
|
| 134 |
+
raises(ValueError, lambda: M.union(R.free_module(1).submodule([x])))
|
| 135 |
+
|
| 136 |
+
assert F.submodule([x, x, x]) != F.submodule([x, x, x], order="ilex")
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def test_SubModulePolyRing_local():
|
| 140 |
+
R = QQ.old_poly_ring(x, y, order=ilex)
|
| 141 |
+
F = R.free_module(3)
|
| 142 |
+
Fd = F.submodule([1 + x, 0, 0], [1 + y, 2 + 2*y, 0], [1, 2, 3])
|
| 143 |
+
M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1])
|
| 144 |
+
|
| 145 |
+
assert F == Fd
|
| 146 |
+
assert Fd == F
|
| 147 |
+
assert F != M
|
| 148 |
+
assert M != F
|
| 149 |
+
assert Fd != M
|
| 150 |
+
assert M != Fd
|
| 151 |
+
assert Fd == F.submodule(*F.basis())
|
| 152 |
+
|
| 153 |
+
assert Fd.is_full_module()
|
| 154 |
+
assert not M.is_full_module()
|
| 155 |
+
assert not Fd.is_zero()
|
| 156 |
+
assert not M.is_zero()
|
| 157 |
+
assert Fd.submodule().is_zero()
|
| 158 |
+
|
| 159 |
+
assert M.contains([x**2 + y**2 + x, 1 + y, 1])
|
| 160 |
+
assert not M.contains([x**2 + y**2 + x, 1 + y, 2])
|
| 161 |
+
assert M.contains([y**2, 1 - x*y, -x])
|
| 162 |
+
|
| 163 |
+
assert F.submodule([1 + x, 0, 0]) == F.submodule([1, 0, 0])
|
| 164 |
+
assert F.submodule(
|
| 165 |
+
[1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1 + x*y])) == F
|
| 166 |
+
|
| 167 |
+
raises(ValueError, lambda: M.submodule([1, 0, 0]))
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def test_SubModulePolyRing_nontriv_global():
|
| 171 |
+
R = QQ.old_poly_ring(x, y, z)
|
| 172 |
+
F = R.free_module(1)
|
| 173 |
+
|
| 174 |
+
def contains(I, f):
|
| 175 |
+
return F.submodule(*[[g] for g in I]).contains([f])
|
| 176 |
+
|
| 177 |
+
assert contains([x, y], x)
|
| 178 |
+
assert contains([x, y], x + y)
|
| 179 |
+
assert not contains([x, y], 1)
|
| 180 |
+
assert not contains([x, y], z)
|
| 181 |
+
assert contains([x**2 + y, x**2 + x], x - y)
|
| 182 |
+
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2)
|
| 183 |
+
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**3)
|
| 184 |
+
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4)
|
| 185 |
+
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y**2)
|
| 186 |
+
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x**4 + y**3 + 2*z*y*x)
|
| 187 |
+
assert contains([x + y + z, x*y + x*z + y*z, x*y*z], x*y*z)
|
| 188 |
+
assert contains([x, 1 + x + y, 5 - 7*y], 1)
|
| 189 |
+
assert contains(
|
| 190 |
+
[x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z],
|
| 191 |
+
x**3)
|
| 192 |
+
assert not contains(
|
| 193 |
+
[x**3 + y**3, y**3 + z**3, z**3 + x**3, x**2*y + x**2*z + y**2*z],
|
| 194 |
+
x**2 + y**2)
|
| 195 |
+
|
| 196 |
+
# compare local order
|
| 197 |
+
assert not contains([x*(1 + x + y), y*(1 + z)], x)
|
| 198 |
+
assert not contains([x*(1 + x + y), y*(1 + z)], x + y)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def test_SubModulePolyRing_nontriv_local():
|
| 202 |
+
R = QQ.old_poly_ring(x, y, z, order=ilex)
|
| 203 |
+
F = R.free_module(1)
|
| 204 |
+
|
| 205 |
+
def contains(I, f):
|
| 206 |
+
return F.submodule(*[[g] for g in I]).contains([f])
|
| 207 |
+
|
| 208 |
+
assert contains([x, y], x)
|
| 209 |
+
assert contains([x, y], x + y)
|
| 210 |
+
assert not contains([x, y], 1)
|
| 211 |
+
assert not contains([x, y], z)
|
| 212 |
+
assert contains([x**2 + y, x**2 + x], x - y)
|
| 213 |
+
assert not contains([x + y + z, x*y + x*z + y*z, x*y*z], x**2)
|
| 214 |
+
assert contains([x*(1 + x + y), y*(1 + z)], x)
|
| 215 |
+
assert contains([x*(1 + x + y), y*(1 + z)], x + y)
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def test_syzygy():
|
| 219 |
+
R = QQ.old_poly_ring(x, y, z)
|
| 220 |
+
M = R.free_module(1).submodule([x*y], [y*z], [x*z])
|
| 221 |
+
S = R.free_module(3).submodule([0, x, -y], [z, -x, 0])
|
| 222 |
+
assert M.syzygy_module() == S
|
| 223 |
+
|
| 224 |
+
M2 = M / ([x*y*z],)
|
| 225 |
+
S2 = R.free_module(3).submodule([z, 0, 0], [0, x, 0], [0, 0, y])
|
| 226 |
+
assert M2.syzygy_module() == S2
|
| 227 |
+
|
| 228 |
+
F = R.free_module(3)
|
| 229 |
+
assert F.submodule(*F.basis()).syzygy_module() == F.submodule()
|
| 230 |
+
|
| 231 |
+
R2 = QQ.old_poly_ring(x, y, z) / [x*y*z]
|
| 232 |
+
M3 = R2.free_module(1).submodule([x*y], [y*z], [x*z])
|
| 233 |
+
S3 = R2.free_module(3).submodule([z, 0, 0], [0, x, 0], [0, 0, y])
|
| 234 |
+
assert M3.syzygy_module() == S3
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def test_in_terms_of_generators():
|
| 238 |
+
R = QQ.old_poly_ring(x, order="ilex")
|
| 239 |
+
M = R.free_module(2).submodule([2*x, 0], [1, 2])
|
| 240 |
+
assert M.in_terms_of_generators(
|
| 241 |
+
[x, x]) == [R.convert(Rational(1, 4)), R.convert(x/2)]
|
| 242 |
+
raises(ValueError, lambda: M.in_terms_of_generators([1, 0]))
|
| 243 |
+
|
| 244 |
+
M = R.free_module(2) / ([x, 0], [1, 1])
|
| 245 |
+
SM = M.submodule([1, x])
|
| 246 |
+
assert SM.in_terms_of_generators([2, 0]) == [R.convert(-2/(x - 1))]
|
| 247 |
+
|
| 248 |
+
R = QQ.old_poly_ring(x, y) / [x**2 - y**2]
|
| 249 |
+
M = R.free_module(2)
|
| 250 |
+
SM = M.submodule([x, 0], [0, y])
|
| 251 |
+
assert SM.in_terms_of_generators(
|
| 252 |
+
[x**2, x**2]) == [R.convert(x), R.convert(y)]
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def test_QuotientModuleElement():
|
| 256 |
+
R = QQ.old_poly_ring(x)
|
| 257 |
+
F = R.free_module(3)
|
| 258 |
+
N = F.submodule([1, x, x**2])
|
| 259 |
+
M = F/N
|
| 260 |
+
e = M.convert([x**2, 2, 0])
|
| 261 |
+
|
| 262 |
+
assert M.convert([x + 1, x**2 + x, x**3 + x**2]) == 0
|
| 263 |
+
assert e == [x**2, 2, 0] + N == F.convert([x**2, 2, 0]) + N == \
|
| 264 |
+
M.convert(F.convert([x**2, 2, 0]))
|
| 265 |
+
|
| 266 |
+
assert M.convert([x**2 + 1, 2*x + 2, x**2]) == e + [0, x, 0] == \
|
| 267 |
+
e + M.convert([0, x, 0]) == e + F.convert([0, x, 0])
|
| 268 |
+
assert M.convert([x**2 + 1, 2, x**2]) == e - [0, x, 0] == \
|
| 269 |
+
e - M.convert([0, x, 0]) == e - F.convert([0, x, 0])
|
| 270 |
+
assert M.convert([0, 2, 0]) == M.convert([x**2, 4, 0]) - e == \
|
| 271 |
+
[x**2, 4, 0] - e == F.convert([x**2, 4, 0]) - e
|
| 272 |
+
assert M.convert([x**3 + x**2, 2*x + 2, 0]) == (1 + x)*e == \
|
| 273 |
+
R.convert(1 + x)*e == e*(1 + x) == e*R.convert(1 + x)
|
| 274 |
+
assert -e == [-x**2, -2, 0]
|
| 275 |
+
|
| 276 |
+
f = [x, x, 0] + N
|
| 277 |
+
assert M.convert([1, 1, 0]) == f / x == f / R.convert(x)
|
| 278 |
+
|
| 279 |
+
M2 = F/[(2, 2*x, 2*x**2), (0, 0, 1)]
|
| 280 |
+
G = R.free_module(2)
|
| 281 |
+
M3 = G/[[1, x]]
|
| 282 |
+
M4 = F.submodule([1, x, x**2], [1, 0, 0]) / N
|
| 283 |
+
raises(CoercionFailed, lambda: M.convert(G.convert([1, x])))
|
| 284 |
+
raises(CoercionFailed, lambda: M.convert(M3.convert([1, x])))
|
| 285 |
+
raises(CoercionFailed, lambda: M.convert(M2.convert([1, x, x])))
|
| 286 |
+
assert M2.convert(M.convert([2, x, x**2])) == [2, x, 0]
|
| 287 |
+
assert M.convert(M4.convert([2, 0, 0])) == [2, 0, 0]
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def test_QuotientModule():
|
| 291 |
+
R = QQ.old_poly_ring(x)
|
| 292 |
+
F = R.free_module(3)
|
| 293 |
+
N = F.submodule([1, x, x**2])
|
| 294 |
+
M = F/N
|
| 295 |
+
|
| 296 |
+
assert M != F
|
| 297 |
+
assert M != N
|
| 298 |
+
assert M == F / [(1, x, x**2)]
|
| 299 |
+
assert not M.is_zero()
|
| 300 |
+
assert (F / F.basis()).is_zero()
|
| 301 |
+
|
| 302 |
+
SQ = F.submodule([1, x, x**2], [2, 0, 0]) / N
|
| 303 |
+
assert SQ == M.submodule([2, x, x**2])
|
| 304 |
+
assert SQ != M.submodule([2, 1, 0])
|
| 305 |
+
assert SQ != M
|
| 306 |
+
assert M.is_submodule(SQ)
|
| 307 |
+
assert not SQ.is_full_module()
|
| 308 |
+
|
| 309 |
+
raises(ValueError, lambda: N/F)
|
| 310 |
+
raises(ValueError, lambda: F.submodule([2, 0, 0]) / N)
|
| 311 |
+
raises(ValueError, lambda: R.free_module(2)/F)
|
| 312 |
+
raises(CoercionFailed, lambda: F.convert(M.convert([1, x, x**2])))
|
| 313 |
+
|
| 314 |
+
M1 = F / [[1, 1, 1]]
|
| 315 |
+
M2 = M1.submodule([1, 0, 0], [0, 1, 0])
|
| 316 |
+
assert M1 == M2
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def test_ModulesQuotientRing():
|
| 320 |
+
R = QQ.old_poly_ring(x, y, order=(("lex", x), ("ilex", y))) / [x**2 + 1]
|
| 321 |
+
M1 = R.free_module(2)
|
| 322 |
+
assert M1 == R.free_module(2)
|
| 323 |
+
assert M1 != QQ.old_poly_ring(x).free_module(2)
|
| 324 |
+
assert M1 != R.free_module(3)
|
| 325 |
+
|
| 326 |
+
assert [x, 1] in M1
|
| 327 |
+
assert [x] not in M1
|
| 328 |
+
assert [1/(R.convert(x) + 1), 2] in M1
|
| 329 |
+
assert [1, 2/(1 + y)] in M1
|
| 330 |
+
assert [1, 2/y] not in M1
|
| 331 |
+
|
| 332 |
+
assert M1.convert([x**2, y]) == [-1, y]
|
| 333 |
+
|
| 334 |
+
F = R.free_module(3)
|
| 335 |
+
Fd = F.submodule([x**2, 0, 0], [1, 2, 0], [1, 2, 3])
|
| 336 |
+
M = F.submodule([x**2 + y**2, 1, 0], [x, y, 1])
|
| 337 |
+
|
| 338 |
+
assert F == Fd
|
| 339 |
+
assert Fd == F
|
| 340 |
+
assert F != M
|
| 341 |
+
assert M != F
|
| 342 |
+
assert Fd != M
|
| 343 |
+
assert M != Fd
|
| 344 |
+
assert Fd == F.submodule(*F.basis())
|
| 345 |
+
|
| 346 |
+
assert Fd.is_full_module()
|
| 347 |
+
assert not M.is_full_module()
|
| 348 |
+
assert not Fd.is_zero()
|
| 349 |
+
assert not M.is_zero()
|
| 350 |
+
assert Fd.submodule().is_zero()
|
| 351 |
+
|
| 352 |
+
assert M.contains([x**2 + y**2 + x, -x**2 + y, 1])
|
| 353 |
+
assert not M.contains([x**2 + y**2 + x, 1 + y, 2])
|
| 354 |
+
assert M.contains([y**2, 1 - x*y, -x])
|
| 355 |
+
|
| 356 |
+
assert F.submodule([x, 0, 0]) == F.submodule([1, 0, 0])
|
| 357 |
+
assert not F.submodule([y, 0, 0]) == F.submodule([1, 0, 0])
|
| 358 |
+
assert F.submodule([1, 0, 0], [0, 1, 0]).union(F.submodule([0, 0, 1])) == F
|
| 359 |
+
assert not M.is_submodule(0)
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def test_module_mul():
|
| 363 |
+
R = QQ.old_poly_ring(x)
|
| 364 |
+
M = R.free_module(2)
|
| 365 |
+
S1 = M.submodule([x, 0], [0, x])
|
| 366 |
+
S2 = M.submodule([x**2, 0], [0, x**2])
|
| 367 |
+
I = R.ideal(x)
|
| 368 |
+
|
| 369 |
+
assert I*M == M*I == S1 == x*M == M*x
|
| 370 |
+
assert I*S1 == S2 == x*S1
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
def test_intersection():
|
| 374 |
+
# SCA, example 2.8.5
|
| 375 |
+
F = QQ.old_poly_ring(x, y).free_module(2)
|
| 376 |
+
M1 = F.submodule([x, y], [y, 1])
|
| 377 |
+
M2 = F.submodule([0, y - 1], [x, 1], [y, x])
|
| 378 |
+
I = F.submodule([x, y], [y**2 - y, y - 1], [x*y + y, x + 1])
|
| 379 |
+
I1, rel1, rel2 = M1.intersect(M2, relations=True)
|
| 380 |
+
assert I1 == M2.intersect(M1) == I
|
| 381 |
+
for i, g in enumerate(I1.gens):
|
| 382 |
+
assert g == sum(c*x for c, x in zip(rel1[i], M1.gens)) \
|
| 383 |
+
== sum(d*y for d, y in zip(rel2[i], M2.gens))
|
| 384 |
+
|
| 385 |
+
assert F.submodule([x, y]).intersect(F.submodule([y, x])).is_zero()
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def test_quotient():
|
| 389 |
+
# SCA, example 2.8.6
|
| 390 |
+
R = QQ.old_poly_ring(x, y, z)
|
| 391 |
+
F = R.free_module(2)
|
| 392 |
+
assert F.submodule([x*y, x*z], [y*z, x*y]).module_quotient(
|
| 393 |
+
F.submodule([y, z], [z, y])) == QQ.old_poly_ring(x, y, z).ideal(x**2*y**2 - x*y*z**2)
|
| 394 |
+
assert F.submodule([x, y]).module_quotient(F.submodule()).is_whole_ring()
|
| 395 |
+
|
| 396 |
+
M = F.submodule([x**2, x**2], [y**2, y**2])
|
| 397 |
+
N = F.submodule([x + y, x + y])
|
| 398 |
+
q, rel = M.module_quotient(N, relations=True)
|
| 399 |
+
assert q == R.ideal(y**2, x - y)
|
| 400 |
+
for i, g in enumerate(q.gens):
|
| 401 |
+
assert g*N.gens[0] == sum(c*x for c, x in zip(rel[i], M.gens))
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def test_groebner_extendend():
|
| 405 |
+
M = QQ.old_poly_ring(x, y, z).free_module(3).submodule([x + 1, y, 1], [x*y, z, z**2])
|
| 406 |
+
G, R = M._groebner_vec(extended=True)
|
| 407 |
+
for i, g in enumerate(G):
|
| 408 |
+
assert g == sum(c*gen for c, gen in zip(R[i], M.gens))
|
.venv/lib/python3.13/site-packages/sympy/polys/benchmarks/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_galoispolys.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Benchmarks for polynomials over Galois fields. """
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
from sympy.polys.galoistools import gf_from_dict, gf_factor_sqf
|
| 5 |
+
from sympy.polys.domains import ZZ
|
| 6 |
+
from sympy.core.numbers import pi
|
| 7 |
+
from sympy.ntheory.generate import nextprime
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def gathen_poly(n, p, K):
|
| 11 |
+
return gf_from_dict({n: K.one, 1: K.one, 0: K.one}, p, K)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def shoup_poly(n, p, K):
|
| 15 |
+
f = [K.one] * (n + 1)
|
| 16 |
+
for i in range(1, n + 1):
|
| 17 |
+
f[i] = (f[i - 1]**2 + K.one) % p
|
| 18 |
+
return f
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def genprime(n, K):
|
| 22 |
+
return K(nextprime(int((2**n * pi).evalf())))
|
| 23 |
+
|
| 24 |
+
p_10 = genprime(10, ZZ)
|
| 25 |
+
f_10 = gathen_poly(10, p_10, ZZ)
|
| 26 |
+
|
| 27 |
+
p_20 = genprime(20, ZZ)
|
| 28 |
+
f_20 = gathen_poly(20, p_20, ZZ)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def timeit_gathen_poly_f10_zassenhaus():
|
| 32 |
+
gf_factor_sqf(f_10, p_10, ZZ, method='zassenhaus')
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def timeit_gathen_poly_f10_shoup():
|
| 36 |
+
gf_factor_sqf(f_10, p_10, ZZ, method='shoup')
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def timeit_gathen_poly_f20_zassenhaus():
|
| 40 |
+
gf_factor_sqf(f_20, p_20, ZZ, method='zassenhaus')
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def timeit_gathen_poly_f20_shoup():
|
| 44 |
+
gf_factor_sqf(f_20, p_20, ZZ, method='shoup')
|
| 45 |
+
|
| 46 |
+
P_08 = genprime(8, ZZ)
|
| 47 |
+
F_10 = shoup_poly(10, P_08, ZZ)
|
| 48 |
+
|
| 49 |
+
P_18 = genprime(18, ZZ)
|
| 50 |
+
F_20 = shoup_poly(20, P_18, ZZ)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def timeit_shoup_poly_F10_zassenhaus():
|
| 54 |
+
gf_factor_sqf(F_10, P_08, ZZ, method='zassenhaus')
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def timeit_shoup_poly_F10_shoup():
|
| 58 |
+
gf_factor_sqf(F_10, P_08, ZZ, method='shoup')
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def timeit_shoup_poly_F20_zassenhaus():
|
| 62 |
+
gf_factor_sqf(F_20, P_18, ZZ, method='zassenhaus')
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def timeit_shoup_poly_F20_shoup():
|
| 66 |
+
gf_factor_sqf(F_20, P_18, ZZ, method='shoup')
|
.venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_groebnertools.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Benchmark of the Groebner bases algorithms. """
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
from sympy.polys.rings import ring
|
| 5 |
+
from sympy.polys.domains import QQ
|
| 6 |
+
from sympy.polys.groebnertools import groebner
|
| 7 |
+
|
| 8 |
+
R, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12 = ring("x1:13", QQ)
|
| 9 |
+
|
| 10 |
+
V = R.gens
|
| 11 |
+
E = [(x1, x2), (x2, x3), (x1, x4), (x1, x6), (x1, x12), (x2, x5), (x2, x7), (x3, x8),
|
| 12 |
+
(x3, x10), (x4, x11), (x4, x9), (x5, x6), (x6, x7), (x7, x8), (x8, x9), (x9, x10),
|
| 13 |
+
(x10, x11), (x11, x12), (x5, x12), (x5, x9), (x6, x10), (x7, x11), (x8, x12)]
|
| 14 |
+
|
| 15 |
+
F3 = [ x**3 - 1 for x in V ]
|
| 16 |
+
Fg = [ x**2 + x*y + y**2 for x, y in E ]
|
| 17 |
+
|
| 18 |
+
F_1 = F3 + Fg
|
| 19 |
+
F_2 = F3 + Fg + [x3**2 + x3*x4 + x4**2]
|
| 20 |
+
|
| 21 |
+
def time_vertex_color_12_vertices_23_edges():
|
| 22 |
+
assert groebner(F_1, R) != [1]
|
| 23 |
+
|
| 24 |
+
def time_vertex_color_12_vertices_24_edges():
|
| 25 |
+
assert groebner(F_2, R) == [1]
|
.venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_solvers.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
.venv/lib/python3.13/site-packages/sympy/polys/domains/tests/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_domains.py
ADDED
|
@@ -0,0 +1,1434 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for classes defining properties of ground domains, e.g. ZZ, QQ, ZZ[x] ... """
|
| 2 |
+
|
| 3 |
+
from sympy.external.gmpy import GROUND_TYPES
|
| 4 |
+
|
| 5 |
+
from sympy.core.numbers import (AlgebraicNumber, E, Float, I, Integer,
|
| 6 |
+
Rational, oo, pi, _illegal)
|
| 7 |
+
from sympy.core.singleton import S
|
| 8 |
+
from sympy.functions.elementary.exponential import exp
|
| 9 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 10 |
+
from sympy.functions.elementary.trigonometric import sin
|
| 11 |
+
from sympy.polys.polytools import Poly
|
| 12 |
+
from sympy.abc import x, y, z
|
| 13 |
+
|
| 14 |
+
from sympy.polys.domains import (ZZ, QQ, RR, CC, FF, GF, EX, EXRAW, ZZ_gmpy,
|
| 15 |
+
ZZ_python, QQ_gmpy, QQ_python)
|
| 16 |
+
from sympy.polys.domains.algebraicfield import AlgebraicField
|
| 17 |
+
from sympy.polys.domains.gaussiandomains import ZZ_I, QQ_I
|
| 18 |
+
from sympy.polys.domains.polynomialring import PolynomialRing
|
| 19 |
+
from sympy.polys.domains.realfield import RealField
|
| 20 |
+
|
| 21 |
+
from sympy.polys.numberfields.subfield import field_isomorphism
|
| 22 |
+
from sympy.polys.rings import ring, PolyElement
|
| 23 |
+
from sympy.polys.specialpolys import cyclotomic_poly
|
| 24 |
+
from sympy.polys.fields import field, FracElement
|
| 25 |
+
|
| 26 |
+
from sympy.polys.agca.extensions import FiniteExtension
|
| 27 |
+
|
| 28 |
+
from sympy.polys.polyerrors import (
|
| 29 |
+
UnificationFailed,
|
| 30 |
+
GeneratorsError,
|
| 31 |
+
CoercionFailed,
|
| 32 |
+
NotInvertible,
|
| 33 |
+
DomainError)
|
| 34 |
+
|
| 35 |
+
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
| 36 |
+
|
| 37 |
+
from itertools import product
|
| 38 |
+
|
| 39 |
+
ALG = QQ.algebraic_field(sqrt(2), sqrt(3))
|
| 40 |
+
|
| 41 |
+
def unify(K0, K1):
|
| 42 |
+
return K0.unify(K1)
|
| 43 |
+
|
| 44 |
+
def test_Domain_unify():
|
| 45 |
+
F3 = GF(3)
|
| 46 |
+
F5 = GF(5)
|
| 47 |
+
|
| 48 |
+
assert unify(F3, F3) == F3
|
| 49 |
+
raises(UnificationFailed, lambda: unify(F3, ZZ))
|
| 50 |
+
raises(UnificationFailed, lambda: unify(F3, QQ))
|
| 51 |
+
raises(UnificationFailed, lambda: unify(F3, ZZ_I))
|
| 52 |
+
raises(UnificationFailed, lambda: unify(F3, QQ_I))
|
| 53 |
+
raises(UnificationFailed, lambda: unify(F3, ALG))
|
| 54 |
+
raises(UnificationFailed, lambda: unify(F3, RR))
|
| 55 |
+
raises(UnificationFailed, lambda: unify(F3, CC))
|
| 56 |
+
raises(UnificationFailed, lambda: unify(F3, ZZ[x]))
|
| 57 |
+
raises(UnificationFailed, lambda: unify(F3, ZZ.frac_field(x)))
|
| 58 |
+
raises(UnificationFailed, lambda: unify(F3, EX))
|
| 59 |
+
|
| 60 |
+
assert unify(F5, F5) == F5
|
| 61 |
+
raises(UnificationFailed, lambda: unify(F5, F3))
|
| 62 |
+
raises(UnificationFailed, lambda: unify(F5, F3[x]))
|
| 63 |
+
raises(UnificationFailed, lambda: unify(F5, F3.frac_field(x)))
|
| 64 |
+
|
| 65 |
+
raises(UnificationFailed, lambda: unify(ZZ, F3))
|
| 66 |
+
assert unify(ZZ, ZZ) == ZZ
|
| 67 |
+
assert unify(ZZ, QQ) == QQ
|
| 68 |
+
assert unify(ZZ, ALG) == ALG
|
| 69 |
+
assert unify(ZZ, RR) == RR
|
| 70 |
+
assert unify(ZZ, CC) == CC
|
| 71 |
+
assert unify(ZZ, ZZ[x]) == ZZ[x]
|
| 72 |
+
assert unify(ZZ, ZZ.frac_field(x)) == ZZ.frac_field(x)
|
| 73 |
+
assert unify(ZZ, EX) == EX
|
| 74 |
+
|
| 75 |
+
raises(UnificationFailed, lambda: unify(QQ, F3))
|
| 76 |
+
assert unify(QQ, ZZ) == QQ
|
| 77 |
+
assert unify(QQ, QQ) == QQ
|
| 78 |
+
assert unify(QQ, ALG) == ALG
|
| 79 |
+
assert unify(QQ, RR) == RR
|
| 80 |
+
assert unify(QQ, CC) == CC
|
| 81 |
+
assert unify(QQ, ZZ[x]) == QQ[x]
|
| 82 |
+
assert unify(QQ, ZZ.frac_field(x)) == QQ.frac_field(x)
|
| 83 |
+
assert unify(QQ, EX) == EX
|
| 84 |
+
|
| 85 |
+
raises(UnificationFailed, lambda: unify(ZZ_I, F3))
|
| 86 |
+
assert unify(ZZ_I, ZZ) == ZZ_I
|
| 87 |
+
assert unify(ZZ_I, ZZ_I) == ZZ_I
|
| 88 |
+
assert unify(ZZ_I, QQ) == QQ_I
|
| 89 |
+
assert unify(ZZ_I, ALG) == QQ.algebraic_field(I, sqrt(2), sqrt(3))
|
| 90 |
+
assert unify(ZZ_I, RR) == CC
|
| 91 |
+
assert unify(ZZ_I, CC) == CC
|
| 92 |
+
assert unify(ZZ_I, ZZ[x]) == ZZ_I[x]
|
| 93 |
+
assert unify(ZZ_I, ZZ_I[x]) == ZZ_I[x]
|
| 94 |
+
assert unify(ZZ_I, ZZ.frac_field(x)) == ZZ_I.frac_field(x)
|
| 95 |
+
assert unify(ZZ_I, ZZ_I.frac_field(x)) == ZZ_I.frac_field(x)
|
| 96 |
+
assert unify(ZZ_I, EX) == EX
|
| 97 |
+
|
| 98 |
+
raises(UnificationFailed, lambda: unify(QQ_I, F3))
|
| 99 |
+
assert unify(QQ_I, ZZ) == QQ_I
|
| 100 |
+
assert unify(QQ_I, ZZ_I) == QQ_I
|
| 101 |
+
assert unify(QQ_I, QQ) == QQ_I
|
| 102 |
+
assert unify(QQ_I, ALG) == QQ.algebraic_field(I, sqrt(2), sqrt(3))
|
| 103 |
+
assert unify(QQ_I, RR) == CC
|
| 104 |
+
assert unify(QQ_I, CC) == CC
|
| 105 |
+
assert unify(QQ_I, ZZ[x]) == QQ_I[x]
|
| 106 |
+
assert unify(QQ_I, ZZ_I[x]) == QQ_I[x]
|
| 107 |
+
assert unify(QQ_I, QQ[x]) == QQ_I[x]
|
| 108 |
+
assert unify(QQ_I, QQ_I[x]) == QQ_I[x]
|
| 109 |
+
assert unify(QQ_I, ZZ.frac_field(x)) == QQ_I.frac_field(x)
|
| 110 |
+
assert unify(QQ_I, ZZ_I.frac_field(x)) == QQ_I.frac_field(x)
|
| 111 |
+
assert unify(QQ_I, QQ.frac_field(x)) == QQ_I.frac_field(x)
|
| 112 |
+
assert unify(QQ_I, QQ_I.frac_field(x)) == QQ_I.frac_field(x)
|
| 113 |
+
assert unify(QQ_I, EX) == EX
|
| 114 |
+
|
| 115 |
+
raises(UnificationFailed, lambda: unify(RR, F3))
|
| 116 |
+
assert unify(RR, ZZ) == RR
|
| 117 |
+
assert unify(RR, QQ) == RR
|
| 118 |
+
assert unify(RR, ALG) == RR
|
| 119 |
+
assert unify(RR, RR) == RR
|
| 120 |
+
assert unify(RR, CC) == CC
|
| 121 |
+
assert unify(RR, ZZ[x]) == RR[x]
|
| 122 |
+
assert unify(RR, ZZ.frac_field(x)) == RR.frac_field(x)
|
| 123 |
+
assert unify(RR, EX) == EX
|
| 124 |
+
assert RR[x].unify(ZZ.frac_field(y)) == RR.frac_field(x, y)
|
| 125 |
+
|
| 126 |
+
raises(UnificationFailed, lambda: unify(CC, F3))
|
| 127 |
+
assert unify(CC, ZZ) == CC
|
| 128 |
+
assert unify(CC, QQ) == CC
|
| 129 |
+
assert unify(CC, ALG) == CC
|
| 130 |
+
assert unify(CC, RR) == CC
|
| 131 |
+
assert unify(CC, CC) == CC
|
| 132 |
+
assert unify(CC, ZZ[x]) == CC[x]
|
| 133 |
+
assert unify(CC, ZZ.frac_field(x)) == CC.frac_field(x)
|
| 134 |
+
assert unify(CC, EX) == EX
|
| 135 |
+
|
| 136 |
+
raises(UnificationFailed, lambda: unify(ZZ[x], F3))
|
| 137 |
+
assert unify(ZZ[x], ZZ) == ZZ[x]
|
| 138 |
+
assert unify(ZZ[x], QQ) == QQ[x]
|
| 139 |
+
assert unify(ZZ[x], ALG) == ALG[x]
|
| 140 |
+
assert unify(ZZ[x], RR) == RR[x]
|
| 141 |
+
assert unify(ZZ[x], CC) == CC[x]
|
| 142 |
+
assert unify(ZZ[x], ZZ[x]) == ZZ[x]
|
| 143 |
+
assert unify(ZZ[x], ZZ.frac_field(x)) == ZZ.frac_field(x)
|
| 144 |
+
assert unify(ZZ[x], EX) == EX
|
| 145 |
+
|
| 146 |
+
raises(UnificationFailed, lambda: unify(ZZ.frac_field(x), F3))
|
| 147 |
+
assert unify(ZZ.frac_field(x), ZZ) == ZZ.frac_field(x)
|
| 148 |
+
assert unify(ZZ.frac_field(x), QQ) == QQ.frac_field(x)
|
| 149 |
+
assert unify(ZZ.frac_field(x), ALG) == ALG.frac_field(x)
|
| 150 |
+
assert unify(ZZ.frac_field(x), RR) == RR.frac_field(x)
|
| 151 |
+
assert unify(ZZ.frac_field(x), CC) == CC.frac_field(x)
|
| 152 |
+
assert unify(ZZ.frac_field(x), ZZ[x]) == ZZ.frac_field(x)
|
| 153 |
+
assert unify(ZZ.frac_field(x), ZZ.frac_field(x)) == ZZ.frac_field(x)
|
| 154 |
+
assert unify(ZZ.frac_field(x), EX) == EX
|
| 155 |
+
|
| 156 |
+
raises(UnificationFailed, lambda: unify(EX, F3))
|
| 157 |
+
assert unify(EX, ZZ) == EX
|
| 158 |
+
assert unify(EX, QQ) == EX
|
| 159 |
+
assert unify(EX, ALG) == EX
|
| 160 |
+
assert unify(EX, RR) == EX
|
| 161 |
+
assert unify(EX, CC) == EX
|
| 162 |
+
assert unify(EX, ZZ[x]) == EX
|
| 163 |
+
assert unify(EX, ZZ.frac_field(x)) == EX
|
| 164 |
+
assert unify(EX, EX) == EX
|
| 165 |
+
|
| 166 |
+
def test_Domain_unify_composite():
|
| 167 |
+
assert unify(ZZ.poly_ring(x), ZZ) == ZZ.poly_ring(x)
|
| 168 |
+
assert unify(ZZ.poly_ring(x), QQ) == QQ.poly_ring(x)
|
| 169 |
+
assert unify(QQ.poly_ring(x), ZZ) == QQ.poly_ring(x)
|
| 170 |
+
assert unify(QQ.poly_ring(x), QQ) == QQ.poly_ring(x)
|
| 171 |
+
|
| 172 |
+
assert unify(ZZ, ZZ.poly_ring(x)) == ZZ.poly_ring(x)
|
| 173 |
+
assert unify(QQ, ZZ.poly_ring(x)) == QQ.poly_ring(x)
|
| 174 |
+
assert unify(ZZ, QQ.poly_ring(x)) == QQ.poly_ring(x)
|
| 175 |
+
assert unify(QQ, QQ.poly_ring(x)) == QQ.poly_ring(x)
|
| 176 |
+
|
| 177 |
+
assert unify(ZZ.poly_ring(x, y), ZZ) == ZZ.poly_ring(x, y)
|
| 178 |
+
assert unify(ZZ.poly_ring(x, y), QQ) == QQ.poly_ring(x, y)
|
| 179 |
+
assert unify(QQ.poly_ring(x, y), ZZ) == QQ.poly_ring(x, y)
|
| 180 |
+
assert unify(QQ.poly_ring(x, y), QQ) == QQ.poly_ring(x, y)
|
| 181 |
+
|
| 182 |
+
assert unify(ZZ, ZZ.poly_ring(x, y)) == ZZ.poly_ring(x, y)
|
| 183 |
+
assert unify(QQ, ZZ.poly_ring(x, y)) == QQ.poly_ring(x, y)
|
| 184 |
+
assert unify(ZZ, QQ.poly_ring(x, y)) == QQ.poly_ring(x, y)
|
| 185 |
+
assert unify(QQ, QQ.poly_ring(x, y)) == QQ.poly_ring(x, y)
|
| 186 |
+
|
| 187 |
+
assert unify(ZZ.frac_field(x), ZZ) == ZZ.frac_field(x)
|
| 188 |
+
assert unify(ZZ.frac_field(x), QQ) == QQ.frac_field(x)
|
| 189 |
+
assert unify(QQ.frac_field(x), ZZ) == QQ.frac_field(x)
|
| 190 |
+
assert unify(QQ.frac_field(x), QQ) == QQ.frac_field(x)
|
| 191 |
+
|
| 192 |
+
assert unify(ZZ, ZZ.frac_field(x)) == ZZ.frac_field(x)
|
| 193 |
+
assert unify(QQ, ZZ.frac_field(x)) == QQ.frac_field(x)
|
| 194 |
+
assert unify(ZZ, QQ.frac_field(x)) == QQ.frac_field(x)
|
| 195 |
+
assert unify(QQ, QQ.frac_field(x)) == QQ.frac_field(x)
|
| 196 |
+
|
| 197 |
+
assert unify(ZZ.frac_field(x, y), ZZ) == ZZ.frac_field(x, y)
|
| 198 |
+
assert unify(ZZ.frac_field(x, y), QQ) == QQ.frac_field(x, y)
|
| 199 |
+
assert unify(QQ.frac_field(x, y), ZZ) == QQ.frac_field(x, y)
|
| 200 |
+
assert unify(QQ.frac_field(x, y), QQ) == QQ.frac_field(x, y)
|
| 201 |
+
|
| 202 |
+
assert unify(ZZ, ZZ.frac_field(x, y)) == ZZ.frac_field(x, y)
|
| 203 |
+
assert unify(QQ, ZZ.frac_field(x, y)) == QQ.frac_field(x, y)
|
| 204 |
+
assert unify(ZZ, QQ.frac_field(x, y)) == QQ.frac_field(x, y)
|
| 205 |
+
assert unify(QQ, QQ.frac_field(x, y)) == QQ.frac_field(x, y)
|
| 206 |
+
|
| 207 |
+
assert unify(ZZ.poly_ring(x), ZZ.poly_ring(x)) == ZZ.poly_ring(x)
|
| 208 |
+
assert unify(ZZ.poly_ring(x), QQ.poly_ring(x)) == QQ.poly_ring(x)
|
| 209 |
+
assert unify(QQ.poly_ring(x), ZZ.poly_ring(x)) == QQ.poly_ring(x)
|
| 210 |
+
assert unify(QQ.poly_ring(x), QQ.poly_ring(x)) == QQ.poly_ring(x)
|
| 211 |
+
|
| 212 |
+
assert unify(ZZ.poly_ring(x, y), ZZ.poly_ring(x)) == ZZ.poly_ring(x, y)
|
| 213 |
+
assert unify(ZZ.poly_ring(x, y), QQ.poly_ring(x)) == QQ.poly_ring(x, y)
|
| 214 |
+
assert unify(QQ.poly_ring(x, y), ZZ.poly_ring(x)) == QQ.poly_ring(x, y)
|
| 215 |
+
assert unify(QQ.poly_ring(x, y), QQ.poly_ring(x)) == QQ.poly_ring(x, y)
|
| 216 |
+
|
| 217 |
+
assert unify(ZZ.poly_ring(x), ZZ.poly_ring(x, y)) == ZZ.poly_ring(x, y)
|
| 218 |
+
assert unify(ZZ.poly_ring(x), QQ.poly_ring(x, y)) == QQ.poly_ring(x, y)
|
| 219 |
+
assert unify(QQ.poly_ring(x), ZZ.poly_ring(x, y)) == QQ.poly_ring(x, y)
|
| 220 |
+
assert unify(QQ.poly_ring(x), QQ.poly_ring(x, y)) == QQ.poly_ring(x, y)
|
| 221 |
+
|
| 222 |
+
assert unify(ZZ.poly_ring(x, y), ZZ.poly_ring(x, z)) == ZZ.poly_ring(x, y, z)
|
| 223 |
+
assert unify(ZZ.poly_ring(x, y), QQ.poly_ring(x, z)) == QQ.poly_ring(x, y, z)
|
| 224 |
+
assert unify(QQ.poly_ring(x, y), ZZ.poly_ring(x, z)) == QQ.poly_ring(x, y, z)
|
| 225 |
+
assert unify(QQ.poly_ring(x, y), QQ.poly_ring(x, z)) == QQ.poly_ring(x, y, z)
|
| 226 |
+
|
| 227 |
+
assert unify(ZZ.frac_field(x), ZZ.frac_field(x)) == ZZ.frac_field(x)
|
| 228 |
+
assert unify(ZZ.frac_field(x), QQ.frac_field(x)) == QQ.frac_field(x)
|
| 229 |
+
assert unify(QQ.frac_field(x), ZZ.frac_field(x)) == QQ.frac_field(x)
|
| 230 |
+
assert unify(QQ.frac_field(x), QQ.frac_field(x)) == QQ.frac_field(x)
|
| 231 |
+
|
| 232 |
+
assert unify(ZZ.frac_field(x, y), ZZ.frac_field(x)) == ZZ.frac_field(x, y)
|
| 233 |
+
assert unify(ZZ.frac_field(x, y), QQ.frac_field(x)) == QQ.frac_field(x, y)
|
| 234 |
+
assert unify(QQ.frac_field(x, y), ZZ.frac_field(x)) == QQ.frac_field(x, y)
|
| 235 |
+
assert unify(QQ.frac_field(x, y), QQ.frac_field(x)) == QQ.frac_field(x, y)
|
| 236 |
+
|
| 237 |
+
assert unify(ZZ.frac_field(x), ZZ.frac_field(x, y)) == ZZ.frac_field(x, y)
|
| 238 |
+
assert unify(ZZ.frac_field(x), QQ.frac_field(x, y)) == QQ.frac_field(x, y)
|
| 239 |
+
assert unify(QQ.frac_field(x), ZZ.frac_field(x, y)) == QQ.frac_field(x, y)
|
| 240 |
+
assert unify(QQ.frac_field(x), QQ.frac_field(x, y)) == QQ.frac_field(x, y)
|
| 241 |
+
|
| 242 |
+
assert unify(ZZ.frac_field(x, y), ZZ.frac_field(x, z)) == ZZ.frac_field(x, y, z)
|
| 243 |
+
assert unify(ZZ.frac_field(x, y), QQ.frac_field(x, z)) == QQ.frac_field(x, y, z)
|
| 244 |
+
assert unify(QQ.frac_field(x, y), ZZ.frac_field(x, z)) == QQ.frac_field(x, y, z)
|
| 245 |
+
assert unify(QQ.frac_field(x, y), QQ.frac_field(x, z)) == QQ.frac_field(x, y, z)
|
| 246 |
+
|
| 247 |
+
assert unify(ZZ.poly_ring(x), ZZ.frac_field(x)) == ZZ.frac_field(x)
|
| 248 |
+
assert unify(ZZ.poly_ring(x), QQ.frac_field(x)) == ZZ.frac_field(x)
|
| 249 |
+
assert unify(QQ.poly_ring(x), ZZ.frac_field(x)) == ZZ.frac_field(x)
|
| 250 |
+
assert unify(QQ.poly_ring(x), QQ.frac_field(x)) == QQ.frac_field(x)
|
| 251 |
+
|
| 252 |
+
assert unify(ZZ.poly_ring(x, y), ZZ.frac_field(x)) == ZZ.frac_field(x, y)
|
| 253 |
+
assert unify(ZZ.poly_ring(x, y), QQ.frac_field(x)) == ZZ.frac_field(x, y)
|
| 254 |
+
assert unify(QQ.poly_ring(x, y), ZZ.frac_field(x)) == ZZ.frac_field(x, y)
|
| 255 |
+
assert unify(QQ.poly_ring(x, y), QQ.frac_field(x)) == QQ.frac_field(x, y)
|
| 256 |
+
|
| 257 |
+
assert unify(ZZ.poly_ring(x), ZZ.frac_field(x, y)) == ZZ.frac_field(x, y)
|
| 258 |
+
assert unify(ZZ.poly_ring(x), QQ.frac_field(x, y)) == ZZ.frac_field(x, y)
|
| 259 |
+
assert unify(QQ.poly_ring(x), ZZ.frac_field(x, y)) == ZZ.frac_field(x, y)
|
| 260 |
+
assert unify(QQ.poly_ring(x), QQ.frac_field(x, y)) == QQ.frac_field(x, y)
|
| 261 |
+
|
| 262 |
+
assert unify(ZZ.poly_ring(x, y), ZZ.frac_field(x, z)) == ZZ.frac_field(x, y, z)
|
| 263 |
+
assert unify(ZZ.poly_ring(x, y), QQ.frac_field(x, z)) == ZZ.frac_field(x, y, z)
|
| 264 |
+
assert unify(QQ.poly_ring(x, y), ZZ.frac_field(x, z)) == ZZ.frac_field(x, y, z)
|
| 265 |
+
assert unify(QQ.poly_ring(x, y), QQ.frac_field(x, z)) == QQ.frac_field(x, y, z)
|
| 266 |
+
|
| 267 |
+
assert unify(ZZ.frac_field(x), ZZ.poly_ring(x)) == ZZ.frac_field(x)
|
| 268 |
+
assert unify(ZZ.frac_field(x), QQ.poly_ring(x)) == ZZ.frac_field(x)
|
| 269 |
+
assert unify(QQ.frac_field(x), ZZ.poly_ring(x)) == ZZ.frac_field(x)
|
| 270 |
+
assert unify(QQ.frac_field(x), QQ.poly_ring(x)) == QQ.frac_field(x)
|
| 271 |
+
|
| 272 |
+
assert unify(ZZ.frac_field(x, y), ZZ.poly_ring(x)) == ZZ.frac_field(x, y)
|
| 273 |
+
assert unify(ZZ.frac_field(x, y), QQ.poly_ring(x)) == ZZ.frac_field(x, y)
|
| 274 |
+
assert unify(QQ.frac_field(x, y), ZZ.poly_ring(x)) == ZZ.frac_field(x, y)
|
| 275 |
+
assert unify(QQ.frac_field(x, y), QQ.poly_ring(x)) == QQ.frac_field(x, y)
|
| 276 |
+
|
| 277 |
+
assert unify(ZZ.frac_field(x), ZZ.poly_ring(x, y)) == ZZ.frac_field(x, y)
|
| 278 |
+
assert unify(ZZ.frac_field(x), QQ.poly_ring(x, y)) == ZZ.frac_field(x, y)
|
| 279 |
+
assert unify(QQ.frac_field(x), ZZ.poly_ring(x, y)) == ZZ.frac_field(x, y)
|
| 280 |
+
assert unify(QQ.frac_field(x), QQ.poly_ring(x, y)) == QQ.frac_field(x, y)
|
| 281 |
+
|
| 282 |
+
assert unify(ZZ.frac_field(x, y), ZZ.poly_ring(x, z)) == ZZ.frac_field(x, y, z)
|
| 283 |
+
assert unify(ZZ.frac_field(x, y), QQ.poly_ring(x, z)) == ZZ.frac_field(x, y, z)
|
| 284 |
+
assert unify(QQ.frac_field(x, y), ZZ.poly_ring(x, z)) == ZZ.frac_field(x, y, z)
|
| 285 |
+
assert unify(QQ.frac_field(x, y), QQ.poly_ring(x, z)) == QQ.frac_field(x, y, z)
|
| 286 |
+
|
| 287 |
+
def test_Domain_unify_algebraic():
|
| 288 |
+
sqrt5 = QQ.algebraic_field(sqrt(5))
|
| 289 |
+
sqrt7 = QQ.algebraic_field(sqrt(7))
|
| 290 |
+
sqrt57 = QQ.algebraic_field(sqrt(5), sqrt(7))
|
| 291 |
+
|
| 292 |
+
assert sqrt5.unify(sqrt7) == sqrt57
|
| 293 |
+
|
| 294 |
+
assert sqrt5.unify(sqrt5[x, y]) == sqrt5[x, y]
|
| 295 |
+
assert sqrt5[x, y].unify(sqrt5) == sqrt5[x, y]
|
| 296 |
+
|
| 297 |
+
assert sqrt5.unify(sqrt5.frac_field(x, y)) == sqrt5.frac_field(x, y)
|
| 298 |
+
assert sqrt5.frac_field(x, y).unify(sqrt5) == sqrt5.frac_field(x, y)
|
| 299 |
+
|
| 300 |
+
assert sqrt5.unify(sqrt7[x, y]) == sqrt57[x, y]
|
| 301 |
+
assert sqrt5[x, y].unify(sqrt7) == sqrt57[x, y]
|
| 302 |
+
|
| 303 |
+
assert sqrt5.unify(sqrt7.frac_field(x, y)) == sqrt57.frac_field(x, y)
|
| 304 |
+
assert sqrt5.frac_field(x, y).unify(sqrt7) == sqrt57.frac_field(x, y)
|
| 305 |
+
|
| 306 |
+
def test_Domain_unify_FiniteExtension():
|
| 307 |
+
KxZZ = FiniteExtension(Poly(x**2 - 2, x, domain=ZZ))
|
| 308 |
+
KxQQ = FiniteExtension(Poly(x**2 - 2, x, domain=QQ))
|
| 309 |
+
KxZZy = FiniteExtension(Poly(x**2 - 2, x, domain=ZZ[y]))
|
| 310 |
+
KxQQy = FiniteExtension(Poly(x**2 - 2, x, domain=QQ[y]))
|
| 311 |
+
|
| 312 |
+
assert KxZZ.unify(KxZZ) == KxZZ
|
| 313 |
+
assert KxQQ.unify(KxQQ) == KxQQ
|
| 314 |
+
assert KxZZy.unify(KxZZy) == KxZZy
|
| 315 |
+
assert KxQQy.unify(KxQQy) == KxQQy
|
| 316 |
+
|
| 317 |
+
assert KxZZ.unify(ZZ) == KxZZ
|
| 318 |
+
assert KxZZ.unify(QQ) == KxQQ
|
| 319 |
+
assert KxQQ.unify(ZZ) == KxQQ
|
| 320 |
+
assert KxQQ.unify(QQ) == KxQQ
|
| 321 |
+
|
| 322 |
+
assert KxZZ.unify(ZZ[y]) == KxZZy
|
| 323 |
+
assert KxZZ.unify(QQ[y]) == KxQQy
|
| 324 |
+
assert KxQQ.unify(ZZ[y]) == KxQQy
|
| 325 |
+
assert KxQQ.unify(QQ[y]) == KxQQy
|
| 326 |
+
|
| 327 |
+
assert KxZZy.unify(ZZ) == KxZZy
|
| 328 |
+
assert KxZZy.unify(QQ) == KxQQy
|
| 329 |
+
assert KxQQy.unify(ZZ) == KxQQy
|
| 330 |
+
assert KxQQy.unify(QQ) == KxQQy
|
| 331 |
+
|
| 332 |
+
assert KxZZy.unify(ZZ[y]) == KxZZy
|
| 333 |
+
assert KxZZy.unify(QQ[y]) == KxQQy
|
| 334 |
+
assert KxQQy.unify(ZZ[y]) == KxQQy
|
| 335 |
+
assert KxQQy.unify(QQ[y]) == KxQQy
|
| 336 |
+
|
| 337 |
+
K = FiniteExtension(Poly(x**2 - 2, x, domain=ZZ[y]))
|
| 338 |
+
assert K.unify(ZZ) == K
|
| 339 |
+
assert K.unify(ZZ[x]) == K
|
| 340 |
+
assert K.unify(ZZ[y]) == K
|
| 341 |
+
assert K.unify(ZZ[x, y]) == K
|
| 342 |
+
|
| 343 |
+
Kz = FiniteExtension(Poly(x**2 - 2, x, domain=ZZ[y, z]))
|
| 344 |
+
assert K.unify(ZZ[z]) == Kz
|
| 345 |
+
assert K.unify(ZZ[x, z]) == Kz
|
| 346 |
+
assert K.unify(ZZ[y, z]) == Kz
|
| 347 |
+
assert K.unify(ZZ[x, y, z]) == Kz
|
| 348 |
+
|
| 349 |
+
Kx = FiniteExtension(Poly(x**2 - 2, x, domain=ZZ))
|
| 350 |
+
Ky = FiniteExtension(Poly(y**2 - 2, y, domain=ZZ))
|
| 351 |
+
Kxy = FiniteExtension(Poly(y**2 - 2, y, domain=Kx))
|
| 352 |
+
assert Kx.unify(Kx) == Kx
|
| 353 |
+
assert Ky.unify(Ky) == Ky
|
| 354 |
+
assert Kx.unify(Ky) == Kxy
|
| 355 |
+
assert Ky.unify(Kx) == Kxy
|
| 356 |
+
|
| 357 |
+
def test_Domain_unify_with_symbols():
|
| 358 |
+
raises(UnificationFailed, lambda: ZZ[x, y].unify_with_symbols(ZZ, (y, z)))
|
| 359 |
+
raises(UnificationFailed, lambda: ZZ.unify_with_symbols(ZZ[x, y], (y, z)))
|
| 360 |
+
|
| 361 |
+
def test_Domain__contains__():
|
| 362 |
+
assert (0 in EX) is True
|
| 363 |
+
assert (0 in ZZ) is True
|
| 364 |
+
assert (0 in QQ) is True
|
| 365 |
+
assert (0 in RR) is True
|
| 366 |
+
assert (0 in CC) is True
|
| 367 |
+
assert (0 in ALG) is True
|
| 368 |
+
assert (0 in ZZ[x, y]) is True
|
| 369 |
+
assert (0 in QQ[x, y]) is True
|
| 370 |
+
assert (0 in RR[x, y]) is True
|
| 371 |
+
|
| 372 |
+
assert (-7 in EX) is True
|
| 373 |
+
assert (-7 in ZZ) is True
|
| 374 |
+
assert (-7 in QQ) is True
|
| 375 |
+
assert (-7 in RR) is True
|
| 376 |
+
assert (-7 in CC) is True
|
| 377 |
+
assert (-7 in ALG) is True
|
| 378 |
+
assert (-7 in ZZ[x, y]) is True
|
| 379 |
+
assert (-7 in QQ[x, y]) is True
|
| 380 |
+
assert (-7 in RR[x, y]) is True
|
| 381 |
+
|
| 382 |
+
assert (17 in EX) is True
|
| 383 |
+
assert (17 in ZZ) is True
|
| 384 |
+
assert (17 in QQ) is True
|
| 385 |
+
assert (17 in RR) is True
|
| 386 |
+
assert (17 in CC) is True
|
| 387 |
+
assert (17 in ALG) is True
|
| 388 |
+
assert (17 in ZZ[x, y]) is True
|
| 389 |
+
assert (17 in QQ[x, y]) is True
|
| 390 |
+
assert (17 in RR[x, y]) is True
|
| 391 |
+
|
| 392 |
+
assert (Rational(-1, 7) in EX) is True
|
| 393 |
+
assert (Rational(-1, 7) in ZZ) is False
|
| 394 |
+
assert (Rational(-1, 7) in QQ) is True
|
| 395 |
+
assert (Rational(-1, 7) in RR) is True
|
| 396 |
+
assert (Rational(-1, 7) in CC) is True
|
| 397 |
+
assert (Rational(-1, 7) in ALG) is True
|
| 398 |
+
assert (Rational(-1, 7) in ZZ[x, y]) is False
|
| 399 |
+
assert (Rational(-1, 7) in QQ[x, y]) is True
|
| 400 |
+
assert (Rational(-1, 7) in RR[x, y]) is True
|
| 401 |
+
|
| 402 |
+
assert (Rational(3, 5) in EX) is True
|
| 403 |
+
assert (Rational(3, 5) in ZZ) is False
|
| 404 |
+
assert (Rational(3, 5) in QQ) is True
|
| 405 |
+
assert (Rational(3, 5) in RR) is True
|
| 406 |
+
assert (Rational(3, 5) in CC) is True
|
| 407 |
+
assert (Rational(3, 5) in ALG) is True
|
| 408 |
+
assert (Rational(3, 5) in ZZ[x, y]) is False
|
| 409 |
+
assert (Rational(3, 5) in QQ[x, y]) is True
|
| 410 |
+
assert (Rational(3, 5) in RR[x, y]) is True
|
| 411 |
+
|
| 412 |
+
assert (3.0 in EX) is True
|
| 413 |
+
assert (3.0 in ZZ) is True
|
| 414 |
+
assert (3.0 in QQ) is True
|
| 415 |
+
assert (3.0 in RR) is True
|
| 416 |
+
assert (3.0 in CC) is True
|
| 417 |
+
assert (3.0 in ALG) is True
|
| 418 |
+
assert (3.0 in ZZ[x, y]) is True
|
| 419 |
+
assert (3.0 in QQ[x, y]) is True
|
| 420 |
+
assert (3.0 in RR[x, y]) is True
|
| 421 |
+
|
| 422 |
+
assert (3.14 in EX) is True
|
| 423 |
+
assert (3.14 in ZZ) is False
|
| 424 |
+
assert (3.14 in QQ) is True
|
| 425 |
+
assert (3.14 in RR) is True
|
| 426 |
+
assert (3.14 in CC) is True
|
| 427 |
+
assert (3.14 in ALG) is True
|
| 428 |
+
assert (3.14 in ZZ[x, y]) is False
|
| 429 |
+
assert (3.14 in QQ[x, y]) is True
|
| 430 |
+
assert (3.14 in RR[x, y]) is True
|
| 431 |
+
|
| 432 |
+
assert (oo in ALG) is False
|
| 433 |
+
assert (oo in ZZ[x, y]) is False
|
| 434 |
+
assert (oo in QQ[x, y]) is False
|
| 435 |
+
|
| 436 |
+
assert (-oo in ZZ) is False
|
| 437 |
+
assert (-oo in QQ) is False
|
| 438 |
+
assert (-oo in ALG) is False
|
| 439 |
+
assert (-oo in ZZ[x, y]) is False
|
| 440 |
+
assert (-oo in QQ[x, y]) is False
|
| 441 |
+
|
| 442 |
+
assert (sqrt(7) in EX) is True
|
| 443 |
+
assert (sqrt(7) in ZZ) is False
|
| 444 |
+
assert (sqrt(7) in QQ) is False
|
| 445 |
+
assert (sqrt(7) in RR) is True
|
| 446 |
+
assert (sqrt(7) in CC) is True
|
| 447 |
+
assert (sqrt(7) in ALG) is False
|
| 448 |
+
assert (sqrt(7) in ZZ[x, y]) is False
|
| 449 |
+
assert (sqrt(7) in QQ[x, y]) is False
|
| 450 |
+
assert (sqrt(7) in RR[x, y]) is True
|
| 451 |
+
|
| 452 |
+
assert (2*sqrt(3) + 1 in EX) is True
|
| 453 |
+
assert (2*sqrt(3) + 1 in ZZ) is False
|
| 454 |
+
assert (2*sqrt(3) + 1 in QQ) is False
|
| 455 |
+
assert (2*sqrt(3) + 1 in RR) is True
|
| 456 |
+
assert (2*sqrt(3) + 1 in CC) is True
|
| 457 |
+
assert (2*sqrt(3) + 1 in ALG) is True
|
| 458 |
+
assert (2*sqrt(3) + 1 in ZZ[x, y]) is False
|
| 459 |
+
assert (2*sqrt(3) + 1 in QQ[x, y]) is False
|
| 460 |
+
assert (2*sqrt(3) + 1 in RR[x, y]) is True
|
| 461 |
+
|
| 462 |
+
assert (sin(1) in EX) is True
|
| 463 |
+
assert (sin(1) in ZZ) is False
|
| 464 |
+
assert (sin(1) in QQ) is False
|
| 465 |
+
assert (sin(1) in RR) is True
|
| 466 |
+
assert (sin(1) in CC) is True
|
| 467 |
+
assert (sin(1) in ALG) is False
|
| 468 |
+
assert (sin(1) in ZZ[x, y]) is False
|
| 469 |
+
assert (sin(1) in QQ[x, y]) is False
|
| 470 |
+
assert (sin(1) in RR[x, y]) is True
|
| 471 |
+
|
| 472 |
+
assert (x**2 + 1 in EX) is True
|
| 473 |
+
assert (x**2 + 1 in ZZ) is False
|
| 474 |
+
assert (x**2 + 1 in QQ) is False
|
| 475 |
+
assert (x**2 + 1 in RR) is False
|
| 476 |
+
assert (x**2 + 1 in CC) is False
|
| 477 |
+
assert (x**2 + 1 in ALG) is False
|
| 478 |
+
assert (x**2 + 1 in ZZ[x]) is True
|
| 479 |
+
assert (x**2 + 1 in QQ[x]) is True
|
| 480 |
+
assert (x**2 + 1 in RR[x]) is True
|
| 481 |
+
assert (x**2 + 1 in ZZ[x, y]) is True
|
| 482 |
+
assert (x**2 + 1 in QQ[x, y]) is True
|
| 483 |
+
assert (x**2 + 1 in RR[x, y]) is True
|
| 484 |
+
|
| 485 |
+
assert (x**2 + y**2 in EX) is True
|
| 486 |
+
assert (x**2 + y**2 in ZZ) is False
|
| 487 |
+
assert (x**2 + y**2 in QQ) is False
|
| 488 |
+
assert (x**2 + y**2 in RR) is False
|
| 489 |
+
assert (x**2 + y**2 in CC) is False
|
| 490 |
+
assert (x**2 + y**2 in ALG) is False
|
| 491 |
+
assert (x**2 + y**2 in ZZ[x]) is False
|
| 492 |
+
assert (x**2 + y**2 in QQ[x]) is False
|
| 493 |
+
assert (x**2 + y**2 in RR[x]) is False
|
| 494 |
+
assert (x**2 + y**2 in ZZ[x, y]) is True
|
| 495 |
+
assert (x**2 + y**2 in QQ[x, y]) is True
|
| 496 |
+
assert (x**2 + y**2 in RR[x, y]) is True
|
| 497 |
+
|
| 498 |
+
assert (Rational(3, 2)*x/(y + 1) - z in QQ[x, y, z]) is False
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
def test_issue_14433():
|
| 502 |
+
assert (Rational(2, 3)*x in QQ.frac_field(1/x)) is True
|
| 503 |
+
assert (1/x in QQ.frac_field(x)) is True
|
| 504 |
+
assert ((x**2 + y**2) in QQ.frac_field(1/x, 1/y)) is True
|
| 505 |
+
assert ((x + y) in QQ.frac_field(1/x, y)) is True
|
| 506 |
+
assert ((x - y) in QQ.frac_field(x, 1/y)) is True
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
def test_Domain_is_field():
|
| 510 |
+
assert ZZ.is_Field is False
|
| 511 |
+
assert GF(5).is_Field is True
|
| 512 |
+
assert GF(6).is_Field is False
|
| 513 |
+
assert QQ.is_Field is True
|
| 514 |
+
assert RR.is_Field is True
|
| 515 |
+
assert CC.is_Field is True
|
| 516 |
+
assert EX.is_Field is True
|
| 517 |
+
assert ALG.is_Field is True
|
| 518 |
+
assert QQ[x].is_Field is False
|
| 519 |
+
assert ZZ.frac_field(x).is_Field is True
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
def test_Domain_get_ring():
|
| 523 |
+
assert ZZ.has_assoc_Ring is True
|
| 524 |
+
assert QQ.has_assoc_Ring is True
|
| 525 |
+
assert ZZ[x].has_assoc_Ring is True
|
| 526 |
+
assert QQ[x].has_assoc_Ring is True
|
| 527 |
+
assert ZZ[x, y].has_assoc_Ring is True
|
| 528 |
+
assert QQ[x, y].has_assoc_Ring is True
|
| 529 |
+
assert ZZ.frac_field(x).has_assoc_Ring is True
|
| 530 |
+
assert QQ.frac_field(x).has_assoc_Ring is True
|
| 531 |
+
assert ZZ.frac_field(x, y).has_assoc_Ring is True
|
| 532 |
+
assert QQ.frac_field(x, y).has_assoc_Ring is True
|
| 533 |
+
|
| 534 |
+
assert EX.has_assoc_Ring is False
|
| 535 |
+
assert RR.has_assoc_Ring is False
|
| 536 |
+
assert ALG.has_assoc_Ring is False
|
| 537 |
+
|
| 538 |
+
assert ZZ.get_ring() == ZZ
|
| 539 |
+
assert QQ.get_ring() == ZZ
|
| 540 |
+
assert ZZ[x].get_ring() == ZZ[x]
|
| 541 |
+
assert QQ[x].get_ring() == QQ[x]
|
| 542 |
+
assert ZZ[x, y].get_ring() == ZZ[x, y]
|
| 543 |
+
assert QQ[x, y].get_ring() == QQ[x, y]
|
| 544 |
+
assert ZZ.frac_field(x).get_ring() == ZZ[x]
|
| 545 |
+
assert QQ.frac_field(x).get_ring() == QQ[x]
|
| 546 |
+
assert ZZ.frac_field(x, y).get_ring() == ZZ[x, y]
|
| 547 |
+
assert QQ.frac_field(x, y).get_ring() == QQ[x, y]
|
| 548 |
+
|
| 549 |
+
assert EX.get_ring() == EX
|
| 550 |
+
|
| 551 |
+
assert RR.get_ring() == RR
|
| 552 |
+
# XXX: This should also be like RR
|
| 553 |
+
raises(DomainError, lambda: ALG.get_ring())
|
| 554 |
+
|
| 555 |
+
|
| 556 |
+
def test_Domain_get_field():
|
| 557 |
+
assert EX.has_assoc_Field is True
|
| 558 |
+
assert ZZ.has_assoc_Field is True
|
| 559 |
+
assert QQ.has_assoc_Field is True
|
| 560 |
+
assert RR.has_assoc_Field is True
|
| 561 |
+
assert ALG.has_assoc_Field is True
|
| 562 |
+
assert ZZ[x].has_assoc_Field is True
|
| 563 |
+
assert QQ[x].has_assoc_Field is True
|
| 564 |
+
assert ZZ[x, y].has_assoc_Field is True
|
| 565 |
+
assert QQ[x, y].has_assoc_Field is True
|
| 566 |
+
|
| 567 |
+
assert EX.get_field() == EX
|
| 568 |
+
assert ZZ.get_field() == QQ
|
| 569 |
+
assert QQ.get_field() == QQ
|
| 570 |
+
assert RR.get_field() == RR
|
| 571 |
+
assert ALG.get_field() == ALG
|
| 572 |
+
assert ZZ[x].get_field() == ZZ.frac_field(x)
|
| 573 |
+
assert QQ[x].get_field() == QQ.frac_field(x)
|
| 574 |
+
assert ZZ[x, y].get_field() == ZZ.frac_field(x, y)
|
| 575 |
+
assert QQ[x, y].get_field() == QQ.frac_field(x, y)
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
def test_Domain_set_domain():
|
| 579 |
+
doms = [GF(5), ZZ, QQ, ALG, RR, CC, EX, ZZ[z], QQ[z], RR[z], CC[z], EX[z]]
|
| 580 |
+
for D1 in doms:
|
| 581 |
+
for D2 in doms:
|
| 582 |
+
assert D1[x].set_domain(D2) == D2[x]
|
| 583 |
+
assert D1[x, y].set_domain(D2) == D2[x, y]
|
| 584 |
+
assert D1.frac_field(x).set_domain(D2) == D2.frac_field(x)
|
| 585 |
+
assert D1.frac_field(x, y).set_domain(D2) == D2.frac_field(x, y)
|
| 586 |
+
assert D1.old_poly_ring(x).set_domain(D2) == D2.old_poly_ring(x)
|
| 587 |
+
assert D1.old_poly_ring(x, y).set_domain(D2) == D2.old_poly_ring(x, y)
|
| 588 |
+
assert D1.old_frac_field(x).set_domain(D2) == D2.old_frac_field(x)
|
| 589 |
+
assert D1.old_frac_field(x, y).set_domain(D2) == D2.old_frac_field(x, y)
|
| 590 |
+
|
| 591 |
+
|
| 592 |
+
def test_Domain_is_Exact():
|
| 593 |
+
exact = [GF(5), ZZ, QQ, ALG, EX]
|
| 594 |
+
inexact = [RR, CC]
|
| 595 |
+
for D in exact + inexact:
|
| 596 |
+
for R in D, D[x], D.frac_field(x), D.old_poly_ring(x), D.old_frac_field(x):
|
| 597 |
+
if D in exact:
|
| 598 |
+
assert R.is_Exact is True
|
| 599 |
+
else:
|
| 600 |
+
assert R.is_Exact is False
|
| 601 |
+
|
| 602 |
+
|
| 603 |
+
def test_Domain_get_exact():
|
| 604 |
+
assert EX.get_exact() == EX
|
| 605 |
+
assert ZZ.get_exact() == ZZ
|
| 606 |
+
assert QQ.get_exact() == QQ
|
| 607 |
+
assert RR.get_exact() == QQ
|
| 608 |
+
assert CC.get_exact() == QQ_I
|
| 609 |
+
assert ALG.get_exact() == ALG
|
| 610 |
+
assert ZZ[x].get_exact() == ZZ[x]
|
| 611 |
+
assert QQ[x].get_exact() == QQ[x]
|
| 612 |
+
assert RR[x].get_exact() == QQ[x]
|
| 613 |
+
assert CC[x].get_exact() == QQ_I[x]
|
| 614 |
+
assert ZZ[x, y].get_exact() == ZZ[x, y]
|
| 615 |
+
assert QQ[x, y].get_exact() == QQ[x, y]
|
| 616 |
+
assert RR[x, y].get_exact() == QQ[x, y]
|
| 617 |
+
assert CC[x, y].get_exact() == QQ_I[x, y]
|
| 618 |
+
assert ZZ.frac_field(x).get_exact() == ZZ.frac_field(x)
|
| 619 |
+
assert QQ.frac_field(x).get_exact() == QQ.frac_field(x)
|
| 620 |
+
assert RR.frac_field(x).get_exact() == QQ.frac_field(x)
|
| 621 |
+
assert CC.frac_field(x).get_exact() == QQ_I.frac_field(x)
|
| 622 |
+
assert ZZ.frac_field(x, y).get_exact() == ZZ.frac_field(x, y)
|
| 623 |
+
assert QQ.frac_field(x, y).get_exact() == QQ.frac_field(x, y)
|
| 624 |
+
assert RR.frac_field(x, y).get_exact() == QQ.frac_field(x, y)
|
| 625 |
+
assert CC.frac_field(x, y).get_exact() == QQ_I.frac_field(x, y)
|
| 626 |
+
assert ZZ.old_poly_ring(x).get_exact() == ZZ.old_poly_ring(x)
|
| 627 |
+
assert QQ.old_poly_ring(x).get_exact() == QQ.old_poly_ring(x)
|
| 628 |
+
assert RR.old_poly_ring(x).get_exact() == QQ.old_poly_ring(x)
|
| 629 |
+
assert CC.old_poly_ring(x).get_exact() == QQ_I.old_poly_ring(x)
|
| 630 |
+
assert ZZ.old_poly_ring(x, y).get_exact() == ZZ.old_poly_ring(x, y)
|
| 631 |
+
assert QQ.old_poly_ring(x, y).get_exact() == QQ.old_poly_ring(x, y)
|
| 632 |
+
assert RR.old_poly_ring(x, y).get_exact() == QQ.old_poly_ring(x, y)
|
| 633 |
+
assert CC.old_poly_ring(x, y).get_exact() == QQ_I.old_poly_ring(x, y)
|
| 634 |
+
assert ZZ.old_frac_field(x).get_exact() == ZZ.old_frac_field(x)
|
| 635 |
+
assert QQ.old_frac_field(x).get_exact() == QQ.old_frac_field(x)
|
| 636 |
+
assert RR.old_frac_field(x).get_exact() == QQ.old_frac_field(x)
|
| 637 |
+
assert CC.old_frac_field(x).get_exact() == QQ_I.old_frac_field(x)
|
| 638 |
+
assert ZZ.old_frac_field(x, y).get_exact() == ZZ.old_frac_field(x, y)
|
| 639 |
+
assert QQ.old_frac_field(x, y).get_exact() == QQ.old_frac_field(x, y)
|
| 640 |
+
assert RR.old_frac_field(x, y).get_exact() == QQ.old_frac_field(x, y)
|
| 641 |
+
assert CC.old_frac_field(x, y).get_exact() == QQ_I.old_frac_field(x, y)
|
| 642 |
+
|
| 643 |
+
|
| 644 |
+
def test_Domain_characteristic():
|
| 645 |
+
for F, c in [(FF(3), 3), (FF(5), 5), (FF(7), 7)]:
|
| 646 |
+
for R in F, F[x], F.frac_field(x), F.old_poly_ring(x), F.old_frac_field(x):
|
| 647 |
+
assert R.has_CharacteristicZero is False
|
| 648 |
+
assert R.characteristic() == c
|
| 649 |
+
for D in ZZ, QQ, ZZ_I, QQ_I, ALG:
|
| 650 |
+
for R in D, D[x], D.frac_field(x), D.old_poly_ring(x), D.old_frac_field(x):
|
| 651 |
+
assert R.has_CharacteristicZero is True
|
| 652 |
+
assert R.characteristic() == 0
|
| 653 |
+
|
| 654 |
+
|
| 655 |
+
def test_Domain_is_unit():
|
| 656 |
+
nums = [-2, -1, 0, 1, 2]
|
| 657 |
+
invring = [False, True, False, True, False]
|
| 658 |
+
invfield = [True, True, False, True, True]
|
| 659 |
+
ZZx, QQx, QQxf = ZZ[x], QQ[x], QQ.frac_field(x)
|
| 660 |
+
assert [ZZ.is_unit(ZZ(n)) for n in nums] == invring
|
| 661 |
+
assert [QQ.is_unit(QQ(n)) for n in nums] == invfield
|
| 662 |
+
assert [ZZx.is_unit(ZZx(n)) for n in nums] == invring
|
| 663 |
+
assert [QQx.is_unit(QQx(n)) for n in nums] == invfield
|
| 664 |
+
assert [QQxf.is_unit(QQxf(n)) for n in nums] == invfield
|
| 665 |
+
assert ZZx.is_unit(ZZx(x)) is False
|
| 666 |
+
assert QQx.is_unit(QQx(x)) is False
|
| 667 |
+
assert QQxf.is_unit(QQxf(x)) is True
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
def test_Domain_convert():
|
| 671 |
+
|
| 672 |
+
def check_element(e1, e2, K1, K2, K3):
|
| 673 |
+
if isinstance(e1, PolyElement):
|
| 674 |
+
assert isinstance(e2, PolyElement) and e1.ring == e2.ring
|
| 675 |
+
elif isinstance(e1, FracElement):
|
| 676 |
+
assert isinstance(e2, FracElement) and e1.field == e2.field
|
| 677 |
+
else:
|
| 678 |
+
assert type(e1) is type(e2), '%s, %s: %s %s -> %s' % (e1, e2, K1, K2, K3)
|
| 679 |
+
assert e1 == e2, '%s, %s: %s %s -> %s' % (e1, e2, K1, K2, K3)
|
| 680 |
+
|
| 681 |
+
def check_domains(K1, K2):
|
| 682 |
+
K3 = K1.unify(K2)
|
| 683 |
+
check_element(K3.convert_from(K1.one, K1), K3.one, K1, K2, K3)
|
| 684 |
+
check_element(K3.convert_from(K2.one, K2), K3.one, K1, K2, K3)
|
| 685 |
+
check_element(K3.convert_from(K1.zero, K1), K3.zero, K1, K2, K3)
|
| 686 |
+
check_element(K3.convert_from(K2.zero, K2), K3.zero, K1, K2, K3)
|
| 687 |
+
|
| 688 |
+
def composite_domains(K):
|
| 689 |
+
domains = [
|
| 690 |
+
K,
|
| 691 |
+
K[y], K[z], K[y, z],
|
| 692 |
+
K.frac_field(y), K.frac_field(z), K.frac_field(y, z),
|
| 693 |
+
# XXX: These should be tested and made to work...
|
| 694 |
+
# K.old_poly_ring(y), K.old_frac_field(y),
|
| 695 |
+
]
|
| 696 |
+
return domains
|
| 697 |
+
|
| 698 |
+
QQ2 = QQ.algebraic_field(sqrt(2))
|
| 699 |
+
QQ3 = QQ.algebraic_field(sqrt(3))
|
| 700 |
+
doms = [ZZ, QQ, QQ2, QQ3, QQ_I, ZZ_I, RR, CC]
|
| 701 |
+
|
| 702 |
+
for i, K1 in enumerate(doms):
|
| 703 |
+
for K2 in doms[i:]:
|
| 704 |
+
for K3 in composite_domains(K1):
|
| 705 |
+
for K4 in composite_domains(K2):
|
| 706 |
+
check_domains(K3, K4)
|
| 707 |
+
|
| 708 |
+
assert QQ.convert(10e-52) == QQ(1684996666696915, 1684996666696914987166688442938726917102321526408785780068975640576)
|
| 709 |
+
|
| 710 |
+
R, xr = ring("x", ZZ)
|
| 711 |
+
assert ZZ.convert(xr - xr) == 0
|
| 712 |
+
assert ZZ.convert(xr - xr, R.to_domain()) == 0
|
| 713 |
+
|
| 714 |
+
assert CC.convert(ZZ_I(1, 2)) == CC(1, 2)
|
| 715 |
+
assert CC.convert(QQ_I(1, 2)) == CC(1, 2)
|
| 716 |
+
|
| 717 |
+
assert QQ.convert_from(RR(0.5), RR) == QQ(1, 2)
|
| 718 |
+
assert RR.convert_from(QQ(1, 2), QQ) == RR(0.5)
|
| 719 |
+
assert QQ_I.convert_from(CC(0.5, 0.75), CC) == QQ_I(QQ(1, 2), QQ(3, 4))
|
| 720 |
+
assert CC.convert_from(QQ_I(QQ(1, 2), QQ(3, 4)), QQ_I) == CC(0.5, 0.75)
|
| 721 |
+
|
| 722 |
+
K1 = QQ.frac_field(x)
|
| 723 |
+
K2 = ZZ.frac_field(x)
|
| 724 |
+
K3 = QQ[x]
|
| 725 |
+
K4 = ZZ[x]
|
| 726 |
+
Ks = [K1, K2, K3, K4]
|
| 727 |
+
for Ka, Kb in product(Ks, Ks):
|
| 728 |
+
assert Ka.convert_from(Kb.from_sympy(x), Kb) == Ka.from_sympy(x)
|
| 729 |
+
|
| 730 |
+
assert K2.convert_from(QQ(1, 2), QQ) == K2(QQ(1, 2))
|
| 731 |
+
|
| 732 |
+
|
| 733 |
+
def test_EX_convert():
|
| 734 |
+
|
| 735 |
+
elements = [
|
| 736 |
+
(ZZ, ZZ(3)),
|
| 737 |
+
(QQ, QQ(1,2)),
|
| 738 |
+
(ZZ_I, ZZ_I(1,2)),
|
| 739 |
+
(QQ_I, QQ_I(1,2)),
|
| 740 |
+
(RR, RR(3)),
|
| 741 |
+
(CC, CC(1,2)),
|
| 742 |
+
(EX, EX(3)),
|
| 743 |
+
(EXRAW, EXRAW(3)),
|
| 744 |
+
(ALG, ALG.from_sympy(sqrt(2))),
|
| 745 |
+
]
|
| 746 |
+
|
| 747 |
+
for R, e in elements:
|
| 748 |
+
for EE in EX, EXRAW:
|
| 749 |
+
elem = EE.from_sympy(R.to_sympy(e))
|
| 750 |
+
assert EE.convert_from(e, R) == elem
|
| 751 |
+
assert R.convert_from(elem, EE) == e
|
| 752 |
+
|
| 753 |
+
|
| 754 |
+
def test_GlobalPolynomialRing_convert():
|
| 755 |
+
K1 = QQ.old_poly_ring(x)
|
| 756 |
+
K2 = QQ[x]
|
| 757 |
+
assert K1.convert(x) == K1.convert(K2.convert(x), K2)
|
| 758 |
+
assert K2.convert(x) == K2.convert(K1.convert(x), K1)
|
| 759 |
+
|
| 760 |
+
K1 = QQ.old_poly_ring(x, y)
|
| 761 |
+
K2 = QQ[x]
|
| 762 |
+
assert K1.convert(x) == K1.convert(K2.convert(x), K2)
|
| 763 |
+
#assert K2.convert(x) == K2.convert(K1.convert(x), K1)
|
| 764 |
+
|
| 765 |
+
K1 = ZZ.old_poly_ring(x, y)
|
| 766 |
+
K2 = QQ[x]
|
| 767 |
+
assert K1.convert(x) == K1.convert(K2.convert(x), K2)
|
| 768 |
+
#assert K2.convert(x) == K2.convert(K1.convert(x), K1)
|
| 769 |
+
|
| 770 |
+
|
| 771 |
+
def test_PolynomialRing__init():
|
| 772 |
+
R, = ring("", ZZ)
|
| 773 |
+
assert ZZ.poly_ring() == R.to_domain()
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
def test_FractionField__init():
|
| 777 |
+
F, = field("", ZZ)
|
| 778 |
+
assert ZZ.frac_field() == F.to_domain()
|
| 779 |
+
|
| 780 |
+
|
| 781 |
+
def test_FractionField_convert():
|
| 782 |
+
K = QQ.frac_field(x)
|
| 783 |
+
assert K.convert(QQ(2, 3), QQ) == K.from_sympy(Rational(2, 3))
|
| 784 |
+
K = QQ.frac_field(x)
|
| 785 |
+
assert K.convert(ZZ(2), ZZ) == K.from_sympy(Integer(2))
|
| 786 |
+
|
| 787 |
+
|
| 788 |
+
def test_inject():
|
| 789 |
+
assert ZZ.inject(x, y, z) == ZZ[x, y, z]
|
| 790 |
+
assert ZZ[x].inject(y, z) == ZZ[x, y, z]
|
| 791 |
+
assert ZZ.frac_field(x).inject(y, z) == ZZ.frac_field(x, y, z)
|
| 792 |
+
raises(GeneratorsError, lambda: ZZ[x].inject(x))
|
| 793 |
+
|
| 794 |
+
|
| 795 |
+
def test_drop():
|
| 796 |
+
assert ZZ.drop(x) == ZZ
|
| 797 |
+
assert ZZ[x].drop(x) == ZZ
|
| 798 |
+
assert ZZ[x, y].drop(x) == ZZ[y]
|
| 799 |
+
assert ZZ.frac_field(x).drop(x) == ZZ
|
| 800 |
+
assert ZZ.frac_field(x, y).drop(x) == ZZ.frac_field(y)
|
| 801 |
+
assert ZZ[x][y].drop(y) == ZZ[x]
|
| 802 |
+
assert ZZ[x][y].drop(x) == ZZ[y]
|
| 803 |
+
assert ZZ.frac_field(x)[y].drop(x) == ZZ[y]
|
| 804 |
+
assert ZZ.frac_field(x)[y].drop(y) == ZZ.frac_field(x)
|
| 805 |
+
Ky = FiniteExtension(Poly(x**2-1, x, domain=ZZ[y]))
|
| 806 |
+
K = FiniteExtension(Poly(x**2-1, x, domain=ZZ))
|
| 807 |
+
assert Ky.drop(y) == K
|
| 808 |
+
raises(GeneratorsError, lambda: Ky.drop(x))
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
def test_Domain_map():
|
| 812 |
+
seq = ZZ.map([1, 2, 3, 4])
|
| 813 |
+
|
| 814 |
+
assert all(ZZ.of_type(elt) for elt in seq)
|
| 815 |
+
|
| 816 |
+
seq = ZZ.map([[1, 2, 3, 4]])
|
| 817 |
+
|
| 818 |
+
assert all(ZZ.of_type(elt) for elt in seq[0]) and len(seq) == 1
|
| 819 |
+
|
| 820 |
+
|
| 821 |
+
def test_Domain___eq__():
|
| 822 |
+
assert (ZZ[x, y] == ZZ[x, y]) is True
|
| 823 |
+
assert (QQ[x, y] == QQ[x, y]) is True
|
| 824 |
+
|
| 825 |
+
assert (ZZ[x, y] == QQ[x, y]) is False
|
| 826 |
+
assert (QQ[x, y] == ZZ[x, y]) is False
|
| 827 |
+
|
| 828 |
+
assert (ZZ.frac_field(x, y) == ZZ.frac_field(x, y)) is True
|
| 829 |
+
assert (QQ.frac_field(x, y) == QQ.frac_field(x, y)) is True
|
| 830 |
+
|
| 831 |
+
assert (ZZ.frac_field(x, y) == QQ.frac_field(x, y)) is False
|
| 832 |
+
assert (QQ.frac_field(x, y) == ZZ.frac_field(x, y)) is False
|
| 833 |
+
|
| 834 |
+
assert RealField()[x] == RR[x]
|
| 835 |
+
|
| 836 |
+
|
| 837 |
+
def test_Domain__algebraic_field():
|
| 838 |
+
alg = ZZ.algebraic_field(sqrt(2))
|
| 839 |
+
assert alg.ext.minpoly == Poly(x**2 - 2)
|
| 840 |
+
assert alg.dom == QQ
|
| 841 |
+
|
| 842 |
+
alg = QQ.algebraic_field(sqrt(2))
|
| 843 |
+
assert alg.ext.minpoly == Poly(x**2 - 2)
|
| 844 |
+
assert alg.dom == QQ
|
| 845 |
+
|
| 846 |
+
alg = alg.algebraic_field(sqrt(3))
|
| 847 |
+
assert alg.ext.minpoly == Poly(x**4 - 10*x**2 + 1)
|
| 848 |
+
assert alg.dom == QQ
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
def test_Domain_alg_field_from_poly():
|
| 852 |
+
f = Poly(x**2 - 2)
|
| 853 |
+
g = Poly(x**2 - 3)
|
| 854 |
+
h = Poly(x**4 - 10*x**2 + 1)
|
| 855 |
+
|
| 856 |
+
alg = ZZ.alg_field_from_poly(f)
|
| 857 |
+
assert alg.ext.minpoly == f
|
| 858 |
+
assert alg.dom == QQ
|
| 859 |
+
|
| 860 |
+
alg = QQ.alg_field_from_poly(f)
|
| 861 |
+
assert alg.ext.minpoly == f
|
| 862 |
+
assert alg.dom == QQ
|
| 863 |
+
|
| 864 |
+
alg = alg.alg_field_from_poly(g)
|
| 865 |
+
assert alg.ext.minpoly == h
|
| 866 |
+
assert alg.dom == QQ
|
| 867 |
+
|
| 868 |
+
|
| 869 |
+
def test_Domain_cyclotomic_field():
|
| 870 |
+
K = ZZ.cyclotomic_field(12)
|
| 871 |
+
assert K.ext.minpoly == Poly(cyclotomic_poly(12))
|
| 872 |
+
assert K.dom == QQ
|
| 873 |
+
|
| 874 |
+
F = QQ.cyclotomic_field(3)
|
| 875 |
+
assert F.ext.minpoly == Poly(cyclotomic_poly(3))
|
| 876 |
+
assert F.dom == QQ
|
| 877 |
+
|
| 878 |
+
E = F.cyclotomic_field(4)
|
| 879 |
+
assert field_isomorphism(E.ext, K.ext) is not None
|
| 880 |
+
assert E.dom == QQ
|
| 881 |
+
|
| 882 |
+
|
| 883 |
+
def test_PolynomialRing_from_FractionField():
|
| 884 |
+
F, x,y = field("x,y", ZZ)
|
| 885 |
+
R, X,Y = ring("x,y", ZZ)
|
| 886 |
+
|
| 887 |
+
f = (x**2 + y**2)/(x + 1)
|
| 888 |
+
g = (x**2 + y**2)/4
|
| 889 |
+
h = x**2 + y**2
|
| 890 |
+
|
| 891 |
+
assert R.to_domain().from_FractionField(f, F.to_domain()) is None
|
| 892 |
+
assert R.to_domain().from_FractionField(g, F.to_domain()) == X**2/4 + Y**2/4
|
| 893 |
+
assert R.to_domain().from_FractionField(h, F.to_domain()) == X**2 + Y**2
|
| 894 |
+
|
| 895 |
+
F, x,y = field("x,y", QQ)
|
| 896 |
+
R, X,Y = ring("x,y", QQ)
|
| 897 |
+
|
| 898 |
+
f = (x**2 + y**2)/(x + 1)
|
| 899 |
+
g = (x**2 + y**2)/4
|
| 900 |
+
h = x**2 + y**2
|
| 901 |
+
|
| 902 |
+
assert R.to_domain().from_FractionField(f, F.to_domain()) is None
|
| 903 |
+
assert R.to_domain().from_FractionField(g, F.to_domain()) == X**2/4 + Y**2/4
|
| 904 |
+
assert R.to_domain().from_FractionField(h, F.to_domain()) == X**2 + Y**2
|
| 905 |
+
|
| 906 |
+
|
| 907 |
+
def test_FractionField_from_PolynomialRing():
|
| 908 |
+
R, x,y = ring("x,y", QQ)
|
| 909 |
+
F, X,Y = field("x,y", ZZ)
|
| 910 |
+
|
| 911 |
+
f = 3*x**2 + 5*y**2
|
| 912 |
+
g = x**2/3 + y**2/5
|
| 913 |
+
|
| 914 |
+
assert F.to_domain().from_PolynomialRing(f, R.to_domain()) == 3*X**2 + 5*Y**2
|
| 915 |
+
assert F.to_domain().from_PolynomialRing(g, R.to_domain()) == (5*X**2 + 3*Y**2)/15
|
| 916 |
+
|
| 917 |
+
|
| 918 |
+
def test_FF_of_type():
|
| 919 |
+
# XXX: of_type is not very useful here because in the case of ground types
|
| 920 |
+
# = flint all elements are of type nmod.
|
| 921 |
+
assert FF(3).of_type(FF(3)(1)) is True
|
| 922 |
+
assert FF(5).of_type(FF(5)(3)) is True
|
| 923 |
+
|
| 924 |
+
|
| 925 |
+
def test___eq__():
|
| 926 |
+
assert not QQ[x] == ZZ[x]
|
| 927 |
+
assert not QQ.frac_field(x) == ZZ.frac_field(x)
|
| 928 |
+
|
| 929 |
+
|
| 930 |
+
def test_RealField_from_sympy():
|
| 931 |
+
assert RR.convert(S.Zero) == RR.dtype(0)
|
| 932 |
+
assert RR.convert(S(0.0)) == RR.dtype(0.0)
|
| 933 |
+
assert RR.convert(S.One) == RR.dtype(1)
|
| 934 |
+
assert RR.convert(S(1.0)) == RR.dtype(1.0)
|
| 935 |
+
assert RR.convert(sin(1)) == RR.dtype(sin(1).evalf())
|
| 936 |
+
|
| 937 |
+
|
| 938 |
+
def test_not_in_any_domain():
|
| 939 |
+
check = list(_illegal) + [x] + [
|
| 940 |
+
float(i) for i in _illegal[:3]]
|
| 941 |
+
for dom in (ZZ, QQ, RR, CC, EX):
|
| 942 |
+
for i in check:
|
| 943 |
+
if i == x and dom == EX:
|
| 944 |
+
continue
|
| 945 |
+
assert i not in dom, (i, dom)
|
| 946 |
+
raises(CoercionFailed, lambda: dom.convert(i))
|
| 947 |
+
|
| 948 |
+
|
| 949 |
+
def test_ModularInteger():
|
| 950 |
+
F3 = FF(3)
|
| 951 |
+
|
| 952 |
+
a = F3(0)
|
| 953 |
+
assert F3.of_type(a) and a == 0
|
| 954 |
+
a = F3(1)
|
| 955 |
+
assert F3.of_type(a) and a == 1
|
| 956 |
+
a = F3(2)
|
| 957 |
+
assert F3.of_type(a) and a == 2
|
| 958 |
+
a = F3(3)
|
| 959 |
+
assert F3.of_type(a) and a == 0
|
| 960 |
+
a = F3(4)
|
| 961 |
+
assert F3.of_type(a) and a == 1
|
| 962 |
+
|
| 963 |
+
a = F3(F3(0))
|
| 964 |
+
assert F3.of_type(a) and a == 0
|
| 965 |
+
a = F3(F3(1))
|
| 966 |
+
assert F3.of_type(a) and a == 1
|
| 967 |
+
a = F3(F3(2))
|
| 968 |
+
assert F3.of_type(a) and a == 2
|
| 969 |
+
a = F3(F3(3))
|
| 970 |
+
assert F3.of_type(a) and a == 0
|
| 971 |
+
a = F3(F3(4))
|
| 972 |
+
assert F3.of_type(a) and a == 1
|
| 973 |
+
|
| 974 |
+
a = -F3(1)
|
| 975 |
+
assert F3.of_type(a) and a == 2
|
| 976 |
+
a = -F3(2)
|
| 977 |
+
assert F3.of_type(a) and a == 1
|
| 978 |
+
|
| 979 |
+
a = 2 + F3(2)
|
| 980 |
+
assert F3.of_type(a) and a == 1
|
| 981 |
+
a = F3(2) + 2
|
| 982 |
+
assert F3.of_type(a) and a == 1
|
| 983 |
+
a = F3(2) + F3(2)
|
| 984 |
+
assert F3.of_type(a) and a == 1
|
| 985 |
+
a = F3(2) + F3(2)
|
| 986 |
+
assert F3.of_type(a) and a == 1
|
| 987 |
+
|
| 988 |
+
a = 3 - F3(2)
|
| 989 |
+
assert F3.of_type(a) and a == 1
|
| 990 |
+
a = F3(3) - 2
|
| 991 |
+
assert F3.of_type(a) and a == 1
|
| 992 |
+
a = F3(3) - F3(2)
|
| 993 |
+
assert F3.of_type(a) and a == 1
|
| 994 |
+
a = F3(3) - F3(2)
|
| 995 |
+
assert F3.of_type(a) and a == 1
|
| 996 |
+
|
| 997 |
+
a = 2*F3(2)
|
| 998 |
+
assert F3.of_type(a) and a == 1
|
| 999 |
+
a = F3(2)*2
|
| 1000 |
+
assert F3.of_type(a) and a == 1
|
| 1001 |
+
a = F3(2)*F3(2)
|
| 1002 |
+
assert F3.of_type(a) and a == 1
|
| 1003 |
+
a = F3(2)*F3(2)
|
| 1004 |
+
assert F3.of_type(a) and a == 1
|
| 1005 |
+
|
| 1006 |
+
a = 2/F3(2)
|
| 1007 |
+
assert F3.of_type(a) and a == 1
|
| 1008 |
+
a = F3(2)/2
|
| 1009 |
+
assert F3.of_type(a) and a == 1
|
| 1010 |
+
a = F3(2)/F3(2)
|
| 1011 |
+
assert F3.of_type(a) and a == 1
|
| 1012 |
+
a = F3(2)/F3(2)
|
| 1013 |
+
assert F3.of_type(a) and a == 1
|
| 1014 |
+
|
| 1015 |
+
a = F3(2)**0
|
| 1016 |
+
assert F3.of_type(a) and a == 1
|
| 1017 |
+
a = F3(2)**1
|
| 1018 |
+
assert F3.of_type(a) and a == 2
|
| 1019 |
+
a = F3(2)**2
|
| 1020 |
+
assert F3.of_type(a) and a == 1
|
| 1021 |
+
|
| 1022 |
+
F7 = FF(7)
|
| 1023 |
+
|
| 1024 |
+
a = F7(3)**100000000000
|
| 1025 |
+
assert F7.of_type(a) and a == 4
|
| 1026 |
+
a = F7(3)**-100000000000
|
| 1027 |
+
assert F7.of_type(a) and a == 2
|
| 1028 |
+
|
| 1029 |
+
assert bool(F3(3)) is False
|
| 1030 |
+
assert bool(F3(4)) is True
|
| 1031 |
+
|
| 1032 |
+
F5 = FF(5)
|
| 1033 |
+
|
| 1034 |
+
a = F5(1)**(-1)
|
| 1035 |
+
assert F5.of_type(a) and a == 1
|
| 1036 |
+
a = F5(2)**(-1)
|
| 1037 |
+
assert F5.of_type(a) and a == 3
|
| 1038 |
+
a = F5(3)**(-1)
|
| 1039 |
+
assert F5.of_type(a) and a == 2
|
| 1040 |
+
a = F5(4)**(-1)
|
| 1041 |
+
assert F5.of_type(a) and a == 4
|
| 1042 |
+
|
| 1043 |
+
if GROUND_TYPES != 'flint':
|
| 1044 |
+
# XXX: This gives a core dump with python-flint...
|
| 1045 |
+
raises(NotInvertible, lambda: F5(0)**(-1))
|
| 1046 |
+
raises(NotInvertible, lambda: F5(5)**(-1))
|
| 1047 |
+
|
| 1048 |
+
raises(ValueError, lambda: FF(0))
|
| 1049 |
+
raises(ValueError, lambda: FF(2.1))
|
| 1050 |
+
|
| 1051 |
+
for n1 in range(5):
|
| 1052 |
+
for n2 in range(5):
|
| 1053 |
+
if GROUND_TYPES != 'flint':
|
| 1054 |
+
with warns_deprecated_sympy():
|
| 1055 |
+
assert (F5(n1) < F5(n2)) is (n1 < n2)
|
| 1056 |
+
with warns_deprecated_sympy():
|
| 1057 |
+
assert (F5(n1) <= F5(n2)) is (n1 <= n2)
|
| 1058 |
+
with warns_deprecated_sympy():
|
| 1059 |
+
assert (F5(n1) > F5(n2)) is (n1 > n2)
|
| 1060 |
+
with warns_deprecated_sympy():
|
| 1061 |
+
assert (F5(n1) >= F5(n2)) is (n1 >= n2)
|
| 1062 |
+
else:
|
| 1063 |
+
raises(TypeError, lambda: F5(n1) < F5(n2))
|
| 1064 |
+
raises(TypeError, lambda: F5(n1) <= F5(n2))
|
| 1065 |
+
raises(TypeError, lambda: F5(n1) > F5(n2))
|
| 1066 |
+
raises(TypeError, lambda: F5(n1) >= F5(n2))
|
| 1067 |
+
|
| 1068 |
+
# https://github.com/sympy/sympy/issues/26789
|
| 1069 |
+
assert GF(Integer(5)) == F5
|
| 1070 |
+
assert F5(Integer(3)) == F5(3)
|
| 1071 |
+
|
| 1072 |
+
|
| 1073 |
+
def test_QQ_int():
|
| 1074 |
+
assert int(QQ(2**2000, 3**1250)) == 455431
|
| 1075 |
+
assert int(QQ(2**100, 3)) == 422550200076076467165567735125
|
| 1076 |
+
|
| 1077 |
+
|
| 1078 |
+
def test_RR_double():
|
| 1079 |
+
assert RR(3.14) > 1e-50
|
| 1080 |
+
assert RR(1e-13) > 1e-50
|
| 1081 |
+
assert RR(1e-14) > 1e-50
|
| 1082 |
+
assert RR(1e-15) > 1e-50
|
| 1083 |
+
assert RR(1e-20) > 1e-50
|
| 1084 |
+
assert RR(1e-40) > 1e-50
|
| 1085 |
+
|
| 1086 |
+
|
| 1087 |
+
def test_RR_Float():
|
| 1088 |
+
f1 = Float("1.01")
|
| 1089 |
+
f2 = Float("1.0000000000000000000001")
|
| 1090 |
+
assert f1._prec == 53
|
| 1091 |
+
assert f2._prec == 80
|
| 1092 |
+
assert RR(f1)-1 > 1e-50
|
| 1093 |
+
assert RR(f2)-1 < 1e-50 # RR's precision is lower than f2's
|
| 1094 |
+
|
| 1095 |
+
RR2 = RealField(prec=f2._prec)
|
| 1096 |
+
assert RR2(f1)-1 > 1e-50
|
| 1097 |
+
assert RR2(f2)-1 > 1e-50 # RR's precision is equal to f2's
|
| 1098 |
+
|
| 1099 |
+
|
| 1100 |
+
def test_CC_double():
|
| 1101 |
+
assert CC(3.14).real > 1e-50
|
| 1102 |
+
assert CC(1e-13).real > 1e-50
|
| 1103 |
+
assert CC(1e-14).real > 1e-50
|
| 1104 |
+
assert CC(1e-15).real > 1e-50
|
| 1105 |
+
assert CC(1e-20).real > 1e-50
|
| 1106 |
+
assert CC(1e-40).real > 1e-50
|
| 1107 |
+
|
| 1108 |
+
assert CC(3.14j).imag > 1e-50
|
| 1109 |
+
assert CC(1e-13j).imag > 1e-50
|
| 1110 |
+
assert CC(1e-14j).imag > 1e-50
|
| 1111 |
+
assert CC(1e-15j).imag > 1e-50
|
| 1112 |
+
assert CC(1e-20j).imag > 1e-50
|
| 1113 |
+
assert CC(1e-40j).imag > 1e-50
|
| 1114 |
+
|
| 1115 |
+
|
| 1116 |
+
def test_gaussian_domains():
|
| 1117 |
+
I = S.ImaginaryUnit
|
| 1118 |
+
a, b, c, d = [ZZ_I.convert(x) for x in (5, 2 + I, 3 - I, 5 - 5*I)]
|
| 1119 |
+
assert ZZ_I.gcd(a, b) == b
|
| 1120 |
+
assert ZZ_I.gcd(a, c) == b
|
| 1121 |
+
assert ZZ_I.lcm(a, b) == a
|
| 1122 |
+
assert ZZ_I.lcm(a, c) == d
|
| 1123 |
+
assert ZZ_I(3, 4) != QQ_I(3, 4) # XXX is this right or should QQ->ZZ if possible?
|
| 1124 |
+
assert ZZ_I(3, 0) != 3 # and should this go to Integer?
|
| 1125 |
+
assert QQ_I(S(3)/4, 0) != S(3)/4 # and this to Rational?
|
| 1126 |
+
assert ZZ_I(0, 0).quadrant() == 0
|
| 1127 |
+
assert ZZ_I(-1, 0).quadrant() == 2
|
| 1128 |
+
|
| 1129 |
+
assert QQ_I.convert(QQ(3, 2)) == QQ_I(QQ(3, 2), QQ(0))
|
| 1130 |
+
assert QQ_I.convert(QQ(3, 2), QQ) == QQ_I(QQ(3, 2), QQ(0))
|
| 1131 |
+
|
| 1132 |
+
for G in (QQ_I, ZZ_I):
|
| 1133 |
+
|
| 1134 |
+
q = G(3, 4)
|
| 1135 |
+
assert str(q) == '3 + 4*I'
|
| 1136 |
+
assert q.parent() == G
|
| 1137 |
+
assert q._get_xy(pi) == (None, None)
|
| 1138 |
+
assert q._get_xy(2) == (2, 0)
|
| 1139 |
+
assert q._get_xy(2*I) == (0, 2)
|
| 1140 |
+
|
| 1141 |
+
assert hash(q) == hash((3, 4))
|
| 1142 |
+
assert G(1, 2) == G(1, 2)
|
| 1143 |
+
assert G(1, 2) != G(1, 3)
|
| 1144 |
+
assert G(3, 0) == G(3)
|
| 1145 |
+
|
| 1146 |
+
assert q + q == G(6, 8)
|
| 1147 |
+
assert q - q == G(0, 0)
|
| 1148 |
+
assert 3 - q == -q + 3 == G(0, -4)
|
| 1149 |
+
assert 3 + q == q + 3 == G(6, 4)
|
| 1150 |
+
assert q * q == G(-7, 24)
|
| 1151 |
+
assert 3 * q == q * 3 == G(9, 12)
|
| 1152 |
+
assert q ** 0 == G(1, 0)
|
| 1153 |
+
assert q ** 1 == q
|
| 1154 |
+
assert q ** 2 == q * q == G(-7, 24)
|
| 1155 |
+
assert q ** 3 == q * q * q == G(-117, 44)
|
| 1156 |
+
assert 1 / q == q ** -1 == QQ_I(S(3)/25, - S(4)/25)
|
| 1157 |
+
assert q / 1 == QQ_I(3, 4)
|
| 1158 |
+
assert q / 2 == QQ_I(S(3)/2, 2)
|
| 1159 |
+
assert q/3 == QQ_I(1, S(4)/3)
|
| 1160 |
+
assert 3/q == QQ_I(S(9)/25, -S(12)/25)
|
| 1161 |
+
i, r = divmod(q, 2)
|
| 1162 |
+
assert 2*i + r == q
|
| 1163 |
+
i, r = divmod(2, q)
|
| 1164 |
+
assert q*i + r == G(2, 0)
|
| 1165 |
+
|
| 1166 |
+
a, b = G(2, 0), G(1, -1)
|
| 1167 |
+
c, d, g = G.gcdex(a, b)
|
| 1168 |
+
assert g == G.gcd(a, b)
|
| 1169 |
+
assert c * a + d * b == g
|
| 1170 |
+
|
| 1171 |
+
raises(ZeroDivisionError, lambda: q % 0)
|
| 1172 |
+
raises(ZeroDivisionError, lambda: q / 0)
|
| 1173 |
+
raises(ZeroDivisionError, lambda: q // 0)
|
| 1174 |
+
raises(ZeroDivisionError, lambda: divmod(q, 0))
|
| 1175 |
+
raises(ZeroDivisionError, lambda: divmod(q, 0))
|
| 1176 |
+
raises(TypeError, lambda: q + x)
|
| 1177 |
+
raises(TypeError, lambda: q - x)
|
| 1178 |
+
raises(TypeError, lambda: x + q)
|
| 1179 |
+
raises(TypeError, lambda: x - q)
|
| 1180 |
+
raises(TypeError, lambda: q * x)
|
| 1181 |
+
raises(TypeError, lambda: x * q)
|
| 1182 |
+
raises(TypeError, lambda: q / x)
|
| 1183 |
+
raises(TypeError, lambda: x / q)
|
| 1184 |
+
raises(TypeError, lambda: q // x)
|
| 1185 |
+
raises(TypeError, lambda: x // q)
|
| 1186 |
+
|
| 1187 |
+
assert G.from_sympy(S(2)) == G(2, 0)
|
| 1188 |
+
assert G.to_sympy(G(2, 0)) == S(2)
|
| 1189 |
+
raises(CoercionFailed, lambda: G.from_sympy(pi))
|
| 1190 |
+
|
| 1191 |
+
PR = G.inject(x)
|
| 1192 |
+
assert isinstance(PR, PolynomialRing)
|
| 1193 |
+
assert PR.domain == G
|
| 1194 |
+
assert len(PR.gens) == 1 and PR.gens[0].as_expr() == x
|
| 1195 |
+
|
| 1196 |
+
if G is QQ_I:
|
| 1197 |
+
AF = G.as_AlgebraicField()
|
| 1198 |
+
assert isinstance(AF, AlgebraicField)
|
| 1199 |
+
assert AF.domain == QQ
|
| 1200 |
+
assert AF.ext.args[0] == I
|
| 1201 |
+
|
| 1202 |
+
for qi in [G(-1, 0), G(1, 0), G(0, -1), G(0, 1)]:
|
| 1203 |
+
assert G.is_negative(qi) is False
|
| 1204 |
+
assert G.is_positive(qi) is False
|
| 1205 |
+
assert G.is_nonnegative(qi) is False
|
| 1206 |
+
assert G.is_nonpositive(qi) is False
|
| 1207 |
+
|
| 1208 |
+
domains = [ZZ, QQ, AlgebraicField(QQ, I)]
|
| 1209 |
+
|
| 1210 |
+
# XXX: These domains are all obsolete because ZZ/QQ with MPZ/MPQ
|
| 1211 |
+
# already use either gmpy, flint or python depending on the
|
| 1212 |
+
# availability of these libraries. We can keep these tests for now but
|
| 1213 |
+
# ideally we should remove these alternate domains entirely.
|
| 1214 |
+
domains += [ZZ_python(), QQ_python()]
|
| 1215 |
+
if GROUND_TYPES == 'gmpy':
|
| 1216 |
+
domains += [ZZ_gmpy(), QQ_gmpy()]
|
| 1217 |
+
|
| 1218 |
+
for K in domains:
|
| 1219 |
+
assert G.convert(K(2)) == G(2, 0)
|
| 1220 |
+
assert G.convert(K(2), K) == G(2, 0)
|
| 1221 |
+
|
| 1222 |
+
for K in ZZ_I, QQ_I:
|
| 1223 |
+
assert G.convert(K(1, 1)) == G(1, 1)
|
| 1224 |
+
assert G.convert(K(1, 1), K) == G(1, 1)
|
| 1225 |
+
|
| 1226 |
+
if G == ZZ_I:
|
| 1227 |
+
assert repr(q) == 'ZZ_I(3, 4)'
|
| 1228 |
+
assert q//3 == G(1, 1)
|
| 1229 |
+
assert 12//q == G(1, -2)
|
| 1230 |
+
assert 12 % q == G(1, 2)
|
| 1231 |
+
assert q % 2 == G(-1, 0)
|
| 1232 |
+
assert i == G(0, 0)
|
| 1233 |
+
assert r == G(2, 0)
|
| 1234 |
+
assert G.get_ring() == G
|
| 1235 |
+
assert G.get_field() == QQ_I
|
| 1236 |
+
else:
|
| 1237 |
+
assert repr(q) == 'QQ_I(3, 4)'
|
| 1238 |
+
assert G.get_ring() == ZZ_I
|
| 1239 |
+
assert G.get_field() == G
|
| 1240 |
+
assert q//3 == G(1, S(4)/3)
|
| 1241 |
+
assert 12//q == G(S(36)/25, -S(48)/25)
|
| 1242 |
+
assert 12 % q == G(0, 0)
|
| 1243 |
+
assert q % 2 == G(0, 0)
|
| 1244 |
+
assert i == G(S(6)/25, -S(8)/25), (G,i)
|
| 1245 |
+
assert r == G(0, 0)
|
| 1246 |
+
q2 = G(S(3)/2, S(5)/3)
|
| 1247 |
+
assert G.numer(q2) == ZZ_I(9, 10)
|
| 1248 |
+
assert G.denom(q2) == ZZ_I(6)
|
| 1249 |
+
|
| 1250 |
+
|
| 1251 |
+
def test_EX_EXRAW():
|
| 1252 |
+
assert EXRAW.zero is S.Zero
|
| 1253 |
+
assert EXRAW.one is S.One
|
| 1254 |
+
|
| 1255 |
+
assert EX(1) == EX.Expression(1)
|
| 1256 |
+
assert EX(1).ex is S.One
|
| 1257 |
+
assert EXRAW(1) is S.One
|
| 1258 |
+
|
| 1259 |
+
# EX has cancelling but EXRAW does not
|
| 1260 |
+
assert 2*EX((x + y*x)/x) == EX(2 + 2*y) != 2*((x + y*x)/x)
|
| 1261 |
+
assert 2*EXRAW((x + y*x)/x) == 2*((x + y*x)/x) != (1 + y)
|
| 1262 |
+
|
| 1263 |
+
assert EXRAW.convert_from(EX(1), EX) is EXRAW.one
|
| 1264 |
+
assert EX.convert_from(EXRAW(1), EXRAW) == EX.one
|
| 1265 |
+
|
| 1266 |
+
assert EXRAW.from_sympy(S.One) is S.One
|
| 1267 |
+
assert EXRAW.to_sympy(EXRAW.one) is S.One
|
| 1268 |
+
raises(CoercionFailed, lambda: EXRAW.from_sympy([]))
|
| 1269 |
+
|
| 1270 |
+
assert EXRAW.get_field() == EXRAW
|
| 1271 |
+
|
| 1272 |
+
assert EXRAW.unify(EX) == EXRAW
|
| 1273 |
+
assert EX.unify(EXRAW) == EXRAW
|
| 1274 |
+
|
| 1275 |
+
|
| 1276 |
+
def test_EX_ordering():
|
| 1277 |
+
elements = [EX(1), EX(x), EX(3)]
|
| 1278 |
+
assert sorted(elements) == [EX(1), EX(3), EX(x)]
|
| 1279 |
+
|
| 1280 |
+
|
| 1281 |
+
def test_canonical_unit():
|
| 1282 |
+
|
| 1283 |
+
for K in [ZZ, QQ, RR]: # CC?
|
| 1284 |
+
assert K.canonical_unit(K(2)) == K(1)
|
| 1285 |
+
assert K.canonical_unit(K(-2)) == K(-1)
|
| 1286 |
+
|
| 1287 |
+
for K in [ZZ_I, QQ_I]:
|
| 1288 |
+
i = K.from_sympy(I)
|
| 1289 |
+
assert K.canonical_unit(K(2)) == K(1)
|
| 1290 |
+
assert K.canonical_unit(K(2)*i) == -i
|
| 1291 |
+
assert K.canonical_unit(-K(2)) == K(-1)
|
| 1292 |
+
assert K.canonical_unit(-K(2)*i) == i
|
| 1293 |
+
|
| 1294 |
+
K = ZZ[x]
|
| 1295 |
+
assert K.canonical_unit(K(x + 1)) == K(1)
|
| 1296 |
+
assert K.canonical_unit(K(-x + 1)) == K(-1)
|
| 1297 |
+
|
| 1298 |
+
K = ZZ_I[x]
|
| 1299 |
+
assert K.canonical_unit(K.from_sympy(I*x)) == ZZ_I(0, -1)
|
| 1300 |
+
|
| 1301 |
+
K = ZZ_I.frac_field(x, y)
|
| 1302 |
+
i = K.from_sympy(I)
|
| 1303 |
+
assert i / i == K.one
|
| 1304 |
+
assert (K.one + i)/(i - K.one) == -i
|
| 1305 |
+
|
| 1306 |
+
|
| 1307 |
+
def test_Domain_is_negative():
|
| 1308 |
+
I = S.ImaginaryUnit
|
| 1309 |
+
a, b = [CC.convert(x) for x in (2 + I, 5)]
|
| 1310 |
+
assert CC.is_negative(a) == False
|
| 1311 |
+
assert CC.is_negative(b) == False
|
| 1312 |
+
|
| 1313 |
+
|
| 1314 |
+
def test_Domain_is_positive():
|
| 1315 |
+
I = S.ImaginaryUnit
|
| 1316 |
+
a, b = [CC.convert(x) for x in (2 + I, 5)]
|
| 1317 |
+
assert CC.is_positive(a) == False
|
| 1318 |
+
assert CC.is_positive(b) == False
|
| 1319 |
+
|
| 1320 |
+
|
| 1321 |
+
def test_Domain_is_nonnegative():
|
| 1322 |
+
I = S.ImaginaryUnit
|
| 1323 |
+
a, b = [CC.convert(x) for x in (2 + I, 5)]
|
| 1324 |
+
assert CC.is_nonnegative(a) == False
|
| 1325 |
+
assert CC.is_nonnegative(b) == False
|
| 1326 |
+
|
| 1327 |
+
|
| 1328 |
+
def test_Domain_is_nonpositive():
|
| 1329 |
+
I = S.ImaginaryUnit
|
| 1330 |
+
a, b = [CC.convert(x) for x in (2 + I, 5)]
|
| 1331 |
+
assert CC.is_nonpositive(a) == False
|
| 1332 |
+
assert CC.is_nonpositive(b) == False
|
| 1333 |
+
|
| 1334 |
+
|
| 1335 |
+
def test_exponential_domain():
|
| 1336 |
+
K = ZZ[E]
|
| 1337 |
+
eK = K.from_sympy(E)
|
| 1338 |
+
assert K.from_sympy(exp(3)) == eK ** 3
|
| 1339 |
+
assert K.convert(exp(3)) == eK ** 3
|
| 1340 |
+
|
| 1341 |
+
|
| 1342 |
+
def test_AlgebraicField_alias():
|
| 1343 |
+
# No default alias:
|
| 1344 |
+
k = QQ.algebraic_field(sqrt(2))
|
| 1345 |
+
assert k.ext.alias is None
|
| 1346 |
+
|
| 1347 |
+
# For a single extension, its alias is used:
|
| 1348 |
+
alpha = AlgebraicNumber(sqrt(2), alias='alpha')
|
| 1349 |
+
k = QQ.algebraic_field(alpha)
|
| 1350 |
+
assert k.ext.alias.name == 'alpha'
|
| 1351 |
+
|
| 1352 |
+
# Can override the alias of a single extension:
|
| 1353 |
+
k = QQ.algebraic_field(alpha, alias='theta')
|
| 1354 |
+
assert k.ext.alias.name == 'theta'
|
| 1355 |
+
|
| 1356 |
+
# With multiple extensions, no default alias:
|
| 1357 |
+
k = QQ.algebraic_field(sqrt(2), sqrt(3))
|
| 1358 |
+
assert k.ext.alias is None
|
| 1359 |
+
|
| 1360 |
+
# With multiple extensions, no default alias, even if one of
|
| 1361 |
+
# the extensions has one:
|
| 1362 |
+
k = QQ.algebraic_field(alpha, sqrt(3))
|
| 1363 |
+
assert k.ext.alias is None
|
| 1364 |
+
|
| 1365 |
+
# With multiple extensions, may set an alias:
|
| 1366 |
+
k = QQ.algebraic_field(sqrt(2), sqrt(3), alias='theta')
|
| 1367 |
+
assert k.ext.alias.name == 'theta'
|
| 1368 |
+
|
| 1369 |
+
# Alias is passed to constructed field elements:
|
| 1370 |
+
k = QQ.algebraic_field(alpha)
|
| 1371 |
+
beta = k.to_alg_num(k([1, 2, 3]))
|
| 1372 |
+
assert beta.alias is alpha.alias
|
| 1373 |
+
|
| 1374 |
+
|
| 1375 |
+
def test_exsqrt():
|
| 1376 |
+
assert ZZ.is_square(ZZ(4)) is True
|
| 1377 |
+
assert ZZ.exsqrt(ZZ(4)) == ZZ(2)
|
| 1378 |
+
assert ZZ.is_square(ZZ(42)) is False
|
| 1379 |
+
assert ZZ.exsqrt(ZZ(42)) is None
|
| 1380 |
+
assert ZZ.is_square(ZZ(0)) is True
|
| 1381 |
+
assert ZZ.exsqrt(ZZ(0)) == ZZ(0)
|
| 1382 |
+
assert ZZ.is_square(ZZ(-1)) is False
|
| 1383 |
+
assert ZZ.exsqrt(ZZ(-1)) is None
|
| 1384 |
+
|
| 1385 |
+
assert QQ.is_square(QQ(9, 4)) is True
|
| 1386 |
+
assert QQ.exsqrt(QQ(9, 4)) == QQ(3, 2)
|
| 1387 |
+
assert QQ.is_square(QQ(18, 8)) is True
|
| 1388 |
+
assert QQ.exsqrt(QQ(18, 8)) == QQ(3, 2)
|
| 1389 |
+
assert QQ.is_square(QQ(-9, -4)) is True
|
| 1390 |
+
assert QQ.exsqrt(QQ(-9, -4)) == QQ(3, 2)
|
| 1391 |
+
assert QQ.is_square(QQ(11, 4)) is False
|
| 1392 |
+
assert QQ.exsqrt(QQ(11, 4)) is None
|
| 1393 |
+
assert QQ.is_square(QQ(9, 5)) is False
|
| 1394 |
+
assert QQ.exsqrt(QQ(9, 5)) is None
|
| 1395 |
+
assert QQ.is_square(QQ(4)) is True
|
| 1396 |
+
assert QQ.exsqrt(QQ(4)) == QQ(2)
|
| 1397 |
+
assert QQ.is_square(QQ(0)) is True
|
| 1398 |
+
assert QQ.exsqrt(QQ(0)) == QQ(0)
|
| 1399 |
+
assert QQ.is_square(QQ(-16, 9)) is False
|
| 1400 |
+
assert QQ.exsqrt(QQ(-16, 9)) is None
|
| 1401 |
+
|
| 1402 |
+
assert RR.is_square(RR(6.25)) is True
|
| 1403 |
+
assert RR.exsqrt(RR(6.25)) == RR(2.5)
|
| 1404 |
+
assert RR.is_square(RR(2)) is True
|
| 1405 |
+
assert RR.almosteq(RR.exsqrt(RR(2)), RR(1.4142135623730951), tolerance=1e-15)
|
| 1406 |
+
assert RR.is_square(RR(0)) is True
|
| 1407 |
+
assert RR.exsqrt(RR(0)) == RR(0)
|
| 1408 |
+
assert RR.is_square(RR(-1)) is False
|
| 1409 |
+
assert RR.exsqrt(RR(-1)) is None
|
| 1410 |
+
|
| 1411 |
+
assert CC.is_square(CC(2)) is True
|
| 1412 |
+
assert CC.almosteq(CC.exsqrt(CC(2)), CC(1.4142135623730951), tolerance=1e-15)
|
| 1413 |
+
assert CC.is_square(CC(0)) is True
|
| 1414 |
+
assert CC.exsqrt(CC(0)) == CC(0)
|
| 1415 |
+
assert CC.is_square(CC(-1)) is True
|
| 1416 |
+
assert CC.exsqrt(CC(-1)) == CC(0, 1)
|
| 1417 |
+
assert CC.is_square(CC(0, 2)) is True
|
| 1418 |
+
assert CC.exsqrt(CC(0, 2)) == CC(1, 1)
|
| 1419 |
+
assert CC.is_square(CC(-3, -4)) is True
|
| 1420 |
+
assert CC.exsqrt(CC(-3, -4)) == CC(1, -2)
|
| 1421 |
+
|
| 1422 |
+
F2 = FF(2)
|
| 1423 |
+
assert F2.is_square(F2(1)) is True
|
| 1424 |
+
assert F2.exsqrt(F2(1)) == F2(1)
|
| 1425 |
+
assert F2.is_square(F2(0)) is True
|
| 1426 |
+
assert F2.exsqrt(F2(0)) == F2(0)
|
| 1427 |
+
|
| 1428 |
+
F7 = FF(7)
|
| 1429 |
+
assert F7.is_square(F7(2)) is True
|
| 1430 |
+
assert F7.exsqrt(F7(2)) == F7(3)
|
| 1431 |
+
assert F7.is_square(F7(3)) is False
|
| 1432 |
+
assert F7.exsqrt(F7(3)) is None
|
| 1433 |
+
assert F7.is_square(F7(0)) is True
|
| 1434 |
+
assert F7.exsqrt(F7(0)) == F7(0)
|
.venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_polynomialring.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for the PolynomialRing classes. """
|
| 2 |
+
|
| 3 |
+
from sympy.polys.domains import QQ, ZZ
|
| 4 |
+
from sympy.polys.polyerrors import ExactQuotientFailed, CoercionFailed, NotReversible
|
| 5 |
+
|
| 6 |
+
from sympy.abc import x, y
|
| 7 |
+
|
| 8 |
+
from sympy.testing.pytest import raises
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_build_order():
|
| 12 |
+
R = QQ.old_poly_ring(x, y, order=(("lex", x), ("ilex", y)))
|
| 13 |
+
assert R.order((1, 5)) == ((1,), (-5,))
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def test_globalring():
|
| 17 |
+
Qxy = QQ.old_frac_field(x, y)
|
| 18 |
+
R = QQ.old_poly_ring(x, y)
|
| 19 |
+
X = R.convert(x)
|
| 20 |
+
Y = R.convert(y)
|
| 21 |
+
|
| 22 |
+
assert x in R
|
| 23 |
+
assert 1/x not in R
|
| 24 |
+
assert 1/(1 + x) not in R
|
| 25 |
+
assert Y in R
|
| 26 |
+
assert X * (Y**2 + 1) == R.convert(x * (y**2 + 1))
|
| 27 |
+
assert X + 1 == R.convert(x + 1)
|
| 28 |
+
raises(ExactQuotientFailed, lambda: X/Y)
|
| 29 |
+
raises(TypeError, lambda: x/Y)
|
| 30 |
+
raises(TypeError, lambda: X/y)
|
| 31 |
+
assert X**2 / X == X
|
| 32 |
+
|
| 33 |
+
assert R.from_GlobalPolynomialRing(ZZ.old_poly_ring(x, y).convert(x), ZZ.old_poly_ring(x, y)) == X
|
| 34 |
+
assert R.from_FractionField(Qxy.convert(x), Qxy) == X
|
| 35 |
+
assert R.from_FractionField(Qxy.convert(x/y), Qxy) is None
|
| 36 |
+
|
| 37 |
+
assert R._sdm_to_vector(R._vector_to_sdm([X, Y], R.order), 2) == [X, Y]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def test_localring():
|
| 41 |
+
Qxy = QQ.old_frac_field(x, y)
|
| 42 |
+
R = QQ.old_poly_ring(x, y, order="ilex")
|
| 43 |
+
X = R.convert(x)
|
| 44 |
+
Y = R.convert(y)
|
| 45 |
+
|
| 46 |
+
assert x in R
|
| 47 |
+
assert 1/x not in R
|
| 48 |
+
assert 1/(1 + x) in R
|
| 49 |
+
assert Y in R
|
| 50 |
+
assert X*(Y**2 + 1)/(1 + X) == R.convert(x*(y**2 + 1)/(1 + x))
|
| 51 |
+
raises(TypeError, lambda: x/Y)
|
| 52 |
+
raises(TypeError, lambda: X/y)
|
| 53 |
+
assert X + 1 == R.convert(x + 1)
|
| 54 |
+
assert X**2 / X == X
|
| 55 |
+
|
| 56 |
+
assert R.from_GlobalPolynomialRing(ZZ.old_poly_ring(x, y).convert(x), ZZ.old_poly_ring(x, y)) == X
|
| 57 |
+
assert R.from_FractionField(Qxy.convert(x), Qxy) == X
|
| 58 |
+
raises(CoercionFailed, lambda: R.from_FractionField(Qxy.convert(x/y), Qxy))
|
| 59 |
+
raises(ExactQuotientFailed, lambda: R.exquo(X, Y))
|
| 60 |
+
raises(NotReversible, lambda: R.revert(X))
|
| 61 |
+
|
| 62 |
+
assert R._sdm_to_vector(
|
| 63 |
+
R._vector_to_sdm([X/(X + 1), Y/(1 + X*Y)], R.order), 2) == \
|
| 64 |
+
[X*(1 + X*Y), Y*(1 + X)]
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def test_conversion():
|
| 68 |
+
L = QQ.old_poly_ring(x, y, order="ilex")
|
| 69 |
+
G = QQ.old_poly_ring(x, y)
|
| 70 |
+
|
| 71 |
+
assert L.convert(x) == L.convert(G.convert(x), G)
|
| 72 |
+
assert G.convert(x) == G.convert(L.convert(x), L)
|
| 73 |
+
raises(CoercionFailed, lambda: G.convert(L.convert(1/(1 + x)), L))
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def test_units():
|
| 77 |
+
R = QQ.old_poly_ring(x)
|
| 78 |
+
assert R.is_unit(R.convert(1))
|
| 79 |
+
assert R.is_unit(R.convert(2))
|
| 80 |
+
assert not R.is_unit(R.convert(x))
|
| 81 |
+
assert not R.is_unit(R.convert(1 + x))
|
| 82 |
+
|
| 83 |
+
R = QQ.old_poly_ring(x, order='ilex')
|
| 84 |
+
assert R.is_unit(R.convert(1))
|
| 85 |
+
assert R.is_unit(R.convert(2))
|
| 86 |
+
assert not R.is_unit(R.convert(x))
|
| 87 |
+
assert R.is_unit(R.convert(1 + x))
|
| 88 |
+
|
| 89 |
+
R = ZZ.old_poly_ring(x)
|
| 90 |
+
assert R.is_unit(R.convert(1))
|
| 91 |
+
assert not R.is_unit(R.convert(2))
|
| 92 |
+
assert not R.is_unit(R.convert(x))
|
| 93 |
+
assert not R.is_unit(R.convert(1 + x))
|
.venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_quotientring.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for quotient rings."""
|
| 2 |
+
|
| 3 |
+
from sympy.polys.domains.integerring import ZZ
|
| 4 |
+
from sympy.polys.domains.rationalfield import QQ
|
| 5 |
+
from sympy.abc import x, y
|
| 6 |
+
|
| 7 |
+
from sympy.polys.polyerrors import NotReversible
|
| 8 |
+
|
| 9 |
+
from sympy.testing.pytest import raises
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def test_QuotientRingElement():
|
| 13 |
+
R = QQ.old_poly_ring(x)/[x**10]
|
| 14 |
+
X = R.convert(x)
|
| 15 |
+
|
| 16 |
+
assert X*(X + 1) == R.convert(x**2 + x)
|
| 17 |
+
assert X*x == R.convert(x**2)
|
| 18 |
+
assert x*X == R.convert(x**2)
|
| 19 |
+
assert X + x == R.convert(2*x)
|
| 20 |
+
assert x + X == 2*X
|
| 21 |
+
assert X**2 == R.convert(x**2)
|
| 22 |
+
assert 1/(1 - X) == R.convert(sum(x**i for i in range(10)))
|
| 23 |
+
assert X**10 == R.zero
|
| 24 |
+
assert X != x
|
| 25 |
+
|
| 26 |
+
raises(NotReversible, lambda: 1/X)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_QuotientRing():
|
| 30 |
+
I = QQ.old_poly_ring(x).ideal(x**2 + 1)
|
| 31 |
+
R = QQ.old_poly_ring(x)/I
|
| 32 |
+
|
| 33 |
+
assert R == QQ.old_poly_ring(x)/[x**2 + 1]
|
| 34 |
+
assert R == QQ.old_poly_ring(x)/QQ.old_poly_ring(x).ideal(x**2 + 1)
|
| 35 |
+
assert R != QQ.old_poly_ring(x)
|
| 36 |
+
|
| 37 |
+
assert R.convert(1)/x == -x + I
|
| 38 |
+
assert -1 + I == x**2 + I
|
| 39 |
+
assert R.convert(ZZ(1), ZZ) == 1 + I
|
| 40 |
+
assert R.convert(R.convert(x), R) == R.convert(x)
|
| 41 |
+
|
| 42 |
+
X = R.convert(x)
|
| 43 |
+
Y = QQ.old_poly_ring(x).convert(x)
|
| 44 |
+
assert -1 + I == X**2 + I
|
| 45 |
+
assert -1 + I == Y**2 + I
|
| 46 |
+
assert R.to_sympy(X) == x
|
| 47 |
+
|
| 48 |
+
raises(ValueError, lambda: QQ.old_poly_ring(x)/QQ.old_poly_ring(x, y).ideal(x))
|
| 49 |
+
|
| 50 |
+
R = QQ.old_poly_ring(x, order="ilex")
|
| 51 |
+
I = R.ideal(x)
|
| 52 |
+
assert R.convert(1) + I == (R/I).convert(1)
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
|
| 3 |
+
sympy.polys.matrices package.
|
| 4 |
+
|
| 5 |
+
The main export from this package is the DomainMatrix class which is a
|
| 6 |
+
lower-level implementation of matrices based on the polys Domains. This
|
| 7 |
+
implementation is typically a lot faster than SymPy's standard Matrix class
|
| 8 |
+
but is a work in progress and is still experimental.
|
| 9 |
+
|
| 10 |
+
"""
|
| 11 |
+
from .domainmatrix import DomainMatrix, DM
|
| 12 |
+
|
| 13 |
+
__all__ = [
|
| 14 |
+
'DomainMatrix', 'DM',
|
| 15 |
+
]
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/_dfm.py
ADDED
|
@@ -0,0 +1,951 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# sympy.polys.matrices.dfm
|
| 3 |
+
#
|
| 4 |
+
# This modules defines the DFM class which is a wrapper for dense flint
|
| 5 |
+
# matrices as found in python-flint.
|
| 6 |
+
#
|
| 7 |
+
# As of python-flint 0.4.1 matrices over the following domains can be supported
|
| 8 |
+
# by python-flint:
|
| 9 |
+
#
|
| 10 |
+
# ZZ: flint.fmpz_mat
|
| 11 |
+
# QQ: flint.fmpq_mat
|
| 12 |
+
# GF(p): flint.nmod_mat (p prime and p < ~2**62)
|
| 13 |
+
#
|
| 14 |
+
# The underlying flint library has many more domains, but these are not yet
|
| 15 |
+
# supported by python-flint.
|
| 16 |
+
#
|
| 17 |
+
# The DFM class is a wrapper for the flint matrices and provides a common
|
| 18 |
+
# interface for all supported domains that is interchangeable with the DDM
|
| 19 |
+
# and SDM classes so that DomainMatrix can be used with any as its internal
|
| 20 |
+
# matrix representation.
|
| 21 |
+
#
|
| 22 |
+
|
| 23 |
+
# TODO:
|
| 24 |
+
#
|
| 25 |
+
# Implement the following methods that are provided by python-flint:
|
| 26 |
+
#
|
| 27 |
+
# - hnf (Hermite normal form)
|
| 28 |
+
# - snf (Smith normal form)
|
| 29 |
+
# - minpoly
|
| 30 |
+
# - is_hnf
|
| 31 |
+
# - is_snf
|
| 32 |
+
# - rank
|
| 33 |
+
#
|
| 34 |
+
# The other types DDM and SDM do not have these methods and the algorithms
|
| 35 |
+
# for hnf, snf and rank are already implemented. Algorithms for minpoly,
|
| 36 |
+
# is_hnf and is_snf would need to be added.
|
| 37 |
+
#
|
| 38 |
+
# Add more methods to python-flint to expose more of Flint's functionality
|
| 39 |
+
# and also to make some of the above methods simpler or more efficient e.g.
|
| 40 |
+
# slicing, fancy indexing etc.
|
| 41 |
+
|
| 42 |
+
from sympy.external.gmpy import GROUND_TYPES
|
| 43 |
+
from sympy.external.importtools import import_module
|
| 44 |
+
from sympy.utilities.decorator import doctest_depends_on
|
| 45 |
+
|
| 46 |
+
from sympy.polys.domains import ZZ, QQ
|
| 47 |
+
|
| 48 |
+
from .exceptions import (
|
| 49 |
+
DMBadInputError,
|
| 50 |
+
DMDomainError,
|
| 51 |
+
DMNonSquareMatrixError,
|
| 52 |
+
DMNonInvertibleMatrixError,
|
| 53 |
+
DMRankError,
|
| 54 |
+
DMShapeError,
|
| 55 |
+
DMValueError,
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
if GROUND_TYPES != 'flint':
|
| 60 |
+
__doctest_skip__ = ['*']
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
flint = import_module('flint')
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
__all__ = ['DFM']
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
@doctest_depends_on(ground_types=['flint'])
|
| 70 |
+
class DFM:
|
| 71 |
+
"""
|
| 72 |
+
Dense FLINT matrix. This class is a wrapper for matrices from python-flint.
|
| 73 |
+
|
| 74 |
+
>>> from sympy.polys.domains import ZZ
|
| 75 |
+
>>> from sympy.polys.matrices.dfm import DFM
|
| 76 |
+
>>> dfm = DFM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 77 |
+
>>> dfm
|
| 78 |
+
[[1, 2], [3, 4]]
|
| 79 |
+
>>> dfm.rep
|
| 80 |
+
[1, 2]
|
| 81 |
+
[3, 4]
|
| 82 |
+
>>> type(dfm.rep) # doctest: +SKIP
|
| 83 |
+
<class 'flint._flint.fmpz_mat'>
|
| 84 |
+
|
| 85 |
+
Usually, the DFM class is not instantiated directly, but is created as the
|
| 86 |
+
internal representation of :class:`~.DomainMatrix`. When
|
| 87 |
+
`SYMPY_GROUND_TYPES` is set to `flint` and `python-flint` is installed, the
|
| 88 |
+
:class:`DFM` class is used automatically as the internal representation of
|
| 89 |
+
:class:`~.DomainMatrix` in dense format if the domain is supported by
|
| 90 |
+
python-flint.
|
| 91 |
+
|
| 92 |
+
>>> from sympy.polys.matrices.domainmatrix import DM
|
| 93 |
+
>>> dM = DM([[1, 2], [3, 4]], ZZ)
|
| 94 |
+
>>> dM.rep
|
| 95 |
+
[[1, 2], [3, 4]]
|
| 96 |
+
|
| 97 |
+
A :class:`~.DomainMatrix` can be converted to :class:`DFM` by calling the
|
| 98 |
+
:meth:`to_dfm` method:
|
| 99 |
+
|
| 100 |
+
>>> dM.to_dfm()
|
| 101 |
+
[[1, 2], [3, 4]]
|
| 102 |
+
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
fmt = 'dense'
|
| 106 |
+
is_DFM = True
|
| 107 |
+
is_DDM = False
|
| 108 |
+
|
| 109 |
+
def __new__(cls, rowslist, shape, domain):
|
| 110 |
+
"""Construct from a nested list."""
|
| 111 |
+
flint_mat = cls._get_flint_func(domain)
|
| 112 |
+
|
| 113 |
+
if 0 not in shape:
|
| 114 |
+
try:
|
| 115 |
+
rep = flint_mat(rowslist)
|
| 116 |
+
except (ValueError, TypeError):
|
| 117 |
+
raise DMBadInputError(f"Input should be a list of list of {domain}")
|
| 118 |
+
else:
|
| 119 |
+
rep = flint_mat(*shape)
|
| 120 |
+
|
| 121 |
+
return cls._new(rep, shape, domain)
|
| 122 |
+
|
| 123 |
+
@classmethod
|
| 124 |
+
def _new(cls, rep, shape, domain):
|
| 125 |
+
"""Internal constructor from a flint matrix."""
|
| 126 |
+
cls._check(rep, shape, domain)
|
| 127 |
+
obj = object.__new__(cls)
|
| 128 |
+
obj.rep = rep
|
| 129 |
+
obj.shape = obj.rows, obj.cols = shape
|
| 130 |
+
obj.domain = domain
|
| 131 |
+
return obj
|
| 132 |
+
|
| 133 |
+
def _new_rep(self, rep):
|
| 134 |
+
"""Create a new DFM with the same shape and domain but a new rep."""
|
| 135 |
+
return self._new(rep, self.shape, self.domain)
|
| 136 |
+
|
| 137 |
+
@classmethod
|
| 138 |
+
def _check(cls, rep, shape, domain):
|
| 139 |
+
repshape = (rep.nrows(), rep.ncols())
|
| 140 |
+
if repshape != shape:
|
| 141 |
+
raise DMBadInputError("Shape of rep does not match shape of DFM")
|
| 142 |
+
if domain == ZZ and not isinstance(rep, flint.fmpz_mat):
|
| 143 |
+
raise RuntimeError("Rep is not a flint.fmpz_mat")
|
| 144 |
+
elif domain == QQ and not isinstance(rep, flint.fmpq_mat):
|
| 145 |
+
raise RuntimeError("Rep is not a flint.fmpq_mat")
|
| 146 |
+
elif domain.is_FF and not isinstance(rep, (flint.fmpz_mod_mat, flint.nmod_mat)):
|
| 147 |
+
raise RuntimeError("Rep is not a flint.fmpz_mod_mat or flint.nmod_mat")
|
| 148 |
+
elif domain not in (ZZ, QQ) and not domain.is_FF:
|
| 149 |
+
raise NotImplementedError("Only ZZ and QQ are supported by DFM")
|
| 150 |
+
|
| 151 |
+
@classmethod
|
| 152 |
+
def _supports_domain(cls, domain):
|
| 153 |
+
"""Return True if the given domain is supported by DFM."""
|
| 154 |
+
return domain in (ZZ, QQ) or domain.is_FF and domain._is_flint
|
| 155 |
+
|
| 156 |
+
@classmethod
|
| 157 |
+
def _get_flint_func(cls, domain):
|
| 158 |
+
"""Return the flint matrix class for the given domain."""
|
| 159 |
+
if domain == ZZ:
|
| 160 |
+
return flint.fmpz_mat
|
| 161 |
+
elif domain == QQ:
|
| 162 |
+
return flint.fmpq_mat
|
| 163 |
+
elif domain.is_FF:
|
| 164 |
+
c = domain.characteristic()
|
| 165 |
+
if isinstance(domain.one, flint.nmod):
|
| 166 |
+
_cls = flint.nmod_mat
|
| 167 |
+
def _func(*e):
|
| 168 |
+
if len(e) == 1 and isinstance(e[0], flint.nmod_mat):
|
| 169 |
+
return _cls(e[0])
|
| 170 |
+
else:
|
| 171 |
+
return _cls(*e, c)
|
| 172 |
+
else:
|
| 173 |
+
m = flint.fmpz_mod_ctx(c)
|
| 174 |
+
_func = lambda *e: flint.fmpz_mod_mat(*e, m)
|
| 175 |
+
return _func
|
| 176 |
+
else:
|
| 177 |
+
raise NotImplementedError("Only ZZ and QQ are supported by DFM")
|
| 178 |
+
|
| 179 |
+
@property
|
| 180 |
+
def _func(self):
|
| 181 |
+
"""Callable to create a flint matrix of the same domain."""
|
| 182 |
+
return self._get_flint_func(self.domain)
|
| 183 |
+
|
| 184 |
+
def __str__(self):
|
| 185 |
+
"""Return ``str(self)``."""
|
| 186 |
+
return str(self.to_ddm())
|
| 187 |
+
|
| 188 |
+
def __repr__(self):
|
| 189 |
+
"""Return ``repr(self)``."""
|
| 190 |
+
return f'DFM{repr(self.to_ddm())[3:]}'
|
| 191 |
+
|
| 192 |
+
def __eq__(self, other):
|
| 193 |
+
"""Return ``self == other``."""
|
| 194 |
+
if not isinstance(other, DFM):
|
| 195 |
+
return NotImplemented
|
| 196 |
+
# Compare domains first because we do *not* want matrices with
|
| 197 |
+
# different domains to be equal but e.g. a flint fmpz_mat and fmpq_mat
|
| 198 |
+
# with the same entries will compare equal.
|
| 199 |
+
return self.domain == other.domain and self.rep == other.rep
|
| 200 |
+
|
| 201 |
+
@classmethod
|
| 202 |
+
def from_list(cls, rowslist, shape, domain):
|
| 203 |
+
"""Construct from a nested list."""
|
| 204 |
+
return cls(rowslist, shape, domain)
|
| 205 |
+
|
| 206 |
+
def to_list(self):
|
| 207 |
+
"""Convert to a nested list."""
|
| 208 |
+
return self.rep.tolist()
|
| 209 |
+
|
| 210 |
+
def copy(self):
|
| 211 |
+
"""Return a copy of self."""
|
| 212 |
+
return self._new_rep(self._func(self.rep))
|
| 213 |
+
|
| 214 |
+
def to_ddm(self):
|
| 215 |
+
"""Convert to a DDM."""
|
| 216 |
+
return DDM.from_list(self.to_list(), self.shape, self.domain)
|
| 217 |
+
|
| 218 |
+
def to_sdm(self):
|
| 219 |
+
"""Convert to a SDM."""
|
| 220 |
+
return SDM.from_list(self.to_list(), self.shape, self.domain)
|
| 221 |
+
|
| 222 |
+
def to_dfm(self):
|
| 223 |
+
"""Return self."""
|
| 224 |
+
return self
|
| 225 |
+
|
| 226 |
+
def to_dfm_or_ddm(self):
|
| 227 |
+
"""
|
| 228 |
+
Convert to a :class:`DFM`.
|
| 229 |
+
|
| 230 |
+
This :class:`DFM` method exists to parallel the :class:`~.DDM` and
|
| 231 |
+
:class:`~.SDM` methods. For :class:`DFM` it will always return self.
|
| 232 |
+
|
| 233 |
+
See Also
|
| 234 |
+
========
|
| 235 |
+
|
| 236 |
+
to_ddm
|
| 237 |
+
to_sdm
|
| 238 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm
|
| 239 |
+
"""
|
| 240 |
+
return self
|
| 241 |
+
|
| 242 |
+
@classmethod
|
| 243 |
+
def from_ddm(cls, ddm):
|
| 244 |
+
"""Convert from a DDM."""
|
| 245 |
+
return cls.from_list(ddm.to_list(), ddm.shape, ddm.domain)
|
| 246 |
+
|
| 247 |
+
@classmethod
|
| 248 |
+
def from_list_flat(cls, elements, shape, domain):
|
| 249 |
+
"""Inverse of :meth:`to_list_flat`."""
|
| 250 |
+
func = cls._get_flint_func(domain)
|
| 251 |
+
try:
|
| 252 |
+
rep = func(*shape, elements)
|
| 253 |
+
except ValueError:
|
| 254 |
+
raise DMBadInputError(f"Incorrect number of elements for shape {shape}")
|
| 255 |
+
except TypeError:
|
| 256 |
+
raise DMBadInputError(f"Input should be a list of {domain}")
|
| 257 |
+
return cls(rep, shape, domain)
|
| 258 |
+
|
| 259 |
+
def to_list_flat(self):
|
| 260 |
+
"""Convert to a flat list."""
|
| 261 |
+
return self.rep.entries()
|
| 262 |
+
|
| 263 |
+
def to_flat_nz(self):
|
| 264 |
+
"""Convert to a flat list of non-zeros."""
|
| 265 |
+
return self.to_ddm().to_flat_nz()
|
| 266 |
+
|
| 267 |
+
@classmethod
|
| 268 |
+
def from_flat_nz(cls, elements, data, domain):
|
| 269 |
+
"""Inverse of :meth:`to_flat_nz`."""
|
| 270 |
+
return DDM.from_flat_nz(elements, data, domain).to_dfm()
|
| 271 |
+
|
| 272 |
+
def to_dod(self):
|
| 273 |
+
"""Convert to a DOD."""
|
| 274 |
+
return self.to_ddm().to_dod()
|
| 275 |
+
|
| 276 |
+
@classmethod
|
| 277 |
+
def from_dod(cls, dod, shape, domain):
|
| 278 |
+
"""Inverse of :meth:`to_dod`."""
|
| 279 |
+
return DDM.from_dod(dod, shape, domain).to_dfm()
|
| 280 |
+
|
| 281 |
+
def to_dok(self):
|
| 282 |
+
"""Convert to a DOK."""
|
| 283 |
+
return self.to_ddm().to_dok()
|
| 284 |
+
|
| 285 |
+
@classmethod
|
| 286 |
+
def from_dok(cls, dok, shape, domain):
|
| 287 |
+
"""Inverse of :math:`to_dod`."""
|
| 288 |
+
return DDM.from_dok(dok, shape, domain).to_dfm()
|
| 289 |
+
|
| 290 |
+
def iter_values(self):
|
| 291 |
+
"""Iterate over the non-zero values of the matrix."""
|
| 292 |
+
m, n = self.shape
|
| 293 |
+
rep = self.rep
|
| 294 |
+
for i in range(m):
|
| 295 |
+
for j in range(n):
|
| 296 |
+
repij = rep[i, j]
|
| 297 |
+
if repij:
|
| 298 |
+
yield rep[i, j]
|
| 299 |
+
|
| 300 |
+
def iter_items(self):
|
| 301 |
+
"""Iterate over indices and values of nonzero elements of the matrix."""
|
| 302 |
+
m, n = self.shape
|
| 303 |
+
rep = self.rep
|
| 304 |
+
for i in range(m):
|
| 305 |
+
for j in range(n):
|
| 306 |
+
repij = rep[i, j]
|
| 307 |
+
if repij:
|
| 308 |
+
yield ((i, j), repij)
|
| 309 |
+
|
| 310 |
+
def convert_to(self, domain):
|
| 311 |
+
"""Convert to a new domain."""
|
| 312 |
+
if domain == self.domain:
|
| 313 |
+
return self.copy()
|
| 314 |
+
elif domain == QQ and self.domain == ZZ:
|
| 315 |
+
return self._new(flint.fmpq_mat(self.rep), self.shape, domain)
|
| 316 |
+
elif self._supports_domain(domain):
|
| 317 |
+
# XXX: Use more efficient conversions when possible.
|
| 318 |
+
return self.to_ddm().convert_to(domain).to_dfm()
|
| 319 |
+
else:
|
| 320 |
+
# It is the callers responsibility to convert to DDM before calling
|
| 321 |
+
# this method if the domain is not supported by DFM.
|
| 322 |
+
raise NotImplementedError("Only ZZ and QQ are supported by DFM")
|
| 323 |
+
|
| 324 |
+
def getitem(self, i, j):
|
| 325 |
+
"""Get the ``(i, j)``-th entry."""
|
| 326 |
+
# XXX: flint matrices do not support negative indices
|
| 327 |
+
# XXX: They also raise ValueError instead of IndexError
|
| 328 |
+
m, n = self.shape
|
| 329 |
+
if i < 0:
|
| 330 |
+
i += m
|
| 331 |
+
if j < 0:
|
| 332 |
+
j += n
|
| 333 |
+
try:
|
| 334 |
+
return self.rep[i, j]
|
| 335 |
+
except ValueError:
|
| 336 |
+
raise IndexError(f"Invalid indices ({i}, {j}) for Matrix of shape {self.shape}")
|
| 337 |
+
|
| 338 |
+
def setitem(self, i, j, value):
|
| 339 |
+
"""Set the ``(i, j)``-th entry."""
|
| 340 |
+
# XXX: flint matrices do not support negative indices
|
| 341 |
+
# XXX: They also raise ValueError instead of IndexError
|
| 342 |
+
m, n = self.shape
|
| 343 |
+
if i < 0:
|
| 344 |
+
i += m
|
| 345 |
+
if j < 0:
|
| 346 |
+
j += n
|
| 347 |
+
try:
|
| 348 |
+
self.rep[i, j] = value
|
| 349 |
+
except ValueError:
|
| 350 |
+
raise IndexError(f"Invalid indices ({i}, {j}) for Matrix of shape {self.shape}")
|
| 351 |
+
|
| 352 |
+
def _extract(self, i_indices, j_indices):
|
| 353 |
+
"""Extract a submatrix with no checking."""
|
| 354 |
+
# Indices must be positive and in range.
|
| 355 |
+
M = self.rep
|
| 356 |
+
lol = [[M[i, j] for j in j_indices] for i in i_indices]
|
| 357 |
+
shape = (len(i_indices), len(j_indices))
|
| 358 |
+
return self.from_list(lol, shape, self.domain)
|
| 359 |
+
|
| 360 |
+
def extract(self, rowslist, colslist):
|
| 361 |
+
"""Extract a submatrix."""
|
| 362 |
+
# XXX: flint matrices do not support fancy indexing or negative indices
|
| 363 |
+
#
|
| 364 |
+
# Check and convert negative indices before calling _extract.
|
| 365 |
+
m, n = self.shape
|
| 366 |
+
|
| 367 |
+
new_rows = []
|
| 368 |
+
new_cols = []
|
| 369 |
+
|
| 370 |
+
for i in rowslist:
|
| 371 |
+
if i < 0:
|
| 372 |
+
i_pos = i + m
|
| 373 |
+
else:
|
| 374 |
+
i_pos = i
|
| 375 |
+
if not 0 <= i_pos < m:
|
| 376 |
+
raise IndexError(f"Invalid row index {i} for Matrix of shape {self.shape}")
|
| 377 |
+
new_rows.append(i_pos)
|
| 378 |
+
|
| 379 |
+
for j in colslist:
|
| 380 |
+
if j < 0:
|
| 381 |
+
j_pos = j + n
|
| 382 |
+
else:
|
| 383 |
+
j_pos = j
|
| 384 |
+
if not 0 <= j_pos < n:
|
| 385 |
+
raise IndexError(f"Invalid column index {j} for Matrix of shape {self.shape}")
|
| 386 |
+
new_cols.append(j_pos)
|
| 387 |
+
|
| 388 |
+
return self._extract(new_rows, new_cols)
|
| 389 |
+
|
| 390 |
+
def extract_slice(self, rowslice, colslice):
|
| 391 |
+
"""Slice a DFM."""
|
| 392 |
+
# XXX: flint matrices do not support slicing
|
| 393 |
+
m, n = self.shape
|
| 394 |
+
i_indices = range(m)[rowslice]
|
| 395 |
+
j_indices = range(n)[colslice]
|
| 396 |
+
return self._extract(i_indices, j_indices)
|
| 397 |
+
|
| 398 |
+
def neg(self):
|
| 399 |
+
"""Negate a DFM matrix."""
|
| 400 |
+
return self._new_rep(-self.rep)
|
| 401 |
+
|
| 402 |
+
def add(self, other):
|
| 403 |
+
"""Add two DFM matrices."""
|
| 404 |
+
return self._new_rep(self.rep + other.rep)
|
| 405 |
+
|
| 406 |
+
def sub(self, other):
|
| 407 |
+
"""Subtract two DFM matrices."""
|
| 408 |
+
return self._new_rep(self.rep - other.rep)
|
| 409 |
+
|
| 410 |
+
def mul(self, other):
|
| 411 |
+
"""Multiply a DFM matrix from the right by a scalar."""
|
| 412 |
+
return self._new_rep(self.rep * other)
|
| 413 |
+
|
| 414 |
+
def rmul(self, other):
|
| 415 |
+
"""Multiply a DFM matrix from the left by a scalar."""
|
| 416 |
+
return self._new_rep(other * self.rep)
|
| 417 |
+
|
| 418 |
+
def mul_elementwise(self, other):
|
| 419 |
+
"""Elementwise multiplication of two DFM matrices."""
|
| 420 |
+
# XXX: flint matrices do not support elementwise multiplication
|
| 421 |
+
return self.to_ddm().mul_elementwise(other.to_ddm()).to_dfm()
|
| 422 |
+
|
| 423 |
+
def matmul(self, other):
|
| 424 |
+
"""Multiply two DFM matrices."""
|
| 425 |
+
shape = (self.rows, other.cols)
|
| 426 |
+
return self._new(self.rep * other.rep, shape, self.domain)
|
| 427 |
+
|
| 428 |
+
# XXX: For the most part DomainMatrix does not expect DDM, SDM, or DFM to
|
| 429 |
+
# have arithmetic operators defined. The only exception is negation.
|
| 430 |
+
# Perhaps that should be removed.
|
| 431 |
+
|
| 432 |
+
def __neg__(self):
|
| 433 |
+
"""Negate a DFM matrix."""
|
| 434 |
+
return self.neg()
|
| 435 |
+
|
| 436 |
+
@classmethod
|
| 437 |
+
def zeros(cls, shape, domain):
|
| 438 |
+
"""Return a zero DFM matrix."""
|
| 439 |
+
func = cls._get_flint_func(domain)
|
| 440 |
+
return cls._new(func(*shape), shape, domain)
|
| 441 |
+
|
| 442 |
+
# XXX: flint matrices do not have anything like ones or eye
|
| 443 |
+
# In the methods below we convert to DDM and then back to DFM which is
|
| 444 |
+
# probably about as efficient as implementing these methods directly.
|
| 445 |
+
|
| 446 |
+
@classmethod
|
| 447 |
+
def ones(cls, shape, domain):
|
| 448 |
+
"""Return a one DFM matrix."""
|
| 449 |
+
# XXX: flint matrices do not have anything like ones
|
| 450 |
+
return DDM.ones(shape, domain).to_dfm()
|
| 451 |
+
|
| 452 |
+
@classmethod
|
| 453 |
+
def eye(cls, n, domain):
|
| 454 |
+
"""Return the identity matrix of size n."""
|
| 455 |
+
# XXX: flint matrices do not have anything like eye
|
| 456 |
+
return DDM.eye(n, domain).to_dfm()
|
| 457 |
+
|
| 458 |
+
@classmethod
|
| 459 |
+
def diag(cls, elements, domain):
|
| 460 |
+
"""Return a diagonal matrix."""
|
| 461 |
+
return DDM.diag(elements, domain).to_dfm()
|
| 462 |
+
|
| 463 |
+
def applyfunc(self, func, domain):
|
| 464 |
+
"""Apply a function to each entry of a DFM matrix."""
|
| 465 |
+
return self.to_ddm().applyfunc(func, domain).to_dfm()
|
| 466 |
+
|
| 467 |
+
def transpose(self):
|
| 468 |
+
"""Transpose a DFM matrix."""
|
| 469 |
+
return self._new(self.rep.transpose(), (self.cols, self.rows), self.domain)
|
| 470 |
+
|
| 471 |
+
def hstack(self, *others):
|
| 472 |
+
"""Horizontally stack matrices."""
|
| 473 |
+
return self.to_ddm().hstack(*[o.to_ddm() for o in others]).to_dfm()
|
| 474 |
+
|
| 475 |
+
def vstack(self, *others):
|
| 476 |
+
"""Vertically stack matrices."""
|
| 477 |
+
return self.to_ddm().vstack(*[o.to_ddm() for o in others]).to_dfm()
|
| 478 |
+
|
| 479 |
+
def diagonal(self):
|
| 480 |
+
"""Return the diagonal of a DFM matrix."""
|
| 481 |
+
M = self.rep
|
| 482 |
+
m, n = self.shape
|
| 483 |
+
return [M[i, i] for i in range(min(m, n))]
|
| 484 |
+
|
| 485 |
+
def is_upper(self):
|
| 486 |
+
"""Return ``True`` if the matrix is upper triangular."""
|
| 487 |
+
M = self.rep
|
| 488 |
+
for i in range(self.rows):
|
| 489 |
+
for j in range(min(i, self.cols)):
|
| 490 |
+
if M[i, j]:
|
| 491 |
+
return False
|
| 492 |
+
return True
|
| 493 |
+
|
| 494 |
+
def is_lower(self):
|
| 495 |
+
"""Return ``True`` if the matrix is lower triangular."""
|
| 496 |
+
M = self.rep
|
| 497 |
+
for i in range(self.rows):
|
| 498 |
+
for j in range(i + 1, self.cols):
|
| 499 |
+
if M[i, j]:
|
| 500 |
+
return False
|
| 501 |
+
return True
|
| 502 |
+
|
| 503 |
+
def is_diagonal(self):
|
| 504 |
+
"""Return ``True`` if the matrix is diagonal."""
|
| 505 |
+
return self.is_upper() and self.is_lower()
|
| 506 |
+
|
| 507 |
+
def is_zero_matrix(self):
|
| 508 |
+
"""Return ``True`` if the matrix is the zero matrix."""
|
| 509 |
+
M = self.rep
|
| 510 |
+
for i in range(self.rows):
|
| 511 |
+
for j in range(self.cols):
|
| 512 |
+
if M[i, j]:
|
| 513 |
+
return False
|
| 514 |
+
return True
|
| 515 |
+
|
| 516 |
+
def nnz(self):
|
| 517 |
+
"""Return the number of non-zero elements in the matrix."""
|
| 518 |
+
return self.to_ddm().nnz()
|
| 519 |
+
|
| 520 |
+
def scc(self):
|
| 521 |
+
"""Return the strongly connected components of the matrix."""
|
| 522 |
+
return self.to_ddm().scc()
|
| 523 |
+
|
| 524 |
+
@doctest_depends_on(ground_types='flint')
|
| 525 |
+
def det(self):
|
| 526 |
+
"""
|
| 527 |
+
Compute the determinant of the matrix using FLINT.
|
| 528 |
+
|
| 529 |
+
Examples
|
| 530 |
+
========
|
| 531 |
+
|
| 532 |
+
>>> from sympy import Matrix
|
| 533 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
| 534 |
+
>>> dfm = M.to_DM().to_dfm()
|
| 535 |
+
>>> dfm
|
| 536 |
+
[[1, 2], [3, 4]]
|
| 537 |
+
>>> dfm.det()
|
| 538 |
+
-2
|
| 539 |
+
|
| 540 |
+
Notes
|
| 541 |
+
=====
|
| 542 |
+
|
| 543 |
+
Calls the ``.det()`` method of the underlying FLINT matrix.
|
| 544 |
+
|
| 545 |
+
For :ref:`ZZ` or :ref:`QQ` this calls ``fmpz_mat_det`` or
|
| 546 |
+
``fmpq_mat_det`` respectively.
|
| 547 |
+
|
| 548 |
+
At the time of writing the implementation of ``fmpz_mat_det`` uses one
|
| 549 |
+
of several algorithms depending on the size of the matrix and bit size
|
| 550 |
+
of the entries. The algorithms used are:
|
| 551 |
+
|
| 552 |
+
- Cofactor for very small (up to 4x4) matrices.
|
| 553 |
+
- Bareiss for small (up to 25x25) matrices.
|
| 554 |
+
- Modular algorithms for larger matrices (up to 60x60) or for larger
|
| 555 |
+
matrices with large bit sizes.
|
| 556 |
+
- Modular "accelerated" for larger matrices (60x60 upwards) if the bit
|
| 557 |
+
size is smaller than the dimensions of the matrix.
|
| 558 |
+
|
| 559 |
+
The implementation of ``fmpq_mat_det`` clears denominators from each
|
| 560 |
+
row (not the whole matrix) and then calls ``fmpz_mat_det`` and divides
|
| 561 |
+
by the product of the denominators.
|
| 562 |
+
|
| 563 |
+
See Also
|
| 564 |
+
========
|
| 565 |
+
|
| 566 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.det
|
| 567 |
+
Higher level interface to compute the determinant of a matrix.
|
| 568 |
+
"""
|
| 569 |
+
# XXX: At least the first three algorithms described above should also
|
| 570 |
+
# be implemented in the pure Python DDM and SDM classes which at the
|
| 571 |
+
# time of writng just use Bareiss for all matrices and domains.
|
| 572 |
+
# Probably in Python the thresholds would be different though.
|
| 573 |
+
return self.rep.det()
|
| 574 |
+
|
| 575 |
+
@doctest_depends_on(ground_types='flint')
|
| 576 |
+
def charpoly(self):
|
| 577 |
+
"""
|
| 578 |
+
Compute the characteristic polynomial of the matrix using FLINT.
|
| 579 |
+
|
| 580 |
+
Examples
|
| 581 |
+
========
|
| 582 |
+
|
| 583 |
+
>>> from sympy import Matrix
|
| 584 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
| 585 |
+
>>> dfm = M.to_DM().to_dfm() # need ground types = 'flint'
|
| 586 |
+
>>> dfm
|
| 587 |
+
[[1, 2], [3, 4]]
|
| 588 |
+
>>> dfm.charpoly()
|
| 589 |
+
[1, -5, -2]
|
| 590 |
+
|
| 591 |
+
Notes
|
| 592 |
+
=====
|
| 593 |
+
|
| 594 |
+
Calls the ``.charpoly()`` method of the underlying FLINT matrix.
|
| 595 |
+
|
| 596 |
+
For :ref:`ZZ` or :ref:`QQ` this calls ``fmpz_mat_charpoly`` or
|
| 597 |
+
``fmpq_mat_charpoly`` respectively.
|
| 598 |
+
|
| 599 |
+
At the time of writing the implementation of ``fmpq_mat_charpoly``
|
| 600 |
+
clears a denominator from the whole matrix and then calls
|
| 601 |
+
``fmpz_mat_charpoly``. The coefficients of the characteristic
|
| 602 |
+
polynomial are then multiplied by powers of the denominator.
|
| 603 |
+
|
| 604 |
+
The ``fmpz_mat_charpoly`` method uses a modular algorithm with CRT
|
| 605 |
+
reconstruction. The modular algorithm uses ``nmod_mat_charpoly`` which
|
| 606 |
+
uses Berkowitz for small matrices and non-prime moduli or otherwise
|
| 607 |
+
the Danilevsky method.
|
| 608 |
+
|
| 609 |
+
See Also
|
| 610 |
+
========
|
| 611 |
+
|
| 612 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly
|
| 613 |
+
Higher level interface to compute the characteristic polynomial of
|
| 614 |
+
a matrix.
|
| 615 |
+
"""
|
| 616 |
+
# FLINT polynomial coefficients are in reverse order compared to SymPy.
|
| 617 |
+
return self.rep.charpoly().coeffs()[::-1]
|
| 618 |
+
|
| 619 |
+
@doctest_depends_on(ground_types='flint')
|
| 620 |
+
def inv(self):
|
| 621 |
+
"""
|
| 622 |
+
Compute the inverse of a matrix using FLINT.
|
| 623 |
+
|
| 624 |
+
Examples
|
| 625 |
+
========
|
| 626 |
+
|
| 627 |
+
>>> from sympy import Matrix, QQ
|
| 628 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
| 629 |
+
>>> dfm = M.to_DM().to_dfm().convert_to(QQ)
|
| 630 |
+
>>> dfm
|
| 631 |
+
[[1, 2], [3, 4]]
|
| 632 |
+
>>> dfm.inv()
|
| 633 |
+
[[-2, 1], [3/2, -1/2]]
|
| 634 |
+
>>> dfm.matmul(dfm.inv())
|
| 635 |
+
[[1, 0], [0, 1]]
|
| 636 |
+
|
| 637 |
+
Notes
|
| 638 |
+
=====
|
| 639 |
+
|
| 640 |
+
Calls the ``.inv()`` method of the underlying FLINT matrix.
|
| 641 |
+
|
| 642 |
+
For now this will raise an error if the domain is :ref:`ZZ` but will
|
| 643 |
+
use the FLINT method for :ref:`QQ`.
|
| 644 |
+
|
| 645 |
+
The FLINT methods for :ref:`ZZ` and :ref:`QQ` are ``fmpz_mat_inv`` and
|
| 646 |
+
``fmpq_mat_inv`` respectively. The ``fmpz_mat_inv`` method computes an
|
| 647 |
+
inverse with denominator. This is implemented by calling
|
| 648 |
+
``fmpz_mat_solve`` (see notes in :meth:`lu_solve` about the algorithm).
|
| 649 |
+
|
| 650 |
+
The ``fmpq_mat_inv`` method clears denominators from each row and then
|
| 651 |
+
multiplies those into the rhs identity matrix before calling
|
| 652 |
+
``fmpz_mat_solve``.
|
| 653 |
+
|
| 654 |
+
See Also
|
| 655 |
+
========
|
| 656 |
+
|
| 657 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.inv
|
| 658 |
+
Higher level method for computing the inverse of a matrix.
|
| 659 |
+
"""
|
| 660 |
+
# TODO: Implement similar algorithms for DDM and SDM.
|
| 661 |
+
#
|
| 662 |
+
# XXX: The flint fmpz_mat and fmpq_mat inv methods both return fmpq_mat
|
| 663 |
+
# by default. The fmpz_mat method has an optional argument to return
|
| 664 |
+
# fmpz_mat instead for unimodular matrices.
|
| 665 |
+
#
|
| 666 |
+
# The convention in DomainMatrix is to raise an error if the matrix is
|
| 667 |
+
# not over a field regardless of whether the matrix is invertible over
|
| 668 |
+
# its domain or over any associated field. Maybe DomainMatrix.inv
|
| 669 |
+
# should be changed to always return a matrix over an associated field
|
| 670 |
+
# except with a unimodular argument for returning an inverse over a
|
| 671 |
+
# ring if possible.
|
| 672 |
+
#
|
| 673 |
+
# For now we follow the existing DomainMatrix convention...
|
| 674 |
+
K = self.domain
|
| 675 |
+
m, n = self.shape
|
| 676 |
+
|
| 677 |
+
if m != n:
|
| 678 |
+
raise DMNonSquareMatrixError("cannot invert a non-square matrix")
|
| 679 |
+
|
| 680 |
+
if K == ZZ:
|
| 681 |
+
raise DMDomainError("field expected, got %s" % K)
|
| 682 |
+
elif K == QQ or K.is_FF:
|
| 683 |
+
try:
|
| 684 |
+
return self._new_rep(self.rep.inv())
|
| 685 |
+
except ZeroDivisionError:
|
| 686 |
+
raise DMNonInvertibleMatrixError("matrix is not invertible")
|
| 687 |
+
else:
|
| 688 |
+
# If more domains are added for DFM then we will need to consider
|
| 689 |
+
# what happens here.
|
| 690 |
+
raise NotImplementedError("DFM.inv() is not implemented for %s" % K)
|
| 691 |
+
|
| 692 |
+
def lu(self):
|
| 693 |
+
"""Return the LU decomposition of the matrix."""
|
| 694 |
+
L, U, swaps = self.to_ddm().lu()
|
| 695 |
+
return L.to_dfm(), U.to_dfm(), swaps
|
| 696 |
+
|
| 697 |
+
def qr(self):
|
| 698 |
+
"""Return the QR decomposition of the matrix."""
|
| 699 |
+
Q, R = self.to_ddm().qr()
|
| 700 |
+
return Q.to_dfm(), R.to_dfm()
|
| 701 |
+
|
| 702 |
+
# XXX: The lu_solve function should be renamed to solve. Whether or not it
|
| 703 |
+
# uses an LU decomposition is an implementation detail. A method called
|
| 704 |
+
# lu_solve would make sense for a situation in which an LU decomposition is
|
| 705 |
+
# reused several times to solve with different rhs but that would imply a
|
| 706 |
+
# different call signature.
|
| 707 |
+
#
|
| 708 |
+
# The underlying python-flint method has an algorithm= argument so we could
|
| 709 |
+
# use that and have e.g. solve_lu and solve_modular or perhaps also a
|
| 710 |
+
# method= argument to choose between the two. Flint itself has more
|
| 711 |
+
# possible algorithms to choose from than are exposed by python-flint.
|
| 712 |
+
|
| 713 |
+
@doctest_depends_on(ground_types='flint')
|
| 714 |
+
def lu_solve(self, rhs):
|
| 715 |
+
"""
|
| 716 |
+
Solve a matrix equation using FLINT.
|
| 717 |
+
|
| 718 |
+
Examples
|
| 719 |
+
========
|
| 720 |
+
|
| 721 |
+
>>> from sympy import Matrix, QQ
|
| 722 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
| 723 |
+
>>> dfm = M.to_DM().to_dfm().convert_to(QQ)
|
| 724 |
+
>>> dfm
|
| 725 |
+
[[1, 2], [3, 4]]
|
| 726 |
+
>>> rhs = Matrix([1, 2]).to_DM().to_dfm().convert_to(QQ)
|
| 727 |
+
>>> dfm.lu_solve(rhs)
|
| 728 |
+
[[0], [1/2]]
|
| 729 |
+
|
| 730 |
+
Notes
|
| 731 |
+
=====
|
| 732 |
+
|
| 733 |
+
Calls the ``.solve()`` method of the underlying FLINT matrix.
|
| 734 |
+
|
| 735 |
+
For now this will raise an error if the domain is :ref:`ZZ` but will
|
| 736 |
+
use the FLINT method for :ref:`QQ`.
|
| 737 |
+
|
| 738 |
+
The FLINT methods for :ref:`ZZ` and :ref:`QQ` are ``fmpz_mat_solve``
|
| 739 |
+
and ``fmpq_mat_solve`` respectively. The ``fmpq_mat_solve`` method
|
| 740 |
+
uses one of two algorithms:
|
| 741 |
+
|
| 742 |
+
- For small matrices (<25 rows) it clears denominators between the
|
| 743 |
+
matrix and rhs and uses ``fmpz_mat_solve``.
|
| 744 |
+
- For larger matrices it uses ``fmpq_mat_solve_dixon`` which is a
|
| 745 |
+
modular approach with CRT reconstruction over :ref:`QQ`.
|
| 746 |
+
|
| 747 |
+
The ``fmpz_mat_solve`` method uses one of four algorithms:
|
| 748 |
+
|
| 749 |
+
- For very small (<= 3x3) matrices it uses a Cramer's rule.
|
| 750 |
+
- For small (<= 15x15) matrices it uses a fraction-free LU solve.
|
| 751 |
+
- Otherwise it uses either Dixon or another multimodular approach.
|
| 752 |
+
|
| 753 |
+
See Also
|
| 754 |
+
========
|
| 755 |
+
|
| 756 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.lu_solve
|
| 757 |
+
Higher level interface to solve a matrix equation.
|
| 758 |
+
"""
|
| 759 |
+
if not self.domain == rhs.domain:
|
| 760 |
+
raise DMDomainError("Domains must match: %s != %s" % (self.domain, rhs.domain))
|
| 761 |
+
|
| 762 |
+
# XXX: As for inv we should consider whether to return a matrix over
|
| 763 |
+
# over an associated field or attempt to find a solution in the ring.
|
| 764 |
+
# For now we follow the existing DomainMatrix convention...
|
| 765 |
+
if not self.domain.is_Field:
|
| 766 |
+
raise DMDomainError("Field expected, got %s" % self.domain)
|
| 767 |
+
|
| 768 |
+
m, n = self.shape
|
| 769 |
+
j, k = rhs.shape
|
| 770 |
+
if m != j:
|
| 771 |
+
raise DMShapeError("Matrix size mismatch: %s * %s vs %s * %s" % (m, n, j, k))
|
| 772 |
+
sol_shape = (n, k)
|
| 773 |
+
|
| 774 |
+
# XXX: The Flint solve method only handles square matrices. Probably
|
| 775 |
+
# Flint has functions that could be used to solve non-square systems
|
| 776 |
+
# but they are not exposed in python-flint yet. Alternatively we could
|
| 777 |
+
# put something here using the features that are available like rref.
|
| 778 |
+
if m != n:
|
| 779 |
+
return self.to_ddm().lu_solve(rhs.to_ddm()).to_dfm()
|
| 780 |
+
|
| 781 |
+
try:
|
| 782 |
+
sol = self.rep.solve(rhs.rep)
|
| 783 |
+
except ZeroDivisionError:
|
| 784 |
+
raise DMNonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
| 785 |
+
|
| 786 |
+
return self._new(sol, sol_shape, self.domain)
|
| 787 |
+
|
| 788 |
+
def fflu(self):
|
| 789 |
+
"""
|
| 790 |
+
Fraction-free LU decomposition of DFM.
|
| 791 |
+
|
| 792 |
+
Explanation
|
| 793 |
+
===========
|
| 794 |
+
|
| 795 |
+
Uses `python-flint` if possible for a matrix of
|
| 796 |
+
integers otherwise uses the DDM method.
|
| 797 |
+
|
| 798 |
+
See Also
|
| 799 |
+
========
|
| 800 |
+
|
| 801 |
+
sympy.polys.matrices.ddm.DDM.fflu
|
| 802 |
+
"""
|
| 803 |
+
if self.domain == ZZ:
|
| 804 |
+
fflu = getattr(self.rep, 'fflu', None)
|
| 805 |
+
if fflu is not None:
|
| 806 |
+
P, L, D, U = self.rep.fflu()
|
| 807 |
+
m, n = self.shape
|
| 808 |
+
return (
|
| 809 |
+
self._new(P, (m, m), self.domain),
|
| 810 |
+
self._new(L, (m, m), self.domain),
|
| 811 |
+
self._new(D, (m, m), self.domain),
|
| 812 |
+
self._new(U, self.shape, self.domain)
|
| 813 |
+
)
|
| 814 |
+
ddm_p, ddm_l, ddm_d, ddm_u = self.to_ddm().fflu()
|
| 815 |
+
P = ddm_p.to_dfm()
|
| 816 |
+
L = ddm_l.to_dfm()
|
| 817 |
+
D = ddm_d.to_dfm()
|
| 818 |
+
U = ddm_u.to_dfm()
|
| 819 |
+
return P, L, D, U
|
| 820 |
+
|
| 821 |
+
def nullspace(self):
|
| 822 |
+
"""Return a basis for the nullspace of the matrix."""
|
| 823 |
+
# Code to compute nullspace using flint:
|
| 824 |
+
#
|
| 825 |
+
# V, nullity = self.rep.nullspace()
|
| 826 |
+
# V_dfm = self._new_rep(V)._extract(range(self.rows), range(nullity))
|
| 827 |
+
#
|
| 828 |
+
# XXX: That gives the nullspace but does not give us nonpivots. So we
|
| 829 |
+
# use the slower DDM method anyway. It would be better to change the
|
| 830 |
+
# signature of the nullspace method to not return nonpivots.
|
| 831 |
+
#
|
| 832 |
+
# XXX: Also python-flint exposes a nullspace method for fmpz_mat but
|
| 833 |
+
# not for fmpq_mat. This is the reverse of the situation for DDM etc
|
| 834 |
+
# which only allow nullspace over a field. The nullspace method for
|
| 835 |
+
# DDM, SDM etc should be changed to allow nullspace over ZZ as well.
|
| 836 |
+
# The DomainMatrix nullspace method does allow the domain to be a ring
|
| 837 |
+
# but does not directly call the lower-level nullspace methods and uses
|
| 838 |
+
# rref_den instead. Nullspace methods should also be added to all
|
| 839 |
+
# matrix types in python-flint.
|
| 840 |
+
ddm, nonpivots = self.to_ddm().nullspace()
|
| 841 |
+
return ddm.to_dfm(), nonpivots
|
| 842 |
+
|
| 843 |
+
def nullspace_from_rref(self, pivots=None):
|
| 844 |
+
"""Return a basis for the nullspace of the matrix."""
|
| 845 |
+
# XXX: Use the flint nullspace method!!!
|
| 846 |
+
sdm, nonpivots = self.to_sdm().nullspace_from_rref(pivots=pivots)
|
| 847 |
+
return sdm.to_dfm(), nonpivots
|
| 848 |
+
|
| 849 |
+
def particular(self):
|
| 850 |
+
"""Return a particular solution to the system."""
|
| 851 |
+
return self.to_ddm().particular().to_dfm()
|
| 852 |
+
|
| 853 |
+
def _lll(self, transform=False, delta=0.99, eta=0.51, rep='zbasis', gram='approx'):
|
| 854 |
+
"""Call the fmpz_mat.lll() method but check rank to avoid segfaults."""
|
| 855 |
+
|
| 856 |
+
# XXX: There are tests that pass e.g. QQ(5,6) for delta. That fails
|
| 857 |
+
# with a TypeError in flint because if QQ is fmpq then conversion with
|
| 858 |
+
# float fails. We handle that here but there are two better fixes:
|
| 859 |
+
#
|
| 860 |
+
# - Make python-flint's fmpq convert with float(x)
|
| 861 |
+
# - Change the tests because delta should just be a float.
|
| 862 |
+
|
| 863 |
+
def to_float(x):
|
| 864 |
+
if QQ.of_type(x):
|
| 865 |
+
return float(x.numerator) / float(x.denominator)
|
| 866 |
+
else:
|
| 867 |
+
return float(x)
|
| 868 |
+
|
| 869 |
+
delta = to_float(delta)
|
| 870 |
+
eta = to_float(eta)
|
| 871 |
+
|
| 872 |
+
if not 0.25 < delta < 1:
|
| 873 |
+
raise DMValueError("delta must be between 0.25 and 1")
|
| 874 |
+
|
| 875 |
+
# XXX: The flint lll method segfaults if the matrix is not full rank.
|
| 876 |
+
m, n = self.shape
|
| 877 |
+
if self.rep.rank() != m:
|
| 878 |
+
raise DMRankError("Matrix must have full row rank for Flint LLL.")
|
| 879 |
+
|
| 880 |
+
# Actually call the flint method.
|
| 881 |
+
return self.rep.lll(transform=transform, delta=delta, eta=eta, rep=rep, gram=gram)
|
| 882 |
+
|
| 883 |
+
@doctest_depends_on(ground_types='flint')
|
| 884 |
+
def lll(self, delta=0.75):
|
| 885 |
+
"""Compute LLL-reduced basis using FLINT.
|
| 886 |
+
|
| 887 |
+
See :meth:`lll_transform` for more information.
|
| 888 |
+
|
| 889 |
+
Examples
|
| 890 |
+
========
|
| 891 |
+
|
| 892 |
+
>>> from sympy import Matrix
|
| 893 |
+
>>> M = Matrix([[1, 2, 3], [4, 5, 6]])
|
| 894 |
+
>>> M.to_DM().to_dfm().lll()
|
| 895 |
+
[[2, 1, 0], [-1, 1, 3]]
|
| 896 |
+
|
| 897 |
+
See Also
|
| 898 |
+
========
|
| 899 |
+
|
| 900 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.lll
|
| 901 |
+
Higher level interface to compute LLL-reduced basis.
|
| 902 |
+
lll_transform
|
| 903 |
+
Compute LLL-reduced basis and transform matrix.
|
| 904 |
+
"""
|
| 905 |
+
if self.domain != ZZ:
|
| 906 |
+
raise DMDomainError("ZZ expected, got %s" % self.domain)
|
| 907 |
+
elif self.rows > self.cols:
|
| 908 |
+
raise DMShapeError("Matrix must not have more rows than columns.")
|
| 909 |
+
|
| 910 |
+
rep = self._lll(delta=delta)
|
| 911 |
+
return self._new_rep(rep)
|
| 912 |
+
|
| 913 |
+
@doctest_depends_on(ground_types='flint')
|
| 914 |
+
def lll_transform(self, delta=0.75):
|
| 915 |
+
"""Compute LLL-reduced basis and transform using FLINT.
|
| 916 |
+
|
| 917 |
+
Examples
|
| 918 |
+
========
|
| 919 |
+
|
| 920 |
+
>>> from sympy import Matrix
|
| 921 |
+
>>> M = Matrix([[1, 2, 3], [4, 5, 6]]).to_DM().to_dfm()
|
| 922 |
+
>>> M_lll, T = M.lll_transform()
|
| 923 |
+
>>> M_lll
|
| 924 |
+
[[2, 1, 0], [-1, 1, 3]]
|
| 925 |
+
>>> T
|
| 926 |
+
[[-2, 1], [3, -1]]
|
| 927 |
+
>>> T.matmul(M) == M_lll
|
| 928 |
+
True
|
| 929 |
+
|
| 930 |
+
See Also
|
| 931 |
+
========
|
| 932 |
+
|
| 933 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.lll
|
| 934 |
+
Higher level interface to compute LLL-reduced basis.
|
| 935 |
+
lll
|
| 936 |
+
Compute LLL-reduced basis without transform matrix.
|
| 937 |
+
"""
|
| 938 |
+
if self.domain != ZZ:
|
| 939 |
+
raise DMDomainError("ZZ expected, got %s" % self.domain)
|
| 940 |
+
elif self.rows > self.cols:
|
| 941 |
+
raise DMShapeError("Matrix must not have more rows than columns.")
|
| 942 |
+
|
| 943 |
+
rep, T = self._lll(transform=True, delta=delta)
|
| 944 |
+
basis = self._new_rep(rep)
|
| 945 |
+
T_dfm = self._new(T, (self.rows, self.rows), self.domain)
|
| 946 |
+
return basis, T_dfm
|
| 947 |
+
|
| 948 |
+
|
| 949 |
+
# Avoid circular imports
|
| 950 |
+
from sympy.polys.matrices.ddm import DDM
|
| 951 |
+
from sympy.polys.matrices.ddm import SDM
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/_typing.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import TypeVar, Protocol
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
T = TypeVar('T')
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class RingElement(Protocol):
|
| 8 |
+
"""A ring element.
|
| 9 |
+
|
| 10 |
+
Must support ``+``, ``-``, ``*``, ``**`` and ``-``.
|
| 11 |
+
"""
|
| 12 |
+
def __add__(self: T, other: T, /) -> T: ...
|
| 13 |
+
def __sub__(self: T, other: T, /) -> T: ...
|
| 14 |
+
def __mul__(self: T, other: T, /) -> T: ...
|
| 15 |
+
def __pow__(self: T, other: int, /) -> T: ...
|
| 16 |
+
def __neg__(self: T, /) -> T: ...
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/ddm.py
ADDED
|
@@ -0,0 +1,1176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
|
| 3 |
+
Module for the DDM class.
|
| 4 |
+
|
| 5 |
+
The DDM class is an internal representation used by DomainMatrix. The letters
|
| 6 |
+
DDM stand for Dense Domain Matrix. A DDM instance represents a matrix using
|
| 7 |
+
elements from a polynomial Domain (e.g. ZZ, QQ, ...) in a dense-matrix
|
| 8 |
+
representation.
|
| 9 |
+
|
| 10 |
+
Basic usage:
|
| 11 |
+
|
| 12 |
+
>>> from sympy import ZZ, QQ
|
| 13 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 14 |
+
>>> A = DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
|
| 15 |
+
>>> A.shape
|
| 16 |
+
(2, 2)
|
| 17 |
+
>>> A
|
| 18 |
+
[[0, 1], [-1, 0]]
|
| 19 |
+
>>> type(A)
|
| 20 |
+
<class 'sympy.polys.matrices.ddm.DDM'>
|
| 21 |
+
>>> A @ A
|
| 22 |
+
[[-1, 0], [0, -1]]
|
| 23 |
+
|
| 24 |
+
The ddm_* functions are designed to operate on DDM as well as on an ordinary
|
| 25 |
+
list of lists:
|
| 26 |
+
|
| 27 |
+
>>> from sympy.polys.matrices.dense import ddm_idet
|
| 28 |
+
>>> ddm_idet(A, QQ)
|
| 29 |
+
1
|
| 30 |
+
>>> ddm_idet([[0, 1], [-1, 0]], QQ)
|
| 31 |
+
1
|
| 32 |
+
>>> A
|
| 33 |
+
[[-1, 0], [0, -1]]
|
| 34 |
+
|
| 35 |
+
Note that ddm_idet modifies the input matrix in-place. It is recommended to
|
| 36 |
+
use the DDM.det method as a friendlier interface to this instead which takes
|
| 37 |
+
care of copying the matrix:
|
| 38 |
+
|
| 39 |
+
>>> B = DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
|
| 40 |
+
>>> B.det()
|
| 41 |
+
1
|
| 42 |
+
|
| 43 |
+
Normally DDM would not be used directly and is just part of the internal
|
| 44 |
+
representation of DomainMatrix which adds further functionality including e.g.
|
| 45 |
+
unifying domains.
|
| 46 |
+
|
| 47 |
+
The dense format used by DDM is a list of lists of elements e.g. the 2x2
|
| 48 |
+
identity matrix is like [[1, 0], [0, 1]]. The DDM class itself is a subclass
|
| 49 |
+
of list and its list items are plain lists. Elements are accessed as e.g.
|
| 50 |
+
ddm[i][j] where ddm[i] gives the ith row and ddm[i][j] gets the element in the
|
| 51 |
+
jth column of that row. Subclassing list makes e.g. iteration and indexing
|
| 52 |
+
very efficient. We do not override __getitem__ because it would lose that
|
| 53 |
+
benefit.
|
| 54 |
+
|
| 55 |
+
The core routines are implemented by the ddm_* functions defined in dense.py.
|
| 56 |
+
Those functions are intended to be able to operate on a raw list-of-lists
|
| 57 |
+
representation of matrices with most functions operating in-place. The DDM
|
| 58 |
+
class takes care of copying etc and also stores a Domain object associated
|
| 59 |
+
with its elements. This makes it possible to implement things like A + B with
|
| 60 |
+
domain checking and also shape checking so that the list of lists
|
| 61 |
+
representation is friendlier.
|
| 62 |
+
|
| 63 |
+
"""
|
| 64 |
+
from itertools import chain
|
| 65 |
+
|
| 66 |
+
from sympy.external.gmpy import GROUND_TYPES
|
| 67 |
+
from sympy.utilities.decorator import doctest_depends_on
|
| 68 |
+
|
| 69 |
+
from .exceptions import (
|
| 70 |
+
DMBadInputError,
|
| 71 |
+
DMDomainError,
|
| 72 |
+
DMNonSquareMatrixError,
|
| 73 |
+
DMShapeError,
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
from sympy.polys.domains import QQ
|
| 77 |
+
|
| 78 |
+
from .dense import (
|
| 79 |
+
ddm_transpose,
|
| 80 |
+
ddm_iadd,
|
| 81 |
+
ddm_isub,
|
| 82 |
+
ddm_ineg,
|
| 83 |
+
ddm_imul,
|
| 84 |
+
ddm_irmul,
|
| 85 |
+
ddm_imatmul,
|
| 86 |
+
ddm_irref,
|
| 87 |
+
ddm_irref_den,
|
| 88 |
+
ddm_idet,
|
| 89 |
+
ddm_iinv,
|
| 90 |
+
ddm_ilu_split,
|
| 91 |
+
ddm_ilu_solve,
|
| 92 |
+
ddm_berk,
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
from .lll import ddm_lll, ddm_lll_transform
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
if GROUND_TYPES != 'flint':
|
| 99 |
+
__doctest_skip__ = ['DDM.to_dfm', 'DDM.to_dfm_or_ddm']
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class DDM(list):
|
| 103 |
+
"""Dense matrix based on polys domain elements
|
| 104 |
+
|
| 105 |
+
This is a list subclass and is a wrapper for a list of lists that supports
|
| 106 |
+
basic matrix arithmetic +, -, *, **.
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
fmt = 'dense'
|
| 110 |
+
is_DFM = False
|
| 111 |
+
is_DDM = True
|
| 112 |
+
|
| 113 |
+
def __init__(self, rowslist, shape, domain):
|
| 114 |
+
if not (isinstance(rowslist, list) and all(type(row) is list for row in rowslist)):
|
| 115 |
+
raise DMBadInputError("rowslist must be a list of lists")
|
| 116 |
+
m, n = shape
|
| 117 |
+
if len(rowslist) != m or any(len(row) != n for row in rowslist):
|
| 118 |
+
raise DMBadInputError("Inconsistent row-list/shape")
|
| 119 |
+
|
| 120 |
+
super().__init__([i.copy() for i in rowslist])
|
| 121 |
+
self.shape = (m, n)
|
| 122 |
+
self.rows = m
|
| 123 |
+
self.cols = n
|
| 124 |
+
self.domain = domain
|
| 125 |
+
|
| 126 |
+
def getitem(self, i, j):
|
| 127 |
+
return self[i][j]
|
| 128 |
+
|
| 129 |
+
def setitem(self, i, j, value):
|
| 130 |
+
self[i][j] = value
|
| 131 |
+
|
| 132 |
+
def extract_slice(self, slice1, slice2):
|
| 133 |
+
ddm = [row[slice2] for row in self[slice1]]
|
| 134 |
+
rows = len(ddm)
|
| 135 |
+
cols = len(ddm[0]) if ddm else len(range(self.shape[1])[slice2])
|
| 136 |
+
return DDM(ddm, (rows, cols), self.domain)
|
| 137 |
+
|
| 138 |
+
def extract(self, rows, cols):
|
| 139 |
+
ddm = []
|
| 140 |
+
for i in rows:
|
| 141 |
+
rowi = self[i]
|
| 142 |
+
ddm.append([rowi[j] for j in cols])
|
| 143 |
+
return DDM(ddm, (len(rows), len(cols)), self.domain)
|
| 144 |
+
|
| 145 |
+
@classmethod
|
| 146 |
+
def from_list(cls, rowslist, shape, domain):
|
| 147 |
+
"""
|
| 148 |
+
Create a :class:`DDM` from a list of lists.
|
| 149 |
+
|
| 150 |
+
Examples
|
| 151 |
+
========
|
| 152 |
+
|
| 153 |
+
>>> from sympy import ZZ
|
| 154 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 155 |
+
>>> A = DDM.from_list([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
|
| 156 |
+
>>> A
|
| 157 |
+
[[0, 1], [-1, 0]]
|
| 158 |
+
>>> A == DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
|
| 159 |
+
True
|
| 160 |
+
|
| 161 |
+
See Also
|
| 162 |
+
========
|
| 163 |
+
|
| 164 |
+
from_list_flat
|
| 165 |
+
"""
|
| 166 |
+
return cls(rowslist, shape, domain)
|
| 167 |
+
|
| 168 |
+
@classmethod
|
| 169 |
+
def from_ddm(cls, other):
|
| 170 |
+
return other.copy()
|
| 171 |
+
|
| 172 |
+
def to_list(self):
|
| 173 |
+
"""
|
| 174 |
+
Convert to a list of lists.
|
| 175 |
+
|
| 176 |
+
Examples
|
| 177 |
+
========
|
| 178 |
+
|
| 179 |
+
>>> from sympy import QQ
|
| 180 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 181 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 182 |
+
>>> A.to_list()
|
| 183 |
+
[[1, 2], [3, 4]]
|
| 184 |
+
|
| 185 |
+
See Also
|
| 186 |
+
========
|
| 187 |
+
|
| 188 |
+
to_list_flat
|
| 189 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_list
|
| 190 |
+
"""
|
| 191 |
+
return [row[:] for row in self]
|
| 192 |
+
|
| 193 |
+
def to_list_flat(self):
|
| 194 |
+
"""
|
| 195 |
+
Convert to a flat list of elements.
|
| 196 |
+
|
| 197 |
+
Examples
|
| 198 |
+
========
|
| 199 |
+
|
| 200 |
+
>>> from sympy import QQ
|
| 201 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 202 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 203 |
+
>>> A.to_list_flat()
|
| 204 |
+
[1, 2, 3, 4]
|
| 205 |
+
>>> A == DDM.from_list_flat(A.to_list_flat(), A.shape, A.domain)
|
| 206 |
+
True
|
| 207 |
+
|
| 208 |
+
See Also
|
| 209 |
+
========
|
| 210 |
+
|
| 211 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_list_flat
|
| 212 |
+
"""
|
| 213 |
+
flat = []
|
| 214 |
+
for row in self:
|
| 215 |
+
flat.extend(row)
|
| 216 |
+
return flat
|
| 217 |
+
|
| 218 |
+
@classmethod
|
| 219 |
+
def from_list_flat(cls, flat, shape, domain):
|
| 220 |
+
"""
|
| 221 |
+
Create a :class:`DDM` from a flat list of elements.
|
| 222 |
+
|
| 223 |
+
Examples
|
| 224 |
+
========
|
| 225 |
+
|
| 226 |
+
>>> from sympy import QQ
|
| 227 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 228 |
+
>>> A = DDM.from_list_flat([1, 2, 3, 4], (2, 2), QQ)
|
| 229 |
+
>>> A
|
| 230 |
+
[[1, 2], [3, 4]]
|
| 231 |
+
>>> A == DDM.from_list_flat(A.to_list_flat(), A.shape, A.domain)
|
| 232 |
+
True
|
| 233 |
+
|
| 234 |
+
See Also
|
| 235 |
+
========
|
| 236 |
+
|
| 237 |
+
to_list_flat
|
| 238 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.from_list_flat
|
| 239 |
+
"""
|
| 240 |
+
assert type(flat) is list
|
| 241 |
+
rows, cols = shape
|
| 242 |
+
if not (len(flat) == rows*cols):
|
| 243 |
+
raise DMBadInputError("Inconsistent flat-list shape")
|
| 244 |
+
lol = [flat[i*cols:(i+1)*cols] for i in range(rows)]
|
| 245 |
+
return cls(lol, shape, domain)
|
| 246 |
+
|
| 247 |
+
def flatiter(self):
|
| 248 |
+
return chain.from_iterable(self)
|
| 249 |
+
|
| 250 |
+
def flat(self):
|
| 251 |
+
items = []
|
| 252 |
+
for row in self:
|
| 253 |
+
items.extend(row)
|
| 254 |
+
return items
|
| 255 |
+
|
| 256 |
+
def to_flat_nz(self):
|
| 257 |
+
"""
|
| 258 |
+
Convert to a flat list of nonzero elements and data.
|
| 259 |
+
|
| 260 |
+
Explanation
|
| 261 |
+
===========
|
| 262 |
+
|
| 263 |
+
This is used to operate on a list of the elements of a matrix and then
|
| 264 |
+
reconstruct a matrix using :meth:`from_flat_nz`. Zero elements are
|
| 265 |
+
included in the list but that may change in the future.
|
| 266 |
+
|
| 267 |
+
Examples
|
| 268 |
+
========
|
| 269 |
+
|
| 270 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 271 |
+
>>> from sympy import QQ
|
| 272 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 273 |
+
>>> elements, data = A.to_flat_nz()
|
| 274 |
+
>>> elements
|
| 275 |
+
[1, 2, 3, 4]
|
| 276 |
+
>>> A == DDM.from_flat_nz(elements, data, A.domain)
|
| 277 |
+
True
|
| 278 |
+
|
| 279 |
+
See Also
|
| 280 |
+
========
|
| 281 |
+
|
| 282 |
+
from_flat_nz
|
| 283 |
+
sympy.polys.matrices.sdm.SDM.to_flat_nz
|
| 284 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_flat_nz
|
| 285 |
+
"""
|
| 286 |
+
return self.to_sdm().to_flat_nz()
|
| 287 |
+
|
| 288 |
+
@classmethod
|
| 289 |
+
def from_flat_nz(cls, elements, data, domain):
|
| 290 |
+
"""
|
| 291 |
+
Reconstruct a :class:`DDM` after calling :meth:`to_flat_nz`.
|
| 292 |
+
|
| 293 |
+
Examples
|
| 294 |
+
========
|
| 295 |
+
|
| 296 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 297 |
+
>>> from sympy import QQ
|
| 298 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 299 |
+
>>> elements, data = A.to_flat_nz()
|
| 300 |
+
>>> elements
|
| 301 |
+
[1, 2, 3, 4]
|
| 302 |
+
>>> A == DDM.from_flat_nz(elements, data, A.domain)
|
| 303 |
+
True
|
| 304 |
+
|
| 305 |
+
See Also
|
| 306 |
+
========
|
| 307 |
+
|
| 308 |
+
to_flat_nz
|
| 309 |
+
sympy.polys.matrices.sdm.SDM.from_flat_nz
|
| 310 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.from_flat_nz
|
| 311 |
+
"""
|
| 312 |
+
return SDM.from_flat_nz(elements, data, domain).to_ddm()
|
| 313 |
+
|
| 314 |
+
def to_dod(self):
|
| 315 |
+
"""
|
| 316 |
+
Convert to a dictionary of dictionaries (dod) format.
|
| 317 |
+
|
| 318 |
+
Examples
|
| 319 |
+
========
|
| 320 |
+
|
| 321 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 322 |
+
>>> from sympy import QQ
|
| 323 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 324 |
+
>>> A.to_dod()
|
| 325 |
+
{0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}
|
| 326 |
+
|
| 327 |
+
See Also
|
| 328 |
+
========
|
| 329 |
+
|
| 330 |
+
from_dod
|
| 331 |
+
sympy.polys.matrices.sdm.SDM.to_dod
|
| 332 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod
|
| 333 |
+
"""
|
| 334 |
+
dod = {}
|
| 335 |
+
for i, row in enumerate(self):
|
| 336 |
+
row = {j:e for j, e in enumerate(row) if e}
|
| 337 |
+
if row:
|
| 338 |
+
dod[i] = row
|
| 339 |
+
return dod
|
| 340 |
+
|
| 341 |
+
@classmethod
|
| 342 |
+
def from_dod(cls, dod, shape, domain):
|
| 343 |
+
"""
|
| 344 |
+
Create a :class:`DDM` from a dictionary of dictionaries (dod) format.
|
| 345 |
+
|
| 346 |
+
Examples
|
| 347 |
+
========
|
| 348 |
+
|
| 349 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 350 |
+
>>> from sympy import QQ
|
| 351 |
+
>>> dod = {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}
|
| 352 |
+
>>> A = DDM.from_dod(dod, (2, 2), QQ)
|
| 353 |
+
>>> A
|
| 354 |
+
[[1, 2], [3, 4]]
|
| 355 |
+
|
| 356 |
+
See Also
|
| 357 |
+
========
|
| 358 |
+
|
| 359 |
+
to_dod
|
| 360 |
+
sympy.polys.matrices.sdm.SDM.from_dod
|
| 361 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.from_dod
|
| 362 |
+
"""
|
| 363 |
+
rows, cols = shape
|
| 364 |
+
lol = [[domain.zero] * cols for _ in range(rows)]
|
| 365 |
+
for i, row in dod.items():
|
| 366 |
+
for j, element in row.items():
|
| 367 |
+
lol[i][j] = element
|
| 368 |
+
return DDM(lol, shape, domain)
|
| 369 |
+
|
| 370 |
+
def to_dok(self):
|
| 371 |
+
"""
|
| 372 |
+
Convert :class:`DDM` to dictionary of keys (dok) format.
|
| 373 |
+
|
| 374 |
+
Examples
|
| 375 |
+
========
|
| 376 |
+
|
| 377 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 378 |
+
>>> from sympy import QQ
|
| 379 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 380 |
+
>>> A.to_dok()
|
| 381 |
+
{(0, 0): 1, (0, 1): 2, (1, 0): 3, (1, 1): 4}
|
| 382 |
+
|
| 383 |
+
See Also
|
| 384 |
+
========
|
| 385 |
+
|
| 386 |
+
from_dok
|
| 387 |
+
sympy.polys.matrices.sdm.SDM.to_dok
|
| 388 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dok
|
| 389 |
+
"""
|
| 390 |
+
dok = {}
|
| 391 |
+
for i, row in enumerate(self):
|
| 392 |
+
for j, element in enumerate(row):
|
| 393 |
+
if element:
|
| 394 |
+
dok[i, j] = element
|
| 395 |
+
return dok
|
| 396 |
+
|
| 397 |
+
@classmethod
|
| 398 |
+
def from_dok(cls, dok, shape, domain):
|
| 399 |
+
"""
|
| 400 |
+
Create a :class:`DDM` from a dictionary of keys (dok) format.
|
| 401 |
+
|
| 402 |
+
Examples
|
| 403 |
+
========
|
| 404 |
+
|
| 405 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 406 |
+
>>> from sympy import QQ
|
| 407 |
+
>>> dok = {(0, 0): 1, (0, 1): 2, (1, 0): 3, (1, 1): 4}
|
| 408 |
+
>>> A = DDM.from_dok(dok, (2, 2), QQ)
|
| 409 |
+
>>> A
|
| 410 |
+
[[1, 2], [3, 4]]
|
| 411 |
+
|
| 412 |
+
See Also
|
| 413 |
+
========
|
| 414 |
+
|
| 415 |
+
to_dok
|
| 416 |
+
sympy.polys.matrices.sdm.SDM.from_dok
|
| 417 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.from_dok
|
| 418 |
+
"""
|
| 419 |
+
rows, cols = shape
|
| 420 |
+
lol = [[domain.zero] * cols for _ in range(rows)]
|
| 421 |
+
for (i, j), element in dok.items():
|
| 422 |
+
lol[i][j] = element
|
| 423 |
+
return DDM(lol, shape, domain)
|
| 424 |
+
|
| 425 |
+
def iter_values(self):
|
| 426 |
+
"""
|
| 427 |
+
Iterate over the non-zero values of the matrix.
|
| 428 |
+
|
| 429 |
+
Examples
|
| 430 |
+
========
|
| 431 |
+
|
| 432 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 433 |
+
>>> from sympy import QQ
|
| 434 |
+
>>> A = DDM([[QQ(1), QQ(0)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 435 |
+
>>> list(A.iter_values())
|
| 436 |
+
[1, 3, 4]
|
| 437 |
+
|
| 438 |
+
See Also
|
| 439 |
+
========
|
| 440 |
+
|
| 441 |
+
iter_items
|
| 442 |
+
to_list_flat
|
| 443 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.iter_values
|
| 444 |
+
"""
|
| 445 |
+
for row in self:
|
| 446 |
+
yield from filter(None, row)
|
| 447 |
+
|
| 448 |
+
def iter_items(self):
|
| 449 |
+
"""
|
| 450 |
+
Iterate over indices and values of nonzero elements of the matrix.
|
| 451 |
+
|
| 452 |
+
Examples
|
| 453 |
+
========
|
| 454 |
+
|
| 455 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 456 |
+
>>> from sympy import QQ
|
| 457 |
+
>>> A = DDM([[QQ(1), QQ(0)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 458 |
+
>>> list(A.iter_items())
|
| 459 |
+
[((0, 0), 1), ((1, 0), 3), ((1, 1), 4)]
|
| 460 |
+
|
| 461 |
+
See Also
|
| 462 |
+
========
|
| 463 |
+
|
| 464 |
+
iter_values
|
| 465 |
+
to_dok
|
| 466 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.iter_items
|
| 467 |
+
"""
|
| 468 |
+
for i, row in enumerate(self):
|
| 469 |
+
for j, element in enumerate(row):
|
| 470 |
+
if element:
|
| 471 |
+
yield (i, j), element
|
| 472 |
+
|
| 473 |
+
def to_ddm(self):
|
| 474 |
+
"""
|
| 475 |
+
Convert to a :class:`DDM`.
|
| 476 |
+
|
| 477 |
+
This just returns ``self`` but exists to parallel the corresponding
|
| 478 |
+
method in other matrix types like :class:`~.SDM`.
|
| 479 |
+
|
| 480 |
+
See Also
|
| 481 |
+
========
|
| 482 |
+
|
| 483 |
+
to_sdm
|
| 484 |
+
to_dfm
|
| 485 |
+
to_dfm_or_ddm
|
| 486 |
+
sympy.polys.matrices.sdm.SDM.to_ddm
|
| 487 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_ddm
|
| 488 |
+
"""
|
| 489 |
+
return self
|
| 490 |
+
|
| 491 |
+
def to_sdm(self):
|
| 492 |
+
"""
|
| 493 |
+
Convert to a :class:`~.SDM`.
|
| 494 |
+
|
| 495 |
+
Examples
|
| 496 |
+
========
|
| 497 |
+
|
| 498 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 499 |
+
>>> from sympy import QQ
|
| 500 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 501 |
+
>>> A.to_sdm()
|
| 502 |
+
{0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}
|
| 503 |
+
>>> type(A.to_sdm())
|
| 504 |
+
<class 'sympy.polys.matrices.sdm.SDM'>
|
| 505 |
+
|
| 506 |
+
See Also
|
| 507 |
+
========
|
| 508 |
+
|
| 509 |
+
SDM
|
| 510 |
+
sympy.polys.matrices.sdm.SDM.to_ddm
|
| 511 |
+
"""
|
| 512 |
+
return SDM.from_list(self, self.shape, self.domain)
|
| 513 |
+
|
| 514 |
+
@doctest_depends_on(ground_types=['flint'])
|
| 515 |
+
def to_dfm(self):
|
| 516 |
+
"""
|
| 517 |
+
Convert to :class:`~.DDM` to :class:`~.DFM`.
|
| 518 |
+
|
| 519 |
+
Examples
|
| 520 |
+
========
|
| 521 |
+
|
| 522 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 523 |
+
>>> from sympy import QQ
|
| 524 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 525 |
+
>>> A.to_dfm()
|
| 526 |
+
[[1, 2], [3, 4]]
|
| 527 |
+
>>> type(A.to_dfm())
|
| 528 |
+
<class 'sympy.polys.matrices._dfm.DFM'>
|
| 529 |
+
|
| 530 |
+
See Also
|
| 531 |
+
========
|
| 532 |
+
|
| 533 |
+
DFM
|
| 534 |
+
sympy.polys.matrices._dfm.DFM.to_ddm
|
| 535 |
+
"""
|
| 536 |
+
return DFM(list(self), self.shape, self.domain)
|
| 537 |
+
|
| 538 |
+
@doctest_depends_on(ground_types=['flint'])
|
| 539 |
+
def to_dfm_or_ddm(self):
|
| 540 |
+
"""
|
| 541 |
+
Convert to :class:`~.DFM` if possible or otherwise return self.
|
| 542 |
+
|
| 543 |
+
Examples
|
| 544 |
+
========
|
| 545 |
+
|
| 546 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 547 |
+
>>> from sympy import QQ
|
| 548 |
+
>>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
|
| 549 |
+
>>> A.to_dfm_or_ddm()
|
| 550 |
+
[[1, 2], [3, 4]]
|
| 551 |
+
>>> type(A.to_dfm_or_ddm())
|
| 552 |
+
<class 'sympy.polys.matrices._dfm.DFM'>
|
| 553 |
+
|
| 554 |
+
See Also
|
| 555 |
+
========
|
| 556 |
+
|
| 557 |
+
to_dfm
|
| 558 |
+
to_ddm
|
| 559 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm
|
| 560 |
+
"""
|
| 561 |
+
if DFM._supports_domain(self.domain):
|
| 562 |
+
return self.to_dfm()
|
| 563 |
+
return self
|
| 564 |
+
|
| 565 |
+
def convert_to(self, K):
|
| 566 |
+
Kold = self.domain
|
| 567 |
+
if K == Kold:
|
| 568 |
+
return self.copy()
|
| 569 |
+
rows = [[K.convert_from(e, Kold) for e in row] for row in self]
|
| 570 |
+
return DDM(rows, self.shape, K)
|
| 571 |
+
|
| 572 |
+
def __str__(self):
|
| 573 |
+
rowsstr = ['[%s]' % ', '.join(map(str, row)) for row in self]
|
| 574 |
+
return '[%s]' % ', '.join(rowsstr)
|
| 575 |
+
|
| 576 |
+
def __repr__(self):
|
| 577 |
+
cls = type(self).__name__
|
| 578 |
+
rows = list.__repr__(self)
|
| 579 |
+
return '%s(%s, %s, %s)' % (cls, rows, self.shape, self.domain)
|
| 580 |
+
|
| 581 |
+
def __eq__(self, other):
|
| 582 |
+
if not isinstance(other, DDM):
|
| 583 |
+
return False
|
| 584 |
+
return (super().__eq__(other) and self.domain == other.domain)
|
| 585 |
+
|
| 586 |
+
def __ne__(self, other):
|
| 587 |
+
return not self.__eq__(other)
|
| 588 |
+
|
| 589 |
+
@classmethod
|
| 590 |
+
def zeros(cls, shape, domain):
|
| 591 |
+
z = domain.zero
|
| 592 |
+
m, n = shape
|
| 593 |
+
rowslist = [[z] * n for _ in range(m)]
|
| 594 |
+
return DDM(rowslist, shape, domain)
|
| 595 |
+
|
| 596 |
+
@classmethod
|
| 597 |
+
def ones(cls, shape, domain):
|
| 598 |
+
one = domain.one
|
| 599 |
+
m, n = shape
|
| 600 |
+
rowlist = [[one] * n for _ in range(m)]
|
| 601 |
+
return DDM(rowlist, shape, domain)
|
| 602 |
+
|
| 603 |
+
@classmethod
|
| 604 |
+
def eye(cls, size, domain):
|
| 605 |
+
if isinstance(size, tuple):
|
| 606 |
+
m, n = size
|
| 607 |
+
elif isinstance(size, int):
|
| 608 |
+
m = n = size
|
| 609 |
+
one = domain.one
|
| 610 |
+
ddm = cls.zeros((m, n), domain)
|
| 611 |
+
for i in range(min(m, n)):
|
| 612 |
+
ddm[i][i] = one
|
| 613 |
+
return ddm
|
| 614 |
+
|
| 615 |
+
def copy(self):
|
| 616 |
+
copyrows = [row[:] for row in self]
|
| 617 |
+
return DDM(copyrows, self.shape, self.domain)
|
| 618 |
+
|
| 619 |
+
def transpose(self):
|
| 620 |
+
rows, cols = self.shape
|
| 621 |
+
if rows:
|
| 622 |
+
ddmT = ddm_transpose(self)
|
| 623 |
+
else:
|
| 624 |
+
ddmT = [[]] * cols
|
| 625 |
+
return DDM(ddmT, (cols, rows), self.domain)
|
| 626 |
+
|
| 627 |
+
def __add__(a, b):
|
| 628 |
+
if not isinstance(b, DDM):
|
| 629 |
+
return NotImplemented
|
| 630 |
+
return a.add(b)
|
| 631 |
+
|
| 632 |
+
def __sub__(a, b):
|
| 633 |
+
if not isinstance(b, DDM):
|
| 634 |
+
return NotImplemented
|
| 635 |
+
return a.sub(b)
|
| 636 |
+
|
| 637 |
+
def __neg__(a):
|
| 638 |
+
return a.neg()
|
| 639 |
+
|
| 640 |
+
def __mul__(a, b):
|
| 641 |
+
if b in a.domain:
|
| 642 |
+
return a.mul(b)
|
| 643 |
+
else:
|
| 644 |
+
return NotImplemented
|
| 645 |
+
|
| 646 |
+
def __rmul__(a, b):
|
| 647 |
+
if b in a.domain:
|
| 648 |
+
return a.mul(b)
|
| 649 |
+
else:
|
| 650 |
+
return NotImplemented
|
| 651 |
+
|
| 652 |
+
def __matmul__(a, b):
|
| 653 |
+
if isinstance(b, DDM):
|
| 654 |
+
return a.matmul(b)
|
| 655 |
+
else:
|
| 656 |
+
return NotImplemented
|
| 657 |
+
|
| 658 |
+
@classmethod
|
| 659 |
+
def _check(cls, a, op, b, ashape, bshape):
|
| 660 |
+
if a.domain != b.domain:
|
| 661 |
+
msg = "Domain mismatch: %s %s %s" % (a.domain, op, b.domain)
|
| 662 |
+
raise DMDomainError(msg)
|
| 663 |
+
if ashape != bshape:
|
| 664 |
+
msg = "Shape mismatch: %s %s %s" % (a.shape, op, b.shape)
|
| 665 |
+
raise DMShapeError(msg)
|
| 666 |
+
|
| 667 |
+
def add(a, b):
|
| 668 |
+
"""a + b"""
|
| 669 |
+
a._check(a, '+', b, a.shape, b.shape)
|
| 670 |
+
c = a.copy()
|
| 671 |
+
ddm_iadd(c, b)
|
| 672 |
+
return c
|
| 673 |
+
|
| 674 |
+
def sub(a, b):
|
| 675 |
+
"""a - b"""
|
| 676 |
+
a._check(a, '-', b, a.shape, b.shape)
|
| 677 |
+
c = a.copy()
|
| 678 |
+
ddm_isub(c, b)
|
| 679 |
+
return c
|
| 680 |
+
|
| 681 |
+
def neg(a):
|
| 682 |
+
"""-a"""
|
| 683 |
+
b = a.copy()
|
| 684 |
+
ddm_ineg(b)
|
| 685 |
+
return b
|
| 686 |
+
|
| 687 |
+
def mul(a, b):
|
| 688 |
+
c = a.copy()
|
| 689 |
+
ddm_imul(c, b)
|
| 690 |
+
return c
|
| 691 |
+
|
| 692 |
+
def rmul(a, b):
|
| 693 |
+
c = a.copy()
|
| 694 |
+
ddm_irmul(c, b)
|
| 695 |
+
return c
|
| 696 |
+
|
| 697 |
+
def matmul(a, b):
|
| 698 |
+
"""a @ b (matrix product)"""
|
| 699 |
+
m, o = a.shape
|
| 700 |
+
o2, n = b.shape
|
| 701 |
+
a._check(a, '*', b, o, o2)
|
| 702 |
+
c = a.zeros((m, n), a.domain)
|
| 703 |
+
ddm_imatmul(c, a, b)
|
| 704 |
+
return c
|
| 705 |
+
|
| 706 |
+
def mul_elementwise(a, b):
|
| 707 |
+
assert a.shape == b.shape
|
| 708 |
+
assert a.domain == b.domain
|
| 709 |
+
c = [[aij * bij for aij, bij in zip(ai, bi)] for ai, bi in zip(a, b)]
|
| 710 |
+
return DDM(c, a.shape, a.domain)
|
| 711 |
+
|
| 712 |
+
def hstack(A, *B):
|
| 713 |
+
"""Horizontally stacks :py:class:`~.DDM` matrices.
|
| 714 |
+
|
| 715 |
+
Examples
|
| 716 |
+
========
|
| 717 |
+
|
| 718 |
+
>>> from sympy import ZZ
|
| 719 |
+
>>> from sympy.polys.matrices.sdm import DDM
|
| 720 |
+
|
| 721 |
+
>>> A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 722 |
+
>>> B = DDM([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
|
| 723 |
+
>>> A.hstack(B)
|
| 724 |
+
[[1, 2, 5, 6], [3, 4, 7, 8]]
|
| 725 |
+
|
| 726 |
+
>>> C = DDM([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
|
| 727 |
+
>>> A.hstack(B, C)
|
| 728 |
+
[[1, 2, 5, 6, 9, 10], [3, 4, 7, 8, 11, 12]]
|
| 729 |
+
"""
|
| 730 |
+
Anew = list(A.copy())
|
| 731 |
+
rows, cols = A.shape
|
| 732 |
+
domain = A.domain
|
| 733 |
+
|
| 734 |
+
for Bk in B:
|
| 735 |
+
Bkrows, Bkcols = Bk.shape
|
| 736 |
+
assert Bkrows == rows
|
| 737 |
+
assert Bk.domain == domain
|
| 738 |
+
|
| 739 |
+
cols += Bkcols
|
| 740 |
+
|
| 741 |
+
for i, Bki in enumerate(Bk):
|
| 742 |
+
Anew[i].extend(Bki)
|
| 743 |
+
|
| 744 |
+
return DDM(Anew, (rows, cols), A.domain)
|
| 745 |
+
|
| 746 |
+
def vstack(A, *B):
|
| 747 |
+
"""Vertically stacks :py:class:`~.DDM` matrices.
|
| 748 |
+
|
| 749 |
+
Examples
|
| 750 |
+
========
|
| 751 |
+
|
| 752 |
+
>>> from sympy import ZZ
|
| 753 |
+
>>> from sympy.polys.matrices.sdm import DDM
|
| 754 |
+
|
| 755 |
+
>>> A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 756 |
+
>>> B = DDM([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
|
| 757 |
+
>>> A.vstack(B)
|
| 758 |
+
[[1, 2], [3, 4], [5, 6], [7, 8]]
|
| 759 |
+
|
| 760 |
+
>>> C = DDM([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
|
| 761 |
+
>>> A.vstack(B, C)
|
| 762 |
+
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]
|
| 763 |
+
"""
|
| 764 |
+
Anew = list(A.copy())
|
| 765 |
+
rows, cols = A.shape
|
| 766 |
+
domain = A.domain
|
| 767 |
+
|
| 768 |
+
for Bk in B:
|
| 769 |
+
Bkrows, Bkcols = Bk.shape
|
| 770 |
+
assert Bkcols == cols
|
| 771 |
+
assert Bk.domain == domain
|
| 772 |
+
|
| 773 |
+
rows += Bkrows
|
| 774 |
+
|
| 775 |
+
Anew.extend(Bk.copy())
|
| 776 |
+
|
| 777 |
+
return DDM(Anew, (rows, cols), A.domain)
|
| 778 |
+
|
| 779 |
+
def applyfunc(self, func, domain):
|
| 780 |
+
elements = [list(map(func, row)) for row in self]
|
| 781 |
+
return DDM(elements, self.shape, domain)
|
| 782 |
+
|
| 783 |
+
def nnz(a):
|
| 784 |
+
"""Number of non-zero entries in :py:class:`~.DDM` matrix.
|
| 785 |
+
|
| 786 |
+
See Also
|
| 787 |
+
========
|
| 788 |
+
|
| 789 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.nnz
|
| 790 |
+
"""
|
| 791 |
+
return sum(sum(map(bool, row)) for row in a)
|
| 792 |
+
|
| 793 |
+
def scc(a):
|
| 794 |
+
"""Strongly connected components of a square matrix *a*.
|
| 795 |
+
|
| 796 |
+
Examples
|
| 797 |
+
========
|
| 798 |
+
|
| 799 |
+
>>> from sympy import ZZ
|
| 800 |
+
>>> from sympy.polys.matrices.sdm import DDM
|
| 801 |
+
>>> A = DDM([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(1)]], (2, 2), ZZ)
|
| 802 |
+
>>> A.scc()
|
| 803 |
+
[[0], [1]]
|
| 804 |
+
|
| 805 |
+
See also
|
| 806 |
+
========
|
| 807 |
+
|
| 808 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.scc
|
| 809 |
+
|
| 810 |
+
"""
|
| 811 |
+
return a.to_sdm().scc()
|
| 812 |
+
|
| 813 |
+
@classmethod
|
| 814 |
+
def diag(cls, values, domain):
|
| 815 |
+
"""Returns a square diagonal matrix with *values* on the diagonal.
|
| 816 |
+
|
| 817 |
+
Examples
|
| 818 |
+
========
|
| 819 |
+
|
| 820 |
+
>>> from sympy import ZZ
|
| 821 |
+
>>> from sympy.polys.matrices.sdm import DDM
|
| 822 |
+
>>> DDM.diag([ZZ(1), ZZ(2), ZZ(3)], ZZ)
|
| 823 |
+
[[1, 0, 0], [0, 2, 0], [0, 0, 3]]
|
| 824 |
+
|
| 825 |
+
See also
|
| 826 |
+
========
|
| 827 |
+
|
| 828 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.diag
|
| 829 |
+
"""
|
| 830 |
+
return SDM.diag(values, domain).to_ddm()
|
| 831 |
+
|
| 832 |
+
def rref(a):
|
| 833 |
+
"""Reduced-row echelon form of a and list of pivots.
|
| 834 |
+
|
| 835 |
+
See Also
|
| 836 |
+
========
|
| 837 |
+
|
| 838 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.rref
|
| 839 |
+
Higher level interface to this function.
|
| 840 |
+
sympy.polys.matrices.dense.ddm_irref
|
| 841 |
+
The underlying algorithm.
|
| 842 |
+
"""
|
| 843 |
+
b = a.copy()
|
| 844 |
+
K = a.domain
|
| 845 |
+
partial_pivot = K.is_RealField or K.is_ComplexField
|
| 846 |
+
pivots = ddm_irref(b, _partial_pivot=partial_pivot)
|
| 847 |
+
return b, pivots
|
| 848 |
+
|
| 849 |
+
def rref_den(a):
|
| 850 |
+
"""Reduced-row echelon form of a with denominator and list of pivots
|
| 851 |
+
|
| 852 |
+
See Also
|
| 853 |
+
========
|
| 854 |
+
|
| 855 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
|
| 856 |
+
Higher level interface to this function.
|
| 857 |
+
sympy.polys.matrices.dense.ddm_irref_den
|
| 858 |
+
The underlying algorithm.
|
| 859 |
+
"""
|
| 860 |
+
b = a.copy()
|
| 861 |
+
K = a.domain
|
| 862 |
+
denom, pivots = ddm_irref_den(b, K)
|
| 863 |
+
return b, denom, pivots
|
| 864 |
+
|
| 865 |
+
def nullspace(a):
|
| 866 |
+
"""Returns a basis for the nullspace of a.
|
| 867 |
+
|
| 868 |
+
The domain of the matrix must be a field.
|
| 869 |
+
|
| 870 |
+
See Also
|
| 871 |
+
========
|
| 872 |
+
|
| 873 |
+
rref
|
| 874 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace
|
| 875 |
+
"""
|
| 876 |
+
rref, pivots = a.rref()
|
| 877 |
+
return rref.nullspace_from_rref(pivots)
|
| 878 |
+
|
| 879 |
+
def nullspace_from_rref(a, pivots=None):
|
| 880 |
+
"""Compute the nullspace of a matrix from its rref.
|
| 881 |
+
|
| 882 |
+
The domain of the matrix can be any domain.
|
| 883 |
+
|
| 884 |
+
Returns a tuple (basis, nonpivots).
|
| 885 |
+
|
| 886 |
+
See Also
|
| 887 |
+
========
|
| 888 |
+
|
| 889 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace
|
| 890 |
+
The higher level interface to this function.
|
| 891 |
+
"""
|
| 892 |
+
m, n = a.shape
|
| 893 |
+
K = a.domain
|
| 894 |
+
|
| 895 |
+
if pivots is None:
|
| 896 |
+
pivots = []
|
| 897 |
+
last_pivot = -1
|
| 898 |
+
for i in range(m):
|
| 899 |
+
ai = a[i]
|
| 900 |
+
for j in range(last_pivot+1, n):
|
| 901 |
+
if ai[j]:
|
| 902 |
+
last_pivot = j
|
| 903 |
+
pivots.append(j)
|
| 904 |
+
break
|
| 905 |
+
|
| 906 |
+
if not pivots:
|
| 907 |
+
return (a.eye(n, K), list(range(n)))
|
| 908 |
+
|
| 909 |
+
# After rref the pivots are all one but after rref_den they may not be.
|
| 910 |
+
pivot_val = a[0][pivots[0]]
|
| 911 |
+
|
| 912 |
+
basis = []
|
| 913 |
+
nonpivots = []
|
| 914 |
+
for i in range(n):
|
| 915 |
+
if i in pivots:
|
| 916 |
+
continue
|
| 917 |
+
nonpivots.append(i)
|
| 918 |
+
vec = [pivot_val if i == j else K.zero for j in range(n)]
|
| 919 |
+
for ii, jj in enumerate(pivots):
|
| 920 |
+
vec[jj] -= a[ii][i]
|
| 921 |
+
basis.append(vec)
|
| 922 |
+
|
| 923 |
+
basis_ddm = DDM(basis, (len(basis), n), K)
|
| 924 |
+
|
| 925 |
+
return (basis_ddm, nonpivots)
|
| 926 |
+
|
| 927 |
+
def particular(a):
|
| 928 |
+
return a.to_sdm().particular().to_ddm()
|
| 929 |
+
|
| 930 |
+
def det(a):
|
| 931 |
+
"""Determinant of a"""
|
| 932 |
+
m, n = a.shape
|
| 933 |
+
if m != n:
|
| 934 |
+
raise DMNonSquareMatrixError("Determinant of non-square matrix")
|
| 935 |
+
b = a.copy()
|
| 936 |
+
K = b.domain
|
| 937 |
+
deta = ddm_idet(b, K)
|
| 938 |
+
return deta
|
| 939 |
+
|
| 940 |
+
def inv(a):
|
| 941 |
+
"""Inverse of a"""
|
| 942 |
+
m, n = a.shape
|
| 943 |
+
if m != n:
|
| 944 |
+
raise DMNonSquareMatrixError("Determinant of non-square matrix")
|
| 945 |
+
ainv = a.copy()
|
| 946 |
+
K = a.domain
|
| 947 |
+
ddm_iinv(ainv, a, K)
|
| 948 |
+
return ainv
|
| 949 |
+
|
| 950 |
+
def lu(a):
|
| 951 |
+
"""L, U decomposition of a"""
|
| 952 |
+
m, n = a.shape
|
| 953 |
+
K = a.domain
|
| 954 |
+
|
| 955 |
+
U = a.copy()
|
| 956 |
+
L = a.eye(m, K)
|
| 957 |
+
swaps = ddm_ilu_split(L, U, K)
|
| 958 |
+
|
| 959 |
+
return L, U, swaps
|
| 960 |
+
|
| 961 |
+
def _fflu(self):
|
| 962 |
+
"""
|
| 963 |
+
Private method for Phase 1 of fraction-free LU decomposition.
|
| 964 |
+
Performs row operations and elimination to compute U and permutation indices.
|
| 965 |
+
|
| 966 |
+
Returns:
|
| 967 |
+
LU : decomposition as a single matrix.
|
| 968 |
+
perm (list): Permutation indices for row swaps.
|
| 969 |
+
"""
|
| 970 |
+
rows, cols = self.shape
|
| 971 |
+
K = self.domain
|
| 972 |
+
|
| 973 |
+
LU = self.copy()
|
| 974 |
+
perm = list(range(rows))
|
| 975 |
+
rank = 0
|
| 976 |
+
|
| 977 |
+
for j in range(min(rows, cols)):
|
| 978 |
+
# Skip columns where all entries are zero
|
| 979 |
+
if all(LU[i][j] == K.zero for i in range(rows)):
|
| 980 |
+
continue
|
| 981 |
+
|
| 982 |
+
# Find the first non-zero pivot in the current column
|
| 983 |
+
pivot_row = -1
|
| 984 |
+
for i in range(rank, rows):
|
| 985 |
+
if LU[i][j] != K.zero:
|
| 986 |
+
pivot_row = i
|
| 987 |
+
break
|
| 988 |
+
|
| 989 |
+
# If no pivot is found, skip column
|
| 990 |
+
if pivot_row == -1:
|
| 991 |
+
continue
|
| 992 |
+
|
| 993 |
+
# Swap rows to bring the pivot to the current rank
|
| 994 |
+
if pivot_row != rank:
|
| 995 |
+
LU[rank], LU[pivot_row] = LU[pivot_row], LU[rank]
|
| 996 |
+
perm[rank], perm[pivot_row] = perm[pivot_row], perm[rank]
|
| 997 |
+
|
| 998 |
+
# Found pivot - (Gauss-Bareiss elimination)
|
| 999 |
+
pivot = LU[rank][j]
|
| 1000 |
+
for i in range(rank + 1, rows):
|
| 1001 |
+
multiplier = LU[i][j]
|
| 1002 |
+
# Denominator is previous pivot or 1
|
| 1003 |
+
denominator = LU[rank - 1][rank - 1] if rank > 0 else K.one
|
| 1004 |
+
for k in range(j + 1, cols):
|
| 1005 |
+
LU[i][k] = K.exquo(pivot * LU[i][k] - LU[rank][k] * multiplier, denominator)
|
| 1006 |
+
# Keep the multiplier for L matrix
|
| 1007 |
+
LU[i][j] = multiplier
|
| 1008 |
+
rank += 1
|
| 1009 |
+
|
| 1010 |
+
return LU, perm
|
| 1011 |
+
|
| 1012 |
+
def fflu(self):
|
| 1013 |
+
"""
|
| 1014 |
+
Fraction-free LU decomposition of DDM.
|
| 1015 |
+
|
| 1016 |
+
See Also
|
| 1017 |
+
========
|
| 1018 |
+
|
| 1019 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.fflu
|
| 1020 |
+
The higher-level interface to this function.
|
| 1021 |
+
"""
|
| 1022 |
+
rows, cols = self.shape
|
| 1023 |
+
K = self.domain
|
| 1024 |
+
|
| 1025 |
+
# Phase 1: Perform row operations and get permutation
|
| 1026 |
+
U, perm = self._fflu()
|
| 1027 |
+
|
| 1028 |
+
# Phase 2: Construct P, L, D matrices
|
| 1029 |
+
# Create P from permutation
|
| 1030 |
+
P = self.zeros((rows, rows), K)
|
| 1031 |
+
for i, pi in enumerate(perm):
|
| 1032 |
+
P[i][pi] = K.one
|
| 1033 |
+
|
| 1034 |
+
# Create L matrix
|
| 1035 |
+
L = self.zeros((rows, rows), K)
|
| 1036 |
+
i = j = 0
|
| 1037 |
+
while i < rows and j < cols:
|
| 1038 |
+
if U[i][j] != K.zero:
|
| 1039 |
+
# Found non-zero pivot
|
| 1040 |
+
# Diagonal entry is the pivot
|
| 1041 |
+
L[i][i] = U[i][j]
|
| 1042 |
+
for l in range(i + 1, rows):
|
| 1043 |
+
# Off-diagonal entries are the multipliers
|
| 1044 |
+
L[l][i] = U[l][j]
|
| 1045 |
+
# zero out the entries in U
|
| 1046 |
+
U[l][j] = K.zero
|
| 1047 |
+
i += 1
|
| 1048 |
+
j += 1
|
| 1049 |
+
|
| 1050 |
+
# Fill remaining diagonal of L with ones
|
| 1051 |
+
for i in range(i, rows):
|
| 1052 |
+
L[i][i] = K.one
|
| 1053 |
+
|
| 1054 |
+
# Create D matrix - using FLINT's approach with accumulator
|
| 1055 |
+
D = self.zeros((rows, rows), K)
|
| 1056 |
+
if rows >= 1:
|
| 1057 |
+
D[0][0] = L[0][0]
|
| 1058 |
+
di = K.one
|
| 1059 |
+
for i in range(1, rows):
|
| 1060 |
+
# Accumulate product of pivots
|
| 1061 |
+
di = L[i - 1][i - 1] * L[i][i]
|
| 1062 |
+
D[i][i] = di
|
| 1063 |
+
|
| 1064 |
+
return P, L, D, U
|
| 1065 |
+
|
| 1066 |
+
def qr(self):
|
| 1067 |
+
"""
|
| 1068 |
+
QR decomposition for DDM.
|
| 1069 |
+
|
| 1070 |
+
Returns:
|
| 1071 |
+
- Q: Orthogonal matrix as a DDM.
|
| 1072 |
+
- R: Upper triangular matrix as a DDM.
|
| 1073 |
+
|
| 1074 |
+
See Also
|
| 1075 |
+
========
|
| 1076 |
+
|
| 1077 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.qr
|
| 1078 |
+
The higher-level interface to this function.
|
| 1079 |
+
"""
|
| 1080 |
+
rows, cols = self.shape
|
| 1081 |
+
K = self.domain
|
| 1082 |
+
Q = self.copy()
|
| 1083 |
+
R = self.zeros((min(rows, cols), cols), K)
|
| 1084 |
+
|
| 1085 |
+
# Check that the domain is a field
|
| 1086 |
+
if not K.is_Field:
|
| 1087 |
+
raise DMDomainError("QR decomposition requires a field (e.g. QQ).")
|
| 1088 |
+
|
| 1089 |
+
dot_cols = lambda i, j: K.sum(Q[k][i] * Q[k][j] for k in range(rows))
|
| 1090 |
+
|
| 1091 |
+
for j in range(cols):
|
| 1092 |
+
for i in range(min(j, rows)):
|
| 1093 |
+
dot_ii = dot_cols(i, i)
|
| 1094 |
+
if dot_ii != K.zero:
|
| 1095 |
+
R[i][j] = dot_cols(i, j) / dot_ii
|
| 1096 |
+
for k in range(rows):
|
| 1097 |
+
Q[k][j] -= R[i][j] * Q[k][i]
|
| 1098 |
+
|
| 1099 |
+
if j < rows:
|
| 1100 |
+
dot_jj = dot_cols(j, j)
|
| 1101 |
+
if dot_jj != K.zero:
|
| 1102 |
+
R[j][j] = K.one
|
| 1103 |
+
|
| 1104 |
+
Q = Q.extract(range(rows), range(min(rows, cols)))
|
| 1105 |
+
|
| 1106 |
+
return Q, R
|
| 1107 |
+
|
| 1108 |
+
def lu_solve(a, b):
|
| 1109 |
+
"""x where a*x = b"""
|
| 1110 |
+
m, n = a.shape
|
| 1111 |
+
m2, o = b.shape
|
| 1112 |
+
a._check(a, 'lu_solve', b, m, m2)
|
| 1113 |
+
if not a.domain.is_Field:
|
| 1114 |
+
raise DMDomainError("lu_solve requires a field")
|
| 1115 |
+
|
| 1116 |
+
L, U, swaps = a.lu()
|
| 1117 |
+
x = a.zeros((n, o), a.domain)
|
| 1118 |
+
ddm_ilu_solve(x, L, U, swaps, b)
|
| 1119 |
+
return x
|
| 1120 |
+
|
| 1121 |
+
def charpoly(a):
|
| 1122 |
+
"""Coefficients of characteristic polynomial of a"""
|
| 1123 |
+
K = a.domain
|
| 1124 |
+
m, n = a.shape
|
| 1125 |
+
if m != n:
|
| 1126 |
+
raise DMNonSquareMatrixError("Charpoly of non-square matrix")
|
| 1127 |
+
vec = ddm_berk(a, K)
|
| 1128 |
+
coeffs = [vec[i][0] for i in range(n+1)]
|
| 1129 |
+
return coeffs
|
| 1130 |
+
|
| 1131 |
+
def is_zero_matrix(self):
|
| 1132 |
+
"""
|
| 1133 |
+
Says whether this matrix has all zero entries.
|
| 1134 |
+
"""
|
| 1135 |
+
zero = self.domain.zero
|
| 1136 |
+
return all(Mij == zero for Mij in self.flatiter())
|
| 1137 |
+
|
| 1138 |
+
def is_upper(self):
|
| 1139 |
+
"""
|
| 1140 |
+
Says whether this matrix is upper-triangular. True can be returned
|
| 1141 |
+
even if the matrix is not square.
|
| 1142 |
+
"""
|
| 1143 |
+
zero = self.domain.zero
|
| 1144 |
+
return all(Mij == zero for i, Mi in enumerate(self) for Mij in Mi[:i])
|
| 1145 |
+
|
| 1146 |
+
def is_lower(self):
|
| 1147 |
+
"""
|
| 1148 |
+
Says whether this matrix is lower-triangular. True can be returned
|
| 1149 |
+
even if the matrix is not square.
|
| 1150 |
+
"""
|
| 1151 |
+
zero = self.domain.zero
|
| 1152 |
+
return all(Mij == zero for i, Mi in enumerate(self) for Mij in Mi[i+1:])
|
| 1153 |
+
|
| 1154 |
+
def is_diagonal(self):
|
| 1155 |
+
"""
|
| 1156 |
+
Says whether this matrix is diagonal. True can be returned even if
|
| 1157 |
+
the matrix is not square.
|
| 1158 |
+
"""
|
| 1159 |
+
return self.is_upper() and self.is_lower()
|
| 1160 |
+
|
| 1161 |
+
def diagonal(self):
|
| 1162 |
+
"""
|
| 1163 |
+
Returns a list of the elements from the diagonal of the matrix.
|
| 1164 |
+
"""
|
| 1165 |
+
m, n = self.shape
|
| 1166 |
+
return [self[i][i] for i in range(min(m, n))]
|
| 1167 |
+
|
| 1168 |
+
def lll(A, delta=QQ(3, 4)):
|
| 1169 |
+
return ddm_lll(A, delta=delta)
|
| 1170 |
+
|
| 1171 |
+
def lll_transform(A, delta=QQ(3, 4)):
|
| 1172 |
+
return ddm_lll_transform(A, delta=delta)
|
| 1173 |
+
|
| 1174 |
+
|
| 1175 |
+
from .sdm import SDM
|
| 1176 |
+
from .dfm import DFM
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/dense.py
ADDED
|
@@ -0,0 +1,824 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
|
| 3 |
+
Module for the ddm_* routines for operating on a matrix in list of lists
|
| 4 |
+
matrix representation.
|
| 5 |
+
|
| 6 |
+
These routines are used internally by the DDM class which also provides a
|
| 7 |
+
friendlier interface for them. The idea here is to implement core matrix
|
| 8 |
+
routines in a way that can be applied to any simple list representation
|
| 9 |
+
without the need to use any particular matrix class. For example we can
|
| 10 |
+
compute the RREF of a matrix like:
|
| 11 |
+
|
| 12 |
+
>>> from sympy.polys.matrices.dense import ddm_irref
|
| 13 |
+
>>> M = [[1, 2, 3], [4, 5, 6]]
|
| 14 |
+
>>> pivots = ddm_irref(M)
|
| 15 |
+
>>> M
|
| 16 |
+
[[1.0, 0.0, -1.0], [0, 1.0, 2.0]]
|
| 17 |
+
|
| 18 |
+
These are lower-level routines that work mostly in place.The routines at this
|
| 19 |
+
level should not need to know what the domain of the elements is but should
|
| 20 |
+
ideally document what operations they will use and what functions they need to
|
| 21 |
+
be provided with.
|
| 22 |
+
|
| 23 |
+
The next-level up is the DDM class which uses these routines but wraps them up
|
| 24 |
+
with an interface that handles copying etc and keeps track of the Domain of
|
| 25 |
+
the elements of the matrix:
|
| 26 |
+
|
| 27 |
+
>>> from sympy.polys.domains import QQ
|
| 28 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 29 |
+
>>> M = DDM([[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]], (2, 3), QQ)
|
| 30 |
+
>>> M
|
| 31 |
+
[[1, 2, 3], [4, 5, 6]]
|
| 32 |
+
>>> Mrref, pivots = M.rref()
|
| 33 |
+
>>> Mrref
|
| 34 |
+
[[1, 0, -1], [0, 1, 2]]
|
| 35 |
+
|
| 36 |
+
"""
|
| 37 |
+
from __future__ import annotations
|
| 38 |
+
from operator import mul
|
| 39 |
+
from .exceptions import (
|
| 40 |
+
DMShapeError,
|
| 41 |
+
DMDomainError,
|
| 42 |
+
DMNonInvertibleMatrixError,
|
| 43 |
+
DMNonSquareMatrixError,
|
| 44 |
+
)
|
| 45 |
+
from typing import Sequence, TypeVar
|
| 46 |
+
from sympy.polys.matrices._typing import RingElement
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
#: Type variable for the elements of the matrix
|
| 50 |
+
T = TypeVar('T')
|
| 51 |
+
|
| 52 |
+
#: Type variable for the elements of the matrix that are in a ring
|
| 53 |
+
R = TypeVar('R', bound=RingElement)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def ddm_transpose(matrix: Sequence[Sequence[T]]) -> list[list[T]]:
|
| 57 |
+
"""matrix transpose"""
|
| 58 |
+
return list(map(list, zip(*matrix)))
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def ddm_iadd(a: list[list[R]], b: Sequence[Sequence[R]]) -> None:
|
| 62 |
+
"""a += b"""
|
| 63 |
+
for ai, bi in zip(a, b):
|
| 64 |
+
for j, bij in enumerate(bi):
|
| 65 |
+
ai[j] += bij
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def ddm_isub(a: list[list[R]], b: Sequence[Sequence[R]]) -> None:
|
| 69 |
+
"""a -= b"""
|
| 70 |
+
for ai, bi in zip(a, b):
|
| 71 |
+
for j, bij in enumerate(bi):
|
| 72 |
+
ai[j] -= bij
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def ddm_ineg(a: list[list[R]]) -> None:
|
| 76 |
+
"""a <-- -a"""
|
| 77 |
+
for ai in a:
|
| 78 |
+
for j, aij in enumerate(ai):
|
| 79 |
+
ai[j] = -aij
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def ddm_imul(a: list[list[R]], b: R) -> None:
|
| 83 |
+
"""a <-- a*b"""
|
| 84 |
+
for ai in a:
|
| 85 |
+
for j, aij in enumerate(ai):
|
| 86 |
+
ai[j] = aij * b
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def ddm_irmul(a: list[list[R]], b: R) -> None:
|
| 90 |
+
"""a <-- b*a"""
|
| 91 |
+
for ai in a:
|
| 92 |
+
for j, aij in enumerate(ai):
|
| 93 |
+
ai[j] = b * aij
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def ddm_imatmul(
|
| 97 |
+
a: list[list[R]], b: Sequence[Sequence[R]], c: Sequence[Sequence[R]]
|
| 98 |
+
) -> None:
|
| 99 |
+
"""a += b @ c"""
|
| 100 |
+
cT = list(zip(*c))
|
| 101 |
+
|
| 102 |
+
for bi, ai in zip(b, a):
|
| 103 |
+
for j, cTj in enumerate(cT):
|
| 104 |
+
ai[j] = sum(map(mul, bi, cTj), ai[j])
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def ddm_irref(a, _partial_pivot=False):
|
| 108 |
+
"""In-place reduced row echelon form of a matrix.
|
| 109 |
+
|
| 110 |
+
Compute the reduced row echelon form of $a$. Modifies $a$ in place and
|
| 111 |
+
returns a list of the pivot columns.
|
| 112 |
+
|
| 113 |
+
Uses naive Gauss-Jordan elimination in the ground domain which must be a
|
| 114 |
+
field.
|
| 115 |
+
|
| 116 |
+
This routine is only really suitable for use with simple field domains like
|
| 117 |
+
:ref:`GF(p)`, :ref:`QQ` and :ref:`QQ(a)` although even for :ref:`QQ` with
|
| 118 |
+
larger matrices it is possibly more efficient to use fraction free
|
| 119 |
+
approaches.
|
| 120 |
+
|
| 121 |
+
This method is not suitable for use with rational function fields
|
| 122 |
+
(:ref:`K(x)`) because the elements will blowup leading to costly gcd
|
| 123 |
+
operations. In this case clearing denominators and using fraction free
|
| 124 |
+
approaches is likely to be more efficient.
|
| 125 |
+
|
| 126 |
+
For inexact numeric domains like :ref:`RR` and :ref:`CC` pass
|
| 127 |
+
``_partial_pivot=True`` to use partial pivoting to control rounding errors.
|
| 128 |
+
|
| 129 |
+
Examples
|
| 130 |
+
========
|
| 131 |
+
|
| 132 |
+
>>> from sympy.polys.matrices.dense import ddm_irref
|
| 133 |
+
>>> from sympy import QQ
|
| 134 |
+
>>> M = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]]
|
| 135 |
+
>>> pivots = ddm_irref(M)
|
| 136 |
+
>>> M
|
| 137 |
+
[[1, 0, -1], [0, 1, 2]]
|
| 138 |
+
>>> pivots
|
| 139 |
+
[0, 1]
|
| 140 |
+
|
| 141 |
+
See Also
|
| 142 |
+
========
|
| 143 |
+
|
| 144 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.rref
|
| 145 |
+
Higher level interface to this routine.
|
| 146 |
+
ddm_irref_den
|
| 147 |
+
The fraction free version of this routine.
|
| 148 |
+
sdm_irref
|
| 149 |
+
A sparse version of this routine.
|
| 150 |
+
|
| 151 |
+
References
|
| 152 |
+
==========
|
| 153 |
+
|
| 154 |
+
.. [1] https://en.wikipedia.org/wiki/Row_echelon_form#Reduced_row_echelon_form
|
| 155 |
+
"""
|
| 156 |
+
# We compute aij**-1 below and then use multiplication instead of division
|
| 157 |
+
# in the innermost loop. The domain here is a field so either operation is
|
| 158 |
+
# defined. There are significant performance differences for some domains
|
| 159 |
+
# though. In the case of e.g. QQ or QQ(x) inversion is free but
|
| 160 |
+
# multiplication and division have the same cost so it makes no difference.
|
| 161 |
+
# In cases like GF(p), QQ<sqrt(2)>, RR or CC though multiplication is
|
| 162 |
+
# faster than division so reusing a precomputed inverse for many
|
| 163 |
+
# multiplications can be a lot faster. The biggest win is QQ<a> when
|
| 164 |
+
# deg(minpoly(a)) is large.
|
| 165 |
+
#
|
| 166 |
+
# With domains like QQ(x) this can perform badly for other reasons.
|
| 167 |
+
# Typically the initial matrix has simple denominators and the
|
| 168 |
+
# fraction-free approach with exquo (ddm_irref_den) will preserve that
|
| 169 |
+
# property throughout. The method here causes denominator blowup leading to
|
| 170 |
+
# expensive gcd reductions in the intermediate expressions. With many
|
| 171 |
+
# generators like QQ(x,y,z,...) this is extremely bad.
|
| 172 |
+
#
|
| 173 |
+
# TODO: Use a nontrivial pivoting strategy to control intermediate
|
| 174 |
+
# expression growth. Rearranging rows and/or columns could defer the most
|
| 175 |
+
# complicated elements until the end. If the first pivot is a
|
| 176 |
+
# complicated/large element then the first round of reduction will
|
| 177 |
+
# immediately introduce expression blowup across the whole matrix.
|
| 178 |
+
|
| 179 |
+
# a is (m x n)
|
| 180 |
+
m = len(a)
|
| 181 |
+
if not m:
|
| 182 |
+
return []
|
| 183 |
+
n = len(a[0])
|
| 184 |
+
|
| 185 |
+
i = 0
|
| 186 |
+
pivots = []
|
| 187 |
+
|
| 188 |
+
for j in range(n):
|
| 189 |
+
# Proper pivoting should be used for all domains for performance
|
| 190 |
+
# reasons but it is only strictly needed for RR and CC (and possibly
|
| 191 |
+
# other domains like RR(x)). This path is used by DDM.rref() if the
|
| 192 |
+
# domain is RR or CC. It uses partial (row) pivoting based on the
|
| 193 |
+
# absolute value of the pivot candidates.
|
| 194 |
+
if _partial_pivot:
|
| 195 |
+
ip = max(range(i, m), key=lambda ip: abs(a[ip][j]))
|
| 196 |
+
a[i], a[ip] = a[ip], a[i]
|
| 197 |
+
|
| 198 |
+
# pivot
|
| 199 |
+
aij = a[i][j]
|
| 200 |
+
|
| 201 |
+
# zero-pivot
|
| 202 |
+
if not aij:
|
| 203 |
+
for ip in range(i+1, m):
|
| 204 |
+
aij = a[ip][j]
|
| 205 |
+
# row-swap
|
| 206 |
+
if aij:
|
| 207 |
+
a[i], a[ip] = a[ip], a[i]
|
| 208 |
+
break
|
| 209 |
+
else:
|
| 210 |
+
# next column
|
| 211 |
+
continue
|
| 212 |
+
|
| 213 |
+
# normalise row
|
| 214 |
+
ai = a[i]
|
| 215 |
+
aijinv = aij**-1
|
| 216 |
+
for l in range(j, n):
|
| 217 |
+
ai[l] *= aijinv # ai[j] = one
|
| 218 |
+
|
| 219 |
+
# eliminate above and below to the right
|
| 220 |
+
for k, ak in enumerate(a):
|
| 221 |
+
if k == i or not ak[j]:
|
| 222 |
+
continue
|
| 223 |
+
akj = ak[j]
|
| 224 |
+
ak[j] -= akj # ak[j] = zero
|
| 225 |
+
for l in range(j+1, n):
|
| 226 |
+
ak[l] -= akj * ai[l]
|
| 227 |
+
|
| 228 |
+
# next row
|
| 229 |
+
pivots.append(j)
|
| 230 |
+
i += 1
|
| 231 |
+
|
| 232 |
+
# no more rows?
|
| 233 |
+
if i >= m:
|
| 234 |
+
break
|
| 235 |
+
|
| 236 |
+
return pivots
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
def ddm_irref_den(a, K):
|
| 240 |
+
"""a <-- rref(a); return (den, pivots)
|
| 241 |
+
|
| 242 |
+
Compute the fraction-free reduced row echelon form (RREF) of $a$. Modifies
|
| 243 |
+
$a$ in place and returns a tuple containing the denominator of the RREF and
|
| 244 |
+
a list of the pivot columns.
|
| 245 |
+
|
| 246 |
+
Explanation
|
| 247 |
+
===========
|
| 248 |
+
|
| 249 |
+
The algorithm used is the fraction-free version of Gauss-Jordan elimination
|
| 250 |
+
described as FFGJ in [1]_. Here it is modified to handle zero or missing
|
| 251 |
+
pivots and to avoid redundant arithmetic.
|
| 252 |
+
|
| 253 |
+
The domain $K$ must support exact division (``K.exquo``) but does not need
|
| 254 |
+
to be a field. This method is suitable for most exact rings and fields like
|
| 255 |
+
:ref:`ZZ`, :ref:`QQ` and :ref:`QQ(a)`. In the case of :ref:`QQ` or
|
| 256 |
+
:ref:`K(x)` it might be more efficient to clear denominators and use
|
| 257 |
+
:ref:`ZZ` or :ref:`K[x]` instead.
|
| 258 |
+
|
| 259 |
+
For inexact domains like :ref:`RR` and :ref:`CC` use ``ddm_irref`` instead.
|
| 260 |
+
|
| 261 |
+
Examples
|
| 262 |
+
========
|
| 263 |
+
|
| 264 |
+
>>> from sympy.polys.matrices.dense import ddm_irref_den
|
| 265 |
+
>>> from sympy import ZZ, Matrix
|
| 266 |
+
>>> M = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)]]
|
| 267 |
+
>>> den, pivots = ddm_irref_den(M, ZZ)
|
| 268 |
+
>>> M
|
| 269 |
+
[[-3, 0, 3], [0, -3, -6]]
|
| 270 |
+
>>> den
|
| 271 |
+
-3
|
| 272 |
+
>>> pivots
|
| 273 |
+
[0, 1]
|
| 274 |
+
>>> Matrix(M).rref()[0]
|
| 275 |
+
Matrix([
|
| 276 |
+
[1, 0, -1],
|
| 277 |
+
[0, 1, 2]])
|
| 278 |
+
|
| 279 |
+
See Also
|
| 280 |
+
========
|
| 281 |
+
|
| 282 |
+
ddm_irref
|
| 283 |
+
A version of this routine that uses field division.
|
| 284 |
+
sdm_irref
|
| 285 |
+
A sparse version of :func:`ddm_irref`.
|
| 286 |
+
sdm_rref_den
|
| 287 |
+
A sparse version of :func:`ddm_irref_den`.
|
| 288 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
|
| 289 |
+
Higher level interface.
|
| 290 |
+
|
| 291 |
+
References
|
| 292 |
+
==========
|
| 293 |
+
|
| 294 |
+
.. [1] Fraction-free algorithms for linear and polynomial equations.
|
| 295 |
+
George C. Nakos , Peter R. Turner , Robert M. Williams.
|
| 296 |
+
https://dl.acm.org/doi/10.1145/271130.271133
|
| 297 |
+
"""
|
| 298 |
+
#
|
| 299 |
+
# A simpler presentation of this algorithm is given in [1]:
|
| 300 |
+
#
|
| 301 |
+
# Given an n x n matrix A and n x 1 matrix b:
|
| 302 |
+
#
|
| 303 |
+
# for i in range(n):
|
| 304 |
+
# if i != 0:
|
| 305 |
+
# d = a[i-1][i-1]
|
| 306 |
+
# for j in range(n):
|
| 307 |
+
# if j == i:
|
| 308 |
+
# continue
|
| 309 |
+
# b[j] = a[i][i]*b[j] - a[j][i]*b[i]
|
| 310 |
+
# for k in range(n):
|
| 311 |
+
# a[j][k] = a[i][i]*a[j][k] - a[j][i]*a[i][k]
|
| 312 |
+
# if i != 0:
|
| 313 |
+
# a[j][k] /= d
|
| 314 |
+
#
|
| 315 |
+
# Our version here is a bit more complicated because:
|
| 316 |
+
#
|
| 317 |
+
# 1. We use row-swaps to avoid zero pivots.
|
| 318 |
+
# 2. We allow for some columns to be missing pivots.
|
| 319 |
+
# 3. We avoid a lot of redundant arithmetic.
|
| 320 |
+
#
|
| 321 |
+
# TODO: Use a non-trivial pivoting strategy. Even just row swapping makes a
|
| 322 |
+
# big difference to performance if e.g. the upper-left entry of the matrix
|
| 323 |
+
# is a huge polynomial.
|
| 324 |
+
|
| 325 |
+
# a is (m x n)
|
| 326 |
+
m = len(a)
|
| 327 |
+
if not m:
|
| 328 |
+
return K.one, []
|
| 329 |
+
n = len(a[0])
|
| 330 |
+
|
| 331 |
+
d = None
|
| 332 |
+
pivots = []
|
| 333 |
+
no_pivots = []
|
| 334 |
+
|
| 335 |
+
# i, j will be the row and column indices of the current pivot
|
| 336 |
+
i = 0
|
| 337 |
+
for j in range(n):
|
| 338 |
+
# next pivot?
|
| 339 |
+
aij = a[i][j]
|
| 340 |
+
|
| 341 |
+
# swap rows if zero
|
| 342 |
+
if not aij:
|
| 343 |
+
for ip in range(i+1, m):
|
| 344 |
+
aij = a[ip][j]
|
| 345 |
+
# row-swap
|
| 346 |
+
if aij:
|
| 347 |
+
a[i], a[ip] = a[ip], a[i]
|
| 348 |
+
break
|
| 349 |
+
else:
|
| 350 |
+
# go to next column
|
| 351 |
+
no_pivots.append(j)
|
| 352 |
+
continue
|
| 353 |
+
|
| 354 |
+
# Now aij is the pivot and i,j are the row and column. We need to clear
|
| 355 |
+
# the column above and below but we also need to keep track of the
|
| 356 |
+
# denominator of the RREF which means also multiplying everything above
|
| 357 |
+
# and to the left by the current pivot aij and dividing by d (which we
|
| 358 |
+
# multiplied everything by in the previous iteration so this is an
|
| 359 |
+
# exact division).
|
| 360 |
+
#
|
| 361 |
+
# First handle the upper left corner which is usually already diagonal
|
| 362 |
+
# with all diagonal entries equal to the current denominator but there
|
| 363 |
+
# can be other non-zero entries in any column that has no pivot.
|
| 364 |
+
|
| 365 |
+
# Update previous pivots in the matrix
|
| 366 |
+
if pivots:
|
| 367 |
+
pivot_val = aij * a[0][pivots[0]]
|
| 368 |
+
# Divide out the common factor
|
| 369 |
+
if d is not None:
|
| 370 |
+
pivot_val = K.exquo(pivot_val, d)
|
| 371 |
+
|
| 372 |
+
# Could defer this until the end but it is pretty cheap and
|
| 373 |
+
# helps when debugging.
|
| 374 |
+
for ip, jp in enumerate(pivots):
|
| 375 |
+
a[ip][jp] = pivot_val
|
| 376 |
+
|
| 377 |
+
# Update columns without pivots
|
| 378 |
+
for jnp in no_pivots:
|
| 379 |
+
for ip in range(i):
|
| 380 |
+
aijp = a[ip][jnp]
|
| 381 |
+
if aijp:
|
| 382 |
+
aijp *= aij
|
| 383 |
+
if d is not None:
|
| 384 |
+
aijp = K.exquo(aijp, d)
|
| 385 |
+
a[ip][jnp] = aijp
|
| 386 |
+
|
| 387 |
+
# Eliminate above, below and to the right as in ordinary division free
|
| 388 |
+
# Gauss-Jordan elmination except also dividing out d from every entry.
|
| 389 |
+
|
| 390 |
+
for jp, aj in enumerate(a):
|
| 391 |
+
|
| 392 |
+
# Skip the current row
|
| 393 |
+
if jp == i:
|
| 394 |
+
continue
|
| 395 |
+
|
| 396 |
+
# Eliminate to the right in all rows
|
| 397 |
+
for kp in range(j+1, n):
|
| 398 |
+
ajk = aij * aj[kp] - aj[j] * a[i][kp]
|
| 399 |
+
if d is not None:
|
| 400 |
+
ajk = K.exquo(ajk, d)
|
| 401 |
+
aj[kp] = ajk
|
| 402 |
+
|
| 403 |
+
# Set to zero above and below the pivot
|
| 404 |
+
aj[j] = K.zero
|
| 405 |
+
|
| 406 |
+
# next row
|
| 407 |
+
pivots.append(j)
|
| 408 |
+
i += 1
|
| 409 |
+
|
| 410 |
+
# no more rows left?
|
| 411 |
+
if i >= m:
|
| 412 |
+
break
|
| 413 |
+
|
| 414 |
+
if not K.is_one(aij):
|
| 415 |
+
d = aij
|
| 416 |
+
else:
|
| 417 |
+
d = None
|
| 418 |
+
|
| 419 |
+
if not pivots:
|
| 420 |
+
denom = K.one
|
| 421 |
+
else:
|
| 422 |
+
denom = a[0][pivots[0]]
|
| 423 |
+
|
| 424 |
+
return denom, pivots
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
def ddm_idet(a, K):
|
| 428 |
+
"""a <-- echelon(a); return det
|
| 429 |
+
|
| 430 |
+
Explanation
|
| 431 |
+
===========
|
| 432 |
+
|
| 433 |
+
Compute the determinant of $a$ using the Bareiss fraction-free algorithm.
|
| 434 |
+
The matrix $a$ is modified in place. Its diagonal elements are the
|
| 435 |
+
determinants of the leading principal minors. The determinant of $a$ is
|
| 436 |
+
returned.
|
| 437 |
+
|
| 438 |
+
The domain $K$ must support exact division (``K.exquo``). This method is
|
| 439 |
+
suitable for most exact rings and fields like :ref:`ZZ`, :ref:`QQ` and
|
| 440 |
+
:ref:`QQ(a)` but not for inexact domains like :ref:`RR` and :ref:`CC`.
|
| 441 |
+
|
| 442 |
+
Examples
|
| 443 |
+
========
|
| 444 |
+
|
| 445 |
+
>>> from sympy import ZZ
|
| 446 |
+
>>> from sympy.polys.matrices.ddm import ddm_idet
|
| 447 |
+
>>> a = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]]
|
| 448 |
+
>>> a
|
| 449 |
+
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
|
| 450 |
+
>>> ddm_idet(a, ZZ)
|
| 451 |
+
0
|
| 452 |
+
>>> a
|
| 453 |
+
[[1, 2, 3], [4, -3, -6], [7, -6, 0]]
|
| 454 |
+
>>> [a[i][i] for i in range(len(a))]
|
| 455 |
+
[1, -3, 0]
|
| 456 |
+
|
| 457 |
+
See Also
|
| 458 |
+
========
|
| 459 |
+
|
| 460 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.det
|
| 461 |
+
|
| 462 |
+
References
|
| 463 |
+
==========
|
| 464 |
+
|
| 465 |
+
.. [1] https://en.wikipedia.org/wiki/Bareiss_algorithm
|
| 466 |
+
.. [2] https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
|
| 467 |
+
"""
|
| 468 |
+
# Bareiss algorithm
|
| 469 |
+
# https://www.math.usm.edu/perry/Research/Thesis_DRL.pdf
|
| 470 |
+
|
| 471 |
+
# a is (m x n)
|
| 472 |
+
m = len(a)
|
| 473 |
+
if not m:
|
| 474 |
+
return K.one
|
| 475 |
+
n = len(a[0])
|
| 476 |
+
|
| 477 |
+
exquo = K.exquo
|
| 478 |
+
# uf keeps track of the sign change from row swaps
|
| 479 |
+
uf = K.one
|
| 480 |
+
|
| 481 |
+
for k in range(n-1):
|
| 482 |
+
if not a[k][k]:
|
| 483 |
+
for i in range(k+1, n):
|
| 484 |
+
if a[i][k]:
|
| 485 |
+
a[k], a[i] = a[i], a[k]
|
| 486 |
+
uf = -uf
|
| 487 |
+
break
|
| 488 |
+
else:
|
| 489 |
+
return K.zero
|
| 490 |
+
|
| 491 |
+
akkm1 = a[k-1][k-1] if k else K.one
|
| 492 |
+
|
| 493 |
+
for i in range(k+1, n):
|
| 494 |
+
for j in range(k+1, n):
|
| 495 |
+
a[i][j] = exquo(a[i][j]*a[k][k] - a[i][k]*a[k][j], akkm1)
|
| 496 |
+
|
| 497 |
+
return uf * a[-1][-1]
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
def ddm_iinv(ainv, a, K):
|
| 501 |
+
"""ainv <-- inv(a)
|
| 502 |
+
|
| 503 |
+
Compute the inverse of a matrix $a$ over a field $K$ using Gauss-Jordan
|
| 504 |
+
elimination. The result is stored in $ainv$.
|
| 505 |
+
|
| 506 |
+
Uses division in the ground domain which should be an exact field.
|
| 507 |
+
|
| 508 |
+
Examples
|
| 509 |
+
========
|
| 510 |
+
|
| 511 |
+
>>> from sympy.polys.matrices.ddm import ddm_iinv, ddm_imatmul
|
| 512 |
+
>>> from sympy import QQ
|
| 513 |
+
>>> a = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
| 514 |
+
>>> ainv = [[None, None], [None, None]]
|
| 515 |
+
>>> ddm_iinv(ainv, a, QQ)
|
| 516 |
+
>>> ainv
|
| 517 |
+
[[-2, 1], [3/2, -1/2]]
|
| 518 |
+
>>> result = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
|
| 519 |
+
>>> ddm_imatmul(result, a, ainv)
|
| 520 |
+
>>> result
|
| 521 |
+
[[1, 0], [0, 1]]
|
| 522 |
+
|
| 523 |
+
See Also
|
| 524 |
+
========
|
| 525 |
+
|
| 526 |
+
ddm_irref: the underlying routine.
|
| 527 |
+
"""
|
| 528 |
+
if not K.is_Field:
|
| 529 |
+
raise DMDomainError('Not a field')
|
| 530 |
+
|
| 531 |
+
# a is (m x n)
|
| 532 |
+
m = len(a)
|
| 533 |
+
if not m:
|
| 534 |
+
return
|
| 535 |
+
n = len(a[0])
|
| 536 |
+
if m != n:
|
| 537 |
+
raise DMNonSquareMatrixError
|
| 538 |
+
|
| 539 |
+
eye = [[K.one if i==j else K.zero for j in range(n)] for i in range(n)]
|
| 540 |
+
Aaug = [row + eyerow for row, eyerow in zip(a, eye)]
|
| 541 |
+
pivots = ddm_irref(Aaug)
|
| 542 |
+
if pivots != list(range(n)):
|
| 543 |
+
raise DMNonInvertibleMatrixError('Matrix det == 0; not invertible.')
|
| 544 |
+
ainv[:] = [row[n:] for row in Aaug]
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
def ddm_ilu_split(L, U, K):
|
| 548 |
+
"""L, U <-- LU(U)
|
| 549 |
+
|
| 550 |
+
Compute the LU decomposition of a matrix $L$ in place and store the lower
|
| 551 |
+
and upper triangular matrices in $L$ and $U$, respectively. Returns a list
|
| 552 |
+
of row swaps that were performed.
|
| 553 |
+
|
| 554 |
+
Uses division in the ground domain which should be an exact field.
|
| 555 |
+
|
| 556 |
+
Examples
|
| 557 |
+
========
|
| 558 |
+
|
| 559 |
+
>>> from sympy.polys.matrices.ddm import ddm_ilu_split
|
| 560 |
+
>>> from sympy import QQ
|
| 561 |
+
>>> L = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
|
| 562 |
+
>>> U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
| 563 |
+
>>> swaps = ddm_ilu_split(L, U, QQ)
|
| 564 |
+
>>> swaps
|
| 565 |
+
[]
|
| 566 |
+
>>> L
|
| 567 |
+
[[0, 0], [3, 0]]
|
| 568 |
+
>>> U
|
| 569 |
+
[[1, 2], [0, -2]]
|
| 570 |
+
|
| 571 |
+
See Also
|
| 572 |
+
========
|
| 573 |
+
|
| 574 |
+
ddm_ilu
|
| 575 |
+
ddm_ilu_solve
|
| 576 |
+
"""
|
| 577 |
+
m = len(U)
|
| 578 |
+
if not m:
|
| 579 |
+
return []
|
| 580 |
+
n = len(U[0])
|
| 581 |
+
|
| 582 |
+
swaps = ddm_ilu(U)
|
| 583 |
+
|
| 584 |
+
zeros = [K.zero] * min(m, n)
|
| 585 |
+
for i in range(1, m):
|
| 586 |
+
j = min(i, n)
|
| 587 |
+
L[i][:j] = U[i][:j]
|
| 588 |
+
U[i][:j] = zeros[:j]
|
| 589 |
+
|
| 590 |
+
return swaps
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
def ddm_ilu(a):
|
| 594 |
+
"""a <-- LU(a)
|
| 595 |
+
|
| 596 |
+
Computes the LU decomposition of a matrix in place. Returns a list of
|
| 597 |
+
row swaps that were performed.
|
| 598 |
+
|
| 599 |
+
Uses division in the ground domain which should be an exact field.
|
| 600 |
+
|
| 601 |
+
This is only suitable for domains like :ref:`GF(p)`, :ref:`QQ`, :ref:`QQ_I`
|
| 602 |
+
and :ref:`QQ(a)`. With a rational function field like :ref:`K(x)` it is
|
| 603 |
+
better to clear denominators and use division-free algorithms. Pivoting is
|
| 604 |
+
used to avoid exact zeros but not for floating point accuracy so :ref:`RR`
|
| 605 |
+
and :ref:`CC` are not suitable (use :func:`ddm_irref` instead).
|
| 606 |
+
|
| 607 |
+
Examples
|
| 608 |
+
========
|
| 609 |
+
|
| 610 |
+
>>> from sympy.polys.matrices.dense import ddm_ilu
|
| 611 |
+
>>> from sympy import QQ
|
| 612 |
+
>>> a = [[QQ(1, 2), QQ(1, 3)], [QQ(1, 4), QQ(1, 5)]]
|
| 613 |
+
>>> swaps = ddm_ilu(a)
|
| 614 |
+
>>> swaps
|
| 615 |
+
[]
|
| 616 |
+
>>> a
|
| 617 |
+
[[1/2, 1/3], [1/2, 1/30]]
|
| 618 |
+
|
| 619 |
+
The same example using ``Matrix``:
|
| 620 |
+
|
| 621 |
+
>>> from sympy import Matrix, S
|
| 622 |
+
>>> M = Matrix([[S(1)/2, S(1)/3], [S(1)/4, S(1)/5]])
|
| 623 |
+
>>> L, U, swaps = M.LUdecomposition()
|
| 624 |
+
>>> L
|
| 625 |
+
Matrix([
|
| 626 |
+
[ 1, 0],
|
| 627 |
+
[1/2, 1]])
|
| 628 |
+
>>> U
|
| 629 |
+
Matrix([
|
| 630 |
+
[1/2, 1/3],
|
| 631 |
+
[ 0, 1/30]])
|
| 632 |
+
>>> swaps
|
| 633 |
+
[]
|
| 634 |
+
|
| 635 |
+
See Also
|
| 636 |
+
========
|
| 637 |
+
|
| 638 |
+
ddm_irref
|
| 639 |
+
ddm_ilu_solve
|
| 640 |
+
sympy.matrices.matrixbase.MatrixBase.LUdecomposition
|
| 641 |
+
"""
|
| 642 |
+
m = len(a)
|
| 643 |
+
if not m:
|
| 644 |
+
return []
|
| 645 |
+
n = len(a[0])
|
| 646 |
+
|
| 647 |
+
swaps = []
|
| 648 |
+
|
| 649 |
+
for i in range(min(m, n)):
|
| 650 |
+
if not a[i][i]:
|
| 651 |
+
for ip in range(i+1, m):
|
| 652 |
+
if a[ip][i]:
|
| 653 |
+
swaps.append((i, ip))
|
| 654 |
+
a[i], a[ip] = a[ip], a[i]
|
| 655 |
+
break
|
| 656 |
+
else:
|
| 657 |
+
# M = Matrix([[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]])
|
| 658 |
+
continue
|
| 659 |
+
for j in range(i+1, m):
|
| 660 |
+
l_ji = a[j][i] / a[i][i]
|
| 661 |
+
a[j][i] = l_ji
|
| 662 |
+
for k in range(i+1, n):
|
| 663 |
+
a[j][k] -= l_ji * a[i][k]
|
| 664 |
+
|
| 665 |
+
return swaps
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
def ddm_ilu_solve(x, L, U, swaps, b):
|
| 669 |
+
"""x <-- solve(L*U*x = swaps(b))
|
| 670 |
+
|
| 671 |
+
Solve a linear system, $A*x = b$, given an LU factorization of $A$.
|
| 672 |
+
|
| 673 |
+
Uses division in the ground domain which must be a field.
|
| 674 |
+
|
| 675 |
+
Modifies $x$ in place.
|
| 676 |
+
|
| 677 |
+
Examples
|
| 678 |
+
========
|
| 679 |
+
|
| 680 |
+
Compute the LU decomposition of $A$ (in place):
|
| 681 |
+
|
| 682 |
+
>>> from sympy import QQ
|
| 683 |
+
>>> from sympy.polys.matrices.dense import ddm_ilu, ddm_ilu_solve
|
| 684 |
+
>>> A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
| 685 |
+
>>> swaps = ddm_ilu(A)
|
| 686 |
+
>>> A
|
| 687 |
+
[[1, 2], [3, -2]]
|
| 688 |
+
>>> L = U = A
|
| 689 |
+
|
| 690 |
+
Solve the linear system:
|
| 691 |
+
|
| 692 |
+
>>> b = [[QQ(5)], [QQ(6)]]
|
| 693 |
+
>>> x = [[None], [None]]
|
| 694 |
+
>>> ddm_ilu_solve(x, L, U, swaps, b)
|
| 695 |
+
>>> x
|
| 696 |
+
[[-4], [9/2]]
|
| 697 |
+
|
| 698 |
+
See Also
|
| 699 |
+
========
|
| 700 |
+
|
| 701 |
+
ddm_ilu
|
| 702 |
+
Compute the LU decomposition of a matrix in place.
|
| 703 |
+
ddm_ilu_split
|
| 704 |
+
Compute the LU decomposition of a matrix and separate $L$ and $U$.
|
| 705 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.lu_solve
|
| 706 |
+
Higher level interface to this function.
|
| 707 |
+
"""
|
| 708 |
+
m = len(U)
|
| 709 |
+
if not m:
|
| 710 |
+
return
|
| 711 |
+
n = len(U[0])
|
| 712 |
+
|
| 713 |
+
m2 = len(b)
|
| 714 |
+
if not m2:
|
| 715 |
+
raise DMShapeError("Shape mismtch")
|
| 716 |
+
o = len(b[0])
|
| 717 |
+
|
| 718 |
+
if m != m2:
|
| 719 |
+
raise DMShapeError("Shape mismtch")
|
| 720 |
+
if m < n:
|
| 721 |
+
raise NotImplementedError("Underdetermined")
|
| 722 |
+
|
| 723 |
+
if swaps:
|
| 724 |
+
b = [row[:] for row in b]
|
| 725 |
+
for i1, i2 in swaps:
|
| 726 |
+
b[i1], b[i2] = b[i2], b[i1]
|
| 727 |
+
|
| 728 |
+
# solve Ly = b
|
| 729 |
+
y = [[None] * o for _ in range(m)]
|
| 730 |
+
for k in range(o):
|
| 731 |
+
for i in range(m):
|
| 732 |
+
rhs = b[i][k]
|
| 733 |
+
for j in range(i):
|
| 734 |
+
rhs -= L[i][j] * y[j][k]
|
| 735 |
+
y[i][k] = rhs
|
| 736 |
+
|
| 737 |
+
if m > n:
|
| 738 |
+
for i in range(n, m):
|
| 739 |
+
for j in range(o):
|
| 740 |
+
if y[i][j]:
|
| 741 |
+
raise DMNonInvertibleMatrixError
|
| 742 |
+
|
| 743 |
+
# Solve Ux = y
|
| 744 |
+
for k in range(o):
|
| 745 |
+
for i in reversed(range(n)):
|
| 746 |
+
if not U[i][i]:
|
| 747 |
+
raise DMNonInvertibleMatrixError
|
| 748 |
+
rhs = y[i][k]
|
| 749 |
+
for j in range(i+1, n):
|
| 750 |
+
rhs -= U[i][j] * x[j][k]
|
| 751 |
+
x[i][k] = rhs / U[i][i]
|
| 752 |
+
|
| 753 |
+
|
| 754 |
+
def ddm_berk(M, K):
|
| 755 |
+
"""
|
| 756 |
+
Berkowitz algorithm for computing the characteristic polynomial.
|
| 757 |
+
|
| 758 |
+
Explanation
|
| 759 |
+
===========
|
| 760 |
+
|
| 761 |
+
The Berkowitz algorithm is a division-free algorithm for computing the
|
| 762 |
+
characteristic polynomial of a matrix over any commutative ring using only
|
| 763 |
+
arithmetic in the coefficient ring.
|
| 764 |
+
|
| 765 |
+
Examples
|
| 766 |
+
========
|
| 767 |
+
|
| 768 |
+
>>> from sympy import Matrix
|
| 769 |
+
>>> from sympy.polys.matrices.dense import ddm_berk
|
| 770 |
+
>>> from sympy.polys.domains import ZZ
|
| 771 |
+
>>> M = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]]
|
| 772 |
+
>>> ddm_berk(M, ZZ)
|
| 773 |
+
[[1], [-5], [-2]]
|
| 774 |
+
>>> Matrix(M).charpoly()
|
| 775 |
+
PurePoly(lambda**2 - 5*lambda - 2, lambda, domain='ZZ')
|
| 776 |
+
|
| 777 |
+
See Also
|
| 778 |
+
========
|
| 779 |
+
|
| 780 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly
|
| 781 |
+
The high-level interface to this function.
|
| 782 |
+
|
| 783 |
+
References
|
| 784 |
+
==========
|
| 785 |
+
|
| 786 |
+
.. [1] https://en.wikipedia.org/wiki/Samuelson%E2%80%93Berkowitz_algorithm
|
| 787 |
+
"""
|
| 788 |
+
m = len(M)
|
| 789 |
+
if not m:
|
| 790 |
+
return [[K.one]]
|
| 791 |
+
n = len(M[0])
|
| 792 |
+
|
| 793 |
+
if m != n:
|
| 794 |
+
raise DMShapeError("Not square")
|
| 795 |
+
|
| 796 |
+
if n == 1:
|
| 797 |
+
return [[K.one], [-M[0][0]]]
|
| 798 |
+
|
| 799 |
+
a = M[0][0]
|
| 800 |
+
R = [M[0][1:]]
|
| 801 |
+
C = [[row[0]] for row in M[1:]]
|
| 802 |
+
A = [row[1:] for row in M[1:]]
|
| 803 |
+
|
| 804 |
+
q = ddm_berk(A, K)
|
| 805 |
+
|
| 806 |
+
T = [[K.zero] * n for _ in range(n+1)]
|
| 807 |
+
for i in range(n):
|
| 808 |
+
T[i][i] = K.one
|
| 809 |
+
T[i+1][i] = -a
|
| 810 |
+
for i in range(2, n+1):
|
| 811 |
+
if i == 2:
|
| 812 |
+
AnC = C
|
| 813 |
+
else:
|
| 814 |
+
C = AnC
|
| 815 |
+
AnC = [[K.zero] for row in C]
|
| 816 |
+
ddm_imatmul(AnC, A, C)
|
| 817 |
+
RAnC = [[K.zero]]
|
| 818 |
+
ddm_imatmul(RAnC, R, AnC)
|
| 819 |
+
for j in range(0, n+1-i):
|
| 820 |
+
T[i+j][j] = -RAnC[0][0]
|
| 821 |
+
|
| 822 |
+
qout = [[K.zero] for _ in range(n+1)]
|
| 823 |
+
ddm_imatmul(qout, T, q)
|
| 824 |
+
return qout
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/dfm.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
sympy.polys.matrices.dfm
|
| 3 |
+
|
| 4 |
+
Provides the :class:`DFM` class if ``GROUND_TYPES=flint'``. Otherwise, ``DFM``
|
| 5 |
+
is a placeholder class that raises NotImplementedError when instantiated.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from sympy.external.gmpy import GROUND_TYPES
|
| 9 |
+
|
| 10 |
+
if GROUND_TYPES == "flint": # pragma: no cover
|
| 11 |
+
# When python-flint is installed we will try to use it for dense matrices
|
| 12 |
+
# if the domain is supported by python-flint.
|
| 13 |
+
from ._dfm import DFM
|
| 14 |
+
|
| 15 |
+
else: # pragma: no cover
|
| 16 |
+
# Other code should be able to import this and it should just present as a
|
| 17 |
+
# version of DFM that does not support any domains.
|
| 18 |
+
class DFM_dummy:
|
| 19 |
+
"""
|
| 20 |
+
Placeholder class for DFM when python-flint is not installed.
|
| 21 |
+
"""
|
| 22 |
+
def __init__(*args, **kwargs):
|
| 23 |
+
raise NotImplementedError("DFM requires GROUND_TYPES=flint.")
|
| 24 |
+
|
| 25 |
+
@classmethod
|
| 26 |
+
def _supports_domain(cls, domain):
|
| 27 |
+
return False
|
| 28 |
+
|
| 29 |
+
@classmethod
|
| 30 |
+
def _get_flint_func(cls, domain):
|
| 31 |
+
raise NotImplementedError("DFM requires GROUND_TYPES=flint.")
|
| 32 |
+
|
| 33 |
+
# mypy really struggles with this kind of conditional type assignment.
|
| 34 |
+
# Maybe there is a better way to annotate this rather than type: ignore.
|
| 35 |
+
DFM = DFM_dummy # type: ignore
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/domainmatrix.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/domainscalar.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
|
| 3 |
+
Module for the DomainScalar class.
|
| 4 |
+
|
| 5 |
+
A DomainScalar represents an element which is in a particular
|
| 6 |
+
Domain. The idea is that the DomainScalar class provides the
|
| 7 |
+
convenience routines for unifying elements with different domains.
|
| 8 |
+
|
| 9 |
+
It assists in Scalar Multiplication and getitem for DomainMatrix.
|
| 10 |
+
|
| 11 |
+
"""
|
| 12 |
+
from ..constructor import construct_domain
|
| 13 |
+
|
| 14 |
+
from sympy.polys.domains import Domain, ZZ
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DomainScalar:
|
| 18 |
+
r"""
|
| 19 |
+
docstring
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __new__(cls, element, domain):
|
| 23 |
+
if not isinstance(domain, Domain):
|
| 24 |
+
raise TypeError("domain should be of type Domain")
|
| 25 |
+
if not domain.of_type(element):
|
| 26 |
+
raise TypeError("element %s should be in domain %s" % (element, domain))
|
| 27 |
+
return cls.new(element, domain)
|
| 28 |
+
|
| 29 |
+
@classmethod
|
| 30 |
+
def new(cls, element, domain):
|
| 31 |
+
obj = super().__new__(cls)
|
| 32 |
+
obj.element = element
|
| 33 |
+
obj.domain = domain
|
| 34 |
+
return obj
|
| 35 |
+
|
| 36 |
+
def __repr__(self):
|
| 37 |
+
return repr(self.element)
|
| 38 |
+
|
| 39 |
+
@classmethod
|
| 40 |
+
def from_sympy(cls, expr):
|
| 41 |
+
[domain, [element]] = construct_domain([expr])
|
| 42 |
+
return cls.new(element, domain)
|
| 43 |
+
|
| 44 |
+
def to_sympy(self):
|
| 45 |
+
return self.domain.to_sympy(self.element)
|
| 46 |
+
|
| 47 |
+
def to_domain(self, domain):
|
| 48 |
+
element = domain.convert_from(self.element, self.domain)
|
| 49 |
+
return self.new(element, domain)
|
| 50 |
+
|
| 51 |
+
def convert_to(self, domain):
|
| 52 |
+
return self.to_domain(domain)
|
| 53 |
+
|
| 54 |
+
def unify(self, other):
|
| 55 |
+
domain = self.domain.unify(other.domain)
|
| 56 |
+
return self.to_domain(domain), other.to_domain(domain)
|
| 57 |
+
|
| 58 |
+
def __bool__(self):
|
| 59 |
+
return bool(self.element)
|
| 60 |
+
|
| 61 |
+
def __add__(self, other):
|
| 62 |
+
if not isinstance(other, DomainScalar):
|
| 63 |
+
return NotImplemented
|
| 64 |
+
self, other = self.unify(other)
|
| 65 |
+
return self.new(self.element + other.element, self.domain)
|
| 66 |
+
|
| 67 |
+
def __sub__(self, other):
|
| 68 |
+
if not isinstance(other, DomainScalar):
|
| 69 |
+
return NotImplemented
|
| 70 |
+
self, other = self.unify(other)
|
| 71 |
+
return self.new(self.element - other.element, self.domain)
|
| 72 |
+
|
| 73 |
+
def __mul__(self, other):
|
| 74 |
+
if not isinstance(other, DomainScalar):
|
| 75 |
+
if isinstance(other, int):
|
| 76 |
+
other = DomainScalar(ZZ(other), ZZ)
|
| 77 |
+
else:
|
| 78 |
+
return NotImplemented
|
| 79 |
+
|
| 80 |
+
self, other = self.unify(other)
|
| 81 |
+
return self.new(self.element * other.element, self.domain)
|
| 82 |
+
|
| 83 |
+
def __floordiv__(self, other):
|
| 84 |
+
if not isinstance(other, DomainScalar):
|
| 85 |
+
return NotImplemented
|
| 86 |
+
self, other = self.unify(other)
|
| 87 |
+
return self.new(self.domain.quo(self.element, other.element), self.domain)
|
| 88 |
+
|
| 89 |
+
def __mod__(self, other):
|
| 90 |
+
if not isinstance(other, DomainScalar):
|
| 91 |
+
return NotImplemented
|
| 92 |
+
self, other = self.unify(other)
|
| 93 |
+
return self.new(self.domain.rem(self.element, other.element), self.domain)
|
| 94 |
+
|
| 95 |
+
def __divmod__(self, other):
|
| 96 |
+
if not isinstance(other, DomainScalar):
|
| 97 |
+
return NotImplemented
|
| 98 |
+
self, other = self.unify(other)
|
| 99 |
+
q, r = self.domain.div(self.element, other.element)
|
| 100 |
+
return (self.new(q, self.domain), self.new(r, self.domain))
|
| 101 |
+
|
| 102 |
+
def __pow__(self, n):
|
| 103 |
+
if not isinstance(n, int):
|
| 104 |
+
return NotImplemented
|
| 105 |
+
return self.new(self.element**n, self.domain)
|
| 106 |
+
|
| 107 |
+
def __pos__(self):
|
| 108 |
+
return self.new(+self.element, self.domain)
|
| 109 |
+
|
| 110 |
+
def __neg__(self):
|
| 111 |
+
return self.new(-self.element, self.domain)
|
| 112 |
+
|
| 113 |
+
def __eq__(self, other):
|
| 114 |
+
if not isinstance(other, DomainScalar):
|
| 115 |
+
return NotImplemented
|
| 116 |
+
return self.element == other.element and self.domain == other.domain
|
| 117 |
+
|
| 118 |
+
def is_zero(self):
|
| 119 |
+
return self.element == self.domain.zero
|
| 120 |
+
|
| 121 |
+
def is_one(self):
|
| 122 |
+
return self.element == self.domain.one
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/eigen.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
|
| 3 |
+
Routines for computing eigenvectors with DomainMatrix.
|
| 4 |
+
|
| 5 |
+
"""
|
| 6 |
+
from sympy.core.symbol import Dummy
|
| 7 |
+
|
| 8 |
+
from ..agca.extensions import FiniteExtension
|
| 9 |
+
from ..factortools import dup_factor_list
|
| 10 |
+
from ..polyroots import roots
|
| 11 |
+
from ..polytools import Poly
|
| 12 |
+
from ..rootoftools import CRootOf
|
| 13 |
+
|
| 14 |
+
from .domainmatrix import DomainMatrix
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def dom_eigenvects(A, l=Dummy('lambda')):
|
| 18 |
+
charpoly = A.charpoly()
|
| 19 |
+
rows, cols = A.shape
|
| 20 |
+
domain = A.domain
|
| 21 |
+
_, factors = dup_factor_list(charpoly, domain)
|
| 22 |
+
|
| 23 |
+
rational_eigenvects = []
|
| 24 |
+
algebraic_eigenvects = []
|
| 25 |
+
for base, exp in factors:
|
| 26 |
+
if len(base) == 2:
|
| 27 |
+
field = domain
|
| 28 |
+
eigenval = -base[1] / base[0]
|
| 29 |
+
|
| 30 |
+
EE_items = [
|
| 31 |
+
[eigenval if i == j else field.zero for j in range(cols)]
|
| 32 |
+
for i in range(rows)]
|
| 33 |
+
EE = DomainMatrix(EE_items, (rows, cols), field)
|
| 34 |
+
|
| 35 |
+
basis = (A - EE).nullspace(divide_last=True)
|
| 36 |
+
rational_eigenvects.append((field, eigenval, exp, basis))
|
| 37 |
+
else:
|
| 38 |
+
minpoly = Poly.from_list(base, l, domain=domain)
|
| 39 |
+
field = FiniteExtension(minpoly)
|
| 40 |
+
eigenval = field(l)
|
| 41 |
+
|
| 42 |
+
AA_items = [
|
| 43 |
+
[Poly.from_list([item], l, domain=domain).rep for item in row]
|
| 44 |
+
for row in A.rep.to_ddm()]
|
| 45 |
+
AA_items = [[field(item) for item in row] for row in AA_items]
|
| 46 |
+
AA = DomainMatrix(AA_items, (rows, cols), field)
|
| 47 |
+
EE_items = [
|
| 48 |
+
[eigenval if i == j else field.zero for j in range(cols)]
|
| 49 |
+
for i in range(rows)]
|
| 50 |
+
EE = DomainMatrix(EE_items, (rows, cols), field)
|
| 51 |
+
|
| 52 |
+
basis = (AA - EE).nullspace(divide_last=True)
|
| 53 |
+
algebraic_eigenvects.append((field, minpoly, exp, basis))
|
| 54 |
+
|
| 55 |
+
return rational_eigenvects, algebraic_eigenvects
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def dom_eigenvects_to_sympy(
|
| 59 |
+
rational_eigenvects, algebraic_eigenvects,
|
| 60 |
+
Matrix, **kwargs
|
| 61 |
+
):
|
| 62 |
+
result = []
|
| 63 |
+
|
| 64 |
+
for field, eigenvalue, multiplicity, eigenvects in rational_eigenvects:
|
| 65 |
+
eigenvects = eigenvects.rep.to_ddm()
|
| 66 |
+
eigenvalue = field.to_sympy(eigenvalue)
|
| 67 |
+
new_eigenvects = [
|
| 68 |
+
Matrix([field.to_sympy(x) for x in vect])
|
| 69 |
+
for vect in eigenvects]
|
| 70 |
+
result.append((eigenvalue, multiplicity, new_eigenvects))
|
| 71 |
+
|
| 72 |
+
for field, minpoly, multiplicity, eigenvects in algebraic_eigenvects:
|
| 73 |
+
eigenvects = eigenvects.rep.to_ddm()
|
| 74 |
+
l = minpoly.gens[0]
|
| 75 |
+
|
| 76 |
+
eigenvects = [[field.to_sympy(x) for x in vect] for vect in eigenvects]
|
| 77 |
+
|
| 78 |
+
degree = minpoly.degree()
|
| 79 |
+
minpoly = minpoly.as_expr()
|
| 80 |
+
eigenvals = roots(minpoly, l, **kwargs)
|
| 81 |
+
if len(eigenvals) != degree:
|
| 82 |
+
eigenvals = [CRootOf(minpoly, l, idx) for idx in range(degree)]
|
| 83 |
+
|
| 84 |
+
for eigenvalue in eigenvals:
|
| 85 |
+
new_eigenvects = [
|
| 86 |
+
Matrix([x.subs(l, eigenvalue) for x in vect])
|
| 87 |
+
for vect in eigenvects]
|
| 88 |
+
result.append((eigenvalue, multiplicity, new_eigenvects))
|
| 89 |
+
|
| 90 |
+
return result
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/exceptions.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
|
| 3 |
+
Module to define exceptions to be used in sympy.polys.matrices modules and
|
| 4 |
+
classes.
|
| 5 |
+
|
| 6 |
+
Ideally all exceptions raised in these modules would be defined and documented
|
| 7 |
+
here and not e.g. imported from matrices. Also ideally generic exceptions like
|
| 8 |
+
ValueError/TypeError would not be raised anywhere.
|
| 9 |
+
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class DMError(Exception):
|
| 14 |
+
"""Base class for errors raised by DomainMatrix"""
|
| 15 |
+
pass
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class DMBadInputError(DMError):
|
| 19 |
+
"""list of lists is inconsistent with shape"""
|
| 20 |
+
pass
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class DMDomainError(DMError):
|
| 24 |
+
"""domains do not match"""
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class DMNotAField(DMDomainError):
|
| 29 |
+
"""domain is not a field"""
|
| 30 |
+
pass
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class DMFormatError(DMError):
|
| 34 |
+
"""mixed dense/sparse not supported"""
|
| 35 |
+
pass
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class DMNonInvertibleMatrixError(DMError):
|
| 39 |
+
"""The matrix in not invertible"""
|
| 40 |
+
pass
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class DMRankError(DMError):
|
| 44 |
+
"""matrix does not have expected rank"""
|
| 45 |
+
pass
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class DMShapeError(DMError):
|
| 49 |
+
"""shapes are inconsistent"""
|
| 50 |
+
pass
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class DMNonSquareMatrixError(DMShapeError):
|
| 54 |
+
"""The matrix is not square"""
|
| 55 |
+
pass
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class DMValueError(DMError):
|
| 59 |
+
"""The value passed is invalid"""
|
| 60 |
+
pass
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
__all__ = [
|
| 64 |
+
'DMError', 'DMBadInputError', 'DMDomainError', 'DMFormatError',
|
| 65 |
+
'DMRankError', 'DMShapeError', 'DMNotAField',
|
| 66 |
+
'DMNonInvertibleMatrixError', 'DMNonSquareMatrixError', 'DMValueError'
|
| 67 |
+
]
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/linsolve.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# sympy.polys.matrices.linsolve module
|
| 3 |
+
#
|
| 4 |
+
# This module defines the _linsolve function which is the internal workhorse
|
| 5 |
+
# used by linsolve. This computes the solution of a system of linear equations
|
| 6 |
+
# using the SDM sparse matrix implementation in sympy.polys.matrices.sdm. This
|
| 7 |
+
# is a replacement for solve_lin_sys in sympy.polys.solvers which is
|
| 8 |
+
# inefficient for large sparse systems due to the use of a PolyRing with many
|
| 9 |
+
# generators:
|
| 10 |
+
#
|
| 11 |
+
# https://github.com/sympy/sympy/issues/20857
|
| 12 |
+
#
|
| 13 |
+
# The implementation of _linsolve here handles:
|
| 14 |
+
#
|
| 15 |
+
# - Extracting the coefficients from the Expr/Eq input equations.
|
| 16 |
+
# - Constructing a domain and converting the coefficients to
|
| 17 |
+
# that domain.
|
| 18 |
+
# - Using the SDM.rref, SDM.nullspace etc methods to generate the full
|
| 19 |
+
# solution working with arithmetic only in the domain of the coefficients.
|
| 20 |
+
#
|
| 21 |
+
# The routines here are particularly designed to be efficient for large sparse
|
| 22 |
+
# systems of linear equations although as well as dense systems. It is
|
| 23 |
+
# possible that for some small dense systems solve_lin_sys which uses the
|
| 24 |
+
# dense matrix implementation DDM will be more efficient. With smaller systems
|
| 25 |
+
# though the bulk of the time is spent just preprocessing the inputs and the
|
| 26 |
+
# relative time spent in rref is too small to be noticeable.
|
| 27 |
+
#
|
| 28 |
+
|
| 29 |
+
from collections import defaultdict
|
| 30 |
+
|
| 31 |
+
from sympy.core.add import Add
|
| 32 |
+
from sympy.core.mul import Mul
|
| 33 |
+
from sympy.core.singleton import S
|
| 34 |
+
|
| 35 |
+
from sympy.polys.constructor import construct_domain
|
| 36 |
+
from sympy.polys.solvers import PolyNonlinearError
|
| 37 |
+
|
| 38 |
+
from .sdm import (
|
| 39 |
+
SDM,
|
| 40 |
+
sdm_irref,
|
| 41 |
+
sdm_particular_from_rref,
|
| 42 |
+
sdm_nullspace_from_rref
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
from sympy.utilities.misc import filldedent
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _linsolve(eqs, syms):
|
| 49 |
+
|
| 50 |
+
"""Solve a linear system of equations.
|
| 51 |
+
|
| 52 |
+
Examples
|
| 53 |
+
========
|
| 54 |
+
|
| 55 |
+
Solve a linear system with a unique solution:
|
| 56 |
+
|
| 57 |
+
>>> from sympy import symbols, Eq
|
| 58 |
+
>>> from sympy.polys.matrices.linsolve import _linsolve
|
| 59 |
+
>>> x, y = symbols('x, y')
|
| 60 |
+
>>> eqs = [Eq(x + y, 1), Eq(x - y, 2)]
|
| 61 |
+
>>> _linsolve(eqs, [x, y])
|
| 62 |
+
{x: 3/2, y: -1/2}
|
| 63 |
+
|
| 64 |
+
In the case of underdetermined systems the solution will be expressed in
|
| 65 |
+
terms of the unknown symbols that are unconstrained:
|
| 66 |
+
|
| 67 |
+
>>> _linsolve([Eq(x + y, 0)], [x, y])
|
| 68 |
+
{x: -y, y: y}
|
| 69 |
+
|
| 70 |
+
"""
|
| 71 |
+
# Number of unknowns (columns in the non-augmented matrix)
|
| 72 |
+
nsyms = len(syms)
|
| 73 |
+
|
| 74 |
+
# Convert to sparse augmented matrix (len(eqs) x (nsyms+1))
|
| 75 |
+
eqsdict, const = _linear_eq_to_dict(eqs, syms)
|
| 76 |
+
Aaug = sympy_dict_to_dm(eqsdict, const, syms)
|
| 77 |
+
K = Aaug.domain
|
| 78 |
+
|
| 79 |
+
# sdm_irref has issues with float matrices. This uses the ddm_rref()
|
| 80 |
+
# function. When sdm_rref() can handle float matrices reasonably this
|
| 81 |
+
# should be removed...
|
| 82 |
+
if K.is_RealField or K.is_ComplexField:
|
| 83 |
+
Aaug = Aaug.to_ddm().rref()[0].to_sdm()
|
| 84 |
+
|
| 85 |
+
# Compute reduced-row echelon form (RREF)
|
| 86 |
+
Arref, pivots, nzcols = sdm_irref(Aaug)
|
| 87 |
+
|
| 88 |
+
# No solution:
|
| 89 |
+
if pivots and pivots[-1] == nsyms:
|
| 90 |
+
return None
|
| 91 |
+
|
| 92 |
+
# Particular solution for non-homogeneous system:
|
| 93 |
+
P = sdm_particular_from_rref(Arref, nsyms+1, pivots)
|
| 94 |
+
|
| 95 |
+
# Nullspace - general solution to homogeneous system
|
| 96 |
+
# Note: using nsyms not nsyms+1 to ignore last column
|
| 97 |
+
V, nonpivots = sdm_nullspace_from_rref(Arref, K.one, nsyms, pivots, nzcols)
|
| 98 |
+
|
| 99 |
+
# Collect together terms from particular and nullspace:
|
| 100 |
+
sol = defaultdict(list)
|
| 101 |
+
for i, v in P.items():
|
| 102 |
+
sol[syms[i]].append(K.to_sympy(v))
|
| 103 |
+
for npi, Vi in zip(nonpivots, V):
|
| 104 |
+
sym = syms[npi]
|
| 105 |
+
for i, v in Vi.items():
|
| 106 |
+
sol[syms[i]].append(sym * K.to_sympy(v))
|
| 107 |
+
|
| 108 |
+
# Use a single call to Add for each term:
|
| 109 |
+
sol = {s: Add(*terms) for s, terms in sol.items()}
|
| 110 |
+
|
| 111 |
+
# Fill in the zeros:
|
| 112 |
+
zero = S.Zero
|
| 113 |
+
for s in set(syms) - set(sol):
|
| 114 |
+
sol[s] = zero
|
| 115 |
+
|
| 116 |
+
# All done!
|
| 117 |
+
return sol
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def sympy_dict_to_dm(eqs_coeffs, eqs_rhs, syms):
|
| 121 |
+
"""Convert a system of dict equations to a sparse augmented matrix"""
|
| 122 |
+
elems = set(eqs_rhs).union(*(e.values() for e in eqs_coeffs))
|
| 123 |
+
K, elems_K = construct_domain(elems, field=True, extension=True)
|
| 124 |
+
elem_map = dict(zip(elems, elems_K))
|
| 125 |
+
neqs = len(eqs_coeffs)
|
| 126 |
+
nsyms = len(syms)
|
| 127 |
+
sym2index = dict(zip(syms, range(nsyms)))
|
| 128 |
+
eqsdict = []
|
| 129 |
+
for eq, rhs in zip(eqs_coeffs, eqs_rhs):
|
| 130 |
+
eqdict = {sym2index[s]: elem_map[c] for s, c in eq.items()}
|
| 131 |
+
if rhs:
|
| 132 |
+
eqdict[nsyms] = -elem_map[rhs]
|
| 133 |
+
if eqdict:
|
| 134 |
+
eqsdict.append(eqdict)
|
| 135 |
+
sdm_aug = SDM(enumerate(eqsdict), (neqs, nsyms + 1), K)
|
| 136 |
+
return sdm_aug
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def _linear_eq_to_dict(eqs, syms):
|
| 140 |
+
"""Convert a system Expr/Eq equations into dict form, returning
|
| 141 |
+
the coefficient dictionaries and a list of syms-independent terms
|
| 142 |
+
from each expression in ``eqs```.
|
| 143 |
+
|
| 144 |
+
Examples
|
| 145 |
+
========
|
| 146 |
+
|
| 147 |
+
>>> from sympy.polys.matrices.linsolve import _linear_eq_to_dict
|
| 148 |
+
>>> from sympy.abc import x
|
| 149 |
+
>>> _linear_eq_to_dict([2*x + 3], {x})
|
| 150 |
+
([{x: 2}], [3])
|
| 151 |
+
"""
|
| 152 |
+
coeffs = []
|
| 153 |
+
ind = []
|
| 154 |
+
symset = set(syms)
|
| 155 |
+
for e in eqs:
|
| 156 |
+
if e.is_Equality:
|
| 157 |
+
coeff, terms = _lin_eq2dict(e.lhs, symset)
|
| 158 |
+
cR, tR = _lin_eq2dict(e.rhs, symset)
|
| 159 |
+
# there were no nonlinear errors so now
|
| 160 |
+
# cancellation is allowed
|
| 161 |
+
coeff -= cR
|
| 162 |
+
for k, v in tR.items():
|
| 163 |
+
if k in terms:
|
| 164 |
+
terms[k] -= v
|
| 165 |
+
else:
|
| 166 |
+
terms[k] = -v
|
| 167 |
+
# don't store coefficients of 0, however
|
| 168 |
+
terms = {k: v for k, v in terms.items() if v}
|
| 169 |
+
c, d = coeff, terms
|
| 170 |
+
else:
|
| 171 |
+
c, d = _lin_eq2dict(e, symset)
|
| 172 |
+
coeffs.append(d)
|
| 173 |
+
ind.append(c)
|
| 174 |
+
return coeffs, ind
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def _lin_eq2dict(a, symset):
|
| 178 |
+
"""return (c, d) where c is the sym-independent part of ``a`` and
|
| 179 |
+
``d`` is an efficiently calculated dictionary mapping symbols to
|
| 180 |
+
their coefficients. A PolyNonlinearError is raised if non-linearity
|
| 181 |
+
is detected.
|
| 182 |
+
|
| 183 |
+
The values in the dictionary will be non-zero.
|
| 184 |
+
|
| 185 |
+
Examples
|
| 186 |
+
========
|
| 187 |
+
|
| 188 |
+
>>> from sympy.polys.matrices.linsolve import _lin_eq2dict
|
| 189 |
+
>>> from sympy.abc import x, y
|
| 190 |
+
>>> _lin_eq2dict(x + 2*y + 3, {x, y})
|
| 191 |
+
(3, {x: 1, y: 2})
|
| 192 |
+
"""
|
| 193 |
+
if a in symset:
|
| 194 |
+
return S.Zero, {a: S.One}
|
| 195 |
+
elif a.is_Add:
|
| 196 |
+
terms_list = defaultdict(list)
|
| 197 |
+
coeff_list = []
|
| 198 |
+
for ai in a.args:
|
| 199 |
+
ci, ti = _lin_eq2dict(ai, symset)
|
| 200 |
+
coeff_list.append(ci)
|
| 201 |
+
for mij, cij in ti.items():
|
| 202 |
+
terms_list[mij].append(cij)
|
| 203 |
+
coeff = Add(*coeff_list)
|
| 204 |
+
terms = {sym: Add(*coeffs) for sym, coeffs in terms_list.items()}
|
| 205 |
+
return coeff, terms
|
| 206 |
+
elif a.is_Mul:
|
| 207 |
+
terms = terms_coeff = None
|
| 208 |
+
coeff_list = []
|
| 209 |
+
for ai in a.args:
|
| 210 |
+
ci, ti = _lin_eq2dict(ai, symset)
|
| 211 |
+
if not ti:
|
| 212 |
+
coeff_list.append(ci)
|
| 213 |
+
elif terms is None:
|
| 214 |
+
terms = ti
|
| 215 |
+
terms_coeff = ci
|
| 216 |
+
else:
|
| 217 |
+
# since ti is not null and we already have
|
| 218 |
+
# a term, this is a cross term
|
| 219 |
+
raise PolyNonlinearError(filldedent('''
|
| 220 |
+
nonlinear cross-term: %s''' % a))
|
| 221 |
+
coeff = Mul._from_args(coeff_list)
|
| 222 |
+
if terms is None:
|
| 223 |
+
return coeff, {}
|
| 224 |
+
else:
|
| 225 |
+
terms = {sym: coeff * c for sym, c in terms.items()}
|
| 226 |
+
return coeff * terms_coeff, terms
|
| 227 |
+
elif not a.has_xfree(symset):
|
| 228 |
+
return a, {}
|
| 229 |
+
else:
|
| 230 |
+
raise PolyNonlinearError('nonlinear term: %s' % a)
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/lll.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from math import floor as mfloor
|
| 4 |
+
|
| 5 |
+
from sympy.polys.domains import ZZ, QQ
|
| 6 |
+
from sympy.polys.matrices.exceptions import DMRankError, DMShapeError, DMValueError, DMDomainError
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _ddm_lll(x, delta=QQ(3, 4), return_transform=False):
|
| 10 |
+
if QQ(1, 4) >= delta or delta >= QQ(1, 1):
|
| 11 |
+
raise DMValueError("delta must lie in range (0.25, 1)")
|
| 12 |
+
if x.shape[0] > x.shape[1]:
|
| 13 |
+
raise DMShapeError("input matrix must have shape (m, n) with m <= n")
|
| 14 |
+
if x.domain != ZZ:
|
| 15 |
+
raise DMDomainError("input matrix domain must be ZZ")
|
| 16 |
+
m = x.shape[0]
|
| 17 |
+
n = x.shape[1]
|
| 18 |
+
k = 1
|
| 19 |
+
y = x.copy()
|
| 20 |
+
y_star = x.zeros((m, n), QQ)
|
| 21 |
+
mu = x.zeros((m, m), QQ)
|
| 22 |
+
g_star = [QQ(0, 1) for _ in range(m)]
|
| 23 |
+
half = QQ(1, 2)
|
| 24 |
+
T = x.eye(m, ZZ) if return_transform else None
|
| 25 |
+
linear_dependent_error = "input matrix contains linearly dependent rows"
|
| 26 |
+
|
| 27 |
+
def closest_integer(x):
|
| 28 |
+
return ZZ(mfloor(x + half))
|
| 29 |
+
|
| 30 |
+
def lovasz_condition(k: int) -> bool:
|
| 31 |
+
return g_star[k] >= ((delta - mu[k][k - 1] ** 2) * g_star[k - 1])
|
| 32 |
+
|
| 33 |
+
def mu_small(k: int, j: int) -> bool:
|
| 34 |
+
return abs(mu[k][j]) <= half
|
| 35 |
+
|
| 36 |
+
def dot_rows(x, y, rows: tuple[int, int]):
|
| 37 |
+
return sum(x[rows[0]][z] * y[rows[1]][z] for z in range(x.shape[1]))
|
| 38 |
+
|
| 39 |
+
def reduce_row(T, mu, y, rows: tuple[int, int]):
|
| 40 |
+
r = closest_integer(mu[rows[0]][rows[1]])
|
| 41 |
+
y[rows[0]] = [y[rows[0]][z] - r * y[rows[1]][z] for z in range(n)]
|
| 42 |
+
mu[rows[0]][:rows[1]] = [mu[rows[0]][z] - r * mu[rows[1]][z] for z in range(rows[1])]
|
| 43 |
+
mu[rows[0]][rows[1]] -= r
|
| 44 |
+
if return_transform:
|
| 45 |
+
T[rows[0]] = [T[rows[0]][z] - r * T[rows[1]][z] for z in range(m)]
|
| 46 |
+
|
| 47 |
+
for i in range(m):
|
| 48 |
+
y_star[i] = [QQ.convert_from(z, ZZ) for z in y[i]]
|
| 49 |
+
for j in range(i):
|
| 50 |
+
row_dot = dot_rows(y, y_star, (i, j))
|
| 51 |
+
try:
|
| 52 |
+
mu[i][j] = row_dot / g_star[j]
|
| 53 |
+
except ZeroDivisionError:
|
| 54 |
+
raise DMRankError(linear_dependent_error)
|
| 55 |
+
y_star[i] = [y_star[i][z] - mu[i][j] * y_star[j][z] for z in range(n)]
|
| 56 |
+
g_star[i] = dot_rows(y_star, y_star, (i, i))
|
| 57 |
+
while k < m:
|
| 58 |
+
if not mu_small(k, k - 1):
|
| 59 |
+
reduce_row(T, mu, y, (k, k - 1))
|
| 60 |
+
if lovasz_condition(k):
|
| 61 |
+
for l in range(k - 2, -1, -1):
|
| 62 |
+
if not mu_small(k, l):
|
| 63 |
+
reduce_row(T, mu, y, (k, l))
|
| 64 |
+
k += 1
|
| 65 |
+
else:
|
| 66 |
+
nu = mu[k][k - 1]
|
| 67 |
+
alpha = g_star[k] + nu ** 2 * g_star[k - 1]
|
| 68 |
+
try:
|
| 69 |
+
beta = g_star[k - 1] / alpha
|
| 70 |
+
except ZeroDivisionError:
|
| 71 |
+
raise DMRankError(linear_dependent_error)
|
| 72 |
+
mu[k][k - 1] = nu * beta
|
| 73 |
+
g_star[k] = g_star[k] * beta
|
| 74 |
+
g_star[k - 1] = alpha
|
| 75 |
+
y[k], y[k - 1] = y[k - 1], y[k]
|
| 76 |
+
mu[k][:k - 1], mu[k - 1][:k - 1] = mu[k - 1][:k - 1], mu[k][:k - 1]
|
| 77 |
+
for i in range(k + 1, m):
|
| 78 |
+
xi = mu[i][k]
|
| 79 |
+
mu[i][k] = mu[i][k - 1] - nu * xi
|
| 80 |
+
mu[i][k - 1] = mu[k][k - 1] * mu[i][k] + xi
|
| 81 |
+
if return_transform:
|
| 82 |
+
T[k], T[k - 1] = T[k - 1], T[k]
|
| 83 |
+
k = max(k - 1, 1)
|
| 84 |
+
assert all(lovasz_condition(i) for i in range(1, m))
|
| 85 |
+
assert all(mu_small(i, j) for i in range(m) for j in range(i))
|
| 86 |
+
return y, T
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def ddm_lll(x, delta=QQ(3, 4)):
|
| 90 |
+
return _ddm_lll(x, delta=delta, return_transform=False)[0]
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def ddm_lll_transform(x, delta=QQ(3, 4)):
|
| 94 |
+
return _ddm_lll(x, delta=delta, return_transform=True)
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/normalforms.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''Functions returning normal forms of matrices'''
|
| 2 |
+
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
|
| 5 |
+
from .domainmatrix import DomainMatrix
|
| 6 |
+
from .exceptions import DMDomainError, DMShapeError
|
| 7 |
+
from sympy.ntheory.modular import symmetric_residue
|
| 8 |
+
from sympy.polys.domains import QQ, ZZ
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
# TODO (future work):
|
| 12 |
+
# There are faster algorithms for Smith and Hermite normal forms, which
|
| 13 |
+
# we should implement. See e.g. the Kannan-Bachem algorithm:
|
| 14 |
+
# <https://www.researchgate.net/publication/220617516_Polynomial_Algorithms_for_Computing_the_Smith_and_Hermite_Normal_Forms_of_an_Integer_Matrix>
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def smith_normal_form(m):
|
| 18 |
+
'''
|
| 19 |
+
Return the Smith Normal Form of a matrix `m` over the ring `domain`.
|
| 20 |
+
This will only work if the ring is a principal ideal domain.
|
| 21 |
+
|
| 22 |
+
Examples
|
| 23 |
+
========
|
| 24 |
+
|
| 25 |
+
>>> from sympy import ZZ
|
| 26 |
+
>>> from sympy.polys.matrices import DomainMatrix
|
| 27 |
+
>>> from sympy.polys.matrices.normalforms import smith_normal_form
|
| 28 |
+
>>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)],
|
| 29 |
+
... [ZZ(3), ZZ(9), ZZ(6)],
|
| 30 |
+
... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ)
|
| 31 |
+
>>> print(smith_normal_form(m).to_Matrix())
|
| 32 |
+
Matrix([[1, 0, 0], [0, 10, 0], [0, 0, 30]])
|
| 33 |
+
|
| 34 |
+
'''
|
| 35 |
+
invs = invariant_factors(m)
|
| 36 |
+
smf = DomainMatrix.diag(invs, m.domain, m.shape)
|
| 37 |
+
return smf
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def is_smith_normal_form(m):
|
| 41 |
+
'''
|
| 42 |
+
Checks that the matrix is in Smith Normal Form
|
| 43 |
+
'''
|
| 44 |
+
domain = m.domain
|
| 45 |
+
shape = m.shape
|
| 46 |
+
zero = domain.zero
|
| 47 |
+
m = m.to_list()
|
| 48 |
+
|
| 49 |
+
for i in range(shape[0]):
|
| 50 |
+
for j in range(shape[1]):
|
| 51 |
+
if i == j:
|
| 52 |
+
continue
|
| 53 |
+
if not m[i][j] == zero:
|
| 54 |
+
return False
|
| 55 |
+
|
| 56 |
+
upper = min(shape[0], shape[1])
|
| 57 |
+
for i in range(1, upper):
|
| 58 |
+
if m[i-1][i-1] == zero:
|
| 59 |
+
if m[i][i] != zero:
|
| 60 |
+
return False
|
| 61 |
+
else:
|
| 62 |
+
r = domain.div(m[i][i], m[i-1][i-1])[1]
|
| 63 |
+
if r != zero:
|
| 64 |
+
return False
|
| 65 |
+
|
| 66 |
+
return True
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def add_columns(m, i, j, a, b, c, d):
|
| 70 |
+
# replace m[:, i] by a*m[:, i] + b*m[:, j]
|
| 71 |
+
# and m[:, j] by c*m[:, i] + d*m[:, j]
|
| 72 |
+
for k in range(len(m)):
|
| 73 |
+
e = m[k][i]
|
| 74 |
+
m[k][i] = a*e + b*m[k][j]
|
| 75 |
+
m[k][j] = c*e + d*m[k][j]
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def invariant_factors(m):
|
| 79 |
+
'''
|
| 80 |
+
Return the tuple of abelian invariants for a matrix `m`
|
| 81 |
+
(as in the Smith-Normal form)
|
| 82 |
+
|
| 83 |
+
References
|
| 84 |
+
==========
|
| 85 |
+
|
| 86 |
+
[1] https://en.wikipedia.org/wiki/Smith_normal_form#Algorithm
|
| 87 |
+
[2] https://web.archive.org/web/20200331143852/https://sierra.nmsu.edu/morandi/notes/SmithNormalForm.pdf
|
| 88 |
+
|
| 89 |
+
'''
|
| 90 |
+
domain = m.domain
|
| 91 |
+
shape = m.shape
|
| 92 |
+
m = m.to_list()
|
| 93 |
+
return _smith_normal_decomp(m, domain, shape=shape, full=False)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def smith_normal_decomp(m):
|
| 97 |
+
'''
|
| 98 |
+
Return the Smith-Normal form decomposition of matrix `m`.
|
| 99 |
+
|
| 100 |
+
Examples
|
| 101 |
+
========
|
| 102 |
+
|
| 103 |
+
>>> from sympy import ZZ
|
| 104 |
+
>>> from sympy.polys.matrices import DomainMatrix
|
| 105 |
+
>>> from sympy.polys.matrices.normalforms import smith_normal_decomp
|
| 106 |
+
>>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)],
|
| 107 |
+
... [ZZ(3), ZZ(9), ZZ(6)],
|
| 108 |
+
... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ)
|
| 109 |
+
>>> a, s, t = smith_normal_decomp(m)
|
| 110 |
+
>>> assert a == s * m * t
|
| 111 |
+
'''
|
| 112 |
+
domain = m.domain
|
| 113 |
+
rows, cols = shape = m.shape
|
| 114 |
+
m = m.to_list()
|
| 115 |
+
|
| 116 |
+
invs, s, t = _smith_normal_decomp(m, domain, shape=shape, full=True)
|
| 117 |
+
smf = DomainMatrix.diag(invs, domain, shape).to_dense()
|
| 118 |
+
|
| 119 |
+
s = DomainMatrix(s, domain=domain, shape=(rows, rows))
|
| 120 |
+
t = DomainMatrix(t, domain=domain, shape=(cols, cols))
|
| 121 |
+
return smf, s, t
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def _smith_normal_decomp(m, domain, shape, full):
|
| 125 |
+
'''
|
| 126 |
+
Return the tuple of abelian invariants for a matrix `m`
|
| 127 |
+
(as in the Smith-Normal form). If `full=True` then invertible matrices
|
| 128 |
+
``s, t`` such that the product ``s, m, t`` is the Smith Normal Form
|
| 129 |
+
are also returned.
|
| 130 |
+
'''
|
| 131 |
+
if not domain.is_PID:
|
| 132 |
+
msg = f"The matrix entries must be over a principal ideal domain, but got {domain}"
|
| 133 |
+
raise ValueError(msg)
|
| 134 |
+
|
| 135 |
+
rows, cols = shape
|
| 136 |
+
zero = domain.zero
|
| 137 |
+
one = domain.one
|
| 138 |
+
|
| 139 |
+
def eye(n):
|
| 140 |
+
return [[one if i == j else zero for i in range(n)] for j in range(n)]
|
| 141 |
+
|
| 142 |
+
if 0 in shape:
|
| 143 |
+
if full:
|
| 144 |
+
return (), eye(rows), eye(cols)
|
| 145 |
+
else:
|
| 146 |
+
return ()
|
| 147 |
+
|
| 148 |
+
if full:
|
| 149 |
+
s = eye(rows)
|
| 150 |
+
t = eye(cols)
|
| 151 |
+
|
| 152 |
+
def add_rows(m, i, j, a, b, c, d):
|
| 153 |
+
# replace m[i, :] by a*m[i, :] + b*m[j, :]
|
| 154 |
+
# and m[j, :] by c*m[i, :] + d*m[j, :]
|
| 155 |
+
for k in range(len(m[0])):
|
| 156 |
+
e = m[i][k]
|
| 157 |
+
m[i][k] = a*e + b*m[j][k]
|
| 158 |
+
m[j][k] = c*e + d*m[j][k]
|
| 159 |
+
|
| 160 |
+
def clear_column():
|
| 161 |
+
# make m[1:, 0] zero by row and column operations
|
| 162 |
+
pivot = m[0][0]
|
| 163 |
+
for j in range(1, rows):
|
| 164 |
+
if m[j][0] == zero:
|
| 165 |
+
continue
|
| 166 |
+
d, r = domain.div(m[j][0], pivot)
|
| 167 |
+
if r == zero:
|
| 168 |
+
add_rows(m, 0, j, 1, 0, -d, 1)
|
| 169 |
+
if full:
|
| 170 |
+
add_rows(s, 0, j, 1, 0, -d, 1)
|
| 171 |
+
else:
|
| 172 |
+
a, b, g = domain.gcdex(pivot, m[j][0])
|
| 173 |
+
d_0 = domain.exquo(m[j][0], g)
|
| 174 |
+
d_j = domain.exquo(pivot, g)
|
| 175 |
+
add_rows(m, 0, j, a, b, d_0, -d_j)
|
| 176 |
+
if full:
|
| 177 |
+
add_rows(s, 0, j, a, b, d_0, -d_j)
|
| 178 |
+
pivot = g
|
| 179 |
+
|
| 180 |
+
def clear_row():
|
| 181 |
+
# make m[0, 1:] zero by row and column operations
|
| 182 |
+
pivot = m[0][0]
|
| 183 |
+
for j in range(1, cols):
|
| 184 |
+
if m[0][j] == zero:
|
| 185 |
+
continue
|
| 186 |
+
d, r = domain.div(m[0][j], pivot)
|
| 187 |
+
if r == zero:
|
| 188 |
+
add_columns(m, 0, j, 1, 0, -d, 1)
|
| 189 |
+
if full:
|
| 190 |
+
add_columns(t, 0, j, 1, 0, -d, 1)
|
| 191 |
+
else:
|
| 192 |
+
a, b, g = domain.gcdex(pivot, m[0][j])
|
| 193 |
+
d_0 = domain.exquo(m[0][j], g)
|
| 194 |
+
d_j = domain.exquo(pivot, g)
|
| 195 |
+
add_columns(m, 0, j, a, b, d_0, -d_j)
|
| 196 |
+
if full:
|
| 197 |
+
add_columns(t, 0, j, a, b, d_0, -d_j)
|
| 198 |
+
pivot = g
|
| 199 |
+
|
| 200 |
+
# permute the rows and columns until m[0,0] is non-zero if possible
|
| 201 |
+
ind = [i for i in range(rows) if m[i][0] != zero]
|
| 202 |
+
if ind and ind[0] != zero:
|
| 203 |
+
m[0], m[ind[0]] = m[ind[0]], m[0]
|
| 204 |
+
if full:
|
| 205 |
+
s[0], s[ind[0]] = s[ind[0]], s[0]
|
| 206 |
+
else:
|
| 207 |
+
ind = [j for j in range(cols) if m[0][j] != zero]
|
| 208 |
+
if ind and ind[0] != zero:
|
| 209 |
+
for row in m:
|
| 210 |
+
row[0], row[ind[0]] = row[ind[0]], row[0]
|
| 211 |
+
if full:
|
| 212 |
+
for row in t:
|
| 213 |
+
row[0], row[ind[0]] = row[ind[0]], row[0]
|
| 214 |
+
|
| 215 |
+
# make the first row and column except m[0,0] zero
|
| 216 |
+
while (any(m[0][i] != zero for i in range(1,cols)) or
|
| 217 |
+
any(m[i][0] != zero for i in range(1,rows))):
|
| 218 |
+
clear_column()
|
| 219 |
+
clear_row()
|
| 220 |
+
|
| 221 |
+
def to_domain_matrix(m):
|
| 222 |
+
return DomainMatrix(m, shape=(len(m), len(m[0])), domain=domain)
|
| 223 |
+
|
| 224 |
+
if m[0][0] != 0:
|
| 225 |
+
c = domain.canonical_unit(m[0][0])
|
| 226 |
+
if domain.is_Field:
|
| 227 |
+
c = 1 / m[0][0]
|
| 228 |
+
if c != domain.one:
|
| 229 |
+
m[0][0] *= c
|
| 230 |
+
if full:
|
| 231 |
+
s[0] = [elem * c for elem in s[0]]
|
| 232 |
+
|
| 233 |
+
if 1 in shape:
|
| 234 |
+
invs = ()
|
| 235 |
+
else:
|
| 236 |
+
lower_right = [r[1:] for r in m[1:]]
|
| 237 |
+
ret = _smith_normal_decomp(lower_right, domain,
|
| 238 |
+
shape=(rows - 1, cols - 1), full=full)
|
| 239 |
+
if full:
|
| 240 |
+
invs, s_small, t_small = ret
|
| 241 |
+
s2 = [[1] + [0]*(rows-1)] + [[0] + row for row in s_small]
|
| 242 |
+
t2 = [[1] + [0]*(cols-1)] + [[0] + row for row in t_small]
|
| 243 |
+
s, s2, t, t2 = list(map(to_domain_matrix, [s, s2, t, t2]))
|
| 244 |
+
s = s2 * s
|
| 245 |
+
t = t * t2
|
| 246 |
+
s = s.to_list()
|
| 247 |
+
t = t.to_list()
|
| 248 |
+
else:
|
| 249 |
+
invs = ret
|
| 250 |
+
|
| 251 |
+
if m[0][0]:
|
| 252 |
+
result = [m[0][0]]
|
| 253 |
+
result.extend(invs)
|
| 254 |
+
# in case m[0] doesn't divide the invariants of the rest of the matrix
|
| 255 |
+
for i in range(len(result)-1):
|
| 256 |
+
a, b = result[i], result[i+1]
|
| 257 |
+
if b and domain.div(b, a)[1] != zero:
|
| 258 |
+
if full:
|
| 259 |
+
x, y, d = domain.gcdex(a, b)
|
| 260 |
+
else:
|
| 261 |
+
d = domain.gcd(a, b)
|
| 262 |
+
|
| 263 |
+
alpha = domain.div(a, d)[0]
|
| 264 |
+
if full:
|
| 265 |
+
beta = domain.div(b, d)[0]
|
| 266 |
+
add_rows(s, i, i + 1, 1, 0, x, 1)
|
| 267 |
+
add_columns(t, i, i + 1, 1, y, 0, 1)
|
| 268 |
+
add_rows(s, i, i + 1, 1, -alpha, 0, 1)
|
| 269 |
+
add_columns(t, i, i + 1, 1, 0, -beta, 1)
|
| 270 |
+
add_rows(s, i, i + 1, 0, 1, -1, 0)
|
| 271 |
+
|
| 272 |
+
result[i+1] = b * alpha
|
| 273 |
+
result[i] = d
|
| 274 |
+
else:
|
| 275 |
+
break
|
| 276 |
+
else:
|
| 277 |
+
if full:
|
| 278 |
+
if rows > 1:
|
| 279 |
+
s = s[1:] + [s[0]]
|
| 280 |
+
if cols > 1:
|
| 281 |
+
t = [row[1:] + [row[0]] for row in t]
|
| 282 |
+
result = invs + (m[0][0],)
|
| 283 |
+
|
| 284 |
+
if full:
|
| 285 |
+
return tuple(result), s, t
|
| 286 |
+
else:
|
| 287 |
+
return tuple(result)
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def _gcdex(a, b):
|
| 291 |
+
r"""
|
| 292 |
+
This supports the functions that compute Hermite Normal Form.
|
| 293 |
+
|
| 294 |
+
Explanation
|
| 295 |
+
===========
|
| 296 |
+
|
| 297 |
+
Let x, y be the coefficients returned by the extended Euclidean
|
| 298 |
+
Algorithm, so that x*a + y*b = g. In the algorithms for computing HNF,
|
| 299 |
+
it is critical that x, y not only satisfy the condition of being small
|
| 300 |
+
in magnitude -- namely that |x| <= |b|/g, |y| <- |a|/g -- but also that
|
| 301 |
+
y == 0 when a | b.
|
| 302 |
+
|
| 303 |
+
"""
|
| 304 |
+
x, y, g = ZZ.gcdex(a, b)
|
| 305 |
+
if a != 0 and b % a == 0:
|
| 306 |
+
y = 0
|
| 307 |
+
x = -1 if a < 0 else 1
|
| 308 |
+
return x, y, g
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def _hermite_normal_form(A):
|
| 312 |
+
r"""
|
| 313 |
+
Compute the Hermite Normal Form of DomainMatrix *A* over :ref:`ZZ`.
|
| 314 |
+
|
| 315 |
+
Parameters
|
| 316 |
+
==========
|
| 317 |
+
|
| 318 |
+
A : :py:class:`~.DomainMatrix` over domain :ref:`ZZ`.
|
| 319 |
+
|
| 320 |
+
Returns
|
| 321 |
+
=======
|
| 322 |
+
|
| 323 |
+
:py:class:`~.DomainMatrix`
|
| 324 |
+
The HNF of matrix *A*.
|
| 325 |
+
|
| 326 |
+
Raises
|
| 327 |
+
======
|
| 328 |
+
|
| 329 |
+
DMDomainError
|
| 330 |
+
If the domain of the matrix is not :ref:`ZZ`.
|
| 331 |
+
|
| 332 |
+
References
|
| 333 |
+
==========
|
| 334 |
+
|
| 335 |
+
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
|
| 336 |
+
(See Algorithm 2.4.5.)
|
| 337 |
+
|
| 338 |
+
"""
|
| 339 |
+
if not A.domain.is_ZZ:
|
| 340 |
+
raise DMDomainError('Matrix must be over domain ZZ.')
|
| 341 |
+
# We work one row at a time, starting from the bottom row, and working our
|
| 342 |
+
# way up.
|
| 343 |
+
m, n = A.shape
|
| 344 |
+
A = A.to_ddm().copy()
|
| 345 |
+
# Our goal is to put pivot entries in the rightmost columns.
|
| 346 |
+
# Invariant: Before processing each row, k should be the index of the
|
| 347 |
+
# leftmost column in which we have so far put a pivot.
|
| 348 |
+
k = n
|
| 349 |
+
for i in range(m - 1, -1, -1):
|
| 350 |
+
if k == 0:
|
| 351 |
+
# This case can arise when n < m and we've already found n pivots.
|
| 352 |
+
# We don't need to consider any more rows, because this is already
|
| 353 |
+
# the maximum possible number of pivots.
|
| 354 |
+
break
|
| 355 |
+
k -= 1
|
| 356 |
+
# k now points to the column in which we want to put a pivot.
|
| 357 |
+
# We want zeros in all entries to the left of the pivot column.
|
| 358 |
+
for j in range(k - 1, -1, -1):
|
| 359 |
+
if A[i][j] != 0:
|
| 360 |
+
# Replace cols j, k by lin combs of these cols such that, in row i,
|
| 361 |
+
# col j has 0, while col k has the gcd of their row i entries. Note
|
| 362 |
+
# that this ensures a nonzero entry in col k.
|
| 363 |
+
u, v, d = _gcdex(A[i][k], A[i][j])
|
| 364 |
+
r, s = A[i][k] // d, A[i][j] // d
|
| 365 |
+
add_columns(A, k, j, u, v, -s, r)
|
| 366 |
+
b = A[i][k]
|
| 367 |
+
# Do not want the pivot entry to be negative.
|
| 368 |
+
if b < 0:
|
| 369 |
+
add_columns(A, k, k, -1, 0, -1, 0)
|
| 370 |
+
b = -b
|
| 371 |
+
# The pivot entry will be 0 iff the row was 0 from the pivot col all the
|
| 372 |
+
# way to the left. In this case, we are still working on the same pivot
|
| 373 |
+
# col for the next row. Therefore:
|
| 374 |
+
if b == 0:
|
| 375 |
+
k += 1
|
| 376 |
+
# If the pivot entry is nonzero, then we want to reduce all entries to its
|
| 377 |
+
# right in the sense of the division algorithm, i.e. make them all remainders
|
| 378 |
+
# w.r.t. the pivot as divisor.
|
| 379 |
+
else:
|
| 380 |
+
for j in range(k + 1, n):
|
| 381 |
+
q = A[i][j] // b
|
| 382 |
+
add_columns(A, j, k, 1, -q, 0, 1)
|
| 383 |
+
# Finally, the HNF consists of those columns of A in which we succeeded in making
|
| 384 |
+
# a nonzero pivot.
|
| 385 |
+
return DomainMatrix.from_rep(A.to_dfm_or_ddm())[:, k:]
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def _hermite_normal_form_modulo_D(A, D):
|
| 389 |
+
r"""
|
| 390 |
+
Perform the mod *D* Hermite Normal Form reduction algorithm on
|
| 391 |
+
:py:class:`~.DomainMatrix` *A*.
|
| 392 |
+
|
| 393 |
+
Explanation
|
| 394 |
+
===========
|
| 395 |
+
|
| 396 |
+
If *A* is an $m \times n$ matrix of rank $m$, having Hermite Normal Form
|
| 397 |
+
$W$, and if *D* is any positive integer known in advance to be a multiple
|
| 398 |
+
of $\det(W)$, then the HNF of *A* can be computed by an algorithm that
|
| 399 |
+
works mod *D* in order to prevent coefficient explosion.
|
| 400 |
+
|
| 401 |
+
Parameters
|
| 402 |
+
==========
|
| 403 |
+
|
| 404 |
+
A : :py:class:`~.DomainMatrix` over :ref:`ZZ`
|
| 405 |
+
$m \times n$ matrix, having rank $m$.
|
| 406 |
+
D : :ref:`ZZ`
|
| 407 |
+
Positive integer, known to be a multiple of the determinant of the
|
| 408 |
+
HNF of *A*.
|
| 409 |
+
|
| 410 |
+
Returns
|
| 411 |
+
=======
|
| 412 |
+
|
| 413 |
+
:py:class:`~.DomainMatrix`
|
| 414 |
+
The HNF of matrix *A*.
|
| 415 |
+
|
| 416 |
+
Raises
|
| 417 |
+
======
|
| 418 |
+
|
| 419 |
+
DMDomainError
|
| 420 |
+
If the domain of the matrix is not :ref:`ZZ`, or
|
| 421 |
+
if *D* is given but is not in :ref:`ZZ`.
|
| 422 |
+
|
| 423 |
+
DMShapeError
|
| 424 |
+
If the matrix has more rows than columns.
|
| 425 |
+
|
| 426 |
+
References
|
| 427 |
+
==========
|
| 428 |
+
|
| 429 |
+
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
|
| 430 |
+
(See Algorithm 2.4.8.)
|
| 431 |
+
|
| 432 |
+
"""
|
| 433 |
+
if not A.domain.is_ZZ:
|
| 434 |
+
raise DMDomainError('Matrix must be over domain ZZ.')
|
| 435 |
+
if not ZZ.of_type(D) or D < 1:
|
| 436 |
+
raise DMDomainError('Modulus D must be positive element of domain ZZ.')
|
| 437 |
+
|
| 438 |
+
def add_columns_mod_R(m, R, i, j, a, b, c, d):
|
| 439 |
+
# replace m[:, i] by (a*m[:, i] + b*m[:, j]) % R
|
| 440 |
+
# and m[:, j] by (c*m[:, i] + d*m[:, j]) % R
|
| 441 |
+
for k in range(len(m)):
|
| 442 |
+
e = m[k][i]
|
| 443 |
+
m[k][i] = symmetric_residue((a * e + b * m[k][j]) % R, R)
|
| 444 |
+
m[k][j] = symmetric_residue((c * e + d * m[k][j]) % R, R)
|
| 445 |
+
|
| 446 |
+
W = defaultdict(dict)
|
| 447 |
+
|
| 448 |
+
m, n = A.shape
|
| 449 |
+
if n < m:
|
| 450 |
+
raise DMShapeError('Matrix must have at least as many columns as rows.')
|
| 451 |
+
A = A.to_list()
|
| 452 |
+
k = n
|
| 453 |
+
R = D
|
| 454 |
+
for i in range(m - 1, -1, -1):
|
| 455 |
+
k -= 1
|
| 456 |
+
for j in range(k - 1, -1, -1):
|
| 457 |
+
if A[i][j] != 0:
|
| 458 |
+
u, v, d = _gcdex(A[i][k], A[i][j])
|
| 459 |
+
r, s = A[i][k] // d, A[i][j] // d
|
| 460 |
+
add_columns_mod_R(A, R, k, j, u, v, -s, r)
|
| 461 |
+
b = A[i][k]
|
| 462 |
+
if b == 0:
|
| 463 |
+
A[i][k] = b = R
|
| 464 |
+
u, v, d = _gcdex(b, R)
|
| 465 |
+
for ii in range(m):
|
| 466 |
+
W[ii][i] = u*A[ii][k] % R
|
| 467 |
+
if W[i][i] == 0:
|
| 468 |
+
W[i][i] = R
|
| 469 |
+
for j in range(i + 1, m):
|
| 470 |
+
q = W[i][j] // W[i][i]
|
| 471 |
+
add_columns(W, j, i, 1, -q, 0, 1)
|
| 472 |
+
R //= d
|
| 473 |
+
return DomainMatrix(W, (m, m), ZZ).to_dense()
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
def hermite_normal_form(A, *, D=None, check_rank=False):
|
| 477 |
+
r"""
|
| 478 |
+
Compute the Hermite Normal Form of :py:class:`~.DomainMatrix` *A* over
|
| 479 |
+
:ref:`ZZ`.
|
| 480 |
+
|
| 481 |
+
Examples
|
| 482 |
+
========
|
| 483 |
+
|
| 484 |
+
>>> from sympy import ZZ
|
| 485 |
+
>>> from sympy.polys.matrices import DomainMatrix
|
| 486 |
+
>>> from sympy.polys.matrices.normalforms import hermite_normal_form
|
| 487 |
+
>>> m = DomainMatrix([[ZZ(12), ZZ(6), ZZ(4)],
|
| 488 |
+
... [ZZ(3), ZZ(9), ZZ(6)],
|
| 489 |
+
... [ZZ(2), ZZ(16), ZZ(14)]], (3, 3), ZZ)
|
| 490 |
+
>>> print(hermite_normal_form(m).to_Matrix())
|
| 491 |
+
Matrix([[10, 0, 2], [0, 15, 3], [0, 0, 2]])
|
| 492 |
+
|
| 493 |
+
Parameters
|
| 494 |
+
==========
|
| 495 |
+
|
| 496 |
+
A : $m \times n$ ``DomainMatrix`` over :ref:`ZZ`.
|
| 497 |
+
|
| 498 |
+
D : :ref:`ZZ`, optional
|
| 499 |
+
Let $W$ be the HNF of *A*. If known in advance, a positive integer *D*
|
| 500 |
+
being any multiple of $\det(W)$ may be provided. In this case, if *A*
|
| 501 |
+
also has rank $m$, then we may use an alternative algorithm that works
|
| 502 |
+
mod *D* in order to prevent coefficient explosion.
|
| 503 |
+
|
| 504 |
+
check_rank : boolean, optional (default=False)
|
| 505 |
+
The basic assumption is that, if you pass a value for *D*, then
|
| 506 |
+
you already believe that *A* has rank $m$, so we do not waste time
|
| 507 |
+
checking it for you. If you do want this to be checked (and the
|
| 508 |
+
ordinary, non-modulo *D* algorithm to be used if the check fails), then
|
| 509 |
+
set *check_rank* to ``True``.
|
| 510 |
+
|
| 511 |
+
Returns
|
| 512 |
+
=======
|
| 513 |
+
|
| 514 |
+
:py:class:`~.DomainMatrix`
|
| 515 |
+
The HNF of matrix *A*.
|
| 516 |
+
|
| 517 |
+
Raises
|
| 518 |
+
======
|
| 519 |
+
|
| 520 |
+
DMDomainError
|
| 521 |
+
If the domain of the matrix is not :ref:`ZZ`, or
|
| 522 |
+
if *D* is given but is not in :ref:`ZZ`.
|
| 523 |
+
|
| 524 |
+
DMShapeError
|
| 525 |
+
If the mod *D* algorithm is used but the matrix has more rows than
|
| 526 |
+
columns.
|
| 527 |
+
|
| 528 |
+
References
|
| 529 |
+
==========
|
| 530 |
+
|
| 531 |
+
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
|
| 532 |
+
(See Algorithms 2.4.5 and 2.4.8.)
|
| 533 |
+
|
| 534 |
+
"""
|
| 535 |
+
if not A.domain.is_ZZ:
|
| 536 |
+
raise DMDomainError('Matrix must be over domain ZZ.')
|
| 537 |
+
if D is not None and (not check_rank or A.convert_to(QQ).rank() == A.shape[0]):
|
| 538 |
+
return _hermite_normal_form_modulo_D(A, D)
|
| 539 |
+
else:
|
| 540 |
+
return _hermite_normal_form(A)
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/rref.py
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Algorithms for computing the reduced row echelon form of a matrix.
|
| 2 |
+
#
|
| 3 |
+
# We need to choose carefully which algorithms to use depending on the domain,
|
| 4 |
+
# shape, and sparsity of the matrix as well as things like the bit count in the
|
| 5 |
+
# case of ZZ or QQ. This is important because the algorithms have different
|
| 6 |
+
# performance characteristics in the extremes of dense vs sparse.
|
| 7 |
+
#
|
| 8 |
+
# In all cases we use the sparse implementations but we need to choose between
|
| 9 |
+
# Gauss-Jordan elimination with division and fraction-free Gauss-Jordan
|
| 10 |
+
# elimination. For very sparse matrices over ZZ with low bit counts it is
|
| 11 |
+
# asymptotically faster to use Gauss-Jordan elimination with division. For
|
| 12 |
+
# dense matrices with high bit counts it is asymptotically faster to use
|
| 13 |
+
# fraction-free Gauss-Jordan.
|
| 14 |
+
#
|
| 15 |
+
# The most important thing is to get the extreme cases right because it can
|
| 16 |
+
# make a big difference. In between the extremes though we have to make a
|
| 17 |
+
# choice and here we use empirically determined thresholds based on timings
|
| 18 |
+
# with random sparse matrices.
|
| 19 |
+
#
|
| 20 |
+
# In the case of QQ we have to consider the denominators as well. If the
|
| 21 |
+
# denominators are small then it is faster to clear them and use fraction-free
|
| 22 |
+
# Gauss-Jordan over ZZ. If the denominators are large then it is faster to use
|
| 23 |
+
# Gauss-Jordan elimination with division over QQ.
|
| 24 |
+
#
|
| 25 |
+
# Timings for the various algorithms can be found at
|
| 26 |
+
#
|
| 27 |
+
# https://github.com/sympy/sympy/issues/25410
|
| 28 |
+
# https://github.com/sympy/sympy/pull/25443
|
| 29 |
+
|
| 30 |
+
from sympy.polys.domains import ZZ
|
| 31 |
+
|
| 32 |
+
from sympy.polys.matrices.sdm import SDM, sdm_irref, sdm_rref_den
|
| 33 |
+
from sympy.polys.matrices.ddm import DDM
|
| 34 |
+
from sympy.polys.matrices.dense import ddm_irref, ddm_irref_den
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _dm_rref(M, *, method='auto'):
|
| 38 |
+
"""
|
| 39 |
+
Compute the reduced row echelon form of a ``DomainMatrix``.
|
| 40 |
+
|
| 41 |
+
This function is the implementation of :meth:`DomainMatrix.rref`.
|
| 42 |
+
|
| 43 |
+
Chooses the best algorithm depending on the domain, shape, and sparsity of
|
| 44 |
+
the matrix as well as things like the bit count in the case of :ref:`ZZ` or
|
| 45 |
+
:ref:`QQ`. The result is returned over the field associated with the domain
|
| 46 |
+
of the Matrix.
|
| 47 |
+
|
| 48 |
+
See Also
|
| 49 |
+
========
|
| 50 |
+
|
| 51 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.rref
|
| 52 |
+
The ``DomainMatrix`` method that calls this function.
|
| 53 |
+
sympy.polys.matrices.rref._dm_rref_den
|
| 54 |
+
Alternative function for computing RREF with denominator.
|
| 55 |
+
"""
|
| 56 |
+
method, use_fmt = _dm_rref_choose_method(M, method, denominator=False)
|
| 57 |
+
|
| 58 |
+
M, old_fmt = _dm_to_fmt(M, use_fmt)
|
| 59 |
+
|
| 60 |
+
if method == 'GJ':
|
| 61 |
+
# Use Gauss-Jordan with division over the associated field.
|
| 62 |
+
Mf = _to_field(M)
|
| 63 |
+
M_rref, pivots = _dm_rref_GJ(Mf)
|
| 64 |
+
|
| 65 |
+
elif method == 'FF':
|
| 66 |
+
# Use fraction-free GJ over the current domain.
|
| 67 |
+
M_rref_f, den, pivots = _dm_rref_den_FF(M)
|
| 68 |
+
M_rref = _to_field(M_rref_f) / den
|
| 69 |
+
|
| 70 |
+
elif method == 'CD':
|
| 71 |
+
# Clear denominators and use fraction-free GJ in the associated ring.
|
| 72 |
+
_, Mr = M.clear_denoms_rowwise(convert=True)
|
| 73 |
+
M_rref_f, den, pivots = _dm_rref_den_FF(Mr)
|
| 74 |
+
M_rref = _to_field(M_rref_f) / den
|
| 75 |
+
|
| 76 |
+
else:
|
| 77 |
+
raise ValueError(f"Unknown method for rref: {method}")
|
| 78 |
+
|
| 79 |
+
M_rref, _ = _dm_to_fmt(M_rref, old_fmt)
|
| 80 |
+
|
| 81 |
+
# Invariants:
|
| 82 |
+
# - M_rref is in the same format (sparse or dense) as the input matrix.
|
| 83 |
+
# - M_rref is in the associated field domain and any denominator was
|
| 84 |
+
# divided in (so is implicitly 1 now).
|
| 85 |
+
|
| 86 |
+
return M_rref, pivots
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _dm_rref_den(M, *, keep_domain=True, method='auto'):
|
| 90 |
+
"""
|
| 91 |
+
Compute the reduced row echelon form of a ``DomainMatrix`` with denominator.
|
| 92 |
+
|
| 93 |
+
This function is the implementation of :meth:`DomainMatrix.rref_den`.
|
| 94 |
+
|
| 95 |
+
Chooses the best algorithm depending on the domain, shape, and sparsity of
|
| 96 |
+
the matrix as well as things like the bit count in the case of :ref:`ZZ` or
|
| 97 |
+
:ref:`QQ`. The result is returned over the same domain as the input matrix
|
| 98 |
+
unless ``keep_domain=False`` in which case the result might be over an
|
| 99 |
+
associated ring or field domain.
|
| 100 |
+
|
| 101 |
+
See Also
|
| 102 |
+
========
|
| 103 |
+
|
| 104 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
|
| 105 |
+
The ``DomainMatrix`` method that calls this function.
|
| 106 |
+
sympy.polys.matrices.rref._dm_rref
|
| 107 |
+
Alternative function for computing RREF without denominator.
|
| 108 |
+
"""
|
| 109 |
+
method, use_fmt = _dm_rref_choose_method(M, method, denominator=True)
|
| 110 |
+
|
| 111 |
+
M, old_fmt = _dm_to_fmt(M, use_fmt)
|
| 112 |
+
|
| 113 |
+
if method == 'FF':
|
| 114 |
+
# Use fraction-free GJ over the current domain.
|
| 115 |
+
M_rref, den, pivots = _dm_rref_den_FF(M)
|
| 116 |
+
|
| 117 |
+
elif method == 'GJ':
|
| 118 |
+
# Use Gauss-Jordan with division over the associated field.
|
| 119 |
+
M_rref_f, pivots = _dm_rref_GJ(_to_field(M))
|
| 120 |
+
|
| 121 |
+
# Convert back to the ring?
|
| 122 |
+
if keep_domain and M_rref_f.domain != M.domain:
|
| 123 |
+
_, M_rref = M_rref_f.clear_denoms(convert=True)
|
| 124 |
+
|
| 125 |
+
if pivots:
|
| 126 |
+
den = M_rref[0, pivots[0]].element
|
| 127 |
+
else:
|
| 128 |
+
den = M_rref.domain.one
|
| 129 |
+
else:
|
| 130 |
+
# Possibly an associated field
|
| 131 |
+
M_rref = M_rref_f
|
| 132 |
+
den = M_rref.domain.one
|
| 133 |
+
|
| 134 |
+
elif method == 'CD':
|
| 135 |
+
# Clear denominators and use fraction-free GJ in the associated ring.
|
| 136 |
+
_, Mr = M.clear_denoms_rowwise(convert=True)
|
| 137 |
+
|
| 138 |
+
M_rref_r, den, pivots = _dm_rref_den_FF(Mr)
|
| 139 |
+
|
| 140 |
+
if keep_domain and M_rref_r.domain != M.domain:
|
| 141 |
+
# Convert back to the field
|
| 142 |
+
M_rref = _to_field(M_rref_r) / den
|
| 143 |
+
den = M.domain.one
|
| 144 |
+
else:
|
| 145 |
+
# Possibly an associated ring
|
| 146 |
+
M_rref = M_rref_r
|
| 147 |
+
|
| 148 |
+
if pivots:
|
| 149 |
+
den = M_rref[0, pivots[0]].element
|
| 150 |
+
else:
|
| 151 |
+
den = M_rref.domain.one
|
| 152 |
+
else:
|
| 153 |
+
raise ValueError(f"Unknown method for rref: {method}")
|
| 154 |
+
|
| 155 |
+
M_rref, _ = _dm_to_fmt(M_rref, old_fmt)
|
| 156 |
+
|
| 157 |
+
# Invariants:
|
| 158 |
+
# - M_rref is in the same format (sparse or dense) as the input matrix.
|
| 159 |
+
# - If keep_domain=True then M_rref and den are in the same domain as the
|
| 160 |
+
# input matrix
|
| 161 |
+
# - If keep_domain=False then M_rref might be in an associated ring or
|
| 162 |
+
# field domain but den is always in the same domain as M_rref.
|
| 163 |
+
|
| 164 |
+
return M_rref, den, pivots
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _dm_to_fmt(M, fmt):
|
| 168 |
+
"""Convert a matrix to the given format and return the old format."""
|
| 169 |
+
old_fmt = M.rep.fmt
|
| 170 |
+
if old_fmt == fmt:
|
| 171 |
+
pass
|
| 172 |
+
elif fmt == 'dense':
|
| 173 |
+
M = M.to_dense()
|
| 174 |
+
elif fmt == 'sparse':
|
| 175 |
+
M = M.to_sparse()
|
| 176 |
+
else:
|
| 177 |
+
raise ValueError(f'Unknown format: {fmt}') # pragma: no cover
|
| 178 |
+
return M, old_fmt
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# These are the four basic implementations that we want to choose between:
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def _dm_rref_GJ(M):
|
| 185 |
+
"""Compute RREF using Gauss-Jordan elimination with division."""
|
| 186 |
+
if M.rep.fmt == 'sparse':
|
| 187 |
+
return _dm_rref_GJ_sparse(M)
|
| 188 |
+
else:
|
| 189 |
+
return _dm_rref_GJ_dense(M)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _dm_rref_den_FF(M):
|
| 193 |
+
"""Compute RREF using fraction-free Gauss-Jordan elimination."""
|
| 194 |
+
if M.rep.fmt == 'sparse':
|
| 195 |
+
return _dm_rref_den_FF_sparse(M)
|
| 196 |
+
else:
|
| 197 |
+
return _dm_rref_den_FF_dense(M)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def _dm_rref_GJ_sparse(M):
|
| 201 |
+
"""Compute RREF using sparse Gauss-Jordan elimination with division."""
|
| 202 |
+
M_rref_d, pivots, _ = sdm_irref(M.rep)
|
| 203 |
+
M_rref_sdm = SDM(M_rref_d, M.shape, M.domain)
|
| 204 |
+
pivots = tuple(pivots)
|
| 205 |
+
return M.from_rep(M_rref_sdm), pivots
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def _dm_rref_GJ_dense(M):
|
| 209 |
+
"""Compute RREF using dense Gauss-Jordan elimination with division."""
|
| 210 |
+
partial_pivot = M.domain.is_RR or M.domain.is_CC
|
| 211 |
+
ddm = M.rep.to_ddm().copy()
|
| 212 |
+
pivots = ddm_irref(ddm, _partial_pivot=partial_pivot)
|
| 213 |
+
M_rref_ddm = DDM(ddm, M.shape, M.domain)
|
| 214 |
+
pivots = tuple(pivots)
|
| 215 |
+
return M.from_rep(M_rref_ddm.to_dfm_or_ddm()), pivots
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def _dm_rref_den_FF_sparse(M):
|
| 219 |
+
"""Compute RREF using sparse fraction-free Gauss-Jordan elimination."""
|
| 220 |
+
M_rref_d, den, pivots = sdm_rref_den(M.rep, M.domain)
|
| 221 |
+
M_rref_sdm = SDM(M_rref_d, M.shape, M.domain)
|
| 222 |
+
pivots = tuple(pivots)
|
| 223 |
+
return M.from_rep(M_rref_sdm), den, pivots
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def _dm_rref_den_FF_dense(M):
|
| 227 |
+
"""Compute RREF using sparse fraction-free Gauss-Jordan elimination."""
|
| 228 |
+
ddm = M.rep.to_ddm().copy()
|
| 229 |
+
den, pivots = ddm_irref_den(ddm, M.domain)
|
| 230 |
+
M_rref_ddm = DDM(ddm, M.shape, M.domain)
|
| 231 |
+
pivots = tuple(pivots)
|
| 232 |
+
return M.from_rep(M_rref_ddm.to_dfm_or_ddm()), den, pivots
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def _dm_rref_choose_method(M, method, *, denominator=False):
|
| 236 |
+
"""Choose the fastest method for computing RREF for M."""
|
| 237 |
+
|
| 238 |
+
if method != 'auto':
|
| 239 |
+
if method.endswith('_dense'):
|
| 240 |
+
method = method[:-len('_dense')]
|
| 241 |
+
use_fmt = 'dense'
|
| 242 |
+
else:
|
| 243 |
+
use_fmt = 'sparse'
|
| 244 |
+
|
| 245 |
+
else:
|
| 246 |
+
# The sparse implementations are always faster
|
| 247 |
+
use_fmt = 'sparse'
|
| 248 |
+
|
| 249 |
+
K = M.domain
|
| 250 |
+
|
| 251 |
+
if K.is_ZZ:
|
| 252 |
+
method = _dm_rref_choose_method_ZZ(M, denominator=denominator)
|
| 253 |
+
elif K.is_QQ:
|
| 254 |
+
method = _dm_rref_choose_method_QQ(M, denominator=denominator)
|
| 255 |
+
elif K.is_RR or K.is_CC:
|
| 256 |
+
# TODO: Add partial pivot support to the sparse implementations.
|
| 257 |
+
method = 'GJ'
|
| 258 |
+
use_fmt = 'dense'
|
| 259 |
+
elif K.is_EX and M.rep.fmt == 'dense' and not denominator:
|
| 260 |
+
# Do not switch to the sparse implementation for EX because the
|
| 261 |
+
# domain does not have proper canonicalization and the sparse
|
| 262 |
+
# implementation gives equivalent but non-identical results over EX
|
| 263 |
+
# from performing arithmetic in a different order. Specifically
|
| 264 |
+
# test_issue_23718 ends up getting a more complicated expression
|
| 265 |
+
# when using the sparse implementation. Probably the best fix for
|
| 266 |
+
# this is something else but for now we stick with the dense
|
| 267 |
+
# implementation for EX if the matrix is already dense.
|
| 268 |
+
method = 'GJ'
|
| 269 |
+
use_fmt = 'dense'
|
| 270 |
+
else:
|
| 271 |
+
# This is definitely suboptimal. More work is needed to determine
|
| 272 |
+
# the best method for computing RREF over different domains.
|
| 273 |
+
if denominator:
|
| 274 |
+
method = 'FF'
|
| 275 |
+
else:
|
| 276 |
+
method = 'GJ'
|
| 277 |
+
|
| 278 |
+
return method, use_fmt
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _dm_rref_choose_method_QQ(M, *, denominator=False):
|
| 282 |
+
"""Choose the fastest method for computing RREF over QQ."""
|
| 283 |
+
# The same sorts of considerations apply here as in the case of ZZ. Here
|
| 284 |
+
# though a new more significant consideration is what sort of denominators
|
| 285 |
+
# we have and what to do with them so we focus on that.
|
| 286 |
+
|
| 287 |
+
# First compute the density. This is the average number of non-zero entries
|
| 288 |
+
# per row but only counting rows that have at least one non-zero entry
|
| 289 |
+
# since RREF can ignore fully zero rows.
|
| 290 |
+
density, _, ncols = _dm_row_density(M)
|
| 291 |
+
|
| 292 |
+
# For sparse matrices use Gauss-Jordan elimination over QQ regardless.
|
| 293 |
+
if density < min(5, ncols/2):
|
| 294 |
+
return 'GJ'
|
| 295 |
+
|
| 296 |
+
# Compare the bit-length of the lcm of the denominators to the bit length
|
| 297 |
+
# of the numerators.
|
| 298 |
+
#
|
| 299 |
+
# The threshold here is empirical: we prefer rref over QQ if clearing
|
| 300 |
+
# denominators would result in a numerator matrix having 5x the bit size of
|
| 301 |
+
# the current numerators.
|
| 302 |
+
numers, denoms = _dm_QQ_numers_denoms(M)
|
| 303 |
+
numer_bits = max([n.bit_length() for n in numers], default=1)
|
| 304 |
+
|
| 305 |
+
denom_lcm = ZZ.one
|
| 306 |
+
for d in denoms:
|
| 307 |
+
denom_lcm = ZZ.lcm(denom_lcm, d)
|
| 308 |
+
if denom_lcm.bit_length() > 5*numer_bits:
|
| 309 |
+
return 'GJ'
|
| 310 |
+
|
| 311 |
+
# If we get here then the matrix is dense and the lcm of the denominators
|
| 312 |
+
# is not too large compared to the numerators. For particularly small
|
| 313 |
+
# denominators it is fastest just to clear them and use fraction-free
|
| 314 |
+
# Gauss-Jordan over ZZ. With very small denominators this is a little
|
| 315 |
+
# faster than using rref_den over QQ but there is an intermediate regime
|
| 316 |
+
# where rref_den over QQ is significantly faster. The small denominator
|
| 317 |
+
# case is probably very common because small fractions like 1/2 or 1/3 are
|
| 318 |
+
# often seen in user inputs.
|
| 319 |
+
|
| 320 |
+
if denom_lcm.bit_length() < 50:
|
| 321 |
+
return 'CD'
|
| 322 |
+
else:
|
| 323 |
+
return 'FF'
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def _dm_rref_choose_method_ZZ(M, *, denominator=False):
|
| 327 |
+
"""Choose the fastest method for computing RREF over ZZ."""
|
| 328 |
+
# In the extreme of very sparse matrices and low bit counts it is faster to
|
| 329 |
+
# use Gauss-Jordan elimination over QQ rather than fraction-free
|
| 330 |
+
# Gauss-Jordan over ZZ. In the opposite extreme of dense matrices and high
|
| 331 |
+
# bit counts it is faster to use fraction-free Gauss-Jordan over ZZ. These
|
| 332 |
+
# two extreme cases need to be handled differently because they lead to
|
| 333 |
+
# different asymptotic complexities. In between these two extremes we need
|
| 334 |
+
# a threshold for deciding which method to use. This threshold is
|
| 335 |
+
# determined empirically by timing the two methods with random matrices.
|
| 336 |
+
|
| 337 |
+
# The disadvantage of using empirical timings is that future optimisations
|
| 338 |
+
# might change the relative speeds so this can easily become out of date.
|
| 339 |
+
# The main thing is to get the asymptotic complexity right for the extreme
|
| 340 |
+
# cases though so the precise value of the threshold is hopefully not too
|
| 341 |
+
# important.
|
| 342 |
+
|
| 343 |
+
# Empirically determined parameter.
|
| 344 |
+
PARAM = 10000
|
| 345 |
+
|
| 346 |
+
# First compute the density. This is the average number of non-zero entries
|
| 347 |
+
# per row but only counting rows that have at least one non-zero entry
|
| 348 |
+
# since RREF can ignore fully zero rows.
|
| 349 |
+
density, nrows_nz, ncols = _dm_row_density(M)
|
| 350 |
+
|
| 351 |
+
# For small matrices use QQ if more than half the entries are zero.
|
| 352 |
+
if nrows_nz < 10:
|
| 353 |
+
if density < ncols/2:
|
| 354 |
+
return 'GJ'
|
| 355 |
+
else:
|
| 356 |
+
return 'FF'
|
| 357 |
+
|
| 358 |
+
# These are just shortcuts for the formula below.
|
| 359 |
+
if density < 5:
|
| 360 |
+
return 'GJ'
|
| 361 |
+
elif density > 5 + PARAM/nrows_nz:
|
| 362 |
+
return 'FF' # pragma: no cover
|
| 363 |
+
|
| 364 |
+
# Maximum bitsize of any entry.
|
| 365 |
+
elements = _dm_elements(M)
|
| 366 |
+
bits = max([e.bit_length() for e in elements], default=1)
|
| 367 |
+
|
| 368 |
+
# Wideness parameter. This is 1 for square or tall matrices but >1 for wide
|
| 369 |
+
# matrices.
|
| 370 |
+
wideness = max(1, 2/3*ncols/nrows_nz)
|
| 371 |
+
|
| 372 |
+
max_density = (5 + PARAM/(nrows_nz*bits**2)) * wideness
|
| 373 |
+
|
| 374 |
+
if density < max_density:
|
| 375 |
+
return 'GJ'
|
| 376 |
+
else:
|
| 377 |
+
return 'FF'
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def _dm_row_density(M):
|
| 381 |
+
"""Density measure for sparse matrices.
|
| 382 |
+
|
| 383 |
+
Defines the "density", ``d`` as the average number of non-zero entries per
|
| 384 |
+
row except ignoring rows that are fully zero. RREF can ignore fully zero
|
| 385 |
+
rows so they are excluded. By definition ``d >= 1`` except that we define
|
| 386 |
+
``d = 0`` for the zero matrix.
|
| 387 |
+
|
| 388 |
+
Returns ``(density, nrows_nz, ncols)`` where ``nrows_nz`` counts the number
|
| 389 |
+
of nonzero rows and ``ncols`` is the number of columns.
|
| 390 |
+
"""
|
| 391 |
+
# Uses the SDM dict-of-dicts representation.
|
| 392 |
+
ncols = M.shape[1]
|
| 393 |
+
rows_nz = M.rep.to_sdm().values()
|
| 394 |
+
if not rows_nz:
|
| 395 |
+
return 0, 0, ncols
|
| 396 |
+
else:
|
| 397 |
+
nrows_nz = len(rows_nz)
|
| 398 |
+
density = sum(map(len, rows_nz)) / nrows_nz
|
| 399 |
+
return density, nrows_nz, ncols
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def _dm_elements(M):
|
| 403 |
+
"""Return nonzero elements of a DomainMatrix."""
|
| 404 |
+
elements, _ = M.to_flat_nz()
|
| 405 |
+
return elements
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
def _dm_QQ_numers_denoms(Mq):
|
| 409 |
+
"""Returns the numerators and denominators of a DomainMatrix over QQ."""
|
| 410 |
+
elements = _dm_elements(Mq)
|
| 411 |
+
numers = [e.numerator for e in elements]
|
| 412 |
+
denoms = [e.denominator for e in elements]
|
| 413 |
+
return numers, denoms
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def _to_field(M):
|
| 417 |
+
"""Convert a DomainMatrix to a field if possible."""
|
| 418 |
+
K = M.domain
|
| 419 |
+
if K.has_assoc_Field:
|
| 420 |
+
return M.to_field()
|
| 421 |
+
else:
|
| 422 |
+
return M
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/sdm.py
ADDED
|
@@ -0,0 +1,2197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
|
| 3 |
+
Module for the SDM class.
|
| 4 |
+
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from operator import add, neg, pos, sub, mul
|
| 8 |
+
from collections import defaultdict
|
| 9 |
+
|
| 10 |
+
from sympy.external.gmpy import GROUND_TYPES
|
| 11 |
+
from sympy.utilities.decorator import doctest_depends_on
|
| 12 |
+
from sympy.utilities.iterables import _strongly_connected_components
|
| 13 |
+
|
| 14 |
+
from .exceptions import DMBadInputError, DMDomainError, DMShapeError
|
| 15 |
+
|
| 16 |
+
from sympy.polys.domains import QQ
|
| 17 |
+
|
| 18 |
+
from .ddm import DDM
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
if GROUND_TYPES != 'flint':
|
| 22 |
+
__doctest_skip__ = ['SDM.to_dfm', 'SDM.to_dfm_or_ddm']
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class SDM(dict):
|
| 26 |
+
r"""Sparse matrix based on polys domain elements
|
| 27 |
+
|
| 28 |
+
This is a dict subclass and is a wrapper for a dict of dicts that supports
|
| 29 |
+
basic matrix arithmetic +, -, *, **.
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
In order to create a new :py:class:`~.SDM`, a dict
|
| 33 |
+
of dicts mapping non-zero elements to their
|
| 34 |
+
corresponding row and column in the matrix is needed.
|
| 35 |
+
|
| 36 |
+
We also need to specify the shape and :py:class:`~.Domain`
|
| 37 |
+
of our :py:class:`~.SDM` object.
|
| 38 |
+
|
| 39 |
+
We declare a 2x2 :py:class:`~.SDM` matrix belonging
|
| 40 |
+
to QQ domain as shown below.
|
| 41 |
+
The 2x2 Matrix in the example is
|
| 42 |
+
|
| 43 |
+
.. math::
|
| 44 |
+
A = \left[\begin{array}{ccc}
|
| 45 |
+
0 & \frac{1}{2} \\
|
| 46 |
+
0 & 0 \end{array} \right]
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 50 |
+
>>> from sympy import QQ
|
| 51 |
+
>>> elemsdict = {0:{1:QQ(1, 2)}}
|
| 52 |
+
>>> A = SDM(elemsdict, (2, 2), QQ)
|
| 53 |
+
>>> A
|
| 54 |
+
{0: {1: 1/2}}
|
| 55 |
+
|
| 56 |
+
We can manipulate :py:class:`~.SDM` the same way
|
| 57 |
+
as a Matrix class
|
| 58 |
+
|
| 59 |
+
>>> from sympy import ZZ
|
| 60 |
+
>>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
| 61 |
+
>>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ)
|
| 62 |
+
>>> A + B
|
| 63 |
+
{0: {0: 3, 1: 2}, 1: {0: 1, 1: 4}}
|
| 64 |
+
|
| 65 |
+
Multiplication
|
| 66 |
+
|
| 67 |
+
>>> A*B
|
| 68 |
+
{0: {1: 8}, 1: {0: 3}}
|
| 69 |
+
>>> A*ZZ(2)
|
| 70 |
+
{0: {1: 4}, 1: {0: 2}}
|
| 71 |
+
|
| 72 |
+
"""
|
| 73 |
+
|
| 74 |
+
fmt = 'sparse'
|
| 75 |
+
is_DFM = False
|
| 76 |
+
is_DDM = False
|
| 77 |
+
|
| 78 |
+
def __init__(self, elemsdict, shape, domain):
|
| 79 |
+
super().__init__(elemsdict)
|
| 80 |
+
self.shape = self.rows, self.cols = m, n = shape
|
| 81 |
+
self.domain = domain
|
| 82 |
+
|
| 83 |
+
if not all(0 <= r < m for r in self):
|
| 84 |
+
raise DMBadInputError("Row out of range")
|
| 85 |
+
if not all(0 <= c < n for row in self.values() for c in row):
|
| 86 |
+
raise DMBadInputError("Column out of range")
|
| 87 |
+
|
| 88 |
+
def getitem(self, i, j):
|
| 89 |
+
try:
|
| 90 |
+
return self[i][j]
|
| 91 |
+
except KeyError:
|
| 92 |
+
m, n = self.shape
|
| 93 |
+
if -m <= i < m and -n <= j < n:
|
| 94 |
+
try:
|
| 95 |
+
return self[i % m][j % n]
|
| 96 |
+
except KeyError:
|
| 97 |
+
return self.domain.zero
|
| 98 |
+
else:
|
| 99 |
+
raise IndexError("index out of range")
|
| 100 |
+
|
| 101 |
+
def setitem(self, i, j, value):
|
| 102 |
+
m, n = self.shape
|
| 103 |
+
if not (-m <= i < m and -n <= j < n):
|
| 104 |
+
raise IndexError("index out of range")
|
| 105 |
+
i, j = i % m, j % n
|
| 106 |
+
if value:
|
| 107 |
+
try:
|
| 108 |
+
self[i][j] = value
|
| 109 |
+
except KeyError:
|
| 110 |
+
self[i] = {j: value}
|
| 111 |
+
else:
|
| 112 |
+
rowi = self.get(i, None)
|
| 113 |
+
if rowi is not None:
|
| 114 |
+
try:
|
| 115 |
+
del rowi[j]
|
| 116 |
+
except KeyError:
|
| 117 |
+
pass
|
| 118 |
+
else:
|
| 119 |
+
if not rowi:
|
| 120 |
+
del self[i]
|
| 121 |
+
|
| 122 |
+
def extract_slice(self, slice1, slice2):
|
| 123 |
+
m, n = self.shape
|
| 124 |
+
ri = range(m)[slice1]
|
| 125 |
+
ci = range(n)[slice2]
|
| 126 |
+
|
| 127 |
+
sdm = {}
|
| 128 |
+
for i, row in self.items():
|
| 129 |
+
if i in ri:
|
| 130 |
+
row = {ci.index(j): e for j, e in row.items() if j in ci}
|
| 131 |
+
if row:
|
| 132 |
+
sdm[ri.index(i)] = row
|
| 133 |
+
|
| 134 |
+
return self.new(sdm, (len(ri), len(ci)), self.domain)
|
| 135 |
+
|
| 136 |
+
def extract(self, rows, cols):
|
| 137 |
+
if not (self and rows and cols):
|
| 138 |
+
return self.zeros((len(rows), len(cols)), self.domain)
|
| 139 |
+
|
| 140 |
+
m, n = self.shape
|
| 141 |
+
if not (-m <= min(rows) <= max(rows) < m):
|
| 142 |
+
raise IndexError('Row index out of range')
|
| 143 |
+
if not (-n <= min(cols) <= max(cols) < n):
|
| 144 |
+
raise IndexError('Column index out of range')
|
| 145 |
+
|
| 146 |
+
# rows and cols can contain duplicates e.g. M[[1, 2, 2], [0, 1]]
|
| 147 |
+
# Build a map from row/col in self to list of rows/cols in output
|
| 148 |
+
rowmap = defaultdict(list)
|
| 149 |
+
colmap = defaultdict(list)
|
| 150 |
+
for i2, i1 in enumerate(rows):
|
| 151 |
+
rowmap[i1 % m].append(i2)
|
| 152 |
+
for j2, j1 in enumerate(cols):
|
| 153 |
+
colmap[j1 % n].append(j2)
|
| 154 |
+
|
| 155 |
+
# Used to efficiently skip zero rows/cols
|
| 156 |
+
rowset = set(rowmap)
|
| 157 |
+
colset = set(colmap)
|
| 158 |
+
|
| 159 |
+
sdm1 = self
|
| 160 |
+
sdm2 = {}
|
| 161 |
+
for i1 in rowset & sdm1.keys():
|
| 162 |
+
row1 = sdm1[i1]
|
| 163 |
+
row2 = {}
|
| 164 |
+
for j1 in colset & row1.keys():
|
| 165 |
+
row1_j1 = row1[j1]
|
| 166 |
+
for j2 in colmap[j1]:
|
| 167 |
+
row2[j2] = row1_j1
|
| 168 |
+
if row2:
|
| 169 |
+
for i2 in rowmap[i1]:
|
| 170 |
+
sdm2[i2] = row2.copy()
|
| 171 |
+
|
| 172 |
+
return self.new(sdm2, (len(rows), len(cols)), self.domain)
|
| 173 |
+
|
| 174 |
+
def __str__(self):
|
| 175 |
+
rowsstr = []
|
| 176 |
+
for i, row in self.items():
|
| 177 |
+
elemsstr = ', '.join('%s: %s' % (j, elem) for j, elem in row.items())
|
| 178 |
+
rowsstr.append('%s: {%s}' % (i, elemsstr))
|
| 179 |
+
return '{%s}' % ', '.join(rowsstr)
|
| 180 |
+
|
| 181 |
+
def __repr__(self):
|
| 182 |
+
cls = type(self).__name__
|
| 183 |
+
rows = dict.__repr__(self)
|
| 184 |
+
return '%s(%s, %s, %s)' % (cls, rows, self.shape, self.domain)
|
| 185 |
+
|
| 186 |
+
@classmethod
|
| 187 |
+
def new(cls, sdm, shape, domain):
|
| 188 |
+
"""
|
| 189 |
+
|
| 190 |
+
Parameters
|
| 191 |
+
==========
|
| 192 |
+
|
| 193 |
+
sdm: A dict of dicts for non-zero elements in SDM
|
| 194 |
+
shape: tuple representing dimension of SDM
|
| 195 |
+
domain: Represents :py:class:`~.Domain` of SDM
|
| 196 |
+
|
| 197 |
+
Returns
|
| 198 |
+
=======
|
| 199 |
+
|
| 200 |
+
An :py:class:`~.SDM` object
|
| 201 |
+
|
| 202 |
+
Examples
|
| 203 |
+
========
|
| 204 |
+
|
| 205 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 206 |
+
>>> from sympy import QQ
|
| 207 |
+
>>> elemsdict = {0:{1: QQ(2)}}
|
| 208 |
+
>>> A = SDM.new(elemsdict, (2, 2), QQ)
|
| 209 |
+
>>> A
|
| 210 |
+
{0: {1: 2}}
|
| 211 |
+
|
| 212 |
+
"""
|
| 213 |
+
return cls(sdm, shape, domain)
|
| 214 |
+
|
| 215 |
+
def copy(A):
|
| 216 |
+
"""
|
| 217 |
+
Returns the copy of a :py:class:`~.SDM` object
|
| 218 |
+
|
| 219 |
+
Examples
|
| 220 |
+
========
|
| 221 |
+
|
| 222 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 223 |
+
>>> from sympy import QQ
|
| 224 |
+
>>> elemsdict = {0:{1:QQ(2)}, 1:{}}
|
| 225 |
+
>>> A = SDM(elemsdict, (2, 2), QQ)
|
| 226 |
+
>>> B = A.copy()
|
| 227 |
+
>>> B
|
| 228 |
+
{0: {1: 2}, 1: {}}
|
| 229 |
+
|
| 230 |
+
"""
|
| 231 |
+
Ac = {i: Ai.copy() for i, Ai in A.items()}
|
| 232 |
+
return A.new(Ac, A.shape, A.domain)
|
| 233 |
+
|
| 234 |
+
@classmethod
|
| 235 |
+
def from_list(cls, ddm, shape, domain):
|
| 236 |
+
"""
|
| 237 |
+
Create :py:class:`~.SDM` object from a list of lists.
|
| 238 |
+
|
| 239 |
+
Parameters
|
| 240 |
+
==========
|
| 241 |
+
|
| 242 |
+
ddm:
|
| 243 |
+
list of lists containing domain elements
|
| 244 |
+
shape:
|
| 245 |
+
Dimensions of :py:class:`~.SDM` matrix
|
| 246 |
+
domain:
|
| 247 |
+
Represents :py:class:`~.Domain` of :py:class:`~.SDM` object
|
| 248 |
+
|
| 249 |
+
Returns
|
| 250 |
+
=======
|
| 251 |
+
|
| 252 |
+
:py:class:`~.SDM` containing elements of ddm
|
| 253 |
+
|
| 254 |
+
Examples
|
| 255 |
+
========
|
| 256 |
+
|
| 257 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 258 |
+
>>> from sympy import QQ
|
| 259 |
+
>>> ddm = [[QQ(1, 2), QQ(0)], [QQ(0), QQ(3, 4)]]
|
| 260 |
+
>>> A = SDM.from_list(ddm, (2, 2), QQ)
|
| 261 |
+
>>> A
|
| 262 |
+
{0: {0: 1/2}, 1: {1: 3/4}}
|
| 263 |
+
|
| 264 |
+
See Also
|
| 265 |
+
========
|
| 266 |
+
|
| 267 |
+
to_list
|
| 268 |
+
from_list_flat
|
| 269 |
+
from_dok
|
| 270 |
+
from_ddm
|
| 271 |
+
"""
|
| 272 |
+
|
| 273 |
+
m, n = shape
|
| 274 |
+
if not (len(ddm) == m and all(len(row) == n for row in ddm)):
|
| 275 |
+
raise DMBadInputError("Inconsistent row-list/shape")
|
| 276 |
+
getrow = lambda i: {j:ddm[i][j] for j in range(n) if ddm[i][j]}
|
| 277 |
+
irows = ((i, getrow(i)) for i in range(m))
|
| 278 |
+
sdm = {i: row for i, row in irows if row}
|
| 279 |
+
return cls(sdm, shape, domain)
|
| 280 |
+
|
| 281 |
+
@classmethod
|
| 282 |
+
def from_ddm(cls, ddm):
|
| 283 |
+
"""
|
| 284 |
+
Create :py:class:`~.SDM` from a :py:class:`~.DDM`.
|
| 285 |
+
|
| 286 |
+
Examples
|
| 287 |
+
========
|
| 288 |
+
|
| 289 |
+
>>> from sympy.polys.matrices.ddm import DDM
|
| 290 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 291 |
+
>>> from sympy import QQ
|
| 292 |
+
>>> ddm = DDM( [[QQ(1, 2), 0], [0, QQ(3, 4)]], (2, 2), QQ)
|
| 293 |
+
>>> A = SDM.from_ddm(ddm)
|
| 294 |
+
>>> A
|
| 295 |
+
{0: {0: 1/2}, 1: {1: 3/4}}
|
| 296 |
+
>>> SDM.from_ddm(ddm).to_ddm() == ddm
|
| 297 |
+
True
|
| 298 |
+
|
| 299 |
+
See Also
|
| 300 |
+
========
|
| 301 |
+
|
| 302 |
+
to_ddm
|
| 303 |
+
from_list
|
| 304 |
+
from_list_flat
|
| 305 |
+
from_dok
|
| 306 |
+
"""
|
| 307 |
+
return cls.from_list(ddm, ddm.shape, ddm.domain)
|
| 308 |
+
|
| 309 |
+
def to_list(M):
|
| 310 |
+
"""
|
| 311 |
+
Convert a :py:class:`~.SDM` object to a list of lists.
|
| 312 |
+
|
| 313 |
+
Examples
|
| 314 |
+
========
|
| 315 |
+
|
| 316 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 317 |
+
>>> from sympy import QQ
|
| 318 |
+
>>> elemsdict = {0:{1:QQ(2)}, 1:{}}
|
| 319 |
+
>>> A = SDM(elemsdict, (2, 2), QQ)
|
| 320 |
+
>>> A.to_list()
|
| 321 |
+
[[0, 2], [0, 0]]
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
"""
|
| 325 |
+
m, n = M.shape
|
| 326 |
+
zero = M.domain.zero
|
| 327 |
+
ddm = [[zero] * n for _ in range(m)]
|
| 328 |
+
for i, row in M.items():
|
| 329 |
+
for j, e in row.items():
|
| 330 |
+
ddm[i][j] = e
|
| 331 |
+
return ddm
|
| 332 |
+
|
| 333 |
+
def to_list_flat(M):
|
| 334 |
+
"""
|
| 335 |
+
Convert :py:class:`~.SDM` to a flat list.
|
| 336 |
+
|
| 337 |
+
Examples
|
| 338 |
+
========
|
| 339 |
+
|
| 340 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 341 |
+
>>> from sympy import QQ
|
| 342 |
+
>>> A = SDM({0:{1:QQ(2)}, 1:{0: QQ(3)}}, (2, 2), QQ)
|
| 343 |
+
>>> A.to_list_flat()
|
| 344 |
+
[0, 2, 3, 0]
|
| 345 |
+
>>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain)
|
| 346 |
+
True
|
| 347 |
+
|
| 348 |
+
See Also
|
| 349 |
+
========
|
| 350 |
+
|
| 351 |
+
from_list_flat
|
| 352 |
+
to_list
|
| 353 |
+
to_dok
|
| 354 |
+
to_ddm
|
| 355 |
+
"""
|
| 356 |
+
m, n = M.shape
|
| 357 |
+
zero = M.domain.zero
|
| 358 |
+
flat = [zero] * (m * n)
|
| 359 |
+
for i, row in M.items():
|
| 360 |
+
for j, e in row.items():
|
| 361 |
+
flat[i*n + j] = e
|
| 362 |
+
return flat
|
| 363 |
+
|
| 364 |
+
@classmethod
|
| 365 |
+
def from_list_flat(cls, elements, shape, domain):
|
| 366 |
+
"""
|
| 367 |
+
Create :py:class:`~.SDM` from a flat list of elements.
|
| 368 |
+
|
| 369 |
+
Examples
|
| 370 |
+
========
|
| 371 |
+
|
| 372 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 373 |
+
>>> from sympy import QQ
|
| 374 |
+
>>> A = SDM.from_list_flat([QQ(0), QQ(2), QQ(0), QQ(0)], (2, 2), QQ)
|
| 375 |
+
>>> A
|
| 376 |
+
{0: {1: 2}}
|
| 377 |
+
>>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain)
|
| 378 |
+
True
|
| 379 |
+
|
| 380 |
+
See Also
|
| 381 |
+
========
|
| 382 |
+
|
| 383 |
+
to_list_flat
|
| 384 |
+
from_list
|
| 385 |
+
from_dok
|
| 386 |
+
from_ddm
|
| 387 |
+
"""
|
| 388 |
+
m, n = shape
|
| 389 |
+
if len(elements) != m * n:
|
| 390 |
+
raise DMBadInputError("Inconsistent flat-list shape")
|
| 391 |
+
sdm = defaultdict(dict)
|
| 392 |
+
for inj, element in enumerate(elements):
|
| 393 |
+
if element:
|
| 394 |
+
i, j = divmod(inj, n)
|
| 395 |
+
sdm[i][j] = element
|
| 396 |
+
return cls(sdm, shape, domain)
|
| 397 |
+
|
| 398 |
+
def to_flat_nz(M):
|
| 399 |
+
"""
|
| 400 |
+
Convert :class:`SDM` to a flat list of nonzero elements and data.
|
| 401 |
+
|
| 402 |
+
Explanation
|
| 403 |
+
===========
|
| 404 |
+
|
| 405 |
+
This is used to operate on a list of the elements of a matrix and then
|
| 406 |
+
reconstruct a modified matrix with elements in the same positions using
|
| 407 |
+
:meth:`from_flat_nz`. Zero elements are omitted from the list.
|
| 408 |
+
|
| 409 |
+
Examples
|
| 410 |
+
========
|
| 411 |
+
|
| 412 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 413 |
+
>>> from sympy import QQ
|
| 414 |
+
>>> A = SDM({0:{1:QQ(2)}, 1:{0: QQ(3)}}, (2, 2), QQ)
|
| 415 |
+
>>> elements, data = A.to_flat_nz()
|
| 416 |
+
>>> elements
|
| 417 |
+
[2, 3]
|
| 418 |
+
>>> A == A.from_flat_nz(elements, data, A.domain)
|
| 419 |
+
True
|
| 420 |
+
|
| 421 |
+
See Also
|
| 422 |
+
========
|
| 423 |
+
|
| 424 |
+
from_flat_nz
|
| 425 |
+
to_list_flat
|
| 426 |
+
sympy.polys.matrices.ddm.DDM.to_flat_nz
|
| 427 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_flat_nz
|
| 428 |
+
"""
|
| 429 |
+
dok = M.to_dok()
|
| 430 |
+
indices = tuple(dok)
|
| 431 |
+
elements = list(dok.values())
|
| 432 |
+
data = (indices, M.shape)
|
| 433 |
+
return elements, data
|
| 434 |
+
|
| 435 |
+
@classmethod
|
| 436 |
+
def from_flat_nz(cls, elements, data, domain):
|
| 437 |
+
"""
|
| 438 |
+
Reconstruct a :class:`~.SDM` after calling :meth:`to_flat_nz`.
|
| 439 |
+
|
| 440 |
+
See :meth:`to_flat_nz` for explanation.
|
| 441 |
+
|
| 442 |
+
See Also
|
| 443 |
+
========
|
| 444 |
+
|
| 445 |
+
to_flat_nz
|
| 446 |
+
from_list_flat
|
| 447 |
+
sympy.polys.matrices.ddm.DDM.from_flat_nz
|
| 448 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.from_flat_nz
|
| 449 |
+
"""
|
| 450 |
+
indices, shape = data
|
| 451 |
+
dok = dict(zip(indices, elements))
|
| 452 |
+
return cls.from_dok(dok, shape, domain)
|
| 453 |
+
|
| 454 |
+
def to_dod(M):
|
| 455 |
+
"""
|
| 456 |
+
Convert to dictionary of dictionaries (dod) format.
|
| 457 |
+
|
| 458 |
+
Examples
|
| 459 |
+
========
|
| 460 |
+
|
| 461 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 462 |
+
>>> from sympy import QQ
|
| 463 |
+
>>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ)
|
| 464 |
+
>>> A.to_dod()
|
| 465 |
+
{0: {1: 2}, 1: {0: 3}}
|
| 466 |
+
|
| 467 |
+
See Also
|
| 468 |
+
========
|
| 469 |
+
|
| 470 |
+
from_dod
|
| 471 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod
|
| 472 |
+
"""
|
| 473 |
+
return {i: row.copy() for i, row in M.items()}
|
| 474 |
+
|
| 475 |
+
@classmethod
|
| 476 |
+
def from_dod(cls, dod, shape, domain):
|
| 477 |
+
"""
|
| 478 |
+
Create :py:class:`~.SDM` from dictionary of dictionaries (dod) format.
|
| 479 |
+
|
| 480 |
+
Examples
|
| 481 |
+
========
|
| 482 |
+
|
| 483 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 484 |
+
>>> from sympy import QQ
|
| 485 |
+
>>> dod = {0: {1: QQ(2)}, 1: {0: QQ(3)}}
|
| 486 |
+
>>> A = SDM.from_dod(dod, (2, 2), QQ)
|
| 487 |
+
>>> A
|
| 488 |
+
{0: {1: 2}, 1: {0: 3}}
|
| 489 |
+
>>> A == SDM.from_dod(A.to_dod(), A.shape, A.domain)
|
| 490 |
+
True
|
| 491 |
+
|
| 492 |
+
See Also
|
| 493 |
+
========
|
| 494 |
+
|
| 495 |
+
to_dod
|
| 496 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod
|
| 497 |
+
"""
|
| 498 |
+
sdm = defaultdict(dict)
|
| 499 |
+
for i, row in dod.items():
|
| 500 |
+
for j, e in row.items():
|
| 501 |
+
if e:
|
| 502 |
+
sdm[i][j] = e
|
| 503 |
+
return cls(sdm, shape, domain)
|
| 504 |
+
|
| 505 |
+
def to_dok(M):
|
| 506 |
+
"""
|
| 507 |
+
Convert to dictionary of keys (dok) format.
|
| 508 |
+
|
| 509 |
+
Examples
|
| 510 |
+
========
|
| 511 |
+
|
| 512 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 513 |
+
>>> from sympy import QQ
|
| 514 |
+
>>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ)
|
| 515 |
+
>>> A.to_dok()
|
| 516 |
+
{(0, 1): 2, (1, 0): 3}
|
| 517 |
+
|
| 518 |
+
See Also
|
| 519 |
+
========
|
| 520 |
+
|
| 521 |
+
from_dok
|
| 522 |
+
to_list
|
| 523 |
+
to_list_flat
|
| 524 |
+
to_ddm
|
| 525 |
+
"""
|
| 526 |
+
return {(i, j): e for i, row in M.items() for j, e in row.items()}
|
| 527 |
+
|
| 528 |
+
@classmethod
|
| 529 |
+
def from_dok(cls, dok, shape, domain):
|
| 530 |
+
"""
|
| 531 |
+
Create :py:class:`~.SDM` from dictionary of keys (dok) format.
|
| 532 |
+
|
| 533 |
+
Examples
|
| 534 |
+
========
|
| 535 |
+
|
| 536 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 537 |
+
>>> from sympy import QQ
|
| 538 |
+
>>> dok = {(0, 1): QQ(2), (1, 0): QQ(3)}
|
| 539 |
+
>>> A = SDM.from_dok(dok, (2, 2), QQ)
|
| 540 |
+
>>> A
|
| 541 |
+
{0: {1: 2}, 1: {0: 3}}
|
| 542 |
+
>>> A == SDM.from_dok(A.to_dok(), A.shape, A.domain)
|
| 543 |
+
True
|
| 544 |
+
|
| 545 |
+
See Also
|
| 546 |
+
========
|
| 547 |
+
|
| 548 |
+
to_dok
|
| 549 |
+
from_list
|
| 550 |
+
from_list_flat
|
| 551 |
+
from_ddm
|
| 552 |
+
"""
|
| 553 |
+
sdm = defaultdict(dict)
|
| 554 |
+
for (i, j), e in dok.items():
|
| 555 |
+
if e:
|
| 556 |
+
sdm[i][j] = e
|
| 557 |
+
return cls(sdm, shape, domain)
|
| 558 |
+
|
| 559 |
+
def iter_values(M):
|
| 560 |
+
"""
|
| 561 |
+
Iterate over the nonzero values of a :py:class:`~.SDM` matrix.
|
| 562 |
+
|
| 563 |
+
Examples
|
| 564 |
+
========
|
| 565 |
+
|
| 566 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 567 |
+
>>> from sympy import QQ
|
| 568 |
+
>>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ)
|
| 569 |
+
>>> list(A.iter_values())
|
| 570 |
+
[2, 3]
|
| 571 |
+
|
| 572 |
+
"""
|
| 573 |
+
for row in M.values():
|
| 574 |
+
yield from row.values()
|
| 575 |
+
|
| 576 |
+
def iter_items(M):
|
| 577 |
+
"""
|
| 578 |
+
Iterate over indices and values of the nonzero elements.
|
| 579 |
+
|
| 580 |
+
Examples
|
| 581 |
+
========
|
| 582 |
+
|
| 583 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 584 |
+
>>> from sympy import QQ
|
| 585 |
+
>>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ)
|
| 586 |
+
>>> list(A.iter_items())
|
| 587 |
+
[((0, 1), 2), ((1, 0), 3)]
|
| 588 |
+
|
| 589 |
+
See Also
|
| 590 |
+
========
|
| 591 |
+
|
| 592 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.iter_items
|
| 593 |
+
"""
|
| 594 |
+
for i, row in M.items():
|
| 595 |
+
for j, e in row.items():
|
| 596 |
+
yield (i, j), e
|
| 597 |
+
|
| 598 |
+
def to_ddm(M):
|
| 599 |
+
"""
|
| 600 |
+
Convert a :py:class:`~.SDM` object to a :py:class:`~.DDM` object
|
| 601 |
+
|
| 602 |
+
Examples
|
| 603 |
+
========
|
| 604 |
+
|
| 605 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 606 |
+
>>> from sympy import QQ
|
| 607 |
+
>>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ)
|
| 608 |
+
>>> A.to_ddm()
|
| 609 |
+
[[0, 2], [0, 0]]
|
| 610 |
+
|
| 611 |
+
"""
|
| 612 |
+
return DDM(M.to_list(), M.shape, M.domain)
|
| 613 |
+
|
| 614 |
+
def to_sdm(M):
|
| 615 |
+
"""
|
| 616 |
+
Convert to :py:class:`~.SDM` format (returns self).
|
| 617 |
+
"""
|
| 618 |
+
return M
|
| 619 |
+
|
| 620 |
+
@doctest_depends_on(ground_types=['flint'])
|
| 621 |
+
def to_dfm(M):
|
| 622 |
+
"""
|
| 623 |
+
Convert a :py:class:`~.SDM` object to a :py:class:`~.DFM` object
|
| 624 |
+
|
| 625 |
+
Examples
|
| 626 |
+
========
|
| 627 |
+
|
| 628 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 629 |
+
>>> from sympy import QQ
|
| 630 |
+
>>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ)
|
| 631 |
+
>>> A.to_dfm()
|
| 632 |
+
[[0, 2], [0, 0]]
|
| 633 |
+
|
| 634 |
+
See Also
|
| 635 |
+
========
|
| 636 |
+
|
| 637 |
+
to_ddm
|
| 638 |
+
to_dfm_or_ddm
|
| 639 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm
|
| 640 |
+
"""
|
| 641 |
+
return M.to_ddm().to_dfm()
|
| 642 |
+
|
| 643 |
+
@doctest_depends_on(ground_types=['flint'])
|
| 644 |
+
def to_dfm_or_ddm(M):
|
| 645 |
+
"""
|
| 646 |
+
Convert to :py:class:`~.DFM` if possible, else :py:class:`~.DDM`.
|
| 647 |
+
|
| 648 |
+
Examples
|
| 649 |
+
========
|
| 650 |
+
|
| 651 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 652 |
+
>>> from sympy import QQ
|
| 653 |
+
>>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ)
|
| 654 |
+
>>> A.to_dfm_or_ddm()
|
| 655 |
+
[[0, 2], [0, 0]]
|
| 656 |
+
>>> type(A.to_dfm_or_ddm()) # depends on the ground types
|
| 657 |
+
<class 'sympy.polys.matrices._dfm.DFM'>
|
| 658 |
+
|
| 659 |
+
See Also
|
| 660 |
+
========
|
| 661 |
+
|
| 662 |
+
to_ddm
|
| 663 |
+
to_dfm
|
| 664 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm
|
| 665 |
+
"""
|
| 666 |
+
return M.to_ddm().to_dfm_or_ddm()
|
| 667 |
+
|
| 668 |
+
@classmethod
|
| 669 |
+
def zeros(cls, shape, domain):
|
| 670 |
+
r"""
|
| 671 |
+
|
| 672 |
+
Returns a :py:class:`~.SDM` of size shape,
|
| 673 |
+
belonging to the specified domain
|
| 674 |
+
|
| 675 |
+
In the example below we declare a matrix A where,
|
| 676 |
+
|
| 677 |
+
.. math::
|
| 678 |
+
A := \left[\begin{array}{ccc}
|
| 679 |
+
0 & 0 & 0 \\
|
| 680 |
+
0 & 0 & 0 \end{array} \right]
|
| 681 |
+
|
| 682 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 683 |
+
>>> from sympy import QQ
|
| 684 |
+
>>> A = SDM.zeros((2, 3), QQ)
|
| 685 |
+
>>> A
|
| 686 |
+
{}
|
| 687 |
+
|
| 688 |
+
"""
|
| 689 |
+
return cls({}, shape, domain)
|
| 690 |
+
|
| 691 |
+
@classmethod
|
| 692 |
+
def ones(cls, shape, domain):
|
| 693 |
+
one = domain.one
|
| 694 |
+
m, n = shape
|
| 695 |
+
row = dict(zip(range(n), [one]*n))
|
| 696 |
+
sdm = {i: row.copy() for i in range(m)}
|
| 697 |
+
return cls(sdm, shape, domain)
|
| 698 |
+
|
| 699 |
+
@classmethod
|
| 700 |
+
def eye(cls, shape, domain):
|
| 701 |
+
"""
|
| 702 |
+
|
| 703 |
+
Returns a identity :py:class:`~.SDM` matrix of dimensions
|
| 704 |
+
size x size, belonging to the specified domain
|
| 705 |
+
|
| 706 |
+
Examples
|
| 707 |
+
========
|
| 708 |
+
|
| 709 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 710 |
+
>>> from sympy import QQ
|
| 711 |
+
>>> I = SDM.eye((2, 2), QQ)
|
| 712 |
+
>>> I
|
| 713 |
+
{0: {0: 1}, 1: {1: 1}}
|
| 714 |
+
|
| 715 |
+
"""
|
| 716 |
+
if isinstance(shape, int):
|
| 717 |
+
rows, cols = shape, shape
|
| 718 |
+
else:
|
| 719 |
+
rows, cols = shape
|
| 720 |
+
one = domain.one
|
| 721 |
+
sdm = {i: {i: one} for i in range(min(rows, cols))}
|
| 722 |
+
return cls(sdm, (rows, cols), domain)
|
| 723 |
+
|
| 724 |
+
@classmethod
|
| 725 |
+
def diag(cls, diagonal, domain, shape=None):
|
| 726 |
+
if shape is None:
|
| 727 |
+
shape = (len(diagonal), len(diagonal))
|
| 728 |
+
sdm = {i: {i: v} for i, v in enumerate(diagonal) if v}
|
| 729 |
+
return cls(sdm, shape, domain)
|
| 730 |
+
|
| 731 |
+
def transpose(M):
|
| 732 |
+
"""
|
| 733 |
+
|
| 734 |
+
Returns the transpose of a :py:class:`~.SDM` matrix
|
| 735 |
+
|
| 736 |
+
Examples
|
| 737 |
+
========
|
| 738 |
+
|
| 739 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 740 |
+
>>> from sympy import QQ
|
| 741 |
+
>>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ)
|
| 742 |
+
>>> A.transpose()
|
| 743 |
+
{1: {0: 2}}
|
| 744 |
+
|
| 745 |
+
"""
|
| 746 |
+
MT = sdm_transpose(M)
|
| 747 |
+
return M.new(MT, M.shape[::-1], M.domain)
|
| 748 |
+
|
| 749 |
+
def __add__(A, B):
|
| 750 |
+
if not isinstance(B, SDM):
|
| 751 |
+
return NotImplemented
|
| 752 |
+
elif A.shape != B.shape:
|
| 753 |
+
raise DMShapeError("Matrix size mismatch: %s + %s" % (A.shape, B.shape))
|
| 754 |
+
return A.add(B)
|
| 755 |
+
|
| 756 |
+
def __sub__(A, B):
|
| 757 |
+
if not isinstance(B, SDM):
|
| 758 |
+
return NotImplemented
|
| 759 |
+
elif A.shape != B.shape:
|
| 760 |
+
raise DMShapeError("Matrix size mismatch: %s - %s" % (A.shape, B.shape))
|
| 761 |
+
return A.sub(B)
|
| 762 |
+
|
| 763 |
+
def __neg__(A):
|
| 764 |
+
return A.neg()
|
| 765 |
+
|
| 766 |
+
def __mul__(A, B):
|
| 767 |
+
"""A * B"""
|
| 768 |
+
if isinstance(B, SDM):
|
| 769 |
+
return A.matmul(B)
|
| 770 |
+
elif B in A.domain:
|
| 771 |
+
return A.mul(B)
|
| 772 |
+
else:
|
| 773 |
+
return NotImplemented
|
| 774 |
+
|
| 775 |
+
def __rmul__(a, b):
|
| 776 |
+
if b in a.domain:
|
| 777 |
+
return a.rmul(b)
|
| 778 |
+
else:
|
| 779 |
+
return NotImplemented
|
| 780 |
+
|
| 781 |
+
def matmul(A, B):
|
| 782 |
+
"""
|
| 783 |
+
Performs matrix multiplication of two SDM matrices
|
| 784 |
+
|
| 785 |
+
Parameters
|
| 786 |
+
==========
|
| 787 |
+
|
| 788 |
+
A, B: SDM to multiply
|
| 789 |
+
|
| 790 |
+
Returns
|
| 791 |
+
=======
|
| 792 |
+
|
| 793 |
+
SDM
|
| 794 |
+
SDM after multiplication
|
| 795 |
+
|
| 796 |
+
Raises
|
| 797 |
+
======
|
| 798 |
+
|
| 799 |
+
DomainError
|
| 800 |
+
If domain of A does not match
|
| 801 |
+
with that of B
|
| 802 |
+
|
| 803 |
+
Examples
|
| 804 |
+
========
|
| 805 |
+
|
| 806 |
+
>>> from sympy import ZZ
|
| 807 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 808 |
+
>>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
| 809 |
+
>>> B = SDM({0:{0:ZZ(2), 1:ZZ(3)}, 1:{0:ZZ(4)}}, (2, 2), ZZ)
|
| 810 |
+
>>> A.matmul(B)
|
| 811 |
+
{0: {0: 8}, 1: {0: 2, 1: 3}}
|
| 812 |
+
|
| 813 |
+
"""
|
| 814 |
+
if A.domain != B.domain:
|
| 815 |
+
raise DMDomainError
|
| 816 |
+
m, n = A.shape
|
| 817 |
+
n2, o = B.shape
|
| 818 |
+
if n != n2:
|
| 819 |
+
raise DMShapeError
|
| 820 |
+
C = sdm_matmul(A, B, A.domain, m, o)
|
| 821 |
+
return A.new(C, (m, o), A.domain)
|
| 822 |
+
|
| 823 |
+
def mul(A, b):
|
| 824 |
+
"""
|
| 825 |
+
Multiplies each element of A with a scalar b
|
| 826 |
+
|
| 827 |
+
Examples
|
| 828 |
+
========
|
| 829 |
+
|
| 830 |
+
>>> from sympy import ZZ
|
| 831 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 832 |
+
>>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
| 833 |
+
>>> A.mul(ZZ(3))
|
| 834 |
+
{0: {1: 6}, 1: {0: 3}}
|
| 835 |
+
|
| 836 |
+
"""
|
| 837 |
+
Csdm = unop_dict(A, lambda aij: aij*b)
|
| 838 |
+
return A.new(Csdm, A.shape, A.domain)
|
| 839 |
+
|
| 840 |
+
def rmul(A, b):
|
| 841 |
+
Csdm = unop_dict(A, lambda aij: b*aij)
|
| 842 |
+
return A.new(Csdm, A.shape, A.domain)
|
| 843 |
+
|
| 844 |
+
def mul_elementwise(A, B):
|
| 845 |
+
if A.domain != B.domain:
|
| 846 |
+
raise DMDomainError
|
| 847 |
+
if A.shape != B.shape:
|
| 848 |
+
raise DMShapeError
|
| 849 |
+
zero = A.domain.zero
|
| 850 |
+
fzero = lambda e: zero
|
| 851 |
+
Csdm = binop_dict(A, B, mul, fzero, fzero)
|
| 852 |
+
return A.new(Csdm, A.shape, A.domain)
|
| 853 |
+
|
| 854 |
+
def add(A, B):
|
| 855 |
+
"""
|
| 856 |
+
|
| 857 |
+
Adds two :py:class:`~.SDM` matrices
|
| 858 |
+
|
| 859 |
+
Examples
|
| 860 |
+
========
|
| 861 |
+
|
| 862 |
+
>>> from sympy import ZZ
|
| 863 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 864 |
+
>>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
| 865 |
+
>>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ)
|
| 866 |
+
>>> A.add(B)
|
| 867 |
+
{0: {0: 3, 1: 2}, 1: {0: 1, 1: 4}}
|
| 868 |
+
|
| 869 |
+
"""
|
| 870 |
+
Csdm = binop_dict(A, B, add, pos, pos)
|
| 871 |
+
return A.new(Csdm, A.shape, A.domain)
|
| 872 |
+
|
| 873 |
+
def sub(A, B):
|
| 874 |
+
"""
|
| 875 |
+
|
| 876 |
+
Subtracts two :py:class:`~.SDM` matrices
|
| 877 |
+
|
| 878 |
+
Examples
|
| 879 |
+
========
|
| 880 |
+
|
| 881 |
+
>>> from sympy import ZZ
|
| 882 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 883 |
+
>>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
| 884 |
+
>>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ)
|
| 885 |
+
>>> A.sub(B)
|
| 886 |
+
{0: {0: -3, 1: 2}, 1: {0: 1, 1: -4}}
|
| 887 |
+
|
| 888 |
+
"""
|
| 889 |
+
Csdm = binop_dict(A, B, sub, pos, neg)
|
| 890 |
+
return A.new(Csdm, A.shape, A.domain)
|
| 891 |
+
|
| 892 |
+
def neg(A):
|
| 893 |
+
"""
|
| 894 |
+
|
| 895 |
+
Returns the negative of a :py:class:`~.SDM` matrix
|
| 896 |
+
|
| 897 |
+
Examples
|
| 898 |
+
========
|
| 899 |
+
|
| 900 |
+
>>> from sympy import ZZ
|
| 901 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 902 |
+
>>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
| 903 |
+
>>> A.neg()
|
| 904 |
+
{0: {1: -2}, 1: {0: -1}}
|
| 905 |
+
|
| 906 |
+
"""
|
| 907 |
+
Csdm = unop_dict(A, neg)
|
| 908 |
+
return A.new(Csdm, A.shape, A.domain)
|
| 909 |
+
|
| 910 |
+
def convert_to(A, K):
|
| 911 |
+
"""
|
| 912 |
+
Converts the :py:class:`~.Domain` of a :py:class:`~.SDM` matrix to K
|
| 913 |
+
|
| 914 |
+
Examples
|
| 915 |
+
========
|
| 916 |
+
|
| 917 |
+
>>> from sympy import ZZ, QQ
|
| 918 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 919 |
+
>>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
| 920 |
+
>>> A.convert_to(QQ)
|
| 921 |
+
{0: {1: 2}, 1: {0: 1}}
|
| 922 |
+
|
| 923 |
+
"""
|
| 924 |
+
Kold = A.domain
|
| 925 |
+
if K == Kold:
|
| 926 |
+
return A.copy()
|
| 927 |
+
Ak = unop_dict(A, lambda e: K.convert_from(e, Kold))
|
| 928 |
+
return A.new(Ak, A.shape, K)
|
| 929 |
+
|
| 930 |
+
def nnz(A):
|
| 931 |
+
"""Number of non-zero elements in the :py:class:`~.SDM` matrix.
|
| 932 |
+
|
| 933 |
+
Examples
|
| 934 |
+
========
|
| 935 |
+
|
| 936 |
+
>>> from sympy import ZZ
|
| 937 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 938 |
+
>>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
|
| 939 |
+
>>> A.nnz()
|
| 940 |
+
2
|
| 941 |
+
|
| 942 |
+
See Also
|
| 943 |
+
========
|
| 944 |
+
|
| 945 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.nnz
|
| 946 |
+
"""
|
| 947 |
+
return sum(map(len, A.values()))
|
| 948 |
+
|
| 949 |
+
def scc(A):
|
| 950 |
+
"""Strongly connected components of a square matrix *A*.
|
| 951 |
+
|
| 952 |
+
Examples
|
| 953 |
+
========
|
| 954 |
+
|
| 955 |
+
>>> from sympy import ZZ
|
| 956 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 957 |
+
>>> A = SDM({0:{0: ZZ(2)}, 1:{1:ZZ(1)}}, (2, 2), ZZ)
|
| 958 |
+
>>> A.scc()
|
| 959 |
+
[[0], [1]]
|
| 960 |
+
|
| 961 |
+
See also
|
| 962 |
+
========
|
| 963 |
+
|
| 964 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.scc
|
| 965 |
+
"""
|
| 966 |
+
rows, cols = A.shape
|
| 967 |
+
assert rows == cols
|
| 968 |
+
V = range(rows)
|
| 969 |
+
Emap = {v: list(A.get(v, [])) for v in V}
|
| 970 |
+
return _strongly_connected_components(V, Emap)
|
| 971 |
+
|
| 972 |
+
def rref(A):
|
| 973 |
+
"""
|
| 974 |
+
|
| 975 |
+
Returns reduced-row echelon form and list of pivots for the :py:class:`~.SDM`
|
| 976 |
+
|
| 977 |
+
Examples
|
| 978 |
+
========
|
| 979 |
+
|
| 980 |
+
>>> from sympy import QQ
|
| 981 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 982 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(2), 1:QQ(4)}}, (2, 2), QQ)
|
| 983 |
+
>>> A.rref()
|
| 984 |
+
({0: {0: 1, 1: 2}}, [0])
|
| 985 |
+
|
| 986 |
+
"""
|
| 987 |
+
B, pivots, _ = sdm_irref(A)
|
| 988 |
+
return A.new(B, A.shape, A.domain), pivots
|
| 989 |
+
|
| 990 |
+
def rref_den(A):
|
| 991 |
+
"""
|
| 992 |
+
|
| 993 |
+
Returns reduced-row echelon form (RREF) with denominator and pivots.
|
| 994 |
+
|
| 995 |
+
Examples
|
| 996 |
+
========
|
| 997 |
+
|
| 998 |
+
>>> from sympy import QQ
|
| 999 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1000 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(2), 1:QQ(4)}}, (2, 2), QQ)
|
| 1001 |
+
>>> A.rref_den()
|
| 1002 |
+
({0: {0: 1, 1: 2}}, 1, [0])
|
| 1003 |
+
|
| 1004 |
+
"""
|
| 1005 |
+
K = A.domain
|
| 1006 |
+
A_rref_sdm, denom, pivots = sdm_rref_den(A, K)
|
| 1007 |
+
A_rref = A.new(A_rref_sdm, A.shape, A.domain)
|
| 1008 |
+
return A_rref, denom, pivots
|
| 1009 |
+
|
| 1010 |
+
def inv(A):
|
| 1011 |
+
"""
|
| 1012 |
+
|
| 1013 |
+
Returns inverse of a matrix A
|
| 1014 |
+
|
| 1015 |
+
Examples
|
| 1016 |
+
========
|
| 1017 |
+
|
| 1018 |
+
>>> from sympy import QQ
|
| 1019 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1020 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
| 1021 |
+
>>> A.inv()
|
| 1022 |
+
{0: {0: -2, 1: 1}, 1: {0: 3/2, 1: -1/2}}
|
| 1023 |
+
|
| 1024 |
+
"""
|
| 1025 |
+
return A.to_dfm_or_ddm().inv().to_sdm()
|
| 1026 |
+
|
| 1027 |
+
def det(A):
|
| 1028 |
+
"""
|
| 1029 |
+
Returns determinant of A
|
| 1030 |
+
|
| 1031 |
+
Examples
|
| 1032 |
+
========
|
| 1033 |
+
|
| 1034 |
+
>>> from sympy import QQ
|
| 1035 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1036 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
| 1037 |
+
>>> A.det()
|
| 1038 |
+
-2
|
| 1039 |
+
|
| 1040 |
+
"""
|
| 1041 |
+
# It would be better to have a sparse implementation of det for use
|
| 1042 |
+
# with very sparse matrices. Extremely sparse matrices probably just
|
| 1043 |
+
# have determinant zero and we could probably detect that very quickly.
|
| 1044 |
+
# In the meantime, we convert to a dense matrix and use ddm_idet.
|
| 1045 |
+
#
|
| 1046 |
+
# If GROUND_TYPES=flint though then we will use Flint's implementation
|
| 1047 |
+
# if possible (dfm).
|
| 1048 |
+
return A.to_dfm_or_ddm().det()
|
| 1049 |
+
|
| 1050 |
+
def lu(A):
|
| 1051 |
+
"""
|
| 1052 |
+
|
| 1053 |
+
Returns LU decomposition for a matrix A
|
| 1054 |
+
|
| 1055 |
+
Examples
|
| 1056 |
+
========
|
| 1057 |
+
|
| 1058 |
+
>>> from sympy import QQ
|
| 1059 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1060 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
| 1061 |
+
>>> A.lu()
|
| 1062 |
+
({0: {0: 1}, 1: {0: 3, 1: 1}}, {0: {0: 1, 1: 2}, 1: {1: -2}}, [])
|
| 1063 |
+
|
| 1064 |
+
"""
|
| 1065 |
+
L, U, swaps = A.to_ddm().lu()
|
| 1066 |
+
return A.from_ddm(L), A.from_ddm(U), swaps
|
| 1067 |
+
|
| 1068 |
+
def qr(self):
|
| 1069 |
+
"""
|
| 1070 |
+
QR decomposition for SDM (Sparse Domain Matrix).
|
| 1071 |
+
|
| 1072 |
+
Returns:
|
| 1073 |
+
- Q: Orthogonal matrix as a SDM.
|
| 1074 |
+
- R: Upper triangular matrix as a SDM.
|
| 1075 |
+
"""
|
| 1076 |
+
ddm_q, ddm_r = self.to_ddm().qr()
|
| 1077 |
+
Q = ddm_q.to_sdm()
|
| 1078 |
+
R = ddm_r.to_sdm()
|
| 1079 |
+
return Q, R
|
| 1080 |
+
|
| 1081 |
+
def lu_solve(A, b):
|
| 1082 |
+
"""
|
| 1083 |
+
|
| 1084 |
+
Uses LU decomposition to solve Ax = b,
|
| 1085 |
+
|
| 1086 |
+
Examples
|
| 1087 |
+
========
|
| 1088 |
+
|
| 1089 |
+
>>> from sympy import QQ
|
| 1090 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1091 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
| 1092 |
+
>>> b = SDM({0:{0:QQ(1)}, 1:{0:QQ(2)}}, (2, 1), QQ)
|
| 1093 |
+
>>> A.lu_solve(b)
|
| 1094 |
+
{1: {0: 1/2}}
|
| 1095 |
+
|
| 1096 |
+
"""
|
| 1097 |
+
return A.from_ddm(A.to_ddm().lu_solve(b.to_ddm()))
|
| 1098 |
+
|
| 1099 |
+
def fflu(self):
|
| 1100 |
+
"""
|
| 1101 |
+
Fraction free LU decomposition of SDM.
|
| 1102 |
+
|
| 1103 |
+
Uses DDM implementation.
|
| 1104 |
+
|
| 1105 |
+
See Also
|
| 1106 |
+
========
|
| 1107 |
+
|
| 1108 |
+
sympy.polys.matrices.ddm.DDM.fflu
|
| 1109 |
+
"""
|
| 1110 |
+
ddm_p, ddm_l, ddm_d, ddm_u = self.to_dfm_or_ddm().fflu()
|
| 1111 |
+
P = ddm_p.to_sdm()
|
| 1112 |
+
L = ddm_l.to_sdm()
|
| 1113 |
+
D = ddm_d.to_sdm()
|
| 1114 |
+
U = ddm_u.to_sdm()
|
| 1115 |
+
return P, L, D, U
|
| 1116 |
+
|
| 1117 |
+
def nullspace(A):
|
| 1118 |
+
"""
|
| 1119 |
+
Nullspace of a :py:class:`~.SDM` matrix A.
|
| 1120 |
+
|
| 1121 |
+
The domain of the matrix must be a field.
|
| 1122 |
+
|
| 1123 |
+
It is better to use the :meth:`~.DomainMatrix.nullspace` method rather
|
| 1124 |
+
than this method which is otherwise no longer used.
|
| 1125 |
+
|
| 1126 |
+
Examples
|
| 1127 |
+
========
|
| 1128 |
+
|
| 1129 |
+
>>> from sympy import QQ
|
| 1130 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1131 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0: QQ(2), 1: QQ(4)}}, (2, 2), QQ)
|
| 1132 |
+
>>> A.nullspace()
|
| 1133 |
+
({0: {0: -2, 1: 1}}, [1])
|
| 1134 |
+
|
| 1135 |
+
|
| 1136 |
+
See Also
|
| 1137 |
+
========
|
| 1138 |
+
|
| 1139 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace
|
| 1140 |
+
The preferred way to get the nullspace of a matrix.
|
| 1141 |
+
|
| 1142 |
+
"""
|
| 1143 |
+
ncols = A.shape[1]
|
| 1144 |
+
one = A.domain.one
|
| 1145 |
+
B, pivots, nzcols = sdm_irref(A)
|
| 1146 |
+
K, nonpivots = sdm_nullspace_from_rref(B, one, ncols, pivots, nzcols)
|
| 1147 |
+
K = dict(enumerate(K))
|
| 1148 |
+
shape = (len(K), ncols)
|
| 1149 |
+
return A.new(K, shape, A.domain), nonpivots
|
| 1150 |
+
|
| 1151 |
+
def nullspace_from_rref(A, pivots=None):
|
| 1152 |
+
"""
|
| 1153 |
+
Returns nullspace for a :py:class:`~.SDM` matrix ``A`` in RREF.
|
| 1154 |
+
|
| 1155 |
+
The domain of the matrix can be any domain.
|
| 1156 |
+
|
| 1157 |
+
The matrix must already be in reduced row echelon form (RREF).
|
| 1158 |
+
|
| 1159 |
+
Examples
|
| 1160 |
+
========
|
| 1161 |
+
|
| 1162 |
+
>>> from sympy import QQ
|
| 1163 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1164 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0: QQ(2), 1: QQ(4)}}, (2, 2), QQ)
|
| 1165 |
+
>>> A_rref, pivots = A.rref()
|
| 1166 |
+
>>> A_null, nonpivots = A_rref.nullspace_from_rref(pivots)
|
| 1167 |
+
>>> A_null
|
| 1168 |
+
{0: {0: -2, 1: 1}}
|
| 1169 |
+
>>> pivots
|
| 1170 |
+
[0]
|
| 1171 |
+
>>> nonpivots
|
| 1172 |
+
[1]
|
| 1173 |
+
|
| 1174 |
+
See Also
|
| 1175 |
+
========
|
| 1176 |
+
|
| 1177 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace
|
| 1178 |
+
The higher-level function that would usually be called instead of
|
| 1179 |
+
calling this one directly.
|
| 1180 |
+
|
| 1181 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace_from_rref
|
| 1182 |
+
The higher-level direct equivalent of this function.
|
| 1183 |
+
|
| 1184 |
+
sympy.polys.matrices.ddm.DDM.nullspace_from_rref
|
| 1185 |
+
The equivalent function for dense :py:class:`~.DDM` matrices.
|
| 1186 |
+
|
| 1187 |
+
"""
|
| 1188 |
+
m, n = A.shape
|
| 1189 |
+
K = A.domain
|
| 1190 |
+
|
| 1191 |
+
if pivots is None:
|
| 1192 |
+
pivots = sorted(map(min, A.values()))
|
| 1193 |
+
|
| 1194 |
+
if not pivots:
|
| 1195 |
+
return A.eye((n, n), K), list(range(n))
|
| 1196 |
+
elif len(pivots) == n:
|
| 1197 |
+
return A.zeros((0, n), K), []
|
| 1198 |
+
|
| 1199 |
+
# In fraction-free RREF the nonzero entry inserted for the pivots is
|
| 1200 |
+
# not necessarily 1.
|
| 1201 |
+
pivot_val = A[0][pivots[0]]
|
| 1202 |
+
assert not K.is_zero(pivot_val)
|
| 1203 |
+
|
| 1204 |
+
pivots_set = set(pivots)
|
| 1205 |
+
|
| 1206 |
+
# Loop once over all nonzero entries making a map from column indices
|
| 1207 |
+
# to the nonzero entries in that column along with the row index of the
|
| 1208 |
+
# nonzero entry. This is basically the transpose of the matrix.
|
| 1209 |
+
nonzero_cols = defaultdict(list)
|
| 1210 |
+
for i, Ai in A.items():
|
| 1211 |
+
for j, Aij in Ai.items():
|
| 1212 |
+
nonzero_cols[j].append((i, Aij))
|
| 1213 |
+
|
| 1214 |
+
# Usually in SDM we want to avoid looping over the dimensions of the
|
| 1215 |
+
# matrix because it is optimised to support extremely sparse matrices.
|
| 1216 |
+
# Here in nullspace though every zero column becomes a nonzero column
|
| 1217 |
+
# so we need to loop once over the columns at least (range(n)) rather
|
| 1218 |
+
# than just the nonzero entries of the matrix. We can still avoid
|
| 1219 |
+
# an inner loop over the rows though by using the nonzero_cols map.
|
| 1220 |
+
basis = []
|
| 1221 |
+
nonpivots = []
|
| 1222 |
+
for j in range(n):
|
| 1223 |
+
if j in pivots_set:
|
| 1224 |
+
continue
|
| 1225 |
+
nonpivots.append(j)
|
| 1226 |
+
|
| 1227 |
+
vec = {j: pivot_val}
|
| 1228 |
+
for ip, Aij in nonzero_cols[j]:
|
| 1229 |
+
vec[pivots[ip]] = -Aij
|
| 1230 |
+
|
| 1231 |
+
basis.append(vec)
|
| 1232 |
+
|
| 1233 |
+
sdm = dict(enumerate(basis))
|
| 1234 |
+
A_null = A.new(sdm, (len(basis), n), K)
|
| 1235 |
+
|
| 1236 |
+
return (A_null, nonpivots)
|
| 1237 |
+
|
| 1238 |
+
def particular(A):
|
| 1239 |
+
ncols = A.shape[1]
|
| 1240 |
+
B, pivots, nzcols = sdm_irref(A)
|
| 1241 |
+
P = sdm_particular_from_rref(B, ncols, pivots)
|
| 1242 |
+
rep = {0:P} if P else {}
|
| 1243 |
+
return A.new(rep, (1, ncols-1), A.domain)
|
| 1244 |
+
|
| 1245 |
+
def hstack(A, *B):
|
| 1246 |
+
"""Horizontally stacks :py:class:`~.SDM` matrices.
|
| 1247 |
+
|
| 1248 |
+
Examples
|
| 1249 |
+
========
|
| 1250 |
+
|
| 1251 |
+
>>> from sympy import ZZ
|
| 1252 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1253 |
+
|
| 1254 |
+
>>> A = SDM({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}, (2, 2), ZZ)
|
| 1255 |
+
>>> B = SDM({0: {0: ZZ(5), 1: ZZ(6)}, 1: {0: ZZ(7), 1: ZZ(8)}}, (2, 2), ZZ)
|
| 1256 |
+
>>> A.hstack(B)
|
| 1257 |
+
{0: {0: 1, 1: 2, 2: 5, 3: 6}, 1: {0: 3, 1: 4, 2: 7, 3: 8}}
|
| 1258 |
+
|
| 1259 |
+
>>> C = SDM({0: {0: ZZ(9), 1: ZZ(10)}, 1: {0: ZZ(11), 1: ZZ(12)}}, (2, 2), ZZ)
|
| 1260 |
+
>>> A.hstack(B, C)
|
| 1261 |
+
{0: {0: 1, 1: 2, 2: 5, 3: 6, 4: 9, 5: 10}, 1: {0: 3, 1: 4, 2: 7, 3: 8, 4: 11, 5: 12}}
|
| 1262 |
+
"""
|
| 1263 |
+
Anew = dict(A.copy())
|
| 1264 |
+
rows, cols = A.shape
|
| 1265 |
+
domain = A.domain
|
| 1266 |
+
|
| 1267 |
+
for Bk in B:
|
| 1268 |
+
Bkrows, Bkcols = Bk.shape
|
| 1269 |
+
assert Bkrows == rows
|
| 1270 |
+
assert Bk.domain == domain
|
| 1271 |
+
|
| 1272 |
+
for i, Bki in Bk.items():
|
| 1273 |
+
Ai = Anew.get(i, None)
|
| 1274 |
+
if Ai is None:
|
| 1275 |
+
Anew[i] = Ai = {}
|
| 1276 |
+
for j, Bkij in Bki.items():
|
| 1277 |
+
Ai[j + cols] = Bkij
|
| 1278 |
+
cols += Bkcols
|
| 1279 |
+
|
| 1280 |
+
return A.new(Anew, (rows, cols), A.domain)
|
| 1281 |
+
|
| 1282 |
+
def vstack(A, *B):
|
| 1283 |
+
"""Vertically stacks :py:class:`~.SDM` matrices.
|
| 1284 |
+
|
| 1285 |
+
Examples
|
| 1286 |
+
========
|
| 1287 |
+
|
| 1288 |
+
>>> from sympy import ZZ
|
| 1289 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1290 |
+
|
| 1291 |
+
>>> A = SDM({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}, (2, 2), ZZ)
|
| 1292 |
+
>>> B = SDM({0: {0: ZZ(5), 1: ZZ(6)}, 1: {0: ZZ(7), 1: ZZ(8)}}, (2, 2), ZZ)
|
| 1293 |
+
>>> A.vstack(B)
|
| 1294 |
+
{0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}, 2: {0: 5, 1: 6}, 3: {0: 7, 1: 8}}
|
| 1295 |
+
|
| 1296 |
+
>>> C = SDM({0: {0: ZZ(9), 1: ZZ(10)}, 1: {0: ZZ(11), 1: ZZ(12)}}, (2, 2), ZZ)
|
| 1297 |
+
>>> A.vstack(B, C)
|
| 1298 |
+
{0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}, 2: {0: 5, 1: 6}, 3: {0: 7, 1: 8}, 4: {0: 9, 1: 10}, 5: {0: 11, 1: 12}}
|
| 1299 |
+
"""
|
| 1300 |
+
Anew = dict(A.copy())
|
| 1301 |
+
rows, cols = A.shape
|
| 1302 |
+
domain = A.domain
|
| 1303 |
+
|
| 1304 |
+
for Bk in B:
|
| 1305 |
+
Bkrows, Bkcols = Bk.shape
|
| 1306 |
+
assert Bkcols == cols
|
| 1307 |
+
assert Bk.domain == domain
|
| 1308 |
+
|
| 1309 |
+
for i, Bki in Bk.items():
|
| 1310 |
+
Anew[i + rows] = Bki
|
| 1311 |
+
rows += Bkrows
|
| 1312 |
+
|
| 1313 |
+
return A.new(Anew, (rows, cols), A.domain)
|
| 1314 |
+
|
| 1315 |
+
def applyfunc(self, func, domain):
|
| 1316 |
+
sdm = {i: {j: func(e) for j, e in row.items()} for i, row in self.items()}
|
| 1317 |
+
return self.new(sdm, self.shape, domain)
|
| 1318 |
+
|
| 1319 |
+
def charpoly(A):
|
| 1320 |
+
"""
|
| 1321 |
+
Returns the coefficients of the characteristic polynomial
|
| 1322 |
+
of the :py:class:`~.SDM` matrix. These elements will be domain elements.
|
| 1323 |
+
The domain of the elements will be same as domain of the :py:class:`~.SDM`.
|
| 1324 |
+
|
| 1325 |
+
Examples
|
| 1326 |
+
========
|
| 1327 |
+
|
| 1328 |
+
>>> from sympy import QQ, Symbol
|
| 1329 |
+
>>> from sympy.polys.matrices.sdm import SDM
|
| 1330 |
+
>>> from sympy.polys import Poly
|
| 1331 |
+
>>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
|
| 1332 |
+
>>> A.charpoly()
|
| 1333 |
+
[1, -5, -2]
|
| 1334 |
+
|
| 1335 |
+
We can create a polynomial using the
|
| 1336 |
+
coefficients using :py:class:`~.Poly`
|
| 1337 |
+
|
| 1338 |
+
>>> x = Symbol('x')
|
| 1339 |
+
>>> p = Poly(A.charpoly(), x, domain=A.domain)
|
| 1340 |
+
>>> p
|
| 1341 |
+
Poly(x**2 - 5*x - 2, x, domain='QQ')
|
| 1342 |
+
|
| 1343 |
+
"""
|
| 1344 |
+
K = A.domain
|
| 1345 |
+
n, _ = A.shape
|
| 1346 |
+
pdict = sdm_berk(A, n, K)
|
| 1347 |
+
plist = [K.zero] * (n + 1)
|
| 1348 |
+
for i, pi in pdict.items():
|
| 1349 |
+
plist[i] = pi
|
| 1350 |
+
return plist
|
| 1351 |
+
|
| 1352 |
+
def is_zero_matrix(self):
|
| 1353 |
+
"""
|
| 1354 |
+
Says whether this matrix has all zero entries.
|
| 1355 |
+
"""
|
| 1356 |
+
return not self
|
| 1357 |
+
|
| 1358 |
+
def is_upper(self):
|
| 1359 |
+
"""
|
| 1360 |
+
Says whether this matrix is upper-triangular. True can be returned
|
| 1361 |
+
even if the matrix is not square.
|
| 1362 |
+
"""
|
| 1363 |
+
return all(i <= j for i, row in self.items() for j in row)
|
| 1364 |
+
|
| 1365 |
+
def is_lower(self):
|
| 1366 |
+
"""
|
| 1367 |
+
Says whether this matrix is lower-triangular. True can be returned
|
| 1368 |
+
even if the matrix is not square.
|
| 1369 |
+
"""
|
| 1370 |
+
return all(i >= j for i, row in self.items() for j in row)
|
| 1371 |
+
|
| 1372 |
+
def is_diagonal(self):
|
| 1373 |
+
"""
|
| 1374 |
+
Says whether this matrix is diagonal. True can be returned
|
| 1375 |
+
even if the matrix is not square.
|
| 1376 |
+
"""
|
| 1377 |
+
return all(i == j for i, row in self.items() for j in row)
|
| 1378 |
+
|
| 1379 |
+
def diagonal(self):
|
| 1380 |
+
"""
|
| 1381 |
+
Returns the diagonal of the matrix as a list.
|
| 1382 |
+
"""
|
| 1383 |
+
m, n = self.shape
|
| 1384 |
+
zero = self.domain.zero
|
| 1385 |
+
return [row.get(i, zero) for i, row in self.items() if i < n]
|
| 1386 |
+
|
| 1387 |
+
def lll(A, delta=QQ(3, 4)):
|
| 1388 |
+
"""
|
| 1389 |
+
Returns the LLL-reduced basis for the :py:class:`~.SDM` matrix.
|
| 1390 |
+
"""
|
| 1391 |
+
return A.to_dfm_or_ddm().lll(delta=delta).to_sdm()
|
| 1392 |
+
|
| 1393 |
+
def lll_transform(A, delta=QQ(3, 4)):
|
| 1394 |
+
"""
|
| 1395 |
+
Returns the LLL-reduced basis and transformation matrix.
|
| 1396 |
+
"""
|
| 1397 |
+
reduced, transform = A.to_dfm_or_ddm().lll_transform(delta=delta)
|
| 1398 |
+
return reduced.to_sdm(), transform.to_sdm()
|
| 1399 |
+
|
| 1400 |
+
|
| 1401 |
+
def binop_dict(A, B, fab, fa, fb):
|
| 1402 |
+
Anz, Bnz = set(A), set(B)
|
| 1403 |
+
C = {}
|
| 1404 |
+
|
| 1405 |
+
for i in Anz & Bnz:
|
| 1406 |
+
Ai, Bi = A[i], B[i]
|
| 1407 |
+
Ci = {}
|
| 1408 |
+
Anzi, Bnzi = set(Ai), set(Bi)
|
| 1409 |
+
for j in Anzi & Bnzi:
|
| 1410 |
+
Cij = fab(Ai[j], Bi[j])
|
| 1411 |
+
if Cij:
|
| 1412 |
+
Ci[j] = Cij
|
| 1413 |
+
for j in Anzi - Bnzi:
|
| 1414 |
+
Cij = fa(Ai[j])
|
| 1415 |
+
if Cij:
|
| 1416 |
+
Ci[j] = Cij
|
| 1417 |
+
for j in Bnzi - Anzi:
|
| 1418 |
+
Cij = fb(Bi[j])
|
| 1419 |
+
if Cij:
|
| 1420 |
+
Ci[j] = Cij
|
| 1421 |
+
if Ci:
|
| 1422 |
+
C[i] = Ci
|
| 1423 |
+
|
| 1424 |
+
for i in Anz - Bnz:
|
| 1425 |
+
Ai = A[i]
|
| 1426 |
+
Ci = {}
|
| 1427 |
+
for j, Aij in Ai.items():
|
| 1428 |
+
Cij = fa(Aij)
|
| 1429 |
+
if Cij:
|
| 1430 |
+
Ci[j] = Cij
|
| 1431 |
+
if Ci:
|
| 1432 |
+
C[i] = Ci
|
| 1433 |
+
|
| 1434 |
+
for i in Bnz - Anz:
|
| 1435 |
+
Bi = B[i]
|
| 1436 |
+
Ci = {}
|
| 1437 |
+
for j, Bij in Bi.items():
|
| 1438 |
+
Cij = fb(Bij)
|
| 1439 |
+
if Cij:
|
| 1440 |
+
Ci[j] = Cij
|
| 1441 |
+
if Ci:
|
| 1442 |
+
C[i] = Ci
|
| 1443 |
+
|
| 1444 |
+
return C
|
| 1445 |
+
|
| 1446 |
+
|
| 1447 |
+
def unop_dict(A, f):
|
| 1448 |
+
B = {}
|
| 1449 |
+
for i, Ai in A.items():
|
| 1450 |
+
Bi = {}
|
| 1451 |
+
for j, Aij in Ai.items():
|
| 1452 |
+
Bij = f(Aij)
|
| 1453 |
+
if Bij:
|
| 1454 |
+
Bi[j] = Bij
|
| 1455 |
+
if Bi:
|
| 1456 |
+
B[i] = Bi
|
| 1457 |
+
return B
|
| 1458 |
+
|
| 1459 |
+
|
| 1460 |
+
def sdm_transpose(M):
|
| 1461 |
+
MT = {}
|
| 1462 |
+
for i, Mi in M.items():
|
| 1463 |
+
for j, Mij in Mi.items():
|
| 1464 |
+
try:
|
| 1465 |
+
MT[j][i] = Mij
|
| 1466 |
+
except KeyError:
|
| 1467 |
+
MT[j] = {i: Mij}
|
| 1468 |
+
return MT
|
| 1469 |
+
|
| 1470 |
+
|
| 1471 |
+
def sdm_dotvec(A, B, K):
|
| 1472 |
+
return K.sum(A[j] * B[j] for j in A.keys() & B.keys())
|
| 1473 |
+
|
| 1474 |
+
|
| 1475 |
+
def sdm_matvecmul(A, B, K):
|
| 1476 |
+
C = {}
|
| 1477 |
+
for i, Ai in A.items():
|
| 1478 |
+
Ci = sdm_dotvec(Ai, B, K)
|
| 1479 |
+
if Ci:
|
| 1480 |
+
C[i] = Ci
|
| 1481 |
+
return C
|
| 1482 |
+
|
| 1483 |
+
|
| 1484 |
+
def sdm_matmul(A, B, K, m, o):
|
| 1485 |
+
#
|
| 1486 |
+
# Should be fast if A and B are very sparse.
|
| 1487 |
+
# Consider e.g. A = B = eye(1000).
|
| 1488 |
+
#
|
| 1489 |
+
# The idea here is that we compute C = A*B in terms of the rows of C and
|
| 1490 |
+
# B since the dict of dicts representation naturally stores the matrix as
|
| 1491 |
+
# rows. The ith row of C (Ci) is equal to the sum of Aik * Bk where Bk is
|
| 1492 |
+
# the kth row of B. The algorithm below loops over each nonzero element
|
| 1493 |
+
# Aik of A and if the corresponding row Bj is nonzero then we do
|
| 1494 |
+
# Ci += Aik * Bk.
|
| 1495 |
+
# To make this more efficient we don't need to loop over all elements Aik.
|
| 1496 |
+
# Instead for each row Ai we compute the intersection of the nonzero
|
| 1497 |
+
# columns in Ai with the nonzero rows in B. That gives the k such that
|
| 1498 |
+
# Aik and Bk are both nonzero. In Python the intersection of two sets
|
| 1499 |
+
# of int can be computed very efficiently.
|
| 1500 |
+
#
|
| 1501 |
+
if K.is_EXRAW:
|
| 1502 |
+
return sdm_matmul_exraw(A, B, K, m, o)
|
| 1503 |
+
|
| 1504 |
+
C = {}
|
| 1505 |
+
B_knz = set(B)
|
| 1506 |
+
for i, Ai in A.items():
|
| 1507 |
+
Ci = {}
|
| 1508 |
+
Ai_knz = set(Ai)
|
| 1509 |
+
for k in Ai_knz & B_knz:
|
| 1510 |
+
Aik = Ai[k]
|
| 1511 |
+
for j, Bkj in B[k].items():
|
| 1512 |
+
Cij = Ci.get(j, None)
|
| 1513 |
+
if Cij is not None:
|
| 1514 |
+
Cij = Cij + Aik * Bkj
|
| 1515 |
+
if Cij:
|
| 1516 |
+
Ci[j] = Cij
|
| 1517 |
+
else:
|
| 1518 |
+
Ci.pop(j)
|
| 1519 |
+
else:
|
| 1520 |
+
Cij = Aik * Bkj
|
| 1521 |
+
if Cij:
|
| 1522 |
+
Ci[j] = Cij
|
| 1523 |
+
if Ci:
|
| 1524 |
+
C[i] = Ci
|
| 1525 |
+
return C
|
| 1526 |
+
|
| 1527 |
+
|
| 1528 |
+
def sdm_matmul_exraw(A, B, K, m, o):
|
| 1529 |
+
#
|
| 1530 |
+
# Like sdm_matmul above except that:
|
| 1531 |
+
#
|
| 1532 |
+
# - Handles cases like 0*oo -> nan (sdm_matmul skips multiplication by zero)
|
| 1533 |
+
# - Uses K.sum (Add(*items)) for efficient addition of Expr
|
| 1534 |
+
#
|
| 1535 |
+
zero = K.zero
|
| 1536 |
+
C = {}
|
| 1537 |
+
B_knz = set(B)
|
| 1538 |
+
for i, Ai in A.items():
|
| 1539 |
+
Ci_list = defaultdict(list)
|
| 1540 |
+
Ai_knz = set(Ai)
|
| 1541 |
+
|
| 1542 |
+
# Nonzero row/column pair
|
| 1543 |
+
for k in Ai_knz & B_knz:
|
| 1544 |
+
Aik = Ai[k]
|
| 1545 |
+
if zero * Aik == zero:
|
| 1546 |
+
# This is the main inner loop:
|
| 1547 |
+
for j, Bkj in B[k].items():
|
| 1548 |
+
Ci_list[j].append(Aik * Bkj)
|
| 1549 |
+
else:
|
| 1550 |
+
for j in range(o):
|
| 1551 |
+
Ci_list[j].append(Aik * B[k].get(j, zero))
|
| 1552 |
+
|
| 1553 |
+
# Zero row in B, check for infinities in A
|
| 1554 |
+
for k in Ai_knz - B_knz:
|
| 1555 |
+
zAik = zero * Ai[k]
|
| 1556 |
+
if zAik != zero:
|
| 1557 |
+
for j in range(o):
|
| 1558 |
+
Ci_list[j].append(zAik)
|
| 1559 |
+
|
| 1560 |
+
# Add terms using K.sum (Add(*terms)) for efficiency
|
| 1561 |
+
Ci = {}
|
| 1562 |
+
for j, Cij_list in Ci_list.items():
|
| 1563 |
+
Cij = K.sum(Cij_list)
|
| 1564 |
+
if Cij:
|
| 1565 |
+
Ci[j] = Cij
|
| 1566 |
+
if Ci:
|
| 1567 |
+
C[i] = Ci
|
| 1568 |
+
|
| 1569 |
+
# Find all infinities in B
|
| 1570 |
+
for k, Bk in B.items():
|
| 1571 |
+
for j, Bkj in Bk.items():
|
| 1572 |
+
if zero * Bkj != zero:
|
| 1573 |
+
for i in range(m):
|
| 1574 |
+
Aik = A.get(i, {}).get(k, zero)
|
| 1575 |
+
# If Aik is not zero then this was handled above
|
| 1576 |
+
if Aik == zero:
|
| 1577 |
+
Ci = C.get(i, {})
|
| 1578 |
+
Cij = Ci.get(j, zero) + Aik * Bkj
|
| 1579 |
+
if Cij != zero:
|
| 1580 |
+
Ci[j] = Cij
|
| 1581 |
+
C[i] = Ci
|
| 1582 |
+
else:
|
| 1583 |
+
Ci.pop(j, None)
|
| 1584 |
+
if Ci:
|
| 1585 |
+
C[i] = Ci
|
| 1586 |
+
else:
|
| 1587 |
+
C.pop(i, None)
|
| 1588 |
+
|
| 1589 |
+
return C
|
| 1590 |
+
|
| 1591 |
+
|
| 1592 |
+
def sdm_irref(A):
|
| 1593 |
+
"""RREF and pivots of a sparse matrix *A*.
|
| 1594 |
+
|
| 1595 |
+
Compute the reduced row echelon form (RREF) of the matrix *A* and return a
|
| 1596 |
+
list of the pivot columns. This routine does not work in place and leaves
|
| 1597 |
+
the original matrix *A* unmodified.
|
| 1598 |
+
|
| 1599 |
+
The domain of the matrix must be a field.
|
| 1600 |
+
|
| 1601 |
+
Examples
|
| 1602 |
+
========
|
| 1603 |
+
|
| 1604 |
+
This routine works with a dict of dicts sparse representation of a matrix:
|
| 1605 |
+
|
| 1606 |
+
>>> from sympy import QQ
|
| 1607 |
+
>>> from sympy.polys.matrices.sdm import sdm_irref
|
| 1608 |
+
>>> A = {0: {0: QQ(1), 1: QQ(2)}, 1: {0: QQ(3), 1: QQ(4)}}
|
| 1609 |
+
>>> Arref, pivots, _ = sdm_irref(A)
|
| 1610 |
+
>>> Arref
|
| 1611 |
+
{0: {0: 1}, 1: {1: 1}}
|
| 1612 |
+
>>> pivots
|
| 1613 |
+
[0, 1]
|
| 1614 |
+
|
| 1615 |
+
The analogous calculation with :py:class:`~.MutableDenseMatrix` would be
|
| 1616 |
+
|
| 1617 |
+
>>> from sympy import Matrix
|
| 1618 |
+
>>> M = Matrix([[1, 2], [3, 4]])
|
| 1619 |
+
>>> Mrref, pivots = M.rref()
|
| 1620 |
+
>>> Mrref
|
| 1621 |
+
Matrix([
|
| 1622 |
+
[1, 0],
|
| 1623 |
+
[0, 1]])
|
| 1624 |
+
>>> pivots
|
| 1625 |
+
(0, 1)
|
| 1626 |
+
|
| 1627 |
+
Notes
|
| 1628 |
+
=====
|
| 1629 |
+
|
| 1630 |
+
The cost of this algorithm is determined purely by the nonzero elements of
|
| 1631 |
+
the matrix. No part of the cost of any step in this algorithm depends on
|
| 1632 |
+
the number of rows or columns in the matrix. No step depends even on the
|
| 1633 |
+
number of nonzero rows apart from the primary loop over those rows. The
|
| 1634 |
+
implementation is much faster than ddm_rref for sparse matrices. In fact
|
| 1635 |
+
at the time of writing it is also (slightly) faster than the dense
|
| 1636 |
+
implementation even if the input is a fully dense matrix so it seems to be
|
| 1637 |
+
faster in all cases.
|
| 1638 |
+
|
| 1639 |
+
The elements of the matrix should support exact division with ``/``. For
|
| 1640 |
+
example elements of any domain that is a field (e.g. ``QQ``) should be
|
| 1641 |
+
fine. No attempt is made to handle inexact arithmetic.
|
| 1642 |
+
|
| 1643 |
+
See Also
|
| 1644 |
+
========
|
| 1645 |
+
|
| 1646 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.rref
|
| 1647 |
+
The higher-level function that would normally be used to call this
|
| 1648 |
+
routine.
|
| 1649 |
+
sympy.polys.matrices.dense.ddm_irref
|
| 1650 |
+
The dense equivalent of this routine.
|
| 1651 |
+
sdm_rref_den
|
| 1652 |
+
Fraction-free version of this routine.
|
| 1653 |
+
"""
|
| 1654 |
+
#
|
| 1655 |
+
# Any zeros in the matrix are not stored at all so an element is zero if
|
| 1656 |
+
# its row dict has no index at that key. A row is entirely zero if its
|
| 1657 |
+
# row index is not in the outer dict. Since rref reorders the rows and
|
| 1658 |
+
# removes zero rows we can completely discard the row indices. The first
|
| 1659 |
+
# step then copies the row dicts into a list sorted by the index of the
|
| 1660 |
+
# first nonzero column in each row.
|
| 1661 |
+
#
|
| 1662 |
+
# The algorithm then processes each row Ai one at a time. Previously seen
|
| 1663 |
+
# rows are used to cancel their pivot columns from Ai. Then a pivot from
|
| 1664 |
+
# Ai is chosen and is cancelled from all previously seen rows. At this
|
| 1665 |
+
# point Ai joins the previously seen rows. Once all rows are seen all
|
| 1666 |
+
# elimination has occurred and the rows are sorted by pivot column index.
|
| 1667 |
+
#
|
| 1668 |
+
# The previously seen rows are stored in two separate groups. The reduced
|
| 1669 |
+
# group consists of all rows that have been reduced to a single nonzero
|
| 1670 |
+
# element (the pivot). There is no need to attempt any further reduction
|
| 1671 |
+
# with these. Rows that still have other nonzeros need to be considered
|
| 1672 |
+
# when Ai is cancelled from the previously seen rows.
|
| 1673 |
+
#
|
| 1674 |
+
# A dict nonzerocolumns is used to map from a column index to a set of
|
| 1675 |
+
# previously seen rows that still have a nonzero element in that column.
|
| 1676 |
+
# This means that we can cancel the pivot from Ai into the previously seen
|
| 1677 |
+
# rows without needing to loop over each row that might have a zero in
|
| 1678 |
+
# that column.
|
| 1679 |
+
#
|
| 1680 |
+
|
| 1681 |
+
# Row dicts sorted by index of first nonzero column
|
| 1682 |
+
# (Maybe sorting is not needed/useful.)
|
| 1683 |
+
Arows = sorted((Ai.copy() for Ai in A.values()), key=min)
|
| 1684 |
+
|
| 1685 |
+
# Each processed row has an associated pivot column.
|
| 1686 |
+
# pivot_row_map maps from the pivot column index to the row dict.
|
| 1687 |
+
# This means that we can represent a set of rows purely as a set of their
|
| 1688 |
+
# pivot indices.
|
| 1689 |
+
pivot_row_map = {}
|
| 1690 |
+
|
| 1691 |
+
# Set of pivot indices for rows that are fully reduced to a single nonzero.
|
| 1692 |
+
reduced_pivots = set()
|
| 1693 |
+
|
| 1694 |
+
# Set of pivot indices for rows not fully reduced
|
| 1695 |
+
nonreduced_pivots = set()
|
| 1696 |
+
|
| 1697 |
+
# Map from column index to a set of pivot indices representing the rows
|
| 1698 |
+
# that have a nonzero at that column.
|
| 1699 |
+
nonzero_columns = defaultdict(set)
|
| 1700 |
+
|
| 1701 |
+
while Arows:
|
| 1702 |
+
# Select pivot element and row
|
| 1703 |
+
Ai = Arows.pop()
|
| 1704 |
+
|
| 1705 |
+
# Nonzero columns from fully reduced pivot rows can be removed
|
| 1706 |
+
Ai = {j: Aij for j, Aij in Ai.items() if j not in reduced_pivots}
|
| 1707 |
+
|
| 1708 |
+
# Others require full row cancellation
|
| 1709 |
+
for j in nonreduced_pivots & set(Ai):
|
| 1710 |
+
Aj = pivot_row_map[j]
|
| 1711 |
+
Aij = Ai[j]
|
| 1712 |
+
Ainz = set(Ai)
|
| 1713 |
+
Ajnz = set(Aj)
|
| 1714 |
+
for k in Ajnz - Ainz:
|
| 1715 |
+
Ai[k] = - Aij * Aj[k]
|
| 1716 |
+
Ai.pop(j)
|
| 1717 |
+
Ainz.remove(j)
|
| 1718 |
+
for k in Ajnz & Ainz:
|
| 1719 |
+
Aik = Ai[k] - Aij * Aj[k]
|
| 1720 |
+
if Aik:
|
| 1721 |
+
Ai[k] = Aik
|
| 1722 |
+
else:
|
| 1723 |
+
Ai.pop(k)
|
| 1724 |
+
|
| 1725 |
+
# We have now cancelled previously seen pivots from Ai.
|
| 1726 |
+
# If it is zero then discard it.
|
| 1727 |
+
if not Ai:
|
| 1728 |
+
continue
|
| 1729 |
+
|
| 1730 |
+
# Choose a pivot from Ai:
|
| 1731 |
+
j = min(Ai)
|
| 1732 |
+
Aij = Ai[j]
|
| 1733 |
+
pivot_row_map[j] = Ai
|
| 1734 |
+
Ainz = set(Ai)
|
| 1735 |
+
|
| 1736 |
+
# Normalise the pivot row to make the pivot 1.
|
| 1737 |
+
#
|
| 1738 |
+
# This approach is slow for some domains. Cross cancellation might be
|
| 1739 |
+
# better for e.g. QQ(x) with division delayed to the final steps.
|
| 1740 |
+
Aijinv = Aij**-1
|
| 1741 |
+
for l in Ai:
|
| 1742 |
+
Ai[l] *= Aijinv
|
| 1743 |
+
|
| 1744 |
+
# Use Aij to cancel column j from all previously seen rows
|
| 1745 |
+
for k in nonzero_columns.pop(j, ()):
|
| 1746 |
+
Ak = pivot_row_map[k]
|
| 1747 |
+
Akj = Ak[j]
|
| 1748 |
+
Aknz = set(Ak)
|
| 1749 |
+
for l in Ainz - Aknz:
|
| 1750 |
+
Ak[l] = - Akj * Ai[l]
|
| 1751 |
+
nonzero_columns[l].add(k)
|
| 1752 |
+
Ak.pop(j)
|
| 1753 |
+
Aknz.remove(j)
|
| 1754 |
+
for l in Ainz & Aknz:
|
| 1755 |
+
Akl = Ak[l] - Akj * Ai[l]
|
| 1756 |
+
if Akl:
|
| 1757 |
+
Ak[l] = Akl
|
| 1758 |
+
else:
|
| 1759 |
+
# Drop nonzero elements
|
| 1760 |
+
Ak.pop(l)
|
| 1761 |
+
if l != j:
|
| 1762 |
+
nonzero_columns[l].remove(k)
|
| 1763 |
+
if len(Ak) == 1:
|
| 1764 |
+
reduced_pivots.add(k)
|
| 1765 |
+
nonreduced_pivots.remove(k)
|
| 1766 |
+
|
| 1767 |
+
if len(Ai) == 1:
|
| 1768 |
+
reduced_pivots.add(j)
|
| 1769 |
+
else:
|
| 1770 |
+
nonreduced_pivots.add(j)
|
| 1771 |
+
for l in Ai:
|
| 1772 |
+
if l != j:
|
| 1773 |
+
nonzero_columns[l].add(j)
|
| 1774 |
+
|
| 1775 |
+
# All done!
|
| 1776 |
+
pivots = sorted(reduced_pivots | nonreduced_pivots)
|
| 1777 |
+
pivot2row = {p: n for n, p in enumerate(pivots)}
|
| 1778 |
+
nonzero_columns = {c: {pivot2row[p] for p in s} for c, s in nonzero_columns.items()}
|
| 1779 |
+
rows = [pivot_row_map[i] for i in pivots]
|
| 1780 |
+
rref = dict(enumerate(rows))
|
| 1781 |
+
return rref, pivots, nonzero_columns
|
| 1782 |
+
|
| 1783 |
+
|
| 1784 |
+
def sdm_rref_den(A, K):
|
| 1785 |
+
"""
|
| 1786 |
+
Return the reduced row echelon form (RREF) of A with denominator.
|
| 1787 |
+
|
| 1788 |
+
The RREF is computed using fraction-free Gauss-Jordan elimination.
|
| 1789 |
+
|
| 1790 |
+
Explanation
|
| 1791 |
+
===========
|
| 1792 |
+
|
| 1793 |
+
The algorithm used is the fraction-free version of Gauss-Jordan elimination
|
| 1794 |
+
described as FFGJ in [1]_. Here it is modified to handle zero or missing
|
| 1795 |
+
pivots and to avoid redundant arithmetic. This implementation is also
|
| 1796 |
+
optimized for sparse matrices.
|
| 1797 |
+
|
| 1798 |
+
The domain $K$ must support exact division (``K.exquo``) but does not need
|
| 1799 |
+
to be a field. This method is suitable for most exact rings and fields like
|
| 1800 |
+
:ref:`ZZ`, :ref:`QQ` and :ref:`QQ(a)`. In the case of :ref:`QQ` or
|
| 1801 |
+
:ref:`K(x)` it might be more efficient to clear denominators and use
|
| 1802 |
+
:ref:`ZZ` or :ref:`K[x]` instead.
|
| 1803 |
+
|
| 1804 |
+
For inexact domains like :ref:`RR` and :ref:`CC` use ``ddm_irref`` instead.
|
| 1805 |
+
|
| 1806 |
+
Examples
|
| 1807 |
+
========
|
| 1808 |
+
|
| 1809 |
+
>>> from sympy.polys.matrices.sdm import sdm_rref_den
|
| 1810 |
+
>>> from sympy.polys.domains import ZZ
|
| 1811 |
+
>>> A = {0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}
|
| 1812 |
+
>>> A_rref, den, pivots = sdm_rref_den(A, ZZ)
|
| 1813 |
+
>>> A_rref
|
| 1814 |
+
{0: {0: -2}, 1: {1: -2}}
|
| 1815 |
+
>>> den
|
| 1816 |
+
-2
|
| 1817 |
+
>>> pivots
|
| 1818 |
+
[0, 1]
|
| 1819 |
+
|
| 1820 |
+
See Also
|
| 1821 |
+
========
|
| 1822 |
+
|
| 1823 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
|
| 1824 |
+
Higher-level interface to ``sdm_rref_den`` that would usually be used
|
| 1825 |
+
instead of calling this function directly.
|
| 1826 |
+
sympy.polys.matrices.sdm.sdm_rref_den
|
| 1827 |
+
The ``SDM`` method that uses this function.
|
| 1828 |
+
sdm_irref
|
| 1829 |
+
Computes RREF using field division.
|
| 1830 |
+
ddm_irref_den
|
| 1831 |
+
The dense version of this algorithm.
|
| 1832 |
+
|
| 1833 |
+
References
|
| 1834 |
+
==========
|
| 1835 |
+
|
| 1836 |
+
.. [1] Fraction-free algorithms for linear and polynomial equations.
|
| 1837 |
+
George C. Nakos , Peter R. Turner , Robert M. Williams.
|
| 1838 |
+
https://dl.acm.org/doi/10.1145/271130.271133
|
| 1839 |
+
"""
|
| 1840 |
+
#
|
| 1841 |
+
# We represent each row of the matrix as a dict mapping column indices to
|
| 1842 |
+
# nonzero elements. We will build the RREF matrix starting from an empty
|
| 1843 |
+
# matrix and appending one row at a time. At each step we will have the
|
| 1844 |
+
# RREF of the rows we have processed so far.
|
| 1845 |
+
#
|
| 1846 |
+
# Our representation of the RREF divides it into three parts:
|
| 1847 |
+
#
|
| 1848 |
+
# 1. Fully reduced rows having only a single nonzero element (the pivot).
|
| 1849 |
+
# 2. Partially reduced rows having nonzeros after the pivot.
|
| 1850 |
+
# 3. The current denominator and divisor.
|
| 1851 |
+
#
|
| 1852 |
+
# For example if the incremental RREF might be:
|
| 1853 |
+
#
|
| 1854 |
+
# [2, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
| 1855 |
+
# [0, 0, 2, 0, 0, 0, 7, 0, 0, 0]
|
| 1856 |
+
# [0, 0, 0, 0, 0, 2, 0, 0, 0, 0]
|
| 1857 |
+
# [0, 0, 0, 0, 0, 0, 0, 2, 0, 0]
|
| 1858 |
+
# [0, 0, 0, 0, 0, 0, 0, 0, 2, 0]
|
| 1859 |
+
#
|
| 1860 |
+
# Here the second row is partially reduced and the other rows are fully
|
| 1861 |
+
# reduced. The denominator would be 2 in this case. We distinguish the
|
| 1862 |
+
# fully reduced rows because we can handle them more efficiently when
|
| 1863 |
+
# adding a new row.
|
| 1864 |
+
#
|
| 1865 |
+
# When adding a new row we need to multiply it by the current denominator.
|
| 1866 |
+
# Then we reduce the new row by cross cancellation with the previous rows.
|
| 1867 |
+
# Then if it is not reduced to zero we take its leading entry as the new
|
| 1868 |
+
# pivot, cross cancel the new row from the previous rows and update the
|
| 1869 |
+
# denominator. In the fraction-free version this last step requires
|
| 1870 |
+
# multiplying and dividing the whole matrix by the new pivot and the
|
| 1871 |
+
# current divisor. The advantage of building the RREF one row at a time is
|
| 1872 |
+
# that in the sparse case we only need to work with the relatively sparse
|
| 1873 |
+
# upper rows of the matrix. The simple version of FFGJ in [1] would
|
| 1874 |
+
# multiply and divide all the dense lower rows at each step.
|
| 1875 |
+
|
| 1876 |
+
# Handle the trivial cases.
|
| 1877 |
+
if not A:
|
| 1878 |
+
return ({}, K.one, [])
|
| 1879 |
+
elif len(A) == 1:
|
| 1880 |
+
Ai, = A.values()
|
| 1881 |
+
j = min(Ai)
|
| 1882 |
+
Aij = Ai[j]
|
| 1883 |
+
return ({0: Ai.copy()}, Aij, [j])
|
| 1884 |
+
|
| 1885 |
+
# For inexact domains like RR[x] we use quo and discard the remainder.
|
| 1886 |
+
# Maybe it would be better for K.exquo to do this automatically.
|
| 1887 |
+
if K.is_Exact:
|
| 1888 |
+
exquo = K.exquo
|
| 1889 |
+
else:
|
| 1890 |
+
exquo = K.quo
|
| 1891 |
+
|
| 1892 |
+
# Make sure we have the rows in order to make this deterministic from the
|
| 1893 |
+
# outset.
|
| 1894 |
+
_, rows_in_order = zip(*sorted(A.items()))
|
| 1895 |
+
|
| 1896 |
+
col_to_row_reduced = {}
|
| 1897 |
+
col_to_row_unreduced = {}
|
| 1898 |
+
reduced = col_to_row_reduced.keys()
|
| 1899 |
+
unreduced = col_to_row_unreduced.keys()
|
| 1900 |
+
|
| 1901 |
+
# Our representation of the RREF so far.
|
| 1902 |
+
A_rref_rows = []
|
| 1903 |
+
denom = None
|
| 1904 |
+
divisor = None
|
| 1905 |
+
|
| 1906 |
+
# The rows that remain to be added to the RREF. These are sorted by the
|
| 1907 |
+
# column index of their leading entry. Note that sorted() is stable so the
|
| 1908 |
+
# previous sort by unique row index is still needed to make this
|
| 1909 |
+
# deterministic (there may be multiple rows with the same leading column).
|
| 1910 |
+
A_rows = sorted(rows_in_order, key=min)
|
| 1911 |
+
|
| 1912 |
+
for Ai in A_rows:
|
| 1913 |
+
|
| 1914 |
+
# All fully reduced columns can be immediately discarded.
|
| 1915 |
+
Ai = {j: Aij for j, Aij in Ai.items() if j not in reduced}
|
| 1916 |
+
|
| 1917 |
+
# We need to multiply the new row by the current denominator to bring
|
| 1918 |
+
# it into the same scale as the previous rows and then cross-cancel to
|
| 1919 |
+
# reduce it wrt the previous unreduced rows. All pivots in the previous
|
| 1920 |
+
# rows are equal to denom so the coefficients we need to make a linear
|
| 1921 |
+
# combination of the previous rows to cancel into the new row are just
|
| 1922 |
+
# the ones that are already in the new row *before* we multiply by
|
| 1923 |
+
# denom. We compute that linear combination first and then multiply the
|
| 1924 |
+
# new row by denom before subtraction.
|
| 1925 |
+
Ai_cancel = {}
|
| 1926 |
+
|
| 1927 |
+
for j in unreduced & Ai.keys():
|
| 1928 |
+
# Remove the pivot column from the new row since it would become
|
| 1929 |
+
# zero anyway.
|
| 1930 |
+
Aij = Ai.pop(j)
|
| 1931 |
+
|
| 1932 |
+
Aj = A_rref_rows[col_to_row_unreduced[j]]
|
| 1933 |
+
|
| 1934 |
+
for k, Ajk in Aj.items():
|
| 1935 |
+
Aik_cancel = Ai_cancel.get(k)
|
| 1936 |
+
if Aik_cancel is None:
|
| 1937 |
+
Ai_cancel[k] = Aij * Ajk
|
| 1938 |
+
else:
|
| 1939 |
+
Aik_cancel = Aik_cancel + Aij * Ajk
|
| 1940 |
+
if Aik_cancel:
|
| 1941 |
+
Ai_cancel[k] = Aik_cancel
|
| 1942 |
+
else:
|
| 1943 |
+
Ai_cancel.pop(k)
|
| 1944 |
+
|
| 1945 |
+
# Multiply the new row by the current denominator and subtract.
|
| 1946 |
+
Ai_nz = set(Ai)
|
| 1947 |
+
Ai_cancel_nz = set(Ai_cancel)
|
| 1948 |
+
|
| 1949 |
+
d = denom or K.one
|
| 1950 |
+
|
| 1951 |
+
for k in Ai_cancel_nz - Ai_nz:
|
| 1952 |
+
Ai[k] = -Ai_cancel[k]
|
| 1953 |
+
|
| 1954 |
+
for k in Ai_nz - Ai_cancel_nz:
|
| 1955 |
+
Ai[k] = Ai[k] * d
|
| 1956 |
+
|
| 1957 |
+
for k in Ai_cancel_nz & Ai_nz:
|
| 1958 |
+
Aik = Ai[k] * d - Ai_cancel[k]
|
| 1959 |
+
if Aik:
|
| 1960 |
+
Ai[k] = Aik
|
| 1961 |
+
else:
|
| 1962 |
+
Ai.pop(k)
|
| 1963 |
+
|
| 1964 |
+
# Now Ai has the same scale as the other rows and is reduced wrt the
|
| 1965 |
+
# unreduced rows.
|
| 1966 |
+
|
| 1967 |
+
# If the row is reduced to zero then discard it.
|
| 1968 |
+
if not Ai:
|
| 1969 |
+
continue
|
| 1970 |
+
|
| 1971 |
+
# Choose a pivot for this row.
|
| 1972 |
+
j = min(Ai)
|
| 1973 |
+
Aij = Ai.pop(j)
|
| 1974 |
+
|
| 1975 |
+
# Cross cancel the unreduced rows by the new row.
|
| 1976 |
+
# a[k][l] = (a[i][j]*a[k][l] - a[k][j]*a[i][l]) / divisor
|
| 1977 |
+
for pk, k in list(col_to_row_unreduced.items()):
|
| 1978 |
+
|
| 1979 |
+
Ak = A_rref_rows[k]
|
| 1980 |
+
|
| 1981 |
+
if j not in Ak:
|
| 1982 |
+
# This row is already reduced wrt the new row but we need to
|
| 1983 |
+
# bring it to the same scale as the new denominator. This step
|
| 1984 |
+
# is not needed in sdm_irref.
|
| 1985 |
+
for l, Akl in Ak.items():
|
| 1986 |
+
Akl = Akl * Aij
|
| 1987 |
+
if divisor is not None:
|
| 1988 |
+
Akl = exquo(Akl, divisor)
|
| 1989 |
+
Ak[l] = Akl
|
| 1990 |
+
continue
|
| 1991 |
+
|
| 1992 |
+
Akj = Ak.pop(j)
|
| 1993 |
+
Ai_nz = set(Ai)
|
| 1994 |
+
Ak_nz = set(Ak)
|
| 1995 |
+
|
| 1996 |
+
for l in Ai_nz - Ak_nz:
|
| 1997 |
+
Ak[l] = - Akj * Ai[l]
|
| 1998 |
+
if divisor is not None:
|
| 1999 |
+
Ak[l] = exquo(Ak[l], divisor)
|
| 2000 |
+
|
| 2001 |
+
# This loop also not needed in sdm_irref.
|
| 2002 |
+
for l in Ak_nz - Ai_nz:
|
| 2003 |
+
Ak[l] = Aij * Ak[l]
|
| 2004 |
+
if divisor is not None:
|
| 2005 |
+
Ak[l] = exquo(Ak[l], divisor)
|
| 2006 |
+
|
| 2007 |
+
for l in Ai_nz & Ak_nz:
|
| 2008 |
+
Akl = Aij * Ak[l] - Akj * Ai[l]
|
| 2009 |
+
if Akl:
|
| 2010 |
+
if divisor is not None:
|
| 2011 |
+
Akl = exquo(Akl, divisor)
|
| 2012 |
+
Ak[l] = Akl
|
| 2013 |
+
else:
|
| 2014 |
+
Ak.pop(l)
|
| 2015 |
+
|
| 2016 |
+
if not Ak:
|
| 2017 |
+
col_to_row_unreduced.pop(pk)
|
| 2018 |
+
col_to_row_reduced[pk] = k
|
| 2019 |
+
|
| 2020 |
+
i = len(A_rref_rows)
|
| 2021 |
+
A_rref_rows.append(Ai)
|
| 2022 |
+
if Ai:
|
| 2023 |
+
col_to_row_unreduced[j] = i
|
| 2024 |
+
else:
|
| 2025 |
+
col_to_row_reduced[j] = i
|
| 2026 |
+
|
| 2027 |
+
# Update the denominator.
|
| 2028 |
+
if not K.is_one(Aij):
|
| 2029 |
+
if denom is None:
|
| 2030 |
+
denom = Aij
|
| 2031 |
+
else:
|
| 2032 |
+
denom *= Aij
|
| 2033 |
+
|
| 2034 |
+
if divisor is not None:
|
| 2035 |
+
denom = exquo(denom, divisor)
|
| 2036 |
+
|
| 2037 |
+
# Update the divisor.
|
| 2038 |
+
divisor = denom
|
| 2039 |
+
|
| 2040 |
+
if denom is None:
|
| 2041 |
+
denom = K.one
|
| 2042 |
+
|
| 2043 |
+
# Sort the rows by their leading column index.
|
| 2044 |
+
col_to_row = {**col_to_row_reduced, **col_to_row_unreduced}
|
| 2045 |
+
row_to_col = {i: j for j, i in col_to_row.items()}
|
| 2046 |
+
A_rref_rows_col = [(row_to_col[i], Ai) for i, Ai in enumerate(A_rref_rows)]
|
| 2047 |
+
pivots, A_rref = zip(*sorted(A_rref_rows_col))
|
| 2048 |
+
pivots = list(pivots)
|
| 2049 |
+
|
| 2050 |
+
# Insert the pivot values
|
| 2051 |
+
for i, Ai in enumerate(A_rref):
|
| 2052 |
+
Ai[pivots[i]] = denom
|
| 2053 |
+
|
| 2054 |
+
A_rref_sdm = dict(enumerate(A_rref))
|
| 2055 |
+
|
| 2056 |
+
return A_rref_sdm, denom, pivots
|
| 2057 |
+
|
| 2058 |
+
|
| 2059 |
+
def sdm_nullspace_from_rref(A, one, ncols, pivots, nonzero_cols):
|
| 2060 |
+
"""Get nullspace from A which is in RREF"""
|
| 2061 |
+
nonpivots = sorted(set(range(ncols)) - set(pivots))
|
| 2062 |
+
|
| 2063 |
+
K = []
|
| 2064 |
+
for j in nonpivots:
|
| 2065 |
+
Kj = {j:one}
|
| 2066 |
+
for i in nonzero_cols.get(j, ()):
|
| 2067 |
+
Kj[pivots[i]] = -A[i][j]
|
| 2068 |
+
K.append(Kj)
|
| 2069 |
+
|
| 2070 |
+
return K, nonpivots
|
| 2071 |
+
|
| 2072 |
+
|
| 2073 |
+
def sdm_particular_from_rref(A, ncols, pivots):
|
| 2074 |
+
"""Get a particular solution from A which is in RREF"""
|
| 2075 |
+
P = {}
|
| 2076 |
+
for i, j in enumerate(pivots):
|
| 2077 |
+
Ain = A[i].get(ncols-1, None)
|
| 2078 |
+
if Ain is not None:
|
| 2079 |
+
P[j] = Ain / A[i][j]
|
| 2080 |
+
return P
|
| 2081 |
+
|
| 2082 |
+
|
| 2083 |
+
def sdm_berk(M, n, K):
|
| 2084 |
+
"""
|
| 2085 |
+
Berkowitz algorithm for computing the characteristic polynomial.
|
| 2086 |
+
|
| 2087 |
+
Explanation
|
| 2088 |
+
===========
|
| 2089 |
+
|
| 2090 |
+
The Berkowitz algorithm is a division-free algorithm for computing the
|
| 2091 |
+
characteristic polynomial of a matrix over any commutative ring using only
|
| 2092 |
+
arithmetic in the coefficient ring. This implementation is for sparse
|
| 2093 |
+
matrices represented in a dict-of-dicts format (like :class:`SDM`).
|
| 2094 |
+
|
| 2095 |
+
Examples
|
| 2096 |
+
========
|
| 2097 |
+
|
| 2098 |
+
>>> from sympy import Matrix
|
| 2099 |
+
>>> from sympy.polys.matrices.sdm import sdm_berk
|
| 2100 |
+
>>> from sympy.polys.domains import ZZ
|
| 2101 |
+
>>> M = {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}}
|
| 2102 |
+
>>> sdm_berk(M, 2, ZZ)
|
| 2103 |
+
{0: 1, 1: -5, 2: -2}
|
| 2104 |
+
>>> Matrix([[1, 2], [3, 4]]).charpoly()
|
| 2105 |
+
PurePoly(lambda**2 - 5*lambda - 2, lambda, domain='ZZ')
|
| 2106 |
+
|
| 2107 |
+
See Also
|
| 2108 |
+
========
|
| 2109 |
+
|
| 2110 |
+
sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly
|
| 2111 |
+
The high-level interface to this function.
|
| 2112 |
+
sympy.polys.matrices.dense.ddm_berk
|
| 2113 |
+
The dense version of this function.
|
| 2114 |
+
|
| 2115 |
+
References
|
| 2116 |
+
==========
|
| 2117 |
+
|
| 2118 |
+
.. [1] https://en.wikipedia.org/wiki/Samuelson%E2%80%93Berkowitz_algorithm
|
| 2119 |
+
"""
|
| 2120 |
+
zero = K.zero
|
| 2121 |
+
one = K.one
|
| 2122 |
+
|
| 2123 |
+
if n == 0:
|
| 2124 |
+
return {0: one}
|
| 2125 |
+
elif n == 1:
|
| 2126 |
+
pdict = {0: one}
|
| 2127 |
+
if M00 := M.get(0, {}).get(0, zero):
|
| 2128 |
+
pdict[1] = -M00
|
| 2129 |
+
|
| 2130 |
+
# M = [[a, R],
|
| 2131 |
+
# [C, A]]
|
| 2132 |
+
a, R, C, A = K.zero, {}, {}, defaultdict(dict)
|
| 2133 |
+
for i, Mi in M.items():
|
| 2134 |
+
for j, Mij in Mi.items():
|
| 2135 |
+
if i and j:
|
| 2136 |
+
A[i-1][j-1] = Mij
|
| 2137 |
+
elif i:
|
| 2138 |
+
C[i-1] = Mij
|
| 2139 |
+
elif j:
|
| 2140 |
+
R[j-1] = Mij
|
| 2141 |
+
else:
|
| 2142 |
+
a = Mij
|
| 2143 |
+
|
| 2144 |
+
# T = [ 1, 0, 0, 0, 0, ... ]
|
| 2145 |
+
# [ -a, 1, 0, 0, 0, ... ]
|
| 2146 |
+
# [ -R*C, -a, 1, 0, 0, ... ]
|
| 2147 |
+
# [ -R*A*C, -R*C, -a, 1, 0, ... ]
|
| 2148 |
+
# [-R*A^2*C, -R*A*C, -R*C, -a, 1, ... ]
|
| 2149 |
+
# [ ... ]
|
| 2150 |
+
# T is (n+1) x n
|
| 2151 |
+
#
|
| 2152 |
+
# In the sparse case we might have A^m*C = 0 for some m making T banded
|
| 2153 |
+
# rather than triangular so we just compute the nonzero entries of the
|
| 2154 |
+
# first column rather than constructing the matrix explicitly.
|
| 2155 |
+
|
| 2156 |
+
AnC = C
|
| 2157 |
+
RC = sdm_dotvec(R, C, K)
|
| 2158 |
+
|
| 2159 |
+
Tvals = [one, -a, -RC]
|
| 2160 |
+
for i in range(3, n+1):
|
| 2161 |
+
AnC = sdm_matvecmul(A, AnC, K)
|
| 2162 |
+
if not AnC:
|
| 2163 |
+
break
|
| 2164 |
+
RAnC = sdm_dotvec(R, AnC, K)
|
| 2165 |
+
Tvals.append(-RAnC)
|
| 2166 |
+
|
| 2167 |
+
# Strip trailing zeros
|
| 2168 |
+
while Tvals and not Tvals[-1]:
|
| 2169 |
+
Tvals.pop()
|
| 2170 |
+
|
| 2171 |
+
q = sdm_berk(A, n-1, K)
|
| 2172 |
+
|
| 2173 |
+
# This would be the explicit multiplication T*q but we can do better:
|
| 2174 |
+
#
|
| 2175 |
+
# T = {}
|
| 2176 |
+
# for i in range(n+1):
|
| 2177 |
+
# Ti = {}
|
| 2178 |
+
# for j in range(max(0, i-len(Tvals)+1), min(i+1, n)):
|
| 2179 |
+
# Ti[j] = Tvals[i-j]
|
| 2180 |
+
# T[i] = Ti
|
| 2181 |
+
# Tq = sdm_matvecmul(T, q, K)
|
| 2182 |
+
#
|
| 2183 |
+
# In the sparse case q might be mostly zero. We know that T[i,j] is nonzero
|
| 2184 |
+
# for i <= j < i + len(Tvals) so if q does not have a nonzero entry in that
|
| 2185 |
+
# range then Tq[j] must be zero. We exploit this potential banded
|
| 2186 |
+
# structure and the potential sparsity of q to compute Tq more efficiently.
|
| 2187 |
+
|
| 2188 |
+
Tvals = Tvals[::-1]
|
| 2189 |
+
|
| 2190 |
+
Tq = {}
|
| 2191 |
+
|
| 2192 |
+
for i in range(min(q), min(max(q)+len(Tvals), n+1)):
|
| 2193 |
+
Ti = dict(enumerate(Tvals, i-len(Tvals)+1))
|
| 2194 |
+
if Tqi := sdm_dotvec(Ti, q, K):
|
| 2195 |
+
Tq[i] = Tqi
|
| 2196 |
+
|
| 2197 |
+
return Tq
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_ddm.py
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.testing.pytest import raises
|
| 2 |
+
from sympy.external.gmpy import GROUND_TYPES
|
| 3 |
+
|
| 4 |
+
from sympy.polys import ZZ, QQ
|
| 5 |
+
|
| 6 |
+
from sympy.polys.matrices.ddm import DDM
|
| 7 |
+
from sympy.polys.matrices.exceptions import (
|
| 8 |
+
DMShapeError, DMNonInvertibleMatrixError, DMDomainError,
|
| 9 |
+
DMBadInputError)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def test_DDM_init():
|
| 13 |
+
items = [[ZZ(0), ZZ(1), ZZ(2)], [ZZ(3), ZZ(4), ZZ(5)]]
|
| 14 |
+
shape = (2, 3)
|
| 15 |
+
ddm = DDM(items, shape, ZZ)
|
| 16 |
+
assert ddm.shape == shape
|
| 17 |
+
assert ddm.rows == 2
|
| 18 |
+
assert ddm.cols == 3
|
| 19 |
+
assert ddm.domain == ZZ
|
| 20 |
+
|
| 21 |
+
raises(DMBadInputError, lambda: DDM([[ZZ(2), ZZ(3)]], (2, 2), ZZ))
|
| 22 |
+
raises(DMBadInputError, lambda: DDM([[ZZ(1)], [ZZ(2), ZZ(3)]], (2, 2), ZZ))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_DDM_getsetitem():
|
| 26 |
+
ddm = DDM([[ZZ(2), ZZ(3)], [ZZ(4), ZZ(5)]], (2, 2), ZZ)
|
| 27 |
+
|
| 28 |
+
assert ddm[0][0] == ZZ(2)
|
| 29 |
+
assert ddm[0][1] == ZZ(3)
|
| 30 |
+
assert ddm[1][0] == ZZ(4)
|
| 31 |
+
assert ddm[1][1] == ZZ(5)
|
| 32 |
+
|
| 33 |
+
raises(IndexError, lambda: ddm[2][0])
|
| 34 |
+
raises(IndexError, lambda: ddm[0][2])
|
| 35 |
+
|
| 36 |
+
ddm[0][0] = ZZ(-1)
|
| 37 |
+
assert ddm[0][0] == ZZ(-1)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def test_DDM_str():
|
| 41 |
+
ddm = DDM([[ZZ(0), ZZ(1)], [ZZ(2), ZZ(3)]], (2, 2), ZZ)
|
| 42 |
+
if GROUND_TYPES == 'gmpy': # pragma: no cover
|
| 43 |
+
assert str(ddm) == '[[0, 1], [2, 3]]'
|
| 44 |
+
assert repr(ddm) == 'DDM([[mpz(0), mpz(1)], [mpz(2), mpz(3)]], (2, 2), ZZ)'
|
| 45 |
+
else: # pragma: no cover
|
| 46 |
+
assert repr(ddm) == 'DDM([[0, 1], [2, 3]], (2, 2), ZZ)'
|
| 47 |
+
assert str(ddm) == '[[0, 1], [2, 3]]'
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_DDM_eq():
|
| 51 |
+
items = [[ZZ(0), ZZ(1)], [ZZ(2), ZZ(3)]]
|
| 52 |
+
ddm1 = DDM(items, (2, 2), ZZ)
|
| 53 |
+
ddm2 = DDM(items, (2, 2), ZZ)
|
| 54 |
+
|
| 55 |
+
assert (ddm1 == ddm1) is True
|
| 56 |
+
assert (ddm1 == items) is False
|
| 57 |
+
assert (items == ddm1) is False
|
| 58 |
+
assert (ddm1 == ddm2) is True
|
| 59 |
+
assert (ddm2 == ddm1) is True
|
| 60 |
+
|
| 61 |
+
assert (ddm1 != ddm1) is False
|
| 62 |
+
assert (ddm1 != items) is True
|
| 63 |
+
assert (items != ddm1) is True
|
| 64 |
+
assert (ddm1 != ddm2) is False
|
| 65 |
+
assert (ddm2 != ddm1) is False
|
| 66 |
+
|
| 67 |
+
ddm3 = DDM([[ZZ(0), ZZ(1)], [ZZ(3), ZZ(3)]], (2, 2), ZZ)
|
| 68 |
+
ddm3 = DDM(items, (2, 2), QQ)
|
| 69 |
+
|
| 70 |
+
assert (ddm1 == ddm3) is False
|
| 71 |
+
assert (ddm3 == ddm1) is False
|
| 72 |
+
assert (ddm1 != ddm3) is True
|
| 73 |
+
assert (ddm3 != ddm1) is True
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def test_DDM_convert_to():
|
| 77 |
+
ddm = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 78 |
+
assert ddm.convert_to(ZZ) == ddm
|
| 79 |
+
ddmq = ddm.convert_to(QQ)
|
| 80 |
+
assert ddmq.domain == QQ
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def test_DDM_zeros():
|
| 84 |
+
ddmz = DDM.zeros((3, 4), QQ)
|
| 85 |
+
assert list(ddmz) == [[QQ(0)] * 4] * 3
|
| 86 |
+
assert ddmz.shape == (3, 4)
|
| 87 |
+
assert ddmz.domain == QQ
|
| 88 |
+
|
| 89 |
+
def test_DDM_ones():
|
| 90 |
+
ddmone = DDM.ones((2, 3), QQ)
|
| 91 |
+
assert list(ddmone) == [[QQ(1)] * 3] * 2
|
| 92 |
+
assert ddmone.shape == (2, 3)
|
| 93 |
+
assert ddmone.domain == QQ
|
| 94 |
+
|
| 95 |
+
def test_DDM_eye():
|
| 96 |
+
ddmz = DDM.eye(3, QQ)
|
| 97 |
+
f = lambda i, j: QQ(1) if i == j else QQ(0)
|
| 98 |
+
assert list(ddmz) == [[f(i, j) for i in range(3)] for j in range(3)]
|
| 99 |
+
assert ddmz.shape == (3, 3)
|
| 100 |
+
assert ddmz.domain == QQ
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def test_DDM_copy():
|
| 104 |
+
ddm1 = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 105 |
+
ddm2 = ddm1.copy()
|
| 106 |
+
assert (ddm1 == ddm2) is True
|
| 107 |
+
ddm1[0][0] = QQ(-1)
|
| 108 |
+
assert (ddm1 == ddm2) is False
|
| 109 |
+
ddm2[0][0] = QQ(-1)
|
| 110 |
+
assert (ddm1 == ddm2) is True
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def test_DDM_transpose():
|
| 114 |
+
ddm = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 115 |
+
ddmT = DDM([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 116 |
+
assert ddm.transpose() == ddmT
|
| 117 |
+
ddm02 = DDM([], (0, 2), QQ)
|
| 118 |
+
ddm02T = DDM([[], []], (2, 0), QQ)
|
| 119 |
+
assert ddm02.transpose() == ddm02T
|
| 120 |
+
assert ddm02T.transpose() == ddm02
|
| 121 |
+
ddm0 = DDM([], (0, 0), QQ)
|
| 122 |
+
assert ddm0.transpose() == ddm0
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def test_DDM_add():
|
| 126 |
+
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 127 |
+
B = DDM([[ZZ(3)], [ZZ(4)]], (2, 1), ZZ)
|
| 128 |
+
C = DDM([[ZZ(4)], [ZZ(6)]], (2, 1), ZZ)
|
| 129 |
+
AQ = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 130 |
+
assert A + B == A.add(B) == C
|
| 131 |
+
|
| 132 |
+
raises(DMShapeError, lambda: A + DDM([[ZZ(5)]], (1, 1), ZZ))
|
| 133 |
+
raises(TypeError, lambda: A + ZZ(1))
|
| 134 |
+
raises(TypeError, lambda: ZZ(1) + A)
|
| 135 |
+
raises(DMDomainError, lambda: A + AQ)
|
| 136 |
+
raises(DMDomainError, lambda: AQ + A)
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def test_DDM_sub():
|
| 140 |
+
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 141 |
+
B = DDM([[ZZ(3)], [ZZ(4)]], (2, 1), ZZ)
|
| 142 |
+
C = DDM([[ZZ(-2)], [ZZ(-2)]], (2, 1), ZZ)
|
| 143 |
+
AQ = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 144 |
+
D = DDM([[ZZ(5)]], (1, 1), ZZ)
|
| 145 |
+
assert A - B == A.sub(B) == C
|
| 146 |
+
|
| 147 |
+
raises(TypeError, lambda: A - ZZ(1))
|
| 148 |
+
raises(TypeError, lambda: ZZ(1) - A)
|
| 149 |
+
raises(DMShapeError, lambda: A - D)
|
| 150 |
+
raises(DMShapeError, lambda: D - A)
|
| 151 |
+
raises(DMShapeError, lambda: A.sub(D))
|
| 152 |
+
raises(DMShapeError, lambda: D.sub(A))
|
| 153 |
+
raises(DMDomainError, lambda: A - AQ)
|
| 154 |
+
raises(DMDomainError, lambda: AQ - A)
|
| 155 |
+
raises(DMDomainError, lambda: A.sub(AQ))
|
| 156 |
+
raises(DMDomainError, lambda: AQ.sub(A))
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def test_DDM_neg():
|
| 160 |
+
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 161 |
+
An = DDM([[ZZ(-1)], [ZZ(-2)]], (2, 1), ZZ)
|
| 162 |
+
assert -A == A.neg() == An
|
| 163 |
+
assert -An == An.neg() == A
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def test_DDM_mul():
|
| 167 |
+
A = DDM([[ZZ(1)]], (1, 1), ZZ)
|
| 168 |
+
A2 = DDM([[ZZ(2)]], (1, 1), ZZ)
|
| 169 |
+
assert A * ZZ(2) == A2
|
| 170 |
+
assert ZZ(2) * A == A2
|
| 171 |
+
raises(TypeError, lambda: [[1]] * A)
|
| 172 |
+
raises(TypeError, lambda: A * [[1]])
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def test_DDM_matmul():
|
| 176 |
+
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 177 |
+
B = DDM([[ZZ(3), ZZ(4)]], (1, 2), ZZ)
|
| 178 |
+
AB = DDM([[ZZ(3), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ)
|
| 179 |
+
BA = DDM([[ZZ(11)]], (1, 1), ZZ)
|
| 180 |
+
|
| 181 |
+
assert A @ B == A.matmul(B) == AB
|
| 182 |
+
assert B @ A == B.matmul(A) == BA
|
| 183 |
+
|
| 184 |
+
raises(TypeError, lambda: A @ 1)
|
| 185 |
+
raises(TypeError, lambda: A @ [[3, 4]])
|
| 186 |
+
|
| 187 |
+
Bq = DDM([[QQ(3), QQ(4)]], (1, 2), QQ)
|
| 188 |
+
|
| 189 |
+
raises(DMDomainError, lambda: A @ Bq)
|
| 190 |
+
raises(DMDomainError, lambda: Bq @ A)
|
| 191 |
+
|
| 192 |
+
C = DDM([[ZZ(1)]], (1, 1), ZZ)
|
| 193 |
+
|
| 194 |
+
assert A @ C == A.matmul(C) == A
|
| 195 |
+
|
| 196 |
+
raises(DMShapeError, lambda: C @ A)
|
| 197 |
+
raises(DMShapeError, lambda: C.matmul(A))
|
| 198 |
+
|
| 199 |
+
Z04 = DDM([], (0, 4), ZZ)
|
| 200 |
+
Z40 = DDM([[]]*4, (4, 0), ZZ)
|
| 201 |
+
Z50 = DDM([[]]*5, (5, 0), ZZ)
|
| 202 |
+
Z05 = DDM([], (0, 5), ZZ)
|
| 203 |
+
Z45 = DDM([[0] * 5] * 4, (4, 5), ZZ)
|
| 204 |
+
Z54 = DDM([[0] * 4] * 5, (5, 4), ZZ)
|
| 205 |
+
Z00 = DDM([], (0, 0), ZZ)
|
| 206 |
+
|
| 207 |
+
assert Z04 @ Z45 == Z04.matmul(Z45) == Z05
|
| 208 |
+
assert Z45 @ Z50 == Z45.matmul(Z50) == Z40
|
| 209 |
+
assert Z00 @ Z04 == Z00.matmul(Z04) == Z04
|
| 210 |
+
assert Z50 @ Z00 == Z50.matmul(Z00) == Z50
|
| 211 |
+
assert Z00 @ Z00 == Z00.matmul(Z00) == Z00
|
| 212 |
+
assert Z50 @ Z04 == Z50.matmul(Z04) == Z54
|
| 213 |
+
|
| 214 |
+
raises(DMShapeError, lambda: Z05 @ Z40)
|
| 215 |
+
raises(DMShapeError, lambda: Z05.matmul(Z40))
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def test_DDM_hstack():
|
| 219 |
+
A = DDM([[ZZ(1), ZZ(2), ZZ(3)]], (1, 3), ZZ)
|
| 220 |
+
B = DDM([[ZZ(4), ZZ(5)]], (1, 2), ZZ)
|
| 221 |
+
C = DDM([[ZZ(6)]], (1, 1), ZZ)
|
| 222 |
+
|
| 223 |
+
Ah = A.hstack(B)
|
| 224 |
+
assert Ah.shape == (1, 5)
|
| 225 |
+
assert Ah.domain == ZZ
|
| 226 |
+
assert Ah == DDM([[ZZ(1), ZZ(2), ZZ(3), ZZ(4), ZZ(5)]], (1, 5), ZZ)
|
| 227 |
+
|
| 228 |
+
Ah = A.hstack(B, C)
|
| 229 |
+
assert Ah.shape == (1, 6)
|
| 230 |
+
assert Ah.domain == ZZ
|
| 231 |
+
assert Ah == DDM([[ZZ(1), ZZ(2), ZZ(3), ZZ(4), ZZ(5), ZZ(6)]], (1, 6), ZZ)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def test_DDM_vstack():
|
| 235 |
+
A = DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)]], (3, 1), ZZ)
|
| 236 |
+
B = DDM([[ZZ(4)], [ZZ(5)]], (2, 1), ZZ)
|
| 237 |
+
C = DDM([[ZZ(6)]], (1, 1), ZZ)
|
| 238 |
+
|
| 239 |
+
Ah = A.vstack(B)
|
| 240 |
+
assert Ah.shape == (5, 1)
|
| 241 |
+
assert Ah.domain == ZZ
|
| 242 |
+
assert Ah == DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)], [ZZ(4)], [ZZ(5)]], (5, 1), ZZ)
|
| 243 |
+
|
| 244 |
+
Ah = A.vstack(B, C)
|
| 245 |
+
assert Ah.shape == (6, 1)
|
| 246 |
+
assert Ah.domain == ZZ
|
| 247 |
+
assert Ah == DDM([[ZZ(1)], [ZZ(2)], [ZZ(3)], [ZZ(4)], [ZZ(5)], [ZZ(6)]], (6, 1), ZZ)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def test_DDM_applyfunc():
|
| 251 |
+
A = DDM([[ZZ(1), ZZ(2), ZZ(3)]], (1, 3), ZZ)
|
| 252 |
+
B = DDM([[ZZ(2), ZZ(4), ZZ(6)]], (1, 3), ZZ)
|
| 253 |
+
assert A.applyfunc(lambda x: 2*x, ZZ) == B
|
| 254 |
+
|
| 255 |
+
def test_DDM_rref():
|
| 256 |
+
|
| 257 |
+
A = DDM([], (0, 4), QQ)
|
| 258 |
+
assert A.rref() == (A, [])
|
| 259 |
+
|
| 260 |
+
A = DDM([[QQ(0), QQ(1)], [QQ(1), QQ(1)]], (2, 2), QQ)
|
| 261 |
+
Ar = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ)
|
| 262 |
+
pivots = [0, 1]
|
| 263 |
+
assert A.rref() == (Ar, pivots)
|
| 264 |
+
|
| 265 |
+
A = DDM([[QQ(1), QQ(2), QQ(1)], [QQ(3), QQ(4), QQ(1)]], (2, 3), QQ)
|
| 266 |
+
Ar = DDM([[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]], (2, 3), QQ)
|
| 267 |
+
pivots = [0, 1]
|
| 268 |
+
assert A.rref() == (Ar, pivots)
|
| 269 |
+
|
| 270 |
+
A = DDM([[QQ(3), QQ(4), QQ(1)], [QQ(1), QQ(2), QQ(1)]], (2, 3), QQ)
|
| 271 |
+
Ar = DDM([[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]], (2, 3), QQ)
|
| 272 |
+
pivots = [0, 1]
|
| 273 |
+
assert A.rref() == (Ar, pivots)
|
| 274 |
+
|
| 275 |
+
A = DDM([[QQ(1), QQ(0)], [QQ(1), QQ(3)], [QQ(0), QQ(1)]], (3, 2), QQ)
|
| 276 |
+
Ar = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]], (3, 2), QQ)
|
| 277 |
+
pivots = [0, 1]
|
| 278 |
+
assert A.rref() == (Ar, pivots)
|
| 279 |
+
|
| 280 |
+
A = DDM([[QQ(1), QQ(0), QQ(1)], [QQ(3), QQ(0), QQ(1)]], (2, 3), QQ)
|
| 281 |
+
Ar = DDM([[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(0), QQ(1)]], (2, 3), QQ)
|
| 282 |
+
pivots = [0, 2]
|
| 283 |
+
assert A.rref() == (Ar, pivots)
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def test_DDM_nullspace():
|
| 287 |
+
# more tests are in test_nullspace.py
|
| 288 |
+
A = DDM([[QQ(1), QQ(1)], [QQ(1), QQ(1)]], (2, 2), QQ)
|
| 289 |
+
Anull = DDM([[QQ(-1), QQ(1)]], (1, 2), QQ)
|
| 290 |
+
nonpivots = [1]
|
| 291 |
+
assert A.nullspace() == (Anull, nonpivots)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def test_DDM_particular():
|
| 295 |
+
A = DDM([[QQ(1), QQ(0)]], (1, 2), QQ)
|
| 296 |
+
assert A.particular() == DDM.zeros((1, 1), QQ)
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def test_DDM_det():
|
| 300 |
+
# 0x0 case
|
| 301 |
+
A = DDM([], (0, 0), ZZ)
|
| 302 |
+
assert A.det() == ZZ(1)
|
| 303 |
+
|
| 304 |
+
# 1x1 case
|
| 305 |
+
A = DDM([[ZZ(2)]], (1, 1), ZZ)
|
| 306 |
+
assert A.det() == ZZ(2)
|
| 307 |
+
|
| 308 |
+
# 2x2 case
|
| 309 |
+
A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 310 |
+
assert A.det() == ZZ(-2)
|
| 311 |
+
|
| 312 |
+
# 3x3 with swap
|
| 313 |
+
A = DDM([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(2), ZZ(5)]], (3, 3), ZZ)
|
| 314 |
+
assert A.det() == ZZ(0)
|
| 315 |
+
|
| 316 |
+
# 2x2 QQ case
|
| 317 |
+
A = DDM([[QQ(1, 2), QQ(1, 2)], [QQ(1, 3), QQ(1, 4)]], (2, 2), QQ)
|
| 318 |
+
assert A.det() == QQ(-1, 24)
|
| 319 |
+
|
| 320 |
+
# Nonsquare error
|
| 321 |
+
A = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 322 |
+
raises(DMShapeError, lambda: A.det())
|
| 323 |
+
|
| 324 |
+
# Nonsquare error with empty matrix
|
| 325 |
+
A = DDM([], (0, 1), ZZ)
|
| 326 |
+
raises(DMShapeError, lambda: A.det())
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def test_DDM_inv():
|
| 330 |
+
A = DDM([[QQ(1, 1), QQ(2, 1)], [QQ(3, 1), QQ(4, 1)]], (2, 2), QQ)
|
| 331 |
+
Ainv = DDM([[QQ(-2, 1), QQ(1, 1)], [QQ(3, 2), QQ(-1, 2)]], (2, 2), QQ)
|
| 332 |
+
assert A.inv() == Ainv
|
| 333 |
+
|
| 334 |
+
A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 335 |
+
raises(DMShapeError, lambda: A.inv())
|
| 336 |
+
|
| 337 |
+
A = DDM([[ZZ(2)]], (1, 1), ZZ)
|
| 338 |
+
raises(DMDomainError, lambda: A.inv())
|
| 339 |
+
|
| 340 |
+
A = DDM([], (0, 0), QQ)
|
| 341 |
+
assert A.inv() == A
|
| 342 |
+
|
| 343 |
+
A = DDM([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ)
|
| 344 |
+
raises(DMNonInvertibleMatrixError, lambda: A.inv())
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def test_DDM_lu():
|
| 348 |
+
A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 349 |
+
L, U, swaps = A.lu()
|
| 350 |
+
assert L == DDM([[QQ(1), QQ(0)], [QQ(3), QQ(1)]], (2, 2), QQ)
|
| 351 |
+
assert U == DDM([[QQ(1), QQ(2)], [QQ(0), QQ(-2)]], (2, 2), QQ)
|
| 352 |
+
assert swaps == []
|
| 353 |
+
|
| 354 |
+
A = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]]
|
| 355 |
+
Lexp = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 1, 1]]
|
| 356 |
+
Uexp = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 0, 1]]
|
| 357 |
+
to_dom = lambda rows, dom: [[dom(e) for e in row] for row in rows]
|
| 358 |
+
A = DDM(to_dom(A, QQ), (4, 4), QQ)
|
| 359 |
+
Lexp = DDM(to_dom(Lexp, QQ), (4, 4), QQ)
|
| 360 |
+
Uexp = DDM(to_dom(Uexp, QQ), (4, 4), QQ)
|
| 361 |
+
L, U, swaps = A.lu()
|
| 362 |
+
assert L == Lexp
|
| 363 |
+
assert U == Uexp
|
| 364 |
+
assert swaps == []
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
def test_DDM_lu_solve():
|
| 368 |
+
# Basic example
|
| 369 |
+
A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 370 |
+
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 371 |
+
x = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
| 372 |
+
assert A.lu_solve(b) == x
|
| 373 |
+
|
| 374 |
+
# Example with swaps
|
| 375 |
+
A = DDM([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 376 |
+
assert A.lu_solve(b) == x
|
| 377 |
+
|
| 378 |
+
# Overdetermined, consistent
|
| 379 |
+
A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ)
|
| 380 |
+
b = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
| 381 |
+
assert A.lu_solve(b) == x
|
| 382 |
+
|
| 383 |
+
# Overdetermined, inconsistent
|
| 384 |
+
b = DDM([[QQ(1)], [QQ(2)], [QQ(4)]], (3, 1), QQ)
|
| 385 |
+
raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b))
|
| 386 |
+
|
| 387 |
+
# Square, noninvertible
|
| 388 |
+
A = DDM([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ)
|
| 389 |
+
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 390 |
+
raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b))
|
| 391 |
+
|
| 392 |
+
# Underdetermined
|
| 393 |
+
A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 394 |
+
b = DDM([[QQ(3)]], (1, 1), QQ)
|
| 395 |
+
raises(NotImplementedError, lambda: A.lu_solve(b))
|
| 396 |
+
|
| 397 |
+
# Domain mismatch
|
| 398 |
+
bz = DDM([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 399 |
+
raises(DMDomainError, lambda: A.lu_solve(bz))
|
| 400 |
+
|
| 401 |
+
# Shape mismatch
|
| 402 |
+
b3 = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
| 403 |
+
raises(DMShapeError, lambda: A.lu_solve(b3))
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def test_DDM_charpoly():
|
| 407 |
+
A = DDM([], (0, 0), ZZ)
|
| 408 |
+
assert A.charpoly() == [ZZ(1)]
|
| 409 |
+
|
| 410 |
+
A = DDM([
|
| 411 |
+
[ZZ(1), ZZ(2), ZZ(3)],
|
| 412 |
+
[ZZ(4), ZZ(5), ZZ(6)],
|
| 413 |
+
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
| 414 |
+
Avec = [ZZ(1), ZZ(-15), ZZ(-18), ZZ(0)]
|
| 415 |
+
assert A.charpoly() == Avec
|
| 416 |
+
|
| 417 |
+
A = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 418 |
+
raises(DMShapeError, lambda: A.charpoly())
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
def test_DDM_getitem():
|
| 422 |
+
dm = DDM([
|
| 423 |
+
[ZZ(1), ZZ(2), ZZ(3)],
|
| 424 |
+
[ZZ(4), ZZ(5), ZZ(6)],
|
| 425 |
+
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
| 426 |
+
|
| 427 |
+
assert dm.getitem(1, 1) == ZZ(5)
|
| 428 |
+
assert dm.getitem(1, -2) == ZZ(5)
|
| 429 |
+
assert dm.getitem(-1, -3) == ZZ(7)
|
| 430 |
+
|
| 431 |
+
raises(IndexError, lambda: dm.getitem(3, 3))
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
def test_DDM_setitem():
|
| 435 |
+
dm = DDM.zeros((3, 3), ZZ)
|
| 436 |
+
dm.setitem(0, 0, 1)
|
| 437 |
+
dm.setitem(1, -2, 1)
|
| 438 |
+
dm.setitem(-1, -1, 1)
|
| 439 |
+
assert dm == DDM.eye(3, ZZ)
|
| 440 |
+
|
| 441 |
+
raises(IndexError, lambda: dm.setitem(3, 3, 0))
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def test_DDM_extract_slice():
|
| 445 |
+
dm = DDM([
|
| 446 |
+
[ZZ(1), ZZ(2), ZZ(3)],
|
| 447 |
+
[ZZ(4), ZZ(5), ZZ(6)],
|
| 448 |
+
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
| 449 |
+
|
| 450 |
+
assert dm.extract_slice(slice(0, 3), slice(0, 3)) == dm
|
| 451 |
+
assert dm.extract_slice(slice(1, 3), slice(-2)) == DDM([[4], [7]], (2, 1), ZZ)
|
| 452 |
+
assert dm.extract_slice(slice(1, 3), slice(-2)) == DDM([[4], [7]], (2, 1), ZZ)
|
| 453 |
+
assert dm.extract_slice(slice(2, 3), slice(-2)) == DDM([[ZZ(7)]], (1, 1), ZZ)
|
| 454 |
+
assert dm.extract_slice(slice(0, 2), slice(-2)) == DDM([[1], [4]], (2, 1), ZZ)
|
| 455 |
+
assert dm.extract_slice(slice(-1), slice(-1)) == DDM([[1, 2], [4, 5]], (2, 2), ZZ)
|
| 456 |
+
|
| 457 |
+
assert dm.extract_slice(slice(2), slice(3, 4)) == DDM([[], []], (2, 0), ZZ)
|
| 458 |
+
assert dm.extract_slice(slice(3, 4), slice(2)) == DDM([], (0, 2), ZZ)
|
| 459 |
+
assert dm.extract_slice(slice(3, 4), slice(3, 4)) == DDM([], (0, 0), ZZ)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def test_DDM_extract():
|
| 463 |
+
dm1 = DDM([
|
| 464 |
+
[ZZ(1), ZZ(2), ZZ(3)],
|
| 465 |
+
[ZZ(4), ZZ(5), ZZ(6)],
|
| 466 |
+
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
| 467 |
+
dm2 = DDM([
|
| 468 |
+
[ZZ(6), ZZ(4)],
|
| 469 |
+
[ZZ(3), ZZ(1)]], (2, 2), ZZ)
|
| 470 |
+
assert dm1.extract([1, 0], [2, 0]) == dm2
|
| 471 |
+
assert dm1.extract([-2, 0], [-1, 0]) == dm2
|
| 472 |
+
|
| 473 |
+
assert dm1.extract([], []) == DDM.zeros((0, 0), ZZ)
|
| 474 |
+
assert dm1.extract([1], []) == DDM.zeros((1, 0), ZZ)
|
| 475 |
+
assert dm1.extract([], [1]) == DDM.zeros((0, 1), ZZ)
|
| 476 |
+
|
| 477 |
+
raises(IndexError, lambda: dm2.extract([2], [0]))
|
| 478 |
+
raises(IndexError, lambda: dm2.extract([0], [2]))
|
| 479 |
+
raises(IndexError, lambda: dm2.extract([-3], [0]))
|
| 480 |
+
raises(IndexError, lambda: dm2.extract([0], [-3]))
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
def test_DDM_flat():
|
| 484 |
+
dm = DDM([
|
| 485 |
+
[ZZ(6), ZZ(4)],
|
| 486 |
+
[ZZ(3), ZZ(1)]], (2, 2), ZZ)
|
| 487 |
+
assert dm.flat() == [ZZ(6), ZZ(4), ZZ(3), ZZ(1)]
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
def test_DDM_is_zero_matrix():
|
| 491 |
+
A = DDM([[QQ(1), QQ(0)], [QQ(0), QQ(0)]], (2, 2), QQ)
|
| 492 |
+
Azero = DDM.zeros((1, 2), QQ)
|
| 493 |
+
assert A.is_zero_matrix() is False
|
| 494 |
+
assert Azero.is_zero_matrix() is True
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
def test_DDM_is_upper():
|
| 498 |
+
# Wide matrices:
|
| 499 |
+
A = DDM([
|
| 500 |
+
[QQ(1), QQ(2), QQ(3), QQ(4)],
|
| 501 |
+
[QQ(0), QQ(5), QQ(6), QQ(7)],
|
| 502 |
+
[QQ(0), QQ(0), QQ(8), QQ(9)]
|
| 503 |
+
], (3, 4), QQ)
|
| 504 |
+
B = DDM([
|
| 505 |
+
[QQ(1), QQ(2), QQ(3), QQ(4)],
|
| 506 |
+
[QQ(0), QQ(5), QQ(6), QQ(7)],
|
| 507 |
+
[QQ(0), QQ(7), QQ(8), QQ(9)]
|
| 508 |
+
], (3, 4), QQ)
|
| 509 |
+
assert A.is_upper() is True
|
| 510 |
+
assert B.is_upper() is False
|
| 511 |
+
|
| 512 |
+
# Tall matrices:
|
| 513 |
+
A = DDM([
|
| 514 |
+
[QQ(1), QQ(2), QQ(3)],
|
| 515 |
+
[QQ(0), QQ(5), QQ(6)],
|
| 516 |
+
[QQ(0), QQ(0), QQ(8)],
|
| 517 |
+
[QQ(0), QQ(0), QQ(0)]
|
| 518 |
+
], (4, 3), QQ)
|
| 519 |
+
B = DDM([
|
| 520 |
+
[QQ(1), QQ(2), QQ(3)],
|
| 521 |
+
[QQ(0), QQ(5), QQ(6)],
|
| 522 |
+
[QQ(0), QQ(0), QQ(8)],
|
| 523 |
+
[QQ(0), QQ(0), QQ(10)]
|
| 524 |
+
], (4, 3), QQ)
|
| 525 |
+
assert A.is_upper() is True
|
| 526 |
+
assert B.is_upper() is False
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
def test_DDM_is_lower():
|
| 530 |
+
# Tall matrices:
|
| 531 |
+
A = DDM([
|
| 532 |
+
[QQ(1), QQ(2), QQ(3), QQ(4)],
|
| 533 |
+
[QQ(0), QQ(5), QQ(6), QQ(7)],
|
| 534 |
+
[QQ(0), QQ(0), QQ(8), QQ(9)]
|
| 535 |
+
], (3, 4), QQ).transpose()
|
| 536 |
+
B = DDM([
|
| 537 |
+
[QQ(1), QQ(2), QQ(3), QQ(4)],
|
| 538 |
+
[QQ(0), QQ(5), QQ(6), QQ(7)],
|
| 539 |
+
[QQ(0), QQ(7), QQ(8), QQ(9)]
|
| 540 |
+
], (3, 4), QQ).transpose()
|
| 541 |
+
assert A.is_lower() is True
|
| 542 |
+
assert B.is_lower() is False
|
| 543 |
+
|
| 544 |
+
# Wide matrices:
|
| 545 |
+
A = DDM([
|
| 546 |
+
[QQ(1), QQ(2), QQ(3)],
|
| 547 |
+
[QQ(0), QQ(5), QQ(6)],
|
| 548 |
+
[QQ(0), QQ(0), QQ(8)],
|
| 549 |
+
[QQ(0), QQ(0), QQ(0)]
|
| 550 |
+
], (4, 3), QQ).transpose()
|
| 551 |
+
B = DDM([
|
| 552 |
+
[QQ(1), QQ(2), QQ(3)],
|
| 553 |
+
[QQ(0), QQ(5), QQ(6)],
|
| 554 |
+
[QQ(0), QQ(0), QQ(8)],
|
| 555 |
+
[QQ(0), QQ(0), QQ(10)]
|
| 556 |
+
], (4, 3), QQ).transpose()
|
| 557 |
+
assert A.is_lower() is True
|
| 558 |
+
assert B.is_lower() is False
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_dense.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.testing.pytest import raises
|
| 2 |
+
|
| 3 |
+
from sympy.polys import ZZ, QQ
|
| 4 |
+
|
| 5 |
+
from sympy.polys.matrices.ddm import DDM
|
| 6 |
+
from sympy.polys.matrices.dense import (
|
| 7 |
+
ddm_transpose,
|
| 8 |
+
ddm_iadd, ddm_isub, ddm_ineg, ddm_imatmul, ddm_imul, ddm_irref,
|
| 9 |
+
ddm_idet, ddm_iinv, ddm_ilu, ddm_ilu_split, ddm_ilu_solve, ddm_berk)
|
| 10 |
+
|
| 11 |
+
from sympy.polys.matrices.exceptions import (
|
| 12 |
+
DMDomainError,
|
| 13 |
+
DMNonInvertibleMatrixError,
|
| 14 |
+
DMNonSquareMatrixError,
|
| 15 |
+
DMShapeError,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_ddm_transpose():
|
| 20 |
+
a = [[1, 2], [3, 4]]
|
| 21 |
+
assert ddm_transpose(a) == [[1, 3], [2, 4]]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def test_ddm_iadd():
|
| 25 |
+
a = [[1, 2], [3, 4]]
|
| 26 |
+
b = [[5, 6], [7, 8]]
|
| 27 |
+
ddm_iadd(a, b)
|
| 28 |
+
assert a == [[6, 8], [10, 12]]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_ddm_isub():
|
| 32 |
+
a = [[1, 2], [3, 4]]
|
| 33 |
+
b = [[5, 6], [7, 8]]
|
| 34 |
+
ddm_isub(a, b)
|
| 35 |
+
assert a == [[-4, -4], [-4, -4]]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def test_ddm_ineg():
|
| 39 |
+
a = [[1, 2], [3, 4]]
|
| 40 |
+
ddm_ineg(a)
|
| 41 |
+
assert a == [[-1, -2], [-3, -4]]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def test_ddm_matmul():
|
| 45 |
+
a = [[1, 2], [3, 4]]
|
| 46 |
+
ddm_imul(a, 2)
|
| 47 |
+
assert a == [[2, 4], [6, 8]]
|
| 48 |
+
|
| 49 |
+
a = [[1, 2], [3, 4]]
|
| 50 |
+
ddm_imul(a, 0)
|
| 51 |
+
assert a == [[0, 0], [0, 0]]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def test_ddm_imatmul():
|
| 55 |
+
a = [[1, 2, 3], [4, 5, 6]]
|
| 56 |
+
b = [[1, 2], [3, 4], [5, 6]]
|
| 57 |
+
|
| 58 |
+
c1 = [[0, 0], [0, 0]]
|
| 59 |
+
ddm_imatmul(c1, a, b)
|
| 60 |
+
assert c1 == [[22, 28], [49, 64]]
|
| 61 |
+
|
| 62 |
+
c2 = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
|
| 63 |
+
ddm_imatmul(c2, b, a)
|
| 64 |
+
assert c2 == [[9, 12, 15], [19, 26, 33], [29, 40, 51]]
|
| 65 |
+
|
| 66 |
+
b3 = [[1], [2], [3]]
|
| 67 |
+
c3 = [[0], [0]]
|
| 68 |
+
ddm_imatmul(c3, a, b3)
|
| 69 |
+
assert c3 == [[14], [32]]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def test_ddm_irref():
|
| 73 |
+
# Empty matrix
|
| 74 |
+
A = []
|
| 75 |
+
Ar = []
|
| 76 |
+
pivots = []
|
| 77 |
+
assert ddm_irref(A) == pivots
|
| 78 |
+
assert A == Ar
|
| 79 |
+
|
| 80 |
+
# Standard square case
|
| 81 |
+
A = [[QQ(0), QQ(1)], [QQ(1), QQ(1)]]
|
| 82 |
+
Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]]
|
| 83 |
+
pivots = [0, 1]
|
| 84 |
+
assert ddm_irref(A) == pivots
|
| 85 |
+
assert A == Ar
|
| 86 |
+
|
| 87 |
+
# m < n case
|
| 88 |
+
A = [[QQ(1), QQ(2), QQ(1)], [QQ(3), QQ(4), QQ(1)]]
|
| 89 |
+
Ar = [[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]]
|
| 90 |
+
pivots = [0, 1]
|
| 91 |
+
assert ddm_irref(A) == pivots
|
| 92 |
+
assert A == Ar
|
| 93 |
+
|
| 94 |
+
# same m < n but reversed
|
| 95 |
+
A = [[QQ(3), QQ(4), QQ(1)], [QQ(1), QQ(2), QQ(1)]]
|
| 96 |
+
Ar = [[QQ(1), QQ(0), QQ(-1)], [QQ(0), QQ(1), QQ(1)]]
|
| 97 |
+
pivots = [0, 1]
|
| 98 |
+
assert ddm_irref(A) == pivots
|
| 99 |
+
assert A == Ar
|
| 100 |
+
|
| 101 |
+
# m > n case
|
| 102 |
+
A = [[QQ(1), QQ(0)], [QQ(1), QQ(3)], [QQ(0), QQ(1)]]
|
| 103 |
+
Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]]
|
| 104 |
+
pivots = [0, 1]
|
| 105 |
+
assert ddm_irref(A) == pivots
|
| 106 |
+
assert A == Ar
|
| 107 |
+
|
| 108 |
+
# Example with missing pivot
|
| 109 |
+
A = [[QQ(1), QQ(0), QQ(1)], [QQ(3), QQ(0), QQ(1)]]
|
| 110 |
+
Ar = [[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(0), QQ(1)]]
|
| 111 |
+
pivots = [0, 2]
|
| 112 |
+
assert ddm_irref(A) == pivots
|
| 113 |
+
assert A == Ar
|
| 114 |
+
|
| 115 |
+
# Example with missing pivot and no replacement
|
| 116 |
+
A = [[QQ(0), QQ(1)], [QQ(0), QQ(2)], [QQ(1), QQ(0)]]
|
| 117 |
+
Ar = [[QQ(1), QQ(0)], [QQ(0), QQ(1)], [QQ(0), QQ(0)]]
|
| 118 |
+
pivots = [0, 1]
|
| 119 |
+
assert ddm_irref(A) == pivots
|
| 120 |
+
assert A == Ar
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def test_ddm_idet():
|
| 124 |
+
A = []
|
| 125 |
+
assert ddm_idet(A, ZZ) == ZZ(1)
|
| 126 |
+
|
| 127 |
+
A = [[ZZ(2)]]
|
| 128 |
+
assert ddm_idet(A, ZZ) == ZZ(2)
|
| 129 |
+
|
| 130 |
+
A = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]]
|
| 131 |
+
assert ddm_idet(A, ZZ) == ZZ(-2)
|
| 132 |
+
|
| 133 |
+
A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(3), ZZ(5)]]
|
| 134 |
+
assert ddm_idet(A, ZZ) == ZZ(-1)
|
| 135 |
+
|
| 136 |
+
A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(2), ZZ(5)]]
|
| 137 |
+
assert ddm_idet(A, ZZ) == ZZ(0)
|
| 138 |
+
|
| 139 |
+
A = [[QQ(1, 2), QQ(1, 2)], [QQ(1, 3), QQ(1, 4)]]
|
| 140 |
+
assert ddm_idet(A, QQ) == QQ(-1, 24)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def test_ddm_inv():
|
| 144 |
+
A = []
|
| 145 |
+
Ainv = []
|
| 146 |
+
ddm_iinv(Ainv, A, QQ)
|
| 147 |
+
assert Ainv == A
|
| 148 |
+
|
| 149 |
+
A = []
|
| 150 |
+
Ainv = []
|
| 151 |
+
raises(DMDomainError, lambda: ddm_iinv(Ainv, A, ZZ))
|
| 152 |
+
|
| 153 |
+
A = [[QQ(1), QQ(2)]]
|
| 154 |
+
Ainv = [[QQ(0), QQ(0)]]
|
| 155 |
+
raises(DMNonSquareMatrixError, lambda: ddm_iinv(Ainv, A, QQ))
|
| 156 |
+
|
| 157 |
+
A = [[QQ(1, 1), QQ(2, 1)], [QQ(3, 1), QQ(4, 1)]]
|
| 158 |
+
Ainv = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
|
| 159 |
+
Ainv_expected = [[QQ(-2, 1), QQ(1, 1)], [QQ(3, 2), QQ(-1, 2)]]
|
| 160 |
+
ddm_iinv(Ainv, A, QQ)
|
| 161 |
+
assert Ainv == Ainv_expected
|
| 162 |
+
|
| 163 |
+
A = [[QQ(1, 1), QQ(2, 1)], [QQ(2, 1), QQ(4, 1)]]
|
| 164 |
+
Ainv = [[QQ(0), QQ(0)], [QQ(0), QQ(0)]]
|
| 165 |
+
raises(DMNonInvertibleMatrixError, lambda: ddm_iinv(Ainv, A, QQ))
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def test_ddm_ilu():
|
| 169 |
+
A = []
|
| 170 |
+
Alu = []
|
| 171 |
+
swaps = ddm_ilu(A)
|
| 172 |
+
assert A == Alu
|
| 173 |
+
assert swaps == []
|
| 174 |
+
|
| 175 |
+
A = [[]]
|
| 176 |
+
Alu = [[]]
|
| 177 |
+
swaps = ddm_ilu(A)
|
| 178 |
+
assert A == Alu
|
| 179 |
+
assert swaps == []
|
| 180 |
+
|
| 181 |
+
A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
| 182 |
+
Alu = [[QQ(1), QQ(2)], [QQ(3), QQ(-2)]]
|
| 183 |
+
swaps = ddm_ilu(A)
|
| 184 |
+
assert A == Alu
|
| 185 |
+
assert swaps == []
|
| 186 |
+
|
| 187 |
+
A = [[QQ(0), QQ(2)], [QQ(3), QQ(4)]]
|
| 188 |
+
Alu = [[QQ(3), QQ(4)], [QQ(0), QQ(2)]]
|
| 189 |
+
swaps = ddm_ilu(A)
|
| 190 |
+
assert A == Alu
|
| 191 |
+
assert swaps == [(0, 1)]
|
| 192 |
+
|
| 193 |
+
A = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)], [QQ(7), QQ(8), QQ(9)]]
|
| 194 |
+
Alu = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(-3), QQ(-6)], [QQ(7), QQ(2), QQ(0)]]
|
| 195 |
+
swaps = ddm_ilu(A)
|
| 196 |
+
assert A == Alu
|
| 197 |
+
assert swaps == []
|
| 198 |
+
|
| 199 |
+
A = [[QQ(0), QQ(1), QQ(2)], [QQ(0), QQ(1), QQ(3)], [QQ(1), QQ(1), QQ(2)]]
|
| 200 |
+
Alu = [[QQ(1), QQ(1), QQ(2)], [QQ(0), QQ(1), QQ(3)], [QQ(0), QQ(1), QQ(-1)]]
|
| 201 |
+
swaps = ddm_ilu(A)
|
| 202 |
+
assert A == Alu
|
| 203 |
+
assert swaps == [(0, 2)]
|
| 204 |
+
|
| 205 |
+
A = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]]
|
| 206 |
+
Alu = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(-3), QQ(-6)]]
|
| 207 |
+
swaps = ddm_ilu(A)
|
| 208 |
+
assert A == Alu
|
| 209 |
+
assert swaps == []
|
| 210 |
+
|
| 211 |
+
A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]]
|
| 212 |
+
Alu = [[QQ(1), QQ(2)], [QQ(3), QQ(-2)], [QQ(5), QQ(2)]]
|
| 213 |
+
swaps = ddm_ilu(A)
|
| 214 |
+
assert A == Alu
|
| 215 |
+
assert swaps == []
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def test_ddm_ilu_split():
|
| 219 |
+
U = []
|
| 220 |
+
L = []
|
| 221 |
+
Uexp = []
|
| 222 |
+
Lexp = []
|
| 223 |
+
swaps = ddm_ilu_split(L, U, QQ)
|
| 224 |
+
assert U == Uexp
|
| 225 |
+
assert L == Lexp
|
| 226 |
+
assert swaps == []
|
| 227 |
+
|
| 228 |
+
U = [[]]
|
| 229 |
+
L = [[QQ(1)]]
|
| 230 |
+
Uexp = [[]]
|
| 231 |
+
Lexp = [[QQ(1)]]
|
| 232 |
+
swaps = ddm_ilu_split(L, U, QQ)
|
| 233 |
+
assert U == Uexp
|
| 234 |
+
assert L == Lexp
|
| 235 |
+
assert swaps == []
|
| 236 |
+
|
| 237 |
+
U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
| 238 |
+
L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]]
|
| 239 |
+
Uexp = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)]]
|
| 240 |
+
Lexp = [[QQ(1), QQ(0)], [QQ(3), QQ(1)]]
|
| 241 |
+
swaps = ddm_ilu_split(L, U, QQ)
|
| 242 |
+
assert U == Uexp
|
| 243 |
+
assert L == Lexp
|
| 244 |
+
assert swaps == []
|
| 245 |
+
|
| 246 |
+
U = [[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]]
|
| 247 |
+
L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]]
|
| 248 |
+
Uexp = [[QQ(1), QQ(2), QQ(3)], [QQ(0), QQ(-3), QQ(-6)]]
|
| 249 |
+
Lexp = [[QQ(1), QQ(0)], [QQ(4), QQ(1)]]
|
| 250 |
+
swaps = ddm_ilu_split(L, U, QQ)
|
| 251 |
+
assert U == Uexp
|
| 252 |
+
assert L == Lexp
|
| 253 |
+
assert swaps == []
|
| 254 |
+
|
| 255 |
+
U = [[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]]
|
| 256 |
+
L = [[QQ(1), QQ(0), QQ(0)], [QQ(0), QQ(1), QQ(0)], [QQ(0), QQ(0), QQ(1)]]
|
| 257 |
+
Uexp = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)], [QQ(0), QQ(0)]]
|
| 258 |
+
Lexp = [[QQ(1), QQ(0), QQ(0)], [QQ(3), QQ(1), QQ(0)], [QQ(5), QQ(2), QQ(1)]]
|
| 259 |
+
swaps = ddm_ilu_split(L, U, QQ)
|
| 260 |
+
assert U == Uexp
|
| 261 |
+
assert L == Lexp
|
| 262 |
+
assert swaps == []
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def test_ddm_ilu_solve():
|
| 266 |
+
# Basic example
|
| 267 |
+
# A = [[QQ(1), QQ(2)], [QQ(3), QQ(4)]]
|
| 268 |
+
U = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)]]
|
| 269 |
+
L = [[QQ(1), QQ(0)], [QQ(3), QQ(1)]]
|
| 270 |
+
swaps = []
|
| 271 |
+
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 272 |
+
x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ)
|
| 273 |
+
xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
| 274 |
+
ddm_ilu_solve(x, L, U, swaps, b)
|
| 275 |
+
assert x == xexp
|
| 276 |
+
|
| 277 |
+
# Example with swaps
|
| 278 |
+
# A = [[QQ(0), QQ(2)], [QQ(3), QQ(4)]]
|
| 279 |
+
U = [[QQ(3), QQ(4)], [QQ(0), QQ(2)]]
|
| 280 |
+
L = [[QQ(1), QQ(0)], [QQ(0), QQ(1)]]
|
| 281 |
+
swaps = [(0, 1)]
|
| 282 |
+
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 283 |
+
x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ)
|
| 284 |
+
xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
| 285 |
+
ddm_ilu_solve(x, L, U, swaps, b)
|
| 286 |
+
assert x == xexp
|
| 287 |
+
|
| 288 |
+
# Overdetermined, consistent
|
| 289 |
+
# A = DDM([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ)
|
| 290 |
+
U = [[QQ(1), QQ(2)], [QQ(0), QQ(-2)], [QQ(0), QQ(0)]]
|
| 291 |
+
L = [[QQ(1), QQ(0), QQ(0)], [QQ(3), QQ(1), QQ(0)], [QQ(5), QQ(2), QQ(1)]]
|
| 292 |
+
swaps = []
|
| 293 |
+
b = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
| 294 |
+
x = DDM([[QQ(0)], [QQ(0)]], (2, 1), QQ)
|
| 295 |
+
xexp = DDM([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
| 296 |
+
ddm_ilu_solve(x, L, U, swaps, b)
|
| 297 |
+
assert x == xexp
|
| 298 |
+
|
| 299 |
+
# Overdetermined, inconsistent
|
| 300 |
+
b = DDM([[QQ(1)], [QQ(2)], [QQ(4)]], (3, 1), QQ)
|
| 301 |
+
raises(DMNonInvertibleMatrixError, lambda: ddm_ilu_solve(x, L, U, swaps, b))
|
| 302 |
+
|
| 303 |
+
# Square, noninvertible
|
| 304 |
+
# A = DDM([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ)
|
| 305 |
+
U = [[QQ(1), QQ(2)], [QQ(0), QQ(0)]]
|
| 306 |
+
L = [[QQ(1), QQ(0)], [QQ(1), QQ(1)]]
|
| 307 |
+
swaps = []
|
| 308 |
+
b = DDM([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 309 |
+
raises(DMNonInvertibleMatrixError, lambda: ddm_ilu_solve(x, L, U, swaps, b))
|
| 310 |
+
|
| 311 |
+
# Underdetermined
|
| 312 |
+
# A = DDM([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 313 |
+
U = [[QQ(1), QQ(2)]]
|
| 314 |
+
L = [[QQ(1)]]
|
| 315 |
+
swaps = []
|
| 316 |
+
b = DDM([[QQ(3)]], (1, 1), QQ)
|
| 317 |
+
raises(NotImplementedError, lambda: ddm_ilu_solve(x, L, U, swaps, b))
|
| 318 |
+
|
| 319 |
+
# Shape mismatch
|
| 320 |
+
b3 = DDM([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
| 321 |
+
raises(DMShapeError, lambda: ddm_ilu_solve(x, L, U, swaps, b3))
|
| 322 |
+
|
| 323 |
+
# Empty shape mismatch
|
| 324 |
+
U = [[QQ(1)]]
|
| 325 |
+
L = [[QQ(1)]]
|
| 326 |
+
swaps = []
|
| 327 |
+
x = [[QQ(1)]]
|
| 328 |
+
b = []
|
| 329 |
+
raises(DMShapeError, lambda: ddm_ilu_solve(x, L, U, swaps, b))
|
| 330 |
+
|
| 331 |
+
# Empty system
|
| 332 |
+
U = []
|
| 333 |
+
L = []
|
| 334 |
+
swaps = []
|
| 335 |
+
b = []
|
| 336 |
+
x = []
|
| 337 |
+
ddm_ilu_solve(x, L, U, swaps, b)
|
| 338 |
+
assert x == []
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def test_ddm_charpoly():
|
| 342 |
+
A = []
|
| 343 |
+
assert ddm_berk(A, ZZ) == [[ZZ(1)]]
|
| 344 |
+
|
| 345 |
+
A = [[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]]
|
| 346 |
+
Avec = [[ZZ(1)], [ZZ(-15)], [ZZ(-18)], [ZZ(0)]]
|
| 347 |
+
assert ddm_berk(A, ZZ) == Avec
|
| 348 |
+
|
| 349 |
+
A = DDM([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 350 |
+
raises(DMShapeError, lambda: ddm_berk(A, ZZ))
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_domainmatrix.py
ADDED
|
@@ -0,0 +1,1383 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.external.gmpy import GROUND_TYPES
|
| 2 |
+
|
| 3 |
+
from sympy import Integer, Rational, S, sqrt, Matrix, symbols
|
| 4 |
+
from sympy import FF, ZZ, QQ, QQ_I, EXRAW
|
| 5 |
+
|
| 6 |
+
from sympy.polys.matrices.domainmatrix import DomainMatrix, DomainScalar, DM
|
| 7 |
+
from sympy.polys.matrices.exceptions import (
|
| 8 |
+
DMBadInputError, DMDomainError, DMShapeError, DMFormatError, DMNotAField,
|
| 9 |
+
DMNonSquareMatrixError, DMNonInvertibleMatrixError,
|
| 10 |
+
)
|
| 11 |
+
from sympy.polys.matrices.ddm import DDM
|
| 12 |
+
from sympy.polys.matrices.sdm import SDM
|
| 13 |
+
|
| 14 |
+
from sympy.testing.pytest import raises
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_DM():
|
| 18 |
+
ddm = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 19 |
+
A = DM([[1, 2], [3, 4]], ZZ)
|
| 20 |
+
if GROUND_TYPES != 'flint':
|
| 21 |
+
assert A.rep == ddm
|
| 22 |
+
else:
|
| 23 |
+
assert A.rep == ddm.to_dfm()
|
| 24 |
+
assert A.shape == (2, 2)
|
| 25 |
+
assert A.domain == ZZ
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_DomainMatrix_init():
|
| 29 |
+
lol = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]]
|
| 30 |
+
dod = {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}}
|
| 31 |
+
ddm = DDM(lol, (2, 2), ZZ)
|
| 32 |
+
sdm = SDM(dod, (2, 2), ZZ)
|
| 33 |
+
|
| 34 |
+
A = DomainMatrix(lol, (2, 2), ZZ)
|
| 35 |
+
if GROUND_TYPES != 'flint':
|
| 36 |
+
assert A.rep == ddm
|
| 37 |
+
else:
|
| 38 |
+
assert A.rep == ddm.to_dfm()
|
| 39 |
+
assert A.shape == (2, 2)
|
| 40 |
+
assert A.domain == ZZ
|
| 41 |
+
|
| 42 |
+
A = DomainMatrix(dod, (2, 2), ZZ)
|
| 43 |
+
assert A.rep == sdm
|
| 44 |
+
assert A.shape == (2, 2)
|
| 45 |
+
assert A.domain == ZZ
|
| 46 |
+
|
| 47 |
+
raises(TypeError, lambda: DomainMatrix(ddm, (2, 2), ZZ))
|
| 48 |
+
raises(TypeError, lambda: DomainMatrix(sdm, (2, 2), ZZ))
|
| 49 |
+
raises(TypeError, lambda: DomainMatrix(Matrix([[1]]), (1, 1), ZZ))
|
| 50 |
+
|
| 51 |
+
for fmt, rep in [('sparse', sdm), ('dense', ddm)]:
|
| 52 |
+
if fmt == 'dense' and GROUND_TYPES == 'flint':
|
| 53 |
+
rep = rep.to_dfm()
|
| 54 |
+
A = DomainMatrix(lol, (2, 2), ZZ, fmt=fmt)
|
| 55 |
+
assert A.rep == rep
|
| 56 |
+
A = DomainMatrix(dod, (2, 2), ZZ, fmt=fmt)
|
| 57 |
+
assert A.rep == rep
|
| 58 |
+
|
| 59 |
+
raises(ValueError, lambda: DomainMatrix(lol, (2, 2), ZZ, fmt='invalid'))
|
| 60 |
+
|
| 61 |
+
raises(DMBadInputError, lambda: DomainMatrix([[ZZ(1), ZZ(2)]], (2, 2), ZZ))
|
| 62 |
+
|
| 63 |
+
# uses copy
|
| 64 |
+
was = [i.copy() for i in lol]
|
| 65 |
+
A[0,0] = ZZ(42)
|
| 66 |
+
assert was == lol
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def test_DomainMatrix_from_rep():
|
| 70 |
+
ddm = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 71 |
+
A = DomainMatrix.from_rep(ddm)
|
| 72 |
+
# XXX: Should from_rep convert to DFM?
|
| 73 |
+
assert A.rep == ddm
|
| 74 |
+
assert A.shape == (2, 2)
|
| 75 |
+
assert A.domain == ZZ
|
| 76 |
+
|
| 77 |
+
sdm = SDM({0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}}, (2, 2), ZZ)
|
| 78 |
+
A = DomainMatrix.from_rep(sdm)
|
| 79 |
+
assert A.rep == sdm
|
| 80 |
+
assert A.shape == (2, 2)
|
| 81 |
+
assert A.domain == ZZ
|
| 82 |
+
|
| 83 |
+
A = DomainMatrix([[ZZ(1)]], (1, 1), ZZ)
|
| 84 |
+
raises(TypeError, lambda: DomainMatrix.from_rep(A))
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def test_DomainMatrix_from_list():
|
| 88 |
+
ddm = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 89 |
+
A = DomainMatrix.from_list([[1, 2], [3, 4]], ZZ)
|
| 90 |
+
if GROUND_TYPES != 'flint':
|
| 91 |
+
assert A.rep == ddm
|
| 92 |
+
else:
|
| 93 |
+
assert A.rep == ddm.to_dfm()
|
| 94 |
+
assert A.shape == (2, 2)
|
| 95 |
+
assert A.domain == ZZ
|
| 96 |
+
|
| 97 |
+
dom = FF(7)
|
| 98 |
+
ddm = DDM([[dom(1), dom(2)], [dom(3), dom(4)]], (2, 2), dom)
|
| 99 |
+
A = DomainMatrix.from_list([[1, 2], [3, 4]], dom)
|
| 100 |
+
if GROUND_TYPES != 'flint':
|
| 101 |
+
assert A.rep == ddm
|
| 102 |
+
else:
|
| 103 |
+
assert A.rep == ddm.to_dfm()
|
| 104 |
+
assert A.shape == (2, 2)
|
| 105 |
+
assert A.domain == dom
|
| 106 |
+
|
| 107 |
+
dom = FF(2**127-1)
|
| 108 |
+
ddm = DDM([[dom(1), dom(2)], [dom(3), dom(4)]], (2, 2), dom)
|
| 109 |
+
A = DomainMatrix.from_list([[1, 2], [3, 4]], dom)
|
| 110 |
+
if GROUND_TYPES != 'flint':
|
| 111 |
+
assert A.rep == ddm
|
| 112 |
+
else:
|
| 113 |
+
assert A.rep == ddm.to_dfm()
|
| 114 |
+
assert A.shape == (2, 2)
|
| 115 |
+
assert A.domain == dom
|
| 116 |
+
|
| 117 |
+
ddm = DDM([[QQ(1, 2), QQ(3, 1)], [QQ(1, 4), QQ(5, 1)]], (2, 2), QQ)
|
| 118 |
+
A = DomainMatrix.from_list([[(1, 2), (3, 1)], [(1, 4), (5, 1)]], QQ)
|
| 119 |
+
if GROUND_TYPES != 'flint':
|
| 120 |
+
assert A.rep == ddm
|
| 121 |
+
else:
|
| 122 |
+
assert A.rep == ddm.to_dfm()
|
| 123 |
+
assert A.shape == (2, 2)
|
| 124 |
+
assert A.domain == QQ
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def test_DomainMatrix_from_list_sympy():
|
| 128 |
+
ddm = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 129 |
+
A = DomainMatrix.from_list_sympy(2, 2, [[1, 2], [3, 4]])
|
| 130 |
+
if GROUND_TYPES != 'flint':
|
| 131 |
+
assert A.rep == ddm
|
| 132 |
+
else:
|
| 133 |
+
assert A.rep == ddm.to_dfm()
|
| 134 |
+
assert A.shape == (2, 2)
|
| 135 |
+
assert A.domain == ZZ
|
| 136 |
+
|
| 137 |
+
K = QQ.algebraic_field(sqrt(2))
|
| 138 |
+
ddm = DDM(
|
| 139 |
+
[[K.convert(1 + sqrt(2)), K.convert(2 + sqrt(2))],
|
| 140 |
+
[K.convert(3 + sqrt(2)), K.convert(4 + sqrt(2))]],
|
| 141 |
+
(2, 2),
|
| 142 |
+
K
|
| 143 |
+
)
|
| 144 |
+
A = DomainMatrix.from_list_sympy(
|
| 145 |
+
2, 2, [[1 + sqrt(2), 2 + sqrt(2)], [3 + sqrt(2), 4 + sqrt(2)]],
|
| 146 |
+
extension=True)
|
| 147 |
+
assert A.rep == ddm
|
| 148 |
+
assert A.shape == (2, 2)
|
| 149 |
+
assert A.domain == K
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def test_DomainMatrix_from_dict_sympy():
|
| 153 |
+
sdm = SDM({0: {0: QQ(1, 2)}, 1: {1: QQ(2, 3)}}, (2, 2), QQ)
|
| 154 |
+
sympy_dict = {0: {0: Rational(1, 2)}, 1: {1: Rational(2, 3)}}
|
| 155 |
+
A = DomainMatrix.from_dict_sympy(2, 2, sympy_dict)
|
| 156 |
+
assert A.rep == sdm
|
| 157 |
+
assert A.shape == (2, 2)
|
| 158 |
+
assert A.domain == QQ
|
| 159 |
+
|
| 160 |
+
fds = DomainMatrix.from_dict_sympy
|
| 161 |
+
raises(DMBadInputError, lambda: fds(2, 2, {3: {0: Rational(1, 2)}}))
|
| 162 |
+
raises(DMBadInputError, lambda: fds(2, 2, {0: {3: Rational(1, 2)}}))
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def test_DomainMatrix_from_Matrix():
|
| 166 |
+
sdm = SDM({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}, (2, 2), ZZ)
|
| 167 |
+
A = DomainMatrix.from_Matrix(Matrix([[1, 2], [3, 4]]))
|
| 168 |
+
assert A.rep == sdm
|
| 169 |
+
assert A.shape == (2, 2)
|
| 170 |
+
assert A.domain == ZZ
|
| 171 |
+
|
| 172 |
+
K = QQ.algebraic_field(sqrt(2))
|
| 173 |
+
sdm = SDM(
|
| 174 |
+
{0: {0: K.convert(1 + sqrt(2)), 1: K.convert(2 + sqrt(2))},
|
| 175 |
+
1: {0: K.convert(3 + sqrt(2)), 1: K.convert(4 + sqrt(2))}},
|
| 176 |
+
(2, 2),
|
| 177 |
+
K
|
| 178 |
+
)
|
| 179 |
+
A = DomainMatrix.from_Matrix(
|
| 180 |
+
Matrix([[1 + sqrt(2), 2 + sqrt(2)], [3 + sqrt(2), 4 + sqrt(2)]]),
|
| 181 |
+
extension=True)
|
| 182 |
+
assert A.rep == sdm
|
| 183 |
+
assert A.shape == (2, 2)
|
| 184 |
+
assert A.domain == K
|
| 185 |
+
|
| 186 |
+
A = DomainMatrix.from_Matrix(Matrix([[QQ(1, 2), QQ(3, 4)], [QQ(0, 1), QQ(0, 1)]]), fmt='dense')
|
| 187 |
+
ddm = DDM([[QQ(1, 2), QQ(3, 4)], [QQ(0, 1), QQ(0, 1)]], (2, 2), QQ)
|
| 188 |
+
|
| 189 |
+
if GROUND_TYPES != 'flint':
|
| 190 |
+
assert A.rep == ddm
|
| 191 |
+
else:
|
| 192 |
+
assert A.rep == ddm.to_dfm()
|
| 193 |
+
assert A.shape == (2, 2)
|
| 194 |
+
assert A.domain == QQ
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def test_DomainMatrix_eq():
|
| 198 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 199 |
+
assert A == A
|
| 200 |
+
B = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(1)]], (2, 2), ZZ)
|
| 201 |
+
assert A != B
|
| 202 |
+
C = [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]]
|
| 203 |
+
assert A != C
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def test_DomainMatrix_unify_eq():
|
| 207 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 208 |
+
B1 = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 209 |
+
B2 = DomainMatrix([[QQ(1), QQ(3)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 210 |
+
B3 = DomainMatrix([[ZZ(1)]], (1, 1), ZZ)
|
| 211 |
+
assert A.unify_eq(B1) is True
|
| 212 |
+
assert A.unify_eq(B2) is False
|
| 213 |
+
assert A.unify_eq(B3) is False
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def test_DomainMatrix_get_domain():
|
| 217 |
+
K, items = DomainMatrix.get_domain([1, 2, 3, 4])
|
| 218 |
+
assert items == [ZZ(1), ZZ(2), ZZ(3), ZZ(4)]
|
| 219 |
+
assert K == ZZ
|
| 220 |
+
|
| 221 |
+
K, items = DomainMatrix.get_domain([1, 2, 3, Rational(1, 2)])
|
| 222 |
+
assert items == [QQ(1), QQ(2), QQ(3), QQ(1, 2)]
|
| 223 |
+
assert K == QQ
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def test_DomainMatrix_convert_to():
|
| 227 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 228 |
+
Aq = A.convert_to(QQ)
|
| 229 |
+
assert Aq == DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def test_DomainMatrix_choose_domain():
|
| 233 |
+
A = [[1, 2], [3, 0]]
|
| 234 |
+
assert DM(A, QQ).choose_domain() == DM(A, ZZ)
|
| 235 |
+
assert DM(A, QQ).choose_domain(field=True) == DM(A, QQ)
|
| 236 |
+
assert DM(A, ZZ).choose_domain(field=True) == DM(A, QQ)
|
| 237 |
+
|
| 238 |
+
x = symbols('x')
|
| 239 |
+
B = [[1, x], [x**2, x**3]]
|
| 240 |
+
assert DM(B, QQ[x]).choose_domain(field=True) == DM(B, ZZ.frac_field(x))
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def test_DomainMatrix_to_flat_nz():
|
| 244 |
+
Adm = DM([[1, 2], [3, 0]], ZZ)
|
| 245 |
+
Addm = Adm.rep.to_ddm()
|
| 246 |
+
Asdm = Adm.rep.to_sdm()
|
| 247 |
+
for A in [Adm, Addm, Asdm]:
|
| 248 |
+
elems, data = A.to_flat_nz()
|
| 249 |
+
assert A.from_flat_nz(elems, data, A.domain) == A
|
| 250 |
+
elemsq = [QQ(e) for e in elems]
|
| 251 |
+
assert A.from_flat_nz(elemsq, data, QQ) == A.convert_to(QQ)
|
| 252 |
+
elems2 = [2*e for e in elems]
|
| 253 |
+
assert A.from_flat_nz(elems2, data, A.domain) == 2*A
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def test_DomainMatrix_to_sympy():
|
| 257 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 258 |
+
assert A.to_sympy() == A.convert_to(EXRAW)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def test_DomainMatrix_to_field():
|
| 262 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 263 |
+
Aq = A.to_field()
|
| 264 |
+
assert Aq == DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def test_DomainMatrix_to_sparse():
|
| 268 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 269 |
+
A_sparse = A.to_sparse()
|
| 270 |
+
assert A_sparse.rep == {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def test_DomainMatrix_to_dense():
|
| 274 |
+
A = DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ)
|
| 275 |
+
A_dense = A.to_dense()
|
| 276 |
+
ddm = DDM([[1, 2], [3, 4]], (2, 2), ZZ)
|
| 277 |
+
if GROUND_TYPES != 'flint':
|
| 278 |
+
assert A_dense.rep == ddm
|
| 279 |
+
else:
|
| 280 |
+
assert A_dense.rep == ddm.to_dfm()
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def test_DomainMatrix_unify():
|
| 284 |
+
Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 285 |
+
Aq = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 286 |
+
assert Az.unify(Az) == (Az, Az)
|
| 287 |
+
assert Az.unify(Aq) == (Aq, Aq)
|
| 288 |
+
assert Aq.unify(Az) == (Aq, Aq)
|
| 289 |
+
assert Aq.unify(Aq) == (Aq, Aq)
|
| 290 |
+
|
| 291 |
+
As = DomainMatrix({0: {1: ZZ(1)}, 1:{0:ZZ(2)}}, (2, 2), ZZ)
|
| 292 |
+
Ad = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 293 |
+
|
| 294 |
+
assert As.unify(As) == (As, As)
|
| 295 |
+
assert Ad.unify(Ad) == (Ad, Ad)
|
| 296 |
+
|
| 297 |
+
Bs, Bd = As.unify(Ad, fmt='dense')
|
| 298 |
+
assert Bs.rep == DDM([[0, 1], [2, 0]], (2, 2), ZZ).to_dfm_or_ddm()
|
| 299 |
+
assert Bd.rep == DDM([[1, 2],[3, 4]], (2, 2), ZZ).to_dfm_or_ddm()
|
| 300 |
+
|
| 301 |
+
Bs, Bd = As.unify(Ad, fmt='sparse')
|
| 302 |
+
assert Bs.rep == SDM({0: {1: 1}, 1: {0: 2}}, (2, 2), ZZ)
|
| 303 |
+
assert Bd.rep == SDM({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ)
|
| 304 |
+
|
| 305 |
+
raises(ValueError, lambda: As.unify(Ad, fmt='invalid'))
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def test_DomainMatrix_to_Matrix():
|
| 309 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 310 |
+
A_Matrix = Matrix([[1, 2], [3, 4]])
|
| 311 |
+
assert A.to_Matrix() == A_Matrix
|
| 312 |
+
assert A.to_sparse().to_Matrix() == A_Matrix
|
| 313 |
+
assert A.convert_to(QQ).to_Matrix() == A_Matrix
|
| 314 |
+
assert A.convert_to(QQ.algebraic_field(sqrt(2))).to_Matrix() == A_Matrix
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def test_DomainMatrix_to_list():
|
| 318 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 319 |
+
assert A.to_list() == [[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]]
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def test_DomainMatrix_to_list_flat():
|
| 323 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 324 |
+
assert A.to_list_flat() == [ZZ(1), ZZ(2), ZZ(3), ZZ(4)]
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def test_DomainMatrix_flat():
|
| 328 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 329 |
+
assert A.flat() == [ZZ(1), ZZ(2), ZZ(3), ZZ(4)]
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def test_DomainMatrix_from_list_flat():
|
| 333 |
+
nums = [ZZ(1), ZZ(2), ZZ(3), ZZ(4)]
|
| 334 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 335 |
+
|
| 336 |
+
assert DomainMatrix.from_list_flat(nums, (2, 2), ZZ) == A
|
| 337 |
+
assert DDM.from_list_flat(nums, (2, 2), ZZ) == A.rep.to_ddm()
|
| 338 |
+
assert SDM.from_list_flat(nums, (2, 2), ZZ) == A.rep.to_sdm()
|
| 339 |
+
|
| 340 |
+
assert A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain)
|
| 341 |
+
|
| 342 |
+
raises(DMBadInputError, DomainMatrix.from_list_flat, nums, (2, 3), ZZ)
|
| 343 |
+
raises(DMBadInputError, DDM.from_list_flat, nums, (2, 3), ZZ)
|
| 344 |
+
raises(DMBadInputError, SDM.from_list_flat, nums, (2, 3), ZZ)
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def test_DomainMatrix_to_dod():
|
| 348 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 349 |
+
assert A.to_dod() == {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}}
|
| 350 |
+
A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(4)]], (2, 2), ZZ)
|
| 351 |
+
assert A.to_dod() == {0: {0: ZZ(1)}, 1: {1: ZZ(4)}}
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def test_DomainMatrix_from_dod():
|
| 355 |
+
items = {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}}
|
| 356 |
+
A = DM([[1, 2], [3, 4]], ZZ)
|
| 357 |
+
assert DomainMatrix.from_dod(items, (2, 2), ZZ) == A.to_sparse()
|
| 358 |
+
assert A.from_dod_like(items) == A
|
| 359 |
+
assert A.from_dod_like(items, QQ) == A.convert_to(QQ)
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def test_DomainMatrix_to_dok():
|
| 363 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 364 |
+
assert A.to_dok() == {(0, 0):ZZ(1), (0, 1):ZZ(2), (1, 0):ZZ(3), (1, 1):ZZ(4)}
|
| 365 |
+
A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(4)]], (2, 2), ZZ)
|
| 366 |
+
dok = {(0, 0):ZZ(1), (1, 1):ZZ(4)}
|
| 367 |
+
assert A.to_dok() == dok
|
| 368 |
+
assert A.to_dense().to_dok() == dok
|
| 369 |
+
assert A.to_sparse().to_dok() == dok
|
| 370 |
+
assert A.rep.to_ddm().to_dok() == dok
|
| 371 |
+
assert A.rep.to_sdm().to_dok() == dok
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def test_DomainMatrix_from_dok():
|
| 375 |
+
items = {(0, 0): ZZ(1), (1, 1): ZZ(2)}
|
| 376 |
+
A = DM([[1, 0], [0, 2]], ZZ)
|
| 377 |
+
assert DomainMatrix.from_dok(items, (2, 2), ZZ) == A.to_sparse()
|
| 378 |
+
assert DDM.from_dok(items, (2, 2), ZZ) == A.rep.to_ddm()
|
| 379 |
+
assert SDM.from_dok(items, (2, 2), ZZ) == A.rep.to_sdm()
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def test_DomainMatrix_repr():
|
| 383 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 384 |
+
assert repr(A) == 'DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)'
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def test_DomainMatrix_transpose():
|
| 388 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 389 |
+
AT = DomainMatrix([[ZZ(1), ZZ(3)], [ZZ(2), ZZ(4)]], (2, 2), ZZ)
|
| 390 |
+
assert A.transpose() == AT
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def test_DomainMatrix_is_zero_matrix():
|
| 394 |
+
A = DomainMatrix([[ZZ(1)]], (1, 1), ZZ)
|
| 395 |
+
B = DomainMatrix([[ZZ(0)]], (1, 1), ZZ)
|
| 396 |
+
assert A.is_zero_matrix is False
|
| 397 |
+
assert B.is_zero_matrix is True
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def test_DomainMatrix_is_upper():
|
| 401 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(0), ZZ(4)]], (2, 2), ZZ)
|
| 402 |
+
B = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 403 |
+
assert A.is_upper is True
|
| 404 |
+
assert B.is_upper is False
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def test_DomainMatrix_is_lower():
|
| 408 |
+
A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 409 |
+
B = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 410 |
+
assert A.is_lower is True
|
| 411 |
+
assert B.is_lower is False
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def test_DomainMatrix_is_diagonal():
|
| 415 |
+
A = DM([[1, 0], [0, 4]], ZZ)
|
| 416 |
+
B = DM([[1, 2], [3, 4]], ZZ)
|
| 417 |
+
assert A.is_diagonal is A.to_sparse().is_diagonal is True
|
| 418 |
+
assert B.is_diagonal is B.to_sparse().is_diagonal is False
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
def test_DomainMatrix_is_square():
|
| 422 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 423 |
+
B = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)], [ZZ(5), ZZ(6)]], (3, 2), ZZ)
|
| 424 |
+
assert A.is_square is True
|
| 425 |
+
assert B.is_square is False
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def test_DomainMatrix_diagonal():
|
| 429 |
+
A = DM([[1, 2], [3, 4]], ZZ)
|
| 430 |
+
assert A.diagonal() == A.to_sparse().diagonal() == [ZZ(1), ZZ(4)]
|
| 431 |
+
A = DM([[1, 2], [3, 4], [5, 6]], ZZ)
|
| 432 |
+
assert A.diagonal() == A.to_sparse().diagonal() == [ZZ(1), ZZ(4)]
|
| 433 |
+
A = DM([[1, 2, 3], [4, 5, 6]], ZZ)
|
| 434 |
+
assert A.diagonal() == A.to_sparse().diagonal() == [ZZ(1), ZZ(5)]
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
def test_DomainMatrix_rank():
|
| 438 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(6), QQ(8)]], (3, 2), QQ)
|
| 439 |
+
assert A.rank() == 2
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def test_DomainMatrix_add():
|
| 443 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 444 |
+
B = DomainMatrix([[ZZ(2), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ)
|
| 445 |
+
assert A + A == A.add(A) == B
|
| 446 |
+
|
| 447 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 448 |
+
L = [[2, 3], [3, 4]]
|
| 449 |
+
raises(TypeError, lambda: A + L)
|
| 450 |
+
raises(TypeError, lambda: L + A)
|
| 451 |
+
|
| 452 |
+
A1 = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 453 |
+
A2 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 454 |
+
raises(DMShapeError, lambda: A1 + A2)
|
| 455 |
+
raises(DMShapeError, lambda: A2 + A1)
|
| 456 |
+
raises(DMShapeError, lambda: A1.add(A2))
|
| 457 |
+
raises(DMShapeError, lambda: A2.add(A1))
|
| 458 |
+
|
| 459 |
+
Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 460 |
+
Aq = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 461 |
+
Asum = DomainMatrix([[QQ(2), QQ(4)], [QQ(6), QQ(8)]], (2, 2), QQ)
|
| 462 |
+
assert Az + Aq == Asum
|
| 463 |
+
assert Aq + Az == Asum
|
| 464 |
+
raises(DMDomainError, lambda: Az.add(Aq))
|
| 465 |
+
raises(DMDomainError, lambda: Aq.add(Az))
|
| 466 |
+
|
| 467 |
+
As = DomainMatrix({0: {1: ZZ(1)}, 1: {0: ZZ(2)}}, (2, 2), ZZ)
|
| 468 |
+
Ad = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 469 |
+
|
| 470 |
+
Asd = As + Ad
|
| 471 |
+
Ads = Ad + As
|
| 472 |
+
assert Asd == DomainMatrix([[1, 3], [5, 4]], (2, 2), ZZ)
|
| 473 |
+
assert Asd.rep == DDM([[1, 3], [5, 4]], (2, 2), ZZ).to_dfm_or_ddm()
|
| 474 |
+
assert Ads == DomainMatrix([[1, 3], [5, 4]], (2, 2), ZZ)
|
| 475 |
+
assert Ads.rep == DDM([[1, 3], [5, 4]], (2, 2), ZZ).to_dfm_or_ddm()
|
| 476 |
+
raises(DMFormatError, lambda: As.add(Ad))
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
def test_DomainMatrix_sub():
|
| 480 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 481 |
+
B = DomainMatrix([[ZZ(0), ZZ(0)], [ZZ(0), ZZ(0)]], (2, 2), ZZ)
|
| 482 |
+
assert A - A == A.sub(A) == B
|
| 483 |
+
|
| 484 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 485 |
+
L = [[2, 3], [3, 4]]
|
| 486 |
+
raises(TypeError, lambda: A - L)
|
| 487 |
+
raises(TypeError, lambda: L - A)
|
| 488 |
+
|
| 489 |
+
A1 = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 490 |
+
A2 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 491 |
+
raises(DMShapeError, lambda: A1 - A2)
|
| 492 |
+
raises(DMShapeError, lambda: A2 - A1)
|
| 493 |
+
raises(DMShapeError, lambda: A1.sub(A2))
|
| 494 |
+
raises(DMShapeError, lambda: A2.sub(A1))
|
| 495 |
+
|
| 496 |
+
Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 497 |
+
Aq = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 498 |
+
Adiff = DomainMatrix([[QQ(0), QQ(0)], [QQ(0), QQ(0)]], (2, 2), QQ)
|
| 499 |
+
assert Az - Aq == Adiff
|
| 500 |
+
assert Aq - Az == Adiff
|
| 501 |
+
raises(DMDomainError, lambda: Az.sub(Aq))
|
| 502 |
+
raises(DMDomainError, lambda: Aq.sub(Az))
|
| 503 |
+
|
| 504 |
+
As = DomainMatrix({0: {1: ZZ(1)}, 1: {0: ZZ(2)}}, (2, 2), ZZ)
|
| 505 |
+
Ad = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 506 |
+
|
| 507 |
+
Asd = As - Ad
|
| 508 |
+
Ads = Ad - As
|
| 509 |
+
assert Asd == DomainMatrix([[-1, -1], [-1, -4]], (2, 2), ZZ)
|
| 510 |
+
assert Asd.rep == DDM([[-1, -1], [-1, -4]], (2, 2), ZZ).to_dfm_or_ddm()
|
| 511 |
+
assert Asd == -Ads
|
| 512 |
+
assert Asd.rep == -Ads.rep
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def test_DomainMatrix_neg():
|
| 516 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 517 |
+
Aneg = DomainMatrix([[ZZ(-1), ZZ(-2)], [ZZ(-3), ZZ(-4)]], (2, 2), ZZ)
|
| 518 |
+
assert -A == A.neg() == Aneg
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def test_DomainMatrix_mul():
|
| 522 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 523 |
+
A2 = DomainMatrix([[ZZ(7), ZZ(10)], [ZZ(15), ZZ(22)]], (2, 2), ZZ)
|
| 524 |
+
assert A*A == A.matmul(A) == A2
|
| 525 |
+
|
| 526 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 527 |
+
L = [[1, 2], [3, 4]]
|
| 528 |
+
raises(TypeError, lambda: A * L)
|
| 529 |
+
raises(TypeError, lambda: L * A)
|
| 530 |
+
|
| 531 |
+
Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 532 |
+
Aq = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 533 |
+
Aprod = DomainMatrix([[QQ(7), QQ(10)], [QQ(15), QQ(22)]], (2, 2), QQ)
|
| 534 |
+
assert Az * Aq == Aprod
|
| 535 |
+
assert Aq * Az == Aprod
|
| 536 |
+
raises(DMDomainError, lambda: Az.matmul(Aq))
|
| 537 |
+
raises(DMDomainError, lambda: Aq.matmul(Az))
|
| 538 |
+
|
| 539 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 540 |
+
AA = DomainMatrix([[ZZ(2), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ)
|
| 541 |
+
x = ZZ(2)
|
| 542 |
+
assert A * x == x * A == A.mul(x) == AA
|
| 543 |
+
|
| 544 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 545 |
+
AA = DomainMatrix.zeros((2, 2), ZZ)
|
| 546 |
+
x = ZZ(0)
|
| 547 |
+
assert A * x == x * A == A.mul(x).to_sparse() == AA
|
| 548 |
+
|
| 549 |
+
As = DomainMatrix({0: {1: ZZ(1)}, 1: {0: ZZ(2)}}, (2, 2), ZZ)
|
| 550 |
+
Ad = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 551 |
+
|
| 552 |
+
Asd = As * Ad
|
| 553 |
+
Ads = Ad * As
|
| 554 |
+
assert Asd == DomainMatrix([[3, 4], [2, 4]], (2, 2), ZZ)
|
| 555 |
+
assert Asd.rep == DDM([[3, 4], [2, 4]], (2, 2), ZZ).to_dfm_or_ddm()
|
| 556 |
+
assert Ads == DomainMatrix([[4, 1], [8, 3]], (2, 2), ZZ)
|
| 557 |
+
assert Ads.rep == DDM([[4, 1], [8, 3]], (2, 2), ZZ).to_dfm_or_ddm()
|
| 558 |
+
|
| 559 |
+
|
| 560 |
+
def test_DomainMatrix_mul_elementwise():
|
| 561 |
+
A = DomainMatrix([[ZZ(2), ZZ(2)], [ZZ(0), ZZ(0)]], (2, 2), ZZ)
|
| 562 |
+
B = DomainMatrix([[ZZ(4), ZZ(0)], [ZZ(3), ZZ(0)]], (2, 2), ZZ)
|
| 563 |
+
C = DomainMatrix([[ZZ(8), ZZ(0)], [ZZ(0), ZZ(0)]], (2, 2), ZZ)
|
| 564 |
+
assert A.mul_elementwise(B) == C
|
| 565 |
+
assert B.mul_elementwise(A) == C
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
def test_DomainMatrix_pow():
|
| 569 |
+
eye = DomainMatrix.eye(2, ZZ)
|
| 570 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 571 |
+
A2 = DomainMatrix([[ZZ(7), ZZ(10)], [ZZ(15), ZZ(22)]], (2, 2), ZZ)
|
| 572 |
+
A3 = DomainMatrix([[ZZ(37), ZZ(54)], [ZZ(81), ZZ(118)]], (2, 2), ZZ)
|
| 573 |
+
assert A**0 == A.pow(0) == eye
|
| 574 |
+
assert A**1 == A.pow(1) == A
|
| 575 |
+
assert A**2 == A.pow(2) == A2
|
| 576 |
+
assert A**3 == A.pow(3) == A3
|
| 577 |
+
|
| 578 |
+
raises(TypeError, lambda: A ** Rational(1, 2))
|
| 579 |
+
raises(NotImplementedError, lambda: A ** -1)
|
| 580 |
+
raises(NotImplementedError, lambda: A.pow(-1))
|
| 581 |
+
|
| 582 |
+
A = DomainMatrix.zeros((2, 1), ZZ)
|
| 583 |
+
raises(DMNonSquareMatrixError, lambda: A ** 1)
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
def test_DomainMatrix_clear_denoms():
|
| 587 |
+
A = DM([[(1,2),(1,3)],[(1,4),(1,5)]], QQ)
|
| 588 |
+
|
| 589 |
+
den_Z = DomainScalar(ZZ(60), ZZ)
|
| 590 |
+
Anum_Z = DM([[30, 20], [15, 12]], ZZ)
|
| 591 |
+
Anum_Q = Anum_Z.convert_to(QQ)
|
| 592 |
+
|
| 593 |
+
assert A.clear_denoms() == (den_Z, Anum_Q)
|
| 594 |
+
assert A.clear_denoms(convert=True) == (den_Z, Anum_Z)
|
| 595 |
+
assert A * den_Z == Anum_Q
|
| 596 |
+
assert A == Anum_Q / den_Z
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
def test_DomainMatrix_clear_denoms_rowwise():
|
| 600 |
+
A = DM([[(1,2),(1,3)],[(1,4),(1,5)]], QQ)
|
| 601 |
+
|
| 602 |
+
den_Z = DM([[6, 0], [0, 20]], ZZ).to_sparse()
|
| 603 |
+
Anum_Z = DM([[3, 2], [5, 4]], ZZ)
|
| 604 |
+
Anum_Q = DM([[3, 2], [5, 4]], QQ)
|
| 605 |
+
|
| 606 |
+
assert A.clear_denoms_rowwise() == (den_Z, Anum_Q)
|
| 607 |
+
assert A.clear_denoms_rowwise(convert=True) == (den_Z, Anum_Z)
|
| 608 |
+
assert den_Z * A == Anum_Q
|
| 609 |
+
assert A == den_Z.to_field().inv() * Anum_Q
|
| 610 |
+
|
| 611 |
+
A = DM([[(1,2),(1,3),0,0],[0,0,0,0], [(1,4),(1,5),(1,6),(1,7)]], QQ)
|
| 612 |
+
den_Z = DM([[6, 0, 0], [0, 1, 0], [0, 0, 420]], ZZ).to_sparse()
|
| 613 |
+
Anum_Z = DM([[3, 2, 0, 0], [0, 0, 0, 0], [105, 84, 70, 60]], ZZ)
|
| 614 |
+
Anum_Q = Anum_Z.convert_to(QQ)
|
| 615 |
+
|
| 616 |
+
assert A.clear_denoms_rowwise() == (den_Z, Anum_Q)
|
| 617 |
+
assert A.clear_denoms_rowwise(convert=True) == (den_Z, Anum_Z)
|
| 618 |
+
assert den_Z * A == Anum_Q
|
| 619 |
+
assert A == den_Z.to_field().inv() * Anum_Q
|
| 620 |
+
|
| 621 |
+
|
| 622 |
+
def test_DomainMatrix_cancel_denom():
|
| 623 |
+
A = DM([[2, 4], [6, 8]], ZZ)
|
| 624 |
+
assert A.cancel_denom(ZZ(1)) == (DM([[2, 4], [6, 8]], ZZ), ZZ(1))
|
| 625 |
+
assert A.cancel_denom(ZZ(3)) == (DM([[2, 4], [6, 8]], ZZ), ZZ(3))
|
| 626 |
+
assert A.cancel_denom(ZZ(4)) == (DM([[1, 2], [3, 4]], ZZ), ZZ(2))
|
| 627 |
+
|
| 628 |
+
A = DM([[1, 2], [3, 4]], ZZ)
|
| 629 |
+
assert A.cancel_denom(ZZ(2)) == (A, ZZ(2))
|
| 630 |
+
assert A.cancel_denom(ZZ(-2)) == (-A, ZZ(2))
|
| 631 |
+
|
| 632 |
+
# Test canonicalization of denominator over Gaussian rationals.
|
| 633 |
+
A = DM([[1, 2], [3, 4]], QQ_I)
|
| 634 |
+
assert A.cancel_denom(QQ_I(0,2)) == (QQ_I(0,-1)*A, QQ_I(2))
|
| 635 |
+
|
| 636 |
+
raises(ZeroDivisionError, lambda: A.cancel_denom(ZZ(0)))
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
def test_DomainMatrix_cancel_denom_elementwise():
|
| 640 |
+
A = DM([[2, 4], [6, 8]], ZZ)
|
| 641 |
+
numers, denoms = A.cancel_denom_elementwise(ZZ(1))
|
| 642 |
+
assert numers == DM([[2, 4], [6, 8]], ZZ)
|
| 643 |
+
assert denoms == DM([[1, 1], [1, 1]], ZZ)
|
| 644 |
+
numers, denoms = A.cancel_denom_elementwise(ZZ(4))
|
| 645 |
+
assert numers == DM([[1, 1], [3, 2]], ZZ)
|
| 646 |
+
assert denoms == DM([[2, 1], [2, 1]], ZZ)
|
| 647 |
+
|
| 648 |
+
raises(ZeroDivisionError, lambda: A.cancel_denom_elementwise(ZZ(0)))
|
| 649 |
+
|
| 650 |
+
|
| 651 |
+
def test_DomainMatrix_content_primitive():
|
| 652 |
+
A = DM([[2, 4], [6, 8]], ZZ)
|
| 653 |
+
A_primitive = DM([[1, 2], [3, 4]], ZZ)
|
| 654 |
+
A_content = ZZ(2)
|
| 655 |
+
assert A.content() == A_content
|
| 656 |
+
assert A.primitive() == (A_content, A_primitive)
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
def test_DomainMatrix_scc():
|
| 660 |
+
Ad = DomainMatrix([[ZZ(1), ZZ(2), ZZ(3)],
|
| 661 |
+
[ZZ(0), ZZ(1), ZZ(0)],
|
| 662 |
+
[ZZ(2), ZZ(0), ZZ(4)]], (3, 3), ZZ)
|
| 663 |
+
As = Ad.to_sparse()
|
| 664 |
+
Addm = Ad.rep
|
| 665 |
+
Asdm = As.rep
|
| 666 |
+
for A in [Ad, As, Addm, Asdm]:
|
| 667 |
+
assert Ad.scc() == [[1], [0, 2]]
|
| 668 |
+
|
| 669 |
+
A = DM([[ZZ(1), ZZ(2), ZZ(3)]], ZZ)
|
| 670 |
+
raises(DMNonSquareMatrixError, lambda: A.scc())
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def test_DomainMatrix_rref():
|
| 674 |
+
# More tests in test_rref.py
|
| 675 |
+
A = DomainMatrix([], (0, 1), QQ)
|
| 676 |
+
assert A.rref() == (A, ())
|
| 677 |
+
|
| 678 |
+
A = DomainMatrix([[QQ(1)]], (1, 1), QQ)
|
| 679 |
+
assert A.rref() == (A, (0,))
|
| 680 |
+
|
| 681 |
+
A = DomainMatrix([[QQ(0)]], (1, 1), QQ)
|
| 682 |
+
assert A.rref() == (A, ())
|
| 683 |
+
|
| 684 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 685 |
+
Ar, pivots = A.rref()
|
| 686 |
+
assert Ar == DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ)
|
| 687 |
+
assert pivots == (0, 1)
|
| 688 |
+
|
| 689 |
+
A = DomainMatrix([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 690 |
+
Ar, pivots = A.rref()
|
| 691 |
+
assert Ar == DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ)
|
| 692 |
+
assert pivots == (0, 1)
|
| 693 |
+
|
| 694 |
+
A = DomainMatrix([[QQ(0), QQ(2)], [QQ(0), QQ(4)]], (2, 2), QQ)
|
| 695 |
+
Ar, pivots = A.rref()
|
| 696 |
+
assert Ar == DomainMatrix([[QQ(0), QQ(1)], [QQ(0), QQ(0)]], (2, 2), QQ)
|
| 697 |
+
assert pivots == (1,)
|
| 698 |
+
|
| 699 |
+
Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 700 |
+
Ar, pivots = Az.rref()
|
| 701 |
+
assert Ar == DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ)
|
| 702 |
+
assert pivots == (0, 1)
|
| 703 |
+
|
| 704 |
+
methods = ('auto', 'GJ', 'FF', 'CD', 'GJ_dense', 'FF_dense', 'CD_dense')
|
| 705 |
+
Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 706 |
+
for method in methods:
|
| 707 |
+
Ar, pivots = Az.rref(method=method)
|
| 708 |
+
assert Ar == DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ)
|
| 709 |
+
assert pivots == (0, 1)
|
| 710 |
+
|
| 711 |
+
raises(ValueError, lambda: Az.rref(method='foo'))
|
| 712 |
+
raises(ValueError, lambda: Az.rref_den(method='foo'))
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
def test_DomainMatrix_columnspace():
|
| 716 |
+
A = DomainMatrix([[QQ(1), QQ(-1), QQ(1)], [QQ(2), QQ(-2), QQ(3)]], (2, 3), QQ)
|
| 717 |
+
Acol = DomainMatrix([[QQ(1), QQ(1)], [QQ(2), QQ(3)]], (2, 2), QQ)
|
| 718 |
+
assert A.columnspace() == Acol
|
| 719 |
+
|
| 720 |
+
Az = DomainMatrix([[ZZ(1), ZZ(-1), ZZ(1)], [ZZ(2), ZZ(-2), ZZ(3)]], (2, 3), ZZ)
|
| 721 |
+
raises(DMNotAField, lambda: Az.columnspace())
|
| 722 |
+
|
| 723 |
+
A = DomainMatrix([[QQ(1), QQ(-1), QQ(1)], [QQ(2), QQ(-2), QQ(3)]], (2, 3), QQ, fmt='sparse')
|
| 724 |
+
Acol = DomainMatrix({0: {0: QQ(1), 1: QQ(1)}, 1: {0: QQ(2), 1: QQ(3)}}, (2, 2), QQ)
|
| 725 |
+
assert A.columnspace() == Acol
|
| 726 |
+
|
| 727 |
+
|
| 728 |
+
def test_DomainMatrix_rowspace():
|
| 729 |
+
A = DomainMatrix([[QQ(1), QQ(-1), QQ(1)], [QQ(2), QQ(-2), QQ(3)]], (2, 3), QQ)
|
| 730 |
+
assert A.rowspace() == A
|
| 731 |
+
|
| 732 |
+
Az = DomainMatrix([[ZZ(1), ZZ(-1), ZZ(1)], [ZZ(2), ZZ(-2), ZZ(3)]], (2, 3), ZZ)
|
| 733 |
+
raises(DMNotAField, lambda: Az.rowspace())
|
| 734 |
+
|
| 735 |
+
A = DomainMatrix([[QQ(1), QQ(-1), QQ(1)], [QQ(2), QQ(-2), QQ(3)]], (2, 3), QQ, fmt='sparse')
|
| 736 |
+
assert A.rowspace() == A
|
| 737 |
+
|
| 738 |
+
|
| 739 |
+
def test_DomainMatrix_nullspace():
|
| 740 |
+
A = DomainMatrix([[QQ(1), QQ(1)], [QQ(1), QQ(1)]], (2, 2), QQ)
|
| 741 |
+
Anull = DomainMatrix([[QQ(-1), QQ(1)]], (1, 2), QQ)
|
| 742 |
+
assert A.nullspace() == Anull
|
| 743 |
+
|
| 744 |
+
A = DomainMatrix([[ZZ(1), ZZ(1)], [ZZ(1), ZZ(1)]], (2, 2), ZZ)
|
| 745 |
+
Anull = DomainMatrix([[ZZ(-1), ZZ(1)]], (1, 2), ZZ)
|
| 746 |
+
assert A.nullspace() == Anull
|
| 747 |
+
|
| 748 |
+
raises(DMNotAField, lambda: A.nullspace(divide_last=True))
|
| 749 |
+
|
| 750 |
+
A = DomainMatrix([[ZZ(2), ZZ(2)], [ZZ(2), ZZ(2)]], (2, 2), ZZ)
|
| 751 |
+
Anull = DomainMatrix([[ZZ(-2), ZZ(2)]], (1, 2), ZZ)
|
| 752 |
+
|
| 753 |
+
Arref, den, pivots = A.rref_den()
|
| 754 |
+
assert den == ZZ(2)
|
| 755 |
+
assert Arref.nullspace_from_rref() == Anull
|
| 756 |
+
assert Arref.nullspace_from_rref(pivots) == Anull
|
| 757 |
+
assert Arref.to_sparse().nullspace_from_rref() == Anull.to_sparse()
|
| 758 |
+
assert Arref.to_sparse().nullspace_from_rref(pivots) == Anull.to_sparse()
|
| 759 |
+
|
| 760 |
+
|
| 761 |
+
def test_DomainMatrix_solve():
|
| 762 |
+
# XXX: Maybe the _solve method should be changed...
|
| 763 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ)
|
| 764 |
+
b = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 765 |
+
particular = DomainMatrix([[1, 0]], (1, 2), QQ)
|
| 766 |
+
nullspace = DomainMatrix([[-2, 1]], (1, 2), QQ)
|
| 767 |
+
assert A._solve(b) == (particular, nullspace)
|
| 768 |
+
|
| 769 |
+
b3 = DomainMatrix([[QQ(1)], [QQ(1)], [QQ(1)]], (3, 1), QQ)
|
| 770 |
+
raises(DMShapeError, lambda: A._solve(b3))
|
| 771 |
+
|
| 772 |
+
bz = DomainMatrix([[ZZ(1)], [ZZ(1)]], (2, 1), ZZ)
|
| 773 |
+
raises(DMNotAField, lambda: A._solve(bz))
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
def test_DomainMatrix_inv():
|
| 777 |
+
A = DomainMatrix([], (0, 0), QQ)
|
| 778 |
+
assert A.inv() == A
|
| 779 |
+
|
| 780 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 781 |
+
Ainv = DomainMatrix([[QQ(-2), QQ(1)], [QQ(3, 2), QQ(-1, 2)]], (2, 2), QQ)
|
| 782 |
+
assert A.inv() == Ainv
|
| 783 |
+
|
| 784 |
+
Az = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 785 |
+
raises(DMNotAField, lambda: Az.inv())
|
| 786 |
+
|
| 787 |
+
Ans = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 788 |
+
raises(DMNonSquareMatrixError, lambda: Ans.inv())
|
| 789 |
+
|
| 790 |
+
Aninv = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(6)]], (2, 2), QQ)
|
| 791 |
+
raises(DMNonInvertibleMatrixError, lambda: Aninv.inv())
|
| 792 |
+
|
| 793 |
+
Z3 = FF(3)
|
| 794 |
+
assert DM([[1, 2], [3, 4]], Z3).inv() == DM([[1, 1], [0, 1]], Z3)
|
| 795 |
+
|
| 796 |
+
Z6 = FF(6)
|
| 797 |
+
raises(DMNotAField, lambda: DM([[1, 2], [3, 4]], Z6).inv())
|
| 798 |
+
|
| 799 |
+
|
| 800 |
+
def test_DomainMatrix_det():
|
| 801 |
+
A = DomainMatrix([], (0, 0), ZZ)
|
| 802 |
+
assert A.det() == 1
|
| 803 |
+
|
| 804 |
+
A = DomainMatrix([[1]], (1, 1), ZZ)
|
| 805 |
+
assert A.det() == 1
|
| 806 |
+
|
| 807 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 808 |
+
assert A.det() == ZZ(-2)
|
| 809 |
+
|
| 810 |
+
A = DomainMatrix([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(3), ZZ(5)]], (3, 3), ZZ)
|
| 811 |
+
assert A.det() == ZZ(-1)
|
| 812 |
+
|
| 813 |
+
A = DomainMatrix([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(1), ZZ(2), ZZ(4)], [ZZ(1), ZZ(2), ZZ(5)]], (3, 3), ZZ)
|
| 814 |
+
assert A.det() == ZZ(0)
|
| 815 |
+
|
| 816 |
+
Ans = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 817 |
+
raises(DMNonSquareMatrixError, lambda: Ans.det())
|
| 818 |
+
|
| 819 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 820 |
+
assert A.det() == QQ(-2)
|
| 821 |
+
|
| 822 |
+
|
| 823 |
+
def test_DomainMatrix_eval_poly():
|
| 824 |
+
dM = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 825 |
+
p = [ZZ(1), ZZ(2), ZZ(3)]
|
| 826 |
+
result = DomainMatrix([[ZZ(12), ZZ(14)], [ZZ(21), ZZ(33)]], (2, 2), ZZ)
|
| 827 |
+
assert dM.eval_poly(p) == result == p[0]*dM**2 + p[1]*dM + p[2]*dM**0
|
| 828 |
+
assert dM.eval_poly([]) == dM.zeros(dM.shape, dM.domain)
|
| 829 |
+
assert dM.eval_poly([ZZ(2)]) == 2*dM.eye(2, dM.domain)
|
| 830 |
+
|
| 831 |
+
dM2 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 832 |
+
raises(DMNonSquareMatrixError, lambda: dM2.eval_poly([ZZ(1)]))
|
| 833 |
+
|
| 834 |
+
|
| 835 |
+
def test_DomainMatrix_eval_poly_mul():
|
| 836 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 837 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 838 |
+
p = [ZZ(1), ZZ(2), ZZ(3)]
|
| 839 |
+
result = DomainMatrix([[ZZ(40)], [ZZ(87)]], (2, 1), ZZ)
|
| 840 |
+
assert A.eval_poly_mul(p, b) == result == p[0]*A**2*b + p[1]*A*b + p[2]*b
|
| 841 |
+
|
| 842 |
+
dM = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 843 |
+
dM1 = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 844 |
+
raises(DMNonSquareMatrixError, lambda: dM1.eval_poly_mul([ZZ(1)], b))
|
| 845 |
+
b1 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 846 |
+
raises(DMShapeError, lambda: dM.eval_poly_mul([ZZ(1)], b1))
|
| 847 |
+
bq = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 848 |
+
raises(DMDomainError, lambda: dM.eval_poly_mul([ZZ(1)], bq))
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
def _check_solve_den(A, b, xnum, xden):
|
| 852 |
+
# Examples for solve_den, solve_den_charpoly, solve_den_rref should use
|
| 853 |
+
# this so that all methods and types are tested.
|
| 854 |
+
|
| 855 |
+
case1 = (A, xnum, b)
|
| 856 |
+
case2 = (A.to_sparse(), xnum.to_sparse(), b.to_sparse())
|
| 857 |
+
|
| 858 |
+
for Ai, xnum_i, b_i in [case1, case2]:
|
| 859 |
+
# The key invariant for solve_den:
|
| 860 |
+
assert Ai*xnum_i == xden*b_i
|
| 861 |
+
|
| 862 |
+
# solve_den_rref can differ at least by a minus sign
|
| 863 |
+
answers = [(xnum_i, xden), (-xnum_i, -xden)]
|
| 864 |
+
assert Ai.solve_den(b) in answers
|
| 865 |
+
assert Ai.solve_den(b, method='rref') in answers
|
| 866 |
+
assert Ai.solve_den_rref(b) in answers
|
| 867 |
+
|
| 868 |
+
# charpoly can only be used if A is square and guarantees to return the
|
| 869 |
+
# actual determinant as a denominator.
|
| 870 |
+
m, n = Ai.shape
|
| 871 |
+
if m == n:
|
| 872 |
+
assert Ai.solve_den(b_i, method='charpoly') == (xnum_i, xden)
|
| 873 |
+
assert Ai.solve_den_charpoly(b_i) == (xnum_i, xden)
|
| 874 |
+
else:
|
| 875 |
+
raises(DMNonSquareMatrixError, lambda: Ai.solve_den_charpoly(b))
|
| 876 |
+
raises(DMNonSquareMatrixError, lambda: Ai.solve_den(b, method='charpoly'))
|
| 877 |
+
|
| 878 |
+
|
| 879 |
+
def test_DomainMatrix_solve_den():
|
| 880 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 881 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 882 |
+
result = DomainMatrix([[ZZ(0)], [ZZ(-1)]], (2, 1), ZZ)
|
| 883 |
+
den = ZZ(-2)
|
| 884 |
+
_check_solve_den(A, b, result, den)
|
| 885 |
+
|
| 886 |
+
A = DomainMatrix([
|
| 887 |
+
[ZZ(1), ZZ(2), ZZ(3)],
|
| 888 |
+
[ZZ(1), ZZ(2), ZZ(4)],
|
| 889 |
+
[ZZ(1), ZZ(3), ZZ(5)]], (3, 3), ZZ)
|
| 890 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(2)], [ZZ(3)]], (3, 1), ZZ)
|
| 891 |
+
result = DomainMatrix([[ZZ(2)], [ZZ(0)], [ZZ(-1)]], (3, 1), ZZ)
|
| 892 |
+
den = ZZ(-1)
|
| 893 |
+
_check_solve_den(A, b, result, den)
|
| 894 |
+
|
| 895 |
+
A = DomainMatrix([[ZZ(2)], [ZZ(2)]], (2, 1), ZZ)
|
| 896 |
+
b = DomainMatrix([[ZZ(3)], [ZZ(3)]], (2, 1), ZZ)
|
| 897 |
+
result = DomainMatrix([[ZZ(3)]], (1, 1), ZZ)
|
| 898 |
+
den = ZZ(2)
|
| 899 |
+
_check_solve_den(A, b, result, den)
|
| 900 |
+
|
| 901 |
+
|
| 902 |
+
def test_DomainMatrix_solve_den_charpoly():
|
| 903 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 904 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 905 |
+
A1 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 906 |
+
raises(DMNonSquareMatrixError, lambda: A1.solve_den_charpoly(b))
|
| 907 |
+
b1 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 908 |
+
raises(DMShapeError, lambda: A.solve_den_charpoly(b1))
|
| 909 |
+
bq = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 910 |
+
raises(DMDomainError, lambda: A.solve_den_charpoly(bq))
|
| 911 |
+
|
| 912 |
+
|
| 913 |
+
def test_DomainMatrix_solve_den_charpoly_check():
|
| 914 |
+
# Test check
|
| 915 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(2), ZZ(4)]], (2, 2), ZZ)
|
| 916 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(3)]], (2, 1), ZZ)
|
| 917 |
+
raises(DMNonInvertibleMatrixError, lambda: A.solve_den_charpoly(b))
|
| 918 |
+
adjAb = DomainMatrix([[ZZ(-2)], [ZZ(1)]], (2, 1), ZZ)
|
| 919 |
+
assert A.adjugate() * b == adjAb
|
| 920 |
+
assert A.solve_den_charpoly(b, check=False) == (adjAb, ZZ(0))
|
| 921 |
+
|
| 922 |
+
|
| 923 |
+
def test_DomainMatrix_solve_den_errors():
|
| 924 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 925 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 926 |
+
raises(DMShapeError, lambda: A.solve_den(b))
|
| 927 |
+
raises(DMShapeError, lambda: A.solve_den_rref(b))
|
| 928 |
+
|
| 929 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 930 |
+
b = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 931 |
+
raises(DMShapeError, lambda: A.solve_den(b))
|
| 932 |
+
raises(DMShapeError, lambda: A.solve_den_rref(b))
|
| 933 |
+
|
| 934 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 935 |
+
b1 = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 936 |
+
raises(DMShapeError, lambda: A.solve_den(b1))
|
| 937 |
+
|
| 938 |
+
A = DomainMatrix([[ZZ(2)]], (1, 1), ZZ)
|
| 939 |
+
b = DomainMatrix([[ZZ(2)]], (1, 1), ZZ)
|
| 940 |
+
raises(DMBadInputError, lambda: A.solve_den(b1, method='invalid'))
|
| 941 |
+
|
| 942 |
+
A = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 943 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 944 |
+
raises(DMNonSquareMatrixError, lambda: A.solve_den_charpoly(b))
|
| 945 |
+
|
| 946 |
+
|
| 947 |
+
def test_DomainMatrix_solve_den_rref_underdetermined():
|
| 948 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(1), ZZ(2)]], (2, 2), ZZ)
|
| 949 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(1)]], (2, 1), ZZ)
|
| 950 |
+
raises(DMNonInvertibleMatrixError, lambda: A.solve_den(b))
|
| 951 |
+
raises(DMNonInvertibleMatrixError, lambda: A.solve_den_rref(b))
|
| 952 |
+
|
| 953 |
+
|
| 954 |
+
def test_DomainMatrix_adj_poly_det():
|
| 955 |
+
A = DM([[ZZ(1), ZZ(2), ZZ(3)],
|
| 956 |
+
[ZZ(4), ZZ(5), ZZ(6)],
|
| 957 |
+
[ZZ(7), ZZ(8), ZZ(9)]], ZZ)
|
| 958 |
+
p, detA = A.adj_poly_det()
|
| 959 |
+
assert p == [ZZ(1), ZZ(-15), ZZ(-18)]
|
| 960 |
+
assert A.adjugate() == p[0]*A**2 + p[1]*A**1 + p[2]*A**0 == A.eval_poly(p)
|
| 961 |
+
assert A.det() == detA
|
| 962 |
+
|
| 963 |
+
A = DM([[ZZ(1), ZZ(2), ZZ(3)],
|
| 964 |
+
[ZZ(7), ZZ(8), ZZ(9)]], ZZ)
|
| 965 |
+
raises(DMNonSquareMatrixError, lambda: A.adj_poly_det())
|
| 966 |
+
|
| 967 |
+
|
| 968 |
+
def test_DomainMatrix_inv_den():
|
| 969 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 970 |
+
den = ZZ(-2)
|
| 971 |
+
result = DomainMatrix([[ZZ(4), ZZ(-2)], [ZZ(-3), ZZ(1)]], (2, 2), ZZ)
|
| 972 |
+
assert A.inv_den() == (result, den)
|
| 973 |
+
|
| 974 |
+
|
| 975 |
+
def test_DomainMatrix_adjugate():
|
| 976 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 977 |
+
result = DomainMatrix([[ZZ(4), ZZ(-2)], [ZZ(-3), ZZ(1)]], (2, 2), ZZ)
|
| 978 |
+
assert A.adjugate() == result
|
| 979 |
+
|
| 980 |
+
|
| 981 |
+
def test_DomainMatrix_adj_det():
|
| 982 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 983 |
+
adjA = DomainMatrix([[ZZ(4), ZZ(-2)], [ZZ(-3), ZZ(1)]], (2, 2), ZZ)
|
| 984 |
+
assert A.adj_det() == (adjA, ZZ(-2))
|
| 985 |
+
|
| 986 |
+
|
| 987 |
+
def test_DomainMatrix_lu():
|
| 988 |
+
A = DomainMatrix([], (0, 0), QQ)
|
| 989 |
+
assert A.lu() == (A, A, [])
|
| 990 |
+
|
| 991 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 992 |
+
L = DomainMatrix([[QQ(1), QQ(0)], [QQ(3), QQ(1)]], (2, 2), QQ)
|
| 993 |
+
U = DomainMatrix([[QQ(1), QQ(2)], [QQ(0), QQ(-2)]], (2, 2), QQ)
|
| 994 |
+
swaps = []
|
| 995 |
+
assert A.lu() == (L, U, swaps)
|
| 996 |
+
|
| 997 |
+
A = DomainMatrix([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 998 |
+
L = DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ)
|
| 999 |
+
U = DomainMatrix([[QQ(3), QQ(4)], [QQ(0), QQ(2)]], (2, 2), QQ)
|
| 1000 |
+
swaps = [(0, 1)]
|
| 1001 |
+
assert A.lu() == (L, U, swaps)
|
| 1002 |
+
|
| 1003 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ)
|
| 1004 |
+
L = DomainMatrix([[QQ(1), QQ(0)], [QQ(2), QQ(1)]], (2, 2), QQ)
|
| 1005 |
+
U = DomainMatrix([[QQ(1), QQ(2)], [QQ(0), QQ(0)]], (2, 2), QQ)
|
| 1006 |
+
swaps = []
|
| 1007 |
+
assert A.lu() == (L, U, swaps)
|
| 1008 |
+
|
| 1009 |
+
A = DomainMatrix([[QQ(0), QQ(2)], [QQ(0), QQ(4)]], (2, 2), QQ)
|
| 1010 |
+
L = DomainMatrix([[QQ(1), QQ(0)], [QQ(0), QQ(1)]], (2, 2), QQ)
|
| 1011 |
+
U = DomainMatrix([[QQ(0), QQ(2)], [QQ(0), QQ(4)]], (2, 2), QQ)
|
| 1012 |
+
swaps = []
|
| 1013 |
+
assert A.lu() == (L, U, swaps)
|
| 1014 |
+
|
| 1015 |
+
A = DomainMatrix([[QQ(1), QQ(2), QQ(3)], [QQ(4), QQ(5), QQ(6)]], (2, 3), QQ)
|
| 1016 |
+
L = DomainMatrix([[QQ(1), QQ(0)], [QQ(4), QQ(1)]], (2, 2), QQ)
|
| 1017 |
+
U = DomainMatrix([[QQ(1), QQ(2), QQ(3)], [QQ(0), QQ(-3), QQ(-6)]], (2, 3), QQ)
|
| 1018 |
+
swaps = []
|
| 1019 |
+
assert A.lu() == (L, U, swaps)
|
| 1020 |
+
|
| 1021 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ)
|
| 1022 |
+
L = DomainMatrix([
|
| 1023 |
+
[QQ(1), QQ(0), QQ(0)],
|
| 1024 |
+
[QQ(3), QQ(1), QQ(0)],
|
| 1025 |
+
[QQ(5), QQ(2), QQ(1)]], (3, 3), QQ)
|
| 1026 |
+
U = DomainMatrix([[QQ(1), QQ(2)], [QQ(0), QQ(-2)], [QQ(0), QQ(0)]], (3, 2), QQ)
|
| 1027 |
+
swaps = []
|
| 1028 |
+
assert A.lu() == (L, U, swaps)
|
| 1029 |
+
|
| 1030 |
+
A = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 1, 2]]
|
| 1031 |
+
L = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 1, 1]]
|
| 1032 |
+
U = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], [0, 0, 0, 1]]
|
| 1033 |
+
to_dom = lambda rows, dom: [[dom(e) for e in row] for row in rows]
|
| 1034 |
+
A = DomainMatrix(to_dom(A, QQ), (4, 4), QQ)
|
| 1035 |
+
L = DomainMatrix(to_dom(L, QQ), (4, 4), QQ)
|
| 1036 |
+
U = DomainMatrix(to_dom(U, QQ), (4, 4), QQ)
|
| 1037 |
+
assert A.lu() == (L, U, [])
|
| 1038 |
+
|
| 1039 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1040 |
+
raises(DMNotAField, lambda: A.lu())
|
| 1041 |
+
|
| 1042 |
+
|
| 1043 |
+
def test_DomainMatrix_lu_solve():
|
| 1044 |
+
# Base case
|
| 1045 |
+
A = b = x = DomainMatrix([], (0, 0), QQ)
|
| 1046 |
+
assert A.lu_solve(b) == x
|
| 1047 |
+
|
| 1048 |
+
# Basic example
|
| 1049 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 1050 |
+
b = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 1051 |
+
x = DomainMatrix([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
| 1052 |
+
assert A.lu_solve(b) == x
|
| 1053 |
+
|
| 1054 |
+
# Example with swaps
|
| 1055 |
+
A = DomainMatrix([[QQ(0), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 1056 |
+
b = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 1057 |
+
x = DomainMatrix([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
| 1058 |
+
assert A.lu_solve(b) == x
|
| 1059 |
+
|
| 1060 |
+
# Non-invertible
|
| 1061 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(2), QQ(4)]], (2, 2), QQ)
|
| 1062 |
+
b = DomainMatrix([[QQ(1)], [QQ(2)]], (2, 1), QQ)
|
| 1063 |
+
raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b))
|
| 1064 |
+
|
| 1065 |
+
# Overdetermined, consistent
|
| 1066 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ)
|
| 1067 |
+
b = DomainMatrix([[QQ(1)], [QQ(2)], [QQ(3)]], (3, 1), QQ)
|
| 1068 |
+
x = DomainMatrix([[QQ(0)], [QQ(1, 2)]], (2, 1), QQ)
|
| 1069 |
+
assert A.lu_solve(b) == x
|
| 1070 |
+
|
| 1071 |
+
# Overdetermined, inconsistent
|
| 1072 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)], [QQ(5), QQ(6)]], (3, 2), QQ)
|
| 1073 |
+
b = DomainMatrix([[QQ(1)], [QQ(2)], [QQ(4)]], (3, 1), QQ)
|
| 1074 |
+
raises(DMNonInvertibleMatrixError, lambda: A.lu_solve(b))
|
| 1075 |
+
|
| 1076 |
+
# Underdetermined
|
| 1077 |
+
A = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 1078 |
+
b = DomainMatrix([[QQ(1)]], (1, 1), QQ)
|
| 1079 |
+
raises(NotImplementedError, lambda: A.lu_solve(b))
|
| 1080 |
+
|
| 1081 |
+
# Non-field
|
| 1082 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1083 |
+
b = DomainMatrix([[ZZ(1)], [ZZ(2)]], (2, 1), ZZ)
|
| 1084 |
+
raises(DMNotAField, lambda: A.lu_solve(b))
|
| 1085 |
+
|
| 1086 |
+
# Shape mismatch
|
| 1087 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 1088 |
+
b = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 1089 |
+
raises(DMShapeError, lambda: A.lu_solve(b))
|
| 1090 |
+
|
| 1091 |
+
|
| 1092 |
+
def test_DomainMatrix_charpoly():
|
| 1093 |
+
A = DomainMatrix([], (0, 0), ZZ)
|
| 1094 |
+
p = [ZZ(1)]
|
| 1095 |
+
assert A.charpoly() == p
|
| 1096 |
+
assert A.to_sparse().charpoly() == p
|
| 1097 |
+
|
| 1098 |
+
A = DomainMatrix([[1]], (1, 1), ZZ)
|
| 1099 |
+
p = [ZZ(1), ZZ(-1)]
|
| 1100 |
+
assert A.charpoly() == p
|
| 1101 |
+
assert A.to_sparse().charpoly() == p
|
| 1102 |
+
|
| 1103 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1104 |
+
p = [ZZ(1), ZZ(-5), ZZ(-2)]
|
| 1105 |
+
assert A.charpoly() == p
|
| 1106 |
+
assert A.to_sparse().charpoly() == p
|
| 1107 |
+
|
| 1108 |
+
A = DomainMatrix([[ZZ(1), ZZ(2), ZZ(3)], [ZZ(4), ZZ(5), ZZ(6)], [ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
| 1109 |
+
p = [ZZ(1), ZZ(-15), ZZ(-18), ZZ(0)]
|
| 1110 |
+
assert A.charpoly() == p
|
| 1111 |
+
assert A.to_sparse().charpoly() == p
|
| 1112 |
+
|
| 1113 |
+
A = DomainMatrix([[ZZ(0), ZZ(1), ZZ(0)],
|
| 1114 |
+
[ZZ(1), ZZ(0), ZZ(1)],
|
| 1115 |
+
[ZZ(0), ZZ(1), ZZ(0)]], (3, 3), ZZ)
|
| 1116 |
+
p = [ZZ(1), ZZ(0), ZZ(-2), ZZ(0)]
|
| 1117 |
+
assert A.charpoly() == p
|
| 1118 |
+
assert A.to_sparse().charpoly() == p
|
| 1119 |
+
|
| 1120 |
+
A = DM([[17, 0, 30, 0, 0, 0, 0, 0, 0, 0],
|
| 1121 |
+
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 1122 |
+
[69, 0, 0, 0, 0, 86, 0, 0, 0, 0],
|
| 1123 |
+
[23, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 1124 |
+
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 1125 |
+
[ 0, 0, 0, 13, 0, 0, 0, 0, 0, 0],
|
| 1126 |
+
[ 0, 0, 0, 0, 0, 0, 0, 32, 0, 0],
|
| 1127 |
+
[ 0, 0, 0, 0, 37, 67, 0, 0, 0, 0],
|
| 1128 |
+
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 1129 |
+
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], ZZ)
|
| 1130 |
+
p = ZZ.map([1, -17, -2070, 0, -771420, 0, 0, 0, 0, 0, 0])
|
| 1131 |
+
assert A.charpoly() == p
|
| 1132 |
+
assert A.to_sparse().charpoly() == p
|
| 1133 |
+
|
| 1134 |
+
Ans = DomainMatrix([[QQ(1), QQ(2)]], (1, 2), QQ)
|
| 1135 |
+
raises(DMNonSquareMatrixError, lambda: Ans.charpoly())
|
| 1136 |
+
|
| 1137 |
+
|
| 1138 |
+
def test_DomainMatrix_charpoly_factor_list():
|
| 1139 |
+
A = DomainMatrix([], (0, 0), ZZ)
|
| 1140 |
+
assert A.charpoly_factor_list() == []
|
| 1141 |
+
|
| 1142 |
+
A = DM([[1]], ZZ)
|
| 1143 |
+
assert A.charpoly_factor_list() == [
|
| 1144 |
+
([ZZ(1), ZZ(-1)], 1)
|
| 1145 |
+
]
|
| 1146 |
+
|
| 1147 |
+
A = DM([[1, 2], [3, 4]], ZZ)
|
| 1148 |
+
assert A.charpoly_factor_list() == [
|
| 1149 |
+
([ZZ(1), ZZ(-5), ZZ(-2)], 1)
|
| 1150 |
+
]
|
| 1151 |
+
|
| 1152 |
+
A = DM([[1, 2, 0], [3, 4, 0], [0, 0, 1]], ZZ)
|
| 1153 |
+
assert A.charpoly_factor_list() == [
|
| 1154 |
+
([ZZ(1), ZZ(-1)], 1),
|
| 1155 |
+
([ZZ(1), ZZ(-5), ZZ(-2)], 1)
|
| 1156 |
+
]
|
| 1157 |
+
|
| 1158 |
+
|
| 1159 |
+
def test_DomainMatrix_eye():
|
| 1160 |
+
A = DomainMatrix.eye(3, QQ)
|
| 1161 |
+
assert A.rep == SDM.eye((3, 3), QQ)
|
| 1162 |
+
assert A.shape == (3, 3)
|
| 1163 |
+
assert A.domain == QQ
|
| 1164 |
+
|
| 1165 |
+
|
| 1166 |
+
def test_DomainMatrix_zeros():
|
| 1167 |
+
A = DomainMatrix.zeros((1, 2), QQ)
|
| 1168 |
+
assert A.rep == SDM.zeros((1, 2), QQ)
|
| 1169 |
+
assert A.shape == (1, 2)
|
| 1170 |
+
assert A.domain == QQ
|
| 1171 |
+
|
| 1172 |
+
|
| 1173 |
+
def test_DomainMatrix_ones():
|
| 1174 |
+
A = DomainMatrix.ones((2, 3), QQ)
|
| 1175 |
+
if GROUND_TYPES != 'flint':
|
| 1176 |
+
assert A.rep == DDM.ones((2, 3), QQ)
|
| 1177 |
+
else:
|
| 1178 |
+
assert A.rep == SDM.ones((2, 3), QQ).to_dfm()
|
| 1179 |
+
assert A.shape == (2, 3)
|
| 1180 |
+
assert A.domain == QQ
|
| 1181 |
+
|
| 1182 |
+
|
| 1183 |
+
def test_DomainMatrix_diag():
|
| 1184 |
+
A = DomainMatrix({0:{0:ZZ(2)}, 1:{1:ZZ(3)}}, (2, 2), ZZ)
|
| 1185 |
+
assert DomainMatrix.diag([ZZ(2), ZZ(3)], ZZ) == A
|
| 1186 |
+
|
| 1187 |
+
A = DomainMatrix({0:{0:ZZ(2)}, 1:{1:ZZ(3)}}, (3, 4), ZZ)
|
| 1188 |
+
assert DomainMatrix.diag([ZZ(2), ZZ(3)], ZZ, (3, 4)) == A
|
| 1189 |
+
|
| 1190 |
+
|
| 1191 |
+
def test_DomainMatrix_hstack():
|
| 1192 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1193 |
+
B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
|
| 1194 |
+
C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
|
| 1195 |
+
|
| 1196 |
+
AB = DomainMatrix([
|
| 1197 |
+
[ZZ(1), ZZ(2), ZZ(5), ZZ(6)],
|
| 1198 |
+
[ZZ(3), ZZ(4), ZZ(7), ZZ(8)]], (2, 4), ZZ)
|
| 1199 |
+
ABC = DomainMatrix([
|
| 1200 |
+
[ZZ(1), ZZ(2), ZZ(5), ZZ(6), ZZ(9), ZZ(10)],
|
| 1201 |
+
[ZZ(3), ZZ(4), ZZ(7), ZZ(8), ZZ(11), ZZ(12)]], (2, 6), ZZ)
|
| 1202 |
+
assert A.hstack(B) == AB
|
| 1203 |
+
assert A.hstack(B, C) == ABC
|
| 1204 |
+
|
| 1205 |
+
|
| 1206 |
+
def test_DomainMatrix_vstack():
|
| 1207 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1208 |
+
B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
|
| 1209 |
+
C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
|
| 1210 |
+
|
| 1211 |
+
AB = DomainMatrix([
|
| 1212 |
+
[ZZ(1), ZZ(2)],
|
| 1213 |
+
[ZZ(3), ZZ(4)],
|
| 1214 |
+
[ZZ(5), ZZ(6)],
|
| 1215 |
+
[ZZ(7), ZZ(8)]], (4, 2), ZZ)
|
| 1216 |
+
ABC = DomainMatrix([
|
| 1217 |
+
[ZZ(1), ZZ(2)],
|
| 1218 |
+
[ZZ(3), ZZ(4)],
|
| 1219 |
+
[ZZ(5), ZZ(6)],
|
| 1220 |
+
[ZZ(7), ZZ(8)],
|
| 1221 |
+
[ZZ(9), ZZ(10)],
|
| 1222 |
+
[ZZ(11), ZZ(12)]], (6, 2), ZZ)
|
| 1223 |
+
assert A.vstack(B) == AB
|
| 1224 |
+
assert A.vstack(B, C) == ABC
|
| 1225 |
+
|
| 1226 |
+
|
| 1227 |
+
def test_DomainMatrix_applyfunc():
|
| 1228 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
|
| 1229 |
+
B = DomainMatrix([[ZZ(2), ZZ(4)]], (1, 2), ZZ)
|
| 1230 |
+
assert A.applyfunc(lambda x: 2*x) == B
|
| 1231 |
+
|
| 1232 |
+
|
| 1233 |
+
def test_DomainMatrix_scalarmul():
|
| 1234 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1235 |
+
lamda = DomainScalar(QQ(3)/QQ(2), QQ)
|
| 1236 |
+
assert A * lamda == DomainMatrix([[QQ(3, 2), QQ(3)], [QQ(9, 2), QQ(6)]], (2, 2), QQ)
|
| 1237 |
+
assert A * 2 == DomainMatrix([[ZZ(2), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ)
|
| 1238 |
+
assert 2 * A == DomainMatrix([[ZZ(2), ZZ(4)], [ZZ(6), ZZ(8)]], (2, 2), ZZ)
|
| 1239 |
+
assert A * DomainScalar(ZZ(0), ZZ) == DomainMatrix({}, (2, 2), ZZ)
|
| 1240 |
+
assert A * DomainScalar(ZZ(1), ZZ) == A
|
| 1241 |
+
|
| 1242 |
+
raises(TypeError, lambda: A * 1.5)
|
| 1243 |
+
|
| 1244 |
+
|
| 1245 |
+
def test_DomainMatrix_truediv():
|
| 1246 |
+
A = DomainMatrix.from_Matrix(Matrix([[1, 2], [3, 4]]))
|
| 1247 |
+
lamda = DomainScalar(QQ(3)/QQ(2), QQ)
|
| 1248 |
+
assert A / lamda == DomainMatrix({0: {0: QQ(2, 3), 1: QQ(4, 3)}, 1: {0: QQ(2), 1: QQ(8, 3)}}, (2, 2), QQ)
|
| 1249 |
+
b = DomainScalar(ZZ(1), ZZ)
|
| 1250 |
+
assert A / b == DomainMatrix({0: {0: QQ(1), 1: QQ(2)}, 1: {0: QQ(3), 1: QQ(4)}}, (2, 2), QQ)
|
| 1251 |
+
|
| 1252 |
+
assert A / 1 == DomainMatrix({0: {0: QQ(1), 1: QQ(2)}, 1: {0: QQ(3), 1: QQ(4)}}, (2, 2), QQ)
|
| 1253 |
+
assert A / 2 == DomainMatrix({0: {0: QQ(1, 2), 1: QQ(1)}, 1: {0: QQ(3, 2), 1: QQ(2)}}, (2, 2), QQ)
|
| 1254 |
+
|
| 1255 |
+
raises(ZeroDivisionError, lambda: A / 0)
|
| 1256 |
+
raises(TypeError, lambda: A / 1.5)
|
| 1257 |
+
raises(ZeroDivisionError, lambda: A / DomainScalar(ZZ(0), ZZ))
|
| 1258 |
+
|
| 1259 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1260 |
+
assert A.to_field() / 2 == DomainMatrix([[QQ(1, 2), QQ(1)], [QQ(3, 2), QQ(2)]], (2, 2), QQ)
|
| 1261 |
+
assert A / 2 == DomainMatrix([[QQ(1, 2), QQ(1)], [QQ(3, 2), QQ(2)]], (2, 2), QQ)
|
| 1262 |
+
assert A.to_field() / QQ(2,3) == DomainMatrix([[QQ(3, 2), QQ(3)], [QQ(9, 2), QQ(6)]], (2, 2), QQ)
|
| 1263 |
+
|
| 1264 |
+
|
| 1265 |
+
def test_DomainMatrix_getitem():
|
| 1266 |
+
dM = DomainMatrix([
|
| 1267 |
+
[ZZ(1), ZZ(2), ZZ(3)],
|
| 1268 |
+
[ZZ(4), ZZ(5), ZZ(6)],
|
| 1269 |
+
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
| 1270 |
+
|
| 1271 |
+
assert dM[1:,:-2] == DomainMatrix([[ZZ(4)], [ZZ(7)]], (2, 1), ZZ)
|
| 1272 |
+
assert dM[2,:-2] == DomainMatrix([[ZZ(7)]], (1, 1), ZZ)
|
| 1273 |
+
assert dM[:-2,:-2] == DomainMatrix([[ZZ(1)]], (1, 1), ZZ)
|
| 1274 |
+
assert dM[:-1,0:2] == DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(4), ZZ(5)]], (2, 2), ZZ)
|
| 1275 |
+
assert dM[:, -1] == DomainMatrix([[ZZ(3)], [ZZ(6)], [ZZ(9)]], (3, 1), ZZ)
|
| 1276 |
+
assert dM[-1, :] == DomainMatrix([[ZZ(7), ZZ(8), ZZ(9)]], (1, 3), ZZ)
|
| 1277 |
+
assert dM[::-1, :] == DomainMatrix([
|
| 1278 |
+
[ZZ(7), ZZ(8), ZZ(9)],
|
| 1279 |
+
[ZZ(4), ZZ(5), ZZ(6)],
|
| 1280 |
+
[ZZ(1), ZZ(2), ZZ(3)]], (3, 3), ZZ)
|
| 1281 |
+
|
| 1282 |
+
raises(IndexError, lambda: dM[4, :-2])
|
| 1283 |
+
raises(IndexError, lambda: dM[:-2, 4])
|
| 1284 |
+
|
| 1285 |
+
assert dM[1, 2] == DomainScalar(ZZ(6), ZZ)
|
| 1286 |
+
assert dM[-2, 2] == DomainScalar(ZZ(6), ZZ)
|
| 1287 |
+
assert dM[1, -2] == DomainScalar(ZZ(5), ZZ)
|
| 1288 |
+
assert dM[-1, -3] == DomainScalar(ZZ(7), ZZ)
|
| 1289 |
+
|
| 1290 |
+
raises(IndexError, lambda: dM[3, 3])
|
| 1291 |
+
raises(IndexError, lambda: dM[1, 4])
|
| 1292 |
+
raises(IndexError, lambda: dM[-1, -4])
|
| 1293 |
+
|
| 1294 |
+
dM = DomainMatrix({0: {0: ZZ(1)}}, (10, 10), ZZ)
|
| 1295 |
+
assert dM[5, 5] == DomainScalar(ZZ(0), ZZ)
|
| 1296 |
+
assert dM[0, 0] == DomainScalar(ZZ(1), ZZ)
|
| 1297 |
+
|
| 1298 |
+
dM = DomainMatrix({1: {0: 1}}, (2,1), ZZ)
|
| 1299 |
+
assert dM[0:, 0] == DomainMatrix({1: {0: 1}}, (2, 1), ZZ)
|
| 1300 |
+
raises(IndexError, lambda: dM[3, 0])
|
| 1301 |
+
|
| 1302 |
+
dM = DomainMatrix({2: {2: ZZ(1)}, 4: {4: ZZ(1)}}, (5, 5), ZZ)
|
| 1303 |
+
assert dM[:2,:2] == DomainMatrix({}, (2, 2), ZZ)
|
| 1304 |
+
assert dM[2:,2:] == DomainMatrix({0: {0: 1}, 2: {2: 1}}, (3, 3), ZZ)
|
| 1305 |
+
assert dM[3:,3:] == DomainMatrix({1: {1: 1}}, (2, 2), ZZ)
|
| 1306 |
+
assert dM[2:, 6:] == DomainMatrix({}, (3, 0), ZZ)
|
| 1307 |
+
|
| 1308 |
+
|
| 1309 |
+
def test_DomainMatrix_getitem_sympy():
|
| 1310 |
+
dM = DomainMatrix({2: {2: ZZ(2)}, 4: {4: ZZ(1)}}, (5, 5), ZZ)
|
| 1311 |
+
val1 = dM.getitem_sympy(0, 0)
|
| 1312 |
+
assert val1 is S.Zero
|
| 1313 |
+
val2 = dM.getitem_sympy(2, 2)
|
| 1314 |
+
assert val2 == 2 and isinstance(val2, Integer)
|
| 1315 |
+
|
| 1316 |
+
|
| 1317 |
+
def test_DomainMatrix_extract():
|
| 1318 |
+
dM1 = DomainMatrix([
|
| 1319 |
+
[ZZ(1), ZZ(2), ZZ(3)],
|
| 1320 |
+
[ZZ(4), ZZ(5), ZZ(6)],
|
| 1321 |
+
[ZZ(7), ZZ(8), ZZ(9)]], (3, 3), ZZ)
|
| 1322 |
+
dM2 = DomainMatrix([
|
| 1323 |
+
[ZZ(1), ZZ(3)],
|
| 1324 |
+
[ZZ(7), ZZ(9)]], (2, 2), ZZ)
|
| 1325 |
+
assert dM1.extract([0, 2], [0, 2]) == dM2
|
| 1326 |
+
assert dM1.to_sparse().extract([0, 2], [0, 2]) == dM2.to_sparse()
|
| 1327 |
+
assert dM1.extract([0, -1], [0, -1]) == dM2
|
| 1328 |
+
assert dM1.to_sparse().extract([0, -1], [0, -1]) == dM2.to_sparse()
|
| 1329 |
+
|
| 1330 |
+
dM3 = DomainMatrix([
|
| 1331 |
+
[ZZ(1), ZZ(2), ZZ(2)],
|
| 1332 |
+
[ZZ(4), ZZ(5), ZZ(5)],
|
| 1333 |
+
[ZZ(4), ZZ(5), ZZ(5)]], (3, 3), ZZ)
|
| 1334 |
+
assert dM1.extract([0, 1, 1], [0, 1, 1]) == dM3
|
| 1335 |
+
assert dM1.to_sparse().extract([0, 1, 1], [0, 1, 1]) == dM3.to_sparse()
|
| 1336 |
+
|
| 1337 |
+
empty = [
|
| 1338 |
+
([], [], (0, 0)),
|
| 1339 |
+
([1], [], (1, 0)),
|
| 1340 |
+
([], [1], (0, 1)),
|
| 1341 |
+
]
|
| 1342 |
+
for rows, cols, size in empty:
|
| 1343 |
+
assert dM1.extract(rows, cols) == DomainMatrix.zeros(size, ZZ).to_dense()
|
| 1344 |
+
assert dM1.to_sparse().extract(rows, cols) == DomainMatrix.zeros(size, ZZ)
|
| 1345 |
+
|
| 1346 |
+
dM = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1347 |
+
bad_indices = [([2], [0]), ([0], [2]), ([-3], [0]), ([0], [-3])]
|
| 1348 |
+
for rows, cols in bad_indices:
|
| 1349 |
+
raises(IndexError, lambda: dM.extract(rows, cols))
|
| 1350 |
+
raises(IndexError, lambda: dM.to_sparse().extract(rows, cols))
|
| 1351 |
+
|
| 1352 |
+
|
| 1353 |
+
def test_DomainMatrix_setitem():
|
| 1354 |
+
dM = DomainMatrix({2: {2: ZZ(1)}, 4: {4: ZZ(1)}}, (5, 5), ZZ)
|
| 1355 |
+
dM[2, 2] = ZZ(2)
|
| 1356 |
+
assert dM == DomainMatrix({2: {2: ZZ(2)}, 4: {4: ZZ(1)}}, (5, 5), ZZ)
|
| 1357 |
+
def setitem(i, j, val):
|
| 1358 |
+
dM[i, j] = val
|
| 1359 |
+
raises(TypeError, lambda: setitem(2, 2, QQ(1, 2)))
|
| 1360 |
+
raises(NotImplementedError, lambda: setitem(slice(1, 2), 2, ZZ(1)))
|
| 1361 |
+
|
| 1362 |
+
|
| 1363 |
+
def test_DomainMatrix_pickling():
|
| 1364 |
+
import pickle
|
| 1365 |
+
dM = DomainMatrix({2: {2: ZZ(1)}, 4: {4: ZZ(1)}}, (5, 5), ZZ)
|
| 1366 |
+
assert pickle.loads(pickle.dumps(dM)) == dM
|
| 1367 |
+
dM = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 1368 |
+
assert pickle.loads(pickle.dumps(dM)) == dM
|
| 1369 |
+
|
| 1370 |
+
|
| 1371 |
+
def test_DomainMatrix_fflu():
|
| 1372 |
+
A = DM([[1, 2], [3, 4]], ZZ)
|
| 1373 |
+
P, L, D, U = A.fflu()
|
| 1374 |
+
assert P.shape == A.shape
|
| 1375 |
+
assert L.shape == A.shape
|
| 1376 |
+
assert D.shape == A.shape
|
| 1377 |
+
assert U.shape == A.shape
|
| 1378 |
+
assert P == DM([[1, 0], [0, 1]], ZZ)
|
| 1379 |
+
assert L == DM([[1, 0], [3, -2]], ZZ)
|
| 1380 |
+
assert D == DM([[1, 0], [0, -2]], ZZ)
|
| 1381 |
+
assert U == DM([[1, 2], [0, -2]], ZZ)
|
| 1382 |
+
di, d = D.inv_den()
|
| 1383 |
+
assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U)
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_domainscalar.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.testing.pytest import raises
|
| 2 |
+
|
| 3 |
+
from sympy.core.symbol import S
|
| 4 |
+
from sympy.polys import ZZ, QQ
|
| 5 |
+
from sympy.polys.matrices.domainscalar import DomainScalar
|
| 6 |
+
from sympy.polys.matrices.domainmatrix import DomainMatrix
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_DomainScalar___new__():
|
| 10 |
+
raises(TypeError, lambda: DomainScalar(ZZ(1), QQ))
|
| 11 |
+
raises(TypeError, lambda: DomainScalar(ZZ(1), 1))
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_DomainScalar_new():
|
| 15 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 16 |
+
B = A.new(ZZ(4), ZZ)
|
| 17 |
+
assert B == DomainScalar(ZZ(4), ZZ)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_DomainScalar_repr():
|
| 21 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 22 |
+
assert repr(A) in {'1', 'mpz(1)'}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_DomainScalar_from_sympy():
|
| 26 |
+
expr = S(1)
|
| 27 |
+
B = DomainScalar.from_sympy(expr)
|
| 28 |
+
assert B == DomainScalar(ZZ(1), ZZ)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_DomainScalar_to_sympy():
|
| 32 |
+
B = DomainScalar(ZZ(1), ZZ)
|
| 33 |
+
expr = B.to_sympy()
|
| 34 |
+
assert expr.is_Integer and expr == 1
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def test_DomainScalar_to_domain():
|
| 38 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 39 |
+
B = A.to_domain(QQ)
|
| 40 |
+
assert B == DomainScalar(QQ(1), QQ)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def test_DomainScalar_convert_to():
|
| 44 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 45 |
+
B = A.convert_to(QQ)
|
| 46 |
+
assert B == DomainScalar(QQ(1), QQ)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def test_DomainScalar_unify():
|
| 50 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 51 |
+
B = DomainScalar(QQ(2), QQ)
|
| 52 |
+
A, B = A.unify(B)
|
| 53 |
+
assert A.domain == B.domain == QQ
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def test_DomainScalar_add():
|
| 57 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 58 |
+
B = DomainScalar(QQ(2), QQ)
|
| 59 |
+
assert A + B == DomainScalar(QQ(3), QQ)
|
| 60 |
+
|
| 61 |
+
raises(TypeError, lambda: A + 1.5)
|
| 62 |
+
|
| 63 |
+
def test_DomainScalar_sub():
|
| 64 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 65 |
+
B = DomainScalar(QQ(2), QQ)
|
| 66 |
+
assert A - B == DomainScalar(QQ(-1), QQ)
|
| 67 |
+
|
| 68 |
+
raises(TypeError, lambda: A - 1.5)
|
| 69 |
+
|
| 70 |
+
def test_DomainScalar_mul():
|
| 71 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 72 |
+
B = DomainScalar(QQ(2), QQ)
|
| 73 |
+
dm = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 74 |
+
assert A * B == DomainScalar(QQ(2), QQ)
|
| 75 |
+
assert A * dm == dm
|
| 76 |
+
assert B * 2 == DomainScalar(QQ(4), QQ)
|
| 77 |
+
|
| 78 |
+
raises(TypeError, lambda: A * 1.5)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def test_DomainScalar_floordiv():
|
| 82 |
+
A = DomainScalar(ZZ(-5), ZZ)
|
| 83 |
+
B = DomainScalar(QQ(2), QQ)
|
| 84 |
+
assert A // B == DomainScalar(QQ(-5, 2), QQ)
|
| 85 |
+
C = DomainScalar(ZZ(2), ZZ)
|
| 86 |
+
assert A // C == DomainScalar(ZZ(-3), ZZ)
|
| 87 |
+
|
| 88 |
+
raises(TypeError, lambda: A // 1.5)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def test_DomainScalar_mod():
|
| 92 |
+
A = DomainScalar(ZZ(5), ZZ)
|
| 93 |
+
B = DomainScalar(QQ(2), QQ)
|
| 94 |
+
assert A % B == DomainScalar(QQ(0), QQ)
|
| 95 |
+
C = DomainScalar(ZZ(2), ZZ)
|
| 96 |
+
assert A % C == DomainScalar(ZZ(1), ZZ)
|
| 97 |
+
|
| 98 |
+
raises(TypeError, lambda: A % 1.5)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def test_DomainScalar_divmod():
|
| 102 |
+
A = DomainScalar(ZZ(5), ZZ)
|
| 103 |
+
B = DomainScalar(QQ(2), QQ)
|
| 104 |
+
assert divmod(A, B) == (DomainScalar(QQ(5, 2), QQ), DomainScalar(QQ(0), QQ))
|
| 105 |
+
C = DomainScalar(ZZ(2), ZZ)
|
| 106 |
+
assert divmod(A, C) == (DomainScalar(ZZ(2), ZZ), DomainScalar(ZZ(1), ZZ))
|
| 107 |
+
|
| 108 |
+
raises(TypeError, lambda: divmod(A, 1.5))
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def test_DomainScalar_pow():
|
| 112 |
+
A = DomainScalar(ZZ(-5), ZZ)
|
| 113 |
+
B = A**(2)
|
| 114 |
+
assert B == DomainScalar(ZZ(25), ZZ)
|
| 115 |
+
|
| 116 |
+
raises(TypeError, lambda: A**(1.5))
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def test_DomainScalar_pos():
|
| 120 |
+
A = DomainScalar(QQ(2), QQ)
|
| 121 |
+
B = DomainScalar(QQ(2), QQ)
|
| 122 |
+
assert +A == B
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def test_DomainScalar_neg():
|
| 126 |
+
A = DomainScalar(QQ(2), QQ)
|
| 127 |
+
B = DomainScalar(QQ(-2), QQ)
|
| 128 |
+
assert -A == B
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def test_DomainScalar_eq():
|
| 132 |
+
A = DomainScalar(QQ(2), QQ)
|
| 133 |
+
assert A == A
|
| 134 |
+
B = DomainScalar(ZZ(-5), ZZ)
|
| 135 |
+
assert A != B
|
| 136 |
+
C = DomainScalar(ZZ(2), ZZ)
|
| 137 |
+
assert A != C
|
| 138 |
+
D = [1]
|
| 139 |
+
assert A != D
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def test_DomainScalar_isZero():
|
| 143 |
+
A = DomainScalar(ZZ(0), ZZ)
|
| 144 |
+
assert A.is_zero() == True
|
| 145 |
+
B = DomainScalar(ZZ(1), ZZ)
|
| 146 |
+
assert B.is_zero() == False
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def test_DomainScalar_isOne():
|
| 150 |
+
A = DomainScalar(ZZ(1), ZZ)
|
| 151 |
+
assert A.is_one() == True
|
| 152 |
+
B = DomainScalar(ZZ(0), ZZ)
|
| 153 |
+
assert B.is_one() == False
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_eigen.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Tests for the sympy.polys.matrices.eigen module
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from sympy.core.singleton import S
|
| 6 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 7 |
+
from sympy.matrices.dense import Matrix
|
| 8 |
+
|
| 9 |
+
from sympy.polys.agca.extensions import FiniteExtension
|
| 10 |
+
from sympy.polys.domains import QQ
|
| 11 |
+
from sympy.polys.polytools import Poly
|
| 12 |
+
from sympy.polys.rootoftools import CRootOf
|
| 13 |
+
from sympy.polys.matrices.domainmatrix import DomainMatrix
|
| 14 |
+
|
| 15 |
+
from sympy.polys.matrices.eigen import dom_eigenvects, dom_eigenvects_to_sympy
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_dom_eigenvects_rational():
|
| 19 |
+
# Rational eigenvalues
|
| 20 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(1), QQ(2)]], (2, 2), QQ)
|
| 21 |
+
rational_eigenvects = [
|
| 22 |
+
(QQ, QQ(3), 1, DomainMatrix([[QQ(1), QQ(1)]], (1, 2), QQ)),
|
| 23 |
+
(QQ, QQ(0), 1, DomainMatrix([[QQ(-2), QQ(1)]], (1, 2), QQ)),
|
| 24 |
+
]
|
| 25 |
+
assert dom_eigenvects(A) == (rational_eigenvects, [])
|
| 26 |
+
|
| 27 |
+
# Test converting to Expr:
|
| 28 |
+
sympy_eigenvects = [
|
| 29 |
+
(S(3), 1, [Matrix([1, 1])]),
|
| 30 |
+
(S(0), 1, [Matrix([-2, 1])]),
|
| 31 |
+
]
|
| 32 |
+
assert dom_eigenvects_to_sympy(rational_eigenvects, [], Matrix) == sympy_eigenvects
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def test_dom_eigenvects_algebraic():
|
| 36 |
+
# Algebraic eigenvalues
|
| 37 |
+
A = DomainMatrix([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], (2, 2), QQ)
|
| 38 |
+
Avects = dom_eigenvects(A)
|
| 39 |
+
|
| 40 |
+
# Extract the dummy to build the expected result:
|
| 41 |
+
lamda = Avects[1][0][1].gens[0]
|
| 42 |
+
irreducible = Poly(lamda**2 - 5*lamda - 2, lamda, domain=QQ)
|
| 43 |
+
K = FiniteExtension(irreducible)
|
| 44 |
+
KK = K.from_sympy
|
| 45 |
+
algebraic_eigenvects = [
|
| 46 |
+
(K, irreducible, 1, DomainMatrix([[KK((lamda-4)/3), KK(1)]], (1, 2), K)),
|
| 47 |
+
]
|
| 48 |
+
assert Avects == ([], algebraic_eigenvects)
|
| 49 |
+
|
| 50 |
+
# Test converting to Expr:
|
| 51 |
+
sympy_eigenvects = [
|
| 52 |
+
(S(5)/2 - sqrt(33)/2, 1, [Matrix([[-sqrt(33)/6 - S(1)/2], [1]])]),
|
| 53 |
+
(S(5)/2 + sqrt(33)/2, 1, [Matrix([[-S(1)/2 + sqrt(33)/6], [1]])]),
|
| 54 |
+
]
|
| 55 |
+
assert dom_eigenvects_to_sympy([], algebraic_eigenvects, Matrix) == sympy_eigenvects
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def test_dom_eigenvects_rootof():
|
| 59 |
+
# Algebraic eigenvalues
|
| 60 |
+
A = DomainMatrix([
|
| 61 |
+
[0, 0, 0, 0, -1],
|
| 62 |
+
[1, 0, 0, 0, 1],
|
| 63 |
+
[0, 1, 0, 0, 0],
|
| 64 |
+
[0, 0, 1, 0, 0],
|
| 65 |
+
[0, 0, 0, 1, 0]], (5, 5), QQ)
|
| 66 |
+
Avects = dom_eigenvects(A)
|
| 67 |
+
|
| 68 |
+
# Extract the dummy to build the expected result:
|
| 69 |
+
lamda = Avects[1][0][1].gens[0]
|
| 70 |
+
irreducible = Poly(lamda**5 - lamda + 1, lamda, domain=QQ)
|
| 71 |
+
K = FiniteExtension(irreducible)
|
| 72 |
+
KK = K.from_sympy
|
| 73 |
+
algebraic_eigenvects = [
|
| 74 |
+
(K, irreducible, 1,
|
| 75 |
+
DomainMatrix([
|
| 76 |
+
[KK(lamda**4-1), KK(lamda**3), KK(lamda**2), KK(lamda), KK(1)]
|
| 77 |
+
], (1, 5), K)),
|
| 78 |
+
]
|
| 79 |
+
assert Avects == ([], algebraic_eigenvects)
|
| 80 |
+
|
| 81 |
+
# Test converting to Expr (slow):
|
| 82 |
+
l0, l1, l2, l3, l4 = [CRootOf(lamda**5 - lamda + 1, i) for i in range(5)]
|
| 83 |
+
sympy_eigenvects = [
|
| 84 |
+
(l0, 1, [Matrix([-1 + l0**4, l0**3, l0**2, l0, 1])]),
|
| 85 |
+
(l1, 1, [Matrix([-1 + l1**4, l1**3, l1**2, l1, 1])]),
|
| 86 |
+
(l2, 1, [Matrix([-1 + l2**4, l2**3, l2**2, l2, 1])]),
|
| 87 |
+
(l3, 1, [Matrix([-1 + l3**4, l3**3, l3**2, l3, 1])]),
|
| 88 |
+
(l4, 1, [Matrix([-1 + l4**4, l4**3, l4**2, l4, 1])]),
|
| 89 |
+
]
|
| 90 |
+
assert dom_eigenvects_to_sympy([], algebraic_eigenvects, Matrix) == sympy_eigenvects
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_fflu.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.polys.matrices import DomainMatrix, DM
|
| 2 |
+
from sympy.polys.domains import ZZ, QQ
|
| 3 |
+
from sympy import Matrix
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
FFLU_EXAMPLES = [
|
| 8 |
+
(
|
| 9 |
+
'zz_2x3',
|
| 10 |
+
DM([[1, 2, 3], [4, 5, 6]], ZZ),
|
| 11 |
+
DM([[1, 0], [0, 1]], ZZ),
|
| 12 |
+
DM([[1, 0], [4, -3]], ZZ),
|
| 13 |
+
DM([[1, 0], [0, -3]], ZZ),
|
| 14 |
+
DM([[1, 2, 3], [0, -3, -6]], ZZ),
|
| 15 |
+
),
|
| 16 |
+
|
| 17 |
+
(
|
| 18 |
+
'zz_2x2',
|
| 19 |
+
DM([[4, 3], [6, 3]], ZZ),
|
| 20 |
+
DM([[1, 0], [0, 1]], ZZ),
|
| 21 |
+
DM([[1, 0], [6, -6]], ZZ),
|
| 22 |
+
DM([[4, 0], [0, -3]], ZZ),
|
| 23 |
+
DM([[4, 3], [0, -3]], ZZ),
|
| 24 |
+
),
|
| 25 |
+
|
| 26 |
+
(
|
| 27 |
+
'zz_3x2',
|
| 28 |
+
DM([[1, 2], [3, 4], [5, 6]], ZZ),
|
| 29 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 30 |
+
DM([[1, 0, 0], [3, 1, 0], [5, 2, 1]], ZZ),
|
| 31 |
+
DM([[1, 0], [0, -2]], ZZ),
|
| 32 |
+
DM([[1, 2], [0, -2], [0, 0]], ZZ),
|
| 33 |
+
),
|
| 34 |
+
|
| 35 |
+
(
|
| 36 |
+
'zz_3x3',
|
| 37 |
+
DM([[1, 2, 3], [4, 5, 6], [7, 8, 9]], ZZ),
|
| 38 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 39 |
+
DM([[1, 0, 0], [4, 1, 0], [7, 2, 1]], ZZ),
|
| 40 |
+
DM([[1, 0, 0], [0, -3, 0], [0, 0, 0]], ZZ),
|
| 41 |
+
DM([[1, 2, 3], [0, -3, -6], [0, 0, 0]], ZZ),
|
| 42 |
+
),
|
| 43 |
+
|
| 44 |
+
(
|
| 45 |
+
'zz_zero',
|
| 46 |
+
DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ),
|
| 47 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 48 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 49 |
+
DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ),
|
| 50 |
+
DM([[0, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ),
|
| 51 |
+
),
|
| 52 |
+
|
| 53 |
+
(
|
| 54 |
+
'zz_empty',
|
| 55 |
+
DM([], ZZ),
|
| 56 |
+
DM([], ZZ),
|
| 57 |
+
DM([], ZZ),
|
| 58 |
+
DM([], ZZ),
|
| 59 |
+
DM([], ZZ),
|
| 60 |
+
),
|
| 61 |
+
|
| 62 |
+
(
|
| 63 |
+
'zz_empty_0x2',
|
| 64 |
+
DomainMatrix([], (0, 2), ZZ),
|
| 65 |
+
DomainMatrix([], (0, 0), ZZ),
|
| 66 |
+
DomainMatrix([], (0, 0), ZZ),
|
| 67 |
+
DomainMatrix([], (0, 0), ZZ),
|
| 68 |
+
DomainMatrix([], (0, 2), ZZ)
|
| 69 |
+
),
|
| 70 |
+
|
| 71 |
+
(
|
| 72 |
+
|
| 73 |
+
'zz_empty_2x0',
|
| 74 |
+
DomainMatrix([[], []], (2, 0), ZZ),
|
| 75 |
+
DomainMatrix.eye((2, 2), ZZ),
|
| 76 |
+
DomainMatrix.eye((2, 2), ZZ),
|
| 77 |
+
DomainMatrix.eye((2, 2), ZZ),
|
| 78 |
+
DomainMatrix([[], []], (2, 0), ZZ)
|
| 79 |
+
|
| 80 |
+
),
|
| 81 |
+
|
| 82 |
+
(
|
| 83 |
+
'zz_negative',
|
| 84 |
+
DM([[-1, -2], [-3, -4]], ZZ),
|
| 85 |
+
DM([[1, 0], [0, 1]], ZZ),
|
| 86 |
+
DM([[-1, 0], [-3, -2]], ZZ),
|
| 87 |
+
DM([[-1, 0], [0, 2]], ZZ),
|
| 88 |
+
DM([[-1, -2], [0, -2]], ZZ),
|
| 89 |
+
),
|
| 90 |
+
|
| 91 |
+
(
|
| 92 |
+
'zz_mixed_signs',
|
| 93 |
+
DM([[1, -2], [-3, 4]], ZZ),
|
| 94 |
+
DM([[1, 0], [0, 1]], ZZ),
|
| 95 |
+
DM([[1, 0], [-3, 1]], ZZ),
|
| 96 |
+
DM([[1, 0], [0, -2]], ZZ),
|
| 97 |
+
DM([[1, -2], [0, -2]], ZZ),
|
| 98 |
+
),
|
| 99 |
+
|
| 100 |
+
(
|
| 101 |
+
'zz_upper_triangular',
|
| 102 |
+
DM([[1, 2, 3], [0, 4, 5], [0, 0, 6]], ZZ),
|
| 103 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 104 |
+
DM([[1, 0, 0], [0, 4, 0], [0, 0, 24]], ZZ),
|
| 105 |
+
DM([[1, 0, 0], [0, 4, 0], [0, 0, 96]], ZZ),
|
| 106 |
+
DM([[1, 2, 3], [0, 4, 5], [0, 0, 24]], ZZ),
|
| 107 |
+
),
|
| 108 |
+
|
| 109 |
+
(
|
| 110 |
+
'zz_lower_triangular',
|
| 111 |
+
DM([[1, 0, 0], [2, 3, 0], [4, 5, 6]], ZZ),
|
| 112 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 113 |
+
DM([[1, 0, 0], [2, 3, 0], [4, 5, 18]], ZZ),
|
| 114 |
+
DM([[1, 0, 0], [0, 3, 0], [0, 0, 54]], ZZ),
|
| 115 |
+
DM([[1, 0, 0], [0, 3, 0], [0, 0, 18]], ZZ),
|
| 116 |
+
),
|
| 117 |
+
|
| 118 |
+
(
|
| 119 |
+
'zz_diagonal',
|
| 120 |
+
DM([[2, 0, 0], [0, 3, 0], [0, 0, 4]], ZZ),
|
| 121 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 122 |
+
DM([[2, 0, 0], [0, 6, 0], [0, 0, 24]], ZZ),
|
| 123 |
+
DM([[2, 0, 0], [0, 12, 0], [0, 0, 144]], ZZ),
|
| 124 |
+
DM([[2, 0, 0], [0, 6, 0], [0, 0, 24]], ZZ)
|
| 125 |
+
|
| 126 |
+
),
|
| 127 |
+
|
| 128 |
+
(
|
| 129 |
+
'rank_deficient_3x3',
|
| 130 |
+
DM([[1, 2, 3], [2, 4, 6], [3, 6, 9]], ZZ),
|
| 131 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 132 |
+
DM([[1, 0, 0], [2, 1, 0], [3, 0, 1]], ZZ),
|
| 133 |
+
DM([[1, 0, 0], [0, 0, 0], [0, 0, 0]], ZZ),
|
| 134 |
+
DM([[1, 2, 3], [0, 0, 0], [0, 0, 0]], ZZ),
|
| 135 |
+
),
|
| 136 |
+
|
| 137 |
+
(
|
| 138 |
+
'zz_1x1',
|
| 139 |
+
DM([[5]], ZZ),
|
| 140 |
+
DM([[1]], ZZ),
|
| 141 |
+
DM([[5]], ZZ),
|
| 142 |
+
DM([[5]], ZZ),
|
| 143 |
+
DM([[5]], ZZ),
|
| 144 |
+
),
|
| 145 |
+
|
| 146 |
+
(
|
| 147 |
+
'zz_nx1_2rows',
|
| 148 |
+
DM([[81], [54]], ZZ),
|
| 149 |
+
DM([[1, 0], [0, 1]], ZZ),
|
| 150 |
+
DM([[81, 0], [54, 81]], ZZ),
|
| 151 |
+
DM([[81, 0], [0, 81]], ZZ),
|
| 152 |
+
DM([[81], [0]], ZZ),
|
| 153 |
+
),
|
| 154 |
+
|
| 155 |
+
(
|
| 156 |
+
'zz_nx2_3rows',
|
| 157 |
+
DM([[2, 7], [7, 45], [25, 84]], ZZ),
|
| 158 |
+
DM([[1, 0, 0], [0, 1, 0], [0, 0, 1]], ZZ),
|
| 159 |
+
DM([[2, 0, 0], [7, 82, 0], [25, 41, 41]], ZZ),
|
| 160 |
+
DM([[2, 0, 0], [0, 82, 0], [0, 0, 41]], ZZ),
|
| 161 |
+
DM([[2, 7], [0, 82], [0, 0]], ZZ),
|
| 162 |
+
),
|
| 163 |
+
|
| 164 |
+
(
|
| 165 |
+
|
| 166 |
+
'zz_1x2',
|
| 167 |
+
DM([[0, 28]], ZZ),
|
| 168 |
+
DM([[1]], ZZ),
|
| 169 |
+
DM([[28]], ZZ),
|
| 170 |
+
DM([[28]], ZZ),
|
| 171 |
+
DM([[0, 28]], ZZ)
|
| 172 |
+
),
|
| 173 |
+
|
| 174 |
+
(
|
| 175 |
+
'zz_nx3_4rows',
|
| 176 |
+
DM([[84, 30, 9], [20, 59, 13], [53, 46, 81], [63, 48, 29]], ZZ),
|
| 177 |
+
DM([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], ZZ),
|
| 178 |
+
DM([[84, 0, 0, 0], [20, 365904, 0, 0], [53, 303411, 303411, 0], [63, 303411, 303411, 303411]], ZZ),
|
| 179 |
+
DM([[84, 0, 0, 0], [0, 365904, 0, 0], [0, 0, 1321658316, 0], [0, 0, 0, 303411]], ZZ),
|
| 180 |
+
DM([[84, 30, 9], [0, 365904, 13], [0, 0, 1321658316], [0, 0, 0]], ZZ),
|
| 181 |
+
),
|
| 182 |
+
|
| 183 |
+
(
|
| 184 |
+
'fflu_row_swap',
|
| 185 |
+
DM([[0, 1, 2], [3, 4, 5], [6, 7, 8]], ZZ),
|
| 186 |
+
DM([[0, 1, 0], [1, 0, 0], [0, 0, 1]], ZZ),
|
| 187 |
+
DM([[3, 0, 0], [0, 3, 0], [6, -3, 1]], ZZ),
|
| 188 |
+
DM([[3, 0, 0], [0, 9, 0], [0, 0, 3]], ZZ),
|
| 189 |
+
DM([[3, 4, 5], [0, 3, 6], [0, 0, 0]], ZZ)
|
| 190 |
+
),
|
| 191 |
+
]
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def _check_fflu(A, P, L, D, U):
|
| 195 |
+
P_field = P.to_field().to_dense()
|
| 196 |
+
L_field = L.to_field().to_dense()
|
| 197 |
+
D_field = D.to_field().to_dense()
|
| 198 |
+
U_field = U.to_field().to_dense()
|
| 199 |
+
m, n = A.shape
|
| 200 |
+
assert P_field.shape == (m, m)
|
| 201 |
+
assert L_field.shape == (m, m)
|
| 202 |
+
assert D_field.shape == (m, m)
|
| 203 |
+
assert U_field.shape == (m, n)
|
| 204 |
+
assert L_field.is_lower
|
| 205 |
+
assert D_field.is_diagonal
|
| 206 |
+
di, d = D.inv_den()
|
| 207 |
+
assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U)
|
| 208 |
+
assert U_field.is_upper
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _to_DM(A, ans):
|
| 212 |
+
if isinstance(A, DomainMatrix):
|
| 213 |
+
return A
|
| 214 |
+
elif isinstance(A, Matrix):
|
| 215 |
+
return A.to_DM(ans.domain)
|
| 216 |
+
return DomainMatrix(A.to_list(), A.shape, A.domain)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def _check_fflu_result(result, A, P_ans, L_ans, D_ans, U_ans):
|
| 220 |
+
P, L, D, U = result
|
| 221 |
+
P = _to_DM(P, P_ans)
|
| 222 |
+
L = _to_DM(L, L_ans)
|
| 223 |
+
D = _to_DM(D, D_ans)
|
| 224 |
+
U = _to_DM(U, U_ans)
|
| 225 |
+
A = _to_DM(A, P_ans)
|
| 226 |
+
m, n = A.shape
|
| 227 |
+
assert P.shape == (m, m)
|
| 228 |
+
assert L.shape == (m, m)
|
| 229 |
+
assert D.shape == (m, m)
|
| 230 |
+
assert U.shape == (m, n)
|
| 231 |
+
assert L.is_lower
|
| 232 |
+
assert D.is_diagonal
|
| 233 |
+
di, d = D.inv_den()
|
| 234 |
+
assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U)
|
| 235 |
+
assert U.is_upper
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
| 239 |
+
def test_dm_dense_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
| 240 |
+
A = A.to_dense()
|
| 241 |
+
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
| 245 |
+
def test_dm_sparse_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
| 246 |
+
A = A.to_sparse()
|
| 247 |
+
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
| 251 |
+
def test_ddm_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
| 252 |
+
A = A.to_ddm()
|
| 253 |
+
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
| 257 |
+
def test_sdm_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
| 258 |
+
A = A.to_sdm()
|
| 259 |
+
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
@pytest.mark.parametrize('name, A, P_ans, L_ans, D_ans, U_ans', FFLU_EXAMPLES)
|
| 263 |
+
def test_dfm_fflu(name, A, P_ans, L_ans, D_ans, U_ans):
|
| 264 |
+
pytest.importorskip('flint')
|
| 265 |
+
if A.domain not in (ZZ, QQ) and not A.domain.is_FF:
|
| 266 |
+
pytest.skip("Domain not supported by DFM")
|
| 267 |
+
A = A.to_dfm()
|
| 268 |
+
_check_fflu_result(A.fflu(), A, P_ans, L_ans, D_ans, U_ans)
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def test_fflu_empty_matrix():
|
| 272 |
+
A = DomainMatrix([], (0, 0), ZZ)
|
| 273 |
+
P, L, D, U = A.fflu()
|
| 274 |
+
assert P.shape == (0, 0)
|
| 275 |
+
assert L.shape == (0, 0)
|
| 276 |
+
assert D.shape == (0, 0)
|
| 277 |
+
assert U.shape == (0, 0)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def test_fflu_properties():
|
| 281 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
|
| 282 |
+
P, L, D, U = A.fflu()
|
| 283 |
+
assert P.shape == (2, 2)
|
| 284 |
+
assert L.shape == (2, 2)
|
| 285 |
+
assert D.shape == (2, 2)
|
| 286 |
+
assert U.shape == (2, 2)
|
| 287 |
+
assert L.is_lower
|
| 288 |
+
assert U.is_upper
|
| 289 |
+
assert D.is_diagonal
|
| 290 |
+
di, d = D.inv_den()
|
| 291 |
+
assert P.matmul(A).rmul(d) == L.matmul(di).matmul(U)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def test_fflu_rank_deficient():
|
| 295 |
+
A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(2), ZZ(4)]], (2, 2), ZZ)
|
| 296 |
+
P, L, D, U = A.fflu()
|
| 297 |
+
assert P.shape == (2, 2)
|
| 298 |
+
assert L.shape == (2, 2)
|
| 299 |
+
assert D.shape == (2, 2)
|
| 300 |
+
assert U.shape == (2, 2)
|
| 301 |
+
assert U.getitem_sympy(1, 1) == 0
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_inverse.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy import ZZ, Matrix
|
| 2 |
+
from sympy.polys.matrices import DM, DomainMatrix
|
| 3 |
+
from sympy.polys.matrices.dense import ddm_iinv
|
| 4 |
+
from sympy.polys.matrices.exceptions import DMNonInvertibleMatrixError
|
| 5 |
+
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
| 6 |
+
|
| 7 |
+
import pytest
|
| 8 |
+
from sympy.testing.pytest import raises
|
| 9 |
+
from sympy.core.numbers import all_close
|
| 10 |
+
|
| 11 |
+
from sympy.abc import x
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# Examples are given as adjugate matrix and determinant adj_det should match
|
| 15 |
+
# these exactly but inv_den only matches after cancel_denom.
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
INVERSE_EXAMPLES = [
|
| 19 |
+
|
| 20 |
+
(
|
| 21 |
+
'zz_1',
|
| 22 |
+
DomainMatrix([], (0, 0), ZZ),
|
| 23 |
+
DomainMatrix([], (0, 0), ZZ),
|
| 24 |
+
ZZ(1),
|
| 25 |
+
),
|
| 26 |
+
|
| 27 |
+
(
|
| 28 |
+
'zz_2',
|
| 29 |
+
DM([[2]], ZZ),
|
| 30 |
+
DM([[1]], ZZ),
|
| 31 |
+
ZZ(2),
|
| 32 |
+
),
|
| 33 |
+
|
| 34 |
+
(
|
| 35 |
+
'zz_3',
|
| 36 |
+
DM([[2, 0],
|
| 37 |
+
[0, 2]], ZZ),
|
| 38 |
+
DM([[2, 0],
|
| 39 |
+
[0, 2]], ZZ),
|
| 40 |
+
ZZ(4),
|
| 41 |
+
),
|
| 42 |
+
|
| 43 |
+
(
|
| 44 |
+
'zz_4',
|
| 45 |
+
DM([[1, 2],
|
| 46 |
+
[3, 4]], ZZ),
|
| 47 |
+
DM([[ 4, -2],
|
| 48 |
+
[-3, 1]], ZZ),
|
| 49 |
+
ZZ(-2),
|
| 50 |
+
),
|
| 51 |
+
|
| 52 |
+
(
|
| 53 |
+
'zz_5',
|
| 54 |
+
DM([[2, 2, 0],
|
| 55 |
+
[0, 2, 2],
|
| 56 |
+
[0, 0, 2]], ZZ),
|
| 57 |
+
DM([[4, -4, 4],
|
| 58 |
+
[0, 4, -4],
|
| 59 |
+
[0, 0, 4]], ZZ),
|
| 60 |
+
ZZ(8),
|
| 61 |
+
),
|
| 62 |
+
|
| 63 |
+
(
|
| 64 |
+
'zz_6',
|
| 65 |
+
DM([[1, 2, 3],
|
| 66 |
+
[4, 5, 6],
|
| 67 |
+
[7, 8, 9]], ZZ),
|
| 68 |
+
DM([[-3, 6, -3],
|
| 69 |
+
[ 6, -12, 6],
|
| 70 |
+
[-3, 6, -3]], ZZ),
|
| 71 |
+
ZZ(0),
|
| 72 |
+
),
|
| 73 |
+
]
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
| 77 |
+
def test_Matrix_inv(name, A, A_inv, den):
|
| 78 |
+
|
| 79 |
+
def _check(**kwargs):
|
| 80 |
+
if den != 0:
|
| 81 |
+
assert A.inv(**kwargs) == A_inv
|
| 82 |
+
else:
|
| 83 |
+
raises(NonInvertibleMatrixError, lambda: A.inv(**kwargs))
|
| 84 |
+
|
| 85 |
+
K = A.domain
|
| 86 |
+
A = A.to_Matrix()
|
| 87 |
+
A_inv = A_inv.to_Matrix() / K.to_sympy(den)
|
| 88 |
+
_check()
|
| 89 |
+
for method in ['GE', 'LU', 'ADJ', 'CH', 'LDL', 'QR']:
|
| 90 |
+
_check(method=method)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
| 94 |
+
def test_dm_inv_den(name, A, A_inv, den):
|
| 95 |
+
if den != 0:
|
| 96 |
+
A_inv_f, den_f = A.inv_den()
|
| 97 |
+
assert A_inv_f.cancel_denom(den_f) == A_inv.cancel_denom(den)
|
| 98 |
+
else:
|
| 99 |
+
raises(DMNonInvertibleMatrixError, lambda: A.inv_den())
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
| 103 |
+
def test_dm_inv(name, A, A_inv, den):
|
| 104 |
+
A = A.to_field()
|
| 105 |
+
if den != 0:
|
| 106 |
+
A_inv = A_inv.to_field() / den
|
| 107 |
+
assert A.inv() == A_inv
|
| 108 |
+
else:
|
| 109 |
+
raises(DMNonInvertibleMatrixError, lambda: A.inv())
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
| 113 |
+
def test_ddm_inv(name, A, A_inv, den):
|
| 114 |
+
A = A.to_field().to_ddm()
|
| 115 |
+
if den != 0:
|
| 116 |
+
A_inv = (A_inv.to_field() / den).to_ddm()
|
| 117 |
+
assert A.inv() == A_inv
|
| 118 |
+
else:
|
| 119 |
+
raises(DMNonInvertibleMatrixError, lambda: A.inv())
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
| 123 |
+
def test_sdm_inv(name, A, A_inv, den):
|
| 124 |
+
A = A.to_field().to_sdm()
|
| 125 |
+
if den != 0:
|
| 126 |
+
A_inv = (A_inv.to_field() / den).to_sdm()
|
| 127 |
+
assert A.inv() == A_inv
|
| 128 |
+
else:
|
| 129 |
+
raises(DMNonInvertibleMatrixError, lambda: A.inv())
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
| 133 |
+
def test_dense_ddm_iinv(name, A, A_inv, den):
|
| 134 |
+
A = A.to_field().to_ddm().copy()
|
| 135 |
+
K = A.domain
|
| 136 |
+
A_result = A.copy()
|
| 137 |
+
if den != 0:
|
| 138 |
+
A_inv = (A_inv.to_field() / den).to_ddm()
|
| 139 |
+
ddm_iinv(A_result, A, K)
|
| 140 |
+
assert A_result == A_inv
|
| 141 |
+
else:
|
| 142 |
+
raises(DMNonInvertibleMatrixError, lambda: ddm_iinv(A_result, A, K))
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
| 146 |
+
def test_Matrix_adjugate(name, A, A_inv, den):
|
| 147 |
+
A = A.to_Matrix()
|
| 148 |
+
A_inv = A_inv.to_Matrix()
|
| 149 |
+
assert A.adjugate() == A_inv
|
| 150 |
+
for method in ["bareiss", "berkowitz", "bird", "laplace", "lu"]:
|
| 151 |
+
assert A.adjugate(method=method) == A_inv
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@pytest.mark.parametrize('name, A, A_inv, den', INVERSE_EXAMPLES)
|
| 155 |
+
def test_dm_adj_det(name, A, A_inv, den):
|
| 156 |
+
assert A.adj_det() == (A_inv, den)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def test_inverse_inexact():
|
| 160 |
+
|
| 161 |
+
M = Matrix([[x-0.3, -0.06, -0.22],
|
| 162 |
+
[-0.46, x-0.48, -0.41],
|
| 163 |
+
[-0.14, -0.39, x-0.64]])
|
| 164 |
+
|
| 165 |
+
Mn = Matrix([[1.0*x**2 - 1.12*x + 0.1473, 0.06*x + 0.0474, 0.22*x - 0.081],
|
| 166 |
+
[0.46*x - 0.237, 1.0*x**2 - 0.94*x + 0.1612, 0.41*x - 0.0218],
|
| 167 |
+
[0.14*x + 0.1122, 0.39*x - 0.1086, 1.0*x**2 - 0.78*x + 0.1164]])
|
| 168 |
+
|
| 169 |
+
d = 1.0*x**3 - 1.42*x**2 + 0.4249*x - 0.0546540000000002
|
| 170 |
+
|
| 171 |
+
Mi = Mn / d
|
| 172 |
+
|
| 173 |
+
M_dm = M.to_DM()
|
| 174 |
+
M_dmd = M_dm.to_dense()
|
| 175 |
+
M_dm_num, M_dm_den = M_dm.inv_den()
|
| 176 |
+
M_dmd_num, M_dmd_den = M_dmd.inv_den()
|
| 177 |
+
|
| 178 |
+
# XXX: We don't check M_dm().to_field().inv() which currently uses division
|
| 179 |
+
# and produces a more complicate result from gcd cancellation failing.
|
| 180 |
+
# DomainMatrix.inv() over RR(x) should be changed to clear denominators and
|
| 181 |
+
# use DomainMatrix.inv_den().
|
| 182 |
+
|
| 183 |
+
Minvs = [
|
| 184 |
+
M.inv(),
|
| 185 |
+
(M_dm_num.to_field() / M_dm_den).to_Matrix(),
|
| 186 |
+
(M_dmd_num.to_field() / M_dmd_den).to_Matrix(),
|
| 187 |
+
M_dm_num.to_Matrix() / M_dm_den.as_expr(),
|
| 188 |
+
M_dmd_num.to_Matrix() / M_dmd_den.as_expr(),
|
| 189 |
+
]
|
| 190 |
+
|
| 191 |
+
for Minv in Minvs:
|
| 192 |
+
for Mi1, Mi2 in zip(Minv.flat(), Mi.flat()):
|
| 193 |
+
assert all_close(Mi2, Mi1)
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_linsolve.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# test_linsolve.py
|
| 3 |
+
#
|
| 4 |
+
# Test the internal implementation of linsolve.
|
| 5 |
+
#
|
| 6 |
+
|
| 7 |
+
from sympy.testing.pytest import raises
|
| 8 |
+
|
| 9 |
+
from sympy.core.numbers import I
|
| 10 |
+
from sympy.core.relational import Eq
|
| 11 |
+
from sympy.core.singleton import S
|
| 12 |
+
from sympy.abc import x, y, z
|
| 13 |
+
|
| 14 |
+
from sympy.polys.matrices.linsolve import _linsolve
|
| 15 |
+
from sympy.polys.solvers import PolyNonlinearError
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test__linsolve():
|
| 19 |
+
assert _linsolve([], [x]) == {x:x}
|
| 20 |
+
assert _linsolve([S.Zero], [x]) == {x:x}
|
| 21 |
+
assert _linsolve([x-1,x-2], [x]) is None
|
| 22 |
+
assert _linsolve([x-1], [x]) == {x:1}
|
| 23 |
+
assert _linsolve([x-1, y], [x, y]) == {x:1, y:S.Zero}
|
| 24 |
+
assert _linsolve([2*I], [x]) is None
|
| 25 |
+
raises(PolyNonlinearError, lambda: _linsolve([x*(1 + x)], [x]))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test__linsolve_float():
|
| 29 |
+
|
| 30 |
+
# This should give the exact answer:
|
| 31 |
+
eqs = [
|
| 32 |
+
y - x,
|
| 33 |
+
y - 0.0216 * x
|
| 34 |
+
]
|
| 35 |
+
# Should _linsolve return floats here?
|
| 36 |
+
sol = {x:0, y:0}
|
| 37 |
+
assert _linsolve(eqs, (x, y)) == sol
|
| 38 |
+
|
| 39 |
+
# Other cases should be close to eps
|
| 40 |
+
|
| 41 |
+
def all_close(sol1, sol2, eps=1e-15):
|
| 42 |
+
close = lambda a, b: abs(a - b) < eps
|
| 43 |
+
assert sol1.keys() == sol2.keys()
|
| 44 |
+
return all(close(sol1[s], sol2[s]) for s in sol1)
|
| 45 |
+
|
| 46 |
+
eqs = [
|
| 47 |
+
0.8*x + 0.8*z + 0.2,
|
| 48 |
+
0.9*x + 0.7*y + 0.2*z + 0.9,
|
| 49 |
+
0.7*x + 0.2*y + 0.2*z + 0.5
|
| 50 |
+
]
|
| 51 |
+
sol_exact = {x:-29/42, y:-11/21, z:37/84}
|
| 52 |
+
sol_linsolve = _linsolve(eqs, [x,y,z])
|
| 53 |
+
assert all_close(sol_exact, sol_linsolve)
|
| 54 |
+
|
| 55 |
+
eqs = [
|
| 56 |
+
0.9*x + 0.3*y + 0.4*z + 0.6,
|
| 57 |
+
0.6*x + 0.9*y + 0.1*z + 0.7,
|
| 58 |
+
0.4*x + 0.6*y + 0.9*z + 0.5
|
| 59 |
+
]
|
| 60 |
+
sol_exact = {x:-88/175, y:-46/105, z:-1/25}
|
| 61 |
+
sol_linsolve = _linsolve(eqs, [x,y,z])
|
| 62 |
+
assert all_close(sol_exact, sol_linsolve)
|
| 63 |
+
|
| 64 |
+
eqs = [
|
| 65 |
+
0.4*x + 0.3*y + 0.6*z + 0.7,
|
| 66 |
+
0.4*x + 0.3*y + 0.9*z + 0.9,
|
| 67 |
+
0.7*x + 0.9*y,
|
| 68 |
+
]
|
| 69 |
+
sol_exact = {x:-9/5, y:7/5, z:-2/3}
|
| 70 |
+
sol_linsolve = _linsolve(eqs, [x,y,z])
|
| 71 |
+
assert all_close(sol_exact, sol_linsolve)
|
| 72 |
+
|
| 73 |
+
eqs = [
|
| 74 |
+
x*(0.7 + 0.6*I) + y*(0.4 + 0.7*I) + z*(0.9 + 0.1*I) + 0.5,
|
| 75 |
+
0.2*I*x + 0.2*I*y + z*(0.9 + 0.2*I) + 0.1,
|
| 76 |
+
x*(0.9 + 0.7*I) + y*(0.9 + 0.7*I) + z*(0.9 + 0.4*I) + 0.4,
|
| 77 |
+
]
|
| 78 |
+
sol_exact = {
|
| 79 |
+
x:-6157/7995 - 411/5330*I,
|
| 80 |
+
y:8519/15990 + 1784/7995*I,
|
| 81 |
+
z:-34/533 + 107/1599*I,
|
| 82 |
+
}
|
| 83 |
+
sol_linsolve = _linsolve(eqs, [x,y,z])
|
| 84 |
+
assert all_close(sol_exact, sol_linsolve)
|
| 85 |
+
|
| 86 |
+
# XXX: This system for x and y over RR(z) is problematic.
|
| 87 |
+
#
|
| 88 |
+
# eqs = [
|
| 89 |
+
# x*(0.2*z + 0.9) + y*(0.5*z + 0.8) + 0.6,
|
| 90 |
+
# 0.1*x*z + y*(0.1*z + 0.6) + 0.9,
|
| 91 |
+
# ]
|
| 92 |
+
#
|
| 93 |
+
# linsolve(eqs, [x, y])
|
| 94 |
+
# The solution for x comes out as
|
| 95 |
+
#
|
| 96 |
+
# -3.9e-5*z**2 - 3.6e-5*z - 8.67361737988404e-20
|
| 97 |
+
# x = ----------------------------------------------
|
| 98 |
+
# 3.0e-6*z**3 - 1.3e-5*z**2 - 5.4e-5*z
|
| 99 |
+
#
|
| 100 |
+
# The 8e-20 in the numerator should be zero which would allow z to cancel
|
| 101 |
+
# from top and bottom. It should be possible to avoid this somehow because
|
| 102 |
+
# the inverse of the matrix only has a quadratic factor (the determinant)
|
| 103 |
+
# in the denominator.
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def test__linsolve_deprecated():
|
| 107 |
+
raises(PolyNonlinearError, lambda:
|
| 108 |
+
_linsolve([Eq(x**2, x**2 + y)], [x, y]))
|
| 109 |
+
raises(PolyNonlinearError, lambda:
|
| 110 |
+
_linsolve([(x + y)**2 - x**2], [x]))
|
| 111 |
+
raises(PolyNonlinearError, lambda:
|
| 112 |
+
_linsolve([Eq((x + y)**2, x**2)], [x]))
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_lll.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.polys.domains import ZZ, QQ
|
| 2 |
+
from sympy.polys.matrices import DM
|
| 3 |
+
from sympy.polys.matrices.domainmatrix import DomainMatrix
|
| 4 |
+
from sympy.polys.matrices.exceptions import DMRankError, DMValueError, DMShapeError, DMDomainError
|
| 5 |
+
from sympy.polys.matrices.lll import _ddm_lll, ddm_lll, ddm_lll_transform
|
| 6 |
+
from sympy.testing.pytest import raises
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_lll():
|
| 10 |
+
normal_test_data = [
|
| 11 |
+
(
|
| 12 |
+
DM([[1, 0, 0, 0, -20160],
|
| 13 |
+
[0, 1, 0, 0, 33768],
|
| 14 |
+
[0, 0, 1, 0, 39578],
|
| 15 |
+
[0, 0, 0, 1, 47757]], ZZ),
|
| 16 |
+
DM([[10, -3, -2, 8, -4],
|
| 17 |
+
[3, -9, 8, 1, -11],
|
| 18 |
+
[-3, 13, -9, -3, -9],
|
| 19 |
+
[-12, -7, -11, 9, -1]], ZZ)
|
| 20 |
+
),
|
| 21 |
+
(
|
| 22 |
+
DM([[20, 52, 3456],
|
| 23 |
+
[14, 31, -1],
|
| 24 |
+
[34, -442, 0]], ZZ),
|
| 25 |
+
DM([[14, 31, -1],
|
| 26 |
+
[188, -101, -11],
|
| 27 |
+
[236, 13, 3443]], ZZ)
|
| 28 |
+
),
|
| 29 |
+
(
|
| 30 |
+
DM([[34, -1, -86, 12],
|
| 31 |
+
[-54, 34, 55, 678],
|
| 32 |
+
[23, 3498, 234, 6783],
|
| 33 |
+
[87, 49, 665, 11]], ZZ),
|
| 34 |
+
DM([[34, -1, -86, 12],
|
| 35 |
+
[291, 43, 149, 83],
|
| 36 |
+
[-54, 34, 55, 678],
|
| 37 |
+
[-189, 3077, -184, -223]], ZZ)
|
| 38 |
+
)
|
| 39 |
+
]
|
| 40 |
+
delta = QQ(5, 6)
|
| 41 |
+
for basis_dm, reduced_dm in normal_test_data:
|
| 42 |
+
reduced = _ddm_lll(basis_dm.rep.to_ddm(), delta=delta)[0]
|
| 43 |
+
assert reduced == reduced_dm.rep.to_ddm()
|
| 44 |
+
|
| 45 |
+
reduced = ddm_lll(basis_dm.rep.to_ddm(), delta=delta)
|
| 46 |
+
assert reduced == reduced_dm.rep.to_ddm()
|
| 47 |
+
|
| 48 |
+
reduced, transform = _ddm_lll(basis_dm.rep.to_ddm(), delta=delta, return_transform=True)
|
| 49 |
+
assert reduced == reduced_dm.rep.to_ddm()
|
| 50 |
+
assert transform.matmul(basis_dm.rep.to_ddm()) == reduced_dm.rep.to_ddm()
|
| 51 |
+
|
| 52 |
+
reduced, transform = ddm_lll_transform(basis_dm.rep.to_ddm(), delta=delta)
|
| 53 |
+
assert reduced == reduced_dm.rep.to_ddm()
|
| 54 |
+
assert transform.matmul(basis_dm.rep.to_ddm()) == reduced_dm.rep.to_ddm()
|
| 55 |
+
|
| 56 |
+
reduced = basis_dm.rep.lll(delta=delta)
|
| 57 |
+
assert reduced == reduced_dm.rep
|
| 58 |
+
|
| 59 |
+
reduced, transform = basis_dm.rep.lll_transform(delta=delta)
|
| 60 |
+
assert reduced == reduced_dm.rep
|
| 61 |
+
assert transform.matmul(basis_dm.rep) == reduced_dm.rep
|
| 62 |
+
|
| 63 |
+
reduced = basis_dm.rep.to_sdm().lll(delta=delta)
|
| 64 |
+
assert reduced == reduced_dm.rep.to_sdm()
|
| 65 |
+
|
| 66 |
+
reduced, transform = basis_dm.rep.to_sdm().lll_transform(delta=delta)
|
| 67 |
+
assert reduced == reduced_dm.rep.to_sdm()
|
| 68 |
+
assert transform.matmul(basis_dm.rep.to_sdm()) == reduced_dm.rep.to_sdm()
|
| 69 |
+
|
| 70 |
+
reduced = basis_dm.lll(delta=delta)
|
| 71 |
+
assert reduced == reduced_dm
|
| 72 |
+
|
| 73 |
+
reduced, transform = basis_dm.lll_transform(delta=delta)
|
| 74 |
+
assert reduced == reduced_dm
|
| 75 |
+
assert transform.matmul(basis_dm) == reduced_dm
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def test_lll_linear_dependent():
|
| 79 |
+
linear_dependent_test_data = [
|
| 80 |
+
DM([[0, -1, -2, -3],
|
| 81 |
+
[1, 0, -1, -2],
|
| 82 |
+
[2, 1, 0, -1],
|
| 83 |
+
[3, 2, 1, 0]], ZZ),
|
| 84 |
+
DM([[1, 0, 0, 1],
|
| 85 |
+
[0, 1, 0, 1],
|
| 86 |
+
[0, 0, 1, 1],
|
| 87 |
+
[1, 2, 3, 6]], ZZ),
|
| 88 |
+
DM([[3, -5, 1],
|
| 89 |
+
[4, 6, 0],
|
| 90 |
+
[10, -4, 2]], ZZ)
|
| 91 |
+
]
|
| 92 |
+
for not_basis in linear_dependent_test_data:
|
| 93 |
+
raises(DMRankError, lambda: _ddm_lll(not_basis.rep.to_ddm()))
|
| 94 |
+
raises(DMRankError, lambda: ddm_lll(not_basis.rep.to_ddm()))
|
| 95 |
+
raises(DMRankError, lambda: not_basis.rep.lll())
|
| 96 |
+
raises(DMRankError, lambda: not_basis.rep.to_sdm().lll())
|
| 97 |
+
raises(DMRankError, lambda: not_basis.lll())
|
| 98 |
+
raises(DMRankError, lambda: _ddm_lll(not_basis.rep.to_ddm(), return_transform=True))
|
| 99 |
+
raises(DMRankError, lambda: ddm_lll_transform(not_basis.rep.to_ddm()))
|
| 100 |
+
raises(DMRankError, lambda: not_basis.rep.lll_transform())
|
| 101 |
+
raises(DMRankError, lambda: not_basis.rep.to_sdm().lll_transform())
|
| 102 |
+
raises(DMRankError, lambda: not_basis.lll_transform())
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def test_lll_wrong_delta():
|
| 106 |
+
dummy_matrix = DomainMatrix.ones((3, 3), ZZ)
|
| 107 |
+
for wrong_delta in [QQ(-1, 4), QQ(0, 1), QQ(1, 4), QQ(1, 1), QQ(100, 1)]:
|
| 108 |
+
raises(DMValueError, lambda: _ddm_lll(dummy_matrix.rep, delta=wrong_delta))
|
| 109 |
+
raises(DMValueError, lambda: ddm_lll(dummy_matrix.rep, delta=wrong_delta))
|
| 110 |
+
raises(DMValueError, lambda: dummy_matrix.rep.lll(delta=wrong_delta))
|
| 111 |
+
raises(DMValueError, lambda: dummy_matrix.rep.to_sdm().lll(delta=wrong_delta))
|
| 112 |
+
raises(DMValueError, lambda: dummy_matrix.lll(delta=wrong_delta))
|
| 113 |
+
raises(DMValueError, lambda: _ddm_lll(dummy_matrix.rep, delta=wrong_delta, return_transform=True))
|
| 114 |
+
raises(DMValueError, lambda: ddm_lll_transform(dummy_matrix.rep, delta=wrong_delta))
|
| 115 |
+
raises(DMValueError, lambda: dummy_matrix.rep.lll_transform(delta=wrong_delta))
|
| 116 |
+
raises(DMValueError, lambda: dummy_matrix.rep.to_sdm().lll_transform(delta=wrong_delta))
|
| 117 |
+
raises(DMValueError, lambda: dummy_matrix.lll_transform(delta=wrong_delta))
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def test_lll_wrong_shape():
|
| 121 |
+
wrong_shape_matrix = DomainMatrix.ones((4, 3), ZZ)
|
| 122 |
+
raises(DMShapeError, lambda: _ddm_lll(wrong_shape_matrix.rep))
|
| 123 |
+
raises(DMShapeError, lambda: ddm_lll(wrong_shape_matrix.rep))
|
| 124 |
+
raises(DMShapeError, lambda: wrong_shape_matrix.rep.lll())
|
| 125 |
+
raises(DMShapeError, lambda: wrong_shape_matrix.rep.to_sdm().lll())
|
| 126 |
+
raises(DMShapeError, lambda: wrong_shape_matrix.lll())
|
| 127 |
+
raises(DMShapeError, lambda: _ddm_lll(wrong_shape_matrix.rep, return_transform=True))
|
| 128 |
+
raises(DMShapeError, lambda: ddm_lll_transform(wrong_shape_matrix.rep))
|
| 129 |
+
raises(DMShapeError, lambda: wrong_shape_matrix.rep.lll_transform())
|
| 130 |
+
raises(DMShapeError, lambda: wrong_shape_matrix.rep.to_sdm().lll_transform())
|
| 131 |
+
raises(DMShapeError, lambda: wrong_shape_matrix.lll_transform())
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def test_lll_wrong_domain():
|
| 135 |
+
wrong_domain_matrix = DomainMatrix.ones((3, 3), QQ)
|
| 136 |
+
raises(DMDomainError, lambda: _ddm_lll(wrong_domain_matrix.rep))
|
| 137 |
+
raises(DMDomainError, lambda: ddm_lll(wrong_domain_matrix.rep))
|
| 138 |
+
raises(DMDomainError, lambda: wrong_domain_matrix.rep.lll())
|
| 139 |
+
raises(DMDomainError, lambda: wrong_domain_matrix.rep.to_sdm().lll())
|
| 140 |
+
raises(DMDomainError, lambda: wrong_domain_matrix.lll())
|
| 141 |
+
raises(DMDomainError, lambda: _ddm_lll(wrong_domain_matrix.rep, return_transform=True))
|
| 142 |
+
raises(DMDomainError, lambda: ddm_lll_transform(wrong_domain_matrix.rep))
|
| 143 |
+
raises(DMDomainError, lambda: wrong_domain_matrix.rep.lll_transform())
|
| 144 |
+
raises(DMDomainError, lambda: wrong_domain_matrix.rep.to_sdm().lll_transform())
|
| 145 |
+
raises(DMDomainError, lambda: wrong_domain_matrix.lll_transform())
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_normalforms.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.testing.pytest import raises
|
| 2 |
+
|
| 3 |
+
from sympy.core.symbol import Symbol
|
| 4 |
+
from sympy.polys.matrices.normalforms import (
|
| 5 |
+
invariant_factors,
|
| 6 |
+
smith_normal_form,
|
| 7 |
+
smith_normal_decomp,
|
| 8 |
+
is_smith_normal_form,
|
| 9 |
+
hermite_normal_form,
|
| 10 |
+
_hermite_normal_form,
|
| 11 |
+
_hermite_normal_form_modulo_D
|
| 12 |
+
)
|
| 13 |
+
from sympy.polys.domains import ZZ, QQ
|
| 14 |
+
from sympy.polys.matrices import DomainMatrix, DM
|
| 15 |
+
from sympy.polys.matrices.exceptions import DMDomainError, DMShapeError
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_is_smith_normal_form():
|
| 19 |
+
|
| 20 |
+
snf_examples = [
|
| 21 |
+
DM([[0, 0], [0, 0]], ZZ),
|
| 22 |
+
DM([[1, 0], [0, 0]], ZZ),
|
| 23 |
+
DM([[1, 0], [0, 1]], ZZ),
|
| 24 |
+
DM([[1, 0], [0, 2]], ZZ),
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
non_snf_examples = [
|
| 28 |
+
DM([[0, 1], [0, 0]], ZZ),
|
| 29 |
+
DM([[0, 0], [0, 1]], ZZ),
|
| 30 |
+
DM([[2, 0], [0, 3]], ZZ),
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
for m in snf_examples:
|
| 34 |
+
assert is_smith_normal_form(m) is True
|
| 35 |
+
|
| 36 |
+
for m in non_snf_examples:
|
| 37 |
+
assert is_smith_normal_form(m) is False
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def test_smith_normal():
|
| 41 |
+
|
| 42 |
+
m = DM([
|
| 43 |
+
[12, 6, 4, 8],
|
| 44 |
+
[3, 9, 6, 12],
|
| 45 |
+
[2, 16, 14, 28],
|
| 46 |
+
[20, 10, 10, 20]], ZZ)
|
| 47 |
+
|
| 48 |
+
smf = DM([
|
| 49 |
+
[1, 0, 0, 0],
|
| 50 |
+
[0, 10, 0, 0],
|
| 51 |
+
[0, 0, 30, 0],
|
| 52 |
+
[0, 0, 0, 0]], ZZ)
|
| 53 |
+
|
| 54 |
+
s = DM([
|
| 55 |
+
[0, 1, -1, 0],
|
| 56 |
+
[1, -4, 0, 0],
|
| 57 |
+
[0, -2, 3, 0],
|
| 58 |
+
[-2, 2, -1, 1]], ZZ)
|
| 59 |
+
|
| 60 |
+
t = DM([
|
| 61 |
+
[1, 1, 10, 0],
|
| 62 |
+
[0, -1, -2, 0],
|
| 63 |
+
[0, 1, 3, -2],
|
| 64 |
+
[0, 0, 0, 1]], ZZ)
|
| 65 |
+
|
| 66 |
+
assert smith_normal_form(m).to_dense() == smf
|
| 67 |
+
assert smith_normal_decomp(m) == (smf, s, t)
|
| 68 |
+
assert is_smith_normal_form(smf)
|
| 69 |
+
assert smf == s * m * t
|
| 70 |
+
|
| 71 |
+
m00 = DomainMatrix.zeros((0, 0), ZZ).to_dense()
|
| 72 |
+
m01 = DomainMatrix.zeros((0, 1), ZZ).to_dense()
|
| 73 |
+
m10 = DomainMatrix.zeros((1, 0), ZZ).to_dense()
|
| 74 |
+
i11 = DM([[1]], ZZ)
|
| 75 |
+
|
| 76 |
+
assert smith_normal_form(m00) == m00.to_sparse()
|
| 77 |
+
assert smith_normal_form(m01) == m01.to_sparse()
|
| 78 |
+
assert smith_normal_form(m10) == m10.to_sparse()
|
| 79 |
+
assert smith_normal_form(i11) == i11.to_sparse()
|
| 80 |
+
|
| 81 |
+
assert smith_normal_decomp(m00) == (m00, m00, m00)
|
| 82 |
+
assert smith_normal_decomp(m01) == (m01, m00, i11)
|
| 83 |
+
assert smith_normal_decomp(m10) == (m10, i11, m00)
|
| 84 |
+
assert smith_normal_decomp(i11) == (i11, i11, i11)
|
| 85 |
+
|
| 86 |
+
x = Symbol('x')
|
| 87 |
+
m = DM([[x-1, 1, -1],
|
| 88 |
+
[ 0, x, -1],
|
| 89 |
+
[ 0, -1, x]], QQ[x])
|
| 90 |
+
dx = m.domain.gens[0]
|
| 91 |
+
assert invariant_factors(m) == (1, dx-1, dx**2-1)
|
| 92 |
+
|
| 93 |
+
zr = DomainMatrix([], (0, 2), ZZ)
|
| 94 |
+
zc = DomainMatrix([[], []], (2, 0), ZZ)
|
| 95 |
+
assert smith_normal_form(zr).to_dense() == zr
|
| 96 |
+
assert smith_normal_form(zc).to_dense() == zc
|
| 97 |
+
|
| 98 |
+
assert smith_normal_form(DM([[2, 4]], ZZ)).to_dense() == DM([[2, 0]], ZZ)
|
| 99 |
+
assert smith_normal_form(DM([[0, -2]], ZZ)).to_dense() == DM([[2, 0]], ZZ)
|
| 100 |
+
assert smith_normal_form(DM([[0], [-2]], ZZ)).to_dense() == DM([[2], [0]], ZZ)
|
| 101 |
+
|
| 102 |
+
assert smith_normal_decomp(DM([[0, -2]], ZZ)) == (
|
| 103 |
+
DM([[2, 0]], ZZ), DM([[-1]], ZZ), DM([[0, 1], [1, 0]], ZZ)
|
| 104 |
+
)
|
| 105 |
+
assert smith_normal_decomp(DM([[0], [-2]], ZZ)) == (
|
| 106 |
+
DM([[2], [0]], ZZ), DM([[0, -1], [1, 0]], ZZ), DM([[1]], ZZ)
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
m = DM([[3, 0, 0, 0], [0, 0, 0, 0], [0, 0, 2, 0]], ZZ)
|
| 110 |
+
snf = DM([[1, 0, 0, 0], [0, 6, 0, 0], [0, 0, 0, 0]], ZZ)
|
| 111 |
+
s = DM([[1, 0, 1], [2, 0, 3], [0, 1, 0]], ZZ)
|
| 112 |
+
t = DM([[1, -2, 0, 0], [0, 0, 0, 1], [-1, 3, 0, 0], [0, 0, 1, 0]], ZZ)
|
| 113 |
+
|
| 114 |
+
assert smith_normal_form(m).to_dense() == snf
|
| 115 |
+
assert smith_normal_decomp(m) == (snf, s, t)
|
| 116 |
+
assert is_smith_normal_form(snf)
|
| 117 |
+
assert snf == s * m * t
|
| 118 |
+
|
| 119 |
+
raises(ValueError, lambda: smith_normal_form(DM([[1]], ZZ[x])))
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def test_hermite_normal():
|
| 123 |
+
m = DM([[2, 7, 17, 29, 41], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]], ZZ)
|
| 124 |
+
hnf = DM([[1, 0, 0], [0, 2, 1], [0, 0, 1]], ZZ)
|
| 125 |
+
assert hermite_normal_form(m) == hnf
|
| 126 |
+
assert hermite_normal_form(m, D=ZZ(2)) == hnf
|
| 127 |
+
assert hermite_normal_form(m, D=ZZ(2), check_rank=True) == hnf
|
| 128 |
+
|
| 129 |
+
m = m.transpose()
|
| 130 |
+
hnf = DM([[37, 0, 19], [222, -6, 113], [48, 0, 25], [0, 2, 1], [0, 0, 1]], ZZ)
|
| 131 |
+
assert hermite_normal_form(m) == hnf
|
| 132 |
+
raises(DMShapeError, lambda: _hermite_normal_form_modulo_D(m, ZZ(96)))
|
| 133 |
+
raises(DMDomainError, lambda: _hermite_normal_form_modulo_D(m, QQ(96)))
|
| 134 |
+
|
| 135 |
+
m = DM([[8, 28, 68, 116, 164], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]], ZZ)
|
| 136 |
+
hnf = DM([[4, 0, 0], [0, 2, 1], [0, 0, 1]], ZZ)
|
| 137 |
+
assert hermite_normal_form(m) == hnf
|
| 138 |
+
assert hermite_normal_form(m, D=ZZ(8)) == hnf
|
| 139 |
+
assert hermite_normal_form(m, D=ZZ(8), check_rank=True) == hnf
|
| 140 |
+
|
| 141 |
+
m = DM([[10, 8, 6, 30, 2], [45, 36, 27, 18, 9], [5, 4, 3, 2, 1]], ZZ)
|
| 142 |
+
hnf = DM([[26, 2], [0, 9], [0, 1]], ZZ)
|
| 143 |
+
assert hermite_normal_form(m) == hnf
|
| 144 |
+
|
| 145 |
+
m = DM([[2, 7], [0, 0], [0, 0]], ZZ)
|
| 146 |
+
hnf = DM([[1], [0], [0]], ZZ)
|
| 147 |
+
assert hermite_normal_form(m) == hnf
|
| 148 |
+
|
| 149 |
+
m = DM([[-2, 1], [0, 1]], ZZ)
|
| 150 |
+
hnf = DM([[2, 1], [0, 1]], ZZ)
|
| 151 |
+
assert hermite_normal_form(m) == hnf
|
| 152 |
+
|
| 153 |
+
m = DomainMatrix([[QQ(1)]], (1, 1), QQ)
|
| 154 |
+
raises(DMDomainError, lambda: hermite_normal_form(m))
|
| 155 |
+
raises(DMDomainError, lambda: _hermite_normal_form(m))
|
| 156 |
+
raises(DMDomainError, lambda: _hermite_normal_form_modulo_D(m, ZZ(1)))
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_nullspace.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy import ZZ, Matrix
|
| 2 |
+
from sympy.polys.matrices import DM, DomainMatrix
|
| 3 |
+
from sympy.polys.matrices.ddm import DDM
|
| 4 |
+
from sympy.polys.matrices.sdm import SDM
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
zeros = lambda shape, K: DomainMatrix.zeros(shape, K).to_dense()
|
| 9 |
+
eye = lambda n, K: DomainMatrix.eye(n, K).to_dense()
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
#
|
| 13 |
+
# DomainMatrix.nullspace can have a divided answer or can return an undivided
|
| 14 |
+
# uncanonical answer. The uncanonical answer is not unique but we can make it
|
| 15 |
+
# unique by making it primitive (remove gcd). The tests here all show the
|
| 16 |
+
# primitive form. We test two things:
|
| 17 |
+
#
|
| 18 |
+
# A.nullspace().primitive()[1] == answer.
|
| 19 |
+
# A.nullspace(divide_last=True) == _divide_last(answer).
|
| 20 |
+
#
|
| 21 |
+
# The nullspace as returned by DomainMatrix and related classes is the
|
| 22 |
+
# transpose of the nullspace as returned by Matrix. Matrix returns a list of
|
| 23 |
+
# of column vectors whereas DomainMatrix returns a matrix whose rows are the
|
| 24 |
+
# nullspace vectors.
|
| 25 |
+
#
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
NULLSPACE_EXAMPLES = [
|
| 29 |
+
|
| 30 |
+
(
|
| 31 |
+
'zz_1',
|
| 32 |
+
DM([[ 1, 2, 3]], ZZ),
|
| 33 |
+
DM([[-2, 1, 0],
|
| 34 |
+
[-3, 0, 1]], ZZ),
|
| 35 |
+
),
|
| 36 |
+
|
| 37 |
+
(
|
| 38 |
+
'zz_2',
|
| 39 |
+
zeros((0, 0), ZZ),
|
| 40 |
+
zeros((0, 0), ZZ),
|
| 41 |
+
),
|
| 42 |
+
|
| 43 |
+
(
|
| 44 |
+
'zz_3',
|
| 45 |
+
zeros((2, 0), ZZ),
|
| 46 |
+
zeros((0, 0), ZZ),
|
| 47 |
+
),
|
| 48 |
+
|
| 49 |
+
(
|
| 50 |
+
'zz_4',
|
| 51 |
+
zeros((0, 2), ZZ),
|
| 52 |
+
eye(2, ZZ),
|
| 53 |
+
),
|
| 54 |
+
|
| 55 |
+
(
|
| 56 |
+
'zz_5',
|
| 57 |
+
zeros((2, 2), ZZ),
|
| 58 |
+
eye(2, ZZ),
|
| 59 |
+
),
|
| 60 |
+
|
| 61 |
+
(
|
| 62 |
+
'zz_6',
|
| 63 |
+
DM([[1, 2],
|
| 64 |
+
[3, 4]], ZZ),
|
| 65 |
+
zeros((0, 2), ZZ),
|
| 66 |
+
),
|
| 67 |
+
|
| 68 |
+
(
|
| 69 |
+
'zz_7',
|
| 70 |
+
DM([[1, 1],
|
| 71 |
+
[1, 1]], ZZ),
|
| 72 |
+
DM([[-1, 1]], ZZ),
|
| 73 |
+
),
|
| 74 |
+
|
| 75 |
+
(
|
| 76 |
+
'zz_8',
|
| 77 |
+
DM([[1],
|
| 78 |
+
[1]], ZZ),
|
| 79 |
+
zeros((0, 1), ZZ),
|
| 80 |
+
),
|
| 81 |
+
|
| 82 |
+
(
|
| 83 |
+
'zz_9',
|
| 84 |
+
DM([[1, 1]], ZZ),
|
| 85 |
+
DM([[-1, 1]], ZZ),
|
| 86 |
+
),
|
| 87 |
+
|
| 88 |
+
(
|
| 89 |
+
'zz_10',
|
| 90 |
+
DM([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
| 91 |
+
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
| 92 |
+
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
| 93 |
+
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
| 94 |
+
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], ZZ),
|
| 95 |
+
DM([[ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
| 96 |
+
[-1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
| 97 |
+
[ 0, -1, 0, 0, 0, 0, 0, 1, 0, 0],
|
| 98 |
+
[ 0, 0, 0, -1, 0, 0, 0, 0, 1, 0],
|
| 99 |
+
[ 0, 0, 0, 0, -1, 0, 0, 0, 0, 1]], ZZ),
|
| 100 |
+
),
|
| 101 |
+
|
| 102 |
+
]
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _to_DM(A, ans):
|
| 106 |
+
"""Convert the answer to DomainMatrix."""
|
| 107 |
+
if isinstance(A, DomainMatrix):
|
| 108 |
+
return A.to_dense()
|
| 109 |
+
elif isinstance(A, DDM):
|
| 110 |
+
return DomainMatrix(list(A), A.shape, A.domain).to_dense()
|
| 111 |
+
elif isinstance(A, SDM):
|
| 112 |
+
return DomainMatrix(dict(A), A.shape, A.domain).to_dense()
|
| 113 |
+
else:
|
| 114 |
+
assert False # pragma: no cover
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _divide_last(null):
|
| 118 |
+
"""Normalize the nullspace by the rightmost non-zero entry."""
|
| 119 |
+
null = null.to_field()
|
| 120 |
+
|
| 121 |
+
if null.is_zero_matrix:
|
| 122 |
+
return null
|
| 123 |
+
|
| 124 |
+
rows = []
|
| 125 |
+
for i in range(null.shape[0]):
|
| 126 |
+
for j in reversed(range(null.shape[1])):
|
| 127 |
+
if null[i, j]:
|
| 128 |
+
rows.append(null[i, :] / null[i, j])
|
| 129 |
+
break
|
| 130 |
+
else:
|
| 131 |
+
assert False # pragma: no cover
|
| 132 |
+
|
| 133 |
+
return DomainMatrix.vstack(*rows)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _check_primitive(null, null_ans):
|
| 137 |
+
"""Check that the primitive of the answer matches."""
|
| 138 |
+
null = _to_DM(null, null_ans)
|
| 139 |
+
cont, null_prim = null.primitive()
|
| 140 |
+
assert null_prim == null_ans
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def _check_divided(null, null_ans):
|
| 144 |
+
"""Check the divided answer."""
|
| 145 |
+
null = _to_DM(null, null_ans)
|
| 146 |
+
null_ans_norm = _divide_last(null_ans)
|
| 147 |
+
assert null == null_ans_norm
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
| 151 |
+
def test_Matrix_nullspace(name, A, A_null):
|
| 152 |
+
A = A.to_Matrix()
|
| 153 |
+
|
| 154 |
+
A_null_cols = A.nullspace()
|
| 155 |
+
|
| 156 |
+
# We have to patch up the case where the nullspace is empty
|
| 157 |
+
if A_null_cols:
|
| 158 |
+
A_null_found = Matrix.hstack(*A_null_cols)
|
| 159 |
+
else:
|
| 160 |
+
A_null_found = Matrix.zeros(A.cols, 0)
|
| 161 |
+
|
| 162 |
+
A_null_found = A_null_found.to_DM().to_field().to_dense()
|
| 163 |
+
|
| 164 |
+
# The Matrix result is the transpose of DomainMatrix result.
|
| 165 |
+
A_null_found = A_null_found.transpose()
|
| 166 |
+
|
| 167 |
+
_check_divided(A_null_found, A_null)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
| 171 |
+
def test_dm_dense_nullspace(name, A, A_null):
|
| 172 |
+
A = A.to_field().to_dense()
|
| 173 |
+
A_null_found = A.nullspace(divide_last=True)
|
| 174 |
+
_check_divided(A_null_found, A_null)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
| 178 |
+
def test_dm_sparse_nullspace(name, A, A_null):
|
| 179 |
+
A = A.to_field().to_sparse()
|
| 180 |
+
A_null_found = A.nullspace(divide_last=True)
|
| 181 |
+
_check_divided(A_null_found, A_null)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
| 185 |
+
def test_ddm_nullspace(name, A, A_null):
|
| 186 |
+
A = A.to_field().to_ddm()
|
| 187 |
+
A_null_found, _ = A.nullspace()
|
| 188 |
+
_check_divided(A_null_found, A_null)
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
| 192 |
+
def test_sdm_nullspace(name, A, A_null):
|
| 193 |
+
A = A.to_field().to_sdm()
|
| 194 |
+
A_null_found, _ = A.nullspace()
|
| 195 |
+
_check_divided(A_null_found, A_null)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
| 199 |
+
def test_dm_dense_nullspace_fracfree(name, A, A_null):
|
| 200 |
+
A = A.to_dense()
|
| 201 |
+
A_null_found = A.nullspace()
|
| 202 |
+
_check_primitive(A_null_found, A_null)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
@pytest.mark.parametrize('name, A, A_null', NULLSPACE_EXAMPLES)
|
| 206 |
+
def test_dm_sparse_nullspace_fracfree(name, A, A_null):
|
| 207 |
+
A = A.to_sparse()
|
| 208 |
+
A_null_found = A.nullspace()
|
| 209 |
+
_check_primitive(A_null_found, A_null)
|
.venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_rref.py
ADDED
|
@@ -0,0 +1,737 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy import ZZ, QQ, ZZ_I, EX, Matrix, eye, zeros, symbols
|
| 2 |
+
from sympy.polys.matrices import DM, DomainMatrix
|
| 3 |
+
from sympy.polys.matrices.dense import ddm_irref_den, ddm_irref
|
| 4 |
+
from sympy.polys.matrices.ddm import DDM
|
| 5 |
+
from sympy.polys.matrices.sdm import SDM, sdm_irref, sdm_rref_den
|
| 6 |
+
|
| 7 |
+
import pytest
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
#
|
| 11 |
+
# The dense and sparse implementations of rref_den are ddm_irref_den and
|
| 12 |
+
# sdm_irref_den. These can give results that differ by some factor and also
|
| 13 |
+
# give different results if the order of the rows is changed. The tests below
|
| 14 |
+
# show all results on lowest terms as should be returned by cancel_denom.
|
| 15 |
+
#
|
| 16 |
+
# The EX domain is also a case where the dense and sparse implementations
|
| 17 |
+
# can give results in different forms: the results should be equivalent but
|
| 18 |
+
# are not canonical because EX does not have a canonical form.
|
| 19 |
+
#
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
a, b, c, d = symbols('a, b, c, d')
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
qq_large_1 = DM([
|
| 26 |
+
[ (1,2), (1,3), (1,5), (1,7), (1,11), (1,13), (1,17), (1,19), (1,23), (1,29), (1,31)],
|
| 27 |
+
[ (1,37), (1,41), (1,43), (1,47), (1,53), (1,59), (1,61), (1,67), (1,71), (1,73), (1,79)],
|
| 28 |
+
[ (1,83), (1,89), (1,97),(1,101),(1,103),(1,107),(1,109),(1,113),(1,127),(1,131),(1,137)],
|
| 29 |
+
[(1,139),(1,149),(1,151),(1,157),(1,163),(1,167),(1,173),(1,179),(1,181),(1,191),(1,193)],
|
| 30 |
+
[(1,197),(1,199),(1,211),(1,223),(1,227),(1,229),(1,233),(1,239),(1,241),(1,251),(1,257)],
|
| 31 |
+
[(1,263),(1,269),(1,271),(1,277),(1,281),(1,283),(1,293),(1,307),(1,311),(1,313),(1,317)],
|
| 32 |
+
[(1,331),(1,337),(1,347),(1,349),(1,353),(1,359),(1,367),(1,373),(1,379),(1,383),(1,389)],
|
| 33 |
+
[(1,397),(1,401),(1,409),(1,419),(1,421),(1,431),(1,433),(1,439),(1,443),(1,449),(1,457)],
|
| 34 |
+
[(1,461),(1,463),(1,467),(1,479),(1,487),(1,491),(1,499),(1,503),(1,509),(1,521),(1,523)],
|
| 35 |
+
[(1,541),(1,547),(1,557),(1,563),(1,569),(1,571),(1,577),(1,587),(1,593),(1,599),(1,601)],
|
| 36 |
+
[(1,607),(1,613),(1,617),(1,619),(1,631),(1,641),(1,643),(1,647),(1,653),(1,659),(1,661)]],
|
| 37 |
+
QQ)
|
| 38 |
+
|
| 39 |
+
qq_large_2 = qq_large_1 + 10**100 * DomainMatrix.eye(11, QQ)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
RREF_EXAMPLES = [
|
| 43 |
+
(
|
| 44 |
+
'zz_1',
|
| 45 |
+
DM([[1, 2, 3]], ZZ),
|
| 46 |
+
DM([[1, 2, 3]], ZZ),
|
| 47 |
+
ZZ(1),
|
| 48 |
+
),
|
| 49 |
+
|
| 50 |
+
(
|
| 51 |
+
'zz_2',
|
| 52 |
+
DomainMatrix([], (0, 0), ZZ),
|
| 53 |
+
DomainMatrix([], (0, 0), ZZ),
|
| 54 |
+
ZZ(1),
|
| 55 |
+
),
|
| 56 |
+
|
| 57 |
+
(
|
| 58 |
+
'zz_3',
|
| 59 |
+
DM([[1, 2],
|
| 60 |
+
[3, 4]], ZZ),
|
| 61 |
+
DM([[1, 0],
|
| 62 |
+
[0, 1]], ZZ),
|
| 63 |
+
ZZ(1),
|
| 64 |
+
),
|
| 65 |
+
|
| 66 |
+
(
|
| 67 |
+
'zz_4',
|
| 68 |
+
DM([[1, 0],
|
| 69 |
+
[3, 4]], ZZ),
|
| 70 |
+
DM([[1, 0],
|
| 71 |
+
[0, 1]], ZZ),
|
| 72 |
+
ZZ(1),
|
| 73 |
+
),
|
| 74 |
+
|
| 75 |
+
(
|
| 76 |
+
'zz_5',
|
| 77 |
+
DM([[0, 2],
|
| 78 |
+
[3, 4]], ZZ),
|
| 79 |
+
DM([[1, 0],
|
| 80 |
+
[0, 1]], ZZ),
|
| 81 |
+
ZZ(1),
|
| 82 |
+
),
|
| 83 |
+
|
| 84 |
+
(
|
| 85 |
+
'zz_6',
|
| 86 |
+
DM([[1, 2, 3],
|
| 87 |
+
[4, 5, 6],
|
| 88 |
+
[7, 8, 9]], ZZ),
|
| 89 |
+
DM([[1, 0, -1],
|
| 90 |
+
[0, 1, 2],
|
| 91 |
+
[0, 0, 0]], ZZ),
|
| 92 |
+
ZZ(1),
|
| 93 |
+
),
|
| 94 |
+
|
| 95 |
+
(
|
| 96 |
+
'zz_7',
|
| 97 |
+
DM([[0, 0, 0],
|
| 98 |
+
[0, 0, 0],
|
| 99 |
+
[1, 0, 0]], ZZ),
|
| 100 |
+
DM([[1, 0, 0],
|
| 101 |
+
[0, 0, 0],
|
| 102 |
+
[0, 0, 0]], ZZ),
|
| 103 |
+
ZZ(1),
|
| 104 |
+
),
|
| 105 |
+
|
| 106 |
+
(
|
| 107 |
+
'zz_8',
|
| 108 |
+
DM([[0, 0, 0],
|
| 109 |
+
[0, 0, 0],
|
| 110 |
+
[0, 0, 0]], ZZ),
|
| 111 |
+
DM([[0, 0, 0],
|
| 112 |
+
[0, 0, 0],
|
| 113 |
+
[0, 0, 0]], ZZ),
|
| 114 |
+
ZZ(1),
|
| 115 |
+
),
|
| 116 |
+
|
| 117 |
+
(
|
| 118 |
+
'zz_9',
|
| 119 |
+
DM([[1, 1, 0],
|
| 120 |
+
[0, 0, 2],
|
| 121 |
+
[0, 0, 0]], ZZ),
|
| 122 |
+
DM([[1, 1, 0],
|
| 123 |
+
[0, 0, 1],
|
| 124 |
+
[0, 0, 0]], ZZ),
|
| 125 |
+
ZZ(1),
|
| 126 |
+
),
|
| 127 |
+
|
| 128 |
+
(
|
| 129 |
+
'zz_10',
|
| 130 |
+
DM([[2, 2, 0],
|
| 131 |
+
[0, 0, 2],
|
| 132 |
+
[0, 0, 0]], ZZ),
|
| 133 |
+
DM([[1, 1, 0],
|
| 134 |
+
[0, 0, 1],
|
| 135 |
+
[0, 0, 0]], ZZ),
|
| 136 |
+
ZZ(1),
|
| 137 |
+
),
|
| 138 |
+
|
| 139 |
+
(
|
| 140 |
+
'zz_11',
|
| 141 |
+
DM([[2, 2, 0],
|
| 142 |
+
[0, 2, 2],
|
| 143 |
+
[0, 0, 2]], ZZ),
|
| 144 |
+
DM([[1, 0, 0],
|
| 145 |
+
[0, 1, 0],
|
| 146 |
+
[0, 0, 1]], ZZ),
|
| 147 |
+
ZZ(1),
|
| 148 |
+
),
|
| 149 |
+
|
| 150 |
+
(
|
| 151 |
+
'zz_12',
|
| 152 |
+
DM([[ 1, 2, 3],
|
| 153 |
+
[ 4, 5, 6],
|
| 154 |
+
[ 7, 8, 9],
|
| 155 |
+
[10, 11, 12]], ZZ),
|
| 156 |
+
DM([[1, 0, -1],
|
| 157 |
+
[0, 1, 2],
|
| 158 |
+
[0, 0, 0],
|
| 159 |
+
[0, 0, 0]], ZZ),
|
| 160 |
+
ZZ(1),
|
| 161 |
+
),
|
| 162 |
+
|
| 163 |
+
(
|
| 164 |
+
'zz_13',
|
| 165 |
+
DM([[ 1, 2, 3],
|
| 166 |
+
[ 4, 5, 6],
|
| 167 |
+
[ 7, 8, 9],
|
| 168 |
+
[10, 11, 13]], ZZ),
|
| 169 |
+
DM([[ 1, 0, 0],
|
| 170 |
+
[ 0, 1, 0],
|
| 171 |
+
[ 0, 0, 1],
|
| 172 |
+
[ 0, 0, 0]], ZZ),
|
| 173 |
+
ZZ(1),
|
| 174 |
+
),
|
| 175 |
+
|
| 176 |
+
(
|
| 177 |
+
'zz_14',
|
| 178 |
+
DM([[1, 2, 4, 3],
|
| 179 |
+
[4, 5, 10, 6],
|
| 180 |
+
[7, 8, 16, 9]], ZZ),
|
| 181 |
+
DM([[1, 0, 0, -1],
|
| 182 |
+
[0, 1, 2, 2],
|
| 183 |
+
[0, 0, 0, 0]], ZZ),
|
| 184 |
+
ZZ(1),
|
| 185 |
+
),
|
| 186 |
+
|
| 187 |
+
(
|
| 188 |
+
'zz_15',
|
| 189 |
+
DM([[1, 2, 4, 3],
|
| 190 |
+
[4, 5, 10, 6],
|
| 191 |
+
[7, 8, 17, 9]], ZZ),
|
| 192 |
+
DM([[1, 0, 0, -1],
|
| 193 |
+
[0, 1, 0, 2],
|
| 194 |
+
[0, 0, 1, 0]], ZZ),
|
| 195 |
+
ZZ(1),
|
| 196 |
+
),
|
| 197 |
+
|
| 198 |
+
(
|
| 199 |
+
'zz_16',
|
| 200 |
+
DM([[1, 2, 0, 1],
|
| 201 |
+
[1, 1, 9, 0]], ZZ),
|
| 202 |
+
DM([[1, 0, 18, -1],
|
| 203 |
+
[0, 1, -9, 1]], ZZ),
|
| 204 |
+
ZZ(1),
|
| 205 |
+
),
|
| 206 |
+
|
| 207 |
+
(
|
| 208 |
+
'zz_17',
|
| 209 |
+
DM([[1, 1, 1],
|
| 210 |
+
[1, 2, 2]], ZZ),
|
| 211 |
+
DM([[1, 0, 0],
|
| 212 |
+
[0, 1, 1]], ZZ),
|
| 213 |
+
ZZ(1),
|
| 214 |
+
),
|
| 215 |
+
|
| 216 |
+
(
|
| 217 |
+
# Here the sparse implementation and dense implementation give very
|
| 218 |
+
# different denominators: 4061232 and -1765176.
|
| 219 |
+
'zz_18',
|
| 220 |
+
DM([[94, 24, 0, 27, 0],
|
| 221 |
+
[79, 0, 0, 0, 0],
|
| 222 |
+
[85, 16, 71, 81, 0],
|
| 223 |
+
[ 0, 0, 72, 77, 0],
|
| 224 |
+
[21, 0, 34, 0, 0]], ZZ),
|
| 225 |
+
DM([[ 1, 0, 0, 0, 0],
|
| 226 |
+
[ 0, 1, 0, 0, 0],
|
| 227 |
+
[ 0, 0, 1, 0, 0],
|
| 228 |
+
[ 0, 0, 0, 1, 0],
|
| 229 |
+
[ 0, 0, 0, 0, 0]], ZZ),
|
| 230 |
+
ZZ(1),
|
| 231 |
+
),
|
| 232 |
+
|
| 233 |
+
(
|
| 234 |
+
# Let's have a denominator that cannot be cancelled.
|
| 235 |
+
'zz_19',
|
| 236 |
+
DM([[1, 2, 4],
|
| 237 |
+
[4, 5, 6]], ZZ),
|
| 238 |
+
DM([[3, 0, -8],
|
| 239 |
+
[0, 3, 10]], ZZ),
|
| 240 |
+
ZZ(3),
|
| 241 |
+
),
|
| 242 |
+
|
| 243 |
+
(
|
| 244 |
+
'zz_20',
|
| 245 |
+
DM([[0, 0, 0, 0, 0],
|
| 246 |
+
[0, 0, 0, 0, 0],
|
| 247 |
+
[0, 0, 0, 0, 4]], ZZ),
|
| 248 |
+
DM([[0, 0, 0, 0, 1],
|
| 249 |
+
[0, 0, 0, 0, 0],
|
| 250 |
+
[0, 0, 0, 0, 0]], ZZ),
|
| 251 |
+
ZZ(1),
|
| 252 |
+
),
|
| 253 |
+
|
| 254 |
+
(
|
| 255 |
+
'zz_21',
|
| 256 |
+
DM([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
| 257 |
+
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
| 258 |
+
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
| 259 |
+
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
| 260 |
+
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], ZZ),
|
| 261 |
+
DM([[1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
| 262 |
+
[0, 1, 0, 0, 0, 0, 0, 1, 0, 0],
|
| 263 |
+
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
| 264 |
+
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
| 265 |
+
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0]], ZZ),
|
| 266 |
+
ZZ(1),
|
| 267 |
+
),
|
| 268 |
+
|
| 269 |
+
(
|
| 270 |
+
'zz_22',
|
| 271 |
+
DM([[1, 1, 1, 0, 1],
|
| 272 |
+
[1, 1, 0, 1, 0],
|
| 273 |
+
[1, 0, 1, 0, 1],
|
| 274 |
+
[1, 1, 0, 1, 0],
|
| 275 |
+
[1, 0, 0, 0, 0]], ZZ),
|
| 276 |
+
DM([[1, 0, 0, 0, 0],
|
| 277 |
+
[0, 1, 0, 0, 0],
|
| 278 |
+
[0, 0, 1, 0, 1],
|
| 279 |
+
[0, 0, 0, 1, 0],
|
| 280 |
+
[0, 0, 0, 0, 0]], ZZ),
|
| 281 |
+
ZZ(1),
|
| 282 |
+
),
|
| 283 |
+
|
| 284 |
+
(
|
| 285 |
+
'zz_large_1',
|
| 286 |
+
DM([
|
| 287 |
+
[ 0, 0, 0, 81, 0, 0, 75, 0, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0],
|
| 288 |
+
[ 0, 0, 0, 0, 0, 86, 0, 92, 79, 54, 0, 7, 0, 0, 0, 0, 79, 0, 0, 0],
|
| 289 |
+
[89, 54, 81, 0, 0, 20, 0, 0, 0, 0, 0, 0, 51, 0, 94, 0, 0, 77, 0, 0],
|
| 290 |
+
[ 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 48, 29, 0, 0, 5, 0, 32, 0],
|
| 291 |
+
[ 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 11],
|
| 292 |
+
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0, 43, 0, 0],
|
| 293 |
+
[ 0, 0, 0, 0, 0, 38, 91, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 26, 0, 0],
|
| 294 |
+
[69, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55],
|
| 295 |
+
[ 0, 13, 18, 49, 49, 88, 0, 0, 35, 54, 0, 0, 51, 0, 0, 0, 0, 0, 0, 87],
|
| 296 |
+
[ 0, 0, 0, 0, 31, 0, 40, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 88, 0],
|
| 297 |
+
[ 0, 0, 0, 0, 0, 0, 0, 0, 98, 0, 0, 0, 15, 53, 0, 92, 0, 0, 0, 0],
|
| 298 |
+
[ 0, 0, 0, 95, 0, 0, 0, 36, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 73, 19],
|
| 299 |
+
[ 0, 65, 14, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0, 0, 34, 0, 0],
|
| 300 |
+
[ 0, 0, 0, 16, 39, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0],
|
| 301 |
+
[ 0, 17, 0, 0, 0, 99, 84, 13, 50, 84, 0, 0, 0, 0, 95, 0, 43, 33, 20, 0],
|
| 302 |
+
[79, 0, 17, 52, 99, 12, 69, 0, 98, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 303 |
+
[ 0, 0, 0, 82, 0, 44, 0, 0, 0, 97, 0, 0, 0, 0, 0, 10, 0, 0, 31, 0],
|
| 304 |
+
[ 0, 0, 21, 0, 67, 0, 0, 0, 0, 0, 4, 0, 50, 0, 0, 0, 33, 0, 0, 0],
|
| 305 |
+
[ 0, 0, 0, 0, 9, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8],
|
| 306 |
+
[ 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 34, 93, 0, 0, 0, 0, 47, 0, 0, 0]],
|
| 307 |
+
ZZ),
|
| 308 |
+
DM([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 309 |
+
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 310 |
+
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 311 |
+
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 312 |
+
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 313 |
+
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 314 |
+
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 315 |
+
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 316 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 317 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 318 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 319 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 320 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
| 321 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
| 322 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
| 323 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
| 324 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
| 325 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
| 326 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
| 327 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]], ZZ),
|
| 328 |
+
ZZ(1),
|
| 329 |
+
),
|
| 330 |
+
|
| 331 |
+
(
|
| 332 |
+
'zz_large_2',
|
| 333 |
+
DM([
|
| 334 |
+
[ 0, 0, 0, 0, 50, 0, 6, 81, 0, 1, 86, 0, 0, 98, 82, 94, 4, 0, 0, 29],
|
| 335 |
+
[ 0, 44, 43, 0, 62, 0, 0, 0, 60, 0, 0, 0, 0, 71, 9, 0, 57, 41, 0, 93],
|
| 336 |
+
[ 0, 0, 28, 0, 74, 89, 42, 0, 28, 0, 6, 0, 0, 0, 44, 0, 0, 0, 77, 19],
|
| 337 |
+
[ 0, 21, 82, 0, 30, 88, 0, 89, 68, 0, 0, 0, 79, 41, 0, 0, 99, 0, 0, 0],
|
| 338 |
+
[31, 0, 0, 0, 19, 64, 0, 0, 79, 0, 5, 0, 72, 10, 60, 32, 64, 59, 0, 24],
|
| 339 |
+
[ 0, 0, 0, 0, 0, 57, 0, 94, 0, 83, 20, 0, 0, 9, 31, 0, 49, 26, 58, 0],
|
| 340 |
+
[ 0, 65, 56, 31, 64, 0, 0, 0, 0, 0, 0, 52, 85, 0, 0, 0, 0, 51, 0, 0],
|
| 341 |
+
[ 0, 35, 0, 0, 0, 69, 0, 0, 64, 0, 0, 0, 0, 70, 0, 0, 90, 0, 75, 76],
|
| 342 |
+
[69, 7, 0, 90, 0, 0, 84, 0, 47, 69, 19, 20, 42, 0, 0, 32, 71, 35, 0, 0],
|
| 343 |
+
[39, 0, 90, 0, 0, 4, 85, 0, 0, 55, 0, 0, 0, 35, 67, 40, 0, 40, 0, 77],
|
| 344 |
+
[98, 63, 0, 71, 0, 50, 0, 2, 61, 0, 38, 0, 0, 0, 0, 75, 0, 40, 33, 56],
|
| 345 |
+
[ 0, 73, 0, 64, 0, 38, 0, 35, 61, 0, 0, 52, 0, 7, 0, 51, 0, 0, 0, 34],
|
| 346 |
+
[ 0, 0, 28, 0, 34, 5, 63, 45, 14, 42, 60, 16, 76, 54, 99, 0, 28, 30, 0, 0],
|
| 347 |
+
[58, 37, 14, 0, 0, 0, 94, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 8, 90, 53],
|
| 348 |
+
[86, 74, 94, 0, 49, 10, 60, 0, 40, 18, 0, 0, 0, 31, 60, 24, 0, 1, 0, 29],
|
| 349 |
+
[53, 0, 0, 97, 0, 0, 58, 0, 0, 39, 44, 47, 0, 0, 0, 12, 50, 0, 0, 11],
|
| 350 |
+
[ 4, 0, 92, 10, 28, 0, 0, 89, 0, 0, 18, 54, 23, 39, 0, 2, 0, 48, 0, 92],
|
| 351 |
+
[ 0, 0, 90, 77, 95, 33, 0, 0, 49, 22, 39, 0, 0, 0, 0, 0, 0, 40, 0, 0],
|
| 352 |
+
[96, 0, 0, 0, 0, 38, 86, 0, 22, 76, 0, 0, 0, 0, 83, 88, 95, 65, 72, 0],
|
| 353 |
+
[81, 65, 0, 4, 60, 0, 19, 0, 0, 68, 0, 0, 89, 0, 67, 22, 0, 0, 55, 33]],
|
| 354 |
+
ZZ),
|
| 355 |
+
DM([
|
| 356 |
+
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 357 |
+
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 358 |
+
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 359 |
+
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 360 |
+
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 361 |
+
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 362 |
+
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 363 |
+
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 364 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 365 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 366 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 367 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
| 368 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
| 369 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
| 370 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
| 371 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
| 372 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
| 373 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
| 374 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
| 375 |
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]],
|
| 376 |
+
ZZ),
|
| 377 |
+
ZZ(1),
|
| 378 |
+
),
|
| 379 |
+
|
| 380 |
+
(
|
| 381 |
+
'zz_large_3',
|
| 382 |
+
DM([
|
| 383 |
+
[62,35,89,58,22,47,30,28,52,72,17,56,80,26,64,21,10,35,24,42,96,32,23,50,92,37,76,94,63,66],
|
| 384 |
+
[20,47,96,34,10,98,19,6,29,2,19,92,61,94,38,41,32,9,5,94,31,58,27,41,72,85,61,62,40,46],
|
| 385 |
+
[69,26,35,68,25,52,94,13,38,65,81,10,29,15,5,4,13,99,85,0,80,51,60,60,26,77,85,2,87,25],
|
| 386 |
+
[99,58,69,15,52,12,18,7,27,56,12,54,21,92,38,95,33,83,28,1,44,8,29,84,92,12,2,25,46,46],
|
| 387 |
+
[93,13,55,48,35,87,24,40,23,35,25,32,0,19,0,85,4,79,26,11,46,75,7,96,76,11,7,57,99,75],
|
| 388 |
+
[128,85,26,51,161,173,77,78,85,103,123,58,91,147,38,91,161,36,123,81,102,25,75,59,17,150,112,65,77,143],
|
| 389 |
+
[15,59,61,82,12,83,34,8,94,71,66,7,91,21,48,69,26,12,64,38,97,87,38,15,51,33,93,43,66,89],
|
| 390 |
+
[74,74,53,39,69,90,41,80,32,66,40,83,87,87,61,38,12,80,24,49,37,90,19,33,56,0,46,57,56,60],
|
| 391 |
+
[82,11,0,25,56,58,39,49,92,93,80,38,19,62,33,85,19,61,14,30,45,91,97,34,97,53,92,28,33,43],
|
| 392 |
+
[83,79,41,16,95,35,53,45,26,4,71,76,61,69,69,72,87,92,59,72,54,11,22,83,8,57,77,55,19,22],
|
| 393 |
+
[49,34,13,31,72,77,52,70,46,41,37,6,42,66,35,6,75,33,62,57,30,14,26,31,9,95,89,13,12,90],
|
| 394 |
+
[29,3,49,30,51,32,77,41,38,50,16,1,87,81,93,88,58,91,83,0,38,67,29,64,60,84,5,60,23,28],
|
| 395 |
+
[79,51,13,20,89,96,25,8,39,62,86,52,49,81,3,85,86,3,61,24,72,11,49,28,8,55,23,52,65,53],
|
| 396 |
+
[96,86,73,20,41,20,37,18,10,61,85,24,40,83,69,41,4,92,23,99,64,33,18,36,32,56,60,98,39,24],
|
| 397 |
+
[32,62,47,80,51,66,17,1,9,30,65,75,75,88,99,92,64,53,53,86,38,51,41,14,35,18,39,25,26,32],
|
| 398 |
+
[39,21,8,16,33,6,35,85,75,62,43,34,18,68,71,28,32,18,12,0,81,53,1,99,3,5,45,99,35,33],
|
| 399 |
+
[19,95,89,45,75,94,92,5,84,93,34,17,50,56,79,98,68,82,65,81,51,90,5,95,33,71,46,61,14,7],
|
| 400 |
+
[53,92,8,49,67,84,21,79,49,95,66,48,36,14,62,97,26,45,58,31,83,48,11,89,67,72,91,34,56,89],
|
| 401 |
+
[56,76,99,92,40,8,0,16,15,48,35,72,91,46,81,14,86,60,51,7,33,12,53,78,48,21,3,89,15,79],
|
| 402 |
+
[81,43,33,49,6,49,36,32,57,74,87,91,17,37,31,17,67,1,40,38,69,8,3,48,59,37,64,97,11,3],
|
| 403 |
+
[98,48,77,16,2,48,57,38,63,59,79,35,16,71,60,86,71,41,14,76,80,97,77,69,4,58,22,55,26,73],
|
| 404 |
+
[80,47,78,44,31,48,47,29,29,62,19,21,17,24,19,3,53,93,97,57,13,54,12,10,77,66,60,75,32,21],
|
| 405 |
+
[86,63,2,13,71,38,86,23,18,15,91,65,77,65,9,92,50,0,17,42,99,80,99,27,10,99,92,9,87,84],
|
| 406 |
+
[66,27,72,13,13,15,72,75,39,3,14,71,15,68,10,19,49,54,11,29,47,20,63,13,97,47,24,62,16,96],
|
| 407 |
+
[42,63,83,60,49,68,9,53,75,87,40,25,12,63,0,12,0,95,46,46,55,25,89,1,51,1,1,96,80,52],
|
| 408 |
+
[35,9,97,13,86,39,66,48,41,57,23,38,11,9,35,72,88,13,41,60,10,64,71,23,1,5,23,57,6,19],
|
| 409 |
+
[70,61,5,50,72,60,77,13,41,94,1,45,52,22,99,47,27,18,99,42,16,48,26,9,88,77,10,94,11,92],
|
| 410 |
+
[55,68,58,2,72,56,81,52,79,37,1,40,21,46,27,60,37,13,97,42,85,98,69,60,76,44,42,46,29,73],
|
| 411 |
+
[73,0,43,17,89,97,45,2,68,14,55,60,95,2,74,85,88,68,93,76,38,76,2,51,45,76,50,79,56,18],
|
| 412 |
+
[72,58,41,39,24,80,23,79,44,7,98,75,30,6,85,60,20,58,77,71,90,51,38,80,30,15,33,10,82,8]],
|
| 413 |
+
ZZ),
|
| 414 |
+
Matrix([
|
| 415 |
+
[eye(29) * 2028539767964472550625641331179545072876560857886207583101,
|
| 416 |
+
Matrix([ 4260575808093245475167216057435155595594339172099000182569,
|
| 417 |
+
169148395880755256182802335904188369274227936894862744452,
|
| 418 |
+
4915975976683942569102447281579134986891620721539038348914,
|
| 419 |
+
6113916866367364958834844982578214901958429746875633283248,
|
| 420 |
+
5585689617819894460378537031623265659753379011388162534838,
|
| 421 |
+
359776822829880747716695359574308645968094838905181892423,
|
| 422 |
+
-2800926112141776386671436511182421432449325232461665113305,
|
| 423 |
+
941642292388230001722444876624818265766384442910688463158,
|
| 424 |
+
3648811843256146649321864698600908938933015862008642023935,
|
| 425 |
+
-4104526163246702252932955226754097174212129127510547462419,
|
| 426 |
+
-704814955438106792441896903238080197619233342348191408078,
|
| 427 |
+
1640882266829725529929398131287244562048075707575030019335,
|
| 428 |
+
-4068330845192910563212155694231438198040299927120544468520,
|
| 429 |
+
136589038308366497790495711534532612862715724187671166593,
|
| 430 |
+
2544937011460702462290799932536905731142196510605191645593,
|
| 431 |
+
755591839174293940486133926192300657264122907519174116472,
|
| 432 |
+
-3683838489869297144348089243628436188645897133242795965021,
|
| 433 |
+
-522207137101161299969706310062775465103537953077871128403,
|
| 434 |
+
-2260451796032703984456606059649402832441331339246756656334,
|
| 435 |
+
-6476809325293587953616004856993300606040336446656916663680,
|
| 436 |
+
3521944238996782387785653800944972787867472610035040989081,
|
| 437 |
+
2270762115788407950241944504104975551914297395787473242379,
|
| 438 |
+
-3259947194628712441902262570532921252128444706733549251156,
|
| 439 |
+
-5624569821491886970999097239695637132075823246850431083557,
|
| 440 |
+
-3262698255682055804320585332902837076064075936601504555698,
|
| 441 |
+
5786719943788937667411185880136324396357603606944869545501,
|
| 442 |
+
-955257841973865996077323863289453200904051299086000660036,
|
| 443 |
+
-1294235552446355326174641248209752679127075717918392702116,
|
| 444 |
+
-3718353510747301598130831152458342785269166356215331448279,
|
| 445 |
+
]),],
|
| 446 |
+
[zeros(1, 29), zeros(1, 1)],
|
| 447 |
+
]).to_DM().to_dense(),
|
| 448 |
+
ZZ(2028539767964472550625641331179545072876560857886207583101),
|
| 449 |
+
),
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
(
|
| 453 |
+
'qq_1',
|
| 454 |
+
DM([[(1,2), 0], [0, 2]], QQ),
|
| 455 |
+
DM([[1, 0], [0, 1]], QQ),
|
| 456 |
+
QQ(1),
|
| 457 |
+
),
|
| 458 |
+
|
| 459 |
+
(
|
| 460 |
+
# Standard square case
|
| 461 |
+
'qq_2',
|
| 462 |
+
DM([[0, 1],
|
| 463 |
+
[1, 1]], QQ),
|
| 464 |
+
DM([[1, 0],
|
| 465 |
+
[0, 1]], QQ),
|
| 466 |
+
QQ(1),
|
| 467 |
+
),
|
| 468 |
+
|
| 469 |
+
(
|
| 470 |
+
# m < n case
|
| 471 |
+
'qq_3',
|
| 472 |
+
DM([[1, 2, 1],
|
| 473 |
+
[3, 4, 1]], QQ),
|
| 474 |
+
DM([[1, 0, -1],
|
| 475 |
+
[0, 1, 1]], QQ),
|
| 476 |
+
QQ(1),
|
| 477 |
+
),
|
| 478 |
+
|
| 479 |
+
(
|
| 480 |
+
# same m < n but reversed
|
| 481 |
+
'qq_4',
|
| 482 |
+
DM([[3, 4, 1],
|
| 483 |
+
[1, 2, 1]], QQ),
|
| 484 |
+
DM([[1, 0, -1],
|
| 485 |
+
[0, 1, 1]], QQ),
|
| 486 |
+
QQ(1),
|
| 487 |
+
),
|
| 488 |
+
|
| 489 |
+
(
|
| 490 |
+
# m > n case
|
| 491 |
+
'qq_5',
|
| 492 |
+
DM([[1, 0],
|
| 493 |
+
[1, 3],
|
| 494 |
+
[0, 1]], QQ),
|
| 495 |
+
DM([[1, 0],
|
| 496 |
+
[0, 1],
|
| 497 |
+
[0, 0]], QQ),
|
| 498 |
+
QQ(1),
|
| 499 |
+
),
|
| 500 |
+
|
| 501 |
+
(
|
| 502 |
+
# Example with missing pivot
|
| 503 |
+
'qq_6',
|
| 504 |
+
DM([[1, 0, 1],
|
| 505 |
+
[3, 0, 1]], QQ),
|
| 506 |
+
DM([[1, 0, 0],
|
| 507 |
+
[0, 0, 1]], QQ),
|
| 508 |
+
QQ(1),
|
| 509 |
+
),
|
| 510 |
+
|
| 511 |
+
(
|
| 512 |
+
# This is intended to trigger the threshold where we give up on
|
| 513 |
+
# clearing denominators.
|
| 514 |
+
'qq_large_1',
|
| 515 |
+
qq_large_1,
|
| 516 |
+
DomainMatrix.eye(11, QQ).to_dense(),
|
| 517 |
+
QQ(1),
|
| 518 |
+
),
|
| 519 |
+
|
| 520 |
+
(
|
| 521 |
+
# This is intended to trigger the threshold where we use rref_den over
|
| 522 |
+
# QQ.
|
| 523 |
+
'qq_large_2',
|
| 524 |
+
qq_large_2,
|
| 525 |
+
DomainMatrix.eye(11, QQ).to_dense(),
|
| 526 |
+
QQ(1),
|
| 527 |
+
),
|
| 528 |
+
|
| 529 |
+
(
|
| 530 |
+
# Example with missing pivot and no replacement
|
| 531 |
+
|
| 532 |
+
# This example is just enough to show a different result from the dense
|
| 533 |
+
# and sparse versions of the algorithm:
|
| 534 |
+
#
|
| 535 |
+
# >>> A = Matrix([[0, 1], [0, 2], [1, 0]])
|
| 536 |
+
# >>> A.to_DM().to_sparse().rref_den()[0].to_Matrix()
|
| 537 |
+
# Matrix([
|
| 538 |
+
# [1, 0],
|
| 539 |
+
# [0, 1],
|
| 540 |
+
# [0, 0]])
|
| 541 |
+
# >>> A.to_DM().to_dense().rref_den()[0].to_Matrix()
|
| 542 |
+
# Matrix([
|
| 543 |
+
# [2, 0],
|
| 544 |
+
# [0, 2],
|
| 545 |
+
# [0, 0]])
|
| 546 |
+
#
|
| 547 |
+
'qq_7',
|
| 548 |
+
DM([[0, 1],
|
| 549 |
+
[0, 2],
|
| 550 |
+
[1, 0]], QQ),
|
| 551 |
+
DM([[1, 0],
|
| 552 |
+
[0, 1],
|
| 553 |
+
[0, 0]], QQ),
|
| 554 |
+
QQ(1),
|
| 555 |
+
),
|
| 556 |
+
|
| 557 |
+
(
|
| 558 |
+
# Gaussian integers
|
| 559 |
+
'zz_i_1',
|
| 560 |
+
DM([[(0,1), 1, 1],
|
| 561 |
+
[ 1, 1, 1]], ZZ_I),
|
| 562 |
+
DM([[1, 0, 0],
|
| 563 |
+
[0, 1, 1]], ZZ_I),
|
| 564 |
+
ZZ_I(1),
|
| 565 |
+
),
|
| 566 |
+
|
| 567 |
+
(
|
| 568 |
+
# EX: test_issue_23718
|
| 569 |
+
'EX_1',
|
| 570 |
+
DM([
|
| 571 |
+
[a, b, 1],
|
| 572 |
+
[c, d, 1]], EX),
|
| 573 |
+
DM([[a*d - b*c, 0, -b + d],
|
| 574 |
+
[ 0, a*d - b*c, a - c]], EX),
|
| 575 |
+
EX(a*d - b*c),
|
| 576 |
+
),
|
| 577 |
+
|
| 578 |
+
]
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
def _to_DM(A, ans):
|
| 582 |
+
"""Convert the answer to DomainMatrix."""
|
| 583 |
+
if isinstance(A, DomainMatrix):
|
| 584 |
+
return A.to_dense()
|
| 585 |
+
elif isinstance(A, Matrix):
|
| 586 |
+
return A.to_DM(ans.domain).to_dense()
|
| 587 |
+
|
| 588 |
+
if not (hasattr(A, 'shape') and hasattr(A, 'domain')):
|
| 589 |
+
shape, domain = ans.shape, ans.domain
|
| 590 |
+
else:
|
| 591 |
+
shape, domain = A.shape, A.domain
|
| 592 |
+
|
| 593 |
+
if isinstance(A, (DDM, list)):
|
| 594 |
+
return DomainMatrix(list(A), shape, domain).to_dense()
|
| 595 |
+
elif isinstance(A, (SDM, dict)):
|
| 596 |
+
return DomainMatrix(dict(A), shape, domain).to_dense()
|
| 597 |
+
else:
|
| 598 |
+
assert False # pragma: no cover
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
def _pivots(A_rref):
|
| 602 |
+
"""Return the pivots from the rref of A."""
|
| 603 |
+
return tuple(sorted(map(min, A_rref.to_sdm().values())))
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
def _check_cancel(result, rref_ans, den_ans):
|
| 607 |
+
"""Check the cancelled result."""
|
| 608 |
+
rref, den, pivots = result
|
| 609 |
+
if isinstance(rref, (DDM, SDM, list, dict)):
|
| 610 |
+
assert type(pivots) is list
|
| 611 |
+
pivots = tuple(pivots)
|
| 612 |
+
rref = _to_DM(rref, rref_ans)
|
| 613 |
+
rref2, den2 = rref.cancel_denom(den)
|
| 614 |
+
assert rref2 == rref_ans
|
| 615 |
+
assert den2 == den_ans
|
| 616 |
+
assert pivots == _pivots(rref)
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
def _check_divide(result, rref_ans, den_ans):
|
| 620 |
+
"""Check the divided result."""
|
| 621 |
+
rref, pivots = result
|
| 622 |
+
if isinstance(rref, (DDM, SDM, list, dict)):
|
| 623 |
+
assert type(pivots) is list
|
| 624 |
+
pivots = tuple(pivots)
|
| 625 |
+
rref_ans = rref_ans.to_field() / den_ans
|
| 626 |
+
rref = _to_DM(rref, rref_ans)
|
| 627 |
+
assert rref == rref_ans
|
| 628 |
+
assert _pivots(rref) == pivots
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 632 |
+
def test_Matrix_rref(name, A, A_rref, den):
|
| 633 |
+
K = A.domain
|
| 634 |
+
A = A.to_Matrix()
|
| 635 |
+
A_rref_found, pivots = A.rref()
|
| 636 |
+
if K.is_EX:
|
| 637 |
+
A_rref_found = A_rref_found.expand()
|
| 638 |
+
_check_divide((A_rref_found, pivots), A_rref, den)
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 642 |
+
def test_dm_dense_rref(name, A, A_rref, den):
|
| 643 |
+
A = A.to_field()
|
| 644 |
+
_check_divide(A.rref(), A_rref, den)
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 648 |
+
def test_dm_dense_rref_den(name, A, A_rref, den):
|
| 649 |
+
_check_cancel(A.rref_den(), A_rref, den)
|
| 650 |
+
|
| 651 |
+
|
| 652 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 653 |
+
def test_dm_sparse_rref(name, A, A_rref, den):
|
| 654 |
+
A = A.to_field().to_sparse()
|
| 655 |
+
_check_divide(A.rref(), A_rref, den)
|
| 656 |
+
|
| 657 |
+
|
| 658 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 659 |
+
def test_dm_sparse_rref_den(name, A, A_rref, den):
|
| 660 |
+
A = A.to_sparse()
|
| 661 |
+
_check_cancel(A.rref_den(), A_rref, den)
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 665 |
+
def test_dm_sparse_rref_den_keep_domain(name, A, A_rref, den):
|
| 666 |
+
A = A.to_sparse()
|
| 667 |
+
A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False)
|
| 668 |
+
A_rref_f = A_rref_f.to_field() / den_f
|
| 669 |
+
_check_divide((A_rref_f, pivots_f), A_rref, den)
|
| 670 |
+
|
| 671 |
+
|
| 672 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 673 |
+
def test_dm_sparse_rref_den_keep_domain_CD(name, A, A_rref, den):
|
| 674 |
+
A = A.to_sparse()
|
| 675 |
+
A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False, method='CD')
|
| 676 |
+
A_rref_f = A_rref_f.to_field() / den_f
|
| 677 |
+
_check_divide((A_rref_f, pivots_f), A_rref, den)
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 681 |
+
def test_dm_sparse_rref_den_keep_domain_GJ(name, A, A_rref, den):
|
| 682 |
+
A = A.to_sparse()
|
| 683 |
+
A_rref_f, den_f, pivots_f = A.rref_den(keep_domain=False, method='GJ')
|
| 684 |
+
A_rref_f = A_rref_f.to_field() / den_f
|
| 685 |
+
_check_divide((A_rref_f, pivots_f), A_rref, den)
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 689 |
+
def test_ddm_rref_den(name, A, A_rref, den):
|
| 690 |
+
A = A.to_ddm()
|
| 691 |
+
_check_cancel(A.rref_den(), A_rref, den)
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 695 |
+
def test_sdm_rref_den(name, A, A_rref, den):
|
| 696 |
+
A = A.to_sdm()
|
| 697 |
+
_check_cancel(A.rref_den(), A_rref, den)
|
| 698 |
+
|
| 699 |
+
|
| 700 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 701 |
+
def test_ddm_rref(name, A, A_rref, den):
|
| 702 |
+
A = A.to_field().to_ddm()
|
| 703 |
+
_check_divide(A.rref(), A_rref, den)
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 707 |
+
def test_sdm_rref(name, A, A_rref, den):
|
| 708 |
+
A = A.to_field().to_sdm()
|
| 709 |
+
_check_divide(A.rref(), A_rref, den)
|
| 710 |
+
|
| 711 |
+
|
| 712 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 713 |
+
def test_ddm_irref(name, A, A_rref, den):
|
| 714 |
+
A = A.to_field().to_ddm().copy()
|
| 715 |
+
pivots_found = ddm_irref(A)
|
| 716 |
+
_check_divide((A, pivots_found), A_rref, den)
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 720 |
+
def test_ddm_irref_den(name, A, A_rref, den):
|
| 721 |
+
A = A.to_ddm().copy()
|
| 722 |
+
(den_found, pivots_found) = ddm_irref_den(A, A.domain)
|
| 723 |
+
result = (A, den_found, pivots_found)
|
| 724 |
+
_check_cancel(result, A_rref, den)
|
| 725 |
+
|
| 726 |
+
|
| 727 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 728 |
+
def test_sparse_sdm_rref(name, A, A_rref, den):
|
| 729 |
+
A = A.to_field().to_sdm()
|
| 730 |
+
_check_divide(sdm_irref(A)[:2], A_rref, den)
|
| 731 |
+
|
| 732 |
+
|
| 733 |
+
@pytest.mark.parametrize('name, A, A_rref, den', RREF_EXAMPLES)
|
| 734 |
+
def test_sparse_sdm_rref_den(name, A, A_rref, den):
|
| 735 |
+
A = A.to_sdm().copy()
|
| 736 |
+
K = A.domain
|
| 737 |
+
_check_cancel(sdm_rref_den(A, K), A_rref, den)
|