/* -*- mode: C -*-  */
/* 
   IGraph library.
   Copyright (C) 2003, 2004, 2005  Gabor Csardi <csardi@rmki.kfki.hu>
   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 <assert.h>
#include <string.h>     /* memcpy & co. */
#include <stdlib.h>

/**
 * \section igraph_spmatrix_constructor_and_destructor Sparse matrix constructors
 * and destructors.
 */

/**
 * \ingroup matrix
 * \function igraph_spmatrix_init
 * \brief Initializes a sparse matrix.
 * 
 * </para><para>
 * 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.
 * 
 * </para><para>
 * 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.
 *
 * </para><para>
 * 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 (end<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)
    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 (end<start) {
    /* First element in the column */
    if (value == 0.0) return 0;
    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;
  }

  /* 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 (end<start) {
    /* First element in the column */
    if (value == 0.0) return 0;
    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;
  }

  /* 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]; i<VECTOR(m->cidx)[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.
 *
 * </para><para>
 * 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 (; ei<VECTOR(m->cidx)[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; i<ncol+1; i++) 
    VECTOR(m->cidx)[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.
 *
 * </para><para>
 * 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]; idx<VECTOR(m->cidx)[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; ei<n; ei++) {
    if (VECTOR(m->ridx)[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]; i<VECTOR(m->cidx)[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; i<n; i++)
    if (VECTOR(m->data)[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; i<VECTOR(m->cidx)[col+1]; i++, n++)
    if (VECTOR(m->data)[i] != 0.0) VECTOR(*res)[n] = VECTOR(m->ridx)[i];
  return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1