Path: blob/master/src/sage/matrix/matrix_integer_sparse.pyx
8817 views
"""1Sparse integer matrices.23AUTHORS:4-- William Stein (2007-02-21)5-- Soroosh Yazdani (2007-02-21)67TESTS:8sage: a = matrix(ZZ,2,range(4), sparse=True)9sage: TestSuite(a).run()10sage: Matrix(ZZ,0,0,sparse=True).inverse()11[]12"""1314##############################################################################15# Copyright (C) 2007 William Stein <[email protected]>16# Distributed under the terms of the GNU General Public License (GPL)17# The full text of the GPL is available at:18# http://www.gnu.org/licenses/19##############################################################################2021include 'sage/modules/binary_search.pxi'22include 'sage/modules/vector_integer_sparse_h.pxi'23include 'sage/modules/vector_integer_sparse_c.pxi'24include 'sage/modules/vector_modn_sparse_h.pxi'25include 'sage/modules/vector_modn_sparse_c.pxi'26from cpython.sequence cimport *2728include 'sage/ext/stdsage.pxi'2930from sage.rings.integer cimport Integer31from matrix cimport Matrix3233from matrix_modn_sparse cimport Matrix_modn_sparse34from sage.structure.element cimport ModuleElement, RingElement, Element, Vector3536import matrix_space3738from sage.rings.integer_ring import ZZ39from sage.rings.finite_rings.integer_mod_ring import IntegerModRing404142cdef class Matrix_integer_sparse(matrix_sparse.Matrix_sparse):4344########################################################################45# LEVEL 1 functionality46# * __cinit__47# * __dealloc__48# * __init__49# * set_unsafe50# * get_unsafe51# * __richcmp__ -- always the same52# * __hash__ -- always simple53########################################################################54def __cinit__(self, parent, entries, copy, coerce):55self._initialized = False56# set the parent, nrows, ncols, etc.57matrix_sparse.Matrix_sparse.__init__(self, parent)5859self._matrix = <mpz_vector*> sage_malloc(parent.nrows()*sizeof(mpz_vector))60if self._matrix == NULL:61raise MemoryError("error allocating sparse matrix")6263# initialize the rows64for i from 0 <= i < parent.nrows():65mpz_vector_init(&self._matrix[i], self._ncols, 0)66# record that rows have been initialized67self._initialized = True6869def __dealloc__(self):70cdef Py_ssize_t i71if self._initialized:72for i from 0 <= i < self._nrows:73mpz_vector_clear(&self._matrix[i])74sage_free(self._matrix)7576def __init__(self, parent, entries, copy, coerce):77"""78Create a sparse matrix over the integers.7980INPUT:81parent -- a matrix space82entries -- * a Python list of triples (i,j,x), where 0 <= i < nrows,830 <= j < ncols, and x is coercible to an int. The i,j84entry of self is set to x. The x's can be 0.85* Alternatively, entries can be a list of *all* the entries86of the sparse matrix (so they would be mostly 0).87copy -- ignored88coerce -- ignored89"""90cdef Py_ssize_t i, j, k91cdef Integer z92cdef PyObject** X9394# fill in entries in the dict case95if entries is None: return96if isinstance(entries, dict):97R = self.base_ring()98for ij, x in entries.iteritems():99z = R(x)100if z != 0:101i, j = ij # nothing better to do since this is user input, which may be bogus.102if i < 0 or j < 0 or i >= self._nrows or j >= self._ncols:103raise IndexError("invalid entries list")104mpz_vector_set_entry(&self._matrix[i], j, z.value)105106elif isinstance(entries, list):107108# Dense input format -- fill in entries109if len(entries) != self._nrows * self._ncols:110raise TypeError("list of entries must be a dictionary of (i,j):x or a dense list of n * m elements")111seq = PySequence_Fast(entries,"expected a list")112X = PySequence_Fast_ITEMS(seq)113k = 0114R = self.base_ring()115# Get fast access to the entries list.116for i from 0 <= i < self._nrows:117for j from 0 <= j < self._ncols:118z = R(<object>X[k])119if z != 0:120mpz_vector_set_entry(&self._matrix[i], j, z.value)121k = k + 1122123else:124125# fill in entries in the scalar case126z = Integer(entries)127if z == 0:128return129if self._nrows != self._ncols:130raise TypeError("matrix must be square to initialize with a scalar.")131for i from 0 <= i < self._nrows:132mpz_vector_set_entry(&self._matrix[i], i, z.value)133134135cdef set_unsafe(self, Py_ssize_t i, Py_ssize_t j, x):136mpz_vector_set_entry(&self._matrix[i], j, (<Integer> x).value)137138cdef get_unsafe(self, Py_ssize_t i, Py_ssize_t j):139cdef Integer x140x = Integer()141mpz_vector_get_entry(&x.value, &self._matrix[i], j)142return x143144def __richcmp__(Matrix self, right, int op): # always need for mysterious reasons.145return self._richcmp(right, op)146147def __hash__(self):148return self._hash()149150151########################################################################152# LEVEL 2 functionality153# * def _pickle154# * def _unpickle155# * cdef _add_156# * cdef _sub_157# * cdef _mul_158# * cdef _cmp_c_impl159# * __neg__160# * __invert__161# * __copy__162# * _multiply_classical163# * _matrix_times_matrix_164# * _list -- list of underlying elements (need not be a copy)165# * x _dict -- sparse dictionary of underlying elements (need not be a copy)166########################################################################167# def _pickle(self):168# def _unpickle(self, data, int version): # use version >= 0169# cpdef ModuleElement _add_(self, ModuleElement right):170# cdef _mul_(self, Matrix right):171# cdef int _cmp_c_impl(self, Matrix right) except -2:172# def __neg__(self):173# def __invert__(self):174# def __copy__(self):175# def _multiply_classical(left, matrix.Matrix _right):176# def _list(self):177178cpdef ModuleElement _lmul_(self, RingElement right):179"""180EXAMPLES::181182sage: a = matrix(ZZ,2,range(6), sparse=True)183sage: 3 * a184[ 0 3 6]185[ 9 12 15]186"""187cdef Py_ssize_t i188cdef mpz_vector* self_row, *M_row189cdef Matrix_integer_sparse M190cdef Integer _x191_x = Integer(right)192M = Matrix_integer_sparse.__new__(Matrix_integer_sparse, self._parent, None, None, None)193for i from 0 <= i < self._nrows:194self_row = &self._matrix[i]195M_row = &M._matrix[i]196mpz_vector_scalar_multiply(M_row, self_row, _x.value)197return M198199cpdef ModuleElement _add_(self, ModuleElement right):200cdef Py_ssize_t i, j201cdef mpz_vector* self_row, *M_row202cdef Matrix_integer_sparse M203204M = Matrix_integer_sparse.__new__(Matrix_integer_sparse, self._parent, None, None, None)205cdef mpz_t mul206mpz_init_set_si(mul,1)207for i from 0 <= i < self._nrows:208mpz_vector_clear(&M._matrix[i])209add_mpz_vector_init(&M._matrix[i], &self._matrix[i], &(<Matrix_integer_sparse>right)._matrix[i], mul)210mpz_clear(mul)211return M212213cpdef ModuleElement _sub_(self, ModuleElement right):214cdef Py_ssize_t i, j215cdef mpz_vector* self_row, *M_row216cdef Matrix_integer_sparse M217218M = Matrix_integer_sparse.__new__(Matrix_integer_sparse, self._parent, None, None, None)219cdef mpz_t mul220mpz_init_set_si(mul,-1)221for i from 0 <= i < self._nrows:222mpz_vector_clear(&M._matrix[i])223add_mpz_vector_init(&M._matrix[i], &self._matrix[i], &(<Matrix_integer_sparse>right)._matrix[i], mul)224mpz_clear(mul)225return M226227def _dict(self):228"""229Unsafe version of the dict method, mainly for internal use.230This may return the dict of elements, but as an *unsafe*231reference to the underlying dict of the object. It might232be dangerous if you change entries of the returned dict.233"""234d = self.fetch('dict')235if not d is None:236return d237238cdef Py_ssize_t i, j, k239d = {}240for i from 0 <= i < self._nrows:241for j from 0 <= j < self._matrix[i].num_nonzero:242x = Integer()243mpz_set((<Integer>x).value, self._matrix[i].entries[j])244d[(int(i),int(self._matrix[i].positions[j]))] = x245self.cache('dict', d)246return d247248########################################################################249# LEVEL 3 functionality (Optional)250# * cdef _sub_251# * __deepcopy__252# * __invert__253# * Matrix windows -- only if you need strassen for that base254# * Other functions (list them here):255########################################################################256257def _nonzero_positions_by_row(self, copy=True):258"""259Returns the list of pairs (i,j) such that self[i,j] != 0.260261It is safe to change the resulting list (unless you give the option copy=False).262263EXAMPLE::264sage: M = Matrix(ZZ, [[0,0,0,1,0,0,0,0],[0,1,0,0,0,0,1,0]], sparse=True); M265[0 0 0 1 0 0 0 0]266[0 1 0 0 0 0 1 0]267sage: M._nonzero_positions_by_row()268[(0, 3), (1, 1), (1, 6)]269270"""271x = self.fetch('nonzero_positions')272if not x is None:273if copy:274return list(x)275return x276nzp = []277cdef Py_ssize_t i, j278for i from 0 <= i < self._nrows:279for j from 0 <= j < self._matrix[i].num_nonzero:280nzp.append((i,self._matrix[i].positions[j]))281self.cache('nonzero_positions', nzp)282if copy:283return list(nzp)284return nzp285286def _nonzero_positions_by_column(self, copy=True):287"""288Returns the list of pairs (i,j) such that self[i,j] != 0, but289sorted by columns, i.e., column j=0 entries occur first, then290column j=1 entries, etc.291292It is safe to change the resulting list (unless you give the option copy=False).293294EXAMPLE::295sage: M = Matrix(ZZ, [[0,0,0,1,0,0,0,0],[0,1,0,0,0,0,1,0]], sparse=True); M296[0 0 0 1 0 0 0 0]297[0 1 0 0 0 0 1 0]298sage: M._nonzero_positions_by_column()299[(1, 1), (0, 3), (1, 6)]300301"""302x = self.fetch('nonzero_positions_by_column')303if not x is None:304if copy:305return list(x)306return x307nzc = [[] for _ in xrange(self._ncols)]308cdef Py_ssize_t i, j309for i from 0 <= i < self._nrows:310for j from 0 <= j < self._matrix[i].num_nonzero:311p = self._matrix[i].positions[j]312nzc[p].append((i,p))313nzc = sum(nzc,[])314self.cache('nonzero_positions_by_column', nzc)315if copy:316return list(nzc)317return nzc318319def _mod_int(self, modulus):320"""321Helper function in reducing matrices mod n.322323INPUT:324325- `modulus` - a number326327OUTPUT:328329This matrix, over `ZZ/nZZ`.330331TESTS::332333sage: M = Matrix(ZZ, sparse=True)334sage: B = M._mod_int(7)335sage: B.parent()336Full MatrixSpace of 0 by 0 sparse matrices over Ring of integers modulo 7337338"""339return self._mod_int_c(modulus)340341cdef _mod_int_c(self, mod_int p):342cdef Py_ssize_t i, j343cdef Matrix_modn_sparse res344cdef mpz_vector* self_row345cdef c_vector_modint* res_row346res = Matrix_modn_sparse.__new__(Matrix_modn_sparse, matrix_space.MatrixSpace(347IntegerModRing(p), self._nrows, self._ncols, sparse=True), None, None, None)348for i from 0 <= i < self._nrows:349self_row = &(self._matrix[i])350res_row = &(res.rows[i])351for j from 0 <= j < self_row.num_nonzero:352set_entry(res_row, self_row.positions[j], mpz_fdiv_ui(self_row.entries[j], p))353return res354355356def rational_reconstruction(self, N):357"""358Use rational reconstruction to lift self to a matrix over the359rational numbers (if possible), where we view self as a matrix360modulo N.361362EXAMPLES:363sage: A = matrix(ZZ, 3, 4, [(1/3)%500, 2, 3, (-4)%500, 7, 2, 2, 3, 4, 3, 4, (5/7)%500], sparse=True)364sage: A.rational_reconstruction(500)365[1/3 2 3 -4]366[ 7 2 2 3]367[ 4 3 4 5/7]368369TEST:370371Check that ticket #9345 is fixed::372373sage: A = random_matrix(ZZ, 3, 3, sparse = True)374sage: A.rational_reconstruction(0)375Traceback (most recent call last):376...377ZeroDivisionError: The modulus cannot be zero378"""379import misc380return misc.matrix_integer_sparse_rational_reconstruction(self, N)381382def _linbox_sparse(self):383cdef Py_ssize_t i, j384v = ['%s %s M'%(self._nrows, self._ncols)]385d = self._dict()386for ij, x in d.iteritems():387v.append('%s %s %s'%(ij[0]+1,ij[1]+1,x))388v.append('0 0 0\n')389return '\n'.join(v)390391def _right_kernel_matrix(self, **kwds):392r"""393Returns a pair that includes a matrix of basis vectors394for the right kernel of ``self``.395396INPUT:397398- ``algorithm`` - determines which algorithm to use, options are:399400- 'pari' - use the ``matkerint()`` function from the PARI library401- 'padic' - use the p-adic algorithm from the IML library402- 'default' - use a heuristic to decide which of the two above403routines is fastest. This is the default value.404405- ``proof`` - this is passed to the p-adic IML algorithm.406If not specified, the global flag for linear algebra will be used.407408OUTPUT:409410Returns a pair. First item is the string is either411'computed-pari-int' or 'computed-iml-int', which identifies412the nature of the basis vectors.413414Second item is a matrix whose rows are a basis for the right kernel,415over the integers, as computed by either the IML or PARI libraries.416417EXAMPLES::418419sage: A = matrix(ZZ, [[4, 7, 9, 7, 5, 0],420... [1, 0, 5, 8, 9, 1],421... [0, 1, 0, 1, 9, 7],422... [4, 7, 6, 5, 1, 4]],423... sparse = True)424425sage: result = A._right_kernel_matrix(algorithm='pari')426sage: result[0]427'computed-pari-int'428sage: X = result[1]; X429[-26 31 -30 21 2 -10]430[-47 -13 48 -14 -11 18]431sage: A*X.transpose() == zero_matrix(ZZ, 4, 2)432True433434sage: result = A._right_kernel_matrix(algorithm='padic')435sage: result[0]436'computed-iml-int'437sage: X = result[1]; X438[-469 214 -30 119 -37 0]439[ 370 -165 18 -91 30 -2]440sage: A*X.transpose() == zero_matrix(ZZ, 4, 2)441True442443sage: result = A._right_kernel_matrix(algorithm='default')444sage: result[0]445'computed-pari-int'446sage: result[1]447[-26 31 -30 21 2 -10]448[-47 -13 48 -14 -11 18]449450sage: result = A._right_kernel_matrix()451sage: result[0]452'computed-pari-int'453sage: result[1]454[-26 31 -30 21 2 -10]455[-47 -13 48 -14 -11 18]456457With the 'default' given as the algorithm, several heuristics are458used to determine if PARI or IML ('padic') is used. The code has459exact details, but roughly speaking, relevant factors are: the460absolute size of the matrix, or the relative dimensions, or the461magnitude of the entries. ::462463sage: A = random_matrix(ZZ, 18, 11, sparse=True)464sage: A._right_kernel_matrix(algorithm='default')[0]465'computed-pari-int'466sage: A = random_matrix(ZZ, 18, 11, x = 10^200, sparse=True)467sage: A._right_kernel_matrix(algorithm='default')[0]468'computed-iml-int'469sage: A = random_matrix(ZZ, 60, 60, sparse=True)470sage: A._right_kernel_matrix(algorithm='default')[0]471'computed-iml-int'472sage: A = random_matrix(ZZ, 60, 55, sparse=True)473sage: A._right_kernel_matrix(algorithm='default')[0]474'computed-pari-int'475476TESTS:477478We test three trivial cases. PARI is used for small matrices,479but we let the heuristic decide that. ::480481sage: A = matrix(ZZ, 0, 2, sparse=True)482sage: A._right_kernel_matrix()[1]483[1 0]484[0 1]485sage: A = matrix(ZZ, 2, 0, sparse=True)486sage: A._right_kernel_matrix()[1].parent()487Full MatrixSpace of 0 by 0 dense matrices over Integer Ring488sage: A = zero_matrix(ZZ, 4, 3, sparse=True)489sage: A._right_kernel_matrix()[1]490[1 0 0]491[0 1 0]492[0 0 1]493"""494return self.dense_matrix()._right_kernel_matrix(**kwds)495496hermite_form = Matrix.echelon_form497498def elementary_divisors(self, algorithm='pari'):499"""500Return the elementary divisors of self, in order.501502The elementary divisors are the invariants of the finite503abelian group that is the cokernel of *left* multiplication by504this matrix. They are ordered in reverse by divisibility.505506INPUT:507self -- matrix508algorithm -- (default: 'pari')509'pari': works robustly, but is slower.510'linbox' -- use linbox (currently off, broken)511512OUTPUT:513list of integers514515EXAMPLES:516sage: matrix(3, range(9),sparse=True).elementary_divisors()517[1, 3, 0]518sage: M = matrix(ZZ, 3, [1,5,7, 3,6,9, 0,1,2], sparse=True)519sage: M.elementary_divisors()520[1, 1, 6]521522This returns a copy, which is safe to change:523sage: edivs = M.elementary_divisors()524sage: edivs.pop()5256526sage: M.elementary_divisors()527[1, 1, 6]528529SEE ALSO: smith_form530"""531return self.dense_matrix().elementary_divisors(algorithm=algorithm)532533def smith_form(self):534r"""535Returns matrices S, U, and V such that S = U\*self\*V, and S is in536Smith normal form. Thus S is diagonal with diagonal entries the537ordered elementary divisors of S.538539This version is for sparse matrices and simply makes the matrix540dense and calls the version for dense integer matrices.541542.. warning::543544The elementary_divisors function, which returns the545diagonal entries of S, is VASTLY faster than this function.546547The elementary divisors are the invariants of the finite abelian548group that is the cokernel of this matrix. They are ordered in549reverse by divisibility.550551EXAMPLES::552553sage: A = MatrixSpace(IntegerRing(), 3, sparse=True)(range(9))554sage: D, U, V = A.smith_form()555sage: D556[1 0 0]557[0 3 0]558[0 0 0]559sage: U560[ 0 1 0]561[ 0 -1 1]562[-1 2 -1]563sage: V564[-1 4 1]565[ 1 -3 -2]566[ 0 0 1]567sage: U*A*V568[1 0 0]569[0 3 0]570[0 0 0]571572It also makes sense for nonsquare matrices::573574sage: A = Matrix(ZZ,3,2,range(6), sparse=True)575sage: D, U, V = A.smith_form()576sage: D577[1 0]578[0 2]579[0 0]580sage: U581[ 0 1 0]582[ 0 -1 1]583[-1 2 -1]584sage: V585[-1 3]586[ 1 -2]587sage: U * A * V588[1 0]589[0 2]590[0 0]591592The examples above show that Trac ticket #10626 has been implemented.593594595.. seealso::596597:meth:`elementary_divisors`598"""599return self.dense_matrix().smith_form()600601602