package ch.akuhn.matrix; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; import ch.akuhn.matrix.Vector.Entry; import ch.akuhn.util.Files; import ch.akuhn.util.PrintOn; /** Two-dimensional table of floating point numbers. *<P> * @author Adrian Kuhn * */ public abstract class Matrix { public double add(int row, int column, double value) { return put(row, column, get(row, column) + value); } public Iterable<Vector> rows() { return vecs(/*isRow*/ true); } private Iterable<Vector> vecs(final boolean isRow) { return new Iterable<Vector>() { @Override public Iterator<Vector> iterator() { return new Iterator<Vector>() { private int count = 0; @Override public boolean hasNext() { return count < (isRow ? rowCount() : columnCount()); } @Override public Vector next() { if (!hasNext()) throw new NoSuchElementException(); return new Vec(count++, isRow); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } public Iterable<Vector> columns() { return vecs(/*isRow*/ false); } public abstract int columnCount(); public double density() { return (double) used() / elementCount(); } public int elementCount() { return rowCount() * columnCount(); } public abstract double get(int row, int column); public abstract double put(int row, int column, double value); public abstract int rowCount(); public abstract int used(); /** @see http://tedlab.mit.edu/~dr/svdlibc/SVD_F_ST.html */ public void storeSparseOn(Appendable appendable) { // this stores the transposed matrix, but as we will transpose it again // when reading it, this can be done without loss of generality. PrintOn out = new PrintOn(appendable); out.print(this.columnCount()).space(); out.print(this.rowCount()).space(); out.print(this.used()).cr(); for (Vector row: rows()) { out.print(row.used()).cr(); for (Entry each: row.entries()) { out.print(each.index).space().print(each.value).space(); } out.cr(); } out.close(); } public void storeSparseOn(String fname) { storeSparseOn(Files.openWrite(fname)); } public Vector row(int row) { return new Vec(row, /*isRow*/ true); } public Vector column(int column) { return new Vec(column, /*isRow*/ false); } public double[][] asArray() { double[][] result = new double[rowCount()][columnCount()]; for (int x = 0; x < result.length; x++) { for (int y = 0; y < result.length; y++) { result[x][y] = get(x,y); } } return result; } public static int indexOf(Vector vec) { return ((Vec) vec).index0; } private class Vec extends Vector { int index0; private boolean isRow; Vec(int n, boolean isRow) { this.isRow = isRow; this.index0 = n; } @Override public int size() { return isRow ? columnCount() : rowCount(); } @Override public double put(int index, double value) { return isRow ? Matrix.this.put(this.index0, index, value) : Matrix.this.put(index, this.index0, value); } @Override public double get(int index) { return isRow ? Matrix.this.get(this.index0, index) : Matrix.this.get(index, this.index0); } @Override public boolean equals(Vector v, double epsilon) { throw new Error("Not yet implemented"); } @Override public Vector times(double scalar) { throw new Error("Not yet implemented"); } @Override public void apply(Function f) { throw new Error("Not yet implemented"); } } /** Returns <code>y = Ax</code>. * */ public Vector mult(Vector x) { assert x.size() == this.columnCount(); Vector y = Vector.dense(this.rowCount()); int i = 0; for (Vector row: rows()) y.put(i++, row.dot(x)); return y; } /** Returns <code>y = (A^T)x</code>. * */ public Vector transposeMultiply(Vector x) { assert x.size() == this.rowCount(); Vector y = Vector.dense(this.columnCount()); int i = 0; for (Vector row: rows()) row.scaleAndAddTo(x.get(i++), y); return y; } /** Returns <code>y = (A^T)Ax</code>. *<P> * Useful for doing singular decomposition using ARPACK's dsaupd routine. * */ public Vector transposeNonTransposeMultiply(Vector x) { return this.transposeMultiply(this.mult(x)); } public static Matrix from(int n, int m, double... values) { assert n * m == values.length; double[][] data = new double[n][]; for (int i = 0; i < n; i++) data[i] = Arrays.copyOfRange(values, i*m, (i+1)*m); return new DenseMatrix(data); } public static Matrix dense(int n, int m) { return new DenseMatrix(n, m); } public boolean isSquare() { return columnCount() == rowCount(); } public double[] asColumnMajorArray() { double[] data = new double[columnCount() * rowCount()]; int n = columnCount(); int i = 0; for (Vector row: rows()) { for (Entry each: row.entries()) { data[i + each.index * n] = each.value; } i++; } return data; } public static SparseMatrix sparse(int n, int m) { return new SparseMatrix(n, m); } public void apply(Function f) { f.apply(this.unwrap()); } public double max() { return Util.max(this.unwrap(), Double.NaN); } public double min() { return Util.min(this.unwrap(), Double.NaN); } public double mean() { double[][] values = unwrap(); return Util.sum(values) / Util.count(values); } public double[][] unwrap() { throw new IllegalStateException("cannot unwrap instance of " + this.getClass().getSimpleName()); } public double[] rowwiseMean() { double[] mean = new double[rowCount()]; int i = 0; for (Vector row: rows()) mean[i++] = row.mean(); return mean; } public int[] getHistogram() { return Util.getHistogram(this.unwrap(), 100); } }