/* -*- mode: C -*- */ /* IGraph library. Copyright (C) 2003, 2004, 2005 Gabor Csardi MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "types.h" #include "memory.h" #include "random.h" #include "error.h" #include #include /* memcpy & co. */ #include /** * \section igraph_spmatrix_constructor_and_destructor Sparse matrix constructors * and destructors. */ /** * \ingroup matrix * \function igraph_spmatrix_init * \brief Initializes a sparse matrix. * * * Every sparse matrix needs to be initialized before using it, this is done * by calling this function. A matrix has to be destroyed if it is not * needed any more, see \ref igraph_spmatrix_destroy(). * \param m Pointer to a not yet initialized sparse matrix object to be * initialized. * \param nrow The number of rows in the matrix. * \param ncol The number of columns in the matrix. * \return Error code. * * Time complexity: operating system dependent. */ int igraph_spmatrix_init(igraph_spmatrix_t *m, long int nrow, long int ncol) { assert(m != NULL); IGRAPH_VECTOR_INIT_FINALLY(&m->ridx, 0); IGRAPH_VECTOR_INIT_FINALLY(&m->cidx, ncol+1); IGRAPH_VECTOR_INIT_FINALLY(&m->data, 0); IGRAPH_FINALLY_CLEAN(3); m->nrow=nrow; m->ncol=ncol; return 0; } /** * \ingroup matrix * \function igraph_spmatrix_destroy * \brief Destroys a sparse matrix object. * * * This function frees all the memory allocated for a sparse matrix * object. The destroyed object needs to be reinitialized before using * it again. * \param m The matrix to destroy. * * Time complexity: operating system dependent. */ void igraph_spmatrix_destroy(igraph_spmatrix_t *m) { assert(m != NULL); igraph_vector_destroy(&m->ridx); igraph_vector_destroy(&m->cidx); igraph_vector_destroy(&m->data); } /** * \ingroup matrix * \function igraph_spmatrix_copy * \brief Copies a sparse matrix. * * * Creates a sparse matrix object by copying another one. * \param to Pointer to an uninitialized sparse matrix object. * \param from The initialized sparse matrix object to copy. * \return Error code, \c IGRAPH_ENOMEM if there * isn't enough memory to allocate the new sparse matrix. * * Time complexity: O(n), the number * of elements in the matrix. */ int igraph_spmatrix_copy(igraph_spmatrix_t *to, const igraph_spmatrix_t *from) { assert(from != NULL); assert(to != NULL); to->nrow = from->nrow; to->ncol = from->ncol; IGRAPH_CHECK(igraph_vector_copy(&to->ridx, &from->ridx)); IGRAPH_CHECK(igraph_vector_copy(&to->cidx, &from->cidx)); IGRAPH_CHECK(igraph_vector_copy(&to->data, &from->data)); return 0; } /** * \section igraph_spmatrix_accessing_elements Accessing elements of a sparse matrix */ /** * \ingroup matrix * \function igraph_spmatrix_e * \brief Accessing an element of a sparse matrix. * * Note that there are no range checks right now. * \param m The matrix object. * \param row The index of the row, starting with zero. * \param col The index of the column, starting with zero. * * Time complexity: O(log n), where n is the number of nonzero elements in * the requested column. */ igraph_real_t igraph_spmatrix_e(const igraph_spmatrix_t *m, long int row, long int col) { long int start, end; assert(m != NULL); start=(long)igraph_vector_e(&m->cidx, col); end=(long)igraph_vector_e(&m->cidx, col+1)-1; if (enddata[start] and * m->data[end], inclusive, ordered by row index */ while (start < end-1) { long int mid=(start+end)/2; if (VECTOR(m->ridx)[mid] > row) { end=mid; } else if (VECTOR(m->ridx)[mid] < row) { start=mid; } else { start=mid; break; } } if (VECTOR(m->ridx)[start] == row) return VECTOR(m->data)[start]; if (VECTOR(m->ridx)[start] != row && VECTOR(m->ridx)[end] == row) return VECTOR(m->data)[end]; return 0; } /** * \ingroup matrix * \function igraph_spmatrix_set * \brief Setting an element of a sparse matrix. * * Note that there are no range checks right now. * \param m The matrix object. * \param row The index of the row, starting with zero. * \param col The index of the column, starting with zero. * \param value The new value. * * Time complexity: O(log n), where n is the number of nonzero elements in * the requested column. */ int igraph_spmatrix_set(igraph_spmatrix_t *m, long int row, long int col, igraph_real_t value) { long int start, end; assert(m != NULL); start=(long)igraph_vector_e(&m->cidx, col); end=(long)igraph_vector_e(&m->cidx, col+1)-1; if (endridx, start, row)); IGRAPH_CHECK(igraph_vector_insert(&m->data, start, value)); for (start=col+1; start < m->ncol+1; start++) VECTOR(m->cidx)[start]++; return 0; } /* Elements residing in column col are between m->data[start] and * m->data[end], inclusive, ordered by row index */ while (start < end-1) { long int mid=(start+end)/2; if (VECTOR(m->ridx)[mid] > row) { end=mid; } else if (VECTOR(m->ridx)[mid] < row) { start=mid; } else { start=mid; break; } } if (VECTOR(m->ridx)[start] == row) { /* Overwriting a value - or deleting it if it has been overwritten by zero */ if (value == 0) { igraph_vector_remove(&m->ridx, start); igraph_vector_remove(&m->data, start); for (start=col+1; start < m->ncol+1; start++) VECTOR(m->cidx)[start]--; } else { VECTOR(m->data)[start] = value; } return 0; } else if (VECTOR(m->ridx)[end] == row) { /* Overwriting a value - or deleting it if it has been overwritten by zero */ if (value == 0) { igraph_vector_remove(&m->ridx, end); igraph_vector_remove(&m->data, end); for (start=col+1; start < m->ncol+1; start++) VECTOR(m->cidx)[start]--; } else { VECTOR(m->data)[end] = value; } return 0; } /* New element has to be inserted, but only if not a zero is * being written into the matrix */ if (value != 0.0) { if (VECTOR(m->ridx)[end] < row) { IGRAPH_CHECK(igraph_vector_insert(&m->ridx, end+1, row)); IGRAPH_CHECK(igraph_vector_insert(&m->data, end+1, value)); } else if (VECTOR(m->ridx)[start] < row) { IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start+1, row)); IGRAPH_CHECK(igraph_vector_insert(&m->data, start+1, value)); } else { IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start, row)); IGRAPH_CHECK(igraph_vector_insert(&m->data, start, value)); } for (start=col+1; start < m->ncol+1; start++) VECTOR(m->cidx)[start]++; } return 0; } /** * \ingroup matrix * \function igraph_spmatrix_add_e * \brief Adding a real value to an element of a sparse matrix. * * Note that there are no range checks right now. This is implemented to avoid * double lookup of a given element in the matrix by using \ref igraph_spmatrix_e() * and \ref igraph_spmatrix_set() consecutively. * * \param m The matrix object. * \param row The index of the row, starting with zero. * \param col The index of the column, starting with zero. * \param value The value to add. * * Time complexity: O(log n), where n is the number of nonzero elements in * the requested column. */ int igraph_spmatrix_add_e(igraph_spmatrix_t *m, long int row, long int col, igraph_real_t value) { long int start, end; assert(m != NULL); start=(long)igraph_vector_e(&m->cidx, col); end=(long)igraph_vector_e(&m->cidx, col+1)-1; if (endridx, start, row)); IGRAPH_CHECK(igraph_vector_insert(&m->data, start, value)); for (start=col+1; start < m->ncol+1; start++) VECTOR(m->cidx)[start]++; return 0; } /* Elements residing in column col are between m->data[start] and * m->data[end], inclusive, ordered by row index */ while (start < end-1) { long int mid=(start+end)/2; if (VECTOR(m->ridx)[mid] > row) { end=mid; } else if (VECTOR(m->ridx)[mid] < row) { start=mid; } else { start=mid; break; } } if (VECTOR(m->ridx)[start] == row) { /* Overwriting a value */ if (VECTOR(m->data)[start] == -1) { igraph_vector_remove(&m->ridx, start); igraph_vector_remove(&m->data, start); for (start=col+1; start < m->ncol+1; start++) VECTOR(m->cidx)[start]--; } else { VECTOR(m->data)[start] += value; } return 0; } else if (VECTOR(m->ridx)[end] == row) { /* Overwriting a value */ if (VECTOR(m->data)[end] == -1) { igraph_vector_remove(&m->ridx, end); igraph_vector_remove(&m->data, end); for (start=col+1; start < m->ncol+1; start++) VECTOR(m->cidx)[start]--; } else { VECTOR(m->data)[end] += value; } return 0; } /* New element has to be inserted, but only if not a zero is * being added to a zero element of the matrix */ if (value != 0.0) { if (VECTOR(m->ridx)[end] < row) { IGRAPH_CHECK(igraph_vector_insert(&m->ridx, end+1, row)); IGRAPH_CHECK(igraph_vector_insert(&m->data, end+1, value)); } else if (VECTOR(m->ridx)[start] < row) { IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start+1, row)); IGRAPH_CHECK(igraph_vector_insert(&m->data, start+1, value)); } else { IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start, row)); IGRAPH_CHECK(igraph_vector_insert(&m->data, start, value)); } for (start=col+1; start < m->ncol+1; start++) VECTOR(m->cidx)[start]++; } return 0; } /** * \function igraph_spmatrix_add_col_values * * Adds the values of a column to another column. * * \param to The index of the column to be added to * \param from The index of the column to be added * \return Error code. */ int igraph_spmatrix_add_col_values(igraph_spmatrix_t *m, long int to, long int from) { long int i; /* TODO: I think this implementation could be speeded up if I don't use * igraph_spmatrix_add_e directly -- but maybe it's not worth the fuss */ for (i=VECTOR(m->cidx)[from]; icidx)[from+1]; i++) { IGRAPH_CHECK(igraph_spmatrix_add_e(m, VECTOR(m->ridx)[i], to, VECTOR(m->data)[i])); } return 0; } /** * \ingroup matrix * \function igraph_spmatrix_resize * \brief Resizes a sparse matrix. * * * This function resizes a sparse matrix by adding more elements to it. * The matrix retains its data even after resizing it, except for the data * which lies outside the new boundaries (if the new size is smaller). * \param m Pointer to an already initialized sparse matrix object. * \param nrow The number of rows in the resized matrix. * \param ncol The number of columns in the resized matrix. * \return Error code. * * Time complexity: O(n). * n is the number of elements in the old matrix. */ int igraph_spmatrix_resize(igraph_spmatrix_t *m, long int nrow, long int ncol) { long int i, j, ci, ei, mincol, nelem; assert(m != NULL); /* Iterating through the matrix data and deleting unnecessary data. */ /* At the same time, we create the new indices as well */ if (nrow < m->nrow) { nelem = igraph_vector_size(&m->data); ei = j = 0; mincol = (m->ncol < ncol) ? m->ncol : ncol; for (ci=0; ci < mincol; ci++) { for (; eicidx)[ci+1]; ei++) { if (VECTOR(m->ridx)[ei] < nrow) { VECTOR(m->ridx)[j]=VECTOR(m->ridx)[ei]; VECTOR(m->data)[j]=VECTOR(m->data)[ei]; j++; } } VECTOR(m->cidx)[ci]=j; } /* Contract the row index and the data vector */ IGRAPH_CHECK(igraph_vector_resize(&m->ridx, j)); IGRAPH_CHECK(igraph_vector_resize(&m->cidx, j)); } /* Updating cidx */ IGRAPH_CHECK(igraph_vector_resize(&m->cidx, ncol+1)); for (i=m->ncol+1; icidx)[i] = VECTOR(m->cidx)[m->ncol]; m->nrow=nrow; m->ncol=ncol; return 0; } /** * \ingroup matrix * \function igraph_spmatrix_count_nonzero * \brief The number of non-zero elements in a sparse matrix. * * \param m Pointer to an initialized sparse matrix object. * \return The size of the matrix. * * Time complexity: O(1). */ long int igraph_spmatrix_count_nonzero(const igraph_spmatrix_t *m) { assert(m != NULL); return igraph_vector_size(&m->data); } /** * \ingroup matrix * \function igraph_spmatrix_size * \brief The number of elements in a sparse matrix. * * \param m Pointer to an initialized sparse matrix object. * \return The size of the matrix. * * Time complexity: O(1). */ long int igraph_spmatrix_size(const igraph_spmatrix_t *m) { assert(m != NULL); return (m->nrow) * (m->ncol); } /** * \ingroup matrix * \function igraph_spmatrix_nrow * \brief The number of rows in a sparse matrix. * * \param m Pointer to an initialized sparse matrix object. * \return The number of rows in the matrix. * * Time complexity: O(1). */ long int igraph_spmatrix_nrow(const igraph_spmatrix_t *m) { assert(m != NULL); return m->nrow; } /** * \ingroup matrix * \function igraph_spmatrix_ncol * \brief The number of columns in a sparse matrix. * * \param m Pointer to an initialized sparse matrix object. * \return The number of columns in the sparse matrix. * * Time complexity: O(1). */ long int igraph_spmatrix_ncol(const igraph_spmatrix_t *m) { assert(m != NULL); return m->ncol; } /** * \ingroup matrix * \brief Copies a sparse matrix to a regular C array. * * * The matrix is copied columnwise, as this is the format most * programs and languages use. * The C array should be of sufficient size, there are (of course) no * range checks done. * \param m Pointer to an initialized sparse matrix object. * \param to Pointer to a C array, the place to copy the data to. * \return Error code. * * Time complexity: O(n), * n is the number of * elements in the matrix. */ int igraph_spmatrix_copy_to(const igraph_spmatrix_t *m, igraph_real_t *to) { long int c, dest_idx, idx, no_of_elements; no_of_elements=igraph_spmatrix_count_nonzero(m); memset(to, 0, sizeof(igraph_real_t)*igraph_spmatrix_size(m)); for (c=0, dest_idx=0; c < m->ncol; c++, dest_idx+=m->nrow) { for (idx=VECTOR(m->cidx)[c]; idxcidx)[c+1]; idx++) { to[dest_idx+(long)VECTOR(m->ridx)[idx]]=VECTOR(m->data)[idx]; } } return 0; } /** * \ingroup matrix * \brief Sets all element in a sparse matrix to zero. * * \param m Pointer to an initialized matrix object. * \return Error code, always returns with success. * * Time complexity: O(n), * n is the number of columns in the matrix */ int igraph_spmatrix_null(igraph_spmatrix_t *m) { assert(m != NULL); igraph_vector_clear(&m->data); igraph_vector_clear(&m->ridx); igraph_vector_null(&m->cidx); return 0; } /** * \ingroup matrix * \function igraph_spmatrix_add_cols * \brief Adds columns to a sparse matrix. * \param m The sparse matrix object. * \param n The number of columns to add. * \return Error code. * * Time complexity: O(1). */ int igraph_spmatrix_add_cols(igraph_spmatrix_t *m, long int n) { igraph_spmatrix_resize(m, m->nrow, m->ncol+n); return 0; } /** * \ingroup matrix * \function igraph_spmatrix_add_rows * \brief Adds rows to a sparse matrix. * \param m The sparse matrix object. * \param n The number of rows to add. * \return Error code. * * Time complexity: O(1). */ int igraph_spmatrix_add_rows(igraph_spmatrix_t *m, long int n) { igraph_spmatrix_resize(m, m->nrow+n, m->ncol); return 0; } /** * \function igraph_spmatrix_clear_row * * Clears a row in the matrix (sets all of its elements to zero) * \param m The matrix. * \param row The index of the row to be cleared. * * Time complexity: O(n), the number of nonzero elements in the matrix. */ int igraph_spmatrix_clear_row(igraph_spmatrix_t *m, long int row) { long int ci, ei, i, j, nremove=0, nremove_old=0, *permvec; assert(m != NULL); permvec=Calloc(igraph_vector_size(&m->data), long int); if (permvec == 0) { IGRAPH_ERROR("can't clear row in sparse matrix", IGRAPH_ENOMEM); } IGRAPH_FINALLY(free, permvec); for (ci=0, i=0, j=1; ci < m->ncol; ci++) { for (ei=VECTOR(m->cidx)[ci]; ei < VECTOR(m->cidx)[ci+1]; ei++) { if (VECTOR(m->ridx)[ei] == row) { /* this element will be deleted, so all elements in cidx from the * column index of this element will have to be decreased by one */ nremove++; } else { /* this element will be kept */ permvec[i] = j; j++; } i++; } if (ci > 0) { VECTOR(m->cidx)[ci] -= nremove_old; } nremove_old = nremove; } VECTOR(m->cidx)[m->ncol] -= nremove; igraph_vector_permdelete(&m->ridx, permvec, nremove); igraph_vector_permdelete(&m->data, permvec, nremove); free(permvec); IGRAPH_FINALLY_CLEAN(1); return 0; } int igraph_i_spmatrix_clear_row_fast(igraph_spmatrix_t *m, long int row) { long int ei, n; assert(m != NULL); n = igraph_vector_size(&m->data); for (ei=0; eiridx)[ei] == row) VECTOR(m->data)[ei]=0.0; } return 0; } int igraph_i_spmatrix_cleanup(igraph_spmatrix_t *m) { long int ci, ei, i, j, nremove=0, nremove_old=0, *permvec; assert(m != NULL); permvec=Calloc(igraph_vector_size(&m->data), long int); if (permvec == 0) { IGRAPH_ERROR("can't perform cleanup on sparse matrix", IGRAPH_ENOMEM); } IGRAPH_FINALLY(free, permvec); for (ci=0, i=0, j=1; ci < m->ncol; ci++) { for (ei=VECTOR(m->cidx)[ci]; ei < VECTOR(m->cidx)[ci+1]; ei++) { if (VECTOR(m->data)[ei] == 0.0) { /* this element will be deleted, so all elements in cidx from the * column index of this element will have to be decreased by one */ nremove++; } else { /* this element will be kept */ permvec[i] = j; j++; } i++; } if (ci > 0) { VECTOR(m->cidx)[ci] -= nremove_old; } nremove_old = nremove; } VECTOR(m->cidx)[m->ncol] -= nremove; igraph_vector_permdelete(&m->ridx, permvec, nremove); igraph_vector_permdelete(&m->data, permvec, nremove); free(permvec); IGRAPH_FINALLY_CLEAN(1); return 0; } /** * \function igraph_spmatrix_clear_col * * Clears a column in the matrix (sets all of its elements to zero) * \param m The matrix. * \param col The index of the column to be cleared. * \return Error code. The current implementation always succeeds. * * Time complexity: TODO */ int igraph_spmatrix_clear_col(igraph_spmatrix_t *m, long int col) { long int i, n; assert(m != NULL); n = (long)VECTOR(m->cidx)[col+1] - (long)VECTOR(m->cidx)[col]; if (n == 0) return 0; igraph_vector_remove_section(&m->ridx, VECTOR(m->cidx)[col], VECTOR(m->cidx)[col+1]); igraph_vector_remove_section(&m->data, VECTOR(m->cidx)[col], VECTOR(m->cidx)[col+1]); for (i=col+1; i <= m->ncol; i++) { VECTOR(m->cidx)[i] -= n; } return 0; } /** * \function igraph_spmatrix_multiply * * Multiplies each element of the sparse matrix by a constant. * \param m The matrix. * \param by The constant. * * Time complexity: O(n), the number of elements in the matrix. */ void igraph_spmatrix_multiply(igraph_spmatrix_t *m, igraph_real_t by) { assert(m != NULL); igraph_vector_multiply(&m->data, by); } /** * \function igraph_spmatrix_colsums * * Calculates the column sums of the matrix. * \param m The matrix. * \param res An initialized \c igraph_vector_t, the result will be stored here. * The vector will be resized as needed. * * Time complexity: O(n), the number of nonzero elements in the matrix. */ int igraph_spmatrix_colsums(const igraph_spmatrix_t *m, igraph_vector_t *res) { long int i, c; assert(m != NULL); IGRAPH_CHECK(igraph_vector_resize(res, m->ncol)); igraph_vector_null(res); for (c=0; c < m->ncol; c++) { for (i=VECTOR(m->cidx)[c]; icidx)[c+1]; i++) { VECTOR(*res)[c] += VECTOR(m->data)[i]; } } return 0; } /** * \function igraph_spmatrix_max_nonzero * * Returns the maximum nonzero element of a matrix. * If the matrix is empty, zero is returned. * * \param m the matrix object. * \param ridx the row index of the maximum element if not \c NULL. * \param cidx the column index of the maximum element if not \c NULL. * * Time complexity: O(n), the number of nonzero elements in the matrix. */ igraph_real_t igraph_spmatrix_max_nonzero(const igraph_spmatrix_t *m, igraph_real_t *ridx, igraph_real_t *cidx) { igraph_real_t res; long int i, n, maxidx; assert(m != NULL); n=igraph_vector_size(&m->data); if (n == 0) return 0.0; maxidx = -1; for (i=0; idata)[i] != 0.0 && (maxidx == -1 || VECTOR(m->data)[i] >= VECTOR(m->data)[maxidx])) maxidx = i; if (maxidx == -1) return 0.0; res=VECTOR(m->data)[maxidx]; if (ridx != 0) *ridx = VECTOR(m->ridx)[maxidx]; if (cidx != 0) { igraph_vector_binsearch(&m->cidx, maxidx, &i); while (VECTOR(m->cidx)[i+1] == VECTOR(m->cidx)[i]) i++; *cidx = (igraph_real_t)i; } return res; } /** * \function igraph_spmatrix_max * * Returns the maximum element of a matrix. * If the matrix is empty, zero is returned. * * \param m the matrix object. * \param ridx the row index of the maximum element if not \c NULL. * \param cidx the column index of the maximum element if not \c NULL. * * Time complexity: O(n), the number of nonzero elements in the matrix. */ igraph_real_t igraph_spmatrix_max(const igraph_spmatrix_t *m, igraph_real_t *ridx, igraph_real_t *cidx) { igraph_real_t res; long int i, j, k, maxidx; assert(m != NULL); i=igraph_vector_size(&m->data); if (i == 0) return 0.0; maxidx=(long)igraph_vector_which_max(&m->data); res=VECTOR(m->data)[maxidx]; if (res>=0.0 || i == m->nrow * m->ncol) { if (ridx != 0) *ridx = VECTOR(m->ridx)[maxidx]; if (cidx != 0) { igraph_vector_binsearch(&m->cidx, maxidx, &i); while (VECTOR(m->cidx)[i+1] == VECTOR(m->cidx)[i]) i++; *cidx = (igraph_real_t)i; } return res; } /* the maximal nonzero element is negative and there is at least a * single zero */ res=0.0; if (cidx != 0 || ridx != 0) { for (i=0; i < m->ncol; i++) { if (VECTOR(m->cidx)[i+1] - VECTOR(m->cidx)[i] < m->nrow) { if (cidx != 0) *cidx = i; if (ridx != 0) { for (j=VECTOR(m->cidx)[i], k=0; j < VECTOR(m->cidx)[i+1]; j++, k++) { if (VECTOR(m->ridx)[j] != k) { *ridx = k; break; } } } break; } } } return res; } int igraph_i_spmatrix_get_col_nonzero_indices(const igraph_spmatrix_t *m, igraph_vector_t *res, long int col) { assert(m != NULL); long int i, n = VECTOR(m->cidx)[col+1]-VECTOR(m->cidx)[col]; IGRAPH_CHECK(igraph_vector_resize(res, n)); for (i=VECTOR(m->cidx)[col], n=0; icidx)[col+1]; i++, n++) if (VECTOR(m->data)[i] != 0.0) VECTOR(*res)[n] = VECTOR(m->ridx)[i]; return 0; }