/*
* Encog(tm) Core v3.3 - Java Version
* http://www.heatonresearch.com/encog/
* https://github.com/encog/encog-java-core
* Copyright 2008-2014 Heaton Research, Inc.
*
* 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.
*
* For more information on Heaton Research copyrights, licenses
* and trademarks visit:
* http://www.heatonresearch.com/copyright
*/
package org.encog.mathutil.matrices;
import java.io.Serializable;
import org.encog.Encog;
import org.encog.mathutil.matrices.decomposition.LUDecomposition;
import org.encog.mathutil.matrices.decomposition.QRDecomposition;
import org.encog.mathutil.randomize.RangeRandomizer;
/**
* This class implements a mathematical matrix. Matrix math is very important to
* neural network processing. Many of the neural network classes make use of the
* matrix classes in this package.
*/
public class Matrix implements Cloneable, Serializable {
/**
* Serial id for this class.
*/
private static final long serialVersionUID = -7977897210426471675L;
/**
* Turn an array of doubles into a column matrix.
*
* @param input
* A double array.
* @return A column matrix.
*/
public static Matrix createColumnMatrix(final double[] input) {
final double[][] d = new double[input.length][1];
for (int row = 0; row < d.length; row++) {
d[row][0] = input[row];
}
return new Matrix(d);
}
/**
* Turn an array of doubles into a row matrix.
*
* @param input
* A double array.
* @return A row matrix.
*/
public static Matrix createRowMatrix(final double[] input) {
final double[][] d = new double[1][input.length];
System.arraycopy(input, 0, d[0], 0, input.length);
return new Matrix(d);
}
/**
* The matrix data.
*/
private final double[][] matrix;
/**
* Construct a bipolar matrix from an array of booleans.
*
* @param sourceMatrix
* The booleans to create the matrix from.
*/
public Matrix(final boolean[][] sourceMatrix) {
this.matrix = new double[sourceMatrix.length][sourceMatrix[0].length];
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
if (sourceMatrix[r][c]) {
set(r, c, 1);
} else {
set(r, c, -1);
}
}
}
}
/**
* Create a matrix from an array of doubles.
*
* @param sourceMatrix
* An array of doubles.
*/
public Matrix(final double[][] sourceMatrix) {
this.matrix = new double[sourceMatrix.length][sourceMatrix[0].length];
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
set(r, c, sourceMatrix[r][c]);
}
}
}
/**
* Create a blank array with the specified number of rows and columns.
*
* @param rows
* How many rows in the matrix.
* @param cols
* How many columns in the matrix.
*/
public Matrix(final int rows, final int cols) {
this.matrix = new double[rows][cols];
}
/**
* Add a value to one cell in the matrix.
*
* @param row
* The row to add to.
* @param col
* The column to add to.
* @param value
* The value to add to the matrix.
*/
public void add(final int row, final int col, final double value) {
validate(row, col);
final double newValue = this.matrix[row][col] + value;
set(row, col, newValue);
}
/**
* Add the specified matrix to this matrix. This will modify the matrix to
* hold the result of the addition.
*
* @param theMatrix
* The matrix to add.
*/
public void add(final Matrix theMatrix) {
final double[][] source = theMatrix.getData();
for (int row = 0; row < getRows(); row++) {
for (int col = 0; col < getCols(); col++) {
this.matrix[row][col] += source[row][col];
}
}
}
/**
* Set all rows and columns to zero.
*/
public void clear() {
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
this.matrix[r][c] = 0;
}
}
}
/**
* Create a copy of the matrix.
*
* @return A colne of the matrix.
*/
@Override
public Matrix clone() {
return new Matrix(this.matrix);
}
/**
* Compare to matrixes with the specified level of precision.
*
* @param theMatrix
* The other matrix to compare to.
* @param precision
* How much precision to use.
* @return True if the two matrixes are equal.
*/
public boolean equals(final Matrix theMatrix, final int precision) {
if (precision < 0) {
throw new MatrixError("Precision can't be a negative number.");
}
final double test = Math.pow(10.0, precision);
if (Double.isInfinite(test) || (test > Long.MAX_VALUE)) {
throw new MatrixError("Precision of " + precision
+ " decimal places is not supported.");
}
final int actualPrecision = (int) Math.pow(Encog.DEFAULT_PRECISION,
precision);
final double[][] data = theMatrix.getData();
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
if ((long) (this.matrix[r][c] * actualPrecision)
!= (long) (data[r][c] * actualPrecision)) {
return false;
}
}
}
return true;
}
/**
* Check to see if this matrix equals another, using default precision.
*
* @param other
* The other matrix to compare.
* @return True if the two matrixes are equal.
*/
@Override
public boolean equals(final Object other) {
if (other == null) return false;
if (other == this) return true;
if (!(other instanceof Matrix))return false;
Matrix otherMyClass = (Matrix)other;
return equals(otherMyClass, Encog.DEFAULT_PRECISION);
}
/**
* Create a matrix from a packed array.
*
* @param array
* The packed array.
* @param index
* Where to start in the packed array.
* @return The new index after this matrix has been read.
*/
public int fromPackedArray(final double[] array, final int index) {
int i = index;
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
this.matrix[r][c] = array[i++];
}
}
return i;
}
/**
* Read the specified cell in the matrix.
*
* @param row
* The row to read.
* @param col
* The column to read.
* @return The value at the specified row and column.
*/
public double get(final int row, final int col) {
validate(row, col);
return this.matrix[row][col];
}
/**
* @return A COPY of this matrix as a 2d array.
*/
public double[][] getArrayCopy() {
final double[][] result = new double[getRows()][getCols()];
for (int i = 0; i < getRows(); i++) {
for (int j = 0; j < getCols(); j++) {
result[i][j] = this.matrix[i][j];
}
}
return result;
}
/**
* Read one entire column from the matrix as a sub-matrix.
*
* @param col
* The column to read.
* @return The column as a sub-matrix.
*/
public Matrix getCol(final int col) {
if (col > getCols()) {
throw new MatrixError("Can't get column #" + col
+ " because it does not exist.");
}
final double[][] newMatrix = new double[getRows()][1];
for (int row = 0; row < getRows(); row++) {
newMatrix[row][0] = this.matrix[row][col];
}
return new Matrix(newMatrix);
}
/**
* Get the columns in the matrix.
*
* @return The number of columns in the matrix.
*/
public int getCols() {
return this.matrix[0].length;
}
/**
* @return Get the 2D matrix array.
*/
public double[][] getData() {
return this.matrix;
}
/**
* Get a submatrix.
*
* @param i0
* Initial row index.
* @param i1
* Final row index.
* @param j0
* Initial column index.
* @param j1
* Final column index.
* @return The specified submatrix.
*/
public Matrix getMatrix(final int i0, final int i1, final int j0,
final int j1) {
final Matrix result = new Matrix(i1 - i0 + 1, j1 - j0 + 1);
final double[][] b = result.getData();
try {
for (int i = i0; i <= i1; i++) {
for (int j = j0; j <= j1; j++) {
b[i - i0][j - j0] = this.matrix[i][j];
}
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new MatrixError("Submatrix indices");
}
return result;
}
/**
* Get a submatrix.
*
* @param i0
* Initial row index.
* @param i1
* Final row index.
* @param c
* Array of column indices.
* @return The specified submatrix.
*/
public Matrix getMatrix(final int i0, final int i1, final int[] c) {
final Matrix result = new Matrix(i1 - i0 + 1, c.length);
final double[][] b = result.getData();
try {
for (int i = i0; i <= i1; i++) {
for (int j = 0; j < c.length; j++) {
b[i - i0][j] = this.matrix[i][c[j]];
}
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new MatrixError("Submatrix indices");
}
return result;
}
/**
* Get a submatrix.
*
* @param r
* Array of row indices.
* @param j0
* Initial column index
* @param j1
* Final column index
* @return The specified submatrix.
*/
public Matrix getMatrix(final int[] r, final int j0, final int j1) {
final Matrix result = new Matrix(r.length, j1 - j0 + 1);
final double[][] b = result.getData();
try {
for (int i = 0; i < r.length; i++) {
for (int j = j0; j <= j1; j++) {
b[i][j - j0] = this.matrix[r[i]][j];
}
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new ArrayIndexOutOfBoundsException("Submatrix indices");
}
return result;
}
/**
* Get a submatrix.
*
* @param r
* Array of row indices.
* @param c
* Array of column indices.
* @return The specified submatrix.
*/
public Matrix getMatrix(final int[] r, final int[] c) {
final Matrix result = new Matrix(r.length, c.length);
final double[][] b = result.getData();
try {
for (int i = 0; i < r.length; i++) {
for (int j = 0; j < c.length; j++) {
b[i][j] = this.matrix[r[i]][c[j]];
}
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new MatrixError("Submatrix indices");
}
return result;
}
/**
* Get the specified row as a sub-matrix.
*
* @param row
* The row to get.
* @return A matrix.
*/
public Matrix getRow(final int row) {
if (row > getRows()) {
throw new MatrixError("Can't get row #" + row
+ " because it does not exist.");
}
final double[][] newMatrix = new double[1][getCols()];
for (int col = 0; col < getCols(); col++) {
newMatrix[0][col] = this.matrix[row][col];
}
return new Matrix(newMatrix);
}
/**
* Get the number of rows in the matrix.
*
* @return The number of rows in the matrix.
*/
public int getRows() {
return this.matrix.length;
}
/**
* Compute a hash code for this matrix.
*
* @return The hash code.
*/
@Override
public int hashCode() {
long result = 0;
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
result += this.matrix[r][c];
}
}
return (int) (result % Integer.MAX_VALUE);
}
/**
* @return The matrix inverted.
*/
public Matrix inverse() {
return solve(MatrixMath.identity(getRows()));
}
/**
* Determine if the matrix is a vector. A vector is has either a single
* number of rows or columns.
*
* @return True if this matrix is a vector.
*/
public boolean isVector() {
if (getRows() == 1) {
return true;
}
return getCols() == 1;
}
/**
* Return true if every value in the matrix is zero.
*
* @return True if the matrix is all zeros.
*/
public boolean isZero() {
for (int row = 0; row < getRows(); row++) {
for (int col = 0; col < getCols(); col++) {
if (this.matrix[row][col] != 0) {
return false;
}
}
}
return true;
}
/**
* Multiply every value in the matrix by the specified value.
*
* @param value
* The value to multiply the matrix by.
*/
public void multiply(final double value) {
for (int row = 0; row < getRows(); row++) {
for (int col = 0; col < getCols(); col++) {
this.matrix[row][col] *= value;
}
}
}
/**
* Multiply every row by the specified vector.
*
* @param vector
* The vector to multiply by.
* @param result
* The result to hold the values.
*/
public void multiply(final double[] vector, final double[] result) {
for (int i = 0; i < getRows(); i++) {
result[i] = 0;
for (int j = 0; j < getCols(); j++) {
result[i] += this.matrix[i][j] * vector[j];
}
}
}
/**
* Randomize the matrix.
* @param min Minimum random value.
* @param max Maximum random value.
*/
public void randomize(final double min, final double max) {
for (int row = 0; row < getRows(); row++) {
for (int col = 0; col < getCols(); col++) {
this.matrix[row][col] = RangeRandomizer.randomize(min, max);
}
}
}
/**
* Set every value in the matrix to the specified value.
*
* @param value
* The value to set the matrix to.
*/
public void set(final double value) {
for (int row = 0; row < getRows(); row++) {
for (int col = 0; col < getCols(); col++) {
this.matrix[row][col] = value;
}
}
}
/**
* Set an individual cell in the matrix to the specified value.
*
* @param row
* The row to set.
* @param col
* The column to set.
* @param value
* The value to be set.
*/
public void set(final int row, final int col, final double value) {
validate(row, col);
this.matrix[row][col] = value;
}
/**
* Set this matrix's values to that of another matrix.
*
* @param theMatrix
* The other matrix.
*/
public void set(final Matrix theMatrix) {
final double[][] source = theMatrix.getData();
for (int row = 0; row < getRows(); row++) {
for (int col = 0; col < getCols(); col++) {
this.matrix[row][col] = source[row][col];
}
}
}
/**
* Set a submatrix.
*
* @param i0
* Initial row index
* @param i1
* Final row index
* @param j0
* Initial column index
* @param j1
* Final column index
* @param x
* A(i0:i1,j0:j1)
*
*/
public void setMatrix(final int i0, final int i1, final int j0,
final int j1, final Matrix x) {
try {
for (int i = i0; i <= i1; i++) {
for (int j = j0; j <= j1; j++) {
this.matrix[i][j] = x.get(i - i0, j - j0);
}
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new MatrixError("Submatrix indices");
}
}
/**
* Set a submatrix.
*
* @param i0
* Initial row index
* @param i1
* Final row index
* @param c
* Array of column indices.
* @param x
* The submatrix.
*/
public void setMatrix(final int i0, final int i1, final int[] c,
final Matrix x) {
try {
for (int i = i0; i <= i1; i++) {
for (int j = 0; j < c.length; j++) {
this.matrix[i][c[j]] = x.get(i - i0, j);
}
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new ArrayIndexOutOfBoundsException("Submatrix indices");
}
}
/**
* Set a submatrix.
*
* @param r
* Array of row indices.
* @param j0
* Initial column index
* @param j1
* Final column index
* @param x
* A(r(:),j0:j1)
*/
public void setMatrix(final int[] r, final int j0, final int j1,
final Matrix x) {
try {
for (int i = 0; i < r.length; i++) {
for (int j = j0; j <= j1; j++) {
this.matrix[r[i]][j] = x.get(i, j - j0);
}
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new ArrayIndexOutOfBoundsException("Submatrix indices");
}
}
/**
* Set a submatrix.
*
* @param r
* Array of row indices.
* @param c
* Array of column indices.
* @param x
* The matrix to set.
*/
public void setMatrix(final int[] r, final int[] c, final Matrix x) {
try {
for (int i = 0; i < r.length; i++) {
for (int j = 0; j < c.length; j++) {
this.matrix[r[i]][c[j]] = x.get(i, j);
}
}
} catch (final ArrayIndexOutOfBoundsException e) {
throw new MatrixError("Submatrix indices");
}
}
/**
* Get the size of the array. This is the number of elements it would take
* to store the matrix as a packed array.
*
* @return The size of the matrix.
*/
public int size() {
return this.matrix[0].length * this.matrix.length;
}
/**
* Solve A*X = B.
*
* @param b
* right hand side.
* @return Solution if A is square, least squares solution otherwise.
*/
public Matrix solve(final Matrix b) {
if (getRows() == getCols()) {
return (new LUDecomposition(this)).solve(b);
} else {
return (new QRDecomposition(this)).solve(b);
}
}
/**
* Sum all of the values in the matrix.
*
* @return The sum of the matrix.
*/
public double sum() {
double result = 0;
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
result += this.matrix[r][c];
}
}
return result;
}
/**
* Convert the matrix into a packed array.
*
* @return The matrix as a packed array.
*/
public double[] toPackedArray() {
final double[] result = new double[getRows() * getCols()];
int index = 0;
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
result[index++] = this.matrix[r][c];
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
final StringBuilder result = new StringBuilder();
result.append("[Matrix: rows=");
result.append(getRows());
result.append(",cols=");
result.append(getCols());
result.append("]");
return result.toString();
}
/**
* Validate that the specified row and column are within the required
* ranges. Otherwise throw a MatrixError exception.
*
* @param row
* The row to check.
* @param col
* The column to check.
*/
private void validate(final int row, final int col) {
if ((row >= getRows()) || (row < 0)) {
final String str = "The row:" + row + " is out of range:"
+ getRows();
throw new MatrixError(str);
}
if ((col >= getCols()) || (col < 0)) {
final String str = "The col:" + col + " is out of range:"
+ getCols();
throw new MatrixError(str);
}
}
public boolean isSquare() {
return getRows()==getCols();
}
}