/*
Copyright 2008-2010 Gephi
Authors : Mathieu Bastian <mathieu.bastian@gephi.org>
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.clustering.plugin.mcl;
import java.util.ArrayList;
import java.util.Collections;
/**
* SparseMatrix is a sparse matrix with row-major format.
* <p>
* Conventions: except for the inherited methods and normalise(double),
* operations leave <tt>this</tt> ummodified (immutable) if there is a return
* value. Within operations, no pruning of values close to zero is done. Pruning
* can be controlled via the prune() method.
*/
//Original author Gregor Heinrich
public class SparseMatrix extends ArrayList<SparseVector> {
private static final long serialVersionUID = 1L;
private int maxVLength;
/**
* empty sparse matrix
*/
public SparseMatrix() {
super();
}
/**
* empty sparse matrix with allocated number of rows
*
* @param rows
* @param cols
*/
public SparseMatrix(int rows, int cols) {
this();
adjustMaxIndex(rows - 1, cols - 1);
}
/**
* create sparse matrix from full matrix
*
* @param x
*/
public SparseMatrix(double[][] x) {
this(x.length - 1, x[0].length - 1);
for (int i = 0; i < x.length; i++) {
SparseVector v = new SparseVector(x[i]);
set(i, v);
}
}
/**
* copy contructor
*
* @param matrix
*/
public SparseMatrix(SparseMatrix matrix) {
for (SparseVector s : matrix) {
add(s.copy());
}
}
/**
* create dense representation
*
* @return
*/
public double[][] getDense() {
double[][] aa = new double[size()][];
for (int i = 0; i < size(); i++) {
aa[i] = new double[maxVLength];
for (int j : get(i).keySet()) {
aa[i][j] = get(i).get(j);
}
}
return aa;
}
/**
* set the sparse vector at index i.
*
* @param i
* @param x
* @return the old value of the element
*/
public SparseVector set(int i, SparseVector x) {
adjustMaxIndex(i, x.getLength() - 1);
return super.set(i, x);
}
/**
* get number at index or 0. if not set. If index > size, returns 0.
*
* @param i
* @param j
* @return
*/
public double get(int i, int j) {
if (i > size() - 1) {
return 0.;
}
return get(i).get(j);
}
/**
* set the value at the index i,j, returning the old value or 0. Increase
* matrix size if index exceeds the dimension.
*
* @param i
* @param j
* @param a
* @return
*/
public double set(int i, int j, double a) {
adjustMaxIndex(i, j);
double b = get(i).get(j);
get(i).put(j, a);
return b;
}
/**
* adjusts the size of the matrix.
*
* @param i index addressed
* @param j index addressed
*/
public void adjustMaxIndex(int i, int j) {
if (i > size() - 1) {
increase(i);
}
if (j >= maxVLength) {
maxVLength = j + 1;
for (int row = 0; row < size(); row++) {
get(row).setLength(maxVLength);
}
}
}
/**
* increase the size of the matrix with empty element SparseVectors.
*
* @param i
*/
private void increase(int i) {
addAll(Collections.nCopies(i - size() + 1, new SparseVector()));
}
/**
* get the size of the matrix
*
* @return
*/
public int[] getSize() {
return new int[]{size(), maxVLength};
}
/**
* adds a to the specified element, growing the matrix if necessary.
*
* @param i
* @param j
* @param a
* @return new value
*/
public double add(int i, int j, double a) {
adjustMaxIndex(i, j);
double b = get(i, j);
a += b;
set(i, j, a);
return a;
}
/**
* normalise rows to rowsum
*
* @param rowsum for each row
* @return vector of old row sums
*/
public SparseVector normalise(double rowsum) {
SparseVector sums = new SparseVector();
int i = 0;
for (SparseVector vec : this) {
sums.put(i, vec.normalise(rowsum));
i++;
}
return sums;
}
/**
* normalise by major dimension (rows)
*/
public void normaliseRows() {
for (SparseVector vec : this) {
vec.normalise();
}
}
/**
* normalise by minor dimension (columns), expensive.
*/
public void normaliseCols() {
double[] sums = new double[maxVLength];
for (int row = 0; row < size(); row++) {
for (int col = 0; col < get(row).getLength(); col++) {
sums[col] += get(row).get(col);
}
}
for (int row = 0; row < size(); row++) {
for (int col = 0; col < get(row).getLength(); col++) {
get(row).mult(col, 1 / sums[col]);
}
}
}
/**
* copy the matrix and its elements
*/
public SparseMatrix copy() {
return new SparseMatrix(this);
}
/**
* immutable multiply this times the vector: A * x, i.e., rowwise.
*
* @param v
* @return
*/
public SparseVector times(SparseVector v) {
SparseVector w = new SparseVector();
for (int i = 0; i < size(); i++) {
w.add(i, get(i).times(v));
}
return w;
}
/**
* immutable multiply the vector times this: x' * A, i.e., colwise.
*
* @param v
* @return
*/
public SparseVector vectorTimes(SparseVector v) {
SparseVector w = new SparseVector();
// only the rows in A that v is nonzero
for (int i : v.keySet()) {
SparseVector a = get(i).copy();
a.factor(v.get(i));
w.add(a);
}
return w;
}
/**
* mutable multiply this matrix (A) with M : A * M'
*
* @param m
* @return modified this
*/
public SparseMatrix timesTransposed(SparseMatrix m) {
// A*M = ;( A(i,:) * M )
for (int i = 0; i < size(); i++) {
set(i, m.times(get(i)));
}
return this;
}
/**
* immutable multiply this matrix (A) with M : A * M
*
* @param m
* @return matrix product
*/
public SparseMatrix times(SparseMatrix m) {
SparseMatrix s = new SparseMatrix();
for (int i = 0; i < size(); i++) {
for (int j = 0; j < m.size(); j++) {
for (int k : get(i).keySet()) {
double a = m.get(k, j);
if (a != 0.) {
s.add(i, j, get(i, k) * a);
}
}
}
}
return s;
}
/**
* immutable multiply matrix M with this (A) : M * A
*
* @param m
* @return
*/
public SparseMatrix matrixTimes(SparseMatrix m) {
return m.times(this);
}
/**
* immutable transpose.
*
* @return
*/
public SparseMatrix transpose() {
SparseMatrix s = new SparseMatrix();
for (int i = 0; i < size(); i++) {
s.set(i, getColum(i));
}
return s;
}
/**
* get a column of the sparse matrix (expensive).
*
* @return
*/
public SparseVector getColum(int i) {
SparseVector s = new SparseVector();
for (int row = 0; row < size(); row++) {
double v = get(row, i);
if (v != 0.) {
s.put(row, v);
}
}
return s;
}
/**
* mutable Hadamard product
*
* @param m
*/
public void hadamardProduct(SparseMatrix m) {
for (int i = 0; i < size(); i++) {
get(i).hadamardProduct(m.get(i));
}
}
/**
* mutable m2 = m .^ s
*
* @param s
* @return
*/
public void hadamardPower(double s) {
for (int i = 0; i < size(); i++) {
get(i).hadamardPower(s);
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < size(); i++) {
sb.append(i).append(" => ").append(get(i)).append("\n");
}
return sb.toString();
}
/**
* prints a dense representation
*
* @return
*/
public String toStringDense() {
double[][] dense = getDense();
StringBuffer b = new StringBuffer();
for (int i = 0; i < dense.length - 1; i++) {
for (int j = 0; j < dense[i].length; j++) {
b.append(Double.toString(dense[i][j])).append(" ");
}
b.append("\n");
}
return b.toString();
}
/**
* prune all values whose magnitude is below threshold
*/
public void prune(double threshold) {
// for (SparseVector v : this) {
for (int i = 0; i < size(); i++) {
SparseVector a = get(i);
a.prune(threshold);
}
}
}