passagemath-meataxe 10.6.31rc3__cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of passagemath-meataxe might be problematic. Click here for more details.
- passagemath_meataxe-10.6.31rc3.dist-info/METADATA +97 -0
- passagemath_meataxe-10.6.31rc3.dist-info/METADATA.bak +98 -0
- passagemath_meataxe-10.6.31rc3.dist-info/RECORD +84 -0
- passagemath_meataxe-10.6.31rc3.dist-info/WHEEL +7 -0
- passagemath_meataxe-10.6.31rc3.dist-info/top_level.txt +2 -0
- passagemath_meataxe.libs/libmtx-dd5e7e28.so.0.0.0 +0 -0
- sage/all__sagemath_meataxe.py +2 -0
- sage/libs/all__sagemath_meataxe.py +1 -0
- sage/libs/meataxe.cpython-312-aarch64-linux-gnu.so +0 -0
- sage/libs/meataxe.pxd +156 -0
- sage/libs/meataxe.pyx +95 -0
- sage/matrix/all__sagemath_meataxe.py +1 -0
- sage/matrix/matrix_gfpn_dense.cpython-312-aarch64-linux-gnu.so +0 -0
- sage/matrix/matrix_gfpn_dense.pxd +37 -0
- sage/matrix/matrix_gfpn_dense.pyx +1960 -0
- sage_wheels/share/meataxe/p002.zzz +0 -0
- sage_wheels/share/meataxe/p003.zzz +0 -0
- sage_wheels/share/meataxe/p004.zzz +0 -0
- sage_wheels/share/meataxe/p005.zzz +0 -0
- sage_wheels/share/meataxe/p007.zzz +0 -0
- sage_wheels/share/meataxe/p008.zzz +0 -0
- sage_wheels/share/meataxe/p009.zzz +0 -0
- sage_wheels/share/meataxe/p011.zzz +0 -0
- sage_wheels/share/meataxe/p013.zzz +0 -0
- sage_wheels/share/meataxe/p016.zzz +0 -0
- sage_wheels/share/meataxe/p017.zzz +0 -0
- sage_wheels/share/meataxe/p019.zzz +0 -0
- sage_wheels/share/meataxe/p023.zzz +0 -0
- sage_wheels/share/meataxe/p025.zzz +0 -0
- sage_wheels/share/meataxe/p027.zzz +0 -0
- sage_wheels/share/meataxe/p029.zzz +0 -0
- sage_wheels/share/meataxe/p031.zzz +0 -0
- sage_wheels/share/meataxe/p032.zzz +0 -0
- sage_wheels/share/meataxe/p037.zzz +0 -0
- sage_wheels/share/meataxe/p041.zzz +0 -0
- sage_wheels/share/meataxe/p043.zzz +0 -0
- sage_wheels/share/meataxe/p047.zzz +0 -0
- sage_wheels/share/meataxe/p049.zzz +0 -0
- sage_wheels/share/meataxe/p053.zzz +0 -0
- sage_wheels/share/meataxe/p059.zzz +0 -0
- sage_wheels/share/meataxe/p061.zzz +0 -0
- sage_wheels/share/meataxe/p064.zzz +0 -0
- sage_wheels/share/meataxe/p067.zzz +0 -0
- sage_wheels/share/meataxe/p071.zzz +0 -0
- sage_wheels/share/meataxe/p073.zzz +0 -0
- sage_wheels/share/meataxe/p079.zzz +0 -0
- sage_wheels/share/meataxe/p081.zzz +0 -0
- sage_wheels/share/meataxe/p083.zzz +0 -0
- sage_wheels/share/meataxe/p089.zzz +0 -0
- sage_wheels/share/meataxe/p097.zzz +0 -0
- sage_wheels/share/meataxe/p101.zzz +0 -0
- sage_wheels/share/meataxe/p103.zzz +0 -0
- sage_wheels/share/meataxe/p107.zzz +0 -0
- sage_wheels/share/meataxe/p109.zzz +0 -0
- sage_wheels/share/meataxe/p113.zzz +0 -0
- sage_wheels/share/meataxe/p121.zzz +0 -0
- sage_wheels/share/meataxe/p125.zzz +0 -0
- sage_wheels/share/meataxe/p127.zzz +0 -0
- sage_wheels/share/meataxe/p128.zzz +0 -0
- sage_wheels/share/meataxe/p131.zzz +0 -0
- sage_wheels/share/meataxe/p137.zzz +0 -0
- sage_wheels/share/meataxe/p139.zzz +0 -0
- sage_wheels/share/meataxe/p149.zzz +0 -0
- sage_wheels/share/meataxe/p151.zzz +0 -0
- sage_wheels/share/meataxe/p157.zzz +0 -0
- sage_wheels/share/meataxe/p163.zzz +0 -0
- sage_wheels/share/meataxe/p167.zzz +0 -0
- sage_wheels/share/meataxe/p169.zzz +0 -0
- sage_wheels/share/meataxe/p173.zzz +0 -0
- sage_wheels/share/meataxe/p179.zzz +0 -0
- sage_wheels/share/meataxe/p181.zzz +0 -0
- sage_wheels/share/meataxe/p191.zzz +0 -0
- sage_wheels/share/meataxe/p193.zzz +0 -0
- sage_wheels/share/meataxe/p197.zzz +0 -0
- sage_wheels/share/meataxe/p199.zzz +0 -0
- sage_wheels/share/meataxe/p211.zzz +0 -0
- sage_wheels/share/meataxe/p223.zzz +0 -0
- sage_wheels/share/meataxe/p227.zzz +0 -0
- sage_wheels/share/meataxe/p229.zzz +0 -0
- sage_wheels/share/meataxe/p233.zzz +0 -0
- sage_wheels/share/meataxe/p239.zzz +0 -0
- sage_wheels/share/meataxe/p241.zzz +0 -0
- sage_wheels/share/meataxe/p243.zzz +0 -0
- sage_wheels/share/meataxe/p251.zzz +0 -0
|
@@ -0,0 +1,1960 @@
|
|
|
1
|
+
# distutils: libraries = mtx
|
|
2
|
+
# sage_setup: distribution = sagemath-meataxe
|
|
3
|
+
# sage.doctest: optional - meataxe
|
|
4
|
+
|
|
5
|
+
r"""
|
|
6
|
+
Dense Matrices over `\mathbb F_q`, with `q<255`.
|
|
7
|
+
|
|
8
|
+
This module is a wrapper for the `Aachen C-MeatAxe library <../spkg/meataxe.html>`_,
|
|
9
|
+
improved by an implementation of the Winograd-Strassen multiplication
|
|
10
|
+
algorithm. It provides matrices over the finite field `\mathbb F_q`,
|
|
11
|
+
where `q\le 255`.
|
|
12
|
+
|
|
13
|
+
By default, it is only used when `q` is odd and not prime, because other
|
|
14
|
+
matrix implementations in SageMath perform better for prime fields or in
|
|
15
|
+
characteristic two.
|
|
16
|
+
|
|
17
|
+
.. NOTE::
|
|
18
|
+
|
|
19
|
+
The examples shown here will only work when the `meataxe
|
|
20
|
+
<../spkg/meataxe.html>` package has been installed.
|
|
21
|
+
|
|
22
|
+
AUTHORS:
|
|
23
|
+
|
|
24
|
+
- Simon King (2015-09): initial version
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# ***************************************************************************
|
|
28
|
+
# Copyright (C) 2015 Simon King <simon.king@uni-jena.de>
|
|
29
|
+
#
|
|
30
|
+
# This program is free software: you can redistribute it and/or modify
|
|
31
|
+
# it under the terms of the GNU General Public License as published by
|
|
32
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
33
|
+
# (at your option) any later version.
|
|
34
|
+
# https://www.gnu.org/licenses/
|
|
35
|
+
# ***************************************************************************
|
|
36
|
+
from cysignals.memory cimport check_realloc, check_malloc, sig_free
|
|
37
|
+
from cpython.bytes cimport PyBytes_AsString, PyBytes_FromStringAndSize
|
|
38
|
+
from cysignals.signals cimport sig_on, sig_off, sig_check
|
|
39
|
+
cimport cython
|
|
40
|
+
|
|
41
|
+
####################
|
|
42
|
+
#
|
|
43
|
+
# import sage types
|
|
44
|
+
#
|
|
45
|
+
####################
|
|
46
|
+
|
|
47
|
+
from sage.cpython.string import FS_ENCODING
|
|
48
|
+
from sage.rings.integer import Integer
|
|
49
|
+
from sage.rings.finite_rings.finite_field_constructor import GF
|
|
50
|
+
from sage.rings.finite_rings.integer_mod import IntegerMod_int
|
|
51
|
+
from sage.matrix.matrix_space import MatrixSpace
|
|
52
|
+
from sage.misc.randstate import current_randstate
|
|
53
|
+
from sage.misc.randstate cimport randstate
|
|
54
|
+
from sage.structure.element cimport Element, Matrix
|
|
55
|
+
from sage.structure.richcmp import rich_to_bool
|
|
56
|
+
from sage.matrix.args cimport MatrixArgs_init
|
|
57
|
+
|
|
58
|
+
from libc.string cimport memset, memcpy
|
|
59
|
+
|
|
60
|
+
cimport sage.matrix.matrix0
|
|
61
|
+
|
|
62
|
+
# The following import is just to ensure that meataxe_init() is called.
|
|
63
|
+
import sage.libs.meataxe
|
|
64
|
+
|
|
65
|
+
####################
|
|
66
|
+
#
|
|
67
|
+
# auxiliary functions
|
|
68
|
+
#
|
|
69
|
+
####################
|
|
70
|
+
|
|
71
|
+
# Fast conversion from field to int and int to field
|
|
72
|
+
cdef class FieldConverter_class:
|
|
73
|
+
"""
|
|
74
|
+
An auxiliary class, used to convert between meataxe ``FEL`` and
|
|
75
|
+
finite field elements.
|
|
76
|
+
|
|
77
|
+
This class is for Givaro finite fields only. It exists in order to
|
|
78
|
+
have a common interface for elements of prime and non-prime fields;
|
|
79
|
+
see :class:`PrimeFieldConverter_class`.
|
|
80
|
+
|
|
81
|
+
.. NOTE::
|
|
82
|
+
|
|
83
|
+
This class is really meant to be used only from Cython.
|
|
84
|
+
We do make the methods available from Python, but that is
|
|
85
|
+
for testing only.
|
|
86
|
+
|
|
87
|
+
.. WARNING::
|
|
88
|
+
|
|
89
|
+
Before calling the ``fel_to_field`` or ``field_to_fel`` methods,
|
|
90
|
+
one should call the ``FfSetField`` function.
|
|
91
|
+
|
|
92
|
+
EXAMPLES::
|
|
93
|
+
|
|
94
|
+
sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense
|
|
95
|
+
sage: F.<y> = GF(125)
|
|
96
|
+
sage: M = MatrixSpace(F, 2, 2, implementation=Matrix_gfpn_dense).one()
|
|
97
|
+
sage: C = M._converter
|
|
98
|
+
sage: C.fel_to_field(15)
|
|
99
|
+
3*y
|
|
100
|
+
sage: F.from_integer(15)
|
|
101
|
+
3*y
|
|
102
|
+
sage: C.field_to_fel(y)
|
|
103
|
+
5
|
|
104
|
+
sage: y.to_integer()
|
|
105
|
+
5
|
|
106
|
+
"""
|
|
107
|
+
def __init__(self, field):
|
|
108
|
+
"""
|
|
109
|
+
INPUT:
|
|
110
|
+
|
|
111
|
+
- ``field`` -- a finite field with Givaro implementation and at most 251
|
|
112
|
+
elements. These assumptions are not tested.
|
|
113
|
+
|
|
114
|
+
EXAMPLES::
|
|
115
|
+
|
|
116
|
+
sage: from sage.matrix.matrix_gfpn_dense import FieldConverter_class
|
|
117
|
+
sage: FieldConverter_class(GF(13^2))
|
|
118
|
+
<sage.matrix.matrix_gfpn_dense.FieldConverter_class object at ...>
|
|
119
|
+
"""
|
|
120
|
+
self.field = field._cache.fetch_int
|
|
121
|
+
self.zero_FEL = self.field_to_fel(field.zero())
|
|
122
|
+
|
|
123
|
+
cpdef fel_to_field(self, FEL x) noexcept:
|
|
124
|
+
"""
|
|
125
|
+
Fetch a python int into the field.
|
|
126
|
+
|
|
127
|
+
EXAMPLES::
|
|
128
|
+
|
|
129
|
+
sage: from sage.matrix.matrix_gfpn_dense import FieldConverter_class
|
|
130
|
+
sage: F.<y> = GF(125)
|
|
131
|
+
sage: C = FieldConverter_class(F)
|
|
132
|
+
sage: C.fel_to_field(15)
|
|
133
|
+
3*y
|
|
134
|
+
sage: F.from_integer(15)
|
|
135
|
+
3*y
|
|
136
|
+
"""
|
|
137
|
+
return self.field(FfToInt(x))
|
|
138
|
+
|
|
139
|
+
cpdef FEL field_to_fel(self, x) except 255:
|
|
140
|
+
"""
|
|
141
|
+
Represent a field element by a python int.
|
|
142
|
+
|
|
143
|
+
EXAMPLES::
|
|
144
|
+
|
|
145
|
+
sage: from sage.matrix.matrix_gfpn_dense import FieldConverter_class
|
|
146
|
+
sage: F.<y> = GF(125)
|
|
147
|
+
sage: C = FieldConverter_class(F)
|
|
148
|
+
sage: C.field_to_fel(y)
|
|
149
|
+
5
|
|
150
|
+
sage: y.to_integer()
|
|
151
|
+
5
|
|
152
|
+
|
|
153
|
+
TESTS:
|
|
154
|
+
|
|
155
|
+
Test invalid input::
|
|
156
|
+
|
|
157
|
+
sage: C.field_to_fel('foo')
|
|
158
|
+
Traceback (most recent call last):
|
|
159
|
+
...
|
|
160
|
+
AttributeError: 'str' object has no attribute 'to_integer'...
|
|
161
|
+
"""
|
|
162
|
+
return FfFromInt(x.to_integer())
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
cdef class PrimeFieldConverter_class(FieldConverter_class):
|
|
166
|
+
"""
|
|
167
|
+
An auxiliary class, used to convert between meataxe ``FEL`` and
|
|
168
|
+
finite field elements.
|
|
169
|
+
|
|
170
|
+
This class is for prime fields only. It exists in order to have a
|
|
171
|
+
common interface for elements of prime and non-prime fields; see
|
|
172
|
+
:class:`FieldConverter_class`.
|
|
173
|
+
|
|
174
|
+
.. NOTE::
|
|
175
|
+
|
|
176
|
+
This class is really meant to be used only from Cython.
|
|
177
|
+
We do make the methods available from Python, but that is
|
|
178
|
+
for testing only.
|
|
179
|
+
|
|
180
|
+
.. WARNING::
|
|
181
|
+
|
|
182
|
+
Before calling the ``fel_to_field`` or ``field_to_fel`` methods,
|
|
183
|
+
one should call the ``FfSetField`` function.
|
|
184
|
+
|
|
185
|
+
EXAMPLES::
|
|
186
|
+
|
|
187
|
+
sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense
|
|
188
|
+
sage: F = GF(5)
|
|
189
|
+
sage: M = MatrixSpace(F, 2, 2, implementation=Matrix_gfpn_dense).one()
|
|
190
|
+
sage: C = M._converter
|
|
191
|
+
sage: C.fel_to_field(2)
|
|
192
|
+
2
|
|
193
|
+
sage: F(2)
|
|
194
|
+
2
|
|
195
|
+
sage: C.field_to_fel(F(2))
|
|
196
|
+
2
|
|
197
|
+
sage: int(F(2))
|
|
198
|
+
2
|
|
199
|
+
"""
|
|
200
|
+
def __init__(self, field):
|
|
201
|
+
"""
|
|
202
|
+
INPUT:
|
|
203
|
+
|
|
204
|
+
- ``field`` -- a finite *prime* field with at most 251 elements.
|
|
205
|
+
This assumption is not tested.
|
|
206
|
+
|
|
207
|
+
EXAMPLES::
|
|
208
|
+
|
|
209
|
+
sage: from sage.matrix.matrix_gfpn_dense import PrimeFieldConverter_class
|
|
210
|
+
sage: PrimeFieldConverter_class(GF(251))
|
|
211
|
+
<sage.matrix.matrix_gfpn_dense.PrimeFieldConverter_class object at ...>
|
|
212
|
+
"""
|
|
213
|
+
self.field = field
|
|
214
|
+
|
|
215
|
+
cpdef fel_to_field(self, FEL x) noexcept:
|
|
216
|
+
"""
|
|
217
|
+
Fetch a python int into the field.
|
|
218
|
+
|
|
219
|
+
EXAMPLES::
|
|
220
|
+
|
|
221
|
+
sage: from sage.matrix.matrix_gfpn_dense import PrimeFieldConverter_class
|
|
222
|
+
sage: F = GF(5)
|
|
223
|
+
sage: C = PrimeFieldConverter_class(F)
|
|
224
|
+
sage: C.fel_to_field(2)
|
|
225
|
+
2
|
|
226
|
+
"""
|
|
227
|
+
return IntegerMod_int(self.field, FfToInt(x))
|
|
228
|
+
|
|
229
|
+
cpdef FEL field_to_fel(self, x) except 255:
|
|
230
|
+
"""
|
|
231
|
+
Represent a field element by a python int.
|
|
232
|
+
|
|
233
|
+
EXAMPLES::
|
|
234
|
+
|
|
235
|
+
sage: from sage.matrix.matrix_gfpn_dense import PrimeFieldConverter_class
|
|
236
|
+
sage: F = GF(5)
|
|
237
|
+
sage: C = PrimeFieldConverter_class(F)
|
|
238
|
+
sage: C.field_to_fel(F(2))
|
|
239
|
+
2
|
|
240
|
+
|
|
241
|
+
TESTS:
|
|
242
|
+
|
|
243
|
+
Test invalid input::
|
|
244
|
+
|
|
245
|
+
sage: C.field_to_fel('foo')
|
|
246
|
+
Traceback (most recent call last):
|
|
247
|
+
...
|
|
248
|
+
TypeError: an integer is required
|
|
249
|
+
"""
|
|
250
|
+
return FfFromInt(x)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
cdef dict _converter_cache = {}
|
|
254
|
+
cdef FieldConverter_class FieldConverter(field) noexcept:
|
|
255
|
+
"""
|
|
256
|
+
Return a :class:`FieldConverter_class` or :class:`PrimeFieldConverter_class` instance,
|
|
257
|
+
depending whether the field is prime or not.
|
|
258
|
+
|
|
259
|
+
EXAMPLES::
|
|
260
|
+
|
|
261
|
+
sage: MS = MatrixSpace(GF(5^3,'y'),2)
|
|
262
|
+
sage: A = MS.random_element()
|
|
263
|
+
sage: A*2 == A+A # indirect doctest
|
|
264
|
+
True
|
|
265
|
+
sage: A = MS.random_element()
|
|
266
|
+
sage: A*2 == A+A
|
|
267
|
+
True
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
return _converter_cache[field]
|
|
271
|
+
except KeyError:
|
|
272
|
+
if field.is_prime_field():
|
|
273
|
+
return _converter_cache.setdefault(field, PrimeFieldConverter_class(field))
|
|
274
|
+
return _converter_cache.setdefault(field, FieldConverter_class(field))
|
|
275
|
+
|
|
276
|
+
######################################
|
|
277
|
+
##
|
|
278
|
+
## Wrapper for MeatAxe matrices
|
|
279
|
+
##
|
|
280
|
+
######################################
|
|
281
|
+
|
|
282
|
+
cdef Matrix_gfpn_dense new_mtx(Matrix_t* mat, Matrix_gfpn_dense template) noexcept:
|
|
283
|
+
"""
|
|
284
|
+
Create a new ``Matrix_gfpn_dense`` from a meataxe matrix
|
|
285
|
+
|
|
286
|
+
INPUT:
|
|
287
|
+
|
|
288
|
+
- ``mat`` -- (``Matrix_t*``) a meataxe matrix
|
|
289
|
+
|
|
290
|
+
- ``template`` -- an optional matrix with the same base ring as the
|
|
291
|
+
new matrix being created. This can also be ``None``.
|
|
292
|
+
|
|
293
|
+
``template`` is only used for efficiency: various objects are simply
|
|
294
|
+
taken from the template instead of being constructed. The condition
|
|
295
|
+
that the base ring is the same is not checked.
|
|
296
|
+
"""
|
|
297
|
+
assert mat is not NULL
|
|
298
|
+
|
|
299
|
+
if template is not None:
|
|
300
|
+
B = template._base_ring
|
|
301
|
+
conv = template._converter
|
|
302
|
+
src = template.Data
|
|
303
|
+
if src is not NULL and src.Nor == mat.Nor and src.Noc == mat.Noc:
|
|
304
|
+
# Same dimensions
|
|
305
|
+
MS = template._parent
|
|
306
|
+
else:
|
|
307
|
+
# Different dimensions
|
|
308
|
+
MS = MatrixSpace(B, mat.Nor, mat.Noc, implementation=Matrix_gfpn_dense)
|
|
309
|
+
else:
|
|
310
|
+
B = GF(mat.Field, 'z')
|
|
311
|
+
conv = FieldConverter(B)
|
|
312
|
+
MS = MatrixSpace(B, mat.Nor, mat.Noc, implementation=Matrix_gfpn_dense)
|
|
313
|
+
|
|
314
|
+
ret = <Matrix_gfpn_dense>Matrix_gfpn_dense.__new__(Matrix_gfpn_dense, MS)
|
|
315
|
+
ret.Data = mat
|
|
316
|
+
ret._converter = conv
|
|
317
|
+
return ret
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
cdef class Matrix_gfpn_dense(Matrix_dense):
|
|
321
|
+
r"""
|
|
322
|
+
Dense matrices over `\mathbb F_q`, `q<255` odd and not prime.
|
|
323
|
+
|
|
324
|
+
NOTE:
|
|
325
|
+
|
|
326
|
+
This class uses a major modification of the Aachen C-MeatAxe
|
|
327
|
+
as backend. In principle, it would also work for prime fields
|
|
328
|
+
and in characteristic two. However, other matrices in Sage,
|
|
329
|
+
relying on linbox, m4ri or m4rie, are more efficient in these
|
|
330
|
+
cases.
|
|
331
|
+
|
|
332
|
+
EXAMPLES::
|
|
333
|
+
|
|
334
|
+
sage: M = MatrixSpace(GF(25,'z'),2,3)([1,2,3,4,5,6])
|
|
335
|
+
sage: print(M)
|
|
336
|
+
[1 2 3]
|
|
337
|
+
[4 0 1]
|
|
338
|
+
sage: type(M)
|
|
339
|
+
<class 'sage.matrix.matrix_gfpn_dense.Matrix_gfpn_dense'>
|
|
340
|
+
|
|
341
|
+
The documentation of the ``__init__`` methods shows further
|
|
342
|
+
ways of creating a :class:`Matrix_gfpn_dense` instance.
|
|
343
|
+
However, these should only be of internal use.
|
|
344
|
+
"""
|
|
345
|
+
##################
|
|
346
|
+
## Init, Dealloc, Copy
|
|
347
|
+
|
|
348
|
+
def __dealloc__(self):
|
|
349
|
+
"""
|
|
350
|
+
TESTS::
|
|
351
|
+
|
|
352
|
+
sage: MS = MatrixSpace(GF(64), 0)
|
|
353
|
+
sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense
|
|
354
|
+
sage: Matrix_gfpn_dense.__new__(Matrix_gfpn_dense, MS)
|
|
355
|
+
[]
|
|
356
|
+
sage: M = None
|
|
357
|
+
sage: M = Matrix_gfpn_dense(MatrixSpace(GF(64,'z'),4), None)
|
|
358
|
+
sage: del M # indirect doctest
|
|
359
|
+
"""
|
|
360
|
+
if self.Data != NULL:
|
|
361
|
+
MatFree(self.Data)
|
|
362
|
+
|
|
363
|
+
def __init__(self, parent, entries=None, copy=None, bint coerce=False, *, bint mutable=True):
|
|
364
|
+
r"""
|
|
365
|
+
Matrix extension class using libmeataxe as backend.
|
|
366
|
+
|
|
367
|
+
INPUT:
|
|
368
|
+
|
|
369
|
+
- ``parent`` -- a matrix space over ``GF(q)`` with `q < 255`
|
|
370
|
+
|
|
371
|
+
- ``entries`` -- see :func:`matrix`
|
|
372
|
+
|
|
373
|
+
- ``copy`` -- ignored (for backwards compatibility)
|
|
374
|
+
|
|
375
|
+
- ``coerce`` -- ignored
|
|
376
|
+
|
|
377
|
+
- ``mutable`` -- if ``False``, the resulting matrix cannot be
|
|
378
|
+
changed, and it can be used as key in a Python dictionary
|
|
379
|
+
|
|
380
|
+
EXAMPLES::
|
|
381
|
+
|
|
382
|
+
sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense
|
|
383
|
+
|
|
384
|
+
1. Creating a zero (3x2)-matrix::
|
|
385
|
+
|
|
386
|
+
sage: Matrix_gfpn_dense(MatrixSpace(GF(4,'z'),3,2))
|
|
387
|
+
[0 0]
|
|
388
|
+
[0 0]
|
|
389
|
+
[0 0]
|
|
390
|
+
|
|
391
|
+
2. Creating a matrix from a list or list of lists::
|
|
392
|
+
|
|
393
|
+
sage: Matrix_gfpn_dense(MatrixSpace(GF(5),2,3),[1,2,3,4,5,6])
|
|
394
|
+
[1 2 3]
|
|
395
|
+
[4 0 1]
|
|
396
|
+
sage: Matrix_gfpn_dense(MatrixSpace(GF(5),2,3),[[1,2,3],[4,5,6]])
|
|
397
|
+
[1 2 3]
|
|
398
|
+
[4 0 1]
|
|
399
|
+
|
|
400
|
+
3. Creating a diagonal matrix::
|
|
401
|
+
|
|
402
|
+
sage: M = Matrix_gfpn_dense(MatrixSpace(GF(7),5),2); M
|
|
403
|
+
[2 0 0 0 0]
|
|
404
|
+
[0 2 0 0 0]
|
|
405
|
+
[0 0 2 0 0]
|
|
406
|
+
[0 0 0 2 0]
|
|
407
|
+
[0 0 0 0 2]
|
|
408
|
+
|
|
409
|
+
TESTS::
|
|
410
|
+
|
|
411
|
+
sage: MS = MatrixSpace(GF(125,'y'),2) # indirect doctest
|
|
412
|
+
sage: A = MS(0)
|
|
413
|
+
sage: A.left_kernel()
|
|
414
|
+
Vector space of degree 2 and dimension 2 over Finite Field in y of size 5^3
|
|
415
|
+
Basis matrix:
|
|
416
|
+
[1 0]
|
|
417
|
+
[0 1]
|
|
418
|
+
sage: A.right_kernel()
|
|
419
|
+
Vector space of degree 2 and dimension 2 over Finite Field in y of size 5^3
|
|
420
|
+
Basis matrix:
|
|
421
|
+
[1 0]
|
|
422
|
+
[0 1]
|
|
423
|
+
"""
|
|
424
|
+
ma = MatrixArgs_init(parent, entries)
|
|
425
|
+
cdef long fl = ma.base.order()
|
|
426
|
+
cdef long nr = ma.nrows
|
|
427
|
+
cdef long nc = ma.ncols
|
|
428
|
+
|
|
429
|
+
self.Data = MatAlloc(fl, nr, nc)
|
|
430
|
+
self._converter = FieldConverter(ma.base)
|
|
431
|
+
|
|
432
|
+
cdef PTR x = self.Data.Data
|
|
433
|
+
assert self.Data.Noc == nc
|
|
434
|
+
assert self.Data.Nor == nr
|
|
435
|
+
FfSetField(fl)
|
|
436
|
+
FfSetNoc(nc)
|
|
437
|
+
it = ma.iter(False)
|
|
438
|
+
cdef long i,j
|
|
439
|
+
for i in range(nr):
|
|
440
|
+
for j in range(nc):
|
|
441
|
+
c = self._converter.field_to_fel(self._coerce_element(next(it)))
|
|
442
|
+
FfInsert(x, j, c)
|
|
443
|
+
sig_check()
|
|
444
|
+
FfStepPtr(&x)
|
|
445
|
+
|
|
446
|
+
self._is_immutable = not mutable
|
|
447
|
+
|
|
448
|
+
@staticmethod
|
|
449
|
+
def from_filename(filename):
|
|
450
|
+
"""
|
|
451
|
+
Create a matrix from a file in MeatAxe format. If the file
|
|
452
|
+
does not exist, an error raised by the MeatAxe library is
|
|
453
|
+
propagated::
|
|
454
|
+
|
|
455
|
+
sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense
|
|
456
|
+
sage: Matrix_gfpn_dense.from_filename('foobarNONEXISTING_FILE')
|
|
457
|
+
Traceback (most recent call last):
|
|
458
|
+
...
|
|
459
|
+
OSError: foobarNONEXISTING_FILE: No such file or directory in file os.c (line 255)
|
|
460
|
+
sage: Matrix_gfpn_dense.from_filename('')
|
|
461
|
+
Traceback (most recent call last):
|
|
462
|
+
...
|
|
463
|
+
ValueError: cannot construct meataxe matrix from empty filename
|
|
464
|
+
"""
|
|
465
|
+
if not filename:
|
|
466
|
+
raise ValueError("cannot construct meataxe matrix from empty filename")
|
|
467
|
+
|
|
468
|
+
if type(filename) is not bytes:
|
|
469
|
+
filename = filename.encode(FS_ENCODING, 'surrogateescape')
|
|
470
|
+
|
|
471
|
+
sig_on()
|
|
472
|
+
try:
|
|
473
|
+
mat = MatLoad(filename)
|
|
474
|
+
finally:
|
|
475
|
+
sig_off()
|
|
476
|
+
return new_mtx(mat, None)
|
|
477
|
+
|
|
478
|
+
def __copy__(self):
|
|
479
|
+
"""
|
|
480
|
+
Return a mutable copy of this matrix.
|
|
481
|
+
|
|
482
|
+
EXAMPLES::
|
|
483
|
+
|
|
484
|
+
sage: M = MatrixSpace(GF(25,'x'), 3, 20)([20*[0],20*[0],[1]+19*[0]])
|
|
485
|
+
sage: N = copy(M) # indirect doctest
|
|
486
|
+
sage: print(N)
|
|
487
|
+
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
|
|
488
|
+
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
|
|
489
|
+
[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
|
|
490
|
+
sage: N == M
|
|
491
|
+
True
|
|
492
|
+
sage: N is M
|
|
493
|
+
False
|
|
494
|
+
sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense
|
|
495
|
+
sage: M = Matrix_gfpn_dense.__new__(Matrix_gfpn_dense, parent(M))
|
|
496
|
+
sage: copy(M)
|
|
497
|
+
Traceback (most recent call last):
|
|
498
|
+
...
|
|
499
|
+
ValueError: cannot copy an uninitialized matrix
|
|
500
|
+
"""
|
|
501
|
+
if self.Data is NULL:
|
|
502
|
+
raise ValueError("cannot copy an uninitialized matrix")
|
|
503
|
+
sig_on()
|
|
504
|
+
try:
|
|
505
|
+
mat = MatDup(self.Data)
|
|
506
|
+
finally:
|
|
507
|
+
sig_off()
|
|
508
|
+
retval = new_mtx(mat, self)
|
|
509
|
+
if self._cache:
|
|
510
|
+
retval._cache = dict(self._cache)
|
|
511
|
+
return retval
|
|
512
|
+
|
|
513
|
+
def __reduce__(self):
|
|
514
|
+
"""
|
|
515
|
+
TESTS::
|
|
516
|
+
|
|
517
|
+
sage: M = MatrixSpace(GF(9,'x'),10,10).random_element()
|
|
518
|
+
sage: M == loads(dumps(M)) # indirect doctest
|
|
519
|
+
True
|
|
520
|
+
sage: M is loads(dumps(M))
|
|
521
|
+
False
|
|
522
|
+
"""
|
|
523
|
+
cdef char* d
|
|
524
|
+
cdef char* x
|
|
525
|
+
cdef size_t i
|
|
526
|
+
cdef PTR p
|
|
527
|
+
cdef size_t pickle_size
|
|
528
|
+
cdef bytes pickle_str
|
|
529
|
+
if self.Data:
|
|
530
|
+
FfSetField(self.Data.Field)
|
|
531
|
+
FfSetNoc(self.Data.Noc)
|
|
532
|
+
pickle_size = FfCurrentRowSizeIo*self.Data.Nor
|
|
533
|
+
d = <char*>check_malloc(pickle_size)
|
|
534
|
+
p = self.Data.Data
|
|
535
|
+
x = d
|
|
536
|
+
for i in range(self.Data.Nor):
|
|
537
|
+
memcpy(x, p, FfCurrentRowSizeIo)
|
|
538
|
+
sig_check()
|
|
539
|
+
x += FfCurrentRowSizeIo
|
|
540
|
+
FfStepPtr(&p)
|
|
541
|
+
pickle_str = PyBytes_FromStringAndSize(d, pickle_size)
|
|
542
|
+
sig_free(d)
|
|
543
|
+
return mtx_unpickle, (self._parent, self.Data.Nor, self.Data.Noc,
|
|
544
|
+
pickle_str,
|
|
545
|
+
not self._is_immutable) # for backward compatibility with the group cohomology package
|
|
546
|
+
else:
|
|
547
|
+
return mtx_unpickle, (0, 0, 0, '', not self._is_immutable)
|
|
548
|
+
|
|
549
|
+
cdef get_unsafe(self, Py_ssize_t i, Py_ssize_t j) noexcept:
|
|
550
|
+
"""
|
|
551
|
+
Get an element without checking.
|
|
552
|
+
|
|
553
|
+
TESTS::
|
|
554
|
+
|
|
555
|
+
sage: F.<z> = GF(9)
|
|
556
|
+
sage: M = MatrixSpace(F,3)(sorted(list(F)))
|
|
557
|
+
sage: type(M)
|
|
558
|
+
<class 'sage.matrix.matrix_gfpn_dense.Matrix_gfpn_dense'>
|
|
559
|
+
sage: M # indirect doctest
|
|
560
|
+
[ 0 1 2]
|
|
561
|
+
[ z z + 1 z + 2]
|
|
562
|
+
[ 2*z 2*z + 1 2*z + 2]
|
|
563
|
+
"""
|
|
564
|
+
if self.Data == NULL:
|
|
565
|
+
raise IndexError("Matrix is empty")
|
|
566
|
+
FfSetField(self.Data.Field)
|
|
567
|
+
return self._converter.fel_to_field(FfExtract(MatGetPtr(self.Data,i), j))
|
|
568
|
+
|
|
569
|
+
cdef inline int get_unsafe_int(self, Py_ssize_t i, Py_ssize_t j) noexcept:
|
|
570
|
+
# NOTE:
|
|
571
|
+
# It is essential that you call FfSetField and FfSetNoc YOURSELF
|
|
572
|
+
# and that you assert that the matrix is not empty!
|
|
573
|
+
# This method is here for speed!
|
|
574
|
+
return FfToInt(FfExtract(MatGetPtr(self.Data,i), j))
|
|
575
|
+
|
|
576
|
+
cdef bint get_is_zero_unsafe(self, Py_ssize_t i, Py_ssize_t j) except -1:
|
|
577
|
+
r"""
|
|
578
|
+
Return 1 if the entry ``(i, j)`` is zero, otherwise 0.
|
|
579
|
+
|
|
580
|
+
EXAMPLES::
|
|
581
|
+
|
|
582
|
+
sage: F.<z> = GF(9)
|
|
583
|
+
sage: M = MatrixSpace(F,2,5)(sorted(list(F))+[0])
|
|
584
|
+
sage: M.zero_pattern_matrix() # indirect doctest
|
|
585
|
+
[1 0 0 0 0]
|
|
586
|
+
[0 0 0 0 1]
|
|
587
|
+
"""
|
|
588
|
+
FfSetField(self.Data.Field)
|
|
589
|
+
return FfExtract(MatGetPtr(self.Data,i), j) == self._converter.zero_FEL
|
|
590
|
+
|
|
591
|
+
cpdef Matrix_gfpn_dense get_slice(self, Py_ssize_t i, Py_ssize_t j) noexcept:
|
|
592
|
+
"""
|
|
593
|
+
Return a horizontal slice of this matrix.
|
|
594
|
+
|
|
595
|
+
NOTE:
|
|
596
|
+
|
|
597
|
+
``M[i:j]`` may return a matrix that uses a different backend than
|
|
598
|
+
MeatAxe. This method is useful when the slice has to be of type
|
|
599
|
+
:class:`Matrix_gfpn_dense`.
|
|
600
|
+
|
|
601
|
+
EXAMPLES::
|
|
602
|
+
|
|
603
|
+
sage: from sage.matrix.matrix_gfpn_dense import Matrix_gfpn_dense as MTX
|
|
604
|
+
sage: M = MTX(MatrixSpace(GF(7), 5, 3), [[0,1,2], [1,2,3], [2,3,4], [3,4,5], [4,5,6]])
|
|
605
|
+
sage: M
|
|
606
|
+
[0 1 2]
|
|
607
|
+
[1 2 3]
|
|
608
|
+
[2 3 4]
|
|
609
|
+
[3 4 5]
|
|
610
|
+
[4 5 6]
|
|
611
|
+
sage: M.get_slice(1,3)
|
|
612
|
+
[1 2 3]
|
|
613
|
+
[2 3 4]
|
|
614
|
+
sage: type(_) is MTX
|
|
615
|
+
True
|
|
616
|
+
"""
|
|
617
|
+
if not 0 <= i < j <= self.Data.Nor:
|
|
618
|
+
raise IndexError("Indices i={}, j={} violate the condition 0 < i < j < {}".format(i,j,self.Data.Nor))
|
|
619
|
+
sig_on()
|
|
620
|
+
try:
|
|
621
|
+
mat = MatAlloc(self.Data.Field, j-i, self.Data.Noc)
|
|
622
|
+
memcpy(mat.Data, FfGetPtr(self.Data.Data, i), FfCurrentRowSize*(j-i))
|
|
623
|
+
finally:
|
|
624
|
+
sig_off()
|
|
625
|
+
return new_mtx(mat, self)
|
|
626
|
+
|
|
627
|
+
cdef set_unsafe(self, Py_ssize_t i, Py_ssize_t j, value) noexcept:
|
|
628
|
+
"""
|
|
629
|
+
Set values without bound checking.
|
|
630
|
+
|
|
631
|
+
TESTS:
|
|
632
|
+
|
|
633
|
+
The following test would have failed in a preliminary version
|
|
634
|
+
of this MeatAxe wrapper::
|
|
635
|
+
|
|
636
|
+
sage: K.<x> = GF(125)
|
|
637
|
+
sage: M = MatrixSpace(K,9,9)()
|
|
638
|
+
sage: N = MatrixSpace(GF(9,'x'),20).random_element()
|
|
639
|
+
sage: M[2,2] = x
|
|
640
|
+
sage: M
|
|
641
|
+
[0 0 0 0 0 0 0 0 0]
|
|
642
|
+
[0 0 0 0 0 0 0 0 0]
|
|
643
|
+
[0 0 x 0 0 0 0 0 0]
|
|
644
|
+
[0 0 0 0 0 0 0 0 0]
|
|
645
|
+
[0 0 0 0 0 0 0 0 0]
|
|
646
|
+
[0 0 0 0 0 0 0 0 0]
|
|
647
|
+
[0 0 0 0 0 0 0 0 0]
|
|
648
|
+
[0 0 0 0 0 0 0 0 0]
|
|
649
|
+
[0 0 0 0 0 0 0 0 0]
|
|
650
|
+
"""
|
|
651
|
+
# ASSUMPTION: value's parent is the base ring
|
|
652
|
+
if self.Data == NULL:
|
|
653
|
+
raise IndexError("Matrix is empty")
|
|
654
|
+
FfSetField(self.Data.Field)
|
|
655
|
+
FfInsert(MatGetPtr(self.Data,i), j, self._converter.field_to_fel(value))
|
|
656
|
+
|
|
657
|
+
cdef void set_unsafe_int(self, Py_ssize_t i, Py_ssize_t j, int value) noexcept:
|
|
658
|
+
# NOTE:
|
|
659
|
+
# It is essential that you call FfSetField and FfSetNoc YOURSELF
|
|
660
|
+
# and that you assert that the matrix is not empty!
|
|
661
|
+
# This method is here for speed!
|
|
662
|
+
FfInsert(FfGetPtr(self.Data.Data,i), j, FfFromInt(value))
|
|
663
|
+
|
|
664
|
+
cdef set_slice_unsafe(self, Py_ssize_t i, Matrix_gfpn_dense S) noexcept:
|
|
665
|
+
# Overwrite the self[i:i+S.nrows()] by the contents of S.
|
|
666
|
+
#
|
|
667
|
+
# NOTE:
|
|
668
|
+
# It is essential that you call FfSetField and FfSetNoc YOURSELF
|
|
669
|
+
# and that the dimensions of self and S match!
|
|
670
|
+
sig_on()
|
|
671
|
+
try:
|
|
672
|
+
memcpy(FfGetPtr(self.Data.Data, i), S.Data.Data, FfCurrentRowSize*S.Data.Nor)
|
|
673
|
+
finally:
|
|
674
|
+
sig_off()
|
|
675
|
+
|
|
676
|
+
def randomize(self, density=None, nonzero=False, *args, **kwds):
|
|
677
|
+
"""
|
|
678
|
+
Fill the matrix with random values.
|
|
679
|
+
|
|
680
|
+
INPUT:
|
|
681
|
+
|
|
682
|
+
- ``density`` -- (optional) real number between zero and one the
|
|
683
|
+
expected density of the resulting matrix
|
|
684
|
+
- ``nonzero`` -- boolean (default: ``False``); if ``True``, all
|
|
685
|
+
inserted marks are nonzero
|
|
686
|
+
|
|
687
|
+
EXAMPLES::
|
|
688
|
+
|
|
689
|
+
sage: MS = MatrixSpace(GF(27,'z'),6,6)
|
|
690
|
+
sage: M = MS.random_element() # indirect doctest
|
|
691
|
+
sage: type(M)
|
|
692
|
+
<class 'sage.matrix.matrix_gfpn_dense.Matrix_gfpn_dense'>
|
|
693
|
+
sage: M = MS.random_element(nonzero=True)
|
|
694
|
+
sage: all(M[i,j] for i in range(6) for j in range(6))
|
|
695
|
+
True
|
|
696
|
+
sage: avg_density = sum(MS.random_element(density=0.5).density()
|
|
697
|
+
....: for _ in range(100))
|
|
698
|
+
sage: avg_density /= 100
|
|
699
|
+
sage: RR(avg_density) # random
|
|
700
|
+
0.5
|
|
701
|
+
|
|
702
|
+
TESTS:
|
|
703
|
+
|
|
704
|
+
The following tests against a bug that was fixed in :issue:`23352`.
|
|
705
|
+
This test could fail for some seed, but it would be highly unlikely::
|
|
706
|
+
|
|
707
|
+
sage: MS = MatrixSpace(GF(9,'x'),1,5)
|
|
708
|
+
sage: any(MS.random_element()[0,4] for _ in range(50))
|
|
709
|
+
True
|
|
710
|
+
"""
|
|
711
|
+
self.check_mutability()
|
|
712
|
+
cdef int fl = self.Data.Field
|
|
713
|
+
density = float(density)
|
|
714
|
+
if density <= 0:
|
|
715
|
+
return
|
|
716
|
+
if density > 1:
|
|
717
|
+
density = float(1)
|
|
718
|
+
|
|
719
|
+
self.clear_cache()
|
|
720
|
+
|
|
721
|
+
cdef PTR x
|
|
722
|
+
cdef unsigned char *y
|
|
723
|
+
x = self.Data.Data
|
|
724
|
+
cdef int nr = self.Data.Nor
|
|
725
|
+
cdef int nc = self.Data.Noc
|
|
726
|
+
cdef Py_ssize_t i, j
|
|
727
|
+
|
|
728
|
+
FfSetField(fl)
|
|
729
|
+
FfSetNoc(nc)
|
|
730
|
+
cdef int MPB, tmp
|
|
731
|
+
cdef unsigned char O
|
|
732
|
+
cdef randstate RandState = current_randstate()
|
|
733
|
+
|
|
734
|
+
if not nonzero:
|
|
735
|
+
if density == 1:
|
|
736
|
+
MPB = 0
|
|
737
|
+
tmp = fl
|
|
738
|
+
while tmp <= 256:
|
|
739
|
+
MPB += 1
|
|
740
|
+
tmp *= fl
|
|
741
|
+
O = (fl**MPB)
|
|
742
|
+
if nc % MPB:
|
|
743
|
+
for i in range(nr):
|
|
744
|
+
y = <unsigned char*>x
|
|
745
|
+
for j in range(FfCurrentRowSizeIo-1):
|
|
746
|
+
y[j] = RandState.c_random() % O
|
|
747
|
+
sig_check()
|
|
748
|
+
for j in range(nc-(nc % MPB), nc):
|
|
749
|
+
FfInsert(x, j, FfFromInt( (RandState.c_random() % fl) ))
|
|
750
|
+
sig_check()
|
|
751
|
+
FfStepPtr(&(x))
|
|
752
|
+
else:
|
|
753
|
+
for i in range(nr):
|
|
754
|
+
y = <unsigned char*>x
|
|
755
|
+
for j in range(FfCurrentRowSizeIo):
|
|
756
|
+
y[j] = RandState.c_random() % O
|
|
757
|
+
sig_check()
|
|
758
|
+
FfStepPtr(&(x))
|
|
759
|
+
else:
|
|
760
|
+
for i in range(nr):
|
|
761
|
+
for j in range(nc):
|
|
762
|
+
if RandState.c_rand_double() < density:
|
|
763
|
+
FfInsert(x, j, FfFromInt( (RandState.c_random() % fl) ))
|
|
764
|
+
sig_check()
|
|
765
|
+
FfStepPtr(&(x))
|
|
766
|
+
else:
|
|
767
|
+
if density == 1:
|
|
768
|
+
fl -= 1
|
|
769
|
+
for i in range(nr):
|
|
770
|
+
for j in range(nc):
|
|
771
|
+
FfInsert(x, j, FfFromInt( (RandState.c_random() % fl)+1 ))
|
|
772
|
+
sig_check()
|
|
773
|
+
FfStepPtr(&(x))
|
|
774
|
+
else:
|
|
775
|
+
fl -= 1
|
|
776
|
+
for i in range(nr):
|
|
777
|
+
for j in range(nc):
|
|
778
|
+
if RandState.c_rand_double() < density:
|
|
779
|
+
FfInsert(x, j, FfFromInt( (RandState.c_random() % fl)+1 ))
|
|
780
|
+
sig_check()
|
|
781
|
+
FfStepPtr(&(x))
|
|
782
|
+
|
|
783
|
+
## Debugging
|
|
784
|
+
# def show_contents(self, r=None):
|
|
785
|
+
# FfSetField(self.Data.Field)
|
|
786
|
+
# FfSetNoc(self.Data.Noc)
|
|
787
|
+
# cdef PTR p
|
|
788
|
+
# cdef size_t i, j
|
|
789
|
+
# if r is not None:
|
|
790
|
+
# r_min = r
|
|
791
|
+
# r_max = r+1
|
|
792
|
+
# else:
|
|
793
|
+
# r_min = 0
|
|
794
|
+
# r_max = self.Data.Nor
|
|
795
|
+
# for i in range(r_min, r_max):
|
|
796
|
+
# p = FfGetPtr(self.Data.Data, i)
|
|
797
|
+
# for j from 0<=j<self.Data.RowSize:
|
|
798
|
+
# print("%3.3d" % p[j])
|
|
799
|
+
# print
|
|
800
|
+
|
|
801
|
+
##################
|
|
802
|
+
## comparison
|
|
803
|
+
cpdef _richcmp_(left, right, int op) noexcept:
|
|
804
|
+
"""
|
|
805
|
+
Compare two :class:`Matrix_gfpn_dense` matrices.
|
|
806
|
+
|
|
807
|
+
Of course, '<' and '>' do not make much sense for matrices.
|
|
808
|
+
|
|
809
|
+
EXAMPLES::
|
|
810
|
+
|
|
811
|
+
sage: M = MatrixSpace(GF(125,'x'),3,20)([20*[0],20*[0],[1]+19*[0]])
|
|
812
|
+
sage: N = copy(M)
|
|
813
|
+
sage: M == N
|
|
814
|
+
True
|
|
815
|
+
sage: M != N
|
|
816
|
+
False
|
|
817
|
+
sage: M < N
|
|
818
|
+
False
|
|
819
|
+
sage: N[2,19] = 1
|
|
820
|
+
sage: M == N
|
|
821
|
+
False
|
|
822
|
+
sage: M != N
|
|
823
|
+
True
|
|
824
|
+
"""
|
|
825
|
+
cdef Matrix_gfpn_dense self = left
|
|
826
|
+
cdef Matrix_gfpn_dense N = right
|
|
827
|
+
if self is None or N is None:
|
|
828
|
+
return rich_to_bool(op, -1)
|
|
829
|
+
if self.Data == NULL:
|
|
830
|
+
if N.Data == NULL:
|
|
831
|
+
return rich_to_bool(op, 0)
|
|
832
|
+
else:
|
|
833
|
+
return rich_to_bool(op, 1)
|
|
834
|
+
elif N.Data == NULL:
|
|
835
|
+
return rich_to_bool(op, -1)
|
|
836
|
+
if self.Data.Field != N.Data.Field:
|
|
837
|
+
if self.Data.Field > N.Data.Field:
|
|
838
|
+
return rich_to_bool(op, 1)
|
|
839
|
+
return rich_to_bool(op, -1)
|
|
840
|
+
if self.Data.Noc != N.Data.Noc:
|
|
841
|
+
if self.Data.Noc > N.Data.Noc:
|
|
842
|
+
return rich_to_bool(op, 1)
|
|
843
|
+
return rich_to_bool(op, -1)
|
|
844
|
+
if self.Data.Nor != N.Data.Nor:
|
|
845
|
+
if self.Data.Nor > N.Data.Nor:
|
|
846
|
+
return rich_to_bool(op, 1)
|
|
847
|
+
return rich_to_bool(op, -1)
|
|
848
|
+
|
|
849
|
+
cdef char* d1 = <char*>self.Data.Data
|
|
850
|
+
cdef char* d2 = <char*>N.Data.Data
|
|
851
|
+
cdef Py_ssize_t total_size = self.Data.RowSize
|
|
852
|
+
total_size *= self.Data.Nor
|
|
853
|
+
cdef bytes s1, s2
|
|
854
|
+
s1 = PyBytes_FromStringAndSize(d1, total_size)
|
|
855
|
+
s2 = PyBytes_FromStringAndSize(d2, total_size)
|
|
856
|
+
if s1 != s2:
|
|
857
|
+
if s1 > s2:
|
|
858
|
+
return rich_to_bool(op, 1)
|
|
859
|
+
return rich_to_bool(op, -1)
|
|
860
|
+
return rich_to_bool(op, 0)
|
|
861
|
+
|
|
862
|
+
cpdef list _rowlist_(self, i, j=-1) noexcept:
|
|
863
|
+
"""
|
|
864
|
+
Return rows as a flat list of python ints.
|
|
865
|
+
|
|
866
|
+
INPUT:
|
|
867
|
+
|
|
868
|
+
- ``i`` -- index of the first row to be extracted
|
|
869
|
+
- ``j`` -- (default: -1) -1, or index of the last
|
|
870
|
+
row to be extracted
|
|
871
|
+
|
|
872
|
+
OUTPUT:
|
|
873
|
+
|
|
874
|
+
If `j=-1` then only the `i`-th row is returned as a list.
|
|
875
|
+
Otherwises, rows `i` to `j` (both included) are returned
|
|
876
|
+
as a list of integers.
|
|
877
|
+
|
|
878
|
+
EXAMPLES::
|
|
879
|
+
|
|
880
|
+
sage: k.<x> = GF(25,'x')
|
|
881
|
+
sage: M = random_matrix(GF(25,'x'), 5,5)
|
|
882
|
+
sage: M = matrix(k,[[ 4, 4*x, x + 3, 4*x + 2, 3*x + 4],
|
|
883
|
+
....: [ x + 2, 3*x + 1, 3, 0, 3],
|
|
884
|
+
....: [ 3*x, 2*x + 4, 1, 0, 2*x],
|
|
885
|
+
....: [4*x + 4, 2*x + 3, 4*x, 1, 3*x + 1],
|
|
886
|
+
....: [3*x + 3, x + 3, x + 2, x + 1, 3*x + 2]])
|
|
887
|
+
sage: M._rowlist_(1)
|
|
888
|
+
[7, 16, 3, 0, 3]
|
|
889
|
+
sage: [M[1,i]._int_repr() for i in range(5)]
|
|
890
|
+
['7', '16', '3', '0', '3']
|
|
891
|
+
sage: M._rowlist_(2,4)
|
|
892
|
+
[15, 14, 1, 0, 10, 24, 13, 20, 1, 16, 18, 8, 7, 6, 17]
|
|
893
|
+
sage: [[M[i,j]._int_repr() for j in range(5)] for i in range(2,5)]
|
|
894
|
+
[['15', '14', '1', '0', '10'],
|
|
895
|
+
['24', '13', '20', '1', '16'],
|
|
896
|
+
['18', '8', '7', '6', '17']]
|
|
897
|
+
"""
|
|
898
|
+
cdef int k
|
|
899
|
+
if self.Data:
|
|
900
|
+
FfSetField(self.Data.Field)
|
|
901
|
+
FfSetNoc(self.Data.Noc)
|
|
902
|
+
else:
|
|
903
|
+
raise ValueError("Matrix is empty")
|
|
904
|
+
if (i < 0) or (i >= self.Data.Nor):
|
|
905
|
+
raise IndexError("Index {} out of range 0..{}",format(i, self.Data.Nor-1))
|
|
906
|
+
cdef PTR p
|
|
907
|
+
p = MatGetPtr(self.Data, i)
|
|
908
|
+
L = [FfToInt(FfExtract(p, k)) for k in range(self.Data.Noc)]
|
|
909
|
+
if j != -1:
|
|
910
|
+
if not isinstance(j, (int, Integer)):
|
|
911
|
+
raise TypeError("Second index must be an integer")
|
|
912
|
+
if j >= self.Data.Nor:
|
|
913
|
+
raise IndexError("Index out of range")
|
|
914
|
+
for k in range(i, j):
|
|
915
|
+
FfStepPtr(&(p)) # This is only called after MatGetPtr, hence, after FfSetNoc.
|
|
916
|
+
L.extend([FfToInt(FfExtract(p,l)) for l in range(self.Data.Noc)])
|
|
917
|
+
return L
|
|
918
|
+
|
|
919
|
+
def _list(self):
|
|
920
|
+
"""
|
|
921
|
+
Return a flat list of all entries of this matrix.
|
|
922
|
+
|
|
923
|
+
The result is cached.
|
|
924
|
+
|
|
925
|
+
EXAMPLES::
|
|
926
|
+
|
|
927
|
+
sage: MatrixSpace(GF(9,'x'),3)(sorted(list(GF(9,'x')))).list() # indirect doctest
|
|
928
|
+
[0, 1, 2, x, x + 1, x + 2, 2*x, 2*x + 1, 2*x + 2]
|
|
929
|
+
|
|
930
|
+
TESTS:
|
|
931
|
+
|
|
932
|
+
This works correctly on matrices with zero rows or columns::
|
|
933
|
+
|
|
934
|
+
sage: M = Matrix(GF(9), 0, 3); M._list()
|
|
935
|
+
[]
|
|
936
|
+
sage: M = Matrix(GF(9), 3, 0); M._list()
|
|
937
|
+
[]
|
|
938
|
+
sage: M = Matrix(GF(9), 0, 0); M._list()
|
|
939
|
+
[]
|
|
940
|
+
"""
|
|
941
|
+
L = self.fetch('list')
|
|
942
|
+
if L is not None:
|
|
943
|
+
return L
|
|
944
|
+
if self.Data is NULL:
|
|
945
|
+
raise IndexError("matrix is empty")
|
|
946
|
+
FfSetField(self.Data.Field)
|
|
947
|
+
FfSetNoc(self.Data.Noc)
|
|
948
|
+
cdef list x = []
|
|
949
|
+
cdef int i, j
|
|
950
|
+
p = self.Data.Data
|
|
951
|
+
for i in range(self.Data.Nor):
|
|
952
|
+
sig_check()
|
|
953
|
+
for j in range(self.Data.Noc):
|
|
954
|
+
x.append(self._converter.fel_to_field(FfExtract(p,j)))
|
|
955
|
+
FfStepPtr(&p)
|
|
956
|
+
self.cache('list', x)
|
|
957
|
+
return x
|
|
958
|
+
|
|
959
|
+
#########################
|
|
960
|
+
## Arithmetics
|
|
961
|
+
cdef rescale_row_c(self, Py_ssize_t i, s, Py_ssize_t start_col) noexcept:
|
|
962
|
+
"""
|
|
963
|
+
Rescale row number `i` in-place by multiplication with the scalar `s`.
|
|
964
|
+
|
|
965
|
+
The scalar `s` is converted into the base ring.
|
|
966
|
+
|
|
967
|
+
EXAMPLES::
|
|
968
|
+
|
|
969
|
+
sage: K.<x> = GF(25)
|
|
970
|
+
sage: M = MatrixSpace(K,5,5)(sorted(list(K)))
|
|
971
|
+
sage: M
|
|
972
|
+
[ 0 1 2 3 4]
|
|
973
|
+
[ x x + 1 x + 2 x + 3 x + 4]
|
|
974
|
+
[ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4]
|
|
975
|
+
[ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4]
|
|
976
|
+
[ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4]
|
|
977
|
+
sage: M.rescale_row(1, 3) # indirect doctest
|
|
978
|
+
sage: M
|
|
979
|
+
[ 0 1 2 3 4]
|
|
980
|
+
[ 3*x 3*x + 3 3*x + 1 3*x + 4 3*x + 2]
|
|
981
|
+
[ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4]
|
|
982
|
+
[ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4]
|
|
983
|
+
[ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4]
|
|
984
|
+
sage: M.rescale_row(4, x)
|
|
985
|
+
sage: M
|
|
986
|
+
[ 0 1 2 3 4]
|
|
987
|
+
[ 3*x 3*x + 3 3*x + 1 3*x + 4 3*x + 2]
|
|
988
|
+
[ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4]
|
|
989
|
+
[ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4]
|
|
990
|
+
[4*x + 2 2 x + 2 2*x + 2 3*x + 2]
|
|
991
|
+
|
|
992
|
+
TESTS:
|
|
993
|
+
|
|
994
|
+
The following tests against bugs fixed in :issue:`25490`. It shows
|
|
995
|
+
that the optional argument ``start_col`` is correctly dealt with,
|
|
996
|
+
in an example where several marks are packed into one byte, and
|
|
997
|
+
so that temporarily the current field and row size are changed.
|
|
998
|
+
::
|
|
999
|
+
|
|
1000
|
+
sage: L.<y> = GF(9)
|
|
1001
|
+
sage: N = MatrixSpace(L,5,5)((3*sorted(list(L)))[:25])
|
|
1002
|
+
sage: N
|
|
1003
|
+
[ 0 1 2 y y + 1]
|
|
1004
|
+
[ y + 2 2*y 2*y + 1 2*y + 2 0]
|
|
1005
|
+
[ 1 2 y y + 1 y + 2]
|
|
1006
|
+
[ 2*y 2*y + 1 2*y + 2 0 1]
|
|
1007
|
+
[ 2 y y + 1 y + 2 2*y]
|
|
1008
|
+
sage: M = MatrixSpace(K,1,25)(sorted(list(K)))
|
|
1009
|
+
sage: N.rescale_row(1,1/y,1)
|
|
1010
|
+
sage: N
|
|
1011
|
+
[ 0 1 2 y y + 1]
|
|
1012
|
+
[ y + 2 2 y + 1 2*y 0]
|
|
1013
|
+
[ 1 2 y y + 1 y + 2]
|
|
1014
|
+
[ 2*y 2*y + 1 2*y + 2 0 1]
|
|
1015
|
+
[ 2 y y + 1 y + 2 2*y]
|
|
1016
|
+
"""
|
|
1017
|
+
if self.Data == NULL or start_col >= self.Data.Noc:
|
|
1018
|
+
return
|
|
1019
|
+
FfSetField(self.Data.Field)
|
|
1020
|
+
cdef FEL c = self._converter.field_to_fel(self._base_ring(s))
|
|
1021
|
+
cdef ssize_t byte_offset = start_col//MPB # how many full bytes will remain unchanged?
|
|
1022
|
+
cdef ssize_t remains = start_col % MPB # how many cols have to be treated separately?
|
|
1023
|
+
cdef ssize_t noc # what bunch of cols will be treated together?
|
|
1024
|
+
cdef int j
|
|
1025
|
+
cdef PTR row_head = MatGetPtr(self.Data, i) + byte_offset
|
|
1026
|
+
if remains:
|
|
1027
|
+
for j in range(remains,MPB):
|
|
1028
|
+
FfInsert(row_head, j, FfMul(FfExtract(row_head,j), c))
|
|
1029
|
+
byte_offset += 1
|
|
1030
|
+
row_head += 1
|
|
1031
|
+
noc = self.Data.Noc - byte_offset*MPB
|
|
1032
|
+
if noc:
|
|
1033
|
+
FfSetNoc(noc)
|
|
1034
|
+
FfMulRow(row_head, c)
|
|
1035
|
+
|
|
1036
|
+
cdef add_multiple_of_row_c(self, Py_ssize_t row_to, Py_ssize_t row_from, multiple, Py_ssize_t start_col) noexcept:
|
|
1037
|
+
"""
|
|
1038
|
+
Add the ``multiple``-fold of row ``row_from`` in-place to row ``row_to``, beginning with ``start_col``
|
|
1039
|
+
|
|
1040
|
+
EXAMPLES::
|
|
1041
|
+
|
|
1042
|
+
sage: K.<x> = GF(25)
|
|
1043
|
+
sage: M = MatrixSpace(K,5,5)(sorted(list(K)))
|
|
1044
|
+
sage: M
|
|
1045
|
+
[ 0 1 2 3 4]
|
|
1046
|
+
[ x x + 1 x + 2 x + 3 x + 4]
|
|
1047
|
+
[ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4]
|
|
1048
|
+
[ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4]
|
|
1049
|
+
[ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4]
|
|
1050
|
+
sage: M.add_multiple_of_row(2, 4, x) # indirect doctest
|
|
1051
|
+
sage: M
|
|
1052
|
+
[ 0 1 2 3 4]
|
|
1053
|
+
[ x x + 1 x + 2 x + 3 x + 4]
|
|
1054
|
+
[ x + 2 2*x + 3 3*x + 4 4*x 1]
|
|
1055
|
+
[ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4]
|
|
1056
|
+
[ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4]
|
|
1057
|
+
|
|
1058
|
+
TESTS:
|
|
1059
|
+
|
|
1060
|
+
The following tests against bugs fixed at :issue:`25490`. It demonstrates
|
|
1061
|
+
that the optional argument ``start_col`` is correctly dealt with,
|
|
1062
|
+
in a situation where several marks are packed into one byte
|
|
1063
|
+
and the current field and row size are temporarily changed.
|
|
1064
|
+
::
|
|
1065
|
+
|
|
1066
|
+
sage: L.<y> = GF(9)
|
|
1067
|
+
sage: N = MatrixSpace(L,5,5)((3*sorted(list(L)))[:25])
|
|
1068
|
+
sage: N
|
|
1069
|
+
[ 0 1 2 y y + 1]
|
|
1070
|
+
[ y + 2 2*y 2*y + 1 2*y + 2 0]
|
|
1071
|
+
[ 1 2 y y + 1 y + 2]
|
|
1072
|
+
[ 2*y 2*y + 1 2*y + 2 0 1]
|
|
1073
|
+
[ 2 y y + 1 y + 2 2*y]
|
|
1074
|
+
sage: M = MatrixSpace(K,1,25)(sorted(list(K)))
|
|
1075
|
+
sage: N.add_multiple_of_row(2,1,-N[2,1]/N[1,1],1)
|
|
1076
|
+
sage: N
|
|
1077
|
+
[ 0 1 2 y y + 1]
|
|
1078
|
+
[ y + 2 2*y 2*y + 1 2*y + 2 0]
|
|
1079
|
+
[ 1 0 2 2*y + 1 y + 2]
|
|
1080
|
+
[ 2*y 2*y + 1 2*y + 2 0 1]
|
|
1081
|
+
[ 2 y y + 1 y + 2 2*y]
|
|
1082
|
+
sage: y-(2*y+1)/y
|
|
1083
|
+
2
|
|
1084
|
+
"""
|
|
1085
|
+
if self.Data == NULL or start_col >= self.Data.Noc:
|
|
1086
|
+
return
|
|
1087
|
+
FfSetField(self.Data.Field)
|
|
1088
|
+
cdef FEL c = self._converter.field_to_fel(self._base_ring(multiple))
|
|
1089
|
+
cdef ssize_t byte_offset = start_col//MPB # how many full bytes will remain unchanged?
|
|
1090
|
+
cdef ssize_t remains = start_col % MPB # how many cols have to be treated separately?
|
|
1091
|
+
cdef ssize_t noc # what bunch of cols will be treated together?
|
|
1092
|
+
cdef PTR row_to_head = MatGetPtr(self.Data, row_to)+byte_offset
|
|
1093
|
+
cdef PTR row_from_head = MatGetPtr(self.Data, row_from)+byte_offset
|
|
1094
|
+
if remains:
|
|
1095
|
+
for j in range(remains,MPB):
|
|
1096
|
+
FfInsert(row_to_head, j, FfAdd(FfExtract(row_to_head,j), FfMul(FfExtract(row_from_head,j), c)))
|
|
1097
|
+
byte_offset += 1
|
|
1098
|
+
row_to_head += 1
|
|
1099
|
+
row_from_head += 1
|
|
1100
|
+
noc = self.Data.Noc - byte_offset*MPB
|
|
1101
|
+
if noc:
|
|
1102
|
+
FfSetNoc(noc)
|
|
1103
|
+
FfAddMulRow(row_to_head, row_from_head, c)
|
|
1104
|
+
|
|
1105
|
+
cdef swap_rows_c(self, Py_ssize_t row1, Py_ssize_t row2) noexcept:
|
|
1106
|
+
"""
|
|
1107
|
+
Swap the rows ``row1`` and ``row2`` in-place.
|
|
1108
|
+
|
|
1109
|
+
EXAMPLES::
|
|
1110
|
+
|
|
1111
|
+
sage: K.<x> = GF(25)
|
|
1112
|
+
sage: M = MatrixSpace(K,5,5)(sorted(list(K)))
|
|
1113
|
+
sage: M
|
|
1114
|
+
[ 0 1 2 3 4]
|
|
1115
|
+
[ x x + 1 x + 2 x + 3 x + 4]
|
|
1116
|
+
[ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4]
|
|
1117
|
+
[ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4]
|
|
1118
|
+
[ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4]
|
|
1119
|
+
sage: M.swap_rows(1, 3) # indirect doctest
|
|
1120
|
+
sage: M
|
|
1121
|
+
[ 0 1 2 3 4]
|
|
1122
|
+
[ 3*x 3*x + 1 3*x + 2 3*x + 3 3*x + 4]
|
|
1123
|
+
[ 2*x 2*x + 1 2*x + 2 2*x + 3 2*x + 4]
|
|
1124
|
+
[ x x + 1 x + 2 x + 3 x + 4]
|
|
1125
|
+
[ 4*x 4*x + 1 4*x + 2 4*x + 3 4*x + 4]
|
|
1126
|
+
"""
|
|
1127
|
+
if not self.Data:
|
|
1128
|
+
raise ValueError("This matrix is empty")
|
|
1129
|
+
FfSetField(self.Data.Field)
|
|
1130
|
+
FfSetNoc(self.Data.Noc)
|
|
1131
|
+
FfSwapRows(MatGetPtr(self.Data, row1), MatGetPtr(self.Data, row2))
|
|
1132
|
+
|
|
1133
|
+
def trace(self):
|
|
1134
|
+
"""
|
|
1135
|
+
Trace of this matrix, i.e., the sum of diagonal elements.
|
|
1136
|
+
|
|
1137
|
+
EXAMPLES::
|
|
1138
|
+
|
|
1139
|
+
sage: K.<x> = GF(125)
|
|
1140
|
+
sage: MatrixSpace(K,7,7)(x).trace()
|
|
1141
|
+
2*x
|
|
1142
|
+
"""
|
|
1143
|
+
if self._nrows != self._ncols:
|
|
1144
|
+
raise ValueError("self must be a square matrix")
|
|
1145
|
+
return self._converter.fel_to_field(MatTrace(self.Data))
|
|
1146
|
+
|
|
1147
|
+
cdef _stack_impl(self, bottom) noexcept:
|
|
1148
|
+
r"""
|
|
1149
|
+
Stack ``self`` on top of ``bottom``.
|
|
1150
|
+
|
|
1151
|
+
INPUT:
|
|
1152
|
+
|
|
1153
|
+
- ``bottom`` -- a matrix with the same number of columns as ``self``
|
|
1154
|
+
|
|
1155
|
+
EXAMPLES::
|
|
1156
|
+
|
|
1157
|
+
sage: M = MatrixSpace(GF(9,'x'),1,9)(sorted(list(GF(9,'x'))))
|
|
1158
|
+
sage: M
|
|
1159
|
+
[ 0 1 2 x x + 1 x + 2 2*x 2*x + 1 2*x + 2]
|
|
1160
|
+
sage: M.stack(M)
|
|
1161
|
+
[ 0 1 2 x x + 1 x + 2 2*x 2*x + 1 2*x + 2]
|
|
1162
|
+
[ 0 1 2 x x + 1 x + 2 2*x 2*x + 1 2*x + 2]
|
|
1163
|
+
|
|
1164
|
+
Check that we can stack a vector (:issue:`31708`)::
|
|
1165
|
+
|
|
1166
|
+
sage: R.<a> = GF(3^3)
|
|
1167
|
+
sage: M = matrix(R, [[1,1],[0,a+1]])
|
|
1168
|
+
sage: M.stack(vector(R, [a,0]))
|
|
1169
|
+
[ 1 1]
|
|
1170
|
+
[ 0 a + 1]
|
|
1171
|
+
[ a 0]
|
|
1172
|
+
"""
|
|
1173
|
+
cdef Matrix_gfpn_dense other = <Matrix_gfpn_dense> bottom
|
|
1174
|
+
|
|
1175
|
+
if self._nrows == 0 or self.Data == NULL:
|
|
1176
|
+
return other.__copy__()
|
|
1177
|
+
if other._nrows == 0 or other.Data == NULL:
|
|
1178
|
+
return self.__copy__()
|
|
1179
|
+
sig_on()
|
|
1180
|
+
try:
|
|
1181
|
+
mat = MatAlloc(self.Data.Field, self.Data.Nor+other.Data.Nor, self.Data.Noc)
|
|
1182
|
+
memcpy(mat.Data, self.Data.Data, FfCurrentRowSize*self.Data.Nor)
|
|
1183
|
+
memcpy(MatGetPtr(mat, self.Data.Nor), other.Data.Data, FfCurrentRowSize*other.Data.Nor)
|
|
1184
|
+
finally:
|
|
1185
|
+
sig_off()
|
|
1186
|
+
return new_mtx(mat, self)
|
|
1187
|
+
|
|
1188
|
+
cpdef _add_(self, right) noexcept:
|
|
1189
|
+
"""
|
|
1190
|
+
TESTS::
|
|
1191
|
+
|
|
1192
|
+
sage: K.<x> = GF(9)
|
|
1193
|
+
sage: M = MatrixSpace(K,3,3)(sorted(list(K)))
|
|
1194
|
+
sage: N = MatrixSpace(K,3,3)(2*x)
|
|
1195
|
+
sage: M+N # indirect doctest
|
|
1196
|
+
[ 2*x 1 2]
|
|
1197
|
+
[ x 1 x + 2]
|
|
1198
|
+
[ 2*x 2*x + 1 x + 2]
|
|
1199
|
+
"""
|
|
1200
|
+
cdef Matrix_gfpn_dense Self = self
|
|
1201
|
+
cdef Matrix_gfpn_dense Right = right
|
|
1202
|
+
assert Self is not None
|
|
1203
|
+
assert Right is not None
|
|
1204
|
+
if Self.Data == NULL or Right.Data == NULL:
|
|
1205
|
+
raise NotImplementedError("The matrices must not be empty")
|
|
1206
|
+
sig_on()
|
|
1207
|
+
try:
|
|
1208
|
+
mat = MatDup(Self.Data)
|
|
1209
|
+
MatAdd(mat, Right.Data)
|
|
1210
|
+
finally:
|
|
1211
|
+
sig_off()
|
|
1212
|
+
return new_mtx(mat, self)
|
|
1213
|
+
|
|
1214
|
+
cpdef _sub_(self, right) noexcept:
|
|
1215
|
+
"""
|
|
1216
|
+
TESTS::
|
|
1217
|
+
|
|
1218
|
+
sage: K.<x> = GF(9)
|
|
1219
|
+
sage: M = MatrixSpace(K,3,3)(sorted(list(K)))
|
|
1220
|
+
sage: N = MatrixSpace(K,3,3)(2*x)
|
|
1221
|
+
sage: M-N # indirect doctest
|
|
1222
|
+
[ x 1 2]
|
|
1223
|
+
[ x 2*x + 1 x + 2]
|
|
1224
|
+
[ 2*x 2*x + 1 2]
|
|
1225
|
+
"""
|
|
1226
|
+
cdef Matrix_gfpn_dense Self = self
|
|
1227
|
+
cdef Matrix_gfpn_dense Right = right
|
|
1228
|
+
assert Self is not None
|
|
1229
|
+
assert Right is not None
|
|
1230
|
+
if Self.Data == NULL or Right.Data == NULL:
|
|
1231
|
+
raise NotImplementedError("The matrices must not be empty")
|
|
1232
|
+
sig_on()
|
|
1233
|
+
try:
|
|
1234
|
+
mat = MatDup(Self.Data)
|
|
1235
|
+
MatAddMul(mat, Right.Data, mtx_taddinv[1])
|
|
1236
|
+
finally:
|
|
1237
|
+
sig_off()
|
|
1238
|
+
return new_mtx(mat, self)
|
|
1239
|
+
|
|
1240
|
+
def __neg__(self):
|
|
1241
|
+
"""
|
|
1242
|
+
TESTS::
|
|
1243
|
+
|
|
1244
|
+
sage: M = MatrixSpace(GF(9,'x'),3,3)(sorted(list(GF(9,'x'))))
|
|
1245
|
+
sage: -M
|
|
1246
|
+
[ 0 2 1]
|
|
1247
|
+
[ 2*x 2*x + 2 2*x + 1]
|
|
1248
|
+
[ x x + 2 x + 1]
|
|
1249
|
+
|
|
1250
|
+
::
|
|
1251
|
+
|
|
1252
|
+
sage: M = MatrixSpace(GF(125,'x'),10,30).random_element()
|
|
1253
|
+
sage: N = MatrixSpace(GF(125,'x'),10,30).random_element()
|
|
1254
|
+
sage: M + (-N) == M - N == -(N - M)
|
|
1255
|
+
True
|
|
1256
|
+
"""
|
|
1257
|
+
if self.Data == NULL:
|
|
1258
|
+
raise ValueError("The matrix must not be empty")
|
|
1259
|
+
return self._lmul_(self._base_ring(-1))
|
|
1260
|
+
|
|
1261
|
+
cpdef _lmul_(self, Element right) noexcept:
|
|
1262
|
+
"""
|
|
1263
|
+
EXAMPLES::
|
|
1264
|
+
|
|
1265
|
+
sage: M = MatrixSpace(GF(9,'x'),3,3)(sorted(list(GF(9,'x'))))
|
|
1266
|
+
sage: K.<x> = GF(9)
|
|
1267
|
+
sage: M = MatrixSpace(K,3,3)(sorted(list(K)))
|
|
1268
|
+
sage: x*M # indirect doctest
|
|
1269
|
+
[ 0 x 2*x]
|
|
1270
|
+
[ x + 1 2*x + 1 1]
|
|
1271
|
+
[2*x + 2 2 x + 2]
|
|
1272
|
+
sage: M*x # indirect doctest
|
|
1273
|
+
[ 0 x 2*x]
|
|
1274
|
+
[ x + 1 2*x + 1 1]
|
|
1275
|
+
[2*x + 2 2 x + 2]
|
|
1276
|
+
sage: -M == (-1)*M
|
|
1277
|
+
True
|
|
1278
|
+
|
|
1279
|
+
TESTS:
|
|
1280
|
+
|
|
1281
|
+
Make sure that :issue:`25076` remains fixed::
|
|
1282
|
+
|
|
1283
|
+
sage: M == M*int(4) == int(4)*M
|
|
1284
|
+
True
|
|
1285
|
+
"""
|
|
1286
|
+
if self.Data == NULL:
|
|
1287
|
+
return self.__copy__()
|
|
1288
|
+
FfSetField(self.Data.Field)
|
|
1289
|
+
sig_on()
|
|
1290
|
+
try:
|
|
1291
|
+
mat = MatDup(self.Data)
|
|
1292
|
+
MatMulScalar(mat, self._converter.field_to_fel(right))
|
|
1293
|
+
finally:
|
|
1294
|
+
sig_off()
|
|
1295
|
+
return new_mtx(mat, self)
|
|
1296
|
+
|
|
1297
|
+
cdef int _strassen_default_cutoff(self, sage.matrix.matrix0.Matrix right) except -2:
|
|
1298
|
+
# Surprisingly, Winograd-Strassen can compete with school book
|
|
1299
|
+
# multiplication for smallish matrices, and of course it is
|
|
1300
|
+
# asymptotically faster. So, we used it by default.
|
|
1301
|
+
return 0
|
|
1302
|
+
|
|
1303
|
+
cpdef Matrix_gfpn_dense _multiply_classical(Matrix_gfpn_dense self, Matrix_gfpn_dense right) noexcept:
|
|
1304
|
+
"""
|
|
1305
|
+
Multiplication using the cubic school book multiplication algorithm.
|
|
1306
|
+
|
|
1307
|
+
EXAMPLES:
|
|
1308
|
+
|
|
1309
|
+
Since by default the asymptotically faster Strassen-Winograd
|
|
1310
|
+
multiplication algorithm is used, the following is a valid
|
|
1311
|
+
consistency check::
|
|
1312
|
+
|
|
1313
|
+
sage: M = MatrixSpace(GF(9,'x'),1000,500).random_element()
|
|
1314
|
+
sage: N = MatrixSpace(GF(9,'x'),500,2000).random_element()
|
|
1315
|
+
sage: M*N == M._multiply_classical(N)
|
|
1316
|
+
True
|
|
1317
|
+
"""
|
|
1318
|
+
"multiply two meataxe matrices by the school book algorithm"
|
|
1319
|
+
if self.Data == NULL or right.Data == NULL:
|
|
1320
|
+
raise ValueError("The matrices must not be empty")
|
|
1321
|
+
if self._ncols != right._nrows:
|
|
1322
|
+
raise ArithmeticError("left ncols must match right nrows")
|
|
1323
|
+
sig_on()
|
|
1324
|
+
try:
|
|
1325
|
+
mat = MatDup(self.Data)
|
|
1326
|
+
MatMul(mat, right.Data)
|
|
1327
|
+
finally:
|
|
1328
|
+
sig_off()
|
|
1329
|
+
return new_mtx(mat, self)
|
|
1330
|
+
|
|
1331
|
+
cpdef Matrix_gfpn_dense _multiply_strassen(Matrix_gfpn_dense self, Matrix_gfpn_dense right, cutoff=0) noexcept:
|
|
1332
|
+
"""
|
|
1333
|
+
Matrix multiplication using the asymptotically fast Strassen-Winograd algorithm.
|
|
1334
|
+
|
|
1335
|
+
INPUT:
|
|
1336
|
+
|
|
1337
|
+
- ``right`` -- a matrix of dimensions suitable to do multiplication
|
|
1338
|
+
- ``cutoff`` -- (optional integer) indicates the minimal size of submatrices
|
|
1339
|
+
that will be considered in the divide-and-conquer algorithm. The size is
|
|
1340
|
+
*not* expressed by the number of rows/columns, but the rowsize expressed
|
|
1341
|
+
in bytes. Depending on the base field, one byte may represent up to eight
|
|
1342
|
+
entries in a matrix row. The default is ``sizeof(long)^2/2`` byte.
|
|
1343
|
+
|
|
1344
|
+
EXAMPLES:
|
|
1345
|
+
|
|
1346
|
+
We test that different cutoffs yield the same result::
|
|
1347
|
+
|
|
1348
|
+
sage: M = MatrixSpace(GF(9,'x'),1500,600).random_element()
|
|
1349
|
+
sage: N = MatrixSpace(GF(9,'x'),600,1500).random_element()
|
|
1350
|
+
sage: M._multiply_strassen(N) == M._multiply_strassen(N,80) == M._multiply_strassen(N,2)
|
|
1351
|
+
True
|
|
1352
|
+
"""
|
|
1353
|
+
if self.Data == NULL or right.Data == NULL:
|
|
1354
|
+
raise ValueError("The matrices must not be empty")
|
|
1355
|
+
if self._ncols != right._nrows:
|
|
1356
|
+
raise ArithmeticError("left ncols must match right nrows")
|
|
1357
|
+
StrassenSetCutoff(cutoff // sizeof(long))
|
|
1358
|
+
sig_on()
|
|
1359
|
+
try:
|
|
1360
|
+
mat = MatAlloc(self.Data.Field, self._nrows, right._ncols)
|
|
1361
|
+
MatMulStrassen(mat, self.Data, right.Data)
|
|
1362
|
+
finally:
|
|
1363
|
+
sig_off()
|
|
1364
|
+
return new_mtx(mat, self)
|
|
1365
|
+
|
|
1366
|
+
cdef _mul_long(self, long n) noexcept:
|
|
1367
|
+
"""
|
|
1368
|
+
Multiply an MTX matrix with a field element represented by an integer.
|
|
1369
|
+
|
|
1370
|
+
TESTS::
|
|
1371
|
+
|
|
1372
|
+
sage: M = random_matrix(GF(9,'x'), 64,51)
|
|
1373
|
+
sage: M == M*int(4) == int(4)*M
|
|
1374
|
+
True
|
|
1375
|
+
sage: M*int(-1)+M == 0
|
|
1376
|
+
True
|
|
1377
|
+
"""
|
|
1378
|
+
if self.Data == NULL:
|
|
1379
|
+
raise ValueError("The matrix must not be empty")
|
|
1380
|
+
FfSetField(self.Data.Field)
|
|
1381
|
+
cdef FEL r
|
|
1382
|
+
with cython.cdivision(False):
|
|
1383
|
+
r = FfFromInt(n % FfChar)
|
|
1384
|
+
sig_on()
|
|
1385
|
+
try:
|
|
1386
|
+
mat = MatDup(self.Data)
|
|
1387
|
+
MatMulScalar(mat, r)
|
|
1388
|
+
finally:
|
|
1389
|
+
sig_off()
|
|
1390
|
+
return new_mtx(mat, self)
|
|
1391
|
+
|
|
1392
|
+
def __truediv__(Matrix_gfpn_dense self, p):
|
|
1393
|
+
"""
|
|
1394
|
+
Divide a matrix by a scalar.
|
|
1395
|
+
|
|
1396
|
+
EXAMPLES::
|
|
1397
|
+
|
|
1398
|
+
sage: K.<x> = GF(9)
|
|
1399
|
+
sage: M = MatrixSpace(K,3,3)(sorted(list(K)))
|
|
1400
|
+
sage: M
|
|
1401
|
+
[ 0 1 2]
|
|
1402
|
+
[ x x + 1 x + 2]
|
|
1403
|
+
[ 2*x 2*x + 1 2*x + 2]
|
|
1404
|
+
sage: M/2 # indirect doctest
|
|
1405
|
+
[ 0 2 1]
|
|
1406
|
+
[ 2*x 2*x + 2 2*x + 1]
|
|
1407
|
+
[ x x + 2 x + 1]
|
|
1408
|
+
sage: M/x
|
|
1409
|
+
[ 0 x + 2 2*x + 1]
|
|
1410
|
+
[ 1 x 2*x + 2]
|
|
1411
|
+
[ 2 x + 1 2*x]
|
|
1412
|
+
"""
|
|
1413
|
+
if self.Data == NULL:
|
|
1414
|
+
return self.__copy__()
|
|
1415
|
+
if not p:
|
|
1416
|
+
raise ZeroDivisionError
|
|
1417
|
+
if p not in self._base_ring:
|
|
1418
|
+
raise ValueError("{} is not a scalar".format(p))
|
|
1419
|
+
p = self._base_ring(p)
|
|
1420
|
+
FfSetField(self.Data.Field)
|
|
1421
|
+
cdef FEL r = mtx_tmultinv[self._converter.field_to_fel(p)]
|
|
1422
|
+
sig_on()
|
|
1423
|
+
try:
|
|
1424
|
+
mat = MatDup(self.Data)
|
|
1425
|
+
MatMulScalar(mat, r)
|
|
1426
|
+
finally:
|
|
1427
|
+
sig_off()
|
|
1428
|
+
return new_mtx(mat, self)
|
|
1429
|
+
|
|
1430
|
+
def __invert__(Matrix_gfpn_dense self):
|
|
1431
|
+
"""
|
|
1432
|
+
Multiplicative inverse of this matrix (if available).
|
|
1433
|
+
|
|
1434
|
+
TESTS::
|
|
1435
|
+
|
|
1436
|
+
sage: MS = MatrixSpace(GF(9,'x'),500)
|
|
1437
|
+
sage: while 1:
|
|
1438
|
+
....: M = MS.random_element()
|
|
1439
|
+
....: if M.rank() == 500:
|
|
1440
|
+
....: break
|
|
1441
|
+
sage: Minv = ~M # indirect doctest
|
|
1442
|
+
sage: Minv*M == M*Minv == 1
|
|
1443
|
+
True
|
|
1444
|
+
|
|
1445
|
+
We use the occasion to demonstrate that errors in MeatAxe are
|
|
1446
|
+
correctly handled in Sage::
|
|
1447
|
+
|
|
1448
|
+
sage: MS = MatrixSpace(GF(25,'x'),5)
|
|
1449
|
+
sage: while 1:
|
|
1450
|
+
....: M = MS.random_element(density=0.4)
|
|
1451
|
+
....: if M.rank() < 5:
|
|
1452
|
+
....: break
|
|
1453
|
+
sage: ~M
|
|
1454
|
+
Traceback (most recent call last):
|
|
1455
|
+
...
|
|
1456
|
+
ZeroDivisionError: Division by zero in file matinv.c (line 50)
|
|
1457
|
+
"""
|
|
1458
|
+
if self.Data == NULL:
|
|
1459
|
+
raise ValueError("The matrix must not be empty")
|
|
1460
|
+
if not self.is_square():
|
|
1461
|
+
raise ArithmeticError("self must be a square matrix")
|
|
1462
|
+
sig_on()
|
|
1463
|
+
try:
|
|
1464
|
+
mat = MatInverse(self.Data)
|
|
1465
|
+
finally:
|
|
1466
|
+
# Inverting a singular matrix causes MatInverse to raise a
|
|
1467
|
+
# ZeroDivisionError (see sage_meataxe_error_handler).
|
|
1468
|
+
# So we need to support exceptions here.
|
|
1469
|
+
sig_off()
|
|
1470
|
+
return new_mtx(mat, self)
|
|
1471
|
+
|
|
1472
|
+
def transpose(Matrix_gfpn_dense self):
|
|
1473
|
+
"""
|
|
1474
|
+
Return the transposed matrix.
|
|
1475
|
+
|
|
1476
|
+
EXAMPLES::
|
|
1477
|
+
|
|
1478
|
+
sage: K.<x> = GF(9)
|
|
1479
|
+
sage: M = MatrixSpace(K, 2,4)(sorted(list(K)[1:]))
|
|
1480
|
+
sage: M
|
|
1481
|
+
[ 1 2 x x + 1]
|
|
1482
|
+
[ x + 2 2*x 2*x + 1 2*x + 2]
|
|
1483
|
+
sage: M.transpose()
|
|
1484
|
+
[ 1 x + 2]
|
|
1485
|
+
[ 2 2*x]
|
|
1486
|
+
[ x 2*x + 1]
|
|
1487
|
+
[ x + 1 2*x + 2]
|
|
1488
|
+
"""
|
|
1489
|
+
if self.Data == NULL:
|
|
1490
|
+
raise ValueError("The matrix must not be empty")
|
|
1491
|
+
sig_on()
|
|
1492
|
+
try:
|
|
1493
|
+
mat = MatTransposed(self.Data)
|
|
1494
|
+
finally:
|
|
1495
|
+
sig_off()
|
|
1496
|
+
return new_mtx(mat, self)
|
|
1497
|
+
|
|
1498
|
+
def order(self):
|
|
1499
|
+
"""
|
|
1500
|
+
Return the multiplicative order of this matrix.
|
|
1501
|
+
|
|
1502
|
+
EXAMPLES::
|
|
1503
|
+
|
|
1504
|
+
sage: K.<x> = GF(27)
|
|
1505
|
+
sage: M = MatrixSpace(K, 4)([2*x^2 + 2*x, 2*x^2 + x, 2*x^2 + x + 1,
|
|
1506
|
+
....: x^2 + x + 2, x + 2, x^2, 2*x + 2, 2*x^2 + 2*x, 2*x^2 + 1,
|
|
1507
|
+
....: 1, 2, x^2 + 2*x + 1, x^2 + x + 2, x + 1, 2*x^2 + 2*x, x^2 + x])
|
|
1508
|
+
sage: M.order()
|
|
1509
|
+
104
|
|
1510
|
+
sage: M^104 == 1
|
|
1511
|
+
True
|
|
1512
|
+
sage: M^103 == 1
|
|
1513
|
+
False
|
|
1514
|
+
"""
|
|
1515
|
+
if self.Data == NULL:
|
|
1516
|
+
raise ValueError("The matrix must not be empty")
|
|
1517
|
+
if self.Data.Nor != self.Data.Noc:
|
|
1518
|
+
raise ValueError("only defined for square matrices")
|
|
1519
|
+
sig_on()
|
|
1520
|
+
try:
|
|
1521
|
+
o = MatOrder(self.Data)
|
|
1522
|
+
finally:
|
|
1523
|
+
sig_off()
|
|
1524
|
+
if o == -1:
|
|
1525
|
+
raise ArithmeticError("order too large")
|
|
1526
|
+
else:
|
|
1527
|
+
return o
|
|
1528
|
+
|
|
1529
|
+
###################
|
|
1530
|
+
## Gauss algorithm
|
|
1531
|
+
|
|
1532
|
+
def left_kernel_matrix(self):
|
|
1533
|
+
"""
|
|
1534
|
+
Return the null space of this matrix, represented as a matrix.
|
|
1535
|
+
|
|
1536
|
+
NOTE:
|
|
1537
|
+
|
|
1538
|
+
- For a matrix `M`, ``M.left_kernel_matrix()*M`` is a null matrix.
|
|
1539
|
+
- The command `M.left_kernel()` uses a generic implementation in Sage,
|
|
1540
|
+
that relies on computing the echelon form of the transposed
|
|
1541
|
+
matrix. This method however uses a MeatAxe function to compute
|
|
1542
|
+
the left kernel matrix.
|
|
1543
|
+
|
|
1544
|
+
EXAMPLES::
|
|
1545
|
+
|
|
1546
|
+
sage: K.<x> = GF(25)
|
|
1547
|
+
sage: M = MatrixSpace(K, 10)()
|
|
1548
|
+
sage: entries = [((0, 2), x), ((0, 4), 3*x + 2),
|
|
1549
|
+
....: ((0, 8), 2*x), ((1, 1), x + 3), ((1, 5), 3*x),
|
|
1550
|
+
....: ((1, 6), x + 4), ((2, 3), 2*x), ((2, 5), 4*x + 1),
|
|
1551
|
+
....: ((2, 6), 4), ((3, 4), x + 4), ((3, 5), x + 1),
|
|
1552
|
+
....: ((5, 5), 3*x), ((5, 7), x + 3), ((6, 1), x),
|
|
1553
|
+
....: ((6, 2), x + 1), ((6, 5), x + 1), ((8, 2), 4),
|
|
1554
|
+
....: ((8, 8), 4), ((8, 9), x + 3), ((9, 8), 4*x + 2)]
|
|
1555
|
+
sage: for (i,j),v in entries: M[i,j] = v
|
|
1556
|
+
sage: M.left_kernel()
|
|
1557
|
+
Vector space of degree 10 and dimension 2 over Finite Field in x of size 5^2
|
|
1558
|
+
Basis matrix:
|
|
1559
|
+
[0 0 0 0 1 0 0 0 0 0]
|
|
1560
|
+
[0 0 0 0 0 0 0 1 0 0]
|
|
1561
|
+
sage: M.left_kernel_matrix()
|
|
1562
|
+
[0 0 0 0 1 0 0 0 0 0]
|
|
1563
|
+
[0 0 0 0 0 0 0 1 0 0]
|
|
1564
|
+
"""
|
|
1565
|
+
cdef Matrix_gfpn_dense OUT = self.fetch("left_kernel_matrix")
|
|
1566
|
+
if OUT is not None:
|
|
1567
|
+
return OUT
|
|
1568
|
+
if self.Data is NULL:
|
|
1569
|
+
raise ValueError("The matrix must not be empty")
|
|
1570
|
+
sig_on()
|
|
1571
|
+
try:
|
|
1572
|
+
mat = MatNullSpace(self.Data)
|
|
1573
|
+
finally:
|
|
1574
|
+
sig_off()
|
|
1575
|
+
OUT = new_mtx(mat, self)
|
|
1576
|
+
self.cache("left_kernel_matrix", OUT)
|
|
1577
|
+
return OUT
|
|
1578
|
+
|
|
1579
|
+
cpdef _echelon_in_place(self, str algorithm) noexcept:
|
|
1580
|
+
"""
|
|
1581
|
+
Change this matrix into echelon form, using classical
|
|
1582
|
+
Gaussian elimination, and return the pivots.
|
|
1583
|
+
|
|
1584
|
+
.. NOTE::
|
|
1585
|
+
|
|
1586
|
+
Use :meth:`_echelon_in_place_classical`, which can take the
|
|
1587
|
+
keyword ``reduced``.
|
|
1588
|
+
|
|
1589
|
+
EXAMPLES::
|
|
1590
|
+
|
|
1591
|
+
sage: K.<x> = GF(25)
|
|
1592
|
+
sage: M = MatrixSpace(K, 9, 3)()
|
|
1593
|
+
sage: entries = [((0, 2), x), ((0, 4), 3*x + 2),
|
|
1594
|
+
....: ((0, 8), 2*x), ((1, 1), x + 3), ((1, 5), 3*x),
|
|
1595
|
+
....: ((1, 6), x + 4), ((2, 0), 2*x), ((2, 5), 4*x + 1)]
|
|
1596
|
+
sage: for (i,j),v in entries: M[j,i] = v
|
|
1597
|
+
sage: M
|
|
1598
|
+
[ 0 0 2*x]
|
|
1599
|
+
[ 0 x + 3 0]
|
|
1600
|
+
[ x 0 0]
|
|
1601
|
+
[ 0 0 0]
|
|
1602
|
+
[3*x + 2 0 0]
|
|
1603
|
+
[ 0 3*x 4*x + 1]
|
|
1604
|
+
[ 0 x + 4 0]
|
|
1605
|
+
[ 0 0 0]
|
|
1606
|
+
[ 2*x 0 0]
|
|
1607
|
+
sage: M._echelon_in_place("classical")
|
|
1608
|
+
(0, 1, 2)
|
|
1609
|
+
sage: M
|
|
1610
|
+
[1 0 0]
|
|
1611
|
+
[0 1 0]
|
|
1612
|
+
[0 0 1]
|
|
1613
|
+
[0 0 0]
|
|
1614
|
+
[0 0 0]
|
|
1615
|
+
[0 0 0]
|
|
1616
|
+
[0 0 0]
|
|
1617
|
+
[0 0 0]
|
|
1618
|
+
[0 0 0]
|
|
1619
|
+
"""
|
|
1620
|
+
return self._echelon_in_place_classical()
|
|
1621
|
+
|
|
1622
|
+
def _echelon_in_place_classical(self, bint reduced=True, **kwds):
|
|
1623
|
+
"""
|
|
1624
|
+
Change this matrix into echelon form, using classical Gaussian elimination
|
|
1625
|
+
and return its pivots.
|
|
1626
|
+
|
|
1627
|
+
INPUT:
|
|
1628
|
+
|
|
1629
|
+
- ``reduced`` -- boolean (default: ``True``); will result
|
|
1630
|
+
in the row-reduced echelon form (otherwise, only a
|
|
1631
|
+
semi-echelon form results)
|
|
1632
|
+
|
|
1633
|
+
EXAMPLES::
|
|
1634
|
+
|
|
1635
|
+
sage: K.<x> = GF(25)
|
|
1636
|
+
sage: M = MatrixSpace(K, 10)()
|
|
1637
|
+
sage: entries = [((0, 2), x), ((0, 4), 3*x + 2),
|
|
1638
|
+
....: ((0, 8), 2*x), ((1, 1), x + 3), ((1, 5), 3*x),
|
|
1639
|
+
....: ((1, 6), x + 4), ((2, 3), 2*x), ((2, 5), 4*x + 1),
|
|
1640
|
+
....: ((2, 6), 4), ((3, 4), x + 4), ((3, 5), x + 1),
|
|
1641
|
+
....: ((5, 5), 3*x), ((5, 7), x + 3), ((6, 1), x),
|
|
1642
|
+
....: ((6, 2), x + 1), ((6, 5), x + 1), ((8, 2), 4),
|
|
1643
|
+
....: ((8, 8), 4), ((8, 9), x + 3), ((9, 8), 4*x + 2)]
|
|
1644
|
+
sage: for (i,j),v in entries: M[i,j] = v
|
|
1645
|
+
sage: M
|
|
1646
|
+
[ 0 0 x 0 3*x + 2 0 0 0 2*x 0]
|
|
1647
|
+
[ 0 x + 3 0 0 0 3*x x + 4 0 0 0]
|
|
1648
|
+
[ 0 0 0 2*x 0 4*x + 1 4 0 0 0]
|
|
1649
|
+
[ 0 0 0 0 x + 4 x + 1 0 0 0 0]
|
|
1650
|
+
[ 0 0 0 0 0 0 0 0 0 0]
|
|
1651
|
+
[ 0 0 0 0 0 3*x 0 x + 3 0 0]
|
|
1652
|
+
[ 0 x x + 1 0 0 x + 1 0 0 0 0]
|
|
1653
|
+
[ 0 0 0 0 0 0 0 0 0 0]
|
|
1654
|
+
[ 0 0 4 0 0 0 0 0 4 x + 3]
|
|
1655
|
+
[ 0 0 0 0 0 0 0 0 4*x + 2 0]
|
|
1656
|
+
sage: M.echelon_form() # indirect doctest
|
|
1657
|
+
[ 0 1 0 0 0 0 0 0 0 4*x + 4]
|
|
1658
|
+
[ 0 0 1 0 0 0 0 0 0 4*x + 2]
|
|
1659
|
+
[ 0 0 0 1 0 0 0 0 0 3*x + 4]
|
|
1660
|
+
[ 0 0 0 0 1 0 0 0 0 3*x + 3]
|
|
1661
|
+
[ 0 0 0 0 0 1 0 0 0 2*x + 3]
|
|
1662
|
+
[ 0 0 0 0 0 0 1 0 0 x]
|
|
1663
|
+
[ 0 0 0 0 0 0 0 1 0 2*x + 2]
|
|
1664
|
+
[ 0 0 0 0 0 0 0 0 1 0]
|
|
1665
|
+
[ 0 0 0 0 0 0 0 0 0 0]
|
|
1666
|
+
[ 0 0 0 0 0 0 0 0 0 0]
|
|
1667
|
+
|
|
1668
|
+
A semi-echelon form can be produced by invoking the single-underscore
|
|
1669
|
+
method directly::
|
|
1670
|
+
|
|
1671
|
+
sage: N = copy(M)
|
|
1672
|
+
sage: N._echelon_in_place_classical(reduced=False)
|
|
1673
|
+
(2, 1, 3, 4, 5, 6, 7, 8)
|
|
1674
|
+
sage: N
|
|
1675
|
+
[ 0 0 x 0 3*x + 2 0 0 0 2*x 0]
|
|
1676
|
+
[ 0 x + 3 0 0 0 3*x x + 4 0 0 0]
|
|
1677
|
+
[ 0 0 0 2*x 0 4*x + 1 4 0 0 0]
|
|
1678
|
+
[ 0 0 0 0 x + 4 x + 1 0 0 0 0]
|
|
1679
|
+
[ 0 0 0 0 0 3*x 0 x + 3 0 0]
|
|
1680
|
+
[ 0 0 0 0 0 0 2*x + 2 4*x 3*x + 3 0]
|
|
1681
|
+
[ 0 0 0 0 0 0 0 x + 1 1 x + 3]
|
|
1682
|
+
[ 0 0 0 0 0 0 0 0 4*x + 2 0]
|
|
1683
|
+
[ 0 0 0 0 0 0 0 0 0 0]
|
|
1684
|
+
[ 0 0 0 0 0 0 0 0 0 0]
|
|
1685
|
+
|
|
1686
|
+
TESTS:
|
|
1687
|
+
|
|
1688
|
+
We verify that the above echelon form is consistent with Sage's generic
|
|
1689
|
+
implementation of dense matrices::
|
|
1690
|
+
|
|
1691
|
+
sage: type(M)
|
|
1692
|
+
<class 'sage.matrix.matrix_gfpn_dense.Matrix_gfpn_dense'>
|
|
1693
|
+
sage: MS = MatrixSpace(M.base_ring(), M.nrows(), M.ncols(), implementation='generic')
|
|
1694
|
+
sage: X = MS(M)
|
|
1695
|
+
sage: type(X)
|
|
1696
|
+
<class 'sage.matrix.matrix_generic_dense.Matrix_generic_dense'>
|
|
1697
|
+
sage: X.echelon_form()
|
|
1698
|
+
[ 0 1 0 0 0 0 0 0 0 4*x + 4]
|
|
1699
|
+
[ 0 0 1 0 0 0 0 0 0 4*x + 2]
|
|
1700
|
+
[ 0 0 0 1 0 0 0 0 0 3*x + 4]
|
|
1701
|
+
[ 0 0 0 0 1 0 0 0 0 3*x + 3]
|
|
1702
|
+
[ 0 0 0 0 0 1 0 0 0 2*x + 3]
|
|
1703
|
+
[ 0 0 0 0 0 0 1 0 0 x]
|
|
1704
|
+
[ 0 0 0 0 0 0 0 1 0 2*x + 2]
|
|
1705
|
+
[ 0 0 0 0 0 0 0 0 1 0]
|
|
1706
|
+
[ 0 0 0 0 0 0 0 0 0 0]
|
|
1707
|
+
[ 0 0 0 0 0 0 0 0 0 0]
|
|
1708
|
+
|
|
1709
|
+
The following was a problem in a preliminary version of the code::
|
|
1710
|
+
|
|
1711
|
+
sage: K.<a> = GF(25)
|
|
1712
|
+
sage: M = MatrixSpace(K, 2, 4)([4, 4, 1, 0, 0, 2*a+1, a+2, 1])
|
|
1713
|
+
sage: M
|
|
1714
|
+
[ 4 4 1 0]
|
|
1715
|
+
[ 0 2*a + 1 a + 2 1]
|
|
1716
|
+
sage: M.echelonize()
|
|
1717
|
+
sage: M
|
|
1718
|
+
[ 1 0 3*a + 4 2*a + 2]
|
|
1719
|
+
[ 0 1 2*a 3*a + 3]
|
|
1720
|
+
"""
|
|
1721
|
+
if self._nrows == 0 or self._ncols == 0:
|
|
1722
|
+
self.cache('in_echelon_form',True)
|
|
1723
|
+
self.cache('rank', 0)
|
|
1724
|
+
self.cache('pivots', ())
|
|
1725
|
+
return ()
|
|
1726
|
+
self._cache = None
|
|
1727
|
+
sig_on()
|
|
1728
|
+
try:
|
|
1729
|
+
MatEchelonize(self.Data)
|
|
1730
|
+
finally:
|
|
1731
|
+
sig_off()
|
|
1732
|
+
# Now, self.Data is in semi-echelon form.
|
|
1733
|
+
r = self.Data.Nor
|
|
1734
|
+
cdef size_t i, j, pos
|
|
1735
|
+
cdef PTR old, dest, src
|
|
1736
|
+
cdef FEL piv
|
|
1737
|
+
self.cache('rank', r)
|
|
1738
|
+
# Next, we do permutations to achieve the reduced echelon form,
|
|
1739
|
+
# if requested.
|
|
1740
|
+
if reduced:
|
|
1741
|
+
pivs = [(self.Data.PivotTable[i],i) for i in range(r)]
|
|
1742
|
+
pivs.sort()
|
|
1743
|
+
if pivs != [(self.Data.PivotTable[i],i) for i in range(r)] or self.Data.Nor < self._nrows:
|
|
1744
|
+
# We copy the row one by one, sorting their pivot positions
|
|
1745
|
+
old = self.Data.Data
|
|
1746
|
+
self.Data.Data = FfAlloc(self._nrows)
|
|
1747
|
+
for i, (pos,j) in enumerate(pivs):
|
|
1748
|
+
# We have to move row j to row i
|
|
1749
|
+
dest = self.Data.Data+FfCurrentRowSize*i
|
|
1750
|
+
memcpy(dest, old+FfCurrentRowSize*j, FfCurrentRowSize)
|
|
1751
|
+
self.Data.PivotTable[i] = pos
|
|
1752
|
+
sig_check()
|
|
1753
|
+
sig_free(old)
|
|
1754
|
+
self.Data.Nor = self._nrows
|
|
1755
|
+
# Now, the pivot columns are strictly increasing.
|
|
1756
|
+
# We now normalize each row, and annulate everything
|
|
1757
|
+
# above the pivot (currently, we only know that the matrix
|
|
1758
|
+
# is zero below the pivots).
|
|
1759
|
+
for i in range(r):
|
|
1760
|
+
src = MatGetPtr(self.Data, i)
|
|
1761
|
+
piv = FfExtract(src, self.Data.PivotTable[i])
|
|
1762
|
+
assert piv!=FF_ZERO
|
|
1763
|
+
if piv != FF_ONE:
|
|
1764
|
+
FfMulRow(src, mtx_tmultinv[piv])
|
|
1765
|
+
sig_check()
|
|
1766
|
+
for j in range(i):
|
|
1767
|
+
dest = MatGetPtr(self.Data, j)
|
|
1768
|
+
piv = FfExtract(dest, self.Data.PivotTable[i])
|
|
1769
|
+
if piv != FF_ONE:
|
|
1770
|
+
FfAddMulRow(dest, src, mtx_taddinv[piv])
|
|
1771
|
+
else:
|
|
1772
|
+
FfSubRow(dest, src)
|
|
1773
|
+
sig_check()
|
|
1774
|
+
elif self.Data.Nor < self._nrows:
|
|
1775
|
+
# Some rows may have vanished. In SageMath, we
|
|
1776
|
+
# want that the number of rows does not change,
|
|
1777
|
+
# thus, we have to append zero rows.
|
|
1778
|
+
self.Data.Data = <PTR>check_realloc(self.Data.Data, FfCurrentRowSize*self._nrows)
|
|
1779
|
+
memset(self.Data.Data + FfCurrentRowSize*self.Data.Nor, FF_ZERO, FfCurrentRowSize*(self._nrows-self.Data.Nor))
|
|
1780
|
+
self.Data.Nor = self._nrows
|
|
1781
|
+
self.cache('pivots', tuple(self.Data.PivotTable[i] for i in range(r)))
|
|
1782
|
+
self.cache('in_echelon_form',True)
|
|
1783
|
+
return self._cache['pivots']
|
|
1784
|
+
|
|
1785
|
+
|
|
1786
|
+
from sage.misc.superseded import deprecation_cython as deprecation
|
|
1787
|
+
|
|
1788
|
+
|
|
1789
|
+
def mtx_unpickle(f, int nr, int nc, data, bint m):
|
|
1790
|
+
r"""
|
|
1791
|
+
Helper function for unpickling.
|
|
1792
|
+
|
|
1793
|
+
EXAMPLES::
|
|
1794
|
+
|
|
1795
|
+
sage: K.<x> = GF(9)
|
|
1796
|
+
sage: M = MatrixSpace(K,10,10).random_element()
|
|
1797
|
+
sage: M == loads(dumps(M)) # indirect doctest
|
|
1798
|
+
True
|
|
1799
|
+
sage: M is loads(dumps(M))
|
|
1800
|
+
False
|
|
1801
|
+
|
|
1802
|
+
We also test pickles with zero rows and columns, as they may constitute
|
|
1803
|
+
corner cases. Note that in the following case, if ``sizeof(long)==8``,
|
|
1804
|
+
two matrix entries are stored in one byte, and therefore the last byte of
|
|
1805
|
+
a row is only half filled::
|
|
1806
|
+
|
|
1807
|
+
sage: M = matrix(K,3,5, [x, 0, 1, 0,-1, 0, 0, 0, 0, 0, -1, 0, 1, 0, x])
|
|
1808
|
+
sage: loads(dumps(M)) == M
|
|
1809
|
+
True
|
|
1810
|
+
sage: M = matrix(K,3,5, [0, 1, 0,-1, 0, x, 0, 1, 0, -1, 0, 0, 0, 0, 0])
|
|
1811
|
+
sage: loads(dumps(M)) == M
|
|
1812
|
+
True
|
|
1813
|
+
|
|
1814
|
+
TESTS:
|
|
1815
|
+
|
|
1816
|
+
We test that a pickle created by one machine can be understood
|
|
1817
|
+
by other machines with different architecture (see :issue:`23411`).
|
|
1818
|
+
Internally, a row is stored in a memory block of length a multiple
|
|
1819
|
+
of ``sizeof(long)``, which may be machine dependent, but in a pickle,
|
|
1820
|
+
only the bytes actually containing data of the row are stored, which
|
|
1821
|
+
is machine independent. We chose a matrix over the field with `13` elements.
|
|
1822
|
+
Since `13^2<255<13^3`, two columns will be stored in one byte. Our matrix
|
|
1823
|
+
has five columns, thus, one row will occupy three bytes in the pickle,
|
|
1824
|
+
but eight bytes (if ``sizeof(long)==8``) in memory, and the pickle
|
|
1825
|
+
string will be six bytes, since we have two rows::
|
|
1826
|
+
|
|
1827
|
+
sage: s = b'Uq\x82\xa7\x8bh'
|
|
1828
|
+
sage: len(s)
|
|
1829
|
+
6
|
|
1830
|
+
sage: from sage.matrix.matrix_gfpn_dense import mtx_unpickle, Matrix_gfpn_dense
|
|
1831
|
+
sage: MS = MatrixSpace(GF(13), 2, 5, implementation=Matrix_gfpn_dense)
|
|
1832
|
+
sage: N = mtx_unpickle(MS, 2, 5, s, True)
|
|
1833
|
+
sage: N
|
|
1834
|
+
[ 6 7 8 9 10]
|
|
1835
|
+
[12 11 10 9 8]
|
|
1836
|
+
sage: type(N)
|
|
1837
|
+
<class 'sage.matrix.matrix_gfpn_dense.Matrix_gfpn_dense'>
|
|
1838
|
+
|
|
1839
|
+
We demonstrate that a slightly different pickle format can be understood
|
|
1840
|
+
as well, that was at some point used by some optional package::
|
|
1841
|
+
|
|
1842
|
+
sage: N == mtx_unpickle(int(13), 2, 5, s, True)
|
|
1843
|
+
True
|
|
1844
|
+
|
|
1845
|
+
In a previous version of this optional module, the whole memory chunk
|
|
1846
|
+
used to store the matrix was stored. The result would have been, as
|
|
1847
|
+
in the following example, a string of length 16. Unpickling works, but
|
|
1848
|
+
results in a warning::
|
|
1849
|
+
|
|
1850
|
+
sage: t = b'Uq\x82\x00\x00\x00\x00\x00\xa7\x8bh\x00\x00\x00\x00\x00'
|
|
1851
|
+
sage: len(t)
|
|
1852
|
+
16
|
|
1853
|
+
sage: N == mtx_unpickle(MS, 2, 5, t, True)
|
|
1854
|
+
doctest:warning
|
|
1855
|
+
...
|
|
1856
|
+
DeprecationWarning: Reading this pickle may be machine dependent
|
|
1857
|
+
See https://github.com/sagemath/sage/issues/23411 for details.
|
|
1858
|
+
True
|
|
1859
|
+
|
|
1860
|
+
Unpickling would even work in the case that the machine creating
|
|
1861
|
+
the deprecated pickle had ``sizeof(long)==9``::
|
|
1862
|
+
|
|
1863
|
+
sage: t = b'Uq\x82\x00\x00\x00\x00\x00\x00\xa7\x8bh\x00\x00\x00\x00\x00\x00'
|
|
1864
|
+
sage: len(t)
|
|
1865
|
+
18
|
|
1866
|
+
sage: N == mtx_unpickle(MS, 2, 5, t, True)
|
|
1867
|
+
True
|
|
1868
|
+
|
|
1869
|
+
The data may be empty, which results in the zero matrix::
|
|
1870
|
+
|
|
1871
|
+
sage: mtx_unpickle(MS, 2, 5, b'', True)
|
|
1872
|
+
[0 0 0 0 0]
|
|
1873
|
+
[0 0 0 0 0]
|
|
1874
|
+
|
|
1875
|
+
We test further corner cases. A :exc:`ValueError` is raised if the number
|
|
1876
|
+
of bytes in the pickle does not comply with either the old or the new
|
|
1877
|
+
pickle format (we test several code paths here)::
|
|
1878
|
+
|
|
1879
|
+
sage: t = b'Uq\x82\x00\x00\x00\x00\x00\xa7\x8bh\x00\x00\x00\x00\x00\x00'
|
|
1880
|
+
sage: mtx_unpickle(MS, 2, 5, t, True)
|
|
1881
|
+
Traceback (most recent call last):
|
|
1882
|
+
...
|
|
1883
|
+
ValueError: Expected a pickle with 3*2 bytes, got 17 instead
|
|
1884
|
+
sage: t = b'Uq\x82\x00\x00\x00\x00\x00\xa7\x8bh\x00\x00\x00\x00\x00\x00'
|
|
1885
|
+
sage: mtx_unpickle(MS, 2, 5, t[:4], True)
|
|
1886
|
+
Traceback (most recent call last):
|
|
1887
|
+
...
|
|
1888
|
+
ValueError: Expected a pickle with 3*2 bytes, got 2*2 instead
|
|
1889
|
+
sage: MS = MatrixSpace(GF(13), 0, 5)
|
|
1890
|
+
sage: mtx_unpickle(MS, 0, 5, s, True)
|
|
1891
|
+
Traceback (most recent call last):
|
|
1892
|
+
...
|
|
1893
|
+
ValueError: This matrix pickle contains data, thus, the number of rows
|
|
1894
|
+
and columns must be positive
|
|
1895
|
+
sage: MS = MatrixSpace(GF(13), 3, 5)
|
|
1896
|
+
sage: mtx_unpickle(MS, 2, 5, s, True)
|
|
1897
|
+
Traceback (most recent call last):
|
|
1898
|
+
...
|
|
1899
|
+
ValueError: Inconsistent dimensions in this matrix pickle
|
|
1900
|
+
sage: mtx_unpickle(MatrixSpace(GF(19),0,5), 0, 5, b'', True)
|
|
1901
|
+
[]
|
|
1902
|
+
"""
|
|
1903
|
+
# The expected input type is bytes. However, Python-2 legacy pickles do
|
|
1904
|
+
# not distinguish between str and bytes. If such pickle is unpickled
|
|
1905
|
+
# in Python-3, Sage will receive a str in `latin1` encoding. Therefore,
|
|
1906
|
+
# in the following line, we use a helper function that would return bytes,
|
|
1907
|
+
# regardless whether the input is bytes or str.
|
|
1908
|
+
if isinstance(data, str):
|
|
1909
|
+
data = data.encode('latin1')
|
|
1910
|
+
cdef bytes Data = data
|
|
1911
|
+
if isinstance(f, int):
|
|
1912
|
+
# This is for old pickles created with the group cohomology spkg
|
|
1913
|
+
MS = MatrixSpace(GF(f, 'z'), nr, nc, implementation=Matrix_gfpn_dense)
|
|
1914
|
+
else:
|
|
1915
|
+
MS = f
|
|
1916
|
+
if MS.nrows() != nr or MS.ncols() != nc:
|
|
1917
|
+
raise ValueError("Inconsistent dimensions in this matrix pickle")
|
|
1918
|
+
f = MS.base_ring().order()
|
|
1919
|
+
|
|
1920
|
+
OUT = <Matrix_gfpn_dense>Matrix_gfpn_dense.__new__(Matrix_gfpn_dense, MS)
|
|
1921
|
+
sig_on()
|
|
1922
|
+
try:
|
|
1923
|
+
OUT.Data = MatAlloc(f, nr, nc)
|
|
1924
|
+
finally:
|
|
1925
|
+
sig_off()
|
|
1926
|
+
OUT._is_immutable = not m
|
|
1927
|
+
OUT._converter = FieldConverter(OUT._base_ring)
|
|
1928
|
+
cdef char *x
|
|
1929
|
+
cdef PTR pt
|
|
1930
|
+
cdef size_t lenData = len(Data)
|
|
1931
|
+
cdef size_t pickled_rowsize
|
|
1932
|
+
cdef size_t i
|
|
1933
|
+
if Data:
|
|
1934
|
+
if nr <= 0 or nc <= 0:
|
|
1935
|
+
raise ValueError("This matrix pickle contains data, thus, the number of rows and columns must be positive")
|
|
1936
|
+
pickled_rowsize = lenData//nr
|
|
1937
|
+
if lenData != pickled_rowsize*nr:
|
|
1938
|
+
raise ValueError(f"Expected a pickle with {FfCurrentRowSizeIo}*{nr} bytes, got {lenData} instead")
|
|
1939
|
+
x = PyBytes_AsString(Data)
|
|
1940
|
+
if pickled_rowsize == FfCurrentRowSizeIo:
|
|
1941
|
+
pt = OUT.Data.Data
|
|
1942
|
+
for i in range(nr):
|
|
1943
|
+
memcpy(pt,x,FfCurrentRowSizeIo)
|
|
1944
|
+
x += FfCurrentRowSizeIo
|
|
1945
|
+
FfStepPtr(&(pt))
|
|
1946
|
+
sig_check()
|
|
1947
|
+
elif pickled_rowsize >= FfCurrentRowSizeIo:
|
|
1948
|
+
deprecation(23411, "Reading this pickle may be machine dependent")
|
|
1949
|
+
if pickled_rowsize == FfCurrentRowSize:
|
|
1950
|
+
memcpy(OUT.Data.Data, x, OUT.Data.RowSize*OUT.Data.Nor)
|
|
1951
|
+
else:
|
|
1952
|
+
pt = OUT.Data.Data
|
|
1953
|
+
for i in range(nr):
|
|
1954
|
+
memcpy(pt,x,FfCurrentRowSizeIo)
|
|
1955
|
+
x += pickled_rowsize
|
|
1956
|
+
FfStepPtr(&(pt))
|
|
1957
|
+
sig_check()
|
|
1958
|
+
else:
|
|
1959
|
+
raise ValueError(f"Expected a pickle with {FfCurrentRowSizeIo}*{nr} bytes, got {pickled_rowsize}*{nr} instead")
|
|
1960
|
+
return OUT
|