MTerryJack commited on
Commit
0ab9f35
·
verified ·
1 Parent(s): be3d9a3

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .venv/lib/python3.13/site-packages/sympy/core/tests/test_basic.py +343 -0
  2. .venv/lib/python3.13/site-packages/sympy/core/tests/test_kind.py +57 -0
  3. .venv/lib/python3.13/site-packages/sympy/core/tests/test_multidimensional.py +24 -0
  4. .venv/lib/python3.13/site-packages/sympy/core/tests/test_singleton.py +76 -0
  5. .venv/lib/python3.13/site-packages/sympy/polys/agca/__init__.py +5 -0
  6. .venv/lib/python3.13/site-packages/sympy/polys/agca/extensions.py +356 -0
  7. .venv/lib/python3.13/site-packages/sympy/polys/agca/homomorphisms.py +691 -0
  8. .venv/lib/python3.13/site-packages/sympy/polys/agca/ideals.py +395 -0
  9. .venv/lib/python3.13/site-packages/sympy/polys/agca/modules.py +1488 -0
  10. .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/__init__.py +0 -0
  11. .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_extensions.py +196 -0
  12. .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_homomorphisms.py +113 -0
  13. .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_ideals.py +131 -0
  14. .venv/lib/python3.13/site-packages/sympy/polys/agca/tests/test_modules.py +408 -0
  15. .venv/lib/python3.13/site-packages/sympy/polys/benchmarks/__init__.py +0 -0
  16. .venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_galoispolys.py +66 -0
  17. .venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_groebnertools.py +25 -0
  18. .venv/lib/python3.13/site-packages/sympy/polys/benchmarks/bench_solvers.py +0 -0
  19. .venv/lib/python3.13/site-packages/sympy/polys/domains/tests/__init__.py +0 -0
  20. .venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_domains.py +1434 -0
  21. .venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_polynomialring.py +93 -0
  22. .venv/lib/python3.13/site-packages/sympy/polys/domains/tests/test_quotientring.py +52 -0
  23. .venv/lib/python3.13/site-packages/sympy/polys/matrices/__init__.py +15 -0
  24. .venv/lib/python3.13/site-packages/sympy/polys/matrices/_dfm.py +951 -0
  25. .venv/lib/python3.13/site-packages/sympy/polys/matrices/_typing.py +16 -0
  26. .venv/lib/python3.13/site-packages/sympy/polys/matrices/ddm.py +1176 -0
  27. .venv/lib/python3.13/site-packages/sympy/polys/matrices/dense.py +824 -0
  28. .venv/lib/python3.13/site-packages/sympy/polys/matrices/dfm.py +35 -0
  29. .venv/lib/python3.13/site-packages/sympy/polys/matrices/domainmatrix.py +0 -0
  30. .venv/lib/python3.13/site-packages/sympy/polys/matrices/domainscalar.py +122 -0
  31. .venv/lib/python3.13/site-packages/sympy/polys/matrices/eigen.py +90 -0
  32. .venv/lib/python3.13/site-packages/sympy/polys/matrices/exceptions.py +67 -0
  33. .venv/lib/python3.13/site-packages/sympy/polys/matrices/linsolve.py +230 -0
  34. .venv/lib/python3.13/site-packages/sympy/polys/matrices/lll.py +94 -0
  35. .venv/lib/python3.13/site-packages/sympy/polys/matrices/normalforms.py +540 -0
  36. .venv/lib/python3.13/site-packages/sympy/polys/matrices/rref.py +422 -0
  37. .venv/lib/python3.13/site-packages/sympy/polys/matrices/sdm.py +2197 -0
  38. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/__init__.py +0 -0
  39. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_ddm.py +558 -0
  40. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_dense.py +350 -0
  41. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_domainmatrix.py +1383 -0
  42. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_domainscalar.py +153 -0
  43. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_eigen.py +90 -0
  44. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_fflu.py +301 -0
  45. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_inverse.py +193 -0
  46. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_linsolve.py +112 -0
  47. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_lll.py +145 -0
  48. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_normalforms.py +156 -0
  49. .venv/lib/python3.13/site-packages/sympy/polys/matrices/tests/test_nullspace.py +209 -0
  50. .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)