/*
* Copyright 2011-2014, by Vladimir Kostyukov and Contributors.
*
* This file is part of la4j project (http://la4j.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributor(s): -
*
*/
package org.la4j.matrix;
import org.la4j.iterator.ColumnMajorMatrixIterator;
import org.la4j.iterator.MatrixIterator;
import org.la4j.iterator.RowMajorMatrixIterator;
import org.la4j.iterator.VectorIterator;
import org.la4j.Matrices;
import org.la4j.Matrix;
import org.la4j.matrix.functor.MatrixAccumulator;
import org.la4j.matrix.functor.MatrixProcedure;
import org.la4j.Vector;
import org.la4j.Vectors;
import org.la4j.matrix.sparse.CCSMatrix;
import org.la4j.matrix.sparse.CRSMatrix;
import org.la4j.vector.functor.VectorAccumulator;
import org.la4j.vector.functor.VectorProcedure;
import org.la4j.vector.SparseVector;
import java.text.NumberFormat;
import java.util.NoSuchElementException;
import java.util.Random;
public abstract class SparseMatrix extends Matrix {
protected int cardinality;
public SparseMatrix(int rows, int columns) {
this(rows, columns, 0);
}
public SparseMatrix(int rows, int columns, int cardinality) {
super(rows, columns);
this.cardinality = cardinality;
}
/**
* Creates a zero {@link SparseMatrix} of the given shape:
* {@code rows} x {@code columns}.
*/
public static SparseMatrix zero(int rows, int columns) {
return CCSMatrix.zero(rows, columns);
}
/**
* Creates a zero {@link SparseMatrix} of the given shape:
* {@code rows} x {@code columns} with the given {@code capacity}.
*/
public static SparseMatrix zero(int rows, int columns, int capacity) {
return CRSMatrix.zero(rows, columns, capacity);
}
/**
* Creates a diagonal {@link SparseMatrix} of the given {@code size} whose
* diagonal elements are equal to {@code diagonal}.
*/
public static SparseMatrix diagonal(int size, double diagonal) {
return CRSMatrix.diagonal(size, diagonal);
}
/**
* Creates an identity {@link SparseMatrix} of the given {@code size}.
*/
public static SparseMatrix identity(int size) {
return CRSMatrix.identity(size);
}
/**
* Creates a random {@link SparseMatrix} of the given shape:
* {@code rows} x {@code columns}.
*/
public static SparseMatrix random(int rows, int columns, double density, Random random) {
return CRSMatrix.random(rows, columns, density, random);
}
/**
* Creates a random symmetric {@link SparseMatrix} of the given {@code size}.
*/
public static SparseMatrix randomSymmetric(int size, double density, Random random) {
return CRSMatrix.randomSymmetric(size, density, random);
}
/**
* Creates a new {@link SparseMatrix} from the given 1D {@code array} with
* compressing (copying) the underlying array.
*/
public static SparseMatrix from1DArray(int rows, int columns, double[] array) {
return CRSMatrix.from1DArray(rows, columns, array);
}
/**
* Creates a new {@link SparseMatrix} from the given 2D {@code array} with
* compressing (copying) the underlying array.
*/
public static SparseMatrix from2DArray(double[][] array) {
return CRSMatrix.from2DArray(array);
}
/**
* Creates a block {@link SparseMatrix} of the given blocks {@code a},
* {@code b}, {@code c} and {@code d}.
*/
public static SparseMatrix block(Matrix a, Matrix b, Matrix c, Matrix d) {
return CRSMatrix.block(a, b, c, d);
}
/**
* Parses {@link SparseMatrix} from the given CSV string.
*
* @param csv the CSV string representing a matrix
*
* @return a parsed matrix
*/
public static SparseMatrix fromCSV(String csv) {
return Matrix.fromCSV(csv).to(Matrices.SPARSE);
}
/**
* Parses {@link SparseMatrix} from the given Matrix Market string.
*
* @param mm the string in Matrix Market format
*
* @return a parsed matrix
*/
public static SparseMatrix fromMatrixMarket(String mm) {
return Matrix.fromMatrixMarket(mm).to(Matrices.SPARSE);
}
@Override
public double get(int i, int j) {
return getOrElse(i, j, 0.0);
}
/**
* Gets the specified element, or a {@code defaultValue} if there
* is no actual element at ({@code i}, {@code j}) in this sparse matrix.
*
* @param i the element's row index
* @param j the element's column index
* @param defaultValue the default value
*
* @return the element of this vector or a default value
*/
public abstract double getOrElse(int i, int j, double defaultValue);
/**
* Checks whether or not this sparse matrix row-major.
*/
public abstract boolean isRowMajor();
public boolean isColumnMajor() {
return !isRowMajor();
}
/**
* Returns the cardinality (the number of non-zero elements)
* of this sparse matrix.
*
* @return the cardinality of this matrix
*/
public int cardinality() {
return cardinality;
}
/**
* Returns the density (non-zero elements divided by total elements)
* of this sparse matrix.
*
* @return the density of this matrix
*/
public double density() {
return cardinality / (double) (rows * columns);
}
/**
* @return a capacity of this sparse matrix
*/
protected long capacity() {
return ((long) rows) * columns;
}
@Override
public Vector getRow(int i) {
Vector result = SparseVector.zero(columns);
VectorIterator it = nonZeroIteratorOfRow(i);
while (it.hasNext()) {
double x = it.next();
int j = it.index();
result.set(j, x);
}
return result;
}
@Override
public Vector getColumn(int j) {
Vector result = SparseVector.zero(rows);
VectorIterator it = nonZeroIteratorOfColumn(j);
while (it.hasNext()) {
double x = it.next();
int i = it.index();
result.set(i, x);
}
return result;
}
@Override
public Matrix multiply(double value) {
MatrixIterator it = nonZeroIterator();
Matrix result = blank();
while (it.hasNext()) {
double x = it.next();
int i = it.rowIndex();
int j = it.columnIndex();
result.set(i, j, x * value);
}
return result;
}
@Override
public Matrix add(double value) {
MatrixIterator it = nonZeroIterator();
Matrix result = DenseMatrix.constant(rows, columns, value);
while (it.hasNext()) {
double x = it.next();
int i = it.rowIndex();
int j = it.columnIndex();
result.set(i, j, x + value);
}
return result;
}
/**
* Whether or not the specified element is zero.
*
* @param i element's row index
* @param j element's column index
*
* @return {@code true} if specified element is zero, {@code false} otherwise
*/
public boolean isZeroAt(int i, int j) {
return !nonZeroAt(i, j);
}
/**
* Whether or not the specified element is not zero.
*
* @param i element's row index
* @param j element's column index
*
* @return {@code true} if specified element is not zero, {@code false} otherwise
*/
public abstract boolean nonZeroAt(int i, int j);
/**
* Applies given {@code procedure} to each non-zero element of this matrix.
*
* @param procedure the matrix procedure
*/
public void eachNonZero(MatrixProcedure procedure) {
MatrixIterator it = nonZeroIterator();
while (it.hasNext()) {
double x = it.next();
int i = it.rowIndex();
int j = it.columnIndex();
procedure.apply(i, j, x);
}
}
/**
* Applies the given {@code procedure} to each non-zero element of the specified row of this matrix.
*
* @param i the row index.
* @param procedure the {@link VectorProcedure}.
*/
public void eachNonZeroInRow(int i, VectorProcedure procedure) {
VectorIterator it = nonZeroIteratorOfRow(i);
while (it.hasNext()) {
double x = it.next();
int j = it.index();
procedure.apply(j, x);
}
}
/**
* Applies the given {@code procedure} to each non-zero element of the specified column of this matrix.
*
* @param j the column index.
* @param procedure the {@link VectorProcedure}.
*/
public void eachNonZeroInColumn(int j, VectorProcedure procedure) {
VectorIterator it = nonZeroIteratorOfColumn(j);
while (it.hasNext()) {
double x = it.next();
int i = it.index();
procedure.apply(i, x);
}
}
/**
* Folds non-zero elements of this matrix with given {@code accumulator}.
*
* @param accumulator the matrix accumulator
*
* @return the accumulated value
*/
public double foldNonZero(MatrixAccumulator accumulator) {
eachNonZero(Matrices.asAccumulatorProcedure(accumulator));
return accumulator.accumulate();
}
/**
* Folds non-zero elements of the specified row in this matrix with the given {@code accumulator}.
*
* @param i the row index.
* @param accumulator the {@link VectorAccumulator}.
*
* @return the accumulated value.
*/
public double foldNonZeroInRow(int i, VectorAccumulator accumulator) {
eachNonZeroInRow(i, Vectors.asAccumulatorProcedure(accumulator));
return accumulator.accumulate();
}
/**
* Folds non-zero elements of the specified column in this matrix with the given {@code accumulator}.
*
* @param j the column index.
* @param accumulator the {@link VectorAccumulator}.
*
* @return the accumulated value.
*/
public double foldNonZeroInColumn(int j, VectorAccumulator accumulator) {
eachNonZeroInColumn(j, Vectors.asAccumulatorProcedure(accumulator));
return accumulator.accumulate();
}
/**
* Folds non-zero elements (in a column-by-column manner) of this matrix with given {@code accumulator}.
*
* @param accumulator the matrix accumulator
*
* @return the accumulated vector
*/
public double[] foldNonZeroInColumns(VectorAccumulator accumulator) {
double[] result = new double[columns];
for (int j = 0; j < columns; j++) {
result[j] = foldNonZeroInColumn(j, accumulator);
}
return result;
}
/**
* Folds non-zero elements (in a row-by-row manner) of this matrix with given {@code accumulator}.
*
* @param accumulator the matrix accumulator
*
* @return the accumulated vector
*/
public double[] foldNonZeroInRows(VectorAccumulator accumulator) {
double[] result = new double[rows];
for (int i = 0; i < rows; i++) {
result[i] = foldNonZeroInRow(i, accumulator);
}
return result;
}
/**
* Returns a non-zero matrix iterator.
*
* @return a non-zero matrix iterator
*/
public MatrixIterator nonZeroIterator() {
return nonZeroRowMajorIterator();
}
/**
* Returns a non-zero row-major matrix iterator.
*
* @return a non-zero row-major matrix iterator.
*/
public RowMajorMatrixIterator nonZeroRowMajorIterator() {
return new RowMajorMatrixIterator(rows, columns) {
private long limit = (long) rows * columns;
private long i = -1;
@Override
public int rowIndex() {
return (int) (i / columns);
}
@Override
public int columnIndex() {
return (int) (i - ((i / columns) * columns));
}
@Override
public double get() {
return SparseMatrix.this.get(rowIndex(), columnIndex());
}
@Override
public void set(double value) {
SparseMatrix.this.set(rowIndex(), columnIndex(), value);
}
@Override
public boolean hasNext() {
while (i + 1 < limit) {
i++;
if (SparseMatrix.this.nonZeroAt(rowIndex(), columnIndex())) {
i--;
break;
}
}
return i + 1 < limit;
}
@Override
public Double next() {
if(!hasNext()) {
throw new NoSuchElementException();
}
i++;
return get();
}
};
}
/**
* Returns a non-zero column-major matrix iterator.
*
* @return a non-zero column major matrix iterator.
*/
public ColumnMajorMatrixIterator nonZeroColumnMajorIterator() {
return new ColumnMajorMatrixIterator(rows, columns) {
private long limit = (long) rows * columns;
private long i = -1;
@Override
public int rowIndex() {
return (int) (i - ((i / rows) * rows));
}
@Override
public int columnIndex() {
return (int) (i / rows);
}
@Override
public double get() {
return SparseMatrix.this.get(rowIndex(), columnIndex());
}
@Override
public void set(double value) {
SparseMatrix.this.set(rowIndex(), columnIndex(), value);
}
@Override
public boolean hasNext() {
while (i + 1 < limit) {
i++;
if (SparseMatrix.this.nonZeroAt(rowIndex(), columnIndex())) {
i--;
break;
}
}
return i + 1 < limit;
}
@Override
public Double next() {
if(!hasNext()) {
throw new NoSuchElementException();
}
i++;
return get();
}
};
}
/**
* Returns a non-zero vector iterator of the given row {@code i}.
*
* @return a non-zero vector iterator
*/
public VectorIterator nonZeroIteratorOfRow(int i) {
final int ii = i;
return new VectorIterator(columns) {
private int j = -1;
@Override
public int index() {
return j;
}
@Override
public double get() {
return SparseMatrix.this.get(ii, j);
}
@Override
public void set(double value) {
SparseMatrix.this.set(ii, j, value);
}
@Override
public boolean hasNext() {
while (j + 1 < columns && SparseMatrix.this.isZeroAt(ii, j + 1)) {
j++;
}
return j + 1 < columns;
}
@Override
public Double next() {
if(!hasNext()) {
throw new NoSuchElementException();
}
j++;
return get();
}
};
}
/**
* Returns a non-zero vector iterator of the given column {@code j}.
*
* @return a non-zero vector iterator
*/
public VectorIterator nonZeroIteratorOfColumn(int j) {
final int jj = j;
return new VectorIterator(rows) {
private int i = -1;
@Override
public int index() {
return i;
}
@Override
public double get() {
return SparseMatrix.this.get(i, jj);
}
@Override
public void set(double value) {
SparseMatrix.this.set(i, jj, value);
}
@Override
public boolean hasNext() {
while (i + 1 < rows && SparseMatrix.this.isZeroAt(i + 1, jj)) {
i++;
}
return i + 1 < rows;
}
@Override
public Double next() {
if(!hasNext()) {
throw new NoSuchElementException();
}
i++;
return get();
}
};
}
@Override
public String toMatrixMarket(NumberFormat formatter) {
String majority = isRowMajor() ? "row-major" : "column-major";
StringBuilder out = new StringBuilder();
MatrixIterator it = nonZeroIterator();
out.append("%%MatrixMarket matrix coordinate real general ")
.append(majority).append('\n');
out.append(rows).append(' ').append(columns).append(' ')
.append(cardinality).append('\n');
while (it.hasNext()) {
double x = it.next();
int i = it.rowIndex();
int j = it.columnIndex();
out.append(i + 1).append(' ').append(j + 1).append(' ')
.append(formatter.format(x)).append('\n');
}
return out.toString();
}
protected void ensureCardinalityIsCorrect(long rows, long columns, long cardinality) {
if (cardinality < 0) {
fail("Cardinality should be positive: " + cardinality + ".");
}
long capacity = capacity();
if (cardinality > capacity) {
fail("Cardinality should be less then or equal to capacity: " + capacity + ".");
}
}
}