import jalview.util.MessageManager;
import java.io.PrintStream;
+import java.lang.Math;
+import java.util.Arrays;
/**
* A class to model rectangular matrices of double values and operations on them
public class Matrix implements MatrixI
{
/*
- * the cell values in row-major order
+ * maximum number of iterations for tqli
*/
- private double[][] value;
+ private static final int MAX_ITER = 45;
+ // fudge - add 15 iterations, just in case
/*
* the number of rows
*/
- protected int rows;
+ final protected int rows;
/*
* the number of columns
*/
- protected int cols;
+ final protected int cols;
+
+ /*
+ * the cell values in row-major order
+ */
+ private double[][] value;
protected double[] d; // Diagonal
protected double[] e; // off diagonal
/**
- * maximum number of iterations for tqli
+ * Constructor given number of rows and columns
*
+ * @param colCount
+ * @param rowCount
*/
- private static final int maxIter = 45; // fudge - add 15 iterations, just in
- // case
-
- /**
- * Default constructor
- */
- public Matrix()
+ protected Matrix(int rowCount, int colCount)
{
-
+ rows = rowCount;
+ cols = colCount;
}
-
+
/**
- * Creates a new Matrix object. For example
+ * Creates a new Matrix object containing a copy of the supplied array values.
+ * For example
*
* <pre>
* new Matrix(new double[][] {{2, 3, 4}, {5, 6, 7})
{
this.rows = values.length;
this.cols = this.rows == 0 ? 0 : values[0].length;
- this.value = values;
+
+ /*
+ * make a copy of the values array, for immutability
+ */
+ this.value = new double[rows][];
+ int i = 0;
+ for (double[] row : values)
+ {
+ if (row != null)
+ {
+ value[i] = new double[row.length];
+ System.arraycopy(row, 0, value[i], 0, row.length);
+ }
+ i++;
+ }
+ }
+
+ public Matrix(float[][] values)
+ {
+ this.rows = values.length;
+ this.cols = this.rows == 0 ? 0 : values[0].length;
+
+ /*
+ * make a copy of the values array, for immutability
+ */
+ this.value = new double[rows][];
+ int i = 0;
+ for (float[] row : values)
+ {
+ if (row != null)
+ {
+ value[i] = new double[row.length];
+ int j = 0;
+ for (float oldValue : row)
+ {
+ value[i][j] = oldValue;
+ j++;
+ }
+ }
+ i++;
+ }
}
- /**
- * Returns a new matrix which is the transpose of this one
- *
- * @return DOCUMENT ME!
- */
@Override
public MatrixI transpose()
{
}
}
- /**
- * Returns a new matrix which is the result of premultiplying this matrix by
- * the supplied argument. If this of size AxB (A rows and B columns), and the
- * argument is CxA (C rows and A columns), the result is of size CxB.
- *
- * @param in
- *
- * @return
- * @throws IllegalArgumentException
- * if the number of columns in the pre-multiplier is not equal to
- * the number of rows in the multiplicand (this)
- */
@Override
public MatrixI preMultiply(MatrixI in)
{
*/
for (int k = 0; k < in.width(); k++)
{
- tmp[i][j] += (in.getValue(i, k) * this.value[k][j]);
+ if (!Double.isNaN(in.getValue(i,k)) && !Double.isNaN(this.value[k][j]))
+ {
+ tmp[i][j] += (in.getValue(i, k) * this.value[k][j]);
+ }
}
}
}
return out;
}
- /**
- * Returns a new matrix which is the result of postmultiplying this matrix by
- * the supplied argument. If this of size AxB (A rows and B columns), and the
- * argument is BxC (B rows and C columns), the result is of size AxC.
- * <p>
- * This method simply returns the result of in.preMultiply(this)
- *
- * @param in
- *
- * @return
- * @throws IllegalArgumentException
- * if the number of rows in the post-multiplier is not equal to the
- * number of columns in the multiplicand (this)
- * @see #preMultiply(Matrix)
- */
@Override
public MatrixI postMultiply(MatrixI in)
{
return in.preMultiply(this);
}
- /**
- * Answers a new matrix with a copy of the values in this one
- *
- * @return
- */
@Override
public MatrixI copy()
{
System.arraycopy(value[i], 0, newmat[i], 0, value[i].length);
}
- return new Matrix(newmat);
+ Matrix m = new Matrix(newmat);
+ if (this.d != null)
+ {
+ m.d = Arrays.copyOf(this.d, this.d.length);
+ }
+ if (this.e != null)
+ {
+ m.e = Arrays.copyOf(this.e, this.e.length);
+ }
+
+ return m;
}
/**
{
iter++;
- if (iter == maxIter)
+ if (iter == MAX_ITER)
{
throw new Exception(MessageManager.formatMessage(
- "exception.matrix_too_many_iteration", new String[] {
- "tqli", Integer.valueOf(maxIter).toString() }));
+ "exception.matrix_too_many_iteration", new String[]
+ { "tqli", Integer.valueOf(MAX_ITER).toString() }));
}
else
{
- // System.out.println("Iteration " + iter);
+ // jalview.bin.Console.outPrintln("Iteration " + iter);
}
g = (d[l] - d[l - 1]) / (2.0 * e[l - 1]);
{
f = getValue(k - 1, i);
setValue(k - 1, i, (s * getValue(k - 1, i - 1)) + (c * f));
- setValue(k - 1, i - 1, (c * getValue(k - 1, i - 1)) - (s * f));
+ setValue(k - 1, i - 1,
+ (c * getValue(k - 1, i - 1)) - (s * f));
}
}
return value[i][j];
}
+ @Override
public void setValue(int i, int j, double val)
{
value[i][j] = val;
{
iter++;
- if (iter == maxIter)
+ if (iter == MAX_ITER)
{
throw new Exception(MessageManager.formatMessage(
- "exception.matrix_too_many_iteration", new String[] {
- "tqli2", Integer.valueOf(maxIter).toString() }));
+ "exception.matrix_too_many_iteration", new String[]
+ { "tqli2", Integer.valueOf(MAX_ITER).toString() }));
}
else
{
- // System.out.println("Iteration " + iter);
+ // jalview.bin.Console.outPrintln("Iteration " + iter);
}
g = (d[l] - d[l - 1]) / (2.0 * e[l - 1]);
}
/**
+ * returns the matrix as a double[][] array
+ *
+ * @return
+ */
+ @Override
+ public double[][] asArray()
+ {
+ return value;
+ }
+
+ /**
* Returns an array containing the values in the specified column
*
* @param col
*
* @return
*/
+ @Override
public double[] getColumn(int col)
{
double[] out = new double[rows];
@Override
public void printD(PrintStream ps, String format)
{
- for (int j = 0; j < rows; j++)
+ for (int j = 0; j < d.length; j++)
{
Format.print(ps, format, d[j]);
}
*
* @param ps
* DOCUMENT ME!
- * @param format TODO
+ * @param format
+ * TODO
*/
@Override
public void printE(PrintStream ps, String format)
{
return e;
}
-
+
@Override
- public int height() {
+ public int height()
+ {
return rows;
}
System.arraycopy(value[i], 0, row, 0, cols);
return row;
}
+
+ /**
+ * Returns a length 2 array of {minValue, maxValue} of all values in the
+ * matrix. Returns null if the matrix is null or empty.
+ *
+ * @return
+ */
+ double[] findMinMax()
+ {
+ if (value == null)
+ {
+ return null;
+ }
+ double min = Double.MAX_VALUE;
+ double max = -Double.MAX_VALUE;
+ boolean empty = true;
+ for (double[] row : value)
+ {
+ if (row != null)
+ {
+ for (double x : row)
+ {
+ empty = false;
+ if (x > max)
+ {
+ max = x;
+ }
+ if (x < min)
+ {
+ min = x;
+ }
+ }
+ }
+ }
+ return empty ? null : new double[] { min, max };
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void reverseRange(boolean maxToZero)
+ {
+ if (value == null)
+ {
+ return;
+ }
+ double[] minMax = findMinMax();
+ if (minMax == null)
+ {
+ return; // empty matrix
+ }
+ double subtractFrom = maxToZero ? minMax[1] : minMax[0] + minMax[1];
+
+ for (double[] row : value)
+ {
+ if (row != null)
+ {
+ int j = 0;
+ for (double x : row)
+ {
+ row[j] = subtractFrom - x;
+ j++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Multiplies every entry in the matrix by the given value.
+ *
+ * @param
+ */
+ @Override
+ public void multiply(double by)
+ {
+ for (double[] row : value)
+ {
+ if (row != null)
+ {
+ for (int i = 0; i < row.length; i++)
+ {
+ row[i] *= by;
+ }
+ }
+ }
+ }
+
+ /**
+ * Add d to all entries of this matrix
+ *
+ * @param d ~ value to add
+ */
+ @Override
+ public void add(double d)
+ {
+ for (double[] row : value)
+ {
+ if (row != null)
+ {
+ for (int i = 0; i < row.length; i++)
+ {
+ row[i] += d;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setD(double[] v)
+ {
+ d = v;
+ }
+
+ @Override
+ public void setE(double[] v)
+ {
+ e = v;
+ }
+
+ public double getTotal()
+ {
+ double d = 0d;
+ for (int i = 0; i < this.height(); i++)
+ {
+ for (int j = 0; j < this.width(); j++)
+ {
+ d += value[i][j];
+ }
+ }
+ return d;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(MatrixI m2, double delta)
+ {
+ if (m2 == null || this.height() != m2.height()
+ || this.width() != m2.width())
+ {
+ return false;
+ }
+ for (int i = 0; i < this.height(); i++)
+ {
+ for (int j = 0; j < this.width(); j++)
+ {
+ double diff = this.getValue(i, j) - m2.getValue(i, j);
+ if (Math.abs(diff) > delta)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a copy in which every value in the matrix is its absolute
+ *
+ * @return
+ */
+ @Override
+ public MatrixI absolute()
+ {
+ MatrixI copy = this.copy();
+ for (int i = 0; i < copy.width(); i++)
+ {
+ double[] row = copy.getRow(i);
+ if (row != null)
+ {
+ for (int j = 0; j < row.length; j++)
+ {
+ row[j] = Math.abs(row[j]);
+ }
+ }
+ }
+ return copy;
+ }
+
+ /**
+ * Returns the mean of each row
+ *
+ * @return
+ */
+ @Override
+ public double[] meanRow()
+ {
+ double[] mean = new double[rows];
+ int i = 0;
+ for (double[] row : value)
+ {
+ if (row != null)
+ {
+ mean[i++] = MiscMath.mean(row);
+ }
+ }
+ return mean;
+ }
+
+ /**
+ * Returns the mean of each column
+ *
+ * @return
+ */
+ @Override
+ public double[] meanCol()
+ {
+ double[] mean = new double[cols];
+ for (int j = 0; j < cols; j++)
+ {
+ double[] column = getColumn(j);
+ if (column != null)
+ {
+ mean[j] = MiscMath.mean(column);
+ }
+ }
+ return mean;
+ }
+
+ /**
+ * return a flattened matrix containing the sum of each column
+ *
+ * @return
+ */
+ @Override
+ public double[] sumCol()
+ {
+ double[] sum = new double[cols];
+ for (int j = 0; j < cols; j++)
+ {
+ double[] column = getColumn(j);
+ if (column != null)
+ {
+ sum[j] = MiscMath.sum(column);
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * returns the mean value of the complete matrix
+ *
+ * @return
+ */
+ @Override
+ public double mean()
+ {
+ double sum = 0;
+ int nanCount = 0;
+ for (double[] row : value)
+ {
+ for (double col : row)
+ {
+ if (!Double.isNaN(col))
+ {
+ sum += col;
+ } else {
+ nanCount++;
+ }
+ }
+ }
+ return sum / (double) (this.rows * this.cols - nanCount);
+ }
+
+ /**
+ * fills up a diagonal matrix with its transposed copy
+ * !other side should be filled with 0
+ * !keeps Double.NaN found in either side
+ *
+ * TODO check on which side it was diagonal and only do calculations for the other side
+ */
+ @Override
+ public void fillDiagonal()
+ {
+ int n = this.rows;
+ int m = this.cols;
+ MatrixI copy = this.transpose(); // goes through each element in the matrix and
+ for (int i = 0; i < n; i++) // adds the value in the transposed copy to the original value
+ {
+ for (int j = 0; j < m; j++)
+ {
+ if (i != j)
+ {
+ this.addValue(i, j, copy.getValue(i,j));
+ }
+ }
+ }
+ }
+
+ /**
+ * counts the number of Double.NaN in the matrix
+ *
+ * @return
+ */
+ @Override
+ public int countNaN()
+ {
+ int NaN = 0;
+ for (int i = 0; i < this.rows; i++)
+ {
+ for (int j = 0; j < this.cols; j++)
+ {
+ if (Double.isNaN(this.getValue(i,j)))
+ {
+ NaN++;
+ }
+ }
+ }
+ return NaN;
+ }
+
+ /**
+ * performs an element-wise addition of this matrix by another matrix ~ this - m
+ * @param m ~ other matrix
+ *
+ * @return
+ */
+ @Override
+ public MatrixI add(MatrixI m)
+ {
+ if (m.width() != cols || m.height() != rows)
+ {
+ throw new IllegalArgumentException("Can't add a " + m.height() + "x" + m.width() + " to a " + this.rows + "x" + this.cols + " matrix");
+ }
+ double[][] tmp = new double[this.rows][this.cols];
+ for (int i = 0; i < this.rows; i++)
+ {
+ for (int j = 0; j < this.cols; j++)
+ {
+ tmp[i][j] = this.getValue(i,j) + m.getValue(i,j);
+ }
+ }
+ return new Matrix(tmp);
+ }
+
+ /**
+ * performs an element-wise subtraction of this matrix by another matrix ~ this - m
+ * @param m ~ other matrix
+ *
+ * @return
+ */
+ @Override
+ public MatrixI subtract(MatrixI m)
+ {
+ if (m.width() != cols || m.height() != rows)
+ {
+ throw new IllegalArgumentException("Can't subtract a " + m.height() + "x" + m.width() + " from a " + this.rows + "x" + this.cols + " matrix");
+ }
+ double[][] tmp = new double[this.rows][this.cols];
+ for (int i = 0; i < this.rows; i++)
+ {
+ for (int j = 0; j < this.cols; j++)
+ {
+ tmp[i][j] = this.getValue(i,j) - m.getValue(i,j);
+ }
+ }
+ return new Matrix(tmp);
+ }
+
+ /**
+ * performs an element-wise multiplication of this matrix by another matrix ~ this * m
+ * @param m ~ other matrix
+ *
+ * @return
+ */
+ @Override
+ public MatrixI elementwiseMultiply(MatrixI m)
+ {
+ if (m.width() != cols || m.height() != rows)
+ {
+ throw new IllegalArgumentException("Can't multiply a " + this.rows + "x" + this.cols + " by a " + m.height() + "x" + m.width() + " matrix");
+ }
+ double[][] tmp = new double[this.rows][this.cols];
+ for (int i = 0; i < this.rows; i++)
+ {
+ for (int j = 0; j < this.cols; j++)
+ {
+ tmp[i][j] = this.getValue(i, j) * m.getValue(i,j);
+ }
+ }
+ return new Matrix(tmp);
+ }
+
+ /**
+ * performs an element-wise division of this matrix by another matrix ~ this / m
+ * @param m ~ other matrix
+ *
+ * @return
+ */
+ @Override
+ public MatrixI elementwiseDivide(MatrixI m)
+ {
+ if (m.width() != cols || m.height() != rows)
+ {
+ throw new IllegalArgumentException("Can't divide a " + this.rows + "x" + this.cols + " by a " + m.height() + "x" + m.width() + " matrix");
+ }
+ double[][] tmp = new double[this.rows][this.cols];
+ for (int i = 0; i < this.rows; i++)
+ {
+ for (int j = 0; j < this.cols; j++)
+ {
+ tmp[i][j] = this.getValue(i, j) / m.getValue(i,j);
+ }
+ }
+ return new Matrix(tmp);
+ }
+
+ /**
+ * calculate the root-mean-square for tow matrices
+ * @param m ~ other matrix
+ *
+ * @return
+ */
+ @Override
+ public double rmsd(MatrixI m)
+ {
+ MatrixI squaredDeviates = this.subtract(m);
+ squaredDeviates = squaredDeviates.preMultiply(squaredDeviates);
+ return Math.sqrt(squaredDeviates.mean());
+ }
+
+ /**
+ * calculates the Frobenius norm of this matrix
+ *
+ * @return
+ */
+ @Override
+ public double norm()
+ {
+ double result = 0;
+ for (double[] row : value)
+ {
+ for (double val : row)
+ {
+ result += Math.pow(val, 2);
+ }
+ }
+ return Math.sqrt(result);
+ }
+
+ /**
+ * returns the sum of all values in this matrix
+ *
+ * @return
+ */
+ @Override
+ public double sum()
+ {
+ double sum = 0;
+ for (double[] row : value)
+ {
+ for (double val : row)
+ {
+ sum += (Double.isNaN(val)) ? 0.0 : val;
+ }
+ }
+ return sum;
+ }
+
+ /**
+ * returns the sum-product of this matrix with vector v
+ * @param v ~ vector
+ *
+ * @return
+ */
+ @Override
+ public double[] sumProduct(double[] v)
+ {
+ if (v.length != cols)
+ {
+ throw new IllegalArgumentException("Vector and matrix do not have the same dimension! (" + v.length + " != " + cols + ")");
+ }
+ double[] result = new double[rows];
+ for (int i = 0; i < rows; i++)
+ {
+ double[] row = value[i];
+ double sum = 0;
+ for (int j = 0; j < row.length; j++)
+ {
+ sum += row[j] * v[j];
+ }
+ result[i] = sum;
+ }
+ return result;
+ }
+
+ /**
+ * mirrors columns of the matrix
+ *
+ * @return
+ */
+ @Override
+ public MatrixI mirrorCol()
+ {
+ double[][] result = new double[rows][cols];
+ for (int i = 0; i < rows; i++)
+ {
+ int k = cols - 1; // reverse col
+ for (int j = 0; j < cols; j++)
+ {
+ result[i][k--] = this.getValue(i,j);
+ }
+ }
+ MatrixI resultMatrix = new Matrix(result);
+ if (d != null)
+ resultMatrix.setD(d);
+ if (e != null)
+ resultMatrix.setE(e);
+
+ return resultMatrix;
+ }
}