smile.math.matrix.Matrix Maven / Gradle / Ivy
/******************************************************************************
* Confidential Proprietary *
* (c) Copyright Haifeng Li 2011, All Rights Reserved *
******************************************************************************/
package smile.math.matrix;
import smile.math.Math;
/**
* A matrix is a rectangular array of numbers. An item in a matrix is called
* an entry or an element. Entries are often denoted by a variable with two
* subscripts. Matrices of the same size can be added and subtracted entrywise
* and matrices of compatible size can be multiplied. These operations have
* many of the properties of ordinary arithmetic, except that matrix
* multiplication is not commutative, that is, AB and BA are not equal in
* general.
*
* Matrices are a key tool in linear algebra. One use of matrices is to
* represent linear transformations and matrix multiplication corresponds
* to composition of linear transformations. Matrices can also keep track of
* the coefficients in a system of linear equations. For a square matrix,
* the determinant and inverse matrix (when it exists) govern the behavior
* of solutions to the corresponding system of linear equations, and
* eigenvalues and eigenvectors provide insight into the geometry of
* the associated linear transformation.
*
* There are several methods to render matrices into a more easily accessible
* form. They are generally referred to as matrix transformation or matrix
* decomposition techniques. The interest of all these decomposition techniques
* is that they preserve certain properties of the matrices in question, such
* as determinant, rank or inverse, so that these quantities can be calculated
* after applying the transformation, or that certain matrix operations are
* algorithmically easier to carry out for some types of matrices.
*
* The LU decomposition factors matrices as a product of lower (L) and an upper
* triangular matrices (U). Once this decomposition is calculated, linear
* systems can be solved more efficiently, by a simple technique called
* forward and back substitution. Likewise, inverses of triangular matrices
* are algorithmically easier to calculate. The QR decomposition factors matrices
* as a product of an orthogonal (Q) and a right triangular matrix (R). QR decomposition
* is often used to solve the linear least squares problem, and is the basis for
* a particular eigenvalue algorithm, the QR algorithm. Singular value decomposition
* expresses any matrix A as a product UDV', where U and V are unitary matrices
* and D is a diagonal matrix. The eigendecomposition or diagonalization
* expresses A as a product VDV-1, where D is a diagonal matrix and
* V is a suitable invertible matrix. If A can be written in this form, it is
* called diagonalizable.
*
* @author Haifeng Li
*/
public class Matrix implements IMatrix {
/**
* The original matrix.
*/
private double[][] A;
/**
* True if the matrix is symmetric.
*/
private boolean symmetric = false;
/**
* True if the matrix is positive definite.
*/
private boolean positive = false;
/**
* The LU decomposition of A.
*/
private CholeskyDecomposition cholesky;
/**
* The LU decomposition of A.
*/
private LUDecomposition lu;
/**
* The LU decomposition of A.
*/
private QRDecomposition qr;
/**
* The SVD decomposition of A.
*/
private SingularValueDecomposition svd;
/**
* The eigen decomposition of A.
*/
private EigenValueDecomposition eigen;
/**
* Matrix determinant.
*/
private double det;
/**
* The rank of Matrix
*/
private int rank;
/**
* Constructor. Note that this is a shallow copy of A.
* @param A the array of matrix.
*/
public Matrix(double[][] A) {
this.A = A;
}
/**
* Constructor. Note that this is a shallow copy of A.
* @param A the array of matrix.
* @param symmetric true if the matrix is symmetric.
*/
public Matrix(double[][] A, boolean symmetric) {
if (symmetric && A.length != A[0].length) {
throw new IllegalArgumentException("A is not square");
}
this.A = A;
this.symmetric = symmetric;
}
/**
* Constructor. Note that this is a shallow copy of A.
* @param A the array of matrix.
* @param symmetric true if the matrix is symmetric.
* @param positive true if the matrix is positive definite.
*/
public Matrix(double[][] A, boolean symmetric, boolean positive) {
if (symmetric && A.length != A[0].length) {
throw new IllegalArgumentException("A is not square");
}
this.A = A;
this.symmetric = symmetric;
this.positive = positive;
}
@Override
public int nrows() {
return A.length;
}
@Override
public int ncols() {
return A[0].length;
}
@Override
public double get(int i, int j) {
return A[i][j];
}
@Override
public void set(int i, int j, double x) {
A[i][j] = x;
}
@Override
public void ax(double[] x, double[] y) {
Math.ax(A, x, y);
}
@Override
public void axpy(double[] x, double[] y) {
Math.axpy(A, x, y);
}
@Override
public void axpy(double[] x, double[] y, double b) {
Math.axpy(A, x, y, b);
}
@Override
public void atx(double[] x, double[] y) {
Math.atx(A, x, y);
}
@Override
public void atxpy(double[] x, double[] y) {
Math.atxpy(A, x, y);
}
@Override
public void atxpy(double[] x, double[] y, double b) {
Math.atxpy(A, x, y, b);
}
@Override
public void asolve(double[] b, double[] x) {
int m = A.length;
int n = A[0].length;
if (m != n) {
throw new IllegalStateException("Matrix is not square.");
}
for (int i = 0; i < n; i++) {
x[i] = A[i][i] != 0.0 ? b[i] / A[i][i] : b[i];
}
}
/**
* Returns the matrix transpose.
*/
public Matrix transpose() {
int m = A.length;
int n = A[0].length;
double[][] B = new double[n][m];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
B[j][i] = A[i][j];
}
}
return new Matrix(B);
}
/**
* Returns the matrix inverse or pseudo inverse.
* @return inverse of A if A is square, pseudo inverse otherwise.
*/
public double[][] inverse() {
return solve(Math.eye(ncols(), nrows()));
}
/**
* Returns the matrix trace. The sum of the diagonal elements.
*/
public double trace() {
int n = Math.min(A.length, A[0].length);
double t = 0.0;
for (int i = 0; i < n; i++) {
t += A[i][i];
}
return t;
}
/**
* Returns the matrix determinant.
*/
public double det() {
if (A.length != A[0].length) {
throw new IllegalArgumentException(String.format("Matrix is not square: %d x %d", A.length, A[0].length));
}
if (symmetric && positive) {
if (cholesky == null) {
cholesky();
}
} else {
if (lu == null) {
LU();
}
}
return det;
}
/**
* Returns the matrix rank.
* @return effective numerical rank.
*/
public int rank() {
svd();
return rank;
}
/**
* Returns the largest eigen pair of matrix with the power iteration
* under the assumptions A has an eigenvalue that is strictly greater
* in magnitude than its other eigenvalues and the starting
* vector has a nonzero component in the direction of an eigenvector
* associated with the dominant eigenvalue.
* @param v on input, it is the non-zero initial guess of the eigen vector.
* On output, it is the eigen vector corresponding largest eigen value.
* @return the largest eigen value.
*/
public double eigen(double[] v) {
if (nrows() != ncols()) {
throw new UnsupportedOperationException("The matrix is not square.");
}
return EigenValueDecomposition.eigen(this, v);
}
/**
* Returns the eigen value decomposition.
*/
public EigenValueDecomposition eigen() {
if (nrows() != ncols()) {
throw new UnsupportedOperationException("The matrix is not square.");
}
int n = A.length;
if (eigen == null || eigen.getEigenVectors().length != n) {
double[][] V = new double[n][n];
for (int i = 0; i < n; i++) {
System.arraycopy(A[i], 0, V[i], 0, n);
}
eigen = EigenValueDecomposition.decompose(V, symmetric);
positive = true;
for (int i = 0; i < n; i++) {
if (eigen.getEigenValues()[i] <= 0) {
positive = false;
break;
}
}
}
return eigen;
}
/**
* Returns the k largest eigen pairs. Only works for symmetric matrix.
*/
public EigenValueDecomposition eigen(int k) {
if (nrows() != ncols()) {
throw new UnsupportedOperationException("The matrix is not square.");
}
if (!symmetric) {
throw new UnsupportedOperationException("The current implementation of eigen value decomposition only works for symmetric matrices");
}
if (eigen == null || eigen.getEigenVectors().length != k) {
eigen = EigenValueDecomposition.decompose(this, k);
}
return eigen;
}
/**
* Returns the singular value decomposition.
*/
public SingularValueDecomposition svd() {
if (svd == null) {
int m = A.length;
int n = A[0].length;
double[][] V = new double[m][n];
for (int i = 0; i < m; i++) {
System.arraycopy(A[i], 0, V[i], 0, n);
}
svd = SingularValueDecomposition.decompose(V);
rank = svd.rank();
}
return svd;
}
/**
* Returns the LU decomposition.
*/
public LUDecomposition LU() {
if (nrows() != ncols()) {
throw new UnsupportedOperationException("The matrix is not square.");
}
if (lu == null) {
lu = new LUDecomposition(A);
det = lu.det();
}
return lu;
}
/**
* Returns the Cholesky decomposition.
*/
public CholeskyDecomposition cholesky() {
if (nrows() != ncols()) {
throw new UnsupportedOperationException("The matrix is not square.");
}
if (!symmetric || !positive) {
throw new UnsupportedOperationException("The matrix is not symmetric positive definite.");
}
if (cholesky == null) {
cholesky = new CholeskyDecomposition(A);
det = cholesky.det();
}
return cholesky;
}
/**
* Returns the QR decomposition.
*/
public QRDecomposition QR() {
if (qr == null) {
qr = new QRDecomposition(A);
}
return qr;
}
/**
* Solve A*x = b in place (exact solution if A is square, least squares
* solution otherwise), which means the results will be stored in b.
* @return the solution matrix, actually b.
*/
public double[] solve(double[] b) {
if (A.length == A[0].length) {
if (symmetric && positive) {
cholesky().solve(b);
} else {
LU().solve(b);
}
} else {
QR().solve(b);
}
return b;
}
/**
* Solve A*X = B in place (exact solution if A is square, least squares
* solution otherwise), which means the results will be stored in B.
* @return the solution matrix, actually B.
*/
public double[][] solve(double[][] B) {
if (A.length == A[0].length) {
if (symmetric && positive) {
cholesky().solve(B);
} else {
LU().solve(B);
}
} else {
QR().solve(B);
}
return B;
}
/**
* Iteratively improve a solution to linear equations.
*
* @param b right hand side of linear equations.
* @param x a solution to linear equations.
*/
public void improve(double[] b, double[] x) {
int n = A.length;
if (A.length != A[0].length) {
throw new IllegalStateException("A is not square.");
}
if (x.length != n || b.length != n) {
throw new IllegalArgumentException(String.format("Row dimensions do not agree: A is %d x %d, but b is %d x 1 and x is %d x 1", A.length, A[0].length, b.length, x.length));
}
if (symmetric && positive) {
// Calculate the right-hand side, accumulating the residual
// in higher precision.
double[] r = new double[n];
for (int i = 0; i < n; i++) {
double sdp = -b[i];
for (int j = 0; j < n; j++) {
sdp += A[i][j] * x[j];
}
r[i] = sdp;
}
// Solve for the error term.
cholesky.solve(r);
// Subtract the error from the old soluiton.
for (int i = 0; i < n; i++) {
x[i] -= r[i];
}
} else {
// Calculate the right-hand side, accumulating the residual
// in higher precision.
double[] r = new double[n];
for (int i = 0; i < n; i++) {
double sdp = -b[i];
for (int j = 0; j < n; j++) {
sdp += A[i][j] * x[j];
}
r[i] = sdp;
}
// Solve for the error term.
lu.solve(r);
// Subtract the error from the old soluiton.
for (int i = 0; i < n; i++) {
x[i] -= r[i];
}
}
}
}